| import copy |
| import logging |
| import os |
| import re |
|
|
| from tensorboard.compat.proto.graph_pb2 import GraphDef |
| from tensorboard.compat.proto.node_def_pb2 import NodeDef |
| from tensorboard.compat.proto.tensor_shape_pb2 import TensorShapeProto |
|
|
| from builtins import bytes |
| from caffe2.proto import caffe2_pb2 |
| from caffe2.python import core, workspace |
|
|
| from typing import Set, Dict, Tuple, List |
|
|
|
|
| def _make_unique_name(seen: Set[str], name: str, min_version: int = 0): |
| """ |
| Make the name unique by appending a unique number to the name. Used for SSA. |
| |
| Args: |
| seen (set): Set of names that have already been used (with respect to |
| some context). |
| name (str): The name to make unique |
| min_version (number): Starting index. Is incremented continually until |
| it can make the resulting name unique relative to 'seen'. |
| |
| Returns: |
| x (str): A version of name that is not in seen. |
| """ |
| assert name is not None |
| i = min_version |
| x = "%s_%d" % (name, i) if i else name |
| while x in seen: |
| i += 1 |
| x = "%s_%d" % (name, i) |
| seen.add(x) |
| return x |
|
|
|
|
| def _rename_tensorflow_style(shapes, blob_name_tracker, ops): |
| """ |
| Convert some of the common names in Caffe2 to tensorflow. |
| NOTE: The common names in both Caffe2 and Tensorflow are currently |
| hardcoded, if either side changes at some point, then this code should |
| change as well. |
| |
| Args: |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| blob_name_tracker: Dictionary of all unique blob names (with respect to |
| some context). |
| ops: List of Caffe2 operators |
| |
| Returns: |
| None. The _rename_all() call modifies blob_name_tracker and ops in-place. |
| """ |
| WEIGHT = re.compile(r"(_w)$") |
| WEIGHT_ = re.compile(r"(_w_)") |
| BN = re.compile(r"(_bn)$") |
| BN_ = re.compile(r"(_bn_)") |
| BIAS = re.compile(r"(_b)$") |
| BIAS_ = re.compile(r"(_b_)") |
| SCALE = re.compile(r"(_s)$") |
| SCALE_ = re.compile(r"(_s_)") |
| SUM = re.compile(r"(_sum)$") |
| SUM_ = re.compile(r"(_sum_)") |
| BRANCH = re.compile(r"(_branch)") |
|
|
| def f(name): |
| inter_name = WEIGHT_.sub("/weight_", WEIGHT.sub("/weight", name)) |
| inter_name = BN_.sub("/batchnorm_", BN.sub("/batchnorm", inter_name)) |
| inter_name = BIAS_.sub("/bias_", BIAS.sub("/bias", inter_name)) |
| inter_name = SCALE_.sub("/scale_", SCALE.sub("/scale", inter_name)) |
| inter_name = SUM_.sub("/sum_", SUM.sub("/sum", inter_name)) |
| new_name = BRANCH.sub("/branch", inter_name) |
| return new_name |
|
|
| _rename_all(shapes, blob_name_tracker, ops, f) |
|
|
|
|
| def _convert_to_ssa(shapes, blob_name_tracker, ops): |
| """ |
| Convert an operator graph to SSA (i.e. out-of-place). |
| i.e. blobs will be renamed so that each blob is produced only once. |
| |
| Args: |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| blob_name_tracker: Dictionary of all unique blob names (with respect to |
| some context). |
| ops: List of Caffe2 operators |
| |
| Returns: |
| None. Modifies blob_name_tracker and ops in-place. |
| """ |
| ir = core.IR(ops) |
| seen: Set[str] = set() |
| versioned: Dict[Tuple[str, int], int] = {} |
| new_shapes = {} |
| new_blob_name_tracker = {} |
|
|
| def ssa_name(name: str, versions: Dict[str, int]) -> int: |
| assert name in versions |
| version = versions[name] |
| if (name, version) in versioned: |
| return versioned[(name, version)] |
| |
| |
| |
| |
| new_name = _make_unique_name(seen, name, min_version=version) |
| versioned[(name, version)] = new_name |
| |
| if name in shapes: |
| new_shapes[new_name] = shapes[name] |
| if blob_name_tracker and name in blob_name_tracker: |
| new_blob_name_tracker[new_name] = blob_name_tracker[name] |
| return new_name |
|
|
| for (op, ssa) in zip(ops, ir.ssa): |
| assert op is ssa.op |
| inputs = list(op.input) |
| outputs = list(op.output) |
| del op.input[:] |
| del op.output[:] |
| op.input.extend(ssa_name(name, ssa.in_versions) for name in inputs) |
| op.output.extend(ssa_name(name, ssa.out_versions) for name in outputs) |
|
|
| shapes.clear() |
| shapes.update(new_shapes) |
| if blob_name_tracker: |
| blob_name_tracker.clear() |
| blob_name_tracker.update(new_blob_name_tracker) |
|
|
|
|
| def _get_blob_names(ops): |
| """ |
| Get all the operator input and output blobs and perform dedup on their names. |
| |
| Args: |
| ops: List of Caffe2 operators to extract inputs and outputs from |
| |
| Returns: |
| set containing distinct inputs and outputs from 'ops' |
| """ |
| names = set() |
| for op in ops: |
| names.update(op.input) |
| names.update(op.output) |
| return {name: name for name in names} |
|
|
|
|
| def _remap_keys(old_dict, rename_fn): |
| """ |
| Rename keys of 'old_dict' according to 'rename_fn'. |
| |
| Args: |
| old_dict: Dictionary (i.e. containing blob_name -> blob_name |
| relationships.) |
| remap_fn: Function string -> string for renaming. |
| |
| Returns: |
| None. Modifies old_dict in-place. |
| """ |
| new_dict = {rename_fn(key): value for key, value in old_dict.items()} |
| old_dict.clear() |
| old_dict.update(new_dict) |
|
|
|
|
| def _rename_all(shapes, blob_name_tracker, ops, rename_fn): |
| """ |
| Rename all the names in the operators. |
| |
| Args: |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| blob_name_tracker: Dictionary of all unique blob names (with respect to |
| some context). |
| ops: List of Caffe2 operators |
| rename_fn: Function string -> string that specifies how to rename |
| |
| Returns: |
| None. Modifies shapes, blob_name_tracker and ops in-place using the |
| specified 'rename_fn'. |
| """ |
| seen: Set[str] = set() |
| renamed: Dict[Tuple[str, int], int] = {} |
|
|
| def g(name): |
| """Collision-free version of f.""" |
| if name is None: |
| return None |
| if name in renamed: |
| return renamed[name] |
| new_name = _make_unique_name(seen, rename_fn(name)) |
| renamed[name] = new_name |
| return new_name |
|
|
| for op in ops: |
| inputs = list(op.input) |
| outputs = list(op.output) |
| del op.input[:] |
| del op.output[:] |
| op.input.extend(g(name) for name in inputs) |
| op.output.extend(g(name) for name in outputs) |
|
|
| _remap_keys(shapes, g) |
| if blob_name_tracker: |
| _remap_keys(blob_name_tracker, g) |
| |
| |
| seen.clear() |
| renamed.clear() |
| for op in ops: |
| op.name = g(op.name) |
|
|
|
|
| def _add_gradient_scope(shapes, blob_name_tracker, ops): |
| """ |
| For all operators or blobs with name containing "_grad", add a |
| "GRADIENTS/" scope. |
| Note: breaks graph execution since the blob -> gradient mapping is |
| hardcoded. |
| |
| Args: |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| blob_name_tracker: Dictionary of all unique blob names (with respect to |
| some context). |
| ops: List of Caffe2 operators |
| |
| Returns: |
| None. Modifies shapes, blob_name_tracker and ops in-place by renaming. |
| """ |
|
|
| def f(name): |
| if "_grad" in name: |
| return "GRADIENTS/{}".format(name) |
| else: |
| return name |
|
|
| _rename_all(shapes, blob_name_tracker, ops, f) |
|
|
|
|
| def _replace_colons(shapes, blob_name_tracker, ops, repl): |
| """ |
| `:i` has a special meaning in Tensorflow. This function replaces all colons |
| with $ to avoid any possible conflicts. |
| |
| Args: |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| blob_name_tracker: Dictionary of all unique blob names (with respect to |
| some context). |
| ops: List of Caffe2 operators |
| repl: String representing the text to replace ':' with. Usually this is |
| '$'. |
| |
| Returns: |
| None. Modifies blob_name_tracker in-place. |
| |
| """ |
|
|
| def f(name): |
| return name.replace(":", repl) |
|
|
| _rename_all(shapes, blob_name_tracker, ops, f) |
|
|
|
|
| def _fill_missing_operator_names(ops): |
| """ |
| Give missing operators a name. |
| We expect C2 operators to be generally unnamed. This gives them a scope |
| (inferred from their outputs) and a name after their type. Duplicates will |
| be postfixed by an index. |
| |
| Args: |
| ops: List of Caffe2 operators to assign names to. |
| |
| Returns: |
| None: Modifies 'ops' in-place. |
| """ |
| seen = set() |
| for op in ops: |
| |
| seen.update(op.input) |
| seen.update(op.output) |
| for op in ops: |
| if op.name: |
| name = op.name |
| elif op.output or op.input: |
| name_list = [os.path.dirname(name) for name in op.output or op.input] |
| scope = os.path.commonprefix(name_list) |
| name = os.path.join(scope, op.type) |
| else: |
| name = op.type |
| assert name |
| op.name = _make_unique_name(seen, name) |
|
|
|
|
| def _tf_device(device_option): |
| """ |
| Handle the devices. |
| |
| Args: |
| device_option (caffe2_pb2.DeviceOption): DeviceOption protobuf, |
| associated to an operator, that contains information such as |
| device_type (optional), cuda_gpu_id (optional), node_name (optional, |
| tells which node the operator should execute on). See caffe2.proto |
| in caffe2/proto for the full list. |
| |
| Returns: |
| Formatted string representing device information contained in |
| device_option. |
| """ |
| if not device_option.HasField("device_type"): |
| return "" |
| if ( |
| device_option.device_type == caffe2_pb2.CPU |
| or device_option.device_type == caffe2_pb2.MKLDNN |
| ): |
| return "/cpu:*" |
| if device_option.device_type == caffe2_pb2.CUDA: |
| return "/gpu:{}".format(device_option.device_id) |
| raise Exception("Unhandled device", device_option) |
|
|
|
|
| def _add_tf_shape(attr_dict, ints): |
| """ |
| Converts a list of ints to a TensorShapeProto representing the dimensions of |
| a blob/object. |
| |
| Args: |
| attr_dict: Dictionary to update (usually attributes of a Node) |
| ints: List of integers representing dimensions of some object. |
| |
| Returns: |
| None. Modifies attr_dict in-place. |
| """ |
| shape_proto = TensorShapeProto() |
| for i in ints: |
| dim = TensorShapeProto.Dim() |
| dim.size = i |
| shape_proto.dim.extend([dim]) |
| attr_dict["_output_shapes"].list.shape.extend([shape_proto]) |
|
|
|
|
| def _set_tf_attr(attr_dict, arg): |
| """ |
| Add attributes to a node. Key is the arg.name, and values can be shape, |
| floats, strings, ints or an empty list. |
| |
| Args: |
| attr_dict: Dictionary to update (usually attributes of a Node) |
| arg: Object with name and data fields. |
| |
| Returns: |
| None. Modifies attr_dict in-place. |
| """ |
| k = arg.name |
| if k == "shape" and arg.ints: |
| _add_tf_shape(attr_dict, arg.ints) |
| return |
| |
| if arg.HasField("f"): |
| attr_dict[k].f = arg.f |
| return |
| |
| if arg.HasField("i"): |
| attr_dict[k].i = arg.i |
| return |
| |
| if arg.HasField("s"): |
| attr_dict[k].s = ( |
| arg.s if isinstance(arg.s, bytes) else str(arg.s).encode("utf-8") |
| ) |
| return |
| if arg.floats: |
| attr_dict[k].list.f.extend(arg.floats) |
| return |
| if arg.ints: |
| attr_dict[k].list.i.extend(arg.ints) |
| return |
| if arg.strings: |
| attr_dict[k].list.s.extend( |
| s if isinstance(s, bytes) else str(s).encode("utf-8") for s in arg.strings |
| ) |
| return |
| |
| attr_dict[k].list.s.extend([]) |
|
|
|
|
| def _operator_to_node(shapes, op): |
| """ |
| Converts an operator to a node in a TF graph. |
| |
| Args: |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| op: The Caffe2 operator to convert to a TF graph node. |
| |
| Returns: |
| n: The TF graph node created from op. |
| """ |
| assert op.name, op |
| n = NodeDef() |
| n.name = op.name |
| n.input.extend(op.input) |
| n.op = op.type |
| n.device = _tf_device(op.device_option) |
| if shapes: |
| |
| for output in op.output: |
| if output not in shapes: |
| break |
| _add_tf_shape(n.attr, shapes[output]) |
| for arg in op.arg: |
| _set_tf_attr(n.attr, arg) |
| return n |
|
|
|
|
| def _operator_to_node_simp(op, inter_blobs, seen): |
| """ |
| Convert the operators to nodes. |
| |
| Args: |
| op: Caffe2 operator to convert to node |
| inter_blobs: Set of intermediate blobs |
| seen: Names that have already been used and are not unique |
| |
| Returns: |
| nodes: Nodes representing 'op' and the outputs of 'op' |
| """ |
| assert op |
| nodes = [] |
| outputs = [o for o in op.output if o not in inter_blobs] |
| seen.update(outputs) |
| len_outputs = len(outputs) |
| if len_outputs == 1: |
| n = NodeDef() |
| n.name = outputs[0] |
| |
| n.input.extend(op.input) |
| n.op = op.type |
| n.device = _tf_device(op.device_option) |
| for arg in op.arg: |
| _set_tf_attr(n.attr, arg) |
| nodes.append(n) |
| elif len_outputs > 1: |
| |
| if op.name: |
| name = op.name |
| else: |
| name_list = list(outputs) |
| scope = os.path.commonprefix(name_list) |
| name = os.path.join(scope, op.type) |
| assert name |
| op.name = _make_unique_name(seen, name) |
| device = _tf_device(op.device_option) |
|
|
| |
| for output in outputs: |
| n = NodeDef() |
| n.name = output |
| n.input.extend([op.name]) |
| n.op = "Blob" |
| n.device = device |
| nodes.append(n) |
|
|
| |
| n = NodeDef() |
| n.name = op.name |
| n.input.extend(op.input) |
| n.op = op.type |
| n.device = device |
| for arg in op.arg: |
| _set_tf_attr(n.attr, arg) |
| nodes.append(n) |
|
|
| return nodes |
|
|
|
|
| def _blob_to_node(producing_ops, shapes, name): |
| """ |
| Converts a blob (operator input or output) to a node in a TF graph. |
| |
| Args: |
| producing_ops: Dictionary of blob name to list of |
| (producing_op, blob_index within producing_op.output) mapping. |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| name: String representing the name of this blob. |
| |
| Returns: |
| n: The TF graph node created from this blob. |
| """ |
| assert name |
| n = NodeDef() |
| n.name = name |
| |
| |
| produced_by = producing_ops.get(name, []) |
| if len(produced_by) > 0: |
| n.op = "Blob" |
| else: |
| |
| |
| n.op = "Placeholder" |
| n.input.extend("%s:%d" % (p_op.name, i) for p_op, i in produced_by) |
| if produced_by: |
| device = produced_by[0][0].device_option |
| if all(producer[0].device_option == device for producer in produced_by): |
| n.device = _tf_device(device) |
| if shapes and name in shapes: |
| _add_tf_shape(n.attr, shapes[name]) |
| return n |
|
|
|
|
| def _clear_debug_info(ops, perform_clear): |
| """ |
| Removes debug information from operators, they are copious. |
| |
| Args: |
| ops: List of Caffe2 operators |
| perform_clear: Boolean passed from _operators_to_graph_def specifying |
| whether to remove the debug information. This boolean is passed into |
| this function to reduce the complexity of _operators_to_graph_def. |
| |
| Returns: |
| None. Modifies the list of Caffe2 operators in-place and removes the |
| 'debug_info' field. |
| |
| """ |
| if not perform_clear: |
| return |
|
|
| for op in ops: |
| if op.HasField("debug_info"): |
| op.ClearField("debug_info") |
|
|
|
|
| def _check_if_forward(blob): |
| """ |
| Blobs with names containing '_m' or 'grad' are part of the backward pass. |
| This function references facebookresearch/Detectron/detectron/utils/net.py. |
| |
| Args: |
| blob: The blob to inspect |
| |
| Returns: |
| Boolean representing whether this blob is part of the forward pass |
| """ |
| |
| return blob.find("__m") < 0 or blob.find("grad") < 0 |
|
|
|
|
| def _check_if_cpu(blob): |
| """ |
| Check if the blob's name starts with '_gpu'. |
| |
| Args: |
| blob: The blob to inspect |
| |
| Returns: |
| Boolean representing whether this blob is associated with a gpu |
| """ |
| return not blob.startswith("_gpu") |
|
|
|
|
| def _compute_in_out(ops): |
| """ |
| Find the input, intermediate and output nodes of a set of operators. |
| |
| Args: |
| ops: List of Caffe2 operators to look through |
| |
| Returns: |
| input_blobs: The input nodes of the set of operators |
| inter_blobs: The intermediate nodes of the set of operators |
| output_blobs: The output nodes of the set of operators |
| """ |
| in_blobs = set() |
| out_blobs = set() |
|
|
| for op in ops: |
| for input_blob in op.input: |
| in_blobs.add(input_blob) |
| for output_blob in op.output: |
| out_blobs.add(output_blob) |
|
|
| input_blobs = list(in_blobs.difference(out_blobs)) |
| output_blobs = list(out_blobs.difference(in_blobs)) |
| inter_blobs = {b for b in output_blobs if b.startswith("_")} |
| output_blobs = [b for b in output_blobs if b not in inter_blobs] |
|
|
| return input_blobs, inter_blobs, output_blobs |
|
|
|
|
| def _filter_ops(ops, filter_fn, perform_filter): |
| """ |
| Filter unwanted operators based on criteria in 'filter_fn'. |
| |
| Args: |
| ops: List of Caffe2 operators to filter |
| filter_fn: Criteria function for whether inputs/outputs in an operator |
| should be filtered. |
| perform_filter: Boolean passed from _operators_to_graph_def specifying |
| whether to filter operators |
| |
| Returns: |
| new_ops: Subset of ops containing a subset of their inputs and outputs. |
| """ |
| if not perform_filter: |
| return ops |
|
|
| new_ops = [] |
| for op in ops: |
| inputs = list(op.input) |
| outputs = list(op.output) |
| del op.input[:] |
| del op.output[:] |
| new_inputs = [i for i in inputs if filter_fn(i)] |
| new_outputs = [o for o in outputs if filter_fn(o)] |
|
|
| |
| if new_outputs: |
| op.input.extend(new_inputs) |
| op.output.extend(new_outputs) |
| new_ops.append(op) |
|
|
| return new_ops |
|
|
|
|
| def _operators_to_graph_def( |
| shapes, |
| ops, |
| colon_replacement="$", |
| with_ssa=True, |
| with_gradient_scope=True, |
| blob_name_tracker=None, |
| show_simplified=False, |
| custom_rename=None, |
| ): |
| """ |
| Main function to convert set of operators to a graph. |
| |
| Args: |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| ops: List of Caffe2 operators, representing some computation graph |
| ### **kwargs (model_to_graph_def, nets_to_graph_def, protos_to_graph_def) ### |
| colon_replacement: Symbol to replace ':' with. ':i' in TF has a special |
| meaning, so we need to replace it with a non-conflicting symbol. |
| with_ssa: Boolean |
| with_gradient_scope: Boolean |
| blob_name_tracker: Dictionary tracking names of blobs (inputs/outputs |
| from operators) |
| show_simplified: Whether to show a simplified version of the model graph |
| Sets all of the following values: |
| clear_debug_info: Boolean representing whether to silence debug |
| info (which can be very verbose) |
| show_forward_only: Boolean representing whether to only show |
| blobs involved in the forward pass |
| show_cpu_only: Boolean representing whether to only show blobs |
| that are not associated with a gpu |
| use_tensorflow_naming: Boolean representing whether to convert |
| some common Caffe2 naming conventions to their Tensorflow |
| counterparts |
| custom_rename: Function string -> string that defines a custom |
| renaming function to use. |
| |
| Returns: |
| current_graph: GraphDef representing the computation graph formed by the |
| set of operators. |
| """ |
| if blob_name_tracker is not None: |
| blob_name_tracker.clear() |
| else: |
| blob_name_tracker = {} |
|
|
| blob_name_tracker.update(_get_blob_names(ops)) |
|
|
| _clear_debug_info(ops, show_simplified) |
| ops = _filter_ops(ops, _check_if_forward, show_simplified) |
| ops = _filter_ops(ops, _check_if_cpu, show_simplified) |
| if custom_rename: |
| _rename_all(shapes, blob_name_tracker, ops, custom_rename) |
| if colon_replacement: |
| _replace_colons(shapes, blob_name_tracker, ops, colon_replacement) |
| if with_ssa: |
| _convert_to_ssa(shapes, blob_name_tracker, ops) |
| if with_gradient_scope: |
| _add_gradient_scope(shapes, blob_name_tracker, ops) |
| _fill_missing_operator_names(ops) |
| if show_simplified: |
| _rename_tensorflow_style(shapes, blob_name_tracker, ops) |
| producing_ops: Dict[caffe2_pb2.OperatorDef, List] = {} |
| blobs = set() |
| input_blobs, inter_blobs, _ = _compute_in_out(ops) |
| current_graph = GraphDef() |
| seen = set(input_blobs) |
| for op in ops: |
| nodes_from_op = ( |
| _operator_to_node_simp(op, inter_blobs, seen) |
| if show_simplified |
| else [_operator_to_node(shapes, op)] |
| ) |
| current_graph.node.extend(nodes_from_op) |
| for input_blob in op.input: |
| blobs.add(input_blob) |
| for i, output_blob in enumerate(op.output): |
| blobs.add(output_blob) |
| producing_ops.setdefault(output_blob, []).append((op, i)) |
|
|
| if show_simplified: |
| |
| blobs = input_blobs |
|
|
| for blob in sorted(blobs): |
| current_graph.node.extend([_blob_to_node(producing_ops, {}, blob)]) |
|
|
| return current_graph |
|
|
|
|
| def _propagate_device_option(net_def): |
| """ |
| Propagate the device options from net to operators. |
| |
| Args: |
| net_def: A caffe2_pb2.NetDef representing a computation graph. The graph |
| consists of Caffe2 operators. |
| |
| Returns: |
| None. Iterates through all ops contained within the net. For each op, |
| modifies the op device_option in-place to be the net device_option |
| if the op has no pre-existing device_option, and leaves the op as-is |
| if it already has a device_option. |
| """ |
| if not net_def.HasField("device_option"): |
| return |
| for op in net_def.op: |
| if not op.HasField("device_option"): |
| op.device_option.CopyFrom(net_def.device_option) |
|
|
|
|
| def _try_get_shapes(nets): |
| """ |
| Get missing shapes for all blobs contained in the nets. |
| |
| Args: |
| nets: List of core.Net to extract blob shape information from. |
| |
| Returns: |
| Dictionary containing blob name to shape/dimensions mapping. The net |
| is a computation graph that is composed of operators, and the |
| operators have input and output blobs, each with their own dims. |
| """ |
| try: |
| |
| |
| shapes, _ = workspace.InferShapesAndTypes(nets) |
| return shapes |
| except Exception as e: |
| logging.warning("Failed to compute shapes: %s", e) |
| return {} |
|
|
|
|
| def model_to_graph_def(model, **kwargs): |
| """ |
| Convert a Caffe2 model to a Tensorflow graph. This function extracts |
| 'param_init_net' and 'net' from the model and passes it to nets_to_graph() |
| for further processing. |
| |
| Args: |
| model (cnn.CNNModelHelper, model_helper.ModelHelper): The model to |
| extract the nets (instances of core.Net) from. |
| |
| Returns: |
| Call to nets_to_graph_def() with extracted 'param_init_net', 'net' and |
| **kwargs. See _operators_to_graph_def for detailed **kwargs. |
| """ |
| nets = [model.param_init_net, model.net] |
| return nets_to_graph_def(nets, **kwargs) |
|
|
|
|
| def nets_to_graph_def(nets, shapes=None, **kwargs): |
| """ |
| Convert a set of Caffe2 nets to a Tensorflow graph. |
| |
| Args: |
| nets: List of core.Nets. core.Net is a wrapper around a NetDef protobuf. |
| The corresponding protobuf can be extracted using .Proto(). |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| |
| Returns: |
| Call to protos_to_graph_def() with the extracted NetDef protobufs and |
| **kwargs. See _operators_to_graph_def for detailed **kwargs. |
| """ |
| |
| |
| |
| |
| shapes = {} |
| nets = [copy.deepcopy(net.Proto()) for net in nets] |
| shapes = copy.deepcopy(shapes) |
| return protos_to_graph_def(nets, shapes, **kwargs) |
|
|
|
|
| def protos_to_graph_def(net_defs, shapes=None, **kwargs): |
| """ |
| Convert a set of Caffe2 net definitions to a Tensorflow graph. |
| |
| Args: |
| net_defs: List of caffe2_pb2.NetDef protobufs representing computation |
| graphs. |
| shapes: Dictionary mapping blob names to their shapes/dimensions. |
| |
| Returns: |
| Call to _operators_to_graph_def() with the extracted operators from the |
| NetDefs and **kwargs. See _operators_to_graph_def for detailed |
| **kwargs. |
| """ |
| for net in net_defs: |
| _propagate_device_option(net) |
| shapes = copy.deepcopy(shapes or {}) |
| ops = [op for net_def in net_defs for op in net_def.op] |
| return _operators_to_graph_def(shapes, ops, **kwargs) |
|
|