1// double pendulum - one file. 2 3/* 4 5@licstart The following is the entire license notice for the 6JavaScript code in this page. 7 8Copyright (C) 2015 david ha, otoro.net, otoro labs 9 10The JavaScript code in this page is free software: you can 11redistribute it and/or modify it under the terms of the GNU 12General Public License (GNU GPL) as published by the Free Software 13Foundation, either version 3 of the License, or (at your option) 14any later version. The code is distributed WITHOUT ANY WARRANTY; 15without even the implied warranty of MERCHANTABILITY or FITNESS 16FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. 17 18As additional permission under GNU GPL version 3 section 7, you 19may distribute non-source (e.g., minimized or compacted) forms of 20that code without the copy of the GNU GPL normally required by 21section 4, provided you include this license notice and a URL 22through which recipients can access the Corresponding Source. 23 24 25@licend The above is the entire license notice 26for the JavaScript code in this page. 27*/ 28 29 30// ----------------------------------------------------------------------------- 31// Scale Methods 32// ----------------------------------------------------------------------------- 33 34// supposed to translate (x_b2, y_b2) -> (x_pixel, y_pixel). everything else scaled by a factor of scaleFactor 35var b2Camera = { 36 scaleFactor : 10, 37 x_b2: 0, 38 y_b2: 0, 39 x_pixel: 0, 40 y_pixel: 0 41}; 42 43var gravity; 44var gravityStrength = 20; 45 46var scaleToWorld = function(a,b) { 47 var newv; 48 if (a instanceof box2d.b2Vec2) { 49 newv = new box2d.b2Vec2(); 50 newv.x = (a.x-b2Camera.x_pixel)/b2Camera.scaleFactor+b2Camera.x_b2; 51 newv.y = (a.y-b2Camera.y_pixel)/b2Camera.scaleFactor+b2Camera.y_b2; 52 return newv; 53 } else if ("undefined"!=typeof b) { 54 newv = new box2d.b2Vec2(); 55 newv.x = (a-b2Camera.x_pixel)/b2Camera.scaleFactor+b2Camera.x_b2; 56 newv.y = (b-b2Camera.y_pixel)/b2Camera.scaleFactor+b2Camera.y_b2; 57 return newv; 58 } else { 59 return a/b2Camera.scaleFactor; 60 } 61}; 62 63 64var makeB2Vec2 = function(a,b) { 65 var newv; 66 newv = new box2d.b2Vec2(); 67 newv.x = (a)/1; 68 newv.y = (b)/1; 69 return newv; 70}; 71 72var scaleToPixels = function(a,b) { 73 var newv; 74 if (a instanceof box2d.b2Vec2) { 75 newv = new box2d.b2Vec2(); 76 newv.x = (a.x-b2Camera.x_b2)*b2Camera.scaleFactor+b2Camera.x_pixel; 77 newv.y = (a.y-b2Camera.y_b2)*b2Camera.scaleFactor+b2Camera.y_pixel; 78 return newv; 79 } else if ("undefined"!=typeof b) { 80 newv = new box2d.b2Vec2(); 81 newv.x = (a-b2Camera.x_b2)*b2Camera.scaleFactor+b2Camera.x_pixel; 82 newv.y = (b-b2Camera.y_b2)*b2Camera.scaleFactor+b2Camera.y_pixel; 83 return newv; 84 } else { 85 return a*b2Camera.scaleFactor; 86 } 87}; 88 89// ----------------------------------------------------------------------------- 90// Create Methods 91// ----------------------------------------------------------------------------- 92 93var createWorld = function() { 94 95 var worldAABB = new box2d.b2AABB(); 96 worldAABB.lowerBound.SetXY(-this.bounds, -this.bounds); 97 worldAABB.upperBound.SetXY(this.bounds, this.bounds); 98 gravity = new box2d.b2Vec2(0,gravityStrength); 99 var doSleep = true; 100 101 return new box2d.b2World(gravity, doSleep); 102}; 103 104// ----------------------------------------------------------------------------- 105// Draw Methods 106// ----------------------------------------------------------------------------- 107 108var debugDraw = function(canvas, scale, world) { 109 110 var context = canvas.getContext('2d'); 111 var j, b, f; 112 context.fillStyle = '#DDD'; 113 context.fillRect(0, 0, canvas.width, canvas.height); 114 115 // Draw joints 116 for( j=world.m_jointList; j; j=j.m_next) { 117 context.lineWidth = 0.25; 118 context.strokeStyle = '#00F'; 119 drawJoint(context, scale, world, j); 120 } 121 122 // Draw body shapes 123 for( b=world.m_bodyList; b; b=b.m_next) { 124 for( f = b.GetFixtureList(); f!==null; f=f.GetNext()) { 125 context.lineWidth = 0.5; 126 context.strokeStyle = '#F00'; 127 drawShape(context, scale, world, b, f); 128 } 129 } 130}; 131 132var drawJoint = function(context, scale, world, joint) { 133 context.save(); 134 context.scale(scale,scale); 135 context.lineWidth /= scale; 136 137 var b1 = joint.m_bodyA; 138 var b2 = joint.m_bodyB; 139 var x1 = b1.GetPosition(); 140 var x2 = b2.GetPosition(); 141 var p1 = joint.GetAnchorA(); 142 var p2 = joint.GetAnchorB(); 143 144 context.beginPath(); 145 switch (joint.m_type) { 146 case box2d.b2Joint.e_distanceJoint: 147 context.moveTo(p1.x, p1.y); 148 context.lineTo(p2.x, p2.y); 149 break; 150 default: { 151 if (b1 == world.m_groundBody) { 152 context.moveTo(p1.x, p1.y); 153 context.lineTo(x2.x, x2.y); 154 } 155 else if (b2 == world.m_groundBody) { 156 context.moveTo(p1.x, p1.y); 157 context.lineTo(x1.x, x1.y); 158 } 159 else { 160 context.moveTo(x1.x, x1.y); 161 context.lineTo(p1.x, p1.y); 162 context.lineTo(x2.x, x2.y); 163 context.lineTo(p2.x, p2.y); 164 } 165 } break; 166 } 167 context.closePath(); 168 context.stroke();
169 context.restore(); 170}; 171 172var drawShape = function(context, scale, world, body, fixture) { 173 174 context.save(); 175 context.scale(scale,scale); 176 177 var bPos = body.GetPosition(); 178 context.translate(bPos.x, bPos.y); 179 context.rotate(body.GetAngleRadians()); 180 181 context.beginPath(); 182 context.lineWidth /= scale; 183 184 var shape = fixture.m_shape; 185 var i; 186 switch(shape.m_type) { 187 case box2d.b2ShapeType.e_circleShape: { 188 var r = shape.m_radius; 189 var segments = 16.0; 190 var theta = 0.0; 191 var dtheta = 2.0 * Math.PI / segments; 192 193 context.moveTo(r, 0); 194 for (i = 0; i < segments; i++) { 195 context.lineTo(r + r * Math.cos(theta), r * Math.sin(theta)); 196 theta += dtheta; 197 } 198 context.lineTo(r, 0); 199 } break; 200 201 case box2d.b2ShapeType.e_polygonShape: 202 case box2d.b2ShapeType.e_chainShape: { 203 204 var vertices = shape.m_vertices; 205 var vertexCount = shape.m_count; 206 if (!vertexCount) return; 207 208 context.moveTo(vertices[0].x, vertices[0].y); 209 for (i = 0; i < vertexCount; i++) 210 context.lineTo(vertices[i].x, vertices[i].y); 211 } break; 212 } 213 214 context.closePath(); 215 context.stroke(); 216 context.restore(); 217}; 218 219var convnetjs = convnetjs || { REVISION: 'ALPHA' }; 220(function(global) { 221 "use strict"; 222 223 // Random number utilities 224 var return_v = false; 225 var v_val = 0.0; 226 var gaussRandom = function() { 227 if(return_v) { 228 return_v = false; 229 return v_val; 230 } 231 var u = 2*Math.random()-1; 232 var v = 2*Math.random()-1; 233 var r = u*u + v*v; 234 if(r == 0 || r > 1) return gaussRandom(); 235 var c = Math.sqrt(-2*Math.log(r)/r); 236 v_val = v*c; // cache this 237 return_v = true; 238 return u*c; 239 } 240 var randf = function(a, b) { return Math.random()*(b-a)+a; } 241 var randi = function(a, b) { return Math.floor(Math.random()*(b-a)+a); } 242 var randn = function(mu, std){ return mu+gaussRandom()*std; } 243 244 // Array utilities 245 var zeros = function(n) { 246 if(typeof(n)==='undefined' || isNaN(n)) { return []; } 247 if(typeof ArrayBuffer === 'undefined') { 248 // lacking browser support 249 var arr = new Array(n); 250 for(var i=0;i<n;i++) { arr[i]= 0; } 251 return arr; 252 } else { 253 return new Float64Array(n); 254 } 255 } 256 257 var arrContains = function(arr, elt) { 258 for(var i=0,n=arr.length;i<n;i++) { 259 if(arr[i]===elt) return true; 260 } 261 return false; 262 } 263 264 var arrUnique = function(arr) { 265 var b = []; 266 for(var i=0,n=arr.length;i<n;i++) { 267 if(!arrContains(b, arr[i])) { 268 b.push(arr[i]); 269 } 270 } 271 return b; 272 } 273 274 // return max and min of a given non-empty array. 275 var maxmin = function(w) { 276 if(w.length === 0) { return {}; } // ... ;s 277 var maxv = w[0]; 278 var minv = w[0]; 279 var maxi = 0; 280 var mini = 0; 281 var n = w.length; 282 for(var i=1;i<n;i++) { 283 if(w[i] > maxv) { maxv = w[i]; maxi = i; } 284 if(w[i] < minv) { minv = w[i]; mini = i; } 285 } 286 return {maxi: maxi, maxv: maxv, mini: mini, minv: minv, dv:maxv-minv}; 287 } 288 289 // create random permutation of numbers, in range [0...n-1] 290 var randperm = function(n) { 291 var i = n, 292 j = 0, 293 temp; 294 var array = []; 295 for(var q=0;q<n;q++)array[q]=q; 296 while (i--) { 297 j = Math.floor(Math.random() * (i+1)); 298 temp = array[i]; 299 array[i] = array[j]; 300 array[j] = temp; 301 } 302 return array; 303 } 304 305 // sample from list lst according to probabilities in list probs 306 // the two lists are of same size, and probs adds up to 1 307 var weightedSample = function(lst, probs) { 308 var p = randf(0, 1.0); 309 var cumprob = 0.0; 310 for(var k=0,n=lst.length;k<n;k++) { 311 cumprob += probs[k]; 312 if(p < cumprob) { return lst[k]; } 313 } 314 } 315 316 // syntactic sugar function for getting default parameter values 317 var getopt = function(opt, field_name, default_value) { 318 return typeof opt[field_name] !== 'undefined' ? opt[field_name] : default_value; 319 } 320 321 global.randf = randf; 322 global.randi = randi; 323 global.randn = randn; 324 global.zeros = zeros; 325 global.maxmin = maxmin; 326 global.randperm = randperm; 327 global.weightedSample = weightedSample; 328 global.arrUnique = arrUnique; 329 global.arrContains = arrContains; 330 global.getopt = getopt; 331 332})(convnetjs); 333(function(global) { 334 "use strict"; 335 336 // Vol is the basic building block of all data in a net. 337 // it is essentially just a 3D volume of numbers, with a 338 // width (sx), height (sy), and depth (depth). 339 // it is used to hold data for all filters, all volumes, 340 // all weights, and also stores all gradients w.r.t. 341 // the data. c is optionally a value to initialize the volume 342 // with. If c is missing, fills the Vol with random numbers. 343 var Vol = function(sx, sy, depth, c) { 344 // this is how you check if a variable is an array. Oh, Javascript :) 345 if(Object.prototype.toString.call(sx) === '[object Array]') { 346 // we were given a list in sx, assume 1D volume and fill it up 347 this.sx = 1; 348 this.sy = 1; 349 this.depth = sx.length; 350 // we have to do the following copy because we want to use 351 // fast typed arrays, not an ordinary javascript array 352 this.w = global.zeros(this.depth); 353 this.dw = global.zeros(this.depth); 354 for(var i=0;i<this.depth;i++) { 355 this.w[i] = sx[i]; 356 } 357 } else { 358 // we were given dimensions of the vol 359 this.sx = sx; 360 this.sy = sy; 361 this.depth = depth; 362 var n = sx*sy*depth; 363 this.w = global.zeros(n); 364 this.dw = global.zeros(n); 365 if(typeof c === 'undefined') { 366 // weight normalization is done to equalize the output 367 // variance of every neuron, otherwise neurons with a lot 368 // of incoming connections have outputs of larger variance 369 var scale = Math.sqrt(1.0/(sx*sy*depth)); 370 for(var i=0;i<n;i++) { 371 this.w[i] = global.randn(0.0, scale); 372 } 373 } else { 374 for(var i=0;i<n;i++) { 375 this.w[i] = c; 376 } 377 } 378 } 379 } 380 381 Vol.prototype = { 382 get: function(x, y, d) { 383 var ix=((this.sx * y)+x)*this.depth+d; 384 return this.w[ix]; 385 }, 386 set: function(x, y, d, v) { 387 var ix=((this.sx * y)+x)*this.depth+d; 388 this.w[ix] = v; 389 }, 390 add: function(x, y, d, v) { 391 var ix=((this.sx * y)+x)*this.depth+d; 392 this.w[ix] += v; 393 }, 394 get_grad: function(x, y, d) { 395 var ix = ((this.sx * y)+x)*this.depth+d; 396 return this.dw[ix]; 397 }, 398 set_grad: function(x, y, d, v) { 399 var ix = ((this.sx * y)+x)*this.depth+d; 400 this.dw[ix] = v; 401 }, 402 add_grad: function(x, y, d, v) { 403 var ix = ((this.sx * y)+x)*this.depth+d; 404 this.dw[ix] += v; 405 }, 406 cloneAndZero: function() { return new Vol(this.sx, this.sy, this.depth, 0.0)}, 407 clone: function() { 408 var V = new Vol(this.sx, this.sy, this.depth, 0.0); 409 var n = this.w.length; 410 for(var i=0;i<n;i++) { V.w[i] = this.w[i]; } 411 return V; 412 }, 413 addFrom: function(V) { for(var k=0;k<this.w.length;k++) { this.w[k] += V.w[k]; }}, 414 addFromScaled: function(V, a) { for(var k=0;k<this.w.length;k++) { this.w[k] += a*V.w[k]; }}, 415 setConst: function(a) { for(var k=0;k<this.w.length;k++) { this.w[k] = a; }}, 416 417 toJSON: function() { 418 // todo: we may want to only save d most significant digits to save space 419 var json = {} 420 json.sx = this.sx; 421 json.sy = this.sy; 422 json.depth = this.depth; 423 json.w = this.w; 424 return json; 425 // we wont back up gradients to save space 426 }, 427 fromJSON: function(json) { 428 this.sx = json.sx; 429 this.sy = json.sy; 430 this.depth = json.depth; 431 432 var n = this.sx*this.sy*this.depth; 433 this.w = global.zeros(n); 434 this.dw = global.zeros(n); 435 // copy over the elements. 436 for(var i=0;i<n;i++) { 437 this.w[i] = json.w[i]; 438 } 439 } 440 } 441 442 global.Vol = Vol; 443})(convnetjs); 444(function(global) { 445 "use strict"; 446 var Vol = global.Vol; // convenience 447 448 // Volume utilities 449 // intended for use with data augmentation 450 // crop is the size of output 451 // dx,dy are offset wrt incoming volume, of the shift 452 // fliplr is boolean on whether we also want to flip left<->right 453 var augment = function(V, crop, dx, dy, fliplr) { 454 // note assumes square outputs of size crop x crop 455 if(typeof(fliplr)==='undefined') var fliplr = false;
456 if(typeof(dx)==='undefined') var dx = global.randi(0, V.sx - crop); 457 if(typeof(dy)==='undefined') var dy = global.randi(0, V.sy - crop); 458 459 // randomly sample a crop in the input volume 460 var W; 461 if(crop !== V.sx || dx!==0 || dy!==0) { 462 W = new Vol(crop, crop, V.depth, 0.0); 463 for(var x=0;x<crop;x++) { 464 for(var y=0;y<crop;y++) { 465 if(x+dx<0 || x+dx>=V.sx || y+dy<0 || y+dy>=V.sy) continue; // oob 466 for(var d=0;d<V.depth;d++) { 467 W.set(x,y,d,V.get(x+dx,y+dy,d)); // copy data over 468 } 469 } 470 } 471 } else { 472 W = V; 473 } 474 475 if(fliplr) { 476 // flip volume horziontally 477 var W2 = W.cloneAndZero(); 478 for(var x=0;x<W.sx;x++) { 479 for(var y=0;y<W.sy;y++) { 480 for(var d=0;d<W.depth;d++) { 481 W2.set(x,y,d,W.get(W.sx - x - 1,y,d)); // copy data over 482 } 483 } 484 } 485 W = W2; //swap 486 } 487 return W; 488 } 489 490 // img is a DOM element that contains a loaded image 491 // returns a Vol of size (W, H, 4). 4 is for RGBA 492 var img_to_vol = function(img, convert_grayscale) { 493 494 if(typeof(convert_grayscale)==='undefined') var convert_grayscale = false; 495 496 var canvas = document.createElement('canvas'); 497 canvas.width = img.width; 498 canvas.height = img.height; 499 var ctx = canvas.getContext("2d"); 500 501 // due to a Firefox bug 502 try { 503 ctx.drawImage(img, 0, 0); 504 } catch (e) { 505 if (e.name === "NS_ERROR_NOT_AVAILABLE") { 506 // sometimes happens, lets just abort 507 return false; 508 } else { 509 throw e; 510 } 511 } 512 513 try { 514 var img_data = ctx.getImageData(0, 0, canvas.width, canvas.height); 515 } catch (e) { 516 if(e.name === 'IndexSizeError') { 517 return false; // not sure what causes this sometimes but okay abort 518 } else { 519 throw e; 520 } 521 } 522 523 // prepare the input: get pixels and normalize them 524 var p = img_data.data; 525 var W = img.width; 526 var H = img.height; 527 var pv = [] 528 for(var i=0;i<p.length;i++) { 529 pv.push(p[i]/255.0-0.5); // normalize image pixels to [-0.5, 0.5] 530 } 531 var x = new Vol(W, H, 4, 0.0); //input volume (image) 532 x.w = pv; 533 534 if(convert_grayscale) { 535 // flatten into depth=1 array 536 var x1 = new Vol(W, H, 1, 0.0); 537 for(var i=0;i<W;i++) { 538 for(var j=0;j<H;j++) { 539 x1.set(i,j,0,x.get(i,j,0)); 540 } 541 } 542 x = x1; 543 } 544 545 return x; 546 } 547 548 global.augment = augment; 549 global.img_to_vol = img_to_vol; 550 551})(convnetjs); 552(function(global) { 553 "use strict"; 554 var Vol = global.Vol; // convenience 555 556 // This file contains all layers that do dot products with input, 557 // but usually in a different connectivity pattern and weight sharing 558 // schemes: 559 // - FullyConn is fully connected dot products 560 // - ConvLayer does convolutions (so weight sharing spatially) 561 // putting them together in one file because they are very similar 562 var ConvLayer = function(opt) { 563 var opt = opt || {}; 564 565 // required 566 this.out_depth = opt.filters; 567 this.sx = opt.sx; // filter size. Should be odd if possible, it's cleaner. 568 this.in_depth = opt.in_depth; 569 this.in_sx = opt.in_sx; 570 this.in_sy = opt.in_sy; 571 572 // optional 573 this.sy = typeof opt.sy !== 'undefined' ? opt.sy : this.sx; 574 this.stride = typeof opt.stride !== 'undefined' ? opt.stride : 1; // stride at which we apply filters to input volume 575 this.pad = typeof opt.pad !== 'undefined' ? opt.pad : 0; // amount of 0 padding to add around borders of input volume 576 this.l1_decay_mul = typeof opt.l1_decay_mul !== 'undefined' ? opt.l1_decay_mul : 0.0; 577 this.l2_decay_mul = typeof opt.l2_decay_mul !== 'undefined' ? opt.l2_decay_mul : 1.0; 578 579 // computed 580 // note we are doing floor, so if the strided convolution of the filter doesnt fit into the input 581 // volume exactly, the output volume will be trimmed and not contain the (incomplete) computed 582 // final application. 583 this.out_sx = Math.floor((this.in_sx + this.pad * 2 - this.sx) / this.stride + 1); 584 this.out_sy = Math.floor((this.in_sy + this.pad * 2 - this.sy) / this.stride + 1); 585 this.layer_type = 'conv'; 586 587 // initializations 588 var bias = typeof opt.bias_pref !== 'undefined' ? opt.bias_pref : 0.0; 589 this.filters = []; 590 for(var i=0;i<this.out_depth;i++) { this.filters.push(new Vol(this.sx, this.sy, this.in_depth)); } 591 this.biases = new Vol(1, 1, this.out_depth, bias); 592 } 593 ConvLayer.prototype = { 594 forward: function(V, is_training) { 595 this.in_act = V; 596 597 var A = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); 598 for(var d=0;d<this.out_depth;d++) { 599 var f = this.filters[d]; 600 var x = -this.pad; 601 var y = -this.pad; 602 for(var ax=0; ax<this.out_sx; x+=this.stride,ax++) { 603 y = -this.pad; 604 for(var ay=0; ay<this.out_sy; y+=this.stride,ay++) { 605 606 // convolve centered at this particular location 607 // could be bit more efficient, going for correctness first 608 var a = 0.0; 609 for(var fx=0;fx<f.sx;fx++) { 610 for(var fy=0;fy<f.sy;fy++) { 611 for(var fd=0;fd<f.depth;fd++) { 612 var oy = y+fy; // coordinates in the original input array coordinates 613 var ox = x+fx; 614 if(oy>=0 && oy<V.sy && ox>=0 && ox<V.sx) { 615 //a += f.get(fx, fy, fd) * V.get(ox, oy, fd); 616 // avoid function call overhead for efficiency, compromise modularity :(
617 a += f.w[((f.sx * fy)+fx)*f.depth+fd] * V.w[((V.sx * oy)+ox)*V.depth+fd]; 618 } 619 } 620 } 621 } 622 a += this.biases.w[d]; 623 A.set(ax, ay, d, a); 624 } 625 } 626 } 627 this.out_act = A; 628 return this.out_act; 629 }, 630 backward: function() { 631 632 // compute gradient wrt weights, biases and input data 633 var V = this.in_act; 634 V.dw = global.zeros(V.w.length); // zero out gradient wrt bottom data, we're about to fill it 635 for(var d=0;d<this.out_depth;d++) { 636 var f = this.filters[d]; 637 var x = -this.pad; 638 var y = -this.pad; 639 for(var ax=0; ax<this.out_sx; x+=this.stride,ax++) { 640 y = -this.pad; 641 for(var ay=0; ay<this.out_sy; y+=this.stride,ay++) { 642 // convolve and add up the gradients. 643 // could be more efficient, going for correctness first 644 var chain_grad = this.out_act.get_grad(ax,ay,d); // gradient from above, from chain rule 645 for(var fx=0;fx<f.sx;fx++) { 646 for(var fy=0;fy<f.sy;fy++) { 647 for(var fd=0;fd<f.depth;fd++) { 648 var oy = y+fy; 649 var ox = x+fx; 650 if(oy>=0 && oy<V.sy && ox>=0 && ox<V.sx) { 651 // forward prop calculated: a += f.get(fx, fy, fd) * V.get(ox, oy, fd); 652 //f.add_grad(fx, fy, fd, V.get(ox, oy, fd) * chain_grad); 653 //V.add_grad(ox, oy, fd, f.get(fx, fy, fd) * chain_grad); 654 655 // avoid function call overhead and use Vols directly for efficiency 656 var ix1 = ((V.sx * oy)+ox)*V.depth+fd; 657 var ix2 = ((f.sx * fy)+fx)*f.depth+fd; 658 f.dw[ix2] += V.w[ix1]*chain_grad; 659 V.dw[ix1] += f.w[ix2]*chain_grad; 660 } 661 } 662 } 663 } 664 this.biases.dw[d] += chain_grad; 665 } 666 } 667 } 668 }, 669 getParamsAndGrads: function() { 670 var response = []; 671 for(var i=0;i<this.out_depth;i++) { 672 response.push({params: this.filters[i].w, grads: this.filters[i].dw, l2_decay_mul: this.l2_decay_mul, l1_decay_mul: this.l1_decay_mul}); 673 } 674 response.push({params: this.biases.w, grads: this.biases.dw, l1_decay_mul: 0.0, l2_decay_mul: 0.0}); 675 return response; 676 }, 677 toJSON: function() { 678 var json = {}; 679 json.sx = this.sx; // filter size in x, y dims 680 json.sy = this.sy; 681 json.stride = this.stride; 682 json.in_depth = this.in_depth; 683 json.out_depth = this.out_depth; 684 json.out_sx = this.out_sx; 685 json.out_sy = this.out_sy; 686 json.layer_type = this.layer_type; 687 json.l1_decay_mul = this.l1_decay_mul; 688 json.l2_decay_mul = this.l2_decay_mul; 689 json.pad = this.pad; 690 json.filters = []; 691 for(var i=0;i<this.filters.length;i++) { 692 json.filters.push(this.filters[i].toJSON()); 693 } 694 json.biases = this.biases.toJSON(); 695 return json; 696 }, 697 fromJSON: function(json) { 698 this.out_depth = json.out_depth; 699 this.out_sx = json.out_sx; 700 this.out_sy = json.out_sy; 701 this.layer_type = json.layer_type; 702 this.sx = json.sx; // filter size in x, y dims 703 this.sy = json.sy; 704 this.stride = json.stride; 705 this.in_depth = json.in_depth; // depth of input volume 706 this.filters = []; 707 this.l1_decay_mul = typeof json.l1_decay_mul !== 'undefined' ? json.l1_decay_mul : 1.0; 708 this.l2_decay_mul = typeof json.l2_decay_mul !== 'undefined' ? json.l2_decay_mul : 1.0; 709 this.pad = typeof json.pad !== 'undefined' ? json.pad : 0; 710 for(var i=0;i<json.filters.length;i++) { 711 var v = new Vol(0,0,0,0); 712 v.fromJSON(json.filters[i]); 713 this.filters.push(v); 714 } 715 this.biases = new Vol(0,0,0,0); 716 this.biases.fromJSON(json.biases); 717 } 718 } 719 720 var FullyConnLayer = function(opt) { 721 var opt = opt || {}; 722 723 // required 724 // ok fine we will allow 'filters' as the word as well 725 this.out_depth = typeof opt.num_neurons !== 'undefined' ? opt.num_neurons : opt.filters; 726 727 // optional 728 this.l1_decay_mul = typeof opt.l1_decay_mul !== 'undefined' ? opt.l1_decay_mul : 0.0; 729 this.l2_decay_mul = typeof opt.l2_decay_mul !== 'undefined' ? opt.l2_decay_mul : 1.0; 730 731 // computed 732 this.num_inputs = opt.in_sx * opt.in_sy * opt.in_depth; 733 this.out_sx = 1; 734 this.out_sy = 1; 735 this.layer_type = 'fc'; 736 737 // initializations 738 var bias = typeof opt.bias_pref !== 'undefined' ? opt.bias_pref : 0.0; 739 this.filters = []; 740 for(var i=0;i<this.out_depth ;i++) { this.filters.push(new Vol(1, 1, this.num_inputs)); } 741 this.biases = new Vol(1, 1, this.out_depth, bias); 742 } 743 744 FullyConnLayer.prototype = { 745 forward: function(V, is_training) { 746 this.in_act = V; 747 var A = new Vol(1, 1, this.out_depth, 0.0); 748 var Vw = V.w; 749 for(var i=0;i<this.out_depth;i++) { 750 var a = 0.0; 751 var wi = this.filters[i].w; 752 for(var d=0;d<this.num_inputs;d++) { 753 a += Vw[d] * wi[d]; // for efficiency use Vols directly for now 754 } 755 a += this.biases.w[i]; 756 A.w[i] = a; 757 } 758 this.out_act = A; 759 return this.out_act; 760 }, 761 backward: function() { 762 var V = this.in_act; 763 V.dw = global.zeros(V.w.length); // zero out the gradient in input Vol 764 765 // compute gradient wrt weights and data 766 for(var i=0;i<this.out_depth;i++) { 767 var tfi = this.filters[i]; 768 var chain_grad = this.out_act.dw[i]; 769 for(var d=0;d<this.num_inputs;d++) { 770 V.dw[d] += tfi.w[d]*chain_grad; // grad wrt input data 771 tfi.dw[d] += V.w[d]*chain_grad; // grad wrt params 772 } 773 this.biases.dw[i] += chain_grad; 774 } 775 }, 776 getParamsAndGrads: function() { 777 var response = []; 778 for(var i=0;i<this.out_depth;i++) { 779 response.push({params: this.filters[i].w, grads: this.filters[i].dw, l1_decay_mul: this.l1_decay_mul, l2_decay_mul: this.l2_decay_mul}); 780 } 781 response.push({params: this.biases.w, grads: this.biases.dw, l1_decay_mul: 0.0, l2_decay_mul: 0.0}); 782 return response; 783 }, 784 toJSON: function() { 785 var json = {}; 786 json.out_depth = this.out_depth; 787 json.out_sx = this.out_sx; 788 json.out_sy = this.out_sy; 789 json.layer_type = this.layer_type; 790 json.num_inputs = this.num_inputs; 791 json.l1_decay_mul = this.l1_decay_mul; 792 json.l2_decay_mul = this.l2_decay_mul; 793 json.filters = []; 794 for(var i=0;i<this.filters.length;i++) { 795 json.filters.push(this.filters[i].toJSON()); 796 } 797 json.biases = this.biases.toJSON(); 798 return json; 799 }, 800 fromJSON: function(json) { 801 this.out_depth = json.out_depth; 802 this.out_sx = json.out_sx; 803 this.out_sy = json.out_sy; 804 this.layer_type = json.layer_type; 805 this.num_inputs = json.num_inputs; 806 this.l1_decay_mul = typeof json.l1_decay_mul !== 'undefined' ? json.l1_decay_mul : 1.0; 807 this.l2_decay_mul = typeof json.l2_decay_mul !== 'undefined' ? json.l2_decay_mul : 1.0; 808 this.filters = []; 809 for(var i=0;i<json.filters.length;i++) { 810 var v = new Vol(0,0,0,0); 811 v.fromJSON(json.filters[i]); 812 this.filters.push(v); 813 } 814 this.biases = new Vol(0,0,0,0); 815 this.biases.fromJSON(json.biases); 816 } 817 } 818 819 global.ConvLayer = ConvLayer; 820 global.FullyConnLayer = FullyConnLayer; 821 822})(convnetjs); 823(function(global) { 824 "use strict"; 825 var Vol = global.Vol; // convenience 826 827 var PoolLayer = function(opt) { 828 829 var opt = opt || {}; 830 831 // required 832 this.sx = opt.sx;
832 // filter size 833 this.in_depth = opt.in_depth; 834 this.in_sx = opt.in_sx; 835 this.in_sy = opt.in_sy; 836 837 // optional 838 this.sy = typeof opt.sy !== 'undefined' ? opt.sy : this.sx; 839 this.stride = typeof opt.stride !== 'undefined' ? opt.stride : 2; 840 this.pad = typeof opt.pad !== 'undefined' ? opt.pad : 0; // amount of 0 padding to add around borders of input volume 841 842 // computed 843 this.out_depth = this.in_depth; 844 this.out_sx = Math.floor((this.in_sx + this.pad * 2 - this.sx) / this.stride + 1); 845 this.out_sy = Math.floor((this.in_sy + this.pad * 2 - this.sy) / this.stride + 1); 846 this.layer_type = 'pool'; 847 // store switches for x,y coordinates for where the max comes from, for each output neuron 848 this.switchx = global.zeros(this.out_sx*this.out_sy*this.out_depth); 849 this.switchy = global.zeros(this.out_sx*this.out_sy*this.out_depth); 850 } 851 852 PoolLayer.prototype = { 853 forward: function(V, is_training) { 854 this.in_act = V; 855 856 var A = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); 857 858 var n=0; // a counter for switches 859 for(var d=0;d<this.out_depth;d++) { 860 var x = -this.pad; 861 var y = -this.pad; 862 for(var ax=0; ax<this.out_sx; x+=this.stride,ax++) { 863 y = -this.pad; 864 for(var ay=0; ay<this.out_sy; y+=this.stride,ay++) { 865 866 // convolve centered at this particular location 867 var a = -99999; // hopefully small enough ;\ 868 var winx=-1,winy=-1; 869 for(var fx=0;fx<this.sx;fx++) { 870 for(var fy=0;fy<this.sy;fy++) { 871 var oy = y+fy; 872 var ox = x+fx; 873 if(oy>=0 && oy<V.sy && ox>=0 && ox<V.sx) { 874 var v = V.get(ox, oy, d); 875 // perform max pooling and store pointers to where 876 // the max came from. This will speed up backprop 877 // and can help make nice visualizations in future 878 if(v > a) { a = v; winx=ox; winy=oy;} 879 } 880 } 881 } 882 this.switchx[n] = winx; 883 this.switchy[n] = winy; 884 n++; 885 A.set(ax, ay, d, a); 886 } 887 } 888 } 889 this.out_act = A; 890 return this.out_act; 891 }, 892 backward: function() { 893 // pooling layers have no parameters, so simply compute 894 // gradient wrt data here 895 var V = this.in_act; 896 V.dw = global.zeros(V.w.length); // zero out gradient wrt data 897 var A = this.out_act; // computed in forward pass 898 899 var n = 0; 900 for(var d=0;d<this.out_depth;d++) { 901 var x = -this.pad; 902 var y = -this.pad; 903 for(var ax=0; ax<this.out_sx; x+=this.stride,ax++) { 904 y = -this.pad; 905 for(var ay=0; ay<this.out_sy; y+=this.stride,ay++) { 906 907 var chain_grad = this.out_act.get_grad(ax,ay,d); 908 V.add_grad(this.switchx[n], this.switchy[n], d, chain_grad); 909 n++; 910 911 } 912 } 913 } 914 }, 915 getParamsAndGrads: function() { 916 return []; 917 }, 918 toJSON: function() { 919 var json = {}; 920 json.sx = this.sx; 921 json.sy = this.sy; 922 json.stride = this.stride; 923 json.in_depth = this.in_depth; 924 json.out_depth = this.out_depth; 925 json.out_sx = this.out_sx; 926 json.out_sy = this.out_sy; 927 json.layer_type = this.layer_type; 928 json.pad = this.pad; 929 return json; 930 }, 931 fromJSON: function(json) { 932 this.out_depth = json.out_depth; 933 this.out_sx = json.out_sx; 934 this.out_sy = json.out_sy; 935 this.layer_type = json.layer_type; 936 this.sx = json.sx; 937 this.sy = json.sy; 938 this.stride = json.stride; 939 this.in_depth = json.in_depth; 940 this.pad = typeof json.pad !== 'undefined' ? json.pad : 0; // backwards compatibility 941 this.switchx = global.zeros(this.out_sx*this.out_sy*this.out_depth); // need to re-init these appropriately 942 this.switchy = global.zeros(this.out_sx*this.out_sy*this.out_depth); 943 } 944 } 945 946 global.PoolLayer = PoolLayer; 947 948})(convnetjs); 949 950(function(global) { 951 "use strict"; 952 var Vol = global.Vol; // convenience 953 954 var InputLayer = function(opt) { 955 var opt = opt || {}; 956 957 // this is a bit silly but lets allow people to specify either ins or outs 958 this.out_sx = typeof opt.out_sx !== 'undefined' ? opt.out_sx : opt.in_sx; 959 this.out_sy = typeof opt.out_sy !== 'undefined' ? opt.out_sy : opt.in_sy; 960 this.out_depth = typeof opt.out_depth !== 'undefined' ? opt.out_depth : opt.in_depth; 961 this.layer_type = 'input'; 962 } 963 InputLayer.prototype = { 964 forward: function(V, is_training) { 965 this.in_act = V; 966 this.out_act = V; 967 return this.out_act; // dummy identity function for now 968 }, 969 backward: function() { }, 970 getParamsAndGrads: function() { 971 return []; 972 }, 973 toJSON: function() { 974 var json = {}; 975 json.out_depth = this.out_depth; 976 json.out_sx = this.out_sx; 977 json.out_sy = this.out_sy; 978 json.layer_type = this.layer_type; 979 return json; 980 }, 981 fromJSON: function(json) { 982 this.out_depth = json.out_depth; 983 this.out_sx = json.out_sx; 984 this.out_sy = json.out_sy; 985 this.layer_type = json.layer_type; 986 } 987 } 988 989 global.InputLayer = InputLayer; 990})(convnetjs); 991(function(global) { 992 "use strict"; 993 var Vol = global.Vol; // convenience 994 995 // Layers that implement a loss. Currently these are the layers that 996 // can initiate a backward() pass. In future we probably want a more 997 // flexible system that can accomodate multiple losses to do multi-task 998 // learning, and stuff like that. But for now, one of the layers in this 999 // file must be the final layer in a Net. 1000 1001 // This is a classifier, with N discrete classes from 0 to N-1 1002 // it gets a stream of N incoming numbers and computes the softmax 1003 // function (exponentiate and normalize to sum to 1 as probabilities should) 1004 var SoftmaxLayer = function(opt) { 1005 var opt = opt || {}; 1006 1007 // computed 1008 this.num_inputs = opt.in_sx * opt.in_sy * opt.in_depth; 1009 this.out_depth = this.num_inputs; 1010 this.out_sx = 1; 1011 this.out_sy = 1; 1012 this.layer_type = 'softmax'; 1013 } 1014 1015 SoftmaxLayer.prototype = { 1016 forward: function(V, is_training) { 1017 this.in_act = V; 1018 1019 var A = new Vol(1, 1, this.out_depth, 0.0); 1020 1021 // compute max activation 1022 var as = V.w; 1023 var amax = V.w[0]; 1024 for(var i=1;i<this.out_depth;i++) { 1025 if(as[i] > amax) amax = as[i]; 1026 } 1027 1028 // compute exponentials (carefully to not blow up) 1029 var es = global.zeros(this.out_depth); 1030 var esum = 0.0; 1031 for(var i=0;i<this.out_depth;i++) { 1032 var e = Math.exp(as[i] - amax); 1033 esum += e; 1034 es[i] = e; 1035 } 1036 1037 // normalize and output to sum to one 1038 for(var i=0;i<this.out_depth;i++) { 1039 es[i] /= esum; 1040 A.w[i] = es[i]; 1041 } 1042 1043 this.es = es; // save these for backprop 1044 this.out_act = A; 1045 return this.out_act; 1046 }, 1047 backward: function(y) { 1048 1049 // compute and accumulate gradient wrt weights and bias of this layer 1050 var x = this.in_act; 1051 x.dw = global.zeros(x.w.length); // zero out the gradient of input Vol 1052 1053 for(var i=0;i<this.out_depth;i++) { 1054 var indicator = i === y ? 1.0 : 0.0; 1055 var mul = -(indicator - this.es[i]); 1056 x.dw[i] = mul; 1057 } 1058 1059 // loss is the class negative log likelihood 1060 return -Math.log(this.es[y]); 1061 }, 1062 getParamsAndGrads: function() { 1063 return []; 1064 }, 1065 toJSON: function() { 1066 var json = {}; 1067 json.out_depth = this.out_depth; 1068 json.out_sx = this.out_sx; 1069 json.out_sy = this.out_sy; 1070 json.layer_type = this.layer_type; 1071 json.num_inputs = this.num_inputs; 1072 return json; 1073 }, 1074 fromJSON: function(json) { 1075 this.out_depth = json.out_depth; 1076 this.out_sx = json.out_sx; 1077 this.out_sy = json.out_sy; 1078 this.layer_type = json.layer_type; 1079 this.num_inputs = json.num_inputs; 1080 } 1081 } 1082 1083 // implements an L2 regression cost layer,
1084 // so penalizes \sum_i(||x_i - y_i||^2), where x is its input 1085 // and y is the user-provided array of "correct" values. 1086 var RegressionLayer = function(opt) { 1087 var opt = opt || {}; 1088 1089 // computed 1090 this.num_inputs = opt.in_sx * opt.in_sy * opt.in_depth; 1091 this.out_depth = this.num_inputs; 1092 this.out_sx = 1; 1093 this.out_sy = 1; 1094 this.layer_type = 'regression'; 1095 } 1096 1097 RegressionLayer.prototype = { 1098 forward: function(V, is_training) { 1099 this.in_act = V; 1100 this.out_act = V; 1101 return V; // identity function 1102 }, 1103 // y is a list here of size num_inputs 1104 backward: function(y) { 1105 1106 // compute and accumulate gradient wrt weights and bias of this layer 1107 var x = this.in_act; 1108 x.dw = global.zeros(x.w.length); // zero out the gradient of input Vol 1109 var loss = 0.0; 1110 if(y instanceof Array || y instanceof Float64Array) { 1111 for(var i=0;i<this.out_depth;i++) { 1112 var dy = x.w[i] - y[i]; 1113 x.dw[i] = dy; 1114 loss += 2*dy*dy; 1115 } 1116 } else { 1117 // assume it is a struct with entries .dim and .val 1118 // and we pass gradient only along dimension dim to be equal to val 1119 var i = y.dim; 1120 var yi = y.val; 1121 var dy = x.w[i] - yi; 1122 x.dw[i] = dy; 1123 loss += 2*dy*dy; 1124 } 1125 return loss; 1126 }, 1127 getParamsAndGrads: function() { 1128 return []; 1129 }, 1130 toJSON: function() { 1131 var json = {}; 1132 json.out_depth = this.out_depth; 1133 json.out_sx = this.out_sx; 1134 json.out_sy = this.out_sy; 1135 json.layer_type = this.layer_type; 1136 json.num_inputs = this.num_inputs; 1137 return json; 1138 }, 1139 fromJSON: function(json) { 1140 this.out_depth = json.out_depth; 1141 this.out_sx = json.out_sx; 1142 this.out_sy = json.out_sy; 1143 this.layer_type = json.layer_type; 1144 this.num_inputs = json.num_inputs; 1145 } 1146 } 1147 1148 var SVMLayer = function(opt) { 1149 var opt = opt || {}; 1150 1151 // computed 1152 this.num_inputs = opt.in_sx * opt.in_sy * opt.in_depth; 1153 this.out_depth = this.num_inputs; 1154 this.out_sx = 1; 1155 this.out_sy = 1; 1156 this.layer_type = 'svm'; 1157 } 1158 1159 SVMLayer.prototype = { 1160 forward: function(V, is_training) { 1161 this.in_act = V; 1162 this.out_act = V; // nothing to do, output raw scores 1163 return V; 1164 }, 1165 backward: function(y) { 1166 1167 // compute and accumulate gradient wrt weights and bias of this layer 1168 var x = this.in_act; 1169 x.dw = global.zeros(x.w.length); // zero out the gradient of input Vol 1170 1171 var yscore = x.w[y]; // score of ground truth 1172 var margin = 1.0; 1173 var loss = 0.0; 1174 for(var i=0;i<this.out_depth;i++) { 1175 if(-yscore + x.w[i] + margin > 0) { 1176 // violating example, apply loss 1177 // I love hinge loss, by the way. Truly. 1178 // Seriously, compare this SVM code with Softmax forward AND backprop code above 1179 // it's clear which one is superior, not only in code, simplicity 1180 // and beauty, but also in practice. 1181 x.dw[i] += 1; 1182 x.dw[y] -= 1; 1183 loss += -yscore + x.w[i] + margin; 1184 } 1185 } 1186 1187 return loss; 1188 }, 1189 getParamsAndGrads: function() { 1190 return []; 1191 }, 1192 toJSON: function() { 1193 var json = {}; 1194 json.out_depth = this.out_depth; 1195 json.out_sx = this.out_sx; 1196 json.out_sy = this.out_sy; 1197 json.layer_type = this.layer_type; 1198 json.num_inputs = this.num_inputs; 1199 return json; 1200 }, 1201 fromJSON: function(json) { 1202 this.out_depth = json.out_depth; 1203 this.out_sx = json.out_sx; 1204 this.out_sy = json.out_sy; 1205 this.layer_type = json.layer_type; 1206 this.num_inputs = json.num_inputs; 1207 } 1208 } 1209 1210 global.RegressionLayer = RegressionLayer; 1211 global.SoftmaxLayer = SoftmaxLayer; 1212 global.SVMLayer = SVMLayer; 1213 1214})(convnetjs); 1215 1216(function(global) { 1217 "use strict"; 1218 var Vol = global.Vol; // convenience 1219 1220 // Implements ReLU nonlinearity elementwise 1221 // x -> max(0, x) 1222 // the output is in [0, inf) 1223 var ReluLayer = function(opt) { 1224 var opt = opt || {}; 1225 1226 // computed 1227 this.out_sx = opt.in_sx; 1228 this.out_sy = opt.in_sy; 1229 this.out_depth = opt.in_depth; 1230 this.layer_type = 'relu';
1231 } 1232 ReluLayer.prototype = { 1233 forward: function(V, is_training) { 1234 this.in_act = V; 1235 var V2 = V.clone(); 1236 var N = V.w.length; 1237 var V2w = V2.w; 1238 for(var i=0;i<N;i++) { 1239 if(V2w[i] < 0) V2w[i] = 0; // threshold at 0 1240 } 1241 this.out_act = V2; 1242 return this.out_act; 1243 }, 1244 backward: function() { 1245 var V = this.in_act; // we need to set dw of this 1246 var V2 = this.out_act; 1247 var N = V.w.length; 1248 V.dw = global.zeros(N); // zero out gradient wrt data 1249 for(var i=0;i<N;i++) { 1250 if(V2.w[i] <= 0) V.dw[i] = 0; // threshold 1251 else V.dw[i] = V2.dw[i]; 1252 } 1253 }, 1254 getParamsAndGrads: function() { 1255 return []; 1256 }, 1257 toJSON: function() { 1258 var json = {}; 1259 json.out_depth = this.out_depth; 1260 json.out_sx = this.out_sx; 1261 json.out_sy = this.out_sy; 1262 json.layer_type = this.layer_type; 1263 return json; 1264 }, 1265 fromJSON: function(json) { 1266 this.out_depth = json.out_depth; 1267 this.out_sx = json.out_sx; 1268 this.out_sy = json.out_sy; 1269 this.layer_type = json.layer_type; 1270 } 1271 } 1272 1273 // Implements Sigmoid nnonlinearity elementwise 1274 // x -> 1/(1+e^(-x)) 1275 // so the output is between 0 and 1. 1276 var SigmoidLayer = function(opt) { 1277 var opt = opt || {}; 1278 1279 // computed 1280 this.out_sx = opt.in_sx; 1281 this.out_sy = opt.in_sy; 1282 this.out_depth = opt.in_depth; 1283 this.layer_type = 'sigmoid'; 1284 } 1285 SigmoidLayer.prototype = { 1286 forward: function(V, is_training) { 1287 this.in_act = V; 1288 var V2 = V.cloneAndZero(); 1289 var N = V.w.length; 1290 var V2w = V2.w; 1291 var Vw = V.w; 1292 for(var i=0;i<N;i++) { 1293 V2w[i] = 1.0/(1.0+Math.exp(-Vw[i])); 1294 } 1295 this.out_act = V2; 1296 return this.out_act; 1297 }, 1298 backward: function() { 1299 var V = this.in_act; // we need to set dw of this 1300 var V2 = this.out_act; 1301 var N = V.w.length; 1302 V.dw = global.zeros(N); // zero out gradient wrt data 1303 for(var i=0;i<N;i++) { 1304 var v2wi = V2.w[i]; 1305 V.dw[i] = v2wi * (1.0 - v2wi) * V2.dw[i]; 1306 } 1307 }, 1308 getParamsAndGrads: function() { 1309 return []; 1310 }, 1311 toJSON: function() { 1312 var json = {}; 1313 json.out_depth = this.out_depth; 1314 json.out_sx = this.out_sx; 1315 json.out_sy = this.out_sy; 1316 json.layer_type = this.layer_type; 1317 return json; 1318 }, 1319 fromJSON: function(json) { 1320 this.out_depth = json.out_depth; 1321 this.out_sx = json.out_sx; 1322 this.out_sy = json.out_sy; 1323 this.layer_type = json.layer_type; 1324 } 1325 } 1326 1327 // Implements Maxout nnonlinearity that computes 1328 // x -> max(x) 1329 // where x is a vector of size group_size. Ideally of course, 1330 // the input size should be exactly divisible by group_size 1331 var MaxoutLayer = function(opt) { 1332 var opt = opt || {}; 1333 1334 // required 1335 this.group_size = typeof opt.group_size !== 'undefined' ? opt.group_size : 2; 1336 1337 // computed 1338 this.out_sx = opt.in_sx; 1339 this.out_sy = opt.in_sy; 1340 this.out_depth = Math.floor(opt.in_depth / this.group_size); 1341 this.layer_type = 'maxout'; 1342 1343 this.switches = global.zeros(this.out_sx*this.out_sy*this.out_depth); // useful for backprop 1344 } 1345 MaxoutLayer.prototype = { 1346 forward: function(V, is_training) { 1347 this.in_act = V; 1348 var N = this.out_depth; 1349 var V2 = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); 1350 1351 // optimization branch. If we're operating on 1D arrays we dont have 1352 // to worry about keeping track of x,y,d coordinates inside 1353 // input volumes. In convnets we do :( 1354 if(this.out_sx === 1 && this.out_sy === 1) { 1355 for(var i=0;i<N;i++) { 1356 var ix = i * this.group_size; // base index offset 1357 var a = V.w[ix]; 1358 var ai = 0; 1359 for(var j=1;j<this.group_size;j++) { 1360 var a2 = V.w[ix+j]; 1361 if(a2 > a) { 1362 a = a2; 1363 ai = j; 1364 } 1365 } 1366 V2.w[i] = a; 1367 this.switches[i] = ix + ai; 1368 } 1369 } else { 1370 var n=0; // counter for switches 1371 for(var x=0;x<V.sx;x++) { 1372 for(var y=0;y<V.sy;y++) { 1373 for(var i=0;i<N;i++) { 1374 var ix = i * this.group_size; 1375 var a = V.get(x, y, ix); 1376 var ai = 0; 1377 for(var j=1;j<this.group_size;j++) { 1378 var a2 = V.get(x, y, ix+j); 1379 if(a2 > a) { 1380 a = a2; 1381 ai = j; 1382 } 1383 } 1384 V2.set(x,y,i,a); 1385 this.switches[n] = ix + ai; 1386 n++; 1387 } 1388 } 1389 } 1390 1391 } 1392 this.out_act = V2; 1393 return this.out_act; 1394 }, 1395 backward: function() { 1396 var V = this.in_act; // we need to set dw of this 1397 var V2 = this.out_act; 1398 var N = this.out_depth; 1399 V.dw = global.zeros(V.w.length); // zero out gradient wrt data 1400 1401 // pass the gradient through the appropriate switch 1402 if(this.out_sx === 1 && this.out_sy === 1) { 1403 for(var i=0;i<N;i++) { 1404 var chain_grad = V2.dw[i]; 1405 V.dw[this.switches[i]] = chain_grad; 1406 } 1407 } else {
1408 // bleh okay, lets do this the hard way 1409 var n=0; // counter for switches 1410 for(var x=0;x<V2.sx;x++) { 1411 for(var y=0;y<V2.sy;y++) { 1412 for(var i=0;i<N;i++) { 1413 var chain_grad = V2.get_grad(x,y,i); 1414 V.set_grad(x,y,this.switches[n],chain_grad); 1415 n++; 1416 } 1417 } 1418 } 1419 } 1420 }, 1421 getParamsAndGrads: function() { 1422 return []; 1423 }, 1424 toJSON: function() { 1425 var json = {}; 1426 json.out_depth = this.out_depth; 1427 json.out_sx = this.out_sx; 1428 json.out_sy = this.out_sy; 1429 json.layer_type = this.layer_type; 1430 json.group_size = this.group_size; 1431 return json; 1432 }, 1433 fromJSON: function(json) { 1434 this.out_depth = json.out_depth; 1435 this.out_sx = json.out_sx; 1436 this.out_sy = json.out_sy; 1437 this.layer_type = json.layer_type; 1438 this.group_size = json.group_size; 1439 this.switches = global.zeros(this.group_size); 1440 } 1441 } 1442 1443 // a helper function, since tanh is not yet part of ECMAScript. Will be in v6. 1444 function tanh(x) { 1445 var y = Math.exp(2 * x); 1446 return (y - 1) / (y + 1); 1447 } 1448 // Implements Tanh nnonlinearity elementwise 1449 // x -> tanh(x) 1450 // so the output is between -1 and 1. 1451 var TanhLayer = function(opt) { 1452 var opt = opt || {}; 1453 1454 // computed 1455 this.out_sx = opt.in_sx; 1456 this.out_sy = opt.in_sy; 1457 this.out_depth = opt.in_depth; 1458 this.layer_type = 'tanh'; 1459 } 1460 TanhLayer.prototype = { 1461 forward: function(V, is_training) { 1462 this.in_act = V; 1463 var V2 = V.cloneAndZero(); 1464 var N = V.w.length; 1465 for(var i=0;i<N;i++) { 1466 V2.w[i] = tanh(V.w[i]); 1467 } 1468 this.out_act = V2; 1469 return this.out_act; 1470 }, 1471 backward: function() { 1472 var V = this.in_act; // we need to set dw of this 1473 var V2 = this.out_act; 1474 var N = V.w.length; 1475 V.dw = global.zeros(N); // zero out gradient wrt data 1476 for(var i=0;i<N;i++) { 1477 var v2wi = V2.w[i]; 1478 V.dw[i] = (1.0 - v2wi * v2wi) * V2.dw[i]; 1479 } 1480 }, 1481 getParamsAndGrads: function() { 1482 return []; 1483 }, 1484 toJSON: function() { 1485 var json = {}; 1486 json.out_depth = this.out_depth; 1487 json.out_sx = this.out_sx; 1488 json.out_sy = this.out_sy; 1489 json.layer_type = this.layer_type; 1490 return json; 1491 }, 1492 fromJSON: function(json) { 1493 this.out_depth = json.out_depth; 1494 this.out_sx = json.out_sx; 1495 this.out_sy = json.out_sy; 1496 this.layer_type = json.layer_type; 1497 } 1498 } 1499 1500 global.TanhLayer = TanhLayer; 1501 global.MaxoutLayer = MaxoutLayer; 1502 global.ReluLayer = ReluLayer; 1503 global.SigmoidLayer = SigmoidLayer; 1504 1505})(convnetjs); 1506 1507(function(global) { 1508 "use strict"; 1509 var Vol = global.Vol; // convenience 1510 1511 // An inefficient dropout layer 1512 // Note this is not most efficient implementation since the layer before 1513 // computed all these activations and now we're just going to drop them :( 1514 // same goes for backward pass. Also, if we wanted to be efficient at test time 1515 // we could equivalently be clever and upscale during train and copy pointers during test 1516 // todo: make more efficient. 1517 var DropoutLayer = function(opt) { 1518 var opt = opt || {}; 1519 1520 // computed 1521 this.out_sx = opt.in_sx; 1522 this.out_sy = opt.in_sy; 1523 this.out_depth = opt.in_depth; 1524 this.layer_type = 'dropout'; 1525 this.drop_prob = typeof opt.drop_prob !== 'undefined' ? opt.drop_prob : 0.5; 1526 this.dropped = global.zeros(this.out_sx*this.out_sy*this.out_depth); 1527 } 1528 DropoutLayer.prototype = { 1529 forward: function(V, is_training) { 1530 this.in_act = V; 1531 if(typeof(is_training)==='undefined') { is_training = false; } // default is prediction mode 1532 var V2 = V.clone(); 1533 var N = V.w.length; 1534 if(is_training) { 1535 // do dropout 1536 for(var i=0;i<N;i++) { 1537 if(Math.random()<this.drop_prob) { V2.w[i]=0; this.dropped[i] = true; } // drop! 1538 else {this.dropped[i] = false;} 1539 } 1540 } else { 1541 // scale the activations during prediction 1542 for(var i=0;i<N;i++) { V2.w[i]*=this.drop_prob; } 1543 } 1544 this.out_act = V2; 1545 return this.out_act; // dummy identity function for now 1546 }, 1547 backward: function() { 1548 var V = this.in_act; // we need to set dw of this 1549 var chain_grad = this.out_act; 1550 var N = V.w.length; 1551 V.dw = global.zeros(N); // zero out gradient wrt data 1552 for(var i=0;i<N;i++) { 1553 if(!(this.dropped[i])) { 1554 V.dw[i] = chain_grad.dw[i]; // copy over the gradient 1555 } 1556 } 1557 }, 1558 getParamsAndGrads: function() { 1559 return []; 1560 }, 1561 toJSON: function() { 1562 var json = {}; 1563 json.out_depth = this.out_depth; 1564 json.out_sx = this.out_sx; 1565 json.out_sy = this.out_sy; 1566 json.layer_type = this.layer_type; 1567 json.drop_prob = this.drop_prob; 1568 return json; 1569 }, 1570 fromJSON: function(json) { 1571 this.out_depth = json.out_depth; 1572 this.out_sx = json.out_sx; 1573 this.out_sy = json.out_sy; 1574 this.layer_type = json.layer_type; 1575 this.drop_prob = json.drop_prob; 1576 } 1577 } 1578 1579 1580 global.DropoutLayer = DropoutLayer; 1581})(convnetjs); 1582(function(global) { 1583 "use strict"; 1584 var Vol = global.Vol; // convenience 1585 1586 // a bit experimental layer for now. I think it works but I'm not 100% 1587 // the gradient check is a bit funky. I'll look into this a bit later. 1588 // Local Response Normalization in window, along depths of volumes 1589 var LocalResponseNormalizationLayer = function(opt) { 1590 var opt = opt || {}; 1591 1592 // required 1593 this.k = opt.k; 1594 this.n = opt.n; 1595 this.alpha = opt.alpha; 1596 this.beta = opt.beta; 1597 1598 // computed 1599 this.out_sx = opt.in_sx; 1600 this.out_sy = opt.in_sy; 1601 this.out_depth = opt.in_depth; 1602 this.layer_type = 'lrn'; 1603 1604 // checks 1605 if(this.n%2 === 0) { console.log('WARNING n should be odd for LRN layer'); } 1606 } 1607 LocalResponseNormalizationLayer.prototype = { 1608 forward: function(V, is_training) { 1609 this.in_act = V; 1610 1611 var A = V.cloneAndZero(); 1612 this.S_cache_ = V.cloneAndZero(); 1613 var n2 = Math.floor(this.n/2); 1614 for(var x=0;x<V.sx;x++) { 1615 for(var y=0;y<V.sy;y++) { 1616 for(var i=0;i<V.depth;i++) { 1617 1618 var ai = V.get(x,y,i); 1619 1620 // normalize in a window of size n 1621 var den = 0.0; 1622 for(var j=Math.max(0,i-n2);j<=Math.min(i+n2,V.depth-1);j++) { 1623 var aa = V.get(x,y,j); 1624 den += aa*aa; 1625 } 1626 den *= this.alpha / this.n; 1627 den += this.k; 1628 this.S_cache_.set(x,y,i,den); // will be useful for backprop 1629 den = Math.pow(den, this.beta); 1630 A.set(x,y,i,ai/den); 1631 } 1632 } 1633 } 1634 1635 this.out_act = A; 1636 return this.out_act; // dummy identity function for now 1637 }, 1638 backward: function() { 1639 // evaluate gradient wrt data 1640 var V = this.in_act; // we need to set dw of this 1641 V.dw = global.zeros(V.w.length); // zero out gradient wrt data 1642 var A = this.out_act; // computed in forward pass 1643 1644 var n2 = Math.floor(this.n/2); 1645 for(var x=0;x<V.sx;x++) { 1646 for(var y=0;y<V.sy;y++) { 1647 for(var i=0;i<V.depth;i++) { 1648 1649 var chain_grad = this.out_act.get_grad(x,y,i); 1650 var S = this.S_cache_.get(x,y,i); 1651 var SB = Math.pow(S, this.beta); 1652 var SB2 = SB*SB; 1653 1654 // normalize in a window of size n 1655 for(var j=Math.max(0,i-n2);j<=Math.min(i+n2,V.depth-1);j++) { 1656 var aj = V.get(x,y,j); 1657 var g = -aj*this.beta*Math.pow(S,this.beta-1)*this.alpha/this.n*2*aj; 1658 if(j===i) g+= SB; 1659 g /= SB2; 1660 g *= chain_grad; 1661 V.add_grad(x,y,j,g); 1662 } 1663 1664 } 1665 } 1666 } 1667 }, 1668 getParamsAndGrads: function() { return []; }, 1669 toJSON: function() { 1670 var json = {}; 1671 json.k = this.k; 1672 json.n = this.n; 1673 json.alpha = this.alpha; // normalize by size 1674 json.beta = this.beta; 1675 json.out_sx = this.out_sx; 1676 json.out_sy = this.out_sy; 1677 json.out_depth = this.out_depth; 1678 json.layer_type = this.layer_type; 1679 return json; 1680 }, 1681 fromJSON: function(json) { 1682 this.k = json.k; 1683 this.n = json.n; 1684 this.alpha = json.alpha; // normalize by size 1685 this.beta = json.beta; 1686 this.out_sx = json.out_sx; 1687 this.out_sy = json.out_sy; 1688 this.out_depth = json.out_depth; 1689 this.layer_type = json.layer_type; 1690 } 1691 } 1692 1693 1694 global.LocalResponseNormalizationLayer = LocalResponseNormalizationLayer; 1695})(convnetjs); 1696(function(global) { 1697 "use strict"; 1698 var Vol = global.Vol; // convenience 1699 1700 // transforms x-> [x, x_i*x_j forall i,j] 1701 // so the fully connected layer afters will essentially be doing tensor multiplies 1702 var QuadTransformLayer = function(opt) { 1703 var opt = opt || {}; 1704 1705 // computed 1706 this.out_sx = opt.in_sx; 1707 this.out_sy = opt.in_sy; 1708 // linear terms, and then quadratic terms, of which there are 1/2*n*(n+1), 1709 // (offdiagonals and the diagonal total) and arithmetic series. 1710 // Actually never mind, lets not be fancy here yet and just include
1711 // terms x_ix_j and x_jx_i twice. Half as efficient but much less 1712 // headache. 1713 this.out_depth = opt.in_depth + opt.in_depth * opt.in_depth; 1714 this.layer_type = 'quadtransform'; 1715 1716 } 1717 QuadTransformLayer.prototype = { 1718 forward: function(V, is_training) { 1719 this.in_act = V; 1720 var N = this.out_depth; 1721 var Ni = V.depth; 1722 var V2 = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); 1723 for(var x=0;x<V.sx;x++) { 1724 for(var y=0;y<V.sy;y++) { 1725 for(var i=0;i<N;i++) { 1726 if(i<Ni) { 1727 V2.set(x,y,i,V.get(x,y,i)); // copy these over (linear terms) 1728 } else { 1729 var i0 = Math.floor((i-Ni)/Ni); 1730 var i1 = (i-Ni) - i0*Ni; 1731 V2.set(x,y,i,V.get(x,y,i0) * V.get(x,y,i1)); // quadratic 1732 } 1733 } 1734 } 1735 } 1736 this.out_act = V2; 1737 return this.out_act; // dummy identity function for now 1738 }, 1739 backward: function() { 1740 var V = this.in_act; 1741 V.dw = global.zeros(V.w.length); // zero out gradient wrt data 1742 var V2 = this.out_act; 1743 var N = this.out_depth; 1744 var Ni = V.depth; 1745 for(var x=0;x<V.sx;x++) { 1746 for(var y=0;y<V.sy;y++) { 1747 for(var i=0;i<N;i++) { 1748 var chain_grad = V2.get_grad(x,y,i); 1749 if(i<Ni) { 1750 V.add_grad(x,y,i,chain_grad); 1751 } else { 1752 var i0 = Math.floor((i-Ni)/Ni); 1753 var i1 = (i-Ni) - i0*Ni; 1754 V.add_grad(x,y,i0,V.get(x,y,i1)*chain_grad); 1755 V.add_grad(x,y,i1,V.get(x,y,i0)*chain_grad); 1756 } 1757 } 1758 } 1759 } 1760 }, 1761 getParamsAndGrads: function() { 1762 return []; 1763 }, 1764 toJSON: function() { 1765 var json = {}; 1766 json.out_depth = this.out_depth; 1767 json.out_sx = this.out_sx; 1768 json.out_sy = this.out_sy; 1769 json.layer_type = this.layer_type; 1770 return json; 1771 }, 1772 fromJSON: function(json) { 1773 this.out_depth = json.out_depth; 1774 this.out_sx = json.out_sx; 1775 this.out_sy = json.out_sy; 1776 this.layer_type = json.layer_type; 1777 } 1778 } 1779 1780 1781 global.QuadTransformLayer = QuadTransformLayer; 1782})(convnetjs); 1783(function(global) { 1784 "use strict"; 1785 var Vol = global.Vol; // convenience 1786 1787 // Net manages a set of layers 1788 // For now constraints: Simple linear order of layers, first layer input last layer a cost layer 1789 var Net = function(options) { 1790 this.layers = []; 1791 } 1792 1793 Net.prototype = { 1794 1795 // takes a list of layer definitions and creates the network layer objects 1796 makeLayers: function(defs) { 1797 1798 // few checks for now 1799 if(defs.length<2) {console.log('ERROR! For now at least have input and softmax layers.');} 1800 if(defs[0].type !== 'input') {console.log('ERROR! For now first layer should be input.');} 1801 1802 // desugar syntactic for adding activations and dropouts 1803 var desugar = function() { 1804 var new_defs = []; 1805 for(var i=0;i<defs.length;i++) { 1806 var def = defs[i]; 1807 1808 if(def.type==='softmax' || def.type==='svm') { 1809 // add an fc layer here, there is no reason the user should 1810 // have to worry about this and we almost always want to 1811 new_defs.push({type:'fc', num_neurons: def.num_classes}); 1812 } 1813 1814 if(def.type==='regression') { 1815 // add an fc layer here, there is no reason the user should 1816 // have to worry about this and we almost always want to 1817 new_defs.push({type:'fc', num_neurons: def.num_neurons}); 1818 } 1819 1820 if((def.type==='fc' || def.type==='conv') 1821 && typeof(def.bias_pref) === 'undefined'){ 1822 def.bias_pref = 0.0; 1823 if(typeof def.activation !== 'undefined' && def.activation === 'relu') { 1824 def.bias_pref = 0.1; // relus like a bit of positive bias to get gradients early 1825 // otherwise it's technically possible that a relu unit will never turn on (by chance) 1826 // and will never get any gradient and never contribute any computation. Dead relu. 1827 } 1828 } 1829 1830 if(typeof def.tensor !== 'undefined') { 1831 // apply quadratic transform so that the upcoming multiply will include 1832 // quadratic terms, equivalent to doing a tensor product 1833 if(def.tensor) {
1834 new_defs.push({type: 'quadtransform'}); 1835 } 1836 } 1837 1838 new_defs.push(def); 1839 1840 if(typeof def.activation !== 'undefined') { 1841 if(def.activation==='relu') { new_defs.push({type:'relu'}); } 1842 else if (def.activation==='sigmoid') { new_defs.push({type:'sigmoid'}); } 1843 else if (def.activation==='tanh') { new_defs.push({type:'tanh'}); } 1844 else if (def.activation==='maxout') { 1845 // create maxout activation, and pass along group size, if provided 1846 var gs = def.group_size !== 'undefined' ? def.group_size : 2; 1847 new_defs.push({type:'maxout', group_size:gs}); 1848 } 1849 else { console.log('ERROR unsupported activation ' + def.activation); } 1850 } 1851 if(typeof def.drop_prob !== 'undefined' && def.type !== 'dropout') { 1852 new_defs.push({type:'dropout', drop_prob: def.drop_prob}); 1853 } 1854 1855 } 1856 return new_defs; 1857 } 1858 defs = desugar(defs); 1859 1860 // create the layers 1861 this.layers = []; 1862 for(var i=0;i<defs.length;i++) { 1863 var def = defs[i]; 1864 if(i>0) { 1865 var prev = this.layers[i-1]; 1866 def.in_sx = prev.out_sx; 1867 def.in_sy = prev.out_sy; 1868 def.in_depth = prev.out_depth; 1869 } 1870 1871 switch(def.type) { 1872 case 'fc': this.layers.push(new global.FullyConnLayer(def)); break; 1873 case 'lrn': this.layers.push(new global.LocalResponseNormalizationLayer(def)); break; 1874 case 'dropout': this.layers.push(new global.DropoutLayer(def)); break; 1875 case 'input': this.layers.push(new global.InputLayer(def)); break; 1876 case 'softmax': this.layers.push(new global.SoftmaxLayer(def)); break; 1877 case 'regression': this.layers.push(new global.RegressionLayer(def)); break; 1878 case 'conv': this.layers.push(new global.ConvLayer(def)); break; 1879 case 'pool': this.layers.push(new global.PoolLayer(def)); break; 1880 case 'relu': this.layers.push(new global.ReluLayer(def)); break; 1881 case 'sigmoid': this.layers.push(new global.SigmoidLayer(def)); break; 1882 case 'tanh': this.layers.push(new global.TanhLayer(def)); break; 1883 case 'maxout': this.layers.push(new global.MaxoutLayer(def)); break; 1884 case 'quadtransform': this.layers.push(new global.QuadTransformLayer(def)); break; 1885 case 'svm': this.layers.push(new global.SVMLayer(def)); break; 1886 default: console.log('ERROR: UNRECOGNIZED LAYER TYPE!'); 1887 } 1888 } 1889 }, 1890 1891 // forward prop the network. A trainer will pass in is_training = true 1892 forward: function(V, is_training) { 1893 if(typeof(is_training)==='undefined') is_training = false; 1894 var act = this.layers[0].forward(V, is_training); 1895 for(var i=1;i<this.layers.length;i++) { 1896 act = this.layers[i].forward(act, is_training); 1897 } 1898 return act; 1899 }, 1900 1901 getCostLoss: function(V, y) { 1902 this.forward(V, false); 1903 var N = this.layers.length; 1904 var loss = this.layers[N-1].backward(y); 1905 return loss; 1906 }, 1907 1908 // backprop: compute gradients wrt all parameters 1909 backward: function(y) { 1910 var N = this.layers.length; 1911 var loss = this.layers[N-1].backward(y); // last layer assumed softmax 1912 for(var i=N-2;i>=0;i--) { // first layer assumed input 1913 this.layers[i].backward(); 1914 } 1915 return loss; 1916 }, 1917 getParamsAndGrads: function() { 1918 // accumulate parameters and gradients for the entire network 1919 var response = []; 1920 for(var i=0;i<this.layers.length;i++) { 1921 var layer_reponse = this.layers[i].getParamsAndGrads(); 1922 for(var j=0;j<layer_reponse.length;j++) { 1923 response.push(layer_reponse[j]); 1924 } 1925 } 1926 return response; 1927 }, 1928 getPrediction: function() { 1929 var S = this.layers[this.layers.length-1]; // softmax layer 1930 var p = S.out_act.w; 1931 var maxv = p[0]; 1932 var maxi = 0; 1933 for(var i=1;i<p.length;i++) { 1934 if(p[i] > maxv) { maxv = p[i]; maxi = i;} 1935 } 1936 return maxi; 1937 }, 1938 toJSON: function() { 1939 var json = {}; 1940 json.layers = []; 1941 for(var i=0;i<this.layers.length;i++) { 1942 json.layers.push(this.layers[i].toJSON()); 1943 } 1944 return json; 1945 }, 1946 fromJSON: function(json) { 1947 this.layers = []; 1948 for(var i=0;i<json.layers.length;i++) { 1949 var Lj = json.layers[i] 1950 var t = Lj.layer_type; 1951 var L; 1952 if(t==='input') { L = new global.InputLayer(); } 1953 if(t==='relu') { L = new global.ReluLayer(); } 1954 if(t==='sigmoid') { L = new global.SigmoidLayer(); } 1955 if(t==='tanh') { L = new global.TanhLayer(); } 1956 if(t==='dropout') { L = new global.DropoutLayer(); } 1957 if(t==='conv') { L = new global.ConvLayer(); } 1958 if(t==='pool') { L = new global.PoolLayer(); } 1959 if(t==='lrn') { L = new global.LocalResponseNormalizationLayer(); } 1960 if(t==='softmax') { L = new global.SoftmaxLayer(); } 1961 if(t==='regression') { L = new global.RegressionLayer(); } 1962 if(t==='fc') { L = new global.FullyConnLayer(); } 1963 if(t==='maxout') { L = new global.MaxoutLayer(); } 1964 if(t==='quadtransform') { L = new global.QuadTransformLayer(); } 1965 if(t==='svm') { L = new global.SVMLayer(); } 1966 L.fromJSON(Lj); 1967 this.layers.push(L); 1968 } 1969 } 1970 } 1971 1972 1973 global.Net = Net; 1974})(convnetjs); 1975(function(global) { 1976 "use strict"; 1977 var Vol = global.Vol; // convenience 1978 1979 var Trainer = function(net, options) { 1980 1981 this.net = net; 1982 1983 var options = options || {}; 1984 this.learning_rate = typeof options.learning_rate !== 'undefined' ? options.learning_rate : 0.01; 1985 this.l1_decay = typeof options.l1_decay !== 'undefined' ? options.l1_decay : 0.0; 1986 this.l2_decay = typeof options.l2_decay !== 'undefined' ? options.l2_decay : 0.0; 1987 this.batch_size = typeof options.batch_size !== 'undefined' ? options.batch_size : 1; 1988 this.method = typeof options.method !== 'undefined' ? options.method : 'sgd'; // sgd/adagrad/adadelta/windowgrad 1989 1990 this.momentum = typeof options.momentum !== 'undefined' ? options.momentum : 0.9; 1991 this.ro = typeof options.ro !== 'undefined' ? options.ro : 0.95; // used in adadelta 1992 this.eps = typeof options.eps !== 'undefined' ? options.eps : 1e-6; // used in adadelta 1993 1994 this.k = 0; // iteration counter 1995 this.gsum = []; // last iteration gradients (used for momentum calculations) 1996 this.xsum = []; // used in adadelta 1997 } 1998 1999 Trainer.prototype = { 2000 train: function(x, y) { 2001 2002 var start = new Date().getTime(); 2003 this.net.forward(x, true); // also set the flag that lets the net know we're just training 2004 var end = new Date().getTime(); 2005 var fwd_time = end - start; 2006 2007 var start = new Date().getTime();
2008 var cost_loss = this.net.backward(y); 2009 var l2_decay_loss = 0.0; 2010 var l1_decay_loss = 0.0; 2011 var end = new Date().getTime(); 2012 var bwd_time = end - start; 2013 2014 this.k++; 2015 if(this.k % this.batch_size === 0) { 2016 2017 var pglist = this.net.getParamsAndGrads(); 2018 2019 // initialize lists for accumulators. Will only be done once on first iteration 2020 if(this.gsum.length === 0 && (this.method !== 'sgd' || this.momentum > 0.0)) { 2021 // only vanilla sgd doesnt need either lists 2022 // momentum needs gsum 2023 // adagrad needs gsum 2024 // adadelta needs gsum and xsum 2025 for(var i=0;i<pglist.length;i++) { 2026 this.gsum.push(global.zeros(pglist[i].params.length)); 2027 if(this.method === 'adadelta') { 2028 this.xsum.push(global.zeros(pglist[i].params.length)); 2029 } else { 2030 this.xsum.push([]); // conserve memory 2031 } 2032 } 2033 } 2034 2035 // perform an update for all sets of weights 2036 for(var i=0;i<pglist.length;i++) { 2037 var pg = pglist[i]; // param, gradient, other options in future (custom learning rate etc) 2038 var p = pg.params; 2039 var g = pg.grads; 2040 2041 // learning rate for some parameters. 2042 var l2_decay_mul = typeof pg.l2_decay_mul !== 'undefined' ? pg.l2_decay_mul : 1.0; 2043 var l1_decay_mul = typeof pg.l1_decay_mul !== 'undefined' ? pg.l1_decay_mul : 1.0; 2044 var l2_decay = this.l2_decay * l2_decay_mul; 2045 var l1_decay = this.l1_decay * l1_decay_mul; 2046 2047 var plen = p.length; 2048 for(var j=0;j<plen;j++) { 2049 l2_decay_loss += l2_decay*p[j]*p[j]/2; // accumulate weight decay loss 2050 l1_decay_loss += l1_decay*Math.abs(p[j]); 2051 var l1grad = l1_decay * (p[j] > 0 ? 1 : -1); 2052 var l2grad = l2_decay * (p[j]); 2053 2054 var gij = (l2grad + l1grad + g[j]) / this.batch_size; // raw batch gradient 2055 2056 var gsumi = this.gsum[i]; 2057 var xsumi = this.xsum[i]; 2058 if(this.method === 'adagrad') { 2059 // adagrad update 2060 gsumi[j] = gsumi[j] + gij * gij; 2061 var dx = - this.learning_rate / Math.sqrt(gsumi[j] + this.eps) * gij; 2062 p[j] += dx; 2063 } else if(this.method === 'windowgrad') { 2064 // this is adagrad but with a moving window weighted average 2065 // so the gradient is not accumulated over the entire history of the run. 2066 // it's also referred to as Idea #1 in Zeiler paper on Adadelta. Seems reasonable to me! 2067 gsumi[j] = this.ro * gsumi[j] + (1-this.ro) * gij * gij; 2068 var dx = - this.learning_rate / Math.sqrt(gsumi[j] + this.eps) * gij; // eps added for better conditioning 2069 p[j] += dx; 2070 } else if(this.method === 'adadelta') { 2071 // assume adadelta if not sgd or adagrad 2072 gsumi[j] = this.ro * gsumi[j] + (1-this.ro) * gij * gij; 2073 var dx = - Math.sqrt((xsumi[j] + this.eps)/(gsumi[j] + this.eps)) * gij; 2074 xsumi[j] = this.ro * xsumi[j] + (1-this.ro) * dx * dx; // yes, xsum lags behind gsum by 1. 2075 p[j] += dx; 2076 } else { 2077 // assume SGD 2078 if(this.momentum > 0.0) { 2079 // momentum update 2080 var dx = this.momentum * gsumi[j] - this.learning_rate * gij; // step 2081 gsumi[j] = dx; // back this up for next iteration of momentum 2082 p[j] += dx; // apply corrected gradient 2083 } else { 2084 // vanilla sgd 2085 p[j] += - this.learning_rate * gij; 2086 } 2087 } 2088 g[j] = 0.0; // zero out gradient so that we can begin accumulating anew 2089 } 2090 } 2091 } 2092 2093 // appending softmax_loss for backwards compatibility, but from now on we will always use cost_loss 2094 // in future, TODO: have to completely redo the way loss is done around the network as currently 2095 // loss is a bit of a hack. Ideally, user should specify arbitrary number of loss functions on any layer 2096 // and it should all be computed correctly and automatically. 2097 return {fwd_time: fwd_time, bwd_time: bwd_time, 2098 l2_decay_loss: l2_decay_loss, l1_decay_loss: l1_decay_loss, 2099 cost_loss: cost_loss, softmax_loss: cost_loss, 2100 loss: cost_loss + l1_decay_loss + l2_decay_loss} 2101 } 2102 } 2103 2104 global.Trainer = Trainer; 2105 global.SGDTrainer = Trainer; // backwards compatibility 2106})(convnetjs); 2107 2108(function(global) { 2109 "use strict"; 2110 2111 // used utilities, make explicit local references 2112 var randf = global.randf; 2113 var randi = global.randi; 2114 var Net = global.Net; 2115 var Trainer = global.Trainer; 2116 var maxmin = global.maxmin; 2117 var randperm = global.randperm; 2118 var weightedSample = global.weightedSample; 2119 var getopt = global.getopt; 2120 var arrUnique = global.arrUnique; 2121 2122 /* 2123 A MagicNet takes data: a list of convnetjs.Vol(), and labels 2124 which for now are assumed to be class indeces 0..K. MagicNet then: 2125 - creates data folds for cross-validation 2126 - samples candidate networks 2127 - evaluates candidate networks on all data folds 2128 - produces predictions by model-averaging the best networks 2129 */ 2130 var MagicNet = function(data, labels, opt) { 2131 var opt = opt || {}; 2132 if(typeof data === 'undefined') { data = []; } 2133 if(typeof labels === 'undefined') { labels = []; } 2134 2135 // required inputs 2136 this.data = data; // store these pointers to data 2137 this.labels = labels; 2138 2139 // optional inputs 2140 this.train_ratio = getopt(opt, 'train_ratio', 0.7); 2141 this.num_folds = getopt(opt, 'num_folds', 10); 2142 this.num_candidates = getopt(opt, 'num_candidates', 50); // we evaluate several in parallel 2143 // how many epochs of data to train every network? for every fold? 2144 // higher values mean higher accuracy in final results, but more expensive 2145 this.num_epochs = getopt(opt, 'num_epochs', 50); 2146 // number of best models to average during prediction. Usually higher = better 2147 this.ensemble_size = getopt(opt, 'ensemble_size', 10); 2148 2149 // candidate parameters 2150 this.batch_size_min = getopt(opt, 'batch_size_min', 10); 2151 this.batch_size_max = getopt(opt, 'batch_size_max', 300); 2152 this.l2_decay_min = getopt(opt, 'l2_decay_min', -4); 2153 this.l2_decay_max = getopt(opt, 'l2_decay_max', 2); 2154 this.learning_rate_min = getopt(opt, 'learning_rate_min', -4); 2155 this.learning_rate_max = getopt(opt, 'learning_rate_max', 0); 2156 this.momentum_min = getopt(opt, 'momentum_min', 0.9); 2157 this.momentum_max = getopt(opt, 'momentum_max', 0.9); 2158 this.neurons_min = getopt(opt, 'neurons_min', 5); 2159 this.neurons_max = getopt(opt, 'neurons_max', 30); 2160 2161 // computed 2162 this.folds = []; // data fold indices, gets filled by sampleFolds() 2163 this.candidates = []; // candidate networks that are being currently evaluated 2164 this.evaluated_candidates = []; // history of all candidates that were fully evaluated on all folds 2165 this.unique_labels = arrUnique(labels); 2166 this.iter = 0; // iteration counter, goes from 0 -> num_epochs * num_training_data 2167 this.foldix = 0; // index of active fold 2168 2169 // callbacks 2170 this.finish_fold_callback = null; 2171 this.finish_batch_callback = null; 2172 2173 // initializations 2174 if(this.data.length > 0) { 2175 this.sampleFolds(); 2176 this.sampleCandidates(); 2177 } 2178 }; 2179 2180 MagicNet.prototype = { 2181 2182 // sets this.folds to a sampling of this.num_folds folds 2183 sampleFolds: function() { 2184 var N = this.data.length; 2185 var num_train = Math.floor(this.train_ratio * N); 2186 this.folds = []; // flush folds, if any 2187 for(var i=0;i<this.num_folds;i++) { 2188 var p = randperm(N); 2189 this.folds.push({train_ix: p.slice(0, num_train), test_ix: p.slice(num_train, N)}); 2190 } 2191 }, 2192 2193 // returns a random candidate network 2194 sampleCandidate: function() { 2195 var input_depth = this.data[0].w.length; 2196 var num_classes = this.unique_labels.length; 2197 2198 // sample network topology and hyperparameters 2199 var layer_defs = []; 2200 layer_defs.push({type:'input', out_sx:1, out_sy:1, out_depth: input_depth}); 2201 var nl = weightedSample([0,1,2,3], [0.2, 0.3, 0.3, 0.2]); // prefer nets with 1,2 hidden layers 2202 for(var q=0;q<nl;q++) { 2203 var ni = randi(this.neurons_min, this.neurons_max); 2204 var act = ['tanh','maxout','relu'][randi(0,3)]; 2205 if(randf(0,1)<0.5) { 2206 var dp = Math.random(); 2207 layer_defs.push({type:'fc', num_neurons: ni, activation: act, drop_prob: dp}); 2208 } else { 2209 layer_defs.push({type:'fc', num_neurons: ni, activation: act}); 2210 } 2211 } 2212 layer_defs.push({type:'softmax', num_classes: num_classes}); 2213 var net = new Net(); 2214 net.makeLayers(layer_defs); 2215 2216 // sample training hyperparameters 2217 var bs = randi(this.batch_size_min, this.batch_size_max); // batch size 2218 var l2 = Math.pow(10, randf(this.l2_decay_min, this.l2_decay_max)); // l2 weight decay 2219 var lr = Math.pow(10, randf(this.learning_rate_min, this.learning_rate_max)); // learning rate 2220 var mom = randf(this.momentum_min, this.momentum_max); // momentum. Lets just use 0.9, works okay usually ;p 2221 var tp = randf(0,1); // trainer type 2222 var trainer_def; 2223 if(tp<0.33) { 2224 trainer_def = {method:'adadelta', batch_size:bs, l2_decay:l2}; 2225 } else if(tp<0.66) { 2226 trainer_def = {method:'adagrad', learning_rate: lr, batch_size:bs, l2_decay:l2}; 2227 } else { 2228 trainer_def = {method:'sgd', learning_rate: lr, momentum: mom, batch_size:bs, l2_decay:l2}; 2229 } 2230 2231 var trainer = new Trainer(net, trainer_def); 2232 2233 var cand = {}; 2234 cand.acc = []; 2235 cand.accv = 0; // this will maintained as sum(acc) for convenience 2236 cand.layer_defs = layer_defs; 2237 cand.trainer_def = trainer_def; 2238 cand.net = net; 2239 cand.trainer = trainer; 2240 return cand; 2241 }, 2242 2243 // sets this.candidates with this.num_candidates candidate nets 2244 sampleCandidates: function() { 2245 this.candidates = []; // flush, if any 2246 for(var i=0;i<this.num_candidates;i++) { 2247 var cand = this.sampleCandidate(); 2248 this.candidates.push(cand); 2249 } 2250 }, 2251 2252 step: function() { 2253 2254 // run an example through current candidate 2255 this.iter++; 2256 2257 // step all candidates on a random data point 2258 var fold = this.folds[this.foldix]; // active fold 2259 var dataix = fold.train_ix[randi(0, fold.train_ix.length)]; 2260 for(var k=0;k<this.candidates.length;k++) { 2261 var x = this.data[dataix]; 2262 var l = this.labels[dataix]; 2263 this.candidates[k].trainer.train(x, l); 2264 } 2265 2266 // process consequences: sample new folds, or candidates 2267 var lastiter = this.num_epochs * fold.train_ix.length; 2268 if(this.iter >= lastiter) { 2269 // finished evaluation of this fold. Get final validation
2270 // accuracies, record them, and go on to next fold. 2271 var val_acc = this.evalValErrors(); 2272 for(var k=0;k<this.candidates.length;k++) { 2273 var c = this.candidates[k]; 2274 c.acc.push(val_acc[k]); 2275 c.accv += val_acc[k]; 2276 } 2277 this.iter = 0; // reset step number 2278 this.foldix++; // increment fold 2279 2280 if(this.finish_fold_callback !== null) { 2281 this.finish_fold_callback(); 2282 } 2283 2284 if(this.foldix >= this.folds.length) { 2285 // we finished all folds as well! Record these candidates 2286 // and sample new ones to evaluate. 2287 for(var k=0;k<this.candidates.length;k++) { 2288 this.evaluated_candidates.push(this.candidates[k]); 2289 } 2290 // sort evaluated candidates according to accuracy achieved 2291 this.evaluated_candidates.sort(function(a, b) { 2292 return (a.accv / a.acc.length) 2293 > (b.accv / b.acc.length) 2294 ? -1 : 1; 2295 }); 2296 // and clip only to the top few ones (lets place limit at 3*ensemble_size) 2297 // otherwise there are concerns with keeping these all in memory 2298 // if MagicNet is being evaluated for a very long time 2299 if(this.evaluated_candidates.length > 3 * this.ensemble_size) { 2300 this.evaluated_candidates = this.evaluated_candidates.slice(0, 3 * this.ensemble_size); 2301 } 2302 if(this.finish_batch_callback !== null) { 2303 this.finish_batch_callback(); 2304 } 2305 this.sampleCandidates(); // begin with new candidates 2306 this.foldix = 0; // reset this 2307 } else { 2308 // we will go on to another fold. reset all candidates nets 2309 for(var k=0;k<this.candidates.length;k++) { 2310 var c = this.candidates[k]; 2311 var net = new Net(); 2312 net.makeLayers(c.layer_defs); 2313 var trainer = new Trainer(net, c.trainer_def); 2314 c.net = net; 2315 c.trainer = trainer; 2316 } 2317 } 2318 } 2319 }, 2320 2321 evalValErrors: function() { 2322 // evaluate candidates on validation data and return performance of current networks 2323 // as simple list 2324 var vals = []; 2325 var fold = this.folds[this.foldix]; // active fold 2326 for(var k=0;k<this.candidates.length;k++) { 2327 var net = this.candidates[k].net; 2328 var v = 0.0; 2329 for(var q=0;q<fold.test_ix.length;q++) { 2330 var x = this.data[fold.test_ix[q]]; 2331 var l = this.labels[fold.test_ix[q]]; 2332 net.forward(x); 2333 var yhat = net.getPrediction(); 2334 v += (yhat === l ? 1.0 : 0.0); // 0 1 loss 2335 } 2336 v /= fold.test_ix.length; // normalize 2337 vals.push(v); 2338 } 2339 return vals; 2340 }, 2341 2342 // returns prediction scores for given test data point, as Vol 2343 // uses an averaged prediction from the best ensemble_size models 2344 // x is a Vol. 2345 predict_soft: function(data) { 2346 // forward prop the best networks 2347 // and accumulate probabilities at last layer into a an output Vol 2348 var nv = Math.min(this.ensemble_size, this.evaluated_candidates.length); 2349 if(nv === 0) { return new convnetjs.Vol(0,0,0); } // not sure what to do here? we're not ready yet 2350 var xout, n; 2351 for(var j=0;j<nv;j++) { 2352 var net = this.evaluated_candidates[j].net; 2353 var x = net.forward(data); 2354 if(j===0) { 2355 xout = x; 2356 n = x.w.length; 2357 } else { 2358 // add it on 2359 for(var d=0;d<n;d++) { 2360 xout.w[d] += x.w[d]; 2361 } 2362 } 2363 } 2364 // produce average 2365 for(var d=0;d<n;d++) { 2366 xout.w[d] /= n; 2367 } 2368 return xout; 2369 }, 2370 2371 predict: function(data) { 2372 var xout = this.predict_soft(data); 2373 if(xout.w.length !== 0) { 2374 var stats = maxmin(xout.w); 2375 var predicted_label = stats.maxi; 2376 } else { 2377 var predicted_label = -1; // error out 2378 } 2379 return predicted_label; 2380 2381 }, 2382 2383 toJSON: function() { 2384 // dump the top ensemble_size networks as a list 2385 var nv = Math.min(this.ensemble_size, this.evaluated_candidates.length); 2386 var json = {}; 2387 json.nets = []; 2388 for(var i=0;i<nv;i++) { 2389 json.nets.push(this.evaluated_candidates[i].net.toJSON()); 2390 } 2391 return json; 2392 }, 2393 2394 fromJSON: function(json) { 2395 this.ensemble_size = json.nets.length; 2396 this.evaluated_candidates = []; 2397 for(var i=0;i<this.ensemble_size;i++) { 2398 var net = new Net(); 2399 net.fromJSON(json.nets[i]); 2400 var dummy_candidate = {}; 2401 dummy_candidate.net = net; 2402 this.evaluated_candidates.push(dummy_candidate); 2403 } 2404 }, 2405 2406 // callback functions 2407 // called when a fold is finished, while evaluating a batch 2408 onFinishFold: function(f) { this.finish_fold_callback = f; }, 2409 // called when a batch of candidates has finished evaluating 2410 onFinishBatch: function(f) { this.finish_batch_callback = f; } 2411 2412 }; 2413 2414 global.MagicNet = MagicNet; 2415})(convnetjs); 2416(function(lib) { 2417 "use strict"; 2418 if (typeof module === "undefined" || typeof module.exports === "undefined") { 2419 window.jsfeat = lib; // in ordinary browser attach library to window 2420 } else { 2421 module.exports = lib; // in nodejs 2422 } 2423})(convnetjs); 2424 2425// GA addon for convnet.js 2426 2427(function(global) { 2428 "use strict"; 2429 var Vol = convnetjs.Vol; // convenience 2430 2431 // used utilities, make explicit local references 2432 var randf = convnetjs.randf; 2433 var randn = convnetjs.randn; 2434 var randi = convnetjs.randi; 2435 var zeros = convnetjs.zeros; 2436 var Net = convnetjs.Net; 2437 var maxmin = convnetjs.maxmin; 2438 var randperm = convnetjs.randperm; 2439 var weightedSample = convnetjs.weightedSample; 2440 var getopt = convnetjs.getopt; 2441 var arrUnique = convnetjs.arrUnique; 2442 2443 function assert(condition, message) { 2444 if (!condition) { 2445 message = message || "Assertion failed"; 2446 if (typeof Error !== "undefined") { 2447 throw new Error(message); 2448 } 2449 throw message; // Fallback 2450 } 2451 } 2452 2453 // returns a random cauchy random variable with gamma (control
2453s magnitude sort of like stdev in randn) 2454 // http://en.wikipedia.org/wiki/Cauchy_distribution 2455 var randc = function(m, gamma) { 2456 return m + gamma * 0.01 * randn(0.0, 1.0) / randn(0.0, 1.0); 2457 }; 2458 2459 // chromosome implementation using an array of floats 2460 var Chromosome = function(floatArray) { 2461 this.fitness = 0; // default value 2462 this.nTrial = 0; // number of trials subjected to so far. 2463 this.gene = floatArray; 2464 }; 2465 2466 Chromosome.prototype = { 2467 burst_mutate: function(burst_magnitude_) { // adds a normal random variable of stdev width, zero mean to each gene. 2468 var burst_magnitude = burst_magnitude_ || 0.1; 2469 var i, N; 2470 N = this.gene.length; 2471 for (i = 0; i < N; i++) { 2472 this.gene[i] += randn(0.0, burst_magnitude); 2473 } 2474 }, 2475 randomize: function(burst_magnitude_) { // resets each gene to a random value with zero mean and stdev 2476 var burst_magnitude = burst_magnitude_ || 0.1; 2477 var i, N; 2478 N = this.gene.length; 2479 for (i = 0; i < N; i++) { 2480 this.gene[i] = randn(0.0, burst_magnitude); 2481 } 2482 }, 2483 mutate: function(mutation_rate_, burst_magnitude_) { // adds random gaussian (0,stdev) to each gene with prob mutation_rate 2484 var mutation_rate = mutation_rate_ || 0.1; 2485 var burst_magnitude = burst_magnitude_ || 0.1; 2486 var i, N; 2487 N = this.gene.length; 2488 for (i = 0; i < N; i++) { 2489 if (randf(0,1) < mutation_rate) { 2490 this.gene[i] += randn(0.0, burst_magnitude); 2491 } 2492 } 2493 }, 2494 crossover: function(partner, kid1, kid2) { // performs one-point crossover with partner to produce 2 kids 2495 //assumes all chromosomes are initialised with same array size. pls make sure of this before calling 2496 var i, N; 2497 N = this.gene.length; 2498 var l = randi(0, N); // crossover point 2499 for (i = 0; i < N; i++) { 2500 if (i < l) { 2501 kid1.gene[i] = this.gene[i]; 2502 kid2.gene[i] = partner.gene[i]; 2503 } else { 2504 kid1.gene[i] = partner.gene[i]; 2505 kid2.gene[i] = this.gene[i]; 2506 } 2507 } 2508 }, 2509 copyFrom: function(c) { // copies c's gene into itself 2510 var i, N; 2511 this.copyFromGene(c.gene); 2512 }, 2513 copyFromGene: function(gene) { // gene into itself 2514 var i, N; 2515 N = this.gene.length; 2516 for (i = 0; i < N; i++) { 2517 this.gene[i] = gene[i]; 2518 } 2519 }, 2520 clone: function() { // returns an exact copy of itself (into new memory, doesn't return reference) 2521 var newGene = zeros(this.gene.length); 2522 var i; 2523 for (i = 0; i < this.gene.length; i++) { 2524 newGene[i] = Math.round(10000*this.gene[i])/10000; 2525 } 2526 var c = new Chromosome(newGene); 2527 c.fitness = this.fitness; 2528 return c; 2529 } 2530 }; 2531 2532 // counts the number of weights and biases in the network 2533 function getNetworkSize(net) { 2534 var layer = null; 2535 var filter = null; 2536 var bias = null; 2537 var w = null; 2538 var count = 0; 2539 var i, j, k; 2540 for ( i = 0; i < net.layers.length; i++) { 2541 layer = net.layers[i]; 2542 filter = layer.filters; 2543 if (filter) { 2544 for ( j = 0; j < filter.length; j++) { 2545 w = filter[j].w; 2546 count += w.length; 2547 } 2548 } 2549 bias = layer.biases; 2550 if (bias) { 2551 w = bias.w; 2552 count += w.length; 2553 } 2554 } 2555 return count; 2556 } 2557 2558 function pushGeneToNetwork(net, gene) { // pushes the gene (floatArray) to fill up weights and biases in net 2559 var count = 0; 2560 var layer = null; 2561 var filter = null; 2562 var bias = null; 2563 var w = null; 2564 var i, j, k; 2565 for ( i = 0; i < net.layers.length; i++) { 2566 layer = net.layers[i]; 2567 filter = layer.filters; 2568 if (filter) { 2569 for ( j = 0; j < filter.length; j++) { 2570 w = filter[j].w; 2571 for ( k = 0; k < w.length; k++) { 2572 w[k] = gene[count++]; 2573 } 2574 } 2575 } 2576 bias = layer.biases; 2577 if (bias) { 2578 w = bias.w; 2579 for ( k = 0; k < w.length; k++) { 2580 w[k] = gene[count++]; 2581 } 2582 } 2583 } 2584 } 2585 2586 function getGeneFromNetwork(net) { // gets all the weight/biases from network in a floatArray 2587 var gene = []; 2588 var layer = null; 2589 var filter = null; 2590 var bias = null; 2591 var w = null; 2592 var i, j, k; 2593 for ( i = 0; i < net.layers.length; i++) { 2594 layer = net.layers[i]; 2595 filter = layer.filters; 2596 if (filter) { 2597 for ( j = 0; j < filter.length; j++) { 2598 w = filter[j].w; 2599 for ( k = 0; k < w.length; k++) { 2600 gene.push(w[k]); 2601 } 2602 } 2603 } 2604 bias = layer.biases; 2605 if (bias) { 2606 w = bias.w; 2607 for ( k = 0; k < w.length; k++) { 2608 gene.push(w[k]); 2609 } 2610 } 2611 } 2612 return gene; 2613 } 2614 2615 function copyFloatArray(x) { // returns a FloatArray copy of real numbered array x. 2616 var N = x.length; 2617 var y = zeros(N); 2618 for (var i = 0; i < N; i++) { 2619 y[i] = x[i]; 2620 } 2621 return y; 2622 } 2623 2624 function copyFloatArrayIntoArray(x, y) { // copies a FloatArray copy of real numbered array x into y 2625 var N = x.length; 2626 for (var i = 0; i < N; i++) { 2627 y[i] = x[i]; 2628 } 2629 } 2630 2631 // implementation of basic conventional neuroevolution algorithm (CNE) 2632 // 2633 // options: 2634 // population_size : positive integer 2635 // mutation_rate : [0, 1], when mutation happens, chance of each gene getting mutated 2636 // elite_percentage : [0, 0.3], only this group mates and produces offsprings 2637 // mutation_size : positive floating point. stdev of gausian noise added for mutations 2638 // target_fitness : after fitness achieved is greater than this float value, learning stops 2639 // burst_generations : positive integer. if best fitness doesn't improve after this number of generations 2640 // then mutate everything! 2641 // best_trial : default 1. save best of best_trial's results for each chromosome. 2642 // 2643 // initGene: init float array to initialize the chromosomes. can be result obtained from pretrained sessions. 2644 var GATrainer = function(net, options_, initGene) { 2645 2646 this.net = net; 2647 2648 var options = options_ || {}; 2649 this.population_size = typeof options.population_size !== 'undefined' ? options.population_size : 100; 2650 this.population_size = Math.floor(this.population_size/2)*2; // make sure even number 2651 this.mutation_rate = typeof options.mutation_rate !== 'undefined' ? options.mutation_rate : 0.01; 2652 this.elite_percentage = typeof options.elite_percentage !== 'undefined' ? options.elite_percentage : 0.2; 2653 this.mutation_size = typeof options.mutation_size !== 'undefined' ? options.mutation_size : 0.05; 2654 this.target_fitness = typeof options.target_fitness !== 'undefined' ? options.target_fitness : 10000000000000000; 2655 this.burst_generations = typeof options.burst_generations !== 'undefined' ? options.burst_generations : 10; 2656 this.best_trial = typeof options.best_trial !== 'undefined' ? options.best_trial : 1; 2657 this.chromosome_size = getNetworkSize(this.net); 2658 2659 var initChromosome = null; 2660 if (initGene) { 2661 initChromosome = new Chromosome(initGene); 2662 } 2663 2664 this.chromosomes = []; // population 2665 for (var i = 0; i < this.population_size; i++) { 2666 var chromosome = new Chromosome(zeros(this.chromosome_size)); 2667 if (initChromosome) { // if initial gene supplied, burst mutate param. 2668 chromosome.copyFrom(initChromosome); 2669 pushGeneToNetwork(this.net, initChromosome.gene); 2670 if (i > 0) { // don't mutate the first guy. 2671 chromosome.burst_mutate(this.mutation_size); 2672 } 2673 } else { 2674 chromosome.randomize(1.0); 2675 } 2676 this.chromosomes.push(chromosome); 2677 } 2678 2679 this.bestFitness = -10000000000000000; 2680 this.bestFitnessCount = 0; 2681 2682 }; 2683 2684 GATrainer.prototype = { 2685 train: function(fitFunc) { // has to pass in fitness function. returns best fitness 2686 var bestFitFunc = function(nTrial, net) { 2687 var bestFitness = -10000000000000000; 2688 var fitness; 2689 for (var i = 0; i < nTrial; i++) { 2690 fitness = fitFunc(net); 2691 if (fitness > bestFitness) { 2692 bestFitness = fitness; 2693 } 2694 } 2695 return bestFitness; 2696 }; 2697 2698 var i, N; 2699 var fitness; 2700 var c = this.chromosomes; 2701 N = this.population_size; 2702 2703 var bestFitness = -10000000000000000; 2704 2705 // process first net (the best one) 2706 pushGeneToNetwork(this.net, c[0].gene); 2707 fitness = bestFitFunc(this.best_trial, this.net); 2708 c[0].fitness = fitness; 2709 bestFitness = fitness; 2710 if (bestFitness > this.target_fitness) { 2711 return bestFitness; 2712 } 2713 2714 for (i = 1; i < N; i++) { 2715 pushGeneToNetwork(this.net, c[i].gene); 2716 fitness = bestFitFunc(this.best_trial, this.net); 2717 c[i].fitness = fitness; 2718 if (fitness > bestFitness) { 2719 bestFitness = fitness; 2720 } 2721 } 2722 2723 // sort the chromosomes by fitness 2724 c = c.sort(function (a, b) { 2725 if (a.fitness > b.fitness) { return -1; } 2726 if (a.fitness < b.fitness) { return 1; } 2727 return 0; 2728 }); 2729 2730 var Nelite = Math.floor(Math.floor(this.elite_percentage*N)/2)*2; // even number 2731 for (i = Nelite; i < N; i+=2) { 2732 var p1 = randi(0, Nelite); 2733 var p2 = randi(0, Nelite); 2734 c[p1].crossover(c[p2], c[i], c[i+1]); 2735 } 2736 2737 for (i = 1; i < N; i++) { // keep best guy the same. don't mutate the best one, so start from 1, not 0. 2738 c[i].mutate(this.mutation_rate, this.mutation_size); 2739 } 2740 2741 // push best one to network. 2742 pushGeneToNetwork(this.net, c[0].gene); 2743 if (bestFitness < this.bestFitness) { // didn't beat the record this time 2744 this.bestFitnessCount++; 2745 if (this.bestFitnessCount > this.burst_generations) { // stagnation, do burst mutate! 2746 for (i = 1; i < N; i++) { 2747 c[i].copyFrom(c[0]); 2748 c[i].burst_mutate(this.mutation_size); 2749 } 2750 //c[0].burst_mutate(this.mutation_size); // don't mutate best solution. 2751 } 2752 2753 } else { 2754 this.bestFitnessCount = 0; // reset count for burst 2755 this.bestFitness = bestFitness; // record the best fitness score 2756 } 2757 2758 return bestFitness; 2759 } 2760 }; 2761 2762 // variant of ESP network implemented 2763 // population of N sub neural nets, each to be co-evolved by ESPTrainer 2764 // fully recurrent. outputs of each sub nn is also the input of all other sub nn's and itself. 2765 // inputs should be order of ~ -10 to +10, and expect output to be similar magnitude. 2766 // user can grab outputs of the the N sub networks and use them to accomplish some task for training 2767 // 2768 // Nsp: Number of sub populations (ie, 4) 2769 // Ninput: Number of real inputs to the system (ie, 2). so actual number of input is Niput + Nsp 2770 // Nhidden: Number of hidden neurons in each sub population (ie, 16) 2771 // genes: (optional) array of Nsp genes (floatArrays) to initialise the network (pretrained); 2772 var ESPNet = function(Nsp, Ninput, Nhidden, genes) { 2773 this.net = []; // an array of convnet.js feed forward nn's 2774 this.Ninput = Ninput; 2775 this.Nsp = Nsp; 2776 this.Nhidden = Nhidden; 2777 this.input = new convnetjs.Vol(1, 1, Nsp+Ninput); // hold most up to date input vector 2778 this.output = zeros(Nsp); 2779 2780 // define the architecture of each sub nn: 2781 var layer_defs = []; 2782 layer_defs.push({ 2783 type: 'input', 2784 out_sx: 1, 2785 out_sy: 1, 2786 out_depth: (Ninput+Nsp) 2787 }); 2788 layer_defs.push({ 2789 type: 'fc', 2790 num_neurons: Nhidden, 2791 activation: 'sigmoid' 2792 }); 2793 layer_defs.push({ 2794 type: 'regression', 2795 num_neurons: 1 // one output for each sub nn, gets fed back into inputs. 2796 }); 2797 2798 var network; 2799 for (var i = 0; i < Nsp; i++) { 2800 network = new convnetjs.Net(); 2801 network.makeLayers(layer_defs); 2802 this.net.push(network); 2803 } 2804 2805 // if pretrained network is supplied: 2806 if (genes) { 2807 this.pushGenes(genes); 2808 } 2809 }; 2810 2811 ESPNet.prototype = { 2812 feedback: function() { // feeds output back to last bit of input vector 2813 var i; 2814 var Ninput = this.Ninput; 2815 var Nsp = this.Nsp; 2816 for (i = 0; i < Nsp; i++) { 2817 this.input.w[i+Ninput] = this.output[i]; 2818 } 2819 }, 2820 setInput: function(input) { // input is a vector of length this.Ninput of real numbers 2821 // this function also grabs the previous most recent output and put it into the internal input vector 2822 var i; 2823 var Ninput = this.Ninput; 2824 var Nsp = this.Nsp; 2825 for (i = 0; i < Ninput; i++) {
2826 this.input.w[i] = input[i]; 2827 } 2828 this.feedback(); 2829 }, 2830 forward: function() { // returns array of output of each Nsp neurons after a forward pass. 2831 var i, j; 2832 var Ninput = this.Ninput; 2833 var Nsp = this.Nsp; 2834 var y = zeros(Nsp); 2835 var a; // temp variable to old output of forward pass 2836 for (i = Nsp-1; i >= 0; i--) { 2837 if (i === 0) { // for the base network, forward with output of other support networks 2838 this.feedback(); 2839 } 2840 a = this.net[i].forward(this.input); // forward pass sub nn # i 2841 y[i] = a.w[0]; // each sub nn only has one output. 2842 this.output[i] = y[i]; // set internal output to track output 2843 } 2844 return y; 2845 }, 2846 getNetworkSize: function() { // return total number of weights and biases in a single sub nn. 2847 return getNetworkSize(this.net[0]); // each network has identical architecture. 2848 }, 2849 getGenes: function() { // return an array of Nsp genes (floatArrays of length getNetworkSize()) 2850 var i; 2851 var Nsp = this.Nsp; 2852 var result = []; 2853 for (i = 0; i < Nsp; i++) { 2854 result.push(getGeneFromNetwork(this.net[i])); 2855 } 2856 return result; 2857 }, 2858 pushGenes: function(genes) { // genes is an array of Nsp genes (floatArrays) 2859 var i; 2860 var Nsp = this.Nsp; 2861 for (i = 0; i < Nsp; i++) { 2862 pushGeneToNetwork(this.net[i], genes[i]); 2863 } 2864 } 2865 }; 2866 2867 // implementation of variation of Enforced Sub Population neuroevolution algorithm 2868 // 2869 // options: 2870 // population_size : population size of each subnetwork inside espnet 2871 // mutation_rate : [0, 1], when mutation happens, chance of each gene getting mutated 2872 // elite_percentage : [0, 0.3], only this group mates and produces offsprings 2873 // mutation_size : positive floating point. stdev of gausian noise added for mutations 2874 // target_fitness : after fitness achieved is greater than this float value, learning stops 2875 // num_passes : number of times each neuron within a sub population is tested 2876 // on average, each neuron will be tested num_passes * esp.Nsp times. 2877 // burst_generations : positive integer. if best fitness doesn't improve after this number of generations 2878 // then start killing neurons that don't contribute to the bottom line! (reinit them with randoms) 2879 // best_mode : if true, this will assign each neuron to the best fitness trial it has experienced. 2880 // if false, this will use the average of all trials experienced. 2881 // initGenes: init Nsp array of floatarray to initialize the chromosomes. can be result obtained from pretrained sessions. 2882 var ESPTrainer = function(espnet, options_, initGenes) { 2883 2884 this.espnet = espnet; 2885 this.Nsp = espnet.Nsp; 2886 var Nsp = this.Nsp; 2887 2888 var options = options_ || {}; 2889 this.population_size = typeof options.population_size !== 'undefined' ? options.population_size : 50; 2890 this.population_size = Math.floor(this.population_size/2)*2; // make sure even number 2891 this.mutation_rate = typeof options.mutation_rate !== 'undefined' ? options.mutation_rate : 0.2; 2892 this.elite_percentage = typeof options.elite_percentage !== 'undefined' ? options.elite_percentage : 0.2; 2893 this.mutation_size = typeof options.mutation_size !== 'undefined' ? options.mutation_size : 0.02; 2894 this.target_fitness = typeof options.target_fitness !== 'undefined' ? options.target_fitness : 10000000000000000; 2895 this.num_passes = typeof options.num_passes !== 'undefined' ? options.num_passes : 2; 2896 this.burst_generations = typeof options.burst_generations !== 'undefined' ? options.burst_generations : 10; 2897 this.best_mode = typeof options.best_mode !== 'undefined' ? options.best_mode : false;
2898 this.chromosome_size = this.espnet.getNetworkSize(); 2899 2900 this.initialize(initGenes); 2901 }; 2902 2903 ESPTrainer.prototype = { 2904 initialize: function(initGenes) { 2905 var i, j; 2906 var y; 2907 var Nsp = this.Nsp; 2908 this.sp = []; // sub populations 2909 this.bestGenes = []; // array of Nsp number of genes, records the best combination of genes for the bestFitness achieved so far. 2910 var chromosomes, chromosome; 2911 for (i = 0; i < Nsp; i++) { 2912 chromosomes = []; // empty list of chromosomes 2913 for (j = 0; j < this.population_size; j++) { 2914 chromosome = new Chromosome(zeros(this.chromosome_size)); 2915 if (initGenes) { 2916 chromosome.copyFromGene(initGenes[i]); 2917 if (j > 0) { // don't mutate first guy (pretrained) 2918 chromosome.burst_mutate(this.mutation_size); 2919 } 2920 } else { // push random genes to this.bestGenes since it has not been initalized. 2921 chromosome.randomize(1.0); // create random gene array if no pretrained one is supplied. 2922 } 2923 chromosomes.push(chromosome); 2924 } 2925 y = copyFloatArray(chromosomes[0].gene); // y should either be random init gene, or pretrained. 2926 this.bestGenes.push(y); 2927 this.sp.push(chromosomes); // push array of chromosomes into each population 2928 } 2929 2930 assert(this.bestGenes.length === Nsp); 2931 this.espnet.pushGenes(this.bestGenes); // initial 2932 2933 this.bestFitness = -10000000000000000; 2934 this.bestFitnessCount = 0; 2935 }, 2936 train: function(fitFunc) { // has to pass in fitness function. returns best fitness 2937 2938 var i, j, k, m, N, Nsp; 2939 var fitness; 2940 var c = this.sp; // array of arrays that holds every single chromosomes (Nsp x N); 2941 N = this.population_size; // number of chromosomes in each sub population 2942 Nsp = this.Nsp; // number of sub populations 2943 2944 var bestFitness = -10000000000000000; 2945 var bestSet, bestGenes; 2946 var cSet; 2947 var genes; 2948 2949 // helper function to return best fitness run nTrial times 2950 var bestFitFunc = function(nTrial, net) { 2951 var bestFitness = -10000000000000000; 2952 var fitness; 2953 for (var i = 0; i < nTrial; i++) { 2954 fitness = fitFunc(net); 2955 if (fitness > bestFitness) { 2956 bestFitness = fitness; 2957 } 2958 } 2959 return bestFitness; 2960 }; 2961 2962 // helper function to create a new array filled with genes from an array of chromosomes 2963 // returns an array of Nsp floatArrays 2964 function getGenesFromChromosomes(s) { 2965 var g = []; 2966 for (var i = 0; i < s.length; i++) { 2967 g.push(copyFloatArray(s[i].gene)); 2968 } 2969 return g; 2970 } 2971 2972 // makes a copy of an array of gene, helper function 2973 function makeCopyOfGenes(s) { 2974 var g = []; 2975 for (var i = 0; i < s.length; i++) { 2976 g.push(copyFloatArray(s[i])); 2977 } 2978 return g; 2979 } 2980 2981 // helper function, randomize all of nth sub population of entire chromosome set c 2982 function randomizeSubPopulation(n, c) { 2983 for (var i = 0; i < N; i++) { 2984 c[n][i].randomize(1.0); 2985 } 2986 } 2987 2988 // helper function used to sort the list of chromosomes according to their fitness 2989 function compareChromosomes(a, b) { 2990 if ((a.fitness/a.nTrial) > (b.fitness/b.nTrial)) { return -1; } 2991 if ((a.fitness/a.nTrial) < (b.fitness/b.nTrial)) { return 1; } 2992 return 0; 2993 } 2994 2995 // iterate over each gene in each sub population to initialise the nTrial to zero (will be incremented later) 2996 for (i = 0; i < Nsp; i++) { // loop over every sub population 2997 for (j = 0; j < N; j++) { 2998 if (this.best_mode) { // best mode turned on, no averaging, but just recording best score. 2999 c[i][j].nTrial = 1; 3000 c[i][j].fitness = -10000000000000000; 3001 } else { 3002 c[i][j].nTrial = 0; 3003 c[i][j].fitness = 0; 3004 } 3005 } 3006 } 3007 3008 // see if the global best gene has met target. if so, can end it now. 3009 assert(this.bestGenes.length === Nsp); 3010 this.espnet.pushGenes(this.bestGenes); // put the random set of networks into the espnet 3011 fitness = fitFunc(this.espnet); // try out this set, and get the fitness 3012 if (fitness > this.target_fitness) { 3013 return fitness; 3014 } 3015 bestGenes = makeCopyOfGenes(this.bestGenes); 3016 bestFitness = fitness; 3017 //this.bestFitness = fitness; 3018 3019 // for each chromosome in a sub population, choose random chromosomes from all othet sub populations to 3020 // build a espnet. perform fitFunc on that esp net to get the fitness of that combination. add the fitness 3021 // to this chromosome, and all participating chromosomes. increment the nTrial of all participating 3022 // chromosomes by one, so afterwards they can be sorted by average fitness 3023 // repeat this process this.num_passes times 3024 for (k = 0; k < this.num_passes; k++) { 3025 for (i = 0; i < Nsp; i++) { 3026 for (j = 0; j < N; j++) { 3027 // build an array of chromosomes randomly 3028 cSet = []; 3029 for (m = 0; m < Nsp; m++) { 3030 if (m === i) { // push current iterated neuron 3031 cSet.push(c[m][j]); 3032 } else { // push random neuron in sub population m 3033 cSet.push(c[m][randi(0, N)]); 3034 } 3035 } 3036 genes = getGenesFromChromosomes(cSet); 3037 assert(genes.length === Nsp); 3038 this.espnet.pushGenes(genes); // put the random set of networks into the espnet 3039 3040 fitness = fitFunc(this.espnet); // try out this set, and get the fitness 3041 3042 for (m = 0; m < Nsp; m++) { // tally the scores into each participating neuron 3043 if (this.best_mode) { 3044 if (fitness > cSet[m].fitness) { // record best fitness this neuron participated in. 3045 cSet[m].fitness = fitness; 3046 } 3047 } else { 3048 cSet[m].nTrial += 1; // increase participation count for each participating neuron 3049 cSet[m].fitness += fitness; 3050 } 3051 } 3052 if (fitness > bestFitness) { 3053 bestFitness = fitness; 3054 bestSet = cSet; 3055 bestGenes = genes; 3056 } 3057 } 3058 } 3059 } 3060 3061 // sort the chromosomes by average fitness 3062 for (i = 0; i < Nsp; i++) { 3063 c[i] = c[i].sort(compareChromosomes); 3064 } 3065 3066 var Nelite = Math.floor(Math.floor(this.elite_percentage*N)/2)*2; // even number 3067 for (i = 0; i < Nsp; i++) { 3068 for (j = Nelite; j < N; j+=2) { 3069 var p1 = randi(0, Nelite); 3070 var p2 = randi(0, Nelite); 3071 c[i][p1].crossover(c[i][p2], c[i][j], c[i][j+1]); 3072 } 3073 } 3074 3075 // mutate the population size after 2*Nelite (keep one set of crossovers unmutiliated!) 3076 for (i = 0; i < Nsp; i++) { 3077 for (j = 2*Nelite; j < N; j++) { 3078 c[i][j].mutate(this.mutation_rate, this.mutation_size); 3079 } 3080 } 3081 3082 // put global and local bestgenes in the last element of each gene 3083 for (i = 0; i < Nsp; i++) { 3084 c[i][N-1].copyFromGene( this.bestGenes[i] ); 3085 c[i][N-2].copyFromGene( bestGenes[i] ); 3086 } 3087 3088 if (bestFitness < this.bestFitness) { // didn't beat the record this time 3089 this.bestFitnessCount++; 3090 if (this.bestFitnessCount > this.burst_generations) { // stagnation, do burst mutate! 3091 // add code here when progress stagnates later. 3092 console.log('stagnating. burst mutate based on best solution.'); 3093 var bestGenesCopy = makeCopyOfGenes(this.bestGenes); 3094 var bestFitnessCopy = this.bestFitness; 3095 this.initialize(bestGenesCopy); 3096 3097 this.bestGenes = bestGenesCopy; 3098 this.bestFitness = this.bestFitnessCopy; 3099 3100 } 3101 3102 } else { 3103 this.bestFitnessCount = 0; // reset count for burst 3104 this.bestFitness = bestFitness; // record the best fitness score 3105 this.bestGenes = bestGenes; // record the set of genes that generated the best fitness 3106 } 3107 3108 // push best one (found so far from all of history, not just this time) to network. 3109 assert(this.bestGenes.length === Nsp); 3110 this.espnet.pushGenes(this.bestGenes); 3111 3112 return bestFitness; 3113 } 3114 }; 3115 3116 convnetjs.ESPNet = ESPNet; 3117 convnetjs.ESPTrainer = ESPTrainer; 3118 convnetjs.GATrainer = GATrainer; 3119})(convnetjs); 3120 3121// useful simple math functions 3122 3123var sign = Math.sign || function sign(x) { 3124 "use strict";
3125 x = +x; // convert to a number 3126 if (x === 0 || isNaN(x)) { 3127 return x; 3128 } 3129 return x > 0 ? 1 : -1; 3130}; 3131 3132function rectify(x, minValue, maxValue) { 3133 "use strict"; 3134 if (x > maxValue) return maxValue; 3135 if (x < minValue) return minValue; 3136 return x; 3137} 3138 3139function getWidth() { 3140 "use strict"; 3141 return $(window).width() - 20 * 0; 3142} 3143 3144function getHeight() { 3145 "use strict"; 3146 return $(window).height() - 20 * 0; 3147} 3148 3149// useful helper functions 3150var getRandom = function (min, max) { 3151 "use strict"; 3152 return Math.random() * (max - min) + min; 3153}; 3154 3155var getRandomInt = function (min, max) { 3156 "use strict"; 3157 return Math.floor(Math.random() * (max - min)) + min; 3158}; 3159 3160var getRandomColor = function () { 3161 "use strict"; 3162 var c = color(random(127, 255), random(127, 255), random(127, 255)); 3163 return c; 3164}; 3165 3166var cosTable = new Array(360); 3167var sinTable = new Array(360); 3168var PI = Math.PI; 3169 3170// pre compute sine and cosine values to the nearest degree 3171for (i = 0; i < 360; i++) { 3172 cosTable[i] = Math.cos((i / 360) * 2 * PI); 3173 sinTable[i] = Math.sin((i / 360) * 2 * PI); 3174} 3175 3176var fastSin = function (xDeg) { 3177 "use strict"; 3178 var deg = Math.round(xDeg); 3179 if (deg >= 0) { 3180 return sinTable[(deg % 360)]; 3181 } 3182 return -sinTable[((-deg) % 360)]; 3183}; 3184 3185var fastCos = function (xDeg) { 3186 "use strict"; 3187 var deg = Math.round(Math.abs(xDeg)); 3188 return cosTable[deg % 360]; 3189}; 3190 3191// get orientation of mobile device 3192 3193/** 3194 * Determine the mobile operating system. 3195 * This function either returns 'iOS', 'Android' or 'unknown' 3196 * 3197 * @returns {String} 3198 */ 3199function getMobileOperatingSystem() { 3200 var userAgent = navigator.userAgent || navigator.vendor || window.opera; 3201 3202 if( userAgent.match( /iPad/i ) || userAgent.match( /iPhone/i ) || userAgent.match( /iPod/i ) ) 3203 { 3204 return 'iOS'; 3205 3206 } 3207 else if( userAgent.match( /Android/i ) ) 3208 { 3209 3210 return 'Android'; 3211 } 3212 else 3213 { 3214 return 'unknown'; 3215 } 3216} 3217 3218var Orientation = { 3219 enabled: false, 3220 x: 0, 3221 y: 0, 3222 z: 0, 3223 alpha: 0, 3224 beta: 0, 3225 gamma: 0, 3226 normalizer: 1, 3227 toString: function() { 3228 var result = ""; 3229 if (this.enabled === true) { 3230 result = "a:"+this.alpha+"\tb:"+this.beta+"\tg:"+this.gamma+"\tx:"+this.x+"\ty:"+this.y+"\tz:"+this.z; 3231 } 3232 return result; 3233 }, 3234 getX: function() { 3235 var result = 0; 3236 if (this.x) { 3237 result = this.x*this.normalizer; 3238 } 3239 return result; 3240 }, 3241 getY: function() { 3242 var result = 0; 3243 if (this.y) { 3244 result = this.y*this.normalizer; 3245 } 3246 return result; 3247 }, 3248 getZ: function() { 3249 var result = 0; 3250 if (this.z) { 3251 result = this.z; 3252 } 3253 return result; 3254 }, 3255 getAlpha: function() { 3256 var result = 0; 3257 if (this.alpha) { 3258 result = this.alpha; 3259 } 3260 return result; 3261 }, 3262 getBeta: function() { 3263 var result = 0; 3264 if (this.beta) { 3265 result = this.beta; 3266 } 3267 return result; 3268 }, 3269 getGamma: function() { 3270 var result = 0; 3271 if (this.gamma) { 3272 result = this.gamma; 3273 } 3274 return result; 3275 }, 3276 getMagnitude: function() { 3277 var result = 0.0; 3278 if (this.x && this.y) { 3279 return Math.sqrt(this.x*this.x+this.y*this.y); 3280 } 3281 return result; 3282 }, 3283 getMag2: function() { 3284 var result = 0.0; 3285 if (this.x && this.y) { 3286 return this.x*this.x+this.y*this.y; 3287 } 3288 return result; 3289 } 3290 3291}; 3292 3293if (getMobileOperatingSystem() === 'Android') { 3294 Orientation.normalizer = -1.0; 3295} 3296 3297window.addEventListener('devicemotion', function (e) { 3298 Orientation.x = Math.round(e.accelerationIncludingGravity.x*10)/10; 3299 Orientation.y = Math.round(e.accelerationIncludingGravity.y*10)/10; 3300 Orientation.z = Math.round(e.accelerationIncludingGravity.z*10)/10; 3301 if (Orientation.getMag2() > 0.00000001) { 3302 Orientation.enabled = true; 3303 } else { 3304 Orientation.enabled = false; 3305 } 3306}, false); 3307 3308 3309window.addEventListener('deviceorientation', function (e) { 3310 Orientation.alpha = Math.round(e.alpha*10)/10; 3311 Orientation.beta = Math.round(e.beta*10)/10; 3312 Orientation.gamma = Math.round(e.gamma*10)/10; 3313}, false); 3314 3315/* 3316if((window.DeviceMotionEvent) || ('listenForDeviceMovement' in window)){ // gyroscope support 3317 console.log('DeviceOrientationEvent support OK'); 3318 Orientation.enabled = true; 3319} else { 3320 console.log('DeviceOrientationEvent support KO'); 3321 Orientation.enabled = false;
3322} 3323*/ 3324 3325// library of physical objects we can use in the p5 script. 3326 3327// constants: 3328// reference heights/widths 3329var ref_w = 36; 3330var ref_h = 48; 3331// define "one ref_u" relative to scale 3332var ref_u = 1; 3333 3334// A boundary is a simple rectangle with x,y,width,and height (box2d natural coordinates) 3335function Boundary(x_, y_, w_, h_) { 3336 "use strict"; 3337 // But we also have to make a body for box2d to know about it 3338 // Body b; 3339 this.x = x_; 3340 this.y = y_; 3341 this.w = w_; 3342 this.h = h_; 3343 this.fillColor = getRandomColor(); 3344 3345 var fd = new box2d.b2FixtureDef(); 3346 fd.density = 1.0; 3347 fd.friction = 0.9; 3348 fd.restitution = 0.2; 3349 3350 var bd = new box2d.b2BodyDef(); 3351 3352 bd.type = box2d.b2BodyType.b2_staticBody; 3353 bd.position.x = x_; 3354 bd.position.y = y_; 3355 fd.shape = new box2d.b2PolygonShape(); 3356 fd.shape.SetAsBox(this.w / 2.0, this.h / 2.0); 3357 this.body = world.CreateBody(bd).CreateFixture(fd); 3358} 3359 3360// Draw the boundary, if it were at an angle we'd have to do something fancier 3361Boundary.prototype.display = function () { 3362 "use strict"; 3363 var pos = scaleToPixels(this.x, this.y); 3364 fill(this.fillColor); 3365 stroke(40); 3366 strokeWeight(1); 3367 rectMode(CENTER); 3368 rect(pos.x, pos.y, scaleToPixels(this.w), scaleToPixels(this.h)); 3369}; 3370 3371// mouse spring object: 3372 3373// Constructor 3374function Spring(x, y) { 3375 "use strict"; 3376 // At first it doesn't exist 3377 this.mouseJoint = null; 3378} 3379 3380// If it exists we set its target to the mouse location 3381Spring.prototype.update = function (x, y) { 3382 "use strict"; 3383 if (this.mouseJoint !== null) { 3384 // Always convert to world coordinates! 3385 var mouseWorld = scaleToWorld(x, y); 3386 this.mouseJoint.SetTarget(mouseWorld); 3387 } 3388}; 3389 3390Spring.prototype.display = function () { 3391 "use strict"; 3392 if (this.mouseJoint !== null) { 3393 3394 var posA = this.mouseJoint.GetAnchorA(); 3395 var posB = this.mouseJoint.GetAnchorB(); 3396 3397 // We can get the two anchor points 3398 var v1 = scaleToPixels(posA.x, posA.y); 3399 var v2 = scaleToPixels(posB.x, posB.y); 3400 // And just draw a line 3401 stroke(240); 3402 strokeWeight(1); 3403 3404 line(v1.x, v1.y, v2.x, v2.y); 3405 } 3406}; 3407 3408// This is the key function where 3409// we attach the spring to an x,y location 3410// and the Box object's location 3411Spring.prototype.bind = function (x, y, box) { 3412 "use strict"; 3413 // Define the joint 3414 var md = new box2d.b2MouseJointDef(); 3415 // Body A is just a fake ground body for simplicity (there isn't anything at the mouse) 3416 md.bodyA = world.CreateBody(new box2d.b2BodyDef()); //world.GetGroundBody(); 3417 // Body 2 is the box's boxy 3418 md.bodyB = box.body; 3419 // Get the mouse location in world coordinates 3420 var mp = scaleToWorld(x, y); 3421 // And that's the target 3422 //println(mp.x + " " + mp.y); 3423 md.target = mp; 3424 //println(md.target.x + " " + md.target.y); 3425 3426 // Some stuff about how strong and bouncy the spring should be 3427 md.maxForce = 2000.0 * box.body.m_mass; 3428 md.frequencyHz = 5; 3429 md.dampingRatio = 0.9; 3430 3431 // Make the joint! 3432 this.mouseJoint = world.CreateJoint(md); 3433}; 3434 3435Spring.prototype.destroy = function () { 3436 "use strict"; 3437 // We can get rid of the joint when the mouse is released 3438 if (this.mouseJoint !== null) { 3439 world.DestroyJoint(this.mouseJoint); 3440 this.mouseJoint = null; 3441 } 3442}; 3443 3444// movable living objects below: 3445 3446// setup generic object that contains a set of reusable functions. other shapes derive from B2Generic 3447var B2Generic = function () { 3448 "use strict"; 3449 this.body = null; 3450 this.life = 60; 3451}; 3452 3453// This function removes the particle from the box2d world, and also in paper.js 3454B2Generic.prototype.killBody = function () { 3455 "use strict"; 3456 world.DestroyBody(this.body); 3457}; 3458 3459B2Generic.prototype.contains = function (x, y) { 3460 "use strict"; 3461 var worldPoint = scaleToWorld(x, y); 3462 var f = this.body.GetFixtureList(); 3463 var inside = f.TestPoint(worldPoint); 3464 return inside; 3465}; 3466 3467// returns screen position 3468B2Generic.prototype.getPosition = function () { 3469 "use strict"; 3470 return scaleToPixels(this.body.GetPosition()); 3471}; 3472 3473// returns screen position 3474B2Generic.prototype.getWorldPosition = function () { 3475 "use strict"; 3476 return this.body.GetPosition(); 3477}; 3478 3479// Is the particle ready for deletion? 3480B2Generic.prototype.done = function () { 3481 "use strict"; 3482 var pos = this.body.GetPosition(); // world position 3483 3484 // kills it sooner if it rolls off the edge 3485 3486 // Is it off the bottom of the screen? also kill it if the life is non positive 3487 /* 3488 if (pos.x > ref_w || pos.x < 0) { 3489 return true; 3490 } 3491 */ 3492 3493 // Is it off the bottom of the screen? also kill it if the life is non positive 3494 if (this.life <= 0 || pos.y > scaleToWorld(height) + 10 * ref_u) { 3495 return true; 3496 } 3497 return false;
3498}; 3499 3500B2Generic.prototype.contains = function (x, y) { 3501 var worldPoint = scaleToWorld(x, y); 3502 var f = this.body.GetFixtureList(); 3503 var inside = f.TestPoint(worldPoint); 3504 return inside; 3505}; 3506 3507// returns position relative to center, x-axis, in terms of ref_w 3508B2Generic.prototype.getRelativePosition = function () { 3509 "use strict"; 3510 var bodyPos = this.body.GetPosition(); 3511 return (bodyPos.x - ref_w / 2) / (64); 3512}; 3513 3514var Setting = function (s) { 3515 "use strict"; 3516 this.x1 = s.x1 || s.x || 0; 3517 this.x2 = s.x2 || s.x || ref_w; 3518 this.y1 = s.y1 || s.y || 0; 3519 this.y2 = s.y2 || s.y || ref_h; 3520 this.r = s.r || 2.4; 3521 this.w = s.w || 2.4; 3522 this.h = s.h || 3.6; 3523 this.numEdges = s.numEdges || 3; 3524 this.ignoreCollision = s.ignoreCollision || false; 3525 this.density = s.density || 1.0; 3526 this.friction = s.friction || 0.9; 3527 this.restitution = s.restitution || 0.1; 3528 this.fillColor = getRandomColor(); // always generate new color to spice things up! 3529 this.edgeColor = s.edgeColor || 40; 3530 this.initialMove = s.initialMove || false; 3531 this.parentObject = s.parentObject || null; 3532}; 3533 3534// the box object: 3535 3536var Box = function (setting_) { 3537 "use strict"; 3538 this.setting = new Setting(setting_); 3539 3540 // console.log('new box created'); 3541 // console.log(this.setting); 3542 3543 // Define a body 3544 var bd = new box2d.b2BodyDef(); 3545 bd.type = box2d.b2BodyType.b2_dynamicBody; 3546 bd.position.x = getRandom(this.setting.x1, this.setting.x2); 3547 bd.position.y = getRandom(this.setting.y1, this.setting.y2); 3548 3549 // Define a fixture 3550 var fd = new box2d.b2FixtureDef(); 3551 // Fixture holds shape 3552 fd.shape = new box2d.b2PolygonShape(); 3553 fd.shape.SetAsBox((this.setting.w / 2), (this.setting.h / 2)); 3554 3555 // Some physics 3556 fd.density = this.setting.density; 3557 fd.friction = this.setting.friction; 3558 fd.restitution = this.setting.restitution; 3559 3560 // stick1 not subject to collision 3561 if (this.setting.ignoreCollision) { 3562 fd.filter.maskBits = 0; 3563 bd.linearDamping = 0.1; 3564 } 3565 3566 // Create the body 3567 this.body = world.CreateBody(bd); 3568 // Attach the fixture 3569 this.body.CreateFixture(fd);
3570 // needed for collision: 3571 this.body.SetUserData(this); 3572 3573 // Some additional stuff 3574 if (this.setting.initialMove) { 3575 this.body.SetLinearVelocity(new box2d.b2Vec2(random(-4, 4), random(-4, 0))); 3576 this.body.SetAngularVelocity(random(-4, 4)); 3577 } 3578}; 3579Box.prototype = new B2Generic(); 3580 3581// Drawing the box 3582Box.prototype.display = function () { 3583 "use strict"; 3584 // Get the body's position 3585 var pos = this.getPosition(); 3586 // Get its angle of rotation 3587 var a = this.body.GetAngleRadians(); 3588 3589 // Draw it! 3590 rectMode(CENTER); 3591 push(); 3592 translate(pos.x, pos.y); 3593 rotate(a); 3594 fill(this.setting.fillColor); 3595 stroke(this.setting.edgeColor); 3596 strokeWeight(1); 3597 rect(0, 0, scaleToPixels(this.setting.w), scaleToPixels(this.setting.h)); 3598 pop(); 3599}; 3600 3601// circle: 3602var Circle = function (s_) { 3603 "use strict"; 3604 this.setting = new Setting(s_); 3605 3606 // console.log('new circle created'); 3607 // console.log(this.setting); 3608 3609 // Define a body 3610 var bd = new box2d.b2BodyDef(); 3611 bd.type = box2d.b2BodyType.b2_dynamicBody; 3612 bd.position.x = getRandom(this.setting.x1, this.setting.x2); 3613 bd.position.y = getRandom(this.setting.y1, this.setting.y2); 3614 3615 // Define a fixture 3616 var fd = new box2d.b2FixtureDef(); 3617 // Fixture holds shape 3618 fd.shape = new box2d.b2CircleShape(); 3619 fd.shape.m_radius = this.setting.r; 3620 3621 // Some physics 3622 fd.density = this.setting.density; 3623 fd.friction = this.setting.friction; 3624 fd.restitution = this.setting.restitution; 3625 3626 // stick1 not subject to collision 3627 if (this.setting.ignoreCollision) { 3628 fd.filter.maskBits = 0; 3629 bd.linearDamping = 0.1; 3630 } 3631 3632 // Create the body 3633 this.body = world.CreateBody(bd); 3634 // Attach the fixture 3635 this.body.CreateFixture(fd);
3636 // needed for collision: 3637 this.body.SetUserData(this); 3638 3639 // Some additional stuff 3640 if (this.setting.initialMove) { 3641 this.body.SetLinearVelocity(new box2d.b2Vec2(random(-4, 4), random(-4, 0))); 3642 this.body.SetAngularVelocity(random(-4, 4)); 3643 } 3644}; 3645Circle.prototype = new B2Generic(); 3646 3647Circle.prototype.display = function () { 3648 "use strict"; 3649 // Get the body's position in screen space 3650 var pos = this.getPosition(); 3651 // Get its angle of rotation 3652 var a = this.body.GetAngleRadians(); 3653 3654 // Draw it! 3655 rectMode(CENTER); 3656 push(); 3657 translate(pos.x, pos.y); 3658 rotate(a); 3659 fill(this.setting.fillColor); 3660 stroke(this.setting.edgeColor); 3661 strokeWeight(1); 3662 ellipse(0, 0, scaleToPixels(this.setting.r * 2), scaleToPixels(this.setting.r * 2)); 3663 // Let's add a line so we can see the rotation 3664 line(0, 0, scaleToPixels(this.setting.r), 0); 3665 pop(); 3666}; 3667 3668// NGon: 3669var NGon = function (s_) { 3670 this.setting = new Setting(s_); 3671 3672 // console.log('new polygon created'); 3673 // console.log(this.setting); 3674 3675 // Define a body 3676 var bd = new box2d.b2BodyDef(); 3677 bd.type = box2d.b2BodyType.b2_dynamicBody; 3678 bd.position.x = getRandom(this.setting.x1, this.setting.x2); 3679 bd.position.y = getRandom(this.setting.y1, this.setting.y2); 3680 3681 // Define a fixture 3682 var fd = new box2d.b2FixtureDef(); 3683 // Fixture holds shape 3684 fd.shape = new box2d.b2PolygonShape(); 3685 3686 var vertices = []; 3687 var angleStep = Math.PI * 2 / this.setting.numEdges; 3688 var i; 3689 3690 for (i = this.setting.numEdges; i >= 0; i--) { 3691 vertices.push(new box2d.b2Vec2(this.setting.r * Math.cos(i * angleStep), this.setting.r * Math.sin(i * angleStep))); 3692 } 3693 3694 fd.shape.SetAsArray(vertices, vertices.length); 3695 3696 // Some physics 3697 fd.density = this.setting.density; 3698 fd.friction = this.setting.friction; 3699 fd.restitution = this.setting.restitution; 3700 3701 // stick1 not subject to collision 3702 if (this.setting.ignoreCollision) { 3703 fd.filter.maskBits = 0; 3704 bd.linearDamping = 0.1; 3705 } 3706 3707 // Create the body 3708 this.body = world.CreateBody(bd); 3709 // Attach the fixture 3710 this.body.CreateFixture(fd);
3711 // needed for collision: 3712 this.body.SetUserData(this); 3713 3714 // Some additional stuff 3715 if (this.setting.initialMove) { 3716 this.body.SetLinearVelocity(new box2d.b2Vec2(random(-4, 4), random(-4, 0))); 3717 this.body.SetAngularVelocity(random(-4, 4)); 3718 } 3719}; 3720NGon.prototype = new B2Generic(); 3721 3722NGon.prototype.display = function () { 3723 "use strict"; 3724 // Get the body's position in screen space 3725 var pos = this.getPosition(); 3726 // Get its angle of rotation 3727 var a = this.body.GetAngleRadians(); 3728 3729 // Draw it! 3730 rectMode(CENTER); 3731 push(); 3732 translate(pos.x, pos.y); 3733 rotate(a); 3734 fill(this.setting.fillColor); 3735 stroke(this.setting.edgeColor); 3736 strokeWeight(1); 3737 3738 var angleStep = Math.PI * 2 / this.setting.numEdges; 3739 var i; 3740 beginShape(); 3741 for (i = this.setting.numEdges; i >= 0; i--) { 3742 vertex(scaleToPixels(this.setting.r * Math.cos(i * angleStep)), scaleToPixels(this.setting.r * Math.sin(i * angleStep))); 3743 } 3744 endShape(CLOSE); 3745 3746 // Let's add a line so we can see the rotation 3747 //line(0,0,scaleToPixels(this.setting.r),0); 3748 3749 pop(); 3750}; 3751 3752// Constructor 3753var Pendulum = function (s_) { 3754 "use strict"; 3755 this.setting = s_; 3756 this.DEFAULTLIFE = 30 * 5; // default life ~ n seconds assumign 30fps 3757 this.CUTOFFANGLE = 70; 3758 this.life = this.setting.life || this.DEFAULTLIFE; 3759 this.dying = false; 3760 this.len = 9.6 * ref_u * 1.1; 3761 this.control = false; // when this is on, then pendulum is alive. 3762 this.manualControl = true; // manual override. can override this.control 3763 this.controlDelay = -1; // delayed start upon contact. 3764 this.initControlDelay = 20; 3765 this.score = 0; // how well it is doing in the physical world (can do cool stuff like adj color) 3766 3767 // 1 means upward, -1 means facing ground. 3768 this.upright = -1.0; 3769 3770 var moveEverythingAtBeginning = false; 3771 if (this.setting.startMode && this.setting.startMode === "random") { 3772 moveEverythingAtBeginning = true; 3773 } 3774 if (this.setting.startOrientation && this.setting.startOrientation === "upright") { 3775 this.upright = 1.0; 3776 } 3777 3778 // keep track of prev angles for velocity 3779 this.prevAngle = 0; 3780 this.prevLowerAngle = 0; 3781 this.angleVelocity = 0; 3782 this.lowerAngleVelocity = 0; 3783 3784 this.cart = new Box({ 3785 x: this.setting.x, 3786 y: this.setting.y, 3787 w: 3 * ref_u, 3788 h: 1.6 * ref_u, 3789 density: 1.0 / 2, 3790 initialMove: moveEverythingAtBeginning, 3791 parentObject: this 3792 }); 3793 3794 this.wheel = new Circle({ 3795 x: this.setting.x, 3796 y: this.setting.y, 3797 r: 2.5 * ref_u, 3798 density: 5, 3799 initialMove: moveEverythingAtBeginning, 3800 parentObject: this 3801 }); 3802 this.wheel.maxShaderLevel = 50; 3803 this.wheel.shaderLevel = 0; 3804 this.wheel.offColor = color(255 - this.wheel.maxShaderLevel, 190 - this.wheel.maxShaderLevel, 50 - this.wheel.maxShaderLevel); 3805 3806 this.wheel.setting.fillColor = this.wheel.offColor; 3807 3808 // joint is not selectable by mouse, and collision is ignored. 3809 var offCollision = true; 3810 this.stick1 = new Box({ 3811 x: this.setting.x, 3812 y: this.setting.y, 3813 w: 0.8 * ref_u, 3814 h: this.len, 3815 density: 1.0 / 2, 3816 ignoreCollision: true, 3817 initialMove: moveEverythingAtBeginning, 3818 parentObject: this 3819 }); 3820 this.stick2 = new Box({ 3821 x: this.setting.x, 3822 y: this.setting.y, 3823 w: 0.8 * ref_u, 3824 h: this.len, 3825 density: 1.0 / 2, 3826 ignoreCollision: true, 3827 initialMove: moveEverythingAtBeginning, 3828 parentObject: this 3829 }); 3830 this.handle = new Circle({ 3831 x: this.setting.x, 3832 y: this.setting.y, 3833 w: 3.6 * ref_u / 2.5, 3834 r: 2.0 * ref_u, 3835 h: 3.6 * ref_u / 2, 3836 density: 1.0 / 6, 3837 ignoreCollision: offCollision, 3838 initialMove: moveEverythingAtBeginning, 3839 parentObject: this 3840 }); 3841 3842 var wjd = new box2d.b2WeldJointDef(); 3843 wjd.bodyA = this.cart.body; 3844 wjd.bodyB = this.stick1.body; 3845 wjd.localAnchorB = makeB2Vec2(0, this.upright * this.len / 2); 3846 var wj = world.CreateJoint(wjd); 3847 3848 var wjd2 = new box2d.b2RevoluteJointDef(); 3849 wjd2.bodyA = this.stick2.body; 3850 wjd2.bodyB = this.stick1.body; 3851 wjd2.localAnchorA = makeB2Vec2(0, this.upright * this.len / 2); 3852 wjd2.localAnchorB = makeB2Vec2(0, -this.upright * this.len / 2); 3853 var wj2 = world.CreateJoint(wjd2); 3854 3855 var wjd3 = new box2d.b2WeldJointDef(); 3856 wjd3.bodyA = this.handle.body; 3857 wjd3.bodyB = this.stick2.body; 3858 wjd3.localAnchorB = makeB2Vec2(0, -this.upright * this.len / 2); 3859 var wj3 = world.CreateJoint(wjd3); 3860 3861 var jd = new box2d.b2WheelJointDef(); // make wheel joint 3862 jd.bodyA = this.wheel.body; 3863 jd.bodyB = this.cart.body; 3864 3865 jd.frequencyHz = 30; 3866 jd.dampingRatio = 1.0; 3867 jd.maxMotorTorque = 3000; 3868 3869 this.motor = world.CreateJoint(jd); 3870 this.motor.EnableMotor(true); 3871 this.setMotorSpeed(0); 3872}; 3873Pendulum.prototype = new B2Generic(); 3874 3875// turns on and off control 3876Pendulum.prototype.enableControl = function () { 3877 "use strict"; 3878 this.controlDelay = this.initControlDelay; 3879 this.initControlDelay = 1; 3880 //console.log('control enabled naturally.'); 3881}; 3882Pendulum.prototype.disableControl = function () { 3883 "use strict"; 3884 this.control = false;
3885}; 3886Pendulum.prototype.controlMode = function () { 3887 "use strict"; 3888 return this.control && this.manualControl; 3889}; 3890Pendulum.prototype.toggleControl = function () { // enable or disable controls manually override 3891 "use strict"; 3892 this.manualControl = !this.manualControl; 3893}; 3894 3895// This function removes the particle from the box2d world 3896Pendulum.prototype.killBody = function () { 3897 "use strict"; 3898 this.cart.killBody(); 3899 this.wheel.killBody(); 3900 this.stick1.killBody(); 3901 this.stick2.killBody(); 3902 this.handle.killBody(); 3903}; 3904 3905Pendulum.prototype.done = function () { 3906 "use strict"; 3907 return this.cart.done() || this.life <= 0; 3908}; 3909 3910Pendulum.prototype.setMotorSpeed = function (speed) { 3911 "use strict"; 3912 this.motor.SetMotorSpeed(speed); 3913}; 3914 3915Pendulum.prototype.getMotorSpeed = function () { 3916 "use strict"; 3917 return this.motor.GetMotorSpeed(); 3918}; 3919 3920// return angle of second handle, with no normalisation. 3921Pendulum.prototype.getRawAngleDegrees = function () { 3922 "use strict"; 3923 var angle1 = (this.stick1.body.GetAngle() * 360 / (2 * PI)); 3924 var angle2 = (this.stick2.body.GetAngle() * 360 / (2 * PI)); 3925 return angle2; 3926}; 3927 3928// return angle of second handle 3929Pendulum.prototype.getAngleDegrees = function () { 3930 "use strict"; 3931 var angle = this.getRawAngleDegrees(); 3932 angle += 90 * (1 - this.upright); // if pendulum is set to be inverted at the beginning, must rotate 180 deg. 3933 while (angle > 180) angle -= 360; 3934 while (angle < -180) angle += 360; 3935 return angle; 3936}; 3937 3938// return angle of first handle, with no normalisation. 3939Pendulum.prototype.getRawLowerAngleDegrees = function () { 3940 "use strict"; 3941 var angle1 = (this.stick1.body.GetAngle() * 360 / (2 * PI)); 3942 var angle2 = (this.stick2.body.GetAngle() * 360 / (2 * PI)); 3943 return angle1; 3944}; 3945 3946// return angle of first handle 3947Pendulum.prototype.getLowerAngleDegrees = function () { 3948 "use strict"; 3949 var angle = this.getRawLowerAngleDegrees(); 3950 angle += 90 * (1 - this.upright); // if pendulum is set to be inverted at the beginning, must rotate 180 deg. 3951 while (angle > 180) angle -= 360; 3952 while (angle < -180) angle += 360; 3953 return angle; 3954}; 3955 3956Pendulum.prototype.getPosition = function () { 3957 "use strict"; 3958 var bodyPos = scaleToPixels(this.wheel.body.GetPosition()); 3959 var handlePos = scaleToPixels(this.handle.body.GetPosition()); 3960 var returnPos = new box2d.b2Vec2(); 3961 returnPos.x = 0.9 * bodyPos.x + 0.1 * handlePos.x; 3962 returnPos.y = 0.9 * bodyPos.y + 0.1 * handlePos.y; 3963 return returnPos; 3964}; 3965 3966Pendulum.prototype.getWorldPosition = function () { 3967 "use strict"; 3968 var bodyPos = this.wheel.body.GetPosition(); 3969 var handlePos = this.handle.body.GetPosition(); 3970 var returnPos = new box2d.b2Vec2(); 3971 returnPos.x = 0.9 * bodyPos.x + 0.1 * handlePos.x; 3972 returnPos.y = 0.9 * bodyPos.y + 0.1 * handlePos.y; 3973 return returnPos; 3974}; 3975 3976// returns position relative to center, x-axis, in terms of ref_w 3977Pendulum.prototype.getRelativePosition = function () { 3978 "use strict"; 3979 var bodyPos = this.wheel.body.GetPosition(); 3980 return (bodyPos.x - ref_w / 2) / 64; 3981}; 3982 3983Pendulum.prototype.getAngleVelocity = function () { 3984 "use strict"; 3985 return this.angleVelocity; 3986}; 3987 3988Pendulum.prototype.getLowerAngleVelocity = function () { 3989 "use strict"; 3990 return this.lowerAngleVelocity; 3991}; 3992 3993Pendulum.prototype.setScore = function(score) { 3994 "use strict"; 3995 this.score = score; 3996}; 3997 3998Pendulum.prototype.display = function () { 3999 "use strict"; 4000 // Get the body's position 4001 //var pos1 = scaleToPixels(this.wheel.body.GetPosition()); 4002 //var pos2 = scaleToPixels(this.stick1.body.GetPosition()); 4003 4004 this.wheel.display(); 4005 this.cart.display(); 4006 this.handle.display(); 4007 this.stick1.display(); 4008 this.stick2.display(); 4009 4010 // change color slowly as it becomes enabled or disabled 4011 var enabled = this.controlMode(); 4012 var r = red(this.wheel.setting.fillColor); 4013 var g = green(this.wheel.setting.fillColor); 4014 var b = blue(this.wheel.setting.fillColor); 4015 var shaderStep = 5; 4016 4017 if (enabled === true && this.wheel.shaderLevel <= this.wheel.maxShaderLevel) { 4018 this.wheel.shaderLevel += shaderStep; 4019 this.wheel.setting.fillColor = color(r + shaderStep, g + shaderStep, b + shaderStep); 4020 } else if (enabled === false && this.wheel.shaderLevel >= 0) { 4021 this.wheel.shaderLevel -= shaderStep; 4022 this.wheel.setting.fillColor = color(r - shaderStep, g - shaderStep, b - shaderStep); 4023 } 4024 4025 // cool trick to set upper handle's color level depending on score 4026 this.handle.setting.fillColor = color(255, 220, 50, min(50+this.score*10, 255)); 4027 4028}; 4029 4030Pendulum.prototype.update = function () { 4031 "use strict"; 4032 // store previous angle and calculate angular velocity 4033 var currentAngle = this.getRawAngleDegrees(); 4034 this.angleVelocity = 30 * (currentAngle - this.prevAngle); 4035 this.prevAngle = currentAngle; 4036 var currentLowerAngle = this.getRawLowerAngleDegrees(); 4037 this.lowerAngleVelocity = 30 * (currentLowerAngle - this.prevLowerAngle); 4038 this.prevLowerAngle = currentLowerAngle; 4039 4040 // dying logic here. 4041 if (this.dying) { 4042 this.life--; 4043 } 4044 4045 if (this.controlDelay >= 0) { 4046 this.controlDelay--; 4047 if (this.controlDelay === 0) { 4048 this.control = true; 4049 } 4050 } 4051 4052 var currentAbsAngle = abs(this.getAngleDegrees()); 4053 if (this.dying === false && currentAbsAngle > this.CUTOFFANGLE) { 4054// this.dying = true; 4055 } else if (this.dying === true && currentAbsAngle <= this.CUTOFFANGLE) { 4056 this.dying = false;
4057 this.life = this.setting.life || this.DEFAULTLIFE; 4058 } 4059}; 4060 4061function Timer() { 4062 "use strict"; 4063 this.startTime = 0; 4064} 4065 4066Timer.prototype.start = function () { 4067 "use strict"; 4068 this.startTime = millis(); 4069}; 4070 4071 4072Timer.prototype.reset = function () { 4073 "use strict"; 4074 var timeTaken = millis() - this.startTime; 4075 this.startTime = millis(); 4076 return timeTaken; 4077}; 4078 4079function NeuroController(initGene_) { 4080 "use strict"; 4081 4082 // load up json chromosome 4083 4084 // pretrain network goes here: 4085 var initGene = null || initGene_; 4086 this.geneData = null; 4087 4088 // set current internal states 4089 this.theta0 = 0; 4090 this.theta0_dot = 0; 4091 this.theta1 = 0; 4092 this.theta1_dot = 0; 4093 this.pos = 0; 4094 4095 this.isTraining = false; 4096 this.generation = 0; 4097 4098 // define the neural network architecture: 4099 this.Nsp = 2; 4100 this.espnet = new convnetjs.ESPNet(this.Nsp, 5, 16, initGene); 4101 // create initial network with this.Nsp subpopulations, 5 inputs, 32 hidden neurons 4102 4103 this.trainer = new convnetjs.ESPTrainer(this.espnet, { 4104 population_size: 50, 4105 mutation_rate: 0.20, 4106 mutation_size: 0.1, 4107 num_passes: 1, 4108 elite_percentage: 0.20, 4109 burst_generations: 8, 4110 best_mode: false 4111 }, 4112 initGene); 4113 4114 this.geneData = this.espnet.getGenes(); 4115 4116 // add random colors for each sub population, and keep them! 4117 this.colorR = convnetjs.zeros(this.Nsp); 4118 this.colorG = convnetjs.zeros(this.Nsp); 4119 this.colorB = convnetjs.zeros(this.Nsp); 4120 for (var i = 0; i < this.Nsp; i++ ) { 4121 this.colorR[i] = convnetjs.randi(0, 255); 4122 this.colorG[i] = convnetjs.randi(0, 255); 4123 this.colorB[i] = convnetjs.randi(0, 255); 4124 } 4125 4126} 4127 4128NeuroController.prototype.setNetwork = function (network) { 4129 "use strict"; 4130 this.espnet = network; 4131}; 4132 4133NeuroController.prototype.setInput = function (theta0, theta0_dot, theta1, theta1_dot, pos) { 4134 "use strict"; 4135 this.theta0 = theta0; 4136 this.theta0_dot = theta0_dot; 4137 this.theta1 = theta1; 4138 this.theta1_dot = theta1_dot; 4139 this.pos = pos; 4140}; 4141 4142NeuroController.prototype.train = function (fitFunc, nGeneration) { 4143 "use strict"; 4144 var fitness = 0; 4145 for (var i = 0; i < nGeneration; i++) { 4146 this.isTraining = true; 4147 fitness = this.trainer.train(fitFunc); 4148 console.log('gen #' + (this.generation++) + ' fitness=' + Math.round(fitness * 100) / 100); 4149 this.isTraining = false; 4150 } 4151 4152 this.geneData = this.espnet.getGenes(); 4153}; 4154 4155NeuroController.prototype.getBestGenes = function () { 4156 "use strict"; 4157 return this.trainer.bestGenes; 4158}; 4159 4160NeuroController.prototype.getBestFitness = function () { 4161 "use strict"; 4162 return this.trainer.bestFitness; 4163}; 4164 4165// get current input for nn (normalised data so the values are in the order of -10 to +10) 4166NeuroController.prototype.getCurrentInputState = function () { 4167 "use strict"; 4168 var x = [0, 0, 0, 0, 0]; 4169 x[0] = this.theta0 / 18; // w is the field holding the actual data 4170 x[1] = this.theta0_dot / 18; 4171 x[2] = this.theta1 / 18; // w is the field holding the actual data 4172 x[3] = this.theta1_dot / 18; 4173 x[4] = this.pos * 10; 4174 return x; 4175}; 4176 4177NeuroController.prototype.getOutput = function () { 4178 "use strict"; 4179 // get output from neural network: 4180 var x = this.getCurrentInputState(); 4181 this.espnet.setInput(x); 4182 var y = this.espnet.forward(); 4183 return y; 4184}; 4185 4186NeuroController.prototype.drawNetwork2 = function () { 4187 // draw network weights on p5.js canvas 4188 "use strict"; 4189 // modify this code so that it works directly with bestGenes array. 4190 // modify code below: 4191 4192 var maxBoxSize = width/8; 4193 4194 function drawWeights(gene, level, numGene, r, g, b) { 4195 var i, j, k; 4196 4197 var shade; 4198 var boxSize; 4199 noStroke(); 4200 boxSize = Math.min(Math.floor(Math.sqrt((width-maxBoxSize)*height/(gene.length*numGene))/1.1), maxBoxSize); 4201 4202 var nBoxRow = Math.floor((width-maxBoxSize)/boxSize); 4203 4204 for(k = gene.length-1; k >= 0; k--) { 4205 i = Math.floor(k/nBoxRow); 4206 j = k % nBoxRow; 4207 shade = 128*(gene[k]*0.5)+128; 4208 fill(r, g, b, shade/6); 4209 rect(j*boxSize,i*boxSize+level*(height/numGene), boxSize, boxSize); 4210 } 4211 } 4212 4213 function drawIO(input, output) { 4214 var i; 4215 var shade; 4216 var inputLength = input.length; 4217 var outputLength = output.length; 4218 var boxSize = Math.min(Math.floor((min(height, width))/(inputLength+2)), maxBoxSize); 4219 noStroke(); 4220 for(i = 0; i < inputLength; i++) { 4221 shade = min(255,max(128*(input[i])+128, 0)); 4222 fill(0, 0, 255, shade/16); 4223 rect(width/2-inputLength*boxSize/2+(i+0)*boxSize, height*1/3, boxSize, boxSize); 4224 } 4225 /* 4226 for (i = 0; i < outputLength; i++) { 4227 shade = 128*(output[i])+128; 4228 fill(0, 0, shade, 40); 4229 rect(width-boxSize, (i+inputLength)*boxSize, boxSize, boxSize); 4230 } 4231 */ 4232 } 4233/* 4234 var genes = this.geneData; 4235 var numGene = this.Nsp; 4236 4237 for (var i = 0; i < numGene; i++) { 4238 drawWeights(genes[i], i, numGene, this.colorR[i], this.colorG[i], this.colorB[i]); 4239 } 4240*/ 4241 4242 drawIO(this.espnet.input.w, this.espnet.output); 4243}; 4244 4245 4246// main sketch code: 4247 4248// A reference to our box2d world 4249var world; 4250 4251// gets the handy zeros function to generate a fast floating point array filled with zeros 4252var zeros = convnetjs.zeros; 4253 4254// A list we'll use to track fixed objects 4255var boundaries = []; 4256 4257var pendulum; // define the main character of this show. 4258var follow; // random ball that bounces around. 4259var baseplate; // the movable plate for which the pendulum moves around on. 4260 4261// A list of movable objects that can be moved by the spring. 4262var movable = []; 4263 4264// mouse manipulate objects via spring 4265var spring; 4266 4267// keep track of the number of steps that occured during simulation 4268var stepNumber = 0; 4269 4270// put in the time dimesion variables 4271var timeStep = 1.0 / 30; 4272 4273// which mode of control to use: 4274var controlMode = { 4275 mode: "neural", 4276 setMode: function (theMode) { 4277 "use strict"; 4278 this.mode = theMode; 4279 }, 4280 getMode: function () { 4281 return this.mode; 4282 } 4283}; 4284 4285// timer object 4286var timer = null; 4287 4288// pretrain network goes here: 4289var initial_gene = null; 4290var geneData = null; 4291 4292// train at all? 4293var initTrainMode = false;
4294 4295var drawNetworkMode = true; 4296var printDetailMode = false; 4297 4298var numTrainBatch = 4; // how many generations should we train at at time before seeing the results in real time once. 4299 4300// define scores for controller functions 4301var initGeneJSON_random = '[{"0":-2.5980645610962028,"1":-1.9264891753902016,"2":0.07637552436792505,"3":0.6670183328050008,"4":1.272548645016405,"5":1.6183754716111167,"6":2.542840866344356,"7":3.131609365786479,"8":1.3847377992249301,"9":0.12702343241044295,"10":0.8289531277662427,"11":0.2029118421578732,"12":-0.5830151795937791,"13":-0.12260567543862913,"14":-0.8786616662640772,"15":0.10383988661702334,"16":-0.632761753397626,"17":-1.4426008075953374,"18":1.1091375288423577,"19":-0.7427014669762009,"20":0.15648628714750307,"21":-0.35100244461121183,"22":-0.852459818446918,"23":0.4157578076490787,"24":-1.1225277018214044,"25":1.1788974606583937,"26":-1.7648444492581297,"27":2.3916025233578133,"28":1.7338696636249264,"29":0.9215684838498516,"30":0.1374732826797588,"31":-0.2907554265453783,"32":-0.3771172599139394,"33":-0.12065569108686522,"34":-1.869302188132859,"35":1.638240576682271,"36":0.568057152884639,"37":0.17284346473953288,"38":-1.6236993686775565,"39":-1.4994773186046002,"40":-2.7868865187342204,"41":1.575399230188649,"42":0.0010487684544205622,"43":-2.009233794773864,"44":0.08197342955452065,"45":-1.6292205922916643,"46":1.0623567527107507,"47":0.1577245525406597,"48":-0.10325492710168246,"49":2.0934582449476298,"50":0.08353263763859753,"51":2.173676348498481,"52":0.9607505882081186,"53":-0.9377288674421345,"54":-1.501660708897993,"55":-1.524727681204356,"56":-0.25124433733472334,"57":0.10066986880918256,"58":-2.134790538345844,"59":0.8480114108823263,"60":-0.4687604512981024,"61":0.35378381538540316,"62":-0.26433614529804983,"63":1.2882343728106227,"64":-0.0042549034589568074,"65":0.13199777162646237,"66":-0.31918459640653396,"67":0.049394161799997796,"68":-0.13863791236614828,"69":0.40963362395503955,"70":-1.4814019532660947,"71":-1.768389520256723,"72":-1.2242436824069673,"73":0.35198684867580626,"74":0.539155609548777,"75":-1.5988305477624536,"76":1.389156071199981,"77":0.10812609349060548,"78":0.16776146157973493,"79":-2.170123919721531,"80":-0.15946596086893086,"81":-0.7769551195096259,"82":2.0780792191834903,"83":0.664414034195271,"84":-2.6328668626449194,"85":0.33801088448186867,"86":-1.1246558230288441,"87":0.8461585793027331,"88":-0.3076008136222011,"89":0.8026492926185533,"90":-0.7553006877811433,"91":-0.19898393497673722,"92":-2.256727156596284,"93":0.19742460918297827,"94":1.3032371217533767,"95":1.8842489350158047,"96":-0.4128087316752014,"97":-0.4040961012851025,"98":-1.6357366018261297,"99":0.42325959222887244,"100":0.3612276108897758,"101":0.10003982054735525,"102":0.5509588150755031,"103":-1.8776541279177055,"104":2.0004791161460638,"105":0.9
4301039100406447154,"106":-0.1787683096239166,"107":-0.9106764256697535,"108":-0.275514307910816,"109":1.2773983013172876,"110":0.018503239834874594,"111":0.006053170836477863,"112":0.9550501351332908,"113":0.27776248233975864,"114":-0.7611106674356081,"115":-1.6664791013584068,"116":-0.54962948449961,"117":1.222477772920564,"118":-2.353305199425632,"119":1.4283137929767176,"120":2.222349179140049,"121":0.05938042017814221,"122":-0.3444363670204805,"123":-0.7405626580209286,"124":-2.0511963778471607,"125":0.48807047758500327,"126":-1.9061212085492005,"127":-2.3809407528043236,"128":-1.0182779304518637,"129":3.5047836476582943,"130":-1.531657049124523,"131":0.08051953646775865,"132":1.2544752920897566,"133":0.08183301748676394,"134":-0.32771997751673865,"135":-2.0376675808989577,"136":-1.7505275193225567,"137":-0.9846016753214257,"138":0.682663979118413,"139":-0.523903906969104,"140":-1.8355520310429836,"141":0.2925721722912017,"142":-0.14888001236338255,"143":2.6910246828058657,"144":1.410697047559158},{"0":-0.06157752911113837,"1":0.9816390982148266,"2":-0.10918905854947444,"3":1.0876433487574215,"4":-0.3545354460698098,"5":0.7806313603113639,"6":-0.8757028449927096,"7":2.681712931710798,"8":0.
430107088197213461361,"9":-0.19543502868785814,"10":-0.7691718148476056,"11":-1.0390743256002968,"12":-0.8915587377390528,"13":-0.0047258225533586065,"14":-1.8044407754769554,"15":3.3295199119253267,"16":0.5510023740655496,"17":1.2428333282453492,"18":-0.41342807770531587,"19":0.5283327177729286,"20":1.447831820356721,"21":-0.9182796687646566,"22":-0.049164811347046145,"23":-0.3811200623277931,"24":-1.397958426043949,"25":0.2991314793169192,"26":1.5696734294473866,"27":0.2463370146590489,"28":-2.525222343579142,"29":-0.2830222147102804,"30":-1.4125218109640918,"31":-1.6839188026576106,"32":-0.8500761017247863,"33":1.699736561146109,"34":-0.874387356078126,"35":-0.42738832737307564,"36":1.3643081426524268,"37":-1.8714485202941478,"38":0.9640928508544087,"39":2.168715335672983,"40":-1.9436228617013391,"41":0.19285846043229032,"42":0.15407336896319623,"43":-2.263768347315484,"44":0.14019428446008217,"45":1.4604888845965205,"46":-0.2568896334410081,"47":0.48913357345470426,"48":-0.6212327735401325,"49":0.803673179889956,"50":-1.1494633728166528,"51":1.4879829815188477,"52":-0.6009487021342236,"53":0.9016342722774113,"54":0.5818570058943275,"55":0.9456305584364377,"56":2.1458649759860324,"57":-1.3862621074739219,"58":-0.027532504846727895,"59":1.7720923184971424,"60":1.8588657271286788,"61":-1.325776801803349,"62":-2.6633110404049694,"63":2.0997759530616356,"64":0.5307162805612904,"65":2.4930816140857592,"66":-2.6992247865220924,"67":-0.6151290151822345,"68":0.5678230213579526,"69":0.6809453114449404,"70":-0.6768835307727936,"71":0.3897255958034984,"72":0.9557127394562057,"73":0.6522894976567295,"74":-0.40815100777359437,"75":0.0661326076037508,"76":-0.5645614180531691,"77":2.0414719720678365,"78":3.7787739061288423,"79":0.2614192042998548,"80":-1.988381644991136,"81":1.107102375637584,"82":0.48604270474601513,"83":-0.014313581090933045,"84":0.5611695933719538,"85":-0.3333075597265268,"86":-1.23273697148324,"87":0.8321654803468709,"88":0.11476200142802381,"89":2.2604421730378084,"90":2.779299685297202,"91":0.9514189999367164,"92":0.17049035397196222,"93":-1.8229214423950433,"94":-0.4971782189327847,"95":-1.327358636081475,"96":0.28399546427844896,"97":0.04293109582458024,"98":-0.08571922154557592,"99":-0.4447146090625111,"100":-1.0241767617569302,"101":1.0635798511995047,"102":1.7612181015170636,"103":1.1040736056935907,"104":-1.7854924536840324,"105":-0.12429820690592522,"106":-0.8506719130110633,"107":-0.0007927947115421441,"108":0.47147547925242117,"109":-0.7131351090933178,"110":-1.915797847677861,"111":-2.022245650352164,"112":-0.9609992681441093,"113":1.5821533770703857,"114":2.877911245908887,"115":2.5829012795204633,"116":-0.7596120077117487,"117":2.128583222290037,"118":-0.6847442225608336,"119":-0.5521973077631342,"120":-1.9128536575481907,"121":1.0832895386459427,"122":0.35421286292343634,"123":-0.7520818876853157,"124":1.3280165348557889,"125":3.8989841337426703,"126":0.32346071724868286,"127":0.38634505512861583,"128":-1.2945308396569484,"129":0.8562925949183655,"130":1.1926989074465928,"131":-0.48440272528893497,"132":0.30483845568117274,"133":-0.36597679402518873,"134":-0.762273487708312,"135":0.038680956185777826,"136":0.4057500915151626,"137":-1.6417598773993691,"138":1.0884717847290397,"139":1.7583285208348245,"140":-0.7103201505130154,"141":-1.0258561519302345,"142":-1.451453296165857,"143":0.42367509510572904,"144":-1.1354318688444254}]'; 4302 4303var initGene = JSON.parse(initGeneJSON_random); 4304 4305var swingController = new NeuroController(initGene); 4306 4307// the below implements the scoring system developed by Grual et al (1996). 4308swingController.resetScore = function() { 4309 // initialize variable to hold historical information 4310 "use strict"; 4311 this.histScore = zeros(100); 4312 this.histIndex = 0; 4313 this.histLife = 0; // the time this thing has been alive for. 4314}; 4315swingController.pushScore = function (thetaLower, thetaLowerDot, thetaUpper, thetaUpperDot, speed, relPos) { 4316 var wThetaLower = 1; 4317 var wThetaLowerDot = 1; 4318 var wThetaUpper = 1; 4319 var wThetaUpperDot = 1; 4320 var wSpeed = 1; 4321 var wRelPos = 1.0; 4322 //return (thetaLower * thetaLower * wThetaLower + thetaLowerDot * thetaLowerDot * wThetaLowerDot + thetaUpper * thetaUpper * wThetaUpper + thetaUpperDot * thetaUpperDot * wThetaUpperDot + speed * speed * wSpeed + relPos * relPos*wRelPos) * -1; 4323 var score = Math.abs(thetaLower)*wThetaLower+Math.abs(thetaLowerDot)*wThetaLowerDot+Math.abs(thetaUpper)*wThetaUpper+Math.abs(thetaUpperDot)*wThetaUpperDot+Math.abs(speed)*wSpeed+Math.abs(relPos)*wRelPos; 4324 this.histScore[this.histIndex] = score; 4325 this.histIndex++; 4326 if (this.histIndex >= 100) this.histIndex = 0; 4327 this.histLife++; // increase time this guy has been alive for by one. 4328}; 4329swingController.getScore = function() { 4330 function f1(t) { 4331 return t / 1000; 4332 } 4333 function f2(t, x) { 4334 if (t < 100) { 4335 return 0; 4336 } 4337 var s = 0; 4338 for (var i = x.length-1; i >= 0; i--) { 4339 s += x[i]*0.01; 4340 } 4341 //console.log(s); 4342 return 10000*0.75 / s; 4343 } 4344 return 0.1 * f1(this.histLife) + 0.9 * f2(this.histLife, this.histScore); 4345}; 4346swingController.controllerName = "swing up"; 4347swingController.resetScore(); 4348 4349 4350 4351 4352// define main controllers: 4353var neuroController = swingController; // train neuroController; 4354var trainNeuroController = true; 4355 4356var gravityFactor = 2; 4357 4358function setGravity(x, y) { 4359 "use strict"; 4360 var gravityVec = new box2d.b2Vec2(x, y); 4361 world.SetGravity(gravityVec); 4362} 4363 4364function setupCamera() { 4365 "use strict"; 4366 b2Camera.x_b2 = ref_w / 2; 4367 b2Camera.y_b2 = ref_h / 2; 4368 4369 b2Camera.x_pixel = width / 2; 4370 b2Camera.y_pixel = height / 2; 4371 4372 b2Camera.scaleFactor = min((width - 10) / ref_w, (height - 10) / ref_h); 4373} 4374 4375// simulation loop, with no drawing 4376function update(nStep) { 4377 "use strict"; 4378 4379 var i; 4380 var step; 4381 // update important tracking positions: 4382 4383 var pendulumWorldPos; 4384 var pendulumRelPos; 4385 var currentAngle; 4386 var currentAngleVelocity; 4387 var currentLowerAngle; 4388 var currentLowerAngleVelocity; 4389 var targetSpeed = 0; 4390 var relPosition = 0; 4391 var score = 0; 4392 4393 for (step = 0; step < nStep; step++) { 4394 4395 // We must always step through time! 4396 // 2nd and 3rd arguments are velocity and position iterations 4397 world.Step(timeStep, 10, 10); 4398 4399 // update important tracking positions: 4400 4401 pendulumWorldPos = pendulum.getWorldPosition(); 4402 pendulumRelPos = pendulum.getRelativePosition(); 4403 currentAngle = pendulum.getAngleDegrees(); 4404 currentAngleVelocity = pendulum.getAngleVelocity(); 4405 currentLowerAngle = pendulum.getLowerAngleDegrees(); 4406 currentLowerAngleVelocity = pendulum.getLowerAngleVelocity(); 4407 targetSpeed = 0; 4408 relPosition = pendulumRelPos*100; // try to center the pendulum. 4409
4410 pendulum.update(); 4411 4412 // if pendulum is off, don't do any control 4413 if (!pendulum.controlMode()) { 4414 controlMode.setMode("off"); 4415 pendulum.setMotorSpeed(0); 4416 } else { 4417 controlMode.setMode("neural"); 4418 } 4419 4420 if (controlMode.getMode() === "neural") { 4421 // deal with control systems here: 4422 4423 neuroController.setInput(currentAngle, currentAngleVelocity, currentLowerAngle, currentLowerAngleVelocity, pendulumRelPos); 4424 var y = neuroController.getOutput(); 4425 targetSpeed = 0; 4426 4427 targetSpeed += y[0]; 4428 4429 // if (abs(targetSpeed) > 1024) targetSpeed = 0; 4430 pendulum.setMotorSpeed(targetSpeed * 100); 4431 4432 } 4433 4434 // deal with dead objects here: 4435 for (i = movable.length - 1; i >= 0; i--) { 4436 if (movable[i].done()) { // object is dead 4437 if (movable[i] === baseplate) { 4438 movable[i].killBody(); 4439 baseplate = new Box(movable[i].setting); 4440 movable[i] = baseplate; 4441 } else if (movable[i] instanceof Box) { // make boxes reborn 4442 movable[i].killBody(); 4443 movable[i] = new Box(movable[i].setting); 4444 } else if (movable[i] instanceof Circle) { // make circle reborn 4445 movable[i].killBody(); 4446 movable[i] = new Circle(movable[i].setting); 4447 } else if (movable[i] instanceof NGon) { // make circle reborn 4448 movable[i].killBody(); 4449 movable[i] = new NGon(movable[i].setting); 4450 } 4451 } 4452 } 4453 4454 // calculate score 4455 neuroController.pushScore(currentLowerAngle, currentLowerAngleVelocity, currentAngle, currentAngleVelocity, targetSpeed*100, relPosition*10); 4456 4457 score = neuroController.getScore(); 4458 4459 // deal with dead pendulum: 4460 if (pendulum.done() || (stepNumber > 60*300 && neuroController.getScore() < 50) ) { 4461 pendulum.killBody(); 4462 makeNewPendulum(0); // downright 4463 if (neuroController.isTraining === false) { // if guy died while not being training, train another generation. 4464 trainNeuroController = true; 4465 } 4466 return score; 4467 } 4468 4469 if (neuroController.getScore() > 30) { 4470 stepNumber = 0; 4471 } 4472 4473 } 4474 4475 return score; 4476 4477} 4478 4479// create pendulum 3 different ways, to train for different modes. 4480function makeNewPendulum(mode) { 4481 "use strict"; 4482 var startMode_ = "notrandom"; 4483 var startOrientation_ = "downright"; 4484 var randomInitPosX = 0.0; // if below is set to 1, then init position of pendulum will be random. 0 - center 4485 var randomInitPosY = 1.0; 4486 4487 var posX = ref_w * (4) / 8 + randomInitPosX*(random(-0.5, 0.5))* ref_w * (6) / 8; 4488 4489 if (mode === 1) { 4490 randomInitPosX = 0.95; 4491 randomInitPosY = 1.0; 4492 startOrientation_ = "upright"; 4493 startMode_ = "notrandom"; 4494 posX = ref_w * (4) / 8 + randomInitPosX*(getRandomInt(0, 1)-0.5)* ref_w * (6) / 8; 4495 } /*else if (mode === 2) { 4496 randomInitPos = 0.0; 4497 startMode_ = "random"; 4498 startOrientation_ = "upright"; 4499 } 4500*/ 4501 4502 neuroController.resetScore(); // has to reset score to zero since new pendulum. 4503 4504 pendulum = new Pendulum({ 4505 x: posX, 4506 y: -ref_h * (5.5) / 8 + randomInitPosY * getRandom(ref_h * (-2) / 8, ref_h * (2) / 8), 4507 startMode: startMode_, 4508 startOrientation: startOrientation_ 4509 }); 4510 4511} 4512 4513// fitness function for GA algorithm. 4514var pendulumFitness = function (network) { 4515 "use strict"; 4516 //neuroController.setNetwork(network); 4517 var result = 1; 4518 4519 pendulum.killBody(); 4520 makeNewPendulum(0); // downright 4521 result *= update(20 * 30); 4522 4523 4524 pendulum.killBody(); 4525 makeNewPendulum(1); // upright 4526 result *= update(20 * 20); 4527 4528 pendulum.killBody(); 4529 makeNewPendulum(0); // downright 4530 4531 return result; 4532}; 4533 4534function setup() { 4535 "use strict"; 4536 4537 //var myCanvas = createCanvas(getWidth() - 0, getHeight() * 1.0); 4538 //myCanvas.parent('p5Container'); 4539 var myCanvas; 4540 if (mobileMode) { 4541 myCanvas = createCanvas(275,385); 4542 } else { // desktop 4543 myCanvas = createCanvas(max($(window).width()/1, 275), 385); 4544 } 4545 myCanvas.parent('p5Container'); 4546 frameRate(60); 4547 4548 // Initialize box2d physics and create the world 4549 world = createWorld(); 4550 setGravity(0, 9.81 * 2); 4551 4552 world.SetContactListener(new CustomListener()); // check to see if things collided. 4553 4554 // Make the spring (it doesn't really get initialized until the mouse is clicked) 4555 spring = new Spring(); 4556 4557 // make a ball for pendulum to follow around 4558 follow = new Circle({ 4559 x1 : ref_w*2/8, 4560 x2 : ref_w*6/8, 4561 y1 : ref_h*5/8, 4562 y2 : ref_h*7/8, 4563 r : 5.0 * ref_u, 4564 numEdges : 3, 4565 initialMove : true 4566 }); 4567 movable.push( follow ); 4568 4569 makeNewPendulum(0); // downright 4570 pendulum.setMotorSpeed(random(0, 0)); 4571 4572 // push base plate 4573 baseplate = new Box({ 4574 x1: ref_w / 2 - 1.0 * ref_u * 0, 4575 x2: ref_w / 2 + 1.0 * ref_u * 0, 4576 y1: ref_h * 4.0 / 8, 4577 y2: ref_h * 4.5 / 8, 4578 w: ref_w - 2.75 * ref_u, 4579 h: 3 * ref_u, 4580 density: 5, 4581 initialMove: false 4582 }); 4583 movable.push(baseplate); 4584
4585 // Add a bunch of fixed boundaries 4586 boundaries.push(new Boundary(ref_w / 2, ref_h - 0.6 * ref_u, ref_w - 0.2, 1.0 * ref_u)); 4587 4588 boundaries.push(new Boundary(2.1 * ref_u, ref_h * 5 / 8 + 0.5 * ref_u, 2.0 * ref_u, 1.0 * ref_u)); 4589 boundaries.push(new Boundary(ref_w - 2.1 * ref_u, ref_h * 5 / 8 + 0.5 * ref_u, 2.0 * ref_u, 1.0 * ref_u)); 4590 4591 boundaries.push(new Boundary(0.6 * ref_u, ref_h * 6.5 / 8 - 1.0 * ref_u, 1.0 * ref_u, ref_h * 3 / 8)); 4592 boundaries.push(new Boundary(ref_w - 0.6 * ref_u, ref_h * 6.5 / 8 - 1.0 * ref_u, 1.0 * ref_u, ref_h * 3 / 8)); 4593 4594 setupCamera(); 4595 4596 timer = new Timer(); 4597 timer.start(); 4598 4599/* 4600 if (drawNetworkMode) $("#nn_weights").text(JSON.stringify(neuroController.getChromosome())); 4601*/ 4602} 4603 4604function draw() { 4605 "use strict"; 4606 4607 if (mainNavBarStatus === false) { // not at the main section yet 4608 return; 4609 } 4610 4611 if (trainNeuroController) { 4612 stepNumber = 0; 4613 var fitnessTimer = new Timer(); 4614 fitnessTimer.start(); 4615 trainNeuroController = false; 4616 if (initTrainMode) { 4617 background(255); 4618 neuroController.train(pendulumFitness, numTrainBatch); 4619 } 4620 var c = neuroController.getBestGenes(); 4621 var bestFitness = neuroController.getBestFitness(); 4622 //if (drawNetworkMode) $("#nn_weights").text(JSON.stringify(c)); 4623 //console.log('best fit: ' + round(bestFitness*10)/10 + '\t time: ' + fitnessTimer.reset()); 4624 } 4625 4626 var score = update(1); 4627 4628 // other info 4629 var pendulumRelPos = pendulum.getRelativePosition(); 4630 var currentAngle = pendulum.getAngleDegrees(); 4631 var currentAngleVelocity = pendulum.getAngleVelocity(); 4632 var currentLowerAngle = pendulum.getLowerAngleDegrees(); 4633 var currentLowerAngleVelocity = pendulum.getLowerAngleVelocity(); 4634 var targetSpeed = pendulum.getMotorSpeed(); 4635 4636 background(255); 4637 4638 // draw gravity 4639 if (true) { 4640 stroke(130, 170, 255); 4641 line(width*7/8, width*1/16, width*7/8+(Orientation.getX()/9.81)*(width*0.125), (0.5+Math.abs(Orientation.getY()/9.81))*(width*0.125)); 4642 if (Orientation.enabled) { 4643 setGravity(Orientation.getX()/32,9.8*2); 4644 } else { 4645 setGravity(0, 9.8*2); 4646 } 4647 } 4648 4649 // show neural network: 4650 if (drawNetworkMode) { 4651 neuroController.drawNetwork2(); 4652 //neuroController.drawNetwork(); 4653 } 4654 4655 // draw gravity 4656 /* 4657 if (true) { 4658 stroke(130, 170, 255); 4659 line(width * 7 / 8, width * 1 / 16, width * 7 / 8 + (Orientation.getX() / 9.81) * (width * 0.125), (0.5 + Math.abs(Orientation.getY() / 9.81)) * (width * 0.125)); 4660 // if (Orientation.enabled) { 4661 // setGravity(Orientation.getX()*gravityFactor,Math.abs(Orientation.getY()*gravityFactor)); 4662 // } else { 4663 // setGravity(0, 9.8*gravityFactor*2); 4664 // } 4665 } 4666 */ 4667 4668 var i; 4669 4670 4671 // Display all the boundaries 4672 for (i = boundaries.length - 1; i >= 0; i--) { 4673 boundaries[i].display(); 4674 } 4675 4676 for (i = movable.length - 1; i >= 0; i--) { 4677 movable[i].display(); 4678 } 4679 4680 // print fps 4681 fill(250, 0, 50, 128); 4682 stroke(250, 0, 50, 128); 4683 textFont("Courier New"); 4684 textSize(16); 4685 //text('fps: ' + floor(1000 / timer.reset() * 1.0) / 1.0, 10, 20); 4686 4687 if (printDetailMode) { 4688 text('generation #'+neuroController.generation, 10, 40); 4689 4690 text('pos*100: ' + round(1000.0 * pendulumRelPos)/10.0, 10, 60); 4691 text('upperTheta: ' + round(100.0 * currentAngle) / 100.0, 10, 80); 4692 text('upperThetaDot: ' + round(1.0 * currentAngleVelocity) / 1.0, 10, 100); 4693 text('lowerTheta: ' + round(100.0 * currentLowerAngle) / 100.0, 10, 120); 4694 text('lowerThetaDot: ' + round(1.0 * currentLowerAngleVelocity) / 1.0, 10, 140); 4695 text('motorSpeed: ' + round(1.0 * targetSpeed) / 1.0, 10, 160); 4696 text('score: ' + round(100.0 * score) / 100.0, 10, 180); 4697 text('step: ' + stepNumber++, 10, 200); 4698 text('name: ' + neuroController.controllerName, 10, 220); 4699 } 4700 4701 4702 4703 4704 // show pendulum last since it is main character. 4705 pendulum.setScore(score); 4706 pendulum.display(); 4707 4708 4709} 4710 4711// When the mouse is released we're done with the spring 4712var deviceReleased = function () { 4713 "use strict"; 4714 spring.destroy(); 4715}; 4716 4717// When the mouse is pressed we. . . 4718var devicePressed = function (x, y) { 4719 "use strict"; 4720 // Check to see if the mouse was clicked on the movable object 4721 var i; 4722 var movableObj; 4723 for (i = movable.length - 1; i >= 0; i--) { 4724 movableObj = movable[i]; 4725 if (movableObj.contains(x, y)) { 4726 spring.bind(x, y, movableObj); 4727 } 4728 } 4729 4730 if (pendulum.wheel.contains(x, y)) { 4731 // And if so, bind the mouse location to the box with a spring 4732 spring.bind(x, y, pendulum.wheel); 4733 // toggle control when wheel is touched or pressed by mouse 4734 pendulum.toggleControl(); 4735 } else if (pendulum.stick1.contains(x, y)) { 4736 // And if so, bind the mouse location to the box with a spring 4737 spring.bind(x, y, pendulum.stick1); 4738 } else if (pendulum.handle.contains(x, y)) { 4739 // And if so, bind the mouse location to the box with a spring 4740 spring.bind(x, y, pendulum.handle); 4741 } 4742 4743}; 4744 4745 4746// ContactListener to listen for collisions! 4747 4748var CustomListener = function () { 4749 "use strict"; 4750}; 4751 4752// Collision event functions! 4753CustomListener.prototype.BeginContact = function (contact) { 4754 "use strict"; 4755 // Get both fixtures 4756 var f1 = contact.GetFixtureA(); 4757 var f2 = contact.GetFixtureB(); 4758 // Get both bodies 4759 var b1 = f1.GetBody(); 4760 var b2 = f2.GetBody(); 4761 4762 // Get our objects that reference these bodies 4763 var o1 = b1.GetUserData(); 4764 var o2 = b2.GetUserData(); 4765
4766 if ((o1 === baseplate && o2 === pendulum.wheel) || (o2 === baseplate && o1 === pendulum.wheel)) { 4767 pendulum.enableControl(); 4768 } 4769 4770}; 4771 4772// Objects stop touching each other 4773CustomListener.prototype.EndContact = function (contact) { 4774 "use strict"; 4775 // Get both fixtures 4776 var f1 = contact.GetFixtureA(); 4777 var f2 = contact.GetFixtureB(); 4778 // Get both bodies 4779 var b1 = f1.GetBody(); 4780 var b2 = f2.GetBody(); 4781 4782 // Get our objects that reference these bodies 4783 var o1 = b1.GetUserData(); 4784 var o2 = b2.GetUserData(); 4785 4786 if ((o1 === baseplate && o2 === pendulum.wheel) || (o2 === baseplate && o1 === pendulum.wheel)) { 4787 pendulum.disableControl(); 4788 } 4789}; 4790 4791CustomListener.prototype.PreSolve = function (contact, manifold) {}; 4792 4793CustomListener.prototype.PostSolve = function (contact, manifold) {}; 4794 4795 4796// interaction with touchpad and mosue: 4797 4798var deviceDragged = function (x, y) { 4799 "use strict"; 4800 // Always alert the spring to the new mouse location 4801 spring.update(x, y); 4802}; 4803 4804/* 4805if (mobileMode === false) { 4806 console.log('defining functions for pendulum interaction'); 4807 4808var mouseDragged = function () { 4809 "use strict"; 4810 deviceDragged(mouseX, mouseY); 4811 return false; 4812}; 4813 4814var touchMoved = function () { 4815 "use strict"; 4816 deviceDragged(touchX, touchY); 4817 return false; 4818}; 4819 4820var mouseReleased = function () { 4821 "use strict"; 4822 deviceReleased(); 4823 return false; 4824}; 4825 4826var touchEnded = function () { 4827 "use strict"; 4828 deviceReleased(); 4829 return false; 4830}; 4831 4832var mousePressed = function () { 4833 "use strict"; 4834 devicePressed(mouseX, mouseY); 4835 return false; 4836}; 4837 4838var touchStarted = function () { 4839 "use strict"; 4840 devicePressed(touchX, touchY); 4841 return false; 4842}; 4843} 4844*/ 4845
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.