import { euclidean, chebyshev } from "../metrics/index.js"; import { MDS } from "../dimred/MDS.js"; import { Randomizer } from "../util/index.js"; import { BallTree } from "../knn/BallTree.js"; import { Matrix } from "../matrix/index.js"; import { neumair_sum } from "../numerical/index.js"; /** * */ export class OAP { constructor(X, depth_field_lag, step_size, depth_weight, d = 2, metric = euclidean, seed = 1212) { this._X = X; this._d = d; [this._N, this._D] = X.shape; this._depth_field_lag = depth_field_lag; this._step_size = step_size; this._depth_weight = depth_weight; this._J = 3; this._max_iter = 1; this._metric = metric; this._seed = seed; this._randomizer = new Randomizer(seed); } _data_depth(technique = "chebyshev") { const X = this._X; const N = this._N; const h = new Float32Array(N); let deepest_point = 0; if (technique === "mdb") { h.fill(1); /* // Modified Band Depth // https://www.tandfonline.com/doi/pdf/10.1198/jasa.2009.0108?casa_token=E1Uzntgs-5AAAAAA:Eo8mUpJDhpLQ5RHBkCB3Mdz0tbGM3Q0v78bwyCIAv7-peLGwfG3TcXLqShIaYuJLEqKc7GvaKlgvUg const randomizer = this._randomizer; const h = new Float32Array(this._N); const J = this._J; const N = this._N; const D = this._D; const X = this._X; const one_div_by_n_choose_j = 1; for (let row = 0; row < N; ++row) { const x = X.row(row); const B_min = new Float32Array(D).fill(Infinity); const B_max = new Float32Array(D).fill(-Infinity); let r = Math.floor(randomizer.random * N); for (let i = 0; i < J; ++i) { const x_j = X.row(r); for (let d = 0; d < D; ++d) { const x_jd = x_j[d] B_min[d] = Math.min(B_min[d], x_jd); B_max[d] = Math.max(B_max[d], x_jd); } r += Math.floor(randomizer.random * (N - 1)); r = r % N; } for (let d = 0; d < D; ++d) { const x_d = x[d]; if (x_d >= B_min[d] && x_d <= B_max[d]) { ++h[row] } } } this._h = h;*/ } else if (technique === "chebyshev") { // L∞ Depth // https://arxiv.org/pdf/1506.01332.pdf for (let i = 0; i < N; ++i) { let x = X.row(i); let sum = 0; for (let j = 0; j < N; ++j) { if (i !== j) { sum += chebyshev(x, X.row(j)); } } h[i] = 1 / (1 + sum / N); if (h[deepest_point] < h[i]) { deepest_point = i; } } } this._h = h; this._deepest_point = deepest_point; } init() { this._iter = 0; // init with MDS const init_MDS = new MDS(this._X, this._d, this._metric); //console.log(init_MDS) this._Y = init_MDS.transform(); // try häääh? this._X_distances = init_MDS._d_X; /*let max = -Infinity init_MDS._d_X._data.forEach(dx => max = Math.max(dx, max)); this._X_distances = init_MDS._d_X.divide(max);*/ // end try hääääh? // compute order statistics this._data_depth(); this._M = this._monotonic_field(this._Y); // return this; } set depth_field_lag(value) { this._depth_field_lag = value; } get depth_field_lag() { return this._depth_field_lag; } set step_size(value) { this._step_size = value; } get step_size() { return this._step_size; } set depth_weight(value) { this._depth_weight = value; } get depth_weight() { return this._depth_weight; } transform(iterations = this._max_iter) { for (let i = 0; i < iterations; ++i) { this.next(); } return this._Y; } *transform_iter() { while (true) { this.next(); yield this._Y; } } _monotonic_field(Y) { const h = this._h; const Y_ = this._Y_; Y, h, Y_; const nn = new BallTree(); nn.add(Y.to2dArray); const N = 5; let M = (x) => { let neighbors = nn.search(x, N).toArray(); let d_sum = 0; //neighbors.reduce((a, b) => a + b.value, 0); let m = 0; for (let i = 0; i < N; ++i) { d_sum += neighbors[i].value; m += h[neighbors[i].element.index] * neighbors[i].value; } //console.log(m, d_sum) m /= d_sum; return m; }; return M; } next() { const iter = ++this._iter; const l = this._depth_field_lag; const step_size = this._step_size; const w = this._depth_weight; const N = this._N; const dim = this._d; const d_X = this._X_distances; const h = this._h; let Y = this._Y; if (iter % l === 1) { // compute monotonic field this._Y_ = this._Y.clone(); this._M = this._monotonic_field(Y); } const M = this._M; // perform gradient step // MDS stress step /*for (let i = 0; i < N; ++i) { const d_x = d_X.row(i); const y_i = Y.row(i) const delta_mds_stress = new Float32Array(dim); for (let j = 0; j < N; ++j) { if (i !== j) { const y_j = Y.row(j) const d_y = metric(y_i, y_j); const d_x_j = d_x[j] === 0 ? 1e-2 : d_x[j] const mult = 1 - (d_x_j / d_y) for (let d = 0; d < dim; ++d) { delta_mds_stress[d] += (mult * (y_i[d] - y_j[d])); } } } for (let d = 0; d < dim; ++d) { Y.set_entry(i, d, Y.entry(i, d) - step_size * delta_mds_stress[d] / N) } }*/ // MDS stress step const d_Y = new Matrix(); d_Y.shape = [ N, N, (i, j) => { return i < j ? euclidean(Y.row(i), Y.row(j)) : d_Y.entry(j, i); }, ]; const ratio = new Matrix(); //d_X.divide(d_Y).mult(-1); ratio.shape = [ N, N, (i, j) => { if (i === j) return 1e-8; 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.sub_entry(i, i, neumair_sum(ratio.row(i))); } const mds_Y = ratio.dot(Y).divide(N); // Data depth step const diff_Y = new Matrix(N, dim, (i, j) => mds_Y.entry(i, j) - Y.entry(i, j)); for (let i = 0; i < N; ++i) { const m = M(Y.row(i)); const dm = M(mds_Y.row(i)); const h_i = h[i]; for (let d = 0; d < dim; ++d) { Y.add_entry(i, d, step_size * (diff_Y.entry(i, d) + w * 2 * (m - h_i) * dm)); } } this._Y = Y; return this._Y; } get projection() { return this._Y; } }