提交 788a53ef 编写于 作者: J jiangjiajun

remove old codes

上级 d2872816
| Github account | name |
|---|---|
| jiangjiajun | Jia-Jun Jiang |
| walloollaw | Long Wang |
| Renwb1991 | Wen-Bin Ren |
| sunyanfang | Yan-Fang Sun |
| Macrobull | Nai-Rui Luo |
# X2Paddle
[![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE)
[![Version](https://img.shields.io/github/release/PaddlePaddle/X2Paddle.svg)](https://github.com/PaddlePaddle/X2Paddle/releases)
# 简介
X2Paddle支持将Caffe和TensorFlow模型转至PaddlePaddle模型,同时我们目前维护了TensorFlow/Caffe与PaddlePaddle接口对比分析文档。
任何使用问题均可通过[ISSUE](https://github.com/PaddlePaddle/X2Paddle/issues)的方式及时反馈,或者也可直接通过pull request的方式一起更新代码和文档。
## [caffe2fluid](caffe2fluid)
1. 支持将Caffe模型转至PaddlePaddle fluid可加载预测模型
2. 提供Caffe-PaddlePaddle常用API的对比文档[[doc](caffe2fluid/doc)]
## [tensorflow2fluid](tensorflow2fluid)
1. 支持将TensorFlow模型转至PaddlePaddle fluid可加载预测模型
2. 提供TensorFlow-PaddlePaddle常用API的对比文档[[doc](tensorflow2fluid/doc)]
## [onnx2fluid](onnx2fluid)
1. 支持将ONNX模型转至PaddlePaddle fluid可加载预测模型
2. PyTorch支持导出为ONNX模型,因此也可通过onnx2fluid支持PyTorch模型的转换
# 贡献代码
clone代码至本地后,先运行`X2Paddle/commit-prepare.sh`配置代码提交环境
# caffe2fluid
[![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE)
caffe2fluid用于将Caffe模型转换为PaddlePaddle模型,此外在[[doc](doc/ReadMe.md)]目录中整理了Caffe-PaddlePaddle的常用API对比分析。
## 环境依赖
> python >= 2.7
> numpy
> protobuf >= 3.6.0
> future
**caffe2fluid的运行仅依赖上述条件**
但建议在环境中安装好Caffe和PaddlePaddle,便于转换模型后测试。环境安装可参考[安装文档](prepare.md)
## 使用方法
### 模型转换
1. Caffe模型转换为PaddlePaddle模型代码和参数文件(参数以numpy形式保存)
```
# --def_path : Caffe配置文件的保存路径
# --caffemodel : Caffe模型的保存路径
# --data-output-path : 转换后模型参数保存路径
# --code-output-path : 转换后模型代码保存路径
python convert.py --def_path alexnet.prototxt \
--caffemodel alexnet.caffemodel \
--data-output-path alexnet.npy \
--code-output-path alexnet.py
```
2. 可通过如下方式,将模型网络结构和参数均序列化保存为PaddlePaddle框架支持加载的模型格式
```
# --model-param-path : 指定序列化后的模型保存路径
python alexnet.py --npy_path alexnet.npy --model-param-path ./fluid_model
```
或者也可在保存时,指定保存模型的输出
```
# 模型的输出为fc8和prob层
python alexnet.py --npy_path alexnet.npy --model-param-path ./fluid --need-layers-name fc8,prob
```
模型的加载及预测可参考PaddlePaddle官方文档[加载预测模型](http://www.paddlepaddle.org/documentation/docs/zh/1.3/api_guides/low_level/inference.html#id4)
### 模型转换前后差异对比
模型转换后,可通过如下方式,逐层对比转换后的模型与原模型的计算结果差异(**运行环境依赖Caffe和paddlepaddle**
```
# alexnet : Caffe配置文件(.prototxt)中“name”的值
# ../../alexnet.prototxt : Caffe配置文件路径
# ../../alexnet.caffemodel : Caffe模型文件路径
# ../../alexnet.py : 转换后模型代码保存路径
# ../../alexnet.npy : 转换后模型参数保存路径
# ./data/65.jpeg : 需要测试的图像数据
cd examples/imagenet
bash tools/diff.sh alexnet ../../alexnet.prototxt \
../../alexnet.caffemodel \
../../alexnet.py \
../../alexnet.npy \
./data/65.jpeg
```
## 自定义层转换
在模型转换中遇到未支持的自定义层,用户可根据自己需要,添加代码实现自定义层,从而支持模型的完整转换,实现方式如下流程,
1.`kaffe/custom_layers`下实现自定义层,例如mylayer.py
> - 实现`shape_func(input_shape, [other_caffe_params])`,计算输出的大小
> - 实现`layer_func(input_shape, [other_caffe_params])`,构造一个PaddlePaddle Fluid层
> - 注册这两个函数 `register(kind=`MyType`, shape=shape_func, layer=layer_func)`
也可参考`kaffe/cusom_layers`下的其它自定义层实现
2. 添加`import mylayer``kaffe/custom_layers/__init__.py`
3. 准备你的pycaffe作为你的定制版本(与以前的env准备相同)
> 选择一:
1. 编译你自己的caffe.proto来代替proto/caffe.proto
2. 修改./kaffe/caffe/resolver.py
```python
try:
# Try to import PyCaffe first
import caffe
self.caffe = caffe
except ImportError:
# Fall back to the protobuf implementation
self.caffepb = import_caffepb()
show_fallback_warning()
# 将上述代码替换为下列代码:
self.caffepb = import_caffepb()
show_fallback_warning()
```
> 选择二:更换你的pycaffe到特定的版本
4. 按照之前步骤,将Caffe模型转换为PaddlePaddle模型
5. 配置环境变量
```
export CAFFE2FLUID_CUSTOM_LAYERS=/path/to/caffe2fluid/kaffe
```
## 模型测试
caffe2fluid在如下模型上通过测试
- [Lenet](https://github.com/ethereon/caffe-tensorflow/blob/master/examples/mnist)
- [ResNet(ResNet-50,ResNet-101,ResNet-152)](https://onedrive.live.com/?authkey=%21AAFW2-FVoxeVRck&id=4006CBB8476FF777%2117887&cid=4006CBB8476FF777)
- [GoogleNet](https://gist.github.com/jimmie33/7ea9f8ac0da259866b854460f4526034)
- [VGG](https://gist.github.com/ksimonyan/211839e770f7b538e2d8)
- [AlexNet](https://github.com/BVLC/caffe/tree/master/models/bvlc_alexnet)
# caffe2fluid
[![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE)
This tool is used to convert a Caffe model to a Fluid model. In the [[doc](doc/ReadMe.md)] directory, the common APIs of Caffe-PaddlePaddle are compared and analyzed.
## Prerequisites
> python >= 2.7
> numpy
> protobuf >= 3.6.0
> future
**The running process of caffe2fluid only relies on above conditions.**
It is recommended to install the Caffe and PaddlePaddle in the environment for testing after converting the model. For environmental installation, please refer to [Installation Documentation](prepare_en.md)
## HowTo
### Model Conversion
1. Convert the Caffe's model to the PaddlePaddle's model code and parameter file (The parameters are saved as the form of numpy).
```
# --def_path : The path of Caffe's configuration file
# --caffemodel : The save path of Caffe's model file
# --data-output-path : The save path of the model after converting
# --code-output-path : The save path of the model code after converting
python convert.py --def_path alexnet.prototxt \
--caffemodel alexnet.caffemodel \
--data-output-path alexnet.npy \
--code-output-path alexnet.py
```
2. The model network structure and parameters can be serialized as the model format supported by the PaddlePaddle framework.
```
# --model-param-path : The save path of PaddlePaddle's serialized model
python alexnet.py --npy_path alexnet.npy --model-param-path ./fluid_model
```
Or you can specify the output of the saved model when saving.
```
# The output of model is the fc8 layer and prob layer.
python alexnet.py --npy_path alexnet.npy --model-param-path ./fluid --need-layers-name fc8,prob
```
Model loading and prediction can refer to the [official PaddlePaddle document](http://www.paddlepaddle.org/documentation/docs/en/1.3/api_guides/low_level/inference_en.html).
### Comparison of differences before and after model conversion
After the model is converted, the difference between the converted model and the original model can be compared layer by layer (**the running environment depends on caffe and paddlepaddle**)
```
# alexnet : The value of "name" in the Caffe's configuration file (.prototxt)
# ../../alexnet.prototxt : The path of Caffe's configuration file
# ../../alexnet.caffemodel : The save path of Caffe's model file
# ../../alexnet.py : The save path of the model after converting
# ../../alexnet.npy : The save path of the model code after converting
# ./data/65.jpeg : The path of image which is need to reference
cd examples/imagenet
bash tools/diff.sh alexnet ../../alexnet.prototxt \
../../alexnet.caffemodel \
../../alexnet.py \
../../alexnet.npy \
./data/65.jpeg
```
## How to convert custom layer
In the model conversion, when encounter an unsupported custom layer, users can add code to achieve a custom layer according to their needs. thus supporting the complete conversion of the model. The implementation is the following process.
1. Implement your custom layer in a file under `kaffe/custom_layers`, eg: mylayer.py
- Implement ```shape_func(input_shape, [other_caffe_params])``` to calculate the output shape
- Implement ```layer_func(inputs, name, [other_caffe_params])``` to construct a fluid layer
- Register these two functions ```register(kind='MyType', shape=shape_func, layer=layer_func)```
- Notes: more examples can be found in `kaffe/custom_layers`
2. Add ```import mylayer``` to `kaffe/custom_layers/__init__.py`
3. Prepare your pycaffe as your customized version(same as previous env prepare)
- (option1)
1. replace `proto/caffe.proto` with your own caffe.proto and compile it
2. modify the ./kaffe/caffe/resolver.py
```python
try:
# Try to import PyCaffe first
import caffe
self.caffe = caffe
except ImportError:
# Fall back to the protobuf implementation
self.caffepb = import_caffepb()
show_fallback_warning()
# replace the above code with:
self.caffepb = import_caffepb()
show_fallback_warning()
```
- (option2) change your `pycaffe` to the customized version
4. Convert the Caffe model to Fluid model
5. Set env $CAFFE2FLUID_CUSTOM_LAYERS to the parent directory of 'custom_layers'
```
export CAFFE2FLUID_CUSTOM_LAYERS=/path/to/caffe2fluid/kaffe
```
### Tested models
The caffe2fluid passed the test on the following model:
- Lenet:
[model addr](https://github.com/ethereon/caffe-tensorflow/blob/master/examples/mnist)
- ResNets:(ResNet-50, ResNet-101, ResNet-152)
[model addr](https://onedrive.live.com/?authkey=%21AAFW2-FVoxeVRck&id=4006CBB8476FF777%2117887&cid=4006CBB8476FF777)
- GoogleNet:
[model addr](https://gist.github.com/jimmie33/7ea9f8ac0da259866b854460f4526034)
- VGG:
[model addr](https://gist.github.com/ksimonyan/211839e770f7b538e2d8)
- AlexNet:
[model addr](https://github.com/BVLC/caffe/tree/master/models/bvlc_alexnet)
### Notes
Some of this code come from here: [caffe-tensorflow](https://github.com/ethereon/caffe-tensorflow)
#!/usr/bin/env python
import os
import sys
import numpy as np
import argparse
from kaffe import KaffeError, print_stderr
from kaffe.paddle import Transformer
def fatal_error(msg):
""" fatal error encounted
"""
print_stderr(msg)
exit(-1)
def validate_arguments(args):
""" validate args
"""
if (args.data_output_path is not None) and (args.caffemodel is None):
fatal_error('No input data path provided.')
if (args.caffemodel is not None) and (args.data_output_path is None):
fatal_error('No output data path provided.')
if (args.code_output_path is None) and (args.data_output_path is None):
fatal_error('No output path specified.')
def convert(def_path, caffemodel_path, data_output_path, code_output_path,
phase):
""" convert caffe model to tf/paddle models
"""
try:
transformer = Transformer(def_path, caffemodel_path, phase=phase)
print_stderr('Converting data...')
if caffemodel_path is not None:
data = transformer.transform_data()
print_stderr('Saving data...')
with open(data_output_path, 'wb') as data_out:
np.save(data_out, data)
if code_output_path:
print_stderr('Saving source...')
s = sys.version
with open(code_output_path, 'wb') as src_out:
if s.startswith('2'):
src_out.write(transformer.transform_source())
else:
src_out.write(str.encode(transformer.transform_source()))
print_stderr('set env variable before using converted model '\
'if used custom_layers:')
custom_pk_path = os.path.dirname(os.path.abspath(__file__))
custom_pk_path = os.path.join(custom_pk_path, 'kaffe')
print_stderr('export CAFFE2FLUID_CUSTOM_LAYERS=%s' % (custom_pk_path))
print_stderr('Done.')
return 0
except KaffeError as err:
fatal_error('Error encountered: {}'.format(err))
return 1
def main():
""" main
"""
parser = argparse.ArgumentParser()
parser.add_argument('--def_path', help='Model definition (.prototxt) path')
parser.add_argument('--caffemodel', help='Model data (.caffemodel) path')
parser.add_argument('--data-output-path', help='Converted data output path')
parser.add_argument(
'--code-output-path', help='Save generated source to this path')
parser.add_argument(
'-p',
'--phase',
default='test',
help='The phase to convert: test (default) or train')
args = parser.parse_args()
validate_arguments(args)
return convert(args.def_path, args.caffemodel, args.data_output_path,
args.code_output_path, args.phase)
if __name__ == '__main__':
ret = main()
sys.exit(ret)
## Accuracy
### [Accuracy](http://caffe.berkeleyvision.org/tutorial/layers/accuracy.html)
```
layer {
name: "accuracy"
type: "Accuracy"
bottom: "input"
bottom: "label"
top: "accuracy"
include {
phase: TEST
}
}
```
### [paddle.fluid.layers.accuracy](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#accuracy)
```python
paddle.fluid.layers.accuracy(
input,
label,
k=1,
correct=None,
total=None
)
```
### 功能差异
#### 计算机制
Caffe:只能计算每个类别中top1中正确预测的个数;
PaddlePaddle:可以通过设置`k`来计算每个类别中top k 中正确预测的个数。
## ArgMax
### [ArgMax](http://caffe.berkeleyvision.org/tutorial/layers/argmax.html)
```
layer {
name: "argmax"
type: "ArgMax"
bottom: "data"
top: "argmax"
argmax_param {
out_max_val: false
top_k: 1
axis: 0
}
}
```
### [paddle.fluid.layers.argmax](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-214-argmax)
```python
paddle.fluid.layers.argmax(
x,
axis=0
)
```
### 功能差异
#### 计算机制
Caffe:可通过`top_k``out_max_val`参数设置得到前`k`的索引或数值;
PaddlePaddle:只能输出最大值的索引;
## BatchNorm
### [BatchNorm](http://caffe.berkeleyvision.org/tutorial/layers/batchnorm.html)
```
layer {
name: "bn"
type: "BatchNorm"
bottom: "data"
top: "bn"
batch_norm_param {
use_global_stats: true
moving_average_fraction: 0.999
eps: 0.00001
}
}
```
### [paddle.fluid.layers.batch_norm](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-36-batch_norm)
```python
paddle.fluid.layers.batch_norm(
input,
act=None,
is_test=False,
momentum=0.9,
epsilon=1e-05,
param_attr=None,
bias_attr=None,
data_layout='NCHW',
in_place=False,
name=None,
moving_mean_name=None,
moving_variance_name=None,
do_model_average_for_mean_and_var=False,
fuse_with_relu=False,
use_global_stats=False
)
```
### 功能差异
#### 计算机制
Caffe:`BatchNorm`仅做了归一化计算,需结合`Scale`层进行缩放变换;
PaddlePaddle:包括归一化计算和缩放变换,`param_attr``bias_attr`即为缩放变换的设置参数。
## Convolution
### [Convolution](http://caffe.berkeleyvision.org/tutorial/layers/convolution.html)
```
layer {
name: "conv"
type: "Convolution"
bottom: "data"
top: "conv"
# 卷积核的局部学习率和权值衰减因子
param {
lr_mult: 1
decay_mult: 1
}
# 偏置项的局部学习率和权值衰减因子
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 20 # 必填项
kernel_size: 5 # 必填项
stride: 1
pad: 0
group: 1
bias_term: True
weight_filler {
type: "gaussian"
value: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
```
### [paddle.fluid.layers.conv2d](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-45-conv2d)
```python
paddle.fluid.layers.conv2d(
input,
num_filters,
output_size,
stride=1,
padding=0,
dilation=1,
groups=None,
param_attr=None,
bias_attr=None,
use_cudnn=True,
act=None,
name=None
)
```
### 功能差异
#### 参数初始化
Caffe:Layer定义中共有两个结构体`param`用于设置局部学习率和权值衰减因子,其中第一个用于设置卷积核,第二个则用于设置偏值项;卷积核和偏置项的初始化参数在`convolution_param`中进行设置;是否使用偏置项可以使用`bias_term`进行设置;
PaddlePaddle:卷积核和偏置项的参数分别使用`param_attr``bias_attr`进行配置,配置参数如下所示,此外将`bias_attr`直接设为`False`表示不使用偏置项。
```python
paddle.fluid.ParamAttr(
name=None,
initializer=None,
learning_rate=1.0,
regularizer=None,
trainable=True,
gradient_clip=None,
do_model_average=False
)
```
#### 空洞卷积
Caffe:无法使用空洞卷积;
PaddlePaddle:使用`dilation`参数来设置空洞卷积。
## Crop
### [Crop](http://caffe.berkeleyvision.org/tutorial/layers/crop.html)
```
layer {
name: "crop"
type: "Crop"
bottom: "data1"
bottom: "data2"
top: “crop"
crop_param {
axis: 1
offset: 0
offset: 2
}
}
```
### [paddle.fluid.layers.crop](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-51-crop)
```python
paddle.fluid.layers.crop(
x,
shape=None,
offsets=None,
name=None
)
```
### 功能差异
#### 输出大小
Caffe:输入为`data1`,裁剪的输出大小与`data2`(Variable类型)一致;
PaddlePaddle:`shape`参数支持python list的方式传入输出大小,同时也支持`Variable`类型的输入。当`shape``Variable`类型时,用法与Caffe类似,裁剪输出大小与`shape`参数的大小一致。
#### 裁剪偏移量
Caffe:只需要设置需要裁剪的维度的偏移量。
PaddlePaddle:每一个维度需要设置偏移量。
### 代码示例
```
# Caffe示例:
# data1 shape:(20,3,128,128)
# data2 shape:(20,2,64,64)
layer {
name: "crop"
type: "Crop"
bottom: "data1"
bottom: "data2"
top: ”crop"
crop_param {
axis: 1
offset: 0
offset: 25
offset: 25
}
}
# 输出shape:(20,2,64,64)
```
```python
# PaddlePaddle示例:
# inputs1输入shape:(20,3,128,128)
output1 = fluid.layers.crop(x=inputs1, shape=inputs2, offsets=[0,0,25,25])
# 输出shape:(20,2,64,64)
output = fluid.layers.crop(x=inputs1, shape=[20,2,64,64], offsets=[0,0,25,25])
```
## Deconvolution
### [Deconvolution](http://caffe.berkeleyvision.org/tutorial/layers/deconvolution.html)
```
layer {
name: "deconv"
type: "Deconvolution"
bottom: "data"
top: "deconv"
# 卷积核的局部学习率和权值衰减因子
param {
lr_mult: 1
decay_mult: 1
}
# 偏置项的局部学习率和权值衰减因子
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 20 # 必填项
kernel_size: 3 # 必填项
stride: 1
pad: 0
group: 1
bias_term: True
weight_filler {
type: "gaussian"
value: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
```
### [paddle.fluid.layers.conv2d_transpose](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-46-conv2d_transpose)
```python
paddle.fluid.layers.conv2d_transpose(
input,
num_filters,
output_size,
stride=1,
padding=0,
dilation=1,
groups=None,
param_attr=None,
bias_attr=None,
use_cudnn=True,
act=None,
name=None
)
```
### 功能差异
#### 参数初始化
Caffe:Layer定义中共有两个结构体`param`用于设置局部学习率和权值衰减因子,其中第一个用于设置卷积核,第二个则用于设置偏值项;卷积核和偏置项的初始化参数在`convolution_param`中进行设置;是否使用偏置项可以使用`bias_term`进行设置;
PaddlePaddle:卷积核和偏置项的参数分别使用`param_attr``bias_attr`进行配置,配置参数如下所示,此外将`bias_attr`直接设为`False`表示不使用偏置项。
```python
paddle.fluid.ParamAttr(
name=None,
initializer=None,
learning_rate=1.0,
regularizer=None,
trainable=True,
gradient_clip=None,
do_model_average=False
)
```
#### 空洞卷积
Caffe:无法使用空洞卷积;
PaddlePaddle:使用`dilation`参数来设置空洞卷积。
## Dropout
### [Dropout](http://caffe.berkeleyvision.org/tutorial/layers/dropout.html)
```
layer {
name: "dropout"
type: "Dropout"
bottom: "data"
top: “dropout"
dropout_param {
dropout_ratio: 0.5
}
}
```
### [paddle.fluid.layers.dropout](hhttp://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-56-dropout)
```python
paddle.fluid.layers.dropout(
x,
dropout_prob,
is_test=False,
seed=None,
name=None,
dropout_implementation="downgrade_in_infer"
)
```
### 功能差异
#### 实现方式
Caffe:采用`upscale_in_train`方式实现;
PaddlePaddle:实现方式支持`downgrade_in_infer``upscale_in_infer`两种方式。
```
1. downgrade_in_infer实现方式
训练时: out = input * mask
预测时: out = input * dropout_prob* (1.0 - dropout_prob)
2. upscale_in_infer实现方式
训练时: out = input * mask / (1.0 - dropout_prob)
预测时: out = input
```
## Eltwise
### [Eltwise](http://caffe.berkeleyvision.org/tutorial/layers/eltwise.html)
```
layer {
name: "eltwise"
type: "Eltwise"
bottom: "data1"
bottom: "data2"
top: "prod"
eltwise_param {
operation: PROD # 还有MAX,SUM
stable_prod_grad: false
# coeff: 1
# coeff: -1
}
}
```
### [paddle.fluid.layers.elementwise_add](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-61-elementwise_add)
### [paddle.fluid.layers.elementwise_max](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-63-elementwise_max)
### [paddle.fluid.layers.elementwise_mul](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-65-elementwise_mul)
```python
paddle.fluid.layers.elementwise_add(
x,
y,
axis=-1,
act=None,
name=None
)
paddle.fluid.layers.elementwise_max(
x,
y,
axis=-1,
act=None,
name=None
)
paddle.fluid.layers.elementwise_mul(
x,
y,
axis=-1,
act=None,
name=None
)
```
### 功能差异
#### 输入数据
Caffe:`num1``num2``shape`必须按相同;
PaddlePaddle:`Y``shape`可以是`X``shape`可以的一个连续子序列,并通过设置`axis`表示从哪一个维度开始对应。
#### 加法操作的差异
Caffe:可以通过设置`coeff`参数为加法的每个输入添加一个权重;
PaddlePaddle:无权重设置功能。
#### 乘法操作
Caffe:可以通过设置`stable_prod_grad`参数来选择是否渐进较慢的梯度计算方法;
PaddlePaddle:无设置`stable_prod_grad`参数的功能。
#### 其他
Caffe:激活函数需要由另外一层完成;
PaddlePaddle:可以通过设置`act`对逐元素操作后的tensor变量执行非线性激活。
## EuclideanLoss
### [EuclideanLoss](http://caffe.berkeleyvision.org/tutorial/layers/euclideanloss.html)
```
layer {
name: "loss"
type: "EuclideanLoss"
bottom: "input"
bottom: "label"
top: "loss"
}
```
### [paddle.fluid.layers.square_error_cost](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-173-square_error_cost)
```python
paddle.fluid.layers.square_error_cost(
input,
label
)
```
### 功能差异
#### 实现方式
Caffe:对整个输入的欧氏距离进行取和后除以两倍的样本个数,最终获得一个标量数值。
PaddlePaddle:使用elemenwise方式,计算`input``label`对应元素的欧式距离,最终获得一个array(输入和输出`shape`一致):
### 代码示例
```python
# 利用PaddlePaddle实现Caffe的EuclideanLoss
def EuclideanLoss(inputs, label):
elw_eud = fluid.layers.square_error_cost(data, label)
eud = fluid.layers.reduce_mean(elw_eud)
eud = fluid.layers.scale(eud, scale=0.5)
return eud
# 调用函数计算欧氏路离
# inputs: [1, 2, 4, 5, 6]
# labels: [6, 5, 4, 3, 2]
# eud: 5.4
inputs = fluid.layers.data(dtype='float32', shape=[5], name='data')
labels = fluid.layers.data(dtype='float32', shape=[5], name='label')
eud = EulideanLoss(inputs, labels)
```
## Exp
### [Exp](http://caffe.berkeleyvision.org/tutorial/layers/exp.html)
```
layer {
name: "exp"
type: "Exp"
bottom: "data"
top: "exp"
exp_param {
base: -1
scale: 1
shift: 0
}
}
```
### [paddle.fluid.layers.exp](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-196-exp)
```python
paddle.fluid.layers.exp(
x,
name=None
)
```
### 功能差异
#### 计算机制
Caffe:有三个关于计算的参数,其计算公式为:
$$
y=\begin{cases}
e^{(shift+scale \times x)},\quad x\leq 0 \\\\
base^{(shift+scale \times x)},\quad x>0
\end{cases}
$$
PaddlePaddle:计算公式为:$$y=e^x$$
## Flatten
### [Flatten](http://caffe.berkeleyvision.org/tutorial/layers/flatten.html)
```
layer {
name: "flatten"
type: "Flatten"
bottom: "data"
top: "flatten"
flatten_param {
axis: 1
end_axis: -1
}
}
```
### [paddle.fluid.layers.reshape](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-134-reshape)
```python
paddle.fluid.layers.reshape(
x,
shape,
actual_shape=None,
act=None,
inplace=False,
name=None
)
```
### 功能差异
#### 输入参数
Caffe:分别使用参数`axis``end_axis`表示起始轴和结束轴,[axis, end_axis]轴上的数据将被压缩至一维,
但如若`axis-end_axis==1`时,则会在`axis`轴之后插入一维;
> 输入数据shape[2, 3, 4, 5]
> axis=1, end_axis=3:输出shape[2, 60]
> axis=3, end_axis=2:输出shape[2, 3, 4, 1, 5]
PaddlePaddle:通过在`shape`参数设置具体的输出shape。
## InnerProduct
### [InnerProduct](http://caffe.berkeleyvision.org/tutorial/layers/innerproduct.html)
```
layer {
name: "fc"
type: "InnerProduct"
bottom: "data"
top: "fc"
# 卷积核的局部学习率和权值衰减因子
param {
lr_mult: 1
decay_mult: 1
}
# 偏置项的局部学习率和权值衰减因子
param {
lr_mult: 2
decay_mult: 0
}
inner_product_param {
num_output: 20 # 必填项
bias_term: True
weight_filler {
type: "gaussian"
value: 0.01
}
bias_filler {
type: "constant"
value: 0
}
}
}
```
### [paddle.fluid.layers.fc](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-71-fc)
```python
paddle.fluid.layers.fc(
input,
size,
num_flatten_dims=1,
param_attr=None,
bias_attr=None,
act=None,
is_test=False,
name=None
)
```
### 功能差异
#### 参数初始化
Caffe:Layer定义中共有两个结构体`param`用于设置局部学习率和权值衰减因子,其中第一个用于设置权重,第二个则用于设置偏值项;权重和偏置项的初始化参数在`InnerProduct`中进行设置;是否使用偏置项可以使用`bias_term`进行设置;
PaddlePaddle:权重和偏置项的参数分别使用`param_attr``bias_attr`进行配置,配置参数如下所示,此外将`bias_attr`直接设为`False`表示不使用偏置项。
```python
paddle.fluid.ParamAttr(
name=None,
initializer=None,
learning_rate=1.0,
regularizer=None,
trainable=True,
gradient_clip=None,
do_model_average=False
)
```
#### 多维输入
Caffe:将输入数据的第一维默认为batch size,其余维度压缩至一维后,得到新的二维输入进行全连接计算;
PaddlePaddle:`[0, num_flatten_dims)``[num_flattens_dim, )`维上的数据分别被压缩至一维,得到新的二维输入进行全连接计算。
#### 其他
Caffe:需要在另一个层中定义激活函数。
PaddlePaddle:可以通过设置`act`这一参数来确定输出的激活函数。
## Input
### [Input](http://caffe.berkeleyvision.org/tutorial/layers/input.html)
```
layer {
name: "input"
type: "Input"
top: "input"
input_param {
shape {
dim: 10
dim: 3
dim: 227
dim: 227
}
}
}
```
### [paddle.fluid.layers.data](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-20-data)
```python
paddle.fluid.layers.data(
name,
shape,
append_batch_size=True,
dtype='float32',
lod_level=0,
type=VarType.LOD_TENSOR,
stop_gradient=True
)
```
### 功能差异
#### 输入shape的差异
Caffe:输入的shape中每一个维度的大小都需要详细定义。
PaddlePaddle:可以根据设置设置`append_batch_size`来确定是否将数据第一个维度的大小加入到shape中,若该参数为True,输入数据第一个维度的大小则由传入数据决定,若该参数为False,则shape的第一个维度为输入数据第一个维度的大小。
#### 其他差异
Caffe:不需要强制定义输入数据的类型。
PaddlePaddle:需要强制定义输入数据的类型,同时可以通过设置`lod_level`表示输入的数据是不是一个序列,设置`stop_gradient`表示是否应该停止计算梯度。
### 代码示例
```
# Caffe示例:
layer {
name: "input"
type: "Input"
top: "input"
input_param {
shape {
dim: 10
dim: 3
dim: 227
dim: 227
}
}
}
# 数据shape为[10,3,227,227]
```
``` python
# PaddlePaddle示例:
# 数据shape为[10,3,227,227]
inputs1 = paddle.fluid.layers.data(name='data1', shape=[10,3,227,227],
dtype='float32', append_batch_size=False)
# 数据shape为[-1,3,227,227]
inputs2 = paddle.fluid.layers.data(name='data2', shape=[3,227,227], dtype='float32')
```
## LRN
### [LRN](http://caffe.berkeleyvision.org/tutorial/layers/lrn.html)
```
layer {
name: "lrn"
type: "LRN"
bottom: "data"
top: "lrn"
lrn_param {
local_size: 5
alpha: 1
beta: 5
norm_region: "ACROSS_CHANNELS"
}
}
```
### [paddle.fluid.layers.lrn](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-101-lrn)
```python
paddle.fluid.layers.lrn(
input,
n=5,
k=1.0,
alpha=0.0001,
beta=0.75,
name=None
)
```
### 功能差异
#### 参数差异
Caffe:参数`norm_region`支持`ACROSS_CHANNELS``WITHIN_CHANNEL`两种模式;
PaddlePaddle:默认且仅支持`ACROSS_CHANNELS`模式。
#### 计算机制
Caffe:在`ACROSS_CHANNELS`模式下,计算公式如下,公式中的$n$即为参数`local_size`
$$output(i,x,y)=input(i,x,y)/(1+\frac{\alpha}{n}\sum_{j=max(0,i-\frac{n}{2})}^{min(C,i+\frac{n}{2})}{input(j,x,y)^2})^\beta$$
PaddlePaddle:计算公式如下,
$$output(i,x,y)=input(i,x,y)/(k+\alpha\sum_{j=max(0,i-\frac{n}{2})}^{min(C,i+\frac{n}{2})}{input(j,x,y)^2})^\beta$$
## Log
### [Log](http://caffe.berkeleyvision.org/tutorial/layers/log.html)
```
layer {
name: "log"
type: "Log"
bottom: "data"
top: "log"
log_param {
base: -1
scale: 1
shift: 0
}
}
```
### [paddle.fluid.layers.log](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-95-log)
```python
paddle.fluid.layers.log(
x,
name=None
)
```
### 功能差异
#### 计算机制
Caffe:计算公式如下,
$$
y=\begin{cases}
ln(shift+scale \times x),\quad base\leq 0 \\\\
log_{base}(shift+scale \times x),\quad base>0
\end{cases}
$$
PaddlePaddle:计算公式如下,
$$y=ln(x)$$
## Pooling
### [Pooling](http://caffe.berkeleyvision.org/tutorial/layers/pooling.html)
```
layer{
name: "pool"
type: "Pooling"
bottom: "data"
top: "pool"
pooling_param {
pool: MAX
kernel_size: 3 # 必填项
stride: 1
pad: 0
}
}
```
### [paddle.fluid.layers.pool2d](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-119-pool2d)
```python
paddle.fluid.layers.pool2d(
input,
pool_size,
pool_type='max',
pool_stride=1,
pool_padding=0,
global_pooling=False,
use_cudnn=True,
ceil_mode=False,
name=None,
exclusive=True
)
```
### 功能差异
#### 输出大小
Caffe:输出大小计算方式如下所示,
```
H_out = (H_in-ksize[0]+2*padding[0])/strides[0]+1
W_out = (W_in-ksize[1]+2*padding[1])/strides[1]+1
```
PaddlePaddle:`ceil_mode``Ture`时,输出大小计算方式与Caffe一致;当`ceil_mode``False`时,输出大小计算方式如下所示,
```
# ceil_model为False时,计算公式
H_out = (H_in-ksize[0]+2*padding[0]+strides[0]-1)/strides[0]+1
W_out = (W_in-ksize[1]+2*padding[1]+strides[1]-1)/strides[1]+1
```
#### 池化方式
Caffe:通过`pool`参数设置,支持`MAX`, `AVE``STOCHASTIC`三种池化方式;
PaddlePaddle:通过`pool_type`参数设置,支持`max``avg`两种池化方式。
#### 其他
Caffe:无`exclusive`参数;
PaddlePaddle:`exclusive`参数为`True`的情况下,`avg`平均池化过程中会忽略填充值。
### 代码示例
```
# Caffe示例:
# 输入shape:(1,3,228,228)
# 输出shape:(1,3,114,114)
layer{
name: "pool"
type: "Pooling"
bottom: "data"
top: "pool"
pooling_param {
pool: MAX
kernel_size: 3
stride: 2
}
}
```
``` python
# PaddlePaddle示例:
# 输入shape:(1,3,228,228)
# 输出shape:(1,3,113,113)
pool1 = paddle.fluid.layers.pool2d(input = inputs , pool_size = 3,
pool_type = 'max', pool_stride = 2,
ceil_mode=False)
```
## Power
### [Power](http://caffe.berkeleyvision.org/tutorial/layers/power.html)
```
layer {
name: "power"
type: "Power"
bottom: "data"
top: "power"
power_param {
power: 1
scale: 1
shift: 0
}
}
```
### [paddle.fluid.layers.pow](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-121-pow)
```python
paddle.fluid.layers.pow(
x,
factor=1.0,
name=None
)
```
### 功能差异
#### 计算机制
Caffe:计算公式如下所示,
$$y=(shift+scale \times x)^2$$
PaddlePaddle:计算公式如下所示,
$$y=x^{factor}$$
# Caffe-Fluid常用层对应表
本文档梳理了Caffe常用Layer与PaddlePaddle API对应关系和差异分析。根据文档对应关系,有Caffe使用经验的用户,可根据对应关系,快速熟悉PaddlePaddle的接口使用 。
| 序号 | Caffe层 | PaddlePaddle接口 | 备注 |
| ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ |
| 1 | [AbsVal](http://caffe.berkeleyvision.org/tutorial/layers/absval.html) | [fluid.layers.abs](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-189-abs) | 功能一致 |
| 2 | [Accuracy](http://caffe.berkeleyvision.org/tutorial/layers/accuracy.html) | [fluid.layers.accuracy](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-269-accuracy) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Accuracy.md) |
| 3 | [ArgMax](http://caffe.berkeleyvision.org/tutorial/layers/argmax.html) | [fluid.layers.argmax](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-214-argmax) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/ArgMax.md) |
| 4 | [BatchNorm](http://caffe.berkeleyvision.org/tutorial/layers/batchnorm.html) | [fluid.layers.batch_norm](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-36-batch_norm) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/BatchNorm.md) |
| 5 | [BNLL](http://caffe.berkeleyvision.org/tutorial/layers/bnll.html) | [fluid.layers.softplus](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-204-softplus) | 功能一致 |
| 6 | [Concat](http://caffe.berkeleyvision.org/tutorial/layers/concat.html) | [fluid.layers.concat](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-219-concat) | 功能一致 |
| 7 | [Convolution](http://caffe.berkeleyvision.org/tutorial/layers/convolution.html) | [fluid.layers.conv2d](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-45-conv2d) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Convolution.md) |
| 8 | [Crop](http://caffe.berkeleyvision.org/tutorial/layers/crop.html) | [fluid.layers.crop](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-51-crop) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Crop.md) |
| 9 | [Deconvolution](http://caffe.berkeleyvision.org/tutorial/layers/deconvolution.html) | [fluid.layers.conv2d_transpose](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-46-conv2d_transpose) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Deconvolution.md) |
| 10 | [Dropout](http://caffe.berkeleyvision.org/tutorial/layers/dropout.html) | [fluid.layers.dropout](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-56-dropout) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Dropout.md) |
| 11 | [Eltwise](http://caffe.berkeleyvision.org/tutorial/layers/eltwise.html) | 无相应接口 | [Paddle实现方法](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Eltwise.md) |
| 12 | [ELU](http://caffe.berkeleyvision.org/tutorial/layers/elu.html) | [fluid.layers.elu](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-68-elu) | 功能一致 |
| 13 | [EuclideanLoss](http://caffe.berkeleyvision.org/tutorial/layers/euclideanloss.html) | [fluid.layers.square_error_cost](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-173-square_error_cost) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/EuclideanLoss.md) |
| 14 | [Exp](http://caffe.berkeleyvision.org/tutorial/layers/exp.html) | [fluid.layers.exp](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-196-exp) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Exp.md) |
| 15 | [Flatten](http://caffe.berkeleyvision.org/tutorial/layers/flatten.html) | [fluid.layers.reshape](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-134-reshape) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Flatten.md) |
| 16 | [InnerProduct](http://caffe.berkeleyvision.org/tutorial/layers/innerproduct.html) | [fluid.layers.fc](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-71-fc) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/InnerProduct.md) |
| 17 | [Input](http://caffe.berkeleyvision.org/tutorial/layers/input.html) | [fluid.layers.data](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-20-data) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Input.md) |
| 18 | [Log](http://caffe.berkeleyvision.org/tutorial/layers/log.html) | [fluid.layers.log](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-95-log) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Log.md) |
| 19 | [LRN](http://caffe.berkeleyvision.org/tutorial/layers/lrn.html) | [fluid.layers.lrn](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-101-lrn) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/LRN.md) |
| 20 | [Pooling](http://caffe.berkeleyvision.org/tutorial/layers/pooling.html) | [fluid.layers.pool2d](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-119-pool2d) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Pooling.md) |
| 21 | [Power](http://caffe.berkeleyvision.org/tutorial/layers/power.html) | [fluid.layers.pow](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-121-pow) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Power.md) |
| 22 | [PReLU](http://caffe.berkeleyvision.org/tutorial/layers/prelu.html) | [fluid.layers.prelu](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-122-prelu) | 功能一致 |
| 23 | [Reduction](http://caffe.berkeleyvision.org/tutorial/layers/reduction.html) | 无相应接口 | [Paddle实现方法](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Reduction.md) |
| 24 | [ReLU](http://caffe.berkeleyvision.org/tutorial/layers/relu.html) | [fluid.layers.leaky_relu](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-132-relu) | 功能一致 |
| 25 | [Reshape](http://caffe.berkeleyvision.org/tutorial/layers/reshape.html) | [fluid.layers.reshape](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-134-reshape) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Reshape.md) |
| 26 | [SigmoidCrossEntropyLoss](http://caffe.berkeleyvision.org/tutorial/layers/sigmoidcrossentropyloss.html) | [fluid.layers.sigmoid_cross_entropy_with_logits](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-163-sigmoid_cross_entropy_with_logits) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/SigmoidCrossEntropyLoss.md) |
| 27 | [Sigmoid](http://caffe.berkeleyvision.org/tutorial/layers/sigmoid.html) | [fluid.layers.sigmoid](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-202-sigmoid) | 功能一致 |
| 28 | [Slice](http://caffe.berkeleyvision.org/tutorial/layers/slice.html) | [fluid.layers.slice](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-165-slice) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Slice.md) |
| 29 | [SoftmaxWithLoss](http://caffe.berkeleyvision.org/tutorial/layers/softmaxwithloss.html) | [fluid.layers.softmax_with_cross_entropy](http://paddlepaddle.org/documentation/docs/zh/1.3/api_cn/layers_cn.html#permalink-164-softmax_with_cross_entropy) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/SofmaxWithLoss.md) |
| 30 | [Softmax](http://caffe.berkeleyvision.org/tutorial/layers/softmax.html) | [fluid.layers.softmax](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-169-softmax_with_cross_entropy) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Sofmax.md) |
| 31 | [TanH](http://caffe.berkeleyvision.org/tutorial/layers/tanh.html) | [fluid.layers.tanh](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-209-tanh) | 功能一致 |
| 32 | [Tile](http://caffe.berkeleyvision.org/tutorial/layers/tile.html) | [fluid.layers.expand](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-70-expand) | [差异对比](https://github.com/PaddlePaddle/X2Paddle/blob/master/caffe2fluid/doc/Tile.md) |
## Reduction
### [Reduction](http://caffe.berkeleyvision.org/tutorial/layers/reshape.html)
```
layer {
name: "reduce"
type: "Reduction"
bottom: "reduce"
top: “reduce"
reduction_param {
operation: SUM
axis: 1
coeff: 2
}
}
```
### [paddle.fluid.layers.reduce_sum](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-131-reduce_sum)
### [paddle.fluid.layers.reduce_mean](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-128-reduce_mean)
```python
paddle.fluid.layers.reduce_sum(
input,
dim=None,
keep_dim=False,
name=None
)
```
```python
paddle.fluid.layers.reduce_mean(
input,
dim=None,
keep_dim=False,
name=None
)
```
### 功能差异
#### 操作类型
Caffe:通过`operation`参数支持`SUM``ASUM``SUMSQ``MEAN`四种操作;
PaddlePaddle:`reduce_sum``reduce_mean`分别对应Caffe的`SUM``MEAN`操作,另外两种无对应。
#### 计算方式
Caffe:`axis``int`型参数,该维及其后维度,均会被降维,且不保留对应部分的维度,如shape为`(30, 3, 6, 8)``axis`为2的情况下,得到的输出shape为`(30, 3)`
PaddlePaddle:`dim`参数为`list`型参数,其指定的维度才会被降维,且当`keep_dim``True`时,降维的维度仍会以`1`的形式保留下来,如shape为`(30, 3, 6, 8)``dim``[2, 3]``keep_dim``True`的情况下,得到的输出shape为`(30, 3, 1, 1)`
### 代码示例
```
# Caffe示例:
# 输入shape:(30,3,6,8)
layer {
name: "reduce"
type: "Reduction"
bottom: "reduce"
top: “reduce"
reduction_param {
operation: SUM
axis: 2
coeff: 2
}
}
# 输出shape:(30,3,)
```
```python
# PaddlePaddle示例:
# 输入shape:(30,3,6,8)
output1 = fluid.layers.reduce_mean(input = inputs, dim=[1])
# 输出shape:(30,6,8)
output2 = fluid.layers.reduce_mean(input = inputs, dim=[1], keep_dim=True, name=None)
# 输出shape:(30,1,6,8)
```
## Reshape
### [Reshape](http://caffe.berkeleyvision.org/tutorial/layers/reshape.html)
```
layer {
name: "reshape"
type: "Reshape"
bottom: "data"
top: "reshape"
reshape_param {
shape{
dim: 1
...
}
axis: 0
num_axes: -1
}
}
```
### [paddle.fluid.layers.reshape](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-134-reshape)
```python
paddle.fluid.layers.reshape(
x,
shape,
actual_shape=None,
act=None,
inplace=False,
name=None
)
```
### 功能差异
#### reshape机制的差异
Caffe:使用0和-1分别代表复制的维度数和推断的维度数,但使用了`axis``num_axes`定义了其他的使用方法。当单独使用`axis`时,表示输出数据的前`axis`个维度由原始输入数据的前`axis`个维度复制而来,而`shape`里的维度信息则添加在这几个维度之后;当同时使用`axis``num_axes`两个参数时,表示`shape`中的第`1`个维度至第`1+num_axes`维度定义为输出中的第`axis+1``axis+num_axes+1`个维度,其余维度的维度数由原始输入数据的维度数代替,直至输出数据和输入数据摊平成一维时大小相同。
PaddlePaddle:使用0和1分别代表复制的维度数和推断的维度数。
#### 输出的差异
Caffe:Reshape层在不改变数据的情况下改变输入blob的维度,处理过程只在输入blob上进行,没有进行数据的拷贝。
PaddlePaddle:可以通过设置`inplace`表示是否对数据进行拷贝。
#### 其他差异
Caffe:激活函数需要由另外一层完成。
PaddlePaddle:可以通过设置`act`对reshpe后的tensor变量执行非线性激活。
### 代码示例
```
# Caffe示例:
# 输入shape:(2,4,6)
layer {
name: "reshape"
type: "Reshape"
bottom: "data"
top: "reshape"
reshape_param {
shape {
dim: 3
dim: 2
}
axis: 2
num_axes: 1
}
}
# 输出shape:(2,4,3,2)
layer {
name: "reshape"
type: "Reshape"
bottom: "data"
top: "reshape"
reshape_param {
shape {
dim: 3
dim: 2
dim: 4
}
axis: 1
}
}
# 输出shape:(2,3,2,4)
```
```python
# PaddlePaddle示例:
# 输入shape:(2,4,6)
output1 = paddle.fluid.layers.reshape(x = inputs , shape = [2,4,-1,3])
# 输出shape:(2,4,2,3)
output2 = paddle.fluid.layers.reshape(x = inputs , axis = [0,2,2,6])
# 输出shape:(2,2,2,6)
```
## SigmoidCrossEntropyLoss
### [SigmoidCrossEntropyLoss](http://caffe.berkeleyvision.org/tutorial/layers/sigmoidcrossentropyloss.html)
```
layer {
name: "loss"
type: "SigmoidCrossEntropyLoss"
bottom: "x"
bottom: "label"
top: "loss"
}
```
### [paddle.fluid.layers.sigmoid_cross_entropy_with_logits](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-163-sigmoid_cross_entropy_with_logits)
```python
paddle.fluid.layers.sigmoid_cross_entropy_with_logits(
x,
label,
ignore_index=-100,
name=None,
normalize=False
)
```
### 功能差异
#### 输入数据
Caffe:输入数据(`x`)的维度最大是4维(`N*C*H*W`);
PaddlePaddle:输入数据(`x``label`)的维度只能是2维(`N*K`)。
#### 输出结果
Caffe:输出的数据大小是`1*1*1*1`,即将所有位置上的loss取均值;
PaddlePaddle:输出和输入大小一致,即`N*H`
#### 其他差异
Caffe:无`ignore_index``normalize`参数;
PaddlePaddle:可以通过设定`ignore_index`来确定忽略的目标值,同时它有一个`normalize`参数进行归一化。
## Slice
### [Slice](http://caffe.berkeleyvision.org/tutorial/layers/slice.html)
```
layer {
name: "slice"
type: "Slice"
bottom: "data"
top: "out1"
top: "out2"
top: "out3"
slice_param {
axis: 1
alice_point: 1
alice_point: 2
}
}
```
### [paddle.fluid.layers.slice](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-165-slice)
```python
paddle.fluid.layers.slice(
input,
axes,
starts,
ends
)
```
### 功能差异
#### 输入参数
Caffe:输入的`axis``alice_point`等参数都是数值。
PaddlePaddle:输入的`axes``starts``ends`等输入参数都是list类型。
#### slice机制
Caffe:只能在一个维度上截取,但可以截取多个切片。
PaddlePaddle:可以在多个维度上截取,但只能截取到一个切片。
#### 其他差异
PaddlePaddle:如果传递给`starts``end`的值大于n(此维度中的元素数目),则表示n。
### 代码示例
```
# Caffe示例:
# 输入shape:(2,6)
layer {
name: "slice"
type: "Slice"
bottom: "data"
top: "out1"
top: "out2"
top: "out3"
slice_param {
axis: 1 # 使用-1效果相同
slice_point: 1
slice_point: 2
}
}
# 输出3个数组,第一个shape:(2,1),第二个shape:(2,1),第三个shape:(2,4)
```
```python
# PaddlePaddle示例:
# 输入shape:(2,6)
output1 = paddle.fluid.layers.slice(input=inputs, axes=[1], starts=[1], ends=[3])
# 输出shape:(2,2)
output2 = paddle.fluid.layers.slice(input=inputs, axes=[0,1], starts=[0,1], ends=[1,3])
# 输出shape:(1,2)
```
## Sofmax
### [Softmax](http://caffe.berkeleyvision.org/tutorial/layers/softmax.html)
```
layer {
name: "softmax"
type: "Softmax"
bottom: "fc"
top: "softmax"
}
```
### [paddle.fluid.layers.softmax](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-168-softmax)
```python
paddle.fluid.layers.softmax(
input,
use_cudnn=False,
name=None,
axis=-1
)
```
### 功能差异
#### 计算机制
Caffe:计算softmax之前,对每个样本中的每个值减去该样本中的最大值;
PaddlePaddle:省略了这一操作直接计算softmax。
#### 使用机制
PaddlePaddle:通过设置`axis`来确定执行softmax的维度索引。
## SofmaxWithLoss
### [SofmaxWithLoss](http://caffe.berkeleyvision.org/tutorial/layers/softmaxwithloss.html)
```
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "logits"
bottom: "label"
top: "loss"
softmax_param {
axis: 1
}
loss_param {
ignore_label: -1
normalize: 0
normalization: FULL
}
}
```
### [paddle.fluid.layers.softmax_with_cross_entropy](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-169-softmax_with_cross_entropy)
```python
paddle.fluid.layers.softmax_with_cross_entropy(
logits,
label,
soft_label=False,
ignore_index=-100,
numeric_stable_mode=True,
return_softmax=False
)
```
### 功能差异
#### 输入数据
Caffe:输入数据(`x`)的维度最大是4维(`N*C*H*W`);
PaddlePaddle:输入数据(`x``label`)的维度只能是2维(`N*K`)。
#### 输入格式
Caffe: 采用硬标签方式输入,同时进行预处理操作(为了避免上溢出和下溢出,对输入的每个值减去batch中该位置上的最大值);
PaddlePaddle:通过参数`soft_label`的设定,支持硬标签和软标签两种输入。
> 计算softmax的loss时,根据每个样本是否被分配至多个类别中可以分为两类——硬标签和软标签
> **硬标签:** 即one-hot label,每个样本仅分到一个类别中。在硬标签中,根据是否对未初始化的log概率进行预处理,又可以分为两类,预处理主要是完成对每个样本中的每个log概率减去该样本中的最大的log概率
> **软标签:** 每个样本至少被分配到一个类别中
#### 输出结果
Caffe:输出是对所有样本的loss进行归一化后的结果,归一化的方式由`normalization``normalize`参数决定;
```
归一化形式:
1. 当`normalization`是FULL或0时,整个loss取和后除以batch的大小.
2. 当`normalization`是VALID或1时,整个loss取和后除以除`ignore_label`以外的样本数。
3. 当`normalization`是NONE时,则loss取和.
4. 当`normalization`未设置时,采用`normalize`的值进行判断,若`normalize==1`则归一化方式是VALID,若`normalize==0`则归一化方式是FULL。
```
PaddlePaddle:输出是每个样本的loss所组成的一个向量,同时如果将参数`return_softmax`设为True,则输出的是loss向量和softmax值组成的一个元组。
### 代码示例
```
# Caffe示例:
# logits输入shape:(100,10)
# label输入shape:(100,1)
# 输出shape:()
layer {
name: "loss"
type: "SoftmaxWithLoss"
bottom: "logits"
bottom: "label"
top: "loss"
loss_param {
ignore_label: -1
normalize: 0
normalization: FULL
}
}
```
```python
# PaddlePaddle示例:
# logits输入shape:(100,10)
# label输入shape:(100,1)
# 输出shape:(10,1)
softmaxwithloss = fluid.layers.softmax_with_cross_entropy(logits=logs, label=labels,
soft_label=False, ignore_index=-100,
numeric_stable_mode=True,
return_softmax=False)
```
## Tile
### [Tile](http://caffe.berkeleyvision.org/tutorial/layers/tile.html)
```
layer {
name: "tile"
type: "Tile"
bottom: "data"
top: "concat"
tile_param {
axis: 1
tiles: 2
}
}
```
### [paddle.fluid.layers.concat](http://paddlepaddle.org/documentation/docs/zh/1.4/api_cn/layers_cn.html#permalink-219-concat)
```python
paddle.fluid.layers.concat(
x,
expand_times,
name=None
)
```
### 功能差异
#### 输入参数
Caffe:只能在一个维度上进行复制。
PaddlePaddle:`expand_times`为一个list或tuple,它存放的是每个维度复制的倍数。
A demo to show converting caffe models trained on 'imagenet' using caffe2fluid
---
# How to use
1. Prepare python environment
2. Download caffe model to "../../models/xxx" which contains "xxx.caffemodel" and "xxx.prototxt"
3. Convert the Caffe model to Fluid model
- generate fluid code and weight file
```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
```
4. Do inference
```
python infer.py infer ./fluid data/65.jpeg
```
5. convert model and do inference together
```
bash ./tools/run.sh alexnet ../../models ../../models
```
* Assume the Caffe model is stored in '../../models/alexnet.prototxt|caffemodel*'
* converted model will be stored as '../../models/alexnet.py|npy*'
6. test the difference with caffe's results(need pycaffe installed)
```
bash ./tools/diff.sh alexnet ../../models/ ../../models
```
* Make sure your caffemodel stored in '../../models/alexnet.prototxt|caffemodel*'
* The results will be stored in '*./results/alexnet.paddle|caffe*'
#!/usr/bin/python
#
#a tool to compare tensors in two files or two directories
#
import sys
import os
import functools
def walk_dir(rootdir):
for subdir, dirs, files in os.walk(rootdir):
for file in files:
yield file
def calc_diff(f1, f2):
import numpy as np
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.flatten()
d2 = d2.flatten()
d1_num = functools.reduce(lambda x, y: x * y, d1.shape)
d2_num = functools.reduce(lambda x, y: x * y, d2.shape)
if d1_num != d2_num:
print(d1.shape)
print(d2.shape)
assert (d1_num == d2_num), "their shape is not consistent"
try:
mask = np.abs(d1) >= np.abs(d2)
mask = mask.astype('int32')
df = np.abs(d1 - d2)
df = df / (1.0e-10 + np.abs(d1) * mask + np.abs(d2) * (1 - mask))
max_df = np.max(df)
sq_df = np.mean(df * df)
return max_df, sq_df
except Exception as e:
return 1.0, 1.0
def compare(path1, path2, no_exception):
def diff(f1, f2):
max_df, sq_df = calc_diff(f1, f2)
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))
return 1
elif os.path.exists(path2) is False:
print('not found %s' % (path2))
return 1
if path1.find('.npy') > 0 and path2.find('.npy') > 0:
diff(path1, path2)
return
for f in walk_dir(path2):
if f.find('.npy') < 0:
continue
f1 = os.path.join(path1, f)
f2 = os.path.join(path2, f)
diff(f1, f2)
print('all checking succeed to pass')
return 0
if __name__ == "__main__":
if len(sys.argv) == 1:
path1 = 'lenet.tf/results'
path2 = 'lenet.paddle/results'
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, no_exception))
#!/bin/env python
#function:
# a demo to show how to use the converted model genereated by caffe2fluid
#
#notes:
# only support imagenet data
import os
import sys
import inspect
import numpy as np
def import_fluid():
import paddle.fluid as fluid
return fluid
def load_data(imgfile, shape):
h, w = shape[1:]
from PIL import Image
im = Image.open(imgfile)
# The storage order of the loaded image is W(widht),
# H(height), C(channel). PaddlePaddle requires
# the CHW order, so transpose them.
im = im.resize((w, h), Image.ANTIALIAS)
im = np.array(im).astype(np.float32)
im = im.transpose((2, 0, 1)) # CHW
im = im[(2, 1, 0), :, :] # BGR
# The mean to be subtracted from each image.
# By default, the per-channel ImageNet mean.
mean = np.array([104., 117., 124.], dtype=np.float32)
mean = mean.reshape([3, 1, 1])
im = im - mean
return im.reshape([1] + shape)
def build_model(net_file, net_name):
print('build model with net_file[%s] and net_name[%s]' %
(net_file, net_name))
net_path = os.path.dirname(net_file)
module_name = os.path.splitext(os.path.basename(net_file))[0]
if net_path not in sys.path:
sys.path.insert(0, net_path)
try:
m = __import__(module_name, fromlist=[net_name])
MyNet = getattr(m, net_name)
except Exception as e:
print('failed to load module[%s.%s]' % (module_name, net_name))
print(e)
return None
fluid = import_fluid()
inputs_dict = MyNet.input_shapes()
input_name = list(inputs_dict.keys())[0]
input_shape = inputs_dict[input_name]
images = fluid.layers.data(
name=input_name, shape=input_shape, dtype='float32')
#label = fluid.layers.data(name='label', shape=[1], dtype='int64')
net = MyNet({input_name: images})
return net, inputs_dict
def dump_results(results, names, root):
if os.path.exists(root) is False:
os.mkdir(root)
for i in range(len(names)):
n = names[i]
res = results[i]
filename = os.path.join(root, n)
np.save(filename + '.npy', res)
def normalize_name(name_map):
return {
k.replace('/', '_'): v.replace('/', '_')
for k, v in name_map.items()
}
def rename_layer_name(names, net):
""" because the names of output layers from caffe maybe changed for 'INPLACE' operation,
and paddle's layers maybe fused, so we need to re-mapping their relationship for comparing
"""
#build a mapping from paddle's name to caffe's name
trace = getattr(net, 'name_trace', None)
cf_trace = trace['caffe']
real2cf = normalize_name(cf_trace['real2chg'])
pd_trace = trace['paddle']
pd2real = normalize_name(pd_trace['chg2real'])
pd_deleted = normalize_name(pd_trace['deleted'])
pd2cf_name = {}
for pd_name, real_name in pd2real.items():
if real_name in real2cf:
pd2cf_name[pd_name] = '%s.%s.%s.both_changed' \
% (real2cf[real_name], real_name, pd_name)
else:
pd2cf_name[pd_name] = '%s.%s.pd_changed' % (real_name, pd_name)
for pd_name, trace in pd_deleted.items():
assert pd_name not in pd2cf_name, "this name[%s] has already exist" % (
pd_name)
pd2cf_name[pd_name] = '%s.pd_deleted' % (pd_name)
for real_name, cf_name in real2cf.items():
if cf_name not in pd2cf_name:
pd2cf_name[cf_name] = '%s.cf_deleted' % (cf_name)
if real_name not in pd2cf_name:
pd2cf_name[real_name] = '%s.%s.cf_changed' % (cf_name, real_name)
ret = []
for name in names:
new_name = pd2cf_name[name] if name in pd2cf_name else name
print('remap paddle name[%s] to output name[%s]' % (name, new_name))
ret.append(new_name)
return ret
def load_model(exe, place, net_file, net_name, net_weight, debug):
""" load model using xxxnet.py and xxxnet.npy
"""
fluid = import_fluid()
#1, build model
net, input_map = build_model(net_file, net_name)
feed_names = input_map.keys()
feed_shapes = [v for k, v in input_map.items()]
prediction = net.get_output()
#2, load weights for this model
startup_program = fluid.default_startup_program()
exe.run(startup_program)
#place = fluid.CPUPlace()
if net_weight.find('.npy') > 0:
net.load(data_path=net_weight, exe=exe, place=place)
else:
raise ValueError('not found weight file')
#3, test this model
test_program = fluid.default_main_program().clone()
fetch_list_var = []
fetch_list_name = []
if debug is False:
fetch_list_var.append(prediction)
else:
for k, v in net.layers.items():
fetch_list_var.append(v)
fetch_list_name.append(k)
return {
'program': test_program,
'feed_names': feed_names,
'fetch_vars': fetch_list_var,
'fetch_names': fetch_list_name,
'feed_shapes': feed_shapes,
'net': net
}
def get_shape(fluid, program, name):
for var in program.list_vars():
if var.type == 'Input':
return list(var.shape[1:])
raise ValueError('not found shape for input layer[%s], '
'you can specify by yourself' % (name))
def load_inference_model(dirname, exe):
""" load fluid's inference model
"""
fluid = import_fluid()
model_fn = 'model'
params_fn = 'params'
if os.path.exists(os.path.join(dirname, model_fn)) \
and os.path.exists(os.path.join(dirname, params_fn)):
program, feed_names, fetch_targets = fluid.io.load_inference_model(\
dirname, exe, model_fn, params_fn)
else:
raise ValueError('not found model files in direcotry[%s]' % (dirname))
#print fluid.global_scope().find_var(feed_names[0])
input_shape = get_shape(fluid, program, feed_names[0])
feed_shapes = [input_shape]
return program, feed_names, fetch_targets, feed_shapes
def infer(model_path, imgfile, net_file=None, net_name=None, debug=True):
""" do inference using a model which consist 'xxx.py' and 'xxx.npy'
"""
fluid = import_fluid()
place = fluid.CPUPlace()
exe = fluid.Executor(place)
try:
ret = load_inference_model(model_path, exe)
program, feed_names, fetch_targets, feed_shapes = ret
debug = False
print('found a inference model for fluid')
except ValueError as e:
print('try to load model using net file and weight file')
net_weight = model_path
ret = load_model(exe, place, net_file, net_name, net_weight, debug)
program = ret['program']
feed_names = ret['feed_names']
fetch_targets = ret['fetch_vars']
fetch_list_name = ret['fetch_names']
feed_shapes = ret['feed_shapes']
net = ret['net']
input_name = list(feed_names)[0]
input_shape = list(feed_shapes)[0]
np_images = load_data(imgfile, input_shape)
results = exe.run(program=program,
feed={input_name: np_images},
fetch_list=fetch_targets)
if debug is True:
dump_path = 'results.paddle'
dump_names = rename_layer_name(fetch_list_name, net)
dump_results(results, dump_names, dump_path)
print('all result of layers dumped to [%s]' % (dump_path))
else:
result = results[0]
print('succeed infer with results[class:%d]' % (np.argmax(result)))
return 0
def caffe_infer(prototxt, caffemodel, datafile):
""" do inference using pycaffe for debug,
all intermediate results will be dumpped to 'results.caffe'
"""
import caffe
net = caffe.Net(prototxt, caffemodel, caffe.TEST)
input_layer = list(net.blobs.keys())[0]
print('got name of input layer is:%s' % (input_layer))
input_shape = list(net.blobs[input_layer].data.shape[1:])
if '.npy' in datafile:
np_images = np.load(datafile)
else:
np_images = load_data(datafile, input_shape)
inputs = {input_layer: np_images}
net.forward_all(**inputs)
results = []
names = []
for k, v in net.blobs.items():
k = k.replace('/', '_')
names.append(k)
results.append(v.data[0].copy())
dump_path = 'results.caffe'
dump_results(results, names, dump_path)
print('all result of layers dumped to [%s]' % (dump_path))
return 0
if __name__ == "__main__":
""" maybe more convenient to use 'run.sh' to call this tool
"""
net_file = 'models/resnet50/resnet50.py'
weight_file = 'models/resnet50/resnet50.npy'
datafile = 'data/65.jpeg'
net_name = 'ResNet50'
model_file = 'models/resnet50/fluid'
ret = None
if len(sys.argv) <= 2:
pass
elif sys.argv[1] == 'caffe':
if len(sys.argv) != 5:
print('usage:')
print('\tpython %s caffe [prototxt] [caffemodel] [datafile]' %
(sys.argv[0]))
sys.exit(1)
prototxt = sys.argv[2]
caffemodel = sys.argv[3]
datafile = sys.argv[4]
ret = caffe_infer(prototxt, caffemodel, datafile)
elif sys.argv[1] == 'infer':
if len(sys.argv) != 4:
print('usage:')
print('\tpython %s infer [fluid_model] [datafile]' % (sys.argv[0]))
sys.exit(1)
model_path = sys.argv[2]
datafile = sys.argv[3]
ret = infer(model_path, datafile)
elif sys.argv[1] == 'dump':
if len(sys.argv) != 6:
print('usage:')
print('\tpython %s dump [net_file] [weight_file] [datafile] [net_name]' \
% (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)
net_file = sys.argv[2]
weight_file = sys.argv[3]
datafile = sys.argv[4]
net_name = sys.argv[5]
ret = infer(weight_file, datafile, net_file, net_name)
if ret is None:
print('usage:')
print(' python %s [infer] [fluid_model] [imgfile]' % (sys.argv[0]))
print(' eg:python %s infer %s %s' % (sys.argv[0], model_file, datafile))
sys.exit(1)
sys.exit(ret)
#!/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
cmd="python ./compare.py $paddle_file $caffe_file"
echo $cmd
eval $cmd
#!/bin/bash
#function:
# a tool used to compare all layers' results
#
#set -x
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"
cat $prototxt | grep name | perl -ne 'if(/^\s*name\s*:\s+\"([^\"]+)/){ print $1."\n";}' >.layer_names
final_layer=$(cat $prototxt | perl -ne 'if(/^\s*top\s*:\s+\"([^\"]+)/){ print $1."\n";}' | tail -n1)
ret=$(grep "^$final_layer$" .layer_names | wc -l)
if [[ $ret -eq 0 ]];then
echo $final_layer >>.layer_names
fi
for i in $(cat .layer_names);do
i=${i//\//_}
cf_npy="results/${model_name}.caffe/${i}.npy"
#pd_npy="results/${model_name}.paddle/${i}.npy"
#pd_npy=$(find results/${model_name}.paddle -iname "${i}*.npy" | head -n1)
pd_npy=$(find results/${model_name}.paddle -iname "${i}.*npy" | grep deleted -v | head -n1)
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
#!/bin/bash
#function:
# a tool used to:
# 1, convert a caffe model
# 2, do inference(only in fluid) using this model
#
#usage:
# cd caffe2fluid/examples/imagenet && bash run.sh alexnet ./models/alexnet.prototxt ./models/alexnet.caffemodel ./models/alexnet.py ./models/alexnet.npy
#
#set -x
if [[ $# -lt 5 ]];then
echo "usage:"
echo " bash $0 [model_name] [cf_prototxt_path] [cf_model_path] [pd_py_path] [pd_npy_path] [imagfile] [only_convert]"
echo " eg: bash $0 alexnet ./models/alexnet.prototxt ./models/alexnet.caffemodel ./models/alexnet.py ./models/alexnet.npy"
exit 1
else
model_name=$1
cf_prototxt_path=$2
cf_model_path=$3
pd_py_path=$4
pd_npy_path=$5
only_convert=$7
fi
proto_file=$cf_prototxt_path
caffemodel_file=$cf_model_path
weight_file=$pd_npy_path
net_file=$pd_py_path
if [[ ! -e $proto_file ]];then
echo "not found prototxt[$proto_file]"
exit 1
fi
if [[ ! -e $caffemodel_file ]];then
echo "not found caffemodel[$caffemodel_file]"
exit 1
fi
if [[ ! -e $pd_model_path ]];then
mkdir $pd_model_path
fi
PYTHON=`which python`
if [[ -z $PYTHON ]];then
PYTHON=`which python`
fi
$PYTHON ../../convert.py \
--npy_path $proto_file \
--caffemodel $caffemodel_file \
--data-output-path $weight_file\
--code-output-path $net_file
ret=$?
if [[ $ret -ne 0 ]];then
echo "failed to convert caffe model[$cf_model_path]"
exit $ret
else
echo "succeed to convert caffe model[$cf_model_path] to fluid model[$pd_model_path]"
fi
if [[ -z $only_convert ]];then
PYTHON=`which python`
if [[ -z $PYTHON ]];then
PYTHON=`which python`
fi
if [[ -n $6 ]];then
imgfile=$6
else
imgfile="data/65.jpeg"
fi
#FIX ME:
# only look the first line in prototxt file for the name of this network, maybe not correct
net_name=`grep "name" $proto_file | head -n1 | perl -ne 'if(/^name\s*:\s*\"([^\"]+)\"/){ print $1."\n";}'`
if [[ -z $net_name ]];then
net_name="MyNet"
fi
cmd="$PYTHON ./infer.py dump $net_file $weight_file $imgfile $net_name"
echo $cmd
eval $cmd
ret=$?
fi
exit $ret
#!/bin/bash
#function:
# a tool used to:
# 1, convert a caffe model
# 2, do inference(only in fluid) using this model
#
#usage:
# cd caffe2fluid/examples/imagenet && bash run.sh alexnet ./models/alexnet.prototxt ./models/alexnet.caffemodel ./models/alexnet.py ./models/alexnet.npy
#
#set -x
if [[ $# -lt 5 ]];then
echo "usage:"
echo " bash $0 [model_name] [cf_prototxt_path] [cf_model_path] [pd_py_path] [pd_npy_path] [imagfile] [only_convert]"
echo " eg: bash $0 alexnet ./models/alexnet.prototxt ./models/alexnet.caffemodel ./models/alexnet.py ./models/alexnet.npy"
exit 1
else
model_name=$1
cf_prototxt_path=$2
cf_model_path=$3
pd_py_path=$4
pd_npy_path=$5
only_convert=$7
fi
proto_file=$cf_prototxt_path
caffemodel_file=$cf_model_path
weight_file=$pd_npy_path
net_file=$pd_py_path
if [[ ! -e $proto_file ]];then
echo "not found prototxt[$proto_file]"
exit 1
fi
if [[ ! -e $caffemodel_file ]];then
echo "not found caffemodel[$caffemodel_file]"
exit 1
fi
if [[ ! -e $pd_model_path ]];then
mkdir $pd_model_path
fi
PYTHON=`which python`
if [[ -z $PYTHON ]];then
PYTHON=`which python`
fi
$PYTHON ../../convert.py \
$proto_file \
--caffemodel $caffemodel_file \
--data-output-path $weight_file\
--code-output-path $net_file
ret=$?
if [[ $ret -ne 0 ]];then
echo "failed to convert caffe model[$cf_model_path]"
exit $ret
else
echo "succeed to convert caffe model[$cf_model_path] to fluid model[$pd_model_path]"
fi
if [[ -z $only_convert ]];then
PYTHON=`which python`
if [[ -z $PYTHON ]];then
PYTHON=`which python`
fi
if [[ -n $6 ]];then
imgfile=$6
else
imgfile="data/65.jpeg"
fi
#FIX ME:
# only look the first line in prototxt file for the name of this network, maybe not correct
net_name=`grep "name" $proto_file | head -n1 | perl -ne 'if(/^name\s*:\s*\"([^\"]+)\"/){ print $1."\n";}'`
if [[ -z $net_name ]];then
net_name="MyNet"
fi
cmd="$PYTHON ./infer.py dump $net_file $weight_file $imgfile $net_name"
echo $cmd
eval $cmd
ret=$?
fi
exit $ret
#!/bin/bash
#
#script to test all models
#
models="alexnet vgg16 googlenet resnet152 resnet101 resnet50"
for i in $models;do
echo "begin to process $i"
bash ./tools/diff.sh $i 2>&1
echo "finished to process $i with ret[$?]"
done
a demo to show converting caffe model on 'mnist' using caffe2fluid
---
# How to use
1. prepare python environment
2. download caffe model to "models.caffe/lenet" which contains "lenet.caffemodel" and "lenet.prototxt"
3. run the tool
eg: bash ./run.sh lenet ./models.caffe/lenet ./models/lenet
#!/bin/env python
#function:
# demo to show how to use converted model using caffe2fluid
#
import sys
import os
import numpy as np
import paddle.fluid as fluid
import paddle
def test_model(exe, test_program, fetch_list, test_reader, feeder):
acc_set = []
for data in test_reader():
acc_np, pred = exe.run(program=test_program,
feed=feeder.feed(data),
fetch_list=fetch_list)
acc_set.append(float(acc_np))
acc_val = np.array(acc_set).mean()
return float(acc_val)
def evaluate(net_file, model_file):
""" main
"""
#1, build model
net_path = os.path.dirname(net_file)
if net_path not in sys.path:
sys.path.insert(0, net_path)
from lenet import LeNet as MyNet
#1, define network topology
images = fluid.layers.data(name='image', shape=[1, 28, 28], dtype='float32')
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
net = MyNet({'data': images})
prediction = net.layers['prob']
acc = fluid.layers.accuracy(input=prediction, label=label)
place = fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
#2, load weights
if model_file.find('.npy') > 0:
net.load(data_path=model_file, exe=exe, place=place)
else:
net.load(data_path=model_file, exe=exe)
#3, test this model
test_program = fluid.default_main_program().clone()
test_reader = paddle.batch(paddle.dataset.mnist.test(), batch_size=128)
feeder = fluid.DataFeeder(feed_list=[images, label], place=place)
fetch_list = [acc, prediction]
print('go to test model using test set')
acc_val = test_model(exe, test_program, \
fetch_list, test_reader, feeder)
print('test accuracy is [%.4f], expected value[0.919]' % (acc_val))
if __name__ == "__main__":
net_file = 'models/lenet/lenet.py'
weight_file = 'models/lenet/lenet.npy'
argc = len(sys.argv)
if argc == 3:
net_file = sys.argv[1]
weight_file = sys.argv[2]
elif argc > 1:
print('usage:')
print('\tpython %s [net_file] [weight_file]' % (sys.argv[0]))
print('\teg:python %s %s %s %s' % (sys.argv[0], net_file, weight_file))
sys.exit(1)
evaluate(net_file, weight_file)
#!/bin/bash
#function:
# a tool used to:
# 1, convert a caffe model
# 2, do inference using this model
#
#usage:
# bash run.sh lenet ./models.caffe/lenet ./models/lenet
#
#set -x
if [[ $# -lt 3 ]];then
echo "usage:"
echo " bash $0 [model_name] [cf_model_path] [pd_model_path] [only_convert]"
echo " eg: bash $0 lenet ./models.caffe/lenet ./models/lenet"
exit 1
else
model_name=$1
cf_model_path=$2
pd_model_path=$3
no_eval=$4
fi
proto_file=$cf_model_path/${model_name}.prototxt
caffemodel_file=$cf_model_path/${model_name}.caffemodel
weight_file=$pd_model_path/${model_name}.npy
net_file=$pd_model_path/${model_name}.py
if [[ ! -e $proto_file ]];then
echo "not found prototxt[$proto_file]"
exit 1
fi
if [[ ! -e $caffemodel_file ]];then
echo "not found caffemodel[$caffemodel_file]"
exit 1
fi
if [[ ! -e $pd_model_path ]];then
mkdir $pd_model_path
fi
PYTHON=`which cfpython`
if [[ -z $PYTHON ]];then
PYTHON=`which python`
fi
$PYTHON ../../convert.py \
$proto_file \
--caffemodel $caffemodel_file \
--data-output-path $weight_file\
--code-output-path $net_file
ret=$?
if [[ $ret -ne 0 ]];then
echo "failed to convert caffe model[$cf_model_path]"
exit $ret
else
echo "succeed to convert caffe model[$cf_model_path] to fluid model[$pd_model_path]"
fi
if [[ -z $only_convert ]];then
PYTHON=`which pdpython`
if [[ -z $PYTHON ]];then
PYTHON=`which python`
fi
net_name=`grep "name" $proto_file | head -n1 | perl -ne 'if(/\"([^\"]+)\"/){ print $1."\n";}'`
if [[ $net_name != "LeNet" ]];then
echo "only support LeNet"
exit 1
fi
$PYTHON ./evaluate.py $net_file $weight_file
ret=$?
fi
exit $ret
from .graph import GraphBuilder, NodeMapper
from .errors import KaffeError, print_stderr
import os
from . import paddle
from .resolver import get_caffe_resolver, has_pycaffe
import os
import sys
import subprocess
SHARED_CAFFE_RESOLVER = None
def import_caffepb():
p = os.path.realpath(__file__)
p = os.path.dirname(p)
p = os.path.join(p, '../../proto')
sys.path.insert(0, p)
s = sys.version
if s.startswith('2'):
import commands
pb_version = commands.getstatusoutput('protoc --version')[1]
else:
import subprocess
pb_version = subprocess.getstatusoutput('protoc --version')[1]
ver_str = pb_version.split(' ')[-1].replace('.', '')
ver_int = int(ver_str)
assert ver_int >= 360, 'The version of protobuf must be larger than 3.6.0!'
import caffe_pb2
return caffe_pb2
class CaffeResolver(object):
def __init__(self):
self.import_caffe()
def import_caffe(self):
self.caffe = None
try:
# Try to import PyCaffe first
import caffe
self.caffe = caffe
except ImportError:
# Fall back to the protobuf implementation
self.caffepb = import_caffepb()
show_fallback_warning()
if self.caffe:
# Use the protobuf code from the imported distribution.
# This way, Caffe variants with custom layers will work.
self.caffepb = self.caffe.proto.caffe_pb2
self.NetParameter = self.caffepb.NetParameter
def has_pycaffe(self):
return self.caffe is not None
def get_caffe_resolver():
global SHARED_CAFFE_RESOLVER
if SHARED_CAFFE_RESOLVER is None:
SHARED_CAFFE_RESOLVER = CaffeResolver()
return SHARED_CAFFE_RESOLVER
def has_pycaffe():
return get_caffe_resolver().has_pycaffe()
def show_fallback_warning():
msg = '''
------------------------------------------------------------
WARNING: PyCaffe not found!
Falling back to a pure protocol buffer implementation.
* Conversions will be drastically slower.
------------------------------------------------------------
'''
sys.stderr.write(msg)
"""
"""
from .register import get_registered_layers
#custom layer import begins
from . import axpy
from . import flatten
from . import argmax
from . import argmax
from . import reshape
from . import roipooling
from . import priorbox
from . import permute
from . import detection_out
from . import normalize
from . import select
from . import crop
from . import power
from . import reduction
#custom layer import ends
custom_layers = get_registered_layers()
def set_args(f, params, node=None):
""" set args for function 'f' using the parameters in node.layer.parameters
Args:
f (function): a python function object
params (object): a object contains attributes needed by f's arguments
Returns:
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:
if arg_name in params:
kwargs[arg_name] = params[arg_name]
if node is not None and len(node.metadata):
kwargs.update(node.metadata)
return arg_list, kwargs
def has_layer(kind):
""" test whether this layer exists in custom layer
"""
return kind in custom_layers
def compute_output_shape(kind, node):
assert kind in custom_layers, "layer[%s] not exist in custom layers" % (
kind)
shape_func = custom_layers[kind]['shape']
parents = node.parents
inputs = [list(p.output_shape) for p in parents]
arg_names, kwargs = set_args(shape_func, node.params)
if len(inputs) == 1:
inputs = inputs[0]
return shape_func(inputs, **kwargs)
def make_node(template, kind, node):
""" make a PaddleNode for custom layer which means construct
a piece of code to define a layer implemented in 'custom_layers'
Args:
@template (PaddleNode): a factory to new a instance of PaddleNode
@kind (str): type of custom layer
@node (graph.Node): a layer in the net
Returns:
instance of PaddleNode
"""
assert kind in custom_layers, "layer[%s] not exist in custom layers" % (
kind)
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.params, node)
return template('custom_layer', kind, **kwargs)
def make_custom_layer(kind, inputs, name, *args, **kwargs):
""" execute a custom layer which is implemented by users
Args:
@kind (str): type name of this layer
@inputs (vars): variable list created by fluid
@namme (str): name for this layer
@args (tuple): other positional arguments
@kwargs (dict): other kv arguments
Returns:
output (var): output variable for this layer
"""
assert kind in custom_layers, "layer[%s] not exist in custom layers" % (
kind)
layer_func = custom_layers[kind]['layer']
return layer_func(inputs, name, *args, **kwargs)
""" a custom layer for 'argmax', maybe we should implement this in standard way.
more info can be found here: http://caffe.berkeleyvision.org/tutorial/layers/argmax.html
"""
from .register import register
def import_fluid():
import paddle.fluid as fluid
return fluid
def argmax_shape(input_shape, out_max_val=False, top_k=1, axis=-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
@out_max_val (bool): parameter from caffe's ArgMax layer
@top_k (int): parameter from caffe's ArgMax layer
@axis (int): parameter from caffe's ArgMax layer
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
input_shape = list(input_shape)
if axis < 0:
axis += len(input_shape)
assert (axis + 1 == len(input_shape)
), 'only can be applied on the last dimension[axis:%d, %s] now,'\
'make sure you have set axis param in xxx.prototxt file' \
% (axis, str(input_shape))
output_shape = input_shape
output_shape[-1] = top_k
if out_max_val is True:
output_shape[-1] *= 2
return output_shape
def argmax_layer(input, name, out_max_val=False, top_k=1, axis=-1):
""" build a layer of type 'ArgMax' using fluid
Args:
@input (variable): input fluid variable for this layer
@name (str): name for this layer
@out_max_val (bool): parameter from caffe's ArgMax layer
@top_k (int): parameter from caffe's ArgMax layer
@axis (int): parameter from caffe's ArgMax layer
Returns:
output (variable): output variable for this layer
"""
fluid = import_fluid()
if axis < 0:
axis += len(input.shape)
if out_max_val is True:
topk_var, index_var = fluid.layers.topk(input=input, k=top_k)
index_var = fluid.layers.cast(index_var, dtype=topk_var.dtype)
output = fluid.layers.concat(
[index_var, topk_var], axis=axis, name=name)
else:
topk_var, index_var = fluid.layers.topk(input=input, k=top_k, name=name)
output = index_var
return output
register(kind='ArgMax', shape=argmax_shape, layer=argmax_layer)
""" A custom layer for 'axpy' which receives 3 tensors and output 1 tensor.
the function performed is:(the mupltiplication and add are elementewise)
output = inputs[0] * inputs[1] + inputs[2]
"""
from .register import register
def axpy_shape(input_shapes):
""" calculate the output shape of this layer using input shapes
Args:
@input_shapes (list of tuples): a list of input shapes
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
assert len(input_shapes) == 3, "not valid input shape for axpy layer"
assert len(input_shapes[0]) == len(input_shapes[1]), 'should have same dims'
output_shape = input_shapes[1]
assert (input_shapes[2] == output_shape),\
"shape not consistent for axpy[%s <--> %s]" \
% (str(output_shape), str(input_shapes[2]))
return output_shape
def axpy_layer(inputs, name):
""" build a layer of type 'Axpy' using fluid
Args:
@inputs (list of variables): input fluid variables for this layer
@name (str): name for this layer
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
assert len(inputs) == 3, "invalid inputs for axpy[%s]" % (name)
alpha = inputs[0]
x = inputs[1]
y = inputs[2]
output = fluid.layers.elementwise_mul(x, alpha, axis=0)
output = fluid.layers.elementwise_add(output, y, name=name)
return output
register(kind='Axpy', shape=axpy_shape, layer=axpy_layer)
""" a custom layer for 'crop', maybe we should implement this in standard way.
more info can be found here: http://caffe.berkeleyvision.org/tutorial/layers/crop.html
"""
from .register import register
def crop_shape(input_shape, shape=None):
""" calculate the output shape of this layer using input shape
Args:
@input_shape (num | list of num): a list of number or num which represents the input shape
@shape (list of integer): the shape of output
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
if isinstance(input_shape, list):
assert len(input_shape) == 2, "the number of crop's inputs must be 2"
return input_shape[1]
elif not shape is None:
assert len(shape) == len(
input_shape.shape), "input_shape is diff with output_shape"
return shape
else:
raise Exception("crop_shape input error")
return None
def crop_layer(input, name, shape=None, axis=2, offset=None):
""" build a layer of type 'Crop' using fluid
Args:
@input (variables | list of variables): input fluid variable for this layer
@shape (list of integer): the shape of output
@name (str): name for this layer
@axis (integer): parameter from caffe's Crop layer
@offset (Variable|list/tuple of integer|None): parameter from caffe's Crop layer
Returns:
output (variable): output variable for this layer
"""
input_shape = None
output_shape = None
input_tensor = None
if isinstance(input, list):
assert len(input) == 2, "the number of crop's inputs must be 2"
input_shape = input[0].shape
output_shape = input[1].shape
input_tensor = input[0]
elif not shape is None:
assert len(shape) == len(
input.shape), "input_shape is diff with output_shape"
input_shape = input.shape
output_shape = shape
input_tensor = input
else:
raise Exception("crop_layer input error")
assert len(output_shape) == len(
input_shape), "input_shape is diff with output_shape"
if axis < 0:
axis += len(input_shape)
if offset is not None:
assert (len(input_shape) - axis
) == len(offset), "invalid offset[%s] in crop layer" % (
str(offset))
offset = [0] * axis + offset
import paddle.fluid as fluid
output = fluid.layers.crop(
input_tensor, shape=output_shape, offsets=offset, name=name)
return output
register(kind='Crop', shape=crop_shape, layer=crop_layer)
""" A custom layer for 'detectionout' used in 'SSD' model to produce outputs
Note: Since Paddle's implementation of 'detectionout' applied 'flatten' and 'softmax' ops on the input of 'conf',
while Caffe's implementation do not.
"""
from .register import register
def detectionoutput_shape(input_shape):
""" the output shape of this layer is dynamic and not determined by 'input_shape'
Args:
@input_shape (list of int): input shape
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
output_shape = [-1, 6]
return output_shape
def detectionoutput_layer(inputs,
name,
background_label=0,
share_location=True,
nms_param=None,
keep_top_k=100,
confidence_threshold=0.1):
""" build a layer of type 'detectionout' using fluid
Args:
@inputs (list of variables): input fluid variables for this layer
@name (str): name for this layer
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
if nms_param is None:
nms_param = {"nms_threshold": 0.3, "top_k": 10, "eta": 1.0}
mbox_conf_flatten = inputs[1]
mbox_priorbox = inputs[2]
mbox_priorbox_list = fluid.layers.split(mbox_priorbox, 2, dim=1)
pb = mbox_priorbox_list[0]
pbv = mbox_priorbox_list[1]
pb = fluid.layers.reshape(x=pb, shape=[-1, 4])
pbv = fluid.layers.reshape(x=pbv, shape=[-1, 4])
mbox_loc = inputs[0]
mbox_loc = fluid.layers.reshape(
x=mbox_loc, shape=[0, mbox_conf_flatten.shape[1], 4])
default = {"nms_threshold": 0.3, "top_k": 10, "eta": 1.0}
fields = ['eta', 'top_k', 'nms_threshold']
for f in default.keys():
if f not in nms_param:
nms_param[f] = default[f]
nmsed_outs = fluid.layers.detection_output(
scores=mbox_conf_flatten,
loc=mbox_loc,
prior_box=pb,
prior_box_var=pbv,
background_label=background_label,
nms_threshold=nms_param["nms_threshold"],
nms_top_k=nms_param["top_k"],
keep_top_k=keep_top_k,
score_threshold=confidence_threshold,
nms_eta=nms_param["eta"])
return nmsed_outs
register(
kind='DetectionOutput',
shape=detectionoutput_shape,
layer=detectionoutput_layer)
""" a custom layer for 'flatten', maybe we should implement this in standard way.
more info can be found here: http://caffe.berkeleyvision.org/tutorial/layers/flatten.html
"""
from .register import register
from functools import reduce
def flatten_shape(input_shape, axis=1, end_axis=-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
@axis (int): parameter from caffe's Flatten layer
@end_axis (int): parameter from caffe's Flatten layer
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
start_axis = axis
end_axis = end_axis
input_shape = list(input_shape)
if start_axis < 0:
start_axis += len(input_shape)
if end_axis < 0:
end_axis += len(input_shape) + 1
assert start_axis <= end_axis, 'invalid axis[%d] or end_axis[%d] params'\
% (start_axis, end_axis)
output_shape = input_shape[0:start_axis]
flat_sz = reduce(lambda a, b: a * b, input_shape[start_axis:end_axis])
if flat_sz < 0:
flat_sz = -1
output_shape += [flat_sz]
output_shape += input_shape[end_axis:-1]
return output_shape
def flatten_layer(input, name, axis=1, end_axis=-1):
""" build a layer of type 'Flatten' using fluid
Args:
@input (variable): input fluid variable for this layer
@name (str): name for this layer
@axis (int): parameter from caffe's Flatten layer
@end_axis (int): parameter from caffe's Flatten layer
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
input_shape = list(input.shape)
if input_shape[0] == -1:
input_shape[0] = 0
output_shape = flatten_shape(input_shape, axis=axis, end_axis=end_axis)
else:
output_shape = flatten_shape(input_shape, axis=axis, end_axis=end_axis)
output = fluid.layers.reshape(input, shape=output_shape, name=name)
return output
register(kind='Flatten', shape=flatten_shape, layer=flatten_layer)
""" A custom layer for 'normalize' op
"""
from .register import register
def normalize_shape(input_shape,
across_spatial=True,
scale_filler=True,
eps=1e-10):
""" calculate the output shape of this layer using input shapes
Args:
@input_shape (list of tuples): input shape
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
output_shape = input_shape
return output_shape
def normalize_layer(input,
name,
across_spatial=True,
scale_filler=True,
channel_shared=False,
eps=1e-10):
""" build a layer of type 'normalize' using fluid
Args:
@inputs (list of variables): input fluid variables for this layer
@name (str): name for this layer
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
param_prefix = name.split('.')[0]
assert across_spatial == False, "Only support across_spatial == False for Normalize[%s]" % (
name)
l2_norm = fluid.layers.l2_normalize(input, axis=1) # l2 norm along channel
shape = [1] if channel_shared else [input.shape[1]]
scale_attr = fluid.ParamAttr(name=param_prefix + '_scale')
scale_param = fluid.layers.create_parameter(
shape=shape, dtype=input.dtype, name=name, attr=scale_attr)
out = fluid.layers.elementwise_mul(
x=l2_norm, y=scale_param, axis=-1 if channel_shared else 1)
return out
register(kind='Normalize', shape=normalize_shape, layer=normalize_layer)
""" A custom layer for 'Permute' which is equivalent to transpose in paddle
"""
from .register import register
def permute_shape(input_shape, order):
""" calculate the output shape of this layer using input shapes
Args:
@input_shape (list of numbers): input shape
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
output_shape = []
for ii in order:
assert ii < len(input_shape), "invalid order for permute[%s]" % (name)
output_shape.append(input_shape[ii])
return output_shape
def permute_layer(input, name, order):
""" build a layer of type 'permute' using fluid
Args:
@input (input variable): input fluid variables for this layer
@name (str): name for this layer
@order (list of int): order to permute the dims
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
output = fluid.layers.transpose(input, order, name=name)
return output
register(kind='Permute', shape=permute_shape, layer=permute_layer)
""" a custom layer for 'power', maybe we should implement this in standard way.
more info can be found here: http://caffe.berkeleyvision.org/tutorial/layers/power.html
"""
from .register import register
def power_shape(input_shape, shape=None):
""" 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
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
return input_shape
def power_layer(input, name, power=1.0, scale=1.0, shift=0.0):
""" build a layer of type 'Power' using fluid
Args:
@input (variables): input fluid variable for this layer
@name (str): name for this layer
@power (float): parameter from caffe's Power layer
@scale (float): parameter from caffe's Power layer
@shift (float): parameter from caffe's Power layer
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
scale_out = fluid.layers.scale(
input, scale=scale, bias=shift, bias_after_scale=True)
output = fluid.layers.pow(scale_out, factor=power)
return output
register(kind='Power', shape=power_shape, layer=power_layer)
""" A custom layer for 'priorbox' which is used in ssd to generate prior box info
Since the order of prior box is different between caffe and paddle,
we use 'slice' and 'concate' ops to align them.
"""
from .register import register
def priorbox_shape(input_shapes, min_size, max_size=None, aspect_ratio=None):
""" calculate the output shape of this layer using input shapes
Args:
@input_shapes (list of tuples): a list of input shapes
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
assert len(input_shapes) == 2, "invalid inputs for Priorbox[%s]" % (name)
fc_shape = input_shapes[0]
N = 1
if not max_size == None:
N += 1
if not aspect_ratio == None:
N += 2 * len(aspect_ratio)
N_bbx = fc_shape[2] * fc_shape[3] * N
output_shape = [1, 2, 4 * N_bbx]
return output_shape
def priorbox_layer(inputs,
name,
min_size,
max_size=None,
aspect_ratio=None,
variance=[0.1, 0.1, 0.2, 0.2],
flip=False,
clip=False,
step=0.0,
offset=0.5):
""" build a layer of type 'Priorbox' using fluid
Args:
@inputs (list of variables): input fluid variables for this layer
@name (str): name for this layer
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
assert len(inputs) == 2, "invalid inputs for Priorbox[%s]" % (name)
input = inputs[0]
image = inputs[1]
steps = tuple(step) if type(step) is list or type(step) is tuple else (step,
step)
box, variance_ = fluid.layers.prior_box(
input,
image,
min_size,
max_size,
aspect_ratio,
variance,
flip,
clip,
steps,
offset,
min_max_aspect_ratios_order=True)
"""
#adjust layout when the output is not consistent with caffe's
feat_shape = list(input.shape)
H = feat_shape[2]
W = feat_shape[3]
box_tmp = fluid.layers.reshape(box, [H, W, -1, 4])
nb_prior_bbx = int(box_tmp.shape[2])
tensor_list = fluid.layers.split(box_tmp, nb_prior_bbx, 2)
#TODO:
# current implementation for this layer is not efficient
# and we should fix this bug in future when Paddle support the same prior-box layout with Caffe
index_list = [0]
index_list = index_list * nb_prior_bbx
index_offset = 0
if max_size is not None:
index_list[1] = -1
index_offset = 1
for ii in xrange(2 * len(aspect_ratio)):
index_list[ii + 1 + index_offset] = ii + 1
tensor_list_gathered = [tensor_list[ii] for ii in index_list]
caffe_prior_bbx = fluid.layers.concat(tensor_list_gathered, axis=2)
box = fluid.layers.reshape(caffe_prior_bbx, [1, 1, -1])
"""
box = fluid.layers.reshape(box, [1, 1, -1])
variance_ = fluid.layers.reshape(variance_, [1, 1, -1])
output = fluid.layers.concat([box, variance_], axis=1)
return output
register(kind='PriorBox', shape=priorbox_shape, layer=priorbox_layer)
""" a custom layer for 'crop', maybe we should implement this in standard way.
more info can be found here: http://caffe.berkeleyvision.org/tutorial/layers/reduction.html
"""
from .register import register
def reduction_shape(input_shape, axis=0):
""" 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
@axis (int): parameter from caffe's reduction layer
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
if axis < 0:
axis += len(input_shape) + 1
assert axis <= len(input_shape), 'invalid axis[%d] error' % (axis)
return input_shape[0:axis]
def reduction_layer(input, name, axis=0, operation=1, coeff=1.0):
""" build a layer of type 'Crop' using fluid
Args:
@input (variable): input fluid variable for this layer
@name (str): name for this layer
@axis (int): parameter from caffe's reduction layer
@operation (int): parameter from caffe's reduction layer
@coeff (float): parameter from caffe's reduction layer
Returns:
output (variable): output variable for this layer
"""
assert operation >= 1 and operation <= 4, "reduction reduction [%s] error" % (
operation)
input_len = len(input.shape)
if axis < 0:
axis += input_len + 1
dim = range(input_len)
import paddle.fluid as fluid
if operation == 1: ## operation = SUM
output = fluid.layers.reduce_sum(
input, dim=dim[axis:], keep_dim=False, name=name)
elif operation == 2: ## operation = ASUM
absout = fluid.layers.abs(input)
output = fluid.layers.reduce_sum(
absout, dim=dim[axis:], keep_dim=False, name=name)
elif operation == 3: ## operation = SUMSQ
powout = fluid.layers.pow(x=input, factor=2.0)
output = fluid.layers.reduce_sum(
powout, dim=dim[axis:], keep_dim=False, name=name)
else: ## operation = MEAN
output = fluid.layers.reduce_mean(
input, dim=dim[axis:], keep_dim=False, name=name)
mulout = fluid.layers.scale(x=output, scale=coeff)
return mulout
register(kind='Reduction', shape=reduction_shape, layer=reduction_layer)
""" this module provides 'register' for registering customized layers
"""
g_custom_layers = {}
def register(kind, shape, layer):
""" register a custom layer or a list of custom layers
Args:
@kind (str or list): type name of the layer
@shape (function): a function to generate the shape of layer's output
@layer (function): a function to generate the shape of layer's output
Returns:
None
"""
assert type(shape).__name__ == 'function', 'shape should be a function'
assert type(layer).__name__ == 'function', 'layer should be a function'
if type(kind) is str:
kind = [kind]
else:
assert type(
kind) is list, 'invalid param "kind" for register, not a list or str'
for k in kind:
assert type(
k) is str, 'invalid param "kind" for register, not a list of str'
assert k not in g_custom_layers, 'this type[%s] has already been registered' % (
k)
print('register layer[%s]' % (k))
g_custom_layers[k] = {'shape': shape, 'layer': layer}
def get_registered_layers():
return g_custom_layers
""" 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
from functools import reduce
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))
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] = 0
output_shape = reshape_shape(input_shape, shape, axis, num_axes)
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)
""" a custom layer for 'ROIPooling', maybe we should implement this in standard way.
more info can be found here: http://caffe.berkeleyvision.org/tutorial/layers/ROIPooling.html
"""
from .register import register
def roipooling_shape(input_shapes, pooled_h, pooled_w, spatial_scale):
""" 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
@out_max_val (bool): parameter from caffe's ROIPooling layer
@top_k (int): parameter from caffe's ROIPooling layer
@axis (int): parameter from caffe's ROIPooling layer
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
assert len(input_shapes) == 2, "not valid input shape for roipooling layer"
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
def roipooling_layer(inputs, name, pooled_h, pooled_w, spatial_scale):
""" build a layer of type 'ROIPooling' using fluid
Args:
@input (variable): input fluid variable for this layer
@name (str): name for this layer
@out_max_val (bool): parameter from caffe's ROIPooling layer
@top_k (int): parameter from caffe's ROIPooling layer
@axis (int): parameter from caffe's ROIPooling layer
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
assert len(inputs) == 2, "not valid input shape for roipooling layer"
base_fea = inputs[0]
rois = inputs[1][:, 1:5]
rois_fea = fluid.layers.roi_pool(base_fea, rois, pooled_h, pooled_w,
spatial_scale)
return rois_fea
register(kind='ROIPooling', shape=roipooling_shape, layer=roipooling_layer)
""" a custom layer for 'select' which is used to replace standard 'Slice' layer
for converting layer with multiple different output tensors
"""
from .register import register
def select_shape(input_shape, slice_point, axis=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
@slice_point (list): parameter from caffe's Slice layer
@axis (int): parameter from caffe's Slice layer
Returns:
@output_shape (list of num): a list of numbers represent the output shape
"""
input_shape = list(input_shape)
start = slice_point[0]
if len(slice_point) == 2:
end = slice_point[1]
else:
end = input_shape[axis]
assert end > start, "invalid slice_point with [start:%d, end:%d]"\
% (start, end)
output_shape = input_shape
output_shape[axis] = end - start
return output_shape
def select_layer(input, name, slice_point, axis=1):
""" build a layer of type 'Slice' using fluid
Args:
@input (variable): input fluid variable for this layer
@name (str): name for this layer
@slice_point (list): parameter from caffe's Slice layer
@axis (int): parameter from caffe's Slice layer
Returns:
output (variable): output variable for this layer
"""
import paddle.fluid as fluid
input_shape = list(input.shape)
start = slice_point[0]
if len(slice_point) == 2:
end = slice_point[1]
else:
end = input_shape[axis]
sections = []
if start > 0:
sections.append(start)
pos = len(sections)
sections.append(end - start)
if end != input_shape[axis]:
sections.append(input_shape[axis] - end)
outputs = fluid.layers.split(input, sections, dim=axis, name=name)
return outputs[pos]
register(kind='Select', shape=select_shape, layer=select_layer)
import sys
#debug level, can be 'warn', 'verbose'
log_level = 'warn'
class KaffeError(Exception):
pass
def print_stderr(msg):
sys.stderr.write('%s\n' % msg)
def debug(msg):
if log_level == 'verbose':
print_stderr('[DEBUG]' + msg)
def notice(msg):
print_stderr('[NOTICE]' + msg)
def warn(msg):
print_stderr('[WARNING]' + msg)
def set_loglevel(level):
global log_level
if 'warn' != level and 'verbose' != level:
raise Exception('not supported log level[%s]' % (level))
log_level = level
from google.protobuf import text_format
from .caffe import get_caffe_resolver
from .errors import KaffeError, print_stderr
from .layers import LayerAdapter, LayerType, NodeKind, NodeDispatch
from .shapes import make_tensor
class Node(object):
def __init__(self, name, kind, layer=None):
self.name = name
self.kind = kind
self.layer = LayerAdapter(layer, kind) if layer else None
self.parents = []
self.children = []
self.data = None #parameters of this node
self.output_shape = None #output shape of this node
self.metadata = {}
def add_parent(self, parent_node):
assert parent_node not in self.parents
self.parents.append(parent_node)
if self not in parent_node.children:
parent_node.children.append(self)
def add_child(self, child_node):
assert child_node not in self.children
self.children.append(child_node)
if self not in child_node.parents:
child_node.parents.append(self)
def get_only_parent(self):
if len(self.parents) != 1:
raise KaffeError('Node (%s) expected to have 1 parent. Found %s.' %
(self, len(self.parents)))
return self.parents[0]
@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)
def __repr__(self):
return '%s (0x%x)' % (self.name, id(self))
class Graph(object):
def __init__(self, nodes=None, name=None, trace={}):
self.nodes = nodes or []
self.node_lut = {node.name: node for node in self.nodes}
self.output_trace = trace
if name is None or name == '':
self.name = 'MyNet'
else:
self.name = name
def add_node(self, node):
self.nodes.append(node)
self.node_lut[node.name] = node
def get_node(self, name):
try:
return self.node_lut[name]
except KeyError:
raise KaffeError('Layer not found: %s' % name)
def add_name_trace(self, trace, which='caffe'):
self.output_trace[which] = trace
def get_name_trace(self, which=None):
if which is not None:
return self.output_trace[which]
else:
return self.output_trace
def get_input_nodes(self):
return [node for node in self.nodes if len(node.parents) == 0]
def get_output_nodes(self):
return [node for node in self.nodes if len(node.children) == 0]
def topologically_sorted(self):
sorted_nodes = []
unsorted_nodes = list(self.nodes)
temp_marked = set()
perm_marked = set()
def visit(node):
if node in temp_marked:
raise KaffeError('Graph is not a DAG.')
if node in perm_marked:
return
temp_marked.add(node)
for child in node.children:
visit(child)
perm_marked.add(node)
temp_marked.remove(node)
sorted_nodes.insert(0, node)
while len(unsorted_nodes):
visit(unsorted_nodes.pop())
return sorted_nodes
def compute_output_shapes(self):
sorted_nodes = self.topologically_sorted()
for node in sorted_nodes:
node.output_shape = make_tensor(
*NodeKind.compute_output_shape(node))
def replaced(self, new_nodes):
return Graph(nodes=new_nodes, name=self.name, trace=self.output_trace)
def transformed(self, transformers):
graph = self
for transformer in transformers:
graph = transformer(graph)
if graph is None:
raise KaffeError('Transformer failed: {}'.format(transformer))
assert isinstance(graph, Graph)
return graph
def __contains__(self, key):
return key in self.node_lut
def __str__(self):
hdr = '{:<20} {:<30} {:>20} {:>20}'.format('Type', 'Name', 'Param',
'Output')
s = [hdr, '-' * 94]
for node in self.topologically_sorted():
# If the node has learned parameters, display the first one's shape.
# In case of convolutions, this corresponds to the weights.
if node.data is None:
data_shape = '--'
out_shape = node.output_shape or '--'
s.append('{:<20} {:<30} {:>20} {:>20}'.format(
node.kind, node.name, data_shape, str(tuple(out_shape))))
else:
for d in node.data:
#data_shape = node.data[0].shape if node.data else '--'
data_shape = d.shape
out_shape = node.output_shape or '--'
s.append('{:<20} {:<30} {:>20} {:>20}'.format(
node.kind, node.name, str(data_shape), str(tuple(out_shape))))
return '\n'.join(s)
class GraphBuilder(object):
'''Constructs a model graph from a Caffe protocol buffer definition.'''
def __init__(self, def_path, phase='test'):
'''
def_path: Path to the model definition (.prototxt)
data_path: Path to the model data (.caffemodel)
phase: Either 'test' or 'train'. Used for filtering phase-specific nodes.
'''
self.def_path = def_path
self.phase = phase
self.load()
def load(self):
'''Load the layer definitions from the prototxt.'''
self.params = get_caffe_resolver().NetParameter()
with open(self.def_path, 'rb') as def_file:
text_format.Merge(def_file.read(), self.params)
def filter_layers(self, layers):
'''Filter out layers based on the current phase.'''
phase_map = {0: 'train', 1: 'test'}
filtered_layer_names = set()
filtered_layers = []
for layer in layers:
phase = self.phase
if len(layer.include):
phase = phase_map[layer.include[0].phase]
if len(layer.exclude):
phase = phase_map[1 - layer.include[0].phase]
exclude = (phase != self.phase)
# Dropout layers appear in a fair number of Caffe
# test-time networks. These are just ignored. We'll
# filter them out here.
if (not exclude) and (phase == 'test'):
exclude = (layer.type == LayerType.Dropout)
if not exclude:
filtered_layers.append(layer)
# Guard against dupes.
assert layer.name not in filtered_layer_names
filtered_layer_names.add(layer.name)
return filtered_layers
def make_node(self, layer):
'''Create a graph node for the given layer.'''
kind = NodeKind.map_raw_kind(layer.type)
if kind is None:
raise KaffeError('Unknown layer type encountered: %s' % layer.type)
# We want to use the layer's top names (the "output" names), rather than the
# name attribute, which is more of readability thing than a functional one.
# Other layers will refer to a node by its "top name".
return Node(layer.name, kind, layer=layer)
def make_input_nodes(self):
'''
Create data input nodes.
This method is for old-style inputs, where the input specification
was not treated as a first-class layer in the prototext.
Newer models use the "Input layer" type.
'''
nodes = [Node(name, NodeKind.Data) for name in self.params.input]
inputs_num = len(nodes)
if inputs_num > 0:
input_dims_num = len(self.params.input_dim)
if input_dims_num > 0 and input_dims_num != inputs_num * 4:
raise KaffeError('invalid input_dim[%d] param in prototxt' %
(input_dims_num))
input_dims = [[]] * inputs_num
for i in range(input_dims_num):
dim = self.params.input_dim[i]
which = int(i / 4)
input_dims[which].append(int(dim))
for i in range(inputs_num):
if len(self.params.input_shape) == inputs_num:
input_dim = map(int, self.params.input_shape[i].dim)
input_dims[i] = input_dim
nodes[i].output_shape = tuple(input_dims[i])
return nodes
def build(self):
'''
Builds the graph from the Caffe layer definitions.
'''
# Get the layers
layers = self.params.layers or self.params.layer
# Filter out phase-excluded layers
layers = self.filter_layers(layers)
# Get any separately-specified input layers
nodes = self.make_input_nodes()
nodes += [self.make_node(layer) for layer in layers]
# Initialize the graph
graph = Graph(nodes=nodes, name=self.params.name)
# Connect the nodes
#
# A note on layers and outputs:
# In Caffe, each layer can produce multiple outputs ("tops") from a set of inputs
# ("bottoms"). The bottoms refer to other layers' tops. The top can rewrite a bottom
# (in case of in-place operations). Note that the layer's name is not used for establishing
# any connectivity. It's only used for data association. By convention, a layer with a
# single top will often use the same name (although this is not required).
#
# The current implementation only supports single-output nodes (note that a node can still
# have multiple children, since multiple child nodes can refer to the single top's name).
node_outputs = {}
output_trace = {}
for layer in layers:
node = graph.get_node(layer.name)
for input_name in layer.bottom:
assert input_name != layer.name
parent_node = node_outputs.get(input_name)
if (parent_node is None) or (parent_node == node):
parent_node = graph.get_node(input_name)
node.add_parent(parent_node)
if len(layer.top) > 1:
raise KaffeError('Multiple top nodes are not supported.')
for output_name in layer.top:
if output_name == layer.name:
# Output is named the same as the node. No further action required.
continue
# There are two possibilities here:
#
# Case 1: output_name refers to another node in the graph.
# This is an "in-place operation" that overwrites an existing node.
# This would create a cycle in the graph. We'll undo the in-placing
# by substituting this node wherever the overwritten node is referenced.
#
# Case 2: output_name violates the convention layer.name == output_name.
# Since we are working in the single-output regime, we will can rename it to
# match the layer name.
#
# For both cases, future references to this top re-routes to this node.
node_outputs[output_name] = node
if output_name in output_trace:
output_trace[output_name].append(node.name)
else:
output_trace[output_name] = [output_name, node.name]
#build a mapping from real-name to changed-name(for caffe's INPLACE inference)
real2chg = {}
deleted = {}
for k, v in output_trace.items():
real2chg[v[-1]] = k
for n in v:
if n in real2chg:
continue
if n not in deleted:
deleted[n] = '%s.%s' % (k, v[-1])
graph.add_name_trace({
'real2chg': real2chg,
'deleted': deleted
}, 'caffe')
graph.compute_output_shapes()
return graph
class NodeMapper(NodeDispatch):
def __init__(self, graph):
self.graph = graph
def map(self):
nodes = self.graph.topologically_sorted()
# Remove input nodes - we'll handle them separately.
input_nodes = self.graph.get_input_nodes()
nodes = [t for t in nodes if t not in input_nodes]
# Decompose DAG into chains.
chains = []
for node in nodes:
attach_to_chain = None
if len(node.parents) == 1:
parent = node.get_only_parent()
for chain in chains:
if chain[-1] == parent:
# Node is part of an existing chain.
attach_to_chain = chain
break
if attach_to_chain is None:
# Start a new chain for this node.
attach_to_chain = []
chains.append(attach_to_chain)
attach_to_chain.append(node)
# Map each chain.
mapped_chains = []
for chain in chains:
mapped_chains.append(self.map_chain(chain))
return self.commit(mapped_chains)
def map_chain(self, chain):
return [self.map_node(node) for node in chain]
def map_node(self, node):
map_func = self.get_handler(node.kind, 'map')
mapped_node = map_func(node)
assert mapped_node is not None
mapped_node.node = node
return mapped_node
def commit(self, mapped_chains):
raise NotImplementedError('Must be implemented by subclass.')
\ No newline at end of file
import re
import numbers
from collections import namedtuple
import sys
from . import custom_layers
from .shapes import *
LAYER_DESCRIPTORS = {
# Caffe Types
'AbsVal': shape_identity,
'Accuracy': shape_scalar,
'ArgMax': shape_not_implemented,
'BatchNorm': shape_identity,
'BNLL': shape_not_implemented,
'Concat': shape_concat,
'ContrastiveLoss': shape_scalar,
'Convolution': shape_convolution,
'Deconvolution': shape_deconvolution,
'Data': shape_data,
'Dropout': shape_identity,
'DummyData': shape_data,
'Crop': shape_crop,
'EuclideanLoss': shape_scalar,
'Eltwise': shape_identity,
'Exp': shape_identity,
'Flatten': shape_not_implemented,
'HDF5Data': shape_data,
'HDF5Output': shape_identity,
'HingeLoss': shape_scalar,
'Im2col': shape_not_implemented,
'ImageData': shape_data,
'InfogainLoss': shape_scalar,
'InnerProduct': shape_inner_product,
'Input': shape_data,
'LRN': shape_identity,
'MemoryData': shape_mem_data,
'MultinomialLogisticLoss': shape_scalar,
'MVN': shape_not_implemented,
'Pooling': shape_pool,
'Power': shape_power,
'ReLU': shape_identity,
'PReLU': shape_identity,
'Scale': shape_identity,
'Sigmoid': shape_identity,
'SigmoidCrossEntropyLoss': shape_scalar,
'Silence': shape_not_implemented,
'Softmax': shape_identity,
'SoftmaxWithLoss': shape_scalar,
'Split': shape_not_implemented,
'Slice': shape_not_implemented,
'TanH': shape_identity,
'WindowData': shape_not_implemented,
'Threshold': shape_identity,
}
# layer types in 'V1LayerParameter'
# (v1layertype name, enum value, mapped to layer type)
v1_layertypes = [
('ABSVAL', 35),
('ACCURACY', 1),
('ARGMAX', 30),
('BNLL', 2),
('CONCAT', 3),
('CONVOLUTION', 4),
('DATA', 5),
('DECONVOLUTION', 39),
('DROPOUT', 6),
('ELTWISE', 25),
('EXP', 38),
('FLATTEN', 8),
('IM2COL', 11),
('INNERPRODUCT', 14),
('LRN', 15),
('MEMORYDATA', 29),
('MULTINOMIALLOGISTICLOSS', 16),
('MVN', 34),
('POOLING', 17),
('POWER', 26),
('RELU', 18),
('SIGMOID', 19),
('SIGMOIDCROSSENTROPYLOSS', 27),
('SILENCE', 36),
('SOFTMAX', 20),
('SPLIT', 22),
('SLICE', 33),
('TANH', 23),
('WINDOWDATA', 24),
('THRESHOLD', 31),
]
LAYER_TYPES = LAYER_DESCRIPTORS.keys()
LayerType = type('LayerType', (), {t: t for t in LAYER_TYPES})
#map the layer name in V1 to standard name
V1_LAYER_MAP = {'_not_init_': True}
def get_v1_layer_map():
global V1_LAYER_MAP
if '_not_init_' not in V1_LAYER_MAP:
return V1_LAYER_MAP
else:
del V1_LAYER_MAP['_not_init_']
name2layer = {}
for n in LAYER_TYPES:
name2layer[n.upper()] = n
for l in v1_layertypes:
n, v = l
if n in name2layer and v not in V1_LAYER_MAP:
V1_LAYER_MAP[v] = name2layer[n]
else:
raise KaffeError('not found v1 layer type %s' % n)
return V1_LAYER_MAP
class NodeKind(LayerType):
@staticmethod
def map_raw_kind(kind):
if custom_layers.has_layer(kind):
return kind
if kind in LAYER_TYPES:
return kind
v1_layers = get_v1_layer_map()
if kind in v1_layers:
return v1_layers[kind]
else:
return None
@staticmethod
def compute_output_shape(node):
if custom_layers.has_layer(node.kind):
return custom_layers.compute_output_shape(node.kind, node)
try:
val = LAYER_DESCRIPTORS[node.kind](node)
return val
except NotImplementedError:
raise KaffeError(
'Output shape computation not implemented for type: %s' %
node.kind)
class NodeDispatchError(KaffeError):
pass
class NodeDispatch(object):
@staticmethod
def get_handler_name(node_kind):
if len(node_kind) <= 6:
# A catch-all for things like ReLU and tanh
return node_kind.lower()
# Convert from CamelCase to under_scored
name = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', node_kind)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', name).lower()
def get_handler(self, node_kind, prefix):
if custom_layers.has_layer(node_kind):
return getattr(self, 'map_custom')
name = self.get_handler_name(node_kind)
name = '_'.join((prefix, name))
try:
return getattr(self, name)
except AttributeError:
raise NodeDispatchError(
'No handler found for node kind: %s (expected: %s)' %
(node_kind, name))
class LayerAdapter(object):
def __init__(self, layer, kind):
self.layer = layer
self.kind = kind
@property
def parameters(self):
name = NodeDispatch.get_handler_name(self.kind)
if self.kind.lower() == "normalize":
name = "norm"
elif self.kind.lower() == "deconvolution":
name = "convolution"
name = '_'.join((name, 'param'))
try:
return getattr(self.layer, name)
except AttributeError:
print(dir(self.layer))
raise NodeDispatchError(
'Caffe parameters not found attr[%s] for layer kind[%s]' %
(name, self.kind))
@staticmethod
def get_kernel_value(scalar, repeated, idx, default=None):
if scalar:
return scalar
if repeated:
if isinstance(repeated, numbers.Number):
return repeated
if len(repeated) == 1:
# Same value applies to all spatial dimensions
return int(repeated[0])
assert idx < len(repeated)
# Extract the value for the given spatial dimension
return repeated[idx]
if default is None:
raise ValueError('Unable to determine kernel parameter!')
return default
@property
def kernel_parameters(self):
assert self.kind in (NodeKind.Convolution, NodeKind.Pooling,\
NodeKind.Deconvolution)
params = self.parameters
k_h = self.get_kernel_value(params.kernel_h, params.kernel_size, 0)
k_w = self.get_kernel_value(params.kernel_w, params.kernel_size, 1)
s_h = self.get_kernel_value(
params.stride_h, params.stride, 0, default=1)
s_w = self.get_kernel_value(
params.stride_w, params.stride, 1, default=1)
p_h = self.get_kernel_value(params.pad_h, params.pad, 0, default=0)
p_w = self.get_kernel_value(params.pad_w, params.pad, 1, default=0)
dila_h = dila_w = 1
if self.kind in (NodeKind.Convolution, NodeKind.Deconvolution):
dila_len = len(params.dilation)
if dila_len == 2:
dila_h = params.dilation[0]
dila_w = params.dilation[1]
elif dila_len == 1:
dila_h = dila_w = params.dilation[0]
else:
assert dila_len == 0, "invalid length[%s] of dilation in convolution" % (
dila_len)
return KernelParameters(k_h, k_w, s_h, s_w, p_h, p_w, dila_h, dila_w)
KernelParameters = namedtuple(
'KernelParameters',
[
'kernel_h', 'kernel_w', 'stride_h', 'stride_w', 'pad_h', 'pad_w',
'dila_h', 'dila_w'
], )
""" this module is used as a template for generating sub class of Network
"""
class MyNet(object):
### automatically generated by caffe2fluid ###
inputs_info = "INPUTS_INFO"
custom_layers_path = "_CAFFE2FLUID_CUSTOM_LAYERS_"
def custom_layer_factory(self):
import os
pk_paths = []
default = os.path.dirname(os.path.abspath(__file__))
location = os.environ.get('CAFFE2FLUID_CUSTOM_LAYERS', default)
pk_name = 'custom_layers'
pk_dir = os.path.join(location, pk_name)
pk_paths.append((location, pk_dir))
location = MyNet.custom_layers_path
pk_dir = os.path.join(MyNet.custom_layers_path, pk_name)
pk_paths.append((location, pk_dir))
for loc, pk_dir in pk_paths:
if os.path.exists(pk_dir):
if loc not in sys.path:
sys.path.insert(0, loc)
break
try:
from custom_layers import make_custom_layer
return make_custom_layer
except Exception as e:
print('maybe you should set $CAFFE2FLUID_CUSTOM_LAYERS first')
raise e
@classmethod
def input_shapes(cls):
return cls.inputs_info
@classmethod
def convert(cls, npy_model, fluid_path, outputs=None):
fluid = import_fluid()
shapes = cls.input_shapes()
input_name = list(shapes.keys())[0]
feed_data = {}
for name, shape in shapes.items():
data_layer = fluid.layers.data(
name=name, shape=shape, dtype="float32")
feed_data[name] = data_layer
net = cls(feed_data)
place = fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
net.load(data_path=npy_model, exe=exe, place=place)
output_vars = []
model_filename = 'model'
params_filename = 'params'
if outputs is None:
output_vars.append(net.get_output())
else:
if outputs[0] == 'dump_all':
model_filename = None
params_filename = None
output_vars.append(net.get_output())
else:
if type(outputs) is list:
for n in outputs:
assert n in net.layers, 'not found layer with this name[%s]' % (
n)
output_vars.append(net.layers[n])
fluid.io.save_inference_model(
fluid_path, [input_name],
output_vars,
exe,
main_program=None,
model_filename=model_filename,
params_filename=params_filename)
return 0
def main():
""" a tool used to convert caffe model to fluid
"""
import sys
import os
import argparse
filename = os.path.splitext(os.path.basename(sys.argv[0]))[0]
parser = argparse.ArgumentParser()
parser.add_argument('--npy_path', help='Model\'s parameters (.npy) path')
parser.add_argument('--model-param-path', help='The path of model and param which are convertd by .npy',
default='./fluid')
parser.add_argument(
'--need-layers-name', help='The layers need to save (split by ,)')
args = parser.parse_args()
npy_weight = args.npy_path
fluid_model = args.model_param_path
outputs = None
if len(sys.argv) >= 6:
outputs = args.need_layers_name.split(',')
ret = MyNet.convert(npy_weight, fluid_model, outputs)
if ret == 0:
outputs = 'last output layer' if outputs is None else outputs
print('succeed to convert to fluid format with output layers[%s]'
' in directory[%s]' % (outputs, fluid_model))
else:
print('failed to convert model to fluid format')
return ret
def generate_net_code(net_name, inputs_info):
""" generate framework of a custom net code which represent a subclass of Network
Args:
@net_name (str): class name for this net
@inputs_info (str): a str which represents a dict, eg: '{"data": [3, 32, 32]}'
Returns:
net_codes (str): codes for this subclass
"""
import os
import inspect
net_codes = str(inspect.getsource(MyNet))
net_codes = net_codes.replace('MyNet(object)', '%s(Network)' % net_name)
net_codes = net_codes.replace('MyNet', net_name)
net_codes = net_codes.replace('"INPUTS_INFO"', inputs_info)
custom_layer_dir = os.path.dirname(os.path.abspath(__file__))
net_codes = net_codes.replace('_CAFFE2FLUID_CUSTOM_LAYERS_',
custom_layer_dir)
return net_codes
def generate_main_code(net_name):
""" generate a piece of code for 'main' function
Args:
@net_name (str): class name for this net
Returns:
main_codes (str): codes for this main function
"""
import inspect
main_codes = str(inspect.getsource(main))
main_codes = main_codes.replace('MyNet', net_name)
return main_codes
if __name__ == "__main__":
""" just for testing
"""
print(generate_net_code('Attribute', "{'data': [3, 277, 277]}"))
print(generate_main_code('Attribute'))
from .transformer import Transformer
from .network import Network
此差异已折叠。
import numpy as np
from past.builtins import basestring
from ..errors import KaffeError, print_stderr
from ..graph import GraphBuilder, NodeMapper
from ..layers import NodeKind
from ..transformers import (DataInjector, DataReshaper, NodeRenamer,
SubNodeFuser, ReLUFuser, BatchNormScaleBiasFuser,
BatchNormPreprocessor, ParameterNamer, CropFuser)
from . import network
class PaddleNode(object):
'''An intermediate representation for Paddle operations.'''
def __init__(self, op, *args, **kwargs):
# A string corresponding to the Paddle operation
self.op = op
# Positional arguments for the operation
self.args = args
# Keyword arguments for the operation
self.kwargs = list(kwargs.items())
# The source Caffe node
self.node = None
def format(self, arg):
'''Returns a string representation for the given value.'''
return "'%s'" % arg if isinstance(arg, basestring) else str(arg)
def pair(self, key, value):
'''Returns key=formatted(value).'''
return '%s=%s' % (key, self.format(value))
def emit(self):
'''Emits the Python source for this node.'''
# Format positional arguments
args = map(self.format, self.args)
args = list(args)
# Format any keyword arguments
if self.kwargs:
args += [self.pair(k, v) for k, v in self.kwargs]
# Set the node name
args.append(self.pair('name', self.node.name))
args = ', '.join(args)
return '%s(%s)' % (self.op, args)
class MaybeActivated(object):
def __init__(self, node, default=True):
self.inject_kwargs = {}
if node.metadata.get('relu', False) != default:
self.inject_kwargs['relu'] = not default
default_slope = 0.0
slope = node.metadata.get('relu_negative_slope', default_slope)
if slope != default_slope:
self.inject_kwargs['relu_negative_slope'] = slope
def __call__(self, *args, **kwargs):
kwargs.update(self.inject_kwargs)
return PaddleNode(*args, **kwargs)
class PaddleMapper(NodeMapper):
def get_kernel_params(self, node):
kernel_params = node.layer.kernel_parameters
input_shape = node.get_only_parent().output_shape
padding = [kernel_params.pad_h, kernel_params.pad_w]
if padding[0] == 0 and padding[1] == 0:
padding = {}
else:
padding = {'padding': padding}
return (kernel_params, padding)
def map_convolution(self, node):
(kernel_params, kwargs) = self.get_kernel_params(node)
h = kernel_params.kernel_h
w = kernel_params.kernel_w
c_o = node.output_shape[1]
c_i = node.parents[0].output_shape[1]
group = node.parameters.group
if group != 1:
kwargs['group'] = group
if not node.parameters.bias_term:
kwargs['biased'] = False
if kernel_params.dila_h != 1 or kernel_params.dila_w != 1:
kwargs['dilation'] = (kernel_params.dila_h, kernel_params.dila_w)
assert kernel_params.kernel_h == h
assert kernel_params.kernel_w == w
return MaybeActivated(node)(
'conv', kernel_params.kernel_h, kernel_params.kernel_w, c_o,
kernel_params.stride_h, kernel_params.stride_w, **kwargs)
def map_deconvolution(self, node):
(kernel_params, kwargs) = self.get_kernel_params(node)
h = kernel_params.kernel_h
w = kernel_params.kernel_w
c_o = node.output_shape[1]
c_i = node.parents[0].output_shape[1]
if not node.parameters.bias_term:
kwargs['biased'] = False
if kernel_params.dila_h != 1 or kernel_params.dila_w != 1:
kwargs['dilation'] = (kernel_params.dila_h, kernel_params.dila_w)
assert kernel_params.kernel_h == h
assert kernel_params.kernel_w == w
return MaybeActivated(node)(
'deconv', kernel_params.kernel_h, kernel_params.kernel_w, c_o,
kernel_params.stride_h, kernel_params.stride_w, **kwargs)
def map_relu(self, node):
return PaddleNode('relu')
def map_prelu(self, node):
channel_shared = getattr(node.parameters, 'channel_shared', False)
return PaddleNode('prelu', channel_shared)
def map_tanh(self, node):
return PaddleNode('tanh')
def map_pooling(self, node):
pool_type = node.parameters.pool
if pool_type == 0:
pool_op = 'max_pool'
elif pool_type == 1:
pool_op = 'avg_pool'
else:
# Stochastic pooling, for instance.
raise KaffeError('Unsupported pooling type.')
ceil_mode = getattr(node.layer.parameters, 'ceil_mode', True)
global_pool = getattr(node.layer.parameters, 'global_pooling', False)
if global_pool:
input_shape = node.get_only_parent().output_shape
return PaddleNode(pool_op, input_shape.height, input_shape.width, 1,
1, ceil_mode)
else:
(kernel_params, padding) = self.get_kernel_params(node)
return PaddleNode(pool_op, kernel_params.kernel_h,
kernel_params.kernel_w, kernel_params.stride_h,
kernel_params.stride_w, ceil_mode, **padding)
def map_sigmoid(self, node):
return PaddleNode('sigmoid')
def map_custom(self, node):
from .. import custom_layers
return custom_layers.make_node(PaddleNode, node.kind, node)
def map_inner_product(self, node):
#TODO: Axis
assert node.parameters.axis == 1
#TODO: Unbiased
assert node.parameters.bias_term == True
return MaybeActivated(node)('fc', node.parameters.num_output)
def map_softmax(self, node):
return PaddleNode('softmax', node.parameters.axis)
def map_lrn(self, node):
params = node.parameters
# The window size must be an odd value. For a window
# size of (2*n+1), Paddle defines depth_radius = n.
assert params.local_size % 2 == 1
# Caffe scales by (alpha/(2*n+1)), whereas Paddle
# just scales by alpha (as does Krizhevsky's paper).
# We'll account for that here.
alpha = params.alpha / float(params.local_size)
return PaddleNode('lrn', params.local_size, alpha, params.beta)
def map_concat(self, node):
return PaddleNode('concat', node.parameters.axis)
def map_dropout(self, node):
return PaddleNode('dropout', node.parameters.dropout_ratio)
def map_batch_norm(self, node):
scale_offset = len(node.data) == 4
#this default value comes from caffe's param in batch_norm
default_eps = 1e-5
kwargs = {'scale_offset': scale_offset}
if node.parameters.eps != default_eps:
kwargs['eps'] = node.parameters.eps
return MaybeActivated(
node, default=False)('batch_normalization', **kwargs)
def map_eltwise(self, node):
operations = {0: 'multiply', 1: 'add', 2: 'max'}
op_code = node.parameters.operation
try:
return PaddleNode(operations[op_code])
except KeyError:
raise KaffeError('Unknown elementwise operation: {}'.format(
op_code))
def map_scale(self, node):
params = node.parameters
return PaddleNode('scale', axis=params.axis, num_axes=params.num_axes)
def commit(self, chains):
return chains
class PaddleEmitter(object):
def __init__(self, tab=None):
self.tab = tab or ' ' * 4
self.prefix = ''
self.net_name = ''
def indent(self):
self.prefix += self.tab
def outdent(self):
self.prefix = self.prefix[:-len(self.tab)]
def statement(self, s):
return self.prefix + s + '\n'
def emit_imports(self):
import inspect
codes = []
codes.append(
'### generated by caffe2fluid, your net is in class "%s" ###\n' %
(self.net_name))
network_source = inspect.getsource(network)
codes.append(network_source + '\n')
return self.statement('\n'.join(codes))
def emit_setup_def(self):
return self.statement('def setup(self):')
def get_inputs_info(self, input_nodes):
input_shapes = {}
for n in input_nodes:
name = n.name
output_shape = n.output_shape
shape = [str(s) for s in output_shape[1:]]
input_shapes[name] = ', '.join(shape)
input_shapes = ['"%s": [%s]' % (n, l) for n, l in input_shapes.items()]
shape_str = ','.join(input_shapes)
return '{%s}' % (shape_str)
def emit_main_def(self, name):
if name is None:
return ''
self.prefix = ''
main_def = self.statement('if __name__ == "__main__":')
self.indent()
main_def += self.statement('exit(main())')
return '\n\n' + main_def
def emit_parents(self, chain):
assert len(chain)
s = 'self.feed('
sep = ', \n' + self.prefix + (' ' * len(s))
s += sep.join(
["'%s'" % parent.name for parent in chain[0].node.parents])
return self.statement(s + ')')
def emit_node(self, node):
return self.statement('self.' + node.emit())
def emit(self, name, chains, input_nodes=None):
from ..net_template import generate_net_code
from ..net_template import generate_main_code
self.net_name = name
inputs_info = self.get_inputs_info(input_nodes)
s = self.emit_imports()
s += generate_net_code(name, inputs_info) + '\n'
self.indent()
# define the net using api
s += self.emit_setup_def()
self.indent()
blocks = []
for chain in chains:
b = ''
b += self.emit_parents(chain)
for node in chain:
b += self.emit_node(node)
blocks.append(b[:-1])
s = s + '\n\n'.join(blocks)
# define the main function
s += '\n\n\n' + generate_main_code(name)
s += self.emit_main_def(name)
return s
class Transformer(object):
def __init__(self, def_path, data_path, verbose=True, phase='test'):
self.verbose = verbose
self.phase = phase
self.load(def_path, data_path, phase)
self.params = None
self.source = None
def load(self, def_path, data_path, phase):
# Build the graph
graph = GraphBuilder(def_path, phase).build()
if data_path is not None:
# Load and associate learned parameters
graph = DataInjector(def_path, data_path)(graph)
# Transform the graph
transformers = [
# Fuse split batch normalization layers
BatchNormScaleBiasFuser(),
# Fuse ReLUs
# TODO: Move non-linearity application to layer wrapper, allowing
# any arbitrary operation to be optionally activated.
ReLUFuser(allowed_parent_types=[
NodeKind.Convolution, NodeKind.InnerProduct, NodeKind.BatchNorm
]),
# Rename nodes
# Slashes are used for scoping in Paddle. Replace slashes
# in node names with underscores.
# (Caffe's GoogLeNet implementation uses slashes)
NodeRenamer(lambda node: node.name.replace('/', '_')),
# Fuse Crop
# Crop is to return a scalar output Blob for an input Blob of arbitrary size.
# When one of the input Blob is "input" or "DummyData", we can remove this input Blob
# and put the shape into the reduction layer.
CropFuser()
]
self.graph = graph.transformed(transformers)
#for the purpose of recording name mapping because of fused nodes
trace = SubNodeFuser.traced_names()
chg2real = {}
deleted = {}
for k, v in trace.items():
chg2real[k] = v[-1] #mapping from changed-name to real-name
for n in v:
if n in chg2real:
continue
if n not in deleted:
deleted[n] = '%s.%s' % (k, v[-1])
self.graph.add_name_trace({
'chg2real': chg2real,
'deleted': deleted
}, 'paddle')
# Display the graph
if self.verbose:
print_stderr(self.graph)
def transform_data(self):
if self.params is None:
transformers = [
# Reshape the parameters to Paddle's ordering
DataReshaper({
# (c_o, c_i) -> (c_i, c_o)
NodeKind.InnerProduct: (1, 0)
}),
# Pre-process batch normalization data
BatchNormPreprocessor(),
# Convert parameters to dictionaries
ParameterNamer(),
]
self.graph = self.graph.transformed(transformers)
self.params = {
node.name: node.data
for node in self.graph.nodes if node.data
}
self.params['caffe2fluid_name_trace'] = self.graph.get_name_trace()
return self.params
def transform_source(self):
if self.source is None:
mapper = PaddleMapper(self.graph)
chains = mapper.map()
emitter = PaddleEmitter()
input_nodes = self.graph.get_input_nodes()
self.source = emitter.emit(self.graph.name, chains, input_nodes)
return self.source
此差异已折叠。
此差异已折叠。
此差异已折叠。
# 环境安装
caffe2fluid在如下环境配置中进行测试,用户可按如下流程配置自己的环境,也可根据自己需求配置,满足caffe2fluid运行对环境的依赖即可。
## 1. 安装Anaconda
可直接参考官网安装文档
[Linux下安装](https://docs.anaconda.com/anaconda/install/linux/)
[Mac下安装](https://docs.anaconda.com/anaconda/install/mac-os/)
## 2.创建python环境
通过使用anaconda,创建python环境,在创建的python环境中安装Caffe和PaddlePaddle,创建的环境可以独立于系统环境,对创建环境的修改,也不会影响其它环境或系统的依赖。
```shell
# 创建名为caffe_paddle的环境,python版本指定为3.5
conda create -n caffe-paddle python=3.5
# 激活环境
source activate caffe-paddle
# 安装PaddlePaddle和Caffe
# 安装后,可在python中执行"import caffe"和
# "import paddle.fluid",判断是否已经安装成功
pip install paddlepaddle-gpu
conda install caffe-gpu
# 安装python的future模块
pip install future
# 注意:由于protobuf版本问题,安装框架过程应先安装PaddlePaddle,再安装Caffe。
# 如若先安装了Caffe,则可以在安装PaddlePaddle后执行下述命令解决
pip uninstall protobuf
pip install protobuf==3.6.0
source deactivate
```
## 3. 在创建的python环境中使用caffe2fluid
在第2步安装中,需要注意到这两行命令
```shell
source activate caffe-paddle
source deactivate
```
**1. 第一行表示激活创建的环境,在使用caffe2fluid时需执行该行命令进入环境**
**2. 第二行表示退出环境**
# Environment Installation
The caffe2fluid is tested in the following environment configuration. In order to meet the environment dependence of the caffe2fluid, users can configure their own environment according to the following process, or configure according to their own needs.
## 1. Anaconda Installation
Directly refer to the official website installation documentation.
[Install in Linux](https://docs.anaconda.com/anaconda/install/linux/)
[Install in Mac](https://docs.anaconda.com/anaconda/install/mac-os/)
## 2.Create Python Environment
Create a python environment by using anaconda. Then install Caffe and PaddlePaddle in the created python environment. The created environment can be independent of the system environment, so the modifications to the creation environment will not affect the dependencies of other environments or systems.
```shell
# Create the environment which is named as caffe_paddle,
# and the version of python is 3.5.
conda create -n caffe-paddle python=3.5
# Activate the environment.
source activate caffe-paddle
# Install the PaddlePaddle and Caffe.
# After installion,run "import caffe" and "import paddle.fluid"
# to determine if it has been installed successfully.
pip install paddlepaddle-gpu
conda install caffe-gpu
# Install the future module of python。
pip install future
# Note: Due to the protobuf version, the installation framework should first install PaddlePaddle and then install Caffe.
# If you installed Caffe first, after installing PaddlePaddle you can solve by the following steps.
pip uninstall protobuf
pip install protobuf==3.6.0
source deactivate
```
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
-e .
onnx>=1.4
paddlepaddle>=1.5
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册