ITPub博客

首页 > 人工智能 > 语音识别 > 微调 NVIDIA Nemotron 以适配沙特阿拉伯语方言,并探索通往其他语言的路径 | NVIDIA 技术博客

微调 NVIDIA Nemotron 以适配沙特阿拉伯语方言,并探索通往其他语言的路径 | NVIDIA 技术博客

语音识别 作者:ITPUB社群 时间:1970-01-01 08:33:46 0 删除 编辑

自动语音识别必须处理人们实际的说话方式,而不仅仅是预训练数据中占主导地位的语言和风格。地区方言和本地录音条件往往代表性不足,因此一个在广泛基准测试中表现良好的多语言模型在部署中仍可能表现不佳。

沙特阿拉伯语就是一个具体例子。一个模型可能能识别现代标准阿拉伯语或英语,却难以应对 Najdi 和 Hijazi 语音,或本地录音条件。仅在目标方言上进行微调可以改善该方言的表现,但同时会削弱其他语言的能力。

NVIDIA Nemotron 3.5 ASR 支持跨 40 种语言-地区的多语言流式转录,包括可直接转录的阿拉伯语,但特定部署的方言和录音条件仍可通过微调获得更好的效果。

本文展示如何使用 NVIDIA NeMo 框架和 ASR 微调配方来适配它:整理低资源语料库、构建加权回放混合、使用高效批处理进行微调,并在独立集上评估转录质量。

何时使用此工作流

当你拥有足够的标注语音来使 ASR 模型专业化,但不足以从头训练一个模型时,此流程非常有用:方言适配、领域特定转录、必须保留现有语言的部署。

其技术解决不同的问题:

  • 最小化整理移除不可用的标签和明显的对齐失败,而不丢弃稀缺、困难的语音。
  • 回放混合穿插少量先前学习的数据,以减少灾难性遗忘。
  • 部分编码器解冻限制了参数变化的数量——比完全微调更便宜、更快速,但会牺牲一定的准确率。
  • 长度分桶减少了填充,使流式编码器的训练变得可行。
  • 束搜索和更大的注意力上下文可以在不重新训练的情况下提高离线准确率,代价是延迟和计算量。

这些并非通用默认设置。回放仅保护其数据所代表的内容;当混合数据变化时,部分解冻需要重新调优。而且此工作流并不能推广为适用于每种阿拉伯语方言或部署环境的证据。

微调演练

前提条件

  • NVIDIA NeMo、PyTorch、Python、OmegaConf
  • 本教程使用 SADA 2022 和 FLEURS
  • 具备 Python、模型微调、WER 和 CER 的实用知识
  • ,000 步基线实验使用了两块 GPU(图形处理器);具体 GPU 型号和内存:NVIDIA RTX PRO 6000 Blackwell Workstation Edition GPU

1. 整理目标语料库,不要过滤掉问题

首先选择你打算部署的方言。在最初的 SADA 实验中,这意味着 Najdi 和 Hijazi:

SAUDI_DIALECTS = {"najdi", "hijazi"}
seen = set()

for item in manifest:
    dialect = str(item.get("speaker_dialect", "")).lower().strip()
    seen.add(dialect)
    if dialect not in SAUDI_DIALECTS:
        continue
    keep(item)

missing = SAUDI_DIALECTS - seen
assert not missing, f"never matched: {missing}; observed {sorted(seen)}"

接下来,移除模型无法学习的引用以及可能未对齐的片段。

在下面的代码中,标记以 Unicode 转义形式出现,以便从左到右渲染;它们拼写为 غيرواضح 和 غير واضح 。代码还应用了一些其他检查。

MIN_DURATION, MAX_DURATION   = 0.5, 30.0     # drop clips too short or too long
MIN_CHAR_RATE, MAX_CHAR_RATE = 1.5, 35.0     # drop misaligned transcripts

ANNOTATION_MARKERS = ['\u063a\u064a\u0631\u0648\u0627\u0636\u062d',
                      '\u063a\u064a\u0631 \u0648\u0627\u0636\u062d']

for row in manifest:
    txt  = normalize_arabic(row['text'])
    rate = len(txt) / row['duration']

    if not txt or txt.lower() == 'nan':                      continue  # stringified NaN
    if any(m in row['text'] for m in ANNOTATION_MARKERS):    continue  # annotation, not speech
    if not (MIN_DURATION  <= row['duration'] <= MAX_DURATION):   continue
    if not (MIN_CHAR_RATE <= rate <= MAX_CHAR_RATE):             continue

    keep(row)
def normalize_arabic(t):                    # target dialect + FLEURS Arabic
    t = re.sub(r'[\u064b-\u0670]', '', t)                        # diacritics
    t = re.sub(r'[\u0623\u0625\u0622\u0627]', '\u0627', t)       # alef variants
    t = t.replace('\u0649', '\u064a').replace('\u0629', '\u0647') # alef maqsura, taa marbuta
    t = re.sub(r'[^\u0600-\u06ff\w\s]', '', t)                   # punctuation
    return ' '.join(t.split())

这些检查在 125,490 条话语中保留了 103,559 条:133.7 小时,即起始集合的 82.5%。目标是移除结构上糟糕的样本,而不是仅仅因为基础模型表现不佳就移除难口音或嘈杂语音。如果你使用自动质量分数,请先检查它们的分布;SADA 运行发现,默认的 UTMOS 阈值 3.0 会拒绝几乎所有内容。

作为第二遍处理,更广泛的 SADA 整理流水线使用 NVIDIA NeMo Curator 来标准化音频并移除严重退化的片段。MonoConversionStage 将输入转换为单声道,而 UTMOSFilterStage 和 SIGMOSFilterStage 对感知质量和背景噪声进行评分。我们没有应用它们的默认阈值,而是先以仅评分模式运行这些阶段,检查分数分布,然后设置语料库特定的截止值:UTMOS ≥ 1.25,SIGMOS 噪声 ≥ 1.5,SIGMOS 总体 ≥ 1.5。它们通过了约 85% 的时长有效样本。这保留了具有挑战性但可用的方言语音,而通用阈值会将其丢弃。

2. 从一个数据集开始并监控模型行为

对于新语言或领域,从你能解释的最简单实验开始:一个具有代表性的目标数据集、常规的全量微调以及固定的评估集。目标是观察模型如何响应,验证训练流水线是否正常工作,并创建一个基线,以便衡量后续更改。

在我们的案例中,SADA 就是第一个数据集。模型是 Cache-Aware FastConformer-RNNT,带有提示的多语言流式处理( strip_lang_tags ,target_lang: ar-AR ),其行为与纯英语流式模型不同,并且需要在清单中显式指定语言条件。

你的起始语料库、训练时长和硬件设置会有所不同。下面的配置更改记录了在遇到优化、内存和分布式训练问题时,这个特定实验是如何演变的;它们是故障排除示例,不是推荐的默认值。未列出的超参数,包括权重衰减、梯度裁剪、混合精度以及以话语为单位的有效批量大小,均保留为 NeMo 框架默认值,并且在此实验中未进行调整。

| 领域 | 配置更改 |
| --- | --- |
| 优化 | 学习率:默认 → 1e-4 → 2e-5 ;预热:10,000 → 100 → 50 步;优化器:AdamW(NeMo 默认);调度器:Noam;d_model=1024 |
| 数据加载 | 批量时长:400 → 300 → 200 秒以防止内存不足错误; is_tarred=false ;工作进程:4 个训练和 2 个验证;桶和洗牌缓冲区:1,000 |
| 验证 | 批量大小 8;每个 epoch 验证一次 |
| 检查点和解码 | 保留最佳三个检查点;使用贪婪解码 |

在最初的仅 SADA 实验中,我们使用验证集来监控微调进度。预训练模型在验证集上产生了 49.5% WER,在完整训练语料库上产生了 59% WER。这一差距反映了训练数据中更嘈杂的音频和更大的方言差异。

验证基线(49.5%)始终是我们的主要指标。在第一次运行 10 个 epoch 后,WER 改善至 47.8%,再运行 10 个 epoch 后仍保持在 47.8%,而在更长的 v4 延续训练期间达到 46.7%。在第 45 个 epoch 之后,验证 WER 不再改善。这种有限的提升和明显的平台期表明,继续采用相同的全量微调设置不太可能带来实质性改进。

3. 哪些做法有效:更窄的目标、回放流和分桶批次

以最少筛选实现更窄的目标

我们没有要求模型一次性改进 11 种方言,而是只针对 Najdi 和 Hijazi 这两种我们打算使用的方言进行训练,并且只做了最少的筛选(见上文第 1 点),在 125,490 条话语中保留了 103,559 条,即 82.5%:

回放流

仅对沙特语音进行微调会覆盖模型在预训练中学到的内容。防御方法是回放:混入一小股先前已学习数据的流,使模型在学习新任务的同时,仍持续被要求执行旧任务。

我们使用了 10% 的 FLEURS,按 7% 英语和 3% 阿拉伯语,与 90% 沙特语音混合。应声明这些比例,而不是直接拼接文件,因为滑动窗口洗牌可能直到训练后期才会触及追加到大型清单末尾的行,因此拼接后的回放集在大部分训练过程中实际上并不存在。

from omegaconf import OmegaConf

mix = OmegaConf.create([
    {"type": "nemo", "manifest_filepath": "sada_train.jsonl", "weight": 0.90},
    {"type": "nemo", "manifest_filepath": "fleurs_en.jsonl",  "weight": 0.07},
    {"type": "nemo", "manifest_filepath": "fleurs_ar.jsonl",  "weight": 0.03},
])
OmegaConf.save(mix, "input_cfg.yaml")
cfg.train_ds.manifest_filepath = None
cfg.train_ds.input_cfg = "input_cfg.yaml"

7% 的英语已经足够。FLEURS 英语保留集略有改善,从 11.04% 提升到 10.42%(见下文表 2),同时模型正在专门学习阿拉伯语方言语音。

分桶批次

第二项变化不那么显眼,但同样重要。

基于时长的分桶会将长度相近的话语分到同一批次中:

cfg.train_ds.use_bucketing        = True
cfg.train_ds.num_buckets          = 30
cfg.train_ds.batch_size           = None
cfg.train_ds.batch_duration       = 400.0
cfg.train_ds.quadratic_duration   = 15.0

请注意,仅设置 num_buckets 本身不会产生任何效果;use_bucketing 默认是 False,因此只设置桶数量而不启用该标志是一个静默的空操作,看起来却像是已经配置好了。

结果

这三项改动合在一起,在两块 GPU 上经过 12,000 步、约 4.5 小时:

| 测试划分 | 之前 | 之后 |
| --- | --- | --- |
| SADA Najdi + Hijazi WER | 55.05% | 29.96% |
| SADA Najdi + Hijazi CER | 31.63% | 12.18% |
| 完整 SADA WER | 58.84% | 35.61% |
| 完整 SADA CER | 35.40% | 15.97% |
| FLEURS 英语 WER | 11.04% | 10.42% |
| FLEURS 英语 CER | 6.47% | 4.53% |
| FLEURS 阿拉伯语 WER | 12.67% | 11.41% |
| FLEURS 阿拉伯语 CER | 5.55% | 3.97% |

专门化并没有让我们丢掉被舍弃的方言。模型在目标方向上提升了 25 个点,在整体上提升了 23 个点,同时英语也有所改善。这看起来像是一个完成的结果。所有数字均使用 NeMo 评估脚本测量。

4. 适配深度:编码器需要更新多少

该模型有 24 层编码器。全量微调会更新所有层;冻结编码器可以保留它,但会限制声学适配。介于两者之间,你可以解冻顶部 N 层并保持其余层不变,同时始终训练解码器、联合网络和提示嵌入。

for parameter in model.parameters():
    parameter.requires_grad = False

for name, parameter in model.named_parameters():
    if any(part in name for part in ("decoder", "joint", "prompt")):
        parameter.requires_grad = True

for layer in model.encoder.layers[-8:]:
    for parameter in layer.parameters():
        parameter.requires_grad = True

trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"trainable: {trainable/1e6:.1f}M / {total/1e6:.1f}M")

在记录的前八配方中,有 2.304 亿参数可训练,4.076 亿参数被冻结。我们相互测试了两种深度,其他条件不变:

| 更新的编码器层数 | SADA WER | SADA CER |
| --- | --- | --- |
| 预训练基线 | 55.05% | 31.63% |
| 顶部 6 层 | 33.42% | 14.10% |
| 顶部 8 层 | 32.32% | 13.53% |
| 全部 24 层 | 29.96% | 12.18% |

每一步中,更多可训练容量都更好。在 134 小时的目标语音下,数据支持更新整个编码器,而部分冻结相比全量微调损失 2.4 个点。

这是关于此数据量的发现,而不是通用规则。冻结是一种减少开销的方式,因此当你的语料比我们的更少,或内存是限制因素时,它就会成为正确选择。

5. 无需重新训练即可改进:更大的上下文和束搜索解码

训练只是系统的一半。在支付另一次训练运行之前,先检查你在推理时能获得什么。

该检查点暴露了多个注意力上下文大小,无需重新训练即可选择。第二个数字是编码器在提交输出之前可以关注多少个未来帧:[56, 3] 是流式默认值,[56, 13] 是其支持的最大值。切换到最高前瞻上下文后,WER 在无需重新训练的情况下降低了 1.31 个绝对百分点。其主要代价是大约 800 ms 的额外延迟。

| 配置 | WER | CER | 对比贪心 |
| --- | --- | --- | --- |
| 贪心, [56, 3] | 29.96% | 12.18% | +0.00 |
| 贪心, [56, 13] | 28.65% | 11.39% | -1.31 |
| MALSD beam-4, [56, 3] | 28.81% | 11.48% | -1.15 |
| MALSD beam-8, [56, 3] | 28.62% | 11.40% | -1.34 |
| MALSD beam-8, [56, 13] | 27.25% | 10.63% | -2.71 |

主要权衡是额外延迟,而不是重新训练成本。十三个前瞻帧而不是三个,意味着每次输出前大约多 800 ms 的缓冲。对于批量转写,例如通话存档、媒体、会议录音,这能在无需重新训练的情况下带来准确率提升,而额外缓冲延迟是可以接受的。对于实时字幕,这可能不合适。正确的设置取决于部署场景,而不是准确率数字。

Beam search 的性能强烈依赖于解码器配置。在后续的扫描中,NeMo 的批处理 malsd_batch 策略以适中的成本带来了有用的增益:beam 4 将 SADA WER 从 29.96 % 降至 28.81 %,运行时间为贪心的 0.59×,而 beam 8 达到 28.62%,运行时间为 0.64×。因此,在我们的测试中,beam 4 提供了最佳的速度–准确率平衡。我们没有评估 MAES 或 NGPU-LM 融合,因此我们的结论仅限于 MALSD。

无论采用哪种策略,有一个细节都值得设置:strip_lang_tags=True。否则,区域设置标签会作为字面文本输出,并在每个话语上被计为插入错误。

| 注意力上下文 | 块大小(延迟) | 用例 |
| --- | --- | --- |
| [56, 0] | 80ms(超低) | 超低延迟语音代理 |
| [56, 1] | 160ms(低) | 交互式语音代理、对话式 AI |
| [56, 3] | 320ms(平衡) | 对话式 AI、实时字幕 |
| [56, 6] | 560ms(中等) | 高准确率且延迟合理 |
| [56, 13] | 1.12s(高) | 最高准确率且高延迟 |

将该工作流应用于其他语言

循环保持不变:整理数据、混合回放、选择适配深度、按持续时间分桶、在独立的目标集和回归集上评估。每种语言变化的是语料元数据、转写约定、归一化、分词器覆盖范围、文字系统处理和能力指标。

对于另一种语言,先从一个经过审核的小型清单开始,并在姓名、数字、借词和混合文字句子上测试基础分词器。将阿拉伯语归一化器替换为能够保留目标文字中有意义区分的归一化器,并端到端验证 unicode 归一化和编码。

对于没有空白词边界的语言,WER 可能会产生误导。请改用字符、token 或词素级别的度量,并说明其局限性。回放数据应代表你需要保留的能力,而不仅仅是方便获取的高资源语音,并且应尽可能使用真实数据而不是合成数据。

用说话人分离扩展该工作流

微调 ASR 改善的是转写内容;说话人分离则增加了谁在何时说话。在多说话人音频中,分离模型识别语音片段并分配一致的说话人标签。将这些标签和时间戳与微调后的 Nemotron 3.5 ASR 输出结合,可创建带说话人归属的转写,将每个参与者的发言分开,适用于会议、访谈、联络中心、课堂和其他多说话人环境。

该工作流还可以帮助语音数据提供商准备多说话人语料库,通过自动生成说话人轮次时间戳和匿名说话人标签,供人工审核,之后数据才用于 ASR 微调或评估。

NVIDIA Nemotron 3 Diarization 刚刚发布,将该工作流从方言感知转录扩展到最多 8 个说话人的说话人归属转录。它是一个开放模型,可以添加到您正在使用的任何 ASR 系统中。Diarization 本身不转录音频;其说话人边界和标签与 ASR 时间戳对齐,以生成最终的结构化转录文本。在 Hugging Face 博客中了解有关架构、基准测试以及如何入门的更多信息。

后续步骤

引发本教程的对话提出了一个比单纯能力更重要的问题:如何使方言适配变得有用,改进目标方言,保留现有技能,并将训练精力花在能改变已部署系统的地方。实验指向了一个实用的答案。轻度整理,按权重混合回放,添加您所需确切行为的示例,并根据您的数据支持程度更新尽可能多的编码器。然后优化解码,并在独立集合上评估每项能力。

开始使用

微调 notebook:https://github.com/nvidia-riva/tutorials/blob/main/asr-finetune-nemotron-3.5-asr-streaming-prompt.ipynb

微调 skill:https://github.com/NVIDIA/skills/tree/main/skills/nemotron-asr-finetune


转载声明:本文为转载整理,原文版权归原作者及原站点所有。

原文来源:NVIDIA Technical Blog

原创地址:https://developer.nvidia.com/blog/fine-tuning-nvidia-nemotron-for-saudi-arabic-dialects-with-a-path-to-other-languages/

原文作者:Elizabeth Goodman;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2022-11-21

  • 博文量
    1122
  • 访问量
    547363