add inception v3 imagenet demo

This commit is contained in:
Leon Chen
2016-10-11 00:29:43 -04:00
parent da3a673dff
commit 130b346b5c
4 changed files with 2653 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+216
View File
@@ -0,0 +1,216 @@
@import './_variables.css';
.demo.inception-v3 {
.top-container {
margin: 10px;
position: relative;
display: flex;
.input-container {
.input-label {
font-family: $font-3;
font-size: 16px;
color: $color-2;
text-align: left;
user-select: none;
cursor: default;
}
.image-url {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
position: relative;
span {
margin: 0 10px;
font-family: $font-3;
font-size: 16px;
color: $color-2;
}
}
}
.controls {
width: 250px;
margin-left: 40px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
& > .mdl-switch {
margin-bottom: 5px;
.mdl-switch__label {
font-size: 12px !important;
}
}
}
}
.columns.input-output {
max-width: 800px;
margin: 0 auto;
.column {
display: flex;
align-items: center;
justify-content: center;
}
.column.input-column {
position: relative;
.loading-indicator {
position: absolute;
top: 0;
left: -10px;
display: flex;
flex-direction: column;
align-self: flex-start;
.mdl-spinner {
margin: 20px;
align-self: center;
}
.error {
color: $color-err;
font-size: 14px;
font-family: $font-1;
margin: 20px;
}
}
.canvas-container {
display: inline-flex;
justify-content: flex-end;
canvas {
background: white;
}
}
}
.column.output-column {
.output {
width: 370px;
height: 160px;
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
.output-class {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding: 6px 0;
.output-label {
text-align: right;
width: 200px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-family: $font-2;
font-size: 18px;
color: $color-2;
padding: 0 6px;
border-right: 2px solid $color-1-lighter;
}
.output-bar {
height: 8px;
transition: width 0.2s ease-out;
}
.output-value {
text-align: left;
margin-left: 5px;
font-family: $font-2;
font-size: 14px;
color: $color-2;
}
}
.output-class.predicted {
.output-label {
color: $color-1;
border-left-color: $color-1;
}
.output-value {
color: $color-1;
}
}
}
}
}
.architecture-container {
min-width: 800px;
max-width: 1200px;
margin: 0 auto;
position: relative;
.layers-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 5px;
position: relative;
z-index: 1;
.layer-column {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 5px;
.layer {
display: inline-block;
background: white;
border: 2px solid white;
border-radius: 5px;
padding: 2px 10px 0px;
margin: 3px;
.layer-class-name {
color: $color-1;
font-size: 14px;
font-weight: bold;
}
.layer-details {
color: #999999;
font-size: 12px;
font-weight: bold;
}
}
.layer.has-result {
border-color: $color-1;
}
}
}
.architecture-connections {
position: absolute;
top: 0;
left: 0;
z-index: 0;
path {
stroke-width: 4px;
stroke: #AAAAAA;
fill: none;
}
}
}
}
+220
View File
@@ -0,0 +1,220 @@
/* global Vue, loadImage */
import './inception-v3.css'
import ndarray from 'ndarray'
import ops from 'ndarray-ops'
import filter from 'lodash/filter'
import * as utils from './utils'
import { IMAGE_URLS } from './image-urls'
import { ARCHITECTURE_DIAGRAM, ARCHITECTURE_CONNECTIONS } from './inception-v3-arch'
const MODEL_FILEPATHS_DEV = {
model: '/demos/data/inception_v3/inception_v3.json',
weights: '/demos/data/inception_v3/inception_v3_weights.buf',
metadata: '/demos/data/inception_v3/inception_v3_metadata.json'
}
const MODEL_FILEPATHS_PROD = {
model: 'demos/data/inception_v3/inception_v3.json',
weights: 'https://transcranial.github.io/keras-js-demos-data/inception_v3/inception_v3_weights.buf',
metadata: 'demos/data/inception_v3/inception_v3_metadata.json'
}
const MODEL_CONFIG = {
filepaths: (process.env.NODE_ENV === 'production') ? MODEL_FILEPATHS_PROD : MODEL_FILEPATHS_DEV
}
/**
*
* VUE COMPONENT
*
*/
export const InceptionV3 = Vue.extend({
props: ['hasWebgl'],
template: require('raw!./inception-v3.template.html'),
data: function () {
return {
model: new KerasJS.Model(Object.assign({ gpu: this.hasWebgl, layerCallPauses: true }, MODEL_CONFIG)),
modelLoading: true,
modelRunning: false,
imageURLInput: null,
imageURLSelect: null,
imageURLSelectList: IMAGE_URLS,
imageLoading: false,
imageLoadingError: false,
output: null,
architectureDiagram: ARCHITECTURE_DIAGRAM,
architectureConnections: ARCHITECTURE_CONNECTIONS,
architectureDiagramPaths: [],
useGpu: this.hasWebgl,
showComputationFlow: true
}
},
computed: {
loadingProgress: function () {
return this.model.getLoadingProgress()
},
architectureDiagramRows: function () {
let rows = []
for (let row = 0; row < 112; row++) {
let cols = []
for (let col = 0; col < 4; col++) {
cols.push(filter(this.architectureDiagram, { row, col }))
}
rows.push(cols)
}
return rows
},
layersWithResults: function () {
// store as computed property for reactivity
return this.model.layersWithResults
},
outputClasses: function () {
if (!this.output) {
let empty = []
for (let i = 0; i < 5; i++) {
empty.push({ name: '-', probability: 0 })
}
return empty
}
return utils.imagenetClassesTopK(this.output, 5)
}
},
ready: function () {
this.model.ready().then(() => {
this.modelLoading = false
})
this.architectureDiagramPaths = []
setTimeout(() => {
this.architectureConnections.forEach(conn => {
const containerElem = document.getElementsByClassName('architecture-container')[0]
const fromElem = document.getElementById(conn.from)
const toElem = document.getElementById(conn.to)
const containerElemCoords = containerElem.getBoundingClientRect()
const fromElemCoords = fromElem.getBoundingClientRect()
const toElemCoords = toElem.getBoundingClientRect()
const xContainer = containerElemCoords.left
const yContainer = containerElemCoords.top
const xFrom = fromElemCoords.left + fromElemCoords.width / 2 - xContainer
const yFrom = fromElemCoords.top + fromElemCoords.height / 2 - yContainer
const xTo = toElemCoords.left + toElemCoords.width / 2 - xContainer
const yTo = toElemCoords.top + toElemCoords.height / 2 - yContainer
let path = `M${xFrom},${yFrom} L${xTo},${yTo}`
if (conn.corner === 'top-right') {
path = `M${xFrom},${yFrom} L${xTo - 10},${yFrom} Q${xTo},${yFrom} ${xTo},${yFrom + 10} L${xTo},${yTo}`
} else if (conn.corner === 'bottom-left') {
path = `M${xFrom},${yFrom} L${xFrom},${yTo - 10} Q${xFrom},${yTo} ${xFrom + 10},${yTo} L${xTo},${yTo}`
} else if (conn.corner === 'top-left') {
path = `M${xFrom},${yFrom} L${xTo + 10},${yFrom} Q${xTo},${yFrom} ${xTo},${yFrom + 10} L${xTo},${yTo}`
} else if (conn.corner === 'bottom-right') {
path = `M${xFrom},${yFrom} L${xFrom},${yFrom + 20} Q${xFrom},${yFrom + 30} ${xFrom - 10},${yFrom + 30} L${xTo + 10},${yFrom + 30} Q${xTo},${yFrom + 30} ${xTo},${yFrom + 40} L${xTo},${yTo}`
}
this.architectureDiagramPaths.push(path)
})
}, 1000)
},
methods: {
toggleGpu: function () {
this.model.toggleGpu(!this.useGpu)
},
toggleComputationFlow: function () {
this.model.layerCallPauses = !this.showComputationFlow
},
imageURLInputChanged: function (e) {
this.imageURLSelect = null
this.loadImageToCanvas(this.imageURLInput)
},
imageURLSelectChanged: function (e) {
this.imageURLInput = this.imageURLSelect
this.loadImageToCanvas(this.imageURLSelect)
},
loadImageToCanvas: function (url) {
if (!url) {
this.clearAll()
return
}
this.imageLoading = true
loadImage(
url,
img => {
if (img.type === 'error') {
this.imageLoadingError = true
this.imageLoading = false
} else {
// load image data onto input canvas
const ctx = document.getElementById('input-canvas').getContext('2d')
ctx.drawImage(img, 0, 0)
this.imageLoadingError = false
this.imageLoading = false
this.modelRunning = true
// model predict
this.$nextTick(function () {
setTimeout(() => {
this.runModel()
}, 200)
})
}
},
{
maxWidth: 299,
maxHeight: 299,
cover: true,
crop: true,
canvas: true,
crossOrigin: 'Anonymous'
}
)
},
runModel: function () {
const ctx = document.getElementById('input-canvas').getContext('2d')
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height)
const { data, width, height } = imageData
// data processing
// see https://github.com/fchollet/keras/blob/master/keras/applications/imagenet_utils.py
let dataTensor = ndarray(new Float32Array(data), [width, height, 4])
let dataProcessedTensor = ndarray(new Float32Array(width * height * 3), [width, height, 3])
ops.divseq(dataTensor, 255)
ops.subseq(dataTensor, 0.5)
ops.mulseq(dataTensor, 2)
ops.assign(dataProcessedTensor.pick(null, null, 0), dataTensor.pick(null, null, 0))
ops.assign(dataProcessedTensor.pick(null, null, 1), dataTensor.pick(null, null, 1))
ops.assign(dataProcessedTensor.pick(null, null, 2), dataTensor.pick(null, null, 2))
const inputData = {
'input_1': dataProcessedTensor.data
}
this.model.predict(inputData).then(outputData => {
this.output = outputData['predictions']
this.modelRunning = false
})
},
clearAll: function () {
this.modelRunning = false
this.imageURLInput = null
this.imageURLSelect = null
this.imageLoading = false
this.imageLoadingError = false
this.output = null
this.model.layersWithResults = []
const ctx = document.getElementById('input-canvas').getContext('2d')
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height)
}
}
})
+97
View File
@@ -0,0 +1,97 @@
<div class="demo inception-v3">
<div class="title">
<span>Inception V3, trained on ImageNet</span>
<mdl-spinner v-if="modelLoading && loadingProgress < 100"></mdl-spinner>
</div>
<div class="loading-progress" v-if="modelLoading && loadingProgress < 100">
Loading...{{ loadingProgress }}%
</div>
<div class="info-panel">
<div class="info-panel-text">
<a href="https://www.flickr.com/search/?text=&license=2%2C3%2C4%2C5%2C6%2C9&sort=interestingness-desc">Flickr creative commons</a>.
</div>
<div class="info-panel-close">
<i class="material-icons">close</i>CLOSE
</div>
</div>
<div class="top-container" v-if="!modelLoading">
<div class="input-container">
<div class="input-label">Enter a valid image URL or select an image from the dropdown:</div>
<div class="image-url">
<mdl-textfield
floating-label="enter image url"
:value.sync="imageURLInput"
spellcheck="false"
@change="imageURLInputChanged"
></mdl-textfield>
<span>or</span>
<mdl-select
label="select image"
id="image-url-select"
:value.sync="imageURLSelect"
:options="imageURLSelectList"
style="width:200px;"
@change="imageURLSelectChanged"
></mdl-select>
</div>
</div>
<div class="controls">
<mdl-switch
:checked.sync="useGpu"
:disabled="modelLoading || modelRunning || !hasWebgl"
@click="toggleGpu"
>Use GPU</mdl-switch>
<mdl-switch
:checked.sync="showComputationFlow"
:disabled="modelLoading || modelRunning"
@click="toggleComputationFlow"
>Show computation flow</mdl-switch>
</div>
</div>
<div class="columns input-output" v-if="!modelLoading">
<div class="column input-column">
<div class="loading-indicator">
<mdl-spinner v-if="imageLoading || modelRunning"></mdl-spinner>
<div class="error" v-if="imageLoadingError">Error loading URL</div>
</div>
<div class="canvas-container">
<canvas id="input-canvas" width="299" height="299"></canvas>
</div>
</div>
<div class="column output-column">
<div class="output">
<div class="output-class"
:class="{ 'predicted': i === 0 && outputClasses[i].probability.toFixed(2) > 0 }"
v-for="i in [0, 1, 2, 3, 4]"
>
<div class="output-label">{{ outputClasses[i].name }}</div>
<div class="output-bar"
style="width: {{ Math.round(100 * outputClasses[i].probability) }}px; background: rgba(27, 188, 155, {{ outputClasses[i].probability.toFixed(2) }});"
></div>
<div class="output-value">{{ Math.round(100 * outputClasses[i].probability) }}%</div>
</div>
</div>
</div>
</div>
<div class="architecture-container" v-if="!modelLoading">
<div v-for="row in architectureDiagramRows" class="layers-row">
<div v-for="layers in row" class="layer-column">
<div
v-for="layer in layers"
v-if="layer.className"
class="layer"
:class="{ 'has-result': layersWithResults.includes(layer.name) }"
:id="layer.name"
>
<div class="layer-class-name">{{ layer.className }}</div>
<div class="layer-details"> {{ layer.details }}</div>
</div>
</div>
</div>
<svg class="architecture-connections" width="100%" height="100%">
<g>
<path v-for="path in architectureDiagramPaths" :d="path" />
</g>
</svg>
</div>
</div>