entropyx
Version:
A simple data mining library, written in TypeScript
113 lines • 4.26 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SammonMapping = void 0;
const distance_1 = require("@/base/distance");
const seedrandom_1 = require("seedrandom");
class SammonMapping {
constructor(options = {}) {
this.maxIterations = options.maxIterations ?? 20;
this.learningRate = options.learningRate ?? 0.3;
this.projectionDimension = options.projectionDimension ?? 2;
this.distanceFn = options.distanceFn ?? distance_1.Distance.euclidean;
if (typeof options.randomSeed === 'number') {
this.random = (0, seedrandom_1.alea)(options.randomSeed.toString());
}
else {
this.random = Math.random;
}
}
fit(data) {
const n = data.length;
if (n === 0) {
return { projection: [] };
}
const distStar = this.createDistanceMatrix(data);
const C = this.sumOfDistances(distStar);
let projected = this.initializeProjection(n, this.projectionDimension);
for (let iter = 0; iter < this.maxIterations; iter++) {
const distProj = this.createDistanceMatrix(projected);
const nextProjection = Array.from({ length: n }, () => new Array(this.projectionDimension).fill(0));
for (let p = 0; p < n; p++) {
for (let q = 0; q < this.projectionDimension; q++) {
const numerator = this.firstDerivative(projected, distProj, distStar, p, q, C);
const denominator = this.secondDerivative(projected, distProj, distStar, p, q, C);
const oldCoord = projected[p][q];
const step = this.learningRate * (numerator / (Math.abs(denominator) + 1e-12));
nextProjection[p][q] = oldCoord - step;
}
}
projected = nextProjection;
}
return { projection: projected };
}
firstDerivative(projected, distProj, distStar, p, q, C) {
let sum = 0;
const n = projected.length;
for (let j = 0; j < n; j++) {
if (j === p)
continue;
const dStar = distStar[p][j];
const dProj = distProj[p][j];
if (dStar === 0 || dProj === 0)
continue;
const term1 = (dStar - dProj) / (dStar * dProj);
const term2 = projected[p][q] - projected[j][q];
sum += term1 * term2;
}
return (-2 / C) * sum;
}
secondDerivative(projected, distProj, distStar, p, q, C) {
let sum = 0;
const n = projected.length;
for (let j = 0; j < n; j++) {
if (j === p)
continue;
const dStar = distStar[p][j];
const dProj = distProj[p][j];
if (dStar === 0 || dProj === 0)
continue;
const base = 1 / (dStar * dProj);
const diff = dStar - dProj;
const coordDiff = projected[p][q] - projected[j][q];
const c = (coordDiff * coordDiff) / dProj;
const d = 1 + diff / dProj;
sum += base * (diff - c * d);
}
return (-2 / C) * sum;
}
createDistanceMatrix(data) {
const n = data.length;
const distances = Array.from({ length: n }, () => new Array(n).fill(0));
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
const d = this.distanceFn(data[i], data[j]);
distances[i][j] = d;
distances[j][i] = d;
}
}
return distances;
}
sumOfDistances(matrix) {
let sum = 0;
const n = matrix.length;
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
sum += matrix[i][j];
}
}
return sum;
}
initializeProjection(n, d) {
const projection = [];
for (let i = 0; i < n; i++) {
const row = [];
for (let j = 0; j < d; j++) {
row.push(this.random());
}
projection.push(row);
}
return projection;
}
}
exports.SammonMapping = SammonMapping;
//# sourceMappingURL=sammon-mapping.js.map