clustering-tfjs
Version:
High-performance TypeScript clustering algorithms (K-Means, Spectral, Agglomerative) with TensorFlow.js acceleration and scikit-learn compatibility
200 lines (199 loc) • 7.76 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.validationBasedOptimization = validationBasedOptimization;
exports.intensiveParameterSweep = intensiveParameterSweep;
const kmeans_1 = require("./kmeans");
/**
* Performs validation-based optimization for spectral clustering.
* Tries multiple k-means initializations and selects the best based on validation score.
*/
async function validationBasedOptimization(embedding, nClusters, metric, attempts, randomState) {
const validationModule = await Promise.resolve().then(() => __importStar(require('../validation')));
let bestLabels = null;
let bestScore = metric === 'davies-bouldin' ? Infinity : -Infinity;
// Try multiple random seeds
for (let attempt = 0; attempt < attempts; attempt++) {
const kmParams = {
nClusters,
randomState: randomState !== undefined ? randomState + attempt : undefined,
nInit: 1, // Single run per seed when using validation
};
const km = new kmeans_1.KMeans(kmParams);
await km.fit(embedding);
const labels = km.labels_;
// Compute validation score based on selected metric
let score;
switch (metric) {
case 'calinski-harabasz':
score = validationModule.calinskiHarabasz(embedding, labels);
break;
case 'davies-bouldin':
score = validationModule.daviesBouldin(embedding, labels);
break;
case 'silhouette':
score = validationModule.silhouetteScore(embedding, labels);
break;
}
// Update best score (lower is better for Davies-Bouldin)
const isBetter = metric === 'davies-bouldin' ? score < bestScore : score > bestScore;
if (isBetter) {
bestScore = score;
bestLabels = labels;
}
}
return {
labels: bestLabels,
config: {
gamma: 0, // Will be set by caller
metric,
attempts,
useValidation: true,
},
score: bestScore,
};
}
/**
* Performs intensive parameter sweep for difficult clustering problems.
* Tests multiple gamma values and validation configurations.
*/
async function intensiveParameterSweep(X, params, computeEmbeddingFromAffinity, computeAffinityMatrix) {
const validationModule = await Promise.resolve().then(() => __importStar(require('../validation')));
const gammaRange = params.gammaRange ?? [
0.01, 0.05, 0.1, 0.5, 1.0, 2.0, 5.0, 10.0, 20.0,
];
const metrics = ['calinski-harabasz', 'davies-bouldin', 'silhouette'];
const attemptsRange = [10, 20, 30];
let bestResult = {
labels: [],
config: {
gamma: params.gamma ?? 1.0,
metric: 'calinski-harabasz',
attempts: 20,
useValidation: false,
},
};
let bestScore = -Infinity;
// Test without validation first
for (const gamma of gammaRange) {
// Recompute affinity and embedding with new gamma
const affinityMatrix = computeAffinityMatrix(X, {
...params,
gamma,
});
const embedding = await computeEmbeddingFromAffinity(affinityMatrix);
// Simple k-means without validation
const km = new kmeans_1.KMeans({
nClusters: params.nClusters,
randomState: params.randomState,
nInit: 10,
});
await km.fit(embedding);
const labels = km.labels_;
// Evaluate with all metrics and pick best
let avgScore = 0;
for (const metric of metrics) {
let score;
switch (metric) {
case 'calinski-harabasz':
score = validationModule.calinskiHarabasz(embedding, labels);
break;
case 'davies-bouldin':
score = -validationModule.daviesBouldin(embedding, labels); // Negate so higher is better
break;
case 'silhouette':
score = validationModule.silhouetteScore(embedding, labels);
break;
}
avgScore += score;
}
avgScore /= metrics.length;
if (avgScore > bestScore) {
bestScore = avgScore;
bestResult = {
labels,
config: {
gamma,
metric: 'calinski-harabasz',
attempts: 0,
useValidation: false,
},
};
}
// Clean up
affinityMatrix.dispose();
embedding.dispose();
}
// Test with validation
for (const gamma of gammaRange) {
for (const attempts of attemptsRange) {
for (const metric of metrics) {
try {
// Recompute affinity and embedding
const affinityMatrix = computeAffinityMatrix(X, {
...params,
gamma,
});
const embedding = await computeEmbeddingFromAffinity(affinityMatrix);
// Validation-based optimization
const result = await validationBasedOptimization(embedding, params.nClusters, metric, attempts, params.randomState);
// Normalize score for comparison
const normalizedScore = metric === 'davies-bouldin'
? -(result.score ?? 0)
: (result.score ?? 0);
if (normalizedScore > bestScore) {
bestScore = normalizedScore;
bestResult = {
labels: result.labels,
config: {
gamma,
metric,
attempts,
useValidation: true,
},
};
}
// Clean up
affinityMatrix.dispose();
embedding.dispose();
}
catch (e) {
// Skip if validation fails
}
}
}
}
return bestResult;
}