PaddleSpeech 说话人验证实战:基于 VoxCeleb 数据集与 ECAPA-TDNN 的完整训练评测指南

原创2026-09-24 20:57:241,195 阅读
文章标签:人工智能语音音频NLP媒体生成

PaddleSpeech 说话人验证实战:基于 VoxCeleb 数据集与 ECAPA-TDNN 的完整训练评测指南

导读

本文以 PaddleSpeech 仓库中 examples/voxceleb/README.md 为骨架,系统讲解说话人验证(Speaker Verification)在 VoxCeleb 数据集上的完整落地路径:从 VoxCeleb1/2 的数据下载、m4a 到 wav 格式转换,到 VoxCeleb 官方评测协议(trial 文件)的解读,再到 PaddleSpeech 中 sv0(ECAPA-TDNN + 纯 Python 实现)的配置、训练、测试与预训练模型推理。读完本文,你将掌握在 PaddleSpeech 中从零跑通 VoxCeleb 说话人验证全流程所需的全部命令、配置参数与原理依据。


一、VoxCeleb 数据集概览

VoxCeleb 是由牛津大学 VGG 团队发布的音视频说话人数据集,语音片段全部来自 YouTube 访谈视频的截取。它的特点非常鲜明:说话人覆盖不同种族、口音、职业与年龄段,所有语音都是"野外(in the wild)"采集,包含背景人声、笑声、重叠语音、姿态变化和光照差异等复杂干扰,每条语音片段时长至少 3 秒,同时提供音频与视频两种模态。

数据集分为两个版本:

  • VoxCeleb1:规模较小,常作为评测集使用;
  • VoxCeleb2:规模更大,是训练集的主力来源。

两个版本之间不存在说话人重叠(no overlap between the two versions),这意味着可以用 VoxCeleb2 训练、VoxCeleb1 评测,而不会出现说话人身份泄漏。PaddleSpeech 仓库在 dataset/voxceleb/README.md 中对数据集特性做了上述描述,对应的下载与 manifest 生成脚本分别是 dataset/voxceleb/voxceleb1.py 与 dataset/voxceleb/voxceleb2.py。

数据集规模统计

dataset vox1 - dev vox1 - test vox2 - dev vox2 - test
spks(说话人数) 1211 40 5994 118
utts(语音条数) 148642 4874 1092009 36273
time(h)(总时长/小时) 340.4 11.2 2360.2 79.9

以上数据来自 examples/voxceleb/README.md 的统计表。可以看到 VoxCeleb2 的 dev 集包含约 109 万条语音、2360 小时,是典型的"大数据量 + 数千说话人"训练场景,非常适合训练大规模说话人嵌入(speaker embedding)模型。


二、VoxCeleb 官方评测协议(trial 文件)

说话人验证的标准评测方式是构建 trial 列表:每一行是一个"注册语音-测试语音"对,并标注该对是否来自同一说话人(positive/negative)。模型对每个 trial 对计算相似度得分,再通过阈值比较得到等错误率(EER)等指标。

PaddleSpeech 仓库在 examples/voxceleb/README.md 中整理了 VoxCeleb 官方的 trial 文件统计:

trial filename nums positive negative
VoxCeleb1 veri_test.txt 37720 18860 18860
VoxCeleb1(cleaned) veri_test2.txt 37611 18802 18809
VoxCeleb1-H list_test_hard.txt 552536 276270 276266
VoxCeleb1-H(cleaned) list_test_hard2.txt 550894 275488 275406
VoxCeleb1-E list_test_all.txt 581480 290743 290737
VoxCeleb1-E(cleaned) list_test_all2.txt 579818 289921 289897

说明:

  • VoxCeleb1 基础协议共 37720 对,正负样本各半(18860/18860),是社区最常用的入门评测;
  • cleaned 版本剔除了官方标注为"噪声/异常"的样本对,数量略有减少;
  • VoxCeleb1-H(hard)与 VoxCeleb1-E(extended)分别是更难(同国籍/同性别的困难对占比更高)与更全(覆盖全部说话人对)的扩展协议,规模都在 50 万对以上。

从 examples/voxceleb/sv0/local/data.sh 可以看到,stage 1 的数据下载阶段会通过 voxceleb1.py 自动生成 manifest.dev 与 manifest.test(注释中标注了 manifest.dev: 148642、manifest.test: 4847),并下载上述 trial 文件。sv0 的默认评测使用 cleaned 版本的 veri_test2.txt(见配置文件中的 verification_file: data/vox1/veri_test2.txt),测试脚本据此计算 EER。


三、VoxCeleb2 数据准备:m4a 转 wav

VoxCeleb2 官方发布的音频文件是 m4a 格式,而 PaddleSpeech 的后续流程要求 wav 输入,因此所有 m4a 必须先完成格式转换,并且只需执行一次(转换耗时可能长达数小时,与机器性能相关)。

原文档给出的转换命令模板为:

ffmpeg -y -i %s -ac 1 -vn -acodec pcm_s16le -ar 16000 %s

各参数含义:

  • -i %s:输入 m4a 文件;
  • -ac 1:强制单声道(说话人识别通常使用单声道音频);
  • -vn:丢弃视频流;
  • -acodec pcm_s16le:编码为 16-bit 线性 PCM;
  • -ar 16000:重采样到 16kHz(与配置文件中的 sr: 16000 一致)。

在 PaddleSpeech 中,这一步由 examples/voxceleb/sv0/local/convert.sh 自动化完成。该脚本的核心是信号量控制的并行转换:默认开 32 个并发槽位(N=32,对应 32 vCPU 环境,可按机器核数调整),遍历目录下所有 *.m4a 文件,逐个执行:

ffmpeg -loglevel panic -i "$f" -ar 16000 "${f%.*}.wav"

转换完成后,把所有 wav 文件统一放入名为 wav 的目录,目录结构形如:

voxceleb2/wav/id00012/21Uxsk56VDQ/00001.wav

即 wav/说话人ID/视频ID/片段ID.wav 的三级结构。数据脚本 examples/voxceleb/sv0/local/data.sh 的 stage 3 会调用 convert.sh 完成 m4a→wav 转换(不删除原始 m4a),stage 4 再通过 voxceleb2.py --generate 从 wav 目录生成 vox2/manifest.vox2 清单文件;由于 VoxCeleb2 全部用于训练,所有数据汇总到一个 manifest 文件。

补充:原文档提到的 VoxCeleb1 数据下载同样由 data.sh 的 stage 1 完成(voxceleb1.py --manifest_prefix ... --target_dir ...),无需手动转换格式。


四、sv0 与 sv1:两条说话人验证路线

examples/voxceleb/README.md 将 VoxCeleb 示例划分为两个子目录:

  • sv0:speaker verification with softmax backend,全 Python 实现,使用 ECAPA-TDNN + AAM-softmax 等损失函数端到端训练,无需 Kaldi 依赖;
  • sv1:dependence on kaldi,基于 plda/sc 后端的传统路线(PLDA 概率线性判别分析 / score 归一化),依赖 Kaldi 生态。

从当前仓库结构看,examples/voxceleb 目录下只保留了 sv0,包含完整的 run.sh、配置文件与 local/ 脚本集,因此本文后续将以 sv0 为主线展开实战讲解。


五、sv0 实战:ECAPA-TDNN 全流程

sv0 的核心说明文档是 examples/voxceleb/sv0/README.md,所有脚本统一由 examples/voxceleb/sv0/run.sh 编排。

5.1 阶段划分(stage)

Stage 功能
0 数据准备:下载 VoxCeleb1、下载 VoxCeleb2、m4a 转 wav、生成 train/dev/test 的 manifest 文件、下载 RIR Noise 数据集并生成增强用噪声 manifest
1 训练模型
2 使用 VoxCeleb trial 评测说话人验证性能

通过 stage 与 stop_stage 参数可以灵活控制执行范围,例如:

# 只跑训练和评测(stage 1、2)
bash run.sh --stage 1 --stop_stage 2

# 只跑数据准备(stage 0)
bash run.sh --stage 0 --stop_stage 0

5.2 环境变量与本地变量

执行任何脚本前必须先引入环境变量:

source path.sh
source ${MAIN_ROOT}/utils/parse_options.sh

其中 examples/voxceleb/sv0/path.sh 负责设置:

  • MAIN_ROOT:指向 PaddleSpeech 仓库根目录(realpath ${PWD}/../../../);
  • PATH:加入仓库根目录与 utils/;
  • PYTHONPATH:加入仓库根目录,保证能 import paddlespeech 包;
  • BIN_DIR:指向 paddlespeech/vector/exps/ecapa_tdnn,即训练/测试/嵌入提取脚本所在目录。

parse_options.sh 提供了 shell 脚本中 --variable value 的参数解析能力。run.sh 中定义的本地变量包括:

变量 含义 默认值
gpus 使用的 GPU 编号;置空则纯 CPU 0,1,2,3
stage 起始阶段 0
stop_stage 结束阶段 50
dir 数据信息目录 data/
exp_dir 实验输出目录 exp/ecapa-tdnn-vox12-big/
conf_path 模型配置文件路径 conf/ecapa_tdnn.yaml

示例:指定 GPU 启动完整流程:

bash run.sh --gpus 0,1

5.3 Stage 0:数据准备

if [ ${stage} -le 0 ] && [ ${stop_stage} -ge 0 ]; then
     # prepare data
     bash ./local/data.sh ${dir} ${conf_path} || exit -1
fi

也可以直接手动执行:

source path.sh
bash ./local/data.sh ./data/ conf/ecapa_tdnn.yaml

data.sh 内部又细分了 8 个子阶段(1~7),核心动作包括:

  1. voxceleb1.py 下载 VoxCeleb1 并生成 manifest.dev、manifest.test;
  2. voxceleb2.py --download 下载 VoxCeleb2(m4a);
  3. convert.sh 将 m4a 批量转 wav;
  4. voxceleb2.py --generate 生成 vox2/manifest.vox2;
  5. make_vox_csv_dataset_from_json.py 将 json 格式 manifest 转成训练框架直接消费的 csv(train.csv 由 vox1.dev + vox2.vox2 合并,dev/test 来自 vox1.test);
  6. rir_noise.py 下载 RIR Noise 增强数据并生成噪声 manifest;
  7. make_rirs_noise_csv_dataset_from_json.py 生成 noise.csv、rir.csv。

处理完成后 data/ 目录结构如下(来自 examples/voxceleb/sv0/README.md):

data/
├── rir_noise
│   ├── csv
│   │   ├── noise.csv
│   │   └── rir.csv
│   ├── manifest.pointsource_noises
│   ├── manifest.real_rirs_isotropic_noises
│   └── manifest.simulated_rirs
├── vox
│   ├── csv
│   │   ├── dev.csv
│   │   ├── enroll.csv
│   │   ├── test.csv
│   │   └── train.csv
│   └── meta
│       └── label2id.txt
└── vox1
    ├── list_test_all2.txt
    ├── list_test_all.txt
    ├── list_test_hard2.txt
    ├── list_test_hard.txt
    ├── manifest.dev
    ├── manifest.test
    ├── veri_test2.txt
    ├── veri_test.txt
    ├── voxceleb1.dev.meta
    └── voxceleb1.test.meta

其中 label2id.txt 是说话人 ID 到训练标签的映射,veri_test*.txt、list_test_*.txt 就是上一节的评测协议文件。

5.4 Stage 1:模型训练

if [ ${stage} -le 1 ] && [ ${stop_stage} -ge 1 ]; then
     # train model, all `ckpt` under `exp` dir
     CUDA_VISIBLE_DEVICES=${gpus} bash ./local/train.sh ${dir} ${exp_dir} ${conf_path}
fi

训练脚本 examples/voxceleb/sv0/local/train.sh 会根据 CUDA_VISIBLE_DEVICES 自动计算 GPU 数量:

  • 多卡时使用 Paddle 分布式启动器:python3 -m paddle.distributed.launch --gpus=$CUDA_VISIBLE_DEVICES ${BIN_DIR}/train.py ...;
  • 单卡/CPU 时直接运行 ${BIN_DIR}/train.py --device {gpu|cpu} --checkpoint-dir ${exp_dir} --data-dir ${dir} --config ${conf_path}。

训练入口是 paddlespeech/vector/exps/ecapa_tdnn/train.py,模型实现位于 paddlespeech/vector/models/ecapa_tdnn.py。训练产物(model.pdparams 及中间 checkpoint)保存在 exp_dir 下。

一条常用的命令组合(纯 CPU 全流程):

source path.sh
bash ./local/data.sh ./data/ conf/ecapa_tdnn.yaml
CUDA_VISIBLE_DEVICES= ./local/train.sh ./data/ exp/ecapa-tdnn-vox12-big/ conf/ecapa_tdnn.yaml

5.5 Stage 2:模型测试(trial 评测)

if [ ${stage} -le 2 ] && [ ${stop_stage} -ge 2 ]; then
     # test ckpt avg_n
     CUDA_VISIBLE_DEVICES=0 bash ./local/test.sh ${dir} ${exp_dir} ${conf_path} || exit -1
fi

测试脚本 examples/voxceleb/sv0/local/test.sh 调用 ${BIN_DIR}/test.py --data-dir ${dir} --load-checkpoint ${exp_dir} --config ${conf_path} --device ${device},即 paddlespeech/vector/exps/ecapa_tdnn/test.py。它会:

  1. 对 verification_file 指定的 trial 文件中的每个语音对提取嵌入向量;
  2. 使用余弦相似度计算得分(run.sh 注释说明当前测试仅支持 cosine 打分);
  3. 计算 EER 等指标输出。

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

sv0 提供两套配置,分别是 examples/voxceleb/sv0/conf/ecapa_tdnn.yaml(VoxCeleb1+2 联合训练,7205 说话人)与 examples/voxceleb/sv0/conf/ecapa_tdnn_small.yaml(仅 VoxCeleb1,1211 说话人,通道数减半为 512,训练 100 轮)。二者结构完全一致,下面以主配置逐段解读。

6.1 数据配置

参数 默认值 说明
augment True 是否启用 RIR/Noise 数据增强
batch_size 32 训练 batch 大小
num_workers 2 DataLoader 工作进程数
num_speakers 7205 分类任务类别数 = vox1(1211) + vox2(5994),测试说话人 41 个不参与
shuffle True 是否打乱样本
skip_prep False 是否跳过数据预处理
split_ratio 0.9 训练/验证集划分比例
chunk_duration 3.0 每条训练语音随机截取 3 秒
random_chunk True 是否随机截取 chunk
verification_file data/vox1/veri_test2.txt 评测用 trial 文件(cleaned 版)

从 paddlespeech/vector/io/augment.py 等源码可以确认,增强会利用 stage 0 生成的 rir.csv、noise.csv 对 3 秒 chunk 叠加真实房间冲击响应与背景噪声,提升模型在真实场景的鲁棒性。

6.2 特征提取配置

# currently, we only support fbank
sr: 16000           # sample rate
n_mels: 80
window_size: 400     #25ms, sample rate 16000, 25 * 16000 / 1000 = 400
hop_size: 160        #10ms, sample rate 16000, 10 * 16000 / 1000 = 160

当前实现仅支持 fbank(filterbank)特征:16kHz 采样率、80 维 mel 滤波器组、25ms 窗长(400 采样点)、10ms 帧移(160 采样点)。特征预处理与批处理逻辑可参考 paddlespeech/vector/io/dataset.py 与 paddlespeech/vector/io/batch.py。

6.3 模型结构配置

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

这是 ECAPA-TDNN 的核心结构:

  • 4 个 TDNN 卷积块,通道数 1024,卷积核 5/3/3/3,膨胀率 1/2/3/4(感受野逐层扩大);
  • 第 5 层 3072 通道(SE-Res2Block 聚合层),kernel 1、dilation 1;
  • 之后接 128 维注意力的 SE 模块,最终通过统计池化(mean+std)得到句级表示,再经全连接映射到 192 维说话人嵌入(lin_neurons: 192,RESULT.md 中标注的 dim 即此值)。

完整实现见 paddlespeech/vector/models/ecapa_tdnn.py(ECAPA-TDNN 在 PaddleSpeech 中的网络主体与模块组装)。

6.4 训练超参数

seed: 1986 # according from speechbrain configuration
epochs: 10
save_interval: 10
log_interval: 10
learning_rate: 1e-8
max_lr: 1e-3
step_size: 140000
  • 训练 10 个 epoch(big 配置),每 10 步保存 checkpoint、打印日志;
  • 使用 循环学习率调度:learning_rate 为初始/最小学习率 1e-8,max_lr 为峰值 1e-3,step_size: 140000 为循环半周期步数。调度器实现可参考 paddlespeech/vector/training/scheduler.py。

6.5 损失函数(AAM-softmax)

margin: 0.2
scale: 30

说话人识别本质是"说话人 ID 分类 + 嵌入度量"联合训练:softmax 分类头配合 margin=0.2(角度间隔)、scale=30(特征缩放因子),即 AAM-softmax(Additive Angular Margin Softmax) 损失。实现位于 paddlespeech/vector/modules/loss.py。正是通过这种"带角度间隔的软间隔"约束,训练出的 192 维嵌入在余弦相似度度量下具备良好的区分性。

6.6 测试与打分配置

global_embedding_norm: True
embedding_mean_norm: True
embedding_std_norm: False

推理阶段对嵌入做归一化处理,相关工具函数见 paddlespeech/vector/io/embedding_norm.py。

6.7 分数归一化(s-norm)

score_norm: s-norm
cohort_size: 20000 # amount of imposter utterances in normalization cohort
n_train_snts: 400000 # used for normalization stats

测试时启用 s-norm(score normalization):用 20000 条冒名顶替(imposter)语音构成归一化群组,利用 400000 条训练语音统计归一化参数,对原始余弦得分做标准化以消除说话人/信道偏差,从而降低 EER。


七、预训练模型与嵌入提取

仓库在 docs/source/released_model.md 中发布了 VoxCeleb 预训练模型,examples/voxceleb/sv0/README.md 给出了下载、解压与直接评测的流程:

wget https://paddlespeech.cdn.bcebos.com/vector/voxceleb/sv0_ecapa_tdnn_voxceleb12_ckpt_0_2_1.tar.gz
tar -xvf sv0_ecapa_tdnn_voxceleb12_ckpt_0_2_1.tar.gz
source path.sh
# 若已完成数据准备并生成 manifest,可跳过上面的 data.sh 两步

CUDA_VISIBLE_DEVICES= bash ./local/test.sh ./data sv0_ecapa_tdnn_voxceleb12_ckpt_0_2_1/model/ conf/ecapa_tdnn.yaml

(注意:上述下载地址为仓库 README 中原始给出的 CDN 链接,实际使用请以 docs/source/released_model.md 当前发布信息为准。)

此外,examples/voxceleb/sv0/local/emb.sh 提供了单条音频嵌入提取的入口,调用 paddlespeech/vector/exps/ecapa_tdnn/extract_emb.py:

python3 ${BIN_DIR}/extract_emb.py --device cpu \
        --config conf/ecapa_tdnn.yaml \
        --audio-path demo/voxceleb/00001.wav --load-checkpoint exp/ecapa-tdnn-vox12-big/

该能力可直接用于构造注册/验证(enroll/test)打分流程,或作为下游声纹应用(如说话人聚类、说话人日志)的特征前端。


八、实验结果参考

仓库 examples/voxceleb/sv0/RESULT.md 记录了发布模型的评测结果:

Model Number of Params Release Config dim Test set Cosine Cosine + S-Norm
ECAPA-TDNN 85M 0.2.1 conf/ecapa_tdnn.yaml 192 test 0.8188 0.7815

该表给出的 EER 为 0.8188%(纯余弦打分),启用 s-norm 后降至 0.7815%(数值越小越好,EER 为等错误率百分比)。同时,RESULT.md 中对照引用了 SpeechBrain 官方 recipe 在同一任务上的参考结果(0.90% 无 s-norm / 0.80% 有 s-norm),说明 PaddleSpeech 该实现达到同类开源工具的主流水平,且 s-norm 带来的收益方向一致。注意这些对比数据来自仓库文档的转述,如需复现,请按上文流程使用同一配置与 trial 文件自行评测。


九、小结与延伸阅读

围绕 examples/voxceleb/README.md,本文完整覆盖了:VoxCeleb1/2 的数据特性与规模、VoxCeleb2 的 m4a→wav 转换、官方 trial 评测协议、sv0(ECAPA-TDNN)从数据准备到训练评测的全流程、核心配置参数逐项解读,以及预训练模型与嵌入提取方法。相关源码均可从以下位置继续深入:

如果你需要的是传统 Kaldi 路线的 PLDA/SC 后端方案,可关注仓库中 sv1 相关设计与 Kaldi 依赖的演进;若要在实际系统中落地声纹注册、验证与说话人日志,建议从 extract_emb.py 出发构建 enroll/test 打分服务。

登录后查看全文
PaddleSpeech