การแยกผู้พูด

ระบุว่าใครพูดเมื่อใดในไฟล์บันทึกที่มีผู้พูดหลายคน มีสามเอนจิน: 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
SegmentationPyannote-Segmentation-3.0~5.7 MBaufklarer/Pyannote-Segmentation-MLX
Embedding ผู้พูดWeSpeaker-ResNet34-LM (MLX)~25 MBaufklarer/WeSpeaker-ResNet34-LM-MLX
Embedding ผู้พูดWeSpeaker-ResNet34-LM (CoreML)~25 MBaufklarer/WeSpeaker-ResNet34-LM-CoreML
SortformerSortformer Diarization (CoreML)~240 MBaufklarer/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
)