频谱图与梅尔特征 本节摘要:神经网络不擅长直接吃原始波形。它吃频谱图(Spectrogram)更好,吃梅尔频谱图(Mel Spectrogram)最好。2026 年每一条 ASR、TTS、音频分类流水线的生死,几乎都取决于这一个前处理选择。一段 10 秒 16 kHz 的音频是 16 万个浮点数,它们与「狗叫」「单词猫」这类标签几乎完全不相关——信息在,但模型抽不出来。频谱图把人耳忽略的微秒级抖动压扁,保留人耳真正关心的「哪些频率在哪些时刻有能量」;梅尔频谱图更进一步,按人耳的对数感知把频率轴扭曲,使 100→200 Hz 听起来与 1000→2000 Hz 「等距」。本节将从零手写分帧、加窗、STFT、梅尔滤波器组、对数梅尔与 MFCC,讲透每一个生产流水线仍会踩的坑。
本节摘要:神经网络不擅长直接吃原始波形。它吃频谱图(Spectrogram)更好,吃梅尔频谱图(Mel Spectrogram)最好。2026 年每一条 ASR、TTS、音频分类流水线的生死,几乎都取决于这一个前处理选择。一段 10 秒 16 kHz 的音频是 16 万个浮点数,它们与「狗叫」「单词猫」这类标签几乎完全不相关——信息在,但模型抽不出来。频谱图把人耳忽略的微秒级抖动压扁,保留人耳真正关心的「哪些频率在哪些时刻有能量」;梅尔频谱图更进一步,按人耳的对数感知把频率轴扭曲,使 100→200 Hz 听起来与 1000→2000 Hz 「等距」。本节将从零手写分帧、加窗、STFT、梅尔滤波器组、对数梅尔与 MFCC,讲透每一个生产流水线仍会踩的坑。读完本节,你能解释为什么 80 维对数梅尔是 2026 年语音 ML 的事实标准输入。
阅读完本节,你应当能够:
拿一段 10 秒、16 kHz 的音频。它是 16 万个取值在 [-1, 1] 的浮点数,与标签「狗叫」或「单词 cat」几乎完全不相关。原始波形里确实有信息,但形态让模型难以提取——同一个音素,间隔 100 毫秒说两次,原始样本完全不同。
频谱图修复了这一点。它压扁人耳忽略的时间细节(微秒级抖动),保留人耳真正关心的结构(哪些频率在约 10~25 毫秒的时间窗里有能量)。梅尔频谱图更进一步:人耳对音高的感知是对数的,100 Hz 与 200 Hz 听起来「一样远」,1000 Hz 与 2000 Hz 也「一样远」;梅尔尺度把频率轴扭曲成匹配人耳感知的形态。从 2010 年到 2026 年,梅尔频谱图始终是语音 ML 中最重要的单一特征。
把波形切成重叠的帧(典型:25 ms 窗、10 ms 步长,在 16 kHz 下即 400 样本 / 160 样本),每帧乘窗函数(默认 Hann,Hamming 略有差异),逐帧 FFT,把幅度谱堆叠成 (n_frames, n_freq_bins) 的矩阵——这就是频谱图。
原始幅度跨 5~6 个数量级。取 log(|X| + 1e-6) 或 20 * log10(|X|) 压缩动态范围。所有生产流水线都用对数幅度,不用原始幅度。
频率 f(赫兹)到梅尔 m 的映射:m = 2595 * log10(1 + f / 700)。1 kHz 以下近似线性,1 kHz 以上近似对数。覆盖 0~8 kHz 的 80 个梅尔箱,是 ASR 的标准输入。
一组在梅尔尺度上等距分布的三角滤波器,每个滤波器是相邻 FFT 箱的加权和。把 STFT 幅度乘以滤波器组矩阵,一次 matmul 即得梅尔频谱图。
log(mel_spec + 1e-10)。Whisper 的输入、Parakeet 的输入、SeamlessM4T 的输入——这是 2026 年事实上的通用音频前端。
在对数梅尔频谱图上做 II 型离散余弦变换(DCT),保留前 13 个系数。它去相关了特征并进一步压缩。2015 年前是主流,之后被直接吃对数梅尔的 CNN/Transformer 超越;但在说话人识别(x-vectors、ECAPA)中仍在用。
FFT 越大,频率分辨率越好,时间分辨率越差。25 ms / 10 ms 是音频 ML 的默认;音乐用 50 ms / 12.5 ms;瞬态检测(鼓点、爆破音)用 5 ms / 2 ms。
💡 25 ms 不是玄学,而是语音物理的产物:人发音时,口腔形状在约 20~30 ms 内基本稳定,因此 25 ms 的窗近似捕捉到一个「静态」的声道形状。短于 10 ms 抓不到完整的基频周期,长于 50 ms 又把多个音素混在一起。
def frame(signal, frame_len, hop): n = 1 + (len(signal) - frame_len) // hop return [signal[i * hop : i * hop + frame_len] for i in range(n)]
10 秒 16 kHz 音频,frame_len=400, hop=160,得到 998 帧。
import math def hann(N): return [0.5 * (1 - math.cos(2 * math.pi * n / (N - 1))) for n in range(N)]
FFT 前逐元素相乘,消除因非零端点截断造成的频谱泄漏。
def stft_magnitude(signal, frame_len=400, hop=160): win = hann(frame_len) frames = frame(signal, frame_len, hop) return [magnitudes(dft([w * s for w, s in zip(win, f)])) for f in frames]
生产代码用 torch.stft 或 librosa.stft(FFT 加速、向量化)。这里的循环仅作教学,只在短片段上跑得动。
def hz_to_mel(f): return 2595.0 * math.log10(1.0 + f / 700.0) def mel_to_hz(m): return 700.0 * (10 ** (m / 2595.0) - 1) def mel_filterbank(n_mels, n_fft, sr, fmin=0, fmax=None): fmax = fmax or sr / 2 mels = [hz_to_mel(fmin) + (hz_to_mel(fmax) - hz_to_mel(fmin)) * i / (n_mels + 1) for i in range(n_mels + 2)] hzs = [mel_to_hz(m) for m in mels] bins = [int(h * n_fft / sr) for h in hzs] fb = [[0.0] * (n_fft // 2 + 1) for _ in range(n_mels)] for m in range(n_mels): for k in range(bins[m], bins[m + 1]): fb[m][k] = (k - bins[m]) / max(1, bins[m + 1] - bins[m]) for k in range(bins[m + 1], bins[m + 2]): fb[m][k] = (bins[m + 2] - k) / max(1, bins[m + 2] - bins[m + 1]) return fb
80 个梅尔箱、覆盖 0~8 kHz、n_fft=400,得到 (80, 201) 的矩阵。把 (n_frames, 201) 的 STFT 幅度乘以它的转置,即得 (n_frames, 80) 的梅尔频谱图。
设计要点:三角滤波器的中心在梅尔尺度上等距,但换算回赫兹后,低频处滤波器很密、高频处很疏——这正是人耳「低频分辨力强、高频分辨力弱」的体现。第 m 个滤波器的左半边是上升斜坡、右半边是下降斜坡,峰值在
bins[m+1]。
def log_mel(mel_spec, eps=1e-10): return [[math.log(max(v, eps)) for v in frame] for frame in mel_spec]
常见替代:librosa.power_to_db(参考值归一化的 dB)、10 * log10(power + eps)。Whisper 用更复杂的裁剪 + 归一化流程(见其 log_mel_spectrogram 函数)。
def dct_ii(x, n_coeffs): N = len(x) return [ sum(x[n] * math.cos(math.pi * k * (2 * n + 1) / (2 * N)) for n in range(N)) for k in range(n_coeffs) ]
对每个对数梅尔帧做 DCT,保留前 13 个系数,即得 MFCC 矩阵。第一个系数通常丢弃(它编码了整体能量)。
2026 年不同任务的特征工程选型:
| 任务 | 特征 |
|---|---|
| ASR(Whisper、Parakeet、SeamlessM4T) | 80 维对数梅尔,10 ms 步长,25 ms 窗 |
| TTS 声学模型(VITS、F5-TTS、Kokoro) | 80 维梅尔,5~12 ms 步长(精细时间控制) |
| 音频分类(AST、PANNs、BEATs) | 128 维对数梅尔,10 ms 步长 |
| 说话人嵌入(ECAPA-TDNN、WavLM) | 80 维对数梅尔,或原始波形 + 自监督 |
| 音乐(MusicGen、Stable Audio 2) | EnCodec 离散 token(不是梅尔) |
| 关键词唤醒 | 40 维 MFCC(适配嵌入式小设备) |
经验法则:只要不是做音乐,先从 80 维对数梅尔开始。任何偏离都需要举证证明。
# 生产标准:torch 一行搞定对数梅尔 import torchaudio mel = torchaudio.transforms.MelSpectrogram( sample_rate=16000, n_fft=400, hop_length=160, n_mels=80, window_fn=torch.hann_window)(wav) log_mel = torchaudio.transforms.AmplitudeToDB( stype="power", top_db=80)(mel) # 等价于 10*log10(power)
💡
AmplitudeToDB的top_db=80会把低于峰值 80 dB 的部分截平,起到归一化作用。Whisper 没用top_db,而是自己一套 clip + 标准化,因此用 HF 的 WhisperProcessor 时不要自己手动算梅尔——直接调它的get_features,否则数值会与预训练分布不一致。
本节产出技能文档(原课程 outputs/skill-feature-extractor.md),针对给定目标模型自动选择特征类型、梅尔数、帧长/步长、归一化方式。核心决策树:
基础:运行 code/main.py。它合成一段 chirp(频率从 200 Hz 扫到 4000 Hz),逐帧打印 argmax 梅尔箱(可选画图),确认峰值随时间上移与扫频一致。
进阶:把 n_mels 取 {40, 80, 128}、frame_len 取 {200, 400, 800},共九组组合重跑。沿时间轴测量峰值带宽,哪组组合分辨扫频最清楚?解释分辨率权衡。
挑战:实现 power_to_db,在 AudioMNIST 上用一个小型 CNN 分类器,对比三种特征的 top-1 准确率:(a) 原始对数梅尔;(b) ref=max 的 dB 梅尔;(c) MFCC-13 + 一阶差分 + 二阶差分。报告结果并分析。
(n_frames, n_freq_bins) 矩阵。log(|X| + eps) 压缩动态范围。m = 2595·log10(1 + f/700):1 kHz 下近似线性、以上近似对数,匹配人耳感知。下一节,我们用这些特征搭建音频分类系统——从 MFCC 上的 k-NN,一路讲到基于对数梅尔的 Transformer 模型 AST 与 BEATs。