PAYATHON 2026

如何提升编程术语的多语言语音识别准确率?

技术老齐

结论

fine-tuning 通常不适合作为第一步,也不是唯一选择。可以先针对代码切换场景搭建完整的识别链路:

  1. 使用长连接 streaming,避免把语音切成缺少上下文的短片段。
  2. 为编程术语、缩写和产品名配置 vocabulary biasing、phrase hints 或 phrase list。
  3. 保留候选结果,再结合术语表、上下文和语言模型重新排序。
  4. 只对置信度较低且可能包含术语的时间区域进行局部重识别。
  5. 使用专门的术语错误率评估系统,再判断是否需要训练定制模型。

多语言课程的主要难点是 code-switching,也就是在一句话中频繁切换自然语言、英文术语和缩写。如果只是增加英文模型的调用次数,模型可能把母语中的相似发音识别成英文,产生更多错误。

不要把音频切得太短

短音频缺少上下文,模型很难判断一个音节究竟是普通词、英文术语还是字母缩写。静音、键盘声和句尾残音也更容易被补成并不存在的内容。

实时处理应优先使用持续的 streaming session,由 VAD 和语义停顿共同确定切片位置。如果必须自行切片,可以保留少量重叠区域:

chunk 1: [0.0s ---------------- 6.0s]
chunk 2:                  [5.2s ---------------- 11.2s]
chunk 3:                                     [10.4s --------]

这里的 0.8s 重叠只是示例,实际值需要根据语速、网络延迟和验证集调整。合并结果时,应比较重叠区域内的 token 或词序列,不能直接拼接字符串。

还需要区分两类边界:

  • VAD 用于判断说话是否暂停。
  • Endpointing 用于判断一句话是否可以提交。

遇到短暂停顿时,可以输出 provisional result,同时保留识别上下文。最终字幕应等到句子边界较稳定时再提交。

给识别器提供术语上下文

大多数商业 STT 服务都支持某种形式的 phrase hints、speech context、phrase list 或 custom vocabulary。不同服务、区域和 API 版本提供的名称与能力可能不同,具体应以所用接口的文档为准。

术语表除了标准拼写,还应记录可能的读法:

[
  {
    "canonical": "PostgreSQL",
    "spoken_forms": ["Postgres", "Postgre S Q L"]
  },
  {
    "canonical": "SQL",
    "spoken_forms": ["S Q L", "sequel"]
  },
  {
    "canonical": "C++",
    "spoken_forms": ["C plus plus"]
  },
  {
    "canonical": "kubectl",
    "spoken_forms": ["cube control", "cube C T L"]
  },
  {
    "canonical": "NumPy",
    "spoken_forms": ["num pie", "numpy"]
  }
]

术语表适合按课程动态加载。Python 课程可以优先加入 NumPy、pandas 和 PyTorch,数据库课程则加入 PostgreSQL、ACID 和 MVCC。一次加入大量与课程无关的英文词,会扩大搜索空间,也可能提高误识别率。

如果 API 支持设置权重,应使用验证集调节。权重太低可能不起作用,太高又可能让模型把普通词强行识别成术语。

不要直接按两个 streams 的 timestamps 合并

不同语言模型在 tokenization、停顿处理和时间戳定义上可能存在差异。即使处理同一段音频,两个模型给出的词边界也不会自然对齐。按绝对 timestamp 逐词选择高概率结果并不可靠。

可以采用下面的处理结构:

音频
  → 主识别器生成文本、时间区间和候选结果
  → 检测低置信度区间
  → 仅对疑似英文术语的区间运行辅助识别
  → 对齐候选序列
  → 根据术语表和上下文重排序

候选序列可以通过动态规划来对齐,例如使用基于编辑距离或 Dynamic Time Warping 的方法。时间戳只负责限制搜索范围,不能作为唯一的匹配条件。

如果服务能够返回 N-best hypotheses,应先尝试重排这些候选,避免重新识别整句话。正确术语有时已经出现在第二或第三候选中。

限制英文模型的替换范围

对每个低置信度片段都调用 English model,容易引入大量多余的英文词。低 confidence 只能说明模型不确定,无法证明对应位置一定是英文。

满足以下条件时,再启动英文重识别:

  • 当前片段的置信度较低。
  • 前后文涉及函数、框架、命令、文件名或 API。
  • 英文候选在拼写或发音上接近术语表中的词。
  • 新候选的得分比原候选有足够提升。
  • 替换后没有破坏整句的语法和时间边界。

下面是一段简化的后处理示例。代码中的阈值仅用于说明逻辑,实际使用前需要根据数据重新标定:

from dataclasses import dataclass
from difflib import SequenceMatcher

@dataclass
class Token:
    text: str
    confidence: float
    start: float
    end: float

GLOSSARY = {
    "postgres": "PostgreSQL",
    "postgre sql": "PostgreSQL",
    "c plus plus": "C++",
    "num pie": "NumPy",
    "cube control": "kubectl",
}

def similarity(a: str, b: str) -> float:
    return SequenceMatcher(None, a.lower(), b.lower()).ratio()

def find_glossary_candidate(text: str):
    best_key = None
    best_score = 0.0

    for spoken_form in GLOSSARY:
        score = similarity(text, spoken_form)
        if score > best_score:
            best_key = spoken_form
            best_score = score

    if best_key is None:
        return None, 0.0

    return GLOSSARY[best_key], best_score

def normalize_token(token: Token) -> str:
    # 示例阈值,不应直接用于生产环境
    if token.confidence >= 0.65:
        return token.text

    candidate, score = find_glossary_candidate(token.text)

    if candidate is not None and score >= 0.82:
        return candidate

    return token.text

生产系统还需要参考课程主题、相邻词、音素距离和 N-best 分数。字符串相似度本身无法覆盖所有发音变体。

在识别后增加受约束的术语纠错

识别完成后,可以使用语言模型或规则系统修正术语格式,例如:

see plus plus  → C++
pie torch      → PyTorch
node jay ess   → Node.js
get request    → GET request

纠错范围必须受术语表约束,同时保留原始识别文本、候选和分数。如果让通用生成模型自由改写整段字幕,它可能删除口误、补入原音频中不存在的代码,或擅自把讲师的原意改得更通顺。

纠错输入可以采用下面的结构:

{
  "course_topic": "Python data processing",
  "recognized_text": "我们先用 num pie 创建一个 array",
  "allowed_terms": ["NumPy", "array", "pandas"],
  "instruction": "只修正 allowed_terms 对应的识别错误,不改写其他内容"
}

即使设置了这些约束,也要验证每次替换是否能对应到原音频区间。

单独处理缩写和代码符号

API、SDK、SQL 既可能逐个字母读,也可能作为单词读。C#、C++、.NET 和 Node.js 还包含普通 ASR 不擅长输出的符号。

处理时可以分成两步:

  1. 先让 ASR 识别可发音的形式,例如 C sharp、C plus plus。
  2. 再由规范化模块根据术语表转换为 C#、C++。

这类转换不能全局执行。例如,普通英语中的 “sharp” 不一定表示 #。只有课程主题、相邻词和术语候选都支持时,才能进行转换。

代码内容还要区分“讲解文字”和“逐字符听写”。讲师开始拼写变量名时,系统需要进入 spelling mode,分别处理字母、数字、下划线和大小写提示。普通句子模型通常无法准确还原代码。

改善音频输入同样重要

比较模型之前,应先统一音频条件:

  • 避免反复进行有损编码。
  • 保持采样率、声道数和音量稳定。
  • 控制回声、背景音乐和键盘声。
  • streaming 过程中不要丢帧,也不要在每个小片段的首尾截断音素。
  • 如果音频中同时有讲师声和翻译播报声,应使用独立音轨或回声消除,防止合成语音被重新送入 ASR。

过强的音频预处理也可能损伤辅音和字母读音,因此需要用真实课程录音验证降噪强度。

什么时候才需要 fine-tuning

完成切片、术语偏置、候选重排和后处理后,如果仍有大量能够稳定复现的术语错误,再考虑 fine-tuning 或供应商提供的 Custom Speech 能力。

训练数据至少需要覆盖:

  • 实际授课语言和口音。
  • 自然出现的中英文切换位置。
  • 真实的麦克风、房间噪声和网络音频条件。
  • 字母缩写的不同读法。
  • 术语在完整句子中的读法,而非只有孤立单词。
  • 正确且一致的标注规范。

只用合成语音或术语单词表训练,可能在测试样本上得到明显改善,却无法反映真实课堂的效果。课程主题、框架名称和讲师口音都会变化,定制模型也需要持续维护。

对于托管 STT,“fine-tuning” 不一定意味着可以直接更新底层模型参数。部分服务只提供声学适配、文本数据适配、自定义词汇或定制端点,实际可用能力取决于产品和部署区域。

用术语指标评估,不要只看整体 WER

整体 Word Error Rate 可能掩盖术语识别问题。课程中普通句子的占比通常很高,即使所有框架名称都识别错误,WER 仍可能看起来可以接受。

除了 WER,还可以单独计算术语错误率:

[ TER = \frac{S_t + D_t + I_t}{N_t} ]

其中:

  • (S_t) 是术语替换数。
  • (D_t) 是术语删除数。
  • (I_t) 是错误插入的术语数。
  • (N_t) 是参考文本中的术语总数。

测试集应按照语言、讲师、课程主题、口音、噪声条件和 code-switching 密度分组。实时系统还要记录首个 provisional result 的延迟、最终结果延迟,以及字幕被反复改写的次数。

推荐的实施顺序

先用真实课程录音建立固定测试集,再按以下顺序优化:

  1. 统一音频输入并检查是否丢帧。
  2. 改用连续 streaming,调整 VAD 和 endpointing。
  3. 根据课程加载规模较小且内容相关的术语表。
  4. 保存 N-best、confidence 和时间区间。
  5. 对可能包含术语的低置信度区域进行局部重识别。
  6. 使用受约束的术语规范化和候选重排。
  7. 分别统计 WER、TER、错误插入率和实时延迟。
  8. 如果错误仍有稳定规律,再使用定制模型或 fine-tuning。

按照这套流程,更容易定位具体环节的问题,也方便在 Whisper、Google STT、Azure STT 和其他识别引擎之间迁移。

备注:内容仅供参考。