3. 声学特征与信号处理
好,咱们进入正题。特征提取和声学模型,说白了就是唤醒词的「耳朵」和「大脑」。耳朵要听得清,大脑要想得明白。这两块要是没配合好,后面再怎么调阈值、做后处理,都是白搭。
我个人习惯把这一章看作是整个唤醒系统的「地基」。地基不稳,房子盖得再高也得塌。我在项目里见过太多团队,一上来就堆模型、调参数,结果发现连最基础的 MFCC 参数都没设对——嗯,这种坑我踩过,所以今天咱们好好捋一遍。
3.1 特征提取:MFCC vs FilterBank
先说说特征提取。唤醒词任务里,最常用的就是 MFCC 和 FilterBank(也叫 FBank)。很多人觉得 MFCC 是「标准答案」,其实不一定。
核心观点:唤醒词这种短时、小模型的任务,FBank 往往比 MFCC 更合适。为什么?因为 MFCC 的 DCT 变换会丢掉一些非线性信息,而这些信息对区分「嗨小爱」和「嗨小艾」这种细微差别,恰恰很重要。
3.1.1 MFCC 提取流程
MFCC 的流程其实不复杂,但每一步都有坑。我列个标准流程,你对照着看:
- 预加重:提升高频分量。公式是 y(t) = x(t) - α·x(t-1),α 一般取 0.97。别小看这一步,我遇到过不加预加重,唤醒率直接掉 5% 的情况。
- 分帧:帧长 25ms,帧移 10ms。唤醒词一般用 16kHz 采样率,算下来一帧 400 个点。
- 加窗:汉明窗。别用矩形窗,频谱泄露会让你怀疑人生。
- FFT:一般做 512 点或 1024 点。点数越大,频率分辨率越高,但计算量也上去了。
- Mel 滤波器组:40 个滤波器。这里有个细节——滤波器个数不是越多越好,我试过 80 个,效果反而变差了。
- 取对数:模拟人耳对声音的非线性感知。
- DCT:这一步就是 MFCC 和 FBank 的分水岭。DCT 会做去相关和降维,通常取 13 维。
我的经验:如果你用 DNN/CNN 这类模型,建议直接用 FBank(40 维),让模型自己去学特征之间的相关性。MFCC 的 DCT 相当于帮模型做了「预处理」,反而可能限制模型的上限。
3.1.2 FBank 的优势
说白了,FBank 就是 MFCC 去掉最后一步 DCT 的结果。保留的信息更完整。我做个对比:
| 对比项 | MFCC | FBank |
|---|---|---|
| 维度 | 13 维(常用) | 40 维(常用) |
| 信息完整性 | 有损(DCT 去相关) | 无损(保留全部 Mel 能量) |
| 模型适配 | 适合 GMM-HMM 等传统模型 | 适合 DNN/CNN/RNN |
| 计算量 | 稍小(维度低) | 稍大(维度高) |
| 唤醒词场景 | 一般 | 推荐 |
你看,唤醒词场景下,FBank 几乎是全面占优。我在一个智能音箱项目里做过对比,同样的模型结构,用 FBank 比 MFCC 的唤醒率高了 2.3%。别小看这 2.3%,在量产线上可能就是「通过」和「不通过」的区别。
3.2 声学模型选型:DNN / CNN / RNN
特征提取完了,接下来就是声学模型。选型这事儿,没有银弹。我根据项目经验,把三种主流结构的适用场景和坑都列出来。
3.2.1 DNN:简单粗暴,但别滥用
DNN 就是全连接层堆叠。优点是简单、训练快、部署容易。缺点是参数多、容易过拟合、对时序建模能力弱。
我建议在以下场景用 DNN:
- 唤醒词很短(比如 2 个音节)
- 算力受限(比如 MCU 上跑)
- 数据量不大(几千条)
但要注意,DNN 对输入帧数很敏感。我见过有人把 100 帧直接拉平喂给 DNN,结果模型参数量爆炸,训练都训不动。正确做法是:先做帧级别的分类,再用后处理做决策。
避坑指南:我曾经在一个项目里用 3 层 DNN,每层 512 个节点,结果在测试集上过拟合得一塌糊涂。后来改成 2 层 256 节点,加 Dropout 0.3,效果反而好了。DNN 不是越深越好,唤醒词这种简单任务,2-3 层足够了。
3.2.2 CNN:捕捉局部模式,抗噪能力强
CNN 在语音领域其实很能打。它通过卷积核在时间和频率维度上滑动,能捕捉到局部模式。比如「小」字的发音在频谱上有个明显的「下凹」模式,CNN 很容易学到。
我常用的 CNN 结构:
- 输入:40 维 FBank,时间维度 T(比如 50 帧)
- 卷积层:2-3 层,3×3 或 5×5 卷积核,通道数 32→64→128
- 池化:时间维度上做全局平均池化,或者用 stride=2 的卷积代替池化
- 全连接:1-2 层,128 或 256 节点
CNN 有个好处:对说话人变化不敏感。我在做「小爱同学」唤醒时,发现 CNN 在多人场景下的表现比 DNN 稳定得多。
3.2.3 RNN(LSTM/GRU):时序建模王者
唤醒词本质上是一个时序分类问题。RNN 天生就是干这个的。LSTM 和 GRU 能记住前面几帧的信息,对「上下文」的理解比 CNN 和 DNN 强。
但 RNN 有个大问题:训练慢,部署难。尤其是在端侧设备上,RNN 的循环结构对硬件不友好。
我个人的经验是:
- 如果算力够(比如手机、智能音箱),用 GRU 或 LSTM,效果最好
- 如果算力受限(比如耳机、遥控器),用 CNN 或 DNN,别硬上 RNN
一个折中方案:用 CNN 做前端特征提取,再接一个小的 GRU 层。这样既利用了 CNN 的抗噪能力,又保留了 RNN 的时序建模能力。我在一个车载项目里用这个方案,唤醒率从 92% 提到了 96%。
3.3 训练技巧:让模型真正「学进去」
模型结构选好了,接下来就是训练。这部分我踩过的坑最多,挑几个重点说。
3.3.1 数据增强:别让模型「死记硬背」
唤醒词模型最容易犯的错就是过拟合。你想想看,训练数据都是在安静环境下录的,到了真实场景,各种噪声、混响、远场,模型直接就懵了。
我常用的数据增强手段:
- 加噪:信噪比 5-20dB 随机,噪声类型包括白噪声、粉红噪声、人声嘈杂、电视声等
- 混响:用 RIR 模拟不同房间的混响
- 音量扰动:随机缩放 0.5-1.5 倍
- 速度扰动:变速 0.9-1.1 倍,注意要同时调整标签对齐
我的习惯:在线增强比离线增强好。在线增强是每轮训练随机做,相当于数据量无限大。离线增强虽然省事,但容易产生重复数据,模型反而会记住这些「伪样本」。
3.3.2 损失函数:别只用交叉熵
唤醒词任务有个特点:正样本(唤醒词)和负样本(非唤醒词)极度不平衡。你可能 99% 的数据都是负样本。这时候用普通的交叉熵损失,模型会倾向于把所有样本都判为负,准确率 99%,但唤醒率是 0%。
我建议的做法:
- 加权交叉熵:给正样本更高的权重,比如正:负 = 10:1
- Focal Loss:让模型更关注难分类的样本。我在一个项目里用 Focal Loss,把误唤醒率从 0.5 次/小时降到了 0.1 次/小时
- 对比学习:让正样本的 embedding 更接近,负样本的 embedding 更远。这个比较新,但效果确实好
3.3.3 训练策略:从小处着手
我训练唤醒词模型有个「三步走」策略:
- 预训练:用大规模语音数据(比如 LibriSpeech、Common Voice)做预训练,让模型学会通用的语音表征。这一步不是必须的,但数据量少的时候很有用。
- 微调:用你的唤醒词数据做微调。学习率要小,一般是预训练学习率的 1/10。
- 硬负样本挖掘:把模型在测试集上误唤醒的样本收集起来,加到训练集里重新训练。我一般做 2-3 轮,效果提升很明显。
我曾经犯过的错:一开始做硬负样本挖掘时,我把所有误唤醒样本都加进去了,结果模型过拟合到这些样本上,换了一批测试集又不行了。后来我改成只加「高置信度误唤醒」的样本(模型打分 > 0.8 的负样本),效果才稳定下来。
3.4 本章知识体系
下面这张图把本章的核心逻辑串起来了。你可以对照着回顾一下:
你看,整个流程是串起来的。特征选错了,模型再强也白搭。模型选错了,训练技巧再好也救不回来。所以我的建议是:先想清楚你的场景——算力多少?数据多少?噪声多大?然后倒推回来选特征和模型。