@ai-on-browser/data-analysis-models
Version:
Data analysis model package without any dependencies
289 lines (266 loc) • 7.41 kB
JavaScript
import Matrix from '../util/matrix.js'
import { adam } from './nns/optimizer.js'
/**
* @ignore
* @typedef {import("./nns/graph").LayerObject} LayerObject
*/
const ActivationFunctions = {
identity: { calc: i => i, grad: () => 1 },
elu: { calc: i => (i > 0 ? i : Math.exp(i) - 1), grad: i => (i > 0 ? 1 : Math.exp(i)) },
gaussian: { calc: i => Math.exp(-(i ** 2) / 2), grad: (i, o) => -o * i },
leaky_relu: { calc: i => (i > 0 ? i : 0.01 * i), grad: i => (i > 0 ? 1 : 0.01) },
relu: { calc: i => Math.max(0, i), grad: i => (i > 0 ? 1 : 0) },
sigmoid: { calc: i => 1 / (1 + Math.exp(-i)), grad: (i, o) => o * (1 - o) },
softplus: { calc: i => Math.log(1 + Math.exp(i)), grad: i => 1 / (1 + Math.exp(-i)) },
softsign: { calc: v => v / (1 + Math.abs(v)), grad: i => 1 / (1 + Math.abs(i)) ** 2 },
tanh: { calc: Math.tanh, grad: (i, o) => 1 - o ** 2 },
}
class MLP {
constructor(layer_sizes, activations) {
this._layer_sizes = layer_sizes
this._activations = activations
this._a = []
this._w = []
this._b = []
for (let i = 0; i < layer_sizes.length - 1; i++) {
this._a[i] = ActivationFunctions[activations[i]]
this._w[i] = Matrix.randn(layer_sizes[i], layer_sizes[i + 1], 0, 0.1)
this._b[i] = Matrix.zeros(1, layer_sizes[i + 1])
}
this._optimizer = new adam()
this._optimizer_mng = this._optimizer.manager()
}
calc(x) {
this._i = [x]
this._o = [x]
for (let i = 0; i < this._w.length; i++) {
this._i[i + 1] = x = x.dot(this._w[i])
x.add(this._b[i])
this._o[i + 1] = x = x.copy()
if (this._a[i]) {
x.map(this._a[i].calc)
}
}
return x
}
update(e, r) {
this._optimizer.learningRate = r
for (let i = this._w.length - 1; i >= 0; i--) {
if (this._a[i]) {
for (let k = 0; k < e.length; k++) {
e.value[k] *= this._a[i].grad(this._i[i + 1].value[k], this._o[i + 1].value[k])
}
}
const dw = this._o[i].tDot(e)
dw.div(this._i[i].rows)
const db = e.mean(0)
e = e.dot(this._w[i].t)
this._w[i].sub(this._optimizer_mng.delta(`w${i}`, dw))
this._b[i].sub(this._optimizer_mng.delta(`b${i}`, db))
}
}
toObject() {
const layers = [{ type: 'input' }]
for (let i = 0; i < this._layer_sizes.length - 1; i++) {
layers.push({
type: 'full',
out_size: this._layer_sizes[i + 1],
activation: this._activations[i],
w: this._w[i]?.toArray(),
b: this._b[i]?.toArray(),
})
}
return layers
}
}
/**
* Multi layer perceptron classifier
*/
export class MLPClassifier {
/**
* @param {number[]} hidden_sizes Sizes of hidden layers
* @param {'identity' | 'elu' | 'gaussian' | 'leaky_relu' | 'relu' | 'sigmoid' | 'softplus' | 'softsign' | 'tanh'} [activation] Activation name
*/
constructor(hidden_sizes, activation = 'tanh') {
this._hidden_sizes = hidden_sizes
this._activations = Array(this._hidden_sizes.length).fill(activation)
this._model = null
this._classes = null
this._epoch = 0
}
/**
* Category list
* @type {*[]}
*/
get categories() {
return this._classes
}
/**
* Epoch
* @type {number}
*/
get epoch() {
return this._epoch
}
/**
* Returns object representation.
* @returns {LayerObject[]} Object represented this neuralnetwork
*/
toObject() {
return [...this._model.toObject(), { type: 'softmax' }, { type: 'output' }]
}
/**
* Fit model.
* @param {Array<Array<number>>} train_x Training data
* @param {*[]} train_y Target values
* @param {number} iteration Iteration count
* @param {number} [rate] Learning rate
* @param {number} [batch] Batch size
* @returns {number} Loss value
*/
fit(train_x, train_y, iteration, rate = 0.001, batch = 0) {
if (!this._model) {
this._classes = [...new Set(train_y)]
const layer_sizes = [train_x[0].length, ...this._hidden_sizes, this._classes.length]
this._model = new MLP(layer_sizes, this._activations)
}
const y = train_y.map(v => {
const yi = Array(this._classes.length).fill(0)
yi[this._classes.indexOf(v)] = 1
return yi
})
const xs = []
const ys = []
if (batch > 0) {
for (let k = 0; k < train_x.length; k += batch) {
xs.push(Matrix.fromArray(train_x.slice(k, k + batch)))
ys.push(Matrix.fromArray(y.slice(k, k + batch)))
}
} else {
xs.push(Matrix.fromArray(train_x))
ys.push(Matrix.fromArray(y))
}
let e
for (let i = 0; i < iteration; i++) {
for (let k = 0; k < xs.length; k++) {
e = this._fitonce(xs[k], ys[k], rate)
}
}
this._epoch += iteration
e.map(v => v ** 2)
return e.mean()
}
_fitonce(x, y, r) {
const p = this._model.calc(x)
p.sub(p.max(1))
p.map(Math.exp)
p.div(p.sum(1))
const e = Matrix.sub(p, y)
this._model.update(e, r)
return e
}
/**
* Returns predicted probabilities.
* @param {Array<Array<number>>} x Sample data
* @returns {Array<Array<number>>} Predicted values
*/
probability(x) {
const p = this._model.calc(Matrix.fromArray(x))
p.sub(p.max(1))
p.map(Math.exp)
p.div(p.sum(1))
return p.toArray()
}
/**
* Returns predicted values.
* @param {Array<Array<number>>} x Sample data
* @returns {*[]} Predicted values
*/
predict(x) {
return this._model
.calc(Matrix.fromArray(x))
.argmax(1)
.value.map(v => this._classes[v])
}
}
/**
* Multi layer perceptron regressor
*/
export class MLPRegressor {
/**
* @param {number[]} hidden_sizes Sizes of hidden layers
* @param {'identity' | 'elu' | 'gaussian' | 'leaky_relu' | 'relu' | 'sigmoid' | 'softplus' | 'softsign' | 'tanh'} [activation] Activation name
*/
constructor(hidden_sizes, activation = 'tanh') {
this._hidden_sizes = hidden_sizes
this._activations = Array(hidden_sizes.length).fill(activation)
this._model = null
this._epoch = 0
}
/**
* Epoch
* @type {number}
*/
get epoch() {
return this._epoch
}
/**
* Returns object representation.
* @returns {LayerObject[]} Object represented this neuralnetwork
*/
toObject() {
return [...this._model.toObject(), { type: 'output' }]
}
/**
* Fit model.
* @param {Array<Array<number>>} train_x Training data
* @param {Array<Array<number>>} train_y Target values
* @param {number} iteration Iteration count
* @param {number} [rate] Learning rate
* @param {number} [batch] Batch size
* @returns {number} Loss value
*/
fit(train_x, train_y, iteration, rate = 0.001, batch = 0) {
if (!this._model) {
const layer_sizes = [train_x[0].length, ...this._hidden_sizes, train_y[0].length]
this._model = new MLP(layer_sizes, this._activations)
}
const xs = []
const ys = []
if (batch > 0) {
for (let k = 0; k < train_x.length; k += batch) {
xs.push(Matrix.fromArray(train_x.slice(k, k + batch)))
ys.push(Matrix.fromArray(train_y.slice(k, k + batch)))
}
} else {
xs.push(Matrix.fromArray(train_x))
ys.push(Matrix.fromArray(train_y))
}
let e
for (let i = 0; i < iteration; i++) {
for (let k = 0; k < xs.length; k++) {
e = this._fitonce(xs[k], ys[k], rate)
}
}
this._epoch += iteration
e.map(v => v ** 2)
return e.mean()
}
_fitonce(x, y, r) {
const p = this._model.calc(x)
const e = Matrix.sub(p, y)
e.div(2)
this._model.update(e, r)
return e
}
/**
* Returns predicted values.
* @param {Array<Array<number>>} x Sample data
* @returns {Array<Array<number>>} Predicted values
*/
predict(x) {
x = Matrix.fromArray(x)
const pred = this._model.calc(x)
return pred.toArray()
}
}