Files
FunASR/docs/training_zh.md

149 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 训练与适配
[English](training.md) | [文档](tutorial/README_zh.md) | [模型注册](model_registration_zh.md) | [模型选择](model_selection.md)
本指南把仓库中的训练脚本与数据集、训练器实现对应起来。命令默认从仓库根目录执行,环境需安装当前 checkout 及所选 recipe 的依赖。命令是针对自有数据和已审查模型资产的模板,不表示已验证完整训练、收敛、显存需求或导出。
## 先确定任务
| 任务 | 改变什么 | 入口 |
| --- | --- | --- |
| 推理 | 不修改权重,识别已有音频 | [推理教程](tutorial/README_zh.md) |
| 适配 | 从预训练权重开始,微调部分参数、全部参数或受支持的 LoRA 适配器 | 下表中的 recipe;先记录独立留出集基线 |
| 从头训练 | 不加载预训练权重,准备架构配置、分词器、特征及训练计划 | [AISHELL Paraformer 脚本](../examples/aishell/paraformer/demo_train_or_finetune.sh)及其[配置](../examples/aishell/paraformer/conf/paraformer_conformer_12e_6d_2048_256.yaml) |
全参数微调不等于从头训练。历史 AISHELL 脚本包含作者本机路径,且传入了 `init_param`;必须替换路径并明确配置初始化方式,才能用于从头训练。目录名 `industrial_data_pretraining` 不表示公开脚本可复现原始工业预训练数据和流程。
| 模型系列 | 实际 recipe 与说明 | 使用边界 |
| --- | --- | --- |
| SenseVoice | [finetune.sh](../examples/industrial_data_pretraining/sense_voice/finetune.sh)、[中文说明](../examples/industrial_data_pretraining/sense_voice/README_zh.md)、[持续适配](../examples/industrial_data_pretraining/sense_voice/CONTINUAL_FINETUNING_zh.md) | 需要正确的富标签与特殊 token。脚本使用用户准备的 `data/train_example.jsonl`、`data/val_example.jsonl`,不会自动创建这些文件。 |
| FunASRNano | [finetune.sh](../examples/industrial_data_pretraining/fun_asr_nano/finetune.sh)、[LoRA 脚本](../examples/industrial_data_pretraining/fun_asr_nano/lora_finetune.sh)、[微调指南](../examples/industrial_data_pretraining/fun_asr_nano/docs/finetune_zh.md) | 默认冻结音频编码器和适配模块、解冻 LLM,并非全参数方案。纯 LoRA 另设 `llm_conf.use_lora=true`、`lora_only=true`、`llm_conf.freeze=true`。应检查实际可训练参数,包括可能存在的 CTC 组件。 |
| Paraformer | [finetune.sh](../examples/industrial_data_pretraining/paraformer/finetune.sh)、[中文说明](../examples/industrial_data_pretraining/paraformer/README_zh.md)、[LoRA 指南](../examples/industrial_data_pretraining/paraformer/README_LoRA_zh.md) | 工业数据微调脚本显式使用 `AudioDataset` / `IndexDSJsonl`。Paraformer LoRA 是独立 recipe,不能套用 Nano 的适配器配置。 |
| MOSS-Transcribe-Diarize | [FunASR 适配器源码](../funasr/models/moss_transcribe_diarize/model.py) | 属于第三方 OpenMOSS 模型。该适配器的 `forward` 明确抛出仅支持推理的异常;本指南不覆盖 MOSS 训练。 |
FunASR 软件采用 MIT 许可证;模型权重、上游代码、数据集和派生 checkpoint 可能采用不同条款。训练、分发前请检查各项许可和数据授权。
## 准备数据
训练集、验证集和最终测试集应分开,尽量按说话人、录音会话划分,而非只按句子划分。检查重复 ID、音频与文本 ID 对齐、文件可读性、解码后的时长与采样率、文本规整方式及语言覆盖。音频相对路径按进程工作目录解析,不会自动按 JSONL 所在目录解析。需要可复现或隐私保护时优先使用稳定的本地音频路径。
### Paraformer:音频与文本 JSONL
[转换器](../funasr/datasets/audio_datasets/scp2jsonl.py)按 utterance ID 合并 SCP 和文本;输入每行是 `utterance_id value`,文本可以包含空格。以下为两秒音频的结构示例:
```json
{"key":"utt001","source":"data/audio/utt001.wav","source_len":200,"target":"hello world","target_len":2}
```
`key`、`source`、`target` 是字符串,长度字段是整数。`source_len` 为 `int(16kHz采样点数 / 160)`,约为 10 ms 单位,不是秒。转换器对有空格的文本按空格统计词数,否则统计字符数,得到 `target_len`;它并不统一等于分词器 token 数。[索引读取器](../funasr/datasets/audio_datasets/index_ds.py)按配置的长度限制过滤数据,[数据集](../funasr/datasets/audio_datasets/datasets.py)再执行特征提取和分词。
准备 `data/list/train_wav.scp`、`train_text.txt`、`val_wav.scp`、`val_text.txt`,从仓库根目录执行:
```bash
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
```
转换器可能跳过缺失音频,或在缺少文本时输出不完整记录。必须比较输入输出数量并拒绝不完整行,不能只看进程退出状态。CLI 配置应来自可信来源:该历史转换器对字符串列表存在 `eval` 回退分支。
### SenseVoice:保留富标签
使用 `SenseVoiceCTCDataset` 时,在音频文本结构上增加明确的监督字段:
```json
{"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|>"}
```
标签必须对应实际录音,不能不加区分地填入示例值。[CTC 数据集源码](../funasr/datasets/sense_voice_datasets/datasets.py)在缺字段时使用上例默认值,但默认值不是经过验证的标注。旧版 `SenseVoiceDataset` 是另一种实现,应保持数据集类与所选模型配置一致。
[sensevoice2jsonl.py](../funasr/datasets/audio_datasets/sensevoice2jsonl.py)通过 `scp_file_list`、`data_type_list` 接收对齐的音频、文本、语言、情感、事件文件。如果缺少语言、情感或事件标签,它会调用 SenseVoice 生成伪标签,可能下载并运行模型;ITN 标志由标点启发式生成。训练前需审核标签与规整规则。增加新语言标签不是改一个字符串,需要分词器与模型协同修改,参见[持续微调指南](../examples/industrial_data_pretraining/sense_voice/CONTINUAL_FINETUNING_zh.md)。
### FunASRNano:ChatML JSONL
Nano 提供的 recipe 不使用普通 Paraformer 数据结构。实际文件见[训练示例](../examples/industrial_data_pretraining/fun_asr_nano/data/train_example.jsonl)和[验证示例](../examples/industrial_data_pretraining/fun_asr_nano/data/val_example.jsonl)。下面仅展示结构,真实数据必须重新计算长度:
```json
{"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 字典列表;assistant 内容为参考转写。[recipe 转换器](../examples/industrial_data_pretraining/fun_asr_nano/tools/scp2jsonl.py)按行配对 SCP 和文本并要求 ID 一致,计算 `speech_length = int((duration * 1000 - 25) // 10 + 1)`,用 `Qwen/Qwen3-0.6B` 分词得到 `text_length`。可能下载分词器或 URL 音频。行数不一致只会产生警告,错误样本可能被省略,必须核对输出数量和全部错误。
使用自己的输入输出文件,不要覆盖仓库示例:
```bash
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
```
## 先做小规模验证
1. 固定 checkout 与模型资产,记录依赖、分词器与前端配置、GPU 配置、数据哈希和随机种子。启用 `trust_remote_code` 前审核下载的 Python 与 requirements。
2. 用已验证样本准备小而互不重叠的 `data/list/train_smoke.jsonl`、`data/list/val_smoke.jsonl`,实际加载音频、分词,确认长度过滤后仍有非空 batch、loss 有限、可训练参数符合预期。
3. 扩大规模前先完成短训练、验证和 checkpoint 保存流程。不要直接运行历史 shell 脚本:其内部设置 GPU、输出目录和依赖工作目录的路径,通常不会透传附加在脚本末尾的 CLI 参数。
以下是从工业数据 recipe 提炼的 Paraformer 小规模验证模板,路径以仓库根目录为基准。需要兼容 GPU、已安装训练依赖、上述两个 JSONL 文件,以及位于 `models/paraformer` 的完整且已审查模型目录。`2000` 是用于短音频的起始 token 预算,不是显存保证;调整时仍须确保单条样本能容纳。只有数据集足够小,一个 epoch 才是小规模运行。
```bash
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
```
每次新实验使用新输出目录,并选用空闲 rendezvous 端口。SenseVoice、Nano 应从各自 recipe、模型配置和数据结构出发,不能只替换上例的模型名而保留 `AudioDataset`。Nano 脚本通过 `PATH` 找到 `funasr-train-ds`,请确认它属于预期安装。SenseVoice、Paraformer 脚本在 `use_deepspeed=false` 时也传入 DeepSpeed 配置路径;启用前必须确认真实配置存在。完整分布式训练和资源需求仍需单独验证。
## Checkpoint 与恢复
[训练入口](../funasr/bin/train_ds.py)调用 [Trainer.resume_checkpoint](../funasr/train_utils/trainer_ds.py)。非 DeepSpeed 分支中,`++train_conf.resume=true` 从 `output_dir/model.pt` 恢复模型、优化器、调度器及存在的 scaler/训练进度状态。这是布尔开关,不是任意 checkpoint 路径。文件不存在时训练器会提示未恢复并继续,必须检查日志确认实际恢复成功。
继续上面的 smoke run 时,保持输出目录和配置,改为 `++train_conf.resume=true`、`++train_conf.max_epoch=2` 后重新执行。epoch 上限是总轮数,不是新增轮数。`++init_param=...` 仅用于初始化权重,不恢复优化器和进度。新适配阶段应使用新输出目录、`resume=false` 和明确选定的初始化权重。
非 DeepSpeed 保存包括 `model.pt`、`model.pt.ep{epoch}` 或 `model.pt.ep{epoch}.{step}`,`model.pt.best` 取决于验证排序。DeepSpeed 使用目录/tag,需要对应恢复或转换流程。不要把这些目录、不完整状态或仅适配器状态当成普通独立权重。保留配置、分词器、前端资产和基座模型来源,检查保存排除项与 LoRA 配置。单个 checkpoint 文件不一定是可加载的模型目录。
## 评估与导出
使用准备好的 Paraformer 模型目录及真实 checkpoint,先检查单条音频:
```python
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"))
```
随后在未参与训练或选模的留出集上解码,基线与适配模型使用一致的规整方式及 CER/WER 单位定义。报告各领域、各语言和整体结果、原任务退化、样本数量与 checkpoint 身份。训练 loss 或训练器验证 accuracy 不自动等于转写 CER/WER。[Nano 微调指南](../examples/industrial_data_pretraining/fun_asr_nano/docs/finetune_zh.md)保留了解码、规整、评分入口;[decode.py](../examples/industrial_data_pretraining/fun_asr_nano/decode.py)使用 VAD 和 `remote_code="./model.py"`,需要 recipe 工作目录。该[历史本地类](../examples/industrial_data_pretraining/fun_asr_nano/model.py)可能覆盖[内置 Nano 类](../funasr/models/fun_asr_nano/model.py),不能假定它保留内置 LoRA 功能;评估前确认实际类、适配器注入和权重加载结果。
导出是单独的兼容性验证任务。[SenseVoice ONNX 示例](../examples/industrial_data_pretraining/sense_voice/export.py)、[Paraformer 导出示例](../examples/industrial_data_pretraining/paraformer/export.py)及[导出工具](../funasr/utils/export_utils.py)只是入口,不承诺所有模型均支持。Paraformer 示例实际选择 contextual checkpoint,必须检查并明确替换为自己的模型。模型导出 hook、分词器、前端、动态形状和运行时解码都需要验证。部署前在留出输入上比较导出运行时与 Python 输出。本次文档修改未运行完整训练、质量基准、真实 checkpoint 恢复、模型下载或模型导出。