From 8b2c3deb0b63202a069d8dbd1247382f05246dba Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Mon, 28 Nov 2022 15:12:06 +0900 Subject: [PATCH 01/15] Initial refactoring stage.py to support multiple training algorithms --- mpa/det/evaluator.py | 2 +- mpa/det/exporter.py | 16 +- mpa/det/incr_stage.py | 282 ++++++++++++++++++++++++++++++ mpa/det/inferrer.py | 15 +- mpa/det/semi_stage.py | 42 +++++ mpa/det/stage.py | 386 ++++++++---------------------------------- mpa/det/trainer.py | 15 +- mpa/det/utils.py | 14 ++ 8 files changed, 433 insertions(+), 339 deletions(-) create mode 100644 mpa/det/incr_stage.py create mode 100644 mpa/det/semi_stage.py create mode 100644 mpa/det/utils.py diff --git a/mpa/det/evaluator.py b/mpa/det/evaluator.py index 7fd96743..b5670f84 100644 --- a/mpa/det/evaluator.py +++ b/mpa/det/evaluator.py @@ -24,7 +24,7 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): """ self._init_logger() mode = kwargs.get('mode', 'train') - if mode not in self.mode: + if mode not in self.patcher.mode: return {} cfg = self.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) diff --git a/mpa/det/exporter.py b/mpa/det/exporter.py index dccbd9cb..a6190410 100644 --- a/mpa/det/exporter.py +++ b/mpa/det/exporter.py @@ -12,25 +12,25 @@ from mpa.registry import STAGES from mpa.utils.logger import get_logger -from .stage import DetectionStage +from mpa.det.utils import load_patcher logger = get_logger() @STAGES.register_module() -class DetectionExporter(DetectionStage): - def __init__(self, **kwargs): - super().__init__(**kwargs) +class DetectionExporter: + def __init__(self, training_type='incremental', **kwargs): + self.patcher = load_patcher(training_type, **kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): - self._init_logger() + self.patcher._init_logger() logger.info('exporting the model') mode = kwargs.get('mode', 'train') - if mode not in self.mode: + if mode not in self.patcher.mode: logger.warning(f'mode for this stage {mode}') return {} - cfg = self.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) + cfg = self.patcher.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) output_path = os.path.join(cfg.work_dir, 'export') os.makedirs(output_path, exist_ok=True) @@ -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/incr_stage.py b/mpa/det/incr_stage.py new file mode 100644 index 00000000..fa4fffcb --- /dev/null +++ b/mpa/det/incr_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 + """ + if 'task_adapt' in cfg: + 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') + + 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..2cc5e9c4 100644 --- a/mpa/det/inferrer.py +++ b/mpa/det/inferrer.py @@ -13,16 +13,17 @@ 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.utils import load_patcher +from mpa.det.stage import DetectionStage logger = get_logger() @STAGES.register_module() -class DetectionInferrer(DetectionStage): - def __init__(self, **kwargs): - super().__init__(**kwargs) +class DetectionInferrer: + def __init__(self, training_type='incremental', **kwargs): + self.patcher = load_patcher(training_type, **kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): """Run inference stage for detection @@ -31,15 +32,15 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): - Environment setup - Run inference via MMDetection -> MMCV """ - self._init_logger() + self.patcher._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: + if mode not in self.patcher.mode: return {} - cfg = self.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) + cfg = self.patcher.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!') diff --git a/mpa/det/semi_stage.py b/mpa/det/semi_stage.py new file mode 100644 index 00000000..3fddeb01 --- /dev/null +++ b/mpa/det/semi_stage.py @@ -0,0 +1,42 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +from mpa.det.incr_stage import IncrDetectionStage +from mpa.utils.logger import get_logger + +logger = get_logger() + + +class SemiDetectionStage(IncrDetectionStage): + """Patch config to support semi supervised learning for object detection""" + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def configure_task(self, cfg, training, **kwargs): + if 'task_adapt' in cfg: + 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') + + 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) + self.configure_task_semi() + + def configure_task_cls_incr(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) diff --git a/mpa/det/stage.py b/mpa/det/stage.py index ae5d2312..199183bd 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,13 +70,29 @@ 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: @@ -124,281 +120,39 @@ 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 patcher""" + pass diff --git a/mpa/det/trainer.py b/mpa/det/trainer.py index 980eb20f..d708fbc9 100644 --- a/mpa/det/trainer.py +++ b/mpa/det/trainer.py @@ -21,18 +21,19 @@ # 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.utils import load_patcher logger = get_logger() +#FIXME DetectionTrainer does not inherit from stage @STAGES.register_module() -class DetectionTrainer(DetectionStage): - def __init__(self, **kwargs): - super().__init__(**kwargs) +class DetectionTrainer: + def __init__(self, training_type='incremental', **kwargs): + self.patcher = load_patcher(training_type, **kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): """Run training stage for detection @@ -41,12 +42,12 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): - Environment setup - Run training via MMDetection -> MMCV """ - self._init_logger() + self.patcher._init_logger() mode = kwargs.get('mode', 'train') - if mode not in self.mode: + if mode not in self.patcher.mode: return {} - cfg = self.configure(model_cfg, model_ckpt, data_cfg, **kwargs) + cfg = self.patcher.configure(model_cfg, model_ckpt, data_cfg, **kwargs) logger.info('train!') # # Work directory diff --git a/mpa/det/utils.py b/mpa/det/utils.py new file mode 100644 index 00000000..a375c4c0 --- /dev/null +++ b/mpa/det/utils.py @@ -0,0 +1,14 @@ +"""Utils for detection task.""" +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +from mpa.det.incr_stage import IncrDetectionStage + + +def load_patcher(training_type, **kwargs): + if training_type == 'incremental': + patcher = IncrDetectionStage(**kwargs) + else: + raise NotImplementedError(f"{training_type} is not supported in detection task") + return patcher From 6a18d9b75f9f98d2c0d9de57fbdd20b78a4b3efa Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Wed, 23 Nov 2022 10:49:43 +0900 Subject: [PATCH 02/15] Initial changes for enabling semisl detection --- mpa/det/stage.py | 3 +-- .../models/detectors/unbiased_teacher.py | 17 +++++++++++++++++ mpa/stage.py | 2 +- recipes/stages/_base_/data/coco_ubt.py | 13 ------------- recipes/stages/detection/finetune.py | 6 ++++++ recipes/stages/detection/semisl.py | 2 ++ 6 files changed, 27 insertions(+), 16 deletions(-) diff --git a/mpa/det/stage.py b/mpa/det/stage.py index 199183bd..3a28e0d0 100644 --- a/mpa/det/stage.py +++ b/mpa/det/stage.py @@ -99,8 +99,7 @@ def configure_data(self, cfg, data_cfg, training, **kwargs): 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 '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( diff --git a/mpa/modules/models/detectors/unbiased_teacher.py b/mpa/modules/models/detectors/unbiased_teacher.py index 92c3987a..5c5f05d7 100644 --- a/mpa/modules/models/detectors/unbiased_teacher.py +++ b/mpa/modules/models/detectors/unbiased_teacher.py @@ -128,9 +128,26 @@ def forward_train(self, loss*self.unlabeled_loss_weight for loss in ul_loss ] # TODO: apply loss_bbox when adopting QFL; + self.save_pseudo_labels(ul_img, pseudo_bboxes, pseudo_labels) return losses + def save_pseudo_labels(self, ul_imgs, pseudo_bboxes, pseudo_labels): + """This saves unlabeled image with pseudo bboxes, this is for debug.""" + import os + + self.save_dir = "/tmp/semisl-det/" + os.makedirs(self.save_dir, exist_ok=True) + + if not hasattr(self, "idx"): + self.idx = 0 + else: + self.idx += 1 + + for ul_img, pseudo_bbox, pseudo_label in zip(ul_imgs, pseudo_bboxes, pseudo_labels): + breakpoint() + + def generate_pseudo_labels(self, teacher_outputs, **kwargs): all_pseudo_bboxes = [] all_pseudo_labels = [] 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/finetune.py b/recipes/stages/detection/finetune.py index 61bf0dfd..a76e12df 100644 --- a/recipes/stages/detection/finetune.py +++ b/recipes/stages/detection/finetune.py @@ -4,6 +4,12 @@ '../_base_/models/detectors/detector.py' ] +task_adapt = dict( + type='mpa', + op='REPLACE', + efficient_mode=False, +) + model = dict(super_type='UnbiasedTeacher') # Used as general framework custom_hooks = [ diff --git a/recipes/stages/detection/semisl.py b/recipes/stages/detection/semisl.py index 85c036c6..2d681f5e 100644 --- a/recipes/stages/detection/semisl.py +++ b/recipes/stages/detection/semisl.py @@ -24,3 +24,5 @@ priority=75, ), ] +ignore = True +adaptive_validation_interval = dict(max_interval=5) From e2aa944e6a5a3849b65d218b69430ee085aa0e94 Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Fri, 25 Nov 2022 10:03:47 +0900 Subject: [PATCH 03/15] Modify semi-sl stage --- mpa/modules/hooks/model_ema_hook.py | 9 +++++ .../models/detectors/unbiased_teacher.py | 36 ++++++++++++++----- mpa/stage.py | 1 + recipes/stages/_base_/default.py | 2 +- recipes/stages/detection/finetune.py | 8 +---- recipes/stages/detection/semisl.py | 2 -- 6 files changed, 39 insertions(+), 19 deletions(-) diff --git a/mpa/modules/hooks/model_ema_hook.py b/mpa/modules/hooks/model_ema_hook.py index 95da07d6..2b89cb17 100644 --- a/mpa/modules/hooks/model_ema_hook.py +++ b/mpa/modules/hooks/model_ema_hook.py @@ -125,6 +125,15 @@ def _copy_model(self): def _ema_model(self): momentum = min(self.momentum, 1.0) + + # For debug + # if hasattr(self, "momentum_cache"): + # if self.momentum != self.momentum_cache: + # print(f"Momentum is changed: {self.momentum_cache} ==> {self.momentum}") + # self.momentum_cache = self.momentum + # else: + # self.momentum_cache = self.momentum + with torch.no_grad(): for name, src_param in self.src_params.items(): dst_param = self.dst_params[name] diff --git a/mpa/modules/models/detectors/unbiased_teacher.py b/mpa/modules/models/detectors/unbiased_teacher.py index 5c5f05d7..6ef7a3f5 100644 --- a/mpa/modules/models/detectors/unbiased_teacher.py +++ b/mpa/modules/models/detectors/unbiased_teacher.py @@ -128,25 +128,43 @@ def forward_train(self, loss*self.unlabeled_loss_weight for loss in ul_loss ] # TODO: apply loss_bbox when adopting QFL; - self.save_pseudo_labels(ul_img, pseudo_bboxes, pseudo_labels) + # self.save_pseudo_labels(ul_img0, pseudo_bboxes, pseudo_labels) return losses def save_pseudo_labels(self, ul_imgs, pseudo_bboxes, pseudo_labels): """This saves unlabeled image with pseudo bboxes, this is for debug.""" import os + import cv2 as cv - self.save_dir = "/tmp/semisl-det/" + self.save_dir = "/home/jaeguk/workspace/tmp/semisl-det/" os.makedirs(self.save_dir, exist_ok=True) - if not hasattr(self, "idx"): - self.idx = 0 + if not hasattr(self, "prefix"): + self.prefix = 0 else: - self.idx += 1 - - for ul_img, pseudo_bbox, pseudo_label in zip(ul_imgs, pseudo_bboxes, pseudo_labels): - breakpoint() - + self.prefix += 1 + + colors = [[0,0,255], [0,255,0], [255,0,0]] + + for idx, (ul_img, pseudo_bbox, pseudo_label) in enumerate(zip(ul_imgs, pseudo_bboxes, pseudo_labels)): + # Change tensor to numpy image + ul_img = ul_img.cpu().permute(1, 2, 0).numpy() + # Reverse normalization + ul_img = ul_img * 255 + ul_img = np.ascontiguousarray(ul_img, dtype=np.uint8) + # If there is no pseudo box, then save image + if len(pseudo_bbox) == 0: + cv.imwrite(os.path.join(self.save_dir, f"{self.prefix}_{idx}.jpg"), ul_img) + continue + # Draw bbox + for bbox, label in zip(pseudo_bbox, pseudo_label): + x1, y1, x2, y2 = bbox.to(int).cpu().numpy() + start = (x1, y1) + end = (x2, y2) + ul_img = cv.rectangle(ul_img, start, end, colors[label], 1) + # Save image to pre-defined path + cv.imwrite(os.path.join(self.save_dir, f"{self.prefix}_{idx}.jpg"), ul_img) def generate_pseudo_labels(self, teacher_outputs, **kwargs): all_pseudo_bboxes = [] diff --git a/mpa/stage.py b/mpa/stage.py index f65c4454..1948ddd0 100644 --- a/mpa/stage.py +++ b/mpa/stage.py @@ -32,6 +32,7 @@ def _set_random_seed(seed, deterministic=False): to True and `torch.backends.cudnn.benchmark` to False. Default: False. """ + seed = random.randint(0, 10000) random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) diff --git a/recipes/stages/_base_/default.py b/recipes/stages/_base_/default.py index 4f02e25e..269bc418 100644 --- a/recipes/stages/_base_/default.py +++ b/recipes/stages/_base_/default.py @@ -4,7 +4,7 @@ cudnn_benchmark = True -seed = 5 +seed = 1234 deterministic = False hparams = dict(dummy=0) diff --git a/recipes/stages/detection/finetune.py b/recipes/stages/detection/finetune.py index a76e12df..904315eb 100644 --- a/recipes/stages/detection/finetune.py +++ b/recipes/stages/detection/finetune.py @@ -4,13 +4,7 @@ '../_base_/models/detectors/detector.py' ] -task_adapt = dict( - type='mpa', - op='REPLACE', - efficient_mode=False, -) - -model = dict(super_type='UnbiasedTeacher') # Used as general framework +model = dict(super_type='UnbiasedTeacher', pseudo_conf_thresh=0.25) # Used as general framework custom_hooks = [ dict( diff --git a/recipes/stages/detection/semisl.py b/recipes/stages/detection/semisl.py index 2d681f5e..85c036c6 100644 --- a/recipes/stages/detection/semisl.py +++ b/recipes/stages/detection/semisl.py @@ -24,5 +24,3 @@ priority=75, ), ] -ignore = True -adaptive_validation_interval = dict(max_interval=5) From b693e61a78ae2a03782651021dd74af137daa384 Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Mon, 28 Nov 2022 15:56:24 +0900 Subject: [PATCH 04/15] Modify semi-sl recipe --- mpa/det/incr_stage.py | 2 +- mpa/det/semi_stage.py | 18 +++--------------- mpa/det/trainer.py | 6 ++++++ mpa/det/utils.py | 3 +++ .../models/detectors/unbiased_teacher.py | 1 + recipes/stages/detection/semisl.py | 6 ++++++ 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/mpa/det/incr_stage.py b/mpa/det/incr_stage.py index fa4fffcb..341817fe 100644 --- a/mpa/det/incr_stage.py +++ b/mpa/det/incr_stage.py @@ -22,8 +22,8 @@ def __init__(self, **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: - 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') diff --git a/mpa/det/semi_stage.py b/mpa/det/semi_stage.py index 3fddeb01..bab6dd82 100644 --- a/mpa/det/semi_stage.py +++ b/mpa/det/semi_stage.py @@ -14,22 +14,10 @@ def __init__(self, **kwargs): super().__init__(**kwargs) def configure_task(self, cfg, training, **kwargs): - if 'task_adapt' in cfg: - 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') + logger.info(f'Semi-SL task config!!!!: training={training}') + super().configure_task(cfg, training, **kwargs) - 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) - self.configure_task_semi() - - def configure_task_cls_incr(cfg, task_adapt_type, org_model_classes, model_classes): + 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 """ diff --git a/mpa/det/trainer.py b/mpa/det/trainer.py index d708fbc9..0aadd6c6 100644 --- a/mpa/det/trainer.py +++ b/mpa/det/trainer.py @@ -33,6 +33,12 @@ @STAGES.register_module() class DetectionTrainer: def __init__(self, training_type='incremental', **kwargs): + # FIXME This is temporary solution for getting training type + # OTX task should give propoer training type to DetectionTrainer + if "config" in kwargs: + config = kwargs['config'] + if 'unlabeled' in config.data: + training_type = 'semisl' self.patcher = load_patcher(training_type, **kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): diff --git a/mpa/det/utils.py b/mpa/det/utils.py index a375c4c0..55053fd1 100644 --- a/mpa/det/utils.py +++ b/mpa/det/utils.py @@ -4,11 +4,14 @@ # from mpa.det.incr_stage import IncrDetectionStage +from mpa.det.semi_stage import SemiDetectionStage def load_patcher(training_type, **kwargs): if training_type == 'incremental': patcher = IncrDetectionStage(**kwargs) + elif training_type == 'semisl': + patcher = SemiDetectionStage(**kwargs) else: raise NotImplementedError(f"{training_type} is not supported in detection task") return patcher diff --git a/mpa/modules/models/detectors/unbiased_teacher.py b/mpa/modules/models/detectors/unbiased_teacher.py index 6ef7a3f5..e4ec79f5 100644 --- a/mpa/modules/models/detectors/unbiased_teacher.py +++ b/mpa/modules/models/detectors/unbiased_teacher.py @@ -128,6 +128,7 @@ def forward_train(self, loss*self.unlabeled_loss_weight for loss in ul_loss ] # TODO: apply loss_bbox when adopting QFL; + # For debug # self.save_pseudo_labels(ul_img0, pseudo_bboxes, pseudo_labels) return losses diff --git a/recipes/stages/detection/semisl.py b/recipes/stages/detection/semisl.py index 85c036c6..04440938 100644 --- a/recipes/stages/detection/semisl.py +++ b/recipes/stages/detection/semisl.py @@ -2,6 +2,12 @@ './finetune.py', ] +task_adapt = dict( + type='mpa', + op='REPLACE', + efficient_mode=False, +) + model = dict( unlabeled_loss_weight=1.0, ) From 2f50661e99e29bd49b99fb10b3fa9e6107a92ce4 Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Mon, 28 Nov 2022 16:01:05 +0900 Subject: [PATCH 05/15] Remove unnecessary changes --- mpa/stage.py | 1 - recipes/stages/_base_/default.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/mpa/stage.py b/mpa/stage.py index 1948ddd0..f65c4454 100644 --- a/mpa/stage.py +++ b/mpa/stage.py @@ -32,7 +32,6 @@ def _set_random_seed(seed, deterministic=False): to True and `torch.backends.cudnn.benchmark` to False. Default: False. """ - seed = random.randint(0, 10000) random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) diff --git a/recipes/stages/_base_/default.py b/recipes/stages/_base_/default.py index 269bc418..4f02e25e 100644 --- a/recipes/stages/_base_/default.py +++ b/recipes/stages/_base_/default.py @@ -4,7 +4,7 @@ cudnn_benchmark = True -seed = 1234 +seed = 5 deterministic = False hparams = dict(dummy=0) From 7dfce99394bebe4046bd9ce8846518cd20b1562c Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Tue, 29 Nov 2022 16:48:35 +0900 Subject: [PATCH 06/15] Modification for linking otx --- mpa/det/evaluator.py | 2 +- mpa/det/exporter.py | 7 ++++--- mpa/det/inferrer.py | 34 ++++++++++++++++++++++++---------- mpa/det/trainer.py | 13 ++++--------- mpa/det/utils.py | 4 ++-- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/mpa/det/evaluator.py b/mpa/det/evaluator.py index b5670f84..e3f5770c 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 a6190410..d5915ac2 100644 --- a/mpa/det/exporter.py +++ b/mpa/det/exporter.py @@ -12,15 +12,16 @@ from mpa.registry import STAGES from mpa.utils.logger import get_logger +from mpa.det.inferrer import DetectionInferrer from mpa.det.utils import load_patcher logger = get_logger() @STAGES.register_module() -class DetectionExporter: - def __init__(self, training_type='incremental', **kwargs): - self.patcher = load_patcher(training_type, **kwargs) +class DetectionExporter(DetectionInferrer): + def __init__(self, **kwargs): + super().__init__(**kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): self.patcher._init_logger() diff --git a/mpa/det/inferrer.py b/mpa/det/inferrer.py index 2cc5e9c4..b1de9766 100644 --- a/mpa/det/inferrer.py +++ b/mpa/det/inferrer.py @@ -22,7 +22,15 @@ @STAGES.register_module() class DetectionInferrer: - def __init__(self, training_type='incremental', **kwargs): + def __init__(self, **kwargs): + # FIXME This is temporary solution for getting training type + # OTX task should give proper training type to DetectionTrainer + training_type = 'INCREMENTAL' + if "config" in kwargs: + config = kwargs['config'] + if 'train_type' in config: + training_type = config.train_type.name + self.training_type = training_type self.patcher = load_patcher(training_type, **kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): @@ -176,15 +184,21 @@ 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: - with torch.no_grad(): - result = eval_model(return_loss=False, rescale=True, **data) - eval_predictions.extend(result) + # FIXME This is heuristic statement + if self.training_type == 'SEMISUPERVISED': + 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) + else: + 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: + 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', diff --git a/mpa/det/trainer.py b/mpa/det/trainer.py index 0aadd6c6..6feefa91 100644 --- a/mpa/det/trainer.py +++ b/mpa/det/trainer.py @@ -24,6 +24,7 @@ from mpa.modules.utils.task_adapt import extract_anchor_ratio from mpa.utils.logger import get_logger from mpa.stage import Stage +from .inferrer import DetectionInferrer from mpa.det.utils import load_patcher logger = get_logger() @@ -31,15 +32,9 @@ #FIXME DetectionTrainer does not inherit from stage @STAGES.register_module() -class DetectionTrainer: - def __init__(self, training_type='incremental', **kwargs): - # FIXME This is temporary solution for getting training type - # OTX task should give propoer training type to DetectionTrainer - if "config" in kwargs: - config = kwargs['config'] - if 'unlabeled' in config.data: - training_type = 'semisl' - self.patcher = load_patcher(training_type, **kwargs) +class DetectionTrainer(DetectionInferrer): + def __init__(self, **kwargs): + super().__init__(**kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): """Run training stage for detection diff --git a/mpa/det/utils.py b/mpa/det/utils.py index 55053fd1..488ca60e 100644 --- a/mpa/det/utils.py +++ b/mpa/det/utils.py @@ -8,9 +8,9 @@ def load_patcher(training_type, **kwargs): - if training_type == 'incremental': + if training_type == 'INCREMENTAL': patcher = IncrDetectionStage(**kwargs) - elif training_type == 'semisl': + elif training_type == 'SEMISUPERVISED': patcher = SemiDetectionStage(**kwargs) else: raise NotImplementedError(f"{training_type} is not supported in detection task") From 84acbb3a9ada57aaeb6cc0b4681a52f6df7cbb21 Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Thu, 1 Dec 2022 16:03:00 +0900 Subject: [PATCH 07/15] Change polymorphism stage --- mpa/det/__init__.py | 2 + mpa/det/evaluator.py | 2 +- mpa/det/exporter.py | 11 +- mpa/det/inferrer.py | 44 ++-- mpa/det/semisl/__init__.py | 6 + mpa/det/semisl/inferrer.py | 202 ++++++++++++++++++ .../{semi_stage.py => semisl/semisl_stage.py} | 2 +- mpa/det/semisl/trainer.py | 169 +++++++++++++++ mpa/det/stage.py | 4 +- mpa/det/trainer.py | 12 +- mpa/det/utils.py | 17 -- mpa/modules/hooks/eval_hook.py | 1 + 12 files changed, 408 insertions(+), 64 deletions(-) create mode 100644 mpa/det/semisl/__init__.py create mode 100644 mpa/det/semisl/inferrer.py rename mpa/det/{semi_stage.py => semisl/semisl_stage.py} (95%) create mode 100644 mpa/det/semisl/trainer.py delete mode 100644 mpa/det/utils.py diff --git a/mpa/det/__init__.py b/mpa/det/__init__.py index f5af563c..1f891f67 100644 --- a/mpa/det/__init__.py +++ b/mpa/det/__init__.py @@ -9,6 +9,8 @@ from . import stage from . import trainer +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 e3f5770c..8476e5e4 100644 --- a/mpa/det/evaluator.py +++ b/mpa/det/evaluator.py @@ -24,7 +24,7 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): """ self._init_logger() mode = kwargs.get('mode', 'train') - if mode not in self.patcher.mode: + if mode not in self.mode: return {} cfg = self.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) diff --git a/mpa/det/exporter.py b/mpa/det/exporter.py index d5915ac2..392808c1 100644 --- a/mpa/det/exporter.py +++ b/mpa/det/exporter.py @@ -12,26 +12,25 @@ from mpa.registry import STAGES from mpa.utils.logger import get_logger -from mpa.det.inferrer import DetectionInferrer -from mpa.det.utils import load_patcher +from mpa.det.incr_stage import IncrDetectionStage logger = get_logger() @STAGES.register_module() -class DetectionExporter(DetectionInferrer): +class DetectionExporter(IncrDetectionStage): def __init__(self, **kwargs): super().__init__(**kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): - self.patcher._init_logger() + self._init_logger() logger.info('exporting the model') mode = kwargs.get('mode', 'train') - if mode not in self.patcher.mode: + if mode not in self.mode: logger.warning(f'mode for this stage {mode}') return {} - cfg = self.patcher.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) + cfg = self.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) output_path = os.path.join(cfg.work_dir, 'export') os.makedirs(output_path, exist_ok=True) diff --git a/mpa/det/inferrer.py b/mpa/det/inferrer.py index b1de9766..b7c55b4a 100644 --- a/mpa/det/inferrer.py +++ b/mpa/det/inferrer.py @@ -14,24 +14,15 @@ from mpa.registry import STAGES from mpa.utils.logger import get_logger -from mpa.det.utils import load_patcher -from mpa.det.stage import DetectionStage +from mpa.det.incr_stage import IncrDetectionStage logger = get_logger() @STAGES.register_module() -class DetectionInferrer: +class DetectionInferrer(IncrDetectionStage): def __init__(self, **kwargs): - # FIXME This is temporary solution for getting training type - # OTX task should give proper training type to DetectionTrainer - training_type = 'INCREMENTAL' - if "config" in kwargs: - config = kwargs['config'] - if 'train_type' in config: - training_type = config.train_type.name - self.training_type = training_type - self.patcher = load_patcher(training_type, **kwargs) + super().__init__(**kwargs) def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): """Run inference stage for detection @@ -40,15 +31,15 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): - Environment setup - Run inference via MMDetection -> MMCV """ - self.patcher._init_logger() + 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.patcher.mode: + if mode not in self.mode: return {} - cfg = self.patcher.configure(model_cfg, model_ckpt, data_cfg, training=False, **kwargs) + 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!') @@ -140,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): @@ -184,21 +175,12 @@ 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 - # FIXME This is heuristic statement - if self.training_type == 'SEMISUPERVISED': - 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) - else: - 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: - with torch.no_grad(): - result = eval_model(return_loss=False, rescale=True, **data) - eval_predictions.extend(result) + 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: + 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', 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..c671470e --- /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.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/semi_stage.py b/mpa/det/semisl/semisl_stage.py similarity index 95% rename from mpa/det/semi_stage.py rename to mpa/det/semisl/semisl_stage.py index bab6dd82..b722cae6 100644 --- a/mpa/det/semi_stage.py +++ b/mpa/det/semisl/semisl_stage.py @@ -8,7 +8,7 @@ logger = get_logger() -class SemiDetectionStage(IncrDetectionStage): +class SemiSLDetectionStage(IncrDetectionStage): """Patch config to support semi supervised learning for object detection""" def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/mpa/det/semisl/trainer.py b/mpa/det/semisl/trainer.py new file mode 100644 index 00000000..adff33b1 --- /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.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 3a28e0d0..61437121 100644 --- a/mpa/det/stage.py +++ b/mpa/det/stage.py @@ -153,5 +153,7 @@ def configure_hyperparams(self, cfg, training, **kwargs): def configure_task(self, cfg, training, **kwargs): """Patch config to support training algorithm. - This should be implemented each algorithm patcher""" + + This should be implemented each algorithm + """ pass diff --git a/mpa/det/trainer.py b/mpa/det/trainer.py index 6feefa91..e5388b15 100644 --- a/mpa/det/trainer.py +++ b/mpa/det/trainer.py @@ -23,16 +23,14 @@ from mpa.registry import STAGES from mpa.modules.utils.task_adapt import extract_anchor_ratio from mpa.utils.logger import get_logger -from mpa.stage import Stage -from .inferrer import DetectionInferrer -from mpa.det.utils import load_patcher +from mpa.det.incr_stage import IncrDetectionStage logger = get_logger() #FIXME DetectionTrainer does not inherit from stage @STAGES.register_module() -class DetectionTrainer(DetectionInferrer): +class DetectionTrainer(IncrDetectionStage): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -43,12 +41,12 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): - Environment setup - Run training via MMDetection -> MMCV """ - self.patcher._init_logger() + self._init_logger() mode = kwargs.get('mode', 'train') - if mode not in self.patcher.mode: + if mode not in self.mode: return {} - cfg = self.patcher.configure(model_cfg, model_ckpt, data_cfg, **kwargs) + cfg = self.configure(model_cfg, model_ckpt, data_cfg, **kwargs) logger.info('train!') # # Work directory diff --git a/mpa/det/utils.py b/mpa/det/utils.py deleted file mode 100644 index 488ca60e..00000000 --- a/mpa/det/utils.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Utils for detection task.""" -# Copyright (C) 2022 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 -# - -from mpa.det.incr_stage import IncrDetectionStage -from mpa.det.semi_stage import SemiDetectionStage - - -def load_patcher(training_type, **kwargs): - if training_type == 'INCREMENTAL': - patcher = IncrDetectionStage(**kwargs) - elif training_type == 'SEMISUPERVISED': - patcher = SemiDetectionStage(**kwargs) - else: - raise NotImplementedError(f"{training_type} is not supported in detection task") - return patcher diff --git a/mpa/modules/hooks/eval_hook.py b/mpa/modules/hooks/eval_hook.py index 83f99906..6bca6ea5 100644 --- a/mpa/modules/hooks/eval_hook.py +++ b/mpa/modules/hooks/eval_hook.py @@ -101,6 +101,7 @@ def single_gpu_test(model, data_loader): for i, data in enumerate(data_loader): with torch.no_grad(): result = model(return_loss=False, **data) + breakpoint() results.append(result) batch_size = data['img'].size(0) From 3e81089bf1f158e494a8654f98abf0919fc799ec Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Fri, 2 Dec 2022 09:51:19 +0900 Subject: [PATCH 08/15] Revert an eval hook change --- mpa/modules/hooks/eval_hook.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mpa/modules/hooks/eval_hook.py b/mpa/modules/hooks/eval_hook.py index 6bca6ea5..83f99906 100644 --- a/mpa/modules/hooks/eval_hook.py +++ b/mpa/modules/hooks/eval_hook.py @@ -101,7 +101,6 @@ def single_gpu_test(model, data_loader): for i, data in enumerate(data_loader): with torch.no_grad(): result = model(return_loss=False, **data) - breakpoint() results.append(result) batch_size = data['img'].size(0) From b0b25f40ff6fb759170bedfc724068e61b454172 Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Mon, 5 Dec 2022 14:45:01 +0900 Subject: [PATCH 09/15] Rename semi_stage and move semisl model config to otx --- mpa/det/semisl/inferrer.py | 2 +- mpa/det/semisl/{semisl_stage.py => stage.py} | 0 mpa/det/semisl/trainer.py | 3 ++- recipes/stages/detection/finetune.py | 2 +- recipes/stages/detection/semisl.py | 13 ++++++++----- 5 files changed, 12 insertions(+), 8 deletions(-) rename mpa/det/semisl/{semisl_stage.py => stage.py} (100%) diff --git a/mpa/det/semisl/inferrer.py b/mpa/det/semisl/inferrer.py index c671470e..89c2c46e 100644 --- a/mpa/det/semisl/inferrer.py +++ b/mpa/det/semisl/inferrer.py @@ -14,7 +14,7 @@ from mpa.registry import STAGES from mpa.utils.logger import get_logger -from mpa.det.semisl.semisl_stage import SemiSLDetectionStage +from mpa.det.semisl.stage import SemiSLDetectionStage logger = get_logger() diff --git a/mpa/det/semisl/semisl_stage.py b/mpa/det/semisl/stage.py similarity index 100% rename from mpa/det/semisl/semisl_stage.py rename to mpa/det/semisl/stage.py diff --git a/mpa/det/semisl/trainer.py b/mpa/det/semisl/trainer.py index adff33b1..534417c9 100644 --- a/mpa/det/semisl/trainer.py +++ b/mpa/det/semisl/trainer.py @@ -23,7 +23,7 @@ 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.semisl_stage import SemiSLDetectionStage +from mpa.det.semisl.stage import SemiSLDetectionStage logger = get_logger() @@ -40,6 +40,7 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): - Environment setup - Run training via MMDetection -> MMCV """ + breakpoint() self._init_logger() mode = kwargs.get('mode', 'train') if mode not in self.mode: diff --git a/recipes/stages/detection/finetune.py b/recipes/stages/detection/finetune.py index 904315eb..66181819 100644 --- a/recipes/stages/detection/finetune.py +++ b/recipes/stages/detection/finetune.py @@ -4,7 +4,7 @@ '../_base_/models/detectors/detector.py' ] -model = dict(super_type='UnbiasedTeacher', pseudo_conf_thresh=0.25) # Used as general framework +model = dict(super_type='UnbiasedTeacher', pseudo_conf_thresh=0.75) # Used as general framework custom_hooks = [ dict( diff --git a/recipes/stages/detection/semisl.py b/recipes/stages/detection/semisl.py index 04440938..d3840fd9 100644 --- a/recipes/stages/detection/semisl.py +++ b/recipes/stages/detection/semisl.py @@ -1,5 +1,7 @@ _base_ = [ - './finetune.py', + './train.py', + '../_base_/data/coco_ubt.py', + '../_base_/models/detectors/detector.py' ] task_adapt = dict( @@ -8,10 +10,6 @@ efficient_mode=False, ) -model = dict( - unlabeled_loss_weight=1.0, -) - custom_hooks = [ dict( type='UnbiasedTeacherHook', @@ -20,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, From d0c7ea1d53742a2ace5203a4c4f4888493ff749e Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Mon, 5 Dec 2022 14:47:33 +0900 Subject: [PATCH 10/15] Revert unnecessary changes of finuetune.py --- recipes/stages/detection/finetune.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/stages/detection/finetune.py b/recipes/stages/detection/finetune.py index 66181819..61bf0dfd 100644 --- a/recipes/stages/detection/finetune.py +++ b/recipes/stages/detection/finetune.py @@ -4,7 +4,7 @@ '../_base_/models/detectors/detector.py' ] -model = dict(super_type='UnbiasedTeacher', pseudo_conf_thresh=0.75) # Used as general framework +model = dict(super_type='UnbiasedTeacher') # Used as general framework custom_hooks = [ dict( From bf127ea25c197aad353f7de353af038138f32548 Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Mon, 5 Dec 2022 16:31:52 +0900 Subject: [PATCH 11/15] Remove breakpoint in trainer.py --- mpa/det/semisl/trainer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mpa/det/semisl/trainer.py b/mpa/det/semisl/trainer.py index 534417c9..cf0316f1 100644 --- a/mpa/det/semisl/trainer.py +++ b/mpa/det/semisl/trainer.py @@ -40,7 +40,6 @@ def run(self, model_cfg, model_ckpt, data_cfg, **kwargs): - Environment setup - Run training via MMDetection -> MMCV """ - breakpoint() self._init_logger() mode = kwargs.get('mode', 'train') if mode not in self.mode: From 96cac351293127ac6148a06fa41c55cba9cdbfcc Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Mon, 5 Dec 2022 17:10:16 +0900 Subject: [PATCH 12/15] Change pseudo label validation directory path --- mpa/modules/models/detectors/unbiased_teacher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpa/modules/models/detectors/unbiased_teacher.py b/mpa/modules/models/detectors/unbiased_teacher.py index e4ec79f5..4b51dac1 100644 --- a/mpa/modules/models/detectors/unbiased_teacher.py +++ b/mpa/modules/models/detectors/unbiased_teacher.py @@ -138,7 +138,7 @@ def save_pseudo_labels(self, ul_imgs, pseudo_bboxes, pseudo_labels): import os import cv2 as cv - self.save_dir = "/home/jaeguk/workspace/tmp/semisl-det/" + self.save_dir = "/tmp/semisl-det/" os.makedirs(self.save_dir, exist_ok=True) if not hasattr(self, "prefix"): From 807007b116761f83644d053f71a72acb342441db Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Tue, 6 Dec 2022 11:16:29 +0900 Subject: [PATCH 13/15] Remove debug code, create incremental folder for incr_stage and modify task adapt hook for semisl --- mpa/det/__init__.py | 1 + mpa/det/exporter.py | 2 +- mpa/det/incremental/__init__.py | 7 ++++ mpa/det/{ => incremental}/incr_stage.py | 0 mpa/det/inferrer.py | 2 +- mpa/det/semisl/stage.py | 23 +++++++++++- mpa/det/trainer.py | 2 +- mpa/modules/hooks/model_ema_hook.py | 9 ----- .../models/detectors/unbiased_teacher.py | 36 ------------------- 9 files changed, 33 insertions(+), 49 deletions(-) create mode 100644 mpa/det/incremental/__init__.py rename mpa/det/{ => incremental}/incr_stage.py (100%) diff --git a/mpa/det/__init__.py b/mpa/det/__init__.py index 1f891f67..7c39fca9 100644 --- a/mpa/det/__init__.py +++ b/mpa/det/__init__.py @@ -9,6 +9,7 @@ from . import stage from . import trainer +from . import incremental from . import semisl import mpa.modules.datasets.pipelines.torchvision2mmdet diff --git a/mpa/det/exporter.py b/mpa/det/exporter.py index 392808c1..f3570bfb 100644 --- a/mpa/det/exporter.py +++ b/mpa/det/exporter.py @@ -12,7 +12,7 @@ from mpa.registry import STAGES from mpa.utils.logger import get_logger -from mpa.det.incr_stage import IncrDetectionStage +from mpa.det.incremental import IncrDetectionStage logger = get_logger() diff --git a/mpa/det/incremental/__init__.py b/mpa/det/incremental/__init__.py new file mode 100644 index 00000000..9e3f9255 --- /dev/null +++ b/mpa/det/incremental/__init__.py @@ -0,0 +1,7 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 +# + +from .incr_stage import IncrDetectionStage + +__all__ = ["IncrDetectionStage"] diff --git a/mpa/det/incr_stage.py b/mpa/det/incremental/incr_stage.py similarity index 100% rename from mpa/det/incr_stage.py rename to mpa/det/incremental/incr_stage.py diff --git a/mpa/det/inferrer.py b/mpa/det/inferrer.py index b7c55b4a..44bcc9b0 100644 --- a/mpa/det/inferrer.py +++ b/mpa/det/inferrer.py @@ -14,7 +14,7 @@ from mpa.registry import STAGES from mpa.utils.logger import get_logger -from mpa.det.incr_stage import IncrDetectionStage +from mpa.det.incremental import IncrDetectionStage logger = get_logger() diff --git a/mpa/det/semisl/stage.py b/mpa/det/semisl/stage.py index b722cae6..66b437b6 100644 --- a/mpa/det/semisl/stage.py +++ b/mpa/det/semisl/stage.py @@ -2,7 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 # -from mpa.det.incr_stage import IncrDetectionStage +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() @@ -28,3 +30,22 @@ def configure_task_cls_incr(self, cfg, task_adapt_type, org_model_classes, model 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/trainer.py b/mpa/det/trainer.py index e5388b15..fd954aa3 100644 --- a/mpa/det/trainer.py +++ b/mpa/det/trainer.py @@ -23,7 +23,7 @@ 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.incr_stage import IncrDetectionStage +from mpa.det.incremental import IncrDetectionStage logger = get_logger() diff --git a/mpa/modules/hooks/model_ema_hook.py b/mpa/modules/hooks/model_ema_hook.py index 2b89cb17..95da07d6 100644 --- a/mpa/modules/hooks/model_ema_hook.py +++ b/mpa/modules/hooks/model_ema_hook.py @@ -125,15 +125,6 @@ def _copy_model(self): def _ema_model(self): momentum = min(self.momentum, 1.0) - - # For debug - # if hasattr(self, "momentum_cache"): - # if self.momentum != self.momentum_cache: - # print(f"Momentum is changed: {self.momentum_cache} ==> {self.momentum}") - # self.momentum_cache = self.momentum - # else: - # self.momentum_cache = self.momentum - with torch.no_grad(): for name, src_param in self.src_params.items(): dst_param = self.dst_params[name] diff --git a/mpa/modules/models/detectors/unbiased_teacher.py b/mpa/modules/models/detectors/unbiased_teacher.py index 4b51dac1..92c3987a 100644 --- a/mpa/modules/models/detectors/unbiased_teacher.py +++ b/mpa/modules/models/detectors/unbiased_teacher.py @@ -128,45 +128,9 @@ def forward_train(self, loss*self.unlabeled_loss_weight for loss in ul_loss ] # TODO: apply loss_bbox when adopting QFL; - # For debug - # self.save_pseudo_labels(ul_img0, pseudo_bboxes, pseudo_labels) return losses - def save_pseudo_labels(self, ul_imgs, pseudo_bboxes, pseudo_labels): - """This saves unlabeled image with pseudo bboxes, this is for debug.""" - import os - import cv2 as cv - - self.save_dir = "/tmp/semisl-det/" - os.makedirs(self.save_dir, exist_ok=True) - - if not hasattr(self, "prefix"): - self.prefix = 0 - else: - self.prefix += 1 - - colors = [[0,0,255], [0,255,0], [255,0,0]] - - for idx, (ul_img, pseudo_bbox, pseudo_label) in enumerate(zip(ul_imgs, pseudo_bboxes, pseudo_labels)): - # Change tensor to numpy image - ul_img = ul_img.cpu().permute(1, 2, 0).numpy() - # Reverse normalization - ul_img = ul_img * 255 - ul_img = np.ascontiguousarray(ul_img, dtype=np.uint8) - # If there is no pseudo box, then save image - if len(pseudo_bbox) == 0: - cv.imwrite(os.path.join(self.save_dir, f"{self.prefix}_{idx}.jpg"), ul_img) - continue - # Draw bbox - for bbox, label in zip(pseudo_bbox, pseudo_label): - x1, y1, x2, y2 = bbox.to(int).cpu().numpy() - start = (x1, y1) - end = (x2, y2) - ul_img = cv.rectangle(ul_img, start, end, colors[label], 1) - # Save image to pre-defined path - cv.imwrite(os.path.join(self.save_dir, f"{self.prefix}_{idx}.jpg"), ul_img) - def generate_pseudo_labels(self, teacher_outputs, **kwargs): all_pseudo_bboxes = [] all_pseudo_labels = [] From 3534451112c45326cfb3573bfe6d9182baa9cefe Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Tue, 6 Dec 2022 16:23:16 +0900 Subject: [PATCH 14/15] Move unlabeled data configuration from base stage to semi-sl stage --- mpa/det/semisl/stage.py | 18 ++++++++++++++++++ mpa/det/stage.py | 13 ------------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/mpa/det/semisl/stage.py b/mpa/det/semisl/stage.py index 66b437b6..4a8dc881 100644 --- a/mpa/det/semisl/stage.py +++ b/mpa/det/semisl/stage.py @@ -15,6 +15,24 @@ class SemiSLDetectionStage(IncrDetectionStage): 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) diff --git a/mpa/det/stage.py b/mpa/det/stage.py index 61437121..d330d518 100644 --- a/mpa/det/stage.py +++ b/mpa/det/stage.py @@ -99,19 +99,6 @@ def configure_data(self, cfg, data_cfg, training, **kwargs): cfg.data.train.org_type = cfg.data.train.type cfg.data.train.type = super_type 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 - ) - ) 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) From a6beb9fa350a062d76a73266466cf6977f7f4244 Mon Sep 17 00:00:00 2001 From: jaegukhyun Date: Wed, 7 Dec 2022 10:03:02 +0900 Subject: [PATCH 15/15] Rename incremental/incr_stage.py to incremental/stage.py --- mpa/det/incremental/__init__.py | 2 +- mpa/det/incremental/{incr_stage.py => stage.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename mpa/det/incremental/{incr_stage.py => stage.py} (100%) diff --git a/mpa/det/incremental/__init__.py b/mpa/det/incremental/__init__.py index 9e3f9255..6353ceac 100644 --- a/mpa/det/incremental/__init__.py +++ b/mpa/det/incremental/__init__.py @@ -2,6 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 # -from .incr_stage import IncrDetectionStage +from .stage import IncrDetectionStage __all__ = ["IncrDetectionStage"] diff --git a/mpa/det/incremental/incr_stage.py b/mpa/det/incremental/stage.py similarity index 100% rename from mpa/det/incremental/incr_stage.py rename to mpa/det/incremental/stage.py