# 了解简单的语音命令 如今,语音服务(例如 Apple Siri,Amazon Alexa,Google Assistant 和 Google Translate)已变得越来越流行,因为语音是我们在某些情况下查找信息或完成任务的最自然和有效的方法。 这些语音服务中的许多服务都是基于云的,因为用户语音可能会很长而且很自由,并且**自动语音识别**(**ASR**)非常复杂,并且需要大量的计算能力。 实际上,得益于深度学习的突破,仅在最近几年,在自然和嘈杂的环境中 ASR 才变得可行。 但是在某些情况下,能够离线识别设备上的简单语音命令是有意义的。 例如,要控制 Raspberry-Pi 驱动的机器人的运动,您不需要复杂的语音命令,不仅设备上的 ASR 比基于云的解决方案还快,而且即使在没有网络访问的环境。 设备上的简单语音命令识别还可以通过仅在发出某些明确的用户命令时才向服务器发送复杂的用户语音来节省网络带宽。 在本章中,我们将首先概述 ASR 技术,涵盖基于最新的深度学习系统和顶级开源项目。 然后,我们将讨论如何训练和重新训练 TensorFlow 模型,以识别简单的语音命令,例如`"left", "right", "up", "down", "stop", "go"`。 接下来,我们将使用训练有素的模型来构建一个简单的 Android 应用程序,然后再构建两个完整的 iOS 应用程序,一个由 Objective-C 实现,另一个由 Swift 实现。 在前两章中我们没有介绍使用 TensorFlow 模型的基于 Swift 的 iOS 应用,而本章是回顾和加强我们对构建基于 Swift 的 TensorFlow iOS 应用的理解的好地方。 总之,本章将涵盖以下主题: * 语音识别-快速概述 * 训练简单的命令识别模型 * 在 Android 中使用简单的语音识别模型 * 在带有 Objective-C 的 iOS 中使用简单的语音识别模型 * 在带有 Swift 的 iOS 中使用简单的语音识别模型 # 语音识别-快速概述 1990 年代出现了第一个实用的独立于说话者的大词汇量和连续语音识别系统。 在 2000 年代初期,领先的初创公司 Nuance 和 SpeechWorks 提供的语音识别引擎为许多第一代基于 Web 的语音服务提供了支持,例如 TellMe,Phone 的 AOL 和 BeVocal。 当时构建的语音识别系统主要基于传统的**隐马尔可夫模型**(**HMM**),并且需要手动编写语法和安静环境以帮助识别引擎更准确地工作。 现代语音识别引擎几乎可以理解嘈杂环境下人们的任何说话,并且基于端到端深度学习,尤其是另一种更适合自然语言处理的深度神经网络,称为**循环神经网络**(**RNN**)。 与传统的基于 HMM 的语音识别不同,传统的基于 HMM 的语音识别需要人的专业知识来构建和微调手工设计的功能以及声学和语言模型,而基于 RNN 的端到端语音识别系统则将音频输入直接转换为文本,而无需将音频输入转换为语音表示以进行进一步处理。 RNN 允许我们处理输入和/或输出的序列,因为根据设计,网络可以存储输入序列中的先前项目或可以生成输出序列。 这使RNN更适用于语音识别(输入是用户说出的单词序列),图像标题(输出是由一系列单词组成的自然语言句子),文本生成和时间序列预测 。 如果您不熟悉 RNN,则一定要查看 Andrey Karpathy 的博客,[循环神经网络的不合理有效性](http://karpathy.github.io/2015/05/21/rnn-effectiveness)。 在本书的后面,我们还将介绍一些详细的 RNN 模型。 [关于 RNN 端到端语音识别的第一篇研究论文发表于 2014 年](http://proceedings.mlr.press/v32/graves14.pdf),使用的是**连接主义的时间分类**(**CTC**)层。 2014 年下半年,百度发布了 [Deep Speech](https://arxiv.org/abs/1412.5567),这是第一个使用基于 CTC 的端到端 RNN 构建但拥有庞大数据集的商业系统之一 ,并在嘈杂的环境中实现了比传统 ASR 系统更低的错误率。 如果您有兴趣,可以查看[深度语音的 TensorFlow 实现](https://github.com/mozilla/DeepSpeech),但是由于此类基于 CTC 的系统存在问题,生成的模型需要太多的资源才能在手机上运行。 在部署期间,它需要一个大型语言模型来纠正部分由 RNN 的性质引起的生成的文本错误(如果您想知道为什么,请阅读前面链接的 RNN 博客以获取一些见识)。 在 2015 年和 2016 年,较新的语音识别系统使用了类似的端到端 RNN 方法,但将 CTC 层替换为[基于注意力的模型](https://arxiv.org/pdf/1508.01211.pdf),因此运行模型时不需要大型语言模型,因此可以在内存有限的移动设备上进行部署。 在本书的此版本中,我们将不会探讨这种可能性,而将介绍如何在移动应用中使用最新的高级 ASR 模型。 相反,我们将从一个更简单的语音识别模型开始,我们知道该模型肯定会在移动设备上很好地工作。 要将离线语音识别功能添加到移动应用程序,您还可以使用以下两个领先的开源语音识别项目之一: * [CMU Sphinx](https://cmusphinx.github.io) 大约 20 年前开始,但仍在积极开发中。 要构建具有语音识别功能的 Android 应用,您可以使用其为 Android 构建的 [PocketSphinx](https://github.com/cmusphinx/pocketsphinx-android)。 要构建具有语音识别功能的 iOS 应用,您可以使用 [OpenEars 框架](https://www.politepix.com/openears),这是一个免费的 SDK,在 iOS 应用中使用 CMU PocketSphinx 构建离线语音识别和文本转换。 * [Kaldi](https://github.com/kaldi-asr/kaldi),成立于 2009 年,最近非常活跃,截至 2018 年 1 月,已有 165 个参与者。要在 Android 上进行尝试,您可以查看[此博客文章](http://jcsilva.github.io/2017/03/18/compile-kaldi-android)。 对于 iOS,请查看[在 iOS 上使用 Kaldi 的原型](https://github.com/keenresearch/keenasr-ios-poc)。 由于这是一本关于在移动设备上使用 TensorFlow 的书,因此 TensorFlow 可用于为图像处理,语音处理和文本处理以及其他智能任务(本章其余部分的)构建强大的模型。 我们将重点介绍如何使用 TensorFlow 训练简单的语音识别模型并将其在移动应用中使用。 # 训练简单的命令识别模型 在本节中,我们将总结[编写良好的 TensorFlow 简单音频识别教程](https://www.tensorflow.org/versions/master/tutorials/audio_recognition)中使用的步骤。 一些在训练模型时可能对您有帮助的提示。 我们将建立的简单语音命令识别模型将能够识别 10 个单词:`"yes", "no", "up", "down", "left", "right", "on", "off", "stop", "go"`; 它也可以检测沉默。 如果没有发现沉默,并且没有发现 10 个单词,它将生成“未知”。 稍后运行`tensorflow/example/speech_commands/train.py`脚本时,我们将下载语音命令数据集并用于训练模型,实际上除了这 10 个单词外,还包含 20 个单词:`"zero", "two", "three", ..., "ten"`(到目前为止,您已经看到的 20 个词称为核心词)和 10 个辅助词:`"bed", "bird", "cat", "dog", "happy", "house", "marvin", "sheila", "tree", "wow"`。 核心词比辅助词(约 1750)具有更多的`.wav`文件记录(约 2350)。 语音命令数据集是从[开放语音记录站点](https://aiyprojects.withgoogle.com/open_speech_recording)收集的。您应该尝试一下,也许自己花些时间来录制自己的录音,以帮助改善录音效果,并在需要时了解如何收集自己的语音命令数据集。 关于使用数据集构建模型,[还有一个 Kaggle 竞赛](https://www.kaggle.com/c/tensorflow-speech-recognition-challenge),您可以在此处了解有关语音模型和提示的更多信息。 在移动应用中要训练和使用的模型基于纸质卷积神经网络,[用于小尺寸关键词发现](http://www.isca-speech.org/archive/interspeech_2015/papers/i15_1478.pdf),这与大多数其他基于 RNN 的大规模语音识别模型不同。 基于 CNN 的语音识别模型是可能的,但很有趣,因为对于简单的语音命令识别,我们可以在短时间内将音频信号转换为图像,或更准确地说,将频谱图转换为频率窗口期间音频信号的分布(有关使用`wav_to_spectrogram`脚本生成的示例频谱图图像,请参见本节开头的 TensorFlow 教程链接)。 换句话说,我们可以将音频信号从其原始时域表示转换为频域表示。 进行此转换的最佳算法是**离散傅立叶变换**(**DFT**),**快速傅立叶变换**(**FFT**)只是一种有效的选择 DFT 实现的算法。 作为移动开发人员,您可能不需要了解 DFT 和 FFT。 但是,您最好了解所有这些模型训练在移动应用中使用时是如何工作的,因为我们知道我们将要介绍的 TensorFlow 简单语音命令模型训练的幕后花絮,这是 FFT 的使用,前十大模型之一。当然,除其他事项外,20 世纪的算法使基于 CNN 的语音命令识别模型训练成为可能。 有关DFT的有趣且直观的教程,您可以阅读[以下文章](http://practicalcryptography.com/miscellaneous/machine-learning/intuitive-guide-discrete-fourier-transform)。 现在,让我们执行以下步骤来训练简单语音命令识别模型: 1. 在终端上,`cd`到您的 TensorFlow 源根,可能是`~/tensorflow-1.4.0`。 2. 只需运行以下命令即可下载我们之前讨论的语音命令数据集: ```py python tensorflow/examples/speech_commands/train.py ``` 您可以使用许多参数:`--wanted_words`默认为以`yes`开头的 10 个核心词; 您可以使用此参数添加更多可以被模型识别的单词。 要训​​练自己的语音命令数据集,请使用`--data_url --data_dir=`禁用语音命令数据集的下载并访问您自己的数据集,其中每个命令应命名为自己的文件夹,其中应包含 1000-2000 个音频剪辑,大约需要 1 秒钟的长度; 如果音频片段更长,则可以相应地更改`--clip_duration_ms`参数值。 有关更多详细信息,请参见`train.py`源代码和 TensorFlow 简单音频识别教程。 3. 如果您接受`train.py`的所有默认参数,则在下载 1.48GB 语音命令数据集之后,在 GTX-1070 GPU 驱动的 Ubuntu 上,完成 18,000 个步骤的整个训练大约需要 90 分钟。 训练完成后,您应该在`/tmp/speech_commands_train`文件夹内看到检查点文件的列表,以及`conv.pbtxt`图形定义文件和名为`conv_labels.txt`的标签文件,其中包含命令列表(与命令列表相同)。 `--wanted_words`参数是默认值或设置为,在文件的开头加上两个附加词`_silence`和`_unknown`): ```py -rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:08 conv.ckpt-18000.meta -rw-rw-r-- 1 jeff jeff 433 Dec 9 21:08 checkpoint -rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:08 conv.ckpt-18000.data-00000-of-00001 -rw-rw-r-- 1 jeff jeff 315 Dec 9 21:08 conv.ckpt-18000.index -rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:08 conv.ckpt-17900.meta -rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:08 conv.ckpt-17900.data-00000-of-00001 -rw-rw-r-- 1 jeff jeff 315 Dec 9 21:08 conv.ckpt-17900.index -rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:07 conv.ckpt-17800.meta -rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:07 conv.ckpt-17800.data-00000-of-00001 -rw-rw-r-- 1 jeff jeff 315 Dec 9 21:07 conv.ckpt-17800.index -rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:07 conv.ckpt-17700.meta -rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:07 conv.ckpt-17700.data-00000-of-00001 -rw-rw-r-- 1 jeff jeff 315 Dec 9 21:07 conv.ckpt-17700.index -rw-rw-r-- 1 jeff jeff 75437 Dec 9 21:06 conv.ckpt-17600.meta -rw-rw-r-- 1 jeff jeff 3707448 Dec 9 21:06 conv.ckpt-17600.data-00000-of-00001 -rw-rw-r-- 1 jeff jeff 315 Dec 9 21:06 conv.ckpt-17600.index -rw-rw-r-- 1 jeff jeff 60 Dec 9 19:41 conv_labels.txt -rw-rw-r-- 1 jeff jeff 121649 Dec 9 19:41 conv.pbtxt ``` `conv_labels.txt`包含以下命令: ```py _silence_ _unknown_ yes no up down left right on off stop go ``` 现在运行以下命令,将图形定义文件和检查点文件组合成一个我们可以在移动应用程序中使用的模型文件: ```py python tensorflow/examples/speech_commands/freeze.py \ --start_checkpoint=/tmp/speech_commands_train/conv.ckpt-18000 \ --output_file=/tmp/speech_commands_graph.pb ``` 4. (可选)在移动应用程序中部署`speech_commands_graph.pb`模型文件之前,可以使用以下命令对其进行快速测试: ```py python tensorflow/examples/speech_commands/label_wav.py \ --graph=/tmp/speech_commands_graph.pb \ --labels=/tmp/speech_commands_train/conv_labels.txt \ --wav=/tmp/speech_dataset/go/9d171fee_nohash_1.wav ``` 您将看到类似以下的输出: ```py go (score = 0.48427) no (score = 0.17657) _unknown_ (score = 0.08560) ``` 5. 使用`summarize_graph`工具查找输入节点和输出节点的名称: ```py bazel-bin/tensorflow/tools/graph_transforms/summarize_graph --in_graph=/tmp/speech_commands_graph.pb ``` 输出应如下所示: ```py Found 1 possible inputs: (name=wav_data, type=string(7), shape=[]) No variables spotted. Found 1 possible outputs: (name=labels_softmax, op=Softmax) ``` 不幸的是,它仅对于输出名称是正确的,并且不显示其他可能的输入。 使用`tensorboard --logdir /tmp/retrain_logs`,然后在浏览器中打开`http://localhost:6006`与图形进行交互也无济于事。 但是,前面各章中显示的小代码段可以帮助您了解输入和输出名称,以下内容与 iPython 进行了交互: ```py In [1]: import tensorflow as tf In [2]: g=tf.GraphDef() In [3]: g.ParseFromString(open("/tmp/speech_commands_graph.pb","rb").read()) In [4]: x=[n.name for n in g.node] In [5]: x Out[5]: [u'wav_data', u'decoded_sample_data', u'AudioSpectrogram', ... u'MatMul', u'add_2', u'labels_softmax'] ``` 因此,我们看到`wav_data`和`decoded_sample_data`都是可能的输入。 如果在`freeze.py`文件中看不到注释,我们就必须深入研究模型训练代码,以准确找出应该使用的输入名称:“结果图包含一个名为 WAV 的编码数据输入 `wav_data`,用于原始 PCM 数据(在 -1.0 到 1.0 范围内浮动)的一种称为`decoded_sample_data`,输出称为`labels_softmax`。” 实际上,在该模型的情况下,有一个 TensorFlow Android 示例应用程序,这是我们在第 1 章,“移动 TensorFlow 入门”中看到的一部分,称为 TF 语音,专门定义了那些输入名称和输出名称。 在本书后面的几章中,您将看到如何在需要时借助或不借助我们的三种方法来查找模型训练的源代码,以找出关键的输入和输出节点名称。 或者希望,当您阅读本书时,TensorFlow `summarize_graph`工具将得到改进,以为我们提供准确的输入和输出节点名称。 现在是时候在移动应用中使用我们的热门新模型了。 # 在 Android 中使用简单的语音识别模型 位于`tensorflow/example/android`的用于简单语音命令识别的 TensorFlow Android 示例应用程序具有在`SpeechActivity.java`文件中进行音频记录和识别的代码,假定该应用程序需要始终准备好接受新的音频命令。 尽管在某些情况下这确实是合理的,但它导致的代码比仅在用户按下按钮后才进行记录和识别的代码要复杂得多,例如 Apple 的 Siri 的工作方式。 在本部分中,我们将向您展示如何创建新的 Android 应用并添加尽可能少的代码来记录用户的语音命令并显示识别结果。 这应该可以帮助您更轻松地将模型集成到自己的 Android 应用中。 但是,如果您需要处理语音命令应始终自动记录和识别的情况,则应查看 TensorFlow 示例 Android 应用。 # 使用模型构建新应用 执行以下步骤来构建一个完整的新 Android 应用,该应用使用我们在上一节中构建的`speech_commands_graph.pb`模型: 1. 通过接受前面几章中的所有默认设置,创建一个名为`AudioRecognition`的新 Android 应用,然后将`compile 'org.tensorflow:tensorflow-android:+'`行添加到应用`build.gradle`文件依赖项的末尾。 2. 将``添加到应用程序的`AndroidManifest.xml`文件中,以便可以允许该应用程序记录音频。 3. 创建一个新的资产文件夹,然后将在上一节的步骤 2 和 3 中生成的`speech_commands_graph.pb`和`conv_actions_labels.txt`文件拖放到`assets`文件夹中。 4. 更改`activity_main.xml`文件以容纳三个 UI 元素。 第一个是用于识别结果显示的`TextView`: ```py ``` 第二个`TextView`将显示上一节第 2 步中使用`train.py` Python 程序训练的 10 个默认命令: ```py ``` 最后一个 UI 元素是一个按钮,在点击该按钮时,它会开始录音一秒钟,然后将录音发送到我们的模型以进行识别: ```py