从 VAD、声纹嵌入到聚类优化的一套解决思路

做语音转写项目时,说话人聚类(Speaker Clustering / Speaker Diarization)往往比 ASR 本身更容易踩坑。

安静的两个人轮流讲话,效果通常不会太差。但一旦进入真实会议、多人讨论、电话录音或者嘈杂环境,问题马上就暴露出来:

  • A 和 B 同时说话时,只识别出了 A;
  • 一个人刚说完,下一句话被错误归到了另一个人;
  • 同一个人在不同时间段被拆成了多个 Speaker;
  • 3 个人的会议被聚类成 4~5 个人;
  • 说话时间比较短的人,经常被归到其他人的类别;
  • 背景噪声、键盘声、笑声加入后,聚类结果明显变差。

这类问题不能简单归结为"换一个聚类算法"就能解决。

实际工程中,说话人聚类更像是一条完整流水线:

现在一些成熟的说话人识别方案已经开始重点优化"说话人分配"和"重叠语音"问题。例如 pyannote 的新版本在说话人分配和聚类方面引入了 VBx,并提供了 exclusive diarization 来降低声纹时间戳和 ASR 时间戳之间的对齐难度。

下面结合实际工程中比较常见的问题,整理一套比较容易落地的优化思路。

二、问题一:重叠语音导致说话人识别错误

这是最棘手的问题之一。

比如会议中出现:

Speaker A:这个方案我觉得------

Speaker B:我同意,而且还可以------

Speaker A:对,所以我们可以继续......

如果直接把音频切成固定窗口,然后提取 Speaker Embedding,那么中间这一段实际上包含两个说话人的声音。

假设得到:

Embedding = f(A + B)

这个向量既不像 A,也不像 B。

如果直接送进聚类算法,就很容易出现:

A + B → Cluster 1 甚至出现:

A → Cluster 1

B → Cluster 1

A → Cluster 2

最终表现出来就是"串人"。

解决方案:增加 OSD(Overlapped Speech Detection)

不要把所有 Speech Activity 都当成普通单人语音。

可以把语音区域进一步划分:

工程上建议至少保留一个 overlap 标记:

复制代码
segment =` `{`
    `"start":` `12.35,`
    `"end":` `14.82,`
    `"speaker_count":` `2,`
    `"overlap":` `True`
`}`
`

这样后面的聚类就不会把所有音频"一视同仁"。

pyannote 的现有 pipeline 也提供了对 overlap 的处理能力,并且其实现中可以在提取 embedding 时排除重叠区域,从而降低混合语音对 Speaker Embedding 的污染。

三、不要直接使用重叠区域生成声纹向量

这是实际优化中非常有效的一步。

假设:

0s ───────────── 10s

A A A A A

A+B

B B B

不要直接:

复制代码
embedding = encoder(audio[0:10])`
`

而应该优先选择:

A 的纯净区域 → embedding_A B 的纯净区域 → embedding_B 代码可以简单写成:

复制代码
import numpy as np`

`def` `remove_overlap(mask, overlap_mask):`
    `"""`
`    删除重叠语音帧`
`    mask: 当前说话人的语音 mask`
`    overlap_mask: overlap mask`
`    """`
`    clean_mask = mask.copy()`
`    clean_mask[overlap_mask >` `0]` `=` `0`
    `return clean_mask`
`

然后只使用:

复制代码
clean_audio = audio[clean_mask]`
`embedding = speaker_encoder(clean_audio)`
`

这样得到的 embedding 通常会比"整段语音直接提取"稳定。

四、问题二:固定窗口切分,容易把两个人切到一起

很多项目一开始会采用:

5 秒一个窗口 10 秒一个窗口 例如:

复制代码
window_size =` `5`

`for start in` `range(0,` `len(audio), window_size):`
`    chunk = audio[start:start + window_size]`
`

这种方法实现简单,但存在明显问题。

假设:

0~3s A

3~5s B

那么第二个窗口:

0~5s = A + B

最终得到的 embedding 就是混合向量。

更合理的方法:滑动窗口 + 重叠

例如:

窗口:2s 步长:0.5s 形成:

0.0 ── 2.0

0.5 ── 2.5

1.0 ── 3.0

1.5 ── 3.5

...

这样可以提高时间分辨率。

pyannote 的 pipeline 同样采用滑动窗口进行 segmentation,并允许控制 segmentation step。

不过这里还有一个关键点:

窗口越短不一定越好。

窗口太短:

0.3s

0.5s

声纹特征不够稳定。

窗口太长:

10s

20s

又容易混入多个说话人。

实际项目通常需要根据录音类型,在"时间分辨率"和"Embedding 稳定性"之间做平衡。

五、问题三:Speaker Embedding 质量直接决定聚类上限

说话人聚类本质上不是直接比较音频,而是在比较:

Speaker Embedding 例如:

A.wav → 0.12, -0.23, 0.44, ... B.wav → 0.18, -0.19, 0.40, ... C.wav → -0.51, 0.31, -0.12, ... 常见的 Speaker Embedding 模型包括:

  • ECAPA-TDNN
  • x-vector
  • ResNet-based Speaker Encoder
  • WavLM
  • CAM++
  • ERes2Net 等

工程上常用余弦相似度:

Python 实现:

复制代码
import numpy as np`

`def` `cosine_similarity(x, y):`
`    x = np.asarray(x)`
`    y = np.asarray(y)`

    `return np.dot(x, y)` `/` `(`
`        np.linalg.norm(x)` `* np.linalg.norm(y)`
    `)`
`

例如:

复制代码
score = cosine_similarity(embedding_a, embedding_b)`

`if score >` `0.75:`
    `print("可能属于同一说话人")`
`

但这里不要迷信固定阈值。

不同模型、不同录音设备、不同噪声环境,对应的最佳阈值可能完全不同。

六、问题四:聚类算法本身也会影响结果

最常见的是:

Agglomerative Clustering 即层次聚类。

基本思想比较简单:

例如:

复制代码
from sklearn.cluster import AgglomerativeClustering`

`model = AgglomerativeClustering(`
`    n_clusters=3,`
`    metric="cosine",`
`    linkage="average"`
`)`

`labels = model.fit_predict(embeddings)`
`

但对于复杂会议场景,仅靠传统 HAC 往往不够。

目前一些较新的 diarization pipeline 已经采用 VBx 等聚类方法改善 speaker assignment 和 counting。pyannote 的 Community-1 就将原有方案中的层次聚类切换到了 VBx。

因此,如果项目长期存在:

Speaker 数量判断错误 Speaker ID 不稳定 相似音色容易串人 可以重点考虑:

逐步进行实验。

七、问题五:复杂场景下不能只看"声纹距离"

真实会议里,经常出现这种情况:

如果只按照 embedding 距离判断,很容易出现:

A → 0

B → 1

A → 2

C → 3

实际上:

0 和 2 都是 A 这时候需要加入时间连续性约束

可以设计一个简单的综合评分:

其中:

  • (S_{embedding}):声纹相似度
  • (S_{temporal}):时间连续性
  • (S_{energy}):能量特征
  • (\alpha,\beta,\gamma):权重

例如:

复制代码
def` `speaker_score(`
`    embedding_score,`
`    temporal_score,`
`    energy_score,`
`    alpha=0.7,`
`    beta=0.2,`
`    gamma=0.1`
`):`
    `return` `(`
`        alpha * embedding_score +`
`        beta * temporal_score +`
`        gamma * energy_score`
    `)`
`

这样可以减少"同一个人被切成多个 Speaker"的情况。

八、问题六:短语音是聚类里的一个大坑

比如:

Speaker A:嗯。

Speaker B:好的。

Speaker A:对。

只有:

0.2~0.5 秒 这样的音频,通常不适合单独建立一个新的 Speaker Cluster。

建议增加最小语音时长:

复制代码
MIN_DURATION =` `0.8`

`def` `valid_segment(start, end):`
    `return` `(end - start)` `>= MIN_DURATION`
`

短语音可以:

这比简单删除短语音更加合理。

九、问题七:同一个人被识别成多个 Speaker

这种情况在长会议里很常见。

例如:

Speaker_0

Speaker_1

Speaker_0

Speaker_3

Speaker_1

实际上可能只有:

A B A C B 解决办法之一是增加 Cluster Merge

计算两个 Cluster 的中心向量:

然后计算:

如果:

复制代码
similarity > merge_threshold:`
`    merge(cluster_i, cluster_j)`
`

示例:

复制代码
def` `cluster_center(embeddings):`
`    center = np.mean(embeddings, axis=0)`
    `return center / np.linalg.norm(center)`


`def` `should_merge(c1, c2, threshold=0.82):`
    `return cosine_similarity(c1, c2)` `>= threshold`
`

这里仍然建议通过真实业务数据进行阈值标定,而不是直接照搬某个项目的数字。

十、复杂环境下增加音频前处理

如果原始音频质量差,再好的聚类模型也很难发挥。

建议增加:

例如统一到:

16 kHz Mono PCM Python 可以简单检查:

复制代码
import soundfile as sf`

`audio, sr = sf.read("meeting.wav")`

`print("sample rate:", sr)`
`print("shape:", audio.shape)`
`

如果输入设备很多,还要特别注意:

  • 麦克风距离;
  • 左右声道音量差;
  • 自动增益;
  • 回声;
  • 混响;
  • 背景音乐;
  • 键盘声;
  • 空调噪声。

实际项目中,采集质量往往比后面调几个聚类参数更加重要。

十一、一个比较实用的工程架构

如果让我重新设计一套多人会议说话人识别流程,我会倾向于下面这种结构:

这个架构有一个非常重要的思想:

不要试图让一个模型解决所有问题。

VAD 负责"有没有人在说话"。

OSD 负责"是不是有人同时说话"。

Speaker Encoder 负责"这个声音像谁"。

Clustering 负责"哪些声音属于同一个人"。

Temporal Smoothing 负责"前后是不是同一个人"。

ASR 负责"他说了什么"。

职责拆开之后,定位问题会容易很多。

十二、如何判断到底是哪一环出了问题?

实际排查时,可以建立一张错误分类表:

|--------------------|-------------------------------|
| 问题表现 | 优先检查模块 |
| 安静环境也串人 | Speaker Embedding |
| 同一个人被拆成多个 ID | Clustering |
| 两个人同时讲话只剩一个人 | OSD |
| 短句经常被归错 | Segment / Embedding |
| 前后 Speaker ID 跳变 | Temporal Smoothing |
| 噪声环境明显变差 | Audio Enhancement |
| ASR 字对了但 Speaker 错 | Diarization ↔ ASR Alignment |
| Speaker 数量经常错误 | Clustering / Speaker Counting |

这张表在工程排查中非常有用。

不要看到"说话人错了"就直接重新训练模型。

先把错误定位到具体模块,再决定是调参数、换模型还是重新训练。

十三、评价指标不要只看准确率

说话人聚类最好使用 DER(Diarization Error Rate)

通常可以理解为:

其中:

  • Miss:应该检测到但没有检测到;
  • False Alarm:非语音区域被错误检测;
  • Confusion:检测到了,但 Speaker 分错了。

因此:

DER ↓

通常意味着整体 diarization 效果更好。

同时建议单独统计:

Overlap DER Speaker Confusion Miss Rate False Alarm Rate Speaker Counting Error 因为如果只看总 DER,很难知道到底是:

VAD 不行 还是:

聚类不行 或者:

重叠语音处理不行 一些公开 benchmark 也会在不跳过 overlap 的情况下报告 DER,这对于评估复杂会议场景尤其有参考意义。

十四、在实际项目中的推荐优化顺序

如果当前系统已经上线,不建议一上来就大改模型。

可以按照下面的顺序逐步处理:

第一阶段:先把数据弄干净

统一采样率

统一声道

音量归一

第二阶段:解决重叠语音

增加 OSD

排除 overlap embedding

必要时加入 speech separation

第三阶段:优化 Speaker Embedding

可以比较:

ECAPA-TDNN

CAM++

WavLM

ResNet Speaker Encoder

选择真正适合当前数据的模型。

第四阶段:优化聚类

从:

AHC 逐步尝试:

AHC + PLDA VBx

第五阶段:增加后处理

Cluster Merge

Temporal Smoothing

Short Segment Reassignment

Speaker ID Tracking

第六阶段:处理 ASR 对齐

最后再做:

这样最终得到:

00:01:23 Speaker_01:

这个方案我们可以继续讨论一下。

00:01:27 Speaker_02:

我觉得还有一个问题需要考虑。

00:01:30 Speaker_01:

对,这一点确实需要补充。

十五、和实际会议系统结合时的一点经验

在实际的离线会议转写系统中,说话人聚类通常不是独立存在的。

它前面连接:

后面连接:

因此,不能只追求某一个模块的实验室指标。

比如:

Diarization DER 降低了 但如果导致:

ASR 时间戳对不上 最终用户看到的会议纪要仍然可能是错的。

这也是为什么现在一些 diarization pipeline 会提供 exclusive diarization,用来把复杂的重叠说话结果转换成更容易和 STT 时间戳对齐的形式。

在类似熙瑾会悟这样的会议语音场景中,如果最终目标是"谁说了什么",那么评价标准就不应该只停留在声纹聚类本身,而应该关注:

这才是一套完整的工程链路。

十六、最后总结

说话人聚类遇到"重叠语音识别错误、复杂场景效果差",通常不是简单修改一个 clustering threshold 就能解决。

比较推荐的整体思路可以归纳成:

其中最值得优先处理的几个点是:

  1. 不要让重叠语音直接污染 Speaker Embedding;
  2. 不要过度依赖固定窗口切分;
  3. 短语音不要轻易建立新的 Speaker Cluster;
  4. 聚类后增加 Cluster Merge;
  5. 通过时间连续性减少 Speaker ID 跳变;
  6. 使用 DER、Confusion、Overlap Error 等指标分别定位问题;
  7. 复杂场景下考虑 AHC、PLDA、VBx 等不同聚类方案;
  8. 最终一定要和 ASR 时间戳进行联合验证。

从工程角度看,真正稳定的说话人识别系统,往往不是"换了一个更大的模型"之后突然变好了,而是把 VAD、OSD、Embedding、Clustering、后处理和 ASR 对齐 这些环节逐个拆开,再针对错误类型逐项优化。

这也是说话人聚类从 Demo 走向实际生产环境时,最值得投入精力的地方。

相关推荐
YOLO数据集集合18 分钟前
垃圾目标检测数据集 |垃圾检测 智慧环卫 城市管理 生活垃圾堆检测 生活垃圾 垃圾场 9023期
人工智能·目标检测·视觉检测·生活·垃圾检测·垃圾
cxr82836 分钟前
混沌背后的秩序:T-Π-O 框架与复杂系统理解的认识论重构
人工智能·认知框架
sramdram1 小时前
语音识别芯片类别有哪些?离线语音识别芯片交互方案分析
人工智能·交互·语音识别·语音识别芯片·离线语音识别芯片
lisw051 小时前
用于信息超材料设计的生成模型!
人工智能·材料科学
资深电气设计1 小时前
堆垛机变频器项目落地观察:FC360变频驱动方案在中鼎科技堆垛机中的应用成效
大数据·人工智能·科技
xwz小王子1 小时前
综述:机器人要学会“接触世界”,光有视觉远远不够
人工智能·机器人
cd_949217211 小时前
哪些AI纹理生成工具适合专业3D角色制作?
人工智能·3d
Profile排查笔记1 小时前
指纹浏览器怎么设置 IP?代理配置、检测与排错流程
前端·人工智能·后端·自动化
冬奇Lab2 小时前
Code Agent 解剖(13):Harness 设计之三——工具执行管道
人工智能