机器学习并不总是一个黑盒
如果你知道神经机器翻译是如何工作的,那么你可能会猜到,我们可以简单地将声音送入神经网络中,并训练使之生成文本:
一个大问题是语速不同。
一个人可能很快地说出「hello!」而另一个人可能会非常缓慢地说「heeeelllllllllllllooooo!」。
这产生了一个更长的声音文件,也产生了更多的数据。
这两个声音文件都应该被识别为完全相同的文本「hello!」而事实证明,把各种长度的音频文件自动对齐到一个固定长度的文本是很难的一件事情。
为了解决这个问题,我们必须使用一些特殊的技巧,并进行一些深度神经网络以外的特殊处理。让我们看看它是如何工作的吧!
将声音转换成比特(Bit)
声音是作为波(wave) 的形式传播的。
我们如何将声波转换成数字呢?让我们使用我说的「hello」这个声音片段举个例子:
![wave](https://upload-images.jianshu.io/upload_images/6071194-702a318c4ee2e8f4.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
我说「hello」的波形
声波是一维的,它在每个时刻都有一个基于其高度的值(声波其实是二维的,有时间,还有振幅(即这个基于高度的值))。
让我们把声波的一小部分放大看看:
![部分](https://upload-images.jianshu.io/upload_images/6071194-32bee2a179f4a9e2.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
为了将这个声波转换成数字,我们只记录声波在等距点的高度:
![等高点](https://upload-images.jianshu.io/upload_images/6071194-85c87f12b1cc7848.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
给声波采样
这被称为采样(sampling)。我们每秒读取数千次,并把声波在该时间点的高度用一个数字记录下来。这基本上就是一个未压缩的 .wav 音频文件。
「CD 音质」的音频是以 44.1khz(每秒 44100 个读数)进行采样的。
但对于语音识别,16khz(每秒 16000 个采样)的采样率就足以覆盖人类语音的频率范围了。
让我们把「Hello」的声波每秒采样 16000 次。
数字采样小助手
因为声波采样只是间歇性的读取,你可能认为它只是对原始声波进行粗略的近似估计。
我们的读数之间有间距,所以我们必然会丢失数据,对吧?
![数字采样](https://upload-images.jianshu.io/upload_images/6071194-882594bd33279701.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
数字采样能否完美重现原始声波?那些间距怎么办?
但是,由于采样定理,我们知道我们可以利用数学,从间隔的采样中完美重建原始声波——只要我们的采样频率比期望得到的最高频率快至少两倍就行。
我提这一点,是因为几乎每个人都会犯这个错误,并误认为使用更高的采样率总是会获得更好的音频质量。
其实并不是。
预处理我们的采样声音数据
我们现在有一个数列,其中每个数字代表 1/16000 秒的声波振幅。
我们可以把这些数字输入到神经网络中,但是试图直接分析这些采样来进行语音识别仍然很困难。
相反,我们可以通过对音频数据进行一些预处理来使问题变得更容易。
让我们开始吧,首先将我们的采样音频分成每份 20 毫秒长的音频块。这是我们第一个 20 毫秒的音频(即我们的前 320 个采样):
将这些数字绘制为简单的折线图,我们就得到了这 20 毫秒内原始声波的大致形状:
![折线图](https://upload-images.jianshu.io/upload_images/6071194-deb39b4e6c410b90.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
虽然这段录音只有 1/50 秒的长度,但即使是这样短暂的录音,也是由不同频率的声音复杂地组合在一起的。
其中有一些低音,一些中音,甚至有几处高音。
但总的来说,就是这些不同频率的声音混合在一起,才组成了人类的语音。
为了使这个数据更容易被神经网络处理,我们将把这个复杂的声波分解成一个个组成部分。
我们将分离低音部分,再分离下一个最低音的部分,以此类推。然后将(从低到高)每个频段(frequency band)中的能量相加,我们就为各个类别的音频片段创建了一个指纹(fingerprint)。
想象你有一段某人在钢琴上演奏 C 大调和弦的录音。
这个声音是由三个音符组合而成的:C、E 和 G。
它们混合在一起组成了一个复杂的声音。我
们想把这个复杂的声音分解成单独的音符,以此来分辨 C、E 和 G。这和语音识别是一样的道理。
傅里叶变换
我们需要傅里叶变换(FourierTransform)来做到这一点。
它将复杂的声波分解为简单的声波。
一旦我们有了这些单独的声波,我们就将每一份频段所包含的能量加在一起。
最终得到的结果便是从低音(即低音音符)到高音,每个频率范围的重要程度。
以每 50hz 为一个频段的话,我们这 20 毫秒的音频所含有的能量从低频到高频就可以表示为下面的列表:
列表中的每个数字表示那份 50Hz 的频段所含的能量
不过,把它们画成这样的图表会更加清晰
![音频](https://upload-images.jianshu.io/upload_images/6071194-ed20e32be1086e28.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
你可以看到,在我们的 20 毫秒声音片段中有很多低频能量,然而在更高的频率中并没有太多的能量。
这是典型「男性」的声音。
如果我们对每 20 毫秒的音频块重复这个过程,我们最终会得到一个频谱图(每一列从左到右都是一个 20 毫秒的块):
![波形](https://upload-images.jianshu.io/upload_images/6071194-76193c05fa885fc2.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
「hello」声音剪辑的完整声谱
频谱图很酷,因为你可以在音频数据中实实在在地看到音符和其他音高模式。
对于神经网络来说,相比于原始声波,从这种数据中寻找规律要容易得多。
因此,这就是我们将要实际输入到神经网络中去的数据表示方式。
从短声音识别字符
现在我们有了格式易于处理的音频,我们将把它输入到深度神经网络中去。
神经网络的输入将会是 20 毫秒的音频块。
对于每个小的音频切片(audio slice),神经网络都将尝试找出当前正在说的声音所对应的字母。
![从短声音识别字符](https://upload-images.jianshu.io/upload_images/6071194-49a22f3f766cb272.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
我们将使用一个循环神经网络——即一个拥有记忆,能影响未来预测的神经网络。
这是因为它预测的每个字母都应该能够影响它对下一个字母的预测。
例如,如果我们到目前为止已经说了「HEL」,那么很有可能我们接下来会说「LO」来完成「Hello」。
我们不太可能会说「XYZ」之类根本读不出来的东西。
因此,具有先前预测的记忆有助于神经网络对未来进行更准确的预测。
当通过神经网络跑完我们的整个音频剪辑(一次一块)之后,我们将最终得到一份映射(mapping),其中标明了每个音频块和其最有可能对应的字母。
这是我说那句「Hello」所对应的映射的大致图案:
![预测](https://upload-images.jianshu.io/upload_images/6071194-fa8eb733d6091f53.png?imageMogr2/auto-orient/strip | imageView2/2/w/600/format/webp) |
我们的神经网络正在预测我说的那个词很有可能是「HHHEE_LL_LLLOOO」。但它同时认为我说的也可能是「HHHUU_LL_LLLOOO」,或者甚至是「AAAUU_LL_LLLOOO」。
我们可以遵循一些步骤来整理这个输出。首先,我们将用单个字符替换任何重复的字符:
· HHHEE_LL_LLLOOO 变为 HE_L_LO
· HHHUU_LL_LLLOOO 变为 HU_L_LO
· AAAUU_LL_LLLOOO 变为 AU_L_LO
然后,我们将删除所有空白:
· HE_L_LO 变为 HELLO
· HU_L_LO 变为 HULLO
· AU_L_LO 变为 AULLO
这让我们得到三种可能的转写——「Hello」、「Hullo」和「Aullo」。如果你大声说出这些词,所有这些声音都类似于「Hello」。因为神经网络每次只预测一个字符,所以它会得出一些纯粹表示发音的转写。
例如,如果你说「He would not go」,它可能会给出一个「He wud net go」的转写。
在我们可能的转写「Hello」、「Hullo」和「Aullo」中,显然「Hello」将更频繁地出现在文本数据库中(更不用说在我们原始的基于音频的训练数据中了),因此它可能就是正解。所以我们会选择「Hello」作为我们的最终结果,而不是其他的转写。