Skip to content

Commit 7deaefd

Browse files
authored
Add Tighter perceptron (#436)
1 parent 5c4664a commit 7deaefd

File tree

6 files changed

+234
-1
lines changed

6 files changed

+234
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ for (let i = 0; i < n; i++) {
122122
| task | model |
123123
| ---- | ----- |
124124
| clustering | (Soft / Kernel / Genetic / Weighted) k-means, k-means++, k-medois, k-medians, x-means, G-means, LBG, ISODATA, Fuzzy c-means, Possibilistic c-means, Agglomerative (complete linkage, single linkage, group average, Ward's, centroid, weighted average, median), DIANA, Monothetic, Mutual kNN, Mean shift, DBSCAN, OPTICS, HDBSCAN, DENCLUE, CLUES, PAM, CLARA, CLARANS, BIRCH, CURE, ROCK, C2P, PLSA, Latent dirichlet allocation, GMM, VBGMM, Affinity propagation, Spectral clustering, Mountain, SOM, GTM, (Growing) Neural gas, Growing cell structures, LVQ, ART, SVC, CAST, CHAMELEON, NMF, Autoencoder |
125-
| classification | (Fisher's) Linear discriminant, Quadratic discriminant, Mixture discriminant, Least squares, Ridge, (Complement / Negation / Universal-set / Selective) Naive Bayes (gaussian), AODE, (Fuzzy / Weighted) k-nearest neighbor, Radius neighbor, Nearest centroid, ENN, NNBCA, ADAMENN, DANN, IKNN, Decision tree, Random forest, Extra trees, GBDT, XGBoost, ALMA, (Aggressive) ROMMA, Online gradient descent, Passive aggressive, RLS, (Selective-sampling) Second order perceptron, AROW, NAROW, Confidence weighted, CELLIP, IELLIP, Normal herd, Stoptron, (Kernelized) Pegasos, MIRA, Forgetron, Projectron, Projectron++, Banditron, Ballseptron, BSGD, (Multinomial) Logistic regression, (Multinomial) Probit, SVM, Gaussian process, HMM, CRF, Bayesian Network, LVQ, (Average / Multiclass / Voted / Kernelized / Selective-sampling / Margin / Shifting / Budget) Perceptron, PAUM, RBP, ADALINE, MLP, LMNN |
125+
| classification | (Fisher's) Linear discriminant, Quadratic discriminant, Mixture discriminant, Least squares, Ridge, (Complement / Negation / Universal-set / Selective) Naive Bayes (gaussian), AODE, (Fuzzy / Weighted) k-nearest neighbor, Radius neighbor, Nearest centroid, ENN, NNBCA, ADAMENN, DANN, IKNN, Decision tree, Random forest, Extra trees, GBDT, XGBoost, ALMA, (Aggressive) ROMMA, Online gradient descent, Passive aggressive, RLS, (Selective-sampling) Second order perceptron, AROW, NAROW, Confidence weighted, CELLIP, IELLIP, Normal herd, Stoptron, (Kernelized) Pegasos, MIRA, Forgetron, Projectron, Projectron++, Banditron, Ballseptron, BSGD, (Multinomial) Logistic regression, (Multinomial) Probit, SVM, Gaussian process, HMM, CRF, Bayesian Network, LVQ, (Average / Multiclass / Voted / Kernelized / Selective-sampling / Margin / Shifting / Budget / Tighter) Perceptron, PAUM, RBP, ADALINE, MLP, LMNN |
126126
| semi-supervised classification | k-nearest neighbor, Radius neighbor, Label propagation, Label spreading, k-means, GMM, S3VM, Ladder network |
127127
| regression | Least squares, Ridge, Lasso, Elastic net, RLS, Bayesian linear, Poisson, Least absolute deviations, Huber, Tukey, Least trimmed squares, Least median squares, Lp norm linear, SMA, Deming, Segmented, LOWESS, spline, Gaussian process, Principal components, Partial least squares, Projection pursuit, Quantile regression, k-nearest neighbor, Radius neighbor, IDW, Nadaraya Watson, Priestley Chao, Gasser Muller, RBF Network, RVM, Decision tree, Random forest, Extra trees, GBDT, XGBoost, SVR, MLP, GMR, Isotonic, Ramer Douglas Peucker, Theil-Sen, Passing-Bablok, Repeated median |
128128
| interpolation | Nearest neighbor, IDW, (Spherical) Linear, Brahmagupta, Logarithmic, Cosine, (Inverse) Smoothstep, Cubic, (Centripetal) Catmull-Rom, Hermit, Polynomial, Lagrange, Trigonometric, Spline, RBF Network, Akima, Natural neighbor, Delaunay |

js/model_selector.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ const AIMethods = [
183183
{ value: 'rbp', title: 'RBP' },
184184
{ value: 'banditron', title: 'Banditron' },
185185
{ value: 'ballseptron', title: 'Ballseptron' },
186+
{ value: 'tighter_perceptron', title: 'Tighter Perceptron' },
186187
{ value: 'bsgd', title: 'BSGD' },
187188
],
188189
Netrowk: [

js/view/tighter_perceptron.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import TighterPerceptron from '../../lib/model/tighter_perceptron.js'
2+
import EnsembleBinaryModel from '../../lib/model/ensemble_binary.js'
3+
import Controller from '../controller.js'
4+
5+
export default function (platform) {
6+
platform.setting.ml.usage = 'Click and add data point. Then, click "Step".'
7+
const controller = new Controller(platform)
8+
let model = null
9+
const calc = cb => {
10+
if (!model) {
11+
model = new EnsembleBinaryModel(function () {
12+
return new TighterPerceptron(beta.value, p.value, update.value)
13+
}, method.value)
14+
model.init(
15+
platform.trainInput,
16+
platform.trainOutput.map(v => v[0])
17+
)
18+
}
19+
model.fit()
20+
console.log(model)
21+
22+
const categories = model.predict(platform.testInput(3))
23+
platform.testResult(categories)
24+
cb && cb()
25+
}
26+
27+
const method = controller.select(['oneone', 'onerest'])
28+
const beta = controller.input.number({ label: ' beta ', min: 0, max: 100, step: 0.1, value: 0.1 })
29+
const p = controller.input.number({ label: ' p ', min: 0, max: 1000, value: 10 })
30+
const update = controller.select(['perceptron', 'mira', 'nobias'])
31+
controller
32+
.stepLoopButtons()
33+
.init(() => {
34+
model = null
35+
platform.init()
36+
})
37+
.step(calc)
38+
.epoch()
39+
}

lib/model/tighter_perceptron.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* Tighter Budget Perceptron
3+
*/
4+
export default class TighterPerceptron {
5+
// Online Learning: A Comprehensive Survey
6+
// https://arxiv.org/abs/1802.02871
7+
// Online (and Offline) on an Even Tighter Budget
8+
// http://proceedings.mlr.press/r5/weston05a/weston05a.pdf
9+
/**
10+
* @param {number} [beta=0] Margine
11+
* @param {number} [p=0] Cachs size
12+
* @param {'perceptron' | 'mira' | 'nobias'} [update=perceptron] Update rule
13+
*/
14+
constructor(beta = 0, p = 0, update = 'perceptron') {
15+
this._beta = beta
16+
this._p = p
17+
this._C = 1
18+
19+
this._update = update
20+
this._loss = (t, y) => {
21+
return t === y ? 0 : 1
22+
}
23+
}
24+
25+
/**
26+
* Initialize this model.
27+
*
28+
* @param {Array<Array<number>>} train_x Training data
29+
* @param {Array<1 | -1>} train_y Target values
30+
*/
31+
init(train_x, train_y) {
32+
this._x = train_x
33+
this._y = train_y
34+
35+
if (this._update === 'mira') {
36+
this._w = Array(this._x[0].length).fill(1)
37+
} else {
38+
this._w = Array(this._x[0].length).fill(0)
39+
}
40+
this._c = 0
41+
this._a = []
42+
}
43+
44+
/**
45+
* Fit model parameters.
46+
*/
47+
fit() {
48+
for (let i = 0; i < this._x.length; i++) {
49+
const pt = this._x[i].reduce((s, v, j) => s + v * this._w[j], this._c)
50+
if (this._y[i] * pt > this._beta) {
51+
continue
52+
}
53+
if (this._p > 0 && this._a.length >= this._p) {
54+
let min_l = Infinity
55+
let min_i = -1
56+
for (let s = 0; s < this._a.length; s++) {
57+
const a = this._a[s]
58+
const ws = this._w.map((w, j) => w - a.a * this._y[a.i] * this._x[a.i][j])
59+
const cs = this._c - a.a * this._y[a.i]
60+
let v = 0
61+
for (let k = 0; k < this._a.length; k++) {
62+
let m = cs
63+
for (let j = 0; j < this._w.length; j++) {
64+
m += ws[j] * this._x[this._a[k].i][j]
65+
}
66+
v += this._loss(this._y[this._a[k].i], m <= 0 ? -1 : 1)
67+
}
68+
if (v < min_l) {
69+
min_l = v
70+
min_i = s
71+
}
72+
}
73+
const ai = this._a[min_i]
74+
for (let k = 0; k < this._w.length; k++) {
75+
this._w[k] -= ai.a * this._y[ai.i] * this._x[ai.i][k]
76+
}
77+
this._c -= ai.a * this._y[ai.i]
78+
this._a.splice(min_i, 1)
79+
}
80+
let alpha = 1
81+
if (this._update === 'mira') {
82+
const xx = this._x[i].reduce((s, v) => s + v ** 2, 1)
83+
alpha = Math.min(1, Math.max(0, (-this._y[i] * pt) / xx))
84+
} else if (this._update === 'nobias') {
85+
const xx = this._x[i].reduce((s, v) => s + v ** 2, 1)
86+
alpha = Math.min(this._C, Math.max(0, (1 - this._y[i] * pt) / xx))
87+
}
88+
this._a.push({ a: alpha, i })
89+
for (let k = 0; k < this._w.length; k++) {
90+
this._w[k] += alpha * this._y[i] * this._x[i][k]
91+
}
92+
this._c += alpha * this._y[i]
93+
}
94+
}
95+
96+
/**
97+
* Returns predicted values.
98+
*
99+
* @param {Array<Array<number>>} data Sample data
100+
* @returns {(1 | -1)[]} Predicted values
101+
*/
102+
predict(data) {
103+
const p = []
104+
for (let i = 0; i < data.length; i++) {
105+
const m = data[i].reduce((s, v, j) => s + v * this._w[j], this._c)
106+
p.push(m <= 0 ? -1 : 1)
107+
}
108+
return p
109+
}
110+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import puppeteer from 'puppeteer'
2+
3+
import { getPage } from '../helper/browser'
4+
5+
describe('classification', () => {
6+
/** @type {puppeteer.Page} */
7+
let page
8+
beforeEach(async () => {
9+
page = await getPage()
10+
}, 10000)
11+
12+
afterEach(async () => {
13+
await page?.close()
14+
})
15+
16+
test('initialize', async () => {
17+
const taskSelectBox = await page.waitForSelector('#ml_selector dl:first-child dd:nth-child(5) select')
18+
taskSelectBox.select('CF')
19+
const modelSelectBox = await page.waitForSelector('#ml_selector .model_selection #mlDisp')
20+
modelSelectBox.select('tighter_perceptron')
21+
const methodMenu = await page.waitForSelector('#ml_selector #method_menu')
22+
const buttons = await methodMenu.waitForSelector('.buttons')
23+
24+
const methods = await buttons.waitForSelector('select:nth-of-type(1)')
25+
await expect((await methods.getProperty('value')).jsonValue()).resolves.toBe('oneone')
26+
const beta = await buttons.waitForSelector('input:nth-of-type(1)')
27+
await expect((await beta.getProperty('value')).jsonValue()).resolves.toBe('0.1')
28+
const p = await buttons.waitForSelector('input:nth-of-type(2)')
29+
await expect((await p.getProperty('value')).jsonValue()).resolves.toBe('10')
30+
const update = await buttons.waitForSelector('select:nth-of-type(2)')
31+
await expect((await update.getProperty('value')).jsonValue()).resolves.toBe('perceptron')
32+
const epoch = await buttons.waitForSelector('[name=epoch]')
33+
await expect(epoch.evaluate(el => el.textContent)).resolves.toBe('0')
34+
}, 10000)
35+
36+
test('learn', async () => {
37+
const taskSelectBox = await page.waitForSelector('#ml_selector dl:first-child dd:nth-child(5) select')
38+
taskSelectBox.select('CF')
39+
const modelSelectBox = await page.waitForSelector('#ml_selector .model_selection #mlDisp')
40+
modelSelectBox.select('tighter_perceptron')
41+
const methodMenu = await page.waitForSelector('#ml_selector #method_menu')
42+
const buttons = await methodMenu.waitForSelector('.buttons')
43+
44+
const epoch = await buttons.waitForSelector('[name=epoch]')
45+
await expect(epoch.evaluate(el => el.textContent)).resolves.toBe('0')
46+
const methodFooter = await page.waitForSelector('#method_footer')
47+
await expect(methodFooter.evaluate(el => el.textContent)).resolves.toBe('')
48+
49+
const initButton = await buttons.waitForSelector('input[value=Initialize]')
50+
await initButton.evaluate(el => el.click())
51+
const stepButton = await buttons.waitForSelector('input[value=Step]:enabled')
52+
await stepButton.evaluate(el => el.click())
53+
54+
await expect(epoch.evaluate(el => el.textContent)).resolves.toBe('1')
55+
await expect(methodFooter.evaluate(el => el.textContent)).resolves.toMatch(/^Accuracy:[0-9.]+$/)
56+
}, 10000)
57+
})
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { jest } from '@jest/globals'
2+
jest.retryTimes(3)
3+
4+
import Matrix from '../../../lib/util/matrix.js'
5+
import TighterPerceptron from '../../../lib/model/tighter_perceptron.js'
6+
7+
import { accuracy } from '../../../lib/evaluate/classification.js'
8+
9+
describe('classification', () => {
10+
test.each([undefined, 'perceptron', 'mira', 'nobias'])('update %s', update => {
11+
const model = new TighterPerceptron(1, 10, update)
12+
const x = Matrix.concat(Matrix.randn(50, 2, 0, 0.2), Matrix.randn(50, 2, 5, 0.2)).toArray()
13+
x[50] = [0.1, 0.1]
14+
const t = []
15+
for (let i = 0; i < x.length; i++) {
16+
t[i] = Math.floor(i / 50) * 2 - 1
17+
}
18+
model.init(x, t)
19+
for (let i = 0; i < 10; i++) {
20+
model.fit()
21+
}
22+
const y = model.predict(x)
23+
const acc = accuracy(y, t)
24+
expect(acc).toBeGreaterThan(0.8)
25+
})
26+
})

0 commit comments

Comments
 (0)