说话人嵌入向量

使用 WeSpeaker ResNet34-LM 提取 256 维 L2 归一化的说话人向量。这些 embedding 捕获说话人独特的声学特征,可用于识别、验证和声音检索。

架构

WeSpeaker ResNet34-LM 是一个深度残差网络,用于说话人表示学习。

阶段细节
输入Conv2d(1 到 32 通道)
ResNet34[3, 4, 6, 3] 残差块
统计池化时间维度上的均值 + 标准差
投影Linear(5120 到 256)
输出L2 归一化的 256 维 embedding

模型大小:约 6.6M 参数,磁盘约 25 MB。

Mel 特征

模型使用由 Hamming 窗计算的 80 维 mel 频率特征。对数缩放使用简单的 log(max(mel, 1e-10)) 公式,无需额外归一化。为提高推理效率,batch normalization 在转换时就已融合到 Conv2d 层中。

CLI 使用

# Extract speaker embedding
.build/release/speech embed-speaker voice.wav

# JSON output (includes the 256-dim vector)
.build/release/speech embed-speaker voice.wav --json

# Choose inference engine
.build/release/speech embed-speaker voice.wav --engine coreml

选项

选项说明
--engineWeSpeaker 使用 mlxcoreml;命名身份使用 redimnet2;CosyVoice 克隆使用 camplusplus
--json含完整 embedding 向量的 JSON 输出格式

使用 ReDimNet2-B6 进行命名语音身份识别

使用 --engine redimnet2 在不同录音之间匹配干净的语音。它从至少两秒的语音中返回 192 维归一化 embedding。固定的六秒 CoreML 窗口会重复较短的样本,并对较长的样本进行中心裁剪。

独立的 embedding 空间

ReDimNet2 用于命名语音配置文件。说话人分离继续使用其自己的 WeSpeaker embedding。ReDimNet2、独立 WeSpeaker 和 Community-1 centroid 不能相互比较。

使用场景

说话人验证

对比两段音频,判断是否来自同一个说话人。分别提取 embedding 并计算余弦相似度。相似度越高,属于同一说话人的概率越大。

import SpeechVAD

let model = try await WeSpeakerModel.fromPretrained()

let samples1: [Float] = loadAudio("sample1.wav")
let samples2: [Float] = loadAudio("sample2.wav")
let embedding1 = model.embed(audio: samples1, sampleRate: 16000)
let embedding2 = model.embed(audio: samples2, sampleRate: 16000)

let similarity = WeSpeakerModel.cosineSimilarity(embedding1, embedding2)
print("Similarity: \(similarity)")  // > 0.7 typically same speaker

说话人识别

将一段未知音频与已注册的说话人 embedding 数据库匹配。余弦相似度最高的已注册说话人即为预测身份。

声音检索

按说话人 embedding 为一组音频录音建立索引,然后用一段新音频查询,找出所有同一说话人的录音。

重要

说话人嵌入向量在至少 2-3 秒的干净语音上效果最佳。非常短的片段或嘈杂录音可能会得到不太可靠的 embedding。对于嘈杂音频,可考虑先应用语音增强

模型下载

模型后端大小HuggingFace
WeSpeaker-ResNet34-LMMLX~25 MBaufklarer/WeSpeaker-ResNet34-LM-MLX
WeSpeaker-ResNet34-LMCoreML~25 MBaufklarer/WeSpeaker-ResNet34-LM-CoreML
ReDimNet2-B6CoreML~25 MiBaufklarer/ReDimNet2-B6-CoreML

Swift API

import SpeechVAD

let model = try await WeSpeakerModel.fromPretrained()

// Extract embedding from audio samples
let samples: [Float] = loadAudio("voice.wav")
let embedding = model.embed(audio: samples, sampleRate: 16000)
print("Embedding dimensions: \(embedding.count)")  // 256