说话人识别与验证 本节摘要: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)。读完本节,你能为任意场景选对模型、注册协议、阈值调优方案与反欺诈护栏。
阅读完本节,你应当能够:
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 维)。验证:取测试语句的嵌入,算余弦相似度,与阈值比较。
标准 softmax 加一个间隔 m:cos(θ + m) 用于正确类。逼迫类间角度分开。典型 m=0.2,scale s=30。
S-norm 或 AS-norm:用一群「冒充者」的均值和标准差归一化每个分数。跨域评估必备。| 模型 | 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 点的阈值。
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 用它在合成说话人数据上做概念验证。
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
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 处的阈值),两者都要汇报。
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 典型阈值;要在你的数据上调
设计要点:注册向量取多个干净样本的平均,是为了降低单次录音的噪声方差——这相当于在嵌入空间做蒙特卡洛平均,得到的锚点更稳定。千万别用单个噪声样本注册,它会把锚点拽偏,污染此后所有验证。
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),选择模型、注册协议、阈值调优方案、反欺诈护栏。核心决策表:
pyannote/speaker-diarization-3.1 一站式。基础:运行 code/main.py。它构造合成「说话人」(不同音色轮廓),做注册,在 100 对试听列表上算 EER。
进阶:用 SpeechBrain ECAPA 在 30 条 VoxCeleb1 语句上(5 说话人 × 6 条),对比余弦 vs PLDA 的 EER。
挑战:用 pyannote.audio 搭建完整「注册 → 分离 → 验证」流水线,在 AMI 开发集上评估 DER。
cos(θ+m),逼迫类间分开,典型 m=0.2, s=30。pyannote.audio 3.1 一站式;2026 年 AMI SOTA DER 约 15%。下一节,我们反向走——把文字变成声音,文本转语音(TTS),从 Tacotron 的注意力机制讲到 F5-TTS 与 Kokoro 这类 2026 年的流匹配(flow matching)模型。