Personal VAD 2.0 工程化:外部说话人嵌入与训练闭环

普通 VAD 只判断“有没有人声”,Personal VAD 还要判断“是不是目标说话人”。在端侧语音识别里,这个区别很关键:系统不仅要过滤静音和噪声,还要在多人说话场景下保留目标说话人的语音片段。

这篇文章记录一次把 Personal VAD 2.0 论文思路落到可训练工程骨架时的设计取舍。重点是模型接口、外部 speaker embedding、数据 manifest、训练闭环和标签对齐风险。

项目代码:https://github.com/fclearner/Personal-vad-2.0

任务定义

Personal VAD 可以建模为逐帧三分类任务:

  • 0:目标说话人语音;
  • 1:非目标说话人语音;
  • 2:非语音。

和普通 VAD 相比,PVAD 多了一个目标说话人的条件输入。这个条件通常来自 enrollment 音频,经过 speaker encoder 提取成 embedding,再输入 PVAD 主模型。

一个可落地的工程接口可以保持很窄:

1
2
logits = model(features, embedding)
logits, lengths = model(features, embedding, lengths, return_lengths=True)

其中:

  • features(batch, frames, 512)
  • embedding(batch, speaker_embedding_dim)(batch, frames, speaker_embedding_dim)
  • logits(batch, frames_out, 3)

embedding=None 时可以用零向量表示无 enrollment 条件,这样同一套模型能覆盖“有目标说话人条件”和“无条件 VAD 退化模式”。

模型结构

一个实用的 PVAD 骨架可以拆成四层:

  1. acoustic encoder:处理堆叠后的声学特征;
  2. speaker projection:把外部 speaker embedding 投影到 encoder 维度;
  3. speaker conditioning:用 speaker 信息调制 acoustic 表示;
  4. frame classifier:输出三类逐帧 logits。

工程上最容易出问题的地方不是分类头,而是 speaker 条件如何进入主干网络。

一种稳妥做法是让 speaker embedding 先经过投影层,再生成调制参数:

1
2
3
4
5
speaker embedding
-> speaker_embedding_proj
-> speaker_pre_net
-> FiLM / cosine conditioning
-> acoustic encoder states

这样做有两个好处:

  • embedding 维度不需要写死,64 维、192 维或其他外部向量都可以通过投影层接入;
  • speaker encoder 可以先冻结在 PVAD 训练图之外,降低第一阶段闭环难度。

如果一开始就把 speaker extractor、PVAD encoder 和训练目标端到端绑在一起,数据、显存、收敛和调试成本都会同时上升。更稳的顺序是先把外部 embedding 文件接进训练,再决定是否做联合训练或蒸馏。

外部 Speaker Embedding

外部 speaker embedding 的推荐接入方式是预提取,而不是在线抽取:

  1. enrollment 音频进入 speaker verification 模型;
  2. 导出固定维度 embedding,例如 CAM++ 常见的 192 维向量;
  3. 将 embedding 保存为 .npy.pt
  4. 训练 manifest 中引用对应 embedding 文件;
  5. PVAD 模型通过 speaker_embedding_dim 读取并投影。

例如:

1
model = Pvad2(speaker_embedding_dim=192)

这条路径把 speaker encoder 和 PVAD 主训练解耦。第一阶段只需要验证三件事:

  • embedding shape 是否稳定;
  • embedding 是否能和样本 speaker identity 对齐;
  • PVAD forward、loss、checkpoint 是否能稳定跑通。

等真实数据闭环完成后,再比较不同 speaker 条件策略:

  • 零 embedding;
  • 随机或已有 embedding;
  • CAM++ / 3D-Speaker embedding;
  • 外部 embedding 加轻量 adapter;
  • speaker encoder 蒸馏或联合训练。

数据 Manifest

训练数据最适合用 manifest 显式描述,而不是把路径规则写死在 dataset 里。JSONL 可以覆盖大多数场景:

1
2
{"id": "utt001", "features": "features/utt001.npy", "labels": "labels/utt001.npy", "embedding": "embeddings/spk001.npy"}
{"id": "utt002", "features": "features/utt002.npy", "labels": "labels/utt002.npy"}

字段约束:

  • features(frames, 512)
  • labels(frames,)
  • embedding:可选,(speaker_embedding_dim,)
  • 未提供 embedding 时,用零向量补齐。

路径相对 manifest 文件所在目录解析,这样数据目录迁移后不需要重写所有样本路径。

对 frame-level 任务,manifest 里最重要的不是路径本身,而是 feature frame 和 label frame 是否严格对齐。只要这里错一帧,训练 loss 仍然可能下降,但指标会失真。

训练闭环

最小训练闭环建议包含:

  • manifest dataset;
  • padding-aware collate;
  • CrossEntropyLoss(ignore_index=-100)
  • train / valid loop;
  • last.ptbest.pt checkpoint;
  • CPU synthetic smoke test;
  • 小样本 overfit test。

训练入口可以保持直接:

1
2
3
4
5
6
7
python train.py \
--train-manifest data/small_train.jsonl \
--valid-manifest data/small_valid.jsonl \
--speaker-embedding-dim 192 \
--epochs 50 \
--batch-size 4 \
--device cuda

在没有真实数据前,synthetic smoke test 只能证明代码路径能跑,不能证明模型有效。真正有价值的第一个实验应该是小样本 overfit:

  • loss 能稳定下降;
  • frame accuracy 明显升高;
  • padding 位置不参与 loss;
  • logits 不出现 NaN;
  • checkpoint 能保存和恢复;
  • enrollment 缺失样本不会破坏 batch。

Enrollment-less Augmentation

Personal VAD 2.0 里一个重要训练机制是 enrollment-less augmentation。直觉上,它让模型在没有目标说话人条件时退化成更接近普通 VAD 的行为。

一个可实现的版本是:

  1. 对部分 utterance 采样 dropout mask;
  2. 将这些样本的 speaker embedding 置零;
  3. 将这些样本里的非目标说话人语音标签 1 改成目标语音标签 0
  4. padding label -100 保持不变。

这一步必须同时改 embedding 和 label。如果只把 embedding 置零,不改 label,模型会在“无目标说话人信息”的条件下被要求区分目标与非目标,说法上就不一致。

Subsampling 与标签对齐

PVAD 很容易在 subsampling 上踩坑。假设模型支持两种入口:

  • linear:特征已经按预期堆叠或下采样后送入模型;
  • conv2d3:模型内部做卷积子采样。

如果 logits 长度和 labels 长度不同,简单截断或补 ignore index 只能让代码继续跑,不等于语义正确。更严谨的做法是为每一种 subsampling 明确定义 label 下采样策略,例如:

  • 取中心帧标签;
  • 窗口多数投票;
  • 目标语音优先;
  • 非语音优先;
  • 混合帧标记为 ignore。

策略选择会直接影响 precision、recall 和误报行为,不能隐藏在 collate 或 loss 的容错逻辑里。

实验路线

比起继续堆模型结构,更值得优先跑通的是数据和评价闭环:

  1. 准备 5 到 20 条真实样本,覆盖目标说话人、非目标说话人、非语音、长短句和 enrollment 缺失;
  2. 跑小样本 overfit,确认 loss、accuracy、padding 和 checkpoint;
  3. 固定 PVAD 主干,比较零 embedding、外部 embedding 和轻量 adapter;
  4. 加入真实验证集,拆分 target speech recall、non-target false accept、non-speech false alarm;
  5. 再考虑 streaming cache、ONNX、量化和端侧延迟。

工程判断上,第一阶段的目标不是复现论文最终指标,而是证明这个系统具备可训练、可评估、可替换 speaker embedding 的闭环。

小结

PVAD 的难点不只是模型结构,而是 speaker 条件、frame label、训练目标和数据组织必须同时对齐。一个合理的工程落点是:

  • speaker embedding 维度配置化;
  • 外部 embedding 先离线接入;
  • manifest 显式记录 feature、label 和 embedding;
  • enrollment-less augmentation 成对修改 embedding 与 label;
  • subsampling 必须配套定义 label 对齐;
  • smoke test 只验证可运行,小样本 overfit 才开始验证可学习。

把这些边界先定清楚,后续再替换 speaker encoder、加入 adapter 或做端侧优化,才不会把模型调试和数据问题混在一起。