我们现在正处在 2018 年 Google Summer of Code 的另一端。这是一个充满挑战和教育意义的经历,我不会想要以其他方式度过它。我感谢 Julia 社区,尤其是我的导师 @MikeInnes 对我的支持。我学到了很多东西,比以前更熟悉神经网络,也学会了如何进行基本的 GPU 编程,这对我的学术生涯将非常有用。
这篇博客文章的剩余部分将总结我的项目和我在整个夏天所做的工作,谈论哪些工作还有待完成,并以简短的教程作为结尾,介绍如何运行我编写的代码,以便你自己尝试。
语音识别目前在许多科技公司中十分流行。例如,谷歌和亚马逊正在大力推广其独立的数字助理设备,分别是 Google Home 和 Alexa。如果没有功能完善的语音识别,这些产品将毫无用武之地。
不幸的是,对于许多研究人员和潜在的语音识别用户来说,语音识别系统的文档似乎不像图像识别那样多。这个 2018 年 Google Summer of Code 项目的目标是向 Flux 模型库 贡献一些语音识别模型,以便其他人可以从这些模型中进行工作。
在项目结束时,编码了两个不同的模型。第一个是使用连接主义时间分类 (CTC) 损失函数的一种相当新的方法(Graves 等人,2006 年)。要实现的模型来自 Zhang 等人(2017 年),它使用卷积层来学习数据中的时间依赖关系,这与使用 CTC 损失的传统方法不同,后者使用循环层。这是一个非常深的网络,作者认为它可以学习时间依赖关系。
第二个网络是一种较老式的帧级识别模型,灵感来自 Graves & Schmidhuber(2005 年)。它预测通过网络传递的每个音频块的类别,并使用分类交叉熵作为其损失函数。它将作为 CTC 网络的基线进行比较。
对于那些不熟悉语音识别系统的人来说,将声学映射到音素标签仍然是一个尚未解决的问题,因为没有人能够仅对音频帧的标签识别达到 95% 或 99% 的准确率。因此,报告的准确率可能看起来不令人满意(CTC 网络的准确率肯定是不令人满意的),但这也是语音识别系统的特征。
一旦分解为步骤,这个项目的 主要任务是实现网络架构,并在 Flux 和 Julia 中实现 CTC 损失函数。这两个任务的朴素实现都很容易,但性能不适合训练网络。回想起来,提高网络的计算效率并不困难,因为只需要在 Flux 的 Chain
函数中添加一个 reshape
调用即可连接卷积层和全连接层。
真正的挑战是使 CTC 损失正确有效地运行。我最初是在 CPU 上进行操作,然后最终决定对百度 GPU 实现的 CTC,warp-ctc,进行简单的移植。这是我第一次尝试编写 GPU 内核,我学到了很多东西。但在花了几个星期移植内核之后,我得到了一个可用的 GPU 实现的损失函数。或者至少我以为是这样。我花了几个星期尝试了各种优化器和各种训练配置和例程,但我无法让网络输出超出空白音素标签类别的预测。我在 几篇 博客文章 中写到了这一点。
事实证明,我的实现中存在一个轻微的错误,该错误源于百度自己的 warp-ctc 库。就像我在写 一篇关于这个错误的博客文章 时一样,我不知道它在百度代码的其他部分中是否真的是一个错误。然而,在修复了错误之后,我发现我的代码中的损失显著下降。具体来说,有一部分代码计算结果为
而它应该计算结果为
有关此内容的更多信息,请参阅我的上一篇博文有关此内容的上一篇博文。进行此更改后,网络最终能够为每个数据时间步输出标签预测。标签并不一定总是合理,但至少它正在做出预测。
这就是语音识别系统目前所处的状态。网络架构应该已正确实现,损失函数现在似乎也正在正确运行。也就是说,网络在一定程度上运行并学习。您可以根据下面的示例输出自行评估其学习效果。
文本转录
The reasons for this dive seemed foolish now.
目标音素序列
h# dh ix r iy z ax n z f axr dh ih s dcl d ay v s iy m dcl d f uw l ix sh epi n aw h#
预测的音素序列
h# pau w iy bcl r iy ux z bcl b iy bcl b uw z ay n pcl p z iy n dcl d v w iy er h#
与目标相比,此预测的音素错误率约为 84%。此模型显然还不能添加到模型库中,因为它表现不佳。
帧级网络的主要任务是找到高效训练网络的方法。在识别结果方面,帧级网络的表现远好于 CTC 网络。仅经过两个训练周期,它就在测试集上达到了约53.5% 的标签预测精度。显然,网络应该接受更长时间的训练才能达到与 Graves & Schmidhuber 相似的性能,但值得注意的是,即使在如此早期的阶段,它的性能也优于 CTC 网络。此网络与 CTC 网络性能之间的差异令人震惊。即使 CTC 网络的任务更难,但允许网络利用训练数据中的额外信息能够取得如此大的改进,仍然令人震惊,甚至不言而喻。
帧级网络在所有意图和目的上都已完成。它应该适合作为演示网络,并且与 Graves 为该网络报告的性能非常接近。如果需要使其运行更快,可以使其使用批处理并在 GPU 上运行。
对于 CTC 网络,需要调查为什么它没有学习到 Zhang 等人报告的程度。下图是使用上面提到的已更正损失函数第一次运行网络时,随时间变化的训练损失和验证损失的图。优化器是 AMSGrad,学习率为 . 它是我在其他优化器和训练配置中看到的行为的特征,无论是全新开始还是继续之前的训练。
验证损失正在稳定在一个次优水平,即使训练损失继续下降。目前,我不确定这种行为的原因是什么,因为我已尽力尽可能地遵循 Zhang 等人给出的实现细节。我不认为问题出在 CTC 函数上,因为我已经多次针对手工解决方案测试了 CTC 损失函数,并发现它产生了正确的结果;同样,它提供的梯度允许网络将一个训练示例拟合到接近零的损失水平,因此该函数似乎提供了一个足够可靠的损失信号来最小化损失。可能需要调查我的反向传播调用方式,以查看批处理中的损失值是否已正确组合。
模型的训练程序运行应该很简单。确保已安装 WAV、Flux、CuArrays、JLD 和 BSON 包。此外,安装我制作的 MFCC 包的分支(它只更新了一行,以使函数在 Julia 0.6 上运行)。首先克隆项目的 Git 存储库。
$> git clone https://github.com/maetshju/gsoc2018.git
用户需要从语言数据联盟下载 TIMIT 语音语料库,正如我在上一篇博文的第一部分中所讨论的那样上一篇博文。
导航到 speech-cnn
文件夹。要从 TIMIT 语料库中提取数据,请使用 00-data.jl
脚本。有关此脚本的更多信息,请参阅专门介绍它的博文。
$> julia 00-data.jl
现在,要训练网络,请运行 01-speech-cnn.jl
脚本。确保您已从数据文件夹中删除了 README.md
文件(如果您下载了它们)。
$> julia 01-speech-cnn.jl
请注意,基本上需要使用 GPU 来训练网络,因为仅在 CPU 上进行的训练过程非常慢。此外,该脚本调用了 CTC 算法的 GPU 实现,如果没有 GPU,它将失败。该脚本可能需要一天以上才能运行,因此请稍后再回来。该脚本完成后,模型应该已经过训练,并准备好在进行预测时使用。
导航到 speech-blstm
文件夹。要从 TIMIT 语料库中提取数据,请使用 00-data.jl
脚本。
$> julia 00-data.jl
现在,要训练网络,请运行 01-speech-blstm.jl
脚本。确保您已从数据文件夹中删除了 README.md
文件(如果您下载了它们)。
$> julia 01-speech-blstm.jl
此网络在 CPU 上训练速度相当快,因此没有实现 GPU 功能。
在此项目期间编写的代码可以在我的 GitHub 上找到我的 GitHub 上。还有一些拉取请求是为了向更大的包生态系统贡献代码而做出的。
Graves, A., & Schmidhuber, J. (2005). Framewise phoneme classification with bidirectional LSTM and other neural network architectures. Neural Networks, 18(5-6), 602-610.
Graves, A., Fernández, S., Gomez, F., & Schmidhuber, J. (2006). Connectionist temporal classification: Labelling unsegmented sequence data with recurrent neural networks. In Proceedings of the 23rd international conference on machine learning (pp. 369-376). ACM.
Zhang, Y., Pezeshki, M., Brakel, P., Zhang, S., Bengio, C. L. Y., & Courville, A. (2017). Towards end-to-end speech recognition with deep convolutional neural networks. arXiv preprint arXiv:1701.02720.