w-cluster 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci-test.yml +3 -3
- package/README.md +4 -6
- package/babel.config.js +4 -3
- package/dist/w-cluster.umd.js +2 -2
- package/dist/w-cluster.umd.js.map +1 -1
- package/dist/w-cluster.wk.umd.js +1 -1
- package/docs/DenseMatrix.html +202 -0
- package/docs/DistancePair.html +202 -0
- package/docs/LowerTriangle.html +202 -0
- package/docs/Rec.html +202 -0
- package/docs/Reco.html +202 -0
- package/docs/WCluster.mjs.html +8 -4
- package/docs/examples/ex-PCA.html +1 -1
- package/docs/examples/ex-cluster-webworker.html +1 -1
- package/docs/examples/ex-cluster.html +1 -1
- package/docs/global.html +6799 -164
- package/docs/index.html +2 -2
- package/docs/k-medoids_alternating.mjs.html +144 -0
- package/docs/k-medoids_arrayadapter.mjs.html +107 -0
- package/docs/k-medoids_dynmsc.mjs.html +255 -0
- package/docs/k-medoids_fastermsc.mjs.html +411 -0
- package/docs/k-medoids_fasterpam.mjs.html +305 -0
- package/docs/k-medoids_fastmsc.mjs.html +166 -0
- package/docs/k-medoids_fastpam1.mjs.html +132 -0
- package/docs/k-medoids_initialization.mjs.html +107 -0
- package/docs/k-medoids_pam.mjs.html +275 -0
- package/docs/k-medoids_pammedsil.mjs.html +307 -0
- package/docs/k-medoids_pamsil.mjs.html +225 -0
- package/docs/k-medoids_par_fasterpam.mjs.html +134 -0
- package/docs/k-medoids_par_silhouette.mjs.html +126 -0
- package/docs/k-medoids_silhouette.mjs.html +193 -0
- package/docs/k-medoids_util.mjs.html +135 -0
- package/g-PCA-nodeworker.mjs +1 -1
- package/g-PCA.mjs +1 -1
- package/g-cluster-kMeans-large.mjs +83 -0
- package/g-cluster-kMeans-nodeworker.mjs +181 -0
- package/g-cluster-kMeans.mjs +178 -0
- package/g-cluster-kMedoids-large-suggest.mjs +95 -0
- package/g-cluster-kMedoids-large.mjs +104 -0
- package/{g-cluster-nodeworker.mjs → g-cluster-kMedoids-nodeworker.mjs} +1 -2
- package/{g-cluster.mjs → g-cluster-kMedoids-simple.mjs} +1 -1
- package/package.json +7 -7
- package/script.txt +18 -0
- package/src/WCluster.mjs +6 -2
- package/src/WClusterCore.mjs +30 -21
- package/src/WClusterMat.mjs +98 -24
- package/src/WPCAMat.mjs +2 -2
- package/src/jaccardBitset.mjs +63 -0
- package/src/k-medoids/README.md +124 -0
- package/src/k-medoids/alternating.mjs +72 -0
- package/src/k-medoids/arrayadapter.mjs +35 -0
- package/src/k-medoids/dynmsc.mjs +183 -0
- package/src/k-medoids/fastermsc.mjs +339 -0
- package/src/k-medoids/fasterpam.mjs +233 -0
- package/src/k-medoids/fastmsc.mjs +94 -0
- package/src/k-medoids/fastpam1.mjs +60 -0
- package/src/k-medoids/index.mjs +37 -0
- package/src/k-medoids/initialization.mjs +35 -0
- package/src/k-medoids/package.json +8 -0
- package/src/k-medoids/pam.mjs +203 -0
- package/src/k-medoids/pammedsil.mjs +235 -0
- package/src/k-medoids/pamsil.mjs +153 -0
- package/src/k-medoids/par_fasterpam.mjs +62 -0
- package/src/k-medoids/par_silhouette.mjs +54 -0
- package/src/k-medoids/silhouette.mjs +121 -0
- package/src/k-medoids/test.mjs +357 -0
- package/src/k-medoids/util.mjs +63 -0
- package/test/jaccardBitset.test.mjs +141 -0
- package/test/kMeans.test.mjs +278 -0
- package/test/kMedoids-large-suggest.test.mjs +140 -0
- package/test/kMedoids-large.test.mjs +142 -0
- package/test/{cluster.test.mjs → kMedoids-simple.test.mjs} +5 -5
- package/toolg/gDocsExams.mjs +1 -1
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import WCluster from './src/WCluster.mjs'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
//針對「較大的數值數據」以 k-means 分群, 並記錄耗時
|
|
5
|
+
//k-means以算術平均更新中心, 適用數值(歐氏)資料; 給定seed使結果可重現
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
//確定性PRNG(mulberry32), 使資料可重現
|
|
9
|
+
function mulberry32(a) {
|
|
10
|
+
return function() {
|
|
11
|
+
a |= 0
|
|
12
|
+
a = a + 0x6D2B79F5 | 0
|
|
13
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a)
|
|
14
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t
|
|
15
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
//產生G個分離良好的數值blob(交錯排列), 回傳{data, truth}
|
|
20
|
+
function genBlobs(G, m, D, sep, jit, seed) {
|
|
21
|
+
let rnd = mulberry32(seed)
|
|
22
|
+
let centers = []
|
|
23
|
+
for (let g = 0; g < G; g++) {
|
|
24
|
+
centers.push(Array.from({ length: D }, (_, d) => g * sep + d * 3))
|
|
25
|
+
}
|
|
26
|
+
let data = []
|
|
27
|
+
let truth = []
|
|
28
|
+
for (let i = 0; i < m; i++) {
|
|
29
|
+
for (let g = 0; g < G; g++) {
|
|
30
|
+
data.push(centers[g].map((c) => c + (rnd() * 2 - 1) * jit))
|
|
31
|
+
truth.push(g)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { data, truth }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
//cluster purity(各分群以多數ground-truth標籤計, 全對為1)
|
|
38
|
+
function purity(ginds, truth) {
|
|
39
|
+
let correct = 0
|
|
40
|
+
let n = truth.length
|
|
41
|
+
for (let g of ginds) {
|
|
42
|
+
let cnt = {}
|
|
43
|
+
for (let i of g) {
|
|
44
|
+
cnt[truth[i]] = (cnt[truth[i]] || 0) + 1
|
|
45
|
+
}
|
|
46
|
+
correct += Math.max(...Object.values(cnt))
|
|
47
|
+
}
|
|
48
|
+
return correct / n
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async function testClusterLarge() {
|
|
53
|
+
|
|
54
|
+
let G = 8 //群數
|
|
55
|
+
let m = 125 //每群點數
|
|
56
|
+
let D = 8 //向量維度
|
|
57
|
+
let n = G * m
|
|
58
|
+
let seed = 0 //k-means隨機初始化種子, 使結果可重現
|
|
59
|
+
|
|
60
|
+
let { data, truth } = genBlobs(G, m, D, 100, 5, 42)
|
|
61
|
+
console.log(`資料: n=${n}, 維度D=${D}, 群數G=${G} (數值向量, 歐氏)`)
|
|
62
|
+
|
|
63
|
+
let t0 = performance.now()
|
|
64
|
+
let r = await WCluster.cluster(data, { mode: 'k-means', kNumber: G, usePCA: false, seed })
|
|
65
|
+
let dt = performance.now() - t0
|
|
66
|
+
|
|
67
|
+
console.log(`耗時: ${dt.toFixed(0)} ms (k-means, seed=${seed})`)
|
|
68
|
+
console.log(`分群數: ${r.ginds.length}, 涵蓋點數: ${r.ginds.flat().length}/${n}`)
|
|
69
|
+
console.log(`purity: ${purity(r.ginds, truth).toFixed(3)} (1.000表示完全還原ground-truth)`)
|
|
70
|
+
// => 耗時為機器相關; k-means複雜度O(n·k·iter), 遠快於k-medoids, 故n可放大。
|
|
71
|
+
// 結構大致為:
|
|
72
|
+
// 資料: n=1000, 維度D=8, 群數G=8 (數值向量, 歐氏)
|
|
73
|
+
// 耗時: 數十~數百 ms (k-means, seed=0)
|
|
74
|
+
// 分群數: 8, 涵蓋點數: 1000/1000
|
|
75
|
+
// purity: 1.000
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
testClusterLarge()
|
|
79
|
+
.catch((err) => {
|
|
80
|
+
console.log(err)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
//node g-cluster-kMeans-large.mjs
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// import WCluster from './src/WCluster.mjs'
|
|
2
|
+
import WCluster from './dist/w-cluster.wk.umd.js'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//k-means受初始隨機群中心影響, 給定seed使結果可重現
|
|
6
|
+
//注意: 本檔走 dist 建置版(WebWorker bundle)。seed/usePCA/funDist 為近期 src 新增,
|
|
7
|
+
// 須先重新 build dist 後, 此處 seed 才會生效; 否則 dist 舊版 k-means 仍為隨機, 下列輸出僅供參考
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async function testCluster() {
|
|
11
|
+
let mode = 'k-means'
|
|
12
|
+
let seed = 0
|
|
13
|
+
|
|
14
|
+
let mat = [
|
|
15
|
+
[40, 50, 60],
|
|
16
|
+
[50, 70, 60],
|
|
17
|
+
[80, 70, 90],
|
|
18
|
+
[50, 60, 80]
|
|
19
|
+
]
|
|
20
|
+
console.log('mat', mat)
|
|
21
|
+
// => mat [ [ 40, 50, 60 ], [ 50, 70, 60 ], [ 80, 70, 90 ], [ 50, 60, 80 ] ]
|
|
22
|
+
|
|
23
|
+
let resMat = await WCluster.cluster(mat, { mode, kNumber: 2, nCompNIPALS: 2, seed })
|
|
24
|
+
console.log(JSON.stringify(resMat, null, 2))
|
|
25
|
+
// => {
|
|
26
|
+
// "keys": null,
|
|
27
|
+
// "ginds": [
|
|
28
|
+
// [ 0, 1, 3 ],
|
|
29
|
+
// [ 2 ]
|
|
30
|
+
// ],
|
|
31
|
+
// "gmat": [
|
|
32
|
+
// [
|
|
33
|
+
// [ -1.704002697669786, 0.43087564048799354 ],
|
|
34
|
+
// [ -0.2509699164590232, -1.1519035967558147 ],
|
|
35
|
+
// [ -0.03633718671228592, 0.48858292291798827 ]
|
|
36
|
+
// ],
|
|
37
|
+
// [
|
|
38
|
+
// [ 1.9913098008410954, 0.23244503334983269 ]
|
|
39
|
+
// ]
|
|
40
|
+
// ],
|
|
41
|
+
// "gltdt": [
|
|
42
|
+
// [
|
|
43
|
+
// [ 40, 50, 60 ],
|
|
44
|
+
// [ 50, 70, 60 ],
|
|
45
|
+
// [ 50, 60, 80 ]
|
|
46
|
+
// ],
|
|
47
|
+
// [
|
|
48
|
+
// [ 80, 70, 90 ]
|
|
49
|
+
// ]
|
|
50
|
+
// ]
|
|
51
|
+
// }
|
|
52
|
+
|
|
53
|
+
let mat2 = [
|
|
54
|
+
[1040, 50, 60],
|
|
55
|
+
[1050, 70, 60],
|
|
56
|
+
[1080, 70, 90],
|
|
57
|
+
[1050, 60, 80]
|
|
58
|
+
]
|
|
59
|
+
console.log('mat2', mat2)
|
|
60
|
+
// => mat2 [ [ 1040, 50, 60 ], [ 1050, 70, 60 ], [ 1080, 70, 90 ], [ 1050, 60, 80 ] ]
|
|
61
|
+
|
|
62
|
+
let resMat2 = await WCluster.cluster(mat2, { mode, kNumber: 2, nCompNIPALS: 2, seed })
|
|
63
|
+
console.log(JSON.stringify(resMat2, null, 2))
|
|
64
|
+
// => {
|
|
65
|
+
// "keys": null,
|
|
66
|
+
// "ginds": [
|
|
67
|
+
// [ 0, 1, 3 ],
|
|
68
|
+
// [ 2 ]
|
|
69
|
+
// ],
|
|
70
|
+
// "gmat": [
|
|
71
|
+
// [
|
|
72
|
+
// [ -1.704002697669786, 0.43087564048799354 ],
|
|
73
|
+
// [ -0.2509699164590232, -1.1519035967558147 ],
|
|
74
|
+
// [ -0.03633718671228592, 0.48858292291798827 ]
|
|
75
|
+
// ],
|
|
76
|
+
// [
|
|
77
|
+
// [ 1.9913098008410954, 0.23244503334983269 ]
|
|
78
|
+
// ]
|
|
79
|
+
// ],
|
|
80
|
+
// "gltdt": [
|
|
81
|
+
// [
|
|
82
|
+
// [ 1040, 50, 60 ],
|
|
83
|
+
// [ 1050, 70, 60 ],
|
|
84
|
+
// [ 1050, 60, 80 ]
|
|
85
|
+
// ],
|
|
86
|
+
// [
|
|
87
|
+
// [ 1080, 70, 90 ]
|
|
88
|
+
// ]
|
|
89
|
+
// ]
|
|
90
|
+
// }
|
|
91
|
+
|
|
92
|
+
let mat3 = [
|
|
93
|
+
[11040, 50, 60],
|
|
94
|
+
[13050, 70, 60],
|
|
95
|
+
[15080, 70, 90],
|
|
96
|
+
[17050, 60, 80]
|
|
97
|
+
]
|
|
98
|
+
console.log('mat3', mat3)
|
|
99
|
+
// => mat3 [ [ 11040, 50, 60 ], [ 13050, 70, 60 ], [ 15080, 70, 90 ], [ 17050, 60, 80 ] ]
|
|
100
|
+
|
|
101
|
+
let resMat3 = await WCluster.cluster(mat3, { mode, kNumber: 2, nCompNIPALS: 2, seed })
|
|
102
|
+
console.log(JSON.stringify(resMat3, null, 2))
|
|
103
|
+
// => {
|
|
104
|
+
// "keys": null,
|
|
105
|
+
// "ginds": [
|
|
106
|
+
// [ 0 ],
|
|
107
|
+
// [ 1, 2, 3 ]
|
|
108
|
+
// ],
|
|
109
|
+
// "gmat": [
|
|
110
|
+
// [
|
|
111
|
+
// [ -1.8599655569892897, 0.4908764271508211 ]
|
|
112
|
+
// ],
|
|
113
|
+
// [
|
|
114
|
+
// [ -0.3950941652793108, -1.0977355294143445 ],
|
|
115
|
+
// [ 1.3430199944041517, -0.17213692267477554 ],
|
|
116
|
+
// [ 0.912039727864449, 0.778996024938299 ]
|
|
117
|
+
// ]
|
|
118
|
+
// ],
|
|
119
|
+
// "gltdt": [
|
|
120
|
+
// [
|
|
121
|
+
// [ 11040, 50, 60 ]
|
|
122
|
+
// ],
|
|
123
|
+
// [
|
|
124
|
+
// [ 13050, 70, 60 ],
|
|
125
|
+
// [ 15080, 70, 90 ],
|
|
126
|
+
// [ 17050, 60, 80 ]
|
|
127
|
+
// ]
|
|
128
|
+
// ]
|
|
129
|
+
// }
|
|
130
|
+
|
|
131
|
+
let ltdt = [
|
|
132
|
+
{ name: 'Cameron', a: 40, b: 50, c: 60 },
|
|
133
|
+
{ name: 'Buckley', a: 50, b: 70, c: 60 },
|
|
134
|
+
{ name: 'Paul', a: 80, b: 70, c: 90 },
|
|
135
|
+
{ name: 'Fawcett', a: 50, b: 60, c: 80 },
|
|
136
|
+
]
|
|
137
|
+
console.log('ltdt', ltdt)
|
|
138
|
+
// => ltdt [
|
|
139
|
+
// { name: 'Cameron', a: 40, b: 50, c: 60 },
|
|
140
|
+
// { name: 'Buckley', a: 50, b: 70, c: 60 },
|
|
141
|
+
// { name: 'Paul', a: 80, b: 70, c: 90 },
|
|
142
|
+
// { name: 'Fawcett', a: 50, b: 60, c: 80 }
|
|
143
|
+
// ]
|
|
144
|
+
|
|
145
|
+
let resLtdt = await WCluster.cluster(ltdt, { mode, kNumber: 2, nCompNIPALS: 2, seed })
|
|
146
|
+
console.log(JSON.stringify(resLtdt, null, 2))
|
|
147
|
+
// => {
|
|
148
|
+
// "keys": [ "a", "b", "c" ],
|
|
149
|
+
// "ginds": [
|
|
150
|
+
// [ 0, 1, 3 ],
|
|
151
|
+
// [ 2 ]
|
|
152
|
+
// ],
|
|
153
|
+
// "gmat": [
|
|
154
|
+
// [
|
|
155
|
+
// [ -1.704002697669786, 0.43087564048799354 ],
|
|
156
|
+
// [ -0.2509699164590232, -1.1519035967558147 ],
|
|
157
|
+
// [ -0.03633718671228592, 0.48858292291798827 ]
|
|
158
|
+
// ],
|
|
159
|
+
// [
|
|
160
|
+
// [ 1.9913098008410954, 0.23244503334983269 ]
|
|
161
|
+
// ]
|
|
162
|
+
// ],
|
|
163
|
+
// "gltdt": [
|
|
164
|
+
// [
|
|
165
|
+
// { "name": "Cameron", "a": 40, "b": 50, "c": 60 },
|
|
166
|
+
// { "name": "Buckley", "a": 50, "b": 70, "c": 60 },
|
|
167
|
+
// { "name": "Fawcett", "a": 50, "b": 60, "c": 80 }
|
|
168
|
+
// ],
|
|
169
|
+
// [
|
|
170
|
+
// { "name": "Paul", "a": 80, "b": 70, "c": 90 }
|
|
171
|
+
// ]
|
|
172
|
+
// ]
|
|
173
|
+
// }
|
|
174
|
+
|
|
175
|
+
}
|
|
176
|
+
testCluster()
|
|
177
|
+
.catch((err) => {
|
|
178
|
+
console.log(err)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
//node g-cluster-kMeans-nodeworker.mjs
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import WCluster from './src/WCluster.mjs'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
//k-means受初始隨機群中心影響, 給定seed使結果可重現
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async function testCluster() {
|
|
8
|
+
let mode = 'k-means'
|
|
9
|
+
let seed = 0
|
|
10
|
+
|
|
11
|
+
let mat = [
|
|
12
|
+
[40, 50, 60],
|
|
13
|
+
[50, 70, 60],
|
|
14
|
+
[80, 70, 90],
|
|
15
|
+
[50, 60, 80]
|
|
16
|
+
]
|
|
17
|
+
console.log('mat', mat)
|
|
18
|
+
// => mat [ [ 40, 50, 60 ], [ 50, 70, 60 ], [ 80, 70, 90 ], [ 50, 60, 80 ] ]
|
|
19
|
+
|
|
20
|
+
let resMat = await WCluster.cluster(mat, { mode, kNumber: 2, nCompNIPALS: 2, seed })
|
|
21
|
+
console.log(JSON.stringify(resMat, null, 2))
|
|
22
|
+
// => {
|
|
23
|
+
// "keys": null,
|
|
24
|
+
// "ginds": [
|
|
25
|
+
// [ 0, 1, 3 ],
|
|
26
|
+
// [ 2 ]
|
|
27
|
+
// ],
|
|
28
|
+
// "gmat": [
|
|
29
|
+
// [
|
|
30
|
+
// [ -1.704002697669786, 0.43087564048799354 ],
|
|
31
|
+
// [ -0.2509699164590232, -1.1519035967558147 ],
|
|
32
|
+
// [ -0.03633718671228592, 0.48858292291798827 ]
|
|
33
|
+
// ],
|
|
34
|
+
// [
|
|
35
|
+
// [ 1.9913098008410954, 0.23244503334983269 ]
|
|
36
|
+
// ]
|
|
37
|
+
// ],
|
|
38
|
+
// "gltdt": [
|
|
39
|
+
// [
|
|
40
|
+
// [ 40, 50, 60 ],
|
|
41
|
+
// [ 50, 70, 60 ],
|
|
42
|
+
// [ 50, 60, 80 ]
|
|
43
|
+
// ],
|
|
44
|
+
// [
|
|
45
|
+
// [ 80, 70, 90 ]
|
|
46
|
+
// ]
|
|
47
|
+
// ]
|
|
48
|
+
// }
|
|
49
|
+
|
|
50
|
+
let mat2 = [
|
|
51
|
+
[1040, 50, 60],
|
|
52
|
+
[1050, 70, 60],
|
|
53
|
+
[1080, 70, 90],
|
|
54
|
+
[1050, 60, 80]
|
|
55
|
+
]
|
|
56
|
+
console.log('mat2', mat2)
|
|
57
|
+
// => mat2 [ [ 1040, 50, 60 ], [ 1050, 70, 60 ], [ 1080, 70, 90 ], [ 1050, 60, 80 ] ]
|
|
58
|
+
|
|
59
|
+
let resMat2 = await WCluster.cluster(mat2, { mode, kNumber: 2, nCompNIPALS: 2, seed })
|
|
60
|
+
console.log(JSON.stringify(resMat2, null, 2))
|
|
61
|
+
// => {
|
|
62
|
+
// "keys": null,
|
|
63
|
+
// "ginds": [
|
|
64
|
+
// [ 0, 1, 3 ],
|
|
65
|
+
// [ 2 ]
|
|
66
|
+
// ],
|
|
67
|
+
// "gmat": [
|
|
68
|
+
// [
|
|
69
|
+
// [ -1.704002697669786, 0.43087564048799354 ],
|
|
70
|
+
// [ -0.2509699164590232, -1.1519035967558147 ],
|
|
71
|
+
// [ -0.03633718671228592, 0.48858292291798827 ]
|
|
72
|
+
// ],
|
|
73
|
+
// [
|
|
74
|
+
// [ 1.9913098008410954, 0.23244503334983269 ]
|
|
75
|
+
// ]
|
|
76
|
+
// ],
|
|
77
|
+
// "gltdt": [
|
|
78
|
+
// [
|
|
79
|
+
// [ 1040, 50, 60 ],
|
|
80
|
+
// [ 1050, 70, 60 ],
|
|
81
|
+
// [ 1050, 60, 80 ]
|
|
82
|
+
// ],
|
|
83
|
+
// [
|
|
84
|
+
// [ 1080, 70, 90 ]
|
|
85
|
+
// ]
|
|
86
|
+
// ]
|
|
87
|
+
// }
|
|
88
|
+
|
|
89
|
+
let mat3 = [
|
|
90
|
+
[11040, 50, 60],
|
|
91
|
+
[13050, 70, 60],
|
|
92
|
+
[15080, 70, 90],
|
|
93
|
+
[17050, 60, 80]
|
|
94
|
+
]
|
|
95
|
+
console.log('mat3', mat3)
|
|
96
|
+
// => mat3 [ [ 11040, 50, 60 ], [ 13050, 70, 60 ], [ 15080, 70, 90 ], [ 17050, 60, 80 ] ]
|
|
97
|
+
|
|
98
|
+
let resMat3 = await WCluster.cluster(mat3, { mode, kNumber: 2, nCompNIPALS: 2, seed })
|
|
99
|
+
console.log(JSON.stringify(resMat3, null, 2))
|
|
100
|
+
// => {
|
|
101
|
+
// "keys": null,
|
|
102
|
+
// "ginds": [
|
|
103
|
+
// [ 0 ],
|
|
104
|
+
// [ 1, 2, 3 ]
|
|
105
|
+
// ],
|
|
106
|
+
// "gmat": [
|
|
107
|
+
// [
|
|
108
|
+
// [ -1.8599655569892897, 0.4908764271508211 ]
|
|
109
|
+
// ],
|
|
110
|
+
// [
|
|
111
|
+
// [ -0.3950941652793108, -1.0977355294143445 ],
|
|
112
|
+
// [ 1.3430199944041517, -0.17213692267477554 ],
|
|
113
|
+
// [ 0.912039727864449, 0.778996024938299 ]
|
|
114
|
+
// ]
|
|
115
|
+
// ],
|
|
116
|
+
// "gltdt": [
|
|
117
|
+
// [
|
|
118
|
+
// [ 11040, 50, 60 ]
|
|
119
|
+
// ],
|
|
120
|
+
// [
|
|
121
|
+
// [ 13050, 70, 60 ],
|
|
122
|
+
// [ 15080, 70, 90 ],
|
|
123
|
+
// [ 17050, 60, 80 ]
|
|
124
|
+
// ]
|
|
125
|
+
// ]
|
|
126
|
+
// }
|
|
127
|
+
|
|
128
|
+
let ltdt = [
|
|
129
|
+
{ name: 'Cameron', a: 40, b: 50, c: 60 },
|
|
130
|
+
{ name: 'Buckley', a: 50, b: 70, c: 60 },
|
|
131
|
+
{ name: 'Paul', a: 80, b: 70, c: 90 },
|
|
132
|
+
{ name: 'Fawcett', a: 50, b: 60, c: 80 },
|
|
133
|
+
]
|
|
134
|
+
console.log('ltdt', ltdt)
|
|
135
|
+
// => ltdt [
|
|
136
|
+
// { name: 'Cameron', a: 40, b: 50, c: 60 },
|
|
137
|
+
// { name: 'Buckley', a: 50, b: 70, c: 60 },
|
|
138
|
+
// { name: 'Paul', a: 80, b: 70, c: 90 },
|
|
139
|
+
// { name: 'Fawcett', a: 50, b: 60, c: 80 }
|
|
140
|
+
// ]
|
|
141
|
+
|
|
142
|
+
let resLtdt = await WCluster.cluster(ltdt, { mode, kNumber: 2, nCompNIPALS: 2, seed })
|
|
143
|
+
console.log(JSON.stringify(resLtdt, null, 2))
|
|
144
|
+
// => {
|
|
145
|
+
// "keys": [ "a", "b", "c" ],
|
|
146
|
+
// "ginds": [
|
|
147
|
+
// [ 0, 1, 3 ],
|
|
148
|
+
// [ 2 ]
|
|
149
|
+
// ],
|
|
150
|
+
// "gmat": [
|
|
151
|
+
// [
|
|
152
|
+
// [ -1.704002697669786, 0.43087564048799354 ],
|
|
153
|
+
// [ -0.2509699164590232, -1.1519035967558147 ],
|
|
154
|
+
// [ -0.03633718671228592, 0.48858292291798827 ]
|
|
155
|
+
// ],
|
|
156
|
+
// [
|
|
157
|
+
// [ 1.9913098008410954, 0.23244503334983269 ]
|
|
158
|
+
// ]
|
|
159
|
+
// ],
|
|
160
|
+
// "gltdt": [
|
|
161
|
+
// [
|
|
162
|
+
// { "name": "Cameron", "a": 40, "b": 50, "c": 60 },
|
|
163
|
+
// { "name": "Buckley", "a": 50, "b": 70, "c": 60 },
|
|
164
|
+
// { "name": "Fawcett", "a": 50, "b": 60, "c": 80 }
|
|
165
|
+
// ],
|
|
166
|
+
// [
|
|
167
|
+
// { "name": "Paul", "a": 80, "b": 70, "c": 90 }
|
|
168
|
+
// ]
|
|
169
|
+
// ]
|
|
170
|
+
// }
|
|
171
|
+
|
|
172
|
+
}
|
|
173
|
+
testCluster()
|
|
174
|
+
.catch((err) => {
|
|
175
|
+
console.log(err)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
//node g-cluster-kMeans.mjs
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import WCluster from './src/WCluster.mjs'
|
|
2
|
+
import { jaccardBitset } from './src/jaccardBitset.mjs'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
//與 g-cluster-kMedoids-large.mjs 同資料、同 fasterPAM 後端(矩陣式FasterPAM), 唯一差別:
|
|
6
|
+
// 距離函數改用 bitset+popcount 版(jaccardBitset helper) 取代逐維 Jaccard。
|
|
7
|
+
// fasterPAM 後端下「建距離矩陣」為主成本, bitset 把 D 個位元打包成 ceil(D/32) 個 word、
|
|
8
|
+
// 以 popcount 一次算 32 維 → 建矩陣加速 ~一個數量級(高維 14–23x), 直接反映在總時間;
|
|
9
|
+
// 分群結果(ginds/purity)與逐維版完全相同。
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
//確定性PRNG(mulberry32), 使資料可重現
|
|
13
|
+
function mulberry32(a) {
|
|
14
|
+
return function() {
|
|
15
|
+
a |= 0
|
|
16
|
+
a = a + 0x6D2B79F5 | 0
|
|
17
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a)
|
|
18
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t
|
|
19
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//產生G個二元blob(交錯排列), 各blob一組base活躍位 + 小幅翻轉, 回傳{data, truth}
|
|
24
|
+
function genBinaryBlobs(G, m, D, active, flips, seed) {
|
|
25
|
+
let rnd = mulberry32(seed)
|
|
26
|
+
let bases = []
|
|
27
|
+
for (let g = 0; g < G; g++) {
|
|
28
|
+
let base = new Array(D).fill(0)
|
|
29
|
+
let s = new Set()
|
|
30
|
+
while (s.size < active) {
|
|
31
|
+
s.add(Math.floor(rnd() * D))
|
|
32
|
+
}
|
|
33
|
+
s.forEach((i) => {
|
|
34
|
+
base[i] = 1
|
|
35
|
+
})
|
|
36
|
+
bases.push(base)
|
|
37
|
+
}
|
|
38
|
+
let data = []
|
|
39
|
+
let truth = []
|
|
40
|
+
for (let i = 0; i < m; i++) {
|
|
41
|
+
for (let g = 0; g < G; g++) {
|
|
42
|
+
let v = bases[g].slice()
|
|
43
|
+
for (let f = 0; f < flips; f++) {
|
|
44
|
+
let p = Math.floor(rnd() * D)
|
|
45
|
+
v[p] = v[p] ? 0 : 1
|
|
46
|
+
}
|
|
47
|
+
data.push(v)
|
|
48
|
+
truth.push(g)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { data, truth }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//cluster purity(各分群以多數ground-truth標籤計, 全對為1)
|
|
55
|
+
function purity(ginds, truth) {
|
|
56
|
+
let correct = 0
|
|
57
|
+
let n = truth.length
|
|
58
|
+
for (let g of ginds) {
|
|
59
|
+
let cnt = {}
|
|
60
|
+
for (let i of g) {
|
|
61
|
+
cnt[truth[i]] = (cnt[truth[i]] || 0) + 1
|
|
62
|
+
}
|
|
63
|
+
correct += Math.max(...Object.values(cnt))
|
|
64
|
+
}
|
|
65
|
+
return correct / n
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
async function testClusterLargeSuggest() {
|
|
70
|
+
|
|
71
|
+
let G = 20 //群數
|
|
72
|
+
let m = 200 //每群點數
|
|
73
|
+
let D = 1000 //向量維度(bit數)
|
|
74
|
+
let n = G * m
|
|
75
|
+
|
|
76
|
+
let { data: rows, truth } = genBinaryBlobs(G, m, D, Math.floor(D * 0.15), 3, 42)
|
|
77
|
+
console.log(`資料: n=${n}, 維度D=${D}, 群數G=${G} (二元向量, Jaccard)`)
|
|
78
|
+
|
|
79
|
+
//bitset 打包 + fasterPAM 後端(計時含打包與建矩陣, 才是公平的端到端比較)
|
|
80
|
+
let t0 = performance.now()
|
|
81
|
+
let { data, funDist } = jaccardBitset(rows)
|
|
82
|
+
let r = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, funDist, useMethod: 'fasterPAM' })
|
|
83
|
+
let dt = performance.now() - t0
|
|
84
|
+
|
|
85
|
+
console.log(`[fasterPAM 後端 + bitset Jaccard(jaccardBitset)]`)
|
|
86
|
+
console.log(` 耗時: ${dt.toFixed(0)} ms, 分群數: ${r.ginds.length}, 涵蓋: ${r.ginds.flat().length}/${n}, purity: ${purity(r.ginds, truth).toFixed(3)}`)
|
|
87
|
+
// => 相對 g-cluster-kMedoids-large.mjs(同後端, 逐維Jaccard)明顯更快(維度越高差距越大), ginds/purity 完全相同
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
testClusterLargeSuggest()
|
|
91
|
+
.catch((err) => {
|
|
92
|
+
console.log(err)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
//node g-cluster-kMedoids-large-suggest.mjs
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import WCluster from './src/WCluster.mjs'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
//針對「較大的二元向量數據」以 k-medoids(fasterPAM 後端=矩陣式FasterPAM) + 逐維 Jaccard 分群, 記錄耗時。
|
|
5
|
+
// 作為 g-cluster-kMedoids-large-suggest.mjs(同 fasterPAM 後端, 但距離改用 bitset) 的對照基準:
|
|
6
|
+
// 兩支差別只在距離函數(逐維 vs 位元打包), 後端相同, 故可直接看出 bitset 對建矩陣的加速。
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
//確定性PRNG(mulberry32), 使資料可重現
|
|
10
|
+
function mulberry32(a) {
|
|
11
|
+
return function() {
|
|
12
|
+
a |= 0
|
|
13
|
+
a = a + 0x6D2B79F5 | 0
|
|
14
|
+
let t = Math.imul(a ^ a >>> 15, 1 | a)
|
|
15
|
+
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t
|
|
16
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//產生G個二元blob(交錯排列), 各blob一組base活躍位 + 小幅翻轉, 回傳{data, truth}
|
|
21
|
+
function genBinaryBlobs(G, m, D, active, flips, seed) {
|
|
22
|
+
let rnd = mulberry32(seed)
|
|
23
|
+
let bases = []
|
|
24
|
+
for (let g = 0; g < G; g++) {
|
|
25
|
+
let base = new Array(D).fill(0)
|
|
26
|
+
let s = new Set()
|
|
27
|
+
while (s.size < active) {
|
|
28
|
+
s.add(Math.floor(rnd() * D))
|
|
29
|
+
}
|
|
30
|
+
s.forEach((i) => {
|
|
31
|
+
base[i] = 1
|
|
32
|
+
})
|
|
33
|
+
bases.push(base)
|
|
34
|
+
}
|
|
35
|
+
let data = []
|
|
36
|
+
let truth = []
|
|
37
|
+
for (let i = 0; i < m; i++) {
|
|
38
|
+
for (let g = 0; g < G; g++) {
|
|
39
|
+
let v = bases[g].slice()
|
|
40
|
+
for (let f = 0; f < flips; f++) {
|
|
41
|
+
let p = Math.floor(rnd() * D)
|
|
42
|
+
v[p] = v[p] ? 0 : 1
|
|
43
|
+
}
|
|
44
|
+
data.push(v)
|
|
45
|
+
truth.push(g)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return { data, truth }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//Jaccard距離(0/1陣列, 逐維)
|
|
52
|
+
let jaccard = (a, b) => {
|
|
53
|
+
let inter = 0
|
|
54
|
+
let uni = 0
|
|
55
|
+
for (let i = 0; i < a.length; i++) {
|
|
56
|
+
let x = a[i]
|
|
57
|
+
let y = b[i]
|
|
58
|
+
if (x & y) inter++
|
|
59
|
+
if (x | y) uni++
|
|
60
|
+
}
|
|
61
|
+
return uni === 0 ? 0 : 1 - inter / uni
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//cluster purity(各分群以多數ground-truth標籤計, 全對為1)
|
|
65
|
+
function purity(ginds, truth) {
|
|
66
|
+
let correct = 0
|
|
67
|
+
let n = truth.length
|
|
68
|
+
for (let g of ginds) {
|
|
69
|
+
let cnt = {}
|
|
70
|
+
for (let i of g) {
|
|
71
|
+
cnt[truth[i]] = (cnt[truth[i]] || 0) + 1
|
|
72
|
+
}
|
|
73
|
+
correct += Math.max(...Object.values(cnt))
|
|
74
|
+
}
|
|
75
|
+
return correct / n
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async function testClusterLarge() {
|
|
80
|
+
|
|
81
|
+
let G = 20 //群數
|
|
82
|
+
let m = 200 //每群點數
|
|
83
|
+
let D = 1000 //向量維度(bit數)
|
|
84
|
+
let n = G * m
|
|
85
|
+
|
|
86
|
+
let { data, truth } = genBinaryBlobs(G, m, D, Math.floor(D * 0.15), 3, 42)
|
|
87
|
+
console.log(`資料: n=${n}, 維度D=${D}, 群數G=${G} (二元向量, Jaccard)`)
|
|
88
|
+
|
|
89
|
+
//fasterPAM 後端(FasterPAM) + 逐維 Jaccard
|
|
90
|
+
let t0 = performance.now()
|
|
91
|
+
let r = await WCluster.cluster(data, { mode: 'k-medoids', kNumber: G, usePCA: false, funDist: jaccard, useMethod: 'fasterPAM' })
|
|
92
|
+
let dt = performance.now() - t0
|
|
93
|
+
|
|
94
|
+
console.log(`[fasterPAM 後端 + 逐維 Jaccard]`)
|
|
95
|
+
console.log(` 耗時: ${dt.toFixed(0)} ms, 分群數: ${r.ginds.length}, 涵蓋: ${r.ginds.flat().length}/${n}, purity: ${purity(r.ginds, truth).toFixed(3)}`)
|
|
96
|
+
// => 耗時為機器相關; 此為對照基準, 與 -suggest(同後端, bitset距離) 相比可見建矩陣加速, 分群結果應完全相同
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
testClusterLarge()
|
|
100
|
+
.catch((err) => {
|
|
101
|
+
console.log(err)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
//node g-cluster-kMedoids-large.mjs
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "w-cluster",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"main": "dist/w-cluster.umd.js",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"lodash-es": "^4.
|
|
7
|
-
"k-medoids": "^1.0.
|
|
8
|
-
"ml-kmeans": "^
|
|
6
|
+
"lodash-es": "^4.18.1",
|
|
7
|
+
"k-medoids": "^1.0.8",
|
|
8
|
+
"ml-kmeans": "^7.0.1",
|
|
9
9
|
"ml-pca": "^4.1.1",
|
|
10
|
-
"wsemi": "^1.
|
|
10
|
+
"wsemi": "^1.8.59"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"w-package-tools": "^1.
|
|
13
|
+
"w-package-tools": "^1.1.9"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
|
-
"test": "mocha --parallel --timeout 60000
|
|
16
|
+
"test": "mocha --parallel --timeout 60000",
|
|
17
17
|
"deploy": "gh-pages -d docs"
|
|
18
18
|
},
|
|
19
19
|
"repository": {
|