返回 文章 apply CMS 文章

为沙特阿拉伯方言微调 NVIDIA Nemotron:兼顾目标方言与语言保持的实用工作流

用最小化整理、回放混合和分桶批次,把多语言 ASR 模型适配到低资源方言,同时不牺牲原有语言能力。

ASRNVIDIA NemotronNeMo方言微调
成长分 / 100 77 综合收获、行动、留存与影响

为沙特阿拉伯方言微调 NVIDIA Nemotron:兼顾目标方言与语言保持的实用工作流
为什么值得读提供了一套可复用的低资源方言 ASR 微调流程,包含数据整理、回放混合、适配深度和解码优化的具体配置。

用 SADA 沙特方言的真实实验数据说明每个技术选择的效果与代价,例如回放比例、解冻层数和束搜索延迟。

关键洞察
  1. 最小化整理只移除结构上不可用的样本(如标注标记、时长异常、字符率异常),保留难口音和嘈杂语音,SADA 中保留 82.5% 话语。
  2. 回放混合用 90% 目标方言加 10% FLEURS(7% 英语、3% 阿拉伯语)按权重声明,可减少灾难性遗忘,英语 WER 甚至略有改善。
  3. 长度分桶需同时设置 use_bucketing=True 和 num_buckets,否则静默无效;分桶减少填充,使流式编码器训练可行。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:19359

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

沙特阿拉伯语使这一点变得具体。一个模型可能能识别现代标准阿拉伯语或英语,却在纳吉迪和希贾兹方言或本地录音条件下表现不佳。仅针对目标方言进行微调可以改善这一点,但会削弱其他语言。

NVIDIA Nemotron 3.5 ASR 支持跨 40 种语言-地区的多语言流式转录,包括可直接转录的阿拉伯语,但特定部署的方言和录音条件仍可从微调中受益。

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

视频 1. 为沙特阿拉伯方言微调 NVIDIA Nemotron

何时使用此工作流

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

其技术解决不同的问题:

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

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

沙特阿拉伯语 ASR 流程,涵盖数据整理、模型微调和评估。

图 1. 沙特阿拉伯语 ASR 适配的架构图,展示数据整理、Nemotron 3.5 微调和评估

微调演练

先决条件

NVIDIA NeMo、PyTorch、Python、OmegaConf - 本教程使用

SADA 2022 和 FLEURS - 具备 Python、模型微调、WER 和 CER 的实用知识

  • 12,000 步基线实验使用了两块 GPU;确切的 GPU 型号和内存:NVIDIA RTX PRO 6000 Blackwell Workstation Edition GPU

1. 整理目标语料库而不滤除问题

首先选择你打算部署的方言。对于初始 SADA 实验,这意味着纳吉迪和希贾兹:

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)}"

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

SADA 用 غيرواضح 标记听不清的语音;由于模型无法输出该标注,每一次出现都会造成不可避免的错误。在下面的代码中,这些标记以 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验证
检查点和解码 保留最佳三个检查点;使用贪婪解码

表1. 在仅SADA基线实验中进行的配置更改

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

验证基线(49.5%)始终是我们的主要指标。经过第一个10轮训练后,WER改善至47.8%,再经过10轮后仍保持在47.8%,而在更长的v4延续训练期间达到了46.7%。在第45轮之后,验证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"

百分之七的英语就足够了。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,因此只设置桶数量而不开启该标志是一种静默的空操作,看起来却像是已经配置好了。

结果

这三项改动合在一起,经过 12,000 步、在两块 GPU 上约 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%

表 2. 在目标方言、完整 SADA 测试集以及英语保持性检查上进行微调前后的 WER 和 CER

专门化并没有让我们丢掉被舍弃的方言。模型在我们瞄准的地方提升了 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%
top 6 33.42% 14.10%
top 8 32.32% 13.53%
全部 24 层 29.96% 12.18%

表 3. 按适配深度划分的 WER 和 CER,Najdi 和 Hijazi 测试集

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

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

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

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

此检查点暴露了多个注意力上下文大小,无需重新训练即可选择。第二个数字是编码器在确定输出之前可以关注多少个未来帧:[56, 3]

是流式默认值,[56, 13]

是其支持的最大值。切换到最高前瞻上下文可在无需重新训练的情况下将 WER 降低 1.31 个绝对百分点。其主要权衡是约 800 毫秒的额外延迟。

配置 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

表 4. 微调检查点上的解码配置,SADA Najdi 和 Hijazi 测试集

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

束搜索性能强烈依赖于解码器配置。在后续的扫描中,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(高) 最高准确率且高延迟

表 5. 注意力上下文大小及其输出延迟和典型用途

将工作流应用于其他语言

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

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

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

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

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

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

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

后续步骤

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

开始使用

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

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