├── .gitignore ├── README.md ├── browser ├── js │ ├── convnet.js │ ├── jquery-1.8.3.min.js │ ├── mnist_labels.js │ ├── util.js │ └── vis.js ├── mnist.html └── mnist │ ├── mnist_batch_0.png │ ├── mnist_batch_1.png │ ├── mnist_batch_2.png │ ├── mnist_batch_20.png │ ├── mnist_batch_3.png │ ├── mnist_batch_4.png │ ├── mnist_batch_5.png │ ├── mnist_batch_6.png │ ├── mnist_batch_7.png │ └── mnist_batch_8.png └── static ├── package.json ├── public ├── cifar10.html ├── cifar10 │ ├── cifar10_batch_0.png │ └── cifar10_batch_50.png ├── css │ └── jquery-ui.min.css ├── image_regression.html ├── imgs │ ├── balls.jpg │ ├── battery.jpg │ ├── cat.jpg │ ├── chess.png │ ├── chip.jpg │ ├── debug.jpg │ ├── dora.jpg │ ├── earth.jpg │ ├── esher.png │ ├── fox.png │ ├── fractal.jpg │ ├── gradient.png │ ├── jitendra.jpg │ ├── lena.png │ ├── pencils.png │ ├── rainforest.jpg │ ├── reddit.jpg │ ├── rubiks.jpg │ ├── starry.jpg │ ├── tesla.jpg │ ├── twitter.png │ └── usa.png ├── js │ ├── cifar10_labels.js │ ├── convnet.js │ ├── jquery-1.8.3.min.js │ ├── jquery-ui.min.js │ ├── mnist_labels.js │ ├── util.js │ └── vis.js ├── mnist.html └── mnist │ ├── mnist_batch_0.png │ ├── mnist_batch_1.png │ ├── mnist_batch_2.png │ ├── mnist_batch_20.png │ ├── mnist_batch_3.png │ ├── mnist_batch_4.png │ ├── mnist_batch_5.png │ ├── mnist_batch_6.png │ ├── mnist_batch_7.png │ └── mnist_batch_8.png └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript and Artificial Intelligence 2 | 3 | Samples and demos of JavaScript Artificial Intelligence. 4 | 5 | AI is a broad term, but it is the better I known. 6 | 7 | The samples are for Node.js and for browser. The covered topics (Work in Progress): 8 | 9 | - Tree Evaluation 10 | - Neural Networks 11 | - Genetic Algorithms 12 | - Evolutionary Programming 13 | - Hybrid Approach 14 | - Machine and Deep Learning 15 | - Decision Trees 16 | 17 | ## Presentation 18 | 19 | - [Presentation Sources](https://github.com/ajlopez/Talks/JavaScriptAI) 20 | - [Presentation Online](http://ajlopez.github.io/Talks/JavaScriptAI) 21 | 22 | ## Projects 23 | 24 | - [ConvNetJs Deep Learning](http://cs.stanford.edu/people/karpathy/convnetjs/) 25 | - [SimpleNeuron Neural Networks](https://github.com/ajlopez/SimpleNeuron) 26 | - [SimpleGA Genetic Algorithms](https://github.com/ajlopez/SimpleGA) 27 | - [SimpleDT Decision Tress](https://github.com/ajlopez/SimpleDT) 28 | - [SimpleGammon Evaluation Backgammon](https://github.com/ajlopez/SimpleGammon) 29 | 30 | ## Resources 31 | 32 | - [Artificial Intelligence: A Modern Approach](http://aima.cs.berkeley.edu/) 33 | - [Computer Backgammon](http://www.bkgm.com/articles/Berliner/ComputerBackgammon/) 34 | - [Backgammon Computer Program Beats World Champion](http://www.bkgm.com/articles/Berliner/BackgammonProgramBeatsWorldChamp/) 35 | - [Machine Learning in Action](http://www.manning.com/pharrington/) with source code 36 | - [Binding Machine Learning and node.js](http://blog.bigml.com/2013/06/27/binding-machine-learning-and-node-js/) 37 | - [What is deep learning, and why should you care?](http://radar.oreilly.com/2014/07/what-is-deep-learning-and-why-should-you-care.html) 38 | - [ImageNet Classification with Deep Convolutional Neural Networks](http://www.cs.toronto.edu/~fritz/absps/imagenet.pdf) 39 | - [Dropout: A simple and effective way to improve neural networks](http://videolectures.net/nips2012_hinton_networks/) 40 | - [Rectifier (neural networks)](http://en.wikipedia.org/wiki/Rectifier_(neural_networks)) 41 | 42 | ## Datasets 43 | 44 | - [Weka Collections of Datasets](http://www.cs.waikato.ac.nz/ml/weka/datasets.html) 45 | - [Machine Learning Databases](http://archive.ics.uci.edu/ml/machine-learning-databases/) 46 | 47 | -------------------------------------------------------------------------------- /browser/js/convnet.js: -------------------------------------------------------------------------------- 1 | var convnetjs = convnetjs || { REVISION: 'ALPHA' }; 2 | (function(global) { 3 | "use strict"; 4 | 5 | // Random number utilities 6 | var return_v = false; 7 | var v_val = 0.0; 8 | var gaussRandom = function() { 9 | if(return_v) { 10 | return_v = false; 11 | return v_val; 12 | } 13 | var u = 2*Math.random()-1; 14 | var v = 2*Math.random()-1; 15 | var r = u*u + v*v; 16 | if(r == 0 || r > 1) return gaussRandom(); 17 | var c = Math.sqrt(-2*Math.log(r)/r); 18 | v_val = v*c; // cache this 19 | return_v = true; 20 | return u*c; 21 | } 22 | var randf = function(a, b) { return Math.random()*(b-a)+a; } 23 | var randi = function(a, b) { return Math.floor(Math.random()*(b-a)+a); } 24 | var randn = function(mu, std){ return mu+gaussRandom()*std; } 25 | 26 | // Array utilities 27 | var zeros = function(n) { 28 | if(typeof(n)==='undefined' || isNaN(n)) { return []; } 29 | if(typeof ArrayBuffer === 'undefined') { 30 | // lacking browser support 31 | var arr = new Array(n); 32 | for(var i=0;i maxv) { maxv = w[i]; maxi = i; } 66 | if(w[i] < minv) { minv = w[i]; mini = i; } 67 | } 68 | return {maxi: maxi, maxv: maxv, mini: mini, minv: minv, dv:maxv-minv}; 69 | } 70 | 71 | // create random permutation of numbers, in range [0...n-1] 72 | var randperm = function(n) { 73 | var i = n, 74 | j = 0, 75 | temp; 76 | var array = []; 77 | for(var q=0;qright 259 | var augment = function(V, crop, dx, dy, fliplr) { 260 | // note assumes square outputs of size crop x crop 261 | if(typeof(fliplr)==='undefined') var fliplr = false; 262 | if(typeof(dx)==='undefined') var dx = global.randi(0, V.sx - crop); 263 | if(typeof(dy)==='undefined') var dy = global.randi(0, V.sy - crop); 264 | 265 | // randomly sample a crop in the input volume 266 | var W; 267 | if(crop !== V.sx || dx!==0 || dy!==0) { 268 | W = new Vol(crop, crop, V.depth, 0.0); 269 | for(var x=0;x=V.sx || y+dy<0 || y+dy>=V.sy) continue; // oob 272 | for(var d=0;d=0 && oy=0 && ox=0 && oy=0 && ox=0 && oy=0 && ox a) { a = v; winx=ox; winy=oy;} 689 | } 690 | } 691 | } 692 | this.switchx[n] = winx; 693 | this.switchy[n] = winy; 694 | n++; 695 | A.set(ax, ay, d, a); 696 | } 697 | } 698 | } 699 | this.out_act = A; 700 | return this.out_act; 701 | }, 702 | backward: function() { 703 | // pooling layers have no parameters, so simply compute 704 | // gradient wrt data here 705 | var V = this.in_act; 706 | V.dw = global.zeros(V.w.length); // zero out gradient wrt data 707 | var A = this.out_act; // computed in forward pass 708 | 709 | var n = 0; 710 | for(var d=0;d amax) amax = as[i]; 841 | } 842 | 843 | // compute exponentials (carefully to not blow up) 844 | var es = global.zeros(this.out_depth); 845 | var esum = 0.0; 846 | for(var i=0;i 0) { 1004 | // violating dimension, apply loss 1005 | x.dw[i] += 1; 1006 | x.dw[y] -= 1; 1007 | loss += ydiff; 1008 | } 1009 | } 1010 | 1011 | return loss; 1012 | }, 1013 | getParamsAndGrads: function() { 1014 | return []; 1015 | }, 1016 | toJSON: function() { 1017 | var json = {}; 1018 | json.out_depth = this.out_depth; 1019 | json.out_sx = this.out_sx; 1020 | json.out_sy = this.out_sy; 1021 | json.layer_type = this.layer_type; 1022 | json.num_inputs = this.num_inputs; 1023 | return json; 1024 | }, 1025 | fromJSON: function(json) { 1026 | this.out_depth = json.out_depth; 1027 | this.out_sx = json.out_sx; 1028 | this.out_sy = json.out_sy; 1029 | this.layer_type = json.layer_type; 1030 | this.num_inputs = json.num_inputs; 1031 | } 1032 | } 1033 | 1034 | global.RegressionLayer = RegressionLayer; 1035 | global.SoftmaxLayer = SoftmaxLayer; 1036 | global.SVMLayer = SVMLayer; 1037 | 1038 | })(convnetjs); 1039 | 1040 | (function(global) { 1041 | "use strict"; 1042 | var Vol = global.Vol; // convenience 1043 | 1044 | // Implements ReLU nonlinearity elementwise 1045 | // x -> max(0, x) 1046 | // the output is in [0, inf) 1047 | var ReluLayer = function(opt) { 1048 | var opt = opt || {}; 1049 | 1050 | // computed 1051 | this.out_sx = opt.in_sx; 1052 | this.out_sy = opt.in_sy; 1053 | this.out_depth = opt.in_depth; 1054 | this.layer_type = 'relu'; 1055 | } 1056 | ReluLayer.prototype = { 1057 | forward: function(V, is_training) { 1058 | this.in_act = V; 1059 | var V2 = V.clone(); 1060 | var N = V.w.length; 1061 | var V2w = V2.w; 1062 | for(var i=0;i 1/(1+e^(-x)) 1099 | // so the output is between 0 and 1. 1100 | var SigmoidLayer = function(opt) { 1101 | var opt = opt || {}; 1102 | 1103 | // computed 1104 | this.out_sx = opt.in_sx; 1105 | this.out_sy = opt.in_sy; 1106 | this.out_depth = opt.in_depth; 1107 | this.layer_type = 'sigmoid'; 1108 | } 1109 | SigmoidLayer.prototype = { 1110 | forward: function(V, is_training) { 1111 | this.in_act = V; 1112 | var V2 = V.cloneAndZero(); 1113 | var N = V.w.length; 1114 | var V2w = V2.w; 1115 | var Vw = V.w; 1116 | for(var i=0;i max(x) 1153 | // where x is a vector of size group_size. Ideally of course, 1154 | // the input size should be exactly divisible by group_size 1155 | var MaxoutLayer = function(opt) { 1156 | var opt = opt || {}; 1157 | 1158 | // required 1159 | this.group_size = typeof opt.group_size !== 'undefined' ? opt.group_size : 2; 1160 | 1161 | // computed 1162 | this.out_sx = opt.in_sx; 1163 | this.out_sy = opt.in_sy; 1164 | this.out_depth = Math.floor(opt.in_depth / this.group_size); 1165 | this.layer_type = 'maxout'; 1166 | 1167 | this.switches = global.zeros(this.out_sx*this.out_sy*this.out_depth); // useful for backprop 1168 | } 1169 | MaxoutLayer.prototype = { 1170 | forward: function(V, is_training) { 1171 | this.in_act = V; 1172 | var N = this.out_depth; 1173 | var V2 = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); 1174 | 1175 | // optimization branch. If we're operating on 1D arrays we dont have 1176 | // to worry about keeping track of x,y,d coordinates inside 1177 | // input volumes. In convnets we do :( 1178 | if(this.out_sx === 1 && this.out_sy === 1) { 1179 | for(var i=0;i a) { 1186 | a = a2; 1187 | ai = j; 1188 | } 1189 | } 1190 | V2.w[i] = a; 1191 | this.switches[i] = ix + ai; 1192 | } 1193 | } else { 1194 | var n=0; // counter for switches 1195 | for(var x=0;x a) { 1204 | a = a2; 1205 | ai = j; 1206 | } 1207 | } 1208 | V2.set(x,y,i,a); 1209 | this.switches[n] = ix + ai; 1210 | n++; 1211 | } 1212 | } 1213 | } 1214 | 1215 | } 1216 | this.out_act = V2; 1217 | return this.out_act; 1218 | }, 1219 | backward: function() { 1220 | var V = this.in_act; // we need to set dw of this 1221 | var V2 = this.out_act; 1222 | var N = this.out_depth; 1223 | V.dw = global.zeros(V.w.length); // zero out gradient wrt data 1224 | 1225 | // pass the gradient through the appropriate switch 1226 | if(this.out_sx === 1 && this.out_sy === 1) { 1227 | for(var i=0;i tanh(x) 1274 | // so the output is between -1 and 1. 1275 | var TanhLayer = function(opt) { 1276 | var opt = opt || {}; 1277 | 1278 | // computed 1279 | this.out_sx = opt.in_sx; 1280 | this.out_sy = opt.in_sy; 1281 | this.out_depth = opt.in_depth; 1282 | this.layer_type = 'tanh'; 1283 | } 1284 | TanhLayer.prototype = { 1285 | forward: function(V, is_training) { 1286 | this.in_act = V; 1287 | var V2 = V.cloneAndZero(); 1288 | var N = V.w.length; 1289 | for(var i=0;i= 2, 'Error! At least one input layer and one loss layer are required.'); 1538 | assert(defs[0].type === 'input', 'Error! First layer must be the input layer, to declare size of inputs'); 1539 | 1540 | // desugar layer_defs for adding activation, dropout layers etc 1541 | var desugar = function() { 1542 | var new_defs = []; 1543 | for(var i=0;i0) { 1595 | var prev = this.layers[i-1]; 1596 | def.in_sx = prev.out_sx; 1597 | def.in_sy = prev.out_sy; 1598 | def.in_depth = prev.out_depth; 1599 | } 1600 | 1601 | switch(def.type) { 1602 | case 'fc': this.layers.push(new global.FullyConnLayer(def)); break; 1603 | case 'lrn': this.layers.push(new global.LocalResponseNormalizationLayer(def)); break; 1604 | case 'dropout': this.layers.push(new global.DropoutLayer(def)); break; 1605 | case 'input': this.layers.push(new global.InputLayer(def)); break; 1606 | case 'softmax': this.layers.push(new global.SoftmaxLayer(def)); break; 1607 | case 'regression': this.layers.push(new global.RegressionLayer(def)); break; 1608 | case 'conv': this.layers.push(new global.ConvLayer(def)); break; 1609 | case 'pool': this.layers.push(new global.PoolLayer(def)); break; 1610 | case 'relu': this.layers.push(new global.ReluLayer(def)); break; 1611 | case 'sigmoid': this.layers.push(new global.SigmoidLayer(def)); break; 1612 | case 'tanh': this.layers.push(new global.TanhLayer(def)); break; 1613 | case 'maxout': this.layers.push(new global.MaxoutLayer(def)); break; 1614 | case 'svm': this.layers.push(new global.SVMLayer(def)); break; 1615 | default: console.log('ERROR: UNRECOGNIZED LAYER TYPE: ' + def.type); 1616 | } 1617 | } 1618 | }, 1619 | 1620 | // forward prop the network. 1621 | // The trainer class passes is_training = true, but when this function is 1622 | // called from outside (not from the trainer), it defaults to prediction mode 1623 | forward: function(V, is_training) { 1624 | if(typeof(is_training) === 'undefined') is_training = false; 1625 | var act = this.layers[0].forward(V, is_training); 1626 | for(var i=1;i=0;i--) { // first layer assumed input 1644 | this.layers[i].backward(); 1645 | } 1646 | return loss; 1647 | }, 1648 | getParamsAndGrads: function() { 1649 | // accumulate parameters and gradients for the entire network 1650 | var response = []; 1651 | for(var i=0;i maxv) { maxv = p[i]; maxi = i;} 1670 | } 1671 | return maxi; // return index of the class with highest class probability 1672 | }, 1673 | toJSON: function() { 1674 | var json = {}; 1675 | json.layers = []; 1676 | for(var i=0;i 0.0)) { 1754 | // only vanilla sgd doesnt need either lists 1755 | // momentum needs gsum 1756 | // adagrad needs gsum 1757 | // adadelta needs gsum and xsum 1758 | for(var i=0;i 0 ? 1 : -1); 1785 | var l2grad = l2_decay * (p[j]); 1786 | 1787 | var gij = (l2grad + l1grad + g[j]) / this.batch_size; // raw batch gradient 1788 | 1789 | var gsumi = this.gsum[i]; 1790 | var xsumi = this.xsum[i]; 1791 | if(this.method === 'adagrad') { 1792 | // adagrad update 1793 | gsumi[j] = gsumi[j] + gij * gij; 1794 | var dx = - this.learning_rate / Math.sqrt(gsumi[j] + this.eps) * gij; 1795 | p[j] += dx; 1796 | } else if(this.method === 'windowgrad') { 1797 | // this is adagrad but with a moving window weighted average 1798 | // so the gradient is not accumulated over the entire history of the run. 1799 | // it's also referred to as Idea #1 in Zeiler paper on Adadelta. Seems reasonable to me! 1800 | gsumi[j] = this.ro * gsumi[j] + (1-this.ro) * gij * gij; 1801 | var dx = - this.learning_rate / Math.sqrt(gsumi[j] + this.eps) * gij; // eps added for better conditioning 1802 | p[j] += dx; 1803 | } else if(this.method === 'adadelta') { 1804 | // assume adadelta if not sgd or adagrad 1805 | gsumi[j] = this.ro * gsumi[j] + (1-this.ro) * gij * gij; 1806 | var dx = - Math.sqrt((xsumi[j] + this.eps)/(gsumi[j] + this.eps)) * gij; 1807 | xsumi[j] = this.ro * xsumi[j] + (1-this.ro) * dx * dx; // yes, xsum lags behind gsum by 1. 1808 | p[j] += dx; 1809 | } else if(this.method === 'nesterov') { 1810 | var dx = gsumi[j]; 1811 | gsumi[j] = gsumi[j] * this.momentum + this.learning_rate * gij; 1812 | dx = this.momentum * dx - (1.0 + this.momentum) * gsumi[j]; 1813 | p[j] += dx; 1814 | } else { 1815 | // assume SGD 1816 | if(this.momentum > 0.0) { 1817 | // momentum update 1818 | var dx = this.momentum * gsumi[j] - this.learning_rate * gij; // step 1819 | gsumi[j] = dx; // back this up for next iteration of momentum 1820 | p[j] += dx; // apply corrected gradient 1821 | } else { 1822 | // vanilla sgd 1823 | p[j] += - this.learning_rate * gij; 1824 | } 1825 | } 1826 | g[j] = 0.0; // zero out gradient so that we can begin accumulating anew 1827 | } 1828 | } 1829 | } 1830 | 1831 | // appending softmax_loss for backwards compatibility, but from now on we will always use cost_loss 1832 | // in future, TODO: have to completely redo the way loss is done around the network as currently 1833 | // loss is a bit of a hack. Ideally, user should specify arbitrary number of loss functions on any layer 1834 | // and it should all be computed correctly and automatically. 1835 | return {fwd_time: fwd_time, bwd_time: bwd_time, 1836 | l2_decay_loss: l2_decay_loss, l1_decay_loss: l1_decay_loss, 1837 | cost_loss: cost_loss, softmax_loss: cost_loss, 1838 | loss: cost_loss + l1_decay_loss + l2_decay_loss} 1839 | } 1840 | } 1841 | 1842 | global.Trainer = Trainer; 1843 | global.SGDTrainer = Trainer; // backwards compatibility 1844 | })(convnetjs); 1845 | 1846 | (function(global) { 1847 | "use strict"; 1848 | 1849 | // used utilities, make explicit local references 1850 | var randf = global.randf; 1851 | var randi = global.randi; 1852 | var Net = global.Net; 1853 | var Trainer = global.Trainer; 1854 | var maxmin = global.maxmin; 1855 | var randperm = global.randperm; 1856 | var weightedSample = global.weightedSample; 1857 | var getopt = global.getopt; 1858 | var arrUnique = global.arrUnique; 1859 | 1860 | /* 1861 | A MagicNet takes data: a list of convnetjs.Vol(), and labels 1862 | which for now are assumed to be class indeces 0..K. MagicNet then: 1863 | - creates data folds for cross-validation 1864 | - samples candidate networks 1865 | - evaluates candidate networks on all data folds 1866 | - produces predictions by model-averaging the best networks 1867 | */ 1868 | var MagicNet = function(data, labels, opt) { 1869 | var opt = opt || {}; 1870 | if(typeof data === 'undefined') { data = []; } 1871 | if(typeof labels === 'undefined') { labels = []; } 1872 | 1873 | // required inputs 1874 | this.data = data; // store these pointers to data 1875 | this.labels = labels; 1876 | 1877 | // optional inputs 1878 | this.train_ratio = getopt(opt, 'train_ratio', 0.7); 1879 | this.num_folds = getopt(opt, 'num_folds', 10); 1880 | this.num_candidates = getopt(opt, 'num_candidates', 50); // we evaluate several in parallel 1881 | // how many epochs of data to train every network? for every fold? 1882 | // higher values mean higher accuracy in final results, but more expensive 1883 | this.num_epochs = getopt(opt, 'num_epochs', 50); 1884 | // number of best models to average during prediction. Usually higher = better 1885 | this.ensemble_size = getopt(opt, 'ensemble_size', 10); 1886 | 1887 | // candidate parameters 1888 | this.batch_size_min = getopt(opt, 'batch_size_min', 10); 1889 | this.batch_size_max = getopt(opt, 'batch_size_max', 300); 1890 | this.l2_decay_min = getopt(opt, 'l2_decay_min', -4); 1891 | this.l2_decay_max = getopt(opt, 'l2_decay_max', 2); 1892 | this.learning_rate_min = getopt(opt, 'learning_rate_min', -4); 1893 | this.learning_rate_max = getopt(opt, 'learning_rate_max', 0); 1894 | this.momentum_min = getopt(opt, 'momentum_min', 0.9); 1895 | this.momentum_max = getopt(opt, 'momentum_max', 0.9); 1896 | this.neurons_min = getopt(opt, 'neurons_min', 5); 1897 | this.neurons_max = getopt(opt, 'neurons_max', 30); 1898 | 1899 | // computed 1900 | this.folds = []; // data fold indices, gets filled by sampleFolds() 1901 | this.candidates = []; // candidate networks that are being currently evaluated 1902 | this.evaluated_candidates = []; // history of all candidates that were fully evaluated on all folds 1903 | this.unique_labels = arrUnique(labels); 1904 | this.iter = 0; // iteration counter, goes from 0 -> num_epochs * num_training_data 1905 | this.foldix = 0; // index of active fold 1906 | 1907 | // callbacks 1908 | this.finish_fold_callback = null; 1909 | this.finish_batch_callback = null; 1910 | 1911 | // initializations 1912 | if(this.data.length > 0) { 1913 | this.sampleFolds(); 1914 | this.sampleCandidates(); 1915 | } 1916 | }; 1917 | 1918 | MagicNet.prototype = { 1919 | 1920 | // sets this.folds to a sampling of this.num_folds folds 1921 | sampleFolds: function() { 1922 | var N = this.data.length; 1923 | var num_train = Math.floor(this.train_ratio * N); 1924 | this.folds = []; // flush folds, if any 1925 | for(var i=0;i