summaryrefslogtreecommitdiff
path: root/code/weightvis.js
diff options
context:
space:
mode:
Diffstat (limited to 'code/weightvis.js')
-rw-r--r--code/weightvis.js360
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;
+})();