├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── caffe.js ├── caffe.min.js └── caffe.min.js.map ├── docs ├── assets │ ├── data │ │ ├── deepdream │ │ │ ├── city.jpg │ │ │ ├── sky.jpg │ │ │ ├── sky_big.jpg │ │ │ └── sky_sq.jpg │ │ ├── faces │ │ │ └── woman.jpg │ │ └── ilsvrc12 │ │ │ ├── det_synset_words.txt │ │ │ ├── imagenet_mean.binaryproto │ │ │ ├── imagenet_mean.txt │ │ │ ├── synset_words.txt │ │ │ └── synsets.txt │ ├── libs │ │ ├── bytebuffer.min.js │ │ ├── d3.min.js │ │ ├── dagre-d3.core.min.js │ │ ├── dagre.core.min.js │ │ ├── graphlib.core.min.js │ │ ├── lodash.min.js │ │ ├── long.min.js │ │ ├── material.min.css │ │ ├── material.min.js │ │ ├── protobuf.min.js │ │ ├── prototxt-parser.min.js │ │ ├── svg-pan-zoom.min.js │ │ └── webcam.min.js │ ├── models │ │ ├── ResNet │ │ │ ├── ResNet-101-deploy.prototxt │ │ │ ├── ResNet-152-deploy.prototxt │ │ │ └── ResNet-50-deploy.prototxt │ │ ├── VGG_CNN_S │ │ │ ├── VGG_mean.binaryproto │ │ │ ├── VGG_mean.txt │ │ │ └── deploy.prototxt │ │ ├── VGG_FACE │ │ │ ├── COPYING │ │ │ ├── README │ │ │ ├── VGG_FACE_deploy.prototxt │ │ │ ├── ak.png │ │ │ ├── fetch_weights.sh │ │ │ └── names.txt │ │ ├── age_net │ │ │ ├── deploy_age.prototxt │ │ │ ├── deploy_gender.prototxt │ │ │ └── mean.binaryproto │ │ ├── bvlc_alexnet │ │ │ ├── alexnet.png │ │ │ ├── compact.prototxt │ │ │ ├── deploy.prototxt │ │ │ └── fetch_weights.sh │ │ ├── bvlc_googlenet │ │ │ ├── caffejs_deepdream.prototxt │ │ │ ├── deploy.prototxt │ │ │ └── googlenet.png │ │ ├── nin_imagenet │ │ │ └── deploy.prototxt │ │ └── squeezenet │ │ │ ├── compact.prototxt │ │ │ └── deploy.prototxt │ ├── scripts │ │ └── deepdream_worker.js │ └── styles │ │ ├── docs.css │ │ ├── filters.css │ │ └── graph.css ├── development-guide.md ├── index.md ├── templates │ ├── base.html │ ├── creator.html │ ├── deepdream.html │ ├── face.html │ ├── imagenet.html │ ├── models.html │ └── webcam.html └── user-guide.md ├── gulpconfig.json ├── gulpfile.js ├── karma.conf.js ├── package.json ├── scripts ├── convert_caffemodel.py └── convert_imagenet_urls.sh ├── src ├── ImgJS │ ├── Image.ts │ └── _module.ts ├── Net │ ├── CaffeModel.ts │ ├── ILayer.ts │ ├── Layers │ │ ├── BaseLayer.ts │ │ ├── ConcatLayer.ts │ │ ├── ConvLayer.ts │ │ ├── DropoutLayer.ts │ │ ├── FullyConnectedLayer.ts │ │ ├── InputLayer.ts │ │ ├── LocalResponseNormalizationLayer.ts │ │ ├── MaxoutLayer.ts │ │ ├── PoolLayer.ts │ │ ├── RegressionLayer.ts │ │ ├── ReluLayer.ts │ │ ├── SVMLayer.ts │ │ ├── SigmoidLayer.ts │ │ ├── SoftmaxLayer.ts │ │ ├── TanhLayer.ts │ │ └── _module.ts │ ├── Model.ts │ ├── Utils.ts │ ├── Vol.ts │ └── _module.ts ├── NumJS │ ├── Array.ts │ ├── MinMax.ts │ ├── Random.ts │ ├── Utils.ts │ └── _module.ts ├── Parser │ ├── BinaryParser.ts │ ├── BinaryprotoParser.ts │ ├── BlobProtoParser.ts │ ├── PrototxtParser.ts │ ├── TextParser.ts │ └── _module.ts ├── Utils │ ├── ActivationDrawer.ts │ ├── GraphDrawer.ts │ └── _module.ts └── _module.ts ├── test └── Net │ ├── CaffeModel.spec.js │ └── Layers │ └── ConvLayer.spec.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .publish/ 2 | node_modules/ 3 | typings/ 4 | weights/ 5 | data/ 6 | docs-pages/ 7 | *.caffemodel -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "7" 4 | before_script: 5 | - npm install 6 | script: 7 | - npm run build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Christoph Körner 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/chaosmail/caffejs.svg?branch=master)](https://travis-ci.org/chaosmail/caffejs) 2 | 3 | # CaffeJS 4 | 5 | This repo is a proof of concept for porting Caffe models to the browser using a modified version of [ConvNetJS (by Andrej Karpathy)](https://github.com/karpathy/convnetjs). It aims to help beginners to dive into Deep Neural Networks by using only a browser. Try out the ImageNet Classification using GoogleNet or the DeepDream entirely in your browser! 6 | 7 | This work is pre-alpha and based on ConvNetJS (which is alpha), so you can imagine how much I need your help! 8 | 9 | ## What's possible with CaffeJS 10 | 11 | * Playing around with Convolutional Neural Nets in the browser 12 | * Loading pretrained Deep Neural Nets entirely in JavaScript 13 | * Running forward and backward passes through Deep Neural Nets 14 | * Visualize model structure, activations and filters 15 | * All of this without installing any software (also running on your mobile devices) 16 | 17 | ## How to run CaffeJS 18 | 19 | Check out the project page hosted on [Github](https://chaosmail.github.io/caffejs/) which includes samples with a pretrained GoogLeNet. To run other Nets (like AlexNet, VGG or ResNet) one has to clone the repo on the local machine and download the additional model weights. 20 | 21 | * [Getting Started](https://chaosmail.github.io/caffejs/) 22 | * [User Guide](https://chaosmail.github.io/caffejs/user-guide.html) 23 | * [Development Guide](https://chaosmail.github.io/caffejs/development-guide.html) 24 | * Many cool examples and demos, like [ImageNet Classification](https://chaosmail.github.io/caffejs/webcam.html), [DeepDream](https://chaosmail.github.io/caffejs/deepdream.html), etc. 25 | 26 | ## What's left to do 27 | 28 | * Debug and fix remaining issues with SoftMax layer 29 | * Implement AVE pooling backward pass 30 | * Implement more layers (Eltwise, Scale, BatchNorm) for ResNet 31 | * Evaluate weight extraction directly from *.caffemodel file (without converting to intermediate binary format) 32 | * Nice documentation 33 | * More samples (Selfie Net, Gender- and AgeNet, Facial Expression Recognition, Segmentation, etc.) 34 | * Write unit tests 35 | * Implement FilterDrawer to visualize filters 36 | * Auto-scale the filters and activations in the visualizations to a meaningful output dimension (seriously, 1x1 px filters are super small) 37 | 38 | ## License 39 | 40 | The software is provided under MIT license. -------------------------------------------------------------------------------- /docs/assets/data/deepdream/city.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/data/deepdream/city.jpg -------------------------------------------------------------------------------- /docs/assets/data/deepdream/sky.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/data/deepdream/sky.jpg -------------------------------------------------------------------------------- /docs/assets/data/deepdream/sky_big.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/data/deepdream/sky_big.jpg -------------------------------------------------------------------------------- /docs/assets/data/deepdream/sky_sq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/data/deepdream/sky_sq.jpg -------------------------------------------------------------------------------- /docs/assets/data/faces/woman.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/data/faces/woman.jpg -------------------------------------------------------------------------------- /docs/assets/data/ilsvrc12/det_synset_words.txt: -------------------------------------------------------------------------------- 1 | n02672831 accordion 2 | n02691156 airplane 3 | n02219486 ant 4 | n02419796 antelope 5 | n07739125 apple 6 | n02454379 armadillo 7 | n07718747 artichoke 8 | n02764044 axe 9 | n02766320 baby bed 10 | n02769748 backpack 11 | n07693725 bagel 12 | n02777292 balance beam 13 | n07753592 banana 14 | n02786058 band aid 15 | n02787622 banjo 16 | n02799071 baseball 17 | n02802426 basketball 18 | n02807133 bathing cap 19 | n02815834 beaker 20 | n02131653 bear 21 | n02206856 bee 22 | n07720875 bell pepper 23 | n02828884 bench 24 | n02834778 bicycle 25 | n02840245 binder 26 | n01503061 bird 27 | n02870880 bookshelf 28 | n02883205 bow tie 29 | n02879718 bow 30 | n02880940 bowl 31 | n02892767 brassiere 32 | n07880968 burrito 33 | n02924116 bus 34 | n02274259 butterfly 35 | n02437136 camel 36 | n02951585 can opener 37 | n02958343 car 38 | n02970849 cart 39 | n02402425 cattle 40 | n02992211 cello 41 | n01784675 centipede 42 | n03000684 chain saw 43 | n03001627 chair 44 | n03017168 chime 45 | n03062245 cocktail shaker 46 | n03063338 coffee maker 47 | n03085013 computer keyboard 48 | n03793489 computer mouse 49 | n03109150 corkscrew 50 | n03128519 cream 51 | n03134739 croquet ball 52 | n03141823 crutch 53 | n07718472 cucumber 54 | n03797390 cup or mug 55 | n03188531 diaper 56 | n03196217 digital clock 57 | n03207941 dishwasher 58 | n02084071 dog 59 | n02121808 domestic cat 60 | n02268443 dragonfly 61 | n03249569 drum 62 | n03255030 dumbbell 63 | n03271574 electric fan 64 | n02503517 elephant 65 | n03314780 face powder 66 | n07753113 fig 67 | n03337140 filing cabinet 68 | n03991062 flower pot 69 | n03372029 flute 70 | n02118333 fox 71 | n03394916 french horn 72 | n01639765 frog 73 | n03400231 frying pan 74 | n02510455 giant panda 75 | n01443537 goldfish 76 | n03445777 golf ball 77 | n03445924 golfcart 78 | n07583066 guacamole 79 | n03467517 guitar 80 | n03483316 hair dryer 81 | n03476991 hair spray 82 | n07697100 hamburger 83 | n03481172 hammer 84 | n02342885 hamster 85 | n03494278 harmonica 86 | n03495258 harp 87 | n03124170 hat with a wide brim 88 | n07714571 head cabbage 89 | n03513137 helmet 90 | n02398521 hippopotamus 91 | n03535780 horizontal bar 92 | n02374451 horse 93 | n07697537 hotdog 94 | n03584254 iPod 95 | n01990800 isopod 96 | n01910747 jellyfish 97 | n01882714 koala bear 98 | n03633091 ladle 99 | n02165456 ladybug 100 | n03636649 lamp 101 | n03642806 laptop 102 | n07749582 lemon 103 | n02129165 lion 104 | n03676483 lipstick 105 | n01674464 lizard 106 | n01982650 lobster 107 | n03710721 maillot 108 | n03720891 maraca 109 | n03759954 microphone 110 | n03761084 microwave 111 | n03764736 milk can 112 | n03770439 miniskirt 113 | n02484322 monkey 114 | n03790512 motorcycle 115 | n07734744 mushroom 116 | n03804744 nail 117 | n03814639 neck brace 118 | n03838899 oboe 119 | n07747607 orange 120 | n02444819 otter 121 | n03908618 pencil box 122 | n03908714 pencil sharpener 123 | n03916031 perfume 124 | n00007846 person 125 | n03928116 piano 126 | n07753275 pineapple 127 | n03942813 ping-pong ball 128 | n03950228 pitcher 129 | n07873807 pizza 130 | n03958227 plastic bag 131 | n03961711 plate rack 132 | n07768694 pomegranate 133 | n07615774 popsicle 134 | n02346627 porcupine 135 | n03995372 power drill 136 | n07695742 pretzel 137 | n04004767 printer 138 | n04019541 puck 139 | n04023962 punching bag 140 | n04026417 purse 141 | n02324045 rabbit 142 | n04039381 racket 143 | n01495701 ray 144 | n02509815 red panda 145 | n04070727 refrigerator 146 | n04074963 remote control 147 | n04116512 rubber eraser 148 | n04118538 rugby ball 149 | n04118776 ruler 150 | n04131690 salt or pepper shaker 151 | n04141076 saxophone 152 | n01770393 scorpion 153 | n04154565 screwdriver 154 | n02076196 seal 155 | n02411705 sheep 156 | n04228054 ski 157 | n02445715 skunk 158 | n01944390 snail 159 | n01726692 snake 160 | n04252077 snowmobile 161 | n04252225 snowplow 162 | n04254120 soap dispenser 163 | n04254680 soccer ball 164 | n04256520 sofa 165 | n04270147 spatula 166 | n02355227 squirrel 167 | n02317335 starfish 168 | n04317175 stethoscope 169 | n04330267 stove 170 | n04332243 strainer 171 | n07745940 strawberry 172 | n04336792 stretcher 173 | n04356056 sunglasses 174 | n04371430 swimming trunks 175 | n02395003 swine 176 | n04376876 syringe 177 | n04379243 table 178 | n04392985 tape player 179 | n04409515 tennis ball 180 | n01776313 tick 181 | n04591157 tie 182 | n02129604 tiger 183 | n04442312 toaster 184 | n06874185 traffic light 185 | n04468005 train 186 | n04487394 trombone 187 | n03110669 trumpet 188 | n01662784 turtle 189 | n03211117 tv or monitor 190 | n04509417 unicycle 191 | n04517823 vacuum 192 | n04536866 violin 193 | n04540053 volleyball 194 | n04542943 waffle iron 195 | n04554684 washer 196 | n04557648 water bottle 197 | n04530566 watercraft 198 | n02062744 whale 199 | n04591713 wine bottle 200 | n02391049 zebra 201 | -------------------------------------------------------------------------------- /docs/assets/data/ilsvrc12/imagenet_mean.binaryproto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/data/ilsvrc12/imagenet_mean.binaryproto -------------------------------------------------------------------------------- /docs/assets/libs/long.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | long.js (c) 2013 Daniel Wirtz 3 | Released under the Apache License, Version 2.0 4 | see: https://github.com/dcodeIO/long.js for details 5 | */ 6 | (function(d,g){"function"===typeof define&&define.amd?define([],g):"function"===typeof require&&"object"===typeof module&&module&&module.exports?module.exports=g():(d.dcodeIO=d.dcodeIO||{}).Long=g()})(this,function(){function d(a,b,c){this.low=a|0;this.high=b|0;this.unsigned=!!c}function g(a){return!0===(a&&a.__isLong__)}function m(a,b){var c,t;if(b){a>>>=0;if(t=0<=a&&256>a)if(c=z[a])return c;c=e(a,0>(a|0)?-1:0,!0);t&&(z[a]=c)}else{a|=0;if(t=-128<=a&&128>a)if(c=A[a])return c;c=e(a,0>a?-1:0,!1);t&& 7 | (A[a]=c)}return c}function n(a,b){if(isNaN(a)||!isFinite(a))return b?p:k;if(b){if(0>a)return p;if(a>=B)return C}else{if(a<=-D)return l;if(a+1>=D)return E}return 0>a?n(-a,b).neg():e(a%4294967296|0,a/4294967296|0,b)}function e(a,b,c){return new d(a,b,c)}function x(a,b,c){if(0===a.length)throw Error("empty string");if("NaN"===a||"Infinity"===a||"+Infinity"===a||"-Infinity"===a)return k;"number"===typeof b?(c=b,b=!1):b=!!b;c=c||10;if(2>c||36d?(d=n(v(c,d)),e=e.mul(d).add(n(g))):(e=e.mul(t),e=e.add(n(g)))}e.unsigned=b;return e}function q(a){return a instanceof d?a:"number"===typeof a?n(a):"string"===typeof a?x(a):e(a.low,a.high,a.unsigned)}Object.defineProperty(d.prototype,"__isLong__",{value:!0,enumerable:!1,configurable:!1});d.isLong=g;var A={},z={};d.fromInt=m;d.fromNumber=n;d.fromBits= 9 | e;var v=Math.pow;d.fromString=x;d.fromValue=q;var B=4294967296*4294967296,D=B/2,F=m(16777216),k=m(0);d.ZERO=k;var p=m(0,!0);d.UZERO=p;var r=m(1);d.ONE=r;var G=m(1,!0);d.UONE=G;var y=m(-1);d.NEG_ONE=y;var E=e(-1,2147483647,!1);d.MAX_VALUE=E;var C=e(-1,-1,!0);d.MAX_UNSIGNED_VALUE=C;var l=e(0,-2147483648,!1);d.MIN_VALUE=l;var b=d.prototype;b.toInt=function(){return this.unsigned?this.low>>>0:this.low};b.toNumber=function(){return this.unsigned?4294967296*(this.high>>>0)+(this.low>>>0):4294967296*this.high+ 10 | (this.low>>>0)};b.toString=function(a){a=a||10;if(2>a||36>>0).toString(a),b=d;if(b.isZero())return f+e;for(;6>f.length;)f="0"+f;e=""+f+e}};b.getHighBits=function(){return this.high};b.getHighBitsUnsigned= 11 | function(){return this.high>>>0};b.getLowBits=function(){return this.low};b.getLowBitsUnsigned=function(){return this.low>>>0};b.getNumBitsAbs=function(){if(this.isNegative())return this.eq(l)?64:this.neg().getNumBitsAbs();for(var a=0!=this.high?this.high:this.low,b=31;0this.high};b.isPositive=function(){return this.unsigned||0<=this.high};b.isOdd= 12 | function(){return 1===(this.low&1)};b.isEven=function(){return 0===(this.low&1)};b.equals=function(a){g(a)||(a=q(a));return this.unsigned!==a.unsigned&&1===this.high>>>31&&1===a.high>>>31?!1:this.high===a.high&&this.low===a.low};b.eq=b.equals;b.notEquals=function(a){return!this.eq(a)};b.neq=b.notEquals;b.lessThan=function(a){return 0>this.comp(a)};b.lt=b.lessThan;b.lessThanOrEqual=function(a){return 0>=this.comp(a)};b.lte=b.lessThanOrEqual;b.greaterThan=function(a){return 0>>0>this.high>>>0||a.high===this.high&&a.low>>>0>this.low>>>0?-1:1:this.sub(a).isNegative()?-1:1};b.comp=b.compare;b.negate=function(){return!this.unsigned&&this.eq(l)?l:this.not().add(r)};b.neg=b.negate;b.add=function(a){g(a)||(a=q(a));var b=this.high>>>16,c=this.high&65535, 14 | d=this.low>>>16,l=a.high>>>16,f=a.high&65535,n=a.low>>>16,k;k=0+((this.low&65535)+(a.low&65535));a=0+(k>>>16);a+=d+n;d=0+(a>>>16);d+=c+f;c=0+(d>>>16);c=c+(b+l)&65535;return e((a&65535)<<16|k&65535,c<<16|d&65535,this.unsigned)};b.subtract=function(a){g(a)||(a=q(a));return this.add(a.neg())};b.sub=b.subtract;b.multiply=function(a){if(this.isZero())return k;g(a)||(a=q(a));if(a.isZero())return k;if(this.eq(l))return a.isOdd()?l:k;if(a.eq(l))return this.isOdd()?l:k;if(this.isNegative())return a.isNegative()? 15 | this.neg().mul(a.neg()):this.neg().mul(a).neg();if(a.isNegative())return this.mul(a.neg()).neg();if(this.lt(F)&&a.lt(F))return n(this.toNumber()*a.toNumber(),this.unsigned);var b=this.high>>>16,c=this.high&65535,d=this.low>>>16,w=this.low&65535,f=a.high>>>16,m=a.high&65535,p=a.low>>>16;a=a.low&65535;var u,h,s,r;r=0+w*a;s=0+(r>>>16);s+=d*a;h=0+(s>>>16);s=(s&65535)+w*p;h+=s>>>16;s&=65535;h+=c*a;u=0+(h>>>16);h=(h&65535)+d*p;u+=h>>>16;h&=65535;h+=w*m;u+=h>>>16;h&=65535;u=u+(b*a+c*p+d*m+w*f)&65535;return e(s<< 16 | 16|r&65535,u<<16|h,this.unsigned)};b.mul=b.multiply;b.divide=function(a){g(a)||(a=q(a));if(a.isZero())throw Error("division by zero");if(this.isZero())return this.unsigned?p:k;var b,c,d;if(this.unsigned){a.unsigned||(a=a.toUnsigned());if(a.gt(this))return p;if(a.gt(this.shru(1)))return G;d=p}else{if(this.eq(l)){if(a.eq(r)||a.eq(y))return l;if(a.eq(l))return r;b=this.shr(1).div(a).shl(1);if(b.eq(k))return a.isNegative()?r:y;c=this.sub(a.mul(b));return d=b.add(c.div(a))}if(a.eq(l))return this.unsigned? 17 | p:k;if(this.isNegative())return a.isNegative()?this.neg().div(a.neg()):this.neg().div(a).neg();if(a.isNegative())return this.div(a.neg()).neg();d=k}for(c=this;c.gte(a);){b=Math.max(1,Math.floor(c.toNumber()/a.toNumber()));for(var e=Math.ceil(Math.log(b)/Math.LN2),e=48>=e?1:v(2,e-48),f=n(b),m=f.mul(a);m.isNegative()||m.gt(c);)b-=e,f=n(b,this.unsigned),m=f.mul(a);f.isZero()&&(f=r);d=d.add(f);c=c.sub(m)}return d};b.div=b.divide;b.modulo=function(a){g(a)||(a=q(a));return this.sub(this.div(a).mul(a))}; 18 | b.mod=b.modulo;b.not=function(){return e(~this.low,~this.high,this.unsigned)};b.and=function(a){g(a)||(a=q(a));return e(this.low&a.low,this.high&a.high,this.unsigned)};b.or=function(a){g(a)||(a=q(a));return e(this.low|a.low,this.high|a.high,this.unsigned)};b.xor=function(a){g(a)||(a=q(a));return e(this.low^a.low,this.high^a.high,this.unsigned)};b.shiftLeft=function(a){g(a)&&(a=a.toInt());return 0===(a&=63)?this:32>a?e(this.low<>>32-a,this.unsigned):e(0,this.low<a?e(this.low>>>a|this.high<<32-a,this.high>>a,this.unsigned):e(this.high>>a-32,0<=this.high?0:-1,this.unsigned)};b.shr=b.shiftRight;b.shiftRightUnsigned=function(a){g(a)&&(a=a.toInt());a&=63;if(0===a)return this;var b=this.high;return 32>a?e(this.low>>>a|b<<32-a,b>>>a,this.unsigned):32===a?e(b,0,this.unsigned):e(b>>>a-32,0,this.unsigned)};b.shru=b.shiftRightUnsigned;b.toSigned=function(){return this.unsigned? 20 | e(this.low,this.high,!1):this};b.toUnsigned=function(){return this.unsigned?this:e(this.low,this.high,!0)};b.toBytes=function(a){return a?this.toBytesLE():this.toBytesBE()};b.toBytesLE=function(){var a=this.high,b=this.low;return[b&255,b>>>8&255,b>>>16&255,b>>>24&255,a&255,a>>>8&255,a>>>16&255,a>>>24&255]};b.toBytesBE=function(){var a=this.high,b=this.low;return[a>>>24&255,a>>>16&255,a>>>8&255,a&255,b>>>24&255,b>>>16&255,b>>>8&255,b&255]};return d}); 21 | -------------------------------------------------------------------------------- /docs/assets/models/VGG_CNN_S/VGG_mean.binaryproto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/models/VGG_CNN_S/VGG_mean.binaryproto -------------------------------------------------------------------------------- /docs/assets/models/VGG_CNN_S/deploy.prototxt: -------------------------------------------------------------------------------- 1 | name: "VGG_CNN_S" 2 | input: "data" 3 | input_dim: 10 4 | input_dim: 3 5 | input_dim: 224 6 | input_dim: 224 7 | layers { 8 | bottom: "data" 9 | top: "conv1" 10 | name: "conv1" 11 | type: CONVOLUTION 12 | convolution_param { 13 | num_output: 96 14 | kernel_size: 7 15 | stride: 2 16 | } 17 | } 18 | layers { 19 | bottom: "conv1" 20 | top: "conv1" 21 | name: "relu1" 22 | type: RELU 23 | } 24 | layers { 25 | bottom: "conv1" 26 | top: "norm1" 27 | name: "norm1" 28 | type: LRN 29 | lrn_param { 30 | local_size: 5 31 | alpha: 0.0005 32 | beta: 0.75 33 | k: 2 34 | } 35 | } 36 | layers { 37 | bottom: "norm1" 38 | top: "pool1" 39 | name: "pool1" 40 | type: POOLING 41 | pooling_param { 42 | pool: MAX 43 | kernel_size: 3 44 | stride: 3 45 | } 46 | } 47 | layers { 48 | bottom: "pool1" 49 | top: "conv2" 50 | name: "conv2" 51 | type: CONVOLUTION 52 | convolution_param { 53 | num_output: 256 54 | kernel_size: 5 55 | } 56 | } 57 | layers { 58 | bottom: "conv2" 59 | top: "conv2" 60 | name: "relu2" 61 | type: RELU 62 | } 63 | layers { 64 | bottom: "conv2" 65 | top: "pool2" 66 | name: "pool2" 67 | type: POOLING 68 | pooling_param { 69 | pool: MAX 70 | kernel_size: 2 71 | stride: 2 72 | } 73 | } 74 | layers { 75 | bottom: "pool2" 76 | top: "conv3" 77 | name: "conv3" 78 | type: CONVOLUTION 79 | convolution_param { 80 | num_output: 512 81 | pad: 1 82 | kernel_size: 3 83 | } 84 | } 85 | layers { 86 | bottom: "conv3" 87 | top: "conv3" 88 | name: "relu3" 89 | type: RELU 90 | } 91 | layers { 92 | bottom: "conv3" 93 | top: "conv4" 94 | name: "conv4" 95 | type: CONVOLUTION 96 | convolution_param { 97 | num_output: 512 98 | pad: 1 99 | kernel_size: 3 100 | } 101 | } 102 | layers { 103 | bottom: "conv4" 104 | top: "conv4" 105 | name: "relu4" 106 | type: RELU 107 | } 108 | layers { 109 | bottom: "conv4" 110 | top: "conv5" 111 | name: "conv5" 112 | type: CONVOLUTION 113 | convolution_param { 114 | num_output: 512 115 | pad: 1 116 | kernel_size: 3 117 | } 118 | } 119 | layers { 120 | bottom: "conv5" 121 | top: "conv5" 122 | name: "relu5" 123 | type: RELU 124 | } 125 | layers { 126 | bottom: "conv5" 127 | top: "pool5" 128 | name: "pool5" 129 | type: POOLING 130 | pooling_param { 131 | pool: MAX 132 | kernel_size: 3 133 | stride: 3 134 | } 135 | } 136 | layers { 137 | bottom: "pool5" 138 | top: "fc6" 139 | name: "fc6" 140 | type: INNER_PRODUCT 141 | inner_product_param { 142 | num_output: 4096 143 | } 144 | } 145 | layers { 146 | bottom: "fc6" 147 | top: "fc6" 148 | name: "relu6" 149 | type: RELU 150 | } 151 | layers { 152 | bottom: "fc6" 153 | top: "fc6" 154 | name: "drop6" 155 | type: DROPOUT 156 | dropout_param { 157 | dropout_ratio: 0.5 158 | } 159 | } 160 | layers { 161 | bottom: "fc6" 162 | top: "fc7" 163 | name: "fc7" 164 | type: INNER_PRODUCT 165 | inner_product_param { 166 | num_output: 4096 167 | } 168 | } 169 | layers { 170 | bottom: "fc7" 171 | top: "fc7" 172 | name: "relu7" 173 | type: RELU 174 | } 175 | layers { 176 | bottom: "fc7" 177 | top: "fc7" 178 | name: "drop7" 179 | type: DROPOUT 180 | dropout_param { 181 | dropout_ratio: 0.5 182 | } 183 | } 184 | layers { 185 | bottom: "fc7" 186 | top: "fc8" 187 | name: "fc8" 188 | type: INNER_PRODUCT 189 | inner_product_param { 190 | num_output: 1000 191 | } 192 | } 193 | layers { 194 | bottom: "fc8" 195 | top: "prob" 196 | name: "prob" 197 | type: SOFTMAX 198 | } -------------------------------------------------------------------------------- /docs/assets/models/VGG_FACE/COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Omkar M. Parkhi 2 | All rights reserved. 3 | 4 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 5 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 6 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 7 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY 8 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 9 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 10 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 11 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 12 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 13 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 14 | 15 | 16 | External libraries: 17 | * dpmCascadeDetector 18 | Code is based on Star Cascade work of Felzenswalb et. al. (http://people.cs.uchicago.edu/~rbg/star-cascade/) all rights of the authors apply. 19 | * convNet 20 | Code is based on MatConvNet library of Vedaldi et. al. (http://www.vlfeat.org/matconvnet/) all rights of the authors apply. 21 | -------------------------------------------------------------------------------- /docs/assets/models/VGG_FACE/README: -------------------------------------------------------------------------------- 1 | ============================================================== 2 | Deep Face Recognition 3 | 4 | Omkar M. Parkhi, Andrea Vedaldi, Andrew Zisserman 5 | Visual Geometry Group, University of Oxford 6 | ============================================================== 7 | 8 | -------- 9 | Overview 10 | -------- 11 | 12 | This package contains the Caffe[3] models for computation of "VGG Face" descriptor. 13 | The algorithm details can be found in [1]. The model was trained using MatConvNet[2] Library. 14 | 15 | The source code and data packages can be downloaded from: 16 | http://www.robots.ox.ac.uk/~vgg/software/vgg_face/ 17 | 18 | The models and other contents of this package can only be used for non-commercial research purposes. 19 | (Please read the licence terms here. http://creativecommons.org/licenses/by-nc/4.0/) 20 | 21 | Please cite [1] if you use the code or the data. If you have any questions regarding the package, 22 | please contact Omkar M. Parkhi 23 | 24 | 25 | ---------- 26 | References 27 | ---------- 28 | 29 | [1] O. M. Parkhi, A. Vedaldi, A. Zisserman 30 | Deep Face Recognition 31 | British Machine Vision Conference, 2015. 32 | 33 | [2] A. Vedaldi, K. Lenc 34 | MatConvNet - Convolutional Neural Networks for MATLAB 35 | arXiv:1412.4564, 2014. 36 | 37 | [3] Y. Jia, E. Shelhamer, J. Donahue, S. Karayev, J. Long, R. Girshick, S. Guadarrama, T. Darrell 38 | Caffe: Convolutional Architecture for Fast Feature Embedding 39 | arXiv:1408.5093, 2014 40 | 41 | -------------------------------------------------------------------------------- /docs/assets/models/VGG_FACE/VGG_FACE_deploy.prototxt: -------------------------------------------------------------------------------- 1 | name: "VGG_FACE_16_layers" 2 | input: "data" 3 | input_dim: 1 4 | input_dim: 3 5 | input_dim: 224 6 | input_dim: 224 7 | layers { 8 | bottom: "data" 9 | top: "conv1_1" 10 | name: "conv1_1" 11 | type: CONVOLUTION 12 | convolution_param { 13 | num_output: 64 14 | pad: 1 15 | kernel_size: 3 16 | } 17 | } 18 | layers { 19 | bottom: "conv1_1" 20 | top: "conv1_1" 21 | name: "relu1_1" 22 | type: RELU 23 | } 24 | layers { 25 | bottom: "conv1_1" 26 | top: "conv1_2" 27 | name: "conv1_2" 28 | type: CONVOLUTION 29 | convolution_param { 30 | num_output: 64 31 | pad: 1 32 | kernel_size: 3 33 | } 34 | } 35 | layers { 36 | bottom: "conv1_2" 37 | top: "conv1_2" 38 | name: "relu1_2" 39 | type: RELU 40 | } 41 | layers { 42 | bottom: "conv1_2" 43 | top: "pool1" 44 | name: "pool1" 45 | type: POOLING 46 | pooling_param { 47 | pool: MAX 48 | kernel_size: 2 49 | stride: 2 50 | } 51 | } 52 | layers { 53 | bottom: "pool1" 54 | top: "conv2_1" 55 | name: "conv2_1" 56 | type: CONVOLUTION 57 | convolution_param { 58 | num_output: 128 59 | pad: 1 60 | kernel_size: 3 61 | } 62 | } 63 | layers { 64 | bottom: "conv2_1" 65 | top: "conv2_1" 66 | name: "relu2_1" 67 | type: RELU 68 | } 69 | layers { 70 | bottom: "conv2_1" 71 | top: "conv2_2" 72 | name: "conv2_2" 73 | type: CONVOLUTION 74 | convolution_param { 75 | num_output: 128 76 | pad: 1 77 | kernel_size: 3 78 | } 79 | } 80 | layers { 81 | bottom: "conv2_2" 82 | top: "conv2_2" 83 | name: "relu2_2" 84 | type: RELU 85 | } 86 | layers { 87 | bottom: "conv2_2" 88 | top: "pool2" 89 | name: "pool2" 90 | type: POOLING 91 | pooling_param { 92 | pool: MAX 93 | kernel_size: 2 94 | stride: 2 95 | } 96 | } 97 | layers { 98 | bottom: "pool2" 99 | top: "conv3_1" 100 | name: "conv3_1" 101 | type: CONVOLUTION 102 | convolution_param { 103 | num_output: 256 104 | pad: 1 105 | kernel_size: 3 106 | } 107 | } 108 | layers { 109 | bottom: "conv3_1" 110 | top: "conv3_1" 111 | name: "relu3_1" 112 | type: RELU 113 | } 114 | layers { 115 | bottom: "conv3_1" 116 | top: "conv3_2" 117 | name: "conv3_2" 118 | type: CONVOLUTION 119 | convolution_param { 120 | num_output: 256 121 | pad: 1 122 | kernel_size: 3 123 | } 124 | } 125 | layers { 126 | bottom: "conv3_2" 127 | top: "conv3_2" 128 | name: "relu3_2" 129 | type: RELU 130 | } 131 | layers { 132 | bottom: "conv3_2" 133 | top: "conv3_3" 134 | name: "conv3_3" 135 | type: CONVOLUTION 136 | convolution_param { 137 | num_output: 256 138 | pad: 1 139 | kernel_size: 3 140 | } 141 | } 142 | layers { 143 | bottom: "conv3_3" 144 | top: "conv3_3" 145 | name: "relu3_3" 146 | type: RELU 147 | } 148 | layers { 149 | bottom: "conv3_3" 150 | top: "pool3" 151 | name: "pool3" 152 | type: POOLING 153 | pooling_param { 154 | pool: MAX 155 | kernel_size: 2 156 | stride: 2 157 | } 158 | } 159 | layers { 160 | bottom: "pool3" 161 | top: "conv4_1" 162 | name: "conv4_1" 163 | type: CONVOLUTION 164 | convolution_param { 165 | num_output: 512 166 | pad: 1 167 | kernel_size: 3 168 | } 169 | } 170 | layers { 171 | bottom: "conv4_1" 172 | top: "conv4_1" 173 | name: "relu4_1" 174 | type: RELU 175 | } 176 | layers { 177 | bottom: "conv4_1" 178 | top: "conv4_2" 179 | name: "conv4_2" 180 | type: CONVOLUTION 181 | convolution_param { 182 | num_output: 512 183 | pad: 1 184 | kernel_size: 3 185 | } 186 | } 187 | layers { 188 | bottom: "conv4_2" 189 | top: "conv4_2" 190 | name: "relu4_2" 191 | type: RELU 192 | } 193 | layers { 194 | bottom: "conv4_2" 195 | top: "conv4_3" 196 | name: "conv4_3" 197 | type: CONVOLUTION 198 | convolution_param { 199 | num_output: 512 200 | pad: 1 201 | kernel_size: 3 202 | } 203 | } 204 | layers { 205 | bottom: "conv4_3" 206 | top: "conv4_3" 207 | name: "relu4_3" 208 | type: RELU 209 | } 210 | layers { 211 | bottom: "conv4_3" 212 | top: "pool4" 213 | name: "pool4" 214 | type: POOLING 215 | pooling_param { 216 | pool: MAX 217 | kernel_size: 2 218 | stride: 2 219 | } 220 | } 221 | layers { 222 | bottom: "pool4" 223 | top: "conv5_1" 224 | name: "conv5_1" 225 | type: CONVOLUTION 226 | convolution_param { 227 | num_output: 512 228 | pad: 1 229 | kernel_size: 3 230 | } 231 | } 232 | layers { 233 | bottom: "conv5_1" 234 | top: "conv5_1" 235 | name: "relu5_1" 236 | type: RELU 237 | } 238 | layers { 239 | bottom: "conv5_1" 240 | top: "conv5_2" 241 | name: "conv5_2" 242 | type: CONVOLUTION 243 | convolution_param { 244 | num_output: 512 245 | pad: 1 246 | kernel_size: 3 247 | } 248 | } 249 | layers { 250 | bottom: "conv5_2" 251 | top: "conv5_2" 252 | name: "relu5_2" 253 | type: RELU 254 | } 255 | layers { 256 | bottom: "conv5_2" 257 | top: "conv5_3" 258 | name: "conv5_3" 259 | type: CONVOLUTION 260 | convolution_param { 261 | num_output: 512 262 | pad: 1 263 | kernel_size: 3 264 | } 265 | } 266 | layers { 267 | bottom: "conv5_3" 268 | top: "conv5_3" 269 | name: "relu5_3" 270 | type: RELU 271 | } 272 | layers { 273 | bottom: "conv5_3" 274 | top: "pool5" 275 | name: "pool5" 276 | type: POOLING 277 | pooling_param { 278 | pool: MAX 279 | kernel_size: 2 280 | stride: 2 281 | } 282 | } 283 | layers { 284 | bottom: "pool5" 285 | top: "fc6" 286 | name: "fc6" 287 | type: INNER_PRODUCT 288 | inner_product_param { 289 | num_output: 4096 290 | } 291 | } 292 | layers { 293 | bottom: "fc6" 294 | top: "fc6" 295 | name: "relu6" 296 | type: RELU 297 | } 298 | layers { 299 | bottom: "fc6" 300 | top: "fc6" 301 | name: "drop6" 302 | type: DROPOUT 303 | dropout_param { 304 | dropout_ratio: 0.5 305 | } 306 | } 307 | layers { 308 | bottom: "fc6" 309 | top: "fc7" 310 | name: "fc7" 311 | type: INNER_PRODUCT 312 | inner_product_param { 313 | num_output: 4096 314 | } 315 | } 316 | layers { 317 | bottom: "fc7" 318 | top: "fc7" 319 | name: "relu7" 320 | type: RELU 321 | } 322 | layers { 323 | bottom: "fc7" 324 | top: "fc7" 325 | name: "drop7" 326 | type: DROPOUT 327 | dropout_param { 328 | dropout_ratio: 0.5 329 | } 330 | } 331 | layers { 332 | bottom: "fc7" 333 | top: "fc8" 334 | name: "fc8" 335 | type: INNER_PRODUCT 336 | inner_product_param { 337 | num_output: 2622 338 | } 339 | } 340 | layers { 341 | bottom: "fc8" 342 | top: "prob" 343 | name: "prob" 344 | type: SOFTMAX 345 | } 346 | -------------------------------------------------------------------------------- /docs/assets/models/VGG_FACE/ak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/models/VGG_FACE/ak.png -------------------------------------------------------------------------------- /docs/assets/models/VGG_FACE/fetch_weights.sh: -------------------------------------------------------------------------------- 1 | curl -L https://www.dropbox.com/sh/gi9dcp6748n1rho/AAB__eZAq5eg3CLAFJ1TACCla?dl=1 -O -J 2 | unzip weights.zip -d weights -------------------------------------------------------------------------------- /docs/assets/models/age_net/deploy_age.prototxt: -------------------------------------------------------------------------------- 1 | name: "CaffeNet" 2 | input: "data" 3 | input_dim: 1 4 | input_dim: 3 5 | input_dim: 227 6 | input_dim: 227 7 | layers { 8 | name: "conv1" 9 | type: CONVOLUTION 10 | bottom: "data" 11 | top: "conv1" 12 | convolution_param { 13 | num_output: 96 14 | kernel_size: 7 15 | stride: 4 16 | } 17 | } 18 | layers { 19 | name: "relu1" 20 | type: RELU 21 | bottom: "conv1" 22 | top: "conv1" 23 | } 24 | layers { 25 | name: "pool1" 26 | type: POOLING 27 | bottom: "conv1" 28 | top: "pool1" 29 | pooling_param { 30 | pool: MAX 31 | kernel_size: 3 32 | stride: 2 33 | } 34 | } 35 | layers { 36 | name: "norm1" 37 | type: LRN 38 | bottom: "pool1" 39 | top: "norm1" 40 | lrn_param { 41 | local_size: 5 42 | alpha: 0.0001 43 | beta: 0.75 44 | } 45 | } 46 | layers { 47 | name: "conv2" 48 | type: CONVOLUTION 49 | bottom: "norm1" 50 | top: "conv2" 51 | convolution_param { 52 | num_output: 256 53 | pad: 2 54 | kernel_size: 5 55 | } 56 | } 57 | layers { 58 | name: "relu2" 59 | type: RELU 60 | bottom: "conv2" 61 | top: "conv2" 62 | } 63 | layers { 64 | name: "pool2" 65 | type: POOLING 66 | bottom: "conv2" 67 | top: "pool2" 68 | pooling_param { 69 | pool: MAX 70 | kernel_size: 3 71 | stride: 2 72 | } 73 | } 74 | layers { 75 | name: "norm2" 76 | type: LRN 77 | bottom: "pool2" 78 | top: "norm2" 79 | lrn_param { 80 | local_size: 5 81 | alpha: 0.0001 82 | beta: 0.75 83 | } 84 | } 85 | layers { 86 | name: "conv3" 87 | type: CONVOLUTION 88 | bottom: "norm2" 89 | top: "conv3" 90 | convolution_param { 91 | num_output: 384 92 | pad: 1 93 | kernel_size: 3 94 | } 95 | } 96 | layers{ 97 | name: "relu3" 98 | type: RELU 99 | bottom: "conv3" 100 | top: "conv3" 101 | } 102 | layers { 103 | name: "pool5" 104 | type: POOLING 105 | bottom: "conv3" 106 | top: "pool5" 107 | pooling_param { 108 | pool: MAX 109 | kernel_size: 3 110 | stride: 2 111 | } 112 | } 113 | layers { 114 | name: "fc6" 115 | type: INNER_PRODUCT 116 | bottom: "pool5" 117 | top: "fc6" 118 | inner_product_param { 119 | num_output: 512 120 | } 121 | } 122 | layers { 123 | name: "relu6" 124 | type: RELU 125 | bottom: "fc6" 126 | top: "fc6" 127 | } 128 | layers { 129 | name: "drop6" 130 | type: DROPOUT 131 | bottom: "fc6" 132 | top: "fc6" 133 | dropout_param { 134 | dropout_ratio: 0.5 135 | } 136 | } 137 | layers { 138 | name: "fc7" 139 | type: INNER_PRODUCT 140 | bottom: "fc6" 141 | top: "fc7" 142 | inner_product_param { 143 | num_output: 512 144 | } 145 | } 146 | layers { 147 | name: "relu7" 148 | type: RELU 149 | bottom: "fc7" 150 | top: "fc7" 151 | } 152 | layers { 153 | name: "drop7" 154 | type: DROPOUT 155 | bottom: "fc7" 156 | top: "fc7" 157 | dropout_param { 158 | dropout_ratio: 0.5 159 | } 160 | } 161 | layers { 162 | name: "fc8" 163 | type: INNER_PRODUCT 164 | bottom: "fc7" 165 | top: "fc8" 166 | inner_product_param { 167 | num_output: 8 168 | } 169 | } 170 | layers { 171 | name: "prob" 172 | type: SOFTMAX 173 | bottom: "fc8" 174 | top: "prob" 175 | } 176 | -------------------------------------------------------------------------------- /docs/assets/models/age_net/deploy_gender.prototxt: -------------------------------------------------------------------------------- 1 | name: "CaffeNet" 2 | input: "data" 3 | input_dim: 1 4 | input_dim: 3 5 | input_dim: 227 6 | input_dim: 227 7 | layers { 8 | name: "conv1" 9 | type: CONVOLUTION 10 | bottom: "data" 11 | top: "conv1" 12 | convolution_param { 13 | num_output: 96 14 | kernel_size: 7 15 | stride: 4 16 | } 17 | } 18 | layers { 19 | name: "relu1" 20 | type: RELU 21 | bottom: "conv1" 22 | top: "conv1" 23 | } 24 | layers { 25 | name: "pool1" 26 | type: POOLING 27 | bottom: "conv1" 28 | top: "pool1" 29 | pooling_param { 30 | pool: MAX 31 | kernel_size: 3 32 | stride: 2 33 | } 34 | } 35 | layers { 36 | name: "norm1" 37 | type: LRN 38 | bottom: "pool1" 39 | top: "norm1" 40 | lrn_param { 41 | local_size: 5 42 | alpha: 0.0001 43 | beta: 0.75 44 | } 45 | } 46 | layers { 47 | name: "conv2" 48 | type: CONVOLUTION 49 | bottom: "norm1" 50 | top: "conv2" 51 | convolution_param { 52 | num_output: 256 53 | pad: 2 54 | kernel_size: 5 55 | } 56 | } 57 | layers { 58 | name: "relu2" 59 | type: RELU 60 | bottom: "conv2" 61 | top: "conv2" 62 | } 63 | layers { 64 | name: "pool2" 65 | type: POOLING 66 | bottom: "conv2" 67 | top: "pool2" 68 | pooling_param { 69 | pool: MAX 70 | kernel_size: 3 71 | stride: 2 72 | } 73 | } 74 | layers { 75 | name: "norm2" 76 | type: LRN 77 | bottom: "pool2" 78 | top: "norm2" 79 | lrn_param { 80 | local_size: 5 81 | alpha: 0.0001 82 | beta: 0.75 83 | } 84 | } 85 | layers { 86 | name: "conv3" 87 | type: CONVOLUTION 88 | bottom: "norm2" 89 | top: "conv3" 90 | convolution_param { 91 | num_output: 384 92 | pad: 1 93 | kernel_size: 3 94 | } 95 | } 96 | layers{ 97 | name: "relu3" 98 | type: RELU 99 | bottom: "conv3" 100 | top: "conv3" 101 | } 102 | layers { 103 | name: "pool5" 104 | type: POOLING 105 | bottom: "conv3" 106 | top: "pool5" 107 | pooling_param { 108 | pool: MAX 109 | kernel_size: 3 110 | stride: 2 111 | } 112 | } 113 | layers { 114 | name: "fc6" 115 | type: INNER_PRODUCT 116 | bottom: "pool5" 117 | top: "fc6" 118 | inner_product_param { 119 | num_output: 512 120 | } 121 | } 122 | layers { 123 | name: "relu6" 124 | type: RELU 125 | bottom: "fc6" 126 | top: "fc6" 127 | } 128 | layers { 129 | name: "drop6" 130 | type: DROPOUT 131 | bottom: "fc6" 132 | top: "fc6" 133 | dropout_param { 134 | dropout_ratio: 0.5 135 | } 136 | } 137 | layers { 138 | name: "fc7" 139 | type: INNER_PRODUCT 140 | bottom: "fc6" 141 | top: "fc7" 142 | inner_product_param { 143 | num_output: 512 144 | } 145 | } 146 | layers { 147 | name: "relu7" 148 | type: RELU 149 | bottom: "fc7" 150 | top: "fc7" 151 | } 152 | layers { 153 | name: "drop7" 154 | type: DROPOUT 155 | bottom: "fc7" 156 | top: "fc7" 157 | dropout_param { 158 | dropout_ratio: 0.5 159 | } 160 | } 161 | layers { 162 | name: "fc8" 163 | type: INNER_PRODUCT 164 | bottom: "fc7" 165 | top: "fc8" 166 | inner_product_param { 167 | num_output: 2 168 | } 169 | } 170 | layers { 171 | name: "prob" 172 | type: SOFTMAX 173 | bottom: "fc8" 174 | top: "prob" 175 | } 176 | -------------------------------------------------------------------------------- /docs/assets/models/age_net/mean.binaryproto: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/models/age_net/mean.binaryproto -------------------------------------------------------------------------------- /docs/assets/models/bvlc_alexnet/alexnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/models/bvlc_alexnet/alexnet.png -------------------------------------------------------------------------------- /docs/assets/models/bvlc_alexnet/compact.prototxt: -------------------------------------------------------------------------------- 1 | name: "AlexNet" 2 | layer { 3 | name: "data" 4 | type: "Input" 5 | top: "data" 6 | input_param { 7 | shape { 8 | dim: 10 9 | dim: 3 10 | dim: 227 11 | dim: 227 12 | } 13 | } 14 | } 15 | layer { 16 | name: "conv1/conv" 17 | type: "Convolution" 18 | bottom: "data" 19 | top: "conv1/conv" 20 | param { 21 | lr_mult: 1 22 | decay_mult: 1 23 | } 24 | param { 25 | lr_mult: 2 26 | decay_mult: 0 27 | } 28 | convolution_param { 29 | num_output: 96 30 | kernel_size: 11 31 | stride: 4 32 | } 33 | } 34 | layer { 35 | name: "conv1/relu" 36 | type: "ReLU" 37 | bottom: "conv1/conv" 38 | top: "conv1/conv" 39 | } 40 | layer { 41 | name: "conv1/norm" 42 | type: "LRN" 43 | bottom: "conv1/conv" 44 | top: "conv1/norm" 45 | lrn_param { 46 | local_size: 5 47 | alpha: 0.0001 48 | beta: 0.75 49 | } 50 | } 51 | layer { 52 | name: "pool1" 53 | type: "Pooling" 54 | bottom: "conv1/norm" 55 | top: "pool1" 56 | pooling_param { 57 | pool: MAX 58 | kernel_size: 3 59 | stride: 2 60 | } 61 | } 62 | layer { 63 | name: "conv2/conv" 64 | type: "Convolution" 65 | bottom: "pool1" 66 | top: "conv2/conv" 67 | param { 68 | lr_mult: 1 69 | decay_mult: 1 70 | } 71 | param { 72 | lr_mult: 2 73 | decay_mult: 0 74 | } 75 | convolution_param { 76 | num_output: 256 77 | pad: 2 78 | kernel_size: 5 79 | group: 2 80 | } 81 | } 82 | layer { 83 | name: "conv2/relu" 84 | type: "ReLU" 85 | bottom: "conv2/conv" 86 | top: "conv2/conv" 87 | } 88 | layer { 89 | name: "conv2/norm" 90 | type: "LRN" 91 | bottom: "conv2/conv" 92 | top: "conv2/norm" 93 | lrn_param { 94 | local_size: 5 95 | alpha: 0.0001 96 | beta: 0.75 97 | } 98 | } 99 | layer { 100 | name: "pool2" 101 | type: "Pooling" 102 | bottom: "conv2/norm" 103 | top: "pool2" 104 | pooling_param { 105 | pool: MAX 106 | kernel_size: 3 107 | stride: 2 108 | } 109 | } 110 | layer { 111 | name: "conv3/conv" 112 | type: "Convolution" 113 | bottom: "pool2" 114 | top: "conv3/conv" 115 | param { 116 | lr_mult: 1 117 | decay_mult: 1 118 | } 119 | param { 120 | lr_mult: 2 121 | decay_mult: 0 122 | } 123 | convolution_param { 124 | num_output: 384 125 | pad: 1 126 | kernel_size: 3 127 | } 128 | } 129 | layer { 130 | name: "conv3/relu" 131 | type: "ReLU" 132 | bottom: "conv3/conv" 133 | top: "conv3/conv" 134 | } 135 | layer { 136 | name: "conv4/conv" 137 | type: "Convolution" 138 | bottom: "conv3/conv" 139 | top: "conv4/conv" 140 | param { 141 | lr_mult: 1 142 | decay_mult: 1 143 | } 144 | param { 145 | lr_mult: 2 146 | decay_mult: 0 147 | } 148 | convolution_param { 149 | num_output: 384 150 | pad: 1 151 | kernel_size: 3 152 | group: 2 153 | } 154 | } 155 | layer { 156 | name: "conv4/relu" 157 | type: "ReLU" 158 | bottom: "conv4/conv" 159 | top: "conv4/conv" 160 | } 161 | layer { 162 | name: "conv5/conv" 163 | type: "Convolution" 164 | bottom: "conv4/conv" 165 | top: "conv5/conv" 166 | param { 167 | lr_mult: 1 168 | decay_mult: 1 169 | } 170 | param { 171 | lr_mult: 2 172 | decay_mult: 0 173 | } 174 | convolution_param { 175 | num_output: 256 176 | pad: 1 177 | kernel_size: 3 178 | group: 2 179 | } 180 | } 181 | layer { 182 | name: "conv5/relu" 183 | type: "ReLU" 184 | bottom: "conv5/conv" 185 | top: "conv5/conv" 186 | } 187 | layer { 188 | name: "pool5" 189 | type: "Pooling" 190 | bottom: "conv5/conv" 191 | top: "pool5" 192 | pooling_param { 193 | pool: MAX 194 | kernel_size: 3 195 | stride: 2 196 | } 197 | } 198 | layer { 199 | name: "fc6/fc" 200 | type: "InnerProduct" 201 | bottom: "pool5" 202 | top: "fc6/fc" 203 | param { 204 | lr_mult: 1 205 | decay_mult: 1 206 | } 207 | param { 208 | lr_mult: 2 209 | decay_mult: 0 210 | } 211 | inner_product_param { 212 | num_output: 4096 213 | } 214 | } 215 | layer { 216 | name: "fc6/relu" 217 | type: "ReLU" 218 | bottom: "fc6/fc" 219 | top: "fc6/fc" 220 | } 221 | layer { 222 | name: "fc6/drop" 223 | type: "Dropout" 224 | bottom: "fc6/fc" 225 | top: "fc6/fc" 226 | dropout_param { 227 | dropout_ratio: 0.5 228 | } 229 | } 230 | layer { 231 | name: "fc7/fc" 232 | type: "InnerProduct" 233 | bottom: "fc6/fc" 234 | top: "fc7/fc" 235 | param { 236 | lr_mult: 1 237 | decay_mult: 1 238 | } 239 | param { 240 | lr_mult: 2 241 | decay_mult: 0 242 | } 243 | inner_product_param { 244 | num_output: 4096 245 | } 246 | } 247 | layer { 248 | name: "fc7/relu" 249 | type: "ReLU" 250 | bottom: "fc7/fc" 251 | top: "fc7/fc" 252 | } 253 | layer { 254 | name: "fc7/drop" 255 | type: "Dropout" 256 | bottom: "fc7/fc" 257 | top: "fc7/fc" 258 | dropout_param { 259 | dropout_ratio: 0.5 260 | } 261 | } 262 | layer { 263 | name: "fc8/fc" 264 | type: "InnerProduct" 265 | bottom: "fc7/fc" 266 | top: "fc8/fc" 267 | param { 268 | lr_mult: 1 269 | decay_mult: 1 270 | } 271 | param { 272 | lr_mult: 2 273 | decay_mult: 0 274 | } 275 | inner_product_param { 276 | num_output: 1000 277 | } 278 | } 279 | layer { 280 | name: "prob" 281 | type: "Softmax" 282 | bottom: "fc8/fc" 283 | top: "prob" 284 | } -------------------------------------------------------------------------------- /docs/assets/models/bvlc_alexnet/deploy.prototxt: -------------------------------------------------------------------------------- 1 | name: "AlexNet" 2 | layer { 3 | name: "data" 4 | type: "Input" 5 | top: "data" 6 | input_param { 7 | shape { 8 | dim: 10 9 | dim: 3 10 | dim: 227 11 | dim: 227 12 | } 13 | } 14 | } 15 | layer { 16 | name: "conv1" 17 | type: "Convolution" 18 | bottom: "data" 19 | top: "conv1" 20 | param { 21 | lr_mult: 1 22 | decay_mult: 1 23 | } 24 | param { 25 | lr_mult: 2 26 | decay_mult: 0 27 | } 28 | convolution_param { 29 | num_output: 96 30 | kernel_size: 11 31 | stride: 4 32 | } 33 | } 34 | layer { 35 | name: "relu1" 36 | type: "ReLU" 37 | bottom: "conv1" 38 | top: "conv1" 39 | } 40 | layer { 41 | name: "norm1" 42 | type: "LRN" 43 | bottom: "conv1" 44 | top: "norm1" 45 | lrn_param { 46 | local_size: 5 47 | alpha: 0.0001 48 | beta: 0.75 49 | } 50 | } 51 | layer { 52 | name: "pool1" 53 | type: "Pooling" 54 | bottom: "norm1" 55 | top: "pool1" 56 | pooling_param { 57 | pool: MAX 58 | kernel_size: 3 59 | stride: 2 60 | } 61 | } 62 | layer { 63 | name: "conv2" 64 | type: "Convolution" 65 | bottom: "pool1" 66 | top: "conv2" 67 | param { 68 | lr_mult: 1 69 | decay_mult: 1 70 | } 71 | param { 72 | lr_mult: 2 73 | decay_mult: 0 74 | } 75 | convolution_param { 76 | num_output: 256 77 | pad: 2 78 | kernel_size: 5 79 | group: 2 80 | } 81 | } 82 | layer { 83 | name: "relu2" 84 | type: "ReLU" 85 | bottom: "conv2" 86 | top: "conv2" 87 | } 88 | layer { 89 | name: "norm2" 90 | type: "LRN" 91 | bottom: "conv2" 92 | top: "norm2" 93 | lrn_param { 94 | local_size: 5 95 | alpha: 0.0001 96 | beta: 0.75 97 | } 98 | } 99 | layer { 100 | name: "pool2" 101 | type: "Pooling" 102 | bottom: "norm2" 103 | top: "pool2" 104 | pooling_param { 105 | pool: MAX 106 | kernel_size: 3 107 | stride: 2 108 | } 109 | } 110 | layer { 111 | name: "conv3" 112 | type: "Convolution" 113 | bottom: "pool2" 114 | top: "conv3" 115 | param { 116 | lr_mult: 1 117 | decay_mult: 1 118 | } 119 | param { 120 | lr_mult: 2 121 | decay_mult: 0 122 | } 123 | convolution_param { 124 | num_output: 384 125 | pad: 1 126 | kernel_size: 3 127 | } 128 | } 129 | layer { 130 | name: "relu3" 131 | type: "ReLU" 132 | bottom: "conv3" 133 | top: "conv3" 134 | } 135 | layer { 136 | name: "conv4" 137 | type: "Convolution" 138 | bottom: "conv3" 139 | top: "conv4" 140 | param { 141 | lr_mult: 1 142 | decay_mult: 1 143 | } 144 | param { 145 | lr_mult: 2 146 | decay_mult: 0 147 | } 148 | convolution_param { 149 | num_output: 384 150 | pad: 1 151 | kernel_size: 3 152 | group: 2 153 | } 154 | } 155 | layer { 156 | name: "relu4" 157 | type: "ReLU" 158 | bottom: "conv4" 159 | top: "conv4" 160 | } 161 | layer { 162 | name: "conv5" 163 | type: "Convolution" 164 | bottom: "conv4" 165 | top: "conv5" 166 | param { 167 | lr_mult: 1 168 | decay_mult: 1 169 | } 170 | param { 171 | lr_mult: 2 172 | decay_mult: 0 173 | } 174 | convolution_param { 175 | num_output: 256 176 | pad: 1 177 | kernel_size: 3 178 | group: 2 179 | } 180 | } 181 | layer { 182 | name: "relu5" 183 | type: "ReLU" 184 | bottom: "conv5" 185 | top: "conv5" 186 | } 187 | layer { 188 | name: "pool5" 189 | type: "Pooling" 190 | bottom: "conv5" 191 | top: "pool5" 192 | pooling_param { 193 | pool: MAX 194 | kernel_size: 3 195 | stride: 2 196 | } 197 | } 198 | layer { 199 | name: "fc6" 200 | type: "InnerProduct" 201 | bottom: "pool5" 202 | top: "fc6" 203 | param { 204 | lr_mult: 1 205 | decay_mult: 1 206 | } 207 | param { 208 | lr_mult: 2 209 | decay_mult: 0 210 | } 211 | inner_product_param { 212 | num_output: 4096 213 | } 214 | } 215 | layer { 216 | name: "relu6" 217 | type: "ReLU" 218 | bottom: "fc6" 219 | top: "fc6" 220 | } 221 | layer { 222 | name: "drop6" 223 | type: "Dropout" 224 | bottom: "fc6" 225 | top: "fc6" 226 | dropout_param { 227 | dropout_ratio: 0.5 228 | } 229 | } 230 | layer { 231 | name: "fc7" 232 | type: "InnerProduct" 233 | bottom: "fc6" 234 | top: "fc7" 235 | param { 236 | lr_mult: 1 237 | decay_mult: 1 238 | } 239 | param { 240 | lr_mult: 2 241 | decay_mult: 0 242 | } 243 | inner_product_param { 244 | num_output: 4096 245 | } 246 | } 247 | layer { 248 | name: "relu7" 249 | type: "ReLU" 250 | bottom: "fc7" 251 | top: "fc7" 252 | } 253 | layer { 254 | name: "drop7" 255 | type: "Dropout" 256 | bottom: "fc7" 257 | top: "fc7" 258 | dropout_param { 259 | dropout_ratio: 0.5 260 | } 261 | } 262 | layer { 263 | name: "fc8" 264 | type: "InnerProduct" 265 | bottom: "fc7" 266 | top: "fc8" 267 | param { 268 | lr_mult: 1 269 | decay_mult: 1 270 | } 271 | param { 272 | lr_mult: 2 273 | decay_mult: 0 274 | } 275 | inner_product_param { 276 | num_output: 1000 277 | } 278 | } 279 | layer { 280 | name: "prob" 281 | type: "Softmax" 282 | bottom: "fc8" 283 | top: "prob" 284 | } -------------------------------------------------------------------------------- /docs/assets/models/bvlc_alexnet/fetch_weights.sh: -------------------------------------------------------------------------------- 1 | curl -L https://www.dropbox.com/sh/7r3283zx3eneaml/AAAPW5GYHDEszPqavnQX3giqa?dl=1 -O -J 2 | unzip weights.zip -d weights -------------------------------------------------------------------------------- /docs/assets/models/bvlc_googlenet/googlenet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaosmail/caffejs/1700cd86105c9a5bfd130014b753a45ed902acaa/docs/assets/models/bvlc_googlenet/googlenet.png -------------------------------------------------------------------------------- /docs/assets/models/nin_imagenet/deploy.prototxt: -------------------------------------------------------------------------------- 1 | name: "nin_imagenet" 2 | input: "data" 3 | input_dim: 10 4 | input_dim: 3 5 | input_dim: 224 6 | input_dim: 224 7 | layers { 8 | bottom: "data" 9 | top: "conv1" 10 | name: "conv1" 11 | type: CONVOLUTION 12 | convolution_param { 13 | num_output: 96 14 | kernel_size: 11 15 | stride: 4 16 | } 17 | } 18 | layers { 19 | bottom: "conv1" 20 | top: "conv1" 21 | name: "relu0" 22 | type: RELU 23 | } 24 | layers { 25 | bottom: "conv1" 26 | top: "cccp1" 27 | name: "cccp1" 28 | type: CONVOLUTION 29 | convolution_param { 30 | num_output: 96 31 | kernel_size: 1 32 | stride: 1 33 | } 34 | } 35 | layers { 36 | bottom: "cccp1" 37 | top: "cccp1" 38 | name: "relu1" 39 | type: RELU 40 | } 41 | layers { 42 | bottom: "cccp1" 43 | top: "cccp2" 44 | name: "cccp2" 45 | type: CONVOLUTION 46 | convolution_param { 47 | num_output: 96 48 | kernel_size: 1 49 | stride: 1 50 | } 51 | } 52 | layers { 53 | bottom: "cccp2" 54 | top: "cccp2" 55 | name: "relu2" 56 | type: RELU 57 | } 58 | layers { 59 | bottom: "cccp2" 60 | top: "pool1" 61 | name: "pool1" 62 | type: POOLING 63 | pooling_param { 64 | pool: MAX 65 | kernel_size: 3 66 | stride: 2 67 | } 68 | } 69 | layers { 70 | bottom: "pool1" 71 | top: "conv2" 72 | name: "conv2" 73 | type: CONVOLUTION 74 | convolution_param { 75 | num_output: 256 76 | pad: 2 77 | kernel_size: 5 78 | stride: 1 79 | } 80 | } 81 | layers { 82 | bottom: "conv2" 83 | top: "conv2" 84 | name: "relu3" 85 | type: RELU 86 | } 87 | layers { 88 | bottom: "conv2" 89 | top: "cccp3" 90 | name: "cccp3" 91 | type: CONVOLUTION 92 | convolution_param { 93 | num_output: 256 94 | kernel_size: 1 95 | stride: 1 96 | } 97 | } 98 | layers { 99 | bottom: "cccp3" 100 | top: "cccp3" 101 | name: "relu5" 102 | type: RELU 103 | } 104 | layers { 105 | bottom: "cccp3" 106 | top: "cccp4" 107 | name: "cccp4" 108 | type: CONVOLUTION 109 | convolution_param { 110 | num_output: 256 111 | kernel_size: 1 112 | stride: 1 113 | } 114 | } 115 | layers { 116 | bottom: "cccp4" 117 | top: "cccp4" 118 | name: "relu6" 119 | type: RELU 120 | } 121 | layers { 122 | bottom: "cccp4" 123 | top: "pool2" 124 | name: "pool2" 125 | type: POOLING 126 | pooling_param { 127 | pool: MAX 128 | kernel_size: 3 129 | stride: 2 130 | } 131 | } 132 | layers { 133 | bottom: "pool2" 134 | top: "conv3" 135 | name: "conv3" 136 | type: CONVOLUTION 137 | convolution_param { 138 | num_output: 384 139 | pad: 1 140 | kernel_size: 3 141 | stride: 1 142 | } 143 | } 144 | layers { 145 | bottom: "conv3" 146 | top: "conv3" 147 | name: "relu7" 148 | type: RELU 149 | } 150 | layers { 151 | bottom: "conv3" 152 | top: "cccp5" 153 | name: "cccp5" 154 | type: CONVOLUTION 155 | convolution_param { 156 | num_output: 384 157 | kernel_size: 1 158 | stride: 1 159 | } 160 | } 161 | layers { 162 | bottom: "cccp5" 163 | top: "cccp5" 164 | name: "relu8" 165 | type: RELU 166 | } 167 | layers { 168 | bottom: "cccp5" 169 | top: "cccp6" 170 | name: "cccp6" 171 | type: CONVOLUTION 172 | convolution_param { 173 | num_output: 384 174 | kernel_size: 1 175 | stride: 1 176 | } 177 | } 178 | layers { 179 | bottom: "cccp6" 180 | top: "cccp6" 181 | name: "relu9" 182 | type: RELU 183 | } 184 | layers { 185 | bottom: "cccp6" 186 | top: "pool3" 187 | name: "pool3" 188 | type: POOLING 189 | pooling_param { 190 | pool: MAX 191 | kernel_size: 3 192 | stride: 2 193 | } 194 | } 195 | layers { 196 | bottom: "pool3" 197 | top: "pool3" 198 | name: "drop" 199 | type: DROPOUT 200 | dropout_param { 201 | dropout_ratio: 0.5 202 | } 203 | } 204 | layers { 205 | bottom: "pool3" 206 | top: "conv4" 207 | name: "conv4" 208 | type: CONVOLUTION 209 | convolution_param { 210 | num_output: 1024 211 | pad: 1 212 | kernel_size: 3 213 | stride: 1 214 | } 215 | } 216 | layers { 217 | bottom: "conv4" 218 | top: "conv4" 219 | name: "relu10" 220 | type: RELU 221 | } 222 | layers { 223 | bottom: "conv4" 224 | top: "cccp7" 225 | name: "cccp7" 226 | type: CONVOLUTION 227 | convolution_param { 228 | num_output: 1024 229 | kernel_size: 1 230 | stride: 1 231 | } 232 | } 233 | layers { 234 | bottom: "cccp7" 235 | top: "cccp7" 236 | name: "relu11" 237 | type: RELU 238 | } 239 | layers { 240 | bottom: "cccp7" 241 | top: "cccp8" 242 | name: "cccp8" 243 | type: CONVOLUTION 244 | convolution_param { 245 | num_output: 1000 246 | kernel_size: 1 247 | stride: 1 248 | } 249 | } 250 | layers { 251 | bottom: "cccp8" 252 | top: "cccp8" 253 | name: "relu12" 254 | type: RELU 255 | } 256 | layers { 257 | bottom: "cccp8" 258 | top: "pool4" 259 | name: "pool4" 260 | type: POOLING 261 | pooling_param { 262 | pool: AVE 263 | kernel_size: 6 264 | stride: 1 265 | } 266 | } -------------------------------------------------------------------------------- /docs/assets/scripts/deepdream_worker.js: -------------------------------------------------------------------------------- 1 | importScripts( 2 | '../libs/d3.min.js', 3 | '../dist/caffe.js' 4 | ); 5 | 6 | var window = self; 7 | var nj = NumJS; 8 | 9 | // Let's create a GoogLeNet model from Caffe 10 | var model = new Net.CaffeModel( 11 | '../models/bvlc_googlenet/caffejs_deepdream.prototxt', 12 | '../models/bvlc_googlenet/weights/' 13 | ); 14 | 15 | // the mean value can be found in train_val.prototxt 16 | var mean = [104.0, 116.0, 122.0]; 17 | 18 | model.load().then(function(d){ 19 | // Notify the main thread that the model is ready 20 | self.postMessage({ 21 | name: 'model-loaded', 22 | }); 23 | }); 24 | 25 | /** 26 | * Deepdream JavaScript implementation (running as Web Worker) 27 | * @src https://github.com/google/deepdream/blob/master/dream.ipynb 28 | */ 29 | 30 | var objective_L2 = function(dst) { 31 | dst.out_act.dw.set(dst.out_act.w); 32 | } 33 | 34 | function make_step(net, in_data, params) { 35 | params = params || {}; 36 | var step_size = params.step_size || 1.5; 37 | var jitter = params.jitter || 32; 38 | var clip_data = params.clip_data === undefined ? true : clip_data; 39 | var end = params.end || 'inception_4c/output'; 40 | var objective = params.objective || objective_L2; 41 | 42 | var src = net.getLayer('data'); 43 | var dst = net.getLayer(end); 44 | 45 | var ox = nj.randi(-jitter, jitter+1); 46 | var oy = nj.randi(-jitter, jitter+1); 47 | 48 | // apply jitter shift 49 | in_data = in_data.roll(ox, oy); 50 | 51 | net.forward(in_data, {end: end}) 52 | objective(dst) // specify the optimization objective 53 | net.backward(undefined, {start: end}) 54 | 55 | var out_data = src.out_act; 56 | var diff = out_data.dw; 57 | 58 | var mean_diff = 0.0; 59 | for (var i=0,len=diff.length;i= 0; i--) { 102 | var octave = octave_n - i; 103 | var octave_base = octaves[i]; 104 | var w = octave_base.sx; 105 | var h = octave_base.sy; 106 | 107 | if (octave > 0) { 108 | // upscale details from the previous octave 109 | var w1 = detail.sx; 110 | var h1 = detail.sy; 111 | detail = detail.zoom(w/w1, h/h1, 1); 112 | } 113 | 114 | // extract details produced on the current octave 115 | vol = octave_base.clone(); 116 | vol.w = nj.add(vol.w, detail.w); 117 | 118 | // For now the model dimensions are fixed 119 | // so lets update them 120 | model.setInputDimensions(vol.sx, vol.sy); 121 | 122 | for (var j = 0; j < iter_n; j++) { 123 | vol = make_step(net, vol, {end: end}); 124 | 125 | self.postMessage({ 126 | name: 'dream-progress', 127 | output: vol.toJSON(), 128 | octave: octave, 129 | iteration: j, 130 | }); 131 | } 132 | 133 | // extract details produced on the current octave 134 | detail = vol.clone() 135 | detail.w = nj.sub(detail.w, octave_base.w); 136 | } 137 | 138 | // returning the resulting image 139 | return vol; 140 | } 141 | 142 | self.onmessage = function(e){ 143 | var vol = Net.Vol.fromJSON(e.data.input); 144 | var params = e.data.params; 145 | 146 | // Perform a deepdream 147 | vol = deepdream(model, vol, params); 148 | 149 | self.postMessage({ 150 | name: 'dream-finished', 151 | output: vol.toJSON(), 152 | }); 153 | } -------------------------------------------------------------------------------- /docs/assets/styles/docs.css: -------------------------------------------------------------------------------- 1 | .page-content { 2 | max-width: 900px; 3 | margin: auto; 4 | } 5 | 6 | .mdl-card__media { 7 | background-color: #fff; 8 | } 9 | 10 | .drawer-separator { 11 | height: 1px; 12 | background-color: #dcdcdc; 13 | margin: 8px 0; 14 | } 15 | 16 | span.mdl-navigation__link { 17 | color: rgb(0,150,136) !important; 18 | font-weight: bold; 19 | } 20 | 21 | .mdl-layout__drawer .mdl-navigation .mdl-navigation__link.is-active { 22 | color: rgb(255,64,129) !important; 23 | background-color: #e0e0e0; 24 | } -------------------------------------------------------------------------------- /docs/assets/styles/filters.css: -------------------------------------------------------------------------------- 1 | .net-layer { 2 | position: relative; 3 | border-bottom: 1px solid #eee; 4 | padding-bottom: 10px; 5 | } 6 | .net-vis { 7 | margin-left: 200px; 8 | } 9 | .net-description { 10 | width: 200px; 11 | position: absolute; 12 | margin-right: 20px; 13 | } -------------------------------------------------------------------------------- /docs/assets/styles/graph.css: -------------------------------------------------------------------------------- 1 | g.layer-conv > rect, g.layer-fc > rect { 2 | fill: #4285f4; 3 | } 4 | 5 | g.layer-pool > rect { 6 | fill: #db4437; 7 | } 8 | 9 | g.layer-lrn > rect { 10 | fill: #0f9d58; 11 | } 12 | 13 | g.layer-inception > rect, g.layer-fire > rect { 14 | fill: #129d5a; 15 | } 16 | 17 | g.layer-concat > rect { 18 | fill: #129d5a; 19 | } 20 | 21 | g.layer-input > rect, g.layer-softmax > rect { 22 | fill: #f4b400; 23 | } 24 | 25 | text { 26 | font-size: 14px; 27 | } 28 | 29 | .label { 30 | color: #000; 31 | } 32 | 33 | .node rect { 34 | fill: #ddd; 35 | stroke: none; 36 | } 37 | 38 | .edgePath path, .edgePath marker { 39 | stroke: #333; 40 | stroke-width: 1.5px; 41 | } 42 | 43 | .edgePath marker { 44 | fill: #fff; 45 | } 46 | 47 | .label text { 48 | padding-left:0.75em; 49 | font-size: 12px; 50 | font-family: monospace; 51 | fill: #333; 52 | } 53 | 54 | svg div { 55 | text-align: center; 56 | font-size: 14px; 57 | } -------------------------------------------------------------------------------- /docs/development-guide.md: -------------------------------------------------------------------------------- 1 | # CaffeJS - Development Guide 2 | 3 | ## Getting Started 4 | 5 | Checkout the project and run `npm install` in the root directory to get started. Now you can start diving into the code. 6 | 7 | You can build your code running `gulp` or using the file watcher `gulp watch`. 8 | 9 | ### Organization of the code 10 | 11 | This library uses Typescript to modularize JavaScript code and use some of the cool Typescript features on top, such as classes, inheritance, fat arrow functions, etc. 12 | 13 | You can find the source code in the `src/` directory organized in 5 modules: 14 | 15 | * Net - A fork of ConvNetJS with Caffe compatibility (considering model weights, output parameters, etc.); contains the ConvNetJS model `Net.Net`, the CaffeJS model `Net.CaffeModel`, the the Volume implementation `Net.Vol` and the layers `Net.Layers` 16 | * ImgJS - An image abstraction for JavaScript to avoid dealing with canvas elements and facilitate conversion to Volumes 17 | * NumJS - An abstraction for operations on Arrays and Volumes 18 | * Parser - A parser for converting Prototxt and Protobuf binaries into JavaScript objects 19 | * Utils - An abstractions for utilities and visualizations, such as the GraphDrawer and ActivationDrawer 20 | 21 | ### Converting `*.caffemodel` files 22 | 23 | You can use the script `scripts/convert_caffemodel.py` to convert `*.caffemodel` files to binary blobs which CaffeJS can load via http and parse. Unfortunately, you need to have `Caffe` and `pycaffe` installed to generate those files. You can as well download the prepared weights via running `sh fetch_weights.sh` in the model directories. 24 | 25 | ## Deploy the docs 26 | 27 | Run `gulp deploy` to build the `docs-pages` folder and deploy it to Github. 28 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Deep Learning in the Browser 2 | 3 | To run the samples, please execute `npm start` from the root directory. Make sure you updated all dependencies by running `npm install` beforehand. 4 | 5 | ### Analyze Deep Learning structures 6 | 7 | [The sample `models.html`](https://chaosmail.github.io/caffejs/models.html) loads famous Deep Learning Models such as AlexNet, VGG, GoogLeNet, etc. directly in your browser and visualizes the layer graph. It also analyzes their structure and prints detailed information such as the network dimension, number of parameters and network size in memory to the console. 8 | 9 | Here is a break-down of **AlexNet** computed with CaffeJS. The dimensions on the left side define the output dimensions of the current layer `d, h, w`. The memory size is computed using Float32 (4 byte). 10 | 11 | ``` 12 | 3x227x227 :: INPUT data 13 | 96x55x55 :: CONV conv1 96x11x11 Stride 4 Pad 0 => 34,944 parameters 14 | 96x55x55 :: RELU relu1 15 | 96x55x55 :: LRN norm1 alpha 0.0001 beta 0.75 k 1 n 5 16 | 96x27x27 :: MAX POOL pool1 3x3 Stride 2 Pad 0 17 | 256x27x27 :: CONV conv2 256x5x5 Stride 1 Pad 2 => 614,656 parameters 18 | 256x27x27 :: RELU relu2 19 | 256x27x27 :: LRN norm2 alpha 0.0001 beta 0.75 k 1 n 5 20 | 256x13x13 :: MAX POOL pool2 3x3 Stride 2 Pad 0 21 | 384x13x13 :: CONV conv3 384x3x3 Stride 1 Pad 1 => 885,120 parameters 22 | 384x13x13 :: RELU relu3 23 | 384x13x13 :: CONV conv4 384x3x3 Stride 1 Pad 1 => 1,327,488 parameters 24 | 384x13x13 :: RELU relu4 25 | 256x13x13 :: CONV conv5 256x3x3 Stride 1 Pad 1 => 884,992 parameters 26 | 256x13x13 :: RELU relu5 27 | 256x6x6 :: MAX POOL pool5 3x3 Stride 2 Pad 0 28 | 4096x1x1 :: FC fc6 => 37,752,832 parameters 29 | 4096x1x1 :: DROPOUT drop6 30 | 4096x1x1 :: RELU relu6 31 | 4096x1x1 :: FC fc7 => 16,781,312 parameters 32 | 4096x1x1 :: DROPOUT drop7 33 | 4096x1x1 :: RELU relu7 34 | 1000x1x1 :: FC fc8 => 4,097,000 parameters 35 | 1000x1x1 :: SOFTMAX prob 36 | --- 37 | Total number of layers 24 38 | Total number of params 62,378,344 (memory: 249.513376Mb): 39 | ``` 40 | 41 | Here is a break-down of **GoogLeNet** computed with CaffeJS (please note, that the layers of each inception module are not sequential as shown here but parallel). 42 | 43 | ``` 44 | 3x224x224 :: INPUT data 45 | 64x113x113 :: CONV conv1/7x7_s2 64x7x7 Stride 2 Pad 3 => 9,472 parameters 46 | 64x113x113 :: RELU conv1/relu_7x7 47 | 64x56x56 :: MAX POOL pool1/3x3_s2 3x3 Stride 2 Pad 0 48 | 64x56x56 :: LRN pool1/norm1 alpha 0.0001 beta 0.75 k 1 n 5 49 | 64x56x56 :: CONV conv2/3x3_reduce 64x1x1 Stride 1 Pad 0 => 4,160 parameters 50 | 64x56x56 :: RELU conv2/relu_3x3_reduce 51 | 192x56x56 :: CONV conv2/3x3 192x3x3 Stride 1 Pad 1 => 110,784 parameters 52 | 192x56x56 :: RELU conv2/relu_3x3 53 | 192x56x56 :: LRN conv2/norm2 alpha 0.0001 beta 0.75 k 1 n 5 54 | 192x28x28 :: MAX POOL pool2/3x3_s2 3x3 Stride 2 Pad 0 55 | ... 56 | 1024x7x7 :: CONCAT inception_5b/output 57 | 1024x1x1 :: AVE POOL pool5/7x7_s1 7x7 Stride 1 Pad 0 58 | 1024x1x1 :: DROPOUT pool5/drop_7x7_s1 59 | 1000x1x1 :: FC loss3/classifier => 1,025,000 parameters 60 | 1000x1x1 :: SOFTMAX prob 61 | --- 62 | Total number of layers 143 63 | Total number of params 6,998,552 (memory: 27.994208Mb): 64 | ``` 65 | 66 | Please note that these samples don't load the networks' weights but only the structure with default initialization. 67 | 68 | ### Webcam Classification using GoogLeNet 69 | 70 | [The demo `webcam.html`](https://chaosmail.github.io/caffejs/webcam.html) uses the pretrained GoogLeNet model from Caffe (trained on ImageNet) to perform classification entirely in your browser using images from your webcam. 71 | 72 | ### ImageNet Classification using SqueezeNet 73 | 74 | [The demo `imagenet.html`](https://chaosmail.github.io/caffejs/imagenet.html) uses the pretrained SqueezeNet model (trained on ImageNet) to perform classification entirely in your browser using images from the ImageNet 2011 dataset. 75 | 76 | ### DeepDream 77 | 78 | [The `deepdream.html`](https://chaosmail.github.io/caffejs/deepdream.html) is a cool example that runs the famous [DeepDream](https://github.com/google/deepdream/blob/master/dream.ipynb) demo entirely in your browser - entirely in JavaScript of course. It uses the pretrained GoogLeNet from Caffe and runs the computation as webworker. -------------------------------------------------------------------------------- /docs/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CaffeJS | {% block title %}Deep Learning in the Browser{% endblock %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | {% block header %}{% endblock %} 16 | 17 | 18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 | 26 | CaffeJS 27 | 28 | 29 |
30 |
31 | 32 | 33 | Fork me on GitHub 34 |
35 | 36 | 37 |
38 | CaffeJS 39 | 52 |
53 | 54 | 55 |
56 |
57 | {% block content %}{% endblock %} 58 |
59 |
60 |
61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /docs/templates/creator.html: -------------------------------------------------------------------------------- 1 | {% set navcreator = true %} 2 | {% extends "base.html" %} 3 | 4 | {% block title %}Deep Learning Model Creator{% endblock %} 5 | 6 | {% block header %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 32 | 33 | {% endblock %} 34 | 35 | {% block content %} 36 |
37 |
38 |

Paste some Caffe mode definition here. Press `shift` + `enter` to update model

39 |
40 |
41 | 42 | 46 |
47 |
48 | 52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 |
60 | 61 | 143 | {% endblock %} -------------------------------------------------------------------------------- /docs/templates/deepdream.html: -------------------------------------------------------------------------------- 1 | {% set navdream = true %} 2 | {% extends "base.html" %} 3 | 4 | {% block title %}DeepDream{% endblock %} 5 | 6 | {% block header %} 7 | 8 | 9 | 10 | 11 | 19 | {% endblock %} 20 | 21 | {% block content %} 22 |
23 | 24 |
25 |
26 |

DeepDream in the Browser

27 |
28 |
29 |

This page runs the famous DeepDream demo entirely in the browser using the pretrained GoogLeNet model from Caffe and CaffeJS (which is based on ConvNetJS). It's a JavaScript port from the original DeepDream demo and performs the computation using a CaffeJS model in a webworker task in your browser. This page including all models and resources is hosted as a static page on Github.

30 |

Debugging this demo: Go to the Sources panel in the Chrome Developer Tools and load the demo. You should see a webworker icon entitled with deepdream_worker.js. You can click on it and set your breakpoints as usual. Additionally, you could enable DevTools for Services Workers in the Resources panel.

31 |

Mordvintsev et al., Inceptionism: Going Deeper into Neural Networks, Google Research Blog, 2015

32 |
33 |
34 | 35 | Blogpost 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 | 48 | 50 | 62 | 63 |
64 |
65 |
66 | 67 | 156 | 157 | {% endblock %} 158 | -------------------------------------------------------------------------------- /docs/templates/face.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CaffeJS Sample - Facial Expression Recognition 5 | 6 | 7 | 8 | 9 | 10 | 11 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /docs/templates/imagenet.html: -------------------------------------------------------------------------------- 1 | {% set navimagenet = true %} 2 | {% extends "base.html" %} 3 | 4 | {% block title %}ImageNet{% endblock %} 5 | 6 | {% block header %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 54 | 55 | {% endblock %} 56 | 57 | {% block content %} 58 |
59 | 60 |
61 | 62 | 63 |
64 |
65 | 66 |
67 |
68 |

ImageNet Classification

69 |
70 |
71 | Feed a picture from ImageNet into the deep neural network running entirely in your browser. 72 |
73 |
74 | 76 | 78 |
79 |
80 | 81 | 82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |

SqueezeNet

91 |
92 |
93 | Using SqueezeNet (5 MB) trained on ImageNet. 94 |
Iandola et al., SqueezeNet: AlexNet-level accuracy with 50x fewer parameters and <0.5MB model size, CoRR, 2016 95 |
96 | 102 |
103 |
104 | 105 | 127 | 128 | 135 |
136 | 137 |
138 | 139 | 246 | {% endblock %} -------------------------------------------------------------------------------- /docs/templates/models.html: -------------------------------------------------------------------------------- 1 | {% set navmodels = true %} 2 | {% extends "base.html" %} 3 | 4 | {% block title %}Deep Learning Models{% endblock %} 5 | 6 | {% block header %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | {% endblock %} 26 | 27 | {% block content %} 28 |
29 |
30 |
31 | 48 |
49 |
50 |
51 | 55 |
56 |
57 | 61 |
62 |
63 |
64 |
65 |
66 | 67 | 177 | {% endblock %} -------------------------------------------------------------------------------- /docs/templates/webcam.html: -------------------------------------------------------------------------------- 1 | {% set navwebcam = true %} 2 | {% extends "base.html" %} 3 | 4 | {% block title %}ImageNet Classification{% endblock %} 5 | 6 | {% block header %} 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 52 | 53 | {% endblock %} 54 | 55 | {% block content %} 56 |
57 | 58 |
59 | 60 | 61 |
62 |
63 |
64 |
65 |
66 |

Webcam

67 |
68 |
69 | Feed a picture from your webcam into the deep neural network running entirely in your browser. 70 |
71 |
72 | 74 |
75 |
76 | 77 | 78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |

GoogLeNet

87 |
88 |
89 | Using bvlc_googlenet (28 MB) trained on ImageNet. 90 |
Szegedy et al., Going Deeper with Convolutions, CoRR, 2014 91 |
92 | 98 |
99 | 100 |
101 | 102 | 124 | 125 | 132 |
133 | 134 |
135 | 136 | 222 | {% endblock %} -------------------------------------------------------------------------------- /docs/user-guide.md: -------------------------------------------------------------------------------- 1 | # CaffeJS - User Guide 2 | 3 | To run the samples, please execute `npm start` from the root directory. Make sure you updated all dependencies by running `npm install` beforehand. 4 | 5 | ## Running CaffeJS 6 | 7 | Clone the repository to your local drive and then start a static webserver in the root directory (e.g. run `npm start`). Now you can open `models.html` that loads and visualizes popular deep learning architectures (such as AlexNet, VGG, GoogLeNet, etc.) to analyze their structure. This loads and parses the Caffe models from `.prototxt` files and convert them on-the-fly to ConvNetJS models. 8 | 9 | To run a forward pass we need to load some pretrained model weights. First make sure you download the model weights for your particular model by running `sh fetch_weights.sh` in the model directories. This will pull and unzip the binary model weights optimized for JavaScript form my Dropbox. These weights have been generated via the `scripts/convert_caffemodel.py`. 10 | 11 | ### Loading the labels 12 | 13 | Ỳou can load the ImageNet labels from the data directory, using the following snippet. 14 | 15 | ```js 16 | var labels; 17 | d3.text('data/ilsvrc12/synset_words.txt', function(data){ 18 | labels = data.split('\n').map(function(d){ 19 | return d.substr(10); 20 | }); 21 | }); 22 | ``` 23 | 24 | ### Using the `mean.binaryproto` values 25 | 26 | To use the `mean.binaryproto` values in CaffeJS, we need to parse the binary files into Volumes. First, we need to include to Protobuf specific libraries in the header. 27 | 28 | ```html 29 | 30 | 31 | 32 | ``` 33 | 34 | Now we can use the `BlobProtoParser` to transform the binary file into a Volume. 35 | 36 | ```js 37 | var mean; 38 | var p = new Parser.BlobProtoParser(); 39 | p.parse('models/age_net/mean.binaryproto').then(function(data){ 40 | mean = data; 41 | }); 42 | ``` 43 | 44 | You can debug the mean values by rendering the Volume to the screen. Add the following line to the callback function to render the Volume to a Canvas. 45 | 46 | ```js 47 | ImgJS.Image.fromMean(mean).render(); 48 | ``` 49 | -------------------------------------------------------------------------------- /gulpconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildDir": "dist/", 3 | "paths": { 4 | "scripts": [ 5 | "src/**/*.ts" 6 | ] 7 | }, 8 | "docs": { 9 | "srcFiles": [ 10 | "docs/index.md", 11 | "docs/user-guide.md", 12 | "docs/development-guide.md" 13 | ], 14 | "exampleFiles": [ 15 | "docs/templates/models.html", 16 | "docs/templates/creator.html", 17 | "docs/templates/webcam.html", 18 | "docs/templates/imagenet.html", 19 | "docs/templates/deepdream.html" 20 | ], 21 | "tplDir": "docs/templates/", 22 | "assetDir": "docs/assets/", 23 | "baseTpl": "templates/base.html", 24 | "exampleTpl": "templates/example.html", 25 | "buildDir": "docs-pages/" 26 | } 27 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const ts = require('gulp-typescript'); 3 | const clean = require('gulp-clean'); 4 | const sourcemaps = require('gulp-sourcemaps'); 5 | const uglify = require('gulp-uglify'); 6 | const rename = require('gulp-rename'); 7 | const plumber = require('gulp-plumber'); 8 | const nunjucks = require('gulp-nunjucks'); 9 | const markdown = require('gulp-markdown'); 10 | const header = require('gulp-header'); 11 | const footer = require('gulp-footer'); 12 | const runSequence = require('run-sequence'); 13 | const karmaServer = require('karma').Server; 14 | const ghPages = require('gulp-gh-pages'); 15 | 16 | // Gulp configuration 17 | const cfg = require('./gulpconfig.json'); 18 | 19 | // Typescript configuration 20 | const tsProject = ts.createProject('tsconfig.json'); 21 | 22 | gulp.task('clean/build', () => { 23 | return gulp.src(cfg.buildDir, {read: false}) 24 | .pipe(plumber()) 25 | .pipe(clean()); 26 | }); 27 | 28 | gulp.task('clean/docs/build', () => { 29 | return gulp.src(cfg.docs.buildDir, {read: false}) 30 | .pipe(plumber()) 31 | .pipe(clean()); 32 | }); 33 | 34 | gulp.task('compile/scripts', ['clean/build'], () => { 35 | 36 | return gulp.src(cfg.paths.scripts) 37 | .pipe(plumber()) 38 | .pipe(sourcemaps.init()) 39 | .pipe(tsProject()).js 40 | .pipe(gulp.dest(cfg.buildDir)) 41 | .pipe(uglify()) 42 | .pipe(rename({suffix: '.min'})) 43 | .pipe(sourcemaps.write('.')) 44 | .pipe(gulp.dest(cfg.buildDir)); 45 | }); 46 | 47 | gulp.task('test/spec', (done) => { 48 | new karmaServer({ 49 | configFile: __dirname + '/karma.conf.js', 50 | singleRun: true 51 | }, done).start(); 52 | }); 53 | 54 | gulp.task('scripts', (callback) => { 55 | runSequence( 56 | 'clean/build', 57 | 'compile/scripts', 58 | 'test/spec', 59 | callback); 60 | }); 61 | 62 | gulp.task('copy/docs/assets', () => { 63 | return gulp.src(cfg.docs.assetDir + "**/*", {base:"docs/assets"}) 64 | .pipe(plumber()) 65 | .pipe(gulp.dest(cfg.docs.buildDir)); 66 | }); 67 | 68 | gulp.task('copy/docs/dist', () => { 69 | return gulp.src(cfg.buildDir + "**/*") 70 | .pipe(plumber()) 71 | .pipe(gulp.dest(cfg.docs.buildDir + cfg.buildDir)); 72 | }); 73 | 74 | gulp.task('compile/docs', () => { 75 | 76 | return gulp.src(cfg.docs.srcFiles) 77 | .pipe(plumber()) 78 | .pipe(markdown()) 79 | .pipe(header('{% extends "' + cfg.docs.baseTpl + '" %}\r\n{% block content %}\r\n')) 80 | .pipe(footer('{% endblock %}')) 81 | .pipe(nunjucks.compile()) 82 | .pipe(gulp.dest(cfg.docs.buildDir)); 83 | }); 84 | 85 | gulp.task('compile/examples', () => { 86 | 87 | return gulp.src(cfg.docs.exampleFiles) 88 | .pipe(plumber()) 89 | .pipe(nunjucks.compile()) 90 | .pipe(gulp.dest(cfg.docs.buildDir)); 91 | }); 92 | 93 | gulp.task('docs', (callback) => { 94 | runSequence( 95 | 'clean/docs/build', 96 | ['scripts', 'compile/docs', 'compile/examples'], 97 | ['copy/docs/dist', 'copy/docs/assets'], 98 | callback); 99 | }); 100 | 101 | gulp.task('watch', ['scripts',], () => { 102 | gulp.watch(cfg.paths.scripts, ['scripts']); 103 | }); 104 | 105 | gulp.task('deploy', ['docs'], () => { 106 | return gulp.src(cfg.docs.buildDir + '**/*') 107 | .pipe(ghPages({force: true})); 108 | }); 109 | 110 | // The default task (called when you run `gulp` from cli) 111 | gulp.task('default', ['scripts']); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Tue Sep 19 2017 19:53:41 GMT+0200 (CEST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'dist/caffe.min.js', 19 | 'test/**/*.spec.js' 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | ], 26 | 27 | 28 | // preprocess matching files before serving them to the browser 29 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 30 | preprocessors: { 31 | }, 32 | 33 | 34 | // test results reporter to use 35 | // possible values: 'dots', 'progress' 36 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 37 | reporters: ['progress'], 38 | 39 | 40 | // web server port 41 | port: 9876, 42 | 43 | 44 | // enable / disable colors in the output (reporters and logs) 45 | colors: true, 46 | 47 | 48 | // level of logging 49 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 50 | logLevel: config.LOG_INFO, 51 | 52 | 53 | // enable / disable watching file and executing tests whenever any file changes 54 | autoWatch: true, 55 | 56 | 57 | // start these browsers 58 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 59 | browsers: ['PhantomJS'], 60 | 61 | 62 | // Continuous Integration mode 63 | // if true, Karma captures browsers, runs the tests and exits 64 | singleRun: false, 65 | 66 | // Concurrency level 67 | // how many browser should be started simultaneous 68 | concurrency: Infinity 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "caffejs", 3 | "version": "0.2.0", 4 | "description": "Cool samples using ConvNetJS", 5 | "main": "dist/caffe.js", 6 | "dependencies": { 7 | "d3": "^3.5.17", 8 | "es6-shim": "^0.35.1", 9 | "queue-async": "^1.2.1", 10 | "webcamjs": "^1.0.6", 11 | "whatwg-fetch": "^2.0.3" 12 | }, 13 | "devDependencies": { 14 | "@types/d3": "^3.5.17", 15 | "@types/dagre": "^0.7.4", 16 | "@types/dagre-d3": "^0.4.17", 17 | "@types/es6-shim": "^0.31.35", 18 | "@types/whatwg-fetch": "^0.0.33", 19 | "dagre-d3": "^0.4.17", 20 | "gulp": "^3.9.1", 21 | "gulp-clean": "^0.3.2", 22 | "gulp-footer": "^1.0.5", 23 | "gulp-gh-pages": "^0.5.4", 24 | "gulp-header": "^1.8.7", 25 | "gulp-markdown": "^1.2.0", 26 | "gulp-nunjucks": "^3.0.0", 27 | "gulp-plumber": "^1.1.0", 28 | "gulp-rename": "^1.2.2", 29 | "gulp-sourcemaps": "^2.6.1", 30 | "gulp-typescript": "^3.2.2", 31 | "gulp-uglify": "^3.0.0", 32 | "http-server": "^0.10.0", 33 | "jasmine-core": "^2.8.0", 34 | "karma": "^1.7.1", 35 | "karma-chrome-launcher": "^2.2.0", 36 | "karma-jasmine": "^1.1.0", 37 | "karma-phantomjs-launcher": "^1.0.4", 38 | "nunjucks": "^3.0.1", 39 | "protobufjs": "^6.8.0", 40 | "prototxt-parser": "^0.1.3", 41 | "run-sequence": "^2.1.0", 42 | "typescript": "^2.5.2" 43 | }, 44 | "scripts": { 45 | "start": "cd docs-pages && http-server", 46 | "build": "gulp", 47 | "test": "./node_modules/karma/bin/karma start karma.conf.js" 48 | }, 49 | "keywords": [ 50 | "caffe", 51 | "convnet", 52 | "convnetjs", 53 | "deeplearning" 54 | ], 55 | "author": "Christoph Koerner", 56 | "license": "MIT" 57 | } 58 | -------------------------------------------------------------------------------- /scripts/convert_caffemodel.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import fs 3 | import json 4 | import numpy as np 5 | 6 | # Make sure that caffe and pycaffe are installed 7 | # and on the python path: 8 | caffe_root = '../../caffe/' # this file is expected to be in {caffe_root}/examples 9 | import sys 10 | sys.path.insert(0, caffe_root + 'python') 11 | 12 | import caffe 13 | 14 | dir_ = "squeezenet" 15 | proto = "deploy.prototxt" 16 | model = "squeezenet.caffemodel" 17 | 18 | # Set the right path to your model definition file, pretrained model weights, 19 | # and the image you would like to classify. 20 | MODEL_FILE = '../docs/assets/models/%s/%s' % (dir_, proto) 21 | PRETRAINED = '../docs/assets/models/%s/%s' % (dir_, model) 22 | WEIGHTS_DIR = '../docs/assets/models/%s/weights/' % dir_ 23 | 24 | # Can be either 0 for TRAIN or 1 for TEST 25 | phase = 1 26 | 27 | prev_shape = () 28 | net = caffe.Net(MODEL_FILE, PRETRAINED, phase) 29 | 30 | def rot90(W): 31 | for i in range(W.shape[0]): 32 | for j in range(W.shape[1]): 33 | W[i, j] = np.rot90(W[i, j], 2) 34 | return W 35 | 36 | for key in net.params: 37 | blobs = net.params[key] 38 | nb_filter = blobs[0].num 39 | stack_size = blobs[0].channels 40 | nb_col = blobs[0].height 41 | nb_row = blobs[0].width 42 | 43 | print("====> Layer: ", key) 44 | print("Expected Shape: ", nb_filter, stack_size, nb_col, nb_row) 45 | print("Found Shape: ", np.array(blobs[0].data).shape) 46 | 47 | weights_p = blobs[0].data.reshape((nb_filter, stack_size, nb_col, nb_row)).astype(dtype=np.float32) 48 | weights_b = blobs[1].data.astype(dtype=np.float32) 49 | 50 | if len(weights_p.shape) > 2: 51 | # Caffe uses the shape f, (d, y, x) 52 | # ConvnetJS uses the shape f, (y, x, d) 53 | weights_p = np.swapaxes(np.swapaxes(weights_p, 3, 1), 2, 1) 54 | 55 | print("Converted to Shape: ", weights_p.shape) 56 | 57 | weights = { 58 | 'filter': weights_p.reshape((nb_filter, stack_size*nb_col*nb_row)), 59 | 'bias': weights_b 60 | } 61 | 62 | filename = WEIGHTS_DIR + key + '.bin' 63 | prev_shape = (nb_filter, stack_size, nb_col, nb_row) 64 | 65 | if not fs.exists(fs.dirname(filename)): 66 | fs.mkdir(fs.dirname(filename)) 67 | 68 | with open(fs.add_suffix(filename, "_filter"), 'wb') as f: 69 | f.write(weights['filter'].astype(np.float32).tostring()) 70 | 71 | with open(fs.add_suffix(filename, "_bias"), 'wb') as f: 72 | f.write(weights['bias'].astype(np.float32).tostring()) -------------------------------------------------------------------------------- /scripts/convert_imagenet_urls.sh: -------------------------------------------------------------------------------- 1 | # Download from http://image-net.org/download-imageurls 2 | 3 | INPUT=data/fall11_urls.txt 4 | OUTPUT=data/imagenet_urls.js 5 | 6 | NUM=1000 7 | 8 | printf "var imagenet_urls = [\n" > $OUTPUT 9 | cat $INPUT | grep flickr | shuf -n $NUM | awk '{ print "{\"label\":\""$1"\",\"url\":\""$2"\"},"; }' >> $OUTPUT 10 | printf "];" >> $OUTPUT -------------------------------------------------------------------------------- /src/ImgJS/Image.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | //import {Promise} from 'es6-shim'; 5 | 6 | namespace ImgJS { 7 | 8 | // declare Promise 9 | declare var Promise: any; 10 | 11 | const nj = NumJS; 12 | 13 | // TODO 14 | // image.size = image.width, image.height 15 | // 16 | // Implement the Vol functions 17 | // image.resize() 18 | // image.roll() 19 | 20 | export class Image { 21 | 22 | data: Uint8ClampedArray; 23 | canvas: HTMLCanvasElement; 24 | image: HTMLImageElement; 25 | 26 | constructor(public src?: string){ 27 | this.canvas = document.createElement('canvas'); 28 | this.image = document.createElement('img'); 29 | } 30 | 31 | set(imgData: ImageData) { 32 | this.data = imgData.data; 33 | this.image.width = imgData.width; 34 | this.image.height = imgData.height; 35 | return this; 36 | } 37 | 38 | load() { 39 | return new Promise((resolve, reject) => { 40 | var ctx = this.canvas.getContext('2d'); 41 | this.image.onload = () => { 42 | (ctx).imageSmoothingEnabled = false; 43 | this.canvas.width = this.image.width; 44 | this.canvas.height = this.image.height; 45 | 46 | ctx.drawImage(this.image, 0, 0); 47 | var imgData = ctx.getImageData(0, 0, this.image.width, this.image.height); 48 | this.data = imgData.data; 49 | 50 | resolve(this.data); 51 | }; 52 | this.image.src = this.src + '?' + new Date().getTime(); 53 | this.image.setAttribute('crossOrigin', ''); 54 | }); 55 | } 56 | 57 | render(canvas?: HTMLCanvasElement) { 58 | if (canvas === undefined) { 59 | canvas = this.canvas; 60 | document.body.appendChild(canvas); 61 | } 62 | canvas.width = this.image.width; 63 | canvas.height = this.image.height; 64 | var ctx = canvas.getContext('2d'); 65 | (ctx).imageSmoothingEnabled = false; 66 | var img = ctx.getImageData(0, 0, this.image.width, this.image.height); 67 | img.data.set(this.data); 68 | ctx.putImageData(img, 0, 0); 69 | } 70 | 71 | static fromMean(vol: Net.Vol, depth = 0, scale = 1, normalize = false) { 72 | return Image.fromVol(vol, 0, [0,1,2], scale, normalize); 73 | } 74 | 75 | static fromFilter(vol: Net.Vol, depth = 0, scale = 1, normalize = true) { 76 | return Image.fromVol(vol, 0, depth, scale, normalize); 77 | } 78 | 79 | // Caffe uses OpenCV to load JPEGs and leaves them in their default BGR order 80 | // hence we need to also convert them back to RGB 81 | static fromVol(vol: Net.Vol, mean: any, channel: number | number[] = [2,1,0], scale = 1, normalize = false, aplha = 255){ 82 | var img = new Image(); 83 | mean = mean !== undefined ? mean : [0, 0, 0]; 84 | 85 | var w = vol.sx; 86 | var h = vol.sy; 87 | var n = w*h*4; 88 | var mm = nj.maxmin(vol.w); 89 | 90 | var c0 = channel ? channel instanceof Array ? channel[0] : +channel : 0; 91 | var c1 = channel ? channel instanceof Array ? channel[1] : +channel : 0; 92 | var c2 = channel ? channel instanceof Array ? channel[2] : +channel : 0; 93 | 94 | var data = new Uint8ClampedArray(n); 95 | 96 | for (let y=0; y < h; y++){ 97 | for (let x=0; x < w; x++){ 98 | let pp = (y*w + x) * 4; 99 | let mean_0 = mean ? mean instanceof Net.Vol ? mean.get(x,y,c0) : +mean[c0] : 0; 100 | let mean_1 = mean ? mean instanceof Net.Vol ? mean.get(x,y,c1) : +mean[c1] : 0; 101 | let mean_2 = mean ? mean instanceof Net.Vol ? mean.get(x,y,c2) : +mean[c2] : 0; 102 | let dval_0 = vol.get(x, y, c0); 103 | let dval_1 = vol.get(x, y, c1); 104 | let dval_2 = vol.get(x, y, c2); 105 | if (normalize) { 106 | dval_0 = Math.floor((vol.get(x, y, c0) - mm.minv) / mm.dv * 255); 107 | dval_1 = Math.floor((vol.get(x, y, c1) - mm.minv) / mm.dv * 255); 108 | dval_2 = Math.floor((vol.get(x, y, c2) - mm.minv) / mm.dv * 255); 109 | } 110 | data[pp + 0] = dval_0 + mean_0; 111 | data[pp + 1] = dval_1 + mean_1; 112 | data[pp + 2] = dval_2 + mean_2; 113 | data[pp + 3] = aplha; 114 | } 115 | } 116 | img.image.width = w; 117 | img.image.height = h; 118 | img.data = data; 119 | return img; 120 | } 121 | 122 | // Caffe uses OpenCV to load JPEGs and leaves them in their default BGR order 123 | // hence we need to also convert to BGR order 124 | // Also mean should be provided in this format 125 | toVol(mean: any, channel = [2,1,0]) { 126 | mean = mean !== undefined ? mean : [0, 0, 0]; 127 | var w = this.image.width; 128 | var h = this.image.height; 129 | var c0 = channel[0]; 130 | var c1 = channel[1]; 131 | var c2 = channel[2]; 132 | var vol = new Net.Vol(w, h, 3, 0.0); 133 | for (var y=0; y < h; y++){ 134 | for (var x=0; x < w; x++){ 135 | var pp = (y*w + x) * 4; 136 | var mean_0 = mean instanceof Net.Vol ? mean.get(x,y,c0) : +mean[c0]; 137 | var mean_1 = mean instanceof Net.Vol ? mean.get(x,y,c1) : +mean[c1]; 138 | var mean_2 = mean instanceof Net.Vol ? mean.get(x,y,c2) : +mean[c2]; 139 | vol.set(x, y, c0, this.data[pp + 0] - mean_0); 140 | vol.set(x, y, c1, this.data[pp + 1] - mean_1); 141 | vol.set(x, y, c2, this.data[pp + 2] - mean_2); 142 | } 143 | } 144 | return vol; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/ImgJS/_module.ts: -------------------------------------------------------------------------------- 1 | /// -------------------------------------------------------------------------------- /src/Net/CaffeModel.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // import * as d3 from 'd3'; 5 | 6 | namespace Net { 7 | 8 | // declare variables 9 | declare var d3: any; 10 | declare var Promise: any; 11 | 12 | interface IEdge { 13 | from: string; 14 | to: string; 15 | } 16 | 17 | export class CaffeModel extends Model { 18 | 19 | public name: string; 20 | 21 | constructor(private modelPath?: string, private weightPath?: string) { 22 | super(); 23 | } 24 | 25 | load() { 26 | return this.fetch(this.modelPath) 27 | .then((model) => this.create(model)) 28 | .then((model) => this.loadWeights()); 29 | } 30 | 31 | fromText(def: string) { 32 | var protoParser = new Parser.PrototxtParser(); 33 | this.create(protoParser.parseString(def)) 34 | return this; 35 | } 36 | 37 | fetch(url: string) { 38 | var protoParser = new Parser.PrototxtParser(); 39 | return protoParser.parse(url); 40 | } 41 | 42 | create(model: any) { 43 | this.name = model.name; 44 | 45 | this.createLayers(model, model.layer || model.layers, model.input === 'data'); 46 | this.createEdges(); 47 | } 48 | 49 | caffeLayerToJs(layerOpt: any): ILayer { 50 | 51 | var layer: ILayer; 52 | var opt: any = { name: layerOpt.name, input: layerOpt.bottom, output: layerOpt.top }; 53 | 54 | // Get predecessors of the current layers 55 | if (layerOpt.bottom !== undefined){ 56 | if (!Array.isArray(layerOpt.bottom)) { 57 | opt.pred = [this.layers.get(layerOpt.bottom)]; 58 | } 59 | else { 60 | opt.pred = layerOpt.bottom.map((d) => this.layers.get(d)); 61 | } 62 | } 63 | 64 | switch (layerOpt.type.toLowerCase()) { 65 | 66 | case 'input': 67 | var p = layerOpt.input_param || {}; 68 | opt.out_depth = +p.shape.dim[1]; 69 | opt.out_sx = +p.shape.dim[2]; 70 | opt.out_sy = +p.shape.dim[3]; 71 | layer = new Layers.InputLayer(opt); 72 | break; 73 | 74 | case 'conv': 75 | case 'convolution': 76 | var p = layerOpt.param || {}; 77 | var cp = layerOpt.convolution_param || {}; 78 | opt.sx = cp.kernel_size !== undefined ? +cp.kernel_size : undefined; 79 | opt.filters = cp.num_output !== undefined ? +cp.num_output : undefined; 80 | opt.pad = cp.pad !== undefined ? +cp.pad : undefined; 81 | opt.stride = cp.stride !== undefined ? +cp.stride : undefined; 82 | opt.l1_decay_mul = p && p.length && p[0].decay_mult !== undefined ? +p[0].decay_mult : 0.0; 83 | opt.l2_decay_mul = p && p.length && p[1].decay_mult !== undefined ? +p[1].decay_mult : 1.0; 84 | opt.group = cp.group !== undefined ? +cp.group : 1; 85 | layer = new Layers.ConvLayer(opt); 86 | break; 87 | 88 | case 'lrn': 89 | var p = layerOpt.lrn_param || {}; 90 | opt.k = p.k !== undefined ? +p.k : 1; 91 | opt.n = p.local_size !== undefined ? +p.local_size : undefined; 92 | opt.alpha = p.alpha !== undefined ? +p.alpha : undefined; 93 | opt.beta = p.beta !== undefined ? +p.beta : undefined; 94 | layer = new Layers.LocalResponseNormalizationLayer(opt); 95 | break; 96 | 97 | case 'dropout': 98 | var dp = layerOpt.dropout_param || {}; 99 | opt.drop_prob = dp.dropout_ratio !== undefined ? +dp.dropout_ratio : undefined; 100 | layer = new Layers.DropoutLayer(opt); 101 | break; 102 | 103 | case 'concat': 104 | var cp = layerOpt.concat_param || {}; 105 | opt.axis = cp.axis !== undefined ? +cp.axis : undefined; 106 | layer = new Layers.ConcatLayer(opt); 107 | break; 108 | 109 | case 'pool': 110 | case 'pooling': 111 | var pp = layerOpt.pooling_param || {}; 112 | opt.pool = pp.pool !== undefined ? pp.pool : undefined; 113 | opt.sx = pp.kernel_size !== undefined ? +pp.kernel_size : undefined; 114 | opt.pad = pp.pad !== undefined ? +pp.pad : undefined; 115 | opt.stride = pp.stride !== undefined ? +pp.stride : undefined; 116 | opt.global_pooling = pp.global_pooling !== undefined && pp.global_pooling !== 'false' ? true : false; 117 | layer = new Layers.PoolLayer(opt); 118 | break; 119 | 120 | case 'inner_product': 121 | case 'innerproduct': 122 | var pp = layerOpt.inner_product_param || {}; 123 | var p = layerOpt.param || {}; 124 | opt.num_neurons = pp.num_output !== undefined ? +pp.num_output : undefined; 125 | opt.l1_decay_mul = p && p.length && p[0].decay_mult !== undefined ? +p[0].decay_mult : 0.0; 126 | opt.l2_decay_mul = p && p.length && p[1].decay_mult !== undefined ? +p[1].decay_mult : 1.0; 127 | layer = new Layers.FullyConnectedLayer(opt); 128 | break; 129 | 130 | case 'softmax': layer = new Layers.SoftmaxLayer(opt); break; 131 | case 'relu': layer = new Layers.ReluLayer(opt); break; 132 | case 'sigmoid': layer = new Layers.SigmoidLayer(opt); break; 133 | case 'tanh': layer = new Layers.TanhLayer(opt); break; 134 | 135 | default: 136 | console.error('Cannot parse layer ' + layerOpt.type, layerOpt); 137 | return; 138 | } 139 | 140 | this.layers.set(layer.name, layer); 141 | } 142 | 143 | createLayers(model: any, layers: any, makeInput: boolean = false) { 144 | this.layers = d3.map(); 145 | 146 | // Create Input layer manually 147 | if (makeInput) { 148 | this.layers.set('data', new Layers.InputLayer({ 149 | name: 'data', 150 | in_depth: +model.input_dim[1], 151 | in_sy: +model.input_dim[2], 152 | in_sx: +model.input_dim[3], 153 | }) 154 | ); 155 | } 156 | 157 | // Create all other layers 158 | layers.forEach((d) => this.caffeLayerToJs(d)); 159 | } 160 | 161 | createEdges() { 162 | this.edges = []; 163 | 164 | let edgeSet = d3.set(); 165 | let getEdgeId = (a, b) => a + ":#:" + b; 166 | 167 | this.layers.values() 168 | .filter((d: any) => d.input !== undefined && d.input !== d.output) 169 | .forEach((d: any) => { 170 | if (!Array.isArray(d.input)) { 171 | this.edges.push({ from: d.input, to: d.output }); 172 | } 173 | else { 174 | d.input.forEach((layerName: string) => { 175 | if (!edgeSet.has(getEdgeId(layerName, d.output))) { 176 | this.edges.push({ from: layerName, to: d.output }); 177 | edgeSet.add(getEdgeId(layerName, d.output)); 178 | } 179 | }); 180 | } 181 | }); 182 | 183 | // Parse self-loops in Caffe 184 | // (usually for ReLU layers due to performance reasons) 185 | // To make this library more efficient, 186 | // we should allow and implement these self loops 187 | this.layers.values() 188 | .filter((d: any) => d.input !== undefined && d.input === d.output) 189 | .forEach((d: any) => { 190 | this.edges 191 | .filter((edge: IEdge) => edge.from === d.input) 192 | .forEach((edge: IEdge) => { 193 | edge.from = d.name; 194 | if (!edgeSet.has(getEdgeId(d.input, d.name))) { 195 | this.edges.push({ from: d.input, to: d.name }); 196 | edgeSet.add(getEdgeId(d.input, d.name)); 197 | } 198 | }) 199 | }); 200 | } 201 | 202 | loadWeights(): any { 203 | if (!this.weightPath) { 204 | return Promise.resolve(); 205 | } 206 | // Load all separate weights for the layers 207 | return Promise.all(this.layers.values() 208 | .filter((d) => d.layer_type == 'conv' || d.layer_type == 'fc') 209 | .map((layer: any) => { 210 | return Promise.all([ 211 | fetch(this.weightPath + layer.name + '_filter.bin') 212 | .then((response) => response.arrayBuffer()) 213 | .then((arrayBuffer) => { 214 | var f = new Float32Array(arrayBuffer); 215 | var n = layer.num_inputs === undefined 216 | ? Math.ceil(layer.sx * layer.sy * layer.in_depth / layer.conv_groups) 217 | : layer.num_inputs; 218 | for(var i=0; i response.arrayBuffer()) 224 | .then((arrayBuffer) => { 225 | var f = new Float32Array(arrayBuffer); 226 | layer.biases.w.set(f); 227 | }) 228 | ]); 229 | })); 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/Net/ILayer.ts: -------------------------------------------------------------------------------- 1 | namespace Net { 2 | 3 | export interface ILayer { 4 | 5 | in_sx: number; 6 | in_sy: number; 7 | in_depth: number; 8 | 9 | out_sx: number; 10 | out_sy: number; 11 | out_depth: number; 12 | 13 | name: string; 14 | layer_type: string; 15 | input: string; 16 | output: string; 17 | 18 | in_act: Vol | Vol[]; 19 | out_act: Vol; 20 | 21 | getNumParameters(): number[]; 22 | getOutputShape(): number[]; 23 | getDescription(): string[]; 24 | updateDimensions(pred?: ILayer[]); 25 | forward(V: Vol, is_training: boolean); 26 | backward(y?: Vol); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Net/Layers/BaseLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | 6 | namespace Net.Layers { 7 | 8 | const nj = NumJS; 9 | 10 | export class BaseLayer { 11 | public in_act: Vol | Vol[]; 12 | public out_act: Vol; 13 | 14 | public in_depth: number = 1; 15 | public in_sy: number = 1; 16 | public in_sx: number = 1; 17 | 18 | public out_depth: number = 1; 19 | public out_sy: number = 1; 20 | public out_sx: number = 1; 21 | 22 | public name: string; 23 | public input: string; 24 | public output: string; 25 | public layer_type: string; 26 | 27 | constructor(opt: any) { 28 | this.name = opt.name !== undefined ? opt.name : ""; 29 | this.input = opt.input !== undefined ? opt.input : undefined; 30 | this.output = opt.output !== undefined ? opt.output : undefined; 31 | 32 | if (!opt.pred) { 33 | this.in_sx = opt.in_sx; 34 | this.in_sy = opt.in_sy; 35 | this.in_depth = opt.in_depth; 36 | } 37 | } 38 | 39 | updateDimensions(pred?: ILayer[]) { 40 | 41 | if (pred){ 42 | this.in_sx = pred[0].out_sx; 43 | this.in_sy = pred[0].out_sy; 44 | this.in_depth = pred[0].out_depth; 45 | } 46 | 47 | this.out_sx = this.in_sx; 48 | this.out_sy = this.in_sy; 49 | this.out_depth = this.in_depth; 50 | } 51 | 52 | resetGradient() { 53 | if (this.in_act instanceof Array) { 54 | for (var j = 0; j < (this.in_act).length; j++) { 55 | (this.in_act)[j].dw = nj.zeros((this.in_act)[j].w.length); 56 | } 57 | } 58 | else { 59 | (this.in_act).dw = nj.zeros((this.in_act).w.length); 60 | } 61 | } 62 | 63 | getNumParameters() { 64 | return [0, 0]; 65 | } 66 | 67 | getOutputShape() { 68 | if (this.in_sy && this.in_sx) { 69 | return [this.in_depth, this.in_sy, this.in_sx]; 70 | } 71 | return [this.in_depth]; 72 | } 73 | 74 | getDescription(){ 75 | return [this.layer_type.toUpperCase(), this.name]; 76 | } 77 | 78 | getParamsAndGrads() { 79 | return []; 80 | } 81 | 82 | toJSON() { 83 | var json: any = {}; 84 | json.out_depth = this.out_depth; 85 | json.out_sx = this.out_sx; 86 | json.out_sy = this.out_sy; 87 | json.layer_type = this.layer_type; 88 | json.name = this.name; 89 | json.output = this.output; 90 | json.input = this.input; 91 | return json; 92 | } 93 | 94 | fromJSON(json: any) { 95 | this.out_depth = json.out_depth; 96 | this.out_sx = json.out_sx; 97 | this.out_sy = json.out_sy; 98 | this.layer_type = json.layer_type; 99 | this.name = json.name; 100 | this.output = json.output; 101 | this.input = json.input; 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/Net/Layers/ConcatLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | export class ConcatLayer extends BaseLayer implements ILayer { 8 | 9 | public layer_type: string = 'concat'; 10 | 11 | public in_act: Vol[]; 12 | public out_act: Vol; 13 | 14 | public axis: number; 15 | 16 | constructor(opt) { 17 | super(opt || {}); 18 | 19 | this.axis = getopt(opt, ['axis'], 1); 20 | 21 | this.updateDimensions(opt.pred); 22 | } 23 | 24 | forward(Vs, is_training) { 25 | this.in_act = Vs; 26 | this.resetGradient(); 27 | var V2 = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); 28 | var offset = 0; 29 | if (this.axis === 0) { 30 | var V2w = V2.w; 31 | for (var j = 0; j < Vs.length; j++) { 32 | V2w.set(Vs[j].w, offset); 33 | offset += Vs[j].w.length; 34 | } 35 | } 36 | else { 37 | for (let j = 0; j < Vs.length; j++) { 38 | let V = Vs[j]; 39 | for (let d = 0; d < V.depth; d++) 40 | for (let x = 0; x < V.sx; x++) { 41 | for (let y = 0; y < V.sy; y++) { 42 | V2.set(x, y, d + offset, V.get(x, y, d)); 43 | } 44 | } 45 | offset += V.depth; 46 | } 47 | } 48 | this.out_act = V2; 49 | return this.out_act; 50 | } 51 | 52 | backward() { 53 | var Vs = this.in_act; // we need to set dw of these 54 | var V2 = this.out_act; 55 | var offset = 0; 56 | if (this.axis === 0) { 57 | let V2dw = V2.dw; 58 | for (let j = 0; j < Vs.length; j++) { 59 | let Vdw = Vs[j].dw; 60 | Vdw = nj.add(Vdw, V2dw.slice(offset, offset + Vdw.length)); 61 | offset += Vdw.length; 62 | } 63 | } 64 | else { 65 | for (let j = 0, len = Vs.length; j < len; ++j) { 66 | let V = Vs[j]; 67 | let Vdw = Vs[j].dw; 68 | for (let d = 0, depth = V.depth; d < depth; ++d) 69 | for (let x = 0, sx = V.sx; x < sx; ++x) { 70 | for (let y = 0, sy = V.sy; y < sy; ++y) { 71 | V.add_grad(x, y, d, V2.get_grad(x, y, d + offset)); 72 | } 73 | } 74 | offset += V.depth; 75 | } 76 | } 77 | } 78 | 79 | updateDimensions(pred: ILayer[]) { 80 | if (pred) { 81 | // concatenation along num 82 | // (n_1 + n_2 + ... + n_K) * c_1 * h * w, and all input c_i should be the same. 83 | if (this.axis == 0) { 84 | this.in_sx = pred[0].in_sx; 85 | this.in_sy = pred[0].in_sy; 86 | this.in_depth = pred[0].in_depth; 87 | } 88 | // concatenate along channels 89 | // n_1 * (c_1 + c_2 + ... + c_K) * h * w, and all input n_i should be the same 90 | else { 91 | this.in_sx = pred[0].in_sx; 92 | this.in_sy = pred[0].in_sy; 93 | this.in_depth = nj.sum(pred.map((d) => d.out_depth)); 94 | } 95 | } 96 | 97 | this.out_sx = this.in_sx; 98 | this.out_sy = this.in_sy; 99 | this.out_depth = this.in_depth; 100 | } 101 | 102 | toJSON() { 103 | var json: any = super.toJSON(); 104 | json.axis = this.axis; 105 | return json; 106 | } 107 | 108 | fromJSON(json:any) { 109 | super.fromJSON(json); 110 | this.axis = json.axis; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Net/Layers/ConvLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | export class ConvLayer extends BaseLayer implements ILayer { 8 | 9 | public layer_type: string = 'conv'; 10 | 11 | public in_act: Vol; 12 | public out_act: Vol; 13 | 14 | public sx: number; 15 | public sy: number; 16 | public stride: number; 17 | public pad: number; 18 | public l1_decay_mul: number; 19 | public l2_decay_mul: number; 20 | public conv_groups: number; 21 | 22 | public biases: Vol; 23 | public filters: Vol[]; 24 | 25 | constructor(opt) { 26 | super(opt || {}); 27 | 28 | // required 29 | this.out_depth = opt.filters; 30 | 31 | // filter size. Should be odd if possible, it's cleaner. 32 | this.sx = opt.sx; 33 | 34 | // optional 35 | this.sy = getopt(opt, ['sy'], this.sx); 36 | 37 | // stride at which we apply filters to input volume 38 | this.stride = getopt(opt, ['stride'], 1); 39 | 40 | // amount of 0 padding to add around borders of input volume 41 | this.pad = getopt(opt, ['pad'], 0); 42 | 43 | // Convolution groups from AlexNet 44 | this.conv_groups = getopt(opt, ['group'], 1); 45 | 46 | this.l1_decay_mul = getopt(opt, ['l1_decay_mul'], 0.0); 47 | this.l2_decay_mul = getopt(opt, ['l2_decay_mul'], 1.0); 48 | 49 | this.updateDimensions(opt.pred); 50 | 51 | // initialize bias 52 | var bias = getopt(opt, ['bias_pref'], 0.0); 53 | this.biases = new Vol(1, 1, this.out_depth, bias); 54 | 55 | // initialize filters 56 | this.filters = []; 57 | var f_depth = Math.ceil(this.in_depth / this.conv_groups); 58 | for (let i = 0; i < this.out_depth; ++i) { 59 | this.filters.push(new Vol(this.sx, this.sy, f_depth, 0.0)); 60 | } 61 | } 62 | 63 | resetGradient() { 64 | super.resetGradient(); 65 | 66 | for (let i = 0; i < this.out_depth; ++i) { 67 | this.filters[i].dw = nj.zeros((this.filters[i]).w.length); 68 | } 69 | this.biases.dw = nj.zeros(this.out_depth); 70 | } 71 | 72 | forward(V, is_training) { 73 | // optimized code by @mdda that achieves 2x speedup over previous version 74 | 75 | this.in_act = V; 76 | this.resetGradient(); 77 | var A = new Vol(this.out_sx | 0, this.out_sy | 0, this.out_depth | 0, 0.0); 78 | 79 | var V_sx = V.sx | 0; 80 | var V_sy = V.sy | 0; 81 | var xy_stride = this.stride | 0; 82 | var f_depth = Math.ceil(this.out_depth / this.conv_groups); 83 | 84 | for (var g = 0; g < this.conv_groups; ++g) { 85 | var f_start = g * f_depth; 86 | var f_end = f_start + f_depth; 87 | for (var d = f_start; d < f_end; ++d) { 88 | var f = this.filters[d]; 89 | var x = -this.pad | 0; 90 | var y = -this.pad | 0; 91 | for (var ay = 0; ay < this.out_sy; y += xy_stride, ++ay) { // xy_stride 92 | x = -this.pad | 0; 93 | for (var ax = 0; ax < this.out_sx; x += xy_stride, ++ax) { // xy_stride 94 | 95 | // convolve centered at this particular location 96 | var a = 0.0; 97 | for (var fy = 0; fy < f.sy; ++fy) { 98 | var oy = y + fy; // coordinates in the original input array coordinates 99 | for (var fx = 0; fx < f.sx; ++fx) { 100 | var ox = x + fx; 101 | if (oy >= 0 && oy < V_sy && ox >= 0 && ox < V_sx) { 102 | for (var fd = 0; fd < f.depth; ++fd) { 103 | // avoid function call overhead (x2) for efficiency, compromise modularity :( 104 | a += f.w[((f.sx * fy) + fx) * f.depth + fd] * V.w[((V_sx * oy) + ox) * V.depth * (g+1) + fd]; 105 | } 106 | } 107 | } 108 | } 109 | a += this.biases.w[d]; 110 | A.set(ax, ay, d, a); 111 | } 112 | } 113 | } 114 | } 115 | this.out_act = A; 116 | return this.out_act; 117 | } 118 | 119 | backward() { 120 | var V = this.in_act; 121 | var V_sx = V.sx | 0; 122 | var V_sy = V.sy | 0; 123 | var xy_stride = this.stride | 0; 124 | var group_depth = Math.ceil(this.out_depth / this.conv_groups); 125 | 126 | for (var g = 0; g < this.conv_groups; ++g) { 127 | var f_start = g * group_depth; 128 | var f_end = f_start + group_depth; 129 | for (var d = f_start; d < f_end; ++d) { 130 | var f = this.filters[d]; 131 | var x = -this.pad | 0; 132 | var y = -this.pad | 0; 133 | for (var ay = 0; ay < this.out_sy; y += xy_stride, ++ay) { // xy_stride 134 | x = -this.pad | 0; 135 | for (var ax = 0; ax < this.out_sx; x += xy_stride, ++ax) { // xy_stride 136 | 137 | // convolve centered at this particular location 138 | var chain_grad = this.out_act.get_grad(ax, ay, d); // gradient from above, from chain rule 139 | for (var fy = 0; fy < f.sy; ++fy) { 140 | var oy = y + fy; // coordinates in the original input array coordinates 141 | for (var fx = 0; fx < f.sx; ++fx) { 142 | var ox = x + fx; 143 | if (oy >= 0 && oy < V_sy && ox >= 0 && ox < V_sx) { 144 | for (var fd = 0; fd < f.depth; ++fd) { 145 | // avoid function call overhead (x2) for efficiency, compromise modularity :( 146 | var ix1 = ((V_sx * oy) + ox) * V.depth * (g+1) + fd; 147 | var ix2 = ((f.sx * fy) + fx) * f.depth + fd; 148 | f.dw[ix2] += V.w[ix1] * chain_grad; 149 | V.dw[ix1] += f.w[ix2] * chain_grad; 150 | } 151 | } 152 | } 153 | } 154 | this.biases.dw[d] += chain_grad; 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | getParamsAndGrads() { 162 | var response = []; 163 | for (var i = 0; i < this.out_depth; i++) { 164 | response.push({ 165 | params: this.filters[i].w, 166 | grads: this.filters[i].dw, 167 | l2_decay_mul: this.l2_decay_mul, 168 | l1_decay_mul: this.l1_decay_mul 169 | }); 170 | } 171 | response.push({ 172 | params: this.biases.w, 173 | grads: this.biases.dw, 174 | l1_decay_mul: 0.0, 175 | l2_decay_mul: 0.0 176 | }); 177 | return response; 178 | } 179 | 180 | updateDimensions(pred: ILayer[]) { 181 | if (pred){ 182 | this.in_sx = pred[0].out_sx; 183 | this.in_sy = pred[0].out_sy; 184 | this.in_depth = pred[0].out_depth; 185 | } 186 | 187 | var s = this.getOutputShape(); 188 | this.out_sx = s[1]; 189 | this.out_sy = s[2]; 190 | } 191 | 192 | getNumParameters() { 193 | return [ 194 | Math.ceil(this.in_depth * this.sx * this.sy * this.out_depth / this.conv_groups), 195 | this.out_depth 196 | ]; 197 | } 198 | 199 | getOutputShape() { 200 | return [ 201 | this.out_depth, 202 | Math.round((this.in_sx + this.pad * 2 - this.sx) / this.stride + 1), 203 | Math.round((this.in_sy + this.pad * 2 - this.sy) / this.stride + 1), 204 | ] 205 | } 206 | 207 | getDescription() { 208 | return super.getDescription().concat([ 209 | [this.out_depth, this.sy, this.sx].join('x') + ' stride=' + this.stride + ' pad=' + this.pad 210 | ]); 211 | } 212 | 213 | toJSON() { 214 | var json: any = super.toJSON(); 215 | json.sx = this.sx; // filter size in x, y dims 216 | json.sy = this.sy; 217 | json.stride = this.stride; 218 | json.in_depth = this.in_depth; 219 | json.l1_decay_mul = this.l1_decay_mul; 220 | json.l2_decay_mul = this.l2_decay_mul; 221 | json.pad = this.pad; 222 | json.filters = []; 223 | for (var i = 0; i < this.filters.length; i++) { 224 | json.filters.push(this.filters[i].toJSON()); 225 | } 226 | json.biases = this.biases.toJSON(); 227 | return json; 228 | } 229 | 230 | fromJSON(json: any) { 231 | super.fromJSON(json); 232 | this.sx = json.sx; // filter size in x, y dims 233 | this.sy = json.sy; 234 | this.stride = json.stride; 235 | this.in_depth = json.in_depth; // depth of input volume 236 | this.filters = []; 237 | this.l1_decay_mul = json.l1_decay_mul !== undefined ? json.l1_decay_mul : 0.0; 238 | this.l2_decay_mul = json.l2_decay_mul !== undefined ? json.l2_decay_mul : 1.0; 239 | this.pad = json.pad !== undefined ? json.pad : 0; 240 | for (var i = 0; i < json.filters.length; i++) { 241 | this.filters.push(Vol.fromJSON(json.filters[i])); 242 | } 243 | this.biases = Vol.fromJSON(json.biases); 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/Net/Layers/DropoutLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // An inefficient dropout layer 8 | // Note this is not most efficient implementation since the layer before 9 | // computed all these activations and now we're just going to drop them :( 10 | // same goes for backward pass. Also, if we wanted to be efficient at test time 11 | // we could equivalently be clever and upscale during train and copy pointers during test 12 | // todo: make more efficient. 13 | export class DropoutLayer extends BaseLayer implements ILayer { 14 | 15 | public layer_type: string = 'dropout'; 16 | 17 | public in_act: Vol; 18 | public out_act: Vol; 19 | 20 | public drop_prob: number; 21 | public dropped: Int8Array; 22 | 23 | constructor(opt) { 24 | super(opt || {}); 25 | 26 | this.drop_prob = getopt(opt, ['drop_prob'], 0.5); 27 | 28 | this.updateDimensions(opt.pred); 29 | } 30 | 31 | forward(V, is_training = false) { 32 | this.in_act = V; 33 | this.resetGradient(); 34 | this.dropped = nj.zeros(this.out_sx * this.out_sy * this.out_depth, Int8Array); 35 | var V2 = V.clone(); 36 | var N = V.w.length; 37 | if (is_training) { 38 | // do dropout 39 | for (var i = 0; i < N; i++) { 40 | // drop! 41 | if (Math.random() < this.drop_prob) { 42 | V2.w[i] = 0; 43 | this.dropped[i] = 1; 44 | } 45 | else { 46 | // scale the activations during training 47 | V2.w[i] *= this.drop_prob; 48 | } 49 | } 50 | } 51 | this.out_act = V2; 52 | return this.out_act; // dummy identity function for now 53 | } 54 | 55 | backward() { 56 | var V = this.in_act; // we need to set dw of this 57 | var chain_grad = this.out_act; 58 | var N = V.w.length; 59 | for (var i = 0; i < N; i++) { 60 | if (this.dropped[i] !== 1) { 61 | V.dw[i] += chain_grad.dw[i]; // copy over the gradient 62 | } 63 | } 64 | } 65 | 66 | toJSON() { 67 | var json: any = super.toJSON(); 68 | json.drop_prob = this.drop_prob; 69 | return json; 70 | } 71 | 72 | fromJSON(json: any) { 73 | super.fromJSON(json); 74 | this.drop_prob = json.drop_prob; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Net/Layers/FullyConnectedLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | export class FullyConnectedLayer extends BaseLayer implements ILayer { 8 | 9 | public layer_type: string = 'fc'; 10 | 11 | public in_act: Vol; 12 | public out_act: Vol; 13 | 14 | public sx: number = 1; 15 | public sy: number = 1; 16 | 17 | public num_inputs: number; 18 | public l1_decay_mul: number; 19 | public l2_decay_mul: number; 20 | 21 | public conv_groups:number = 1; 22 | 23 | public biases: Vol; 24 | public filters: Vol[]; 25 | 26 | constructor(opt) { 27 | super(opt || {}); 28 | 29 | // required 30 | // ok fine we will allow 'filters' as the word as well 31 | this.out_depth = opt.num_neurons !== undefined ? opt.num_neurons : opt.filters; 32 | 33 | // optional 34 | this.l1_decay_mul = getopt(opt, ['l1_decay_mul'], 0.0); 35 | this.l2_decay_mul = getopt(opt, ['l2_decay_mul'], 1.0); 36 | 37 | this.updateDimensions(opt.pred); 38 | 39 | // initialize bias 40 | var bias = getopt(opt, ['bias_pref'], 0.0); 41 | this.biases = new Vol(1, 1, this.out_depth, bias); 42 | 43 | // initialize filters 44 | this.filters = []; 45 | for (let i = 0; i < this.out_depth; ++i) { 46 | this.filters.push(new Vol(1, 1, this.num_inputs, 0.0)); 47 | } 48 | 49 | if (opt.filters !== undefined) { 50 | for (let i = 0; i < this.out_depth; ++i) { 51 | this.filters[i].w.set(opt.filters[i]); 52 | } 53 | } 54 | 55 | if (opt.biases !== undefined) { 56 | this.biases.w.set(opt.biases); 57 | } 58 | } 59 | 60 | resetGradient() { 61 | super.resetGradient(); 62 | 63 | for (let i = 0; i < this.out_depth; ++i) { 64 | this.filters[i].dw = nj.zeros((this.filters[i]).w.length); 65 | } 66 | this.biases.dw = nj.zeros(this.out_depth); 67 | } 68 | 69 | forward(V, is_training) { 70 | this.in_act = V; 71 | this.resetGradient(); 72 | var A = new Vol(1, 1, this.out_depth, 0.0); 73 | var Vw = V.w; 74 | for (var i = 0; i < this.out_depth; ++i) { 75 | var a = 0.0; 76 | var wi = this.filters[i].w; 77 | for (var d = 0; d < this.num_inputs; ++d) { 78 | a += Vw[d] * wi[d]; // for efficiency use Vols directly for now 79 | } 80 | a += this.biases.w[i]; 81 | A.w[i] = a; 82 | } 83 | this.out_act = A; 84 | return this.out_act; 85 | } 86 | 87 | backward() { 88 | var V = this.in_act; 89 | 90 | // compute gradient wrt weights and data 91 | for (var i = 0; i < this.out_depth; ++i) { 92 | var tfi = this.filters[i]; 93 | var chain_grad = this.out_act.dw[i]; 94 | for (var d = 0; d < this.num_inputs; ++d) { 95 | V.dw[d] += tfi.w[d] * chain_grad; // grad wrt input data 96 | tfi.dw[d] += V.w[d] * chain_grad; // grad wrt params 97 | } 98 | this.biases.dw[i] += chain_grad; 99 | } 100 | } 101 | 102 | getParamsAndGrads() { 103 | var response = []; 104 | for (var i = 0; i < this.out_depth; ++i) { 105 | response.push({ 106 | params: this.filters[i].w, 107 | grads: this.filters[i].dw, 108 | l1_decay_mul: this.l1_decay_mul, 109 | l2_decay_mul: this.l2_decay_mul 110 | }); 111 | } 112 | response.push({ 113 | params: this.biases.w, 114 | grads: this.biases.dw, 115 | l1_decay_mul: 0.0, 116 | l2_decay_mul: 0.0 117 | }); 118 | return response; 119 | } 120 | 121 | updateDimensions(pred: ILayer[]) { 122 | if (pred){ 123 | this.in_sx = pred[0].out_sx; 124 | this.in_sy = pred[0].out_sy; 125 | this.in_depth = pred[0].out_depth; 126 | } 127 | 128 | this.num_inputs = this.in_sx * this.in_sy * this.in_depth; 129 | } 130 | 131 | getNumParameters() { 132 | return [this.in_depth * this.in_sx * this.in_sy * this.out_depth, this.out_depth]; 133 | } 134 | 135 | getOutputShape() { 136 | return [this.out_depth, 1, 1] 137 | } 138 | 139 | getDescription(){ 140 | return [this.layer_type.toUpperCase(), this.name, this.out_depth.toString()]; 141 | } 142 | 143 | toJSON() { 144 | var json: any = super.toJSON(); 145 | json.num_inputs = this.num_inputs; 146 | json.l1_decay_mul = this.l1_decay_mul; 147 | json.l2_decay_mul = this.l2_decay_mul; 148 | json.filters = []; 149 | for (var i = 0; i < this.filters.length; i++) { 150 | json.filters.push(this.filters[i].toJSON()); 151 | } 152 | json.biases = this.biases.toJSON(); 153 | return json; 154 | } 155 | 156 | fromJSON(json: any) { 157 | super.fromJSON(json); 158 | this.num_inputs = json.num_inputs; 159 | this.l1_decay_mul = json.l1_decay_mul !== undefined ? json.l1_decay_mul : 1.0; 160 | this.l2_decay_mul = json.l2_decay_mul !== undefined ? json.l2_decay_mul : 1.0; 161 | this.filters = []; 162 | for (var i = 0; i < json.filters.length; i++) { 163 | this.filters.push(Vol.fromJSON(json.filters[i])); 164 | } 165 | this.biases = Vol.fromJSON(json.biases); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/Net/Layers/InputLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | export class InputLayer extends BaseLayer implements ILayer { 8 | 9 | public layer_type: string = 'input'; 10 | 11 | public in_act: Vol; 12 | public out_act: Vol; 13 | 14 | constructor(opt) { 15 | super(opt || {}); 16 | 17 | // required: depth 18 | this.in_depth = getopt(opt, ['in_depth', 'out_depth', 'depth'], 0); 19 | 20 | // optional: default these dimensions to 1 21 | this.in_sx = getopt(opt, ['in_sx', 'out_sx', 'sx', 'width'], 1); 22 | this.in_sy = getopt(opt, ['in_sy', 'out_sy', 'sy', 'height'], 1); 23 | 24 | this.updateDimensions(); 25 | } 26 | 27 | forward (V, is_training) { 28 | this.in_act = V; 29 | this.resetGradient(); 30 | this.out_act = V; 31 | return this.out_act; // simply identity function for now 32 | } 33 | 34 | backward() {} 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Net/Layers/LocalResponseNormalizationLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // a bit experimental layer for now. I think it works but I'm not 100% 8 | // the gradient check is a bit funky. I'll look into this a bit later. 9 | // Local Response Normalization in window, along depths of volumes 10 | export class LocalResponseNormalizationLayer extends BaseLayer implements ILayer { 11 | 12 | public layer_type: string = 'lrn'; 13 | 14 | public in_act: Vol; 15 | public out_act: Vol; 16 | 17 | public k: number; 18 | public n: number; 19 | public alpha: number; 20 | public beta: number; 21 | 22 | private S_cache_: Vol; 23 | 24 | constructor(opt) { 25 | super(opt || {}); 26 | 27 | // required 28 | this.k = opt.k; 29 | this.n = opt.n; 30 | this.alpha = opt.alpha; 31 | this.beta = opt.beta; 32 | 33 | // checks 34 | if (this.n % 2 === 0) { 35 | console.warn('WARNING n should be odd for LRN layer'); 36 | } 37 | 38 | this.updateDimensions(opt.pred); 39 | } 40 | 41 | forward(V, is_training) { 42 | this.in_act = V; 43 | this.resetGradient(); 44 | var A = V.cloneAndZero(); 45 | this.S_cache_ = V.cloneAndZero(); 46 | var n2 = Math.floor(this.n / 2); 47 | for (var x = 0; x < V.sx; x++) { 48 | for (var y = 0; y < V.sy; y++) { 49 | for (var i = 0; i < V.depth; i++) { 50 | 51 | var a_i = V.get(x, y, i); 52 | var f0 = this.k; 53 | var f1 = this.alpha / this.n; 54 | var sum = 0.0; 55 | 56 | // normalize in a window of size n 57 | for (var j = Math.max(0, i - n2); j <= Math.min(i + n2, V.depth - 1); j++) { 58 | var aa = V.get(x, y, j); 59 | sum += aa * aa; 60 | } 61 | 62 | // will be useful for backprop 63 | var scale_i = f0 + f1 * sum; 64 | this.S_cache_.set(x, y, i, scale_i); 65 | var b_i = a_i * Math.pow(scale_i, -this.beta); 66 | A.set(x, y, i, b_i); 67 | } 68 | } 69 | } 70 | 71 | this.out_act = A; 72 | return this.out_act; // dummy identity function for now 73 | } 74 | 75 | backward() { 76 | // evaluate gradient wrt data 77 | var V = this.in_act; // we need to set dw of this 78 | var A = this.out_act; // computed in forward pass 79 | 80 | var n2 = Math.floor(this.n / 2); 81 | for (var x = 0; x < V.sx; x++) { 82 | for (var y = 0; y < V.sy; y++) { 83 | for (var i = 0; i < V.depth; i++) { 84 | 85 | var scale_i = this.S_cache_.get(x, y, i); 86 | var a_i = V.get(x, y, i); 87 | var be_i = A.get_grad(x, y, i); 88 | var f0 = Math.pow(scale_i, -this.beta) * be_i; 89 | var f1 = 2.0 * this.alpha * this.beta / this.n * a_i; 90 | var sum = 0.0; 91 | 92 | // normalize in a window of size n 93 | for (var j = Math.max(0, i - n2); j <= Math.min(i + n2, V.depth - 1); j++) { 94 | var b_j = A.get(x, y, j); 95 | var be_j = A.get_grad(x, y, j); 96 | var scale_j = this.S_cache_.get(x, y, j); 97 | 98 | sum += be_j * b_j / scale_j; 99 | } 100 | 101 | var ae_i = f0 - f1 * sum; 102 | V.add_grad(x, y, i, ae_i); 103 | } 104 | } 105 | } 106 | } 107 | 108 | getDescription() { 109 | return super.getDescription().concat([ 110 | 'n=' + this.n + ' ' + 'k=' + this.k, 111 | ]); 112 | } 113 | 114 | toJSON() { 115 | var json: any = super.toJSON(); 116 | json.k = this.k; 117 | json.n = this.n; 118 | json.alpha = this.alpha; // normalize by size 119 | json.beta = this.beta; 120 | return json; 121 | } 122 | 123 | fromJSON(json: any) { 124 | super.fromJSON(json); 125 | this.k = json.k; 126 | this.n = json.n; 127 | this.alpha = json.alpha; // normalize by size 128 | this.beta = json.beta; 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Net/Layers/MaxoutLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // Implements Maxout nonlinearity that computes x -> max(x) 8 | // where x is a vector of size group_size. Ideally of course, 9 | // the input size should be exactly divisible by group_size 10 | export class MaxoutLayer extends BaseLayer implements ILayer { 11 | 12 | public layer_type: string = 'maxout'; 13 | 14 | public in_act: Vol; 15 | public out_act: Vol; 16 | 17 | public group_size: number; 18 | public switches: Uint32Array; 19 | 20 | constructor(opt) { 21 | super(opt || {}); 22 | 23 | // required 24 | this.group_size = opt.group_size !== undefined ? opt.group_size : 2; 25 | 26 | this.updateDimensions(opt.pred); 27 | } 28 | 29 | forward(V, is_training) { 30 | this.in_act = V; 31 | this.resetGradient(); 32 | this.switches = nj.zeros(this.out_sx * this.out_sy * this.out_depth, Uint32Array); // useful for backprop 33 | 34 | var N = this.out_depth; 35 | var V2 = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); 36 | 37 | // optimization branch. If we're operating on 1D arrays we dont have 38 | // to worry about keeping track of x,y,d coordinates inside 39 | // input volumes. In convnets we do :( 40 | if (this.out_sx === 1 && this.out_sy === 1) { 41 | for (var i = 0; i < N; i++) { 42 | var ix = i * this.group_size; // base index offset 43 | var a = V.w[ix]; 44 | var ai = 0; 45 | for (var j = 1; j < this.group_size; j++) { 46 | var a2 = V.w[ix + j]; 47 | if (a2 > a) { 48 | a = a2; 49 | ai = j; 50 | } 51 | } 52 | V2.w[i] = a; 53 | this.switches[i] = ix + ai; 54 | } 55 | } else { 56 | var n = 0; // counter for switches 57 | for (var x = 0; x < V.sx; x++) { 58 | for (var y = 0; y < V.sy; y++) { 59 | for (var i = 0; i < N; i++) { 60 | var ix = i * this.group_size; 61 | var a = V.get(x, y, ix); 62 | var ai = 0; 63 | for (var j = 1; j < this.group_size; j++) { 64 | var a2 = V.get(x, y, ix + j); 65 | if (a2 > a) { 66 | a = a2; 67 | ai = j; 68 | } 69 | } 70 | V2.set(x, y, i, a); 71 | this.switches[n] = ix + ai; 72 | n++; 73 | } 74 | } 75 | } 76 | 77 | } 78 | this.out_act = V2; 79 | return this.out_act; 80 | } 81 | 82 | backward() { 83 | var V = this.in_act; // we need to set dw of this 84 | var V2 = this.out_act; 85 | var N = this.out_depth; 86 | 87 | // pass the gradient through the appropriate switch 88 | if (this.out_sx === 1 && this.out_sy === 1) { 89 | for (var i = 0; i < N; i++) { 90 | var chain_grad = V2.dw[i]; 91 | V.dw[this.switches[i]] += chain_grad; 92 | } 93 | } else { 94 | // bleh okay, lets do this the hard way 95 | var n = 0; // counter for switches 96 | for (var x = 0; x < V2.sx; x++) { 97 | for (var y = 0; y < V2.sy; y++) { 98 | for (var i = 0; i < N; i++) { 99 | var chain_grad = V2.get_grad(x, y, i); 100 | V.add_grad(x, y, this.switches[n], chain_grad); 101 | n++; 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | updateDimensions(pred: ILayer[]) { 109 | 110 | if (pred){ 111 | this.in_sx = pred[0].out_sx; 112 | this.in_sy = pred[0].out_sy; 113 | this.in_depth = pred[0].out_depth; 114 | } 115 | 116 | this.out_sx = this.in_sx; 117 | this.out_sy = this.in_sy; 118 | this.out_depth = Math.floor(this.in_depth / this.group_size); 119 | } 120 | 121 | toJSON() { 122 | var json: any = super.toJSON(); 123 | json.group_size = this.group_size; 124 | return json; 125 | } 126 | 127 | fromJSON(json: any) { 128 | super.fromJSON(json); 129 | this.group_size = json.group_size; 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Net/Layers/PoolLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | export class PoolLayer extends BaseLayer implements ILayer { 8 | 9 | public layer_type: string = 'pool'; 10 | 11 | public in_act: Vol; 12 | public out_act: Vol; 13 | 14 | public sx: number; 15 | public sy: number; 16 | public stride: number; 17 | public pad: number; 18 | public pool: string; 19 | 20 | public biases: Vol; 21 | public filters: Vol[]; 22 | 23 | public switchx: Uint32Array; 24 | public switchy: Uint32Array; 25 | 26 | constructor(opt) { 27 | super(opt || {}); 28 | 29 | // required 30 | this.out_depth = opt.filters; 31 | 32 | this.pool = getopt(opt, ['pool'], 'MAX');; 33 | 34 | if (getopt(opt, ['global_pooling'], false)) { 35 | 36 | // Get the dimensions of the previous layer 37 | this.sx = opt.pred[0].out_sx; 38 | this.sy = opt.pred[0].out_sy; 39 | } 40 | else { 41 | 42 | // filter size. Should be odd if possible, it's cleaner. 43 | this.sx = opt.sx; 44 | 45 | // optional 46 | this.sy = getopt(opt, ['sy'], this.sx); 47 | } 48 | 49 | // stride at which we apply filters to input volume 50 | this.stride = getopt(opt, ['stride'], 1); 51 | 52 | // amount of 0 padding to add around borders of input volume 53 | this.pad = getopt(opt, ['pad'], 0); 54 | 55 | this.updateDimensions(opt.pred); 56 | } 57 | 58 | forward(V, is_training) { 59 | this.in_act = V; 60 | this.resetGradient(); 61 | this.switchx = nj.zeros(this.out_sx * this.out_sy * this.out_depth); 62 | this.switchy = nj.zeros(this.out_sx * this.out_sy * this.out_depth); 63 | 64 | var A = new Vol(this.out_sx, this.out_sy, this.out_depth, 0.0); 65 | 66 | if (this.pool === 'AVE') { 67 | let n = this.sx * this.sy; 68 | for (let d = 0; d < this.out_depth; ++d) { 69 | for (let ax = 0; ax < this.out_sx; ++ax) { 70 | for (let ay = 0; ay < this.out_sy; ++ay) { 71 | let v = 0.0; 72 | let xstart = ax * this.stride - this.pad; 73 | let ystart = ay * this.stride - this.pad; 74 | let xend = Math.min(xstart + this.sx, V.sx + this.pad); 75 | let yend = Math.min(ystart + this.sy, V.sy + this.pad); 76 | xstart = Math.max(xstart, 0); 77 | ystart = Math.max(ystart, 0); 78 | xend = Math.min(xend, V.sx); 79 | yend = Math.min(yend, V.sy); 80 | let pool_size = (xend - xstart) * (yend - ystart); 81 | // perform average pooling 82 | for (let x = xstart; x < xend; ++x) { 83 | for (let y = ystart; y < yend; ++y) { 84 | v += V.get(x, y, d); 85 | } 86 | } 87 | A.set(ax, ay, d, v / pool_size); 88 | } 89 | } 90 | } 91 | } 92 | else { 93 | var n = 0; // a counter for switches 94 | for (var d = 0; d < this.out_depth; ++d) { 95 | var x = -this.pad; 96 | var y = -this.pad; 97 | for (var ax = 0; ax < this.out_sx; x += this.stride, ax++) { 98 | y = -this.pad; 99 | for (var ay = 0; ay < this.out_sy; y += this.stride, ay++) { 100 | 101 | // convolve centered at this particular location 102 | var a = -99999; // hopefully small enough ;\ 103 | var winx = -1, winy = -1; 104 | for (var fx = 0; fx < this.sx; ++fx) { 105 | for (var fy = 0; fy < this.sy; ++fy) { 106 | var oy = y + fy; 107 | var ox = x + fx; 108 | if (oy >= 0 && oy < V.sy && ox >= 0 && ox < V.sx) { 109 | var v = V.get(ox, oy, d); 110 | // perform max pooling and store pointers to where 111 | // the max came from. This will speed up backprop 112 | // and can help make nice visualizations in future 113 | if (v > a) { a = v; winx = ox; winy = oy; } 114 | } 115 | } 116 | } 117 | this.switchx[n] = winx; 118 | this.switchy[n] = winy; 119 | n++; 120 | A.set(ax, ay, d, a); 121 | } 122 | } 123 | } 124 | } 125 | this.out_act = A; 126 | return this.out_act; 127 | } 128 | 129 | backward() { 130 | // pooling layers have no parameters, so simply compute 131 | // gradient wrt data here 132 | var V = this.in_act; 133 | var A = this.out_act; // computed in forward pass 134 | 135 | if (this.pool === 'AVE') { 136 | // TODO 137 | // backprop for average pooling 138 | } 139 | else { 140 | var n = 0; 141 | for (var d = 0; d < this.out_depth; ++d) { 142 | var x = -this.pad; 143 | for (var ax = 0; ax < this.out_sx; x += this.stride, ax++) { 144 | var y = -this.pad; 145 | for (var ay = 0; ay < this.out_sy; y += this.stride, ay++) { 146 | var chain_grad = this.out_act.get_grad(ax, ay, d); 147 | V.add_grad(this.switchx[n], this.switchy[n], d, chain_grad); 148 | n++; 149 | } 150 | } 151 | } 152 | } 153 | } 154 | 155 | updateDimensions(pred: ILayer[]) { 156 | if (pred){ 157 | this.in_sx = pred[0].out_sx; 158 | this.in_sy = pred[0].out_sy; 159 | this.in_depth = pred[0].out_depth; 160 | } 161 | 162 | var s = this.getOutputShape(); 163 | this.out_sx = s[1]; 164 | this.out_sy = s[2]; 165 | this.out_depth = this.in_depth; 166 | } 167 | 168 | getOutputShape() { 169 | return [ 170 | this.out_depth, 171 | // using ceil do to Caffe compatibility 172 | // https://github.com/BVLC/caffe/issues/1318 173 | // https://github.com/BVLC/caffe/issues/4252 174 | Math.ceil((this.in_sx + this.pad * 2 - this.sx) / this.stride + 1), 175 | Math.ceil((this.in_sy + this.pad * 2 - this.sy) / this.stride + 1), 176 | ] 177 | } 178 | 179 | getDescription() { 180 | return [ 181 | this.pool + " " + this.layer_type.toUpperCase(), 182 | this.name, 183 | [this.sy, this.sx].join('x') + ' stride=' + this.stride + ' pad=' + this.pad 184 | ]; 185 | } 186 | 187 | toJSON() { 188 | var json: any = super.toJSON(); 189 | json.sx = this.sx; 190 | json.sy = this.sy; 191 | json.stride = this.stride; 192 | json.pool = this.pool; 193 | json.in_depth = this.in_depth; 194 | json.pad = this.pad; 195 | return json; 196 | } 197 | 198 | fromJSON(json: any) { 199 | super.fromJSON(json); 200 | this.pool = json.pool !== undefined ? json.pool : 'MAX'; 201 | this.sx = json.sx; 202 | this.sy = json.sy; 203 | this.stride = json.stride; 204 | this.in_depth = json.in_depth; 205 | this.pad = json.pad !== undefined ? json.pad : 0; // backwards compatibility 206 | this.switchx = nj.zeros(this.out_sx * this.out_sy * this.out_depth, Uint32Array); // need to re-init these appropriately 207 | this.switchy = nj.zeros(this.out_sx * this.out_sy * this.out_depth, Uint32Array); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/Net/Layers/RegressionLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // implements an L2 regression cost layer, 8 | // so penalizes \sum_i(||x_i - y_i||^2), where x is its input 9 | // and y is the user-provided array of "correct" values. 10 | export class RegressionLayer extends BaseLayer implements ILayer { 11 | 12 | public layer_type: string = 'regression'; 13 | 14 | public in_act: Vol; 15 | public out_act: Vol; 16 | 17 | public num_inputs: number; 18 | 19 | constructor(opt) { 20 | super(opt || {}); 21 | 22 | this.updateDimensions(opt.pred); 23 | } 24 | 25 | forward(V, is_training) { 26 | this.in_act = V; 27 | this.resetGradient(); 28 | this.out_act = V; 29 | return V; // identity function 30 | } 31 | 32 | // y is a list here of size num_inputs 33 | // or it can be a number if only one value is regressed 34 | // or it can be a struct {dim: i, val: x} where we only want to 35 | // regress on dimension i and asking it to have value x 36 | backward(y) { 37 | 38 | // compute and accumulate gradient wrt weights and bias of this layer 39 | var x = this.in_act; 40 | var loss = 0.0; 41 | if (y instanceof Float32Array) { 42 | for (let i = 0; i < this.out_depth; i++) { 43 | let dy = x.w[i] - y[i]; 44 | x.dw[i] = dy; 45 | loss += 0.5 * dy * dy; 46 | } 47 | } else if (typeof y === 'number') { 48 | // lets hope that only one number is being regressed 49 | let dy = x.w[0] - y; 50 | x.dw[0] = dy; 51 | loss += 0.5 * dy * dy; 52 | } else { 53 | // assume it is a struct with entries .dim and .val 54 | // and we pass gradient only along dimension dim to be equal to val 55 | let i = y.dim; 56 | let yi = y.val; 57 | let dy = x.w[i] - yi; 58 | x.dw[i] = dy; 59 | loss += 0.5 * dy * dy; 60 | } 61 | return loss; 62 | } 63 | 64 | updateDimensions(pred: ILayer[]) { 65 | if (pred){ 66 | this.in_sx = pred[0].out_sx; 67 | this.in_sy = pred[0].out_sy; 68 | this.in_depth = pred[0].out_depth; 69 | } 70 | 71 | this.num_inputs = this.in_sx * this.in_sy * this.in_depth; 72 | this.out_depth = this.num_inputs; 73 | } 74 | 75 | getOutputShape() { 76 | return [this.out_depth, 1, 1] 77 | } 78 | 79 | toJSON() { 80 | var json: any = super.toJSON(); 81 | json.num_inputs = this.num_inputs; 82 | return json; 83 | } 84 | 85 | fromJSON(json: any) { 86 | super.fromJSON(json); 87 | this.num_inputs = json.num_inputs; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Net/Layers/ReluLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // Implements ReLU nonlinearity elementwise 8 | // x -> max(0, x) 9 | // the output is in [0, inf) 10 | export class ReluLayer extends BaseLayer implements ILayer { 11 | 12 | public layer_type: string = 'relu'; 13 | 14 | public in_act: Vol; 15 | public out_act: Vol; 16 | 17 | constructor(opt) { 18 | super(opt || {}); 19 | 20 | this.updateDimensions(opt.pred); 21 | } 22 | 23 | forward(V, is_training) { 24 | this.in_act = V; 25 | this.resetGradient(); 26 | var V2 = V.clone(); 27 | var N = V.w.length; 28 | var V2w = V2.w; 29 | for (var i = 0; i < N; i++) { 30 | if (V2w[i] < 0) V2w[i] = 0; // threshold at 0 31 | } 32 | this.out_act = V2; 33 | return this.out_act; 34 | } 35 | 36 | backward() { 37 | var V = this.in_act; // we need to set dw of this 38 | var V2 = this.out_act; 39 | var N = V.w.length; 40 | for (var i = 0; i < N; i++) { 41 | if (V2.w[i] <= 0) V.dw[i] = 0; // threshold 42 | else V.dw[i] += V2.dw[i]; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Net/Layers/SVMLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // implements an L2 regression cost layer, 8 | // so penalizes \sum_i(||x_i - y_i||^2), where x is its input 9 | // and y is the user-provided array of "correct" values. 10 | export class SVMLayer extends BaseLayer implements ILayer { 11 | 12 | public layer_type: string = 'svm'; 13 | 14 | public in_act: Vol; 15 | public out_act: Vol; 16 | 17 | public num_inputs: number; 18 | 19 | constructor(opt) { 20 | super(opt || {}); 21 | 22 | this.updateDimensions(opt.pred); 23 | } 24 | 25 | forward(V, is_training) { 26 | this.in_act = V; 27 | this.resetGradient(); 28 | this.out_act = V; 29 | return V; // identity function 30 | } 31 | 32 | backward(y) { 33 | 34 | // compute and accumulate gradient wrt weights and bias of this layer 35 | var x = this.in_act; 36 | 37 | // we're using structured loss here, which means that the score 38 | // of the ground truth should be higher than the score of any other 39 | // class, by a margin 40 | var yscore = x.w[y]; // score of ground truth 41 | var margin = 1.0; 42 | var loss = 0.0; 43 | for (var i = 0; i < this.out_depth; i++) { 44 | if (y === i) { continue; } 45 | var ydiff = -yscore + x.w[i] + margin; 46 | if (ydiff > 0) { 47 | // violating dimension, apply loss 48 | x.dw[i] += 1; 49 | x.dw[y] -= 1; 50 | loss += ydiff; 51 | } 52 | } 53 | 54 | return loss; 55 | } 56 | 57 | updateDimensions(pred: ILayer[]) { 58 | 59 | if (pred){ 60 | this.in_sx = pred[0].out_sx; 61 | this.in_sy = pred[0].out_sy; 62 | this.in_depth = pred[0].out_depth; 63 | } 64 | 65 | this.num_inputs = this.in_sx * this.in_sy * this.in_depth; 66 | this.out_depth = this.num_inputs; 67 | } 68 | 69 | getOutputShape() { 70 | return [this.out_depth, 1, 1] 71 | } 72 | 73 | toJSON() { 74 | var json: any = super.toJSON(); 75 | json.num_inputs = this.num_inputs; 76 | return json; 77 | } 78 | 79 | fromJSON(json: any) { 80 | super.fromJSON(json); 81 | this.num_inputs = json.num_inputs; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/Net/Layers/SigmoidLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // Implements Sigmoid nnonlinearity elementwise 8 | // x -> 1/(1+e^(-x)) 9 | // so the output is between 0 and 1. 10 | export class SigmoidLayer extends BaseLayer implements ILayer { 11 | 12 | public layer_type: string = 'sigmoid'; 13 | 14 | public in_act: Vol; 15 | public out_act: Vol; 16 | 17 | constructor(opt) { 18 | super(opt || {}); 19 | 20 | this.updateDimensions(opt.pred); 21 | } 22 | 23 | forward(V, is_training) { 24 | this.in_act = V; 25 | this.resetGradient(); 26 | var V2 = V.cloneAndZero(); 27 | var N = V.w.length; 28 | var V2w = V2.w; 29 | var Vw = V.w; 30 | for (var i = 0; i < N; i++) { 31 | V2w[i] = 1.0 / (1.0 + Math.exp(-Vw[i])); 32 | } 33 | this.out_act = V2; 34 | return this.out_act; 35 | } 36 | 37 | backward() { 38 | var V = this.in_act; // we need to set dw of this 39 | var V2 = this.out_act; 40 | var N = V.w.length; 41 | for (var i = 0; i < N; i++) { 42 | var v2wi = V2.w[i]; 43 | V.dw[i] += v2wi * (1.0 - v2wi) * V2.dw[i]; 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Net/Layers/SoftmaxLayer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // This is a classifier, with N discrete classes from 0 to N-1 8 | // it gets a stream of N incoming numbers and computes the softmax 9 | // function (exponentiate and normalize to sum to 1 as probabilities should) 10 | export class SoftmaxLayer extends BaseLayer implements ILayer { 11 | 12 | public layer_type: string = 'softmax'; 13 | 14 | public in_act: Vol; 15 | public out_act: Vol; 16 | 17 | public num_inputs: number; 18 | private es: Float32Array; 19 | 20 | constructor(opt) { 21 | super(opt || {}); 22 | 23 | this.updateDimensions(opt.pred); 24 | } 25 | 26 | forward(V, is_training) { 27 | this.in_act = V; 28 | this.resetGradient(); 29 | 30 | var A = new Vol(1, 1, this.out_depth, 0.0); 31 | 32 | // compute max activation 33 | var as = V.w; 34 | var amax = V.w[0]; 35 | for(var i=1;i amax) amax = as[i]; 37 | } 38 | 39 | // compute exponentials (carefully to not blow up) 40 | var es = nj.zeros(this.out_depth); 41 | var esum = 0.0; 42 | for(var i=0;i 2 | 3 | namespace Net.Layers { 4 | 5 | const nj = NumJS; 6 | 7 | // Implements Tanh nnonlinearity elementwise 8 | // x -> tanh(x) 9 | // so the output is between -1 and 1. 10 | export class TanhLayer extends BaseLayer implements ILayer { 11 | 12 | public layer_type: string = 'tanh'; 13 | 14 | public in_act: Vol; 15 | public out_act: Vol; 16 | 17 | constructor(opt) { 18 | super(opt || {}); 19 | 20 | this.updateDimensions(opt.pred); 21 | } 22 | 23 | forward(V, is_training) { 24 | this.in_act = V; 25 | this.resetGradient(); 26 | var V2 = V.cloneAndZero(); 27 | var N = V.w.length; 28 | for (var i = 0; i < N; i++) { 29 | V2.w[i] = nj.tanh(V.w[i]); 30 | } 31 | this.out_act = V2; 32 | return this.out_act; 33 | } 34 | 35 | backward() { 36 | var V = this.in_act; // we need to set dw of this 37 | var V2 = this.out_act; 38 | var N = V.w.length; 39 | for (var i = 0; i < N; i++) { 40 | var v2wi = V2.w[i]; 41 | V.dw[i] += (1.0 - v2wi * v2wi) * V2.dw[i]; 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /src/Net/Layers/_module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | /// 8 | /// 9 | /// 10 | /// 11 | /// 12 | /// 13 | /// 14 | /// 15 | /// 16 | -------------------------------------------------------------------------------- /src/Net/Model.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | //import * as d3 from 'd3'; 4 | 5 | namespace Net { 6 | 7 | // declare variables 8 | declare var d3: any; 9 | 10 | const nj = NumJS; 11 | 12 | interface IEdge { 13 | from: string; 14 | to: string; 15 | } 16 | 17 | export class Model { 18 | 19 | public layers: any /*d3.Map*/; 20 | public edges: IEdge[]; 21 | 22 | constructor(){ 23 | 24 | } 25 | 26 | getLayer(name: string): ILayer { 27 | return this.layers.get(name); 28 | } 29 | 30 | setInputDimensions(width: number, height: number, depth: number = 3) { 31 | this.layerIterator((layer: ILayer, i: number, pred: ILayer[]) => { 32 | if (i === 0) { 33 | layer.in_sx = width; 34 | layer.in_sy= height; 35 | layer.in_depth = depth; 36 | layer.updateDimensions(); 37 | } 38 | else { 39 | layer.updateDimensions(pred); 40 | } 41 | }); 42 | } 43 | 44 | layerIterator(iteratorFn: (layer: ILayer, i: number, pred: ILayer[]) => any, params: any = {}) { 45 | var layerStack: ILayer[] = []; 46 | var edges: IEdge[] = []; 47 | var layer: ILayer; 48 | var i = 0; 49 | 50 | // Store the visited nodes 51 | var visited = d3.set(); 52 | 53 | // Forward traversal 54 | if (params.reverse === undefined || params.reverse === false) { 55 | // Define the current layer 56 | layer = params.start ? this.layers.get(params.start) : this.layers.get('data'); 57 | edges = this.edges; 58 | } 59 | // Backward traversal 60 | else { 61 | // Define the current layer 62 | layer = params.start ? this.layers.get(params.start) : this.layers.values()[this.layers.size() - 1]; 63 | edges = this.edges.map((d: IEdge) => { 64 | return { from: d.to, to: d.from } 65 | }); 66 | } 67 | 68 | // Aggregate all edges by the from property 69 | // Reverse edge directions 70 | var edgesFrom = d3.map( 71 | d3.nest() 72 | .key((d: IEdge) => d.from) 73 | .entries(edges), (d) => d.key); 74 | 75 | // Aggregate all edges by the to property 76 | // Reverse edge directions 77 | var edgesTo = d3.map( 78 | d3.nest() 79 | .key((d: IEdge) => d.to) 80 | .entries(edges), (d) => d.key); 81 | 82 | // Start with the first layer 83 | layerStack.push(layer); 84 | 85 | while (layerStack.length) { 86 | // Take a layer from the stack 87 | let layer = layerStack.pop(); 88 | 89 | // Set the layer visited 90 | visited.add(layer.name); 91 | 92 | // Collect the previous Layers 93 | var parentKeys = edgesTo.get(layer.name); 94 | var parents = parentKeys === undefined ? undefined 95 | : parentKeys.values.map((d) => this.layers.get(d.from)); 96 | 97 | // Call the iterator callback 98 | iteratorFn(layer, i++, parents); 99 | 100 | // Check if we reached the end layer 101 | if (params.end && layer.name === params.end) { 102 | break; 103 | } 104 | 105 | // Get all children for this layer 106 | var childrenKeys = edgesFrom.get(layer.name); 107 | if (childrenKeys) { 108 | childrenKeys.values 109 | // Only check adjacent nodes that have 110 | // not been visited yet 111 | .filter((d) => !visited.has(d.to)) 112 | .forEach((d) => { 113 | // Check if there are still any unvisited parents 114 | // of the next child which need to be visited first 115 | var parentKeysOfChild = edgesTo.get(d.to); 116 | var unvisitedParents = parentKeysOfChild === undefined ? [] 117 | : parentKeysOfChild.values.filter((d) => !visited.has(d.from)); 118 | 119 | // All previous parents have been visited 120 | if (unvisitedParents.length === 0) { 121 | // Add the layer to the stack 122 | layerStack.push(this.layers.get(d.to)); 123 | } 124 | }); 125 | } 126 | } 127 | } 128 | 129 | forward(V, is_training = false, params: any = {}) { 130 | var activationMap = d3.map(); 131 | var currentActivation; 132 | this.layerIterator((layer, i, parents) => { 133 | if (parents === undefined) { 134 | currentActivation = V; 135 | } 136 | else if (parents.length > 1) { 137 | currentActivation = parents.map((d) => activationMap.get(d.name)); 138 | } 139 | else { 140 | currentActivation = activationMap.get(parents[0].name); 141 | } 142 | currentActivation = layer.forward(currentActivation, is_training); 143 | activationMap.set(layer.name, currentActivation); 144 | }, params); 145 | 146 | return currentActivation; 147 | } 148 | 149 | backward(y, params: any = {}) { 150 | params.reverse = true; 151 | 152 | var loss; 153 | 154 | this.layerIterator((layer, i, parents) => { 155 | if (y !== undefined && i === 0) { 156 | // last layer assumed to be loss layer 157 | loss = layer.backward(y); 158 | } 159 | else { 160 | // backprop to all other layers 161 | layer.backward(); 162 | } 163 | }, params); 164 | 165 | return loss; 166 | } 167 | 168 | debugStructure(){ 169 | var numParams = 0; 170 | var f = d3.format('s'); 171 | var f2 = d3.format(',d'); 172 | var numLayers = 0; 173 | 174 | this.layerIterator((layer, i, pred) => { 175 | var numParamsPerLayer = nj.sum(layer.getNumParameters()); 176 | 177 | var str = ""; 178 | str += layer.getOutputShape().join('x') + " :: "; 179 | str += layer.getDescription().join(' '); 180 | 181 | if (numParamsPerLayer) { 182 | numParams += numParamsPerLayer; 183 | str += " => " + f2(numParamsPerLayer) + ' parameters'; 184 | } 185 | 186 | //if (['relu', 'tanh', 'sigmoid'].indexOf(layer.layer_type) === -1) { 187 | numLayers += 1; 188 | console.log(str); 189 | //} 190 | }); 191 | 192 | console.log('---') 193 | console.log('Total number of layers ' + f2(numLayers)); 194 | console.log('Total number of params ' + f2(numParams) + ' (memory: ' + f(numParams*4) + 'b): '); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/Net/Utils.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net { 4 | 5 | const nj = NumJS; 6 | 7 | // sample from list lst according to probabilities in list probs 8 | // the two lists are of same size, and probs adds up to 1 9 | export function weightedSample(lst, probs) { 10 | var p = nj.randf(0, 1.0); 11 | var cumprob = 0.0; 12 | for (var k = 0, n = lst.length; k < n; k++) { 13 | cumprob += probs[k]; 14 | if (p < cumprob) { return lst[k]; } 15 | } 16 | } 17 | 18 | // syntactic sugar function for getting default parameter values 19 | export function getopt(opt: any = {}, field_name, default_value) { 20 | if (typeof field_name === 'string') { 21 | // case of single string 22 | return (opt[field_name] !== undefined) ? opt[field_name] : default_value; 23 | } else { 24 | // assume we are given a list of string instead 25 | var ret = default_value; 26 | field_name.forEach((f) => { 27 | ret = opt[f] !== undefined ? opt[f] : ret; 28 | }); 29 | return ret; 30 | } 31 | } 32 | 33 | export function assert(condition, message) { 34 | if (!condition) { 35 | message = message || "Assertion failed"; 36 | if (typeof Error !== "undefined") { 37 | throw new Error(message); 38 | } 39 | throw message; // Fallback 40 | } 41 | } 42 | 43 | export function arrContains(arr, elt) { 44 | for (var i = 0, n = arr.length; i < n; i++) { 45 | if (arr[i] === elt) return true; 46 | } 47 | return false; 48 | } 49 | 50 | export function arrUnique(arr) { 51 | var b = []; 52 | for (var i = 0, n = arr.length; i < n; i++) { 53 | if (!arrContains(b, arr[i])) { 54 | b.push(arr[i]); 55 | } 56 | } 57 | return b; 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/Net/Vol.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Net { 4 | 5 | const nj = NumJS; 6 | 7 | export class Vol { 8 | 9 | public w: Float32Array; 10 | public dw: Float32Array; 11 | 12 | constructor(public sx: number, public sy: number, public depth: number, fill?: number) { 13 | // we were given dimensions of the vol 14 | var n = this.sx * this.sy * this.depth; 15 | this.w = nj.zeros(n); 16 | // this.dw = nj.zeros(n); 17 | if (fill === undefined) { 18 | // weight normalization is done to equalize the output 19 | // variance of every neuron, otherwise neurons with a lot 20 | // of incoming connections have outputs of larger variance 21 | let scale = Math.sqrt(1.0 / n); 22 | for (let i = 0; i < n; ++i) { 23 | this.w[i] = nj.randn(0.0, scale); 24 | } 25 | } 26 | // The weights are already zero filled, so we need to do this 27 | // only when using a different constant 28 | else if (fill !== 0.0) { 29 | nj.fill(this.w, fill); 30 | } 31 | } 32 | 33 | static fromArray(A: Float32Array): Vol { 34 | // we were given a list in A, assume 1D volume and fill it up 35 | var vol = new Vol(1, 1, A.length, 0.0); 36 | vol.w.set(A); 37 | return vol; 38 | } 39 | 40 | clone(): Vol { 41 | var vol = new Vol(this.sx, this.sy, this.depth, 0.0); 42 | vol.w.set(this.w); 43 | return vol; 44 | } 45 | 46 | cloneAndZero(): Vol { 47 | var vol = new Vol(this.sx, this.sy, this.depth, 0.0); 48 | return vol; 49 | } 50 | 51 | get(x, y, d) { 52 | var ix = ((this.sx * y) + x) * this.depth + d; 53 | return this.w[ix]; 54 | } 55 | 56 | set(x, y, d, v) { 57 | var ix = ((this.sx * y) + x) * this.depth + d; 58 | this.w[ix] = v; 59 | } 60 | 61 | add(x, y, d, v) { 62 | var ix = ((this.sx * y) + x) * this.depth + d; 63 | this.w[ix] += v; 64 | return this; 65 | } 66 | 67 | sub(x, y, d, v) { 68 | var ix = ((this.sx * y) + x) * this.depth + d; 69 | this.w[ix] -= v; 70 | return this; 71 | } 72 | 73 | mul(x, y, d, v) { 74 | var ix = ((this.sx * y) + x) * this.depth + d; 75 | this.w[ix] *= v; 76 | return this; 77 | } 78 | 79 | div(x, y, d, v) { 80 | var ix = ((this.sx * y) + x) * this.depth + d; 81 | this.w[ix] /= v; 82 | return this; 83 | } 84 | 85 | get_grad(x, y, d) { 86 | var ix = ((this.sx * y) + x) * this.depth + d; 87 | return this.dw[ix]; 88 | } 89 | 90 | set_grad(x, y, d, v) { 91 | var ix = ((this.sx * y) + x) * this.depth + d; 92 | this.dw[ix] = v; 93 | } 94 | 95 | add_grad(x, y, d, v) { 96 | var ix = ((this.sx * y) + x) * this.depth + d; 97 | this.dw[ix] += v; 98 | } 99 | 100 | sub_grad(x, y, d, v) { 101 | var ix = ((this.sx * y) + x) * this.depth + d; 102 | this.dw[ix] -= v; 103 | } 104 | 105 | mul_grad(x, y, d, v) { 106 | var ix = ((this.sx * y) + x) * this.depth + d; 107 | this.dw[ix] *= v; 108 | } 109 | 110 | div_grad(x, y, d, v) { 111 | var ix = ((this.sx * y) + x) * this.depth + d; 112 | this.dw[ix] /= v; 113 | } 114 | 115 | roll(ox: number = 0.0, oy: number = 0.0, od: number = 0.0) { 116 | var V2 = this.clone(); 117 | for (let d = 0, depth = V2.depth; d < depth; ++d) { 118 | for (let x = 0, sx = V2.sx; x < sx; ++x) { 119 | for (let y = 0, sy = V2.sy; y < sy; ++y) { 120 | let dval = this.get(nj.mod((x + ox), this.sx), nj.mod((y + oy), this.sy), nj.mod((d + od), this.depth)); 121 | V2.set(x, y, d, dval); 122 | } 123 | } 124 | } 125 | return V2; 126 | } 127 | 128 | zoom(zx: number = 1.0, zy: number = 1.0, zd: number = 1.0) { 129 | var V2 = new Vol(Math.round(this.sx * zx), Math.round(this.sy * zy), Math.round(this.depth * zd), 0.0); 130 | for (let d = 0, depth = V2.depth; d < depth; ++d) { 131 | for (let x = 0, sx = V2.sx; x < sx; ++x) { 132 | for (let y = 0, sy = V2.sy; y < sy; ++y) { 133 | let n = 0; 134 | let ox = Math.ceil(1.0 / zx); 135 | let oy = Math.ceil(1.0 / zy); 136 | let od = Math.ceil(1.0 / zd); 137 | let startx = Math.ceil(x / zx); 138 | let starty = Math.ceil(y / zy); 139 | let startd = Math.ceil(d / zd); 140 | let endx = Math.min(startx + ox, this.sx); 141 | let endy = Math.min(starty + oy, this.sy); 142 | let endd = Math.min(startd + od, this.depth); 143 | for (let dx = startx; dx < endx; dx++) { 144 | for (let dy = starty; dy < endy; dy++) { 145 | for (let dd = startd; dd < endd; dd++) { 146 | let dval = this.get(dx, dy, dd); 147 | V2.add(x, y, d, dval); 148 | n ++; 149 | } 150 | } 151 | } 152 | V2.div(x, y, d, n); 153 | } 154 | } 155 | } 156 | return V2; 157 | } 158 | 159 | toJSON() { 160 | // todo: we may want to only save d most significant digits to save space 161 | var json:any = {} 162 | json.sx = this.sx; 163 | json.sy = this.sy; 164 | json.depth = this.depth; 165 | json.w = this.w; 166 | return json; 167 | // we wont back up gradients to save space 168 | } 169 | 170 | static fromJSON(json: any) { 171 | var vol = new Vol(json.sx, json.sy, json.depth, 0.0); 172 | 173 | vol.w.set(json.w); 174 | 175 | return vol; 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/Net/_module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// 6 | /// 7 | -------------------------------------------------------------------------------- /src/NumJS/Array.ts: -------------------------------------------------------------------------------- 1 | namespace NumJS { 2 | 3 | type DEFAULT_TYPE = Float32Array; 4 | 5 | export var DEFAULT_TYPE_INSTANCE = Float32Array; 6 | 7 | export function zeros(n: number, dtype = DEFAULT_TYPE_INSTANCE) { 8 | return new dtype(n); 9 | } 10 | 11 | export function fill(A, b) { 12 | // Note, this is faster than the native .fill() method 13 | for (let i = 0, len = A.length; i < len; ++i) A[i] = b; 14 | return A; 15 | } 16 | 17 | export function prod(A) { 18 | var prev = 1.0; 19 | for (let i = 0, len = A.length; i < len; ++i) prev *= A[i]; 20 | return prev; 21 | } 22 | 23 | export function sum(A) { 24 | var prev = 0.0; 25 | for (let i = 0, len = A.length; i < len; ++i) prev += A[i]; 26 | return prev; 27 | } 28 | 29 | export function add(A, B) { 30 | if (A.length === B.length) { 31 | for (let i = 0, len = A.length; i < len; ++i) A[i] += B[i]; 32 | return A; 33 | } 34 | else { 35 | throw new TypeError("Bad input shapes " + A.length + " " + B.length); 36 | } 37 | } 38 | 39 | export function addConst(A, b) { 40 | for (let i = 0, len = A.length; i < len; ++i) A[i] += b; 41 | return A; 42 | } 43 | 44 | export function sub(A, B) { 45 | if (A.length === B.length) { 46 | for (let i = 0, len = A.length; i < len; ++i) A[i] -= B[i]; 47 | return A; 48 | } 49 | else { 50 | throw new TypeError("Bad input shape " + A.length + " " + B.length); 51 | } 52 | } 53 | 54 | export function subConst(A, b) { 55 | for (let i = 0, len = A.length; i < len; ++i) A[i] -= b; 56 | return A; 57 | } 58 | 59 | export function mul(A, B) { 60 | if (A.length === B.length) { 61 | for (let i = 0, len = A.length; i < len; ++i) A[i] *= B[i]; 62 | return A; 63 | } 64 | else { 65 | throw new TypeError("Bad input shape " + A.length + " " + B.length); 66 | } 67 | } 68 | 69 | export function mulByConst(A, b) { 70 | for (let i = 0, len = A.length; i < len; ++i) A[i] *= b; 71 | return A; 72 | } 73 | 74 | export function div(A, B) { 75 | if (A.length === B.length) { 76 | for (let i = 0, len = A.length; i < len; ++i) A[i] /= B[i]; 77 | return A; 78 | } 79 | else { 80 | throw new TypeError("Bad input shape " + A.length + " " + B.length); 81 | } 82 | } 83 | 84 | export function divByConst(A, b) { 85 | for (let i = 0, len = A.length; i < len; ++i) A[i] /= b; 86 | return A; 87 | } 88 | 89 | export function addScaled(A, B, c) { 90 | if (A.length === B.length) { 91 | for (let i = 0, len = A.length; i < len; ++i) A[i] += c * B[i]; 92 | return A; 93 | } 94 | else { 95 | throw new TypeError("Bad input shape " + A.length + " " + B.length); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/NumJS/MinMax.ts: -------------------------------------------------------------------------------- 1 | namespace NumJS { 2 | 3 | export function max(A) { 4 | var max_ = Number.NEGATIVE_INFINITY; 5 | for (let i = 0, len = A.length; i < len; ++i) { 6 | if (A[i] > max_) { 7 | max_ = A[i]; 8 | } 9 | } 10 | return max_; 11 | } 12 | 13 | export function argmax(A) { 14 | var max_ = Number.NEGATIVE_INFINITY; 15 | var idx = 0; 16 | for (let i = 0, len = A.length; i < len; ++i) { 17 | if (A[i] > max_) { 18 | max_ = A[i]; 19 | idx = i; 20 | } 21 | } 22 | return idx; 23 | } 24 | 25 | export function maxn(A, n) { 26 | n = n || 3; 27 | return A.slice(0).sort(function(a, b) { 28 | return b - a; 29 | }).slice(0, n); 30 | } 31 | 32 | export function argmaxn(A, n) { 33 | n = n || 3; 34 | var len = A.length; 35 | var indices = new Uint32Array(len); 36 | for (let i = 0; i < len; ++i) indices[i] = i; 37 | return indices.sort(function(a, b) { 38 | return A[b] - A[a]; 39 | }).slice(0, n); 40 | } 41 | 42 | export function maxmin (w): any { 43 | if (w.length === 0) { 44 | return {}; 45 | } 46 | var maxv = w[0]; 47 | var minv = w[0]; 48 | var maxi = 0; 49 | var mini = 0; 50 | for (let i = 1, len = w.length; i < len; ++i) { 51 | if (w[i] > maxv) { maxv = w[i]; maxi = i; } 52 | if (w[i] < minv) { minv = w[i]; mini = i; } 53 | } 54 | return { maxi: maxi, maxv: maxv, mini: mini, minv: minv, dv: maxv - minv }; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/NumJS/Random.ts: -------------------------------------------------------------------------------- 1 | namespace NumJS { 2 | var cached: boolean = false; 3 | var cachedVal: number = 0.0; 4 | 5 | export function gaussRandom() { 6 | if (cached) { 7 | cached = false; 8 | return cachedVal; 9 | } 10 | var u = 2 * Math.random() - 1; 11 | var v = 2 * Math.random() - 1; 12 | var r = u * u + v * v; 13 | if (r == 0 || r > 1) return gaussRandom(); 14 | var c = Math.sqrt(-2 * Math.log(r) / r); 15 | cachedVal = v * c; // cache this 16 | cached = true; 17 | return u * c; 18 | } 19 | 20 | export function randf(a: number, b: number) { return Math.random() * (b - a) + a; } 21 | export function randi(a: number, b: number) { return Math.floor(Math.random() * (b - a) + a); } 22 | export function randn(mu: number, std: number) { return mu + gaussRandom() * std; } 23 | 24 | // create random permutation of numbers, in range [0...n-1] 25 | export function randperm(n) { 26 | var i = n; 27 | var j = 0; 28 | var temp; 29 | var arr = []; 30 | for(let q = 0; q < n; ++q) arr[q] = q; 31 | while (i--) { 32 | j = Math.floor(Math.random() * (i+1)); 33 | temp = arr[i]; 34 | arr[i] = arr[j]; 35 | arr[j] = temp; 36 | } 37 | return arr; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/NumJS/Utils.ts: -------------------------------------------------------------------------------- 1 | namespace NumJS { 2 | export function clip(value, min, max) { return Math.max(Math.min(value, max), min); } 3 | export function mod(a, b) { return ((a % b) + b) % b; } 4 | export function tanh(x) { var y = Math.exp(2 * x); return (y - 1) / (y + 1); } 5 | } 6 | -------------------------------------------------------------------------------- /src/NumJS/_module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// -------------------------------------------------------------------------------- /src/Parser/BinaryParser.ts: -------------------------------------------------------------------------------- 1 | namespace Parser { 2 | 3 | export abstract class BinaryParser { 4 | 5 | constructor() { 6 | 7 | } 8 | 9 | protected fetch(url: string) { 10 | var req = new Request(url); 11 | return fetch(req).then((response) => response.arrayBuffer()); 12 | } 13 | 14 | public parse(url: string) { 15 | return this.fetch(url).then((response) => this.parseBuffer(response)); 16 | } 17 | 18 | abstract parseBuffer(raw: ArrayBuffer); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Parser/BlobProtoParser.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | namespace Parser { 5 | 6 | export class BlobProtoParser extends BinaryprotoParser { 7 | 8 | parseProto(rawData: ArrayBuffer, protoParser: any) { 9 | // Decode the protobuf data 10 | var blob = protoParser.BlobProto.decode(rawData); 11 | 12 | var sx = blob.width, sy = blob.height, depth = blob.channels; 13 | var data = blob.data; 14 | 15 | // Generate a new Vol from the blob data 16 | var vol = new Net.Vol(sx, sy, depth, 0.0); 17 | 18 | for (let d = 0; d < depth; d++) { 19 | for (let y = 0; y < sy; y++) { 20 | for (let x = 0; x < sx; x++) { 21 | var ix = ((sy * d) + y) * sx + x; 22 | vol.set(x, y, d, data[ix]); 23 | } 24 | } 25 | } 26 | return vol; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Parser/PrototxtParser.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | namespace Parser { 4 | 5 | // declare variables 6 | declare var prototxtParser: any; 7 | 8 | export class PrototxtParser extends TextParser { 9 | 10 | parseString(raw: string){ 11 | return prototxtParser.parse(raw); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Parser/TextParser.ts: -------------------------------------------------------------------------------- 1 | namespace Parser { 2 | 3 | export abstract class TextParser { 4 | 5 | constructor() {} 6 | 7 | protected fetch(url: string) { 8 | var req = new Request(url); 9 | return fetch(req).then((response) => response.text()); 10 | } 11 | 12 | public parse(url: string) { 13 | return this.fetch(url).then((response) => this.parseString(response)); 14 | } 15 | 16 | abstract parseString(raw: string); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Parser/_module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// -------------------------------------------------------------------------------- /src/Utils/ActivationDrawer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | //import * as d3 from 'd3'; 5 | 6 | namespace Utils { 7 | 8 | // declare variables 9 | declare var d3: any; 10 | 11 | const nj = NumJS; 12 | const im = ImgJS; 13 | 14 | export class ActivationDrawer extends Net.Model { 15 | 16 | constructor() { 17 | super(); 18 | } 19 | 20 | render(element, renderInput: boolean = true, maxElementsPerLayer?: number, width?, height?) { 21 | // Clean 22 | var $elem = d3.select(element); 23 | $elem.selectAll('*').remove(); 24 | 25 | this.layerIterator(function(layer, j){ 26 | 27 | if (!renderInput && j == 0) { 28 | return; 29 | } 30 | 31 | $elem.append('h5').text(layer.name); 32 | 33 | var $div = $elem.append('div') 34 | .attr('class', 'net-layer'); 35 | 36 | var $vis = $div.append('div') 37 | .attr('class', 'net-vis'); 38 | 39 | var $weights = $vis.append('div') 40 | .attr('class', 'net-weights'); 41 | 42 | var $activations = $vis.append('div') 43 | .attr('class', 'net-activations'); 44 | 45 | for (let i=0, len=layer.out_act.depth; i= maxElementsPerLayer){ 47 | return; 48 | } 49 | let $canv: any = $activations.append('canvas'); 50 | let A = layer.out_act; 51 | 52 | // if (width && height){ 53 | // A = A.zoom(width / A.sx, height / A.sy); 54 | // } 55 | 56 | im.Image.fromFilter(A, i, 1).render($canv[0][0]); 57 | } 58 | }); 59 | } 60 | 61 | static fromNet(model: Net.Model){ 62 | var g = new ActivationDrawer; 63 | g.layers = model.layers; 64 | g.edges = model.edges; 65 | return g; 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/Utils/GraphDrawer.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // import * as d3 from 'd3'; 4 | // import * as dagreD3 from 'dagre-d3'; 5 | 6 | namespace Utils { 7 | 8 | // declare variables 9 | declare var d3: any; 10 | declare var dagreD3: any; 11 | 12 | export class GraphDrawer extends Net.Model { 13 | 14 | graph: any; 15 | $elem: any; 16 | $svg: any; 17 | $g: any; 18 | 19 | width: number; 20 | height: number; 21 | 22 | static readonly MIN_LAYER_HEIGHT: number = 16; 23 | static readonly MAX_LAYER_HEIGHT: number = 48; 24 | 25 | static readonly MIN_LAYER_WIDTH: number = 128; 26 | static readonly MAX_LAYER_WIDTH: number = 512; 27 | 28 | static readonly NODE_RADIUS: number = 4; 29 | static readonly LAYER_GROUP_SEP: string = '/'; 30 | static readonly LAYER_NAME_SEP: string = '_'; 31 | 32 | constructor(private compact: boolean = false) { 33 | super(); 34 | } 35 | 36 | destructor() { 37 | 38 | // Clear the events 39 | if (window) { 40 | window.addEventListener("resize", null); 41 | } 42 | } 43 | 44 | render(element, width?, height?) { 45 | this.width = width; 46 | this.height = height; 47 | 48 | // Create the renderer 49 | var render = new dagreD3.render(); 50 | this.graph = this.compact ? this.createCompactGraph() : this.createGraph(); 51 | 52 | // Clean 53 | this.$elem = d3.select(element); 54 | this.$elem.selectAll('*').remove(); 55 | 56 | // Run the renderer. This is what draws the final graph. 57 | this.$svg = this.$elem.append('svg'); 58 | 59 | this.$g = this.$svg.append("g"); 60 | render(this.$g, this.graph); 61 | 62 | this.postProcessGraph(); 63 | 64 | return this; 65 | } 66 | 67 | autoScale() { 68 | 69 | // put the proper event here 70 | if (window) { 71 | window.addEventListener("resize", () => this.postProcessGraph()); 72 | } 73 | 74 | return this; 75 | } 76 | 77 | fit() { 78 | // this.$g.attr("transform", "translate(" + 0 + ")"); 79 | this.$svg.attr("viewBox", "0 0 " + this.graph.graph().width + " " + this.graph.graph().height) 80 | // this.$svg.attr("preserveAspectRatio", "xMinYMax meet"); 81 | return this; 82 | } 83 | 84 | rotate() { 85 | var xOffset = (this.width - this.graph.graph().width) / 2; 86 | this.$g.attr("transform", "rotate(270) translate(" + xOffset + ")"); 87 | this.$svg.attr("viewBox", "0 0 " + this.graph.graph().height + " " + this.graph.graph().width); 88 | return this; 89 | } 90 | 91 | getHeightScaleTemplate(exp: number = 0.25) { 92 | return d3.scale.pow().exponent(exp).range([GraphDrawer.MIN_LAYER_HEIGHT, GraphDrawer.MAX_LAYER_HEIGHT]).clamp(true); 93 | } 94 | 95 | getWidthScaleTemplate(exp: number = 0.75) { 96 | return d3.scale.pow().exponent(exp).range([GraphDrawer.MIN_LAYER_WIDTH, GraphDrawer.MAX_LAYER_WIDTH]).clamp(true); 97 | } 98 | 99 | getWidthScale() { 100 | var extWidth = d3.extent(this.layers.values(), d => d.getOutputShape()[1]); 101 | var extHeight = d3.extent(this.layers.values(), d => d.getOutputShape()[2]); 102 | 103 | var widthScale = this.getWidthScaleTemplate().domain([ 104 | Math.min(extWidth[0], extHeight[0]), 105 | Math.max(extWidth[1], extHeight[1]) 106 | ]); 107 | 108 | return function(layer) { 109 | var layerSize = Math.max(layer.getOutputShape()[1], layer.getOutputShape()[2]); 110 | return widthScale(layerSize); 111 | }; 112 | } 113 | 114 | getHeightScale() { 115 | var extDepth = d3.extent(this.layers.values(), d => d.getOutputShape()[0]); 116 | 117 | var heightScale = this.getHeightScaleTemplate().domain([ 118 | extDepth[0], extDepth[1] 119 | ]); 120 | 121 | return function(layer) { 122 | var layerDepth = layer.getOutputShape()[0]; 123 | return heightScale(layerDepth); 124 | }; 125 | } 126 | 127 | createCompactGraph() { 128 | 129 | var g = new dagreD3.graphlib.Graph() 130 | .setGraph({}) 131 | .setDefaultEdgeLabel(() => ''); 132 | 133 | var getWidth = this.getWidthScale(); 134 | var getHeight = this.getHeightScale(); 135 | 136 | var take = (desc, k) => desc.splice(k, 1); 137 | var skip = (desc, k) => desc.filter((d, i) => i !== k); 138 | var after = (d, f) => d != "" ? d + f : d; 139 | var before = (d, f) => d != "" ? f + d : d; 140 | var emph = (d) => "" + d + ""; 141 | 142 | var paramsFormat = d3.format('s'); 143 | 144 | var getNumParams = function(layerGroup) { 145 | var numParams = d3.sum(layerGroup.values, d => d3.sum(d.getNumParameters())); 146 | return numParams ? after(paramsFormat(numParams), " parameters") : ""; 147 | }; 148 | 149 | var getCompactLabel = function(layerGroup) { 150 | if (layerGroup.values.length === 1) { 151 | return layerGroup.values.map(d => emph(d.getDescription()[0]))[0]; 152 | } 153 | else if (layerGroup.values.length <= 5) { 154 | return layerGroup.values.map(d => emph(d.getDescription()[0])).join(" + "); 155 | } 156 | return emph(layerGroup.key.split(GraphDrawer.LAYER_NAME_SEP)[0].toUpperCase()); 157 | } 158 | 159 | var getCompactClass = function(layerGroup) { 160 | if (layerGroup.values.length === 1) { 161 | return layerGroup.values[0].layer_type; 162 | } 163 | else if (layerGroup.values.length <= 5) { 164 | return layerGroup.values[0].layer_type; 165 | } 166 | return layerGroup.key.split(GraphDrawer.LAYER_NAME_SEP)[0].replace(/\d/g, ''); 167 | } 168 | 169 | var layers = d3.nest() 170 | .key(d => d.name.split(GraphDrawer.LAYER_GROUP_SEP)[0]) 171 | .entries(this.layers.values()); 172 | 173 | layers.forEach(function(layer, i){ 174 | var lastLayer = layer.values[layer.values.length - 1]; 175 | 176 | g.setNode(layer.key, { 177 | labelType: "html", 178 | label: getCompactLabel(layer) + before(getNumParams(layer), "
"), 179 | class: "layer layer-" + getCompactClass(layer), 180 | width: getWidth(lastLayer), 181 | height: getHeight(lastLayer) 182 | }); 183 | 184 | if (i > 0) { 185 | var prev = layers[i - 1]; 186 | var prevLayer = prev.values[prev.values.length - 1]; 187 | 188 | g.setEdge(prev.key, layer.key, { 189 | label: prevLayer.getOutputShape().join('x'), 190 | class: "edge" 191 | }); 192 | } 193 | }); 194 | 195 | return GraphDrawer.addRoundCorners(g); 196 | } 197 | 198 | createGraph() { 199 | 200 | var emph = (arr, j) => arr.map((d, i) => i === j ? "" + d + "" : d); 201 | 202 | var getWidth = this.getWidthScale(); 203 | 204 | // Create the input graph 205 | var g = new dagreD3.graphlib.Graph() 206 | .setGraph({}) 207 | .setDefaultEdgeLabel(() => ''); 208 | 209 | this.layerIterator((layer, i, pred) => { 210 | // var numParamsPerLayer = nj.sum(layer.getNumParameters()); 211 | g.setNode(layer.name, { 212 | labelType: "html", 213 | label: emph(layer.getDescription(), 0).join('
'), 214 | class: "layer layer-" + layer.layer_type, 215 | width: getWidth(layer) 216 | }); 217 | }); 218 | 219 | this.edges 220 | .filter((edge) => edge.from !== undefined && edge.to !== undefined) 221 | .forEach((edge) => { 222 | g.setEdge(edge.from, edge.to, { 223 | label: this.layers.get(edge.from).getOutputShape().join('x'), 224 | class: "edge" 225 | }); 226 | }); 227 | 228 | return GraphDrawer.addRoundCorners(g); 229 | } 230 | 231 | static addRoundCorners(graph) { 232 | graph.nodes().forEach(function(v) { 233 | var node = graph.node(v); 234 | 235 | // Round the corners of the nodes 236 | node.rx = node.ry = GraphDrawer.NODE_RADIUS; 237 | }); 238 | return graph; 239 | } 240 | 241 | static fromNet(model: Net.Model, compact: boolean = false){ 242 | var g = new GraphDrawer(compact); 243 | g.layers = model.layers; 244 | g.edges = model.edges; 245 | return g; 246 | } 247 | 248 | postProcessGraph() { 249 | var containerBBox = this.$elem[0][0].getBoundingClientRect(); 250 | 251 | // Set the SVG coordinates 252 | this.$svg.attr('width', this.width || this.graph.graph().width); 253 | this.$svg.attr('height', this.height || this.graph.graph().height); 254 | 255 | // Center the graph 256 | var xOffset = (containerBBox.width - this.graph.graph().width) / 2; 257 | if (xOffset) { 258 | this.$g.attr("transform", "translate(" + xOffset + ")"); 259 | } 260 | 261 | // Center the overflowing text 262 | this.$svg.selectAll('.layer foreignObject > div').style({ 263 | "margin": "0 -1000%", 264 | "display": "block" 265 | }); 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/Utils/_module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// -------------------------------------------------------------------------------- /src/_module.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | /// 5 | /// -------------------------------------------------------------------------------- /test/Net/CaffeModel.spec.js: -------------------------------------------------------------------------------- 1 | describe("A suite", function() { 2 | it("contains spec with an expectation", function() { 3 | expect(true).toBe(true); 4 | }); 5 | }); -------------------------------------------------------------------------------- /test/Net/Layers/ConvLayer.spec.js: -------------------------------------------------------------------------------- 1 | describe("ConvLayer", function() { 2 | 3 | // Create an input layer 4 | var _in = new Net.Layers.InputLayer({ 5 | width: 128, height: 128, depth: 3 6 | }); 7 | 8 | describe("constructor", function(){ 9 | 10 | var opt = {filters: 3, sx: 3, stride: 1, pad: 0, pred: [_in]}; 11 | 12 | it("should initialize filters", function() { 13 | 14 | var layer = new Net.Layers.ConvLayer(opt); 15 | 16 | var actual = layer.filters.length; 17 | var expected = 3; 18 | 19 | expect(actual).toEqual(expected); 20 | }); 21 | }); 22 | }); -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "module": "none", 5 | "target": "es5", 6 | "sourceMap": true, 7 | "outFile": "caffe.js", 8 | "baseUrl": "src", 9 | "types": [ 10 | ], 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ] 14 | }, 15 | "exclude": [ 16 | "node_modules" 17 | ] 18 | } --------------------------------------------------------------------------------