diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3160afd098e664d02a20d7a68f58351b0363c011 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,3 @@ +# 模型部署 + +本目录为PaddleX模型部署代码。 diff --git a/docs/apis/datasets.md b/docs/apis/datasets.md index f715ab58aaef90d0317332aba5869e15993f9920..b40637f2aef586c79bb92b5bce3c5deb4b00893e 100644 --- a/docs/apis/datasets.md +++ b/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。 \ No newline at end of file diff --git a/docs/apis/images/insect_bbox_pr_curve(iou-0.5).png b/docs/apis/images/insect_bbox_pr_curve(iou-0.5).png index 081c4225316c06eeeacb25b0323b02d366e8e453..87c41aeab79779eb5355ea5d2e9be41087eb9006 100644 Binary files a/docs/apis/images/insect_bbox_pr_curve(iou-0.5).png and b/docs/apis/images/insect_bbox_pr_curve(iou-0.5).png differ diff --git a/docs/apis/images/xiaoduxiong_bbox_pr_curve(iou-0.5).png b/docs/apis/images/xiaoduxiong_bbox_pr_curve(iou-0.5).png deleted file mode 100644 index 990ebcd184232647efed7ba4b22dfcdfca75fe1b..0000000000000000000000000000000000000000 Binary files a/docs/apis/images/xiaoduxiong_bbox_pr_curve(iou-0.5).png and /dev/null differ diff --git a/docs/apis/images/xiaoduxiong_segm_pr_curve(iou-0.5).png b/docs/apis/images/xiaoduxiong_segm_pr_curve(iou-0.5).png deleted file mode 100644 index 55f5dd4032725d97b2439a8b1708f42d4f13693a..0000000000000000000000000000000000000000 Binary files a/docs/apis/images/xiaoduxiong_segm_pr_curve(iou-0.5).png and /dev/null differ diff --git a/docs/apis/load_model.md b/docs/apis/load_model.md index 9a32ca613cb61c72aea0d1822c7fba6f26846472..0e460ee583c8bb4030d202372dae40882b7b34dd 100644 --- a/docs/apis/load_model.md +++ b/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) ``` diff --git a/docs/apis/models.md b/docs/apis/models.md index ad098ca71d5588439e753f42b1ada1f3756e8f22..e0a3547ea194cb4ca155fc3be60004a7452e1f6c 100644 --- a/docs/apis/models.md +++ b/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评估函数接口 diff --git a/docs/apis/transforms/cls_transforms.md b/docs/apis/transforms/cls_transforms.md index f4ec32ca16e6a78fbc65ca17ac1ae7f47b774d1e..ff6a9ebff33a9938ef439dd881d5ba9c492da479 100644 --- a/docs/apis/transforms/cls_transforms.md +++ b/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。 diff --git a/docs/apis/transforms/det_transforms.md b/docs/apis/transforms/det_transforms.md index fdb1aa0e815d50b03f2373a9f8d0221ff0ac3507..21212cedec6a88483393d7232583e87ccf749dc4 100644 --- a/docs/apis/transforms/det_transforms.md +++ b/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.]。 diff --git a/docs/apis/transforms/seg_transforms.md b/docs/apis/transforms/seg_transforms.md index 318026e43619c82668ed20f8d1de5e5d806a3c05..d339f8c2048013cc73ac8d55984d5e6e5c98c20b 100644 --- a/docs/apis/transforms/seg_transforms.md +++ b/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。 diff --git a/docs/apis/visualize.md b/docs/apis/visualize.md index bedad074101a801d4800577ac21e606c6c0a03fc..1138115ea89ed32b59af623517c64676c62184dd 100644 --- a/docs/apis/visualize.md +++ b/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) diff --git a/docs/datasets.md b/docs/datasets.md index 3eb82c28a903927f56139b3bb8069f36b5cea1cd..b197b43b6c1ce2dd8c91bae3c484573365493ba0 100644 --- a/docs/datasets.md +++ b/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 表中模型相关指标均为在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| diff --git a/paddlex/__init__.py b/paddlex/__init__.py index 14e56b432f6d3a86dbfd787f1b171229b3eb74e7..a0b333f1862cebcce4cac06b4baf238fe0aa83b5 100644 --- a/paddlex/__init__.py +++ b/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 diff --git a/paddlex/cv/datasets/__init__.py b/paddlex/cv/datasets/__init__.py index c686e4539e1de67187822c130b37dae998ac049b..b8bf8f9c3fc0355a02a1802db706546c2f6fa4b7 100644 --- a/paddlex/cv/datasets/__init__.py +++ b/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 \ No newline at end of file diff --git a/paddlex/cv/datasets/coco.py b/paddlex/cv/datasets/coco.py index 52a3336ce60334bd95fa0d84d1adfb164dfb686b..e2d3ac4e7d4525071e884bdbdea157b406ecd86e 100644 --- a/paddlex/cv/datasets/coco.py +++ b/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, diff --git a/paddlex/cv/datasets/easydata_cls.py b/paddlex/cv/datasets/easydata_cls.py new file mode 100644 index 0000000000000000000000000000000000000000..baff5d9111ab5484f8f0199ba399c55d6c6f3376 --- /dev/null +++ b/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)) + \ No newline at end of file diff --git a/paddlex/cv/datasets/easydata_det.py b/paddlex/cv/datasets/easydata_det.py new file mode 100644 index 0000000000000000000000000000000000000000..173720f0d5df56eb553b5e4a828962e0554403ad --- /dev/null +++ b/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 \ No newline at end of file diff --git a/paddlex/cv/datasets/easydata_seg.py b/paddlex/cv/datasets/easydata_seg.py new file mode 100644 index 0000000000000000000000000000000000000000..4988248d291accc0469494d0f00e16440784ae7a --- /dev/null +++ b/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 diff --git a/paddlex/cv/datasets/imagenet.py b/paddlex/cv/datasets/imagenet.py index 06ca1cedae8bc5e0bf67c6068cd8b75376f87bfd..99723d3b8f4ec6f8c0b9297f9fe66c1fbc60693f 100644 --- a/paddlex/cv/datasets/imagenet.py +++ b/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。 """ diff --git a/paddlex/cv/datasets/seg_dataset.py b/paddlex/cv/datasets/seg_dataset.py index 2c56c5d001135abe90415444baf17877012f9541..61697e3d799ccb0ca765410a81e7257741acfb44 100644 --- a/paddlex/cv/datasets/seg_dataset.py +++ b/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。 """ diff --git a/paddlex/cv/datasets/voc.py b/paddlex/cv/datasets/voc.py index 6ab985fed760001f06d499987baf5d5c6b4dd049..3a4c9ae9e4868adb24e1ae8ca24f978bdc406e98 100644 --- a/paddlex/cv/datasets/voc.py +++ b/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, diff --git a/paddlex/cv/models/base.py b/paddlex/cv/models/base.py index 7cb39b88280c04064db0dfbb0500d99443aaa66c..a61209c15f2a95ab0a47aaeb5119e29b8c4cc475 100644 --- a/paddlex/cv/models/base.py +++ b/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 diff --git a/paddlex/cv/models/classifier.py b/paddlex/cv/models/classifier.py index 88a15095b3c8ad8475ca5955f2de5649be32da0d..49a1f0fbd720c88b081ddd20b620d4bb01f76a24 100644 --- a/paddlex/cv/models/classifier.py +++ b/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, diff --git a/paddlex/cv/models/deeplabv3p.py b/paddlex/cv/models/deeplabv3p.py index 3091f58a7017c8644acb718d595dae61e07ee5f6..7720da6d88e0cd3e267455eb6777e44f04244bbd 100644 --- a/paddlex/cv/models/deeplabv3p.py +++ b/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, diff --git a/paddlex/cv/models/faster_rcnn.py b/paddlex/cv/models/faster_rcnn.py index c689bc65badce67d2168f73d957da325afca0c72..f5b7e94e9c3d43669ea77906c33be2b4f164cc99 100644 --- a/paddlex/cv/models/faster_rcnn.py +++ b/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, diff --git a/paddlex/cv/models/mask_rcnn.py b/paddlex/cv/models/mask_rcnn.py index 7956dc7e9a48f6d0300db80e7331151e795e2c32..77b2bd368036aa02c1a9937aaa10c021c9e2f7f0 100644 --- a/paddlex/cv/models/mask_rcnn.py +++ b/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, diff --git a/paddlex/cv/models/slim/visualize.py b/paddlex/cv/models/slim/visualize.py index 5fcbad9865c3a356ef514098fa236112a1cb3169..d9380abb2f1184cfe59d77b84d6841b5c4fd7288 100644 --- a/paddlex/cv/models/slim/visualize.py +++ b/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() diff --git a/paddlex/cv/models/unet.py b/paddlex/cv/models/unet.py index fffb842d1b9fe8e63af2a303ae04f1609fd0d3c1..d7ce60c6fb8cc520691d8760f4193ac6ccffdbb6 100644 --- a/paddlex/cv/models/unet.py +++ b/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) diff --git a/paddlex/cv/models/utils/visualize.py b/paddlex/cv/models/utils/visualize.py index 38de5b41aa6658e2ccf7e8c0fec7e8f5a291ecc5..07a705be653fcde1846d5d7aecea825f8b782014 100644 --- a/paddlex/cv/models/utils/visualize.py +++ b/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() diff --git a/paddlex/cv/models/yolo_v3.py b/paddlex/cv/models/yolo_v3.py index 7821997e3b6c536a7b654ab29a242b76dcdf2d46..a2055543e8e00f5b97161d7f6a57e1a94acafad7 100644 --- a/paddlex/cv/models/yolo_v3.py +++ b/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, diff --git a/paddlex/cv/transforms/det_transforms.py b/paddlex/cv/transforms/det_transforms.py index fb66a78d64917e384c8241bd57cf50b1bf0c63cf..5b350d134f5a2ef4ed5d4bbebe6f9ce9d8014818 100644 --- a/paddlex/cv/transforms/det_transforms.py +++ b/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 diff --git a/paddlex/cv/transforms/seg_transforms.py b/paddlex/cv/transforms/seg_transforms.py index db887c70ad8d7a6aadd0212342c31252c8baf8a0..78ab4cf4dc941a853482cf09b05a7c23083cc384 100644 --- a/paddlex/cv/transforms/seg_transforms.py +++ b/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] diff --git a/paddlex/utils/utils.py b/paddlex/utils/utils.py index 27dc6dea16b24085e286f2948907310c6b4ff4de..c9d23115ed74ff38f5395b4b04d1eb45a98486ad 100644 --- a/paddlex/utils/utils.py +++ b/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 diff --git a/setup.py b/setup.py index db8c2a8a17420bc56f98d528a406284885d14df9..99b91b86b1442a23515bfc93c326ec1498d67e27 100644 --- a/setup.py +++ b/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=[