UNPKG

@astermind/astermind-pro

Version:

Astermind Pro - Premium ML Toolkit with Advanced RAG, Reranking, Summarization, and Information Flow Analysis

249 lines 8.71 kB
// sparse-elm.ts — Sparse ELM with L1/L2 regularization and feature selection // Efficient for high-dimensional data with interpretability import { ELM } from '@astermind/astermind-elm'; import { requireLicense } from '../core/license.js'; /** * Sparse ELM with regularization and feature selection * Features: * - L1/L2/Elastic net regularization * - Weight pruning for sparsity * - Feature importance ranking * - Interpretable models */ export class SparseELM { constructor(options) { this.trained = false; this.weightMask = []; // Track which weights are active this.featureImportance = []; requireLicense(); // Premium feature - requires valid license this.options = { categories: options.categories, hiddenUnits: options.hiddenUnits ?? 256, maxLen: options.maxLen ?? 100, useTokenizer: options.useTokenizer ?? true, activation: options.activation ?? 'relu', regularization: { type: options.regularization.type, lambda: options.regularization.lambda, alpha: options.regularization.alpha ?? 0.5, }, sparsityTarget: options.sparsityTarget ?? 0.5, pruneThreshold: options.pruneThreshold ?? 1e-6, }; this.elm = new ELM({ useTokenizer: this.options.useTokenizer ? true : undefined, hiddenUnits: this.options.hiddenUnits, categories: this.options.categories, maxLen: this.options.maxLen, activation: this.options.activation, }); } /** * Train sparse ELM with regularization */ train(X, y) { // Prepare labels const labelIndices = y.map(label => typeof label === 'number' ? label : this.options.categories.indexOf(label)); // Encode inputs const encoded = this.options.useTokenizer ? X.map(x => { const enc = this.elm.encoder?.encode?.(x) || x; return this.elm.encoder?.normalize?.(enc) || enc; }) : X; // Train base ELM this.elm.setCategories?.(this.options.categories); this.elm.trainFromData?.(encoded, labelIndices); // Apply regularization and sparsification this._applyRegularization(); this._pruneWeights(); this._computeFeatureImportance(); this.trained = true; } /** * Predict with sparse model */ predict(X, topK = 3) { if (!this.trained) { throw new Error('Model must be trained before prediction'); } // Use base ELM for prediction (sparsity is in weights) const XArray = Array.isArray(X[0]) ? X : [X]; const preds = this.elm.predictFromVector?.(XArray, topK) || []; return preds.map((pred) => ({ label: pred.label || this.options.categories[pred.index || 0], prob: pred.prob || 0, })); } /** * Apply regularization to weights */ _applyRegularization() { const model = this.elm.model; if (!model || !model.W) return; const W = model.W; const lambda = this.options.regularization.lambda; const alpha = this.options.regularization.alpha || 0.5; // Apply regularization for (let i = 0; i < W.length; i++) { for (let j = 0; j < W[i].length; j++) { const w = W[i][j]; if (this.options.regularization.type === 'l1') { // L1: soft thresholding const sign = w >= 0 ? 1 : -1; W[i][j] = sign * Math.max(0, Math.abs(w) - lambda); } else if (this.options.regularization.type === 'l2') { // L2: shrinkage W[i][j] = w / (1 + lambda); } else if (this.options.regularization.type === 'elastic') { // Elastic net: combination const l1 = alpha * lambda; const l2 = (1 - alpha) * lambda; const sign = w >= 0 ? 1 : -1; const softThresh = sign * Math.max(0, Math.abs(w) - l1); W[i][j] = softThresh / (1 + l2); } } } } /** * Prune small weights for sparsity */ _pruneWeights() { const model = this.elm.model; if (!model || !model.W) return; const W = model.W; const threshold = this.options.pruneThreshold; this.weightMask = []; // Prune weights below threshold for (let i = 0; i < W.length; i++) { this.weightMask[i] = []; for (let j = 0; j < W[i].length; j++) { if (Math.abs(W[i][j]) < threshold) { W[i][j] = 0; this.weightMask[i][j] = false; } else { this.weightMask[i][j] = true; } } } // Enforce sparsity target const currentSparsity = this._computeSparsity(); if (currentSparsity < this.options.sparsityTarget) { this._enforceSparsityTarget(); } } /** * Compute current sparsity ratio */ _computeSparsity() { if (this.weightMask.length === 0) return 0; let total = 0; let zeros = 0; for (const row of this.weightMask) { for (const active of row) { total++; if (!active) zeros++; } } return total > 0 ? zeros / total : 0; } /** * Enforce target sparsity by pruning more weights */ _enforceSparsityTarget() { const model = this.elm.model; if (!model || !model.W) return; const W = model.W; const target = this.options.sparsityTarget; // Collect all weights with their absolute values const weights = []; for (let i = 0; i < W.length; i++) { for (let j = 0; j < W[i].length; j++) { if (Math.abs(W[i][j]) > 0) { weights.push({ i, j, abs: Math.abs(W[i][j]) }); } } } // Sort by absolute value weights.sort((a, b) => a.abs - b.abs); // Prune smallest weights to reach target const totalWeights = W.length * (W[0]?.length || 0); const targetZeros = Math.floor(totalWeights * target); const currentZeros = totalWeights - weights.length; const needToPrune = targetZeros - currentZeros; for (let k = 0; k < Math.min(needToPrune, weights.length); k++) { const { i, j } = weights[k]; W[i][j] = 0; if (this.weightMask[i]) { this.weightMask[i][j] = false; } } } /** * Compute feature importance based on weight magnitudes */ _computeFeatureImportance() { const model = this.elm.model; if (!model || !model.W) return; const W = model.W; const inputDim = W[0]?.length || 0; this.featureImportance = new Array(inputDim).fill(0); // Sum absolute weights for each input feature for (let i = 0; i < W.length; i++) { for (let j = 0; j < W[i].length; j++) { this.featureImportance[j] += Math.abs(W[i][j]); } } // Normalize const max = Math.max(...this.featureImportance); if (max > 0) { for (let i = 0; i < this.featureImportance.length; i++) { this.featureImportance[i] /= max; } } } /** * Get feature importance scores */ getFeatureImportance() { return [...this.featureImportance]; } /** * Get sparsity statistics */ getSparsityStats() { const model = this.elm.model; if (!model || !model.W) { return { sparsity: 0, activeWeights: 0, totalWeights: 0 }; } const W = model.W; let total = 0; let active = 0; for (let i = 0; i < W.length; i++) { for (let j = 0; j < W[i].length; j++) { total++; if (Math.abs(W[i][j]) > this.options.pruneThreshold) { active++; } } } return { sparsity: total > 0 ? 1 - active / total : 0, activeWeights: active, totalWeights: total, }; } } //# sourceMappingURL=sparse-elm.js.map