赞
踩
(以下内容搬运自飞桨PaddleSpeech语音技术课程,点击链接可直接运行源码)
通过声音,人的大脑会获取到大量的信息,其中的一个场景是:识别和归类。如:识别熟悉的亲人或朋友的声音、识别不同乐器发出的声音、识别不同环境产生的声音,等等。
我们可以根据不同声音的特征(频率,音色等)进行区分,这种区分行为的本质,就是对声音进行分类。
音色:
声音是由发声的物体的振动产生的。当发声物体的主体振动时会发出一个基音,同时其余各部分也有复合的振动,这些振动组合产生泛音。正是这些泛音决定了发生物体的音色,使人能辨别出不同的乐器甚至不同的人发出的声音。所以根据音色的不同可以划分出男音和女音;高音、中音和低音;弦乐和管乐等。
声音分类根据用途还可以继续细分:
使用 PaddleSpeech 的预训练模型对一段音频做实时的声音检测,结果如下视频所示。
# 环境准备:安装 paddlespeech 和 paddleaudio
!pip install paddlespeech==1.2.0
!pip install paddleaudio==1.0.1
import warnings
warnings.filterwarnings("ignore")
import IPython
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
下面通过一个例子观察音频文件的波形,直观地了解数字音频文件的包含的内容。
# 获取示例音频
!test -f ./dog.wav || wget https://paddlespeech.bj.bcebos.com/PaddleAudio/dog.wav
IPython.display.Audio('./dog.wav')
from paddleaudio import load
data, sr = load(file='./dog.wav', mono=True, dtype='float32') # 单通道,float32音频样本点
print('wav shape: {}'.format(data.shape))
print('sample rate: {}'.format(sr))
# 展示音频波形
plt.figure()
plt.plot(data)
plt.show()
!paddlespeech cls --input ./dog.wav
!paddlespeech help
对于一段音频,一般会将整段音频进行分帧,每一帧含有一定长度的信号数据,一般使用 25ms
,帧与帧之间的移动距离称为帧移,一般使用 10ms
,然后对每一帧的信号数据加窗后,进行离散傅立叶变换(DFT)得到频谱图。
通过按照上面的对一段音频进行分帧后,我们可以用傅里叶变换来分析每一帧信号的频率特性。将每一帧的频率信息拼接后,可以获得该音频不同时刻的频率特征——Spectrogram,也称作为语谱图。
下面例子采用 paddle.signal.stft
演示如何提取示例音频的频谱特征,并进行可视化:
import paddle import numpy as np data, sr = load(file='./dog.wav', sr=32000, mono=True, dtype='float32') x = paddle.to_tensor(data) n_fft = 1024 win_length = 1024 hop_length = 320 # [D, T] spectrogram = paddle.signal.stft(x, n_fft=n_fft, win_length=win_length, hop_length=512, onesided=True) print('spectrogram.shape: {}'.format(spectrogram.shape)) print('spectrogram.dtype: {}'.format(spectrogram.dtype)) spec = np.log(np.abs(spectrogram.numpy())**2) plt.figure() plt.title("Log Power Spectrogram") plt.imshow(spec[:100, :], origin='lower') plt.show()
研究表明,人类对声音的感知是非线性的,随着声音频率的增加,人对更高频率的声音的区分度会不断下降。
例如同样是相差 500Hz 的频率,一般人可以轻松分辨出声音中 500Hz 和 1,000Hz 之间的差异,但是很难分辨出 10,000Hz 和 10,500Hz 之间的差异。
因此,学者提出了梅尔频率,在该频率计量方式下,人耳对相同数值的频率变化的感知程度是一样的。
关于梅尔频率的计算,其会对原始频率的低频的部分进行较多的采样,从而对应更多的频率,而对高频的声音进行较少的采样,从而对应较少的频率。使得人耳对梅尔频率的低频和高频的区分性一致。
Mel Fbank 的计算过程如下,而我们一般都是使用 LogFBank 作为识别特征:
下面例子采用 paddleaudio.features.LogMelSpectrogram
演示如何提取示例音频的 LogFBank:
from paddleaudio.features import LogMelSpectrogram f_min=50.0 f_max=14000.0 # - sr: 音频文件的采样率。 # - n_fft: FFT样本点个数。 # - hop_length: 音频帧之间的间隔。 # - win_length: 窗函数的长度。 # - window: 窗函数种类。 # - n_mels: 梅尔刻度数量。 feature_extractor = LogMelSpectrogram( sr=sr, n_fft=n_fft, hop_length=hop_length, win_length=win_length, window='hann', f_min=f_min, f_max=f_max, n_mels=64) x = paddle.to_tensor(data).unsqueeze(0) # [B, L] log_fbank = feature_extractor(x) # [B, D, T] log_fbank = log_fbank.squeeze(0) # [D, T] print('log_fbank.shape: {}'.format(log_fbank.shape)) plt.figure() plt.imshow(log_fbank.numpy(), origin='lower') plt.show()
在传统的声音和信号的研究领域中,声音特征是一类包含丰富先验知识的手工特征,如频谱图、梅尔频谱和梅尔频率倒谱系数等。
因此在一些分类的应用上,可以采用传统的机器学习方法例如决策树、svm和随机森林等方法。
一个典型的应用案例是:男声和女声分类。
传统机器学习方法可以捕捉声音特征的差异(例如男声和女声的声音在音高上往往差异较大)并实现分类任务。
而深度学习方法则可以突破特征的限制,更灵活的组网方式和更深的网络层次,可以更好地提取声音的高层特征,从而获得更好的分类指标。
随着深度学习算法的快速发展和在分类任务上的优异表现,当下流行的声音分类模型无一不是采用深度学习网络搭建而成的,如 AudioCLIP[1]、PANNs[2] 和 Audio Spectrogram Transformer[3] 等。
在声音分类和声音检测的场景中(如环境声音分类、情绪识别和音乐流派分类等)由于可获取的据集有限,且语音数据标注的成本高,用户可以收集到的数据集体量往往较小,这种数据量稀少的情况对于模型训练是非常不利的。
预训练模型能够减少领域数据的需求量,并达到较高的识别准确率。在CV和NLP领域中,有诸如 MobileNet、VGG19、YOLO、BERT 和 ERNIE 等开源的预训练模型,在图像检测、图像分类、文本分类和文本生成等各自领域内的任务中,使用预训练模型在下游任务的数据集上进行 finetune ,往往可以更快和更容易获得较好的效果和指标。
相较于 CV 领域的 ImageNet 数据集,谷歌在 2017 年开放了一个大规模的音频数据集 AudioSet[4],它是目前最大的用于音频分类任务的数据集。该数据集包含了 632 类的音频类别以及 2084320 条人工标记的每段 10 秒长度的声音剪辑片段(包括 527 个标签),数据总时长为 5,800 小时。
PANNs
(PANNs: Large-Scale Pretrained Audio Neural Networks for Audio Pattern Recognition[2])是基于 AudioSet 数据集训练的声音分类/识别的模型,其中PANNs-CNN14
在测试集上取得了较好的效果:mAP 为 0.431,AUC 为 0.973,d-prime 为 2.732,经过预训练后,该模型可以用于提取音频的 embbedding ,适合用于声音分类和声音检测等下游任务。本示例将使用 PANNs
的预训练模型 Finetune 完成声音分类的任务。
本教程选取 PANNs
中的预训练模型 cnn14
作为 backbone,用于提取声音的深层特征,SoundClassifer
创建下游的分类网络,实现对输入音频的分类。
此课程选取了ESC-50: Dataset for Environmental Sound Classification[5] 数据集作为示例。
ESC-50是一个包含有 2000 个带标签的环境声音样本,音频样本采样率为 44,100Hz 的单通道音频文件,所有样本根据标签被划分为 50 个类别,每个类别有 40 个样本。
音频样本可分为 5 个主要类别:
ESC-50 数据集中的提供的 meta/esc50.csv
文件包含的部分信息如下:
filename,fold,target,category,esc10,src_file,take
1-100038-A-14.wav,1,14,chirping_birds,False,100038,A
1-100210-A-36.wav,1,36,vacuum_cleaner,False,100210,A
1-101296-A-19.wav,1,19,thunderstorm,False,101296,A
...
在此声音分类的任务中,我们将target
作为训练过程的分类标签。
调用以下代码自动下载并读取数据集音频文件,创建训练集和验证集。
from paddleaudio.datasets import ESC50
train_ds = ESC50(mode='train', sample_rate=sr)
dev_ds = ESC50(mode='dev', sample_rate=sr)
通过下列代码,用 paddleaudio.features.LogMelSpectrogram
初始化一个音频特征提取器,在训练过程中实时提取音频的 LogFBank 特征,其中主要的参数如下:
feature_extractor = LogMelSpectrogram(
sr=sr,
n_fft=n_fft,
hop_length=hop_length,
win_length=win_length,
window='hann',
f_min=f_min,
f_max=f_max,
n_mels=64)
选取cnn14
作为 backbone,用于提取音频的特征:
from paddlespeech.cls.models import cnn14
backbone = cnn14(pretrained=True, extract_embedding=True)
SoundClassifer
接收cnn14
作为backbone模型,并创建下游的分类网络:
import paddle.nn as nn class SoundClassifier(nn.Layer): def __init__(self, backbone, num_class, dropout=0.1): super().__init__() self.backbone = backbone self.dropout = nn.Dropout(dropout) self.fc = nn.Linear(self.backbone.emb_size, num_class) def forward(self, x): x = x.unsqueeze(1) x = self.backbone(x) x = self.dropout(x) logits = self.fc(x) return logits model = SoundClassifier(backbone, num_class=len(ESC50.label_list))
batch_size = 16
train_loader = paddle.io.DataLoader(train_ds, batch_size=batch_size, shuffle=True)
dev_loader = paddle.io.DataLoader(dev_ds, batch_size=batch_size,)
optimizer = paddle.optimizer.Adam(learning_rate=1e-4, parameters=model.parameters())
criterion = paddle.nn.loss.CrossEntropyLoss()
from paddleaudio.utils import logger epochs = 20 steps_per_epoch = len(train_loader) log_freq = 10 eval_freq = 10 for epoch in range(1, epochs + 1): model.train() avg_loss = 0 num_corrects = 0 num_samples = 0 for batch_idx, batch in enumerate(train_loader): waveforms, labels = batch feats = feature_extractor(waveforms) feats = paddle.transpose(feats, [0, 2, 1]) # [B, N, T] -> [B, T, N] logits = model(feats) loss = criterion(logits, labels) loss.backward() optimizer.step() if isinstance(optimizer._learning_rate, paddle.optimizer.lr.LRScheduler): optimizer._learning_rate.step() optimizer.clear_grad() # Calculate loss avg_loss += loss.numpy()[0] # Calculate metrics preds = paddle.argmax(logits, axis=1) num_corrects += (preds == labels).numpy().sum() num_samples += feats.shape[0] if (batch_idx + 1) % log_freq == 0: lr = optimizer.get_lr() avg_loss /= log_freq avg_acc = num_corrects / num_samples print_msg = 'Epoch={}/{}, Step={}/{}'.format( epoch, epochs, batch_idx + 1, steps_per_epoch) print_msg += ' loss={:.4f}'.format(avg_loss) print_msg += ' acc={:.4f}'.format(avg_acc) print_msg += ' lr={:.6f}'.format(lr) logger.train(print_msg) avg_loss = 0 num_corrects = 0 num_samples = 0 if epoch % eval_freq == 0 and batch_idx + 1 == steps_per_epoch: model.eval() num_corrects = 0 num_samples = 0 with logger.processing('Evaluation on validation dataset'): for batch_idx, batch in enumerate(dev_loader): waveforms, labels = batch feats = feature_extractor(waveforms) feats = paddle.transpose(feats, [0, 2, 1]) logits = model(feats) preds = paddle.argmax(logits, axis=1) num_corrects += (preds == labels).numpy().sum() num_samples += feats.shape[0] print_msg = '[Evaluation result]' print_msg += ' dev_acc={:.4f}'.format(num_corrects / num_samples) logger.eval(print_msg)
执行预测,获取 Top K 分类结果:
top_k = 10 wav_file = './dog.wav' waveform, sr = load(wav_file, sr=sr) feature_extractor = LogMelSpectrogram( sr=sr, n_fft=n_fft, hop_length=hop_length, win_length=win_length, window='hann', f_min=f_min, f_max=f_max, n_mels=64) feats = feature_extractor(paddle.to_tensor(paddle.to_tensor(waveform).unsqueeze(0))) feats = paddle.transpose(feats, [0, 2, 1]) # [B, N, T] -> [B, T, N] logits = model(feats) probs = nn.functional.softmax(logits, axis=1).numpy() sorted_indices = probs[0].argsort() msg = f'[{wav_file}]\n' for idx in sorted_indices[-1:-top_k-1:-1]: msg += f'{ESC50.label_list[idx]}: {probs[0][idx]:.5f}\n' print(msg)
关于如何自定义分类数据集,请参考文档 PaddleSpeech/docs/source/cls/custom_dataset.md
请关注我们的 Github Repo,非常欢迎加入以下微信群参与讨论:
[1] Guzhov, A., Raue, F., Hees, J., & Dengel, A.R. (2021). AudioCLIP: Extending CLIP to Image, Text and Audio. ArXiv, abs/2106.13043.
[2] Kong, Q., Cao, Y., Iqbal, T., Wang, Y., Wang, W., & Plumbley, M.D. (2020). PANNs: Large-Scale Pretrained Audio Neural Networks for Audio Pattern Recognition. IEEE/ACM Transactions on Audio, Speech, and Language Processing, 28, 2880-2894.
[3] Gong, Y., Chung, Y., & Glass, J.R. (2021). AST: Audio Spectrogram Transformer. ArXiv, abs/2104.01778.
[4] Gemmeke, J.F., Ellis, D.P., Freedman, D., Jansen, A., Lawrence, W., Moore, R.C., Plakal, M., & Ritter, M. (2017). Audio Set: An ontology and human-labeled dataset for audio events. 2017 IEEE International Conference on Acoustics, Speech and Signal Processing (ICASSP), 776-780.
[5] Piczak, K.J. (2015). ESC: Dataset for Environmental Sound Classification. Proceedings of the 23rd ACM international conference on Multimedia.
P.S. 欢迎关注我们的 github repo PaddleSpeech, 是基于飞桨 PaddlePaddle 的语音方向的开源模型库,用于语音和音频中的各种关键任务的开发,包含大量基于深度学习前沿和有影响力的模型。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。