summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--index.js472
1 files changed, 274 insertions, 198 deletions
diff --git a/index.js b/index.js
index 56b2e5d..e58ddd7 100644
--- a/index.js
+++ b/index.js
@@ -54,237 +54,313 @@ function makeSlider(labelName, x, y, scale, cb) {
/* network construction */
-function drawNodes(nodes, layout, env) {
- var cnt = 0;
- var biasLayers = 0;
- for (layer=0; layer<layout.net.layers; layer++) {
- if (layout.net[layer].isBias) biasLayers++;
+var Network = (function() {
+ function Network(layout) {
+ this.layout = layout;
}
- for (layer=0; layer<layout.net.layers; layer++) {
- for (node=0; node<layout.net[layer].size; node++) {
- // dynamic ("responsive") attributes for all window sizes
- if (layout.net[layer].isBias) {
- nodes[cnt].x = (layer - biasLayers + 1.25) * (env.width / (layout.net.layers - biasLayers));
- nodes[cnt].y = (node + 0.25) * (env.height / layout.net[layer].size);
- nodes[cnt].r = d3.min([env.width, env.height]) / d3.max([layout.net.layers, layout.net[layer].size]) * 0.2;
- } else {
- nodes[cnt].x = (layer - biasLayers + 0.5) * (env.width / (layout.net.layers - biasLayers));
- nodes[cnt].y = (node + 0.5) * (env.height / layout.net[layer].size);
- nodes[cnt].r = d3.min([env.width, env.height]) / d3.max([layout.net.layers, layout.net[layer].size]) * 0.3;
- }
- cnt++;
+
+ 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;
}
- }
- return nodes;
-}
+ this.responsiveRadius = d3.min([env.width, env.height]) / d3.max([this.layout.net.layers, largestLayerSize]);
+ this._updateNodes(env);
+ };
-function drawLinks(links, weights) {
- 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++) {
- links[cnt++].weight = weights[layer][layer2][node];
+ 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
+ });
}
}
- }
- return links;
-}
+ };
-function createNet(layout) {
- // create layers
- var nodes = [];
- for (layer=0; layer<layout.net.layers; layer++) {
- for (node=0; node<layout.net[layer].size; node++) {
- nodes.push({
- id: layer.toString() + "_" + node.toString(),
- fixed: true
- });
- }
- }
- var nodesMap = d3.map();
- nodes.forEach(function (n) {
- return nodesMap.set(n.id, n);
- });
- // create links between adjacent layers
- var links = [];
- for (layer=0; layer<layout.net.layers-1; layer++) { // for every layer except the last
- for (src=0; src<layout.net[layer].size; src++) { // get all nodes
- if (layout.net[layer].isBias) {
- 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<layout.net[layer+1].size; tar++) { // get all nodes of the next layer
- links.push({ // connect both nodes
+ 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() + "_" + tar.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
+ });
+ }
}
}
}
- }
-
- return {
- "nodes": nodes,
- "links": links
};
-}
-
-/* visualisation */
+ 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++;
+ }
+ }
+ };
-// create visualisation
-function envRedraw(weights, duration) {
- var width = parseInt(svg.style("width")) || 800,
- height = parseInt(svg.style("height")) || 600;
- net.nodes = drawNodes(net.nodes, netSpec, designSpec, {"width": width, "height": height});
- net.links = drawLinks(net.links, weights);
+ 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;
+ }
+ }
+ }
+ };
- node = svg.selectAll(".node");
- node
- .transition()
- .duration(duration)
- .attr("cx", function(d) { return d.x; })
- .attr("cy", function(d) { return d.y; })
- .attr("r", function(d) { return d.r; });
- link = svg.selectAll(".link");
- link
- .transition()
- .duration(duration)
- .style("stroke-width", function(d) { return d.weight * 4; })
- .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; });
-}
+ return Network;
+})();
/* data handling */
-// now we're getting to the grips
-function preload(callback) {
- d3.json("data/log.json")
- .get(function(e, json) {
- if (e) console.log(e);
- callback(json);
+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;
+ };
-var cache = {};
-preload(function (json) {
- // json format:
- // json.
- // [batch][epoch] [[sourceIndex x targetIndex]] (int)
- // careful - root and first level nodes are objects and have no '.length'
+ 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)
- layout.net = {};
- // network structure
- console.log(json);
- 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;
+ 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[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<Object.keys(json).length; batch++) {
- layout.layers[batch] = {};
- for (epoch=0; epoch<Object.keys(json[batch]).length; epoch++) {
- layout.layers[batch][epoch] = {};
- for (layer=0; layer<json[batch][epoch].length; layer++) {
- layout.layers[batch][epoch][layer] = {};
- layout.layers[batch][epoch][layer].min = d3.min(json[batch][epoch][layer]);
- layout.layers[batch][epoch][layer].max = d3.max(json[batch][epoch][layer]);
- // TODO use these values while drawing links!
+ 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;
+ }
}
}
+ console.log(layout); // DEBUG
+
+ if (this._finished) this._finished(layout, json);
+ };
+
+ return Loader;
+})();
+
+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);
+ console.log("frame duration:" + this.frameDuration); // DEBUG
}
- setup(layout, json);
-});
+ 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");
+ }
+ }
+ };
-function setup(layout, data) {
- // create empty elements and determine the visualisation's size
- var svg = d3.select("#visContainer").append("svg")
- .attr("width", "100%")
- .attr("height", "100%");
- var width = parseInt(svg.style("width")) || 800,
- height = parseInt(svg.style("height")) || 600;
+ return Animator;
+})();
- /*var slideScaler = d3.scale.linear()
- .domain([0, 180])
- .range([0, 200])
- .clamp(true);
- function slid(val) {
- console.log(val);
+var Visualizer = (function() {
+ function Visualizer(svg) {
+ this.loader = new Loader();
+ this.svg = svg;
}
- makeSlider("Test", 10, 20, slideScaler, slid);*/
- var net = createNet(layout);
- console.log(layout);
- console.log(net);
- net.nodes = drawNodes(net.nodes, layout, {"width": width, "height": height});
- net.links = drawLinks(net.links, data[0][0]);
+ Visualizer.prototype.start = function(url, animationTime, resolution) {
+ // animationTime in seconds per frame
+ // resolution in epochs per frame
+ var self = this;
+ this.animationTime = animationTime;
+ this.resolution = resolution;
+ function wrapper(layout, data) {
+ self.layout = layout;
+ self.data = data;
+ self._setup();
+ }
+ this.loader.load("data/log.json", wrapper);
+ };
- var link = svg.selectAll(".link")
- .data(net.links)
- .enter().append("line")
- .attr("class", "link")
- .style("stroke", "grey")
- .style("stroke-width", function(d) { return d.weight * 4; }) // TODO make width always stay between 0-1
- .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; });
- var node = svg.selectAll(".node")
- .data(net.nodes)
- .enter().append("circle")
- .attr("class", "node")
- .attr("cx", function(d) { return d.x; })
- .attr("cy", function(d) { return d.y; })
- .attr("r", function(d) { return d.r; });
+ Visualizer.prototype._setup = function() {
+ console.log("setup"); // DEBUG
+ var self = this;
+ // create empty elements and determine the visualisation's size
+ this.svg
+ .attr("width", "100%")
+ .attr("height", "100%");
+ var width = parseInt(this.svg.style("width")) || 800,
+ height = parseInt(this.svg.style("height")) || 600;
- window.addEventListener("resize", envRedraw, true);
- // TODO animation & refresh
-}
+ /*var slideScaler = d3.scale.linear()
+ .domain([0, 180])
+ .range([0, 200])
+ .clamp(true);
+ function slid(val) {
+ console.log(val);
+ }
+ makeSlider("Test", 10, 20, slideScaler, slid);*/
+
+ 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]);
-var maxBatch = 8, maxEpoch = 300;
-var epochStep = 50;
-var numLayers = 3;
-var currBatch = 0, currEpoch = 0;
-function animate() {
- frameDuration = 20000 / (maxBatch * maxEpoch / epochStep);
- envRedraw(cache[currBatch][currEpoch], frameDuration);
- currEpoch += epochStep;
- if (currEpoch >= maxEpoch) {
- currEpoch = 0;
- console.log("batch " + currBatch.toString());
- if (++currBatch >= maxBatch) {
- currBatch = 0;
- return; // stop
+ function resized() {
+ 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; });
}
- }
- setTimeout(animate, frameDuration);
-}
+ window.addEventListener("resize", resized, true);
+
+ var link = this.svg.selectAll(".link")
+ .data(this.network.links)
+ .enter().append("line")
+ .attr("class", "link")
+ .style("stroke", "grey")
+ .style("stroke-width", function(d) { return d.weight * 10; })
+ .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; });
+ var node = this.svg.selectAll(".node")
+ .data(this.network.nodes)
+ .enter().append("circle")
+ .attr("class", "node")
+ .attr("cx", function(d) { return d.x; })
+ .attr("cy", function(d) { return d.y; })
+ .attr("r", function(d) { return d.r; });
+
+ this.animator = new Animator(this.layout, this.animationTime * 1000, this.resolution);
+ this.animator.start(function(batch, epoch, duration) {
+ self.network.updateWeights(self.data[batch][epoch], self.layout.layers[batch][epoch]);
+
+ // 10% extra time per frame for the code execution
+ duration = duration / 1.1;
+ link
+ .transition()
+ .duration(duration)
+ .style("stroke-width", function(d) { return d.weight * 10; });
+ });
+ };
+
+ return Visualizer;
+})();
+
+var svg = d3.select("#visContainer").append("svg");
+var visualizer = new Visualizer(svg);
+visualizer.start("data/log.json", 30, 10);