@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
83 lines (75 loc) • 1.86 kB
JavaScript
import Matrix from '../util/matrix.js'
/**
* Sammon mapping
*/
export default class Sammon {
// https://en.wikipedia.org/wiki/Sammon_mapping
// https://oimokihujin.hatenablog.com/entry/2014/06/01/073231
/**
* @param {number} rd Reduced dimension
*/
constructor(rd) {
this._rd = rd
}
/**
* Initialize model.
* @param {Array<Array<number>>} x Sample data
*/
init(x) {
this._x = x
const n = this._x.length
this._y = Matrix.randn(n, this._rd)
this._alpha = 0.3
this._d = Matrix.zeros(n, n)
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
let d = 0
for (let k = 0; k < x[i].length; k++) {
d += (x[i][k] - x[j][k]) ** 2
}
d = Math.sqrt(d)
this._d.set(i, j, d)
this._d.set(j, i, d)
}
}
}
/**
* Fit model and returns reduced values.
* @returns {Array<Array<number>>} Predicted values
*/
fit() {
const c = this._d.sum()
const n = this._y.rows
const d = this._y.cols
for (let i = 0; i < n; i++) {
const de = Matrix.zeros(1, d)
const dde = Matrix.zeros(1, d)
for (let j = 0; j < n; j++) {
if (i === j) continue
let dp = 0
for (let k = 0; k < d; k++) {
dp += (this._y.at(i, k) - this._y.at(j, k)) ** 2
}
dp = Math.sqrt(dp)
if (dp === 0) continue
const t1 = (this._d.at(i, j) - dp) / (this._d.at(i, j) * dp)
const t2 = Matrix.sub(this._y.row(i), this._y.row(j))
de.sub(Matrix.mult(t2, (2 / c) * t1))
dde.sub(
Matrix.map(t2, v => (2 / c) * t1 * (1 - (v ** 2 / dp) * (1 / (this._d.at(i, j) - dp) + 1 / dp)))
)
}
for (let j = 0; j < d; j++) {
this._y.subAt(i, j, (this._alpha * de.at(0, j)) / Math.abs(dde.at(0, j)))
}
}
return this._y.toArray()
}
/**
* Returns reduced values.
* @returns {Array<Array<number>>} Predicted values
*/
predict() {
return this._y.toArray()
}
}