Diarization v2: Fenster-Clustering mit gelernter Schwelle + Live-Namensmarker
#74 (22 Teilnehmer) ergab automatisch 44 Sprecher, dieselbe Stimme mehrfach. Ursache gemessen: ein Zentroid pro sherpa-Cluster aus max. 3 Schnitten, fester Kosinus auf rohen Embeddings (Kreuz-Turn-Kosinus ~0,4 = keine Trennschaerfe). v2: sherpa liefert nur Turn-Grenzen, Identitaet aus 3-s-Fenster-Embeddings, Mittelwert-Zentrierung, Average-Linkage-AHC, Weak-Fold, Schwelle pro Aufnahme per 2-Gauss-EM gelernt. Validiert: #74 44->20, #70 ->4, #68 ->2. Live-Namensmarker: Namen waehrend der Aufnahme tippen; Zeitstempel benennt spaeter den Cluster und dient als must-/cannot-link-Zwangsbedingung.
This commit is contained in:
+14
-2
@@ -7,8 +7,20 @@ import readline from 'node:readline';
|
||||
|
||||
const BIN = '/opt/callrec/spk_embed';
|
||||
const DIR = '/opt/sherpa/sherpa-onnx-v1.13.3-linux-aarch64-shared-cpu';
|
||||
const EMB = process.env.DIAR_EMB || '/opt/sherpa/nemo_titanet_small.onnx';
|
||||
// Identity/clustering model. Benchmarked 2026-07-27 on our own conference audio
|
||||
// (#74, 1181 mean-centred windows, same-turn vs cross-turn cosine):
|
||||
// TitaNet-large d' 3.75 / 197 s CAM++ (3D-Speaker) d' 3.72 / 82 s
|
||||
// TitaNet-small d' 3.54 / 57 s wespeaker VoxCeleb resnet34 d' 2.43 / 185 s
|
||||
// Fed through the full v2 pipeline the three good models ended up within one
|
||||
// speaker of each other (21/22/21 on a 23-person weekly), so TitaNet-small stays
|
||||
// the default: it is the fastest AND keeps every already-learned voiceprint
|
||||
// valid. SPK_MODEL switches models — prints are then stored per model (see
|
||||
// db.upsertVoiceprint), because cosine across models is meaningless.
|
||||
const EMB = process.env.SPK_MODEL || '/opt/sherpa/nemo_titanet_small.onnx';
|
||||
export const THRESHOLD = +(process.env.SPK_THRESHOLD || 0.5);
|
||||
// Voiceprints are only comparable within the SAME embedding model — stored with
|
||||
// every print so a model swap can't silently match against stale vectors.
|
||||
export function modelId() { return EMB.split('/').pop().replace(/\.onnx$/, ''); }
|
||||
|
||||
let proc = null, ready = null, rl = null, pending = null, chain = Promise.resolve();
|
||||
|
||||
@@ -56,4 +68,4 @@ export function bestMatch(emb, voiceprints, threshold = THRESHOLD) {
|
||||
return (best && bs >= threshold) ? { id: best.id, name: best.name, score: bs } : null;
|
||||
}
|
||||
|
||||
export function status() { return { model: EMB, present: fs.existsSync(EMB), running: !!proc, threshold: THRESHOLD }; }
|
||||
export function status() { return { model: EMB, modelId: modelId(), present: fs.existsSync(EMB), running: !!proc, threshold: THRESHOLD }; }
|
||||
|
||||
Reference in New Issue
Block a user