การแยกผู้พูด
ระบุว่าใครพูดเมื่อใดในไฟล์บันทึกที่มีผู้พูดหลายคน มีสามเอนจิน: Pyannote แบบเบาและเป็นค่าเริ่มต้น, Community-1 แบบเต็มไปป์ไลน์ (CoreML + PLDA/VBx แบบ native) และ Sortformer สำหรับ streaming
เอนจิน
เลือก --engine pyannote (ค่าเริ่มต้น), --engine community1 หรือ --engine sortformer
Pyannote (ค่าเริ่มต้น)
Pipeline สองขั้นตอน: Pyannote segmentation ประมวลผลหน้าต่างที่ซ้อนทับด้วยการเรียงลำดับผู้พูดตามกิจกรรม (Pearson correlation ในบริเวณที่ซ้อนทับ) เพื่อกำหนดป้ายผู้พูดในระดับ global การสกัด embedding ของ WeSpeaker ภายหลังช่วยให้สามารถระบุผู้พูดเป้าหมายผ่านเสียงลงทะเบียน
Community-1 (CoreML + VBx แบบ native)
แพ็กเกจ Community-1 CoreML ใช้บริบท 10 วินาที ก้าว 1 วินาที embedding ของ WeSpeaker แบบมี mask, PLDA/VBx แบบ native และการจับคู่แบบมีข้อจำกัด พร้อมคืน centroid 256 มิติสำหรับผู้พูดแต่ละคน การตรวจสอบ parity ห้าไฟล์ได้ 4.66% DER / 21.43% JER
เมื่อใช้ตัวให้คะแนนแบบ frame-grid ของ speech-swift กับชุดย่อยเดียวกัน Community-1 ได้ 9.31% DER / 16.20% JER, 25.5× real-time และ RSS สูงสุด 1.54 GB; เอนจิน Pyannote ค่าเริ่มต้นได้ 5.03% / 9.28%, 105.7× real-time และ 393 MB ดังนั้น Community-1 จึงเป็นไปป์ไลน์ทางเลือกเพื่อให้ตรงกับ upstream ซึ่งรองรับขอบเขตจำนวนผู้พูดและคืนค่า centroid ไม่ใช่การอัปเกรดความแม่นยำหรือความเร็วในทุกกรณี ผลจากวิธีให้คะแนนทั้งสองไม่สามารถเปรียบเทียบกันโดยตรงได้
Sortformer (CoreML)
โมเดลการแยกผู้พูดแบบ neural end-to-end ของ NVIDIA ทำนายกิจกรรมของผู้พูดสูงสุด 4 คนต่อ frame โดยตรง โดยไม่ต้องมีขั้นตอน embedding หรือ clustering แยก ทำงานบน Neural Engine ผ่าน CoreML ด้วยบัฟเฟอร์สถานะ streaming (FIFO + cache ผู้พูด)
สำหรับเสียงสด SortformerStreamingSession รันโมเดล Sortformer แบบ .streaming เพิ่มทีละส่วนด้วยบัฟเฟอร์ PCM โมโน 16 kHz ขนาดใดก็ได้ เมื่อมีบริบทล่วงหน้า 560 ms แล้ว ระบบจะเดินหน้าทีละช่วงเสียง 480 ms โดยการทดลองห้าไฟล์บน Neural Engine ตระกูล M วัดค่ามัธยฐานของเวลาแฝงได้ 12.4 ms ต่อการ push 500 ms ป้ายกำกับผู้พูดอิง Arrival-Order Speaker Cache แทนการจัดกลุ่มแต่ละหน้าต่างใหม่ และทุก push จะคืนสแนปช็อตของทั้งสตรีม finish() จะประมวลผลส่วนท้ายที่ค้างในบัฟเฟอร์ ในการทดสอบความเท่าเทียมด้วยโมเดล เซกเมนต์สุดท้ายตรงกับ diarization แบบเต็มบัฟเฟอร์ของ .streaming ภายในประมาณหนึ่งเฟรม 80 ms
Sortformer ไม่สร้าง embedding ของผู้พูด แฟล็ก --target-speaker และ --embedding-engine มีให้ใช้เฉพาะกับเอนจิน Pyannote เท่านั้น
ค่า baseline ของ CALLHOME ที่เผยแพร่แล้ว
ตารางนี้เป็นค่า baseline การแยกผู้พูดแบบไม่ระบุตัวตนที่ NVIDIA เผยแพร่สำหรับ diar_streaming_sortformer_4spk-v2 เป็นข้อมูลอ้างอิงภายนอก ไม่ใช่ผลวัดในเครื่องของการแปลง CoreML ใน speech-swift และไม่ใช่ผลการระบุชื่อผู้พูด
| ชุดข้อมูล | เงื่อนไข | DER |
|---|---|---|
| CALLHOME Part 2 | ผู้พูดอ้างอิง 2 คน | 6.57% |
| CALLHOME Part 2 | ผู้พูดอ้างอิง 3 คน | 10.05% |
| CALLHOME Part 2 | ผู้พูดอ้างอิง 4 คน | 12.44% |
ทุกแถวใช้เวลาแฝงของบัฟเฟอร์อินพุต 1.04 วินาที รวมเสียงพูดซ้อน ใช้ collar 0.25 วินาที และ post-processing ที่ปรับด้วย CALLHOME Part 1 ซึ่งแยกจากกัน CALLHOME ต้องมีสิทธิ์ใช้งานและไม่ได้รวมมาให้ diarization-bench ในเครื่องจะแสดงผลที่เปรียบเทียบได้แยกตามจำนวนผู้พูดอ้างอิง ทั้งในคอนโซลและฟิลด์ JSON byReferenceSpeakerCount
Pipeline ของ Pyannote
Pipeline เริ่มต้นทำงานในสองขั้นตอน:
ขั้นตอนที่ 1: Segmentation + การเรียงลำดับผู้พูด
Pyannote segmentation-3.0 ประมวลผลหน้าต่างเลื่อนยาว 10 วินาทีโดยซ้อนทับ 50% decoder powerset แปลงเอาต์พุต 7 คลาสเป็นความน่าจะเป็นต่อผู้พูด (สูงสุด 3 ผู้พูดในระดับ local ต่อหน้าต่าง) หน้าต่างที่อยู่ติดกันแบ่งบริเวณซ้อนทับ 5 วินาที — ตัวตนของผู้พูดถูกส่งต่อระหว่างหน้าต่างโดยคำนวณ Pearson correlation ระหว่างวิถีความน่าจะเป็นในบริเวณที่ซ้อนทับ พร้อมการ matching แบบ exclusive greedy เพื่อให้ได้ ID ผู้พูดในระดับ global ที่สอดคล้อง
ขั้นตอนที่ 2: Embedding ภายหลัง
หลังการแยก WeSpeaker ResNet34-LM สกัด embedding centroid 256 มิติสำหรับผู้พูดแต่ละคน embedding เหล่านี้ช่วยให้สามารถสกัดผู้พูดเป้าหมาย (--target-speaker) แต่ไม่ตัดสินใจการกำหนดผู้พูด
การย้ายจาก pyannote.audio
หากคุณมาจากไลบรารี Python pyannote.audio — การแทนที่ subclass ของ Pipeline ที่ตั้งค่า pipeline.segmentation = ... หรือย้ายออกจากเซิร์ฟเวอร์ที่รัน pyannote/speaker-diarization-3.1 — Soniqo wrap ตัวโมเดล Pyannote-Segmentation-3.0 เองและรันทั้งหมดบนอุปกรณ์ Apple Silicon ไม่ต้องใช้ Python runtime ไม่ต้องใช้ CUDA ไม่ต้องใช้ Hugging Face token ตอน inference
| 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 = ... (subclass แบบกำหนดเอง) |
คงที่: Pyannote-Segmentation-3.0 (MLX หรือ CoreML เลือกอัตโนมัติ) |
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 |
น้ำหนัก Pyannote-Segmentation-3.0 ถูกแปลงจาก checkpoint ต้นทางของ HuggingFace ดังนั้น logits ของ segmentation จึงเทียบเท่าเชิงตัวเลขภายในขีดจำกัดความคลาดเคลื่อนของ float ส่วนการเรียงลำดับหลัง segmentation (Pearson correlation บนหน้าต่างที่ซ้อนทับ + matching แบบ exclusive greedy) และขั้นตอน embedding ภายหลังด้วย WeSpeaker ถูกเขียนใหม่ใน Swift แต่ให้เอาต์พุต RTTM ที่เทียบเท่ากับ pipeline Python อ้างอิง
ยังไม่มีเวอร์ชัน streaming OnlineSpeakerDiarization สำหรับเอนจิน Pyannote สำหรับการแยกผู้พูดแบบเรียลไทม์ ให้ใช้ --engine sortformer แทน เอนจินนี้รันโมเดล Sortformer ด้วยบัฟเฟอร์สถานะ FIFO + cache ผู้พูด
การใช้งาน 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
การสกัดผู้พูดเป้าหมาย
ให้เสียงลงทะเบียนของผู้พูดที่ทราบเพื่อสกัดเฉพาะ segment ของพวกเขาออกจากไฟล์บันทึก Pipeline คำนวณ embedding ของเสียงลงทะเบียนและค้นหา cluster ที่มีความคล้ายคลึง cosine สูงสุด
# Extract segments for a specific speaker
.build/release/speech diarize meeting.wav --target-speaker enrollment.wav
การให้คะแนน DER
ประเมินคุณภาพการแยกโดยเทียบกับไฟล์ RTTM อ้างอิง Pipeline คำนวณ Diarization Error Rate (DER) ซึ่งวัดสัดส่วนเวลาที่ถูกกำหนดป้ายผิด
# Score against reference RTTM
.build/release/speech diarize meeting.wav --score-against reference.rttm
เอาต์พุต RTTM
แฟล็ก --rttm สร้างเอาต์พุต Rich Transcription Time Marked ซึ่งเป็นรูปแบบมาตรฐานที่ใช้สำหรับการประเมินการแยกผู้พูด แต่ละบรรทัดเป็นรูปแบบ:
SPEAKER filename 1 start_time duration <NA> <NA> speaker_id <NA> <NA>
ตัวเลือก
| ตัวเลือก | คำอธิบาย |
|---|---|
--target-speaker | เสียงลงทะเบียนสำหรับสกัดผู้พูดเป้าหมาย (เฉพาะ pyannote) |
--embedding-engine | เอนจิน embedding ผู้พูด: mlx หรือ coreml (เฉพาะ pyannote) |
--vad-filter | กรองล่วงหน้าด้วย Silero VAD (เฉพาะ pyannote) |
--rttm | เอาต์พุตในรูปแบบ RTTM |
--json | รูปแบบเอาต์พุต JSON |
--score-against | ไฟล์ RTTM อ้างอิงสำหรับการประเมิน DER |
การแยกผู้พูดทำงานได้ดีที่สุดกับไฟล์บันทึกที่มีการสลับการพูดอย่างชัดเจน การพูดทับซ้อนกันมากอาจลดความแม่นยำได้ จำนวนผู้พูดถูกกำหนดอัตโนมัติ
การโหลดโมเดล
โมเดลถูกดาวน์โหลดอัตโนมัติเมื่อใช้ครั้งแรก:
| องค์ประกอบ | โมเดล | ขนาด | HuggingFace |
|---|---|---|---|
| Segmentation | Pyannote-Segmentation-3.0 | ~5.7 MB | aufklarer/Pyannote-Segmentation-MLX |
| Embedding ผู้พูด | WeSpeaker-ResNet34-LM (MLX) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-MLX |
| Embedding ผู้พูด | WeSpeaker-ResNet34-LM (CoreML) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-CoreML |
| Sortformer | Sortformer Diarization (CoreML) | ~240 MB | aufklarer/Sortformer-Diarization-CoreML |
Swift API
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
)