You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于WhisperX Speaker Diarization实现跨音频一致说话人识别

问题

我用WhisperX的Speaker Diarization转录固定4位嘉宾的系列播客,生成带时间戳和说话人标注的文本。但单集处理时,不同集的说话人标签(比如SPEAKER_00、SPEAKER_01)无法对应——比如第400集的SPEAKER_00和第423集的SPEAKER_00可能是不同嘉宾。

我目前想到两个思路:

  • 把所有音频合并成大文件处理后拆分结果
  • 写单独脚本分析语音片段,跨文件匹配说话人

有没有更优的方案实现数百个播客音频的一致说话人识别?当前使用的代码如下:

# -*- coding: utf-8 -*-
"""WhisperX_Speaker_Diarization.ipynb

Automatically generated by Colab.

Original file is located at
    https://colab.research.google.com/drive/1IHum-j2AOjVOs_ZoqJ5yBUjf1kI4SLmt

    pip install --q git+https://github.com/m-bain/whisperx.git

Run with:
    python3 Collab\ Notebooks/whisperx_speaker_diarization.py > output/output.log 
"""
import whisperx
import gc
from dotenv import load_dotenv
import os
import json
import time

# Load environment variables from .env file
load_dotenv()

# Get Hugging Face token from environment variable
huggingface_token = os.getenv("HUGGINGFACE_TOKEN")

device = "cuda"
batch_size = 4 # reduce if low on GPU mem
compute_type = "int8" # change from "float16" to "int8" if low on GPU mem (may reduce accuracy)

audio_file = "audio/short_MEGA64_PODCAST_483.mp3"

audio = whisperx.load_audio(audio_file)

model = whisperx.load_model("large-v2", device, compute_type=compute_type)

result = model.transcribe(audio, batch_size=batch_size)
print(result["segments"]) # before alignment

# delete model if low on GPU resources
# import gc; gc.collect(); torch.cuda.empty_cache(); del model

# 2. Align whisper output
model_a, metadata = whisperx.load_align_model(language_code=result["language"], device=device)
result = whisperx.align(result["segments"], model_a, metadata, audio, device, return_char_alignments=False)

result

diarize_model = whisperx.DiarizationPipeline(use_auth_token=huggingface_token,
                                             device=device)

diarize_segments = diarize_model(audio, min_speakers=1, max_speakers=8)

diarize_segments

diarize_segments.speaker.unique()

result = whisperx.assign_word_speakers(diarize_segments, result)
print(diarize_segments)
# print(result["segments"]) # segments are now assigned speaker IDs

# Save the result to a JSON file with a unique filename
timestamp = int(time.time() * 1000)
audio_filename = os.path.basename(audio_file).split('.')[0]
output_filename = f"speaker_timestamps_{audio_filename}_{timestamp}.json"

with open(output_filename, 'w') as f:
    json.dump(result, f)

print(f"Results saved to {output_filename}")

更优解决方案

1. 基于说话人嵌入的全局匹配(推荐)

WhisperX底层依赖的pyannote.audio可以提取说话人的语音嵌入(代表语音特征的向量),核心思路是建立一个全局的嘉宾特征库,再将单集的说话人特征与库中匹配,实现统一标签:

  • 步骤1:构建全局嘉宾特征库
    从已处理的播客中,为每个说话人抽取1-2分钟清晰的语音片段,用pyannote的嵌入模型计算每个嘉宾的平均嵌入向量,保存为全局特征库(比如用Numpy文件存储)。
  • 步骤2:单集处理时匹配全局特征
    处理新集时,提取当前所有说话人的嵌入向量,与全局库中的向量计算余弦相似度,找到最匹配的嘉宾标签,替换原有的SPEAKER_XX临时标签。

这种方案GPU资源占用可控,适合批量处理数百集,且匹配准确率较高。

2. Few-Shot说话人验证(若有已知样本)

如果有嘉宾的已知语音样本(比如单独的自我介绍录音、某一集里明确标注的片段),可以直接用pyannote的Few-Shot验证功能,在处理每一集时,用已知样本和当前集的说话人片段做匹配,直接映射到对应嘉宾标签,无需提前构建全局库。

3. 优化合并拆分方案(不推荐)

如果坚持用合并音频的思路,需注意:

  • 合并时给每个原音频之间添加5秒以上的静音,方便后续识别拆分边界
  • 处理后根据静音位置拆分结果,避免说话人标签跨集混淆
    但该方案对GPU内存要求极高,数百集的合并文件体积过大,实际操作可行性低。

代码修改示例(全局匹配方案)

在原有代码基础上添加说话人嵌入匹配逻辑:

# 新增依赖导入
from pyannote.audio import Model
from pyannote.audio.pipelines import SpeakerVerification
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

# 预加载说话人嵌入模型(需Hugging Face权限)
embedding_model = Model.from_pretrained(
    "pyannote/speaker-diarization-3.1",
    use_auth_token=huggingface_token
)
verification_pipeline = SpeakerVerification(embedding_model)

# 假设已提前构建全局嘉宾嵌入库(示例)
# 实际使用时需从已处理的播客中提取并保存
global_embeddings = {
    "Guest_A": np.load("guest_a_embedding.npy"),
    "Guest_B": np.load("guest_b_embedding.npy"),
    "Guest_C": np.load("guest_c_embedding.npy"),
    "Guest_D": np.load("guest_d_embedding.npy")
}

def map_speaker_labels(diarize_segments, global_embeddings, audio):
    speaker_embeddings = {}
    # 提取当前集每个说话人的嵌入向量
    for speaker in diarize_segments.speaker.unique():
        # 取该说话人第一个清晰的语音片段
        speaker_segments = diarize_segments[diarize_segments.speaker == speaker]
        start = speaker_segments.iloc[0]["start"]
        end = speaker_segments.iloc[0]["end"]
        # 提取对应音频片段(pyannote默认16kHz采样率)
        speaker_audio = audio[int(start*16000):int(end*16000)]
        # 计算嵌入
        embedding = verification_pipeline.embed(speaker_audio)
        speaker_embeddings[speaker] = embedding
    
    # 匹配全局嵌入,生成标签映射
    label_mapping = {}
    for local_speaker, local_emb in speaker_embeddings.items():
        max_sim = -1
        best_match = None
        for global_label, global_emb in global_embeddings.items():
            sim = cosine_similarity([local_emb], [global_emb])[0][0]
            if sim > max_sim:
                max_sim = sim
                best_match = global_label
        label_mapping[local_speaker] = best_match
    
    # 替换原标签
    diarize_segments["speaker"] = diarize_segments["speaker"].map(label_mapping)
    return diarize_segments

# 在原有代码生成diarize_segments后调用
diarize_segments = map_speaker_labels(diarize_segments, global_embeddings, audio)

# 后续继续执行assign_word_speakers和保存逻辑
result = whisperx.assign_word_speakers(diarize_segments, result)

内容的提问来源于stack exchange,提问作者Martin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 15:49:51