diff --git a/demos/src/mnist-cnn.css b/demos/src/mnist-cnn.css index f771de7..f96c657 100644 --- a/demos/src/mnist-cnn.css +++ b/demos/src/mnist-cnn.css @@ -93,7 +93,7 @@ border-bottom: 2px solid $color-1-lighter; .output-label { - font-family: 'Inconsolata', sans-serif; + font-family: $font-2; font-size: 1.5rem; color: $color-2; } diff --git a/demos/src/mnist-cnn.js b/demos/src/mnist-cnn.js index f283e34..2e346eb 100644 --- a/demos/src/mnist-cnn.js +++ b/demos/src/mnist-cnn.js @@ -110,8 +110,6 @@ export const MnistCnn = Vue.extend({ }, ready: function () { - // initialize KerasJS model - this.model.initialize() this.model.ready().then(() => { this.modelLoading = false this.$nextTick(function () { diff --git a/demos/src/mnist-vae.js b/demos/src/mnist-vae.js index d6999db..3b97fb5 100644 --- a/demos/src/mnist-vae.js +++ b/demos/src/mnist-vae.js @@ -81,8 +81,6 @@ export const MnistVae = Vue.extend({ }, ready: function () { - // initialize KerasJS model - this.model.initialize() this.model.ready().then(() => { this.modelLoading = false this.$nextTick(function () { diff --git a/demos/src/resnet50.css b/demos/src/resnet50.css index 2f98175..63b7409 100644 --- a/demos/src/resnet50.css +++ b/demos/src/resnet50.css @@ -73,50 +73,83 @@ } .column.output-column { - justify-content: center; + justify-content: flex-start; .output { + height: 160px; + display: flex; + flex-direction: column; + align-items: flex-end; + justify-content: center; user-select: none; cursor: default; - } - } - .layer-results-container { - position: relative; - - .layer-result { - position: relative; - z-index: 1; - margin: 30px 20px; - background: white; - border-radius: 10px; - padding: 20px; - overflow-x: auto; - - .layer-result-heading { - font-size: 1rem; - color: #999999; - margin-bottom: 10px; + .output-class { display: flex; - flex-direction: column; - font-size: 12px; + flex-direction: row; + align-items: center; + justify-content: center; + padding: 6px 0; - span.layer-class { - color: $color-1; - font-size: 14px; - font-weight: bold; + .output-label { + font-family: $font-2; + font-size: 20px; + color: $color-2; + padding: 0 6px; + border-right: 2px solid $color-1-lighter; + } + + .output-bar { + height: 8px; + transition: height 0.2s ease-out; + } + + .output-bar-rest { + height: 8px; + background: none; + } + + .output-value { + width: 50px; + font-family: $font-2; + font-size: 16px; + color: $color-2; } } - .layer-result-canvas-container { - display: inline-flex; - flex-wrap: wrap; - background: white; - - canvas { - border: 1px solid lightgray; - margin: 1px; + .output-class.predicted { + .output-label { + color: $color-1; + border-right-color: $color-1; } + + .output-value { + color: $color-1; + } + } + } + } + + .architecture-container { + position: relative; + + .layer { + display: inline-block; + position: relative; + background: white; + border-radius: 5px; + padding: 10px; + + .layer-class-name { + color: $color-1; + font-size: 14px; + font-weight: bold; + } + + .layer-details { + color: #999999; + font-size: 12px; + font-weight: bold; } } } diff --git a/demos/src/resnet50.js b/demos/src/resnet50.js index e11da06..fa809cf 100644 --- a/demos/src/resnet50.js +++ b/demos/src/resnet50.js @@ -23,11 +23,18 @@ const MODEL_CONFIG = { const IMAGE_URL_LIST = [ { name: 'cat', value: 'http://i.imgur.com/CzXTtJV.jpg' }, - { name: 'dog', value: 'URL2' } + { name: 'dog', value: 'http://i.imgur.com/OB0y6MR.jpg' }, + { name: 'bridge', value: 'http://i.imgur.com/Bvke53p.jpg' } ] -const LAYER_DISPLAY_CONFIG = { -} +const ARCHITECTURE_DIAGRAM = [ + { + name: 'zeropadding2d_1', + className: 'ZeroPadding2D', + details: '3x3 padding', + layout: 'full' + }, +] /** * @@ -43,14 +50,14 @@ export const ResNet50 = Vue.extend({ return { model: new KerasJS.Model(Object.assign({ gpu: this.hasWebgl }, MODEL_CONFIG)), modelLoading: true, + modelRunning: false, imageURLInput: null, imageURLSelect: null, imageURLSelectList: IMAGE_URL_LIST, imageLoading: false, imageLoadingError: false, - output: new Float32Array(1000), - layerResultImages: [], - layerDisplayConfig: LAYER_DISPLAY_CONFIG, + output: null, + architectureDiagram: ARCHITECTURE_DIAGRAM, useGpu: this.hasWebgl } }, @@ -58,17 +65,16 @@ export const ResNet50 = Vue.extend({ computed: { loadingProgress: function () { return this.model.getLoadingProgress() + }, + outputClasses: function () { + if (!this.output) return [] + return utils.imagenetClassesTopK(this.output, 5) } }, ready: function () { - // initialize KerasJS model - this.model.initialize() this.model.ready().then(() => { this.modelLoading = false - this.$nextTick(function () { - //this.getIntermediateResults() - }) }) }, @@ -102,9 +108,12 @@ export const ResNet50 = Vue.extend({ ctx.drawImage(img, 0, 0) this.imageLoadingError = false this.imageLoading = false + this.modelRunning = true // model predict this.$nextTick(function () { - this.runModel() + setTimeout(() => { + this.runModel() + }, 200) }) } }, @@ -140,63 +149,7 @@ export const ResNet50 = Vue.extend({ } const outputData = this.model.predict(inputData) this.output = outputData['fc1000'] - console.log(JSON.stringify(utils.imagenetClassesTopK(this.output, 5))) - //this.getIntermediateResults() - }, - - getIntermediateResults: function () { - let results = [] - for (let [name, layer] of this.model.modelLayersMap.entries()) { - const layerClass = layer.layerClass || '' - if (layerClass === 'InputLayer') continue - - 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) => { - const ctx = document.getElementById(`intermediate-result-${layerNum}-${imageNum}`).getContext('2d') - ctx.putImageData(image, 0, 0) - const 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) => { - const 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() - }) - }) + this.modelRunning = false } } }) diff --git a/demos/src/resnet50.template.html b/demos/src/resnet50.template.html index 7b112d2..4407b5e 100644 --- a/demos/src/resnet50.template.html +++ b/demos/src/resnet50.template.html @@ -7,8 +7,15 @@ Loading...{{ loadingProgress }}%
-
Select an image from dropdown or enter a valid image URL
+
Enter a valid image URL or select an image from the dropdown:
+ + or - or -
- +
Error loading URL
@@ -39,37 +39,35 @@
Use GPU
+
+
{{ entry.name }}
+
+
+
{{ Math.round(100 * entry.probability) }}%
+
- +
diff --git a/demos/src/utils/index.js b/demos/src/utils/index.js index ed596bf..3d26297 100644 --- a/demos/src/utils/index.js +++ b/demos/src/utils/index.js @@ -108,8 +108,12 @@ export function tensorStats (tensor) { * calculates min and max for a ndarray tensor */ export function tensorMinMax (tensor) { - const min = Math.min(...tensor.data) - const max = Math.max(...tensor.data) + let min = Infinity + let max = -Infinity + for (let i = 0, len = tensor.data.length; i < len; i++) { + if (tensor.data[i] < min) min = tensor.data[i] + if (tensor.data[i] > max) max = tensor.data[i] + } return { min, max } } diff --git a/demos/webpack.dev.config.js b/demos/webpack.dev.config.js index 0896425..0efd3b6 100644 --- a/demos/webpack.dev.config.js +++ b/demos/webpack.dev.config.js @@ -11,7 +11,7 @@ module.exports = { path: path.join(__dirname, 'dist'), filename: 'bundle.js' }, - devtool: 'cheap-module-eval-source-map', + devtool: 'eval-source-map', module: { loaders: [ {