UNPKG

greataptic

Version:

A simplistic neural network library.

1,245 lines (1,078 loc) 41.5 kB
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