Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ for (let i = 0; i < n; i++) {
| task | model |
| ---- | ----- |
| 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, DBCLASD, CLUES, PAM, CLARA, CLARANS, BIRCH, CURE, ROCK, C2P, PLSA, Latent dirichlet allocation, GMM, VBGMM, Affinity propagation, Spectral clustering, Mountain, (Growing) SOM, GTM, (Growing) Neural gas, Growing cell structures, LVQ, ART, SVC, CAST, CHAMELEON, COLL, CLIQUE, PROCLUS, ORCLUS, FINDIT, NMF, Autoencoder |
| classification | (Fisher's) Linear discriminant, Quadratic discriminant, Mixture discriminant, Least squares, (Multiclass / Kernel) Ridge, (Complement / Negation / Universal-set / Selective) Naive Bayes (gaussian), AODE, (Fuzzy / Weighted) k-nearest neighbor, Radius neighbor, Nearest centroid, ENN, ENaN, NNBCA, ADAMENN, DANN, IKNN, Decision tree, Random forest, Extra trees, GBDT, XGBoost, ALMA, (Aggressive) ROMMA, (Bounded) Online gradient descent, (Budgeted online) 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, (Multiclass) BSGD, ILK, SILK, (Multinomial) Logistic regression, (Multinomial) Probit, SVM, Gaussian process, HMM, CRF, Bayesian Network, LVQ, (Average / Multiclass / Voted / Kernelized / Selective-sampling / Margin / Shifting / Budget / Tighter / Tightest) Perceptron, PAUM, RBP, ADALINE, MADALINE, MLP, LMNN |
| classification | (Fisher's) Linear discriminant, Quadratic discriminant, Mixture discriminant, Least squares, (Multiclass / Kernel) Ridge, (Complement / Negation / Universal-set / Selective) Naive Bayes (gaussian), AODE, (Fuzzy / Weighted) k-nearest neighbor, Radius neighbor, Nearest centroid, ENN, ENaN, NNBCA, ADAMENN, DANN, IKNN, Decision tree, Random forest, Extra trees, GBDT, XGBoost, ALMA, (Aggressive) ROMMA, (Bounded) Online gradient descent, (Budgeted online) 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, (Multiclass) BSGD, ILK, SILK, (Multinomial) Logistic regression, (Multinomial) Probit, Ordinal regression, SVM, Gaussian process, HMM, CRF, Bayesian Network, LVQ, (Average / Multiclass / Voted / Kernelized / Selective-sampling / Margin / Shifting / Budget / Tighter / Tightest) Perceptron, PAUM, RBP, ADALINE, MADALINE, MLP, LMNN |
| semi-supervised classification | k-nearest neighbor, Radius neighbor, Label propagation, Label spreading, k-means, GMM, S3VM, Ladder network |
| 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, LOESS, 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 |
| 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 |
Expand Down
1 change: 1 addition & 0 deletions js/model_selector.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ const AIMethods = [
{ value: 'nearest_centroid', title: 'Nearest Centroid' },
{ value: 'logistic', title: 'Logistic regression' },
{ value: 'probit', title: 'Probit' },
{ value: 'ordinal_regression', title: 'Ordinal regression' },
{ value: 'svm', title: 'Support vector machine' },
{ value: 'gaussian_process', title: 'Gaussian Process' },
{ value: 'hmm', title: 'HMM' },
Expand Down
36 changes: 36 additions & 0 deletions js/view/ordinal_regression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import OrdinalRegression from '../../lib/model/ordinal_regression.js'
import Controller from '../controller.js'

export default function (platform) {
platform.setting.ml.usage =
'Click and add data point. Next, click "Initialize". Finally, click "Fit" button repeatedly.'
platform.setting.ml.reference = {
title: 'Ordinal regression (Wikipedia)',
url: 'https://en.wikipedia.org/wiki/Ordinal_regression',
}
const controller = new Controller(platform)

let model = null
const fitModel = () => {
if (!model) {
return
}

model.fit(
platform.trainInput,
platform.trainOutput.map(v => v[0])
)
const pred = model.predict(platform.testInput(4))
platform.testResult(pred)
}

const rate = controller.input.number({ label: ' Learning rate ', value: 0.001, min: 0, max: 100, step: 0.001 })
controller
.stepLoopButtons()
.init(() => {
model = new OrdinalRegression(rate.value)
platform.init()
})
.step(fitModel)
.epoch()
}
109 changes: 109 additions & 0 deletions lib/model/ordinal_regression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import Matrix from '../util/matrix.js'

/**
* Ordinal regression
*/
export default class OrdinalRegression {
// https://en.wikipedia.org/wiki/Ordinal_regression
/**
* @param {number} [rate=0.001] Learning rate
*/
constructor(rate = 0.001) {
this._w = null
this._s = 1
this._a = rate
}

_gaussian(x) {
if (x === Infinity || x === -Infinity) {
return 0
}
return Math.exp(-(x ** 2) / (2 * this._s ** 2)) / Math.sqrt(2 * Math.PI * this._s ** 2)
}

_cdf(z) {
if (z === Infinity) {
return 1
} else if (z === -Infinity) {
return 0
}
const p = 0.3275911
const a1 = 0.254829592
const a2 = -0.284496736
const a3 = 1.421413741
const a4 = -1.453152027
const a5 = 1.061405429

const sign = z < 0 ? -1 : 1
const x = Math.abs(z) / Math.sqrt(2 * this._s ** 2)
const t = 1 / (1 + p * x)
const erf = 1 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-x * x)
return 0.5 * (1 + sign * erf)
}

/**
* Fit model.
*
* @param {Array<Array<number>>} x Training data
* @param {Array<number>} y Target values
*/
fit(x, y) {
x = Matrix.fromArray(x)
if (!this._response) {
this._response = [...new Set(y)]
this._response.sort((a, b) => a - b)
this._t = Array.from(this._response, (_, i) => i - 1)
this._t[0] = -Infinity
this._t.push(Infinity)
this._w = Matrix.randn(x.cols, 1, 0, 0.1)
}

const pstar = x.dot(this._w).value
const dt = Array(this._t.length).fill(0)
const dw = Matrix.zeros(this._w.rows, this._w.cols)
for (let i = 0; i < y.length; i++) {
const k = this._response.indexOf(y[i])
const pk0 = this._cdf(this._t[k] - pstar[i])
const pk1 = this._cdf(this._t[k + 1] - pstar[i])
if (pk0 === pk1) {
continue
}

dt[k] += this._gaussian(this._t[k] - pstar[i]) / (pk0 - pk1)
dt[k + 1] -= this._gaussian(this._t[k + 1] - pstar[i]) / (pk0 - pk1)

const dwi = x.row(i).t
dwi.mult((this._gaussian(this._t[k + 1] - pstar[i]) - this._gaussian(this._t[k] - pstar[i])) / (pk0 - pk1))
dw.add(dwi)
}
dw.mult(this._a)

this._w.add(dw)
for (let i = 1; i < this._t.length - 1; i++) {
this._t[i] += this._a * dt[i]
}
this._t.sort((a, b) => a - b)
}

/**
* Returns predicted values.
*
* @param {Array<Array<number>>} x Sample data
* @returns {Array<number>} Predicted values
*/
predict(x) {
x = Matrix.fromArray(x)
const pstar = x.dot(this._w).value
const p = []
for (let i = 0; i < pstar.length; i++) {
p[i] = null
for (let k = 0; k < this._response.length; k++) {
if (this._t[k] < pstar[i] && pstar[i] <= this._t[k + 1]) {
p[i] = this._response[k]
break
}
}
}
return p
}
}
47 changes: 47 additions & 0 deletions tests/gui/view/ordinal_regression.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { getPage } from '../helper/browser'

describe('classification', () => {
/** @type {Awaited<ReturnType<getPage>>} */
let page
beforeEach(async () => {
page = await getPage()
}, 10000)

afterEach(async () => {
await page?.close()
})

test('initialize', async () => {
const taskSelectBox = await page.waitForSelector('#ml_selector dl:first-child dd:nth-child(5) select')
await taskSelectBox.selectOption('CF')
const modelSelectBox = await page.waitForSelector('#ml_selector .model_selection #mlDisp')
await modelSelectBox.selectOption('ordinal_regression')
const methodMenu = await page.waitForSelector('#ml_selector #method_menu')
const buttons = await methodMenu.waitForSelector('.buttons')

const rate = await buttons.waitForSelector('input:nth-of-type(1)')
await expect((await rate.getProperty('value')).jsonValue()).resolves.toBe('0.001')
}, 10000)

test('learn', async () => {
const taskSelectBox = await page.waitForSelector('#ml_selector dl:first-child dd:nth-child(5) select')
await taskSelectBox.selectOption('CF')
const modelSelectBox = await page.waitForSelector('#ml_selector .model_selection #mlDisp')
await modelSelectBox.selectOption('ordinal_regression')
const methodMenu = await page.waitForSelector('#ml_selector #method_menu')
const buttons = await methodMenu.waitForSelector('.buttons')

const epoch = await buttons.waitForSelector('[name=epoch]')
await expect(epoch.evaluate(el => el.textContent)).resolves.toBe('0')
const methodFooter = await page.waitForSelector('#method_footer', { state: 'attached' })
await expect(methodFooter.evaluate(el => el.textContent)).resolves.toBe('')

const initButton = await buttons.waitForSelector('input[value=Initialize]')
await initButton.evaluate(el => el.click())
const stepButton = await buttons.waitForSelector('input[value=Step]:enabled')
await stepButton.evaluate(el => el.click())

await expect(epoch.evaluate(el => el.textContent)).resolves.toBe('1')
await expect(methodFooter.evaluate(el => el.textContent)).resolves.toMatch(/^Accuracy:[0-9.]+$/)
}, 10000)
})
24 changes: 24 additions & 0 deletions tests/lib/model/ordinal_regression.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Matrix from '../../../lib/util/matrix.js'
import OrdinalRegression from '../../../lib/model/ordinal_regression.js'

import { rmse } from '../../../lib/evaluate/regression.js'

describe('ordinal', () => {
test('fit', () => {
const model = new OrdinalRegression()
const x = Matrix.concat(
Matrix.concat(Matrix.randn(50, 2, 0, 0.2), Matrix.randn(50, 2, 2, 0.2)),
Matrix.randn(50, 2, 4, 0.2)
).toArray()
const t = []
for (let i = 0; i < x.length; i++) {
t[i] = Math.floor(i / 50)
}
for (let i = 0; i < 100; i++) {
model.fit(x, t)
}
const y = model.predict(x)
const err = rmse(y, t)
expect(err).toBeLessThan(0.5)
})
})