FlyingQianMM 5 жил өмнө
parent
commit
d3f538340a
92 өөрчлөгдсөн 1775 нэмэгдсэн , 572 устгасан
  1. 4 3
      .github/ISSUE_TEMPLATE/4_gui.md
  2. 7 1
      README.md
  3. 126 0
      README_EN.md
  4. 1 1
      deploy/lite/export_lite.py
  5. 6 4
      deploy/openvino/python/converter.py
  6. 9 22
      deploy/openvino/python/deploy.py
  7. 3 3
      deploy/openvino/src/paddlex.cpp
  8. 1 1
      docs/apis/models/semantic_segmentation.md
  9. 10 0
      docs/apis/slim.md
  10. 1 1
      docs/apis/transforms/cls_transforms.md
  11. 34 0
      docs/data/annotation/classification.md
  12. 13 0
      docs/data/annotation/index.rst
  13. 42 0
      docs/data/annotation/instance_segmentation.md
  14. 26 0
      docs/data/annotation/labelme.md
  15. 47 0
      docs/data/annotation/object_detection.md
  16. BIN
      docs/data/annotation/pics/detection1.png
  17. BIN
      docs/data/annotation/pics/detection2.png
  18. BIN
      docs/data/annotation/pics/detection3.png
  19. BIN
      docs/data/annotation/pics/detection4.png
  20. BIN
      docs/data/annotation/pics/detection5.png
  21. 42 0
      docs/data/annotation/semantic_segmentation.md
  22. 4 0
      docs/deploy/export_model.md
  23. 1 1
      docs/deploy/hub_serving.md
  24. 3 1
      docs/deploy/openvino/export_openvino_model.md
  25. 3 1
      docs/deploy/openvino/introduction.md
  26. 1 1
      docs/deploy/openvino/linux.md
  27. 2 2
      docs/deploy/openvino/python.md
  28. 3 1
      docs/deploy/openvino/windows.md
  29. 11 10
      docs/deploy/raspberry/Raspberry.md
  30. 17 0
      docs/deploy/server/encryption.md
  31. 1 1
      docs/deploy/server/index.rst
  32. 2 2
      docs/examples/change_detection.md
  33. 27 0
      docs/examples/human_segmentation.md
  34. 32 0
      docs/gui/FAQ.md
  35. 13 7
      docs/gui/download.md
  36. 33 14
      docs/gui/how_to_use.md
  37. BIN
      docs/gui/images/paddlexoverview.png
  38. BIN
      docs/gui/images/paddlexoverview_en.png
  39. 1 1
      docs/gui/index.rst
  40. 41 0
      docs/gui/introduce.md
  41. 68 8
      docs/index.rst
  42. 14 0
      docs/slim/index.rst
  43. 78 0
      docs/slim/prune.md
  44. 38 0
      docs/slim/quant.md
  45. 0 1
      docs/train/index.rst
  46. 41 0
      docs/train/model_export.md
  47. BIN
      examples/human_segmentation/data/background.jpg
  48. BIN
      examples/human_segmentation/data/human_image.jpg
  49. 15 6
      paddlex/__init__.py
  50. 3 1
      paddlex/command.py
  51. 242 405
      paddlex/converter.py
  52. 5 7
      paddlex/cv/datasets/analysis.py
  53. 15 7
      paddlex/cv/datasets/voc.py
  54. 11 5
      paddlex/cv/models/base.py
  55. 7 0
      paddlex/cv/models/load_model.py
  56. 5 4
      paddlex/cv/models/ppyolo.py
  57. 54 12
      paddlex/cv/models/slim/prune.py
  58. 5 2
      paddlex/cv/models/slim/visualize.py
  59. 5 5
      paddlex/cv/models/utils/pretrain_weights.py
  60. 2 2
      paddlex/cv/models/utils/seg_eval.py
  61. 9 9
      paddlex/cv/transforms/cls_transforms.py
  62. 5 3
      paddlex/cv/transforms/seg_transforms.py
  63. 16 5
      paddlex/deploy.py
  64. 2 1
      paddlex/tools/x2coco.py
  65. 2 1
      paddlex/tools/x2imagenet.py
  66. 5 4
      paddlex/tools/x2seg.py
  67. 4 3
      paddlex/tools/x2voc.py
  68. 1 1
      requirements.txt
  69. 1 1
      setup.py
  70. 4 0
      tutorials/compress/README.md
  71. 1 1
      tutorials/compress/classification/cal_sensitivities_file.py
  72. 51 0
      tutorials/slim/prune/image_classification/README.md
  73. 44 0
      tutorials/slim/prune/image_classification/mobilenetv2_prune_train.py
  74. 41 0
      tutorials/slim/prune/image_classification/mobilenetv2_train.py
  75. 17 0
      tutorials/slim/prune/image_classification/params_analysis.py
  76. BIN
      tutorials/slim/prune/image_classification/sensitivities.png
  77. 3 0
      tutorials/slim/prune/image_classification/slim_visualize.py
  78. 50 0
      tutorials/slim/prune/object_detection/README.md
  79. 14 0
      tutorials/slim/prune/object_detection/params_analysis.py
  80. BIN
      tutorials/slim/prune/object_detection/sensitivities.png
  81. 3 0
      tutorials/slim/prune/object_detection/slim_visualize.py
  82. 54 0
      tutorials/slim/prune/object_detection/yolov3_prune_train.py
  83. 51 0
      tutorials/slim/prune/object_detection/yolov3_train.py
  84. 51 0
      tutorials/slim/prune/semantic_segmentation/README.md
  85. 14 0
      tutorials/slim/prune/semantic_segmentation/params_analysis.py
  86. BIN
      tutorials/slim/prune/semantic_segmentation/sensitivities.png
  87. 3 0
      tutorials/slim/prune/semantic_segmentation/slim_visualize.py
  88. 46 0
      tutorials/slim/prune/semantic_segmentation/unet_prune_train.py
  89. 43 0
      tutorials/slim/prune/semantic_segmentation/unet_train.py
  90. 16 0
      tutorials/slim/quant/image_classification/README.md
  91. 31 0
      tutorials/slim/quant/image_classification/mobilenetv2_quant.py
  92. 18 0
      tutorials/slim/quant/image_classification/paddlelite_export.py

+ 4 - 3
.github/ISSUE_TEMPLATE/4_gui.md

@@ -1,9 +1,10 @@
 ---
-name: 4. PaddleX GUI使用问题
-about: Paddle GUI客户端使用问题
+name: 4. PaddleX可视化客户端使用问题
+about: PaddleX可视化客户端使用问题
 ---
-问题类型:PaddleX GUI  
+问题类型:PaddleX可视化客户端  
 **问题描述**  
 
 ===================================  
+
 请在这里描述您在使用GUI过程中的问题

+ 7 - 1
README.md

@@ -1,3 +1,5 @@
+简体中文| [English](./README_EN.md)
+
 
 
 
@@ -108,8 +110,12 @@ pip install paddlex -i https://mirror.baidu.com/pypi/simple
 ## 交流与反馈
 
 - 项目官网:https://www.paddlepaddle.org.cn/paddle/paddlex
+
 - PaddleX用户交流群:957286141 (手机QQ扫描如下二维码快速加入)  
-  ![](./docs/gui/images/QR2.jpg)
+  
+  <p align="center">
+    <img src="./docs/gui/images/QR2.jpg" width="250" height ="360" alt="QR" align="middle" />
+  </p>
 
 
 

+ 126 - 0
README_EN.md

@@ -0,0 +1,126 @@
+[简体中文](./README.md) | English
+
+
+
+
+
+<p align="center">
+  <img src="./docs/gui/images/paddlex.png" width="360" height ="55" alt="PaddleX" align="middle" />
+</p>
+
+
+<p align= "center"> PaddleX -- PaddlePaddle End-to-End Development Toolkit, 
+  enables developers to implement real industry projects in a low-code form quickly </p>
+
+[![License](https://img.shields.io/badge/license-Apache%202-red.svg)](LICENSE)[![Version](https://img.shields.io/github/release/PaddlePaddle/PaddleX.svg)](https://github.com/PaddlePaddle/PaddleX/releases)![python version](https://img.shields.io/badge/python-3.6+-orange.svg)![support os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-yellow.svg)
+![QQGroup](https://img.shields.io/badge/QQ_Group-1045148026-52B6EF?style=social&logo=tencent-qq&logoColor=000&logoWidth=20)
+
+[Complete PaddleX Online Documentation Contents](https://paddlex.readthedocs.io/zh_CN/develop/index.html)
+
+It is integrated the abilities of **Image classification**, **Object detection**, **Semantic segmentation**, and **Instance segmentation** in the Paddle CV toolkits, and get through the whole-process development from **Data preparation** and **Model training and optimization** to **Multi-end deployment**. At the same time, PaddleX provides **Succinct APIs** and a **Graphical Ueser Interface**. Developers can quickly complete the end-to-end process development of the Paddle in a form of **low-code**  without installing different libraries.
+
+**PaddleX** has been validated in a dozen of industry application scenarios such as **Quality Inspection**, **Security**, **Patrol Inspection**, **Remote Sensing**, **Retail**,  **Medical** etc.. In addition, it **provides a wealth of case practice tutorials**, to help developer could apply to actual cases easily.
+
+![](./docs/gui/images/paddlexoverview_en.png)
+
+
+## Installation
+
+**PaddleX has two development modes to meet different needs of users:**
+
+1.**Python development mode:**
+
+The design of PaddleX Python API taking into account of comprehensive functions, development flexibility, and integration convenience, giving developers the smoothest deep learning development experience.
+
+**Pre-dependence**
+
+> - paddlepaddle >= 1.8.4
+> - python >= 3.6
+> - cython
+> - pycocotools
+
+```
+pip install paddlex -i https://mirror.baidu.com/pypi/simple
+```
+Please refer to the [PaddleX installation](https://paddlex.readthedocs.io/zh_CN/develop/install.html) for detailed installation method.
+
+
+2. **Padlde GUI(Graphical Ueser Interface) mode:**
+
+It's a all-in-one client enable develops could implement deep learning projects without code. 
+
+- Go to [PaddleX Official Website](https://www.paddlepaddle.org.cn/paddle/paddlex) to download the all-in-one client.
+
+- Go to [PaddleX GUI tutorial](./docs/gui/how_to_use.md ) for details of using it.
+
+- [PaddleX GUI Environment Requirements for Installation](. /docs/gui/download.md)
+
+
+## Product Module Description
+
+- **Data preparation**: Compatible with common data protocols such as ImageNet, VOC, COCO, and seamlessly interconnecting with Labelme, Colabeler, and [EasyData intelligent data service platform](https://ai.baidu.com/easydata/), to help developers to quickly complete data preparations.
+- **Data pre-processing and enhancement**: Provides a minimalist image pre-processing and enhancement method--Transforms. Adapts imgaug which is a powerful image enhancement library, so that PaddleX could supports **Hundreds of data enhancement strategies**, which makes developers quickly alleviate the situation of traing with small sample dataset.
+- **Model training**: PaddleX integrates [PaddleClas](https://github.com/PaddlePaddle/PaddleClas), [PaddleDetection](https://github.com/PaddlePaddle/PaddleDetection), and [PaddleSeg](https://github.com/PaddlePaddle/PaddleSeg) etcs. So it provides a large number of selected, industry-proven, high-quality pre-trained models, enabling developers to achieve the industry requirements much more quickly.
+- **Model tuning**: Model-interpretability module and [VisualDL](https://github.com/PaddlePaddle/VisualDL) visual analysis tool are integrated as well. It allows developers to understand the model's feature extraction region and the change of the training process parameters more intuitively , so as to quickly optimize the model.
+- **Multi-End Secure Deployment**: The built-in model compression tool-- [PaddleSlim](https://github.com/PaddlePaddle/PaddleSlim)  and **Model Encryption Deployment Module**, are seamlessly interconnected with native prediction library **Paddle Inference** and Multi-platform high performance deep learning inference engine-- [Paddle Lite](https://github.com/PaddlePaddle/Paddle-Lite) , to enable developers to quickly implement multi-end, high-performance, secure deployments of the model.
+
+
+
+## Full Documentation and API Description
+
+- [Complete PaddleX online documentation contents](https://paddlex.readthedocs.io/zh_CN/develop/index.html)
+
+- [10-Minute Quick Start Tutorial Series](https://paddlex.readthedocs.io/zh_CN/develop/quick_start.html)
+- [Collection of PaddleX Model Training Tutorials](https://paddlex.readthedocs.io/zh_CN/develop/train/index.html)
+- [PaddleX API Interface Description](https://paddlex.readthedocs.io/zh_CN/develop/apis/index.html)
+
+### Examples of Online Projects
+
+To get developers up to speed with the PaddleX API, we've created a complete series of sample tutorials that you can run PaddleX projects online through the **AIStudio** quickly.
+
+- [PaddleX Quick Start - CV Model Training](https://aistudio.baidu.com/aistudio/projectdetail/450925)
+- [PaddleX Quick Start - MobileNetV3-ssld Cosmetics Classification](https://aistudio.baidu.com/aistudio/projectdetail/450220)
+- [PaddleX Quick Start - Faster-RCNN AI Bug Recognition](https://aistudio.baidu.com/aistudio/projectdetail/439888)
+- [PaddleX Quick Start - DeepLabv3+ Semantic Segmentation](https://aistudio.baidu.com/aistudio/projectdetail/440197)
+
+
+
+## Full Process Industry Applications
+
+(continue to be updated)
+
+* Industrial inspections:
+  - [Industrial Meter Readings](https://paddlex.readthedocs.io/zh_CN/develop/examples/meter_reader.html)
+* Industrial quality control:
+  - Battery separator defect detection (Coming Soon)
+* [Portrait Segmentation](https://paddlex.readthedocs.io/zh_CN/develop/examples/human_segmentation.html)
+
+
+
+## [FAQ](./docs/gui/faq.md)
+
+
+
+## Communication and Feedback
+
+- Project official website: https://www.paddlepaddle.org.cn/paddle/paddlex
+- PaddleX user group: 957286141 (Scan the following QR code on Mobile QQ to join quickly)
+
+<p align="center">
+  <img src="./docs/gui/images/QR2.jpg" width="250" height ="360" alt="QR" align="middle" />
+</p>
+
+## Release Note
+
+> [Complete Release Note](https://paddlex.readthedocs.io/zh_CN/develop/change_log.html)
+- 2020.09.05 v1.2.0
+- 2020.07.13 v1.1.0
+- 2020.07.12 v1.0.8
+- 2020.05.20 v1.0.0
+- 2020.05.17 v0.1.8
+
+
+
+## Contribution
+
+You are welcomed to contribute codes to PaddleX or provide suggestions. If you can fix an issue or add a new feature, please feel free to submit Pull Requests.

+ 1 - 1
deploy/lite/export_lite.py

@@ -42,7 +42,7 @@ if __name__ == '__main__':
     parser.add_argument(
         "--save_file",
         type=str,
-        default="paddlex.onnx",
+        default="paddlex",
         help="file name for storing the output files.",
         required=True)
     FLAGS = parser.parse_args()

+ 6 - 4
deploy/openvino/python/converter.py

@@ -20,6 +20,8 @@ import sys
 import yaml
 import paddlex as pdx
 
+assert pdx.__version__ >= '1.2.6', "paddlex >= 1.2.6 is required."
+
 
 def arg_parser():
     parser = argparse.ArgumentParser()
@@ -50,10 +52,11 @@ def arg_parser():
 
 def export_openvino_model(model, args):
 
+    onnx_save_file = os.path.join(args.save_dir, 'paddle2onnx_model.onnx')
     if model.__class__.__name__ == "YOLOv3":
-        pdx.converter.export_onnx_model(model, args.save_dir)
+        pdx.converter.export_onnx_model(model, onnx_save_file)
     else:
-        pdx.converter.export_onnx_model(model, args.save_dir, 11)
+        pdx.converter.export_onnx_model(model, onnx_save_file, 11)
 
     import mo.main as mo
     from mo.utils.cli_parser import get_onnx_cli_parser
@@ -61,8 +64,7 @@ def export_openvino_model(model, args):
     onnx_parser.add_argument("--model_dir", type=_text_type)
     onnx_parser.add_argument("--save_dir", type=_text_type)
     onnx_parser.add_argument("--fixed_input_shape")
-    onnx_input = os.path.join(args.save_dir, 'paddle2onnx_model.onnx')
-    onnx_parser.set_defaults(input_model=onnx_input)
+    onnx_parser.set_defaults(input_model=onnx_save_file)
     onnx_parser.set_defaults(output_dir=args.save_dir)
     shape_list = args.fixed_input_shape[1:-1].split(',')
     with open(osp.join(args.model_dir, "model.yml")) as f:

+ 9 - 22
deploy/openvino/python/deploy.py

@@ -38,11 +38,6 @@ class Predictor:
         self.model_name = self.info['Model']
         self.num_classes = self.info['_Attributes']['num_classes']
         self.labels = self.info['_Attributes']['labels']
-        if self.info['Model'] == 'MaskRCNN':
-            if self.info['_init_params']['with_fpn']:
-                self.mask_head_resolution = 28
-            else:
-                self.mask_head_resolution = 14
         transforms_mode = self.info.get('TransformsMode', 'RGB')
         if transforms_mode == 'RGB':
             to_rgb = True
@@ -137,7 +132,7 @@ class Predictor:
     def preprocess(self, image):
         res = dict()
         if self.model_type == "classifier":
-            im, = self.transforms(image)
+            im = self.transforms(image)
             im = np.expand_dims(im, axis=0).copy()
             res['image'] = im
         elif self.model_type == "detector":
@@ -147,14 +142,6 @@ class Predictor:
                 im_shape = np.expand_dims(im_shape, axis=0).copy()
                 res['image'] = im
                 res['im_size'] = im_shape
-            if self.model_name.count('RCNN') > 0:
-                im, im_resize_info, im_shape = self.transforms(image)
-                im = np.expand_dims(im, axis=0).copy()
-                im_resize_info = np.expand_dims(im_resize_info, axis=0).copy()
-                im_shape = np.expand_dims(im_shape, axis=0).copy()
-                res['image'] = im
-                res['im_info'] = im_resize_info
-                res['im_shape'] = im_shape
         elif self.model_type == "segmenter":
             im, im_info = self.transforms(image)
             im = np.expand_dims(im, axis=0).copy()
@@ -173,6 +160,7 @@ class Predictor:
             'category': self.labels[l],
             'score': preds[output_name][0][l],
         } for l in pred_label]
+        print(result)
         return result
 
     def segmenter_postprocess(self, preds, preprocessed_inputs):
@@ -185,8 +173,10 @@ class Predictor:
         score_name = next(it)
         score_map = np.squeeze(preds[score_name])
         score_map = np.transpose(score_map, (1, 2, 0))
+
         im_info = preprocessed_inputs['im_info']
-        for info in im_info[::-1]:
+
+        for info in im_info[0][::-1]:
             if info[0] == 'resize':
                 w, h = info[1][1], info[1][0]
                 label_map = cv2.resize(label_map, (w, h), cv2.INTER_NEAREST)
@@ -195,21 +185,18 @@ class Predictor:
                 w, h = info[1][1], info[1][0]
                 label_map = label_map[0:h, 0:w]
                 score_map = score_map[0:h, 0:w, :]
-            else:
-                raise Exception("Unexpected info '{}' in im_info".format(info[
-                    0]))
         return {'label_map': label_map, 'score_map': score_map}
 
     def detector_postprocess(self, preds, preprocessed_inputs):
         """对图像检测结果做后处理
         """
         outputs = self.net.outputs
-        for name in outpus:
-            if (len(outputs[name].shape == 3)):
-                output = preds[name][0]
+        for name in outputs:
+            if (len(outputs[name].shape) == 2):
+                output = preds[name]
         result = []
         for out in output:
-            if (out[0] > 0):
+            if (out[0] >= 0):
                 result.append(out.tolist())
             else:
                 pass

+ 3 - 3
deploy/openvino/src/paddlex.cpp

@@ -164,12 +164,12 @@ bool Model::predict(const cv::Mat& im, DetResult* result) {
   InferenceEngine::OutputsDataMap out_maps = network_.getOutputsInfo();
   std::string outputName;
   for (const auto & output_map : out_maps) {
-    if (output_map.second->getTensorDesc().getDims().size() == 3) {
+    if (output_map.second->getTensorDesc().getDims().size() == 2) {
       outputName = output_map.first;
     }
   }
   if (outputName.empty()) {
-    std::cerr << "get result node failed!" << std::endl:
+    std::cerr << "get result node failed!" << std::endl;
     return false;
   }
   InferenceEngine::Blob::Ptr output = infer_request.GetBlob(outputName);
@@ -185,7 +185,7 @@ bool Model::predict(const cv::Mat& im, DetResult* result) {
   }
   int num_boxes = size / 6;
   for (int i = 0; i < num_boxes; ++i) {
-    if (data[i * 6] > 0) {
+    if (data[i * 6] >= 0) {
       Box box;
       box.category_id = static_cast<int>(data[i * 6]);
       box.category = labels[box.category_id];

+ 1 - 1
docs/apis/models/semantic_segmentation.md

@@ -13,7 +13,7 @@ paddlex.seg.DeepLabv3p(num_classes=2, backbone='MobileNetV2_x1.0', output_stride
 > > - **num_classes** (int): 类别数。
 > > - **backbone** (str): DeepLabv3+的backbone网络,实现特征图的计算,取值范围为['Xception65', 'Xception41', 'MobileNetV2_x0.25', 'MobileNetV2_x0.5', 'MobileNetV2_x1.0', 'MobileNetV2_x1.5', 'MobileNetV2_x2.0', 'MobileNetV3_large_x1_0_ssld'],默认值为'MobileNetV2_x1.0'。
 > > - **output_stride** (int): backbone 输出特征图相对于输入的下采样倍数,一般取值为8或16。默认16。
-> > - **aspp_with_sep_conv** (bool):  decoder模块是否采用separable convolutions。默认True。
+> > - **aspp_with_sep_conv** (bool):  aspp模块是否采用separable convolutions。默认True。
 > > - **decoder_use_sep_conv** (bool): decoder模块是否采用separable convolutions。默认True。
 > > - **encoder_with_aspp** (bool): 是否在encoder阶段采用aspp模块。默认True。
 > > - **enable_decoder** (bool): 是否使用decoder模块。默认True。

+ 10 - 0
docs/apis/slim.md

@@ -1,6 +1,16 @@
 # 模型压缩
 
+## paddlex.slim.prune.analysis
+> **计算参数敏感度**
+```
+paddlex.slim.prune.analysis(model, dataset, batch_size, save_file='model.sensi.data')
+```
+此函数接口与`paddlex.slim.cal_params_sensitivites`接口功能一致,仅修改了函数名,参数名,顺序和默认值,推荐使用此接口。
+
+使用示例参考[教程-模型裁剪训练](https://github.com/PaddlePaddle/PaddleX/tree/develop/tutorials/slim/prune)
+
 ## paddlex.slim.cal_params_sensitivities
+> 此函数接口与`paddlex.slim.prune.analysis`功能一致,推荐使用`paddlex.slim.prune.analysis`接口  
 > **计算参数敏感度**  
 ```
 paddlex.slim.cal_params_sensitivities(model, save_file, eval_dataset, batch_size=8)

+ 1 - 1
docs/apis/transforms/cls_transforms.md

@@ -68,7 +68,7 @@ paddlex.cls.transforms.RandomCrop(crop_size=224, lower_scale=0.08, lower_ratio=3
 * **crop_size** (int): 随机裁剪后重新调整的目标边长。默认为224。
 * **lower_scale** (float): 裁剪面积相对原面积比例的最小限制。默认为0.08。
 * **lower_ratio** (float): 宽变换比例的最小限制。默认为3. / 4。
-* **upper_ratio** (float): 宽变换比例的最限制。默认为4. / 3。
+* **upper_ratio** (float): 宽变换比例的最限制。默认为4. / 3。
 
 ## RandomHorizontalFlip
 ```python

+ 34 - 0
docs/data/annotation/classification.md

@@ -0,0 +1,34 @@
+# 图像分类
+
+图像分类标注是一项最基础,最简单的标注任务,用户只需将属于同一类的图片放在同一个文件夹下即可,例如下所示目录结构,
+```
+MyDataset/ # 图像分类数据集根目录
+|--dog/ # 当前文件夹所有图片属于dog类别
+|  |--d1.jpg
+|  |--d2.jpg
+|  |--...
+|  |--...
+|
+|--...
+|
+|--snake/ # 当前文件夹所有图片属于snake类别
+|  |--s1.jpg
+|  |--s2.jpg
+|  |--...
+|  |--...
+```
+
+## 数据划分
+
+在模型进行训练时,我们需要划分训练集,验证集和测试集,因此需要对如上数据进行划分,直接使用paddlex命令即可将数据集随机划分成70%训练集,20%验证集和10%测试集
+```
+paddlex --split_dataset --format ImageNet --dataset_dir MyDataset --val_value 0.2 --test_value 0.1
+```
+
+划分好的数据集会额外生成`labels.txt`, `train_list.txt`, `val_list.txt`, `test_list.txt`四个文件,之后可直接进行训练。
+
+> 注:如您使用PaddleX可视化客户端进行模型训练,数据集划分功能集成在客户端内,无需自行使用命令划分
+
+
+- [图像分类任务训练示例代码](https://github.com/PaddlePaddle/PaddleX/blob/develop/tutorials/train/image_classification/mobilenetv2.py)
+

+ 13 - 0
docs/data/annotation/index.rst

@@ -0,0 +1,13 @@
+数据标注、转换、划分
+=======================================
+
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 目录:
+
+   classification.md
+   object_detection.md
+   instance_segmentation.md
+   semantic_segmentation.md
+   labelme.md

+ 42 - 0
docs/data/annotation/instance_segmentation.md

@@ -0,0 +1,42 @@
+# 实例分割
+
+实例分割数据的标注推荐使用LabelMe标注工具,如您先前并无安装,那么LabelMe的安装可参考[LabelMe安装和启动](labelme.md)
+
+**注意:LabelMe对于中文支持不够友好,因此请不要在如下的路径以及文件名中出现中文字符!**
+
+## 准备工作     
+
+1. 将收集的图像存放于`JPEGImages`文件夹下,例如存储在`D:\MyDataset\JPEGImages`
+2. 创建与图像文件夹相对应的文件夹`Annotations`,用于存储标注的json文件,如`D:MyDataset\Annotations`
+3. 打开LabelMe,点击”Open Dir“按钮,选择需要标注的图像所在的文件夹打开,则”File List“对话框中会显示所有图像所对应的绝对路径,接着便可以开始遍历每张图像,进行标注工作      
+
+## 目标边缘标注    
+
+1. 打开多边形标注工具(右键菜单->Create Polygon)以打点的方式圈出目标的轮廓,并在弹出的对话框中写明对应label(当label已存在时点击即可,此处请注意label勿使用中文),具体如下提所示,当框标注错误时,可点击左侧的“Edit Polygons”再点击标注框,通过拖拉进行修改,也可再点击“Delete Polygon”进行删除。
+![](./pics/detection2.png)
+
+2. 点击右侧”Save“,将标注结果保存到中创建的文件夹Annotations目录中
+
+## 格式转换
+
+LabelMe标注后的数据还需要进行转换为MSCOCO格式,才可以用于实例分割任务的训练,创建保存目录`D:\dataset_seg`,在python环境中安装paddlex后,使用如下命令即可
+```
+paddlex --data_conversion --source labelme --to MSCOCO \
+        --pics D:\MyDataset\JPEGImages \
+        --annotations D:\MyDataset\Annotations \
+        --save_dir D:\dataset_coco
+```
+
+## 数据集划分
+
+转换完数据后,为了进行训练,还需要将数据划分为训练集、验证集和测试集,同样在安装paddlex后,使用如下命令即可将数据划分为70%训练集,20%验证集和10%的测试集
+```
+paddlex --split_dataset --format COCO --dataset_dir D:\MyDataset --val_value 0.2 --test_value 0.1
+```
+执行上面命令行,会在`D:\MyDataset`下生成`train.json`, `val.json`, `test.json`,分别存储训练样本信息,验证样本信息,测试样本信息
+
+> 注:如您使用PaddleX可视化客户端进行模型训练,数据集划分功能集成在客户端内,无需自行使用命令划分
+
+
+- [实例分割任务训练示例代码](https://github.com/PaddlePaddle/PaddleX/blob/develop/tutorials/train/instance_segmentation/mask_rcnn_r50_fpn.py)
+

+ 26 - 0
docs/data/annotation/labelme.md

@@ -0,0 +1,26 @@
+# LabelMe的安装和启动
+
+LabelMe可用于标注目标检测、实例分割、语义分割数据集,是一款开源的标注工具。
+
+## 1. 安装Anaconda
+
+推荐使用Anaconda安装python依赖,有经验的开发者可以跳过此步骤。安装Anaconda的方式可以参考[文档](../../appendix/anaconda_install.md)。
+
+在安装Anaconda,并创建环境之后,再进行接下来的步骤
+
+## 2. 安装LabelMe
+
+进入Python环境后,执行如下命令即可
+```
+conda activate my_paddlex
+conda install pyqt
+pip install labelme
+```
+
+## 3. 启动LabelMe
+
+进入安装了LabelMe的Python环境,执行如下命令即可启动LabelMe
+```
+conda activate my_paddlex
+labelme
+```

+ 47 - 0
docs/data/annotation/object_detection.md

@@ -0,0 +1,47 @@
+# 目标检测
+
+目标检测数据的标注推荐使用LabelMe标注工具,如您先前并无安装,那么LabelMe的安装可参考[LabelMe安装和启动](labelme.md)
+
+**注意:LabelMe对于中文支持不够友好,因此请不要在如下的路径以及文件名中出现中文字符!**
+
+## 准备工作     
+
+1. 将收集的图像存放于`JPEGImages`文件夹下,例如存储在`D:\MyDataset\JPEGImages`
+2. 创建与图像文件夹相对应的文件夹`Annotations`,用于存储标注的json文件,如`D:MyDataset\Annotations`
+3. 打开LabelMe,点击”Open Dir“按钮,选择需要标注的图像所在的文件夹打开,则”File List“对话框中会显示所有图像所对应的绝对路径,接着便可以开始遍历每张图像,进行标注工作      
+
+## 目标框标注    
+
+1. 打开矩形框标注工具(右键菜单->Create Rectangle),具体如下图所示     
+![](./pics/detection1.png)
+
+2. 使用拖拉的方式对目标物体进行标识,并在弹出的对话框中写明对应label(当label已存在时点击即可, 此处请注意label勿使用中文),具体如下图所示,当框标注错误时,可点击左侧的“Edit Polygons”再点击标注框,通过拖拉进行修改,也可再点击“Delete Polygon”进行删除。    
+![](./pics/detection3.png)
+
+3. 点击右侧”Save“,将标注结果保存到中创建的文件夹Annotations目录中
+
+## 格式转换
+
+LabelMe标注后的数据还需要进行转换为PascalVOC或MSCOCO格式,才可以用于目标检测任务的训练,创建`D:\dataset_voc`目录,在python环境中安装paddlex后,使用如下命令即可
+```
+paddlex --data_conversion --source labelme --to PascalVOC \
+        --pics D:\MyDataset\JPEGImages \
+        --annotations D:\MyDataset\Annotations \
+        --save_dir D:\dataset_voc
+```
+
+> 注:此文档中以LabelMe为示例,展示了格式转换,如您使用的是数据标注精灵工具,则可在标注完后,选择直接保存为PascalVOC格式
+
+## 数据集划分
+
+转换完数据后,为了进行训练,还需要将数据划分为训练集、验证集和测试集,同样在安装paddlex后,使用如下命令即可将数据划分为70%训练集,20%验证集和10%的测试集
+```
+paddlex --split_dataset --format VOC --dataset_dir D:\MyDataset --val_value 0.2 --test_value 0.1
+```
+执行上面命令行,会在`D:\MyDataset`下生成`labels.txt`, `train_list.txt`, `val_list.txt`和`test_list.txt`,分别存储类别信息,训练样本列表,验证样本列表,测试样本列表
+
+> 注:如您使用PaddleX可视化客户端进行模型训练,数据集划分功能集成在客户端内,无需自行使用命令划分
+
+
+- [目标检测任务训练示例代码](https://github.com/PaddlePaddle/PaddleX/blob/develop/tutorials/train/object_detection/yolov3_mobilenetv1.py)
+

BIN
docs/data/annotation/pics/detection1.png


BIN
docs/data/annotation/pics/detection2.png


BIN
docs/data/annotation/pics/detection3.png


BIN
docs/data/annotation/pics/detection4.png


BIN
docs/data/annotation/pics/detection5.png


+ 42 - 0
docs/data/annotation/semantic_segmentation.md

@@ -0,0 +1,42 @@
+# 语义分割
+
+语义数据的标注推荐使用LabelMe标注工具,如您先前并无安装,那么LabelMe的安装可参考[LabelMe安装和启动](labelme.md),语义分割的标注与实例分割类似,流程如下
+
+**注意:LabelMe对于中文支持不够友好,因此请不要在如下的路径以及文件名中出现中文字符!**
+
+## 准备工作     
+
+1. 将收集的图像存放于`JPEGImages`文件夹下,例如存储在`D:\MyDataset\JPEGImages`
+2. 创建与图像文件夹相对应的文件夹`Annotations`,用于存储标注的json文件,如`D:MyDataset\Annotations`
+3. 打开LabelMe,点击”Open Dir“按钮,选择需要标注的图像所在的文件夹打开,则”File List“对话框中会显示所有图像所对应的绝对路径,接着便可以开始遍历每张图像,进行标注工作      
+
+## 目标边缘标注    
+
+1. 打开多边形标注工具(右键菜单->Create Polygon)以打点的方式圈出目标的轮廓,并在弹出的对话框中写明对应label(当label已存在时点击即可,此处请注意label勿使用中文),具体如下提所示,当框标注错误时,可点击左侧的“Edit Polygons”再点击标注框,通过拖拉进行修改,也可再点击“Delete Polygon”进行删除。
+![](./pics/detection2.png)
+
+2. 点击右侧”Save“,将标注结果保存到中创建的文件夹Annotations目录中
+
+## 格式转换
+
+LabelMe标注后的数据还需要进行转换为SEG格式,才可以用于语义分割任务的训练,创建保存目录`D:\dataset_seg`,在python环境中安装paddlex后,使用如下命令即可
+```
+paddlex --data_conversion --source labelme --to SEG \
+        --pics D:\MyDataset\JPEGImages \
+        --annotations D:\MyDataset\Annotations \
+        --save_dir D:\dataset_seg
+```
+
+## 数据集划分
+
+转换完数据后,为了进行训练,还需要将数据划分为训练集、验证集和测试集,同样在安装paddlex后,使用如下命令即可将数据划分为70%训练集,20%验证集和10%的测试集
+```
+paddlex --split_dataset --format SEG --dataset_dir D:\MyDataset --val_value 0.2 --test_value 0.1
+```
+执行上面命令行,会在`D:\MyDataset`下生成`train_list.txt`, `val_list.txt`, `test_list.txt`,分别存储训练样本信息,验证样本信息,测试样本信息
+
+> 注:如您使用PaddleX可视化客户端进行模型训练,数据集划分功能集成在客户端内,无需自行使用命令划分
+
+
+- [语义分割任务训练示例代码](https://github.com/PaddlePaddle/PaddleX/blob/develop/tutorials/train/semantic_segmentation/deeplabv3p_xception65.py)
+

+ 4 - 0
docs/deploy/export_model.md

@@ -1,5 +1,7 @@
 # 部署模型导出
 
+**注:所有涉及到模型部署,均需要参考本文档,进行部署模型导出**  
+
 在服务端部署模型时需要将训练过程中保存的模型导出为inference格式模型,导出的inference格式模型包括`__model__`、`__params__`和`model.yml`三个文件,分别表示模型的网络结构、模型权重和模型的配置文件(包括数据预处理参数等)。
 
 > **检查你的模型文件夹**,如果里面是`model.pdparams`, `model.pdmodel`和`model.yml`3个文件时,那么就需要按照下面流程进行模型导出
@@ -22,7 +24,9 @@ paddlex --export_inference --model_dir=./xiaoduxiong_epoch_12 --save_dir=./infer
 
 **注意**:
 - 分类模型的固定输入大小请保持与训练时的输入大小一致;
+- 检测模型模型中YOLO系列请保存w与h一致,且为32的倍数大小;RCNN类无此限制,按需设定即可
 - 指定[w,h]时,w和h中间逗号隔开,不允许存在空格等其他字符。
+- 需要注意的,w,h设得越大,模型在预测过程中所需要的耗时和内存/显存占用越高;设得太小,会影响模型精度
 
 ```
 paddlex --export_inference --model_dir=./xiaoduxiong_epoch_12 --save_dir=./inference_model --fixed_input_shape=[640,960]

+ 1 - 1
docs/deploy/hub_serving.md

@@ -1,4 +1,4 @@
-# 轻量级服务化部署
+# PaddleHub轻量级服务化部署
 ## 简介
 借助`PaddleHub-Serving`,可以将`PaddleX`的`Inference Model`进行快速部署,以提供在线预测的能力。
 

+ 3 - 1
docs/deploy/openvino/export_openvino_model.md

@@ -17,6 +17,8 @@ paddle模型转openvino之前需要先把paddle模型导出为inference格式模
 paddlex --export_inference --model_dir=/path/to/paddle_model --save_dir=./inference_model --fixed_input_shape=[w,h]
 ```
 
+**注意**:需要转OpenVINO模型时,导出inference模型请务必指定`--fixed_input_shape`参数来固定模型的输入大小,且模型的输入大小需要与训练时一致。 PaddleX客户端在发布模型时没有固定输入大小,因此对于可视化客户端,请找到任务所在目录,从里面的`output`文件夹找到`best_model`模型目录,将此目录使用如上命令进行固定shape导出即可。
+
 ## 导出OpenVINO模型
 
 ```
@@ -35,7 +37,7 @@ python converter.py --model_dir /path/to/inference_model --save_dir /path/to/ope
 | --model_dir  | Paddle模型路径,请确保__model__, \_\_params__model.yml在同一个目录|
 | --save_dir  | OpenVINO模型保存路径 |
 | --fixed_input_shape  | 模型输入的[W,H] |
-| --data type(option)  | FP32、FP16,默认为FP32,VPU下的IR需要为FP16 |  
+| --data_type(option)  | (可选)FP32、FP16,默认为FP32,VPU下的IR需要为FP16 |  
 
 **注意**:
 - 由于OpenVINO 从2021.1版本开始支持ONNX的resize-11 OP的原因,请下载OpenVINO 2021.1+的版本

+ 3 - 1
docs/deploy/openvino/introduction.md

@@ -1,5 +1,7 @@
 # OpenVINO部署简介
-PaddleX支持将训练好的Paddle模型通过OpenVINO实现模型的预测加速,OpenVINO详细资料与安装流程请参考[OpenVINO](https://docs.openvinotoolkit.org/latest/index.html),本文档使用OpenVINO 2020.4测试通过。
+PaddleX支持将训练好的Paddle模型通过OpenVINO实现模型的预测加速,OpenVINO详细资料与安装流程请参考[OpenVINO](https://docs.openvinotoolkit.org/latest/index.html),本文档使用OpenVINO 2020.4与2021.1测试通过。  
+**注意**:由于PaddleX分割模型使用了ReSize-11 Op,OpenVINO 2021.1版本开始支持支持Resize-11 ,请务必下载OpenVINO 2021.1+版本  
+
 
 ## 部署支持情况
 下表提供了PaddleX在不同环境下对使用OpenVINO加速的支持情况  

+ 1 - 1
docs/deploy/openvino/linux.md

@@ -7,7 +7,7 @@
 * GCC* 5.4.0
 * CMake 3.0+
 * PaddleX 1.0+
-* OpenVINO 2020.4
+* OpenVINO 2021.1+
 * 硬件平台:CPU、VPU
 
 **说明**:PaddleX安装请参考[PaddleX](https://paddlex.readthedocs.io/zh_CN/develop/install.html) , OpenVINO安装请根据相应的系统参考[OpenVINO-Linux](https://docs.openvinotoolkit.org/latest/_docs_install_guides_installing_openvino_linux.html)或者[OpenVINO-Raspbian](https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_raspbian.html)

+ 2 - 2
docs/deploy/openvino/python.md

@@ -1,9 +1,9 @@
 # Python预测部署
-文档说明了在python下基于OpenVINO的预测部署,部署前需要先将paddle模型转换为OpenVINO的Inference Engine,请参考[模型转换](docs/deploy/openvino/export_openvino_model.md)。目前CPU硬件上支持PadlleX的分类、检测、分割模型;VPU上支持PaddleX的分类模型。
+文档说明了在python下基于OpenVINO的预测部署,部署前需要先将paddle模型转换为OpenVINO的Inference Engine,请参考[模型转换](./export_openvino_model.md)。目前CPU硬件上支持PadlleX的分类、检测、分割模型;VPU上支持PaddleX的分类模型。
 
 ## 前置条件
 * Python 3.6+
-* OpenVINO 2020.4
+* OpenVINO 2021.1
 
 **说明**:OpenVINO安装请参考[OpenVINO](https://docs.openvinotoolkit.org/latest/index.html)  
 

+ 3 - 1
docs/deploy/openvino/windows.md

@@ -5,7 +5,7 @@ Windows 平台下,我们使用`Visual Studio 2019 Community` 进行了测试
 
 ## 前置条件
 * Visual Studio 2019
-* OpenVINO 2020.4
+* OpenVINO 2021.1+
 * CMake 3.0+
 
 **说明**:PaddleX安装请参考[PaddleX](https://paddlex.readthedocs.io/zh_CN/develop/install.html) , OpenVINO安装请参考[OpenVINO-Windows](https://docs.openvinotoolkit.org/latest/openvino_docs_install_guides_installing_openvino_windows.html)  
@@ -42,9 +42,11 @@ git clone https://github.com/PaddlePaddle/PaddleX.git
 提供了依赖软件预编译库:
 - [gflas](https://bj.bcebos.com/paddlex/deploy/windows/third-parts.zip)  
 - [opencv](https://bj.bcebos.com/paddleseg/deploy/opencv-3.4.6-vc14_vc15.exe)  
+
 请下载上面两个连接的预编译库。若需要自行下载请参考:
 - gflags:[下载地址](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags)
 - opencv:[下载地址](https://opencv.org/releases/)  
+
 下载完opencv后需要配置环境变量,如下流程所示  
     - 我的电脑->属性->高级系统设置->环境变量
     - 在系统变量中找到Path(如没有,自行创建),并双击编辑

+ 11 - 10
docs/deploy/raspberry/Raspberry.md

@@ -31,11 +31,11 @@ sudo apt-get upgrade
 
 请确保系统已经安装好上述基本软件,并配置好相应环境,**下面所有示例以工作目录 `/root/projects/`演示**。
 
-## Paddle-Lite模型转换
+### Paddle-Lite模型转换
 将PaddleX模型转换为Paddle-Lite模型,具体请参考[Paddle-Lite模型转换](./export_nb_model.md)
 
-## Paddle-Lite 预测
-### Step1 下载PaddleX预测代码
+### Paddle-Lite 预测
+#### Step1 下载PaddleX预测代码
 ```
 mkdir -p /root/projects
 cd /root/projects
@@ -43,7 +43,7 @@ git clone https://github.com/PaddlePaddle/PaddleX.git
 ```
 **说明**:其中C++预测代码在PaddleX/deploy/raspberry 目录,该目录不依赖任何PaddleX下其他目录,如果需要在python下预测部署请参考[Python预测部署](./python.md)。  
 
-### Step2:Paddle-Lite预编译库下载
+#### Step2:Paddle-Lite预编译库下载
 提供了下载的opt工具对应的Paddle-Lite在ArmLinux下面的预编译库:[Paddle-Lite(ArmLinux)预编译库](https://bj.bcebos.com/paddlex/deploy/lite/inference_lite_2.6.1_armlinux.tar.bz2)。  
 建议用户使用预编译库,若需要自行编译,在树莓派上LX终端输入
 ```
@@ -56,14 +56,15 @@ sudo ./lite/tools/build.sh  --arm_os=armlinux --arm_abi=armv7hf --arm_lang=gcc
 
 **注意**:预测库版本需要跟opt版本一致,更多Paddle-Lite编译内容请参考[Paddle-Lite编译](https://paddle-lite.readthedocs.io/zh/latest/user_guides/source_compile.html);更多预编译Paddle-Lite预测库请参考[Paddle-Lite Release Note](https://github.com/PaddlePaddle/Paddle-Lite/releases)
 
-### Step3 软件依赖
+#### Step3 软件依赖
 提供了依赖软件的预编包或者一键编译,用户不需要单独下载或编译第三方依赖软件。若需要自行编译第三方依赖软件请参考:
 
 - gflags:编译请参考 [编译文档](https://gflags.github.io/gflags/#download)  
 
 - opencv: 编译请参考
 [编译文档](https://docs.opencv.org/master/d7/d9f/tutorial_linux_install.html)
-### Step4: 编译
+
+#### Step4: 编译
 编译`cmake`的命令在`scripts/build.sh`中,修改LITE_DIR为Paddle-Lite预测库目录,若自行编译第三方依赖软件请根据Step1中编译软件的实际情况修改主要参数,其主要内容说明如下:
 ```
 # Paddle-Lite预编译库的路径
@@ -79,7 +80,7 @@ OPENCV_DIR=$(pwd)/deps/opencv/
  ```  
 
 
-### Step3: 预测
+#### Step5: 预测
 
 编译成功后,分类任务的预测可执行程序为`classifier`,分割任务的预测可执行程序为`segmenter`,检测任务的预测可执行程序为`detector`,其主要命令参数说明如下:  
 
@@ -92,7 +93,7 @@ OPENCV_DIR=$(pwd)/deps/opencv/
 | --cfg_file | PaddleX model 的.yml配置文件 |
 | --save_dir | 可视化结果图片保存地址,仅适用于检测和分割任务,默认值为" "既不保存可视化结果 |
 
-### 样例
+#### 样例
 `样例一`:
 单张图片分类任务  
 测试图片 `/path/to/test_img.jpeg`  
@@ -152,5 +153,5 @@ OPENCV_DIR=$(pwd)/deps/opencv/
 
 ## NCS2部署
 树莓派支持通过OpenVINO在NCS2上跑PaddleX模型预测,目前仅支持PaddleX的分类网络,基于NCS2的方式包含Paddle模型转OpenVINO IR以及部署IR在NCS2上进行预测两个步骤。
-- 模型转换请参考:[PaddleX模型转换为OpenVINO IR]('./openvino/export_openvino_model.md'),raspbian OS上的OpenVINO不支持模型转换,需要先在host侧转换FP16的IR。
-- 预测部署请参考[OpenVINO部署](./openvino/linux.md)中VPU在raspbian OS部署的部分
+- 模型转换请参考:[PaddleX模型转换为OpenVINO IR](../openvino/export_openvino_model.md),raspbian OS上的OpenVINO不支持模型转换,需要先在host侧转换FP16的IR。
+- 预测部署请参考[OpenVINO部署](../openvino/linux.md)中VPU在raspbian OS部署的部分

+ 17 - 0
docs/deploy/server/encryption.md

@@ -165,3 +165,20 @@ D:\projects\images\xiaoduxiongn.jpeg
 .\paddlex_inference\detector.exe --model_dir=D:\projects\paddlex_encrypted_model --image_list=D:\projects\images_list.txt --use_gpu=1 --save_dir=output --key=kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=
 ```
 `--key`传入加密工具输出的密钥,例如`kLAl1qOs5uRbFt0/RrIDTZW2+tOf5bzvUIaHGF8lJ1c=`, 图片文件`可视化预测结果`会保存在`save_dir`参数设置的目录下。
+
+## 附录-PaddlePaddle其它模型加密使用
+
+PaddleX提供的加密方案不仅只适配于PaddleX训练的模型,对于PaddlePaddle其它模型套件或用户自行开发的模型同样适用。用户完全可以使用PaddleX的加密工具来加密如PaddleDetection、PaddleSeg、PaddleClas、PaddleOCR等套件训练的模型。
+
+### 加密方法
+1. 下载PaddleX加密工具
+- [Linux版本](https://bj.bcebos.com/paddlex/tools/1.2.0/paddlex-encryption.zip)
+- [Windows版本](https://bj.bcebos.com/paddlex/tools/win/1.2.0/paddlex-encryption.zip)
+
+2. 加密模型
+一般我们使用Paddle训练导出的部署模型都可以保存为`__model__`和`__params__`两个文件,PaddleX在模型保存时,还会额外再保存一个`model.yml`文件,用于存储模型的一些信息。PaddleX在加密模型时,会读取模型目录中这三个文件,进行加密,生成`__model__.encrypted`, `__params__.encrypted`和`model.yml.encrypted`三个文件。  
+因此在使用PaddleX加密工具加密别的套件或用户自行开发训练导出的模型时,需在模型目录中包含`__model__`,`__params__`和`model.yml`三个文件。非PaddleX训练的模型,用户可自行创建一个`model.yml`的同名文件,加密后无需理会此文件即可。
+> 模型加密命令参考本文档前面步骤
+
+3. 加载加密模型
+在使用Paddle预测库加载加密模型时,C++代码开发参考[Paddle部署代码模型加载函数](https://github.com/PaddlePaddle/PaddleX/blob/develop/deploy/cpp/src/paddlex.cpp#L46),同时需要引入新的头文件`paddle_model_decrypt.h`,相应依赖就在下载的加密工具中,CMakelist中参考[PaddleX的依赖](https://github.com/PaddlePaddle/PaddleX/blob/develop/deploy/cpp/CMakeLists.txt#L52)即可。

+ 1 - 1
docs/deploy/server/index.rst

@@ -1,4 +1,4 @@
-服务端部署
+CPU/GPU(加密)部署
 =======================================
 
 

+ 2 - 2
docs/examples/change_detection.md

@@ -6,7 +6,7 @@
 
 * Paddle paddle >= 1.8.4
 * Python >= 3.5
-* PaddleX >= 1.3.0
+* PaddleX >= 1.2.2
 
 安装的相关问题参考[PaddleX安装](../install.md)
 
@@ -43,7 +43,7 @@ python prepare_data.py
 
 **注意:**
 
-* tiff格式的图片PaddleX统一使用gdal库读取,gdal安装可参考[文档](https://paddlex.readthedocs.io/zh_CN/develop/examples/multi-channel_remote_sensing/README.html#id2)。若数据是tiff格式的三通道RGB图像,如果不想安装gdal,需自行转成jpeg、bmp、png格式图片。
+* tiff格式的图片PaddleX统一使用gdal库读取,gdal安装可参考[gdal文档](https://paddlex.readthedocs.io/zh_CN/develop/examples/multi-channel_remote_sensing/README.html#id2)。若数据是tiff格式的三通道RGB图像,如果不想安装gdal,需自行转成jpeg、bmp、png格式图片。
 
 * label文件需为单通道的png格式图片,且标注从0开始计数,标注255表示该类别不参与计算。例如本案例中,0表示`unchanged`类,1表示`changed`类。
 

+ 27 - 0
docs/examples/human_segmentation.md

@@ -17,6 +17,33 @@
 > * Inference Model和Quant Inference Model为预测部署模型,包含`__model__`计算图结构、`__params__`模型参数和`model.yaml`基础的模型配置信息。
 > * 其中Inference Model适用于服务端的CPU和GPU预测部署,Qunat Inference Model为量化版本,适用于通过Paddle Lite进行移动端等端侧设备部署。
 
+#### 关于预测锯齿问题
+在训练完模型后,可能会遇到预测出来结果存在『锯齿』的问题,这个可能存在的原因是由于模型在预测过程中,经历了原图缩放再放大的过程,如下流程所示,
+
+```
+原图输入 -> 预处理transforms将图像缩放至目标大小 -> Paddle模型预测 -> 预测结果放大至原图大小 
+```
+对于这种原因导致的问题,可以手动修改模型中的`model.yml`文件,将预处理中的目标大小**调整到更高**优化此问题,如在本文档中提供的人像分割server端模型中`model.yml`文件内容,修改`target_size`至1024*1024(这样也会带来模型预测所需的资源更多,预测速度更慢)
+```
+Model: DeepLabv3p
+Transforms:
+- Resize:
+    interp: LINEAR
+    target_size:
+    - 512
+    - 512
+```
+修改为
+```
+Model: DeepLabv3p
+Transforms:
+- Resize:
+    interp: LINEAR
+    target_size:
+    - 1024
+    - 1024
+```
+
 
 预训练模型的存储大小和推理时长如下所示,其中移动端模型的运行环境为cpu:骁龙855,内存:6GB,图片大小:192*192
 

+ 32 - 0
docs/gui/FAQ.md

@@ -0,0 +1,32 @@
+# PaddleX客户端常见问题
+
+## 1. 训练出错,提示『训练任务异常中止,请查阅错误日志具体确认原因』?
+请按照以下步骤来查找原因
+
+- 1.首先根据提示,找到错误日志,根据日志提示判断原因
+- 2.如无法确定原因,测a)尝试重新训练,看是否能正常训练; b)调低batchsize(同时按比例调低学习率)排除是否是显存不足原因导致
+- 3.如第2步仍然失败,请前往GitHub提ISSUE,a) 描述清楚问题 b) 贴上训练参数截图 c) 附上错误日志   https://github.com/PaddlePaddle/PaddleX/issues
+- 4.如无Github帐号,则可加QQ群1045148026在线咨询工程师(咨询时请附上出错日志)
+
+## 2. 没有使用GPU,使用CPU,错误日志仍然提示"cuda error"
+部分Windows机型由于GPU型号或驱动较老,导致训练时无法使用GPU,还会导致使用不了CPU,针对此情况,可采取如下方式解决
+- 1.在PaddleX客户端安装目录下,删除"paddle"文件夹
+- 2.下载paddlepaddle-cpu(压缩文件可在[百度网盘](https://pan.baidu.com/s/1GrzLCuzuw-PAEx4BELnc0w)下载,提取码iaor,约57M),下载解压后,将目前中的paddle文件夹拷贝至PaddleX客户端安装目录下即可
+- 3.重新启动PaddleX客户端,替换后客户端仅支持使用CPU训练模型
+
+## 3. 如何升级PaddleX客户端
+PaddleX客户端目前需用户手动下载最新版升级,旧版本可直接删除原安装目录即可。升级前请备份好工作空间下的3个workspace.*.pb文件,避免升级过程可能导致项目信息丢失。
+
+PaddleX更新历史和下载地址: https://www.paddlepaddle.org.cn/paddlex/download
+
+## 4. 如何卸载PaddleX客户端
+客户端安装本质只是解压文件到相应目录,因此卸载直接删除安装目录和桌面快捷方式即可。
+
+## 5. 使用客户端训练检测模型在测试图片中出现很多目标框,甚至同一物体上出现多个目标框
+目标检测模型和实例分割模型,在模型预测阶段,会将所有可能的目标都输出,对于输出,我们需要可以按如下方式来处理
+- 1.观察预测出来的各个目标框,各框上应该还同时包含相应框的置信度分数
+- 2.设定一个threshold,过滤掉低置信度的目标框
+上面两个步骤,在客户端的评估界面,我们可以手动输入threshold后,重新再预测;而对于在实际使用,例如Python预测部署,则根据得到结果中的'score'进行过滤
+
+## 6. 使用CPU训练时,如何设置CPU_NUM多CPU卡进行训练
+Windows平台上由于缺少NCCL库,无法使用多GPU或多CPU训练。而对于v1.1.5版本及以下版本,当前无法设置多CPU进行训练(v1.1.5版本及以下版本,请勿在环境变量中设置CPU_NUM,可能会导致无法使用CPU进行模型训练)

+ 13 - 7
docs/gui/download.md

@@ -1,15 +1,22 @@
-## PaddleX GUI安装
+# 下载安装
 
- PaddleX GUI是提升项目开发效率的核心模块,开发者可快速完成深度学习模型全流程开发。我们诚挚地邀请您前往 [官网](https://www.paddlepaddle.org.cn/paddle/paddleX)下载试用PaddleX GUI可视化前端,并获得您宝贵的意见或开源项目贡献。
+下载地址:https://www.paddlepaddle.org.cn/paddle/paddleXPaddleX 
 
 
+## 安装方式
 
-### 安装推荐环境
+**注意:安装/解压路径请务必在不包含中文和空格的路径下,否则会导致可能无法正确训练模型**
+
+- Windows下载后双击后选择安装路径即可
+- Mac/Ubuntu下载后解压即可
+
+
+## 安装推荐环境
 
 * **操作系统**:
-  * Windows7/8/10(推荐Windows 10);
+  * Windows 10;
   * Mac OS 10.13+;
-  * Ubuntu 18.04+;
+  * Ubuntu 18.04(Ubuntu暂只支持18.04)
 
 ***注:处理器需为x86_64架构,支持MKL。***
 
@@ -24,5 +31,4 @@
   * **内存**:建议8G以上  
   * **硬盘空间**:建议SSD剩余空间1T以上(非必须)  
 
-***注:PaddleX在Mac OS系统只支持单卡模型。Windows系统暂不支持NCCL。***
-
+***注:PaddleX在Mac OS系统只支持CPU训练。Windows系统只支持单GPU卡训练。***

+ 33 - 14
docs/gui/how_to_use.md

@@ -1,14 +1,16 @@
-# PaddleX GUI使用教程
+# 使用方法
 
 *注:如果你的系统是 Mac OS 10.15.5及以上,在双击客户端icon后,需要在Terminal中执行 sudo xattr -r -d com.apple.quarantine /Users/username/PaddleX ,并稍等几秒来启动客户端,其中 /Users/username/PaddleX 为您保存PaddleX的文件夹路径*
 
-**第一步:准备数据**
+## 准备和导入数据
 
-在开始模型训练前,您需要根据不同的任务类型,将数据标注为相应的格式。目前PaddleX支持【图像分类】、【目标检测】、【语义分割】、【实例分割】四种任务类型。不同类型任务的数据处理方式可查看[数据标注方式](https://paddlex.readthedocs.io/zh_CN/latest/appendix/datasets.html)。
+**第一步: 准备数据**
+在开始模型训练前,您需要根据不同的任务类型,将数据标注为相应的格式。目前PaddleX支持【图像分类】、【目标检测】、【语义分割】、【实例分割】四种任务类型。  
+开发者可以参考PaddleX使用文档中的[2.数据准备-数据标注](../data/annotations/index.html)来进行数据标注和转换工作。 如若开发者自行准备数据,请注意数据格式与PaddleX支持四种数据格式是否一致。
 
- 
 
-**第二步:导入我的数据集**
+
+**第二步:导入的据集**
 
 ①数据标注完成后,您需要根据不同的任务,将数据和标注文件,按照客户端提示更名并保存到正确的文件中。
 
@@ -22,7 +24,10 @@
 
 ![](images/dataset2.jpg)
 
-**第三步:创建项目**
+
+## 创建项目和任务
+
+**第一步:创建项目**
 
 ① 在完成数据导入后,您可以点击「新建项目」创建一个项目。
 
@@ -32,7 +37,7 @@
 
 
 
-**第步:项目开发**
+**第步:项目开发**
 
 ① **数据选择**:项目创建完成后,您需要选择已载入客户端并校验后的数据集,并点击下一步,进入参数配置页面。
 
@@ -42,9 +47,11 @@
 
 ![](images/project2.jpg)
 
+## 任务模型训练
+
 参数配置完成后,点击启动训练,模型开始训练并进行效果评估。
 
- **训练可视化**:在训练过程中,您可通过VisualDL查看模型训练过程参数变化、日志详情,及当前最优的训练集和验证集训练指标。模型在训练过程中通过点击"中止训练"随时中止训练过程。
+ **训练可视化**:在训练过程中,您可通过VisualDL查看模型训练过程参数变化、日志详情,及当前最优的训练集和验证集训练指标。模型在训练过程中通过点击"中止训练"随时中止训练过程。
 
 ![](images/visualization1.jpg)
 
@@ -52,11 +59,24 @@
 
 ![](images/visualization2.jpg)
 
-④ **模型裁剪**:如果开发者希望减少模型的体积、计算量,提升模型在设备上的预测性能,可以采用PaddleX提供的模型裁剪策略。裁剪过程将对模型各卷积层的敏感度信息进行分析,根据各参数对模型效果的影响进行不同比例的裁剪,再进行精调训练获得最终裁剪后的模型。
+> 模型训练是最容易出错的步骤,经常遇到的原因为电脑无法联网下载预训练模型、显存不够。训练检测模型\实例分割模型对于显存要求较高,**建议用户通过在Windows/Mac/Ubuntu的命令行终端(Windows的Cmd命令终端)执行`nvidia-smi`命令**查看显存情况,请不要使用系统自带的任务管理器查看。  
+
+## 任务模型裁剪训练
+
+此步骤可选,模型裁剪训练相对比普通的任务模型训练,需要消耗更多的时间,需要在正常任务模型训练的基础上,增加『**模型裁剪分类**』和『**模型裁剪训练**』两个步骤。  
+
+裁剪过程将对模型各卷积层的敏感度信息进行分析,根据各参数对模型效果的影响进行不同比例的裁剪,再进行精调训练获得最终裁剪后的模型。  
+裁剪训练后的模型体积,计算量都会减少,并且可以提升模型在低性能设备的预测速度,如移动端,边缘设备,CPU。
+
+在可视化客户端上,**用户训练好模型后**,在训练界面,
+- 首先,点击『模型裁剪分析』,此过程将会消耗较长的时间
+- 接着,点击『开始模型裁剪训练』,客户端会创建一个新的任务,无需修改参数,直接再启动训练即可
 
 ![](images/visualization3.jpg)
 
-⑤ **模型评估**:在模型评估页面,您可查看训练后的模型效果。评估方法包括混淆矩阵、精度、召回率等。
+## 模型效果评估
+
+在模型评估页面,您可查看训练后的模型效果。评估方法包括混淆矩阵、精度、召回率等。
 
 ![](images/visualization4.jpg)
 
@@ -64,11 +84,10 @@
 
 ![](images/visualization5.jpg)
 
+## 模型发布
 
-
-**第五步:模型发布**
-
-当模型效果满意后,您可根据实际的生产环境需求,选择将模型发布为需要的版本。
+当模型效果满意后,您可根据实际的生产环境需求,选择将模型发布为需要的版本。  
+> 如若要部署到移动端/边缘设备,对于部分支持量化的模型,还可以根据需求选择是否量化。量化可以压缩模型体积,提升预测速度
 
 ![](images/publish.jpg)
 

BIN
docs/gui/images/paddlexoverview.png


BIN
docs/gui/images/paddlexoverview_en.png


+ 1 - 1
docs/gui/index.rst

@@ -47,7 +47,7 @@ PaddleX GUI覆盖深度学习模型开发必经的 **数据处理** 、 **超参
    :caption: 文档目录
    
    download.md
-   how_to_use.md
+   usage/index
    faq.md
 
 

+ 41 - 0
docs/gui/introduce.md

@@ -0,0 +1,41 @@
+# 介绍
+
+PaddleX可视化客户端基于PaddleX开发的可视化深度学习模型训练套件,目前支持训练视觉领域的图像分类、目标检测、实例分割和语义分割四大任务,同时支持模型裁剪、模型量化两种方式压缩模型。开发者以点选、键入的方式快速体验深度学习模型开发的全流程。可以作为您提升深度学习模型开发效率的工具。
+
+PaddleX GUI 当前提供Windows,Mac,Ubuntu三种版本一键绿色安装的方式。请至飞桨官网:https://www.paddlepaddle.org.cn/paddle/paddleX 下载您需要的版本。
+
+## 功能
+PaddleX可视化客户端是PaddleX API的衍生品,它在集成API功能的基础上,额外提供了可视化分析、评估等附加功能,致力于为开发者带来极致顺畅的开发体验。其拥有以下独特的功能:
+
+### 全流程打通
+
+PaddleX GUI覆盖深度学习模型开发必经的 **数据处理** 、 **超参配置** 、 **模型训练及优化** 、 **模型发布** 全流程,无需开发一行代码,即可得到高性深度学习推理模型。
+
+### 数据集智能分析
+
+详细的数据结构说明,并提供 **数据标签自动校验** 。支持 **可视化数据预览** 、 **数据分布图表展示** 、 **一键数据集切分** 等实用功能
+
+### 自动超参推荐
+
+集成飞桨团队长时间产业实践经验,根据用户选择的模型类别、骨架网络等,提供多种针对性优化的 **预训练模型** ,并 **提供推荐超参配置** ,可 **一键开启多种优化策略**
+
+### 可视化模型评估
+
+集成 **可视化分析工具:VisualDL** , 以线性图表的形式展示acc、lr等关键参数在训练过程中的变化趋势。提供 **混淆矩阵** 等实用方法,帮助快速定位问题,加速调参。模型评估报告一键导出,方便项目复盘分析。
+
+### 模型裁剪及量化
+
+一键启动模型裁剪、量化,在不同阶段为开发者提供模型优化的策略,满足不同环境对模型性能的需求。
+
+### 预训练模型管理
+
+可对历史训练模型进行保存及管理,未进行裁剪的模型可以保存为预训练模型,在后续任务中使用。
+
+### 可视化模型测试
+
+客户端直接展示模型预测效果,无需上线即可进行效果评估
+
+### 模型多端部署
+
+点选式选择模型发布平台、格式,一键导出预测模型,并匹配完善的模型预测部署说明文档,贴心助力产业端到端项目落地
+

+ 68 - 8
docs/index.rst

@@ -8,18 +8,78 @@ PaddleX是基于飞桨核心框架、开发套件和工具组件的深度学习
 * 官方QQ用户群: 1045148026  
 * GitHub Issue反馈: http://www.github.com/PaddlePaddle/PaddleX/issues
 
+1. 注:本使用手册在打印为pdf后,可能会存在部分格式的兼容问题;
+2. 注:本文档持续在http://paddlex.readthedocs.io/进行更新。
+
 
 .. toctree::
-   :maxdepth: 2
-   :caption: PaddleX使用文档目录
+   :maxdepth: 1
+   :caption: 1. 快速了解PaddleX
 
    quick_start.md
    install.md
-   data/index
+
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 2. 数据准备
+
+   data/annotation/index
+   data/format/index
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 3. 模型训练与参数调整
+
    train/index
-   deploy/index
-   examples/index
-   gui/index
-   apis/index
+   train/prediction.md
+   appendix/parameters.md
+   train/model_export.md
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 4. 模型压缩优化
+
+   slim/prune.md
+   slim/quant.md
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 5. 模型多端安全部署
+
+   deploy/export_model.md
+   deploy/hub_serving.md
+   deploy/server/index
+   deploy/nvidia-jetson.md
+   deploy/paddlelite/android.md
+   deploy/raspberry/index
+   deploy/openvino/index
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 6. 产业案例集
+
+   examples/meter_reader.md
+   examples/human_segmentation.md
+   examples/remote_sensing.md
+   examples/multi-channel_remote_sensing/README.md
+   examples/change_detection.md
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 7. 可视化客户端使用
+
+   gui/introduce.md
+   gui/download.md
+   gui/how_to_use.md
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 8. 附录
+
+   apis/index.rst
+   appendix/model_zoo.md
+   appendix/metrics.md
+   appendix/interpret.md
+   appendix/how_to_offline_run.md
    change_log.md
-   appendix/index

+ 14 - 0
docs/slim/index.rst

@@ -0,0 +1,14 @@
+模型压缩
+=======================================
+
+PaddleX集成了PaddleSlim, 可帮助用户对模型进行裁剪和量化,减小模型的计算量和体积,提升模型的预测性能。
+
+- 模型裁剪多适用于低性能设备,如CPU、移动端和端侧部署时使用,对于性能较好的GPU,裁剪不一定能带来性能的提升。
+- 模型量化将模型的浮点型计算转为整型计算,从而加速模型的预测速度,减小模型的体积,对于PaddleLite在Arm设备上部署性能有明显提升。
+
+.. toctree::
+   :maxdepth: 1
+   :caption: 文档目录:
+
+   prune.md
+   quant.md

+ 78 - 0
docs/slim/prune.md

@@ -0,0 +1,78 @@
+# 模型裁剪
+
+模型裁剪可以更好地满足在端侧、移动端上部署场景下的性能需求,可以有效得降低模型的体积,以及计算量,加速预测性能。PaddleX集成了PaddleSlim的基于敏感度的通道裁剪算法,用户可以在PaddleX的训练代码里轻松使用起来。
+
+在本文档中展示了分类模型的裁剪过程,文档中代码以及更多其它模型的的裁剪代码可在Github中的[tutorials/slim/prune](https://github.com/PaddlePaddle/PaddleX/tree/develop/tutorials/slim/prune)目录获取。
+
+
+## 使用方法
+
+模型裁剪相对比我们普通训练一个模型,步骤会多出两步
+
+- 1.采用正常的方式训练一个模型  
+- 2.对模型的参数进行敏感度分析
+- 3.根据第2步得到的敏感度信息,对模型进行裁剪,并以第1步训练好的模型作为预训练权重,继续进行训练
+
+具体我们以图像分类模型MobileNetV2为例,本示例中所有代码均可在Github的[tutorials/slim/prune/image_classification]中获得。
+
+### 第一步 正常训练模型
+
+此步骤中采用正常的代码进行模型训练,在获取[本示例代码](https://github.com/PaddlePaddle/PaddleX/tree/develop/tutorials/slim/prune/image_classification)后,直接执行如下命令即可
+```
+python mobilenetv2_train.py
+```
+在训练完成后,我们以`output/mobilenetv2/best_model`保存的模型,继续接下来的步骤
+
+
+### 第二步 参数敏感度分析
+
+此步骤中,我们需要加载第一步训练保存的模型,并通过不断地遍历参数,分析各参数裁剪后在验证数据集上的精度损失,以此判断各参数的敏感度。敏感度分析的代码很简单, 用户可直接查看[params_analysis.py](https://github.com/PaddlePaddle/PaddleX/tree/develop/tutorials/slim/prune/image_classification)。在命令行终端执行如下命令开始参数分析。
+```
+python params_analysis.py
+```
+
+在此步骤中,我们会得到保存的`mobilenetv2.sensi.data`文件,这个文件保存了模型中每个参数的敏感度,在后续的裁剪训练中,会根据此文件中保存的信息,对各个参数进行裁剪。同时,我们也可以对这个文件进行可视化分析,判断`eval_metric_loss`的大小设置与模型被裁剪比例的关系。(`eval_metric_loss`的说明见第三步)
+
+模型裁剪比例可视化分析代码见[slim_visualize.py](https://github.com/PaddlePaddle/PaddleX/tree/develop/tutorials/slim/prune/image_classification),执行如下命令即可
+```
+python slim_visualize.py
+```
+可视化结果如下,该图表明,当我们将`eval_metric_loss`设为0.05时,模型将被裁剪掉65%;将`eval_metric_loss`设为0.10,模型将被裁剪掉68.0%。因此在实际使用时,我们可以根据自己的需求,去设置`eval_metric_loss`控制裁剪比例。
+
+### 第三步 模型裁剪训练
+
+在前两步,我们得到了正常训练保存的模型`output/mobilenetv2/best_model`和基于该保存模型得到的参数敏感度信息文件`mobilenetv2.sensi.data`,接下来则是进行模型裁剪训练。  
+模型裁剪训练的代码第第一步基本一致,唯一区别在最后的`train`函数中,我们修改了`pretrain_weights`,`save_dir`,`sensitivities_file`和`eval_metric_loss`四个参数,如下所示
+```
+model.train(
+	num_epoch=10,
+	train_dataset=train_dataset,
+	train_batch_size=32,
+	eval_dataset=eval_dataset,
+	lr_decay_epochs=[4,6,8],
+	learning_rate=0.025,
+	pretrain_weights='output/mobilenetv2/best_model',
+	save_dir='output/mobilenetv2_prune',
+	sensitivities_file='./mobilenetv2.sensi.data',
+	eval_metric_loss=0.05,
+	use_vdl=True)
+```
+具体代码见[tutorials/slim/prune/image_classification/mobilenetv2_prune_train.py](https://github.com/PaddlePaddle/PaddleX/tree/develop/tutorials/slim/prune/image_classification),执行如下命令即可
+```
+python mobilenetv2_prune_train.py
+```
+其中修改的4个参数函数如下
+- pretrain_weights: 预训练权重,在裁剪训练中,将其指定为第一步正常训练得到的模型路径
+- save_dir: 裁剪训练过程中,模型保存的新路径
+- sensitivities_file: 第二步中分析得到的各参数敏感度信息文件
+- eval_metric_loss: 可用于控制模型最终被裁剪的比例,见第二步中的可视化说明
+
+## 裁剪效果
+
+在本示例的数据集上,经过裁剪训练后,模型的效果对比如下,其中预测速度不包括图像的预处理和结果的后处理。  
+从表中可以看到,对于本示例中的简单数据集,模型裁剪掉68%后,模型准确度没有降低,在CPU的单张图片预测用时减少了37%
+
+| 模型 | 参数大小 | CPU预测速度(MKLDNN关闭) | 准确率 |
+| :--- | :----- | :-------------------- | :--- |
+| output/mobilenetv2/best_model | 8.7M | 0.057s | 0.92 | 
+| output/mobilenetv2_prune/best_model | 2.8M | 0.036s | 0.99 |

+ 38 - 0
docs/slim/quant.md

@@ -0,0 +1,38 @@
+# 模型量化
+
+模型量化将模型的计算从浮点型转为整型,从而加速模型的预测计算速度,在移动端/边缘端设备上降低模型的体积。
+
+> 注:量化后的模型,通过PaddleLite转换为PaddleLite部署的模型格式后,模型体积将会大幅压缩。如若量化后的模型仍是以服务端本地部署格式(文件包括__model__和__params__),那么模型的文件大小是无法呈现参数变化情况的。
+
+## 使用方法
+
+PaddleX中已经将量化功能作为模型导出的一个API,代码使用方式如下,本示例代码和模型数据均可通过GitHub项目上代码[tutorials/slim/quant/image_classification](https://github.com/PaddlePaddle/PaddleX/tree/develop/tutorials/slim/quant/image_classification)获取得到
+```
+import paddlex as pdx
+model = pdx.load_model('mobilenetv2_vegetables')
+# 加载数据集用于量化
+dataset = pdx.datasets.ImageNet(
+                data_dir='vegetables_cls',
+                file_list='vegetables_cls/train_list.txt',
+                label_list='vegetables_cls/labels.txt',
+                transforms=model.test_transforms)
+
+# 开始量化
+pdx.slim.export_quant_model(model, dataset, 
+			  batch_size=4,
+			  batch_num=5,
+	                  save_dir='./quant_mobilenet', 
+	                  cache_dir='./tmp')
+```
+
+在获取本示例代码后,执行如下命令即可完成量化和PaddleLite的模型导出
+```
+# 将mobilenetv2模型量化保存
+python mobilenetv2_quant.py
+# 将量化后的模型导出为PaddleLite部署格式
+python paddlelite_export.py
+```
+
+## 量化效果
+
+在本示例中,我们可以看到模型量化后的服务端部署模型格式`server_mobilenet`和`quant_mobilenet`两个目录中,模型参数大小并无变化。 但在使用PaddleLite导出后,`mobilenetv2.nb`和`mobilenetv2_quant.nb`大小分别为8.8M, 2.7M,压缩至原来的31%。

+ 0 - 1
docs/train/index.rst

@@ -12,5 +12,4 @@ PaddleX集成了PaddleClas、PaddleDetection和PaddleSeg三大CV工具套件中
    object_detection.md
    instance_segmentation.md
    semantic_segmentation.md
-   prediction.md
    visualdl.md

+ 41 - 0
docs/train/model_export.md

@@ -0,0 +1,41 @@
+# 模型保存
+
+## 训练过程保存
+
+PaddleX在模型训练过程中,根据`train`函数接口中的`save_interval_epoch`参数设置,每间隔相应轮数保存一次模型,模型目录中包含了`model.pdparams`, `model.yml`等文件。
+
+在训练过程中保存的模型,可用于作为pretrain_weights继续训练模型,也可使用`paddlex.load_model`接口加载测试模型的预测和评估等。
+
+## 部署模型导出
+
+在前面提到的训练中保存的模型,如若要用于部署(部署可参阅PaddleX文档中的模型多端部署章节),需导出为部署的模型格式,部署的模型目录中包含`__model__`,`__params__`和`model.yml`三个文件。
+
+模型部署在Python层面,可以使用基于高性能预测库的python接口`paddlex.deploy.Predictor`,也可使用`paddlex.load_model`接口。
+
+模型部署可参考文档[部署模型导出](../deploy/export_model.md)
+
+> 【总结】如若模型目录中包含`model.pdparams`,那说明模型是训练过程中保存的,部署时需要进行导出;部署的模型目录中需包含`__model__`,`__params__`和`model.yml`三个文件。
+
+## 模型部署文件说明
+
+- `__model__`:保存了模型的网络结构信息
+- `__params__`: 保存了模型网络中的参数权重
+- `model.yml`:在PaddleX中,将模型的预处理,后处理,以及类别相关信息均存储在此文件中
+
+## 模型导出为ONNX模型
+
+PaddleX作为开放开源的套件,其中的大部分模型均支持导出为ONNX协议,满足开发者多样性的需求。
+> 需要注意的是ONNX存在多个OpSet版本,下表为PaddleX各模型支持导出的ONNX协议版本。
+
+| 模型 | ONNX OpSet 9 | ONNX OpSet 10 | ONNX OpSet 11 |
+| :--- | :----------- | :-----------  | :------------ |
+| 图像分类 | 支持 |  支持 | 支持 |
+| 目标检测(仅YOLOv3系列) | - | 支持 | 支持 |
+| 语义分割(FastSCNN不支持) | - | - | 支持 |
+
+### 如何导出
+
+- 1. 参考文档[部署模型导出](../deploy/eport_model.md),将训练保存的模型导出为部署模型  
+- 2. 安装paddle2onnx `pip install paddle2onnx`,转换命令如下,通过`--opset_version`指定版本(9/10/11),转换使用方法参考[Paddle2ONNX说明](https://github.com/PaddlePaddle/paddle2onnx)
+
+- 附: Paddle2ONNX参阅 [https://github.com/PaddlePaddle/paddle2onnx](https://github.com/PaddlePaddle/paddle2onnx)

BIN
examples/human_segmentation/data/background.jpg


BIN
examples/human_segmentation/data/human_image.jpg


+ 15 - 6
paddlex/__init__.py

@@ -14,6 +14,8 @@
 
 from __future__ import absolute_import
 
+__version__ = '1.2.6'
+
 import os
 if 'FLAGS_eager_delete_tensor_gb' not in os.environ:
     os.environ['FLAGS_eager_delete_tensor_gb'] = '0.0'
@@ -23,6 +25,19 @@ if "CUDA_VISIBLE_DEVICES" in os.environ:
     if os.environ["CUDA_VISIBLE_DEVICES"].count("-1") > 0:
         os.environ["CUDA_VISIBLE_DEVICES"] = ""
 
+import paddle
+version = paddle.__version__.strip().split('.')
+if version[0] == '1':
+    if version[1] != '8':
+        raise Exception(
+            'For running paddlex(v{}), Version of paddlepaddle should be greater than 1.8.3'.
+            format(__version__))
+elif version[0] == '2':
+    print(
+        "[WARNING] You are using paddlepaddle(v{}) which may not compatible with paddlex(v{}), paddlepaddle==1.8.4 is strongly recommended.".
+        format(paddle.__version__, __version__))
+    paddle.enable_static()
+
 from .utils.utils import get_environ_info
 from . import cv
 from . import det
@@ -43,10 +58,6 @@ except:
         "[WARNING] pycocotools install: https://paddlex.readthedocs.io/zh_CN/develop/install.html#pycocotools"
     )
 
-import paddlehub as hub
-if hub.version.hub_version < '1.8.2':
-    raise Exception("[ERROR] paddlehub >= 1.8.2 is required")
-
 env_info = get_environ_info()
 load_model = cv.models.load_model
 datasets = cv.datasets
@@ -55,5 +66,3 @@ transforms = cv.transforms
 log_level = 2
 
 from . import interpret
-
-__version__ = '1.2.1'

+ 3 - 1
paddlex/command.py

@@ -162,6 +162,7 @@ def main():
         assert args.save_dir is not None, "--save_dir should be defined to create onnx model"
 
         model = pdx.load_model(args.model_dir)
+
         if model.status == "Normal" or model.status == "Prune":
             logging.error(
                 "Only support inference model, try to export model first as below,",
@@ -169,7 +170,8 @@ def main():
             logging.error(
                 "paddlex --export_inference --model_dir model_path --save_dir infer_model"
             )
-        pdx.converter.export_onnx_model(model, args.save_dir, args.onnx_opset)
+        save_file = os.path.join(args.save_dir, 'paddle2onnx_model.onnx')
+        pdx.converter.export_onnx_model(model, save_file, args.onnx_opset)
 
     if args.data_conversion:
         assert args.source is not None, "--source should be defined while converting dataset"

+ 242 - 405
paddlex/converter.py

@@ -19,17 +19,244 @@ import sys
 import paddlex as pdx
 import paddlex.utils.logging as logging
 
-__all__ = ['export_onnx']
 
-
-def export_onnx(model_dir, save_dir, fixed_input_shape):
-    assert len(fixed_input_shape) == 2, "len of fixed input shape must == 2"
-    model = pdx.load_model(model_dir, fixed_input_shape)
-    model_name = os.path.basename(model_dir.strip('/')).split('/')[-1]
-    export_onnx_model(model, save_dir)
-
-
-def export_onnx_model(model, save_dir, opset_version=10):
+class MultiClassNMS4OpenVINO():
+    """
+    Convert the paddle multiclass_nms to onnx op.
+    This op is get the select boxes from origin boxes.
+    """
+    @classmethod
+    def opset_10(cls, graph, node, **kw):
+        from paddle2onnx.constant import dtypes
+        import numpy as np
+        result_name = node.output('Out', 0)
+        background = node.attr('background_label')
+        normalized = node.attr('normalized')
+        if normalized == False:
+            logging.warning(
+                        "The parameter normalized of multiclass_nms OP of Paddle is False, which has diff with ONNX." \
+                        " Please set normalized=True in multiclass_nms of Paddle, see doc Q1 in" \
+                        " https://github.com/PaddlePaddle/paddle2onnx/blob/develop/FAQ.md")
+    
+        #convert the paddle attribute to onnx tensor
+        node_score_threshold = graph.make_node(
+            'Constant',
+            inputs=[],
+            dtype=dtypes.ONNX.FLOAT,
+            value=[float(node.attr('score_threshold'))])
+    
+        node_iou_threshold = graph.make_node(
+            'Constant',
+            inputs=[],
+            dtype=dtypes.ONNX.FLOAT,
+            value=[float(node.attr('nms_threshold'))])
+    
+        node_keep_top_k = graph.make_node(
+            'Constant',
+            inputs=[],
+            dtype=dtypes.ONNX.INT64,
+            value=[np.int64(node.attr('keep_top_k'))])
+    
+        node_keep_top_k_2D = graph.make_node(
+            'Constant',
+            inputs=[],
+            dtype=dtypes.ONNX.INT64,
+            dims=[1, 1],
+            value=[node.attr('keep_top_k')])
+    
+        # the paddle data format is x1,y1,x2,y2
+        kwargs = {'center_point_box': 0}
+    
+        node_select_nms= graph.make_node(
+            'NonMaxSuppression',
+            inputs=[node.input('BBoxes', 0), node.input('Scores', 0), node_keep_top_k,\
+                node_iou_threshold, node_score_threshold])
+    
+        # step 1 nodes select the nms class
+        # create some const value to use
+        node_const_value = [result_name+"@const_0",
+            result_name+"@const_1",\
+            result_name+"@const_2",\
+            result_name+"@const_-1"]
+        value_const_value = [0, 1, 2, -1]
+        for name, value in zip(node_const_value, value_const_value):
+            graph.make_node(
+                'Constant',
+                layer_name=name,
+                inputs=[],
+                outputs=[name],
+                dtype=dtypes.ONNX.INT64,
+                value=[value])
+    
+        # In this code block, we will deocde the raw score data, reshape N * C * M to 1 * N*C*M
+        # and the same time, decode the select indices to 1 * D, gather the select_indices
+        node_gather_1 = graph.make_node(
+            'Gather',
+            inputs=[node_select_nms, result_name + "@const_1"],
+            axis=1)
+    
+        node_gather_1 = graph.make_node(
+            'Unsqueeze', inputs=[node_gather_1], axes=[0])
+    
+        node_gather_2 = graph.make_node(
+            'Gather',
+            inputs=[node_select_nms, result_name + "@const_2"],
+            axis=1)
+    
+        node_gather_2 = graph.make_node(
+            'Unsqueeze', inputs=[node_gather_2], axes=[0])
+    
+        # reshape scores N * C * M to (N*C*M) * 1
+        node_reshape_scores_rank1 = graph.make_node(
+            "Reshape",
+            inputs=[node.input('Scores', 0), result_name + "@const_-1"])
+    
+        # get the shape of scores
+        node_shape_scores = graph.make_node(
+            'Shape', inputs=node.input('Scores'))
+    
+        # gather the index: 2 shape of scores
+        node_gather_scores_dim1 = graph.make_node(
+            'Gather',
+            inputs=[node_shape_scores, result_name + "@const_2"],
+            axis=0)
+    
+        # mul class * M
+        node_mul_classnum_boxnum = graph.make_node(
+            'Mul', inputs=[node_gather_1, node_gather_scores_dim1])
+    
+        # add class * M * index
+        node_add_class_M_index = graph.make_node(
+            'Add', inputs=[node_mul_classnum_boxnum, node_gather_2])
+    
+        # Squeeze the indices to 1 dim
+        node_squeeze_select_index = graph.make_node(
+            'Squeeze', inputs=[node_add_class_M_index], axes=[0, 2])
+    
+        # gather the data from flatten scores
+        node_gather_select_scores = graph.make_node(
+            'Gather',
+            inputs=[node_reshape_scores_rank1, node_squeeze_select_index],
+            axis=0)
+    
+        # get nums to input TopK
+        node_shape_select_num = graph.make_node(
+            'Shape', inputs=[node_gather_select_scores])
+    
+        node_gather_select_num = graph.make_node(
+            'Gather',
+            inputs=[node_shape_select_num, result_name + "@const_0"],
+            axis=0)
+    
+        node_unsqueeze_select_num = graph.make_node(
+            'Unsqueeze', inputs=[node_gather_select_num], axes=[0])
+    
+        node_concat_topK_select_num = graph.make_node(
+            'Concat',
+            inputs=[node_unsqueeze_select_num, node_keep_top_k_2D],
+            axis=0)
+    
+        node_cast_concat_topK_select_num = graph.make_node(
+            'Cast', inputs=[node_concat_topK_select_num], to=6)
+        # get min(topK, num_select)
+        node_compare_topk_num_select = graph.make_node(
+            'ReduceMin', inputs=[node_cast_concat_topK_select_num], keepdims=0)
+    
+        # unsqueeze the indices to 1D tensor
+        node_unsqueeze_topk_select_indices = graph.make_node(
+            'Unsqueeze', inputs=[node_compare_topk_num_select], axes=[0])
+    
+        # cast the indices to INT64
+        node_cast_topk_indices = graph.make_node(
+            'Cast', inputs=[node_unsqueeze_topk_select_indices], to=7)
+    
+        # select topk scores  indices
+        outputs_topk_select_topk_indices = [result_name + "@topk_select_topk_values",\
+            result_name + "@topk_select_topk_indices"]
+        node_topk_select_topk_indices = graph.make_node(
+            'TopK',
+            inputs=[node_gather_select_scores, node_cast_topk_indices],
+            outputs=outputs_topk_select_topk_indices)
+    
+        # gather topk label, scores, boxes
+        node_gather_topk_scores = graph.make_node(
+            'Gather',
+            inputs=[
+                node_gather_select_scores, outputs_topk_select_topk_indices[1]
+            ],
+            axis=0)
+    
+        node_gather_topk_class = graph.make_node(
+            'Gather',
+            inputs=[
+                node_gather_1, outputs_topk_select_topk_indices[1]
+            ],
+            axis=1)
+    
+        # gather the boxes need to gather the boxes id, then get boxes
+        node_gather_topk_boxes_id = graph.make_node(
+            'Gather',
+            inputs=[
+                node_gather_2, outputs_topk_select_topk_indices[1]
+            ],
+            axis=1)
+    
+        # squeeze the gather_topk_boxes_id to 1 dim
+        node_squeeze_topk_boxes_id = graph.make_node(
+            'Squeeze', inputs=[node_gather_topk_boxes_id], axes=[0, 2])
+    
+        node_gather_select_boxes = graph.make_node(
+            'Gather',
+            inputs=[node.input('BBoxes', 0), node_squeeze_topk_boxes_id],
+            axis=1)
+    
+        # concat the final result
+        # before concat need to cast the class to float
+        node_cast_topk_class = graph.make_node(
+            'Cast', inputs=[node_gather_topk_class], to=1)
+    
+        node_unsqueeze_topk_scores = graph.make_node(
+            'Unsqueeze', inputs=[node_gather_topk_scores], axes=[0, 2])
+    
+        inputs_concat_final_results = [node_cast_topk_class, node_unsqueeze_topk_scores, \
+            node_gather_select_boxes]
+        node_sort_by_socre_results = graph.make_node(
+            'Concat', inputs=inputs_concat_final_results, axis=2)
+    
+        # select topk classes indices
+        node_squeeze_cast_topk_class = graph.make_node(
+            'Squeeze', inputs=[node_cast_topk_class], axes=[0, 2])
+        node_neg_squeeze_cast_topk_class = graph.make_node(
+            'Neg', inputs=[node_squeeze_cast_topk_class])
+    
+        outputs_topk_select_classes_indices = [result_name + "@topk_select_topk_classes_scores",\
+            result_name + "@topk_select_topk_classes_indices"]
+        node_topk_select_topk_indices = graph.make_node(
+            'TopK',
+            inputs=[node_neg_squeeze_cast_topk_class, node_cast_topk_indices],
+            outputs=outputs_topk_select_classes_indices)
+        node_concat_final_results = graph.make_node(
+            'Gather',
+            inputs=[
+                node_sort_by_socre_results,
+                outputs_topk_select_classes_indices[1]
+            ],
+            axis=1)
+        node_concat_final_results = graph.make_node(
+            'Squeeze',
+            inputs=[node_concat_final_results],
+            outputs=[node.output('Out', 0)],
+            axes=[0])
+    
+        if node.type == 'multiclass_nms2':
+            graph.make_node(
+                'Squeeze',
+                inputs=[node_gather_2],
+                outputs=node.output('Index'),
+                axes=[0])
+    
+
+def export_onnx_model(model, save_file, opset_version=10):
     if model.__class__.__name__ == "FastSCNN" or (
             model.model_type == "detector" and
             model.__class__.__name__ != "YOLOv3"):
@@ -46,401 +273,11 @@ def export_onnx_model(model, save_dir, opset_version=10):
         logging.warning(
             "Export for openVINO by default, the output of multiclass_nms exported to onnx will contains background. If you need onnx completely consistent with paddle, please use paddle2onnx to export"
         )
-        p2o.op_mapper.opset9.paddle_custom_layer.multiclass_nms.multiclass_nms = multiclass_nms_for_openvino
-    mapper = p2o.PaddleOpMapper()
-    mapper.convert(
+    
+    p2o.register_op_mapper('multiclass_nms', MultiClassNMS4OpenVINO)
+    
+    p2o.program2onnx(
         model.test_prog,
-        save_dir,
         scope=model.scope,
+        save_file=save_file,
         opset_version=opset_version)
-
-
-def multiclass_nms_for_openvino(op, block):
-    """
-    Convert the paddle multiclass_nms to onnx op.
-    This op is get the select boxes from origin boxes.
-    This op is for OpenVINO, which donn't support dynamic shape).
-    """
-    import math
-    import sys
-    import numpy as np
-    import paddle.fluid.core as core
-    import paddle.fluid as fluid
-    import onnx
-    import warnings
-    from onnx import helper, onnx_pb
-    inputs = dict()
-    outputs = dict()
-    attrs = dict()
-    for name in op.input_names:
-        inputs[name] = op.input(name)
-    for name in op.output_names:
-        outputs[name] = op.output(name)
-    for name in op.attr_names:
-        attrs[name] = op.attr(name)
-
-    result_name = outputs['Out'][0]
-    background = attrs['background_label']
-    normalized = attrs['normalized']
-    if normalized == False:
-        warnings.warn(
-            'The parameter normalized of multiclass_nms OP of Paddle is False, which has diff with ONNX. \
-                         Please set normalized=True in multiclass_nms of Paddle'
-        )
-
-    #convert the paddle attribute to onnx tensor
-    name_score_threshold = [outputs['Out'][0] + "@score_threshold"]
-    name_iou_threshold = [outputs['Out'][0] + "@iou_threshold"]
-    name_keep_top_k = [outputs['Out'][0] + '@keep_top_k']
-    name_keep_top_k_2D = [outputs['Out'][0] + '@keep_top_k_1D']
-
-    node_score_threshold = onnx.helper.make_node(
-        'Constant',
-        inputs=[],
-        outputs=name_score_threshold,
-        value=onnx.helper.make_tensor(
-            name=name_score_threshold[0] + "@const",
-            data_type=onnx.TensorProto.FLOAT,
-            dims=(),
-            vals=[float(attrs['score_threshold'])]))
-
-    node_iou_threshold = onnx.helper.make_node(
-        'Constant',
-        inputs=[],
-        outputs=name_iou_threshold,
-        value=onnx.helper.make_tensor(
-            name=name_iou_threshold[0] + "@const",
-            data_type=onnx.TensorProto.FLOAT,
-            dims=(),
-            vals=[float(attrs['nms_threshold'])]))
-
-    node_keep_top_k = onnx.helper.make_node(
-        'Constant',
-        inputs=[],
-        outputs=name_keep_top_k,
-        value=onnx.helper.make_tensor(
-            name=name_keep_top_k[0] + "@const",
-            data_type=onnx.TensorProto.INT64,
-            dims=(),
-            vals=[np.int64(attrs['keep_top_k'])]))
-
-    node_keep_top_k_2D = onnx.helper.make_node(
-        'Constant',
-        inputs=[],
-        outputs=name_keep_top_k_2D,
-        value=onnx.helper.make_tensor(
-            name=name_keep_top_k_2D[0] + "@const",
-            data_type=onnx.TensorProto.INT64,
-            dims=[1, 1],
-            vals=[np.int64(attrs['keep_top_k'])]))
-
-    # the paddle data format is x1,y1,x2,y2
-    kwargs = {'center_point_box': 0}
-
-    name_select_nms = [outputs['Out'][0] + "@select_index"]
-    node_select_nms= onnx.helper.make_node(
-        'NonMaxSuppression',
-        inputs=inputs['BBoxes'] + inputs['Scores'] + name_keep_top_k +\
-            name_iou_threshold + name_score_threshold,
-        outputs=name_select_nms)
-    # step 1 nodes select the nms class
-    node_list = [
-        node_score_threshold, node_iou_threshold, node_keep_top_k,
-        node_keep_top_k_2D, node_select_nms
-    ]
-
-    # create some const value to use
-    name_const_value = [result_name+"@const_0",
-        result_name+"@const_1",\
-        result_name+"@const_2",\
-        result_name+"@const_-1"]
-    value_const_value = [0, 1, 2, -1]
-    for name, value in zip(name_const_value, value_const_value):
-        node = onnx.helper.make_node(
-            'Constant',
-            inputs=[],
-            outputs=[name],
-            value=onnx.helper.make_tensor(
-                name=name + "@const",
-                data_type=onnx.TensorProto.INT64,
-                dims=[1],
-                vals=[value]))
-        node_list.append(node)
-
-    # In this code block, we will deocde the raw score data, reshape N * C * M to 1 * N*C*M
-    # and the same time, decode the select indices to 1 * D, gather the select_indices
-    outputs_gather_1_ = [result_name + "@gather_1_"]
-    node_gather_1_ = onnx.helper.make_node(
-        'Gather',
-        inputs=name_select_nms + [result_name + "@const_1"],
-        outputs=outputs_gather_1_,
-        axis=1)
-    node_list.append(node_gather_1_)
-    outputs_gather_1 = [result_name + "@gather_1"]
-    node_gather_1 = onnx.helper.make_node(
-        'Unsqueeze',
-        inputs=outputs_gather_1_,
-        outputs=outputs_gather_1,
-        axes=[0])
-    node_list.append(node_gather_1)
-
-    outputs_gather_2_ = [result_name + "@gather_2_"]
-    node_gather_2_ = onnx.helper.make_node(
-        'Gather',
-        inputs=name_select_nms + [result_name + "@const_2"],
-        outputs=outputs_gather_2_,
-        axis=1)
-    node_list.append(node_gather_2_)
-
-    outputs_gather_2 = [result_name + "@gather_2"]
-    node_gather_2 = onnx.helper.make_node(
-        'Unsqueeze',
-        inputs=outputs_gather_2_,
-        outputs=outputs_gather_2,
-        axes=[0])
-    node_list.append(node_gather_2)
-
-    # reshape scores N * C * M to (N*C*M) * 1
-    outputs_reshape_scores_rank1 = [result_name + "@reshape_scores_rank1"]
-    node_reshape_scores_rank1 = onnx.helper.make_node(
-        "Reshape",
-        inputs=inputs['Scores'] + [result_name + "@const_-1"],
-        outputs=outputs_reshape_scores_rank1)
-    node_list.append(node_reshape_scores_rank1)
-
-    # get the shape of scores
-    outputs_shape_scores = [result_name + "@shape_scores"]
-    node_shape_scores = onnx.helper.make_node(
-        'Shape', inputs=inputs['Scores'], outputs=outputs_shape_scores)
-    node_list.append(node_shape_scores)
-
-    # gather the index: 2 shape of scores
-    outputs_gather_scores_dim1 = [result_name + "@gather_scores_dim1"]
-    node_gather_scores_dim1 = onnx.helper.make_node(
-        'Gather',
-        inputs=outputs_shape_scores + [result_name + "@const_2"],
-        outputs=outputs_gather_scores_dim1,
-        axis=0)
-    node_list.append(node_gather_scores_dim1)
-
-    # mul class * M
-    outputs_mul_classnum_boxnum = [result_name + "@mul_classnum_boxnum"]
-    node_mul_classnum_boxnum = onnx.helper.make_node(
-        'Mul',
-        inputs=outputs_gather_1 + outputs_gather_scores_dim1,
-        outputs=outputs_mul_classnum_boxnum)
-    node_list.append(node_mul_classnum_boxnum)
-
-    # add class * M * index
-    outputs_add_class_M_index = [result_name + "@add_class_M_index"]
-    node_add_class_M_index = onnx.helper.make_node(
-        'Add',
-        inputs=outputs_mul_classnum_boxnum + outputs_gather_2,
-        outputs=outputs_add_class_M_index)
-    node_list.append(node_add_class_M_index)
-
-    # Squeeze the indices to 1 dim
-    outputs_squeeze_select_index = [result_name + "@squeeze_select_index"]
-    node_squeeze_select_index = onnx.helper.make_node(
-        'Squeeze',
-        inputs=outputs_add_class_M_index,
-        outputs=outputs_squeeze_select_index,
-        axes=[0, 2])
-    node_list.append(node_squeeze_select_index)
-
-    # gather the data from flatten scores
-    outputs_gather_select_scores = [result_name + "@gather_select_scores"]
-    node_gather_select_scores = onnx.helper.make_node('Gather',
-        inputs=outputs_reshape_scores_rank1 + \
-            outputs_squeeze_select_index,
-        outputs=outputs_gather_select_scores,
-        axis=0)
-    node_list.append(node_gather_select_scores)
-
-    # get nums to input TopK
-    outputs_shape_select_num = [result_name + "@shape_select_num"]
-    node_shape_select_num = onnx.helper.make_node(
-        'Shape',
-        inputs=outputs_gather_select_scores,
-        outputs=outputs_shape_select_num)
-    node_list.append(node_shape_select_num)
-
-    outputs_gather_select_num = [result_name + "@gather_select_num"]
-    node_gather_select_num = onnx.helper.make_node(
-        'Gather',
-        inputs=outputs_shape_select_num + [result_name + "@const_0"],
-        outputs=outputs_gather_select_num,
-        axis=0)
-    node_list.append(node_gather_select_num)
-
-    outputs_unsqueeze_select_num = [result_name + "@unsqueeze_select_num"]
-    node_unsqueeze_select_num = onnx.helper.make_node(
-        'Unsqueeze',
-        inputs=outputs_gather_select_num,
-        outputs=outputs_unsqueeze_select_num,
-        axes=[0])
-    node_list.append(node_unsqueeze_select_num)
-
-    outputs_concat_topK_select_num = [result_name + "@conat_topK_select_num"]
-    node_conat_topK_select_num = onnx.helper.make_node(
-        'Concat',
-        inputs=outputs_unsqueeze_select_num + name_keep_top_k_2D,
-        outputs=outputs_concat_topK_select_num,
-        axis=0)
-    node_list.append(node_conat_topK_select_num)
-
-    outputs_cast_concat_topK_select_num = [
-        result_name + "@concat_topK_select_num"
-    ]
-    node_outputs_cast_concat_topK_select_num = onnx.helper.make_node(
-        'Cast',
-        inputs=outputs_concat_topK_select_num,
-        outputs=outputs_cast_concat_topK_select_num,
-        to=6)
-    node_list.append(node_outputs_cast_concat_topK_select_num)
-    # get min(topK, num_select)
-    outputs_compare_topk_num_select = [
-        result_name + "@compare_topk_num_select"
-    ]
-    node_compare_topk_num_select = onnx.helper.make_node(
-        'ReduceMin',
-        inputs=outputs_cast_concat_topK_select_num,
-        outputs=outputs_compare_topk_num_select,
-        keepdims=0)
-    node_list.append(node_compare_topk_num_select)
-
-    # unsqueeze the indices to 1D tensor
-    outputs_unsqueeze_topk_select_indices = [
-        result_name + "@unsqueeze_topk_select_indices"
-    ]
-    node_unsqueeze_topk_select_indices = onnx.helper.make_node(
-        'Unsqueeze',
-        inputs=outputs_compare_topk_num_select,
-        outputs=outputs_unsqueeze_topk_select_indices,
-        axes=[0])
-    node_list.append(node_unsqueeze_topk_select_indices)
-
-    # cast the indices to INT64
-    outputs_cast_topk_indices = [result_name + "@cast_topk_indices"]
-    node_cast_topk_indices = onnx.helper.make_node(
-        'Cast',
-        inputs=outputs_unsqueeze_topk_select_indices,
-        outputs=outputs_cast_topk_indices,
-        to=7)
-    node_list.append(node_cast_topk_indices)
-
-    # select topk scores  indices
-    outputs_topk_select_topk_indices = [result_name + "@topk_select_topk_values",\
-        result_name + "@topk_select_topk_indices"]
-    node_topk_select_topk_indices = onnx.helper.make_node(
-        'TopK',
-        inputs=outputs_gather_select_scores + outputs_cast_topk_indices,
-        outputs=outputs_topk_select_topk_indices)
-    node_list.append(node_topk_select_topk_indices)
-
-    # gather topk label, scores, boxes
-    outputs_gather_topk_scores = [result_name + "@gather_topk_scores"]
-    node_gather_topk_scores = onnx.helper.make_node(
-        'Gather',
-        inputs=outputs_gather_select_scores +
-        [outputs_topk_select_topk_indices[1]],
-        outputs=outputs_gather_topk_scores,
-        axis=0)
-    node_list.append(node_gather_topk_scores)
-
-    outputs_gather_topk_class = [result_name + "@gather_topk_class"]
-    node_gather_topk_class = onnx.helper.make_node(
-        'Gather',
-        inputs=outputs_gather_1 + [outputs_topk_select_topk_indices[1]],
-        outputs=outputs_gather_topk_class,
-        axis=1)
-    node_list.append(node_gather_topk_class)
-
-    # gather the boxes need to gather the boxes id, then get boxes
-    outputs_gather_topk_boxes_id = [result_name + "@gather_topk_boxes_id"]
-    node_gather_topk_boxes_id = onnx.helper.make_node(
-        'Gather',
-        inputs=outputs_gather_2 + [outputs_topk_select_topk_indices[1]],
-        outputs=outputs_gather_topk_boxes_id,
-        axis=1)
-    node_list.append(node_gather_topk_boxes_id)
-
-    # squeeze the gather_topk_boxes_id to 1 dim
-    outputs_squeeze_topk_boxes_id = [result_name + "@squeeze_topk_boxes_id"]
-    node_squeeze_topk_boxes_id = onnx.helper.make_node(
-        'Squeeze',
-        inputs=outputs_gather_topk_boxes_id,
-        outputs=outputs_squeeze_topk_boxes_id,
-        axes=[0, 2])
-    node_list.append(node_squeeze_topk_boxes_id)
-
-    outputs_gather_select_boxes = [result_name + "@gather_select_boxes"]
-    node_gather_select_boxes = onnx.helper.make_node(
-        'Gather',
-        inputs=inputs['BBoxes'] + outputs_squeeze_topk_boxes_id,
-        outputs=outputs_gather_select_boxes,
-        axis=1)
-    node_list.append(node_gather_select_boxes)
-
-    # concat the final result
-    # before concat need to cast the class to float
-    outputs_cast_topk_class = [result_name + "@cast_topk_class"]
-    node_cast_topk_class = onnx.helper.make_node(
-        'Cast',
-        inputs=outputs_gather_topk_class,
-        outputs=outputs_cast_topk_class,
-        to=1)
-    node_list.append(node_cast_topk_class)
-
-    outputs_unsqueeze_topk_scores = [result_name + "@unsqueeze_topk_scores"]
-    node_unsqueeze_topk_scores = onnx.helper.make_node(
-        'Unsqueeze',
-        inputs=outputs_gather_topk_scores,
-        outputs=outputs_unsqueeze_topk_scores,
-        axes=[0, 2])
-    node_list.append(node_unsqueeze_topk_scores)
-
-    inputs_concat_final_results = outputs_cast_topk_class + outputs_unsqueeze_topk_scores +\
-        outputs_gather_select_boxes
-    outputs_sort_by_socre_results = [result_name + "@concat_topk_scores"]
-    node_sort_by_socre_results = onnx.helper.make_node(
-        'Concat',
-        inputs=inputs_concat_final_results,
-        outputs=outputs_sort_by_socre_results,
-        axis=2)
-    node_list.append(node_sort_by_socre_results)
-
-    # select topk classes indices
-    outputs_squeeze_cast_topk_class = [
-        result_name + "@squeeze_cast_topk_class"
-    ]
-    node_squeeze_cast_topk_class = onnx.helper.make_node(
-        'Squeeze',
-        inputs=outputs_cast_topk_class,
-        outputs=outputs_squeeze_cast_topk_class,
-        axes=[0, 2])
-    node_list.append(node_squeeze_cast_topk_class)
-    outputs_neg_squeeze_cast_topk_class = [
-        result_name + "@neg_squeeze_cast_topk_class"
-    ]
-    node_neg_squeeze_cast_topk_class = onnx.helper.make_node(
-        'Neg',
-        inputs=outputs_squeeze_cast_topk_class,
-        outputs=outputs_neg_squeeze_cast_topk_class)
-    node_list.append(node_neg_squeeze_cast_topk_class)
-    outputs_topk_select_classes_indices = [result_name + "@topk_select_topk_classes_scores",\
-        result_name + "@topk_select_topk_classes_indices"]
-    node_topk_select_topk_indices = onnx.helper.make_node(
-        'TopK',
-        inputs=outputs_neg_squeeze_cast_topk_class + outputs_cast_topk_indices,
-        outputs=outputs_topk_select_classes_indices)
-    node_list.append(node_topk_select_topk_indices)
-    outputs_concat_final_results = outputs['Out']
-    node_concat_final_results = onnx.helper.make_node(
-        'Gather',
-        inputs=outputs_sort_by_socre_results +
-        [outputs_topk_select_classes_indices[1]],
-        outputs=outputs_concat_final_results,
-        axis=1)
-    node_list.append(node_concat_final_results)
-    return node_list

+ 5 - 7
paddlex/cv/datasets/analysis.py

@@ -134,8 +134,7 @@ class Seg:
             self.label_value_list[id] = unique
             self.label_value_num_list[id] = counts
 
-    def _get_clipped_mean_std(self, start, end, clip_min_value,
-                              clip_max_value):
+    def _get_clipped_mean_std(self, start, end, clip_min_value, clip_max_value):
         for id in range(start, end):
             full_path_im, full_path_label = self.file_list[id]
             image, label = Compose.decode_image(full_path_im, full_path_label)
@@ -158,9 +157,9 @@ class Seg:
         self.im_std_list = [[] for i in range(len(self.file_list))]
         self.im_value_list = [[] for i in range(len(self.file_list))]
         self.im_value_num_list = [[] for i in range(len(self.file_list))]
-        self.im_height_list = np.zeros(len(self.file_list), dtype='int32')
-        self.im_width_list = np.zeros(len(self.file_list), dtype='int32')
-        self.im_channel_list = np.zeros(len(self.file_list), dtype='int32')
+        self.im_height_list = np.zeros(len(self.file_list), dtype='int64')
+        self.im_width_list = np.zeros(len(self.file_list), dtype='int64')
+        self.im_channel_list = np.zeros(len(self.file_list), dtype='int64')
         self.label_value_list = [[] for i in range(len(self.file_list))]
         self.label_value_num_list = [[] for i in range(len(self.file_list))]
 
@@ -171,8 +170,7 @@ class Seg:
             start = one_worker_file * i
             end = one_worker_file * (
                 i + 1) if i < num_workers - 1 else len(self.file_list)
-            t = threading.Thread(
-                target=self._get_image_info, args=(start, end))
+            t = threading.Thread(target=self._get_image_info, args=(start, end))
             threads.append(t)
         for t in threads:
             t.start()

+ 15 - 7
paddlex/cv/datasets/voc.py

@@ -150,15 +150,23 @@ class VOCDetection(Dataset):
                         cname = 'lou_di'
                     gt_class[i][0] = cname2cid[cname]
                     pattern = re.compile('<difficult>', re.IGNORECASE)
-                    diff_tag = pattern.findall(str(ET.tostringlist(obj)))[0][
-                        1:-1]
-                    try:
-                        _difficult = int(obj.find(diff_tag).text)
-                    except Exception:
+                    diff_tag = pattern.findall(str(ET.tostringlist(obj)))
+                    if len(diff_tag) == 0:
                         _difficult = 0
+                    else:
+                        diff_tag = diff_tag[0][1:-1]
+                        try:
+                            _difficult = int(obj.find(diff_tag).text)
+                        except Exception:
+                            _difficult = 0
                     pattern = re.compile('<bndbox>', re.IGNORECASE)
-                    box_tag = pattern.findall(str(ET.tostringlist(obj)))[0][1:
-                                                                            -1]
+                    box_tag = pattern.findall(str(ET.tostringlist(obj)))
+                    if len(box_tag) == 0:
+                        logging.warning(
+                            "There's no field '<bndbox>' in one of object, so this object will be ignored. xml file: {}".
+                            format(xml_file))
+                        continue
+                    box_tag = box_tag[0][1:-1]
                     box_element = obj.find(box_tag)
                     pattern = re.compile('<xmin>', re.IGNORECASE)
                     xmin_tag = pattern.findall(

+ 11 - 5
paddlex/cv/models/base.py

@@ -153,10 +153,11 @@ class BaseAPI:
         is_use_cache_file = True
         if cache_dir is None:
             is_use_cache_file = False
+        quant_prog = self.test_prog.clone(for_test=True)
         post_training_quantization = PaddleXPostTrainingQuantization(
             executor=self.exe,
             dataset=dataset,
-            program=self.test_prog,
+            program=quant_prog,
             inputs=self.test_inputs,
             outputs=self.test_outputs,
             batch_size=batch_size,
@@ -370,6 +371,7 @@ class BaseAPI:
             var.name for var in list(self.test_inputs.values())
         ]
         test_outputs = list(self.test_outputs.values())
+        save_prog = self.test_prog.clone(for_test=True)
         with fluid.scope_guard(self.scope):
             fluid.io.save_inference_model(
                 dirname=save_dir,
@@ -377,7 +379,7 @@ class BaseAPI:
                 params_filename='__params__',
                 feeded_var_names=test_input_names,
                 target_vars=test_outputs,
-                main_program=self.test_prog)
+                main_program=save_prog)
         model_info = self.get_model_info()
         model_info['status'] = 'Infer'
 
@@ -484,6 +486,9 @@ class BaseAPI:
         best_accuracy = -1.0
         best_model_epoch = -1
         start_epoch = self.completed_epochs
+        # task_id: 目前由PaddleX GUI赋值
+        # 用于在VisualDL日志中注明所属任务id
+        task_id = getattr(paddlex, "task_id", "")
         for i in range(start_epoch, num_epochs):
             records = list()
             step_start_time = time.time()
@@ -514,8 +519,8 @@ class BaseAPI:
                     if use_vdl:
                         for k, v in step_metrics.items():
                             log_writer.add_scalar(
-                                'Metrics/Training(Step): {}'.format(k), v,
-                                num_steps)
+                                '{}-Metrics/Training(Step): {}'.format(
+                                    task_id, k), v, num_steps)
 
                     # 估算剩余时间
                     avg_step_time = np.mean(time_stat)
@@ -581,7 +586,8 @@ class BaseAPI:
                                 if v.size > 1:
                                     continue
                             log_writer.add_scalar(
-                                "Metrics/Eval(Epoch): {}".format(k), v, i + 1)
+                                "{}-Metrics/Eval(Epoch): {}".format(
+                                    task_id, k), v, i + 1)
                 self.save_model(save_dir=current_save_dir)
                 if getattr(self, 'use_ema', False):
                     self.exe.run(self.ema.restore_program)

+ 7 - 0
paddlex/cv/models/load_model.py

@@ -131,6 +131,13 @@ def fix_input_shape(info, fixed_input_shape=None):
         padding = {'Padding': {}}
         if info['_Attributes']['model_type'] == 'classifier':
             pass
+        elif info['Model'].count('YOLO') > 0:
+            resize_op_index = None
+            for i in range(len(info['Transforms'])):
+                if list(info['Transforms'][i].keys())[0] == 'Resize':
+                    resize_op_index = i
+            if resize_op_index is not None:
+                info['Transforms'][resize_op_index]['Resize']['target_size'] = fixed_input_shape[0]
         else:
             resize['ResizeByShort']['short_size'] = min(fixed_input_shape)
             resize['ResizeByShort']['max_size'] = max(fixed_input_shape)

+ 5 - 4
paddlex/cv/models/ppyolo.py

@@ -166,8 +166,7 @@ class PPYOLO(BaseAPI):
             use_matrix_nms=self.use_matrix_nms,
             use_fine_grained_loss=self.use_fine_grained_loss,
             use_iou_loss=self.use_iou_loss,
-            batch_size=self.batch_size_per_gpu
-            if hasattr(self, 'batch_size_per_gpu') else 8,
+            batch_size=getattr(self, 'batch_size_per_gpu', 8),
             input_channel=self.input_channel)
         if mode == 'train' and self.use_iou_loss or self.use_iou_aware:
             model.max_height = self.max_height
@@ -307,8 +306,7 @@ class PPYOLO(BaseAPI):
         self.use_ema = use_ema
         self.ema_decay = ema_decay
 
-        self.batch_size_per_gpu = int(train_batch_size /
-                                      paddlex.env_info['num'])
+        self.batch_size_per_gpu = self._get_single_card_bs(train_batch_size)
         if self.use_fine_grained_loss:
             for transform in train_dataset.transforms.transforms:
                 if isinstance(transform, paddlex.det.transforms.Resize):
@@ -572,6 +570,9 @@ class PPYOLO(BaseAPI):
             self.__class__.__name__,
             self.thread_pool,
             input_channel=input_channel)
+        im, im_size = PPYOLO._preprocess(
+            img_file_list, transforms, self.model_type,
+            self.__class__.__name__, self.thread_pool)
 
         with fluid.scope_guard(self.scope):
             result = self.exe.run(self.test_prog,

+ 54 - 12
paddlex/cv/models/slim/prune.py

@@ -21,9 +21,6 @@ import os.path as osp
 from functools import reduce
 import paddle.fluid as fluid
 from multiprocessing import Process, Queue
-import paddleslim
-from paddleslim.prune import Pruner, load_sensitivities
-from paddleslim.core import GraphWrapper
 from .prune_config import get_prune_params
 import paddlex.utils.logging as logging
 from paddlex.utils import seconds_to_hms
@@ -36,6 +33,10 @@ def sensitivity(program,
                 sensitivities_file=None,
                 pruned_ratios=None,
                 scope=None):
+    import paddleslim
+    from paddleslim.prune import Pruner, load_sensitivities
+    from paddleslim.core import GraphWrapper
+
     if scope is None:
         scope = fluid.global_scope()
     else:
@@ -104,7 +105,12 @@ def sensitivity(program,
     return sensitivities
 
 
-def channel_prune(program, prune_names, prune_ratios, place, only_graph=False, scope=None):
+def channel_prune(program,
+                  prune_names,
+                  prune_ratios,
+                  place,
+                  only_graph=False,
+                  scope=None):
     """通道裁剪。
 
     Args:
@@ -119,6 +125,10 @@ def channel_prune(program, prune_names, prune_ratios, place, only_graph=False, s
     Returns:
         paddle.fluid.Program: 裁剪后的Program。
     """
+    import paddleslim
+    from paddleslim.prune import Pruner, load_sensitivities
+    from paddleslim.core import GraphWrapper
+
     prog_var_shape_dict = {}
     for var in program.list_vars():
         try:
@@ -163,6 +173,10 @@ def prune_program(model, prune_params_ratios=None):
         prune_params_ratios (dict): 由裁剪参数名和裁剪率组成的字典,当为None时
             使用默认裁剪参数名和裁剪率。默认为None。
     """
+    import paddleslim
+    from paddleslim.prune import Pruner, load_sensitivities
+    from paddleslim.core import GraphWrapper
+
     assert model.status == 'Normal', 'Only the models saved while training are supported!'
     place = model.places[0]
     train_prog = model.train_prog
@@ -175,10 +189,15 @@ def prune_program(model, prune_params_ratios=None):
     prune_ratios = [
         prune_params_ratios[prune_name] for prune_name in prune_names
     ]
-    model.train_prog = channel_prune(train_prog, prune_names, prune_ratios,
-                                     place, scope=model.scope)
+    model.train_prog = channel_prune(
+        train_prog, prune_names, prune_ratios, place, scope=model.scope)
     model.test_prog = channel_prune(
-        eval_prog, prune_names, prune_ratios, place, only_graph=True, scope=model.scope)
+        eval_prog,
+        prune_names,
+        prune_ratios,
+        place,
+        only_graph=True,
+        scope=model.scope)
 
 
 def update_program(program, model_dir, place, scope=None):
@@ -193,6 +212,10 @@ def update_program(program, model_dir, place, scope=None):
     Returns:
         paddle.fluid.Program: 更新后的Program。
     """
+    import paddleslim
+    from paddleslim.prune import Pruner, load_sensitivities
+    from paddleslim.core import GraphWrapper
+
     graph = GraphWrapper(program)
     with open(osp.join(model_dir, "prune.yml")) as f:
         shapes = yaml.load(f.read(), Loader=yaml.Loader)
@@ -203,11 +226,9 @@ def update_program(program, model_dir, place, scope=None):
     for block in program.blocks:
         for param in block.all_parameters():
             if param.name in shapes:
-                param_tensor = scope.find_var(
-                    param.name).get_tensor()
+                param_tensor = scope.find_var(param.name).get_tensor()
                 param_tensor.set(
-                    np.zeros(list(shapes[param.name])).astype('float32'),
-                    place)
+                    np.zeros(list(shapes[param.name])).astype('float32'), place)
     graph.update_groups_of_conv()
     graph.infer_shape()
     return program
@@ -243,6 +264,10 @@ def cal_params_sensitivities(model, save_file, eval_dataset, batch_size=8):
 
             其中``weight_0``是卷积Kernel名;``sensitivities['weight_0']``是一个字典,key是裁剪率,value是敏感度。
     """
+    import paddleslim
+    from paddleslim.prune import Pruner, load_sensitivities
+    from paddleslim.core import GraphWrapper
+
     assert model.status == 'Normal', 'Only the models saved while training are supported!'
     if os.path.exists(save_file):
         os.remove(save_file)
@@ -268,6 +293,11 @@ def cal_params_sensitivities(model, save_file, eval_dataset, batch_size=8):
     return sensitivitives
 
 
+def analysis(model, dataset, batch_size=8, save_file='./model.sensi.data'):
+    return cal_params_sensitivities(
+        model, eval_dataset=dataset, batch_size=batch_size, save_file=save_file)
+
+
 def get_params_ratios(sensitivities_file, eval_metric_loss=0.05):
     """根据设定的精度损失容忍度metric_loss_thresh和计算保存的模型参数敏感度信息文件sensetive_file,
         获取裁剪的参数配置。
@@ -288,6 +318,10 @@ def get_params_ratios(sensitivities_file, eval_metric_loss=0.05):
 
             其中key是卷积Kernel名;value是裁剪率。
     """
+    import paddleslim
+    from paddleslim.prune import Pruner, load_sensitivities
+    from paddleslim.core import GraphWrapper
+
     if not osp.exists(sensitivities_file):
         raise Exception('The sensitivities file is not exists!')
     sensitivitives = paddleslim.prune.load_sensitivities(sensitivities_file)
@@ -296,7 +330,11 @@ def get_params_ratios(sensitivities_file, eval_metric_loss=0.05):
     return params_ratios
 
 
-def cal_model_size(program, place, sensitivities_file, eval_metric_loss=0.05, scope=None):
+def cal_model_size(program,
+                   place,
+                   sensitivities_file,
+                   eval_metric_loss=0.05,
+                   scope=None):
     """在可容忍的精度损失下,计算裁剪后模型大小相对于当前模型大小的比例。
 
     Args:
@@ -309,6 +347,10 @@ def cal_model_size(program, place, sensitivities_file, eval_metric_loss=0.05, sc
     Returns:
         float: 裁剪后模型大小相对于当前模型大小的比例。
     """
+    import paddleslim
+    from paddleslim.prune import Pruner, load_sensitivities
+    from paddleslim.core import GraphWrapper
+
     prune_params_ratios = get_params_ratios(sensitivities_file,
                                             eval_metric_loss)
     prog_var_shape_dict = {}

+ 5 - 2
paddlex/cv/models/slim/visualize.py

@@ -16,7 +16,6 @@ import os.path as osp
 import tqdm
 import numpy as np
 from .prune import cal_model_size
-from paddleslim.prune import load_sensitivities
 
 
 def visualize(model, sensitivities_file, save_dir='./'):
@@ -42,7 +41,11 @@ def visualize(model, sensitivities_file, save_dir='./'):
     y = list()
     for loss_thresh in tqdm.tqdm(list(np.arange(0.05, 1, 0.05))):
         prune_ratio = 1 - cal_model_size(
-            program, place, sensitivities_file, eval_metric_loss=loss_thresh, scope=model.scope)
+            program,
+            place,
+            sensitivities_file,
+            eval_metric_loss=loss_thresh,
+            scope=model.scope)
         x.append(prune_ratio)
         y.append(loss_thresh)
     plt.plot(x, y, color='green', linewidth=0.5, marker='o', markersize=3)

+ 5 - 5
paddlex/cv/models/utils/pretrain_weights.py

@@ -1,6 +1,5 @@
 import paddlex
 import paddlex.utils.logging as logging
-import paddlehub as hub
 import os
 import os.path as osp
 
@@ -161,8 +160,7 @@ def get_pretrain_weights(flag, class_name, backbone, save_dir):
             logging.warning(warning_info.format(class_name, flag, 'IMAGENET'))
             flag = 'IMAGENET'
         elif class_name == 'FastSCNN':
-            logging.warning(
-                warning_info.format(class_name, flag, 'CITYSCAPES'))
+            logging.warning(warning_info.format(class_name, flag, 'CITYSCAPES'))
             flag = 'CITYSCAPES'
     elif flag == 'CITYSCAPES':
         model_name = '{}_{}'.format(class_name, backbone)
@@ -185,8 +183,7 @@ def get_pretrain_weights(flag, class_name, backbone, save_dir):
             logging.warning(warning_info.format(class_name, flag, 'COCO'))
             flag = 'COCO'
         elif class_name == 'FastSCNN':
-            logging.warning(
-                warning_info.format(class_name, flag, 'CITYSCAPES'))
+            logging.warning(warning_info.format(class_name, flag, 'CITYSCAPES'))
             flag = 'CITYSCAPES'
     elif flag == 'BAIDU10W':
         if class_name not in ['ResNet50_vd']:
@@ -217,6 +214,8 @@ def get_pretrain_weights(flag, class_name, backbone, save_dir):
             fname = osp.split(url)[-1].split('.')[0]
             paddlex.utils.download_and_decompress(url, path=new_save_dir)
             return osp.join(new_save_dir, fname)
+
+        import paddlehub as hub
         try:
             logging.info(
                 "Connecting PaddleHub server to get pretrain weights...")
@@ -287,6 +286,7 @@ def get_pretrain_weights(flag, class_name, backbone, save_dir):
             paddlex.utils.download_and_decompress(url, path=new_save_dir)
             return osp.join(new_save_dir, fname)
 
+        import paddlehub as hub
         try:
             logging.info(
                 "Connecting PaddleHub server to get pretrain weights...")

+ 2 - 2
paddlex/cv/models/utils/seg_eval.py

@@ -169,8 +169,8 @@ class ConfusionMatrix(object):
                 recall = 0
             else:
                 recall = self.confusion_matrix[c][c] / vij[c]
-            if vji[c] == 0 and vij[c] == 0:
-                f1score = 0
+            if recall + precision <= 1e-06:
+                f1_score = 0
             else:
                 f1score = 2 * precision * recall / (recall + precision)
             f1score_list.append(f1score)

+ 9 - 9
paddlex/cv/transforms/cls_transforms.py

@@ -68,13 +68,15 @@ class Compose(ClsTransform):
         if isinstance(im, np.ndarray):
             if len(im.shape) != 3:
                 raise Exception(
-                    "im should be 3-dimension, but now is {}-dimensions".
-                    format(len(im.shape)))
+                    "im should be 3-dimension, but now is {}-dimensions".format(
+                        len(im.shape)))
         else:
             try:
+                im_path = im
                 im = cv2.imread(im).astype('float32')
             except:
-                raise TypeError('Can\'t read The image file {}!'.format(im))
+                raise TypeError('Can\'t read The image file {}!'.format(
+                    im_path))
         im = im.astype('float32')
         im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
         for op in self.transforms:
@@ -140,8 +142,8 @@ class RandomCrop(ClsTransform):
             tuple: 当label为空时,返回的tuple为(im, ),对应图像np.ndarray数据;
                    当label不为空时,返回的tuple为(im, label),分别对应图像np.ndarray数据、图像类别id。
         """
-        im = random_crop(im, self.crop_size, self.lower_scale,
-                         self.lower_ratio, self.upper_ratio)
+        im = random_crop(im, self.crop_size, self.lower_scale, self.lower_ratio,
+                         self.upper_ratio)
         if label is None:
             return (im, )
         else:
@@ -271,14 +273,12 @@ class ResizeByShort(ClsTransform):
         im_short_size = min(im.shape[0], im.shape[1])
         im_long_size = max(im.shape[0], im.shape[1])
         scale = float(self.short_size) / im_short_size
-        if self.max_size > 0 and np.round(scale *
-                                          im_long_size) > self.max_size:
+        if self.max_size > 0 and np.round(scale * im_long_size) > self.max_size:
             scale = float(self.max_size) / float(im_long_size)
         resized_width = int(round(im.shape[1] * scale))
         resized_height = int(round(im.shape[0] * scale))
         im = cv2.resize(
-            im, (resized_width, resized_height),
-            interpolation=cv2.INTER_LINEAR)
+            im, (resized_width, resized_height), interpolation=cv2.INTER_LINEAR)
 
         if label is None:
             return (im, )

+ 5 - 3
paddlex/cv/transforms/seg_transforms.py

@@ -93,17 +93,19 @@ class Compose(SegTransform):
             raise Exception('Image format {} is not supported!'.format(ext))
 
     @staticmethod
-    def decode_image(im, label, input_channel=3):
+    def decode_image(im_path, label, input_channel=3):
         if isinstance(im, np.ndarray):
             if len(im.shape) != 3:
                 raise Exception(
                     "im should be 3-dimensions, but now is {}-dimensions".
                     format(len(im.shape)))
+            im = im_path
         else:
             try:
-                im = Compose.read_img(im, input_channel).astype('float32')
+                im = Compose.read_img(im_path, input_channel).astype('float32')
             except:
-                raise ValueError('Can\'t read The image file {}!'.format(im))
+                raise ValueError('Can\'t read The image file {}!'.format(
+                    im_path))
         im = im.astype('float32')
         if label is not None:
             if isinstance(label, np.ndarray):

+ 16 - 5
paddlex/deploy.py

@@ -35,7 +35,8 @@ class Predictor:
                  mkl_thread_num=4,
                  use_trt=False,
                  use_glog=False,
-                 memory_optimize=True):
+                 memory_optimize=True,
+                 max_trt_batch_size=1):
         """ 创建Paddle Predictor
 
             Args:
@@ -47,6 +48,7 @@ class Predictor:
                 use_trt: 是否使用TensorRT,默认False
                 use_glog: 是否启用glog日志, 默认False
                 memory_optimize: 是否启动内存优化,默认True
+                max_trt_batch_size: 在使用TensorRT时配置的最大batch size,默认1
         """
         if not osp.isdir(model_dir):
             raise Exception("[ERROR] Path {} not exist.".format(model_dir))
@@ -78,9 +80,9 @@ class Predictor:
             to_rgb = False
         self.transforms = build_transforms(self.model_type,
                                            self.info['Transforms'], to_rgb)
-        self.predictor = self.create_predictor(use_gpu, gpu_id, use_mkl,
-                                               mkl_thread_num, use_trt,
-                                               use_glog, memory_optimize)
+        self.predictor = self.create_predictor(
+            use_gpu, gpu_id, use_mkl, mkl_thread_num, use_trt, use_glog,
+            memory_optimize, max_trt_batch_size)
         # 线程池,在模型在预测时用于对输入数据以图片为单位进行并行处理
         # 主要用于batch_predict接口
         thread_num = mp.cpu_count() if mp.cpu_count() < 8 else 8
@@ -101,7 +103,8 @@ class Predictor:
                          mkl_thread_num=4,
                          use_trt=False,
                          use_glog=False,
-                         memory_optimize=True):
+                         memory_optimize=True,
+                         max_trt_batch_size=1):
         config = fluid.core.AnalysisConfig(
             os.path.join(self.model_dir, '__model__'),
             os.path.join(self.model_dir, '__params__'))
@@ -109,6 +112,14 @@ class Predictor:
         if use_gpu:
             # 设置GPU初始显存(单位M)和Device ID
             config.enable_use_gpu(100, gpu_id)
+            if use_trt:
+                config.enable_tensorrt_engine(
+                    workspace_size=1 << 10,
+                    max_batch_size=max_trt_batch_size,
+                    min_subgraph_size=3,
+                    precision_mode=fluid.core.AnalysisConfig.Precision.Float32,
+                    use_static=False,
+                    use_calib_mode=False)
         else:
             config.disable_gpu()
         if use_mkl and not use_gpu:

+ 2 - 1
paddlex/tools/x2coco.py

@@ -66,7 +66,8 @@ class X2COCO(object):
         """
         assert osp.exists(image_dir), "he image folder does not exist!"
         assert osp.exists(json_dir), "The json folder does not exist!"
-        assert osp.exists(dataset_save_dir), "The save folder does not exist!"
+        if not osp.exists(dataset_save_dir):
+            os.makedirs(dataset_save_dir)
         # Convert the image files.
         new_image_dir = osp.join(dataset_save_dir, "JPEGImages")
         if osp.exists(new_image_dir):

+ 2 - 1
paddlex/tools/x2imagenet.py

@@ -36,7 +36,8 @@ class X2ImageNet(object):
         """
         assert osp.exists(image_dir), "The image folder does not exist!"
         assert osp.exists(json_dir), "The json folder does not exist!"
-        assert osp.exists(dataset_save_dir), "The save folder does not exist!"
+        if not osp.exists(dataset_save_dir):
+            os.makedirs(dataset_save_dir)
         assert len(os.listdir(dataset_save_dir)) == 0, "The save folder must be empty!"
         for img_name in os.listdir(image_dir):
             img_name_part = osp.splitext(img_name)[0]

+ 5 - 4
paddlex/tools/x2seg.py

@@ -105,7 +105,8 @@ class X2Seg(object):
         """
         assert osp.exists(image_dir), "The image folder does not exist!"
         assert osp.exists(json_dir), "The json folder does not exist!"
-        assert osp.exists(dataset_save_dir), "The save folder does not exist!"
+        if not osp.exists(dataset_save_dir):
+            os.makedirs(dataset_save_dir)
         # Convert the image files.
         new_image_dir = osp.join(dataset_save_dir, "JPEGImages")
         if osp.exists(new_image_dir):
@@ -158,7 +159,7 @@ class JingLing2Seg(X2Seg):
             img_name_part = osp.splitext(img_name)[0]
             json_file = osp.join(json_dir, img_name_part + ".json")
             if not osp.exists(json_file):
-                os.remove(os.remove(osp.join(image_dir, img_name)))
+                os.remove(osp.join(image_dir, img_name))
                 continue
             with open(json_file, mode="r", \
                               encoding=get_encoding(json_file)) as j:
@@ -211,7 +212,7 @@ class LabelMe2Seg(X2Seg):
             img_name_part = osp.splitext(img_name)[0]
             json_file = osp.join(json_dir, img_name_part + ".json")
             if not osp.exists(json_file):
-                os.remove(os.remove(osp.join(image_dir, img_name)))
+                os.remove(osp.join(image_dir, img_name))
                 continue
             with open(json_file, mode="r", \
                               encoding=get_encoding(json_file)) as j:
@@ -297,7 +298,7 @@ class EasyData2Seg(X2Seg):
             img_name_part = osp.splitext(img_name)[0]
             json_file = osp.join(json_dir, img_name_part + ".json")
             if not osp.exists(json_file):
-                os.remove(os.remove(osp.join(image_dir, img_name)))
+                os.remove(osp.join(image_dir, img_name))
                 continue
             img_file = osp.join(image_dir, img_name)
             img = np.asarray(PIL.Image.open(img_file))

+ 4 - 3
paddlex/tools/x2voc.py

@@ -36,7 +36,8 @@ class X2VOC(object):
         """
         assert osp.exists(image_dir), "The image folder does not exist!"
         assert osp.exists(json_dir), "The json folder does not exist!"
-        assert osp.exists(dataset_save_dir), "The save folder does not exist!"
+        if not osp.exists(dataset_save_dir):
+            os.makedirs(dataset_save_dir)
         # Convert the image files.
         new_image_dir = osp.join(dataset_save_dir, "JPEGImages")
         if osp.exists(new_image_dir):
@@ -70,7 +71,7 @@ class LabelMe2VOC(X2VOC):
             json_file = osp.join(json_dir, img_name_part + ".json")
             i += 1
             if not osp.exists(json_file):
-                os.remove(os.remove(osp.join(image_dir, img_name)))
+                os.remove(osp.join(image_dir, img_name))
                 continue
             xml_doc = minidom.Document()
             root = xml_doc.createElement("annotation")
@@ -163,7 +164,7 @@ class EasyData2VOC(X2VOC):
             img_name_part = osp.splitext(img_name)[0]
             json_file = osp.join(json_dir, img_name_part + ".json")
             if not osp.exists(json_file):
-                os.remove(os.remove(osp.join(image_dir, img_name)))
+                os.remove(osp.join(image_dir, img_name))
                 continue
             xml_doc = minidom.Document()
             root = xml_doc.createElement("annotation")

+ 1 - 1
requirements.txt

@@ -4,7 +4,7 @@ sklearn
 cython
 pycocotools
 visualdl >= 2.0.0b
-paddleslim == 1.0.1
+paddleslim == 1.1.1
 shapely
 paddle2onnx
 paddlepaddle-gpu

+ 1 - 1
setup.py

@@ -19,7 +19,7 @@ long_description = "PaddlePaddle Entire Process Development Toolkit"
 
 setuptools.setup(
     name="paddlex",
-    version='1.2.1',
+    version='1.2.6',
     author="paddlex",
     author_email="paddlex@baidu.com",
     description=long_description,

+ 4 - 0
tutorials/compress/README.md

@@ -1,4 +1,8 @@
 # 使用教程——模型压缩
+
+本目录下教程已更新至[tutorials/slim/prune](../slim/prune),请参考该目录下教程使用。
+
+
 本目录下整理了使用PaddleX进行模型剪裁训练的代码,代码均会自动下载数据,并使用单张GPU卡进行训练。
 
 PaddleX提供了两种剪裁训练方式,  

+ 1 - 1
tutorials/compress/classification/cal_sensitivities_file.py

@@ -39,7 +39,7 @@ if __name__ == '__main__':
     parser = argparse.ArgumentParser(description=__doc__)
     parser.add_argument(
         "--model_dir",
-        default="./output/mobilenet/best_model",
+        default="./output/mobilenetv2/best_model",
         type=str,
         help="The model path.")
     parser.add_argument(

+ 51 - 0
tutorials/slim/prune/image_classification/README.md

@@ -0,0 +1,51 @@
+# 图像分类模型裁剪训练
+
+## 第一步 正常训练图像分类模型
+
+```
+python mobilenetv2_train.py
+```
+
+在此步骤中,训练的模型会保存在`output/mobilenetv2`目录下
+
+## 第二步 分析模型参数信息
+
+```
+python param_analysis.py
+```
+参数分析完后,会得到`mobilenetv2.sensi.data`文件,此文件保存了各参数的敏感度信息。  
+
+> 我们可以继续加载模型和敏感度文件,进行可视化,如下命令所示
+> ```
+> python slim_visualize.py
+> ```
+> 可视化结果出下图
+纵轴为`eval_metric_loss`(接下来第三步需要配置的参数),横轴为模型被裁剪的比例,从图中可以看到,  
+- 当`eval_metric_loss`设0.05时,模型被裁掉68.4%(剩余31.6%)  
+- 当`eval_metric_loss`设0.1时,模型被裁掉78.5%(剩余21.5%)
+
+![](./sensitivities.png)
+
+## 第三步 模型进行裁剪训练
+
+```
+python mobilenetv2_prune_train.py
+```
+此步骤的代码与第一步的代码基本一致,唯一的区别是在最后的train函数中,`mobilenetv2_prune_train.py`修改了里面的`pretrain_weights`、`save_dir`、`sensitivities_file`和`eval_metric_loss`四个参数
+
+- pretrain_weights: 在裁剪训练中,设置为之前训练好的模型
+- save_dir: 模型训练过程中,模型的保存位置
+- sensitivities_file: 在第二步中分析得到的参数敏感度信息文件
+- eval_metric_loss: 第二步中可视化的相关参数,通过此参数可相应的改变最终模型被裁剪的比例
+
+
+## 裁剪效果
+
+在本示例数据上,裁剪效果对比如下,其中预测采用**CPU,关闭MKLDNN**进行预测,预测时间不包含数据的预处理和结果的后处理。  
+可以看到在模型被裁剪掉64%后,模型精度还有上升,单张图片的预测用时减少了37%。
+
+
+| 模型 | 参数文件大小 | 预测速度 | 准确率 |
+| :--- | :----------  | :------- | :--- |
+| MobileNetV2 |    8.7M       |   0.057s  | 0.92 |
+| MobileNetV2(裁掉68%) | 2.8M | 0.036s | 0.99 |

+ 44 - 0
tutorials/slim/prune/image_classification/mobilenetv2_prune_train.py

@@ -0,0 +1,44 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+
+from paddlex.cls import transforms
+import paddlex as pdx
+
+veg_dataset = 'https://bj.bcebos.com/paddlex/datasets/vegetables_cls.tar.gz'
+pdx.utils.download_and_decompress(veg_dataset, path='./')
+
+train_transforms = transforms.Compose([
+    transforms.RandomCrop(crop_size=224), transforms.RandomHorizontalFlip(),
+    transforms.Normalize()
+])
+eval_transforms = transforms.Compose([
+    transforms.ResizeByShort(short_size=256),
+    transforms.CenterCrop(crop_size=224), transforms.Normalize()
+])
+
+train_dataset = pdx.datasets.ImageNet(
+    data_dir='vegetables_cls',
+    file_list='vegetables_cls/train_list.txt',
+    label_list='vegetables_cls/labels.txt',
+    transforms=train_transforms,
+    shuffle=True)
+eval_dataset = pdx.datasets.ImageNet(
+    data_dir='vegetables_cls',
+    file_list='vegetables_cls/val_list.txt',
+    label_list='vegetables_cls/labels.txt',
+    transforms=eval_transforms)
+
+model = pdx.cls.MobileNetV2(num_classes=len(train_dataset.labels))
+
+model.train(
+    num_epochs=10,
+    train_dataset=train_dataset,
+    train_batch_size=32,
+    eval_dataset=eval_dataset,
+    lr_decay_epochs=[4, 6, 8],
+    learning_rate=0.025,
+    pretrain_weights='output/mobilenetv2/best_model',
+    save_dir='output/mobilenetv2_prune',
+    sensitivities_file='./mobilenetv2.sensi.data',
+    eval_metric_loss=0.05,
+    use_vdl=True)

+ 41 - 0
tutorials/slim/prune/image_classification/mobilenetv2_train.py

@@ -0,0 +1,41 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+
+from paddlex.cls import transforms
+import paddlex as pdx
+
+veg_dataset = 'https://bj.bcebos.com/paddlex/datasets/vegetables_cls.tar.gz'
+pdx.utils.download_and_decompress(veg_dataset, path='./')
+
+train_transforms = transforms.Compose([
+    transforms.RandomCrop(crop_size=224), transforms.RandomHorizontalFlip(),
+    transforms.Normalize()
+])
+eval_transforms = transforms.Compose([
+    transforms.ResizeByShort(short_size=256),
+    transforms.CenterCrop(crop_size=224), transforms.Normalize()
+])
+
+train_dataset = pdx.datasets.ImageNet(
+    data_dir='vegetables_cls',
+    file_list='vegetables_cls/train_list.txt',
+    label_list='vegetables_cls/labels.txt',
+    transforms=train_transforms,
+    shuffle=True)
+eval_dataset = pdx.datasets.ImageNet(
+    data_dir='vegetables_cls',
+    file_list='vegetables_cls/val_list.txt',
+    label_list='vegetables_cls/labels.txt',
+    transforms=eval_transforms)
+
+model = pdx.cls.MobileNetV2(num_classes=len(train_dataset.labels))
+
+model.train(
+    num_epochs=10,
+    train_dataset=train_dataset,
+    train_batch_size=32,
+    eval_dataset=eval_dataset,
+    lr_decay_epochs=[4, 6, 8],
+    learning_rate=0.025,
+    save_dir='output/mobilenetv2',
+    use_vdl=True)

+ 17 - 0
tutorials/slim/prune/image_classification/params_analysis.py

@@ -0,0 +1,17 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+import paddlex as pdx
+
+model = pdx.load_model('output/mobilenetv2/best_model')
+
+eval_dataset = pdx.datasets.ImageNet(
+    data_dir='vegetables_cls',
+    file_list='vegetables_cls/val_list.txt',
+    label_list='vegetables_cls/labels.txt',
+    transforms=model.eval_transforms)
+
+pdx.slim.prune.analysis(
+    model,
+    dataset=eval_dataset,
+    batch_size=16,
+    save_file='mobilenetv2.sensi.data')

BIN
tutorials/slim/prune/image_classification/sensitivities.png


+ 3 - 0
tutorials/slim/prune/image_classification/slim_visualize.py

@@ -0,0 +1,3 @@
+import paddlex as pdx
+model = pdx.load_model('output/mobilenetv2/best_model')
+pdx.slim.visualize(model, 'mobilenetv2.sensi.data', save_dir='./')

+ 50 - 0
tutorials/slim/prune/object_detection/README.md

@@ -0,0 +1,50 @@
+# 目标检测模型裁剪训练
+
+## 第一步 正常训练目标检测模型
+
+```
+python yolov3_train.py
+```
+
+在此步骤中,训练的模型会保存在`output/yolov3_mobilenetv1`目录下
+
+## 第二步 分析模型参数信息
+
+```
+python param_analysis.py
+```
+参数分析完后,会得到`yolov3.sensi.data`文件,此文件保存了各参数的敏感度信息。  
+
+> 我们可以继续加载模型和敏感度文件,进行可视化,如下命令所示
+> ```
+> python slim_visualize.py
+> ```
+> 可视化结果出下图
+纵轴为`eval_metric_loss`(接下来第三步需要配置的参数),横轴为模型被裁剪的比例,从图中可以看到,  
+- 当`eval_metric_loss`设0.05时,模型被裁掉63.1%(剩余36.9%)  
+- 当`eval_metric_loss`设0.1时,模型被裁掉68.6%(剩余31.4%)
+
+![](./sensitivities.png)
+
+## 第三步 模型进行裁剪训练
+
+```
+python yolov3_prune_train.py
+```
+此步骤的代码与第一步的代码基本一致,唯一的区别是在最后的train函数中,`yolov3_prune_train.py`修改了里面的`pretrain_weights`、`save_dir`、`sensitivities_file`和`eval_metric_loss`四个参数
+
+- pretrain_weights: 在裁剪训练中,设置为之前训练好的模型
+- save_dir: 模型训练过程中,模型的保存位置
+- sensitivities_file: 在第二步中分析得到的参数敏感度信息文件
+- eval_metric_loss: 第二步中可视化的相关参数,通过此参数可相应的改变最终模型被裁剪的比例
+
+## 裁剪效果
+
+在本示例数据上,裁剪效果对比如下,其中预测采用**CPU,关闭MKLDNN**进行预测,预测时间不包含数据的预处理和结果的后处理。  
+可以看到在模型被裁剪掉63%后,模型精度还有上升,单张图片的预测用时减少了30%。
+
+
+| 模型 | 参数文件大小 | 预测速度 | MAP |
+| :--- | :----------  | :------- | :--- |
+| YOLOv3-MobileNetV1 |    93M       |   1.045s  | 0.635 |
+| YOLOv3-MobileNetV1(裁掉63%) | 35M | 0.735s | 0.735 |

+ 14 - 0
tutorials/slim/prune/object_detection/params_analysis.py

@@ -0,0 +1,14 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+import paddlex as pdx
+
+model = pdx.load_model('output/yolov3_mobilenetv1/best_model')
+
+eval_dataset = pdx.datasets.VOCDetection(
+    data_dir='insect_det',
+    file_list='insect_det/val_list.txt',
+    label_list='insect_det/labels.txt',
+    transforms=model.eval_transforms)
+
+pdx.slim.prune.analysis(
+    model, dataset=eval_dataset, batch_size=8, save_file='yolov3.sensi.data')

BIN
tutorials/slim/prune/object_detection/sensitivities.png


+ 3 - 0
tutorials/slim/prune/object_detection/slim_visualize.py

@@ -0,0 +1,3 @@
+import paddlex as pdx
+model = pdx.load_model('output/yolov3_mobilenetv1/best_model')
+pdx.slim.visualize(model, 'yolov3.sensi.data', save_dir='./')

+ 54 - 0
tutorials/slim/prune/object_detection/yolov3_prune_train.py

@@ -0,0 +1,54 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+
+from paddlex.det import transforms
+import paddlex as pdx
+
+insect_dataset = 'https://bj.bcebos.com/paddlex/datasets/insect_det.tar.gz'
+pdx.utils.download_and_decompress(insect_dataset, path='./')
+
+train_transforms = transforms.Compose([
+    transforms.MixupImage(mixup_epoch=250),
+    transforms.RandomDistort(),
+    transforms.RandomExpand(),
+    transforms.RandomCrop(),
+    transforms.Resize(
+        target_size=608, interp='RANDOM'),
+    transforms.RandomHorizontalFlip(),
+    transforms.Normalize(),
+])
+
+eval_transforms = transforms.Compose([
+    transforms.Resize(
+        target_size=608, interp='CUBIC'),
+    transforms.Normalize(),
+])
+
+train_dataset = pdx.datasets.VOCDetection(
+    data_dir='insect_det',
+    file_list='insect_det/train_list.txt',
+    label_list='insect_det/labels.txt',
+    transforms=train_transforms,
+    shuffle=True)
+eval_dataset = pdx.datasets.VOCDetection(
+    data_dir='insect_det',
+    file_list='insect_det/val_list.txt',
+    label_list='insect_det/labels.txt',
+    transforms=eval_transforms)
+
+num_classes = len(train_dataset.labels)
+
+model = pdx.det.YOLOv3(num_classes=num_classes, backbone='MobileNetV1')
+
+model.train(
+    num_epochs=270,
+    train_dataset=train_dataset,
+    train_batch_size=8,
+    eval_dataset=eval_dataset,
+    learning_rate=0.000125,
+    lr_decay_epochs=[210, 240],
+    pretrain_weights='output/yolov3_mobilenetv1/best_model',
+    save_dir='output/yolov3_mobilenetv1_prune',
+    sensitivities_file='./yolov3.sensi.data',
+    eval_metric_loss=0.05,
+    use_vdl=True)

+ 51 - 0
tutorials/slim/prune/object_detection/yolov3_train.py

@@ -0,0 +1,51 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+
+from paddlex.det import transforms
+import paddlex as pdx
+
+insect_dataset = 'https://bj.bcebos.com/paddlex/datasets/insect_det.tar.gz'
+pdx.utils.download_and_decompress(insect_dataset, path='./')
+
+train_transforms = transforms.Compose([
+    transforms.MixupImage(mixup_epoch=250),
+    transforms.RandomDistort(),
+    transforms.RandomExpand(),
+    transforms.RandomCrop(),
+    transforms.Resize(
+        target_size=608, interp='RANDOM'),
+    transforms.RandomHorizontalFlip(),
+    transforms.Normalize(),
+])
+
+eval_transforms = transforms.Compose([
+    transforms.Resize(
+        target_size=608, interp='CUBIC'),
+    transforms.Normalize(),
+])
+
+train_dataset = pdx.datasets.VOCDetection(
+    data_dir='insect_det',
+    file_list='insect_det/train_list.txt',
+    label_list='insect_det/labels.txt',
+    transforms=train_transforms,
+    shuffle=True)
+eval_dataset = pdx.datasets.VOCDetection(
+    data_dir='insect_det',
+    file_list='insect_det/val_list.txt',
+    label_list='insect_det/labels.txt',
+    transforms=eval_transforms)
+
+num_classes = len(train_dataset.labels)
+
+model = pdx.det.YOLOv3(num_classes=num_classes, backbone='MobileNetV1')
+
+model.train(
+    num_epochs=270,
+    train_dataset=train_dataset,
+    train_batch_size=8,
+    eval_dataset=eval_dataset,
+    learning_rate=0.000125,
+    lr_decay_epochs=[210, 240],
+    save_dir='output/yolov3_mobilenetv1',
+    use_vdl=True)

+ 51 - 0
tutorials/slim/prune/semantic_segmentation/README.md

@@ -0,0 +1,51 @@
+# 语义分割模型裁剪训练
+
+## 第一步 正常训练语义分割模型
+
+```
+python unet_train.py
+```
+
+在此步骤中,训练的模型会保存在`output/unet`目录下
+
+## 第二步 分析模型参数信息
+
+```
+python param_analysis.py
+```
+参数分析完后,会得到`unet.sensi.data`文件,此文件保存了各参数的敏感度信息。  
+
+> 我们可以继续加载模型和敏感度文件,进行可视化,如下命令所示
+> ```
+> python slim_visualize.py
+> ```
+> 可视化结果出下图
+纵轴为`eval_metric_loss`(接下来第三步需要配置的参数),横轴为模型被裁剪的比例,从图中可以看到,  
+- 当`eval_metric_loss`设0.05时,模型被裁掉64.1%(剩余35.9%)  
+- 当`eval_metric_loss`设0.1时,模型被裁掉70.9%(剩余29.1%)
+
+![](./sensitivities.png)
+
+## 第三步 模型进行裁剪训练
+
+```
+python unet_prune_train.py
+```
+此步骤的代码与第一步的代码基本一致,唯一的区别是在最后的train函数中,`unet_prune_train.py`修改了里面的`pretrain_weights`、`save_dir`、`sensitivities_file`和`eval_metric_loss`四个参数
+
+- pretrain_weights: 在裁剪训练中,设置为之前训练好的模型
+- save_dir: 模型训练过程中,模型的保存位置
+- sensitivities_file: 在第二步中分析得到的参数敏感度信息文件
+- eval_metric_loss: 第二步中可视化的相关参数,通过此参数可相应的改变最终模型被裁剪的比例
+
+## 裁剪效果
+
+在本示例数据上,裁剪效果对比如下,其中预测采用**CPU,关闭MKLDNN**进行预测,预测时间不包含数据的预处理和结果的后处理。  
+可以看到在模型被裁剪掉64%后,模型精度基本保持不变,单张图片的预测用时降低了近50%。
+
+> 此处仅做对比,使用了UNet模型,实际上在低性能设备上,更建议使用deeplab-mobilenet或fastscnn等轻量级分割模型。
+
+| 模型 | 参数文件大小 | 预测速度 | mIOU |
+| :--- | :----------  | :------- | :--- |
+| UNet |    52M       |   9.85s  | 0.915 |
+| UNet(裁掉64%) | 19M | 4.80s | 0.911 |

+ 14 - 0
tutorials/slim/prune/semantic_segmentation/params_analysis.py

@@ -0,0 +1,14 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+import paddlex as pdx
+
+model = pdx.load_model('output/unet/best_model')
+
+eval_dataset = pdx.datasets.SegDataset(
+    data_dir='optic_disc_seg',
+    file_list='optic_disc_seg/val_list.txt',
+    label_list='optic_disc_seg/labels.txt',
+    transforms=model.eval_transforms)
+
+pdx.slim.prune.analysis(
+    model, dataset=eval_dataset, batch_size=4, save_file='unet.sensi.data')

BIN
tutorials/slim/prune/semantic_segmentation/sensitivities.png


+ 3 - 0
tutorials/slim/prune/semantic_segmentation/slim_visualize.py

@@ -0,0 +1,3 @@
+import paddlex as pdx
+model = pdx.load_model('output/unet/best_model')
+pdx.slim.visualize(model, 'unet.sensi.data', save_dir='./')

+ 46 - 0
tutorials/slim/prune/semantic_segmentation/unet_prune_train.py

@@ -0,0 +1,46 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+
+import paddlex as pdx
+from paddlex.seg import transforms
+
+optic_dataset = 'https://bj.bcebos.com/paddlex/datasets/optic_disc_seg.tar.gz'
+pdx.utils.download_and_decompress(optic_dataset, path='./')
+
+train_transforms = transforms.Compose([
+    transforms.RandomHorizontalFlip(), transforms.ResizeRangeScaling(),
+    transforms.RandomPaddingCrop(crop_size=512), transforms.Normalize()
+])
+
+eval_transforms = transforms.Compose([
+    transforms.ResizeByLong(long_size=512), transforms.Padding(target_size=512),
+    transforms.Normalize()
+])
+
+train_dataset = pdx.datasets.SegDataset(
+    data_dir='optic_disc_seg',
+    file_list='optic_disc_seg/train_list.txt',
+    label_list='optic_disc_seg/labels.txt',
+    transforms=train_transforms,
+    shuffle=True)
+eval_dataset = pdx.datasets.SegDataset(
+    data_dir='optic_disc_seg',
+    file_list='optic_disc_seg/val_list.txt',
+    label_list='optic_disc_seg/labels.txt',
+    transforms=eval_transforms)
+
+num_classes = len(train_dataset.labels)
+
+model = pdx.seg.UNet(num_classes=num_classes)
+
+model.train(
+    num_epochs=20,
+    train_dataset=train_dataset,
+    train_batch_size=4,
+    eval_dataset=eval_dataset,
+    learning_rate=0.01,
+    pretrain_weights='output/unet/best_model',
+    save_dir='output/unet_prune',
+    sensitivities_file='./unet.sensi.data',
+    eval_metric_loss=0.05,
+    use_vdl=True)

+ 43 - 0
tutorials/slim/prune/semantic_segmentation/unet_train.py

@@ -0,0 +1,43 @@
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+
+import paddlex as pdx
+from paddlex.seg import transforms
+
+optic_dataset = 'https://bj.bcebos.com/paddlex/datasets/optic_disc_seg.tar.gz'
+pdx.utils.download_and_decompress(optic_dataset, path='./')
+
+train_transforms = transforms.Compose([
+    transforms.RandomHorizontalFlip(), transforms.ResizeRangeScaling(),
+    transforms.RandomPaddingCrop(crop_size=512), transforms.Normalize()
+])
+
+eval_transforms = transforms.Compose([
+    transforms.ResizeByLong(long_size=512), transforms.Padding(target_size=512),
+    transforms.Normalize()
+])
+
+train_dataset = pdx.datasets.SegDataset(
+    data_dir='optic_disc_seg',
+    file_list='optic_disc_seg/train_list.txt',
+    label_list='optic_disc_seg/labels.txt',
+    transforms=train_transforms,
+    shuffle=True)
+eval_dataset = pdx.datasets.SegDataset(
+    data_dir='optic_disc_seg',
+    file_list='optic_disc_seg/val_list.txt',
+    label_list='optic_disc_seg/labels.txt',
+    transforms=eval_transforms)
+
+num_classes = len(train_dataset.labels)
+
+model = pdx.seg.UNet(num_classes=num_classes)
+
+model.train(
+    num_epochs=20,
+    train_dataset=train_dataset,
+    train_batch_size=4,
+    eval_dataset=eval_dataset,
+    learning_rate=0.01,
+    save_dir='output/unet',
+    use_vdl=True)

+ 16 - 0
tutorials/slim/quant/image_classification/README.md

@@ -0,0 +1,16 @@
+# 图像分类模型量化
+
+在此目录下提供了MobileNetV2模型的量化示例,执行如下命令即可
+
+## 第一步 量化模型
+```
+python mobilenetv2_quant.py
+```
+执行代码会自动下载模型和数据集
+
+## 第二步 导出为PaddleLite模型
+
+```
+python paddlelite_export.py
+```
+执行此脚本前,需安装paddlelite,在python环境中`pip install paddlelite`即可

+ 31 - 0
tutorials/slim/quant/image_classification/mobilenetv2_quant.py

@@ -0,0 +1,31 @@
+import paddlex as pdx
+import os
+os.environ['CUDA_VISIBLE_DEVICES'] = '0'
+
+# 下载训练好的模型
+url = 'https://bj.bcebos.com/paddlex/models/mobilenetv2_vegetables.tar.gz'
+pdx.utils.download_and_decompress(url, path='.')
+
+# 下载相应的训练数据集
+url = 'https://bj.bcebos.com/paddlex/datasets/vegetables_cls.tar.gz'
+pdx.utils.download_and_decompress(url, path='.')
+
+# 加载模型
+model = pdx.load_model('mobilenetv2_vegetables')
+
+# 将正常模型导出为部署格式,用于对比
+import time
+for i in range(60):
+    print('save', i)
+    time.sleep(1)
+    model.export_inference_model('server_mobilenet')
+
+# 加载数据集用于量化
+dataset = pdx.datasets.ImageNet(
+                data_dir='vegetables_cls',
+                file_list='vegetables_cls/train_list.txt',
+                label_list='vegetables_cls/labels.txt',
+                transforms=model.test_transforms)
+
+# 开始量化
+pdx.slim.export_quant_model(model, dataset, batch_size=4, batch_num=10, save_dir='./quant_mobilenet', cache_dir='./tmp')

+ 18 - 0
tutorials/slim/quant/image_classification/paddlelite_export.py

@@ -0,0 +1,18 @@
+# 需先安装paddlelite
+import paddlelite.lite as lite
+
+model_filename = 'server_mobilenet/__model__'
+params_filename = 'server_mobilenet/__params__'
+export_filename = 'mobilenetv2'
+
+opt = lite.Opt()
+# 将正常模型导出为Lite模型
+opt.run_optimize("", model_filename, params_filename, 'naive_buffer', 'arm', export_filename) 
+
+
+quant_model_filename = 'quant_mobilenet/__model__'
+quant_params_filename = 'quant_mobilenet/__params__'
+quant_export_filename = 'mobilenetv2_quant'
+
+# 将量化模型导出为Lite模型
+opt.run_optimize("", quant_model_filename, quant_params_filename, 'naive_buffer', 'arm', quant_export_filename)