提交 145dc810 authored 作者: Christof Angermueller's avatar Christof Angermueller

Merge d3viz branch

......@@ -14,6 +14,8 @@
*.toc
*.vrb
.noseids
*.DS_Store
*.bak
Theano.egg-info
\#*\#
build
......@@ -26,18 +28,15 @@ doc/indexes/oplist.txt
doc/indexes/typelist.txt
html
pdf
pull.sh
setuptools-*.egg
theano/generated_version.py
theano/generated_version.py.out
distribute-*.egg
distribute-*.tar.gz
Theano.suo
*.DS_Store
*.bak
.ipynb_checkpoints
.pydevproject
/doc/library/d3viz/GSoC/150808_labels/
doc/library/d3viz/GSoC/
doc/library/d3viz/examples/
doc/library/d3viz/index_files/
<!DOCTYPE html>
<html>
<head lang='en'>
<meta charset="utf-8">
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type='text/javascript' src="http://cpettitt.github.io/project/dagre-d3/v0.1.5/dagre-d3.min.js"></script>
<script type='text/javascript' src="http://cpettitt.github.io/project/graphlib-dot/v0.4.10/graphlib-dot.min.js"></script>
</head>
<body>
<style>
.svg {
}
.nodeRect {
stroke: black;
border: 3px solid black;
fill: lightsteelblue;
}
.nodeText {
color: black;
font-family: courier;
}
.edge {
stroke: black;
stroke-width: 3px;
}
.edgeLabelRect {
fill: white;
}
.edgeLabelText {
fill: limegreen;
font-family: courier;
text-anchor: start;
}
.arrowHead {
stroke: black;
stroke-width: 3px;
fill: black;
}
</style>
<script type="text/javascript">
// Global attributes
var dotGraphDef = 'digraph G { graph [bb="0,0,763,388"]; "DimShuffle{x,0}" [height=0.5, pos="600,282", shape=ellipse, width=2.0339]; "Elemwise{add,no_inplace}" [fillcolor="#FFAABB", height=0.5, pos="474,194", shape=ellipse, style=filled, width=3.0624]; "DimShuffle{x,0}" -> "Elemwise{add,no_inplace}" [label="1 TensorType(float64, row)", lp="648,238", pos="e,516.94,210.65 587.13,264.21 578.18,253.39 565.47,239.59 552,230 544.1,224.38 535.16,219.35 526.22,214.97"]; "name=b TensorType(float64, vector)" [fillcolor=green, height=0.5, pos="607,370", shape=box, style=filled, width=3.0903]; "name=b TensorType(float64, vector)" -> "DimShuffle{x,0}" [label="TensorType(float64, vector)", lp="684,326", pos="e,601.39,300.08 605.58,351.6 604.62,339.75 603.32,323.82 602.22,310.29"]; dot [height=0.5, pos="362,282", shape=ellipse, width=0.75]; dot -> "Elemwise{add,no_inplace}" [label="0 TensorType(float64, matrix)", lp="467,238", pos="e,414.72,209.23 365,263.89 367.74,252.92 372.81,239.1 382,230 388.76,223.31 396.93,217.87 405.58,213.46"]; "name=X TensorType(float64, matrix)" [fillcolor=green, height=0.5, pos="114,370", shape=box, style=filled, width=3.1667]; "name=X TensorType(float64, matrix)" -> dot [label="0 TensorType(float64, matrix)", lp="273,326", pos="e,335.37,285 134.04,351.84 148.22,340.57 168.18,326.4 188,318 233.06,298.9 289.18,290.03 325.29,286.04"]; "name=W TensorType(float64, matrix)" [fillcolor=green, height=0.5, pos="362,370", shape=box, style=filled, width=3.2014]; "name=W TensorType(float64, matrix)" -> dot [label="1 TensorType(float64, matrix)", lp="447,326", pos="e,362,300.08 362,351.6 362,339.75 362,323.82 362,310.29"]; Softmax [height=0.5, pos="474,106", shape=ellipse, width=1.1472]; "Elemwise{add,no_inplace}" -> Softmax [label="TensorType(float64, matrix)", lp="554,150", pos="e,474,124.08 474,175.6 474,163.75 474,147.82 474,134.29"]; "TensorType(float64, matrix) id=6" [fillcolor=blue, height=0.5, pos="474,18", shape=box, style=filled, width=2.8403]; Softmax -> "TensorType(float64, matrix) id=6" [label="TensorType(float64, matrix)", lp="554,62", pos="e,474,36.084 474,87.597 474,75.746 474,59.817 474,46.292"]; } ';
var width = 800;
var height = 600;
// Add SVG element
var svg = d3.select('body').append('svg').attr('class', 'svg').attr('width', width).attr('height', height);
var pane = svg.append('g').attr('transform', 'scale(0.8)');
// Definition head of edges
svg.append("defs").append("marker")
.attr("id", 'markerEnd')
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 5)
.attr("refY", 3)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,0 L6,3 L0,6 Z")
.attr('style', 'arrowHead');
function textWidth(text) {
return text.length * 10.5;
}
// Parse dot graph definition
var graph = {};
var nodes = [];
var edges = [];
var dotGraph = graphlibDot.parse(dotGraphDef);
// TODO: parse from file
var dotWidth = 763;
var dotHeight = 388;
var scaleDotX = d3.scale.linear().domain([0, dotWidth]).range([0, width]);
var scaleDotY = d3.scale.linear().domain([0, dotHeight]).range([0, height]);
// Parse nodes
var i = 0;
for (nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.index = i++;
node.value.width = textWidth(node.value.label);
node.value.height = 40;
node.value.cx = node.value.width / 2;
node.value.cy = node.value.height / 2;
node.value.pos = node.value.pos.split(',').map(function(d) {return parseInt(d);});
node.x = scaleDotX(node.value.pos[0]);
node.y = scaleDotY(dotHeight - node.value.pos[1]);
node.fixed = false;
nodes.push(node);
dotGraph._nodes[nodeId] = node;
}
// Parse edges
for (edgeId in dotGraph._edges) {
var edge = dotGraph._edges[edgeId];
edge.source = dotGraph._nodes[edge.u].index;
edge.target = dotGraph._nodes[edge.v].index;
edge.value.width = textWidth(edge.value.label);
edge.value.height = 40;
edges.push(edge);
dotGraph._edges[edgeId] = edge;
}
// Setup graph
graph['nodes'] = nodes;
graph['edges'] = edges;
// Add edges
edges = pane.append('g').attr('id', 'edges').selectAll('path').data(graph['edges']).enter().append('path')
.attr('class', 'edge')
.attr('marker-end', 'url(#markerEnd)');
// Add edge labels
edgeLabels = pane.append('g').attr('id', 'edgeLabels').selectAll('g').data(graph['edges']).enter().append('g')
.attr('opacity', 0)
.on('mouseover', function(d) {d3.select(this).attr('opacity', 1.0);})
.on('mouseout', function(d) {d3.select(this).attr('opacity', 0.0);});
var edgeLabelsRect = edgeLabels.append('rect')
.attr('class', 'edgeLabelRect')
.attr('fill', 'white')
.attr('width', function(d) {return d.value.width;})
.attr('height', function(d) {return d.value.height;});
var edgeLabelsText = edgeLabels.append('text')
.attr('class', 'edgeLabelText')
.attr('dy', function(d) {return 0.5 * d.value.height;})
.text(function(d) {return d.value.label;});
// Add nodes
nodes = pane.append('g').attr('id', 'nodes').selectAll('g').data(graph['nodes']).enter().append('g');
var nodesRect = nodes.append('rect')
.attr('class', 'nodeRect')
.attr('width', function(d) {return d.value.width;})
.attr('height', function(d) {return d.value.height;});
var nodesText = nodes.append('text')
.attr('class', 'nodeText')
.attr('x', 5)
.attr('dy', function(d) {return d.value.height - 15;})
.text(function(d) {return d.value.label;});
// Update graph
function update() {
// Update nodes
nodes.attr('transform', function(d) { return 'translate(' + d.x + ' ' + d.y + ')'; });
// Update edges
edges.attr('d', function(d) {
return 'M' + (d.source.x + d.source.value.cx) + ',' + (d.source.y + d.source.value.cy) + ' L' + (d.target.x + d.target.value.cx) + ',' + (d.target.y - 3);
});
// Update edge labels
edgeLabels.attr('transform', function(d) {
return 'translate(' + (0.5 * (d.source.x + d.source.value.cx + d.target.x + d.target.value.cx) - 0.5 * d.value.width) + ',' + (0.5 * (d.source.y + d.source.value.cy + d.target.y + d.target.value.cy) - 0.5 * d.value.height) + ')';
});
}
// Drag-start event handler
function dragStart(d) {
d3.event.sourceEvent.stopPropagation();
d.fixed = true;
}
// Zoom and translate event handler
function zoom(d) {
pane.attr('transform', 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')');
}
// Force layout
var layout = d3.layout.force()
.nodes(graph['nodes'])
.links(graph['edges'])
.size([width, height])
.gravity(0.2)
.charge(-6000)
.linkDistance(75)
.linkStrength(0.1)
.on('tick', update);
// Drag behavour
var drag = layout.drag()
.on('dragstart', dragStart);
nodes.call(drag);
// Zoom behaviour
var bZoom = d3.behavior.zoom()
.scaleExtent([0.2, 8])
.on('zoom', zoom);
svg.call(bZoom);
// Start force layout
layout.start();
</script>
</body>
</html>
{
"cells": [
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The autoreload extension is already loaded. To reload it, use:\n",
" %reload_ext autoreload\n"
]
}
],
"source": [
"import numpy as np\n",
"import theano\n",
"import theano.tensor as T\n",
"import theano.d3printing as d3p\n",
"from IPython.display import HTML\n",
"\n",
"%load_ext autoreload\n",
"%autoreload 2"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"# Logistic regression model\n",
"num_in = 64**2\n",
"num_out = 10\n",
"W = theano.shared(np.random.randn(num_in, num_out), name='W', borrow=True)\n",
"b = theano.shared(np.random.randn(num_out), name='b', borrow=True)\n",
"X = T.dmatrix('X')\n",
"y = T.nnet.softmax(X.dot(W) + b)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The output file is available at logreg.html\n"
]
}
],
"source": [
"html = d3p.d3print(y, 'logreg.html', return_html=True)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.9"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
This source diff could not be displayed because it is too large. You can view the blob instead.
.. _libdoc_printing:
============================================================================
:mod:`d3viz` -- d3viz: Interactive visualization of Theano compute graphs
============================================================================
.. module:: d3viz
:platform: Unix, Windows
:synopsis: Allows to interactively visualize Theano compute graphs
.. moduleauthor:: Christof Angermueller
Guide
=====
Requirements
------------
``d3viz`` requires the `pydot <https://pypi.python.org/pypi/pydot>`__
package, which can be installed with ``pip``:
::
pip install pydot
Overview
--------
``d3viz`` extends Theano’s `printing
module <http://deeplearning.net/software/theano/library/printing.html>`__
to interactively visualize compute graphs. Instead of creating a static
picture, it creates an HTML file, which can be opened with any
web-browser. ``d3viz`` allows
- to zoom to different regions and to move graphs via drag and drop,
- to position nodes both manually and automatically,
- to retrieve additional information about nodes and edges such as
their data type or definition in the source code,
- to edit node labels,
- to visualizing profiling information, and
- to explore nested graphs such as OpFromGraph nodes.
.. code:: python
import theano as th
import theano.tensor as T
import numpy as np
As an example, consider the following multilayer perceptron with one
hidden layer and a softmax output layer.
.. code:: python
ninputs = 1000
nfeatures = 100
noutputs = 10
nhiddens = 50
rng = np.random.RandomState(0)
x = T.dmatrix('x')
wh = th.shared(rng.normal(0, 1, (nfeatures, nhiddens)), borrow=True)
bh = th.shared(np.zeros(nhiddens), borrow=True)
h = T.nnet.sigmoid(T.dot(x, wh) + bh)
wy = th.shared(rng.normal(0, 1, (nhiddens, noutputs)))
by = th.shared(np.zeros(noutputs), borrow=True)
y = T.nnet.softmax(T.dot(h, wy) + by)
predict = th.function([x], y)
The function ``predict`` outputs the probability of 10 classes. You can
visualize it with :py:func:`theano.printing.pydotprint` as follows:
.. code:: python
from theano.printing import pydotprint
import os
if not os.path.exists('examples'):
os.makedirs('examples')
pydotprint(predict, 'examples/mlp.png')
.. parsed-literal::
The output file is available at examples/mlp.png
.. code:: python
from IPython.display import Image
Image('./examples/mlp.png', width='80%')
.. image:: index_files/index_10_0.png
To visualize it interactively, import :py:func:`theano.d3viz.d3viz.d3viz` from
the the :py:mod:`theano.d3viz.d3viz` module, which can be called as before:
.. code:: python
import theano.d3viz as d3v
d3v.d3viz(predict, 'examples/mlp.html')
`open <./examples/mlp.html>`__
When you open the output file ``mlp.html`` in your web-browser, you will
see an interactive visualization of the compute graph. You can move the
whole graph or single nodes via drag and drop, and zoom via the mouse
wheel. When you move the mouse cursor over a node, a window will pop up
that displays detailed information about the node, such as its data type
or definition in the source code. When you left-click on a node and
select ``Edit``, you can change the predefined node label. If you are
dealing with a complex graph with many nodes, the default node layout
may not be perfect. In this case, you can press the ``Release node``
button in the top-left corner to automatically arrange nodes. To reset
nodes to their default position, press the ``Reset nodes`` button.
Profiling
---------
Theano allows `function
profiling <http://deeplearning.net/software/theano/tutorial/profiling.html>`__
via the ``profile=True`` flag. After at least one function call, the
compute time of each node can be printed in text form with
``debugprint``. However, analyzing complex graphs in this way can be
cumbersome.
``d3viz`` can visualize the same timing information graphically, and
hence help to spot bottlenecks in the compute graph more easily! To
begin with, we will redefine the ``predict`` function, this time by
using ``profile=True`` flag. Afterwards, we capture the runtime on
random data:
.. code:: python
predict_profiled = th.function([x], y, profile=True)
x_val = rng.normal(0, 1, (ninputs, nfeatures))
y_val = predict_profiled(x_val)
.. code:: python
d3v.d3viz(predict_profiled, 'examples/mlp2.html')
`open <./examples/mlp2.html>`__
When you open the HTML file in your browser, you will find an additional
``Toggle profile colors`` button in the menu bar. By clicking on it,
nodes will be colored by their compute time, where red corresponds to a
high compute time. You can read out the exact timing information of a
node by moving the cursor over it.
Different output formats
------------------------
Internally, ``d3viz`` represents a compute graph in the `Graphviz DOT
language <http://www.graphviz.org/>`__, using the
`pydot <https://pypi.python.org/pypi/pydot>`__ package, and defines a
front-end based on the `d3.js <http://d3js.org/>`__ library to visualize
it. However, any other Graphviz front-end can be used, which allows to
export graphs to different formats.
.. code:: python
formatter = d3v.formatting.PyDotFormatter()
pydot_graph = formatter(predict_profiled)
pydot_graph.write_png('examples/mlp2.png');
pydot_graph.write_png('examples/mlp2.pdf');
.. code:: python
Image('./examples/mlp2.png')
.. image:: index_files/index_24_0.png
Here, we used the :py:class:`theano.d3viz.formatting.PyDotFormatter` class to
convert the compute graph into a ``pydot`` graph, and created a PNG and PDF
file. You can find all output formats supported by Graphviz `here
<http://www.graphviz.org/doc/info/output.html>`__.
OpFromGraph nodes
-----------------
An
`OpFromGraph <http://deeplearning.net/software/theano/library/compile/opfromgraph.html>`__
node defines a new operation, which can be called with different inputs
at different places in the compute graph. Each ``OpFromGraph`` node
defines a nested graph, which will be visualized accordingly by
``d3viz``.
.. code:: python
x, y, z = T.scalars('xyz')
e = T.nnet.sigmoid((x + y + z)**2)
op = th.OpFromGraph([x, y, z], [e])
e2 = op(x, y, z) + op(z, y, x)
f = th.function([x, y, z], e2)
.. code:: python
d3v.d3viz(f, 'examples/ofg.html')
`open <./examples/ofg.html>`__
In this example, an operation with three inputs is defined, which is
used to build a function that calls this operations twice, each time
with different input arguments.
In the ``d3viz`` visualization, you will find two OpFromGraph nodes,
which correspond to the two OpFromGraph calls. When you double click on
one of them, the nested graph appears with the correct mapping of its
input arguments. You can move it around by drag and drop in the shaded
area, and close it again by double-click.
An OpFromGraph operation can be composed of further OpFromGraph
operations, which will be visualized as nested graphs as you can see in
the following example.
.. code:: python
x, y, z = T.scalars('xyz')
e = x * y
op = th.OpFromGraph([x, y], [e])
e2 = op(x, y) + z
op2 = th.OpFromGraph([x, y, z], [e2])
e3 = op2(x, y, z) + z
f = th.function([x, y, z], [e3])
.. code:: python
d3v.d3viz(f, 'examples/ofg2.html')
`open <./examples/ofg2.html>`__
Feedback
--------
If you have any problems or great ideas on how to improve ``d3viz``,
please let me know!
- Christof Angermueller
- cangermueller@gmail.com
- http://cangermueller.com
References
==========
d3viz module
------------
.. automodule:: theano.d3viz.d3viz
:members:
PyDotFormatter
--------------
.. autoclass:: theano.d3viz.formatting.PyDotFormatter
:members: __call__
:special-members:
:private-members:
"""Extends printing module by dynamic visualizations."""
# Authors: Christof Angermueller <cangermueller@gmail.com>
import os.path
from theano.printing import pydotprint
def replace_patterns(x, replace):
""" Replaces patterns defined by `replace` in x."""
for from_, to in replace.items():
x = x.replace(str(from_), str(to))
return x
def d3print(fct, outfile=None, return_html=False, print_message=True,
width=800, height=600,
*args, **kwargs):
"""Creates dynamic graph visualization using d3.js javascript library.
:param fct: A compiled Theano function, variable, apply or a list of
variables
:param outfile: The output file
:param return_html: If True, return HTML code
:param print_message: If True, print message at the end
:param *args, **kwargs: Parameters passed to pydotprint
"""
# Generate dot graph definition by calling pydotprint
dot_graph = pydotprint(fct, format='dot', return_image=True, *args, **kwargs)
dot_graph = dot_graph.replace('\n', ' ')
dot_graph = dot_graph.replace('node [label="\N"];', '')
# Read template HTML file and replace variables
template_file = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'template.html')
f = open(template_file)
template = f.read()
f.close()
replace = {
'%% DOT_GRAPH %%': dot_graph,
'%% WIDTH %%': width,
'%% HEIGHT %%': height
}
html = replace_patterns(template, replace)
# Write output file
if outfile is not None:
f = open(outfile, 'w')
f.write(html)
f.close()
if print_message:
print('The output file is available at %s' % (outfile))
if return_html:
return html
<!DOCTYPE html>
<html>
<head lang='en'>
<meta charset="utf-8">
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type='text/javascript' src="http://cpettitt.github.io/project/dagre-d3/v0.1.5/dagre-d3.min.js"></script>
<script type='text/javascript' src="http://cpettitt.github.io/project/graphlib-dot/v0.4.10/graphlib-dot.min.js"></script>
</head>
<body>
<style>
.svg {
}
.nodeRect {
stroke: black;
border: 3px solid black;
fill: lightsteelblue;
}
.nodeText {
color: black;
font-family: courier;
}
.edge {
stroke: black;
stroke-width: 3px;
}
.edgeLabelRect {
fill: white;
}
.edgeLabelText {
fill: limegreen;
font-family: courier;
text-anchor: start;
}
.arrowHead {
stroke: black;
stroke-width: 3px;
fill: black;
}
</style>
<script type="text/javascript">
// Global attributes
var dotGraphDef = '%% DOT_GRAPH %%';
var width = %% WIDTH %%;
var height = %% HEIGHT %%;
// Add SVG element
var svg = d3.select('body').append('svg').attr('class', 'svg').attr('width', width).attr('height', height);
var pane = svg.append('g').attr('transform', 'scale(0.8)');
// Definition head of edges
svg.append("defs").append("marker")
.attr("id", 'markerEnd')
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("refX", 5)
.attr("refY", 3)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,0 L6,3 L0,6 Z")
.attr('style', 'arrowHead');
function textWidth(text) {
return text.length * 10.5;
}
// Parse dot graph definition
var graph = {};
var nodes = [];
var edges = [];
var dotGraph = graphlibDot.parse(dotGraphDef);
// TODO: parse from file
var dotWidth = 763;
var dotHeight = 388;
var scaleDotX = d3.scale.linear().domain([0, dotWidth]).range([0, width]);
var scaleDotY = d3.scale.linear().domain([0, dotHeight]).range([0, height]);
// Parse nodes
var i = 0;
for (nodeId in dotGraph._nodes) {
var node = dotGraph._nodes[nodeId];
node.index = i++;
node.value.width = textWidth(node.value.label);
node.value.height = 40;
node.value.cx = node.value.width / 2;
node.value.cy = node.value.height / 2;
node.value.pos = node.value.pos.split(',').map(function(d) {return parseInt(d);});
node.x = scaleDotX(node.value.pos[0]);
node.y = scaleDotY(dotHeight - node.value.pos[1]);
node.fixed = false;
nodes.push(node);
dotGraph._nodes[nodeId] = node;
}
// Parse edges
for (edgeId in dotGraph._edges) {
var edge = dotGraph._edges[edgeId];
edge.source = dotGraph._nodes[edge.u].index;
edge.target = dotGraph._nodes[edge.v].index;
edge.value.width = textWidth(edge.value.label);
edge.value.height = 40;
edges.push(edge);
dotGraph._edges[edgeId] = edge;
}
// Setup graph
graph['nodes'] = nodes;
graph['edges'] = edges;
// Add edges
edges = pane.append('g').attr('id', 'edges').selectAll('path').data(graph['edges']).enter().append('path')
.attr('class', 'edge')
.attr('marker-end', 'url(#markerEnd)');
// Add edge labels
edgeLabels = pane.append('g').attr('id', 'edgeLabels').selectAll('g').data(graph['edges']).enter().append('g')
.attr('opacity', 0)
.on('mouseover', function(d) {d3.select(this).attr('opacity', 1.0);})
.on('mouseout', function(d) {d3.select(this).attr('opacity', 0.0);});
var edgeLabelsRect = edgeLabels.append('rect')
.attr('class', 'edgeLabelRect')
.attr('fill', 'white')
.attr('width', function(d) {return d.value.width;})
.attr('height', function(d) {return d.value.height;});
var edgeLabelsText = edgeLabels.append('text')
.attr('class', 'edgeLabelText')
.attr('dy', function(d) {return 0.5 * d.value.height;})
.text(function(d) {return d.value.label;});
// Add nodes
nodes = pane.append('g').attr('id', 'nodes').selectAll('g').data(graph['nodes']).enter().append('g');
var nodesRect = nodes.append('rect')
.attr('class', 'nodeRect')
.attr('width', function(d) {return d.value.width;})
.attr('height', function(d) {return d.value.height;});
var nodesText = nodes.append('text')
.attr('class', 'nodeText')
.attr('x', 5)
.attr('dy', function(d) {return d.value.height - 15;})
.text(function(d) {return d.value.label;});
// Update graph
function update() {
// Update nodes
nodes.attr('transform', function(d) { return 'translate(' + d.x + ' ' + d.y + ')'; });
// Update edges
edges.attr('d', function(d) {
return 'M' + (d.source.x + d.source.value.cx) + ',' + (d.source.y + d.source.value.cy) + ' L' + (d.target.x + d.target.value.cx) + ',' + (d.target.y - 3);
});
// Update edge labels
edgeLabels.attr('transform', function(d) {
return 'translate(' + (0.5 * (d.source.x + d.source.value.cx + d.target.x + d.target.value.cx) - 0.5 * d.value.width) + ',' + (0.5 * (d.source.y + d.source.value.cy + d.target.y + d.target.value.cy) - 0.5 * d.value.height) + ')';
});
}
// Drag-start event handler
function dragStart(d) {
d3.event.sourceEvent.stopPropagation();
d.fixed = true;
}
// Zoom and translate event handler
function zoom(d) {
pane.attr('transform', 'translate(' + d3.event.translate + ') scale(' + d3.event.scale + ')');
}
// Force layout
var layout = d3.layout.force()
.nodes(graph['nodes'])
.links(graph['edges'])
.size([width, height])
.gravity(0.2)
.charge(-6000)
.linkDistance(75)
.linkStrength(0.1)
.on('tick', update);
// Drag behavour
var drag = layout.drag()
.on('dragstart', dragStart);
nodes.call(drag);
// Zoom behaviour
var bZoom = d3.behavior.zoom()
.scaleExtent([0.2, 8])
.on('zoom', zoom);
svg.call(bZoom);
// Start force layout
layout.start();
</script>
</body>
</html>
.d3-context-menu {
position: absolute;
display: none;
background-color: #f2f2f2;
border-radius: 4px;
font-family: Arial, sans-serif;
font-size: 14px;
min-width: 50px;
border: 1px solid #d4d4d4;
z-index:1200;
}
.d3-context-menu ul {
list-style-type: none;
margin: 4px 0px;
padding: 0px;
cursor: default;
}
.d3-context-menu ul li {
padding: 4px 16px;
}
.d3-context-menu ul li:hover {
background-color: #4677f8;
color: #fefefe;
}
svg {
margin-left:auto;
margin-right:auto;
display:block;
position: fixed;
border: 0px solid black;
top: 32px; left:0%; right:0%; bottom:0%;
}
.menuBar {
border-bottom: 1px solid black;
height: 22px;
}
input.menuBar {
}
.nodeRect {
stroke: black;
border: 3px solid black;
}
.nodeEllipse {
stroke: black;
border: 3px solid black;
}
.nodeText {
color: black;
}
.edge {
stroke-width: 3px;
cursor: pointer;
opacity: 0.4;
}
.edgeLabelRect {
stroke: black;
border: 1px solid black;
fill: skyblue;
opacity: 0.9;
}
.edgeLabelText {
fill: black;
text-anchor: start;
}
.arrowHead {
stroke: green;
stroke-width: 1px;
}
.arrowHead_n {
stroke: green;
}
.arrowHead_r {
stroke-width: 3px;
fill: red;
stroke: red;
}
.arrowHead_b {
stroke: dodgerblue;
}
.edgeTooltip {
position: absolute;
text-align: center;
vertical-align: middle;
min-width: 10px;
min-height: 10px;
padding: 5px;
background: lightsteelblue;
border: 1px solid black;
border-radius: 8px;
pointer-events: none;
}
.nodeInfo {
position: absolute;
text-align: left;
vertical-align: middle;
min-width: 10px;
min-height: 10px;
padding: 5px;
background: lightsteelblue;
border: 1px solid black;
border-radius: 8px;
pointer-events: none;
}
path.hull {
fill: lightsteelblue;
fill-opacity: 0.3;
}
\ No newline at end of file
"""Dynamic visualization of Theano graphs.
Author: Christof Angermueller <cangermueller@gmail.com>
"""
import os
import os.path as pt
import shutil
import re
from formatting import PyDotFormatter
__path__ = pt.dirname(pt.realpath(__file__))
def replace_patterns(x, replace):
"""Replace patterns `replace` in x."""
for from_, to in replace.items():
x = x.replace(str(from_), str(to))
return x
def escape_quotes(s):
"""Escape quotes in string."""
s = re.sub(r'''(['"])''', r'\\\1', s)
return s
def d3viz(fct, outfile, copy_deps=True, *args, **kwargs):
"""Create HTML file with dynamic visualizing of a Theano function graph.
:param fct: A compiled Theano function, variable, apply or a list of
variables.
:param outfile: The output HTML file.
:param copy_deps: Copy javascript and CSS dependencies to output directory.
:param *args, **kwargs: Arguments passed to PyDotFormatter.
In the HTML file, the whole graph or single nodes can be moved by drag and
drop. Zooming is possible via the mouse wheel. Detailed information about
nodes and edges are displayed via mouse-over events. Node labels can be
edited by selecting Edit from the context menu.
Input nodes are colored in green, output nodes in blue. Apply nodes are
ellipses, and colored depending on the type of operation they perform. Red
ellipses are transfers from/to the GPU (ops with names GpuFromHost,
HostFromGpu).
Edges are black by default. If a node returns a view of an
input, the input edge will be blue. If it returns a destroyed input, the
edge will be red.
"""
# Create DOT graph
formatter = PyDotFormatter(*args, **kwargs)
graph = formatter(fct)
dot_graph = escape_quotes(graph.create_dot()).replace('\n', '')
# Create output directory if not existing
outdir = pt.dirname(outfile)
if not pt.exists(outdir):
os.makedirs(outdir)
# Read template HTML file
template_file = pt.join(__path__, 'html', 'template.html')
f = open(template_file)
template = f.read()
f.close()
# Copy dependencies to output directory
src_deps = __path__
if copy_deps:
dst_deps = 'd3viz'
for d in ['js', 'css']:
dep = pt.join(outdir, dst_deps, d)
if not pt.exists(dep):
shutil.copytree(pt.join(src_deps, d), dep)
else:
dst_deps = src_deps
# Replace patterns in template
replace = {
'%% JS_DIR %%': pt.join(dst_deps, 'js'),
'%% CSS_DIR %%': pt.join(dst_deps, 'css'),
'%% DOT_GRAPH %%': pt.basename(dot_graph),
}
html = replace_patterns(template, replace)
# Write HTML file
if outfile is not None:
f = open(outfile, 'w')
f.write(html)
f.close()
def d3write(fct, path, *args, **kwargs):
"""Convert Theano graph to pydot graph and write to file."""
formatter = PyDotFormatter(*args, **kwargs)
graph = formatter(fct)
graph.write_dot(path)
差异被折叠。
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="%% CSS_DIR %%/d3viz.css"/>
<link rel="stylesheet" href="%% CSS_DIR %%/d3-context-menu.css"/>
<script type="text/javascript" src="%% JS_DIR %%/d3viz.js"></script>
<script type="text/javascript" src="%% JS_DIR %%/d3.v3.min.js"></script>
<script type="text/javascript" src="%% JS_DIR %%/dagre-d3.js"></script>
<script type="text/javascript" src="%% JS_DIR %%/graphlib-dot.js"></script>
<script type="text/javascript" src="%% JS_DIR %%/d3-context-menu.js"></script>
</head>
<body>
<div id='menu' class='menuBar'>
<input name="resetNodes"
type="button"
value="Reset nodes"
onclick="resetNodes()"/>
<input name="releaseNodes"
type="button"
value="Release nodes"
onclick="releaseNodes()"/>
</div>
<script type="text/javascript">
// Backend graph in DOT format
var dotGraph = graphlibDot.read("%% DOT_GRAPH %%");
// Frontend graph for visualization
var graph = {};
var forceLayout;
var isProfiled = false; // true if profiling information available
var useProfileColors = false;
var fixOnInit = true; // fix nodes on initialization
var maxProfilePer = 0;
var profileColors = ["#fff5f0","#fee0d2","#fcbba1","#fc9272","#fb6a4a","#ef3b2c","#cb181d","#a50f15"];
var pad = 10;
var isEditNode = false; // true if node is edited
var menuItems = [
{
title: 'Edit',
action: function(elm, d, i) {
editNode(elm, d);
}
},
{
title: 'Release',
action: function(elm, d, i) {
releaseNode(d);
}
}
];
// Create main panel
d3.select('body').select('svg').remove();
var svg = d3.select('body').append('svg')
.attr('width', '100%')
.attr('height', '95%');
var pane = svg.append('g');
// Zoom behaviour
var zoom = d3.behavior.zoom()
.scaleExtent([0.2, 8])
.on('zoom', function(d) {
var trans = d3.event.translate;
trans[0] += 300;
trans[1] += 100;
pane.attr('transform', 'translate(' + trans + ') scale(' + d3.event.scale + ')');
});
svg.call(zoom);
zoom.event(svg);
svg.on("dblclick.zoom", null);
// Edges
var edgeDiv = d3.select('body').append('div')
.attr('class', 'edgeTooltip')
.style('opacity', 0.0);
// Div for node details
var nodeInfo = d3.select('body').append('div')
.attr('class', 'nodeInfo')
.style('opacity', 0.0);
// Definition head of edges
var markerData = [
{'id': 'n', 'color': 'black'},
{'id': 'r', 'color': 'red'},
{'id': 'b', 'color': 'dodgerblue'}];
svg.append("defs").selectAll('marker').data(markerData).enter().append("marker")
.attr("id", function(d) { return 'edgeArrow_' + d.id;})
.attr("markerWidth", 4)
.attr("markerHeight", 4)
.attr("refX", 2)
.attr("refY", 2)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,0 L4,2 L0,4 Z")
.attr('fill', function(d) { return d.color;});
// Initialize graph
processDotGraph(dotGraph);
graph = frontEndGraph(dotGraph);
drawGraph();
</script>
</body>
</html>
d3.contextMenu = function (menu, openCallback) {
// create the div element that will hold the context menu
d3.selectAll('.d3-context-menu').data([1])
.enter()
.append('div')
.attr('class', 'd3-context-menu');
// close menu
d3.select('body').on('click.d3-context-menu', function() {
d3.select('.d3-context-menu').style('display', 'none');
});
// this gets executed when a contextmenu event occurs
return function(data, index) {
var elm = this;
d3.selectAll('.d3-context-menu').html('');
var list = d3.selectAll('.d3-context-menu').append('ul');
list.selectAll('li').data(menu).enter()
.append('li')
.html(function(d) {
return d.title;
})
.on('click', function(d, i) {
d.action(elm, data, index);
d3.select('.d3-context-menu').style('display', 'none');
});
// the openCallback allows an action to fire before the menu is displayed
// an example usage would be closing a tooltip
if (openCallback) openCallback(data, index);
// display context menu
d3.select('.d3-context-menu')
.style('left', (d3.event.pageX - 2) + 'px')
.style('top', (d3.event.pageY - 2) + 'px')
.style('display', 'block');
d3.event.preventDefault();
};
};
This source diff could not be displayed because it is too large. You can view the blob instead.
差异被折叠。
This source diff could not be displayed because it is too large. You can view the blob instead.
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论