️ Day 1语音信号处理基础下接下来我们学习语音面试的最高频考点之一MFCC。然后做完整的总结和面试模拟。第六步MFCC —— 在 FBank 基础上再进一步6.1 回顾目前我们走到了哪里原始波形 → 分帧加窗 → FFT → 功率谱 → Mel滤波器组 → Log → FBankLog-Mel频谱图 ↓ 我们在这里 ↓ DCT ↓ MFCCMFCC FBank DCT离散余弦变换6.2 为什么还要做 DCTFBank 的 80 个维度之间是有相关性的——相邻的 Mel 滤波器有重叠所以相邻维度的值是相关的。DCT 的作用 去相关性Decorrelation 压缩DCT 类似于一种频率分析但作用在 Mel 频带维度上把 80 维有相关性的 FBank → 变换成一组正交的、去相关的系数然后只保留前12~13 个系数低阶系数扔掉后面的低阶系数描述了频谱的整体包络形状声道特征高阶系数描述频谱细节对语音识别不太重要FBank (80维, 有相关性) ↓ DCT MFCC 全部系数 (80维, 去相关) ↓ 截断只保留前 12~13 个 MFCC (13维, 紧凑表示)6.3 MFCC 完整提取流程 ⭐⭐⭐面试背诵级步骤1: 预加重 (Pre-emphasis) y[n] x[n] - α·x[n-1], α ≈ 0.97 目的: 提升高频分量平衡频谱高频能量通常较低 ​ 步骤2: 分帧 (Framing) 帧长 25ms, 帧移 10ms ​ 步骤3: 加窗 (Windowing) Hamming 窗: w[n] 0.54 - 0.46·cos(2πn/(N-1)) 目的: 减少频谱泄漏 ​ 步骤4: FFT 每帧做快速傅里叶变换得到频谱 ​ 步骤5: 功率谱 (Power Spectrum) P |X(k)|² / N ​ 步骤6: Mel 滤波器组 80个三角滤波器按 Mel 刻度均匀分布 每个滤波器输出加权能量和 ​ 步骤7: 取对数 (Log) 模拟人耳对响度的对数感知 → 到这一步 FBank 特征 ​ 步骤8: DCT (离散余弦变换) 去相关性压缩 ​ 步骤9: 取前 12~13 个系数 → 这就是 MFCC ​ 可选: 加上 差分(Delta) 和 加速度(Delta-Delta) → 最终 MFCC 静态(13) 一阶差分(13) 二阶差分(13) 39 维6.4 Delta 和 Delta-Delta 是什么MFCC 只描述了每一帧此刻的频谱特征但语音是动态变化的。Delta一阶差分描述特征随时间的变化速度Delta-Delta二阶差分描述特征随时间的变化加速度直觉理解静态 MFCC: 这一帧的频谱长什么样 Delta: 频谱正在怎么变化 像速度 Delta-Delta: 频谱变化的趋势 像加速度6.5 动手提取 MFCCimport librosa import librosa.display import matplotlib.pyplot as plt import numpy as np ​ # 加载音频 y, sr librosa.load(librosa.ex(trumpet), sr16000) ​ # 提取 MFCC mfccs librosa.feature.mfcc( yy, srsr, n_mfcc13, # 取前13个系数 n_fft512, hop_length160, win_length400, n_mels80, # Mel滤波器数量 windowhamming ) ​ print(fMFCC 形状: {mfccs.shape}) print(f → {mfccs.shape[0]} 维 × {mfccs.shape[1]} 帧) ​ # 计算 Delta 和 Delta-Delta mfcc_delta librosa.feature.delta(mfccs, order1) mfcc_delta2 librosa.feature.delta(mfccs, order2) ​ # 拼接成 39 维特征 mfcc_full np.concatenate([mfccs, mfcc_delta, mfcc_delta2], axis0) print(f完整 MFCC (静态ΔΔΔ) 形状: {mfcc_full.shape}) # (39, 帧数) ​ # 可视化 fig, axes plt.subplots(3, 1, figsize(14, 10)) ​ # 静态 MFCC img1 librosa.display.specshow(mfccs, srsr, hop_length160, x_axistime, axaxes[0]) axes[0].set_title(MFCC (静态, 13维)) axes[0].set_ylabel(MFCC Coefficients) fig.colorbar(img1, axaxes[0]) ​ # Delta img2 librosa.display.specshow(mfcc_delta, srsr, hop_length160, x_axistime, axaxes[1]) axes[1].set_title(MFCC Delta (一阶差分, 13维)) axes[1].set_ylabel(Delta Coefficients) fig.colorbar(img2, axaxes[1]) ​ # Delta-Delta img3 librosa.display.specshow(mfcc_delta2, srsr, hop_length160, x_axistime, axaxes[2]) axes[2].set_title(MFCC Delta-Delta (二阶差分, 13维)) axes[2].set_ylabel(Delta-Delta Coefficients) fig.colorbar(img3, axaxes[2]) ​ plt.tight_layout() plt.show()6.6 理解每个 MFCC 系数的含义# 各系数的含义 # 第0个系数(C0): 代表这一帧的总能量 # 第1个系数(C1): 频谱的整体倾斜度低频vs高频的平衡 # 第2~12个系数: 描述频谱包络的细节形状 ​ # 可视化单个系数随时间的变化 plt.figure(figsize(14, 6)) for i in range(5): plt.subplot(5, 1, i1) plt.plot(mfccs[i], linewidth0.8) plt.ylabel(fC{i}) if i 0: plt.title(各MFCC系数随时间变化) if i 4: plt.xticks([]) plt.xlabel(Frame) plt.tight_layout() plt.show()第七步FBank vs MFCC —— 面试必考对比 ⭐⭐⭐7.1 一图对比FBank MFCC (Log-Mel频谱图) (FBank DCT 截断) ┌──────────────────┐ ┌──────────────────┐ 维度: │ 80 维 │ │ 13 维 (39维) │ └──────────────────┘ └──────────────────┘ 相关性: 相邻维度有相关性 维度之间去相关 信息量: 保留更多信息 有信息损失(DCT截断) 适用模型: 深度学习 (DNN/CNN/Transformer) 传统模型 (GMM-HMM) 是否主流: ⭐ 现代主流 逐渐被取代7.2 核心区别总结特征FBankMFCC计算Log-Mel频谱到log为止FBank DCT 截断维度通常 80 维通常 13 维含delta共39维维度间相关性有相关性去相关正交信息量更完整DCT截断丢失部分信息适合模型深度学习模型能自己学习去相关传统GMM需要对角协方差要求特征去相关现在用哪个⭐FBank 是主流部分传统系统还在用7.3 面试回答模板Q: FBank 和 MFCC 有什么区别为什么现在更多用 FBankA:MFCC 是在 FBank 的基础上多做了一步 DCT离散余弦变换目的是去相关性并降维。在传统的 GMM-HMM 系统中GMM 通常使用对角协方差矩阵要求特征各维度独立所以 MFCC 的去相关性很有用。但是在深度学习时代神经网络本身就能学习特征之间的相关性比如卷积层可以建模相邻频带的关系不需要人工去相关。而 DCT 截断反而会丢失信息。所以现在主流的端到端语音系统如 Conformer、Whisper 等都直接使用80维的 FBankLog-Mel频谱图作为输入特征。7.4 动手对比# FBank vs MFCC 对比可视化 # FBank mel_spec librosa.feature.melspectrogram( yy, srsr, n_fft512, hop_length160, win_length400, n_mels80 ) fbank librosa.power_to_db(mel_spec, refnp.max) ​ # MFCC mfccs librosa.feature.mfcc( yy, srsr, n_mfcc13, n_fft512, hop_length160, win_length400, n_mels80 ) ​ fig, axes plt.subplots(2, 1, figsize(14, 8)) ​ img1 librosa.display.specshow(fbank, srsr, hop_length160, x_axistime, y_axismel, axaxes[0]) axes[0].set_title(FBank (Log-Mel Spectrogram) — 80维) fig.colorbar(img1, axaxes[0], format%2.0f dB) ​ img2 librosa.display.specshow(mfccs, srsr, hop_length160, x_axistime, axaxes[1]) axes[1].set_title(MFCC — 13维) axes[1].set_ylabel(MFCC Coefficient) fig.colorbar(img2, axaxes[1]) ​ plt.tight_layout() plt.show()第八步完整 Pipeline 总结 ️语音特征提取完整流程 ═══════════════════ ​ 原始音频 (.wav, 16kHz, 16bit) │ ▼ ① 预加重 (Pre-emphasis) ← 提升高频平衡频谱 │ y[n] x[n] - 0.97·x[n-1] ▼ ② 分帧 (Framing) ← 帧长25ms, 帧移10ms │ ▼ ③ 加窗 (Windowing) ← Hamming窗减少频谱泄漏 │ ▼ ④ FFT (快速傅里叶变换) ← 时域 → 频域 │ ▼ ⑤ 功率谱 |X(k)|² ← 每个频率的能量 │ ▼ ⑥ Mel 滤波器组 (80个三角滤波器) ← 模拟人耳非线性感知 │ ▼ ⑦ 取对数 (Log) ← 模拟人耳对响度的对数感知 │ ├─────────────────────────────── → FBank (80维) ⭐ 深度学习主流输入 │ ▼ ⑧ DCT (离散余弦变换) ← 去相关性 │ ▼ ⑨ 取前 12~13 个系数 ← 压缩 │ ├─────────────────────────────── → MFCC (13维) │ ▼ ⑩ Delta Delta-Delta ← 加入动态特征 │ └─────────────────────────────── → MFCCΔΔΔ (39维) 传统系统输入第九步Day 1 综合实践把所有知识串起来手动实现一遍完整流程不用 librosa 的高层封装import numpy as np import librosa from scipy.fftpack import dct import matplotlib.pyplot as plt ​ # 加载音频 y, sr librosa.load(librosa.ex(trumpet), sr16000) ​ # ① 预加重 pre_emphasis 0.97 y_preemph np.append(y[0], y[1:] - pre_emphasis * y[:-1]) ​ print(--- ① 预加重 ---) print(f原始信号前5个值: {y[:5]}) print(f预加重后前5个值: {y_preemph[:5]}) ​ # ② 分帧 frame_length int(0.025 * sr) # 25ms 400 个采样点 hop_length int(0.010 * sr) # 10ms 160 个采样点 ​ # 计算帧数 num_frames 1 (len(y_preemph) - frame_length) // hop_length ​ # 分帧 frames np.zeros((num_frames, frame_length)) for i in range(num_frames): start i * hop_length frames[i] y_preemph[start : start frame_length] ​ print(f\n--- ② 分帧 ---) print(f帧长: {frame_length} 点 ({frame_length/sr*1000:.0f}ms)) print(f帧移: {hop_length} 点 ({hop_length/sr*1000:.0f}ms)) print(f总帧数: {num_frames}) print(fframes 形状: {frames.shape}) ​ # ③ 加窗 (Hamming) hamming_window np.hamming(frame_length) frames_windowed frames * hamming_window ​ print(f\n--- ③ 加窗 ---) plt.figure(figsize(10, 3)) plt.plot(hamming_window) plt.title(Hamming Window) plt.show() ​ # ④ FFT NFFT 512 magnitude np.abs(np.fft.rfft(frames_windowed, nNFFT)) # rfft只返回正频率 ​ print(f\n--- ④ FFT ---) print(fFFT 点数: {NFFT}) print(f频谱形状: {magnitude.shape} (帧数 × 频率bins)) print(f频率 bins NFFT/2 1 {NFFT//2 1}) ​ # ⑤ 功率谱 power_spectrum (magnitude ** 2) / NFFT ​ print(f\n--- ⑤ 功率谱 ---) print(f功率谱形状: {power_spectrum.shape}) ​ # ⑥ Mel 滤波器组 n_mels 80 mel_filter librosa.filters.mel(srsr, n_fftNFFT, n_melsn_mels) print(f\n--- ⑥ Mel 滤波器 ---) print(fMel 滤波器矩阵形状: {mel_filter.shape}) # (80, 257) ​ # 应用滤波器: (帧数, 257) × (257, 80)^T ... # 或者 (80, 257) (257, 帧数) mel_power np.dot(power_spectrum, mel_filter.T) # (帧数, 80) print(fMel 功率谱形状: {mel_power.shape}) ​ # ⑦ 取对数 → FBank mel_power np.where(mel_power 0, np.finfo(float).eps, mel_power) # 避免log(0) fbank np.log(mel_power) # (帧数, 80) ​ print(f\n--- ⑦ FBank ---) print(fFBank 形状: {fbank.shape}) ​ # 可视化 FBank plt.figure(figsize(14, 5)) plt.imshow(fbank.T, aspectauto, originlower, cmapviridis) plt.title(FBank (手动实现)) plt.xlabel(Frame) plt.ylabel(Mel Filter Index) plt.colorbar(labelLog Energy) plt.show() ​ # ⑧⑨ DCT → MFCC n_mfcc 13 mfcc_manual dct(fbank, type2, axis1, normortho)[:, :n_mfcc] # 取前13个 ​ print(f\n--- ⑧⑨ MFCC ---) print(fMFCC 形状: {mfcc_manual.shape}) ​ # 可视化 MFCC plt.figure(figsize(14, 5)) plt.imshow(mfcc_manual.T, aspectauto, originlower, cmapviridis) plt.title(MFCC (手动实现, 13维)) plt.xlabel(Frame) plt.ylabel(MFCC Coefficient) plt.colorbar() plt.show() ​ # 验证和 librosa 的结果对比 mfcc_librosa librosa.feature.mfcc( yy, srsr, n_mfcc13, n_fft512, hop_length160, win_length400, n_mels80 ).T # 转置为 (帧数, 13) ​ print(f\n--- 验证 ---) print(f手动 MFCC 形状: {mfcc_manual.shape}) print(flibrosa MFCC 形状: {mfcc_librosa.shape}) # 注意由于预加重和其他微小差异数值不会完全一样但趋势应该一致 min_frames min(mfcc_manual.shape[0], mfcc_librosa.shape[0]) correlation np.corrcoef(mfcc_manual[:min_frames, 1].flatten(), mfcc_librosa[:min_frames, 1].flatten())[0, 1] print(f第1个MFCC系数的相关系数: {correlation:.4f}) # 应该很接近1第十步补充知识 —— SpecAugment这不是特征提取的一部分但面试很爱问属于数据增强。SpecAugment (Google 2019)在训练 ASR 模型时直接对 FBank/Mel 频谱图做增强┌─────────────────────┐ │ ███████████████████ │ ← 原始 Mel 频谱图 │ ███████████████████ │ │ ███████████████████ │ │ ███████████████████ │ └─────────────────────┘ ​ 三种增强方式: ​ 1. Time Warping (时间扭曲) ← 沿时间轴非线性扭曲 2. Frequency Masking (频率遮蔽) ← 随机遮住某些频率带 ​ ┌─────────────────────┐ │ ███████████████████ │ │ ░░░░░░░░░░░░░░░░░░ │ ← 被遮住的频率带 │ ███████████████████ │ │ ███████████████████ │ └─────────────────────┘ ​ 3. Time Masking (时间遮蔽) ← 随机遮住某些时间段 ​ ┌─────────────────────┐ │ █████░░░░░██████████ │ │ █████░░░░░██████████ │ ← 被遮住的时间段 │ █████░░░░░██████████ │ │ █████░░░░░██████████ │ └─────────────────────┘为什么有效迫使模型不要过度依赖某些频率或时间段的信息提高鲁棒性和泛化能力实现极其简单效果显著WER 相对下降 10%# SpecAugment 简单演示 import librosa import numpy as np import matplotlib.pyplot as plt ​ y, sr librosa.load(librosa.ex(trumpet), sr16000) mel_spec librosa.feature.melspectrogram(yy, srsr, n_mels80, hop_length160) mel_db librosa.power_to_db(mel_spec, refnp.max) ​ fig, axes plt.subplots(1, 3, figsize(18, 4)) ​ # 原始 axes[0].imshow(mel_db, aspectauto, originlower) axes[0].set_title(Original) ​ # Frequency Masking mel_freq_mask mel_db.copy() f0 np.random.randint(10, 60) f_width np.random.randint(5, 20) mel_freq_mask[f0:f0f_width, :] mel_db.min() axes[1].imshow(mel_freq_mask, aspectauto, originlower) axes[1].set_title(fFrequency Masking (f{f0}, width{f_width})) ​ # Time Masking mel_time_mask mel_db.copy() t0 np.random.randint(10, mel_db.shape[1]-50) t_width np.random.randint(10, 40) mel_time_mask[:, t0:t0t_width] mel_db.min() axes[2].imshow(mel_time_mask, aspectauto, originlower) axes[2].set_title(fTime Masking (t{t0}, width{t_width})) ​ plt.tight_layout() plt.show() Day 1 面试模拟题下面是面试中关于 Day 1 内容的高频问题。请你先自己想然后对照答案Q1: 请完整描述 MFCC 的提取流程。预加重用一阶高通滤波器 (y[n] x[n] - 0.97x[n-1]) 提升高频分帧帧长 25ms帧移 10ms相邻帧有重叠加窗每帧乘以 Hamming 窗减少频谱泄漏FFT每帧做快速傅里叶变换得到频谱功率谱取模的平方Mel 滤波器组用 80 个按 Mel 刻度分布的三角滤波器加权求和取对数模拟人耳对响度的对数感知 → 到这里就是 FBankDCT离散余弦变换去相关性取前 12-13 个系数→ MFCC可选计算 Delta 和 Delta-Delta拼成 39 维Q2: FBank 和 MFCC 有什么区别为什么深度学习时代更多用 FBankMFCC 比 FBank 多了 DCT 截断两步。DCT 的目的是去相关性因为传统 GMM 使用对角协方差矩阵要求输入特征各维度独立。但在深度学习中神经网络尤其是 CNN本身就能学习特征间的相关性关系不需要人为去相关。DCT 截断反而会丢失信息。所以现代端到端系统Conformer, Whisper等直接使用 80 维 FBank 作为输入。Q3: 为什么要用 Mel 频率刻度人耳对频率的感知是非线性的对低频区域的分辨能力强对高频区域的分辨能力弱。Mel 刻度通过 (m 2595 \log_{10}(1 f/700)) 来模拟这种非线性感知。在 Mel 刻度下低频部分获得更高的分辨率高频部分被压缩使得特征更符合人类听觉系统。Q4: 为什么帧长通常选 25ms帧移选 10ms帧长 25ms基于短时平稳假设——语音信号在 20~30ms 内可近似视为平稳信号。太短则频率分辨率不够频率分辨率 采样率/FFT点数太长则跨越不同音素不再平稳。帧移 10ms保证相邻帧有约 60% 的重叠25ms帧长 - 10ms帧移 15ms重叠使时间轴上过渡平滑不丢失信息。Q5: SpecAugment 是什么为什么有效SpecAugment 是 Google 2019 年提出的数据增强方法直接在频谱图上操作包括Time Warping时间轴非线性扭曲Frequency Masking随机遮住若干连续频率带Time Masking随机遮住若干连续时间帧有效原因迫使模型不过度依赖特定频率或时间段的信息提高泛化能力和鲁棒性。实现简单不需要额外数据效果却非常显著。Q6: 预加重为什么要做系数为什么是 0.97人在发声时声门激励信号经过声道后高频部分会有约-20dB/decade的衰减。预加重用一个一阶高通滤波器 (y[n] x[n] - \alpha \cdot x[n-1]) 来补偿这种衰减使频谱更平坦有助于后续的频谱分析和模型训练。系数 0.97 是经验值在 0.95~0.99 之间都可以0.97 是最常用的。注意在现代深度学习系统中有时会跳过预加重因为模型本身可以学到这种补偿。✅ Day 1 完成核心知识清单✅ 声音的物理本质频率、振幅、相位 ✅ 采样与量化采样率、奈奎斯特定理 ✅ 傅里叶变换时域→频域 ✅ STFT分帧FFT得到语谱图 ✅ 帧长25ms、帧移10ms 的原因 ✅ 加窗Hamming窗的原因 ✅ Mel 刻度模拟人耳非线性感知 ✅ Mel 滤波器组三角形滤波器 ✅ FBank Log-Mel 频谱图80维深度学习主流输入 ✅ MFCC FBank DCT 截断13维/39维 ✅ FBank vs MFCC 的区别与适用场景 ✅ Delta / Delta-Delta动态特征 ✅ SpecAugment频谱图数据增强 ✅ 预加重的作用 Day 2 预告明天我们进入语音识别 (ASR) 的核心——CTC 算法ASR 的问题定义为什么语音识别很难传统 ASR 流水线简要了解CTC 详解blank 标记、前向算法、解码CTC 的优缺点面试核心动手用 PyTorch 跑一个 CTC-based ASR