从预训练到 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 | ~95M | LibriSpeech 960 hr | 无(预训练模型) | 直接加载使用 |
| HuBERT Large | ~316M | Libri-Light 60k hr | 无(预训练模型) | 直接加载使用 |
| HuBERT Extra Large | ~1B | Libri-Light 60k hr | 无(预训练模型) | 直接加载使用 |
| HuBERT Large | ~316M | Libri-Light 60k hr | LibriSpeech 960 hr | ASR 微调模型 |
| HuBERT Extra Large | ~1B | Libri-Light 60k hr | LibriSpeech 960 hr | ASR 微调模型 |
下载地址请以原文档表格中的官方链接为准。此外,仓库还提供了 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_pretraining,task.sample_rate: 16000,task.label_rate: ${model.label_rate}(与聚类帧率一致);task.max_sample_size/min_sample_size: 250000/32000,random_crop: true表示训练时随机裁剪音频段;task.normalize: false(必须与特征提取器一致,否则帧对齐错位);criterion._name: hubert,pred_masked_weight: 1.0、pred_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: 256、feature_grad_mult: 0.1(特征提取器梯度乘以 0.1 以稳定训练)、dropout/attention_dropout: 0.1、encoder_layerdrop: 0.05;optimization.max_update: 400000、lr: [0.0005];optimizer._name: adam,adam_betas: (0.9,0.98);lr_scheduler._name: polynomial_decay,warmup_updates: 32000;checkpoint.save_interval_updates: 25000、keep_interval_updates: 1、no_epoch_checkpoints: true(按 update 数保存);distributed_training.distributed_world_size: 32、nprocs_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: true、task.labels: ["ltr"]、task.single_target: true(微调阶段只预测一个目标,即字符序列);criterion._name: ctc、zero_infinity: true(CTC 损失中把无穷大梯度置零);model._name: hubert_ctc,w2v_path指向预训练权重;微调时feature_grad_mult: 0.0冻结卷积特征提取器;mask_prob: 0.75、mask_length: 10、mask_channel_prob: 0.5、mask_channel_length: 64表示继续做时间/通道掩码(SpecAugment 式增强);layerdrop: 0.1;freeze_finetune_updates: 10000:前 10000 步冻结除输出层外的编码器,dataset.validate_after_updates: ${model.freeze_finetune_updates}与其呼应;optimization.max_update: 25000、lr: [2e-5]、sentence_avg: true;lr_scheduler._name: tri_stage,warmup_steps: 8000、hold_steps: 0、decay_steps: 72000、final_lr_scale: 0.05;checkpoint.best_checkpoint_metric: wer(按验证集 WER 选最佳 checkpoint)。
解码(推理)
假设test.tsv与test.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/test(infer_viterbi.yaml中hydra.run.dir: ${common_eval.results_path}/viterbi,common_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: 500、beamthreshold: 100、lmweight: 2、wordscore: -1、silweight: 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: 500、beamthreshold: 25、lmweight: 2、wordscore: -1、silweight: 0。两种带 LM 的解码结果分别保存于decode/kenlm/test、decode/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
使用注意事项小结
- 帧率一致性是数据准备的生命线:
label_rate、聚类特征的帧率(MFCC 100Hz / HuBERT 特征 50Hz)与预训练配置必须严格对齐; normalize全链路一致:特征提取、预训练、微调与解码四个环节的task.normalize必须保持一致;- 分片并行:
nshard/rank体系让特征提取与打标签可在多机多卡上并行,最后再合并.km文件; - 显存不足优先调
--max_chunk,而不是盲目减小batch_size; - 微调阶段默认冻结卷积特征提取器(
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),仅供参考