Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
47a425b
Bugfix UMAP and small optimizations
johkehrer Apr 25, 2022
a6fb974
Rewrite Matrix.dot to avoid copying column to new array; bugfix in se…
johkehrer Apr 25, 2022
ec354b0
UMAP: Skip updating current / other, when grad_coeff is 0 (has no eff…
johkehrer May 13, 2022
b65ea88
Bugfix in TSNE
johkehrer May 17, 2022
dce969c
Matrix class can now also deal with Float32Arrays
johkehrer May 19, 2022
8a11f1d
Small optimizations in UMAP
johkehrer May 23, 2022
7e274d6
Speed up matrix operations
johkehrer May 24, 2022
59bcf7f
Bugfix when directly accessing matrix values
johkehrer May 24, 2022
2ffebc4
Add convenience function that performs Matrix.transpose().dot()
johkehrer May 25, 2022
3922f7a
Add convenience function that performs Matrix.dot(B.transposed())
johkehrer May 25, 2022
246df15
Bugfix in TriMap: parameters passed to PCA, argsort
johkehrer May 27, 2022
a6c6202
Add test cases for matrix methods
johkehrer May 27, 2022
02600cc
Rewrite matrix inversion since it failed in some cases
johkehrer May 30, 2022
052e83d
Small changes to matrix inversion
johkehrer May 31, 2022
9a7df9b
Use convenience functions to change matrix elements (add, sub)
johkehrer May 31, 2022
9674722
Add tests for QR method
johkehrer Jun 2, 2022
e4e79a4
Small changes to code
johkehrer Jun 7, 2022
b322a3d
Add test cases for QR decomposition and eigenvalues
johkehrer Jun 8, 2022
2e2ed6a
Test cases for eigenvalue decomposition
johkehrer Jun 23, 2022
7397cea
Initialize TSNE with random Gaussian distribution
johkehrer Jul 19, 2022
677d0c2
Optimization to TSNE
johkehrer Jul 19, 2022
225ffad
TSNE now uses euclidean_squared as default metric
johkehrer Jul 20, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion dimred/ISOMAP.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,11 @@ export class ISOMAP extends DR {

for (let i = 0; i < rows; ++i) {
for (let j = 0; j < rows; ++j) {
let min_val = G.entry(i, j);
for (let k = 0; k < rows; ++k) {
G.set_entry(i, j, Math.min(G.entry(i, j), G.entry(i, k) + G.entry(k, j)));
min_val = Math.min(min_val, G.entry(i, k) + G.entry(k, j));
}
G.set_entry(i, j, min_val);
}
}

Expand Down
4 changes: 2 additions & 2 deletions dimred/LDA.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class LDA extends DR {
const v = V_mean.row(unique_labels[label].id);
const m = new Matrix(cols, 1, (j) => v[j] - X_mean);
const N = unique_labels[label].count;
S_b = S_b.add(m.dot(m.transpose()).mult(N));
S_b = S_b.add(m.dotTrans(m).mult(N));
}

// scatter_within
Expand All @@ -81,7 +81,7 @@ export class LDA extends DR {
const R = unique_labels[label].rows;
for (let i = 0, n = unique_labels[label].count; i < n; ++i) {
const row_v = new Matrix(cols, 1, (j, _) => R[i][j] - m.entry(j, 0));
S_w = S_w.add(row_v.dot(row_v.transpose()));
S_w = S_w.add(row_v.dotTrans(row_v));
}
}

Expand Down
6 changes: 3 additions & 3 deletions dimred/LLE.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ export class LLE extends DR {
for (let row = 0; row < rows; ++row) {
const nN_row = nN[row];
const Z = new Matrix(neighbors, cols, (i, j) => X.entry(nN_row[i].j, j) - X.entry(row, j));
const C = Z.dot(Z.T);
const C = Z.dotTrans(Z);
if (neighbors > cols) {
const C_trace = neumair_sum(C.diag) / 1000;
for (let j = 0; j < neighbors; ++j) {
C.set_entry(j, j, C.entry(j, j) + C_trace);
C.add_entry(j, j, C_trace);
}
}
// reconstruct;
Expand All @@ -66,7 +66,7 @@ export class LLE extends DR {
// comp embedding
const I = new Matrix(rows, rows, "identity");
const IW = I.sub(W);
const M = IW.T.dot(IW);
const M = IW.transDot(IW);
const { eigenvectors: V } = simultaneous_poweriteration(M.T.inverse(), d + 1, eig_args);
this.Y = Matrix.from(V.slice(1, 1 + d)).T;

Expand Down
5 changes: 2 additions & 3 deletions dimred/LSP.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,9 @@ export class LSP extends DR {
transform() {
this.check_init();
const A = this._A;
const AT = A.T;
const b = this._b;
const ATA = AT.dot(A);
const ATb = AT.dot(b);
const ATA = A.transDot(A);
const ATb = A.transDot(b);
this.Y = Matrix.solve_CG(ATA, ATb, this._randomizer);
return this.projection;
}
Expand Down
7 changes: 3 additions & 4 deletions dimred/LTSA.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,16 @@ export class LTSA extends DR {
// center X_i
X_i = X_i.dot(O);
// correlation matrix
const C = X_i.dot(X_i.transpose());
const C = X_i.dotTrans(X_i);
const { eigenvectors: g } = simultaneous_poweriteration(C, d, eig_args);
//g.push(linspace(0, k).map(_ => 1 / Math.sqrt(k + 1)));
const G_i_t = Matrix.from(g);
// 2. Constructing alignment matrix
const W_i = G_i_t.transpose()
.dot(G_i_t)
const W_i = G_i_t.transDot(G_i_t)
.add(1 / Math.sqrt(neighbors + 1));
for (let i = 0; i < neighbors + 1; ++i) {
for (let j = 0; j < neighbors + 1; ++j) {
B.set_entry(I_i[i], I_i[j], B.entry(I_i[i], I_i[j]) - (i === j ? 1 : 0) + W_i.entry(i, j));
B.add_entry(I_i[i], I_i[j], W_i.entry(i, j) - (i === j ? 1 : 0));
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions dimred/MLLE.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class MLLE{
let X_i = Matrix.from(I_i.map(n => X.row(n)));
X_i = X_i.sub(x_i);
//X_i = X_i.dot(new Matrix(X_i._cols, X_i._cols, "center"))
let C_i = X_i.dot(X_i.transpose()) // k by k
let C_i = X_i.dotTrans(X_i); // k by k

let gamma = neumair_sum(C_i.diag) / 1000;
for (let j = 0; j < k; ++j) {
Expand Down Expand Up @@ -91,9 +91,9 @@ export class MLLE{
H_i = H_i.sub(h.mult(2).outer(h));
let W_i = V_i.sub(V_i.dot(h).dot(h.T).mult(2)).add(w_i.mult(1 - alpha_i))
*/
let W_i = V_i.sub(V_i.dot(h).dot(h.T).mult(2)).add(w_i.mult(1 - alpha_i).dot(ones.T))
W_i = W_i.dot(W_i.T)
let W_i = V_i.sub(V_i.dot(h).dotTrans(h).mult(2)).add(w_i.mult(1 - alpha_i).dotTrans(ones))

W_i = W_i.dotTrans(W_i);
for (let i = 0; i < k + 1; ++i) {
for (let j = 0; j < s_i; ++j) {
Phi.set_entry(I_i[i], I_i[j], Phi.entry(I_i[i], I_i[j]) - (i === j ? 1 : 0 ) + W_i.entry(i, j));
Expand Down
4 changes: 2 additions & 2 deletions dimred/OAP.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ export class OAP {
return i < j ? -d_X.entry(i, j) / d_Y.entry(i, j) : ratio.entry(j, i);
}]
for (let i = 0; i < N; ++i) {
ratio.set_entry(i, i, ratio.entry(i, i) - neumair_sum(ratio.row(i)));
ratio.sub_entry(i, i, neumair_sum(ratio.row(i)));
}
const mds_Y = ratio.dot(Y).divide(N);

Expand All @@ -232,7 +232,7 @@ export class OAP {
const dm = M(mds_Y.row(i));
const h_i = h[i];
for (let d = 0; d < dim; ++d) {
Y.set_entry(i, d, Y.entry(i, d) + step_size * (diff_Y.entry(i, d) + w * 2 * (m - h_i) * dm));
Y.add_entry(i, d, step_size * (diff_Y.entry(i, d) + w * 2 * (m - h_i) * dm));
}
}

Expand Down
5 changes: 2 additions & 3 deletions dimred/PCA.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,8 @@ export class PCA extends DR {
}
const { d, eig_args } = this._parameters;
const X = this.X;
const means = Matrix.from(X.meanCols);
const X_cent = X.sub(means);
const C = X_cent.transpose().dot(X_cent);
const X_cent = X.sub(X.meanCols);
const C = X_cent.transDot(X_cent);
const { eigenvectors: V } = simultaneous_poweriteration(C, d, eig_args);
this.V = Matrix.from(V).transpose();
return this.V;
Expand Down
66 changes: 31 additions & 35 deletions dimred/TSNE.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Matrix } from "../matrix/index.js";
import { euclidean } from "../metrics/index.js";
import { euclidean_squared } from "../metrics/index.js";
import { DR } from "./DR.js";

/**
Expand All @@ -18,15 +18,15 @@ export class TSNE extends DR {
* @param {Number} [parameters.perplexity = 50] - perplexity.
* @param {Number} [parameters.epsilon = 10] - learning parameter.
* @param {Number} [parameters.d = 2] - the dimensionality of the projection.
* @param {Function|"precomputed"} [parameters.metric = euclidean] - the metric which defines the distance between two points.
* @param {Function|"precomputed"} [parameters.metric = euclidean_squared] - the metric which defines the distance between two points.
* @param {Number} [parameters.seed = 1212] - the seed for the random number generator.
* @returns {TSNE}
*/
constructor(X, parameters) {
super(X, { perplexity: 50, epsilon: 10, d: 2, metric: euclidean, seed: 1212 }, parameters);
super(X, { perplexity: 50, epsilon: 10, d: 2, metric: euclidean_squared, seed: 1212 }, parameters);
[this._N, this._D] = this.X.shape;
this._iter = 0;
this.Y = new Matrix(this._N, this.parameter("d"), () => this._randomizer.random);
this.Y = new Matrix(this._N, this.parameter("d"), () => this._randomizer.gauss_random() * 1e-4);
return this;
}

Expand Down Expand Up @@ -56,66 +56,62 @@ export class TSNE extends DR {
}
}

const P = new Matrix(N, N, "zeros");
const P = new Matrix(N, N, 0);

this._ystep = new Matrix(N, D, "zeros");
this._ystep = new Matrix(N, D, 0);
this._gains = new Matrix(N, D, 1);

// search for fitting sigma
let prow = new Float64Array(N)
const tol = 1e-4;
const maxtries = 50;
for (let i = 0; i < N; ++i) {
const dist_i = Delta.row(i);
const prow = P.row(i);
let betamin = -Infinity;
let betamax = Infinity;
let beta = 1;
let cnt = maxtries;
let done = false;
let psum;

let num = 0;
while (!done) {
let psum = 0;
while (!done && cnt--) {
// compute entropy and kernel row with beta precision
psum = 0;
let dp_sum = 0;
for (let j = 0; j < N; ++j) {
let pj = Math.exp(-Delta.entry(i, j) * beta);
if (i === j) pj = 0;
const dist = dist_i[j];
const pj = (i !== j) ? Math.exp(-dist * beta) : 0;
dp_sum += dist * pj;
prow[j] = pj;
psum += pj;
}
let Hhere = 0;
for (let j = 0; j < N; ++j) {
let pj = psum === 0 ? 0 : prow[j] / psum;
prow[j] = pj;
if (pj > 1e-7) {
Hhere -= pj * Math.log(pj);
}
}
if (Hhere > Htarget) {
// compute entropy
const H = psum > 0 ? Math.log(psum) + beta * dp_sum / psum : 0;
if (H > Htarget) {
betamin = beta;
beta = betamax === Infinity ? beta * 2 : (beta + betamax) / 2;
} else {
betamax = beta;
beta = betamin === -Infinity ? beta / 2 : (beta + betamin) / 2;
}
++num;
if (Math.abs(Hhere - Htarget) < tol) done = true;
if (num >= maxtries) done = true;
done = Math.abs(H - Htarget) < tol;
}

// normalize p
for (let j = 0; j < N; ++j) {
P.set_entry(i, j, prow[j]);
prow[j] /= psum;
}
}

//compute probabilities
const Pout = new Matrix(N, N, "zeros");
// compute probabilities
const N2 = N * 2;
for (let i = 0; i < N; ++i) {
for (let j = i; j < N; ++j) {
const p = Math.max((P.entry(i, j) + P.entry(j, i)) / N2, 1e-100);
Pout.set_entry(i, j, p);
Pout.set_entry(j, i, p);
P.set_entry(i, j, p);
P.set_entry(j, i, p);
}
}
this._P = Pout;
this._P = P;
return this;
}

Expand Down Expand Up @@ -195,7 +191,7 @@ export class TSNE extends DR {
for (let j = 0; j < N; ++j) {
const premult = 4 * (pmul * P.entry(i, j) - Q.entry(i, j)) * Qu.entry(i, j);
for (let d = 0; d < dim; ++d) {
grad.set_entry(i, d, grad.entry(i, d) + premult * (Y.entry(i, d) - Y.entry(j, d)));
grad.add_entry(i, d, premult * (Y.entry(i, d) - Y.entry(j, d)));
}
}
}
Expand All @@ -216,14 +212,14 @@ export class TSNE extends DR {
const newsid = momval * sid - epsilon * newgain * gid;
ystep.set_entry(i, d, newsid);

Y.set_entry(i, d, Y.entry(i, d) + newsid);
Y.add_entry(i, d, newsid);
ymean[d] += Y.entry(i, d);
}
}

for (let i = 0; i < N; ++i) {
for (let d = 0; d < 2; ++d) {
Y.set_entry(i, d, Y.entry(i, d) - ymean[d] / N);
for (let d = 0; d < dim; ++d) {
Y.sub_entry(i, d, ymean[d] / N);
}
}

Expand Down
18 changes: 7 additions & 11 deletions dimred/TriMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ export class TriMap extends DR {
init(pca = null, knn = null) {
const X = this.X;
const N = X.shape[0];
const { d, metric, c } = this._parameters;
const { c, d, metric, seed } = this._parameters;
this.n_inliers = 2 * c;
this.n_outliers = 1 * c;
this.n_random = 1 * c;
this.Y = pca || new PCA(X, d).transform();
this.Y = pca || new PCA(X, { d, seed }).transform();
this.knn = knn || new BallTree(X.to2dArray, metric);
const { triplets, weights } = this._generate_triplets(this.n_inliers, this.n_outliers, this.n_random);
this.triplets = triplets;
Expand Down Expand Up @@ -156,7 +156,7 @@ export class TriMap extends DR {
const triplets = new Matrix(N * n_inliers * n_outliers, 3);
for (let i = 0; i < N; ++i) {
let n_i = i * n_inliers * n_outliers;
const sort_indices = this.__argsort(P.row(i).map((d) => -d));
const sort_indices = this.__argsort(P.row(i));
for (let j = 0; j < n_inliers; ++j) {
let n_j = j * n_outliers;
const sim = nbrs.entry(i, sort_indices[j]);
Expand All @@ -179,11 +179,7 @@ export class TriMap extends DR {
* @param {Array} A
*/
__argsort(A) {
return A.map((d, i) => {
return { d: d, i: i };
})
.sort((a, b) => a.d - b.d)
.map((d) => d.i);
return linspace(0, A.length - 1).sort((i, j) => A[j] - A[i]);
}

/**
Expand Down Expand Up @@ -314,9 +310,9 @@ export class TriMap extends DR {
for (let d = 0; d < dim; ++d) {
const gs = y_ij[d] * d_ik * w;
const go = y_ik[d] * d_ij * w;
grad.set_entry(i, d, grad.entry(i, d) + gs - go);
grad.set_entry(j, d, grad.entry(j, d) - gs);
grad.set_entry(k, d, grad.entry(k, d) + go);
grad.add_entry(i, d, gs - go);
grad.sub_entry(j, d, gs);
grad.add_entry(k, d, go);
}
}
return { grad, loss, n_viol };
Expand Down
Loading