查看原文
其他

手把手 | 如何训练一个简单的音频识别网络

2017-09-28 机器学习小能手 大数据文摘

大数据文摘作品

编译:happen,吴双

高宁,笪洁琼,魏子敏


本文将一步步向你展示,如何建立一个能识别10个不同词语的基本语音识别网络。你需要知道,真正的语音与音频识别系统要复杂的多,但就像图像识别领域的MNIST,它将让你对所涉及的技术有个基本了解。


完成本教程后,你将拥有一个模型,能够辨别一个1秒钟的音频片段是否是无声的、无法识别的词语,或者是“yes”、“no”、“up”、“down”、“left”、“right”、“on”、“off”、“stop”、“go”。你还可以使用这个模型并在Android应用程序中运行它。


注:本文含有大量代码,需要代码原文的同学请参考文末来源地址中的内容。


准备工作

确保你已经安装了TensorFlow,由于脚本程序将下载超过1GB的训练数据,你需要畅通的网络连接,而且你的机器需要有足够的空余空间。训练过程本身可能需要几个小时,所以确保你有一台可以使用这么长时间的机器。


训练

开始训练前,在TensorFlow 源码树下运行:

这个脚本程序将开始下载“语音命令数据集”,包括65000条由不同的人说30个不同词语组成的WAVE音频文件。这份数据由Google收集,并在CC-BY协议许可下发行,你可以通过贡献自己五分钟的声音来帮助提升它。这份文件大小超过1GB,所以这部分可能需要一段的时间,但你应该看一下过程日志,一旦它被下载一次,你就不需要再进行这一步了。

这表明初始化进程已经完成,循环训练已经开始,你将看到每一次训练产生的输出信息。这里分别解释一下含义:

在100步之后,你将会看到一行输出如下:

就可以从该点重新开始脚本。


混淆矩阵

在400步之后,将记录如下的信息:

第一部分是混淆矩阵。为了理解它的含义,首先需要知道所使用的标签,它们是 “silence”、 “unknown”、 “yes”、 “no”、 “up”、 “down”、 “left”、 “right”、 “on”、 “off”、“stop”和“go”。每一列代表一组被预测为某个标签的样本,因此第一列代表着所有预测为“silence”的片段,第二列都被预测为“unknown”词,第三列是“yes”,以此类推。


每一行代表着正确的、完全真实为该标签的片段。第一行是所有为“silence”的片段,第二行的片段都是“unknown”词,第三行是“yes”,以此类推。


这个矩阵比单单一个准确率更有用,因为它能很好地总结出网络所犯的错误。在这个例子中,除了第一项之外,第一行中的所有项都是零。因为第一行指的是所有实际上是“silence”的片段,这意味着没有一个被否定地标记为词语,所以我们没有漏报一个“silence”。这表明网络已经能够很好地区分“silence”和词语。


但是,如果我们看一下第一列,就会看到很多非零值。列代表所有被预测为“silence”的片段,所以除第一项之外的正数都是错误的。这意味着一些真实发声的词语实际上被预测为“silence”,所以我们存在一些误报。


一个完美的模型会产生一个混淆矩阵,其中所有项都是零,除了通过中心这条对角线。通过该模式发现偏差,可以帮助你弄清楚模型是如何混淆的,一旦你发现了问题,你就可以通过添加更多的数据或清理类别来解决这些问题。


验证

在混淆矩阵之后,你会看到一行信息如下:

把数据分为三份是很好的做法。最大一份(在这个例子中大约数据的80%)是用来训练网络,较小(这里用10%,作为“验证”)的一份保留用于评估训练过程中的准确率,另一份(最后的10%,作为“测试”)用于在训练完成时评估准确率。


划分数据是因为网络会在训练过程中记录输入,这是有风险的。通过将验证集分开,你可以确保模型在从未使用过的数据上运行。测试集是一个额外的保障,以确保你在调整模型过程中没有同时运行训练集和验证集,也没有更大量的输入。


训练脚本自动将数据集划分为这三类,上述日志行展示了模型在验证集上运行的准确率。理想情况中,这个值将与训练集准确率十分接近。如果训练集准确率上升的同时验证集准确率没有上升,这意味着出现过拟合,你的模型仅仅只在训练集上学习,并没有推广到更广泛的模式中。

Tensorflow的可视化工具


可视化训练过程的一个好方法是使用tensorboard。该脚本默认将事件保存到/tmp/retrain_logs,你可以通过输入命令行来加载:

在浏览器输入“http://localhost:6006”,你将看到一些展示训练过程的图表。


结束训练


经过几个小时的训练(取决于机器的速度),脚本将完成了所有18000个步骤。它将打印一个最终的混淆矩阵,连同一个准确率,这些都是在测试集上运行得到的。使用默认配置,你将得到85%~90%之间的准确率。


因为音频识别在移动设备上特别有用,接下来我们将把它导出到压缩包,使得它能够在这些平台上使用。为此,运行命令如下:

一旦压缩后的模型建好,你可以运行label_wav.py脚本来测试,如下:

它将输出三个标签:

希望“left”是最高分,指的是正确的标签,但由于训练是随机的,它可能不是你测试的第一个文件。在同一个文件夹中测试一些其他WAV文件,看看结果如何。


分数将在0到1之间,值越高意味着模型对预测越自信。


在Android应用里运行模型

如果你想观察模型在实际应用中表现如何,最简单的方法就是下载并在你的手机中安装已构建好的Android演示应用了。你可以在你的应用列表找到名为“TF Speech”的应用,打开应用,它会展示与我们刚刚训练模型所用相同的动作词列表,选择“Yes”或者“No”开始。一旦你给予app使用手机的权限,你就可以说一些词,看看是否被模型识别出来并在UI高亮显示。


你也可以自己来构建这个应用程序,因为它是开源代码并且在github的TensorFlow存储库中可调用。默认情况下,它会从tensorflow.org下载一个预先训练的模型,但你可以轻松地用自己训练的模型替换它。如果这样做的话,你需要确保主要的SpeechActivity Java源文件(如SAMPLE_RATE和SAMPLE_DURATION)中的常量与你在进行训练时对默认值进行的更改相匹配。 你还可以看到一个Java版本的RecognizeCommands模块与本教程中的C ++版本非常相似。 如果你因此调整了参数设置,可以在SpeechActivity中进行更新,以获得与你的服务器测试相同的结果。


演示app会根据你在压缩图形旁复制的标签文本文件自动更新其用户界面列表,这意味着你可以轻松地尝试不同的模型,而无需进行任何代码更改。如果你更改了路径,则需要更新LABEL_FILENAME和MODEL_FILENAME以指向你新添加的文件。


这个模型是如何运作的?


本教程使用的体系结构是基于《用于小尺寸关键字检测的卷积神经网络》一文中的部分描述。选用它的原因是其相对简单,可快速训练和易于理解,而不是技术的先进性。建立神经网络模型以处理音频有许多不同的方法,包括反复网络或扩张(无序)卷积等。而本教程基于的卷积网络则对于使用图像识别的人来说非常熟悉。这乍一听似乎有点让人惊讶,毕竟音频是跨越时间的一维连续信号,而不是2D的空间问题。

如果你打开/tmp/spectrogram.png,你将看到:

不同于时间从左向右的常规频谱图,由于TensorFlow的记忆顺序,图像中的时间是从上到下增加的,频率从左到右。你应该可以看到几个明显不同的部分,第一个音节“ha”明显区别于与“ppy”。


由于人耳对某些频率比其他频率更敏感,因此在语音识别中,惯用的方法会是针对该特性做一个进一步的处理,将其转换为一组Mel-Frequency倒谱系数,简称为MFCC。这也是一个二维的单通道显示,所以它可以被看作是图像。如果你针对的是一般声音而不是语音,你会发现你是可以跳过此步骤并直接在频谱图上操作的。


接下来,由这些处理步骤产生的图像会被输入到多层卷积神经网络,其含有一个全链接层后以分类器结尾。 你可以在tensorflow / examples / speech_commands / models.py中看到此部分的定义。

精度流

 

大多数音频识别应用程序需要在连续的音频流上运行,而不是单独的剪辑段。在这种环境中使用模型的典型方法是在不同的偏移时间上重复应用它,并在短时间内平均结果以产生平滑的预测。如果你将输入视为图像,它则会沿着时间轴不断滚动。我们想要识别的词可以随时开始,所以需要采取一系列的快照来在提供给模型的时间窗口中捕获大部分的话语。如果我们以足够高的速度进行采样,那么是很有可能在多个时间窗口中捕获该单词的,因此将结果进行平均可以提高预测的整体信度。


有关如何在流式传输数据上使用模型的示例,可以查看test_streaming_accuracy.cc。 它使用了RecognizeCommands来运行长格式输入音频,以尝试查找单词,并将这些预测与标签和时间的完全真值列表进行比较。这使它成为将模型应用到音频信号流的一个很好的例子。


你需要一个长音频文件和显示其中每个单词被说出位置的标签来做测试。如果不想自己录制,可以使用generate_streaming_test_wav实用程序生成一些合成的测试数据。默认情况下,该程序将创建一个10分钟的.wav文件,文件的词频基本上是每三秒一个,同时提供一个包含了每个单词被说出位置的完全真值文本文件。词汇选自当前数据集的测试部分,并与背景噪声混合。想要运行它,请使用

这将保存一个.wav文件/tmp/speech_commands_train/streaming_test.wav, 

并提供一个包含标签的文本文件在

运行精度测试:

这部分程序将输出正确匹配的词数,有多少词语被给出了错误标签,以及没有真正的词语被说出时模型却被触发的次数。这里有各种参数可以控制信号平均的工作原理,包括--average_window_ms,它设置用以结果平滑的时间长度,--sample_stride_ms,是模型应用程序之间的时间,--suppression_ms,用以设置在找到第一个词后再次触发后续检测的间隔时间,以及--detection_threshold,它控制给出肯定性预测的平均得分的阈值。


你会看到精度流输出三个数字,而不仅仅是训练中使用的一个度量。这是因为不同的应用程序有不同的要求,一些能够容忍频繁的不正确结果,只要最终找到真实的单词即可(高查全),而另一些则非常专注于确保预测的标签是高可能正确的,即使一些词语并没有被监测出来(高精度)。该工具输出的数值会让你了解到你的模型在应用程序中的表现性能,基于此你可以尝试调整信号平滑参数来调优其性能。要了解你的应用程序的正确参数,可以查看生成的ROC曲线来帮助了解平衡。


识别命令

精度流工具使用了一个简单的解码器,该解码器被包含在一个叫做识别命令的小型C ++类中。这个类随着时间推移运行TensorFlow模型的输出,对信号进行平均,当有足够的证据认为已经找到识别单词时,返回标签信息。它的执行很简单,只需跟踪最后几个预测值并对其进行平均,因此可以根据需要轻松地移植到其他平台和语言上。例如,在Android上的Java或Raspberry Pi上的Python上执行类似的操作都很方便。只要这些算法执行上具有相同的逻辑,就可以使用流测试工具调整控制平均值的参数,然后将其传输到应用程序以获得类似的结果。


高阶训练

培训脚本的默认设置旨在于较小的文件中生成良好的端到端结果,但其实有很多选项可以更改,你可以根据自己的要求自定义结果。


自定义训练集

默认情况下,脚本程序将下载Speech Commands dataset数据集,但你也可以提供自己的训练数据。为了在自定义数据上做训练,你应该确保每个识别目标单词至少有几百个录音,并按类别归入文件夹。例如,如果你想从猫叫声中识别狗叫声,需要先创建一个名为animal_sounds的根文件夹,然后将其中的两个子文件夹命名为bark(狗叫)和miaow(猫叫)。最后,将音频文件分类放入相应的文件夹中。


将脚本设置为指向新的音频文件夹,需要设置--data_url= to disable downloading of the Speech Commands dataset(取消下载Speech Commands dataset), 并设置--data_dir=/your/data/folder/,从而找到你新建的音频文件。


这些文件本身应该是16位小端PCM编码的WAVE格式。采样率默认为16,000,但只要所有音频的速率保持一致(脚本不支持重复采样),你可以使用--sample_rate更改此参数。剪辑段也应该采用大致相同的时间区段。默认的预期时间区段为1秒,但也可以使用--clip_duration_ms进行设置。如果在开始时一些剪辑段有不同数量的静音时间,可以查看编辑工具来标准化它们(这是一种快速却投机的方法)。


要注意的一个问题是,你可能会在数据集中重复相同的声音,如果它们分布在训练,验证和测试集中,则可能会产生有误导性的指标表现。例如,“语音命令”集中含有一个人多次重复的相同单词。这些重复中的每一个都可能与其他重复相当接近,所以如果在训练时过度匹配且对其中之一进行记忆,那么在测试集中看到非常相似的副本时,它可能表现出不切实际的好。为了避免这种风险,“语音命令”会尽力确保将单个人说出的同一个单词的所有剪辑放入同一分区。

 

基于片段名称的哈希值,会将片段分为训练集,测试集,以及校验集。那么在有新的片段加入时也可以保证集合的平稳划分,避免任何训练样本迁移到其他集合。为了保证所有说话人的声音都在同样的类别内,在使用哈希函数计算分配时会忽略“非哈希”之后的文件名。即就是,如果你有两个文件,命名分别为pete_nohash_0.wav和pete_nohash_1.wav,这两个文件将会被分配到同一数据集。


不确定类型

在使用你的应用时,很可能听到一些不在训练集范围内的声音,你会希望模型可以在这些情况下标记出那些它无法识别的噪音。为了帮助神经网络学习需要忽略哪些声音,你需要准备一些不属于你的预测类型的音频片段。怎么做呢?你可以创建“呱呱”“噜噜”“哞哞”等子文件夹,然后将你的用户可能碰到的其他动物的声音混入子文件夹。--wanted_words参数对应的脚本定义了你所关心的类型,所有上述子文件中的声音会用来在测试中混入_unknown_的类型。语音命令数据集中含有二十种未知类型,包含了从0到9的数字,和一些随机的命名,例如“sheila”。


默认情况下,测试数据的10%是来自于未知类型,但是你可以通过参数--unknown_percentage来进行调整,增加这个值可以使模型更好的区分未知和预测的声音,但是如果这个数值过大可能会适得其反,因为模型会为了安全而将所有的声音都归类到未知类型!


背景噪音

真实的应用需要在有噪音的环境中进行语音识别。为了使模型在干扰下具有良好的鲁棒性,我们需要对具有相似属性的录音进行训练。语音命令数据集中的文件不是来自录音室,而是用户在不同的环境中通过不同设备获取的录音,这在一定程度上可以增强训练的真实性。此外,你可以在输入端混合一些随机的环境音频。在语音命令数据集中有一个特殊的文件夹“_background_noise_”(背景噪音),其中包含了数分钟的白噪音和机器的声音,以及日常家务的活动的录音。


背景噪音文件的小片段是随机选择,然后在训练中以一个较低的音量混入音频片段中。这些文件的音量也是随时选择的,通过--background_volume(背景音量)参数进行控制,0是静音,1是最大音量。不是所有的片段都需要添加背景噪音,--background_frequency(背景噪音频率)可以控制背景噪音混入的比例。


你的应用程序可能运行在某种特定的环境下,具有不同的背景噪声模式,而不是默认的这些,所以你可以在_background_noise_(背景噪音)文件夹中添加自己的音频片段。这些片段应该保持与主数据集相同的采样率,但持续时间要更长,这样可以从它们中选择一组较好的随机片段。


静音

在大多数情况下,你关心的声音是断断续续的,所以知道什么时候没有匹配的音频是很重要的。为了支持这一点,我们使用特殊的_silence_(静音)标签来标志模型没有识别出有用信息。因为在真实的环境中从来没有完全的静音状态,实际训练时,我们必须提供一些安静的和一些不相关的音频。为此,我们使用_background_noise_(背景噪音)文件夹,这些音频也被混在真正的剪辑,从中选择一些段的音频数据然后标记它们的类型为_silence_(静音)。默认情况下训练集的10%的数据来自该文件夹中,但是,--silence_percentage(静音比例)可以用来控制静音文件的混入比例。与未知类型音频相同,比例的调整是以假阴性作为代价,如果设置的比例越高,模型会将更多的声音设置为静音类型,但是如果比例过高,就会使模型陷入倾向于预测是静音类型的困境。


时间推移

在训练中增加背景噪音是一种有效的方法来扩大数据集和增加整体的准确性,时间推移也可以起到同样的作用。这包括了对训练样本数据进行随机的时间抵消,在音频的开始或者结束会有一个小片段被切除,并以0进行填充。在训练集的开始阶段使用这种方法来模拟真实的变化,并通过--time_shift_ms参数来进行控制,默认值是100毫秒。增大这个值可以为训练集提供更多的变化,但是会增加切除音频重要部分的风险。还可以使用时间收缩和音量缩放来实现真实的扭曲,从而扩大数据集,但这两种方法超出了本教程的范围。


自定义模型

这个脚本对应的模型相当大,每次的推算都使用了超过8亿次的浮点运算以及94万个权重参数。这在台式机或现代的手机上会以有限的速度运行,但是因为太多的计算使得在现有设备有限的资源下很难有一个较高的交互速度。为了支持这些使用场景,我们提供了几个可用的替代方案。


low_latency_conv参数基于神经网络少量关键词识别论文中描述的拓扑'cnn-one-fstride4'。准确度相对于卷积要低一些,但是权重参数的数量基本相同,更重要的是每次预测只需要1.1亿浮点运算,很大的提升了运行速度。


你可以在命令行中使用--model_architecture=low_latency_conv来设定使用这种模型。同时,需要更新训练集的学习率以及训练的次数,整体的代码如下:

代码中设定了训练的迭代次数为20,000,学习率为0.01,然后将学习率调整为0.001,迭代次数调整为6000,对模型进行优化。


low_latency_svdf 基于论文“使用秩约束拓扑结构实现深度神经网络压缩”中的拓扑结构。同样的,准确度相对于卷积网络来说偏低,但只需要使用75万个参数,最重要的是可以优化测试时的执行(当你实际使用你的应用时),最终只有75万次的浮点运算。


你可以在命令行中使用--model_architecture=low_latency_svdf来设定使用这个模型,然后更新训练的学习率和迭代次数,整体的代码如下:

需要注意的是尽管这个模型的迭代次数与前两个拓扑结构相比大了很多,但计算量的减少意味着在训练时最终所使用的时间相当,最终的准确率可以达到85%。你可以通过调整SVDF层的这些参数,相对简单地调整这个拓扑结构的计算量和准确率。

  • 秩 – 相似度的秩(这个值越高,准确度越好,但同时计算量会增大)

  • 节点数量 – 和其他层类型相似,层中的节点数量(节点数越多,质量越好,同时计算量会越大) 


关于运行时间,考虑到本层允许通过缓存一些内部神经网络的激活结果来进行优化,你需要保证当你暂停执行,或者在流传输模式下执行模型时,需要保证使用的是同一个步调(例如,'clip_stride_ms' 标志)。当你压缩图时,以及在流模式下执行模型时(例如test_streaming_UNK acy.cc)。


如果你想尝试自定义模型,还有一些参数也可以进行自定义,比如可以从调整声谱图的创建参数开始。这个参数会调整模型输入的图像大小,在models.py文件中的创建代码会根据不同的维度对计算量和权重进行自适应。如果你的输入较小,那么模型会使用更小的运算量来进行处理,所以这是权衡准确度和减少延迟时间的好方法。--window_stride_ms 参数可以控制每个频率的分析样本与前一个之间的距离。如果增大这个值,那么在给定区间内的采样数会减少,输入的时间轴也会缩小。--dct_coefficient_count参数控制用来统计频率的分类数量,所以如果减小这个值意味着从另一个维度上缩小了输入。--window_size_ms参数不会影响输入的大小,但是它控制了计算每个样本频率的区域的宽度。如果你需要验证的声音很短,可以通过--clip_duration_ms参数来减少训练样本的时长,因为这样就是从时间维度上减少了输入。但是你需要确保所有的训练数据在片段的初始部分中包含你所需要的正确音频。


针对你的问题,如果你脑海中有一个完全不同的模型,你可以将其插入到models.py文件中,然后使用其他部分的脚本处理所有的预处理和训练机制。同时你需要在create_model中新增代码用来查询你的架构名称,然后调用模型的创建函数。这个函数中包含了声谱图的输入,以及一些其他模型信息,同时会创建TensorFlow的操作来读取数据、创建输出的预测向量,以及使用一个占位符来控制神经元的丢失率。剩下的代码会将整个模型进行集成,执行输入计算,应用softmax函数以及损失函数来进行训练。


当你调整模型以及训练超参数时,普遍遇到的问题是由于数字精度的问题,有些数值并不可以进行缓慢变化。一般来说,你可以通过降低这些值的量级来处理,比如对于学习率和权重初始化函数。但如果这个问题还是持续存在,你可以使用--check_nans标志进行跟踪,寻找问题的根源。这个操作将在TensorFlow中的大多数常规操作之间插入检查操作,这样在遇到问题时,会停止训练过程并返回有用的错误信息。

 

原文链接:https://www.tensorflow.org/versions/master/tutorials/audio_recognition?utm_campaign=Revue%20newsletter&utm_medium=Newsletter&utm_source=The%20Wild%20Week%20in%20AI


志愿者介绍

回复“志愿者”加入我们

往期精彩文章

点击图片阅读

吴恩达导师Michael I. Jordan刚去清华手写版书讲了三天课,这有一份他的课程笔记

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存