├── LICENSE.md ├── README.md ├── cnnutil.js ├── cnnvis.js ├── process_images.py ├── tester.js ├── trainer.js └── utilities.js /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 birdID contributors 4 | 5 | birdID uses a shared copyright model: each contributor holds copyright over 6 | their contributions. The project versioning records all such 7 | contribution and copyright details. 8 | By contributing to this repository through pull-request, comment, 9 | or otherwise, the contributor releases their content to the license and 10 | copyright terms herein. 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #BirdID_Convnetjs 2 | ---------- 3 | 4 | How to use: 5 | ---------- 6 | Format your dataset for use with process_images.py. It requires the images to be in the following folder structure: 7 | 8 | . 9 | |-- path_to_folders_with_images 10 | | |-- class1 11 | | | |-- some_image1.jpg 12 | | | |-- some_image1.jpg 13 | | | |-- some_image1.jpg 14 | | | └ ... 15 | | |-- class2 16 | | | └ ... 17 | | |-- class3 18 | ... 19 | | └-- classN 20 | 21 | The code can be modified to create two versions of the dataset with different dimensions, to be used for augmentation. 22 | 23 | - trainer.js: Define the network and train it 24 | 25 | - tester.js: Run a trained network and output accuracy. Prints per-category accuracy and the accuracy of the top 2 and top 3 guesses as well. 26 | 27 | 28 | Dependencies: 29 | ---------- 30 | 31 | - [Node.js][4] 32 | - [convnetjs][1] 33 | - [graceful-fs][2] 34 | - [node-canvas][3] 35 | 36 | [1]: https://github.com/karpathy/convnetjs 37 | [2]: https://github.com/isaacs/node-graceful-fs 38 | [3]: https://github.com/Automattic/node-canvas 39 | [4]: https://nodejs.org/ 40 | -------------------------------------------------------------------------------- /cnnutil.js: -------------------------------------------------------------------------------- 1 | 2 | // contains various utility functions 3 | var cnnutil = (function(exports){ 4 | 5 | // a window stores _size_ number of values 6 | // and returns averages. Useful for keeping running 7 | // track of validation or training accuracy during SGD 8 | var Window = function(size, minsize) { 9 | this.v = []; 10 | this.size = typeof(size)==='undefined' ? 100 : size; 11 | this.minsize = typeof(minsize)==='undefined' ? 20 : minsize; 12 | this.sum = 0; 13 | } 14 | Window.prototype = { 15 | add: function(x) { 16 | this.v.push(x); 17 | this.sum += x; 18 | if(this.v.length>this.size) { 19 | var xold = this.v.shift(); 20 | this.sum -= xold; 21 | } 22 | }, 23 | get_average: function() { 24 | if(this.v.length < this.minsize) return -1; 25 | else return this.sum/this.v.length; 26 | }, 27 | reset: function(x) { 28 | this.v = []; 29 | this.sum = 0; 30 | } 31 | } 32 | 33 | // returns min, max and indeces of an array 34 | var maxmin = function(w) { 35 | if(w.length === 0) { return {}; } // ... ;s 36 | 37 | var maxv = w[0]; 38 | var minv = w[0]; 39 | var maxi = 0; 40 | var mini = 0; 41 | for(var i=1;i maxv) { maxv = w[i]; maxi = i; } 43 | if(w[i] < minv) { minv = w[i]; mini = i; } 44 | } 45 | return {maxi: maxi, maxv: maxv, mini: mini, minv: minv, dv:maxv-minv}; 46 | } 47 | 48 | // returns string representation of float 49 | // but truncated to length of d digits 50 | var f2t = function(x, d) { 51 | if(typeof(d)==='undefined') { var d = 5; } 52 | var dd = 1.0 * Math.pow(10, d); 53 | return '' + Math.floor(x*dd)/dd; 54 | } 55 | 56 | exports = exports || {}; 57 | exports.Window = Window; 58 | exports.maxmin = maxmin; 59 | exports.f2t = f2t; 60 | return exports; 61 | 62 | })(typeof module != 'undefined' && module.exports); // add exports to module.exports if in node.js 63 | 64 | 65 | -------------------------------------------------------------------------------- /cnnvis.js: -------------------------------------------------------------------------------- 1 | 2 | // contains various utility functions 3 | var cnnvis = (function(exports){ 4 | 5 | // can be used to graph loss, or accuract over time 6 | var Graph = function(options) { 7 | var options = options || {}; 8 | this.step_horizon = options.step_horizon || 1000; 9 | 10 | this.pts = []; 11 | 12 | this.maxy = -9999; 13 | this.miny = 9999; 14 | } 15 | 16 | Graph.prototype = { 17 | // canv is the canvas we wish to update with this new datapoint 18 | add: function(step, y) { 19 | var time = new Date().getTime(); // in ms 20 | if(y>this.maxy*0.99) this.maxy = y*1.05; 21 | if(y this.step_horizon) this.step_horizon *= 2; 25 | }, 26 | // elt is a canvas we wish to draw into 27 | drawSelf: function(canv) { 28 | 29 | var pad = 25; 30 | var H = canv.height; 31 | var W = canv.width; 32 | var ctx = canv.getContext('2d'); 33 | 34 | ctx.clearRect(0, 0, W, H); 35 | ctx.font="10px Georgia"; 36 | 37 | var f2t = function(x) { 38 | var dd = 1.0 * Math.pow(10, 2); 39 | return '' + Math.floor(x*dd)/dd; 40 | } 41 | 42 | // draw guidelines and values 43 | ctx.strokeStyle = "#999"; 44 | ctx.beginPath(); 45 | var ng = 10; 46 | for(var i=0;i<=ng;i++) { 47 | var xpos = i/ng*(W-2*pad)+pad; 48 | ctx.moveTo(xpos, pad); 49 | ctx.lineTo(xpos, H-pad); 50 | ctx.fillText(f2t(i/ng*this.step_horizon/1000)+'k',xpos,H-pad+14); 51 | } 52 | for(var i=0;i<=ng;i++) { 53 | var ypos = i/ng*(H-2*pad)+pad; 54 | ctx.moveTo(pad, ypos); 55 | ctx.lineTo(W-pad, ypos); 56 | ctx.fillText(f2t((ng-i)/ng*(this.maxy-this.miny) + this.miny), 0, ypos); 57 | } 58 | ctx.stroke(); 59 | 60 | var N = this.pts.length; 61 | if(N<2) return; 62 | 63 | // draw the actual curve 64 | var t = function(x, y, s) { 65 | var tx = x / s.step_horizon * (W-pad*2) + pad; 66 | var ty = H - ((y-s.miny) / (s.maxy-s.miny) * (H-pad*2) + pad); 67 | return {tx:tx, ty:ty} 68 | } 69 | 70 | ctx.strokeStyle = "red"; 71 | ctx.beginPath() 72 | for(var i=0;ithis.maxy*0.99) this.maxy = y*1.05; 112 | if(y this.step_horizon) this.step_horizon *= 2; 120 | }, 121 | // elt is a canvas we wish to draw into 122 | drawSelf: function(canv) { 123 | 124 | var pad = 25; 125 | var H = canv.height; 126 | var W = canv.width; 127 | var ctx = canv.getContext('2d'); 128 | 129 | ctx.clearRect(0, 0, W, H); 130 | ctx.font="10px Georgia"; 131 | 132 | var f2t = function(x) { 133 | var dd = 1.0 * Math.pow(10, 2); 134 | return '' + Math.floor(x*dd)/dd; 135 | } 136 | 137 | // draw guidelines and values 138 | ctx.strokeStyle = "#999"; 139 | ctx.beginPath(); 140 | var ng = 10; 141 | for(var i=0;i<=ng;i++) { 142 | var xpos = i/ng*(W-2*pad)+pad; 143 | ctx.moveTo(xpos, pad); 144 | ctx.lineTo(xpos, H-pad); 145 | ctx.fillText(f2t(i/ng*this.step_horizon/1000)+'k',xpos,H-pad+14); 146 | } 147 | for(var i=0;i<=ng;i++) { 148 | var ypos = i/ng*(H-2*pad)+pad; 149 | ctx.moveTo(pad, ypos); 150 | ctx.lineTo(W-pad, ypos); 151 | ctx.fillText(f2t((ng-i)/ng*(this.maxy-this.miny) + this.miny), 0, ypos); 152 | } 153 | ctx.stroke(); 154 | 155 | var N = this.pts.length; 156 | if(N<2) return; 157 | 158 | // draw legend 159 | for(var k=0;k total) {//if too many, decrement the last one 139 | while (sum > total) { 140 | amounts[amounts.length - 1]--; 141 | sum--; 142 | } 143 | } 144 | 145 | //randomly assign names to each group 146 | var name_clone = names.slice(0); 147 | var label_clone = labels.slice(0); 148 | var result = [[ ], [ ] ]; 149 | //names, labels 150 | 151 | for (var i = 0; i < amounts.length; i++){ 152 | result[0].push([ ]);//initializing the corresponding arrays 153 | result[1].push([ ]); 154 | for (var j = 0; j < amounts[i]; j++){ 155 | var index = Math.floor(Math.random() * name_clone.length); 156 | result[0][i].push(name_clone.splice(index, 1)[0]); 157 | result[1][i].push(label_clone.splice(index, 1)[0]);//remove one element at the calculated random index 158 | //the splice method returns an array of one item, so that item is pushed to the appropriate index in the result array 159 | } 160 | } 161 | 162 | return result; 163 | } 164 | 165 | //given the softmax layer, returns an array of the top predictions from the layer, in desencding order. "amount" determines how many predictions to return. 166 | var getTopPrediction = function(softmax_layer, amount) { 167 | 168 | //assert(softmax_layer.layer_type === 'softmax', 'getTopPrediction() requires a softmax layer'); 169 | 170 | if (softmax_layer.layer_type !== 'softmax') 171 | return; 172 | 173 | var p = Object.keys(softmax_layer.out_act.w).map(function(k) { return softmax_layer.out_act.w[k] });//made into an array 174 | 175 | var result = [ ]; 176 | 177 | if (p.length < amount || amount === 0)//requested too many 178 | return; 179 | 180 | for (var a = 0; a < amount; a++) { 181 | 182 | var maxV = p[0]; 183 | var maxi = 0; 184 | 185 | for (var i = 1; i < p.length; i++) { 186 | 187 | if (p[i] > maxV) { 188 | maxV = p[i]; 189 | maxi = i; 190 | } 191 | } 192 | 193 | result.push(maxi); 194 | p[maxi] = 0;//set the element to zero so that it is not picked up again 195 | 196 | } 197 | return result; 198 | } 199 | 200 | var getSVMPrediction = function (svm_layer) { 201 | 202 | if (svm_layer.layer_type !== 'svm') 203 | return; 204 | 205 | var p = svm_layer.out_act.w; 206 | var maxV = p[0]; 207 | var maxi = 0; 208 | 209 | for (var i = 1; i < p.length; i++) { 210 | 211 | if (p[i] > maxV) { 212 | maxV = p[i]; 213 | maxi = i; 214 | } 215 | } 216 | 217 | return maxi;//returns the index of the class with the highest score 218 | } 219 | 220 | exports = exports || {}; 221 | exports.img_to_vol = img_to_vol; 222 | exports.drawImg= drawImg; 223 | exports.draw_activations = draw_activations; 224 | exports.split_dataset = split_dataset; 225 | exports.getTopPrediction = getTopPrediction; 226 | exports.getSVMPrediction = getSVMPrediction; 227 | return exports; 228 | 229 | })(module.exports);//added to exports 230 | --------------------------------------------------------------------------------