4. 数据采集与标注
好,咱们进入第四章。说实话,声学特征这块儿,是很多做唤醒词的同学容易忽略的环节。大家总觉得模型结构才是王道,特征嘛,随便拿个开源工具提一下就行。我以前也这么想,直到有一次在低信噪比场景下怎么调模型都提不了几个点,回头一查,原来是特征提取的参数没对齐。嗯,从那以后,我再也不敢小看特征工程了。
今天咱们就把 MFCC、Fbank、Mel-spectrogram 这三兄弟彻底聊透。你想想看,它们本质上都是对声音的「频谱描述」,但描述的角度和保留的信息量完全不同。
4.1 从声音到频谱:基础流程
不管用哪种特征,第一步都一样:把时域的音频信号变成频域的表示。我习惯把这个流程拆成四步:
- 预加重:提升高频能量,补偿口唇辐射的衰减。说白了就是让高频信号别太弱。
- 分帧加窗:把长信号切成 20-40ms 的短帧,每帧加个汉明窗,减少频谱泄露。
- FFT:对每帧做快速傅里叶变换,得到频谱。
- Mel 滤波器组:把线性频率映射到 Mel 刻度上,模拟人耳的听觉特性。
这三兄弟的差异,就出在第四步之后怎么处理。
核心认知:Mel-spectrogram 是「原始频谱 + Mel 滤波」的直接产物;Fbank 是 Mel-spectrogram 取对数后的结果;MFCC 是 Fbank 再做 DCT(离散余弦变换)并丢弃高阶系数的结果。
4.2 三兄弟的详细对比
咱们一个一个说清楚。
4.2.1 Mel-spectrogram
这是最「原始」的 Mel 域特征。经过 Mel 滤波器组后,每个帧得到一个向量,维度等于滤波器个数(通常 40-128)。它保留了完整的频谱能量分布信息。我在项目中遇到过,用 Mel-spectrogram 做唤醒词,在安静环境下效果确实好,但一到嘈杂环境,那些噪声的频谱细节也跟着进来了,模型容易过拟合。
4.2.2 Fbank(Filter Bank)
Fbank 就是在 Mel-spectrogram 的基础上取了个对数。为什么要取对数?因为人耳对声音的响度感知是对数级的。说白了,你感觉声音大了 1 倍,实际能量可能大了 10 倍。取对数之后,特征分布更平滑,模型训练也更稳定。我个人习惯,在大多数唤醒词场景下,Fbank 是首选。
4.2.3 MFCC
MFCC 是在 Fbank 的基础上再做 DCT 变换,然后只保留前 13-20 个系数。DCT 的作用是去相关——把 Fbank 中相邻频带之间的冗余信息去掉。这样做的好处是特征维度大幅降低,计算量小。但代价是什么?丢失了高频细节。我记得早期做语音识别时,MFCC 是绝对的主流。但到了唤醒词时代,尤其是深度学习模型,大家发现保留更多信息反而更好,所以 Fbank 又回来了。
| 特性 | Mel-spectrogram | Fbank | MFCC |
|---|---|---|---|
| 维度 | 40-128 | 40-80 | 13-20 |
| 信息保留 | 最完整 | 较完整 | 有损(丢弃高阶) |
| 计算量 | 中 | 中 | 低 |
| 去相关性 | 无 | 无 | 强(DCT) |
| 唤醒词适用性 | 高算力场景 | 推荐(通用) | 资源受限场景 |
我的经验:如果你不确定选哪个,先上 Fbank(40 维)。这是最稳妥的起点。模型跑通了,再根据资源情况决定是否降维到 MFCC 或升维到 Mel-spectrogram。
4.3 特征归一化技巧
特征提出来了,但直接扔给模型训练?不行。你想想看,不同人的音量不一样,不同麦克风的增益不一样,不同环境的底噪也不一样。如果不做归一化,模型学到的可能是「音量大小」而不是「语音内容」。
归一化这块,我踩过不少坑。下面是我觉得最实用的几种方法。
4.3.1 均值归一化(Mean Normalization)
对每个特征维度,减去均值,除以标准差。这是最基础的做法。但要注意:均值和标准差是在整个训练集上统计的,还是在线实时计算的?
- 全局统计:用训练集的全局均值和标准差。优点是稳定,缺点是如果测试集和训练集分布差异大,效果会打折扣。
- 在线统计:对当前音频片段自己算均值和标准差。优点是自适应强,缺点是短片段统计不稳定。
我个人习惯,在唤醒词场景下,用在线均值归一化。因为唤醒词通常很短(1-2 秒),全局统计容易受静音段影响。
4.3.2 倒谱均值归一化(CMVN)
CMVN 是 MFCC 时代的经典方法。它对每个 utterance 的所有帧,在倒谱域上做均值归一化。说白了,就是去掉信道的卷积效应。但 CMVN 对 Fbank 也有效果,只是没那么明显。
避坑指南:我曾经在 Fbank 上直接套用 CMVN,结果发现模型在远场场景下反而变差了。后来排查发现,CMVN 在去除信道效应的同时,也抹掉了一些对唤醒词有用的低频能量分布信息。所以,用 CMVN 之前,一定要在验证集上先测一下。
4.3.3 对数能量归一化
这个技巧比较小众,但我在一个低功耗项目里用过,效果不错。做法是:对每帧的能量取对数,然后减去整个 utterance 的平均对数能量。这样做的好处是,能消除不同麦克风增益带来的整体能量偏移,同时保留帧与帧之间的相对能量变化——这对唤醒词的「起音」和「尾音」检测很有帮助。
4.3.4 动态特征扩展(Delta / Delta-Delta)
严格来说这不是归一化,但它是特征工程里非常重要的一环。Delta 特征计算的是帧与帧之间的变化率,Delta-Delta 是变化率的变化率。为什么要加这个?因为人耳对声音的变化比绝对值更敏感。唤醒词中的「音调转折」和「音节边界」,在 Delta 特征上表现得非常明显。
# 一个简单的 Delta 特征计算示例(Python 伪代码)
def compute_delta(feats, window=2):
"""
feats: [T, D] 的 Fbank 特征
window: 计算差分时考虑的上下文帧数
"""
T, D = feats.shape
delta = np.zeros_like(feats)
for t in range(T):
# 前后各取 window 帧做线性回归
start = max(0, t - window)
end = min(T, t + window + 1)
x = np.arange(start, end) - t
y = feats[start:end]
# 最小二乘拟合斜率
delta[t] = np.linalg.lstsq(x[:, None], y, rcond=None)[0]
return delta
重要提醒:加 Delta 特征会让维度变成原来的 3 倍(原始 + Delta + Delta-Delta)。如果你的模型很小,要谨慎。我见过有人把 40 维 Fbank 直接扩展到 120 维,结果模型参数量翻倍,效果只提升了 0.2%。不值当。
4.4 实战中的特征选择策略
说了这么多,到底怎么选?我总结了一个简单的决策流程:
- 先看算力:MCU 级别 → MFCC(13 维 + Delta);DSP 级别 → Fbank(40 维 + Delta);云端级别 → Mel-spectrogram(80 维 + Delta)。
- 再看噪声:安静环境 → 均值归一化即可;嘈杂环境 → 在线均值归一化 + 对数能量归一化。
- 最后看数据量:数据量大(>1000 小时)→ 让模型自己学,用 Mel-spectrogram;数据量小(<100 小时)→ 用 Fbank + 强归一化,减少模型对无关变化的敏感度。
嗯,特征工程这块儿,说深了可以写一本书。但咱们做唤醒词,抓住一个核心就行:让模型看到的是「语音内容」,而不是「说话的人」或「录音的环境」。归一化就是帮你把后两者抹掉。