- 人工智能
- 语音
- 音频
- 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.
本文是 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 逐项选择:
- 选择一个或多个 AMI 会议,会议 ID 请参照 local/ami_splits.py 中定义的 dev/eval 会议集合;
- 媒体流只需选择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=gpu3.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对每个录音的说话区间做两步处理:
merge_rttm_intervals:把时间上重叠的相邻说话区间合并为一个区间(重叠部分标记为overlap);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 模型加载与推理
代码调用链如下:
- 通过
paddle.set_device(args.device)设置 GPU/CPU; - 构建 ECAPA-TDNN 骨干网络:
EcapaTdnn(**config.model)(定义在 paddlespeech/vector/models/ecapa_tdnn.py); - 用
SpeakerIdetification包装成说话人识别模型; - 加载预训练权重:
paddle.load(os.path.join(args.load_checkpoint, 'model.pdparams'))后model.set_state_dict(state_dict); 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_type | full_corpus_asr | 数据集划分方式,可选scenario_only/full_corpus/full_corpus_asr |
skip_TNO | True | 是否跳过 TNO 会议 |
mic_type | Mix-Headset | 麦克风类型,可选Mix-Lapel、Mix-Headset、Array1、Array1-01、BeamformIt |
vad_type | oracle | VAD 类型,当前仅使用标注的 Oracle VAD,为后续 VAD 预留 |
max_subseg_dur | 3.0 | 子段最大时长(秒) |
overlap | 1.5 | 相邻子段重叠时长(秒) |
embedding_dir | emb | 嵌入保存目录(相对save_folder) |
meta_data_dir | metadata | JSON 元数据目录 |
ref_rttm_dir | ref_rttms | 参考 RTTM 目录 |
sys_rttm_dir | sys_rttms | 系统输出 RTTM 目录 |
der_dir | DER | DER 结果目录 |
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/kmeans | p-value(保留每行前 p% 的相似度值) | np.arange(0.002, 0.015, 0.001) |
AHC后端 | 距离阈值(p_val 即阈值) | np.arange(0.0, 1.0, 0.1) |
nnaffinity | n_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:
- 按
oracle_n_spkrs决定说话人数来源:True时通过get_oracle_num_spkrs从SPKR-INFO读取;False且cosaffinity 时置None,由谱聚类用**最大特征间隙(max eigen gap)**自动估计说话人数; - 依据
backend分发到 paddlespeech/vector/cluster/diarization.py 中的聚类函数; - 逐录音生成
sys_rttms/<mic>/AMI_<split>/<tag>/<rec_id>.rttm,并拼接为sys_output.rttm; - 用
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:
| System | Mic. | Orcl. (Dev) | Orcl. (Eval) | Est. (Dev) | Est. (Eval) |
|---|---|---|---|---|---|
| ECAPA-TDNN + SC | HeadsetMix | 1.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 %九、快速上手清单与复现要点
- 环境准备:安装 PaddleSpeech 及其依赖(sklearn、yacs、tqdm 等),确保
MAIN_ROOT环境变量可用(由path.sh自动设置)。 - 数据下载:标注包(
ami_public_manual_1.6.2.zip,约几十 MB)+ 音频 Signals(Headset mix,约 10GB)。会议 ID 以 local/ami_splits.py 为准。 - 一键运行:
./run.sh --data_folder ./amicorpus --manual_annot_folder ./ami_public_manual_1.6.2(在examples/ami/sd0目录下),或分 stage 执行。 - 修改实验:想换聚类后端,修改
conf/ecapa_tdnn.yaml的backend(SC/AHC/kmeans)与affinity(cos/nn);想评估麦克风差异,修改mic_type;想验证自动估计说话人数,将oracle_n_spkrs设为False。 - 结果产物:参考 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.
相关推荐
SpeechBrain 实战:基于 ECAPA-TDNN 深度嵌入与谱聚类的 AMI 说话人日志(Speaker Diarization)完整指南
SpeechBrain 实战:基于 ECAPA TDNN 深度嵌入与谱聚类的 AMI 说话人日志(Speaker Diarization)完整指南 说话人日志(
人工智能深度学习语音音频NLP预训练NeMo 说话人日志(Speaker Diarization)实战指南:从聚类式推理到端到端 Sortformer 流式方案
NeMo 说话人日志(Speaker Diarization)实战指南:从聚类式推理到端到端 Sortformer 流式方案 导读 本文以 NeMo(本项目为
开发工具代码质量静态分析NeMo 说话人任务实战:说话人识别(Speaker Recognition)与说话人日志(Speaker Diarization)完整指南
NeMo 说话人任务实战:说话人识别(Speaker Recognition)与说话人日志(Speaker Diarization)完整指南 导读 本文围绕 N
前端CMS
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考