@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
173 lines (165 loc) • 4.75 kB
JavaScript
const kernels = {
gaussian:
({ s = 1 }) =>
(a, b) =>
Math.exp(-a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0) / s ** 2),
polynomial:
({ d = 2 }) =>
(a, b) =>
(1 + a.reduce((s, v, i) => s + v * b[i], 0)) ** d,
}
/**
* Implicit online Learning with Kernels
*/
export class ILK {
// Implicit Online Learning with Kernels
// https://proceedings.neurips.cc/paper/2006/file/a92c274b8be496fb05d95033552eeddd-Paper.pdf
/**
* @param {number} [eta] Learning rate
* @param {number} [lambda] Regularization constant
* @param {number} [c] Penalty imposed on point prediction violations.
* @param {'gaussian' | 'polynomial' | { name: 'gaussian', s?: number } | { name: 'polynomial', d?: number } | function (number[], number[]): number} [kernel] Kernel name
* @param {'square' | 'hinge' | 'logistic'} [loss] Loss type name
*/
constructor(eta = 1, lambda = 1, c = 1, kernel = 'gaussian', loss = 'hinge') {
this._eta = eta
this._lambda = lambda
this._c = c
if (typeof kernel === 'function') {
this._kernel = kernel
} else {
if (typeof kernel === 'string') {
kernel = { name: kernel }
}
this._kernel = kernels[kernel.name](kernel)
}
if (loss === 'square') {
this._loss = (f, k, y) => {
const tau = (this._eta * this._lambda) / (1 + this._eta * this._lambda)
return (this._c * (1 - tau) * (y - (1 - tau) * f)) / (1 + this._c * (1 - tau) * k)
}
} else if (loss === 'hinge') {
this._rho = 1
this._loss = (f, k, y) => {
const tau = (this._eta * this._lambda) / (1 + this._eta * this._lambda)
const ahat = (y * (this._rho - (1 - tau) * y * f)) / k
if (y * ahat < 0) {
return 0
} else if (y * ahat > (1 - tau) * this._c) {
return y * (1 - tau) * this._c
}
return ahat
}
} else if (loss === 'graph') {
throw new Error('Not implemented.')
} else if (loss === 'logistic') {
this._loss = (f, k, y) => {
const fn = a => {
const tau = (this._eta * this._lambda) / (1 + this._eta * this._lambda)
return ((1 - tau) * this._c * y) / (1 + Math.exp(y * (1 - tau) * f + a * y * k)) - a
}
let ap = [0, fn(0)]
let an = [y * this._c, fn(y * this._c)]
while (an[0] - ap[0] >= 1.0e-8) {
const ma = (an[0] + ap[0]) / 2
const mf = fn(ma)
if (mf === 0) {
return ma
} else if (ap[1] * mf > 0) {
ap = [ma, mf]
} else {
an = [ma, mf]
}
}
return (an[0] + ap[0]) / 2
}
}
this._sv = []
this._a = []
}
/**
* Update model parameters with one data.
* @param {number[]} x Training data
* @param {1 | -1} y Target value
*/
update(x, y) {
let s = 0
for (let k = 0; k < this._sv.length; k++) {
s += this._a[k] * this._kernel(x, this._sv[k])
}
const k = this._kernel(x, x)
const at = this._loss(s, k, y)
const tau = (this._eta * this._lambda) / (1 + this._eta * this._lambda)
for (let k = 0; k < this._a.length; k++) {
this._a[k] *= 1 - tau
}
if (at === 0) {
return
}
this._sv.push(x)
this._a.push(at)
}
/**
* Fit model.
* @param {Array<Array<number>>} x Training data
* @param {Array<1 | -1>} y Target values
*/
fit(x, y) {
for (let t = 0; t < x.length; t++) {
this.update(x[t], y[t])
}
}
/**
* Returns predicted values.
* @param {Array<Array<number>>} data Sample data
* @returns {(1 | -1)[]} Predicted values
*/
predict(data) {
const pred = []
for (let i = 0; i < data.length; i++) {
let s = 0
for (let k = 0; k < this._sv.length; k++) {
s += this._a[k] * this._kernel(data[i], this._sv[k])
}
pred[i] = s < 0 ? -1 : 1
}
return pred
}
}
/**
* Sparse Implicit online Learning with Kernels
*/
export class SILK extends ILK {
/**
* @param {number} [eta] Learning rate
* @param {number} [lambda] Regularization constant
* @param {number} [c] Penalty imposed on point prediction violations.
* @param {number} [w] Buffer size
* @param {'gaussian' | 'polynomial' | { name: 'gaussian', s?: number } | { name: 'polynomial', d?: number } | function (number[], number[]): number} [kernel] Kernel name
* @param {'square' | 'hinge' | 'graph' | 'logistic'} [loss] Loss type name
*/
constructor(eta = 1, lambda = 1, c = 1, w = 10, kernel = 'gaussian', loss = 'hinge') {
super(eta, lambda, c, kernel, loss)
this._w = w
}
/**
* Update model parameters with one data.
* @param {number[]} x Training data
* @param {1 | -1} y Target value
*/
update(x, y) {
super.update(x, y)
if (this._sv.length > this._w) {
let mina = Infinity
let mink = -1
for (let k = 0; k < this._sv.length; k++) {
if (Math.abs(this._a[k]) < mina) {
mina = Math.abs(this._a[k])
mink = k
}
}
this._a.splice(mink, 1)
this._sv.splice(mink, 1)
}
}
}