使用 CRNN、CTC 损失、DeepSpeech Beam Search Decoder 和 KenLM Scorer 进行语音识别

理论

如今,三个最流行的终端到终端的ASR(自动语音识别)模型是 Jasper Wave2Letter + Deep Speech 2 . 。现在它们 可以 作为Nvidia 制作的 OpenSeq2Seq 工具包的一部分使用。所有这些 ASR 系统都基于神经声学模型,它在每个时间步长 t 的所有目标字符 c 上产生概率分布 Pt© ,然后由 CTC 损失 函数评估:

Nvidia对 CTC ASR 管道架构的总结

本质上,本文描述的端到端语音识别系统是由几个简单部分组成的:

python
import librosa
import librosa.display as display
import torch
import torchaudio
import matplotlib.pyplot as plt
import numpy as np

train_dataset = torchaudio.datasets.LIBRISPEECH("./data", url="train-clean-100", download=True)
waveform, sample_rate,  _, _, _, _ = train_dataset[0]
num_channels, num_frames = waveform.shape
audio_transforms = torchaudio.transforms.MelSpectrogram()
spectrograms = audio_transforms(waveform)
img = spectrograms.log2().permute(1, 2, 0)
f, (ax, ax1, ax2) = plt.subplots(3, 1)
f.set_size_inches(15, 8)
ax.title.set_text('Waveform')
ax.grid(True)
ax.plot(torch.arange(0, num_frames) / sample_rate, waveform[0], linewidth=1)
ax1.title.set_text('Mel Spectrogram')
ax1.imshow(img)

spec = np.abs(librosa.stft(waveform.numpy().squeeze(0), hop_length=512))
spec = librosa.amplitude_to_db(spec, ref=np.max)
display.specshow(spec, sr=sample_rate, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f dB')
ax2.title.set_text('Spectrogram')
plt.show()

如何使用librosa和torchaudio将波形转换为频谱图

  • 频谱图是一幅图像,因此我们可以使用 卷积层 从中提取特征。在这篇文章中,我将使用的当红组合 Conv2d 层和 GELU 激活功能(因为 RELU 更好)与 dropout 的正规化建设。此外,我认为使用 层归一化 跳过连接 以获得更快的收敛和更好的泛化是有益的。因此,神经网络的第一部分将由以下层组成:
# First Conv2d layer</strong>
Conv2d(1, 32, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)) <strong># 7 blocks of these layers
# Skip connection is added to this Conv2d layer</strong>
LayerNorm((64,), eps=1e-05, elementwise_affine=True)
GELU()
Dropout(p=0.2, inplace=False)
Conv2d(32, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  • 同时,频谱图是时间序列数据,因此使用 GRU 等双向 RNN 层从 CNN 层检测到的特征中捕获时频模式是很自然的。出于与之前相同的原因,我将使用层规范化和 dropout:
**# I will use 5 blocks of these layers**
LayerNorm((512,), eps=1e-05, elementwise_affine=True)
GELU()
GRU(512, 512, batch_first=True, bidirectional=True)
Dropout(p=0.2, inplace=False)
  • 实际上,我们需要的是将频谱图图像的每个小的垂直切片映射到某个字符,因此我们的模型将为每个垂直特征向量和每个字符生成概率分布。可以使用 线性 (全连接)层来完成:
<strong># Input for this layer is an output from the last GRU layer
# We need to gradually reduce number of outputs from 1024 to the number of characters used in LibriSpeech dataset - 28 + 'blank'</strong>
Linear(in_features= **1024** , out_features= **512** , bias=True)
GELU()
Dropout(p=0.2, inplace=False)
Linear(in_features= **512** , out_features= **29** , bias=True)
  • 然后我们可以使用贪心解码器或波束搜索解码器来生成最终转录。

贪婪解码器取入该模型的输出和每个垂直特征矢量时,它选择具有最高概率的字符。波束搜索译码器稍微复杂。波束搜索基于一种启发式,假设具有高概率的随机变量链具有相当高的概率条件。基本上,它采用p(x1)的k 个最高概率解,然后为每个解采用p(x2|x1)的k 个最高概率解。然后我们需要取 p(x2|x1) * p(x1) 值最高的那些K并重复。

我认为Andrew Ng 的 这个视频 这篇文章 是关于这个主题的最直观的指南。

根据谷歌的 这篇 关于 NMT 的 论文 “……我们发现……调整良好的波束搜索对于获得最先进的结果至关重要。”

生成的模型具有以下架构:

Speech recognition model’s architecture (1,645,181 trainable parameters)

语音识别模型的架构(1,645,181 个可训练参数)


实践

数据集

在本文中,我使用了 LibriSpeech ASR 语料库 ,该 语料库 包含大约 1000 小时 的分段和对齐英语语音,源自阅读有声读物。此数据集中的话语由 28 个 字符(目标类)组成:

```python
target_dir = "./data"
if not os.path.isdir(target_dir):
    os.makedirs(target_dir)
train_dataset = torchaudio.datasets.LIBRISPEECH(target_dir, url="train-clean-100", download=True)
test_dataset = torchaudio.datasets.LIBRISPEECH(target_dir, url="test-clean", download=True)
classes = "' abcdefghijklmnopqrstuvwxyz"

然后我们需要使用 MelSpectrogram 将波形转换为频谱图, 并定义函数将文本转换为整数,反之亦然:

audio_transforms = torchaudio.transforms.MelSpectrogram()
num_to_char_map = {c: i for i, c in enumerate(list(classes))}
char_to_num_map = {v: k for k, v in num_to_char_map.items()}
str_to_num = lambda text: [num_to_char_map[c] for c in text]
num_to_str = lambda labels: ''.join([char_to_num_map[i] for i in labels])

这个 collat​​e 函数对于准备我们模型所需的张量是很有必要的—— 谱图和标签以及它们的长度 。这些“长度”张量稍后将被 CTC 损失函数使用:

image

现在,我们必须初始化 的DataLoader 小号 使用训练和验证数据集和随机种子设置为一个固定值,以获得可重复的结果:

image

训练

下一步是定义训练和验证循环,选择优化器、超参数和指标来评估训练进度。我决定使用具有 5e-4 的相当低学习率的 AdamW 优化器,将这个模型训练 25 个 时期,并使用 levenshtein jiwer 包来计算质量指标:



训练并验证循环


image
模型直接遵循上一段描述的架构进行实践

对于推理,我用的 柱搜索解码器 通过 DeepSpeech 它允许我们使用基于 KenLM 工具包的 外部语言模型 评分器

text_file = open("chars.txt", "w", encoding='utf-8')
text_file.write('\n'.join(list(classes)))
text_file.close()

生成将用于字母表初始化的自定义字母表映射文件


KenLM 模型和外部 .scorer 生成

image

使用ds-ctcdecoder 的推理过程


训练和验证结果

预训练中的权重可 在这里 和产生的得分手,请 点击这里 。另请注意,批量大小是根据可用 GPU 内存量选择的,并且该模型在训练期间消耗了大约 22 GB的 VRAM

1_xW3dUkSc2o1rMIgJv0o4SQ

用于此项目的 CUDA 设备

测试

出于测试目的,我从测试集中抽取了 20 个随机频谱图:

模型推断和显示频谱图

测试集中几个随机样本的结果

如你所见,这个相对较小的模型绝对能够识别人类语音,并在 LibriSpeech 数据集上表现出良好的性能。

这个项目也可以 在我的 GitHub 上找到

原文作者 Nikita Schneider
原文链接 https://medium.com/geekculture/audio-recognition-using-crnn-ctc-loss-beam-search-decoder-and-kenlm-scorer-24472e43fb2f