Skip to content

SenseVoice-Small 增量微调:保留普通话+粤语的前提下新增潮汕话识别,如何避免灾难性遗忘? #3388

Description

@13649480419

背景
我们线上用 SenseVoice-Small 做普通话 +粤语识别(已部署),输出标准中文。现在想在不损失现有普通话/粤语识别能力的前提下,新增潮汕话识别。

难点:潮汕话不在 SenseVoice 现有语种里;而且我们拿不到官方普通话/粤语的原始训练数据,担心直接微调会导致普/粤能力灾难性遗忘。

想请教的问题

  1. 如何防遗忘:在没有原始 zh/yue 训练数据的情况下,微调新增一个语种,有没有官方推荐做法避免遗忘?是否只能靠混入其他 zh/yue
    数据"复习"?新增语种数据 vs 复习数据的推荐配比大概是多少?

  2. 能否部分参数微调:SenseVoice-Small 微调是否支持冻结部分参数(比如冻结encoder、只训练部分层)用于低资源/防遗忘?文档里我只看到全量微调,请问有没有冻结/adapter 的官方做法?

  3. 语种标签冲突:SenseVoice 的语种 token 是闭集(<|zh|> <|en|> <|yue|> <|ja|> <|ko|>),没有潮汕话槽位。我打算把潮汕话标成
    <|zh|>(因为输出目标是标准中文)——这样会不会干扰原有普通话(同为 <|zh|>)的识别?能否新增一个自定义语种 token?如何添加?

  4. 有没有增量扩展新语种的官方示例或建议?

我已经尝试/了解的

  • 已读训练文档 https://modelscope.github.io/FunASR/zh/training.html ,了解 finetune.sh 与 jsonl 数据格式(key/source/target/text_language 等)。
  • 已确认拿不到官方 zh/yue 原始数据,所以卡在"如何防遗忘"上。
  • 目前主要未解决两点:① 无原始 zh/yue 数据下的遗忘问题;② 潮汕话挤用 <|zh|> 是否会干扰普通话。

计划使用的命令

计划用 examples/industrial_data_pretraining/sense_voice/finetune.sh + jsonl(潮汕话样本 text_language 标为
<|zh|>)。尚未开跑,想先确认方向是否正确。

Activity

  1. LauraGPT commented on Jul 23, 2026

    @LauraGPT
    Collaborator

    先给结论:没有旧域约束时,任何全量或部分参数微调都不能保证普通话/粤语零退化。当前最稳妥的路线是“旧模型伪标注/公开数据 replay + 小学习率分阶段解冻 + 三套独立验证集”,而不是只用潮汕话单域继续训练。

    1. 防遗忘

    拿不到官方原始数据不等于不能做 replay。建议先用当前线上 SenseVoice-Small 给你们已有的、无标注的普通话/粤语生产音频生成伪标签,过滤低置信度/异常长短样本,再补充许可允许的公开 zh/yue 数据。训练前固定三套不参与训练的验证集:普通话、粤语、潮汕话,并始终用原始线上模型作为 retention baseline。

    目前没有官方固定配比。可以先把下面三档作为实验起点,不是官方保证值:

    • replay:new = 1:1
    • replay:new = 2:1
    • replay:new = 3:1

    replay 内再按线上普通话/粤语流量或业务重要性分层采样。选择“潮汕话达到目标,同时 zh/yue 相对 CER/WER 退化不超过你们业务阈值”的最小新域占比;不要只看混合总指标。若连 3:1 replay 仍明显遗忘,继续增加 replay、降低学习率/训练步数,或加强 teacher-student 约束。

    2. 冻结参数

    SenseVoice-Small 没有单独文档化的 adapter/LoRA recipe,但 FunASR 通用训练入口已经支持按参数名前缀冻结。funasr/bin/train_ds.py 会读取 freeze_param,所以可以先在现有命令加:

    ++freeze_param="encoder" \
    ++optim_conf.lr=0.00002

    这会冻结整个 encoder.*,只训练语言查询 embedding 和 CTC/output 部分,适合先做短暂 head warm-up;但潮汕话的声学差异较大,长期完全冻结 encoder 往往容量不够。更建议第二阶段只冻结前部层并放开后部层,例如通过逗号分隔的精确前缀冻结:

    ++freeze_param="encoder.encoders0,encoder.encoders.0,encoder.encoders.1,encoder.encoders.2" \
    ++optim_conf.lr=0.00002

    实际冻结多少层要靠三套验证集选择。启动后请检查日志里的 Setting ... requires_grad = False 和 model summary,确认冻结范围符合预期。

    仓库里的 LoRA 基础设施和示例目前主要接在 Paraformer/SANM 的特定模块上;SenseVoice-Small 的构造路径没有把 LoRA 配置完整接通。因此不建议把 lora_only 当作现成的 SenseVoice 官方方案,否则可能没有可训练的 LoRA 参数。

    3. 语言 token

    当前 SenseVoice-Small 的 language id 是闭集:<|zh|>、<|en|>、<|yue|>、<|ja|>、<|ko|>。训练数据集假定 text_language 恰好编码成一个特殊 token,模型内部的 lid_int_dict / lid_dict 也只认识这些固定 id。

    因此请不要直接把 <|teochew|>、<|nan|> 等字符串写进 JSONL:现有 tokenizer 会把它们切成多个普通 token,语言查询会回退到 auto,而且后续 text[:, 4:] 的目标位置也会错位。

    在不 fork 模型结构的前提下,如果输出目标是标准中文,先用 <|zh|> 是当前可运行方案。它确实与普通话共享条件,因此 replay 和独立 zh 验证集是必要的。新增真正的自定义 language token 不是数据侧一行配置:需要同时修改 SentencePiece 模型、vocab/CTC 输出维度、lid_dict、lid_int_dict、language embedding、旧权重迁移,以及导出/推理代码;目前仓库没有这条官方示例。

    建议的最小实验顺序

    1. 先保存当前线上模型在固定 zh/yue/chaozhou 验证集上的基线。
    2. 用 <|zh|>,replay:new=2:1,冻结整个 encoder,低学习率做短 warm-up。
    3. 保留 replay,逐步解冻 encoder 后部层;每个 checkpoint 同时评估三套集合。
    4. 若需要严格区分潮汕话条件,再单独设计 custom-LID 模型 fork,不要在这轮数据微调里混做。

    为了把建议收敛成更具体的配置,麻烦补充:潮汕话/可用于 replay 的普通话/粤语各有多少小时和说话人数,以及当前线上模型在三套验证集上的 CER/WER(没有潮汕话基线也可以先给前两套)。

  2. added
    needs feedbackWaiting for reporter feedback or retest results
    and removed
    needs triageNeeds maintainer triage and routing
    on Jul 23, 2026
  3. 13649480419 commented on Jul 23, 2026

    @13649480419
    Author

    数据量(当前值,潮汕话与普/粤 后续都会持续增多)

    • 潮汕话:8.45小时,说话人数约20人
    • 可 replay 的普/粤:自有录音共约 75 小时(普通话约 100h、粤语约10h),无人工标注,仅音频 + 当前模型 ASR 输出(拟作伪标签,过滤低置信/异常长短),说话人数约240人。
      当前线上模型三套验证集基线 CER(离线 SenseVoice)
    • 普通话(FLEURS test,300 句):8.9%
    • 粤语(ASCEND 口语对话,314 句):9.1%
    • 潮汕话(自有数据域 held-out,300 句):93.3%(现模型基本不会潮汕话,为起点)
  4. LauraGPT commented on Jul 23, 2026

    @LauraGPT
    Collaborator

    感谢补充数据。按你们当前的 8.45 小时潮汕话数据,建议先跑下面这一档:

    • 每个 epoch:潮汕话 8.45h + replay 16.9h(replay:new=2:1)。
    • replay 第一版可用普通话约 10.1h、粤语约 6.8h。这个 60:40 是为了显式保护粤语的实验起点,不是官方固定比例;不要按 100h:10h 原始池比例直接采样,否则粤语容易被淹没。
    • 每个 epoch 从旧域池轮换采样,不需要把全部旧域音频重复塞入每轮。你回复中的“共约 75h”与“普通话约 100h + 粤语约 10h”不一致,请在生成 manifest 时以逐条统计结果为准,但这不影响每轮 16.9h 的 replay 预算。
    • 三套验证集先固定,并确保与训练/replay 说话人不重叠。保留当前基线:zh 8.9%、yue 9.1%、Teochew 93.3%;提前定义 zh/yue 可接受的相对退化阈值。
    • 第一阶段用 <|zh|>,冻结整个 encoder,lr=2e-5,先跑 3–5 epoch,并频繁保存/评估,不要直接沿用默认 50 epoch、2e-4。
    • 若 zh/yue 保持住但潮汕话 CER 停滞,再从最佳 checkpoint 放开 encoder 后部层;若任一旧语种超出阈值,则增加 replay、提高粤语最低占比、降低学习率或提前停止。
    • 选 checkpoint 时先淘汰所有违反 zh/yue 保持阈值的模型,再在剩余模型里选潮汕话 CER 最低者。

    我已经把完整的中英文配方、冻结参数和 1:1 / 2:1 / 3:1 实验矩阵合入仓库:

    你们可以先按 2:1 跑第一轮,把每个 checkpoint 的 zh/yue/Teochew CER 表贴回来,我们再根据三条曲线收敛第二阶段解冻层数。

  5. LauraGPT commented on Aug 11, 2026

    @LauraGPT
    Collaborator

    当前问题已有可执行的 2:1 replay、分阶段冻结/解冻和三语种验证方案,对应中英文持续微调指南也已通过 #3396 合入 main。先按已提供方案将 issue 标记为 completed,避免开放队列长期停留在等待反馈状态。后续跑出 zh / yue / 潮汕话各 checkpoint 的 CER 表后,可以直接重新打开本 issue;届时我们再根据三条曲线一起收敛 replay 比例、学习率和解冻层数。

  6. LauraGPT commented on Aug 29, 2026

    @LauraGPT
    Collaborator

    重新打开这个 issue。之前因为已经给出训练方案和指南就标记 completed,但方案尚未经过你们的三语种 checkpoint/CER 结果验证,这不等于实际问题已经解决。

    请按方便的节奏继续实验;有 zh / yue / 潮汕话的 CER 表或训练曲线后直接贴在这里。即使暂时没有结果,本 issue 也会保持为等待反馈,而不是因为队列整理而关闭。

  7. 13649480419 commented on Sep 8, 2026

    @13649480419
    Author

    抱歉隔了很久才回来。这段时间我们大部分精力花在语料采集上,所以拖到现在才回复。
    也谢谢您 8-29 主动重开这个 issue、并说明可以按我们的节奏推进。您当时当天就给出了完整配方,
    还把中英文持续微调指南合进了主仓(#3396),对我们帮助很大。

    先说一个必须坦白的情况,它决定了这条回复能提供什么、不能提供什么。

    一、为什么我不贴潮汕话那一列的 CER

    我们按您的一阶段配方完整跑完了,但事后发现当时的潮汕话测试集本身不可信:
    它的参考文本与训练标签同源,而这批标签究竟是逐字转写、还是书面语意译,
    我们当时根本没有验证过。后来另建了一套与训练标签不同源的独立测试集,
    才发现旧尺子量出来的数字没有参考价值。

    所以早期那些潮汕话 CER 我就不贴了——贴出来只会误导您对 replay 配比的判断。

    这一条本身可能是最值得写进指南的教训:给一个全新语种做持续微调时,如果测试集的
    参考文本与训练标签同源、且标注规范未经独立核验,那么该语种上的所有 CER 都不能用来
    选 checkpoint,也不能用来判断某个配置好不好。
    我们在这上面浪费了相当长的时间,
    而它不报错、不异常,从曲线上完全看不出来。

    二、旧域保持这一侧的结果(这部分尺子是干净的)

    旧域四格用的是 FLEURS-cmn / FLEURS-yue 等公开测试集,参考文本没有上面那个问题。
    这里只给相对退化量——我们的绝对基线后来因为 CER 口径修订变动过,绝对值不宜跨期比较:

    配置 旧域四格最差退化 备注
    <|zh|>、冻结整个 encoder、lr 2e-5(按指南) +1.66pp 普通话退化最明显(普·朗读 +1.40pp)
    <|minnan|>、冻结 encoder、更小 lr +0.97pp 普通话两格 ±0.0
    <|minnan|>、解冻 + teacher-student 约束 + WiSE-FT +0.17pp 旧域达标

    一个可能对指南有用的观察:共享 <|zh|> 会显式干扰普通话。
    有 <|yue|> 隔离的粤语几乎不动,而普通话明显退化;改用词表中已经存在的 <|minnan|>
    (单 token,不属于您提醒的"自定义字符串被切碎"那种情形)之后,普通话退化归零。
    如果指南里能提示一句"新语种优先复用词表中已有的空闲语种 token,而不是共享 <|zh|>",
    应该能帮到后来的人。

    至于潮汕话本身,目前已经从"基本不识别"(我们 7-23 贴过的起点:held-out 集 CER 93.3%,
    后来更换测试集后,训前甚至因为大量插入而超过 100%)推进到了业务可用的水平。
    具体数字我们还在用那套独立测试集复核,等稳定了再完整汇报。
    我们这边的后续实验也还在进行中,很抱歉这一轮不能在 replay 配比的收敛上帮到指南。

    三、一个可能值得修的问题,和两条来自部署侧的提醒

    1.(可能值得修)model.pt.best 对纯 CTC 的 SenseVoice 是静默失效的

    funasr/train_utils/trainer.py 里 avg_keep_nbest_models_type 默认取 "acc",
    而选优判据用的是 cur_acc >= best_acc——是 >=,平手也会覆盖。
    SenseVoice-Small 是纯 CTC,验证集 acc 恒为 0.0000,于是 0.0 >= 0.0 恒成立
    ⇒ model.pt.best 必然等于最后一个 checkpoint(我们做过 md5 比对确认)。

    它不报错也不告警,使用者会以为自己拿到了最优点,实际拿到的是最后一个点。
    建议:CTC-only 模型下默认改用 loss,或者在检测到 acc 恒为 0 时打一条 WARNING;
    持续微调指南里也建议加一句"请勿依赖 model.pt.best,按验证集逐点实测选点"。

    2.(部署侧提醒)新增语种 tag 后,要检查下游有没有对语种做闭集校验

    这一条不是 FunASR 本身的问题,是我们在自己的部署链路上踩到的,但我觉得值得写进指南:
    我们的服务端对识别结果的语种 tag 做了闭集校验,遇到闭集之外的 tag 就判为异常、
    丢弃离线结果——最终端点只返回一个句号,而模型其实识别得好好的,日志里只有一行 WARNING。

    结果就是:模型训好了,端到端仍然是空输出,而且很难往模型之外的地方去想。
    指南里如果能提示一句"新增语种 tag 后,需同步检查下游/部署侧是否存在语种校验或白名单",
    应该能省掉后来者不少排查时间。

    3.(现象反馈)发射速率被压低 / CTC blank 偏置

    上次提到的这个现象仍然存在。我们从解码侧验证过:对 blank 施加惩罚无法挽回——
    把输出长度拉回 1.0 时精确率同步崩掉,说明底下并没有可用的分布,属训练侧现象而非解码侧。
    持续学习场景下若官方有推荐的对策(length_normalized_loss、blank 相关正则、
    新域 loss 权重等),也希望能写进指南。

    四、两个想请教的问题

    1. SenseVoice-Small 预训练时,粤语数据里朗读与自发口语的大致配比是什么量级?
      我们观察到在持续微调中,粤语这两种体裁的稳健性差别很大——朗读几乎不动,
      自发口语明显更脆弱。想确认这是否与预训练分布本身有关。

    2. embed 表里各语种槽位在持续微调中的梯度问题。 如果某个语种在新的训练数据里
      占比很低,它对应的槽位在整个微调过程中几乎拿不到梯度、基本保持原样。
      官方对这种情况有建议吗——显式冻结、特殊初始化,还是不必处理?

  8. LauraGPT commented on Sep 8, 2026

    @LauraGPT
    Collaborator

    谢谢这次把可信结果和不可信结果分开说明,尤其是独立测试集的标注口径问题。这比继续用旧 CER 调配比更有价值。你们的旧域退化表会作为这一实验的反馈保留,不会写成通用保证,也不要求你补交已经失效的潮汕话数字。

    我核对了当前源码:Trainer 默认确实用 avg_keep_nbest_models_type="acc",且平手会覆盖 best。对于你描述的验证 acc 始终为 0 的运行,这个指标没有选点能力。后续新运行可以在现有训练命令中明确加入:

    ++train_conf.avg_keep_nbest_models_type=loss

    这是当前已有参数,不是尚未发布的修复;它只使 best 按验证 loss 选取,不保证是最低 CER 或旧域保持最好的 checkpoint。仍需保留多个候选并在独立的新域、普通话/粤语朗读及自发口语集上逐点比较。我会把 CTC 场景的默认指标/告警和相应回归测试作为单独修复处理,本条回复不代表问题已经修好。

    关于另外两问:

    • 我目前没有可核验的 SenseVoice-Small 预训练粤语朗读/自发口语配比,不能据此给出一个比例,也不能把你们的差异直接归因于该配比。
    • 低频语种槽位建议先保留预训练初始化,记录实际采样次数、对应 embedding 行的梯度和参数变化,再做冻结/低学习率对照。槽位本身变化小也不代表该语种能力不会退化,因为共享 encoder 仍可能变化;梯度置零也不一定阻止 AdamW 等优化器的 weight decay。

    <|minnan|> 的实验结果值得记录,但“词表里已有”不能自动等同于“空闲、可任意复用”。指南需要区分你们的具体对照结果与通用建议,并加入 tokenizer/模型映射/服务端白名单的端到端检查。独立标注规范、best 选点边界、下游语种校验这三点也会纳入后续指南修订。issue 保持开放。

  9. LauraGPT commented on Sep 8, 2026

    @LauraGPT
    Collaborator

    这次已把选点问题做成独立修复并合入 #3677,同时更新了 SenseVoice 的微调脚本和中英文持续微调指南。

    关键原因不是 >= 本身,而是当前 SenseVoice 的验证统计没有 ASR acc,只有 acc_rich;此前 trainer 把缺失的 acc 当成了 0。修复后,缺失或非有限指标不再进入 best、保留数量筛选和 checkpoint 平均;真正的数值 0 仍是合法指标。普通 trainer 和 DeepSpeed trainer 的保存/恢复路径也同步修正,写入的 best 指针与排名对应当前保存结果。

    维护脚本现在显式使用 ++train_conf.avg_keep_nbest_models_type=loss。新指南强调:这是模型总验证 loss,不等于最低 CER,也不保证旧域保持;平均后的模型同样需要独立评估。改变选点指标请使用新的输出目录,旧运行中已经写入的虚假 0 排名不会自动修复,已删掉的候选权重也无法恢复。

    本次最终提交在 ind-gpu8 上通过 97 项 CPU 回归,包括真实双进程 Gloo、缺失/非有限指标、保存/恢复、筛选/平均、写入失败时不提前删除旧候选,以及文档契约。DeepSpeed 的保存接口使用模拟引擎边界测试,不是完整 DeepSpeed/GPU 声学训练验证。

    你提出的独立标注来源/逐字转写口径、旧域体裁分开评估、tokenizer/模型映射/服务端语种白名单检查,已经加入 中文指南 和 英文指南。已撤下可能误导后续选点的旧 CER 表,没有把 <|minnan|> 写成任意可复用的空闲槽位。

    本次是源码修复,尚未包含在 PyPI 1.4.14 中。它解决的是选点与保存逻辑,不代表灾难性遗忘或 CTC blank 偏置已经解决。issue 继续保持开放,后续按你们独立测试集的复核结果推进。

  10. LauraGPT commented on Sep 9, 2026

    @LauraGPT
    Collaborator

    更新发布状态:#3677 的 trainer 选点与保存修复现在已包含在 PyPI FunASR 1.4.15 中,不再需要仅为此修复安装开发分支。

    训练脚本与指南请同时参考固定版本的中文持续微调指南;pip 升级不会自动替换你复制到工作目录里的训练脚本。更换选点指标请使用新输出目录,旧运行中错误的排名或已删除权重不会自动恢复。

    这次发布解决的是有效指标选点和 checkpoint 保存边界,不代表 CER、遗忘或 blank 偏置已解决。issue 保持开放,后续继续依据你们的独立测试集结果判断。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs feedbackWaiting for reporter feedback or retest resultsquestionFurther information is requested

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions