diff --git a/mpa/det/__init__.py b/mpa/det/__init__.py index f5af563c..7c39fca9 100644 --- a/mpa/det/__init__.py +++ b/mpa/det/__init__.py @@ -9,6 +9,9 @@ from . import stage from . import trainer +from . import incremental +from . import semisl + import mpa.modules.datasets.pipelines.torchvision2mmdet import mpa.modules.datasets.det_csv_dataset diff --git a/mpa/det/evaluator.py b/mpa/det/evaluator.py index 7fd96743..8476e5e4 100644 --- a/mpa/det/evaluator.py +++ b/mpa/det/evaluator.py @@ -5,7 +5,7 @@ import os.path as osp import json from mpa.registry import STAGES -from .inferrer import DetectionInferrer +from mpa.det.inferrer import DetectionInferrer from mpa.utils.logger import get_logger logger = get_logger() diff --git a/mpa/det/exporter.py b/mpa/det/exporter.py index dccbd9cb..f3570bfb 100644 --- a/mpa/det/exporter.py +++ b/mpa/det/exporter.py @@ -12,13 +12,13 @@ from mpa.registry import STAGES from mpa.utils.logger import get_logger -from .stage import DetectionStage +from mpa.det.incremental import IncrDetectionStage logger = get_logger() @STAGES.register_module() -class DetectionExporter(DetectionStage): +class DetectionExporter(IncrDetectionStage): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -47,7 +47,7 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): model = model.cpu() precision = kwargs.pop('precision', 'FP32') logger.info(f'Model will be exported with precision {precision}') - + export_model(model, cfg, output_path, target='openvino', precision=precision) except Exception as ex: # output_model.model_status = ModelStatus.FAILED diff --git a/mpa/det/incremental/__init__.py b/mpa/det/incremental/__init__.py new file mode 100644 index 00000000..6353ceac --- /dev/null +++ b/mpa/det/incremental/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +from .stage import IncrDetectionStage + +__all__ = ["IncrDetectionStage"] diff --git a/mpa/det/incremental/stage.py b/mpa/det/incremental/stage.py new file mode 100644 index 00000000..341817fe --- /dev/null +++ b/mpa/det/incremental/stage.py @@ -0,0 +1,282 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +import numpy as np +import torch + +from mmcv import ConfigDict +from mmdet.datasets import build_dataset +from mpa.det.stage import DetectionStage +from mpa.utils.config_utils import update_or_add_custom_hook +from mpa.utils.logger import get_logger + +logger = get_logger() + + +class IncrDetectionStage(DetectionStage): + """Patch config to support incremental learning for object detection""" + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def configure_task(self, cfg, training, **kwargs): + """Patch config to support incremental learning + """ + logger.info(f'Incremental task config!!!!: training={training}') + if 'task_adapt' in cfg: + task_adapt_type = cfg['task_adapt'].get('type', None) + task_adapt_op = cfg['task_adapt'].get('op', 'REPLACE') + + org_model_classes, model_classes, data_classes = \ + self.configure_classes(cfg, task_adapt_type, task_adapt_op) + if data_classes != model_classes: + self.configure_task_data_pipeline(cfg, model_classes, data_classes) + # TODO[JAEGUK]: configure_anchor is not working + if cfg['task_adapt'].get('use_mpa_anchor', False): + self.configure_anchor(cfg) + self.configure_task_cls_incr(cfg, task_adapt_type, org_model_classes, model_classes) + + def configure_classes(self, cfg, task_adapt_type, task_adapt_op): + """Patch classes for model and dataset.""" + org_model_classes = self.get_model_classes(cfg) + data_classes = self.get_data_classes(cfg) + + # Model classes + if task_adapt_op == 'REPLACE': + if len(data_classes) == 0: + model_classes = org_model_classes.copy() + else: + model_classes = data_classes.copy() + elif task_adapt_op == 'MERGE': + model_classes = org_model_classes + [cls for cls in data_classes if cls not in org_model_classes] + else: + raise KeyError(f'{task_adapt_op} is not supported for task_adapt options!') + + if task_adapt_type == 'mpa': + data_classes = model_classes + cfg.task_adapt.final = model_classes + cfg.model.task_adapt = ConfigDict( + src_classes=org_model_classes, + dst_classes=model_classes, + ) + + # Model architecture + head_names = ('mask_head', 'bbox_head', 'segm_head') + num_classes = len(model_classes) + if 'roi_head' in cfg.model: + # For Faster-RCNNs + for head_name in head_names: + if head_name in cfg.model.roi_head: + if isinstance(cfg.model.roi_head[head_name], list): + for head in cfg.model.roi_head[head_name]: + head.num_classes = num_classes + else: + cfg.model.roi_head[head_name].num_classes = num_classes + else: + # For other architectures (including SSD) + for head_name in head_names: + if head_name in cfg.model: + cfg.model[head_name].num_classes = num_classes + + # Eval datasets + if cfg.get('task', 'detection') == 'detection': + eval_types = ['val', 'test'] + for eval_type in eval_types: + if cfg.data[eval_type]['type'] == 'TaskAdaptEvalDataset': + cfg.data[eval_type]['model_classes'] = model_classes + else: + # Wrap original dataset config + org_type = cfg.data[eval_type]['type'] + cfg.data[eval_type]['type'] = 'TaskAdaptEvalDataset' + cfg.data[eval_type]['org_type'] = org_type + cfg.data[eval_type]['model_classes'] = model_classes + + return org_model_classes, model_classes, data_classes + + def configure_task_data_pipeline(self, cfg, model_classes, data_classes): + # Trying to alter class indices of training data according to model class order + tr_data_cfg = self.get_train_data_cfg(cfg) + class_adapt_cfg = dict(type='AdaptClassLabels', src_classes=data_classes, dst_classes=model_classes) + pipeline_cfg = tr_data_cfg.pipeline + for i, op in enumerate(pipeline_cfg): + if op['type'] == 'LoadAnnotations': # insert just after this op + op_next_ann = pipeline_cfg[i + 1] if i + 1 < len(pipeline_cfg) else {} + if op_next_ann.get('type', '') == class_adapt_cfg['type']: + op_next_ann.update(class_adapt_cfg) + else: + pipeline_cfg.insert(i + 1, class_adapt_cfg) + break + + def configure_anchor(self, cfg, proposal_ratio=None): + if cfg.model.type in ['SingleStageDetector', 'CustomSingleStageDetector']: + anchor_cfg = cfg.model.bbox_head.anchor_generator + if anchor_cfg.type == 'SSDAnchorGeneratorClustered': + cfg.model.bbox_head.anchor_generator.pop('input_size', None) + + def configure_task_cls_incr(self, cfg, task_adapt_type, org_model_classes, model_classes): + """Patch config for incremental learning""" + if task_adapt_type == 'mpa': + self.configure_bbox_head(cfg, model_classes) + self.configure_task_adapt_hook(cfg, org_model_classes, model_classes) + self.configure_ema(cfg) + self.configure_val_interval(cfg) + else: + src_data_cfg = self.get_train_data_cfg(cfg) + src_data_cfg.pop('old_new_indices', None) + + def configure_bbox_head(self, cfg, model_classes): + """Patch bbox head in detector for class incremental learning. + Most of patching are related with hyper-params in focal loss + """ + if cfg.get('task', 'detection') == 'detection': + bbox_head = cfg.model.bbox_head + else: + bbox_head = cfg.model.roi_head.bbox_head + + # TODO Remove this part + # This is not related with patching bbox head + # This might be useless when semisl using MPADetDataset + tr_data_cfg = self.get_train_data_cfg(cfg) + if tr_data_cfg.type != 'MPADetDataset': + tr_data_cfg.img_ids_dict = self.get_img_ids_for_incr(cfg, org_model_classes, model_classes) + tr_data_cfg.org_type = tr_data_cfg.type + tr_data_cfg.type = 'DetIncrCocoDataset' + + alpha, gamma = 0.25, 2.0 + if bbox_head.type in ['SSDHead', 'CustomSSDHead']: + gamma = 1 if cfg['task_adapt'].get('efficient_mode', False) else 2 + bbox_head.type = 'CustomSSDHead' + bbox_head.loss_cls = ConfigDict( + type='FocalLoss', + loss_weight=1.0, + gamma=gamma, + reduction='none', + ) + elif bbox_head.type in ['ATSSHead']: + gamma = 3 if cfg['task_adapt'].get('efficient_mode', False) else 4.5 + bbox_head.loss_cls.gamma = gamma + elif bbox_head.type in ['VFNetHead', 'CustomVFNetHead']: + alpha = 0.75 + gamma = 1 if cfg['task_adapt'].get('efficient_mode', False) else 2 + # TODO Move this part + # This is not related with patching bbox head + elif bbox_head.type in ['YOLOXHead', 'CustomYOLOXHead']: + if cfg.data.train.type == 'MultiImageMixDataset': + cfg.data.train.pop('ann_file', None) + cfg.data.train.pop('img_prefix', None) + cfg.data.train['labels'] = cfg.data.train.pop('labels', None) + self.add_yolox_hooks(cfg) + + if cfg.get('ignore', False): + bbox_head.loss_cls = ConfigDict( + type='CrossSigmoidFocalLoss', + use_sigmoid=True, + num_classes=len(model_classes), + alpha=alpha, + gamma=gamma + ) + + @staticmethod + def configure_task_adapt_hook(cfg, org_model_classes, model_classes): + """Add TaskAdaptHook for sampler.""" + sampler_flag = True + if len(set(org_model_classes) & set(model_classes)) == 0 or set(org_model_classes) == set(model_classes): + sampler_flag = False + update_or_add_custom_hook( + cfg, + ConfigDict( + type='TaskAdaptHook', + src_classes=org_model_classes, + dst_classes=model_classes, + model_type=cfg.model.type, + sampler_flag=sampler_flag, + efficient_mode=cfg['task_adapt'].get('efficient_mode', False) + ) + ) + + @staticmethod + def configure_ema(cfg): + """Patch ema settings.""" + adaptive_ema = cfg.get('adaptive_ema', {}) + if adaptive_ema: + update_or_add_custom_hook( + cfg, + ConfigDict( + type='CustomModelEMAHook', + priority='ABOVE_NORMAL', + resume_from=cfg.get("resume_from"), + **adaptive_ema + ) + ) + else: + update_or_add_custom_hook(cfg, ConfigDict(type='EMAHook', priority='ABOVE_NORMAL', resume_from=cfg.get("resume_from"), momentum=0.1)) + + @staticmethod + def configure_val_interval(cfg): + """Patch validation interval.""" + adaptive_validation_interval = cfg.get('adaptive_validation_interval', {}) + if adaptive_validation_interval: + update_or_add_custom_hook( + cfg, + ConfigDict(type='AdaptiveTrainSchedulingHook', **adaptive_validation_interval) + ) + + @staticmethod + def get_img_ids_for_incr(cfg, org_model_classes, model_classes): + # get image ids of old classes & new class + # to setup experimental dataset (COCO format) + new_classes = np.setdiff1d(model_classes, org_model_classes).tolist() + old_classes = np.intersect1d(org_model_classes, model_classes).tolist() + + src_data_cfg = self.get_train_data_cfg(cfg) + + ids_old, ids_new = [], [] + data_cfg = cfg.data.test.copy() + data_cfg.test_mode = src_data_cfg.get('test_mode', False) + data_cfg.ann_file = src_data_cfg.get('ann_file', None) + data_cfg.img_prefix = src_data_cfg.get('img_prefix', None) + old_data_cfg = data_cfg.copy() + if 'classes' in old_data_cfg: + old_data_cfg.classes = old_classes + old_dataset = build_dataset(old_data_cfg) + ids_old = old_dataset.dataset.img_ids + if len(new_classes) > 0: + data_cfg.classes = new_classes + dataset = build_dataset(data_cfg) + ids_new = dataset.dataset.img_ids + ids_old = np.setdiff1d(ids_old, ids_new).tolist() + + sampled_ids = ids_old + ids_new + outputs = dict( + old_classes=old_classes, + new_classes=new_classes, + img_ids=sampled_ids, + img_ids_old=ids_old, + img_ids_new=ids_new, + ) + return outputs + + @staticmethod + def add_yolox_hooks(cfg): + update_or_add_custom_hook( + cfg, + ConfigDict( + type='YOLOXModeSwitchHook', + num_last_epochs=15, + priority=48)) + update_or_add_custom_hook( + cfg, + ConfigDict( + type='SyncRandomSizeHook', + ratio_range=(10, 20), + img_scale=(640, 640), + interval=1, + priority=48, + device='cuda' if torch.cuda.is_available() else 'cpu')) + update_or_add_custom_hook( + cfg, + ConfigDict( + type='SyncNormHook', + num_last_epochs=15, + interval=1, + priority=48)) diff --git a/mpa/det/inferrer.py b/mpa/det/inferrer.py index 45613301..44bcc9b0 100644 --- a/mpa/det/inferrer.py +++ b/mpa/det/inferrer.py @@ -13,14 +13,14 @@ from mmdet.utils.deployment import get_saliency_map, get_feature_vector from mpa.registry import STAGES -from .stage import DetectionStage from mpa.utils.logger import get_logger +from mpa.det.incremental import IncrDetectionStage logger = get_logger() @STAGES.register_module() -class DetectionInferrer(DetectionStage): +class DetectionInferrer(IncrDetectionStage): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -131,7 +131,7 @@ def infer(self, cfg, eval=False, dump_features=False, dump_saliency_map=False): # wrap_fp16_model(model) # InferenceProgressCallback (Time Monitor enable into Infer task) - DetectionStage.set_inference_progress_callback(model, cfg) + self.set_inference_progress_callback(model, cfg) # Checkpoint if cfg.get('load_from', None): @@ -175,9 +175,6 @@ def dummy_dump_saliency_hook(model, input, out): feature_vector_hook = dump_features_hook if dump_features else dummy_dump_features_hook saliency_map_hook = dump_saliency_hook if dump_saliency_map else dummy_dump_saliency_hook - # Use a single gpu for testing. Set in both mm_val_dataloader and eval_model - if is_module_wrapper(model): - model = model.module with eval_model.module.backbone.register_forward_hook(feature_vector_hook): with eval_model.module.backbone.register_forward_hook(saliency_map_hook): for data in data_loader: diff --git a/mpa/det/semisl/__init__.py b/mpa/det/semisl/__init__.py new file mode 100644 index 00000000..c801cd42 --- /dev/null +++ b/mpa/det/semisl/__init__.py @@ -0,0 +1,6 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +from . import trainer +from . import inferrer diff --git a/mpa/det/semisl/inferrer.py b/mpa/det/semisl/inferrer.py new file mode 100644 index 00000000..89c2c46e --- /dev/null +++ b/mpa/det/semisl/inferrer.py @@ -0,0 +1,202 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# +from typing import List, Tuple + +import torch +from mmcv.parallel import MMDataParallel, is_module_wrapper +from mmcv.runner import load_checkpoint + +from mmdet.datasets import build_dataloader, build_dataset, replace_ImageToTensor +from mmdet.models import build_detector +from mmdet.parallel import MMDataCPU +from mmdet.utils.deployment import get_saliency_map, get_feature_vector + +from mpa.registry import STAGES +from mpa.utils.logger import get_logger +from mpa.det.semisl.stage import SemiSLDetectionStage + +logger = get_logger() + + +@STAGES.register_module() +class SemiSLDetectionInferrer(SemiSLDetectionStage): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): + """Run inference stage for detection + + - Configuration + - Environment setup + - Run inference via MMDetection -> MMCV + """ + self._init_logger() + mode = kwargs.get('mode', 'train') + eval = kwargs.get('eval', False) + dump_features = kwargs.get('dump_features', False) + dump_saliency_map = kwargs.get('dump_saliency_map', False) + if mode not in self.mode: + return {} + + cfg = self.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) + # cfg.dump(osp.join(cfg.work_dir, 'config.py')) + # logger.info(f'Config:\n{cfg.pretty_text}') + # logger.info('infer!') + + # mmcv.mkdir_or_exist(osp.abspath(cfg.work_dir)) + + outputs = self.infer(cfg, eval=eval, dump_features=dump_features, + dump_saliency_map=dump_saliency_map) + + # Save outputs + # output_file_path = osp.join(cfg.work_dir, 'infer_result.npy') + # np.save(output_file_path, outputs, allow_pickle=True) + return dict( + # output_file_path=output_file_path, + outputs=outputs + ) + # TODO: save in json + """ + class NumpyEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, np.ndarray): + return obj.tolist() + return json.JSONEncoder.default(self, obj) + + a = np.array([[1, 2, 3], [4, 5, 6]]) + json_dump = json.dumps({'a': a, 'aa': [2, (2, 3, 4), a], 'bb': [2]}, + cls=NumpyEncoder) + print(json_dump) + """ + + def infer(self, cfg, eval=False, dump_features=False, dump_saliency_map=False): + samples_per_gpu = cfg.data.test.pop('samples_per_gpu', 1) + if samples_per_gpu > 1: + # Replace 'ImageToTensor' to 'DefaultFormatBundle' + cfg.data.test.pipeline = replace_ImageToTensor(cfg.data.test.pipeline) + + data_cfg = cfg.data.test.copy() + # Input source + if 'input_source' in cfg: + input_source = cfg.get('input_source') + logger.info(f'Inferring on input source: data.{input_source}') + if input_source == 'train': + src_data_cfg = self.get_train_data_cfg(cfg) + else: + src_data_cfg = cfg.data[input_source] + data_cfg.test_mode = src_data_cfg.get('test_mode', False) + data_cfg.ann_file = src_data_cfg.ann_file + data_cfg.img_prefix = src_data_cfg.img_prefix + if 'classes' in src_data_cfg: + data_cfg.classes = src_data_cfg.classes + self.dataset = build_dataset(data_cfg) + dataset = self.dataset + + # Data loader + data_loader = build_dataloader( + dataset, + samples_per_gpu=samples_per_gpu, + workers_per_gpu=cfg.data.workers_per_gpu, + dist=False, + shuffle=False) + + # Target classes + if 'task_adapt' in cfg: + target_classes = cfg.task_adapt.final + if len(target_classes) < 1: + raise KeyError(f'target_classes={target_classes} is empty check the metadata from model ckpt or recipe ' + f'configuration') + else: + target_classes = dataset.CLASSES + + # Model + cfg.model.pretrained = None + if cfg.model.get('neck'): + if isinstance(cfg.model.neck, list): + for neck_cfg in cfg.model.neck: + if neck_cfg.get('rfp_backbone'): + if neck_cfg.rfp_backbone.get('pretrained'): + neck_cfg.rfp_backbone.pretrained = None + elif cfg.model.neck.get('rfp_backbone'): + if cfg.model.neck.rfp_backbone.get('pretrained'): + cfg.model.neck.rfp_backbone.pretrained = None + + model = build_detector(cfg.model) + model.CLASSES = target_classes + + # TODO: Check Inference FP16 Support + # fp16_cfg = cfg.get('fp16', None) + # if fp16_cfg is not None: + # wrap_fp16_model(model) + + # InferenceProgressCallback (Time Monitor enable into Infer task) + self.set_inference_progress_callback(model, cfg) + + # Checkpoint + if cfg.get('load_from', None): + load_checkpoint(model, cfg.load_from, map_location='cpu') + + model.eval() + if torch.cuda.is_available(): + eval_model = MMDataParallel(model.cuda(cfg.gpu_ids[0]), + device_ids=cfg.gpu_ids) + else: + eval_model = MMDataCPU(model) + + eval_predictions = [] + feature_vectors = [] + saliency_maps = [] + + def dump_features_hook(mod, inp, out): + with torch.no_grad(): + feature_vector = get_feature_vector(out) + assert feature_vector.size(0) == 1 + feature_vectors.append(feature_vector.view(-1).detach().cpu().numpy()) + + def dummy_dump_features_hook(mod, inp, out): + feature_vectors.append(None) + + def dump_saliency_hook(model: torch.nn.Module, input: Tuple, out: List[torch.Tensor]): + """ Dump the last feature map to `saliency_maps` cache + + Args: + model (torch.nn.Module): PyTorch model + input (Tuple): input + out (List[torch.Tensor]): a list of feature maps + """ + with torch.no_grad(): + saliency_map = get_saliency_map(out[-1]) + saliency_maps.append(saliency_map.squeeze(0).detach().cpu().numpy()) + + def dummy_dump_saliency_hook(model, input, out): + saliency_maps.append(None) + + feature_vector_hook = dump_features_hook if dump_features else dummy_dump_features_hook + saliency_map_hook = dump_saliency_hook if dump_saliency_map else dummy_dump_saliency_hook + + with eval_model.module.model_t.backbone.register_forward_hook(feature_vector_hook): + with eval_model.module.model_t.backbone.register_forward_hook(saliency_map_hook): + for data in data_loader: + with torch.no_grad(): + result = eval_model(return_loss=False, rescale=True, **data) + eval_predictions.extend(result) + + for key in [ + 'interval', 'tmpdir', 'start', 'gpu_collect', 'save_best', + 'rule', 'dynamic_intervals' + ]: + cfg.evaluation.pop(key, None) + + metric = None + if eval: + metric = dataset.evaluate(eval_predictions, **cfg.evaluation)[cfg.evaluation.metric] + + outputs = dict( + classes=target_classes, + detections=eval_predictions, + metric=metric, + feature_vectors=feature_vectors, + saliency_maps=saliency_maps + ) + return outputs diff --git a/mpa/det/semisl/stage.py b/mpa/det/semisl/stage.py new file mode 100644 index 00000000..4a8dc881 --- /dev/null +++ b/mpa/det/semisl/stage.py @@ -0,0 +1,69 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +from mmcv import ConfigDict +from mpa.det.incremental import IncrDetectionStage +from mpa.utils.config_utils import update_or_add_custom_hook +from mpa.utils.logger import get_logger + +logger = get_logger() + + +class SemiSLDetectionStage(IncrDetectionStage): + """Patch config to support semi supervised learning for object detection""" + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def configure_data(self, cfg, data_cfg, training, **kwargs): + """ Patch cfg.data.""" + super().configure_data(cfg, data_cfg, training, **kwargs) + if training: + if 'unlabeled' in cfg.data: + if len(cfg.data.unlabeled.get('pipeline', [])) == 0: + cfg.data.unlabeled.pipeline = cfg.data.train.pipeline.copy() + update_or_add_custom_hook( + cfg, + ConfigDict( + type='UnlabeledDataHook', + unlabeled_data_cfg=cfg.data.unlabeled, + samples_per_gpu=cfg.data.unlabeled.pop('samples_per_gpu', cfg.data.samples_per_gpu), + workers_per_gpu=cfg.data.unlabeled.pop('workers_per_gpu', cfg.data.workers_per_gpu), + seed=cfg.seed + ) + ) + + def configure_task(self, cfg, training, **kwargs): + logger.info(f'Semi-SL task config!!!!: training={training}') + super().configure_task(cfg, training, **kwargs) + + def configure_task_cls_incr(self, cfg, task_adapt_type, org_model_classes, model_classes): + """Patch for class incremental learning. + Semi supervised learning should support incrmental learning + """ + if task_adapt_type == 'mpa': + self.configure_bbox_head(cfg, model_classes) + self.configure_task_adapt_hook(cfg, org_model_classes, model_classes) + self.configure_val_interval(cfg) + else: + src_data_cfg = self.get_train_data_cfg(cfg) + src_data_cfg.pop('old_new_indices', None) + + @staticmethod + def configure_task_adapt_hook(cfg, org_model_classes, model_classes): + """Add TaskAdaptHook for sampler. + + TaskAdaptHook does not support ComposedDL + """ + sampler_flag = False + update_or_add_custom_hook( + cfg, + ConfigDict( + type='TaskAdaptHook', + src_classes=org_model_classes, + dst_classes=model_classes, + model_type=cfg.model.type, + sampler_flag=sampler_flag, + efficient_mode=cfg['task_adapt'].get('efficient_mode', False) + ) + ) diff --git a/mpa/det/semisl/trainer.py b/mpa/det/semisl/trainer.py new file mode 100644 index 00000000..cf0316f1 --- /dev/null +++ b/mpa/det/semisl/trainer.py @@ -0,0 +1,169 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +import numbers +import os +import os.path as osp +import time +import glob + +import torch +import torch.distributed as dist +import torch.multiprocessing as mp +from mmcv.utils import get_git_hash +from mmdet import __version__ +from mmdet.apis import train_detector +from mmdet.datasets import build_dataset +from mmdet.models import build_detector +from mmdet.utils import collect_env +# TODO[JAEGUK]: Remove import detection_tasks +# from detection_tasks.apis.detection.config_utils import cluster_anchors + +from mpa.registry import STAGES +from mpa.modules.utils.task_adapt import extract_anchor_ratio +from mpa.utils.logger import get_logger +from mpa.det.semisl.stage import SemiSLDetectionStage + +logger = get_logger() + + +@STAGES.register_module() +class SemiSLDetectionTrainer(SemiSLDetectionStage): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): + """Run training stage for detection + + - Configuration + - Environment setup + - Run training via MMDetection -> MMCV + """ + self._init_logger() + mode = kwargs.get('mode', 'train') + if mode not in self.mode: + return {} + + cfg = self.configure(model_cfg, model_ckpt, data_cfg, **kwargs) + logger.info('train!') + + # # Work directory + # mmcv.mkdir_or_exist(osp.abspath(cfg.work_dir)) + + timestamp = time.strftime('%Y%m%d_%H%M%S', time.localtime()) + + # Environment + distributed = False + if cfg.gpu_ids is not None: + if isinstance(cfg.get('gpu_ids'), numbers.Number): + cfg.gpu_ids = [cfg.get('gpu_ids')] + if len(cfg.gpu_ids) > 1: + distributed = True + + logger.info(f'cfg.gpu_ids = {cfg.gpu_ids}, distributed = {distributed}') + env_info_dict = collect_env() + env_info = '\n'.join([(f'{k}: {v}') for k, v in env_info_dict.items()]) + dash_line = '-' * 60 + '\n' + logger.info('Environment info:\n' + dash_line + env_info + '\n' + dash_line) + + # Data + datasets = [build_dataset(cfg.data.train)] + + if hasattr(cfg, 'hparams'): + if cfg.hparams.get('adaptive_anchor', False): + num_ratios = cfg.hparams.get('num_anchor_ratios', 5) + proposal_ratio = extract_anchor_ratio(datasets[0], num_ratios) + self.configure_anchor(cfg, proposal_ratio) + + # Dataset for HPO + hp_config = kwargs.get('hp_config', None) + if hp_config is not None: + import hpopt + + if isinstance(datasets[0], list): + for idx, ds in enumerate(datasets[0]): + datasets[0][idx] = hpopt.createHpoDataset(ds, hp_config) + else: + datasets[0] = hpopt.createHpoDataset(datasets[0], hp_config) + + # Target classes + if 'task_adapt' in cfg: + target_classes = cfg.task_adapt.get('final', []) + else: + target_classes = datasets[0].CLASSES + + # Metadata + meta = dict() + meta['env_info'] = env_info + # meta['config'] = cfg.pretty_text + meta['seed'] = cfg.seed + meta['exp_name'] = cfg.work_dir + if cfg.checkpoint_config is not None: + cfg.checkpoint_config.meta = dict( + mmdet_version=__version__ + get_git_hash()[:7], + CLASSES=target_classes) + if 'proposal_ratio' in locals(): + cfg.checkpoint_config.meta.update({'anchor_ratio': proposal_ratio}) + + if distributed: + if cfg.dist_params.get('linear_scale_lr', False): + new_lr = len(cfg.gpu_ids) * cfg.optimizer.lr + logger.info(f'enabled linear scaling rule to the learning rate. \ + changed LR from {cfg.optimizer.lr} to {new_lr}') + cfg.optimizer.lr = new_lr + + # Save config + # cfg.dump(osp.join(cfg.work_dir, 'config.py')) + # logger.info(f'Config:\n{cfg.pretty_text}') + + if distributed: + os.environ['MASTER_ADDR'] = cfg.dist_params.get('master_addr', 'localhost') + os.environ['MASTER_PORT'] = cfg.dist_params.get('master_port', '29500') + mp.spawn(self.train_worker, nprocs=len(cfg.gpu_ids), + args=(target_classes, datasets, cfg, distributed, True, timestamp, meta)) + else: + self.train_worker( + None, + target_classes, + datasets, + cfg, + distributed, + True, + timestamp, + meta) + + # Save outputs + output_ckpt_path = osp.join(cfg.work_dir, 'latest.pth') + best_ckpt_path = glob.glob(osp.join(cfg.work_dir, 'best_*.pth')) + if len(best_ckpt_path) > 0: + output_ckpt_path = best_ckpt_path[0] + return dict(final_ckpt=output_ckpt_path) + + @staticmethod + def train_worker(gpu, target_classes, datasets, cfg, distributed=False, + validate=False, timestamp=None, meta=None): + if distributed: + torch.cuda.set_device(gpu) + dist.init_process_group(backend=cfg.dist_params.get('backend', 'nccl'), + world_size=len(cfg.gpu_ids), rank=gpu) + logger.info(f'dist info world_size = {dist.get_world_size()}, rank = {dist.get_rank()}') + + # model + model = build_detector(cfg.model) + model.CLASSES = target_classes + # Do clustering for SSD model + # TODO[JAEGUK]: Temporal Disable cluster_anchors for SSD model + # if hasattr(cfg.model, 'bbox_head') and hasattr(cfg.model.bbox_head, 'anchor_generator'): + # if getattr(cfg.model.bbox_head.anchor_generator, 'reclustering_anchors', False): + # train_cfg = Stage.get_train_data_cfg(cfg) + # train_dataset = train_cfg.get('otx_dataset', None) + # cfg, model = cluster_anchors(cfg, train_dataset, model) + train_detector( + model, + datasets, + cfg, + distributed=distributed, + validate=True, + timestamp=timestamp, + meta=meta) diff --git a/mpa/det/stage.py b/mpa/det/stage.py index ae5d2312..d330d518 100644 --- a/mpa/det/stage.py +++ b/mpa/det/stage.py @@ -15,6 +15,7 @@ class DetectionStage(Stage): + """Patch config to support otx train.""" def __init__(self, **kwargs): super().__init__(**kwargs) @@ -23,8 +24,22 @@ def configure(self, model_cfg, model_ckpt, data_cfg, training=True, **kwargs): """ logger.info(f'configure!: training={training}') - # Recipe + model cfg = self.cfg + self.configure_model(cfg, model_cfg, training, **kwargs) + self.configure_ckpt(cfg, model_ckpt, kwargs.get('pretrained', None)) + self.configure_data(cfg, data_cfg, training, **kwargs) + self.configure_regularization(cfg, training) + self.configure_hyperparams(cfg, training, **kwargs) + self.configure_task(cfg, training, **kwargs) + self.configure_hook(cfg) + return cfg + + def configure_model(self, cfg, model_cfg, training, **kwargs): + """ Patch config's model. + Replace cfg.model to model_cfg + Change model type to super type + Patch for OMZ backbones + """ if model_cfg: if hasattr(model_cfg, 'model'): cfg.merge_from_dict(model_cfg._cfg_dict) @@ -36,42 +51,7 @@ def configure(self, model_cfg, model_ckpt, data_cfg, training=True, **kwargs): raise ValueError( f'Given model_cfg ({model_cfg.filename}) is not supported by detection recipe' ) - self.configure_model(cfg, training, **kwargs) - - # Checkpoint - if model_ckpt: - cfg.load_from = self.get_model_ckpt(model_ckpt) - pretrained = kwargs.get('pretrained', None) - if pretrained and isinstance(pretrained, str): - logger.info(f'Overriding cfg.load_from -> {pretrained}') - cfg.load_from = pretrained # Overriding by stage input - - if cfg.get('resume', False): - cfg.resume_from = cfg.load_from - - # Data - if data_cfg: - cfg.merge_from_dict(data_cfg) - self.configure_data(cfg, training, **kwargs) - - # Task - if 'task_adapt' in cfg: - self.configure_task(cfg, training, **kwargs) - - # Regularization - if training: - self.configure_regularization(cfg) - - # Other hyper-parameters - if 'hyperparams' in cfg: - self.configure_hyperparams(cfg, training, **kwargs) - - # Hooks - self.configure_hook(cfg) - - return cfg - def configure_model(self, cfg, training, **kwargs): super_type = cfg.model.pop('super_type', None) if super_type: cfg.model.arch_type = cfg.model.type @@ -90,33 +70,35 @@ def configure_model(self, cfg, training, **kwargs): else: raise NotImplementedError(f'Unknown model type - {cfg.model.type}') - def configure_anchor(self, cfg, proposal_ratio=None): - if cfg.model.type in ['SingleStageDetector', 'CustomSingleStageDetector']: - anchor_cfg = cfg.model.bbox_head.anchor_generator - if anchor_cfg.type == 'SSDAnchorGeneratorClustered': - cfg.model.bbox_head.anchor_generator.pop('input_size', None) + def configure_ckpt(self, cfg, model_ckpt, pretrained): + """ Patch checkpoint path for pretrained weight. + Replace cfg.load_from to model_ckpt + Replace cfg.load_from to pretrained + Replace cfg.resume_from to cfg.load_from + """ + if model_ckpt: + cfg.load_from = self.get_model_ckpt(model_ckpt) + if pretrained and isinstance(pretrained, str): + logger.info(f'Overriding cfg.load_from -> {pretrained}') + cfg.load_from = pretrained # Overriding by stage input + if cfg.get('resume', False): + cfg.resume_from = cfg.load_from + + def configure_data(self, cfg, data_cfg, training, **kwargs): + """ Patch cfg.data. + Merge cfg and data_cfg + Match cfg.data.train.type to super_type + Patch for unlabeled data path ==> This may be moved to SemiDetectionStage + """ + if data_cfg: + cfg.merge_from_dict(data_cfg) - def configure_data(self, cfg, training, **kwargs): Stage.configure_data(cfg, training, **kwargs) super_type = cfg.data.train.pop('super_type', None) if super_type: cfg.data.train.org_type = cfg.data.train.type cfg.data.train.type = super_type if training: - if 'unlabeled' in cfg.data and cfg.data.unlabeled.get('img_file', None): - cfg.data.unlabeled.ann_file = cfg.data.unlabeled.pop('img_file') - if len(cfg.data.unlabeled.get('pipeline', [])) == 0: - cfg.data.unlabeled.pipeline = cfg.data.train.pipeline.copy() - update_or_add_custom_hook( - cfg, - ConfigDict( - type='UnlabeledDataHook', - unlabeled_data_cfg=cfg.data.unlabeled, - samples_per_gpu=cfg.data.unlabeled.pop('samples_per_gpu', cfg.data.samples_per_gpu), - workers_per_gpu=cfg.data.unlabeled.pop('workers_per_gpu', cfg.data.workers_per_gpu), - seed=cfg.seed - ) - ) if 'dataset' in cfg.data.train: train_cfg = self.get_train_data_cfg(cfg) train_cfg.otx_dataset = cfg.data.train.pop('otx_dataset', None) @@ -124,281 +106,41 @@ def configure_data(self, cfg, training, **kwargs): train_cfg.data_classes = cfg.data.train.pop('data_classes', None) train_cfg.new_classes = cfg.data.train.pop('new_classes', None) - def configure_task(self, cfg, training, **kwargs): - """Adjust settings for task adaptation - """ - logger.info(f'task config!!!!: training={training}') - task_adapt_type = cfg['task_adapt'].get('type', None) - task_adapt_op = cfg['task_adapt'].get('op', 'REPLACE') - - # Task classes - org_model_classes, model_classes, data_classes = \ - self.configure_task_classes(cfg, task_adapt_type, task_adapt_op) - - # Data pipeline - if data_classes != model_classes: - self.configure_task_data_pipeline(cfg, model_classes, data_classes) - - # Evaluation dataset - if cfg.get('task', 'detection') == 'detection': - self.configure_task_eval_dataset(cfg, model_classes) - - # Training hook for task adaptation - self.configure_task_adapt_hook(cfg, org_model_classes, model_classes) - - # Anchor setting - # TODO[JAEGUK]: configure_anchor is not working - if cfg['task_adapt'].get('use_mpa_anchor', False): - self.configure_anchor(cfg) - - # Incremental learning - self.configure_task_cls_incr(cfg, task_adapt_type, org_model_classes, model_classes) - - def configure_task_classes(self, cfg, task_adapt_type, task_adapt_op): - - # Input classes - org_model_classes = self.get_model_classes(cfg) - data_classes = self.get_data_classes(cfg) - - # Model classes - if task_adapt_op == 'REPLACE': - if len(data_classes) == 0: - model_classes = org_model_classes.copy() - else: - model_classes = data_classes.copy() - elif task_adapt_op == 'MERGE': - model_classes = org_model_classes + [cls for cls in data_classes if cls not in org_model_classes] - else: - raise KeyError(f'{task_adapt_op} is not supported for task_adapt options!') - - if task_adapt_type == 'mpa': - data_classes = model_classes - cfg.task_adapt.final = model_classes - cfg.model.task_adapt = ConfigDict( - src_classes=org_model_classes, - dst_classes=model_classes, - ) - - # Model architecture - head_names = ('mask_head', 'bbox_head', 'segm_head') - num_classes = len(model_classes) - if 'roi_head' in cfg.model: - # For Faster-RCNNs - for head_name in head_names: - if head_name in cfg.model.roi_head: - if isinstance(cfg.model.roi_head[head_name], list): - for head in cfg.model.roi_head[head_name]: - head.num_classes = num_classes - else: - cfg.model.roi_head[head_name].num_classes = num_classes - else: - # For other architectures (including SSD) - for head_name in head_names: - if head_name in cfg.model: - cfg.model[head_name].num_classes = num_classes - - return org_model_classes, model_classes, data_classes - - def configure_task_data_pipeline(self, cfg, model_classes, data_classes): - # Trying to alter class indices of training data according to model class order - tr_data_cfg = self.get_train_data_cfg(cfg) - class_adapt_cfg = dict(type='AdaptClassLabels', src_classes=data_classes, dst_classes=model_classes) - pipeline_cfg = tr_data_cfg.pipeline - for i, op in enumerate(pipeline_cfg): - if op['type'] == 'LoadAnnotations': # insert just after this op - op_next_ann = pipeline_cfg[i + 1] if i + 1 < len(pipeline_cfg) else {} - if op_next_ann.get('type', '') == class_adapt_cfg['type']: - op_next_ann.update(class_adapt_cfg) - else: - pipeline_cfg.insert(i + 1, class_adapt_cfg) - break - - def configure_task_eval_dataset(self, cfg, model_classes): - # - Altering model outputs according to dataset class order - eval_types = ['val', 'test'] - for eval_type in eval_types: - if cfg.data[eval_type]['type'] == 'TaskAdaptEvalDataset': - cfg.data[eval_type]['model_classes'] = model_classes - else: - # Wrap original dataset config - org_type = cfg.data[eval_type]['type'] - cfg.data[eval_type]['type'] = 'TaskAdaptEvalDataset' - cfg.data[eval_type]['org_type'] = org_type - cfg.data[eval_type]['model_classes'] = model_classes - - def configure_task_adapt_hook(self, cfg, org_model_classes, model_classes): - task_adapt_hook = ConfigDict( - type='TaskAdaptHook', - src_classes=org_model_classes, - dst_classes=model_classes, - model_type=cfg.model.type, - ) - update_or_add_custom_hook(cfg, task_adapt_hook) - - def configure_task_cls_incr(self, cfg, task_adapt_type, org_model_classes, model_classes): - if cfg.get('task', 'detection') == 'detection': - bbox_head = cfg.model.bbox_head - else: - bbox_head = cfg.model.roi_head.bbox_head - if task_adapt_type == 'mpa': - tr_data_cfg = self.get_train_data_cfg(cfg) - if tr_data_cfg.type != 'MPADetDataset': - tr_data_cfg.img_ids_dict = self.get_img_ids_for_incr(cfg, org_model_classes, model_classes) - tr_data_cfg.org_type = tr_data_cfg.type - tr_data_cfg.type = 'DetIncrCocoDataset' - alpha, gamma = 0.25, 2.0 - if bbox_head.type in ['SSDHead', 'CustomSSDHead']: - gamma = 1 if cfg['task_adapt'].get('efficient_mode', False) else 2 - bbox_head.type = 'CustomSSDHead' - bbox_head.loss_cls = ConfigDict( - type='FocalLoss', - loss_weight=1.0, - gamma=gamma, - reduction='none', - ) - elif bbox_head.type in ['ATSSHead']: - gamma = 3 if cfg['task_adapt'].get('efficient_mode', False) else 4.5 - bbox_head.loss_cls.gamma = gamma - elif bbox_head.type in ['VFNetHead', 'CustomVFNetHead']: - alpha = 0.75 - gamma = 1 if cfg['task_adapt'].get('efficient_mode', False) else 2 - elif bbox_head.type in ['YOLOXHead', 'CustomYOLOXHead']: - if cfg.data.train.type == 'MultiImageMixDataset': - cfg.data.train.pop('ann_file', None) - cfg.data.train.pop('img_prefix', None) - cfg.data.train['labels'] = cfg.data.train.pop('labels', None) - self.add_yolox_hooks(cfg) - # Ignore Mode - if cfg.get('ignore', False): - bbox_head.loss_cls = ConfigDict( - type='CrossSigmoidFocalLoss', - use_sigmoid=True, - num_classes=len(model_classes), - alpha=alpha, - gamma=gamma - ) - - sampler_flag = True - if len(set(org_model_classes) & set(model_classes)) == 0 or set(org_model_classes) == set(model_classes): - sampler_flag = False - - update_or_add_custom_hook( - cfg, - ConfigDict( - type='TaskAdaptHook', - sampler_flag=sampler_flag, - efficient_mode=cfg['task_adapt'].get('efficient_mode', False) - ) - ) - - adaptive_ema = cfg.get('adaptive_ema', {}) - if adaptive_ema: - update_or_add_custom_hook( - cfg, - ConfigDict( - type='CustomModelEMAHook', - priority='ABOVE_NORMAL', - resume_from=cfg.get("resume_from"), - **adaptive_ema - ) - ) - else: - update_or_add_custom_hook(cfg, ConfigDict(type='EMAHook', priority='ABOVE_NORMAL', resume_from=cfg.get("resume_from"), momentum=0.1)) - - adaptive_validation_interval = cfg.get('adaptive_validation_interval', {}) - if adaptive_validation_interval: - update_or_add_custom_hook( - cfg, - ConfigDict(type='AdaptiveTrainSchedulingHook', **adaptive_validation_interval) - ) - else: - src_data_cfg = Stage.get_train_data_cfg(cfg) - src_data_cfg.pop('old_new_indices', None) - - def configure_regularization(self, cfg): - if cfg.model.get('l2sp_weight', 0.0) > 0.0: - logger.info('regularization config!!!!') - - # Checkpoint - l2sp_ckpt = cfg.model.get('l2sp_ckpt', None) - if l2sp_ckpt is None: - if 'pretrained' in cfg.model: - l2sp_ckpt = cfg.model.pretrained - if cfg.load_from: - l2sp_ckpt = cfg.load_from - cfg.model.l2sp_ckpt = l2sp_ckpt - - # Disable weight decay - if 'weight_decay' in cfg.optimizer: - cfg.optimizer.weight_decay = 0.0 - - @staticmethod - def get_img_ids_for_incr(cfg, org_model_classes, model_classes): - # get image ids of old classes & new class - # to setup experimental dataset (COCO format) - new_classes = np.setdiff1d(model_classes, org_model_classes).tolist() - old_classes = np.intersect1d(org_model_classes, model_classes).tolist() - - src_data_cfg = Stage.get_train_data_cfg(cfg) - - ids_old, ids_new = [], [] - data_cfg = cfg.data.test.copy() - data_cfg.test_mode = src_data_cfg.get('test_mode', False) - data_cfg.ann_file = src_data_cfg.get('ann_file', None) - data_cfg.img_prefix = src_data_cfg.get('img_prefix', None) - old_data_cfg = data_cfg.copy() - if 'classes' in old_data_cfg: - old_data_cfg.classes = old_classes - old_dataset = build_dataset(old_data_cfg) - ids_old = old_dataset.dataset.img_ids - if len(new_classes) > 0: - data_cfg.classes = new_classes - dataset = build_dataset(data_cfg) - ids_new = dataset.dataset.img_ids - ids_old = np.setdiff1d(ids_old, ids_new).tolist() - - sampled_ids = ids_old + ids_new - outputs = dict( - old_classes=old_classes, - new_classes=new_classes, - img_ids=sampled_ids, - img_ids_old=ids_old, - img_ids_new=ids_new, - ) - return outputs + def configure_regularization(self, cfg, training): + """Patch regularization parameters.""" + if training: + if cfg.model.get('l2sp_weight', 0.0) > 0.0: + logger.info('regularization config!!!!') + + # Checkpoint + l2sp_ckpt = cfg.model.get('l2sp_ckpt', None) + if l2sp_ckpt is None: + if 'pretrained' in cfg.model: + l2sp_ckpt = cfg.model.pretrained + if cfg.load_from: + l2sp_ckpt = cfg.load_from + cfg.model.l2sp_ckpt = l2sp_ckpt + + # Disable weight decay + if 'weight_decay' in cfg.optimizer: + cfg.optimizer.weight_decay = 0.0 def configure_hyperparams(self, cfg, training, **kwargs): - hyperparams = kwargs.get('hyperparams', None) - if hyperparams is not None: - bs = hyperparams.get('bs', None) - if bs is not None: - cfg.data.samples_per_gpu = bs + """Patch optimization hyparms such as batch size, learning rate.""" + if 'hyperparams' in cfg: + hyperparams = kwargs.get('hyperparams', None) + if hyperparams is not None: + bs = hyperparams.get('bs', None) + if bs is not None: + cfg.data.samples_per_gpu = bs - lr = hyperparams.get('lr', None) - if lr is not None: - cfg.optimizer.lr = lr + lr = hyperparams.get('lr', None) + if lr is not None: + cfg.optimizer.lr = lr - @staticmethod - def add_yolox_hooks(cfg): - update_or_add_custom_hook( - cfg, - ConfigDict( - type='YOLOXModeSwitchHook', - num_last_epochs=15, - priority=48)) - update_or_add_custom_hook( - cfg, - ConfigDict( - type='SyncRandomSizeHook', - ratio_range=(10, 20), - img_scale=(640, 640), - interval=1, - priority=48, - device='cuda' if torch.cuda.is_available() else 'cpu')) - update_or_add_custom_hook( - cfg, - ConfigDict( - type='SyncNormHook', - num_last_epochs=15, - interval=1, - priority=48)) + def configure_task(self, cfg, training, **kwargs): + """Patch config to support training algorithm. + + This should be implemented each algorithm + """ + pass diff --git a/mpa/det/trainer.py b/mpa/det/trainer.py index 980eb20f..fd954aa3 100644 --- a/mpa/det/trainer.py +++ b/mpa/det/trainer.py @@ -21,16 +21,16 @@ # from detection_tasks.apis.detection.config_utils import cluster_anchors from mpa.registry import STAGES -from .stage import DetectionStage from mpa.modules.utils.task_adapt import extract_anchor_ratio from mpa.utils.logger import get_logger -from mpa.stage import Stage +from mpa.det.incremental import IncrDetectionStage logger = get_logger() +#FIXME DetectionTrainer does not inherit from stage @STAGES.register_module() -class DetectionTrainer(DetectionStage): +class DetectionTrainer(IncrDetectionStage): def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/mpa/stage.py b/mpa/stage.py index d00ab585..f65c4454 100644 --- a/mpa/stage.py +++ b/mpa/stage.py @@ -243,7 +243,7 @@ def update_hook(opt, custom_hooks, idx, hook): hook.update(**opt) custom_hook_options = cfg.pop('custom_hook_options', {}) - logger.info(f"configure_hook() {cfg.get('custom_hooks', [])} <- {custom_hook_options}") + # logger.info(f"configure_hook() {cfg.get('custom_hooks', [])} <- {custom_hook_options}") custom_hooks = cfg.get('custom_hooks', []) for idx, hook in enumerate(custom_hooks): for opt_key, opt in custom_hook_options.items(): diff --git a/recipes/stages/_base_/data/coco_ubt.py b/recipes/stages/_base_/data/coco_ubt.py index e5c49081..bbb9a19f 100644 --- a/recipes/stages/_base_/data/coco_ubt.py +++ b/recipes/stages/_base_/data/coco_ubt.py @@ -18,22 +18,9 @@ workers_per_gpu=2, train=dict( type=__dataset_type, - super_type='PseudoBalancedDataset', ann_file=__data_root_path + 'annotations/instances_train2017.json', img_prefix=__data_root_path + 'train2017/', pipeline=__train_pipeline, - classes=[ - 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truckl', - 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', - 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', - 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', - 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', - 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', - 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', - 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', - 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', - 'hair drier', 'toothbrush' - ] ), unlabeled=dict( type=__dataset_type, diff --git a/recipes/stages/detection/semisl.py b/recipes/stages/detection/semisl.py index 85c036c6..d3840fd9 100644 --- a/recipes/stages/detection/semisl.py +++ b/recipes/stages/detection/semisl.py @@ -1,9 +1,13 @@ _base_ = [ - './finetune.py', + './train.py', + '../_base_/data/coco_ubt.py', + '../_base_/models/detectors/detector.py' ] -model = dict( - unlabeled_loss_weight=1.0, +task_adapt = dict( + type='mpa', + op='REPLACE', + efficient_mode=False, ) custom_hooks = [ @@ -14,6 +18,11 @@ # min_pseudo_label_ratio=0.1, min_pseudo_label_ratio=0.0, ), + dict( + type='DualModelEMAHook', + epoch_momentum=0.4, + start_epoch=2, + ), dict( type='LazyEarlyStoppingHook', start=3,