4. 数据增强与预处理

模型架构选型,说白了就是给唤醒词任务挑一个最合适的“脑子”。
我做了这么多年嵌入式端的语音唤醒,见过太多“模型跑不动”或者“精度差得离谱”的案例。
选型选对了,后面优化事半功倍;选错了,后面再怎么调参都是白费力气。

今天咱们就把主流的几种架构捋一遍。
DNN、CNN、RNN、TCN、Transformer,还有轻量化模型MobileNet、SqueezeNet。
我会结合自己的项目经验,告诉你每种架构到底适合什么场景,坑在哪里。

核心观点:没有最好的架构,只有最合适的架构。唤醒词模型选型,本质是在“精度、延迟、模型大小”三者之间找平衡。
唤醒词模型架构选型决策图 唤醒词模型架构选型决策图 唤醒词任务 资源极度受限 中等资源 资源充足 DNN SqueezeNet CNN TCN RNN/LSTM Transformer 选型建议 • 资源 < 100KB Flash → DNN / SqueezeNet • 资源 100KB ~ 1MB → CNN / TCN / MobileNet • 资源 > 1MB → RNN / Transformer(需量化+剪枝)

4.1 DNN:最朴素,但也最稳

DNN(深度神经网络)就是全连接层堆叠。
结构简单,参数量可控,在资源极度受限的MCU上很常见。

我记得有一次做一个2KB RAM的唤醒词项目,只能用DNN。
输入是10帧MFCC特征,每帧13维,两层隐藏层各32个神经元。
模型大小才3.2KB,跑一次推理不到5ms。

适用场景:Flash < 64KB、RAM < 4KB的极低资源设备。
缺点:无法捕捉时序依赖,精度上限低。唤醒词稍微复杂一点就拉胯。

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')
])
注意:CNN的感受野有限。如果唤醒词很长(比如“小爱同学”4个字),需要堆很多层才能覆盖整个时序范围。我曾经踩过这个坑,模型叠到8层才勉强够,但参数量已经爆炸了。

4.3 RNN/LSTM:天生处理序列

RNN(循环神经网络)天生就是为时序数据设计的。
每个时间步都保留一个隐藏状态,理论上可以记住任意长的依赖。

但实际用起来,RNN有两个大问题:
一是梯度消失/爆炸,长序列基本学不动;
二是推理是串行的,没法并行,延迟高。

LSTM加了门控机制,缓解了梯度问题。
但参数量大,在嵌入式上跑起来很吃力。
我做过一个LSTM的唤醒词模型,参数量200K,在Cortex-M4上推理一次要80ms——太慢了。

我的建议:除非你的芯片有硬件加速(比如NPU支持LSTM),否则尽量别用RNN/LSTM做唤醒词。TCN和CNN是更好的选择。

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+)。

什么时候用Transformer?当你的唤醒词非常复杂(比如多语种混合),而且硬件资源充裕(比如有GPU或NPU)。否则,老老实实用TCN或CNN。

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 我的选型决策流程

说了这么多,到底怎么选?
我一般按这个流程走:

  1. 先看硬件资源:Flash多大?RAM多大?有没有硬件加速器?
  2. 再看唤醒词复杂度:几个字?有没有噪声环境?
  3. 定精度目标:唤醒率要95%以上?还是90%就够?
  4. 选候选架构:根据上面三个条件,从表格里挑2-3个候选。
  5. 快速原型验证:用候选架构各训一个小模型,对比精度和延迟。
  6. 最终定版:选那个“精度达标、延迟最低、模型最小”的。
避坑指南:我曾经一上来就选Transformer,觉得精度最高。结果模型太大,在目标芯片上根本跑不动。白白浪费了两周时间。所以,一定要先看硬件,再选模型。顺序反了,后面全是坑。