|  |  | 
|  |  | 
|  | from collections import defaultdict | 
|  |  | 
|  | import caffe2.python.nomnigraph as ng | 
|  | from caffe2.python import core, utils | 
|  |  | 
|  |  | 
|  | def transpose_network(nn): | 
|  | """ | 
|  | Convert all Convolutions operators which are in the NCHW order | 
|  | to NHWC order and also transform their inputs and outputs so that the | 
|  | rest of the graph is not affected. | 
|  | """ | 
|  | # track the incoming tensors into NHWC2NCHW operators | 
|  | incoming = {}  # output tensor -> input tensor | 
|  | # track outgoing tensors from NCHW2NHWC operators | 
|  | outgoing = defaultdict(lambda: [])  # input tensor -> list of operators | 
|  | dfg = nn.dataFlow | 
|  | orig_nodes = [x for x in nn.nodes] | 
|  | for node in orig_nodes: | 
|  | if node.isOperator() and node.name == "Conv": | 
|  | arg_dict = utils.ArgsToDict(node.annotation.operator_def.arg) | 
|  | # a missing "order" argument implies default NCHW order | 
|  | if "order" in arg_dict and arg_dict["order"] != "NCHW": | 
|  | continue | 
|  | inputs = [x for x in node.inputs] | 
|  | assert len(inputs) >= 2, "Conv operator should have two inputs" | 
|  | outputs = [x for x in node.outputs] | 
|  | assert len(outputs) >= 1, "Conv operator should have an output" | 
|  | for inp in inputs: | 
|  | nn.deleteEdge(inp, node) | 
|  | for outp in outputs: | 
|  | nn.deleteEdge(node, outp) | 
|  | # only the first two inputs of the Convolution the data and the | 
|  | # weights need to be transformed | 
|  | for idx in range(2): | 
|  | new_inp = nn.createUniqueDataNode(inputs[idx].name) | 
|  | transp = dfg.createNode(ng.NeuralNetOperator("NCHW2NHWC")) | 
|  | nn.createEdge(inputs[idx], transp) | 
|  | nn.createEdge(transp, new_inp) | 
|  | outgoing[inputs[idx]].append(transp) | 
|  | inputs[idx] = new_inp | 
|  | for idx in range(len(outputs)): | 
|  | new_outp = nn.createUniqueDataNode(outputs[idx].name) | 
|  | transp = dfg.createNode(ng.NeuralNetOperator("NHWC2NCHW")) | 
|  | nn.createEdge(transp, outputs[idx]) | 
|  | nn.createEdge(new_outp, transp) | 
|  | incoming[outputs[idx]] = new_outp | 
|  | outputs[idx] = new_outp | 
|  | # create a new Convolution with identical arguments as the original | 
|  | # one except for the order | 
|  | arg_dict["order"] = "NHWC" | 
|  | new_node = nn.createNode(core.CreateOperator("Conv", [], [], | 
|  | **arg_dict)) | 
|  | for inp in inputs: | 
|  | nn.createEdge(inp, new_node) | 
|  | for outp in outputs: | 
|  | nn.createEdge(new_node, outp) | 
|  |  | 
|  | nn.deleteNode(node) | 
|  |  | 
|  | # finally, we will compress | 
|  | # case 1: | 
|  | # X -> NHWC2NCHW -> Y -> NCHW2NHWC -> Z1 ; Y -> NCHW2NHWC -> Z2 | 
|  | #  to: | 
|  | # X -> NHWC2NCHW -> Y   and replace Z1 with X and replace Z2 with X | 
|  | # And case 2: | 
|  | # Y -> NCHW2NHWC -> Z1 ; Y -> NCHW2NHWC -> Z2 | 
|  | #  to: | 
|  | # Y -> NCHW2NHWC -> Z1     and   replace Z2 with Z1 | 
|  |  | 
|  | # orig_tensor is one of the tensors in the original graph in NCHW order | 
|  | for orig_tensor in outgoing: | 
|  | # new_tensor is identical to orig_tensor except the order is NHWC | 
|  | if orig_tensor in incoming:  # case 1 (see above) | 
|  | new_tensor = incoming[orig_tensor] | 
|  | else:  # case 2 (see above) | 
|  | out_ops = outgoing[orig_tensor] | 
|  | new_tensor = out_ops[0].outputs[0] | 
|  | outgoing[orig_tensor] = out_ops[1:] | 
|  |  | 
|  | for opnode in outgoing[orig_tensor]: | 
|  | # there should only be one output, so this iteration is overkill | 
|  | for out in opnode.outputs: | 
|  | nn.replaceAllUsesWith(out, new_tensor) | 
|  | nn.deleteNode(out) | 
|  | nn.deleteNode(opnode) |