(以下内容搬运自飞桨PaddleSpeech语音技术课程,点击链接可直接运行源码)
1. 识别声音
通过声音,人的大脑会获取到大量的信息,其中的一个场景是:识别和归类。如:识别熟悉的亲人或朋友的声音、识别不同乐器发出的声音、识别不同环境产生的声音,等等。
我们可以根据不同声音的特征(频率,音色等)进行区分,这种区分行为的本质,就是对声音进行分类。
音色:
声音是由发声的物体的振动产生的。当发声物体的主体振动时会发出一个基音,同时其余各部分也有复合的振动,这些振动组合产生泛音。正是这些泛音决定了发生物体的音色,使人能辨别出不同的乐器甚至不同的人发出的声音。所以根据音色的不同可以划分出男音和女音;高音、中音和低音;弦乐和管乐等。
声音分类根据用途还可以继续细分:
- 副语言识别:说话人识别(Speaker Recognition), 情绪识别(Speech Emotion Recognition),性别分类(Speaker gender classification)
- 音乐识别:音乐流派分类(Music Genre Classification)
- 场景识别:环境声音分类(Environmental Sound Classification)
- 声音事件检测:各个环境中的声音事件和起始时间的检测
1.1 Audio Tagging
使用 PaddleSpeech 的预训练模型对一段音频做实时的声音检测,结果如下视频所示。
2. 音频和特征提取
# 环境准备:安装 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
2.1 数字音频
2.1.1 声音信号和音频文件
下面通过一个例子观察音频文件的波形,直观地了解数字音频文件的包含的内容。
# 获取示例音频
!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
2.2 音频特征提取
2.2.1 短时傅里叶变换
对于一段音频,一般会将整段音频进行分帧,每一帧含有一定长度的信号数据,一般使用 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()
2.2.2 LogFBank
研究表明,人类对声音的感知是非线性的,随着声音频率的增加,人对更高频率的声音的区分度会不断下降。
例如同样是相差 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()
2.3 声音分类方法
2.3.1 传统机器学习方法
在传统的声音和信号的研究领域中,声音特征是一类包含丰富先验知识的手工特征,如频谱图、梅尔频谱和梅尔频率倒谱系数等。
因此在一些分类的应用上,可以采用传统的机器学习方法例如决策树、svm和随机森林等方法。
一个典型的应用案例是:男声和女声分类。
2.3.2 深度学习方法
传统机器学习方法可以捕捉声音特征的差异(例如男声和女声的声音在音高上往往差异较大)并实现分类任务。
而深度学习方法则可以突破特征的限制,更灵活的组网方式和更深的网络层次,可以更好地提取声音的高层特征,从而获得更好的分类指标。
随着深度学习算法的快速发展和在分类任务上的优异表现,当下流行的声音分类模型无一不是采用深度学习网络搭建而成的,如 AudioCLIP[1]、PANNs[2] 和 Audio Spectrogram Transformer[3] 等。
2.3.3 Pretrain + Finetune
在声音分类和声音检测的场景中(如环境声音分类、情绪识别和音乐流派分类等)由于可获取的据集有限,且语音数据标注的成本高,用户可以收集到的数据集体量往往较小,这种数据量稀少的情况对于模型训练是非常不利的。
预训练模型能够减少领域数据的需求量,并达到较高的识别准确率。在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
创建下游的分类网络,实现对输入音频的分类。
3. 实践:环境声音分类
3.1 数据集准备
此课程选取了ESC-50: Dataset for Environmental Sound Classification[5] 数据集作为示例。
ESC-50是一个包含有 2000 个带标签的环境声音样本,音频样本采样率为 44,100Hz 的单通道音频文件,所有样本根据标签被划分为 50 个类别,每个类别有 40 个样本。
音频样本可分为 5 个主要类别:
- 动物声音(Animals)
- 自然界产生的声音和水声(Natural soundscapes & water sounds)
- 人类发出的非语言声音(Human, non-speech sounds)
- 室内声音(Interior/domestic sounds)
- 室外声音和一般噪声(Exterior/urban noises)。
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
...
- filename: 音频文件名字。
- fold: 数据集自身提供的N-Fold验证信息,用于切分训练集和验证集。
- target: 标签数值。
- category: 标签文本信息。
- esc10: 文件是否为ESC-10的数据集子集。
- src_file: 原始音频文件前缀。
- take: 原始文件的截取段落信息。
在此声音分类的任务中,我们将target
作为训练过程的分类标签。
3.1.1 数据集初始化
调用以下代码自动下载并读取数据集音频文件,创建训练集和验证集。
from paddleaudio.datasets import ESC50
train_ds = ESC50(mode='train', sample_rate=sr)
dev_ds = ESC50(mode='dev', sample_rate=sr)
3.1.2 特征提取
通过下列代码,用 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)
3.2 模型
3.2.1 选取预训练模型
选取cnn14
作为 backbone,用于提取音频的特征:
from paddlespeech.cls.models import cnn14
backbone = cnn14(pretrained=True, extract_embedding=True)
3.2.2 构建分类模型
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))
3.3 Finetune
- 创建 DataLoader
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,)
- 定义优化器和 Loss
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)
3.4 音频预测
执行预测,获取 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)
4. 作业
- 使用开发模式安装 PaddleSpeech
环境要求:docker, Ubuntu 16.04,root user。
参考安装方法:使用Docker安装paddlespeech - 在 MusicSpeech 数据集上完成 music/speech 二分类。
- 在 GTZAN Genre Collection 音乐分类数据集上利用 PANNs 预训练模型实现音乐类别十分类。
关于如何自定义分类数据集,请参考文档 PaddleSpeech/docs/source/cls/custom_dataset.md
5. 关注 PaddleSpeech
请关注我们的 Github Repo,非常欢迎加入以下微信群参与讨论:
- 扫描二维码
- 添加运营小姐姐微信
- 通过后回复【语音】
- 系统自动邀请加入技术群
6. 参考文献
[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 的语音方向的开源模型库,用于语音和音频中的各种关键任务的开发,包含大量基于深度学习前沿和有影响力的模型。