diff --git a/fluid/image_classification/caffe2fluid/README.md b/fluid/image_classification/caffe2fluid/README.md index 9a6daad90222ab036cac896a66e50f273deac3d7..dae7d05d1e41ee47a87630eaa7c5321c2e9ec270 100644 --- a/fluid/image_classification/caffe2fluid/README.md +++ b/fluid/image_classification/caffe2fluid/README.md @@ -1,6 +1,15 @@ ### Caffe2Fluid This tool is used to convert a Caffe model to a Fluid model +### Key Features +1. Convert caffe model to fluid model with codes of defining a network(useful for re-training) + +2. Pycaffe is not necessary when just want convert model without do caffe-inference + +3. Caffe's customized layers convertion also be supported by extending this tool + +4. A bunch of tools in '*examples/imagenet/tools*' are provided to compare the difference + ### HowTo 1. Prepare caffepb.py in ./proto if your python has no 'pycaffe' module, two options provided here: - Generate pycaffe from caffe.proto @@ -29,10 +38,10 @@ This tool is used to convert a Caffe model to a Fluid model ``` 3. Use the converted model to infer - - See more details in '*examples/imagenet/run.sh*' + - See more details in '*examples/imagenet/tools/run.sh*' 4. Compare the inference results with caffe - - See more details in '*examples/imagenet/diff.sh*' + - See more details in '*examples/imagenet/tools/diff.sh*' ### How to convert custom layer 1. Implement your custom layer in a file under '*kaffe/custom_layers*', eg: mylayer.py diff --git a/fluid/image_classification/caffe2fluid/examples/imagenet/README.md b/fluid/image_classification/caffe2fluid/examples/imagenet/README.md index b9cf1941d29428c84c34df2a9ec00d7ae8e79014..ad965cd43dab24f162f3deafa249d1b06fffaecf 100644 --- a/fluid/image_classification/caffe2fluid/examples/imagenet/README.md +++ b/fluid/image_classification/caffe2fluid/examples/imagenet/README.md @@ -1,4 +1,4 @@ -A demo to show converting caffe models on 'imagenet' using caffe2fluid +A demo to show converting caffe models trained on 'imagenet' using caffe2fluid --- @@ -10,28 +10,32 @@ A demo to show converting caffe models on 'imagenet' using caffe2fluid 3. Convert the Caffe model to Fluid model - generate fluid code and weight file -
python convert.py alexnet.prototxt \
+        ```python convert.py alexnet.prototxt \
         --caffemodel alexnet.caffemodel \
         --data-output-path alexnet.npy \
         --code-output-path alexnet.py
-    
+ ``` - save weights as fluid model file -
python alexnet.py alexnet.npy ./fluid_model
-    
+ ``` + python alexnet.py alexnet.npy ./fluid + ``` 4. Do inference -
python infer.py infer ./fluid_mode data/65.jpeg
-
+ ``` + python infer.py infer ./fluid data/65.jpeg + ``` 5. convert model and do inference together -
bash ./run.sh alexnet ./models.caffe/alexnet ./models/alexnet
-
- The Caffe model is stored in './models.caffe/alexnet/alexnet.prototxt|caffemodel' - and the Fluid model will be save in './models/alexnet/alexnet.py|npy' + ``` + bash ./tools/run.sh alexnet ./models.caffe/alexnet ./models/alexnet + ``` + * Assume the Caffe model is stored in '*./models.caffe/alexnet/alexnet.prototxt|caffemodel*' + * converted model will be stored as '*./models/alexnet/alexnet.py|npy*' 6. test the difference with caffe's results(need pycaffe installed) -
bash ./diff.sh resnet
-
-Make sure your caffemodel stored in './models.caffe/resnet'. -The results will be stored in './results/resnet.paddle|caffe' + ``` + bash ./tools/diff.sh resnet + ``` + * Make sure your caffemodel stored in '*./models.caffe/resnet*' + * The results will be stored in '*./results/resnet.paddle|caffe*' diff --git a/fluid/image_classification/caffe2fluid/examples/imagenet/compare.py b/fluid/image_classification/caffe2fluid/examples/imagenet/compare.py index 07d4ed1af50a803aee206da6c7582d079a1a1dca..041d0094b4e262838e33b922cf770715425d6f04 100644 --- a/fluid/image_classification/caffe2fluid/examples/imagenet/compare.py +++ b/fluid/image_classification/caffe2fluid/examples/imagenet/compare.py @@ -17,8 +17,21 @@ def walk_dir(rootdir): def calc_diff(f1, f2): import numpy as np - d1 = np.load(f1).flatten() - d2 = np.load(f2).flatten() + d1 = np.load(f1) + d2 = np.load(f2) + + print d1.shape + print d2.shape + #print d1[0, 0, 0:10, 0:10] + #print d2[0, 0, 0:10, 0:10] + #d1 = d1[:, :, 1:-2, 1:-2] + #d2 = d2[:, :, 1:-2, 1:-2] + + d1 = d1.flatten() + d2 = d2.flatten() + + #print d1[:10] + #print d2[:10] d1_num = reduce(lambda x, y: x * y, d1.shape) d2_num = reduce(lambda x, y: x * y, d2.shape) @@ -36,15 +49,16 @@ def calc_diff(f1, f2): return -1.0, -1.0 -def compare(path1, path2): +def compare(path1, path2, no_exception): def diff(f1, f2): max_df, sq_df = calc_diff(f1, f2) - print('compare %s <=> %s with result[max_df:%.4e, sq_df:%.4e]' % - (f1, f2, max_df, sq_df)) - assert (max_df < 1e-5), \ - 'max_df is too large with value[%.6e]' % (max_df) - assert (sq_df < 1e-10), \ - 'sq_df is too large with value[%.6e]' % (sq_df) + print('[max_df:%.4e, sq_df:%.4e] when compare %s <=> %s' % + (max_df, sq_df, os.path.basename(f1), os.path.basename(f2))) + if no_exception is False: + assert (max_df < 1e-5), \ + 'max_df is too large with value[%.6e]' % (max_df) + assert (sq_df < 1e-10), \ + 'sq_df is too large with value[%.6e]' % (sq_df) if os.path.exists(path1) is False: print('not found %s' % (path1)) @@ -73,13 +87,17 @@ if __name__ == "__main__": if len(sys.argv) == 1: path1 = 'lenet.tf/results' path2 = 'lenet.paddle/results' - elif len(sys.argv) == 3: + elif len(sys.argv) >= 3: path1 = sys.argv[1] path2 = sys.argv[2] + if len(sys.argv) == 4: + no_exception = True + else: + no_exception = False else: print('usage:') print(' %s [path1] [path2]' % (sys.argv[0])) exit(1) - print('compare inner result in %s %s' % (path1, path2)) - exit(compare(path1, path2)) + #print('compare inner result in %s %s' % (path1, path2)) + exit(compare(path1, path2, no_exception)) diff --git a/fluid/image_classification/caffe2fluid/examples/imagenet/infer.py b/fluid/image_classification/caffe2fluid/examples/imagenet/infer.py index d71a91ad7e731e4585ae4adfb44b0a1019260e0d..57f80d4cfe03acd5f78bc873e0c6245a4d2548e7 100644 --- a/fluid/image_classification/caffe2fluid/examples/imagenet/infer.py +++ b/fluid/image_classification/caffe2fluid/examples/imagenet/infer.py @@ -213,7 +213,6 @@ def caffe_infer(prototxt, caffemodel, datafile): results = [] names = [] for k, v in net.blobs.items(): - k = k.rstrip('_output') k = k.replace('/', '_') names.append(k) results.append(v.data.copy()) @@ -259,7 +258,7 @@ if __name__ == "__main__": print('usage:') print('\tpython %s dump [net_file] [weight_file] [datafile] [net_name]' \ % (sys.argv[0])) - print('\teg:python dump %s %s %s %s %s' % (sys.argv[0],\ + print('\teg:python %s dump %s %s %s %s' % (sys.argv[0],\ net_file, weight_file, datafile, net_name)) sys.exit(1) diff --git a/fluid/image_classification/caffe2fluid/examples/imagenet/tools/cmp.sh b/fluid/image_classification/caffe2fluid/examples/imagenet/tools/cmp.sh new file mode 100755 index 0000000000000000000000000000000000000000..1ed2c8446d3a98aef302fa6a2c82d158a9b08419 --- /dev/null +++ b/fluid/image_classification/caffe2fluid/examples/imagenet/tools/cmp.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# +#function: +# a tool used to compare the results produced by paddle and caffe +# + +if [[ $# -lt 2 ]];then + echo "usage:" + echo " bash $0 [model_name] [param_name] [caffe_name]" + exit 1 +fi + +model_name=$1 +param_name=$2 +paddle_file="./results/${model_name}.paddle/${param_name}.npy" +if [[ $# -eq 3 ]];then + caffe_file="./results/${model_name}.caffe/${3}.npy" +else + caffe_file="./results/${model_name}.caffe/${2}.npy" +fi +python ./compare.py $paddle_file $caffe_file diff --git a/fluid/image_classification/caffe2fluid/examples/imagenet/tools/cmp_layers.sh b/fluid/image_classification/caffe2fluid/examples/imagenet/tools/cmp_layers.sh new file mode 100755 index 0000000000000000000000000000000000000000..d080f78bc58b58a121dd577b837786911e44f7a4 --- /dev/null +++ b/fluid/image_classification/caffe2fluid/examples/imagenet/tools/cmp_layers.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +#function: +# a tool used to compare all layers' results +# + +if [[ $# -ne 1 ]];then + echo "usage:" + echo " bash $0 [model_name]" + echo " eg:bash $0 alexnet" + exit 1 +fi + +model_name=$1 +prototxt="models.caffe/$model_name/${model_name}.prototxt" +layers=$(cat $prototxt | perl -ne 'if(/^\s+name\s*:\s*\"([^\"]+)/){print $1."\n";}') + +for i in $layers;do + cf_npy="results/${model_name}.caffe/${i}.npy" + pd_npy="results/${model_name}.paddle/${i}.npy" + + if [[ ! -e $cf_npy ]];then + echo "caffe's result not exist[$cf_npy]" + continue + fi + + if [[ ! -e $pd_npy ]];then + echo "paddle's result not exist[$pd_npy]" + continue + fi + + python compare.py $cf_npy $pd_npy no_exception + if [[ $? -eq 0 ]];then + echo "succeed to compare layer[$i]" + else + echo "failed to compare layer[$i]" + fi + +done diff --git a/fluid/image_classification/caffe2fluid/examples/imagenet/diff.sh b/fluid/image_classification/caffe2fluid/examples/imagenet/tools/diff.sh similarity index 96% rename from fluid/image_classification/caffe2fluid/examples/imagenet/diff.sh rename to fluid/image_classification/caffe2fluid/examples/imagenet/tools/diff.sh index af72caea536d6b6c3d1027e7d1327af52a6ceda6..1bf56081f83fa964301459064eda86f64d9d1cbd 100755 --- a/fluid/image_classification/caffe2fluid/examples/imagenet/diff.sh +++ b/fluid/image_classification/caffe2fluid/examples/imagenet/tools/diff.sh @@ -36,7 +36,7 @@ model_caffemodel="models.caffe/${model_name}/${model_name}.caffemodel" paddle_results="$results_root/${model_name}.paddle" rm -rf $paddle_results rm -rf "results.paddle" -bash run.sh $model_name ./models.caffe/$model_name ./models/$model_name +bash ./tools/run.sh $model_name ./models.caffe/$model_name ./models/$model_name if [[ $? -ne 0 ]] || [[ ! -e "results.paddle" ]];then echo "not found paddle's results, maybe failed to convert" exit 1 diff --git a/fluid/image_classification/caffe2fluid/examples/imagenet/run.sh b/fluid/image_classification/caffe2fluid/examples/imagenet/tools/run.sh similarity index 94% rename from fluid/image_classification/caffe2fluid/examples/imagenet/run.sh rename to fluid/image_classification/caffe2fluid/examples/imagenet/tools/run.sh index 0fdd56e4519bf726a8e5bc95559d1d9b47f14774..d9b2365d57b15df7a1810547a2d7da0a2105c2b5 100755 --- a/fluid/image_classification/caffe2fluid/examples/imagenet/run.sh +++ b/fluid/image_classification/caffe2fluid/examples/imagenet/tools/run.sh @@ -6,7 +6,7 @@ # 2, do inference(only in fluid) using this model # #usage: -# bash run.sh resnet50 ./models.caffe/resnet50 ./models/resnet50 +# cd caffe2fluid/examples/imagenet && bash run.sh resnet50 ./models.caffe/resnet50 ./models/resnet50 # #set -x diff --git a/fluid/image_classification/caffe2fluid/kaffe/custom_layers/__init__.py b/fluid/image_classification/caffe2fluid/kaffe/custom_layers/__init__.py index e2276c09c2c408f4c6e65264b4bde91429df53ca..bc480853a0c32dfbad359c3f8e6f9ef04912b020 100644 --- a/fluid/image_classification/caffe2fluid/kaffe/custom_layers/__init__.py +++ b/fluid/image_classification/caffe2fluid/kaffe/custom_layers/__init__.py @@ -7,13 +7,14 @@ from .register import get_registered_layers import axpy import flatten import argmax +import reshape #custom layer import ends custom_layers = get_registered_layers() -def set_args(f, params): +def set_args(f, params, node=None): """ set args for function 'f' using the parameters in node.layer.parameters Args: @@ -24,19 +25,15 @@ def set_args(f, params): arg_names (list): a list of argument names kwargs (dict): a dict contains needed arguments """ + from ..protobuf_to_dict import protobuf_to_dict + argc = f.__code__.co_argcount arg_list = f.__code__.co_varnames[0:argc] kwargs = {} for arg_name in arg_list: - try: - v = getattr(params, arg_name, None) - except Exception as e: - #maybe failed to extract caffe's parameters - v = None - - if v is not None: - kwargs[arg_name] = v + if arg_name in params: + kwargs[arg_name] = params[arg_name] return arg_list, kwargs @@ -54,7 +51,7 @@ def compute_output_shape(kind, node): parents = node.parents inputs = [list(p.output_shape) for p in parents] - arg_names, kwargs = set_args(shape_func, node.layer.parameters) + arg_names, kwargs = set_args(shape_func, node.params) if len(inputs) == 1: inputs = inputs[0] @@ -80,7 +77,7 @@ def make_node(template, kind, node): layer_func = custom_layers[kind]['layer'] #construct arguments needed by custom layer function from node's parameters - arg_names, kwargs = set_args(layer_func, node.layer.parameters) + arg_names, kwargs = set_args(layer_func, node.params, node) return template('custom_layer', kind, **kwargs) diff --git a/fluid/image_classification/caffe2fluid/kaffe/custom_layers/reshape.py b/fluid/image_classification/caffe2fluid/kaffe/custom_layers/reshape.py new file mode 100644 index 0000000000000000000000000000000000000000..6b8d5681ec68c7a899cb3fdbd4fca0249402bfa0 --- /dev/null +++ b/fluid/image_classification/caffe2fluid/kaffe/custom_layers/reshape.py @@ -0,0 +1,123 @@ +""" a custom layer for 'reshape', maybe we should implement this in standard way. + more info can be found here: http://caffe.berkeleyvision.org/tutorial/layers/reshape.html +""" +from .register import register + + +def import_fluid(): + import paddle.fluid as fluid + return fluid + + +def reshape_shape(input_sp, shape, axis=0, num_axes=-1): + """ calculate the output shape of this layer using input shape + + Args: + @input_shape (list of num): a list of number which represents the input shape + @shape (object): parameter from caffe's Reshape layer + @axis (int): parameter from caffe's Reshape layer + @num_axes(int): parameter from caffe's Reshape layer + + Returns: + @output_shape (list of num): a list of numbers represent the output shape + """ + + def count(num_list): + return reduce(lambda a, b: a * b, num_list) + + input_shape = list(input_sp) + input_count = count(input_shape) + + input_num_axes = len(input_shape) + + input_start_axis = axis + start_axis = input_start_axis if input_start_axis >= 0 \ + else input_num_axes + input_start_axis + 1 + + assert start_axis >= 0, "[Reshape]axis %d out of range" % (input_start_axis) + assert start_axis <= input_num_axes, "[Reshape]axis %d out of range for %d-D input data"\ + % (input_start_axis, input_num_axes) + + assert num_axes >= -1, "[Reshape]num_axes must be >= 0, or -1 for all" + + end_axis = input_num_axes if num_axes == -1 else start_axis + num_axes + assert end_axis <= input_num_axes, "end_axis[%d] = axis[%d] + num_axes[%d] is out of range"\ + % (end_axis, start_axis, num_axes) + + num_axes_replaced = end_axis - start_axis + num_axes_retained = input_num_axes - num_axes_replaced + num_new_axes = len(shape['dim']) + output_shape = [] + + for i in range(start_axis): + output_shape.append(input_shape[i]) + + for i in range(num_new_axes): + output_shape.append(shape['dim'][i]) + + for i in range(end_axis, input_num_axes): + output_shape.append(input_shape[i]) + + assert len(output_shape) == num_axes_retained + num_new_axes,\ + "[Reshape]invalid dims of output shape[%s]" % (str(output_shape)) + + inferred_axis = -1 + copy_axes = [] + constant_count = 1 + for i in range(num_new_axes): + top_dim = shape['dim'][i] + if top_dim == 0: + copy_axes.append(i) + elif top_dim == -1: + assert inferred_axis == -1, "[Reshape]new shape contains multiple -1 dims" + else: + constant_count *= top_dim + + if inferred_axis >= 0: + explicit_count = constant_count + explicit_count *= count(input_shape[0:start_axis]) + explicit_count *= count(input_shape[end_axis:]) + + for i in range(len(copy_axes)): + explicit_count *= output_shape[start_axis + copy_axes[i]] + + assert input_count % explicit_count == 0, "[Reshape]botom count[%d] "\ + "must be divisible by product of the specified dimensions[%d] "\ + % (input_count, explicit_count) + + output_count = count(output_shape) + assert output_count == input_count, "[Reshape]output count[%d] must match input count[%d]" % ( + output_count, input_count) + + return output_shape + + +def reshape_layer(input, name, shape, axis=0, num_axes=-1): + """ build a layer of type 'Flatten' using fluid + + Args: + @input (variable): input fluid variable for this layer + @name (str): name for this layer + @shape (object): parameter from caffe's Reshape layer + @axis (int): parameter from caffe's Reshape layer + @num_axes(int): parameter from caffe's Reshape layer + + Returns: + output (variable): output variable for this layer + """ + fluid = import_fluid() + + input_shape = list(input.shape) + + if input_shape[0] == -1: + input_shape[0] = 1 + output_shape = reshape_shape(input_shape, shape, axis, num_axes) + output_shape[0] = -1 + else: + output_shape = reshape_shape(input_shape, shape, axis, num_axes) + + output = fluid.layers.reshape(input, shape=output_shape, name=name) + return output + + +register(kind='Reshape', shape=reshape_shape, layer=reshape_layer) diff --git a/fluid/image_classification/caffe2fluid/kaffe/graph.py b/fluid/image_classification/caffe2fluid/kaffe/graph.py index 6182a5352dac4746c64ebef0b3a886399dbd3d57..c7e3f8c44af44053b1bd67bc49438181300e3567 100644 --- a/fluid/image_classification/caffe2fluid/kaffe/graph.py +++ b/fluid/image_classification/caffe2fluid/kaffe/graph.py @@ -13,8 +13,8 @@ class Node(object): self.layer = LayerAdapter(layer, kind) if layer else None self.parents = [] self.children = [] - self.data = None - self.output_shape = None + self.data = None #parameters of this node + self.output_shape = None #output shape of this node self.metadata = {} def add_parent(self, parent_node): @@ -37,10 +37,24 @@ class Node(object): @property def parameters(self): + """ get parameters stored in a protobuf object + """ if self.layer is not None: return self.layer.parameters return None + @property + def params(self): + """ get parameters stored in a dict + """ + from .protobuf_to_dict import protobuf_to_dict + + p = self.parameters + if p is not None: + return protobuf_to_dict(p) + else: + return None + def __str__(self): return '[%s] %s' % (self.kind, self.name) diff --git a/fluid/image_classification/caffe2fluid/kaffe/paddle/network.py b/fluid/image_classification/caffe2fluid/kaffe/paddle/network.py index 258830bdac00af8fb9f2e83207730b404a04f7d5..85fd0b7bb3996cfa613013da97969a6f63162bb5 100644 --- a/fluid/image_classification/caffe2fluid/kaffe/paddle/network.py +++ b/fluid/image_classification/caffe2fluid/kaffe/paddle/network.py @@ -22,15 +22,13 @@ def layer(op): layer_input = self.terminals[0] else: layer_input = list(self.terminals) + # Perform the operation and get the output. layer_output = op(self, layer_input, *args, **kwargs) # Add to layer LUT. self.layers[name] = layer_output # This output is now the input for the next layer. self.feed(layer_output) - #print('output shape of %s:' % (name)) - #print layer_output.shape - # Return self for chained calls. return self @@ -129,6 +127,7 @@ class Network(object): s_w, name, relu=True, + relu_negative_slope=0.0, padding=None, group=1, biased=True): @@ -144,6 +143,14 @@ class Network(object): fluid = import_fluid() prefix = name + '_' + leaky_relu = False + act = 'relu' + if relu is False: + act = None + elif relu_negative_slope != 0.0: + leaky_relu = True + act = None + output = fluid.layers.conv2d( input=input, filter_size=[k_h, k_w], @@ -153,7 +160,11 @@ class Network(object): groups=group, param_attr=fluid.ParamAttr(name=prefix + "weights"), bias_attr=fluid.ParamAttr(name=prefix + "biases"), - act="relu" if relu is True else None) + act=act) + + if leaky_relu: + output = fluid.layers.leaky_relu(output, alpha=relu_negative_slope) + return output @layer @@ -286,8 +297,32 @@ class Network(object): @layer def dropout(self, input, drop_prob, name, is_test=True): fluid = import_fluid() - output = fluid.layers.dropout( - input, dropout_prob=drop_prob, is_test=is_test, name=name) + if is_test: + output = input + else: + output = fluid.layers.dropout( + input, dropout_prob=drop_prob, is_test=is_test) + return output + + @layer + def scale(self, input, axis=1, num_axes=1, name=None): + fluid = import_fluid() + + assert num_axes == 1, "layer scale not support this num_axes[%d] now" % ( + num_axes) + + prefix = name + '_' + scale_shape = input.shape[axis:axis + num_axes] + param_attr = fluid.ParamAttr(name=prefix + 'scale') + scale_param = fluid.layers.create_parameter( + shape=scale_shape, dtype=input.dtype, name=name, attr=param_attr) + + offset_attr = fluid.ParamAttr(name=prefix + 'offset') + offset_param = fluid.layers.create_parameter( + shape=scale_shape, dtype=input.dtype, name=name, attr=offset_attr) + + output = fluid.layers.elementwise_mul(input, scale_param, axis=axis) + output = fluid.layers.elementwise_add(output, offset_param, axis=axis) return output def custom_layer_factory(self): diff --git a/fluid/image_classification/caffe2fluid/kaffe/protobuf_to_dict.py b/fluid/image_classification/caffe2fluid/kaffe/protobuf_to_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..cdc8d44589ff21b25442597430d3dd4c2825dcb2 --- /dev/null +++ b/fluid/image_classification/caffe2fluid/kaffe/protobuf_to_dict.py @@ -0,0 +1,185 @@ +"""a util for convert protobuf to dict +""" + +from google.protobuf.message import Message +from google.protobuf.descriptor import FieldDescriptor + +__all__ = [ + "protobuf_to_dict", "TYPE_CALLABLE_MAP", "dict_to_protobuf", + "REVERSE_TYPE_CALLABLE_MAP" +] + +EXTENSION_CONTAINER = '___X' + +TYPE_CALLABLE_MAP = { + FieldDescriptor.TYPE_DOUBLE: float, + FieldDescriptor.TYPE_FLOAT: float, + FieldDescriptor.TYPE_INT32: int, + FieldDescriptor.TYPE_INT64: long, + FieldDescriptor.TYPE_UINT32: int, + FieldDescriptor.TYPE_UINT64: long, + FieldDescriptor.TYPE_SINT32: int, + FieldDescriptor.TYPE_SINT64: long, + FieldDescriptor.TYPE_FIXED32: int, + FieldDescriptor.TYPE_FIXED64: long, + FieldDescriptor.TYPE_SFIXED32: int, + FieldDescriptor.TYPE_SFIXED64: long, + FieldDescriptor.TYPE_BOOL: bool, + FieldDescriptor.TYPE_STRING: unicode, + FieldDescriptor.TYPE_BYTES: lambda b: b.encode("base64"), + FieldDescriptor.TYPE_ENUM: int, +} + + +def repeated(type_callable): + return lambda value_list: [type_callable(value) for value in value_list] + + +def enum_label_name(field, value): + return field.enum_type.values_by_number[int(value)].name + + +def protobuf_to_dict(pb, + type_callable_map=TYPE_CALLABLE_MAP, + use_enum_labels=False): + result_dict = {} + extensions = {} + for field, value in pb.ListFields(): + type_callable = _get_field_value_adaptor(pb, field, type_callable_map, + use_enum_labels) + if field.label == FieldDescriptor.LABEL_REPEATED: + type_callable = repeated(type_callable) + + if field.is_extension: + extensions[str(field.number)] = type_callable(value) + continue + + result_dict[field.name] = type_callable(value) + + if extensions: + result_dict[EXTENSION_CONTAINER] = extensions + return result_dict + + +def _get_field_value_adaptor(pb, + field, + type_callable_map=TYPE_CALLABLE_MAP, + use_enum_labels=False): + if field.type == FieldDescriptor.TYPE_MESSAGE: + # recursively encode protobuf sub-message + return lambda pb: protobuf_to_dict(pb, + type_callable_map=type_callable_map, + use_enum_labels=use_enum_labels) + + if use_enum_labels and field.type == FieldDescriptor.TYPE_ENUM: + return lambda value: enum_label_name(field, value) + + if field.type in type_callable_map: + return type_callable_map[field.type] + + raise TypeError("Field %s.%s has unrecognised type id %d" % + (pb.__class__.__name__, field.name, field.type)) + + +def get_bytes(value): + return value.decode('base64') + + +REVERSE_TYPE_CALLABLE_MAP = {FieldDescriptor.TYPE_BYTES: get_bytes, } + + +def dict_to_protobuf(pb_klass_or_instance, + values, + type_callable_map=REVERSE_TYPE_CALLABLE_MAP, + strict=True): + """Populates a protobuf model from a dictionary. + + :param pb_klass_or_instance: a protobuf message class, or an protobuf instance + :type pb_klass_or_instance: a type or instance of a subclass of google.protobuf.message.Message + :param dict values: a dictionary of values. Repeated and nested values are + fully supported. + :param dict type_callable_map: a mapping of protobuf types to callables for setting + values on the target instance. + :param bool strict: complain if keys in the map are not fields on the message. + """ + if isinstance(pb_klass_or_instance, Message): + instance = pb_klass_or_instance + else: + instance = pb_klass_or_instance() + return _dict_to_protobuf(instance, values, type_callable_map, strict) + + +def _get_field_mapping(pb, dict_value, strict): + field_mapping = [] + for key, value in dict_value.items(): + if key == EXTENSION_CONTAINER: + continue + if key not in pb.DESCRIPTOR.fields_by_name: + if strict: + raise KeyError("%s does not have a field called %s" % (pb, key)) + continue + field_mapping.append( + (pb.DESCRIPTOR.fields_by_name[key], value, getattr(pb, key, None))) + + for ext_num, ext_val in dict_value.get(EXTENSION_CONTAINER, {}).items(): + try: + ext_num = int(ext_num) + except ValueError: + raise ValueError("Extension keys must be integers.") + if ext_num not in pb._extensions_by_number: + if strict: + raise KeyError( + "%s does not have a extension with number %s. Perhaps you forgot to import it?" + % (pb, key)) + continue + ext_field = pb._extensions_by_number[ext_num] + pb_val = None + pb_val = pb.Extensions[ext_field] + field_mapping.append((ext_field, ext_val, pb_val)) + + return field_mapping + + +def _dict_to_protobuf(pb, value, type_callable_map, strict): + fields = _get_field_mapping(pb, value, strict) + + for field, input_value, pb_value in fields: + if field.label == FieldDescriptor.LABEL_REPEATED: + for item in input_value: + if field.type == FieldDescriptor.TYPE_MESSAGE: + m = pb_value.add() + _dict_to_protobuf(m, item, type_callable_map, strict) + elif field.type == FieldDescriptor.TYPE_ENUM and isinstance( + item, basestring): + pb_value.append(_string_to_enum(field, item)) + else: + pb_value.append(item) + continue + if field.type == FieldDescriptor.TYPE_MESSAGE: + _dict_to_protobuf(pb_value, input_value, type_callable_map, strict) + continue + + if field.type in type_callable_map: + input_value = type_callable_map[field.type](input_value) + + if field.is_extension: + pb.Extensions[field] = input_value + continue + + if field.type == FieldDescriptor.TYPE_ENUM and isinstance(input_value, + basestring): + input_value = _string_to_enum(field, input_value) + + setattr(pb, field.name, input_value) + + return pb + + +def _string_to_enum(field, input_value): + enum_dict = field.enum_type.values_by_name + try: + input_value = enum_dict[input_value].number + except KeyError: + raise KeyError("`%s` is not a valid value for field `%s`" % + (input_value, field.name)) + return input_value diff --git a/fluid/image_classification/caffe2fluid/kaffe/transformers.py b/fluid/image_classification/caffe2fluid/kaffe/transformers.py index 6d98703da3313cf466eb43c2adc49c0e0640a8de..67377ae7f8ec8f253414258cebf8a9924bebfd03 100644 --- a/fluid/image_classification/caffe2fluid/kaffe/transformers.py +++ b/fluid/image_classification/caffe2fluid/kaffe/transformers.py @@ -150,8 +150,8 @@ class DataReshaper(object): if node.kind not in self.reshaped_node_types: # Check for 2+ dimensional data - if any(len(tensor.shape) > 1 for tensor in node.data): - notice('parmaters not reshaped for node: {}'.format(node)) + #if any(len(tensor.shape) > 1 for tensor in node.data): + # notice('parmaters not reshaped for node: {}'.format(node)) continue transpose_order = self.map(node.kind) @@ -233,8 +233,9 @@ class ReLUFuser(SubNodeFuser): parent.kind in self.allowed_parent_types) and \ child.kind == NodeKind.ReLU) - def merge(self, parent, _): + def merge(self, parent, child): parent.metadata['relu'] = True + parent.metadata['relu_negative_slope'] = child.parameters.negative_slope class BatchNormScaleBiasFuser(SubNodeFuser): @@ -316,8 +317,11 @@ class ParameterNamer(object): names = ('mean', 'variance') if len(node.data) == 4: names += ('scale', 'offset') + elif node.kind == NodeKind.Scale: + names = ('scale', 'offset') else: - warn('Unhandled parameters: {}'.format(node.kind)) + warn('Unhandled parameters when naming this it[%s]' % + (node.kind)) continue assert len(names) == len(node.data) node.data = dict(zip(names, node.data))