UNPKG

@ai-on-browser/data-analysis-models

Version:

Data analysis model package without any dependencies

194 lines (174 loc) 4.44 kB
const kernels = { gaussian: ({ d = 1 }) => (a, b) => { const r = a.reduce((acc, v, i) => acc + (v - b[i]) ** 2, 0) return Math.exp(-r / (2 * d * d)) }, linear: () => (a, b) => a.reduce((acc, v, i) => acc + v * b[i], 0), } /** * Support vector regression */ export default class SVR { // https://jp.mathworks.com/matlabcentral/fileexchange/79790-sequential-minimal-optimization-smo-for-svr /** * @param {'gaussian' | 'linear' | { name: 'gaussian', d?: number } | { name: 'linear' } | function (number[], number[]): number} kernel Kernel name */ constructor(kernel) { if (typeof kernel === 'function') { this._kernel = kernel } else { if (typeof kernel === 'string') { kernel = { name: kernel } } this._kernel = kernels[kernel.name](kernel) } this._C = 1 this._eps = 0.001 this._tolerance = 0.001 } /** * Initialize this model. * @param {Array<Array<number>>} x Training data * @param {Array<Array<number>>} y Target values */ init(x, y) { this._x = x this._t = y.map(v => v[0]) const n = this._x.length this._a = Array(n).fill(0) this._b = 0 this._alldata = true this._err = this._t.map(v => -v) this._k = [] for (let i = 0; i < n; this._k[i++] = []); for (let i = 0; i < n; i++) { for (let j = i; j < n; j++) { this._k[i][j] = this._k[j][i] = this._kernel(this._x[i], this._x[j]) } } } /** * Fit model. */ fit() { const changed = this._fitOnce() if (this._alldata) { this._alldata = false if (changed === 0) { return } } else if (changed === 0) { this._alldata = true } } _fitOnce() { let change = 0 const n = this._x.length const between_eps = v => this._eps < v && v < this._C - this._eps for (let i = 0; i < n; i++) { if (!between_eps(this._a[i]) && !this._alldata) { continue } if ( (this._a[i] >= this._C - this._eps || this._err[i] >= -this._tolerance) && (this._a[i] <= this._eps || this._err[i] <= this._tolerance) ) { continue } let ii = -1 let ii_e = null for (let j = 0; j < n; j++) { if (between_eps(this._a[j])) { if (this._err[i] > 0) { if (ii_e === null || ii_e > this._err[i]) { ii = j ii_e = this._err[i] } } else { if (ii_e === null || ii_e < this._err[i]) { ii = j ii_e = this._err[i] } } } } if (ii === -1 || ii === i) { const offset = Math.floor(Math.random() * (n + 1)) for (let j = 0; j < n; j++) { const p = (j + offset) % n if (p === i) { continue } if (between_eps(this._a[p])) { ii = p break } } } if (ii === -1 || ii === i) { const offset = Math.floor(Math.random() * (n + 1)) for (let j = 0; j < n; j++) { const p = (j + offset) % n if (p === i) { continue } ii = p break } } if (ii === -1 || ii === i) { continue } const j = ii const ai_old = this._a[i] const aj_old = this._a[j] const s = this._a[i] + this._a[j] const kii = this._k[i][i] const kjj = this._k[j][j] const kij = this._k[i][j] const eta = kii + kjj - 2 * kij const delta = (2 * this._eps) / eta let fi = 0 let fj = 0 for (let m = 0; m < n; m++) { fi += this._k[i][m] * this._a[m] fj += this._k[j][m] * this._a[m] } this._a[i] += (1 / eta) * (this._t[i] - this._t[j] - fi + fj) this._a[j] = s - this._a[i] if (this._a[i] * this._a[j] < 0) { if (Math.abs(this._a[i]) >= delta && Math.abs(this._a[j]) >= delta) { this._a[i] -= Math.sign(this._a[i]) * delta } else { this._a[i] = Math.abs(this._a[i]) - Math.abs(this._a[j]) < 0 ? 0 : s } } const low = Math.max(s - this._C, -this._C) const high = Math.min(this._C, s + this._C) this._a[i] = Math.min(Math.max(this._a[i], low), high) this._a[j] = s - this._a[i] for (let m = 0; m < n; m++) { this._err[m] += (this._a[i] - ai_old) * this._k[i][m] + (this._a[j] - aj_old) * this._k[j][m] } change++ } this._b = this._err.reduce((s, v) => s + v, 0) / n return change } /** * Returns predicted values. * @param {Array<Array<number>>} x Sample data * @returns {number[]} Predicted values */ predict(x) { return x.map(v => { let y = 0 for (let n = 0; n < this._x.length; n++) { if (this._a[n]) y += this._a[n] * this._kernel(v, this._x[n]) } return y - this._b }) } }