Skip to content

Commit 427b6a6

Browse files
authored
Remove KMeans model dependency from some models (#820)
1 parent ee5cbfd commit 427b6a6

File tree

3 files changed

+156
-19
lines changed

3 files changed

+156
-19
lines changed

lib/model/gmeans.js

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,69 @@
11
import Matrix from '../util/matrix.js'
22

3-
import { KMeans } from './kmeans.js'
3+
class KMeans {
4+
constructor(x, k) {
5+
this._x = x
6+
this._k = k
7+
8+
const n = this._x.length
9+
const idx = []
10+
for (let i = 0; i < this._k; i++) {
11+
idx.push(Math.floor(Math.random() * (n - i)))
12+
}
13+
for (let i = idx.length - 1; i >= 0; i--) {
14+
for (let j = idx.length - 1; j > i; j--) {
15+
if (idx[i] <= idx[j]) {
16+
idx[j]++
17+
}
18+
}
19+
}
20+
this._c = idx.map(v => this._x[v])
21+
22+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
23+
}
24+
25+
get centroids() {
26+
return this._c
27+
}
28+
29+
fit() {
30+
const p = this.predict()
31+
32+
const c = this._c.map(p => Array.from(p, () => 0))
33+
const count = Array(this._k).fill(0)
34+
const n = this._x.length
35+
for (let i = 0; i < n; i++) {
36+
for (let j = 0; j < this._x[i].length; j++) {
37+
c[p[i]][j] += this._x[i][j]
38+
}
39+
count[p[i]]++
40+
}
41+
let d = 0
42+
for (let k = 0; k < this._k; k++) {
43+
const mc = c[k].map(v => v / count[k])
44+
d += this._c[k].reduce((s, v, j) => s + (v - mc[j]) ** 2, 0)
45+
this._c[k] = c[k].map(v => v / count[k])
46+
}
47+
return d
48+
}
49+
50+
predict() {
51+
const p = []
52+
const n = this._x.length
53+
for (let i = 0; i < n; i++) {
54+
let min_d = Infinity
55+
p[i] = -1
56+
for (let k = 0; k < this._k; k++) {
57+
const d = this._d(this._x[i], this._c[k])
58+
if (d < min_d) {
59+
min_d = d
60+
p[i] = k
61+
}
62+
}
63+
}
64+
return p
65+
}
66+
}
467

568
const cvTable = [
669
[0.514, 0.578, 0.683, 0.779, 0.926],
@@ -123,16 +186,13 @@ export default class GMeans {
123186
}
124187

125188
_split_cluster(datas, k = 2) {
126-
const kmeans = new KMeans()
127-
for (let i = 0; i < k; i++) {
128-
kmeans.add(datas)
129-
}
130-
while (kmeans.fit(datas) > 0);
189+
const kmeans = new KMeans(datas, k)
190+
while (kmeans.fit() > 0);
131191
return this._create_clusters(kmeans, datas)
132192
}
133193

134194
_create_clusters(model, datas) {
135-
const k = model.size
195+
const k = model.centroids.length
136196
const p = model.predict(datas)
137197
const ds = []
138198
for (let i = 0; i < k; ds[i++] = []);

lib/model/lbg.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import Matrix from '../util/matrix.js'
22

3-
import { KMeans } from './kmeans.js'
4-
53
/**
64
* Linde-Buzo-Gray algorithm
75
*/
@@ -65,9 +63,28 @@ export default class LBG {
6563
new_centroids.push(cp, cn)
6664
}
6765

68-
const kmeans = new KMeans()
69-
kmeans._centroids = new_centroids
70-
while (kmeans.fit(datas) > 0) this._centroids = kmeans.centroids
66+
this._centroids = new_centroids
67+
let d = 0
68+
do {
69+
const p = this.predict(datas)
70+
71+
const size = this._centroids.length
72+
const c = this._centroids.map(p => Array.from(p, () => 0))
73+
const count = Array(size).fill(0)
74+
const n = datas.length
75+
for (let i = 0; i < n; i++) {
76+
for (let j = 0; j < datas[i].length; j++) {
77+
c[p[i]][j] += datas[i][j]
78+
}
79+
count[p[i]]++
80+
}
81+
d = 0
82+
for (let k = 0; k < size; k++) {
83+
const mc = c[k].map(v => v / count[k])
84+
d += this._centroids[k].reduce((s, v, j) => s + (v - mc[j]) ** 2, 0)
85+
this._centroids[k] = c[k].map(v => v / count[k])
86+
}
87+
} while (d > 0)
7188
}
7289

7390
/**

lib/model/xmeans.js

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,69 @@
11
import Matrix from '../util/matrix.js'
22

3-
import { KMeans } from './kmeans.js'
3+
class KMeans {
4+
constructor(x, k) {
5+
this._x = x
6+
this._k = k
7+
8+
const n = this._x.length
9+
const idx = []
10+
for (let i = 0; i < this._k; i++) {
11+
idx.push(Math.floor(Math.random() * (n - i)))
12+
}
13+
for (let i = idx.length - 1; i >= 0; i--) {
14+
for (let j = idx.length - 1; j > i; j--) {
15+
if (idx[i] <= idx[j]) {
16+
idx[j]++
17+
}
18+
}
19+
}
20+
this._c = idx.map(v => this._x[v])
21+
22+
this._d = (a, b) => Math.sqrt(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0))
23+
}
24+
25+
get centroids() {
26+
return this._c
27+
}
28+
29+
fit() {
30+
const p = this.predict()
31+
32+
const c = this._c.map(p => Array.from(p, () => 0))
33+
const count = Array(this._k).fill(0)
34+
const n = this._x.length
35+
for (let i = 0; i < n; i++) {
36+
for (let j = 0; j < this._x[i].length; j++) {
37+
c[p[i]][j] += this._x[i][j]
38+
}
39+
count[p[i]]++
40+
}
41+
let d = 0
42+
for (let k = 0; k < this._k; k++) {
43+
const mc = c[k].map(v => v / count[k])
44+
d += this._c[k].reduce((s, v, j) => s + (v - mc[j]) ** 2, 0)
45+
this._c[k] = c[k].map(v => v / count[k])
46+
}
47+
return d
48+
}
49+
50+
predict() {
51+
const p = []
52+
const n = this._x.length
53+
for (let i = 0; i < n; i++) {
54+
let min_d = Infinity
55+
p[i] = -1
56+
for (let k = 0; k < this._k; k++) {
57+
const d = this._d(this._x[i], this._c[k])
58+
if (d < min_d) {
59+
min_d = d
60+
p[i] = k
61+
}
62+
}
63+
}
64+
return p
65+
}
66+
}
467

568
/**
669
* x-means
@@ -92,16 +155,13 @@ export default class XMeans {
92155
}
93156

94157
_split_cluster(datas, k = 2) {
95-
const kmeans = new KMeans()
96-
for (let i = 0; i < k; i++) {
97-
kmeans.add(datas)
98-
}
99-
while (kmeans.fit(datas) > 0);
158+
const kmeans = new KMeans(datas, k)
159+
while (kmeans.fit() > 0);
100160
return this._create_clusters(kmeans, datas)
101161
}
102162

103163
_create_clusters(model, datas) {
104-
const k = model.size
164+
const k = model.centroids.length
105165
const p = model.predict(datas)
106166
const ds = []
107167
for (let i = 0; i < k; ds[i++] = []);

0 commit comments

Comments
 (0)