🧠 第六章 自然语言处理
📄 一句话概括

8 类合成音节,24 条训练、16 条测试,4 维手工特征配最近邻:不加额外噪声时 16 / 16 全对; 噪声加到信号标准差的 3% 掉到 0.875,5% 掉到 0.500,10% 只剩 0.125。

📝 本节和前面几节的关系

上一节做文字问答,这一节做声音。语音识别是 NLP 里最不受文本技巧影响的一块,它更像信号处理。

一、声音要变成文字,中间隔着三层

  1. 波形。每秒 16000 个采样点,一段 3 秒的话就是 48000 个数。
  2. 特征。分帧之后每帧算一组数(能量、过零率、频谱形状)。这一步把 48000 个数压成几百组。
  3. 声学模型。把特征序列映射成音素或字。这一步才需要训练,也是沙箱里做不了的部分。 Demo 做的是前两层加一个最近邻分类器。它证明「特征能分开合成的音节」,不证明「能识别真人说话」。 这个区别必须说清,否则读者会以为语音识别就这么简单。

二、四个手工特征各管什么

对数能量:这一帧有多响。区分有声和无声最直接。 过零率:波形穿过零点的频繁程度。浊音(a、o)低,清音(s、f)高。 谱质心:频谱能量的重心,粗略对应「音高」。 谱滚降:85% 的能量集中在这个频率以下,反映高频成分多少。 四个数就把一帧说完了。 真实系统用 13 维 MFCC 加一阶二阶差分,共 39 维,原理是同一类——都是把频谱的形状压成几个数。

三、干净时 100%,加 3% 噪声就开始垮

不加额外噪声时 16 / 16。这个满分没什么可夸的:8 个类别只差基频和谐波数,特征上分得干干净净。 噪声按信号标准差的百分比加进去,同一套代码同一套特征: 1% 和 2% 还是 16/16,3% 掉到 14 / 16,5% 掉到 8 / 16,10% 只剩 2 / 16。 垮得这么快,原因在特征本身。 谱质心和过零率对宽带噪声极敏感——一点噪声就把高频段填满,质心立刻往上跳。 更麻烦的是标准化:训练集里谱质心几乎不变,标准差很小,一标准化,这点波动被放大几十倍, 直接盖过其他维度。

四、真 ASR 靠什么扛住噪声

三条,都是 Demo 没有的: 1. 在噪声数据上训练。训练集里就混入各种噪声,模型学会忽略它。 2. 用神经网络代替手工特征。让它自己从波形里学抗噪的表示,不用人猜哪个特征是稳的。 3. 语言模型纠错。声学模型听错「四是四」,语言模型能根据上下文改回来。 所以真实语音识别的准确率不是靠某一个环节,而是三环叠加。任何一环单独拿出来看,都不惊艳。 这一节能带走的结论是:手工特征在实验室里够用,在真实环境里不够。 这也是深度学习在语音领域全面取代传统方法的原因。

五、动手 Demo:音节识别:4 维手工特征加最近邻,噪声一过 3% 就垮

合成 8 类音节(基频 4 档 × 谐波 2 档),每类 3 条做训练、2 条做测试; 每段算对数能量、过零率、谱质心、谱滚降四个特征,标准化后做最近邻分类; 先看干净的一档,再把宽带噪声按信号标准差的 1% 到 20% 逐档加进去,打出每档的准确率。

sp = np.abs(np.fft.rfft(x * np.hanning(len(x))))
cen = float((sp * fr).sum() / sp.sum())        # 谱质心
cum = np.cumsum(sp) / sp.sum()
roll = float(fr[int(np.searchsorted(cum, 0.85))])

q = (feat(x) - mu) / sd                        # 标准化
pred = ytr[int(np.argmin(((Z - q) ** 2).sum(1)))]

实跑输出:

--------------------------------------------------------------------
语音识别链路:从波形到音节模板(不是真 ASR)
--------------------------------------------------------------------
  采样率                   8000 Hz,每段 0.25 秒
  类别数                   8(基频 4 档 × 谐波 2 档)
  训练样本                 24 条(每类 3 条)
  测试样本                 16 条(每类 2 条,另加宽带噪声)
  特征                     对数能量、过零率、谱质心、谱滚降(4 维)

  先看干净的一档(不加额外噪声):
  最近邻准确率             16 / 16 = 1.000
  类别           测试条数   答对
  -----------------------------
  f0=150 谐波1          2      2
  f0=150 谐波3          2      2
  f0=220 谐波1          2      2
  f0=220 谐波3          2      2
  f0=300 谐波1          2      2
  f0=300 谐波3          2      2
  f0=420 谐波1          2      2
  f0=420 谐波3          2      2

  再把宽带噪声按信号标准差的百分比加进去,同一套代码同一套特征:
  噪声(占信号标准差)      答对   准确率
  --------------------------------------
                    0%   16 / 16    1.000
                    1%   16 / 16    1.000
                    2%   16 / 16    1.000
                    3%   14 / 16    0.875
                    5%    8 / 16    0.500
                   10%    2 / 16    0.125
                   20%    2 / 16    0.125

  曲线在 3% 之后就垮。垮得这么快,原因在特征本身:谱质心和过零率对宽带噪声极敏感——一点噪声就把高频段填满,质心立刻往上跳,而训练集里这个维度几乎不变,标准化之后它的波动被放大了几十倍。
  「合成音上 100%」这句话本身没什么价值:类别只差基频和谐波数,本来就分得开。真 ASR 要处理口音、连读、混响、说话人差异,沙箱里没有声学模型,那个数我给不了。

干净那一档 8 个类别全对,每类 2 条。这个结果说明特征和流程是通的,不说明方法强。 噪声扫描表是这一节的重点:0%、1%、2% 都是 1.000,3% 掉两个,5% 掉一半,10% 只剩两个。 曲线不是缓慢下滑,是断崖。 断崖的原因我写在正文里了——谱质心这个维度在训练集里几乎不变,标准化把它的波动放大了。 如果改用分频带对数能量(比如 0~250、250~500、500~1000、1000~4000 Hz 四段),抗噪会好一些, 但也就是推到 5% 到 10% 之间,不会质变。

30 秒实验

  1. 把噪声档位加密到 3%、4%、5%,看准确率是怎么一档一档掉的。
  2. 把谱质心这个特征删掉,只用三个特征,看抗噪有没有变好。
  3. 想一想:为什么在噪声数据上训练能提高抗噪能力?

常见误区

  1. 「语音识别就是分类」——分类只是最后一步,前面还有波形、分帧、特征。
  2. 「手工特征够用了」——合成音上够用,真人带噪环境差得远。
  3. 「准确率 100% 说明方法好」——类别只差基频,本来就分得开。
自测三问(点开对答案) 1. 语音识别中间那三层是什么? —— 波形、特征、声学模型。沙箱里只能做前两层加一个简单分类器。 2. 过零率用来区分什么? —— 浊音和清音:浊音波形慢、过零率低;清音接近噪声、过零率高。 3. 为什么噪声一过 3% 准确率就断崖? —— 谱质心和过零率对宽带噪声极敏感,标准化之后这点波动被放大几十倍。

声音处理完,下一节回到文字,做一件更见功力的事:翻译。

相关笔记