UNPKG

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

Version:

Data analysis model package without any dependencies

191 lines (174 loc) 3.93 kB
/** * Affinity propagation model */ export default class AffinityPropagation { // https://qiita.com/daiki_yosky/items/98ce56e37623c369cc60 // https://tjo.hatenablog.com/entry/2014/07/31/190218 constructor() { this._epoch = 0 this._l = 0.8 this._x = [] this._y = null } /** * Category list * @type {number[]} */ get categories() { const y = this.predict() return [...new Set(y)] } /** * Centroids * @type {Array<Array<number>>} */ get centroids() { return this.categories.map(i => this._x[i]) } /** * Number of clusters * @type {number} */ get size() { const y = this.predict() return new Set(y).size } /** * Learning epoch * @type {number} */ get epoch() { return this._epoch } /** * Initialize this model. * @param {Array<Array<number>>} datas Training data */ init(datas) { this._x = datas const n = datas.length this._r = Array(n) this._a = Array(n) this._ar = Array(n) this._s = Array(n) this._as = Array(n) for (let i = 0; i < n; i++) { this._r[i] = Array(n).fill(0) this._a[i] = Array(n).fill(0) this._ar[i] = Array(n).fill(0) this._s[i] = Array(n) this._as[i] = Array(n) } this._y = null this._epoch = 0 let min = Infinity for (let i = 0; i < n; i++) { for (let j = 0; j < i; j++) { const d = -this._x[i].reduce((s, v, k) => s + (v - this._x[j][k]) ** 2, 0) this._s[i][j] = this._s[j][i] = d this._as[i][j] = this._as[j][i] = d min = Math.min(min, d) } } for (let i = 0; i < n; i++) { this._s[i][i] = this._as[i][i] = min } } /** * Fit this model once. */ fit() { // Frey. et al. "Clustering by Passing Messages Between Data Points" (2007) // "Fast Algorithm for Affinity Propagation" const x = this._x const n = x.length const l = this._l for (let i = 0; i < n; i++) { for (let k = 0; k < n; k++) { let m = -Infinity const ss = i === k ? this._s[i] : this._as[i] for (let kd = 0; kd < n; kd++) { if (k === kd) continue m = Math.max(m, ss[kd]) } this._r[i][k] = l * this._r[i][k] + (1 - l) * (this._s[i][k] - m) } } for (let i = 0; i < n; i++) { for (let k = 0; k < n; k++) { let s = i === k ? 0 : this._r[k][k] for (let id = 0; id < n; id++) { if (id !== i && id !== k) { s += Math.max(0, this._r[id][k]) } } if (i !== k) s = Math.min(0, s) const aik = l * this._a[i][k] + (1 - l) * s this._a[i][k] = aik this._ar[i][k] = aik + this._r[i][k] this._as[i][k] = aik + this._s[i][k] } } this._y = null this._epoch++ } __fit() { // Frey. et al. "Mixture Modeling by Affinity Propagation" (2006) const x = this._x const n = x.length for (let i = 0; i < n; i++) { for (let j = 0; j < n; j++) { let s = 0 for (let k = 0; k < n; k++) { if (k === j) continue s += this._a[i][k] * this._s[i][k] } this._r[i][j] = this._s[i][j] / s } } for (let i = 0; i < n; i++) { let p = 1 for (let k = 0; k < n; k++) { if (k === i) continue p *= 1 + this._r[k][i] } this._a[i][i] = p - 1 this._ar[i][i] = this._a[i][i] + this._r[i][i] for (let j = 0; j < n; j++) { if (i === j) continue p = 1 / this._r[i][i] - 1 for (let k = 0; k < n; k++) { if (k === i || k === j) continue p *= 1 / (1 + this._r[k][i]) } this._a[i][j] = 1 / (1 + p) this._ar[i][j] = this._a[i][j] + this._r[i][j] } } this._y = null this._epoch++ } /** * Returns categories corresponding the data. * @returns {number[]} Predicted values */ predict() { if (!this._y) { this._y = [] const n = this._x.length for (let i = 0; i < n; i++) { let max_v = -Infinity let max_i = -1 for (let j = 0; j < n; j++) { const v = this._ar[i][j] if (max_v < v) { max_v = v max_i = j } } this._y.push(max_i) } } return this._y } }