From 3084cde446fa0d2f9962709ee4be62857e01a287 Mon Sep 17 00:00:00 2001 From: winsty Date: Wed, 7 Oct 2015 16:04:33 -0700 Subject: [PATCH 1/5] caffe converter --- tools/caffe_converter/convert_model.py | 96 +++++++++++++++++++ tools/caffe_converter/convert_symbol.py | 117 ++++++++++++++++++++++++ 2 files changed, 213 insertions(+) create mode 100644 tools/caffe_converter/convert_model.py create mode 100644 tools/caffe_converter/convert_symbol.py diff --git a/tools/caffe_converter/convert_model.py b/tools/caffe_converter/convert_model.py new file mode 100644 index 000000000000..ab5ca632f870 --- /dev/null +++ b/tools/caffe_converter/convert_model.py @@ -0,0 +1,96 @@ +import mxnet as mx +import caffe +import argparse + +data = mx.symbol.Variable(name='data') +conv1_1 = mx.symbol.Convolution(name='conv1_1', data=data , num_filter=64, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu1_1 = mx.symbol.Activation(name='relu1_1', data=conv1_1 , act_type='relu') +conv1_2 = mx.symbol.Convolution(name='conv1_2', data=relu1_1 , num_filter=64, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu1_2 = mx.symbol.Activation(name='relu1_2', data=conv1_2 , act_type='relu') +pool1 = mx.symbol.Pooling(name='pool1', data=relu1_2 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') +conv2_1 = mx.symbol.Convolution(name='conv2_1', data=pool1 , num_filter=128, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu2_1 = mx.symbol.Activation(name='relu2_1', data=conv2_1 , act_type='relu') +conv2_2 = mx.symbol.Convolution(name='conv2_2', data=relu2_1 , num_filter=128, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu2_2 = mx.symbol.Activation(name='relu2_2', data=conv2_2 , act_type='relu') +pool2 = mx.symbol.Pooling(name='pool2', data=relu2_2 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') +conv3_1 = mx.symbol.Convolution(name='conv3_1', data=pool2 , num_filter=256, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu3_1 = mx.symbol.Activation(name='relu3_1', data=conv3_1 , act_type='relu') +conv3_2 = mx.symbol.Convolution(name='conv3_2', data=relu3_1 , num_filter=256, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu3_2 = mx.symbol.Activation(name='relu3_2', data=conv3_2 , act_type='relu') +conv3_3 = mx.symbol.Convolution(name='conv3_3', data=relu3_2 , num_filter=256, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu3_3 = mx.symbol.Activation(name='relu3_3', data=conv3_3 , act_type='relu') +pool3 = mx.symbol.Pooling(name='pool3', data=relu3_3 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') +conv4_1 = mx.symbol.Convolution(name='conv4_1', data=pool3 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu4_1 = mx.symbol.Activation(name='relu4_1', data=conv4_1 , act_type='relu') +conv4_2 = mx.symbol.Convolution(name='conv4_2', data=relu4_1 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu4_2 = mx.symbol.Activation(name='relu4_2', data=conv4_2 , act_type='relu') +conv4_3 = mx.symbol.Convolution(name='conv4_3', data=relu4_2 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu4_3 = mx.symbol.Activation(name='relu4_3', data=conv4_3 , act_type='relu') +pool4 = mx.symbol.Pooling(name='pool4', data=relu4_3 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') +conv5_1 = mx.symbol.Convolution(name='conv5_1', data=pool4 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu5_1 = mx.symbol.Activation(name='relu5_1', data=conv5_1 , act_type='relu') +conv5_2 = mx.symbol.Convolution(name='conv5_2', data=relu5_1 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu5_2 = mx.symbol.Activation(name='relu5_2', data=conv5_2 , act_type='relu') +conv5_3 = mx.symbol.Convolution(name='conv5_3', data=relu5_2 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) +relu5_3 = mx.symbol.Activation(name='relu5_3', data=conv5_3 , act_type='relu') +pool5 = mx.symbol.Pooling(name='pool5', data=relu5_3 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') +flatten_0=mx.symbol.Flatten(name='flatten_0', data=pool5) +fc6 = mx.symbol.FullyConnected(name='fc6', data=flatten_0 , num_hidden=4096, no_bias=False) +relu6 = mx.symbol.Activation(name='relu6', data=fc6 , act_type='relu') +drop6 = mx.symbol.Dropout(name='drop6', data=relu6 , p=0.500000) +fc7 = mx.symbol.FullyConnected(name='fc7', data=drop6 , num_hidden=4096, no_bias=False) +relu7 = mx.symbol.Activation(name='relu7', data=fc7 , act_type='relu') +drop7 = mx.symbol.Dropout(name='drop7', data=relu7 , p=0.500000) +fc8 = mx.symbol.FullyConnected(name='fc8', data=drop7 , num_hidden=1000, no_bias=False) +prob = mx.symbol.Softmax(name='prob', data=fc8 ) + +parser = argparse.ArgumentParser(description='Caffe prototxt to mxnet model parameter converter.\ + Note that only basic functions are implemented. You are welcomed to contribute to this file.') +parser.add_argument('caffe_prototxt', help='The prototxt file in Caffe format') +parser.add_argument('caffe_model', help='The binary model parameter file in Caffe format') +parser.add_argument('save_model_name', help='The name of the output model prefix') +args = parser.parse_args() + +caffe.set_mode_cpu() +net_caffe = caffe.Net(args.caffe_prototxt, args.caffe_model, caffe.TEST) +arg_shapes, output_shapes, aux_shapes = prob.infer_shape(data=(1,3,224,224)) +arg_names = prob.list_arguments() +arg_shape_dic = dict(zip(arg_names, arg_shapes)) +arg_params = {} + +first_conv = True +layer_names = net_caffe._layer_names +for layer_idx, layer in enumerate(net_caffe.layers): + layer_name = layer_names[layer_idx].replace('/', '_') + if layer.type == 'Convolution' or layer.type == 'InnerProduct': + assert(len(layer.blobs) == 2) + wmat = layer.blobs[0].data + bias = layer.blobs[1].data + if first_conv: + print 'Swapping BGR of caffe into RGB in cxxnet' + wmat[:, [0, 2], :, :] = wmat[:, [2, 0], :, :] + + assert(wmat.flags['C_CONTIGUOUS'] is True) + assert(bias.flags['C_CONTIGUOUS'] is True) + print 'converting layer {0}, wmat shape = {1}, bias shape = {2}'.format(layer_name, wmat.shape, bias.shape) + wmat = wmat.reshape((wmat.shape[0], -1)) + bias = bias.reshape((bias.shape[0], 1)) + weight_name = layer_name + "_weight" + bias_name = layer_name + "_bias" + + wmat = wmat.reshape(arg_shape_dic[weight_name]) + arg_params[weight_name] = mx.nd.zeros(wmat.shape) + arg_params[weight_name][:] = wmat + + bias = bias.reshape(arg_shape_dic[bias_name]) + arg_params[bias_name] = mx.nd.zeros(bias.shape) + arg_params[bias_name][:] = bias + + if first_conv and layer.type == 'Convolution': + first_conv = False + +model = mx.model.FeedForward(ctx=mx.cpu(), symbol=prob, + arg_params=arg_params, aux_params={}, num_round=1, + learning_rate=0.05, momentum=0.9, wd=0.0001) + +model.save(args.save_model_name) diff --git a/tools/caffe_converter/convert_symbol.py b/tools/caffe_converter/convert_symbol.py new file mode 100644 index 000000000000..356790d7867f --- /dev/null +++ b/tools/caffe_converter/convert_symbol.py @@ -0,0 +1,117 @@ +import caffe +from caffe.proto import caffe_pb2 +from google.protobuf import text_format +import argparse + +def readProtoSolverFile(filepath): + solver_config = caffe.proto.caffe_pb2.NetParameter() + return readProtoFile(filepath, solver_config) + +def readProtoFile(filepath, parser_object): + + file = open(filepath, "r") + + if not file: + raise self.ProcessException("ERROR (" + filepath + ")!") + + text_format.Merge(str(file.read()), parser_object) + file.close() + return parser_object + +def main(): + parser = argparse.ArgumentParser(description='Caffe prototxt to mxnet config converter.\ + Note that only basic functions are implemented. You are welcomed to contribute to this file.') + parser.add_argument('caffe_proto_file', help='The prototxt file in Caffe format') + args = parser.parse_args() + proto = readProtoSolverFile(args.caffe_proto_file) + connection = dict() + symbols = dict() + top = dict() + mapping = {'data' : 'data'} + need_flatten = {'data' : False} + flatten_count = 0 + layer = proto.layer + for i in range(len(layer)): + type_string = '' + param_string = '' + name = layer[i].name.replace('/', '_') + if layer[i].type == 'Convolution': + type_string = 'mx.symbol.Convolution' + param = layer[i].convolution_param + pad = 0 if len(param.pad) == 0 else param.pad[0] + stride = 1 if len(param.stride) == 0 else param.stride[0] + param_string = "num_filter=%d, pad=(%d,%d), kernel=(%d,%d), stride=(%d,%d), no_bias=%s" %\ + (param.num_output, pad, pad, param.kernel_size[0],\ + param.kernel_size[0], stride, stride, not param.bias_term) + need_flatten[name] = True + if layer[i].type == 'Pooling': + type_string = 'mx.symbol.Pooling' + param = layer[i].pooling_param + param_string = "pad=(%d,%d), kernel=(%d,%d), stride=(%d,%d)" %\ + (param.pad, param.pad, param.kernel_size,\ + param.kernel_size, param.stride, param.stride) + if param.pool == 0: + param_string = param_string + ", pool_type='max'" + elif param.pool == 1: + param_string = param_string + ", pool_type='avg'" + else: + print "Unknown Pooling Method!" + return + need_flatten[name] = True + if layer[i].type == 'ReLU': + type_string = 'mx.symbol.Activation' + param_string = "act_type='relu'" + need_flatten[name] = need_flatten[mapping[proto.layer[i].bottom[0]]] + if layer[i].type == 'LRN': + type_string = 'mx.symbol.LRN' + param = layer[i].lrn_param + param_string = "alpha=%f, beta=%f, knorm=%f, nsize=%d" %\ + (param.alpha, param.beta, param.k, param.local_size) + need_flatten[name] = True + if layer[i].type == 'InnerProduct': + type_string = 'mx.symbol.FullyConnected' + param = layer[i].inner_product_param + param_string = "num_hidden=%d, no_bias=%s" % (param.num_output, not param.bias_term) + need_flatten[name] = False + if layer[i].type == 'Dropout': + type_string = 'mx.symbol.Dropout' + param = layer[i].dropout_param + param_string = "p=%f" % param.dropout_ratio + need_flatten[name] = need_flatten[mapping[proto.layer[i].bottom[0]]] + if layer[i].type == 'Softmax': + type_string = 'mx.symbol.Softmax' + if layer[i].type == 'Flatten': + type_string = 'mx.symbol.Flatten' + need_flatten[name] = False + if layer[i].type == 'Split': + type_string = 'split' + if layer[i].type == 'Concat': + type_string = 'mx.symbol.Concat' + need_flatten[name] = True + if type_string == '': + print 'Unknown Layer %s!' % layer[i].type + return + + if type_string != 'split': + bottom = layer[i].bottom + if param_string != "": + param_string = ", " + param_string + if len(bottom) == 1: + if need_flatten[mapping[bottom[0]]] and type_string == 'mx.symbol.FullyConnected': + flatten_name = "flatten_%d" % flatten_count + print "%s=mx.symbol.Flatten(name='%s', data=%s)" %\ + (flatten_name, flatten_name, mapping[bottom[0]]) + flatten_count += 1 + need_flatten[flatten_name] = False + bottom[0] = flatten_name + mapping[bottom[0]] = bottom[0] + print "%s = %s(name='%s', data=%s %s)" %\ + (name, type_string, name, mapping[bottom[0]], param_string) + else: + print "%s = %s(name='%s', *[%s] %s)" %\ + (name, type_string, name, ','.join([mapping[x] for x in bottom]), param_string) + for j in range(len(layer[i].top)): + mapping[layer[i].top[j]] = name + +if __name__ == '__main__': + main() From 6acf718e752d1d60f2603eaff503a21e84289ded Mon Sep 17 00:00:00 2001 From: winsty Date: Wed, 7 Oct 2015 17:38:51 -0700 Subject: [PATCH 2/5] clean up converter --- tools/caffe_converter/convert_model.py | 150 +++++++----------- tools/caffe_converter/convert_symbol.py | 200 ++++++++++++------------ 2 files changed, 158 insertions(+), 192 deletions(-) diff --git a/tools/caffe_converter/convert_model.py b/tools/caffe_converter/convert_model.py index ab5ca632f870..1b36749e81d1 100644 --- a/tools/caffe_converter/convert_model.py +++ b/tools/caffe_converter/convert_model.py @@ -1,96 +1,60 @@ import mxnet as mx import caffe import argparse - -data = mx.symbol.Variable(name='data') -conv1_1 = mx.symbol.Convolution(name='conv1_1', data=data , num_filter=64, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu1_1 = mx.symbol.Activation(name='relu1_1', data=conv1_1 , act_type='relu') -conv1_2 = mx.symbol.Convolution(name='conv1_2', data=relu1_1 , num_filter=64, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu1_2 = mx.symbol.Activation(name='relu1_2', data=conv1_2 , act_type='relu') -pool1 = mx.symbol.Pooling(name='pool1', data=relu1_2 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') -conv2_1 = mx.symbol.Convolution(name='conv2_1', data=pool1 , num_filter=128, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu2_1 = mx.symbol.Activation(name='relu2_1', data=conv2_1 , act_type='relu') -conv2_2 = mx.symbol.Convolution(name='conv2_2', data=relu2_1 , num_filter=128, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu2_2 = mx.symbol.Activation(name='relu2_2', data=conv2_2 , act_type='relu') -pool2 = mx.symbol.Pooling(name='pool2', data=relu2_2 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') -conv3_1 = mx.symbol.Convolution(name='conv3_1', data=pool2 , num_filter=256, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu3_1 = mx.symbol.Activation(name='relu3_1', data=conv3_1 , act_type='relu') -conv3_2 = mx.symbol.Convolution(name='conv3_2', data=relu3_1 , num_filter=256, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu3_2 = mx.symbol.Activation(name='relu3_2', data=conv3_2 , act_type='relu') -conv3_3 = mx.symbol.Convolution(name='conv3_3', data=relu3_2 , num_filter=256, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu3_3 = mx.symbol.Activation(name='relu3_3', data=conv3_3 , act_type='relu') -pool3 = mx.symbol.Pooling(name='pool3', data=relu3_3 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') -conv4_1 = mx.symbol.Convolution(name='conv4_1', data=pool3 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu4_1 = mx.symbol.Activation(name='relu4_1', data=conv4_1 , act_type='relu') -conv4_2 = mx.symbol.Convolution(name='conv4_2', data=relu4_1 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu4_2 = mx.symbol.Activation(name='relu4_2', data=conv4_2 , act_type='relu') -conv4_3 = mx.symbol.Convolution(name='conv4_3', data=relu4_2 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu4_3 = mx.symbol.Activation(name='relu4_3', data=conv4_3 , act_type='relu') -pool4 = mx.symbol.Pooling(name='pool4', data=relu4_3 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') -conv5_1 = mx.symbol.Convolution(name='conv5_1', data=pool4 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu5_1 = mx.symbol.Activation(name='relu5_1', data=conv5_1 , act_type='relu') -conv5_2 = mx.symbol.Convolution(name='conv5_2', data=relu5_1 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu5_2 = mx.symbol.Activation(name='relu5_2', data=conv5_2 , act_type='relu') -conv5_3 = mx.symbol.Convolution(name='conv5_3', data=relu5_2 , num_filter=512, pad=(1,1), kernel=(3,3), stride=(1,1), no_bias=False) -relu5_3 = mx.symbol.Activation(name='relu5_3', data=conv5_3 , act_type='relu') -pool5 = mx.symbol.Pooling(name='pool5', data=relu5_3 , pad=(0,0), kernel=(2,2), stride=(2,2), pool_type='max') -flatten_0=mx.symbol.Flatten(name='flatten_0', data=pool5) -fc6 = mx.symbol.FullyConnected(name='fc6', data=flatten_0 , num_hidden=4096, no_bias=False) -relu6 = mx.symbol.Activation(name='relu6', data=fc6 , act_type='relu') -drop6 = mx.symbol.Dropout(name='drop6', data=relu6 , p=0.500000) -fc7 = mx.symbol.FullyConnected(name='fc7', data=drop6 , num_hidden=4096, no_bias=False) -relu7 = mx.symbol.Activation(name='relu7', data=fc7 , act_type='relu') -drop7 = mx.symbol.Dropout(name='drop7', data=relu7 , p=0.500000) -fc8 = mx.symbol.FullyConnected(name='fc8', data=drop7 , num_hidden=1000, no_bias=False) -prob = mx.symbol.Softmax(name='prob', data=fc8 ) - -parser = argparse.ArgumentParser(description='Caffe prototxt to mxnet model parameter converter.\ - Note that only basic functions are implemented. You are welcomed to contribute to this file.') -parser.add_argument('caffe_prototxt', help='The prototxt file in Caffe format') -parser.add_argument('caffe_model', help='The binary model parameter file in Caffe format') -parser.add_argument('save_model_name', help='The name of the output model prefix') -args = parser.parse_args() - -caffe.set_mode_cpu() -net_caffe = caffe.Net(args.caffe_prototxt, args.caffe_model, caffe.TEST) -arg_shapes, output_shapes, aux_shapes = prob.infer_shape(data=(1,3,224,224)) -arg_names = prob.list_arguments() -arg_shape_dic = dict(zip(arg_names, arg_shapes)) -arg_params = {} - -first_conv = True -layer_names = net_caffe._layer_names -for layer_idx, layer in enumerate(net_caffe.layers): - layer_name = layer_names[layer_idx].replace('/', '_') - if layer.type == 'Convolution' or layer.type == 'InnerProduct': - assert(len(layer.blobs) == 2) - wmat = layer.blobs[0].data - bias = layer.blobs[1].data - if first_conv: - print 'Swapping BGR of caffe into RGB in cxxnet' - wmat[:, [0, 2], :, :] = wmat[:, [2, 0], :, :] - - assert(wmat.flags['C_CONTIGUOUS'] is True) - assert(bias.flags['C_CONTIGUOUS'] is True) - print 'converting layer {0}, wmat shape = {1}, bias shape = {2}'.format(layer_name, wmat.shape, bias.shape) - wmat = wmat.reshape((wmat.shape[0], -1)) - bias = bias.reshape((bias.shape[0], 1)) - weight_name = layer_name + "_weight" - bias_name = layer_name + "_bias" - - wmat = wmat.reshape(arg_shape_dic[weight_name]) - arg_params[weight_name] = mx.nd.zeros(wmat.shape) - arg_params[weight_name][:] = wmat - - bias = bias.reshape(arg_shape_dic[bias_name]) - arg_params[bias_name] = mx.nd.zeros(bias.shape) - arg_params[bias_name][:] = bias - - if first_conv and layer.type == 'Convolution': - first_conv = False - -model = mx.model.FeedForward(ctx=mx.cpu(), symbol=prob, - arg_params=arg_params, aux_params={}, num_round=1, - learning_rate=0.05, momentum=0.9, wd=0.0001) - -model.save(args.save_model_name) +from convert_symbol import proto2symbol + +def main(): + parser = argparse.ArgumentParser(description='Caffe prototxt to mxnet model parameter converter.\ + Note that only basic functions are implemented. You are welcomed to contribute to this file.') + parser.add_argument('caffe_prototxt', help='The prototxt file in Caffe format') + parser.add_argument('caffe_model', help='The binary model parameter file in Caffe format') + parser.add_argument('save_model_name', help='The name of the output model prefix') + args = parser.parse_args() + + prob = proto2symbol(args.caffe_prototxt) + caffe.set_mode_cpu() + net_caffe = caffe.Net(args.caffe_prototxt, args.caffe_model, caffe.TEST) + arg_shapes, output_shapes, aux_shapes = prob.infer_shape(data=(1,3,224,224)) + arg_names = prob.list_arguments() + arg_shape_dic = dict(zip(arg_names, arg_shapes)) + arg_params = {} + + first_conv = True + layer_names = net_caffe._layer_names + for layer_idx, layer in enumerate(net_caffe.layers): + layer_name = layer_names[layer_idx].replace('/', '_') + if layer.type == 'Convolution' or layer.type == 'InnerProduct': + assert(len(layer.blobs) == 2) + wmat = layer.blobs[0].data + bias = layer.blobs[1].data + if first_conv: + print 'Swapping BGR of caffe into RGB in cxxnet' + wmat[:, [0, 2], :, :] = wmat[:, [2, 0], :, :] + + assert(wmat.flags['C_CONTIGUOUS'] is True) + assert(bias.flags['C_CONTIGUOUS'] is True) + print 'converting layer {0}, wmat shape = {1}, bias shape = {2}'.format(layer_name, wmat.shape, bias.shape) + wmat = wmat.reshape((wmat.shape[0], -1)) + bias = bias.reshape((bias.shape[0], 1)) + weight_name = layer_name + "_weight" + bias_name = layer_name + "_bias" + + wmat = wmat.reshape(arg_shape_dic[weight_name]) + arg_params[weight_name] = mx.nd.zeros(wmat.shape) + arg_params[weight_name][:] = wmat + + bias = bias.reshape(arg_shape_dic[bias_name]) + arg_params[bias_name] = mx.nd.zeros(bias.shape) + arg_params[bias_name][:] = bias + + if first_conv and layer.type == 'Convolution': + first_conv = False + + model = mx.model.FeedForward(ctx=mx.cpu(), symbol=prob, + arg_params=arg_params, aux_params={}, num_round=1, + learning_rate=0.05, momentum=0.9, wd=0.0001) + + model.save(args.save_model_name) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/tools/caffe_converter/convert_symbol.py b/tools/caffe_converter/convert_symbol.py index 356790d7867f..ca2e47a29b37 100644 --- a/tools/caffe_converter/convert_symbol.py +++ b/tools/caffe_converter/convert_symbol.py @@ -8,110 +8,112 @@ def readProtoSolverFile(filepath): return readProtoFile(filepath, solver_config) def readProtoFile(filepath, parser_object): - file = open(filepath, "r") - if not file: raise self.ProcessException("ERROR (" + filepath + ")!") - text_format.Merge(str(file.read()), parser_object) file.close() return parser_object -def main(): - parser = argparse.ArgumentParser(description='Caffe prototxt to mxnet config converter.\ - Note that only basic functions are implemented. You are welcomed to contribute to this file.') - parser.add_argument('caffe_proto_file', help='The prototxt file in Caffe format') - args = parser.parse_args() - proto = readProtoSolverFile(args.caffe_proto_file) - connection = dict() - symbols = dict() - top = dict() - mapping = {'data' : 'data'} - need_flatten = {'data' : False} - flatten_count = 0 - layer = proto.layer - for i in range(len(layer)): - type_string = '' - param_string = '' - name = layer[i].name.replace('/', '_') - if layer[i].type == 'Convolution': - type_string = 'mx.symbol.Convolution' - param = layer[i].convolution_param - pad = 0 if len(param.pad) == 0 else param.pad[0] - stride = 1 if len(param.stride) == 0 else param.stride[0] - param_string = "num_filter=%d, pad=(%d,%d), kernel=(%d,%d), stride=(%d,%d), no_bias=%s" %\ - (param.num_output, pad, pad, param.kernel_size[0],\ - param.kernel_size[0], stride, stride, not param.bias_term) - need_flatten[name] = True - if layer[i].type == 'Pooling': - type_string = 'mx.symbol.Pooling' - param = layer[i].pooling_param - param_string = "pad=(%d,%d), kernel=(%d,%d), stride=(%d,%d)" %\ - (param.pad, param.pad, param.kernel_size,\ - param.kernel_size, param.stride, param.stride) - if param.pool == 0: - param_string = param_string + ", pool_type='max'" - elif param.pool == 1: - param_string = param_string + ", pool_type='avg'" - else: - print "Unknown Pooling Method!" - return - need_flatten[name] = True - if layer[i].type == 'ReLU': - type_string = 'mx.symbol.Activation' - param_string = "act_type='relu'" - need_flatten[name] = need_flatten[mapping[proto.layer[i].bottom[0]]] - if layer[i].type == 'LRN': - type_string = 'mx.symbol.LRN' - param = layer[i].lrn_param - param_string = "alpha=%f, beta=%f, knorm=%f, nsize=%d" %\ - (param.alpha, param.beta, param.k, param.local_size) - need_flatten[name] = True - if layer[i].type == 'InnerProduct': - type_string = 'mx.symbol.FullyConnected' - param = layer[i].inner_product_param - param_string = "num_hidden=%d, no_bias=%s" % (param.num_output, not param.bias_term) - need_flatten[name] = False - if layer[i].type == 'Dropout': - type_string = 'mx.symbol.Dropout' - param = layer[i].dropout_param - param_string = "p=%f" % param.dropout_ratio - need_flatten[name] = need_flatten[mapping[proto.layer[i].bottom[0]]] - if layer[i].type == 'Softmax': - type_string = 'mx.symbol.Softmax' - if layer[i].type == 'Flatten': - type_string = 'mx.symbol.Flatten' - need_flatten[name] = False - if layer[i].type == 'Split': - type_string = 'split' - if layer[i].type == 'Concat': - type_string = 'mx.symbol.Concat' - need_flatten[name] = True - if type_string == '': - print 'Unknown Layer %s!' % layer[i].type - return - - if type_string != 'split': - bottom = layer[i].bottom - if param_string != "": - param_string = ", " + param_string - if len(bottom) == 1: - if need_flatten[mapping[bottom[0]]] and type_string == 'mx.symbol.FullyConnected': - flatten_name = "flatten_%d" % flatten_count - print "%s=mx.symbol.Flatten(name='%s', data=%s)" %\ - (flatten_name, flatten_name, mapping[bottom[0]]) - flatten_count += 1 - need_flatten[flatten_name] = False - bottom[0] = flatten_name - mapping[bottom[0]] = bottom[0] - print "%s = %s(name='%s', data=%s %s)" %\ - (name, type_string, name, mapping[bottom[0]], param_string) - else: - print "%s = %s(name='%s', *[%s] %s)" %\ - (name, type_string, name, ','.join([mapping[x] for x in bottom]), param_string) - for j in range(len(layer[i].top)): - mapping[layer[i].top[j]] = name +def proto2script(proto_file): + # parser = argparse.ArgumentParser(description='Caffe prototxt to mxnet config converter.\ + # Note that only basic functions are implemented. You are welcomed to contribute to this file.') + # parser.add_argument('caffe_proto_file', help='The prototxt file in Caffe format') + # args = parser.parse_args() + proto = readProtoSolverFile(proto_file) + connection = dict() + symbols = dict() + top = dict() + mapping = {'data' : 'data'} + need_flatten = {'data' : False} + flatten_count = 0 + symbol_string = "" + layer = proto.layer + for i in range(len(layer)): + type_string = '' + param_string = '' + name = layer[i].name.replace('/', '_') + if layer[i].type == 'Convolution': + type_string = 'mx.symbol.Convolution' + param = layer[i].convolution_param + pad = 0 if len(param.pad) == 0 else param.pad[0] + stride = 1 if len(param.stride) == 0 else param.stride[0] + param_string = "num_filter=%d, pad=(%d,%d), kernel=(%d,%d), stride=(%d,%d), no_bias=%s" %\ + (param.num_output, pad, pad, param.kernel_size[0],\ + param.kernel_size[0], stride, stride, not param.bias_term) + need_flatten[name] = True + if layer[i].type == 'Pooling': + type_string = 'mx.symbol.Pooling' + param = layer[i].pooling_param + param_string = "pad=(%d,%d), kernel=(%d,%d), stride=(%d,%d)" %\ + (param.pad, param.pad, param.kernel_size,\ + param.kernel_size, param.stride, param.stride) + if param.pool == 0: + param_string = param_string + ", pool_type='max'" + elif param.pool == 1: + param_string = param_string + ", pool_type='avg'" + else: + raise Exception("Unknown Pooling Method!") + need_flatten[name] = True + if layer[i].type == 'ReLU': + type_string = 'mx.symbol.Activation' + param_string = "act_type='relu'" + need_flatten[name] = need_flatten[mapping[proto.layer[i].bottom[0]]] + if layer[i].type == 'LRN': + type_string = 'mx.symbol.LRN' + param = layer[i].lrn_param + param_string = "alpha=%f, beta=%f, knorm=%f, nsize=%d" %\ + (param.alpha, param.beta, param.k, param.local_size) + need_flatten[name] = True + if layer[i].type == 'InnerProduct': + type_string = 'mx.symbol.FullyConnected' + param = layer[i].inner_product_param + param_string = "num_hidden=%d, no_bias=%s" % (param.num_output, not param.bias_term) + need_flatten[name] = False + if layer[i].type == 'Dropout': + type_string = 'mx.symbol.Dropout' + param = layer[i].dropout_param + param_string = "p=%f" % param.dropout_ratio + need_flatten[name] = need_flatten[mapping[proto.layer[i].bottom[0]]] + if layer[i].type == 'Softmax': + type_string = 'mx.symbol.Softmax' + if layer[i].type == 'Flatten': + type_string = 'mx.symbol.Flatten' + need_flatten[name] = False + if layer[i].type == 'Split': + type_string = 'split' + if layer[i].type == 'Concat': + type_string = 'mx.symbol.Concat' + need_flatten[name] = True + if type_string == '': + raise Exception('Unknown Layer %s!' % layer[i].type) + + if type_string != 'split': + bottom = layer[i].bottom + if param_string != "": + param_string = ", " + param_string + if len(bottom) == 1: + if need_flatten[mapping[bottom[0]]] and type_string == 'mx.symbol.FullyConnected': + flatten_name = "flatten_%d" % flatten_count + symbol_string += "%s=mx.symbol.Flatten(name='%s', data=%s)\n" %\ + (flatten_name, flatten_name, mapping[bottom[0]]) + flatten_count += 1 + need_flatten[flatten_name] = False + bottom[0] = flatten_name + mapping[bottom[0]] = bottom[0] + symbol_string += "%s = %s(name='%s', data=%s %s)\n" %\ + (name, type_string, name, mapping[bottom[0]], param_string) + else: + symbol_string += "%s = %s(name='%s', *[%s] %s)\n" %\ + (name, type_string, name, ','.join([mapping[x] for x in bottom]), param_string) + for j in range(len(layer[i].top)): + mapping[layer[i].top[j]] = name + return symbol_string -if __name__ == '__main__': - main() +def proto2symbol(proto_file): + sym = proto2script(proto_file) + sym = "import mxnet as mx\n" \ + + "data = mx.symbol.Variable(name='data')\n" \ + + sym + exec(sym) + return prob From ab3907a78bd6755aba7f9c34eaaea20fe565a7ba Mon Sep 17 00:00:00 2001 From: winsty Date: Wed, 7 Oct 2015 18:29:01 -0700 Subject: [PATCH 3/5] adaptive data and output --- tools/caffe_converter/convert_model.py | 2 +- tools/caffe_converter/convert_symbol.py | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/tools/caffe_converter/convert_model.py b/tools/caffe_converter/convert_model.py index 1b36749e81d1..7f362dbbe06d 100644 --- a/tools/caffe_converter/convert_model.py +++ b/tools/caffe_converter/convert_model.py @@ -28,7 +28,7 @@ def main(): wmat = layer.blobs[0].data bias = layer.blobs[1].data if first_conv: - print 'Swapping BGR of caffe into RGB in cxxnet' + print 'Swapping BGR of caffe into RGB in mxnet' wmat[:, [0, 2], :, :] = wmat[:, [2, 0], :, :] assert(wmat.flags['C_CONTIGUOUS'] is True) diff --git a/tools/caffe_converter/convert_symbol.py b/tools/caffe_converter/convert_symbol.py index ca2e47a29b37..ea673c4a7863 100644 --- a/tools/caffe_converter/convert_symbol.py +++ b/tools/caffe_converter/convert_symbol.py @@ -16,19 +16,19 @@ def readProtoFile(filepath, parser_object): return parser_object def proto2script(proto_file): - # parser = argparse.ArgumentParser(description='Caffe prototxt to mxnet config converter.\ - # Note that only basic functions are implemented. You are welcomed to contribute to this file.') - # parser.add_argument('caffe_proto_file', help='The prototxt file in Caffe format') - # args = parser.parse_args() proto = readProtoSolverFile(proto_file) connection = dict() symbols = dict() top = dict() - mapping = {'data' : 'data'} - need_flatten = {'data' : False} flatten_count = 0 symbol_string = "" layer = proto.layer + + # We assume the first bottom blob of first layer is the output from data layer + input_name = layer[0].bottom[0] + output_name = "" + mapping = {input_name : 'data'} + need_flatten = {input_name : False} for i in range(len(layer)): type_string = '' param_string = '' @@ -77,6 +77,9 @@ def proto2script(proto_file): need_flatten[name] = need_flatten[mapping[proto.layer[i].bottom[0]]] if layer[i].type == 'Softmax': type_string = 'mx.symbol.Softmax' + + # We only support single output network for now. + output_name = name if layer[i].type == 'Flatten': type_string = 'mx.symbol.Flatten' need_flatten[name] = False @@ -108,12 +111,13 @@ def proto2script(proto_file): (name, type_string, name, ','.join([mapping[x] for x in bottom]), param_string) for j in range(len(layer[i].top)): mapping[layer[i].top[j]] = name - return symbol_string + return symbol_string, output_name def proto2symbol(proto_file): - sym = proto2script(proto_file) + sym, output_name = proto2script(proto_file) sym = "import mxnet as mx\n" \ + "data = mx.symbol.Variable(name='data')\n" \ + sym exec(sym) - return prob + exec("ret = " + output_name) + return ret From 06be47a15ccc2f9f88cd7796b46ad9bed8c7c07c Mon Sep 17 00:00:00 2001 From: winsty Date: Wed, 7 Oct 2015 18:46:55 -0700 Subject: [PATCH 4/5] converter readme --- tools/caffe_converter/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 tools/caffe_converter/README.md diff --git a/tools/caffe_converter/README.md b/tools/caffe_converter/README.md new file mode 100644 index 000000000000..7adab9a93c32 --- /dev/null +++ b/tools/caffe_converter/README.md @@ -0,0 +1,16 @@ +# Convert Caffe Model to Mxnet Format + +## Introduction + +This is an experimental tool for conversion of Caffe model into mxnet model. There are several limitations to note: +* Please first make sure that there is corresponding operator in mxnet before conversion. +* The tool only supports single input and single output network. +* The tool can only work with the L2LayerParameter in Caffe. For older version, please use the ```upgrade_net_proto_binary``` and ```upgrade_net_proto_text``` in ```tools``` folder of Caffe to upgrate them. + +## Notes on Codes +* The core function for converting symbol is in ```convert_symbols.py```. ```proto2script``` converts the prototxt to corresponding python script to generate the symbol. Therefore if you need to modify the auto-generated symbols, you can print out the return value. You can also find the supported layers/operators there. +* The weights are converted in ```convert_model.py```. + +## Usage +Run ```python convert_model.py caffe_prototxt caffe_model save_model_name``` to convert the models. Run with ```-h``` for more details of parameters. + From af896d13dd59824aac1bd7973fb4f683cabe27d8 Mon Sep 17 00:00:00 2001 From: winsty Date: Wed, 7 Oct 2015 18:51:48 -0700 Subject: [PATCH 5/5] update converter readme --- tools/caffe_converter/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/caffe_converter/README.md b/tools/caffe_converter/README.md index 7adab9a93c32..7089f5563ab9 100644 --- a/tools/caffe_converter/README.md +++ b/tools/caffe_converter/README.md @@ -7,6 +7,8 @@ This is an experimental tool for conversion of Caffe model into mxnet model. The * The tool only supports single input and single output network. * The tool can only work with the L2LayerParameter in Caffe. For older version, please use the ```upgrade_net_proto_binary``` and ```upgrade_net_proto_text``` in ```tools``` folder of Caffe to upgrate them. +We have verified the results of VGG_16 model and BVLC_googlenet results from Caffe model zoo. + ## Notes on Codes * The core function for converting symbol is in ```convert_symbols.py```. ```proto2script``` converts the prototxt to corresponding python script to generate the symbol. Therefore if you need to modify the auto-generated symbols, you can print out the return value. You can also find the supported layers/operators there. * The weights are converted in ```convert_model.py```.