Instructions to use czyhust/finetune_spk-sortformer with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- NeMo
How to use czyhust/finetune_spk-sortformer with NeMo:
# tag did not correspond to a valid NeMo domain.
- Notebooks
- Google Colab
- Kaggle
| # Sortformer V2:双流 Speaker-Aware 说话人日志 | |
| ## 一、整体思路 | |
| 在原始 Sortformer 的 18 层 Transformer Encoder 基础上,引入一条与主特征流(H1)并行的 **speaker 特征流(H2)**。两条流在每一层都做 self-attention 和互相之间的 cross-attention,让说话人表征与 diarization 表征持续交互。spk 分支有自己的输出头,用 chunk 级别的 PIL loss 单独监督。 | |
| spk encoder 的表征**只在第一层之前作为输入**,此后 H2 由每层的 H2 分支自己演化,不再重新注入。 | |
| --- | |
| ## 二、网络结构 | |
| ``` | |
| 原始音频 (16kHz, 最长300s) | |
| │ | |
| ├──────────────────────────────┐ | |
| │ │ | |
| ▼ ▼ | |
| Mel Spectrogram CAM++ Speaker Encoder(冻结backbone) | |
| (128维, 10ms) Fbank → 卷积backbone → 帧级特征 | |
| │ → Conv下采样 → 192维@80ms | |
| ▼ │ | |
| FastConformer (17层, 512维) │ | |
| 8x下采样 → 512维@80ms │ | |
| │ │ | |
| ▼ ▼ | |
| Linear 512→192 H2 初始表征 (B,T,192) | |
| │ │ | |
| ▼ ▼ | |
| ┌──────────────────────────────────────────┐ | |
| │ DualStream Transformer (18层, 192维) │ | |
| │ │ | |
| │ 每层 Block: │ | |
| │ H1: self-attn → H1/H2 互attn → FFN │ | |
| │ H2: self-attn → H1/H2 互attn → FFN │ | |
| │ │ | |
| └──────────────────────────────────────────┘ | |
| │ │ | |
| ▼ ▼ | |
| 主 Head (预训练权重) spk Head (随机初始化) | |
| │ │ | |
| ▼ ▼ | |
| ATS+PIL loss chunk级PIL loss | |
| ``` | |
| ### 每层 DualStreamTransformerBlock 细节 | |
| ``` | |
| 输入: H1 (B,T,192), H2 (B,T,192) | |
| 1. H1 self-attention (first_sub_layer, 预训练权重) | |
| H1 = LN1(H1 + MHA(H1,H1,H1)) | |
| 2. H2 self-attention (h2_self_attn, 随机初始化) | |
| H2 = LN1(H2 + MHA(H2,H2,H2)) | |
| 3. 互 attention(无 gate, 随机初始化) | |
| H1 = crossLN1(H1 + MHA(H1→H2)) # H1 attend H2 | |
| H2 = crossLN2(H2 + MHA(H2→H1)) # H2 attend H1 | |
| 4. FFN | |
| H1 = LN2(H1 + FFN(H1)) (second_sub_layer, 预训练权重) | |
| H2 = LN2(H2 + FFN(H2)) (h2_ffn, 随机初始化) | |
| ``` | |
| 所有 attention 均为 **8 头**(head_dim = 192/8 = 24)。 | |
| **关键设计:H1 的参数名与原始 TransformerEncoderBlock 完全一致**(`first_sub_layer`、`layer_norm_1`、`second_sub_layer`、`layer_norm_2`),因此原始 Sortformer checkpoint 可以无缝 warm-start 到 H1 流,新增的 H2 流和互 attention 保持随机初始化。 | |
| ### 权重来源总结 | |
| | 模块 | 权重来源 | | |
| |------|---------| | |
| | FastConformer encoder | 预训练 | | |
| | H1 self-attn + FFN + LN | 预训练(同名加载) | | |
| | H2 self-attn + FFN + LN | 随机初始化 | | |
| | H1↔H2 互 attention ×2 + LN | 随机初始化 | | |
| | 主输出 head | 预训练 | | |
| | spk 输出 head | 随机初始化 | | |
| | CAM++ 卷积 backbone | 预训练,**永远冻结** | | |
| | CAM++ 下采样 conv (proj) | 随机初始化,**可训练** | | |
| --- | |
| ## 三、Speaker Encoder(CAM++) | |
| ### 提取流程 | |
| ``` | |
| 原始音频 (最长300s) | |
| │ | |
| ▼ | |
| 切分为无重叠窗口:每窗口 2 秒(chunk_dur_sec=2.0, chunk_stride_sec=2.0) | |
| │ | |
| ▼ 每窗口独立处理(encode_batch_size=16 批量并行): | |
| Kaldi Fbank (80维, 10ms) → CAM++ 卷积backbone(冻结) | |
| → 帧级特征 (512维, 20ms),每帧编码周围大范围上下文 | |
| │ | |
| ▼ | |
| Conv1d(512→192, kernel=12, stride=4, padding=4) ← 可训练下采样层 | |
| → 每个输出帧融合 240ms(12帧×20ms)上下文 | |
| → 192维 @80ms 帧率 | |
| │ | |
| ▼ | |
| 窗口按时间顺序拼接 → (B, T_diar, 192),与 sortformer 帧率严格对齐 | |
| ``` | |
| ### 设计说明 | |
| - **不做池化**:CAM++ 原始的 utterance-level TSTP 全局池化不适用于逐帧场景。改为可学习卷积下采样,让 H2 流自己的 self-attention 做时间整合。 | |
| - **kernel=12(240ms)**:每个 H2 帧融合 240ms 的 CAM++ 帧级特征,比输出帧率(80ms)宽 3 倍,提供更丰富的局部上下文。 | |
| - **proj 可训练**:下采样卷积是新参数(~98K),随训练学习如何把帧级特征映射到 H2 空间;CAM++ backbone 冻结。 | |
| - **2s 窗口**:与 CAM++ 训练时长一致,窗口间无重叠,显存可控。 | |
| --- | |
| ## 四、训练算法 | |
| ### 双损失 | |
| ``` | |
| 主分支: loss1 = 0.5 × ATS_loss + 0.5 × PIL_loss (原始 Sortformer 损失) | |
| spk分支: loss2 = chunk级 PIL loss | |
| 总损失: loss = loss1 + spk_pil_weight × loss2 (spk_pil_weight=1.0) | |
| ``` | |
| ### Chunk 级 PIL Loss(spk 分支) | |
| 主分支沿用原始做法:整段拼接后一次匈牙利排列匹配。spk 分支不同——按 `chunk_len=188` 帧(15.04秒)切块: | |
| ``` | |
| spk_preds / targets 按 188 帧切成 n 个 chunk | |
| 每个 chunk 内部独立做 PIL 排列匹配 → 各自算 BCE | |
| 最后所有 chunk 的 loss 取平均 | |
| ``` | |
| 每个 chunk 是独立的排列问题,模型被强制在短窗口内做说话人区分,与流式推理场景一致。 | |
| ### 两种训练模式 | |
| | 模式 | 配置 | 可训练参数 | 说明 | | |
| |------|------|-----------|------| | |
| | 只训新增 | `freeze_base_model: true` | ~13.4M | H2流 + 互attn + spk head + proj | | |
| | 全量训练 | `freeze_base_model: false` | ~131M | 所有参数(除 CAM++ backbone) | | |
| 两种模式下 **CAM++ 卷积 backbone 均冻结**(proj 下采样层可训练)。 | |
| ### 学习率分组(全量模式) | |
| | 参数组 | 学习率 | | |
| |--------|--------| | |
| | 原始 Sortformer 参数 | `lr=2e-5` | | |
| | 新增 H2 流 + 互attn + spk head + proj | `speaker_lr=1e-4` | | |
| | CAM++ backbone | 冻结 | | |
| --- | |
| ## 五、流式模式 | |
| - 流式推理时每 chunk 处理 `[spkcache | fifo | chunk]` 拼接序列 | |
| - `spkcache_spk`/`fifo_spk` 同步缓存对应帧的 speaker embedding,压缩时用相同的 `topk_indices` gather,保证 H2 与 H1 每帧时刻严格对齐 | |
| - spk 分支(H2 → spk head)只用于训练监督,推理时只用主分支输出 | |
| --- | |
| ## 六、超参数 | |
| ### 新增超参数 | |
| | 参数 | 值 | 说明 | | |
| |------|-----|------| | |
| | `use_dual_stream` | true | 启用双流架构 | | |
| | `spk_pil_weight` | 1.0 | spk 分支 chunk PIL loss 权重 | | |
| | `freeze_base_model` | false/true | 只训新增 / 全量训练 | | |
| | `speaker_lr` | 1e-4 | 新增参数学习率(全量模式) | | |
| | `speaker_encoder.chunk_dur_sec` | 2.0 | spk encoder 窗口长度(无重叠) | | |
| | `speaker_encoder.chunk_stride_sec` | 2.0 | 窗口步长(=窗口长,无重叠) | | |
| | `speaker_encoder.encode_batch_size` | 24 | 多窗口批量并行 | | |
| | `speaker_encoder.downsample_kernel` | 12 | 下采样卷积核(240ms 上下文) | | |
| | `speaker_encoder.checkpoint_path` | null | 权重从 warm-start nemo 加载,不硬编码 | | |
| ### 沿用超参数 | |
| | 参数 | 值 | | |
| |------|-----| | |
| | 采样率 | 16000 | | |
| | 最大说话人数 | 8 | | |
| | 最大音频时长 | 300s | | |
| | batch size | 1 | | |
| | 优化器 | AdamW (β=[0.9,0.98]) | | |
| | 基础学习率 | 2e-5 | | |
| | 权重衰减 | 2e-3 | | |
| | 最大 epoch | 10 | | |
| | chunk_len | 188 (15.04s) | | |
| | spkcache_len | 376 | | |
| | 因果注意力概率 | 0.5 (rc=7) | | |
| | dropout (attn/ffn) | 0.5 | | |
| --- | |
| ## 7. 文件结构 | |
| ``` | |
| fintune_sortformer_v2/ | |
| ├── docs/ARCHITECTURE.md ← 本文档 | |
| ├── checkpoints/ | |
| │ └── dual_stream_init_8spk.nemo ← 初始化nemo(含全部权重) | |
| ├── src/ | |
| │ ├── finetune_pipeline/ | |
| │ │ ├── conf/ | |
| │ │ │ └── streaming_sortformer_diarizer_8spk-v1-finetune.yaml | |
| │ │ │ ← use_dual_stream, spk_pil_weight, freeze_base_model, speaker_lr | |
| │ │ └── scripts/ | |
| │ │ ├── init_dual_stream_nemo.py ← 生成含spk encoder权重的nemo | |
| │ │ └── ckpt2nemo.py ← ckpt转nemo(含全部新权重) | |
| │ └── third_party/nemo/collections/asr/ | |
| │ ├── models/sortformer_diar_models.py ← 双流接入、spk head、chunk PIL loss、两种训练模式 | |
| │ └── modules/ | |
| │ ├── speaker_encoder.py ← 冻结CAM++ backbone + 可训练Conv下采样 | |
| │ ├── sortformer_modules.py ← spk cache 追踪(spkcache_spk/fifo_spk) | |
| │ └── transformer/ | |
| │ ├── dual_stream_transformer.py ← 双流block + encoder | |
| │ └── transformer_encoders.py ← 原始单流(use_dual_stream=false时用) | |
| └── wespeaker/ ← CAM++ 模型 | |
| ``` | |
| --- | |
| ## 8. 初始化与训练流程 | |
| ```bash | |
| # 1. 生成初始化 nemo(H1=预训练, H2/互attn=随机, CAM++=冻结, proj=随机) | |
| python src/finetune_pipeline/scripts/init_dual_stream_nemo.py \ | |
| --pretrained /path/to/original_sortformer.nemo \ | |
| --output /path/to/output_dual_stream.nemo | |
| # 2. 训练时 warm-start 这个 nemo | |
| bash run_train.sh --init_nemo_path /path/to/output_dual_stream.nemo | |
| # 3. 训练后 ckpt 转 nemo(ckpt 已含全部权重) | |
| python src/finetune_pipeline/scripts/ckpt2nemo.py \ | |
| /path/to/checkpoint.ckpt /path/to/output.nemo | |
| ``` | |