UNPKG

echogarden

Version:

An easy-to-use speech toolset. Includes tools for synthesis, recognition, alignment, speech translation, language detection, source separation and more.

879 lines (617 loc) 20.5 kB
import { clip } from "../utilities/Utilities.js" export function covarianceMatrixOfSamples(samples: ArrayLike<number>[], weights?: ArrayLike<number>, biased = false) { if (samples.length == 0) { throw new Error('No vectors given') } const { centeredVectors: centeredSamples, mean } = centerVectors(samples, weights) let covarianceMatrix if (weights) { covarianceMatrix = weightedCovarianceMatrixOfCenteredSamples(centeredSamples, weights) } else { covarianceMatrix = covarianceMatrixOfCenteredSamples(centeredSamples, biased) } return { covarianceMatrix, mean } } export function covarianceMatrixOfCenteredSamples(centeredSamples: ArrayLike<number>[], biased = false, diagonalRegularizationAmount = 1e-6) { const sampleCount = centeredSamples.length if (sampleCount == 0) { throw new Error('No vectors given') } const sampleSizeMetric = biased || sampleCount == 1 ? sampleCount : sampleCount - 1 const featureCount = centeredSamples[0].length const covarianceMatrix = createVectorArray(featureCount, featureCount) if (sampleCount == 1) { return covarianceMatrix } for (let i = 0; i < featureCount; i++) { for (let j = 0; j < featureCount; j++) { if (i > j) { covarianceMatrix[i][j] = covarianceMatrix[j][i] continue } let matrixElement = 0.0 for (const sample of centeredSamples) { matrixElement += sample[i] * sample[j] } matrixElement /= sampleSizeMetric if (i == j) { matrixElement += diagonalRegularizationAmount } covarianceMatrix[i][j] = matrixElement } } return covarianceMatrix } export function weightedCovarianceMatrixOfCenteredSamples(centeredSamples: ArrayLike<number>[], weights: ArrayLike<number>, diagonalRegularizationAmount = 1e-6) { const sampleCount = centeredSamples.length if (sampleCount == 0) { throw new Error('No vectors given') } const featureCount = centeredSamples[0].length const covarianceMatrix = createVectorArray(featureCount, featureCount) if (sampleCount == 1) { return covarianceMatrix } for (let i = 0; i < featureCount; i++) { for (let j = 0; j < featureCount; j++) { if (i > j) { covarianceMatrix[i][j] = covarianceMatrix[j][i] continue } let matrixElement = 0.0 for (let sampleIndex = 0; sampleIndex < sampleCount; sampleIndex++) { const sample = centeredSamples[sampleIndex] const weight = weights[sampleIndex] matrixElement += weight * (sample[i] * sample[j]) } if (i == j) { matrixElement += diagonalRegularizationAmount } covarianceMatrix[i][j] = matrixElement } } return covarianceMatrix } export function centerVectors(vectors: ArrayLike<number>[], weights?: ArrayLike<number>) { const vectorCount = vectors.length if (vectorCount == 0) { return { centeredVectors: [] as Float32Array[], mean: new Float32Array(0) } } let mean: Float32Array if (weights) { mean = weightedMeanOfVectors(vectors, weights) } else { mean = meanOfVectors(vectors) } const centeredVectors: Float32Array[] = [] for (let i = 0; i < vectorCount; i++) { const centeredVector = subtractVectors(vectors[i], mean) centeredVectors.push(centeredVector) } return { centeredVectors, mean } } export function centerVector(vector: ArrayLike<number>) { const mean = meanOfVector(vector) const centeredVector = new Float32Array(vector.length) for (let i = 0; i < vector.length; i++) { centeredVector[i] = vector[i] - mean } return centeredVector } export function scaleToSumTo1(vector: ArrayLike<number>) { if (vector.length == 0) { return new Float32Array(0) } if (vector.length == 1) { return Float32Array.from([1]) } const minValue = vector[indexOfMin(vector)] const scaledVector = Float32Array.from(vector) if (minValue < 0) { const addedOffset = -minValue * 2 for (let i = 0; i < scaledVector.length; i++) { scaledVector[i] += addedOffset } } const sum = sumVector(scaledVector) if (sum == 0) { return scaledVector } if (sum == Infinity) { throw new Error('Vector sum is infinite') } for (let i = 0; i < vector.length; i++) { scaledVector[i] /= sum scaledVector[i] = zeroIfNaN(scaledVector[i]) } return scaledVector } export function normalizeVector(vector: ArrayLike<number>, kind: 'population' | 'sample' = 'population') { if (vector.length == 0) { throw new Error('Vector is empty') } const mean = meanOfVector(vector) const stdDeviation = stdDeviationOfVector(vector, kind, mean) const normalizedVector = createVector(vector.length) for (let i = 0; i < vector.length; i++) { normalizedVector[i] = (vector[i] - mean) / stdDeviation normalizedVector[i] = zeroIfNaN(normalizedVector[i]) } return { normalizedVector, mean, stdDeviation } } export function normalizeVectors(vectors: ArrayLike<number>[], kind: 'population' | 'sample' = 'population') { const vectorCount = vectors.length if (vectorCount == 0) { return { normalizedVectors: [] as Float32Array[], mean: new Float32Array(0), stdDeviation: new Float32Array(0) } } const featureCount = vectors[0].length const mean = meanOfVectors(vectors) const stdDeviation = stdDeviationOfVectors(vectors, kind, mean) const normalizedVectors: Float32Array[] = [] for (const vector of vectors) { const normalizedVector = createVector(featureCount) for (let featureIndex = 0; featureIndex < featureCount; featureIndex++) { normalizedVector[featureIndex] = (vector[featureIndex] - mean[featureIndex]) / stdDeviation[featureIndex] normalizedVector[featureIndex] = zeroIfNaN(normalizedVector[featureIndex]) } normalizedVectors.push(normalizedVector) } return { normalizedVectors, mean, stdDeviation } } export function deNormalizeVectors(normalizedVectors: ArrayLike<number>[], originalMean: ArrayLike<number>, originalStdDeviation: ArrayLike<number>) { const vectorCount = normalizeVectors.length if (vectorCount == 0) { return [] as Float32Array[] } const featureCount = normalizedVectors[0].length const deNormalizedVectors: Float32Array[] = [] for (const normalizedVector of normalizedVectors) { const deNormalizedVector = createVector(featureCount) for (let featureIndex = 0; featureIndex < featureCount; featureIndex++) { deNormalizedVector[featureIndex] = originalMean[featureIndex] + (normalizedVector[featureIndex] * originalStdDeviation[featureIndex]) } deNormalizedVectors.push(deNormalizedVector) } return deNormalizedVectors } export function meanOfVectors(vectors: ArrayLike<number>[]) { const vectorCount = vectors.length if (vectorCount == 0) { return new Float32Array(0) } const featureCount = vectors[0].length const result = createVector(featureCount) for (const vector of vectors) { for (let featureIndex = 0; featureIndex < featureCount; featureIndex++) { result[featureIndex] += vector[featureIndex] } } for (let featureIndex = 0; featureIndex < featureCount; featureIndex++) { result[featureIndex] /= vectorCount } return result } export function weightedMeanOfVectors(vectors: ArrayLike<number>[], weights: ArrayLike<number>) { const vectorCount = vectors.length if (vectorCount == 0) { return new Float32Array(0) } const featureCount = vectors[0].length const result = createVector(featureCount) for (let vectorIndex = 0; vectorIndex < vectorCount; vectorIndex++) { const vector = vectors[vectorIndex] const vectorWeight = weights[vectorIndex] for (let featureIndex = 0; featureIndex < featureCount; featureIndex++) { result[featureIndex] += vectorWeight * vector[featureIndex] } } return result } export function stdDeviationOfVectors(vectors: ArrayLike<number>[], kind: 'population' | 'sample' = 'population', mean?: ArrayLike<number>) { return varianceOfVectors(vectors, kind, mean).map(v => Math.sqrt(v)) } export function varianceOfVectors(vectors: ArrayLike<number>[], kind: 'population' | 'sample' = 'population', mean?: ArrayLike<number>) { const vectorCount = vectors.length if (vectorCount == 0) { return new Float32Array(0) } const sampleSizeMetric = kind == 'population' || vectorCount == 1 ? vectorCount : vectorCount - 1 const featureCount = vectors[0].length if (!mean) { mean = meanOfVectors(vectors) } const result = createVector(featureCount) for (const vector of vectors) { for (let i = 0; i < featureCount; i++) { result[i] += (vector[i] - mean[i]) ** 2 } } for (let i = 0; i < featureCount; i++) { result[i] /= sampleSizeMetric } return result } export function meanOfVector(vector: ArrayLike<number>) { if (vector.length == 0) { return 0 } return sumVector(vector) / vector.length } export function medianOfVector(vector: ArrayLike<number>) { if (vector.length == 0) { throw new Error('Vector is empty') } const sortedArray = Array.from(vector).sort((a, b) => a - b) const median = sortedArray[Math.floor(sortedArray.length / 2)] return median } export function stdDeviationOfVector(vector: ArrayLike<number>, kind: 'population' | 'sample' = 'population', mean?: number) { return Math.sqrt(varianceOfVector(vector, kind, mean)) } export function varianceOfVector(vector: ArrayLike<number>, kind: 'population' | 'sample' = 'population', mean?: number) { if (vector.length == 0) { return 0 } const sampleSizeMetric = kind == 'population' || vector.length == 1 ? vector.length : vector.length - 1 if (mean == null) { mean = meanOfVector(vector) } let result = 0.0 for (let i = 0; i < vector.length; i++) { result += (vector[i] - mean) ** 2 } return result / sampleSizeMetric } export function logOfVector(vector: ArrayLike<number>, minVal = 1e-40) { const result = new Float32Array(vector.length) for (let i = 0; i < vector.length; i++) { const value = vector[i] result[i] = Math.log(value + minVal) } return result } export function expOfVector(vector: ArrayLike<number>) { const result = new Float32Array(vector.length) for (let i = 0; i < vector.length; i++) { const value = vector[i] result[i] = Math.exp(value) } return result } export function transpose(matrix: ArrayLike<number>[]) { const vectorCount = matrix.length const featureCount = matrix[0].length const transposedMatrix = createVectorArray(featureCount, vectorCount) for (let i = 0; i < vectorCount; i++) { for (let j = 0; j < featureCount; j++) { transposedMatrix[j][i] = matrix[i][j] } } return transposedMatrix } export function movingAverageOfWindow3(vector: ArrayLike<number>) { const elementCount = vector.length if (elementCount == 0) { return new Float32Array(0) } if (elementCount == 1) { return Float32Array.from(vector) } const result = new Float32Array(elementCount) result[0] = (vector[0] + vector[0] + vector[1]) / 3 for (let i = 1; i < elementCount - 1; i++) { result[i] = (vector[i - 1] + vector[i] + vector[i + 1]) / 3 } result[elementCount - 1] = (vector[elementCount - 2] + vector[elementCount - 1] + vector[elementCount - 1]) / 3 return result } export function averageMeanSquaredError(actual: ArrayLike<number>[], expected: ArrayLike<number>[]) { if (actual.length != expected.length) { throw new Error('Vectors are not the same length') } const vectorCount = actual.length if (vectorCount == 0) { return 0 } let sum = 0.0 for (let i = 0; i < vectorCount; i++) { sum += meanSquaredError(actual[i], expected[i]) } return sum / vectorCount } export function meanSquaredError(actual: ArrayLike<number>, expected: ArrayLike<number>) { if (actual.length != expected.length) { throw new Error('Vectors are not the same length') } const featureCount = actual.length if (featureCount == 0) { return 0 } let sum = 0.0 for (let i = 0; i < featureCount; i++) { sum += (actual[i] - expected[i]) ** 2 } return sum / featureCount } export function euclideanDistance(vector1: ArrayLike<number>, vector2: ArrayLike<number>) { return Math.sqrt(squaredEuclideanDistance(vector1, vector2)) } export function squaredEuclideanDistance(vector1: ArrayLike<number>, vector2: ArrayLike<number>) { if (vector1.length !== vector2.length) { throw new Error('Vectors are not the same length') } const elementCount = vector1.length if (elementCount === 0) { return 0 } let sum = 0.0 for (let i = 0; i < elementCount; i++) { sum += (vector1[i] - vector2[i]) ** 2 } return sum } export function euclideanDistance13Dim(vector1: ArrayLike<number>, vector2: ArrayLike<number>) { return Math.sqrt(squaredEuclideanDistance13Dim(vector1, vector2)) } export function squaredEuclideanDistance13Dim(vector1: ArrayLike<number>, vector2: ArrayLike<number>) { // Assumes the input has 13 dimensions (optimized for 13-dimensional MFCC vectors) const result = (vector1[0] - vector2[0]) ** 2 + (vector1[1] - vector2[1]) ** 2 + (vector1[2] - vector2[2]) ** 2 + (vector1[3] - vector2[3]) ** 2 + (vector1[4] - vector2[4]) ** 2 + (vector1[5] - vector2[5]) ** 2 + (vector1[6] - vector2[6]) ** 2 + (vector1[7] - vector2[7]) ** 2 + (vector1[8] - vector2[8]) ** 2 + (vector1[9] - vector2[9]) ** 2 + (vector1[10] - vector2[10]) ** 2 + (vector1[11] - vector2[11]) ** 2 + (vector1[12] - vector2[12]) ** 2 return result } export function cosineDistance(vector1: ArrayLike<number>, vector2: ArrayLike<number>) { return 1 - cosineSimilarity(vector1, vector2) } export function cosineSimilarity(vector1: ArrayLike<number>, vector2: ArrayLike<number>) { if (vector1.length !== vector2.length) { throw new Error('Vectors are not the same length') } if (vector1.length === 0) { return 0 } const elementCount = vector1.length let dotProduct = 0.0 let squaredMagnitude1 = 0.0 let squaredMagnitude2 = 0.0 for (let i = 0; i < elementCount; i++) { const element1 = vector1[i] const element2 = vector2[i] dotProduct += element1 * element2 squaredMagnitude1 += element1 ** 2 squaredMagnitude2 += element2 ** 2 } let result = dotProduct / (Math.sqrt(squaredMagnitude1) * Math.sqrt(squaredMagnitude2) + 1e-40) result = zeroIfNaN(result) result = clip(result, -1.0, 1.0) return result } export function cosineDistancePrecomputedMagnitudes(vector1: ArrayLike<number>, vector2: ArrayLike<number>, magnitude1: number, magnitude2: number) { return 1 - cosineSimilarityPrecomputedMagnitudes(vector1, vector2, magnitude1, magnitude2) } export function cosineSimilarityPrecomputedMagnitudes(vector1: ArrayLike<number>, vector2: ArrayLike<number>, magnitude1: number, magnitude2: number) { if (vector1.length != vector2.length) { throw new Error('Vectors are not the same length') } if (vector1.length == 0) { return 0 } const featureCount = vector1.length let dotProduct = 0.0 for (let i = 0; i < featureCount; i++) { dotProduct += vector1[i] * vector2[i] } let result = dotProduct / (magnitude1 * magnitude2 + 1e-40) result = zeroIfNaN(result) result = clip(result, -1.0, 1.0) return result } export function minkowskiDistance(vector1: ArrayLike<number>, vector2: ArrayLike<number>, power: number) { if (vector1.length != vector2.length) { throw new Error('Vectors are not the same length') } const elementCount = vector1.length if (elementCount == 0) { return 0 } let sum = 0.0 for (let i = 0; i < elementCount; i++) { sum += Math.abs(vector1[i] - vector2[i]) ** power } return sum ** (1 / power) } export function subtractVectors(vector1: ArrayLike<number>, vector2: ArrayLike<number>) { const elementCount = vector1.length if (vector1.length != vector2.length) { throw new Error('Vectors are not the same length') } const result = createVector(vector1.length) for (let i = 0; i < elementCount; i++) { result[i] = vector1[i] - vector2[i] } return result } export function sumVector(vector: ArrayLike<number>) { const elementCount = vector.length let result = 0.0 for (let i = 0; i < elementCount; i++) { result += vector[i] } return result } export function sumOfSquaresOfVector(vector: ArrayLike<number>) { const elementCount = vector.length let result = 0.0 for (let i = 0; i < elementCount; i++) { result += vector[i] ** 2 } return result } export function sumAndSumOfSquaresOfVector(vector: ArrayLike<number>) { const elementCount = vector.length let sum = 0.0 let sumOfSquares = 0.0 for (let i = 0; i < elementCount; i++) { const value = vector[i] sum += value sumOfSquares += value ** 2 } return { sum, sumOfSquares } } export function dotProduct(vector1: ArrayLike<number>, vector2: ArrayLike<number>) { if (vector1.length != vector2.length) { throw new Error('Vectors are not the same length') } const elementCount = vector1.length let result = 0.0 for (let i = 0; i < elementCount; i++) { result += vector1[i] * vector2[i] } return result } export function magnitude(vector: ArrayLike<number>) { const featureCount = vector.length let squaredMagnitude = 0.0 for (let i = 0; i < featureCount; i++) { squaredMagnitude += vector[i] ** 2 } return Math.sqrt(squaredMagnitude) } export function maxValue(vector: ArrayLike<number>) { return vector[indexOfMax(vector)] } export function indexOfMax(vector: ArrayLike<number>) { if (vector.length == 0) { return -1 } let maxValue = vector[0] let result = 0 for (let i = 1; i < vector.length; i++) { if (vector[i] > maxValue) { maxValue = vector[i] result = i } } return result } export function minValue(vector: ArrayLike<number>) { return vector[indexOfMin(vector)] } export function indexOfMin(vector: ArrayLike<number>) { const elementCount = vector.length let minValue = Infinity let result = -1 for (let i = 0; i < elementCount; i++) { if (vector[i] < minValue) { minValue = vector[i] result = i } } return result } export function sigmoid(x: number) { const result = 1 / (1 + Math.exp(-x)) return zeroIfNaN(result) } export function softmax(logits: ArrayLike<number>, temperature = 1.0) { temperature = Math.max(temperature, 0.00001) const logitCount = logits.length if (logitCount === 0) { return new Float32Array(0) } let maxValue = -Infinity for (let i = 0; i < logitCount; i++) { const value = logits[i] if (value > maxValue) { maxValue = value } } const temperatureReciprocal = 1 / temperature const results = new Float32Array(logitCount) let sumOfExponentiatedLogits = 0.0 for (let i = 0; i < logitCount; i++) { const logit = logits[i] const eToLogit = Math.exp((logit - maxValue) * temperatureReciprocal) sumOfExponentiatedLogits += eToLogit results[i] = eToLogit } const sumOfExponentiatedValuesReciprocal = 1 / (sumOfExponentiatedLogits + 1e-20) for (let i = 0; i < logitCount; i++) { results[i] *= sumOfExponentiatedValuesReciprocal } return results } export function hammingDistance(value1: number, value2: number, bitLength = 32) { let valueXor = value1 ^ value2 let result = 0 for (let i = 0; i < bitLength; i++) { result += valueXor & 1 valueXor = valueXor >> 1 } return result } export function createVectorArray(vectorCount: number, featureCount: number, initialValue = 0.0) { const result: Float32Array[] = new Array(vectorCount) for (let i = 0; i < vectorCount; i++) { result[i] = createVector(featureCount, initialValue) } return result } export function createVector(elementCount: number, initialValue = 0.0) { const result = new Float32Array(elementCount) if (initialValue !== 0) { result.fill(initialValue) } return result } export function zeroIfNaN(val: number) { if (isNaN(val)) { return 0 } else { return val } } export function logSumExp(values: ArrayLike<number>, minVal = 1e-40) { return Math.log(minVal + sumExp(values)) } export function sumExp(values: ArrayLike<number>) { let sumOfExp = 0 for (let i = 0; i < values.length; i++) { const value = values[i] sumOfExp += Math.exp(value) } return sumOfExp } export function logSoftmax(values: ArrayLike<number>, minVal = 1e-40) { const softMaxOfValues = softmax(values) return logOfVector(softMaxOfValues, minVal) } export class IncrementalMean { currentElementCount = 0 currentMean = 0.0 addValueToMean(value: number) { this.currentElementCount += 1 this.currentMean += (value + this.currentMean) / this.currentElementCount } // 1, 3.2, 7.23 // 0 + ((1 - 0) / 1) = 1 // 1 + ((3.2 - 1) / 2) = 2.1 // 2.1 + ((7.23 - 3.1) / 3) = 2.04 // 3.81 } export type DistanceFunction = (a: ArrayLike<number>, b: ArrayLike<number>) => number export interface ComplexNumber { real: number imaginary: number }