NVIDIA NeMo Speech ASR N-gram 语言模型融合:KenLM 训练、Beam Search 与 WFST 解码实战指南
【免费下载链接】SpeechA scalable generative AI framework built for researchers and developers working on Large Language Models, Multimodal, and Speech AI (Automatic Speech Recognition and Text-to-Speech)项目地址: https://gitcode.com/GitHub_Trending/nem/Speech
N-gram 语言模型(LM)融合是 NeMo Speech 中提升 ASR 准确率、实现转录定制的经典手段:先在文本语料上训练 KenLM 模型,再让 beam search 解码器在搜索候选时将 LM 分数并入声学分数进行联合打分。本篇基于 NeMo Speech 仓库的官方文档 legacy_language_modeling_and_customization.rst 及配套脚本,系统讲解该方案的环境搭建、解码打分公式、CTC 与 Transducer(RNNT/HAT)模型的 beam search 评估流程、三种解码引擎(OpenSeq2Seq/Flashlight/PyCTCDecode)的配置差异、超参数网格搜索,以及面向 CTC 模型的 WFST 解码快速上手,帮助读者在真实项目中完成"训练 N-gram LM → 网格搜索调参 → 选定最优解码配置"的完整闭环。
一、方案原理:LM 分数如何参与解码打分
NeMo 的 beam search 解码器支持 KenLM 库训练的 N-gram LM,可以融合字符级(character-based)与 BPE 子词级(subword-based)两类模型。在 beam search 解码中,解码器把 N-gram LM 产生的分数并入自身打分公式:
final_score = acoustic_score + beam_alpha * lm_score + beam_beta * seq_length其中:
acoustic_score:声学编码器(acoustic encoder)预测的分数;lm_score:N-gram LM 估计的语言模型分数;beam_alpha:决定 N-gram LM 的权重。取值越大,解码越偏向语言模型、越弱化声学模型;beam_beta:序列长度惩罚项。取负值会惩罚长序列,鼓励解码器输出更短的预测;取正值则偏好更长的候选。
该公式是理解后续所有脚本参数(beam_alpha、beam_beta网格搜索)的核心:调参的本质就是在"声学证据"与"语言先验"之间寻找最佳平衡点。
二、环境安装:Beam Search 解码器与 KenLM 不是默认依赖
NeMo 默认安装不包含 beam search 解码器与 KenLM 库,使用前必须额外安装。仓库提供两种官方方式:
1. 使用安装脚本手动安装
安装脚本位于 install_beamsearch_decoders.sh,它会依次完成以下工作(可直接查看脚本确认步骤):
- 安装系统依赖
swig、liblzma-dev(Flashlight 解码器需要 liblzma); - 下载并编译 Boost 1.80.0(KenLM 依赖),并设置
BOOST_ROOT; - 克隆 NVIDIA/OpenSeq2Seq 仓库并切换到
ctc-decoders分支,将其decoders目录拷贝到 NeMo 根目录,脚本内会将 setup_os2s_decoders.py 复制为该目录的setup.py后执行setup.sh; - 编译安装 KenLM,
KENLM_MAX_ORDER=10表示支持的最大 n-gram 阶数(与setup_os2s_decoders.py中保持一致),安装后需导出环境变量KENLM_LIB与KENLM_ROOT; - 克隆 flashlight/text 并构建 wheel 安装(Flashlight 解码器)。
脚本支持传入 NeMo 根目录作为第一个位置参数(Docker 环境默认为/workspace/nemo),非 root 用户会自动加上sudo。
2. 构建包含全部依赖的 Docker 镜像
另一种更省事的方式是构建 Dockerfile.ngramtools 镜像。从源码可看到它以nvcr.io/nvidia/nemo:23.03为基础镜像,在 NeMo 仓库根目录执行构建:
DOCKER_BUILDKIT=1 docker build -t nemo:23.03.1 -f ./scripts/installers/Dockerfile.ngramtools .该镜像在运行install_beamsearch_decoders.sh的基础上,还会执行 install_opengrm.sh 额外安装 OpenGrm NGram 工具(用于后文的 N-gram LM 合并/插值)。
三、训练 N-gram LM:train_kenlm.py
融合解码前需要先用 KenLM 训练 N-gram LM。训练脚本为 train_kenlm.py,详细参数说明见配套文档 ngram_utils.rst。基本用法:
python train_kenlm.py nemo_model_file=<ASR模型的.nemo文件路径> \ train_paths=<训练文本或JSON manifest文件路径列表> \ kenlm_bin_path=<KenLM库bin目录路径> \ kenlm_model_file=<二进制KenLM模型保存路径> \ ngram_length=<N-gram模型的阶数> \ preserve_arpa=true关键点:
- 脚本会根据 ASR 模型类型自动检测并支持字符级与 BPE 子词级两种编码;
train_paths支持文本文件、JSON manifest(每行一条{"audio_filepath": ..., "text": "..."}的 JSONL)、目录等,脚本会从每行提取text字段构建训练文本;- 对 BPE 类模型,官方建议 N-gram 阶数取 6,更高阶可能需要重新编译 KenLM 才能支持(安装脚本默认
KENLM_MAX_ORDER=10); ngram_prune(默认[0])用于剪枝控制,preserve_arpa=true会保留中间 ARPA 文件——这一点与下文的 WFST 解码强相关,因为 WFST 解码需要 ARPA 格式 LM。
此外,ngram_merge.py 可以按alpha * ngram_a + beta * ngram_b的权重把两个 ARPA N-gram LM 插值合并并生成 KenLM 二进制文件,适用于"通用 LM + 领域 LM"混合等定制场景(该功能依赖 OpenGrm,同样可用Dockerfile.ngramtools镜像)。
四、CTC 模型评估:eval_beamsearch_ngram_ctc.py 完整实操
评估 CTC 模型在 beam search + N-gram LM 下的表现,使用 eval_beamsearch_ngram_ctc.py。脚本参数较多,官方建议先用python eval_beamsearch_ngram_ctc.py --help查看全部参数。基本调用命令:
python eval_beamsearch_ngram_ctc.py nemo_model_file=<ASR模型的.nemo文件路径> \ input_manifest=<评估JSON manifest文件路径> \ kenlm_model_file=<KenLM二进制模型路径> \ beam_width=[<beam宽度列表,逗号分隔>] \ beam_alpha=[<beam alpha列表,逗号分隔>] \ beam_beta=[<beam beta列表,逗号分隔>] \ preds_output_folder=<可选,保存预测结果的目录> \ probs_cache_file=null \ decoding_mode=beamsearch_ngram \ decoding_strategy="<beam、pyctcdecode 或 flashlight>"4.1 三种解码模式
通过--decoding_mode切换三种评估模式:
| 模式 | 行为 |
|---|---|
greedy | 只做贪心解码,不做 beam search |
beamsearch | 做 beam search 解码,但不使用 N-gram LM(等效于把 LM 权重beam_alpha置零) |
beamsearch_ngram | 使用 N-gram LM 的 beam search 解码(默认) |
在beamsearch模式下,结果以 WER(词错误率)和 CER(字符错误率)报告;同时脚本还会额外报告在全部候选中挑选最优假设时的 best WER/CER(源码日志中称为 "Oracle WER/CER in candidates with perfect LM",对应beam_search_eval()里对 n-best 候选逐一计算编辑距离取最小值的逻辑),可作为预测候选集质量的指示器。
结合源码(main()函数,eval_beamsearch_ngram_ctc.py)可以看到实际执行流程:
- 若
nemo_model_file以.nemo结尾,用ASRModel.restore_from加载本地模型;否则回退到ASRModel.from_pretrained按预训练模型名加载——即该参数可以直接填预训练模型名; - 先跑一遍贪心解码并打印
Greedy WER/CER作为基线; - 只有
beamsearch_ngram模式才要求kenlm_model_file存在,否则报FileNotFoundError; - 对
beam_width、beam_alpha、beam_beta三个参数用 sklearn 的ParameterGrid做笛卡尔积网格搜索,逐组调用beam_search_eval(),最终分别输出使 WER 与 CER 最小的最优超参数组合; - 若指定
preds_output_folder,每组超参数的 n-best 预测会写入形如preds_out_width{w}_alpha{a}_beta{b}.tsv的文件(每行:预测文本 \t 分数)。
注意源码中配置字段名为hyps_cache_file(用 msgpack 缓存 N-best 假设结果,缓存条目数必须与 manifest 行数一致,否则提示删除缓存文件重算),而文档示例中写作probs_cache_file,二者指同一类缓存机制,实际字段名以--help输出为准。
4.2 关键参数表
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
nemo_model_file | str | 必填 | ASR 模型.nemo文件路径(用于提取分词器) |
input_manifest | str | 必填 | 评估集 manifest 路径,可为 JSON manifest |
kenlm_model_file | str | 必填 | KenLM 二进制模型文件路径 |
preds_output_folder | str | None | 可选的预测结果输出目录 |
probs_cache_file | str | None | 模型输出(N-best 假设)的缓存文件 |
acoustic_batch_size | int | 16 | 计算 log 概率的 batch size |
use_amp | bool | False | 可用时使用 AMP 计算 log 概率 |
device | str | cuda | 加载模型计算 log 概率的设备,可取cpu、cuda、cuda:0、cuda:1等 |
decoding_mode | str | beamsearch_ngram | 解码方案 |
beam_width | float/int | 必填(源码默认[128]) | beam search 的宽度或宽度列表 |
beam_alpha | float | 必填(源码默认[1.0]) | beam search 的 alpha 参数或列表 |
beam_beta | float | 必填(源码默认[0.0]) | beam search 的 beta 参数或列表 |
beam_batch_size | int | 128 | beam search 解码的 batch size,更大略快但更耗内存 |
decoding_strategy | str | beam | 解码策略引擎,见下文三种引擎 |
decoding | DictConfig | BeamCTCInferConfig | beam search 各库的子配置,见--help |
text_processing.do_lowercase | bool | False | 是否将文本统一小写 |
text_processing.punctuation_marks | str | "" | 需要处理的标点字符串,示例".\,?" |
text_processing.rm_punctuation | bool | False | 是否从文本中移除标点 |
text_processing.separate_punctuation | bool | True | 是否用空格把标点与前面的词分开 |
beam_width决定解码器保留的 top 候选数量:宽度越大通常越准但越慢。文档特别提示:脚本内嵌了完整的 CTC beam 解码子配置,因此可以通过decoding子配置向flashlight、pyctcdecode等不同 beam search 库透传各自的参数。
五、三种 Beam Search 解码引擎详解
NeMo ASR 的 CTC 模型支持多种 beam search 引擎,默认引擎是beam(OpenSeq2Seq 解码库)。
5.1 OpenSeq2Seq(beam)
基于 CPU 的 beam search 引擎,效率较高,支持字符级与子词级模型,需要提供一个字符/子词级的 KenLM 模型。其配置即上文参数表中的decoding子配置(BeamCTCInferConfig,源码默认beam_size=128)。
5.2 Flashlight(flashlight)
Flashlight 是 C++ 实现的 ASR 解码库,支持 CPU 与 CUDA,效率较高,支持字符级与子词级模型,但要求提供ARPA 格式的 KenLM 文件(可用train_kenlm.py的preserve_arpa=true得到)。它支持词表内解码(lexicon-based)、无词表解码(lexicon-free)、beam 剪枝阈值等高级特性。
源码/文档中给出的配置数据类:
@dataclass class FlashlightConfig: lexicon_path: Optional[str] = None boost_path: Optional[str] = None beam_size_token: int = 16 beam_threshold: float = 20.0 unk_weight: float = -math.inf sil_weight: float = 0.0使用示例:
# Lexicon-based decoding python eval_beamsearch_ngram_ctc.py ... \ decoding_strategy="flashlight" \ decoding.beam.flashlight_cfg.lexicon_path='/path/to/lexicon.lexicon' \ decoding.beam.flashlight_cfg.beam_size_token = 32 \ decoding.beam.flashlight_cfg.beam_threshold = 25.0 # Lexicon-free decoding python eval_beamsearch_ngram_ctc.py ... \ decoding_strategy="flashlight" \ decoding.beam.flashlight_cfg.beam_size_token = 32 \ decoding.beam.flashlight_cfg.beam_threshold = 25.05.3 PyCTCDecode(pyctcdecode)
PyCTCDecode 是纯 Python 的 ASR 解码库,基于 CPU,效率尚可,支持字符级与子词级模型,需要字符/子词级的 KenLM ARPA 或 BINARY 模型。它提供 word boosting(热词加权)等高级特性,常用于转录定制场景。
配置数据类:
@dataclass class PyCTCDecodeConfig: beam_prune_logp: float = -10.0 token_min_logp: float = -5.0 prune_history: bool = False hotwords: Optional[List[str]] = None hotword_weight: float = 10.0使用示例(含热词加权):
# PyCTCDecoding python eval_beamsearch_ngram_ctc.py ... \ decoding_strategy="pyctcdecode" \ decoding.beam.pyctcdecode_cfg.beam_prune_logp = -10. \ decoding.beam.pyctcdecode_cfg.token_min_logp = -5. \ decoding.beam.pyctcdecode_cfg.hotwords=[<热词字符串列表>] \ decoding.beam.pyctcdecode_cfg.hotword_weight=10.0热词(hotwords)与加权机制的完整说明可参考仓库内同级文档 word_boosting.rst。
六、超参数网格搜索
beam search + N-gram LM 的核心超参数是beam_width、beam_alpha、beam_beta。模型精度主要依赖后两者的取值。每个参数既可以给单个值,也可以给一个列表;脚本会对三者所有组合(笛卡尔积)各跑一次 beam search 解码。例如:
python eval_beamsearch_ngram_ctc.py ... \ beam_width=[64,128] \ beam_alpha=[1.0] \ beam_beta=[1.0,0.5]该组合会产生 2 × 1 × 2 = 4 次 beam search 解码。脚本会遍历全部组合并分别输出使 WER 最小、使 CER 最小的最优超参数组合(Best WER Candidate/Best CER Candidate日志行),最终选参可直接采用该结果。
七、Transducer 模型(RNNT 与 HAT)的 Beam Search N-gram 解码
对 RNNT/HAT 结构的 Transducer 模型,NeMo 提供了平行的评估脚本 eval_beamsearch_ngram_transducer.py:
python eval_beamsearch_ngram_transducer.py nemo_model_file=<ASR模型的.nemo文件路径> \ input_manifest=<评估JSON manifest文件路径> \ kenlm_model_file=<KenLM二进制模型路径> \ beam_width=[<beam宽度列表,逗号分隔>] \ beam_alpha=[<beam alpha列表,逗号分隔>] \ preds_output_folder=<可选,保存预测结果的目录> \ probs_cache_file=null \ decoding_strategy=<greedy_batch 或 maes 解码> \ maes_prefix_alpha=[<maes prefix alpha列表,逗号分隔>] \ maes_expansion_gamma=[<maes expansion gamma列表,逗号分隔>] \ hat_subtract_ilm=<HAT模型:是否减去内嵌LM(True/False)> \ hat_ilm_weight=<HAT模型:内嵌LM权重列表,逗号分隔>结合源码可确认的关键默认值与行为:
decoding_strategy默认greedy_batch,可选["greedy_batch", "beam", "tsd", "alsd", "maes"];maes_prefix_alpha默认[2]、maes_expansion_gamma默认[2.3];acoustic_batch_size与beam_batch_size均默认为 128,同样支持device(默认cuda)、use_amp控制声学 log 概率计算;- 与 CTC 脚本相同的网格搜索机制:
beam_width×beam_alpha(或 MAES 两个参数)组合遍历; - HAT 模型特有参数:
hat_subtract_ilm(默认False)控制是否减去内嵌 LM(internal LM),hat_ilm_weight(默认[0.0])为其权重列表——从源码结构看,这是为了在外部 N-gram LM 与 HAT 架构自带 LM 之间做补偿/重加权。
八、WFST CTC 解码
加权有限状态转换器(WFST)是带输入/输出符号和半环上权重的有限状态机,可以充当 N-gram LM,用于一类特殊的 LM 强制 beam search,即 WFST 解码。严格地说,WFST 解码更接近带 LM 的贪心 N 层搜索(greedy N-depth search),因此渐近复杂度不如传统 beam search,但速度更快。
注意:当前 NeMo 的 WFST 解码仅支持 CTC 模型与基于词(word-based)的 LM。
运行前提:提供一个 NeMo ASR 模型,加一个 ARPA LM 或现成的 WFST LM(进阶)。ARPA LM 可直接用 KenLM 从源文本构建:
<kenlm_bin_path>/lmplz -o <ngram_length> --arpa <out_arpa_path> --prune <ngram_prune>评估脚本为 eval_wfst_decoding_ctc.py(从源码注释看,该脚本支持 BPE 级编码并自动检测模型类型)。参数很多,建议先python eval_wfst_decoding_ctc.py --help查看。基本用法:
python eval_wfst_decoding_ctc.py nemo_model_file=<ASR模型的.nemo文件路径> \ input_manifest=<评估JSON manifest文件路径> \ arpa_model_file=<ARPA LM模型路径> \ decoding_wfst_file=<解码WFST文件路径> \ beam_width=[<beam宽度列表,逗号分隔>] \ lm_weight=[<LM权重乘数列表,逗号分隔>] \ open_vocabulary_decoding=<是否为WFST解码使用开放词汇模式> \ decoding_mode=<解码模式,影响输出,通常填 "nbest"> \ decoding_search_type=<WFST解码库,通常填 "riva"> \ preds_output_folder=<可选,保存预测结果的目录> \ probs_cache_file=null由于 WFST 解码是 LM 强制的(搜索沿着最宽(WIDEST)的图进行),只有被 WFST 接受的词序列才能出现在解码结果中。要突破这一限制,可以传open_vocabulary_decoding=true(实验性功能)。源码中对应的默认值为:beam_width=[10]、lm_weight=[1.0]、acoustic_batch_size=16、beam_batch_size=512、decoding_mode="nbest"、decoding_search_type="riva"(解码后端)。
8.1 快速上手示例
以下示例从 OpenSLR 下载一个 3-gram ARPA LM(并转小写),用stt_en_conformer_ctc_small_ls模型在 LibriSpeechtest_other上按lm_weight网格评估:
wget -O - https://www.openslr.org/resources/11/3-gram.pruned.1e-7.arpa.gz | \ gunzip -c | tr '[:upper:]' '[:lower:]' > 3-gram.pruned.1e-7.arpa && \ python eval_wfst_decoding_ctc.py nemo_model_file="stt_en_conformer_ctc_small_ls" \ input_manifest="<data_dir>/Librispeech/test_other.json" \ arpa_model_file="3-gram.pruned.1e-7.arpa" \ decoding_wfst_file="3-gram.pruned.1e-7.fst" \ beam_width=[8] \ lm_weight=[0.5,0.6,0.7,0.8,0.9]注意:构建解码 WFST 是一个耗时过程。即使你手上还没有现成的 WFST 文件,也建议把decoding_wfst_file填上——这样首次运行构建的解码 WFST 会被缓冲(缓存)到该路径,下次运行无需重建。
九、小结与延伸阅读
本文覆盖了 NeMo Speech 中"legacy"语言模型定制路径的完整工具链:KenLM 训练(train_kenlm.py)、LM 合并插值(ngram_merge.py)、CTC 模型 beam search 评估(eval_beamsearch_ngram_ctc.py,含beam/flashlight/pyctcdecode三种引擎与beam_alpha/beam_beta网格搜索)、Transducer 模型评估(eval_beamsearch_ngram_transducer.py,含 MAES 搜索与 HAT 内嵌 LM 处理)以及 WFST 解码(eval_wfst_decoding_ctc.py,CTC + 词级 LM 限制)。选择建议:追求简单可靠的 CPU 方案用默认beam(OpenSeq2Seq);需要词表/词表内解码或 GPU 加速用flashlight(ARPA 格式);需要热词加权定制转录用pyctcdecode;需要词级 LM 强制搜索且接受其 CTC 限定则用 WFST 解码。
相关仓库资源可继续深入:
- N-gram 训练与合并脚本详细参数:ngram_utils.rst;
- 热词加权定制:word_boosting.rst;
- 脚本目录:scripts/asr_language_modeling/ngram_lm/;
- 神经重打分(neural rescorer)等其他 LM 融合方案见同级文档 neural_rescoring.rst 与 ngpulm_language_modeling_and_customization.rst;
- 离线 ASR 推理相关教程可参考 Offline_ASR_with_VAD_for_CTC_models.ipynb。
【免费下载链接】SpeechA scalable generative AI framework built for researchers and developers working on Large Language Models, Multimodal, and Speech AI (Automatic Speech Recognition and Text-to-Speech)项目地址: https://gitcode.com/GitHub_Trending/nem/Speech
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考