commit 61c2f045523f5ef617f92861738c3bd0ea5f864a Author: wehub-resource-sync Date: Mon Jul 13 12:49:29 2026 +0800 chore: import upstream snapshot with attribution diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..736b986 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Andrej Karpathy + +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. + diff --git a/README.wehub.md b/README.wehub.md new file mode 100644 index 0000000..6ce10f1 --- /dev/null +++ b/README.wehub.md @@ -0,0 +1,7 @@ +# WeHub 来源说明 + +- 原始项目:`karpathy/convnetjs` +- 原始仓库:https://github.com/karpathy/convnetjs +- 导入方式:上游默认分支的最新快照 +- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准 +- 本文件仅用于记录来源,不代表 WeHub 是原项目作者 diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..8060045 --- /dev/null +++ b/Readme.md @@ -0,0 +1,118 @@ + +# ConvNetJS + +ConvNetJS is a Javascript implementation of Neural networks, together with nice browser-based demos. It currently supports: + +- Common **Neural Network modules** (fully connected layers, non-linearities) +- Classification (SVM/Softmax) and Regression (L2) **cost functions** +- Ability to specify and train **Convolutional Networks** that process images +- An experimental **Reinforcement Learning** module, based on Deep Q Learning + +For much more information, see the main page at [convnetjs.com](http://convnetjs.com) + +**Note**: I am not actively maintaining ConvNetJS anymore because I simply don't have time. I think the npm repo might not work at this point. + +## Online Demos +- [Convolutional Neural Network on MNIST digits](http://cs.stanford.edu/~karpathy/convnetjs/demo/mnist.html) +- [Convolutional Neural Network on CIFAR-10](http://cs.stanford.edu/~karpathy/convnetjs/demo/cifar10.html) +- [Toy 2D data](http://cs.stanford.edu/~karpathy/convnetjs/demo/classify2d.html) +- [Toy 1D regression](http://cs.stanford.edu/~karpathy/convnetjs/demo/regression.html) +- [Training an Autoencoder on MNIST digits](http://cs.stanford.edu/~karpathy/convnetjs/demo/autoencoder.html) +- [Deep Q Learning Reinforcement Learning demo](http://cs.stanford.edu/people/karpathy/convnetjs/demo/rldemo.html) +- [Image Regression ("Painting")](http://cs.stanford.edu/~karpathy/convnetjs/demo/image_regression.html) +- [Comparison of SGD/Adagrad/Adadelta on MNIST](http://cs.stanford.edu/people/karpathy/convnetjs/demo/trainers.html) + +## Example Code + +Here's a minimum example of defining a **2-layer neural network** and training +it on a single data point: + +```javascript +// species a 2-layer neural network with one hidden layer of 20 neurons +var layer_defs = []; +// input layer declares size of input. here: 2-D data +// ConvNetJS works on 3-Dimensional volumes (sx, sy, depth), but if you're not dealing with images +// then the first two dimensions (sx, sy) will always be kept at size 1 +layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth:2}); +// declare 20 neurons, followed by ReLU (rectified linear unit non-linearity) +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'}); +// declare the linear classifier on top of the previous hidden layer +layer_defs.push({type:'softmax', num_classes:10}); + +var net = new convnetjs.Net(); +net.makeLayers(layer_defs); + +// forward a random data point through the network +var x = new convnetjs.Vol([0.3, -0.5]); +var prob = net.forward(x); + +// prob is a Vol. Vols have a field .w that stores the raw data, and .dw that stores gradients +console.log('probability that x is class 0: ' + prob.w[0]); // prints 0.50101 + +var trainer = new convnetjs.SGDTrainer(net, {learning_rate:0.01, l2_decay:0.001}); +trainer.train(x, 0); // train the network, specifying that x is class zero + +var prob2 = net.forward(x); +console.log('probability that x is class 0: ' + prob2.w[0]); +// now prints 0.50374, slightly higher than previous 0.50101: the networks +// weights have been adjusted by the Trainer to give a higher probability to +// the class we trained the network with (zero) +``` + +and here is a small **Convolutional Neural Network** if you wish to predict on images: + +```javascript +var layer_defs = []; +layer_defs.push({type:'input', out_sx:32, out_sy:32, out_depth:3}); // declare size of input +// output Vol is of size 32x32x3 here +layer_defs.push({type:'conv', sx:5, filters:16, stride:1, pad:2, activation:'relu'}); +// the layer will perform convolution with 16 kernels, each of size 5x5. +// the input will be padded with 2 pixels on all sides to make the output Vol of the same size +// output Vol will thus be 32x32x16 at this point +layer_defs.push({type:'pool', sx:2, stride:2}); +// output Vol is of size 16x16x16 here +layer_defs.push({type:'conv', sx:5, filters:20, stride:1, pad:2, activation:'relu'}); +// output Vol is of size 16x16x20 here +layer_defs.push({type:'pool', sx:2, stride:2}); +// output Vol is of size 8x8x20 here +layer_defs.push({type:'conv', sx:5, filters:20, stride:1, pad:2, activation:'relu'}); +// output Vol is of size 8x8x20 here +layer_defs.push({type:'pool', sx:2, stride:2}); +// output Vol is of size 4x4x20 here +layer_defs.push({type:'softmax', num_classes:10}); +// output Vol is of size 1x1x10 here + +net = new convnetjs.Net(); +net.makeLayers(layer_defs); + +// helpful utility for converting images into Vols is included +var x = convnetjs.img_to_vol(document.getElementById('some_image')) +var output_probabilities_vol = net.forward(x) +``` + +## Getting Started +A [Getting Started](http://cs.stanford.edu/people/karpathy/convnetjs/started.html) tutorial is available on main page. + +The full [Documentation](http://cs.stanford.edu/people/karpathy/convnetjs/docs.html) can also be found there. + +See the **releases** page for this project to get the minified, compiled library, and a direct link to is also available below for convenience (but please host your own copy) + +- [convnet.js](http://cs.stanford.edu/people/karpathy/convnetjs/build/convnet.js) +- [convnet-min.js](http://cs.stanford.edu/people/karpathy/convnetjs/build/convnet-min.js) + +## Compiling the library from src/ to build/ +If you would like to add features to the library, you will have to change the code in `src/` and then compile the library into the `build/` directory. The compilation script simply concatenates files in `src/` and then minifies the result. + +The compilation is done using an ant task: it compiles `build/convnet.js` by concatenating the source files in `src/` and then minifies the result into `build/convnet-min.js`. Make sure you have **ant** installed (on Ubuntu you can simply *sudo apt-get install* it), then cd into `compile/` directory and run: + + $ ant -lib yuicompressor-2.4.8.jar -f build.xml + +The output files will be in `build/` +## Use in Node +The library is also available on *node.js*: + +1. Install it: `$ npm install convnetjs` +2. Use it: `var convnetjs = require("convnetjs");` + +## License +MIT diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..6158a6d --- /dev/null +++ b/bower.json @@ -0,0 +1,31 @@ +{ + "name": "convnetjs", + "version": "0.0.0", + "authors": [ + "Andrej Karpathy " + ], + "description": "Deep Learning in Javascript. Train Convolutional Neural Networks (or ordinary ones) in your browser.", + "main": "build/convnet.js", + "moduleType": [ + "amd", + "es6", + "globals", + "node", + "yui" + ], + "keywords": [ + "machine", + "learning", + "AI", + "convnet" + ], + "license": "MIT", + "homepage": "http://cs.stanford.edu/people/karpathy/convnetjs/", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/build/deepqlearn.js b/build/deepqlearn.js new file mode 100644 index 0000000..05d6960 --- /dev/null +++ b/build/deepqlearn.js @@ -0,0 +1,292 @@ +var deepqlearn = deepqlearn || { REVISION: 'ALPHA' }; + +(function(global) { + "use strict"; + + // An agent is in state0 and does action0 + // environment then assigns reward0 and provides new state, state1 + // Experience nodes store all this information, which is used in the + // Q-learning update step + var Experience = function(state0, action0, reward0, state1) { + this.state0 = state0; + this.action0 = action0; + this.reward0 = reward0; + this.state1 = state1; + } + + // A Brain object does all the magic. + // over time it receives some inputs and some rewards + // and its job is to set the outputs to maximize the expected reward + var Brain = function(num_states, num_actions, opt) { + var opt = opt || {}; + // in number of time steps, of temporal memory + // the ACTUAL input to the net will be (x,a) temporal_window times, and followed by current x + // so to have no information from previous time step going into value function, set to 0. + this.temporal_window = typeof opt.temporal_window !== 'undefined' ? opt.temporal_window : 1; + // size of experience replay memory + this.experience_size = typeof opt.experience_size !== 'undefined' ? opt.experience_size : 30000; + // number of examples in experience replay memory before we begin learning + this.start_learn_threshold = typeof opt.start_learn_threshold !== 'undefined'? opt.start_learn_threshold : Math.floor(Math.min(this.experience_size*0.1, 1000)); + // gamma is a crucial parameter that controls how much plan-ahead the agent does. In [0,1] + this.gamma = typeof opt.gamma !== 'undefined' ? opt.gamma : 0.8; + + // number of steps we will learn for + this.learning_steps_total = typeof opt.learning_steps_total !== 'undefined' ? opt.learning_steps_total : 100000; + // how many steps of the above to perform only random actions (in the beginning)? + this.learning_steps_burnin = typeof opt.learning_steps_burnin !== 'undefined' ? opt.learning_steps_burnin : 3000; + // what epsilon value do we bottom out on? 0.0 => purely deterministic policy at end + this.epsilon_min = typeof opt.epsilon_min !== 'undefined' ? opt.epsilon_min : 0.05; + // what epsilon to use at test time? (i.e. when learning is disabled) + this.epsilon_test_time = typeof opt.epsilon_test_time !== 'undefined' ? opt.epsilon_test_time : 0.01; + + // advanced feature. Sometimes a random action should be biased towards some values + // for example in flappy bird, we may want to choose to not flap more often + if(typeof opt.random_action_distribution !== 'undefined') { + // this better sum to 1 by the way, and be of length this.num_actions + this.random_action_distribution = opt.random_action_distribution; + if(this.random_action_distribution.length !== num_actions) { + console.log('TROUBLE. random_action_distribution should be same length as num_actions.'); + } + var a = this.random_action_distribution; + var s = 0.0; for(var k=0;k0.0001) { console.log('TROUBLE. random_action_distribution should sum to 1!'); } + } else { + this.random_action_distribution = []; + } + + // states that go into neural net to predict optimal action look as + // x0,a0,x1,a1,x2,a2,...xt + // this variable controls the size of that temporal window. Actions are + // encoded as 1-of-k hot vectors + this.net_inputs = num_states * this.temporal_window + num_actions * this.temporal_window + num_states; + this.num_states = num_states; + this.num_actions = num_actions; + this.window_size = Math.max(this.temporal_window, 2); // must be at least 2, but if we want more context even more + this.state_window = new Array(this.window_size); + this.action_window = new Array(this.window_size); + this.reward_window = new Array(this.window_size); + this.net_window = new Array(this.window_size); + + // create [state -> value of all possible actions] modeling net for the value function + var layer_defs = []; + if(typeof opt.layer_defs !== 'undefined') { + // this is an advanced usage feature, because size of the input to the network, and number of + // actions must check out. This is not very pretty Object Oriented programming but I can't see + // a way out of it :( + layer_defs = opt.layer_defs; + if(layer_defs.length < 2) { console.log('TROUBLE! must have at least 2 layers'); } + if(layer_defs[0].type !== 'input') { console.log('TROUBLE! first layer must be input layer!'); } + if(layer_defs[layer_defs.length-1].type !== 'regression') { console.log('TROUBLE! last layer must be input regression!'); } + if(layer_defs[0].out_depth * layer_defs[0].out_sx * layer_defs[0].out_sy !== this.net_inputs) { + console.log('TROUBLE! Number of inputs must be num_states * temporal_window + num_actions * temporal_window + num_states!'); + } + if(layer_defs[layer_defs.length-1].num_neurons !== this.num_actions) { + console.log('TROUBLE! Number of regression neurons should be num_actions!'); + } + } else { + // create a very simple neural net by default + layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth:this.net_inputs}); + if(typeof opt.hidden_layer_sizes !== 'undefined') { + // allow user to specify this via the option, for convenience + var hl = opt.hidden_layer_sizes; + for(var k=0;k maxval) { maxk = k; maxval = action_values.w[k]; } + } + return {action:maxk, value:maxval}; + }, + getNetInput: function(xt) { + // return s = (x,a,x,a,x,a,xt) state vector. + // It's a concatenation of last window_size (x,a) pairs and current state x + var w = []; + w = w.concat(xt); // start with current state + // and now go backwards and append states and actions from history temporal_window times + var n = this.window_size; + for(var k=0;k this.temporal_window) { + // we have enough to actually do something reasonable + var net_input = this.getNetInput(input_array); + if(this.learning) { + // compute epsilon for the epsilon-greedy policy + this.epsilon = Math.min(1.0, Math.max(this.epsilon_min, 1.0-(this.age - this.learning_steps_burnin)/(this.learning_steps_total - this.learning_steps_burnin))); + } else { + this.epsilon = this.epsilon_test_time; // use test-time value + } + var rf = convnetjs.randf(0,1); + if(rf < this.epsilon) { + // choose a random action with epsilon probability + action = this.random_action(); + } else { + // otherwise use our policy to make decision + var maxact = this.policy(net_input); + action = maxact.action; + } + } else { + // pathological case that happens first few iterations + // before we accumulate window_size inputs + var net_input = []; + action = this.random_action(); + } + + // remember the state and action we took for backward pass + this.net_window.shift(); + this.net_window.push(net_input); + this.state_window.shift(); + this.state_window.push(input_array); + this.action_window.shift(); + this.action_window.push(action); + + return action; + }, + backward: function(reward) { + this.latest_reward = reward; + this.average_reward_window.add(reward); + this.reward_window.shift(); + this.reward_window.push(reward); + + if(!this.learning) { return; } + + // various book-keeping + this.age += 1; + + // it is time t+1 and we have to store (s_t, a_t, r_t, s_{t+1}) as new experience + // (given that an appropriate number of state measurements already exist, of course) + if(this.forward_passes > this.temporal_window + 1) { + var e = new Experience(); + var n = this.window_size; + e.state0 = this.net_window[n-2]; + e.action0 = this.action_window[n-2]; + e.reward0 = this.reward_window[n-2]; + e.state1 = this.net_window[n-1]; + if(this.experience.length < this.experience_size) { + this.experience.push(e); + } else { + // replace. finite memory! + var ri = convnetjs.randi(0, this.experience_size); + this.experience[ri] = e; + } + } + + // learn based on experience, once we have some samples to go on + // this is where the magic happens... + if(this.experience.length > this.start_learn_threshold) { + var avcost = 0.0; + for(var k=0;k < this.tdtrainer.batch_size;k++) { + var re = convnetjs.randi(0, this.experience.length); + var e = this.experience[re]; + var x = new convnetjs.Vol(1, 1, this.net_inputs); + x.w = e.state0; + var maxact = this.policy(e.state1); + var r = e.reward0 + this.gamma * maxact.value; + var ystruct = {dim: e.action0, val: r}; + var loss = this.tdtrainer.train(x, ystruct); + avcost += loss.loss; + } + avcost = avcost/this.tdtrainer.batch_size; + this.average_loss_window.add(avcost); + } + }, + visSelf: function(elt) { + elt.innerHTML = ''; // erase elt first + + // elt is a DOM element that this function fills with brain-related information + var brainvis = document.createElement('div'); + + // basic information + var desc = document.createElement('div'); + var t = ''; + t += 'experience replay size: ' + this.experience.length + '
'; + t += 'exploration epsilon: ' + this.epsilon + '
'; + t += 'age: ' + this.age + '
'; + t += 'average Q-learning loss: ' + this.average_loss_window.get_average() + '
'; + t += 'smooth-ish reward: ' + this.average_reward_window.get_average() + '
'; + desc.innerHTML = t; + brainvis.appendChild(desc); + + elt.appendChild(brainvis); + } + } + + global.Brain = Brain; +})(deepqlearn); + +(function(lib) { + "use strict"; + if (typeof module === "undefined" || typeof module.exports === "undefined") { + window.deepqlearn = lib; // in ordinary browser attach library to window + } else { + module.exports = lib; // in nodejs + } +})(deepqlearn); diff --git a/build/util.js b/build/util.js new file mode 100644 index 0000000..0b700d1 --- /dev/null +++ b/build/util.js @@ -0,0 +1,64 @@ + +// contains various utility functions +var cnnutil = (function(exports){ + + // a window stores _size_ number of values + // and returns averages. Useful for keeping running + // track of validation or training accuracy during SGD + var Window = function(size, minsize) { + this.v = []; + this.size = typeof(size)==='undefined' ? 100 : size; + this.minsize = typeof(minsize)==='undefined' ? 20 : minsize; + this.sum = 0; + } + Window.prototype = { + add: function(x) { + this.v.push(x); + this.sum += x; + if(this.v.length>this.size) { + var xold = this.v.shift(); + this.sum -= xold; + } + }, + get_average: function() { + if(this.v.length < this.minsize) return -1; + else return this.sum/this.v.length; + }, + reset: function(x) { + this.v = []; + this.sum = 0; + } + } + + // returns min, max and indeces of an array + var maxmin = function(w) { + if(w.length === 0) { return {}; } // ... ;s + + var maxv = w[0]; + var minv = w[0]; + var maxi = 0; + var mini = 0; + for(var i=1;i maxv) { maxv = w[i]; maxi = i; } + if(w[i] < minv) { minv = w[i]; mini = i; } + } + return {maxi: maxi, maxv: maxv, mini: mini, minv: minv, dv:maxv-minv}; + } + + // returns string representation of float + // but truncated to length of d digits + var f2t = function(x, d) { + if(typeof(d)==='undefined') { var d = 5; } + var dd = 1.0 * Math.pow(10, d); + return '' + Math.floor(x*dd)/dd; + } + + exports = exports || {}; + exports.Window = Window; + exports.maxmin = maxmin; + exports.f2t = f2t; + return exports; + +})(typeof module != 'undefined' && module.exports); // add exports to module.exports if in node.js + + diff --git a/build/vis.js b/build/vis.js new file mode 100644 index 0000000..ab4e20f --- /dev/null +++ b/build/vis.js @@ -0,0 +1,195 @@ + +// contains various utility functions +var cnnvis = (function(exports){ + + // can be used to graph loss, or accuract over time + var Graph = function(options) { + var options = options || {}; + this.step_horizon = options.step_horizon || 1000; + + this.pts = []; + + this.maxy = -9999; + this.miny = 9999; + } + + Graph.prototype = { + // canv is the canvas we wish to update with this new datapoint + add: function(step, y) { + var time = new Date().getTime(); // in ms + if(y>this.maxy*0.99) this.maxy = y*1.05; + if(y this.step_horizon) this.step_horizon *= 2; + }, + // elt is a canvas we wish to draw into + drawSelf: function(canv) { + + var pad = 25; + var H = canv.height; + var W = canv.width; + var ctx = canv.getContext('2d'); + + ctx.clearRect(0, 0, W, H); + ctx.font="10px Georgia"; + + var f2t = function(x) { + var dd = 1.0 * Math.pow(10, 2); + return '' + Math.floor(x*dd)/dd; + } + + // draw guidelines and values + ctx.strokeStyle = "#999"; + ctx.beginPath(); + var ng = 10; + for(var i=0;i<=ng;i++) { + var xpos = i/ng*(W-2*pad)+pad; + ctx.moveTo(xpos, pad); + ctx.lineTo(xpos, H-pad); + ctx.fillText(f2t(i/ng*this.step_horizon/1000)+'k',xpos,H-pad+14); + } + for(var i=0;i<=ng;i++) { + var ypos = i/ng*(H-2*pad)+pad; + ctx.moveTo(pad, ypos); + ctx.lineTo(W-pad, ypos); + ctx.fillText(f2t((ng-i)/ng*(this.maxy-this.miny) + this.miny), 0, ypos); + } + ctx.stroke(); + + var N = this.pts.length; + if(N<2) return; + + // draw the actual curve + var t = function(x, y, s) { + var tx = x / s.step_horizon * (W-pad*2) + pad; + var ty = H - ((y-s.miny) / (s.maxy-s.miny) * (H-pad*2) + pad); + return {tx:tx, ty:ty} + } + + ctx.strokeStyle = "red"; + ctx.beginPath() + for(var i=0;ithis.maxy*0.99) this.maxy = y*1.05; + if(y this.step_horizon) this.step_horizon *= 2; + }, + // elt is a canvas we wish to draw into + drawSelf: function(canv) { + + var pad = 25; + var H = canv.height; + var W = canv.width; + var ctx = canv.getContext('2d'); + + ctx.clearRect(0, 0, W, H); + ctx.font="10px Georgia"; + + var f2t = function(x) { + var dd = 1.0 * Math.pow(10, 2); + return '' + Math.floor(x*dd)/dd; + } + + // draw guidelines and values + ctx.strokeStyle = "#999"; + ctx.beginPath(); + var ng = 10; + for(var i=0;i<=ng;i++) { + var xpos = i/ng*(W-2*pad)+pad; + ctx.moveTo(xpos, pad); + ctx.lineTo(xpos, H-pad); + ctx.fillText(f2t(i/ng*this.step_horizon/1000)+'k',xpos,H-pad+14); + } + for(var i=0;i<=ng;i++) { + var ypos = i/ng*(H-2*pad)+pad; + ctx.moveTo(pad, ypos); + ctx.lineTo(W-pad, ypos); + ctx.fillText(f2t((ng-i)/ng*(this.maxy-this.miny) + this.miny), 0, ypos); + } + ctx.stroke(); + + var N = this.pts.length; + if(N<2) return; + + // draw legend + for(var k=0;k + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compile/yuicompressor-2.4.8.jar b/compile/yuicompressor-2.4.8.jar new file mode 100644 index 0000000..a1cf0a0 Binary files /dev/null and b/compile/yuicompressor-2.4.8.jar differ diff --git a/demo/autoencoder.html b/demo/autoencoder.html new file mode 100644 index 0000000..e2d37f1 --- /dev/null +++ b/demo/autoencoder.html @@ -0,0 +1,85 @@ + + + + + + ConvNetJS MNIST demo + + + + + + + + + + + + + + + +
+

ConvNetJS Denoising Autoencoder demo

+

Description

+

+ All the other demos are examples of Supervised Learning, so in this demo I wanted to show an example of Unsupervised Learning. We are going to train an autoencoder on MNIST digits. +

+

+ An autoencoder is a regression task where the network is asked to predict its input (in other words, model the identity function). Sounds simple enough, except the network has a tight bottleneck of a few neurons in the middle (in the default example only two!), forcing it to create effective representations that compress the input into a low-dimensional code that can be used by the decoder to reproduce the original input. +

+

Report questions/bugs/suggestions to @karpathy. +

+

Training Stats

+
+
+ Current image: +
+ +
+ Learning rate: + +
+
+ +
+
+ +
+
+ +
+ Loss:
+ + +
+ +
+ +
+
+
+ +
+ +
+
+ +
+
+
+ +
+
+ +
+

Network Visualization

+
+
+ +
+ + + + + diff --git a/demo/automatic.html b/demo/automatic.html new file mode 100644 index 0000000..794990a --- /dev/null +++ b/demo/automatic.html @@ -0,0 +1,183 @@ + + + + + + ConvNetJS Automatic + + + + + + + + + + + + + + + + + + + + + +
+

ConvNetJS Automatic Prediction Demo

+

Introduction

+

+ This demo illustrates the usage of ConvNetJS' MagicNet class, which performs fully automatic prediction given your arbitrary data. Internally, the MagicNet tries out many different types of networks, performs cross-validations of network hyper-parameters across folds of your data, and creates a final classifier by model averaging the best architectures. The API for MagicNet looks as follows: +

+ +
+var opts = {}; // options struct
+opts.train_ratio = 0.7;
+opts.num_folds = 10; // number of folds to eval per candidate
+opts.num_candidates = 10; // number of candidates to eval in parallel
+opts.num_epochs = 50; // epochs to make through data per fold
+// below, train_data is a list of input Vols and train_labels is a 
+// list of integer correct labels (in 0...K).
+var magicNet = new convnetjs.MagicNet(train_data, train_labels, opts);
+magicNet.onFinishBatch(finishedBatch); // example of setting callback for events
+
+// start training magicNet. Every step() call all candidates train on one example
+setInterval(function(){ magicNet.step() }, 0);
+
+// once at least one batch of candidates is evaluated on all folds we can do prediction!
+function finishedBatch() {
+  // prediction example. xout is Vol of scores
+  // there is also predict_soft(), which returns the full score volume for all labels
+  var predicted_label = magicNet.predict(some_test_vol);
+}
+
+ +

Your data

+

+ Currently made input data assumptions: +

+
    +
  • Provide data as CSV (comma-separated) values. Leave out any header rows.
  • +
  • Every row is a data point.
  • +
  • No missing values.
  • +
  • Last column is the class (only classification is currently supported).
  • +
+

+ The text area is pre-filled with a Car Quality Evaluation dataset to show you example input, but there are a few buttons that load some example datasets (more details on these: Iris data, Car Eval data, Yeast Data). A nice place to find more datasets are UCI Repository or mldata.org. +

+ +
+ + + +
+ + + + + (and send % of imported data randomly into Test Set below) + +
+
+ +

Cross-Validation

+ +
+ + Index of column to classify as target. (e.g. 0 = first column, -1 = last column) +
+ +
+ + Percent of data to use for training (rest will be validation) +
+ +
+ Number of data folds to evaluate per candidate +
+ +
+ Number of candidates in a batch, to evaluate in parallel +
+ +
+ Number of epochs to make over each fold +
+ +
+ Number of Neurons in each layer: Min Max +
+ + + + +
+ +

Below: graph of the validation accuracy for current batch of candidate models as a function of the number of training points they have seen during training. Good networks will rise up as high as possible and stay there. The best performer is printed in detail below the graph. The graph is less wiggly if there is more data.

+
+
+ + +
+
+ +

Evaluate on Test Set

+

+ Paste a test set in box below to evaluate the final test accuracy, which is based on a model-averaged ensemble of the best discovered network from the training data above. The CSV pasted below should be in the same format as the one used for training data above. The text field is pre-filled with the training data. +

+ +
+ Number of best models to average in the ensemble network +
+ + + +
+
+
+ +

Export Best Network

+

+ + + Above you can export a trained MagicNet in JSON format. The exported MagicNet is simply a thin wrapper around a list of the best networks that were discovered during cross-validation and it can be loaded and used again as follows: +
+var magicNet = new convnetjs.MagicNet();
+magicNet.fromJSON(json);
+magicNet.predict(some_vol); // ready to use!
+
+ +







+ +
+ + diff --git a/demo/cifar10.html b/demo/cifar10.html new file mode 100644 index 0000000..a8a1594 --- /dev/null +++ b/demo/cifar10.html @@ -0,0 +1,145 @@ + + + + + + ConvNetJS CIFAR-10 demo + + + + + + + + + + + + + + + + + + +
+

ConvNetJS CIFAR-10 demo

+

Description

+

+ This demo trains a Convolutional Neural Network on the CIFAR-10 dataset in your browser, with nothing but Javascript. The state of the art on this dataset is about 90% accuracy and human performance is at about 94% (not perfect as the dataset can be a bit ambiguous). I used this python script to parse the original files (python version) into batches of images that can be easily loaded into page DOM with img tags. +

+

This dataset is more difficult and it takes longer to train a network. Data augmentation includes random flipping and random image shifts by up to 2px horizontally and verically.

+

+ By default, in this demo we're using Adadelta which is one of per-parameter adaptive step size methods, so we don't have to worry about changing learning rates or momentum over time. However, I still included the text fields for changing these if you'd like to play around with SGD+Momentum trainer. +

+

Report questions/bugs/suggestions to @karpathy.

+

Training Stats

+
+
+ +
+ +
+ Learning rate: + +
+ + Momentum: + +
+ + Batch size: + +
+ + Weight decay: + +
+ +
+
+ +
+ +
+
+
+
+ Loss:
+ + +
+ +
+
+
+
+ Test an image from your computer: +
+ +
+ + + +
+
+
+
+ +

Instantiate a Network and Trainer

+
+
+ +
+ +
+

Network Visualization

+
+
+ +
+

Example predictions on Test set

+
+
+
+ +
+ + + + + diff --git a/demo/classify2d.html b/demo/classify2d.html new file mode 100644 index 0000000..c933ae6 --- /dev/null +++ b/demo/classify2d.html @@ -0,0 +1,86 @@ + + +ConvNetJS demo: Classify toy 2D data + + + + + + + + + + + + + + +
+

ConvnetJS demo: toy 2d classification with 2-layer neural network

+ +

The simulation below shows a toy binary problem with a few data points of class 0 (red) and 1 (green). The network is set up as:

+ + +
+ + +

Feel free to change this, the text area above gets eval()'d when you hit the button and the network gets reloaded. Every 10th of a second, all points are fed to the network multiple times through the trainer class to train the network. The resulting predictions of the network are then "painted" under the data points to show you the generalization.

+ +

On the right we visualize the transformed representation of all grid points in the original space and the data, for a given layer and only for 2 neurons at a time. The number in the bracket shows the total number of neurons at that level of representation. If the number is more than 2, you will only see the two visualized but you can cycle through all of them with the cycle button.

+ +
+
+Browser not supported for Canvas. Get a real browser. + + +
+
+

+Controls:
+CLICK: Add red data point
+SHIFT+CLICK: Add green data point
+CTRL+CLICK: Remove closest data point
+

+
+ +
+ Browser not supported for Canvas. Get a real browser. +
+
+ +
+ +
+ +

Go back to ConvNetJS

+ +
+ + + diff --git a/demo/css/automatic.css b/demo/css/automatic.css new file mode 100644 index 0000000..5894e85 --- /dev/null +++ b/demo/css/automatic.css @@ -0,0 +1,82 @@ +#wrap { + width:800px; + margin-left: auto; + margin-right: auto; +} +h2 { + text-align: center; + font-size: 34px; + font-weight: 300; + margin-bottom: 50px; +} +h1 { + font-size: 26px; + font-weight: 400; + border-bottom: 1px #999 solid; +} +#datamsg{ + background-color: white; + margin-top: 2px; + padding: 10px; +} +#prepromsg{ + margin-top: 10px; + padding: 10px; +} +.msg{ + padding: 2px; +} +body { + font-family: 'Lato', sans-serif; + color: #333; + font-size: 20px; + font-weight: 300; +} +input[type=text] { + border: 1px solid #999; + padding: 3px; + font-size: 18px; + color: #333; +} +.clouds-flat-button { + position: relative; + vertical-align: top; + width: 100%; + height: 80px; + padding: 0; + color:#454545; + text-align: center; + font-size: 16px; + background: #ecf0f1; + border: 0; + border-bottom: 2px solid #dadedf; + cursor: pointer; + -webkit-box-shadow: inset 0 -2px #dadedf; + box-shadow: inset 0 -2px #dadedf; +} +.clouds-flat-button:active { + top: 1px; + outline: none; + -webkit-box-shadow: none; + box-shadow: none; +} +#bestmodel { + font-size: 16px; + background-color: #FAFAFA; + padding: 10px; +} +#bestmodeloverall { + font-size: 16px; + background-color: #FAFAFA; + padding: 10px; + margin-top: 10px; +} +.syntaxhighlighter { + font-size: 14px !important; + overflow-y: hidden !important; +} +.sopts { + box-shadow: 0px 0px 2px 0px #555; + margin-bottom: 5px; + padding: 10px; +} \ No newline at end of file diff --git a/demo/css/style.css b/demo/css/style.css new file mode 100644 index 0000000..fbec769 --- /dev/null +++ b/demo/css/style.css @@ -0,0 +1,102 @@ +.layer { + border: 1px solid #999; + margin-bottom: 5px; + text-align: left; + padding: 10px; +} +.layer_act { + width: 500px; + float: right; +} +.ltconv { + background-color: #FDD; +} +.ltrelu { + background-color: #FDF; +} +.ltpool { + background-color: #DDF; +} +.ltsoftmax { + background-color: #FFD; +} +.ltfc { + background-color: #DFF; +} +.ltlrn { + background-color: #DFD; +} +.ltdropout { + background-color: #AAA; +} +.ltitle { + color: #333; + font-size: 18px; +} +.actmap { + margin: 1px; +} +#trainstats { + text-align: left; +} +.clear { + clear: both; +} +#wrap { + width: 800px; + margin-left: auto; + margin-right: auto; +} +h1 { + font-size: 16px; + color: #333; + background-color: #DDD; + border-bottom: 1px #999 solid; + text-align: center; +} +h2 { + text-align: center; +} +.secpart { + width: 400px; + float: left; +} +#lossgraph { + /*border: 1px solid #F0F;*/ + width: 100%; +} +.testdiv canvas { + float: left; + width: 64px; +} +.testdiv { + background-color: rgba(255,255,255,0.65); + height: auto; + width: auto; + display: inline-block; + font-size: 12px; + box-shadow: 0px 0px 2px 2px #EEE; + margin: 5px; + padding: 5px; + color: black; +} +.probsdiv { + float: left; + width: 100px; + margin-left: 1px; +} +.pp { + margin: 1px; + padding: 1px; +} +#testset_acc { + /* margin-bottom: 200px; */ +} +#testset_vis { + margin-bottom: 200px; +} +body { + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + /* color: #333; */ + /* padding: 20px; */ +} \ No newline at end of file diff --git a/demo/image_regression.html b/demo/image_regression.html new file mode 100644 index 0000000..a8ae7e1 --- /dev/null +++ b/demo/image_regression.html @@ -0,0 +1,111 @@ + + +ConvNetJS demo: Image Painting + + + + + + + + + + + + + + + + +
+

ConvnetJS demo: Image "Painting"

+ +

This demo that treats the pixels of an image as a learning problem: it takes the (x,y) position on a grid and learns to predict the color at that point using regression to (r,g,b). It's a bit like compression, since the image information is encoded in the weights of the network, but almost certainly not of practical kind :)

+

+ Note that the entire ConvNetJS definition is shown in textbox below and it gets eval()'d to create the network, so feel free to fiddle with the parameters and hit "reload". I found that, empirically and interestingly, deeper networks tend to work much better on this task given a fixed parameter budget. +

+ +

Report questions/bugs/suggestions to @karpathy.

+ + +

+ + +
+Choose your own image: + +
+ +
+ +
+
+ Original Image
+ +
+
+ Neural Network output
+ +
+ +
+
+ +
+
+
+ +
Learning rate:
+
+
The learning rate should probably be decreased over time (slide left) to let the network better overfit the training data. It's nice to not have to worry about overfitting.
+ +

+
+ You can upload your own image above (click Choose File), or you can click on any of the images below to load them. +
+ + +

+

Go back to ConvNetJS

+ +
+ + + + diff --git a/demo/js/autoencoder.js b/demo/js/autoencoder.js new file mode 100644 index 0000000..9760312 --- /dev/null +++ b/demo/js/autoencoder.js @@ -0,0 +1,495 @@ +// globals +var layer_defs, net, trainer; +var t = "\ +layer_defs = [];\n\ +layer_defs.push({type:'input', out_sx:28, out_sy:28, out_depth:1});\n\ +layer_defs.push({type:'fc', num_neurons:50, activation:'tanh'});\n\ +layer_defs.push({type:'fc', num_neurons:50, activation:'tanh'});\n\ +layer_defs.push({type:'fc', num_neurons:2});\n\ +layer_defs.push({type:'fc', num_neurons:50, activation:'tanh'});\n\ +layer_defs.push({type:'fc', num_neurons:50, activation:'tanh'});\n\ +layer_defs.push({type:'regression', num_neurons:28*28});\n\ +\n\ +net = new convnetjs.Net();\n\ +net.makeLayers(layer_defs);\n\ +\n\ +trainer = new convnetjs.SGDTrainer(net, {learning_rate:1, method:'adadelta', batch_size:50, l2_decay:0.001, l1_decay:0.001});\n\ +"; + +// ------------------------ +// BEGIN MNIST SPECIFIC STUFF +// ------------------------ +var sample_training_instance = function() { + + // find an unloaded batch + var bi = Math.floor(Math.random()*loaded_train_batches.length); + var b = loaded_train_batches[bi]; + var k = Math.floor(Math.random()*3000); // sample within the batch + var n = b*3000+k; + + // load more batches over time + if(step_num%5000===0 && step_num>0) { + for(var i=0;i3) { + // actual weights + filters_div.appendChild(document.createTextNode('Weights:')); + filters_div.appendChild(document.createElement('br')); + for(var j=0;j= selected_layer.out_depth) d1 = 0; // and wrap + if(d0 >= selected_layer.out_depth) d0 = 0; // and wrap + $("#cyclestatus").html('drawing neurons ' + d0 + ' and ' + d1 + ' of layer #' + lix + ' (' + net.layers[lix].layer_type + ')'); +} +function updateLix(newlix) { + $("#button"+lix).css('background-color', ''); // erase highlight + lix = newlix; + d0 = 0; + d1 = 1; // reset these + $("#button"+lix).css('background-color', '#FFA'); + + $("#cyclestatus").html('drawing neurons ' + d0 + ' and ' + d1 + ' of layer with index ' + lix + ' (' + net.layers[lix].layer_type + ')'); +} + + +var lossGraph = new cnnvis.Graph(); +var xLossWindow = new cnnutil.Window(100); +var w2LossWindow = new cnnutil.Window(100); +var w1LossWindow = new cnnutil.Window(100); +var step_num = 0; +var colors = ["red", "blue", "green", "orange", "magenta", "cyan", "purple", "silver", "olive", "lime", "yellow"]; +var step = function(sample) { + + // train on it with network + var stats = trainer.train(sample.x, sample.x.w); + + // keep track of stats such as the average training error and loss + xLossWindow.add(stats.cost_loss); + w1LossWindow.add(stats.l1_decay_loss); + w2LossWindow.add(stats.l2_decay_loss); + + // visualize training status + var train_elt = document.getElementById("trainstats"); + train_elt.innerHTML = ''; + var t = 'Forward time per example: ' + stats.fwd_time + 'ms'; + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'Backprop time per example: ' + stats.bwd_time + 'ms'; + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'Regression loss: ' + f2t(xLossWindow.get_average()); + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'L2 Weight decay loss: ' + f2t(w2LossWindow.get_average()); + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'L1 Weight decay loss: ' + f2t(w1LossWindow.get_average()); + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'Examples seen: ' + step_num; + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + + // visualize activations + if(step_num % 100 === 0) { + var vis_elt = document.getElementById("visnet"); + visualize_activations(net, vis_elt); + } + + // visualize embedding + if(step_num % 100 === 0) { + + var embcanvas = document.getElementById('embedding'); + var ctx = embcanvas.getContext("2d"); + var EW = embcanvas.width; + var EH = embcanvas.height; + + // propagate a few training examples through the network and grab codes + var xcodes = []; + var ycodes = []; + var ns = embed_samples.length; // number of samples + for(var k=0;k= 0 && xw1 >= 0 && xw2 >= 0) { // if they are -1 it means not enough data was accumulated yet for estimates + lossGraph.add(step_num, xa + xw1 + xw2); + lossGraph.drawSelf(document.getElementById("lossgraph")); + } + } + + step_num++; +} + +// user settings +var change_lr = function() { + trainer.learning_rate = parseFloat(document.getElementById("lr_input").value); + update_net_param_display(); +} +var update_net_param_display = function() { + document.getElementById('lr_input').value = trainer.learning_rate; +} +var toggle_pause = function() { + paused = !paused; + var btn = document.getElementById('buttontp'); + if(paused) { btn.value = 'resume' } + else { btn.value = 'pause'; } +} +var dump_json = function() { + document.getElementById("dumpjson").value = JSON.stringify(net.toJSON()); +} +var clear_graph = function() { + lossGraph = new cnnvis.Graph(); // reinit graph too +} +var reset_all = function() { + update_net_param_display(); + + // reinit windows that keep track of val/train accuracies + lossGraph = new cnnvis.Graph(); // reinit graph too + step_num = 0; + + // enter buttons for layers + var t = ''; + for(var i=1;i"; + } + $("#layer_ixes").html(t); + $("#button"+lix).css('background-color', '#FFA'); + $("#cyclestatus").html('drawing neurons ' + d0 + ' and ' + d1 + ' of layer with index ' + lix + ' (' + net.layers[lix].layer_type + ')'); + +} +var load_from_json = function() { + var jsonString = document.getElementById("dumpjson").value; + var json = JSON.parse(jsonString); + net = new convnetjs.Net(); + net.fromJSON(json); + reset_all(); +} +var change_net = function() { + eval($("#newnet").val()); + reset_all(); +} diff --git a/demo/js/automatic.js b/demo/js/automatic.js new file mode 100644 index 0000000..43fdc9a --- /dev/null +++ b/demo/js/automatic.js @@ -0,0 +1,355 @@ + + // utility functions + Array.prototype.contains = function(v) { + for(var i = 0; i < this.length; i++) { + if(this[i] === v) return true; + } + return false; + }; + Array.prototype.unique = function() { + var arr = []; + for(var i = 0; i < this.length; i++) { + if(!arr.contains(this[i])) { + arr.push(this[i]); + } + } + return arr; + } + + function FAIL(outdivid, msg) { + $(outdivid).prepend("
"+msg+"
") + } + function SUCC(outdivid, msg) { + $(outdivid).prepend("
"+msg+"
") + } + + // looks at a column i of data and guesses what's in it + // returns results of analysis: is column numeric? How many unique entries and what are they? + function guessColumn(data, c) { + var numeric = true; + var vs = []; + for(var i=0,n=data.length;i 20 && i>3 && i < D-3) { + if(i==4) { + SUCC(outdivid, "..."); // suppress output for too many columns + } + } else { + SUCC(outdivid, "column " + i + " looks " + (res.numeric ? "numeric" : "NOT numeric") + " and has " + res.num + " unique elements"); + } + } + + return {arr: arr, colstats: colstats}; + } + + // process input mess into vols and labels + function makeDataset(arr, colstats) { + + var labelix = parseInt($("#labelix").val()); + if(labelix < 0) labelix = D + labelix; // -1 should turn to D-1 + + var data = []; + var labels = []; + for(var i=0;i 0) { + t += 'Results based on ' + c.acc.length + ' folds:'; + t += 'best model in current batch (validation accuracy ' + mm.maxv + '):
'; + t += 'Net layer definitions:
'; + t += JSON.stringify(cm.layer_defs); + t += '
Trainer definition:
'; + t += JSON.stringify(cm.trainer_def); + t += '
'; + } + $('#bestmodel').html(t); + + // also print out the best model so far + var t = ''; + if(magicNet.evaluated_candidates.length > 0) { + var cm = magicNet.evaluated_candidates[0]; + t += 'validation accuracy of best model so far, overall: ' + cm.accv / cm.acc.length + '
'; + t += 'Net layer definitions:
'; + t += JSON.stringify(cm.layer_defs); + t += '
Trainer definition:
'; + t += JSON.stringify(cm.trainer_def); + t += '
'; + } + $('#bestmodeloverall').html(t); + } + } + + // TODO: MOVE TO CONVNETJS UTILS + var randperm = function(n) { + var i = n, + j = 0, + temp; + var array = []; + for(var q=0;q"; + } + $("#layer_ixes").html(t); + $("#button"+lix).css('background-color', '#FFA'); + $("#cyclestatus").html('drawing neurons ' + d0 + ' and ' + d1 + ' of layer with index ' + lix + ' (' + net.layers[lix].layer_type + ')'); +} +function updateLix(newlix) { + $("#button"+lix).css('background-color', ''); // erase highlight + lix = newlix; + d0 = 0; + d1 = 1; // reset these + $("#button"+lix).css('background-color', '#FFA'); + + $("#cyclestatus").html('drawing neurons ' + d0 + ' and ' + d1 + ' of layer with index ' + lix + ' (' + net.layers[lix].layer_type + ')'); +} + + +function myinit() { } + +function random_data(){ + data = []; + labels = []; + for(var k=0;k<40;k++) { + data.push([convnetjs.randf(-3,3), convnetjs.randf(-3,3)]); labels.push(convnetjs.randf(0,1) > 0.5 ? 1 : 0); + } + N = labels.length; +} + +function original_data(){ + + data = []; + labels = []; + data.push([-0.4326 , 1.1909 ]); labels.push(1); + data.push([3.0, 4.0]); labels.push(1); + data.push([0.1253 , -0.0376 ]); labels.push(1); + data.push([0.2877 , 0.3273 ]); labels.push(1); + data.push([-1.1465 , 0.1746 ]); labels.push(1); + data.push([1.8133 , 1.0139 ]); labels.push(0); + data.push([2.7258 , 1.0668 ]); labels.push(0); + data.push([1.4117 , 0.5593 ]); labels.push(0); + data.push([4.1832 , 0.3044 ]); labels.push(0); + data.push([1.8636 , 0.1677 ]); labels.push(0); + data.push([0.5 , 3.2 ]); labels.push(1); + data.push([0.8 , 3.2 ]); labels.push(1); + data.push([1.0 , -2.2 ]); labels.push(1); + N = labels.length; +} + +function circle_data() { + data = []; + labels = []; + for(var i=0;i<50;i++) { + var r = convnetjs.randf(0.0, 2.0); + var t = convnetjs.randf(0.0, 2*Math.PI); + data.push([r*Math.sin(t), r*Math.cos(t)]); + labels.push(1); + } + for(var i=0;i<50;i++) { + var r = convnetjs.randf(3.0, 5.0); + //var t = convnetjs.randf(0.0, 2*Math.PI); + var t = 2*Math.PI*i/50.0 + data.push([r*Math.sin(t), r*Math.cos(t)]); + labels.push(0); + } + N = data.length; +} + +function spiral_data() { + data = []; + labels = []; + var n = 100; + for(var i=0;i= selected_layer.out_depth) d1 = 0; // and wrap + if(d0 >= selected_layer.out_depth) d0 = 0; // and wrap + $("#cyclestatus").html('drawing neurons ' + d0 + ' and ' + d1 + ' of layer #' + lix + ' (' + net.layers[lix].layer_type + ')'); +} + +var lix = 4; // layer id to track first 2 neurons of +var d0 = 0; // first dimension to show visualized +var d1 = 1; // second dimension to show visualized +function draw(){ + + ctx.clearRect(0,0,WIDTH,HEIGHT); + + var netx = new convnetjs.Vol(1,1,2); + // draw decisions in the grid + var density= 5.0; + var gridstep = 2; + var gridx = []; + var gridy = []; + var gridl = []; + for(var x=0.0, cx=0; x<=WIDTH; x+= density, cx++) { + for(var y=0.0, cy=0; y<=HEIGHT; y+= density, cy++) { + //var dec= svm.marginOne([(x-WIDTH/2)/ss, (y-HEIGHT/2)/ss]); + netx.w[0] = (x-WIDTH/2)/ss; + netx.w[1] = (y-HEIGHT/2)/ss; + var a = net.forward(netx, false); + + if(a.w[0] > a.w[1]) ctx.fillStyle = 'rgb(250, 150, 150)'; + else ctx.fillStyle = 'rgb(150, 250, 150)'; + + //ctx.fillStyle = 'rgb(150,' + Math.floor(a.w[0]*105)+150 + ',150)'; + //ctx.fillStyle = 'rgb(' + Math.floor(a.w[0]*255) + ',' + Math.floor(a.w[1]*255) + ', 0)'; + ctx.fillRect(x-density/2-1, y-density/2-1, density+2, density+2); + + if(cx%gridstep === 0 && cy%gridstep===0) { + // record the transformation information + var xt = net.layers[lix].out_act.w[d0]; // in screen coords + var yt = net.layers[lix].out_act.w[d1]; // in screen coords + gridx.push(xt); + gridy.push(yt); + gridl.push(a.w[0] > a.w[1]); // remember final label as well + } + } + } + + // draw axes + ctx.beginPath(); + ctx.strokeStyle = 'rgb(50,50,50)'; + ctx.lineWidth = 1; + ctx.moveTo(0, HEIGHT/2); + ctx.lineTo(WIDTH, HEIGHT/2); + ctx.moveTo(WIDTH/2, 0); + ctx.lineTo(WIDTH/2, HEIGHT); + ctx.stroke(); + + // draw representation transformation axes for two neurons at some layer + var mmx = cnnutil.maxmin(gridx); + var mmy = cnnutil.maxmin(gridy); + visctx.clearRect(0,0,visWIDTH,visHEIGHT); + visctx.strokeStyle = 'rgb(0, 0, 0)'; + var n = Math.floor(Math.sqrt(gridx.length)); // size of grid. Should be fine? + var ng = gridx.length; + var c = 0; // counter + visctx.beginPath() + for(var x=0;x= 0 && ix2 >= 0 && ix1 < ng && ix2 < ng && y= 0 && ix2 >= 0 && ix1 < ng && ix2 < ng && x =0) { + console.log('splicing ' + mink); + data.splice(mink, 1); + labels.splice(mink, 1); + N -= 1; + } + + } else { + // add datapoint at location of click + data.push([xt, yt]); + labels.push(shiftPressed ? 1 : 0); + N += 1; + } + +} + +function keyDown(key){ +} + +function keyUp(key) { +} + +$(function() { + // note, globals + viscanvas = document.getElementById('viscanvas'); + visctx = viscanvas.getContext('2d'); + visWIDTH = viscanvas.width; + visHEIGHT = viscanvas.height; + + circle_data(); + $("#layerdef").val(t); + reload(); + NPGinit(20); + +}); \ No newline at end of file diff --git a/demo/js/image-helpers.js b/demo/js/image-helpers.js new file mode 100644 index 0000000..a44dbd3 --- /dev/null +++ b/demo/js/image-helpers.js @@ -0,0 +1,54 @@ + +var loadFile = function(event) { +var reader = new FileReader(); +reader.onload = function(){ + var preview = document.getElementById('preview_img'); + preview.src = centerCrop(reader.result); + preview.src = resize(preview.src); +}; +reader.readAsDataURL(event.target.files[0]); +}; + +function centerCrop(src){ + var image = new Image(); + image.src = src; + + var max_width = Math.min(image.width, image.height); + var max_height = Math.min(image.width, image.height); + + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext("2d"); + + ctx.clearRect(0, 0, canvas.width, canvas.height); + canvas.width = max_width; + canvas.height = max_height; + ctx.drawImage(image, (max_width - image.width)/2, (max_height - image.height)/2, image.width, image.height); + return canvas.toDataURL("image/png"); +} + +function resize(src){ + var image = new Image(); + image.src = src; + + var canvas = document.createElement('canvas'); + canvas.width = image.width; + canvas.height = image.height; + var ctx = canvas.getContext("2d"); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(image, 0, 0, image.width, image.height); + + var dst = document.createElement('canvas'); + dst.width = image_dimension; + dst.height = image_dimension; + + window.pica.WW = false; + window.pica.resizeCanvas(canvas, dst, { + quality: 2, + unsharpAmount: 500, + unsharpThreshold: 100, + transferable: false + }, function (err) { }); + window.pica.WW = true; + return dst.toDataURL("image/png"); +} + diff --git a/demo/js/image_regression.js b/demo/js/image_regression.js new file mode 100644 index 0000000..23bebd1 --- /dev/null +++ b/demo/js/image_regression.js @@ -0,0 +1,173 @@ + +var data, labels; +var layer_defs, net, trainer; + +// create neural net +var t = "layer_defs = [];\n\ +layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth:2}); // 2 inputs: x, y \n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'});\n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'});\n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'});\n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'});\n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'});\n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'});\n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'});\n\ +layer_defs.push({type:'regression', num_neurons:3}); // 3 outputs: r,g,b \n\ +\n\ +net = new convnetjs.Net();\n\ +net.makeLayers(layer_defs);\n\ +\n\ +trainer = new convnetjs.SGDTrainer(net, {learning_rate:0.01, momentum:0.9, batch_size:5, l2_decay:0.0});\n\ +"; + +var batches_per_iteration = 100; +var mod_skip_draw = 100; +var smooth_loss = -1; + +function update(){ + // forward prop the data + var W = nn_canvas.width; + var H = nn_canvas.height; + + var p = oridata.data; + + var v = new convnetjs.Vol(1,1,2); + var loss = 0; + var lossi = 0; + var N = batches_per_iteration; + for(var iters=0;iters0) { + for(var i=0;i 0 ? dwq : 0.0; } + draw_activations_COLOR(activations_div, dd, scale); + for(var q=0;q3) { + // actual weights + filters_div.appendChild(document.createTextNode('Weights:')); + filters_div.appendChild(document.createElement('br')); + for(var j=0;j' + classes_txt[preds[k].k] + '' + } + probsdiv.innerHTML = t; + probsdiv.className = 'probsdiv'; + div.appendChild(probsdiv); + + // add it into DOM + $(div).prependTo($("#testset_vis")).hide().fadeIn('slow').slideDown('slow'); + if($(".probsdiv").length>200) { + $("#testset_vis > .probsdiv").last().remove(); // pop to keep upper bound of shown items + } + } + testAccWindow.add(num_correct/num_total); + $("#testset_acc").text('test accuracy based on last 200 test images: ' + testAccWindow.get_average()); +} +var testImage = function(img) { + var x = convnetjs.img_to_vol(img); + var out_p = net.forward(x); + + + var vis_elt = document.getElementById("visnet"); + visualize_activations(net, vis_elt); + + var preds =[] + for(var k=0;k' + classes_txt[preds[k].k] + '' + } + + probsdiv.innerHTML = t; + probsdiv.className = 'probsdiv'; + div.appendChild(probsdiv); + + // add it into DOM + $(div).prependTo($("#testset_vis")).hide().fadeIn('slow').slideDown('slow'); + if($(".probsdiv").length>200) { + $("#testset_vis > .probsdiv").last().remove(); // pop to keep upper bound of shown items + } +} + +var lossGraph = new cnnvis.Graph(); +var xLossWindow = new cnnutil.Window(100); +var wLossWindow = new cnnutil.Window(100); +var trainAccWindow = new cnnutil.Window(100); +var valAccWindow = new cnnutil.Window(100); +var testAccWindow = new cnnutil.Window(50, 1); +var step_num = 0; +var step = function(sample) { + + var x = sample.x; + var y = sample.label; + + if(sample.isval) { + // use x to build our estimate of validation error + net.forward(x); + var yhat = net.getPrediction(); + var val_acc = yhat === y ? 1.0 : 0.0; + valAccWindow.add(val_acc); + return; // get out + } + + // train on it with network + var stats = trainer.train(x, y); + var lossx = stats.cost_loss; + var lossw = stats.l2_decay_loss; + + // keep track of stats such as the average training error and loss + var yhat = net.getPrediction(); + var train_acc = yhat === y ? 1.0 : 0.0; + xLossWindow.add(lossx); + wLossWindow.add(lossw); + trainAccWindow.add(train_acc); + + // visualize training status + var train_elt = document.getElementById("trainstats"); + train_elt.innerHTML = ''; + var t = 'Forward time per example: ' + stats.fwd_time + 'ms'; + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'Backprop time per example: ' + stats.bwd_time + 'ms'; + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'Classification loss: ' + f2t(xLossWindow.get_average()); + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'L2 Weight decay loss: ' + f2t(wLossWindow.get_average()); + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'Training accuracy: ' + f2t(trainAccWindow.get_average()); + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'Validation accuracy: ' + f2t(valAccWindow.get_average()); + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + var t = 'Examples seen: ' + step_num; + train_elt.appendChild(document.createTextNode(t)); + train_elt.appendChild(document.createElement('br')); + + // visualize activations + if(step_num % 100 === 0) { + var vis_elt = document.getElementById("visnet"); + visualize_activations(net, vis_elt); + } + + // log progress to graph, (full loss) + if(step_num % 200 === 0) { + var xa = xLossWindow.get_average(); + var xw = wLossWindow.get_average(); + if(xa >= 0 && xw >= 0) { // if they are -1 it means not enough data was accumulated yet for estimates + lossGraph.add(step_num, xa + xw); + lossGraph.drawSelf(document.getElementById("lossgraph")); + } + } + + // run prediction on test set + if((step_num % 100 === 0 && step_num > 0) || step_num===100) { + test_predict(); + } + step_num++; +} + +// user settings +var change_lr = function() { + trainer.learning_rate = parseFloat(document.getElementById("lr_input").value); + update_net_param_display(); +} +var change_momentum = function() { + trainer.momentum = parseFloat(document.getElementById("momentum_input").value); + update_net_param_display(); +} +var change_batch_size = function() { + trainer.batch_size = parseFloat(document.getElementById("batch_size_input").value); + update_net_param_display(); +} +var change_decay = function() { + trainer.l2_decay = parseFloat(document.getElementById("decay_input").value); + update_net_param_display(); +} +var update_net_param_display = function() { + document.getElementById('lr_input').value = trainer.learning_rate; + document.getElementById('momentum_input').value = trainer.momentum; + document.getElementById('batch_size_input').value = trainer.batch_size; + document.getElementById('decay_input').value = trainer.l2_decay; +} +var toggle_pause = function() { + paused = !paused; + var btn = document.getElementById('buttontp'); + if(paused) { btn.value = 'resume' } + else { btn.value = 'pause'; } +} +var dump_json = function() { + document.getElementById("dumpjson").value = JSON.stringify(this.net.toJSON()); +} +var clear_graph = function() { + lossGraph = new cnnvis.Graph(); // reinit graph too +} +var reset_all = function() { + // reinit trainer + trainer = new convnetjs.SGDTrainer(net, {learning_rate:trainer.learning_rate, momentum:trainer.momentum, batch_size:trainer.batch_size, l2_decay:trainer.l2_decay}); + update_net_param_display(); + + // reinit windows that keep track of val/train accuracies + xLossWindow.reset(); + wLossWindow.reset(); + trainAccWindow.reset(); + valAccWindow.reset(); + testAccWindow.reset(); + lossGraph = new cnnvis.Graph(); // reinit graph too + step_num = 0; +} +var load_from_json = function() { + var jsonString = document.getElementById("dumpjson").value; + var json = JSON.parse(jsonString); + net = new convnetjs.Net(); + net.fromJSON(json); + reset_all(); +} + +var load_pretrained = function() { + $.getJSON(dataset_name + "_snapshot.json", function(json){ + net = new convnetjs.Net(); + net.fromJSON(json); + trainer.learning_rate = 0.0001; + trainer.momentum = 0.9; + trainer.batch_size = 2; + trainer.l2_decay = 0.00001; + reset_all(); + }); +} + +var change_net = function() { + eval($("#newnet").val()); + reset_all(); +} diff --git a/demo/js/jquery-1.8.3.min.js b/demo/js/jquery-1.8.3.min.js new file mode 100644 index 0000000..3883779 --- /dev/null +++ b/demo/js/jquery-1.8.3.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/demo/js/npgmain.js b/demo/js/npgmain.js new file mode 100644 index 0000000..6ed6dec --- /dev/null +++ b/demo/js/npgmain.js @@ -0,0 +1,134 @@ +//Simple game engine +//Author: Andrej Karpathy +//License: BSD +//This function does all the boring canvas stuff. To use it, just create functions: +//update() gets called every frame +//draw() gets called every frame +//myinit() gets called once in beginning +//mouseClick(x, y) gets called on mouse click +//keyUp(keycode) gets called when key is released +//keyDown(keycode) gets called when key is pushed + +var canvas; +var ctx; +var WIDTH; +var HEIGHT; +var FPS; + +function drawBubble(x, y, w, h, radius) +{ + var r = x + w; + var b = y + h; + ctx.beginPath(); + ctx.strokeStyle="black"; + ctx.lineWidth="2"; + ctx.moveTo(x+radius, y); + ctx.lineTo(x+radius/2, y-10); + ctx.lineTo(x+radius * 2, y); + ctx.lineTo(r-radius, y); + ctx.quadraticCurveTo(r, y, r, y+radius); + ctx.lineTo(r, y+h-radius); + ctx.quadraticCurveTo(r, b, r-radius, b); + ctx.lineTo(x+radius, b); + ctx.quadraticCurveTo(x, b, x, b-radius); + ctx.lineTo(x, y+radius); + ctx.quadraticCurveTo(x, y, x+radius, y); + ctx.stroke(); +} + +function drawRect(x, y, w, h){ + ctx.beginPath(); + ctx.rect(x,y,w,h); + ctx.closePath(); + ctx.fill(); + ctx.stroke(); +} + +function drawCircle(x, y, r){ + ctx.beginPath(); + ctx.arc(x, y, r, 0, Math.PI*2, true); + ctx.closePath(); + ctx.stroke(); + ctx.fill(); +} + +//uniform distribution integer +function randi(s, e) { + return Math.floor(Math.random()*(e-s) + s); +} + +//uniform distribution +function randf(s, e) { + return Math.random()*(e-s) + s; +} + +//normal distribution random number +function randn(mean, variance) { + var V1, V2, S; + do { + var U1 = Math.random(); + var U2 = Math.random(); + V1 = 2 * U1 - 1; + V2 = 2 * U2 - 1; + S = V1 * V1 + V2 * V2; + } while (S > 1); + X = Math.sqrt(-2 * Math.log(S) / S) * V1; + X = mean + Math.sqrt(variance) * X; + return X; +} + +function eventClick(e) { + + //get position of cursor relative to top left of canvas + var x; + var y; + if (e.pageX || e.pageY) { + x = e.pageX; + y = e.pageY; + } else { + x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; + y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; + } + x -= canvas.offsetLeft; + y -= canvas.offsetTop; + + //call user-defined callback + mouseClick(x, y, e.shiftKey, e.ctrlKey); +} + +//event codes can be found here: +//http://www.aspdotnetfaq.com/Faq/What-is-the-list-of-KeyCodes-for-JavaScript-KeyDown-KeyPress-and-KeyUp-events.aspx +function eventKeyUp(e) { + var keycode = ('which' in e) ? e.which : e.keyCode; + keyUp(keycode); +} + +function eventKeyDown(e) { + var keycode = ('which' in e) ? e.which : e.keyCode; + keyDown(keycode); +} + +function NPGinit(FPS){ + //takes frames per secont to run at + + canvas = document.getElementById('NPGcanvas'); + ctx = canvas.getContext('2d'); + WIDTH = canvas.width; + HEIGHT = canvas.height; + canvas.addEventListener('click', eventClick, false); + + //canvas element cannot get focus by default. Requires to either set + //tabindex to 1 so that it's focusable, or we need to attach listeners + //to the document. Here we do the latter + document.addEventListener('keyup', eventKeyUp, true); + document.addEventListener('keydown', eventKeyDown, true); + + setInterval(NPGtick, 1000/FPS); + + myinit(); +} + +function NPGtick() { + update(); + draw(); +} diff --git a/demo/js/pica.js b/demo/js/pica.js new file mode 100644 index 0000000..b14319a --- /dev/null +++ b/demo/js/pica.js @@ -0,0 +1,709 @@ +/* pica 1.0.7 nodeca/pica */!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.pica=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= bkHalf && y >= bkHalf && x + bkHalf < srcW && y + bkHalf < srcH) { + for (by = 0; by < 3; by++) { + for (bx = 0; bx < 3; bx++) { + sx = x + bx - bkHalf; + sy = y + by - bkHalf; + + br += gs[sx + sy * srcW] * blurKernel[bPtr++]; + } + } + return (br - (br % _bkWsum)) / _bkWsum; + } + + for (by = 0; by < 3; by++) { + for (bx = 0; bx < 3; bx++) { + sx = x + bx - bkHalf; + sy = y + by - bkHalf; + + if (sx >= 0 && sx < srcW && sy >= 0 && sy < srcH) { + w = blurKernel[bPtr]; + wsum += w; + br += gs[sx + sy * srcW] * w; + } + bPtr++; + } + } + return ((br - (br % wsum)) / wsum)|0; +} + +function blur(src, srcW, srcH/*, radius*/) { + var x, y, + output = new Uint16Array(src.length); + + for (x = 0; x < srcW; x++) { + for (y = 0; y < srcH; y++) { + output[y * srcW + x] = blurPoint(src, x, y, srcW, srcH); + } + } + + return output; +} + +module.exports = blur; + +},{}],2:[function(require,module,exports){ +// High speed resize with tuneable speed/quality ratio + +'use strict'; + + +var unsharp = require('./unsharp'); + + +// Precision of fixed FP values +var FIXED_FRAC_BITS = 14; +var FIXED_FRAC_VAL = 1 << FIXED_FRAC_BITS; + + +// +// Presets for quality 0..3. Filter functions + window size +// +var FILTER_INFO = [ + { // Nearest neibor (Box) + win: 0.5, + filter: function (x) { + return (x >= -0.5 && x < 0.5) ? 1.0 : 0.0; + } + }, + { // Hamming + win: 1.0, + filter: function (x) { + if (x <= -1.0 || x >= 1.0) { return 0.0; } + if (x > -1.19209290E-07 && x < 1.19209290E-07) { return 1.0; } + var xpi = x * Math.PI; + return ((Math.sin(xpi) / xpi) * (0.54 + 0.46 * Math.cos(xpi / 1.0))); + } + }, + { // Lanczos, win = 2 + win: 2.0, + filter: function (x) { + if (x <= -2.0 || x >= 2.0) { return 0.0; } + if (x > -1.19209290E-07 && x < 1.19209290E-07) { return 1.0; } + var xpi = x * Math.PI; + return (Math.sin(xpi) / xpi) * Math.sin(xpi / 2.0) / (xpi / 2.0); + } + }, + { // Lanczos, win = 3 + win: 3.0, + filter: function (x) { + if (x <= -3.0 || x >= 3.0) { return 0.0; } + if (x > -1.19209290E-07 && x < 1.19209290E-07) { return 1.0; } + var xpi = x * Math.PI; + return (Math.sin(xpi) / xpi) * Math.sin(xpi / 3.0) / (xpi / 3.0); + } + } +]; + +function clampTo8(i) { return i < 0 ? 0 : (i > 255 ? 255 : i); } + +function toFixedPoint(num) { return Math.floor(num * FIXED_FRAC_VAL); } + + +// Calculate convolution filters for each destination point, +// and pack data to Int16Array: +// +// [ shift, length, data..., shift2, length2, data..., ... ] +// +// - shift - offset in src image +// - length - filter length (in src points) +// - data - filter values sequence +// +function createFilters(quality, srcSize, destSize) { + + var filterFunction = FILTER_INFO[quality].filter; + + var scale = destSize / srcSize; + var scaleInverted = 1.0 / scale; + var scaleClamped = Math.min(1.0, scale); // For upscale + + // Filter window (averaging interval), scaled to src image + var srcWindow = FILTER_INFO[quality].win / scaleClamped; + + var destPixel, srcPixel, srcFirst, srcLast, filterElementSize, + floatFilter, fxpFilter, total, fixedTotal, pxl, idx, floatVal, fixedVal; + var leftNotEmpty, rightNotEmpty, filterShift, filterSize; + + var maxFilterElementSize = Math.floor((srcWindow + 1) * 2 ); + var packedFilter = new Int16Array((maxFilterElementSize + 2) * destSize); + var packedFilterPtr = 0; + + // For each destination pixel calculate source range and built filter values + for (destPixel = 0; destPixel < destSize; destPixel++) { + + // Scaling should be done relative to central pixel point + srcPixel = (destPixel + 0.5) * scaleInverted; + + srcFirst = Math.max(0, Math.floor(srcPixel - srcWindow)); + srcLast = Math.min(srcSize - 1, Math.ceil(srcPixel + srcWindow)); + + filterElementSize = srcLast - srcFirst + 1; + floatFilter = new Float32Array(filterElementSize); + fxpFilter = new Int16Array(filterElementSize); + + total = 0.0; + + // Fill filter values for calculated range + for (pxl = srcFirst, idx = 0; pxl <= srcLast; pxl++, idx++) { + floatVal = filterFunction(((pxl + 0.5) - srcPixel) * scaleClamped); + total += floatVal; + floatFilter[idx] = floatVal; + } + + // Normalize filter, convert to fixed point and accumulate conversion error + fixedTotal = 0; + + for (idx = 0; idx < floatFilter.length; idx++) { + fixedVal = toFixedPoint(floatFilter[idx] / total); + fixedTotal += fixedVal; + fxpFilter[idx] = fixedVal; + } + + // Compensate normalization error, to minimize brightness drift + fxpFilter[destSize >> 1] += toFixedPoint(1.0) - fixedTotal; + + // + // Now pack filter to useable form + // + // 1. Trim heading and tailing zero values, and compensate shitf/length + // 2. Put all to single array in this format: + // + // [ pos shift, data length, value1, value2, value3, ... ] + // + + leftNotEmpty = 0; + while (leftNotEmpty < fxpFilter.length && fxpFilter[leftNotEmpty] === 0) { + leftNotEmpty++; + } + + if (leftNotEmpty < fxpFilter.length) { + rightNotEmpty = fxpFilter.length - 1; + while (rightNotEmpty > 0 && fxpFilter[rightNotEmpty] === 0) { + rightNotEmpty--; + } + + filterShift = srcFirst + leftNotEmpty; + filterSize = rightNotEmpty - leftNotEmpty + 1; + + packedFilter[packedFilterPtr++] = filterShift; // shift + packedFilter[packedFilterPtr++] = filterSize; // size + + packedFilter.set(fxpFilter.subarray(leftNotEmpty, rightNotEmpty + 1), packedFilterPtr); + packedFilterPtr += filterSize; + } else { + // zero data, write header only + packedFilter[packedFilterPtr++] = 0; // shift + packedFilter[packedFilterPtr++] = 0; // size + } + } + return packedFilter; +} + +// Convolve image in horizontal directions and transpose output. In theory, +// transpose allow: +// +// - use the same convolver for both passes (this fails due different +// types of input array and temporary buffer) +// - making vertical pass by horisonltal lines inprove CPU cache use. +// +// But in real life this doesn't work :) +// +function convolveHorizontally(src, dest, srcW, srcH, destW, filters) { + + var r, g, b, a; + var filterPtr, filterShift, filterSize; + var srcPtr, srcY, destX, filterVal; + var srcOffset = 0, destOffset = 0; + + // For each row + for (srcY = 0; srcY < srcH; srcY++) { + filterPtr = 0; + + // Apply precomputed filters to each destination row point + for (destX = 0; destX < destW; destX++) { + // Get the filter that determines the current output pixel. + filterShift = filters[filterPtr++]; + filterSize = filters[filterPtr++]; + + srcPtr = (srcOffset + (filterShift * 4))|0; + + r = g = b = a = 0; + + // Apply the filter to the row to get the destination pixel r, g, b, a + for (; filterSize > 0; filterSize--) { + filterVal = filters[filterPtr++]; + + // Use reverse order to workaround deopts in old v8 (node v.10) + // Big thanks to @mraleph (Vyacheslav Egorov) for the tip. + a = (a + filterVal * src[srcPtr + 3])|0; + b = (b + filterVal * src[srcPtr + 2])|0; + g = (g + filterVal * src[srcPtr + 1])|0; + r = (r + filterVal * src[srcPtr])|0; + srcPtr = (srcPtr + 4)|0; + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with FIXED_FRAC_BITS bits of fractional part. + dest[destOffset + 3] = clampTo8(a >> 14/*FIXED_FRAC_BITS*/); + dest[destOffset + 2] = clampTo8(b >> 14/*FIXED_FRAC_BITS*/); + dest[destOffset + 1] = clampTo8(g >> 14/*FIXED_FRAC_BITS*/); + dest[destOffset] = clampTo8(r >> 14/*FIXED_FRAC_BITS*/); + destOffset = (destOffset + srcH * 4)|0; + } + + destOffset = ((srcY + 1) * 4)|0; + srcOffset = ((srcY + 1) * srcW * 4)|0; + } +} + +// Technically, convolvers are the same. But input array and temporary +// buffer can be of different type (especially, in old browsers). So, +// keep code in separate functions to avoid deoptimizations & speed loss. + +function convolveVertically(src, dest, srcW, srcH, destW, filters) { + + var r, g, b, a; + var filterPtr, filterShift, filterSize; + var srcPtr, srcY, destX, filterVal; + var srcOffset = 0, destOffset = 0; + + // For each row + for (srcY = 0; srcY < srcH; srcY++) { + filterPtr = 0; + + // Apply precomputed filters to each destination row point + for (destX = 0; destX < destW; destX++) { + // Get the filter that determines the current output pixel. + filterShift = filters[filterPtr++]; + filterSize = filters[filterPtr++]; + + srcPtr = (srcOffset + (filterShift * 4))|0; + + r = g = b = a = 0; + + // Apply the filter to the row to get the destination pixel r, g, b, a + for (; filterSize > 0; filterSize--) { + filterVal = filters[filterPtr++]; + + // Use reverse order to workaround deopts in old v8 (node v.10) + // Big thanks to @mraleph (Vyacheslav Egorov) for the tip. + a = (a + filterVal * src[srcPtr + 3])|0; + b = (b + filterVal * src[srcPtr + 2])|0; + g = (g + filterVal * src[srcPtr + 1])|0; + r = (r + filterVal * src[srcPtr])|0; + srcPtr = (srcPtr + 4)|0; + } + + // Bring this value back in range. All of the filter scaling factors + // are in fixed point with FIXED_FRAC_BITS bits of fractional part. + dest[destOffset + 3] = clampTo8(a >> 14/*FIXED_FRAC_BITS*/); + dest[destOffset + 2] = clampTo8(b >> 14/*FIXED_FRAC_BITS*/); + dest[destOffset + 1] = clampTo8(g >> 14/*FIXED_FRAC_BITS*/); + dest[destOffset] = clampTo8(r >> 14/*FIXED_FRAC_BITS*/); + destOffset = (destOffset + srcH * 4)|0; + } + + destOffset = ((srcY + 1) * 4)|0; + srcOffset = ((srcY + 1) * srcW * 4)|0; + } +} + + +function resetAlpha(dst, width, height) { + var ptr = 3, len = (width * height * 4)|0; + while (ptr < len) { dst[ptr] = 0xFF; ptr = (ptr + 4)|0; } +} + + +function resize(options) { + var src = options.src; + var srcW = options.width; + var srcH = options.height; + var destW = options.toWidth; + var destH = options.toHeight; + var dest = options.dest || new Uint8Array(destW * destH * 4); + var quality = options.quality === undefined ? 3 : options.quality; + var alpha = options.alpha || false; + var unsharpAmount = options.unsharpAmount === undefined ? 0 : (options.unsharpAmount|0); + var unsharpThreshold = options.unsharpThreshold === undefined ? 0 : (options.unsharpThreshold|0); + + if (srcW < 1 || srcH < 1 || destW < 1 || destH < 1) { return []; } + + var filtersX = createFilters(quality, srcW, destW), + filtersY = createFilters(quality, srcH, destH); + + var tmp = new Uint8Array(destW * srcH * 4); + + // To use single function we need src & tmp of the same type. + // But src can be CanvasPixelArray, and tmp - Uint8Array. So, keep + // vertical and horizontal passes separately to avoid deoptimization. + + convolveHorizontally(src, tmp, srcW, srcH, destW, filtersX); + convolveVertically(tmp, dest, srcH, destW, destH, filtersY); + + // That's faster than doing checks in convolver. + // !!! Note, canvas data is not premultipled. We don't need other + // alpha corrections. + + if (!alpha) { + resetAlpha(dest, destW, destH); + } + + if (unsharpAmount) { + unsharp(dest, destW, destH, unsharpAmount, 1.0, unsharpThreshold); + } + + return dest; +} + + +module.exports = resize; + +},{"./unsharp":3}],3:[function(require,module,exports){ +// Unsharp mask filter +// +// http://stackoverflow.com/a/23322820/1031804 +// USM(O) = O + (2 * (Amount / 100) * (O - GB)) +// GB - gaussial blur. +// +// brightness = 0.299*R + 0.587*G + 0.114*B +// http://stackoverflow.com/a/596243/1031804 +// +// To simplify math, normalize brighness mutipliers to 2^16: +// +// brightness = (19595*R + 38470*G + 7471*B) / 65536 + +'use strict'; + + +var blur = require('./blur'); + + +function clampTo8(i) { return i < 0 ? 0 : (i > 255 ? 255 : i); } + +// Convert image to greyscale, 16bits FP result (8.8) +// +function greyscale(src, srcW, srcH) { + var size = srcW * srcH; + var result = new Uint16Array(size); // We don't use sign, but that helps to JIT + var i, srcPtr; + + for (i = 0, srcPtr = 0; i < size; i++) { + result[i] = (src[srcPtr + 2] * 7471 // blue + + src[srcPtr + 1] * 38470 // green + + src[srcPtr] * 19595) >>> 8; // red + srcPtr = (srcPtr + 4)|0; + } + + return result; +} + + +// Apply unsharp mask to src +// +// NOTE: radius is ignored to simplify gaussian blur calculation +// on practice we need radius 0.3..2.0. Use 1.0 now. +// +function unsharp(src, srcW, srcH, amount, radius, threshold) { + var x, y, c, diff = 0, corr, srcPtr; + + // Normalized delta multiplier. Expect that: + var AMOUNT_NORM = Math.floor(amount * 256 / 50); + + // Convert to grayscale: + // + // - prevent color drift + // - speedup blur calc + // + var gs = greyscale(src, srcW, srcH); + var blured = blur(gs, srcW, srcH, 1); + var fpThreshold = threshold << 8; + var gsPtr = 0; + + for (y = 0; y < srcH; y++) { + for (x = 0; x < srcW; x++) { + + // calculate brightness blur, difference & update source buffer + + diff = gs[gsPtr] - blured[gsPtr]; + + // Update source image if thresold exceeded + if (Math.abs(diff) > fpThreshold) { + // Calculate correction multiplier + corr = 65536 + ((diff * AMOUNT_NORM) >> 8); + srcPtr = gsPtr * 4; + + c = src[srcPtr]; + src[srcPtr++] = clampTo8((c * corr) >> 16); + c = src[srcPtr]; + src[srcPtr++] = clampTo8((c * corr) >> 16); + c = src[srcPtr]; + src[srcPtr] = clampTo8((c * corr) >> 16); + } + + gsPtr++; + + } // end row + } // end column +} + + +module.exports = unsharp; + +},{"./blur":1}],4:[function(require,module,exports){ +// Proxy to simplify split between webworker/plain calls +'use strict'; + +var resize = require('./pure/resize'); + +module.exports = function (options, callback) { + var output = resize(options); + + callback(null, output); +}; + +},{"./pure/resize":2}],5:[function(require,module,exports){ +// Web Worker wrapper for image resize function + +'use strict'; + +module.exports = function(self) { + var resize = require('./resize'); + + self.onmessage = function (ev) { + resize(ev.data, function(err, output) { + if (err) { + self.postMessage({ err: err }); + return; + } + + self.postMessage({ output: output }, [ output.buffer ]); + }); + }; +}; + +},{"./resize":4}],6:[function(require,module,exports){ +var bundleFn = arguments[3]; +var sources = arguments[4]; +var cache = arguments[5]; + +var stringify = JSON.stringify; + +module.exports = function (fn) { + var keys = []; + var wkey; + var cacheKeys = Object.keys(cache); + + for (var i = 0, l = cacheKeys.length; i < l; i++) { + var key = cacheKeys[i]; + if (cache[key].exports === fn) { + wkey = key; + break; + } + } + + if (!wkey) { + wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); + var wcache = {}; + for (var i = 0, l = cacheKeys.length; i < l; i++) { + var key = cacheKeys[i]; + wcache[key] = key; + } + sources[wkey] = [ + Function(['require','module','exports'], '(' + fn + ')(self)'), + wcache + ]; + } + var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); + + var scache = {}; scache[wkey] = wkey; + sources[skey] = [ + Function(['require'],'require(' + stringify(wkey) + ')(self)'), + scache + ]; + + var src = '(' + bundleFn + ')({' + + Object.keys(sources).map(function (key) { + return stringify(key) + ':[' + + sources[key][0] + + ',' + stringify(sources[key][1]) + ']' + ; + }).join(',') + + '},{},[' + stringify(skey) + '])' + ; + return new Worker(window.URL.createObjectURL( + new Blob([src], { type: 'text/javascript' }) + )); +}; + +},{}]},{},[])("./") +}); \ No newline at end of file diff --git a/demo/js/regression.js b/demo/js/regression.js new file mode 100644 index 0000000..807fc69 --- /dev/null +++ b/demo/js/regression.js @@ -0,0 +1,159 @@ + +var N, data, labels; +var ss = 30.0; // scale for drawing + +var layer_defs, net, trainer; + +// create neural net +var t = "layer_defs = [];\n\ +layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth:1});\n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'relu'});\n\ +layer_defs.push({type:'fc', num_neurons:20, activation:'sigmoid'});\n\ +layer_defs.push({type:'regression', num_neurons:1});\n\ +\n\ +net = new convnetjs.Net();\n\ +net.makeLayers(layer_defs);\n\ +\n\ +trainer = new convnetjs.SGDTrainer(net, {learning_rate:0.01, momentum:0.0, batch_size:1, l2_decay:0.001});\n\ +"; + +var lix=2; // layer id of layer we'd like to draw outputs of +function reload() { + eval($("#layerdef").val()); + + // refresh buttons + var t = ''; + for(var i=1;i"; + } + $("#layer_ixes").html(t); + $("#button"+lix).css('background-color', '#FFA'); +} +function updateLix(newlix) { + $("#button"+lix).css('background-color', ''); // erase highlight + lix = newlix; + $("#button"+lix).css('background-color', '#FFA'); +} + +function regen_data() { + N = parseInt($("#num_data").val()); + data = []; + labels = []; + for(var i=0;i0.0&&ua<1.0&&ub>0.0&&ub<1.0) { + var up = new Vec(p1.x+ua*(p2.x-p1.x), p1.y+ua*(p2.y-p1.y)); + return {ua:ua, ub:ub, up:up}; // up is intersection point + } + return false; + } + + var line_point_intersect = function(p1,p2,p0,rad) { + var v = new Vec(p2.y-p1.y,-(p2.x-p1.x)); // perpendicular vector + var d = Math.abs((p2.x-p1.x)*(p1.y-p0.y)-(p1.x-p0.x)*(p2.y-p1.y)); + d = d / v.length(); + if(d > rad) { return false; } + + v.normalize(); + v.scale(d); + var up = p0.add(v); + if(Math.abs(p2.x-p1.x)>Math.abs(p2.y-p1.y)) { + var ua = (up.x - p1.x) / (p2.x - p1.x); + } else { + var ua = (up.y - p1.y) / (p2.y - p1.y); + } + if(ua>0.0&&ua<1.0) { + return {ua:ua, up:up}; + } + return false; + } + + // Wall is made up of two points + var Wall = function(p1, p2) { + this.p1 = p1; + this.p2 = p2; + } + + // World object contains many agents and walls and food and stuff + var util_add_box = function(lst, x, y, w, h) { + lst.push(new Wall(new Vec(x,y), new Vec(x+w,y))); + lst.push(new Wall(new Vec(x+w,y), new Vec(x+w,y+h))); + lst.push(new Wall(new Vec(x+w,y+h), new Vec(x,y+h))); + lst.push(new Wall(new Vec(x,y+h), new Vec(x,y))); + } + + // item is circle thing on the floor that agent can interact with (see or eat, etc) + var Item = function(x, y, type) { + this.p = new Vec(x, y); // position + this.type = type; + this.rad = 10; // default radius + this.age = 0; + this.cleanup_ = false; + } + + var World = function() { + this.agents = []; + this.W = canvas.width; + this.H = canvas.height; + + this.clock = 0; + + // set up walls in the world + this.walls = []; + var pad = 10; + util_add_box(this.walls, pad, pad, this.W-pad*2, this.H-pad*2); + util_add_box(this.walls, 100, 100, 200, 300); // inner walls + this.walls.pop(); + util_add_box(this.walls, 400, 100, 200, 300); + this.walls.pop(); + + // set up food and poison + this.items = [] + for(var k=0;k<30;k++) { + var x = convnetjs.randf(20, this.W-20); + var y = convnetjs.randf(20, this.H-20); + var t = convnetjs.randi(1, 3); // food or poison (1 and 2) + var it = new Item(x, y, t); + this.items.push(it); + } + } + + World.prototype = { + // helper function to get closest colliding walls/items + stuff_collide_: function(p1, p2, check_walls, check_items) { + var minres = false; + + // collide with walls + if(check_walls) { + for(var i=0,n=this.walls.length;ieyep + var eyep = new Vec(a.p.x + e.max_range * Math.sin(a.angle + e.angle), + a.p.y + e.max_range * Math.cos(a.angle + e.angle)); + var res = this.stuff_collide_(a.p, eyep, true, true); + if(res) { + // eye collided with wall + e.sensed_proximity = res.up.dist_from(a.p); + e.sensed_type = res.type; + } else { + e.sensed_proximity = e.max_range; + e.sensed_type = -1; + } + } + } + + // let the agents behave in the world based on their input + for(var i=0,n=this.agents.length;i2*Math.PI)a.angle-=2*Math.PI; + + // agent is trying to move from p to op. Check walls + var res = this.stuff_collide_(a.op, a.p, true, false); + if(res) { + // wall collision! reset position + a.p = a.op; + } + + // handle boundary conditions + if(a.p.x<0)a.p.x=0; + if(a.p.x>this.W)a.p.x=this.W; + if(a.p.y<0)a.p.y=0; + if(a.p.y>this.H)a.p.y=this.H; + } + + // tick all items + var update_items = false; + for(var i=0,n=this.items.length;i 5000 && this.clock % 100 === 0 && convnetjs.randf(0,1)<0.1) { + it.cleanup_ = true; // replace this one, has been around too long + update_items = true; + } + } + if(update_items) { + var nt = []; + for(var i=0,n=this.items.length;i 0.75) forward_reward = 0.1 * proximity_reward; + + // agents like to eat good things + var digestion_reward = this.digestion_signal; + this.digestion_signal = 0.0; + + var reward = proximity_reward + forward_reward + digestion_reward; + + // pass to brain for learning + this.brain.backward(reward); + } + } + + function draw_net() { + if(simspeed <=1) { + // we will always draw at these speeds + } else { + if(w.clock % 50 !== 0) return; // do this sparingly + } + + var canvas = document.getElementById("net_canvas"); + var ctx = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + ctx.clearRect(0, 0, canvas.width, canvas.height); + var L = w.agents[0].brain.value_net.layers; + var dx = (W - 50)/L.length; + var x = 10; + var y = 40; + ctx.font="12px Verdana"; + ctx.fillStyle = "rgb(0,0,0)"; + ctx.fillText("Value Function Approximating Neural Network:", 10, 14); + for(var k=0;k= 0) ctx.fillStyle = "rgb(0,0," + v + ")"; + if(v < 0) ctx.fillStyle = "rgb(" + (-v) + ",0,0)"; + ctx.fillRect(x,y,10,10); + y += 12; + if(y>H-25) { y = 40; x += 12}; + } + x += 50; + y = 40; + } + } + + var reward_graph = new cnnvis.Graph(); + function draw_stats() { + var canvas = document.getElementById("vis_canvas"); + var ctx = canvas.getContext("2d"); + var W = canvas.width; + var H = canvas.height; + ctx.clearRect(0, 0, canvas.width, canvas.height); + var a = w.agents[0]; + var b = a.brain; + var netin = b.last_input_array; + ctx.strokeStyle = "rgb(0,0,0)"; + //ctx.font="12px Verdana"; + //ctx.fillText("Current state:",10,10); + ctx.lineWidth = 10; + ctx.beginPath(); + for(var k=0,n=netin.length;k255)r=255;if(r<0)r=0; + ctx.fillStyle = "rgb(" + r + ", 150, 150)"; + ctx.strokeStyle = "rgb(0,0,0)"; + for(var i=0,n=agents.length;i0) { + for(var i=0;i + + + + + ConvNetJS MNIST demo + + + + + + + + + + + + + + + + + + +
+

ConvNetJS MNIST demo

+

Description

+

+ This demo trains a Convolutional Neural Network on the MNIST digits dataset in your browser, with nothing but Javascript. The dataset is fairly easy and one should expect to get somewhere around 99% accuracy within few minutes. I used this python script to parse the original files into batches of images that can be easily loaded into page DOM with img tags. +

+

+ This network takes a 28x28 MNIST image and crops a random 24x24 window before training on it (this technique is called data augmentation and improves generalization). Similarly to do prediction, 4 random crops are sampled and the probabilities across all crops are averaged to produce final predictions. The network runs at about 5ms for both forward and backward pass on my reasonably decent Ubuntu+Chrome machine. +

+

+ By default, in this demo we're using Adadelta which is one of per-parameter adaptive step size methods, so we don't have to worry about changing learning rates or momentum over time. However, I still included the text fields for changing these if you'd like to play around with SGD+Momentum trainer. +

+

Report questions/bugs/suggestions to @karpathy.

+

Training Stats

+
+
+ +
+ +
+ Learning rate: + +
+ + Momentum: + +
+ + Batch size: + +
+ + Weight decay: + +
+ +
+
+ +
+
+
+ Loss:
+ + +
+ +
+
+
+
+ Test an image from your computer: +
+ +
+ + + +
+
+
+
+ +

Instantiate a Network and Trainer

+
+
+ +
+ +
+

Network Visualization

+
+
+ +
+

Example predictions on Test set

+
+
+
+ +
+ + + + + diff --git a/demo/regression.html b/demo/regression.html new file mode 100644 index 0000000..de7e13c --- /dev/null +++ b/demo/regression.html @@ -0,0 +1,74 @@ + + +ConvNetJS demo: Classify toy 2D data + + + + + + + + + + + + + + + + +
+

ConvnetJS demo: toy 1d regression

+ +

The simulation below is a 1-dimensional regression where a neural network is trained to regress to y coordinates for every given point x through an L2 loss. That is, the minimized cost function computes the squared difference between the predicted y-coordinate and the "correct" y coordinate. Every 10th of a second, all points are fed to the network multiple times through the trainer class to train the network.

+ +

The simulation below will eval() whatever you have in the text area and reload. Feel free to explore and use ConvNetJS to instantiate your own network!

+ +

Report questions/bugs/suggestions to @karpathy.

+ + +

+ + +
+Number of points to generate: + +
+ +
+

Add data points by clicking!

+
+ +Also draw outputs of a layer (click layer button below) in red. +
+ +Browser not supported for Canvas. Get a real browser. + +

Go back to ConvNetJS

+ +
+ + + + diff --git a/demo/rldemo.html b/demo/rldemo.html new file mode 100644 index 0000000..dac859f --- /dev/null +++ b/demo/rldemo.html @@ -0,0 +1,149 @@ + + + + + + ConvNetJS Deep Q Learning Reinforcement Learning with Neural Network demo + + + + + + + + + + + + + + + +
+

ConvNetJS Deep Q Learning Demo

+

Description

+

+ This demo follows the description of the Deep Q Learning algorithm described in + Playing Atari with Deep Reinforcement Learning, + a paper from NIPS 2013 Deep Learning Workshop from DeepMind. The paper is a nice demo of a fairly + standard (model-free) Reinforcement Learning algorithm (Q Learning) learning to play Atari games. +

+

+ In this demo, instead of Atari games, we'll start out with something more simple: + a 2D agent that has 9 eyes pointing in different angles ahead and every eye senses 3 values + along its direction (up to a certain maximum visibility distance): distance to a wall, distance to + a green thing, or distance to a red thing. The agent navigates by using one of 5 actions that turn + it different angles. The red things are apples and the agent gets reward for eating them. The green + things are poison and the agent gets negative reward for eating them. The training takes a few tens + of minutes with current parameter settings. +

+

+ Over time, the agent learns to avoid states that lead to states with low rewards, and picks actions + that lead to better states instead. +

+

Q-Learner full specification and options

+

+ The textfield below gets eval()'d to produce the Q-learner for this demo. This allows you to fiddle with + various parameters and settings and also shows how you can use the API for your own purposes. + All of these settings are optional but are listed to give an idea of possibilities. + Feel free to change things around and hit reload! Documentation for all + options is the paper linked to above, and there are also + comments for every option in the source code javascript file. +

+ + + +

Q-Learner API

+

It's very simple to use deeqlearn.Brain: Initialize your network:

+
+   var brain = new deepqlearn.Brain(num_inputs, num_actions);
+   
+

And to train it proceed in loops as follows:

+
+   var action = brain.forward(array_with_num_inputs_numbers);
+   // action is a number in [0, num_actions) telling index of the action the agent chooses
+   // here, apply the action on environment and observe some reward. Finally, communicate it:
+   brain.backward(reward); // <-- learning magic happens here
+   
+

That's it! Let the agent learn over time (it will take opt.learning_steps_total), and it + will only get better and better at accumulating reward as it learns. Note that the agent will still take + random actions with probability opt.epsilon_min even once it's fully trained. + To completely disable this randomness, or change it, you can disable the learning and set epsilon_test_time to 0:

+
+   brain.epsilon_test_time = 0.0; // don't make any random choices, ever
+   brain.learning = false;
+   var action = brain.forward(array_with_num_inputs_numbers); // get optimal action from learned policy
+   
+ +

State Visualizations

+ +
Left: Current input state (quite a useless thing to look at). Right: Average reward over time (this should go up as agent becomes better on average at collecting rewards)
+ +
+
+ +
+ (Takes ~10 minutes to train with current settings. If you're impatient, scroll down and load an example pre-trained network from pre-filled JSON) +
+ +
+ +

Controls

+ + + +
+ + + +

I/O

+

+ You can save and load a network from JSON here. Note that the textfield is prefilled with a + pretrained network that works reasonable well, if you're impatient to let yours train enough. + Just hit the load button! +

+ + + +
+ +
+ + diff --git a/demo/speedtest.html b/demo/speedtest.html new file mode 100644 index 0000000..9046a47 --- /dev/null +++ b/demo/speedtest.html @@ -0,0 +1,55 @@ + + + + + + Simple speed test + + + + + + + + + + +
+ + + + + diff --git a/demo/trainers.html b/demo/trainers.html new file mode 100644 index 0000000..50e9694 --- /dev/null +++ b/demo/trainers.html @@ -0,0 +1,47 @@ + + + + + + ConvNetJS Trainer Comparison on MNIST + + + + + + + + + + + + + +
+

ConvNetJS Trainer demo on MNIST

+

Description

+

+ This demo lets you evaluate multiple trainers against each other on MNIST. By default I've set up a little benchmark that puts SGD/SGD with momentum/Adam/Adagrad/Adadelta/Nesterov against each other. For reference math and explanations on these refer to Matthew Zeiler's Adadelta paper (Windowgrad is Idea #1 in the paper). In my own experience, Adagrad/Adadelta are "safer" because they don't depend so strongly on setting of learning rates (with Adadelta being slightly better), but well-tuned SGD+Momentum almost always converges faster and at better final values. +

+

Report questions/bugs/suggestions to @karpathy.

+ + +

+ + +

Loss vs. Number of examples seen

+ + +

Testing Accuracy vs. Number of examples seen

+ + +

Training Accuracy vs. Number of examples seen

+ + +
+ + + + + diff --git a/src/convnet_export.js b/src/convnet_export.js new file mode 100644 index 0000000..5d3ffb8 --- /dev/null +++ b/src/convnet_export.js @@ -0,0 +1,8 @@ +(function(lib) { + "use strict"; + if (typeof module === "undefined" || typeof module.exports === "undefined") { + window.convnetjs = lib; // in ordinary browser attach library to window + } else { + module.exports = lib; // in nodejs + } +})(convnetjs); diff --git a/src/convnet_init.js b/src/convnet_init.js new file mode 100644 index 0000000..5739e78 --- /dev/null +++ b/src/convnet_init.js @@ -0,0 +1 @@ +var convnetjs = convnetjs || { REVISION: 'ALPHA' }; diff --git a/src/convnet_layers_dotproducts.js b/src/convnet_layers_dotproducts.js new file mode 100644 index 0000000..67f3f02 --- /dev/null +++ b/src/convnet_layers_dotproducts.js @@ -0,0 +1,275 @@ +(function(global) { + "use strict"; + var Vol = global.Vol; // convenience + + // This file contains all layers that do dot products with input, + // but usually in a different connectivity pattern and weight sharing + // schemes: + // - FullyConn is fully connected dot products + // - ConvLayer does convolutions (so weight sharing spatially) + // putting them together in one file because they are very similar + var ConvLayer = function(opt) { + var opt = opt || {}; + + // required + this.out_depth = opt.filters; + this.sx = opt.sx; // filter size. Should be odd if possible, it's cleaner. + this.in_depth = opt.in_depth; + this.in_sx = opt.in_sx; + this.in_sy = opt.in_sy; + + // optional + this.sy = typeof opt.sy !== 'undefined' ? opt.sy : this.sx; + this.stride = typeof opt.stride !== 'undefined' ? opt.stride : 1; // stride at which we apply filters to input volume + this.pad = typeof opt.pad !== 'undefined' ? opt.pad : 0; // amount of 0 padding to add around borders of input volume + this.l1_decay_mul = typeof opt.l1_decay_mul !== 'undefined' ? opt.l1_decay_mul : 0.0; + this.l2_decay_mul = typeof opt.l2_decay_mul !== 'undefined' ? opt.l2_decay_mul : 1.0; + + // computed + // note we are doing floor, so if the strided convolution of the filter doesnt fit into the input + // volume exactly, the output volume will be trimmed and not contain the (incomplete) computed + // final application. + this.out_sx = Math.floor((this.in_sx + this.pad * 2 - this.sx) / this.stride + 1); + this.out_sy = Math.floor((this.in_sy + this.pad * 2 - this.sy) / this.stride + 1); + this.layer_type = 'conv'; + + // initializations + var bias = typeof opt.bias_pref !== 'undefined' ? opt.bias_pref : 0.0; + this.filters = []; + for(var i=0;i=0 && oy=0 && ox=0 && oy=0 && ox amax) amax = as[i]; + } + + // compute exponentials (carefully to not blow up) + var es = global.zeros(this.out_depth); + var esum = 0.0; + for(var i=0;i 0) { + // violating dimension, apply loss + x.dw[i] += 1; + x.dw[y] -= 1; + loss += ydiff; + } + } + + return loss; + }, + getParamsAndGrads: function() { + return []; + }, + toJSON: function() { + var json = {}; + json.out_depth = this.out_depth; + json.out_sx = this.out_sx; + json.out_sy = this.out_sy; + json.layer_type = this.layer_type; + json.num_inputs = this.num_inputs; + return json; + }, + fromJSON: function(json) { + this.out_depth = json.out_depth; + this.out_sx = json.out_sx; + this.out_sy = json.out_sy; + this.layer_type = json.layer_type; + this.num_inputs = json.num_inputs; + } + } + + global.RegressionLayer = RegressionLayer; + global.SoftmaxLayer = SoftmaxLayer; + global.SVMLayer = SVMLayer; + +})(convnetjs); + diff --git a/src/convnet_layers_nonlinearities.js b/src/convnet_layers_nonlinearities.js new file mode 100644 index 0000000..2123d98 --- /dev/null +++ b/src/convnet_layers_nonlinearities.js @@ -0,0 +1,291 @@ +(function(global) { + "use strict"; + var Vol = global.Vol; // convenience + + // Implements ReLU nonlinearity elementwise + // x -> max(0, x) + // the output is in [0, inf) + var ReluLayer = function(opt) { + var opt = opt || {}; + + // computed + this.out_sx = opt.in_sx; + this.out_sy = opt.in_sy; + this.out_depth = opt.in_depth; + this.layer_type = 'relu'; + } + ReluLayer.prototype = { + forward: function(V, is_training) { + this.in_act = V; + var V2 = V.clone(); + var N = V.w.length; + var V2w = V2.w; + for(var i=0;i 1/(1+e^(-x)) + // so the output is between 0 and 1. + var SigmoidLayer = function(opt) { + var opt = opt || {}; + + // computed + this.out_sx = opt.in_sx; + this.out_sy = opt.in_sy; + this.out_depth = opt.in_depth; + this.layer_type = 'sigmoid'; + } + SigmoidLayer.prototype = { + forward: function(V, is_training) { + this.in_act = V; + var V2 = V.cloneAndZero(); + var N = V.w.length; + var V2w = V2.w; + var Vw = V.w; + for(var i=0;i max(x) + // where x is a vector of size group_size. Ideally of course, + // the input size should be exactly divisible by group_size + var MaxoutLayer = function(opt) { + var opt = opt || {}; + + // required + this.group_size = typeof opt.group_size !== 'undefined' ? opt.group_size : 2; + + // computed + this.out_sx = opt.in_sx; + this.out_sy = opt.in_sy; + this.out_depth = Math.floor(opt.in_depth / this.group_size); + this.layer_type = 'maxout'; + + this.switches = global.zeros(this.out_sx*this.out_sy*this.out_depth); // useful for backprop + } + MaxoutLayer.prototype = { + forward: function(V, is_training) { + this.in_act = V; + var N = this.out_depth; + var V2 = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); + + // optimization branch. If we're operating on 1D arrays we dont have + // to worry about keeping track of x,y,d coordinates inside + // input volumes. In convnets we do :( + if(this.out_sx === 1 && this.out_sy === 1) { + for(var i=0;i a) { + a = a2; + ai = j; + } + } + V2.w[i] = a; + this.switches[i] = ix + ai; + } + } else { + var n=0; // counter for switches + for(var x=0;x a) { + a = a2; + ai = j; + } + } + V2.set(x,y,i,a); + this.switches[n] = ix + ai; + n++; + } + } + } + + } + this.out_act = V2; + return this.out_act; + }, + backward: function() { + var V = this.in_act; // we need to set dw of this + var V2 = this.out_act; + var N = this.out_depth; + V.dw = global.zeros(V.w.length); // zero out gradient wrt data + + // pass the gradient through the appropriate switch + if(this.out_sx === 1 && this.out_sy === 1) { + for(var i=0;i tanh(x) + // so the output is between -1 and 1. + var TanhLayer = function(opt) { + var opt = opt || {}; + + // computed + this.out_sx = opt.in_sx; + this.out_sy = opt.in_sy; + this.out_depth = opt.in_depth; + this.layer_type = 'tanh'; + } + TanhLayer.prototype = { + forward: function(V, is_training) { + this.in_act = V; + var V2 = V.cloneAndZero(); + var N = V.w.length; + for(var i=0;i=0 && oy=0 && ox a) { a = v; winx=ox; winy=oy;} + } + } + } + this.switchx[n] = winx; + this.switchy[n] = winy; + n++; + A.set(ax, ay, d, a); + } + } + } + this.out_act = A; + return this.out_act; + }, + backward: function() { + // pooling layers have no parameters, so simply compute + // gradient wrt data here + var V = this.in_act; + V.dw = global.zeros(V.w.length); // zero out gradient wrt data + var A = this.out_act; // computed in forward pass + + var n = 0; + for(var d=0;d num_epochs * num_training_data + this.foldix = 0; // index of active fold + + // callbacks + this.finish_fold_callback = null; + this.finish_batch_callback = null; + + // initializations + if(this.data.length > 0) { + this.sampleFolds(); + this.sampleCandidates(); + } + }; + + MagicNet.prototype = { + + // sets this.folds to a sampling of this.num_folds folds + sampleFolds: function() { + var N = this.data.length; + var num_train = Math.floor(this.train_ratio * N); + this.folds = []; // flush folds, if any + for(var i=0;i