Phân tách người nói

Xác định ai đã nói lúc nào trong một bản ghi có nhiều người nói. Hai engine phân tách người nói có sẵn: pipeline Pyannote hai giai đoạn (segmentation + chuỗi hóa người nói dựa trên hoạt động, sau đó embedding hậu kỳ) và mô hình Sortformer end-to-end (CoreML, Neural Engine).

Engine

Chọn engine bằng --engine pyannote (mặc định) hoặc --engine sortformer.

Pyannote (mặc định)

Pipeline hai giai đoạn: Pyannote segmentation xử lý các cửa sổ chồng lấn với chuỗi hóa người nói dựa trên hoạt động (tương quan Pearson trong vùng chồng lấn) để gán nhãn người nói toàn cục. Trích xuất embedding WeSpeaker hậu kỳ cho phép nhận dạng người nói mục tiêu thông qua âm thanh đăng ký.

Sortformer (CoreML)

Mô hình phân tách người nói neural end-to-end của NVIDIA. Dự đoán trực tiếp hoạt động của tối đa 4 người nói theo từng frame mà không cần các giai đoạn embedding hay clustering riêng. Chạy trên Neural Engine qua CoreML với các buffer trạng thái streaming (FIFO + cache người nói).

Lưu ý

Sortformer không sinh ra embedding người nói. Các cờ --target-speaker--embedding-engine chỉ có sẵn với engine Pyannote.

Pipeline Pyannote

Pipeline mặc định chạy theo hai giai đoạn:

Giai đoạn 1: Segmentation + chuỗi hóa người nói

Pyannote segmentation-3.0 xử lý cửa sổ trượt 10 giây với độ chồng lấn 50%. Một decoder powerset chuyển đầu ra 7 lớp thành xác suất theo từng người nói (tối đa 3 người nói cục bộ mỗi cửa sổ). Các cửa sổ liền kề chia sẻ vùng chồng lấn 5 giây — danh tính người nói được lan truyền giữa các cửa sổ bằng cách tính tương quan Pearson giữa các quỹ đạo xác suất trong vùng chồng lấn, kèm matching tham lam độc quyền để có ID người nói toàn cục nhất quán.

Giai đoạn 2: Embedding hậu kỳ

Sau khi phân tách, WeSpeaker ResNet34-LM trích xuất một embedding centroid 256 chiều cho mỗi người nói. Các embedding này cho phép trích xuất người nói mục tiêu (--target-speaker) nhưng không quyết định việc gán người nói.

Chuyển từ pyannote.audio

Nếu bạn đến từ thư viện Python pyannote.audio — thay thế một lớp con của Pipeline đặt pipeline.segmentation = ..., hoặc chuyển khỏi một server đang chạy pyannote/speaker-diarization-3.1 — Soniqo bao bọc chính mô hình Pyannote-Segmentation-3.0 và chạy hoàn toàn trên thiết bị Apple Silicon. Không cần Python runtime, không cần CUDA, không cần Hugging Face token khi suy luận.

pyannote.audio (Python)Soniqo (Swift)
Pipeline.from_pretrained("pyannote/speaker-diarization-3.1") DiarizationPipeline.fromPretrained()
pipeline(audio_file) pipeline.diarize(audio: samples, sampleRate: 16000)
pipeline.segmentation = ... (lớp con tùy biến) Cố định: Pyannote-Segmentation-3.0 (MLX hoặc CoreML, tự chọn)
diarization.itertracks(yield_label=True) for seg in result.segments { ... }
diarization.write_rttm(file) CLI: --rttm
pyannote.metrics.diarization.DiarizationErrorRate CLI: --score-against reference.rttm

Trọng số Pyannote-Segmentation-3.0 được chuyển đổi từ checkpoint upstream của HuggingFace, do đó logits segmentation tương đương về mặt số trong giới hạn dung sai của float. Phần chuỗi hóa hậu segmentation (tương quan Pearson trên cửa sổ chồng lấn + matching tham lam độc quyền) và các giai đoạn embedding hậu kỳ với WeSpeaker được cài lại trong Swift nhưng tạo ra đầu ra RTTM tương đương với pipeline Python tham chiếu.

Chưa được hỗ trợ

Chưa có phiên bản streaming OnlineSpeakerDiarization cho engine Pyannote. Để phân tách người nói theo thời gian thực, dùng --engine sortformer thay thế, engine này chạy mô hình Sortformer với các buffer trạng thái FIFO + cache người nói.

Sử dụng CLI

# Basic diarization (pyannote, default)
.build/release/speech diarize meeting.wav

# End-to-end Sortformer (CoreML)
.build/release/speech diarize meeting.wav --engine sortformer

# RTTM output format (for evaluation)
.build/release/speech diarize meeting.wav --rttm

# JSON output
.build/release/speech diarize meeting.wav --json

Trích xuất người nói mục tiêu

Cung cấp âm thanh đăng ký của một người nói đã biết để chỉ trích xuất các đoạn của họ khỏi bản ghi. Pipeline tính embedding của âm thanh đăng ký và tìm cluster có độ tương tự cosine cao nhất.

# Extract segments for a specific speaker
.build/release/speech diarize meeting.wav --target-speaker enrollment.wav

Chấm điểm DER

Đánh giá chất lượng phân tách bằng cách so với một file RTTM tham chiếu. Pipeline tính Diarization Error Rate (DER), đại lượng đo tỉ lệ thời gian bị gán nhãn sai.

# Score against reference RTTM
.build/release/speech diarize meeting.wav --score-against reference.rttm

Đầu ra RTTM

Cờ --rttm tạo đầu ra Rich Transcription Time Marked, một định dạng chuẩn dùng để đánh giá phân tách người nói. Mỗi dòng theo định dạng:

SPEAKER filename 1 start_time duration <NA> <NA> speaker_id <NA> <NA>

Tùy chọn

Tùy chọnMô tả
--target-speakerÂm thanh đăng ký để trích xuất người nói mục tiêu (chỉ pyannote)
--embedding-engineEngine embedding người nói: mlx hoặc coreml (chỉ pyannote)
--vad-filterLọc trước bằng Silero VAD (chỉ pyannote)
--rttmĐầu ra theo định dạng RTTM
--jsonĐịnh dạng đầu ra JSON
--score-againstFile RTTM tham chiếu để đánh giá DER
Quan trọng

Phân tách người nói hoạt động tốt nhất với bản ghi có các lượt nói rõ ràng. Tiếng nói chồng lấn cao có thể làm giảm độ chính xác. Số lượng người nói được xác định tự động.

Tải mô hình

Mô hình được tải tự động ở lần dùng đầu tiên:

Thành phầnMô hìnhKích thướcHuggingFace
SegmentationPyannote-Segmentation-3.0~5.7 MBaufklarer/Pyannote-Segmentation-MLX
Embedding người nóiWeSpeaker-ResNet34-LM (MLX)~25 MBaufklarer/WeSpeaker-ResNet34-LM-MLX
Embedding người nóiWeSpeaker-ResNet34-LM (CoreML)~25 MBaufklarer/WeSpeaker-ResNet34-LM-CoreML
SortformerSortformer Diarization (CoreML)~240 MBaufklarer/Sortformer-Diarization-CoreML

API Swift

import SpeechVAD

let pipeline = try await DiarizationPipeline.fromPretrained()
let result = pipeline.diarize(audio: samples, sampleRate: 16000)
for seg in result.segments {
    print("Speaker \(seg.speakerId): [\(seg.startTime)s - \(seg.endTime)s]")
}

// Target speaker extraction
let targetEmb = pipeline.embeddingModel.embed(audio: enrollmentAudio, sampleRate: 16000)
let segments = pipeline.extractSpeaker(
    audio: meetingAudio, sampleRate: 16000,
    targetEmbedding: targetEmb
)