语音是人类最自然的交互方式,也是深度学习最成功的落地场景之一。从手机输入法的语音转文字,到智能音箱的唤醒词,再到有声书的自动合成,背后是同一套声学建模思路:把连续的声波,映射成离散的符号。
语音信号的物理与数学表示
声音是空气压强的振动。麦克风把振动转成电压,模数转换器再把它转成离散时间序列——这就是模型看到的一维波形。
采样率与量化位深
- 采样率(Sample Rate):每秒采样点数。电话语音 8kHz,宽带语音 16kHz,音乐 44.1kHz。16kHz 是语音识别的标准,因为人声能量主要集中在 8kHz 以下。
- 量化位深(Bit Depth):每个采样点的精度,常见 16-bit,取值范围
[-32768, 32767]。训练前统一归一化到[-1, 1]。
# 归一化与重采样
import torchaudio
wav, sr = torchaudio.load("speech.wav") # wav: [channels, time]
wav = wav.mean(dim=0, keepdim=True) # 多声道转单声道
if sr != 16000:
wav = torchaudio.functional.resample(wav, sr, 16000)
wav = wav / wav.abs().max() # 峰值归一化到 [-1, 1]
短时傅里叶变换与分帧
波形本身信息密度低,模型很难直接从原始采样点学到音素。经典做法是分帧 + 加窗 + 傅里叶变换:
- 分帧:每帧 25ms(16kHz 下 400 个采样点),帧移 10ms(160 点),相邻帧重叠。
- 加窗:乘汉明窗,减少帧边界处的频谱泄漏。
- STFT:对每帧做 FFT,得到该帧的频谱。
帧数 ≈ 音频时长(秒) / 帧移(0.01s)
# 10 秒音频 → 约 1000 帧,每帧 201 个频点(n_fft=400)
相位与幅度
STFT 的输出是复数,包含幅度与相位。人耳对幅度远比相位敏感,因此绝大多数声学特征只用幅度谱,把相位丢掉。相位在语音合成里则由声码器负责重建。
声学特征:从波形到梅尔频谱
原始频谱维度高(201 维)且不符合人耳感知。梅尔刻度(Mel Scale) 把频率轴按人耳感知重新拉伸:低频区分辨率高,高频区压缩。
梅尔频谱与滤波器组
梅尔滤波器组是一组三角形滤波器,把线性频谱投影到 40~80 维的梅尔频带:
import torchaudio.transforms as T
mel = T.MelSpectrogram(
sample_rate=16000,
n_fft=400, # 25ms 窗
hop_length=160, # 10ms 帧移
n_mels=80, # 梅尔频带数
f_min=0.0,
f_max=8000.0,
)
log_mel = T.AmplitudeToDB()(mel(wav)) # 转对数刻度
log_mel 就是 Whisper 等现代模型的输入特征,形状 [n_mels, frames]。
MFCC 与频谱图的取舍
| 特征 | 维度 | 特点 | 适用 |
|---|---|---|---|
| 波形 | 16000/s | 信息完整,模型负担重 | 端到端模型 |
| 梅尔频谱 | 80 | 保留细节,与听觉对齐 | 神经 ASR 主流 |
| MFCC | 13~40 | 经 DCT 去相关,维度低 | 传统 GMM-HMM |
MFCC 在 DCT 步骤丢掉了频带间的相关性信息,对神经网络反而是损失。现代 ASR 一律用 log-Mel,不用 MFCC,这是十年前到现在的最大观念转变之一。
数据增强
语音数据量往往不足,增强是标配:
- SpecAugment:在频谱图上随机遮蔽若干时间步与频带,迫使模型不依赖单一频段。
- 速度扰动:0.9x~1.1x 变速,模拟不同语速。
- 加噪混响:叠加背景噪声与房间冲激响应,提升鲁棒性。
spec_aug = T.FrequencyMasking(freq_mask_param=27)
time_aug = T.TimeMasking(time_mask_param=100)
log_mel = time_aug(spec_aug(log_mel))
ASR 的经典架构:CTC 与注意力
语音识别的核心难点是输入输出长度不对齐:一段 5 秒音频有 500 帧特征,但对应的文字可能只有 20 个字符。CTC 与注意力是解决对齐问题的两条路线。
CTC 的空白符机制
CTC(Connectionist Temporal Classification) 引入一个特殊的空白符 ε,让模型为每一帧输出一个符号(含空白),再通过「合并重复、删除空白」的规则折叠成最终序列。
帧级输出: h h ε ε e e l l ε l ε o o
折叠规则: 合并连续重复 → 删除 ε
最终结果: h e l l o
CTC 的损失对所有可能的对齐路径求和,用动态规划(前向后向算法)高效计算:
import torch.nn as nn
ctc_loss = nn.CTCLoss(blank=0, zero_infinity=True)
# log_probs: [T, N, C] 逐帧 log 概率
# targets: [N, S] 目标序列
# input_lengths / target_lengths: 各自长度
loss = ctc_loss(log_probs, targets, input_lengths, target_lengths)
CTC 的最大优势是单调对齐:天然适合流式识别,因为每一帧只依赖当前与历史。缺点是条件独立假设——假设每帧输出独立,无法建模语言层面的依赖,通常需要外接语言模型做纠错。
注意力编码解码
另一条路线是 Attention-based Encoder-Decoder(AED):编码器把整段音频压成隐状态序列,解码器自回归地生成文字,每一步用交叉注意力「看」编码器输出。
- 优点:能建模长程语言依赖,识别质量通常优于纯 CTC。
- 缺点:需要完整音频才能开始解码,天然不适合流式。
RNN-T 与混合方案
工业界的主流是混合架构:
| 架构 | 对齐方式 | 流式 | 备注 |
|---|---|---|---|
| CTC | 帧级单调 | 易 | 简单,需外部 LM |
| AED | 交叉注意力 | 难 | 质量高,延迟大 |
| RNN-T | 联合网络 | 可 | 流式首选,训练复杂 |
| CTC/AED 混合 | 两者联合 | 可 | 取长补短,工业常用 |
混合训练的做法是 L = λ·L_ctc + (1-λ)·L_aed,用 CTC 加速收敛并提供单调对齐约束,用 AED 提供语言建模能力。
Conformer 与流式识别
2020 年 Google 提出的 Conformer 成为 ASR 编码器的事实标准,至今仍是多数工业系统的骨干。
卷积与自注意力的融合
Conformer 的洞察是:自注意力擅长建模全局依赖,卷积擅长捕捉局部模式(如共振峰的短时变化)。于是它把两者塞进一个块:
Conformer Block = ½ FFN → MHSA → Conv Module → ½ FFN
- MHSA:多头自注意力,建模全局上下文。
- Conv Module:深度可分离卷积 + GLU,捕捉局部声学模式。
- Macaron FFN:前后各半个前馈网络,训练更稳。
import torch.nn as nn
class ConformerBlock(nn.Module):
def __init__(self, d_model=512, kernel=31):
super().__init__()
self.ffn1 = nn.Sequential(nn.Linear(d_model, d_model * 4),
nn.SiLU(),
nn.Linear(d_model * 4, d_model))
self.attn = nn.MultiheadAttention(d_model, num_heads=8,
batch_first=True)
self.conv = nn.Sequential(
nn.Conv1d(d_model, d_model * 2, kernel,
padding=kernel // 2, groups=d_model),
nn.GLU(dim=1),
)
self.norm = nn.LayerNorm(d_model)
def forward(self, x):
x = x + 0.5 * self.ffn1(self.norm(x))
x = x + self.attn(self.norm(x), self.norm(x), self.norm(x))[0]
c = self.conv(self.norm(x).transpose(1, 2)).transpose(1, 2)
x = x + c
return x
流式识别的分块机制
流式识别的关键是限制每个时刻能看到的历史范围。Conformer 用分块注意力(Chunked Attention):把音频切成固定长度的块(如 16 帧),块内做全注意力,块间用带缓存的自注意力。
# 流式推理的核心:缓存上一块的 KV
cache = None
for chunk in audio_chunks:
out, cache = encoder(chunk, cache=cache)
text_chunk = decoder(out)
yield text_chunk
代价是感受野受限:流式模型只能看有限历史,识别质量通常低于离线模型。工业上用延迟-质量曲线权衡:块越大延迟越高、质量越好。
延迟与质量的权衡
| 模式 | 延迟 | 相对质量 | 场景 |
|---|---|---|---|
| 离线全量 | 秒级 | 100% | 录音转写 |
| 大块流式 | 500ms+ | 97% | 会议实时字幕 |
| 小块流式 | 100ms | 92% | 语音输入法 |
| 逐帧流式 | 30ms | 85% | 唤醒词、命令词 |
Whisper 与大规模弱监督预训练
2022 年 OpenAI 的 Whisper 证明了「数据规模 + 弱监督」的威力:用 68 万小时网络音频(带字幕)训练一个标准 Encoder-Decoder,不做任何领域定制,在多个数据集上零样本超越专门训练的模型。
多任务统一格式
Whisper 把语音任务统一成序列到序列的文本生成:
<|startoftranscript|><|zh|><|transcribe|><|notimestamps|> 你好世界<|endoftext|>
- 语言标记(
<|zh|>)让一个模型支持 99 种语言。 - 任务标记区分「转写」与「翻译」(转写保留原语言,翻译统一成英文)。
- 时间戳标记支持词级对齐。
这种用特殊 token 表达任务的设计,后来被大量多模态模型借鉴。
数据规模的作用
Whisper 的关键实验结论:数据量与多样性比模型架构更重要。在同等参数量下,弱监督大数据训练的模型泛化能力远强于小规模精标数据训练的模型。代价是模型很大(large-v3 约 1.5B 参数),推理成本高。
import whisper
model = whisper.load_model("large-v3")
result = model.transcribe(
"audio.wav",
language="zh",
task="transcribe",
word_timestamps=True,
temperature=(0.0, 0.2, 0.4), # 失败时回退采样,抑制重复
)
print(result["text"])
蒸馏与量化部署
生产上很少直接用 large-v3。压缩路径:
- 蒸馏:用 large 模型生成伪标签,训练 small/base 学生模型。
- 量化:INT8 量化,权重体积减半,速度提升明显。
- 更快推理:改用 faster-whisper(CTranslate2 后端),同等精度下速度快数倍。
# faster-whisper:CTranslate2 后端,显存与速度都更优
from faster_whisper import WhisperModel
model = WhisperModel("large-v3", device="cuda", compute_type="int8_float16")
segments, info = model.transcribe("audio.wav", language="zh", vad_filter=True)
for seg in segments:
print(f"{seg.start:.2f}-{seg.end:.2f} {seg.text}")
vad_filter=True 开启语音活动检测,能显著减少静音段的幻觉输出——这是 Whisper 生产部署最重要的一行配置。
TTS 与神经声码器
语音合成(Text-to-Speech)走的是相反方向:把文字变成声波。现代 TTS 分两级:声学模型生成中间表示,声码器把中间表示还原成波形。
Tacotron 与 FastSpeech
- Tacotron 2:自回归生成梅尔频谱,质量高但慢,且容易出现注意力跳步导致的重复或漏字。
- FastSpeech 2:非自回归,用时长预测器显式预测每个音素的帧数,一次并行生成全部频谱。速度快几十倍,稳定性好。
FastSpeech 2 流水线:
音素序列 → 编码器 → 时长预测 → 长度规整(按预测时长展开)→ 解码器 → 梅尔频谱
时长预测是 FastSpeech 的核心:它把「对齐」从隐式的注意力变成了显式的回归目标,消除了自回归的误差累积。
神经声码器与编解码
声码器把梅尔频谱还原成 16kHz 波形(每秒 16000 个采样点):
| 声码器 | 原理 | 质量 | 速度 |
|---|---|---|---|
| Griffin-Lim | 相位迭代估计 | 一般 | 快 |
| WaveNet | 自回归采样 | 极高 | 极慢 |
| HiFi-GAN | 生成对抗 + 多周期判别器 | 高 | 快 |
| BigVGAN | 大规模训练 GAN | 很高 | 中 |
HiFi-GAN 是当前性价比最高的选择:音质接近 WaveNet,速度满足实时。近年还出现了神经音频编解码器(SoundStream、EnCodec),把音频压成离散 token,让 TTS 变成「语言模型生成 token」的问题,进一步统一了语音与文本的建模范式。
# HiFi-GAN 声码器推理
import torch
vocoder = torch.hub.load("jik876/hifi-gan", "hifigan_16khz")
with torch.no_grad():
wav = vocoder(mel_spectrogram.unsqueeze(0)).squeeze()
torchaudio.save("output.wav", wav.unsqueeze(0), 16000)
端到端 TTS
最新趋势是端到端:文本直接生成波形或离散音频码,跳过显式梅尔频谱。VALL-E、Tortoise 等模型用「文本 + 3 秒参考音频」就能克隆音色,把 TTS 变成了少样本学习问题。
说话人识别与语音克隆
除了「说了什么」,语音还携带「谁在说」的信息。
声纹嵌入
说话人识别把一段语音压成一个固定维度的声纹嵌入(Speaker Embedding),如 192 维的 ECAPA-TDNN 输出。同一个人的两段语音嵌入余弦相似度高,不同人则低。
from speechbrain.pretrained import EncoderClassifier
spk = EncoderClassifier.from_hparams(
source="speechbrain/spkrec-ecapa-voxceleb"
)
emb = spk.encode_batch(wav) # [1, 1, 192]
score = torch.nn.functional.cosine_similarity(emb_a, emb_b)
生产上的典型用法:注册声纹时存一个嵌入向量,验证时算余弦相似度与阈值比较。阈值需要按业务调——金融场景宁可拒真也不能认假。
语音克隆与安全边界
语音克隆只需几秒参考音频即可复刻音色,随之而来的是深度伪造(Deepfake) 风险。工程上的防护:
- 水印:在生成音频中嵌入不可感知的标识,便于溯源。
- 活体检测:验证时要求随机文本朗读,防止录音重放。
- 声纹 + 内容双因子:同时校验「是谁」与「说了什么」。
评测指标:WER、MOS 与延迟
语音任务没有单一指标,识别与合成各有一套。
识别指标
- WER(词错误率):
(替换 + 删除 + 插入) / 参考词数。中文按字算,称 CER。WER 可能超过 100%(插入过多)。 - 实时率 RTF:
处理耗时 / 音频时长。RTF < 1 才算实时。 - 首字延迟:流式场景的关键指标,从说完到出第一个字的时间。
def wer(ref, hyp):
# 用动态规划算编辑距离
r, h = ref.split(), hyp.split()
d = [[0] * (len(h) + 1) for _ in range(len(r) + 1)]
for i in range(len(r) + 1):
d[i][0] = i
for j in range(len(h) + 1):
d[0][j] = j
for i in range(1, len(r) + 1):
for j in range(1, len(h) + 1):
cost = 0 if r[i - 1] == h[j - 1] else 1
d[i][j] = min(d[i - 1][j] + 1, d[i][j - 1] + 1,
d[i - 1][j - 1] + cost)
return d[len(r)][len(h)] / max(len(r), 1)
合成指标
- MOS(平均意见分):人工 1~5 分打分,最权威但昂贵。常配 MOSNet 之类的自动打分模型做近似。
- RTF 与首包延迟:TTS 服务的关键 SLA。
- 韵律自然度:停顿、重音、语调是否自然,目前主要靠人评。
端到端延迟分解
流式语音系统的端到端延迟由多段构成:
| 阶段 | 典型耗时 | 优化手段 |
|---|---|---|
| 音频采集 | 20~40ms | 减小分块 |
| VAD 检测 | 10~30ms | 轻量模型 |
| 声学编码 | 20~80ms | 分块注意力、量化 |
| 解码 | 10~50ms | 贪心/beam 裁剪 |
| 后处理与上屏 | 5~20ms | 增量渲染 |
工程部署与流式管线
从模型到服务,语音系统有一整套专属工程问题。
流式管线的组件划分
一个生产级流式 ASR 服务通常拆成:
麦克风 → VAD → 分块缓冲 → 声学编码器 → 解码器 → 标点恢复 → 上屏
↑
热词/上下文注入
- VAD:判断是否有语音,静音段直接跳过,节省算力。
- 分块缓冲:把连续音频切成定长块,配合左上下文拼接。
- 标点恢复:ASR 输出通常无标点,需单独的标点模型补上。
批处理与并发
流式服务天然是长连接,每个连接占用显存。并发估算:
单卡显存 = 模型权重 + 每连接缓存 × 并发数
# 例:Conformer 编码器 200MB,每连接缓存 30MB,80GB 卡
# 可承载并发 ≈ (80 - 20) GB / 30 MB ≈ 2000 路(理论)
实际还要考虑计算瓶颈。常用的调度手段是动态批处理:把多个连接的当前分块拼成一个 batch 一起前向,空闲连接不占算力。
# 动态批处理伪代码
while True:
batch = collect_ready_chunks(timeout_ms=10) # 攒一小会儿
if not batch:
continue
outs = model(batch) # 一次前向处理多路
for conn, out in zip(batch.connections, outs):
conn.push(out)
热词与上下文注入
业务方总想让模型认识专有名词(人名、产品名)。主流做法是偏置解码:在 beam search 时给热词更高的分数。
# 偏置解码:命中热词路径时加分
for beam in beams:
if beam.last_token_in(hotwords):
beam.score += bias_weight
更先进的做法是用 Contextual Biasing:把热词编码成向量,让解码器通过注意力「参考」这些词,效果比纯加分更好,尤其在热词很多时。
生产踩坑清单
- 采样率不匹配:训练 16kHz、推理送 8kHz 音频,WER 直接翻倍。上线前统一采样率并做断言。
- Whisper 静音幻觉:长静音段会生成「谢谢观看」之类的幻觉字幕,必须开 VAD 过滤。
- 重采样质量:用线性插值重采样会引入混叠,务必用带抗混叠滤波的
torchaudio.functional.resample或soxr。 - 噪声鲁棒性:实验室干净音频上训练的模型,到了车载/户外场景 WER 可能恶化 3 倍以上,必须做加噪增强。
- 数字与单位:ASR 输出「一二三」还是「123」,取决于后处理规范化,需和下游系统约定一致。
- 标点与大小写:英文 ASR 常输出全小写无标点,需专门恢复,否则下游 NLP 任务效果受损。
- 首字延迟与块大小:块越大首字延迟越高,语音输入法要压在 100ms 内,会议字幕可以放宽到 500ms。
总结
语音深度学习的骨架是「特征 → 声学模型 → 声码器/文本」:log-Mel 特征把波形变成模型友好的二维图,CTC 与注意力解决输入输出长度不对齐,Conformer 用卷积加自注意力统一了局部与全局建模,Whisper 用海量弱监督数据证明了规模的力量。合成侧,FastSpeech 用显式时长预测换来非自回归的并行速度,HiFi-GAN 用 GAN 把声码器做到实时。落地时,延迟预算、采样一致性、VAD 与热词偏置往往比换模型更能决定成败——语音系统的线上体验,一半在模型,一半在管道。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。