@thi.ng/tsne 0.1.14 → 0.1.15
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/CHANGELOG.md +10 -1
- package/package.json +2 -2
- package/tsne.d.ts +3 -1
- package/tsne.js +27 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-
|
|
3
|
+
- **Last updated**: 2025-06-03T13:13:39Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -11,6 +11,15 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
11
11
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
12
12
|
and/or version bumps of transitive dependencies.
|
|
13
13
|
|
|
14
|
+
### [0.1.15](https://github.com/thi-ng/umbrella/tree/@thi.ng/tsne@0.1.15) (2025-06-03)
|
|
15
|
+
|
|
16
|
+
#### ⏱ Performance improvements
|
|
17
|
+
|
|
18
|
+
- avoid allocations, optimize computeGradient() ([8888892](https://github.com/thi-ng/umbrella/commit/8888892))
|
|
19
|
+
- pre-allocate arrays for gradient & yMean
|
|
20
|
+
- skip gradient update if same rows
|
|
21
|
+
- use destructured Math fns
|
|
22
|
+
|
|
14
23
|
### [0.1.9](https://github.com/thi-ng/umbrella/tree/@thi.ng/tsne@0.1.9) (2025-04-16)
|
|
15
24
|
|
|
16
25
|
#### ♻️ Refactoring
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/tsne",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Highly configurable t-SNE implementation for arbitrary dimensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
"status": "alpha",
|
|
83
83
|
"year": 2021
|
|
84
84
|
},
|
|
85
|
-
"gitHead": "
|
|
85
|
+
"gitHead": "cce6fa39000042b21cfecfcde6d147b4ceb4e424\n"
|
|
86
86
|
}
|
package/tsne.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare class TSNE {
|
|
|
13
13
|
points: number[][];
|
|
14
14
|
steps: number[][];
|
|
15
15
|
gains: number[][];
|
|
16
|
+
gradient: FloatArray[];
|
|
17
|
+
ymean: FloatArray;
|
|
16
18
|
opDist: DistanceFn;
|
|
17
19
|
opDivN: VecOpVN;
|
|
18
20
|
opSub: VecOpVV;
|
|
@@ -21,7 +23,7 @@ export declare class TSNE {
|
|
|
21
23
|
update(): number;
|
|
22
24
|
computeGradient(): {
|
|
23
25
|
cost: number;
|
|
24
|
-
gradient:
|
|
26
|
+
gradient: FloatArray[];
|
|
25
27
|
};
|
|
26
28
|
}
|
|
27
29
|
//# sourceMappingURL=tsne.d.ts.map
|
package/tsne.js
CHANGED
|
@@ -7,10 +7,10 @@ import { repeatedly } from "@thi.ng/transducers";
|
|
|
7
7
|
import {
|
|
8
8
|
distSq,
|
|
9
9
|
divN,
|
|
10
|
-
sub
|
|
11
|
-
zeroes
|
|
10
|
+
sub
|
|
12
11
|
} from "@thi.ng/vectors";
|
|
13
12
|
const EPS = Number.EPSILON;
|
|
13
|
+
const { exp, log, max, sign } = Math;
|
|
14
14
|
const DEFAULT_OPTS = {
|
|
15
15
|
rnd: SYSTEM,
|
|
16
16
|
dist: distSq,
|
|
@@ -44,6 +44,8 @@ class TSNE {
|
|
|
44
44
|
points;
|
|
45
45
|
steps;
|
|
46
46
|
gains;
|
|
47
|
+
gradient;
|
|
48
|
+
ymean;
|
|
47
49
|
opDist;
|
|
48
50
|
opDivN;
|
|
49
51
|
opSub;
|
|
@@ -70,11 +72,13 @@ class TSNE {
|
|
|
70
72
|
this.gains = initMatrix(n, dim, () => 1);
|
|
71
73
|
this.q = new Float64Array(n * n);
|
|
72
74
|
this.qu = new Float64Array(n * n);
|
|
75
|
+
this.ymean = new Float64Array(dim);
|
|
76
|
+
this.gradient = [...repeatedly(() => new Float64Array(dim), n)];
|
|
73
77
|
this.iter = 0;
|
|
74
78
|
}
|
|
75
79
|
update() {
|
|
76
80
|
if (++this.iter >= this.opts.maxIter) return 0;
|
|
77
|
-
const { n, dim, points, steps, gains, opDivN, opSub } = this;
|
|
81
|
+
const { n, dim, points, steps, gains, ymean, opDivN, opSub } = this;
|
|
78
82
|
const {
|
|
79
83
|
rate,
|
|
80
84
|
minGain,
|
|
@@ -84,7 +88,7 @@ class TSNE {
|
|
|
84
88
|
} = this.opts;
|
|
85
89
|
const { cost, gradient } = this.computeGradient();
|
|
86
90
|
const momentum = tweenParam($momentum, this.iter);
|
|
87
|
-
|
|
91
|
+
ymean.fill(0);
|
|
88
92
|
let i, d;
|
|
89
93
|
for (i = 0; i < n; i++) {
|
|
90
94
|
const row = points[i];
|
|
@@ -93,9 +97,9 @@ class TSNE {
|
|
|
93
97
|
const rowGains = gains[i];
|
|
94
98
|
for (d = 0; d < dim; d++) {
|
|
95
99
|
let step = rowStep[d];
|
|
96
|
-
const newGain =
|
|
100
|
+
const newGain = max(
|
|
97
101
|
minGain,
|
|
98
|
-
|
|
102
|
+
sign(rowGrad[d]) === sign(step) ? (
|
|
99
103
|
// rowGrad[d] * step < 0
|
|
100
104
|
rowGains[d] * gainDecay
|
|
101
105
|
) : rowGains[d] + gainBias
|
|
@@ -112,7 +116,7 @@ class TSNE {
|
|
|
112
116
|
return cost;
|
|
113
117
|
}
|
|
114
118
|
computeGradient() {
|
|
115
|
-
const { n, dim, points: y, p, q, qu, opDist } = this;
|
|
119
|
+
const { n, dim, gradient, points: y, p, q, qu, opDist } = this;
|
|
116
120
|
let i, j, rowIdx, d;
|
|
117
121
|
let rowI, rowJ;
|
|
118
122
|
let qsum = 0;
|
|
@@ -128,22 +132,25 @@ class TSNE {
|
|
|
128
132
|
}
|
|
129
133
|
qsum = 1 / qsum;
|
|
130
134
|
for (i = n * n; i-- > 0; ) {
|
|
131
|
-
q[i] =
|
|
135
|
+
q[i] = max(qu[i] * qsum, EPS);
|
|
132
136
|
}
|
|
133
137
|
let cost = 0;
|
|
134
|
-
const gradient = new Array(n);
|
|
135
138
|
const gscale = tweenParam(this.opts.gradientScale, this.iter);
|
|
136
139
|
for (i = 0; i < n; i++) {
|
|
137
|
-
|
|
140
|
+
const g = gradient[i].fill(0);
|
|
138
141
|
rowI = y[i];
|
|
139
|
-
|
|
142
|
+
rowIdx = i * n;
|
|
140
143
|
for (j = 0; j < n; j++) {
|
|
141
|
-
rowJ = y[j];
|
|
142
144
|
const ij = rowIdx + j;
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
const pij = p[ij];
|
|
146
|
+
const qij = q[ij];
|
|
147
|
+
cost += -pij * log(qij);
|
|
148
|
+
const s = 4 * (gscale * pij - qij) * qu[ij];
|
|
149
|
+
if (i !== j) {
|
|
150
|
+
rowJ = y[j];
|
|
151
|
+
for (d = 0; d < dim; d++) {
|
|
152
|
+
g[d] += s * (rowI[d] - rowJ[d]);
|
|
153
|
+
}
|
|
147
154
|
}
|
|
148
155
|
}
|
|
149
156
|
}
|
|
@@ -166,7 +173,7 @@ const pairwiseDistances = (points, distFn) => {
|
|
|
166
173
|
return dist;
|
|
167
174
|
};
|
|
168
175
|
const initProbabilities = (distances, n, perplexity, eps, iter) => {
|
|
169
|
-
const htarget =
|
|
176
|
+
const htarget = log(perplexity);
|
|
170
177
|
const p = new Float64Array(n * n);
|
|
171
178
|
for (let i = 0; i < n; i++) {
|
|
172
179
|
distProbRow(
|
|
@@ -184,7 +191,7 @@ const initProbabilities = (distances, n, perplexity, eps, iter) => {
|
|
|
184
191
|
for (let i = 0; i < n; i++) {
|
|
185
192
|
const ii = i * n;
|
|
186
193
|
for (let j = 0; j < n; j++) {
|
|
187
|
-
res[ii + j] =
|
|
194
|
+
res[ii + j] = max((p[ii + j] + p[j * n + i]) * invN2, EPS);
|
|
188
195
|
}
|
|
189
196
|
}
|
|
190
197
|
return res;
|
|
@@ -210,7 +217,7 @@ const rowEntropy = (distances, row, n, i, beta) => {
|
|
|
210
217
|
let psum = 0;
|
|
211
218
|
for (let j = 0; j < n; j++) {
|
|
212
219
|
if (i !== j) {
|
|
213
|
-
psum += row[j] =
|
|
220
|
+
psum += row[j] = exp(-distances[ii + j] * beta);
|
|
214
221
|
} else {
|
|
215
222
|
row[j] = 0;
|
|
216
223
|
}
|
|
@@ -223,7 +230,7 @@ const normalizeRow = (row, n, psum) => {
|
|
|
223
230
|
psum = 1 / psum;
|
|
224
231
|
for (let i = 0; i < n; i++) {
|
|
225
232
|
const p = row[i] *= psum;
|
|
226
|
-
if (p > 1e-7) h -= p *
|
|
233
|
+
if (p > 1e-7) h -= p * log(p);
|
|
227
234
|
}
|
|
228
235
|
} else {
|
|
229
236
|
row.fill(0);
|