Comparar commits
243 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 0b31eed0d1 | |||
| a5fbefea5a | |||
| 3d8265d804 | |||
| 34c93cb718 | |||
| d3e2ea8488 | |||
| 4cfd5a828f | |||
| 24807634e8 | |||
| ebac6d8c33 | |||
| b0a9559beb | |||
| 5a6c487102 | |||
| 9a06d558aa | |||
| 0a544dbc9e | |||
| 3c433021e5 | |||
| e2240eb7b3 | |||
| efb642c2a7 | |||
| 697de49de0 | |||
| 82d64f27a9 | |||
| 116df545fe | |||
| e6953b5028 | |||
| 47745614e3 | |||
| 5041da2c25 | |||
| 05113ec720 | |||
| d3044fbe2a | |||
| f6e8d79f2a | |||
| e1ff6b86df | |||
| c233227b10 | |||
| 8ff4f28075 | |||
| 533d77aaea | |||
| 29bd2ecf28 | |||
| 8a7d82b25c | |||
| 45e4cc2ab3 | |||
| bd5062bdaa | |||
| 0345fe648d | |||
| b820493823 | |||
| 7ee75f9984 | |||
| 25e1e151a2 | |||
| c5be6ffa3d | |||
| 50d28c328f | |||
| 5524e29646 | |||
| abdce5117a | |||
| a8d237f577 | |||
| 238be47c90 | |||
| 4a8301d3f9 | |||
| 6fc3bbf898 | |||
| 8895db5052 | |||
| dab9030d19 | |||
| fb0037bb0e | |||
| 74cdcc35ac | |||
| 82375f977d | |||
| b752e29788 | |||
| ab3470dd8d | |||
| b71201262a | |||
| e8ce3a998c | |||
| afeb75eeef | |||
| 65dbed4936 | |||
| 3975fcb894 | |||
| f22842a350 | |||
| afa660ecde | |||
| d9ea8fac74 | |||
| 576e8977fd | |||
| 806387cae5 | |||
| c44c226151 | |||
| a5d5045784 | |||
| b2c3220c70 | |||
| b1a0846e87 | |||
| bdefeb2853 | |||
| 7bc08646ab | |||
| a4042c4364 | |||
| 4d50133633 | |||
| 169bea133c | |||
| 6da278357f | |||
| 26dbe8ceee | |||
| 8e19e852a0 | |||
| 06b4566eb3 | |||
| 96f88da732 | |||
| a3bd74e72a | |||
| db2a42330e | |||
| e0f20561c2 | |||
| ec8cd5f29b | |||
| f4eed6a396 | |||
| 70b974ddcd | |||
| 892aa1b349 | |||
| 52fe07fc95 | |||
| d4aa1d23ac | |||
| ed5e287a40 | |||
| 8be394c4c7 | |||
| 26b75f9542 | |||
| e4b1639bf4 | |||
| 8c4e3db35c | |||
| cbfe912e54 | |||
| f1c5d7c9e2 | |||
| a8e7ca02df | |||
| b7664e608a | |||
| bbf1046fdc | |||
| ab840c2150 | |||
| 798462380c | |||
| 473e07962a | |||
| 10ad23e88b | |||
| 84f78fcd7c | |||
| fe91cf0b5b | |||
| 65a07d8308 | |||
| e0a6a21977 | |||
| ec02e046f8 | |||
| fd44c0bdfe | |||
| d03c61455c | |||
| a59a06d4dc | |||
| bae5eb462c | |||
| 2b30b70af0 | |||
| 43fef38be9 | |||
| dbe8fd1526 | |||
| 35edc26484 | |||
| 36de19118f | |||
| ec0b9f4b6a | |||
| 902eb992b8 | |||
| 1d129e50d6 | |||
| a407542612 | |||
| 3a729890cb | |||
| d13062eed0 | |||
| 9724b4457f | |||
| 3f1f5b483a | |||
| ef0b488b2f | |||
| fcde4bd112 | |||
| 10cf382145 | |||
| 321fb4ea4c | |||
| 346cf5669f | |||
| b26d7e03c2 | |||
| ac4c2684b2 | |||
| 418610215e | |||
| 281474b46e | |||
| c2ebf9c054 | |||
| 00809d2fd2 | |||
| 91b8abab12 | |||
| 40a1f896b4 | |||
| a58a3aae00 | |||
| 6fb5a051fe | |||
| 64eac61a99 | |||
| 1e00acb166 | |||
| 1b430ae012 | |||
| 69e2acabed | |||
| 4c14532dce | |||
| 846b25ea84 | |||
| af7a44c808 | |||
| 2c7de4e9fa | |||
| 2d4d15bff1 | |||
| a6dcace957 | |||
| ae0e77c1e0 | |||
| 9535b635ef | |||
| 2f8c98a399 | |||
| b5f4249cdb | |||
| d91a445a98 | |||
| 17f049a6f7 | |||
| c31aa638df | |||
| 7373c1268f | |||
| f1c87a7860 | |||
| c62f108ea5 | |||
| 8a25ecda4d | |||
| 0e2300aa98 | |||
| 1470f5e3db | |||
| a62b054c65 | |||
| 245eaab620 | |||
| 912976791b | |||
| 83264fb988 | |||
| e0cd3dee9e | |||
| 2e8428c3a5 | |||
| 8acbda38ec | |||
| 92b083bc92 | |||
| 933aceb869 | |||
| 8c0374782d | |||
| 190f9a859a | |||
| 15b4069854 | |||
| 8bdebef70f | |||
| eb46ae5535 | |||
| d7f268e2ce | |||
| 0829ba97a2 | |||
| 76de621d8d | |||
| ee9f140c6d | |||
| ccd242b8e2 | |||
| ea63c9f2e1 | |||
| d3e1e352be | |||
| 88c5867f56 | |||
| d8c4da5243 | |||
| 2f0d447a87 | |||
| 3cb8a62fe9 | |||
| a1a83b6f0b | |||
| cb46ea001a | |||
| 2fe1d77df2 | |||
| d16c16fd8d | |||
| 71648fa5c7 | |||
| e41739a92b | |||
| ee25127159 | |||
| 48d67de2b2 | |||
| 98f41f6fbf | |||
| 3f94d1837e | |||
| 919b1e8cf1 | |||
| 0094d1219c | |||
| 5d77f4c906 | |||
| 84732a8ccf | |||
| 2a65c2d28d | |||
| c0f2565a9f | |||
| 95ea3fb5e5 | |||
| bd47c96f4f | |||
| d8070e28a3 | |||
| be38adda7c | |||
| cf4db74983 | |||
| 5c14bd1247 | |||
| f7f71d11d4 | |||
| 53924ae6a5 | |||
| c100f2dae5 | |||
| 3613965a98 | |||
| 6555fdfbc7 | |||
| e3661bc6f8 | |||
| 141d8632cf | |||
| 8c7a2453a4 | |||
| f73820a72b | |||
| e939790d5e | |||
| fb5694e328 | |||
| 42a693c7da | |||
| b833deb547 | |||
| e4dc16787b | |||
| b7541310b8 | |||
| 1fad9095c8 | |||
| 65dbc1d60c | |||
| 8e305c4f51 | |||
| 7d13fb3f7a | |||
| 2377bb228a | |||
| 3ac81149ac | |||
| a50c99da8e | |||
| b00c643105 | |||
| 7d129876bb | |||
| bb6a51dfd3 | |||
| 8931e57af7 | |||
| 9271828b5f | |||
| 2e46c8c031 | |||
| 68fd02f8b6 | |||
| 06ab9450e8 | |||
| 00037d95dc | |||
| 0de7d91c91 | |||
| 3729f879ea | |||
| f5c7b7b64b | |||
| 454439b058 | |||
| 47bc37f9e9 | |||
| 2958d346b7 | |||
| 89ad4aa326 |
@@ -0,0 +1,20 @@
|
||||
# Mac.
|
||||
.DS_STORE
|
||||
|
||||
# Node.
|
||||
node_modules
|
||||
|
||||
# Tmp files.
|
||||
*~
|
||||
|
||||
# Dot.
|
||||
*.dot
|
||||
*.png
|
||||
|
||||
# Demo.
|
||||
demo.js
|
||||
|
||||
# Degub
|
||||
debug.html
|
||||
|
||||
.settings
|
||||
@@ -0,0 +1,9 @@
|
||||
language: node_js
|
||||
script: "npm run test:travis"
|
||||
node_js:
|
||||
# always latest release
|
||||
- "node"
|
||||
# previous releases
|
||||
- "6"
|
||||
- "5"
|
||||
- "4"
|
||||
+29
-5
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Juan Cazala
|
||||
Copyright (c) 2016 Juan Cazala - juancazala.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -9,14 +9,38 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE
|
||||
|
||||
|
||||
|
||||
********************************************************************************************
|
||||
SYNAPTIC (v1.0.8)
|
||||
********************************************************************************************
|
||||
|
||||
Synaptic is a javascript neural network library for node.js and the browser, its generalized
|
||||
algorithm is architecture-free, so you can build and train basically any type of first order
|
||||
or even second order neural network architectures.
|
||||
|
||||
http://en.wikipedia.org/wiki/Recurrent_neural_network#Second_Order_Recurrent_Neural_Network
|
||||
|
||||
The library includes a few built-in architectures like multilayer perceptrons, multilayer
|
||||
long-short term memory networks (LSTM) or liquid state machines, and a trainer capable of
|
||||
training any given network, and includes built-in training tasks/tests like solving an XOR,
|
||||
passing a Distracted Sequence Recall test or an Embeded Reber Grammar test.
|
||||
|
||||
The algorithm implemented by this library has been taken from Derek D. Monner's paper:
|
||||
|
||||
|
||||
A generalized LSTM-like training algorithm for second-order recurrent neural networks
|
||||
http://www.overcomplete.net/papers/nn2012.pdf
|
||||
|
||||
There are references to the equations in that paper commented through the source code.
|
||||
|
||||
Arquivo executável
+201
@@ -0,0 +1,201 @@
|
||||
Synaptic [](https://travis-ci.org/cazala/synaptic) [](https://synaptic-slack-ugiqacqvmd.now.sh/)
|
||||
========
|
||||
|
||||
## Important: [Synaptic 2.x](https://github.com/cazala/synaptic/issues/140) is in stage of discussion now! Feel free to participate
|
||||
|
||||
Synaptic is a javascript neural network library for **node.js** and the **browser**, its generalized algorithm is architecture-free, so you can build and train basically any type of first order or even [second order neural network](http://en.wikipedia.org/wiki/Recurrent_neural_network#Second_Order_Recurrent_Neural_Network) architectures.
|
||||
|
||||
This library includes a few built-in architectures like [multilayer perceptrons](http://en.wikipedia.org/wiki/Multilayer_perceptron), [multilayer long-short term memory](http://en.wikipedia.org/wiki/Long_short_term_memory) networks (LSTM), [liquid state machines](http://en.wikipedia.org/wiki/Liquid_state_machine) or [Hopfield](http://en.wikipedia.org/wiki/Hopfield_network) networks, and a trainer capable of training any given network, which includes built-in training tasks/tests like solving an XOR, completing a Distracted Sequence Recall task or an [Embedded Reber Grammar](http://www.willamette.edu/~gorr/classes/cs449/reber.html) test, so you can easily test and compare the performance of different architectures.
|
||||
|
||||
|
||||
The algorithm implemented by this library has been taken from Derek D. Monner's paper:
|
||||
|
||||
[A generalized LSTM-like training algorithm for second-order recurrent neural networks](http://www.overcomplete.net/papers/nn2012.pdf)
|
||||
|
||||
|
||||
There are references to the equations in that paper commented through the source code.
|
||||
|
||||
####Introduction
|
||||
|
||||
If you have no prior knowledge about Neural Networks, you should start by [reading this guide](https://github.com/cazala/synaptic/wiki/Neural-Networks-101).
|
||||
|
||||
|
||||
If you want a practical example on how to feed data to a neural network, then take a look at [this article](https://github.com/cazala/synaptic/wiki/Normalization-101).
|
||||
|
||||
You may also want to take a look at [this article](http://blog.webkid.io/neural-networks-in-javascript/).
|
||||
|
||||
####Demos
|
||||
|
||||
- [Solve an XOR](http://caza.la/synaptic/#/xor)
|
||||
- [Discrete Sequence Recall Task](http://caza.la/synaptic/#/dsr)
|
||||
- [Learn Image Filters](http://caza.la/synaptic/#/image-filters)
|
||||
- [Paint an Image](http://caza.la/synaptic/#/paint-an-image)
|
||||
- [Self Organizing Map](http://caza.la/synaptic/#/self-organizing-map)
|
||||
- [Read from Wikipedia](http://caza.la/synaptic/#/wikipedia)
|
||||
|
||||
The source code of these demos can be found in [this branch](https://github.com/cazala/synaptic/tree/gh-pages/scripts).
|
||||
|
||||
####Getting started
|
||||
|
||||
- [Neurons](https://github.com/cazala/synaptic/wiki/Neurons/)
|
||||
- [Layers](https://github.com/cazala/synaptic/wiki/Layers/)
|
||||
- [Networks](https://github.com/cazala/synaptic/wiki/Networks/)
|
||||
- [Trainer](https://github.com/cazala/synaptic/wiki/Trainer/)
|
||||
- [Architect](https://github.com/cazala/synaptic/wiki/Architect/)
|
||||
|
||||
To try out the examples, checkout the [gh-pages](https://github.com/cazala/synaptic/tree/gh-pages) branch.
|
||||
|
||||
`git checkout gh-pages`
|
||||
|
||||
|
||||
##Overview
|
||||
|
||||
###Installation
|
||||
|
||||
#####In node
|
||||
|
||||
You can install synaptic with [npm](http://npmjs.org):
|
||||
|
||||
```cmd
|
||||
npm install synaptic --save
|
||||
```
|
||||
|
||||
#####In the browser
|
||||
|
||||
You can install synaptic with [bower](http://bower.io):
|
||||
|
||||
```cmd
|
||||
bower install synaptic
|
||||
```
|
||||
|
||||
Or you can simply use the CDN link, kindly provided by [CDNjs](https://cdnjs.com/)
|
||||
|
||||
```html
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/synaptic/1.0.8/synaptic.js"></script>
|
||||
```
|
||||
|
||||
###Usage
|
||||
|
||||
```javascript
|
||||
var synaptic = require('synaptic'); // this line is not needed in the browser
|
||||
var Neuron = synaptic.Neuron,
|
||||
Layer = synaptic.Layer,
|
||||
Network = synaptic.Network,
|
||||
Trainer = synaptic.Trainer,
|
||||
Architect = synaptic.Architect;
|
||||
```
|
||||
|
||||
Now you can start to create networks, train them, or use built-in networks from the [Architect](http://github.com/cazala/synaptic#architect).
|
||||
|
||||
###Examples
|
||||
|
||||
#####Perceptron
|
||||
|
||||
This is how you can create a simple **perceptron**:
|
||||
|
||||
.
|
||||
|
||||
```javascript
|
||||
function Perceptron(input, hidden, output)
|
||||
{
|
||||
// create the layers
|
||||
var inputLayer = new Layer(input);
|
||||
var hiddenLayer = new Layer(hidden);
|
||||
var outputLayer = new Layer(output);
|
||||
|
||||
// connect the layers
|
||||
inputLayer.project(hiddenLayer);
|
||||
hiddenLayer.project(outputLayer);
|
||||
|
||||
// set the layers
|
||||
this.set({
|
||||
input: inputLayer,
|
||||
hidden: [hiddenLayer],
|
||||
output: outputLayer
|
||||
});
|
||||
}
|
||||
|
||||
// extend the prototype chain
|
||||
Perceptron.prototype = new Network();
|
||||
Perceptron.prototype.constructor = Perceptron;
|
||||
```
|
||||
|
||||
Now you can test your new network by creating a trainer and teaching the perceptron to learn an XOR
|
||||
|
||||
```javascript
|
||||
var myPerceptron = new Perceptron(2,3,1);
|
||||
var myTrainer = new Trainer(myPerceptron);
|
||||
|
||||
myTrainer.XOR(); // { error: 0.004998819355993572, iterations: 21871, time: 356 }
|
||||
|
||||
myPerceptron.activate([0,0]); // 0.0268581547421616
|
||||
myPerceptron.activate([1,0]); // 0.9829673642853368
|
||||
myPerceptron.activate([0,1]); // 0.9831714267395621
|
||||
myPerceptron.activate([1,1]); // 0.02128894618097928
|
||||
```
|
||||
|
||||
#####Long Short-Term Memory
|
||||
|
||||
This is how you can create a simple **long short-term memory** network with input gate, forget gate, output gate, and peephole connections:
|
||||
|
||||

|
||||
|
||||
```javascript
|
||||
function LSTM(input, blocks, output)
|
||||
{
|
||||
// create the layers
|
||||
var inputLayer = new Layer(input);
|
||||
var inputGate = new Layer(blocks);
|
||||
var forgetGate = new Layer(blocks);
|
||||
var memoryCell = new Layer(blocks);
|
||||
var outputGate = new Layer(blocks);
|
||||
var outputLayer = new Layer(output);
|
||||
|
||||
// connections from input layer
|
||||
var input = inputLayer.project(memoryCell);
|
||||
inputLayer.project(inputGate);
|
||||
inputLayer.project(forgetGate);
|
||||
inputLayer.project(outputGate);
|
||||
|
||||
// connections from memory cell
|
||||
var output = memoryCell.project(outputLayer);
|
||||
|
||||
// self-connection
|
||||
var self = memoryCell.project(memoryCell);
|
||||
|
||||
// peepholes
|
||||
memoryCell.project(inputGate);
|
||||
memoryCell.project(forgetGate);
|
||||
memoryCell.project(outputGate);
|
||||
|
||||
// gates
|
||||
inputGate.gate(input, Layer.gateType.INPUT);
|
||||
forgetGate.gate(self, Layer.gateType.ONE_TO_ONE);
|
||||
outputGate.gate(output, Layer.gateType.OUTPUT);
|
||||
|
||||
// input to output direct connection
|
||||
inputLayer.project(outputLayer);
|
||||
|
||||
// set the layers of the neural network
|
||||
this.set({
|
||||
input: inputLayer,
|
||||
hidden: [inputGate, forgetGate, memoryCell, outputGate],
|
||||
output: outputLayer
|
||||
});
|
||||
}
|
||||
|
||||
// extend the prototype chain
|
||||
LSTM.prototype = new Network();
|
||||
LSTM.prototype.constructor = LSTM;
|
||||
```
|
||||
|
||||
These are examples for explanatory purposes, the [Architect](https://github.com/cazala/synaptic/wiki/Architect/) already includes Multilayer Perceptrons and
|
||||
Multilayer LSTM network architectures.
|
||||
|
||||
##Contribute
|
||||
|
||||
**Synaptic** is an Open Source project that started in Buenos Aires, Argentina. Anybody in the world is welcome to contribute to the development of the project.
|
||||
|
||||
If you want to contribute feel free to send PR's, just make sure to run **npm run test** and **npm run build** before submiting it. This way you'll run all the test specs and build the web distribution files.
|
||||
|
||||
<3
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "synaptic",
|
||||
"version": "1.0.8",
|
||||
"homepage": "https://github.com/cazala/synaptic",
|
||||
"authors": [
|
||||
"Juan Cazala <juancazala@gmail.com>"
|
||||
],
|
||||
"description": "architecture-free neural network library for node.js and the browser",
|
||||
"main": "./dist/synaptic.min.js",
|
||||
"moduleType": [
|
||||
"amd",
|
||||
"globals",
|
||||
"node"
|
||||
],
|
||||
"keywords": [
|
||||
"neural",
|
||||
"network",
|
||||
"deep",
|
||||
"learning",
|
||||
"machine",
|
||||
"learning",
|
||||
"lstm",
|
||||
"perceptron"
|
||||
],
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
]
|
||||
}
|
||||
externo
+2848
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
externo
+2848
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -0,0 +1,25 @@
|
||||
// Karma configuration
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['mocha'],
|
||||
files: [
|
||||
'dist/synaptic.js',
|
||||
'test/[^_]*.js'
|
||||
],
|
||||
exclude: [
|
||||
],
|
||||
preprocessors: {
|
||||
'test/*.js': ['webpack'],
|
||||
},
|
||||
reporters: ['progress'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
singleRun: false,
|
||||
concurrency: Infinity,
|
||||
browserNoActivityTimeout: 60000,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "synaptic",
|
||||
"version": "1.0.9",
|
||||
"description": "architecture-free neural network library",
|
||||
"main": "./src/synaptic",
|
||||
"scripts": {
|
||||
"test": "npm run test:src",
|
||||
"test:src": "mocha test --require src/synaptic.js ./test",
|
||||
"test:dist": "npm run build && npm run test:mocha:dist && npm run test:karma:browsers",
|
||||
"test:mocha:src": "mocha test --require src/synaptic.js ./test",
|
||||
"test:mocha:dist": "mocha test --require dist/synaptic.js ./test",
|
||||
"test:karma:browsers": "karma start --single-run --browsers Chrome,Firefox,SafariPrivate",
|
||||
"test:karma:phantomjs": "karma start --single-run --browsers PhantomJS",
|
||||
"test:travis": "npm run test:mocha:src && npm run build && npm run test:mocha:dist",
|
||||
"build": "webpack --config webpack.config.js"
|
||||
},
|
||||
"prepush": [
|
||||
"test",
|
||||
"build"
|
||||
],
|
||||
"devDependencies": {
|
||||
"chai": "^3.5.0",
|
||||
"chai-stats": "^0.3.0",
|
||||
"karma": "^1.1.2",
|
||||
"karma-chrome-launcher": "^1.0.1",
|
||||
"karma-firefox-launcher": "^1.0.0",
|
||||
"karma-mocha": "^1.1.1",
|
||||
"karma-phantomjs-launcher": "^1.0.1",
|
||||
"karma-safari-launcher": "^1.0.0",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"mocha": "^2.2.4",
|
||||
"pre-push": "^0.1.1",
|
||||
"webpack": "^1.13.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/cazala/synaptic.git"
|
||||
},
|
||||
"keywords": [
|
||||
"neural network",
|
||||
"machine learning",
|
||||
"long short term memory",
|
||||
"perceptron",
|
||||
"architecture free"
|
||||
],
|
||||
"author": "Juan Cazala <juancazala@gmail.com> (http://juancazala.com/)",
|
||||
"license": {
|
||||
"type": "MIT",
|
||||
"url": "https://github.com/cazala/synaptic/blob/master/LICENSE"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/cazala/synaptic/issues"
|
||||
},
|
||||
"homepage": "http://synaptic.juancazala.com",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// update license year and version
|
||||
var fs = require('fs')
|
||||
module.exports = function() {
|
||||
var year = (new Date).getFullYear()
|
||||
var version = require('./package.json').version
|
||||
// LICENSE
|
||||
var license = fs.readFileSync('LICENSE', 'utf-8')
|
||||
.replace(/\(c\) ([0-9]+)/, `(c) ${year}`)
|
||||
.replace(/SYNAPTIC \(v(.*)\)/, `SYNAPTIC (v${version})`)
|
||||
fs.writeFileSync('LICENSE', license)
|
||||
// bower.json
|
||||
var bower = fs.readFileSync('bower.json', 'utf-8')
|
||||
.replace(/\"version\": \"(.*)\",/, `"version": "${version}",`)
|
||||
fs.writeFileSync('bower.json', bower)
|
||||
// README.md
|
||||
var readme = fs.readFileSync('README.md', 'utf-8')
|
||||
.replace(/ajax\/libs\/synaptic\/(.*)\/synaptic.js/, `ajax/libs/synaptic/${version}/synaptic.js`)
|
||||
fs.writeFileSync('README.md', readme)
|
||||
// return license for dist banner
|
||||
return license
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
// import
|
||||
var Layer = require('./layer')
|
||||
, Network = require('./network')
|
||||
, Trainer = require('./trainer')
|
||||
|
||||
/*******************************************************************************************
|
||||
ARCHITECT
|
||||
*******************************************************************************************/
|
||||
|
||||
// Collection of useful built-in architectures
|
||||
var Architect = {
|
||||
|
||||
// Multilayer Perceptron
|
||||
Perceptron: function Perceptron() {
|
||||
|
||||
var args = Array.prototype.slice.call(arguments); // convert arguments to Array
|
||||
if (args.length < 3)
|
||||
throw new Error("not enough layers (minimum 3) !!");
|
||||
|
||||
var inputs = args.shift(); // first argument
|
||||
var outputs = args.pop(); // last argument
|
||||
var layers = args; // all the arguments in the middle
|
||||
|
||||
var input = new Layer(inputs);
|
||||
var hidden = [];
|
||||
var output = new Layer(outputs);
|
||||
|
||||
var previous = input;
|
||||
|
||||
// generate hidden layers
|
||||
for (var level in layers) {
|
||||
var size = layers[level];
|
||||
var layer = new Layer(size);
|
||||
hidden.push(layer);
|
||||
previous.project(layer);
|
||||
previous = layer;
|
||||
}
|
||||
previous.project(output);
|
||||
|
||||
// set layers of the neural network
|
||||
this.set({
|
||||
input: input,
|
||||
hidden: hidden,
|
||||
output: output
|
||||
});
|
||||
|
||||
// trainer for the network
|
||||
this.trainer = new Trainer(this);
|
||||
},
|
||||
|
||||
// Multilayer Long Short-Term Memory
|
||||
LSTM: function LSTM() {
|
||||
|
||||
var args = Array.prototype.slice.call(arguments); // convert arguments to array
|
||||
if (args.length < 3)
|
||||
throw new Error("not enough layers (minimum 3) !!");
|
||||
|
||||
var last = args.pop();
|
||||
var option = {
|
||||
peepholes: Layer.connectionType.ALL_TO_ALL,
|
||||
hiddenToHidden: false,
|
||||
outputToHidden: false,
|
||||
outputToGates: false,
|
||||
inputToOutput: true,
|
||||
};
|
||||
if (typeof last != 'number') {
|
||||
var outputs = args.pop();
|
||||
if (last.hasOwnProperty('peepholes'))
|
||||
option.peepholes = last.peepholes;
|
||||
if (last.hasOwnProperty('hiddenToHidden'))
|
||||
option.hiddenToHidden = last.hiddenToHidden;
|
||||
if (last.hasOwnProperty('outputToHidden'))
|
||||
option.outputToHidden = last.outputToHidden;
|
||||
if (last.hasOwnProperty('outputToGates'))
|
||||
option.outputToGates = last.outputToGates;
|
||||
if (last.hasOwnProperty('inputToOutput'))
|
||||
option.inputToOutput = last.inputToOutput;
|
||||
} else
|
||||
var outputs = last;
|
||||
|
||||
var inputs = args.shift();
|
||||
var layers = args;
|
||||
|
||||
var inputLayer = new Layer(inputs);
|
||||
var hiddenLayers = [];
|
||||
var outputLayer = new Layer(outputs);
|
||||
|
||||
var previous = null;
|
||||
|
||||
// generate layers
|
||||
for (var layer in layers) {
|
||||
// generate memory blocks (memory cell and respective gates)
|
||||
var size = layers[layer];
|
||||
|
||||
var inputGate = new Layer(size).set({
|
||||
bias: 1
|
||||
});
|
||||
var forgetGate = new Layer(size).set({
|
||||
bias: 1
|
||||
});
|
||||
var memoryCell = new Layer(size);
|
||||
var outputGate = new Layer(size).set({
|
||||
bias: 1
|
||||
});
|
||||
|
||||
hiddenLayers.push(inputGate);
|
||||
hiddenLayers.push(forgetGate);
|
||||
hiddenLayers.push(memoryCell);
|
||||
hiddenLayers.push(outputGate);
|
||||
|
||||
// connections from input layer
|
||||
var input = inputLayer.project(memoryCell);
|
||||
inputLayer.project(inputGate);
|
||||
inputLayer.project(forgetGate);
|
||||
inputLayer.project(outputGate);
|
||||
|
||||
// connections from previous memory-block layer to this one
|
||||
if (previous != null) {
|
||||
var cell = previous.project(memoryCell);
|
||||
previous.project(inputGate);
|
||||
previous.project(forgetGate);
|
||||
previous.project(outputGate);
|
||||
}
|
||||
|
||||
// connections from memory cell
|
||||
var output = memoryCell.project(outputLayer);
|
||||
|
||||
// self-connection
|
||||
var self = memoryCell.project(memoryCell);
|
||||
|
||||
// hidden to hidden recurrent connection
|
||||
if (option.hiddenToHidden)
|
||||
memoryCell.project(memoryCell, Layer.connectionType.ALL_TO_ELSE);
|
||||
|
||||
// out to hidden recurrent connection
|
||||
if (option.outputToHidden)
|
||||
outputLayer.project(memoryCell);
|
||||
|
||||
// out to gates recurrent connection
|
||||
if (option.outputToGates) {
|
||||
outputLayer.project(inputGate);
|
||||
outputLayer.project(outputGate);
|
||||
outputLayer.project(forgetGate);
|
||||
}
|
||||
|
||||
// peepholes
|
||||
memoryCell.project(inputGate, option.peepholes);
|
||||
memoryCell.project(forgetGate, option.peepholes);
|
||||
memoryCell.project(outputGate, option.peepholes);
|
||||
|
||||
// gates
|
||||
inputGate.gate(input, Layer.gateType.INPUT);
|
||||
forgetGate.gate(self, Layer.gateType.ONE_TO_ONE);
|
||||
outputGate.gate(output, Layer.gateType.OUTPUT);
|
||||
if (previous != null)
|
||||
inputGate.gate(cell, Layer.gateType.INPUT);
|
||||
|
||||
previous = memoryCell;
|
||||
}
|
||||
|
||||
// input to output direct connection
|
||||
if (option.inputToOutput)
|
||||
inputLayer.project(outputLayer);
|
||||
|
||||
// set the layers of the neural network
|
||||
this.set({
|
||||
input: inputLayer,
|
||||
hidden: hiddenLayers,
|
||||
output: outputLayer
|
||||
});
|
||||
|
||||
// trainer
|
||||
this.trainer = new Trainer(this);
|
||||
},
|
||||
|
||||
// Liquid State Machine
|
||||
Liquid: function Liquid(inputs, hidden, outputs, connections, gates) {
|
||||
|
||||
// create layers
|
||||
var inputLayer = new Layer(inputs);
|
||||
var hiddenLayer = new Layer(hidden);
|
||||
var outputLayer = new Layer(outputs);
|
||||
|
||||
// make connections and gates randomly among the neurons
|
||||
var neurons = hiddenLayer.neurons();
|
||||
var connectionList = [];
|
||||
|
||||
for (var i = 0; i < connections; i++) {
|
||||
// connect two random neurons
|
||||
var from = Math.random() * neurons.length | 0;
|
||||
var to = Math.random() * neurons.length | 0;
|
||||
var connection = neurons[from].project(neurons[to]);
|
||||
connectionList.push(connection);
|
||||
}
|
||||
|
||||
for (var j = 0; j < gates; j++) {
|
||||
// pick a random gater neuron
|
||||
var gater = Math.random() * neurons.length | 0;
|
||||
// pick a random connection to gate
|
||||
var connection = Math.random() * connectionList.length | 0;
|
||||
// let the gater gate the connection
|
||||
neurons[gater].gate(connectionList[connection]);
|
||||
}
|
||||
|
||||
// connect the layers
|
||||
inputLayer.project(hiddenLayer);
|
||||
hiddenLayer.project(outputLayer);
|
||||
|
||||
// set the layers of the network
|
||||
this.set({
|
||||
input: inputLayer,
|
||||
hidden: [hiddenLayer],
|
||||
output: outputLayer
|
||||
});
|
||||
|
||||
// trainer
|
||||
this.trainer = new Trainer(this);
|
||||
},
|
||||
|
||||
Hopfield: function Hopfield(size) {
|
||||
|
||||
var inputLayer = new Layer(size);
|
||||
var outputLayer = new Layer(size);
|
||||
|
||||
inputLayer.project(outputLayer, Layer.connectionType.ALL_TO_ALL);
|
||||
|
||||
this.set({
|
||||
input: inputLayer,
|
||||
hidden: [],
|
||||
output: outputLayer
|
||||
});
|
||||
|
||||
var trainer = new Trainer(this);
|
||||
|
||||
var proto = Architect.Hopfield.prototype;
|
||||
|
||||
proto.learn = proto.learn || function(patterns)
|
||||
{
|
||||
var set = [];
|
||||
for (var p in patterns)
|
||||
set.push({
|
||||
input: patterns[p],
|
||||
output: patterns[p]
|
||||
});
|
||||
|
||||
return trainer.train(set, {
|
||||
iterations: 500000,
|
||||
error: .00005,
|
||||
rate: 1
|
||||
});
|
||||
};
|
||||
|
||||
proto.feed = proto.feed || function(pattern)
|
||||
{
|
||||
var output = this.activate(pattern);
|
||||
|
||||
var pattern = [];
|
||||
for (var i in output)
|
||||
pattern[i] = output[i] > .5 ? 1 : 0;
|
||||
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extend prototype chain (so every architectures is an instance of Network)
|
||||
for (var architecture in Architect) {
|
||||
Architect[architecture].prototype = new Network();
|
||||
Architect[architecture].prototype.constructor = Architect[architecture];
|
||||
}
|
||||
|
||||
// export
|
||||
if (module) module.exports = Architect;
|
||||
+276
@@ -0,0 +1,276 @@
|
||||
// export
|
||||
if (module) module.exports = Layer;
|
||||
|
||||
// import
|
||||
var Neuron = require('./neuron')
|
||||
, Network = require('./network')
|
||||
|
||||
/*******************************************************************************************
|
||||
LAYER
|
||||
*******************************************************************************************/
|
||||
|
||||
function Layer(size, label) {
|
||||
this.size = size | 0;
|
||||
this.list = [];
|
||||
this.label = label || null;
|
||||
this.connectedTo = [];
|
||||
|
||||
while (size--) {
|
||||
var neuron = new Neuron();
|
||||
this.list.push(neuron);
|
||||
}
|
||||
}
|
||||
|
||||
Layer.prototype = {
|
||||
|
||||
// activates all the neurons in the layer
|
||||
activate: function(input) {
|
||||
|
||||
var activations = [];
|
||||
|
||||
if (typeof input != 'undefined') {
|
||||
if (input.length != this.size)
|
||||
throw new Error("INPUT size and LAYER size must be the same to activate!");
|
||||
|
||||
for (var id in this.list) {
|
||||
var neuron = this.list[id];
|
||||
var activation = neuron.activate(input[id]);
|
||||
activations.push(activation);
|
||||
}
|
||||
} else {
|
||||
for (var id in this.list) {
|
||||
var neuron = this.list[id];
|
||||
var activation = neuron.activate();
|
||||
activations.push(activation);
|
||||
}
|
||||
}
|
||||
return activations;
|
||||
},
|
||||
|
||||
// propagates the error on all the neurons of the layer
|
||||
propagate: function(rate, target) {
|
||||
|
||||
if (typeof target != 'undefined') {
|
||||
if (target.length != this.size)
|
||||
throw new Error("TARGET size and LAYER size must be the same to propagate!");
|
||||
|
||||
for (var id = this.list.length - 1; id >= 0; id--) {
|
||||
var neuron = this.list[id];
|
||||
neuron.propagate(rate, target[id]);
|
||||
}
|
||||
} else {
|
||||
for (var id = this.list.length - 1; id >= 0; id--) {
|
||||
var neuron = this.list[id];
|
||||
neuron.propagate(rate);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// projects a connection from this layer to another one
|
||||
project: function(layer, type, weights) {
|
||||
|
||||
if (layer instanceof Network)
|
||||
layer = layer.layers.input;
|
||||
|
||||
if (layer instanceof Layer) {
|
||||
if (!this.connected(layer))
|
||||
return new Layer.connection(this, layer, type, weights);
|
||||
} else
|
||||
throw new Error("Invalid argument, you can only project connections to LAYERS and NETWORKS!");
|
||||
|
||||
|
||||
},
|
||||
|
||||
// gates a connection betwenn two layers
|
||||
gate: function(connection, type) {
|
||||
|
||||
if (type == Layer.gateType.INPUT) {
|
||||
if (connection.to.size != this.size)
|
||||
throw new Error("GATER layer and CONNECTION.TO layer must be the same size in order to gate!");
|
||||
|
||||
for (var id in connection.to.list) {
|
||||
var neuron = connection.to.list[id];
|
||||
var gater = this.list[id];
|
||||
for (var input in neuron.connections.inputs) {
|
||||
var gated = neuron.connections.inputs[input];
|
||||
if (gated.ID in connection.connections)
|
||||
gater.gate(gated);
|
||||
}
|
||||
}
|
||||
} else if (type == Layer.gateType.OUTPUT) {
|
||||
if (connection.from.size != this.size)
|
||||
throw new Error("GATER layer and CONNECTION.FROM layer must be the same size in order to gate!");
|
||||
|
||||
for (var id in connection.from.list) {
|
||||
var neuron = connection.from.list[id];
|
||||
var gater = this.list[id];
|
||||
for (var projected in neuron.connections.projected) {
|
||||
var gated = neuron.connections.projected[projected];
|
||||
if (gated.ID in connection.connections)
|
||||
gater.gate(gated);
|
||||
}
|
||||
}
|
||||
} else if (type == Layer.gateType.ONE_TO_ONE) {
|
||||
if (connection.size != this.size)
|
||||
throw new Error("The number of GATER UNITS must be the same as the number of CONNECTIONS to gate!");
|
||||
|
||||
for (var id in connection.list) {
|
||||
var gater = this.list[id];
|
||||
var gated = connection.list[id];
|
||||
gater.gate(gated);
|
||||
}
|
||||
}
|
||||
connection.gatedfrom.push({layer: this, type: type});
|
||||
},
|
||||
|
||||
// true or false whether the whole layer is self-connected or not
|
||||
selfconnected: function() {
|
||||
|
||||
for (var id in this.list) {
|
||||
var neuron = this.list[id];
|
||||
if (!neuron.selfconnected())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
// true of false whether the layer is connected to another layer (parameter) or not
|
||||
connected: function(layer) {
|
||||
// Check if ALL to ALL connection
|
||||
var connections = 0;
|
||||
for (var here in this.list) {
|
||||
for (var there in layer.list) {
|
||||
var from = this.list[here];
|
||||
var to = layer.list[there];
|
||||
var connected = from.connected(to);
|
||||
if (connected.type == 'projected')
|
||||
connections++;
|
||||
}
|
||||
}
|
||||
if (connections == this.size * layer.size)
|
||||
return Layer.connectionType.ALL_TO_ALL;
|
||||
|
||||
// Check if ONE to ONE connection
|
||||
connections = 0;
|
||||
for (var neuron in this.list) {
|
||||
var from = this.list[neuron];
|
||||
var to = layer.list[neuron];
|
||||
var connected = from.connected(to);
|
||||
if (connected.type == 'projected')
|
||||
connections++;
|
||||
}
|
||||
if (connections == this.size)
|
||||
return Layer.connectionType.ONE_TO_ONE;
|
||||
},
|
||||
|
||||
// clears all the neuorns in the layer
|
||||
clear: function() {
|
||||
for (var id in this.list) {
|
||||
var neuron = this.list[id];
|
||||
neuron.clear();
|
||||
}
|
||||
},
|
||||
|
||||
// resets all the neurons in the layer
|
||||
reset: function() {
|
||||
for (var id in this.list) {
|
||||
var neuron = this.list[id];
|
||||
neuron.reset();
|
||||
}
|
||||
},
|
||||
|
||||
// returns all the neurons in the layer (array)
|
||||
neurons: function() {
|
||||
return this.list;
|
||||
},
|
||||
|
||||
// adds a neuron to the layer
|
||||
add: function(neuron) {
|
||||
this.neurons[neuron.ID] = neuron || new Neuron();
|
||||
this.list.push(neuron);
|
||||
this.size++;
|
||||
},
|
||||
|
||||
set: function(options) {
|
||||
options = options || {};
|
||||
|
||||
for (var i in this.list) {
|
||||
var neuron = this.list[i];
|
||||
if (options.label)
|
||||
neuron.label = options.label + '_' + neuron.ID;
|
||||
if (options.squash)
|
||||
neuron.squash = options.squash;
|
||||
if (options.bias)
|
||||
neuron.bias = options.bias;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// represents a connection from one layer to another, and keeps track of its weight and gain
|
||||
Layer.connection = function LayerConnection(fromLayer, toLayer, type, weights) {
|
||||
this.ID = Layer.connection.uid();
|
||||
this.from = fromLayer;
|
||||
this.to = toLayer;
|
||||
this.selfconnection = toLayer == fromLayer;
|
||||
this.type = type;
|
||||
this.connections = {};
|
||||
this.list = [];
|
||||
this.size = 0;
|
||||
this.gatedfrom = [];
|
||||
|
||||
if (typeof this.type == 'undefined')
|
||||
{
|
||||
if (fromLayer == toLayer)
|
||||
this.type = Layer.connectionType.ONE_TO_ONE;
|
||||
else
|
||||
this.type = Layer.connectionType.ALL_TO_ALL;
|
||||
}
|
||||
|
||||
if (this.type == Layer.connectionType.ALL_TO_ALL ||
|
||||
this.type == Layer.connectionType.ALL_TO_ELSE) {
|
||||
for (var here in this.from.list) {
|
||||
for (var there in this.to.list) {
|
||||
var from = this.from.list[here];
|
||||
var to = this.to.list[there];
|
||||
if(this.type == Layer.connectionType.ALL_TO_ELSE && from == to)
|
||||
continue;
|
||||
var connection = from.project(to, weights);
|
||||
|
||||
this.connections[connection.ID] = connection;
|
||||
this.size = this.list.push(connection);
|
||||
}
|
||||
}
|
||||
} else if (this.type == Layer.connectionType.ONE_TO_ONE) {
|
||||
|
||||
for (var neuron in this.from.list) {
|
||||
var from = this.from.list[neuron];
|
||||
var to = this.to.list[neuron];
|
||||
var connection = from.project(to, weights);
|
||||
|
||||
this.connections[connection.ID] = connection;
|
||||
this.size = this.list.push(connection);
|
||||
}
|
||||
}
|
||||
|
||||
fromLayer.connectedTo.push(this);
|
||||
}
|
||||
|
||||
// types of connections
|
||||
Layer.connectionType = {};
|
||||
Layer.connectionType.ALL_TO_ALL = "ALL TO ALL";
|
||||
Layer.connectionType.ONE_TO_ONE = "ONE TO ONE";
|
||||
Layer.connectionType.ALL_TO_ELSE = "ALL TO ELSE";
|
||||
|
||||
// types of gates
|
||||
Layer.gateType = {};
|
||||
Layer.gateType.INPUT = "INPUT";
|
||||
Layer.gateType.OUTPUT = "OUTPUT";
|
||||
Layer.gateType.ONE_TO_ONE = "ONE TO ONE";
|
||||
|
||||
(function() {
|
||||
var connections = 0;
|
||||
Layer.connection.uid = function() {
|
||||
return connections++;
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,645 @@
|
||||
// export
|
||||
if (module) module.exports = Network;
|
||||
|
||||
// import
|
||||
var Neuron = require('./neuron')
|
||||
, Layer = require('./layer')
|
||||
, Trainer = require('./trainer')
|
||||
|
||||
/*******************************************************************************************
|
||||
NETWORK
|
||||
*******************************************************************************************/
|
||||
|
||||
function Network(layers) {
|
||||
if (typeof layers != 'undefined') {
|
||||
this.layers = layers || {
|
||||
input: null,
|
||||
hidden: {},
|
||||
output: null
|
||||
};
|
||||
this.optimized = null;
|
||||
}
|
||||
}
|
||||
Network.prototype = {
|
||||
|
||||
// feed-forward activation of all the layers to produce an ouput
|
||||
activate: function(input) {
|
||||
|
||||
if (this.optimized === false)
|
||||
{
|
||||
this.layers.input.activate(input);
|
||||
for (var layer in this.layers.hidden)
|
||||
this.layers.hidden[layer].activate();
|
||||
return this.layers.output.activate();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.optimized == null)
|
||||
this.optimize();
|
||||
return this.optimized.activate(input);
|
||||
}
|
||||
},
|
||||
|
||||
// back-propagate the error thru the network
|
||||
propagate: function(rate, target) {
|
||||
|
||||
if (this.optimized === false)
|
||||
{
|
||||
this.layers.output.propagate(rate, target);
|
||||
var reverse = [];
|
||||
for (var layer in this.layers.hidden)
|
||||
reverse.push(this.layers.hidden[layer]);
|
||||
reverse.reverse();
|
||||
for (var layer in reverse)
|
||||
reverse[layer].propagate(rate);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.optimized == null)
|
||||
this.optimize();
|
||||
this.optimized.propagate(rate, target);
|
||||
}
|
||||
},
|
||||
|
||||
// project a connection to another unit (either a network or a layer)
|
||||
project: function(unit, type, weights) {
|
||||
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
|
||||
if (unit instanceof Network)
|
||||
return this.layers.output.project(unit.layers.input, type, weights);
|
||||
|
||||
if (unit instanceof Layer)
|
||||
return this.layers.output.project(unit, type, weights);
|
||||
|
||||
throw new Error("Invalid argument, you can only project connections to LAYERS and NETWORKS!");
|
||||
},
|
||||
|
||||
// let this network gate a connection
|
||||
gate: function(connection, type) {
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
this.layers.output.gate(connection, type);
|
||||
},
|
||||
|
||||
// clear all elegibility traces and extended elegibility traces (the network forgets its context, but not what was trained)
|
||||
clear: function() {
|
||||
|
||||
this.restore();
|
||||
|
||||
var inputLayer = this.layers.input,
|
||||
outputLayer = this.layers.output;
|
||||
|
||||
inputLayer.clear();
|
||||
for (var layer in this.layers.hidden) {
|
||||
var hiddenLayer = this.layers.hidden[layer];
|
||||
hiddenLayer.clear();
|
||||
}
|
||||
outputLayer.clear();
|
||||
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
},
|
||||
|
||||
// reset all weights and clear all traces (ends up like a new network)
|
||||
reset: function() {
|
||||
|
||||
this.restore();
|
||||
|
||||
var inputLayer = this.layers.input,
|
||||
outputLayer = this.layers.output;
|
||||
|
||||
inputLayer.reset();
|
||||
for (var layer in this.layers.hidden) {
|
||||
var hiddenLayer = this.layers.hidden[layer];
|
||||
hiddenLayer.reset();
|
||||
}
|
||||
outputLayer.reset();
|
||||
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
},
|
||||
|
||||
// hardcodes the behaviour of the whole network into a single optimized function
|
||||
optimize: function() {
|
||||
|
||||
var that = this;
|
||||
var optimized = {};
|
||||
var neurons = this.neurons();
|
||||
|
||||
for (var i in neurons) {
|
||||
var neuron = neurons[i].neuron;
|
||||
var layer = neurons[i].layer;
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
optimized = neuron.optimize(optimized, layer);
|
||||
}
|
||||
for (var i in optimized.propagation_sentences)
|
||||
optimized.propagation_sentences[i].reverse();
|
||||
optimized.propagation_sentences.reverse();
|
||||
|
||||
var hardcode = "";
|
||||
hardcode += "var F = Float64Array ? new Float64Array(" + optimized.memory +
|
||||
") : []; ";
|
||||
for (var i in optimized.variables)
|
||||
hardcode += "F[" + optimized.variables[i].id + "] = " + (optimized.variables[
|
||||
i].value || 0) + "; ";
|
||||
hardcode += "var activate = function(input){\n";
|
||||
for (var i in optimized.inputs)
|
||||
hardcode += "F[" + optimized.inputs[i] + "] = input[" + i + "]; ";
|
||||
for (var currentLayer in optimized.activation_sentences) {
|
||||
if (optimized.activation_sentences[currentLayer].length > 0) {
|
||||
for (var currentNeuron in optimized.activation_sentences[currentLayer]) {
|
||||
hardcode += optimized.activation_sentences[currentLayer][currentNeuron].join(" ");
|
||||
hardcode += optimized.trace_sentences[currentLayer][currentNeuron].join(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
hardcode += " var output = []; "
|
||||
for (var i in optimized.outputs)
|
||||
hardcode += "output[" + i + "] = F[" + optimized.outputs[i] + "]; ";
|
||||
hardcode += "return output; }; "
|
||||
hardcode += "var propagate = function(rate, target){\n";
|
||||
hardcode += "F[" + optimized.variables.rate.id + "] = rate; ";
|
||||
for (var i in optimized.targets)
|
||||
hardcode += "F[" + optimized.targets[i] + "] = target[" + i + "]; ";
|
||||
for (var currentLayer in optimized.propagation_sentences)
|
||||
for (var currentNeuron in optimized.propagation_sentences[currentLayer])
|
||||
hardcode += optimized.propagation_sentences[currentLayer][currentNeuron].join(" ") + " ";
|
||||
hardcode += " };\n";
|
||||
hardcode +=
|
||||
"var ownership = function(memoryBuffer){\nF = memoryBuffer;\nthis.memory = F;\n};\n";
|
||||
hardcode +=
|
||||
"return {\nmemory: F,\nactivate: activate,\npropagate: propagate,\nownership: ownership\n};";
|
||||
hardcode = hardcode.split(";").join(";\n");
|
||||
|
||||
var constructor = new Function(hardcode);
|
||||
|
||||
var network = constructor();
|
||||
network.data = {
|
||||
variables: optimized.variables,
|
||||
activate: optimized.activation_sentences,
|
||||
propagate: optimized.propagation_sentences,
|
||||
trace: optimized.trace_sentences,
|
||||
inputs: optimized.inputs,
|
||||
outputs: optimized.outputs,
|
||||
check_activation: this.activate,
|
||||
check_propagation: this.propagate
|
||||
}
|
||||
|
||||
network.reset = function() {
|
||||
if (that.optimized) {
|
||||
that.optimized = null;
|
||||
that.activate = network.data.check_activation;
|
||||
that.propagate = network.data.check_propagation;
|
||||
}
|
||||
}
|
||||
|
||||
this.optimized = network;
|
||||
this.activate = network.activate;
|
||||
this.propagate = network.propagate;
|
||||
},
|
||||
|
||||
// restores all the values from the optimized network the their respective objects in order to manipulate the network
|
||||
restore: function() {
|
||||
if (!this.optimized)
|
||||
return;
|
||||
|
||||
var optimized = this.optimized;
|
||||
|
||||
var getValue = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
|
||||
var unit = args.shift();
|
||||
var prop = args.pop();
|
||||
|
||||
var id = prop + '_';
|
||||
for (var property in args)
|
||||
id += args[property] + '_';
|
||||
id += unit.ID;
|
||||
|
||||
var memory = optimized.memory;
|
||||
var variables = optimized.data.variables;
|
||||
|
||||
if (id in variables)
|
||||
return memory[variables[id].id];
|
||||
return 0;
|
||||
}
|
||||
|
||||
var list = this.neurons();
|
||||
|
||||
// link id's to positions in the array
|
||||
var ids = {};
|
||||
for (var i in list) {
|
||||
var neuron = list[i].neuron;
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
|
||||
neuron.state = getValue(neuron, 'state');
|
||||
neuron.old = getValue(neuron, 'old');
|
||||
neuron.activation = getValue(neuron, 'activation');
|
||||
neuron.bias = getValue(neuron, 'bias');
|
||||
|
||||
for (var input in neuron.trace.elegibility)
|
||||
neuron.trace.elegibility[input] = getValue(neuron, 'trace',
|
||||
'elegibility', input);
|
||||
|
||||
for (var gated in neuron.trace.extended)
|
||||
for (var input in neuron.trace.extended[gated])
|
||||
neuron.trace.extended[gated][input] = getValue(neuron, 'trace',
|
||||
'extended', gated, input);
|
||||
}
|
||||
|
||||
// get connections
|
||||
for (var i in list) {
|
||||
var neuron = list[i].neuron;
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
|
||||
for (var j in neuron.connections.projected) {
|
||||
var connection = neuron.connections.projected[j];
|
||||
connection.weight = getValue(connection, 'weight');
|
||||
connection.gain = getValue(connection, 'gain');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// returns all the neurons in the network
|
||||
neurons: function() {
|
||||
|
||||
var neurons = [];
|
||||
|
||||
var inputLayer = this.layers.input.neurons(),
|
||||
outputLayer = this.layers.output.neurons();
|
||||
|
||||
for (var neuron in inputLayer)
|
||||
neurons.push({
|
||||
neuron: inputLayer[neuron],
|
||||
layer: 'input'
|
||||
});
|
||||
|
||||
for (var layer in this.layers.hidden) {
|
||||
var hiddenLayer = this.layers.hidden[layer].neurons();
|
||||
for (var neuron in hiddenLayer)
|
||||
neurons.push({
|
||||
neuron: hiddenLayer[neuron],
|
||||
layer: layer
|
||||
});
|
||||
}
|
||||
for (var neuron in outputLayer)
|
||||
neurons.push({
|
||||
neuron: outputLayer[neuron],
|
||||
layer: 'output'
|
||||
});
|
||||
|
||||
return neurons;
|
||||
},
|
||||
|
||||
// returns number of inputs of the network
|
||||
inputs: function() {
|
||||
return this.layers.input.size;
|
||||
},
|
||||
|
||||
// returns number of outputs of hte network
|
||||
outputs: function() {
|
||||
return this.layers.output.size;
|
||||
},
|
||||
|
||||
// sets the layers of the network
|
||||
set: function(layers) {
|
||||
|
||||
this.layers = layers;
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
},
|
||||
|
||||
setOptimize: function(bool){
|
||||
this.restore();
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
this.optimized = bool? null : false;
|
||||
},
|
||||
|
||||
// returns a json that represents all the neurons and connections of the network
|
||||
toJSON: function(ignoreTraces) {
|
||||
|
||||
this.restore();
|
||||
|
||||
var list = this.neurons();
|
||||
var neurons = [];
|
||||
var connections = [];
|
||||
|
||||
// link id's to positions in the array
|
||||
var ids = {};
|
||||
for (var i in list) {
|
||||
var neuron = list[i].neuron;
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
ids[neuron.ID] = i;
|
||||
|
||||
var copy = {
|
||||
trace: {
|
||||
elegibility: {},
|
||||
extended: {}
|
||||
},
|
||||
state: neuron.state,
|
||||
old: neuron.old,
|
||||
activation: neuron.activation,
|
||||
bias: neuron.bias,
|
||||
layer: list[i].layer
|
||||
};
|
||||
|
||||
copy.squash = neuron.squash == Neuron.squash.LOGISTIC ? "LOGISTIC" :
|
||||
neuron.squash == Neuron.squash.TANH ? "TANH" :
|
||||
neuron.squash == Neuron.squash.IDENTITY ? "IDENTITY" :
|
||||
neuron.squash == Neuron.squash.HLIM ? "HLIM" :
|
||||
null;
|
||||
|
||||
neurons.push(copy);
|
||||
}
|
||||
|
||||
// get connections
|
||||
for (var i in list) {
|
||||
var neuron = list[i].neuron;
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
|
||||
for (var j in neuron.connections.projected) {
|
||||
var connection = neuron.connections.projected[j];
|
||||
connections.push({
|
||||
from: ids[connection.from.ID],
|
||||
to: ids[connection.to.ID],
|
||||
weight: connection.weight,
|
||||
gater: connection.gater ? ids[connection.gater.ID] : null,
|
||||
});
|
||||
}
|
||||
if (neuron.selfconnected())
|
||||
connections.push({
|
||||
from: ids[neuron.ID],
|
||||
to: ids[neuron.ID],
|
||||
weight: neuron.selfconnection.weight,
|
||||
gater: neuron.selfconnection.gater ? ids[neuron.selfconnection.gater.ID] : null,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
neurons: neurons,
|
||||
connections: connections
|
||||
}
|
||||
},
|
||||
|
||||
// export the topology into dot language which can be visualized as graphs using dot
|
||||
/* example: ... console.log(net.toDotLang());
|
||||
$ node example.js > example.dot
|
||||
$ dot example.dot -Tpng > out.png
|
||||
*/
|
||||
toDot: function(edgeConnection) {
|
||||
if (! typeof edgeConnection)
|
||||
edgeConnection = false;
|
||||
var code = "digraph nn {\n rankdir = BT\n";
|
||||
var layers = [this.layers.input].concat(this.layers.hidden, this.layers.output);
|
||||
for (var layer in layers) {
|
||||
for (var to in layers[layer].connectedTo) { // projections
|
||||
var connection = layers[layer].connectedTo[to];
|
||||
var layerTo = connection.to;
|
||||
var size = connection.size;
|
||||
var layerID = layers.indexOf(layers[layer]);
|
||||
var layerToID = layers.indexOf(layerTo);
|
||||
/* http://stackoverflow.com/questions/26845540/connect-edges-with-graph-dot
|
||||
* DOT does not support edge-to-edge connections
|
||||
* This workaround produces somewhat weird graphs ...
|
||||
*/
|
||||
if ( edgeConnection) {
|
||||
if (connection.gatedfrom.length) {
|
||||
var fakeNode = "fake" + layerID + "_" + layerToID;
|
||||
code += " " + fakeNode +
|
||||
" [label = \"\", shape = point, width = 0.01, height = 0.01]\n";
|
||||
code += " " + layerID + " -> " + fakeNode + " [label = " + size + ", arrowhead = none]\n";
|
||||
code += " " + fakeNode + " -> " + layerToID + "\n";
|
||||
} else
|
||||
code += " " + layerID + " -> " + layerToID + " [label = " + size + "]\n";
|
||||
for (var from in connection.gatedfrom) { // gatings
|
||||
var layerfrom = connection.gatedfrom[from].layer;
|
||||
var layerfromID = layers.indexOf(layerfrom);
|
||||
code += " " + layerfromID + " -> " + fakeNode + " [color = blue]\n";
|
||||
}
|
||||
} else {
|
||||
code += " " + layerID + " -> " + layerToID + " [label = " + size + "]\n";
|
||||
for (var from in connection.gatedfrom) { // gatings
|
||||
var layerfrom = connection.gatedfrom[from].layer;
|
||||
var layerfromID = layers.indexOf(layerfrom);
|
||||
code += " " + layerfromID + " -> " + layerToID + " [color = blue]\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
code += "}\n";
|
||||
return {
|
||||
code: code,
|
||||
link: "https://chart.googleapis.com/chart?chl=" + escape(code.replace("/ /g", "+")) + "&cht=gv"
|
||||
}
|
||||
},
|
||||
|
||||
// returns a function that works as the activation of the network and can be used without depending on the library
|
||||
standalone: function() {
|
||||
if (!this.optimized)
|
||||
this.optimize();
|
||||
|
||||
var data = this.optimized.data;
|
||||
|
||||
// build activation function
|
||||
var activation = "function (input) {\n";
|
||||
|
||||
// build inputs
|
||||
for (var i in data.inputs)
|
||||
activation += "F[" + data.inputs[i] + "] = input[" + i + "];\n";
|
||||
|
||||
// build network activation
|
||||
for (var neuron in data.activate) { // shouldn't this be layer?
|
||||
for (var sentence in data.activate[neuron])
|
||||
activation += data.activate[neuron][sentence].join('') + "\n";
|
||||
}
|
||||
|
||||
// build outputs
|
||||
activation += "var output = [];\n";
|
||||
for (var i in data.outputs)
|
||||
activation += "output[" + i + "] = F[" + data.outputs[i] + "];\n";
|
||||
activation += "return output;\n}";
|
||||
|
||||
// reference all the positions in memory
|
||||
var memory = activation.match(/F\[(\d+)\]/g);
|
||||
var dimension = 0;
|
||||
var ids = {};
|
||||
for (var address in memory) {
|
||||
var tmp = memory[address].match(/\d+/)[0];
|
||||
if (!(tmp in ids)) {
|
||||
ids[tmp] = dimension++;
|
||||
}
|
||||
}
|
||||
var hardcode = "F = {\n";
|
||||
for (var i in ids)
|
||||
hardcode += ids[i] + ": " + this.optimized.memory[i] + ",\n";
|
||||
hardcode = hardcode.substring(0, hardcode.length - 2) + "\n};\n";
|
||||
hardcode = "var run = " + activation.replace(/F\[(\d+)]/g, function(
|
||||
index) {
|
||||
return 'F[' + ids[index.match(/\d+/)[0]] + ']'
|
||||
}).replace("{\n", "{\n" + hardcode + "") + ";\n";
|
||||
hardcode += "return run";
|
||||
|
||||
// return standalone function
|
||||
return new Function(hardcode)();
|
||||
},
|
||||
|
||||
|
||||
// Return a HTML5 WebWorker specialized on training the network stored in `memory`.
|
||||
// Train based on the given dataSet and options.
|
||||
// The worker returns the updated `memory` when done.
|
||||
worker: function(memory, set, options) {
|
||||
|
||||
// Copy the options and set defaults (options might be different for each worker)
|
||||
var workerOptions = {};
|
||||
if(options) workerOptions = options;
|
||||
workerOptions.rate = options.rate || .2;
|
||||
workerOptions.iterations = options.iterations || 100000;
|
||||
workerOptions.error = options.error || .005;
|
||||
workerOptions.cost = options.cost || null;
|
||||
workerOptions.crossValidate = options.crossValidate || null;
|
||||
|
||||
// Cost function might be different for each worker
|
||||
costFunction = "var cost = " + (options && options.cost || this.cost || Trainer.cost.MSE) + ";\n";
|
||||
var workerFunction = Network.getWorkerSharedFunctions();
|
||||
workerFunction = workerFunction.replace(/var cost = options && options\.cost \|\| this\.cost \|\| Trainer\.cost\.MSE;/g, costFunction);
|
||||
|
||||
// Set what we do when training is finished
|
||||
workerFunction = workerFunction.replace('return results;',
|
||||
'postMessage({action: "done", message: results, memoryBuffer: F}, [F.buffer]);');
|
||||
|
||||
// Replace log with postmessage
|
||||
workerFunction = workerFunction.replace("console.log('iterations', iterations, 'error', error, 'rate', currentRate)",
|
||||
"postMessage({action: 'log', message: {\n" +
|
||||
"iterations: iterations,\n" +
|
||||
"error: error,\n" +
|
||||
"rate: currentRate\n" +
|
||||
"}\n" +
|
||||
"})");
|
||||
|
||||
// Replace schedule with postmessage
|
||||
workerFunction = workerFunction.replace("abort = this.schedule.do({ error: error, iterations: iterations, rate: currentRate })",
|
||||
"postMessage({action: 'schedule', message: {\n" +
|
||||
"iterations: iterations,\n" +
|
||||
"error: error,\n" +
|
||||
"rate: currentRate\n" +
|
||||
"}\n" +
|
||||
"})");
|
||||
|
||||
if (!this.optimized)
|
||||
this.optimize();
|
||||
|
||||
var hardcode = "var inputs = " + this.optimized.data.inputs.length + ";\n";
|
||||
hardcode += "var outputs = " + this.optimized.data.outputs.length + ";\n";
|
||||
hardcode += "var F = new Float64Array([" + this.optimized.memory.toString() + "]);\n";
|
||||
hardcode += "var activate = " + this.optimized.activate.toString() + ";\n";
|
||||
hardcode += "var propagate = " + this.optimized.propagate.toString() + ";\n";
|
||||
hardcode +=
|
||||
"onmessage = function(e) {\n" +
|
||||
"if (e.data.action == 'startTraining') {\n" +
|
||||
"train(" + JSON.stringify(set) + "," + JSON.stringify(workerOptions) + ");\n" +
|
||||
"}\n" +
|
||||
"}";
|
||||
|
||||
var workerSourceCode = workerFunction + '\n' + hardcode;
|
||||
var blob = new Blob([workerSourceCode]);
|
||||
var blobURL = window.URL.createObjectURL(blob);
|
||||
|
||||
return new Worker(blobURL);
|
||||
},
|
||||
|
||||
// returns a copy of the network
|
||||
clone: function() {
|
||||
return Network.fromJSON(this.toJSON());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a static String to store the source code of the functions
|
||||
* that are identical for all the workers (train, _trainSet, test)
|
||||
*
|
||||
* @return {String} Source code that can train a network inside a worker.
|
||||
* @static
|
||||
*/
|
||||
Network.getWorkerSharedFunctions = function() {
|
||||
// If we already computed the source code for the shared functions
|
||||
if(typeof Network._SHARED_WORKER_FUNCTIONS !== 'undefined')
|
||||
return Network._SHARED_WORKER_FUNCTIONS;
|
||||
|
||||
// Otherwise compute and return the source code
|
||||
// We compute them by simply copying the source code of the train, _trainSet and test functions
|
||||
// using the .toString() method
|
||||
|
||||
// Load and name the train function
|
||||
var train_f = Trainer.prototype.train.toString();
|
||||
train_f = train_f.replace('function (set', 'function train(set') + '\n';
|
||||
|
||||
// Load and name the _trainSet function
|
||||
var _trainSet_f = Trainer.prototype._trainSet.toString().replace(/this.network./g, '');
|
||||
_trainSet_f = _trainSet_f.replace('function (set', 'function _trainSet(set') + '\n';
|
||||
_trainSet_f = _trainSet_f.replace('this.crossValidate', 'crossValidate');
|
||||
_trainSet_f = _trainSet_f.replace('crossValidate = true', 'crossValidate = { }');
|
||||
|
||||
// Load and name the test function
|
||||
var test_f = Trainer.prototype.test.toString().replace(/this.network./g, '');
|
||||
test_f = test_f.replace('function (set', 'function test(set') + '\n';
|
||||
|
||||
return Network._SHARED_WORKER_FUNCTIONS = train_f + _trainSet_f + test_f;
|
||||
};
|
||||
|
||||
// rebuild a network that has been stored in a json using the method toJSON()
|
||||
Network.fromJSON = function(json) {
|
||||
|
||||
var neurons = [];
|
||||
|
||||
var layers = {
|
||||
input: new Layer(),
|
||||
hidden: [],
|
||||
output: new Layer()
|
||||
};
|
||||
|
||||
for (var i in json.neurons) {
|
||||
var config = json.neurons[i];
|
||||
|
||||
var neuron = new Neuron();
|
||||
neuron.trace.elegibility = {};
|
||||
neuron.trace.extended = {};
|
||||
neuron.state = config.state;
|
||||
neuron.old = config.old;
|
||||
neuron.activation = config.activation;
|
||||
neuron.bias = config.bias;
|
||||
neuron.squash = config.squash in Neuron.squash ? Neuron.squash[config.squash] : Neuron.squash.LOGISTIC;
|
||||
neurons.push(neuron);
|
||||
|
||||
if (config.layer == 'input')
|
||||
layers.input.add(neuron);
|
||||
else if (config.layer == 'output')
|
||||
layers.output.add(neuron);
|
||||
else {
|
||||
if (typeof layers.hidden[config.layer] == 'undefined')
|
||||
layers.hidden[config.layer] = new Layer();
|
||||
layers.hidden[config.layer].add(neuron);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i in json.connections) {
|
||||
var config = json.connections[i];
|
||||
var from = neurons[config.from];
|
||||
var to = neurons[config.to];
|
||||
var weight = config.weight;
|
||||
var gater = neurons[config.gater];
|
||||
|
||||
var connection = from.project(to, weight);
|
||||
if (gater)
|
||||
gater.gate(connection);
|
||||
}
|
||||
|
||||
return new Network(layers);
|
||||
};
|
||||
+796
@@ -0,0 +1,796 @@
|
||||
// export
|
||||
if (module) module.exports = Neuron;
|
||||
|
||||
/******************************************************************************************
|
||||
NEURON
|
||||
*******************************************************************************************/
|
||||
|
||||
function Neuron() {
|
||||
this.ID = Neuron.uid();
|
||||
this.label = null;
|
||||
this.connections = {
|
||||
inputs: {},
|
||||
projected: {},
|
||||
gated: {}
|
||||
};
|
||||
this.error = {
|
||||
responsibility: 0,
|
||||
projected: 0,
|
||||
gated: 0
|
||||
};
|
||||
this.trace = {
|
||||
elegibility: {},
|
||||
extended: {},
|
||||
influences: {}
|
||||
};
|
||||
this.state = 0;
|
||||
this.old = 0;
|
||||
this.activation = 0;
|
||||
this.selfconnection = new Neuron.connection(this, this, 0); // weight = 0 -> not connected
|
||||
this.squash = Neuron.squash.LOGISTIC;
|
||||
this.neighboors = {};
|
||||
this.bias = Math.random() * .2 - .1;
|
||||
}
|
||||
|
||||
Neuron.prototype = {
|
||||
|
||||
// activate the neuron
|
||||
activate: function(input) {
|
||||
// activation from enviroment (for input neurons)
|
||||
if (typeof input != 'undefined') {
|
||||
this.activation = input;
|
||||
this.derivative = 0;
|
||||
this.bias = 0;
|
||||
return this.activation;
|
||||
}
|
||||
|
||||
// old state
|
||||
this.old = this.state;
|
||||
|
||||
// eq. 15
|
||||
this.state = this.selfconnection.gain * this.selfconnection.weight *
|
||||
this.state + this.bias;
|
||||
|
||||
for (var i in this.connections.inputs) {
|
||||
var input = this.connections.inputs[i];
|
||||
this.state += input.from.activation * input.weight * input.gain;
|
||||
}
|
||||
|
||||
// eq. 16
|
||||
this.activation = this.squash(this.state);
|
||||
|
||||
// f'(s)
|
||||
this.derivative = this.squash(this.state, true);
|
||||
|
||||
// update traces
|
||||
var influences = [];
|
||||
for (var id in this.trace.extended) {
|
||||
// extended elegibility trace
|
||||
var neuron = this.neighboors[id];
|
||||
|
||||
// if gated neuron's selfconnection is gated by this unit, the influence keeps track of the neuron's old state
|
||||
var influence = neuron.selfconnection.gater == this ? neuron.old : 0;
|
||||
|
||||
// index runs over all the incoming connections to the gated neuron that are gated by this unit
|
||||
for (var incoming in this.trace.influences[neuron.ID]) { // captures the effect that has an input connection to this unit, on a neuron that is gated by this unit
|
||||
influence += this.trace.influences[neuron.ID][incoming].weight *
|
||||
this.trace.influences[neuron.ID][incoming].from.activation;
|
||||
}
|
||||
influences[neuron.ID] = influence;
|
||||
}
|
||||
|
||||
for (var i in this.connections.inputs) {
|
||||
var input = this.connections.inputs[i];
|
||||
|
||||
// elegibility trace - Eq. 17
|
||||
this.trace.elegibility[input.ID] = this.selfconnection.gain * this.selfconnection
|
||||
.weight * this.trace.elegibility[input.ID] + input.gain * input.from
|
||||
.activation;
|
||||
|
||||
for (var id in this.trace.extended) {
|
||||
// extended elegibility trace
|
||||
var xtrace = this.trace.extended[id];
|
||||
var neuron = this.neighboors[id];
|
||||
var influence = influences[neuron.ID];
|
||||
|
||||
// eq. 18
|
||||
xtrace[input.ID] = neuron.selfconnection.gain * neuron.selfconnection
|
||||
.weight * xtrace[input.ID] + this.derivative * this.trace.elegibility[
|
||||
input.ID] * influence;
|
||||
}
|
||||
}
|
||||
|
||||
// update gated connection's gains
|
||||
for (var connection in this.connections.gated) {
|
||||
this.connections.gated[connection].gain = this.activation;
|
||||
}
|
||||
|
||||
return this.activation;
|
||||
},
|
||||
|
||||
// back-propagate the error
|
||||
propagate: function(rate, target) {
|
||||
// error accumulator
|
||||
var error = 0;
|
||||
|
||||
// whether or not this neuron is in the output layer
|
||||
var isOutput = typeof target != 'undefined';
|
||||
|
||||
// output neurons get their error from the enviroment
|
||||
if (isOutput)
|
||||
this.error.responsibility = this.error.projected = target - this.activation; // Eq. 10
|
||||
|
||||
else // the rest of the neuron compute their error responsibilities by backpropagation
|
||||
{
|
||||
// error responsibilities from all the connections projected from this neuron
|
||||
for (var id in this.connections.projected) {
|
||||
var connection = this.connections.projected[id];
|
||||
var neuron = connection.to;
|
||||
// Eq. 21
|
||||
error += neuron.error.responsibility * connection.gain * connection.weight;
|
||||
}
|
||||
|
||||
// projected error responsibility
|
||||
this.error.projected = this.derivative * error;
|
||||
|
||||
error = 0;
|
||||
// error responsibilities from all the connections gated by this neuron
|
||||
for (var id in this.trace.extended) {
|
||||
var neuron = this.neighboors[id]; // gated neuron
|
||||
var influence = neuron.selfconnection.gater == this ? neuron.old : 0; // if gated neuron's selfconnection is gated by this neuron
|
||||
|
||||
// index runs over all the connections to the gated neuron that are gated by this neuron
|
||||
for (var input in this.trace.influences[id]) { // captures the effect that the input connection of this neuron have, on a neuron which its input/s is/are gated by this neuron
|
||||
influence += this.trace.influences[id][input].weight * this.trace.influences[
|
||||
neuron.ID][input].from.activation;
|
||||
}
|
||||
// eq. 22
|
||||
error += neuron.error.responsibility * influence;
|
||||
}
|
||||
|
||||
// gated error responsibility
|
||||
this.error.gated = this.derivative * error;
|
||||
|
||||
// error responsibility - Eq. 23
|
||||
this.error.responsibility = this.error.projected + this.error.gated;
|
||||
}
|
||||
|
||||
// learning rate
|
||||
rate = rate || .1;
|
||||
|
||||
// adjust all the neuron's incoming connections
|
||||
for (var id in this.connections.inputs) {
|
||||
var input = this.connections.inputs[id];
|
||||
|
||||
// Eq. 24
|
||||
var gradient = this.error.projected * this.trace.elegibility[input.ID];
|
||||
for (var id in this.trace.extended) {
|
||||
var neuron = this.neighboors[id];
|
||||
gradient += neuron.error.responsibility * this.trace.extended[
|
||||
neuron.ID][input.ID];
|
||||
}
|
||||
input.weight += rate * gradient; // adjust weights - aka learn
|
||||
}
|
||||
|
||||
// adjust bias
|
||||
this.bias += rate * this.error.responsibility;
|
||||
},
|
||||
|
||||
project: function(neuron, weight) {
|
||||
// self-connection
|
||||
if (neuron == this) {
|
||||
this.selfconnection.weight = 1;
|
||||
return this.selfconnection;
|
||||
}
|
||||
|
||||
// check if connection already exists
|
||||
var connected = this.connected(neuron);
|
||||
if (connected && connected.type == "projected") {
|
||||
// update connection
|
||||
if (typeof weight != 'undefined')
|
||||
connected.connection.weight = weight;
|
||||
// return existing connection
|
||||
return connected.connection;
|
||||
} else {
|
||||
// create a new connection
|
||||
var connection = new Neuron.connection(this, neuron, weight);
|
||||
}
|
||||
|
||||
// reference all the connections and traces
|
||||
this.connections.projected[connection.ID] = connection;
|
||||
this.neighboors[neuron.ID] = neuron;
|
||||
neuron.connections.inputs[connection.ID] = connection;
|
||||
neuron.trace.elegibility[connection.ID] = 0;
|
||||
|
||||
for (var id in neuron.trace.extended) {
|
||||
var trace = neuron.trace.extended[id];
|
||||
trace[connection.ID] = 0;
|
||||
}
|
||||
|
||||
return connection;
|
||||
},
|
||||
|
||||
gate: function(connection) {
|
||||
// add connection to gated list
|
||||
this.connections.gated[connection.ID] = connection;
|
||||
|
||||
var neuron = connection.to;
|
||||
if (!(neuron.ID in this.trace.extended)) {
|
||||
// extended trace
|
||||
this.neighboors[neuron.ID] = neuron;
|
||||
var xtrace = this.trace.extended[neuron.ID] = {};
|
||||
for (var id in this.connections.inputs) {
|
||||
var input = this.connections.inputs[id];
|
||||
xtrace[input.ID] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// keep track
|
||||
if (neuron.ID in this.trace.influences)
|
||||
this.trace.influences[neuron.ID].push(connection);
|
||||
else
|
||||
this.trace.influences[neuron.ID] = [connection];
|
||||
|
||||
// set gater
|
||||
connection.gater = this;
|
||||
},
|
||||
|
||||
// returns true or false whether the neuron is self-connected or not
|
||||
selfconnected: function() {
|
||||
return this.selfconnection.weight !== 0;
|
||||
},
|
||||
|
||||
// returns true or false whether the neuron is connected to another neuron (parameter)
|
||||
connected: function(neuron) {
|
||||
var result = {
|
||||
type: null,
|
||||
connection: false
|
||||
};
|
||||
|
||||
if (this == neuron) {
|
||||
if (this.selfconnected()) {
|
||||
result.type = 'selfconnection';
|
||||
result.connection = this.selfconnection;
|
||||
return result;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var type in this.connections) {
|
||||
for (var connection in this.connections[type]) {
|
||||
var connection = this.connections[type][connection];
|
||||
if (connection.to == neuron) {
|
||||
result.type = type;
|
||||
result.connection = connection;
|
||||
return result;
|
||||
} else if (connection.from == neuron) {
|
||||
result.type = type;
|
||||
result.connection = connection;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// clears all the traces (the neuron forgets it's context, but the connections remain intact)
|
||||
clear: function() {
|
||||
|
||||
for (var trace in this.trace.elegibility)
|
||||
this.trace.elegibility[trace] = 0;
|
||||
|
||||
for (var trace in this.trace.extended)
|
||||
for (var extended in this.trace.extended[trace])
|
||||
this.trace.extended[trace][extended] = 0;
|
||||
|
||||
this.error.responsibility = this.error.projected = this.error.gated = 0;
|
||||
},
|
||||
|
||||
// all the connections are randomized and the traces are cleared
|
||||
reset: function() {
|
||||
this.clear();
|
||||
|
||||
for (var type in this.connections)
|
||||
for (var connection in this.connections[type])
|
||||
this.connections[type][connection].weight = Math.random() * .2 - .1;
|
||||
this.bias = Math.random() * .2 - .1;
|
||||
|
||||
this.old = this.state = this.activation = 0;
|
||||
},
|
||||
|
||||
// hardcodes the behaviour of the neuron into an optimized function
|
||||
optimize: function(optimized, layer) {
|
||||
|
||||
optimized = optimized || {};
|
||||
var store_activation = [];
|
||||
var store_trace = [];
|
||||
var store_propagation = [];
|
||||
var varID = optimized.memory || 0;
|
||||
var neurons = optimized.neurons || 1;
|
||||
var inputs = optimized.inputs || [];
|
||||
var targets = optimized.targets || [];
|
||||
var outputs = optimized.outputs || [];
|
||||
var variables = optimized.variables || {};
|
||||
var activation_sentences = optimized.activation_sentences || [];
|
||||
var trace_sentences = optimized.trace_sentences || [];
|
||||
var propagation_sentences = optimized.propagation_sentences || [];
|
||||
var layers = optimized.layers || { __count: 0, __neuron: 0 };
|
||||
|
||||
// allocate sentences
|
||||
var allocate = function(store){
|
||||
var allocated = layer in layers && store[layers.__count];
|
||||
if (!allocated)
|
||||
{
|
||||
layers.__count = store.push([]) - 1;
|
||||
layers[layer] = layers.__count;
|
||||
}
|
||||
};
|
||||
allocate(activation_sentences);
|
||||
allocate(trace_sentences);
|
||||
allocate(propagation_sentences);
|
||||
var currentLayer = layers.__count;
|
||||
|
||||
// get/reserve space in memory by creating a unique ID for a variablel
|
||||
var getVar = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
|
||||
if (args.length == 1) {
|
||||
if (args[0] == 'target') {
|
||||
var id = 'target_' + targets.length;
|
||||
targets.push(varID);
|
||||
} else
|
||||
var id = args[0];
|
||||
if (id in variables)
|
||||
return variables[id];
|
||||
return variables[id] = {
|
||||
value: 0,
|
||||
id: varID++
|
||||
};
|
||||
} else {
|
||||
var extended = args.length > 2;
|
||||
if (extended)
|
||||
var value = args.pop();
|
||||
|
||||
var unit = args.shift();
|
||||
var prop = args.pop();
|
||||
|
||||
if (!extended)
|
||||
var value = unit[prop];
|
||||
|
||||
var id = prop + '_';
|
||||
for (var property in args)
|
||||
id += args[property] + '_';
|
||||
id += unit.ID;
|
||||
if (id in variables)
|
||||
return variables[id];
|
||||
|
||||
return variables[id] = {
|
||||
value: value,
|
||||
id: varID++
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// build sentence
|
||||
var buildSentence = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
var store = args.pop();
|
||||
var sentence = "";
|
||||
for (var i in args)
|
||||
if (typeof args[i] == 'string')
|
||||
sentence += args[i];
|
||||
else
|
||||
sentence += 'F[' + args[i].id + ']';
|
||||
|
||||
store.push(sentence + ';');
|
||||
};
|
||||
|
||||
// helper to check if an object is empty
|
||||
var isEmpty = function(obj) {
|
||||
for (var prop in obj) {
|
||||
if (obj.hasOwnProperty(prop))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// characteristics of the neuron
|
||||
var noProjections = isEmpty(this.connections.projected);
|
||||
var noGates = isEmpty(this.connections.gated);
|
||||
var isInput = layer == 'input' ? true : isEmpty(this.connections.inputs);
|
||||
var isOutput = layer == 'output' ? true : noProjections && noGates;
|
||||
|
||||
// optimize neuron's behaviour
|
||||
var rate = getVar('rate');
|
||||
var activation = getVar(this, 'activation');
|
||||
if (isInput)
|
||||
inputs.push(activation.id);
|
||||
else {
|
||||
activation_sentences[currentLayer].push(store_activation);
|
||||
trace_sentences[currentLayer].push(store_trace);
|
||||
propagation_sentences[currentLayer].push(store_propagation);
|
||||
var old = getVar(this, 'old');
|
||||
var state = getVar(this, 'state');
|
||||
var bias = getVar(this, 'bias');
|
||||
if (this.selfconnection.gater)
|
||||
var self_gain = getVar(this.selfconnection, 'gain');
|
||||
if (this.selfconnected())
|
||||
var self_weight = getVar(this.selfconnection, 'weight');
|
||||
buildSentence(old, ' = ', state, store_activation);
|
||||
if (this.selfconnected())
|
||||
if (this.selfconnection.gater)
|
||||
buildSentence(state, ' = ', self_gain, ' * ', self_weight, ' * ',
|
||||
state, ' + ', bias, store_activation);
|
||||
else
|
||||
buildSentence(state, ' = ', self_weight, ' * ', state, ' + ',
|
||||
bias, store_activation);
|
||||
else
|
||||
buildSentence(state, ' = ', bias, store_activation);
|
||||
for (var i in this.connections.inputs) {
|
||||
var input = this.connections.inputs[i];
|
||||
var input_activation = getVar(input.from, 'activation');
|
||||
var input_weight = getVar(input, 'weight');
|
||||
if (input.gater)
|
||||
var input_gain = getVar(input, 'gain');
|
||||
if (this.connections.inputs[i].gater)
|
||||
buildSentence(state, ' += ', input_activation, ' * ',
|
||||
input_weight, ' * ', input_gain, store_activation);
|
||||
else
|
||||
buildSentence(state, ' += ', input_activation, ' * ',
|
||||
input_weight, store_activation);
|
||||
}
|
||||
var derivative = getVar(this, 'derivative');
|
||||
switch (this.squash) {
|
||||
case Neuron.squash.LOGISTIC:
|
||||
buildSentence(activation, ' = (1 / (1 + Math.exp(-', state, ')))',
|
||||
store_activation);
|
||||
buildSentence(derivative, ' = ', activation, ' * (1 - ',
|
||||
activation, ')', store_activation);
|
||||
break;
|
||||
case Neuron.squash.TANH:
|
||||
var eP = getVar('aux');
|
||||
var eN = getVar('aux_2');
|
||||
buildSentence(eP, ' = Math.exp(', state, ')', store_activation);
|
||||
buildSentence(eN, ' = 1 / ', eP, store_activation);
|
||||
buildSentence(activation, ' = (', eP, ' - ', eN, ') / (', eP, ' + ', eN, ')', store_activation);
|
||||
buildSentence(derivative, ' = 1 - (', activation, ' * ', activation, ')', store_activation);
|
||||
break;
|
||||
case Neuron.squash.IDENTITY:
|
||||
buildSentence(activation, ' = ', state, store_activation);
|
||||
buildSentence(derivative, ' = 1', store_activation);
|
||||
break;
|
||||
case Neuron.squash.HLIM:
|
||||
buildSentence(activation, ' = +(', state, ' > 0)', store_activation);
|
||||
buildSentence(derivative, ' = 1', store_activation);
|
||||
case Neuron.squash.RELU:
|
||||
buildSentence(activation, ' = ', state, ' > 0 ? ', state, ' : 0', store_activation);
|
||||
buildSentence(derivative, ' = ', state, ' > 0 ? 1 : 0', store_activation);
|
||||
break;
|
||||
}
|
||||
|
||||
for (var id in this.trace.extended) {
|
||||
// calculate extended elegibility traces in advance
|
||||
|
||||
var neuron = this.neighboors[id];
|
||||
var influence = getVar('influences[' + neuron.ID + ']');
|
||||
var neuron_old = getVar(neuron, 'old');
|
||||
var initialized = false;
|
||||
if (neuron.selfconnection.gater == this)
|
||||
{
|
||||
buildSentence(influence, ' = ', neuron_old, store_trace);
|
||||
initialized = true;
|
||||
}
|
||||
for (var incoming in this.trace.influences[neuron.ID]) {
|
||||
var incoming_weight = getVar(this.trace.influences[neuron.ID]
|
||||
[incoming], 'weight');
|
||||
var incoming_activation = getVar(this.trace.influences[neuron.ID]
|
||||
[incoming].from, 'activation');
|
||||
|
||||
if (initialized)
|
||||
buildSentence(influence, ' += ', incoming_weight, ' * ', incoming_activation, store_trace);
|
||||
else {
|
||||
buildSentence(influence, ' = ', incoming_weight, ' * ', incoming_activation, store_trace);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i in this.connections.inputs) {
|
||||
var input = this.connections.inputs[i];
|
||||
if (input.gater)
|
||||
var input_gain = getVar(input, 'gain');
|
||||
var input_activation = getVar(input.from, 'activation');
|
||||
var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
|
||||
.elegibility[input.ID]);
|
||||
if (this.selfconnected()) {
|
||||
if (this.selfconnection.gater) {
|
||||
if (input.gater)
|
||||
buildSentence(trace, ' = ', self_gain, ' * ', self_weight,
|
||||
' * ', trace, ' + ', input_gain, ' * ', input_activation,
|
||||
store_trace);
|
||||
else
|
||||
buildSentence(trace, ' = ', self_gain, ' * ', self_weight,
|
||||
' * ', trace, ' + ', input_activation, store_trace);
|
||||
} else {
|
||||
if (input.gater)
|
||||
buildSentence(trace, ' = ', self_weight, ' * ', trace, ' + ',
|
||||
input_gain, ' * ', input_activation, store_trace);
|
||||
else
|
||||
buildSentence(trace, ' = ', self_weight, ' * ', trace, ' + ',
|
||||
input_activation, store_trace);
|
||||
}
|
||||
} else {
|
||||
if (input.gater)
|
||||
buildSentence(trace, ' = ', input_gain, ' * ', input_activation,
|
||||
store_trace);
|
||||
else
|
||||
buildSentence(trace, ' = ', input_activation, store_trace);
|
||||
}
|
||||
for (var id in this.trace.extended) {
|
||||
// extended elegibility trace
|
||||
var neuron = this.neighboors[id];
|
||||
var influence = getVar('influences[' + neuron.ID + ']');
|
||||
|
||||
var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
|
||||
.elegibility[input.ID]);
|
||||
var xtrace = getVar(this, 'trace', 'extended', neuron.ID, input.ID,
|
||||
this.trace.extended[neuron.ID][input.ID]);
|
||||
if (neuron.selfconnected())
|
||||
var neuron_self_weight = getVar(neuron.selfconnection, 'weight');
|
||||
if (neuron.selfconnection.gater)
|
||||
var neuron_self_gain = getVar(neuron.selfconnection, 'gain');
|
||||
if (neuron.selfconnected())
|
||||
if (neuron.selfconnection.gater)
|
||||
buildSentence(xtrace, ' = ', neuron_self_gain, ' * ',
|
||||
neuron_self_weight, ' * ', xtrace, ' + ', derivative, ' * ',
|
||||
trace, ' * ', influence, store_trace);
|
||||
else
|
||||
buildSentence(xtrace, ' = ', neuron_self_weight, ' * ',
|
||||
xtrace, ' + ', derivative, ' * ', trace, ' * ',
|
||||
influence, store_trace);
|
||||
else
|
||||
buildSentence(xtrace, ' = ', derivative, ' * ', trace, ' * ',
|
||||
influence, store_trace);
|
||||
}
|
||||
}
|
||||
for (var connection in this.connections.gated) {
|
||||
var gated_gain = getVar(this.connections.gated[connection], 'gain');
|
||||
buildSentence(gated_gain, ' = ', activation, store_activation);
|
||||
}
|
||||
}
|
||||
if (!isInput) {
|
||||
var responsibility = getVar(this, 'error', 'responsibility', this.error
|
||||
.responsibility);
|
||||
if (isOutput) {
|
||||
var target = getVar('target');
|
||||
buildSentence(responsibility, ' = ', target, ' - ', activation,
|
||||
store_propagation);
|
||||
for (var id in this.connections.inputs) {
|
||||
var input = this.connections.inputs[id];
|
||||
var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
|
||||
.elegibility[input.ID]);
|
||||
var input_weight = getVar(input, 'weight');
|
||||
buildSentence(input_weight, ' += ', rate, ' * (', responsibility,
|
||||
' * ', trace, ')', store_propagation);
|
||||
}
|
||||
outputs.push(activation.id);
|
||||
} else {
|
||||
if (!noProjections && !noGates) {
|
||||
var error = getVar('aux');
|
||||
for (var id in this.connections.projected) {
|
||||
var connection = this.connections.projected[id];
|
||||
var neuron = connection.to;
|
||||
var connection_weight = getVar(connection, 'weight');
|
||||
var neuron_responsibility = getVar(neuron, 'error',
|
||||
'responsibility', neuron.error.responsibility);
|
||||
if (connection.gater) {
|
||||
var connection_gain = getVar(connection, 'gain');
|
||||
buildSentence(error, ' += ', neuron_responsibility, ' * ',
|
||||
connection_gain, ' * ', connection_weight,
|
||||
store_propagation);
|
||||
} else
|
||||
buildSentence(error, ' += ', neuron_responsibility, ' * ',
|
||||
connection_weight, store_propagation);
|
||||
}
|
||||
var projected = getVar(this, 'error', 'projected', this.error.projected);
|
||||
buildSentence(projected, ' = ', derivative, ' * ', error,
|
||||
store_propagation);
|
||||
buildSentence(error, ' = 0', store_propagation);
|
||||
for (var id in this.trace.extended) {
|
||||
var neuron = this.neighboors[id];
|
||||
var influence = getVar('aux_2');
|
||||
var neuron_old = getVar(neuron, 'old');
|
||||
if (neuron.selfconnection.gater == this)
|
||||
buildSentence(influence, ' = ', neuron_old, store_propagation);
|
||||
else
|
||||
buildSentence(influence, ' = 0', store_propagation);
|
||||
for (var input in this.trace.influences[neuron.ID]) {
|
||||
var connection = this.trace.influences[neuron.ID][input];
|
||||
var connection_weight = getVar(connection, 'weight');
|
||||
var neuron_activation = getVar(connection.from, 'activation');
|
||||
buildSentence(influence, ' += ', connection_weight, ' * ',
|
||||
neuron_activation, store_propagation);
|
||||
}
|
||||
var neuron_responsibility = getVar(neuron, 'error',
|
||||
'responsibility', neuron.error.responsibility);
|
||||
buildSentence(error, ' += ', neuron_responsibility, ' * ',
|
||||
influence, store_propagation);
|
||||
}
|
||||
var gated = getVar(this, 'error', 'gated', this.error.gated);
|
||||
buildSentence(gated, ' = ', derivative, ' * ', error,
|
||||
store_propagation);
|
||||
buildSentence(responsibility, ' = ', projected, ' + ', gated,
|
||||
store_propagation);
|
||||
for (var id in this.connections.inputs) {
|
||||
var input = this.connections.inputs[id];
|
||||
var gradient = getVar('aux');
|
||||
var trace = getVar(this, 'trace', 'elegibility', input.ID, this
|
||||
.trace.elegibility[input.ID]);
|
||||
buildSentence(gradient, ' = ', projected, ' * ', trace,
|
||||
store_propagation);
|
||||
for (var id in this.trace.extended) {
|
||||
var neuron = this.neighboors[id];
|
||||
var neuron_responsibility = getVar(neuron, 'error',
|
||||
'responsibility', neuron.error.responsibility);
|
||||
var xtrace = getVar(this, 'trace', 'extended', neuron.ID,
|
||||
input.ID, this.trace.extended[neuron.ID][input.ID]);
|
||||
buildSentence(gradient, ' += ', neuron_responsibility, ' * ',
|
||||
xtrace, store_propagation);
|
||||
}
|
||||
var input_weight = getVar(input, 'weight');
|
||||
buildSentence(input_weight, ' += ', rate, ' * ', gradient,
|
||||
store_propagation);
|
||||
}
|
||||
|
||||
} else if (noGates) {
|
||||
buildSentence(responsibility, ' = 0', store_propagation);
|
||||
for (var id in this.connections.projected) {
|
||||
var connection = this.connections.projected[id];
|
||||
var neuron = connection.to;
|
||||
var connection_weight = getVar(connection, 'weight');
|
||||
var neuron_responsibility = getVar(neuron, 'error',
|
||||
'responsibility', neuron.error.responsibility);
|
||||
if (connection.gater) {
|
||||
var connection_gain = getVar(connection, 'gain');
|
||||
buildSentence(responsibility, ' += ', neuron_responsibility,
|
||||
' * ', connection_gain, ' * ', connection_weight,
|
||||
store_propagation);
|
||||
} else
|
||||
buildSentence(responsibility, ' += ', neuron_responsibility,
|
||||
' * ', connection_weight, store_propagation);
|
||||
}
|
||||
buildSentence(responsibility, ' *= ', derivative,
|
||||
store_propagation);
|
||||
for (var id in this.connections.inputs) {
|
||||
var input = this.connections.inputs[id];
|
||||
var trace = getVar(this, 'trace', 'elegibility', input.ID, this
|
||||
.trace.elegibility[input.ID]);
|
||||
var input_weight = getVar(input, 'weight');
|
||||
buildSentence(input_weight, ' += ', rate, ' * (',
|
||||
responsibility, ' * ', trace, ')', store_propagation);
|
||||
}
|
||||
} else if (noProjections) {
|
||||
buildSentence(responsibility, ' = 0', store_propagation);
|
||||
for (var id in this.trace.extended) {
|
||||
var neuron = this.neighboors[id];
|
||||
var influence = getVar('aux');
|
||||
var neuron_old = getVar(neuron, 'old');
|
||||
if (neuron.selfconnection.gater == this)
|
||||
buildSentence(influence, ' = ', neuron_old, store_propagation);
|
||||
else
|
||||
buildSentence(influence, ' = 0', store_propagation);
|
||||
for (var input in this.trace.influences[neuron.ID]) {
|
||||
var connection = this.trace.influences[neuron.ID][input];
|
||||
var connection_weight = getVar(connection, 'weight');
|
||||
var neuron_activation = getVar(connection.from, 'activation');
|
||||
buildSentence(influence, ' += ', connection_weight, ' * ',
|
||||
neuron_activation, store_propagation);
|
||||
}
|
||||
var neuron_responsibility = getVar(neuron, 'error',
|
||||
'responsibility', neuron.error.responsibility);
|
||||
buildSentence(responsibility, ' += ', neuron_responsibility,
|
||||
' * ', influence, store_propagation);
|
||||
}
|
||||
buildSentence(responsibility, ' *= ', derivative,
|
||||
store_propagation);
|
||||
for (var id in this.connections.inputs) {
|
||||
var input = this.connections.inputs[id];
|
||||
var gradient = getVar('aux');
|
||||
buildSentence(gradient, ' = 0', store_propagation);
|
||||
for (var id in this.trace.extended) {
|
||||
var neuron = this.neighboors[id];
|
||||
var neuron_responsibility = getVar(neuron, 'error',
|
||||
'responsibility', neuron.error.responsibility);
|
||||
var xtrace = getVar(this, 'trace', 'extended', neuron.ID,
|
||||
input.ID, this.trace.extended[neuron.ID][input.ID]);
|
||||
buildSentence(gradient, ' += ', neuron_responsibility, ' * ',
|
||||
xtrace, store_propagation);
|
||||
}
|
||||
var input_weight = getVar(input, 'weight');
|
||||
buildSentence(input_weight, ' += ', rate, ' * ', gradient,
|
||||
store_propagation);
|
||||
}
|
||||
}
|
||||
}
|
||||
buildSentence(bias, ' += ', rate, ' * ', responsibility,
|
||||
store_propagation);
|
||||
}
|
||||
return {
|
||||
memory: varID,
|
||||
neurons: neurons + 1,
|
||||
inputs: inputs,
|
||||
outputs: outputs,
|
||||
targets: targets,
|
||||
variables: variables,
|
||||
activation_sentences: activation_sentences,
|
||||
trace_sentences: trace_sentences,
|
||||
propagation_sentences: propagation_sentences,
|
||||
layers: layers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// represents a connection between two neurons
|
||||
Neuron.connection = function Connection(from, to, weight) {
|
||||
|
||||
if (!from || !to)
|
||||
throw new Error("Connection Error: Invalid neurons");
|
||||
|
||||
this.ID = Neuron.connection.uid();
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.weight = typeof weight == 'undefined' ? Math.random() * .2 - .1 :
|
||||
weight;
|
||||
this.gain = 1;
|
||||
this.gater = null;
|
||||
}
|
||||
|
||||
|
||||
// squashing functions
|
||||
Neuron.squash = {};
|
||||
|
||||
// eq. 5 & 5'
|
||||
Neuron.squash.LOGISTIC = function(x, derivate) {
|
||||
if (!derivate)
|
||||
return 1 / (1 + Math.exp(-x));
|
||||
var fx = Neuron.squash.LOGISTIC(x);
|
||||
return fx * (1 - fx);
|
||||
};
|
||||
Neuron.squash.TANH = function(x, derivate) {
|
||||
if (derivate)
|
||||
return 1 - Math.pow(Neuron.squash.TANH(x), 2);
|
||||
var eP = Math.exp(x);
|
||||
var eN = 1 / eP;
|
||||
return (eP - eN) / (eP + eN);
|
||||
};
|
||||
Neuron.squash.IDENTITY = function(x, derivate) {
|
||||
return derivate ? 1 : x;
|
||||
};
|
||||
Neuron.squash.HLIM = function(x, derivate) {
|
||||
return derivate ? 1 : x > 0 ? 1 : 0;
|
||||
};
|
||||
Neuron.squash.RELU = function(x, derivate) {
|
||||
if (derivate)
|
||||
return x > 0 ? 1 : 0;
|
||||
return x > 0 ? x : 0;
|
||||
};
|
||||
|
||||
// unique ID's
|
||||
(function() {
|
||||
var neurons = 0;
|
||||
var connections = 0;
|
||||
Neuron.uid = function() {
|
||||
return neurons++;
|
||||
}
|
||||
Neuron.connection.uid = function() {
|
||||
return connections++;
|
||||
}
|
||||
Neuron.quantity = function() {
|
||||
return {
|
||||
neurons: neurons,
|
||||
connections: connections
|
||||
}
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,33 @@
|
||||
var Synaptic = {
|
||||
Neuron: require('./neuron'),
|
||||
Layer: require('./layer'),
|
||||
Network: require('./network'),
|
||||
Trainer: require('./trainer'),
|
||||
Architect: require('./architect')
|
||||
};
|
||||
|
||||
// CommonJS & AMD
|
||||
if (typeof define !== 'undefined' && define.amd)
|
||||
{
|
||||
define([], function(){ return Synaptic });
|
||||
}
|
||||
|
||||
// Node.js
|
||||
if (typeof module !== 'undefined' && module.exports)
|
||||
{
|
||||
module.exports = Synaptic;
|
||||
}
|
||||
|
||||
// Browser
|
||||
if (typeof window == 'object')
|
||||
{
|
||||
(function(){
|
||||
var oldSynaptic = window['synaptic'];
|
||||
Synaptic.ninja = function(){
|
||||
window['synaptic'] = oldSynaptic;
|
||||
return Synaptic;
|
||||
};
|
||||
})();
|
||||
|
||||
window['synaptic'] = Synaptic;
|
||||
}
|
||||
@@ -0,0 +1,676 @@
|
||||
// export
|
||||
if (module) module.exports = Trainer;
|
||||
|
||||
/*******************************************************************************************
|
||||
TRAINER
|
||||
*******************************************************************************************/
|
||||
|
||||
function Trainer(network, options) {
|
||||
options = options || {};
|
||||
this.network = network;
|
||||
this.rate = options.rate || .2;
|
||||
this.iterations = options.iterations || 100000;
|
||||
this.error = options.error || .005;
|
||||
this.cost = options.cost || null;
|
||||
this.crossValidate = options.crossValidate || null;
|
||||
}
|
||||
|
||||
Trainer.prototype = {
|
||||
|
||||
// trains any given set to a network
|
||||
train: function(set, options) {
|
||||
|
||||
var error = 1;
|
||||
var iterations = bucketSize = 0;
|
||||
var abort = false;
|
||||
var currentRate;
|
||||
var cost = options && options.cost || this.cost || Trainer.cost.MSE;
|
||||
var crossValidate = false, testSet, trainSet;
|
||||
|
||||
var start = Date.now();
|
||||
|
||||
if (options) {
|
||||
if (options.shuffle) {
|
||||
//+ Jonas Raoni Soares Silva
|
||||
//@ http://jsfromhell.com/array/shuffle [v1.0]
|
||||
function shuffle(o) { //v1.0
|
||||
for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
|
||||
return o;
|
||||
};
|
||||
}
|
||||
if (options.iterations)
|
||||
this.iterations = options.iterations;
|
||||
if (options.error)
|
||||
this.error = options.error;
|
||||
if (options.rate)
|
||||
this.rate = options.rate;
|
||||
if (options.cost)
|
||||
this.cost = options.cost;
|
||||
if (options.schedule)
|
||||
this.schedule = options.schedule;
|
||||
if (options.customLog){
|
||||
// for backward compatibility with code that used customLog
|
||||
console.log('Deprecated: use schedule instead of customLog')
|
||||
this.schedule = options.customLog;
|
||||
}
|
||||
if (this.crossValidate || options.crossValidate) {
|
||||
if(!this.crossValidate) this.crossValidate = {};
|
||||
crossValidate = true;
|
||||
if (options.crossValidate.testSize)
|
||||
this.crossValidate.testSize = options.crossValidate.testSize;
|
||||
if (options.crossValidate.testError)
|
||||
this.crossValidate.testError = options.crossValidate.testError;
|
||||
}
|
||||
}
|
||||
|
||||
currentRate = this.rate;
|
||||
if(Array.isArray(this.rate)) {
|
||||
var bucketSize = Math.floor(this.iterations / this.rate.length);
|
||||
}
|
||||
|
||||
if(crossValidate) {
|
||||
var numTrain = Math.ceil((1 - this.crossValidate.testSize) * set.length);
|
||||
trainSet = set.slice(0, numTrain);
|
||||
testSet = set.slice(numTrain);
|
||||
}
|
||||
|
||||
var lastError = 0;
|
||||
while ((!abort && iterations < this.iterations && error > this.error)) {
|
||||
if (crossValidate && error <= this.crossValidate.testError) {
|
||||
break;
|
||||
}
|
||||
|
||||
var currentSetSize = set.length;
|
||||
error = 0;
|
||||
iterations++;
|
||||
|
||||
if(bucketSize > 0) {
|
||||
var currentBucket = Math.floor(iterations / bucketSize);
|
||||
currentRate = this.rate[currentBucket] || currentRate;
|
||||
}
|
||||
|
||||
if(typeof this.rate === 'function') {
|
||||
currentRate = this.rate(iterations, lastError);
|
||||
}
|
||||
|
||||
if (crossValidate) {
|
||||
this._trainSet(trainSet, currentRate, cost);
|
||||
error += this.test(testSet).error;
|
||||
currentSetSize = 1;
|
||||
} else {
|
||||
error += this._trainSet(set, currentRate, cost);
|
||||
currentSetSize = set.length;
|
||||
}
|
||||
|
||||
// check error
|
||||
error /= currentSetSize;
|
||||
lastError = error;
|
||||
|
||||
if (options) {
|
||||
if (this.schedule && this.schedule.every && iterations %
|
||||
this.schedule.every == 0)
|
||||
abort = this.schedule.do({ error: error, iterations: iterations, rate: currentRate });
|
||||
else if (options.log && iterations % options.log == 0) {
|
||||
console.log('iterations', iterations, 'error', error, 'rate', currentRate);
|
||||
};
|
||||
if (options.shuffle)
|
||||
shuffle(set);
|
||||
}
|
||||
}
|
||||
|
||||
var results = {
|
||||
error: error,
|
||||
iterations: iterations,
|
||||
time: Date.now() - start
|
||||
};
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
// trains any given set to a network, using a WebWorker (only for the browser). Returns a Promise of the results.
|
||||
trainAsync: function(set, options) {
|
||||
var train = this.workerTrain.bind(this);
|
||||
return new Promise(function(resolve, reject) {
|
||||
try {
|
||||
train(set, resolve, options, true)
|
||||
} catch(e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// preforms one training epoch and returns the error (private function used in this.train)
|
||||
_trainSet: function(set, currentRate, costFunction) {
|
||||
var errorSum = 0;
|
||||
for (var train in set) {
|
||||
var input = set[train].input;
|
||||
var target = set[train].output;
|
||||
|
||||
var output = this.network.activate(input);
|
||||
this.network.propagate(currentRate, target);
|
||||
|
||||
errorSum += costFunction(target, output);
|
||||
}
|
||||
return errorSum;
|
||||
},
|
||||
|
||||
// tests a set and returns the error and elapsed time
|
||||
test: function(set, options) {
|
||||
|
||||
var error = 0;
|
||||
var input, output, target;
|
||||
var cost = options && options.cost || this.cost || Trainer.cost.MSE;
|
||||
|
||||
var start = Date.now();
|
||||
|
||||
for (var test in set) {
|
||||
input = set[test].input;
|
||||
target = set[test].output;
|
||||
output = this.network.activate(input);
|
||||
error += cost(target, output);
|
||||
}
|
||||
|
||||
error /= set.length;
|
||||
|
||||
var results = {
|
||||
error: error,
|
||||
time: Date.now() - start
|
||||
};
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
// trains any given set to a network using a WebWorker [deprecated: use trainAsync instead]
|
||||
workerTrain: function(set, callback, options, suppressWarning) {
|
||||
|
||||
if (!suppressWarning) {
|
||||
console.warn('Deprecated: do not use `workerTrain`, use `trainAsync` instead.')
|
||||
}
|
||||
var that = this;
|
||||
|
||||
if (!this.network.optimized)
|
||||
this.network.optimize();
|
||||
|
||||
// Create a new worker
|
||||
var worker = this.network.worker(this.network.optimized.memory, set, options);
|
||||
|
||||
// train the worker
|
||||
worker.onmessage = function(e) {
|
||||
switch(e.data.action) {
|
||||
case 'done':
|
||||
var iterations = e.data.message.iterations;
|
||||
var error = e.data.message.error;
|
||||
var time = e.data.message.time;
|
||||
|
||||
that.network.optimized.ownership(e.data.memoryBuffer);
|
||||
|
||||
// Done callback
|
||||
callback({
|
||||
error: error,
|
||||
iterations: iterations,
|
||||
time: time
|
||||
});
|
||||
|
||||
// Delete the worker and all its associated memory
|
||||
worker.terminate();
|
||||
break;
|
||||
|
||||
case 'log':
|
||||
console.log(e.data.message);
|
||||
|
||||
case 'schedule':
|
||||
if (options && options.schedule && typeof options.schedule.do === 'function') {
|
||||
var scheduled = options.schedule.do
|
||||
scheduled(e.data.message)
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// Start the worker
|
||||
worker.postMessage({action: 'startTraining'});
|
||||
},
|
||||
|
||||
// trains an XOR to the network
|
||||
XOR: function(options) {
|
||||
|
||||
if (this.network.inputs() != 2 || this.network.outputs() != 1)
|
||||
throw new Error("Incompatible network (2 inputs, 1 output)");
|
||||
|
||||
var defaults = {
|
||||
iterations: 100000,
|
||||
log: false,
|
||||
shuffle: true,
|
||||
cost: Trainer.cost.MSE
|
||||
};
|
||||
|
||||
if (options)
|
||||
for (var i in options)
|
||||
defaults[i] = options[i];
|
||||
|
||||
return this.train([{
|
||||
input: [0, 0],
|
||||
output: [0]
|
||||
}, {
|
||||
input: [1, 0],
|
||||
output: [1]
|
||||
}, {
|
||||
input: [0, 1],
|
||||
output: [1]
|
||||
}, {
|
||||
input: [1, 1],
|
||||
output: [0]
|
||||
}], defaults);
|
||||
},
|
||||
|
||||
// trains the network to pass a Distracted Sequence Recall test
|
||||
DSR: function(options) {
|
||||
options = options || {};
|
||||
|
||||
var targets = options.targets || [2, 4, 7, 8];
|
||||
var distractors = options.distractors || [3, 5, 6, 9];
|
||||
var prompts = options.prompts || [0, 1];
|
||||
var length = options.length || 24;
|
||||
var criterion = options.success || 0.95;
|
||||
var iterations = options.iterations || 100000;
|
||||
var rate = options.rate || .1;
|
||||
var log = options.log || 0;
|
||||
var schedule = options.schedule || {};
|
||||
var cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY;
|
||||
|
||||
var trial, correct, i, j, success;
|
||||
trial = correct = i = j = success = 0;
|
||||
var error = 1,
|
||||
symbols = targets.length + distractors.length + prompts.length;
|
||||
|
||||
var noRepeat = function(range, avoid) {
|
||||
var number = Math.random() * range | 0;
|
||||
var used = false;
|
||||
for (var i in avoid)
|
||||
if (number == avoid[i])
|
||||
used = true;
|
||||
return used ? noRepeat(range, avoid) : number;
|
||||
};
|
||||
|
||||
var equal = function(prediction, output) {
|
||||
for (var i in prediction)
|
||||
if (Math.round(prediction[i]) != output[i])
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
var start = Date.now();
|
||||
|
||||
while (trial < iterations && (success < criterion || trial % 1000 != 0)) {
|
||||
// generate sequence
|
||||
var sequence = [],
|
||||
sequenceLength = length - prompts.length;
|
||||
for (i = 0; i < sequenceLength; i++) {
|
||||
var any = Math.random() * distractors.length | 0;
|
||||
sequence.push(distractors[any]);
|
||||
}
|
||||
var indexes = [],
|
||||
positions = [];
|
||||
for (i = 0; i < prompts.length; i++) {
|
||||
indexes.push(Math.random() * targets.length | 0);
|
||||
positions.push(noRepeat(sequenceLength, positions));
|
||||
}
|
||||
positions = positions.sort();
|
||||
for (i = 0; i < prompts.length; i++) {
|
||||
sequence[positions[i]] = targets[indexes[i]];
|
||||
sequence.push(prompts[i]);
|
||||
}
|
||||
|
||||
//train sequence
|
||||
var distractorsCorrect;
|
||||
var targetsCorrect = distractorsCorrect = 0;
|
||||
error = 0;
|
||||
for (i = 0; i < length; i++) {
|
||||
// generate input from sequence
|
||||
var input = [];
|
||||
for (j = 0; j < symbols; j++)
|
||||
input[j] = 0;
|
||||
input[sequence[i]] = 1;
|
||||
|
||||
// generate target output
|
||||
var output = [];
|
||||
for (j = 0; j < targets.length; j++)
|
||||
output[j] = 0;
|
||||
|
||||
if (i >= sequenceLength) {
|
||||
var index = i - sequenceLength;
|
||||
output[indexes[index]] = 1;
|
||||
}
|
||||
|
||||
// check result
|
||||
var prediction = this.network.activate(input);
|
||||
|
||||
if (equal(prediction, output))
|
||||
if (i < sequenceLength)
|
||||
distractorsCorrect++;
|
||||
else
|
||||
targetsCorrect++;
|
||||
else {
|
||||
this.network.propagate(rate, output);
|
||||
}
|
||||
|
||||
error += cost(output, prediction);
|
||||
|
||||
if (distractorsCorrect + targetsCorrect == length)
|
||||
correct++;
|
||||
}
|
||||
|
||||
// calculate error
|
||||
if (trial % 1000 == 0)
|
||||
correct = 0;
|
||||
trial++;
|
||||
var divideError = trial % 1000;
|
||||
divideError = divideError == 0 ? 1000 : divideError;
|
||||
success = correct / divideError;
|
||||
error /= length;
|
||||
|
||||
// log
|
||||
if (log && trial % log == 0)
|
||||
console.log("iterations:", trial, " success:", success, " correct:",
|
||||
correct, " time:", Date.now() - start, " error:", error);
|
||||
if (schedule.do && schedule.every && trial % schedule.every == 0)
|
||||
schedule.do({
|
||||
iterations: trial,
|
||||
success: success,
|
||||
error: error,
|
||||
time: Date.now() - start,
|
||||
correct: correct
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
iterations: trial,
|
||||
success: success,
|
||||
error: error,
|
||||
time: Date.now() - start
|
||||
}
|
||||
},
|
||||
|
||||
// train the network to learn an Embeded Reber Grammar
|
||||
ERG: function(options) {
|
||||
|
||||
options = options || {};
|
||||
var iterations = options.iterations || 150000;
|
||||
var criterion = options.error || .05;
|
||||
var rate = options.rate || .1;
|
||||
var log = options.log || 500;
|
||||
var cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY;
|
||||
|
||||
// gramar node
|
||||
var Node = function() {
|
||||
this.paths = [];
|
||||
};
|
||||
Node.prototype = {
|
||||
connect: function(node, value) {
|
||||
this.paths.push({
|
||||
node: node,
|
||||
value: value
|
||||
});
|
||||
return this;
|
||||
},
|
||||
any: function() {
|
||||
if (this.paths.length == 0)
|
||||
return false;
|
||||
var index = Math.random() * this.paths.length | 0;
|
||||
return this.paths[index];
|
||||
},
|
||||
test: function(value) {
|
||||
for (var i in this.paths)
|
||||
if (this.paths[i].value == value)
|
||||
return this.paths[i];
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
var reberGrammar = function() {
|
||||
|
||||
// build a reber grammar
|
||||
var output = new Node();
|
||||
var n1 = (new Node()).connect(output, "E");
|
||||
var n2 = (new Node()).connect(n1, "S");
|
||||
var n3 = (new Node()).connect(n1, "V").connect(n2, "P");
|
||||
var n4 = (new Node()).connect(n2, "X");
|
||||
n4.connect(n4, "S");
|
||||
var n5 = (new Node()).connect(n3, "V");
|
||||
n5.connect(n5, "T");
|
||||
n2.connect(n5, "X");
|
||||
var n6 = (new Node()).connect(n4, "T").connect(n5, "P");
|
||||
var input = (new Node()).connect(n6, "B");
|
||||
|
||||
return {
|
||||
input: input,
|
||||
output: output
|
||||
}
|
||||
};
|
||||
|
||||
// build an embeded reber grammar
|
||||
var embededReberGrammar = function() {
|
||||
var reber1 = reberGrammar();
|
||||
var reber2 = reberGrammar();
|
||||
|
||||
var output = new Node();
|
||||
var n1 = (new Node).connect(output, "E");
|
||||
reber1.output.connect(n1, "T");
|
||||
reber2.output.connect(n1, "P");
|
||||
var n2 = (new Node).connect(reber1.input, "P").connect(reber2.input,
|
||||
"T");
|
||||
var input = (new Node).connect(n2, "B");
|
||||
|
||||
return {
|
||||
input: input,
|
||||
output: output
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// generate an ERG sequence
|
||||
var generate = function() {
|
||||
var node = embededReberGrammar().input;
|
||||
var next = node.any();
|
||||
var str = "";
|
||||
while (next) {
|
||||
str += next.value;
|
||||
next = next.node.any();
|
||||
}
|
||||
return str;
|
||||
};
|
||||
|
||||
// test if a string matches an embeded reber grammar
|
||||
var test = function(str) {
|
||||
var node = embededReberGrammar().input;
|
||||
var i = 0;
|
||||
var ch = str.charAt(i);
|
||||
while (i < str.length) {
|
||||
var next = node.test(ch);
|
||||
if (!next)
|
||||
return false;
|
||||
node = next.node;
|
||||
ch = str.charAt(++i);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// helper to check if the output and the target vectors match
|
||||
var different = function(array1, array2) {
|
||||
var max1 = 0;
|
||||
var i1 = -1;
|
||||
var max2 = 0;
|
||||
var i2 = -1;
|
||||
for (var i in array1) {
|
||||
if (array1[i] > max1) {
|
||||
max1 = array1[i];
|
||||
i1 = i;
|
||||
}
|
||||
if (array2[i] > max2) {
|
||||
max2 = array2[i];
|
||||
i2 = i;
|
||||
}
|
||||
}
|
||||
|
||||
return i1 != i2;
|
||||
};
|
||||
|
||||
var iteration = 0;
|
||||
var error = 1;
|
||||
var table = {
|
||||
"B": 0,
|
||||
"P": 1,
|
||||
"T": 2,
|
||||
"X": 3,
|
||||
"S": 4,
|
||||
"E": 5
|
||||
};
|
||||
|
||||
var start = Date.now();
|
||||
while (iteration < iterations && error > criterion) {
|
||||
var i = 0;
|
||||
error = 0;
|
||||
|
||||
// ERG sequence to learn
|
||||
var sequence = generate();
|
||||
|
||||
// input
|
||||
var read = sequence.charAt(i);
|
||||
// target
|
||||
var predict = sequence.charAt(i + 1);
|
||||
|
||||
// train
|
||||
while (i < sequence.length - 1) {
|
||||
var input = [];
|
||||
var target = [];
|
||||
for (var j = 0; j < 6; j++) {
|
||||
input[j] = 0;
|
||||
target[j] = 0;
|
||||
}
|
||||
input[table[read]] = 1;
|
||||
target[table[predict]] = 1;
|
||||
|
||||
var output = this.network.activate(input);
|
||||
|
||||
if (different(output, target))
|
||||
this.network.propagate(rate, target);
|
||||
|
||||
read = sequence.charAt(++i);
|
||||
predict = sequence.charAt(i + 1);
|
||||
|
||||
error += cost(target, output);
|
||||
}
|
||||
error /= sequence.length;
|
||||
iteration++;
|
||||
if (iteration % log == 0) {
|
||||
console.log("iterations:", iteration, " time:", Date.now() - start,
|
||||
" error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
iterations: iteration,
|
||||
error: error,
|
||||
time: Date.now() - start,
|
||||
test: test,
|
||||
generate: generate
|
||||
}
|
||||
},
|
||||
|
||||
timingTask: function(options){
|
||||
|
||||
if (this.network.inputs() != 2 || this.network.outputs() != 1)
|
||||
throw new Error("Invalid Network: must have 2 inputs and one output");
|
||||
|
||||
if (typeof options == 'undefined')
|
||||
options = {};
|
||||
|
||||
// helper
|
||||
function getSamples (trainingSize, testSize){
|
||||
|
||||
// sample size
|
||||
var size = trainingSize + testSize;
|
||||
|
||||
// generate samples
|
||||
var t = 0;
|
||||
var set = [];
|
||||
for (var i = 0; i < size; i++) {
|
||||
set.push({ input: [0,0], output: [0] });
|
||||
}
|
||||
while(t < size - 20) {
|
||||
var n = Math.round(Math.random() * 20);
|
||||
set[t].input[0] = 1;
|
||||
for (var j = t; j <= t + n; j++){
|
||||
set[j].input[1] = n / 20;
|
||||
set[j].output[0] = 0.5;
|
||||
}
|
||||
t += n;
|
||||
n = Math.round(Math.random() * 20);
|
||||
for (var k = t+1; k <= (t + n) && k < size; k++)
|
||||
set[k].input[1] = set[t].input[1];
|
||||
t += n;
|
||||
}
|
||||
|
||||
// separate samples between train and test sets
|
||||
var trainingSet = []; var testSet = [];
|
||||
for (var l = 0; l < size; l++)
|
||||
(l < trainingSize ? trainingSet : testSet).push(set[l]);
|
||||
|
||||
// return samples
|
||||
return {
|
||||
train: trainingSet,
|
||||
test: testSet
|
||||
}
|
||||
}
|
||||
|
||||
var iterations = options.iterations || 200;
|
||||
var error = options.error || .005;
|
||||
var rate = options.rate || [.03, .02];
|
||||
var log = options.log === false ? false : options.log || 10;
|
||||
var cost = options.cost || this.cost || Trainer.cost.MSE;
|
||||
var trainingSamples = options.trainSamples || 7000;
|
||||
var testSamples = options.trainSamples || 1000;
|
||||
|
||||
// samples for training and testing
|
||||
var samples = getSamples(trainingSamples, testSamples);
|
||||
|
||||
// train
|
||||
var result = this.train(samples.train, {
|
||||
rate: rate,
|
||||
log: log,
|
||||
iterations: iterations,
|
||||
error: error,
|
||||
cost: cost
|
||||
});
|
||||
|
||||
return {
|
||||
train: result,
|
||||
test: this.test(samples.test)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Built-in cost functions
|
||||
Trainer.cost = {
|
||||
// Eq. 9
|
||||
CROSS_ENTROPY: function(target, output)
|
||||
{
|
||||
var crossentropy = 0;
|
||||
for (var i in output)
|
||||
crossentropy -= (target[i] * Math.log(output[i]+1e-15)) + ((1-target[i]) * Math.log((1+1e-15)-output[i])); // +1e-15 is a tiny push away to avoid Math.log(0)
|
||||
return crossentropy;
|
||||
},
|
||||
MSE: function(target, output)
|
||||
{
|
||||
var mse = 0;
|
||||
for (var i in output)
|
||||
mse += Math.pow(target[i] - output[i], 2);
|
||||
return mse / output.length;
|
||||
},
|
||||
BINARY: function(target, output){
|
||||
var misses = 0;
|
||||
for (var i in output)
|
||||
misses += Math.round(target[i] * 2) != Math.round(output[i] * 2);
|
||||
return misses;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
Test using mocha, from root directory:
|
||||
|
||||
`mocha test`
|
||||
|
||||
To test the web version, start a web server at the root dir of this repo, then use your OS browser.
|
||||
|
||||
[Python](http://xkcd.com/353/) to the rescue! From command line run this command:
|
||||
|
||||
```bash
|
||||
$ python -m SimpleHTTPServer 8080
|
||||
#go to http://localhost:8080/test/test_browser.html
|
||||
```
|
||||
|
||||
Or you can use node.js instead:
|
||||
|
||||
```bash
|
||||
$ npm install -g http-server
|
||||
```
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
$ http-server
|
||||
#go to http://localhost:8080/test/test_browser.html
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
global.synaptic = require('../dist/synaptic');
|
||||
@@ -0,0 +1 @@
|
||||
global.synaptic = require('../src/synaptic');
|
||||
@@ -0,0 +1 @@
|
||||
[^_]*.js
|
||||
Arquivo executável
+602
@@ -0,0 +1,602 @@
|
||||
// import
|
||||
var chai = require('chai');
|
||||
chai.use(require('chai-stats'));
|
||||
var assert = chai.assert;
|
||||
|
||||
var Perceptron = synaptic.Architect.Perceptron;
|
||||
var LSTM = synaptic.Architect.LSTM;
|
||||
var Layer = synaptic.Layer;
|
||||
var Network = synaptic.Network;
|
||||
var Trainer = synaptic.Trainer;
|
||||
|
||||
|
||||
|
||||
var learningRate = .5;
|
||||
|
||||
|
||||
// utils
|
||||
|
||||
function noRepeat(range, avoid) {
|
||||
var number = Math.random() * range | 0;
|
||||
for (var i in avoid) {
|
||||
if (number == avoid[i]) {
|
||||
return noRepeat(range, avoid);
|
||||
}
|
||||
}
|
||||
return number;
|
||||
}
|
||||
|
||||
function equal(prediction, output) {
|
||||
for (var i in prediction)
|
||||
if (Math.round(prediction[i]) != output[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function generateRandomArray(size) {
|
||||
var array = [];
|
||||
for (var j = 0; j < size; j++)
|
||||
array.push(Math.random() + .5 | 0);
|
||||
return array;
|
||||
}
|
||||
|
||||
function calculateMse(a, b) {
|
||||
var mse = 0;
|
||||
for (var k in a)
|
||||
mse += Math.pow(a[k] - b[k], 2);
|
||||
mse /= a.length;
|
||||
|
||||
return mse;
|
||||
}
|
||||
|
||||
function equalWithError(output, expected, error) {
|
||||
return Math.abs(output - expected) <= error;
|
||||
}
|
||||
|
||||
// specs
|
||||
|
||||
describe('Basic Neural Network', function () {
|
||||
|
||||
it("trains an AND gate", function () {
|
||||
|
||||
var inputLayer = new Layer(2),
|
||||
outputLayer = new Layer(1);
|
||||
|
||||
inputLayer.project(outputLayer);
|
||||
|
||||
var network = new Network({
|
||||
input: inputLayer,
|
||||
output: outputLayer
|
||||
});
|
||||
|
||||
var trainer = new Trainer(network);
|
||||
|
||||
var trainingSet = [{
|
||||
input: [0, 0],
|
||||
output: [0]
|
||||
}, {
|
||||
input: [0, 1],
|
||||
output: [0]
|
||||
}, {
|
||||
input: [1, 0],
|
||||
output: [0]
|
||||
}, {
|
||||
input: [1, 1],
|
||||
output: [1]
|
||||
}];
|
||||
|
||||
trainer.train(trainingSet, {
|
||||
iterations: 1000,
|
||||
error: .001
|
||||
});
|
||||
|
||||
var test00 = Math.round(network.activate([0, 0]));
|
||||
assert.equal(test00, 0, "[0,0] did not output 0");
|
||||
|
||||
var test01 = Math.round(network.activate([0, 1]));
|
||||
assert.equal(test01, 0, "[0,1] did not output 0");
|
||||
|
||||
var test10 = Math.round(network.activate([1, 0]));
|
||||
assert.equal(test10, 0, "[1,0] did not output 0");
|
||||
|
||||
var test11 = Math.round(network.activate([1, 1]));
|
||||
assert.equal(test11, 1, "[1,1] did not output 1");
|
||||
});
|
||||
|
||||
it("trains an OR gate", function () {
|
||||
|
||||
var inputLayer = new Layer(2),
|
||||
outputLayer = new Layer(1);
|
||||
|
||||
inputLayer.project(outputLayer);
|
||||
|
||||
var network = new Network({
|
||||
input: inputLayer,
|
||||
output: outputLayer
|
||||
});
|
||||
|
||||
var trainer = new Trainer(network);
|
||||
|
||||
var trainingSet = [{
|
||||
input: [0, 0],
|
||||
output: [0]
|
||||
}, {
|
||||
input: [0, 1],
|
||||
output: [1]
|
||||
}, {
|
||||
input: [1, 0],
|
||||
output: [1]
|
||||
}, {
|
||||
input: [1, 1],
|
||||
output: [1]
|
||||
}];
|
||||
|
||||
trainer.train(trainingSet, {
|
||||
iterations: 1000,
|
||||
error: .001
|
||||
});
|
||||
|
||||
var test00 = Math.round(network.activate([0, 0]));
|
||||
assert.equal(test00, 0, "[0,0] did not output 0");
|
||||
|
||||
var test01 = Math.round(network.activate([0, 1]));
|
||||
assert.equal(test01, 1, "[0,1] did not output 1");
|
||||
|
||||
var test10 = Math.round(network.activate([1, 0]));
|
||||
assert.equal(test10, 1, "[1,0] did not output 1");
|
||||
|
||||
var test11 = Math.round(network.activate([1, 1]));
|
||||
assert.equal(test11, 1, "[1,1] did not output 1");
|
||||
});
|
||||
|
||||
it("trains a NOT gate", function () {
|
||||
|
||||
var inputLayer = new Layer(1),
|
||||
outputLayer = new Layer(1);
|
||||
|
||||
inputLayer.project(outputLayer);
|
||||
|
||||
var network = new Network({
|
||||
input: inputLayer,
|
||||
output: outputLayer
|
||||
});
|
||||
|
||||
var trainer = new Trainer(network);
|
||||
var trainingSet = [{
|
||||
input: [0],
|
||||
output: [1]
|
||||
}, {
|
||||
input: [1],
|
||||
output: [0]
|
||||
}];
|
||||
|
||||
trainer.train(trainingSet, {
|
||||
iterations: 1000,
|
||||
error: .001
|
||||
});
|
||||
|
||||
var test0 = Math.round(network.activate([0]));
|
||||
assert.equal(test0, 1, "0 did not output 1");
|
||||
|
||||
var test1 = Math.round(network.activate([1]));
|
||||
assert.equal(test1, 0, "1 did not output 0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Perceptron - XOR", function () {
|
||||
|
||||
var perceptron = new Perceptron(2, 3, 1);
|
||||
perceptron.trainer.XOR();
|
||||
|
||||
it("should return near-0 value on [0,0]", function () {
|
||||
assert.isAtMost(perceptron.activate([0, 0]), .49, "[0,0] did not output 0");
|
||||
});
|
||||
|
||||
it("should return near-1 value on [0,1]", function () {
|
||||
assert.isAtLeast(perceptron.activate([0, 1]), .51, "[0,1] did not output 1");
|
||||
});
|
||||
|
||||
it("should return near-1 value on [1,0]", function () {
|
||||
assert.isAtLeast(perceptron.activate([1, 0]), .51, "[1,0] did not output 1");
|
||||
});
|
||||
|
||||
it("should return near-0 value on [1,1]", function () {
|
||||
assert.isAtMost(perceptron.activate([1, 1]), .49, "[1,1] did not output 0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Perceptron - SIN", function () {
|
||||
var mySin = function (x) {
|
||||
return (Math.sin(x) + 1) / 2;
|
||||
};
|
||||
|
||||
var sinNetwork = new Perceptron(1, 12, 1);
|
||||
var trainingSet = [];
|
||||
|
||||
while (trainingSet.length < 800) {
|
||||
var inputValue = Math.random() * Math.PI * 2;
|
||||
trainingSet.push({
|
||||
input: [inputValue],
|
||||
output: [mySin(inputValue)]
|
||||
});
|
||||
}
|
||||
|
||||
var results = sinNetwork.trainer.train(trainingSet, {
|
||||
iterations: 2000,
|
||||
log: false,
|
||||
error: 1e-6,
|
||||
cost: Trainer.cost.MSE,
|
||||
});
|
||||
|
||||
[0, .5 * Math.PI, 2]
|
||||
.forEach(function (x) {
|
||||
var y = mySin(x);
|
||||
it("should return value around " + y + " when [" + x + "] is on input", function () {
|
||||
// near scalability: abs(expected-actual) < 0.5 * 10**(-decimal)
|
||||
// 0.5 * Math.pow(10, -.15) => 0.35397289219206896
|
||||
assert.almostEqual(sinNetwork.activate([x])[0], y, .15);
|
||||
});
|
||||
});
|
||||
|
||||
var errorResult = results.error;
|
||||
it("Sin error: " + errorResult, function () {
|
||||
assert.isAtMost(errorResult, .001, "Sin error not less than or equal to desired error.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Perceptron - SIN - CrossValidate", function () {
|
||||
|
||||
var mySin = function (x) {
|
||||
return (Math.sin(x) + 1) / 2;
|
||||
};
|
||||
|
||||
var sinNetwork = new Perceptron(1, 12, 1);
|
||||
|
||||
var trainingSet = Array.apply(null, Array(800)).map(function () {
|
||||
var inputValue = Math.random() * Math.PI * 2;
|
||||
return {
|
||||
input: [inputValue],
|
||||
output: [mySin(inputValue)]
|
||||
};
|
||||
});
|
||||
|
||||
var results = sinNetwork.trainer.train(trainingSet, {
|
||||
iterations: 2000,
|
||||
log: false,
|
||||
error: 1e-6,
|
||||
cost: Trainer.cost.MSE,
|
||||
crossValidate: {
|
||||
testSize: .3,
|
||||
testError: 1e-6
|
||||
}
|
||||
});
|
||||
|
||||
var test0 = sinNetwork.activate([0])[0];
|
||||
var expected0 = mySin(0);
|
||||
it("input: [0] output: " + test0 + ", expected: " + expected0, function () {
|
||||
assert.isAtMost(Math.abs(test0 - expected0), .035, "[0] did not output " + expected0);
|
||||
});
|
||||
|
||||
var test05PI = sinNetwork.activate([.5 * Math.PI])[0];
|
||||
var expected05PI = mySin(.5 * Math.PI);
|
||||
it("input: [0.5*Math.PI] output: " + test05PI + ", expected: " + expected05PI, function () {
|
||||
assert.isAtMost(Math.abs(test05PI - expected05PI), .035, "[0.5*Math.PI] did not output " + expected05PI);
|
||||
});
|
||||
|
||||
var test2 = sinNetwork.activate([2])[0];
|
||||
var expected2 = mySin(2);
|
||||
it("input: [2] output: " + test2 + ", expected: " + expected2, function () {
|
||||
var eq = equalWithError(test2, expected2, .035);
|
||||
assert.equal(eq, true, "[2] did not output " + expected2);
|
||||
});
|
||||
|
||||
var errorResult = results.error;
|
||||
it("CrossValidation error: " + errorResult, function () {
|
||||
var lessThanOrEqualError = errorResult <= .001;
|
||||
assert.equal(lessThanOrEqualError, true, "CrossValidation error not less than or equal to desired error.");
|
||||
});
|
||||
});
|
||||
|
||||
describe("LSTM - Discrete Sequence Recall", function () {
|
||||
|
||||
var targets = [2, 4];
|
||||
var distractors = [3, 5];
|
||||
var prompts = [0, 1];
|
||||
var length = 9;
|
||||
|
||||
var lstm = new LSTM(5, 3, 2);
|
||||
lstm.trainer.DSR({
|
||||
targets: targets,
|
||||
distractors: distractors,
|
||||
prompts: prompts,
|
||||
length: length,
|
||||
rate: .17,
|
||||
iterations: 250000
|
||||
});
|
||||
|
||||
var symbols = targets.length + distractors.length + prompts.length;
|
||||
var sequence = [],
|
||||
indexes = [],
|
||||
positions = [];
|
||||
var sequenceLength = length - prompts.length;
|
||||
|
||||
for (i = 0; i < sequenceLength; i++) {
|
||||
var any = Math.random() * distractors.length | 0;
|
||||
sequence.push(distractors[any]);
|
||||
}
|
||||
indexes = [], positions = [];
|
||||
for (i = 0; i < prompts.length; i++) {
|
||||
indexes.push(Math.random() * targets.length | 0);
|
||||
positions.push(noRepeat(sequenceLength, positions));
|
||||
}
|
||||
positions = positions.sort();
|
||||
for (i = 0; i < prompts.length; i++) {
|
||||
sequence[positions[i]] = targets[indexes[i]];
|
||||
sequence.push(prompts[i]);
|
||||
}
|
||||
|
||||
var check = function (which) {
|
||||
// generate input from sequence
|
||||
var input = [];
|
||||
for (j = 0; j < symbols; j++)
|
||||
input[j] = 0;
|
||||
input[sequence[which]] = 1;
|
||||
|
||||
// generate target output
|
||||
var output = [];
|
||||
for (j = 0; j < targets.length; j++)
|
||||
output[j] = 0;
|
||||
|
||||
if (which >= sequenceLength) {
|
||||
var index = which - sequenceLength;
|
||||
output[indexes[index]] = 1;
|
||||
}
|
||||
|
||||
// check result
|
||||
var prediction = lstm.activate(input);
|
||||
return {
|
||||
prediction: prediction,
|
||||
output: output
|
||||
};
|
||||
};
|
||||
|
||||
var value = function (array) {
|
||||
var max = .5;
|
||||
var res = -1;
|
||||
for (var i in array)
|
||||
if (array[i] > max) {
|
||||
max = array[i];
|
||||
res = i;
|
||||
}
|
||||
return res == -1 ? '-' : targets[res];
|
||||
};
|
||||
|
||||
it("targets: " + targets, function () {
|
||||
assert(true);
|
||||
});
|
||||
it("distractors: " + distractors, function () {
|
||||
assert(true);
|
||||
});
|
||||
it("prompts: " + prompts, function () {
|
||||
assert(true);
|
||||
});
|
||||
it("length: " + length + "\n", function () {
|
||||
assert(true);
|
||||
});
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
var test = check(i);
|
||||
it((i + 1) + ") input: " + sequence[i] + " output: " + value(test.prediction),
|
||||
function () {
|
||||
var ok = equal(test.prediction, test.output);
|
||||
assert(ok);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("LSTM - Timing Task", function () {
|
||||
var network = new LSTM(2, 7, 1);
|
||||
var result = network.trainer.timingTask({
|
||||
log: false,
|
||||
trainSamples: 4000,
|
||||
testSamples: 500
|
||||
});
|
||||
|
||||
it("should complete the training in less than 200 iterations", function () {
|
||||
assert(result.train.iterations <= 200);
|
||||
});
|
||||
|
||||
it("should pass the test with an error smaller than 0.05", function () {
|
||||
assert(result.test.error < .05);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Optimized and Unoptimized Networks Equivalency", function () {
|
||||
|
||||
var optimized;
|
||||
var unoptimized;
|
||||
beforeEach(function () {
|
||||
optimized = new LSTM(2, 1, 1);
|
||||
unoptimized = optimized.clone();
|
||||
unoptimized.setOptimize(false);
|
||||
});
|
||||
|
||||
|
||||
it('should produce the same output for both networks', function () {
|
||||
this.timeout(30000);
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var input = generateRandomArray(2);
|
||||
var target = generateRandomArray(1);
|
||||
optimized.activate(input);
|
||||
unoptimized.activate(input);
|
||||
optimized.propagate(learningRate, target);
|
||||
unoptimized.propagate(learningRate, target);
|
||||
}
|
||||
var mse = calculateMse(optimized.activate(input), unoptimized.activate(input));
|
||||
assert.isAtMost(mse, 1e-9, 'output should be same for both networks after ' + i + ' iterations');
|
||||
});
|
||||
});
|
||||
|
||||
describe("toJSON/fromJSON Networks Equivalency", function () {
|
||||
var original;
|
||||
var imported;
|
||||
beforeEach(function () {
|
||||
original = new LSTM(10, 5, 5);
|
||||
imported = Network.fromJSON(original.toJSON());
|
||||
});
|
||||
|
||||
it('should produce the same output for both networks', function () {
|
||||
this.timeout(30000);
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var input = generateRandomArray(10);
|
||||
var output1 = original.activate(input);
|
||||
var output2 = imported.activate(input);
|
||||
|
||||
var target = generateRandomArray(5);
|
||||
|
||||
// propagate networks
|
||||
original.propagate(learningRate, target);
|
||||
imported.propagate(learningRate, target);
|
||||
|
||||
assert.isAtMost(calculateMse(output1, output2), 1e-10,
|
||||
'output should be same for both networks after ' + i + ' iterations');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cloned Networks Equivalency", function () {
|
||||
var original;
|
||||
var cloned;
|
||||
beforeEach(function () {
|
||||
original = new LSTM(10, 5, 5);
|
||||
cloned = Network.fromJSON(original.toJSON());
|
||||
});
|
||||
|
||||
it('should produce the same output for both networks', function () {
|
||||
this.timeout(30000);
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var input = generateRandomArray(10);
|
||||
var output1 = original.activate(input);
|
||||
var output2 = cloned.activate(input);
|
||||
|
||||
var target = generateRandomArray(5);
|
||||
|
||||
// propagate networks
|
||||
original.propagate(learningRate, target);
|
||||
cloned.propagate(learningRate, target);
|
||||
|
||||
assert.isAtMost(calculateMse(output1, output2), 1e-10,
|
||||
'output should be same for both networks after ' + i + ' iterations');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Scheduled Tasks", function () {
|
||||
var perceptron = new Perceptron(2, 3, 1);
|
||||
|
||||
it('should stop training at 3000 iterations', function () {
|
||||
var final_stats = perceptron.trainer.XOR({
|
||||
iterations: 3000,
|
||||
rate: 0.000001,
|
||||
error: 0.000001,
|
||||
schedule: {
|
||||
every: 1000,
|
||||
do: function (data) {
|
||||
return data.iterations == 20000;
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.equal(final_stats.iterations, 3000)
|
||||
});
|
||||
|
||||
it('should abort the training at 2000 iterations', function () {
|
||||
var final_stats = perceptron.trainer.XOR({
|
||||
iterations: 3000,
|
||||
rate: 0.000001,
|
||||
error: 0.000001,
|
||||
schedule: {
|
||||
every: 1000,
|
||||
do: function (data) {
|
||||
return data.iterations == 2000;
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.equal(final_stats.iterations, 2000)
|
||||
});
|
||||
|
||||
it('should work even if schedule.do() returns no value', function () {
|
||||
var final_stats = perceptron.trainer.XOR({
|
||||
iterations: 3000,
|
||||
rate: 0.000001,
|
||||
error: 0.000001,
|
||||
schedule: {
|
||||
every: 1000,
|
||||
do: function (data) {}
|
||||
}
|
||||
});
|
||||
assert.equal(final_stats.iterations, 3000)
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Rate Callback Check", function () {
|
||||
var perceptron = new Perceptron(2, 3, 1);
|
||||
|
||||
it('should switch rate from 0.01 to 0.005 after 1000 iterations', function () {
|
||||
var final_stats = perceptron.trainer.XOR({
|
||||
iterations: 2000,
|
||||
rate: function (iterations, error) {
|
||||
return iterations < 1000 ? 0.01 : 0.005
|
||||
},
|
||||
error: 0.000001,
|
||||
schedule: {
|
||||
every: 1,
|
||||
do: function (data) {
|
||||
switch (data.iterations) {
|
||||
case 1:
|
||||
case 500:
|
||||
case 999:
|
||||
assert.equal(data.rate, 0.01);
|
||||
break;
|
||||
|
||||
case 1000:
|
||||
case 1500:
|
||||
case 2000:
|
||||
assert.equal(data.rate, 0.005);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Rate Array Check", function () {
|
||||
var perceptron = new Perceptron(2, 3, 1);
|
||||
|
||||
it('should switch rate from 0.01 to 0.005 after 1000 iterations', function () {
|
||||
var final_stats = perceptron.trainer.XOR({
|
||||
iterations: 2000,
|
||||
rate: [0.01, 0.005],
|
||||
error: 0.000001,
|
||||
schedule: {
|
||||
every: 1,
|
||||
do: function (data) {
|
||||
switch (data.iterations) {
|
||||
case 1:
|
||||
case 500:
|
||||
case 999:
|
||||
assert.equal(data.rate, 0.01);
|
||||
break;
|
||||
|
||||
case 1000:
|
||||
case 1500:
|
||||
case 2000:
|
||||
assert.equal(data.rate, 0.005);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
var webpack = require('webpack')
|
||||
var license = require('./prebuild.js')
|
||||
module.exports = {
|
||||
context: __dirname,
|
||||
entry: {
|
||||
synaptic: './src/synaptic.js',
|
||||
'synaptic.min': './src/synaptic.js'
|
||||
},
|
||||
output: {
|
||||
path: 'dist',
|
||||
filename: '[name].js',
|
||||
},
|
||||
plugins: [
|
||||
new webpack.NoErrorsPlugin(),
|
||||
new webpack.BannerPlugin(license())
|
||||
]
|
||||
}
|
||||
Referência em uma Nova Issue
Bloquear um usuário