声纹识别技术实践:AI 如何通过几秒钟的声音判断是不是同一个人?
摘要: 人的声音和指纹一样,具有一定的个体差异。声纹识别(Speaker Recognition)就是利用这些差异,从语音中提取说话人特征,判断两段录音是否来自同一个人,或者从已注册人员中寻找最匹配的说话人。本文介绍声纹识别的核心原理,并以开源 SpeechBrain ECAPA-TDNN 模型为例,通过双录音身份核验、多人声纹库匹配两个 Python 案例,展示如何构建一个本地声纹识别系统。
一、声音也能识别一个人的身份?
假设有人给我们打电话。
电话接通后,对方只说了一句:
你好,我想咨询一下我的订单。
即使对方没有自报姓名,我们也可能凭借声音判断出他是谁。
这是因为不同人的声音存在一定差异,包括音色、共振特征、发声习惯等。
声纹识别试图让机器学习这些差异。
但这里有个很有意思的问题:
如果同一个人第一次说中文,第二次说英文,AI 还能判断出这是同一个人吗?
理论上可以。
因为声纹模型主要学习说话人的声学特征,而不只是识别具体说了哪些文字。
不过,跨语言、跨设备、跨环境的声纹匹配通常更困难,需要实际数据验证。
1.1 与 ASR、Diarization 的区别
此前我们研究过 ASR 和说话人分离(Diarization),现在可以把它们放在一起比较:
| 模型 | 核心问题 | 输出 |
|---|---|---|
| ASR | 说了什么? | 转写文字 |
| VAD | 什么时候有人说话? | 语音时间段 |
| Diarization | 一段录音中有几个不同的人? | 匿名说话人标签 |
| Speaker Verification | 两段声音是否来自同一个人? | 相似度与判断 |
| Speaker Identification | 这段声音最接近已注册的哪个人? | 候选身份 |
其中,声纹识别又分为两个典型任务。
1:1 验证(Verification)
给定两段录音,判断是否来自同一个说话人。
1:N 辨识(Identification)
给定一段新录音,与声纹库中的 N 个候选人比较,找出最相似的人。
这两种任务使用的底层模型通常可以相同,只是后续匹配方法不同。
二、声纹识别模型的核心原理
现代声纹识别通常使用 Speaker Embeddings。
它与文本 Embeddings 非常相似,只是输入从文字变成了声音。
原始录音
│
▼
音频预处理
│
▼
声学特征提取
│
▼
Speaker Encoder
│
▼
声纹向量
│
▼
余弦相似度计算
│
▼
同一人 / 不同人
例如,两段录音被编码为:
[
v_1=f(audio_1)
]
[
v_2=f(audio_2)
]
再计算余弦相似度:
[
sim(v_1,v_2)=
\frac{v_1\cdot v_2}{|v_1||v_2|}
]
相似度越高,说明模型认为两段录音中的声学特征越接近。
但要注意:声纹相似度不等于身份匹配的真实概率。
例如,相似度 0.85,并不意味着有 85% 的概率来自同一个人。
2.1 典型开源模型
| 模型 | 特点 | 适用场景 |
|---|---|---|
| ECAPA-TDNN | 经典声纹模型,精度较好、参数量较小 | 声纹验证、身份匹配 |
| ResNet34 Speaker Encoder | 使用残差网络提取声学特征 | 声纹识别、声纹聚类 |
| CAM++ | 轻量级说话人表示模型 | 中文语音、实时应用 |
| ERes2Net | 多尺度声学特征建模 | 复杂录音环境 |
| ReDimNet | 面向效率与精度的声纹网络 | 低延迟声纹识别 |
本文选用:
SpeechBrain ECAPA-TDNN
对应模型:
speechbrain/spkrec-ecapa-voxceleb
主要特点:
- 模型权重约 90MB。
- 输出 192 维 Speaker Embeddings。
- 支持 CPU 和 GPU 推理。
- 使用 VoxCeleb1、VoxCeleb2 数据训练。
- 模型许可为 Apache 2.0。
在官方 VoxCeleb1 Cleaned 测试集上,报告的 EER 为 0.80%。
EER 是等错误率,越低越好。这个结果属于指定测试集上的实验成绩,不能直接推广到中文电话录音或其他场景。
该模型主要采用英语语音数据训练,跨语言匹配可以作为实验方向,但需要额外验证。
相关开源项目:
如果业务以中文语音为主,还可以对比 WeSpeaker 提供的中文预训练模型。
三、实践案例一:判断两段录音是否来自同一个人
3.1 业务场景
假设我们有两段录音:
speaker_zh.wav # 某人用中文说话
speaker_en.wav # 某人用英文说话
我们希望判断它们是否来自同一个人。
这可以用于研究跨语言说话人匹配,也可以作为声纹验证系统的基础实验。
3.2 环境准备
建议使用 Python 3.10 或更新版本。
安装与运行环境兼容的 PyTorch、Torchaudio,然后执行:
pip install -U speechbrain soundfile
首次运行会下载模型权重,之后可以使用本地缓存进行离线推理。
建议先把两段录音统一转换为 16kHz 单声道 WAV。
ffmpeg -i speaker_zh.mp3 -ar 16000 -ac 1 speaker_zh.wav
ffmpeg -i speaker_en.mp3 -ar 16000 -ac 1 speaker_en.wav
3.3 Python 完整实现
import torch
from speechbrain.inference.speaker import SpeakerRecognition
# 1. 加载预训练声纹模型
device = "cuda" if torch.cuda.is_available() else "cpu"
model = SpeakerRecognition.from_hparams(
source="speechbrain/spkrec-ecapa-voxceleb",
savedir="pretrained_models/ecapa",
run_opts={"device": device}
)
# 2. 两段录音
audio1 = "speaker_zh.wav"
audio2 = "speaker_en.wav"
# 3. 执行1:1声纹验证
score, prediction = model.verify_files(
audio1,
audio2
)
# 4. 获取相似度与判断结果
similarity = float(score.item())
same_speaker = bool(prediction.item())
print(f"声纹相似度:{similarity:.4f}")
if same_speaker:
print("模型判断:可能是同一个人")
else:
print("模型判断:可能是不同的人")
这里的 verify_files() 是 SpeechBrain 官方提供的接口。
它会自动提取两段语音的声纹向量,并计算相似度。
需要注意,官方接口默认采用 0.25 作为余弦相似度决策阈值。这是接口的默认设置,正式应用时应根据业务录音重新确定阈值。
3.4 如何解读结果?
假设得到:
声纹相似度:0.82
模型判断:可能是同一个人
以上是演示结果,不是实际模型运行结果。
这里的 0.82 是两段声音特征向量的相似度。
如果我们换成另一位说话人的录音,相似度可能明显下降。
不过,影响声纹匹配的因素有很多:
- 麦克风及录音设备。
- 背景噪声。
- 录音时长。
- 说话语速与情绪。
- 不同语言的发音习惯。
因此,一段安静环境录制的中文音频,与同一个人在嘈杂环境中的英文录音,仍然可能出现较低的相似度。
这也是实际部署时必须进行场景验证的原因。
四、实践案例二:建立一个多人声纹库
第一个案例只是两段录音之间的比较。
但实际业务中,我们通常希望系统具备这样的能力:
新来一段录音,能否从已经注册的人员中找到最接近的说话人?
例如,一个经过授权的企业会议系统,已经保存了三名参与者的参考录音。
当新的语音片段进入系统时,我们希望找到最相似的已注册人员。
4.1 核心架构
人员A参考录音 ──> 声纹向量A ──┐
│
人员B参考录音 ──> 声纹向量B ──┼──> 声纹库
│
人员C参考录音 ──> 声纹向量C ──┘
│
新录音 │
│ │
▼ │
ECAPA-TDNN │
│ │
▼ │
新声纹向量 ────────────────────────┘
│
▼
计算余弦相似度
│
▼
寻找最相似的候选人
│
▼
阈值判断 / 未知说话人
对于每个人,我们可以保存多段参考录音,再对声纹向量进行平均,形成一个更加稳定的声纹模板。
4.2 Python 实现
准备文件:
voices/
├── person_a_1.wav
├── person_a_2.wav
├── person_b_1.wav
├── person_b_2.wav
├── person_c_1.wav
├── person_c_2.wav
└── incoming.wav
所有录音建议统一为 16kHz 单声道 WAV,且每个文件尽量只包含一名说话人。
使用以下代码:
import numpy as np
import torch
from speechbrain.inference.speaker import SpeakerRecognition
# 1. 加载模型
device = "cuda" if torch.cuda.is_available() else "cpu"
model = SpeakerRecognition.from_hparams(
source="speechbrain/spkrec-ecapa-voxceleb",
savedir="pretrained_models/ecapa",
run_opts={"device": device}
)
# 2. 提取声纹向量
def get_embedding(audio_path):
# 官方加载方法,自动处理采样率等
signal = model.load_audio(audio_path)
with torch.no_grad():
embedding = model.encode_batch(
signal.unsqueeze(0)
)
vector = embedding.squeeze().cpu().numpy()
# L2归一化
vector = vector / (
np.linalg.norm(vector) + 1e-12
)
return vector
# 3. 注册声纹
enrollment = {
"person_A": [
"voices/person_a_1.wav",
"voices/person_a_2.wav"
],
"person_B": [
"voices/person_b_1.wav",
"voices/person_b_2.wav"
],
"person_C": [
"voices/person_c_1.wav",
"voices/person_c_2.wav"
]
}
speaker_database = {}
for name, files in enrollment.items():
# 提取同一人的多段声纹
embeddings = [
get_embedding(path)
for path in files
]
# 计算平均声纹模板
template = np.mean(embeddings, axis=0)
template = template / (
np.linalg.norm(template) + 1e-12
)
speaker_database[name] = template
# 4. 识别新录音
query = get_embedding("voices/incoming.wav")
# 5. 与声纹库逐一比较
scores = {}
for name, template in speaker_database.items():
scores[name] = float(
np.dot(query, template)
)
# 6. 相似度排序
ranking = sorted(
scores.items(),
key=lambda x: x[1],
reverse=True
)
for name, score in ranking:
print(f"{name}: {score:.4f}")
# 7. 加入未知说话人拒识
best_name, best_score = ranking[0]
# 仅为实验阈值,正式使用需校准
threshold = 0.55
if best_score >= threshold:
print("最相似的已注册人员:", best_name)
else:
print("未知说话人,需要进一步核验")
4.3 这个案例有什么价值?
这里有两个值得关注的地方。
第一,声纹可以预先计算。
和文本 Embeddings 一样,已经注册的声纹可以提前保存。
新录音到来时,只需提取一次声纹向量,再与已有模板比较。
对于几十人、几百人的小规模声纹库,直接使用 NumPy 就可以完成匹配。
第二,可以支持未知说话人。
如果系统每次都强制从已注册人员中选择一个人,那么即使是一名陌生人,也会得到某个匹配结果。
因此,需要设置拒识阈值。
但代码中的 0.55 只是实验值。由于这里采用了多录音平均模板,其评分分布可能与官方 verify_files() 的评分分布不同,不能直接套用同一阈值。
对于规模更大的声纹库,还可以使用 FAISS 等向量检索工具加速候选搜索。
五、声纹识别实际部署有哪些难点?
5.1 阈值怎么确定?
声纹系统通常存在两类错误:
FAR(False Acceptance Rate):误接受率。
把不同的人错误识别为同一个人。
FRR(False Rejection Rate):误拒绝率。
把同一个人的两段录音错误识别为不同的人。
提高相似度阈值,通常会降低 FAR,但可能提高 FRR。
反过来,降低阈值会减少部分误拒绝,却可能增加冒认成功的风险。
因此,应当准备两类测试数据:
正样本:
同一个人的不同录音
负样本:
不同人的录音组合
然后分别计算不同阈值下的 FAR 和 FRR。
当两者相等时,对应的错误率称为 EER(Equal Error Rate)。
对于身份核验场景,实际阈值还应根据误接受和误拒绝的业务成本选择,不一定采用 EER 对应的阈值。
5.2 声音克隆会不会骗过声纹模型?
这可能是当前声纹识别最值得关注的问题。
此前我们介绍过 Qwen3-TTS,它可以使用少量参考录音复刻一个人的音色。
那么,如果攻击者生成一段高度相似的合成语音,再交给声纹识别模型,会发生什么?
部分声纹模型确实可能受到语音合成、声音转换及录音重放攻击的影响。
这是因为传统声纹模型主要判断声音的特征是否接近,而声音是不是由真人实时发出,属于另一个任务。
因此,在涉及身份认证的系统中,通常还需要额外增加:
- 录音重放与合成语音检测。
- 活体检测或随机口令挑战。
- 其他身份认证因素。
- 设备和异常行为风险检查。
例如,开源的 AASIST 就是专门研究语音反欺骗检测的模型。
但反欺骗模型也会存在误判和对新型伪造手段泛化不足的问题,因此声纹更适合成为多因素认证的一部分。
5.3 中文与英文混合会影响识别吗?
会有可能。
例如,同一个人在说普通话、英语和某些方言时,发声习惯、音素分布和语速可能发生变化。
因此,如果目标是中文客服或跨语言应用,可以同时测试:
| 测试组合 | 目的 |
|---|---|
| 中文 vs 中文 | 中文同语言识别 |
| 英文 vs 英文 | 英文同语言识别 |
| 中文 vs 英文 | 跨语言识别 |
| 不同设备录音 | 跨设备稳健性 |
| 安静 vs 嘈杂 | 抗噪声能力 |
对于以中文为主的业务,还建议额外对比 WeSpeaker 的中文声纹模型。
WeSpeaker 官方支持加载中文预训练模型,并提供 compute_similarity()、register() 和 recognize() 等接口。
六、总结:声纹识别在语音 AI 系统中的位置
本文通过两个案例介绍了声纹识别的核心能力:
- 双录音身份核验: 提取两段录音的 Speaker Embeddings,判断是否可能来自同一个人。
- 多人声纹库匹配: 为已注册人员建立声纹模板,对新录音进行相似度排序和未知人员拒识。
把它与之前的 VAD、ASR 和 Diarization 结合,可以得到一条更完整的语音处理链路:
原始录音
│
▼
VAD 语音检测
│
▼
Diarization 说话人分离
│
┌──────┴──────┐
▼ ▼
ASR 声纹识别
│ │
▼ ▼
转写文本 候选身份
│ │
└──────┬──────┘
▼
带说话人信息的记录
│
▼
LLM / 业务系统
例如,企业会议系统可以先通过 Diarization 区分发言者,再使用事先获得授权的声纹模板,将匿名说话人对应到参会人员,最后用 ASR 生成带发言人标签的会议纪要。
需要留意的是,声纹向量具有生物识别属性,应当妥善保存、限制访问,并在注册前取得相应授权。
最后,用一句话总结这几类模型的区别:
ASR 关注说了什么,Diarization 关注哪些片段属于同一个人,声纹识别则进一步判断声音是否匹配某个已知说话人。
随着 TTS 和声音克隆技术越来越成熟,声纹模型依然具有很高的应用价值,但仅靠“声音听起来像”,已经不足以完成可靠的身份认证。
真正值得研究的下一步,是将 声纹识别、语音反欺骗检测与多因素身份核验 结合起来,构建更可靠的语音身份识别系统。
- 点赞
- 收藏
- 关注作者
评论(0)