Эх сурвалжийг харах

Merge remote-tracking branch 'paddle/develop' into cpp_trt

Channingss 5 жил өмнө
parent
commit
4c3da33aca
36 өөрчлөгдсөн 1038 нэмэгдсэн , 197 устгасан
  1. 3 0
      deploy/README.md
  2. 63 4
      docs/apis/datasets.md
  3. BIN
      docs/apis/images/insect_bbox_pr_curve(iou-0.5).png
  4. BIN
      docs/apis/images/xiaoduxiong_bbox_pr_curve(iou-0.5).png
  5. BIN
      docs/apis/images/xiaoduxiong_segm_pr_curve(iou-0.5).png
  6. 1 1
      docs/apis/load_model.md
  7. 19 7
      docs/apis/models.md
  8. 3 1
      docs/apis/transforms/cls_transforms.md
  9. 9 3
      docs/apis/transforms/det_transforms.md
  10. 3 1
      docs/apis/transforms/seg_transforms.md
  11. 18 32
      docs/apis/visualize.md
  12. 173 10
      docs/datasets.md
  13. BIN
      docs/images/visualized_maskrcnn.jpeg
  14. 25 22
      docs/model_zoo.md
  15. 11 0
      paddlex/__init__.py
  16. 3 0
      paddlex/cv/datasets/__init__.py
  17. 4 3
      paddlex/cv/datasets/coco.py
  18. 86 0
      paddlex/cv/datasets/easydata_cls.py
  19. 190 0
      paddlex/cv/datasets/easydata_det.py
  20. 116 0
      paddlex/cv/datasets/easydata_seg.py
  21. 1 1
      paddlex/cv/datasets/imagenet.py
  22. 1 1
      paddlex/cv/datasets/seg_dataset.py
  23. 5 4
      paddlex/cv/datasets/voc.py
  24. 10 1
      paddlex/cv/models/base.py
  25. 9 2
      paddlex/cv/models/classifier.py
  26. 9 2
      paddlex/cv/models/deeplabv3p.py
  27. 17 9
      paddlex/cv/models/faster_rcnn.py
  28. 14 6
      paddlex/cv/models/mask_rcnn.py
  29. 4 3
      paddlex/cv/models/slim/visualize.py
  30. 13 6
      paddlex/cv/models/unet.py
  31. 145 30
      paddlex/cv/models/utils/visualize.py
  32. 13 5
      paddlex/cv/models/yolo_v3.py
  33. 30 40
      paddlex/cv/transforms/det_transforms.py
  34. 2 2
      paddlex/cv/transforms/seg_transforms.py
  35. 36 0
      paddlex/utils/utils.py
  36. 2 1
      setup.py

+ 3 - 0
deploy/README.md

@@ -0,0 +1,3 @@
+# 模型部署
+
+本目录为PaddleX模型部署代码。

+ 63 - 4
docs/apis/datasets.md

@@ -16,7 +16,7 @@ paddlex.datasets.ImageNet(data_dir, file_list, label_list, transforms=None, num_
 > * **transforms** (paddlex.cls.transforms): 数据集中每个样本的预处理/增强算子,详见[paddlex.cls.transforms](./transforms/cls_transforms.md)。  
 > * **num_workers** (int|str):数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的一半。  
 > * **buffer_size** (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。  
-> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'thread'(Windows和Mac下会强制使用thread,该参数无效)。  
+> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。  
 > * **shuffle** (bool): 是否需要对数据集中样本打乱顺序。默认为False。  
 
 ## VOCDetection类
@@ -37,7 +37,7 @@ paddlex.datasets.VOCDetection(data_dir, file_list, label_list, transforms=None,
 > * **transforms** (paddlex.det.transforms): 数据集中每个样本的预处理/增强算子,详见[paddlex.det.transforms](./transforms/det_transforms.md)。  
 > * **num_workers** (int|str):数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的一半。
 > * **buffer_size** (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。  
-> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'thread'(Windows和Mac下会强制使用thread,该参数无效)。  
+> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。  
 > * **shuffle** (bool): 是否需要对数据集中样本打乱顺序。默认为False。  
 
 ## CocoDetection类
@@ -57,7 +57,7 @@ paddlex.datasets.CocoDetection(data_dir, ann_file, transforms=None, num_workers=
 > * **transforms** (paddlex.det.transforms): 数据集中每个样本的预处理/增强算子,详见[paddlex.det.transforms](./transforms/det_transforms.md)。  
 > * **num_workers** (int|str):数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的一半。  
 > * **buffer_size** (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。  
-> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'thread'(Windows和Mac下会强制使用thread,该参数无效)。  
+> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。  
 > * **shuffle** (bool): 是否需要对数据集中样本打乱顺序。默认为False。  
 
 ## SegDataset类
@@ -78,5 +78,64 @@ paddlex.datasets.SegDataset(data_dir, file_list, label_list, transforms=None, nu
 > * **transforms** (paddlex.seg.transforms): 数据集中每个样本的预处理/增强算子,详见[paddlex.seg.transforms](./transforms/seg_transforms.md)。  
 > * **num_workers** (int|str):数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的一半。
 > * **buffer_size** (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。  
-> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'thread'(Windows和Mac下会强制使用thread,该参数无效)。  
+> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。  
+> * **shuffle** (bool): 是否需要对数据集中样本打乱顺序。默认为False。 
+
+## EasyDataCls类
+```
+paddlex.datasets.SegDataset(data_dir, file_list, label_list, transforms=None, num_workers='auto', buffer_size=100, parallel_method='thread', shuffle=False)
+```
+读取EasyData图像分类数据集,并对样本进行相应的处理。EasyData图像分类任务数据集格式的介绍可查看文档:[数据集格式说明](../datasets.md)  
+
+
+### 参数
+
+> * **data_dir** (str): 数据集所在的目录路径。  
+> * **file_list** (str): 描述数据集图片文件和对应标注文件的文件路径(文本内每行路径为相对`data_dir`的相对路径)。
+> * **label_list** (str): 描述数据集包含的类别信息文件路径。  
+> * **transforms** (paddlex.seg.transforms): 数据集中每个样本的预处理/增强算子,详见[paddlex.cls.transforms](./transforms/cls_transforms.md)。  
+> * **num_workers** (int|str):数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的一半。
+> * **buffer_size** (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。  
+> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。  
+> * **shuffle** (bool): 是否需要对数据集中样本打乱顺序。默认为False。
+
+## EasyDataDet类
+
+```
+paddlex.datasets.EasyDataDet(data_dir, file_list, label_list, transforms=None, num_workers=‘auto’, buffer_size=100, parallel_method='thread', shuffle=False)
+```
+
+读取EasyData目标检测格式数据集,并对样本进行相应的处理,该格式的数据集同样可以应用到实例分割模型的训练中。EasyData目标检测或实例分割任务数据集格式的介绍可查看文档:[数据集格式说明](../datasets.md)  
+
+
+### 参数
+
+> * **data_dir** (str): 数据集所在的目录路径。  
+> * **file_list** (str): 描述数据集图片文件和对应标注文件的文件路径(文本内每行路径为相对`data_dir`的相对路径)。
+> * **label_list** (str): 描述数据集包含的类别信息文件路径。  
+> * **transforms** (paddlex.det.transforms): 数据集中每个样本的预处理/增强算子,详见[paddlex.det.transforms](./transforms/det_transforms.md)。  
+> * **num_workers** (int|str):数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的一半。
+> * **buffer_size** (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。  
+> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。  
 > * **shuffle** (bool): 是否需要对数据集中样本打乱顺序。默认为False。  
+
+
+## EasyDataSeg类
+
+```
+paddlex.datasets.EasyDataSeg(data_dir, file_list, label_list, transforms=None, num_workers='auto', buffer_size=100, parallel_method='thread', shuffle=False)
+```
+
+读取EasyData语分分割任务数据集,并对样本进行相应的处理。EasyData语义分割任务数据集格式的介绍可查看文档:[数据集格式说明](../datasets.md)  
+
+
+### 参数
+
+> * **data_dir** (str): 数据集所在的目录路径。  
+> * **file_list** (str): 描述数据集图片文件和对应标注文件的文件路径(文本内每行路径为相对`data_dir`的相对路径)。
+> * **label_list** (str): 描述数据集包含的类别信息文件路径。  
+> * **transforms** (paddlex.seg.transforms): 数据集中每个样本的预处理/增强算子,详见[paddlex.seg.transforms](./transforms/seg_transforms.md)。  
+> * **num_workers** (int|str):数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的一半。
+> * **buffer_size** (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。  
+> * **parallel_method** (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。  
+> * **shuffle** (bool): 是否需要对数据集中样本打乱顺序。默认为False。 

BIN
docs/apis/images/insect_bbox_pr_curve(iou-0.5).png


BIN
docs/apis/images/xiaoduxiong_bbox_pr_curve(iou-0.5).png


BIN
docs/apis/images/xiaoduxiong_segm_pr_curve(iou-0.5).png


+ 1 - 1
docs/apis/load_model.md

@@ -34,7 +34,7 @@ pred_result = model.predict('./xiaoduxiong_ins_det/JPEGImages/WechatIMG114.jpeg'
 
 # 在验证集上进行评估
 eval_reader = pdx.cv.datasets.CocoDetection(data_dir=data_dir,
-                                            ann_file=ann_file
+                                            ann_file=ann_file,
                                             transforms=model.eval_transforms)
 eval_result = model.evaluate(eval_reader, batch_size=1)
 ```

+ 19 - 7
docs/apis/models.md

@@ -17,7 +17,7 @@ paddlex.cls.ResNet50(num_classes=1000)
 #### 分类器训练函数接口
 
 > ```python
-> train(self, num_epochs, train_dataset, train_batch_size=64, eval_dataset=None, save_interval_epochs=1, log_interval_steps=2, save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=0.025, lr_decay_epochs=[30, 60, 90], lr_decay_gamma=0.1, use_vdl=False, sensitivities_file=None, eval_metric_loss=0.05)
+> train(self, num_epochs, train_dataset, train_batch_size=64, eval_dataset=None, save_interval_epochs=1, log_interval_steps=2, save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=0.025, lr_decay_epochs=[30, 60, 90], lr_decay_gamma=0.1, use_vdl=False, sensitivities_file=None, eval_metric_loss=0.05, early_stop=False, early_stop_patience=5)
 > ```
 >
 > **参数:**
@@ -37,6 +37,8 @@ paddlex.cls.ResNet50(num_classes=1000)
 > > - **use_vdl** (bool): 是否使用VisualDL进行可视化。默认值为False。
 > > - **sensitivities_file** (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串'DEFAULT',则自动下载在ImageNet图片数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
 > > - **eval_metric_loss** (float): 可容忍的精度损失。默认为0.05。
+> > - **early_stop** (float): 是否使用提前终止训练策略。默认值为False。
+> > - **early_stop_patience** (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内连续下降或持平,则终止训练。默认值为5。
 
 #### 分类器评估函数接口
 
@@ -75,7 +77,7 @@ paddlex.cls.ResNet50(num_classes=1000)
 
 ### 其它分类器类
 
-除`ResNet50`外,`paddlex.cls`下还提供了`ResNet18`、`ResNet34`、`ResNet101`、`ResNet50_vd`、`ResNet101_vd`、`DarkNet53`、`MobileNetV1`、`MobileNetV2`、`MobileNetV3_small`、`MobileNetV3_large`、`Xception41`、`Xception65`、`Xception71`、`ShuffleNetV2`,  使用方式(包括函数接口和参数)均与`ResNet50`一致,各模型效果可参考[模型库](../model_zoo.md)中列表。
+除`ResNet50`外,`paddlex.cls`下还提供了`ResNet18`、`ResNet34`、`ResNet101`、`ResNet50_vd`、`ResNet101_vd`、`ResNet50_vd_ssld`、`ResNet101_vd_ssld`、`DarkNet53`、`MobileNetV1`、`MobileNetV2`、`MobileNetV3_small`、`MobileNetV3_large`、`MobileNetV3_small_ssld`、`MobileNetV3_large_ssld`、`Xception41`、`Xception65`、`Xception71`、`ShuffleNetV2`,  使用方式(包括函数接口和参数)均与`ResNet50`一致,各模型效果可参考[模型库](../model_zoo.md)中列表。
 
 
 
@@ -109,7 +111,7 @@ paddlex.det.YOLOv3(num_classes=80, backbone='MobileNetV1', anchors=None, anchor_
 #### YOLOv3训练函数接口
 
 > ```python
-> train(self, num_epochs, train_dataset, train_batch_size=8, eval_dataset=None, save_interval_epochs=20, log_interval_steps=2, save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=1.0/8000, warmup_steps=1000, warmup_start_lr=0.0, lr_decay_epochs=[213, 240], lr_decay_gamma=0.1, metric=None, use_vdl=False, sensitivities_file=None, eval_metric_loss=0.05)
+> train(self, num_epochs, train_dataset, train_batch_size=8, eval_dataset=None, save_interval_epochs=20, log_interval_steps=2, save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=1.0/8000, warmup_steps=1000, warmup_start_lr=0.0, lr_decay_epochs=[213, 240], lr_decay_gamma=0.1, metric=None, use_vdl=False, sensitivities_file=None, eval_metric_loss=0.05, early_stop=False, early_stop_patience=5)
 > ```
 >
 > **参数:**
@@ -132,6 +134,8 @@ paddlex.det.YOLOv3(num_classes=80, backbone='MobileNetV1', anchors=None, anchor_
 > > - **use_vdl** (bool): 是否使用VisualDL进行可视化。默认值为False。
 > > - **sensitivities_file** (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串'DEFAULT',则自动下载在PascalVOC数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
 > > - **eval_metric_loss** (float): 可容忍的精度损失。默认为0.05。
+> > - **early_stop** (float): 是否使用提前终止训练策略。默认值为False。
+> > - **early_stop_patience** (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内连续下降或持平,则终止训练。默认值为5。
 
 #### YOLOv3评估函数接口
 
@@ -186,7 +190,7 @@ paddlex.det.FasterRCNN(num_classes=81, backbone='ResNet50', with_fpn=True, aspec
 #### FasterRCNN训练函数接口
 
 > ```python
-> train(self, num_epochs, train_dataset, train_batch_size=2, eval_dataset=None, save_interval_epochs=1, log_interval_steps=2,save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=0.0025, warmup_steps=500, warmup_start_lr=1.0/1200, lr_decay_epochs=[8, 11], lr_decay_gamma=0.1, metric=None, use_vdl=False)
+> train(self, num_epochs, train_dataset, train_batch_size=2, eval_dataset=None, save_interval_epochs=1, log_interval_steps=2,save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=0.0025, warmup_steps=500, warmup_start_lr=1.0/1200, lr_decay_epochs=[8, 11], lr_decay_gamma=0.1, metric=None, use_vdl=False, early_stop=False, early_stop_patience=5)
 >
 > ```
 >
@@ -208,6 +212,8 @@ paddlex.det.FasterRCNN(num_classes=81, backbone='ResNet50', with_fpn=True, aspec
 > > - **lr_decay_gamma** (float): 默认优化器的学习率衰减率。默认为0.1。
 > > - **metric** (bool): 训练过程中评估的方式,取值范围为['COCO', 'VOC']。默认值为None。
 > > - **use_vdl** (bool): 是否使用VisualDL进行可视化。默认值为False。
+> > - **early_stop** (float): 是否使用提前终止训练策略。默认值为False。
+> > - **early_stop_patience** (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内连续下降或持平,则终止训练。默认值为5。
 
 #### FasterRCNN评估函数接口
 
@@ -264,7 +270,7 @@ paddlex.det.MaskRCNN(num_classes=81, backbone='ResNet50', with_fpn=True, aspect_
 #### MaskRCNN训练函数接口
 
 > ```python
-> train(self, num_epochs, train_dataset, train_batch_size=1, eval_dataset=None, save_interval_epochs=1, log_interval_steps=20, save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=1.0/800, warmup_steps=500, warmup_start_lr=1.0 / 2400, lr_decay_epochs=[8, 11], lr_decay_gamma=0.1, metric=None, use_vdl=False)
+> train(self, num_epochs, train_dataset, train_batch_size=1, eval_dataset=None, save_interval_epochs=1, log_interval_steps=20, save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=1.0/800, warmup_steps=500, warmup_start_lr=1.0 / 2400, lr_decay_epochs=[8, 11], lr_decay_gamma=0.1, metric=None, use_vdl=False, early_stop=False, early_stop_patience=5)
 >
 > ```
 >
@@ -286,6 +292,8 @@ paddlex.det.MaskRCNN(num_classes=81, backbone='ResNet50', with_fpn=True, aspect_
 > > - **lr_decay_gamma** (float): 默认优化器的学习率衰减率。默认为0.1。
 > > - **metric** (bool): 训练过程中评估的方式,取值范围为['COCO', 'VOC']。默认值为None。
 > > - **use_vdl** (bool): 是否使用VisualDL进行可视化。默认值为False。
+> > - **early_stop** (float): 是否使用提前终止训练策略。默认值为False。
+> > - **early_stop_patience** (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内连续下降或持平,则终止训练。默认值为5。
 
 #### MaskRCNN评估函数接口
 
@@ -350,7 +358,7 @@ paddlex.seg.DeepLabv3p(num_classes=2, backbone='MobileNetV2_x1.0', output_stride
 #### DeepLabv3训练函数接口
 
 > ```python
-> train(self, num_epochs, train_dataset, train_batch_size=2, eval_dataset=None, eval_batch_size=1, save_interval_epochs=1, log_interval_steps=2, save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=0.01, lr_decay_power=0.9, use_vdl=False, sensitivities_file=None, eval_metric_loss=0.05):
+> train(self, num_epochs, train_dataset, train_batch_size=2, eval_dataset=None, eval_batch_size=1, save_interval_epochs=1, log_interval_steps=2, save_dir='output', pretrain_weights='IMAGENET', optimizer=None, learning_rate=0.01, lr_decay_power=0.9, use_vdl=False, sensitivities_file=None, eval_metric_loss=0.05, early_stop=False, early_stop_patience=5):
 >
 > ```
 >
@@ -370,6 +378,8 @@ paddlex.seg.DeepLabv3p(num_classes=2, backbone='MobileNetV2_x1.0', output_stride
 > > - **use_vdl** (bool): 是否使用VisualDL进行可视化。默认False。
 > > - **sensitivities_file** (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串'DEFAULT',则自动下载在ImageNet图片数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
 > > - **eval_metric_loss** (float): 可容忍的精度损失。默认为0.05。
+> > - **early_stop** (float): 是否使用提前终止训练策略。默认值为False。
+> > - **early_stop_patience** (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内连续下降或持平,则终止训练。默认值为5。
 
 #### DeepLabv3评估函数接口
 
@@ -427,7 +437,7 @@ paddlex.seg.UNet(num_classes=2, upsample_mode='bilinear', use_bce_loss=False, us
 #### Unet训练函数接口
 
 > ```python
-> train(self, num_epochs, train_dataset, train_batch_size=2, eval_dataset=None, eval_batch_size=1, save_interval_epochs=1, log_interval_steps=2, save_dir='output', pretrain_weights='COCO', optimizer=None, learning_rate=0.01, lr_decay_power=0.9, use_vdl=False, sensitivities_file=None, eval_metric_loss=0.05):
+> train(self, num_epochs, train_dataset, train_batch_size=2, eval_dataset=None, eval_batch_size=1, save_interval_epochs=1, log_interval_steps=2, save_dir='output', pretrain_weights='COCO', optimizer=None, learning_rate=0.01, lr_decay_power=0.9, use_vdl=False, sensitivities_file=None, eval_metric_loss=0.05, early_stop=False, early_stop_patience=5):
 > ```
 >
 > **参数:**
@@ -446,6 +456,8 @@ paddlex.seg.UNet(num_classes=2, upsample_mode='bilinear', use_bce_loss=False, us
 > > - **use_vdl** (bool): 是否使用VisualDL进行可视化。默认False。
 > > - **sensitivities_file** (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串'DEFAULT',则自动下载在ImageNet图片数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
 > > - **eval_metric_loss** (float): 可容忍的精度损失。默认为0.05。
+> > - **early_stop** (float): 是否使用提前终止训练策略。默认值为False。
+> > - **early_stop_patience** (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内连续下降或持平,则终止训练。默认值为5。
 
 #### Unet评估函数接口
 

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

@@ -109,7 +109,9 @@ paddlex.cls.transforms.RandomDistort(brightness_range=0.9, brightness_prob=0.5,
 
 以一定的概率对图像进行随机像素内容变换,模型训练时的数据增强操作。  
 1. 对变换的操作顺序进行随机化操作。
-2. 按照1中的顺序以一定的概率对图像在范围[-range, range]内进行随机像素内容变换。
+2. 按照1中的顺序以一定的概率对图像在范围[-range, range]内进行随机像素内容变换。  
+
+【注意】该数据增强必须在数据增强Normalize之前使用。
 
 ### 参数
 * **brightness_range** (float): 明亮度因子的范围。默认为0.9。

+ 9 - 3
docs/apis/transforms/det_transforms.md

@@ -85,7 +85,9 @@ paddlex.det.transforms.RandomDistort(brightness_range=0.5, brightness_prob=0.5,
 
 以一定的概率对图像进行随机像素内容变换,模型训练时的数据增强操作。  
 1. 对变换的操作顺序进行随机化操作。
-2. 按照1中的顺序以一定的概率对图像在范围[-range, range]内进行随机像素内容变换。
+2. 按照1中的顺序以一定的概率对图像在范围[-range, range]内进行随机像素内容变换。  
+
+【注意】该数据增强必须在数据增强Normalize之前使用。
 
 ### 参数
 * **brightness_range** (float): 明亮度因子的范围。默认为0.5。
@@ -135,7 +137,9 @@ paddlex.det.transforms.RandomExpand(ratio=4., prob=0.5, fill_value=[123.675, 116
 ### 参数
 * **ratio** (float): 图像扩张的最大比例。默认为4.0。
 * **prob** (float): 随机扩张的概率。默认为0.5。
-* **fill_value** (list): 扩张图像的初始填充值(0-255)。默认为[123.675, 116.28, 103.53]。
+* **fill_value** (list): 扩张图像的初始填充值(0-255)。默认为[123.675, 116.28, 103.53]。  
+
+【注意】该数据增强必须在数据增强Resize、ResizeByShort之前使用。
 
 ## RandomCrop类
 ```python
@@ -152,7 +156,9 @@ paddlex.det.transforms.RandomCrop(aspect_ratio=[.5, 2.], thresholds=[.0, .1, .3,
     (4) 如果cover_all_box为True且存在真实标注框的IoU小于thresh,则继续第3步。
     (5) 筛选出位于候选裁剪区域内的真实标注框,若有效框的个数为0,则继续第3步,否则进行第4步。
 4. 换算有效真值标注框相对候选裁剪区域的位置坐标。
-5. 换算有效分割区域相对候选裁剪区域的位置坐标。
+5. 换算有效分割区域相对候选裁剪区域的位置坐标。  
+
+【注意】该数据增强必须在数据增强Resize、ResizeByShort之前使用。
 
 ### 参数
 * **aspect_ratio** (list): 裁剪后短边缩放比例的取值范围,以[min, max]形式表示。默认值为[.5, 2.]。

+ 3 - 1
docs/apis/transforms/seg_transforms.md

@@ -153,7 +153,9 @@ paddlex.seg.transforms.RandomDistort(brightness_range=0.5, brightness_prob=0.5,
 以一定的概率对图像进行随机像素内容变换,模型训练时的数据增强操作。
 
 1.对变换的操作顺序进行随机化操作。
-2.按照1中的顺序以一定的概率对图像在范围[-range, range]内进行随机像素内容变换。
+2.按照1中的顺序以一定的概率对图像在范围[-range, range]内进行随机像素内容变换。  
+
+【注意】该数据增强必须在数据增强Normalize之前使用。
 
 ### 参数
 * **brightness_range** (float): 明亮度因子的范围。默认为0.5。

+ 18 - 32
docs/apis/visualize.md

@@ -40,8 +40,16 @@ paddlex.det.draw_pr_curve(eval_details_file=None, gt=None, pred_bbox=None, pred_
 **注意:**`eval_details_file`的优先级更高,只要`eval_details_file`不为None,就会从`eval_details_file`提取真值信息和预测结果做分析。当`eval_details_file`为None时,则用`gt`、`pred_mask`、`pred_mask`做分析。
 
 ### 使用示例
-> 示例一:
-点击下载如下示例中的[模型](https://bj.bcebos.com/paddlex/models/xiaoduxiong_epoch_12.tar.gz)和[数据集](https://bj.bcebos.com/paddlex/datasets/xiaoduxiong_ins_det.tar.gz)
+点击下载如下示例中的[模型](https://bj.bcebos.com/paddlex/models/insect_epoch_270.zip)和[数据集](https://bj.bcebos.com/paddlex/datasets/insect_det.tar.gz)
+
+> 方式一:分析训练过程中保存的模型文件夹中的评估结果文件`eval_details.json`,例如[模型](https://bj.bcebos.com/paddlex/models/insect_epoch_270.zip)中的`eval_details.json`。
+```
+import paddlex as pdx
+eval_details_file = 'insect_epoch_270/eval_details.json'
+pdx.det.draw_pr_curve(eval_details_file, save_dir='./insect')
+```
+> 方式二:分析模型评估函数返回的评估结果。
+
 ```
 import os
 # 选择使用0号卡
@@ -50,40 +58,18 @@ os.environ['CUDA_VISIBLE_DEVICES'] = '0'
 from paddlex.det import transforms
 import paddlex as pdx
 
-eval_transforms = transforms.Compose([
-    transforms.Normalize(),
-    transforms.ResizeByShort(short_size=800, max_size=1333),
-    transforms.Padding(coarsest_stride=32)
-])
-
-eval_dataset = pdx.datasets.CocoDetection(
-    data_dir='xiaoduxiong_ins_det/JPEGImages',
-    ann_file='xiaoduxiong_ins_det/val.json',
-    transforms=eval_transforms)
-
-model = pdx.load_model('xiaoduxiong_epoch_12')
-metrics, evaluate_details = model.evaluate(eval_dataset, batch_size=1, return_details=True)
+model = pdx.load_model('insect_epoch_270')
+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)
+metrics, evaluate_details = model.evaluate(eval_dataset, batch_size=8, return_details=True)
 gt = evaluate_details['gt']
 bbox = evaluate_details['bbox']
-mask = evaluate_details['mask']
-
-# 分别可视化bbox和mask的准召曲线
-pdx.det.draw_pr_curve(gt=gt, pred_bbox=bbox, pred_mask=mask, save_dir='./xiaoduxiong')
+pdx.det.draw_pr_curve(gt=gt, pred_bbox=bbox, save_dir='./insect')
 ```
-预测框的各个类别的准确率和召回率的对应关系、召回率和置信度阈值的对应关系可视化如下:
-![](./images/xiaoduxiong_bbox_pr_curve(iou-0.5).png)
-
-预测mask的各个类别的准确率和召回率的对应关系、召回率和置信度阈值的对应关系可视化如下:
-![](./images/xiaoduxiong_segm_pr_curve(iou-0.5).png)
-
-> 示例二:
-使用[yolov3_darknet53.py示例代码](https://github.com/PaddlePaddle/PaddleX/blob/develop/tutorials/train/detection/yolov3_darknet53.py)训练完成后,加载模型评估结果文件进行分析:
 
-```
-import paddlex as pdx
-eval_details_file = 'output/yolov3_darknet53/best_model/eval_details.json'
-pdx.det.draw_pr_curve(eval_details_file, save_dir='./insect')
-```
 预测框的各个类别的准确率和召回率的对应关系、召回率和置信度阈值的对应关系可视化如下:
 ![](./images/insect_bbox_pr_curve(iou-0.5).png)
 

+ 173 - 10
docs/datasets.md

@@ -41,8 +41,8 @@ labelA
 labelB
 ...
 ```
-[点击这里](https://bj.bcebos.com/paddlex/datasets/vegetables_cls.tar.gz),下载蔬菜分类分类数据集
-在PaddleX中,使用`paddlex.cv.datasets.ImageNet`([API说明](./apis/datasets.html#imagenet))加载分类数据集
+[点击这里](https://bj.bcebos.com/paddlex/datasets/vegetables_cls.tar.gz),下载蔬菜分类分类数据集
+在PaddleX中,使用`paddlex.cv.datasets.ImageNet`([API说明](./apis/datasets.html#imagenet))加载分类数据集
 
 ## 目标检测VOC
 目标检测VOC数据集包含图像文件夹、标注信息文件夹、标签文件及图像列表文件。
@@ -81,8 +81,8 @@ labelA
 labelB
 ...
 ```
-[点击这里](https://bj.bcebos.com/paddlex/datasets/insect_det.tar.gz),下载昆虫检测数据集
-在PaddleX中,使用`paddlex.cv.datasets.VOCDetection`([API说明](./apis/datasets.html#vocdetection))加载目标检测VOC数据集
+[点击这里](https://bj.bcebos.com/paddlex/datasets/insect_det.tar.gz),下载昆虫检测数据集
+在PaddleX中,使用`paddlex.cv.datasets.VOCDetection`([API说明](./apis/datasets.html#vocdetection))加载目标检测VOC数据集
 
 ## 目标检测和实例分割COCO
 目标检测和实例分割COCO数据集包含图像文件夹及图像标注信息文件。
@@ -135,7 +135,7 @@ labelB
   ]
 }
 ```
-每个字段的含义如下所示:
+其中,每个字段的含义如下所示:
 
 | 域名 | 字段名 | 含义 | 数据类型 | 备注 |
 |:-----|:--------|:------------|------|:-----|
@@ -155,8 +155,8 @@ labelB
 | categories | supercategory | 类别父类的标签名 | str |  |
 
 
-[点击这里](https://bj.bcebos.com/paddlex/datasets/garbage_ins_det.tar.gz),下载垃圾实例分割数据集
-在PaddleX中,使用`paddlex.cv.datasets.COCODetection`([API说明](./apis/datasets.html#cocodetection))加载COCO格式数据集
+[点击这里](https://bj.bcebos.com/paddlex/datasets/garbage_ins_det.tar.gz),下载垃圾实例分割数据集
+在PaddleX中,使用`paddlex.cv.datasets.COCODetection`([API说明](./apis/datasets.html#cocodetection))加载COCO格式数据集
 
 ## 语义分割数据
 语义分割数据集包含原图、标注图及相应的文件列表文件。
@@ -191,13 +191,176 @@ images/xxx2.png annotations/xxx2.png
 
 `labels.txt`: 每一行为一个单独的类别,相应的行号即为类别对应的id(行号从0开始),如下所示:
 ```
+background
 labelA
 labelB
 ...
 ```
 
-标注图像为单通道图像,像素值即为对应的类别,像素标注类别需要从0开始递增,
+标注图像为单通道图像,像素值即为对应的类别,像素标注类别需要从0开始递增(一般第一个类别为`background`)
 例如0,1,2,3表示有4种类别,标注类别最多为256类。其中可以指定特定的像素值用于表示该值的像素不参与训练和评估(默认为255)。
 
-[点击这里](https://bj.bcebos.com/paddlex/datasets/optic_disc_seg.tar.gz),下载视盘语义分割数据集
-在PaddleX中,使用`paddlex.cv.datasets.SegReader`([API说明](./apis/datasets.html#segreader))加载语义分割数据集
+[点击这里](https://bj.bcebos.com/paddlex/datasets/optic_disc_seg.tar.gz),下载视盘语义分割数据集。  
+在PaddleX中,使用`paddlex.cv.datasets.SegReader`([API说明](./apis/datasets.html#segreader))加载语义分割数据集。
+
+
+## 图像分类EasyDataCls
+
+图像分类EasyDataCls数据集包含存放图像和json文件的文件夹、标签文件及图像列表文件。
+参考数据文件结构如下:
+```
+./dataset/  # 数据集根目录
+|--easydata  # 存放图像和json文件的文件夹
+|  |--0001.jpg
+|  |--0001.json
+|  |--0002.jpg
+|  |--0002.json
+|  └--...
+|
+|--train_list.txt  # 训练文件列表文件
+|
+|--val_list.txt  # 验证文件列表文件
+|
+└--labels.txt  # 标签列表文件
+
+```
+其中,图像文件名应与json文件名一一对应。   
+
+每个json文件存储于`labels`相关的信息。如下所示:
+```
+{"labels": [{"name": "labelA"}]}
+```
+其中,`name`字段代表对应图像的类别。  
+
+`train_list.txt`和`val_list.txt`文本以空格为分割符分为两列,第一列为图像文件相对于dataset的相对路径,第二列为json文件相对于dataset的相对路径。如下所示:
+```
+easydata/0001.jpg easydata/0001.json
+easydata/0002.jpg easydata/0002.json
+...
+```
+
+`labels.txt`: 每一行为一个单独的类别,相应的行号即为类别对应的id(行号从0开始),如下所示:
+```
+labelA
+labelB
+...
+```
+[点击这里](https://ai.baidu.com/easydata/),可以标注图像分类EasyDataCls数据集。  
+在PaddleX中,使用`paddlex.cv.datasets.EasyDataCls`([API说明](./apis/datasets.html#easydatacls))加载分类数据集。
+
+
+## 目标检测和实例分割EasyDataDet
+
+目标检测和实例分割EasyDataDet数据集包含存放图像和json文件的文件夹、标签文件及图像列表文件。
+参考数据文件结构如下:
+```
+./dataset/  # 数据集根目录ß
+|--easydata  # 存放图像和json文件的文件夹
+|  |--0001.jpg
+|  |--0001.json
+|  |--0002.jpg
+|  |--0002.json
+|  └--...
+|
+|--train_list.txt  # 训练文件列表文件
+|
+|--val_list.txt  # 验证文件列表文件
+|
+└--labels.txt  # 标签列表文件
+
+```
+其中,图像文件名应与json文件名一一对应。   
+
+每个json文件存储于`labels`相关的信息。如下所示:
+```
+"labels": [{"y1": 18, "x2": 883, "x1": 371, "y2": 404, "name": "labelA", 
+            "mask": "kVfc0`0Zg0<F7J7I5L5K4L4L4L3N3L3N3L3N2N3M2N2N2N2N2N2N1O2N2O1N2N1O2O1N101N1O2O1N101N10001N101N10001N10001O0O10001O000O100000001O0000000000000000000000O1000001O00000O101O000O101O0O101O0O2O0O101O0O2O0O2N2O0O2O0O2N2O1N1O2N2N2O1N2N2N2N2N2N2M3N3M2M4M2M4M3L4L4L4K6K5J7H9E\\iY1"}, 
+           {"y1": 314, "x2": 666, "x1": 227, "y2": 676, "name": "labelB",
+            "mask": "mdQ8g0Tg0:G8I6K5J5L4L4L4L4M2M4M2M4M2N2N2N3L3N2N2N2N2O1N1O2N2N2O1N1O2N2O0O2O1N1O2O0O2O0O2O001N100O2O000O2O000O2O00000O2O000000001N100000000000000000000000000000000001O0O100000001O0O10001N10001O0O101N10001N101N101N101N101N2O0O2N2O0O2N2N2O0O2N2N2N2N2N2N2N2N2N3L3N2N3L3N3L4M2M4L4L5J5L5J7H8H;BUcd<"}, 
+           ...]}
+```
+其中,list中的每个元素代表一个标注信息,标注信息中字段的含义如下所示: 
+
+| 字段名 | 含义 | 数据类型 | 备注 |
+|:--------|:------------|------|:-----|
+| x1 | 标注框左下角横坐标 | int | |
+| y1 | 标注框左下角纵坐标 | int | |
+| x2 | 标注框右上角横坐标 | int | |
+| y2 | 标注框右上角纵坐标 | int | |
+| name | 标注框中物体类标 | str | |
+| mask | 分割区域布尔型numpy编码后的字符串 | str | 该字段可以不存在,当不存在时只能进行目标检测 |
+
+`train_list.txt`和`val_list.txt`文本以空格为分割符分为两列,第一列为图像文件相对于dataset的相对路径,第二列为json文件相对于dataset的相对路径。如下所示:
+```
+easydata/0001.jpg easydata/0001.json
+easydata/0002.jpg easydata/0002.json
+...
+```
+
+`labels.txt`: 每一行为一个单独的类别,相应的行号即为类别对应的id(行号从0开始),如下所示:
+```
+labelA
+labelB
+...
+```
+
+[点击这里](https://ai.baidu.com/easydata/),可以标注图像分类EasyDataDet数据集。  
+在PaddleX中,使用`paddlex.cv.datasets.EasyDataDet`([API说明](./apis/datasets.html#easydatadet))加载分类数据集。
+
+## 语义分割EasyDataSeg
+
+语义分割EasyDataSeg数据集包含存放图像和json文件的文件夹、标签文件及图像列表文件。
+参考数据文件结构如下:
+```
+./dataset/  # 数据集根目录ß
+|--easydata  # 存放图像和json文件的文件夹
+|  |--0001.jpg
+|  |--0001.json
+|  |--0002.jpg
+|  |--0002.json
+|  └--...
+|
+|--train_list.txt  # 训练文件列表文件
+|
+|--val_list.txt  # 验证文件列表文件
+|
+└--labels.txt  # 标签列表文件
+
+```
+其中,图像文件名应与json文件名一一对应。   
+
+每个json文件存储于`labels`相关的信息。如下所示:
+```
+"labels": [{"y1": 18, "x2": 883, "x1": 371, "y2": 404, "name": "labelA", 
+            "mask": "kVfc0`0Zg0<F7J7I5L5K4L4L4L3N3L3N3L3N2N3M2N2N2N2N2N2N1O2N2O1N2N1O2O1N101N1O2O1N101N10001N101N10001N10001O0O10001O000O100000001O0000000000000000000000O1000001O00000O101O000O101O0O101O0O2O0O101O0O2O0O2N2O0O2O0O2N2O1N1O2N2N2O1N2N2N2N2N2N2M3N3M2M4M2M4M3L4L4L4K6K5J7H9E\\iY1"}, 
+           {"y1": 314, "x2": 666, "x1": 227, "y2": 676, "name": "labelB",
+            "mask": "mdQ8g0Tg0:G8I6K5J5L4L4L4L4M2M4M2M4M2N2N2N3L3N2N2N2N2O1N1O2N2N2O1N1O2N2O0O2O1N1O2O0O2O0O2O001N100O2O000O2O000O2O00000O2O000000001N100000000000000000000000000000000001O0O100000001O0O10001N10001O0O101N10001N101N101N101N101N2O0O2N2O0O2N2N2O0O2N2N2N2N2N2N2N2N2N3L3N2N3L3N3L4M2M4L4L5J5L5J7H8H;BUcd<"}, 
+           ...]}
+```
+其中,list中的每个元素代表一个标注信息,标注信息中字段的含义如下所示: 
+
+| 字段名 | 含义 | 数据类型 | 备注 |
+|:--------|:------------|------|:-----|
+| x1 | 标注框左下角横坐标 | int | |
+| y1 | 标注框左下角纵坐标 | int | |
+| x2 | 标注框右上角横坐标 | int | |
+| y2 | 标注框右上角纵坐标 | int | |
+| name | 标注框中物体类标 | str | |
+| mask | 分割区域布尔型numpy编码后的字符串 | str | 该字段必须存在 |
+
+`train_list.txt`和`val_list.txt`文本以空格为分割符分为两列,第一列为图像文件相对于dataset的相对路径,第二列为json文件相对于dataset的相对路径。如下所示:
+```
+easydata/0001.jpg easydata/0001.json
+easydata/0002.jpg easydata/0002.json
+...
+```
+
+`labels.txt`: 每一行为一个单独的类别,相应的行号即为类别对应的id(行号从0开始),如下所示:
+```
+labelA
+labelB
+...
+```
+
+[点击这里](https://ai.baidu.com/easydata/),可以标注图像分类EasyDataSeg数据集。  
+在PaddleX中,使用`paddlex.cv.datasets.EasyDataSeg`([API说明](./apis/datasets.html#easydataseg))加载分类数据集。

BIN
docs/images/visualized_maskrcnn.jpeg


+ 25 - 22
docs/model_zoo.md

@@ -4,35 +4,38 @@
 表中相关模型也可下载好作为相应模型的预训练模型,通过`pretrain_weights`指定目录加载使用。
 
 ## 图像分类模型
-> 表中模型相关指标均为在ImageNet数据集上使用PaddlePaddle Python预测接口测试得到(测试GPU型号为Nvidia Tesla P4),预测速度为每张图片预测用时(不包括预处理和后处理),表中符号`-`表示相关指标暂未测试。
+> 表中模型相关指标均为在ImageNet数据集上使用PaddlePaddle Python预测接口测试得到(测试GPU型号为Nvidia Tesla P40),预测速度为每张图片预测用时(不包括预处理和后处理),表中符号`-`表示相关指标暂未测试。
 
 
-| 模型  | 模型大小 | 预测速度(毫秒) | Top1准确率 | Top5准确率 |
+| 模型  | 模型大小 | 预测速度(毫秒) | Top1准确率(%) | Top5准确率(%) |
 | :----|  :------- | :----------- | :--------- | :--------- |
-| ResNet18| 46.9MB   | 3.456        | 70.98%     | 89.92%     |
-| ResNet34| 87.5MB   | 5.668        | 74.57%     | 92.14%     |
-| ResNet50| 102.7MB  | 8.787        | 76.50%     | 93.00%     |
-| ResNet101 |179.1MB  | 15.447      | 77.56%     | 93.64%    |
-| ResNet50_vd |102.8MB  | 9.058        | 79.12%     | 94.44%     |
-| ResNet101_vd| 179.2MB  | 15.685       | 80.17%     | 94.97%     |
-| DarkNet53|166.9MB  | 11.969       | 78.04%     | 94.05%     |
-| MobileNetV1 | 16.4MB   | 2.609        | 70.99%     | 89.68%     |
-| MobileNetV2 | 14.4MB   | 4.546        | 72.15%     | 90.65%     |
-| MobileNetV3_large|  22.8MB   | -        | 75.3%     | 75.3%     |
-| MobileNetV3_small |  12.5MB   | 6.809        | 67.46%     | 87.12%     |
-| Xception41 |92.4MB   | 13.757       | 79.30%     | 94.53%     |
-| Xception65 | 144.6MB  | 19.216       | 81.00%     | 95.49%     |
-| Xception71| 151.9MB  | 23.291       | 81.11%     | 95.45%     |
-| DenseNet121 | 32.8MB   | 12.437       | 75.66%     | 92.58%     |
-| DenseNet161|116.3MB  | 27.717       | 78.57%     | 94.14%     |
-| DenseNet201|  84.6MB   | 26.583       | 77.63%     | 93.66%     |
-| ShuffleNetV2 | 10.2MB   | 6.101        | 68.8%     | 88.5%     |
+| ResNet18| 46.9MB   | 1.499        | 71.0     | 89.9     |
+| ResNet34| 87.5MB   | 2.272        | 74.6    | 92.1    |
+| ResNet50| 102.7MB  | 2.939        | 76.5     | 93.0     |
+| ResNet101 |179.1MB  | 5.314      | 77.6     | 93.6  |
+| ResNet50_vd |102.8MB  | 3.165        | 79.1     | 94.4     |
+| ResNet101_vd| 179.2MB  | 5.252       | 80.2   | 95.0     |
+| ResNet50_vd_ssld |102.8MB  | 3.165        | 82.4     | 96.1     |
+| ResNet101_vd_ssld| 179.2MB  | 5.252       | 83.7   | 96.7     |
+| DarkNet53|166.9MB  | 3.139       | 78.0     | 94.1     |
+| MobileNetV1 | 16.0MB   | 32.523        | 71.0     | 89.7    |
+| MobileNetV2 | 14.0MB   | 23.318        | 72.2     | 90.7    |
+| MobileNetV3_large|  21.0MB   | 19.308        | 75.3    | 93.2   |
+| MobileNetV3_small |  12.0MB   | 6.546        | 68.2    | 88.1     |
+| MobileNetV3_large_ssld|  21.0MB   | 19.308        | 79.0     | 94.5     |
+| MobileNetV3_small_ssld |  12.0MB   | 6.546        | 71.3     | 90.1     |
+| Xception41 |92.4MB   | 4.408       | 79.6    | 94.4     |
+| Xception65 | 144.6MB  | 6.464       | 80.3     | 94.5     |
+| DenseNet121 | 32.8MB   | 4.371       | 75.7     | 92.6     |
+| DenseNet161|116.3MB  | 8.863       | 78.6     | 94.1     |
+| DenseNet201|  84.6MB   | 8.173       | 77.6     | 93.7     |
+| ShuffleNetV2 | 9.0MB   | 10.941        | 68.8     | 88.5     |
 
 ## 目标检测模型
 
 > 表中模型相关指标均为在MSCOCO数据集上使用PaddlePaddle Python预测接口测试得到(测试GPU型号为Nvidia Tesla V100测试得到,表中符号`-`表示相关指标暂未测试。
 
-| 模型    | 模型大小    | 预测时间(毫秒) | BoxAP |
+| 模型    | 模型大小    | 预测时间(毫秒) | BoxAP(%) |
 |:-------|:-----------|:-------------|:----------|
 |FasterRCNN-ResNet50|135.6MB| 78.450 | 35.2 |
 |FasterRCNN-ResNet50_vd| 135.7MB | 79.523 | 36.4 |
@@ -50,7 +53,7 @@
 
 > 表中模型相关指标均为在MSCOCO数据集上测试得到。
 
-| 模型 |模型大小 | 预测时间(毫秒) | BoxAP | SegAP |
+| 模型 |模型大小 | 预测时间(毫秒) | BoxAP | SegAP(%) |
 |:---------|:---------|:----------|:---------|:--------|
 |MaskRCNN-ResNet50|51.2MB| 86.096 | 36.5 |32.2|
 |MaskRCNN-ResNet50-FPN|184.6MB | 65.859 | 37.9 |34.2|

+ 11 - 0
paddlex/__init__.py

@@ -20,6 +20,17 @@ from . import seg
 from . import cls
 from . import slim
 
+try:
+    import pycocotools
+except:
+    print("[WARNING] pycocotools is not installed, detection model is not available now.")
+    print("[WARNING] pycocotools install: https://github.com/PaddlePaddle/PaddleX/blob/develop/docs/install.md")
+
+import paddlehub as hub
+if hub.version.hub_version < '1.6.2':
+    raise Exception("[ERROR] paddlehub >= 1.6.2 is required")
+
+
 env_info = get_environ_info()
 load_model = cv.models.load_model
 datasets = cv.datasets

+ 3 - 0
paddlex/cv/datasets/__init__.py

@@ -16,3 +16,6 @@ from .imagenet import ImageNet
 from .voc import VOCDetection
 from .coco import CocoDetection
 from .seg_dataset import SegDataset
+from .easydata_cls import EasyDataCls
+from .easydata_det import EasyDataDet
+from .easydata_seg import EasyDataSeg

+ 4 - 3
paddlex/cv/datasets/coco.py

@@ -19,7 +19,6 @@ import random
 import numpy as np
 import paddlex.utils.logging as logging
 import paddlex as pst
-from pycocotools.coco import COCO
 from .voc import VOCDetection
 from .dataset import is_pic
 
@@ -35,7 +34,7 @@ class CocoDetection(VOCDetection):
             系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的一半。
         buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。
         parallel_method (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'
-            线程和'process'进程两种方式。默认为'thread'(Windows和Mac下会强制使用thread,该参数无效)。
+            线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。
         shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。
     """
 
@@ -47,6 +46,8 @@ class CocoDetection(VOCDetection):
                  buffer_size=100,
                  parallel_method='process',
                  shuffle=False):
+        from pycocotools.coco import COCO
+
         super(VOCDetection, self).__init__(
             transforms=transforms,
             num_workers=num_workers,
@@ -111,7 +112,7 @@ class CocoDetection(VOCDetection):
 
             im_info = {
                 'im_id': np.array([img_id]).astype('int32'),
-                'origin_shape': np.array([im_h, im_w]).astype('int32'),
+                'image_shape': np.array([im_h, im_w]).astype('int32'),
             }
             label_info = {
                 'is_crowd': is_crowd,

+ 86 - 0
paddlex/cv/datasets/easydata_cls.py

@@ -0,0 +1,86 @@
+# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import os.path as osp
+import random
+import copy
+import json
+import paddlex.utils.logging as logging
+from .imagenet import ImageNet
+from .dataset import is_pic
+from .dataset import get_encoding
+
+
+class EasyDataCls(ImageNet):
+    """读取EasyDataCls格式的分类数据集,并对样本进行相应的处理。
+
+    Args:
+        data_dir (str): 数据集所在的目录路径。
+        file_list (str): 描述数据集图片文件和类别id的文件路径(文本内每行路径为相对data_dir的相对路)。
+        label_list (str): 描述数据集包含的类别信息文件路径。
+        transforms (paddlex.cls.transforms): 数据集中每个样本的预处理/增强算子。
+        num_workers (int|str): 数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据
+            系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核
+            数的一半。
+        buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。
+        parallel_method (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'
+            线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。
+        shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。
+    """
+    
+    def __init__(self,
+                 data_dir,
+                 file_list,
+                 label_list,
+                 transforms=None,
+                 num_workers='auto',
+                 buffer_size=100,
+                 parallel_method='process',
+                 shuffle=False):
+        super(ImageNet, self).__init__(
+            transforms=transforms,
+            num_workers=num_workers,
+            buffer_size=buffer_size,
+            parallel_method=parallel_method,
+            shuffle=shuffle)
+        self.file_list = list()
+        self.labels = list()
+        self._epoch = 0
+        
+        with open(label_list, encoding=get_encoding(label_list)) as f:
+            for line in f:
+                item = line.strip()
+                self.labels.append(item)
+        logging.info("Starting to read file list from dataset...")
+        with open(file_list, encoding=get_encoding(file_list)) as f:
+            for line in f:
+                img_file, json_file = [osp.join(data_dir, x) \
+                        for x in line.strip().split()[:2]]
+                if not is_pic(img_file):
+                    continue
+                if not osp.isfile(json_file):
+                    continue
+                if not osp.exists(img_file):
+                    raise IOError(
+                        'The image file {} is not exist!'.format(img_file))
+                with open(json_file, mode='r', \
+                          encoding=get_encoding(json_file)) as j:
+                    json_info = json.load(j)
+                label = json_info['labels'][0]['name']
+                self.file_list.append([img_file, self.labels.index(label)])
+        self.num_samples = len(self.file_list)
+        logging.info("{} samples in file {}".format(
+            len(self.file_list), file_list))
+    

+ 190 - 0
paddlex/cv/datasets/easydata_det.py

@@ -0,0 +1,190 @@
+# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import os.path as osp
+import random
+import copy
+import json
+import cv2
+import numpy as np
+import paddlex.utils.logging as logging
+from .voc import VOCDetection
+from .dataset import is_pic
+from .dataset import get_encoding
+
+class EasyDataDet(VOCDetection):
+    """读取EasyDataDet格式的检测数据集,并对样本进行相应的处理。
+
+    Args:
+        data_dir (str): 数据集所在的目录路径。
+        file_list (str): 描述数据集图片文件和对应标注文件的文件路径(文本内每行路径为相对data_dir的相对路)。
+        label_list (str): 描述数据集包含的类别信息文件路径。
+        transforms (paddlex.det.transforms): 数据集中每个样本的预处理/增强算子。
+        num_workers (int|str): 数据集中样本在预处理过程中的线程或进程数。默认为'auto'。当设为'auto'时,根据
+            系统的实际CPU核数设置`num_workers`: 如果CPU核数的一半大于8,则`num_workers`为8,否则为CPU核数的
+            一半。
+        buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。
+        parallel_method (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'
+            线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。
+        shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。
+    """
+    
+    def __init__(self,
+                 data_dir,
+                 file_list,
+                 label_list,
+                 transforms=None,
+                 num_workers='auto',
+                 buffer_size=100,
+                 parallel_method='process',
+                 shuffle=False):
+        super(VOCDetection, self).__init__(
+            transforms=transforms,
+            num_workers=num_workers,
+            buffer_size=buffer_size,
+            parallel_method=parallel_method,
+            shuffle=shuffle)
+        self.file_list = list()
+        self.labels = list()
+        self._epoch = 0
+        
+        annotations = {}
+        annotations['images'] = []
+        annotations['categories'] = []
+        annotations['annotations'] = []
+        
+        cname2cid = {}
+        label_id = 1
+        with open(label_list, encoding=get_encoding(label_list)) as fr:
+            for line in fr.readlines():
+                cname2cid[line.strip()] = label_id
+                label_id += 1
+                self.labels.append(line.strip())
+        logging.info("Starting to read file list from dataset...")
+        for k, v in cname2cid.items():
+            annotations['categories'].append({
+                'supercategory': 'component',
+                'id': v,
+                'name': k
+            })
+            
+        from pycocotools.mask import decode
+        ct = 0
+        ann_ct = 0
+        with open(file_list, encoding=get_encoding(file_list)) as f:
+            for line in f:
+                img_file, json_file = [osp.join(data_dir, x) \
+                        for x in line.strip().split()[:2]]
+                if not is_pic(img_file):
+                    continue
+                if not osp.isfile(json_file):
+                    continue
+                if not osp.exists(img_file):
+                    raise IOError(
+                        'The image file {} is not exist!'.format(img_file))
+                with open(json_file, mode='r', \
+                          encoding=get_encoding(json_file)) as j:
+                    json_info = json.load(j)
+                im_id = np.array([ct])
+                im = cv2.imread(img_file)
+                im_w = im.shape[1]
+                im_h = im.shape[0]
+                objs = json_info['labels']
+                gt_bbox = np.zeros((len(objs), 4), dtype=np.float32)
+                gt_class = np.zeros((len(objs), 1), dtype=np.int32)
+                gt_score = np.ones((len(objs), 1), dtype=np.float32)
+                is_crowd = np.zeros((len(objs), 1), dtype=np.int32)
+                difficult = np.zeros((len(objs), 1), dtype=np.int32)
+                gt_poly = [None] * len(objs)
+                for i, obj in enumerate(objs):
+                    cname = obj['name']
+                    gt_class[i][0] = cname2cid[cname]
+                    x1 = max(0, obj['x1'])
+                    y1 = max(0, obj['y1'])
+                    x2 = min(im_w - 1, obj['x2'])
+                    y2 = min(im_h - 1, obj['y2'])
+                    gt_bbox[i] = [x1, y1, x2, y2]
+                    is_crowd[i][0] = 0
+                    if 'mask' in obj:
+                        mask_dict = {}
+                        mask_dict['size'] = [im_h, im_w]
+                        mask_dict['counts'] = obj['mask'].encode()
+                        mask = decode(mask_dict)
+                        gt_poly[i] = self.mask2polygon(mask)
+                    annotations['annotations'].append({
+                        'iscrowd':
+                        0,
+                        'image_id':
+                        int(im_id[0]),
+                        'bbox': [x1, y1, x2 - x1 + 1, y2 - y1 + 1],
+                        'area':
+                        float((x2 - x1 + 1) * (y2 - y1 + 1)),
+                        'segmentation':
+                        [[x1, y1, x1, y2, x2, y2, x2, y1]] if gt_poly[i] is None else gt_poly[i],
+                        'category_id':
+                        cname2cid[cname],
+                        'id':
+                        ann_ct,
+                        'difficult':
+                        0
+                    })
+                    ann_ct += 1
+                im_info = {
+                    'im_id': im_id,
+                    'image_shape': np.array([im_h, im_w]).astype('int32'),
+                }
+                label_info = {
+                    'is_crowd': is_crowd,
+                    'gt_class': gt_class,
+                    'gt_bbox': gt_bbox,
+                    'gt_score': gt_score,
+                    'difficult': difficult
+                }
+                if None not in gt_poly:
+                    label_info['gt_poly'] = gt_poly
+                voc_rec = (im_info, label_info)
+                if len(objs) != 0:
+                    self.file_list.append([img_file, voc_rec])
+                    ct += 1
+                    annotations['images'].append({
+                        'height':
+                        im_h,
+                        'width':
+                        im_w,
+                        'id':
+                        int(im_id[0]),
+                        'file_name':
+                        osp.split(img_file)[1]
+                    })
+
+        if not len(self.file_list) > 0:
+            raise Exception('not found any voc record in %s' % (file_list))
+        logging.info("{} samples in file {}".format(
+            len(self.file_list), file_list))
+        self.num_samples = len(self.file_list)
+        from pycocotools.coco import COCO
+        self.coco_gt = COCO()
+        self.coco_gt.dataset = annotations
+        self.coco_gt.createIndex()
+        
+    def mask2polygon(self, mask):
+        contours, hierarchy = cv2.findContours(
+            (mask).astype(np.uint8), cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
+        segmentation = []
+        for contour in contours:
+            contour_list = contour.flatten().tolist()
+            if len(contour_list) > 4:
+                segmentation.append(contour_list)
+        return segmentation

+ 116 - 0
paddlex/cv/datasets/easydata_seg.py

@@ -0,0 +1,116 @@
+# copyright (c) 2020 PaddlePaddle Authors. All Rights Reserve.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+import os.path as osp
+import random
+import copy
+import json
+import cv2
+import numpy as np
+import paddlex.utils.logging as logging
+from .dataset import Dataset
+from .dataset import get_encoding
+from .dataset import is_pic
+
+class EasyDataSeg(Dataset):
+    """读取EasyDataSeg语义分割任务数据集,并对样本进行相应的处理。
+
+    Args:
+        data_dir (str): 数据集所在的目录路径。
+        file_list (str): 描述数据集图片文件和对应标注文件的文件路径(文本内每行路径为相对data_dir的相对路)。
+        label_list (str): 描述数据集包含的类别信息文件路径。
+        transforms (list): 数据集中每个样本的预处理/增强算子。
+        num_workers (int): 数据集中样本在预处理过程中的线程或进程数。默认为4。
+        buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。
+        parallel_method (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'
+            线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。
+        shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。
+    """
+
+    def __init__(self,
+                 data_dir,
+                 file_list,
+                 label_list,
+                 transforms=None,
+                 num_workers='auto',
+                 buffer_size=100,
+                 parallel_method='process',
+                 shuffle=False):
+        super(EasyDataSeg, self).__init__(
+            transforms=transforms,
+            num_workers=num_workers,
+            buffer_size=buffer_size,
+            parallel_method=parallel_method,
+            shuffle=shuffle)
+        self.file_list = list()
+        self.labels = list()
+        self._epoch = 0
+
+        from pycocotools.mask import decode
+        cname2cid = {}
+        label_id = 0
+        with open(label_list, encoding=get_encoding(label_list)) as fr:
+            for line in fr.readlines():
+                cname2cid[line.strip()] = label_id
+                label_id += 1
+                self.labels.append(line.strip())
+                
+        with open(file_list, encoding=get_encoding(file_list)) as f:
+            for line in f:
+                img_file, json_file = [osp.join(data_dir, x) \
+                        for x in line.strip().split()[:2]]
+                if not is_pic(img_file):
+                    continue
+                if not osp.isfile(json_file):
+                    continue
+                if not osp.exists(img_file):
+                    raise IOError(
+                        'The image file {} is not exist!'.format(img_file))
+                with open(json_file, mode='r', \
+                          encoding=get_encoding(json_file)) as j:
+                    json_info = json.load(j)
+                im = cv2.imread(img_file)
+                im_w = im.shape[1]
+                im_h = im.shape[0]
+                objs = json_info['labels']
+                lable_npy = np.zeros([im_h, im_w]).astype('uint8')
+                for i, obj in enumerate(objs):
+                    cname = obj['name']
+                    cid = cname2cid[cname]
+                    mask_dict = {}
+                    mask_dict['size'] = [im_h, im_w]
+                    mask_dict['counts'] = obj['mask'].encode()
+                    mask = decode(mask_dict)
+                    mask *= cid
+                    conflict_index = np.where(((lable_npy > 0) & (mask == cid)) == True)
+                    mask[conflict_index] = 0
+                    lable_npy += mask
+                self.file_list.append([img_file, lable_npy])
+        self.num_samples = len(self.file_list)
+        logging.info("{} samples in file {}".format(
+            len(self.file_list), file_list))
+
+    def iterator(self):
+        self._epoch += 1
+        self._pos = 0
+        files = copy.deepcopy(self.file_list)
+        if self.shuffle:
+            random.shuffle(files)
+        files = files[:self.num_samples]
+        self.num_samples = len(files)
+        for f in files:
+            lable_npy = f[1]
+            sample = [f[0], None, lable_npy]
+            yield sample

+ 1 - 1
paddlex/cv/datasets/imagenet.py

@@ -35,7 +35,7 @@ class ImageNet(Dataset):
             数的一半。
         buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。
         parallel_method (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'
-            线程和'process'进程两种方式。默认为'thread'(Windows和Mac下会强制使用thread,该参数无效)。
+            线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。
         shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。
     """
 

+ 1 - 1
paddlex/cv/datasets/seg_dataset.py

@@ -33,7 +33,7 @@ class SegDataset(Dataset):
         num_workers (int): 数据集中样本在预处理过程中的线程或进程数。默认为4。
         buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。
         parallel_method (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'
-            线程和'process'进程两种方式。默认为'thread'(Windows和Mac下会强制使用thread,该参数无效)。
+            线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。
         shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。
     """
 

+ 5 - 4
paddlex/cv/datasets/voc.py

@@ -17,8 +17,8 @@ import copy
 import os.path as osp
 import random
 import numpy as np
+from collections import OrderedDict
 import xml.etree.ElementTree as ET
-from pycocotools.coco import COCO
 import paddlex.utils.logging as logging
 from .dataset import Dataset
 from .dataset import is_pic
@@ -38,7 +38,7 @@ class VOCDetection(Dataset):
             一半。
         buffer_size (int): 数据集中样本在预处理过程中队列的缓存长度,以样本数为单位。默认为100。
         parallel_method (str): 数据集中样本在预处理过程中并行处理的方式,支持'thread'
-            线程和'process'进程两种方式。默认为'thread'(Windows和Mac下会强制使用thread,该参数无效)。
+            线程和'process'进程两种方式。默认为'process'(Windows和Mac下会强制使用thread,该参数无效)。
         shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。
     """
 
@@ -51,6 +51,7 @@ class VOCDetection(Dataset):
                  buffer_size=100,
                  parallel_method='process',
                  shuffle=False):
+        from pycocotools.coco import COCO
         super(VOCDetection, self).__init__(
             transforms=transforms,
             num_workers=num_workers,
@@ -66,7 +67,7 @@ class VOCDetection(Dataset):
         annotations['categories'] = []
         annotations['annotations'] = []
 
-        cname2cid = {}
+        cname2cid = OrderedDict()
         label_id = 1
         with open(label_list, 'r', encoding=get_encoding(label_list)) as fr:
             for line in fr.readlines():
@@ -145,7 +146,7 @@ class VOCDetection(Dataset):
 
                 im_info = {
                     'im_id': im_id,
-                    'origin_shape': np.array([im_h, im_w]).astype('int32'),
+                    'image_shape': np.array([im_h, im_w]).astype('int32'),
                 }
                 label_info = {
                     'is_crowd': is_crowd,

+ 10 - 1
paddlex/cv/models/base.py

@@ -24,6 +24,7 @@ import json
 import functools
 import paddlex.utils.logging as logging
 from paddlex.utils import seconds_to_hms
+from paddlex.utils.utils import EarlyStop
 import paddlex
 from collections import OrderedDict
 from os import path as osp
@@ -334,7 +335,9 @@ class BaseAPI:
                    save_interval_epochs=1,
                    log_interval_steps=10,
                    save_dir='output',
-                   use_vdl=False):
+                   use_vdl=False,
+                   early_stop=False,
+                   early_stop_patience=5):
         if not osp.isdir(save_dir):
             if osp.exists(save_dir):
                 os.remove(save_dir)
@@ -396,6 +399,9 @@ class BaseAPI:
             train_step_component = OrderedDict()
             eval_component = OrderedDict()
 
+        thresh = 0.0001
+        if early_stop:
+            earlystop = EarlyStop(early_stop_patience, thresh)
         best_accuracy_key = ""
         best_accuracy = -1.0
         best_model_epoch = 1
@@ -507,3 +513,6 @@ class BaseAPI:
                     'Current evaluated best model in eval_dataset is epoch_{}, {}={}'
                     .format(best_model_epoch, best_accuracy_key,
                             best_accuracy))
+                if eval_dataset is not None and early_stop:
+                    if earlystop(current_accuracy):
+                        break

+ 9 - 2
paddlex/cv/models/classifier.py

@@ -110,7 +110,9 @@ class BaseClassifier(BaseAPI):
               lr_decay_gamma=0.1,
               use_vdl=False,
               sensitivities_file=None,
-              eval_metric_loss=0.05):
+              eval_metric_loss=0.05,
+              early_stop=False,
+              early_stop_patience=5):
         """训练。
 
         Args:
@@ -132,6 +134,9 @@ class BaseClassifier(BaseAPI):
             sensitivities_file (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串'DEFAULT',
                 则自动下载在ImageNet图片数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
             eval_metric_loss (float): 可容忍的精度损失。默认为0.05。
+            early_stop (bool): 是否使用提前终止训练策略。默认值为False。
+            early_stop_patience (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内
+                连续下降或持平,则终止训练。默认值为5。
 
         Raises:
             ValueError: 模型从inference model进行加载。
@@ -166,7 +171,9 @@ class BaseClassifier(BaseAPI):
             save_interval_epochs=save_interval_epochs,
             log_interval_steps=log_interval_steps,
             save_dir=save_dir,
-            use_vdl=use_vdl)
+            use_vdl=use_vdl,
+            early_stop=early_stop,
+            early_stop_patience=early_stop_patience)
 
     def evaluate(self,
                  eval_dataset,

+ 9 - 2
paddlex/cv/models/deeplabv3p.py

@@ -232,7 +232,9 @@ class DeepLabv3p(BaseAPI):
               lr_decay_power=0.9,
               use_vdl=False,
               sensitivities_file=None,
-              eval_metric_loss=0.05):
+              eval_metric_loss=0.05,
+              early_stop=False,
+              early_stop_patience=5):
         """训练。
 
         Args:
@@ -253,6 +255,9 @@ class DeepLabv3p(BaseAPI):
             sensitivities_file (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串'DEFAULT',
                 则自动下载在ImageNet图片数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
             eval_metric_loss (float): 可容忍的精度损失。默认为0.05。
+            early_stop (bool): 是否使用提前终止训练策略。默认值为False。
+            early_stop_patience (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内
+                连续下降或持平,则终止训练。默认值为5。
 
         Raises:
             ValueError: 模型从inference model进行加载。
@@ -289,7 +294,9 @@ class DeepLabv3p(BaseAPI):
             save_interval_epochs=save_interval_epochs,
             log_interval_steps=log_interval_steps,
             save_dir=save_dir,
-            use_vdl=use_vdl)
+            use_vdl=use_vdl,
+            early_stop=early_stop,
+            early_stop_patience=early_stop_patience)
 
     def evaluate(self,
                  eval_dataset,

+ 17 - 9
paddlex/cv/models/faster_rcnn.py

@@ -32,7 +32,7 @@ class FasterRCNN(BaseAPI):
     Args:
         num_classes (int): 包含了背景类的类别数。默认为81。
         backbone (str): FasterRCNN的backbone网络,取值范围为['ResNet18', 'ResNet50',
-            'ResNet50vd', 'ResNet101', 'ResNet101vd']。默认为'ResNet50'。
+            'ResNet50_vd', 'ResNet101', 'ResNet101_vd']。默认为'ResNet50'。
         with_fpn (bool): 是否使用FPN结构。默认为True。
         aspect_ratios (list): 生成anchor高宽比的可选值。默认为[0.5, 1.0, 2.0]。
         anchor_sizes (list): 生成anchor大小的可选值。默认为[32, 64, 128, 256, 512]。
@@ -47,7 +47,7 @@ class FasterRCNN(BaseAPI):
         self.init_params = locals()
         super(FasterRCNN, self).__init__('detector')
         backbones = [
-            'ResNet18', 'ResNet50', 'ResNet50vd', 'ResNet101', 'ResNet101vd'
+            'ResNet18', 'ResNet50', 'ResNet50_vd', 'ResNet101', 'ResNet101_vd'
         ]
         assert backbone in backbones, "backbone should be one of {}".format(
             backbones)
@@ -67,7 +67,7 @@ class FasterRCNN(BaseAPI):
         elif backbone_name == 'ResNet50':
             layers = 50
             variant = 'b'
-        elif backbone_name == 'ResNet50vd':
+        elif backbone_name == 'ResNet50_vd':
             layers = 50
             variant = 'd'
             norm_type = 'affine_channel'
@@ -75,7 +75,7 @@ class FasterRCNN(BaseAPI):
             layers = 101
             variant = 'b'
             norm_type = 'affine_channel'
-        elif backbone_name == 'ResNet101vd':
+        elif backbone_name == 'ResNet101_vd':
             layers = 101
             variant = 'd'
             norm_type = 'affine_channel'
@@ -165,7 +165,9 @@ class FasterRCNN(BaseAPI):
               lr_decay_epochs=[8, 11],
               lr_decay_gamma=0.1,
               metric=None,
-              use_vdl=False):
+              use_vdl=False,
+              early_stop=False,
+              early_stop_patience=5):
         """训练。
 
         Args:
@@ -178,7 +180,7 @@ class FasterRCNN(BaseAPI):
             log_interval_steps (int): 训练日志输出间隔(单位:迭代次数)。默认为20。
             save_dir (str): 模型保存路径。默认值为'output'。
             pretrain_weights (str): 若指定为路径时,则加载路径下预训练模型;若为字符串'IMAGENET',
-                则自动下载在ImageNet图片数据上预训练的模型权重;若为None,则不使用预训练模型。默认为None
+                则自动下载在ImageNet图片数据上预训练的模型权重;若为None,则不使用预训练模型。默认为'IMAGENET'
             optimizer (paddle.fluid.optimizer): 优化器。当该参数为None时,使用默认优化器:
                 fluid.layers.piecewise_decay衰减策略,fluid.optimizer.Momentum优化方法。
             learning_rate (float): 默认优化器的初始学习率。默认为0.0025。
@@ -188,6 +190,9 @@ class FasterRCNN(BaseAPI):
             lr_decay_gamma (float): 默认优化器的学习率衰减率。默认为0.1。
             metric (bool): 训练过程中评估的方式,取值范围为['COCO', 'VOC']。默认值为None。
             use_vdl (bool): 是否使用VisualDL进行可视化。默认值为False。
+            early_stop (bool): 是否使用提前终止训练策略。默认值为False。
+            early_stop_patience (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内
+                连续下降或持平,则终止训练。默认值为5。
 
         Raises:
             ValueError: 评估类型不在指定列表中。
@@ -196,11 +201,12 @@ class FasterRCNN(BaseAPI):
         if metric is None:
             if isinstance(train_dataset, paddlex.datasets.CocoDetection):
                 metric = 'COCO'
-            elif isinstance(train_dataset, paddlex.datasets.VOCDetection):
+            elif isinstance(train_dataset, paddlex.datasets.VOCDetection) or \
+                    isinstance(train_dataset, paddlex.datasets.EasyDataDet):
                 metric = 'VOC'
             else:
                 raise ValueError(
-                    "train_dataset should be datasets.VOCDetection or datasets.COCODetection."
+                    "train_dataset should be datasets.VOCDetection or datasets.COCODetection or datasets.EasyDataDet."
                 )
         assert metric in ['COCO', 'VOC'], "Metric only support 'VOC' or 'COCO'"
         self.metric = metric
@@ -235,7 +241,9 @@ class FasterRCNN(BaseAPI):
             save_interval_epochs=save_interval_epochs,
             log_interval_steps=log_interval_steps,
             save_dir=save_dir,
-            use_vdl=use_vdl)
+            use_vdl=use_vdl,
+            early_stop=early_stop,
+            early_stop_patience=early_stop_patience)
 
     def evaluate(self,
                  eval_dataset,

+ 14 - 6
paddlex/cv/models/mask_rcnn.py

@@ -32,7 +32,7 @@ class MaskRCNN(FasterRCNN):
     Args:
         num_classes (int): 包含了背景类的类别数。默认为81。
         backbone (str): MaskRCNN的backbone网络,取值范围为['ResNet18', 'ResNet50',
-            'ResNet50vd', 'ResNet101', 'ResNet101vd']。默认为'ResNet50'。
+            'ResNet50_vd', 'ResNet101', 'ResNet101_vd']。默认为'ResNet50'。
         with_fpn (bool): 是否使用FPN结构。默认为True。
         aspect_ratios (list): 生成anchor高宽比的可选值。默认为[0.5, 1.0, 2.0]。
         anchor_sizes (list): 生成anchor大小的可选值。默认为[32, 64, 128, 256, 512]。
@@ -46,7 +46,7 @@ class MaskRCNN(FasterRCNN):
                  anchor_sizes=[32, 64, 128, 256, 512]):
         self.init_params = locals()
         backbones = [
-            'ResNet18', 'ResNet50', 'ResNet50vd', 'ResNet101', 'ResNet101vd'
+            'ResNet18', 'ResNet50', 'ResNet50_vd', 'ResNet101', 'ResNet101_vd'
         ]
         assert backbone in backbones, "backbone should be one of {}".format(
             backbones)
@@ -130,7 +130,9 @@ class MaskRCNN(FasterRCNN):
               lr_decay_epochs=[8, 11],
               lr_decay_gamma=0.1,
               metric=None,
-              use_vdl=False):
+              use_vdl=False,
+              early_stop=False,
+              early_stop_patience=5):
         """训练。
 
         Args:
@@ -153,17 +155,21 @@ class MaskRCNN(FasterRCNN):
             lr_decay_gamma (float): 默认优化器的学习率衰减率。默认为0.1。
             metric (bool): 训练过程中评估的方式,取值范围为['COCO', 'VOC']。
             use_vdl (bool): 是否使用VisualDL进行可视化。默认值为False。
+            early_stop (bool): 是否使用提前终止训练策略。默认值为False。
+            early_stop_patience (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内
+                连续下降或持平,则终止训练。默认值为5。
 
         Raises:
             ValueError: 评估类型不在指定列表中。
             ValueError: 模型从inference model进行加载。
         """
         if metric is None:
-            if isinstance(train_dataset, paddlex.datasets.CocoDetection):
+            if isinstance(train_dataset, paddlex.datasets.CocoDetection) or \
+                    isinstance(train_dataset, paddlex.datasets.EasyDataDet):
                 metric = 'COCO'
             else:
                 raise Exception(
-                    "train_dataset should be datasets.COCODetection.")
+                    "train_dataset should be datasets.COCODetection or datasets.EasyDataDet.")
         assert metric in ['COCO', 'VOC'], "Metric only support 'VOC' or 'COCO'"
         self.metric = metric
         if not self.trainable:
@@ -201,7 +207,9 @@ class MaskRCNN(FasterRCNN):
             save_interval_epochs=save_interval_epochs,
             log_interval_steps=log_interval_steps,
             save_dir=save_dir,
-            use_vdl=use_vdl)
+            use_vdl=use_vdl,
+            early_stop=early_stop,
+            early_stop_patience=early_stop_patience)
 
     def evaluate(self,
                  eval_dataset,

+ 4 - 3
paddlex/cv/models/slim/visualize.py

@@ -15,9 +15,6 @@
 import os.path as osp
 import tqdm
 import numpy as np
-import matplotlib
-matplotlib.use('Agg')
-import matplotlib.pyplot as plt
 from .prune import cal_model_size
 from paddleslim.prune import load_sensitivities
 
@@ -30,6 +27,10 @@ def visualize(model, sensitivities_file, save_dir='./'):
         model (paddlex.cv.models): paddlex中的模型。
         sensitivities_file (str): 敏感度文件存储路径。
     """
+    import matplotlib
+    matplotlib.use('Agg')
+    import matplotlib.pyplot as plt
+
     program = model.test_prog
     place = model.places[0]
     fig = plt.figure()

+ 13 - 6
paddlex/cv/models/unet.py

@@ -119,7 +119,9 @@ class UNet(DeepLabv3p):
               lr_decay_power=0.9,
               use_vdl=False,
               sensitivities_file=None,
-              eval_metric_loss=0.05):
+              eval_metric_loss=0.05,
+              early_stop=False,
+              early_stop_patience=5):
         """训练。
 
         Args:
@@ -140,12 +142,17 @@ class UNet(DeepLabv3p):
             sensitivities_file (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串'DEFAULT',
                 则自动下载在ImageNet图片数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
             eval_metric_loss (float): 可容忍的精度损失。默认为0.05。
+            early_stop (bool): 是否使用提前终止训练策略。默认值为False。
+            early_stop_patience (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内
+                连续下降或持平,则终止训练。默认值为5。
 
         Raises:
             ValueError: 模型从inference model进行加载。
         """
-        return super(UNet, self).train(
-            num_epochs, train_dataset, train_batch_size, eval_dataset,
-            save_interval_epochs, log_interval_steps, save_dir,
-            pretrain_weights, optimizer, learning_rate, lr_decay_power,
-            use_vdl, sensitivities_file, eval_metric_loss)
+        return super(
+            UNet,
+            self).train(num_epochs, train_dataset, train_batch_size,
+                        eval_dataset, save_interval_epochs, log_interval_steps,
+                        save_dir, pretrain_weights, optimizer, learning_rate,
+                        lr_decay_power, use_vdl, sensitivities_file,
+                        eval_metric_loss, early_stop, early_stop_patience)

+ 145 - 30
paddlex/cv/models/utils/visualize.py

@@ -14,10 +14,8 @@
 
 import os
 import cv2
+import colorsys
 import numpy as np
-import matplotlib.pyplot as plt
-from PIL import Image, ImageDraw
-
 import paddlex.utils.logging as logging
 from .detection_eval import fixed_linspace, backup_linspace, loadRes
 
@@ -28,13 +26,13 @@ def visualize_detection(image, result, threshold=0.5, save_dir='./'):
     """
 
     image_name = os.path.split(image)[-1]
-    image = Image.open(image).convert('RGB')
+    image = cv2.imread(image)
     image = draw_bbox_mask(image, result, threshold=threshold)
     if save_dir is not None:
         if not os.path.exists(save_dir):
             os.makedirs(save_dir)
         out_path = os.path.join(save_dir, 'visualize_{}'.format(image_name))
-        image.save(out_path, quality=95)
+        cv2.imwrite(out_path, image)
         logging.info('The visualized result is saved as {}'.format(out_path))
     else:
         return image
@@ -123,49 +121,163 @@ def clip_bbox(bbox):
     return xmin, ymin, xmax, ymax
 
 
-def draw_bbox_mask(image, results, threshold=0.5, alpha=0.7):
+def draw_bbox_mask(image, results, threshold=0.5):
+    import matplotlib
+    matplotlib.use('Agg')
+    import matplotlib as mpl
+    import matplotlib.figure as mplfigure
+    import matplotlib.colors as mplc
+    from matplotlib.backends.backend_agg import FigureCanvasAgg
+
+    # refer to  https://github.com/facebookresearch/detectron2/blob/master/detectron2/utils/visualizer.py
+    def _change_color_brightness(color, brightness_factor):
+        assert brightness_factor >= -1.0 and brightness_factor <= 1.0
+        color = mplc.to_rgb(color)
+        polygon_color = colorsys.rgb_to_hls(*mplc.to_rgb(color))
+        modified_lightness = polygon_color[1] + (
+            brightness_factor * polygon_color[1])
+        modified_lightness = 0.0 if modified_lightness < 0.0 else modified_lightness
+        modified_lightness = 1.0 if modified_lightness > 1.0 else modified_lightness
+        modified_color = colorsys.hls_to_rgb(
+            polygon_color[0], modified_lightness, polygon_color[2])
+        return modified_color
+
+    _SMALL_OBJECT_AREA_THRESH = 1000
+    # setup figure
+    width, height = image.shape[1], image.shape[0]
+    scale = 1
+    fig = mplfigure.Figure(frameon=False)
+    dpi = fig.get_dpi()
+    fig.set_size_inches(
+        (width * scale + 1e-2) / dpi,
+        (height * scale + 1e-2) / dpi,
+    )
+    canvas = FigureCanvasAgg(fig)
+    ax = fig.add_axes([0.0, 0.0, 1.0, 1.0])
+    ax.axis("off")
+    ax.set_xlim(0.0, width)
+    ax.set_ylim(height)
+    default_font_size = max(np.sqrt(height * width) // 90, 10 // scale)
+    linewidth = max(default_font_size / 4, 1)
+
     labels = list()
     for dt in np.array(results):
         if dt['category'] not in labels:
             labels.append(dt['category'])
-    color_map = get_color_map_list(len(labels))
+    color_map = get_color_map_list(256)
 
+    keep_results = []
+    areas = []
     for dt in np.array(results):
         cname, bbox, score = dt['category'], dt['bbox'], dt['score']
         if score < threshold:
             continue
-
+        keep_results.append(dt)
+        areas.append(bbox[2] * bbox[3])
+    areas = np.asarray(areas)
+    sorted_idxs = np.argsort(-areas).tolist()
+    keep_results = [keep_results[k]
+                    for k in sorted_idxs] if len(keep_results) > 0 else []
+
+    for dt in np.array(keep_results):
+        cname, bbox, score = dt['category'], dt['bbox'], dt['score']
         xmin, ymin, w, h = bbox
         xmax = xmin + w
         ymax = ymin + h
 
-        color = tuple(color_map[labels.index(cname)])
-
+        color = tuple(color_map[labels.index(cname) + 2])
+        color = [c / 255. for c in color]
         # draw bbox
-        draw = ImageDraw.Draw(image)
-        draw.line([(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin),
-                   (xmin, ymin)],
-                  width=2,
-                  fill=color)
-
-        # draw label
-        text = "{} {:.2f}".format(cname, score)
-        tw, th = draw.textsize(text)
-        draw.rectangle([(xmin + 1, ymin - th), (xmin + tw + 1, ymin)],
-                       fill=color)
-        draw.text((xmin + 1, ymin - th), text, fill=(255, 255, 255))
+        ax.add_patch(
+            mpl.patches.Rectangle(
+                (xmin, ymin),
+                w,
+                h,
+                fill=False,
+                edgecolor=color,
+                linewidth=linewidth * scale,
+                alpha=0.8,
+                linestyle="-",
+            ))
 
         # draw mask
         if 'mask' in dt:
             mask = dt['mask']
-            color_mask = np.array(color_map[labels.index(
-                dt['category'])]).astype('float32')
-            img_array = np.array(image).astype('float32')
-            idx = np.nonzero(mask)
-            img_array[idx[0], idx[1], :] *= 1.0 - alpha
-            img_array[idx[0], idx[1], :] += alpha * color_mask
-            image = Image.fromarray(img_array.astype('uint8'))
-    return image
+            mask = np.ascontiguousarray(mask)
+            res = cv2.findContours(
+                mask.astype("uint8"), cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
+            hierarchy = res[-1]
+            alpha = 0.5
+            if hierarchy is not None:
+                has_holes = (hierarchy.reshape(-1, 4)[:, 3] >= 0).sum() > 0
+                res = res[-2]
+                res = [x.flatten() for x in res]
+                res = [x for x in res if len(x) >= 6]
+                for segment in res:
+                    segment = segment.reshape(-1, 2)
+                    edge_color = mplc.to_rgb(color) + (1, )
+                    polygon = mpl.patches.Polygon(
+                        segment,
+                        fill=True,
+                        facecolor=mplc.to_rgb(color) + (alpha, ),
+                        edgecolor=edge_color,
+                        linewidth=max(default_font_size // 15 * scale, 1),
+                    )
+                    ax.add_patch(polygon)
+
+        # draw label
+        text_pos = (xmin, ymin)
+        horiz_align = "left"
+        instance_area = w * h
+        if (instance_area < _SMALL_OBJECT_AREA_THRESH * scale
+                or h < 40 * scale):
+            if ymin >= height - 5:
+                text_pos = (xmin, ymin)
+            else:
+                text_pos = (xmin, ymax)
+        height_ratio = h / np.sqrt(height * width)
+        font_size = (np.clip((height_ratio - 0.02) / 0.08 + 1, 1.2, 2) * 0.5 *
+                     default_font_size)
+        text = "{} {:.2f}".format(cname, score)
+        color = np.maximum(list(mplc.to_rgb(color)), 0.2)
+        color[np.argmax(color)] = max(0.8, np.max(color))
+        color = _change_color_brightness(color, brightness_factor=0.7)
+        ax.text(
+            text_pos[0],
+            text_pos[1],
+            text,
+            size=font_size * scale,
+            family="sans-serif",
+            bbox={
+                "facecolor": "black",
+                "alpha": 0.8,
+                "pad": 0.7,
+                "edgecolor": "none"
+            },
+            verticalalignment="top",
+            horizontalalignment=horiz_align,
+            color=color,
+            zorder=10,
+            rotation=0,
+        )
+
+    s, (width, height) = canvas.print_to_buffer()
+    buffer = np.frombuffer(s, dtype="uint8")
+
+    img_rgba = buffer.reshape(height, width, 4)
+    rgb, alpha = np.split(img_rgba, [3], axis=2)
+
+    try:
+        import numexpr as ne
+        visualized_image = ne.evaluate(
+            "image * (1 - alpha / 255.0) + rgb * (alpha / 255.0)")
+    except ImportError:
+        alpha = alpha.astype("float32") / 255.0
+        visualized_image = image * (1 - alpha) + rgb * alpha
+
+    visualized_image = visualized_image.astype("uint8")
+
+    return visualized_image
 
 
 def draw_pr_curve(eval_details_file=None,
@@ -190,6 +302,9 @@ def draw_pr_curve(eval_details_file=None,
         raise Exception("There is no predicted bbox.")
     if pred_mask is not None and len(pred_mask) == 0:
         raise Exception("There is no predicted mask.")
+    import matplotlib
+    matplotlib.use('Agg')
+    import matplotlib.pyplot as plt
     from pycocotools.coco import COCO
     from pycocotools.cocoeval import COCOeval
     coco = COCO()

+ 13 - 5
paddlex/cv/models/yolo_v3.py

@@ -164,7 +164,9 @@ class YOLOv3(BaseAPI):
               metric=None,
               use_vdl=False,
               sensitivities_file=None,
-              eval_metric_loss=0.05):
+              eval_metric_loss=0.05,
+              early_stop=False,
+              early_stop_patience=5):
         """训练。
 
         Args:
@@ -177,7 +179,7 @@ class YOLOv3(BaseAPI):
             log_interval_steps (int): 训练日志输出间隔(单位:迭代次数)。默认为10。
             save_dir (str): 模型保存路径。默认值为'output'。
             pretrain_weights (str): 若指定为路径时,则加载路径下预训练模型;若为字符串'IMAGENET',
-                则自动下载在ImageNet图片数据上预训练的模型权重;若为None,则不使用预训练模型。默认为None
+                则自动下载在ImageNet图片数据上预训练的模型权重;若为None,则不使用预训练模型。默认为'IMAGENET'
             optimizer (paddle.fluid.optimizer): 优化器。当该参数为None时,使用默认优化器:
                 fluid.layers.piecewise_decay衰减策略,fluid.optimizer.Momentum优化方法。
             learning_rate (float): 默认优化器的学习率。默认为1.0/8000。
@@ -190,6 +192,9 @@ class YOLOv3(BaseAPI):
             sensitivities_file (str): 若指定为路径时,则加载路径下敏感度信息进行裁剪;若为字符串'DEFAULT',
                 则自动下载在ImageNet图片数据上获得的敏感度信息进行裁剪;若为None,则不进行裁剪。默认为None。
             eval_metric_loss (float): 可容忍的精度损失。默认为0.05。
+            early_stop (bool): 是否使用提前终止训练策略。默认值为False。
+            early_stop_patience (int): 当使用提前终止训练策略时,如果验证集精度在`early_stop_patience`个epoch内
+                连续下降或持平,则终止训练。默认值为5。
 
         Raises:
             ValueError: 评估类型不在指定列表中。
@@ -200,11 +205,12 @@ class YOLOv3(BaseAPI):
         if metric is None:
             if isinstance(train_dataset, paddlex.datasets.CocoDetection):
                 metric = 'COCO'
-            elif isinstance(train_dataset, paddlex.datasets.VOCDetection):
+            elif isinstance(train_dataset, paddlex.datasets.VOCDetection) or \
+                    isinstance(train_dataset, paddlex.datasets.EasyDataDet):
                 metric = 'VOC'
             else:
                 raise ValueError(
-                    "train_dataset should be datasets.VOCDetection or datasets.COCODetection."
+                    "train_dataset should be datasets.VOCDetection or datasets.COCODetection or datasets.EasyDataDet."
                 )
         assert metric in ['COCO', 'VOC'], "Metric only support 'VOC' or 'COCO'"
         self.metric = metric
@@ -240,7 +246,9 @@ class YOLOv3(BaseAPI):
             save_interval_epochs=save_interval_epochs,
             log_interval_steps=log_interval_steps,
             save_dir=save_dir,
-            use_vdl=use_vdl)
+            use_vdl=use_vdl,
+            early_stop=early_stop,
+            early_stop_patience=early_stop_patience)
 
     def evaluate(self,
                  eval_dataset,

+ 30 - 40
paddlex/cv/transforms/det_transforms.py

@@ -58,8 +58,8 @@ class Compose:
             im (str/np.ndarray): 图像路径/图像np.ndarray数据。
             im_info (dict): 存储与图像相关的信息,dict中的字段如下:
                 - im_id (np.ndarray): 图像序列号,形状为(1,)。
-                - origin_shape (np.ndarray): 图像原始大小,形状为(2,),
-                                        origin_shape[0]为高,origin_shape[1]为宽。
+                - image_shape (np.ndarray): 图像原始大小,形状为(2,),
+                                        image_shape[0]为高,image_shape[1]为宽。
                 - mixup (list): list为[im, im_info, label_info],分别对应
                                 与当前图像进行mixup的图像np.ndarray数据、图像相关信息、标注框相关信息;
                                 注意,当前epoch若无需进行mixup,则无该字段。
@@ -93,9 +93,6 @@ class Compose:
             # make default im_info with [h, w, 1]
             im_info['im_resize_info'] = np.array(
                 [im.shape[0], im.shape[1], 1.], dtype=np.float32)
-            # copy augment_shape from origin_shape
-            im_info['augment_shape'] = np.array([im.shape[0],
-                                                 im.shape[1]]).astype('int32')
             if not self.use_mixup:
                 if 'mixup' in im_info:
                     del im_info['mixup']
@@ -407,16 +404,13 @@ class RandomHorizontalFlip:
             raise TypeError(
                 'Cannot do RandomHorizontalFlip! ' +
                 'Becasuse the im_info and label_info can not be None!')
-        if 'augment_shape' not in im_info:
-            raise TypeError('Cannot do RandomHorizontalFlip! ' + \
-                            'Becasuse augment_shape is not in im_info!')
         if 'gt_bbox' not in label_info:
             raise TypeError('Cannot do RandomHorizontalFlip! ' + \
                             'Becasuse gt_bbox is not in label_info!')
-        augment_shape = im_info['augment_shape']
+        image_shape = im_info['image_shape']
         gt_bbox = label_info['gt_bbox']
-        height = augment_shape[0]
-        width = augment_shape[1]
+        height = image_shape[0]
+        width = image_shape[1]
 
         if np.random.uniform(0, 1) < self.prob:
             im = horizontal_flip(im)
@@ -587,7 +581,7 @@ class MixupImage:
             (2)拼接原图像标注框和mixup图像标注框。
             (3)拼接原图像标注框类别和mixup图像标注框类别。
             (4)原图像标注框混合得分乘以factor,mixup图像标注框混合得分乘以(1-factor),叠加2个结果。
-    3. 更新im_info中的augment_shape信息。
+    3. 更新im_info中的image_shape信息。
 
     Args:
         alpha (float): 随机beta分布的下限。默认为1.5。
@@ -630,7 +624,7 @@ class MixupImage:
                    当label_info不为空时,返回的tuple为(im, im_info, label_info),分别对应图像np.ndarray数据、
                    存储与标注框相关信息的字典。
                    其中,im_info更新字段为:
-                       - augment_shape (np.ndarray): mixup后的图像高、宽二者组成的np.ndarray,形状为(2,)。
+                       - image_shape (np.ndarray): mixup后的图像高、宽二者组成的np.ndarray,形状为(2,)。
                    im_info删除的字段:
                        - mixup (list): 与当前字段进行mixup的图像相关信息。
                    label_info更新字段为:
@@ -694,8 +688,8 @@ class MixupImage:
         label_info['gt_score'] = gt_score
         label_info['gt_class'] = gt_class
         label_info['is_crowd'] = is_crowd
-        im_info['augment_shape'] = np.array([im.shape[0],
-                                             im.shape[1]]).astype('int32')
+        im_info['image_shape'] = np.array([im.shape[0],
+                                           im.shape[1]]).astype('int32')
         im_info.pop('mixup')
         if label_info is None:
             return (im, im_info)
@@ -741,7 +735,7 @@ class RandomExpand:
                    当label_info不为空时,返回的tuple为(im, im_info, label_info),分别对应图像np.ndarray数据、
                    存储与标注框相关信息的字典。
                    其中,im_info更新字段为:
-                       - augment_shape (np.ndarray): 扩张后的图像高、宽二者组成的np.ndarray,形状为(2,)。
+                       - image_shape (np.ndarray): 扩张后的图像高、宽二者组成的np.ndarray,形状为(2,)。
                    label_info更新字段为:
                        - gt_bbox (np.ndarray): 随机扩张后真实标注框坐标,形状为(n, 4),
                                           其中n代表真实标注框的个数。
@@ -754,9 +748,6 @@ class RandomExpand:
             raise TypeError(
                 'Cannot do RandomExpand! ' +
                 'Becasuse the im_info and label_info can not be None!')
-        if 'augment_shape' not in im_info:
-            raise TypeError('Cannot do RandomExpand! ' + \
-                            'Becasuse augment_shape is not in im_info!')
         if 'gt_bbox' not in label_info or \
                 'gt_class' not in label_info:
             raise TypeError('Cannot do RandomExpand! ' + \
@@ -764,9 +755,9 @@ class RandomExpand:
         if np.random.uniform(0., 1.) < self.prob:
             return (im, im_info, label_info)
 
-        augment_shape = im_info['augment_shape']
-        height = int(augment_shape[0])
-        width = int(augment_shape[1])
+        image_shape = im_info['image_shape']
+        height = int(image_shape[0])
+        width = int(image_shape[1])
 
         expand_ratio = np.random.uniform(1., self.ratio)
         h = int(height * expand_ratio)
@@ -779,7 +770,7 @@ class RandomExpand:
         canvas *= np.array(self.fill_value, dtype=np.float32)
         canvas[y:y + height, x:x + width, :] = im
 
-        im_info['augment_shape'] = np.array([h, w]).astype('int32')
+        im_info['image_shape'] = np.array([h, w]).astype('int32')
         if 'gt_bbox' in label_info and len(label_info['gt_bbox']) > 0:
             label_info['gt_bbox'] += np.array([x, y] * 2, dtype=np.float32)
         if 'gt_poly' in label_info and len(label_info['gt_poly']) > 0:
@@ -835,12 +826,14 @@ class RandomCrop:
             tuple: 当label_info为空时,返回的tuple为(im, im_info),分别对应图像np.ndarray数据、存储与图像相关信息的字典;
                    当label_info不为空时,返回的tuple为(im, im_info, label_info),分别对应图像np.ndarray数据、
                    存储与标注框相关信息的字典。
-                   其中,label_info更新字段为:
-                       - gt_bbox (np.ndarray): 随机裁剪后真实标注框坐标,形状为(n, 4),
+                   其中,im_info更新字段为:
+                           - image_shape (np.ndarray): 扩裁剪的图像高、宽二者组成的np.ndarray,形状为(2,)。
+                       label_info更新字段为:
+                           - gt_bbox (np.ndarray): 随机裁剪后真实标注框坐标,形状为(n, 4),
                                           其中n代表真实标注框的个数。
-                       - gt_class (np.ndarray): 随机裁剪后每个真实标注框对应的类别序号,形状为(n, 1),
+                           - gt_class (np.ndarray): 随机裁剪后每个真实标注框对应的类别序号,形状为(n, 1),
                                            其中n代表真实标注框的个数。
-                       - gt_score (np.ndarray): 随机裁剪后每个真实标注框对应的混合得分,形状为(n, 1),
+                           - gt_score (np.ndarray): 随机裁剪后每个真实标注框对应的混合得分,形状为(n, 1),
                                            其中n代表真实标注框的个数。
 
         Raises:
@@ -850,9 +843,6 @@ class RandomCrop:
             raise TypeError(
                 'Cannot do RandomCrop! ' +
                 'Becasuse the im_info and label_info can not be None!')
-        if 'augment_shape' not in im_info:
-            raise TypeError('Cannot do RandomCrop! ' + \
-                            'Becasuse augment_shape is not in im_info!')
         if 'gt_bbox' not in label_info or \
                 'gt_class' not in label_info:
             raise TypeError('Cannot do RandomCrop! ' + \
@@ -861,9 +851,9 @@ class RandomCrop:
         if len(label_info['gt_bbox']) == 0:
             return (im, im_info, label_info)
 
-        augment_shape = im_info['augment_shape']
-        w = augment_shape[1]
-        h = augment_shape[0]
+        image_shape = im_info['image_shape']
+        w = image_shape[1]
+        h = image_shape[0]
         gt_bbox = label_info['gt_bbox']
         thresholds = list(self.thresholds)
         if self.allow_no_crop:
@@ -922,7 +912,7 @@ class RandomCrop:
                 label_info['gt_bbox'] = np.take(cropped_box, valid_ids, axis=0)
                 label_info['gt_class'] = np.take(
                     label_info['gt_class'], valid_ids, axis=0)
-                im_info['augment_shape'] = np.array(
+                im_info['image_shape'] = np.array(
                     [crop_box[3] - crop_box[1],
                      crop_box[2] - crop_box[0]]).astype('int32')
                 if 'gt_score' in label_info:
@@ -993,7 +983,7 @@ class ArrangeFasterRCNN:
             im_resize_info = im_info['im_resize_info']
             im_id = im_info['im_id']
             im_shape = np.array(
-                (im_info['augment_shape'][0], im_info['augment_shape'][1], 1),
+                (im_info['image_shape'][0], im_info['image_shape'][1], 1),
                 dtype=np.float32)
             gt_bbox = label_info['gt_bbox']
             gt_class = label_info['gt_class']
@@ -1006,7 +996,7 @@ class ArrangeFasterRCNN:
                                 'Becasuse the im_info can not be None!')
             im_resize_info = im_info['im_resize_info']
             im_shape = np.array(
-                (im_info['augment_shape'][0], im_info['augment_shape'][1], 1),
+                (im_info['image_shape'][0], im_info['image_shape'][1], 1),
                 dtype=np.float32)
             outputs = (im, im_resize_info, im_shape)
         return outputs
@@ -1086,7 +1076,7 @@ class ArrangeMaskRCNN:
                                 'Becasuse the im_info can not be None!')
             im_resize_info = im_info['im_resize_info']
             im_shape = np.array(
-                (im_info['augment_shape'][0], im_info['augment_shape'][1], 1),
+                (im_info['image_shape'][0], im_info['image_shape'][1], 1),
                 dtype=np.float32)
             if self.mode == 'eval':
                 im_id = im_info['im_id']
@@ -1137,7 +1127,7 @@ class ArrangeYOLOv3:
                 raise TypeError(
                     'Cannot do ArrangeYolov3! ' +
                     'Becasuse the im_info and label_info can not be None!')
-            im_shape = im_info['augment_shape']
+            im_shape = im_info['image_shape']
             if len(label_info['gt_bbox']) != len(label_info['gt_class']):
                 raise ValueError("gt num mismatch: bbox and class.")
             if len(label_info['gt_bbox']) != len(label_info['gt_score']):
@@ -1161,7 +1151,7 @@ class ArrangeYOLOv3:
                 raise TypeError(
                     'Cannot do ArrangeYolov3! ' +
                     'Becasuse the im_info and label_info can not be None!')
-            im_shape = im_info['augment_shape']
+            im_shape = im_info['image_shape']
             if len(label_info['gt_bbox']) != len(label_info['gt_class']):
                 raise ValueError("gt num mismatch: bbox and class.")
             im_id = im_info['im_id']
@@ -1180,6 +1170,6 @@ class ArrangeYOLOv3:
             if im_info is None:
                 raise TypeError('Cannot do ArrangeYolov3! ' +
                                 'Becasuse the im_info can not be None!')
-            im_shape = im_info['augment_shape']
+            im_shape = im_info['image_shape']
             outputs = (im, im_shape)
         return outputs

+ 2 - 2
paddlex/cv/transforms/seg_transforms.py

@@ -66,8 +66,8 @@ class Compose:
         if self.to_rgb:
             im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
         if label is not None:
-            label = np.asarray(Image.open(label))
-
+            if not isinstance(label, np.ndarray):
+                label = np.asarray(Image.open(label))
         for op in self.transforms:
             outputs = op(im, im_info, label)
             im = outputs[0]

+ 36 - 0
paddlex/utils/utils.py

@@ -220,3 +220,39 @@ def load_pretrain_weights(exe, main_prog, weights_dir, fuse_bn=False):
             len(vars_to_load), weights_dir))
     if fuse_bn:
         fuse_bn_weights(exe, main_prog, weights_dir)
+
+
+class EarlyStop:
+    def __init__(self, patience, thresh):
+        self.patience = patience
+        self.counter = 0
+        self.score = None
+        self.max = 0
+        self.thresh = thresh
+        if patience < 1:
+            raise Exception("Argument patience should be a positive integer.")
+
+    def __call__(self, current_score):
+        if self.score is None:
+            self.score = current_score
+            return False
+        elif current_score > self.max:
+            self.counter = 0
+            self.score = current_score
+            self.max = current_score
+            return False
+        else:
+            if (abs(self.score - current_score) < self.thresh
+                    or current_score < self.score):
+                self.counter += 1
+                self.score = current_score
+                logging.debug(
+                    "EarlyStopping: %i / %i" % (self.counter, self.patience))
+                if self.counter >= self.patience:
+                    logging.info("EarlyStopping: Stop training")
+                    return True
+                return False
+            else:
+                self.counter = 0
+                self.score = current_score
+                return False

+ 2 - 1
setup.py

@@ -29,7 +29,8 @@ setuptools.setup(
     packages=setuptools.find_packages(),
     setup_requires=['cython', 'numpy', 'sklearn'],
     install_requires=[
-        'pycocotools', 'pyyaml', 'colorama', 'tqdm', 'visualdl==1.3.0',
+        "pycocotools;platform_system!='Windows'", 
+        'pyyaml', 'colorama', 'tqdm', 'visualdl==1.3.0',
         'paddleslim==1.0.1', 'paddlehub>=1.6.2'
     ],
     classifiers=[