From 0d764bd7f828b51dcf78d5c4440da441f7f39a4c Mon Sep 17 00:00:00 2001 From: Leon Chen Date: Wed, 5 Oct 2016 02:09:29 -0400 Subject: [PATCH] start mnist VAE demo --- demos/src/index.css | 16 +++ demos/src/index.js | 7 +- demos/src/menu.js | 1 + demos/src/mnist-cnn.css | 17 --- demos/src/mnist-cnn.js | 2 +- demos/src/mnist-vae.css | 58 ++++++++++ demos/src/mnist-vae.js | 249 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 331 insertions(+), 19 deletions(-) create mode 100644 demos/src/mnist-vae.css create mode 100644 demos/src/mnist-vae.js diff --git a/demos/src/index.css b/demos/src/index.css index 9ea5ad5..5f14d57 100644 --- a/demos/src/index.css +++ b/demos/src/index.css @@ -60,3 +60,19 @@ body { background: rgba(255, 255, 255, 0.5); border-radius: 10px; } + +/****************************************************************/ +/* MDL overrides */ + +.demo .mdl-switch__label { + font-size: 14px !important; + color: #69707a; +} + +.demo .mdl-switch.is-checked .mdl-switch__thumb { + background: rgba(27, 188, 155, 0.6); +} + +.demo .mdl-switch.is-checked .mdl-switch__track { + background: rgba(27, 188, 155, 0.3); +} diff --git a/demos/src/index.js b/demos/src/index.js index 31c38e8..26e02e9 100644 --- a/demos/src/index.js +++ b/demos/src/index.js @@ -5,10 +5,12 @@ import './index.css' import { Menu } from './menu' import { Home } from './home' import { MnistCnn } from './mnist-cnn' +import { MnistVae } from './mnist-vae' Vue.component('menu', Menu) Vue.component('home', Home) Vue.component('mnist-cnn', MnistCnn) +Vue.component('mnist-vae', MnistVae) Vue.use(VueMdl.default) @@ -22,7 +24,10 @@ const app = new Vue({ // Simple routing function matchRoute () { - const routes = ['mnist-cnn'] + const routes = [ + 'mnist-cnn', + 'mnist-vae' + ] const { hash } = window.location const route = hash.substr(2) diff --git a/demos/src/menu.js b/demos/src/menu.js index c9c4592..54fc9a6 100644 --- a/demos/src/menu.js +++ b/demos/src/menu.js @@ -12,6 +12,7 @@ export const Menu = Vue.extend({
-
Basic Convnet - MNIST
+
Basic Convnet for MNIST
Loading...{{ loadingProgress }}%
diff --git a/demos/src/mnist-vae.css b/demos/src/mnist-vae.css new file mode 100644 index 0000000..fd9c33c --- /dev/null +++ b/demos/src/mnist-vae.css @@ -0,0 +1,58 @@ +.demo.mnist-vae .column { + display: flex; + align-items: center; + justify-content: center; +} + +.demo.mnist-vae .column.input-column { + justify-content: flex-end; +} + +.demo.mnist-vae .column.controls-column { + align-items: flex-start; + justify-content: flex-start; + padding-top: 80px; +} + +.demo.mnist-vae .column.output-column { + justify-content: center; +} + +.demo.mnist-vae .input-container { + display: inline-flex; + flex-direction: column; + margin: 20px; + position: relative; +} + +.demo.mnist-vae .input-label { + font-family: 'Nothing You Could Do', cursive; + font-size: 18px; + color: #69707a; + text-align: right; +} + +.demo.mnist-vae .input-label span.arrow { + font-size: 36px; + color: #CCCCCC; + position: absolute; + right: -32px; + top: 8px; +} + +.demo.mnist-vae .canvas-container { + display: inline-flex; + justify-content: flex-end; + margin: 10px 0; +} + +.demo.mnist-vae .canvas-container canvas { + background: white; + border: 15px solid rgba(27, 188, 155, 0.3); + transition: border-color 0.2s ease-in; +} + +.demo.mnist-vae .canvas-container canvas:hover { + border-color: rgba(27, 188, 155, 0.6); + cursor: crosshair; +} diff --git a/demos/src/mnist-vae.js b/demos/src/mnist-vae.js new file mode 100644 index 0000000..061e0ba --- /dev/null +++ b/demos/src/mnist-vae.js @@ -0,0 +1,249 @@ +/* global Vue */ + +import './mnist-vae.css' + +import debounce from 'lodash/debounce' +import * as utils from './utils' + +const MODEL_CONFIG = { + filepaths: { + model: '/demos/data/mnist_vae/mnist_vae.json', + weights: '/demos/data/mnist_vae/mnist_vae_weights.buf', + metadata: '/demos/data/mnist_vae/mnist_vae_metadata.json' + }, + gpu: false +} + +if (process.env.NODE_ENV === 'production') { + Object.assign(MODEL_CONFIG, { + filepaths: { + model: 'demos/data/mnist_vae/mnist_vae.json', + weights: 'https://transcranial.github.io/keras-js-demos-data/mnist_vae/mnist_vae_weights.buf', + metadata: 'demos/data/mnist_vae/mnist_vae_metadata.json' + } + }) +} + +const LAYER_DISPLAY_CONFIG = { + 'dense_10': { + heading: 'ReLU activation, output dimensions = 128', + scalingFactor: 2 + }, + 'dense_11': { + heading: 'ReLU activation, output dimensions = 25088 (64 x 14 x 14)', + scalingFactor: 2 + }, + 'deconvolution2d_10': { + heading: '64 3x3 filters, border mode same, 1x1 strides, ReLU activation', + scalingFactor: 2 + }, + 'deconvolution2d_11': { + heading: '64 3x3 filters, border mode same, 1x1 strides, ReLU activation', + scalingFactor: 2 + }, + 'deconvolution2d_12': { + heading: '64 2x2 filters, border mode valid, 2x2 strides, ReLU activation', + scalingFactor: 2 + }, + 'convolution2d_8': { + heading: '1 2x2 filters, border mode valid, 1x1 strides, sigmoid activation', + scalingFactor: 2 + } +} + +/** + * + * VUE COMPONENT + * + */ +export const MnistVae = Vue.extend({ + template: ` +
+
Convolutional Variational Autoencoder, trained on MNIST
+
+ Loading...{{ loadingProgress }}% +
+
+
+
+
Move around the latent space
+
+ +
+
+
+
+ Use GPU +
+
+
+
+
+
+ {{ layerResult.layerClass }} + {{ layerDisplayConfig[layerResult.name].heading }} +
+
+ + +
+
+
+
+ `, + + data: function () { + return { + model: new KerasJS.Model(MODEL_CONFIG), + modelLoading: true, + input: new Float32Array(2), + output: new Float32Array(27 * 27), + crosshairsActivated: false, + coordinates: [0, 0], + layerResultImages: [], + layerDisplayConfig: LAYER_DISPLAY_CONFIG, + useGpu: MODEL_CONFIG.gpu + } + }, + + computed: { + loadingProgress: function () { + return this.model.getLoadingProgress() + } + }, + + created: function () { + // initialize KerasJS model + this.model.initialize() + this.model.ready().then(() => { + this.modelLoading = false + this.getIntermediateResults() + }) + }, + + methods: { + + activateCrosshairs: function (e) { + this.crosshairsActivated = true + }, + + deactivateCrosshairs: function (e) { + this.crosshairsActivated = false + }, + + drawCrosshairs: function (e) { + if (!this.crosshairsActivated) return + + const [x, y] = this.getEventCanvasCoordinates(e) + const ctx = document.getElementById('input-canvas').getContext('2d') + ctx.clearRect(0, 0, 200, 200) + ctx.strokeStyle = '#1BBC9B' + ctx.beginPath() + ctx.moveTo(x, 0) + ctx.lineTo(x, 200) + ctx.stroke() + ctx.beginPath() + ctx.moveTo(0, y) + ctx.lineTo(200, y) + ctx.stroke() + }, + + getEventCanvasCoordinates: function (e) { + const borderSize = 15 + let { clientX, clientY } = e + // for touch event + if (e.touches && e.touches.length) { + clientX = e.touches[0].clientX + clientY = e.touches[0].clientY + } + + const canvas = document.getElementById('input-canvas') + const { left, top } = canvas.getBoundingClientRect() + const [x, y] = [clientX - left - borderSize, clientY - top - borderSize] + return [x, y] + }, + + selectCoordinate: function (e) { + + }, + + getIntermediateResults: function () { + let results = [] + for (let [name, layer] of this.model.modelLayersMap.entries()) { + if (name === 'input') continue + + const layerClass = layer.layerClass || '' + + let images = [] + if (layer.result && layer.result.tensor.shape.length === 3) { + images = utils.unroll3Dtensor(layer.result.tensor) + } else if (layer.result && layer.result.tensor.shape.length === 2) { + images = [utils.image2Dtensor(layer.result.tensor)] + } else if (layer.result && layer.result.tensor.shape.length === 1) { + images = [utils.image1Dtensor(layer.result.tensor)] + } + results.push({ + name, + layerClass, + images + }) + } + this.layerResultImages = results + setTimeout(() => { + this.showIntermediateResults() + }, 0) + }, + + showIntermediateResults: function () { + this.layerResultImages.forEach((result, layerNum) => { + const scalingFactor = this.layerDisplayConfig[result.name].scalingFactor + result.images.forEach((image, imageNum) => { + let ctx = document.getElementById(`intermediate-result-${layerNum}-${imageNum}`).getContext('2d') + ctx.putImageData(image, 0, 0) + let ctxScaled = document.getElementById(`intermediate-result-${layerNum}-${imageNum}-scaled`).getContext('2d') + ctxScaled.save() + ctxScaled.scale(scalingFactor, scalingFactor) + ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height) + ctxScaled.drawImage(document.getElementById(`intermediate-result-${layerNum}-${imageNum}`), 0, 0) + ctxScaled.restore() + }) + }) + }, + + clearIntermediateResults: function () { + this.layerResultImages.forEach((result, layerNum) => { + const scalingFactor = this.layerDisplayConfig[result.name].scalingFactor + result.images.forEach((image, imageNum) => { + let ctxScaled = document.getElementById(`intermediate-result-${layerNum}-${imageNum}-scaled`).getContext('2d') + ctxScaled.save() + ctxScaled.scale(scalingFactor, scalingFactor) + ctxScaled.clearRect(0, 0, ctxScaled.canvas.width, ctxScaled.canvas.height) + ctxScaled.restore() + }) + }) + }, + + toggleGpu: function () { + this.model.gpu = !this.useGpu + } + } +})