语音识别把音频转成文字,但“转出一句话”只是链路的一部分。采样率、声道、噪声、专有名词和长音频切段,都可能改变输出。这里沿着音频输入、特征、识别和评估走一遍,让错误能够定位到具体环节。
我做语音相关项目之前有个误解,以为这是个”调用 API 就行”的成熟领域。实际做下来发现:API 确实能调,但效果不好时你根本不知道往哪改——是采样率不对、是音频太吵、是领域词不认识,还是语言模型没选对。这篇先把”声波怎么变成文字”的链路讲清楚,再上手实战。
前置阅读:深度学习课程 06:RNN、LSTM 与 GRU(序列建模基础)和 深度学习课程 07:Transformer 与注意力机制。现代 ASR 模型大量复用这两个结构。
声波是怎么变成数字的
麦克风采集到的是连续声波,计算机只能处理离散数字,中间隔着一个采样过程:
- 采样率:每秒采多少个点。常见电话音频为 8kHz,Whisper 使用 16kHz 输入。以具体模型要求为准;重采样需要改变样本序列,不能只改文件头。
- 位深度:每个点用多少 bit 表示振幅,常见 16bit。
- 声道:单声道(mono)还是双声道(stereo)。Whisper 的这条推理路径使用单声道;如果左右声道分别记录不同说话人,应先保留声道身份,再分别处理,不能直接混合后期待说话人信息仍然完整。
一段 10 秒的 16kHz 单声道音频,解码后有 16 万个采样值。可以计算频谱特征,也有模型直接编码波形;特征形式是模型设计的一部分,不是所有 ASR 都必须先提取 MFCC。
MFCC:把波形压成”语音的指纹”
人耳对频率的感知不是线性的——我们对低频变化敏感,对高频变化迟钝。MFCC(梅尔频率倒谱系数)就是按人耳特性设计的特征:
- 分帧:这里采用 25ms 窗长、10ms 帧移,相邻帧重叠 15ms。窗长、帧移、重叠不是同一个量。
- 傅里叶变换:每帧从时域转到频域,看这一帧有哪些频率成分。
- 梅尔滤波:按人耳感知特性把频率轴压缩(低频细、高频粗)。
- 取对数 + 离散余弦变换:得到每帧十几维的 MFCC 系数。
一段 10 秒音频按上述设置、不做边界补齐时,有 floor((160000-400)/160)+1 = 998 帧;13 维 MFCC 共 12974 个数,元素数量约减少到原来的 1/12,并非两个数量级。压缩也会丢失信息,是否合适要由任务验证。
import librosa
# 加载音频并强制转成 16kHz 单声道
y, sr = librosa.load("speech.wav", sr=16000, mono=True)
mfcc = librosa.feature.mfcc(
y=y, sr=sr, n_mfcc=13, n_fft=400, win_length=400,
hop_length=160, center=False,
)
print(mfcc.shape) # (13, 帧数)
模型演进:从拼接系统到端到端
了解演进路线有助于理解现在为什么这么简单:
传统时代:声学模型(GMM-HMM,后来是 DNN-HMM)把 MFCC 映射到音素,发音词典把音素拼成词,语言模型把词连成通顺的句子。三个模块分开训练、分开调, pipeline 复杂,每个环节都要专家知识。
端到端时代:一个神经网络直接从频谱特征输出文字。CTC 解决了”音频帧和文字对不齐”的问题;RNN-Transducer 支持流式识别;Transformer/Conformer 结构把准确率推到新高度。代表就是 OpenAI 的 Whisper——68 万小时多语言数据训练,中文直接可用,开源免费。
Whisper 可以作为离线转写的基线,但低延迟流式场景需要单独选型。特别注意:Whisper 使用 log-Mel 频谱,不是上面演示的 13 维 MFCC;不要把该 MFCC 矩阵直接作为 Whisper 的输入。
Whisper 实战:中文语音转文字
# pip install openai-whisper
import whisper
model = whisper.load_model("small") # tiny/base/small/medium/large
result = model.transcribe(
"meeting.mp3",
language="zh", # 显式指定中文,避免自动检测出错
initial_prompt="以下是普通话会议录音,涉及人工智能、模型训练等术语。", # 领域提示
)
print(result["text"])
for seg in result["segments"]:
print(f"[{seg['start']:.1f}s - {seg['end']:.1f}s] {seg['text']}")
几个实战要点:
模型尺寸怎么选。 先在相同音频上记录 CER、实时率(推理秒数/音频秒数)、峰值内存与硬件,再决定尺寸。官方显存和速度表是特定环境下的近似参考,不能据此保证某个型号在你的中文会议、口音和设备上最好。安装还需要可用的 ffmpeg,首次加载可能下载模型。Whisper 官方说明
initial_prompt 是一个待验证的条件。 可以提供术语和输出风格示例,但不能保证提分或强制输出格式。比较无提示与有提示的术语错误、插入错误,尤其检查静音或噪声段是否生成了提示中的文字。
segments 带时间戳,但不等于说话人分离。 起止秒数可用于制作字幕;判断“谁在说”还需要声道信息或说话人分离模块,时间边界也应抽样检查。
工程化:长音频和脏音频
真实场景的音频不是教科书里的干净 10 秒录音:
长音频切分。 Whisper 的 transcribe 内部已经按滑动的 30 秒窗口处理,不应把它描述成整小时一次送进网络。整段解码仍有内存成本;如果要控制资源、重试或并行,可以在外层切块。VAD 删除静音后必须保存原始时间偏移,边界留适量重叠并去重,否则字幕会错位或丢字。下面只是接口示意,不是可运行的 VAD 实现:
# 用 silero-vad 或 webrtcvad 做语音活动检测后切分
# 伪代码示意流程:
# chunks = vad_split("meeting.mp3", max_duration=30)
# texts = [model.transcribe(c)["text"] for c in chunks]
降噪与归一化。 把原始音频作为对照组。降噪可能损伤辅音,增益可能放大底噪或削波;只有验证集 CER 和人工试听均支持时才保留该处理,不预设它一定优于换模型。
格式转换。 各种来源的音频格式五花八门,用 ffmpeg 统一成 16kHz 单声道 wav 是最稳的预处理:
ffmpeg -i input.m4a -ar 16000 -ac 1 -c:a pcm_s16le output.wav
评估:中文优先看 CER,别把整句当作一个词
语音识别效果的通用指标是 WER(词错误率):
S 是替换数,D 是删除数,I 是插入数,N 是参考词数;CER 则以字符计数。默认按空格分词的 WER 会把没有空格的中文句子视作一个词。中文若要报告 WER,必须说明分词器;否则优先报告 CER。插入很多时错误率可以超过 100%。
from jiwer import cer, wer
reference = "今天会议讨论模型训练进度"
hypothesis = "今天会议讨论模型训练进展"
print(cer(reference, hypothesis)) # 1/12 = 0.08333...
print(wer(reference, hypothesis)) # 1.0:默认分词把整句当成一个词
两句各有 12 个字符,只发生一次替换。已用本仓库标准库编辑距离实现复算这两个结果,输出见 运行记录。这不代表运行了 jiwer、librosa 或 Whisper;本轮没有安装 jiwer,也没有下载模型或转写录音。上面的 jiwer 调用依据 官方用法。
调预处理和模型用验证集,最终测试集只用于冻结方案后的验收。提前固定简繁体、空格、标点和数字归一化规则,两个输出用同一规则。全语料 CER 应计算总编辑数除以总参考字符数,不直接平均长短句的 CER;另报噪声、口音、术语和静音子集。50–100 条可做早期排错样本,但不自动具备充分的统计代表性。
踩坑排查清单
| 症状 | 原因 | 处理 |
|---|---|---|
| 识别结果全是乱码/英文 | 中文音频被当成其他语言 | 显式 language="zh" |
| 结果没有标点 | 模型/prompt 问题 | initial_prompt 里加一句带标点的示例 |
| 专业术语全错 | 领域词没见过 | initial_prompt 塞入术语;量大考虑微调 |
| 长音频 OOM 或极慢 | 解码缓存、模型尺寸或设备资源不足 | 测峰值内存与耗时;必要时外层切块并维护时间映射 |
| 音频加载报错 | 格式/编码问题 | ffmpeg 统一转 16kHz 单声道 wav |
| 噪声环境下识别崩 | 信噪比太低 | 先降噪;检查麦克风采集质量 |
| 同一段音频两次结果不同 | 解码配置或数值计算差异 | 固定模型版本、参数与环境;temperature=0 不保证跨硬件逐位一致 |
练习
- 录一段 30 秒的中文语音,分别用 Whisper 的 tiny 和 small 模型识别,对比错误数和耗时。
- 用
initial_prompt做对比实验:找一段含专业术语的音频,先空 prompt 识别,再把术语写进 prompt 识别,统计术语错误率的变化。 - 用 librosa 提取同一段音频的 MFCC,画出热力图(
librosa.display.specshow),直观理解”每帧 13 维”长什么样。 - 给一段带背景音乐的录音做识别,对比降噪前后的 WER。
面试常问
Q:语音识别的基本流程是什么? 传统路线:音频 → 预处理(采样率统一、分帧)→ 特征提取(MFCC/FBank)→ 声学模型 → 发音词典 → 语言模型 → 文字。端到端路线:音频 → 频谱特征 → 单个神经网络 → 直接输出文字。现代系统(Whisper 等)都是端到端。
Q:为什么 ASR 用 16kHz 采样率? 16kHz 采样的奈奎斯特频率为 8kHz,是许多语音系统的带宽与成本折中,不意味着 8kHz 以上没有语音信息。应匹配模型训练时的输入要求;8kHz 音频上采样到 16kHz 不能恢复原本缺失的高频。
Q:CTC 解决了什么问题? 语音帧和输出文字之间没有对齐关系(一个字对应多少帧不固定),CTC 通过引入”空白”符号和对所有可能对齐路径求和,让模型可以在没有逐帧标注的情况下训练。
Q:怎么提升特定领域(如医疗、法律)的识别准确率? 成本从低到高:initial_prompt 注入术语 → 后处理纠错(术语表替换)→ 用领域数据微调模型。先试最便宜的手段。
Q:实时语音识别(边说边出字)和离线识别有什么区别? 实时要求流式模型(如 RNN-T),每收到一小段音频就立刻输出,延迟优先;离线可以整段音频看完再识别,用双向注意力看全局上下文,准确率优先。架构选择上流式模型通常不能用完整的双向编码器。
语音转成文字之后,下一步就是理解文字的含义。下一篇进入 NLP 的世界:自然语言处理:从分词到大模型时代。
💬 评论
这套方案在哪一步卡住了?也欢迎补充你的实现方式或踩坑经验。