4. 数据增强与预处理
模型架构选型,说白了就是给唤醒词任务挑一个最合适的“脑子”。
我做了这么多年嵌入式端的语音唤醒,见过太多“模型跑不动”或者“精度差得离谱”的案例。
选型选对了,后面优化事半功倍;选错了,后面再怎么调参都是白费力气。
今天咱们就把主流的几种架构捋一遍。
DNN、CNN、RNN、TCN、Transformer,还有轻量化模型MobileNet、SqueezeNet。
我会结合自己的项目经验,告诉你每种架构到底适合什么场景,坑在哪里。
4.1 DNN:最朴素,但也最稳
DNN(深度神经网络)就是全连接层堆叠。
结构简单,参数量可控,在资源极度受限的MCU上很常见。
我记得有一次做一个2KB RAM的唤醒词项目,只能用DNN。
输入是10帧MFCC特征,每帧13维,两层隐藏层各32个神经元。
模型大小才3.2KB,跑一次推理不到5ms。
缺点:无法捕捉时序依赖,精度上限低。唤醒词稍微复杂一点就拉胯。
4.2 CNN:用卷积提取局部模式
CNN在语音唤醒里很流行。
它把MFCC特征图当成“图像”来处理,用卷积核提取局部时频模式。
我习惯用2D卷积,kernel size一般是(3,3)或(5,5)。
步长设(1,1),padding保持尺寸不变。
后面接全局平均池化,再丢进全连接层分类。
# 一个典型的唤醒词CNN结构
model = Sequential([
Conv2D(16, (3,3), activation='relu', input_shape=(40, 20, 1)),
MaxPooling2D((2,2)),
Conv2D(32, (3,3), activation='relu'),
MaxPooling2D((2,2)),
GlobalAveragePooling2D(),
Dense(64, activation='relu'),
Dense(num_classes, activation='softmax')
])
4.3 RNN/LSTM:天生处理序列
RNN(循环神经网络)天生就是为时序数据设计的。
每个时间步都保留一个隐藏状态,理论上可以记住任意长的依赖。
但实际用起来,RNN有两个大问题:
一是梯度消失/爆炸,长序列基本学不动;
二是推理是串行的,没法并行,延迟高。
LSTM加了门控机制,缓解了梯度问题。
但参数量大,在嵌入式上跑起来很吃力。
我做过一个LSTM的唤醒词模型,参数量200K,在Cortex-M4上推理一次要80ms——太慢了。
4.4 TCN:CNN的时序进化版
TCN(时序卷积网络)用因果卷积和空洞卷积来扩大感受野。
它既有CNN的并行计算优势,又能覆盖长时序依赖。
我特别喜欢TCN的一点:训练快,推理也快。
空洞率按指数增长(1,2,4,8...),几层就能覆盖上百帧。
参数量控制得好,精度还比LSTM高。
| 架构 | 感受野覆盖 | 推理延迟 | 参数量(典型) | 嵌入式友好度 |
|---|---|---|---|---|
| DNN | 无时序 | 极低 | 1K-10K | ★★★★★ |
| CNN | 局部 | 低 | 10K-100K | ★★★★☆ |
| RNN/LSTM | 全局 | 高 | 50K-500K | ★★☆☆☆ |
| TCN | 全局 | 低 | 20K-200K | ★★★★☆ |
| Transformer | 全局 | 中-高 | 100K-1M+ | ★★☆☆☆ |
4.5 Transformer:大模型时代的降维打击
Transformer在NLP领域已经统治了。
自注意力机制可以捕捉任意距离的依赖,精度确实高。
但用在唤醒词上,有点“杀鸡用牛刀”的感觉。
一个标准的Transformer编码器层,参数量轻松上百万。
在嵌入式上跑?除非你有专门的NPU,否则别想了。
不过,有一种变体叫“轻量Transformer”,比如MobileBERT、TinyBERT。
通过知识蒸馏和参数共享,把模型压到几百KB。
我试过在树莓派上跑,精度不错,但延迟还是偏高(50ms+)。
4.6 轻量化模型:MobileNet & SqueezeNet
轻量化模型是专门为移动端和嵌入式设计的。
它们通过特殊的卷积结构,大幅减少参数量和计算量。
MobileNet
核心是深度可分离卷积(Depthwise Separable Convolution)。
把标准卷积拆成两步:深度卷积 + 逐点卷积。
参数量直接降到原来的1/8到1/9。
我习惯用MobileNetV1做唤醒词的特征提取器。
输入是40x20的MFCC图,经过几层深度可分离卷积,再全局池化。
模型大小控制在50KB以内,精度比同参数量CNN高3-5%。
# MobileNetV1风格的唤醒词模型
def mobilenet_v1_wakeword(input_shape=(40, 20, 1), num_classes=2):
inputs = Input(shape=input_shape)
x = DepthwiseConv2D((3,3), padding='same', activation='relu')(inputs)
x = Conv2D(32, (1,1), activation='relu')(x)
x = DepthwiseConv2D((3,3), strides=(2,2), padding='same', activation='relu')(x)
x = Conv2D(64, (1,1), activation='relu')(x)
x = GlobalAveragePooling2D()(x)
outputs = Dense(num_classes, activation='softmax')(x)
return Model(inputs, outputs)
SqueezeNet
SqueezeNet用Fire Module替代标准卷积层。
Fire Module = Squeeze层(1x1卷积降维) + Expand层(1x1和3x3卷积混合)。
参数量比AlexNet少50倍,但精度差不多。
在唤醒词场景下,SqueezeNet适合极低资源设备。
我试过把SqueezeNet压缩到10KB以内,在Cortex-M0上跑,推理延迟不到10ms。
但精度嘛...只能说勉强能用,唤醒率85%左右。
• 资源 < 50KB Flash → SqueezeNet(极致压缩)
• 资源 50KB ~ 200KB Flash → MobileNet(精度与大小平衡)
• 资源 > 200KB Flash → 可以考虑标准CNN或TCN
4.7 我的选型决策流程
说了这么多,到底怎么选?
我一般按这个流程走:
- 先看硬件资源:Flash多大?RAM多大?有没有硬件加速器?
- 再看唤醒词复杂度:几个字?有没有噪声环境?
- 定精度目标:唤醒率要95%以上?还是90%就够?
- 选候选架构:根据上面三个条件,从表格里挑2-3个候选。
- 快速原型验证:用候选架构各训一个小模型,对比精度和延迟。
- 最终定版:选那个“精度达标、延迟最低、模型最小”的。