4. 语音信号特性
好,咱们进入第四章。前面我们讲了声音怎么变成数字信号,怎么分帧加窗。但说实话,那些都只是预处理。真正让机器听懂声音的关键一步,就是特征提取。
我个人习惯把MFCC叫做“声音的身份证”。它能把一段复杂的语音,浓缩成几十个数字。你想想看,一秒钟的语音有16000个采样点,但MFCC只取13个系数。这压缩比,够狠吧?
但压缩不是乱扔信息。MFCC的核心思路是:模仿人耳听声音的方式。人耳对低频敏感,对高频迟钝。MFCC就设计了一套滤波器,专门放大低频,压缩高频。说白了,就是让机器用人类的耳朵去听。
核心观点:MFCC不是最完美的特征,但它是语音识别领域最“稳”的特征。从1990年代用到现在,依然活跃在唤醒词模型的第一线。
4.1 梅尔滤波器组:模拟人耳的“非线性”
先问一个问题:为什么叫“梅尔”?
Mel这个单位,是心理学家Stevens和Volkmann在1940年提出的。他们发现,人耳对频率的感知不是线性的。比如,1000Hz到2000Hz的差距,人耳觉得很大;但8000Hz到9000Hz的差距,人耳几乎听不出来。
梅尔刻度就是把这个非线性关系数学化了。公式很简单:
Mel(f) = 2595 * log10(1 + f / 700)
你看,f是实际频率,Mel(f)是感知频率。这个公式把线性频率映射到了梅尔尺度上。在梅尔尺度上,人耳对每个刻度的敏感度是一样的。
那滤波器组怎么来的?
我们在梅尔尺度上均匀地放一排三角形滤波器。每个滤波器覆盖一个频带。低频区域滤波器密集,高频区域滤波器稀疏。这样,低频信息被精细提取,高频信息被粗略提取。
我在项目中遇到过一个问题:滤波器数量怎么选?
常见的是40个。但如果你做唤醒词模型,我建议用20-26个。为什么?因为唤醒词通常只有2-3个音节,信息集中在低频。滤波器太多反而引入噪声。我曾经试过用40个滤波器做“小爱同学”的唤醒,结果误唤醒率高了30%。后来降到24个,效果立马好了。
我的经验:滤波器数量不是越多越好。唤醒词模型建议20-26个,语音识别用40个,音乐分析可以用64个。具体调参时,先看你的任务对高频敏感不敏感。
4.2 对数能量:把“响度”拉进特征
滤波器组输出的是能量值。但这些能量值的动态范围很大。安静时可能只有0.1,大声说话时可能到1000。直接拿这些值去训练模型,数值大的特征会主导梯度,数值小的特征会被忽略。
怎么办?取对数。
对数运算能把大数值压缩,把小数值放大。公式就是:
log_energy = log(energy + epsilon)
epsilon是一个很小的正数,防止log(0)出现。我一般取1e-10。
取完对数后,能量值变成了分贝尺度。这个尺度更符合人耳的响度感知。你想想看,人耳对声音的感知也是对数级的——声音能量翻倍,你只感觉响了一点点。
另外,我习惯把每一帧的总能量也单独存下来。这个总能量特征,在唤醒词模型中很有用。比如,当环境噪声突然变大时,总能量会跳变。模型可以利用这个信息判断“是不是有人在说话”。
注意:取对数前一定要加epsilon。我见过有人直接log(0),结果训练时出现NaN,整个模型崩了。这种低级错误,排查起来很头疼。
4.3 离散余弦变换(DCT):去相关和降维
取完对数后,我们得到的是对数梅尔频谱。但这个频谱有个问题:相邻的滤波器之间有重叠,所以相邻的能量值是高度相关的。
高度相关意味着什么?意味着信息冗余。你想想看,如果两个特征总是同时变大变小,那保留一个就够了。
离散余弦变换(DCT)就是用来干这个的。它能把相关的能量值,变成一组不相关的系数。而且,DCT有个很好的性质:能量集中在前面几个系数上。
具体来说,DCT的公式是:
C(k) = sum_{n=0}^{N-1} x(n) * cos(pi * k * (n + 0.5) / N)
其中x(n)是对数梅尔能量,N是滤波器数量,k是系数索引。k=0时,C(0)是所有能量的平均值,也就是总能量。k=1时,C(1)是能量的一阶变化。k越大,对应的频率变化越快。
我们通常只取前13个系数。为什么是13?
嗯,这个数字不是拍脑袋定的。实验发现,前13个系数已经包含了90%以上的语音信息。后面的系数主要包含噪声和细节。对于唤醒词模型,13个系数足够用了。
我记得有一次,一个同事非要用26个系数,说“信息越多越好”。结果模型训练时间翻倍,准确率只提升了0.2%。后来我给他看了DCT的能量分布图,他才服气。
关键点:DCT不是压缩,而是“去相关+能量集中”。它让特征更适合用高斯模型或神经网络去建模。
4.4 MFCC特征向量的生成流程
好了,我们把所有步骤串起来。下面是完整的MFCC生成流程:
- 预加重:提升高频,补偿口唇辐射损失。
- 分帧:25ms一帧,帧移10ms。
- 加窗:汉明窗,减少频谱泄漏。
- FFT:计算每一帧的频谱。
- 梅尔滤波器组:用40个三角形滤波器,把频谱映射到梅尔尺度。
- 取对数:log(能量 + epsilon),模拟人耳响度感知。
- DCT:去相关,取前13个系数。
- 动态特征:计算一阶差分(Δ)和二阶差分(ΔΔ),捕捉时序变化。
最终,每一帧的特征向量是:13个MFCC + 13个Δ + 13个ΔΔ + 1个对数能量 = 40维。
这个40维向量,就是送入唤醒词模型的“原材料”。
下面这张图,是我自己画的MFCC生成流程。你看一眼,应该就全明白了。
4.5 代码实战:用Python提取MFCC
光说不练假把式。下面我用Python写一个完整的MFCC提取函数。你可以在自己的机器上跑一下。
import numpy as np
import librosa
def extract_mfcc(audio_path, sr=16000, n_mfcc=13, n_fft=512, hop_length=160, n_mels=40):
"""
提取MFCC特征
audio_path: 音频文件路径
sr: 采样率,默认16kHz
n_mfcc: MFCC系数个数,默认13
n_fft: FFT窗口大小,默认512
hop_length: 帧移,默认160(10ms)
n_mels: 梅尔滤波器数量,默认40
"""
# 1. 加载音频
y, sr = librosa.load(audio_path, sr=sr)
# 2. 提取MFCC
mfcc = librosa.feature.mfcc(
y=y,
sr=sr,
n_mfcc=n_mfcc,
n_fft=n_fft,
hop_length=hop_length,
n_mels=n_mels
)
# 3. 计算一阶差分
delta = librosa.feature.delta(mfcc)
# 4. 计算二阶差分
delta2 = librosa.feature.delta(mfcc, order=2)
# 5. 计算对数能量
log_energy = np.log(np.sum(y**2) + 1e-10)
# 6. 拼接特征
features = np.vstack([mfcc, delta, delta2])
return features.T # 返回 (帧数, 特征维度)
# 使用示例
features = extract_mfcc("wake_word.wav")
print(f"特征形状: {features.shape}") # 输出: (帧数, 39)
我的建议:librosa的mfcc函数默认已经包含了预加重、分帧、加窗、FFT、梅尔滤波、对数、DCT全套流程。你只需要关心参数设置。但如果你要做嵌入式部署,建议自己手写一遍,避免依赖库。
4.6 避坑指南
最后,分享几个我在项目中踩过的坑:
- 采样率不匹配:训练时用16kHz,部署时用8kHz,MFCC会完全不一样。一定要统一采样率。
- 帧移太大:帧移超过20ms,会丢失语音的时序细节。唤醒词模型建议10ms。
- 忘记归一化:MFCC系数的数值范围很大,直接送入神经网络会导致梯度爆炸。建议做均值方差归一化。
- 滤波器数量乱选:唤醒词用20-26个,别贪多。
嗯,这一章就到这里。MFCC虽然老,但它是声学模型的基石。你把它吃透了,后面讲神经网络特征提取时,就会轻松很多。