在视觉和语言领域的深度学习方面取得了很多进展,文中一步步说明当我们处理音频数据时,使用了哪些类型的模型和流程。
作者 / Dimitre Oliveira
原文链接 / https://pub.towardsai.net/a-gentle-introduction-to-audio-classification-with-tensorflow-c469cb0be6f5
图片来源: https://www.tensorflow.org/tutorials/audio/simple_audio
最近在视觉和语言领域的深度学习方面取得了很多进展,能很直观地理解为什么CNN在图像上表现得很好,因为像素的局部相关,以及因为它具有顺序性,像RNN或转化器这样的顺序模型在语言上也表现得非常好。但音频呢?当我们处理音频数据时,使用了哪些类型的模型和流程?
在本文中,你将学习如何处理一个简单的音频分类问题。你将学习到一些常用的、有效的方法,以及Tensorflow代码来实现。
声明:本文给出的代码是基于我为“Rainforest Connection Species Audio Detection”Kaggle比赛开发的工作,但出于演示目的,我将使用“Speech Commands(语音指令)”数据集。
波形图
我们通常有".wav "格式的音频文件,它们通常被称为 waveforms(波形),它是一个时间序列,其中有每个特定时间的信号振幅,如果我们将这些波形样本之一可视化,会得到下图这样:
直觉上人们可能会考虑使用某种RNN模型对这些数据建模为一个常规时间序列(例如股票价格预测),事实上这可以做到,但由于我们使用的是音频信号,更合适的选择是将波形样本转化为声谱图。
声谱图
声谱图是波形信号的图像表示,它显示了其随时间变化的频率强度范围,它在想评估信号随时间变化的频率分布时非常有用。下图是上文中波形图像的声谱图表示。
x 轴是采样时间,y 轴是频率
语音命令用例
为了使本教程更简单,我们将使用“Speech Commands语音命令”数据集,该数据集有一秒钟的音频片段,带有 "下"、"走"、"左"、"不"、"右"、"停"、"上 "和 "是"等口语化的词语。
使用Tensorflow进行音频处理
现在我们已经知道了如何使用深度学习模型来处理音频数据,可以继续看代码实现,我们的流水线将遵循下图描述的简单工作流程:
简单的音频处理图
值得注意,在我们的用例的第1步,将数据直接从“. wav”文件中加载的,第3个步是可选的,因为音频文件每个只有一秒钟,因为文件较长裁剪音频可能是一个好主意,也是为了保持所有样本的固定长度。
加载数据
代码语言:javascript复制
代码语言:javascript复制def load_dataset(filenames):
dataset = tf.data.Dataset.from_tensor_slices(filenames)
return dataset
load_datasetv函数将负责加载.wav文件并将其转换为Tensorflow数据集。
提取波形和标签
代码语言:javascript复制
代码语言:javascript复制commands = np.array(tf.io.gfile.listdir(str(data_dir)))
commands = commands[commands != 'README.md']def decode_audio(audio_binary):
audio, _ = tf.audio.decode_wav(audio_binary)
return tf.squeeze(audio, axis=-1)def get_label(filename):
label = tf.strings.split(filename, os.path.sep)[-2]
label = tf.argmax(label == commands)
return labeldef get_waveform_and_label(filename):
label = get_label(filename)
audio_binary = tf.io.read_file(filename)
waveform = decode_audio(audio_binary)
return waveform, label
在加载.wav文件后,可以用tf.audio.decode_wav函数来对它们进行解码,它将把.wav文件变成float tensor。接下来,我们需要从文件中提取标签,在这个特定的用例中,我们可以从每个样本的文件路径中获取标签,之后只需要对它们进行一次编码。
一个例子
首先,我们得到一个像这样的文件路径:
代码语言:javascript复制"data/mini_speech_commands/up/50f55535_nohash_0.wav"
然后提取第二个"/"后面的文本,在这种情况下,标签是UP,最后使用commands列表对标签进行一次编码。
代码语言:javascript复制Commands: ['up' 'down' 'go' 'stop' 'left' 'no' 'yes' 'right']
Label = 'up'
After one-hot encoding:
Label = [1, 0, 0, 0, 0, 0, 0, 0]
将波形转换为声谱表
下一步是将波形文件转换为声谱图,幸运的是Tensorflow有一个函数可以做到这一点, tf.signal.stft应用短时Fourier变换(STFT)将音频转换为时频域,然后我们应用 tf.abs 算子去除信号相位,只保留幅值。注意,tf.signal.stft函数有一些参数,如frame_length 和frame_step,它们会影响生成的声谱图,我不会详细介绍如何调整它们,但你可以参考这个视频来了解更多。(:https://www.coursera.org/lecture/audio-signal-processing/stft-2-tjEQe)
代码语言:javascript复制
代码语言:javascript复制def get_spectrogram(waveform, padding=False, min_padding=48000):
waveform = tf.cast(waveform, tf.float32)
spectrogram = tf.signal.stft(waveform, frame_length=2048, frame_step=512, fft_length=2048)
spectrogram = tf.abs(spectrogram)
return spectrogramdef get_spectrogram_tf(waveform, label):
spectrogram = get_spectrogram(waveform)
spectrogram = tf.expand_dims(spectrogram, axis=-1)
return spectrogram, label
将声谱图转化为RGB图像
最后一步是将声谱图转换为RGB图像,这一步是可选的,但这里我们将使用在ImageNet数据集上预训练的模型,该模型需要输入3个通道的图像。否则,你只可以保留一个通道的声谱图。
代码语言:javascript复制
代码语言:javascript复制def prepare_sample(spectrogram, label):
spectrogram = tf.image.resize(spectrogram, [HEIGHT, WIDTH])
spectrogram = tf.image.grayscale_to_rgb(spectrogram)
return spectrogram, label
将所有的东西结合起来
代码语言:javascript复制
代码语言:javascript复制HEIGHT, WIDTH = 128, 128
AUTO = tf.data.AUTOTUNEdef get_dataset(filenames, batch_size=32):
dataset = load_dataset(filenames)
dataset = files_ds.map(get_waveform_and_label, num_parallel_calls=AUTO)
dataset = dataset.map(get_spectrogram_tf, num_parallel_calls=AUTO)
dataset = dataset.map(prepare_sample, num_parallel_calls=AUTO) dataset = dataset.shuffle(256)
dataset = dataset.repeat()
dataset = dataset.batch(batch_size)
dataset = dataset.prefetch(AUTO)
return dataset
将所有东西集合在一起,有 get_dataset 函数将文件名作为输入,在执行了上面描述的所有步骤后,返回一个带有RGB光谱图图像及其标签的Tensorflow数据集。
模型
代码语言:javascript复制
代码语言:javascript复制def model_fn(input_shape, N_CLASSES):
inputs = L.Input(shape=input_shape, name='input_audio')
base_model = efn.EfficientNetB0(input_tensor=inputs,
include_top=False,
weights='imagenet')
x = L.GlobalAveragePooling2D()(base_model.output)
x = L.Dropout(.5)(x)
output = L.Dense(N_CLASSES, activation='softmax',name='output')(x)
model = Model(inputs=inputs, outputs=output)
return model
我们的模型将有一个EfficientNetB0主干,在其顶部添加了一个GlobalAveragePooling2D,然后是一个Dropout,最后一个Dense层将进行实际的多类分类。
对于一个小的数据集,EfficientNetB0可能是一个很好的基线,即使是一个快速而轻巧的模型,它也有不错的准确性。
训练
代码语言:javascript复制
代码语言:javascript复制model = model_fn((None, None, CHANNELS), N_CLASSES)model.compile(optimizer=tf.optimizers.Adam(),
loss=losses.CategoricalCrossentropy(),
metrics=[metrics.CategoricalAccuracy()])
model.fit(x=get_dataset(FILENAMES),
steps_per_epoch=100,
epochs=10)
训练代码对于Keras模型来说是非常标准的,所以你可能不会在这里找到任何新东西。
结论
现在你应该对将深度学习应用于音频文件的工作流程有了更清楚的了解,虽然这不是你能做到的唯一方法,但它是关于易用性和性能之间的权衡的最佳选择。如果你打算对音频进行建模,你可能还要考虑其他有前途的方法,如变压器。
作为额外的预处理步骤,截断或填充波形可能是一个好主意,如果你的样本有不同的长度,或者如果样本太长,只需要其中的一小部分,你可以在下面的参考资料部分找到如何做的代码。
参考文献
- Simple audio recognition: Recognizing keywords
https://www.tensorflow.org/tutorials/audio/simple_audio
- Rainforest-Audio classification Tensorflow starter
https://www.kaggle.com/dimitreoliveira/rainforest-audio-classification-tensorflow-starter
- Rainforest-Audio classification TF Improved
https://www.kaggle.com/dimitreoliveira/rainforest-audio-classification-tf-improved/notebook