【SSL】HuBERT

发布时间:2026/8/8 7:23:25
【SSL】HuBERT 前言wav2vec 2.0 建立了大规模自监督预训练加少量标注数据微调的范式但它的训练目标是和模型一起学出来的量化目标随训练不断变化。还有码本坍缩和模块复杂等缺点。于是 Meta原 Facebook AI ResearchFAIR语音团队又提出了 HuBERTHidden-unitBERT2021。它先用 k-means 给每一帧聚类打好离散标签训练期间标签固定做 BERT 式掩码分类。HuBERT 和 wav2vec 2.0 架构一样只是训练目标不同没有量化码本相对简单效果在不同尺寸的训练数据上持平或略好。一、语音 BERT 的三个障碍没有词表文本天然离散语音是连续波形不存在词表帧不是单元声音单元音素长约 50~100ms跨多帧且相邻帧相关没有边界预训练时不知道音素从哪帧开始到哪帧结束没有分词器。wav2vec 2.0 通过自造量化目标不做分类进行对比学习。但引入了前言中的一些缺点。HuBERT 对 MFCC 特征进行 k-means 聚类给每一帧打类别标签。聚出来的类被称为hidden units隐单元是粗糙的发音类别。词表有 k 个单元每帧都有标签可以类似交叉熵分类训练。二、k-means 聚类2.1 第一轮聚类LibriSpeech 960h 音频 → 提取 MFCC(13 维 Δ ΔΔ 39 维) → k-means 聚类,k 100 → 每一帧得到一个标签 h_t ∈ {0, 1, ..., 99}通过聚类固定了训练标签。2.2 训练的一致性相比 wav2vec 2.0 的双码本 102,400 类通过 MFCC k-means 聚 100 类伪标签这个标签类别非常粗糙。HuBERT 论文指出标签的一致性比正确性更重要。只要 k-means 是确定的映射相似的声音总是拿到相同的标签哪怕类别本身划分得很粗糙掩码预测任务也是 work 的。三、训练3.1 架构与 wav2vec 2.0 几乎相同CNN 特征编码器同款 7 层一维卷积320 倍下采样输出 50Hz × 512 维感受野 25ms、帧移 20ms逐层推导可见 wav2vec 2.0 博客Transformer 上下文网络双向注意力同款卷积位置嵌入没有量化模块换成了分类头投影 单元 embedding 表。三档配置BaseLargeX-LargeTransformer 层数122448隐藏维76810241280参数量~95M~317M~1B预训练数据LS-960960 小时Libri-Light 6 万小时Libri-Light 6 万小时3.2 掩码掩码策略与 wav2vec 2.0 同风格。以p 8 % p 8\%p8%的概率采样掩码起点每个起点向后连续掩l 10 l 10l10帧允许重叠。某帧被掩当且仅当它自己或它前面 9 帧中至少一个是起点P ( 被掩 ) 1 − ( 1 − 0.08 ) 10 1 − 0.92 10 ≈ 57 % P(\text{被掩}) 1 - (1-0.08)^{10} 1 - 0.92^{10} \approx 57\%P(被掩)1−(1−0.08)101−0.9210≈57%比 BERT 的 15% 多不少语音帧冗余度高20ms 一帧而音素长 50~100ms相邻帧强相关掩太少任务失去难度。被掩位置替换为一个共享的可学习向量768 维全batch/训练过程共用这一个。3.3 分类标签有 k 个预测将 Transformer 在被掩位置 t 输出的向量通过计算相似度得到 k 个标签上的概率分布。p ( c ∣ X ~ , t ) exp ⁡ ( sim ( A o t , e c ) / τ ) ∑ c ′ 1 k exp ⁡ ( sim ( A o t , e c ′ ) / τ ) , τ 0.1 c 0 , … , k − 1 p(c \mid \tilde{X}, t) \frac{\exp\big(\text{sim}(A\, o_t,\ \mathbf{e}_c)/\tau\big)}{\sum_{c1}^{k} \exp\big(\text{sim}(A\, o_t,\ \mathbf{e}_{c})/\tau\big)}, \qquad \tau 0.1c0,\dots,k-1p(c∣X~,t)∑c′1k​exp(sim(Aot​,ec′​)/τ)exp(sim(Aot​,ec​)/τ)​,τ0.1c0,…,k−1其中X ~ \tilde XX~是掩码后的输入约一半的帧被换成了下划线模型看不到这些位置的真实声音。o t o_tot​是 Transformer 在位置 t 的输出768 维。A AA是投影矩阵768→256A o t A\,o_tAot​把猜测变换到和选项 embedding 同一个空间。e c \mathbf{e}_cec​是第 c 个选项的标准画像注意e c \mathbf{e}_cec​是可学习向量随机初始化的。sim ( ⋅ , ⋅ ) \text{sim}(\cdot,\cdot)sim(⋅,⋅)是余弦相似度打分。τ \tauτ是温度0.1 是作者遍历后选的超参数余弦分数属于[−1, 1]除以 0.1 可以将打分差距拉开。softmax 之后得到了属于每个类别的概率。我在很长一段时间里以为e c \mathbf{e}_cec​肯定会与聚类质心有某种显示推导关系看过代码后发现基本没关系e c \mathbf{e}_cec​就是数据训练得到的。首先初次聚类质心 39 维维数就不匹配MFCC 质心和 768 维输出在两个不同空间。 其次即使投影到相同空间聚类的标签很粗糙也可能使模型越学越错不如从头训练。k-means 的质心39 维向量只在聚类打标签时用一次。3.4 交叉熵损失CE t − log ⁡ p ( h t ∣ X ~ , t ) , L m ∑ t ∈ M CE t \text{CE}_t -\log p(h_t \mid \tilde X, t), \qquad \mathcal{L}_m \sum_{t \in M} \text{CE}_tCEt​−logp(ht​∣X~,t),Lm​t∈M∑​CEt​举个例子# k2,某个被掩位置 tlogitscos_sim(A o_t,E)/0.1# E: (2, 256) —— 两个 e_c 堆成的矩阵,全部参与打分# logits: (2,) 比如 [2.0, 7.0]targeth_t# h_t 是整数比如 1lossF.cross_entropy(logits,target)# 内部:softmax([2,7]) [0.007, 0.993] → loss -log(第 target 格) -log(0.993) ≈ 0.007对照 wav2vec 2.0 InfoNCE 是101 个候选里认出正样本要采负样本、要防塌缩本质是 101 分类HuBERT 标签固定k 分类。3.5 loss 只算掩码位置总损失形式上是掩码位置与未掩位置两部分的加权L α ∑ t ∈ M CE ( h t ) ( 1 − α ) ∑ t ∉ M CE ( h t ) \mathcal{L} \alpha \sum_{t \in M} \text{CE}(h_t) \;\; (1-\alpha) \sum_{t \notin M} \text{CE}(h_t)Lαt∈M∑​CE(ht​)(1−α)t∈/M∑​CE(ht​)论文消融的结论是α 1只算掩码位置最好而且对坏标签最鲁棒。可以理解成未掩码的位置模型直接学 k-means 的聚类方式对模型学习声音单元的时序结构没有任何益处。3.6 三轮流程第 1 轮:MFCC(39 维) ── k-means(k100) ──► 标签 v1 ──► 训 HuBERT-Base(第 1 版) │ 第 2 轮:取第 1 版 Base 的【第 6 层】特征 ── k-means(k500) ──► 标签 v2 ──► 重训 Base(最终版) │ 第 3 轮:取第 2 版 Base 的【第 9 层】特征 ── k-means(k500) ──► 标签 v3 ──► 训 Large / X-Large第一轮训完HuBERT 中间层特征已经好于 MFCC。它们编码了上下文信息、对说话人和信道更不敏感、和音素的对齐度大幅提升。用更好的特征聚类 → 更好的标签 → 更好的模型 → 迭代。每一轮内部标签仍是固定的。第 6 层、第 9 层是作者通过一些客观指标phone purity/cluster purity/PNMI 逐层评估挑选出来的。四、对比 wav2vec 2.04.1 对照表wav2vec 2.0HuBERT架构CNN 双向 Transformer相同目标从哪来PQ 码本 Gumbel与模型联合学在线离线 k-means训练期间固定目标会漂移吗会和模型一起变不会塌缩风险有死码字零梯度、自增强退化解无防塌机关diversity loss 可缓解不需要损失InfoNCE对比1 正 100 同句负交叉熵k 分类负样本需要精心设计同句采样不需要可导离散化Gumbel-Softmax straight-through不需要单元质量提升靠端到端梯度靠重聚类注意e c \mathbf{e}_cec​虽然是可学习的但每个类别由固定标签决定不会目标漂移。4.2 目标空间 k100~500 vs 10 万wav2vec 2.0 的码本容量 102,400 和 HuBERT 的 k100~500感觉 HuBERT 粗糙了许多担心效果会很差下面讨论这样设计的合理性。首先 102,400 是两本码本各 320 条的笛卡尔积理论容量实际利用率因塌缩趋势远达不到理论值。HuBERT 的 k 是分类的类别数语义是发音类别。英语音素约 40~50 个k 取 100 略细于音素k 取 500 约等于音素加语境变体类似传统 ASR 里 triphone 状态聚类。正如 w2v2 的双码本是服务于对比学习聚类个数是服务于分类任务的如果类别非常大会造成分类样本变少聚类质量下降监督信号稀疏。效果上同参数量 HuBERT 比 wav2vec 2.0 相当或略好X-Large约 1B 参数差距会更大一点。五、实践5.1 简单尝试自包含脚本无 GPU 也能跑模型 ~360MB国内先export HF_ENDPOINThttps://hf-mirror.com# probe_hubert.py —— 验证 HuBERT 的帧率/维度链,与 wav2vec 2.0 对照importtorchfromtransformersimportHubertModel modelHubertModel.from_pretrained(facebook/hubert-base-ls960)model.eval()sr,seconds16000,3wavtorch.randn(1,sr*seconds)withtorch.no_grad():outmodel(wav,output_hidden_statesTrue)cout.last_hidden_state hsout.hidden_statesprint(fc (context):{tuple(c.shape)})# (1, 149, 768) ← 与 wav2vec2-base 完全一致print(fhidden_states:{len(hs)}个)# 13 投影后输入 12 层print(f实测帧率:{c.shape[1]/seconds:.1f}Hz)# ≈ 50 HzHF 里HubertModel与Wav2Vec2Model接口几乎一致连代码都大量共享。5.2 迷你 hidden units对特征跑 k-means感受离散单元# mini_units.py —— 用第 6 层特征聚一版微缩 hidden unitsimporttorchfromtransformersimportHubertModelfromsklearn.clusterimportKMeans modelHubertModel.from_pretrained(facebook/hubert-base-ls960)model.eval()# import torchaudio# wav, sr torchaudio.load(your.wav)# wav torchaudio.functional.resample(wav, sr, 16000)[:1, :16000*3]wavtorch.randn(1,16000*3)withtorch.no_grad():outmodel(wav,output_hidden_statesTrue)featsout.hidden_states[6][0].numpy()# 第 6 层特征(HuBERT 第二轮聚类)labelsKMeans(n_clusters5,n_init10,random_state0).fit_predict(feats)print(.join(str(l)forlinlabels))# 真实语音上会看到明显的分段:如 000003333311111444...(同一发音段落标签连续)# 这串数字,就是一版微缩的 hidden units总结HuBERT 相当于语音版 BERT用 k-means 聚类造标签掩码后预测被掩帧的离散单元标签交叉熵分类。目标离线确定wav2vec 2.0 的负采样、Gumbel、diversity loss、防塌缩机关都不需要。利用标签的一致性迭代提纯标签精度模型性能逐轮上涨。离线固定目标的方式被 WavLM、w2v-BERT 等继承。