Skip to content

Commit cb4bb6c

Browse files
committed
Remove SVM worker
1 parent 37995ce commit cb4bb6c

File tree

2 files changed

+272
-302
lines changed

2 files changed

+272
-302
lines changed

model/svm.js

Lines changed: 272 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,304 @@
1-
class SVMWorker extends BaseWorker {
2-
constructor() {
3-
super('model/worker/svm_worker.js');
1+
const Kernel = {
2+
gaussian:
3+
(d = 1) =>
4+
(a, b) => {
5+
let r = a.reduce((acc, v, i) => acc + (v - b[i]) ** 2, 0)
6+
return Math.exp(-r / (2 * d * d))
7+
},
8+
linear: () => (a, b) => a.reduce((acc, v, i) => acc + v * b[i], 0),
9+
}
10+
11+
class SVM {
12+
constructor(kernel) {
13+
this._n = 0
14+
this._a = []
15+
this._x = []
16+
this._t = []
17+
this._b = 0
18+
19+
this._C = 1000
20+
this._eps = 0.001
21+
this._tolerance = 0.001
22+
this._err = []
23+
24+
this._kernel = kernel
425
}
526

6-
initialize(kernel, train_x, train_y, method = "oneone") {
7-
this._postMessage({
8-
"mode": "init",
9-
"method": method,
10-
"kernel": kernel,
11-
"x": train_x,
12-
"y": train_y
13-
});
27+
init(train_x, train_y) {
28+
this._n = train_x.length
29+
this._a = Array(this._n).fill(0)
30+
this._x = train_x.map((x) => x)
31+
this._t = train_y
32+
this._err = Array(this._n).fill(0)
33+
this._alldata = true
1434
}
1535

16-
fit(iteration, cb) {
17-
this._postMessage({
18-
"mode": "fit",
19-
"iteration": iteration
20-
}, cb);
36+
fit() {
37+
let changed = this._fitOnce(this._alldata)
38+
if (this._alldata) {
39+
this._alldata = false
40+
if (changed == 0) {
41+
return
42+
}
43+
} else if (changed == 0) {
44+
this._alldata = true
45+
}
2146
}
2247

23-
predict(x, cb) {
24-
this._postMessage({
25-
"mode": "predict",
26-
"x": x
27-
}, cb);
48+
_fitOnce(all = false) {
49+
let change = 0
50+
51+
const between_eps = (v) => this._eps < v && v < this._C - this._eps
52+
for (let i = 0; i < this._n; i++) {
53+
let ei = 0
54+
if (between_eps(this._a[i])) {
55+
ei = this._err[i]
56+
} else if (all) {
57+
ei = this.predict(this._x[i]) - this._t[i]
58+
} else {
59+
continue
60+
}
61+
const yfi = ei * this._t[i]
62+
63+
if (
64+
(this._a[i] >= this._C - this._eps || yfi >= -this._tolerance) &&
65+
(this._a[i] <= this._eps || yfi <= this._tolerance)
66+
) {
67+
continue
68+
}
69+
70+
let max_e = 0
71+
let max_j = -1
72+
73+
const offset = Math.floor(Math.random() * (this._n + 1))
74+
let in_eps = []
75+
let out_eps = []
76+
for (let j = 0; j < this._n; j++) {
77+
const p = (j + offset) % this._n
78+
if (p === i) {
79+
continue
80+
}
81+
if (between_eps(this._a[p])) {
82+
const ej = this._err[p]
83+
if (Math.abs(ei - ej) > max_e) {
84+
max_e = Math.abs(ei - ej)
85+
if (max_j >= 0) in_eps.push(max_j)
86+
max_j = p
87+
} else {
88+
in_eps.push(p)
89+
}
90+
} else {
91+
out_eps.push(p)
92+
}
93+
}
94+
const checks = max_j >= 0 ? [].concat(max_j, in_eps, out_eps) : [].concat(in_eps, out_eps)
95+
for (let ck = 0; ck < checks.length; ck++) {
96+
const j = checks[ck]
97+
98+
const ai_old = this._a[i]
99+
const aj_old = this._a[j]
100+
let u, v
101+
if (this._t[i] != this._t[j]) {
102+
u = Math.max(0, ai_old - aj_old)
103+
v = Math.min(this._C, this._C + ai_old - aj_old)
104+
} else {
105+
u = Math.max(0, ai_old + aj_old - this._C)
106+
v = Math.min(this._C, ai_old + aj_old)
107+
}
108+
if (u == v) {
109+
continue
110+
}
111+
112+
const kii = this._kernel(this._x[i], this._x[i])
113+
const kjj = this._kernel(this._x[j], this._x[j])
114+
const kij = this._kernel(this._x[i], this._x[j])
115+
const k = kii + kjj - 2 * kij
116+
const ej = between_eps(this._a[j]) ? this._err[j] : this.predict(this._x[j]) - this._t[j]
117+
118+
let bClip = false
119+
let ai_new = 0,
120+
aj_new = 0
121+
if (k <= 0) {
122+
let lh = [u, v].map((t) => {
123+
let ai_n = t
124+
let aj_n = aj_old + this._t[i] * this._t[j] * (ai_old - ai_n)
125+
this._a[i] = ai_n
126+
this._a[j] = aj_n
127+
const v1 =
128+
this.predict(this._x[j]) + this._b - this._t[j] * aj_old * kjj - this._t[i] * ai_old * kij
129+
const v2 =
130+
this.predict(this._x[i]) + this._b - this._t[j] * aj_old * kij - this._t[i] * ai_old * kii
131+
const lobj =
132+
aj_n +
133+
ai_n -
134+
(kjj * aj_n ** 2) / 2 -
135+
(kii * ai_n ** 2) / 2 -
136+
this._t[j] * this._t[i] * kij * aj_n * ai_n -
137+
this._t[j] * aj_n * v1 -
138+
this._t[i] * ai_n * v2
139+
})
140+
this._a[i] = ai_old
141+
this._a[j] = aj_old
142+
143+
ai_new = lh[0] > lh[1] + this._eps ? u : lh[0] < lh[1] - this._eps ? v : ai_old
144+
bClip = true
145+
} else {
146+
ai_new = ai_old + (this._t[i] * (ej - ei)) / k
147+
if (ai_new > v) {
148+
bClip = true
149+
ai_new = v
150+
} else if (ai_new < u) {
151+
bClip = true
152+
ai_new = u
153+
}
154+
}
155+
156+
if (Math.abs(ai_new - ai_old) < this._eps * (ai_new + ai_old + this._eps)) {
157+
continue
158+
}
159+
aj_new = aj_old + this._t[i] * this._t[j] * (ai_old - ai_new)
160+
const b_old = this._b
161+
if (between_eps(this._a[i])) {
162+
this._b += ei + (ai_new - ai_old) * this._t[i] * kii + (aj_new - aj_old) * this._t[j] * kij
163+
} else if (between_eps(this._a[j])) {
164+
this._b += ej + (ai_new - ai_old) * this._t[i] * kij + (aj_new - aj_old) * this._t[j] * kjj
165+
} else {
166+
this._b +=
167+
(ei +
168+
ej +
169+
(ai_new - ai_old) * this._t[i] * (kii + kij) +
170+
(aj_new - aj_old) * this._t[j] * (kij + kjj)) /
171+
2
172+
}
173+
174+
for (let m = 0; m < this._n; m++) {
175+
if (m == i || m == j) {
176+
continue
177+
}
178+
this._err[m] +=
179+
this._t[j] * (aj_new - aj_old) * this._kernel(this._x[j], this._x[m]) +
180+
this._t[i] * (ai_new - ai_old) * this._kernel(this._x[i], this._x[m]) +
181+
b_old -
182+
this._b
183+
}
184+
185+
this._a[i] = ai_new
186+
this._a[j] = aj_new
187+
188+
if (!bClip) {
189+
this._err[i] = 0
190+
} else if (between_eps(ai_new)) {
191+
this._err[i] = this.predict(this._x[i]) - this._t[i]
192+
}
193+
this._err[j] = this.predict(this._x[j]) - this._t[j]
194+
195+
change++
196+
break
197+
}
198+
}
199+
return change
200+
}
201+
202+
predict(data) {
203+
const f = (v) => {
204+
let y = 0
205+
for (let n = 0; n < this._n; n++) {
206+
if (this._a[n]) y += this._a[n] * this._t[n] * this._kernel(v, this._x[n])
207+
}
208+
return y - this._b
209+
}
210+
return !Array.isArray(data[0]) ? f(data) : data.map(f)
28211
}
29212
}
30213

31-
var dispSVM = function(elm, platform) {
32-
const step = 4;
33-
let model = new SVMWorker();
34-
let learn_epoch = 0;
214+
var dispSVM = function (elm, platform) {
215+
const step = 4
216+
let model = null
217+
let learn_epoch = 0
35218

36-
const calcSVM = function(cb) {
219+
const calcSVM = function (cb) {
37220
if (platform.datas.length == 0) {
38-
return;
221+
return
39222
}
40-
const iteration = +elm.select("[name=iteration]").property("value");
223+
const iteration = +elm.select('[name=iteration]').property('value')
41224
platform.fit((tx, ty, fit_cb) => {
42-
model.fit(iteration, e => {
43-
platform.predict((px, pred_cb) => {
44-
model.predict(px, e => {
45-
let data = e.data;
46-
pred_cb(data);
47-
learn_epoch += iteration
48-
cb && cb();
49-
});
50-
}, step)
51-
});
52-
});
53-
};
54-
55-
elm.append("select")
56-
.attr("name", "method")
57-
.selectAll("option")
58-
.data(["oneone", "onerest"])
225+
for (let i = 0; i < iteration; i++) {
226+
model.fit()
227+
}
228+
platform.predict((px, pred_cb) => {
229+
const data = model.predict(px)
230+
pred_cb(data)
231+
learn_epoch += iteration
232+
cb && cb()
233+
}, step)
234+
})
235+
}
236+
237+
elm.append('select')
238+
.attr('name', 'method')
239+
.selectAll('option')
240+
.data(['oneone', 'onerest'])
59241
.enter()
60-
.append("option")
61-
.property("value", d => d)
62-
.text(d => d);
63-
elm.append("select")
64-
.attr("name", "kernel")
65-
.on("change", function() {
66-
const k = d3.select(this).property("value");
67-
if (k == "gaussian") {
68-
elm.select("input[name=gamma]").style("display", "inline");
242+
.append('option')
243+
.property('value', (d) => d)
244+
.text((d) => d)
245+
elm.append('select')
246+
.attr('name', 'kernel')
247+
.on('change', function () {
248+
const k = d3.select(this).property('value')
249+
if (k == 'gaussian') {
250+
elm.select('input[name=gamma]').style('display', 'inline')
69251
} else {
70-
elm.select("input[name=gamma]").style("display", "none");
252+
elm.select('input[name=gamma]').style('display', 'none')
71253
}
72254
})
73-
.selectAll("option")
74-
.data(["gaussian", "linear"])
255+
.selectAll('option')
256+
.data(['gaussian', 'linear'])
75257
.enter()
76-
.append("option")
77-
.property("value", d => d)
78-
.text(d => d);
79-
elm.append("input")
80-
.attr("type", "number")
81-
.attr("name", "gamma")
82-
.attr("value", 1)
83-
.attr("min", 0.01)
84-
.attr("max", 10.0)
85-
.attr("step", 0.01);
258+
.append('option')
259+
.property('value', (d) => d)
260+
.text((d) => d)
261+
elm.append('input')
262+
.attr('type', 'number')
263+
.attr('name', 'gamma')
264+
.attr('value', 1)
265+
.attr('min', 0.01)
266+
.attr('max', 10.0)
267+
.attr('step', 0.01)
86268
const slbConf = platform.setting.ml.controller.stepLoopButtons().init(() => {
87-
let kernel = elm.select("[name=kernel]").property("value");
88-
if (kernel == "gaussian") {
89-
kernel = [kernel, +elm.select("input[name=gamma]").property("value")];
269+
const kernel = elm.select('[name=kernel]').property('value')
270+
const kernel_args = []
271+
if (kernel == 'gaussian') {
272+
kernel_args.push(+elm.select('input[name=gamma]').property('value'))
273+
}
274+
const method = elm.select('[name=method]').property('value')
275+
if (method == 'onerest') {
276+
model = new OneVsRestModel(SVM, null, [Kernel[kernel](...kernel_args)])
277+
} else {
278+
model = new OneVsOneModel(SVM, null, [Kernel[kernel](...kernel_args)])
90279
}
91280
platform.fit((tx, ty) => {
92-
model.initialize(kernel, tx, ty.map(v => v[0]), elm.select("[name=method]").property("value"));
281+
model.init(
282+
tx,
283+
ty.map((v) => v[0])
284+
)
93285
})
94286
learn_epoch = 0
95287
platform.init()
96288
})
97-
elm.append("span")
98-
.text(" Iteration ");
99-
elm.append("select")
100-
.attr("name", "iteration")
101-
.selectAll("option")
289+
elm.append('span').text(' Iteration ')
290+
elm.append('select')
291+
.attr('name', 'iteration')
292+
.selectAll('option')
102293
.data([1, 10, 100, 1000])
103294
.enter()
104-
.append("option")
105-
.property("value", d => d)
106-
.text(d => d);
295+
.append('option')
296+
.property('value', (d) => d)
297+
.text((d) => d)
107298
slbConf.step(calcSVM).epoch(() => learn_epoch)
108-
109-
return () => {
110-
model.terminate();
111-
};
112299
}
113300

114-
export default function(platform) {
301+
export default function (platform) {
115302
platform.setting.ml.usage = 'Click and add data point. Then, click "Calculate".'
116-
platform.setting.terminate = dispSVM(platform.setting.ml.configElement, platform);
303+
dispSVM(platform.setting.ml.configElement, platform)
117304
}

0 commit comments

Comments
 (0)