diff --git a/README.md b/README.md index fc3fea0e8d56e56b69a6029c90e4c1776fef1cc3..cb9bf623a8f83286eca7c2fa33bf053c8fc76a70 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,12 @@ X2Paddle支持将其余深度学习框架训练得到的模型,转换至Paddle 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)查看我们的模型测试列表,可以在[OP-LIST](op_list.md)中查看目前X2Paddle支持的OP列表。如果你在新的模型上进行了测试转换,也欢迎继续补充该列表;如若无法转换,可通过ISSUE反馈给我们,我们会尽快跟进。 +X2Paddle在多个主流的CV模型上,测试过TensorFlow/Caffe/ONNX/PyTorch模型的转换,可以在[X2Paddle-Model-Zoo](./docs/introduction/x2paddle_model_zoo.md)查看我们的模型测试列表,可以在[OP-LIST](./docs/introduction/op_list.md)中查看目前X2Paddle支持的OP列表。如果你在新的模型上进行了测试转换,也欢迎继续补充该列表;如若无法转换,可通过ISSUE反馈给我们,我们会尽快跟进。 ## 环境依赖 python == 2.7 | python >= 3.5 -paddlepaddle >= 2.0.0 +paddlepaddle 2.0-rc 或者 develop **按需安装以下依赖** tensorflow : tensorflow == 1.14.0 @@ -30,7 +30,7 @@ python setup.py install ### 安装方式二 我们会定期更新pip源上的x2paddle版本 ``` -pip install x2paddle --index https://pypi.Python.org/simple/ +pip install x2paddle==1.0.0rc0 --index https://pypi.Python.org/simple/ ``` ## 使用方法 ### TensorFlow @@ -47,7 +47,7 @@ x2paddle --framework=onnx --model=onnx_model.onnx --save_dir=pd_model ``` ### PyTorch -> PyTorch不支持命令行使用方式,详见[PyTorch2Paddle](pytorch2paddle.md) +> PyTorch不支持命令行使用方式,详见[PyTorch2Paddle](./docs/user_guides/pytorch2paddle.md) ### Paddle2ONNX > Paddle2ONNX功能已迁移至新的github: https://github.com/PaddlePaddle/paddle2onnx, 欢迎大家去新的代码仓库查看详细介绍以及新功能。 @@ -62,15 +62,21 @@ x2paddle --framework=onnx --model=onnx_model.onnx --save_dir=pd_model |--save_dir | 指定转换后的模型保存目录路径 | |--model | 当framework为tensorflow/onnx时,该参数指定tensorflow的pb模型文件或onnx模型路径 | |--caffe_proto | **[可选]** 由caffe.proto编译成caffe_pb2.py文件的存放路径,当存在自定义Layer时使用,默认为None | -|--define_input_shape | **[可选]** For TensorFlow, 当指定该参数时,强制用户输入每个Placeholder的shape,见[文档Q2](FAQ.md) | +|--define_input_shape | **[可选]** For TensorFlow, 当指定该参数时,强制用户输入每个Placeholder的shape,见[文档Q2](./docs/user_guides/FAQ.md) | |--params_merge | **[可选]** 当指定该参数时,转换完成后,inference_model中的所有模型参数将合并保存为一个文件__params__ | ## 使用转换后的模型 +- 静态图: 转换后的模型包括`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) +`model_with_code`中保存了模型参数,和转换后的python模型静态图代码。 +`inference_model`中保存了序列化的模型结构和参数,可直接使用paddle的接口进行加载,见[paddle.static.load_inference_model](https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0-rc/api/paddle/static/load_inference_model_cn.html#load-inference-model)。 +- 动态图: +转换后的模型包括`model.pdparams`和`x2paddle_code.py`两个文件,以及`inference_model`一个目录。 +`model.pdparams`中保存了模型参数。 +`x2paddle_code.py`是转换后的python模型动态图代码。 +`inference_model`中保存了序列化的模型结构和参数,可直接使用paddle的接口进行加载,见[paddle.static.load_inference_model](https://www.paddlepaddle.org.cn/documentation/docs/zh/2.0-rc/api/paddle/static/load_inference_model_cn.html#load-inference-model)。 ## 小工具 X2Paddle提供了工具解决如下问题,详见[tools/README.md](tools/README.md) @@ -78,11 +84,12 @@ X2Paddle提供了工具解决如下问题,详见[tools/README.md](tools/README 2. 合并模型参数文件 ## 相关文档 -1. [X2Paddle使用过程中常见问题](FAQ.md) -2. [如何导出TensorFlow的pb模型](export_tf_model.md) -3. [X2Paddle测试模型库](x2paddle_model_zoo.md) -4. [PyTorch模型导出为ONNX模型](pytorch_to_onnx.md) -5. [X2Paddle内置的Caffe自定义层](caffe_custom_layer.md) +1. [X2Paddle使用过程中常见问题](./docs/user_guides/FAQ.md) +2. [如何导出TensorFlow的pb模型](./docs/user_guides/export_tf_model.md) +3. [X2Paddle测试模型库](./docs/introduction/x2paddle_model_zoo.md) +4. [X2Paddle支持的op列表](./docs/introduction/op_list.md) +5. [PyTorch模型导出为ONNX模型](./docs/user_guides/pytorch2onnx.md) +6. [X2Paddle添加内置的Caffe自定义层](./docs/user_guides/add_caffe_custom_layer.md) ## 更新历史 2019.08.05 @@ -91,7 +98,15 @@ X2Paddle提供了工具解决如下问题,详见[tools/README.md](tools/README 3. 解决Windows上保存模型无法加载的问题 4. 新增optimizer,优化代码结构,合并conv、batch_norm的bias和激活函数 -**如果你需要之前版本的tensorflow2fluid/caffe2fluid/onnx2fluid,可以继续访问release-0.3分支,获取之前版本的代码使用。** +2020.12.09 +1. 新增PyTorch2Paddle转换方式,转换得到Paddle动态图代码,并动转静获得inference_model。 +方式一:trace方式,转换后的代码有模块划分,每个模块的功能与PyTorch相同。 +方式二:script方式,转换后的代码按执行顺序逐行出现。 +2. 新增Caffe/ONNX/Tensorflow到Paddle动态图的转换。 +3. 新增TensorFlow op(14个):Neg、Greater、FloorMod、LogicalAdd、Prd、Equal、Conv3D、Ceil、AddN、DivNoNan、Where、MirrorPad、Size、TopKv2 +4. 新增Optimizer模块,主要包括op融合、op消除功能,转换后的代码可读性更强,进行预测时耗时更短。 + +**如果你需要之前版本的tensorflow2fluid/caffe2fluid/onnx2fluid,可以继续访问release-0.9分支,获取之前版本的代码使用。** ## Acknowledgements diff --git a/add_caffe_custom_layer.md b/add_caffe_custom_layer.md deleted file mode 100644 index 8b665d795ba1e24a5ad3341944334491042deb29..0000000000000000000000000000000000000000 --- a/add_caffe_custom_layer.md +++ /dev/null @@ -1,78 +0,0 @@ -## 如何转换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 -``` diff --git a/caffe_custom_layer.md b/caffe_custom_layer.md deleted file mode 100644 index a7bddf2d7823851bf8c538a8bfe0bb35aff32e8b..0000000000000000000000000000000000000000 --- a/caffe_custom_layer.md +++ /dev/null @@ -1,14 +0,0 @@ -目前,代码中已经提供了10个非官方op(不在[官网](http://caffe.berkeleyvision.org/tutorial/layers)上的op)的转换,这些op对应的Caffe实现源码如下: - -| op | 该版本实现源码 | -|-------|--------| -| PriorBox | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/prior_box_layer.cpp) | -| DetectionOutput | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/detection_output_layer.cpp) | -| ConvolutionDepthwise | [code](https://github.com/farmingyard/caffe-mobilenet/blob/master/conv_dw_layer.cpp) | -| ShuffleChannel | [code](https://github.com/farmingyard/ShuffleNet/blob/master/shuffle_channel_layer.cpp) | -| Permute | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/permute_layer.cpp) | -| Normalize | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/normalize_layer.cpp) | -| ROIPooling | [code](https://github.com/rbgirshick/caffe-fast-rcnn/blob/0dcd397b29507b8314e252e850518c5695efbb83/src/caffe/layers/roi_pooling_layer.cpp) | -| Axpy | [code](https://github.com/hujie-frank/SENet/blob/master/src/caffe/layers/axpy_layer.cpp) | -| ReLU6 | [code](https://github.com/chuanqi305/ssd/blob/ssd/src/caffe/layers/relu6_layer.cpp) | -| Upsample | [code](https://github.com/eric612/MobileNet-YOLO/blob/master/src/caffe/layers/upsample_layer.cpp) | diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000000000000000000000000000000000..656efbee055b7c4206df655cfeb8e5b1110dee9b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,9 @@ +# 一、 introduction +1. op_list.md:当前转换各个框架支持的op。 +2. x2paddle_model_zoo.md:测试过的模型列表。 +# 二、 user_guides +1. FQA.md:常见问题集合。 +2. add_caffe_custom_layer.md:添加caffe自定义Layer的方法,以及当前支持的自定义Layer列表。 +3. export_tf_model.md:导出本工具支持的TensorFlow模型。 +4. pytorch2onnx.md:将PyTorch导出为ONNX。 +5. pytorch2paddle.md:将PyTorch模型转换为Paddle模型。 diff --git a/op_list.md b/docs/introduction/op_list.md similarity index 58% rename from op_list.md rename to docs/introduction/op_list.md index 8b47b62b0300d9532a4fba974f923b758a2154e2..c52bf1b6c0d2091af82b584e574791268c45a1fe 100644 --- a/op_list.md +++ b/docs/introduction/op_list.md @@ -24,7 +24,11 @@ | 57 | Sqrt | 58 | Softplus | 59 | Erf | 60 | AddV2 | | 61 | LessEqual | 62 | BatchMatMul | 63 | BatchMatMulV2 | 64 | ExpandDims | | 65 | BatchToSpaceND | 66 | SpaceToBatchND | 67 | OneHot | 68 | Pow | -| 69 | All | 70 | GatherV2 | 71 | IteratorV2 | | | +| 69 | All | 70 | GatherV2 | 71 | IteratorV2 | 72 | Neg | +| 73 | Greater | 74 | FloorMod | 75 | LogicalAdd | 76 | Prod | +| 77 | Equal | 78 | Conv3D | 79 | Ceil | 80 | AddN | +| 81 | DivNoNan | 82 | Where | 83 | MirrorPad | 84 | Size | +| 85 | TopKv2 | | | | | | | ## Caffe @@ -58,3 +62,44 @@ | 45 | Squeeze | 46 | Equal | 47 | Identity | 48 | GlobalAveragePool | | 49 | MaxPool | 50 | Conv | 51 | Gemm | 52 | NonZero | | 53 | Abs | 54 | Floor | + +## PyTorch +Aten: +| 序号 | OP | 序号 | OP | 序号 | OP | 序号 | OP | +|------|------|------|------|------|------|------|------| +| 1 | aten::abs | 2 | aten::adaptive_avg_pool2d | 3 | aten::addmm | 4 | aten::add | +| 5 | aten::add\_ | 6 | aten::\_\_and\_\_ | 7 | aten::append | 8 | aten::arange | +| 9 | aten::avg\_pool2d | 10 | aten::avg\_pool3d | 11 | aten::avg_pool1d | 12 | aten::batch_norm | +| 13 | aten::cat | 14 | aten::chunk | 15 | aten::clamp | 16 | aten::\_\_contains\_\_ | +| 17 | aten::constant\_pad\_nd | 18 | aten::contiguous | 19 | aten::conv2d | 20 | aten::\_convolution | +| 21 | aten::conv_transpose2d | 22 | aten::cos | 23 | aten::cumsum | 24 | aten::detach | +| 25 | aten::dict | 26 | aten::dim | 27 | aten::div\_ | 28 | aten::div | +| 29 | aten::dropout | 30 | aten::dropout_ | 31 | aten::embedding | 32 | aten::eq | +| 33 | aten::exp | 34 | aten::expand | 35 | aten::expand_as | 36 | aten::eye | +| 37 | aten::feature_dropout | 38 | aten::flatten | 39 | aten::Float | 40 | aten::floor | +| 41 | aten::floordiv | 42 | aten::floor_divide | 43 | aten::full_like | 44 | aten::gather | +| 45 | aten::gelu | 46 | aten::\_\_getitem\_\_ | 47 | aten::gt | 48 | aten::hardtanh\_ | +| 49 | aten::index\_select | 50 | aten::Int | 51 | aten::\_\_is\_\_ | 52 | aten::\_\_isnot\_\_ | +| 53 | aten::layer\_norm | 54 | aten::le |55|aten::leaky\_relu\_|56|aten::len| +| 57 | aten::log | 58 | aten::lt |59|aten::masked\_fil\l_|60|aten::masked\_fill| +| 61 | aten::max | 62 | aten::max\_pool2d |63|aten::matmul|64|aten\_min| +| 65 | aten::mean | 66 | aten::meshgrid |67|aten::mul|68|aten::mul\_| +| 69 | aten::ne | 70 | aten::neg |71|aten::\_\_not\_\_|72|aten::ones| +| 73 | aten::permute | 74 | aten::pow |75|aten::relu|76|aten::relu\_| +| 77 | aten::relu6 | 78 | aten::repeat |79|aten::reshape|80|aten::rsub| +| 81 | aten::ScalarImplicit | 82 | aten::select |83|aten::\_set\_item|84|aten::sigmoid| +| 85 | aten::sin | 86 | aten::size |87|aten::slice|88|aten::softmax| +| 89 | aten::softplus | 90 | aten::sqrt |91|aten::squeeze|92|aten::stack| +| 93 | aten::sub | 94 | aten::t |95|aten::tanh|96|aten::split| +| 97 | aten::transpose | 98 | aten::to |99|aten::type\_as|100|aten::unsqueeze| +| 101 | aten::upsample\_bilinear2d | 102 | aten::values |103|aten::view|104|aten::warn| +| 105 | aten::where | 106 | aten::zeros |107|aten::zeros\_like||| + +Prim: +| 序号 | OP | 序号 | OP | 序号 | OP | 序号 | OP | +|------|------|------|------|------|------|------|------| +| 1 | prim::Constant | 2 | prim::data | 3 | prim::DictConstruct | 4 | prim::GetAttr | +| 5 | prim::If | 6 | prim::ListConstruct | 7 | prim::ListUnpack | 8 | prim::Loop | +| 9 | prim::min | 10 | prim::NumToTensor | 11 | prim::RaiseException | 12 | prim::requires\_grad | +| 13 | prim::SetAttr | 14 | prim::shape | 15 | prim::TupleConstruct | 16 | prim::TupleUnpack | +| 17 | prim::unchecked\_cast | 18 | prim::Uninitialized | |||| diff --git a/x2paddle_model_zoo.md b/docs/introduction/x2paddle_model_zoo.md similarity index 78% rename from x2paddle_model_zoo.md rename to docs/introduction/x2paddle_model_zoo.md index 871b8c327003e577b976eabc9eee55293fda8fbd..7197db50b56e12c7c14f93ccc53887733181da20 100644 --- a/x2paddle_model_zoo.md +++ b/docs/introduction/x2paddle_model_zoo.md @@ -74,3 +74,25 @@ |Ultra-Light-Fast-Generic-Face-Detector-1MB| [onnx_model](https://github.com/Linzaer/Ultra-Light-Fast-Generic-Face-Detector-1MB/tree/master/models/onnx)|9 | |BERT| [pytorch(huggingface)](https://github.com/huggingface/transformers/blob/master/notebooks/04-onnx-export.ipynb)|11|转换时需指定input shape,见[文档Q3](FAQ.md)| |GPT2| [pytorch(huggingface)](https://github.com/huggingface/transformers/blob/master/notebooks/04-onnx-export.ipynb)|11|转换时需指定input shape,见[文档Q3](FAQ.md)| + + +## PyTorch + +| 模型 | 代码 | 备注 | +|------|----------|------| +| AlexNet | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/alexnet.py)|-| +| MNasNet | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/mnasnet.py) |-| +| MobileNetV2 | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/mobilenet.py) |-| +| ResNet18 | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py) |-| +| ShuffleNetV2 | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/shufflenet.py) |-| +| SqueezeNet | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/squeezenet.py) |-| +| VGG16 | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/vgg.py) |-| +| InceptionV3 | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/inception.py) |-| +| DeepLabv3_ResNet50 | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/segmentation/deeplabv3.py) |-| +| FCN_ResNet50 | [code](https://github.com/pytorch/vision/blob/master/torchvision/models/segmentation/fcn.py) |-| +| CamembertForQuestionAnswering | [code](https://huggingface.co/transformers/model_doc/camembert.html) |只支持trace模式| +| DPRContextEncoder | [code](https://huggingface.co/transformers/model_doc/dpr.html) |只支持trace模式| +| ElectraModel | [code](https://huggingface.co/transformers/model_doc/electra.html ) |只支持trace模式| +| FlaubertModel | [code](https://huggingface.co/transformers/model_doc/flaubert.html) |只支持trace模式| +| Roberta| [code](https://huggingface.co/transformers/model_doc/roberta.html) |只支持trace模式| +| XLMRobertaForTokenClassification|[code](https://huggingface.co/transformers/model_doc/xlmroberta.html) |只支持trace模式| diff --git a/FAQ.md b/docs/user_guides/FAQ.md similarity index 80% rename from FAQ.md rename to docs/user_guides/FAQ.md index 4247a15752abb8e68fd8ee6307b5eb3930b40a9f..19e3ace0d367f11ccf1141e13bee38db0946349f 100644 --- a/FAQ.md +++ b/docs/user_guides/FAQ.md @@ -23,3 +23,20 @@ A: 此提示为错误信息,表示该模型的转换需要固定的输入大 > 1. 模型来源于PaddleX导出,可以在导出的命令中,指定--fixed_input_shape=[Height,Width],详情可见:[PaddleX模型导出文档](https://github.com/PaddlePaddle/PaddleX/blob/develop/docs/deploy/export_model.md)。 > 2. 模型来源于PaddleDetection导出,可以在导出模型的时候,指定 TestReader.inputs_def.image_shape=[Channel,Height,Width], 详情可见:[PaddleDetection模型导出文档](https://github.com/PaddlePaddle/PaddleDetection/blob/master/docs/advanced_tutorials/deploy/EXPORT_MODEL.md#设置导出模型的输入大小)。 > 3. 模型来源于自己构建,可在网络构建的`fluid.data(shape=[])`中,指定shape参数来固定模型的输入大小。 + + +**Q6. 进行动态图转换时,提示『Fail to generate inference model! Problem happend while export inference model from python code...』** +A: 此提示为无法将动态图代码转换为静态图模型,有两种可能: +> 使用动态图代码确认转换后的代码是否正确,可使用如下代码进行确认: +``` +import paddle +import numpy as np +np.random.seed(6) +# ipt为输入数据 +ipt = np.random.rand(1, 3, 224, 224).astype("float32") +paddle.disable_static() +# pd_model_dygraph为保存路径(其中的”/“用”.“替换) +from pd_model_dygraph.x2paddle_code import main +out =main(ipt) +``` +> 若运行代码无误,则说明代码中有op不支持动转静,我们将会再未来支持;若报错,则说明pytorch2paddle转换出错,请提issue,我们将及时回复。 \ No newline at end of file diff --git a/docs/user_guides/add_caffe_custom_layer.md b/docs/user_guides/add_caffe_custom_layer.md new file mode 100644 index 0000000000000000000000000000000000000000..4407f21741f6a1adb2ce186b985789bbc15555a6 --- /dev/null +++ b/docs/user_guides/add_caffe_custom_layer.md @@ -0,0 +1,205 @@ +## 如何转换Caffe自定义Layer + +本文档介绍如何将Caffe自定义Layer转换为PaddlePaddle模型中的对应实现, 用户可根据自己需要,添加代码实现自定义层,从而支持模型的完整转换。 +目前,代码中已经提供了10个非官方op(不在[官网](http://caffe.berkeleyvision.org/tutorial/layers)上的op)的转换,这些op对应的Caffe实现源码如下: + +| op | 该版本实现源码 | +|-------|--------| +| PriorBox | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/prior_box_layer.cpp) | +| DetectionOutput | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/detection_output_layer.cpp) | +| ConvolutionDepthwise | [code](https://github.com/farmingyard/caffe-mobilenet/blob/master/conv_dw_layer.cpp) | +| ShuffleChannel | [code](https://github.com/farmingyard/ShuffleNet/blob/master/shuffle_channel_layer.cpp) | +| Permute | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/permute_layer.cpp) | +| Normalize | [code](https://github.com/weiliu89/caffe/blob/ssd/src/caffe/layers/normalize_layer.cpp) | +| ROIPooling | [code](https://github.com/rbgirshick/caffe-fast-rcnn/blob/0dcd397b29507b8314e252e850518c5695efbb83/src/caffe/layers/roi_pooling_layer.cpp) | +| Axpy | [code](https://github.com/hujie-frank/SENet/blob/master/src/caffe/layers/axpy_layer.cpp) | +| ReLU6 | [code](https://github.com/chuanqi305/ssd/blob/ssd/src/caffe/layers/relu6_layer.cpp) | +| Upsample | [code](https://github.com/eric612/MobileNet-YOLO/blob/master/src/caffe/layers/upsample_layer.cpp) | + +添加代码实现自定义层的步骤如下: + +***步骤一 下载代码*** +此处涉及修改源码,应先卸载x2paddle,并且下载源码,主要有以下两步完成: +``` +pip uninstall x2paddle +pip install git+https://github.com/PaddlePaddle/X2Paddle.git@develop +``` + +***步骤二 编译caffe.proto*** +该步骤依赖protobuf编译器,其安装过程有以下两种方式: +> 选择一:pip install protobuf (protobuf >= 3.6.0) +> 选择二:使用[官方源码](https://github.com/protocolbuffers/protobuf)进行编译 + +使用脚本./tools/compile.sh将caffe.proto(包含所需的自定义Layer信息)编译成我们所需的目标语言(Python) +使用方式: +``` +bash ./toos/compile.sh /home/root/caffe/src/caffe/proto +# /home/root/caffe/src/caffe/proto为caffe.proto的存放路径,生成的caffe_pb2.py也将保存在该路径下 +``` + +***步骤三 添加自定义Layer的实现代码*** +**静态图方式:** +- 进入./x2paddle/op_mapper/static/caffe2paddle/caffe_custom_layer,创建.py文件,例如mylayer.py +- 仿照./x2paddle/op_mapper/static/caffe2paddle/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 + ``` +**动态图方式:** +> 【注意】若Caffe自定义layer与Paddle的op一一对应,使用方式一,否则使用方式二。 + +- 方式一: +1. 仿照./x2paddle/op_mapper/dygraph/caffe2paddle/caffe_op_mapper.py中的CaffeOpMapper类中的映射方法(输入为self和node),实现类似的映射方法,以下述的映射方法为例: +```python +def Permute(self, node): + assert len( + node.inputs) == 1, "The count of Permute node\'s input is not 1." + input = self.graph.get_input_node(node, idx=0, copy=True) + params = node.layer.permute_param + order = list(params.order) + self.paddle_graph.add_layer( + "paddle.transpose", + inputs={"x": input.name}, + outputs=[node.layer_name], + perm=order) +``` +>需完成的步骤: +> a. 获取Caffe Layer的属性,并对应转换为Paddle的属性。 +> b. 获取当前Layer的输入。 +> c. 使用self.paddle_graph.add_layer为PaddleGraph添加layer。其中,第一个参数代表Paddle的kernel;inputs是一个字典,用于存储paddle中的输入的key与其输入名字;outputs是一个列表,用于存储输出的名字;其余参数为属性对应关系。 +2. 仿照./x2paddle/decoder/caffe_shape_inference.py中的shape_xx方法,实现获取当前Layer输出大小的函数,以下述方法为例: +```python +def shape_permute(layer, input_shape): + order = layer.permute_param.order + inshape = input_shape[0] + output_shape = [] + order = list(order) + for ii in order: + assert ii < len(inshape), "invalid order for permute[%s]" % (name) + output_shape.append(inshape[ii]) + return [output_shape] +``` +>参数: +> layer (caffe_pb2.LayerParameter): caffe的Layer,可用于获取当前Layer的属性。 +> input_shape (list): 其中每个元素代表该层每个输入数据的大小。 + + +- 方式二: +1. 进入./x2paddle/op_mapper/dygraph/caffe2paddle/caffe_custom_layer,创建.py文件,例如mylayer.py +2. 仿照./x2paddle/op_mapper/dygraph/caffe2paddle/caffe_custom_layer中的其他文件,在mylayer.py中主要需要实现1个类,下面以roipooling.py为例分析代码: + +```python +class ROIPooling(object): + def __init__(self, pooled_height, pooled_width, spatial_scale): + self.roipooling_layer_attrs = { + "pooled_height": pooled_height, + "pooled_width": pooled_width, + "spatial_scale": spatial_scale} + + def __call__(self, x0, x1): + slice_x1 = paddle.slice(input=x1, axes=[1], + starts=[1], ends=[5]) + out = fluid.layers.roi_pool(input=x0, + rois=slice_x1, + **self.roipooling_layer_attrs) + return out +``` + +>\_\_init\_\_函数:用于初始化各个属性 +>\_\_call\_\_函数:用于组合实现当前Layer的前向,输入为当前Layer所需要的输入 + + +3. 仿照./x2paddle/op_mapper/dygraph/caffe2paddle/caffe_op_mapper.py中的CaffeOpMapper类中的映射方法(输入为self和node),实现类似的映射方法,以下述的映射方法为例: +```python +def ROIPooling(self, node): + roipooling_name = name_generator("roipooling", self.nn_name2id) + output_name = node.layer_name + layer_outputs = [roipooling_name, output_name] + assert len( + node.inputs) == 2, "The count of ROIPooling node\'s input is not 2." + input0 = self.graph.get_input_node(node, idx=0, copy=True) + input1 = self.graph.get_input_node(node, idx=1, copy=True) + inputs_dict = {} + inputs_dict["x0"] = input0.name + inputs_dict["x1"] = input1.name + params = node.layer.roi_pooling_param + layer_attrs = { + "pooled_height": params.pooled_h, + "pooled_width": params.pooled_w, + "spatial_scale": params.spatial_scale} + self.paddle_graph.add_layer( + "custom_layer:ROIPooling", + inputs=inputs_dict, + outputs=layer_outputs, + **layer_attrs) +``` +>需完成的步骤: +> a. 获取Caffe Layer的属性,并对应转换为Paddle的属性。 +> b. 获取当前Layer的输入。 +> c. 使用self.paddle_graph.add_layer为PaddleGraph添加layer。其中,第一个参数代表Paddle的kernel(此处kernel必须以“custom_layer:“开头);inputs是一个字典,用于存储paddle中的输入的key与其输入名字;outputs是一个列表,用于存储输出的名字;其余参数为属性对应关系。 + +4. 仿照./x2paddle/decoder/caffe_shape_inference.py中的shape_xx方法,实现获取当前Layer输出大小的函数,以下述方法为例: +```python +def shape_roipooling(layer, input_shape): + pooled_w = layer.roi_pooling_param.pooled_w + pooled_h = layer.roi_pooling_param.pooled_h + base_fea_shape = input_shapes[0] + rois_shape = input_shapes[1] + output_shape = base_fea_shape + output_shape[0] = rois_shape[0] + output_shape[2] = pooled_h + output_shape[3] = pooled_w + return [output_shape] + +``` +>参数: +> layer (caffe_pb2.LayerParameter): caffe的Layer,可用于获取当前Layer的属性。 +> input_shape (list): 其中每个元素代表该层每个输入数据的大小。 + +***步骤四 运行转换代码*** +``` +# 在X2Paddle目录下安装x2paddle +python setup.py install +# 运行转换代码 +x2paddle --framework=caffe + --prototxt=deploy.proto + --weight=deploy.caffemodel + --save_dir=pd_model + --caffe_proto=/home/root/caffe/src/caffe/proto/caffe_pb2.py +``` diff --git a/docs/user_guides/export_tf_model.md b/docs/user_guides/export_tf_model.md new file mode 100644 index 0000000000000000000000000000000000000000..87471afd03db35e640b57a0838d12ffec74580fb --- /dev/null +++ b/docs/user_guides/export_tf_model.md @@ -0,0 +1,112 @@ +## 如何导出TensorFlow模型 + +本文档介绍如何将TensorFlow模型导出为X2Paddle支持的模型格式。 +TensorFlow目前一般分为3种保存格式(checkpoint、FrozenModel、SavedModel),X2Paddle支持的是FrozenModel(将网络参数和网络结构同时保存到同一个文件中,并且只保存指定的前向计算子图),下面示例展示了如何导出X2Paddle支持的模型格式。 + +***下列代码Tensorflow 1.X下使用*** +- checkpoint模型+代码 +步骤一 下载模型参数文件 +``` +wget http://download.tensorflow.org/models/vgg_16_2016_08_28.tar.gz +tar xzvf vgg_16_2016_08_28.tar.gz +``` + +步骤二 加载和导出模型 +``` +#coding: utf-8 +import tensorflow.contrib.slim as slim +from tensorflow.contrib.slim.nets import vgg +from tensorflow.python.framework import graph_util +import tensorflow as tf + +# 固化模型函数 +# output_tensor_names: list,指定模型的输出tensor的name +# freeze_model_path: 模型导出的文件路径 +def freeze_model(sess, output_tensor_names, freeze_model_path): + out_graph = graph_util.convert_variables_to_constants( + sess, sess.graph.as_graph_def(), output_tensor_names) + with tf.gfile.GFile(freeze_model_path, 'wb') as f: + f.write(out_graph.SerializeToString()) + + print("freeze model saved in {}".format(freeze_model_path)) + +# 加载模型参数 +sess = tf.Session() +inputs = tf.placeholder(dtype=tf.float32, + shape=[None, 224, 224, 3], + name="inputs") +logits, endpoint = vgg.vgg_16(inputs, num_classes=1000, is_training=False) +load_model = slim.assign_from_checkpoint_fn( + "vgg_16.ckpt", slim.get_model_variables("vgg_16")) +load_model(sess) + +# 导出模型 +freeze_model(sess, ["vgg_16/fc8/squeezed"], "vgg16.pb") +``` +- 纯checkpoint模型 +文件结构: +> |--- checkpoint +> |--- model.ckpt-240000.data-00000-of-00001 +> |--- model.ckpt-240000.index +> |--- model.ckpt-240000.meta + +加载和导出模型: +```python +#coding: utf-8 +from tensorflow.python.framework import graph_util +import tensorflow as tf + +# 固化模型函数 +# output_tensor_names: list,指定模型的输出tensor的name +# freeze_model_path: 模型导出的文件路径 +def freeze_model(sess, output_tensor_names, freeze_model_path): + out_graph = graph_util.convert_variables_to_constants( + sess, sess.graph.as_graph_def(), output_tensor_names) + with tf.gfile.GFile(freeze_model_path, 'wb') as f: + f.write(out_graph.SerializeToString()) + + print("freeze model saved in {}".format(freeze_model_path)) + +# 加载模型参数 +# 此处需要修改input_checkpoint(checkpoint的前缀)和save_pb_file(模型导出的文件路径) +input_checkpoint = "./tfhub_models/save/model.ckpt" +save_pb_file = "./tfhub_models/save.pb" +sess = tf.Session() +saver = tf.train.import_meta_graph(input_checkpoint + '.meta', clear_devices=True) +saver.restore(sess, input_checkpoint) + +# 此处需要修改freeze_model的第二个参数,指定模型的输出tensor的name +freeze_model(sess, ["vgg_16/fc8/squeezed"], save_pb_file) +``` + +- SavedModel模型 +文件结构: +> |-- variables +> |------ variables.data-00000-of-00001 +> |------ variables.data-00000-of-00001 +> |-- saved_model.pb + +加载和导出模型: +```python +#coding: utf-8 +import tensorflow as tf +sess = tf.Session(graph=tf.Graph()) +# tf.saved_model.loader.load最后一个参数代表saved_model的保存路径 +tf.saved_model.loader.load(sess, {}, "/mnt/saved_model") +graph = tf.get_default_graph() + +from tensorflow.python.framework import graph_util +# 固化模型函数 +# output_tensor_names: list,指定模型的输出tensor的name +# freeze_model_path: 模型导出的文件路径 +def freeze_model(sess, output_tensor_names, freeze_model_path): + out_graph = graph_util.convert_variables_to_constants( + sess, sess.graph.as_graph_def(), output_tensor_names) + with tf.gfile.GFile(freeze_model_path, 'wb') as f: + f.write(out_graph.SerializeToString()) + + print("freeze model saved in {}".format(freeze_model_path)) + +# 导出模型 +freeze_model(sess, ["logits"], "model.pb") +``` diff --git a/pytorch_to_onnx.md b/docs/user_guides/pytorch2onnx.md similarity index 100% rename from pytorch_to_onnx.md rename to docs/user_guides/pytorch2onnx.md diff --git a/pytorch2paddle.md b/docs/user_guides/pytorch2paddle.md similarity index 98% rename from pytorch2paddle.md rename to docs/user_guides/pytorch2paddle.md index a751345a9bb104312d3a9c81745af9c94f3d556a..82f2d204f936c8b62f15654ba39dbb8559f88785 100644 --- a/pytorch2paddle.md +++ b/docs/user_guides/pytorch2paddle.md @@ -46,7 +46,7 @@ torch_module.load_state_dict(torch_state_dict) torch_module.eval() # 进行转换 from x2paddle.convert import pytorch2paddle -pytorch2paddle(torch_model, +pytorch2paddle(torch_module, save_dir="pd_model_trace", jit_type="trace", input_examples=[torch.tensor(input_data)]) diff --git a/export_tf_model.md b/export_tf_model.md deleted file mode 100644 index 7bed9512424fd103f8a4e3c3ad66362add88fa90..0000000000000000000000000000000000000000 --- a/export_tf_model.md +++ /dev/null @@ -1,44 +0,0 @@ -## 如何导出TensorFlow模型 - -本文档介绍如何将TensorFlow模型导出为X2Paddle支持的模型格式。 - -TensorFlow提供了接口可将网络参数和网络结构同时保存到同一个文件中,并且只保存指定的前向计算子图,下面示例展示了如何导出tensorflow/models下的VGG16模型 - -步骤一 下载模型参数文件 -``` -wget http://download.tensorflow.org/models/vgg_16_2016_08_28.tar.gz -tar xzvf vgg_16_2016_08_28.tar.gz -``` - -步骤二 加载和导出模型 -``` -#coding: utf-8 -import tensorflow.contrib.slim as slim -from tensorflow.contrib.slim.nets import vgg -from tensorflow.python.framework import graph_util -import tensorflow as tf - -# 固化模型函数 -# output_tensor_names: list,指定模型的输出tensor的name -# freeze_model_path: 模型导出的文件路径 -def freeze_model(sess, output_tensor_names, freeze_model_path): - out_graph = graph_util.convert_variables_to_constants( - sess, sess.graph.as_graph_def(), output_tensor_names) - with tf.gfile.GFile(freeze_model_path, 'wb') as f: - f.write(out_graph.SerializeToString()) - - print("freeze model saved in {}".format(freeze_model_path)) - -# 加载模型参数 -sess = tf.Session() -inputs = tf.placeholder(dtype=tf.float32, - shape=[None, 224, 224, 3], - name="inputs") -logits, endpoint = vgg.vgg_16(inputs, num_classes=1000, is_training=False) -load_model = slim.assign_from_checkpoint_fn( - "vgg_16.ckpt", slim.get_model_variables("vgg_16")) -load_model(sess) - -# 导出模型 -freeze_model(sess, ["vgg_16/fc8/squeezed"], "vgg16.pb") -``` diff --git a/requirements.txt b/requirements.txt index 8664da7d29c45f04abbefe4cd2050e768f139821..9d7748adf401214b95099980aaaecb875e4f153e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ pre-commit yapf == 0.28.0 +pandas +treelib diff --git a/tools/merge_params.py b/tools/merge_params.py index 1b2fa18df5153edfdcac012e3b84b1893803b0a5..f85e7ab97396dcdcd247436473c43588e422ede2 100644 --- a/tools/merge_params.py +++ b/tools/merge_params.py @@ -1,15 +1,17 @@ +import paddle import paddle.fluid as fluid import sys model_dir = sys.argv[1] new_model_dir = sys.argv[2] -exe = fluid.Executor(fluid.CPUPlace()) +paddle.enable_static() +exe = paddle.static.Executor(paddle.CPUPlace()) [inference_program, feed_target_names, - fetch_targets] = fluid.io.load_inference_model( + fetch_targets] = paddle.static.load_inference_model( dirname=model_dir, executor=exe) print(feed_target_names) -fluid.io.save_inference_model( +paddle.static.save_inference_model( dirname=new_model_dir, feeded_var_names=feed_target_names, target_vars=fetch_targets, diff --git a/x2paddle/op_mapper/dygraph/pytorch2paddle/aten.py b/x2paddle/op_mapper/dygraph/pytorch2paddle/aten.py index bb094023b9c620461639d16b3acbbd5a8bc8b171..297a0b0b6fb2e1fa48372a9ce0b1549fb05e898a 100644 --- a/x2paddle/op_mapper/dygraph/pytorch2paddle/aten.py +++ b/x2paddle/op_mapper/dygraph/pytorch2paddle/aten.py @@ -3179,6 +3179,39 @@ def aten_permute(mapper, graph, node): return current_inputs, current_outputs +def aten_pixel_shuffle(mapper, graph, node): + """ 构造以像素的方式重排的PaddleLayer。 + + TorchScript示例: + %x.6 : aten::pixel_shuffle(%input.101, %726) + 参数含义: + %x.6 (Tensor): 输出,重排后的Tensor。 + %input.101 (Tensor): 需要重排的Tensor。 + %726 (int): 增大空间分辨率的增大因子。 + """ + scope_name = mapper.normalize_scope_name(node) + output_name = mapper._get_outputs_name(node)[0] + layer_outputs = [output_name] + layer_inputs = {} + layer_attrs = {} + inputs_name, inputs_node = mapper._get_inputs_name(node) + # 获取当前节点输出的list + current_outputs = [output_name] + # 处理输入0,即%input.101 + mapper._check_input(graph, inputs_node[0], inputs_name[0], current_outputs, scope_name) + layer_inputs["x"] = inputs_name[0] + current_inputs = list(layer_inputs.values()) + # 处理输入1,即%726 + layer_attrs["upscale_factor"] = mapper.attrs[inputs_name[1]] + + graph.add_layer( + "paddle.nn.functional.pixel_shuffle", + inputs=layer_inputs, + outputs=layer_outputs, + scope_name=scope_name, + **layer_attrs) + return current_inputs, current_outputs + def aten_pow(mapper, graph, node): """ 构造指数激活的PaddleLayer。