# Copyright 2018 The MACE Authors. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import math import numpy as np import six import google.protobuf.text_format from py_proto import mace_pb2 from transform import base_converter from transform import shape_inference from transform.base_converter import PoolingType from transform.base_converter import ActivationType from transform.base_converter import EltwiseType from transform.base_converter import FrameworkType from transform.base_converter import DataFormat from transform.base_converter import MaceOp from transform.base_converter import MaceKeyword from transform.base_converter import ConverterUtil from utils.util import mace_check from py_proto import caffe_pb2 caffe_group_str = 'group' caffe_kernel_h_str = 'kernel_h' caffe_kernel_w_str = 'kernel_w' caffe_stride_h_str = 'stride_h' caffe_stride_w_str = 'stride_w' caffe_pad_h_str = 'pad_h' caffe_pad_w_str = 'pad_w' class CaffeOperator(object): """CaffeOperator merges and provides both layer and weights information. Layer records caffe layer proto, while blobs records the weight data in format of numpy ndarray. """ def __init__(self): self._layer = None self._blobs = None @property def name(self): return self._layer.name @property def type(self): return self._layer.type @property def layer(self): return self._layer @property def blobs(self): return self._blobs @layer.setter def layer(self, layer): self._layer = layer @blobs.setter def blobs(self, blobs): self._blobs = [self.blob_to_nparray(blob) for blob in blobs] def get_blob(self, index): mace_check(index < len(self._blobs), "blob out of index") return self._blobs[index] @staticmethod def blob_to_nparray(blob): if blob.num != 0: return (np.asarray(blob.data, dtype=np.float32).reshape( (blob.num, blob.channels, blob.height, blob.width))) else: return np.asarray(blob.data, dtype=np.float32).reshape( blob.shape.dim) class CaffeNet(object): """CaffeNet contains caffe operations. Output of each layer has unique name as we replace duplicated output name with unique one, while keep mace input/output name which user specifies unchanged.""" def __init__(self): self._ops = {} self._consumers = {} # for in-place op, its input name is the same with output name, # so we change the output name to an alias self._alias_op_output_name = {} self._used_op_output_name = set() @property def ops(self): return self._ops.values() def get_op(self, op_name): return self._ops.get(op_name, None) def get_consumers(self, tensor_name): return self._consumers.get(tensor_name, []) def add_layer(self, layer): op = CaffeOperator() op.layer = layer self._ops[layer.name] = op # change op output name if it is an in-place op layer.bottom[:] = [self._alias_op_output_name.get(layer_input, layer_input) for layer_input in layer.bottom][:] for i in six.moves.range(len(layer.top)): old_name = layer.top[i] if layer.type == 'Input': new_name = old_name else: idx = 0 new_name = old_name + '#' + str(idx) while new_name in self._used_op_output_name: idx += 1 new_name = old_name + '#' + str(idx) layer.top[i] = new_name self._alias_op_output_name[old_name] = new_name self._used_op_output_name.update([new_name]) for input_tensor in layer.bottom: if input_tensor not in self._consumers: self._consumers[input_tensor] = [] self._consumers[input_tensor].append(op) def add_blob(self, weight): if weight.name in self._ops: op = self._ops[weight.name] op.blobs = list(weight.blobs) class CaffeConverter(base_converter.ConverterInterface): """A class for convert caffe model to mace model.""" pooling_type_mode = { caffe_pb2.PoolingParameter.AVE: PoolingType.AVG, caffe_pb2.PoolingParameter.MAX: PoolingType.MAX } eltwise_type = { caffe_pb2.EltwiseParameter.PROD: EltwiseType.PROD, caffe_pb2.EltwiseParameter.SUM: EltwiseType.SUM, caffe_pb2.EltwiseParameter.MAX: EltwiseType.MAX, } activation_type = { 'ReLU': ActivationType.RELU, 'ReLU6': ActivationType.RELUX, 'PReLU': ActivationType.PRELU, 'TanH': ActivationType.TANH, 'Sigmoid': ActivationType.SIGMOID, 'Clip': ActivationType.RELUX, 'ELU': ActivationType.ELU, } def __init__(self, option, src_model_file, src_weight_file): self._op_converters = { 'Input': self.convert_nop, 'Convolution': self.convert_conv2d, 'Deconvolution': self.convert_deconv2d, 'Eltwise': self.convert_elementwise, 'Add': self.convert_add, 'ReLU': self.convert_activation, 'ReLU6': self.convert_activation, 'TanH': self.convert_activation, 'Sigmoid': self.convert_activation, 'PReLU': self.convert_activation, 'Clip': self.convert_activation, 'ELU': self.convert_activation, 'Pooling': self.convert_pooling, 'Concat': self.convert_concat, 'Slice': self.convert_slice, 'Softmax': self.convert_softmax, 'InnerProduct': self.convert_fully_connected, 'Interp': self.convert_interp, 'BatchNorm': self.convert_folded_batchnorm, 'GroupNorm': self.convert_group_norm, 'Crop': self.convert_crop, 'Scale': self.convert_scale, 'ShuffleChannel': self.convert_channel_shuffle, 'Permute': self.convert_permute, 'Flatten': self.convert_flatten, 'PriorBox': self.convert_prior_box, 'Reshape': self.convert_reshape, 'L2Normalization': self.convert_lpnorm, 'L1Normalization': self.convert_lpnorm, 'MVN': self.convert_MVN, 'Bias': self.convert_bias, 'ArgMax': self.convert_argmax, 'ResizeNearest': self.convert_resize_nearest, } self._option = option self._mace_net_def = mace_pb2.NetDef() ConverterUtil.set_filter_format(self._mace_net_def, DataFormat.OIHW) ConverterUtil.add_data_format_arg(self._mace_net_def, DataFormat.NCHW) ConverterUtil.set_framework_type( self._mace_net_def, FrameworkType.CAFFE.value) self._caffe_net = CaffeNet() self._caffe_layers = caffe_pb2.NetParameter() caffe_weights = caffe_pb2.NetParameter() # parse prototxt with open(src_model_file, 'r') as f: google.protobuf.text_format.Merge( str(f.read()), self._caffe_layers) self.filter_test_layers(self._caffe_layers) for layer in self._caffe_layers.layer: self._caffe_net.add_layer(layer) # parse model weight with open(src_weight_file, 'rb') as f: caffe_weights.ParseFromString(f.read()) self.filter_test_layers(caffe_weights) for weight in caffe_weights.layer: self._caffe_net.add_blob(weight) self._skip_ops = [] def run(self): self.convert_ops() shape_inferer = shape_inference.ShapeInference( self._mace_net_def, self._option.input_nodes.values()) shape_inferer.run() self.replace_output_tensor_name() return self._mace_net_def @staticmethod def replace_input_name(ops, src_name, dst_name): for op in ops: for i in six.moves.range(len(op.input)): if op.input[i] == src_name: op.input[i] = dst_name def replace_output_tensor_name(self): consumers = {} for op in self._mace_net_def.op: for input_name in op.input: if input_name not in consumers: consumers[input_name] = [] consumers[input_name].append(op) # replace the last op with same prefix name with the original top name ops = [op for op in self._mace_net_def.op] ops.reverse() visited = set() for op in ops: for i in six.moves.range(len(op.output)): original_output_name = op.output[i].split('#')[0] if original_output_name not in visited and \ original_output_name not in self._option.input_nodes: self.replace_input_name( consumers.get(op.output[i], []), op.output[i], original_output_name) op.output[i] = original_output_name visited.update([original_output_name]) # if user set op name as output node, replace it with op name for op in self._mace_net_def.op: if op.name in self._option.output_nodes and op.name not in visited: if len(op.output) > 0: self.replace_input_name( consumers.get(op.output[0], []), op.output, op.name) op.output[0] = op.name @staticmethod def filter_test_layers(layers): phase_map = {0: 'train', 1: 'test'} while True: changed = False for layer in layers.layer: phase = 'test' if len(layer.include): phase = phase_map[layer.include[0].phase] if len(layer.exclude): phase = phase_map[layer.exclude[0].phase] if phase != 'test' or layer.type == 'Dropout': print("Remove layer %s (%s)" % (layer.name, layer.type)) layers.layer.remove(layer) changed = True break if not changed: break @staticmethod def add_stride_pad_kernel_arg(param, op_def): try: if len(param.stride) > 1 or len(param.kernel_size) > 1 or len( param.pad) > 1: raise Exception( 'Mace does not support multiple stride/kernel_size/pad') stride = [param.stride[0], param.stride[0]] if len(param.stride) else [1, 1] pad = [param.pad[0] * 2, param.pad[0] * 2] if len(param.pad) else [0, 0] kernel = [param.kernel_size[0], param.kernel_size[0]] if len( param.kernel_size) else [0, 0] except TypeError: stride = [param.stride, param.stride] pad = [param.pad * 2, param.pad * 2] kernel = [param.kernel_size, param.kernel_size] if param.HasField(caffe_stride_h_str) or param.HasField( caffe_stride_w_str): stride = [param.stride_h, param.stride_w] if param.HasField(caffe_pad_h_str) or param.HasField(caffe_pad_w_str): pad = [param.pad_h * 2, param.pad_w * 2] strides_arg = op_def.arg.add() strides_arg.name = MaceKeyword.mace_strides_str strides_arg.ints.extend(stride) padding_arg = op_def.arg.add() padding_arg.name = MaceKeyword.mace_padding_values_str padding_arg.ints.extend(pad) if op_def.type == MaceOp.Pooling.name: if param.HasField(caffe_kernel_h_str) or param.HasField( caffe_kernel_w_str): kernel = [param.kernel_h, param.kernel_w] kernels_arg = op_def.arg.add() kernels_arg.name = MaceKeyword.mace_kernel_str kernels_arg.ints.extend(kernel) if param.HasField('global_pooling'): global_pooling_arg = op_def.arg.add() global_pooling_arg.name = MaceKeyword.mace_global_pooling_str global_pooling_arg.i = 1 def convert_ops(self): layer_names = set() for layer in self._caffe_layers.layer: caffe_op = self._caffe_net.get_op(layer.name) if caffe_op not in self._skip_ops: mace_check(layer.name not in layer_names, "There is duplicate layer name '%s' in your model" % layer.name) mace_check(layer.type in self._op_converters, "Mace does not support caffe op type %s yet" % layer.type) layer_names.add(layer.name) self._op_converters[layer.type](caffe_op) def add_tensor(self, name, shape, data_type, value): tensor = self._mace_net_def.tensors.add() tensor.name = name tensor.dims.extend(list(shape)) tensor.data_type = data_type tensor.float_data.extend(value.flat) def convert_nop(self, layer): pass def convert_general_op(self, caffe_op): op = self._mace_net_def.op.add() op.name = caffe_op.name op.type = caffe_op.type op.input.extend(caffe_op.layer.bottom) op.output.extend(caffe_op.layer.top) data_type_arg = op.arg.add() data_type_arg.name = 'T' data_type_arg.i = self._option.data_type framework_type_arg = op.arg.add() framework_type_arg.name = MaceKeyword.mace_framework_type_str framework_type_arg.i = FrameworkType.CAFFE.value ConverterUtil.add_data_format_arg(op, DataFormat.NCHW) return op def convert_conv2d(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.convolution_param is_depthwise = False if param.HasField(caffe_group_str) and param.group > 1: filter_data = caffe_op.blobs[0] mace_check(param.group == filter_data.shape[0] and filter_data.shape[1] == 1, "Mace do not support group convolution yet") is_depthwise = True caffe_op.blobs[0] = filter_data.reshape(1, filter_data.shape[0], filter_data.shape[2], filter_data.shape[3]) if is_depthwise: op.type = MaceOp.DepthwiseConv2d.name else: op.type = MaceOp.Conv2D.name self.add_stride_pad_kernel_arg(param, op) # dilation is specific for convolution in caffe dilations = [1, 1] if len(param.dilation) > 0: dilation_arg = op.arg.add() dilation_arg.name = MaceKeyword.mace_dilations_str if len(param.dilation) == 1: dilations = [param.dilation[0], param.dilation[0]] elif len(param.dilation) == 2: dilations = [param.dilation[0], param.dilation[1]] dilation_arg.ints.extend(dilations) filter_tensor_name = op.name + '_filter' filter_data = caffe_op.blobs[0] self.add_tensor(filter_tensor_name, filter_data.shape, mace_pb2.DT_FLOAT, filter_data) op.input.extend([filter_tensor_name]) if len(caffe_op.blobs) == 2: bias_tensor_name = op.name + '_bias' bias_data = caffe_op.blobs[1] # caffe of old version has 4-dimension bias, so reshape it # to single dimension self.add_tensor(bias_tensor_name, bias_data.reshape(-1).shape, mace_pb2.DT_FLOAT, bias_data) op.input.extend([bias_tensor_name]) def convert_deconv2d(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.convolution_param if param.HasField(caffe_group_str) and param.group > 1: group_arg = op.arg.add() group_arg.name = MaceKeyword.mace_group_str group_arg.i = param.group op.type = MaceOp.DepthwiseDeconv2d.name else: op.type = MaceOp.Deconv2D.name self.add_stride_pad_kernel_arg(param, op) # dilation is specific for convolution in caffe dilations = [1, 1] if len(param.dilation) > 0: dilation_arg = op.arg.add() dilation_arg.name = MaceKeyword.mace_dilations_str if len(param.dilation) == 1: dilations = [param.dilation[0], param.dilation[0]] elif len(param.dilation) == 2: dilations = [param.dilation[0], param.dilation[1]] mace_check(dilations[0] == 1 and dilations[1] == 1, "Mace only supports dilation == 1 deconvolution.") dilation_arg.ints.extend(dilations) filter_tensor_name = op.name + '_filter' filter_data = caffe_op.blobs[0] self.add_tensor(filter_tensor_name, filter_data.shape, mace_pb2.DT_FLOAT, filter_data) print("convert conv2d, the filter shape is: ", filter_data.shape) op.input.extend([filter_tensor_name]) if len(caffe_op.blobs) == 2: bias_tensor_name = op.name + '_bias' bias_data = caffe_op.blobs[1] # caffe of old version has 4-dimension bias, so reshape it # to single dimension self.add_tensor(bias_tensor_name, bias_data.reshape(-1).shape, mace_pb2.DT_FLOAT, bias_data) op.input.extend([bias_tensor_name]) def convert_elementwise(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.eltwise_param op.type = MaceOp.Eltwise.name type_arg = op.arg.add() type_arg.name = MaceKeyword.mace_element_type_str type_arg.i = self.eltwise_type[param.operation].value if len(param.coeff) > 0: coeff_arg = op.arg.add() coeff_arg.name = 'coeff' coeff_arg.floats.extend(list(param.coeff)) def convert_add(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.AddN.name def convert_activation(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.Activation.name type_arg = op.arg.add() type_arg.name = MaceKeyword.mace_activation_type_str type_arg.s = six.b(self.activation_type[caffe_op.type].name) if caffe_op.type == 'PReLU': alpha_tensor_name = caffe_op.name + '_alpha' alpha_data = caffe_op.blobs[0] self.add_tensor(alpha_tensor_name, alpha_data.reshape(-1).shape, mace_pb2.DT_FLOAT, alpha_data) op.input.extend([alpha_tensor_name]) elif caffe_op.type == 'ReLU': negative_slope = caffe_op.layer.relu_param.negative_slope if negative_slope != 0: param_arg = op.arg.add() param_arg.name = MaceKeyword.mace_activation_coefficient_str param_arg.f = caffe_op.layer.relu_param.negative_slope type_arg.s = six.b(ActivationType.LEAKYRELU.name) elif caffe_op.type == 'ReLU6': limit_arg = op.arg.add() limit_arg.name = MaceKeyword.mace_activation_max_limit_str limit_arg.f = 6.0 elif caffe_op.type == 'Clip': mace_check(caffe_op.layer.clip_param.min == 0, "Mace only supports min == 0 Clip op") limit_arg = op.arg.add() limit_arg.name = MaceKeyword.mace_activation_max_limit_str limit_arg.f = caffe_op.layer.clip_param.max elif caffe_op.type == 'ELU': # TODO(luxuhui): we have not verify ELU for Caffe param_arg = op.arg.add() param_arg.name = MaceKeyword.mace_activation_coefficient_str param_arg.f = caffe_op.layer.elu_param.alpha def convert_folded_batchnorm(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.BatchNorm.name scale_op = None for consumer in self._caffe_net.get_consumers(caffe_op.layer.top[0]): if consumer.type == 'Scale': scale_op = consumer mace_check(scale_op is not None, "batchnorm is not followed by scale") self._skip_ops.append(scale_op) epsilon_value = caffe_op.layer.batch_norm_param.eps mace_check(caffe_op.blobs[2][0] != 0, "batchnorm scalar is zero") mean_value = (1. / caffe_op.blobs[2][0]) * caffe_op.blobs[0] var_value = (1. / caffe_op.blobs[2][0]) * caffe_op.blobs[1] gamma_value = scale_op.blobs[0] beta_value = np.zeros_like(mean_value) if len(scale_op.blobs) == 2: beta_value = scale_op.blobs[1] scale_value = ( (1.0 / np.vectorize(math.sqrt)(var_value + epsilon_value)) * gamma_value).reshape(-1) offset_value = ((-mean_value * scale_value) + beta_value).reshape(-1) input_names = [op.name + '_scale', op.name + '_offset'] self.add_tensor(input_names[0], scale_value.reshape(-1).shape, mace_pb2.DT_FLOAT, scale_value) self.add_tensor(input_names[1], offset_value.reshape(-1).shape, mace_pb2.DT_FLOAT, offset_value) op.input.extend([name for name in input_names]) op.output[:] = scale_op.layer.top[:] def convert_group_norm(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.GroupNorm.name epsilon_arg = op.arg.add() epsilon_arg.name = MaceKeyword.mace_epsilon_str group_num_arg = op.arg.add() group_num_arg.name = MaceKeyword.mace_group_num_str if hasattr(caffe_op, 'layer') and \ hasattr(caffe_op.layer, 'group_norm_param'): param = caffe_op.layer.group_norm_param epsilon_arg.f = param.eps group_num_arg.i = param.group_num else: epsilon_arg.f = 1e-5 group_num_arg.i = 32 def convert_pooling(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.pooling_param op.type = MaceOp.Pooling.name self.add_stride_pad_kernel_arg(param, op) pooling_type_arg = op.arg.add() pooling_type_arg.name = MaceKeyword.mace_pooling_type_str pooling_type_arg.i = self.pooling_type_mode[param.pool].value def convert_softmax(self, caffe_op): self.convert_general_op(caffe_op) def convert_crop(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.crop_param op.type = MaceOp.Crop.name axis = 2 if param.HasField('axis'): axis = param.axis axis = 4 + axis if axis < 0 else axis offset_value = -1 * np.ones(4, dtype=np.int32) offset_len = len(param.offset) if offset_len <= 1: while axis < 4: offset_value[axis] = 0 if offset_len < 1 else param.offset[0] axis += 1 else: offset_value[axis:] = param.offset offset_arg = op.arg.add() offset_arg.name = MaceKeyword.mace_offset_str offset_arg.ints.extend(offset_value) def convert_concat(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.concat_param op.type = MaceOp.Concat.name axis_arg = op.arg.add() axis_arg.name = MaceKeyword.mace_axis_str axis_arg.i = 1 if param.HasField(MaceKeyword.mace_axis_str): axis_arg.i = param.axis elif param.HasField('concat_dim'): axis_arg.i = param.concat_dim def convert_slice(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.Split.name if caffe_op.layer.HasField('slice_param'): param = caffe_op.layer.slice_param mace_check(not param.HasField('axis') or param.axis == 1 or param.axis == -3, "Mace do not support slice with axis %d" % param.axis) mace_check(len(param.slice_point) == 0, "Mace do not support slice with slice_point") axis_arg = op.arg.add() axis_arg.name = MaceKeyword.mace_axis_str axis_arg.i = 1 def convert_interp(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.interp_param mace_check(param.HasField("height") and param.HasField("width"), 'Only support bilinear interp with height and width') op.type = MaceOp.ResizeBilinear.name size_arg = op.arg.add() size_arg.name = MaceKeyword.mace_resize_size_str size_value = np.array([param.height, param.width], dtype=np.int32) size_arg.ints.extend(size_value) # interp op's `align_corners` param is always true in caffe align_corners_arg = op.arg.add() align_corners_arg.name = MaceKeyword.mace_align_corners_str align_corners_arg.i = 1 def convert_fully_connected(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.inner_product_param op.type = MaceOp.FullyConnected.name mace_check((param.axis == 1 or param.axis == -3) and not param.transpose, "Do not support non-default axis and transpose") mace_check(caffe_op.blobs[0].ndim in [2, 4], "Unexpected fc weigth ndim.") if caffe_op.blobs[0].ndim == 4: mace_check(list(caffe_op.blobs[0].shape[:2]) == [1, 1], "Do not support 4D weight with shape [1, 1, *, *]") weight_tensor_name = op.name + '_weight' weight_data = caffe_op.blobs[0].reshape(param.num_output, -1) self.add_tensor(weight_tensor_name, weight_data.shape, mace_pb2.DT_FLOAT, weight_data) op.input.extend([weight_tensor_name]) if len(caffe_op.blobs) == 2: bias_tensor_name = op.name + '_bias' bias_data = caffe_op.blobs[1] self.add_tensor(bias_tensor_name, bias_data.reshape(-1).shape, mace_pb2.DT_FLOAT, bias_data) op.input.extend([bias_tensor_name]) def convert_scale(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.Eltwise.name scale_op_name = op.name op.name = scale_op_name + '_prod' type_arg = op.arg.add() type_arg.name = MaceKeyword.mace_element_type_str type_arg.i = EltwiseType.PROD.value if len(caffe_op.blobs) >= 1: scale_tensor_name = scale_op_name + '_scale' scale_data = caffe_op.blobs[0] self.add_tensor(scale_tensor_name, scale_data.shape, mace_pb2.DT_FLOAT, scale_data) op.input.extend([scale_tensor_name]) if len(caffe_op.blobs) == 2: bias_tensor_name = scale_op_name + '_offset' bias_data = caffe_op.blobs[1] # caffe of old version has 4-dimension bias, so reshape it # to single dimension self.add_tensor(bias_tensor_name, bias_data.reshape(-1).shape, mace_pb2.DT_FLOAT, bias_data) op.input.extend([bias_tensor_name]) biasadd_op = self._mace_net_def.op.add() biasadd_op.name = scale_op_name + '_biasadd' biasadd_op.type = MaceOp.BiasAdd.name biasadd_op.output.extend(op.output) op.output[:] = [op.output[0] + '_prod_output'] biasadd_op.input.extend(op.output) biasadd_op.input.extend([op.input[2]]) biasadd_op.output_shape.extend(op.output_shape) del op.input[2] data_type_arg = biasadd_op.arg.add() data_type_arg.name = 'T' data_type_arg.i = self._option.data_type ConverterUtil.add_data_format_arg(biasadd_op, DataFormat.NCHW) def convert_channel_shuffle(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.shuffle_channel_param op.type = MaceOp.ChannelShuffle.name group_arg = op.arg.add() group_arg.name = MaceKeyword.mace_group_str group_arg.i = 1 if param.HasField('group'): group_arg.i = param.group def convert_permute(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.permute_param op.type = MaceOp.Transpose.name dims_arg = op.arg.add() dims_arg.name = MaceKeyword.mace_dims_str dims_arg.ints.extend(list(param.order)) def convert_flatten(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.flatten_param op.type = MaceOp.Reshape.name axis_arg = op.arg.add() axis_arg.name = MaceKeyword.mace_axis_str axis_arg.i = 1 if param.HasField('axis'): axis_arg.i = param.axis axis_arg.i = 4 + axis_arg.i if axis_arg.i < 0 else axis_arg.i end_axis_arg = op.arg.add() end_axis_arg.name = MaceKeyword.mace_end_axis_str end_axis_arg.i = -1 if param.HasField('end_axis'): end_axis_arg.i = param.end_axis def convert_prior_box(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.prior_box_param op.type = MaceOp.PriorBox.name min_size_arg = op.arg.add() min_size_arg.name = MaceKeyword.mace_min_size_str min_size_arg.floats.extend(list(param.min_size)) max_size_arg = op.arg.add() max_size_arg.name = MaceKeyword.mace_max_size_str max_size_arg.floats.extend(list(param.max_size)) flip_arg = op.arg.add() flip_arg.name = MaceKeyword.mace_flip_str flip_arg.i = 1 if param.HasField('flip'): flip_arg.i = int(param.flip) aspect_ratio = [1.0] for i in param.aspect_ratio: already_exist = False for ar in aspect_ratio: if abs(i - ar) < 1e-6: already_exist = True break if not already_exist: aspect_ratio.append(i) if flip_arg.i: aspect_ratio.append(1.0 / i) aspect_ratio_arg = op.arg.add() aspect_ratio_arg.name = MaceKeyword.mace_aspect_ratio_str aspect_ratio_arg.floats.extend(list(aspect_ratio)) clip_arg = op.arg.add() clip_arg.name = MaceKeyword.mace_clip_str clip_arg.i = 0 if param.HasField('clip'): clip_arg.i = int(param.clip) variance_arg = op.arg.add() variance_arg.name = MaceKeyword.mace_variance_str variance_arg.floats.extend(list(param.variance)) offset_arg = op.arg.add() offset_arg.name = MaceKeyword.mace_offset_str offset_arg.f = 0.5 if param.HasField('offset'): offset_arg.f = param.offset step_h_arg = op.arg.add() step_h_arg.name = MaceKeyword.mace_step_h_str step_h_arg.f = 0 if param.HasField('step_h'): mace_check(not param.HasField('step'), "Either step or step_h/step_w should be specified; not both.") # noqa step_h_arg.f = param.step_h mace_check(step_h_arg.f > 0, "step_h should be larger than 0.") step_w_arg = op.arg.add() step_w_arg.name = MaceKeyword.mace_step_w_str step_w_arg.f = 0 if param.HasField('step_w'): mace_check(not param.HasField('step'), "Either step or step_h/step_w should be specified; not both.") # noqa step_w_arg.f = param.step_w mace_check(step_w_arg.f > 0, "step_w should be larger than 0.") if param.HasField('step'): mace_check( not param.HasField('step_h') and not param.HasField('step_w'), "Either step or step_h/step_w should be specified; not both.") mace_check(param.step > 0, "step should be larger than 0.") step_h_arg.f = param.step step_w_arg.f = param.step def convert_reshape(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.reshape_param op.type = MaceOp.Reshape.name dim_arg = op.arg.add() dim_arg.name = MaceKeyword.mace_dim_str dim_arg.ints.extend(list(param.shape.dim)) axis_arg = op.arg.add() axis_arg.name = 'reshape_' + MaceKeyword.mace_axis_str axis_arg.i = 0 if param.HasField('axis'): axis_arg.i = param.axis num_axes_arg = op.arg.add() num_axes_arg.name = MaceKeyword.mace_num_axes_str num_axes_arg.i = -1 if param.HasField('num_axes'): num_axes_arg.i = param.num_axes def convert_lpnorm(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.l2normalization_param op.type = MaceOp.LpNorm.name axis_arg = op.arg.add() axis_arg.name = MaceKeyword.mace_axis_str axis_arg.i = -1 if param.HasField('axis'): axis_arg.i = param.axis p_arg = op.arg.add() p_arg.name = MaceKeyword.mace_p_str if caffe_op.type == 'L1Normalization': p_arg.i = 1 elif caffe_op.type == 'L2Normalization': p_arg.i = 2 else: mace_check(False, "Can not support %s" % caffe_op.type) def convert_MVN(self, caffe_op): op = self.convert_general_op(caffe_op) param = caffe_op.layer.mvn_param op.type = MaceOp.MVNorm.name if param.HasField('normalize_variance'): nv_arg = op.arg.add() nv_arg.name = MaceKeyword.mace_nor_var_str nv_arg.i = param.normalize_variance if param.HasField('across_channels'): across_ch_arg = op.arg.add() across_ch_arg.name = MaceKeyword.mace_across_ch_str across_ch_arg.i = param.across_channels if param.HasField('eps'): eps_arg = op.arg.add() eps_arg.name = MaceKeyword.mace_epsilon_str eps_arg.f = param.eps def convert_bias(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.BiasAdd.name param = caffe_op.layer.bias_param mace_check(not param.axis or param.axis == 0 or param.axis == 1, "BiasAdd only support axis with 0 or 1.") axis_arg = op.arg.add() axis_arg.name = MaceKeyword.mace_axis_str axis_arg.i = 1 if param.axis is not None: mace_check(param.axis == 0 or param.axis == 1, "BiasAdd only support axis with 0 or 1.") axis_arg.i = param.axis if len(caffe_op.blobs) >= 1: bias_tensor_name = op.name + '_bias' bias_data = caffe_op.blobs[0] self.add_tensor(bias_tensor_name, bias_data.shape, mace_pb2.DT_FLOAT, bias_data) op.input.extend([bias_tensor_name]) def convert_resize_nearest(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.ResizeNearestNeighbor.name align_corners_arg = op.arg.add() align_corners_arg.name = MaceKeyword.mace_align_corners_str align_corners_arg.i = 0 height_scale_arg = op.arg.add() height_scale_arg.name = MaceKeyword.mace_height_scale_str width_scale_arg = op.arg.add() width_scale_arg.name = MaceKeyword.mace_width_scale_str if hasattr(caffe_op, 'layer') and \ hasattr(caffe_op.layer, 'resize_nearest_param'): param = caffe_op.layer.resize_nearest_param height_scale_arg.f = param.height_scale width_scale_arg.f = param.width_scale else: height_scale_arg.f = 2.0 width_scale_arg.f = 2.0 def convert_argmax(self, caffe_op): op = self.convert_general_op(caffe_op) op.type = MaceOp.ArgMax.name out_max_val = False if hasattr(caffe_op, 'layer') and \ hasattr(caffe_op.layer, 'argmax_param'): param = caffe_op.layer.argmax_param if hasattr(param, 'out_max_val'): axis_arg = op.arg.add() axis_arg.name = MaceKeyword.mace_out_val_str axis_arg.i = param.out_max_val out_max_val = param.out_max_val if hasattr(param, MaceKeyword.mace_top_k_str): axis_arg = op.arg.add() axis_arg.name = MaceKeyword.mace_top_k_str axis_arg.i = param.top_k if hasattr(param, MaceKeyword.mace_axis_str): axis_arg = op.arg.add() axis_arg.name = MaceKeyword.mace_axis_str axis_arg.i = param.axis if out_max_val: op.output_type.extend([mace_pb2.DT_FLOAT]) else: op.output_type.extend([mace_pb2.DT_INT32])