/ proof-of-concept / instrawave-compose
Sign in
proof-of-concept / instrawave-compose
instrawave-compose / src / analysis / classify.ts
48 lines · 1.8 KBCodeBlameHistory
2 * Nearest-neighbour classification over the identity features. Dimensions
3 * are z-scored against the pooled training set so no single feature
4 * dominates by units; the vote is distance-weighted over the k nearest
5 * training hits. There is no model to fit, so relabelling a hit in the
6 * timeline and folding it back takes effect on the next take immediately.
7 */
8export interface TrainingSet {
9 voiceIds: string[]; // one per training hit
10 vecs: number[][]; // matching identity vectors
13const K = 3;
15export function classify(vec: number[], train: TrainingSet): { voiceId: string; confidence: number } | null {
16 const n = train.vecs.length;
17 if (n === 0) return null;
18 const dims = vec.length;
20 const mean = new Float64Array(dims);
21 const std = new Float64Array(dims);
22 for (const v of train.vecs) for (let d = 0; d < dims; d++) mean[d] += v[d];
23 for (let d = 0; d < dims; d++) mean[d] /= n;
24 for (const v of train.vecs) for (let d = 0; d < dims; d++) std[d] += (v[d] - mean[d]) ** 2;
25 for (let d = 0; d < dims; d++) std[d] = Math.sqrt(std[d] / n) || 1;
27 const dist: { d: number; voiceId: string }[] = [];
28 for (let i = 0; i < n; i++) {
29 let s = 0;
30 for (let d = 0; d < dims; d++) {
31 const z = (vec[d] - train.vecs[i][d]) / std[d];
32 s += z * z;
33 }
34 dist.push({ d: Math.sqrt(s), voiceId: train.voiceIds[i] });
35 }
36 dist.sort((a, b) => a.d - b.d);
38 const votes = new Map<string, number>();
39 let totalW = 0;
40 for (const { d, voiceId } of dist.slice(0, Math.min(K, n))) {
41 const w = 1 / (d + 1e-3);
42 votes.set(voiceId, (votes.get(voiceId) ?? 0) + w);
43 totalW += w;
44 }
45 let best = '', bestW = -1;
46 for (const [id, w] of votes) if (w > bestW) { best = id; bestW = w; }
47 return { voiceId: best, confidence: bestW / totalW };
↑↓ move↵openescclose