@thi.ng/tsne 0.1.14 → 0.1.16

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2025-05-28T12:02:40Z
3
+ - **Last updated**: 2025-06-05T12:59:44Z
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.14",
3
+ "version": "0.1.16",
4
4
  "description": "Highly configurable t-SNE implementation for arbitrary dimensions",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -51,7 +51,16 @@
51
51
  "typescript": "^5.8.3"
52
52
  },
53
53
  "keywords": [
54
- "typescript"
54
+ "2d",
55
+ "3d",
56
+ "cluster",
57
+ "compression",
58
+ "dataviz",
59
+ "nd",
60
+ "tsne",
61
+ "typescript",
62
+ "vector",
63
+ "visualization"
55
64
  ],
56
65
  "publishConfig": {
57
66
  "access": "public"
@@ -82,5 +91,5 @@
82
91
  "status": "alpha",
83
92
  "year": 2021
84
93
  },
85
- "gitHead": "61c3833b7ef7d044621454b5ea4af885d39f065e\n"
94
+ "gitHead": "5a1224bbd501c0dad203fa231749f21740350db7\n"
86
95
  }
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: ReadonlyVec[];
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
- const ymean = zeroes(dim);
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 = Math.max(
100
+ const newGain = max(
97
101
  minGain,
98
- Math.sign(rowGrad[d]) === Math.sign(step) ? (
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] = Math.max(qu[i] * qsum, EPS);
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
- rowIdx = i * n;
140
+ const g = gradient[i].fill(0);
138
141
  rowI = y[i];
139
- const g = gradient[i] = zeroes(dim);
142
+ rowIdx = i * n;
140
143
  for (j = 0; j < n; j++) {
141
- rowJ = y[j];
142
144
  const ij = rowIdx + j;
143
- cost += -p[ij] * Math.log(q[ij]);
144
- const s = 4 * (gscale * p[ij] - q[ij]) * qu[ij];
145
- for (d = 0; d < dim; d++) {
146
- g[d] += s * (rowI[d] - rowJ[d]);
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 = Math.log(perplexity);
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] = Math.max((p[ii + j] + p[j * n + i]) * invN2, EPS);
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] = Math.exp(-distances[ii + j] * beta);
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 * Math.log(p);
233
+ if (p > 1e-7) h -= p * log(p);
227
234
  }
228
235
  } else {
229
236
  row.fill(0);