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).
Sortformer không sinh ra embedding người nói. Các cờ --target-speaker và --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ó 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ọn | Mô tả |
|---|---|
--target-speaker | Âm thanh đăng ký để trích xuất người nói mục tiêu (chỉ pyannote) |
--embedding-engine | Engine embedding người nói: mlx hoặc coreml (chỉ pyannote) |
--vad-filter | Lọ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-against | File RTTM tham chiếu để đánh giá DER |
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ần | Mô hình | Kích thước | HuggingFace |
|---|---|---|---|
| Segmentation | Pyannote-Segmentation-3.0 | ~5.7 MB | aufklarer/Pyannote-Segmentation-MLX |
| Embedding người nói | WeSpeaker-ResNet34-LM (MLX) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-MLX |
| Embedding người nói | WeSpeaker-ResNet34-LM (CoreML) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-CoreML |
| Sortformer | Sortformer Diarization (CoreML) | ~240 MB | aufklarer/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
)