add_caffe_custom_layer.md 7.9 KB
Newer Older
S
SunAhong1993 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
## 如何转换Caffe自定义Layer

本文档介绍如何将Caffe自定义Layer转换为PaddlePaddle模型中的对应实现, 用户可根据自己需要,添加代码实现自定义层,从而支持模型的完整转换。 
目前,代码中已经提供了10个非官方op(不在[官网](http://caffe.berkeleyvision.org/tutorial/layers)上的op)的转换,这些op对应的Caffe实现源码如下:

| op | 该版本实现源码 |
|-------|--------|
| PriorBox | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/prior_box_layer.cpp) |
| DetectionOutput | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/detection_output_layer.cpp) |
| ConvolutionDepthwise | [code](https://github.com/farmingyard/caffe-mobilenet/blob/master/conv_dw_layer.cpp) |
| ShuffleChannel | [code](https://github.com/farmingyard/ShuffleNet/blob/master/shuffle_channel_layer.cpp) |
| Permute | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/permute_layer.cpp) |
| Normalize | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/normalize_layer.cpp) |
| ROIPooling | [code](https://github.com/rbgirshick/caffe-fast-rcnn/blob/0dcd397b29507b8314e252e850518c5695efbb83/src/caffe/layers/roi_pooling_layer.cpp) |
| Axpy | [code](https://github.com/hujie-frank/SENet/blob/master/src/caffe/layers/axpy_layer.cpp) |
| ReLU6 | [code](https://github.com/chuanqi305/ssd/blob/ssd/src/caffe/layers/relu6_layer.cpp) |
| Upsample | [code](https://github.com/eric612/MobileNet-YOLO/blob/master/src/caffe/layers/upsample_layer.cpp) |

添加代码实现自定义层的步骤如下:

***步骤一 下载代码***  
此处涉及修改源码,应先卸载x2paddle,并且下载源码,主要有以下两步完成:
```
pip uninstall x2paddle
pip install git+https://github.com/PaddlePaddle/X2Paddle.git@develop
```

***步骤二 编译caffe.proto***  
该步骤依赖protobuf编译器,其安装过程有以下两种方式:
> 选择一:pip install protobuf  (protobuf >= 3.6.0)
> 选择二:使用[官方源码](https://github.com/protocolbuffers/protobuf)进行编译

使用脚本./tools/compile.sh将caffe.proto(包含所需的自定义Layer信息)编译成我们所需的目标语言(Python)  
使用方式:
```
bash ./toos/compile.sh /home/root/caffe/src/caffe/proto
# /home/root/caffe/src/caffe/proto为caffe.proto的存放路径,生成的caffe_pb2.py也将保存在该路径下
```
S
SunAhong1993 已提交
39
将生成的caffe_pb2.py替换x2paddle/decoder下的caffe_pb2.py。
S
SunAhong1993 已提交
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162

***步骤三 添加自定义Layer的实现代码***
> 【注意】若Caffe自定义layer与Paddle的op一一对应,使用方式一,否则使用方式二。

- 方式一:
1. 仿照./x2paddle/op_mapper/dygraph/caffe2paddle/caffe_op_mapper.py中的CaffeOpMapper类中的映射方法(输入为self和node),实现类似的映射方法,以下述的映射方法为例:
```python
def Permute(self, node):
    assert len(
        node.inputs) == 1, "The count of Permute node\'s input is not 1."
    input = self.graph.get_input_node(node, idx=0, copy=True)
    params = node.layer.permute_param
    order = list(params.order)    
    self.paddle_graph.add_layer(
        "paddle.transpose",
        inputs={"x": input.name},
        outputs=[node.layer_name],
        perm=order)
```
>需完成的步骤:
>    a. 获取Caffe Layer的属性,并对应转换为Paddle的属性。
>    b. 获取当前Layer的输入。
>    c. 使用self.paddle_graph.add_layer为PaddleGraph添加layer。其中,第一个参数代表Paddle的kernel;inputs是一个字典,用于存储paddle中的输入的key与其输入名字;outputs是一个列表,用于存储输出的名字;其余参数为属性对应关系。
2. 仿照./x2paddle/decoder/caffe_shape_inference.py中的shape_xx方法,实现获取当前Layer输出大小的函数,以下述方法为例:
```python
def shape_permute(layer, input_shape):
    order = layer.permute_param.order
    inshape = input_shape[0]
    output_shape = []
    order = list(order)
    for ii in order:
        assert ii < len(inshape), "invalid order for permute[%s]" % (name)
        output_shape.append(inshape[ii])
    return [output_shape]
```
>参数:
>    layer (caffe_pb2.LayerParameter): caffe的Layer,可用于获取当前Layer的属性。
>    input_shape (list): 其中每个元素代表该层每个输入数据的大小。


- 方式二:
1. 进入./x2paddle/op_mapper/dygraph/caffe2paddle/caffe_custom_layer,创建.py文件,例如mylayer.py
2. 仿照./x2paddle/op_mapper/dygraph/caffe2paddle/caffe_custom_layer中的其他文件,在mylayer.py中主要需要实现1个类,下面以roipooling.py为例分析代码:
  
```python
class ROIPooling(object):
    def __init__(self, pooled_height, pooled_width, spatial_scale):
        self.roipooling_layer_attrs = {
            "pooled_height": pooled_height,
            "pooled_width": pooled_width,
            "spatial_scale": spatial_scale}

    def __call__(self, x0, x1):
        slice_x1 = paddle.slice(input=x1, axes=[1], 
                                starts=[1], ends=[5])
        out = fluid.layers.roi_pool(input=x0, 
                                    rois=slice_x1, 
                                    **self.roipooling_layer_attrs)
        return out
```

>\_\_init\_\_函数:用于初始化各个属性
>\_\_call\_\_函数:用于组合实现当前Layer的前向,输入为当前Layer所需要的输入


3. 仿照./x2paddle/op_mapper/dygraph/caffe2paddle/caffe_op_mapper.py中的CaffeOpMapper类中的映射方法(输入为self和node),实现类似的映射方法,以下述的映射方法为例:
```python
def ROIPooling(self, node):
      roipooling_name = name_generator("roipooling", self.nn_name2id)
      output_name = node.layer_name
      layer_outputs = [roipooling_name, output_name]
      assert len(
          node.inputs) == 2, "The count of ROIPooling node\'s input is not 2."
      input0 = self.graph.get_input_node(node, idx=0, copy=True)
      input1 = self.graph.get_input_node(node, idx=1, copy=True)
      inputs_dict = {}
      inputs_dict["x0"] = input0.name
      inputs_dict["x1"] = input1.name
      params = node.layer.roi_pooling_param
      layer_attrs = {
          "pooled_height": params.pooled_h,
          "pooled_width": params.pooled_w,
          "spatial_scale": params.spatial_scale}
      self.paddle_graph.add_layer(
          "custom_layer:ROIPooling",
          inputs=inputs_dict,
          outputs=layer_outputs,
          **layer_attrs)
```
>需完成的步骤:
>    a. 获取Caffe Layer的属性,并对应转换为Paddle的属性。
>    b. 获取当前Layer的输入。
>    c. 使用self.paddle_graph.add_layer为PaddleGraph添加layer。其中,第一个参数代表Paddle的kernel(此处kernel必须以“custom_layer:“开头);inputs是一个字典,用于存储paddle中的输入的key与其输入名字;outputs是一个列表,用于存储输出的名字;其余参数为属性对应关系。

4. 仿照./x2paddle/decoder/caffe_shape_inference.py中的shape_xx方法,实现获取当前Layer输出大小的函数,以下述方法为例:
```python
def shape_roipooling(layer, input_shape):
  pooled_w = layer.roi_pooling_param.pooled_w
  pooled_h = layer.roi_pooling_param.pooled_h
  base_fea_shape = input_shapes[0]
  rois_shape = input_shapes[1]
  output_shape = base_fea_shape
  output_shape[0] = rois_shape[0]
  output_shape[2] = pooled_h
  output_shape[3] = pooled_w
  return [output_shape]

```
>参数:
>    layer (caffe_pb2.LayerParameter): caffe的Layer,可用于获取当前Layer的属性。
>    input_shape (list): 其中每个元素代表该层每个输入数据的大小。

***步骤四 运行转换代码***
```
# 在X2Paddle目录下安装x2paddle
python setup.py install
# 运行转换代码
x2paddle --framework=caffe
         --prototxt=deploy.proto
         --weight=deploy.caffemodel
         --save_dir=pd_model
         --caffe_proto=/home/root/caffe/src/caffe/proto/caffe_pb2.py
```