4. 数据采集与标注

好,咱们进入第四章。说实话,声学特征这块儿,是很多做唤醒词的同学容易忽略的环节。大家总觉得模型结构才是王道,特征嘛,随便拿个开源工具提一下就行。我以前也这么想,直到有一次在低信噪比场景下怎么调模型都提不了几个点,回头一查,原来是特征提取的参数没对齐。嗯,从那以后,我再也不敢小看特征工程了。

今天咱们就把 MFCC、Fbank、Mel-spectrogram 这三兄弟彻底聊透。你想想看,它们本质上都是对声音的「频谱描述」,但描述的角度和保留的信息量完全不同。

4.1 从声音到频谱:基础流程

不管用哪种特征,第一步都一样:把时域的音频信号变成频域的表示。我习惯把这个流程拆成四步:

  1. 预加重:提升高频能量,补偿口唇辐射的衰减。说白了就是让高频信号别太弱。
  2. 分帧加窗:把长信号切成 20-40ms 的短帧,每帧加个汉明窗,减少频谱泄露。
  3. FFT:对每帧做快速傅里叶变换,得到频谱。
  4. Mel 滤波器组:把线性频率映射到 Mel 刻度上,模拟人耳的听觉特性。

这三兄弟的差异,就出在第四步之后怎么处理。

核心认知:Mel-spectrogram 是「原始频谱 + Mel 滤波」的直接产物;Fbank 是 Mel-spectrogram 取对数后的结果;MFCC 是 Fbank 再做 DCT(离散余弦变换)并丢弃高阶系数的结果。

声学特征提取流程对比 声学特征提取流程对比 时域音频 预加重 + 分帧 FFT → 频谱 Mel 滤波器组 Mel-spectrogram Fbank(取 log) MFCC(DCT 降维) 三者的核心差异 • Mel-spectrogram:保留完整频谱能量分布 • Fbank:对数压缩,更符合人耳响度感知 • MFCC:DCT 去相关,丢弃高阶细节 唤醒词场景选型建议 • 资源受限(MCU):MFCC(13-20 维) • 中等资源(DSP):Fbank(40-80 维) • 云端/高算力:Mel-spectrogram(保留细节) • 噪声鲁棒优先:Fbank + 对数归一化

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 实战中的特征选择策略

说了这么多,到底怎么选?我总结了一个简单的决策流程:

  1. 先看算力:MCU 级别 → MFCC(13 维 + Delta);DSP 级别 → Fbank(40 维 + Delta);云端级别 → Mel-spectrogram(80 维 + Delta)。
  2. 再看噪声:安静环境 → 均值归一化即可;嘈杂环境 → 在线均值归一化 + 对数能量归一化。
  3. 最后看数据量:数据量大(>1000 小时)→ 让模型自己学,用 Mel-spectrogram;数据量小(<100 小时)→ 用 Fbank + 强归一化,减少模型对无关变化的敏感度。

嗯,特征工程这块儿,说深了可以写一本书。但咱们做唤醒词,抓住一个核心就行:让模型看到的是「语音内容」,而不是「说话的人」或「录音的环境」。归一化就是帮你把后两者抹掉。