4、唤醒词检测算法:从模板匹配到深度学习

唤醒词检测,说白了就是让设备一直「竖着耳朵」听,听到特定词才醒过来。这个环节对功耗极其敏感——你想想看,设备可能一天要监听十几万次声音,每次都要做计算。算法选不好,电池撑不过三天。

我个人习惯把唤醒词算法分成三代:第一代是模板匹配,第二代是统计模型,第三代是深度学习。今天咱们就挨个聊透。

4.1 基于模板匹配(DTW)

DTW,全称Dynamic Time Warping,动态时间规整。这玩意儿是上世纪的老古董了,但我在一些超低功耗的MCU项目里还在用。为什么?因为它简单到极致。

核心思想:把唤醒词的语音特征存成一个模板。每次听到声音,就提取特征,跟模板做匹配。匹配得分超过阈值,就认为检测到了。

这里有个关键问题:同一个人说同一个词,每次的语速、音调都不一样。DTW解决的就是这个——它能把两个语音序列在时间轴上「扭一扭」,让它们对齐。

DTW的匹配过程

  1. 提取语音的MFCC特征(梅尔频率倒谱系数)
  2. 计算待测序列与模板序列的距离矩阵
  3. 用动态规划找出一条最优路径
  4. 路径的累积距离就是匹配得分
// 简化的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训练流程

  1. 收集唤醒词音频(正样本)和各类噪声、非唤醒词(负样本)
  2. 提取MFCC特征,每帧通常39维(13维+一阶差分+二阶差分)
  3. 用EM算法训练两个GMM:一个给唤醒词,一个给非唤醒词
  4. 推理时计算似然比: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采样策略这些硬件层面的东西。这些做好了,算法才能发挥最大价值。