From c1d7399b34f0c09f64296c1cc449832b3be9a0c4 Mon Sep 17 00:00:00 2001 From: Cameron Knight Date: Thu, 16 Jul 2015 18:25:49 -0700 Subject: [PATCH] Unroll loops and data within toFunction() This should be functionality equivalent to before, and the unit tests remain green. The resultant function should be much more friendly to the JITter, as it contains object accesses at the top, several pure math executions (which the JITter could theoretically parallelize), and an object create upon return. A greater-than 10x speedup was observed with the new method. --- lib/neuralnetwork.js | 55 ++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/lib/neuralnetwork.js b/lib/neuralnetwork.js index f147f9a..f633822 100644 --- a/lib/neuralnetwork.js +++ b/lib/neuralnetwork.js @@ -370,27 +370,42 @@ NeuralNetwork.prototype = { return this; }, - toFunction: function() { - var json = this.toJSON(); + toFunction: function() { // return standalone function that mimics run() - return new Function("input", -' var net = ' + JSON.stringify(json) + ';\n\n\ - for (var i = 1; i < net.layers.length; i++) {\n\ - var layer = net.layers[i];\n\ - var output = {};\n\ - \n\ - for (var id in layer) {\n\ - var node = layer[id];\n\ - var sum = node.bias;\n\ - \n\ - for (var iid in node.weights) {\n\ - sum += node.weights[iid] * input[iid];\n\ - }\n\ - output[id] = (1 / (1 + Math.exp(-sum)));\n\ - }\n\ - input = output;\n\ - }\n\ - return output;'); + + var layers = this.toJSON().layers; + // we start with 'input', but will use each layer as the next input + var input = 'input'; + + // NOTE: keys are sorted so as to guarantee a consistent order and matching + // key-to-index, as indexes are used to generate variable names + return new Function(input, [].concat( + _.map(_.keys(layers[0]).sort(), function(iid, i) { + // collect our inputs as variables + // e.g. var input_0 = input['x']; + return 'var ' + input + '_' + i + ' = ' + input + '[' + JSON.stringify(iid) + '];'; + }), + _.map(layers.slice(1), function(layer, i) { + var output = 'output' + i; + var result = _.map(_.keys(layer).sort(), function(id, j) { + var node = layer[id]; + // e.g. bias + (weight_0 * inputX_0) + (weight_1 * inputX_1) + ... + var sum = [ + JSON.stringify(node.bias) + ].concat(_.map(_.keys(node.weights).sort(), function(iid, k) { + return JSON.stringify(node.weights[iid]) + ' * ' + input + '_' + k; + })).join(' + '); + return 'var ' + output + '_' + j + ' = (1 / (1 + Math.exp(-(' + sum + '))));'; + }).join('\n'); + input = output; + return result; + }), [ + // finally, return the resultant object, which consists of the values of our last layer + // e.g. return {a: outputX_0, b: outputX_1, c: outputX_2}; + 'return {' + _.map(_.keys(layers[layers.length - 1]).sort(), function(id, j) { + return JSON.stringify(id) + ': ' + input + '_' + j; + }).join(', ') + '};' + ]).join('\n')); }, // This will create a TrainStream (WriteStream)