@astermind/astermind-pro
Version:
Astermind Pro - Premium ML Toolkit with Advanced RAG, Reranking, Summarization, and Information Flow Analysis
257 lines • 9.04 kB
JavaScript
// online-kernel-elm.ts — Online Kernel ELM for streaming data
// Incremental kernel learning with forgetting mechanisms
import { OnlineRidge } from '../math/online-ridge.js';
import { requireLicense } from '../core/license.js';
/**
* Online Kernel ELM for real-time learning from streaming data
* Features:
* - Incremental kernel matrix updates
* - Sliding window with forgetting
* - Adaptive landmark selection
* - Real-time prediction
*/
export class OnlineKernelELM {
constructor(options) {
// Storage for streaming data
this.landmarks = [];
this.landmarkIndices = [];
this.samples = [];
this.labels = [];
this.sampleWeights = [];
// Online ridge for incremental updates
this.onlineRidge = null;
this.kernelMatrix = [];
this.kernelMatrixInv = [];
this.trained = false;
requireLicense(); // Premium feature - requires valid license
this.kernelType = options.kernel.type;
this.kernelParams = {
gamma: options.kernel.gamma ?? 0.01,
degree: options.kernel.degree ?? 2,
coef0: options.kernel.coef0 ?? 0,
};
this.categories = options.categories;
this.ridgeLambda = options.ridgeLambda ?? 0.001;
this.windowSize = options.windowSize ?? 1000;
this.decayFactor = options.decayFactor ?? 0.99;
this.maxLandmarks = options.maxLandmarks ?? 100;
}
/**
* Initial training with batch data
*/
fit(X, y) {
const oneHotY = this._toOneHot(y);
// Select landmarks
this._selectLandmarks(X);
// Compute initial kernel matrix
this._computeKernelMatrix(X);
// Initialize online ridge
this.onlineRidge = new OnlineRidge(this.landmarks.length, this.categories.length, this.ridgeLambda);
// Train on initial batch
for (let i = 0; i < X.length; i++) {
const phi = this._computeKernelFeatures(X[i]);
const yVec = new Float64Array(oneHotY[i]);
this.onlineRidge.update(phi, yVec);
}
// Store samples
this.samples = X.map(x => [...x]);
this.labels = Array.isArray(y[0])
? y.map(yy => this._argmax(yy))
: y;
this.sampleWeights = new Array(X.length).fill(1.0);
this.trained = true;
}
/**
* Incremental update with new sample
*/
update(x, y) {
if (!this.trained) {
throw new Error('Model must be initially trained with fit() before incremental updates');
}
const oneHotY = Array.isArray(y)
? y
: (() => {
const oh = new Array(this.categories.length).fill(0);
oh[y] = 1;
return oh;
})();
// Add to samples
this.samples.push([...x]);
this.labels.push(Array.isArray(y) ? this._argmax(y) : y);
this.sampleWeights.push(1.0);
// Apply decay to old samples
for (let i = 0; i < this.sampleWeights.length; i++) {
this.sampleWeights[i] *= this.decayFactor;
}
// Remove old samples if window exceeded
if (this.samples.length > this.windowSize) {
const removeCount = this.samples.length - this.windowSize;
this.samples.splice(0, removeCount);
this.labels.splice(0, removeCount);
this.sampleWeights.splice(0, removeCount);
}
// Update landmarks if needed (adaptive strategy)
if (this.landmarkStrategy === 'adaptive') {
this._updateLandmarksAdaptive();
}
// Compute kernel features
const phi = this._computeKernelFeatures(x);
const yVec = new Float64Array(oneHotY);
// Update online ridge
if (this.onlineRidge) {
this.onlineRidge.update(phi, yVec);
}
}
/**
* Predict with online model
*/
predict(x, topK = 3) {
if (!this.trained || !this.onlineRidge) {
throw new Error('Model must be trained before prediction');
}
const XArray = Array.isArray(x[0]) ? x : [x];
const allPredictions = [];
for (const xi of XArray) {
const predictions = [];
const phi = this._computeKernelFeatures(xi);
const logits = this.onlineRidge.predict(phi);
// Convert to probabilities
const probs = this._softmax(logits);
// 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;
}
/**
* Select landmarks from data
*/
_selectLandmarks(X) {
const strategy = this.landmarkStrategy || 'uniform';
const n = Math.min(this.maxLandmarks, X.length);
if (strategy === 'uniform') {
const step = Math.max(1, Math.floor(X.length / n));
this.landmarkIndices = Array.from({ length: n }, (_, i) => Math.min(X.length - 1, i * step));
}
else if (strategy === 'random') {
const indices = Array.from({ length: X.length }, (_, i) => i);
for (let i = indices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[indices[i], indices[j]] = [indices[j], indices[i]];
}
this.landmarkIndices = indices.slice(0, n);
}
else {
// Adaptive: use first n samples initially
this.landmarkIndices = Array.from({ length: n }, (_, i) => i);
}
this.landmarks = this.landmarkIndices.map(idx => [...X[idx]]);
}
/**
* Compute kernel features for a sample
*/
_computeKernelFeatures(x) {
const features = new Float64Array(this.landmarks.length);
for (let i = 0; i < this.landmarks.length; i++) {
features[i] = this._kernel(x, this.landmarks[i]);
}
return features;
}
/**
* Compute kernel between two vectors
*/
_kernel(x1, x2) {
if (this.kernelType === 'linear') {
return this._dot(x1, x2);
}
else if (this.kernelType === 'rbf') {
const dist = this._squaredDistance(x1, x2);
return Math.exp(-this.kernelParams.gamma * dist);
}
else if (this.kernelType === 'polynomial') {
const dot = this._dot(x1, x2);
return Math.pow(dot + this.kernelParams.coef0, this.kernelParams.degree);
}
return 0;
}
_dot(a, b) {
let sum = 0;
for (let i = 0; i < Math.min(a.length, b.length); i++) {
sum += a[i] * b[i];
}
return sum;
}
_squaredDistance(a, b) {
let sum = 0;
for (let i = 0; i < Math.min(a.length, b.length); i++) {
const diff = a[i] - b[i];
sum += diff * diff;
}
return sum;
}
_computeKernelMatrix(X) {
// For online learning, we don't need full kernel matrix
// This is kept for compatibility
this.kernelMatrix = [];
}
_updateLandmarksAdaptive() {
// Adaptive landmark selection based on prediction error
// In practice, you might replace landmarks with high error
// For now, keep existing landmarks
}
_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 landmarkStrategy() {
return 'adaptive'; // Default for online learning
}
}
//# sourceMappingURL=online-kernel-elm.js.map