def local_scan_to_gpua(node): info = copy.deepcopy(node.op.info) if info.get('gpua', False): return info['gpua'] = True nw_ins = [node.inputs[0]] e = (1 + node.op.n_seqs + node.op.n_mit_mot + node.op.n_mit_sot + node.op.n_sit_sot + node.op.n_shared_outs) nw_ins += [safe_to_gpu(x) for x in node.inputs[1:e]] b = e e = e + node.op.n_nit_sot nw_ins += node.inputs[b:e] nw_ins += [safe_to_gpu(x) for x in node.inputs[e:]] scan_ins = [tensor_to_gpu(x) for x in node.op.inputs] scan_outs = [safe_to_gpu(x) for x in node.op.outputs] scan_outs = scan_utils.clone( scan_outs, replace=zip(node.op.inputs, [safe_to_cpu(x) for x in scan_ins])) # We need to construct the hash here, because scan # __init__ does not know about the gpu and can not # handle graphs with inputs being on the gpu tmp_in, tmp_out = gpu_reconstruct_graph(scan_ins, scan_outs) local_fgraph = gof.FunctionGraph(tmp_in, tmp_out, clone=False) _cmodule_key = gof.CLinker().cmodule_key_(local_fgraph, []) info['gpu_hash'] = hash(_cmodule_key) nw_op = scan_op.Scan(scan_ins, scan_outs, info, typeConstructor=GpuArrayType).make_node(*nw_ins) return nw_op.outputs
def test_softmax_optimizations_w_bias2(self): x = tensor.matrix('x') b = tensor.vector('b') c = tensor.vector('c') one_of_n = tensor.lvector('one_of_n') op = crossentropy_categorical_1hot fgraph = gof.FunctionGraph([x, b, c, one_of_n], [op(softmax(T.add(x, b, c)), one_of_n)]) assert fgraph.outputs[0].owner.op == op #print 'BEFORE' #for node in fgraph.toposort(): # print node.op #print '----' theano.compile.mode.optdb.query( theano.compile.mode.OPT_FAST_RUN).optimize(fgraph) #print 'AFTER' #for node in fgraph.toposort(): # print node.op #print '====' assert len(fgraph.toposort()) == 3 assert str(fgraph.outputs[0].owner.op) == 'OutputGuard' assert (fgraph.outputs[0].owner.inputs[0].owner.op == crossentropy_softmax_argmax_1hot_with_bias)
def test_softmax_optimizations_vector(self): x = tensor.vector('x') one_of_n = tensor.lvector('one_of_n') op = crossentropy_categorical_1hot fgraph = gof.FunctionGraph([x, one_of_n], [op(softmax(x), one_of_n)]) assert fgraph.outputs[0].owner.op == op theano.compile.mode.optdb.query( theano.compile.mode.OPT_FAST_RUN).optimize(fgraph) assert str(fgraph.outputs[0].owner.op) == 'OutputGuard' assert (fgraph.outputs[0].owner.inputs[0].owner.op == crossentropy_softmax_argmax_1hot_with_bias)
def test_argmax_pushdown_bias(): x = tensor.matrix() b = tensor.vector() out = tensor.argmax(softmax_with_bias(x, b), axis=-1) fgraph = gof.FunctionGraph([x, b], [out]) theano.compile.mode.optdb.query( theano.compile.mode.OPT_FAST_RUN).optimize(fgraph) #print 'AFTER' #for node in fgraph.toposort(): # print node.op assert len(fgraph.toposort()) == 4 assert isinstance(fgraph.toposort()[0].op, tensor.DimShuffle) assert isinstance(fgraph.toposort()[1].op, tensor.Elemwise) assert isinstance(fgraph.toposort()[2].op, tensor.MaxAndArgmax) assert str(fgraph.toposort()[3].op) == 'OutputGuard' x = tensor.matrix() b = tensor.vector() out = tensor.max_and_argmax(softmax_with_bias(x, b), axis=-1)[0] fgraph = gof.FunctionGraph([x, b], [out]) backup = config.warn.argmax_pushdown_bug config.warn.argmax_pushdown_bug = False try: theano.compile.mode.optdb.query( theano.compile.mode.OPT_FAST_RUN).optimize(fgraph) finally: config.warn.argmax_pushdown_bug = backup #print 'AFTER' #for node in fgraph.toposort(): # print node.op assert len(fgraph.toposort()) == 3 assert isinstance(fgraph.toposort()[0].op, SoftmaxWithBias) assert isinstance(fgraph.toposort()[1].op, tensor.CAReduce) assert isinstance(fgraph.toposort()[1].op.scalar_op, theano.scalar.Maximum) assert str(fgraph.toposort()[2].op) == 'OutputGuard'
def test_argmax_pushdown(): x = tensor.matrix() #test that the max_and_argmax is pushed down if the max is not used out = tensor.max_and_argmax(softmax(tensor.exp(tensor.tanh(sigmoid(x)))), axis=-1)[1] fgraph = gof.FunctionGraph([x], [out]) theano.compile.mode.optdb.query( theano.compile.mode.OPT_FAST_RUN).optimize(fgraph) #print 'AFTER' #for node in fgraph.toposort(): #print node.op assert len(fgraph.toposort()) == 2 # an output_guard is second assert fgraph.toposort()[0].op == tensor.basic._max_and_argmax assert str(fgraph.toposort()[1].op) == 'OutputGuard' x = tensor.matrix() #test that the max_and_argmax is not pushed down if the max is used out = tensor.max_and_argmax(softmax(tensor.exp(tensor.tanh(sigmoid(x)))), axis=-1)[0] fgraph = gof.FunctionGraph([x], [out]) backup = config.warn.argmax_pushdown_bug config.warn.argmax_pushdown_bug = False try: theano.compile.mode.optdb.query( theano.compile.mode.OPT_FAST_RUN).optimize(fgraph) finally: config.warn.argmax_pushdown_bug = backup #print 'AFTER' #for node in fgraph.toposort(): #print node.op assert len(fgraph.toposort()) == 4 # an output_guard is second assert isinstance(fgraph.toposort()[0].op, tensor.Elemwise) assert isinstance(fgraph.toposort()[1].op, Softmax) assert isinstance(fgraph.toposort()[2].op, tensor.CAReduce) assert isinstance(fgraph.toposort()[2].op.scalar_op, theano.scalar.Maximum) assert str(fgraph.toposort()[3].op) == 'OutputGuard'
def local_scan_to_gpua(node, context_name): info = copy.deepcopy(node.op.info) if info.get('gpua', False): return info['gpua'] = True nw_ins = [node.inputs[0]] e = (1 + node.op.n_seqs + node.op.n_mit_mot + node.op.n_mit_sot + node.op.n_sit_sot + node.op.n_shared_outs) nw_ins += [safe_to_gpu(x, context_name) for x in node.inputs[1:e]] b = e e = e + node.op.n_nit_sot nw_ins += node.inputs[b:e] nw_ins += [safe_to_gpu(x, context_name) for x in node.inputs[e:]] scan_ins = [tensor_to_gpu(x, context_name) for x in node.op.inputs] # The inner output corresponding to the looping condition should not be # moved to the gpu if node.op.info['as_while']: scan_outs = [safe_to_gpu(x, context_name) for x in node.op.outputs[:-1]] scan_outs += [node.op.outputs[-1]] else: scan_outs = [safe_to_gpu(x, context_name) for x in node.op.outputs] scan_outs = scan_utils.clone( scan_outs, replace=list(zip(node.op.inputs, (safe_to_cpu(x) for x in scan_ins)))) # We need to construct the hash here, because scan # __init__ does not know about the gpu and can not # handle graphs with inputs being on the gpu tmp_in, tmp_out = gpu_reconstruct_graph(scan_ins, scan_outs) local_fgraph = gof.FunctionGraph(tmp_in, tmp_out, clone=True) _cmodule_key = gof.CLinker().cmodule_key_(local_fgraph, []) info['gpu_hash'] = hash(_cmodule_key) def typebuild(dtype, broadcastable, context_name=context_name): return GpuArrayType(dtype=dtype, broadcastable=broadcastable, context_name=context_name) nw_op = scan_op.Scan(scan_ins, scan_outs, info, typeConstructor=typebuild).make_node(*nw_ins) return nw_op.outputs
def test_softmax_grad_optimizations_vector(self): x = tensor.vector('x') one_of_n = tensor.lvector('one_of_n') op = crossentropy_categorical_1hot xe = op(softmax(x), one_of_n) sum_xe = tensor.sum(xe) g_x = tensor.grad(sum_xe, x) fgraph = gof.FunctionGraph([x, one_of_n], [g_x]) #print 'BEFORE' #for node in fgraph.toposort(): # print node.op, node.inputs #print '----' theano.compile.mode.optdb.query( theano.compile.mode.OPT_FAST_RUN).optimize(fgraph) #print 'AFTER' #for node in fgraph.toposort(): # print node.op, node.inputs # the function has 9 ops because the dimshuffle and elemwise{second} # aren't getting cleaned up as well as we'd like. has_cx1hot = False has_cx1hotdx = False has_softmax = False has_softmaxdx = False for node in fgraph.toposort(): if node.op == crossentropy_softmax_argmax_1hot_with_bias: has_cx1hot = True if node.op == crossentropy_softmax_1hot_with_bias_dx: has_cx1hotdx = True if node.op == softmax: has_softmax = True if node.op == softmax_grad: has_softmaxdx = True assert has_cx1hot assert has_cx1hotdx assert not has_softmax assert not has_softmaxdx
def __call__(self, fct, graph=None): """Create pydot graph from function. Parameters ---------- fct : theano.compile.function.types.Function A compiled Theano function, variable, apply or a list of variables. graph: pydot.Dot `pydot` graph to which nodes are added. Creates new one if undefined. Returns ------- pydot.Dot Pydot graph of `fct` """ if graph is None: graph = pd.Dot() self.__nodes = {} profile = None if isinstance(fct, Function): profile = getattr(fct, "profile", None) fgraph = fct.maker.fgraph elif isinstance(fct, gof.FunctionGraph): fgraph = fct else: if isinstance(fct, gof.Variable): fct = [fct] elif isinstance(fct, gof.Apply): fct = fct.outputs assert isinstance(fct, (list, tuple)) assert all(isinstance(v, gof.Variable) for v in fct) fgraph = gof.FunctionGraph(inputs=gof.graph.inputs(fct), outputs=fct) outputs = fgraph.outputs topo = fgraph.toposort() outputs = list(outputs) # Loop over apply nodes for node in topo: nparams = {} __node_id = self.__node_id(node) nparams["name"] = __node_id nparams["label"] = apply_label(node) nparams["profile"] = apply_profile(fgraph, node, profile) nparams["node_type"] = "apply" nparams["apply_op"] = nparams["label"] nparams["shape"] = self.shapes["apply"] use_color = None for opName, color in self.apply_colors.items(): if opName in node.op.__class__.__name__: use_color = color if use_color: nparams["style"] = "filled" nparams["fillcolor"] = use_color nparams["type"] = "colored" pd_node = dict_to_pdnode(nparams) graph.add_node(pd_node) # Loop over input nodes for id, var in enumerate(node.inputs): var_id = self.__node_id(var.owner if var.owner else var) if var.owner is None: vparams = { "name": var_id, "label": var_label(var), "node_type": "input", } if isinstance(var, gof.Constant): vparams["node_type"] = "constant_input" elif isinstance( var, theano.tensor.sharedvar.TensorSharedVariable): vparams["node_type"] = "shared_input" vparams["dtype"] = type_to_str(var.type) vparams["tag"] = var_tag(var) vparams["style"] = "filled" vparams["fillcolor"] = self.node_colors[ vparams["node_type"]] vparams["shape"] = self.shapes["input"] pd_var = dict_to_pdnode(vparams) graph.add_node(pd_var) edge_params = {} if hasattr(node.op, "view_map") and id in reduce( list.__add__, node.op.view_map.values(), []): edge_params["color"] = self.node_colors["output"] elif hasattr(node.op, "destroy_map") and id in reduce( list.__add__, node.op.destroy_map.values(), []): edge_params["color"] = "red" edge_label = vparams["dtype"] if len(node.inputs) > 1: edge_label = str(id) + " " + edge_label pdedge = pd.Edge(var_id, __node_id, label=edge_label, **edge_params) graph.add_edge(pdedge) # Loop over output nodes for id, var in enumerate(node.outputs): var_id = self.__node_id(var) if var in outputs or len(fgraph.clients[var]) == 0: vparams = { "name": var_id, "label": var_label(var), "node_type": "output", "dtype": type_to_str(var.type), "tag": var_tag(var), "style": "filled", } if len(fgraph.clients[var]) == 0: vparams["fillcolor"] = self.node_colors["unused"] else: vparams["fillcolor"] = self.node_colors["output"] vparams["shape"] = self.shapes["output"] pd_var = dict_to_pdnode(vparams) graph.add_node(pd_var) graph.add_edge( pd.Edge(__node_id, var_id, label=vparams["dtype"])) elif var.name or not self.compact: graph.add_edge( pd.Edge(__node_id, var_id, label=vparams["dtype"])) # Create sub-graph for OpFromGraph nodes if isinstance(node.op, builders.OpFromGraph): subgraph = pd.Cluster(__node_id) gf = PyDotFormatter() # Use different node prefix for sub-graphs gf.__node_prefix = __node_id node.op.prepare_node(node, None, None, "py") gf(node.op.fn, subgraph) graph.add_subgraph(subgraph) pd_node.get_attributes()["subg"] = subgraph.get_name() def format_map(m): return str([list(x) for x in m]) # Inputs mapping ext_inputs = [self.__node_id(x) for x in node.inputs] int_inputs = [gf.__node_id(x) for x in node.op.local_inputs] assert len(ext_inputs) == len(int_inputs) h = format_map(zip(ext_inputs, int_inputs)) pd_node.get_attributes()["subg_map_inputs"] = h # Outputs mapping ext_outputs = [self.__node_id(x) for x in node.outputs] int_outputs = [gf.__node_id(x) for x in node.op.local_outputs] assert len(ext_outputs) == len(int_outputs) h = format_map(zip(int_outputs, ext_outputs)) pd_node.get_attributes()["subg_map_outputs"] = h return graph
def pydotprint( fct, outfile=None, compact=True, format='png', with_ids=False, high_contrast=True, cond_highlight=None, colorCodes=None, max_label_size=70, scan_graphs=False, var_with_name_simple=False, print_output_file=True, assert_nb_all_strings=-1, return_image=False, ): """Print to a file the graph of a compiled theano function's ops. Supports all pydot output formats, including png and svg. :param fct: a compiled Theano function, a Variable, an Apply or a list of Variable. :param outfile: the output file where to put the graph. :param compact: if True, will remove intermediate var that don't have name. :param format: the file format of the output. :param with_ids: Print the toposort index of the node in the node name. and an index number in the variable ellipse. :param high_contrast: if true, the color that describes the respective node is filled with its corresponding color, instead of coloring the border :param colorCodes: dictionary with names of ops as keys and colors as values :param cond_highlight: Highlights a lazy if by sorrounding each of the 3 possible categories of ops with a border. The categories are: ops that are on the left branch, ops that are on the right branch, ops that are on both branches As an alternative you can provide the node that represents the lazy if :param scan_graphs: if true it will plot the inner graph of each scan op in files with the same name as the name given for the main file to which the name of the scan op is concatenated and the index in the toposort of the scan. This index can be printed with the option with_ids. :param var_with_name_simple: If true and a variable have a name, we will print only the variable name. Otherwise, we concatenate the type to the var name. :param assert_nb_all_strings: Used for tests. If non-negative, assert that the number of unique string nodes in the dot graph is equal to this number. This is used in tests to verify that dot won't merge Theano nodes. :param return_image: If True, it will create the image and return it. Useful to display the image in ipython notebook. .. code-block:: python import theano v = theano.tensor.vector() from IPython.display import SVG SVG(theano.printing.pydotprint(v*2, return_image=True, format='svg')) In the graph, ellipses are Apply Nodes (the execution of an op) and boxes are variables. If variables have names they are used as text (if multiple vars have the same name, they will be merged in the graph). Otherwise, if the variable is constant, we print its value and finally we print the type + a unique number to prevent multiple vars from being merged. We print the op of the apply in the Apply box with a number that represents the toposort order of application of those Apply. If an Apply has more than 1 input, we label each edge between an input and the Apply node with the input's index. Green boxes are inputs variables to the graph, blue boxes are outputs variables of the graph, grey boxes are variables that are not outputs and are not used, red ellipses are transfers from/to the gpu (ops with names GpuFromHost, HostFromGpu). For edges, they are black by default. If a node returns a view of an input, we put the corresponding input edge in blue. If it returns a destroyed input, we put the corresponding edge in red. .. note:: Since October 20th, 2014, this print the inner function of all scan separately after the top level debugprint output. """ if colorCodes is None: colorCodes = default_colorCodes if outfile is None: outfile = os.path.join( config.compiledir, 'theano.pydotprint.' + config.device + '.' + format) if isinstance(fct, Function): mode = fct.maker.mode profile = getattr(fct, "profile", None) if (not isinstance(mode, ProfileMode) or fct not in mode.profile_stats): mode = None outputs = fct.maker.fgraph.outputs topo = fct.maker.fgraph.toposort() elif isinstance(fct, gof.FunctionGraph): mode = None profile = None outputs = fct.outputs topo = fct.toposort() else: if isinstance(fct, gof.Variable): fct = [fct] elif isinstance(fct, gof.Apply): fct = fct.outputs assert isinstance(fct, (list, tuple)) assert all(isinstance(v, gof.Variable) for v in fct) fct = gof.FunctionGraph(inputs=gof.graph.inputs(fct), outputs=fct) mode = None profile = None outputs = fct.outputs topo = fct.toposort() if not pydot_imported: raise RuntimeError("Failed to import pydot. You must install pydot" " for `pydotprint` to work.") return g = pd.Dot() if cond_highlight is not None: c1 = pd.Cluster('Left') c2 = pd.Cluster('Right') c3 = pd.Cluster('Middle') cond = None for node in topo: if (node.op.__class__.__name__ == 'IfElse' and node.op.name == cond_highlight): cond = node if cond is None: _logger.warn("pydotprint: cond_highlight is set but there is no" " IfElse node in the graph") cond_highlight = None if cond_highlight is not None: def recursive_pass(x, ls): if not x.owner: return ls else: ls += [x.owner] for inp in x.inputs: ls += recursive_pass(inp, ls) return ls left = set(recursive_pass(cond.inputs[1], [])) right = set(recursive_pass(cond.inputs[2], [])) middle = left.intersection(right) left = left.difference(middle) right = right.difference(middle) middle = list(middle) left = list(left) right = list(right) var_str = {} all_strings = set() def var_name(var): if var in var_str: return var_str[var] if var.name is not None: if var_with_name_simple: varstr = var.name else: varstr = 'name=' + var.name + " " + str(var.type) elif isinstance(var, gof.Constant): dstr = 'val=' + str(np.asarray(var.data)) if '\n' in dstr: dstr = dstr[:dstr.index('\n')] varstr = '%s %s' % (dstr, str(var.type)) elif (var in input_update and input_update[var].variable.name is not None): if var_with_name_simple: varstr = input_update[var].variable.name + " UPDATE" else: varstr = (input_update[var].variable.name + " UPDATE " + str(var.type)) else: # a var id is needed as otherwise var with the same type will be # merged in the graph. varstr = str(var.type) if (varstr in all_strings) or with_ids: idx = ' id=' + str(len(var_str)) if len(varstr) + len(idx) > max_label_size: varstr = varstr[:max_label_size - 3 - len(idx)] + idx + '...' else: varstr = varstr + idx elif len(varstr) > max_label_size: varstr = varstr[:max_label_size - 3] + '...' idx = 1 while varstr in all_strings: idx += 1 suffix = ' id=' + str(idx) varstr = (varstr[:max_label_size - 3 - len(suffix)] + '...' + suffix) var_str[var] = varstr all_strings.add(varstr) return varstr apply_name_cache = {} def apply_name(node): if node in apply_name_cache: return apply_name_cache[node] prof_str = '' if mode: time = mode.profile_stats[fct].apply_time.get(node, 0) # second, % total time in profiler, %fct time in profiler if mode.local_time == 0: pt = 0 else: pt = time * 100 / mode.local_time if mode.profile_stats[fct].fct_callcount == 0: pf = 0 else: pf = time * 100 / mode.profile_stats[fct].fct_call_time prof_str = ' (%.3fs,%.3f%%,%.3f%%)' % (time, pt, pf) elif profile: time = profile.apply_time.get(node, 0) # second, %fct time in profiler if profile.fct_callcount == 0: pf = 0 else: pf = time * 100 / profile.fct_call_time prof_str = ' (%.3fs,%.3f%%)' % (time, pf) applystr = str(node.op).replace(':', '_') applystr += prof_str if (applystr in all_strings) or with_ids: idx = ' id=' + str(topo.index(node)) if len(applystr) + len(idx) > max_label_size: applystr = (applystr[:max_label_size - 3 - len(idx)] + idx + '...') else: applystr = applystr + idx elif len(applystr) > max_label_size: applystr = applystr[:max_label_size - 3] + '...' idx = 1 while applystr in all_strings: idx += 1 suffix = ' id=' + str(idx) applystr = (applystr[:max_label_size - 3 - len(suffix)] + '...' + suffix) all_strings.add(applystr) apply_name_cache[node] = applystr return applystr # Update the inputs that have an update function input_update = {} # Here outputs can be the original list, as we should not change # it, we must copy it. outputs = list(outputs) if isinstance(fct, Function): for i in reversed(fct.maker.expanded_inputs): if i.update is not None: input_update[outputs.pop()] = i apply_shape = 'ellipse' var_shape = 'box' for node_idx, node in enumerate(topo): astr = apply_name(node) use_color = None for opName, color in colorCodes.items(): if opName in node.op.__class__.__name__: use_color = color if use_color is None: nw_node = pd.Node(astr, shape=apply_shape) elif high_contrast: nw_node = pd.Node(astr, style='filled', fillcolor=use_color, shape=apply_shape) else: nw_node = pd.Node(astr, color=use_color, shape=apply_shape) g.add_node(nw_node) if cond_highlight: if node in middle: c3.add_node(nw_node) elif node in left: c1.add_node(nw_node) elif node in right: c2.add_node(nw_node) for id, var in enumerate(node.inputs): varstr = var_name(var) label = str(var.type) if len(node.inputs) > 1: label = str(id) + ' ' + label if len(label) > max_label_size: label = label[:max_label_size - 3] + '...' param = {} if hasattr(node.op, 'view_map') and id in reduce( list.__add__, node.op.view_map.values(), []): param['color'] = 'blue' elif hasattr(node.op, 'destroy_map') and id in reduce( list.__add__, node.op.destroy_map.values(), []): param['color'] = 'red' if var.owner is None: if high_contrast: g.add_node( pd.Node(varstr, style='filled', fillcolor='green', shape=var_shape)) else: g.add_node(pd.Node(varstr, color='green', shape=var_shape)) g.add_edge(pd.Edge(varstr, astr, label=label, **param)) elif var.name or not compact: g.add_edge(pd.Edge(varstr, astr, label=label, **param)) else: # no name, so we don't make a var ellipse g.add_edge( pd.Edge(apply_name(var.owner), astr, label=label, **param)) for id, var in enumerate(node.outputs): varstr = var_name(var) out = var in outputs label = str(var.type) if len(node.outputs) > 1: label = str(id) + ' ' + label if len(label) > max_label_size: label = label[:max_label_size - 3] + '...' if out: g.add_edge(pd.Edge(astr, varstr, label=label)) if high_contrast: g.add_node( pd.Node(varstr, style='filled', fillcolor='blue', shape=var_shape)) else: g.add_node(pd.Node(varstr, color='blue', shape=var_shape)) elif len(var.clients) == 0: g.add_edge(pd.Edge(astr, varstr, label=label)) if high_contrast: g.add_node( pd.Node(varstr, style='filled', fillcolor='grey', shape=var_shape)) else: g.add_node(pd.Node(varstr, color='grey', shape=var_shape)) elif var.name or not compact: g.add_edge(pd.Edge(astr, varstr, label=label)) # else: # don't add egde here as it is already added from the inputs. if cond_highlight: g.add_subgraph(c1) g.add_subgraph(c2) g.add_subgraph(c3) if not outfile.endswith('.' + format): outfile += '.' + format if assert_nb_all_strings != -1: assert len(all_strings) == assert_nb_all_strings, len(all_strings) if scan_graphs: scan_ops = [(idx, x) for idx, x in enumerate(topo) if isinstance(x.op, theano.scan_module.scan_op.Scan)] path, fn = os.path.split(outfile) basename = '.'.join(fn.split('.')[:-1]) # Safe way of doing things .. a file name may contain multiple . ext = fn[len(basename):] for idx, scan_op in scan_ops: # is there a chance that name is not defined? if hasattr(scan_op.op, 'name'): new_name = basename + '_' + scan_op.op.name + '_' + str(idx) else: new_name = basename + '_' + str(idx) new_name = os.path.join(path, new_name + ext) pydotprint(scan_op.op.fn, new_name, compact, format, with_ids, high_contrast, cond_highlight, colorCodes, max_label_size, scan_graphs) if return_image: return g.create(prog='dot', format=format) else: g.write(outfile, prog='dot', format=format) if print_output_file: print 'The output file is available at', outfile
def __call__(self, fct, graph=None): """Create pydot graph from function. Parameters ---------- fct : theano.compile.function_module.Function A compiled Theano function, variable, apply or a list of variables. graph: pydot.Dot `pydot` graph to which nodes are added. Creates new one if undefined. Returns ------- pydot.Dot Pydot graph of `fct` """ if graph is None: graph = pd.Dot() self.__nodes = {} profile = None if isinstance(fct, Function): mode = fct.maker.mode if (not isinstance(mode, ProfileMode) or fct not in mode.profile_stats): mode = None if mode: profile = mode.profile_stats[fct] else: profile = getattr(fct, "profile", None) outputs = fct.maker.fgraph.outputs topo = fct.maker.fgraph.toposort() elif isinstance(fct, gof.FunctionGraph): outputs = fct.outputs topo = fct.toposort() else: if isinstance(fct, gof.Variable): fct = [fct] elif isinstance(fct, gof.Apply): fct = fct.outputs assert isinstance(fct, (list, tuple)) assert all(isinstance(v, gof.Variable) for v in fct) fct = gof.FunctionGraph(inputs=gof.graph.inputs(fct), outputs=fct) outputs = fct.outputs topo = fct.toposort() outputs = list(outputs) # Loop over apply nodes for node in topo: nparams = {} __node_id = self.__node_id(node) nparams['name'] = __node_id nparams['label'] = apply_label(node) nparams['profile'] = apply_profile(node, profile) nparams['node_type'] = 'apply' nparams['apply_op'] = nparams['label'] nparams['shape'] = self.shapes['apply'] use_color = None for opName, color in iteritems(self.apply_colors): if opName in node.op.__class__.__name__: use_color = color if use_color: nparams['style'] = 'filled' nparams['fillcolor'] = use_color nparams['type'] = 'colored' pd_node = dict_to_pdnode(nparams) graph.add_node(pd_node) # Loop over input nodes for id, var in enumerate(node.inputs): var_id = self.__node_id(var.owner if var.owner else var) if var.owner is None: vparams = { 'name': var_id, 'label': var_label(var), 'node_type': 'input' } if isinstance(var, gof.Constant): vparams['node_type'] = 'constant_input' elif isinstance( var, theano.tensor.sharedvar.TensorSharedVariable): vparams['node_type'] = 'shared_input' vparams['dtype'] = type_to_str(var.type) vparams['tag'] = var_tag(var) vparams['style'] = 'filled' vparams['fillcolor'] = self.node_colors[ vparams['node_type']] vparams['shape'] = self.shapes['input'] pd_var = dict_to_pdnode(vparams) graph.add_node(pd_var) edge_params = {} if hasattr(node.op, 'view_map') and \ id in reduce(list.__add__, itervalues(node.op.view_map), []): edge_params['color'] = self.node_colors['output'] elif hasattr(node.op, 'destroy_map') and \ id in reduce(list.__add__, itervalues(node.op.destroy_map), []): edge_params['color'] = 'red' edge_label = vparams['dtype'] if len(node.inputs) > 1: edge_label = str(id) + ' ' + edge_label pdedge = pd.Edge(var_id, __node_id, label=edge_label, **edge_params) graph.add_edge(pdedge) # Loop over output nodes for id, var in enumerate(node.outputs): var_id = self.__node_id(var) if var in outputs or len(var.clients) == 0: vparams = { 'name': var_id, 'label': var_label(var), 'node_type': 'output', 'dtype': type_to_str(var.type), 'tag': var_tag(var), 'style': 'filled' } if len(var.clients) == 0: vparams['fillcolor'] = self.node_colors['unused'] else: vparams['fillcolor'] = self.node_colors['output'] vparams['shape'] = self.shapes['output'] pd_var = dict_to_pdnode(vparams) graph.add_node(pd_var) graph.add_edge( pd.Edge(__node_id, var_id, label=vparams['dtype'])) elif var.name or not self.compact: graph.add_edge( pd.Edge(__node_id, var_id, label=vparams['dtype'])) # Create sub-graph for OpFromGraph nodes if isinstance(node.op, builders.OpFromGraph): subgraph = pd.Cluster(__node_id) gf = PyDotFormatter() # Use different node prefix for sub-graphs gf.__node_prefix = __node_id gf(node.op.fn, subgraph) graph.add_subgraph(subgraph) pd_node.get_attributes()['subg'] = subgraph.get_name() def format_map(m): return str([list(x) for x in m]) # Inputs mapping ext_inputs = [self.__node_id(x) for x in node.inputs] int_inputs = [ gf.__node_id(x) for x in node.op.fn.maker.fgraph.inputs ] assert len(ext_inputs) == len(int_inputs) h = format_map(zip(ext_inputs, int_inputs)) pd_node.get_attributes()['subg_map_inputs'] = h # Outputs mapping ext_outputs = [] for n in topo: for i in n.inputs: h = i.owner if i.owner else i if h is node: ext_outputs.append(self.__node_id(n)) int_outputs = node.op.fn.maker.fgraph.outputs int_outputs = [gf.__node_id(x) for x in int_outputs] assert len(ext_outputs) == len(int_outputs) h = format_map(zip(int_outputs, ext_outputs)) pd_node.get_attributes()['subg_map_outputs'] = h return graph
def FunctionGraph(i, o): e = gof.FunctionGraph(i, o) return e
def test_extending_2(self): ''' This test fails in DebugMode for the same reasons the test in tensor/tests/test_basic.py:T_scalarfromtensor.test0 fails on debug mode ( as much as I could tell - Razvan ) ''' from theano import gof class Double(gof.Type): def filter(self, x, strict=False, allow_downcast=None): if strict and not isinstance(x, float): raise TypeError('Expected a float!') return float(x) def values_eq_approx(self, x, y, tolerance=1e-4): return abs(x - y) / (abs(x) + abs(y)) < tolerance def __str__(self): return "double" # Added to make those tests pass in DebugMode @staticmethod def may_share_memory(a, b): return a is b double = Double() class BinaryDoubleOp(gof.Op): def __init__(self, name, fn): self.name = name self.fn = fn def __eq__(self, other): return type(self) == type(other) and ( self.name == other.name) and (self.fn == other.fn) def __hash__(self): return hash(type(self)) ^ hash(self.name) ^ hash(self.fn) def make_node(self, x, y): if isinstance(x, (int, float)): x = gof.Constant(double, x) if isinstance(y, (int, float)): y = gof.Constant(double, y) if x.type != double or y.type != double: raise TypeError('%s only works on doubles' % self.name) return gof.Apply(self, [x, y], [double()]) def perform(self, node, inp, out): x, y = inp z, = out z[0] = self.fn(x, y) def __str__(self): return self.name add = BinaryDoubleOp(name='add', fn=lambda x, y: x + y) sub = BinaryDoubleOp(name='sub', fn=lambda x, y: x - y) mul = BinaryDoubleOp(name='mul', fn=lambda x, y: x * y) div = BinaryDoubleOp(name='div', fn=lambda x, y: x / y) def c_declare(name, sub, check_input=True): return """ double %(name)s; """ % dict(name=name) double.c_declare = c_declare def c_init(name, sub): return """ %(name)s = 0.0; """ % dict(name=name) double.c_init = c_init def c_extract(name, sub, check_input=True): if (check_input): pre = """ if (!PyFloat_Check(py_%(name)s)) { PyErr_SetString(PyExc_TypeError, "expected a float"); %(fail)s }""" % dict(name=name, fail=sub['fail']) else: pre = "" return pre + """ %(name)s = PyFloat_AsDouble(py_%(name)s); """ % dict(name=name, fail=sub['fail']) double.c_extract = c_extract def c_sync(name, sub): return """ Py_XDECREF(py_%(name)s); py_%(name)s = PyFloat_FromDouble(%(name)s); if (!py_%(name)s) { printf("PyFloat_FromDouble failed on: %%f\\n", %(name)s); Py_XINCREF(Py_None); py_%(name)s = Py_None; } """ % dict(name=name) double.c_sync = c_sync def c_cleanup(name, sub): return "" double.c_cleanup = c_cleanup from theano import function x, y, z = double('x'), double('y'), double('z') a = add(x, y) b = mul(a, z) f = function([x, y, z], b) assert f(1.0, 2.0, 3.0) == 9.0 from theano import gof class Double(gof.Type): def filter(self, x, strict=False, allow_downcast=None): if strict and not isinstance(x, float): raise TypeError('Expected a float!') return float(x) def values_eq_approx(self, x, y, tolerance=1e-4): return abs(x - y) / (x + y) < tolerance def __str__(self): return "double" def c_declare(self, name, sub, check_input=True): return """ double %(name)s; """ % dict(name=name) def c_init(self, name, sub): return """ %(name)s = 0.0; """ % dict(name=name) def c_extract(self, name, sub, check_input=True): if (check_input): pre = """ if (!PyFloat_Check(py_%(name)s)) { PyErr_SetString(PyExc_TypeError, "expected a float"); %(fail)s } """ % dict(sub, name=name) else: pre = "" return pre + """ %(name)s = PyFloat_AsDouble(py_%(name)s); """ % dict(sub, name=name) def c_sync(self, name, sub): return """ Py_XDECREF(py_%(name)s); py_%(name)s = PyFloat_FromDouble(%(name)s); if (!py_%(name)s) { printf("PyFloat_FromDouble failed on: %%f\\n", %(name)s); Py_XINCREF(Py_None); py_%(name)s = Py_None; } """ % dict(name=name) def c_cleanup(self, name, sub): return "" # Added to make those tests pass in DebugMode @staticmethod def may_share_memory(a, b): return a is b double = Double() def c_code(node, name, input_names, output_names, sub): x_name, y_name = input_names[0], input_names[1] output_name = output_names[0] return """ %(output_name)s = %(x_name)s * %(y_name)s; """ % locals() mul.c_code = c_code from theano import gof class BinaryDoubleOp(gof.Op): def __init__(self, name, fn, ccode): self.name = name self.fn = fn self.ccode = ccode def make_node(self, x, y): if isinstance(x, (int, float)): x = gof.Constant(double, x) if isinstance(y, (int, float)): y = gof.Constant(double, y) if x.type != double or y.type != double: raise TypeError('%s only works on doubles' % self.name) return gof.Apply(self, [x, y], [double()]) def perform(self, node, inp, out): x, y = inp z, = out z[0] = self.fn(x, y) def __str__(self): return self.name def c_code(self, node, name, inp, out, sub): x, y = inp z, = out return self.ccode % locals() add = BinaryDoubleOp(name='add', fn=lambda x, y: x + y, ccode="%(z)s = %(x)s + %(y)s;") sub = BinaryDoubleOp(name='sub', fn=lambda x, y: x - y, ccode="%(z)s = %(x)s - %(y)s;") mul = BinaryDoubleOp(name='mul', fn=lambda x, y: x * y, ccode="%(z)s = %(x)s * %(y)s;") div = BinaryDoubleOp(name='div', fn=lambda x, y: x / y, ccode="%(z)s = %(x)s / %(y)s;") from theano.gof import toolbox class Simplify(gof.Optimizer): def add_requirements(self, fgraph): fgraph.attach_feature(toolbox.ReplaceValidate()) def apply(self, fgraph): for node in fgraph.toposort(): if node.op == div: x, y = node.inputs z = node.outputs[0] if x.owner and x.owner.op == mul: a, b = x.owner.inputs if y == a: fgraph.replace_validate(z, b) elif y == b: fgraph.replace_validate(z, a) simplify = Simplify() x = double('x') y = double('y') z = double('z') a = add(z, mul(div(mul(y, x), y), div(z, x))) e = gof.FunctionGraph([x, y, z], [a]) simplify.optimize(e) class LocalSimplify(gof.LocalOptimizer): def transform(self, node): if node.op == div: x, y = node.inputs if x.owner and x.owner.op == mul: a, b = x.owner.inputs if y == a: return [b] elif y == b: return [a] return False def tracks(self): # This should be needed for the EquilibriumOptimizer # but it isn't now # TODO: do this and explain it return [] # that's not what you should do local_simplify = LocalSimplify() x = double('x') y = double('y') z = double('z') a = add(z, mul(div(mul(y, x), y), div(z, x))) e = gof.FunctionGraph([x, y, z], [a]) simplify = gof.TopoOptimizer(local_simplify) simplify.optimize(e)
def process_node(self, fgraph, node): # this flag tells if there was any change during the last iterations changed = True clean_inputs, clean_outputs = scan_utils.reconstruct_graph( node.op.inputs, node.op.outputs) local_fgraph = gof.FunctionGraph(clean_inputs, clean_outputs) max_iterations = 2 * len(local_fgraph.toposort()) + 3 counts = 0 to_remove = [] to_replace = [] replace_with_in = [] replace_with_out = [] op = node.op # Construct the list of non_sequences to simplify a few things st = op.n_seqs st += int(numpy.sum([len(x) for x in op.tap_array[:(op.n_mit_mot + op.n_mit_sot)]])) st += op.n_sit_sot st += op.n_shared_outs non_seqs = clean_inputs[st:] st = (op.n_seqs + op.n_mit_mot + op.n_mit_sot + op.n_sit_sot + op.n_nit_sot + op.n_shared_outs + 1) outer_non_seqs = node.inputs[st:] assert len(non_seqs) == len(outer_non_seqs) while changed and counts < max_iterations: counts += 1 changed = False for nd in local_fgraph.toposort(): if (numpy.all([(x in non_seqs) or (x.owner in to_remove) or isinstance(x, tensor.Constant) for x in nd.inputs]) and # we can do this because the assumption is that a # viewOp or deepCopyOp will be just at the end of the # function and not somewhere in the middle .. not isinstance(nd.op, theano.compile.ViewOp) and not isinstance(nd.op, theano.compile.DeepCopyOp) and # and we didn't already looked at this node not nd in to_remove): # We have a candidate node to removable # Step 1. Reconstruct it on outside to_remove.append(nd) outside_ins = [] for x in nd.inputs: if x in non_seqs: outside_ins += [outer_non_seqs[non_seqs.index(x)]] elif x in to_replace: outside_ins += [ replace_with_out[to_replace.index(x)]] elif isinstance(x, theano.Constant): outside_ins += [x.clone()] else: raise Exception( ('Error in the `scan_pushout_non_seq_' 'operations`. The optimization tries ' 'to move some computation fron scan ' 'which is not allowed to move. Report ' 'this on theano-users list'), x) outside_ins = [x.type.filter_variable(y) for x,y in zip(nd.inputs, outside_ins)] nw_outer_node = nd.op.make_node(*outside_ins) # Step 2. Create variables for replacements for idx, y in enumerate(nd.outputs): y_place_holder = scan_utils.safe_new(y, '_replace') to_replace += [y] replace_with_in += [y_place_holder] assert type(y) == type(nw_outer_node.outputs[idx]) replace_with_out += [nw_outer_node.outputs[idx]] changed = True if counts >= max_iterations: raise Exception('Error in the `scan_pushout_non_seq_operations`.' ' The optimization exhausted the maximal number ' 'of iterations allowed!') # We need to check all candidate replacements and choose those that # make sense for us # Step 1. which elements of `to_replace` are used by remaining # components of the inner function clean_to_replace = [] clean_replace_with_in = [] clean_replace_with_out = [] existent_nodes = [nd for nd in local_fgraph.toposort() if nd not in to_remove] to_keep = [] for nd in existent_nodes: to_keep += nd.inputs for idx, out in enumerate(to_replace): if out in to_keep and out.owner not in existent_nodes: clean_to_replace += [out] clean_replace_with_in += [replace_with_in[idx]] clean_replace_with_out += [replace_with_out[idx]] if len(clean_to_replace) > 0: # We can finally put an end to all this madness givens = {} nw_outer = [] nw_inner = [] for to_repl, repl_in, repl_out in zip(clean_to_replace, clean_replace_with_in, clean_replace_with_out): if isinstance(repl_out, theano.Constant): repl_in = repl_out.clone() else: nw_inner += [repl_in] nw_outer += [repl_out] givens[to_repl] = repl_in _op_outs = scan_utils.clone(clean_outputs, replace=givens) _op_ins = clean_inputs + nw_inner op_ins, op_outs = scan_utils.reconstruct_graph(_op_ins, _op_outs) # Reconstruct node nwScan = scan_op.Scan(op_ins, op_outs, op.info) nw_node = nwScan.make_node(* (node.inputs + nw_outer)) fgraph.replace_all_validate_remove( zip(node.outputs, nw_node.outputs), remove=[node], reason='scan_push_computation_out') return True elif to_keep == []: # Nothing in the inner graph should be kept replace_with = {} for idx, out in enumerate(to_replace): if out in local_fgraph.outputs: x = node.outputs[local_fgraph.outputs.index(out)] y = replace_with_out[idx] shape = [y.shape[idx] for idx in xrange(y.ndim)] replace_with[x] = tensor.alloc(y, node.inputs[0], *shape) # We need to add one extra dimension to the outputs if replace_with: fgraph.replace_all_validate_remove( replace_with.items(), remove=[node], reason='scan_push_computation_out') else: return False
def pydotprint( fct, outfile=None, compact=True, format='png', with_ids=False, high_contrast=True, cond_highlight=None, colorCodes=None, max_label_size=70, scan_graphs=False, var_with_name_simple=False, print_output_file=True, return_image=False, ): """Print to a file the graph of a compiled theano function's ops. Supports all pydot output formats, including png and svg. :param fct: a compiled Theano function, a Variable, an Apply or a list of Variable. :param outfile: the output file where to put the graph. :param compact: if True, will remove intermediate var that don't have name. :param format: the file format of the output. :param with_ids: Print the toposort index of the node in the node name. and an index number in the variable ellipse. :param high_contrast: if true, the color that describes the respective node is filled with its corresponding color, instead of coloring the border :param colorCodes: dictionary with names of ops as keys and colors as values :param cond_highlight: Highlights a lazy if by surrounding each of the 3 possible categories of ops with a border. The categories are: ops that are on the left branch, ops that are on the right branch, ops that are on both branches As an alternative you can provide the node that represents the lazy if :param scan_graphs: if true it will plot the inner graph of each scan op in files with the same name as the name given for the main file to which the name of the scan op is concatenated and the index in the toposort of the scan. This index can be printed with the option with_ids. :param var_with_name_simple: If true and a variable have a name, we will print only the variable name. Otherwise, we concatenate the type to the var name. :param return_image: If True, it will create the image and return it. Useful to display the image in ipython notebook. .. code-block:: python import theano v = theano.tensor.vector() from IPython.display import SVG SVG(theano.printing.pydotprint(v*2, return_image=True, format='svg')) In the graph, ellipses are Apply Nodes (the execution of an op) and boxes are variables. If variables have names they are used as text (if multiple vars have the same name, they will be merged in the graph). Otherwise, if the variable is constant, we print its value and finally we print the type + a unique number to prevent multiple vars from being merged. We print the op of the apply in the Apply box with a number that represents the toposort order of application of those Apply. If an Apply has more than 1 input, we label each edge between an input and the Apply node with the input's index. Variable color code:: - Cyan boxes are SharedVariable, inputs and/or outputs) of the graph, - Green boxes are inputs variables to the graph, - Blue boxes are outputs variables of the graph, - Grey boxes are variables that are not outputs and are not used, Default apply node code:: - Red ellipses are transfers from/to the gpu - Yellow are scan node - Brown are shape node - Magenta are IfElse node - Dark pink are elemwise node - Purple are subtensor - Orange are alloc node For edges, they are black by default. If a node returns a view of an input, we put the corresponding input edge in blue. If it returns a destroyed input, we put the corresponding edge in red. .. note:: Since October 20th, 2014, this print the inner function of all scan separately after the top level debugprint output. """ if colorCodes is None: colorCodes = default_colorCodes if outfile is None: outfile = os.path.join( config.compiledir, 'theano.pydotprint.' + config.device + '.' + format) if isinstance(fct, Function): profile = getattr(fct, "profile", None) outputs = fct.maker.fgraph.outputs topo = fct.maker.fgraph.toposort() elif isinstance(fct, gof.FunctionGraph): profile = None outputs = fct.outputs topo = fct.toposort() else: if isinstance(fct, gof.Variable): fct = [fct] elif isinstance(fct, gof.Apply): fct = fct.outputs assert isinstance(fct, (list, tuple)) assert all(isinstance(v, gof.Variable) for v in fct) fct = gof.FunctionGraph(inputs=gof.graph.inputs(fct), outputs=fct) profile = None outputs = fct.outputs topo = fct.toposort() if not pydot_imported: raise RuntimeError( "Failed to import pydot. You must install graphviz" " and either pydot or pydot-ng for " "`pydotprint` to work.", pydot_imported_msg) g = pd.Dot() if cond_highlight is not None: c1 = pd.Cluster('Left') c2 = pd.Cluster('Right') c3 = pd.Cluster('Middle') cond = None for node in topo: if (node.op.__class__.__name__ == 'IfElse' and node.op.name == cond_highlight): cond = node if cond is None: _logger.warn("pydotprint: cond_highlight is set but there is no" " IfElse node in the graph") cond_highlight = None if cond_highlight is not None: def recursive_pass(x, ls): if not x.owner: return ls else: ls += [x.owner] for inp in x.inputs: ls += recursive_pass(inp, ls) return ls left = set(recursive_pass(cond.inputs[1], [])) right = set(recursive_pass(cond.inputs[2], [])) middle = left.intersection(right) left = left.difference(middle) right = right.difference(middle) middle = list(middle) left = list(left) right = list(right) var_str = {} var_id = {} all_strings = set() def var_name(var): if var in var_str: return var_str[var], var_id[var] if var.name is not None: if var_with_name_simple: varstr = var.name else: varstr = 'name=' + var.name + " " + str(var.type) elif isinstance(var, gof.Constant): dstr = 'val=' + str(np.asarray(var.data)) if '\n' in dstr: dstr = dstr[:dstr.index('\n')] varstr = '%s %s' % (dstr, str(var.type)) elif (var in input_update and input_update[var].name is not None): varstr = input_update[var].name if not var_with_name_simple: varstr += str(var.type) else: # a var id is needed as otherwise var with the same type will be # merged in the graph. varstr = str(var.type) if len(varstr) > max_label_size: varstr = varstr[:max_label_size - 3] + '...' var_str[var] = varstr var_id[var] = str(id(var)) all_strings.add(varstr) return varstr, var_id[var] apply_name_cache = {} apply_name_id = {} def apply_name(node): if node in apply_name_cache: return apply_name_cache[node], apply_name_id[node] prof_str = '' if profile: time = profile.apply_time.get(node, 0) # second, %fct time in profiler if profile.fct_callcount == 0 or profile.fct_call_time == 0: pf = 0 else: pf = time * 100 / profile.fct_call_time prof_str = ' (%.3fs,%.3f%%)' % (time, pf) applystr = str(node.op).replace(':', '_') applystr += prof_str if (applystr in all_strings) or with_ids: idx = ' id=' + str(topo.index(node)) if len(applystr) + len(idx) > max_label_size: applystr = (applystr[:max_label_size - 3 - len(idx)] + idx + '...') else: applystr = applystr + idx elif len(applystr) > max_label_size: applystr = applystr[:max_label_size - 3] + '...' idx = 1 while applystr in all_strings: idx += 1 suffix = ' id=' + str(idx) applystr = (applystr[:max_label_size - 3 - len(suffix)] + '...' + suffix) all_strings.add(applystr) apply_name_cache[node] = applystr apply_name_id[node] = str(id(node)) return applystr, apply_name_id[node] # Update the inputs that have an update function input_update = {} reverse_input_update = {} # Here outputs can be the original list, as we should not change # it, we must copy it. outputs = list(outputs) if isinstance(fct, Function): function_inputs = zip(fct.maker.expanded_inputs, fct.maker.fgraph.inputs) for i, fg_ii in reversed(list(function_inputs)): if i.update is not None: k = outputs.pop() # Use the fgaph.inputs as it isn't the same as maker.inputs input_update[k] = fg_ii reverse_input_update[fg_ii] = k apply_shape = 'ellipse' var_shape = 'box' for node_idx, node in enumerate(topo): astr, aid = apply_name(node) use_color = None for opName, color in iteritems(colorCodes): if opName in node.op.__class__.__name__: use_color = color if use_color is None: nw_node = pd.Node(aid, label=astr, shape=apply_shape) elif high_contrast: nw_node = pd.Node(aid, label=astr, style='filled', fillcolor=use_color, shape=apply_shape) else: nw_node = pd.Node(aid, label=astr, color=use_color, shape=apply_shape) g.add_node(nw_node) if cond_highlight: if node in middle: c3.add_node(nw_node) elif node in left: c1.add_node(nw_node) elif node in right: c2.add_node(nw_node) for idx, var in enumerate(node.inputs): varstr, varid = var_name(var) label = "" if len(node.inputs) > 1: label = str(idx) param = {} if label: param['label'] = label if hasattr(node.op, 'view_map') and idx in reduce( list.__add__, node.op.view_map.values(), []): param['color'] = colorCodes['Output'] elif hasattr(node.op, 'destroy_map') and idx in reduce( list.__add__, node.op.destroy_map.values(), []): param['color'] = 'red' if var.owner is None: color = 'green' if isinstance(var, SharedVariable): # Input are green, output blue # Mixing blue and green give cyan! (input and output var) color = "cyan" if high_contrast: g.add_node( pd.Node(varid, style='filled', fillcolor=color, label=varstr, shape=var_shape)) else: g.add_node( pd.Node(varid, color=color, label=varstr, shape=var_shape)) g.add_edge(pd.Edge(varid, aid, **param)) elif var.name or not compact or var in outputs: g.add_edge(pd.Edge(varid, aid, **param)) else: # no name, so we don't make a var ellipse if label: label += " " label += str(var.type) if len(label) > max_label_size: label = label[:max_label_size - 3] + '...' param['label'] = label g.add_edge(pd.Edge(apply_name(var.owner)[1], aid, **param)) for idx, var in enumerate(node.outputs): varstr, varid = var_name(var) out = var in outputs label = "" if len(node.outputs) > 1: label = str(idx) if len(label) > max_label_size: label = label[:max_label_size - 3] + '...' param = {} if label: param['label'] = label if out or var in input_update: g.add_edge(pd.Edge(aid, varid, **param)) if high_contrast: g.add_node( pd.Node(varid, style='filled', label=varstr, fillcolor=colorCodes['Output'], shape=var_shape)) else: g.add_node( pd.Node(varid, color=colorCodes['Output'], label=varstr, shape=var_shape)) elif len(var.clients) == 0: g.add_edge(pd.Edge(aid, varid, **param)) # grey mean that output var isn't used if high_contrast: g.add_node( pd.Node(varid, style='filled', label=varstr, fillcolor='grey', shape=var_shape)) else: g.add_node( pd.Node(varid, label=varstr, color='grey', shape=var_shape)) elif var.name or not compact: if not (not compact): if label: label += " " label += str(var.type) if len(label) > max_label_size: label = label[:max_label_size - 3] + '...' param['label'] = label g.add_edge(pd.Edge(aid, varid, **param)) g.add_node(pd.Node(varid, shape=var_shape, label=varstr)) # else: # don't add egde here as it is already added from the inputs. # The var that represent updates, must be linked to the input var. for sha, up in input_update.items(): _, shaid = var_name(sha) _, upid = var_name(up) g.add_edge( pd.Edge(shaid, upid, label="UPDATE", color=colorCodes['Output'])) if cond_highlight: g.add_subgraph(c1) g.add_subgraph(c2) g.add_subgraph(c3) if not outfile.endswith('.' + format): outfile += '.' + format if scan_graphs: scan_ops = [(idx, x) for idx, x in enumerate(topo) if isinstance(x.op, theano.scan_module.scan_op.Scan)] path, fn = os.path.split(outfile) basename = '.'.join(fn.split('.')[:-1]) # Safe way of doing things .. a file name may contain multiple . ext = fn[len(basename):] for idx, scan_op in scan_ops: # is there a chance that name is not defined? if hasattr(scan_op.op, 'name'): new_name = basename + '_' + scan_op.op.name + '_' + str(idx) else: new_name = basename + '_' + str(idx) new_name = os.path.join(path, new_name + ext) if hasattr(scan_op.op, 'fn'): to_print = scan_op.op.fn else: to_print = scan_op.op.outputs pydotprint(to_print, new_name, compact, format, with_ids, high_contrast, cond_highlight, colorCodes, max_label_size, scan_graphs) if return_image: return g.create(prog='dot', format=format) else: try: g.write(outfile, prog='dot', format=format) except pd.InvocationException: # based on https://github.com/Theano/Theano/issues/2988 version = getattr(pd, '__version__', "") if version and [int(n) for n in version.split(".")] < [1, 0, 28]: raise Exception("Old version of pydot detected, which can " "cause issues with pydot printing. Try " "upgrading pydot version to a newer one") raise if print_output_file: print('The output file is available at', outfile)