未验证 提交 14cf068a 编写于 作者: C channings 提交者: GitHub

Merge pull request #1 from PaddlePaddle/develop

pull
......@@ -2,3 +2,12 @@
**Q1. TensorFlow模型转换过程中,提示『Unknown shape for input tensor[tensor name: "input"], Please define shape of input here』?**
A:该提示信息表示无法从TensorFlow的pb模型中获取到输入tensor(tensor名为"input:)的shape信息,所以需要用户手动在提示后输入详细的shape信息,如None,224,224,3 其中None表示Batch
**Q2. TensorFlow模型转换失败怎么解决?**
A: 如果并非是由缺少OP导致,那可能是由于TensorFlow模型转换时(NHWC->NCHW格式转换导致),在这种情况下,采用如下方式进行转换,同时固化输入大小的方式,继续尝试转换,见如下命令,转换过程中,根据提示,输入相应tensor的固化shape大小
```
x2paddle -f tensorflow -m tf.pb -s pd-model --without_data_format_optimization --define_input_shape
```
> 1. 目前Tensorflow的CV模型大部分均为`NHWC`的输入格式,而Paddle的默认输入格式为`NCHW`,因此X2Paddle在转换过程中,会对如`axis`, `shape`等参数进行转换,适应Paddle的NCHW格式。但在这种情况下,可能会由于TensorFlow模型太复杂,导致出错。 指定`--without_data_format_optimization`后,会停止对`axis`,`shape`等参数的优化(这可能会带来一定数量的transpose操作)
......@@ -4,22 +4,38 @@
X2Paddle支持将其余深度学习框架训练得到的模型,转换至PaddlePaddle模型。
X2Paddle is a toolkit for converting trained model to PaddlePaddle from other deep learning frameworks.
## 转换模型库
X2Paddle在多个主流的CV模型上,测试过TensorFlow/Caffe/ONNX模型的转换,可以在[X2Paddle-Model-Zoo](x2paddle_model_zoo.md)查看我们的模型测试列表。如果你在新的模型上进行了测试转换,也欢迎继续补充该列表;如若无法转换,可通过ISSUE反馈给我们,我们会尽快跟进。
## 环境依赖
python >= 3.5
paddlepaddle >= 1.5.0
**以下依赖只需对应安装自己需要的即可**
转换tensorflow模型 : tensorflow == 1.14.0
转换caffe模型 : caffe == 1.0.0
转换onnx模型 : onnx == 1.5.0 pytorch == 1.1.0
**按需安装以下依赖**
tensorflow : tensorflow == 1.14.0
caffe : 无
onnx : onnx == 1.5.0 pytorch == 1.1.0
## 安装
### 安装方式一(推荐)
使用最新的代码版本,可使用如下方式进行安装
```
pip install git+https://github.com/PaddlePaddle/X2Paddle.git@develop
```
### 安装方式二
我们会定期更新pip源上的x2paddle版本
```
pip install x2paddle
```
如果需要使用最新的代码版本,可使用如下方式进行安装
### 安装方式三
```
pip install git+https://github.com/PaddlePaddle/X2Paddle.git@develop
git clone https://github.com/PaddlePaddle/X2Paddle.git
cd X2Paddle
git checkout develop
python setup.py install
```
## 使用方法
......@@ -38,30 +54,42 @@ x2paddle --framework=onnx --model=onnx_model.onnx --save_dir=pd_model
### 参数选项
| 参数 | |
|----------|--------------|
|--framework | 源模型类型 (tensorflow、caffe) |
|--framework | 源模型类型 (tensorflow、caffe、onnx) |
|--prototxt | 当framework为caffe时,该参数指定caffe模型的proto文件路径 |
|--weight | 当framework为caffe时,该参数指定caffe模型的参数文件路径 |
|--save_dir | 指定转换后的模型保存目录路径 |
|--model | 当framework为tensorflow时,该参数指定tensorflow的pb模型文件路径 |
|--caffe_proto | [可选]由caffe.proto编译成caffe_pb2.py文件的存放路径,当没有安装caffe或者使用自定义Layer时使用,默认为None |
|--model | 当framework为tensorflow/onnx时,该参数指定tensorflow的pb模型文件或onnx模型路径 |
|--caffe_proto | **[可选]** 由caffe.proto编译成caffe_pb2.py文件的存放路径,当存在自定义Layer时使用,默认为None |
|--without_data_format_optimization | **[可选]** For TensorFlow, 当指定该参数时,关闭NHWC->NCHW的优化,见[文档Q2](FAQ.md) |
|--define_input_shape | **[可选]** For TensorFlow, 当指定该参数时,强制用户输入每个Placeholder的shape,见[文档Q2](FAQ.md) |
## 使用转换后的模型
转换后的模型包括`model_with_code``inference_model`两个目录。
`model_with_code`中保存了模型参数,和转换后的python模型代码
`inference_model`中保存了序列化的模型结构和参数,可直接使用paddle的接口进行加载,见[load_inference_model](https://www.paddlepaddle.org.cn/documentation/docs/zh/1.5/api_guides/low_level/inference.html#api-guide-inference)
## 小工具
X2Paddle提供了工具解决如下问题,详见[tools/README.md](tools/README.md)
1. 检测模型是否在PaddleLite中支持
2. 合并模型参数文件
## 相关文档
1. [X2Paddle使用过程中常见问题](FAQ.md)
2. [如何导出TensorFlow的pb模型](export_tf_model.md)
3. [X2Paddle测试模型库](x2paddle_model_zoo.md)
3. [X2Paddle测试模型库](x2paddle_model_zoo.md)
4. [PyTorch模型导出为ONNX模型](pytorch_to_onnx.md)
5. [X2Paddle内置的Caffe自定义层](caffe_custom_layer.md)
## 更新历史
2019.08.05
1. 统一tensorflow/caffe(onnx的代码即将release)模型转换代码和对外接口
1. 统一tensorflow/caffe/onnx模型转换代码和对外接口
2. 解决上一版caffe2fluid无法转换多分支模型的问题
3. 解决Windows上保存模型无法加载的问题
4. 新增optimizer,优化代码结构,合并conv、batch_norm的bias和激活函数
> X2Paddle 0.3及之前版本各子转换工具相互独立,在0.4版本,我们将tensorflow/caffe/onnx三个工具的代码整合,提供更易用的对外使用方式。用户可继续在[分支release-0.3](https://github.com/PaddlePaddle/X2Paddle/tree/release-0.3)中访问和使用之前的版本。
**如果你需要之前版本的tensorflow2fluid/caffe2fluid/onnx2fluid,可以继续访问release-0.3分支,获取之前版本的代码使用。**
## Acknowledgements
......
## 如何转换Caffe自定义Layer
本文档介绍如何将Caffe自定义Layer转换为PaddlePaddle模型中的对应实现, 用户可根据自己需要,添加代码实现自定义层,从而支持模型的完整转换。
***步骤一 下载代码***
此处涉及修改源码,应先卸载x2paddle,并且下载源码,主要有以下两步完成:
```
pip uninstall x2paddle
pip install git+https://github.com/PaddlePaddle/X2Paddle.git@develop
```
***步骤二 编译caffe.proto***
该步骤依赖protobuf编译器,其安装过程有以下两种方式:
> 选择一:pip install protobuf
> 选择二:使用[官方源码](https://github.com/protocolbuffers/protobuf)进行编译
使用脚本./tools/compile.sh将caffe.proto(包含所需的自定义Layer信息)编译成我们所需的目标语言(Python)
使用方式:
```
bash ./toos/compile.sh /home/root/caffe/src/caffe/proto
# /home/root/caffe/src/caffe/proto为caffe.proto的存放路径,生成的caffe_pb2.py也将保存在该路径下
```
***步骤三 添加自定义Layer的实现代码***
- 进入./x2paddle/op_mapper/caffe_custom_layer,创建.py文件,例如mylayer.py
- 仿照./x2paddle/op_mapper/caffe_custom_layer中的其他文件,在mylayer.py中主要需要实现3个函数,下面以roipooling.py为例分析代码:
1. `def roipooling_shape(input_shape, pooled_w=None, pooled_h=None)`
参数:
1. input_shape(list):其中每个元素代表该层每个输入数据的shape,为必须传入的参数
2. pooled_w(int):代表ROI Pooling的kernel的宽,其命名与.prototxt中roi_pooling_param中的key一致
3. pooled_h(int):代表ROI Pooling的kernel的高,其命名与.prototxt中roi_pooling_param中的key一致
功能:计算出进行ROI Pooling后的shape
返回:一个list,其中每个元素代表每个输出数据的shape,由于ROI Pooling的输出数据只有一个,所以其list长度为1
2. `def roipooling_layer(inputs, input_shape=None, name=None, pooled_w=None, pooled_h=None, spatial_scale=None)`
参数:
1. inputs(list):其中每个元素代表该层每个输入数据,为必须传入的参数
2. input_shape(list):其中每个元素代表该层每个输入数据的shape,为必须传入的参数
3. name(str):ROI Pooling层的名字,为必须传入的参数
4. pooled_w(int):代表ROI Pooling的kernel的宽,其命名与.prototxt中roi_pooling_param中的key一致
5. pooled_h(int):代表ROI Pooling的kernel的高,其命名与.prototxt中roi_pooling_param中的key一致
6. spatial_scale(float):用于将ROI坐标从输入比例转换为池化时使用的比例,其命名与.prototxt中roi_pooling_param中的key一致
功能:运用PaddlePaddle完成组网来实现`roipooling_layer`的功能
返回:一个Variable,为组网后的结果
3. `def roipooling_weights(name, data=None)`
参数:
1. name(str):ROI Pooling层的名字,为必须传入的参数
2. data(list):由Caffe模型.caffemodel获得的关于roipooling的参数,roipooling的参数为None
功能:为每个参数(例如kernel、bias等)命名;同时,若Caffe中该层参数与PaddlePaddle中参数的格式不一致,则变换操作也在该函数中实现。
返回:一个list,包含每个参数的名字。
- 在roipooling.py中注册`roipooling`,主要运用下述代码实现:
```
register(kind='ROIPooling', shape=roipooling_shape, layer=roipooling_layer, weights=roipooling_weights)
# kind为在model.prototxt中roipooling的type
```
- 在./x2paddle/op_mapper/caffe_custom_layer/\_\_init\_\_.py中引入该层的使用
```
from . import roipooling
```
***步骤四 运行转换代码***
```
# 在X2Paddle目录下安装x2paddle
python setup.py install
# 运行转换代码
x2paddle --framework=caffe
--prototxt=deploy.proto
--weight=deploy.caffemodel
--save_dir=pd_model
--caffe_proto=/home/root/caffe/src/caffe/proto/caffe_pb2.py
```
目前,代码中已经提供了8个非官方op(不在[官网](http://caffe.berkeleyvision.org/tutorial/layers)上的op)的转换,这些op对应的Caffe实现源码如下:
| op | 该版本实现源码 |
|-------|--------|
| PriorBox | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/prior_box_layer.cpp) |
| DetectionOutput | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/detection_output_layer.cpp) |
| ConvolutionDepthwise | [code](https://github.com/farmingyard/caffe-mobilenet/blob/master/conv_dw_layer.cpp) |
| ShuffleChannel | [code](https://github.com/farmingyard/ShuffleNet/blob/master/shuffle_channel_layer.cpp) |
| Permute | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/permute_layer.cpp) |
| Normalize | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/normalize_layer.cpp) |
| ROIPooling | [code](https://github.com/rbgirshick/caffe-fast-rcnn/blob/0dcd397b29507b8314e252e850518c5695efbb83/src/caffe/layers/roi_pooling_layer.cpp) |
| Axpy | [code](https://github.com/hujie-frank/SENet/blob/master/src/caffe/layers/axpy_layer.cpp) |
## PyTorch模型导出为ONNX模型
目前onnx2paddle主要支持onnx operator version 9。 用户可通过如下示例代码,将torchvison或者自己开发写的模型转换成onnx model:
```
#coding: utf-8
import torch
import torchvision
# 指定输入大小的shape
dummy_input = torch.randn(1, 3, 224, 224)
# 构建pytorch model,并载入模型参数
resnet18 = torchvision.models.resnet18(pretrained=True)
# 导出resnet18.onnx模型文件
torch.onnx.export(resnet18, dummy_input, "resnet18.onnx",verbose=True)
```
### 一、PaddleLite部署
使用X2Paddle转换后的模型均可以使用Paddle Fluid进行预测。但对于PaddleLite上的部署,则需要检查模型中的OP是否都在PaddleLite中支持。使用`check_for_lite.py`可以进行检查。
```
python tools/check_for_lite.py paddle_model/inference_model/__model__
```
### 二、模型参数合并
X2Paddle转换后产出的路径下包括两个目录,
1. `model_with_code`: 包含保存的参数文件和模型python代码文件,供用户debug
2. `inference_model`: 参数文件和序列化的模型结构文件,供用户预测部署
其中在`inference_model`中,X2Paddle将每个参数独立保存在不同的文件中(文件名和参数名一致),用户可使用`merge_params.py`将参数文件合并成一个文件使用
```
python tools/merge_params.py paddle_model/inference_model new_model_dir
```
合并参数后的模型保存在`new_model_dir`
import urllib
import sys
from paddle.fluid.framework import Program
ops_h = "https://raw.githubusercontent.com/PaddlePaddle/Paddle-Lite/develop/lite/api/_paddle_use_ops.h"
try:
fetch = urllib.urlretrieve(ops_h, "./_paddle_use_ops.h")
except:
fetch = urllib.request.urlretrieve(ops_h, "./_paddle_use_ops.h")
ops = list()
with open("./_paddle_use_ops.h") as f:
for line in f:
if "USE_LITE_OP" in line:
op = line.strip().split('(')[1].split(')')[0]
ops.append(op)
model_file = sys.argv[1]
with open(model_file, 'rb') as f:
program = Program.parse_from_string(f.read())
unsupported_ops = set()
for op in program.blocks[0].ops:
if op.type not in ops:
unsupported_ops.add(op.type)
nums = len(unsupported_ops)
if len(unsupported_ops) > 0:
print("========= {} OPs are not supported in Paddle-Lite=========".format(
nums))
for op in unsupported_ops:
print("========= {} ========".format(op))
else:
print("\n========== Good News! ========")
print("Good! All ops in this model are supported in Paddle-Lite!\n")
#!/bin/bash
#function:
# script used to generate caffe_pb2.py from caffe.proto using protoc
#
PROTOC=`which protoc`
if [[ -z $PROTOC ]];then
echo "not found protoc, you should first install it following this[https://github.com/google/protobuf/releases]"
exit 1
fi
WORK_ROOT=$1
PY_NAME="$WORK_ROOT/caffe_pb2.py"
$PROTOC --proto_path=$WORK_ROOT --python_out=$WORK_ROOT $WORK_ROOT/caffe.proto
ret=$?
if [ -e "$PY_NAME" ];then
echo "succeed to generate [$PY_NAME]"
exit 0
else
echo "failed to generate [$PY_NAME]"
fi
exit $ret
import paddle.fluid as fluid
import sys
model_dir = sys.argv[1]
new_model_dir = sys.argv[2]
exe = fluid.Executor(fluid.CPUPlace())
[inference_program, feed_target_names,
fetch_targets] = fluid.io.load_inference_model(dirname=model_dir, executor=exe)
print(feed_target_names)
fluid.io.save_inference_model(dirname=new_model_dir,
feeded_var_names=feed_target_names,
target_vars=fetch_targets,
executor=exe,
main_program=inference_program,
params_filename="__params__")
__version__ = "0.4.1"
__version__ = "0.4.5"
......@@ -23,7 +23,7 @@ def arg_parser():
"-m",
type=_text_type,
default=None,
help="model file path")
help="define model file path for tensorflow or onnx")
parser.add_argument("--prototxt",
"-p",
type=_text_type,
......@@ -39,29 +39,47 @@ def arg_parser():
type=_text_type,
default=None,
help="path to save translated model")
parser.add_argument("--framework",
"-f",
type=_text_type,
default=None,
help="define which deeplearning framework")
parser.add_argument(
"--framework",
"-f",
type=_text_type,
default=None,
help="define which deeplearning framework(tensorflow/caffe/onnx)")
parser.add_argument(
"--caffe_proto",
"-c",
type=_text_type,
default=None,
help="the .py file compiled by caffe proto file of caffe model")
help="optional: the .py file compiled by caffe proto file of caffe model"
)
parser.add_argument("--version",
"-v",
action="store_true",
default=False,
help="get version of x2paddle")
parser.add_argument(
"--without_data_format_optimization",
"-wo",
action="store_true",
default=False,
help="tf model conversion without data format optimization")
parser.add_argument("--define_input_shape",
"-d",
action="store_true",
default=False,
help="define input shape for tf model")
return parser
def tf2paddle(model_path, save_dir):
def tf2paddle(model_path,
save_dir,
without_data_format_optimization=False,
define_input_shape=False):
# check tensorflow installation and version
try:
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = '3'
import tensorflow as tf
version = tf.__version__
if version >= '2.0.0' or version < '1.0.0':
......@@ -75,27 +93,47 @@ def tf2paddle(model_path, save_dir):
from x2paddle.decoder.tf_decoder import TFDecoder
from x2paddle.op_mapper.tf_op_mapper import TFOpMapper
from x2paddle.op_mapper.tf_op_mapper_nhwc import TFOpMapperNHWC
from x2paddle.optimizer.tf_optimizer import TFOptimizer
print("Now translating model from tensorflow to paddle.")
model = TFDecoder(model_path)
mapper = TFOpMapper(model)
optimizer = TFOptimizer(mapper)
# neccesary optimization
optimizer.delete_redundance_code()
# optimizer below is experimental
optimizer.merge_activation()
optimizer.merge_bias()
model = TFDecoder(model_path, define_input_shape=define_input_shape)
if not without_data_format_optimization:
mapper = TFOpMapper(model)
optimizer = TFOptimizer(mapper)
# neccesary optimization
optimizer.delete_redundance_code()
# optimizer below is experimental
optimizer.merge_activation()
optimizer.merge_bias()
optimizer.merge_batch_norm()
optimizer.merge_prelu()
else:
mapper = TFOpMapperNHWC(model)
optimizer = TFOptimizer(mapper)
optimizer.delete_redundance_code()
optimizer.strip_graph()
optimizer.merge_activation()
optimizer.merge_bias()
optimizer.make_nchw_input_output()
optimizer.remove_transpose()
mapper.save_inference_model(save_dir)
def caffe2paddle(proto, weight, save_dir, caffe_proto):
from x2paddle.decoder.caffe_decoder import CaffeDecoder
from x2paddle.op_mapper.caffe_op_mapper import CaffeOpMapper
from x2paddle.optimizer.caffe_optimizer import CaffeOptimizer
import google.protobuf as gpb
ver_str = gpb.__version__.replace('.', '')
ver_int = int(ver_str[0:2])
assert ver_int >= 36, 'The version of protobuf must be larger than 3.6.0!'
print("Now translating model from caffe to paddle.")
model = CaffeDecoder(proto, weight, caffe_proto)
mapper = CaffeOpMapper(model)
optimizer = CaffeOptimizer(mapper)
optimizer.merge_bn_scale()
optimizer.merge_op_activation()
mapper.save_inference_model(save_dir)
......@@ -139,13 +177,17 @@ def onnx2paddle(model_path, save_dir):
def main():
if len(sys.argv) < 2:
print("Use \"x2paddle -h\" to print the help information")
print("For more information, please follow our github repo below:)")
print("\nGithub: https://github.com/PaddlePaddle/X2Paddle.git\n")
return
parser = arg_parser()
args = parser.parse_args()
if args.version:
print("x2paddle-{} with python>=3.5\n".format(x2paddle.__version__))
import x2paddle
print("x2paddle-{} with python>=3.5, paddlepaddle>=1.5.0\n".format(
x2paddle.__version__))
return
try:
......@@ -156,12 +198,19 @@ def main():
return
except:
print("paddlepaddle not installed, use \"pip install paddlepaddle\"")
assert args.framework is not None, "--from is not defined(tensorflow/caffe)"
assert args.framework is not None, "--framework is not defined(support tensorflow/caffe/onnx)"
assert args.save_dir is not None, "--save_dir is not defined"
if args.framework == "tensorflow":
assert args.model is not None, "--model should be defined while translating tensorflow model"
tf2paddle(args.model, args.save_dir)
without_data_format_optimization = False
define_input_shape = False
if args.without_data_format_optimization:
without_data_format_optimization = True
if args.define_input_shape:
define_input_shape = True
tf2paddle(args.model, args.save_dir, without_data_format_optimization,
define_input_shape)
elif args.framework == "caffe":
assert args.prototxt is not None and args.weight is not None, "--prototxt and --weight should be defined while translating caffe model"
......
......@@ -14,6 +14,7 @@
from x2paddle.core.graph import GraphNode
import collections
from x2paddle.core.util import *
class Layer(object):
......@@ -63,11 +64,8 @@ class Layer(object):
else:
layer_code = layer_code + key + "={}, ".format(
input.layer_name)
elif isinstance(input, str):
layer_code = layer_code + key + "={}, ".format(input)
else:
raise Exception(
"Element of inputs should GraphNode or String")
layer_code = layer_code + key + "={}, ".format(input)
elif isinstance(self.inputs, GraphNode):
if hasattr(self.inputs, "index"):
layer_code += (self.inputs.layer_name +
......@@ -81,6 +79,8 @@ class Layer(object):
param_attr = collections.OrderedDict(self.param_attr)
for key, value in param_attr.items():
if '\n' in str(value):
value = string(str(value).replace('\n', ','))
layer_code = layer_code + key + "={}, ".format(value)
layer_code = layer_code.strip(", ")
......
......@@ -85,7 +85,7 @@ class Graph(object):
node.index = int(idx)
return node
else:
raise Exception("Graph doesn't have node [%s]." % name)
return None
else:
if copy:
node = cp.copy(self.node_map[name])
......
......@@ -97,6 +97,11 @@ class OpMapper(object):
import model
try:
inputs, outputs = model.x2paddle_net()
for i, out in enumerate(outputs):
if isinstance(out, list):
for out_part in out:
outputs.append(out_part)
del outputs[i]
input_names = [input.name for input in inputs]
exe = fluid.Executor(fluid.CPUPlace())
exe.run(fluid.default_startup_program())
......@@ -143,6 +148,8 @@ class OpMapper(object):
for i in range(len(self.graph.topo_sort)):
node_name = self.graph.topo_sort[i]
node = self.graph.get_node(node_name)
if node is None:
continue
if len(node.fluid_code.layers) == 0:
continue
self.add_codes(node.fluid_code.gen_codes(), 1)
......
......@@ -26,6 +26,11 @@ def string(param):
def run_net(param_dir="./"):
import os
inputs, outputs = x2paddle_net()
for i, out in enumerate(outputs):
if isinstance(out, list):
for out_part in out:
outputs.append(out_part)
del outputs[i]
exe = fluid.Executor(fluid.CPUPlace())
exe.run(fluid.default_startup_program())
......
......@@ -47,7 +47,7 @@ class CaffeResolver(object):
class CaffeGraphNode(GraphNode):
def __init__(self, layer, layer_name=None):
def __init__(self, layer, type_str, layer_name=None):
if layer_name is None:
super(CaffeGraphNode,
self).__init__(layer,
......@@ -56,28 +56,18 @@ class CaffeGraphNode(GraphNode):
super(CaffeGraphNode,
self).__init__(layer,
layer_name.replace('/', '_').replace('-', '_'))
self.layer_type = layer.type
self.layer_type = type_str
self.fluid_code = FluidCode()
self.data = None
def set_params(self, params):
self.data = params
def set_output_shape(self, input_shape, is_input=True):
func_name = 'shape_' + self.layer_type.lower()
if is_input:
self.output_shape = getattr(caffe_shape, func_name)(self.layer,
input_shape)
else:
self.output_shape = input_shape
def set_input_shape(self, input_shape):
self.input_shape = input_shape
class CaffeGraph(Graph):
def __init__(self, model, params):
def __init__(self, model, params, caffe_pb):
self.params = params
self.caffe_pb = caffe_pb
super(CaffeGraph, self).__init__(model)
def filter_layers(self, layers):
......@@ -86,6 +76,9 @@ class CaffeGraph(Graph):
filtered_layer_names = set()
filtered_layers = []
for layer in layers:
if hasattr(layer, 'input'):
continue
type_str = self.get_layer_type(layer)
phase = 'test'
if len(layer.include):
phase = phase_map[layer.include[0].phase]
......@@ -96,7 +89,7 @@ class CaffeGraph(Graph):
# test-time networks. These are just ignored. We'll
# filter them out here.
if (not exclude) and (phase == 'test'):
exclude = (layer.type == 'Dropout')
exclude = (type_str == 'Dropout')
if not exclude:
filtered_layers.append(layer)
# Guard against dupes.
......@@ -106,13 +99,85 @@ class CaffeGraph(Graph):
print('The filter layer:' + layer.name)
return filtered_layers
def generate_input_layer(self, dims, index):
dim_str = ''
for dim in dims:
dim_str += 'dim: {}\n'.format(str(dim))
input_str = 'layer {\n'
input_str += 'name: \"{}\"\n '.format(str(self.model.input[index]))
input_str += 'type: "Input"\n'
input_str += 'top: \"{}\"\n'.format(str(self.model.input[index]))
input_str += 'input_param {\n'
input_str += 'shape {\n'
input_str += dim_str
input_str += '}}}'
input_str = str.encode(input_str)
net = self.caffe_pb.NetParameter()
text_format.Merge(input_str, net)
return net.layers or net.layer
def input2layers(self, input_layers=[]):
inputs_num = len(self.model.input)
if inputs_num != 0:
input_dims_num = len(self.model.input_dim)
if input_dims_num != 0:
if input_dims_num > 0 and input_dims_num != inputs_num * 4:
raise Error('invalid input_dim[%d] param in prototxt' %
(input_dims_num))
for i in range(inputs_num):
dims = self.model.input_dim[i * 4:(i + 1) * 4]
l = self.generate_input_layer(dims, i)
input_layers.append(l[0])
else:
for i in range(inputs_num):
dims = self.model.input_shape[i].dim[0:4]
l = self.generate_input_layer(dims, i)
input_layers.append(l[0])
def transform_input_layers(self, layers, input_layers=[]):
for layer in layers:
if hasattr(layer, 'input'):
input_dims_num = len(layers.input_dim)
if input_dims_num > 0 and input_dims_num != 4:
raise Error('invalid input_dim[%d] param in prototxt' %
(input_dims_num))
dims = self.model.input_dim[0:4]
l = self.generate_input_layer(dims, i)
input_layers.append(l[0])
def get_layer_type(self, layer):
if isinstance(layer.type, int):
enum_values = self.caffe_pb._V1LAYERPARAMETER_LAYERTYPE.values
vals = [val for val in enum_values if val.number == layer.type]
part = vals[0].name.split('_')
part = [s.capitalize() for s in part]
type_str = ''
type_str = type_str.join(part)
if 'relu' in type_str.lower():
type_str = type_str.replace('elu', 'eLU')
elif type_str.lower() == 'lrn':
type_str = 'LRN'
return type_str
else:
return layer.type
def build(self):
layers = self.model.layers or self.model.layer
layers = self.filter_layers(layers)
input_layers = []
self.input2layers(input_layers)
self.transform_input_layers(layers, input_layers)
layers = input_layers + layers
top_layer = {}
for layer in layers:
self.node_map[layer.name] = CaffeGraphNode(layer)
if hasattr(layer, 'input'):
continue
type_str = self.get_layer_type(layer)
self.node_map[layer.name] = CaffeGraphNode(layer, type_str)
for in_name in layer.bottom:
if in_name in top_layer:
self.connect(top_layer[in_name][-1], layer.name)
......@@ -125,7 +190,6 @@ class CaffeGraph(Graph):
top_layer[out_name] = [layer.name]
else:
top_layer[out_name].append(layer.name)
for layer_name, data in self.params:
if layer_name in self.node_map:
node = self.node_map[layer_name]
......@@ -157,19 +221,26 @@ class CaffeDecoder(object):
self.resolver = CaffeResolver(caffe_proto=caffe_proto)
self.net = self.resolver.NetParameter()
with open(proto_path, 'rb') as proto_file:
proto_str = self.old2new(proto_file)
proto_str = proto_file.read()
text_format.Merge(proto_str, self.net)
self.load_using_pb()
self.caffe_graph = CaffeGraph(self.net, self.params)
self.caffe_graph = CaffeGraph(self.net, self.params,
self.resolver.caffepb)
self.caffe_graph.build()
def load_using_pb(self):
data = self.resolver.NetParameter()
data.MergeFromString(open(self.model_path, 'rb').read())
pair = lambda layer: (layer.name, self.normalize_pb_data(layer))
layers = data.layers or data.layer
import time
start = time.time()
self.params = [pair(layer) for layer in layers if layer.blobs]
end = time.time()
print('cost:', str(end - start))
def normalize_pb_data(self, layer):
transformed = []
......@@ -182,72 +253,8 @@ class CaffeDecoder(object):
c_i = blob.channels
h = blob.height
w = blob.width
data = np.array(blob.data, dtype=np.float32).reshape(c_o, c_i, h, w)
data = np.asarray(list(blob.data),
dtype=np.float32).reshape(c_o, c_i, h, w)
transformed.append(data)
return transformed
def old2new(self, proto_file):
part1_str = ''
part2_str = ''
part3_str = ''
is_input = False
dims = []
line = proto_file.readline()
print('Check if it is a new style of caffe...')
while line:
l_str = bytes.decode(line)
if l_str.replace(' ', '').startswith('input:'):
part2_str += 'layer {\n'
part2_str += (
' name: ' +
l_str.strip().replace(' ', '').split('input:')[-1] + '\n')
part2_str += ' type: \"Input\"\n'
part2_str += (
' top: ' +
l_str.strip().replace(' ', '').split('input:')[-1] + '\n')
is_input = True
line = proto_file.readline()
continue
elif l_str.replace(' ', '').startswith('input_dim:'):
dims.append(
int(l_str.strip().replace(' ', '').split('input_dim:')[-1]))
if len(dims) == 4:
part2_str += ' input_param { shape: { dim: ' + str(dims[0]) + \
' dim: ' + str(dims[1]) + \
' dim: ' + str(dims[2]) + \
' dim: ' + str(dims[3]) + ' } }\n'
dims = []
part2_str += '}\n'
line = proto_file.readline()
if bytes.decode(line).replace(' ', '').startswith('}'):
line = proto_file.readline()
continue
elif l_str.replace(' ', '').startswith('input_shape'):
part2_str += l_str.replace('input_shape',
'input_param { shape: ')
l_str = bytes.decode(proto_file.readline())
while l_str:
if '}' in l_str:
part2_str += l_str + '\n}\n}'
break
else:
part2_str += l_str
l_str = bytes.decode(proto_file.readline())
line = proto_file.readline()
continue
if not is_input:
part1_str += bytes.decode(line)
else:
part3_str += bytes.decode(line)
line = proto_file.readline()
out = part1_str + part2_str + part3_str
layer_str = 'layer{'
part = out.split(layer_str)
if len(part) == 1:
layer_str = 'layer {'
part = out.split(layer_str)
for i in range(len(part)):
if part[i].strip().replace(' ', '') == '' or part[i].count(':') > 1:
continue
out = out.replace(layer_str + part[i], part[i].replace(' ', ''))
return str.encode(out)
此差异已折叠。
......@@ -15,7 +15,6 @@
from x2paddle.core.graph import GraphNode, Graph
from x2paddle.core.fluid_code import FluidCode
from tensorflow.python.framework import tensor_util
from tensorflow.python.platform import gfile
from tensorflow.core.framework import attr_value_pb2
import tensorflow as tf
import copy as cp
......@@ -39,7 +38,7 @@ class TFGraphNode(GraphNode):
self.pd_data_format = "NCHW"
self.fluid_code = FluidCode()
self.dtype_map = {1: "float32", 3: "int32", 4: "int8", 9: "int64"}
self.dtype_map = {1: "float32", 3: "int32", 4: "uint8", 9: "int64"}
@property
def out_shapes(self):
......@@ -52,7 +51,11 @@ class TFGraphNode(GraphNode):
@property
def dtype(self):
dtype = self.layer.attr["dtype"].type
keys = ['dtype', 'Tidx', 'T', 'DstT']
for k in keys:
dtype = self.layer.attr[k].type
if dtype > 0:
break
if dtype not in self.dtype_map:
raise Exception("Dtype[{}] not in dtype_map".format(dtype))
return self.dtype_map[dtype]
......@@ -125,6 +128,8 @@ class TFGraph(Graph):
items[0] = self.identity_map[items[0]]
new_node_name = ":".join(items)
node = super(TFGraph, self).get_node(new_node_name, copy)
if node is None:
return None
if len(items) == 1 and node.layer_type in self.multi_out_ops:
node.index = 0
return node
......@@ -134,7 +139,7 @@ class TFGraph(Graph):
raise Exception("Node[{}] not in graph".format(node_name))
inputs = self.node_map[node_name].inputs
outputs = self.node_map[node_name].outputs
assert len(inputs) == 1
# assert len(inputs) == 1
input_node = self.node_map[inputs[0]]
idx = input_node.outputs.index(node_name)
del input_node.outputs[idx]
......@@ -171,7 +176,7 @@ class TFGraph(Graph):
def _remove_identity_node(self):
identity_node = list()
for node_name, node in self.node_map.items():
if node.layer_type == "Identity":
if node.layer_type == "Identity" or node.layer_type == "StopGradient":
identity_node.append(node_name)
for node_name in identity_node:
......@@ -198,18 +203,29 @@ class TFGraph(Graph):
class TFDecoder(object):
def __init__(self, pb_model, data_format="NHWC"):
self.sess = tf.Session()
def __init__(self, pb_model, data_format="NHWC", define_input_shape=False):
try:
self.sess = tf.compat.v1.Session()
except:
self.sess = tf.Session()
self.input_info = dict()
with gfile.FastGFile(pb_model, 'rb') as f:
graph_def = tf.GraphDef()
self.define_input_shape = define_input_shape
with open(pb_model, 'rb') as f:
try:
graph_def = tf.compat.v1.GraphDef()
except:
graph_def = tf.GraphDef()
graph_def.ParseFromString(f.read())
input_map = self._check_input_shape(graph_def)
self._fix_output_shape(graph_def)
self.sess.graph.as_default()
tf.import_graph_def(graph_def, name='', input_map=input_map)
self.sess.run(tf.global_variables_initializer())
try:
initializer = tf.compat.v1.global_variables_initializer()
except:
initializer = tf.global_variables_initializer()
self.sess.run(initializer)
self.tf_graph = TFGraph(
self.sess.graph._as_graph_def(add_shapes=True)[0], data_format)
......@@ -229,10 +245,14 @@ class TFDecoder(object):
if layer.op != "Placeholder":
continue
graph_node = TFGraphNode(layer)
dtype = graph_node.dtype
dtype = graph_node.layer.attr['dtype'].type
need_define_shape = 0
if not graph_node.get_attr("shape"):
if self.define_input_shape:
need_define_shape = 3
elif graph_node.layer.attr[
'shape'].shape.unknown_rank or not graph_node.get_attr(
"shape"):
need_define_shape = 1
else:
value = graph_node.layer.attr["shape"].shape
......@@ -241,13 +261,21 @@ class TFDecoder(object):
need_define_shape = 2
if need_define_shape > 0:
shape = None
if graph_node.get_attr("shape"):
value = value = graph_node.layer.attr["shape"].shape
shape = [dim.size for dim in value.dim]
if need_define_shape == 1:
print("Unknown shape for input tensor[tensor name: \"{}\"]".
format(layer.name))
else:
elif need_define_shape == 2:
print(
"\nShape[now is {}] for input tensor[tensor name: \"{}\"] not support yet"
.format(shape, layer.name))
else:
print(
"Define shape[now is {}] for input tensor[tensor name: \"{}\']"
.format(shape, layer.name))
print(
"Use your keyboard type the shape of input tensor below :)")
......@@ -264,12 +292,20 @@ class TFDecoder(object):
for dim in shape.strip().split(',')
]
assert shape.count(None) <= 1, "Only one dimension can be None"
x2paddle_input = tf.placeholder(dtype=dtype,
shape=shape,
name="x2paddle_{}".format(
layer.name))
try:
x2paddle_input = tf.compat.v1.placeholder(
dtype=dtype,
shape=shape,
name="x2paddle_{}".format(layer.name))
except:
x2paddle_input = tf.placeholder(dtype=dtype,
shape=shape,
name="x2paddle_{}".format(
layer.name))
input_map["{}:0".format(layer.name)] = x2paddle_input
shape[shape.index(None)] = -1
if shape.count(None) > 0:
shape[shape.index(None)] = -1
self.input_info["x2paddle_{}".format(layer.name)] = (shape,
dtype)
else:
......@@ -282,7 +318,6 @@ class TFDecoder(object):
# trick method
# should be removed after PaddlePaddle V1.6 been released
def infer_tensor(self, graph_node):
print("========== Use infer_tensor for tensor: ", graph_node.layer.name)
if hasattr(graph_node, "index"):
tensor_name = graph_node.layer.name + ":{}".format(graph_node.index)
else:
......@@ -298,8 +333,6 @@ class TFDecoder(object):
return self.sess.run([output_tensor], feed)[0]
def infer_shape_tensor(self, graph_node, out_shape=None):
print("========== Use infer_shape_tensor for tensor: ",
graph_node.layer.name)
if hasattr(graph_node, "index"):
tensor_name = graph_node.layer.name + ":{}".format(graph_node.index)
else:
......@@ -341,3 +374,38 @@ class TFDecoder(object):
return results[0].tolist()
else:
raise Exception("Couldn't infer a stable shape shape tensor value")
def infer_tensor_shape(self, graph_node):
if hasattr(graph_node, "index"):
tensor_name = graph_node.layer.name + ":{}".format(graph_node.index)
else:
tensor_name = graph_node.layer.name + ":0"
feed = dict()
batch_size = [2, 3, 5]
shapes = list()
for b in batch_size:
for input_name, info in self.input_info.items():
(shape, dtype) = cp.deepcopy(info)
input_tensor = self.sess.graph.get_tensor_by_name(input_name +
":0")
if shape.count(-1) > 0:
shape[shape.index(-1)] = b
feed[input_tensor] = numpy.random.random_sample(shape)
output_tensor = self.sess.graph.get_tensor_by_name(tensor_name)
shape = self.sess.run([output_tensor], feed)[0].shape
shapes.append(numpy.array(shape))
compare01 = (shapes[0] == shapes[1])
compare12 = (shapes[1] == shapes[2])
if compare01.all() and compare12.all():
return shape[0].tolist()
if (compare01 == compare12).all():
index = numpy.argwhere(compare01 == False).flatten()
if index.shape[0] != 1:
raise Exception("There's not only one unstable dimension")
if index[0] != 0:
raise Exception("Batch size not in the first dimension")
shapes[0][0] = -1
return shapes[0].tolist()
......@@ -19,20 +19,29 @@ def convolutiondepthwise_shape(input_shape,
if isinstance(kernel_size, numbers.Number):
[k_h, k_w] = [kernel_size] * 2
elif len(kernel_size) > 0:
k_h = kernel_h if kernel_h else kernel_size[0]
k_w = kernel_w if kernel_w else kernel_size[len(kernel_size) - 1]
k_h = kernel_h if kernel_h > 0 else kernel_size[0]
k_w = kernel_w if kernel_w > 0 else kernel_size[len(kernel_size) - 1]
elif kernel_h > 0 or kernel_w > 0:
k_h = kernel_h
k_w = kernel_w
[s_h, s_w] = [1, 1]
if isinstance(stride, numbers.Number):
[s_h, s_w] = [stride] * 2
elif len(stride) > 0:
s_h = stride_h if stride_h else stride[0]
s_w = stride_w if stride_w else stride[len(stride) - 1]
s_h = stride_h if stride_h > 0 else stride[0]
s_w = stride_w if stride_w > 0 else stride[len(stride) - 1]
elif stride_h > 0 or stride_w > 0:
s_h = stride_h
s_w = stride_w
[p_h, p_w] = [0, 0]
if isinstance(pad, numbers.Number):
[p_h, p_w] = [pad] * 2
elif len(pad) > 0:
p_h = pad_h if pad_h else pad[0]
p_w = pad_w if pad_w else pad[len(pad) - 1]
p_h = pad_h if pad_h > 0 else pad[0]
p_w = pad_w if pad_w > 0 else pad[len(pad) - 1]
elif pad_h > 0 or pad_w > 0:
p_h = pad_h
p_w = pad_w
dila_len = len(dilation)
dila_h = 1
dila_w = 1
......@@ -74,20 +83,29 @@ def convolutiondepthwise_layer(inputs,
if isinstance(kernel_size, numbers.Number):
[k_h, k_w] = [kernel_size] * 2
elif len(kernel_size) > 0:
k_h = kernel_h if kernel_h else kernel_size[0]
k_w = kernel_w if kernel_w else kernel_size[len(kernel_size) - 1]
k_h = kernel_h if kernel_h > 0 else kernel_size[0]
k_w = kernel_w if kernel_w > 0 else kernel_size[len(kernel_size) - 1]
elif kernel_h > 0 or kernel_w > 0:
k_h = kernel_h
k_w = kernel_w
[s_h, s_w] = [1, 1]
if isinstance(stride, numbers.Number):
[s_h, s_w] = [stride] * 2
elif len(stride) > 0:
s_h = stride_h if stride_h else stride[0]
s_w = stride_w if stride_w else stride[len(stride) - 1]
s_h = stride_h if stride_h > 0 else stride[0]
s_w = stride_w if stride_w > 0 else stride[len(stride) - 1]
elif stride_h > 0 or stride_w > 0:
s_h = stride_h
s_w = stride_w
[p_h, p_w] = [0, 0]
if isinstance(pad, numbers.Number):
[p_h, p_w] = [pad] * 2
elif len(pad) > 0:
p_h = pad_h if pad_h else pad[0]
p_w = pad_w if pad_w else pad[len(pad) - 1]
p_h = pad_h if pad_h > 0 else pad[0]
p_w = pad_w if pad_w > 0 else pad[len(pad) - 1]
elif pad_h > 0 or pad_w > 0:
p_h = pad_h
p_w = pad_w
input = inputs[0]
dila_len = len(dilation)
dila_h = 1
......
......@@ -14,6 +14,18 @@ def detectionoutput_layer(inputs,
confidence_threshold=0.1,
input_shape=None,
name=None):
nms_param_str = nms_param
nms_param = {}
part = nms_param_str.split(',')
for s in part:
if s == '':
break
else:
name, obj = s.split(': ')
if name == 'top_k':
nms_param[name] = int(obj)
else:
nms_param[name] = float(obj)
if nms_param is None:
nms_param = {"nms_threshold": 0.3, "top_k": 10, "eta": 1.0}
mbox_conf_flatten = inputs[1]
......@@ -24,20 +36,21 @@ def detectionoutput_layer(inputs,
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=[-1, mbox_conf_flatten.shape[1], 4])
mbox_loc = fluid.layers.reshape(x=mbox_loc, shape=[-1, pb.shape[0], 4])
mbox_conf_flatten = fluid.layers.reshape(x=mbox_conf_flatten,
shape=[0, pb.shape[0], -1])
default = {"nms_threshold": 0.3, "top_k": 10, "eta": 1.0}
fields = ['eta', 'top_k', 'nms_threshold']
for f in default.keys():
if not nms_param.has_key(f):
if f not in nms_param:
nms_param[f] = default[f]
out = fluid.layers.detection_output(
scores=mbox_conf_flatten,
loc=mbox_loc,
prior_box=pb,
prior_box_var=pbv,
background_label=background_label,
background_label=background_label_id,
nms_threshold=nms_param["nms_threshold"],
nms_top_k=nms_param["top_k"],
keep_top_k=keep_top_k,
......
......@@ -18,6 +18,8 @@ def normalize_layer(inputs,
shape=[1] if channel_shared else [input_shape[0][1]],
dtype=input.dtype,
attr=name + '_scale')
scale_param = fluid.layers.reshape(x=scale_param, \
shape=[1] if channel_shared else [input_shape[0][1]])
out = fluid.layers.elementwise_mul(x=l2_norm,
y=scale_param,
axis=-1 if channel_shared else 1)
......
......@@ -3,7 +3,7 @@ from x2paddle.core.util import *
def priorbox_shape(input_shape, max_size=None, aspect_ratio=None):
fc_shape = input_shapes[0]
fc_shape = input_shape[0]
N = 1
if not max_size == None:
N += 1
......@@ -18,26 +18,27 @@ def priorbox_layer(inputs,
step=0.0,
offset=0.5,
min_size=None,
max_size=None,
max_size=[],
aspect_ratio=[1.0],
flip=False,
clip=False,
variance=[0.1, 0.1, 0.2, 0.2],
input_shape=None,
name=None):
input = input_shape[0]
image = input_shape[1]
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_sizes=list(min_size),
max_sizes=list(max_size),
aspect_ratios=list(aspect_ratio),
variance=list(variance),
min_sizes=min_size,
max_sizes=max_size,
aspect_ratios=aspect_ratio,
variance=variance,
flip=flip,
clip=clip,
steps=step,
steps=steps,
offset=offset,
name=name,
min_max_aspect_ratios_order=True)
......
......@@ -9,12 +9,12 @@ def shufflechannel_shape(input_shape):
def shufflechannel_layer(inputs, group=None, input_shape=None, name=None):
input = inputs[0]
c_fm = fluid.layers.split(input, num_or_sections=input_shape[0][1], dim=1)
size = int(input_shape[0][1]/group)
size = int(input_shape[0][1] / group)
new_c_fm = []
for i in range(size):
for j in range(group):
new_c_fm.append(c_fm[j * size + i])
out = fluid.layers.concat(new_c_fm, axis = 1)
out = fluid.layers.concat(new_c_fm, axis=1)
return out
......
......@@ -13,104 +13,67 @@
# limitations under the License.
import math
def get_params_w_h(params):
import numbers
from functools import reduce
def get_kernel_parameters(params):
[k_h, k_w] = [1, 1]
if isinstance(params.kernel_size, numbers.Number):
[k_h, k_w] = [params.kernel_size] * 2
elif len(params.kernel_size) > 0:
k_h = params.kernel_h if params.kernel_h > 0 else params.kernel_size[0]
k_w = params.kernel_w if params.kernel_w > 0 else params.kernel_size[
len(params.kernel_size) - 1]
elif params.kernel_h > 0 or params.kernel_w > 0:
k_h = params.kernel_h
k_w = params.kernel_w
[s_h, s_w] = [1, 1]
if isinstance(params.stride, numbers.Number):
[s_h, s_w] = [params.stride] * 2
elif len(params.stride) > 0:
s_h = params.stride_h if params.stride_h > 0 else params.stride[0]
s_w = params.stride_w if params.stride_w > 0 else params.stride[
len(params.stride) - 1]
elif params.stride_h > 0 or params.stride_w > 0:
s_h = params.stride_h
s_w = params.stride_w
[p_h, p_w] = [0, 0]
if isinstance(params.pad, numbers.Number):
[p_h, p_w] = [params.pad] * 2
elif len(params.pad) > 0:
p_h = params.pad_h if params.pad_h > 0 else params.pad[0]
p_w = params.pad_w if params.pad_w > 0 else params.pad[len(params.pad) - 1]
elif params.pad_h > 0 or params.pad_w > 0:
p_h = params.pad_h
p_w = params.pad_w
dila_h = dila_w = 1
if hasattr(params, 'dilation'):
if len(params.dilation) == 0:
dila_h = 1
dila_w = 1
elif len(params.dilation) == 1:
dila_h = params.dilation[0]
dila_w = params.dilation[0]
else:
dila_len = len(params.dilation)
if dila_len == 2:
dila_h = params.dilation[0]
dila_w = params.dilation[1]
else:
dila_h = 1
dila_w = 1
if not isinstance(getattr(params, 'pad'), int):
if len(params.pad) == 0:
pad_h = 0
pad_w = 0
elif len(params.pad) == 1:
pad_h = params.pad[0]
pad_w = params.pad[0]
else:
pad_h, pad_w, = params.pad[0]
pad_w = params.pad[1]
if params.pad_h != 0 or params.pad_w != 0:
pad_h = params.pad_h
pad_w = params.pad_w
else:
if params.pad_h != 0 or params.pad_w != 0:
pad_h = params.pad_h
pad_w = params.pad_w
else:
pad_h = getattr(params, 'pad')
pad_w = getattr(params, 'pad')
if not isinstance(getattr(params, 'kernel_size'), int):
if len(params.kernel_size) == 0:
kernel_h = 1
kernel_w = 1
elif len(params.kernel_size) == 1:
kernel_h = params.kernel_size[0]
kernel_w = params.kernel_size[0]
elif dila_len == 1:
dila_h = dila_w = params.dilation[0]
else:
kernel_h = params.kernel_size[0]
kernel_w = params.kernel_size[1]
if params.kernel_h != 0 or params.kernel_w != 0:
kernel_h = params.kernel_h
kernel_w = params.kernel_w
else:
if params.kernel_h != 0 or params.kernel_w != 0:
kernel_h = params.kernel_h
kernel_w = params.kernel_w
else:
kernel_h = getattr(params, 'kernel_size')
kernel_w = getattr(params, 'kernel_size')
if not isinstance(getattr(params, 'stride'), int):
if len(params.stride) == 0:
stride_h = 1
stride_w = 1
elif len(params.stride) == 1:
stride_h = params.stride[0]
stride_w = params.stride[0]
else:
stride_h = params.stride[0]
stride_w = params.stride[1]
if params.stride_h != 0 or params.stride_w != 0:
stride_h = params.stride_h
stride_w = params.stride_w
else:
if params.stride_h != 0 or params.stride_w != 0:
stride_h = params.stride_h
stride_w = params.stride_w
else:
stride_h = getattr(params, 'stride')
stride_w = getattr(params, 'stride')
return dila_h, dila_w, pad_h, pad_w, kernel_h, kernel_w, stride_h, stride_w
assert dila_len == 0, "invalid length[%s] of dilation in convolution" % (
dila_len)
return dila_h, dila_w, p_h, p_w, k_h, k_w, s_h, s_w
def get_filter_output_shape(i_h, i_w, params, round_func):
dila_h, dila_w, pad_h, pad_w, kernel_h, kernel_w, stride_h, stride_w = get_params_w_h(
def get_strided_kernel_output_shape(params, input_shape, round_func):
i_h = input_shape[2]
i_w = input_shape[3]
dila_h, dila_w, pad_h, pad_w, kernel_h, kernel_w, stride_h, stride_w = get_kernel_parameters(
params)
o_h = (i_h + 2 * pad_h - (dila_h *
(kernel_h - 1) + 1)) / float(stride_h) + 1
o_w = (i_w + 2 * pad_w - (dila_w *
(kernel_w - 1) + 1)) / float(stride_w) + 1
return (int(round_func(o_h)), int(round_func(o_w)))
def get_strided_kernel_output_shape(params, input_shape, round_func):
o_h, o_w = get_filter_output_shape(input_shape[2], input_shape[3], params,
round_func)
o_h = int(round_func(o_h))
o_w = int(round_func(o_w))
has_c_o = hasattr(params, 'num_output')
c = params.num_output if has_c_o else input_shape[1]
return [[input_shape[0], c, o_h, o_w]]
......@@ -120,11 +83,12 @@ def shape_convolution(layer, input_shape):
def shape_deconvolution(layer, input_shape):
h_i = input_shape[2]
w_i = input_shape[3]
h_i = input_shape[0][2]
w_i = input_shape[0][3]
params = layer.convolution_param
dila_h, dila_w, pad_h, pad_w, kernel_h, kernel_w, stride_h, stride_w = get_params_w_h(
dila_h, dila_w, pad_h, pad_w, kernel_h, kernel_w, stride_h, stride_w = get_kernel_parameters(
params)
h_o = (h_i - 1) * stride_h - 2 * pad_h + dila_h * (kernel_h - 1) + 1
......@@ -170,13 +134,25 @@ def shape_input(layer, input_shape):
return [list(layer.input_param.shape[0].dim)]
def shape_memorydata(layer, input_shape):
params = layer.memory_data_param
shape = []
shape.append(int(params.batch_size))
shape.append(int(params.channels))
shape.append(int(params.height))
shape.append(int(params.width))
return [shape]
def shape_concat(layer, input_shape):
params = layer.concat_param
axis = params.axis
output_shape = None
for shape in input_shape:
if output_shape is None:
output_shape = shape
output_shape = []
for i in range(len(shape)):
output_shape.append(shape[i])
else:
output_shape[axis] += shape[axis]
return [output_shape]
......@@ -184,14 +160,28 @@ def shape_concat(layer, input_shape):
def shape_slice(layer, input_shape):
inshape = input_shape[0]
top_len = len(layer.top)
params = layer.slice_param
axis = params.axis
count = inshape[axis]
slice_dim = params.slice_dim
if slice_dim != 1 and axis == 1:
axis = slice_dim
points = list(params.slice_point)
count = inshape[axis]
if len(points) == 0:
assert count % top_len == 0, "the parameter of Slice is wrong"
part = count / top_len
t = part
while t < count:
points.append(int(t))
t += part
points = [0] + points + [count]
output_shape = []
for i in range(len(points)):
shape = inshape
shape = []
for ii in range(len(inshape)):
shape.append(inshape[ii])
size = points[i + 1] - points[i]
shape[axis] = size
output_shape.append(shape)
......@@ -238,8 +228,8 @@ def shape_reshape(layer, input_shape):
inshape = input_shape[0]
params = layer.reshape_param
axis = params.axis if hasattr(params, axis) else 0
num_axes = params.num_axes if hasattr(params, num_axes) else -1
axis = params.axis if hasattr(params, 'axis') else 0
num_axes = params.num_axes if hasattr(params, 'num_axes') else -1
if inshape[0] == -1:
inshape[0] = 1
input_count = count(inshape)
......@@ -262,14 +252,14 @@ def shape_reshape(layer, input_shape):
num_axes_replaced = end_axis - start_axis
num_axes_retained = input_num_axes - num_axes_replaced
num_new_axes = len(shape['dim'])
num_new_axes = len(list(params.shape.dim))
outshape = []
for i in range(start_axis):
outshape.append(inshape[i])
for i in range(num_new_axes):
outshape.append(shape['dim'][i])
outshape.append(params.shape.dim[i])
for i in range(end_axis, input_num_axes):
outshape.append(inshape[i])
......@@ -281,7 +271,7 @@ def shape_reshape(layer, input_shape):
copy_axes = []
constant_count = 1
for i in range(num_new_axes):
top_dim = shape['dim'][i]
top_dim = params.shape.dim[i]
if top_dim == 0:
copy_axes.append(i)
copy_axis_index = start_axis + i
......@@ -297,24 +287,20 @@ def shape_reshape(layer, input_shape):
l = inshape[0:start_axis]
if len(l) > 0:
explicit_count *= count(l)
l = inshape[end_axis:]
if len(l) > 0:
explicit_count *= count(l)
for i in range(len(copy_axes)):
explicit_count *= outshape[start_axis + copy_axes[i]]
assert input_count % explicit_count == 0, "[Reshape]botom count[%d] "\
"must be divisible by product of the specified dimensions[%d] "\
% (input_count, explicit_count)
outshape[start_axis + inferred_axis] = input_count / explicit_count
outshape[start_axis + inferred_axis] = int(input_count / explicit_count)
output_count = count(outshape)
assert output_count == input_count, "[Reshape]output count[%d] must match input count[%d]" % (
output_count, input_count)
if inshape[0] == -1:
outshape[0] = -1
outshape[0] = -1
return [outshape]
......@@ -345,18 +331,22 @@ def shape_crop(layer, input_shape):
def shape_flatten(layer, input_shape):
assert len(input_shape) == 1, "the number of flatten's inputs must be 1"
inshape = input_shape[0]
params = layer.flatten_param
start_axis = params.axis
end_axis = params.end_axis
if start_axis < 0:
start_axis += len(input_shape[0])
start_axis += len(inshape)
if end_axis < 0:
end_axis += len(input_shape[0]) + 1
end_axis += len(inshape) + 1
assert start_axis <= end_axis, 'invalid axis[%d] or end_axis[%d] params'\
% (start_axis, end_axis)
output_shape = [0] * (start_axis - 0) + [
-1
] + [0] * (len(input_shape[0]) - end_axis)
output_shape = inshape[0:start_axis]
if len(inshape[start_axis:end_axis]) != 0:
flat_sz = reduce(lambda a, b: a * b, inshape[start_axis:end_axis])
output_shape += [flat_sz]
output_shape += inshape[end_axis:len(inshape)]
output_shape[0] = -1
return [output_shape]
......
此差异已折叠。
此差异已折叠。
# Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from x2paddle.decoder.caffe_decoder import CaffeGraph
from x2paddle.core.util import *
class CaffeOptimizer(object):
layers_with_act = ['Convolution', 'Deconvolution', 'InnerProduct']
activation_ops = ['ReLU', 'Sigmoid']
def __init__(self, mapper):
self.graph = mapper.graph
def merge_bn_scale(self):
for node_name in self.graph.topo_sort:
node = self.graph.get_node(node_name)
if node.layer_type == 'Scale':
parent_node = self.graph.get_bottom_node(node, idx=0)
if parent_node.layer_type == 'BatchNorm':
is_delete_node = True if len(
parent_node.outputs) == 1 else False
parent_fluid_layer = parent_node.fluid_code.layers[0]
input = parent_fluid_layer.inputs
parent_param_attr = parent_fluid_layer.param_attr
parent_param_attr['param_attr'] = string(node.layer_name +
'_scale')
parent_param_attr['bias_attr'] = string(node.layer_name +
'_offset')
if is_delete_node:
parent_node.fluid_code.clear()
node.fluid_code.clear()
node.fluid_code.add_layer("batch_norm",
inputs=input,
output=node,
param_attr=parent_param_attr)
def merge_op_activation(self):
for node_name in self.graph.topo_sort:
node = self.graph.get_node(node_name)
if node.layer_type in self.activation_ops:
parent_node = self.graph.get_bottom_node(node, idx=0)
if parent_node.layer_type in self.layers_with_act:
is_delete_node = True if len(
parent_node.outputs) == 1 else False
parent_fluid_layer = parent_node.fluid_code.layers[0]
input = parent_fluid_layer.inputs
parent_param_attr = parent_fluid_layer.param_attr
parent_param_attr['act'] = string(node.layer_type.lower())
op = parent_fluid_layer.op
if is_delete_node:
parent_node.fluid_code.clear()
node.fluid_code.clear()
node.fluid_code.add_layer(op,
inputs=input,
output=node,
param_attr=parent_param_attr)
此差异已折叠。
目前X2Paddle支持40+的TensorFlow OP,40+的Caffe Layer,覆盖了大部分CV分类模型常用的操作。我们在如下模型列表中测试了X2Paddle的转换
# X2Paddle模型测试库
> 目前X2Paddle支持40+的TensorFlow OP,40+的Caffe Layer,覆盖了大部分CV分类模型常用的操作。我们在如下模型列表中测试了X2Paddle的转换。
# TensorFlow
**注:** 受限于不同框架的差异,部分模型可能会存在目前无法转换的情况,如TensorFlow中包含控制流的模型,NLP模型等。对于CV常见的模型,如若您发现无法转换或转换失败,存在较大diff等问题,欢迎通过[ISSUE反馈](https://github.com/PaddlePaddle/X2Paddle/issues/new)的方式告知我们(模型名,代码实现或模型获取方式),我们会及时跟进:)
| 模型 | 代码 |
|------|----------|
| SqueezeNet | [code](https://github.com/tensorflow/tpu/blob/master/models/official/squeezenet/squeezenet_model.py)|
| MobileNet_V1 | [code](https://github.com/tensorflow/models/blob/master/research/slim/nets/mobilenet_v1.md) |
| MobileNet_V2 | [code](https://github.com/tensorflow/models/tree/master/research/slim/nets/mobilenet) |
| ShuffleNet | [code](https://github.com/TropComplique/shufflenet-v2-tensorflow) |
| mNASNet | [code](https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet) |
| EfficientNet | [code](https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet) |
| Inception_V4 | [code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_v4.py) |
| Inception_ResNet_V2 | [code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_resnet_v2.py) |
| VGG16 | [code](https://github.com/tensorflow/models/blob/master/research/slim/nets/vgg.py) |
| ResNet_V1_101 | [code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v1.py) |
| ResNet_V2_101 | [code](https://github.com/tensorflow/models/blob/master/research/slim/nets/resnet_v2.py) |
## TensorFlow
| 模型 | 代码 | 备注 |
|------|----------|------|
| SqueezeNet | [code](https://github.com/tensorflow/tpu/blob/master/models/official/squeezenet/squeezenet_model.py)|-|
| MobileNet_V1 | [code](https://github.com/tensorflow/models/tree/master/research/slim/nets) |-|
| MobileNet_V2 | [code](https://github.com/tensorflow/models/tree/master/research/slim/nets) |-|
| ShuffleNet | [code](https://github.com/TropComplique/shufflenet-v2-tensorflow) |-|
| mNASNet | [code](https://github.com/tensorflow/tpu/tree/master/models/official/mnasnet) |-|
| EfficientNet | [code](https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet) |-|
| Inception_V4 | [code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_v4.py) |-|
| Inception_ResNet_V2 | [code](https://github.com/tensorflow/models/blob/master/research/slim/nets/inception_resnet_v2.py) |-|
| VGG16 | [code](https://github.com/tensorflow/models/tree/master/research/slim/nets) |-|
| ResNet_V1_101 | [code](https://github.com/tensorflow/models/tree/master/research/slim/nets) |-|
| ResNet_V2_101 | [code](https://github.com/tensorflow/models/tree/master/research/slim/nets) |-|
| UNet | [code1](https://github.com/jakeret/tf_unet )/[code2](https://github.com/lyatdawn/Unet-Tensorflow) |-|
|MTCNN | [code](https://github.com/AITTSMD/MTCNN-Tensorflow) |-|
|YOLO-V3| [code](https://github.com/YunYang1994/tensorflow-yolov3) | 转换需要关闭NHWC->NCHW的优化,见[文档Q2](FAQ.md) |
|Inception_V4| [code](https://github.com/tensorflow/models/tree/master/research/slim/nets) | - |
|Inception_ResNet_V2| [code](https://github.com/tensorflow/models/tree/master/research/slim/nets) | - |
# Caffe
## Caffe
| 模型 | 代码 |
|-------|--------|
| SqueezeNet | [code](https://github.com/DeepScale/SqueezeNet/tree/master/SqueezeNet_v1.1) |
| MobileNet_V1 | [code](https://github.com/shicai/MobileNet-Caffe) |
| MobileNet_V2 | [code](https://github.com/shicai/MobileNet-Caffe) |
| ShuffleNet | [code](https://github.com/miaow1988/ShuffleNet_V2_pytorch_caffe/releases/tag/v0.1.0) |
| ShuffleNet_v2 | [code](https://github.com/miaow1988/ShuffleNet_V2_pytorch_caffe/releases/tag/v0.1.0) |
| mNASNet | [code](https://github.com/LiJianfei06/MnasNet-caffe) |
| MTCNN | [code](https://github.com/kpzhang93/MTCNN_face_detection_alignment/tree/master/code/codes/MTCNNv1/model) |
| Mobilenet_SSD | [code](https://github.com/chuanqi305/MobileNet-SSD) |
| ResNet18 | [code](https://github.com/HolmesShuan/ResNet-18-Caffemodel-on-ImageNet/blob/master/deploy.prototxt) |
| ResNet50 | [code](https://github.com/soeaver/caffe-model/blob/master/cls/resnet/deploy_resnet50.prototxt) |
| Unet | [code](https://github.com/jolibrain/deepdetect/blob/master/templates/caffe/unet/deploy.prototxt) |
| VGGNet | [code](https://gist.github.com/ksimonyan/211839e770f7b538e2d8#file-vgg_ilsvrc_16_layers_deploy-prototxt) |
| FaceDetection | [code](https://github.com/ShiqiYu/libfacedetection/blob/master/models/caffe/yufacedetectnet-open-v1.prototxt) |
# ONNX
## ONNX
**注:** 部分模型来源于PyTorch,PyTorch的转换可参考[pytorch_to_onnx.md](pytorch_to_onnx.md)
| 模型 | 来源 | operator version|
|-------|--------|---------|
......@@ -47,19 +66,3 @@
| EfficientNet | [pytorch(personal practice)](https://github.com/rwightman/gen-efficientnet-pytorch) |9|
| SqueezeNet | [onnx official](https://s3.amazonaws.com/download.onnx/models/opset_9/squeezenet.tar.gz) |9|
目前onnx2paddle主要支持onnx operator version 9;
如何将torchvison或者个人开发者写的pytroch model转换成onnx model:
```
import torch
import torchvision
#根据不同模型调整输入的shape
dummy_input = torch.randn(1, 3, 224, 224)
#预训练后的pytorch model
resnet18 = torchvision.models.resnet18(pretrained=True)
#"resnet18.onnx"为onnx model的存储路径
torch.onnx.export(resnet18, dummy_input, "resnet18.onnx",verbose=True)
```
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册