☰
PaddleSpeech 说话人日志(Speaker Diarization)实战:基于 AMI 语料库的 ECAPA-TDNN + 谱聚类方案(sd0 示例全解析)
2026/9/25 4:12:55 网站建设 项目流程
  • 人工智能
  • 语音
  • 音频
  • NLP
  • 媒体生成

【免费下载链接】PaddleSpeech

Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.

项目地址:https://gitcode.com/paddlepaddle/PaddleSpeech
点击查看免费下载

本文是 PaddleSpeech 开源语音工具包中examples/ami示例的完整技术指南,围绕 AMI 会议语料库上的说话人日志(Speaker Diarization)任务展开:以预训练 ECAPA-TDNN x-vector 提取说话人嵌入,再通过 AHC / 谱聚类(Spectral Clustering)完成"谁在何时说话"的自动标注,并以 Diarization Error Rate(DER)作为统一评测指标。读完本文,你将掌握从 AMI 数据下载、标注解析、子段切分、嵌入提取、聚类后端选择到 DER 评测的完整流水线,并能直接复现该示例在 HeadsetMix 录音下的最优结果。

一、任务与数据集背景

1.1 说话人日志任务是什么

说话人日志(Speaker Diarization)要解决的核心问题是:给定一段包含多人对话的录音,自动回答"谁在什么时间段说话"。它与说话人识别(Speaker Verification)不同——后者假设说话人已知,前者需要在不知道说话人身份的情况下,把音频按说话人切分成不同片段并归簇。该任务广泛应用于会议纪要、客服质检、影视字幕、语音检索等场景。

在 PaddleSpeech 中,该任务由examples/ami/sd0示例完整承载。主 README(examples/ami/README.md)对其定位只有一句话:

sd0 - speaker diarization by AHC, SC base on embeddings

即:基于嵌入向量(embeddings)的说话人日志,聚类后端支持 AHC(凝聚层次聚类)与 SC(谱聚类)两种方案。

1.2 AMI 会议语料库简介

AMI(Augmented Multi-party Interaction)会议语料库由爱丁堡大学等机构采集,是说话人日志领域最经典的评测基准之一。官方对其的描述是:

  • 包含100 小时的会议录音;
  • 录音使用多种信号源并同步到统一时间轴,包括近讲麦克风(close-talking)与远场麦克风(far-field)、单人与全景摄像机、幻灯片投影仪与电子白板的输出;
  • 会议期间与会者还使用未同步的笔记录书写内容;
  • 会议用英语录制,分别在声学特性不同的三个房间完成,参与者大多为非英语母语者。

在本示例中,仅使用其中一种麦克风信号:Headset mix(头戴式麦克风混音)。全部数据下载约需要10GB磁盘空间。

说明:本文以下内容均基于 PaddleSpeech 仓库中examples/ami/sd0目录下的实际脚本与配置文件展开,读者可按 examples/ami/sd0/README.md 中的说明逐步复现。

二、整体架构:一条从音频到 DER 的完整流水线

从脚本结构与源码调用链看,sd0示例的说话人日志流水线可划分为四个阶段:

AMI 原始数据 + 人工标注(XML) │ ▼ [阶段0] 数据准备 ami_prepare.py │ 解析 segments/*.xml → 参考 RTTM │ 合并/切分 → 子段 JSON 元数据 ▼ [阶段1] 嵌入提取 compute_embdding.py │ ECAPA-TDNN 前向 → 每个子段 192 维 x-vector │ 按录音保存为 *.emb_stat.pkl ▼ [阶段2] 聚类 + 超参调优 experiment.py │ dev 集调 p-value / 阈值 / n_neighbors │ 用最优超参在 dev & eval 上聚类 → 系统 RTTM ▼ [阶段3] DER 评测 utils/DER.py 参考 RTTM vs 系统 RTTM → DER

入口脚本 run.sh 只做两件事:下载预训练模型(stage 1)、调用local/process.sh(stage 2)执行完整的处理与评测流程。三个 Python 脚本分别对应阶段 0~2。

三、数据准备:从 AMI 官网到本地目录

3.1 下载标注文件与音频信号

按照 examples/ami/sd0/README.md 的说明,需要准备两份数据:

(1)人工标注(Annotations):包含会议的分段(segments)XML 标注,用于生成参考 RTTM(Oracle 标注)。下载命令:

## download annotations wget http://groups.inf.ed.ac.uk/ami/AMICorpusAnnotations/ami_public_manual_1.6.2.zip && unzip ami_public_manual_1.6.2.zip

解压后得到ami_public_manual_1.6.2目录,其中segments/下每个录音对应若干*.segments.xml文件(每个说话人一个)。

(2)音频信号(Signals):需要按 AMI 官网下载指引使用 chooser 逐项选择:

  1. 选择一个或多个 AMI 会议,会议 ID 请参照 local/ami_splits.py 中定义的 dev/eval 会议集合;
  2. 媒体流只需选择Headset mix。

3.2 目录约定与一键启动

准备好数据后,直接运行:

./run.sh --data_folder ./amicorpus --manual_annot_folder ./ami_public_manual_1.6.2

其中./amicorpus为音频数据根目录(amicorpus/下按会议 ID 组织,如ES2002/audio/ES2002.Mix-Headset.wav),./ami_public_manual_1.6.2为标注根目录。也可直接修改 run.sh 顶部的默认变量:

TARGET_DIR=/home/dataset/AMI data_folder=${TARGET_DIR}/amicorpus manual_annot_folder=${TARGET_DIR}/ami_public_manual_1.6.2 save_folder=./save pretraind_model_dir=${save_folder}/sv0_ecapa_tdnn_voxceleb12_ckpt_0_1_1/model conf_path=conf/ecapa_tdnn.yaml device=gpu

3.3 分两个 stage 执行

run.sh 通过--stage控制执行粒度(默认从 stage 0 开始):

  • stage 1:下载并解压预训练说话人嵌入模型sv0_ecapa_tdnn_voxceleb12_ckpt_0_1_1.tar.gz到save/目录;
  • stage 2:调用bash ./local/process.sh <data_folder> <manual_annot_folder> <save_folder> <pretraind_model_dir> <conf_path> <device>,依次执行数据准备、嵌入提取与聚类评测。

path.sh 负责初始化环境:将仓库根目录MAIN_ROOT及utils加入PATH,设置PYTHONPATH、LC_ALL=C与 UTF-8 编码,保证脚本在各环境下可复现。

四、阶段 0:数据准备源码剖析(ami_prepare.py)

local/ami_prepare.py 完成两件事:生成参考 RTTM、生成子段(sub-segment)JSON 元数据。

4.1 从 XML 标注生成参考 RTTM

RTTM(Rich Transcription Time Markup)是说话人日志的标准标注格式,每行形如:

SPEAKER ES2008c 0 37.880 0.590 <NA> <NA> ES2008c.A_PM <NA> <NA> SPKR-INFO ES2008c 0 <NA> <NA> <NA> unknown ES2008c.A_PM <NA> <NA>
  • SPEAKER行记录说话区间:录音 ID、起始时间、时长、说话人 ID;
  • SPKR-INFO行记录录音中出现的说话人列表,供 Oracle 说话人数统计使用。

prepare_segs_for_RTTM遍历segments/<rec_id>.*.segments.xml,解析 XML 中每个<segment>元素的transcriber_start/transcriber_end属性,结合说话人字母(A/B/C…)拼出rec_id.speaker形式的说话人 ID,按开始时间排序后由get_RTTM_per_rec写出。该函数还会过滤掉transcriber_start > transcriber_end的错误标注行。最终每个 split 生成一个汇总参考文件,例如fullref_ami_dev.rttm。

4.2 段合并与子段切分

说话人日志需要以固定粒度的子段为单位提取嵌入,因此prepare_metadata对每个录音的说话区间做两步处理:

  1. merge_rttm_intervals:把时间上重叠的相邻说话区间合并为一个区间(重叠部分标记为overlap);
  2. get_subsegments:将时长大于max_subseg_dur(默认 3.0 秒)的长段,按shift = max_subseg_dur - overlap(默认 1.5 秒重叠)滑动切分为约 3 秒的子段;不足 3 秒的段保持原样。

每个子段被记录为rec_id_start_end形式的子段 ID,并计算起止采样点(SAMPLERATE = 16000),写入 JSON:

{ "ES2008c_37.88_38.42": { "wav": { "file": "/path/to/amicorpus/ES2008c/audio/ES2008c.Mix-Headset.wav", "duration": 0.54, "start": 606080, "stop": 614720 } } }

注:当mic_type为Array1(8 麦阵列)时,JSON 中改用"files"键存放 8 个通道的文件路径列表。

最终输出三份 JSON:ami_train.Mix-Headset.subsegs.json、ami_dev.Mix-Headset.subsegs.json、ami_eval.Mix-Headset.subsegs.json。skip()函数通过检查三个 JSON 与选项文件opt_ami_prepare.<mic>.pkl是否已存在且一致,实现阶段跳过的幂等性。

4.3 数据集划分(ami_splits.py)

local/ami_splits.py 提供三种官方划分,配置项split_type决定使用哪种:

split_type含义会议数(train/dev/test)
scenario_only仅场景化会议(ES/IS/TS)25 / 5 / 5
full_corpus全部会议41 / 11 / 6
full_corpus_asr(默认)与 ASR 一致的全量划分,dev 中剔除与 train 说话人重叠的会议45 / 9 / 4

同时,skip_TNO=True会跳过 TNO(TS 开头)会议——它们属于非场景化录制,说话人数量波动更大(3~5 人)。

五、阶段 1:ECAPA-TDNN 嵌入提取(compute_embdding.py)

local/compute_embdding.py 负责为 dev / eval 每个录音的每个子段提取 192 维说话人嵌入。

5.1 模型加载与推理

代码调用链如下:

  1. 通过paddle.set_device(args.device)设置 GPU/CPU;
  2. 构建 ECAPA-TDNN 骨干网络:EcapaTdnn(**config.model)(定义在 paddlespeech/vector/models/ecapa_tdnn.py);
  3. 用SpeakerIdetification包装成说话人识别模型;
  4. 加载预训练权重:paddle.load(os.path.join(args.load_checkpoint, 'model.pdparams'))后model.set_state_dict(state_dict);
  5. model.eval()进入推理模式,前向model.backbone(feats, lengths)得到(N, emb_size)的嵌入。

5.2 逐录音提取与保存

对每个录音:

  • 从全量 JSON 中切出该录音的子集元数据(prepare_subset_json);
  • 用JSONDataset加载 mel 频谱特征(80 维 fbank、25ms 窗、10ms 帧移),DataLoader按batch_size=16批量推理,collate 时做均值归一化(batch_feature_normalize,mean_norm=True, std_norm=False);
  • 将嵌入与子段 ID 组装成EmbeddingMeta(segset+stats)对象,序列化为<rec_id>.Mix-Headset.emb_stat.pkl;
  • 若该文件已存在则跳过(幂等)。

嵌入文件路径约定为<save_folder>/emb/AMI_<dev|eval>/<rec_id>.Mix-Headset.emb_stat.pkl,与配置中的embedding_dir: emb对应。

六、配置文件全解:ecapa_tdnn.yaml

conf/ecapa_tdnn.yaml 是本次实验唯一的配置文件(yacs CfgNode 解析),按功能分为五个区块,逐项说明如下。

6.1 数据准备设置

配置项默认值说明
split_typefull_corpus_asr数据集划分方式,可选scenario_only/full_corpus/full_corpus_asr
skip_TNOTrue是否跳过 TNO 会议
mic_typeMix-Headset麦克风类型,可选Mix-Lapel、Mix-Headset、Array1、Array1-01、BeamformIt
vad_typeoracleVAD 类型,当前仅使用标注的 Oracle VAD,为后续 VAD 预留
max_subseg_dur3.0子段最大时长(秒)
overlap1.5相邻子段重叠时长(秒)
embedding_diremb嵌入保存目录(相对save_folder)
meta_data_dirmetadataJSON 元数据目录
ref_rttm_dirref_rttms参考 RTTM 目录
sys_rttm_dirsys_rttms系统输出 RTTM 目录
der_dirDERDER 结果目录

6.2 特征提取设置

sr: 16000 # 采样率 n_mels: 80 # mel 滤波器个数 window_size: 400 # 25ms:25 * 16000 / 1000 = 400 hop_size: 160 # 10ms:10 * 16000 / 1000 = 160

注释明确说明当前仅支持 fbank 特征;left_frames、right_frames、deltas等项被注释掉,为后续扩展预留。

6.3 模型设置

seed: 1234 emb_dim: 192 # 输出嵌入维度 batch_size: 16 model: input_size: 80 channels: [1024, 1024, 1024, 1024, 3072] kernel_sizes: [5, 3, 3, 3, 1] dilations: [1, 2, 3, 4, 1] attention_channels: 128 lin_neurons: 192

这些参数直接对应EcapaTdnn构造函数的字段:channels为各层卷积通道数(四层 SE-Res2Block 加一层 bottleneck),kernel_sizes与dilations为各层卷积核尺寸与空洞率,attention_channels为 SE 注意力模块的中间通道数,lin_neurons为最终线性层输出维度(=emb_dim)。配置文件会自动下载 ECAPA-TDNN 预训练模型(best),即run.shstage 1 中下载的sv0_ecapa_tdnn_voxceleb12_ckpt_0_1_1(基于 VoxCeleb1/2 训练)。

6.4 谱聚类设置

backend: 'SC' # 可选 'SC' / 'AHC' / 'kmeans' affinity: 'cos' # 相似度矩阵类型,可选 'cos' / 'nn' max_num_spkrs: 10 # 每段录音最大说话人数 oracle_n_spkrs: True # 是否使用标注中的真实说话人数

关键约束(配置注释中已说明):kmeans后端只能配合cosaffinity 使用;oracle_n_spkrs=True时说话人数来自参考 RTTM 的SPKR-INFO行,否则需要估计。

6.5 DER 评测设置

ignore_overlap: True # 评测时是否忽略重叠说话区间 forgiveness_collar: 0.25 # 容忍误差(collar),单位秒

这两项与 utils/DER.py 的评测逻辑直接对应。

七、阶段 2:聚类与超参调优(experiment.py + diarization.py)

7.1 在 dev 集上调优超参

local/experiment.py 遵循"dev 集调参、dev/eval 双集评测"的标准实验范式,按后端/相似度类型选择不同的调优策略:

组合调优对象搜索范围
cosaffinity +SC/kmeansp-value(保留每行前 p% 的相似度值)np.arange(0.002, 0.015, 0.001)
AHC后端距离阈值(p_val 即阈值)np.arange(0.0, 1.0, 0.1)
nnaffinityn_neighbors 近邻数range(5, 15),固定 oracle 说话人数

调优逻辑(dev_pval_tuner等):对每个候选值在完整 dev 集上跑一遍聚类 → 生成系统 RTTM → 调用DER()计算 DER → 取 DER 最小的候选值。对kmeans+ oracle 说话人数场景,p-value 搜索会提前终止(break),因为 kmeans 不需要剪枝。

7.2 最终聚类与评测

得到最优超参后,对 dev 和 eval 分别执行diarize_dataset:

  1. 按oracle_n_spkrs决定说话人数来源:True时通过get_oracle_num_spkrs从SPKR-INFO读取;False且cosaffinity 时置None,由谱聚类用**最大特征间隙(max eigen gap)**自动估计说话人数;
  2. 依据backend分发到 paddlespeech/vector/cluster/diarization.py 中的聚类函数;
  3. 逐录音生成sys_rttms/<mic>/AMI_<split>/<tag>/<rec_id>.rttm,并拼接为sys_output.rttm;
  4. 用DER()计算总体 DER,并调用write_ders_file把逐录音 DER 写入DER/<split>_DER_<tag>文件。

7.3 三个聚类后端的实现细节

从diarization.py源码看:

  • 谱聚类 SC(cos affinity):do_spec_clustering使用SpecClustUnorm(min_num_spkrs=2、max_num_spkrs=10),内部基于 p-value 剪枝后的 cos 相似度矩阵,通过最大特征间隙估计簇数(k=None 时);
  • 谱聚类 SC(nn affinity):使用SpecCluster(n_clusters=k、affinity="nearest_neighbors"、assign_labels="kmeans"、random_state=1234);
  • AHC:do_AHC基于 sklearn 的AgglomerativeClustering,affinity="cosine"、linkage="average";已知说话人数时直接指定n_clusters,未知时用distance_threshold=p_val自动确定簇数。

聚类完成后,统一经过两步后处理:merge_ssegs_same_speaker(合并相邻、属于同一说话人的子段)与distribute_overlap(对属于不同说话人的重叠子段,取中点切分时长),最终write_rttm输出 RTTM。diarization.py的注释也坦诚指出:AHC 在未知说话人数时仅作为实验,更推荐使用谱聚类。

八、阶段 3:DER 评测指标解读

8.1 DER 的构成

Diarization Error Rate 是说话人日志的国际通用评测指标,由三部分构成:

DER = Missed Speech(MS) + False Alarm(FA) + Speaker Error Rate(SER)
  • Missed Speech:参考标注有人说话、系统判定无人说话的时间比例;
  • False Alarm:系统判定有人说话、参考标注无人说话的时间比例;
  • Speaker Error:说话人标签归属错误的时间比例。

评测参数ignore_overlap=True忽略重叠说话区间,forgiveness_collar=0.25允许边界有 0.25 秒误差,这些与 utils/DER.py 的DER()函数签名(DER(ref_rttm, sys_rttm, ignore_overlap, forgiveness_collar))一一对应。

8.2 本示例报告的最优结果

examples/ami/sd0/README.md 给出了 **ECAPA-TDNN + SC(谱聚类)**在 HeadsetMix 上的最优 DER:

SystemMic.Orcl. (Dev)Orcl. (Eval)Est. (Dev)Est. (Eval)
ECAPA-TDNN + SCHeadsetMix1.54%3.07%1.56%3.28%
  • Orcl.(Oracle):使用标注中的真实说话人数;
  • Est.(Estimated):使用最大特征间隙自动估计说话人数。

可以看到,在 oracle 说话人数下 dev 集 DER 仅 1.54%,即使改用估计说话人数,性能也几乎不损失(1.56%),说明 cos 相似度下的特征间隙估计在 AMI 上非常可靠。运行结束后,脚本会打印:

Final Diarization Error Rate (%) on AMI corpus: Dev = 1.54 % | Eval = 3.07 %

九、快速上手清单与复现要点

  1. 环境准备:安装 PaddleSpeech 及其依赖(sklearn、yacs、tqdm 等),确保MAIN_ROOT环境变量可用(由path.sh自动设置)。
  2. 数据下载:标注包(ami_public_manual_1.6.2.zip,约几十 MB)+ 音频 Signals(Headset mix,约 10GB)。会议 ID 以 local/ami_splits.py 为准。
  3. 一键运行:./run.sh --data_folder ./amicorpus --manual_annot_folder ./ami_public_manual_1.6.2(在examples/ami/sd0目录下),或分 stage 执行。
  4. 修改实验:想换聚类后端,修改conf/ecapa_tdnn.yaml的backend(SC/AHC/kmeans)与affinity(cos/nn);想评估麦克风差异,修改mic_type;想验证自动估计说话人数,将oracle_n_spkrs设为False。
  5. 结果产物:参考 RTTM 位于save/ref_rttms/,系统 RTTM 位于save/sys_rttms/,逐录音 DER 位于save/DER/。

十、延伸阅读

  • 流水线核心聚类实现:paddlespeech/vector/cluster/diarization.py(do_spec_clustering/do_AHC/read_rttm/write_ders_file)
  • 嵌入提取模型:paddlespeech/vector/models/ecapa_tdnn.py(EcapaTdnn网络定义)
  • 评测脚本:utils/DER.py
  • 数据划分:local/ami_splits.py
  • 数据准备:local/ami_prepare.py
  • 配置模板:conf/ecapa_tdnn.yaml

通过以上步骤,你可以在自己的机器上完整复现 AMI 说话人日志实验,并将这套"嵌入提取 + 聚类 + DER 评测"的流水线迁移到其他会议或电话对话数据集上。

  • 人工智能
  • 语音
  • 音频
  • NLP
  • 媒体生成

【免费下载链接】PaddleSpeech

Easy-to-use Speech Toolkit including Self-Supervised Learning model, SOTA/Streaming ASR with punctuation, Streaming TTS with text frontend, Speaker Verification System, End-to-End Speech Translation and Keyword Spotting. Won NAACL2022 Best Demo Award.

项目地址:https://gitcode.com/paddlepaddle/PaddleSpeech
点击查看免费下载

相关推荐

上一篇:WVP-GB28181-Pro:如何构建企业级国标视频监控统一平台?
下一篇:rough-notation性能预算设置:控制资源使用上限

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

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

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

立即咨询