Comparing version
@@ -9,3 +9,3 @@ /* | ||
path = require("path"), | ||
sys = require("sys") | ||
util = require('util') | ||
build = require("./build"); | ||
@@ -17,6 +17,6 @@ | ||
task('build', [], function (dest) { | ||
sys.puts("building..."); | ||
util.puts("building..."); | ||
dest = dest || prefix + ".js"; | ||
build.build(dest); | ||
sys.puts("> " + dest); | ||
util.puts("> " + dest); | ||
}); | ||
@@ -30,3 +30,3 @@ | ||
fs.writeFileSync(dest, minified, "utf-8"); | ||
sys.puts("> " + dest) | ||
util.puts("> " + dest) | ||
}); | ||
@@ -33,0 +33,0 @@ |
@@ -41,2 +41,13 @@ var _ = require("underscore"); | ||
function lookupFromArray(array) { | ||
var lookup = {}; | ||
// super fast loop | ||
var z = 0; | ||
var i = array.length; | ||
while (i-- > 0) { | ||
lookup[array[i]] = z++; | ||
}; | ||
return lookup; | ||
} | ||
module.exports = { | ||
@@ -46,3 +57,4 @@ buildLookup: buildLookup, | ||
toArray: toArray, | ||
toHash: toHash | ||
toHash: toHash, | ||
lookupFromArray: lookupFromArray | ||
}; |
var _ = require("underscore"), | ||
lookup = require("./lookup"); | ||
lookup = require("./lookup"), | ||
Writable = require('stream').Writable, | ||
inherits = require('inherits'); | ||
@@ -9,2 +11,4 @@ var NeuralNetwork = function(options) { | ||
this.hiddenSizes = options.hiddenLayers; | ||
this.binaryThresh = options.binaryThresh || 0.5; | ||
} | ||
@@ -176,4 +180,10 @@ | ||
formatData: function(data) { | ||
if (!_.isArray(data)) { // turn stream datum into array | ||
var tmp = []; | ||
tmp.push(data); | ||
data = tmp; | ||
} | ||
// turn sparse hash input into arrays with 0s as filler | ||
if (!_(data[0].input).isArray()) { | ||
var datum = data[0].input; | ||
if (!_(datum).isArray() && !(datum instanceof Float64Array)) { | ||
if (!this.inputLookup) { | ||
@@ -200,5 +210,4 @@ this.inputLookup = lookup.buildLookup(_(data).pluck("input")); | ||
test : function(data, binaryThresh) { | ||
test : function(data) { | ||
data = this.formatData(data); | ||
binaryThresh = binaryThresh || 0.5; | ||
@@ -224,3 +233,3 @@ // for binary classification problems with one output node | ||
if (isBinary) { | ||
actual = output[0] > binaryThresh ? 1 : 0; | ||
actual = output[0] > this.binaryThresh ? 1 : 0; | ||
expected = target[0]; | ||
@@ -329,3 +338,3 @@ } | ||
} | ||
return { layers: layers }; | ||
return { layers: layers, outputLookup:!!this.outputLookup, inputLookup:!!this.inputLookup }; | ||
}, | ||
@@ -344,6 +353,6 @@ | ||
var layer = json.layers[i]; | ||
if (i == 0 && !layer[0]) { | ||
if (i == 0 && (!layer[0] || json.inputLookup)) { | ||
this.inputLookup = lookup.lookupFromHash(layer); | ||
} | ||
else if (i == this.outputLayer && !layer[0]) { | ||
else if (i == this.outputLayer && (!layer[0] || json.outputLookup)) { | ||
this.outputLookup = lookup.lookupFromHash(layer); | ||
@@ -388,2 +397,12 @@ } | ||
return output;'); | ||
}, | ||
// This will create a TrainStream (WriteStream) | ||
// for us to send the training data to. | ||
// param: opts - the training options | ||
createTrainStream: function(opts) { | ||
opts = opts || {}; | ||
opts.neuralNetwork = this; | ||
this.trainStream = new TrainStream(opts); | ||
return this.trainStream; | ||
} | ||
@@ -422,1 +441,133 @@ } | ||
exports.NeuralNetwork = NeuralNetwork; | ||
function TrainStream(opts) { | ||
Writable.call(this, { | ||
objectMode: true | ||
}); | ||
opts = opts || {}; | ||
// require the neuralNetwork | ||
if (!opts.neuralNetwork) { | ||
throw new Error('no neural network specified'); | ||
} | ||
this.neuralNetwork = opts.neuralNetwork; | ||
this.dataFormatDetermined = false; | ||
this.inputKeys = []; | ||
this.outputKeys = []; // keeps track of keys seen | ||
this.i = 0; // keep track of the for loop i variable that we got rid of | ||
this.iterations = opts.iterations || 20000; | ||
this.errorThresh = opts.errorThresh || 0.005; | ||
this.log = opts.log || false; | ||
this.logPeriod = opts.logPeriod || 10; | ||
this.callback = opts.callback; | ||
this.callbackPeriod = opts.callbackPeriod || 10; | ||
this.floodCallback = opts.floodCallback; | ||
this.doneTrainingCallback = opts.doneTrainingCallback; | ||
this.size = 0; | ||
this.count = 0; | ||
this.sum = 0; | ||
this.on('finish', this.finishStreamIteration); | ||
return this; | ||
} | ||
inherits(TrainStream, Writable); | ||
/* | ||
_write expects data to be in the form of a datum. | ||
ie. {input: {a: 1 b: 0}, output: {z: 0}} | ||
*/ | ||
TrainStream.prototype._write = function(chunk, enc, next) { | ||
if (!chunk) { // check for the end of one interation of the stream | ||
this.emit('finish'); | ||
return next(); | ||
} | ||
if (!this.dataFormatDetermined) { | ||
this.size++; | ||
this.inputKeys = _.union(this.inputKeys, _.keys(chunk.input)); | ||
this.outputKeys = _.union(this.outputKeys, _.keys(chunk.output)); | ||
this.firstDatum = this.firstDatum || chunk; | ||
return next(); | ||
} | ||
this.count++; | ||
var data = this.neuralNetwork.formatData(chunk); | ||
this.trainDatum(data[0]); | ||
// tell the Readable Stream that we are ready for more data | ||
next(); | ||
} | ||
TrainStream.prototype.trainDatum = function(datum) { | ||
var err = this.neuralNetwork.trainPattern(datum.input, datum.output); | ||
this.sum += err; | ||
} | ||
TrainStream.prototype.finishStreamIteration = function() { | ||
if (this.dataFormatDetermined && this.size !== this.count) { | ||
console.log("This iteration's data length was different from the first."); | ||
} | ||
if (!this.dataFormatDetermined) { | ||
// create the lookup | ||
this.neuralNetwork.inputLookup = lookup.lookupFromArray(this.inputKeys); | ||
this.neuralNetwork.outputLookup = lookup.lookupFromArray(this.outputKeys); | ||
var data = this.neuralNetwork.formatData(this.firstDatum); | ||
var inputSize = data[0].input.length; | ||
var outputSize = data[0].output.length; | ||
var hiddenSizes = this.hiddenSizes; | ||
if (!hiddenSizes) { | ||
hiddenSizes = [Math.max(3, Math.floor(inputSize / 2))]; | ||
} | ||
var sizes = _([inputSize, hiddenSizes, outputSize]).flatten(); | ||
this.dataFormatDetermined = true; | ||
this.neuralNetwork.initialize(sizes); | ||
if (typeof this.floodCallback === 'function') { | ||
this.floodCallback(); | ||
} | ||
return; | ||
} | ||
var error = this.sum / this.size; | ||
if (this.log && (this.i % this.logPeriod == 0)) { | ||
console.log("iterations:", this.i, "training error:", error); | ||
} | ||
if (this.callback && (this.i % this.callbackPeriod == 0)) { | ||
this.callback({ | ||
error: error, | ||
iterations: this.i | ||
}); | ||
} | ||
this.sum = 0; | ||
this.count = 0; | ||
// update the iterations | ||
this.i++; | ||
// do a check here to see if we need the stream again | ||
if (this.i < this.iterations && error > this.errorThresh) { | ||
if (typeof this.floodCallback === 'function') { | ||
return this.floodCallback(); | ||
} | ||
} else { | ||
// done training | ||
if (typeof this.doneTrainingCallback === 'function') { | ||
return this.doneTrainingCallback({ | ||
error: error, | ||
iterations: this.i | ||
}); | ||
} | ||
} | ||
} |
{ | ||
"name": "brain", | ||
"description": "Neural network library", | ||
"version": "0.6.1", | ||
"version": "0.6.2", | ||
"author": "Heather Arthur <fayearthur@gmail.com>", | ||
@@ -12,3 +12,4 @@ "repository": { | ||
"dependencies": { | ||
"underscore" : ">=1.3.3" | ||
"underscore" : ">=1.5.1", | ||
"inherits": "~2.0.1" | ||
}, | ||
@@ -15,0 +16,0 @@ "devDependencies" : { |
@@ -21,3 +21,3 @@ # brain | ||
npm install brain | ||
npm install brain | ||
@@ -92,3 +92,24 @@ # Using in the browser | ||
``` | ||
# Streams | ||
The network now has a [WriteStream](http://nodejs.org/api/stream.html#stream_class_stream_writable). You can train the network by using `pipe()` to send the training data to the network. | ||
#### Example | ||
Refer to `test/unit/stream-bitwise.js` for an example on how to train the network with a stream. | ||
#### Initialization | ||
To train the network using a stream you must first create the stream by calling `net.createTrainStream()` which takes the following options: | ||
* `floodCallback()` - the callback function to re-populate the stream. This gets called on every training iteration. | ||
* `doneTrainingCallback(info)` - the callback function to execute when the network is done training. The `info` param will contain a hash of information about how the training went: | ||
```javascript | ||
{ | ||
error: 0.0039139985510105032, // training error | ||
iterations: 406 // training iterations | ||
} | ||
``` | ||
#### Transform | ||
Use a [Transform](http://nodejs.org/api/stream.html#stream_class_stream_transform) to coerce the data into the correct format. You might also use a Transform stream to normalize your data on the fly. | ||
# Options | ||
@@ -95,0 +116,0 @@ `NeuralNetwork()` takes a hash of options: |
@@ -7,6 +7,6 @@ var assert = require('should'), | ||
net.train([{input: {a: Math.random(), b: Math.random()}, | ||
output: {c: Math.random(), d: Math.random()}}, | ||
{input: {a: Math.random(), b: Math.random()}, | ||
output: {c: Math.random(), d: Math.random()}}]); | ||
net.train([{input: {"0": Math.random(), b: Math.random()}, | ||
output: {c: Math.random(), "0": Math.random()}}, | ||
{input: {"0": Math.random(), b: Math.random()}, | ||
output: {c: Math.random(), "0": Math.random()}}]); | ||
@@ -16,3 +16,3 @@ var serialized = net.toJSON(); | ||
var input = {a : Math.random(), b: Math.random()}; | ||
var input = {"0" : Math.random(), b: Math.random()}; | ||
@@ -19,0 +19,0 @@ it('toJSON()/fromJSON()', function() { |
166940
5.15%25
4.17%3792
6.76%139
17.8%2
100%+ Added
+ Added
Updated