UNPKG

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
"use strict"; 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; }