├── .vscode
└── settings.json
├── README.md
├── assets
├── logo.png
└── logo.svg
├── examples
├── nn.ts
└── perceptron.ts
├── mod.ts
└── src
├── Connection.ts
├── Cost.ts
├── Layer.js
├── LayerConnection.ts
├── Network.js
├── Neuron.js
├── Squash.ts
├── Trainer.js
├── architect.ts
└── architectures
├── Hopfield.js
├── LSTM.js
├── Liquid.js
└── Perceptron.js
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "deno.enable": true,
3 | "deno.lint": true,
4 | "deno.unstable": true
5 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | dependency-less neural network library
9 | Synaptic.js ported to Deno
10 |
11 | ## Usage:
12 |
13 | ```ts
14 |
15 | import { Layer, Network, Trainer } from "./mod.ts";
16 |
17 | class Perceptron extends Network {
18 | public constructor(input: any, hidden: any, output: any) {
19 | super();
20 | let inputLayer = new Layer(input);
21 | let hiddenLayer = new Layer(hidden);
22 | let outputLayer = new Layer(output);
23 |
24 | inputLayer.project(hiddenLayer);
25 | hiddenLayer.project(outputLayer);
26 |
27 | this.set({
28 | input: inputLayer,
29 | hidden: [hiddenLayer],
30 | output: outputLayer,
31 | });
32 | }
33 | }
34 |
35 |
36 | let myPerceptron = new Perceptron(2,3,1);
37 |
38 | let myTrainer = new Trainer(myPerceptron);
39 |
40 | myTrainer.XOR();
41 |
42 | myPerceptron.activate([0,0]); // 0.0268581547421616
43 | myPerceptron.activate([1,0]); // 0.9829673642853368
44 | myPerceptron.activate([0,1]); // 0.9831714267395621
45 | myPerceptron.activate([1,1]); // 0.02128894618097928
46 | ```
47 |
48 | ```
49 | deno run -A ./test.ts
50 | ```
--------------------------------------------------------------------------------
/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/load1n9/synaptic/18b5007f36667063e13345ab09c7dad8839e1a93/assets/logo.png
--------------------------------------------------------------------------------
/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
166 |
--------------------------------------------------------------------------------
/examples/nn.ts:
--------------------------------------------------------------------------------
1 | import { Layer, Network } from "../mod.ts";
2 |
3 | const inputLayer: Layer = new Layer(2);
4 | const hiddenLayer: Layer = new Layer(3);
5 | const outputLayer: Layer = new Layer(1);
6 |
7 | inputLayer.project(hiddenLayer);
8 | hiddenLayer.project(outputLayer);
9 |
10 | const myNetwork: Network = new Network({
11 | input: inputLayer,
12 | hidden: [hiddenLayer],
13 | output: outputLayer
14 | });
15 |
16 | const learningRate = .3;
17 |
18 | for (let i:number = 0; i < 20000; i++) {
19 |
20 | myNetwork.activate([0,0]);
21 | myNetwork.propagate(learningRate, [0]);
22 |
23 | myNetwork.activate([0,1]);
24 | myNetwork.propagate(learningRate, [1]);
25 |
26 | myNetwork.activate([1,0]);
27 | myNetwork.propagate(learningRate, [1]);
28 |
29 | myNetwork.activate([1,1]);
30 | myNetwork.propagate(learningRate, [0]);
31 | }
32 |
33 | console.log(myNetwork.activate([0,0])); // [0.015020775950893527]
34 | console.log(myNetwork.activate([0,1])); // [0.9815816381088985]
35 | console.log(myNetwork.activate([1,0])); // [0.9871822457132193]
36 | console.log(myNetwork.activate([1,1])); // [0.012950087641929467]
37 |
--------------------------------------------------------------------------------
/examples/perceptron.ts:
--------------------------------------------------------------------------------
1 | import { Layer, Network, Trainer } from "../mod.ts";
2 |
3 | class Perceptron extends Network {
4 | public constructor(input: number, hidden: number, output: number) {
5 | super();
6 | const inputLayer = new Layer(input);
7 | const hiddenLayer = new Layer(hidden);
8 | const outputLayer = new Layer(output);
9 |
10 | inputLayer.project(hiddenLayer);
11 | hiddenLayer.project(outputLayer);
12 |
13 | this.set({
14 | input: inputLayer,
15 | hidden: [hiddenLayer],
16 | output: outputLayer,
17 | });
18 | }
19 | }
20 |
21 | const myPerceptron = new Perceptron(2, 3, 1);
22 |
23 | const myTrainer = new Trainer(myPerceptron);
24 |
25 | myTrainer.XOR();
26 |
27 | console.log(
28 | myPerceptron.activate([0, 0]), // 0.0268581547421616
29 | myPerceptron.activate([1, 0]), // 0.9829673642853368
30 | myPerceptron.activate([0, 1]), // 0.9831714267395621
31 | myPerceptron.activate([1, 1]), // 0.02128894618097928
32 | );
33 |
--------------------------------------------------------------------------------
/mod.ts:
--------------------------------------------------------------------------------
1 | export { Neuron } from "./src/Neuron.js";
2 | export { Layer } from "./src/Layer.js";
3 | export { Trainer } from "./src/Trainer.js";
4 | export { Network } from "./src/Network.js";
5 | import * as Architect from "./src/architect.ts";
6 | export { Architect };
7 |
--------------------------------------------------------------------------------
/src/Connection.ts:
--------------------------------------------------------------------------------
1 | export let connections = 0;
2 |
3 | export class Connection {
4 | public ID = Connection.uid();
5 | public gain = 1;
6 | public gater: any = null;
7 | public weight: any;
8 | public constructor(
9 | public from: any,
10 | public to: any,
11 | weight?: any,
12 | ) {
13 | this.weight = weight || Math.random() * .2 - .1;
14 | }
15 |
16 | static uid() {
17 | return connections++;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Cost.ts:
--------------------------------------------------------------------------------
1 | export const CROSS_ENTROPY = (target: number[], output: number[]) => {
2 | let crossentropy = 0;
3 | for (const i in output) {
4 | crossentropy -= (target[i] * Math.log(output[i] + 1e-15)) +
5 | ((1 - target[i]) * Math.log((1 + 1e-15) - output[i]));
6 | }
7 | return crossentropy;
8 | };
9 |
10 | export const MSE = (target: number[], output: number[]) => {
11 | let mse = 0;
12 | for (let i = 0; i < output.length; i++) {
13 | mse += Math.pow(target[i] - output[i], 2);
14 | }
15 | return mse / output.length;
16 | };
17 |
18 | export const BINARY = (target: number[], output: number[]) => {
19 | let misses = 0;
20 | for (let i = 0; i < output.length; i++) {
21 | misses += Number(Math.round(target[i] * 2) != Math.round(output[i] * 2));
22 | }
23 | return misses;
24 | };
25 |
--------------------------------------------------------------------------------
/src/Layer.js:
--------------------------------------------------------------------------------
1 | import { LayerConnection } from "./LayerConnection.ts";
2 | import { Neuron } from "./Neuron.js";
3 | import { Network } from "./Network.js";
4 |
5 | const connectionType = {
6 | ALL_TO_ALL: "ALL TO ALL",
7 | ONE_TO_ONE: "ONE TO ONE",
8 | ALL_TO_ELSE: "ALL TO ELSE",
9 | };
10 |
11 | const gateType = {
12 | INPUT: "INPUT",
13 | OUTPUT: "OUTPUT",
14 | ONE_TO_ONE: "ONE TO ONE",
15 | };
16 |
17 | export class Layer {
18 | static connectionType = connectionType;
19 | static gateType = gateType;
20 |
21 | constructor(size) {
22 | this.size = size | 0;
23 | this.list = [];
24 |
25 | this.connectedTo = [];
26 |
27 | while (size--) {
28 | let neuron = new Neuron();
29 | this.list.push(neuron);
30 | }
31 | }
32 |
33 | activate(input) {
34 | let activations = [];
35 |
36 | if (typeof input != "undefined") {
37 | if (input.length != this.size) {
38 | throw new Error(
39 | "INPUT size and LAYER size must be the same to activate!",
40 | );
41 | }
42 |
43 | for (let id in this.list) {
44 | let neuron = this.list[id];
45 | let activation = neuron.activate(input[id]);
46 | activations.push(activation);
47 | }
48 | } else {
49 | for (let id in this.list) {
50 | let neuron = this.list[id];
51 | let activation = neuron.activate();
52 | activations.push(activation);
53 | }
54 | }
55 | return activations;
56 | }
57 |
58 | propagate(rate, target) {
59 | if (typeof target != "undefined") {
60 | if (target.length != this.size) {
61 | throw new Error(
62 | "TARGET size and LAYER size must be the same to propagate!",
63 | );
64 | }
65 |
66 | for (let id = this.list.length - 1; id >= 0; id--) {
67 | let neuron = this.list[id];
68 | neuron.propagate(rate, target[id]);
69 | }
70 | } else {
71 | for (let id = this.list.length - 1; id >= 0; id--) {
72 | let neuron = this.list[id];
73 | neuron.propagate(rate);
74 | }
75 | }
76 | }
77 |
78 | project(layer, type, weights) {
79 | if (layer instanceof Network) {
80 | layer = layer.layers.input;
81 | }
82 |
83 | if (layer instanceof Layer) {
84 | if (!this.connected(layer)) {
85 | return new LayerConnection(this, layer, type, weights);
86 | }
87 | } else {
88 | throw new Error(
89 | "Invalid argument, you can only project connections to LAYERS and NETWORKS!",
90 | );
91 | }
92 | }
93 |
94 | gate(connection, type) {
95 | if (type == Layer.gateType.INPUT) {
96 | if (connection.to.size != this.size) {
97 | throw new Error(
98 | "GATER layer and CONNECTION.TO layer must be the same size in order to gate!",
99 | );
100 | }
101 |
102 | for (let id in connection.to.list) {
103 | let neuron = connection.to.list[id];
104 | let gater = this.list[id];
105 | for (let input in neuron.connections.inputs) {
106 | let gated = neuron.connections.inputs[input];
107 | if (gated.ID in connection.connections) {
108 | gater.gate(gated);
109 | }
110 | }
111 | }
112 | } else if (type == Layer.gateType.OUTPUT) {
113 | if (connection.from.size != this.size) {
114 | throw new Error(
115 | "GATER layer and CONNECTION.FROM layer must be the same size in order to gate!",
116 | );
117 | }
118 |
119 | for (var id in connection.from.list) {
120 | let neuron = connection.from.list[id];
121 | let gater = this.list[id];
122 | for (let projected in neuron.connections.projected) {
123 | let gated = neuron.connections.projected[projected];
124 | if (gated.ID in connection.connections) {
125 | gater.gate(gated);
126 | }
127 | }
128 | }
129 | } else if (type == Layer.gateType.ONE_TO_ONE) {
130 | if (connection.size != this.size) {
131 | throw new Error(
132 | "The number of GATER UNITS must be the same as the number of CONNECTIONS to gate!",
133 | );
134 | }
135 |
136 | for (let id in connection.list) {
137 | let gater = this.list[id];
138 | let gated = connection.list[id];
139 | gater.gate(gated);
140 | }
141 | }
142 | connection.gatedfrom.push({ layer: this, type: type });
143 | }
144 |
145 | selfconnected() {
146 | for (let id in this.list) {
147 | let neuron = this.list[id];
148 | if (!neuron.selfconnected()) {
149 | return false;
150 | }
151 | }
152 | return true;
153 | }
154 |
155 | connected(layer) {
156 | let connections = 0;
157 | for (let here in this.list) {
158 | for (let there in layer.list) {
159 | let from = this.list[here];
160 | let to = layer.list[there];
161 | let connected = from.connected(to);
162 | if (connected.type == "projected") {
163 | connections++;
164 | }
165 | }
166 | }
167 | if (connections == this.size * layer.size) {
168 | return Layer.connectionType.ALL_TO_ALL;
169 | }
170 |
171 | connections = 0;
172 | for (let neuron in this.list) {
173 | let from = this.list[neuron];
174 | let to = layer.list[neuron];
175 | let connected = from.connected(to);
176 | if (connected.type == "projected") {
177 | connections++;
178 | }
179 | }
180 | if (connections == this.size) {
181 | return Layer.connectionType.ONE_TO_ONE;
182 | }
183 | }
184 |
185 | clear() {
186 | for (let id in this.list) {
187 | let neuron = this.list[id];
188 | neuron.clear();
189 | }
190 | }
191 |
192 | reset() {
193 | for (let id in this.list) {
194 | let neuron = this.list[id];
195 | neuron.reset();
196 | }
197 | }
198 |
199 | neurons() {
200 | return this.list;
201 | }
202 |
203 | add(neuron) {
204 | neuron = neuron || new Neuron();
205 | this.list.push(neuron);
206 | this.size++;
207 | }
208 |
209 | set(options) {
210 | options = options || {};
211 |
212 | for (let i in this.list) {
213 | let neuron = this.list[i];
214 | if (options.label) {
215 | neuron.label = options.label + "_" + neuron.ID;
216 | }
217 | if (options.squash) {
218 | neuron.squash = options.squash;
219 | }
220 | if (options.bias) {
221 | neuron.bias = options.bias;
222 | }
223 | }
224 | return this;
225 | }
226 | }
227 |
--------------------------------------------------------------------------------
/src/LayerConnection.ts:
--------------------------------------------------------------------------------
1 | import { Layer } from "./Layer.js";
2 |
3 | export let connections = 0;
4 |
5 | export class LayerConnection {
6 | public ID: number;
7 | public from: any;
8 | public to: any;
9 | public selfconnection: any;
10 | public connections: any;
11 | public list: any;
12 | public size: number;
13 | public gatedfrom: any;
14 |
15 | public constructor(
16 | fromLayer: any,
17 | toLayer: any,
18 | public type?: any,
19 | weights?: any,
20 | ) {
21 | this.ID = LayerConnection.uid();
22 | this.from = fromLayer;
23 | this.to = toLayer;
24 | this.selfconnection = toLayer == fromLayer;
25 | this.connections = {};
26 | this.list = [];
27 | this.size = 0;
28 | this.gatedfrom = [];
29 |
30 | if (typeof this.type == "undefined") {
31 | if (fromLayer == toLayer) {
32 | this.type = Layer.connectionType.ONE_TO_ONE;
33 | } else {
34 | this.type = Layer.connectionType.ALL_TO_ALL;
35 | }
36 | }
37 |
38 | if (
39 | this.type == Layer.connectionType.ALL_TO_ALL ||
40 | this.type == Layer.connectionType.ALL_TO_ELSE
41 | ) {
42 | for (let here in this.from.list) {
43 | for (let there in this.to.list) {
44 | let from = this.from.list[here];
45 | let to = this.to.list[there];
46 | if (this.type == Layer.connectionType.ALL_TO_ELSE && from == to) {
47 | continue;
48 | }
49 | let connection = from.project(to, weights);
50 |
51 | this.connections[connection.ID] = connection;
52 | this.size = this.list.push(connection);
53 | }
54 | }
55 | } else if (this.type == Layer.connectionType.ONE_TO_ONE) {
56 | for (let neuron in this.from.list) {
57 | let from = this.from.list[neuron];
58 | let to = this.to.list[neuron];
59 | let connection = from.project(to, weights);
60 |
61 | this.connections[connection.ID] = connection;
62 | this.size = this.list.push(connection);
63 | }
64 | }
65 |
66 | fromLayer.connectedTo.push(this);
67 | }
68 |
69 | static uid() {
70 | return connections++;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Network.js:
--------------------------------------------------------------------------------
1 | import { Neuron } from "./Neuron.js";
2 | import { Layer } from "./Layer.js";
3 | import { Trainer } from "./Trainer.js";
4 |
5 | export class Network {
6 | constructor(layers) {
7 | if (typeof layers != "undefined") {
8 | this.layers = {
9 | input: layers.input || null,
10 | hidden: layers.hidden || [],
11 | output: layers.output || null,
12 | };
13 | this.optimized = null;
14 | }
15 | }
16 |
17 | activate(input) {
18 | if (this.optimized === false) {
19 | this.layers.input.activate(input);
20 | for (var i = 0; i < this.layers.hidden.length; i++) {
21 | this.layers.hidden[i].activate();
22 | }
23 | return this.layers.output.activate();
24 | } else {
25 | if (this.optimized == null) {
26 | this.optimize();
27 | }
28 | return this.optimized.activate(input);
29 | }
30 | }
31 |
32 | propagate(rate, target) {
33 | if (this.optimized === false) {
34 | this.layers.output.propagate(rate, target);
35 | for (let i = this.layers.hidden.length - 1; i >= 0; i--) {
36 | this.layers.hidden[i].propagate(rate);
37 | }
38 | } else {
39 | if (this.optimized == null) {
40 | this.optimize();
41 | }
42 | this.optimized.propagate(rate, target);
43 | }
44 | }
45 |
46 | project(unit, type, weights) {
47 | if (this.optimized) {
48 | this.optimized.reset();
49 | }
50 |
51 | if (unit instanceof Network) {
52 | return this.layers.output.project(unit.layers.input, type, weights);
53 | }
54 |
55 | if (unit instanceof Layer) {
56 | return this.layers.output.project(unit, type, weights);
57 | }
58 |
59 | throw new Error(
60 | "Invalid argument, you can only project connections to LAYERS and NETWORKS!",
61 | );
62 | }
63 |
64 | gate(connection, type) {
65 | if (this.optimized) {
66 | this.optimized.reset();
67 | }
68 | this.layers.output.gate(connection, type);
69 | }
70 |
71 | clear() {
72 | this.restore();
73 |
74 | let inputLayer = this.layers.input,
75 | outputLayer = this.layers.output;
76 |
77 | inputLayer.clear();
78 | for (let i = 0; i < this.layers.hidden.length; i++) {
79 | this.layers.hidden[i].clear();
80 | }
81 | outputLayer.clear();
82 |
83 | if (this.optimized) {
84 | this.optimized.reset();
85 | }
86 | }
87 |
88 | reset() {
89 | this.restore();
90 |
91 | let inputLayer = this.layers.input,
92 | outputLayer = this.layers.output;
93 |
94 | inputLayer.reset();
95 | for (let i = 0; i < this.layers.hidden.length; i++) {
96 | this.layers.hidden[i].reset();
97 | }
98 | outputLayer.reset();
99 |
100 | if (this.optimized) {
101 | this.optimized.reset();
102 | }
103 | }
104 |
105 | optimize() {
106 | let that = this;
107 | let optimized = {};
108 | let neurons = this.neurons();
109 |
110 | for (let i = 0; i < neurons.length; i++) {
111 | let neuron = neurons[i].neuron;
112 | let layer = neurons[i].layer;
113 | while (neuron.neuron) {
114 | neuron = neuron.neuron;
115 | }
116 | optimized = neuron.optimize(optimized, layer);
117 | }
118 |
119 | for (let i = 0; i < optimized.propagation_sentences.length; i++) {
120 | optimized.propagation_sentences[i].reverse();
121 | }
122 | optimized.propagation_sentences.reverse();
123 |
124 | let hardcode = "";
125 | hardcode += "var F = Float64Array ? new Float64Array(" + optimized.memory +
126 | ") : []; ";
127 | for (let i in optimized.variables) {
128 | hardcode += "F[" + optimized.variables[i].id + "] = " +
129 | (optimized.variables[
130 | i
131 | ].value || 0) + "; ";
132 | }
133 | hardcode += "var activate = function(input){\n";
134 | for (let i = 0; i < optimized.inputs.length; i++) {
135 | hardcode += "F[" + optimized.inputs[i] + "] = input[" + i + "]; ";
136 | }
137 | for (let i = 0; i < optimized.activation_sentences.length; i++) {
138 | if (optimized.activation_sentences[i].length > 0) {
139 | for (let j = 0; j < optimized.activation_sentences[i].length; j++) {
140 | hardcode += optimized.activation_sentences[i][j].join(" ");
141 | hardcode += optimized.trace_sentences[i][j].join(" ");
142 | }
143 | }
144 | }
145 | hardcode += " var output = []; ";
146 | for (let i = 0; i < optimized.outputs.length; i++) {
147 | hardcode += "output[" + i + "] = F[" + optimized.outputs[i] + "]; ";
148 | }
149 | hardcode += "return output; }; ";
150 | hardcode += "var propagate = function(rate, target){\n";
151 | hardcode += "F[" + optimized.variables.rate.id + "] = rate; ";
152 | for (let i = 0; i < optimized.targets.length; i++) {
153 | hardcode += "F[" + optimized.targets[i] + "] = target[" + i + "]; ";
154 | }
155 | for (let i = 0; i < optimized.propagation_sentences.length; i++) {
156 | for (let j = 0; j < optimized.propagation_sentences[i].length; j++) {
157 | hardcode += optimized.propagation_sentences[i][j].join(" ") + " ";
158 | }
159 | }
160 | hardcode += " };\n";
161 | hardcode +=
162 | "var ownership = function(memoryBuffer){\nF = memoryBuffer;\nthis.memory = F;\n};\n";
163 | hardcode +=
164 | "return {\nmemory: F,\nactivate: activate,\npropagate: propagate,\nownership: ownership\n};";
165 | hardcode = hardcode.split(";").join(";\n");
166 |
167 | let constructor = new Function(hardcode);
168 |
169 | let network = constructor();
170 | network.data = {
171 | variables: optimized.variables,
172 | activate: optimized.activation_sentences,
173 | propagate: optimized.propagation_sentences,
174 | trace: optimized.trace_sentences,
175 | inputs: optimized.inputs,
176 | outputs: optimized.outputs,
177 | check_activation: this.activate,
178 | check_propagation: this.propagate,
179 | };
180 |
181 | network.reset = () => {
182 | if (that.optimized) {
183 | that.optimized = null;
184 | that.activate = network.data.check_activation;
185 | that.propagate = network.data.check_propagation;
186 | }
187 | };
188 |
189 | this.optimized = network;
190 | this.activate = network.activate;
191 | this.propagate = network.propagate;
192 | }
193 |
194 | restore() {
195 | if (!this.optimized) {
196 | return;
197 | }
198 |
199 | let optimized = this.optimized;
200 |
201 | let getValue = () => {
202 | let args = Array.prototype.slice.call(arguments);
203 |
204 | let unit = args.shift();
205 | let prop = args.pop();
206 |
207 | let id = prop + "_";
208 | for (let property in args) {
209 | id += args[property] + "_";
210 | }
211 | id += unit.ID;
212 |
213 | let memory = optimized.memory;
214 | let variables = optimized.data.variables;
215 |
216 | if (id in variables) {
217 | return memory[variables[id].id];
218 | }
219 | return 0;
220 | };
221 |
222 | let list = this.neurons();
223 |
224 | for (let i = 0; i < list.length; i++) {
225 | let neuron = list[i].neuron;
226 | while (neuron.neuron) {
227 | neuron = neuron.neuron;
228 | }
229 |
230 | neuron.state = getValue(neuron, "state");
231 | neuron.old = getValue(neuron, "old");
232 | neuron.activation = getValue(neuron, "activation");
233 | neuron.bias = getValue(neuron, "bias");
234 |
235 | for (let input in neuron.trace.elegibility) {
236 | neuron.trace.elegibility[input] = getValue(
237 | neuron,
238 | "trace",
239 | "elegibility",
240 | input,
241 | );
242 | }
243 |
244 | for (let gated in neuron.trace.extended) {
245 | for (let input in neuron.trace.extended[gated]) {
246 | neuron.trace.extended[gated][input] = getValue(
247 | neuron,
248 | "trace",
249 | "extended",
250 | gated,
251 | input,
252 | );
253 | }
254 | }
255 |
256 | for (let j in neuron.connections.projected) {
257 | let connection = neuron.connections.projected[j];
258 | connection.weight = getValue(connection, "weight");
259 | connection.gain = getValue(connection, "gain");
260 | }
261 | }
262 | }
263 |
264 | neurons() {
265 | let neurons = [];
266 |
267 | let inputLayer = this.layers.input.neurons(),
268 | outputLayer = this.layers.output.neurons();
269 |
270 | for (let i = 0; i < inputLayer.length; i++) {
271 | neurons.push({
272 | neuron: inputLayer[i],
273 | layer: "input",
274 | });
275 | }
276 |
277 | for (let i = 0; i < this.layers.hidden.length; i++) {
278 | let hiddenLayer = this.layers.hidden[i].neurons();
279 | for (let j = 0; j < hiddenLayer.length; j++) {
280 | neurons.push({
281 | neuron: hiddenLayer[j],
282 | layer: i,
283 | });
284 | }
285 | }
286 |
287 | for (let i = 0; i < outputLayer.length; i++) {
288 | neurons.push({
289 | neuron: outputLayer[i],
290 | layer: "output",
291 | });
292 | }
293 |
294 | return neurons;
295 | }
296 |
297 | inputs() {
298 | return this.layers.input.size;
299 | }
300 |
301 | outputs() {
302 | return this.layers.output.size;
303 | }
304 |
305 | set(layers) {
306 | this.layers = {
307 | input: layers.input || null,
308 | hidden: layers.hidden || [],
309 | output: layers.output || null,
310 | };
311 | if (this.optimized) {
312 | this.optimized.reset();
313 | }
314 | }
315 |
316 | setOptimize(bool) {
317 | this.restore();
318 | if (this.optimized) {
319 | this.optimized.reset();
320 | }
321 | this.optimized = bool ? null : false;
322 | }
323 |
324 | toJSON(ignoreTraces) {
325 | this.restore();
326 |
327 | let list = this.neurons();
328 | let neurons = [];
329 | let connections = [];
330 |
331 | let ids = {};
332 | for (let i = 0; i < list.length; i++) {
333 | let neuron = list[i].neuron;
334 | while (neuron.neuron) {
335 | neuron = neuron.neuron;
336 | }
337 | ids[neuron.ID] = i;
338 |
339 | let copy = {
340 | trace: {
341 | elegibility: {},
342 | extended: {},
343 | },
344 | state: neuron.state,
345 | old: neuron.old,
346 | activation: neuron.activation,
347 | bias: neuron.bias,
348 | layer: list[i].layer,
349 | };
350 |
351 | copy.squash = neuron.squash == Neuron.squash.LOGISTIC
352 | ? "LOGISTIC"
353 | : neuron.squash == Neuron.squash.TANH
354 | ? "TANH"
355 | : neuron.squash == Neuron.squash.IDENTITY
356 | ? "IDENTITY"
357 | : neuron.squash == Neuron.squash.HLIM
358 | ? "HLIM"
359 | : neuron.squash == Neuron.squash.RELU
360 | ? "RELU"
361 | : null;
362 |
363 | neurons.push(copy);
364 | }
365 |
366 | for (let i = 0; i < list.length; i++) {
367 | let neuron = list[i].neuron;
368 | while (neuron.neuron) {
369 | neuron = neuron.neuron;
370 | }
371 |
372 | for (let j in neuron.connections.projected) {
373 | let connection = neuron.connections.projected[j];
374 | connections.push({
375 | from: ids[connection.from.ID],
376 | to: ids[connection.to.ID],
377 | weight: connection.weight,
378 | gater: connection.gater ? ids[connection.gater.ID] : null,
379 | });
380 | }
381 | if (neuron.selfconnected()) {
382 | connections.push({
383 | from: ids[neuron.ID],
384 | to: ids[neuron.ID],
385 | weight: neuron.selfconnection.weight,
386 | gater: neuron.selfconnection.gater
387 | ? ids[neuron.selfconnection.gater.ID]
388 | : null,
389 | });
390 | }
391 | }
392 |
393 | return {
394 | neurons: neurons,
395 | connections: connections,
396 | };
397 | }
398 |
399 | toDot(edgeConnection) {
400 | if (!typeof edgeConnection) {
401 | edgeConnection = false;
402 | }
403 | let code = "digraph nn {\n rankdir = BT\n";
404 | let layers = [this.layers.input].concat(
405 | this.layers.hidden,
406 | this.layers.output,
407 | );
408 | for (let i = 0; i < layers.length; i++) {
409 | for (let j = 0; j < layers[i].connectedTo.length; j++) {
410 | let connection = layers[i].connectedTo[j];
411 | let layerTo = connection.to;
412 | let size = connection.size;
413 | let layerID = layers.indexOf(layers[i]);
414 | let layerToID = layers.indexOf(layerTo);
415 | if (edgeConnection) {
416 | if (connection.gatedfrom.length) {
417 | let fakeNode = "fake" + layerID + "_" + layerToID;
418 | code += " " + fakeNode +
419 | ' [label = "", shape = point, width = 0.01, height = 0.01]\n';
420 | code += " " + layerID + " -> " + fakeNode + " [label = " + size +
421 | ", arrowhead = none]\n";
422 | code += " " + fakeNode + " -> " + layerToID + "\n";
423 | } else {
424 | code += " " + layerID + " -> " + layerToID + " [label = " +
425 | size + "]\n";
426 | }
427 | for (let from in connection.gatedfrom) { // gatings
428 | let layerfrom = connection.gatedfrom[from].layer;
429 | let layerfromID = layers.indexOf(layerfrom);
430 | code += " " + layerfromID + " -> " + fakeNode +
431 | " [color = blue]\n";
432 | }
433 | } else {
434 | code += " " + layerID + " -> " + layerToID + " [label = " + size +
435 | "]\n";
436 | for (let from in connection.gatedfrom) { // gatings
437 | let layerfrom = connection.gatedfrom[from].layer;
438 | let layerfromID = layers.indexOf(layerfrom);
439 | code += " " + layerfromID + " -> " + layerToID +
440 | " [color = blue]\n";
441 | }
442 | }
443 | }
444 | }
445 | code += "}\n";
446 | return {
447 | code: code,
448 | link: "https://chart.googleapis.com/chart?chl=" +
449 | escape(code.replace("/ /g", "+")) + "&cht=gv",
450 | };
451 | }
452 |
453 | standalone() {
454 | if (!this.optimized) {
455 | this.optimize();
456 | }
457 |
458 | let data = this.optimized.data;
459 |
460 | let activation = "function (input) {\n";
461 |
462 | for (let i = 0; i < data.inputs.length; i++) {
463 | activation += "F[" + data.inputs[i] + "] = input[" + i + "];\n";
464 | }
465 |
466 | for (let i = 0; i < data.activate.length; i++) {
467 | for (let j = 0; j < data.activate[i].length; j++) {
468 | activation += data.activate[i][j].join("") + "\n";
469 | }
470 | }
471 |
472 | activation += "var output = [];\n";
473 | for (let i = 0; i < data.outputs.length; i++) {
474 | activation += "output[" + i + "] = F[" + data.outputs[i] + "];\n";
475 | }
476 | activation += "return output;\n}";
477 |
478 | let memory = activation.match(/F\[(\d+)\]/g);
479 | let dimension = 0;
480 | let ids = {};
481 |
482 | for (let i = 0; i < memory.length; i++) {
483 | let tmp = memory[i].match(/\d+/)[0];
484 | if (!(tmp in ids)) {
485 | ids[tmp] = dimension++;
486 | }
487 | }
488 | let hardcode = "F = {\n";
489 |
490 | for (let i in ids) {
491 | hardcode += ids[i] + ": " + this.optimized.memory[i] + ",\n";
492 | }
493 | hardcode = hardcode.substring(0, hardcode.length - 2) + "\n};\n";
494 | hardcode = "var run = " +
495 | activation.replace(/F\[(\d+)]/g, function (index) {
496 | return "F[" + ids[index.match(/\d+/)[0]] + "]";
497 | }).replace("{\n", "{\n" + hardcode + "") + ";\n";
498 | hardcode += "return run";
499 |
500 | return new Function(hardcode)();
501 | }
502 |
503 | worker(memory, set, options) {
504 | let workerOptions = {};
505 | if (options) workerOptions = options;
506 | workerOptions.rate = workerOptions.rate || .2;
507 | workerOptions.iterations = workerOptions.iterations || 100000;
508 | workerOptions.error = workerOptions.error || .005;
509 | workerOptions.cost = workerOptions.cost || null;
510 | workerOptions.crossValidate = workerOptions.crossValidate || null;
511 |
512 | let costFunction = "// REPLACED BY WORKER\nvar cost = " +
513 | (options && options.cost || this.cost || Trainer.cost.MSE) + ";\n";
514 | let workerFunction = Network.getWorkerSharedFunctions();
515 | workerFunction = workerFunction.replace(
516 | /var cost = options && options\.cost \|\| this\.cost \|\| Trainer\.cost\.MSE;/g,
517 | costFunction,
518 | );
519 |
520 | workerFunction = workerFunction.replace(
521 | "return results;",
522 | 'postMessage({action: "done", message: results, memoryBuffer: F}, [F.buffer]);',
523 | );
524 |
525 | workerFunction = workerFunction.replace(
526 | "console.log('iterations', iterations, 'error', error, 'rate', currentRate)",
527 | "postMessage({action: 'log', message: {\n" +
528 | "iterations: iterations,\n" +
529 | "error: error,\n" +
530 | "rate: currentRate\n" +
531 | "}\n" +
532 | "})",
533 | );
534 |
535 | workerFunction = workerFunction.replace(
536 | "abort = this.schedule.do({ error: error, iterations: iterations, rate: currentRate })",
537 | "postMessage({action: 'schedule', message: {\n" +
538 | "iterations: iterations,\n" +
539 | "error: error,\n" +
540 | "rate: currentRate\n" +
541 | "}\n" +
542 | "})",
543 | );
544 |
545 | if (!this.optimized) {
546 | this.optimize();
547 | }
548 |
549 | let hardcode = "var inputs = " + this.optimized.data.inputs.length + ";\n";
550 | hardcode += "var outputs = " + this.optimized.data.outputs.length + ";\n";
551 | hardcode += "var F = new Float64Array([" +
552 | this.optimized.memory.toString() + "]);\n";
553 | hardcode += "var activate = " + this.optimized.activate.toString() + ";\n";
554 | hardcode += "var propagate = " + this.optimized.propagate.toString() +
555 | ";\n";
556 | hardcode += "onmessage = function(e) {\n" +
557 | "if (e.data.action == 'startTraining') {\n" +
558 | "train(" + JSON.stringify(set) + "," + JSON.stringify(workerOptions) +
559 | ");\n" +
560 | "}\n" +
561 | "}";
562 |
563 | let workerSourceCode = workerFunction + "\n" + hardcode;
564 | let blob = new Blob([workerSourceCode]);
565 | let blobURL = window.URL.createObjectURL(blob);
566 |
567 | return new Worker(blobURL);
568 | }
569 |
570 | clone() {
571 | return Network.fromJSON(this.toJSON());
572 | }
573 |
574 | static getWorkerSharedFunctions() {
575 | if (typeof Network._SHARED_WORKER_FUNCTIONS !== "undefined") {
576 | return Network._SHARED_WORKER_FUNCTIONS;
577 | }
578 | let train_f = Trainer.prototype.train.toString();
579 | train_f = train_f.replace(/this._trainSet/g, "_trainSet");
580 | train_f = train_f.replace(/this.test/g, "test");
581 | train_f = train_f.replace(/this.crossValidate/g, "crossValidate");
582 | train_f = train_f.replace("crossValidate = true", "// REMOVED BY WORKER");
583 |
584 | let _trainSet_f = Trainer.prototype._trainSet.toString().replace(
585 | /this.network./g,
586 | "",
587 | );
588 |
589 | let test_f = Trainer.prototype.test.toString().replace(
590 | /this.network./g,
591 | "",
592 | );
593 |
594 | return Network._SHARED_WORKER_FUNCTIONS = train_f + "\n" + _trainSet_f +
595 | "\n" + test_f;
596 | }
597 |
598 | static fromJSON(json) {
599 | let neurons = [];
600 |
601 | let layers = {
602 | input: new Layer(),
603 | hidden: [],
604 | output: new Layer(),
605 | };
606 |
607 | for (let i = 0; i < json.neurons.length; i++) {
608 | let config = json.neurons[i];
609 |
610 | let neuron = new Neuron();
611 | neuron.trace.elegibility = {};
612 | neuron.trace.extended = {};
613 | neuron.state = config.state;
614 | neuron.old = config.old;
615 | neuron.activation = config.activation;
616 | neuron.bias = config.bias;
617 | neuron.squash = config.squash in Neuron.squash
618 | ? Neuron.squash[config.squash]
619 | : Neuron.squash.LOGISTIC;
620 | neurons.push(neuron);
621 |
622 | if (config.layer == "input") {
623 | layers.input.add(neuron);
624 | } else if (config.layer == "output") {
625 | layers.output.add(neuron);
626 | } else {
627 | if (typeof layers.hidden[config.layer] == "undefined") {
628 | layers.hidden[config.layer] = new Layer();
629 | }
630 | layers.hidden[config.layer].add(neuron);
631 | }
632 | }
633 |
634 | for (let i = 0; i < json.connections.length; i++) {
635 | let config = json.connections[i];
636 | let from = neurons[config.from];
637 | let to = neurons[config.to];
638 | let weight = config.weight;
639 | let gater = neurons[config.gater];
640 |
641 | let connection = from.project(to, weight);
642 | if (gater) {
643 | gater.gate(connection);
644 | }
645 | }
646 |
647 | return new Network(layers);
648 | }
649 | }
650 |
--------------------------------------------------------------------------------
/src/Neuron.js:
--------------------------------------------------------------------------------
1 | import { Connection, connections } from './Connection.ts';
2 | import * as squash from './Squash.ts';
3 | let neurons = 0;
4 |
5 |
6 |
7 | export class Neuron {
8 | static squash = squash;
9 |
10 | constructor() {
11 | this.ID = Neuron.uid();
12 |
13 | this.connections = {
14 | inputs: {},
15 | projected: {},
16 | gated: {}
17 | };
18 | this.error = {
19 | responsibility: 0,
20 | projected: 0,
21 | gated: 0
22 | };
23 | this.trace = {
24 | elegibility: {},
25 | extended: {},
26 | influences: {}
27 | };
28 | this.state = 0;
29 | this.old = 0;
30 | this.activation = 0;
31 | this.selfconnection = new Connection(this, this, 0);
32 | this.squash = Neuron.squash.LOGISTIC;
33 | this.neighboors = {};
34 | this.bias = Math.random() * .2 - .1;
35 | }
36 |
37 | activate(input) {
38 | if (typeof input != 'undefined') {
39 | this.activation = input;
40 | this.derivative = 0;
41 | this.bias = 0;
42 | return this.activation;
43 | }
44 |
45 | this.old = this.state;
46 |
47 | this.state = this.selfconnection.gain * this.selfconnection.weight *
48 | this.state + this.bias;
49 |
50 | for (var i in this.connections.inputs) {
51 | var input = this.connections.inputs[i];
52 | this.state += input.from.activation * input.weight * input.gain;
53 | }
54 |
55 | this.activation = this.squash(this.state);
56 |
57 | this.derivative = this.squash(this.state, true);
58 |
59 | var influences = [];
60 | for (var id in this.trace.extended) {
61 | var neuron = this.neighboors[id];
62 |
63 | var influence = neuron.selfconnection.gater == this ? neuron.old : 0;
64 |
65 | for (var incoming in this.trace.influences[neuron.ID]) {
66 | influence += this.trace.influences[neuron.ID][incoming].weight *
67 | this.trace.influences[neuron.ID][incoming].from.activation;
68 | }
69 | influences[neuron.ID] = influence;
70 | }
71 |
72 | for (var i in this.connections.inputs) {
73 | var input = this.connections.inputs[i];
74 |
75 | this.trace.elegibility[input.ID] = this.selfconnection.gain * this.selfconnection
76 | .weight * this.trace.elegibility[input.ID] + input.gain * input.from
77 | .activation;
78 |
79 | for (var id in this.trace.extended) {
80 | var xtrace = this.trace.extended[id];
81 | var neuron = this.neighboors[id];
82 | var influence = influences[neuron.ID];
83 |
84 | xtrace[input.ID] = neuron.selfconnection.gain * neuron.selfconnection
85 | .weight * xtrace[input.ID] + this.derivative * this.trace.elegibility[
86 | input.ID] * influence;
87 | }
88 | }
89 |
90 | for (var connection in this.connections.gated) {
91 | this.connections.gated[connection].gain = this.activation;
92 | }
93 |
94 | return this.activation;
95 | }
96 |
97 | propagate(rate, target) {
98 | var error = 0;
99 |
100 | var isOutput = typeof target != 'undefined';
101 |
102 | if (isOutput)
103 | this.error.responsibility = this.error.projected = target - this.activation;
104 |
105 | else {
106 | for (var id in this.connections.projected) {
107 | var connection = this.connections.projected[id];
108 | var neuron = connection.to;
109 | error += neuron.error.responsibility * connection.gain * connection.weight;
110 | }
111 |
112 | this.error.projected = this.derivative * error;
113 |
114 | error = 0;
115 | for (var id in this.trace.extended) {
116 | var neuron = this.neighboors[id];
117 | var influence = neuron.selfconnection.gater == this ? neuron.old : 0;
118 |
119 | for (var input in this.trace.influences[id]) {
120 | influence += this.trace.influences[id][input].weight * this.trace.influences[
121 | neuron.ID][input].from.activation;
122 | }
123 | error += neuron.error.responsibility * influence;
124 | }
125 |
126 | this.error.gated = this.derivative * error;
127 |
128 | this.error.responsibility = this.error.projected + this.error.gated;
129 | }
130 |
131 | rate = rate || .1;
132 |
133 | for (var id in this.connections.inputs) {
134 | var input = this.connections.inputs[id];
135 |
136 | var gradient = this.error.projected * this.trace.elegibility[input.ID];
137 | for (var id in this.trace.extended) {
138 | var neuron = this.neighboors[id];
139 | gradient += neuron.error.responsibility * this.trace.extended[
140 | neuron.ID][input.ID];
141 | }
142 | input.weight += rate * gradient;
143 | }
144 |
145 | this.bias += rate * this.error.responsibility;
146 | }
147 |
148 | project(neuron, weight) {
149 | if (neuron == this) {
150 | this.selfconnection.weight = 1;
151 | return this.selfconnection;
152 | }
153 |
154 | var connected = this.connected(neuron);
155 | if (connected && connected.type == 'projected') {
156 | if (typeof weight != 'undefined')
157 | connected.connection.weight = weight;
158 | return connected.connection;
159 | } else {
160 | var connection = new Connection(this, neuron, weight);
161 | }
162 |
163 | this.connections.projected[connection.ID] = connection;
164 | this.neighboors[neuron.ID] = neuron;
165 | neuron.connections.inputs[connection.ID] = connection;
166 | neuron.trace.elegibility[connection.ID] = 0;
167 |
168 | for (var id in neuron.trace.extended) {
169 | var trace = neuron.trace.extended[id];
170 | trace[connection.ID] = 0;
171 | }
172 |
173 | return connection;
174 | }
175 |
176 | gate(connection) {
177 | this.connections.gated[connection.ID] = connection;
178 |
179 | var neuron = connection.to;
180 | if (!(neuron.ID in this.trace.extended)) {
181 | this.neighboors[neuron.ID] = neuron;
182 | var xtrace = this.trace.extended[neuron.ID] = {};
183 | for (var id in this.connections.inputs) {
184 | var input = this.connections.inputs[id];
185 | xtrace[input.ID] = 0;
186 | }
187 | }
188 |
189 | if (neuron.ID in this.trace.influences)
190 | this.trace.influences[neuron.ID].push(connection);
191 | else
192 | this.trace.influences[neuron.ID] = [connection];
193 |
194 | connection.gater = this;
195 | }
196 |
197 | selfconnected() {
198 | return this.selfconnection.weight !== 0;
199 | }
200 |
201 | connected(neuron) {
202 | var result = {
203 | type: null,
204 | connection: false
205 | };
206 |
207 | if (this == neuron) {
208 | if (this.selfconnected()) {
209 | result.type = 'selfconnection';
210 | result.connection = this.selfconnection;
211 | return result;
212 | } else
213 | return false;
214 | }
215 |
216 | for (var type in this.connections) {
217 | for (var connection in this.connections[type]) {
218 | var connection = this.connections[type][connection];
219 | if (connection.to == neuron) {
220 | result.type = type;
221 | result.connection = connection;
222 | return result;
223 | } else if (connection.from == neuron) {
224 | result.type = type;
225 | result.connection = connection;
226 | return result;
227 | }
228 | }
229 | }
230 |
231 | return false;
232 | }
233 |
234 | clear() {
235 | for (const trace in this.trace.elegibility) {
236 | this.trace.elegibility[trace] = 0;
237 | }
238 |
239 | for (const trace in this.trace.extended) {
240 | for (const extended in this.trace.extended[trace]) {
241 | this.trace.extended[trace][extended] = 0;
242 | }
243 | }
244 |
245 | this.error.responsibility = this.error.projected = this.error.gated = 0;
246 | }
247 |
248 | reset() {
249 | this.clear();
250 |
251 | for (var type in this.connections) {
252 | for (var connection in this.connections[type]) {
253 | this.connections[type][connection].weight = Math.random() * .2 - .1;
254 | }
255 | }
256 |
257 | this.bias = Math.random() * .2 - .1;
258 | this.old = this.state = this.activation = 0;
259 | }
260 |
261 | optimize(optimized, layer) {
262 |
263 | optimized = optimized || {};
264 | var store_activation = [];
265 | var store_trace = [];
266 | var store_propagation = [];
267 | var varID = optimized.memory || 0;
268 | var neurons = optimized.neurons || 1;
269 | var inputs = optimized.inputs || [];
270 | var targets = optimized.targets || [];
271 | var outputs = optimized.outputs || [];
272 | var variables = optimized.variables || {};
273 | var activation_sentences = optimized.activation_sentences || [];
274 | var trace_sentences = optimized.trace_sentences || [];
275 | var propagation_sentences = optimized.propagation_sentences || [];
276 | var layers = optimized.layers || { __count: 0, __neuron: 0 };
277 |
278 | var allocate = function (store) {
279 | var allocated = layer in layers && store[layers.__count];
280 | if (!allocated) {
281 | layers.__count = store.push([]) - 1;
282 | layers[layer] = layers.__count;
283 | }
284 | };
285 | allocate(activation_sentences);
286 | allocate(trace_sentences);
287 | allocate(propagation_sentences);
288 | var currentLayer = layers.__count;
289 |
290 | var getVar = function () {
291 | var args = Array.prototype.slice.call(arguments);
292 |
293 | if (args.length == 1) {
294 | if (args[0] == 'target') {
295 | var id = 'target_' + targets.length;
296 | targets.push(varID);
297 | } else
298 | var id = args[0];
299 | if (id in variables)
300 | return variables[id];
301 | return variables[id] = {
302 | value: 0,
303 | id: varID++
304 | };
305 | } else {
306 | var extended = args.length > 2;
307 | if (extended)
308 | var value = args.pop();
309 |
310 | var unit = args.shift();
311 | var prop = args.pop();
312 |
313 | if (!extended)
314 | var value = unit[prop];
315 |
316 | var id = prop + '_';
317 | for (var i = 0; i < args.length; i++)
318 | id += args[i] + '_';
319 | id += unit.ID;
320 | if (id in variables)
321 | return variables[id];
322 |
323 | return variables[id] = {
324 | value: value,
325 | id: varID++
326 | };
327 | }
328 | };
329 |
330 | var buildSentence = function () {
331 | var args = Array.prototype.slice.call(arguments);
332 | var store = args.pop();
333 | var sentence = '';
334 | for (var i = 0; i < args.length; i++)
335 | if (typeof args[i] == 'string')
336 | sentence += args[i];
337 | else
338 | sentence += 'F[' + args[i].id + ']';
339 |
340 | store.push(sentence + ';');
341 | };
342 |
343 | var isEmpty = function (obj) {
344 | for (var prop in obj) {
345 | if (obj.hasOwnProperty(prop))
346 | return false;
347 | }
348 | return true;
349 | };
350 |
351 | var noProjections = isEmpty(this.connections.projected);
352 | var noGates = isEmpty(this.connections.gated);
353 | var isInput = layer == 'input' ? true : isEmpty(this.connections.inputs);
354 | var isOutput = layer == 'output' ? true : noProjections && noGates;
355 |
356 | var rate = getVar('rate');
357 | var activation = getVar(this, 'activation');
358 | if (isInput)
359 | inputs.push(activation.id);
360 | else {
361 | activation_sentences[currentLayer].push(store_activation);
362 | trace_sentences[currentLayer].push(store_trace);
363 | propagation_sentences[currentLayer].push(store_propagation);
364 | var old = getVar(this, 'old');
365 | var state = getVar(this, 'state');
366 | var bias = getVar(this, 'bias');
367 | if (this.selfconnection.gater)
368 | var self_gain = getVar(this.selfconnection, 'gain');
369 | if (this.selfconnected())
370 | var self_weight = getVar(this.selfconnection, 'weight');
371 | buildSentence(old, ' = ', state, store_activation);
372 | if (this.selfconnected())
373 | if (this.selfconnection.gater)
374 | buildSentence(state, ' = ', self_gain, ' * ', self_weight, ' * ',
375 | state, ' + ', bias, store_activation);
376 | else
377 | buildSentence(state, ' = ', self_weight, ' * ', state, ' + ',
378 | bias, store_activation);
379 | else
380 | buildSentence(state, ' = ', bias, store_activation);
381 | for (var i in this.connections.inputs) {
382 | var input = this.connections.inputs[i];
383 | var input_activation = getVar(input.from, 'activation');
384 | var input_weight = getVar(input, 'weight');
385 | if (input.gater)
386 | var input_gain = getVar(input, 'gain');
387 | if (this.connections.inputs[i].gater)
388 | buildSentence(state, ' += ', input_activation, ' * ',
389 | input_weight, ' * ', input_gain, store_activation);
390 | else
391 | buildSentence(state, ' += ', input_activation, ' * ',
392 | input_weight, store_activation);
393 | }
394 | var derivative = getVar(this, 'derivative');
395 | switch (this.squash) {
396 | case Neuron.squash.LOGISTIC:
397 | buildSentence(activation, ' = (1 / (1 + Math.exp(-', state, ')))',
398 | store_activation);
399 | buildSentence(derivative, ' = ', activation, ' * (1 - ',
400 | activation, ')', store_activation);
401 | break;
402 | case Neuron.squash.TANH:
403 | var eP = getVar('aux');
404 | var eN = getVar('aux_2');
405 | buildSentence(eP, ' = Math.exp(', state, ')', store_activation);
406 | buildSentence(eN, ' = 1 / ', eP, store_activation);
407 | buildSentence(activation, ' = (', eP, ' - ', eN, ') / (', eP, ' + ', eN, ')', store_activation);
408 | buildSentence(derivative, ' = 1 - (', activation, ' * ', activation, ')', store_activation);
409 | break;
410 | case Neuron.squash.IDENTITY:
411 | buildSentence(activation, ' = ', state, store_activation);
412 | buildSentence(derivative, ' = 1', store_activation);
413 | break;
414 | case Neuron.squash.HLIM:
415 | buildSentence(activation, ' = +(', state, ' > 0)', store_activation);
416 | buildSentence(derivative, ' = 1', store_activation);
417 | break;
418 | case Neuron.squash.RELU:
419 | buildSentence(activation, ' = ', state, ' > 0 ? ', state, ' : 0', store_activation);
420 | buildSentence(derivative, ' = ', state, ' > 0 ? 1 : 0', store_activation);
421 | break;
422 | }
423 |
424 | for (var id in this.trace.extended) {
425 | var neuron = this.neighboors[id];
426 | var influence = getVar('influences[' + neuron.ID + ']');
427 | var neuron_old = getVar(neuron, 'old');
428 | var initialized = false;
429 | if (neuron.selfconnection.gater == this) {
430 | buildSentence(influence, ' = ', neuron_old, store_trace);
431 | initialized = true;
432 | }
433 | for (var incoming in this.trace.influences[neuron.ID]) {
434 | var incoming_weight = getVar(this.trace.influences[neuron.ID]
435 | [incoming], 'weight');
436 | var incoming_activation = getVar(this.trace.influences[neuron.ID]
437 | [incoming].from, 'activation');
438 |
439 | if (initialized)
440 | buildSentence(influence, ' += ', incoming_weight, ' * ', incoming_activation, store_trace);
441 | else {
442 | buildSentence(influence, ' = ', incoming_weight, ' * ', incoming_activation, store_trace);
443 | initialized = true;
444 | }
445 | }
446 | }
447 |
448 | for (var i in this.connections.inputs) {
449 | var input = this.connections.inputs[i];
450 | if (input.gater)
451 | var input_gain = getVar(input, 'gain');
452 | var input_activation = getVar(input.from, 'activation');
453 | var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
454 | .elegibility[input.ID]);
455 | if (this.selfconnected()) {
456 | if (this.selfconnection.gater) {
457 | if (input.gater)
458 | buildSentence(trace, ' = ', self_gain, ' * ', self_weight,
459 | ' * ', trace, ' + ', input_gain, ' * ', input_activation,
460 | store_trace);
461 | else
462 | buildSentence(trace, ' = ', self_gain, ' * ', self_weight,
463 | ' * ', trace, ' + ', input_activation, store_trace);
464 | } else {
465 | if (input.gater)
466 | buildSentence(trace, ' = ', self_weight, ' * ', trace, ' + ',
467 | input_gain, ' * ', input_activation, store_trace);
468 | else
469 | buildSentence(trace, ' = ', self_weight, ' * ', trace, ' + ',
470 | input_activation, store_trace);
471 | }
472 | } else {
473 | if (input.gater)
474 | buildSentence(trace, ' = ', input_gain, ' * ', input_activation,
475 | store_trace);
476 | else
477 | buildSentence(trace, ' = ', input_activation, store_trace);
478 | }
479 | for (var id in this.trace.extended) {
480 | var neuron = this.neighboors[id];
481 | var influence = getVar('influences[' + neuron.ID + ']');
482 |
483 | var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
484 | .elegibility[input.ID]);
485 | var xtrace = getVar(this, 'trace', 'extended', neuron.ID, input.ID,
486 | this.trace.extended[neuron.ID][input.ID]);
487 | if (neuron.selfconnected())
488 | var neuron_self_weight = getVar(neuron.selfconnection, 'weight');
489 | if (neuron.selfconnection.gater)
490 | var neuron_self_gain = getVar(neuron.selfconnection, 'gain');
491 | if (neuron.selfconnected())
492 | if (neuron.selfconnection.gater)
493 | buildSentence(xtrace, ' = ', neuron_self_gain, ' * ',
494 | neuron_self_weight, ' * ', xtrace, ' + ', derivative, ' * ',
495 | trace, ' * ', influence, store_trace);
496 | else
497 | buildSentence(xtrace, ' = ', neuron_self_weight, ' * ',
498 | xtrace, ' + ', derivative, ' * ', trace, ' * ',
499 | influence, store_trace);
500 | else
501 | buildSentence(xtrace, ' = ', derivative, ' * ', trace, ' * ',
502 | influence, store_trace);
503 | }
504 | }
505 | for (var connection in this.connections.gated) {
506 | var gated_gain = getVar(this.connections.gated[connection], 'gain');
507 | buildSentence(gated_gain, ' = ', activation, store_activation);
508 | }
509 | }
510 | if (!isInput) {
511 | var responsibility = getVar(this, 'error', 'responsibility', this.error
512 | .responsibility);
513 | if (isOutput) {
514 | var target = getVar('target');
515 | buildSentence(responsibility, ' = ', target, ' - ', activation,
516 | store_propagation);
517 | for (var id in this.connections.inputs) {
518 | var input = this.connections.inputs[id];
519 | var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
520 | .elegibility[input.ID]);
521 | var input_weight = getVar(input, 'weight');
522 | buildSentence(input_weight, ' += ', rate, ' * (', responsibility,
523 | ' * ', trace, ')', store_propagation);
524 | }
525 | outputs.push(activation.id);
526 | } else {
527 | if (!noProjections && !noGates) {
528 | var error = getVar('aux');
529 | for (var id in this.connections.projected) {
530 | var connection = this.connections.projected[id];
531 | var neuron = connection.to;
532 | var connection_weight = getVar(connection, 'weight');
533 | var neuron_responsibility = getVar(neuron, 'error',
534 | 'responsibility', neuron.error.responsibility);
535 | if (connection.gater) {
536 | var connection_gain = getVar(connection, 'gain');
537 | buildSentence(error, ' += ', neuron_responsibility, ' * ',
538 | connection_gain, ' * ', connection_weight,
539 | store_propagation);
540 | } else
541 | buildSentence(error, ' += ', neuron_responsibility, ' * ',
542 | connection_weight, store_propagation);
543 | }
544 | var projected = getVar(this, 'error', 'projected', this.error.projected);
545 | buildSentence(projected, ' = ', derivative, ' * ', error,
546 | store_propagation);
547 | buildSentence(error, ' = 0', store_propagation);
548 | for (var id in this.trace.extended) {
549 | var neuron = this.neighboors[id];
550 | var influence = getVar('aux_2');
551 | var neuron_old = getVar(neuron, 'old');
552 | if (neuron.selfconnection.gater == this)
553 | buildSentence(influence, ' = ', neuron_old, store_propagation);
554 | else
555 | buildSentence(influence, ' = 0', store_propagation);
556 | for (var input in this.trace.influences[neuron.ID]) {
557 | var connection = this.trace.influences[neuron.ID][input];
558 | var connection_weight = getVar(connection, 'weight');
559 | var neuron_activation = getVar(connection.from, 'activation');
560 | buildSentence(influence, ' += ', connection_weight, ' * ',
561 | neuron_activation, store_propagation);
562 | }
563 | var neuron_responsibility = getVar(neuron, 'error',
564 | 'responsibility', neuron.error.responsibility);
565 | buildSentence(error, ' += ', neuron_responsibility, ' * ',
566 | influence, store_propagation);
567 | }
568 | var gated = getVar(this, 'error', 'gated', this.error.gated);
569 | buildSentence(gated, ' = ', derivative, ' * ', error,
570 | store_propagation);
571 | buildSentence(responsibility, ' = ', projected, ' + ', gated,
572 | store_propagation);
573 | for (var id in this.connections.inputs) {
574 | var input = this.connections.inputs[id];
575 | var gradient = getVar('aux');
576 | var trace = getVar(this, 'trace', 'elegibility', input.ID, this
577 | .trace.elegibility[input.ID]);
578 | buildSentence(gradient, ' = ', projected, ' * ', trace,
579 | store_propagation);
580 | for (var id in this.trace.extended) {
581 | var neuron = this.neighboors[id];
582 | var neuron_responsibility = getVar(neuron, 'error',
583 | 'responsibility', neuron.error.responsibility);
584 | var xtrace = getVar(this, 'trace', 'extended', neuron.ID,
585 | input.ID, this.trace.extended[neuron.ID][input.ID]);
586 | buildSentence(gradient, ' += ', neuron_responsibility, ' * ',
587 | xtrace, store_propagation);
588 | }
589 | var input_weight = getVar(input, 'weight');
590 | buildSentence(input_weight, ' += ', rate, ' * ', gradient,
591 | store_propagation);
592 | }
593 |
594 | } else if (noGates) {
595 | buildSentence(responsibility, ' = 0', store_propagation);
596 | for (var id in this.connections.projected) {
597 | var connection = this.connections.projected[id];
598 | var neuron = connection.to;
599 | var connection_weight = getVar(connection, 'weight');
600 | var neuron_responsibility = getVar(neuron, 'error',
601 | 'responsibility', neuron.error.responsibility);
602 | if (connection.gater) {
603 | var connection_gain = getVar(connection, 'gain');
604 | buildSentence(responsibility, ' += ', neuron_responsibility,
605 | ' * ', connection_gain, ' * ', connection_weight,
606 | store_propagation);
607 | } else
608 | buildSentence(responsibility, ' += ', neuron_responsibility,
609 | ' * ', connection_weight, store_propagation);
610 | }
611 | buildSentence(responsibility, ' *= ', derivative,
612 | store_propagation);
613 | for (var id in this.connections.inputs) {
614 | var input = this.connections.inputs[id];
615 | var trace = getVar(this, 'trace', 'elegibility', input.ID, this
616 | .trace.elegibility[input.ID]);
617 | var input_weight = getVar(input, 'weight');
618 | buildSentence(input_weight, ' += ', rate, ' * (',
619 | responsibility, ' * ', trace, ')', store_propagation);
620 | }
621 | } else if (noProjections) {
622 | buildSentence(responsibility, ' = 0', store_propagation);
623 | for (var id in this.trace.extended) {
624 | var neuron = this.neighboors[id];
625 | var influence = getVar('aux');
626 | var neuron_old = getVar(neuron, 'old');
627 | if (neuron.selfconnection.gater == this)
628 | buildSentence(influence, ' = ', neuron_old, store_propagation);
629 | else
630 | buildSentence(influence, ' = 0', store_propagation);
631 | for (var input in this.trace.influences[neuron.ID]) {
632 | var connection = this.trace.influences[neuron.ID][input];
633 | var connection_weight = getVar(connection, 'weight');
634 | var neuron_activation = getVar(connection.from, 'activation');
635 | buildSentence(influence, ' += ', connection_weight, ' * ',
636 | neuron_activation, store_propagation);
637 | }
638 | var neuron_responsibility = getVar(neuron, 'error',
639 | 'responsibility', neuron.error.responsibility);
640 | buildSentence(responsibility, ' += ', neuron_responsibility,
641 | ' * ', influence, store_propagation);
642 | }
643 | buildSentence(responsibility, ' *= ', derivative,
644 | store_propagation);
645 | for (var id in this.connections.inputs) {
646 | var input = this.connections.inputs[id];
647 | var gradient = getVar('aux');
648 | buildSentence(gradient, ' = 0', store_propagation);
649 | for (var id in this.trace.extended) {
650 | var neuron = this.neighboors[id];
651 | var neuron_responsibility = getVar(neuron, 'error',
652 | 'responsibility', neuron.error.responsibility);
653 | var xtrace = getVar(this, 'trace', 'extended', neuron.ID,
654 | input.ID, this.trace.extended[neuron.ID][input.ID]);
655 | buildSentence(gradient, ' += ', neuron_responsibility, ' * ',
656 | xtrace, store_propagation);
657 | }
658 | var input_weight = getVar(input, 'weight');
659 | buildSentence(input_weight, ' += ', rate, ' * ', gradient,
660 | store_propagation);
661 | }
662 | }
663 | }
664 | buildSentence(bias, ' += ', rate, ' * ', responsibility,
665 | store_propagation);
666 | }
667 | return {
668 | memory: varID,
669 | neurons: neurons + 1,
670 | inputs: inputs,
671 | outputs: outputs,
672 | targets: targets,
673 | variables: variables,
674 | activation_sentences: activation_sentences,
675 | trace_sentences: trace_sentences,
676 | propagation_sentences: propagation_sentences,
677 | layers: layers
678 | }
679 | }
680 |
681 | static uid() {
682 | return neurons++;
683 | }
684 |
685 | static quantity() {
686 | return {
687 | neurons: neurons,
688 | connections: connections
689 | }
690 | }
691 | }
--------------------------------------------------------------------------------
/src/Squash.ts:
--------------------------------------------------------------------------------
1 | export const LOGISTIC = (x: number, derivate?: boolean) => {
2 | const fx = 1 / (1 + Math.exp(-x));
3 | if (!derivate) {
4 | return fx;
5 | }
6 | return fx * (1 - fx);
7 | };
8 |
9 | export const TANH = (x: number, derivate?: boolean) => {
10 | if (derivate) {
11 | return 1 - Math.pow(Math.tanh(x), 2);
12 | }
13 | return Math.tanh(x);
14 | };
15 |
16 | export const IDENTITY = (x: number, derivate?: boolean) => {
17 | return derivate ? 1 : x;
18 | };
19 |
20 | export const HLIM = (x: number, derivate?: boolean) => {
21 | return derivate ? 1 : x > 0 ? 1 : 0;
22 | };
23 |
24 | export const RELU = (x: number, derivate?: boolean) => {
25 | if (derivate) {
26 | return x > 0 ? 1 : 0;
27 | }
28 | return x > 0 ? x : 0;
29 | };
30 |
--------------------------------------------------------------------------------
/src/Trainer.js:
--------------------------------------------------------------------------------
1 | import * as cost from './Cost.ts';
2 |
3 | const shuffleInplace = (o) => {
4 | for (let j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
5 | return o;
6 | };
7 |
8 |
9 |
10 | export class Trainer {
11 | static cost = cost;
12 |
13 | constructor(network, options) {
14 | options = options || {};
15 | this.network = network;
16 | this.rate = options.rate || .2;
17 | this.iterations = options.iterations || 100000;
18 | this.error = options.error || .005;
19 | this.cost = options.cost || null;
20 | this.crossValidate = options.crossValidate || null;
21 | }
22 |
23 | train(set, options) {
24 | var error = 1;
25 | var iterations = bucketSize = 0;
26 | var abort = false;
27 | var currentRate;
28 | var cost = options && options.cost || this.cost || Trainer.cost.MSE;
29 | var crossValidate = false, testSet, trainSet;
30 |
31 | var start = Date.now();
32 |
33 | if (options) {
34 | if (options.iterations)
35 | this.iterations = options.iterations;
36 | if (options.error)
37 | this.error = options.error;
38 | if (options.rate)
39 | this.rate = options.rate;
40 | if (options.cost)
41 | this.cost = options.cost;
42 | if (options.schedule)
43 | this.schedule = options.schedule;
44 | if (options.customLog) {
45 | console.log('Deprecated: use schedule instead of customLog')
46 | this.schedule = options.customLog;
47 | }
48 | if (this.crossValidate || options.crossValidate) {
49 | if (!this.crossValidate) this.crossValidate = {};
50 | crossValidate = true;
51 | if (options.crossValidate.testSize)
52 | this.crossValidate.testSize = options.crossValidate.testSize;
53 | if (options.crossValidate.testError)
54 | this.crossValidate.testError = options.crossValidate.testError;
55 | }
56 | }
57 |
58 | currentRate = this.rate;
59 | if (Array.isArray(this.rate)) {
60 | var bucketSize = Math.floor(this.iterations / this.rate.length);
61 | }
62 |
63 | if (crossValidate) {
64 | var numTrain = Math.ceil((1 - this.crossValidate.testSize) * set.length);
65 | trainSet = set.slice(0, numTrain);
66 | testSet = set.slice(numTrain);
67 | }
68 |
69 | var lastError = 0;
70 | while ((!abort && iterations < this.iterations && error > this.error)) {
71 | if (crossValidate && error <= this.crossValidate.testError) {
72 | break;
73 | }
74 |
75 | var currentSetSize = set.length;
76 | error = 0;
77 | iterations++;
78 |
79 | if (bucketSize > 0) {
80 | var currentBucket = Math.floor(iterations / bucketSize);
81 | currentRate = this.rate[currentBucket] || currentRate;
82 | }
83 |
84 | if (typeof this.rate === 'function') {
85 | currentRate = this.rate(iterations, lastError);
86 | }
87 |
88 | if (crossValidate) {
89 | this._trainSet(trainSet, currentRate, cost);
90 | error += this.test(testSet).error;
91 | currentSetSize = 1;
92 | } else {
93 | error += this._trainSet(set, currentRate, cost);
94 | currentSetSize = set.length;
95 | }
96 |
97 | error /= currentSetSize;
98 | lastError = error;
99 |
100 | if (options) {
101 | if (this.schedule && this.schedule.every && iterations %
102 | this.schedule.every == 0)
103 | abort = this.schedule.do({ error: error, iterations: iterations, rate: currentRate });
104 | else if (options.log && iterations % options.log == 0) {
105 | console.log('iterations', iterations, 'error', error, 'rate', currentRate);
106 | }
107 | if (options.shuffle)
108 | shuffleInplace(set);
109 | }
110 | }
111 |
112 | const results = {
113 | error: error,
114 | iterations: iterations,
115 | time: Date.now() - start
116 | };
117 |
118 | return results;
119 | }
120 |
121 | trainAsync(set, options) {
122 | var train = this.workerTrain.bind(this);
123 | return new Promise(function (resolve, reject) {
124 | try {
125 | train(set, resolve, options, true)
126 | } catch (e) {
127 | reject(e)
128 | }
129 | })
130 | }
131 |
132 | _trainSet(set, currentRate, costFunction) {
133 | var errorSum = 0;
134 | for (let i = 0; i < set.length; i++) {
135 | const input = set[i].input;
136 | const target = set[i].output;
137 |
138 | const output = this.network.activate(input);
139 | this.network.propagate(currentRate, target);
140 |
141 | errorSum += costFunction(target, output);
142 | }
143 | return errorSum;
144 | }
145 |
146 | test(set, options) {
147 | var error = 0;
148 | var input, output, target;
149 | var cost = options && options.cost || this.cost || Trainer.cost.MSE;
150 |
151 | var start = Date.now();
152 |
153 | for (let i = 0; i < set.length; i++) {
154 | input = set[i].input;
155 | target = set[i].output;
156 | output = this.network.activate(input);
157 | error += cost(target, output);
158 | }
159 |
160 | error /= set.length;
161 |
162 | var results = {
163 | error: error,
164 | time: Date.now() - start
165 | };
166 |
167 | return results;
168 | }
169 |
170 | workerTrain(set, callback, options, suppressWarning) {
171 | if (!suppressWarning) {
172 | console.warn('Deprecated: do not use `workerTrain`, use `trainAsync` instead.')
173 | }
174 | var that = this;
175 |
176 | if (!this.network.optimized)
177 | this.network.optimize();
178 |
179 | var worker = this.network.worker(this.network.optimized.memory, set, options);
180 |
181 | worker.onmessage = function (e) {
182 | switch (e.data.action) {
183 | case 'done':
184 | var iterations = e.data.message.iterations;
185 | var error = e.data.message.error;
186 | var time = e.data.message.time;
187 |
188 | that.network.optimized.ownership(e.data.memoryBuffer);
189 |
190 | callback({
191 | error: error,
192 | iterations: iterations,
193 | time: time
194 | });
195 |
196 | worker.terminate();
197 | break;
198 |
199 | case 'log':
200 | console.log(e.data.message);
201 |
202 | case 'schedule':
203 | if (options && options.schedule && typeof options.schedule.do === 'function') {
204 | var scheduled = options.schedule.do
205 | scheduled(e.data.message)
206 | }
207 | break;
208 | }
209 | };
210 |
211 | worker.postMessage({ action: 'startTraining' });
212 | }
213 |
214 | XOR(options) {
215 | if (this.network.inputs() != 2 || this.network.outputs() != 1)
216 | throw new Error('Incompatible network (2 inputs, 1 output)');
217 |
218 | var defaults = {
219 | iterations: 100000,
220 | log: false,
221 | shuffle: true,
222 | cost: Trainer.cost.MSE
223 | };
224 |
225 | if (options)
226 | for (var i in options)
227 | defaults[i] = options[i];
228 |
229 | return this.train([{
230 | input: [0, 0],
231 | output: [0]
232 | }, {
233 | input: [1, 0],
234 | output: [1]
235 | }, {
236 | input: [0, 1],
237 | output: [1]
238 | }, {
239 | input: [1, 1],
240 | output: [0]
241 | }], defaults);
242 | }
243 |
244 | DSR(options) {
245 | options = options || {};
246 |
247 | var targets = options.targets || [2, 4, 7, 8];
248 | var distractors = options.distractors || [3, 5, 6, 9];
249 | var prompts = options.prompts || [0, 1];
250 | var length = options.length || 24;
251 | var criterion = options.success || 0.95;
252 | var iterations = options.iterations || 100000;
253 | var rate = options.rate || .1;
254 | var log = options.log || 0;
255 | var schedule = options.schedule || {};
256 | var cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY;
257 |
258 | var trial, correct, i, j, success;
259 | trial = correct = i = j = success = 0;
260 | var error = 1,
261 | symbols = targets.length + distractors.length + prompts.length;
262 |
263 | var noRepeat = function (range, avoid) {
264 | var number = Math.random() * range | 0;
265 | var used = false;
266 | for (var i in avoid)
267 | if (number == avoid[i])
268 | used = true;
269 | return used ? noRepeat(range, avoid) : number;
270 | };
271 |
272 | var equal = function (prediction, output) {
273 | for (var i in prediction)
274 | if (Math.round(prediction[i]) != output[i])
275 | return false;
276 | return true;
277 | };
278 |
279 | var start = Date.now();
280 |
281 | while (trial < iterations && (success < criterion || trial % 1000 != 0)) {
282 | var sequence = [],
283 | sequenceLength = length - prompts.length;
284 | for (i = 0; i < sequenceLength; i++) {
285 | var any = Math.random() * distractors.length | 0;
286 | sequence.push(distractors[any]);
287 | }
288 | var indexes = [],
289 | positions = [];
290 | for (i = 0; i < prompts.length; i++) {
291 | indexes.push(Math.random() * targets.length | 0);
292 | positions.push(noRepeat(sequenceLength, positions));
293 | }
294 | positions = positions.sort();
295 | for (i = 0; i < prompts.length; i++) {
296 | sequence[positions[i]] = targets[indexes[i]];
297 | sequence.push(prompts[i]);
298 | }
299 |
300 | var distractorsCorrect;
301 | var targetsCorrect = distractorsCorrect = 0;
302 | error = 0;
303 | for (i = 0; i < length; i++) {
304 | var input = [];
305 | for (j = 0; j < symbols; j++)
306 | input[j] = 0;
307 | input[sequence[i]] = 1;
308 |
309 | var output = [];
310 | for (j = 0; j < targets.length; j++)
311 | output[j] = 0;
312 |
313 | if (i >= sequenceLength) {
314 | var index = i - sequenceLength;
315 | output[indexes[index]] = 1;
316 | }
317 |
318 | var prediction = this.network.activate(input);
319 |
320 | if (equal(prediction, output))
321 | if (i < sequenceLength)
322 | distractorsCorrect++;
323 | else
324 | targetsCorrect++;
325 | else {
326 | this.network.propagate(rate, output);
327 | }
328 |
329 | error += cost(output, prediction);
330 |
331 | if (distractorsCorrect + targetsCorrect == length)
332 | correct++;
333 | }
334 |
335 | if (trial % 1000 == 0)
336 | correct = 0;
337 | trial++;
338 | var divideError = trial % 1000;
339 | divideError = divideError == 0 ? 1000 : divideError;
340 | success = correct / divideError;
341 | error /= length;
342 |
343 | if (log && trial % log == 0)
344 | console.log('iterations:', trial, ' success:', success, ' correct:',
345 | correct, ' time:', Date.now() - start, ' error:', error);
346 | if (schedule.do && schedule.every && trial % schedule.every == 0)
347 | schedule.do({
348 | iterations: trial,
349 | success: success,
350 | error: error,
351 | time: Date.now() - start,
352 | correct: correct
353 | });
354 | }
355 |
356 | return {
357 | iterations: trial,
358 | success: success,
359 | error: error,
360 | time: Date.now() - start
361 | }
362 | }
363 |
364 | ERG(options) {
365 |
366 | options = options || {};
367 | var iterations = options.iterations || 150000;
368 | var criterion = options.error || .05;
369 | var rate = options.rate || .1;
370 | var log = options.log || 500;
371 | var cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY;
372 |
373 | var Node = function () {
374 | this.paths = [];
375 | };
376 | Node.prototype = {
377 | connect: function (node, value) {
378 | this.paths.push({
379 | node: node,
380 | value: value
381 | });
382 | return this;
383 | },
384 | any: function () {
385 | if (this.paths.length == 0)
386 | return false;
387 | var index = Math.random() * this.paths.length | 0;
388 | return this.paths[index];
389 | },
390 | test: function (value) {
391 | for (var i in this.paths)
392 | if (this.paths[i].value == value)
393 | return this.paths[i];
394 | return false;
395 | }
396 | };
397 |
398 | var reberGrammar = function () {
399 |
400 | var output = new Node();
401 | var n1 = (new Node()).connect(output, 'E');
402 | var n2 = (new Node()).connect(n1, 'S');
403 | var n3 = (new Node()).connect(n1, 'V').connect(n2, 'P');
404 | var n4 = (new Node()).connect(n2, 'X');
405 | n4.connect(n4, 'S');
406 | var n5 = (new Node()).connect(n3, 'V');
407 | n5.connect(n5, 'T');
408 | n2.connect(n5, 'X');
409 | var n6 = (new Node()).connect(n4, 'T').connect(n5, 'P');
410 | var input = (new Node()).connect(n6, 'B');
411 |
412 | return {
413 | input: input,
414 | output: output
415 | }
416 | };
417 |
418 | var embededReberGrammar = function () {
419 | var reber1 = reberGrammar();
420 | var reber2 = reberGrammar();
421 |
422 | var output = new Node();
423 | var n1 = (new Node).connect(output, 'E');
424 | reber1.output.connect(n1, 'T');
425 | reber2.output.connect(n1, 'P');
426 | var n2 = (new Node).connect(reber1.input, 'P').connect(reber2.input,
427 | 'T');
428 | var input = (new Node).connect(n2, 'B');
429 |
430 | return {
431 | input: input,
432 | output: output
433 | }
434 |
435 | };
436 |
437 | var generate = function () {
438 | var node = embededReberGrammar().input;
439 | var next = node.any();
440 | var str = '';
441 | while (next) {
442 | str += next.value;
443 | next = next.node.any();
444 | }
445 | return str;
446 | };
447 |
448 | var test = function (str) {
449 | var node = embededReberGrammar().input;
450 | var i = 0;
451 | var ch = str.charAt(i);
452 | while (i < str.length) {
453 | var next = node.test(ch);
454 | if (!next)
455 | return false;
456 | node = next.node;
457 | ch = str.charAt(++i);
458 | }
459 | return true;
460 | };
461 |
462 | var different = function (array1, array2) {
463 | var max1 = 0;
464 | var i1 = -1;
465 | var max2 = 0;
466 | var i2 = -1;
467 | for (var i in array1) {
468 | if (array1[i] > max1) {
469 | max1 = array1[i];
470 | i1 = i;
471 | }
472 | if (array2[i] > max2) {
473 | max2 = array2[i];
474 | i2 = i;
475 | }
476 | }
477 |
478 | return i1 != i2;
479 | };
480 |
481 | var iteration = 0;
482 | var error = 1;
483 | var table = {
484 | 'B': 0,
485 | 'P': 1,
486 | 'T': 2,
487 | 'X': 3,
488 | 'S': 4,
489 | 'E': 5
490 | };
491 |
492 | var start = Date.now();
493 | while (iteration < iterations && error > criterion) {
494 | var i = 0;
495 | error = 0;
496 |
497 | var sequence = generate();
498 |
499 | var read = sequence.charAt(i);
500 | var predict = sequence.charAt(i + 1);
501 |
502 | while (i < sequence.length - 1) {
503 | var input = [];
504 | var target = [];
505 | for (var j = 0; j < 6; j++) {
506 | input[j] = 0;
507 | target[j] = 0;
508 | }
509 | input[table[read]] = 1;
510 | target[table[predict]] = 1;
511 |
512 | var output = this.network.activate(input);
513 |
514 | if (different(output, target))
515 | this.network.propagate(rate, target);
516 |
517 | read = sequence.charAt(++i);
518 | predict = sequence.charAt(i + 1);
519 |
520 | error += cost(target, output);
521 | }
522 | error /= sequence.length;
523 | iteration++;
524 | if (iteration % log == 0) {
525 | console.log('iterations:', iteration, ' time:', Date.now() - start,
526 | ' error:', error);
527 | }
528 | }
529 |
530 | return {
531 | iterations: iteration,
532 | error: error,
533 | time: Date.now() - start,
534 | test: test,
535 | generate: generate
536 | }
537 | }
538 |
539 | timingTask(options) {
540 |
541 | if (this.network.inputs() != 2 || this.network.outputs() != 1)
542 | throw new Error('Invalid Network: must have 2 inputs and one output');
543 |
544 | if (typeof options == 'undefined')
545 | options = {};
546 |
547 | function getSamples(trainingSize, testSize) {
548 |
549 | var size = trainingSize + testSize;
550 |
551 | var t = 0;
552 | var set = [];
553 | for (var i = 0; i < size; i++) {
554 | set.push({ input: [0, 0], output: [0] });
555 | }
556 | while (t < size - 20) {
557 | var n = Math.round(Math.random() * 20);
558 | set[t].input[0] = 1;
559 | for (var j = t; j <= t + n; j++) {
560 | set[j].input[1] = n / 20;
561 | set[j].output[0] = 0.5;
562 | }
563 | t += n;
564 | n = Math.round(Math.random() * 20);
565 | for (var k = t + 1; k <= (t + n) && k < size; k++)
566 | set[k].input[1] = set[t].input[1];
567 | t += n;
568 | }
569 |
570 | var trainingSet = [];
571 | var testSet = [];
572 | for (var l = 0; l < size; l++)
573 | (l < trainingSize ? trainingSet : testSet).push(set[l]);
574 |
575 | return {
576 | train: trainingSet,
577 | test: testSet
578 | }
579 | }
580 |
581 | var iterations = options.iterations || 200;
582 | var error = options.error || .005;
583 | var rate = options.rate || [.03, .02];
584 | var log = options.log === false ? false : options.log || 10;
585 | var cost = options.cost || this.cost || Trainer.cost.MSE;
586 | var trainingSamples = options.trainSamples || 7000;
587 | var testSamples = options.trainSamples || 1000;
588 |
589 | var samples = getSamples(trainingSamples, testSamples);
590 |
591 | var result = this.train(samples.train, {
592 | rate: rate,
593 | log: log,
594 | iterations: iterations,
595 | error: error,
596 | cost: cost
597 | });
598 |
599 | return {
600 | train: result,
601 | test: this.test(samples.test)
602 | }
603 | }
604 |
605 | }
--------------------------------------------------------------------------------
/src/architect.ts:
--------------------------------------------------------------------------------
1 | export { Perceptron } from './architectures/Perceptron.js';
2 | export { LSTM } from './architectures/LSTM.js';
3 | export { Liquid } from './architectures/Liquid.js';
4 | export { Hopfield } from './architectures/Hopfield.js';
--------------------------------------------------------------------------------
/src/architectures/Hopfield.js:
--------------------------------------------------------------------------------
1 | import { Network } from '../Network.js';
2 | import { Trainer } from '../Trainer.js';
3 | import { Layer } from '../Layer.js';
4 |
5 | export class Hopfield extends Network {
6 | constructor(size) {
7 | super();
8 | let inputLayer = new Layer(size);
9 | let outputLayer = new Layer(size);
10 |
11 | inputLayer.project(outputLayer, Layer.connectionType.ALL_TO_ALL);
12 |
13 | this.set({
14 | input: inputLayer,
15 | hidden: [],
16 | output: outputLayer
17 | });
18 |
19 | this.trainer = new Trainer(this);
20 | }
21 |
22 | learn(patterns) {
23 | let set = [];
24 | for (let p in patterns)
25 | set.push({
26 | input: patterns[p],
27 | output: patterns[p]
28 | });
29 |
30 | return this.trainer.train(set, {
31 | iterations: 500000,
32 | error: .00005,
33 | rate: 1
34 | });
35 | }
36 |
37 | feed(pattern) {
38 | let output = this.activate(pattern);
39 |
40 | pattern = [];
41 | for (let i in output)
42 | pattern[i] = output[i] > .5 ? 1 : 0;
43 |
44 | return pattern;
45 | }
46 | }
--------------------------------------------------------------------------------
/src/architectures/LSTM.js:
--------------------------------------------------------------------------------
1 | import { Network } from '../Network.js';
2 | import { Layer } from '../Layer.js';
3 |
4 | export class LSTM extends Network {
5 | constructor() {
6 | super();
7 | let args = Array.prototype.slice.call(arguments);
8 | if (args.length < 3)
9 | throw new Error("not enough layers (minimum 3) !!");
10 |
11 | let last = args.pop();
12 | let option = {
13 | peepholes: Layer.connectionType.ALL_TO_ALL,
14 | hiddenToHidden: false,
15 | outputToHidden: false,
16 | outputToGates: false,
17 | inputToOutput: true,
18 | };
19 | if (typeof last != 'number') {
20 | let outputs = args.pop();
21 | if (last.hasOwnProperty('peepholes'))
22 | option.peepholes = last.peepholes;
23 | if (last.hasOwnProperty('hiddenToHidden'))
24 | option.hiddenToHidden = last.hiddenToHidden;
25 | if (last.hasOwnProperty('outputToHidden'))
26 | option.outputToHidden = last.outputToHidden;
27 | if (last.hasOwnProperty('outputToGates'))
28 | option.outputToGates = last.outputToGates;
29 | if (last.hasOwnProperty('inputToOutput'))
30 | option.inputToOutput = last.inputToOutput;
31 | } else {
32 | let outputs = last;
33 | }
34 |
35 | let inputs = args.shift();
36 | let layers = args;
37 |
38 | let inputLayer = new Layer(inputs);
39 | let hiddenLayers = [];
40 | let outputLayer = new Layer(outputs);
41 |
42 | let previous = null;
43 |
44 | for (let i = 0; i < layers.length; i++) {
45 | let size = layers[i];
46 |
47 | let inputGate = new Layer(size).set({
48 | bias: 1
49 | });
50 | let forgetGate = new Layer(size).set({
51 | bias: 1
52 | });
53 | let memoryCell = new Layer(size);
54 | let outputGate = new Layer(size).set({
55 | bias: 1
56 | });
57 |
58 | hiddenLayers.push(inputGate);
59 | hiddenLayers.push(forgetGate);
60 | hiddenLayers.push(memoryCell);
61 | hiddenLayers.push(outputGate);
62 |
63 | let input = inputLayer.project(memoryCell);
64 | inputLayer.project(inputGate);
65 | inputLayer.project(forgetGate);
66 | inputLayer.project(outputGate);
67 |
68 | if (previous != null) {
69 | let cell = previous.project(memoryCell);
70 | previous.project(inputGate);
71 | previous.project(forgetGate);
72 | previous.project(outputGate);
73 | }
74 |
75 | let output = memoryCell.project(outputLayer);
76 |
77 | let self = memoryCell.project(memoryCell);
78 |
79 | if (option.hiddenToHidden)
80 | memoryCell.project(memoryCell, Layer.connectionType.ALL_TO_ELSE);
81 |
82 | if (option.outputToHidden)
83 | outputLayer.project(memoryCell);
84 |
85 | if (option.outputToGates) {
86 | outputLayer.project(inputGate);
87 | outputLayer.project(outputGate);
88 | outputLayer.project(forgetGate);
89 | }
90 |
91 | memoryCell.project(inputGate, option.peepholes);
92 | memoryCell.project(forgetGate, option.peepholes);
93 | memoryCell.project(outputGate, option.peepholes);
94 |
95 | inputGate.gate(input, Layer.gateType.INPUT);
96 | forgetGate.gate(self, Layer.gateType.ONE_TO_ONE);
97 | outputGate.gate(output, Layer.gateType.OUTPUT);
98 | if (previous != null)
99 | inputGate.gate(cell, Layer.gateType.INPUT);
100 |
101 | previous = memoryCell;
102 | }
103 |
104 | if (option.inputToOutput)
105 | inputLayer.project(outputLayer);
106 |
107 | this.set({
108 | input: inputLayer,
109 | hidden: hiddenLayers,
110 | output: outputLayer
111 | });
112 | }
113 | }
--------------------------------------------------------------------------------
/src/architectures/Liquid.js:
--------------------------------------------------------------------------------
1 | import { Network } from '../Network.js';
2 | import { Layer } from '../Layer.js';
3 |
4 | export class Liquid extends Network {
5 | constructor(inputs, hidden, outputs, connections, gates) {
6 | super();
7 | let inputLayer = new Layer(inputs);
8 | let hiddenLayer = new Layer(hidden);
9 | let outputLayer = new Layer(outputs);
10 |
11 | let neurons = hiddenLayer.neurons();
12 | let connectionList = [];
13 |
14 | for (let i = 0; i < connections; i++) {
15 | let from = Math.random() * neurons.length | 0;
16 | let to = Math.random() * neurons.length | 0;
17 | let connection = neurons[from].project(neurons[to]);
18 | connectionList.push(connection);
19 | }
20 |
21 | for (let j = 0; j < gates; j++) {
22 | let gater = Math.random() * neurons.length | 0;
23 | let connection = Math.random() * connectionList.length | 0;
24 | neurons[gater].gate(connectionList[connection]);
25 | }
26 |
27 | inputLayer.project(hiddenLayer);
28 | hiddenLayer.project(outputLayer);
29 |
30 | this.set({
31 | input: inputLayer,
32 | hidden: [hiddenLayer],
33 | output: outputLayer
34 | });
35 | }
36 | }
--------------------------------------------------------------------------------
/src/architectures/Perceptron.js:
--------------------------------------------------------------------------------
1 | import { Network } from '../Network.js';
2 | import { Layer } from '../Layer.js';
3 |
4 | export class Perceptron extends Network {
5 | constructor() {
6 | super();
7 | let args = Array.prototype.slice.call(arguments);
8 | if (args.length < 3)
9 | throw new Error('not enough layers (minimum 3) !!');
10 |
11 | let inputs = args.shift();
12 | let outputs = args.pop();
13 | let layers = args;
14 |
15 | let input = new Layer(inputs);
16 | let hidden = [];
17 | let output = new Layer(outputs);
18 |
19 | let previous = input;
20 |
21 | for (let i = 0; i < layers.length; i++) {
22 | let size = layers[i];
23 | let layer = new Layer(size);
24 | hidden.push(layer);
25 | previous.project(layer);
26 | previous = layer;
27 | }
28 | previous.project(output);
29 |
30 | this.set({
31 | input: input,
32 | hidden: hidden,
33 | output: output
34 | });
35 | }
36 | }
--------------------------------------------------------------------------------