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. Có ba engine: Pyannote nhẹ và mặc định, Community-1 với pipeline đầy đủ (CoreML + PLDA/VBx native), và Sortformer cho streaming.

Engine

Chọn --engine pyannote (mặc định), --engine community1, 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ý.

Community-1 (CoreML + VBx native)

Bundle Community-1 CoreML dùng ngữ cảnh 10 giây, bước 1 giây, embedding WeSpeaker có mask, PLDA/VBx native và gán có ràng buộc; trả về centroid 256 chiều cho mỗi người nói. Kiểm tra parity trên năm tệp đạt 4,66% DER / 21,43% JER.

Với bộ chấm điểm theo lưới frame của speech-swift trên cùng tập con, Community-1 đạt 9,31% DER / 16,20% JER, nhanh hơn thời gian thực 25,5× và RSS đỉnh 1,54 GB; engine Pyannote mặc định đạt 5,03% / 9,28%, nhanh hơn thời gian thực 105,7× và 393 MB. Vì vậy Community-1 là pipeline tùy chọn để đạt parity với upstream, có giới hạn số người nói và trả về centroid, không phải là nâng cấp độ chính xác hay tốc độ trong mọi trường hợp. Không thể so sánh trực tiếp kết quả của hai phương pháp chấm điểm.

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).

Với âm thanh trực tiếp, SortformerStreamingSession chạy tăng dần mô hình Sortformer .streaming với bộ đệm PCM mono 16 kHz có kích thước tùy ý. Khi đã có 560 ms ngữ cảnh nhìn trước, phiên tiến theo bước âm thanh 480 ms; thử nghiệm năm tệp trên Neural Engine dòng M đo được độ trễ trung vị 12,4 ms cho mỗi push 500 ms. Nhãn người nói tuân theo Arrival-Order Speaker Cache thay vì gom cụm lại từng cửa sổ, và mỗi push trả về ảnh chụp toàn bộ luồng. finish() xử lý phần đuôi còn trong bộ đệm. Trong kiểm thử tương đương có mô hình, các đoạn cuối khớp với diarization .streaming toàn bộ đệm trong khoảng một khung 80 ms.

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.

Baseline CALLHOME đã công bố

Bảng sau là baseline diarization thô với người nói ẩn danh do NVIDIA công bố cho diar_streaming_sortformer_4spk-v2. Đây là tham chiếu bên ngoài, không phải phép đo cục bộ cho bản chuyển đổi CoreML của speech-swift và không phải kết quả gán tên người nói.

Tập dữ liệuĐiều kiệnDER
CALLHOME Part 22 người nói tham chiếu6,57%
CALLHOME Part 23 người nói tham chiếu10,05%
CALLHOME Part 24 người nói tham chiếu12,44%

Tất cả các hàng dùng độ trễ bộ đệm đầu vào 1,04 giây, tính cả lời nói chồng lấp, collar 0,25 giây và hậu xử lý được tinh chỉnh trên CALLHOME Part 1 tách biệt. CALLHOME cần giấy phép và không được đóng gói. diarization-bench cục bộ báo cáo riêng kết quả tương đương theo số người nói tham chiếu trong terminal và trường JSON byReferenceSpeakerCount.

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
)