@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
252 lines (234 loc) • 5.89 kB
JavaScript
import Matrix from '../util/matrix.js'
/**
* Conditional random fields
*/
export default class CRF {
// https://qiita.com/research-PORT-INC/items/518b960e6d0d0475e1df
// https://www.slideshare.net/rezoolab/seminar-19715143
// https://data-analytics.fun/2020/12/02/conditional-random-fields/
// https://www.ism.ac.jp/editsec/toukei/pdf/64-2-179.pdf
constructor() {
this._xn = 0
this._yn = 0
this._w = null
this._e = 0.1
this._x_cand = []
this._y_cand = []
this._phi = (x, y1, y0) => {
x = this._x_cand.indexOf(x)
y1 = y1 === undefined ? this._yn - 1 : this._y_cand.indexOf(y1)
y0 = y0 === undefined ? this._yn - 1 : this._y_cand.indexOf(y0)
const pxyy = Array(this._xn * this._yn * this._yn).fill(0)
if (x >= 0 && y1 >= 0 && y0 >= 0) {
pxyy[x * this._yn * this._yn + y1 * this._yn + y0] = 1
}
const pxy = Array(this._xn * this._yn).fill(0)
if (x >= 0 && y1 >= 0) {
pxy[x * this._yn + y1] = 1
}
const pyy = Array(this._yn * this._yn).fill(0)
if (y1 >= 0 && y0 >= 0) {
pyy[y1 * this._yn + y0] = 1
}
return [].concat(pyy, pxy, pxyy)
}
}
_psi(x, y1, y0) {
const phi = this._phi(x, y1, y0)
let v = 0
for (let i = 0; i < phi.length; i++) {
v += this._w[i] * phi[i]
}
return Math.exp(v)
}
_alpha(x, scaled = false) {
const alphas = [Array(this._yn).fill(1)]
const c = scaled && [1]
for (let k = 0; k < x.length; k++) {
const ak = []
for (let i = 0; i < this._yn; i++) {
ak[i] = 0
for (let j = 0; j < this._yn; j++) {
ak[i] += alphas[k][j] * this._psi(x[k], this._y_cand[i], this._y_cand[j])
}
}
alphas[k + 1] = ak
if (c) {
c[k] = ak.reduce((s, v) => s + v, 0)
alphas[k + 1] = ak.map(v => v / c[k])
}
}
if (scaled) {
return [alphas, c]
}
return alphas
}
_beta(x, scaled = false) {
const betas = []
betas[x.length] = Array(this._yn).fill(1)
const c = scaled && []
if (scaled) {
c[x.length] = 1
}
for (let k = x.length - 1; k >= 0; k--) {
const bk = []
for (let i = 0; i < this._yn; i++) {
bk[i] = 0
for (let j = 0; j < this._yn; j++) {
bk[i] += betas[k + 1][j] * this._psi(x[k], this._y_cand[j], this._y_cand[i])
}
}
betas[k] = bk
if (c) {
c[k] = bk.reduce((s, v) => s + v, 0)
betas[k] = bk.map(v => v / c[k])
}
}
if (scaled) {
return [betas, c]
}
return betas
}
_z(x) {
const alpha = this._alpha(x)
const an = alpha[alpha.length - 1]
return an.reduce((s, v) => s + v, 0)
}
_p(x, scaled = false) {
let alpha, ca
if (scaled) {
;[alpha, ca] = this._alpha(x, true)
} else {
alpha = this._alpha(x)
}
let beta, cb
if (scaled) {
;[beta, cb] = this._beta(x, true)
} else {
beta = this._beta(x)
}
let z
if (!scaled) {
z = 1 / this._z(x)
} else {
z = 1
}
const p = []
for (let t = x.length - 1; t >= 0; t--) {
p[t] = []
if (scaled) {
z *= cb[t] / ca[t]
}
for (let i = 0; i < this._yn; i++) {
p[t][i] = []
for (let j = 0; j < this._yn; j++) {
p[t][i][j] = this._psi(x[t], this._y_cand[i], this._y_cand[j]) * alpha[t][j] * beta[t][i] * z
}
}
}
return p
}
/**
* Fit model.
* @param {Array<Array<*>>} x Training data
* @param {Array<Array<*>>} y Target values
*/
fit(x, y) {
if (!this._w) {
const x_cand = new Set()
const y_cand = new Set()
for (let i = 0; i < x.length; i++) {
for (let k = 0; k < x[i].length; k++) {
x_cand.add(x[i][k])
y_cand.add(y[i][k])
}
}
this._x_cand = [...x_cand]
this._y_cand = [...y_cand]
this._xn = this._x_cand.length
this._yn = this._y_cand.length + 1
const testvec = this._phi(x[0][1], y[0][1], y[0][0])
this._w = Matrix.randn(testvec.length, 1).value
}
const d = this._w.length
const dw = Array(d).fill(0)
for (let s = 0; s < x.length; s++) {
const p = this._p(x[s], true)
for (let t = 0; t < x[s].length; t++) {
const phi = this._phi(x[s][t], y[s][t], y[s][t - 1])
for (let i = 0; i < this._yn; i++) {
for (let j = 0; j < this._yn; j++) {
const phi_ = this._phi(x[s][t], this._y_cand[i], this._y_cand[j])
for (let k = 0; k < d; k++) {
phi[k] -= p[t][i][j] * phi_[k]
}
}
}
for (let k = 0; k < d; k++) {
dw[k] += phi[k]
}
}
}
for (let k = 0; k < d; k++) {
this._w[k] += (this._e * dw[k]) / x.length
}
}
/**
* Returns probability P(y|x).
* @param {*[]} x Sample data
* @param {*[]} y Target values
* @returns {number} Predicted values
*/
probability(x, y) {
const z = this._z(x)
const p = this._phi(x[0], y[0], undefined)
for (let i = 1; i < x.length; i++) {
const pi = this._phi(x[i], y[i], y[i - 1])
for (let k = 0; k < pi.length; k++) {
p[k] += pi[k]
}
}
return Math.exp(p.reduce((s, v, d) => s + v * this._w[d], 0)) / z
}
/**
* Returns predicted labels.
* @param {Array<Array<*>>} x Sample data
* @returns {Array<Array<*>>} Predicted values
*/
predict(x) {
const p = []
for (let k = 0; k < x.length; k++) {
const f = [Array(this._yn).fill(0)]
const g = []
for (let t = 0; t < x[k].length; t++) {
f[t + 1] = []
g[t] = []
for (let i = 0; i < this._yn; i++) {
f[t + 1][i] = -Infinity
g[t][i] = null
for (let j = 0; j < this._yn; j++) {
const phi = this._phi(x[k][t], this._y_cand[i], this._y_cand[j])
const v = f[t][j] + phi.reduce((s, v, d) => s + v * this._w[d], 0)
if (f[t + 1][i] < v) {
f[t + 1][i] = v
g[t][i] = j
}
}
}
}
p[k] = []
let mv = -Infinity
for (let i = 0; i < this._yn; i++) {
if (mv < f[x[k].length][i]) {
mv = f[x[k].length][i]
p[k][x[k].length - 1] = i
}
}
for (let t = x[k].length - 2; t >= 0; t--) {
p[k][t] = g[t + 1][p[k][t + 1]]
}
p[k] = p[k].map(v => this._y_cand[v] ?? null)
}
return p
}
}