【飞桨PaddleSpeech语音技术课程】— 声音分类

(以下内容搬运自飞桨PaddleSpeech语音技术课程,点击链接可直接运行源码)

1. 识别声音

通过声音,人的大脑会获取到大量的信息,其中的一个场景是:识别和归类。如:识别熟悉的亲人或朋友的声音、识别不同乐器发出的声音、识别不同环境产生的声音,等等。

我们可以根据不同声音的特征(频率,音色等)进行区分,这种区分行为的本质,就是对声音进行分类。

音色:
声音是由发声的物体的振动产生的。当发声物体的主体振动时会发出一个基音,同时其余各部分也有复合的振动,这些振动组合产生泛音。正是这些泛音决定了发生物体的音色,使人能辨别出不同的乐器甚至不同的人发出的声音。所以根据音色的不同可以划分出男音和女音;高音、中音和低音;弦乐和管乐等。

声音分类根据用途还可以继续细分:

  • 副语言识别:说话人识别(Speaker Recognition), 情绪识别(Speech Emotion Recognition),性别分类(Speaker gender classification)
  • 音乐识别:音乐流派分类(Music Genre Classification)
  • 场景识别:环境声音分类(Environmental Sound Classification)
  • 声音事件检测:各个环境中的声音事件和起始时间的检测
图片来源:http://speech.ee.ntu.edu.tw/~tlkagk/courses/DLHLP20/Speaker%20(v3).pdf

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,也称作为语谱图。

图片参考:DLHLP 李宏毅 语音识别课程PPT;https://www.shong.win/2016/04/09/fft/



下面例子采用 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 之间的差异。

因此,学者提出了梅尔频率,在该频率计量方式下,人耳对相同数值的频率变化的感知程度是一样的。

图片来源:https://www.researchgate.net/figure/Curve-relationship-between-frequency-signal-with-its-mel-frequency-scale-Algorithm-1_fig3_221910348

关于梅尔频率的计算,其会对原始频率的低频的部分进行较多的采样,从而对应更多的频率,而对高频的声音进行较少的采样,从而对应较少的频率。使得人耳对梅尔频率的低频和高频的区分性一致。

图片来源:https://ww2.mathworks.cn/help/audio/ref/mfcc.html

Mel Fbank 的计算过程如下,而我们一般都是使用 LogFBank 作为识别特征:

图片来源:https://ww2.mathworks.cn/help/audio/ref/mfcc.html



下面例子采用 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和随机森林等方法。

一个典型的应用案例是:男声和女声分类。

图片来源:https://journals.plos.org/plosone/article/figure?id=10.1371/journal.pone.0179403.g001

2.3.2 深度学习方法

传统机器学习方法可以捕捉声音特征的差异(例如男声和女声的声音在音高上往往差异较大)并实现分类任务。

而深度学习方法则可以突破特征的限制,更灵活的组网方式和更深的网络层次,可以更好地提取声音的高层特征,从而获得更好的分类指标。

随着深度学习算法的快速发展和在分类任务上的优异表现,当下流行的声音分类模型无一不是采用深度学习网络搭建而成的,如 AudioCLIP[1]PANNs[2]Audio Spectrogram Transformer[3] 等。

图片来源:https://towardsdatascience.com/audio-deep-learning-made-simple-sound-classification-step-by-step-cebc936bbe5

2.3.3 Pretrain + Finetune

在声音分类和声音检测的场景中(如环境声音分类、情绪识别和音乐流派分类等)由于可获取的据集有限,且语音数据标注的成本高,用户可以收集到的数据集体量往往较小,这种数据量稀少的情况对于模型训练是非常不利的。

预训练模型能够减少领域数据的需求量,并达到较高的识别准确率。在CV和NLP领域中,有诸如 MobileNet、VGG19、YOLO、BERT 和 ERNIE 等开源的预训练模型,在图像检测、图像分类、文本分类和文本生成等各自领域内的任务中,使用预训练模型在下游任务的数据集上进行 finetune ,往往可以更快和更容易获得较好的效果和指标。

相较于 CV 领域的 ImageNet 数据集,谷歌在 2017 年开放了一个大规模的音频数据集 AudioSet[4],它是目前最大的用于音频分类任务的数据集。该数据集包含了 632 类的音频类别以及 2084320 条人工标记的每段 10 秒长度的声音剪辑片段(包括 527 个标签),数据总时长为 5,800 小时。

图片来源:https://research.google.com/audioset/ontology/index.html

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

  1. 创建 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,)
  1. 定义优化器和 Loss
optimizer = paddle.optimizer.Adam(learning_rate=1e-4, parameters=model.parameters())
criterion = paddle.nn.loss.CrossEntropyLoss()
  1. 启动模型训练
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. 作业

  1. 使用开发模式安装 PaddleSpeech
    环境要求:docker, Ubuntu 16.04,root user。
    参考安装方法:使用Docker安装paddlespeech
  2. MusicSpeech 数据集上完成 music/speech 二分类。
  3. 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 的语音方向的开源模型库,用于语音和音频中的各种关键任务的开发,包含大量基于深度学习前沿和有影响力的模型。

猜你喜欢

转载自blog.csdn.net/qq_21275321/article/details/127550827