Created
April 19, 2020 07:36
-
-
Save mjamroz/5f378e8756570876351d41b076a9b5cd to your computer and use it in GitHub Desktop.
Convert mxnet mobilenet 1.0 model into tensorflow js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# REPLACE mmdnn/conversion/common/DataStructure/emitter.py | |
#---------------------------------------------------------------------------------------------- | |
# Copyright (c) Microsoft Corporation. All rights reserved. | |
# Licensed under the MIT License. See License.txt in the project root for license information. | |
#---------------------------------------------------------------------------------------------- | |
from six import string_types as _string_types | |
import mmdnn.conversion.common.IR.graph_pb2 as graph_pb2 | |
from mmdnn.conversion.common.IR.graph_pb2 import NodeDef, GraphDef, DataType | |
class Emitter(object): | |
def __init__(self): | |
self.body_code = str() | |
self.weights_dict = dict() | |
self.used_layers = set() | |
self.weight_loaded = False | |
self.layers_codes = dict() | |
def run(self, dstNetworkPath, dstWeightPath = None, phase = 'test'): | |
self.save_code(dstNetworkPath, phase) | |
# share functions | |
def add_body(self, indent, codes): | |
if isinstance(codes, _string_types): | |
codes = [codes] | |
for code in codes: | |
self.body_code += (" " * indent) + code + '\n' | |
def _load_weights(self, file_name=None): | |
import numpy as np | |
self.weight_loaded = True | |
try: | |
self.weights_dict = np.load(file_name, allow_pickle=True).item() | |
except: | |
self.weights_dict = np.load(file_name, encoding='bytes', allow_pickle=True).item() | |
def parent_variable_name(self, IR_node, path_or_name = [0]): | |
if isinstance(path_or_name, _string_types): | |
path = [IR_node.in_edges.index(path_or_name)] | |
elif isinstance(path_or_name, list): | |
path = path_or_name | |
else: | |
raise ValueError | |
return self.IR_graph.get_parent_variable_name(IR_node.name, path) | |
def _build(self): | |
self.IR_graph.build() | |
def gen_code(self, phase): | |
raise NotImplementedError("do not use base emitter class.") | |
def save_code(self, filepath, phase): | |
code = self.gen_code(phase) | |
with open(filepath, 'w') as fout: | |
fout.write(code) | |
print("Target network code snippet is saved as [{}].".format(filepath)) | |
@staticmethod | |
def save_weights(weights, filename): | |
import numpy as np | |
with open(filename, 'wb') as of: | |
np.save(of, weights) | |
print("Target weights are saved as [{}].".format(filename)) | |
@staticmethod | |
def _image_in_transpose_str(dim): | |
dims = [dim] | |
dims.extend(range(dim)) | |
return ','.join('%s' % id for id in dims) | |
@staticmethod | |
def _image_out_transpose_str(dim): | |
dims = list(range(1, dim + 1)) | |
dims.append(0) | |
return ','.join('%s' % id for id in dims) | |
@staticmethod | |
def _conv_kernel_transpose_str(dim): | |
dims = [dim + 1, dim] | |
dims.extend(range(dim)) | |
return ','.join('%s' % id for id in dims) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from mxnet.gluon import nn | |
from gluoncv.model_zoo import get_model | |
from mxnet import image, cpu, init | |
from gluoncv.data.transforms.presets.imagenet import transform_eval | |
from sys import argv | |
context = [cpu()] | |
net = get_model("mobilenet1.0", ctx=context, pretrained=True) | |
with net.name_scope(): | |
net.output = nn.Dense(2796) | |
net.output.initialize(init.Xavier(), ctx=context) | |
net.hybridize(static_alloc=True, static_shape=True) | |
net.load_parameters("mobilenet-imagenet-111-0-0140.params", ctx=context, cast_dtype=True) | |
img = image.imread("zawilec_6331.jpg") | |
img = transform_eval(img) | |
net(img) | |
net.export("model", epoch=140) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# REPLACE mmdnn/conversion/mxnet/mxnet_parser.py | |
# ---------------------------------------------------------------------------------------------- | |
# Copyright (c) Microsoft Corporation. All rights reserved. | |
# Licensed under the MIT License. See License.txt in the project root for license information. | |
# ---------------------------------------------------------------------------------------------- | |
import os | |
import sys | |
import math | |
import mxnet as mx | |
import numpy as np | |
from mmdnn.conversion.mxnet.mxnet_graph import MXNetGraph | |
import mmdnn.conversion.common.IR.graph_pb2 as graph_pb2 | |
from mmdnn.conversion.common.IR.graph_pb2 import NodeDef, GraphDef, DataType | |
from mmdnn.conversion.common.DataStructure.parser import Parser | |
from mmdnn.conversion.common.utils import * | |
class MXNetParser(Parser): | |
dtype_map = { | |
"int8": graph_pb2.DT_INT8, | |
"int16": graph_pb2.DT_INT16, | |
"int32": graph_pb2.DT_INT32, | |
"int64": graph_pb2.DT_INT64, | |
"uint8": graph_pb2.DT_UINT8, | |
"uint16": graph_pb2.DT_UINT16, | |
"uint32": graph_pb2.DT_UINT32, | |
"uint64": graph_pb2.DT_UINT64, | |
"float16": graph_pb2.DT_FLOAT16, | |
"float32": graph_pb2.DT_FLOAT32, | |
"float64": graph_pb2.DT_FLOAT64, | |
} | |
activation_map = { | |
"relu": "Relu", | |
"sigmoid": "Sigmoid", | |
"tanh": "Tanh", | |
# Not support yet | |
# "softrelu" : "SoftReLU" | |
} | |
channels_last = ["NDHWC", "NHWC", "NWC"] | |
channels_first = ["NCDHW", "NCHW", "NCW"] | |
@property | |
def src_graph(self): | |
return self.mxnet_graph | |
@staticmethod | |
def str2bool(v): | |
return v.lower() in ("1", "true") | |
@staticmethod | |
def str2intList(v): | |
v = v.replace("(", "") | |
v = v.replace(")", "") | |
if v == "": | |
return list() | |
else: | |
return [int(s) for s in v.split(",")] | |
@staticmethod | |
def transpose(data, dim): | |
if dim == 1: | |
data = data.transpose((2, 1, 0)) | |
elif dim == 2: | |
data = data.transpose((2, 3, 1, 0)) | |
elif dim == 3: | |
data = data.transpose((2, 3, 4, 1, 0)) | |
else: | |
print("Warning: The weight of dim {0} cannot transpose" % dim) | |
return data | |
@staticmethod | |
def _convert_axis(IR_node, axis): | |
ndim = len(IR_node.attr["_output_shapes"].list.shape[0].dim) | |
if axis == 0: | |
return 0 | |
elif axis == 1: | |
return ndim - 1 | |
else: | |
return axis - 1 | |
def trace_shape(self, source_node, IR_node): | |
input_node = self.IR_layer_map[IR_node.input[0]] | |
while len(input_node.attr["_output_shapes"].list.shape[0].dim) <= 2: | |
IR_node = input_node | |
input_node = self.IR_layer_map[IR_node.input[0]] | |
input_shape = list() | |
for e in input_node.attr["_output_shapes"].list.shape[0].dim: | |
input_shape.append(e.size) | |
C = input_shape.pop() | |
ret = [C] + input_shape[1:] | |
return ret | |
def check_pad_mode(self, source_node, IR_node): | |
kernel = MXNetParser.str2intList(source_node.get_attr("kernel")) | |
dim = len(kernel) | |
pad = source_node.get_attr("pad", "()") | |
if pad == "()": | |
pad = list([0] * dim) | |
else: | |
pad = MXNetParser.str2intList(pad) | |
stride = source_node.get_attr("stride") | |
if stride == None: | |
stride = list([1] * dim) | |
else: | |
stride = MXNetParser.str2intList(stride) | |
dilate = source_node.get_attr("dilate") | |
if dilate == None: | |
dilate = list([1] * dim) | |
else: | |
dilate = MXNetParser.str2intList(dilate) | |
input_shape = list() | |
if len(source_node.in_edges) == 0 or IR_node.input[0] not in self.IR_layer_map: | |
input_shape = self.data_shape | |
else: | |
for e in ( | |
self.IR_layer_map[IR_node.input[0]] | |
.attr["_output_shapes"] | |
.list.shape[0] | |
.dim | |
): | |
input_shape.append(e.size) | |
valid_flag = True | |
same_flag = True | |
for i in range(dim): | |
if not pad[i] == 0: | |
valid_flag = False | |
output_shape = int( | |
math.floor( | |
float(input_shape[i] + 2 * pad[i] - dilate[i] * (kernel[i] - 1) - 1) | |
/ float(stride[i]) | |
) | |
+ 1 | |
) | |
same_pad_shape = int(math.ceil(float(input_shape[i]) / float(stride[i]))) | |
if not output_shape == same_pad_shape: | |
same_flag = False | |
if valid_flag: | |
return "VALID" | |
elif same_flag: | |
return "SAME" | |
else: | |
return "None" | |
@staticmethod | |
def _load_model(weights, epoch): | |
"""Load a mxnet model from disk | |
Parameters | |
---------- | |
model_path: str | |
Path where the model network/params path is (json/params file) | |
prefix: str | |
prefix for json file, e.g. prefix-symbol.json | |
epoch: int | |
save epoch number | |
Returns | |
------- | |
model: A mxnet model | |
params: A pair of dictionaries each mapping parameter names to NDArray values | |
""" | |
# Load the model network and weights | |
sym, arg_params, aux_params = mx.model.load_checkpoint(weights, int(epoch)) | |
# digraph = mx.viz.plot_network(sym, save_format='jpg') # For debugging | |
# digraph.render() | |
model = mx.mod.Module(symbol=sym) | |
arg_params.update(aux_params) | |
return model, arg_params | |
""" | |
MXNet new api does not support load data without data_shapes | |
""" | |
# model.bind(data_shapes = data_shapes) | |
# model.init_params() | |
# mod.load(model_path, epoch_num) | |
# return mod.get_params() | |
@staticmethod | |
def _load_json_file(model_path): | |
"""Load a mxnet network json file | |
Parameters | |
---------- | |
model_path: str | |
Path where the model network/params path is (json/params file) | |
(Deleted) | |
prefix: str | |
prefix for json file, e.g. prefix-symbol.json | |
Returns | |
------- | |
data["nodes"]: all the layer information(including weights, bias) with format | |
data["nodes"][layer_num][params = {"name", "op", "attr", "inputs"}] | |
""" | |
import json | |
# load the model network | |
with open(model_path, "r") as data_file: | |
data = json.load(data_file) | |
# adjust the data format | |
assert isinstance(data["nodes"], list) | |
return data["nodes"] | |
def __init__(self, input_arg): | |
super(MXNetParser, self).__init__() | |
json_data = list() | |
self.data_shape = tuple() | |
# load model files into MXNet graph | |
# data_shape arguments added to calculate infer_shape(required) | |
# if isinstance(input_arg, basestring): | |
if len(input_arg) == 2: | |
with open(input_arg[0], "r") as input_json: | |
json_string = input_json.read() | |
symbol = mx.sym.load_json(json_string) | |
self.model = mx.mod.Module(symbol=symbol) | |
json_data = MXNetParser._load_json_file(input_arg[0]) | |
self.data_shape = tuple([1] + list(map(int, input_arg[1]))) | |
elif len(input_arg) == 4: | |
self.model, self.weight_data = MXNetParser._load_model( | |
input_arg[1], input_arg[2] | |
) | |
json_data = MXNetParser._load_json_file(input_arg[0]) | |
self.weight_loaded = True | |
assert isinstance(input_arg[3], list) | |
self.data_shape = tuple([1] + list(map(int, input_arg[3]))) | |
else: | |
raise ValueError( | |
"the # of input arguments [{}] is not supported" % len(input_arg) | |
) | |
# Build network graph | |
self.data_format = "None" | |
self.mxnet_graph = MXNetGraph(self.model) | |
self.mxnet_graph.build(json_data) | |
def gen_IR(self): | |
self.IR_layer_map = dict() | |
for layer in self.mxnet_graph.topological_sort: | |
current_node = self.mxnet_graph.get_node(layer) | |
node_type = current_node.type | |
if hasattr(self, "rename_" + node_type): | |
func = getattr(self, "rename_" + node_type) | |
func(current_node) | |
else: | |
self.rename_UNKNOWN(current_node) | |
def _copy_and_reop(self, source_node, IR_node, new_op=None): | |
new_op = source_node.type if new_op == None else new_op | |
if source_node.name.startswith("_"): | |
source_node.real_name = source_node.name[1:] | |
IR_node.name = source_node.real_name | |
IR_node.op = new_op | |
self.IR_layer_map[IR_node.name] = IR_node | |
def set_output_shape(self, source_node, IR_node): | |
sym_group = self.model.symbol.get_internals() | |
for sym in sym_group: | |
if source_node.name == sym.name: | |
arg_shape, output_shape, aux_shape = sym.infer_shape( | |
data=self.data_shape | |
) | |
for idx in range(len(output_shape)): | |
output_list = list(output_shape[idx]) | |
# transpose to channel last | |
if not self.data_format in MXNetParser.channels_last: | |
channel = output_list.pop(1) | |
output_list.append(channel) | |
if IR_node.op == "DataInput": | |
MXNetParser._copy_shape(IR_node, [-1] + output_list[1:]) | |
shape = graph_pb2.TensorShape() | |
for dim in output_list: | |
new_dim = shape.dim.add() | |
if dim == None: | |
new_dim.size = -1 | |
else: | |
new_dim.size = dim | |
IR_node.attr["_output_shapes"].list.shape.extend([shape]) | |
break | |
def _convert_identity_operation(self, source_node, new_op=None): | |
IR_node = self.IR_graph.node.add() | |
# name, op | |
self._copy_and_reop(source_node, IR_node, new_op) | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
return IR_node | |
def _defuse_padding(self, source_node): | |
IR_node = self.IR_graph.node.add() | |
IR_node.name = source_node.name + "_pad" | |
IR_node.op = "Pad" | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
self.IR_layer_map[IR_node.name] = IR_node | |
# attr | |
assign_IRnode_values(IR_node, {"mode": "CONSTANT"}) | |
# print("Warning: MXNet symbol pad does not support channel last") | |
pad = MXNetParser.str2intList(source_node.get_attr("pad")) | |
args["pads"] = [0, 0] | |
for e in pad: | |
args["pads"].extend([e, e]) | |
args["pads"] += [0, 0] | |
args["pads"] = convert_tf_pad_to_onnx(args["pads"]) | |
IR_node.set_attrs(args) | |
# IR_node.attr["pads"].list.i.extend([0, 0]) | |
# for e in pad: | |
# IR_node.attr["pads"].list.i.extend([e, e]) | |
# IR_node.attr["pads"].list.i.extend([0, 0]) | |
IR_node.attr["constant_values"].f = 0.0 | |
@staticmethod | |
def _copy_shape(IR_node, output_list): | |
if not output_list == None: | |
for dim in output_list: | |
new_dim = IR_node.attr["shape"].shape.dim.add() | |
if dim == None: | |
new_dim.size = -1 | |
else: | |
new_dim.size = dim | |
else: | |
IR_node.attr["shape"].shape.unknown_rank = True | |
def rename_UNKNOWN(self, source_node): | |
print( | |
"Warning: MXNet Parser has not supported operator %s with name %s." | |
% (source_node.type, source_node.name) | |
) | |
if source_node.type == "null" and source_node.name != "label": | |
print( | |
"Warning: convert the null operator with name [%s] into input layer." | |
% source_node.name | |
) | |
IR_node = self.IR_graph.node.add() | |
# name, op | |
self._copy_and_reop(source_node, IR_node, "DataInput") | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
self.set_output_shape(source_node, IR_node) | |
else: | |
raise NotImplementedError() | |
""" | |
Here start with Neural Network Symbol | |
""" | |
def rename_Pad(self, source_node): | |
IR_node = self._convert_identity_operation(source_node) | |
kwargs = dict() | |
pad = MXNetParser.str2intList(source_node.get_attr("pad_width")) | |
pad += [pad.pop(2), pad.pop(3)] | |
kwargs["pads"] = pad | |
kwargs["pads"] = convert_tf_pad_to_onnx(kwargs["pads"]) | |
kwargs["mode"] = "CONSTANT" | |
assign_IRnode_values(IR_node, kwargs) | |
IR_node.attr["constant_values"].f = 0.0 | |
def rename_FullyConnected(self, source_node): | |
IR_node = self._convert_identity_operation(source_node) | |
# units | |
IR_node.attr["units"].i = int(source_node.get_attr("num_hidden")) | |
# use bias (no_bias default = False) | |
IR_node.attr["use_bias"].b = not MXNetParser.str2bool( | |
source_node.get_attr("no_bias", "False") | |
) | |
# weights | |
if self.weight_loaded: | |
if self.data_format == "NM": | |
self.set_weight( | |
source_node.name, | |
"weights", | |
self.weight_data.get(source_node.name + "_weight") | |
.asnumpy() | |
.transpose((1, 0)), | |
) | |
else: | |
weight = self._try_to_load_mobnet( | |
source_node.name, "_weight" | |
).transpose((1, 0)) | |
original_shape = weight.shape | |
channel_first_list = self.trace_shape(source_node, IR_node) | |
dim = len(channel_first_list) + 1 | |
weight = weight.reshape(channel_first_list + [original_shape[1]]) | |
assert dim > 2 | |
weight = weight.transpose(list(range(1, dim - 1)) + [0, dim - 1]) | |
weight = weight.reshape(original_shape) | |
self.set_weight(source_node.name, "weights", weight) | |
if IR_node.attr["use_bias"].b: | |
self.set_weight( | |
source_node.name, | |
"bias", | |
self._try_to_load_mobnet(source_node.name, "_bias"), | |
) | |
if not self.data_format == "NM": | |
# print("Warning: Layer [{}] has changed model data format from [{}] to [NM]".format(source_node.name, self.data_format)) | |
self.data_format = "NM" | |
def rename_Convolution(self, source_node): | |
IR_node = self.IR_graph.node.add() | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
dim = 0 | |
layout = "None" | |
# kernel_shape | |
kernel = MXNetParser.str2intList(source_node.get_attr("kernel")) | |
dim = len(kernel) | |
IR_node.attr["kernel_shape"].list.i.extend(kernel) | |
layout = source_node.get_attr("layout") | |
if layout == None or layout == "None": | |
if dim == 1: | |
layout = "NCW" | |
elif dim == 2: | |
layout = "NCHW" | |
elif dim == 3: | |
layout = "NCDHW" | |
if not self.data_format == layout: | |
# print("Warning: Layer [{}] has changed model data format from [{}] to [{}]".format(source_node.name, self.data_format, layout)) | |
self.data_format = layout | |
# groups | |
group = int(source_node.get_attr("num_group", "1")) | |
IR_node.attr["group"].i = group | |
in_channel = ( | |
self.IR_layer_map[IR_node.input[0]] | |
.attr["_output_shapes"] | |
.list.shape[0] | |
.dim[-1] | |
.size | |
) | |
if group == in_channel: | |
self._copy_and_reop(source_node, IR_node, "DepthwiseConv") | |
else: | |
self._copy_and_reop(source_node, IR_node, "Conv") | |
# in_channel = in_channel // group | |
out_channel = int(source_node.get_attr("num_filter")) | |
IR_node.attr["kernel_shape"].list.i.extend([in_channel, out_channel]) | |
# use_bias (no_bias default = False) | |
IR_node.attr["use_bias"].b = not MXNetParser.str2bool( | |
source_node.get_attr("no_bias", "False") | |
) | |
# strides | |
strides = source_node.get_attr("stride") | |
IR_node.attr["strides"].list.i.append(1) | |
if not strides == None: | |
IR_node.attr["strides"].list.i.extend(MXNetParser.str2intList(strides)) | |
else: | |
IR_node.attr["strides"].list.i.extend([1] * dim) | |
IR_node.attr["strides"].list.i.append(1) | |
# dilations | |
dilate = source_node.get_attr("dilate") | |
IR_node.attr["dilations"].list.i.append(1) | |
if not dilate == None: | |
IR_node.attr["dilations"].list.i.extend(MXNetParser.str2intList(dilate)) | |
else: | |
IR_node.attr["dilations"].list.i.extend([1] * dim) | |
IR_node.attr["dilations"].list.i.append(1) | |
# data_format | |
assign_IRnode_values(IR_node, {"data_format": layout}) | |
# padding | |
if "pad" in source_node.attr: | |
pad = MXNetParser.str2intList(source_node.get_attr("pad")) | |
IR_node.attr["pads"].list.i.extend(([0] + pad + [0]) * 2) | |
else: | |
IR_node.attr["pads"].list.i.extend([0, 0] * (dim + 2)) | |
# weights | |
if self.weight_loaded: | |
weight = self._try_to_load_mobnet(source_node.name, "_weight") | |
if not layout in MXNetParser.channels_last: | |
weight = MXNetParser.transpose(weight, dim) | |
if IR_node.op == "DepthwiseConv": | |
weight = weight.transpose(0, 1, 3, 2) | |
self.set_weight(source_node.name, "weights", weight) | |
if IR_node.attr["use_bias"].b: | |
self.set_weight( | |
source_node.name, | |
"bias", | |
self.weight_data.get(source_node.name + "_bias").asnumpy(), | |
) | |
def _try_to_load_mobnet(self, source_node_name, suffix): | |
for key in [ | |
source_node_name + suffix, | |
source_node_name.replace("_fwd", "") + suffix, | |
source_node_name.replace("_fwd", "") | |
+ suffix.replace("_moving", "_running"), | |
]: | |
try: | |
return self.weight_data.get(key).asnumpy() | |
except: | |
pass | |
def rename_Activation(self, source_node): | |
self._convert_identity_operation( | |
source_node, | |
new_op=MXNetParser.activation_map[source_node.get_attr("act_type")], | |
) | |
def rename_BatchNorm(self, source_node): | |
IR_node = self._convert_identity_operation(source_node) | |
# axis | |
if self.data_format in MXNetParser.channels_first or self.data_format == "None": | |
IR_node.attr["axis"].i = MXNetParser._convert_axis( | |
IR_node, int(source_node.get_attr("axis", "1")) | |
) | |
else: | |
IR_node.attr["axis"].i = int(source_node.get_attr("axis", "1")) | |
# scale | |
IR_node.attr["scale"].b = not MXNetParser.str2bool( | |
source_node.get_attr("fix_gamma", "True") | |
) | |
IR_node.attr["bias"].b = True | |
# epsilon | |
IR_node.attr["epsilon"].f = float(source_node.get_attr("eps", "0.001")) | |
# momentum | |
IR_node.attr["momentum"].f = float(source_node.get_attr("momentum", "0.9")) | |
# weights | |
if self.weight_loaded: | |
# gamma | |
if IR_node.attr["scale"].b: | |
self.set_weight( | |
source_node.name, | |
"scale", | |
self._try_to_load_mobnet(source_node.name, "_gamma"), | |
) | |
# beta | |
if IR_node.attr["bias"].b: | |
self.set_weight( | |
source_node.name, | |
"bias", | |
self._try_to_load_mobnet(source_node.name, "_beta"), | |
) | |
# mean | |
self.set_weight( | |
source_node.name, | |
"mean", | |
self._try_to_load_mobnet(source_node.name, "_moving_mean"), | |
) | |
# var | |
self.set_weight( | |
source_node.name, | |
"var", | |
self._try_to_load_mobnet(source_node.name, "_moving_var"), | |
) | |
def rename_Pooling(self, source_node): | |
IR_node = self.IR_graph.node.add() | |
# name, op | |
self._copy_and_reop(source_node, IR_node, "Pool") | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
# pooling type (sum not allowed yet) | |
pool_type = source_node.get_attr("pool_type") | |
if pool_type == "sum": | |
print("Warning: sum pooling is not supported yet.") | |
elif pool_type == "max": | |
assign_IRnode_values(IR_node, {"pooling_type": "MAX"}) | |
elif pool_type == "avg": | |
assign_IRnode_values(IR_node, {"pooling_type": "AVG"}) | |
else: | |
raise ValueError("Error pool_type {}.".format(pool_type)) | |
kernel_shape = MXNetParser.str2intList(source_node.get_attr("kernel")) | |
if MXNetParser.str2bool(source_node.get_attr("global_pool", "False")): | |
IR_node.attr["global_pooling"].b = True | |
IR_node.attr["kernel_shape"].list.i[:] = [1] * (len(kernel_shape) + 2) | |
IR_node.attr["strides"].list.i[:] = [1] * (len(kernel_shape) + 2) | |
else: | |
IR_node.attr["global_pooling"].b = False | |
# strides | |
strides = source_node.get_attr("stride") | |
IR_node.attr["strides"].list.i.append(1) | |
if not strides == None: | |
IR_node.attr["strides"].list.i.extend(MXNetParser.str2intList(strides)) | |
IR_node.attr["strides"].list.i.append(1) | |
# kernel_shape | |
IR_node.attr["kernel_shape"].list.i.append(1) | |
IR_node.attr["kernel_shape"].list.i.extend(kernel_shape) | |
IR_node.attr["kernel_shape"].list.i.append(1) | |
# padding | |
if "pad" in source_node.attr: | |
pad = MXNetParser.str2intList(source_node.get_attr("pad")) | |
IR_node.attr["pads"].list.i.extend(([0] + pad + [0]) * 2) | |
else: | |
IR_node.attr["pads"].list.i.extend(([0]) * 8) | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
def rename_SoftmaxOutput(self, source_node): | |
IR_node = self.IR_graph.node.add() | |
# name, op | |
self._copy_and_reop(source_node, IR_node, "Softmax") | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
if "attr" in source_node.layer or "param" in source_node.layer: | |
print("Warning: SoftmaxOutput attrs are not supported in IR.") | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
def rename_softmax(self, source_node): | |
IR_node = self._convert_identity_operation(source_node, new_op="Softmax") | |
# dim | |
if self.data_format in MXNetParser.channels_first or self.data_format == "None": | |
IR_node.attr["dim"].i = MXNetParser._convert_axis( | |
IR_node, int(source_node.get_attr("axis", "-1")) | |
) | |
else: | |
IR_node.attr["dim"].i = int(source_node.get_attr("axis", "-1")) | |
# def rename_log_softmax(self, source_node): | |
# raise NotImplementedError("not support yet") | |
# def rename_Correlation(self, source_node): | |
# raise NotImplementedError("not support yet") | |
def rename_Deconvolution(self, source_node): | |
IR_node = self.IR_graph.node.add() | |
# name, op | |
self._copy_and_reop(source_node, IR_node, "ConvTranspose") | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
dim = 0 | |
layout = "None" | |
# padding | |
if "pad" in source_node.attr: | |
pad = MXNetParser.str2intList(source_node.get_attr("pad")) | |
IR_node.attr["pads"].list.i.extend(([0] + pad + [0]) * 2) | |
else: | |
IR_node.attr["pads"].list.i.extend([0, 0] * (dim + 2)) | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
# kernel_shape | |
kernel = MXNetParser.str2intList(source_node.get_attr("kernel")) | |
dim = len(kernel) | |
IR_node.attr["kernel_shape"].list.i.extend(kernel) | |
layout = source_node.get_attr("layout") | |
if layout == None or layout == "None": | |
if dim == 1: | |
layout = "NCW" | |
elif dim == 2: | |
layout = "NCHW" | |
elif dim == 3: | |
layout = "NCDHW" | |
if not self.data_format == layout: | |
# print("Warning: Layer [{}] has changed model data format from [{}] to [{}]".format(source_node.name, self.data_format, layout)) | |
self.data_format = layout | |
in_channel = ( | |
self.IR_layer_map[IR_node.input[0]] | |
.attr["_output_shapes"] | |
.list.shape[0] | |
.dim[-1] | |
.size | |
) | |
out_channel = int(source_node.get_attr("num_filter")) | |
IR_node.attr["kernel_shape"].list.i.extend([out_channel, in_channel]) | |
# use_bias (no_bias default = False) | |
IR_node.attr["use_bias"].b = not MXNetParser.str2bool( | |
source_node.get_attr("no_bias", "False") | |
) | |
# strides | |
strides = source_node.get_attr("strides") | |
IR_node.attr["strides"].list.i.append(1) | |
if not strides == None: | |
IR_node.attr["strides"].list.i.extend(MXNetParser.str2intList(strides)) | |
else: | |
IR_node.attr["strides"].list.i.extend([1] * dim) | |
IR_node.attr["strides"].list.i.append(1) | |
# dilations | |
dilate = source_node.get_attr("dilate") | |
IR_node.attr["dilations"].list.i.append(1) | |
if not dilate == None: | |
IR_node.attr["dilations"].list.i.extend(MXNetParser.str2intList(dilate)) | |
else: | |
IR_node.attr["dilations"].list.i.extend([1] * dim) | |
IR_node.attr["dilations"].list.i.append(1) | |
# data_format | |
IR_node.attr["data_format"].s = layout | |
# groups | |
IR_node.attr["group"].i = int(source_node.get_attr("num_group", "1")) | |
# weights | |
if self.weight_loaded: | |
weight = self.weight_data.get(source_node.name + "_weight").asnumpy() | |
if not layout in MXNetParser.channels_last: | |
weight = MXNetParser.transpose(weight, dim) | |
self.set_weight(source_node.name, "weights", weight) | |
if IR_node.attr["use_bias"].b: | |
self.set_weight( | |
source_node.name, | |
"bias", | |
self.weight_data.get(source_node.name + "_bias").asnumpy(), | |
) | |
# def rename_RNN(self, source_node): | |
# raise NotImplementedError("RNN not support yet") | |
def rename_Embedding(self, source_node): | |
IR_node = self.IR_graph.node.add() | |
# name, op | |
self._copy_and_reop(source_node, IR_node) | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
# input_dim | |
IR_node.attr["input_dim"].i = int(source_node.get_attr("input_dim")) | |
# output_dim | |
IR_node.attr["output_dim"].i = int(source_node.get_attr("output_dim")) | |
# dtype | |
IR_node.attr["dtype"].type = MXNetParser.dtype_map[ | |
source_node.get_attr("dtype", "float32") | |
] | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
# IR only support elu and prelu from {'elu', 'leaky', 'prelu', 'rrelu'} | |
def rename_LeakyReLU(self, source_node): | |
act_type = source_node.get_attr("act_type", None) | |
if act_type: | |
if not act_type == "elu" and not act_type == "prelu": | |
print("Warning: Activation Type %s is not supported yet." % act_type) | |
# return | |
IR_node = self.IR_graph.node.add() | |
# name, op | |
if act_type == "prelu": | |
self._copy_and_reop(source_node, IR_node, "PRelu") | |
# gamma | |
self.set_weight( | |
source_node.name, | |
"gamma", | |
self.weight_data.get(source_node.name + "_gamma").asnumpy(), | |
) | |
else: # All other cases set to 'Elu' | |
self._copy_and_reop(source_node, IR_node, "Elu") | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
# alpha [exp(x) - alpha], but mxnet attr slope [slope*(exp(x) - 1)] when x < 0 | |
if "slope" in source_node.attr: | |
raise ValueError("Attribute Slope is not supported in IR format") | |
# IR_node.attr["alpha"].f = float() | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
# raise NotImplementedError("slope cannot convert to alpha") | |
# def rename_InstanceNorm(self, source_node): | |
# raise NotImplementedError | |
# def rename_L2Normalization(self, source_node): | |
# raise NotImplementedError | |
def rename_LRN(self, source_node): | |
IR_node = self._convert_identity_operation(source_node) | |
# alpha | |
IR_node.attr["alpha"].f = float(source_node.get_attr("alpha", "0.0001")) | |
# beta | |
IR_node.attr["beta"].f = float(source_node.get_attr("beta", "0.75")) | |
# knorm | |
IR_node.attr["k"].f = float(source_node.get_attr("knorm", "2")) | |
# nsize | |
IR_node.attr["size"].i = float(source_node.get_attr["nsize"]) | |
def rename_ROIPooling(self, source_node): | |
raise NotImplementedError() | |
def rename_Dropout(self, source_node): | |
IR_node = self._convert_identity_operation(source_node) | |
# keep_prob | |
IR_node.attr["keep_prob"].f = float(source_node.get_attr("p", "0.5")) | |
# mode | |
assign_IRnode_values(IR_node, {"mode": "training"}) | |
""" | |
Here start with Symbol manipulation routines | |
""" | |
# reverse cannot support yet | |
def rename_reshape(self, source_node): | |
IR_node = self._convert_identity_operation(source_node, new_op="Reshape") | |
# old API target_shape not support yet | |
shape = source_node.get_attr("shape") | |
if not shape == None: | |
shape_list = MXNetParser.str2intList(shape) | |
for param in shape_list: | |
if param <= 0 and not param == -1: | |
raise ValueError( | |
"special value %d for Reshape is not pre-defined in IR." % param | |
) | |
IR_node.attr["shape"].list.i.extend(shape_list) | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
# raise NotImplementedError("adjust output shape") | |
def rename_Flatten(self, source_node): | |
self._convert_identity_operation(source_node, new_op="Flatten") | |
def rename_Concat(self, source_node): | |
IR_node = self._convert_identity_operation(source_node, new_op="Concat") | |
# dim | |
if self.data_format in MXNetParser.channels_first or self.data_format == "None": | |
IR_node.attr["axis"].i = MXNetParser._convert_axis( | |
IR_node, int(source_node.get_attr("dim", "1")) | |
) | |
else: | |
IR_node.attr["axis"].i = int(source_node.get_attr("dim", "1")) | |
def rename_cast(self, source_node): | |
IR_node = self._convert_identity_operation(source_node, new_op="Cast") | |
# dtype | |
IR_node.attr["dtype"].type = MXNetParser.dtype_map[ | |
source_node.get_attr("dtype") | |
] | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
def rename_expand_dims(self, source_node): | |
IR_node = self.IR_graph.node.add() | |
# name, op | |
self._copy_and_reop(source_node, IR_node) | |
# input edge | |
self.convert_inedge(source_node, IR_node) | |
# output shape | |
self.set_output_shape(source_node, IR_node) | |
# axis | |
if self.data_format in MXNetParser.channels_first or self.data_format == "None": | |
IR_node.attr["axis"].i = MXNetParser._convert_axis( | |
IR_node, int(source_node.get_attr("axis")) | |
) | |
else: | |
IR_node.attr["axis"].i = int(source_node.get_attr("axis")) | |
def rename_elemwise_add(self, source_node): | |
self._convert_identity_operation(source_node, new_op="Add") | |
def rename__Plus(self, source_node): | |
self._convert_identity_operation(source_node, new_op="Add") | |
def rename_broadcast_add(self, source_node): | |
self._convert_identity_operation(source_node, new_op="Add") | |
def rename_broadcast_mul(self, source_node): | |
self._convert_identity_operation(source_node, new_op="Mul") | |
def rename__mul(self, source_node): | |
self._convert_identity_operation(source_node, new_op="Mul") | |
def rename__copy(self, source_node): | |
self._convert_identity_operation(source_node) | |
# raise NotImplementedError("No matching IR api") | |
def _convert_scalar_operator(self, source_node, new_op): | |
value = source_node.get_attr("scalar") | |
value_node = self.IR_graph.node.add() | |
value_node.name = source_node.real_name + "_second" | |
# left strip the "_" at the beginning of the name | |
# Issue #85, #135 | |
value_node.name = value_node.name.lstrip("_") | |
value_node.op = "Constant" | |
self.set_weight(value_node.name, "value", np.array([value], np.float32)) | |
IR_node = self._convert_identity_operation(source_node, new_op) | |
IR_node.input.append(value_node.name) | |
return IR_node | |
def rename__mul_scalar(self, source_node): | |
self._convert_scalar_operator(source_node, "Mul") | |
def rename__minus_scalar(self, source_node): | |
self._convert_scalar_operator(source_node, "Sub") | |
def rename__copy(self, source_node): | |
source_node.real_name = self.get_parent(source_node.name, [0]).real_name | |
def rename_BlockGrad(self, source_node): | |
return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[[source]] | |
url = "https://pypi.org/simple" | |
verify_ssl = true | |
name = "pypi" | |
[packages] | |
tensorflow = "==1.15.0" | |
mxnet-mkl = "==1.6.0" | |
numpy = "==1.18.2" | |
tensorflowjs = "==1.7.2" | |
gluoncv = "==0.6.0" | |
mmdnn = "==0.2.5" | |
[dev-packages] | |
[requires] | |
python_version = "3.6" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# *params *json as converted with export.py | |
mmtoir -f mxnet -n mobilenet-imagenet-111-0-symbol.json -w mobilenet-imagenet-111-0-0140.params -d mobilenet_v1_1.0 --inputShape 3,224,224 | |
mmtocode -f tensorflow --IRModelPath mobilenet_v1_1.0.pb --IRWeightPath mobilenet_v1_1.0.npy --dstModelPath tf_mobilenet.py | |
# newer numpy: | |
sed -i 's/, encoding/, allow_pickle=True, encoding/g' tf_mobilenet.py | |
sed -i 's/d(weight_file)/d(weight_file,allow_pickle=True)/g' tf_mobilenet.py | |
mmtomodel -f tensorflow -in tf_mobilenet.py -iw mobilenet_v1_1.0.npy -o tf_mobilenet10 --dump_tag SERVING | |
tensorflowjs_converter --input_format=tf_saved_model --quantization_bytes=1 --saved_model_tags=serve --weight_shard_size_bytes 10000000000000 tf_mobilenet10/ tf_mobilenet10_js |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment