FunASR 训练与微调实战指南:从数据集构建、Smoke Test 到检查点恢复与模型导出
2026/9/13 22:16:32 网站建设 项目流程

FunASR 训练与微调实战指南:从数据集构建、Smoke Test 到检查点恢复与模型导出

【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR

FunASR 是一个开源语音识别工具包,覆盖推理、流式 ASR、VAD、标点、说话人分离等全链路,也内置了从零训练(Training from scratch)、全参微调(Fine-tuning)与 LoRA 适配的训练体系。本文以仓库内 docs/training.md 为主线,完整讲解在 FunASR 仓库中如何把已入库的 Recipe 脚本与数据集、Trainer 实现正确衔接起来:包括三种核心数据格式(Paraformer 音频-文本 JSONL、SenseVoice 富标签 JSONL、FunASRNano ChatML JSONL)的构建命令,工业级finetune.sh的参数逐项解析,小规模 Smoke Test 的最小可运行模板,非 DeepSpeed 路径下model.pt检查点的恢复语义,以及评估与导出环节的注意事项。读完本文,你将具备把自有语音数据接入 FunASR 训练管线并安全跑通"数据校验 → 冒烟训练 → 断点续训 → 评估导出"全流程的实操能力。

前置说明:文中所有命令均假定位于仓库根目录执行,且当前环境已安装本仓库及所选 Recipe 的依赖。文档中的脚本是面向你自有数据与已审核模型资产的模板,不代表仓库作者已完成全量训练、收敛性、GPU 显存或导出流程的实测。

一、先选对任务:推理、微调还是从零训练

在动手前,需要明确你的目标属于哪一类工作,因为它们的起点、改动范围与约束完全不同:

任务类型改动内容起点
推理(Inference)不改变任何权重,仅对已有音频解码推理教程
适配/微调(Adaptation)从预训练权重出发,微调选定参数、全部参数或受支持的 LoRA 适配器下方各 Recipe;开始前先记录一个留出集(held-out)基线
从零训练(Training from scratch)不加载预训练权重,直接按配置初始化指定架构;需自行准备 tokenizer、特征与训练计划AISHELL Paraformer Recipe 及其 配置

需要特别警惕两个容易踩坑的认知:

  1. 全参微调不等于从零训练。历史版本的 AISHELL 脚本内含作者本地路径,并且通过init_param注入初始权重;如果你想用它跑一次"从零训练",必须先替换其中的路径,并刻意配置初始化方式,而不是直接复用。
  2. industrial_data_pretraining下的脚本并不能复现原始工业预训练语料与流程。这些脚本只是可运行的模板,不构成对原始预训练过程的重现证据。

各模型家族的可用 Recipe 与关键边界如下表:

模型家族真实 Recipe 与配套材料重要边界
SenseVoicefinetune.sh、英文 README、持续微调指南富标签与 tokenizer 特殊 token 至关重要。脚本期望用户自行准备data/train_example.jsonldata/val_example.jsonl,它不会替你生成。
FunASRNanofinetune.sh、LoRA 脚本、微调指南默认脚本冻结音频编码器/适配器、仅解冻 LLM,并非全参 Recipe。纯 LoRA 还需同时设置llm_conf.use_lora=truelora_only=truellm_conf.freeze=true。训练前务必审计可训练参数,包括任何 CTC 组件。
Paraformerfinetune.sh、README、LoRA 指南工业 Recipe 显式选用AudioDataset/IndexDSJsonl。Paraformer 的 LoRA 是独立 Recipe,不是 Nano 那套适配器配置。
MOSS-Transcribe-DiarizeFunASR 适配器源码第三方 OpenMOSS 模型。该适配器的forward会抛出"仅推理"错误;MOSS 的训练不在本文讨论范围内。

此外,请注意许可证边界:FunASR 软件本体是 MIT 许可,但模型权重、上游代码、数据集与派生 checkpoint 可能采用不同许可。训练或再分发前,务必核查每份资产的条款与数据使用同意(data consent)要求。

二、准备数据集:质量优先,三种格式各就各位

无论选择哪个家族,数据准备都应遵循同一条底线:训练集、验证集、最终测试集必须分离,最好按说话人/会话维度切分而不只是按语句切分。逐条检查:重复 ID、音频与转写 ID 是否一一对应、文件是否可读、解码后的时长/采样率、转写文本规范化以及语言覆盖度。音频相对路径是相对进程工作目录解析的,而不是隐式相对 JSONL 文件所在目录;当可复现性或隐私重要时,优先使用稳定的本地绝对音频路径。

2.1 Paraformer:音频与文本 JSONL

Paraformer 家族使用的转换器是 scp2jsonl.py,它按 utterance ID 将wav.scp与文本文件连接起来。输入文件每一行的格式都是utterance_id value,其中转写文本可以包含空格。

以一条两秒录音为例,生成的结构如下:

{"key":"utt001","source":"data/audio/utt001.wav","source_len":200,"target":"hello world","target_len":2}

字段语义必须精确理解,否则后续长度过滤会失真:

  • keysourcetarget是字符串;source_lentarget_len是整数。
  • source_len的计算式为int(samples_at_16kHz / 160),即约 10ms 为单位的数值,而不是秒数。该逻辑在 scp2jsonl.py 的parse_context_length中实现:先以 16kHz 采样率读取音频,context_len = int(sample_num * 1000 / 16000 / 10)
  • target_len在文本含空格时按空白分词数统计,否则按字符数统计(见 scp2jsonl.py),它并不普遍等于 tokenizer 的 token 数。

从源码结构看,JSONL 生成后的数据流是:IndexDSJsonl索引读取器(index_ds.py)按配置的max/min_source_lengthmax/min_target_lengthmax_token_length对样本做过滤(index_ds.py),随后 datasets.py 中的AudioDataset(注册名即"AudioDataset")完成特征提取与 tokenization。也就是说,你写入的source_len/target_len会直接影响哪些样本被过滤掉,写错单位会导致样本被误杀或漏网。

准备好data/list/train_wav.scptrain_text.txtval_wav.scpval_text.txt后,从仓库根目录执行:

python -m funasr.datasets.audio_datasets.scp2jsonl \ ++scp_file_list='["data/list/train_wav.scp", "data/list/train_text.txt"]' \ ++data_type_list='["source", "target"]' \ ++jsonl_file_out=data/list/train.jsonl python -m funasr.datasets.audio_datasets.scp2jsonl \ ++scp_file_list='["data/list/val_wav.scp", "data/list/val_text.txt"]' \ ++data_type_list='["source", "target"]' \ ++jsonl_file_out=data/list/val.jsonl

两点重要提醒:

  1. 转换器会静默丢数据:音频缺失的行会被continue跳过(scp2jsonl.py),转写缺失时也可能产出不完整记录。因此必须对比输入/输出条数并剔除不完整行——进程正常退出并不代表数据完整。
  2. 只使用可信的 CLI 配置方式:这个历史转换器对字符串形式的列表存在eval回退(scp2jsonl.py),请通过++覆盖传入经过审查的参数,不要引入不受控的字符串输入。

2.2 SenseVoice:保留富标签

SenseVoiceCTCDataset路径时,需要在音频/文本 schema 中显式增加监督字段:

{"key":"utt001","source":"data/audio/utt001.wav","source_len":200,"target":"你好","target_len":2,"text_language":"<|zh|>","emo_target":"<|NEUTRAL|>","event_target":"<|Speech|>","with_or_wo_itn":"<|woitn|>"}

这些字符串必须是对应录音的真实标签,绝不能不加区分地全局套用占位值。从 SenseVoice CTC 数据集实现 看,缺失字段时会默认补成上述值,但默认值不等于经校验的真值。同时注意:更早的SenseVoiceDataset是另一套数据集实现,务必保持数据集类与所选模型配置一致。

若你手头没有对齐好的语言/情感/事件标注,sensevoice2jsonl.py 支持通过scp_file_listdata_type_list接收对齐的 source/target/language/emotion/event 文件;当语言、情感或事件标签缺失时,它会调用 SenseVoice 生成伪标签(这会下载/运行模型),其 ITN 标志则基于标点启发式。训练前务必人工复核标签与规范化结果。另外,不要凭空发明新的语言标签——那需要 tokenizer 与模型侧的协同修改,请先参阅持续微调指南。

2.3 FunASRNano:ChatML JSONL

Nano 家族在其提供的 Recipe 中不消费Paraformer 那种平面 schema,而是使用 ChatML 结构。真实样例见 train_example.jsonl 与 val_example.jsonl。以下结构示例中的长度字段必须针对真实数据重新计算:

{"messages":[{"role":"system","content":"You are a helpful assistant."},{"role":"user","content":"语音转写:<|startofspeech|>!data/audio/utt001.wav<|endofspeech|>"},{"role":"assistant","content":"你好"}],"speech_length":198,"text_length":1}

字段约定:

  • messages是 role/content 字典的列表;assistantcontent即目标转写。
  • Recipe 自带转换器 scp2jsonl.py 按顺序配对 SCP 行与转写行,要求 ID 一致,并计算speech_length = int((duration * 1000 - 25) // 10 + 1)(见 scp2jsonl.py)。
  • text_length取自Qwen/Qwen3-0.6B的 tokenization(scp2jsonl.py)。这意味着运行转换器可能拉取该 tokenizer;若 wav 路径是 URL,还会联网下载音频。行数不匹配时脚本只打 Warning(scp2jsonl.py),坏配对会被直接跳过——所以同样要检查输出条数与每一条报错。

使用你自己的输入/输出文件,而不是覆盖仓库自带的示例数据:

python examples/industrial_data_pretraining/fun_asr_nano/tools/scp2jsonl.py \ ++scp_file=data/list/train_wav.scp \ ++transcript_file=data/list/train_text.txt \ ++jsonl_file=data/list/nano_train.jsonl python examples/industrial_data_pretraining/fun_asr_nano/tools/scp2jsonl.py \ ++scp_file=data/list/val_wav.scp \ ++transcript_file=data/list/val_text.txt \ ++jsonl_file=data/list/nano_val.jsonl

三、先小规模验证,再启动大规模训练

3.1 启动前的三项检查

  1. 锁定环境:固定 checkout 与模型资产版本,记录依赖、tokenizer/frontend 配置、GPU 配置、数据哈希与随机种子。凡涉及trust_remote_code,先人工审查所有下载的 Python 代码与 requirements。
  2. 构建微小且互斥的冒烟集:从已校验的记录中切出data/list/train_smoke.jsonldata/list/val_smoke.jsonl真正加载它们的音频、真正 tokenize 它们的 target,确认长度过滤后 batch 非空、loss 有限、可训练参数名符合预期。
  3. 先跑一个短训练/验证/存盘点周期再放大。不要盲目运行历史 shell 脚本:它们内部写死了 GPU ID、输出路径和依赖工作目录的路径,通常不会透传你在命令行末尾追加的++覆盖参数

3.2 根目录相对路径的 Paraformer Smoke Test 模板

以下模板脱胎于工业 Recipe,适用于:兼容 GPU + 已安装训练依赖 + 两份已备好的 JSONL + 一个已审核的完整模型目录models/paraformer

CUDA_VISIBLE_DEVICES=0 torchrun --nnodes=1 --nproc_per_node=1 \ --master_addr=127.0.0.1 --master_port=29619 \ funasr/bin/train_ds.py \ ++model=./models/paraformer \ ++train_data_set_list=data/list/train_smoke.jsonl \ ++valid_data_set_list=data/list/val_smoke.jsonl \ ++dataset=AudioDataset ++dataset_conf.index_ds=IndexDSJsonl \ ++dataset_conf.data_split_num=1 ++dataset_conf.batch_sampler=BatchSampler \ ++dataset_conf.batch_type=token ++dataset_conf.batch_size=2000 \ ++dataset_conf.sort_size=16 ++dataset_conf.num_workers=0 \ ++train_conf.max_epoch=1 ++train_conf.log_interval=1 \ ++train_conf.resume=false ++train_conf.use_deepspeed=false \ ++train_conf.validate_interval=1 ++train_conf.save_checkpoint_interval=1 \ ++train_conf.keep_nbest_models=1 ++train_conf.avg_nbest_model=1 \ ++optim_conf.lr=0.0002 ++output_dir=./outputs/paraformer-smoke

各关键参数的含义与取值建议:

参数说明
++dataset=AudioDataset++dataset_conf.index_ds=IndexDSJsonl数据链路与工业 Recipe 保持一致;IndexDSJsonl在 index_ds.py 注册,负责 JSONL 解析与长度过滤
++dataset_conf.batch_type=token++dataset_conf.batch_size=2000按 token 数切 batch。2000只是短音频的起步值,不是显存保证;只有在确认单样本仍能放下的前提下才可调低
++dataset_conf.sort_size=16++dataset_conf.num_workers=0排序窗口与 worker 数;冒烟阶段设 0 便于串行排障
++train_conf.max_epoch=1只对小数据集才是"小的",输入数据集大时 1 个 epoch 依然不小
++train_conf.resume=false全新实验必须关闭续训(语义见第四节)
++train_conf.use_deepspeed=false非 DeepSpeed 路径,便于单卡冒烟
++train_conf.validate_interval=1++save_checkpoint_interval=1每个 epoch 都验证并落盘,冒烟期最大化反馈
++train_conf.keep_nbest_models=1++avg_nbest_model=1只保留/平均最优模型,控制冒烟产物规模
++optim_conf.lr=0.0002与工业 Recipe 一致的初始学习率

每个新实验都要使用全新的输出目录,并确保 rendezvous 端口可用。若跑 SenseVoice 或 Nano,请从各自家族的 Recipe/配置与 schema 出发,不要把该命令里的AudioDataset直接替换到别的家族上。另外注意:Nano 的脚本通过PATH查找funasr-train-ds,请确认它指向你预期的安装;SenseVoice 与 Paraformer 的脚本即使在use_deepspeed=false时仍含有 DeepSpeed 配置路径(参见 paraformer finetune.sh 与++train_conf.deepspeed_config=${deepspeed_config}),启用 DeepSpeed 前务必核对真实配置。完整分布式训练与资源规划需要另行验证。

四、检查点与断点续训:resume的精确语义

4.1 入口与恢复逻辑

训练入口是 funasr/bin/train_ds.py(Hydra 包装的main_hydra),它会调用 Trainer.resume_checkpoint。分布式模式由_resolve_distributed_config决定(train_ds.py):use_deepspeeduse_fsdp互斥,world_size > 1 且两者都关时自动回退为 DDP。

在非 DeepSpeed 路径下,++train_conf.resume=true只会恢复output_dir/model.pt这一固定文件,包含模型、优化器、调度器以及可用的 scaler/进度状态(trainer_ds.py)。要点:

  • 是布尔开关,不是任意 checkpoint 路径。想续跑哪个实验,就复用那个实验的output_dir
  • 若该文件不存在,Trainer 会打印"No checkpoint found ... does not resume status!"并继续训练(trainer_ds.py),而不是报错中断——所以必须在日志中确认恢复确实发生
  • 加载时还受excludes键过滤影响(trainer_ds.py),且会处理module.前缀差异;缺失的键会打印Miss key in ckpt

4.2 续跑与初始化权重的区别

  • 继续冒烟训练:重跑原命令,保持相同输出目录与配置,追加++train_conf.resume=true并把++train_conf.max_epoch=2。注意max_epoch 是累计总数,不是追加的额外 epoch 数
  • 加载初始权重++init_param=...只加载初始权重,不恢复优化器/调度器/进度状态。因此,进入一个新的适配阶段时,应使用全新输出目录 +resume=false,并显式指定选定的初始化 checkpoint。

4.3 保存产物形态

  • 非 DeepSpeed:保存model.ptmodel.pt.ep{epoch}model.pt.ep{epoch}.{step}model.pt.best依据验证排名产生。训练结束时入口会调用average_checkpointsavg_nbest_model个最佳模型做平均(train_ds.py)。
  • DeepSpeed:保存为 checkpoint 目录/标签形式,需要走 DeepSpeed 对应的恢复/转换路径。

不要把上述目录或残缺的 adaptor-only 状态当作普通独立权重。请保留配置、tokenizer/frontend 资产与基础模型出处(provenance),检查保存排除项与 LoRA 设置——一个 checkpoint 文件本身未必是可直接加载的模型目录。

五、评估与导出:用留出集说话

5.1 单文件健全性检查

加载一个备好的 Paraformer 模型目录与一个真实选中的 checkpoint,做一次单文件健全性检查:

from pathlib import Path from funasr import AutoModel checkpoint = Path("outputs/paraformer-smoke/model.pt") assert checkpoint.is_file(), checkpoint model = AutoModel( model="./models/paraformer", hub="ms", init_param=str(checkpoint), device="cpu", disable_update=True, ) print(model.generate(input="data/audio/heldout.wav"))

5.2 评估口径

随后用未触碰过的留出集解码,基线与适配后权重必须使用同一套文本规范化与 CER/WER 单位定义。报告时既要给聚合分数,也要给按领域/语言拆分的结果、保留任务(retained-task)回退情况、数据条数与 checkpoint 身份标识。务必记住:训练 loss 或 Trainer 的验证 accuracy 并不自动等于转写 CER/WER

Nano 家族可参考其 微调指南 中的解码/规范化/计分引用;decode.py 使用了 VAD 且带remote_code="./model.py",因此它假定在 Recipe 工作目录下运行。那个 legacy 本地类 可以替换内置 Nano 类,但不要假设它保留内置 LoRA 支持——评估前务必核对当前激活的类、适配器注入方式与已加载的键。

5.3 导出是独立的兼容性任务

导出不等于训练成功。以下都是入口点而非普适支持承诺

  • SenseVoice ONNX 示例
  • Paraformer 导出示例
  • 导出工具

注意 Paraformer 导出示例自身会选定一个 contextual checkpoint,使用时必须仔细核对并主动选择你的模型。模型专属导出钩子、tokenizer/frontend 资产、动态形状与运行时解码都需要逐一验证;部署前请在留出输入上对比导出运行时与 Python 结果。

六、全文关键结论速览

  • 先定位任务类型(推理 / 微调 / 从零训练),不同家族(SenseVoice、FunASRNano、Paraformer)各有独立 Recipe 与数据 schema 边界,不可混用。
  • 三种 JSONL 格式中,source_len以约 10ms 为单位、target_len是空白分词数或字符数;Nano 的speech_length另有专门公式,且转换器可能联网拉取 tokenizer 与音频。
  • 数据转换器会静默跳行,必须核对输入/输出条数;先构建微小 smoke 集跑通"非空 batch → 有限 loss → 预期可训练参数"再放大。
  • resume=true只恢复固定文件output_dir/model.pt(含 optimizer/scheduler/scaler/进度),是布尔开关而非任意路径;init_param仅加载权重。续跑时max_epoch为累计总数。
  • 训练 loss/验证 accuracy ≠ CER/WER,评估必须用统一规范化的留出集并报告领域级分数;导出(ONNX 等)是独立的兼容性任务,部署前必须对比运行时输出。
  • 所有脚本都是针对自有数据与已审核资产的模板;FunASR 软件为 MIT 许可,但权重、数据集与派生 checkpoint 的许可需单独核查。

(本文全部命令与代码均可直接在仓库根目录执行;所有引用文件路径均为仓库内真实相对路径,可继续深入阅读 docs/training.md 原文及其余配套文档。)

【免费下载链接】FunASROpen-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serving.项目地址: https://gitcode.com/GitHub_Trending/fun/FunASR

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询