diff options
| author | schneefux <schneefux+commit@schneefux.xyz> | 2016-03-31 16:09:45 +0200 |
|---|---|---|
| committer | schneefux <schneefux+commit@schneefux.xyz> | 2016-03-31 16:09:45 +0200 |
| commit | a24b803cb9065021f3ff4dfbd780606d8b072bf0 (patch) | |
| tree | 152d90b05dba438e823f9f234032ed6dda99b881 /code | |
| parent | 5f954f48f8ff55c339faed709ebd26b0984abfb2 (diff) | |
| download | vortrag-knn-a24b803cb9065021f3ff4dfbd780606d8b072bf0.tar.gz vortrag-knn-a24b803cb9065021f3ff4dfbd780606d8b072bf0.zip | |
further abstract weight vis; move dependencies; highlight hovers
Diffstat (limited to 'code')
| -rw-r--r-- | code/weightvis.js | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/code/weightvis.js b/code/weightvis.js new file mode 100644 index 0000000..7b4b5ef --- /dev/null +++ b/code/weightvis.js @@ -0,0 +1,360 @@ +// material colors +var + // 50 100 200 300 400 500 + pDeepPurple = ['#ede7f6', '#d1c4e9', '#b39ddb', '#9575cd', '#7e57c2', '#673ab7', + // 600 700 800 900 + '#5e35b1', '#512da8', '#4527a0', '#311b92', + // A100 A200 A400 A700 + '#b388ff', '#7c4dff', '#651fff', '#6200ea'], + // A100 A200 A400 A700 + pGreen = ['#b9f6ca', '#69f0ae', '#00e676', '#00c853'], + pDark = ['#000000', '#212121', '#303030', '#424242'], + pLight = ['#E0E0E0', '#F5F5F5', '#FAFAFA', '#FFFFFF'], + + primaryColor = [pDeepPurple[1], pDeepPurple[5], pDeepPurple[7]], + secondaryColor = pGreen[3], + themeColor = pLight, + + accentColor = secondaryColor, + bgColor = themeColor[3], + + linkColor = primaryColor[0], + linkHighlightColor = primaryColor[1], + nodeColor = primaryColor[1], + nodeHighlightColor = accentColor +; + + +/* network construction */ + +var Network = (function() { + function Network(layout) { + this.layout = layout; + } + + Network.prototype.init = function() { + this._createNodes(); + this._createLinks(); + }; + + Network.prototype.resize = function(env) { + var largestLayerSize = 0; + for (layer=0; layer<this.layout.net.layers; layer++) { + if (this.layout.net[layer].size > largestLayerSize) largestLayerSize = this.layout.net[layer].size; + } + this.responsiveRadius = d3.min([env.width, env.height]) / d3.max([this.layout.net.layers, largestLayerSize]); + this._updateNodes(env); + }; + + Network.prototype._createNodes = function() { + // create layers + this.nodes = []; + for (layer=0; layer<this.layout.net.layers; layer++) { + for (node=0; node<this.layout.net[layer].size; node++) { + this.nodes.push({ + id: layer.toString() + "_" + node.toString(), + fixed: true + }); + } + } + }; + + Network.prototype._createLinks = function() { + this.links = []; + var nodesMap = d3.map(); + this.nodes.forEach(function (n) { + return nodesMap.set(n.id, n); + }); + // create links between adjacent layers + for (layer=0; layer<this.layout.net.layers-1; layer++) { // for every layer except the last + for (src=0; src<this.layout.net[layer].size; src++) { // get all nodes + if (this.layout.net[layer].isBias) { + this.links.push({ // in case of a bias layer, connect 1:1 + source: nodesMap.get(layer.toString() + "_" + src.toString()), + target: nodesMap.get((layer+1).toString() + "_" + src.toString()), + weight: 1 + }); + } else { // normal layer + for (tar=0; tar<this.layout.net[layer+1].size; tar++) { // get all nodes of the next layer + this.links.push({ // connect both nodes + source: nodesMap.get(layer.toString() + "_" + src.toString()), + target: nodesMap.get((layer+1).toString() + "_" + tar.toString()), + weight: 1 + }); + } + } + } + } + }; + + Network.prototype._updateNodes = function(env) { + biasLayers = 0; + for (layer=0; layer<this.layout.net.layers; layer++) { + if (this.layout.net[layer].isBias) biasLayers++; + } + var cnt = 0; + for (layer=0; layer<this.layout.net.layers; layer++) { + for (node=0; node<this.layout.net[layer].size; node++) { + // dynamic ("responsive") attributes for all window sizes + if (this.layout.net[layer].isBias) { + this.nodes[cnt].x = (layer - biasLayers + 1.25) * (env.width / (this.layout.net.layers - biasLayers)); + this.nodes[cnt].y = (node + 0.25) * (env.height / this.layout.net[layer].size); + this.nodes[cnt].r = this.responsiveRadius * 0.2; + } else { + this.nodes[cnt].x = (layer - biasLayers + 0.5) * (env.width / (this.layout.net.layers - biasLayers)); + this.nodes[cnt].y = (node + 0.5) * (env.height / this.layout.net[layer].size); + this.nodes[cnt].r = this.responsiveRadius * 0.3; + } + cnt++; + } + } + }; + + Network.prototype.updateWeights = function(weights, epochLayout) { + var cnt = 0; + for (layer=0; layer<weights.length; layer++) { + for (layer2=0; layer2<weights[layer].length; layer2++) { + for (node=0; node<weights[layer][layer2].length; node++) { + // scaled = (x - min) / (max - min) + var scaled = (weights[layer][layer2][node] - epochLayout.min) / (epochLayout.max - epochLayout.min); + this.links[cnt++].weight = scaled; + } + } + } + }; + + return Network; +})(); + + +/* data handling */ + +var Loader = (function() { + function Loader() { + } + + Loader.prototype.load = function(url, callback) { + var self = this; + d3.json(url) + .get(function(e, json) { + if (e) console.log(e); + self._parse(json); + }); + this._finished = callback; + }; + + Loader.prototype._parse = function(json) { + // json format: + // json. + // [batch][epoch] [[sourceIndex x targetIndex]] (int) + // careful - root and first level nodes are objects and have no '.length' + + var layout = {}; + // layout format: + // layout. + // net.layers (int) + // net.[net.layers]. + // isBias (bool) + // size (int) + // layers.[batch][epoch]. + // min (int) + // max (int) + // data. + // batches (int) + // epochs (int) + layout.data = {}; + layout.data.batches = Object.keys(json).length; + layout.data.epochs = Object.keys(json[0]).length; + layout.net = {}; + // network structure + layout.net.layers = json[0][0].length; + for (layer=0; layer<layout.net.layers; layer++) { + layerObj = {}; + if (json[0][0][layer].length == 1) { // bias + layerObj.isBias = true; + layerObj.size = json[0][0][0][layer].length; // workaround - Matrix should be rotatet serverside + } else { + layerObj.isBias = false; + layerObj.size = json[0][0][layer].length; + } + layout.net[layer] = layerObj; + } + layout.net[layout.net.layers] = {'isBias': false, 'size': json[0][0][layout.net.layers-1][0].length}; + layout.net.layers += 1; // output layer + // layer properties + layout.layers = {}; + for (batch=0; batch<layout.data.batches; batch++) { + layout.layers[batch] = {}; + for (epoch=0; epoch<layout.data.epochs; epoch++) { + layout.layers[batch][epoch] = {}; + layout.layers[batch][epoch].max = 0; + layout.layers[batch][epoch].min = Infinity; + for (layer=0; layer<json[batch][epoch].length; layer++) { + var layerMin = d3.min(json[batch][epoch][layer]); + var layerMax = d3.max(json[batch][epoch][layer]); + if (layerMin < layout.layers[batch][epoch].min) layout.layers[batch][epoch].min = layerMin; + if (layerMax > layout.layers[batch][epoch].max) layout.layers[batch][epoch].max = layerMax; + } + } + } + if (this._finished) this._finished(layout, json); + }; + + return Loader; +})(); + + +/* animation stepping */ + +var Animator = (function() { + function Animator(layout, time, step) { + this.layout = layout; + this.step = step; + this.batch = this.epoch = 0; + this.frameDuration = time / (this.layout.data.batches * this.layout.data.epochs / this.step); + } + + Animator.prototype.start = function(callback) { + var self = this; + wrapper = function() { + callback(self.batch, self.epoch, self.frameDuration); + self.animate(); + }; + this.pid = setInterval(wrapper, this.frameDuration); + }; + + Animator.prototype.animate = function() { + this.epoch += this.step; + if (this.epoch >= this.layout.data.epochs) { + this.epoch = 0; + if (++this.batch>= this.layout.data.batches) { + this.batch = 0; + clearInterval(this.pid); // suicide + console.log("finished"); + } + } + }; + + return Animator; +})(); + + +/* main */ + +var Visualizer = (function() { + var _weightScaler = 10; // size of thickest line + + function Visualizer(svg) { + this.loader = new Loader(); + this.svg = svg; + } + + Visualizer.prototype.getSteps = function() { + return this.layout.data.batches * this.layout.data.epochs; + }; + + Visualizer.prototype.load = function(url, callback) { + var self = this; + function wrapper(layout, data) { + self.layout = layout; + self.data = data; + self._setup(); + callback(); + } + this.loader.load("data/log.json", wrapper); + }; + + Visualizer.prototype.animate = function(animationTime, resolution) { + // animationTime in seconds per frame + // resolution in epochs per frame + var self = this; + this.domLink = this.svg.selectAll(".link"); + + this.animator = new Animator(this.layout, animationTime * 1000, resolution); + this.animator.start(function(batch, epoch){self.show(batch, epoch);}); + }; + + Visualizer.prototype.showStep = function(step) { + var epoch = step % this.layout.data.epochs, + batch = Math.floor(step / this.layout.data.epochs); + this.show(batch, epoch); + }; + + Visualizer.prototype.show = function(batch, epoch, transitionDuration) { + this.network.updateWeights(this.data[batch][epoch], this.layout.layers[batch][epoch]); + if (transitionDuration) { + // 10% extra time per frame for the code execution + this.domLink + .transition() + .duration(transitionDuration / 1.1); + } + this.domLink.style("stroke-width", function(d) { return d.weight * _weightScaler; }); + }; + + Visualizer.prototype._setup = function() { + var self = this; + // create empty elements and determine the visualisation's size + var width = parseInt(this.svg.style("width")) || 800, + height = parseInt(this.svg.style("height")) || 600; + + this.network = new Network(this.layout); + this.network.init(); + this.network.resize({"width": width, "height": height}); + this.network.updateWeights(this.data[0][0], this.layout.layers[0][0]); + + function reposition() { + width = parseInt(self.svg.style("width")) || 800; + height = parseInt(self.svg.style("height")) || 600; + + self.network.resize({"width": width, "height": height}); + + node + .attr("cx", function(d) { return d.x; }) + .attr("cy", function(d) { return d.y; }) + .attr("r", function(d) { return d.r; }); + link + .attr("x1", function(d) { return d.source.x; }) + .attr("y1", function(d) { return d.source.y; }) + .attr("x2", function(d) { return d.target.x; }) + .attr("y2", function(d) { return d.target.y; }); + } + window.addEventListener("resize", reposition, true); + + var link = this.svg.selectAll(".link") + .data(this.network.links) + .enter().append("line") + .attr("class", function(d) { return "link source-" + d.source.id + " target-" + d.target.id; }) + .style("stroke", linkColor) + .style("stroke-width", function(d) { return d.weight * _weightScaler; }) + .on("mouseover", function(d) { + d3.select("#node-" + d.source.id).style("fill", nodeHighlightColor); + d3.select("#node-" + d.target.id).style("fill", nodeHighlightColor); + d3.select(this).style("stroke", linkHighlightColor); + }) + .on("mouseout", function(d) { + d3.select("#node-" + d.source.id).style("fill", nodeColor); + d3.select("#node-" + d.target.id).style("fill", nodeColor); + d3.select(this).style("stroke", linkColor); + }); + var node = this.svg.selectAll(".node") + .data(this.network.nodes) + .enter().append("circle") + .attr("class", "node") + .attr("id", function(d) { return "node-" + d.id; }) + .style("fill", nodeColor) + .on("mouseover", function(d) { + d3.selectAll(".source-" + d.id).style("stroke", linkHighlightColor); + d3.selectAll(".target-" + d.id).style("stroke", linkHighlightColor); + d3.select(this).style("fill", nodeHighlightColor); + }) + .on("mouseout", function(d) { + d3.selectAll(".source-" + d.id).style("stroke", linkColor); + d3.selectAll(".target-" + d.id).style("stroke", linkColor); + d3.select(this).style("fill", nodeColor); + }); + + reposition(); + }; + + return Visualizer; +})(); |
