UNPKG

@jaehyun-ko/speaker-verification

Version:

Real-time speaker verification in the browser using NeXt-TDNN models

116 lines (115 loc) 3.99 kB
"use strict"; /** * Score Normalization (S-norm) implementation * Based on the paper: adaptive s-norm with cohort selection */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ScoreNormalizer = void 0; class ScoreNormalizer { constructor(config = {}) { this.cohortEmbeddings = []; this.config = { cohortSize: config.cohortSize || 6000, topK: config.topK || 300 }; } /** * Add cohort embeddings for score normalization */ addCohortEmbeddings(embeddings) { this.cohortEmbeddings = embeddings.slice(0, this.config.cohortSize); } /** * Load cohort embeddings from a file */ async loadCohortEmbeddings(url) { try { const response = await fetch(url); const data = await response.json(); // Convert array of arrays to Float32Arrays const embeddings = data.embeddings.map((emb) => new Float32Array(emb)); this.addCohortEmbeddings(embeddings); } catch (error) { throw error; } } /** * Compute cosine similarity between two embeddings */ computeSimilarity(emb1, emb2) { let dotProduct = 0; for (let i = 0; i < emb1.length; i++) { dotProduct += emb1[i] * emb2[i]; } return dotProduct; // Embeddings are already normalized } /** * Apply S-normalization to a raw score * * @param enrollEmbedding - Enrollment speaker embedding * @param testEmbedding - Test speaker embedding * @param rawScore - Raw cosine similarity score * @returns Normalized score */ normalize(enrollEmbedding, testEmbedding, rawScore) { if (this.cohortEmbeddings.length === 0) { return rawScore; } // Compute scores between enrollment and cohort const enrollCohortScores = []; for (const cohortEmb of this.cohortEmbeddings) { const score = this.computeSimilarity(enrollEmbedding, cohortEmb); enrollCohortScores.push(score); } // Compute scores between test and cohort const testCohortScores = []; for (const cohortEmb of this.cohortEmbeddings) { const score = this.computeSimilarity(testEmbedding, cohortEmb); testCohortScores.push(score); } // Sort and select top-K scores enrollCohortScores.sort((a, b) => b - a); // Descending order testCohortScores.sort((a, b) => b - a); const topKEnroll = enrollCohortScores.slice(0, this.config.topK); const topKTest = testCohortScores.slice(0, this.config.topK); // Compute statistics const meanEnroll = this.computeMean(topKEnroll); const stdEnroll = this.computeStd(topKEnroll, meanEnroll); const meanTest = this.computeMean(topKTest); const stdTest = this.computeStd(topKTest, meanTest); // Apply symmetric S-normalization const normalizedScore = 0.5 * ((rawScore - meanEnroll) / (stdEnroll + 1e-6) + (rawScore - meanTest) / (stdTest + 1e-6)); return normalizedScore; } /** * Compute mean of an array */ computeMean(values) { if (values.length === 0) return 0; const sum = values.reduce((a, b) => a + b, 0); return sum / values.length; } /** * Compute standard deviation of an array */ computeStd(values, mean) { if (values.length === 0) return 1; const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length; return Math.sqrt(variance); } /** * Get statistics about the cohort */ getCohortStats() { return { size: this.cohortEmbeddings.length, topK: this.config.topK, loaded: this.cohortEmbeddings.length > 0 }; } } exports.ScoreNormalizer = ScoreNormalizer;