从预训练到 ASR 微调:unilm 仓库 HuBERT 自监督语音模型完整实战指南
2026/9/14 22:45:10 网站建设 项目流程

从预训练到 ASR 微调:unilm 仓库 HuBERT 自监督语音模型完整实战指南

【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm

导读

本文基于 unilm 仓库中 kosmos-2/fairseq/examples/hubert/README.md 及配套源码,系统讲解 HuBERT 自监督语音表征学习模型的完整使用链路:加载官方预训练权重、准备伪标签数据(特征提取 + K-means 聚类)、预训练新模型、以 CTC 损失微调做语音识别,以及三种解码方式(Viterbi / KenLM / Fairseq-LM)。读完本文,你将掌握在 fairseq(unilm 仓库内置版本)上复现 HuBERT 全流程的完整命令与关键参数语义,并能结合源码理解每一步的底层实现。

HuBERT 简介与官方模型清单

HuBERT(Hidden-Unit BERT)是一种自监督语音预训练方法,核心思想是在无人工标注的条件下,通过离线聚类得到的"隐式单元"(hidden units)作为伪标签,配合掩码预测目标训练 BERT 式语音编码器,从而获得可用于下游任务(如 ASR)的通用语音表征。unilm 仓库在 kosmos-2/fairseq/examples/hubert/ 目录下提供了完整的示例代码与配置文件。

官方发布的预训练与微调模型如下表所示(模型体积以参数量计,预训练数据来自 LibriSpeech 960 小时与 Libri-Light 60k 小时):

模型参数量预训练数据微调数据用途
HuBERT Base~95MLibriSpeech 960 hr无(预训练模型)直接加载使用
HuBERT Large~316MLibri-Light 60k hr无(预训练模型)直接加载使用
HuBERT Extra Large~1BLibri-Light 60k hr无(预训练模型)直接加载使用
HuBERT Large~316MLibri-Light 60k hrLibriSpeech 960 hrASR 微调模型
HuBERT Extra Large~1BLibri-Light 60k hrLibriSpeech 960 hrASR 微调模型

下载地址请以原文档表格中的官方链接为准。此外,仓库还提供了 update_ckpt.py 与 measure_teacher_quality.py 等辅助脚本,分别用于旧版 checkpoint 的字段对齐迁移和评估伪标签教师的质量。

加载预训练模型

通过 fairseq 的 checkpoint 工具即可一行加载模型,配合任务(task)对象可直接提取特征或进行下游推理:

ckpt_path = "/path/to/the/checkpoint.pt" models, cfg, task = fairseq.checkpoint_utils.load_model_ensemble_and_task([ckpt_path]) model = models[0]

从源码结构看,这一调用同时解析模型、配置文件(Hydra cfg)与任务对象。例如特征提取脚本 dump_hubert_feature.py 正是利用该 API 加载模型后调用model.extract_features(source=x_chunk, padding_mask=None, mask=False, output_layer=self.layer)获取指定 Transformer 层的中间表征,说明同一加载路径可复用于训练数据准备与下游推理。

训练新模型

数据准备:分片特征提取与 K-means 伪标签

训练 HuBERT 需要三类文件,具体生成步骤见 simple_kmeans/README.md:

  • {train,valid}.tsv:波形列表文件。首行为音频根目录,后续每行是一个音频文件的相对路径;
  • {train,valid}.km:按帧对齐的伪标签文件,每行是音频对应的一串聚类 ID;
  • dict.km.txt:占位词典文件(见下文)。

label_rate必须与聚类所用特征的帧率一致:MFCC 特征默认为 100Hz,HuBERT 特征默认为 50Hz。整个准备流程分四步,全部基于分片(shard)并行:

1. 特征提取

提取 39 维 MFCC+delta+ddelta 特征(用于第 1 轮迭代预训练):

python dump_mfcc_feature.py ${tsv_dir} ${split} ${nshard} ${rank} ${feat_dir}

提取已训练 HuBERT 模型第${layer}层 Transformer 特征(用于第 2 轮及以后迭代):

python dump_hubert_feature.py ${tsv_dir} ${split} ${ckpt_path} ${layer} ${nshard} ${rank} ${feat_dir}
  • tsv 会被切分为${nshard}个分片,本命令只处理${rank}号分片,rank取值[0, nshard-1]
  • 特征保存为${feat_dir}/${split}_${rank}_${nshard}.{npy,len}.npy为拼接特征矩阵,.len为每段音频的帧数);
  • 显存不足时,可通过--max_chunk调小单次送入模型的音频块大小(默认 1600000 采样点,见 dump_hubert_feature.py);
  • 源码中MfccFeatureReader通过torchaudio.compliance.kaldi.mfcc计算 MFCC 并级联 delta/ddelta;HubertFeatureReader则按max_chunk分块调用extract_features再拼接,二者读取音频时都要求采样率与任务配置一致(默认 16000Hz,多声道取均值)。

2. K-means 聚类

用 10% 的数据拟合一个含${n_clusters}个簇的 MiniBatchKMeans 模型:

python learn_kmeans.py ${feat_dir} ${split} ${nshard} ${km_path} ${n_cluster} --percent 0.1

模型保存到${km_path}(joblib 格式)。可调参数(源码 learn_kmeans.py 中以-h查看)包括:

  • --percent:采样比例,设为-1表示使用全部数据;
  • --init:簇初始化方法,默认k-means++
  • --max_iter:迭代轮数上限,默认 100;
  • --batch_size:批大小,默认 10000;
  • --tol:收敛容差,默认 0.0;
  • --max_no_improvement:连续无改进即停止,默认 100;
  • --n_init:随机初始化次数,默认 20;
  • --reassignment_ratio:重分配比例,默认 0.0;
  • --seed:随机种子,默认 0。

3. K-means 应用(打标签)

用训练好的聚类模型为每个分片生成伪标签:

python dump_km_label.py ${feat_dir} ${split} ${km_path} ${nshard} ${rank} ${lab_dir}

输出到${lab_dir}/${split}_${rank}_${nshard}.km。源码 dump_km_label.py 中ApplyKmeans通过"最近簇中心"距离(在 GPU 上以矩阵运算加速)为每帧分配 ID,每段音频的 ID 序列写为一行空格分隔的整数。

4. 合并分片并创建占位词典

for rank in $(seq 0 $((nshard - 1))); do cat $lab_dir/${split}_${rank}_${nshard}.km done > $lab_dir/${split}.km

占位词典(每行一个聚类 ID,频数可随便写,仅用于 fairseq 词典加载):

for x in $(seq 0 $((n_clusters - 1))); do echo "$x 1" done >> $lab_dir/dict.km.txt

预训练 HuBERT 模型

假设{train,valid}.tsv位于/path/to/data{train,valid}.km位于/path/to/labels,标签帧率为 100Hz,训练一个 12 层 Transformer 的 base 模型:

$ python fairseq_cli/hydra_train.py \ --config-dir /path/to/fairseq-py/examples/hubert/config/pretrain \ --config-name hubert_base_librispeech \ task.data=/path/to/data task.label_dir=/path/to/labels task.labels='["km"]' model.label_rate=100

(在 unilm 仓库中,fairseq_cli/hydra_train.py实际位于 kosmos-2/fairseq/fairseq_cli/,--config-dir指向 config/pretrain/。)

hubert_base_librispeech.yaml 给出了 base 模型的完整默认配置,关键参数如下:

  • task._name: hubert_pretrainingtask.sample_rate: 16000task.label_rate: ${model.label_rate}(与聚类帧率一致);task.max_sample_size/min_sample_size: 250000/32000random_crop: true表示训练时随机裁剪音频段;task.normalize: false(必须与特征提取器一致,否则帧对齐错位);
  • criterion._name: hubertpred_masked_weight: 1.0pred_nomask_weight: 0.0(只对掩码帧算损失)、loss_weights: [10,]
  • model部分:mask_prob: 0.80(掩码比例)、conv_feature_layers: '[(512,10,5)] + [(512,3,2)] * 4 + [(512,2,2)] * 2'(CNN 特征提取器结构:通道数、卷积核、步长)、final_dim: 256feature_grad_mult: 0.1(特征提取器梯度乘以 0.1 以稳定训练)、dropout/attention_dropout: 0.1encoder_layerdrop: 0.05
  • optimization.max_update: 400000lr: [0.0005]optimizer._name: adamadam_betas: (0.9,0.98)lr_scheduler._name: polynomial_decaywarmup_updates: 32000
  • checkpoint.save_interval_updates: 25000keep_interval_updates: 1no_epoch_checkpoints: true(按 update 数保存);
  • distributed_training.distributed_world_size: 32nprocs_per_node: 8等为示例分布式配置。

该目录还提供了 hubert_large_librivox.yaml 与 hubert_xlarge_librivox.yaml,分别对应 Large(~316M)与 Extra Large(~1B)规模。

用 CTC 损失微调做 ASR

假设{train,valid}.tsv位于/path/to/data,字符级转录{train,valid}.ltr位于/path/to/trans,用预训练 checkpoint/path/to/checkpoint微调:

$ python fairseq_cli/hydra_train.py \ --config-dir /path/to/fairseq-py/examples/hubert/config/finetune \ --config-name base_10h \ task.data=/path/to/data task.label_dir=/path/to/trans \ model.w2v_path=/path/to/checkpoint

(配置文件位于 config/finetune/base_10h.yaml。)该配置的关键点:

  • task.fine_tuning: truetask.labels: ["ltr"]task.single_target: true(微调阶段只预测一个目标,即字符序列);
  • criterion._name: ctczero_infinity: true(CTC 损失中把无穷大梯度置零);
  • model._name: hubert_ctcw2v_path指向预训练权重;微调时feature_grad_mult: 0.0冻结卷积特征提取器;mask_prob: 0.75mask_length: 10mask_channel_prob: 0.5mask_channel_length: 64表示继续做时间/通道掩码(SpecAugment 式增强);layerdrop: 0.1
  • freeze_finetune_updates: 10000:前 10000 步冻结除输出层外的编码器,dataset.validate_after_updates: ${model.freeze_finetune_updates}与其呼应;
  • optimization.max_update: 25000lr: [2e-5]sentence_avg: truelr_scheduler._name: tri_stagewarmup_steps: 8000hold_steps: 0decay_steps: 72000final_lr_scale: 0.05
  • checkpoint.best_checkpoint_metric: wer(按验证集 WER 选最佳 checkpoint)。

解码(推理)

假设test.tsvtest.ltr为待解码数据,位于/path/to/data,微调模型位于/path/to/checkpoint。支持三种解码模式:

模式说明配置文件
Viterbi 解码贪心解码,不带语言模型infer_viterbi.yaml
KenLM 解码配合 arpa 格式的 KenLM n-gram 语言模型infer_kenlm.yaml
Fairseq-LM 解码配合 Fairseq 神经网络语言模型infer_fsqlm.yaml

task.normalize必须与微调时保持一致,否则输入分布不一致会显著影响识别结果。

Viterbi 解码

$ python examples/speech_recognition/new/infer.py \ --config-dir /path/to/fairseq-py/examples/hubert/config/decode \ --config-name infer_viterbi \ task.data=/path/to/data \ task.normalize=[true|false] \ decoding.exp_dir=/path/to/experiment/directory \ common_eval.path=/path/to/checkpoint dataset.gen_subset=test \

解码结果保存于/path/to/experiment/directory/decode/viterbi/testinfer_viterbi.yamlhydra.run.dir: ${common_eval.results_path}/viterbicommon_eval.post_process: letter表示对输出做 letter 级别后处理)。

KenLM / Fairseq-LM 解码

假设发音词典与 n-gram 语言模型分别位于/path/to/lexicon/path/to/arpa

$ python examples/speech_recognition/new/infer.py \ --config-dir /path/to/fairseq-py/examples/hubert/config/decode \ --config-name infer_kenlm \ task.data=/path/to/data \ task.normalize=[true|false] \ decoding.exp_dir=/path/to/experiment/directory \ common_eval.path=/path/to/checkpoint dataset.gen_subset=test \ decoding.decoder.lexicon=/path/to/lexicon \ decoding.decoder.lmpath=/path/to/arpa

(unilm 仓库中infer.py实际位于 kosmos-2/fairseq/examples/speech_recognition/new/infer.py,解码默认超参数定义在 examples/speech_recognition/hydra/decoder.py,可通过命令行覆盖。)

infer_kenlm.yaml的默认解码超参数为:beam: 500beamthreshold: 100lmweight: 2wordscore: -1silweight: 0。例如把束宽改为 500 可追加decoding.decoder.beam=500。常用参数语义:

  • decoding.decoder.beam:束搜索宽度;
  • decoding.decoder.beamthreshold:束剪枝阈值,超出该值终止扩展;
  • decoding.decoder.lmweight:语言模型权重,越大越依赖 LM;
  • decoding.decoder.wordscore:词得分(word bonus),鼓励切分出更多词;
  • decoding.decoder.silweight:静音帧权重。

若改用 Fairseq 神经 LM,把--config-name换成infer_fsqlm,并按 infer_fsqlm.yaml 修改lexicon/lmpath指向相应文件;该配置默认beam: 500beamthreshold: 25lmweight: 2wordscore: -1silweight: 0。两种带 LM 的解码结果分别保存于decode/kenlm/testdecode/fsqlm/test下,输出目录名会携带束搜索参数(如beam500_th100_lmw2_wrd-1_sil0),便于对不同超参数组合做对比实验。decoding.unique_wer_file: true表示各超参数组合共用同一个 WER 结果文件以便汇总。

在 unilm 仓库中快速上手的路径索引

  • 主文档:kosmos-2/fairseq/examples/hubert/README.md
  • 数据准备:kosmos-2/fairseq/examples/hubert/simple_kmeans/README.md(特征提取、聚类、打标签、合并分片、伪词典脚本均在 simple_kmeans/)
  • 预训练配置:config/pretrain/(base / large / xlarge 三个规模)
  • 微调配置:config/finetune/base_10h.yaml
  • 解码配置:config/decode/(viterbi / kenlm / fsqlm 三种模式)
  • 解码超参数定义:examples/speech_recognition/hydra/decoder.py

使用注意事项小结

  1. 帧率一致性是数据准备的生命线label_rate、聚类特征的帧率(MFCC 100Hz / HuBERT 特征 50Hz)与预训练配置必须严格对齐;
  2. normalize全链路一致:特征提取、预训练、微调与解码四个环节的task.normalize必须保持一致;
  3. 分片并行nshard/rank体系让特征提取与打标签可在多机多卡上并行,最后再合并.km文件;
  4. 显存不足优先调--max_chunk,而不是盲目减小batch_size
  5. 微调阶段默认冻结卷积特征提取器(feature_grad_mult: 0.0)并做前 10000 步的编码器冻结,这是稳定 CTC 微调的关键设置。

【免费下载链接】unilmLarge-scale Self-supervised Pre-training Across Tasks, Languages, and Modalities项目地址: https://gitcode.com/GitHub_Trending/un/unilm

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

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

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

立即咨询