From af9aae730e055cf9a162306b3c4b1937a34c9442 Mon Sep 17 00:00:00 2001 From: cuicheng01 Date: Sun, 26 Sep 2021 07:05:13 +0000 Subject: [PATCH] add multilabel feature --- deploy/configs/inference_multilabel_cls.yaml | 33 +++++ deploy/images/0517_2715693311.jpg | Bin 0 -> 16727 bytes deploy/python/postprocess.py | 23 +++- deploy/python/predict_cls.py | 2 - deploy/shell/predict.sh | 3 + .../multilabel/multilabel.md | 78 ++++++----- .../quick_start/MobileNetV1_multilabel.yaml | 129 ++++++++++++++++++ .../professional/MobileNetV1_multilabel.yaml | 129 ++++++++++++++++++ ppcls/data/dataloader/multilabel_dataset.py | 7 +- ppcls/data/postprocess/__init__.py | 2 +- ppcls/data/postprocess/topk.py | 16 ++- ppcls/engine/engine.py | 19 +-- ppcls/engine/evaluation/classification.py | 3 +- ppcls/engine/train/train.py | 4 +- ppcls/loss/__init__.py | 1 + ppcls/loss/multilabelloss.py | 43 ++++++ ppcls/metric/__init__.py | 6 +- ppcls/metric/metrics.py | 76 ++++++++++- tools/train.sh | 2 +- train.sh | 7 + 20 files changed, 519 insertions(+), 64 deletions(-) create mode 100644 deploy/configs/inference_multilabel_cls.yaml create mode 100644 deploy/images/0517_2715693311.jpg create mode 100644 ppcls/configs/quick_start/MobileNetV1_multilabel.yaml create mode 100644 ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml create mode 100644 ppcls/loss/multilabelloss.py create mode 100755 train.sh diff --git a/deploy/configs/inference_multilabel_cls.yaml b/deploy/configs/inference_multilabel_cls.yaml new file mode 100644 index 00000000..9dc05297 --- /dev/null +++ b/deploy/configs/inference_multilabel_cls.yaml @@ -0,0 +1,33 @@ +Global: + infer_imgs: "./images/0517_2715693311.jpg" + inference_model_dir: "../inference/" + batch_size: 1 + use_gpu: True + enable_mkldnn: False + cpu_num_threads: 10 + enable_benchmark: True + use_fp16: False + ir_optim: True + use_tensorrt: False + gpu_mem: 8000 + enable_profile: False +PreProcess: + transform_ops: + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 0.00392157 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + channel_num: 3 + - ToCHWImage: +PostProcess: + main_indicator: MultiLabelTopk + MultiLabelTopk: + topk: 5 + class_id_map_file: None + SavePreLabel: + save_dir: ./pre_label/ diff --git a/deploy/images/0517_2715693311.jpg b/deploy/images/0517_2715693311.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bd9d2f632192b5be14a8f684303bdeb87bedcfaf GIT binary patch literal 16727 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 literal 0 HcmV?d00001 diff --git a/deploy/python/postprocess.py b/deploy/python/postprocess.py index 61b5fbce..d26cbaa9 100644 --- a/deploy/python/postprocess.py +++ b/deploy/python/postprocess.py @@ -81,12 +81,14 @@ class Topk(object): class_id_map = None return class_id_map - def __call__(self, x, file_names=None): + def __call__(self, x, file_names=None, multilabel=False): if file_names is not None: assert x.shape[0] == len(file_names) y = [] for idx, probs in enumerate(x): - index = probs.argsort(axis=0)[-self.topk:][::-1].astype("int32") + index = probs.argsort(axis=0)[-self.topk:][::-1].astype( + "int32") if not multilabel else np.where( + probs >= 0.5)[0].astype("int32") clas_id_list = [] score_list = [] label_name_list = [] @@ -108,6 +110,14 @@ class Topk(object): return y +class MultiLabelTopk(Topk): + def __init__(self, topk=1, class_id_map_file=None): + super().__init__() + + def __call__(self, x, file_names=None): + return super().__call__(x, file_names, multilabel=True) + + class SavePreLabel(object): def __init__(self, save_dir): if save_dir is None: @@ -128,23 +138,24 @@ class SavePreLabel(object): os.makedirs(output_dir, exist_ok=True) shutil.copy(image_file, output_dir) + class Binarize(object): - def __init__(self, method = "round"): + def __init__(self, method="round"): self.method = method self.unit = np.array([[128, 64, 32, 16, 8, 4, 2, 1]]).T def __call__(self, x, file_names=None): if self.method == "round": x = np.round(x + 1).astype("uint8") - 1 - + if self.method == "sign": x = ((np.sign(x) + 1) / 2).astype("uint8") embedding_size = x.shape[1] assert embedding_size % 8 == 0, "The Binary index only support vectors with sizes multiple of 8" - + byte = np.zeros([x.shape[0], embedding_size // 8], dtype=np.uint8) for i in range(embedding_size // 8): - byte[:, i:i+1] = np.dot(x[:, i * 8: (i + 1)* 8], self.unit) + byte[:, i:i + 1] = np.dot(x[:, i * 8:(i + 1) * 8], self.unit) return byte diff --git a/deploy/python/predict_cls.py b/deploy/python/predict_cls.py index dc686540..cdeb32e4 100644 --- a/deploy/python/predict_cls.py +++ b/deploy/python/predict_cls.py @@ -71,7 +71,6 @@ class ClsPredictor(Predictor): output_names = self.paddle_predictor.get_output_names() output_tensor = self.paddle_predictor.get_output_handle(output_names[ 0]) - if self.benchmark: self.auto_logger.times.start() if not isinstance(images, (list, )): @@ -119,7 +118,6 @@ def main(config): ) == len(image_list): if len(batch_imgs) == 0: continue - batch_results = cls_predictor.predict(batch_imgs) for number, result_dict in enumerate(batch_results): filename = batch_names[number] diff --git a/deploy/shell/predict.sh b/deploy/shell/predict.sh index 44be9428..f0f59f4a 100644 --- a/deploy/shell/predict.sh +++ b/deploy/shell/predict.sh @@ -1,6 +1,9 @@ # classification python3.7 python/predict_cls.py -c configs/inference_cls.yaml +# multilabel_classification +#python3.7 python/predict_cls.py -c configs/inference_multilabel_cls.yaml + # feature extractor # python3.7 python/predict_rec.py -c configs/inference_rec.yaml diff --git a/docs/zh_CN/advanced_tutorials/multilabel/multilabel.md b/docs/zh_CN/advanced_tutorials/multilabel/multilabel.md index ef445ca8..50eec827 100644 --- a/docs/zh_CN/advanced_tutorials/multilabel/multilabel.md +++ b/docs/zh_CN/advanced_tutorials/multilabel/multilabel.md @@ -25,58 +25,66 @@ tar -xf NUS-SCENE-dataset.tar cd ../../ ``` -## 二、环境准备 +## 二、模型训练 -### 2.1 下载预训练模型 +```shell +export CUDA_VISIBLE_DEVICES=0,1,2,3 +python3 -m paddle.distributed.launch \ + --gpus="0,1,2,3" \ + tools/train.py \ + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml +``` + +训练10epoch之后,验证集最好的正确率应该在0.95左右。 -本例展示基于ResNet50_vd模型的多标签分类流程,因此首先下载ResNet50_vd的预训练模型 +## 三、模型评估 ```bash -mkdir pretrained -cd pretrained -wget https://paddle-imagenet-models-name.bj.bcebos.com/dygraph/ResNet50_vd_pretrained.pdparams -cd ../ +python3 tools/eval.py \ + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" ``` -## 三、模型训练 +## 四、模型预测 -```shell -export CUDA_VISIBLE_DEVICES=0 -python -m paddle.distributed.launch \ - --gpus="0" \ - tools/train.py \ - -c ./configs/quick_start/ResNet50_vd_multilabel.yaml +```bash +python3 tools/infer.py \ + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" +``` + +得到类似下面的输出: +``` +[{'class_ids': [6, 13, 17, 23, 26, 30], 'scores': [0.95683, 0.5567, 0.55211, 0.99088, 0.5943, 0.78767], 'file_name': './deploy/images/0517_2715693311.jpg', 'label_names': []}] ``` -训练10epoch之后,验证集最好的正确率应该在0.72左右。 +## 五、基于预测引擎预测 -## 四、模型评估 +### 5.1 导出inference model ```bash -python tools/eval.py \ - -c ./configs/quick_start/ResNet50_vd_multilabel.yaml \ - -o pretrained_model="./output/ResNet50_vd/best_model/ppcls" \ - -o load_static_weights=False +python3 tools/export_model.py \ + -c ./ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml \ + -o Arch.pretrained="./output/MobileNetV1/best_model" ``` +inference model的路径默认在当前路径下`./inference` -评估指标采用mAP,验证集的mAP应该在0.57左右。 +### 5.2 基于预测引擎预测 -## 五、模型预测 +首先进入deploy目录下: ```bash -python tools/infer/infer.py \ - -i "./dataset/NUS-WIDE-SCENE/NUS-SCENE-dataset/images/0199_434752251.jpg" \ - --model ResNet50_vd \ - --pretrained_model "./output/ResNet50_vd/best_model/ppcls" \ - --use_gpu True \ - --load_static_weights False \ - --multilabel True \ - --class_num 33 +cd ./deploy +``` + +通过预测引擎推理预测: + +``` +python3 python/predict_cls.py \ + -c configs/inference_multilabel_cls.yaml ``` 得到类似下面的输出: -``` - class id: 3, probability: 0.6025 - class id: 23, probability: 0.5491 - class id: 32, probability: 0.7006 -``` \ No newline at end of file +``` +0517_2715693311.jpg: class id(s): [6, 13, 17, 23, 26, 30], score(s): [0.96, 0.56, 0.55, 0.99, 0.59, 0.79], label_name(s): [] +``` diff --git a/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml b/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml new file mode 100644 index 00000000..e9c021b6 --- /dev/null +++ b/ppcls/configs/quick_start/MobileNetV1_multilabel.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 10 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference + use_multilabel: True +# model architecture +Arch: + name: MobileNetV1 + class_num: 33 + pretrained: True + +# loss function config for traing/eval process +Loss: + Train: + - MultiLabelLoss: + weight: 1.0 + Eval: + - MultiLabelLoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.1 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: MultiLabelDataset + image_root: ./dataset/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: MultiLabelDataset + image_root: ./dataset/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_test_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 256 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: dataset/NUS-SCENE-dataset/images/0001_109549716.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: MutiLabelTopk + topk: 5 + class_id_map_file: None + +Metric: + Train: + - HammingDistance: + - AccuracyScore: + Eval: + - HammingDistance: + - AccuracyScore: diff --git a/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml b/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml new file mode 100644 index 00000000..7c9e5a7e --- /dev/null +++ b/ppcls/configs/quick_start/professional/MobileNetV1_multilabel.yaml @@ -0,0 +1,129 @@ +# global configs +Global: + checkpoints: null + pretrained_model: null + output_dir: ./output/ + device: gpu + save_interval: 1 + eval_during_train: True + eval_interval: 1 + epochs: 10 + print_batch_step: 10 + use_visualdl: False + # used for static mode and model export + image_shape: [3, 224, 224] + save_inference_dir: ./inference + use_multilabel: True +# model architecture +Arch: + name: MobileNetV1 + class_num: 33 + pretrained: True + +# loss function config for traing/eval process +Loss: + Train: + - MultiLabelLoss: + weight: 1.0 + Eval: + - MultiLabelLoss: + weight: 1.0 + + +Optimizer: + name: Momentum + momentum: 0.9 + lr: + name: Cosine + learning_rate: 0.1 + regularizer: + name: 'L2' + coeff: 0.00004 + + +# data loader for train and eval +DataLoader: + Train: + dataset: + name: MultiLabelDataset + image_root: ./dataset/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_train_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - RandCropImage: + size: 224 + - RandFlipImage: + flip_code: 1 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + + sampler: + name: DistributedBatchSampler + batch_size: 64 + drop_last: False + shuffle: True + loader: + num_workers: 4 + use_shared_memory: True + + Eval: + dataset: + name: MultiLabelDataset + image_root: ./dataset/NUS-SCENE-dataset/images/ + cls_label_path: ./dataset/NUS-SCENE-dataset/multilabel_test_list.txt + transform_ops: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + sampler: + name: DistributedBatchSampler + batch_size: 256 + drop_last: False + shuffle: False + loader: + num_workers: 4 + use_shared_memory: True + +Infer: + infer_imgs: ./deploy/images/0517_2715693311.jpg + batch_size: 10 + transforms: + - DecodeImage: + to_rgb: True + channel_first: False + - ResizeImage: + resize_short: 256 + - CropImage: + size: 224 + - NormalizeImage: + scale: 1.0/255.0 + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] + order: '' + - ToCHWImage: + PostProcess: + name: MultiLabelTopk + topk: 5 + class_id_map_file: None + +Metric: + Train: + - HammingDistance: + - AccuracyScore: + Eval: + - HammingDistance: + - AccuracyScore: diff --git a/ppcls/data/dataloader/multilabel_dataset.py b/ppcls/data/dataloader/multilabel_dataset.py index fafecc71..08d2ba15 100644 --- a/ppcls/data/dataloader/multilabel_dataset.py +++ b/ppcls/data/dataloader/multilabel_dataset.py @@ -33,7 +33,7 @@ class MultiLabelDataset(CommonDataset): with open(self._cls_path) as fd: lines = fd.readlines() for l in lines: - l = l.strip().split(" ") + l = l.strip().split("\t") self.images.append(os.path.join(self._img_root, l[0])) labels = l[1].split(',') @@ -44,13 +44,14 @@ class MultiLabelDataset(CommonDataset): def __getitem__(self, idx): try: - img = cv2.imread(self.images[idx]) - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) + with open(self.images[idx], 'rb') as f: + img = f.read() if self._transform_ops: img = transform(img, self._transform_ops) img = img.transpose((2, 0, 1)) label = np.array(self.labels[idx]).astype("float32") return (img, label) + except Exception as ex: logger.error("Exception occured when parse line: {} with msg: {}". format(self.images[idx], ex)) diff --git a/ppcls/data/postprocess/__init__.py b/ppcls/data/postprocess/__init__.py index 801e7f10..831a4da0 100644 --- a/ppcls/data/postprocess/__init__.py +++ b/ppcls/data/postprocess/__init__.py @@ -16,7 +16,7 @@ import importlib from . import topk -from .topk import Topk +from .topk import Topk, MultiLabelTopk def build_postprocess(config): diff --git a/ppcls/data/postprocess/topk.py b/ppcls/data/postprocess/topk.py index 2410e329..9c1371bf 100644 --- a/ppcls/data/postprocess/topk.py +++ b/ppcls/data/postprocess/topk.py @@ -45,15 +45,17 @@ class Topk(object): class_id_map = None return class_id_map - def __call__(self, x, file_names=None): + def __call__(self, x, file_names=None, multilabel=False): assert isinstance(x, paddle.Tensor) if file_names is not None: assert x.shape[0] == len(file_names) - x = F.softmax(x, axis=-1) + x = F.softmax(x, axis=-1) if not multilabel else F.sigmoid(x) x = x.numpy() y = [] for idx, probs in enumerate(x): - index = probs.argsort(axis=0)[-self.topk:][::-1].astype("int32") + index = probs.argsort(axis=0)[-self.topk:][::-1].astype( + "int32") if not multilabel else np.where( + probs >= 0.5)[0].astype("int32") clas_id_list = [] score_list = [] label_name_list = [] @@ -73,3 +75,11 @@ class Topk(object): result["label_names"] = label_name_list y.append(result) return y + + +class MultiLabelTopk(Topk): + def __init__(self, topk=1, class_id_map_file=None): + super().__init__() + + def __call__(self, x, file_names=None): + return super().__call__(x, file_names, multilabel=True) diff --git a/ppcls/engine/engine.py b/ppcls/engine/engine.py index 2d488f8c..d0f2d647 100644 --- a/ppcls/engine/engine.py +++ b/ppcls/engine/engine.py @@ -355,7 +355,8 @@ class Engine(object): def export(self): assert self.mode == "export" - model = ExportModel(self.config["Arch"], self.model) + use_multilabel = self.config["Global"].get("use_multilabel", False) + model = ExportModel(self.config["Arch"], self.model, use_multilabel) if self.config["Global"]["pretrained_model"] is not None: load_dygraph_pretrain(model.base_model, self.config["Global"]["pretrained_model"]) @@ -388,10 +389,9 @@ class ExportModel(nn.Layer): ExportModel: add softmax onto the model """ - def __init__(self, config, model): + def __init__(self, config, model, use_multilabel): super().__init__() self.base_model = model - # we should choose a final model to export if isinstance(self.base_model, DistillationModel): self.infer_model_name = config["infer_model_name"] @@ -402,10 +402,13 @@ class ExportModel(nn.Layer): if self.infer_output_key == "features" and isinstance(self.base_model, RecModel): self.base_model.head = IdentityHead() - if config.get("infer_add_softmax", True): - self.softmax = nn.Softmax(axis=-1) + if use_multilabel: + self.out_act = nn.Sigmoid() else: - self.softmax = None + if config.get("infer_add_softmax", True): + self.out_act = nn.Softmax(axis=-1) + else: + self.out_act = None def eval(self): self.training = False @@ -421,6 +424,6 @@ class ExportModel(nn.Layer): x = x[self.infer_model_name] if self.infer_output_key is not None: x = x[self.infer_output_key] - if self.softmax is not None: - x = self.softmax(x) + if self.out_act is not None: + x = self.out_act(x) return x diff --git a/ppcls/engine/evaluation/classification.py b/ppcls/engine/evaluation/classification.py index b1ddc418..005d740d 100644 --- a/ppcls/engine/evaluation/classification.py +++ b/ppcls/engine/evaluation/classification.py @@ -52,7 +52,8 @@ def classification_eval(evaler, epoch_id=0): time_info["reader_cost"].update(time.time() - tic) batch_size = batch[0].shape[0] batch[0] = paddle.to_tensor(batch[0]).astype("float32") - batch[1] = batch[1].reshape([-1, 1]).astype("int64") + if not evaler.config["Global"].get("use_multilabel", False): + batch[1] = batch[1].reshape([-1, 1]).astype("int64") # image input out = evaler.model(batch[0]) # calc loss diff --git a/ppcls/engine/train/train.py b/ppcls/engine/train/train.py index 73f22508..e1585483 100644 --- a/ppcls/engine/train/train.py +++ b/ppcls/engine/train/train.py @@ -36,8 +36,8 @@ def train_epoch(trainer, epoch_id, print_batch_step): paddle.to_tensor(batch[0]['label']) ] batch_size = batch[0].shape[0] - batch[1] = batch[1].reshape([-1, 1]).astype("int64") - + if not trainer.config["Global"].get("use_multilabel", False): + batch[1] = batch[1].reshape([-1, 1]).astype("int64") trainer.global_step += 1 # image input if trainer.amp: diff --git a/ppcls/loss/__init__.py b/ppcls/loss/__init__.py index 5421f421..7c037480 100644 --- a/ppcls/loss/__init__.py +++ b/ppcls/loss/__init__.py @@ -20,6 +20,7 @@ from .distanceloss import DistanceLoss from .distillationloss import DistillationCELoss from .distillationloss import DistillationGTCELoss from .distillationloss import DistillationDMLLoss +from .multilabelloss import MultiLabelLoss class CombinedLoss(nn.Layer): diff --git a/ppcls/loss/multilabelloss.py b/ppcls/loss/multilabelloss.py new file mode 100644 index 00000000..d30d5b8d --- /dev/null +++ b/ppcls/loss/multilabelloss.py @@ -0,0 +1,43 @@ +import paddle +import paddle.nn as nn +import paddle.nn.functional as F + + +class MultiLabelLoss(nn.Layer): + """ + Multi-label loss + """ + + def __init__(self, epsilon=None): + super().__init__() + if epsilon is not None and (epsilon <= 0 or epsilon >= 1): + epsilon = None + self.epsilon = epsilon + + def _labelsmoothing(self, target, class_num): + if target.ndim == 1 or target.shape[-1] != class_num: + one_hot_target = F.one_hot(target, class_num) + else: + one_hot_target = target + soft_target = F.label_smooth(one_hot_target, epsilon=self.epsilon) + soft_target = paddle.reshape(soft_target, shape=[-1, class_num]) + return soft_target + + def _binary_crossentropy(self, input, target, class_num): + if self.epsilon is not None: + target = self._labelsmoothing(target, class_num) + cost = F.binary_cross_entropy_with_logits( + logit=input, label=target) + else: + cost = F.binary_cross_entropy_with_logits( + logit=input, label=target) + + return cost + + def forward(self, x, target): + if isinstance(x, dict): + x = x["logits"] + class_num = x.shape[-1] + loss = self._binary_crossentropy(x, target, class_num) + loss = loss.mean() + return {"MultiLabelLoss": loss} diff --git a/ppcls/metric/__init__.py b/ppcls/metric/__init__.py index 4c817a11..94721235 100644 --- a/ppcls/metric/__init__.py +++ b/ppcls/metric/__init__.py @@ -19,6 +19,8 @@ from collections import OrderedDict from .metrics import TopkAcc, mAP, mINP, Recallk, Precisionk from .metrics import DistillationTopkAcc from .metrics import GoogLeNetTopkAcc +from .metrics import HammingDistance, AccuracyScore + class CombinedMetrics(nn.Layer): def __init__(self, config_list): @@ -32,7 +34,8 @@ class CombinedMetrics(nn.Layer): metric_name = list(config)[0] metric_params = config[metric_name] if metric_params is not None: - self.metric_func_list.append(eval(metric_name)(**metric_params)) + self.metric_func_list.append( + eval(metric_name)(**metric_params)) else: self.metric_func_list.append(eval(metric_name)()) @@ -42,6 +45,7 @@ class CombinedMetrics(nn.Layer): metric_dict.update(metric_func(*args, **kwargs)) return metric_dict + def build_metrics(config): metrics_list = CombinedMetrics(copy.deepcopy(config)) return metrics_list diff --git a/ppcls/metric/metrics.py b/ppcls/metric/metrics.py index 204d2af0..37509eb1 100644 --- a/ppcls/metric/metrics.py +++ b/ppcls/metric/metrics.py @@ -15,6 +15,12 @@ import numpy as np import paddle import paddle.nn as nn +import paddle.nn.functional as F + +from sklearn.metrics import hamming_loss +from sklearn.metrics import accuracy_score as accuracy_metric +from sklearn.metrics import multilabel_confusion_matrix +from sklearn.preprocessing import binarize class TopkAcc(nn.Layer): @@ -198,7 +204,7 @@ class Precisionk(nn.Layer): equal_flag = paddle.logical_and(equal_flag, keep_mask.astype('bool')) equal_flag = paddle.cast(equal_flag, 'float32') - + Ns = paddle.arange(gallery_img_id.shape[0]) + 1 equal_flag_cumsum = paddle.cumsum(equal_flag, axis=1) Precision_at_k = (paddle.mean(equal_flag_cumsum, axis=0) / Ns).numpy() @@ -232,3 +238,71 @@ class GoogLeNetTopkAcc(TopkAcc): def forward(self, x, label): return super().forward(x[0], label) + + +class MutiLabelMetric(object): + def __init__(self): + pass + + def _multi_hot_encode(self, logits, threshold=0.5): + return binarize(logits, threshold=threshold) + + def __call__(self, output): + output = F.sigmoid(output) + preds = self._multi_hot_encode(logits=output.numpy(), threshold=0.5) + return preds + + +class HammingDistance(MutiLabelMetric): + """ + Soft metric based label for multilabel classification + Returns: + The smaller the return value is, the better model is. + """ + + def __init__(self): + super().__init__() + + def __call__(self, output, target): + preds = super().__call__(output) + metric_dict = dict() + metric_dict["HammingDistance"] = paddle.to_tensor( + hamming_loss(target, preds)) + return metric_dict + + +class AccuracyScore(MutiLabelMetric): + """ + Hard metric for multilabel classification + Args: + base: ["sample", "label"], default="sample" + if "sample", return metric score based sample, + if "label", return metric score based label. + Returns: + accuracy: + """ + + def __init__(self, base="label"): + super().__init__() + assert base in ["sample", "label" + ], 'must be one of ["sample", "label"]' + self.base = base + + def __call__(self, output, target): + preds = super().__call__(output) + metric_dict = dict() + if self.base == "sample": + accuracy = accuracy_metric(target, preds) + elif self.base == "label": + mcm = multilabel_confusion_matrix(target, preds) + tns = mcm[:, 0, 0] + fns = mcm[:, 1, 0] + tps = mcm[:, 1, 1] + fps = mcm[:, 0, 1] + accuracy = (sum(tps) + sum(tns)) / ( + sum(tps) + sum(tns) + sum(fns) + sum(fps)) + precision = sum(tps) / (sum(tps) + sum(fps)) + recall = sum(tps) / (sum(tps) + sum(fns)) + F1 = 2 * (accuracy * recall) / (accuracy + recall) + metric_dict["AccuracyScore"] = paddle.to_tensor(accuracy) + return metric_dict diff --git a/tools/train.sh b/tools/train.sh index 5fced863..083934a5 100755 --- a/tools/train.sh +++ b/tools/train.sh @@ -4,4 +4,4 @@ # python3.7 tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml # for multi-cards train -python3.7 -m paddle.distributed.launch --gpus="0,1,2,3" tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml \ No newline at end of file +python3.7 -m paddle.distributed.launch --gpus="0,1,2,3" tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml diff --git a/train.sh b/train.sh new file mode 100755 index 00000000..47ae2a68 --- /dev/null +++ b/train.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +# for single card train +# python3.7 tools/train.py -c ./ppcls/configs/ImageNet/ResNet/ResNet50.yaml + +# for multi-cards train +python3.7 -m paddle.distributed.launch --gpus="0" tools/train.py -c ./MobileNetV2.yaml -- GitLab