diff --git a/lib/model/gmeans.js b/lib/model/gmeans.js index b930785ad..9ec9f5764 100644 --- a/lib/model/gmeans.js +++ b/lib/model/gmeans.js @@ -1,6 +1,69 @@ import Matrix from '../util/matrix.js' -import { KMeans } from './kmeans.js' +class KMeans { + constructor(x, k) { + this._x = x + this._k = k + + const n = this._x.length + const idx = [] + for (let i = 0; i < this._k; i++) { + idx.push(Math.floor(Math.random() * (n - i))) + } + for (let i = idx.length - 1; i >= 0; i--) { + for (let j = idx.length - 1; j > i; j--) { + if (idx[i] <= idx[j]) { + idx[j]++ + } + } + } + this._c = idx.map(v => this._x[v]) + + this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0)) + } + + get centroids() { + return this._c + } + + fit() { + const p = this.predict() + + const c = this._c.map(p => Array.from(p, () => 0)) + const count = Array(this._k).fill(0) + const n = this._x.length + for (let i = 0; i < n; i++) { + for (let j = 0; j < this._x[i].length; j++) { + c[p[i]][j] += this._x[i][j] + } + count[p[i]]++ + } + let d = 0 + for (let k = 0; k < this._k; k++) { + const mc = c[k].map(v => v / count[k]) + d += this._c[k].reduce((s, v, j) => s + (v - mc[j]) ** 2, 0) + this._c[k] = c[k].map(v => v / count[k]) + } + return d + } + + predict() { + const p = [] + const n = this._x.length + for (let i = 0; i < n; i++) { + let min_d = Infinity + p[i] = -1 + for (let k = 0; k < this._k; k++) { + const d = this._d(this._x[i], this._c[k]) + if (d < min_d) { + min_d = d + p[i] = k + } + } + } + return p + } +} const cvTable = [ [0.514, 0.578, 0.683, 0.779, 0.926], @@ -123,16 +186,13 @@ export default class GMeans { } _split_cluster(datas, k = 2) { - const kmeans = new KMeans() - for (let i = 0; i < k; i++) { - kmeans.add(datas) - } - while (kmeans.fit(datas) > 0); + const kmeans = new KMeans(datas, k) + while (kmeans.fit() > 0); return this._create_clusters(kmeans, datas) } _create_clusters(model, datas) { - const k = model.size + const k = model.centroids.length const p = model.predict(datas) const ds = [] for (let i = 0; i < k; ds[i++] = []); diff --git a/lib/model/lbg.js b/lib/model/lbg.js index 433022a02..fb0b86d47 100644 --- a/lib/model/lbg.js +++ b/lib/model/lbg.js @@ -1,7 +1,5 @@ import Matrix from '../util/matrix.js' -import { KMeans } from './kmeans.js' - /** * Linde-Buzo-Gray algorithm */ @@ -65,9 +63,28 @@ export default class LBG { new_centroids.push(cp, cn) } - const kmeans = new KMeans() - kmeans._centroids = new_centroids - while (kmeans.fit(datas) > 0) this._centroids = kmeans.centroids + this._centroids = new_centroids + let d = 0 + do { + const p = this.predict(datas) + + const size = this._centroids.length + const c = this._centroids.map(p => Array.from(p, () => 0)) + const count = Array(size).fill(0) + const n = datas.length + for (let i = 0; i < n; i++) { + for (let j = 0; j < datas[i].length; j++) { + c[p[i]][j] += datas[i][j] + } + count[p[i]]++ + } + d = 0 + for (let k = 0; k < size; k++) { + const mc = c[k].map(v => v / count[k]) + d += this._centroids[k].reduce((s, v, j) => s + (v - mc[j]) ** 2, 0) + this._centroids[k] = c[k].map(v => v / count[k]) + } + } while (d > 0) } /** diff --git a/lib/model/xmeans.js b/lib/model/xmeans.js index 37c4368f3..661cc29da 100644 --- a/lib/model/xmeans.js +++ b/lib/model/xmeans.js @@ -1,6 +1,69 @@ import Matrix from '../util/matrix.js' -import { KMeans } from './kmeans.js' +class KMeans { + constructor(x, k) { + this._x = x + this._k = k + + const n = this._x.length + const idx = [] + for (let i = 0; i < this._k; i++) { + idx.push(Math.floor(Math.random() * (n - i))) + } + for (let i = idx.length - 1; i >= 0; i--) { + for (let j = idx.length - 1; j > i; j--) { + if (idx[i] <= idx[j]) { + idx[j]++ + } + } + } + this._c = idx.map(v => this._x[v]) + + this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0)) + } + + get centroids() { + return this._c + } + + fit() { + const p = this.predict() + + const c = this._c.map(p => Array.from(p, () => 0)) + const count = Array(this._k).fill(0) + const n = this._x.length + for (let i = 0; i < n; i++) { + for (let j = 0; j < this._x[i].length; j++) { + c[p[i]][j] += this._x[i][j] + } + count[p[i]]++ + } + let d = 0 + for (let k = 0; k < this._k; k++) { + const mc = c[k].map(v => v / count[k]) + d += this._c[k].reduce((s, v, j) => s + (v - mc[j]) ** 2, 0) + this._c[k] = c[k].map(v => v / count[k]) + } + return d + } + + predict() { + const p = [] + const n = this._x.length + for (let i = 0; i < n; i++) { + let min_d = Infinity + p[i] = -1 + for (let k = 0; k < this._k; k++) { + const d = this._d(this._x[i], this._c[k]) + if (d < min_d) { + min_d = d + p[i] = k + } + } + } + return p + } +} /** * x-means @@ -92,16 +155,13 @@ export default class XMeans { } _split_cluster(datas, k = 2) { - const kmeans = new KMeans() - for (let i = 0; i < k; i++) { - kmeans.add(datas) - } - while (kmeans.fit(datas) > 0); + const kmeans = new KMeans(datas, k) + while (kmeans.fit() > 0); return this._create_clusters(kmeans, datas) } _create_clusters(model, datas) { - const k = model.size + const k = model.centroids.length const p = model.predict(datas) const ds = [] for (let i = 0; i < k; ds[i++] = []);