4. 声学特征工程:MFCC、Fbank、Pitch 特征提取对比、特征归一化(CMVN)、特征拼接与降维
声学特征工程,说白了就是把原始音频波形变成模型能听懂的数字语言。我刚开始做语音识别时,总觉得特征提取就是个黑盒子——丢进去音频,出来一堆数字,至于为什么选这个不选那个,完全靠猜。后来踩的坑多了,才慢慢摸清楚门道。
这一章,咱们就把 MFCC、Fbank、Pitch 这三个最常用的特征掰开揉碎讲清楚。再聊聊 CMVN 归一化、特征拼接和降维这些实战中绕不开的操作。
核心观点:没有绝对最好的特征,只有最适合当前任务的特征。MFCC 适合传统 GMM-HMM 系统,Fbank 在 DNN 时代更吃香,Pitch 则是抗噪场景的救命稻草。
4.1 三大特征:MFCC、Fbank、Pitch
先说说 MFCC。这玩意儿在语音界火了二十多年,至今仍是经典。它的核心思路是模拟人耳听觉特性——人对低频更敏感,对高频分辨率低。MFCC 通过 Mel 滤波器组把线性频率映射到 Mel 刻度,再取对数、做 DCT 变换,最后得到一组去相关的倒谱系数。
我的经验:MFCC 的 DCT 这一步其实是把能量分布压缩到前几维。我见过有人把 40 维 Fbank 做 DCT 得到 13 维 MFCC,结果识别率掉了 2 个点。为什么?因为 DCT 去掉了特征之间的相关性,但 DNN 本身不介意相关性,它自己会学。
Fbank(Filter Bank)就简单多了。它只做到 Mel 滤波器组和对数这一步,不做 DCT。所以 Fbank 各维之间是相关的,保留了更多原始能量分布信息。在 DNN 时代,Fbank 逐渐取代了 MFCC 成为主流。
Pitch(基频)是另一个维度。它描述的是声带振动的频率,单位是 Hz。男声基频大概在 80-200 Hz,女声在 150-350 Hz。Pitch 特征在噪声环境下特别有用——因为噪声通常不会改变基频的周期性。
| 特征 | 维度(典型值) | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| MFCC | 13-40 | 去相关、适合 GMM | DCT 丢失信息 | 传统系统、小模型 |
| Fbank | 40-80 | 保留能量、适合 DNN | 维间冗余 | DNN/RNN/Transformer |
| Pitch | 1-3 | 抗噪、区分性别 | 信息量少 | 噪声环境、说话人识别 |
避坑指南:我曾经在一个远场语音项目里只用 MFCC,结果在 -5dB 信噪比下识别率直接崩到 30%。后来加上 Pitch 特征,同样的噪声条件识别率回升到 65%。Pitch 在低信噪比下真的是救命稻草。
4.2 特征归一化:CMVN
CMVN(Cepstral Mean and Variance Normalization)是语音特征工程里最基础的归一化操作。它的原理很简单:对每个 utterance 的特征序列,减去均值、除以标准差。
为什么要做 CMVN?因为不同麦克风、不同信道、不同说话人录出来的特征分布不一样。同一个词,张三在安静房间用 iPhone 录,和李四在嘈杂咖啡馆用 Android 录,特征均值可能差好几个数量级。CMVN 能把这些差异抹平。
# CMVN 实现示例(Python 伪代码)
def cmvn(feats, norm_vars=True):
"""
feats: [T, D] 特征矩阵
norm_vars: 是否做方差归一化
"""
mean = np.mean(feats, axis=0) # 按时间轴求均值
feats = feats - mean # 减均值
if norm_vars:
std = np.std(feats, axis=0)
std[std < 1e-10] = 1e-10 # 防止除零
feats = feats / std # 除标准差
return feats
这里有个细节:CMVN 是在 utterance 级别做的,还是全局级别做的?我个人的习惯是:训练时用全局均值和方差,推理时用当前 utterance 的统计量。但要注意,如果 utterance 太短(比如不到 1 秒),统计量不稳定,这时候用全局统计量更靠谱。
实战建议:CMVN 一定要做,但不要盲目做。我见过有人把 CMVN 用在 Pitch 特征上,结果基频信息被归一化掉了,识别率反而下降。Pitch 特征通常只做均值归一化,不做方差归一化。
4.3 特征拼接与降维
特征拼接,就是把不同特征在维度上拼起来。比如把 40 维 Fbank 和 3 维 Pitch 拼成 43 维。这听起来简单,但有个坑:不同特征的数值范围不一样。Fbank 的值通常在 0-50 之间,Pitch 可能在 80-500 之间。如果不做归一化直接拼接,Pitch 会主导梯度更新。
所以我的做法是:先对每个特征做独立的 CMVN,再拼接。拼接之后,特征维度可能膨胀到 100 维以上,这时候就需要降维了。
常用的降维方法有两种:
- LDA(线性判别分析):有监督降维,目标是让类间距离最大、类内距离最小。适合分类任务。
- PCA(主成分分析):无监督降维,目标是保留方差最大的方向。适合压缩和去噪。
我个人的经验是:在语音识别里,LDA 比 PCA 好用。因为 LDA 利用了标签信息,降维后的特征对音素区分更友好。但 LDA 有个限制——降维后的维度不能超过类别数减 1。对于音素级别的任务,类别数通常有 40-60 个,所以 LDA 降到 39 维左右就够了。
一个小技巧:拼接特征时,可以试试把当前帧的前后几帧也拼进来。比如拼上前后 3 帧,特征维度就变成了 (2*3+1) * 43 = 301 维。这种上下文拼接在 DNN 时代很常见,能帮模型捕捉时序信息。
4.4 实战流程:从音频到特征
最后,我总结一下完整的特征提取流程。这个流程我在多个项目里验证过,效果稳定:
- 预加重:提升高频能量,公式 y(t) = x(t) - 0.97 * x(t-1)
- 分帧加窗:帧长 25ms,帧移 10ms,加汉明窗
- FFT:计算功率谱,通常用 512 点或 1024 点 FFT
- Mel 滤波器组:40 个滤波器,覆盖 0-8000 Hz
- 取对数:log 压缩,模拟人耳响度感知
- (可选)DCT:得到 MFCC,通常取 13 维
- Pitch 提取:用自相关法或倒谱法,提取基频和清浊音标志
- CMVN:对每个特征做均值方差归一化
- 拼接:Fbank + Pitch + 一阶差分 + 二阶差分
- (可选)降维:LDA 或 PCA,降到 40-60 维
注意:差分特征(一阶差分、二阶差分)能提供动态信息,但也会增加维度。我建议先不加差分,等模型 baseline 跑通后再加。有时候差分特征带来的提升还不如多加几帧上下文。
嗯,特征工程这部分就讲到这里。说白了,特征提取没有银弹,关键是根据你的任务、模型和场景去试。我每次做新项目,都会先跑一组对比实验:MFCC vs Fbank vs Fbank+Pitch,看看哪个组合在当前数据上表现最好。这个习惯帮我避免了很多「想当然」的坑。