当你说出“打开”时,从嘴里出来的是一道空气的波。麦克风把它变成一串数字。然后,在某个地方,有什么东西判定这串数字的意思是“打开”。这中间发生的并不是计算机理解了语言——而是某种朴素得多、也巧妙得多的东西。
首先,声音是什么?
呼气时,来自肺部的气流使喉部的声带振动:男性大约每秒一百到一百五十次,女性大约两百到两百五十次。由此产生的原始嗡鸣再经过咽喉、鼻腔和口腔的塑造。
这个区分很重要。声带产生的声源和口腔施加的滤波是两回事。区分一个元音和另一个元音的,不是振动的快慢,而是口腔在那一刻的形状。而这正是需要教给机器的东西。
从声波到数字
要把连续的波送进计算机,就必须每隔一段时间采样一次。经典法则告诉我们:只要采样率高于信号中最高频率的两倍,就能从样本中无损地重建原来的波。看得足够勤,就什么也不会丢。
接着要对信号进行清理:切成很短的窗口,再通过滤波器。目的是减少录音时混入的噪声,留下更稳定、便于处理的信号。
语音从哪里开始?
麦克风一直在录音,但真正说出来的内容只占录音的一小部分,其余都是静音和环境噪声。系统必须先找到语音所在的位置。
两个简单的指标就够了:一段信号的能量,以及信号穿过零点的次数。静音时能量很低。在嘶嘶作响的辅音里,能量低但过零率高。把两者结合起来看,就能出奇准确地把语音和噪声分开。
把声音压缩成几个数字
一秒钟的音频有成千上万个样本。直接比较它们既繁重又没有意义。取而代之的做法是,把每个短窗口压缩成几个系数,用来描述那一瞬间声道的形状。这种方法叫作线性预测编码。
它背后的想法很美:尝试把每个样本预测为它前面几个样本的加权和。携带信息的不是预测得有多好,而是哪些权重让预测变好。这些权重编码了声道的几何形状。成千上万个数字就这样变成了几个系数。
同一个词,不同的语速
现在来看真正的难题。同一个人把“打开”说了两遍,语速不同。两段录音长度不同,峰值也落在不同的位置。逐点比较行不通。
解决办法是动态时间规整:允许时间伸缩。算法寻找一条路径,把一个序列的开头连到另一个序列的开头、结尾连到结尾,并使沿途的总差异最小。这样,一段录音里拖长的音节就能与另一段里短促的音节对应起来。
直观理解 拉伸时间
想象两条橡皮带,上面写着同一首歌,其中一条被拉长了。把它们并排放好,找出让字母对齐所需的最少拉伸。动态时间规整做的正是这件事,只不过对象是数字。
系统为每条指令保存一个模板。新的语音进来时,会与所有模板逐一比较,代价最低的那个胜出。如果这个代价低于设定的阈值,指令就算被识别;否则,系统会老实地说它不知道。
今天的情况如何?
这套架构是为识别十五条土耳其语指令而设计的——数字零到九,再加上打开、关闭、删除、取消和搜索。对于小而封闭的词汇表,它至今仍然绰绰有余。
今天的语音助手不再匹配模板,而是使用在数百万小时语音上训练的神经网络。差别不只在规模:新系统不受词汇表束缚,还能解析从未听过的句子。
然而,链条最前面的几环并没有改变。找到语音从哪里开始,把信号压缩成有意义的系数,容忍时间上的偏移——这些仍以某种形式存在于每一个语音系统之中。
本文依据什么?
本文基于 Selim Kusursuz 的综述 Turkish Voice Recognition(2019)及其中考察的模型。
- Rabiner, Schafer, Digital Processing of Speech Signals.
- Baygün, Development of a voice recognition system for Turkish commands, master's thesis, Pamukkale University, 2006.
- LeCun, Bengio, Hinton, Deep learning, Nature, 2015.