Comparar commits
76 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 1b2f31330e | |||
| ec2a393158 | |||
| d3e2ea8488 | |||
| 4cfd5a828f | |||
| 24807634e8 | |||
| 5df6e5cb17 | |||
| 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 |
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": []
|
||||
}
|
||||
@@ -16,3 +16,5 @@ demo.js
|
||||
|
||||
# Degub
|
||||
debug.html
|
||||
|
||||
.settings
|
||||
|
||||
+7
-1
@@ -1,3 +1,9 @@
|
||||
language: node_js
|
||||
script: "npm run test:travis"
|
||||
node_js:
|
||||
- "0.10"
|
||||
# always latest release
|
||||
- "node"
|
||||
# previous releases
|
||||
- "6"
|
||||
- "5"
|
||||
- "4"
|
||||
Arquivo executável → Arquivo normal
+29
-5
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Juan Cazala (juancazala.com)
|
||||
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.
|
||||
|
||||
+21
-13
@@ -1,4 +1,4 @@
|
||||
Synaptic [](https://travis-ci.org/cazala/synaptic)
|
||||
Synaptic [](https://travis-ci.org/cazala/synaptic) [](https://synaptic-slack-ugiqacqvmd.now.sh/)
|
||||
========
|
||||
|
||||
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.
|
||||
@@ -17,6 +17,11 @@ There are references to the equations in that paper commented through the source
|
||||
|
||||
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://synaptic.juancazala.com/#/xor)
|
||||
@@ -36,12 +41,17 @@ The source code of these demos can be found in [this branch](https://github.com/
|
||||
- [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
|
||||
@@ -49,10 +59,17 @@ npm install synaptic --save
|
||||
```
|
||||
|
||||
#####In the browser
|
||||
Just include the file synaptic.js from `/dist` directory with a script tag in your HTML:
|
||||
|
||||
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="synaptic.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/synaptic/1.0.8/synaptic.js"></script>
|
||||
```
|
||||
|
||||
###Usage
|
||||
@@ -68,15 +85,6 @@ var Neuron = synaptic.Neuron,
|
||||
|
||||
Now you can start to create networks, train them, or use built-in networks from the [Architect](http://github.com/cazala/synaptic#architect).
|
||||
|
||||
###Gulp Tasks
|
||||
|
||||
- **gulp**: runs all the tests and builds the minified and unminified bundles into `/dist`.
|
||||
- **gulp build**: builds the bundle: `/dist/synaptic.js`.
|
||||
- **gulp min**: builds the minified bundle: `/dist/synaptic.min.js`.
|
||||
- **gulp debug**: builds the bundle `/dist/synaptic.js` with sourcemaps.
|
||||
- **gulp dev**: same as `gulp debug`, but watches the source files and rebuilds when any change is detected.
|
||||
- **gulp test**: runs all the tests.
|
||||
|
||||
###Examples
|
||||
|
||||
#####Perceptron
|
||||
@@ -186,6 +194,6 @@ Multilayer LSTM network architectures.
|
||||
|
||||
**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 the default **gulp** task before submiting it. This way you'll run all the test specs and build the web distribution files.
|
||||
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
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "synaptic",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.8",
|
||||
"homepage": "https://github.com/cazala/synaptic",
|
||||
"authors": [
|
||||
"Juan Cazala <juancazala@gmail.com>"
|
||||
|
||||
externo
+2889
-2734
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
-54
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -1,60 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var license = '/*\n\nThe MIT License (MIT)\n\nCopyright (c) 2014 Juan Cazala - juancazala.com\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the "Software"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE\n\n\n\n********************************************************************************************\n SYNAPTIC\n********************************************************************************************\n\nSynaptic is a javascript neural network library for node.js and the browser, its generalized\nalgorithm is architecture-free, so you can build and train basically any type of first order\nor even second order neural network architectures.\n\nhttp://en.wikipedia.org/wiki/Recurrent_neural_network#Second_Order_Recurrent_Neural_Network\n\nThe library includes a few built-in architectures like multilayer perceptrons, multilayer\nlong-short term memory networks (LSTM) or liquid state machines, and a trainer capable of\ntraining any given network, and includes built-in training tasks/tests like solving an XOR,\npassing a Distracted Sequence Recall test or an Embeded Reber Grammar test.\n\nThe algorithm implemented by this library has been taken from Derek D. Monner\'s paper:\n\n\nA generalized LSTM-like training algorithm for second-order recurrent neural networks\nhttp://www.overcomplete.net/papers/nn2012.pdf\n\nThere are references to the equations in that paper commented through the source code.\n\n\n********************************************************************************************/\n'
|
||||
var globals = 'var Neuron = synaptic.Neuron, Layer = synaptic.Layer, Network = synaptic.Network, Trainer = synaptic.Trainer, Architect = synaptic.Architect;';
|
||||
|
||||
// import
|
||||
var gulp = require('gulp');
|
||||
var browserify = require('browserify');
|
||||
var uglify = require('gulp-uglify');
|
||||
var mocha = require('gulp-mocha');
|
||||
var prepend = require('gulp-insert').prepend;
|
||||
var append = require('gulp-insert').append;
|
||||
var source = require('vinyl-source-stream');
|
||||
var buffer = require('vinyl-buffer');
|
||||
|
||||
// default task: runs all the tests, and builds all the files into dist (minified and unminifed)
|
||||
gulp.task('default', ['test', 'build', 'min']);
|
||||
|
||||
// build source into /dist for the web
|
||||
gulp.task('build', function () {
|
||||
return browserify({ entries: ['./src/synaptic.js'] })
|
||||
.bundle()
|
||||
.pipe(source('synaptic.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(append(globals))
|
||||
.pipe(gulp.dest('./dist'));
|
||||
});
|
||||
|
||||
// build source into /dist for web (minified)
|
||||
gulp.task('min', function () {
|
||||
return browserify({ entries: ['./src/synaptic.js'] })
|
||||
.bundle()
|
||||
.pipe(source('synaptic.min.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(uglify())
|
||||
.pipe(prepend(license))
|
||||
.pipe(append(globals))
|
||||
.pipe(gulp.dest('./dist'));
|
||||
});
|
||||
|
||||
// build source into /dist with sourcemaps for debugging
|
||||
gulp.task('debug', function () {
|
||||
return browserify({ entries: ['./src/synaptic.js'], debug: true })
|
||||
.bundle()
|
||||
.pipe(source('synaptic.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(append(globals))
|
||||
.pipe(gulp.dest('./dist'));
|
||||
});
|
||||
|
||||
// run all the tests with mocha
|
||||
gulp.task('test', function () {
|
||||
return gulp.src('test/synaptic.js', {read: false})
|
||||
.pipe(mocha());
|
||||
});
|
||||
|
||||
// watch for changed and re-build (debug)
|
||||
gulp.task('dev', function () {
|
||||
gulp.watch('./src/*.js', ['debug']);
|
||||
});
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0]);
|
||||
|
||||
if (!nodeMajorVersion || nodeMajorVersion < 6) {
|
||||
module.exports = require('./dist/synaptic')
|
||||
} else {
|
||||
try {
|
||||
module.exports = require('./src/synaptic')
|
||||
} catch (e) {
|
||||
module.exports = require('./dist/synaptic')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
var webpack = require('webpack');
|
||||
var webpackConfig = require('./webpack.config');
|
||||
// Karma configuration
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['mocha'],
|
||||
files: [
|
||||
'test/*.js',
|
||||
],
|
||||
exclude: [
|
||||
],
|
||||
preprocessors: {
|
||||
'test/*.js': ['webpack'],
|
||||
},
|
||||
client: {
|
||||
grep: /^[^(Node)]/
|
||||
},
|
||||
reporters: ['progress'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
singleRun: false,
|
||||
concurrency: Infinity,
|
||||
browserNoActivityTimeout: 60000,
|
||||
webpack: {
|
||||
loaders: webpackConfig.loaders,
|
||||
plugins: webpackConfig.plugins
|
||||
.concat([
|
||||
// does not affect production build, only used for tests
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.SYNAPTIC_PREFER_SRC': JSON.stringify(process.env.SYNAPTIC_PREFER_SRC),
|
||||
'process.env.KARMA': 'true',
|
||||
}),
|
||||
new webpack.NoErrorsPlugin(),
|
||||
])
|
||||
},
|
||||
})
|
||||
}
|
||||
Arquivo executável → Arquivo normal
+42
-15
@@ -1,22 +1,43 @@
|
||||
{
|
||||
"name": "synaptic",
|
||||
"version": "1.0.0",
|
||||
"description": "Architecture-free neural network library",
|
||||
"main": "./src/synaptic",
|
||||
"version": "1.0.8",
|
||||
"description": "architecture-free neural network library",
|
||||
"main": "./index.js",
|
||||
"scripts": {
|
||||
"test": "mocha test"
|
||||
"test": "npm run test:src",
|
||||
"test:src": "npm run test:mocha:src",
|
||||
"test:dist": "npm run build && npm run test:mocha:dist && npm run test:karma:browsers",
|
||||
"test:mocha:src": "SYNAPTIC_PREFER_SRC=true mocha test",
|
||||
"test:mocha:dist": "mocha test",
|
||||
"test:karma:browsers": "karma start --single-run --browsers Chrome,Firefox,SafariPrivate",
|
||||
"test:karma:chrome": "karma start --browsers Chrome",
|
||||
"test:karma:phantomjs": "karma start --single-run --browsers PhantomJS",
|
||||
"test:travis": "npm run build && npm run test:mocha:dist",
|
||||
"build": "webpack --config webpack.config.js"
|
||||
},
|
||||
"prepush": [
|
||||
"test",
|
||||
"build"
|
||||
],
|
||||
"devDependencies": {
|
||||
"browserify": "^10.1.3",
|
||||
"gulp": "^3.8.11",
|
||||
"gulp-insert": "^0.4.0",
|
||||
"gulp-mocha": "^2.0.1",
|
||||
"gulp-sourcemaps": "^1.5.2",
|
||||
"gulp-uglify": "^1.2.0",
|
||||
"gulp-util": "^3.0.4",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"mocha": "^2.2.4"
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-preset-es2015": "^6.9.0",
|
||||
"chai": "^3.5.0",
|
||||
"chai-as-promised": "^5.3.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-private-launcher": "^1.0.0",
|
||||
"karma-webpack": "^1.7.0",
|
||||
"mocha": "^2.2.4",
|
||||
"pre-push": "^0.1.1",
|
||||
"tiny-worker": "^1.1.5",
|
||||
"webpack": "^1.13.1",
|
||||
"webworker-threads": "^0.7.8",
|
||||
"worker-loader": "^0.7.1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -38,5 +59,11 @@
|
||||
"url": "https://github.com/cazala/synaptic/issues"
|
||||
},
|
||||
"homepage": "http://synaptic.juancazala.com",
|
||||
"engines" : { "node" : "^0.10.0" }
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-worker": "0.0.2",
|
||||
"pseudo-worker": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
// import
|
||||
var Layer = require('./layer'),
|
||||
Network = require('./network'),
|
||||
Trainer = require('./trainer');
|
||||
|
||||
/*******************************************************************************************
|
||||
ARCHITECT
|
||||
*******************************************************************************************/
|
||||
|
||||
// Colection 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 "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 (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 "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;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
const Network = require('../network');
|
||||
const Trainer = require('../trainer');
|
||||
|
||||
class AbstractArchitecture extends Network {
|
||||
constructor(layers) {
|
||||
super(layers);
|
||||
this.trainer = new this.constructor.Trainer(this);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AbstractArchitecture;
|
||||
|
||||
AbstractArchitecture.NetworkClass = Network;
|
||||
AbstractArchitecture.Trainer = Trainer;
|
||||
@@ -0,0 +1,42 @@
|
||||
const AbstractArchitecture = require('./abstract-architecture');
|
||||
const Layer = require('../layer');
|
||||
|
||||
module.exports = class Hopfield extends AbstractArchitecture {
|
||||
constructor(size) {
|
||||
const inputLayer = new Layer(size);
|
||||
const outputLayer = new Layer(size);
|
||||
|
||||
inputLayer.project(outputLayer, Layer.connectionType.ALL_TO_ALL);
|
||||
|
||||
super({
|
||||
input: inputLayer,
|
||||
hidden: [],
|
||||
output: outputLayer
|
||||
});
|
||||
}
|
||||
|
||||
learn(patterns) {
|
||||
const set = [];
|
||||
for (let p in patterns)
|
||||
set.push({
|
||||
input: patterns[p],
|
||||
output: patterns[p]
|
||||
});
|
||||
|
||||
return this.trainer.train(set, {
|
||||
iterations: 500000,
|
||||
error: .00005,
|
||||
rate: 1
|
||||
});
|
||||
}
|
||||
|
||||
feed(pattern) {
|
||||
const output = this.activate(pattern);
|
||||
|
||||
pattern = [];
|
||||
for (let i in output)
|
||||
pattern[i] = output[i] > .5 ? 1 : 0;
|
||||
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports.Hopfield = require('./hopfield');
|
||||
module.exports.LSTM = require('./lstm');
|
||||
module.exports.Liquid = require('./liquid');
|
||||
module.exports.Perceptron = require('./perceptron');
|
||||
@@ -0,0 +1,44 @@
|
||||
// Liquid State Machine
|
||||
const AbstractArchitecture = require('./abstract-architecture');
|
||||
const Layer = require('../layer');
|
||||
|
||||
module.exports = class Liquid extends AbstractArchitecture {
|
||||
constructor(inputs, hidden, outputs, connections, gates) {
|
||||
// create layers
|
||||
const inputLayer = new Layer(inputs);
|
||||
const hiddenLayer = new Layer(hidden);
|
||||
const outputLayer = new Layer(outputs);
|
||||
|
||||
// make connections and gates randomly among the neurons
|
||||
const neurons = hiddenLayer.neurons();
|
||||
const connectionList = [];
|
||||
|
||||
for (let i = 0; i < connections; i++) {
|
||||
// connect two random neurons
|
||||
const from = Math.random() * neurons.length | 0;
|
||||
const to = Math.random() * neurons.length | 0;
|
||||
var connection = neurons[from].project(neurons[to]);
|
||||
connectionList.push(connection);
|
||||
}
|
||||
|
||||
for (let j = 0; j < gates; j++) {
|
||||
// pick a random gater neuron
|
||||
const 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
|
||||
super({
|
||||
input: inputLayer,
|
||||
hidden: [hiddenLayer],
|
||||
output: outputLayer
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// Multilayer Long Short-Term Memory
|
||||
const AbstractArchitecture = require('./abstract-architecture');
|
||||
const Layer = require('../layer');
|
||||
|
||||
module.exports = class LSTM extends AbstractArchitecture {
|
||||
constructor(...args) {
|
||||
if (args.length < 3)
|
||||
throw new Error(`Not enough layers. Minimum 3 expected, instead got ${args[0]}, ${args[1]}`);
|
||||
|
||||
const last = args.pop();
|
||||
const 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;
|
||||
|
||||
const inputs = args.shift();
|
||||
const layers = args;
|
||||
|
||||
const inputLayer = new Layer(inputs);
|
||||
const hiddenLayers = [];
|
||||
const outputLayer = new Layer(outputs);
|
||||
|
||||
let previous = null;
|
||||
|
||||
// generate layers
|
||||
for (let layer in layers) {
|
||||
// generate memory blocks (memory cell and respective gates)
|
||||
const size = layers[layer];
|
||||
|
||||
const inputGate = new Layer(size).set({
|
||||
bias: 1
|
||||
});
|
||||
const forgetGate = new Layer(size).set({
|
||||
bias: 1
|
||||
});
|
||||
const memoryCell = new Layer(size);
|
||||
const outputGate = new Layer(size).set({
|
||||
bias: 1
|
||||
});
|
||||
|
||||
hiddenLayers.push(inputGate);
|
||||
hiddenLayers.push(forgetGate);
|
||||
hiddenLayers.push(memoryCell);
|
||||
hiddenLayers.push(outputGate);
|
||||
|
||||
// connections from input layer
|
||||
const 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
|
||||
const output = memoryCell.project(outputLayer);
|
||||
|
||||
// self-connection
|
||||
const 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
|
||||
super({
|
||||
input: inputLayer,
|
||||
hidden: hiddenLayers,
|
||||
output: outputLayer
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
// Multilayer Perceptron
|
||||
const AbstractArchitecture = require('./abstract-architecture');
|
||||
const Layer = require('../layer');
|
||||
|
||||
module.exports = class Perceptron extends AbstractArchitecture {
|
||||
constructor(...args) {
|
||||
if (args.length < 3)
|
||||
throw new Error(`Not enough layers. Minimum 3 expected, instead got ${args[0]}, ${args[1]}`);
|
||||
|
||||
const inputs = args.shift(); // first argument
|
||||
const outputs = args.pop(); // last argument
|
||||
const layers = args; // all the arguments in the middle
|
||||
|
||||
const input = new Layer(inputs);
|
||||
const hidden = [];
|
||||
const output = new Layer(outputs);
|
||||
|
||||
let previous = input;
|
||||
|
||||
// generate hidden layers
|
||||
for (let level in layers) {
|
||||
const size = layers[level];
|
||||
const layer = new Layer(size);
|
||||
hidden.push(layer);
|
||||
previous.project(layer);
|
||||
previous = layer;
|
||||
}
|
||||
previous.project(output);
|
||||
|
||||
// set layers of the neural network
|
||||
super({
|
||||
input,
|
||||
hidden,
|
||||
output
|
||||
});
|
||||
}
|
||||
};
|
||||
+107
-115
@@ -1,32 +1,37 @@
|
||||
// import
|
||||
var Neuron = require('./neuron');
|
||||
const Neuron = require('./neuron');
|
||||
|
||||
/*******************************************************************************************
|
||||
LAYER
|
||||
*******************************************************************************************/
|
||||
LAYER
|
||||
*******************************************************************************************/
|
||||
|
||||
function Layer(size, label) {
|
||||
this.size = size | 0;
|
||||
this.list = [];
|
||||
this.label = label || null;
|
||||
this.connectedTo = [];
|
||||
class Layer {
|
||||
constructor(size, label) {
|
||||
this.label = label || null;
|
||||
this.connectedTo = [];
|
||||
|
||||
while (size--) {
|
||||
var neuron = new Neuron();
|
||||
this.list.push(neuron);
|
||||
if (Array.isArray(size)) {
|
||||
const neurons = size;
|
||||
this.size = neurons.length;
|
||||
this.list = neurons.slice(); // cloning the array
|
||||
} else {
|
||||
this.size = size | 0;
|
||||
this.list = [];
|
||||
|
||||
while (size--) {
|
||||
const neuron = new Neuron();
|
||||
this.list.push(neuron);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Layer.prototype = {
|
||||
|
||||
// activates all the neurons in the layer
|
||||
activate: function(input) {
|
||||
activate(input) {
|
||||
|
||||
var activations = [];
|
||||
const activations = [];
|
||||
|
||||
if (typeof input != 'undefined') {
|
||||
if (input.length != this.size)
|
||||
throw "INPUT size and LAYER size must be the same to activate!";
|
||||
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];
|
||||
@@ -41,14 +46,14 @@ Layer.prototype = {
|
||||
}
|
||||
}
|
||||
return activations;
|
||||
},
|
||||
}
|
||||
|
||||
// propagates the error on all the neurons of the layer
|
||||
propagate: function(rate, target) {
|
||||
propagate(rate, target) {
|
||||
|
||||
if (typeof target != 'undefined') {
|
||||
if (target.length != this.size)
|
||||
throw "TARGET size and LAYER size must be the same to propagate!";
|
||||
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];
|
||||
@@ -60,34 +65,32 @@ Layer.prototype = {
|
||||
neuron.propagate(rate);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// projects a connection from this layer to another one
|
||||
project: function(layer, type, weights) {
|
||||
|
||||
if (layer instanceof require('./network'))
|
||||
layer = layer.layers.input;
|
||||
project(layer, type, weights) {
|
||||
if (layer && layer.layers && layer.layers.input)
|
||||
return this.project(layer.layers.input, type, weights);
|
||||
|
||||
if (layer instanceof Layer) {
|
||||
if (!this.connected(layer))
|
||||
return new Layer.connection(this, layer, type, weights);
|
||||
} else
|
||||
throw "Invalid argument, you can only project connections to LAYERS and NETWORKS!";
|
||||
|
||||
|
||||
},
|
||||
} 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) {
|
||||
gate(connection, type) {
|
||||
|
||||
if (type == Layer.gateType.INPUT) {
|
||||
if (connection.to.size != this.size)
|
||||
throw "GATER layer and CONNECTION.TO layer must be the same size in order to gate!";
|
||||
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) {
|
||||
for (let input in neuron.connections.inputs) {
|
||||
var gated = neuron.connections.inputs[input];
|
||||
if (gated.ID in connection.connections)
|
||||
gater.gate(gated);
|
||||
@@ -95,12 +98,12 @@ Layer.prototype = {
|
||||
}
|
||||
} else if (type == Layer.gateType.OUTPUT) {
|
||||
if (connection.from.size != this.size)
|
||||
throw "GATER layer and CONNECTION.FROM layer must be the same size in order to gate!";
|
||||
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) {
|
||||
for (let projected in neuron.connections.projected) {
|
||||
var gated = neuron.connections.projected[projected];
|
||||
if (gated.ID in connection.connections)
|
||||
gater.gate(gated);
|
||||
@@ -108,7 +111,7 @@ Layer.prototype = {
|
||||
}
|
||||
} else if (type == Layer.gateType.ONE_TO_ONE) {
|
||||
if (connection.size != this.size)
|
||||
throw "The number of GATER UNITS must be the same as the number of CONNECTIONS to gate!";
|
||||
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];
|
||||
@@ -116,26 +119,26 @@ Layer.prototype = {
|
||||
gater.gate(gated);
|
||||
}
|
||||
}
|
||||
connection.gatedfrom.push({layer: this, type: type});
|
||||
},
|
||||
connection.gatedfrom.push({layer: this, type});
|
||||
}
|
||||
|
||||
// true or false whether the whole layer is self-connected or not
|
||||
selfconnected: function() {
|
||||
selfconnected() {
|
||||
|
||||
for (var id in this.list) {
|
||||
var neuron = this.list[id];
|
||||
for (let id in this.list) {
|
||||
const 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) {
|
||||
connected(layer) {
|
||||
// Check if ALL to ALL connection
|
||||
var connections = 0;
|
||||
for (var here in this.list) {
|
||||
for (var there in layer.list) {
|
||||
let connections = 0;
|
||||
for (let here in this.list) {
|
||||
for (let there in layer.list) {
|
||||
var from = this.list[here];
|
||||
var to = layer.list[there];
|
||||
var connected = from.connected(to);
|
||||
@@ -148,7 +151,7 @@ Layer.prototype = {
|
||||
|
||||
// Check if ONE to ONE connection
|
||||
connections = 0;
|
||||
for (var neuron in this.list) {
|
||||
for (let neuron in this.list) {
|
||||
var from = this.list[neuron];
|
||||
var to = layer.list[neuron];
|
||||
var connected = from.connected(to);
|
||||
@@ -157,43 +160,35 @@ Layer.prototype = {
|
||||
}
|
||||
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];
|
||||
clear() {
|
||||
for (let id in this.list) {
|
||||
const 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];
|
||||
reset() {
|
||||
for (let id in this.list) {
|
||||
const neuron = this.list[id];
|
||||
neuron.reset();
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// returns all the neurons in the layer (array)
|
||||
neurons: function() {
|
||||
neurons() {
|
||||
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];
|
||||
set(options = {}) {
|
||||
for (let i in this.list) {
|
||||
const neuron = this.list[i];
|
||||
if (options.label)
|
||||
neuron.label = options.label + '_' + neuron.ID;
|
||||
neuron.label = `${options.label}_${neuron.ID}`;
|
||||
if (options.squash)
|
||||
neuron.squash = options.squash;
|
||||
if (options.bias)
|
||||
@@ -204,52 +199,53 @@ Layer.prototype = {
|
||||
}
|
||||
|
||||
// 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 = [];
|
||||
Layer.connection = class LayerConnection {
|
||||
constructor(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 (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 ||
|
||||
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;
|
||||
for (let here in this.from.list) {
|
||||
for (let 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 (let 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);
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
fromLayer.connectedTo.push(this);
|
||||
}
|
||||
|
||||
// types of connections
|
||||
@@ -264,13 +260,9 @@ 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++;
|
||||
}
|
||||
})();
|
||||
|
||||
// export
|
||||
if (module) module.exports = Layer;
|
||||
((() => {
|
||||
let connections = 0;
|
||||
Layer.connection.uid = () => connections++
|
||||
}))();
|
||||
|
||||
module.exports = Layer;
|
||||
@@ -0,0 +1,18 @@
|
||||
const Network = require('./network');
|
||||
|
||||
class SynapticNetwork extends Network {
|
||||
}
|
||||
|
||||
module.exports = SynapticNetwork;
|
||||
|
||||
// features enabling
|
||||
if (global.Worker) {
|
||||
// this code should be kept on
|
||||
const WorkerProxyNetwork = require('./worker-proxy-network');
|
||||
// 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.
|
||||
SynapticNetwork.prototype.worker = function () {
|
||||
return WorkerProxyNetwork.fromJSON(this.toJSON());
|
||||
}
|
||||
}
|
||||
@@ -1,64 +1,52 @@
|
||||
// import
|
||||
var Neuron = require('./neuron'),
|
||||
Layer = require('./layer');
|
||||
const Layer = require('../layer');
|
||||
const Neuron = require('../neuron');
|
||||
|
||||
/*******************************************************************************************
|
||||
NETWORK
|
||||
*******************************************************************************************/
|
||||
NETWORK
|
||||
*******************************************************************************************/
|
||||
|
||||
function Network(layers) {
|
||||
if (typeof layers != 'undefined') {
|
||||
this.layers = layers || {
|
||||
input: null,
|
||||
hidden: {},
|
||||
output: null
|
||||
};
|
||||
class Network {
|
||||
constructor(layers) {
|
||||
this.layers = layers;
|
||||
this.optimized = null;
|
||||
}
|
||||
}
|
||||
Network.prototype = {
|
||||
|
||||
// feed-forward activation of all the layers to produce an ouput
|
||||
activate: function(input) {
|
||||
|
||||
if (this.optimized === false)
|
||||
{
|
||||
activate(input) {
|
||||
if (this.optimized === false) {
|
||||
this.layers.input.activate(input);
|
||||
for (var layer in this.layers.hidden)
|
||||
for (let layer in this.layers.hidden)
|
||||
this.layers.hidden[layer].activate();
|
||||
return this.layers.output.activate();
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
else {
|
||||
if (this.optimized == null)
|
||||
this.optimize();
|
||||
return this.optimized.activate(input);
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// back-propagate the error thru the network
|
||||
propagate: function(rate, target) {
|
||||
propagate(rate, target) {
|
||||
|
||||
if (this.optimized === false)
|
||||
{
|
||||
if (this.optimized === false) {
|
||||
this.layers.output.propagate(rate, target);
|
||||
var reverse = [];
|
||||
const 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
|
||||
{
|
||||
}
|
||||
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) {
|
||||
project(unit, type, weights) {
|
||||
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
@@ -69,64 +57,62 @@ Network.prototype = {
|
||||
if (unit instanceof Layer)
|
||||
return this.layers.output.project(unit, type, weights);
|
||||
|
||||
throw "Invalid argument, you can only project connections to LAYERS and NETWORKS!";
|
||||
},
|
||||
throw new Error("Invalid argument, you can only project connections to LAYERS and NETWORKS!");
|
||||
}
|
||||
|
||||
// let this network gate a connection
|
||||
gate: function(connection, type) {
|
||||
gate(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() {
|
||||
clear() {
|
||||
|
||||
this.restore();
|
||||
|
||||
var inputLayer = this.layers.input,
|
||||
outputLayer = this.layers.output;
|
||||
const inputLayer = this.layers.input, outputLayer = this.layers.output;
|
||||
|
||||
inputLayer.clear();
|
||||
for (var layer in this.layers.hidden) {
|
||||
var hiddenLayer = this.layers.hidden[layer];
|
||||
for (let layer in this.layers.hidden) {
|
||||
const 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() {
|
||||
reset() {
|
||||
|
||||
this.restore();
|
||||
|
||||
var inputLayer = this.layers.input,
|
||||
outputLayer = this.layers.output;
|
||||
const inputLayer = this.layers.input, outputLayer = this.layers.output;
|
||||
|
||||
inputLayer.reset();
|
||||
for (var layer in this.layers.hidden) {
|
||||
var hiddenLayer = this.layers.hidden[layer];
|
||||
for (let layer in this.layers.hidden) {
|
||||
const 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() {
|
||||
optimize() {
|
||||
|
||||
var that = this;
|
||||
var optimized = {};
|
||||
var neurons = this.neurons();
|
||||
const that = this;
|
||||
let optimized = {};
|
||||
const neurons = this.neurons();
|
||||
|
||||
for (var i in neurons) {
|
||||
var neuron = neurons[i].neuron;
|
||||
var layer = neurons[i].layer;
|
||||
let neuron = neurons[i].neuron;
|
||||
const layer = neurons[i].layer;
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
optimized = neuron.optimize(optimized, layer);
|
||||
@@ -135,15 +121,14 @@ Network.prototype = {
|
||||
optimized.propagation_sentences[i].reverse();
|
||||
optimized.propagation_sentences.reverse();
|
||||
|
||||
var hardcode = "";
|
||||
hardcode += "var F = Float64Array ? new Float64Array(" + optimized.memory +
|
||||
") : []; ";
|
||||
let 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 += `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 + "]; ";
|
||||
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]) {
|
||||
@@ -154,15 +139,15 @@ Network.prototype = {
|
||||
}
|
||||
hardcode += " var output = []; "
|
||||
for (var i in optimized.outputs)
|
||||
hardcode += "output[" + i + "] = F[" + optimized.outputs[i] + "]; ";
|
||||
hardcode += `output[${i}] = F[${optimized.outputs[i]}]; `;
|
||||
hardcode += "return output; }; "
|
||||
hardcode += "var propagate = function(rate, target){\n";
|
||||
hardcode += "F[" + optimized.variables.rate.id + "] = rate; ";
|
||||
hardcode += `F[${optimized.variables.rate.id}] = rate; `;
|
||||
for (var i in optimized.targets)
|
||||
hardcode += "F[" + optimized.targets[i] + "] = target[" + i + "]; ";
|
||||
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 += `${optimized.propagation_sentences[currentLayer][currentNeuron].join(" ")} `;
|
||||
hardcode += " };\n";
|
||||
hardcode +=
|
||||
"var ownership = function(memoryBuffer){\nF = memoryBuffer;\nthis.memory = F;\n};\n";
|
||||
@@ -170,9 +155,9 @@ Network.prototype = {
|
||||
"return {\nmemory: F,\nactivate: activate,\npropagate: propagate,\nownership: ownership\n};";
|
||||
hardcode = hardcode.split(";").join(";\n");
|
||||
|
||||
var constructor = new Function(hardcode);
|
||||
const constructor = new Function(hardcode);
|
||||
|
||||
var network = constructor();
|
||||
const network = constructor();
|
||||
network.data = {
|
||||
variables: optimized.variables,
|
||||
activate: optimized.activation_sentences,
|
||||
@@ -184,7 +169,7 @@ Network.prototype = {
|
||||
check_propagation: this.propagate
|
||||
}
|
||||
|
||||
network.reset = function() {
|
||||
network.reset = () => {
|
||||
if (that.optimized) {
|
||||
that.optimized = null;
|
||||
that.activate = network.data.check_activation;
|
||||
@@ -195,38 +180,38 @@ Network.prototype = {
|
||||
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() {
|
||||
restore() {
|
||||
if (!this.optimized)
|
||||
return;
|
||||
|
||||
var optimized = this.optimized;
|
||||
const optimized = this.optimized;
|
||||
|
||||
var getValue = function() {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
const getValue = function () {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
|
||||
var unit = args.shift();
|
||||
var prop = args.pop();
|
||||
const unit = args.shift();
|
||||
const prop = args.pop();
|
||||
|
||||
var id = prop + '_';
|
||||
for (var property in args)
|
||||
id += args[property] + '_';
|
||||
let id = `${prop}_`;
|
||||
for (let property in args)
|
||||
id += `${args[property]}_`;
|
||||
id += unit.ID;
|
||||
|
||||
var memory = optimized.memory;
|
||||
var variables = optimized.data.variables;
|
||||
const memory = optimized.memory;
|
||||
const variables = optimized.data.variables;
|
||||
|
||||
if (id in variables)
|
||||
return memory[variables[id].id];
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
var list = this.neurons();
|
||||
const list = this.neurons();
|
||||
|
||||
// link id's to positions in the array
|
||||
var ids = {};
|
||||
const ids = {};
|
||||
for (var i in list) {
|
||||
var neuron = list[i].neuron;
|
||||
while (neuron.neuron)
|
||||
@@ -241,7 +226,7 @@ Network.prototype = {
|
||||
neuron.trace.elegibility[input] = getValue(neuron, 'trace',
|
||||
'elegibility', input);
|
||||
|
||||
for (var gated in neuron.trace.extended)
|
||||
for (let gated in neuron.trace.extended)
|
||||
for (var input in neuron.trace.extended[gated])
|
||||
neuron.trace.extended[gated][input] = getValue(neuron, 'trace',
|
||||
'extended', gated, input);
|
||||
@@ -253,21 +238,20 @@ Network.prototype = {
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
|
||||
for (var j in neuron.connections.projected) {
|
||||
var connection = neuron.connections.projected[j];
|
||||
for (let j in neuron.connections.projected) {
|
||||
const connection = neuron.connections.projected[j];
|
||||
connection.weight = getValue(connection, 'weight');
|
||||
connection.gain = getValue(connection, 'gain');
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// returns all the neurons in the network
|
||||
neurons: function() {
|
||||
neurons() {
|
||||
|
||||
var neurons = [];
|
||||
const neurons = [];
|
||||
|
||||
var inputLayer = this.layers.input.neurons(),
|
||||
outputLayer = this.layers.output.neurons();
|
||||
const inputLayer = this.layers.input.neurons(), outputLayer = this.layers.output.neurons();
|
||||
|
||||
for (var neuron in inputLayer)
|
||||
neurons.push({
|
||||
@@ -275,12 +259,12 @@ Network.prototype = {
|
||||
layer: 'input'
|
||||
});
|
||||
|
||||
for (var layer in this.layers.hidden) {
|
||||
var hiddenLayer = this.layers.hidden[layer].neurons();
|
||||
for (let layer in this.layers.hidden) {
|
||||
const hiddenLayer = this.layers.hidden[layer].neurons();
|
||||
for (var neuron in hiddenLayer)
|
||||
neurons.push({
|
||||
neuron: hiddenLayer[neuron],
|
||||
layer: layer
|
||||
layer
|
||||
});
|
||||
}
|
||||
for (var neuron in outputLayer)
|
||||
@@ -290,51 +274,51 @@ Network.prototype = {
|
||||
});
|
||||
|
||||
return neurons;
|
||||
},
|
||||
}
|
||||
|
||||
// returns number of inputs of the network
|
||||
inputs: function() {
|
||||
inputs() {
|
||||
return this.layers.input.size;
|
||||
},
|
||||
}
|
||||
|
||||
// returns number of outputs of hte network
|
||||
outputs: function() {
|
||||
outputs() {
|
||||
return this.layers.output.size;
|
||||
},
|
||||
}
|
||||
|
||||
// sets the layers of the network
|
||||
set: function(layers) {
|
||||
|
||||
set(layers) {
|
||||
console.warn('This method is deprecated! Use super(layers) in constructor instead');
|
||||
this.layers = layers;
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
},
|
||||
}
|
||||
|
||||
setOptimize: function(bool){
|
||||
setOptimize(bool) {
|
||||
this.restore();
|
||||
if (this.optimized)
|
||||
this.optimized.reset();
|
||||
this.optimized = bool? null : false;
|
||||
},
|
||||
this.optimized = bool ? null : false;
|
||||
}
|
||||
|
||||
// returns a json that represents all the neurons and connections of the network
|
||||
toJSON: function(ignoreTraces) {
|
||||
toJSON(ignoreTraces) {
|
||||
|
||||
this.restore();
|
||||
|
||||
var list = this.neurons();
|
||||
var neurons = [];
|
||||
var connections = [];
|
||||
const list = this.neurons();
|
||||
const neurons = [];
|
||||
const connections = [];
|
||||
|
||||
// link id's to positions in the array
|
||||
var ids = {};
|
||||
const ids = {};
|
||||
for (var i in list) {
|
||||
var neuron = list[i].neuron;
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
ids[neuron.ID] = i;
|
||||
|
||||
var copy = {
|
||||
const copy = {
|
||||
trace: {
|
||||
elegibility: {},
|
||||
extended: {}
|
||||
@@ -346,11 +330,7 @@ Network.prototype = {
|
||||
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;
|
||||
copy.squash = neuron.squash.squashType || neuron.squash.toString();
|
||||
|
||||
neurons.push(copy);
|
||||
}
|
||||
@@ -361,8 +341,8 @@ Network.prototype = {
|
||||
while (neuron.neuron)
|
||||
neuron = neuron.neuron;
|
||||
|
||||
for (var j in neuron.connections.projected) {
|
||||
var connection = neuron.connections.projected[j];
|
||||
for (let j in neuron.connections.projected) {
|
||||
const connection = neuron.connections.projected[j];
|
||||
connections.push({
|
||||
from: ids[connection.from.ID],
|
||||
to: ids[connection.to.ID],
|
||||
@@ -380,202 +360,178 @@ Network.prototype = {
|
||||
}
|
||||
|
||||
return {
|
||||
neurons: neurons,
|
||||
connections: connections
|
||||
neurons,
|
||||
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)
|
||||
$ node example.js > example.dot
|
||||
$ dot example.dot -Tpng > out.png
|
||||
*/
|
||||
toDot(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);
|
||||
let code = "digraph nn {\n rankdir = BT\n";
|
||||
const layers = [this.layers.input].concat(this.layers.hidden, this.layers.output);
|
||||
for (let layer in layers) {
|
||||
for (let to in layers[layer].connectedTo) { // projections
|
||||
const connection = layers[layer].connectedTo[to];
|
||||
const layerTo = connection.to;
|
||||
const size = connection.size;
|
||||
const layerID = layers.indexOf(layers[layer]);
|
||||
const 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 (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";
|
||||
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";
|
||||
code += ` ${layerID} -> ${layerToID} [label = ${size}]\n`;
|
||||
for (var from in connection.gatedfrom) { // gatings
|
||||
var layerfrom = connection.gatedfrom[from].layer;
|
||||
var type = connection.gatedfrom[from].type;
|
||||
var layerfromID = layers.indexOf(layerfrom);
|
||||
code += " " + layerfromID + " -> " + fakeNode + " [color = blue]\n";
|
||||
code += ` ${layerfromID} -> ${fakeNode} [color = blue]\n`;
|
||||
}
|
||||
} else {
|
||||
code += " " + layerID + " -> " + layerToID + " [label = " + size + "]\n";
|
||||
code += ` ${layerID} -> ${layerToID} [label = ${size}]\n`;
|
||||
for (var from in connection.gatedfrom) { // gatings
|
||||
var layerfrom = connection.gatedfrom[from].layer;
|
||||
var type = connection.gatedfrom[from].type;
|
||||
var layerfromID = layers.indexOf(layerfrom);
|
||||
code += " " + layerfromID + " -> " + layerToID + " [color = blue]\n";
|
||||
code += ` ${layerfromID} -> ${layerToID} [color = blue]\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
code += "}\n";
|
||||
return {
|
||||
code: code,
|
||||
link: "https://chart.googleapis.com/chart?chl=" + escape(code.replace("/ /g", "+")) + "&cht=gv"
|
||||
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() {
|
||||
standalone() {
|
||||
if (!this.optimized)
|
||||
this.optimize();
|
||||
|
||||
var data = this.optimized.data;
|
||||
const data = this.optimized.data;
|
||||
|
||||
// build activation function
|
||||
var activation = "function (input) {\n";
|
||||
let activation = "function (input) {\n";
|
||||
|
||||
// build inputs
|
||||
for (var i in data.inputs)
|
||||
activation += "F[" + data.inputs[i] + "] = input[" + i + "];\n";
|
||||
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] + "\n";
|
||||
for (let neuron in data.activate) { // shouldn't this be layer?
|
||||
for (let 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 += `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];
|
||||
const memory = activation.match(/F\[(\d+)\]/g);
|
||||
let dimension = 0;
|
||||
const ids = {};
|
||||
for (let address in memory) {
|
||||
const tmp = memory[address].match(/\d+/)[0];
|
||||
if (!(tmp in ids)) {
|
||||
ids[tmp] = dimension++;
|
||||
}
|
||||
}
|
||||
var hardcode = "F = {\n";
|
||||
let 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 += `${ids[i]}: ${this.optimized.memory[i]},\n`;
|
||||
hardcode = `${hardcode.substring(0, hardcode.length - 2)}\n};\n`;
|
||||
hardcode = `var run = ${activation.replace(/F\[(\d+)]/g, index => 'F[' + ids[index.match(/\d+/)[0]] + ']').replace("{\n", "{\n" + hardcode + "")};\n`;
|
||||
hardcode += "return run";
|
||||
|
||||
// return standalone function
|
||||
return new Function(hardcode)();
|
||||
},
|
||||
|
||||
worker: function() {
|
||||
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 = null;\n";
|
||||
hardcode += "var activate = " + this.optimized.activate.toString() +
|
||||
";\n";
|
||||
hardcode += "var propagate = " + this.optimized.propagate.toString() +
|
||||
";\n";
|
||||
hardcode += "onmessage = function(e){\n";
|
||||
hardcode += "F = e.data.memoryBuffer;\n";
|
||||
hardcode += "if (e.data.action == 'activate'){\n";
|
||||
hardcode += "if (e.data.input.length == inputs){\n";
|
||||
hardcode +=
|
||||
"postMessage( { action: 'activate', output: activate(e.data.input), memoryBuffer: F }, [F.buffer]);\n";
|
||||
hardcode += "}\n}\nelse if (e.data.action == 'propagate'){\n";
|
||||
hardcode += "propagate(e.data.rate, e.data.target);\n";
|
||||
hardcode +=
|
||||
"postMessage({ action: 'propagate', memoryBuffer: F }, [F.buffer]);\n";
|
||||
hardcode += "}\n}\n";
|
||||
|
||||
var blob = new Blob([hardcode]);
|
||||
var blobURL = window.URL.createObjectURL(blob);
|
||||
|
||||
return new Worker(blobURL);
|
||||
},
|
||||
}
|
||||
|
||||
// returns a copy of the network
|
||||
clone: function() {
|
||||
return Network.fromJSON(this.toJSON());
|
||||
clone() {
|
||||
return this.constructor.fromJSON(this.toJSON());
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild a network that has been stored in a json using the method toJSON()
|
||||
Network.fromJSON = function(json) {
|
||||
Network.fromJSON = function (json) {
|
||||
|
||||
var neurons = [];
|
||||
const neurons = [];
|
||||
|
||||
var layers = {
|
||||
input: new Layer(),
|
||||
const layers = {
|
||||
input: [],
|
||||
hidden: [],
|
||||
output: new Layer()
|
||||
}
|
||||
output: []
|
||||
};
|
||||
|
||||
for (var i in json.neurons) {
|
||||
var config = json.neurons[i];
|
||||
|
||||
var neuron = new Neuron();
|
||||
const 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;
|
||||
if (Neuron.squash[config.squash] instanceof Function && Neuron.squash[config.squash].length === 1) {
|
||||
neuron.squash = Neuron.squash[config.squash];
|
||||
}
|
||||
|
||||
if (!neuron.squash) {
|
||||
neuron.squash = Neuron.squash.LOGISTIC;
|
||||
}
|
||||
|
||||
neurons.push(neuron);
|
||||
|
||||
if (config.layer == 'input')
|
||||
layers.input.add(neuron);
|
||||
layers.input.push(neuron);
|
||||
else if (config.layer == 'output')
|
||||
layers.output.add(neuron);
|
||||
layers.output.push(neuron);
|
||||
else {
|
||||
if (typeof layers.hidden[config.layer] == 'undefined')
|
||||
layers.hidden[config.layer] = new Layer();
|
||||
layers.hidden[config.layer].add(neuron);
|
||||
layers.hidden[config.layer] = [];
|
||||
layers.hidden[config.layer].push(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];
|
||||
const from = neurons[config.from];
|
||||
const to = neurons[config.to];
|
||||
const weight = config.weight;
|
||||
const gater = neurons[config.gater];
|
||||
|
||||
var connection = from.project(to, weight);
|
||||
const connection = from.project(to, weight);
|
||||
if (gater)
|
||||
gater.gate(connection);
|
||||
}
|
||||
|
||||
return new Network(layers);
|
||||
}
|
||||
const instance = new this.NetworkClass({
|
||||
input: new Layer(layers.input),
|
||||
hidden: layers.hidden.map(layer => new Layer(layer)),
|
||||
output: new Layer(layers.output)
|
||||
});
|
||||
Object.setPrototypeOf(instance, instance.__proto__ = this.prototype);
|
||||
return instance;
|
||||
};
|
||||
|
||||
// export
|
||||
if (module) module.exports = Network;
|
||||
Network.NetworkClass = Network;
|
||||
|
||||
module.exports = Network;
|
||||
@@ -0,0 +1,3 @@
|
||||
module.exports.GET = '%get';
|
||||
module.exports.SET = '%set';
|
||||
module.exports.FROM_JSON = '%fromJSON';
|
||||
@@ -0,0 +1,38 @@
|
||||
var WorkerNetwork = require('./network');
|
||||
var constants = require('./worker-communication-constants');
|
||||
|
||||
onmessage = function (e) {
|
||||
const {method, args, callbackId} = e.data;
|
||||
try {
|
||||
var response = callMethod(method, args);
|
||||
postMessage({response: [null, response], callbackId})
|
||||
} catch (e) {
|
||||
postMessage({response: [e.message], callbackId})
|
||||
}
|
||||
};
|
||||
|
||||
const callMethod = (function() {
|
||||
var instance;
|
||||
|
||||
return function callMethod(method, args) {
|
||||
var result;
|
||||
switch (method) {
|
||||
case constants.GET:
|
||||
result = instance[args[0]];
|
||||
break;
|
||||
case constants.SET:
|
||||
instance[method][args[0]] = args[1];
|
||||
break;
|
||||
case constants.FROM_JSON:
|
||||
instance = WorkerNetwork.fromJSON(args[0]);
|
||||
break;
|
||||
default:
|
||||
result = instance[method](...args);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
var WorkerNetworkWorker = require('worker?inline!./worker-index.worker.js');
|
||||
|
||||
module.exports = class WorkerNetworkProxyAPI {
|
||||
constructor() {
|
||||
var worker = this.worker = new WorkerNetworkWorker();
|
||||
this.callbacks = {};
|
||||
this.nextCallbackId = 1;
|
||||
|
||||
worker.onmessage = ({data: {response: [error, data] = [], callbackId} = {}}) =>
|
||||
this.callbacks[callbackId](error, data);
|
||||
}
|
||||
|
||||
subscribe(method, args, callback) {
|
||||
const callbackId = this.nextCallbackId++;
|
||||
this.callbacks[callbackId] = callback;
|
||||
this.worker.postMessage({method, args, callbackId});
|
||||
return callbackId;
|
||||
}
|
||||
|
||||
unsubscribe(callbackId) {
|
||||
return delete this.callbacks[callbackId];
|
||||
}
|
||||
|
||||
callMethod(method, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const callbackId = this.subscribe(method, args, (err, data) => {
|
||||
this.unsubscribe(callbackId);
|
||||
err ? reject(err) : resolve(data);
|
||||
});
|
||||
})
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
var Network = require('./network');
|
||||
var constants = require('./worker-communication-constants');
|
||||
var WorkerNetworkProxyAPI = require('./worker-network-proxy-api');
|
||||
|
||||
const workerNetworkConstructorInternalKey = {};
|
||||
|
||||
const workerNetworkProxyKey = '_workerNetworkProxyAPI';
|
||||
const getGetterName = key => `get${key[0].toUpperCase()}${key.slice(1)}`;
|
||||
const getSetterName = key => `set${key[0].toUpperCase()}${key.slice(1)}`;
|
||||
|
||||
class WorkerProxyNetwork extends Network {
|
||||
constructor(key, workerNetworkProxyAPI) {
|
||||
if (key !== workerNetworkConstructorInternalKey)
|
||||
throw new Error(
|
||||
`cannot initiate ${this.name} directly as it is constructed in an async manner. ` +
|
||||
`Instead of that use ${this.name}.fromJSON() or ${this.name}.construct`);
|
||||
|
||||
super();
|
||||
this[workerNetworkProxyKey] = workerNetworkProxyAPI;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WorkerProxyNetwork;
|
||||
|
||||
WorkerProxyNetwork.construct = function (proxy) { return new this(workerNetworkConstructorInternalKey, proxy) };
|
||||
|
||||
WorkerProxyNetwork.fromJSON = function (jsonAlikeObject, proxyAPI = new WorkerNetworkProxyAPI()) {
|
||||
return proxyAPI.callMethod(constants.FROM_JSON, [jsonAlikeObject])
|
||||
.then(() => this.construct(proxyAPI))
|
||||
};
|
||||
|
||||
|
||||
const prototypeKeys = Object.getOwnPropertyNames(Network.prototype);
|
||||
|
||||
const methodKeys = prototypeKeys.filter(key => (Network.prototype[key] instanceof Function));
|
||||
const propertyKeys = prototypeKeys.filter(key => !(Network.prototype[key] instanceof Function));
|
||||
|
||||
const createDef = (key, value, length) => {
|
||||
Object.defineProperty(value, 'name', {value: key});
|
||||
if (length !== undefined)
|
||||
Object.defineProperty(value, 'length', {value: length});
|
||||
return {key, descriptor: {value}};
|
||||
};
|
||||
|
||||
const definitions = [
|
||||
...methodKeys
|
||||
.map(key =>
|
||||
createDef(key,
|
||||
function (...args) { return this[workerNetworkProxyKey].callMethod(key, args) }, Network.prototype[key].length)),
|
||||
...propertyKeys
|
||||
.map(key => ({
|
||||
key,
|
||||
descriptor: {
|
||||
get() {
|
||||
throw new Error(`cannot get property ${this.constructor.name}#${key} directly as actual data reflected is stored in a worker. `
|
||||
+ `To get it use method ${this.constructor.name}#${getGetterName(key)} with a signature () => Promise<value>`)
|
||||
},
|
||||
set() {
|
||||
throw new Error(`cannot set property ${this.constructor.name}#${key} directly as actual data reflected is stored in a worker. `
|
||||
+ `To set it use method ${this.constructor.name}#${getSetterName(key)} with a signature (value) => Promise<>`)
|
||||
}
|
||||
}
|
||||
})),
|
||||
...propertyKeys
|
||||
.map(key => createDef(getGetterName(key),
|
||||
function () { return this[workerNetworkProxyKey].callMethod(constants.GET, [key]) })),
|
||||
...propertyKeys
|
||||
.map(key => createDef(getSetterName(key),
|
||||
function (value) { return this[workerNetworkProxyKey].callMethod(constants.SET, [key, value]) }))
|
||||
];
|
||||
|
||||
for (const definition of definitions)
|
||||
Object.defineProperty(WorkerProxyNetwork.prototype, definition.key, definition.descriptor);
|
||||
+192
-202
@@ -1,38 +1,37 @@
|
||||
/******************************************************************************************
|
||||
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 = {
|
||||
class Neuron {
|
||||
constructor() {
|
||||
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;
|
||||
}
|
||||
|
||||
// activate the neuron
|
||||
activate: function(input) {
|
||||
activate(input) {
|
||||
// activation from enviroment (for input neurons)
|
||||
if (typeof input != 'undefined') {
|
||||
this.activation = input;
|
||||
@@ -60,69 +59,68 @@ Neuron.prototype = {
|
||||
this.derivative = this.squash(this.state, true);
|
||||
|
||||
// update traces
|
||||
var influences = [];
|
||||
const influences = [];
|
||||
for (var id in this.trace.extended) {
|
||||
// extended elegibility trace
|
||||
var xtrace = this.trace.extended[id];
|
||||
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
|
||||
for (let 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;
|
||||
.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];
|
||||
const 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[
|
||||
.weight * xtrace[input.ID] + this.derivative * this.trace.elegibility[
|
||||
input.ID] * influence;
|
||||
}
|
||||
}
|
||||
|
||||
// update gated connection's gains
|
||||
for (var connection in this.connections.gated) {
|
||||
for (let connection in this.connections.gated) {
|
||||
this.connections.gated[connection].gain = this.activation;
|
||||
}
|
||||
|
||||
return this.activation;
|
||||
},
|
||||
}
|
||||
|
||||
// back-propagate the error
|
||||
propagate: function(rate, target) {
|
||||
propagate(rate, target) {
|
||||
// error accumulator
|
||||
var error = 0;
|
||||
let error = 0;
|
||||
|
||||
// whether or not this neuron is in the output layer
|
||||
var isOutput = typeof target != 'undefined';
|
||||
const 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];
|
||||
const connection = this.connections.projected[id];
|
||||
var neuron = connection.to;
|
||||
// Eq. 21
|
||||
error += neuron.error.responsibility * connection.gain * connection.weight;
|
||||
@@ -135,12 +133,12 @@ Neuron.prototype = {
|
||||
// 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
|
||||
let 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;
|
||||
neuron.ID][input].from.activation;
|
||||
}
|
||||
// eq. 22
|
||||
error += neuron.error.responsibility * influence;
|
||||
@@ -161,20 +159,20 @@ Neuron.prototype = {
|
||||
var input = this.connections.inputs[id];
|
||||
|
||||
// Eq. 24
|
||||
var gradient = this.error.projected * this.trace.elegibility[input.ID];
|
||||
let 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];
|
||||
neuron.ID][input.ID];
|
||||
}
|
||||
input.weight += rate * gradient; // adjust weights - aka learn
|
||||
}
|
||||
|
||||
// adjust bias
|
||||
this.bias += rate * this.error.responsibility;
|
||||
},
|
||||
}
|
||||
|
||||
project: function(neuron, weight) {
|
||||
project(neuron, weight) {
|
||||
// self-connection
|
||||
if (neuron == this) {
|
||||
this.selfconnection.weight = 1;
|
||||
@@ -182,7 +180,7 @@ Neuron.prototype = {
|
||||
}
|
||||
|
||||
// check if connection already exists
|
||||
var connected = this.connected(neuron);
|
||||
const connected = this.connected(neuron);
|
||||
if (connected && connected.type == "projected") {
|
||||
// update connection
|
||||
if (typeof weight != 'undefined')
|
||||
@@ -200,25 +198,25 @@ Neuron.prototype = {
|
||||
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];
|
||||
for (let id in neuron.trace.extended) {
|
||||
const trace = neuron.trace.extended[id];
|
||||
trace[connection.ID] = 0;
|
||||
}
|
||||
|
||||
return connection;
|
||||
},
|
||||
}
|
||||
|
||||
gate: function(connection) {
|
||||
gate(connection) {
|
||||
// add connection to gated list
|
||||
this.connections.gated[connection.ID] = connection;
|
||||
|
||||
var neuron = connection.to;
|
||||
const 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];
|
||||
const xtrace = this.trace.extended[neuron.ID] = {};
|
||||
for (let id in this.connections.inputs) {
|
||||
const input = this.connections.inputs[id];
|
||||
xtrace[input.ID] = 0;
|
||||
}
|
||||
}
|
||||
@@ -231,16 +229,16 @@ Neuron.prototype = {
|
||||
|
||||
// set gater
|
||||
connection.gater = this;
|
||||
},
|
||||
}
|
||||
|
||||
// returns true or false whether the neuron is self-connected or not
|
||||
selfconnected: function() {
|
||||
selfconnected() {
|
||||
return this.selfconnection.weight !== 0;
|
||||
},
|
||||
}
|
||||
|
||||
// returns true or false whether the neuron is connected to another neuron (parameter)
|
||||
connected: function(neuron) {
|
||||
var result = {
|
||||
connected(neuron) {
|
||||
const result = {
|
||||
type: null,
|
||||
connection: false
|
||||
};
|
||||
@@ -254,7 +252,7 @@ Neuron.prototype = {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var type in this.connections) {
|
||||
for (let type in this.connections) {
|
||||
for (var connection in this.connections[type]) {
|
||||
var connection = this.connections[type][connection];
|
||||
if (connection.to == neuron) {
|
||||
@@ -270,73 +268,69 @@ Neuron.prototype = {
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}
|
||||
|
||||
// clears all the traces (the neuron forgets it's context, but the connections remain intact)
|
||||
clear: function() {
|
||||
clear() {
|
||||
|
||||
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])
|
||||
for (let 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() {
|
||||
reset() {
|
||||
this.clear();
|
||||
|
||||
for (var type in this.connections)
|
||||
for (var connection in this.connections[type])
|
||||
for (let type in this.connections)
|
||||
for (let 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 that = this;
|
||||
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 };
|
||||
optimize(optimized = {}, layer) {
|
||||
const store_activation = [];
|
||||
const store_trace = [];
|
||||
const store_propagation = [];
|
||||
let varID = optimized.memory || 0;
|
||||
const neurons = optimized.neurons || 1;
|
||||
const inputs = optimized.inputs || [];
|
||||
const targets = optimized.targets || [];
|
||||
const outputs = optimized.outputs || [];
|
||||
const variables = optimized.variables || {};
|
||||
const activation_sentences = optimized.activation_sentences || [];
|
||||
const trace_sentences = optimized.trace_sentences || [];
|
||||
const propagation_sentences = optimized.propagation_sentences || [];
|
||||
const layers = optimized.layers || {__count: 0, __neuron: 0};
|
||||
|
||||
// allocate sentences
|
||||
var allocate = function(store){
|
||||
var allocated = layer in layers && store[layers.__count];
|
||||
if (!allocated)
|
||||
{
|
||||
const allocate = store => {
|
||||
const 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;
|
||||
const 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);
|
||||
const getVar = function () {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
|
||||
if (args.length == 1) {
|
||||
if (args[0] == 'target') {
|
||||
var id = 'target_' + targets.length;
|
||||
var id = `target_${targets.length}`;
|
||||
targets.push(varID);
|
||||
} else
|
||||
var id = args[0];
|
||||
@@ -347,47 +341,47 @@ Neuron.prototype = {
|
||||
id: varID++
|
||||
};
|
||||
} else {
|
||||
var extended = args.length > 2;
|
||||
const extended = args.length > 2;
|
||||
if (extended)
|
||||
var value = args.pop();
|
||||
|
||||
var unit = args.shift();
|
||||
var prop = args.pop();
|
||||
const unit = args.shift();
|
||||
const prop = args.pop();
|
||||
|
||||
if (!extended)
|
||||
var value = unit[prop];
|
||||
|
||||
var id = prop + '_';
|
||||
for (var property in args)
|
||||
id += args[property] + '_';
|
||||
var id = `${prop}_`;
|
||||
for (let property in args)
|
||||
id += `${args[property]}_`;
|
||||
id += unit.ID;
|
||||
if (id in variables)
|
||||
return variables[id];
|
||||
|
||||
return variables[id] = {
|
||||
value: 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)
|
||||
const buildSentence = function () {
|
||||
const args = Array.prototype.slice.call(arguments);
|
||||
const store = args.pop();
|
||||
let sentence = "";
|
||||
for (let i in args)
|
||||
if (typeof args[i] == 'string')
|
||||
sentence += args[i];
|
||||
else
|
||||
sentence += 'F[' + args[i].id + ']';
|
||||
sentence += `F[${args[i].id}]`;
|
||||
|
||||
store.push(sentence + ';');
|
||||
}
|
||||
store.push(`${sentence};`);
|
||||
};
|
||||
|
||||
// helper to check if an object is empty
|
||||
var isEmpty = function(obj) {
|
||||
for (var prop in obj) {
|
||||
const isEmpty = obj => {
|
||||
for (let prop in obj) {
|
||||
if (obj.hasOwnProperty(prop))
|
||||
return false;
|
||||
}
|
||||
@@ -395,22 +389,22 @@ Neuron.prototype = {
|
||||
};
|
||||
|
||||
// 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;
|
||||
const noProjections = isEmpty(this.connections.projected);
|
||||
const noGates = isEmpty(this.connections.gated);
|
||||
const isInput = layer == 'input' ? true : isEmpty(this.connections.inputs);
|
||||
const isOutput = layer == 'output' ? true : noProjections && noGates;
|
||||
|
||||
// optimize neuron's behaviour
|
||||
var rate = getVar('rate');
|
||||
var activation = getVar(this, 'activation');
|
||||
const rate = getVar('rate');
|
||||
const 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');
|
||||
const old = getVar(this, 'old');
|
||||
const state = getVar(this, 'state');
|
||||
var bias = getVar(this, 'bias');
|
||||
if (this.selfconnection.gater)
|
||||
var self_gain = getVar(this.selfconnection, 'gain');
|
||||
@@ -448,8 +442,8 @@ Neuron.prototype = {
|
||||
activation, ')', store_activation);
|
||||
break;
|
||||
case Neuron.squash.TANH:
|
||||
var eP = getVar('aux');
|
||||
var eN = getVar('aux_2');
|
||||
const eP = getVar('aux');
|
||||
const 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);
|
||||
@@ -460,29 +454,29 @@ Neuron.prototype = {
|
||||
buildSentence(derivative, ' = 1', store_activation);
|
||||
break;
|
||||
case Neuron.squash.HLIM:
|
||||
buildSentence(activation, ' = +(', state, ' > 0)',
|
||||
store_activation);
|
||||
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 xtrace = this.trace.extended[id];
|
||||
|
||||
var neuron = this.neighboors[id];
|
||||
var influence = getVar('influences[' + neuron.ID + ']');
|
||||
var influence = getVar(`influences[${neuron.ID}]`);
|
||||
var neuron_old = getVar(neuron, 'old');
|
||||
var initialized = false;
|
||||
if (neuron.selfconnection.gater == this)
|
||||
{
|
||||
let 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]
|
||||
for (let incoming in this.trace.influences[neuron.ID]) {
|
||||
const incoming_weight = getVar(this.trace.influences[neuron.ID]
|
||||
[incoming], 'weight');
|
||||
var incoming_activation = getVar(this.trace.influences[neuron.ID]
|
||||
const incoming_activation = getVar(this.trace.influences[neuron.ID]
|
||||
[incoming].from, 'activation');
|
||||
|
||||
if (initialized)
|
||||
@@ -493,7 +487,7 @@ Neuron.prototype = {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (var i in this.connections.inputs) {
|
||||
var input = this.connections.inputs[i];
|
||||
if (input.gater)
|
||||
@@ -527,10 +521,8 @@ Neuron.prototype = {
|
||||
}
|
||||
for (var id in this.trace.extended) {
|
||||
// extended elegibility trace
|
||||
var xtrace = this.trace.extended[id];
|
||||
var neuron = this.neighboors[id];
|
||||
var influence = getVar('influences[' + neuron.ID + ']');
|
||||
var neuron_old = getVar(neuron, 'old');
|
||||
var influence = getVar(`influences[${neuron.ID}]`);
|
||||
|
||||
var trace = getVar(this, 'trace', 'elegibility', input.ID, this.trace
|
||||
.elegibility[input.ID]);
|
||||
@@ -555,15 +547,15 @@ Neuron.prototype = {
|
||||
}
|
||||
}
|
||||
for (var connection in this.connections.gated) {
|
||||
var gated_gain = getVar(this.connections.gated[connection], 'gain');
|
||||
const gated_gain = getVar(this.connections.gated[connection], 'gain');
|
||||
buildSentence(gated_gain, ' = ', activation, store_activation);
|
||||
}
|
||||
}
|
||||
if (!isInput) {
|
||||
var responsibility = getVar(this, 'error', 'responsibility', this.error
|
||||
const responsibility = getVar(this, 'error', 'responsibility', this.error
|
||||
.responsibility);
|
||||
if (isOutput) {
|
||||
var target = getVar('target');
|
||||
const target = getVar('target');
|
||||
buildSentence(responsibility, ' = ', target, ' - ', activation,
|
||||
store_propagation);
|
||||
for (var id in this.connections.inputs) {
|
||||
@@ -577,7 +569,7 @@ Neuron.prototype = {
|
||||
outputs.push(activation.id);
|
||||
} else {
|
||||
if (!noProjections && !noGates) {
|
||||
var error = getVar('aux');
|
||||
const error = getVar('aux');
|
||||
for (var id in this.connections.projected) {
|
||||
var connection = this.connections.projected[id];
|
||||
var neuron = connection.to;
|
||||
@@ -593,7 +585,7 @@ Neuron.prototype = {
|
||||
buildSentence(error, ' += ', neuron_responsibility, ' * ',
|
||||
connection_weight, store_propagation);
|
||||
}
|
||||
var projected = getVar(this, 'error', 'projected', this.error.projected);
|
||||
const projected = getVar(this, 'error', 'projected', this.error.projected);
|
||||
buildSentence(projected, ' = ', derivative, ' * ', error,
|
||||
store_propagation);
|
||||
buildSentence(error, ' = 0', store_propagation);
|
||||
@@ -617,7 +609,7 @@ Neuron.prototype = {
|
||||
buildSentence(error, ' += ', neuron_responsibility, ' * ',
|
||||
influence, store_propagation);
|
||||
}
|
||||
var gated = getVar(this, 'error', 'gated', this.error.gated);
|
||||
const gated = getVar(this, 'error', 'gated', this.error.gated);
|
||||
buildSentence(gated, ' = ', derivative, ' * ', error,
|
||||
store_propagation);
|
||||
buildSentence(responsibility, ' = ', projected, ' + ', gated,
|
||||
@@ -719,77 +711,75 @@ Neuron.prototype = {
|
||||
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
|
||||
inputs,
|
||||
outputs,
|
||||
targets,
|
||||
variables,
|
||||
activation_sentences,
|
||||
trace_sentences,
|
||||
propagation_sentences,
|
||||
layers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// represents a connection between two neurons
|
||||
Neuron.connection = function Connection(from, to, weight) {
|
||||
Neuron.connection = class NeuronConnection {
|
||||
constructor(from, to, weight) {
|
||||
|
||||
if (!from || !to)
|
||||
throw "Connection Error: Invalid neurons";
|
||||
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;
|
||||
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 = {};
|
||||
|
||||
function registerSquash(name, fn) {
|
||||
Neuron.squash[name] = fn;
|
||||
fn.squashType = [name];
|
||||
}
|
||||
|
||||
// eq. 5 & 5'
|
||||
Neuron.squash.LOGISTIC = function(x, derivate) {
|
||||
registerSquash('LOGISTIC', (x, derivate) => {
|
||||
if (!derivate)
|
||||
return 1 / (1 + Math.exp(-x));
|
||||
var fx = Neuron.squash.LOGISTIC(x);
|
||||
const fx = Neuron.squash.LOGISTIC(x);
|
||||
return fx * (1 - fx);
|
||||
};
|
||||
Neuron.squash.TANH = function(x, derivate) {
|
||||
});
|
||||
registerSquash('TANH', (x, derivate) => {
|
||||
if (derivate)
|
||||
return 1 - Math.pow(Neuron.squash.TANH(x), 2);
|
||||
var eP = Math.exp(x);
|
||||
var eN = 1 / eP;
|
||||
const eP = Math.exp(x);
|
||||
const 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);
|
||||
};
|
||||
});
|
||||
registerSquash('IDENTITY', (x, derivate) => derivate ? 1 : x);
|
||||
registerSquash('HLIM', (x, derivate) => derivate ? 1 : x > 0 ? 1 : 0);
|
||||
registerSquash('RELU', (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
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// export
|
||||
if (module) module.exports = Neuron;
|
||||
((() => {
|
||||
let neurons = 0;
|
||||
let connections = 0;
|
||||
Neuron.uid = () => neurons++
|
||||
Neuron.connection.uid = () => connections++
|
||||
Neuron.quantity = () => ({
|
||||
neurons,
|
||||
connections
|
||||
})
|
||||
}))();
|
||||
|
||||
module.exports = Neuron;
|
||||
|
||||
+11
-77
@@ -1,84 +1,18 @@
|
||||
/*
|
||||
var Synaptic = {};
|
||||
|
||||
The MIT License (MIT)
|
||||
Synaptic.Neuron = require('./neuron');
|
||||
Synaptic.Layer = require('./layer');
|
||||
Synaptic.Network = require('./network/index');
|
||||
Synaptic.Trainer = require('./trainer');
|
||||
Synaptic.Architect = require('./architect');
|
||||
|
||||
Copyright (c) 2014 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
|
||||
in the Software without restriction, including without limitation the rights
|
||||
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 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
|
||||
|
||||
|
||||
|
||||
********************************************************************************************
|
||||
SYNAPTIC
|
||||
********************************************************************************************
|
||||
|
||||
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.
|
||||
|
||||
|
||||
********************************************************************************************/
|
||||
|
||||
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;
|
||||
}
|
||||
module.exports = Synaptic;
|
||||
|
||||
// Browser
|
||||
if (typeof window == 'object')
|
||||
{
|
||||
(function(){
|
||||
var oldSynaptic = window['synaptic'];
|
||||
Synaptic.ninja = function(){
|
||||
window['synaptic'] = oldSynaptic;
|
||||
return Synaptic;
|
||||
};
|
||||
})();
|
||||
if (typeof window == 'object') {
|
||||
//noinspection CommaExpressionJS
|
||||
Synaptic.ninja = ((oldSynaptic = window['synaptic']) =>
|
||||
() => (window['synaptic'] = oldSynaptic, Synaptic))();
|
||||
|
||||
window['synaptic'] = Synaptic;
|
||||
}
|
||||
|
||||
+311
-353
@@ -2,37 +2,29 @@
|
||||
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;
|
||||
}
|
||||
|
||||
Trainer.prototype = {
|
||||
class Trainer {
|
||||
constructor(network, 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;
|
||||
}
|
||||
|
||||
// trains any given set to a network
|
||||
train: function(set, options) {
|
||||
train(set, options) {
|
||||
|
||||
var error = 1;
|
||||
var iterations = bucketSize = 0;
|
||||
var abort = false;
|
||||
var input, output, target, currentRate;
|
||||
var cost = options && options.cost || this.cost || Trainer.cost.MSE;
|
||||
let error = 1;
|
||||
let iterations = bucketSize = 0;
|
||||
let abort = false;
|
||||
let currentRate;
|
||||
const cost = options && options.cost || this.cost || Trainer.cost.MSE;
|
||||
let crossValidate = false, testSet, trainSet;
|
||||
|
||||
var start = Date.now();
|
||||
const 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)
|
||||
@@ -48,44 +40,63 @@ Trainer.prototype = {
|
||||
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)) {
|
||||
bucketSize = Math.floor(this.iterations / this.rate.length);
|
||||
var bucketSize = Math.floor(this.iterations / this.rate.length);
|
||||
}
|
||||
|
||||
if(crossValidate) {
|
||||
const numTrain = Math.ceil((1 - this.crossValidate.testSize) * set.length);
|
||||
trainSet = set.slice(0, numTrain);
|
||||
testSet = set.slice(numTrain);
|
||||
}
|
||||
|
||||
while (!abort && iterations < this.iterations && error > this.error) {
|
||||
error = 0;
|
||||
|
||||
if(bucketSize > 0) {
|
||||
var currentBucket = Math.floor(iterations / bucketSize);
|
||||
currentRate = this.rate[currentBucket] || currentRate;
|
||||
var lastError = 0;
|
||||
while ((!abort && iterations < this.iterations && error > this.error)) {
|
||||
if (crossValidate && error <= this.crossValidate.testError) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (var train in set) {
|
||||
input = set[train].input;
|
||||
target = set[train].output;
|
||||
let currentSetSize = set.length;
|
||||
error = 0;
|
||||
iterations++;
|
||||
|
||||
output = this.network.activate(input);
|
||||
this.network.propagate(currentRate, target);
|
||||
if(bucketSize > 0) {
|
||||
const currentBucket = Math.floor(iterations / bucketSize);
|
||||
currentRate = this.rate[currentBucket] || currentRate;
|
||||
}
|
||||
|
||||
if(typeof this.rate === 'function') {
|
||||
currentRate = this.rate(iterations, lastError);
|
||||
}
|
||||
|
||||
error += cost(target, output);
|
||||
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
|
||||
iterations++;
|
||||
error /= set.length;
|
||||
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
|
||||
});
|
||||
abort = this.schedule.do({ error, iterations, rate: currentRate });
|
||||
else if (options.log && iterations % options.log == 0) {
|
||||
console.log('iterations', iterations, 'error', error, 'rate', currentRate);
|
||||
};
|
||||
@@ -94,26 +105,52 @@ Trainer.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
var results = {
|
||||
error: error,
|
||||
iterations: iterations,
|
||||
const results = {
|
||||
error,
|
||||
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(set, options) {
|
||||
const train = this.workerTrain.bind(this);
|
||||
return new Promise((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(set, currentRate, costFunction) {
|
||||
let errorSum = 0;
|
||||
for (let train in set) {
|
||||
const input = set[train].input;
|
||||
const target = set[train].output;
|
||||
|
||||
const 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){
|
||||
test(set, options) {
|
||||
|
||||
var error = 0;
|
||||
var abort = false;
|
||||
var input, output, target;
|
||||
var cost = options && options.cost || this.cost || Trainer.cost.MSE;
|
||||
let error = 0;
|
||||
let input, output, target;
|
||||
const cost = options && options.cost || this.cost || Trainer.cost.MSE;
|
||||
|
||||
var start = Date.now();
|
||||
const start = Date.now();
|
||||
|
||||
for (var test in set) {
|
||||
for (let test in set) {
|
||||
input = set[test].input;
|
||||
target = set[test].output;
|
||||
output = this.network.activate(input);
|
||||
@@ -122,162 +159,80 @@ Trainer.prototype = {
|
||||
|
||||
error /= set.length;
|
||||
|
||||
var results = {
|
||||
error: error,
|
||||
const results = {
|
||||
error,
|
||||
time: Date.now() - start
|
||||
}
|
||||
};
|
||||
|
||||
return results;
|
||||
},
|
||||
}
|
||||
|
||||
// trains any given set to a network using a WebWorker
|
||||
workerTrain: function(set, callback, options) {
|
||||
// trains any given set to a network using a WebWorker [deprecated: use trainAsync instead]
|
||||
workerTrain(set, callback, options, suppressWarning) {
|
||||
|
||||
var that = this;
|
||||
var error = 1;
|
||||
var iterations = bucketSize = 0;
|
||||
var input, output, target, currentRate;
|
||||
var length = set.length;
|
||||
var abort = false;
|
||||
var cost = options && options.cost || that.cost || Trainer.cost.MSE;
|
||||
|
||||
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)
|
||||
that.iterations = options.iterations;
|
||||
if (options.error)
|
||||
that.error = options.error;
|
||||
if (options.rate)
|
||||
that.rate = options.rate;
|
||||
if (options.cost)
|
||||
that.cost = options.cost;
|
||||
if (options.schedule)
|
||||
that.schedule = options.schedule;
|
||||
if (options.customLog)
|
||||
{
|
||||
// for backward compatibility with code that used customLog
|
||||
console.log('Deprecated: use schedule instead of customLog')
|
||||
that.schedule = options.customLog;
|
||||
}
|
||||
if (!suppressWarning) {
|
||||
console.warn('Deprecated: do not use `workerTrain`, use `trainAsync` instead.')
|
||||
}
|
||||
const that = this;
|
||||
|
||||
// dynamic learning rate
|
||||
currentRate = that.rate;
|
||||
if(Array.isArray(that.rate)) {
|
||||
bucketSize = Math.floor(that.iterations / that.rate.length);
|
||||
}
|
||||
if (!this.network.optimized)
|
||||
this.network.optimize();
|
||||
|
||||
// create a worker
|
||||
var worker = that.network.worker();
|
||||
|
||||
// activate the network
|
||||
function activateWorker(input)
|
||||
{
|
||||
worker.postMessage({
|
||||
action: "activate",
|
||||
input: input,
|
||||
memoryBuffer: that.network.optimized.memory
|
||||
}, [that.network.optimized.memory.buffer]);
|
||||
}
|
||||
|
||||
// backpropagate the network
|
||||
function propagateWorker(target){
|
||||
if(bucketSize > 0) {
|
||||
var currentBucket = Math.floor(iterations / bucketSize);
|
||||
currentRate = that.rate[currentBucket] || currentRate;
|
||||
}
|
||||
worker.postMessage({
|
||||
action: "propagate",
|
||||
target: target,
|
||||
rate: currentRate,
|
||||
memoryBuffer: that.network.optimized.memory
|
||||
}, [that.network.optimized.memory.buffer]);
|
||||
}
|
||||
// Create a new worker
|
||||
const worker = this.network.worker(this.network.optimized.memory, set, options);
|
||||
|
||||
// train the worker
|
||||
worker.onmessage = function(e){
|
||||
// give control of the memory back to the network
|
||||
that.network.optimized.ownership(e.data.memoryBuffer);
|
||||
worker.onmessage = e => {
|
||||
switch(e.data.action) {
|
||||
case 'done':
|
||||
const iterations = e.data.message.iterations;
|
||||
const error = e.data.message.error;
|
||||
const time = e.data.message.time;
|
||||
|
||||
if (e.data.action == "propagate")
|
||||
{
|
||||
if (index >= length)
|
||||
{
|
||||
index = 0;
|
||||
iterations++;
|
||||
error /= set.length;
|
||||
that.network.optimized.ownership(e.data.memoryBuffer);
|
||||
|
||||
// log
|
||||
if (options) {
|
||||
if (that.schedule && that.schedule.every && iterations % that.schedule.every == 0)
|
||||
abort = that.schedule.do({
|
||||
error: error,
|
||||
iterations: iterations,
|
||||
rate: currentRate
|
||||
});
|
||||
else if (options.log && iterations % options.log == 0) {
|
||||
console.log('iterations', iterations, 'error', error);
|
||||
};
|
||||
if (options.shuffle)
|
||||
shuffle(set);
|
||||
}
|
||||
// Done callback
|
||||
callback({
|
||||
error,
|
||||
iterations,
|
||||
time
|
||||
});
|
||||
|
||||
if (!abort && iterations < that.iterations && error > that.error)
|
||||
{
|
||||
activateWorker(set[index].input);
|
||||
} else {
|
||||
// callback
|
||||
callback({
|
||||
error: error,
|
||||
iterations: iterations,
|
||||
time: Date.now() - start
|
||||
})
|
||||
}
|
||||
error = 0;
|
||||
} else {
|
||||
activateWorker(set[index].input);
|
||||
// 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') {
|
||||
const scheduled = options.schedule.do;
|
||||
scheduled(e.data.message)
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (e.data.action == "activate")
|
||||
{
|
||||
error += cost(set[index].output, e.data.output);
|
||||
propagateWorker(set[index].output);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// kick it
|
||||
var index = 0;
|
||||
var iterations = 0;
|
||||
activateWorker(set[index].input);
|
||||
},
|
||||
// Start the worker
|
||||
worker.postMessage({action: 'startTraining'});
|
||||
}
|
||||
|
||||
// trains an XOR to the network
|
||||
XOR: function(options) {
|
||||
XOR(options) {
|
||||
|
||||
if (this.network.inputs() != 2 || this.network.outputs() != 1)
|
||||
throw "Error: Incompatible network (2 inputs, 1 output)";
|
||||
throw new Error("Incompatible network (2 inputs, 1 output)");
|
||||
|
||||
var defaults = {
|
||||
const defaults = {
|
||||
iterations: 100000,
|
||||
log: false,
|
||||
shuffle: true,
|
||||
cost: Trainer.cost.MSE
|
||||
}
|
||||
};
|
||||
|
||||
if (options)
|
||||
for (var i in options)
|
||||
for (let i in options)
|
||||
defaults[i] = options[i];
|
||||
|
||||
return this.train([{
|
||||
@@ -293,55 +248,53 @@ Trainer.prototype = {
|
||||
input: [1, 1],
|
||||
output: [0]
|
||||
}], defaults);
|
||||
},
|
||||
}
|
||||
|
||||
// trains the network to pass a Distracted Sequence Recall test
|
||||
DSR: function(options) {
|
||||
options = options || {};
|
||||
DSR(options={}) {
|
||||
const targets = options.targets || [2, 4, 7, 8];
|
||||
const distractors = options.distractors || [3, 5, 6, 9];
|
||||
const prompts = options.prompts || [0, 1];
|
||||
const length = options.length || 24;
|
||||
const criterion = options.success || 0.95;
|
||||
const iterations = options.iterations || 100000;
|
||||
const rate = options.rate || .1;
|
||||
const log = options.log || 0;
|
||||
const schedule = options.schedule || {};
|
||||
const cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY;
|
||||
|
||||
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;
|
||||
let trial, correct, i, j, success;
|
||||
trial = correct = i = j = success = 0;
|
||||
let error = 1;
|
||||
const symbols = targets.length + distractors.length + prompts.length;
|
||||
|
||||
var trial = correct = i = j = success = 0,
|
||||
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)
|
||||
const noRepeat = (range, avoid) => {
|
||||
const number = Math.random() * range | 0;
|
||||
let used = false;
|
||||
for (let 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)
|
||||
const equal = (prediction, output) => {
|
||||
for (let i in prediction)
|
||||
if (Math.round(prediction[i]) != output[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
var start = Date.now();
|
||||
const start = Date.now();
|
||||
|
||||
while (trial < iterations && (success < criterion || trial % 1000 != 0)) {
|
||||
// generate sequence
|
||||
var sequence = [],
|
||||
sequenceLength = length - prompts.length;
|
||||
const sequence = [], sequenceLength = length - prompts.length;
|
||||
for (i = 0; i < sequenceLength; i++) {
|
||||
var any = Math.random() * distractors.length | 0;
|
||||
const any = Math.random() * distractors.length | 0;
|
||||
sequence.push(distractors[any]);
|
||||
}
|
||||
var indexes = [],
|
||||
positions = [];
|
||||
const indexes = [];
|
||||
let positions = [];
|
||||
for (i = 0; i < prompts.length; i++) {
|
||||
indexes.push(Math.random() * targets.length | 0);
|
||||
positions.push(noRepeat(sequenceLength, positions));
|
||||
@@ -353,27 +306,28 @@ Trainer.prototype = {
|
||||
}
|
||||
|
||||
//train sequence
|
||||
var targetsCorrect = distractorsCorrect = 0;
|
||||
let distractorsCorrect;
|
||||
let targetsCorrect = distractorsCorrect = 0;
|
||||
error = 0;
|
||||
for (i = 0; i < length; i++) {
|
||||
// generate input from sequence
|
||||
var input = [];
|
||||
const input = [];
|
||||
for (j = 0; j < symbols; j++)
|
||||
input[j] = 0;
|
||||
input[sequence[i]] = 1;
|
||||
|
||||
// generate target output
|
||||
var output = [];
|
||||
const output = [];
|
||||
for (j = 0; j < targets.length; j++)
|
||||
output[j] = 0;
|
||||
|
||||
if (i >= sequenceLength) {
|
||||
var index = i - sequenceLength;
|
||||
const index = i - sequenceLength;
|
||||
output[indexes[index]] = 1;
|
||||
}
|
||||
|
||||
// check result
|
||||
var prediction = this.network.activate(input);
|
||||
const prediction = this.network.activate(input);
|
||||
|
||||
if (equal(prediction, output))
|
||||
if (i < sequenceLength)
|
||||
@@ -394,7 +348,7 @@ Trainer.prototype = {
|
||||
if (trial % 1000 == 0)
|
||||
correct = 0;
|
||||
trial++;
|
||||
var divideError = trial % 1000;
|
||||
let divideError = trial % 1000;
|
||||
divideError = divideError == 0 ? 1000 : divideError;
|
||||
success = correct / divideError;
|
||||
error /= length;
|
||||
@@ -406,132 +360,133 @@ Trainer.prototype = {
|
||||
if (schedule.do && schedule.every && trial % schedule.every == 0)
|
||||
schedule.do({
|
||||
iterations: trial,
|
||||
success: success,
|
||||
error: error,
|
||||
success,
|
||||
error,
|
||||
time: Date.now() - start,
|
||||
correct: correct
|
||||
correct
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
iterations: trial,
|
||||
success: success,
|
||||
error: error,
|
||||
success,
|
||||
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;
|
||||
ERG(options={}) {
|
||||
const iterations = options.iterations || 150000;
|
||||
const criterion = options.error || .05;
|
||||
const rate = options.rate || .1;
|
||||
const log = options.log || 500;
|
||||
const cost = options.cost || this.cost || Trainer.cost.CROSS_ENTROPY;
|
||||
|
||||
// gramar node
|
||||
var Node = function() {
|
||||
this.paths = [];
|
||||
}
|
||||
Node.prototype = {
|
||||
connect: function(node, value) {
|
||||
class Node {
|
||||
constructor() {
|
||||
this.paths = [];
|
||||
}
|
||||
|
||||
connect(node, value) {
|
||||
this.paths.push({
|
||||
node: node,
|
||||
value: value
|
||||
node,
|
||||
value
|
||||
});
|
||||
return this;
|
||||
},
|
||||
any: function() {
|
||||
}
|
||||
|
||||
any() {
|
||||
if (this.paths.length == 0)
|
||||
return false;
|
||||
var index = Math.random() * this.paths.length | 0;
|
||||
const index = Math.random() * this.paths.length | 0;
|
||||
return this.paths[index];
|
||||
},
|
||||
test: function(value) {
|
||||
for (var i in this.paths)
|
||||
}
|
||||
|
||||
test(value) {
|
||||
for (let i in this.paths)
|
||||
if (this.paths[i].value == value)
|
||||
return this.paths[i];
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var reberGrammar = function() {
|
||||
const reberGrammar = () => {
|
||||
|
||||
// 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")
|
||||
const output = new Node();
|
||||
const n1 = (new Node()).connect(output, "E");
|
||||
const n2 = (new Node()).connect(n1, "S");
|
||||
const n3 = (new Node()).connect(n1, "V").connect(n2, "P");
|
||||
const n4 = (new Node()).connect(n2, "X");
|
||||
n4.connect(n4, "S");
|
||||
var n5 = (new Node()).connect(n3, "V")
|
||||
const 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")
|
||||
n2.connect(n5, "X");
|
||||
const n6 = (new Node()).connect(n4, "T").connect(n5, "P");
|
||||
const input = (new Node()).connect(n6, "B");
|
||||
|
||||
return {
|
||||
input: input,
|
||||
output: output
|
||||
input,
|
||||
output
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// build an embeded reber grammar
|
||||
var embededReberGrammar = function() {
|
||||
var reber1 = reberGrammar();
|
||||
var reber2 = reberGrammar();
|
||||
const embededReberGrammar = () => {
|
||||
const reber1 = reberGrammar();
|
||||
const reber2 = reberGrammar();
|
||||
|
||||
var output = new Node();
|
||||
var n1 = (new Node).connect(output, "E");
|
||||
const output = new Node();
|
||||
const 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,
|
||||
const n2 = (new Node).connect(reber1.input, "P").connect(reber2.input,
|
||||
"T");
|
||||
var input = (new Node).connect(n2, "B");
|
||||
const input = (new Node).connect(n2, "B");
|
||||
|
||||
return {
|
||||
input: input,
|
||||
output: output
|
||||
input,
|
||||
output
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// generate an ERG sequence
|
||||
var generate = function() {
|
||||
var node = embededReberGrammar().input;
|
||||
var next = node.any();
|
||||
var str = "";
|
||||
const generate = () => {
|
||||
const node = embededReberGrammar().input;
|
||||
let next = node.any();
|
||||
let 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);
|
||||
const test = str => {
|
||||
let node = embededReberGrammar().input;
|
||||
let i = 0;
|
||||
let ch = str.charAt(i);
|
||||
while (i < str.length) {
|
||||
var next = node.test(ch);
|
||||
const 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) {
|
||||
const different = (array1, array2) => {
|
||||
let max1 = 0;
|
||||
let i1 = -1;
|
||||
let max2 = 0;
|
||||
let i2 = -1;
|
||||
for (let i in array1) {
|
||||
if (array1[i] > max1) {
|
||||
max1 = array1[i];
|
||||
i1 = i;
|
||||
@@ -543,44 +498,44 @@ Trainer.prototype = {
|
||||
}
|
||||
|
||||
return i1 != i2;
|
||||
}
|
||||
};
|
||||
|
||||
var iteration = 0;
|
||||
var error = 1;
|
||||
var table = {
|
||||
let iteration = 0;
|
||||
let error = 1;
|
||||
const table = {
|
||||
"B": 0,
|
||||
"P": 1,
|
||||
"T": 2,
|
||||
"X": 3,
|
||||
"S": 4,
|
||||
"E": 5
|
||||
}
|
||||
};
|
||||
|
||||
var start = Date.now();
|
||||
const start = Date.now();
|
||||
while (iteration < iterations && error > criterion) {
|
||||
var i = 0;
|
||||
let i = 0;
|
||||
error = 0;
|
||||
|
||||
// ERG sequence to learn
|
||||
var sequence = generate();
|
||||
const sequence = generate();
|
||||
|
||||
// input
|
||||
var read = sequence.charAt(i);
|
||||
let read = sequence.charAt(i);
|
||||
// target
|
||||
var predict = sequence.charAt(i + 1);
|
||||
let predict = sequence.charAt(i + 1);
|
||||
|
||||
// train
|
||||
while (i < sequence.length - 1) {
|
||||
var input = [];
|
||||
var target = [];
|
||||
for (var j = 0; j < 6; j++) {
|
||||
const input = [];
|
||||
const target = [];
|
||||
for (let 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);
|
||||
const output = this.network.activate(input);
|
||||
|
||||
if (different(output, target))
|
||||
this.network.propagate(rate, target);
|
||||
@@ -600,77 +555,77 @@ Trainer.prototype = {
|
||||
|
||||
return {
|
||||
iterations: iteration,
|
||||
error: error,
|
||||
error,
|
||||
time: Date.now() - start,
|
||||
test: test,
|
||||
generate: generate
|
||||
test,
|
||||
generate
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
timingTask: function(options){
|
||||
timingTask(options) {
|
||||
|
||||
if (this.network.inputs() != 2 || this.network.outputs() != 1)
|
||||
throw "Invalid Network: must have 2 inputs and one output";
|
||||
throw new Error("Invalid Network: must have 2 inputs and one output");
|
||||
|
||||
if (typeof options == 'undefined')
|
||||
var options = {};
|
||||
options = {};
|
||||
|
||||
// helper
|
||||
function getSamples (trainingSize, testSize){
|
||||
|
||||
|
||||
// sample size
|
||||
var size = trainingSize + testSize;
|
||||
|
||||
const size = trainingSize + testSize;
|
||||
|
||||
// generate samples
|
||||
var t = 0;
|
||||
var set = [];
|
||||
for (var i = 0; i < size; i++) {
|
||||
let t = 0;
|
||||
const set = [];
|
||||
for (let i = 0; i < size; i++) {
|
||||
set.push({ input: [0,0], output: [0] });
|
||||
}
|
||||
while(t < size - 20) {
|
||||
var n = Math.round(Math.random() * 20);
|
||||
let n = Math.round(Math.random() * 20);
|
||||
set[t].input[0] = 1;
|
||||
for (var j = t; j <= t + n; j++){
|
||||
for (let 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++)
|
||||
for (let 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++)
|
||||
const trainingSet = []; const testSet = [];
|
||||
for (let l = 0; l < size; l++)
|
||||
(l < trainingSize ? trainingSet : testSet).push(set[l]);
|
||||
|
||||
// return samples
|
||||
return {
|
||||
train: trainingSet,
|
||||
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;
|
||||
const iterations = options.iterations || 200;
|
||||
const error = options.error || .005;
|
||||
const rate = options.rate || [.03, .02];
|
||||
const log = options.log === false ? false : options.log || 10;
|
||||
const cost = options.cost || this.cost || Trainer.cost.MSE;
|
||||
const trainingSamples = options.trainSamples || 7000;
|
||||
const testSamples = options.trainSamples || 1000;
|
||||
|
||||
// samples for training and testing
|
||||
var samples = getSamples(trainingSamples, testSamples);
|
||||
const samples = getSamples(trainingSamples, testSamples);
|
||||
|
||||
// train
|
||||
var result = this.train(samples.train, {
|
||||
rate: rate,
|
||||
log: log,
|
||||
iterations: iterations,
|
||||
error: error,
|
||||
cost: cost
|
||||
const result = this.train(samples.train, {
|
||||
rate,
|
||||
log,
|
||||
iterations,
|
||||
error,
|
||||
cost
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -678,33 +633,36 @@ Trainer.prototype = {
|
||||
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)
|
||||
CROSS_ENTROPY(target, output) {
|
||||
let crossentropy = 0;
|
||||
for (let 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(target, output) {
|
||||
let mse = 0;
|
||||
for (let 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)
|
||||
BINARY(target, output) {
|
||||
let misses = 0;
|
||||
for (let i in output)
|
||||
misses += Math.round(target[i] * 2) != Math.round(output[i] * 2);
|
||||
return misses;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// export
|
||||
if (module) module.exports = Trainer;
|
||||
//+ Jonas Raoni Soares Silva
|
||||
//@ http://jsfromhell.com/array/shuffle [v1.0]
|
||||
function shuffle(o) { //v1.0
|
||||
for (let j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
|
||||
return o;
|
||||
};
|
||||
|
||||
module.exports = Trainer;
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
Test using gulp, from root directory:
|
||||
Test using mocha, from root directory:
|
||||
|
||||
`gulp test`
|
||||
`mocha test`
|
||||
|
||||
To test the web version, start a web server at the root dir of this repo, then use your OS browser.
|
||||
|
||||
|
||||
+289
-148
@@ -1,58 +1,68 @@
|
||||
// import
|
||||
|
||||
var assert = require('assert'),
|
||||
synaptic = require('../src/synaptic');
|
||||
var synaptic = require('../src/synaptic.js');
|
||||
|
||||
var Perceptron = synaptic.Architect.Perceptron,
|
||||
LSTM = synaptic.Architect.LSTM,
|
||||
Layer = synaptic.Layer,
|
||||
Network = synaptic.Network,
|
||||
Trainer = synaptic.Trainer;
|
||||
|
||||
var chai = require('chai');
|
||||
chai.use(require('chai-stats/lib/stats'));
|
||||
chai.use(require('chai-as-promised'));
|
||||
var {assert, expect} = chai;
|
||||
|
||||
var {Layer, Network, Trainer} = synaptic;
|
||||
var {Perceptron, LSTM} = synaptic.Architect;
|
||||
|
||||
|
||||
|
||||
var learningRate = .5;
|
||||
|
||||
|
||||
// utils
|
||||
|
||||
function noRepeat (range, avoid) {
|
||||
function noRepeat(range, avoid) {
|
||||
var number = Math.random() * range | 0;
|
||||
for (var i in avoid){
|
||||
if (number == avoid[i]){
|
||||
return noRepeat(range,avoid);
|
||||
for (var i in avoid) {
|
||||
if (number == avoid[i]) {
|
||||
return noRepeat(range, avoid);
|
||||
}
|
||||
}
|
||||
return number;
|
||||
};
|
||||
}
|
||||
|
||||
function equal (prediction, output) {
|
||||
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 compare (a, b) {
|
||||
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 < 1e-10;
|
||||
return mse;
|
||||
}
|
||||
|
||||
function equalWithError(output, expected, error) {
|
||||
return Math.abs(output - expected) <= error;
|
||||
}
|
||||
|
||||
// specs
|
||||
|
||||
describe('Basic Neural Network', function() {
|
||||
describe('Basic Neural Network', function () {
|
||||
|
||||
it("trains an AND gate", function() {
|
||||
it("trains an AND gate", function () {
|
||||
|
||||
var inputLayer = new Layer(2),
|
||||
outputLayer = new Layer(1);
|
||||
outputLayer = new Layer(1);
|
||||
|
||||
inputLayer.project(outputLayer);
|
||||
|
||||
@@ -88,14 +98,14 @@ describe('Basic Neural Network', function() {
|
||||
var test01 = Math.round(network.activate([0, 1]));
|
||||
assert.equal(test01, 0, "[0,1] did not output 0");
|
||||
|
||||
var test10 = Math.round(network.activate([0, 1]));
|
||||
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() {
|
||||
|
||||
it("trains an OR gate", function () {
|
||||
|
||||
var inputLayer = new Layer(2),
|
||||
outputLayer = new Layer(1);
|
||||
@@ -134,18 +144,17 @@ describe('Basic Neural Network', function() {
|
||||
var test01 = Math.round(network.activate([0, 1]));
|
||||
assert.equal(test01, 1, "[0,1] did not output 1");
|
||||
|
||||
var test10 = Math.round(network.activate([0, 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() {
|
||||
it("trains a NOT gate", function () {
|
||||
|
||||
var inputLayer = new Layer(1),
|
||||
outputLayer = new Layer(1),
|
||||
network;
|
||||
outputLayer = new Layer(1);
|
||||
|
||||
inputLayer.project(outputLayer);
|
||||
|
||||
@@ -176,37 +185,121 @@ describe('Basic Neural Network', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Perceptron - XOR", function() {
|
||||
describe("Perceptron - XOR", function () {
|
||||
|
||||
var perceptron = new Perceptron(2, 3, 1);
|
||||
perceptron.trainer.XOR();
|
||||
|
||||
var test00 = Math.round(perceptron.activate([0, 0]));
|
||||
it("input: [0,0] output: " + test00, function() {
|
||||
|
||||
assert.equal(test00, 0, "[0,0] did not output 0");
|
||||
it("should return near-0 value on [0,0]", function () {
|
||||
assert.isAtMost(perceptron.activate([0, 0]), .49, "[0,0] did not output 0");
|
||||
});
|
||||
|
||||
var test01 = Math.round(perceptron.activate([0, 1]));
|
||||
it("input: [0,1] output: " + test01, function() {
|
||||
|
||||
assert.equal(test01, 1, "[0,1] did not output 1");
|
||||
it("should return near-1 value on [0,1]", function () {
|
||||
assert.isAtLeast(perceptron.activate([0, 1]), .51, "[0,1] did not output 1");
|
||||
});
|
||||
|
||||
var test10 = Math.round(perceptron.activate([1, 0]));
|
||||
it("input: [1,0] output: " + test10, function() {
|
||||
|
||||
assert.equal(test10, 1, "[1,0] 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");
|
||||
});
|
||||
|
||||
var test11 = Math.round(perceptron.activate([1, 1]));
|
||||
it("input: [1,1] output: " + test11, function() {
|
||||
|
||||
assert.equal(test11, 0, "[1,1] did not output 0");
|
||||
it("should return near-0 value on [1,1]", function () {
|
||||
assert.isAtMost(perceptron.activate([1, 1]), .49, "[1,1] did not output 0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("LSTM - Discrete Sequence Recall", function() {
|
||||
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];
|
||||
@@ -244,7 +337,7 @@ describe("LSTM - Discrete Sequence Recall", function() {
|
||||
sequence.push(prompts[i]);
|
||||
}
|
||||
|
||||
var check = function(which) {
|
||||
var check = function (which) {
|
||||
// generate input from sequence
|
||||
var input = [];
|
||||
for (j = 0; j < symbols; j++)
|
||||
@@ -269,7 +362,7 @@ describe("LSTM - Discrete Sequence Recall", function() {
|
||||
};
|
||||
};
|
||||
|
||||
var value = function(array) {
|
||||
var value = function (array) {
|
||||
var max = .5;
|
||||
var res = -1;
|
||||
for (var i in array)
|
||||
@@ -280,190 +373,238 @@ describe("LSTM - Discrete Sequence Recall", function() {
|
||||
return res == -1 ? '-' : targets[res];
|
||||
};
|
||||
|
||||
it("targets: " + targets, function() {
|
||||
it("targets: " + targets, function () {
|
||||
assert(true);
|
||||
});
|
||||
it("distractors: " + distractors, function() {
|
||||
it("distractors: " + distractors, function () {
|
||||
assert(true);
|
||||
});
|
||||
it("prompts: " + prompts, function() {
|
||||
it("prompts: " + prompts, function () {
|
||||
assert(true);
|
||||
});
|
||||
it("length: " + length + "\n", function() {
|
||||
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() {
|
||||
function () {
|
||||
var ok = equal(test.prediction, test.output);
|
||||
assert(ok);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("LSTM - Timing Task", function() {
|
||||
var network = new synaptic.Architect.LSTM(2,7,1);
|
||||
var result = network.trainer.timingTask({
|
||||
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() {
|
||||
|
||||
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() {
|
||||
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 = new LSTM(2,1,1)
|
||||
describe("Optimized and Unoptimized Networks Equivalency", function () {
|
||||
|
||||
var unoptimized = optimized.clone();
|
||||
unoptimized.setOptimize(false);
|
||||
var optimized;
|
||||
var unoptimized;
|
||||
beforeEach(function () {
|
||||
optimized = new LSTM(2, 1, 1);
|
||||
unoptimized = optimized.clone();
|
||||
unoptimized.setOptimize(false);
|
||||
});
|
||||
|
||||
var learningRate = .5;
|
||||
var iterations = 1000;
|
||||
|
||||
for (var i = 1; i <= iterations; i++)
|
||||
{
|
||||
//random input
|
||||
it('should produce the same output for both networks', function () {
|
||||
this.timeout(30000);
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var input = generateRandomArray(2);
|
||||
|
||||
// activate networks
|
||||
var output1 = optimized.activate(input);
|
||||
var output2 = unoptimized.activate(input);
|
||||
|
||||
if (i % 100 == 0)
|
||||
it('should produce the same output for both networks after ' + i + ' iterations', function(){
|
||||
assert(compare(output1, output2));
|
||||
});
|
||||
|
||||
// random target
|
||||
var target = generateRandomArray(1);
|
||||
|
||||
// propagate networks
|
||||
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 = new LSTM(10,5,5);
|
||||
describe("toJSON/fromJSON Networks Equivalency", function () {
|
||||
var original;
|
||||
var imported;
|
||||
beforeEach(function () {
|
||||
original = new LSTM(10, 5, 5);
|
||||
imported = Network.fromJSON(original.toJSON());
|
||||
});
|
||||
|
||||
var exported = original.toJSON();
|
||||
var imported = Network.fromJSON(exported);
|
||||
|
||||
var learningRate = .5;
|
||||
var iterations = 1000;
|
||||
|
||||
for (var i = 1; i <= iterations; i++)
|
||||
{
|
||||
//random input
|
||||
it('should produce the same output for both networks', function () {
|
||||
this.timeout(30000);
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var input = generateRandomArray(10);
|
||||
|
||||
// activate networks
|
||||
var output1 = original.activate(input);
|
||||
var output2 = imported.activate(input);
|
||||
|
||||
if (i % 100 == 0)
|
||||
it('should produce the same output for both networks after ' + i + ' iterations', function(){
|
||||
assert(compare(output1, output2));
|
||||
});
|
||||
|
||||
// random target
|
||||
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 = new LSTM(10,5,5);
|
||||
describe("Cloned Networks Equivalency", function () {
|
||||
var original;
|
||||
var cloned;
|
||||
beforeEach(function () {
|
||||
original = new LSTM(10, 5, 5);
|
||||
cloned = Network.fromJSON(original.toJSON());
|
||||
});
|
||||
|
||||
var cloned = original.clone();
|
||||
|
||||
var learningRate = .5;
|
||||
var iterations = 1000;
|
||||
|
||||
for (var i = 1; i <= iterations; i++)
|
||||
{
|
||||
//random input
|
||||
it('should produce the same output for both networks', function () {
|
||||
this.timeout(30000);
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var input = generateRandomArray(10);
|
||||
|
||||
// activate networks
|
||||
var output1 = original.activate(input);
|
||||
var output2 = cloned.activate(input);
|
||||
|
||||
if (i % 100 == 0)
|
||||
it('should produce the same output for both networks after ' + i + ' iterations', function(){
|
||||
assert(compare(output1, output2));
|
||||
});
|
||||
|
||||
// random target
|
||||
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() {
|
||||
describe("Scheduled Tasks", function () {
|
||||
var perceptron = new Perceptron(2, 3, 1);
|
||||
|
||||
it('should stop training at 3000 iterations', function(){
|
||||
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) {
|
||||
if( data.iterations == 20000){
|
||||
return true
|
||||
}
|
||||
}
|
||||
every: 1000,
|
||||
do: function (data) {
|
||||
return data.iterations == 20000;
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.equal( final_stats.iterations, 3000 )
|
||||
assert.equal(final_stats.iterations, 3000)
|
||||
});
|
||||
|
||||
it('should abort the training at 2000 iterations', function(){
|
||||
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) {
|
||||
if( data.iterations == 2000){
|
||||
return true
|
||||
}
|
||||
}
|
||||
every: 1000,
|
||||
do: function (data) {
|
||||
return data.iterations == 2000;
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.equal( final_stats.iterations, 2000 )
|
||||
assert.equal(final_stats.iterations, 2000)
|
||||
});
|
||||
|
||||
it('should work even if shedule.do() returns no value', function(){
|
||||
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) {}
|
||||
}
|
||||
every: 1000,
|
||||
do: function (data) {}
|
||||
}
|
||||
});
|
||||
assert.equal( final_stats.iterations, 3000 )
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
// browser-specific tests
|
||||
} else {
|
||||
// Node.js-specific tests
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
var synaptic = require('../src/synaptic.js');
|
||||
|
||||
var chai = require('chai');
|
||||
chai.use(require('chai-stats/lib/stats'));
|
||||
chai.use(require('chai-as-promised'));
|
||||
var {assert, expect} = chai;
|
||||
|
||||
var {Perceptron} = synaptic.Architect;
|
||||
|
||||
describe('Browser: Web Worker', function () {
|
||||
if (!global.Worker) {
|
||||
return
|
||||
}
|
||||
|
||||
var perceptron;
|
||||
var perceptronWorker;
|
||||
beforeEach(() => {
|
||||
perceptron = new Perceptron(2, 3, 1);
|
||||
perceptron.trainer.XOR();
|
||||
return perceptron.worker()
|
||||
.then(network => {
|
||||
perceptronWorker = network
|
||||
})
|
||||
});
|
||||
|
||||
it('Should at least work', () =>
|
||||
assert.isFulfilled(perceptronWorker.activate([1, 0])));
|
||||
|
||||
it("should return near-0 value on [0,0]", function () {
|
||||
expect(perceptronWorker.activate([0, 0])).to.eventually.be.at.most(.49, "[0,0] did not output 0");
|
||||
});
|
||||
|
||||
it("should return near-1 value on [0,1]", function () {
|
||||
expect(perceptronWorker.activate([0, 1])).to.eventually.be.at.most(.51, "[0,1] did not output 1");
|
||||
});
|
||||
|
||||
it("should return near-1 value on [1,0]", function () {
|
||||
expect(perceptronWorker.activate([1, 0])).to.eventually.be.at.most(.51, "[1,0] did not output 1");
|
||||
});
|
||||
|
||||
it("should return near-0 value on [1,1]", function () {
|
||||
expect(perceptronWorker.activate([1, 1])).to.eventually.be.at.most(.49, "[1,1] did not output 0");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
var webpack = require('webpack');
|
||||
var license = require('./prebuild.js');
|
||||
|
||||
module.exports = {
|
||||
context: __dirname,
|
||||
entry: [
|
||||
'./src/synaptic.js'
|
||||
],
|
||||
output: {
|
||||
path: 'dist',
|
||||
filename: 'synaptic.js',
|
||||
libraryTarget: 'umd',
|
||||
umdNamedDefine: 'synaptic',
|
||||
},
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /\/node_modules\//,
|
||||
loaders: ['babel'],
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
new webpack.BannerPlugin(license()),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.WEBPACK': 'true',
|
||||
}),
|
||||
|
||||
]
|
||||
};
|
||||
Referência em uma Nova Issue
Bloquear um usuário