@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
144 lines (137 loc) • 3.81 kB
JavaScript
const kernels = {
gaussian:
({ s = 1 }) =>
(a, b) =>
Math.exp(-(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0) ** 2) / s ** 2),
polynomial:
({ d = 2 }) =>
(a, b) =>
(1 + a.reduce((s, v, i) => s + v * b[i])) ** d,
}
/**
* Bounded Online Gradient Descent
*/
export default class BOGD {
// Fast Bounded Online Gradient Descent Algorithms
// https://arxiv.org/abs/1206.4633
/**
* @param {number} [b] Maximum budget size
* @param {number} [eta] Stepsize
* @param {number} [lambda] Regularization parameter
* @param {number} [gamma] Maximum coefficient
* @param {'uniform' | 'nonuniform'} [sampling] Sampling approach
* @param {'gaussian' | 'polynomial' | { name: 'gaussian', s?: number } | { name: 'polynomial', d?: number } | function (number[], number[]): number} [kernel] Kernel name
* @param {'zero_one' | 'hinge'} [loss] Loss type name
*/
constructor(
b = 10,
eta = 1,
lambda = 0.1,
gamma = 0.1,
sampling = 'nonuniform',
kernel = 'gaussian',
loss = 'hinge'
) {
this._b = b
this._eta = eta
this._lambda = lambda
this._gamma = gamma
this._sampling = sampling
if (typeof kernel === 'function') {
this._kernel = kernel
} else {
if (typeof kernel === 'string') {
kernel = { name: kernel }
}
this._kernel = kernels[kernel.name](kernel)
}
if (loss === 'zero_one') {
this._gloss = (t, y) => {
return t * (y <= 0 ? -1 : 1) <= 0 ? -1 : 0
}
} else if (loss === 'hinge') {
this._gloss = (t, y) => {
return t * (y <= 0 ? -1 : 1) <= 1 ? -1 : 0
}
}
this._sv = []
this._alpha = []
}
/**
* 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++) {
let s = 0
for (let k = 0; k < this._sv.length; k++) {
const sk = this._sv[k]
s += this._alpha[k] * sk.y * this._kernel(x[t], sk.x)
}
const gloss = this._gloss(y[t], s)
if (gloss === 0) {
this._alpha = this._alpha.map(v => (1 - this._eta * this._lambda) * v)
} else if (this._sv.length < this._b) {
this._alpha = this._alpha.map(v => (1 - this._eta * this._lambda) * v)
this._alpha.push(-this._eta * gloss)
this._sv.push({ x: x[t], y: y[t] })
} else {
let ik = -1
const p = []
if (this._sampling === 'uniform') {
for (let k = 0; k < this._sv.length; k++) {
p[k] = 1 / this._sv.length
}
ik = Math.floor(Math.random() * this._sv.length)
} else {
const ak = []
for (let k = 0; k < this._sv.length; k++) {
const sk = this._sv[k]
ak[k] = this._alpha[k] * Math.sqrt(this._kernel(sk.x, sk.x))
}
const s = (this._b - 1) / ak.reduce((s, v) => s + v, 0)
for (let k = 0; k < this._sv.length; k++) {
p[k] = 1 - s * ak[k]
}
let r = Math.random() * p.reduce((s, v) => s + v, 0)
for (let k = 0; k < p.length; k++) {
r -= p[k]
if (r < 0) {
ik = k
break
}
}
}
const le = this._lambda * this._eta
for (let k = 0; k < this._sv.length; k++) {
this._alpha[k] *= (1 - le) / (1 - p[k])
}
this._sv.splice(ik, 1)
this._alpha.splice(ik, 1)
this._sv.push({ x: x[t], y: y[t] })
this._alpha.push(-this._eta * gloss)
}
for (let k = 0; k < this._sv.length; k++) {
this._alpha[k] = Math.min(this._alpha[k], this._gamma * this._eta)
}
}
}
/**
* 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++) {
const sk = this._sv[k]
s += this._alpha[k] * sk.y * this._kernel(data[i], sk.x)
}
pred[i] = s < 0 ? -1 : 1
}
return pred
}
}