@astermind/astermind-pro
Version:
Astermind Pro - Premium ML Toolkit with Advanced RAG, Reranking, Summarization, and Information Flow Analysis
189 lines • 7.02 kB
JavaScript
// multi-kernel-elm.ts — Multi-Kernel ELM combining multiple kernel types
// Combines RBF, polynomial, and linear kernels for improved accuracy
import { KernelELM } from '@astermind/astermind-elm';
import { requireLicense } from '../core/license.js';
/**
* Multi-Kernel ELM that combines multiple kernel types
* Uses weighted combination of kernels for improved accuracy
*/
export class MultiKernelELM {
constructor(categories, options) {
this.kelms = [];
this.kernelWeights = [];
this.categories = [];
this.trained = false;
requireLicense(); // Premium feature - requires valid license
this.categories = categories;
this.options = {
kernels: options.kernels,
ridgeLambda: options.ridgeLambda ?? 0.001,
learnWeights: options.learnWeights ?? true,
nystrom: {
m: options.nystrom?.m ?? 100,
strategy: options.nystrom?.strategy ?? 'uniform',
},
};
// Initialize kernel ELMs
for (const kernelConfig of this.options.kernels) {
const kelm = new KernelELM({
outputDim: categories.length,
kernel: {
type: kernelConfig.type === 'polynomial' ? 'rbf' : kernelConfig.type, // Map polynomial to rbf for now
gamma: kernelConfig.params?.gamma ?? 0.01,
},
ridgeLambda: this.options.ridgeLambda,
task: 'classification',
mode: 'nystrom',
nystrom: {
m: this.options.nystrom.m,
strategy: this.options.nystrom.strategy === 'random' ? 'uniform' : this.options.nystrom.strategy,
},
});
this.kelms.push(kelm);
}
// Initialize kernel weights
if (this.options.learnWeights) {
this.kernelWeights = this.options.kernels.map((k, i) => k.weight ?? 1.0 / this.options.kernels.length);
}
else {
this.kernelWeights = this.options.kernels.map((k) => k.weight ?? 1.0 / this.options.kernels.length);
}
}
/**
* Train the multi-kernel ELM
*/
fit(X, y) {
// Convert y to one-hot if needed
const oneHotY = this._toOneHot(y);
// Train each kernel ELM
for (const kelm of this.kelms) {
kelm.fit(X, oneHotY);
}
// Learn optimal kernel weights if enabled
if (this.options.learnWeights && this.kelms.length > 1) {
this._learnKernelWeights(X, oneHotY);
}
this.trained = true;
}
/**
* Predict with multi-kernel combination
*/
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 allPredictions = [];
for (const x of XArray) {
const predictions = [];
// Get predictions from each kernel
const kernelPredictions = this.kelms.map((kelm) => {
const pred = kelm.transform?.([x]) || kelm.predict?.([x]);
return (Array.isArray(pred) ? pred[0] : pred) || new Float64Array(this.categories.length);
});
// Weighted combination
const combined = new Float64Array(this.categories.length);
for (let i = 0; i < this.kelms.length; i++) {
const weight = this.kernelWeights[i];
for (let j = 0; j < this.categories.length; j++) {
combined[j] += kernelPredictions[i][j] * weight;
}
}
// Convert to probabilities
const probs = this._softmax(combined);
// Get top-K
const indexed = [];
for (let idx = 0; idx < probs.length; idx++) {
indexed.push({
label: this.categories[idx],
prob: probs[idx],
index: idx,
});
}
indexed.sort((a, b) => b.prob - a.prob);
const topResults = [];
for (let i = 0; i < Math.min(topK, indexed.length); i++) {
topResults.push({
label: indexed[i].label,
prob: indexed[i].prob,
});
}
predictions.push(...topResults);
allPredictions.push(...predictions);
}
return allPredictions;
}
/**
* Learn optimal kernel weights using validation performance
*/
_learnKernelWeights(X, y) {
// Simple approach: weight by validation accuracy
// In practice, you might use cross-validation
const weights = new Float64Array(this.kelms.length);
for (let i = 0; i < this.kelms.length; i++) {
const kelm = this.kelms[i];
let correct = 0;
let total = 0;
// Evaluate on training data (in production, use validation set)
for (let j = 0; j < Math.min(100, X.length); j++) {
const pred = kelm.transform?.([X[j]]) || kelm.predict?.([X[j]]);
const predVec = (Array.isArray(pred) ? pred[0] : pred) || new Float64Array(0);
const predIdx = this._argmax(predVec);
const trueIdx = this._argmax(y[j]);
if (predIdx === trueIdx)
correct++;
total++;
}
weights[i] = total > 0 ? correct / total : 1.0 / this.kelms.length;
}
// Normalize weights
const sum = Array.from(weights).reduce((a, b) => a + b, 0);
if (sum > 0) {
for (let i = 0; i < weights.length; i++) {
this.kernelWeights[i] = weights[i] / sum;
}
}
}
_toOneHot(y) {
if (Array.isArray(y[0])) {
return y;
}
const labels = y;
return labels.map((label) => {
const oneHot = new Array(this.categories.length).fill(0);
oneHot[label] = 1;
return oneHot;
});
}
_softmax(logits) {
const max = Math.max(...Array.from(logits));
const exp = new Float64Array(logits.length);
let sum = 0;
for (let i = 0; i < logits.length; i++) {
exp[i] = Math.exp(logits[i] - max);
sum += exp[i];
}
for (let i = 0; i < exp.length; i++) {
exp[i] /= sum;
}
return exp;
}
_argmax(arr) {
let maxIdx = 0;
let maxVal = arr[0] || 0;
for (let i = 1; i < arr.length; i++) {
if ((arr[i] || 0) > maxVal) {
maxVal = arr[i] || 0;
maxIdx = i;
}
}
return maxIdx;
}
/**
* Get current kernel weights
*/
getKernelWeights() {
return [...this.kernelWeights];
}
}
//# sourceMappingURL=multi-kernel-elm.js.map