greataptic
Version:
A simplistic neural network library.
1,245 lines (1,078 loc) • 41.5 kB
JavaScript
var util = require('util');
var {
Vector
} = require('./vector.js');
var shuffle = require('shuffle-array');
let words = ['above-mentioned', 'above-listed', 'before-mentioned', 'aforementioned', 'abundance', 'accelerate', 'accentuate', 'accommodation', 'accompany', 'accomplish', 'accorded', 'accordingly', 'accrue', 'accurate', 'acquiesce', 'acquire', 'additional', 'address', 'addressees', 'adjustment', 'admissible', 'advantageous', 'advise', 'aggregate', 'aircraft', 'alleviate', 'allocate', 'alternatively', 'ameliorate', 'and/or', 'anticipate', 'applicant', 'application', 'apparent', 'apprehend', 'appreciable', 'appropriate', 'approximate', 'ascertain', 'attain', 'attempt', 'authorize', 'beg', 'belated', 'beneficial', 'bestow', 'beverage', 'capability', 'caveat', 'cease', 'chauffeur', 'clearly', 'obviously', 'combined', 'commence', 'complete', 'component', 'comprise', 'conceal', 'concerning', 'consequently', 'consolidate', 'constitutes', 'contains', 'convene', 'corridor', 'currently', 'deem', 'delete', 'demonstrate', 'depart', 'designate', 'desire', 'determine', 'disclose', 'different', 'discontinue', 'disseminate', 'duly', 'authorized', 'signed', 'each...apiece', 'economical', 'elect', 'eliminate', 'elucidate', 'emphasize', 'employ', 'encounter', 'endeavor', 'end', 'result', 'product', 'enquiry', 'ensure', 'entitlement', 'enumerate', 'equipments', 'equitable', 'equivalent', 'establish', 'evaluate', 'evidenced', 'evident', 'evince', 'excluding', 'exclusively', 'exhibit', 'expedite', 'expeditious', 'expend', 'expertise', 'expiration', 'facilitate', 'fauna', 'feasible', 'females', 'finalize', 'flora', 'following', 'forfeit', 'formulate', 'forward', 'frequently', 'function', 'furnish', 'grant', 'herein', 'heretofore', 'herewith', 'thereof', 'wherefore', 'wherein', 'however', 'identical', 'identify', 'immediately', 'impacted', 'implement', 'inasmuch', 'inception', 'indicate', 'indication', 'initial', 'initiate', 'interface', 'irregardless', 'liaison', '-ly', 'doubtless', 'fast', 'ill', 'much', 'seldom', 'thus', 'magnitude', 'maintain', 'majority', 'maximum', 'merge', 'methodology', 'minimize', 'minimum', 'modify', 'monitor', 'moreover', 'multiple', 'necessitate', 'nevertheless', 'notify', 'not...unless', 'not...except', 'not...until', 'notwithstanding', 'numerous', 'objective', 'obligate', 'observe', 'obtain', 'operate', 'optimum', 'option', 'orientate', '...out', 'calculate', 'cancel,', 'distribute', 'segregate', 'separate', 'overall', 'parameters', 'participate', 'particulars', 'perchance', 'perform', 'permit', 'perspire', 'peruse', 'place', 'portion', 'possess', 'potentiality', 'practicable', 'preclude', 'preowned', 'previously', 'prioritize', 'proceed', 'procure', 'proficiency', 'promulgate', 'provide', 'purchase', 'reflect', 'regarding', 'relocate', 'remain', 'remainder', 'remuneration', 'render', 'represents', 'request', 'require', 'requirement', 'reside', 'residence', 'respectively', 'retain', 'retire', 'rigorous', 'selection', 'separate', 'shall', 'solicit', 'state-of-the-art', 'strategize', 'subject', 'submit', 'subsequent', 'subsequently', 'substantial', 'sufficient', 'terminate', 'therefore', 'therein', 'timely', 'transpire', 'transmit', 'type', 'validate', 'variation', 'very', 'viable', 'warrant', 'whereas', 'whosoever', 'whomsoever', 'witnessed'];
function _logit(x) {
return Math.log(x / (1 - x));
}
function gaussRand() {
// adapted from https://stackoverflow.com/a/49434653/5129091
var u = Math.random(),
v = Math.random();
if (u === 0) u = 0.5;
if (v === 0) v = 0.5;
let res = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v) / 10 + .5;
if (res > 1 || res < 0) return gaussRand();
return res;
}
let greataptic = {};
greataptic.Vector = Vector;
greataptic.$vec = function $vec(b) {
return new Vector(b);
};
function mutateNum(x, amount) {
return x + 2 * amount * (Math.random() - 0.5);
}
function mutateList(l, amount) {
return l.map(x => mutateNum(x, amount));
}
function stepInterp(a, b, alpha) {
return (1 - alpha) * a + alpha * b;
}
function stepInterpList(la, lb, alpha) {
let res = [];
for (let i = 0; i < Math.min(la.length, lb.length); i++) res[i] = stepInterp(la[i], lb[i], alpha);
return res;
}
class Layer {
constructor(next, name = null, data = null) {
this.size = 0;
this.next = next;
this.type = this.typename();
this.data = data;
this.name = null;
}
process(vec) {
return vec;
}
remake(data) {
return new this.constructor(this.next, this.name, data);
}
_load(data) {
this.data = data;
}
_save() {
return this.data;
}
static load(data) {
let res = new this(data.next, data.name, null);
res._load(data.data);
return res;
}
save() {
return {
type: this.type,
next: this.next,
name: this.name,
data: this._save()
};
}
typename() {
throw new Error("Don't use Layer directly! (tried to call method 'name')");
}
mutate(amount = null) {
return this;
}
breed(layer) {
return Math.random() > 0.5 ? layer : this;
}
applyStep(layer, fitness) {
return this;
}
}
;
class ActivationLayer extends Layer {
mutate() {}
breed(layer) {
return this;
}
applyStep(layer, fitness) {
return this;
}
process(vec) {
return vec.map(this.activate);
}
activate(n) {
return n;
}
} // List of layer types.
let LSTMLayer;
let types = {
// Sequential layer group.
sequence: class GroupLayer extends Layer {
typename() {
return 'sequence';
}
constructor(next, name = null, parts) {
super(next, name);
this.data = parts;
this.size = parts.slice(-1)[0].size;
}
process(vec) {
let res = vec;
this.data.forEach(l => res = l.process(res));
return res;
}
mutate(amount = null) {
this.data.forEach(l => {
if (l.mutate) l.mutate(amount);
});
}
breed(layer) {
let p = this.data.map((l, i) => types[l.type].breed(l, layer.data[i]));
return this.remake(p);
}
applyStep(layer, fitness) {
let p = this.data.map((l, i) => types[l.type].applyStep(l, layer.data[i], fitness));
return this.remake(p);
}
},
combo: class ConcatLayer extends Layer {
typename() {
return 'combo';
}
process(vec) {
let res = this.data.reduce((a, ln) => a.concat(types[ln.type].process(vec, ln).data), []);
return new Vector(res);
}
constructor(next = null, name = null, parts) {
super(next, name = null);
this.data = parts;
this.size = parts.map(p => p.size).reduce((a, b) => a + b, 0);
}
mutate(amount = null) {
this.data.forEach(l => {
if (l.mutate) l.mutate(amount);
});
}
breed(layer2) {
let p = this.data.map((l, i) => types[l.type].breed(l, layer2.data[i]));
return this.remake(p);
}
applyStep(layer2, fitness) {
let p = this.data.map((l, i) => types[l.type].applyStep(l, layer2.data[i], fitness));
return this.remake(p);
}
},
sigmoid: class ASigmoidLayer extends Layer {
typename() {
return 'sigmoid';
}
activate(n) {
return 1 / (1 + Math.exp(-n));
}
},
tanh: class ATanhLayer extends Layer {
typename() {
return 'tanh';
}
activate(n) {
return Math.tanh(n);
}
},
logit: class ALogitLayer extends Layer {
typename() {
return 'logit';
}
activate(n) {
return Math.log(n / (1 - n));
}
},
spiking: class SpikingLayer extends Layer {
typename() {
return 'spiking';
}
constructor(next, name = null, nodes) {
super(next, name);
this.data = nodes;
}
process(vec) {
let res = new Array(this.data.length).fill(0);
this.data.forEach((node, ni) => {
if ((node.power += Math.max(new Vector(node.weights).multiplyVec(vec).sum(), 0)) > node.limit) {
node.power = 0;
res[ni] = node.output;
}
});
return new Vector(res);
}
mutate(amount = null) {
function _mut(x) {
if (amount != null) return x + 2 * amount * (Math.random() - 0.5);else return x + _logit(0.25 + 0.5 * Math.random());
}
this.data = this.data.map(n => ({
power: n.power,
output: n.output,
limit: _mut(n.limit),
weights: n.weights.map(_mut)
}));
}
breed(layer2) {
return this.remake(this.data.map((node, ni) => ({
power: choice([node.power, layer2.data[ni].power]),
output: choice([node.output, layer2.data[ni].output]),
limit: choice([node.limit, layer2.data[ni].limit]),
weights: node.weights.map((x, xi) => Math.random() > 0.5 ? x : layer2.data[ni].weights[xi])
})));
}
applyStep(layer2, fitness) {
return this.remake(this.data.map((node, ni) => ({
power: stepInterp(node.power, layer2.data[ni].power, fitness),
output: stepInterp(node.output, layer2.data[ni].output, fitness),
limit: stepInterp(node.limit, layer2.data[ni].limit, fitness),
weights: node.weights.map((x, xi) => stepInterp(x, layer2.data[ni].weights[xi], fitness))
})));
}
},
square: class SquareLayer extends Layer {
typename() {
return 'square';
}
constructor(next, name = null, nodes) {
super(next, name);
this.data = nodes;
}
process(vec) {
return new Vector(this.data.map(n => {
return new Vector(n.weights.linear).multiplyVec(vec).add(new Vector(n.weights.square).multiplyVec(vec.pow(2))).sum() + n.offset;
}));
}
mutate(amount = null) {
function _mut(x) {
if (amount != null) return x + 2 * amount * (Math.random() - 0.5);else return x + _logit(0.25 + 0.5 * Math.random());
}
this.data = this.data.map(l => ({
weights: {
linear: l.weights.linear.map(_mut),
square: l.weights.square.map(_mut)
},
offset: _mut(l.offset)
}));
}
breed(layer2) {
return this.remake(this.data.map((n, ni) => ({
offset: choice([n.offset, layer2.data[ni].offset]),
weights: {
linear: n.weights.linear.map((x, xi) => Math.random() > 0.5 ? x : layer2.data[ni].weights.linear[xi]),
square: n.weights.square.map((x, xi) => Math.random() > 0.5 ? x : layer2.data[ni].weights.square[xi])
}
})));
}
applyStep(layer2, fitness) {
return this.remake(this.data.map((n, ni) => ({
offset: choice([n.offset, layer2.data[ni].offset]),
weights: {
linear: n.weights.linear.map((x, xi) => Math.random() > 0.5 ? x : layer2.data[ni].weights.linear[xi]),
square: n.weights.square.map((x, xi) => Math.random() > 0.5 ? x : layer2.data[ni].weights.square[xi])
}
})));
}
},
linear: class LinearLayer extends Layer {
typename() {
return 'linear';
}
constructor(next, name = null, nodes) {
super(next, name);
this.data = nodes;
}
process(vec) {
return new Vector(this.data.map(n => {
return new Vector(n.weights).multiplyVec(vec).sum() + n.offset;
}));
}
mutate(amount = null) {
function _mut(x) {
if (amount != null) return x + 2 * amount * (Math.random() - 0.5);else return x + _logit(0.25 + 0.5 * Math.random());
}
this.data = this.data.map(l => ({
weights: l.weights.map(_mut),
offset: _mut(l.offset)
}));
}
breed(layer2) {
return this.remake(this.data.map((n, ni) => ({
offset: choice([n.offset, layer2.data[ni].offset]),
weights: n.weights.map((x, xi) => Math.random() > 0.5 ? x : layer2.data[ni].weights[xi])
})));
}
applyStep(layer2, fitness) {
return this.remake(this.data.map((n, ni) => ({
offset: stepInterp(n.offset, layer2.data[ni].offset, fitness),
weights: n.weights.map((x, xi) => stepInterp(x, layer2.data[ni].weights[xi], fitness))
})));
}
},
lstm: LSTMLayer = class LSTMLayer extends Layer {
typename() {
return 'lstm';
}
static randomGates(lastSize, size) {
return {
input: {
weights: new Array(size).fill(0).map(() => Vector.random(lastSize).data),
hiddenWeights: new Array(size).fill(0).map(() => Vector.random(lastSize).data),
offset: Vector.random(size).data
},
output: {
weights: new Array(size).fill(0).map(() => Vector.random(lastSize).data),
hiddenWeights: new Array(size).fill(0).map(() => Vector.random(lastSize).data),
offset: Vector.random(size).data
},
forget: {
weights: new Array(size).fill(0).map(() => Vector.random(lastSize).data),
hiddenWeights: new Array(size).fill(0).map(() => Vector.random(lastSize).data),
offset: Vector.random(size).data
},
cell: {
weights: new Array(size).fill(0).map(() => Vector.random(lastSize).data),
hiddenWeights: new Array(size).fill(0).map(() => Vector.random(lastSize).data),
offset: Vector.random(size).data
}
};
}
static randomStates(size) {
return {
cell: Array.apply(null, Array(size)).map(Number.prototype.valueOf, 0),
hidden: Array.apply(null, Array(size)).map(Number.prototype.valueOf, 0)
};
}
constructor(next, name, gates, states, activation = 'sigmoid', cellActivation = 'tanh') {
super(next, name, {
gates: gates,
states: states,
activation: activation,
cellActivation: cellActivation
});
}
process(vec) {
vec = new Vector(vec);
let {
forget,
input,
output
} = this.data.gates;
let gCell = this.data.gates.cell;
let act = this.data.activation;
let cAct = this.data.cellActivation;
let sHidden = new Vector(this.data.states.hidden);
let sCell = new Vector(this.data.states.cell);
let gat = {
forget: {
weights: forget.weights.map(w => new Vector(w)),
hiddenWeights: forget.hiddenWeights.map(w => new Vector(w))
},
input: {
weights: input.weights.map(w => new Vector(w)),
hiddenWeights: input.hiddenWeights.map(w => new Vector(w))
},
output: {
weights: output.weights.map(w => new Vector(w)),
hiddenWeights: output.hiddenWeights.map(w => new Vector(w))
},
cell: {
weights: gCell.weights.map(w => new Vector(w)),
hiddenWeights: gCell.hiddenWeights.map(w => new Vector(w))
}
};
let forgVec = new Vector(gat.forget.weights.map((n, i) => greataptic.activate(act, n.multiplyVec(vec).sum() + gat.forget.hiddenWeights[i].multiplyVec(sHidden).sum() + forget.offset[i])));
let inpVec = new Vector(gat.input.weights.map((n, i) => greataptic.activate(act, n.multiplyVec(vec).sum() + gat.input.hiddenWeights[i].multiplyVec(sHidden).sum() + input.offset[i])));
let outVec = new Vector(gat.output.weights.map((n, i) => greataptic.activate(act, n.multiplyVec(vec).sum() + gat.output.hiddenWeights[i].multiplyVec(sHidden).sum() + output.offset[i])));
this.data.states.cell = forgVec.multiplyVec(sCell).add(inpVec.multiplyVec(gat.cell.weights.map((n, i) => greataptic.activate(cAct, n.multiplyVec(vec).sum() + gat.cell.hiddenWeights[i].multiplyVec(sHidden).sum() + gCell.offset[i])))).data;
this.data.states.hidden = outVec.multiplyVec(this.data.states.cell.map(x => greataptic.activate(cAct, x))).data;
return new Vector(this.data.states.hidden);
}
breedGate(g1, g2, choices) {
return {
weights: g1.weights.map((w, i) => choices[i] ? w : g2.weights[i]),
hiddenWeights: g1.hiddenWeights.map((w, i) => choices[i] ? w : g2.hiddenWeights[i]),
offset: g1.offset.map((o, i) => choices[i] ? o : g2.offset[i])
};
}
breedGates(g1, g2, choices) {
if (!choices) {
for (let i = 0; i < g1.hidden.weights.length; i++) choices.push(+(Math.random() > 0.5));
}
return {
hidden: this.breedGate(g1.hidden, g2.hidden, choices),
input: this.breedGate(g1.input, g2.input, choices),
output: this.breedGate(g1.output, g2.output, choices),
cell: this.breedGate(g1.cell, g2.cell, choices)
};
}
breedState(s1, s2, choices) {
return s1.map((x, i) => choices[i] ? x : s2[i]);
}
breedStates(s1, s2, choices) {
return {
cell: this.breedState(s1.cell, s2.cell, choices),
hidden: this.breedState(s1.hidden, s2.hidden, choices)
};
}
breed(layer2) {
let choices = [];
let bred = new this.constructor(this.next, this.name, this.breedGates(this.data.gates, layer2.data.gates, choices), this.breedStates(this.data.states, layer2.data.states, choices), this.data.activation, this.data.cellActivation);
return bred;
}
stepGate(g1, g2, fit) {
return {
weights: g1.weights.map((w, i) => stepInterpList(w, g2.weights[i], fit)),
hiddenWeights: g1.hiddenWeights.map((w, i) => stepInterpList(w, g2.hiddenWeights[i], fit)),
offset: stepInterpList(g1.offset, g2.offset, fit)
};
}
stepGates(g1, g2, fit) {
return {
forget: this.stepGate(g1.forget, g2.forget, fit),
input: this.stepGate(g1.input, g2.input, fit),
output: this.stepGate(g1.output, g2.output, fit),
cell: this.stepGate(g1.cell, g2.cell, fit)
};
}
stepState(s1, s2, fit) {
return s1.map((x, i) => stepInterp(x, s2[i], fit));
}
stepStates(s1, s2, fit) {
return {
cell: this.stepState(s1.cell, s2.cell, fit),
hidden: this.stepState(s1.hidden, s2.hidden, fit)
};
}
applyStep(layer2, fit) {
let stepped = new this.constructor(this.next, this.name, this.stepGates(this.data.gates, layer2.data.gates, fit), this.stepStates(this.data.states, layer2.data.states, fit), this.data.activation, this.data.cellActivation);
stepped.name = this.name;
return stepped;
}
mutateGate(g1, amount) {
return {
weights: g1.weights.map(w => mutateList(w, amount)),
hiddenWeights: g1.hiddenWeights.map(w => mutateList(w, amount)),
offset: mutateList(g1.offset, amount)
};
}
mutateGates(g1, amount) {
g1.forget = this.mutateGate(g1.forget, amount);
g1.input = this.mutateGate(g1.input, amount);
g1.output = this.mutateGate(g1.output, amount);
g1.cell = this.mutateGate(g1.cell, amount);
}
mutateState(s1, amount) {
return mutateList(s1, amount);
}
mutateStates(s1, amount) {
s1.cell = this.mutateState(s1.cell, amount);
s1.hidden = this.mutateState(s1.hidden, amount);
}
mutate(amount = null) {
if (amount == null) amount = _logit(0.25 + 0.5 * Math.random());
this.mutateGates(this.data.gates, amount);
this.mutateStates(this.data.states, amount);
}
},
lstm_peephole: class LSTMPeepholLayer extends LSTMLayer {
typename() {
return 'lstm_peephole';
}
process(vec) {
let {
forget,
input,
output
} = this.data.gates;
let gCell = this.data.gates.cell;
let act = this.data.activation;
let cAct = this.data.cellActivation;
let sCell = new Vector(this.data.states.cell);
let gat = {
forget: {
weights: forget.weights.map(w => new Vector(w)),
hiddenWeights: forget.hiddenWeights.map(w => new Vector(w))
},
input: {
weights: input.weights.map(w => new Vector(w)),
hiddenWeights: input.hiddenWeights.map(w => new Vector(w))
},
output: {
weights: output.weights.map(w => new Vector(w)),
hiddenWeights: output.hiddenWeights.map(w => new Vector(w))
},
cell: {
weights: gCell.weights.map(w => new Vector(w)),
hiddenWeights: gCell.hiddenWeights.map(w => new Vector(w))
}
};
let forgVec = new Vector(gat.forget.weights.map((n, i) => greataptic.activate(act, n.multiplyVec(vec).sum() + gat.forget.hiddenWeights[i].multiplyVec(sCell).sum() + forget.offset[i])));
let inpVec = new Vector(gat.input.weights.map((n, i) => greataptic.activate(act, n.multiplyVec(vec).sum() + gat.input.hiddenWeights[i].multiplyVec(sCell).sum() + input.offset[i])));
let outVec = new Vector(gat.output.weights.map((n, i) => greataptic.activate(act, n.multiplyVec(vec).sum() + gat.output.hiddenWeights[i].multiplyVec(sCell).sum() + output.offset[i])));
this.data.states.cell = forgVec.multiplyVec(sCell).add(inpVec.multiplyVec(gat.cell.weights.map((n, i) => greataptic.activate(cAct, n.multiplyVec(vec).sum() + gCell.offset[i])))).data;
this.data.states.hidden = outVec.multiplyVec(this.data.states.cell.map(x => greataptic.activate(cAct, x))).data;
return new Vector(this.data.states.hidden);
}
}
};
greataptic.layerTypes = types;
greataptic.isNetworkData = function (n) {
return !!(n && n.layers && n.first != null);
};
function choice(l) {
return l[Math.floor(l.length * Math.random())];
}
greataptic.breed = function breed(nets) {
let res = nets[0].clone();
let newLayers = {};
Object.keys(res.data.layers).forEach(i => {
let l = res.data.layers[i];
let n2 = choice(nets.slice(1));
let l2 = n2.data.layers[i];
if (n2 !== nets[0] && l2.type === l.type && types[l.type].breed) l = types[l.type].breed(l, l2);
newLayers[i] = l;
});
res.data.layers = newLayers;
return res;
};
let $net = greataptic.$net = function (data) {
return new NeuralNetwork(data);
};
let NeuralNetwork = greataptic.NeuralNetwork = class NeuralNetwork {
constructor(net) {
if (!greataptic.isNetworkData(net)) throw new Error(`The net parameter passed to NeuralNetwork's constructor (${util.inspect(net)}) is invalid!`);
this.data = net;
this.id = new Array(5).fill(0).map(() => choice(words)).join('-') + '-' + Math.ceil(Math.random() * 10000);
}
json() {
let lays = {};
Object.entries(this.data.layers).forEach(l => {
let [id, lay] = l;
lays[id] = lay.save();
});
return JSON.stringify({
layers: lays,
first: this.data.first
});
}
compute(vec) {
if (!Vector.is(vec)) vec = new Vector(vec);
let li;
let layer = this.data.layers[li = this.data.first];
let res = vec;
let i = 0; // eslint-disable-next-line no-constant-condition
while (true) {
if (layer == null) throw new Error(`Layer '${li}' in network pool (#${i + 1} in sequence) does not exist!`); //let old = res;
layer.net = this;
res = layer.process(res);
delete layer.net;
if (layer.next == null) break;else {
layer = this.data.layers[li = layer.next];
i++;
}
}
return res;
}
computeAsync(vec) {
if (!Vector.is(vec)) vec = new Vector(vec);
let li;
let layer = this.data.layers[li = this.data.first];
let res = vec;
let i = 0;
return new Promise((resolve => {
// eslint-disable-next-line no-constant-condition
function pass() {
if (layer == null) throw new Error(`Layer '${li}' in network pool (#${i + 1} in sequence) does not exist!`);
let t = types[layer.type];
if (t == null) throw new Error(`No such layer type ${util.inspect(layer.type)}!`); //let old = res;
layer.net = this;
res = t.process(res, layer);
delete layer.net;
if (layer.next == null) resolve(res);else {
layer = this.data.layers[li = layer.next];
i++;
setTimeout(pass, 0);
}
}
pass();
}).bind(this));
}
clone() {
return greataptic.fromJSON(this.json());
}
mutate(amount = null) {
let c = this.clone();
Array.from(Object.entries(c.data.layers)).forEach(e => {
let l = e[1];
if (l.type && types[l.type].mutate) {
types[l.type].mutate(l, amount);
}
});
return c;
}
applyStep(otherNet, fitness) {
if (fitness === 0) return;
Array.from(Object.entries(this.data.layers)).forEach(e => {
let id = e[0];
let l = e[1];
if (l.type && types[l.type].applyStep && otherNet.data.layers[id] && otherNet.data.layers[id].type === l.type) this.data.layers[id] = types[l.type].applyStep(l, otherNet.data.layers[id], fitness);
});
}
error(inputSet, expectedSet) {
let outputSet = inputSet.map(i => this.compute(i));
let res = outputSet.map((os, si) => os.data.map((o, i) => Math.pow(o - expectedSet[si][i], 2)).reduce((a, b) => a + b, 0)).reduce((a, b) => a + b, 0);
return res / inputSet.length;
}
staticFitness(inputSet, expectedSet) {
let err = this.error(inputSet, expectedSet);
return 1 / (1 + err);
}
_evolveStep(ctx) {
let populateSteps = () => {
let res = ctx.steps = new Array(ctx.population).fill(0).map(() => ctx.makeStep(this));
return res;
};
let tryStep = step => {
return Promise.resolve(ctx.getFitness(step)).then(fit => {
step.fitness = fit;
});
};
let concludeSteps = () => {
let res = this.clone();
let newFit = 0;
let denom = 1;
ctx.steps.forEach(s => {
res.applyStep(s, s.fitness * ctx.stepFactor);
newFit = (newFit * (denom - 1) + s.fitness) / denom++;
});
res.fitness = newFit;
res.trainCtx = ctx;
if (ctx.options.postStep) ctx.options.postStep(res);
return res;
};
populateSteps();
let prom = Promise.resolve();
if (ctx.options.stepAll) {
let batchPop = ctx.options.batchPop;
let i = 0;
let batchIndex = 1; // eslint-disable-next-line no-constant-condition
while (true) {
if (i >= ctx.steps.length) break;
batchPop = Math.min(ctx.steps.length - i, batchPop);
let ls = ctx.steps.slice(i, i + batchPop);
let gen = ctx.options.generation || null;
let bi = batchIndex;
prom = prom.then(() => Promise.resolve(ctx.options.stepAll(ls, gen, bi)));
i += batchPop;
batchIndex++;
}
} else ctx.steps.forEach(step => {
prom = prom.then(() => tryStep(step));
});
return new Promise(resolve => {
prom.then(() => {
resolve(concludeSteps());
});
});
}
evolveStep(options = {}) {
let getFitness = net => {
return new Promise(() => {
if (options.step) return Promise.resolve(options.step(net, options.generation || null));else return 0;
}).then(res => {
if (options.inputSet && options.expectedSet && !options.callbackOnly) res += this.staticFitness(options.inputSet, options.expectedSet);
return res;
});
};
let makeStep = () => {
if (options.random) return this.mutate(100);else return this.mutate(gaussRand() * ctx.maxMutation * Math.pow(options.mutationDecay, (options.generation || 0) - 1));
};
let ctx = {
options: options,
population: options.population || 50,
maxGens: options.maxGens || 500,
maxMutation: options.maxMutation || 0.5,
mutationDecay: options.mutationDecay || 0.85,
stepFactor: options.stepFactor || 0.75,
getFitness: getFitness,
makeStep: makeStep,
steps: null
};
return this._evolveStep(ctx);
}
evolve(options = {}) {
let fitQuota = options.quota != null ? options.quota : Infinity;
let cur = this;
let gen = 1;
options.random = options.random || false;
return new Promise(resolve => {
function iter() {
options.generation = gen;
cur.evolveStep(options).then(evolved => {
options.random = false;
let ctx = evolved.trainCtx;
if (options.debug) // print debug status
console.log(`[DEBUG] Generation ${gen}/${ctx.maxGens}: fitness is now ${evolved.fitness}.`);
if (evolved.fitness >= fitQuota || ++gen > evolved.trainCtx.maxGens) resolve(evolved);else {
if (!cur.fitness || evolved.fitness > cur.fitness) cur = evolved;
setTimeout(iter, 0);
}
});
}
setTimeout(iter, 0);
});
}
};
greataptic.sequential = function sequential(inputSize, layers) {
if (layers.length === 0) {
layers = [{
size: inputSize,
post: 'sigmoid'
}];
}
let res = {
layers: {},
first: '1'
};
let lastLayer = null;
let lastSize = null;
function buildLayer(l, index, after) {
let subres = [];
let postAfter = after;
let preAfter = index + 'm';
let midAfter = l.post == null ? postAfter : index + 'p';
let preId = index;
let midId = l.pre == null ? index : index + 'm';
let postId = index + 'p';
let subs = 0;
function addLayer(l, id, size) {
lastSize = size;
lastLayer = l;
l.name = id;
subres.push(l);
res.layers[id] = l;
}
let layerInput = lastSize == null ? inputSize : lastSize;
if (l.pre != null) {
addLayer(new types[l.pre](preAfter, null, l.data != null ? l.data : null), preId, layerInput);
layerInput = lastSize == null ? inputSize : lastSize;
}
if (l.type === 'combo') {
addLayer(new types.combo(midAfter, null, l.parts.map((ls, i, a) => new types.sequence(null, index + '_presub' + subs, buildLayer(ls, index + '_sub' + ++subs, i + 1 === a.length ? null : index + '_sub' + (subs + 1))), null)), midId, l.parts.reduce((a, ls) => a + ls.size, 0));
} else if (l.type === 'sequence') addLayer(new types.sequence(midAfter, null, l.parts.map((ls, i, a) => buildLayer(ls, index + '_sub' + ++subs, i + 1 === a.length ? null : index + '_sub' + (subs + 1))).reduce((a, b) => a.concat(b), [])), midId, l.parts.reduce((a, b) => b.size || a, null));else if (l.size != null) {
if (l.type === 'spiking') addLayer(new types.spiking(midAfter, null, new Array(l.size).fill(0).map(() => ({
weights: Vector.random(layerInput).data,
power: 0,
output: Math.pow(Math.random() * 3, 2),
limit: _logit(Math.random() * 0.45 + 0.5)
}))), midId, l.size);else if (l.type === 'linear' || l.type == null) {
addLayer(new types.linear(midAfter, null, new Array(l.size).fill(0).map(() => ({
offset: _logit(0.225 + 0.55 * Math.random()),
weights: Vector.random(layerInput).data
}))), midId, l.size);
} else if (l.type === 'square') addLayer(new types.square(midAfter, null, new Array(l.size).fill(0).map(() => ({
offset: _logit(0.25 + 0.5 * Math.random()),
weights: {
linear: Vector.random(layerInput).data,
square: Vector.random(layerInput).data
}
}))), midId, l.size);else if (l.type.startsWith('lstm') && types[l.type]) addLayer(new types[l.type](midAfter, null, types[l.type].randomGates(lastSize, l.size), types[l.type].randomStates(l.size), l.activation || 'sigmoid', l.stateActivation || 'tanh'), midId, l.size);
} else addLayer(new types[l.type](midAfter), midId, layerInput);
if (l.post != null) addLayer(new types[l.post](postAfter), postId, lastSize != null ? lastSize : layerInput);
return subres;
}
layers.forEach((l, i) => buildLayer(l, '' + (i + 1), '' + (i + 2)));
if (lastLayer) lastLayer.next = null;
return greataptic.$net(res);
};
greataptic.fromJSON = function fromJSON(j) {
let res = JSON.parse(j);
let lays = res.layers;
Object.entries(lays).forEach(l => {
let [id, lay] = l;
lays[id] = types[lay.type].load(lay);
});
return new greataptic.NeuralNetwork(res);
};
function inputNoise(size, options = {}) {
let range = options.range || 1;
let negative = options.negative == null || options.negative;
return new Array(size).fill(0).map(() => Math.random() * (negative ? range * 2 : range) - (negative ? range : 0));
}
greataptic.createVectorifier = function createVectorifier(args) {
let vectorifier = {
args: new Map(),
getSize: function () {
let res = 0;
vectorifier.args.forEach(a => {
res += a.getSize();
});
return res;
},
encode: function (obj) {
let enc = [];
vectorifier.args.forEach(a => {
if (obj[a.name] === undefined) throw new Error(`Required argument ${a.name} not passed to vectorizer!`);else enc = enc.concat(a.encode(obj[a.name]));
});
return enc;
},
decode: function (vect) {
let cursor = 0;
let res = {};
vectorifier.args.forEach(a => {
res[a.name] = a.decode(vect.slice(cursor, cursor + a.getSize()));
cursor += a.getSize();
});
return res;
}
};
args.forEach(arg => {
let aname = arg.name;
const strspace = ' 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
if (arg.type.toLowerCase() === 'simplestring') vectorifier.args.set(aname, {
name: aname,
getSize: function getSize() {
return arg.size;
},
encode: function encode(s) {
let sl = s.slice(0, arg.size).split('').map(x => strspace.indexOf(x) === -1 ? strspace[0] : x).join('');
while (sl.length < arg.size) sl += arg.default || ' ';
return Array.from(sl).map(c => {
return strspace.indexOf(c) / strspace.length;
});
},
decode: function decode(v) {
let x = v.map(i => strspace[Math.floor(i * strspace.length)]).join('');
return x;
}
});else if (arg.type.toLowerCase() === 'number') vectorifier.args.set(aname, {
name: aname,
getSize: function getSize() {
return 1;
},
encode: function encode(n) {
return [(n - arg.min) / (arg.max - arg.min)];
},
decode: function decode(v) {
let res = v[0] * (arg.max - arg.min) + arg.min;
if (arg.rounded) res = Math.round(res);
return res;
}
});else if (arg.type.toLowerCase() === 'numbers') {
vectorifier.args.set(aname, {
name: aname,
getSize: function getSize() {
return arg.size;
},
encode: function encode(a) {
a = a.slice(0, arg.size);
while (a.length < arg.size) a.push(0);
return a.map(n => (n - arg.min) / (arg.max - arg.min));
},
decode: function decode(v) {
return v.map(n => {
let res = n * (arg.max - arg.min) + arg.min;
if (arg.rounded) res = Math.round(res);
return res;
});
}
});
} else if (arg.type.toLowerCase() === 'string') vectorifier.args.set(aname, {
name: aname,
getSize: function getSize() {
return arg.size;
},
encode: function encode(s) {
let sl = s.slice(0, arg.size);
while (sl.length < arg.size) sl += arg.default || ' ';
return Array.from(sl).map(c => {
return c.charCodeAt(0) / 256;
});
},
decode: function decode(v) {
return v.map(i => String.fromCharCode(Math.floor(i * 256))).join('');
}
});
});
return vectorifier;
};
greataptic.GAN = class GAN {
constructor(properties) {
properties.size = properties.size || {
output: 40
};
this.size = {
noise: +properties.size.noise || 15,
output: +properties.size.output
};
this.generator = greataptic.sequential(this.size.noise, (properties.generatorLayers || this.generatorDefaultLayers()).concat([{
size: this.size.output,
type: properties.outputType || 'linear',
post: 'sigmoid'
}]));
this.discriminator = greataptic.sequential(this.size.output, (properties.discriminatorLayers || this.discriminatorDefaultLayers()).concat([{
size: 1,
post: 'sigmoid'
}]));
this.noiseOptions = properties.noise || {};
}
generatorDefaultLayers() {
return [{
size: this.size.noise * 2 + 10,
post: 'sigmoid'
}, {
size: Math.ceil(this.size.noise * 1.5 + this.size.output) + 1,
post: 'sigmoid'
}];
}
discriminatorDefaultLayers() {
return [{
size: Math.ceil(this.size.noise * 2 + this.size.output) + 5,
post: 'sigmoid'
}, {
size: Math.ceil(this.size.output / 1.5) + 2,
post: 'sigmoid'
}];
}
rate(data) {
let input = Array.from(data.data || data).concat(new Array(Math.max(0, this.size.output - (data.data || data).length)).fill(0)).slice(0, this.size.output);
return this.discriminator.compute(new Vector(input)).data[0];
}
evolve(realData, options) {
realData = realData.map(rd => new Vector(rd));
let fake = [];
let _step = options.step;
let _postStep = options.postStep;
Object.assign(options, {
step: net => {
let f = this.generate(net);
fake.push(f);
if (_step != null) _step(net);
return this.rate(f);
},
postStep: net => {
this._evolveCycleDiscGrade(realData, fake, (options.discriminatorTrainOptions || {}).comparisonSize || 4, options.discriminatorTrainOptions || null);
net.fake = fake;
if (_postStep != null) _postStep(net);
}
});
return this.generator.evolve(options).then(newGenerator => {
return this.generator = newGenerator;
});
}
_evolveCycleDiscGrade(realData, fakeData, maxComparisonSize = 4, discriminatorOptions = null) {
maxComparisonSize = Math.min(maxComparisonSize, realData.length, fakeData.length);
realData = shuffle(realData, {
copy: true
}).slice(0, maxComparisonSize);
fakeData = shuffle(fakeData, {
copy: true
}).slice(0, maxComparisonSize);
let expec = new Array(realData.length).fill([1]);
let notExpec = new Array(realData.length).fill([0]);
let _opts = {
inputSet: realData.concat(fakeData),
expectedSet: expec.concat(notExpec),
maxGens: 15,
maxMutation: 0.3,
population: 30
};
Object.assign(_opts, discriminatorOptions);
return this.discriminator.evolve(_opts).then(disc => {
this.discriminator = disc;
});
}
makeNoise() {
return inputNoise(this.size.noise, this.noiseOptions);
}
generate(net = null) {
let noise = this.makeNoise();
let res = (net != null ? net : this.generator).compute(noise).data;
res.noise = noise;
return res;
}
};
greataptic.StaticMultiEvolver = class StaticMultiEvolver {
constructor(nets, processor) {
if (nets instanceof Map) this.nets = nets;else this.nets = new Map(Object.entries(nets));
this.processor = processor;
}
cloneNets() {
let res = new Map();
this.nets.forEach((net, name) => {
res.set(name, net.clone());
});
return res;
}
compute(input, netMap = this.nets) {
let nets = {};
netMap.forEach((net, name) => {
nets[name] = net;
});
return Promise.resolve(this.processor(nets, new Vector(input)));
}
error(nets, inputSet, expectedSet) {
return new Promise(resolve => {
let outputProms = inputSet.map(i => this.compute(i, nets));
Promise.all(outputProms).then(outputSet => {
let res = outputSet.map((os, si) => os.data.map((o, i) => Math.pow(o - expectedSet[si][i], 2)).reduce((a, b) => a + b, 0)).reduce((a, b) => a + b, 0);
resolve(res / inputSet.length);
});
});
}
staticFitness(nets, inputSet, expectedSet) {
return new Promise(resolve => {
this.error(nets, inputSet, expectedSet).then(errVal => {
resolve(1 / (1 + errVal));
});
});
}
getFitness(nets, inputSet, expectedSet) {
return this.staticFitness(nets, inputSet, expectedSet);
}
evolveStatic(inputSet, expectedSet, options = {}) {
let fitQuota = options.quota != null ? options.quota : Infinity;
let cur = this.cloneNets();
let gen = 1;
options.random = options.random || false;
return new Promise(resolve => {
let iter = () => {
let proms = [];
let evolved = new Map();
this.getFitness(cur, inputSet, expectedSet).then(oldFit => {
cur.forEach((net, name) => {
let thisIterOptions = {};
Object.assign(thisIterOptions, options);
Object.assign(thisIterOptions, {
step: stepNet => {
let oldNet = cur.get(name);
cur.set(name, stepNet);
this.getFitness(cur, inputSet, expectedSet).then(fit => {
cur.set(name, oldNet);
return fit;
});
}
});
proms.push(net.evolveStep(thisIterOptions).then(net => {
evolved.set(name, net);
}));
});
Promise.all(proms).then(() => {
this.getFitness(evolved, inputSet, expectedSet).then(newFit => {
options.random = false;
if (options.debug) // print debug status
console.log(`[DEBUG] Generation ${gen}: fitness is now ${newFit}.`);
if (newFit >= fitQuota || ++gen > (options.maxGens || 500)) {
this.nets = evolved;
resolve(evolved);
} else {
if (newFit > oldFit) cur = evolved;
process.nextTick = iter;
}
});
});
});
};
process.nextTick = iter;
});
}
};
module.exports = greataptic;
//# sourceMappingURL=index.js.map