4、唤醒词检测算法:从模板匹配到深度学习
唤醒词检测,说白了就是让设备一直「竖着耳朵」听,听到特定词才醒过来。这个环节对功耗极其敏感——你想想看,设备可能一天要监听十几万次声音,每次都要做计算。算法选不好,电池撑不过三天。
我个人习惯把唤醒词算法分成三代:第一代是模板匹配,第二代是统计模型,第三代是深度学习。今天咱们就挨个聊透。
4.1 基于模板匹配(DTW)
DTW,全称Dynamic Time Warping,动态时间规整。这玩意儿是上世纪的老古董了,但我在一些超低功耗的MCU项目里还在用。为什么?因为它简单到极致。
核心思想:把唤醒词的语音特征存成一个模板。每次听到声音,就提取特征,跟模板做匹配。匹配得分超过阈值,就认为检测到了。
这里有个关键问题:同一个人说同一个词,每次的语速、音调都不一样。DTW解决的就是这个——它能把两个语音序列在时间轴上「扭一扭」,让它们对齐。
DTW的匹配过程:
- 提取语音的MFCC特征(梅尔频率倒谱系数)
- 计算待测序列与模板序列的距离矩阵
- 用动态规划找出一条最优路径
- 路径的累积距离就是匹配得分
// 简化的DTW距离计算(C语言伪代码)
float dtw_distance(float *test, int len_test, float *ref, int len_ref) {
float D[len_test+1][len_ref+1];
// 初始化边界
for (i = 0; i <= len_test; i++) D[i][0] = INF;
for (j = 0; j <= len_ref; j++) D[0][j] = INF;
D[0][0] = 0;
// 动态规划填表
for (i = 1; i <= len_test; i++) {
for (j = 1; j <= len_ref; j++) {
float cost = euclidean_dist(test[i-1], ref[j-1]);
D[i][j] = cost + min(D[i-1][j], D[i][j-1], D[i-1][j-1]);
}
}
return D[len_test][len_ref];
}
优点:
- 不需要训练数据,录几句就能用
- 计算量小,在Cortex-M0上都能跑
- 内存占用低,几十KB就够
缺点:
- 对噪声敏感——我在做空调遥控器项目时,风扇一开,误唤醒率直接翻倍
- 不同人说的同一词,匹配效果差异大
- 模板多了以后,存储和计算都扛不住
我的经验:DTW适合做「单用户、安静环境」的唤醒。比如智能手表上的「嘿,手表」,用户就一个人用,环境相对安静。这时候DTW的功耗优势就体现出来了——整个检测流程只需要几毫秒,电流不到1mA。
4.2 基于高斯混合模型(GMM)
GMM比DTW进了一步。它不再存一个固定的模板,而是用多个高斯分布去拟合语音特征的分布。说白了,就是给「唤醒词」和「非唤醒词」各建一个概率模型。
为什么用GMM? 你想想看,同一个词,不同人说的特征分布其实是有规律的。GMM能捕捉这种统计规律,比DTW的单一模板鲁棒得多。
我在做智能音箱项目时,一开始用的DTW,结果家里三个人,只有我喊得醒。换成GMM后,全家人都能用了。
GMM训练流程:
- 收集唤醒词音频(正样本)和各类噪声、非唤醒词(负样本)
- 提取MFCC特征,每帧通常39维(13维+一阶差分+二阶差分)
- 用EM算法训练两个GMM:一个给唤醒词,一个给非唤醒词
- 推理时计算似然比:P(特征|唤醒词) / P(特征|非唤醒词)
// GMM似然计算(简化版)
float gmm_likelihood(float *feature, int n_mix, float *weights,
float *means, float *covars) {
float prob = 0.0;
for (int m = 0; m < n_mix; m++) {
float diff = feature[0] - means[m];
float gauss = exp(-0.5 * diff * diff / covars[m]);
prob += weights[m] * gauss;
}
return prob;
}
GMM的优缺点:
| 维度 | 说明 |
|---|---|
| 优点 | 比DTW鲁棒,能适应不同说话人;计算量适中;模型大小可控(通常几十KB到几百KB) |
| 缺点 | 需要标注数据训练;对非平稳噪声(比如突然的关门声)处理不好;无法捕捉时序信息 |
避坑指南:我曾经在GMM训练时犯过一个低级错误——正负样本比例严重失衡。唤醒词只有100条,非唤醒词却有10000条。结果模型学到的全是「说啥都不像唤醒词」。后来我强制把负样本采样到正样本的3倍以内,效果才正常。
4.3 基于深度神经网络(DNN)
DNN是当前的主流方案。它把唤醒词检测当成一个二分类问题:输入是语音特征,输出是「是唤醒词」或「不是唤醒词」的概率。
为什么DNN能赢? 因为它能自动学习特征之间的高阶组合关系。GMM只能看到「当前帧的特征」,DNN能看到「过去几十帧的上下文」。这对唤醒词检测来说太重要了——你喊「小爱同学」,四个字之间的关联性,DNN能学得明明白白。
典型的DNN唤醒词模型结构:
- 输入层:40维FBank特征,取20帧上下文,共800维
- 隐藏层:3~5层全连接,每层128~256个神经元,激活函数用ReLU
- 输出层:2个神经元(唤醒词/非唤醒词),Softmax归一化
- 参数量:通常在50万~200万之间
# 用Keras构建一个简单的DNN唤醒词模型
from keras.models import Sequential
from keras.layers import Dense, Dropout
model = Sequential()
model.add(Dense(256, activation='relu', input_shape=(800,)))
model.add(Dropout(0.3))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.3))
model.add(Dense(64, activation='relu'))
model.add(Dense(2, activation='softmax'))
model.compile(optimizer='adam', loss='categorical_crossentropy')
DNN的优缺点:
| 维度 | 说明 |
|---|---|
| 优点 | 准确率最高,误唤醒率最低;能适应复杂噪声环境;支持多说话人 |
| 缺点 | 计算量大,需要硬件加速(DSP或NPU);模型文件大(几MB到几十MB);训练需要大量数据 |
我的建议:如果你做的是电池供电的设备,比如TWS耳机、智能手表,我建议用「两级唤醒」策略。第一级用DTW或GMM做粗检,功耗极低;只有粗检通过后,才启动DNN做精检。这样平均功耗能降低80%以上。我在一个耳机项目里就是这么干的,待机电流从2mA降到了0.3mA。
4.4 三种算法的对比与选型
说了这么多,到底该选哪个?我整理了一张表,你一看就明白:
| 维度 | DTW | GMM | DNN |
|---|---|---|---|
| 准确率 | 低(70%~80%) | 中(85%~92%) | 高(95%~99%) |
| 计算量 | 极低(<1 MIPS) | 低(1~10 MIPS) | 高(100~1000 MIPS) |
| 内存需求 | <10 KB | 10~100 KB | 1~10 MB |
| 训练数据需求 | 0(无需训练) | 几百条 | 几万条 |
| 适用场景 | 超低功耗MCU | 中端MCU/DSP | 带NPU的SoC |
选型建议:
- 如果你用的是Cortex-M0/M3,RAM不到64KB——老老实实用DTW
- 如果是Cortex-M4/M7,有DSP指令——GMM是性价比之选
- 如果芯片带了NPU或者你不在乎功耗——直接上DNN,省心
最后提醒一句:不管选哪种算法,一定要在真实场景下做测试。我在实验室里GMM跑得好好的,结果放到工厂车间里,误唤醒率从1%飙到了30%。噪声环境变了,一切都要重新调。嗯,这个坑我替你们踩过了。
下一章咱们聊聊「低功耗语音前端设计」,包括麦克风选型、模拟前端、ADC采样策略这些硬件层面的东西。这些做好了,算法才能发挥最大价值。