NeMo ASR+VAD 推理管线实战指南:基于 VAD 分段语音识别的完整流程与源码解析
【免费下载链接】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
本文以 NVIDIA NeMo 仓库(当前项目根目录/data/web/disk1/git_repo/GitHub_Trending/nem/Speech)中 examples/asr/asr_vad/README.md 为骨架,结合 speech_to_text_with_vad.py 及 VAD 工具库源码,系统讲解 ASR+VAD 联合推理管线的输入输出格式、命令参数、Frame-VAD / Segment-VAD 两种模式、基于 RTTM 的非语音段特征处理策略,以及每个关键参数在底层代码中的实际作用。读完本文,你将能直接上手运行"先语音活动检测、后语音识别"的完整推理流程,也能独立配置 VAD 后处理参数以获得理想的语音分段效果。
一、管线概览:ASR 与 VAD 如何协同工作
speech_to_text_with_vad.py提供了一条完整的ASR+VAD 推理管线,同时支持三种运行模式:
- ASR + VAD 联合推理:先用 VAD 模型检测语音/非语音区间,再仅对语音部分进行 ASR 转写(默认模式);
- 仅 ASR:跳过 VAD,直接对整段音频做语音识别;
- 仅 VAD:只做语音活动检测,输出语音分段结果,不进行转写。
从源码主函数 main() 可以看出,整条管线按顺序执行三个核心阶段:
prepare_inference_manifest(cfg):规范化输入清单(manifest 或音频目录);extract_audio_features(...):使用 VAD 模型的 preprocessor 提取并缓存音频特征(.pt 文件);- 若配置了
vad_model,执行run_vad_inference(...)生成语音分段(RTTM); - 若配置了
asr_model,执行run_asr_inference(...)基于分段结果完成转写并计算 WER/CER。
这三个阶段依次执行,中间产物(特征文件、VAD 预测、RTTM)都会被写入输出目录,因此重复运行同一任务时,如果中间结果已存在,程序会自动跳过已完成的阶段(详见下文"断点续跑"说明)。
二、输入格式:Manifest 与音频目录两种方式
管线支持两种输入形式,由InferenceConfig中的manifest_filepath与audio_dir两个字段控制(源码见 InferenceConfig):
| 输入方式 | 配置参数 | 说明 |
|---|---|---|
| Manifest 清单 | manifest_filepath=/PATH/TO/MANIFEST.json | 每行一个 JSON 字典的清单文件,字段要求严格 |
| 音频目录 | audio_dir=/PATH/TO/AUDIOS+audio_type=wav | 自动扫描目录下所有指定类型的音频文件,audio_type默认为wav |
2.1 Manifest 必备字段
Manifest 是 NeMo 生态通用的 JSON Lines 格式,每一行必须是一个合法的 Python 字典,其中["audio_filepath", "offset", "duration"]三个字段为必需项:
{"audio_filepath": "/path/to/audio_file1", "offset": 0, "duration": 10000} {"audio_filepath": "/path/to/audio_file2", "offset": 0, "duration": 10000}各字段含义:
audio_filepath:音频文件的绝对路径(或相对路径,程序会自动尝试基于 manifest 所在目录解析);offset:从音频的哪个时间点(秒)开始处理;duration:要处理的音频时长(秒)。注意示例中写的是10000,即处理从offset开始长达 10000 秒的音频(实际会被音频总长度截断),等效于处理整段音频。
2.2 提供文本标注以计算 WER
如果希望管线在转写完成后自动计算WER(词错误率),需要在 manifest 中额外提供text字段作为 ground truth:
{"audio_filepath": "/path/to/audio_file1", "offset": 0, "duration": 10000, "text": "hello world"} {"audio_filepath": "/path/to/audio_file2", "offset": 0, "duration": 10000, "text": "hello world"}当 manifest 中不存在text字段时,run_asr_inference会统计生成的字数/词数并打印;存在text时则调用word_error_rate同时计算并打印 WER 与 CER(源码见 run_asr_inference):
----------------------------------------- WER=0.0432, CER=0.0125 -----------------------------------------2.3 音频目录输入的内部处理
当audio_dir不为空且未提供manifest_filepath时,prepare_inference_manifest 会递归扫描目录下所有*.{audio_type}文件,为每个文件生成{"audio_filepath": ..., "duration": 1000000, "offset": 0}条目,并自动写入输出目录下的temp_manifest_input.json。同时该函数还会对 manifest 中的相对路径做归一化:若audio_filepath长度小于 255、不是绝对路径且相对 manifest 所在目录可找到文件,则自动拼接为绝对路径,并统一补上label="infer"、text="-"两个字段。
三、输出:VAD 预测、RTTM 分段与转写清单
输出写入output_dir(默认./outputs),主要内容包括:
| 产物 | 路径/命名 | 说明 |
|---|---|---|
| 音频特征 | outputs/features/*.pt | 每段音频提取的 log-mel 特征(torch tensor) |
| 帧级 VAD 预测 | outputs/vad_frame_pred/*.frame | 每行一个 speech 概率值,对应一个 VAD 帧 |
| 语音分段(RTTM) | outputs/vad_rttm-{postprocessing 参数拼接}/*.rttm | 每个语音段的起止时间与时长 |
| 转写结果清单 | outputs/{原清单名}-{ASR模型名}-{tag}.json | 每行在原条目基础上追加pred_text、feature_file、rttm_file |
| 中间清单 | temp_manifest_input.json、temp_manifest_input_feature.json、temp_manifest_{seg}_*.json | 各阶段传递的临时数据 |
其中 RTTM 分段目录名会把后处理参数拼进目录名(如vad_rttm-onset0.3offset0.3pad_onset0.2...),源码见 run_vad_inference,便于对比不同后处理参数的效果。
四、快速上手:运行 ASR+VAD 推理
以默认设置运行 ASR+VAD 联合推理,在examples/asr/asr_vad目录下执行:
python speech_to_text_with_vad.py \ manifest_filepath=/PATH/TO/MANIFEST.json \ vad_model=vad_multilingual_frame_marblenet \ asr_model=stt_en_conformer_ctc_large \ vad_config=../conf/vad/frame_vad_infer_postprocess.yaml参数说明:
manifest_filepath:输入清单路径(必填,除非使用audio_dir);vad_model:VAD 模型名。可传.nemo本地模型路径、.ckpt检查点路径,或 NGC 上的预训练模型名(如vad_multilingual_frame_marblenet)。init_frame_vad_model会按上述顺序依次尝试加载(见 vad_utils.py);asr_model:ASR 模型名,同样支持.nemo/.ckpt/ NGC 预训练名三种来源(见 init_asr_model);vad_config:VAD 后处理 YAML 配置文件路径(见下文第六节)。
提示:若使用音频目录输入,将
manifest_filepath替换为audio_dir=/PATH/TO/AUDIOS audio_type=wav即可;audio_type默认值就是wav。
4.1 仅使用 ASR(关闭 VAD)
设置vad_model=None且use_rttm=False,管线将跳过 VAD 阶段,直接对整段音频转写:
python speech_to_text_with_vad.py \ manifest_filepath=/PATH/TO/MANIFEST.json \ asr_model=stt_en_conformer_ctc_large \ vad_model=None \ use_rttm=False4.2 仅使用 VAD(不转写)
设置asr_model=None,同时指定vad_model与vad_config,管线只输出语音分段:
python speech_to_text_with_vad.py \ manifest_filepath=/PATH/TO/MANIFEST.json \ vad_model=vad_multilingual_frame_marblenet \ asr_model=None \ vad_config=../conf/vad/frame_vad_infer_postprocess.yaml4.3 开启性能剖析
设置profiling=True可启用 PyTorch Profiler,对 CPU/CUDA 各阶段耗时、显存占用进行记录,并在结束后打印按cuda_time_total排序的前 15 行统计表(源码见 main)。注意:profiling 会显著拖慢程序运行速度,仅建议在性能排查时临时开启。
五、基于 RTTM 的非语音段特征处理
VAD 阶段会把检测出的语音分段写入 RTTM 文件,ASR 阶段可以选择用这些分段信息"屏蔽"非语音区域的特征,避免模型对静音/噪声段产生无效输出。相关参数如下:
5.1 use_rttm 与 rttm_mode
use_rttm:True/False,是否启用 RTTM 特征处理,默认True;rttm_mode:RTTM 的使用方式,仅可为mask或drop:mask:用 RTTM 分段信息掩蔽非语音段的特征(把特征值替换为feat_mask_val),特征序列长度保持不变;drop:直接丢弃非语音段的特征,仅保留语音段特征送入 ASR,特征序列变短。
源码中这两个参数被透传给feature_to_text_dataset.get_bpe_dataset / get_char_dataset(见 feature_to_text_dataset.py 与 run_asr_inference),由数据集的use_rttm、rttm_mode、feat_mask_val、frame_unit_time_secs等字段控制实际的特征裁剪/掩蔽逻辑。
5.2 实践建议:drop 模式要留足 padding
文档与代码均建议:当rttm_mode='drop'时,应使用更大的pad_onset和pad_offset(在 VAD 配置的postprocessing中设置),以避免 VAD 边界误差导致语音特征被误删。因为 drop 模式是硬性裁剪,边界上的轻微误差会直接丢掉有效语音。
5.3 feat_mask_val:掩蔽值
设置feat_mask_val可指定掩蔽时使用的具体特征值。默认feat_mask_val=None,此时程序自动选择默认值:
normalize='post_norm'时使用-16.530(零 log-mel 谱特征值,即log(1e-7)量级的静音特征)——README 中写作 -16.530,脚本 docstring 中写作 -16.635,二者是同一概念的不同精度表述,实际以运行时打印的feat_mask_val为准;normalize='pre_norm'时使用0(与 SpecAugment 的掩蔽值一致,即特征归一化前的 0 值)。
5.4 normalize:掩蔽与归一化的先后顺序
normalize决定特征归一化与掩蔽的先后关系:
normalize='pre_norm':先归一化、后掩蔽;normalize='post_norm':先掩蔽、后归一化(默认值)。
配套参数normalize_type(默认per_feature)决定计算均值/方差的方式,normalize_audio_db(默认None)可设置为数值以在特征提取前对音频做 RMS DB 归一化,这两个参数同样会传入特征提取与 ASR 数据集的配置中。
六、Frame-VAD 与 Segment-VAD 两种模式
VAD 模型分为两类,由vad_type参数选择(默认frame),源码在 run_vad_inference 中据此分别调用init_frame_vad_model(加载EncDecFrameClassificationModel)或init_vad_model(加载EncDecClassificationModel)。
6.1 Frame-VAD(帧级 VAD)
python speech_to_text_with_vad.py \ manifest_filepath=/PATH/TO/MANIFEST.json \ vad_model=vad_multilingual_frame_marblenet \ asr_model=stt_en_conformer_ctc_large \ vad_config=../conf/vad/frame_vad_infer_postprocess.yaml- 对每个20ms音频帧输出一个 speech/non-speech 预测;
- 对应的 frame_vad_infer_postprocess.yaml 中,
shift_length_in_sec: 0.02(20ms 帧长)与window_length_in_sec: 0.0(帧级 VAD 必须为 0,无需滑窗上下文)是预训练 Frame-VAD 模型(如vad_multilingual_frame_marblenet)的固定约束,不可随意改动; - 帧级预测结果保存为
.frame文件,每行一个概率值(generate_vad_frame_pred中逐行写入,见 speech_to_text_with_vad.py)。
6.2 Segment-VAD(分段级 VAD)
python speech_to_text_with_vad.py \ manifest_filepath=/PATH/TO/MANIFEST.json \ vad_model=vad_multilingual_marblenet \ asr_model=stt_en_conformer_ctc_large \ vad_type='segment' \ vad_config=../conf/vad/vad_inference_postprocessing.yaml- 将音频切分为重叠的短分段,对每个分段独立做 VAD 分类,最后把各分段的预测拼接成完整语音分段表;
- 分段大小与步长由 VAD 配置中的
window_length_in_sec与shift_length_in_sec控制:对应的 vad_inference_postprocessing.yaml 中默认值分别为0.63 秒和0.08 秒(0.08s 步长用于更快的推理); - Segment-VAD 可配合重叠平滑(
smoothing设为median或mean,overlap=0.875)对重叠分段的预测做多数表决/平均,提升边界稳定性——该逻辑由generate_overlap_vad_seq实现(见 vad_utils.py)。注意:smoothing与overlap在 Frame-VAD 配置中已标记为 Deprecated,仅对 Segment-VAD 有意义。
七、VAD 后处理配置详解
两份配置文件的vad.parameters.postprocessing字段控制"帧级概率 → 语音分段"的转换,核心参数如下(Frame-VAD 与 Segment-VAD 配置中默认值略有差异):
| 参数 | Frame-VAD 默认值 | Segment-VAD 默认值 | 作用 |
|---|---|---|---|
onset | 0.3 | 0.5 | 语音开始判定阈值:概率超过该值认为进入语音 |
offset | 0.3 | 0.3 | 语音结束判定阈值:概率低于该值认为语音结束 |
pad_onset | 0.2 | 0.2 | 每个语音段前额外填充的时长(秒),防止切掉语音起始 |
pad_offset | 0.2 | 0.2 | 每个语音段后额外填充的时长(秒) |
min_duration_on | 0.2 | 0.5 | 小于该时长的语音段将被删除(短语音滤波) |
min_duration_off | 0.2 | 0.5 | 小于该时长的非语音间隙将被合并到语音段 |
filter_speech_first | True | True | 滤波执行顺序:先滤语音段还是先合并间隙 |
底层转换由generate_vad_segment_table调用binarization(阈值二值化)与filtering(短段滤波/间隙合并)完成(见 vad_utils.py 与generate_vad_segment_table_per_tensor),最终按 RTTM 格式写出:
SPEAKER <NA> 1 0.3200 0.5600 <NA> <NA> speech <NA> <NA>Frame-VAD 配置中另有两个与长音频处理相关的字段:
prepare_manifest.auto_split: true:自动按split_duration切分超长音频,避免 CUDA 显存溢出;prepare_manifest.split_duration: 400:单条最长秒数,若仍显存不足可调小。
八、断点续跑与临时数据
整个管线以"产物是否已存在"作为阶段跳过的依据,具备天然的断点续跑能力:
- 若
outputs/features/temp_manifest_input_feature.json已存在,特征提取阶段直接跳过(extract_audio_features); - 若
outputs/vad_frame_pred目录已存在,帧级预测阶段直接跳过; - 若
outputs/vad_rttm-{参数拼接}目录已存在,分段生成阶段直接跳过。
因此,若需调整后处理参数(如onset/pad_onset)或更换 ASR 解码方式,不必重跑特征提取与 VAD 预测——只需删除对应阶段的输出目录或修改配置后重跑,管线会复用已有中间产物。所有临时文件都落在output_dir(默认./outputs)中,不会污染输入数据。
九、进阶参数:InferenceConfig 全字段速查
除前述参数外,InferenceConfig数据类(speech_to_text_with_vad.py)还提供以下可直接在命令行覆盖的选项:
| 参数 | 默认值 | 说明 |
|---|---|---|
batch_size | 1 | ASR 批大小。注意特征提取与 VAD 仅支持单样本/批(内部强制 batch=1) |
num_workers | 8 | DataLoader 工作进程数 |
sample_rate | 16000 | 音频采样率 |
frame_unit_time_secs | 0.01 | 单帧时长(秒),须与 ASR 配置中的window_stride一致(通常 10ms) |
audio_type | wav | audio_dir模式下扫描的音频扩展名 |
output_dir | ./outputs | 输出目录,可覆盖 |
output_filename | 自动生成 | 转写结果清单文件名,默认按模型名+处理方式拼装 |
pred_name_postfix | None | 自定义输出文件名后缀 |
compute_langs | False | 置 True 时输出语言 ID 信息(需模型支持) |
ctc_decoding | CTCDecodingConfig() | CTC 解码策略配置 |
rnnt_decoding | RNNTDecodingConfig(fused_batch_size=-1) | RNNT 解码策略配置 |
解码策略的自动选择逻辑值得注意(见 run_asr_inference):
- 若传入额外参数
decoder_type(rnnt/ctc),按 Hybrid 模型处理,分别调用rnnt_decoder_predictions_tensor或ctc_decoder_predictions_tensor; - 否则若模型具有
joint属性(RNNT 模型),走 RNNT 解码; - 否则按 CTC 模型处理,并自动从
asr_model.decoder.vocabulary读取字符表。
十、运行前置条件
- 环境:需安装本项目 NeMo(
pip install nemo_toolkit[asr]或从仓库源码安装,参见 README.md),并保证 CUDA 环境可用(代码会自动回退到 CPU,但推理会明显变慢); - 模型下载:
vad_model/asr_model传 NGC 预训练模型名时,程序首次运行会自动下载模型权重,需保持网络连通;也可预先将模型导出为.nemo文件传入本地路径离线使用; - 音频格式:默认要求 16kHz 单声道音频,若采样率不一致需先重采样,或在 manifest 中按实际时长正确填写
offset/duration; - 运行位置:示例脚本与配置采用相对路径(
../conf/vad/...),建议在examples/asr/asr_vad目录下执行,或将vad_config改为绝对路径。
总结
speech_to_text_with_vad.py是 NeMo 中"VAD 前置过滤 + ASR 精准转写"的标准参考实现:一条命令即可在整段长音频上自动完成语音活动检测、语音分段(RTTM)、分段转写与 WER/CER 评估。理解 Frame-VAD 与 Segment-VAD 的差异、mask/drop两种 RTTM 处理策略,以及postprocessing各阈值对分段质量的影响,是把它应用到生产场景(电话录音转写、会议语音、长音频批处理)的关键。建议以本仓库的 asr_vad 示例、frame_vad_infer_postprocess.yaml 与 vad_inference_postprocessing.yaml 为起点,结合 vad_utils.py 中的二值化/滤波源码进行参数调试。
【免费下载链接】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),仅供参考