UNPKG

@astermind/astermind-premium

Version:

Astermind Premium - Premium ML Toolkit

211 lines 7.49 kB
// sparse-kernel-elm.ts — Sparse Kernel ELM // Sparse kernel matrix approximation with landmark selection import { KernelELM } from '@astermind/astermind-elm'; import { requireLicense } from '../core/license.js'; /** * Sparse Kernel ELM with landmark-based approximation * Features: * - Sparse kernel matrix approximation * - Landmark selection strategies * - Reduced computational complexity * - Scalable to large datasets */ export class SparseKernelELM { constructor(options) { this.landmarks = []; this.trained = false; requireLicense(); // Premium feature - requires valid license this.categories = options.categories; this.options = { categories: options.categories, kernelType: options.kernelType ?? 'rbf', numLandmarks: options.numLandmarks ?? 100, landmarkSelection: options.landmarkSelection ?? 'kmeans', gamma: options.gamma ?? 1.0, degree: options.degree ?? 2, coef0: options.coef0 ?? 0, activation: options.activation ?? 'relu', maxLen: options.maxLen ?? 100, useTokenizer: options.useTokenizer ?? true, }; this.kelm = new KernelELM({ useTokenizer: this.options.useTokenizer ? true : undefined, categories: this.options.categories, maxLen: this.options.maxLen, kernel: this.options.kernelType, gamma: this.options.gamma, degree: this.options.degree, coef0: this.options.coef0, }); } /** * Train with sparse kernel approximation */ train(X, y) { // Prepare labels const labelIndices = y.map(label => typeof label === 'number' ? label : this.options.categories.indexOf(label)); // Select landmarks this._selectLandmarks(X); // Train on landmarks (reduced dataset) this.kelm.setCategories?.(this.options.categories); this.kelm.trainFromData?.(this.landmarks, this._getLandmarkLabels(X, y, labelIndices)); this.trained = true; } /** * Select landmark points */ _selectLandmarks(X) { const numLandmarks = Math.min(this.options.numLandmarks, X.length); if (this.options.landmarkSelection === 'random') { // Random selection const indices = new Set(); while (indices.size < numLandmarks) { indices.add(Math.floor(Math.random() * X.length)); } this.landmarks = Array.from(indices).map(i => [...X[i]]); } else if (this.options.landmarkSelection === 'kmeans') { // K-means centroids as landmarks this.landmarks = this._kmeansLandmarks(X, numLandmarks); } else if (this.options.landmarkSelection === 'diverse') { // Diverse selection (maximize distance) this.landmarks = this._diverseLandmarks(X, numLandmarks); } else { // Default: first N points this.landmarks = X.slice(0, numLandmarks).map(x => [...x]); } } /** * K-means landmark selection */ _kmeansLandmarks(X, k) { // Simplified k-means (in practice, use proper k-means) const centroids = []; const dim = X[0].length; // Initialize centroids randomly for (let i = 0; i < k; i++) { const idx = Math.floor(Math.random() * X.length); centroids.push([...X[idx]]); } // Simple iteration (simplified) for (let iter = 0; iter < 10; iter++) { const clusters = Array(k).fill(null).map(() => []); // Assign points to nearest centroid for (const x of X) { let minDist = Infinity; let nearest = 0; for (let i = 0; i < k; i++) { const dist = this._euclideanDistance(x, centroids[i]); if (dist < minDist) { minDist = dist; nearest = i; } } clusters[nearest].push(x); } // Update centroids for (let i = 0; i < k; i++) { if (clusters[i].length > 0) { const newCentroid = new Array(dim).fill(0); for (const point of clusters[i]) { for (let j = 0; j < dim; j++) { newCentroid[j] += point[j]; } } for (let j = 0; j < dim; j++) { newCentroid[j] /= clusters[i].length; } centroids[i] = newCentroid; } } } return centroids; } /** * Diverse landmark selection */ _diverseLandmarks(X, k) { const landmarks = []; // Start with random point let firstIdx = Math.floor(Math.random() * X.length); landmarks.push([...X[firstIdx]]); // Greedily select points that maximize minimum distance while (landmarks.length < k) { let maxMinDist = -1; let bestIdx = -1; for (let i = 0; i < X.length; i++) { const minDist = Math.min(...landmarks.map(l => this._euclideanDistance(X[i], l))); if (minDist > maxMinDist) { maxMinDist = minDist; bestIdx = i; } } if (bestIdx >= 0) { landmarks.push([...X[bestIdx]]); } else { break; } } return landmarks; } /** * Get labels for landmarks */ _getLandmarkLabels(X, y, labelIndices) { const landmarkLabels = []; for (const landmark of this.landmarks) { // Find nearest point in X let minDist = Infinity; let nearestIdx = 0; for (let i = 0; i < X.length; i++) { const dist = this._euclideanDistance(landmark, X[i]); if (dist < minDist) { minDist = dist; nearestIdx = i; } } landmarkLabels.push(labelIndices[nearestIdx]); } return landmarkLabels; } _euclideanDistance(a, b) { let sum = 0; for (let i = 0; i < Math.min(a.length, b.length); i++) { sum += Math.pow(a[i] - b[i], 2); } return Math.sqrt(sum); } /** * Predict using sparse kernel */ predict(X, topK = 3) { if (!this.trained) { throw new Error('Model must be trained before prediction'); } const XArray = Array.isArray(X[0]) ? X : [X]; const results = []; for (const x of XArray) { // Use landmarks for prediction const preds = this.kelm.predictFromVector?.([x], topK) || []; for (const pred of preds.slice(0, topK)) { results.push({ label: pred.label || this.options.categories[pred.index || 0], prob: pred.prob || 0, }); } } return results; } /** * Get selected landmarks */ getLandmarks() { return this.landmarks.map(l => [...l]); } } //# sourceMappingURL=sparse-kernel-elm.js.map