/
incremental_pca.js
94 lines (86 loc) · 2.14 KB
/
incremental_pca.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import Matrix from '../util/matrix.js'
/**
* Incremental principal component analysis
*/
export default class IncrementalPCA {
// https://catian.hatenadiary.org/entry/20081114/1226684726
// https://scikit-learn.org/stable/modules/generated/sklearn.decomposition.IncrementalPCA.html
// https://www.cs.toronto.edu/~dross/ivt/RossLimLinYang_ijcv.pdf
// https://www.jstage.jst.go.jp/article/ieejeiss/129/1/129_1_112/_pdf
/**
* @param {number} [f] Forgetting factor
* @param {number | null} [rd] Reduced dimension
*/
constructor(f = 0.95, rd = null) {
this._f = f
this._rd = rd ?? 0
this._batch_size = 0
this._u = null
this._s = null
this._m = null
this._n = 0
}
/**
* Update parameters.
*
* @param {Matrix} x Training data
*/
update(x) {
if (!this._u) {
this._m = x.mean(1)
x.sub(this._m)
const [u, s] = x.svd()
this._u = u
this._s = s
this._n = x.cols
return
}
const m = x.cols
const ib = x.mean(1)
const bh = Matrix.sub(x, ib)
const con = Matrix.sub(ib, this._m)
con.mult(Math.sqrt((m * this._n) / (m + this._n)))
bh.concat(con, 1)
const ob = this._u.dot(this._u.tDot(bh))
ob.isub(bh)
const [bt] = ob.qr()
const r = Matrix.diag([...this._s.map(v => v * this._f), bt.dot(ob)])
r.set(0, this._s.length, this._u.tDot(bh))
const [ut, st] = r.svd()
this._s = st
this._u = Matrix.concat(this._u, bt, 1).dot(ut)
this._m.mult(this._n * this._f)
this._m.add(Matrix.mult(ib, m))
this._m.div((this._n = this._n * this._f + m))
}
/**
* Fit model.
*
* @param {Array<Array<number>>} x Training data
*/
fit(x) {
x = Matrix.fromArray(x)
if (this._batch_size <= 0) {
this._batch_size = 5 * x.cols
}
for (let i = 0; i < x.rows; i += this._batch_size) {
const r = []
for (let k = 0; k < this._batch_size && i + k < x.rows; k++) {
r[k] = i + k
}
this.update(x.row(r).t)
}
}
/**
* Returns reduced datas.
*
* @param {Array<Array<number>>} x Sample data
* @returns {Array<Array<number>>} Predicted values
*/
predict(x) {
x = Matrix.fromArray(x)
const u = this._u
u.resize(u.rows, this._rd > 0 ? this._rd : x.cols)
return x.dot(u).toArray()
}
}