diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d30f40e..0000000 --- a/.gitignore +++ /dev/null @@ -1,21 +0,0 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. - -# dependencies -/node_modules - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/README.md b/README.md deleted file mode 100644 index dfc8e87..0000000 --- a/README.md +++ /dev/null @@ -1,48 +0,0 @@ -## Convolution Visualizer - -Live at [Convolution Visualizer](https://ezyang.github.io/convolution-visualizer/index.html). - -Made with the help of our fine friends at [React](https://reactjs.org/) -and [D3.js](https://d3js.org/). - -### Things to do - -Want to play around with the code? Clone this repository and run `yarn -start` to start a development instance. The main code lives in -`src/index.js`. This [React manual](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md) may be of interest. - -Here are some project ideas: - -* Tweak the CSS so that the weight and output matrices - are displayed to the right of the input if there is space. -* Add a slider for adjusting speed of the animation. -* Add a slider which specifies the animation timestep you - are on; this way, you can run the animation forward and - backward by dragging the slider. -* Add output size and output padding sliders. When these - sliders are adjusted, you recompute the input size using - the transposed convolution formula. -* Add an onClick handler, which pins your selection at - the current mouse collection until another click - occurs (disabling the hover behavior.) -* Add a mode which, when enabled, labels cells with variables and - renders the mathematical formula to compute the output - cell you are moused over. -* Render code for PyTorch (or your favorite framework) which performs the - selected convolution. -* Add more exotic convolution types like circular convolution. -* Add a "true" convolution mode, where the weights are flipped - before multiplication. -* Support bigger input sizes than 16 (decreasing the size of - the squares when inputs are large), and optimize the code so that it - still runs quickly in these cases. -* Support assymmetric inputs/kernels/strides/dilations. - -Bigger projects: - -* Create an in-browser canvas application, which convolves - an input image against a displayed filter. Bonus points - if your canvas supports painting capabilities. -* Design a visualization which demonstrates the principles - of group convolution, allowing you to slide from standard - to depthwise convolution. diff --git a/asset-manifest.json b/asset-manifest.json new file mode 100644 index 0000000..8dd9de3 --- /dev/null +++ b/asset-manifest.json @@ -0,0 +1,6 @@ +{ + "main.css": "static/css/main.0a8c4c1d.css", + "main.css.map": "static/css/main.0a8c4c1d.css.map", + "main.js": "static/js/main.52d8c6a6.js", + "main.js.map": "static/js/main.52d8c6a6.js.map" +} \ No newline at end of file diff --git a/public/favicon.ico b/favicon.ico similarity index 100% rename from public/favicon.ico rename to favicon.ico diff --git a/index.html b/index.html new file mode 100644 index 0000000..b064348 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ +
\ No newline at end of file
diff --git a/public/manifest.json b/manifest.json
similarity index 100%
rename from public/manifest.json
rename to manifest.json
diff --git a/package.json b/package.json
deleted file mode 100644
index aaa913f..0000000
--- a/package.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "name": "convolution-visualizer",
- "version": "0.1.0",
- "private": true,
- "dependencies": {
- "d3-scale-chromatic": "^1.2.0",
- "d3v4": "^4.2.2",
- "gh-pages": "^1.1.0",
- "react": "^16.2.0",
- "react-dom": "^16.2.0",
- "react-scripts": "1.1.1"
- },
- "homepage": "https://ezyang.github.io/convolution-visualizer",
- "scripts": {
- "start": "react-scripts start",
- "build": "react-scripts build",
- "test": "react-scripts test --env=jsdom",
- "eject": "react-scripts eject",
- "predeploy": "yarn build",
- "deploy": "gh-pages -d build"
- }
-}
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index 4c20600..0000000
--- a/public/index.html
+++ /dev/null
@@ -1,40 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/service-worker.js b/service-worker.js
new file mode 100644
index 0000000..f74600e
--- /dev/null
+++ b/service-worker.js
@@ -0,0 +1 @@
+"use strict";var precacheConfig=[["/convolution-visualizer/index.html","c31fcbcdc948f11d0ad4710a324cd486"],["/convolution-visualizer/static/css/main.0a8c4c1d.css","4224debf179b937ff2fb2bc55511e387"],["/convolution-visualizer/static/js/main.52d8c6a6.js","77a2b473388c980290cca51da8c8467b"]],cacheName="sw-precache-v3-sw-precache-webpack-plugin-"+(self.registration?self.registration.scope:""),ignoreUrlParametersMatching=[/^utm_/],addDirectoryIndex=function(e,t){var n=new URL(e);return"/"===n.pathname.slice(-1)&&(n.pathname+=t),n.toString()},cleanResponse=function(e){return e.redirected?("body"in e?Promise.resolve(e.body):e.blob()).then(function(t){return new Response(t,{headers:e.headers,status:e.status,statusText:e.statusText})}):Promise.resolve(e)},createCacheKey=function(e,t,n,r){var a=new URL(e);return r&&a.pathname.match(r)||(a.search+=(a.search?"&":"")+encodeURIComponent(t)+"="+encodeURIComponent(n)),a.toString()},isPathWhitelisted=function(e,t){if(0===e.length)return!0;var n=new URL(t).pathname;return e.some(function(e){return n.match(e)})},stripIgnoredUrlParameters=function(e,t){var n=new URL(e);return n.hash="",n.search=n.search.slice(1).split("&").map(function(e){return e.split("=")}).filter(function(e){return t.every(function(t){return!t.test(e[0])})}).map(function(e){return e.join("=")}).join("&"),n.toString()},hashParamName="_sw-precache",urlsToCacheKeys=new Map(precacheConfig.map(function(e){var t=e[0],n=e[1],r=new URL(t,self.location),a=createCacheKey(r,hashParamName,n,/\.\w{8}\./);return[r.toString(),a]}));function setOfCachedUrls(e){return e.keys().then(function(e){return e.map(function(e){return e.url})}).then(function(e){return new Set(e)})}self.addEventListener("install",function(e){e.waitUntil(caches.open(cacheName).then(function(e){return setOfCachedUrls(e).then(function(t){return Promise.all(Array.from(urlsToCacheKeys.values()).map(function(n){if(!t.has(n)){var r=new Request(n,{credentials:"same-origin"});return fetch(r).then(function(t){if(!t.ok)throw new Error("Request for "+n+" returned a response with status "+t.status);return cleanResponse(t).then(function(t){return e.put(n,t)})})}}))})}).then(function(){return self.skipWaiting()}))}),self.addEventListener("activate",function(e){var t=new Set(urlsToCacheKeys.values());e.waitUntil(caches.open(cacheName).then(function(e){return e.keys().then(function(n){return Promise.all(n.map(function(n){if(!t.has(n.url))return e.delete(n)}))})}).then(function(){return self.clients.claim()}))}),self.addEventListener("fetch",function(e){if("GET"===e.request.method){var t,n=stripIgnoredUrlParameters(e.request.url,ignoreUrlParametersMatching),r="index.html";(t=urlsToCacheKeys.has(n))||(n=addDirectoryIndex(n,r),t=urlsToCacheKeys.has(n));var a="/convolution-visualizer/index.html";!t&&"navigate"===e.request.mode&&isPathWhitelisted(["^(?!\\/__).*"],e.request.url)&&(n=new URL(a,self.location).toString(),t=urlsToCacheKeys.has(n)),t&&e.respondWith(caches.open(cacheName).then(function(e){return e.match(urlsToCacheKeys.get(n)).then(function(e){if(e)return e;throw Error("The cached response that was expected is missing.")})}).catch(function(t){return console.warn('Couldn\'t serve response for "%s" from cache: %O',e.request.url,t),fetch(e.request)}))}});
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
deleted file mode 100644
index e5d2fca..0000000
--- a/src/index.css
+++ /dev/null
@@ -1,37 +0,0 @@
-h1 { margin-bottom: 0 }
-
-.author { margin-left: 2em; }
-
-p {
- max-width: 80em;
-}
-
-body {
- font: 14px "Century Gothic", Futura, sans-serif;
- margin: 20px;
- margin-right:40px;
-}
-
-.form {
- margin-bottom: 1em;
- float: left;
-}
-
-.viewport {
- margin-left: 16em;
-}
-
-.grid-container {
- margin-bottom: 1em;
-}
-
-table {
- border-collapse: collapse;
-}
-
-td {
- background: #fff;
- border: 1px solid #999;
- height: 34px;
- width: 34px;
-}
diff --git a/src/index.js b/src/index.js
deleted file mode 100644
index 3064c9a..0000000
--- a/src/index.js
+++ /dev/null
@@ -1,761 +0,0 @@
-import React from 'react';
-import ReactDOM from 'react-dom';
-import * as d3 from 'd3v4';
-import './index.css';
-
-/**
- * An HTML5 range slider and associated raw text input.
- *
- * Properties:
- * - min: The minimum allowed value for the slider range
- * - max: The maximum allowed value for the slider range
- * - value: The current value of the slider
- * - disabled: Whether or not to disable the slider. A slider
- * is automatically disabled when min == max.
- * - onChange: Callback when the value of this slider changes.
- */
-function Slider(props) {
- const max = parseInt(props.max, 10);
- const min = parseInt(props.min, 10);
- const maxLength = max ? Math.ceil(Math.log10(max)) : 1;
- const disabled = props.disabled || min >= max;
- return (
-
-
-
-
- );
-}
-
-/**
- * Create a 1-dimensional array of size 'length', where the 'i'th entry
- * is initialized to 'f(i)', or 'undefined' if 'f' is not passed.
- */
-function array1d(length, f) {
- return Array.from({length: length}, f ? ((v, i) => f(i)) : undefined);
-}
-
-/**
- * Create a 2-dimensional array of size 'height' x 'width', where the 'i','j' entry
- * is initialized to 'f(i, j)', or 'undefined' if 'f' is not passed.
- */
-function array2d(height, width, f) {
- return Array.from({length: height}, (v, i) => Array.from({length: width}, f ? ((w, j) => f(i, j)) : undefined));
-}
-
-/**
- * The classic convolution output size formula for a single dimension.
- *
- * The derivation for many special cases is worked out in:
- * http://deeplearning.net/software/theano/tutorial/conv_arithmetic.html
- */
-function computeOutputSize(input_size, weight_size, padding, dilation, stride) {
- return Math.floor((input_size + 2 * padding - dilation * (weight_size - 1) - 1) / stride + 1);
-}
-
-/**
- * Test if a set of parameters is valid.
- */
-function paramsOK(input_h, input_w, weight_h, weight_w, padding, dilation, stride_h, stride_w) {
- const output_h = computeOutputSize(input_h, weight_h, padding, dilation, stride_h);
- const output_w = computeOutputSize(input_w, weight_w, padding, dilation, stride_w);
- return output_h > 0 && output_w > 0;
-}
-
-
-// We use the next two functions (maxWhile and minWhile) to
-// inefficiently compute the bounds for various parameters
-// given fixed values for other parameters.
-
-/**
- * Given a predicate 'pred' and a starting integer 'start',
- * find the largest integer i >= start such that 'pred(i)'
- * is true OR end, whichever is smaller.
- */
-function maxWhile(start, end, pred) {
- for (let i = start; i <= end; i++) {
- if (pred(i)) continue;
- return i - 1;
- }
- return end;
-}
-
-/**
- * Given a predicate 'pred' and a starting integer 'start',
- * find the smallest integer i <= start such that 'pred(i)'
- * is true OR end, whichever is larger.
- */
-function minWhile(start, end, pred) {
- for (let i = start; i >= end; i--) {
- if (pred(i)) continue;
- return i + 1;
- }
- return end;
-}
-
-/**
- * Return the color at 0 <= p <= 1 for the RGB linear interpolation
- * between color (0) and white (1).
- */
-function whiten(color, p) {
- return d3.interpolateRgb(color, "white")(p)
-}
-
-/**
- * Top-level component for the entire visualization. This component
- * controls top level parameters like input sizes, but not the mouse
- * interaction with the actual visualized grids.
- */
-class App extends React.Component {
- constructor(props) {
- super(props);
- this.state = {
- input_height: 5,
- input_width: 5,
- weight_height: 3,
- weight_width: 3,
- padding: 0,
- dilation: 1,
- stride_height: 1,
- stride_width: 1,
- // State to control the UI mode
- inputShape: 'square',
- kernelShape: 'square',
- strideShape: 'square',
- };
- }
-
- // React controlled components clobber saved browser state, so
- // instead we manually save/load our state from localStorage.
-
- componentDidMount() {
- const state = localStorage.getItem("state");
- if (state) {
- this.setState(JSON.parse(state));
- }
- }
-
- componentDidUpdate() {
- localStorage.setItem("state", JSON.stringify(this.state));
- }
-
- // A smarter handler for dimension changes that respects the current shape mode.
- handleDimensionChange = (type, dimension) => (e) => {
- const r = parseInt(e.target.value, 10);
- if (isNaN(r)) return;
-
- // TODO: transposed convolution
- // FIX: Correctly map the 'type' string to its corresponding state key
- let shapeKey;
- if (type === 'input') shapeKey = 'inputShape';
- else if (type === 'weight') shapeKey = 'kernelShape';
- else if (type === 'stride') shapeKey = 'strideShape';
-
- const shape = this.state[shapeKey];
-
- if (shape === 'square') {
- // In square mode, the slider controls both height and width
- this.setState({
- [`${type}_height`]: r,
- [`${type}_width`]: r,
- });
- } else {
- // In rectangular mode, sliders are independent
- this.setState({
- [`${type}_${dimension}`]: r
- });
- }
- };
-
- // Handles the user switching between "Square" and "Rectangular"
- handleShapeChange = (type) => (e) => {
- const newShape = e.target.value;
- const key = `${type}Shape`;
-
- if (newShape === 'square') {
- // When switching back to square, make width equal to height
- const height = this.state[`${type}_height`];
- this.setState({
- [key]: newShape,
- [`${type}_width`]: height,
- });
- } else {
- this.setState({ [key]: newShape });
- }
- };
-
- render() {
- const { input_height, input_width, weight_height, weight_width, padding, dilation, stride_height, stride_width, inputShape, kernelShape, strideShape } = this.state;
-
- const padded_input_height = input_height + padding * 2;
- const padded_input_width = input_width + padding * 2;
-
- const output_height = computeOutputSize(input_height, weight_height, padding, dilation, stride_height);
- const output_width = computeOutputSize(input_width, weight_width, padding, dilation, stride_width);
-
- const output = array2d(output_height, output_width, (i, j) => array2d(weight_height, weight_width));
-
- for (let h_out = 0; h_out < output_height; h_out++) {
- for (let w_out = 0; w_out < output_width; w_out++) {
- for (let h_kern = 0; h_kern < weight_height; h_kern++) {
- for (let w_kern = 0; w_kern < weight_width; w_kern++) {
- const h_im = h_out * stride_height + h_kern * dilation;
- const w_im = w_out * stride_width + w_kern * dilation;
- output[h_out][w_out][h_kern][w_kern] = h_im * padded_input_width + w_im;
- }
- }
- }
- }
-
- // Make an extended params dictionary with our new computed values
- // to pass to the inner component.
- const params = Object.assign({
- padded_input_height: padded_input_height,
- padded_input_width: padded_input_width,
- output_height: output_height,
- output_width: output_width,
- output: output,
- }, this.state);
-
- const onChange = (state_key) => (e) => {
- const r = parseInt(e.target.value, 10);
- // Text inputs can sometimes temporarily be in invalid states.
- // If it's not a valid number, refuse to set it.
- if (!isNaN(r)) {
- this.setState({[state_key]: r});
- }
- };
-
- // An arbitrary constant I found aesthetically pleasing.
- const max_input_size = 16;
-
- return (
- - This interactive visualization demonstrates how various convolution parameters - affect shapes and data dependencies between the input, weight and - output matrices. Hovering over an input/output will highlight the - corresponding output/input, while hovering over an weight - will highlight which inputs were multiplied into that weight to - compute an output. (Strictly speaking, the operation visualized - here is a correlation, not a convolution, as a true - convolution flips its weights before performing a correlation. - However, most deep learning frameworks still call these convolutions, - and in the end it's all the same to gradient descent.) -
- -| (empty) |
=1)return+e(t[r-1],r-1,t);var r,i=(r-1)*n,a=Math.floor(i),u=+e(t[a],a,t);return u+(+e(t[a+1],a+1,t)-u)*(i-a)}}function g(t,e,r){return t=Id.call(t,o).sort(n),Math.ceil((r-e)/(2*(y(t,.75)-y(t,.25))*Math.pow(t.length,-1/3)))}function m(t,n,e){return Math.ceil((e-n)/(3.5*u(t)*Math.pow(t.length,-1/3)))}function _(t,n){var e,r,i=-1,o=t.length;if(null==n){for(;++i \n This interactive visualization demonstrates how various convolution parameters\n affect shapes and data dependencies between the input, weight and\n output matrices. Hovering over an input/output will highlight the\n corresponding output/input, while hovering over an weight\n will highlight which inputs were multiplied into that weight to\n compute an output. (Strictly speaking, the operation visualized\n here is a correlation, not a convolution, as a true\n convolution flips its weights before performing a correlation.\n However, most deep learning frameworks still call these convolutions,\n and in the end it's all the same to gradient descent.)\n =g)<<1|t>=y)&&(l=d[d.length-1],d[d.length-1]=d[d.length-1-c],d[d.length-1-c]=l)}else{var m=t-+this._x.call(null,v.data),_=n-+this._y.call(null,v.data),b=m*m+_*_;if(bhv)if(d>vv-hv)l.moveTo(f*Math.cos(h),f*Math.sin(h)),l.arc(0,0,f,h,p,!v),s>hv&&(l.moveTo(s*Math.cos(p),s*Math.sin(p)),l.arc(0,0,s,p,h,v));else{var y,g,m=h,_=p,b=h,x=p,w=d,M=d,k=u.apply(this,arguments)/2,C=k>hv&&(i?+i.apply(this,arguments):Math.sqrt(s*s+f*f)),E=Math.min(Math.abs(f-s)/2,+r.apply(this,arguments)),T=E,S=E;if(C>hv){var N=an(C/s*Math.sin(k)),A=an(C/f*Math.sin(k));(w-=2*N)>hv?(N*=v?1:-1,b+=N,x-=N):(w=0,b=x=(h+p)/2),(M-=2*A)>hv?(A*=v?1:-1,m+=A,_-=A):(M=0,m=_=(h+p)/2)}var P=f*Math.cos(m),O=f*Math.sin(m),I=s*Math.cos(x),R=s*Math.sin(x);if(E>hv){var D=f*Math.cos(_),L=f*Math.sin(_),U=s*Math.cos(b),z=s*Math.sin(b);if(d>1)+m+t+_+w.slice(x)}return w+m+t+_}t=ii(t);var e=t.fill,a=t.align,u=t.sign,l=t.symbol,c=t.zero,s=t.width,f=t.comma,h=t.precision,p=t.type,d="$"===l?i[0]:"#"===l&&/[boxX]/.test(p)?"0"+p.toLowerCase():"",v="$"===l?i[1]:/[%p]/.test(p)?"%":"",y=vm[p],g=!p||/[defgprs%]/.test(p);return h=null==h?p?6:12:/[gprs]/.test(p)?Math.max(1,Math.min(21,h)):Math.max(0,Math.min(20,h)),n.toString=function(){return t+""},n}function e(t,e){var r=n((t=ii(t),t.type="f",t)),i=3*Math.max(-8,Math.min(8,Math.floor(Jr(e)/3))),o=Math.pow(10,-i),a=mm[8+i/3];return function(t){return r(o*t)+a}}var r=t.grouping&&t.thousands?ti(t.grouping,t.thousands):ai,i=t.currency,o=t.decimal;return{format:n,formatPrefix:e}}function li(n){return gm=ui(n),t.format=gm.format,t.formatPrefix=gm.formatPrefix,gm}function ci(t){return Math.max(0,-Jr(Math.abs(t)))}function si(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Jr(n)/3)))-Jr(Math.abs(t)))}function fi(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,Jr(n)-Jr(t))+1}function hi(t){if(0<=t.y&&t.y<100){var n=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return n.setFullYear(t.y),n}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function pi(t){if(0<=t.y&&t.y<100){var n=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return n.setUTCFullYear(t.y),n}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function di(t){return{y:t,m:0,d:1,H:0,M:0,S:0,L:0}}function vi(t){function n(t,n){return function(e){var r,i,o,a=[],u=-1,l=0,c=t.length;for(e instanceof Date||(e=new Date(+e));++u=n-1){var c=l[t];return c.x0=r,c.y0=i,c.x1=a,c.y1=u,void 0}for(var f=s[t],h=e/2+f,p=t+1,d=n-1;p1?(p.on(t,n),o):p.on(t)}}}function Ms(){function t(t){var n,u=i.length,l=qt(i,bs,xs).visitAfter(e);for(a=t,n=0;n=s)){(t.data!==o||t.next)&&(0===i&&(i=ds(),p+=i*i),0===l&&(l=ds(),p+=l*l),pConvolution Visualizer
\n props.onMouseEnter(e, i, j)) : undefined}\n onMouseLeave={props.onMouseLeave ?\n ((e) => props.onMouseLeave(e, i, j)) : undefined} />\n });\n return {xrow} ;\n });\n return {xgrid}
;\n}\n\n// ========================================\n\nReactDOM.render(\n