/
kernelized_pegasos.js
94 lines (89 loc) · 2.51 KB
/
kernelized_pegasos.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
/**
* Kernelized Primal Estimated sub-GrAdientSOlver for SVM
*/
export default class KernelizedPegasos {
// Pegasos: Primal Estimated sub-GrAdient SOlver for SVM
// https://home.ttic.edu/~nati/Publications/PegasosMPB.pdf
// https://sandipanweb.wordpress.com/2018/04/29/implementing-pegasos-primal-estimated-sub-gradient-solver-for-svm-using-it-for-sentiment-classification-and-switching-to-logistic-regression-objective-by-changing-the-loss-function-in-python/
/**
* @param {number} rate Learning rate
* @param {'gaussian' | 'polynomial' | { name: 'gaussian', s?: number } | { name: 'polynomial', d?: number } | function (number[], number[]): number} [kernel] Kernel name
*/
constructor(rate, kernel = 'gaussian') {
this._r = rate
this._itr = 100
if (typeof kernel === 'function') {
this._kernel = kernel
} else {
if (typeof kernel === 'string') {
kernel = { name: kernel }
}
switch (kernel.name) {
case 'gaussian':
this._s = kernel.s ?? 1
this._kernel = (a, b) =>
Math.exp(-(a.reduce((s, v, i) => s + (v - b[i]) ** 2, 0) ** 2) / this._s ** 2)
break
case 'polynomial':
this._d = kernel.d ?? 2
this._kernel = (a, b) => (1 + a.reduce((s, v, i) => s + v * b[i])) ** this._d
break
}
}
}
/**
* Initialize this model.
*
* @param {Array<Array<number>>} train_x Training data
* @param {Array<1 | -1>} train_y Target values
*/
init(train_x, train_y) {
this._x = train_x
this._y = train_y
this._t = 0
this._a = Array(this._x.length).fill(0)
const n = this._x.length
this._k = []
for (let i = 0; i < n; i++) {
this._k[i] = []
this._k[i][i] = this._kernel(this._x[i], this._x[i])
for (let j = 0; j < i; j++) {
this._k[i][j] = this._k[j][i] = this._kernel(this._x[i], this._x[j])
}
}
}
/**
* Fit model parameters.
*/
fit() {
for (let i = 0; i < this._itr; i++) {
const r = Math.floor(Math.random() * this._x.length)
this._t++
const eta = 1 / (this._r * this._t)
let p = 0
for (let j = 0; j < this._x.length; j++) {
p += this._a[j] * this._y[j] * this._k[r][j]
}
if (this._y[r] * eta * p < 1) {
this._a[r]++
}
}
}
/**
* Returns predicted values.
*
* @param {Array<Array<number>>} data Sample data
* @returns {(1 | -1)[]} Predicted values
*/
predict(data) {
const p = []
for (let i = 0; i < data.length; i++) {
let m = 0
for (let j = 0; j < this._x.length; j++) {
m += this._a[j] * this._y[j] * this._kernel(data[i], this._x[j])
}
p.push(m <= 0 ? -1 : 1)
}
return p
}
}