3. 特征提取与声学模型:MFCC、FilterBank 特征提取;DNN/CNN/RNN 声学模型选型与训练技巧

好,咱们进入正题。特征提取和声学模型,说白了就是唤醒词的「耳朵」和「大脑」。耳朵要听得清,大脑要想得明白。这两块要是没配合好,后面再怎么调阈值、做后处理,都是白搭。

我个人习惯把这一章看作是整个唤醒系统的「地基」。地基不稳,房子盖得再高也得塌。我在项目里见过太多团队,一上来就堆模型、调参数,结果发现连最基础的 MFCC 参数都没设对——嗯,这种坑我踩过,所以今天咱们好好捋一遍。

3.1 特征提取:MFCC vs FilterBank

先说说特征提取。唤醒词任务里,最常用的就是 MFCC 和 FilterBank(也叫 FBank)。很多人觉得 MFCC 是「标准答案」,其实不一定。

核心观点:唤醒词这种短时、小模型的任务,FBank 往往比 MFCC 更合适。为什么?因为 MFCC 的 DCT 变换会丢掉一些非线性信息,而这些信息对区分「嗨小爱」和「嗨小艾」这种细微差别,恰恰很重要。

3.1.1 MFCC 提取流程

MFCC 的流程其实不复杂,但每一步都有坑。我列个标准流程,你对照着看:

  1. 预加重:提升高频分量。公式是 y(t) = x(t) - α·x(t-1),α 一般取 0.97。别小看这一步,我遇到过不加预加重,唤醒率直接掉 5% 的情况。
  2. 分帧:帧长 25ms,帧移 10ms。唤醒词一般用 16kHz 采样率,算下来一帧 400 个点。
  3. 加窗:汉明窗。别用矩形窗,频谱泄露会让你怀疑人生。
  4. FFT:一般做 512 点或 1024 点。点数越大,频率分辨率越高,但计算量也上去了。
  5. Mel 滤波器组:40 个滤波器。这里有个细节——滤波器个数不是越多越好,我试过 80 个,效果反而变差了。
  6. 取对数:模拟人耳对声音的非线性感知。
  7. 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 训练策略:从小处着手

我训练唤醒词模型有个「三步走」策略:

  1. 预训练:用大规模语音数据(比如 LibriSpeech、Common Voice)做预训练,让模型学会通用的语音表征。这一步不是必须的,但数据量少的时候很有用。
  2. 微调:用你的唤醒词数据做微调。学习率要小,一般是预训练学习率的 1/10。
  3. 硬负样本挖掘:把模型在测试集上误唤醒的样本收集起来,加到训练集里重新训练。我一般做 2-3 轮,效果提升很明显。

我曾经犯过的错:一开始做硬负样本挖掘时,我把所有误唤醒样本都加进去了,结果模型过拟合到这些样本上,换了一批测试集又不行了。后来我改成只加「高置信度误唤醒」的样本(模型打分 > 0.8 的负样本),效果才稳定下来。

3.4 本章知识体系

下面这张图把本章的核心逻辑串起来了。你可以对照着回顾一下:

特征提取与声学模型知识体系 特征提取与声学模型知识体系 原始语音信号 特征提取:MFCC vs FBank MFCC(13维) FBank(40维) DNN(全连接) CNN(卷积) RNN(LSTM/GRU) 训练技巧:增强 + 损失 + 策略 核心原则:特征匹配模型,模型匹配场景,训练匹配数据

你看,整个流程是串起来的。特征选错了,模型再强也白搭。模型选错了,训练技巧再好也救不回来。所以我的建议是:先想清楚你的场景——算力多少?数据多少?噪声多大?然后倒推回来选特征和模型。