Browse Source

[Feature] Fix ppcls and test pass

geoyee 3 năm trước cách đây
mục cha
commit
7f88bd8e69

+ 1 - 0
.gitignore

@@ -131,6 +131,7 @@ dmypy.json
 
 # testdata
 tutorials/train/change_detection/DataSet/
+tutorials/train/classification/DataSet/
 optic_disc_seg.tar
 optic_disc_seg/
 output/

+ 2 - 1
paddlers/datasets/clas_dataset.py

@@ -25,7 +25,7 @@ class ClasDataset(Dataset):
     Args:
         data_dir (str): 数据集所在的目录路径。
         file_list (str): 描述数据集图片文件和对应标注序号(文本内每行路径为相对data_dir的相对路)。
-        label_list (str): 描述数据集包含的类别信息文件路径。默认值为None。
+        label_list (str): 描述数据集包含的类别信息文件路径,文件格式为(类别 说明)。默认值为None。
         transforms (paddlers.transforms): 数据集中每个样本的预处理/增强算子。
         num_workers (int|str): 数据集中样本在预处理过程中的线程或进程数。默认为'auto'。
         shuffle (bool): 是否需要对数据集中样本打乱顺序。默认为False。
@@ -45,6 +45,7 @@ class ClasDataset(Dataset):
         self.num_workers = get_num_workers(num_workers)
         self.shuffle = shuffle
         self.file_list = list()
+        self.label_list = label_list
         self.labels = list()
 
         # TODO:非None时,让用户跳转数据集分析生成label_list

+ 1 - 1
paddlers/models/ppcls/loss/__init__.py

@@ -63,5 +63,5 @@ class CombinedLoss(nn.Layer):
 
 def build_loss(config):
     module_class = CombinedLoss(copy.deepcopy(config))
-    logger.debug("build loss {} success.".format(module_class))
+    # logger.debug("build loss {} success.".format(module_class))
     return module_class

+ 66 - 238
paddlers/tasks/classifier.py

@@ -15,7 +15,6 @@
 import math
 import os.path as osp
 import numpy as np
-import cv2
 from collections import OrderedDict
 import paddle
 import paddle.nn.functional as F
@@ -26,8 +25,10 @@ from paddlers.transforms import arrange_transforms
 from paddlers.utils import get_single_card_bs, DisablePrint
 import paddlers.utils.logging as logging
 from .base import BaseModel
-from .utils import seg_metrics as metrics
-from paddlers.utils.checkpoint import seg_pretrain_weights_dict
+from paddlers.models.ppcls.metric import build_metrics
+from paddlers.models.ppcls.loss import build_loss
+from paddlers.models.ppcls.data.postprocess import build_postprocess
+from paddlers.utils.checkpoint import imagenet_weights
 from paddlers.transforms import Decode, Resize
 
 __all__ = ["ResNet50_vd", "MobileNetV3_small_x1_0", "HRNet_W18_C"]
@@ -49,8 +50,10 @@ class BaseClassifier(BaseModel):
         self.model_name = model_name
         self.num_classes = num_classes
         self.use_mixed_loss = use_mixed_loss
+        self.metrics = None
         self.losses = None
         self.labels = None
+        self._postprocess = None
         if params.get('with_net', True):
             params.pop('with_net', None)
             self.net = self.build_net(**params)
@@ -97,95 +100,35 @@ class BaseClassifier(BaseModel):
         ]
         return input_spec
 
-    # FIXME: use ppcls instead of ppseg, in infet / metrics and etc.
     def run(self, net, inputs, mode):
         net_out = net(inputs[0])
-        logit = net_out[0]
+        label = paddle.to_tensor(inputs[1], dtype="int64")
         outputs = OrderedDict()
         if mode == 'test':
-            origin_shape = inputs[1]
-            if self.status == 'Infer':
-                label_map_list, score_map_list = self._postprocess(
-                    net_out, origin_shape, transforms=inputs[2])
-            else:
-                logit_list = self._postprocess(
-                    logit, origin_shape, transforms=inputs[2])
-                label_map_list = []
-                score_map_list = []
-                for logit in logit_list:
-                    logit = paddle.transpose(logit, perm=[0, 2, 3, 1])  # NHWC
-                    label_map_list.append(
-                        paddle.argmax(
-                            logit, axis=-1, keepdim=False, dtype='int32')
-                        .squeeze().numpy())
-                    score_map_list.append(
-                        F.softmax(
-                            logit, axis=-1).squeeze().numpy().astype(
-                                'float32'))
-            outputs['label_map'] = label_map_list
-            outputs['score_map'] = score_map_list
+            result = self._postprocess(net_out)
+            outputs = result[0]
 
         if mode == 'eval':
-            if self.status == 'Infer':
-                pred = paddle.unsqueeze(net_out[0], axis=1)  # NCHW
-            else:
-                pred = paddle.argmax(
-                    logit, axis=1, keepdim=True, dtype='int32')
-            label = inputs[1]
-            origin_shape = [label.shape[-2:]]
-            pred = self._postprocess(
-                pred, origin_shape, transforms=inputs[2])[0]  # NCHW
-            intersect_area, pred_area, label_area = paddleseg.utils.metrics.calculate_area(
-                pred, label, self.num_classes)
-            outputs['intersect_area'] = intersect_area
-            outputs['pred_area'] = pred_area
-            outputs['label_area'] = label_area
-            outputs['conf_mat'] = metrics.confusion_matrix(pred, label,
-                                                           self.num_classes)
+            # print(self._postprocess(net_out)[0])  # for test
+            label = paddle.unsqueeze(label, axis=-1)
+            metric_dict = self.metrics(net_out, label)
+            outputs['top1'] = metric_dict["top1"]
+            outputs['top5'] = metric_dict["top5"]
+
         if mode == 'train':
-            loss_list = metrics.loss_computation(
-                logits_list=net_out, labels=inputs[1], losses=self.losses)
-            loss = sum(loss_list)
-            outputs['loss'] = loss
+            loss_list = self.losses(net_out, label)
+            outputs['loss'] = loss_list['loss']
         return outputs
 
-    # FIXME: use ppcls instead of ppseg, in loss.
+    def default_metric(self):
+        # TODO: other metrics
+        default_config = [{"TopkAcc":{"topk": [1, 5]}}]
+        return build_metrics(default_config) 
+
     def default_loss(self):
-        if isinstance(self.use_mixed_loss, bool):
-            if self.use_mixed_loss:
-                losses = [
-                    paddleseg.models.CrossEntropyLoss(),
-                    paddleseg.models.LovaszSoftmaxLoss()
-                ]
-                coef = [.8, .2]
-                loss_type = [
-                    paddleseg.models.MixedLoss(
-                        losses=losses, coef=coef),
-                ]
-            else:
-                loss_type = [paddleseg.models.CrossEntropyLoss()]
-        else:
-            losses, coef = list(zip(*self.use_mixed_loss))
-            if not set(losses).issubset(
-                ['CrossEntropyLoss', 'DiceLoss', 'LovaszSoftmaxLoss']):
-                raise ValueError(
-                    "Only 'CrossEntropyLoss', 'DiceLoss', 'LovaszSoftmaxLoss' are supported."
-                )
-            losses = [getattr(paddleseg.models, loss)() for loss in losses]
-            loss_type = [
-                paddleseg.models.MixedLoss(
-                    losses=losses, coef=list(coef))
-            ]
-        if self.model_name == 'FastSCNN':
-            loss_type *= 2
-            loss_coef = [1.0, 0.4]
-        elif self.model_name == 'BiSeNetV2':
-            loss_type *= 5
-            loss_coef = [1.0] * 5
-        else:
-            loss_coef = [1.0]
-        losses = {'types': loss_type, 'coef': loss_coef}
-        return losses
+        # TODO: mixed_loss
+        default_config = [{"CELoss":{"weight": 1.0}}]
+        return build_loss(default_config)
 
     def default_optimizer(self,
                           parameters,
@@ -203,6 +146,14 @@ class BaseClassifier(BaseModel):
             weight_decay=4e-5)
         return optimizer
 
+    def default_postprocess(self, class_id_map_file):
+        default_config = {
+            "name": "Topk",
+            "topk": 1,
+            "class_id_map_file": class_id_map_file
+        }
+        return build_postprocess(default_config)
+
     def train(self,
               num_epochs,
               train_dataset,
@@ -212,7 +163,7 @@ class BaseClassifier(BaseModel):
               save_interval_epochs=1,
               log_interval_steps=2,
               save_dir='output',
-              pretrain_weights='CITYSCAPES',  # FIXME: fix clas's pretrain weights
+              pretrain_weights='IMAGENET',
               learning_rate=0.01,
               lr_decay_power=0.9,
               early_stop=False,
@@ -255,6 +206,9 @@ class BaseClassifier(BaseModel):
         self.labels = train_dataset.labels
         if self.losses is None:
             self.losses = self.default_loss()
+        self.metrics = self.default_metric()
+        self._postprocess = self.default_postprocess(train_dataset.label_list)
+        # print(self._postprocess.class_id_map)
 
         if optimizer is None:
             num_steps_each_epoch = train_dataset.num_samples // train_batch_size
@@ -265,7 +219,7 @@ class BaseClassifier(BaseModel):
             self.optimizer = optimizer
 
         if pretrain_weights is not None and not osp.exists(pretrain_weights):
-            if pretrain_weights not in seg_pretrain_weights_dict[
+            if pretrain_weights not in imagenet_weights[
                     self.model_name]:
                 logging.warning(
                     "Path of pretrain_weights('{}') does not exist!".format(
@@ -273,9 +227,9 @@ class BaseClassifier(BaseModel):
                 logging.warning("Pretrain_weights is forcibly set to '{}'. "
                                 "If don't want to use pretrain weights, "
                                 "set pretrain_weights to be None.".format(
-                                    seg_pretrain_weights_dict[self.model_name][
+                                    imagenet_weights[self.model_name][
                                         0]))
-                pretrain_weights = seg_pretrain_weights_dict[self.model_name][
+                pretrain_weights = imagenet_weights[self.model_name][
                     0]
         elif pretrain_weights is not None and osp.exists(pretrain_weights):
             if osp.splitext(pretrain_weights)[-1] != '.pdparams':
@@ -370,12 +324,8 @@ class BaseClassifier(BaseModel):
 
         Returns:
             collections.OrderedDict with key-value pairs:
-                {"miou": `mean intersection over union`,
-                 "category_iou": `category-wise mean intersection over union`,
-                 "oacc": `overall accuracy`,
-                 "category_acc": `category-wise accuracy`,
-                 "kappa": ` kappa coefficient`,
-                 "category_F1-score": `F1 score`}.
+                {"top1": `acc of top1`,
+                 "top5": `acc of top5`}.
 
         """
         arrange_transforms(
@@ -403,73 +353,26 @@ class BaseClassifier(BaseModel):
         self.eval_data_loader = self.build_data_loader(
             eval_dataset, batch_size=batch_size, mode='eval')
 
-        intersect_area_all = 0
-        pred_area_all = 0
-        label_area_all = 0
-        conf_mat_all = []
         logging.info(
             "Start to evaluate(total_samples={}, total_steps={})...".format(
                 eval_dataset.num_samples,
                 math.ceil(eval_dataset.num_samples * 1.0 / batch_size)))
+
+        top1s = []
+        top5s = []
         with paddle.no_grad():
             for step, data in enumerate(self.eval_data_loader):
                 data.append(eval_dataset.transforms.transforms)
                 outputs = self.run(self.net, data, 'eval')
-                pred_area = outputs['pred_area']
-                label_area = outputs['label_area']
-                intersect_area = outputs['intersect_area']
-                conf_mat = outputs['conf_mat']
-
-                # Gather from all ranks
-                if nranks > 1:
-                    intersect_area_list = []
-                    pred_area_list = []
-                    label_area_list = []
-                    conf_mat_list = []
-                    paddle.distributed.all_gather(intersect_area_list,
-                                                  intersect_area)
-                    paddle.distributed.all_gather(pred_area_list, pred_area)
-                    paddle.distributed.all_gather(label_area_list, label_area)
-                    paddle.distributed.all_gather(conf_mat_list, conf_mat)
-
-                    # Some image has been evaluated and should be eliminated in last iter
-                    if (step + 1) * nranks > len(eval_dataset):
-                        valid = len(eval_dataset) - step * nranks
-                        intersect_area_list = intersect_area_list[:valid]
-                        pred_area_list = pred_area_list[:valid]
-                        label_area_list = label_area_list[:valid]
-                        conf_mat_list = conf_mat_list[:valid]
-
-                    intersect_area_all += sum(intersect_area_list)
-                    pred_area_all += sum(pred_area_list)
-                    label_area_all += sum(label_area_list)
-                    conf_mat_all.extend(conf_mat_list)
-
-                else:
-                    intersect_area_all = intersect_area_all + intersect_area
-                    pred_area_all = pred_area_all + pred_area
-                    label_area_all = label_area_all + label_area
-                    conf_mat_all.append(conf_mat)
-        # FIXME: fix metrics
-        class_iou, miou = paddleseg.utils.metrics.mean_iou(
-            intersect_area_all, pred_area_all, label_area_all)
-        # TODO 确认是按oacc还是macc
-        class_acc, oacc = paddleseg.utils.metrics.accuracy(intersect_area_all,
-                                                           pred_area_all)
-        kappa = paddleseg.utils.metrics.kappa(intersect_area_all,
-                                              pred_area_all, label_area_all)
-        category_f1score = metrics.f1_score(intersect_area_all, pred_area_all,
-                                            label_area_all)
-        eval_metrics = OrderedDict(
-            zip([
-                'miou', 'category_iou', 'oacc', 'category_acc', 'kappa',
-                'category_F1-score'
-            ], [miou, class_iou, oacc, class_acc, kappa, category_f1score]))
+                top1s.append(outputs["top1"])
+                top5s.append(outputs["top5"])
 
+        top1 = np.mean(top1s)
+        top5 = np.mean(top5s)
+        eval_metrics = OrderedDict(zip(['top1', 'top5'], [top1, top5]))
         if return_details:
-            conf_mat = sum(conf_mat_all)
-            eval_details = {'confusion_matrix': conf_mat.tolist()}
-            return eval_metrics, eval_details
+            # TODO: add details
+            return eval_metrics, None
         return eval_metrics
 
     def predict(self, img_file, transforms=None):
@@ -485,10 +388,11 @@ class BaseClassifier(BaseModel):
 
         Returns:
             If img_file is a string or np.array, the result is a dict with key-value pairs:
-            {"label map": `label map`, "score_map": `score map`}.
+            {"label map": `class_ids_map`, "scores_map": `label_names_map`}.
             If img_file is a list, the result is a list composed of dicts with the corresponding fields:
-            label_map(np.ndarray): the predicted label map (HW)
-            score_map(np.ndarray): the prediction score map (HWC)
+            class_ids_map(np.ndarray): class_ids
+            scores_map(np.ndarray): scores
+            label_names_map(np.ndarray): label_names
 
         """
         if transforms is None and not hasattr(self, 'test_transforms'):
@@ -504,21 +408,23 @@ class BaseClassifier(BaseModel):
         self.net.eval()
         data = (batch_im, batch_origin_shape, transforms.transforms)
         outputs = self.run(self.net, data, 'test')
-        label_map_list = outputs['label_map']
-        score_map_list = outputs['score_map']
+        label_list = outputs['class_ids']
+        score_list = outputs['scores']
+        name_list = outputs['label_names']
         if isinstance(img_file, list):
             prediction = [{
-                'label_map': l,
-                'score_map': s
-            } for l, s in zip(label_map_list, score_map_list)]
+                'class_ids_map': l,
+                'scores_map': s, 
+                'label_names_map': n, 
+            } for l, s, n in zip(label_list, score_list, name_list)]
         else:
             prediction = {
-                'label_map': label_map_list[0],
-                'score_map': score_map_list[0]
+                'class_ids': label_list[0],
+                'scores': score_list[0],
+                'label_names': name_list[0]
             }
         return prediction
 
-    # FIXME: adaptive clas
     def _preprocess(self, images, transforms, to_tensor=True):
         arrange_transforms(
             model_type=self.model_type, transforms=transforms, mode='test')
@@ -587,84 +493,6 @@ class BaseClassifier(BaseModel):
             batch_restore_list.append(restore_list)
         return batch_restore_list
 
-    # FIXME: adaptive clas
-    def _postprocess(self, batch_pred, batch_origin_shape, transforms):
-        batch_restore_list = BaseClassifier.get_transforms_shape_info(
-            batch_origin_shape, transforms)
-        if isinstance(batch_pred, (tuple, list)) and self.status == 'Infer':
-            return self._infer_postprocess(
-                batch_label_map=batch_pred[0],
-                batch_score_map=batch_pred[1],
-                batch_restore_list=batch_restore_list)
-        results = []
-        if batch_pred.dtype == paddle.float32:
-            mode = 'bilinear'
-        else:
-            mode = 'nearest'
-        for pred, restore_list in zip(batch_pred, batch_restore_list):
-            pred = paddle.unsqueeze(pred, axis=0)
-            for item in restore_list[::-1]:
-                h, w = item[1][0], item[1][1]
-                if item[0] == 'resize':
-                    pred = F.interpolate(
-                        pred, (h, w), mode=mode, data_format='NCHW')
-                elif item[0] == 'padding':
-                    x, y = item[2]
-                    pred = pred[:, :, y:y + h, x:x + w]
-                else:
-                    pass
-            results.append(pred)
-        return results
-
-    # FIXME: adaptive clas
-    def _infer_postprocess(self, batch_label_map, batch_score_map,
-                           batch_restore_list):
-        label_maps = []
-        score_maps = []
-        for label_map, score_map, restore_list in zip(
-                batch_label_map, batch_score_map, batch_restore_list):
-            if not isinstance(label_map, np.ndarray):
-                label_map = paddle.unsqueeze(label_map, axis=[0, 3])
-                score_map = paddle.unsqueeze(score_map, axis=0)
-            for item in restore_list[::-1]:
-                h, w = item[1][0], item[1][1]
-                if item[0] == 'resize':
-                    if isinstance(label_map, np.ndarray):
-                        label_map = cv2.resize(
-                            label_map, (w, h), interpolation=cv2.INTER_NEAREST)
-                        score_map = cv2.resize(
-                            score_map, (w, h), interpolation=cv2.INTER_LINEAR)
-                    else:
-                        label_map = F.interpolate(
-                            label_map, (h, w),
-                            mode='nearest',
-                            data_format='NHWC')
-                        score_map = F.interpolate(
-                            score_map, (h, w),
-                            mode='bilinear',
-                            data_format='NHWC')
-                elif item[0] == 'padding':
-                    x, y = item[2]
-                    if isinstance(label_map, np.ndarray):
-                        label_map = label_map[..., y:y + h, x:x + w]
-                        score_map = score_map[..., y:y + h, x:x + w]
-                    else:
-                        label_map = label_map[:, :, y:y + h, x:x + w]
-                        score_map = score_map[:, :, y:y + h, x:x + w]
-                else:
-                    pass
-            label_map = label_map.squeeze()
-            score_map = score_map.squeeze()
-            if not isinstance(label_map, np.ndarray):
-                label_map = label_map.numpy()
-                score_map = score_map.numpy()
-            label_maps.append(label_map.squeeze())
-            score_maps.append(score_map.squeeze())
-        return label_maps, score_maps
-
-__all__ = ["ResNet50_vd", "MobileNetV3_small_x1_0", "HRNet_W18_C"]
-
-
 class ResNet50_vd(BaseClassifier):
     def __init__(self,
                  num_classes=2,

+ 58 - 0
tutorials/train/classification/resnet50_vd_rs.py

@@ -0,0 +1,58 @@
+import sys
+
+sys.path.append("E:/dataFiles/github/PaddleRS")
+
+import paddlers as pdrs
+from paddlers import transforms as T
+
+# 下载aistudio的数据到当前文件夹并解压、整理
+# https://aistudio.baidu.com/aistudio/datasetdetail/63189
+
+# 定义训练和验证时的transforms
+# API说明:https://github.com/PaddlePaddle/paddlers/blob/develop/docs/apis/transforms/transforms.md
+train_transforms = T.Compose([
+    T.Resize(target_size=512),
+    T.RandomHorizontalFlip(),
+    T.Normalize(
+        mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
+])
+
+eval_transforms = T.Compose([
+    T.Resize(target_size=512),
+    T.Normalize(
+        mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
+])
+
+# 定义训练和验证所用的数据集
+# API说明:https://github.com/PaddlePaddle/paddlers/blob/develop/docs/apis/datasets.md
+train_dataset = pdrs.datasets.ClasDataset(
+    data_dir='E:/dataFiles/github/PaddleRS/tutorials/train/classification/DataSet',
+    file_list='tutorials/train/classification/DataSet/train_list.txt',
+    label_list='tutorials/train/classification/DataSet/label_list.txt',
+    transforms=train_transforms,
+    num_workers=0,
+    shuffle=True)
+
+eval_dataset = pdrs.datasets.ClasDataset(
+    data_dir='E:/dataFiles/github/PaddleRS/tutorials/train/classification/DataSet',
+    file_list='tutorials/train/classification/DataSet/test_list.txt',
+    label_list='tutorials/train/classification/DataSet/label_list.txt',
+    transforms=eval_transforms,
+    num_workers=0,
+    shuffle=False)
+
+# 初始化模型,并进行训练
+# 可使用VisualDL查看训练指标,参考https://github.com/PaddlePaddle/paddlers/blob/develop/docs/visualdl.md
+num_classes = len(train_dataset.labels)
+model = pdrs.tasks.ResNet50_vd(num_classes=num_classes)
+
+# API说明:https://github.com/PaddlePaddle/paddlers/blob/develop/docs/apis/models/semantic_segmentation.md
+# 各参数介绍与调整说明:https://github.com/PaddlePaddle/paddlers/blob/develop/docs/parameters.md
+model.train(
+    num_epochs=10,
+    train_dataset=train_dataset,
+    train_batch_size=4,
+    eval_dataset=eval_dataset,
+    learning_rate=0.01,
+    pretrain_weights=None,
+    save_dir='output/resnet_vd')