说话人识别与验证


文档摘要

说话人识别与验证 本节摘要:ASR 问「他说了什么」,说话人识别(Speaker Recognition)问「这是谁说的」。数学形式看起来一样——嵌入加余弦——但每一个生产决策都吊在一个叫 EER(等错误率)的单一数字上。本节会讲透三类典型任务:1:1 验证(他是不是自称的那个人)、1:N 识别(他是注册库里的哪一位)、开集识别(可能是未注册的陌生人);并带你走过从 i-vector、x-vector 到 2026 年统治领域的 ECAPA-TDNN、WavLM-SV 与 ReDimNet 的演进。核心机制是把变长音频压成定长嵌入(192256 维),注册时存库,验证时算余弦、比阈值。训练说话人模型的关键损失是 AAM-softmax,它在角度空间里给正确类加一个间隔,逼迫类间分开。

说话人识别与验证

本节摘要:ASR 问「他说了什么」,说话人识别(Speaker Recognition)问「这是谁说的」。数学形式看起来一样——嵌入加余弦——但每一个生产决策都吊在一个叫 EER(等错误率)的单一数字上。本节会讲透三类典型任务:1:1 验证(他是不是自称的那个人)、1:N 识别(他是注册库里的哪一位)、开集识别(可能是未注册的陌生人);并带你走过从 i-vector、x-vector 到 2026 年统治领域的 ECAPA-TDNN、WavLM-SV 与 ReDimNet 的演进。核心机制是把变长音频压成定长嵌入(192~256 维),注册时存库,验证时算余弦、比阈值。训练说话人模型的关键损失是 AAM-softmax,它在角度空间里给正确类加一个间隔,逼迫类间分开。本节还会讲清 PLDA、S-norm 等评分技巧,以及多说话人场景下的「谁在什么时候说话」——说话人分离(Diarization)。读完本节,你能为任意场景选对模型、注册协议、阈值调优方案与反欺诈护栏。

学习目标

阅读完本节,你应当能够:

  1. 区分验证(1:1)、识别(1:N)、开集识别三类任务,并为每类选择正确的评估指标。
  2. 解释 ECAPA-TDNN、WavLM-SV、x-vector 三类嵌入模型的架构与权衡,知道何时该用哪个。
  3. 推导 AAM-softmax 损失在角度空间施加间隔的直觉,说明它为什么能拉开类间距离。
  4. 手写余弦评分、EER 计算与基于相似度对的阈值选择。
  5. 识别 信道失配、短语句、噪声注册、跨条件固定阈值、未归一化嵌入 五类陷阱并给出对策。
  6. pyannote.audio 搭建端到端的说话人分离流水线(VAD → 切段 → 嵌入 → 聚类 → 平滑)。

一、问题与直觉

用户说了一句口令。你想知道:他是自称的那个人吗(验证, 1:1)?还是注册库里的第一位(识别, 1:N)?或者都不是——他是未注册的陌生人(开集)?

2018 年前:GMM-UBM + i-vector,EER 还行,但对信道漂移(电话 vs 笔记本)与情绪很脆。2018-2022:x-vector(TDNN 骨干 + 角度间隔训练)。2022+:ECAPA-TDNN 与 WavLM-large 嵌入。到 2026 年,整个领域被三个模型和一个指标统治。

这个指标就是 EER(等错误率)。把决策阈值设到「错误接受率 = 错误拒绝率」的交点,这个交叉点就是 EER。每篇论文、每个排行榜、每次采购谈判都用它。

流水线:注册 + 验证

注册:录 5~30 秒目标说话人,算定长嵌入(ECAPA-TDNN 是 192 维,WavLM-large 是 256 维)。验证:取测试语句的嵌入,算余弦相似度,与阈值比较。

三类嵌入模型

  • ECAPA-TDNN(2020,2026 年仍统治):Emphasized Channel Attention, Propagation and Aggregation - Time-Delay Neural Network。1D 卷积块 + squeeze-excitation + 多头注意力池化,接一个线性层到 192 维。在 VoxCeleb 1+2(2700 说话人、110 万句)上用加性角度间隔损失(AAM-softmax)训练。
  • WavLM-SV(2022+):在预训练的 WavLM-large 自监督骨干上用 AAM 损失微调。质量更高但更慢——300+ MB vs 15 MB。
  • x-vector(基线):TDNN + 统计池化。经典,CPU/边缘设备上仍实用。

AAM-softmax:角度空间的间隔

标准 softmax 加一个间隔 m:cos(θ + m) 用于正确类。逼迫类间角度分开。典型 m=0.2,scale s=30

评分:余弦、PLDA、归一化

  • 余弦:注册与测试嵌入之间,阈值决策。
  • PLDA(概率 LDA):把嵌入投到潜空间,使同说话人 vs 不同说话人有闭式似然比。在余弦之上能再降 10~20% EER。2020 年前的标准,现在只在闭集场景用。
  • 分数归一化:S-normAS-norm:用一群「冒充者」的均值和标准差归一化每个分数。跨域评估必备。

2026 年值得记住的数字

模型 VoxCeleb1-O EER 参数 吞吐(A100)
x-vector(经典) 3.10% 5 M 400× RT
ECAPA-TDNN 0.87% 15 M 200× RT
WavLM-SV large 0.42% 316 M 20× RT
Pyannote 3.1 分割 + 嵌入 0.65% 6 M 100× RT
ReDimNet(2024) 0.39% 24 M 100× RT

分离:谁在什么时候说话

在多说话人片段里回答「谁在什么时候说话」。流水线:VAD → 切段 → 对每段嵌入 → 聚类(凝聚或谱)→ 平滑边界。现代栈:pyannote.audio 3.1,把说话人分割 + 嵌入 + 聚类打包在一次调用里。2026 年 AMI 数据集上 SOTA DER 约 15%(2022 年是 23%)。

💡 EER 之所以成为通用指标,是因为它把「错误接受」与「错误拒绝」这两种代价不对称的错误强制平衡到一个点。但生产里两种错误的代价往往不同——银行语音登录的错误接受代价远高于错误拒绝,因此实际部署要在 EER 阈值基础上向严调,而不是直接用 EER 点的阈值。

二、从零实现

第 1 步:用 MFCC 统计量做玩具嵌入

def embed_mfcc_stats(signal, sr): frames = featurize_mfcc(signal, sr, n_mfcc=13) mean = [sum(f[i] for f in frames) / len(frames) for i in range(13)] std = [ math.sqrt(sum((f[i] - mean[i]) ** 2 for f in frames) / len(frames)) for i in range(13) ] return mean + std # 26 维

离 SOTA 十万八千里——仅作教学。code/main.py 用它在合成说话人数据上做概念验证。

第 2 步:余弦相似度 + 阈值

def cosine(a, b): dot = sum(x * y for x, y in zip(a, b)) na = math.sqrt(sum(x * x for x in a)) nb = math.sqrt(sum(x * x for x in b)) return dot / (na * nb) if na and nb else 0.0 def verify(enroll, test, threshold=0.75): return cosine(enroll, test) >= threshold

第 3 步:从相似度对算 EER

def eer(same_scores, diff_scores): thresholds = sorted(set(same_scores + diff_scores)) best = (1.0, 1.0, 0.0) # (fa, fr, threshold) for t in thresholds: fr = sum(1 for s in same_scores if s < t) / len(same_scores) fa = sum(1 for s in diff_scores if s >= t) / len(diff_scores) if abs(fa - fr) < abs(best[0] - best[1]): best = (fa, fr, t) return (best[0] + best[1]) / 2, best[2]

返回 (eer, eer 处的阈值),两者都要汇报。

第 4 步:用 SpeechBrain 上生产

from speechbrain.pretrained import EncoderClassifier clf = EncoderClassifier.from_hparams(source="speechbrain/spkrec-ecapa-voxceleb") # 注册:对 3~5 个干净样本的嵌入取平均 enroll = torch.stack([clf.encode_batch(load(x)) for x in enrollment_clips]).mean(0) # 验证 score = clf.similarity(enroll, clf.encode_batch(load("test.wav"))).item() verdict = score > 0.25 # ECAPA 典型阈值;要在你的数据上调

设计要点:注册向量取多个干净样本的平均,是为了降低单次录音的噪声方差——这相当于在嵌入空间做蒙特卡洛平均,得到的锚点更稳定。千万别用单个噪声样本注册,它会把锚点拽偏,污染此后所有验证。

第 5 步:用 pyannote 做分离

from pyannote.audio import Pipeline pipe = Pipeline.from_pretrained("pyannote/speaker-diarization-3.1") diarization = pipe("meeting.wav", num_speakers=None) for turn, _, speaker in diarization.itertracks(yield_label=True): print(f"{turn.start:.1f}–{turn.end:.1f} {speaker}")

三、框架对比

2026 年工具栈:

场景
闭集 1:1 验证、边缘 ECAPA-TDNN + 余弦阈值
开集验证、云端 WavLM-SV + AS-norm
分离(会议、播客) pyannote/speaker-diarization-3.1
反欺诈(重放 / 深度伪造检测) AASIST 或 RawNet2
嵌入式(KWS + 注册) Titanet-Small(NeMo)

⚠️ 五类陷阱:① 信道失配——在 VoxCeleb(网视频)上训练 ≠ 电话音频,务必在目标信道上评估;② 短语句——测试音频低于 3 秒,EER 急剧恶化;③ 带噪注册——一个噪声注册会毒化锚点,用 ≥3 个干净样本取平均;④ 跨条件固定阈值——始终在目标域的留出开发集上调阈值;⑤ 未归一化嵌入做余弦——先 L2 归一化,否则模长会主导相似度。

四、可复用产物

本节产出技能文档(原课程 outputs/skill-speaker-verifier.md),选择模型、注册协议、阈值调优方案、反欺诈护栏。核心决策表:

  • 边缘 / 算力受限 → ECAPA-TDNN(15M),余弦阈值。
  • 云端开集 → WavLM-SV + AS-norm,EER 更低但 316M 参数。
  • 多人会议 → pyannote/speaker-diarization-3.1 一站式。
  • 注册协议 → ≥3 个干净样本,嵌入取平均,L2 归一化。
  • 阈值 → 在目标域留出开发集上算 EER,再按业务代价偏严或偏松。
  • 反欺诈 → 叠加 AASIST 做重放/合成检测(第 16 节详讲)。

五、练习

  1. 基础:运行 code/main.py。它构造合成「说话人」(不同音色轮廓),做注册,在 100 对试听列表上算 EER。

  2. 进阶:用 SpeechBrain ECAPA 在 30 条 VoxCeleb1 语句上(5 说话人 × 6 条),对比余弦 vs PLDA 的 EER。

  3. 挑战:用 pyannote.audio 搭建完整「注册 → 分离 → 验证」流水线,在 AMI 开发集上评估 DER。

本节要点回顾

  1. ASR 问「说什么」,说话人识别问「谁说的」——数学形式像(嵌入+余弦),但所有决策吊在 EER 这一个数字上。
  2. 三类任务:验证(1:1)、识别(1:N)、开集(可能是未注册陌生人),评估与阈值策略各不相同。
  3. 三大嵌入模型:ECAPA-TDNN(192 维,15M,边缘友好)、WavLM-SV(256 维,316M,质量最高)、x-vector(经典基线)。
  4. AAM-softmax 在角度空间给正确类加间隔 cos(θ+m),逼迫类间分开,典型 m=0.2, s=30
  5. EER = 错误接受率 = 错误拒绝率的交点,所有论文与采购谈判的标准指标;但生产里要按业务代价偏调。
  6. PLDA + S-norm 是评分层增强,前者闭集再降 10~20% EER,后者跨域归一化必备。
  7. 分离流水线:VAD → 切段 → 嵌入 → 聚类 → 平滑,pyannote.audio 3.1 一站式;2026 年 AMI SOTA DER 约 15%。
  8. 五类陷阱:信道失配、短语句(<3 秒)、噪声注册、跨条件固定阈值、未归一化嵌入。

下一节,我们反向走——把文字变成声音,文本转语音(TTS),从 Tacotron 的注意力机制讲到 F5-TTS 与 Kokoro 这类 2026 年的流匹配(flow matching)模型。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U