UNPKG

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

Version:

Data analysis model package without any dependencies

180 lines (162 loc) 4.15 kB
import Matrix from '../util/matrix.js' class GaussianKernel { constructor(a = 1.0, b = 1.0, e = 0.1) { this._a = a this._b = b this._e = e } _calc(x0, x1) { const s = Matrix.sub(x0, x1).reduce((acc, v) => acc + v * v, 0) return this._a * Math.exp(-s / (2 * this._b)) } _grad(x0, x1, k) { const g = Matrix.sub(x0, x1) g.mult((-this._a * k) / this._b) return g } calc(x, y) { if (!y) { y = x } const n = x.rows const m = y.rows const K = new Matrix(n, m) for (let i = 0; i < n; i++) { const xi = x.row(i) for (let j = 0; j < m; j++) { const v = this._calc(xi, y.row(j)) K.set(i, j, v) } } return K } grad(x, k) { const n = x.rows const d = new Matrix(n, n) for (let i = 0; i < n; i++) { const xi = x.row(i) for (let j = 0; j < n; j++) { const v = this._grad(xi, x.row(j), k.at(i, j)) d.set(i, j, v.value) } } return d } update(x, k, G) { if (this._e === 0) { return } const n = x.rows const d = new Matrix(n, n) for (let i = 0; i < n; i++) { const xi = x.row(i) for (let j = 0; j < n; j++) { const di = Matrix.sub(xi, x.row(j)) di.map(v => v ** 2) const s = -(this._a * di.sum()) / (2 * this._b ** 2) d.set(i, j, s * k.at(i, j)) } } const dsig = Matrix.mult(G, d.t).sum() this._b = Math.exp(Math.log(this._b) + (this._e / n) * dsig) if (this._b === 0) { this._b = 1.0e-8 } } } /** * Gaussian Process Latent Variable Model */ export default class GPLVM { // https://qiita.com/student-i/items/328030426fa42b6010f9 // https://cmbnur.com/?p=1621 // https://ayatoashihara.github.io/my_blog/post/post9/ /** * @param {number} rd Reduced dimension * @param {number} alpha Precision parameter * @param {number} [ez] Learning rate for z * @param {number} [ea] Learning rate for alpha * @param {number} [ep] Learning rate for kernel * @param {'gaussian' | { name: 'gaussian', a?: number, b?: number}} [kernel] Kernel name */ constructor(rd, alpha, ez = 1, ea = 0.005, ep = 0.2, kernel = 'gaussian') { this._rd = rd this._alpha = alpha if (typeof kernel === 'string') { kernel = { name: kernel } } this._kernel = new GaussianKernel(kernel.a, kernel.b, ep) this._ez = ez this._ea = ea } /** * Initialize model. * @param {Array<Array<number>>} x Training data */ init(x) { this._x = Matrix.fromArray(x) this._z = Matrix.randn(x.length, this._rd, 0, 0.01) this._s = this._x.dot(this._x.t) } /** * Fit model. */ fit() { const n = this._x.rows const ker = this._kernel.calc(this._z) const K = Matrix.add(ker, Matrix.eye(n, n, this._alpha)) const Kinv = K.inv() const G = Kinv.dot(this._s).dot(Kinv) G.sub(Matrix.mult(Kinv, this._x.cols)) G.div(2) const dK = this._kernel.grad(this._z, K) const dz = new Matrix(n, this._rd) for (let i = 0; i < n; i++) { const dzi = Matrix.zeros(1, this._rd) for (let j = 0; j < n; j++) { const v = new Matrix(1, this._rd, dK.at(i, j)) v.mult(G.at(i, j)) dzi.add(v) } dz.set(i, 0, dzi) } dz.mult(this._ez / n) this._kernel.update(this._z, ker, G) this._z.add(dz) this._alpha = Math.exp(Math.log(this._alpha) + (this._ea / n) * G.trace() * this._alpha) } /** * Returns log likelihood. * @returns {number} Log likelihood */ llh() { const n = this._x.rows const ker = this._kernel.calc(this._z) const K = Matrix.add(ker, Matrix.eye(n, n, this._alpha)) return ( (-this._x.length * Math.log(2 * Math.PI)) / 2 - (this._x.cols * Math.log(K.det())) / 2 - (this._x.cols * K.solve(this._s).trace()) / 2 ) } /** * Returns reduced datas. * @returns {Array<Array<number>>} Predicted values */ predict() { return this._z.toArray() } /** * Returns reconstruct datas. * @param {Array<Array<number>>} z Sample data * @returns {Array<Array<number>>} Predicted values */ reconstruct(z) { z = Matrix.fromArray(z) const n = this._z.rows const K = this._kernel.calc(this._z) const Ka = Matrix.add(K, Matrix.eye(n, n, this._alpha)) const ks = this._kernel.calc(z, this._z) return ks.dot(Ka.solve(this._x)).toArray() } }