From dc6451b39d6eaafbef7b2d5b2259600e2866fd4a Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:28:02 +0800 Subject: [PATCH 01/19] add switch hook and UTs --- mmcls/engine/hooks/switch_aug_hook.py | 203 +++++++++ .../test_hooks/test_switch_aug_hook.py | 425 ++++++++++++++++++ 2 files changed, 628 insertions(+) create mode 100644 mmcls/engine/hooks/switch_aug_hook.py create mode 100644 tests/test_engine/test_hooks/test_switch_aug_hook.py diff --git a/mmcls/engine/hooks/switch_aug_hook.py b/mmcls/engine/hooks/switch_aug_hook.py new file mode 100644 index 00000000000..34fd5580014 --- /dev/null +++ b/mmcls/engine/hooks/switch_aug_hook.py @@ -0,0 +1,203 @@ +# Copyright (c) OpenMMLab. All rights reserved. +from typing import Optional, Sequence + +from mmcv.transforms import Compose +from mmcls.models.utils import RandomBatchAugment +from mmengine.model import is_model_wrapper +from mmengine.hooks import Hook + +from mmcls.registry import HOOKS, MODELS + +DATA_BATCH = Optional[Sequence[dict]] + + +@HOOKS.register_module() +class SwitchTrainAugHook(Hook): + """switch configuration during the training, including data pipeline, batch + augments and loss. + + Args: + action_epoch (int): switch train augments at the epoch of action_epoch. + Defaults to None. + action_iter (int): switch train augments at the iter of action_iter. + Defaults to None. + pipeline (dict, str, optional): the new train pipeline. + Defaults to 'unchange', means not changing the train pipeline. + train_augments (dict, str, optional): the new train augments. + Defaults to 'unchange', means not changing the train augments. + loss (dict, str, optional): the new train loss. + Defaults to 'unchange', means not changing the train loss. + + Note: + This hook would modify the ``model.data_preprocessor.batch_augments`` + , ``runner.train_loop.dataloader.dataset.pipeline`` and + ``runner.model.head.loss`` fields. + """ + priority = 'NORMAL' + + def __init__(self, + action_epoch=None, + action_iter=None, + pipeline='unchange', + train_augments='unchange', + loss='unchange'): + + if action_iter is None and action_epoch is None: + raise ValueError('one of `action_iter` and `action_epoch` ' + 'must be set in `SwitchTrainAugHook`.') + if action_iter is not None and action_epoch is not None: + raise ValueError('`action_iter` and `action_epoch` should ' + 'not be both set in `SwitchTrainAugHook`.') + + if action_iter is not None: + assert isinstance(action_iter, int) and action_iter >= 0, ( + '`action_iter` must be a number larger than 0 in ' + f'`SwitchTrainAugHook`, but got begin: {action_iter}') + self.by_epoch = False + if action_epoch is not None: + assert isinstance(action_epoch, int) and action_epoch >= 0, ( + '`action_epoch` must be a number larger than 0 in ' + f'`SwitchTrainAugHook`, but got begin: {action_epoch}') + self.by_epoch = True + + self.action_epoch = action_epoch + self.action_iter = action_iter + + self.pipeline = pipeline + if pipeline != 'unchange': + self.pipeline = Compose(pipeline) + self._restart_dataloader = False + + self.train_augments = train_augments + if train_augments is not None and train_augments != 'unchange': + self.train_augments = RandomBatchAugment(**train_augments) + + self.loss = MODELS.build(loss) if loss != 'unchange' else loss + + + def before_train(self, runner) -> None: + """before run setting. If resume form a checkpoint, check whether is + the latest processed hook, if True, do the switch process before train. + + Args: + runner (Runner): The runner of the training, validation or testing + process. + """ + # if this hook is the latest switch hook obj in the previously unfinished + # tasks, then do the switch process before train + if runner._resume and self._is_lastest_switch_hook(runner): + action_milestone_str = ' after resume' + self._do_switch(runner, action_milestone_str) + + def before_train_epoch(self, runner): + """switch augments.""" + if self.by_epoch and runner.epoch + 1 == self.action_epoch: + action_milestone_str = f' at Epoch {runner.epoch + 1}' + self._do_switch(runner, action_milestone_str) + + def after_train_iter(self, + runner, + batch_idx: int, + data_batch: DATA_BATCH = None, + outputs: Optional[dict] = None) -> None: + """switch augments.""" + if not self.by_epoch and runner.iter + 1 == self.action_iter: + action_milestone_str = f' at Iter {runner.iter + 1}' + self._do_switch(runner, action_milestone_str) + + def _is_lastest_switch_hook(self, runner): + """a helper function to judge if this hook is the latest processed switch + hooks with the same class name in a runner.""" + # collect all the switch_hook with the same class name in a list. + switch_hook_objs = [ + hook_obj for hook_obj in runner._hooks + if isinstance(hook_obj, SwitchTrainAugHook) + ] + + # get the latest swict hook based on the current iter. + dataset_length = len(runner.train_loop.dataloader) + cur_iter = runner.train_loop.iter + + min_gap, min_gap_idx = float('inf'), -1 + for i, switch_hook_obj in enumerate(switch_hook_objs): + # use iter to calculate + if switch_hook_obj.by_epoch: + exe_iter = switch_hook_obj.action_epoch * dataset_length + else: + exe_iter = switch_hook_obj.action_iter + + gap = cur_iter - exe_iter + if gap < 0: + # this hook have not beend executed + continue + elif gap > 0 and min_gap > gap: + # this hook have been executed and is closer to cur iter + min_gap = gap + min_gap_idx = i + + # return if self is the latest executed switch hook + return min_gap_idx != -1 and self is switch_hook_objs[min_gap_idx] + + def _do_switch(self, runner, action_milestone_str=''): + """do the switch aug process.""" + if self.train_augments != 'unchange': + self._switch_batch_augments(runner) + runner.logger.info(f'Switch train aug{action_milestone_str}.') + + if self.pipeline != 'unchange': + self._switch_train_loader_pipeline(runner) + runner.logger.info(f'Switch train pipeline{action_milestone_str}.') + + if self.loss != 'unchange': + self._switch_train_loss(runner) + runner.logger.info(f'Switch train loss{action_milestone_str}.') + + def _switch_batch_augments(self, runner): + """switch the train aug.""" + model = runner.model + if is_model_wrapper(model): + model = model.module + + model.data_preprocessor.batch_augments = self.train_augments + + def _switch_train_loader_pipeline(self, runner): + """switch the train loader dataset pipeline.""" + train_loader = runner.train_loop.dataloader + if hasattr(train_loader.dataset, 'pipeline'): + # for dataset + if self.pipeline is not None: + train_loader.dataset.pipeline = self.pipeline + elif hasattr(train_loader.dataset, 'datasets'): + # for concat dataset wrappers + for ds in train_loader.dataset.datasets: + ds.pipeline = self.pipeline + elif hasattr(train_loader.dataset.dataset, 'pipeline'): + # for other dataset wrappers + train_loader.dataset.dataset.pipeline = self.pipeline + else: + raise ValueError( + 'train_loader.dataset or train_loader.dataset.dataset' + ' must have pipeline') + + # The dataset pipeline cannot be updated when persistent_workers + # is True, so we need to force the dataloader's multi-process + # restart. This is a very hacky approach. + if hasattr(train_loader, 'persistent_workers') \ + and train_loader.persistent_workers is True: + train_loader._DataLoader__initialized = False + train_loader._iterator = None + self._restart_dataloader = True + else: + # Once the restart is complete, we need to restore + # the initialization flag. + if self._restart_dataloader: + train_loader._DataLoader__initialized = True + + def _switch_train_loss(self, runner): + """switch the train loss.""" + model = runner.model + if is_model_wrapper(model): + model = model.module + + assert hasattr(model.head, 'loss') + model.head.loss = self.loss diff --git a/tests/test_engine/test_hooks/test_switch_aug_hook.py b/tests/test_engine/test_hooks/test_switch_aug_hook.py new file mode 100644 index 00000000000..b42f14bb1fb --- /dev/null +++ b/tests/test_engine/test_hooks/test_switch_aug_hook.py @@ -0,0 +1,425 @@ +# Copyright (c) OpenMMLab. All rights reserved. +import copy +import random +import tempfile +import logging +from unittest import TestCase +from unittest.mock import MagicMock, patch +from typing import List + +import torch +import torch.nn as nn +from mmengine.logging import MMLogger +from mmengine.dataset import BaseDataset +from torch.utils.data import DataLoader +from mmcv.transforms import BaseTransform +from mmengine.runner import Runner, EpochBasedTrainLoop, IterBasedTrainLoop +from mmengine.model import BaseDataPreprocessor, BaseModel +from mmengine.utils import digit_version +from mmengine.dataset import ConcatDataset, RepeatDataset + +from mmcls.structures import ClsDataSample +from mmcls.models.losses import LabelSmoothLoss +from mmcls.engine import SwitchTrainAugHook +from mmcls.models.utils.batch_augments import (CutMix, + Mixup, RandomBatchAugment) +from mmcls.utils import register_all_modules + + +register_all_modules() + +class MockDataPreprocessor(BaseDataPreprocessor): + """mock preprocessor that do nothing.""" + + def forward(self, data, training): + + return data['imgs'], ClsDataSample() + + +class ExampleModel(BaseModel): + + def __init__(self): + super(ExampleModel, self).__init__() + self.data_preprocessor = MockDataPreprocessor() + self.conv = nn.Linear(1, 1) + self.bn = nn.BatchNorm1d(1) + self.test_cfg = None + + def forward(self, batch_inputs, data_samples, mode='tensor'): + batch_inputs = batch_inputs.to(next(self.parameters()).device) + return self.bn(self.conv(batch_inputs)) + + def train_step(self, data, optim_wrapper): + outputs = {'loss': 0.5, 'num_samples': 1} + return outputs + +class ExampleDataset(BaseDataset): + + def load_data_list(self) -> List[dict]: + return [i for i in range(10)] + + def __getitem__(self, idx): + results = dict(imgs=torch.tensor([1.0], dtype=torch.float32)) + return results + + def __len__(self): + return 10 + +class TestSwitchTrainAugHook(TestCase): + DEFAULT_CFG = dict( + action_epoch=1, + action_iter=None) + + def setUp(self): + # optimizer + self.optim_wrapper = dict(optimizer=dict(type='SGD', lr=0.1)) + # learning policy + self.epochscheduler = dict( + type='MultiStepLR', by_epoch=True, milestones=[1]) + self.iterscheduler = dict( + type='MultiStepLR', by_epoch=False, milestones=[1]) + + self.tmpdir = tempfile.TemporaryDirectory() + self.loader = DataLoader(ExampleDataset(), batch_size=2) + + def test_init(self): + # check action_epoch and action_iter both set + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['action_iter'] = 3 + with self.assertRaises(ValueError): + SwitchTrainAugHook(**cfg) + + # check action_epoch and action_iter both None + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['action_epoch'] = None + with self.assertRaises(ValueError): + SwitchTrainAugHook(**cfg) + + # check action_epoch > 0 + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['action_epoch'] = -1 + with self.assertRaises(AssertionError): + SwitchTrainAugHook(**cfg) + + # check action_iter > 0 + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['action_epoch'] = None + cfg['action_iter'] = "3" + with self.assertRaises(AssertionError): + SwitchTrainAugHook(**cfg) + + # test by_epoch is True + cfg = copy.deepcopy(self.DEFAULT_CFG) + hook_obj = SwitchTrainAugHook(**cfg) + self.assertTrue(hook_obj.by_epoch) + self.assertEqual(hook_obj.action_epoch, 1) + self.assertEqual(hook_obj.action_iter, None) + self.assertEqual(hook_obj.pipeline, 'unchange') + self.assertEqual(hook_obj.train_augments, 'unchange') + self.assertEqual(hook_obj.loss, 'unchange') + + # test by_epoch is False + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['action_epoch'] = None + cfg['action_iter'] = 3 + hook_obj = SwitchTrainAugHook(**cfg) + self.assertFalse(hook_obj.by_epoch) + self.assertEqual(hook_obj.action_epoch, None) + self.assertEqual(hook_obj.action_iter, 3) + + # test pipeline, loss + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [dict(type='LoadImageFromFile')] + cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.1) + hook_obj = SwitchTrainAugHook(**cfg) + self.assertIsInstance(hook_obj.pipeline, BaseTransform) + self.assertIsInstance(hook_obj.loss, LabelSmoothLoss) + + # test pieline is [], and train_augments + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [] + train_cfg=dict(augments=[ + dict(type='Mixup', alpha=0.8), + dict(type='CutMix', alpha=1.0)]) + cfg['train_augments'] = train_cfg + hook_obj = SwitchTrainAugHook(**cfg) + self.assertIsInstance(hook_obj.pipeline, BaseTransform) + self.assertIsInstance(hook_obj.train_augments, RandomBatchAugment) + + def test_before_train_epoch(self): + # test call once in epoch loop + runner = self.init_runner() + switch_hook_cfg1 = copy.deepcopy(self.DEFAULT_CFG) + switch_hook_cfg1['type'] = 'SwitchTrainAugHook' + runner.register_custom_hooks([switch_hook_cfg1]) + with patch.object(SwitchTrainAugHook, '_do_switch') as mock: + runner.train() + mock.assert_called_once() + + # test mutil call in epoch loop + runner = self.init_runner() + switch_hook_cfg2 = copy.deepcopy(switch_hook_cfg1) + switch_hook_cfg3 = copy.deepcopy(switch_hook_cfg1) + switch_hook_cfg2["action_epoch"] = 2 + switch_hook_cfg3["action_epoch"] = 3 + runner.register_custom_hooks( + [switch_hook_cfg1, switch_hook_cfg2, switch_hook_cfg3]) + with patch.object(SwitchTrainAugHook, '_do_switch') as mock: + runner.train() + self.assertEqual(mock.call_count, 3) + + + def test_before_train_iter(self): + # test call once in iter loop + runner = self.init_runner(by_epoch=False) + switch_hook_cfg1 = copy.deepcopy(self.DEFAULT_CFG) + switch_hook_cfg1['type'] = 'SwitchTrainAugHook' + switch_hook_cfg1['action_epoch'] = None + switch_hook_cfg1['action_iter'] = 1 + runner.register_custom_hooks([switch_hook_cfg1]) + with patch.object(SwitchTrainAugHook, '_do_switch') as mock: + runner.train() + mock.assert_called_once() + + # test mutil call in iter loop + runner = self.init_runner(by_epoch=False) + switch_hook_cfg2 = copy.deepcopy(switch_hook_cfg1) + switch_hook_cfg3 = copy.deepcopy(switch_hook_cfg1) + switch_hook_cfg2["action_iter"] = 2 + switch_hook_cfg3["action_iter"] = 3 + runner.register_custom_hooks( + [switch_hook_cfg1, switch_hook_cfg2, switch_hook_cfg3]) + with patch.object(SwitchTrainAugHook, '_do_switch') as mock: + runner.train() + self.assertEqual(mock.call_count, 3) + + + def test_do_switch(self): + # test switch train augments + runner = MagicMock() + cfg = copy.deepcopy(self.DEFAULT_CFG) + train_cfg = dict(augments=[ + dict(type='Mixup', alpha=0.5), + dict(type='CutMix', alpha=0.9)]) + cfg['train_augments'] = train_cfg + hook_obj = SwitchTrainAugHook(**cfg) + with patch.object(SwitchTrainAugHook, '_switch_train_loss') as m_loss: + with patch.object( + SwitchTrainAugHook, '_switch_train_loader_pipeline') as m_pipe: + hook_obj._do_switch(runner) + m_loss.assert_not_called() + m_pipe.assert_not_called() + runner_bath_augs = runner.model.data_preprocessor.batch_augments + self.assertIsInstance(runner_bath_augs, RandomBatchAugment) + self.assertEqual(len(runner_bath_augs.augments), 2) + self.assertIsInstance(runner_bath_augs.augments[0], Mixup) + self.assertEqual(runner_bath_augs.augments[0].alpha, 0.5) + self.assertIsInstance(runner_bath_augs.augments[1], CutMix) + self.assertEqual(runner_bath_augs.augments[1].alpha, 0.9) + + # test switch data aug + runner = MagicMock() + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [dict(type='LoadImageFromFile')] + hook_obj = SwitchTrainAugHook(**cfg) + with patch.object(SwitchTrainAugHook, '_switch_batch_augments') as m_batch: + with patch.object( + SwitchTrainAugHook, '_switch_train_loss') as m_loss: + hook_obj._do_switch(runner) + m_batch.assert_not_called() + m_loss.assert_not_called() + runner_pipeline = runner.train_loop.dataloader.dataset.pipeline + self.assertIsInstance(runner_pipeline, BaseTransform) + self.assertEqual(len(runner_pipeline.transforms), 1) + + # test with persistent_workers=True + if digit_version(torch.__version__) >= digit_version('1.8.0'): + runner = MagicMock() + loader = DataLoader(ExampleDataset(), persistent_workers=True, num_workers=1) + runner.train_loop.dataloader = loader + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [dict(type='LoadImageFromFile'), + dict(type='Resize', scale=256)] + hook_obj = SwitchTrainAugHook(**cfg) + hook_obj._do_switch(runner) + runner_pipeline = runner.train_loop.dataloader.dataset.pipeline + self.assertIsInstance(runner_pipeline, BaseTransform) + self.assertEqual(len(runner_pipeline.transforms), 2) + + # test with ConcatDataset warpper + runner = MagicMock() + loader = DataLoader( + ConcatDataset([ExampleDataset(), ExampleDataset()]), + persistent_workers=True, + num_workers=1) + runner.train_loop.dataloader = loader + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [dict(type='LoadImageFromFile'), + dict(type='Resize', scale=256)] + hook_obj = SwitchTrainAugHook(**cfg) + hook_obj._do_switch(runner) + for i in range(2): + runner_dataset = runner.train_loop.dataloader.dataset.datasets[i] + runner_pipeline = runner_dataset.pipeline + self.assertIsInstance(runner_pipeline, BaseTransform) + self.assertEqual(len(runner_pipeline.transforms), 2) + + # test with RepeatDataset warpper + runner = MagicMock() + loader = DataLoader( + RepeatDataset(ExampleDataset(), 3), + persistent_workers=True, + num_workers=1) + runner.train_loop.dataloader = loader + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [dict(type='LoadImageFromFile'), + dict(type='Resize', scale=256)] + hook_obj = SwitchTrainAugHook(**cfg) + hook_obj._do_switch(runner) + runner_pipeline = runner.train_loop.dataloader.dataset.dataset.pipeline + self.assertIsInstance(runner_pipeline, BaseTransform) + self.assertEqual(len(runner_pipeline.transforms), 2) + + # test switch loss + runner = MagicMock() + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.2) + hook_obj = SwitchTrainAugHook(**cfg) + with patch.object(SwitchTrainAugHook, '_switch_batch_augments') as m_batch: + with patch.object( + SwitchTrainAugHook, '_switch_train_loader_pipeline') as m_pipe: + hook_obj._do_switch(runner) + m_batch.assert_not_called() + m_pipe.assert_not_called() + runner_loss = runner.model.head.loss + self.assertIsInstance(runner_loss, nn.Module) + self.assertTrue(runner_loss.label_smooth_val, 0.2) + + # test both + runner = MagicMock() + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [dict(type='LoadImageFromFile')] + cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.2) + cfg['train_augments'] = dict(augments=[dict(type='Mixup', alpha=0.5)]) + hook_obj = SwitchTrainAugHook(**cfg) + with patch.object(SwitchTrainAugHook, '_switch_batch_augments') as m_batch: + with patch.object( + SwitchTrainAugHook, '_switch_train_loader_pipeline') as m_pipe: + with patch.object( + SwitchTrainAugHook, '_switch_train_loss') as m_loss: + hook_obj._do_switch(runner) + m_batch.assert_called_once() + m_pipe.assert_called_once() + m_loss.assert_called_once() + + def create_patch(self, object, name): + patcher = patch.object(object, name) + thing = patcher.start() + self.addCleanup(patcher.stop) + return thing + + def test_before_train(self): + # test not resume + runner = self.init_runner(resume=False, epoch=2) + hook_obj1 = SwitchTrainAugHook(action_epoch=4) + hook_obj2 = SwitchTrainAugHook(action_epoch=7) + runner.register_custom_hooks([hook_obj1, hook_obj2]) + mock_hook1 = self.create_patch(hook_obj1, '_do_switch') + mock_hook2 = self.create_patch(hook_obj2, '_do_switch') + runner.call_hook('before_train') + mock_hook1.assert_not_called() + mock_hook2.assert_not_called() + + # test resume from no processed switch hook + runner = self.init_runner(resume=True, epoch=2) + hook_obj1 = SwitchTrainAugHook(action_epoch=4) + hook_obj2 = SwitchTrainAugHook(action_epoch=7) + runner.register_custom_hooks([hook_obj1, hook_obj2]) + mock_hook1 = self.create_patch(hook_obj1, '_do_switch') + mock_hook2 = self.create_patch(hook_obj2, '_do_switch') + runner.call_hook('before_train') + mock_hook1.assert_not_called() + mock_hook2.assert_not_called() + + # test resume from epoch processed switch hook + runner = self.init_runner(resume=True, epoch=5) + hook_obj1 = SwitchTrainAugHook(action_epoch=2) + hook_obj2 = SwitchTrainAugHook(action_epoch=7) + hook_obj3 = SwitchTrainAugHook(action_epoch=3) + runner.register_custom_hooks([hook_obj1, hook_obj2, hook_obj3]) + mock_hook1 = self.create_patch(hook_obj1, '_do_switch') + mock_hook2 = self.create_patch(hook_obj2, '_do_switch') + mock_hook3 = self.create_patch(hook_obj3, '_do_switch') + runner.call_hook('before_train') + mock_hook1.assert_not_called() + mock_hook2.assert_not_called() + mock_hook3.assert_called_once() + + # test resume from iter processed switch hook + runner = self.init_runner(resume=True, iter=15, by_epoch=False) + hook_obj1 = SwitchTrainAugHook(action_iter=2) + hook_obj2 = SwitchTrainAugHook(action_iter=12) + hook_obj3 = SwitchTrainAugHook(action_epoch=18) + runner.register_custom_hooks([hook_obj1, hook_obj2, hook_obj3]) + mock_hook1 = self.create_patch(hook_obj1, '_do_switch') + mock_hook2 = self.create_patch(hook_obj2, '_do_switch') + mock_hook3 = self.create_patch(hook_obj3, '_do_switch') + runner.call_hook('before_train') + mock_hook1.assert_not_called() + mock_hook2.assert_called_once() + mock_hook3.assert_not_called() + + # test resume from epoch loop and iter hook + runner = self.init_runner(resume=True, epoch=1) # i epoch = 5 iter + hook_obj1 = SwitchTrainAugHook(action_iter=2) + hook_obj2 = SwitchTrainAugHook(action_iter=15) + hook_obj3 = SwitchTrainAugHook(action_iter=7) + runner.register_custom_hooks([hook_obj1, hook_obj2, hook_obj3]) + mock_hook1 = self.create_patch(hook_obj1, '_do_switch') + mock_hook2 = self.create_patch(hook_obj2, '_do_switch') + mock_hook3 = self.create_patch(hook_obj3, '_do_switch') + runner.call_hook('before_train') + mock_hook1.assert_called_once() + mock_hook2.assert_not_called() + mock_hook3.assert_not_called() + + def init_runner(self, resume=False, epoch=None, iter=None, by_epoch=True): + if by_epoch: + runner = Runner( + model=ExampleModel(), + work_dir=self.tmpdir.name, + train_dataloader=self.loader, + optim_wrapper=self.optim_wrapper, + param_scheduler=self.epochscheduler, + train_cfg=dict(by_epoch=True, max_epochs=3), + default_hooks=dict(logger=None), + log_processor=dict(window_size=1), + experiment_name=f'test_{resume}_{epoch}_{iter}_{random.random()}', + default_scope='mmcls') + else: + runner = Runner( + model=ExampleModel(), + work_dir=self.tmpdir.name, + train_dataloader=self.loader, + optim_wrapper=self.optim_wrapper, + param_scheduler=self.iterscheduler, + train_cfg=dict(by_epoch=False, max_iters=3), + default_hooks=dict(logger=None), + log_processor=dict(window_size=1), + experiment_name=f'test_{resume}_{epoch}_{iter}_{random.random()}', + default_scope='mmcls') + runner._resume = resume + dataset_length = len(self.loader) + if epoch and by_epoch: + runner.train_loop._epoch = epoch + runner.train_loop._iter = epoch * dataset_length + if iter and not by_epoch: + runner.train_loop._iter = iter + return runner + + def tearDown(self) -> None: + # `FileHandler` should be closed in Windows, otherwise we cannot + # delete the temporary directory. + logging.shutdown() + MMLogger._instance_dict.clear() + self.tmpdir.cleanup() From d074d26bfd1cb95f040b3cc19982bf4101dd95b3 Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 17:42:50 +0800 Subject: [PATCH 02/19] update doc --- mmcls/engine/hooks/switch_aug_hook.py | 40 +++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/mmcls/engine/hooks/switch_aug_hook.py b/mmcls/engine/hooks/switch_aug_hook.py index 34fd5580014..2d9046dcc2f 100644 --- a/mmcls/engine/hooks/switch_aug_hook.py +++ b/mmcls/engine/hooks/switch_aug_hook.py @@ -27,6 +27,34 @@ class SwitchTrainAugHook(Hook): Defaults to 'unchange', means not changing the train augments. loss (dict, str, optional): the new train loss. Defaults to 'unchange', means not changing the train loss. + + Example: + >>> # in config + >>> # deinfe new_train_pipeline, new_train_augments or new_loss + >>> custom_hooks = [ + dict( + type='SwitchDataAugHook', + action_epoch=37, + pipeline=new_train_pipeline, + train_augments=new_train_augments, + loss=new_loss), + >>> ] + >>> + >>> # switch data augments by epoch + >>> switch_hook = dict( + >>> type='SwitchTrainAugHook', + >>> pipeline=new_pipeline, + >>> action_epoch=5) + >>> runner.register_custom_hooks([switch_hook]) + >>> + >>> # switch train augments and loss by iter + >>> switch_hook = dict( + >>> type='SwitchTrainAugHook', + >>> train_augments=new_train_augments, + >>> loss=new_loss, + >>> action_iter=5) + >>> runner.register_custom_hooks([switch_hook]) + Note: This hook would modify the ``model.data_preprocessor.batch_augments`` @@ -52,12 +80,12 @@ def __init__(self, if action_iter is not None: assert isinstance(action_iter, int) and action_iter >= 0, ( '`action_iter` must be a number larger than 0 in ' - f'`SwitchTrainAugHook`, but got begin: {action_iter}') + f'`SwitchTrainAugHook`, but got action_iter: {action_iter}') self.by_epoch = False if action_epoch is not None: assert isinstance(action_epoch, int) and action_epoch >= 0, ( '`action_epoch` must be a number larger than 0 in ' - f'`SwitchTrainAugHook`, but got begin: {action_epoch}') + f'`SwitchTrainAugHook`, but got action_epoch: {action_epoch}') self.by_epoch = True self.action_epoch = action_epoch @@ -77,7 +105,7 @@ def __init__(self, def before_train(self, runner) -> None: """before run setting. If resume form a checkpoint, check whether is - the latest processed hook, if True, do the switch process before train. + the latest processed hook, if True, do the switch process. Args: runner (Runner): The runner of the training, validation or testing @@ -90,7 +118,7 @@ def before_train(self, runner) -> None: self._do_switch(runner, action_milestone_str) def before_train_epoch(self, runner): - """switch augments.""" + """do before train epoch.""" if self.by_epoch and runner.epoch + 1 == self.action_epoch: action_milestone_str = f' at Epoch {runner.epoch + 1}' self._do_switch(runner, action_milestone_str) @@ -100,7 +128,7 @@ def after_train_iter(self, batch_idx: int, data_batch: DATA_BATCH = None, outputs: Optional[dict] = None) -> None: - """switch augments.""" + """do before train iter.""" if not self.by_epoch and runner.iter + 1 == self.action_iter: action_milestone_str = f' at Iter {runner.iter + 1}' self._do_switch(runner, action_milestone_str) @@ -153,7 +181,7 @@ def _do_switch(self, runner, action_milestone_str=''): runner.logger.info(f'Switch train loss{action_milestone_str}.') def _switch_batch_augments(self, runner): - """switch the train aug.""" + """switch the train augments.""" model = runner.model if is_model_wrapper(model): model = model.module From 090cb448395089cbdcb99c75c93623ad07e0b26d Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 18:04:21 +0800 Subject: [PATCH 03/19] update doc --- mmcls/engine/hooks/switch_aug_hook.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mmcls/engine/hooks/switch_aug_hook.py b/mmcls/engine/hooks/switch_aug_hook.py index 2d9046dcc2f..4fbf07fc2b8 100644 --- a/mmcls/engine/hooks/switch_aug_hook.py +++ b/mmcls/engine/hooks/switch_aug_hook.py @@ -31,14 +31,13 @@ class SwitchTrainAugHook(Hook): Example: >>> # in config >>> # deinfe new_train_pipeline, new_train_augments or new_loss - >>> custom_hooks = [ - dict( - type='SwitchDataAugHook', - action_epoch=37, - pipeline=new_train_pipeline, - train_augments=new_train_augments, - loss=new_loss), - >>> ] + >>> custom_hooks = [ + >>> dict( + >>> type='SwitchDataAugHook', + >>> action_epoch=37, + >>> pipeline=new_train_pipeline, + >>> train_augments=new_train_augments, + >>> loss=new_loss),] >>> >>> # switch data augments by epoch >>> switch_hook = dict( From db1533855e2421358c30a8fd28ed7ee17115dc19 Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 18:10:50 +0800 Subject: [PATCH 04/19] fix lint --- mmcls/engine/hooks/switch_aug_hook.py | 37 ++-- .../test_hooks/test_switch_aug_hook.py | 159 +++++++++--------- 2 files changed, 102 insertions(+), 94 deletions(-) diff --git a/mmcls/engine/hooks/switch_aug_hook.py b/mmcls/engine/hooks/switch_aug_hook.py index 4fbf07fc2b8..176da8b1fd0 100644 --- a/mmcls/engine/hooks/switch_aug_hook.py +++ b/mmcls/engine/hooks/switch_aug_hook.py @@ -2,10 +2,10 @@ from typing import Optional, Sequence from mmcv.transforms import Compose -from mmcls.models.utils import RandomBatchAugment -from mmengine.model import is_model_wrapper from mmengine.hooks import Hook +from mmengine.model import is_model_wrapper +from mmcls.models.utils import RandomBatchAugment from mmcls.registry import HOOKS, MODELS DATA_BATCH = Optional[Sequence[dict]] @@ -27,28 +27,28 @@ class SwitchTrainAugHook(Hook): Defaults to 'unchange', means not changing the train augments. loss (dict, str, optional): the new train loss. Defaults to 'unchange', means not changing the train loss. - + Example: >>> # in config >>> # deinfe new_train_pipeline, new_train_augments or new_loss >>> custom_hooks = [ - >>> dict( - >>> type='SwitchDataAugHook', - >>> action_epoch=37, + >>> dict( + >>> type='SwitchDataAugHook', + >>> action_epoch=37, >>> pipeline=new_train_pipeline, >>> train_augments=new_train_augments, >>> loss=new_loss),] >>> >>> # switch data augments by epoch >>> switch_hook = dict( - >>> type='SwitchTrainAugHook', - >>> pipeline=new_pipeline, + >>> type='SwitchTrainAugHook', + >>> pipeline=new_pipeline, >>> action_epoch=5) >>> runner.register_custom_hooks([switch_hook]) - >>> + >>> >>> # switch train augments and loss by iter >>> switch_hook = dict( - >>> type='SwitchTrainAugHook', + >>> type='SwitchTrainAugHook', >>> train_augments=new_train_augments, >>> loss=new_loss, >>> action_iter=5) @@ -100,7 +100,6 @@ def __init__(self, self.train_augments = RandomBatchAugment(**train_augments) self.loss = MODELS.build(loss) if loss != 'unchange' else loss - def before_train(self, runner) -> None: """before run setting. If resume form a checkpoint, check whether is @@ -110,8 +109,8 @@ def before_train(self, runner) -> None: runner (Runner): The runner of the training, validation or testing process. """ - # if this hook is the latest switch hook obj in the previously unfinished - # tasks, then do the switch process before train + # if this hook is the latest switch hook obj in the previously + # unfinished tasks, then do the switch process before train if runner._resume and self._is_lastest_switch_hook(runner): action_milestone_str = ' after resume' self._do_switch(runner, action_milestone_str) @@ -133,8 +132,8 @@ def after_train_iter(self, self._do_switch(runner, action_milestone_str) def _is_lastest_switch_hook(self, runner): - """a helper function to judge if this hook is the latest processed switch - hooks with the same class name in a runner.""" + """a helper function to judge if this hook is the latest processed + switch hooks with the same class name in a runner.""" # collect all the switch_hook with the same class name in a list. switch_hook_objs = [ hook_obj for hook_obj in runner._hooks @@ -144,7 +143,7 @@ def _is_lastest_switch_hook(self, runner): # get the latest swict hook based on the current iter. dataset_length = len(runner.train_loop.dataloader) cur_iter = runner.train_loop.iter - + min_gap, min_gap_idx = float('inf'), -1 for i, switch_hook_obj in enumerate(switch_hook_objs): # use iter to calculate @@ -174,7 +173,7 @@ def _do_switch(self, runner, action_milestone_str=''): if self.pipeline != 'unchange': self._switch_train_loader_pipeline(runner) runner.logger.info(f'Switch train pipeline{action_milestone_str}.') - + if self.loss != 'unchange': self._switch_train_loss(runner) runner.logger.info(f'Switch train loss{action_milestone_str}.') @@ -209,8 +208,8 @@ def _switch_train_loader_pipeline(self, runner): # The dataset pipeline cannot be updated when persistent_workers # is True, so we need to force the dataloader's multi-process # restart. This is a very hacky approach. - if hasattr(train_loader, 'persistent_workers') \ - and train_loader.persistent_workers is True: + if hasattr(train_loader, 'persistent_workers' + ) and train_loader.persistent_workers is True: train_loader._DataLoader__initialized = False train_loader._iterator = None self._restart_dataloader = True diff --git a/tests/test_engine/test_hooks/test_switch_aug_hook.py b/tests/test_engine/test_hooks/test_switch_aug_hook.py index b42f14bb1fb..d8f51757d68 100644 --- a/tests/test_engine/test_hooks/test_switch_aug_hook.py +++ b/tests/test_engine/test_hooks/test_switch_aug_hook.py @@ -1,33 +1,31 @@ # Copyright (c) OpenMMLab. All rights reserved. import copy +import logging import random import tempfile -import logging +from typing import List from unittest import TestCase from unittest.mock import MagicMock, patch -from typing import List import torch import torch.nn as nn -from mmengine.logging import MMLogger -from mmengine.dataset import BaseDataset -from torch.utils.data import DataLoader from mmcv.transforms import BaseTransform -from mmengine.runner import Runner, EpochBasedTrainLoop, IterBasedTrainLoop +from mmengine.dataset import BaseDataset, ConcatDataset, RepeatDataset +from mmengine.logging import MMLogger from mmengine.model import BaseDataPreprocessor, BaseModel +from mmengine.runner import Runner from mmengine.utils import digit_version -from mmengine.dataset import ConcatDataset, RepeatDataset +from torch.utils.data import DataLoader -from mmcls.structures import ClsDataSample -from mmcls.models.losses import LabelSmoothLoss from mmcls.engine import SwitchTrainAugHook -from mmcls.models.utils.batch_augments import (CutMix, - Mixup, RandomBatchAugment) +from mmcls.models.losses import LabelSmoothLoss +from mmcls.models.utils.batch_augments import CutMix, Mixup, RandomBatchAugment +from mmcls.structures import ClsDataSample from mmcls.utils import register_all_modules - register_all_modules() + class MockDataPreprocessor(BaseDataPreprocessor): """mock preprocessor that do nothing.""" @@ -53,6 +51,7 @@ def train_step(self, data, optim_wrapper): outputs = {'loss': 0.5, 'num_samples': 1} return outputs + class ExampleDataset(BaseDataset): def load_data_list(self) -> List[dict]: @@ -65,10 +64,9 @@ def __getitem__(self, idx): def __len__(self): return 10 + class TestSwitchTrainAugHook(TestCase): - DEFAULT_CFG = dict( - action_epoch=1, - action_iter=None) + DEFAULT_CFG = dict(action_epoch=1, action_iter=None) def setUp(self): # optimizer @@ -104,12 +102,12 @@ def test_init(self): # check action_iter > 0 cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['action_epoch'] = None - cfg['action_iter'] = "3" + cfg['action_iter'] = '3' with self.assertRaises(AssertionError): SwitchTrainAugHook(**cfg) - + # test by_epoch is True - cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg = copy.deepcopy(self.DEFAULT_CFG) hook_obj = SwitchTrainAugHook(**cfg) self.assertTrue(hook_obj.by_epoch) self.assertEqual(hook_obj.action_epoch, 1) @@ -119,7 +117,7 @@ def test_init(self): self.assertEqual(hook_obj.loss, 'unchange') # test by_epoch is False - cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['action_epoch'] = None cfg['action_iter'] = 3 hook_obj = SwitchTrainAugHook(**cfg) @@ -128,7 +126,7 @@ def test_init(self): self.assertEqual(hook_obj.action_iter, 3) # test pipeline, loss - cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['pipeline'] = [dict(type='LoadImageFromFile')] cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.1) hook_obj = SwitchTrainAugHook(**cfg) @@ -136,15 +134,16 @@ def test_init(self): self.assertIsInstance(hook_obj.loss, LabelSmoothLoss) # test pieline is [], and train_augments - cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['pipeline'] = [] - train_cfg=dict(augments=[ + train_cfg = dict(augments=[ dict(type='Mixup', alpha=0.8), - dict(type='CutMix', alpha=1.0)]) + dict(type='CutMix', alpha=1.0) + ]) cfg['train_augments'] = train_cfg hook_obj = SwitchTrainAugHook(**cfg) self.assertIsInstance(hook_obj.pipeline, BaseTransform) - self.assertIsInstance(hook_obj.train_augments, RandomBatchAugment) + self.assertIsInstance(hook_obj.train_augments, RandomBatchAugment) def test_before_train_epoch(self): # test call once in epoch loop @@ -160,17 +159,16 @@ def test_before_train_epoch(self): runner = self.init_runner() switch_hook_cfg2 = copy.deepcopy(switch_hook_cfg1) switch_hook_cfg3 = copy.deepcopy(switch_hook_cfg1) - switch_hook_cfg2["action_epoch"] = 2 - switch_hook_cfg3["action_epoch"] = 3 + switch_hook_cfg2['action_epoch'] = 2 + switch_hook_cfg3['action_epoch'] = 3 runner.register_custom_hooks( [switch_hook_cfg1, switch_hook_cfg2, switch_hook_cfg3]) with patch.object(SwitchTrainAugHook, '_do_switch') as mock: runner.train() self.assertEqual(mock.call_count, 3) - def test_before_train_iter(self): - # test call once in iter loop + # test call once in iter loop runner = self.init_runner(by_epoch=False) switch_hook_cfg1 = copy.deepcopy(self.DEFAULT_CFG) switch_hook_cfg1['type'] = 'SwitchTrainAugHook' @@ -185,61 +183,65 @@ def test_before_train_iter(self): runner = self.init_runner(by_epoch=False) switch_hook_cfg2 = copy.deepcopy(switch_hook_cfg1) switch_hook_cfg3 = copy.deepcopy(switch_hook_cfg1) - switch_hook_cfg2["action_iter"] = 2 - switch_hook_cfg3["action_iter"] = 3 + switch_hook_cfg2['action_iter'] = 2 + switch_hook_cfg3['action_iter'] = 3 runner.register_custom_hooks( [switch_hook_cfg1, switch_hook_cfg2, switch_hook_cfg3]) with patch.object(SwitchTrainAugHook, '_do_switch') as mock: runner.train() self.assertEqual(mock.call_count, 3) - def test_do_switch(self): # test switch train augments runner = MagicMock() - cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg = copy.deepcopy(self.DEFAULT_CFG) train_cfg = dict(augments=[ dict(type='Mixup', alpha=0.5), - dict(type='CutMix', alpha=0.9)]) + dict(type='CutMix', alpha=0.9) + ]) cfg['train_augments'] = train_cfg hook_obj = SwitchTrainAugHook(**cfg) with patch.object(SwitchTrainAugHook, '_switch_train_loss') as m_loss: - with patch.object( - SwitchTrainAugHook, '_switch_train_loader_pipeline') as m_pipe: + with patch.object(SwitchTrainAugHook, + '_switch_train_loader_pipeline') as m_pipe: hook_obj._do_switch(runner) m_loss.assert_not_called() m_pipe.assert_not_called() - runner_bath_augs = runner.model.data_preprocessor.batch_augments - self.assertIsInstance(runner_bath_augs, RandomBatchAugment) - self.assertEqual(len(runner_bath_augs.augments), 2) - self.assertIsInstance(runner_bath_augs.augments[0], Mixup) - self.assertEqual(runner_bath_augs.augments[0].alpha, 0.5) - self.assertIsInstance(runner_bath_augs.augments[1], CutMix) - self.assertEqual(runner_bath_augs.augments[1].alpha, 0.9) + runner_batchaug = runner.model.data_preprocessor.batch_augments + self.assertIsInstance(runner_batchaug, RandomBatchAugment) + self.assertEqual(len(runner_batchaug.augments), 2) + self.assertIsInstance(runner_batchaug.augments[0], Mixup) + self.assertEqual(runner_batchaug.augments[0].alpha, 0.5) + self.assertIsInstance(runner_batchaug.augments[1], CutMix) + self.assertEqual(runner_batchaug.augments[1].alpha, 0.9) # test switch data aug runner = MagicMock() - cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['pipeline'] = [dict(type='LoadImageFromFile')] hook_obj = SwitchTrainAugHook(**cfg) - with patch.object(SwitchTrainAugHook, '_switch_batch_augments') as m_batch: - with patch.object( - SwitchTrainAugHook, '_switch_train_loss') as m_loss: + with patch.object(SwitchTrainAugHook, + '_switch_batch_augments') as m_batch: + with patch.object(SwitchTrainAugHook, + '_switch_train_loss') as m_loss: hook_obj._do_switch(runner) m_batch.assert_not_called() m_loss.assert_not_called() runner_pipeline = runner.train_loop.dataloader.dataset.pipeline self.assertIsInstance(runner_pipeline, BaseTransform) self.assertEqual(len(runner_pipeline.transforms), 1) - + # test with persistent_workers=True if digit_version(torch.__version__) >= digit_version('1.8.0'): runner = MagicMock() - loader = DataLoader(ExampleDataset(), persistent_workers=True, num_workers=1) + loader = DataLoader( + ExampleDataset(), persistent_workers=True, num_workers=1) runner.train_loop.dataloader = loader - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [dict(type='LoadImageFromFile'), - dict(type='Resize', scale=256)] + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=256) + ] hook_obj = SwitchTrainAugHook(**cfg) hook_obj._do_switch(runner) runner_pipeline = runner.train_loop.dataloader.dataset.pipeline @@ -249,13 +251,16 @@ def test_do_switch(self): # test with ConcatDataset warpper runner = MagicMock() loader = DataLoader( - ConcatDataset([ExampleDataset(), ExampleDataset()]), - persistent_workers=True, + ConcatDataset([ExampleDataset(), + ExampleDataset()]), + persistent_workers=True, num_workers=1) runner.train_loop.dataloader = loader - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [dict(type='LoadImageFromFile'), - dict(type='Resize', scale=256)] + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=256) + ] hook_obj = SwitchTrainAugHook(**cfg) hook_obj._do_switch(runner) for i in range(2): @@ -267,13 +272,15 @@ def test_do_switch(self): # test with RepeatDataset warpper runner = MagicMock() loader = DataLoader( - RepeatDataset(ExampleDataset(), 3), - persistent_workers=True, + RepeatDataset(ExampleDataset(), 3), + persistent_workers=True, num_workers=1) runner.train_loop.dataloader = loader - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [dict(type='LoadImageFromFile'), - dict(type='Resize', scale=256)] + cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg['pipeline'] = [ + dict(type='LoadImageFromFile'), + dict(type='Resize', scale=256) + ] hook_obj = SwitchTrainAugHook(**cfg) hook_obj._do_switch(runner) runner_pipeline = runner.train_loop.dataloader.dataset.dataset.pipeline @@ -282,12 +289,13 @@ def test_do_switch(self): # test switch loss runner = MagicMock() - cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.2) hook_obj = SwitchTrainAugHook(**cfg) - with patch.object(SwitchTrainAugHook, '_switch_batch_augments') as m_batch: - with patch.object( - SwitchTrainAugHook, '_switch_train_loader_pipeline') as m_pipe: + with patch.object(SwitchTrainAugHook, + '_switch_batch_augments') as m_batch: + with patch.object(SwitchTrainAugHook, + '_switch_train_loader_pipeline') as m_pipe: hook_obj._do_switch(runner) m_batch.assert_not_called() m_pipe.assert_not_called() @@ -297,20 +305,21 @@ def test_do_switch(self): # test both runner = MagicMock() - cfg = copy.deepcopy(self.DEFAULT_CFG) + cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['pipeline'] = [dict(type='LoadImageFromFile')] cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.2) cfg['train_augments'] = dict(augments=[dict(type='Mixup', alpha=0.5)]) hook_obj = SwitchTrainAugHook(**cfg) - with patch.object(SwitchTrainAugHook, '_switch_batch_augments') as m_batch: - with patch.object( - SwitchTrainAugHook, '_switch_train_loader_pipeline') as m_pipe: - with patch.object( - SwitchTrainAugHook, '_switch_train_loss') as m_loss: + with patch.object(SwitchTrainAugHook, + '_switch_batch_augments') as m_batch: + with patch.object(SwitchTrainAugHook, + '_switch_train_loader_pipeline') as m_pipe: + with patch.object(SwitchTrainAugHook, + '_switch_train_loss') as m_loss: hook_obj._do_switch(runner) m_batch.assert_called_once() m_pipe.assert_called_once() - m_loss.assert_called_once() + m_loss.assert_called_once() def create_patch(self, object, name): patcher = patch.object(object, name) @@ -370,7 +379,7 @@ def test_before_train(self): mock_hook3.assert_not_called() # test resume from epoch loop and iter hook - runner = self.init_runner(resume=True, epoch=1) # i epoch = 5 iter + runner = self.init_runner(resume=True, epoch=1) # i epoch = 5 iter hook_obj1 = SwitchTrainAugHook(action_iter=2) hook_obj2 = SwitchTrainAugHook(action_iter=15) hook_obj3 = SwitchTrainAugHook(action_iter=7) @@ -394,7 +403,7 @@ def init_runner(self, resume=False, epoch=None, iter=None, by_epoch=True): train_cfg=dict(by_epoch=True, max_epochs=3), default_hooks=dict(logger=None), log_processor=dict(window_size=1), - experiment_name=f'test_{resume}_{epoch}_{iter}_{random.random()}', + experiment_name=f'test_{resume}_{random.random()}', default_scope='mmcls') else: runner = Runner( @@ -406,7 +415,7 @@ def init_runner(self, resume=False, epoch=None, iter=None, by_epoch=True): train_cfg=dict(by_epoch=False, max_iters=3), default_hooks=dict(logger=None), log_processor=dict(window_size=1), - experiment_name=f'test_{resume}_{epoch}_{iter}_{random.random()}', + experiment_name=f'test_{resume}_{random.random()}', default_scope='mmcls') runner._resume = resume dataset_length = len(self.loader) From 8f0b3534b0cee012545393629f0660b26397d7ca Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:34:28 +0800 Subject: [PATCH 05/19] fix ci --- tests/test_engine/test_hooks/test_switch_aug_hook.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_engine/test_hooks/test_switch_aug_hook.py b/tests/test_engine/test_hooks/test_switch_aug_hook.py index d8f51757d68..b48ca973e2d 100644 --- a/tests/test_engine/test_hooks/test_switch_aug_hook.py +++ b/tests/test_engine/test_hooks/test_switch_aug_hook.py @@ -253,8 +253,7 @@ def test_do_switch(self): loader = DataLoader( ConcatDataset([ExampleDataset(), ExampleDataset()]), - persistent_workers=True, - num_workers=1) + persistent_workers=True) runner.train_loop.dataloader = loader cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['pipeline'] = [ From efb995414923e9ed90141da2eafca96c5689657f Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:48:22 +0800 Subject: [PATCH 06/19] fix ci --- tests/test_engine/test_hooks/test_switch_aug_hook.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_engine/test_hooks/test_switch_aug_hook.py b/tests/test_engine/test_hooks/test_switch_aug_hook.py index b48ca973e2d..073c8bc73d4 100644 --- a/tests/test_engine/test_hooks/test_switch_aug_hook.py +++ b/tests/test_engine/test_hooks/test_switch_aug_hook.py @@ -252,8 +252,7 @@ def test_do_switch(self): runner = MagicMock() loader = DataLoader( ConcatDataset([ExampleDataset(), - ExampleDataset()]), - persistent_workers=True) + ExampleDataset()])) runner.train_loop.dataloader = loader cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['pipeline'] = [ From 18f92cc2be43437da26d9c593a9f62a8ab46f89e Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 20:13:23 +0800 Subject: [PATCH 07/19] fix typo --- mmcls/engine/hooks/switch_aug_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmcls/engine/hooks/switch_aug_hook.py b/mmcls/engine/hooks/switch_aug_hook.py index 176da8b1fd0..f2415bf52f1 100644 --- a/mmcls/engine/hooks/switch_aug_hook.py +++ b/mmcls/engine/hooks/switch_aug_hook.py @@ -33,7 +33,7 @@ class SwitchTrainAugHook(Hook): >>> # deinfe new_train_pipeline, new_train_augments or new_loss >>> custom_hooks = [ >>> dict( - >>> type='SwitchDataAugHook', + >>> type='SwitchTrainAugHook', >>> action_epoch=37, >>> pipeline=new_train_pipeline, >>> train_augments=new_train_augments, From 82aabed45254aba0adc826a6255bd5af1085ff0f Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 22:46:45 +0800 Subject: [PATCH 08/19] fix ci --- tests/test_engine/test_hooks/test_switch_aug_hook.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/test_engine/test_hooks/test_switch_aug_hook.py b/tests/test_engine/test_hooks/test_switch_aug_hook.py index 073c8bc73d4..cb9acb79089 100644 --- a/tests/test_engine/test_hooks/test_switch_aug_hook.py +++ b/tests/test_engine/test_hooks/test_switch_aug_hook.py @@ -269,10 +269,7 @@ def test_do_switch(self): # test with RepeatDataset warpper runner = MagicMock() - loader = DataLoader( - RepeatDataset(ExampleDataset(), 3), - persistent_workers=True, - num_workers=1) + loader = DataLoader(RepeatDataset(ExampleDataset(), 3)) runner.train_loop.dataloader = loader cfg = copy.deepcopy(self.DEFAULT_CFG) cfg['pipeline'] = [ From 7d7cab26482332f0c882509e93628cf6a6f433e0 Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 19:33:17 +0800 Subject: [PATCH 09/19] update configs names --- configs/mobileone/README.md | 14 ++++++------- .../deploy/mobileone-s0_deploy_8xb128_in1k.py | 3 --- .../deploy/mobileone-s0_deploy_8xb32_in1k.py | 3 +++ .../deploy/mobileone-s1_deploy_8xb128_in1k.py | 3 --- .../deploy/mobileone-s1_deploy_8xb32_in1k.py | 3 +++ .../deploy/mobileone-s2_deploy_8xb128_in1k.py | 3 --- .../deploy/mobileone-s2_deploy_8xb32_in1k.py | 3 +++ .../deploy/mobileone-s3_deploy_8xb128_in1k.py | 3 --- .../deploy/mobileone-s3_deploy_8xb32_in1k.py | 3 +++ .../deploy/mobileone-s4_deploy_8xb128_in1k.py | 3 --- .../deploy/mobileone-s4_deploy_8xb32_in1k.py | 3 +++ configs/mobileone/metafile.yml | 20 +++++++++---------- ...128_in1k.py => mobileone-s0_8xb32_in1k.py} | 0 ...128_in1k.py => mobileone-s1_8xb32_in1k.py} | 0 ...128_in1k.py => mobileone-s2_8xb32_in1k.py} | 0 ...128_in1k.py => mobileone-s3_8xb32_in1k.py} | 0 ...128_in1k.py => mobileone-s4_8xb32_in1k.py} | 0 17 files changed, 32 insertions(+), 32 deletions(-) delete mode 100644 configs/mobileone/deploy/mobileone-s0_deploy_8xb128_in1k.py create mode 100644 configs/mobileone/deploy/mobileone-s0_deploy_8xb32_in1k.py delete mode 100644 configs/mobileone/deploy/mobileone-s1_deploy_8xb128_in1k.py create mode 100644 configs/mobileone/deploy/mobileone-s1_deploy_8xb32_in1k.py delete mode 100644 configs/mobileone/deploy/mobileone-s2_deploy_8xb128_in1k.py create mode 100644 configs/mobileone/deploy/mobileone-s2_deploy_8xb32_in1k.py delete mode 100644 configs/mobileone/deploy/mobileone-s3_deploy_8xb128_in1k.py create mode 100644 configs/mobileone/deploy/mobileone-s3_deploy_8xb32_in1k.py delete mode 100644 configs/mobileone/deploy/mobileone-s4_deploy_8xb128_in1k.py create mode 100644 configs/mobileone/deploy/mobileone-s4_deploy_8xb32_in1k.py rename configs/mobileone/{mobileone-s0_8xb128_in1k.py => mobileone-s0_8xb32_in1k.py} (100%) rename configs/mobileone/{mobileone-s1_8xb128_in1k.py => mobileone-s1_8xb32_in1k.py} (100%) rename configs/mobileone/{mobileone-s2_8xb128_in1k.py => mobileone-s2_8xb32_in1k.py} (100%) rename configs/mobileone/{mobileone-s3_8xb128_in1k.py => mobileone-s3_8xb32_in1k.py} (100%) rename configs/mobileone/{mobileone-s4_8xb128_in1k.py => mobileone-s4_8xb32_in1k.py} (100%) diff --git a/configs/mobileone/README.md b/configs/mobileone/README.md index 80fbb148359..1bb31a1d5b6 100644 --- a/configs/mobileone/README.md +++ b/configs/mobileone/README.md @@ -18,11 +18,11 @@ Efficient neural network backbones for mobile devices are often optimized for me | Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download | | :------------: | :-----------------------------: | :----------------------------: | :-------: | :-------: | :--------------------------------------------------: | :-----------------------------------------------------: | -| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.36 | 89.87 | [config (train)](./mobileone-s0_8xb128_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220915-007ae971.pth) | -| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.76 | 92.77 | [config (train)](./mobileone-s1_8xb128_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_3rdparty_in1k_20220915-473c8469.pth) | -| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.39 | 93.63 | [config (train)](./mobileone-s2_8xb128_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_3rdparty_in1k_20220915-ed2e4c30.pth) | -| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 77.93 | 93.89 | [config (train)](./mobileone-s3_8xb128_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_3rdparty_in1k_20220915-84d6a02c.pth) | -| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.30 | 94.37 | [config (train)](./mobileone-s4_8xb128_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb128_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_3rdparty_in1k_20220915-ce9509ee.pth) | +| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.36 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220915-007ae971.pth) | +| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.76 | 92.77 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_3rdparty_in1k_20220915-473c8469.pth) | +| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.39 | 93.63 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_3rdparty_in1k_20220915-ed2e4c30.pth) | +| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 77.93 | 93.89 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_3rdparty_in1k_20220915-84d6a02c.pth) | +| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.30 | 94.37 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_3rdparty_in1k_20220915-ce9509ee.pth) | *Models with * are converted from the [official repo](https://github.com/apple/ml-mobileone). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.* @@ -45,7 +45,7 @@ python tools/convert_models/reparameterize_model.py ${CFG_PATH} ${SRC_CKPT_PATH} For example: ```shell -python ./tools/convert_models/reparameterize_model.py ./configs/mobileone/mobileone-s0_8xb128_in1k.py https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220811-db5ce29b.pth ./mobileone_s0_deploy.pth +python ./tools/convert_models/reparameterize_model.py ./configs/mobileone/mobileone-s0_8xb32_in1k.py https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220811-db5ce29b.pth ./mobileone_s0_deploy.pth ``` To use reparameterized weights, the config file must switch to **the deploy config files**. @@ -57,7 +57,7 @@ python tools/test.py ${Deploy_CFG} ${Deploy_Checkpoint} --metrics accuracy For example of using the reparameterized weights above: ```shell -python ./tools/test.py ./configs/mobileone/deploy/mobileone-s0_deploy_8xb128_in1k.py mobileone_s0_deploy.pth --metrics accuracy +python ./tools/test.py ./configs/mobileone/deploy/mobileone-s0_deploy_8xb32_in1k.py mobileone_s0_deploy.pth --metrics accuracy ``` ### In the code diff --git a/configs/mobileone/deploy/mobileone-s0_deploy_8xb128_in1k.py b/configs/mobileone/deploy/mobileone-s0_deploy_8xb128_in1k.py deleted file mode 100644 index 8902483cfd2..00000000000 --- a/configs/mobileone/deploy/mobileone-s0_deploy_8xb128_in1k.py +++ /dev/null @@ -1,3 +0,0 @@ -_base_ = ['../mobileone-s0_8xb128_in1k.py'] - -model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s0_deploy_8xb32_in1k.py b/configs/mobileone/deploy/mobileone-s0_deploy_8xb32_in1k.py new file mode 100644 index 00000000000..145f3f4ec90 --- /dev/null +++ b/configs/mobileone/deploy/mobileone-s0_deploy_8xb32_in1k.py @@ -0,0 +1,3 @@ +_base_ = ['../mobileone-s0_8xb32_in1k.py'] + +model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s1_deploy_8xb128_in1k.py b/configs/mobileone/deploy/mobileone-s1_deploy_8xb128_in1k.py deleted file mode 100644 index 7bcf3211a13..00000000000 --- a/configs/mobileone/deploy/mobileone-s1_deploy_8xb128_in1k.py +++ /dev/null @@ -1,3 +0,0 @@ -_base_ = ['../mobileone-s1_8xb128_in1k.py'] - -model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s1_deploy_8xb32_in1k.py b/configs/mobileone/deploy/mobileone-s1_deploy_8xb32_in1k.py new file mode 100644 index 00000000000..8602c31ce6c --- /dev/null +++ b/configs/mobileone/deploy/mobileone-s1_deploy_8xb32_in1k.py @@ -0,0 +1,3 @@ +_base_ = ['../mobileone-s1_8xb32_in1k.py'] + +model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s2_deploy_8xb128_in1k.py b/configs/mobileone/deploy/mobileone-s2_deploy_8xb128_in1k.py deleted file mode 100644 index 5d64d51937c..00000000000 --- a/configs/mobileone/deploy/mobileone-s2_deploy_8xb128_in1k.py +++ /dev/null @@ -1,3 +0,0 @@ -_base_ = ['../mobileone-s2_8xb128_in1k.py'] - -model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s2_deploy_8xb32_in1k.py b/configs/mobileone/deploy/mobileone-s2_deploy_8xb32_in1k.py new file mode 100644 index 00000000000..97aaddd0740 --- /dev/null +++ b/configs/mobileone/deploy/mobileone-s2_deploy_8xb32_in1k.py @@ -0,0 +1,3 @@ +_base_ = ['../mobileone-s2_8xb32_in1k.py'] + +model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s3_deploy_8xb128_in1k.py b/configs/mobileone/deploy/mobileone-s3_deploy_8xb128_in1k.py deleted file mode 100644 index 8c710f7836a..00000000000 --- a/configs/mobileone/deploy/mobileone-s3_deploy_8xb128_in1k.py +++ /dev/null @@ -1,3 +0,0 @@ -_base_ = ['../mobileone-s3_8xb128_in1k.py'] - -model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s3_deploy_8xb32_in1k.py b/configs/mobileone/deploy/mobileone-s3_deploy_8xb32_in1k.py new file mode 100644 index 00000000000..0d335a7ba93 --- /dev/null +++ b/configs/mobileone/deploy/mobileone-s3_deploy_8xb32_in1k.py @@ -0,0 +1,3 @@ +_base_ = ['../mobileone-s3_8xb32_in1k.py'] + +model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s4_deploy_8xb128_in1k.py b/configs/mobileone/deploy/mobileone-s4_deploy_8xb128_in1k.py deleted file mode 100644 index 6ca4d18e7d2..00000000000 --- a/configs/mobileone/deploy/mobileone-s4_deploy_8xb128_in1k.py +++ /dev/null @@ -1,3 +0,0 @@ -_base_ = ['../mobileone-s4_8xb128_in1k.py'] - -model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/deploy/mobileone-s4_deploy_8xb32_in1k.py b/configs/mobileone/deploy/mobileone-s4_deploy_8xb32_in1k.py new file mode 100644 index 00000000000..b82f5a9ac7e --- /dev/null +++ b/configs/mobileone/deploy/mobileone-s4_deploy_8xb32_in1k.py @@ -0,0 +1,3 @@ +_base_ = ['../mobileone-s4_8xb32_in1k.py'] + +model = dict(backbone=dict(deploy=True)) diff --git a/configs/mobileone/metafile.yml b/configs/mobileone/metafile.yml index 04eaceff2ab..676c30b4be4 100644 --- a/configs/mobileone/metafile.yml +++ b/configs/mobileone/metafile.yml @@ -16,9 +16,9 @@ Collections: Version: v1.0.0rc1 Models: - - Name: mobileone-s0_3rdparty_8xb128_in1k + - Name: mobileone-s0_3rdparty_8xb32_in1k In Collection: MobileOne - Config: configs/mobileone/mobileone-s0_8xb128_in1k.py + Config: configs/mobileone/mobileone-s0_8xb32_in1k.py Metadata: FLOPs: 1091227648 # 1.09G Parameters: 5293272 # 5.29M @@ -32,9 +32,9 @@ Models: Converted From: Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s0_unfused.pth.tar Code: https://github.com/apple/ml-mobileone - - Name: mobileone-s1_3rdparty_8xb128_in1k + - Name: mobileone-s1_3rdparty_8xb32_in1k In Collection: MobileOne - Config: configs/mobileone/mobileone-s1_8xb128_in1k.py + Config: configs/mobileone/mobileone-s1_8xb32_in1k.py Metadata: FLOPs: 863491328 # 8.6G Parameters: 4825192 # 4.82M @@ -48,9 +48,9 @@ Models: Converted From: Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s1_unfused.pth.tar Code: https://github.com/apple/ml-mobileone - - Name: mobileone-s2_3rdparty_8xb128_in1k + - Name: mobileone-s2_3rdparty_8xb32_in1k In Collection: MobileOne - Config: configs/mobileone/mobileone-s2_8xb128_in1k.py + Config: configs/mobileone/mobileone-s2_8xb32_in1k.py Metadata: FLOPs: 1344083328 Parameters: 7884648 @@ -64,9 +64,9 @@ Models: Converted From: Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s2_unfused.pth.tar Code: https://github.com/apple/ml-mobileone - - Name: mobileone-s3_3rdparty_8xb128_in1k + - Name: mobileone-s3_3rdparty_8xb32_in1k In Collection: MobileOne - Config: configs/mobileone/mobileone-s3_8xb128_in1k.py + Config: configs/mobileone/mobileone-s3_8xb32_in1k.py Metadata: FLOPs: 1951043584 Parameters: 10170600 @@ -80,9 +80,9 @@ Models: Converted From: Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s3_unfused.pth.tar Code: https://github.com/apple/ml-mobileone - - Name: mobileone-s4_3rdparty_8xb128_in1k + - Name: mobileone-s4_3rdparty_8xb32_in1k In Collection: MobileOne - Config: configs/mobileone/mobileone-s4_8xb128_in1k.py + Config: configs/mobileone/mobileone-s4_8xb32_in1k.py Metadata: FLOPs: 3052580688 Parameters: 14951248 diff --git a/configs/mobileone/mobileone-s0_8xb128_in1k.py b/configs/mobileone/mobileone-s0_8xb32_in1k.py similarity index 100% rename from configs/mobileone/mobileone-s0_8xb128_in1k.py rename to configs/mobileone/mobileone-s0_8xb32_in1k.py diff --git a/configs/mobileone/mobileone-s1_8xb128_in1k.py b/configs/mobileone/mobileone-s1_8xb32_in1k.py similarity index 100% rename from configs/mobileone/mobileone-s1_8xb128_in1k.py rename to configs/mobileone/mobileone-s1_8xb32_in1k.py diff --git a/configs/mobileone/mobileone-s2_8xb128_in1k.py b/configs/mobileone/mobileone-s2_8xb32_in1k.py similarity index 100% rename from configs/mobileone/mobileone-s2_8xb128_in1k.py rename to configs/mobileone/mobileone-s2_8xb32_in1k.py diff --git a/configs/mobileone/mobileone-s3_8xb128_in1k.py b/configs/mobileone/mobileone-s3_8xb32_in1k.py similarity index 100% rename from configs/mobileone/mobileone-s3_8xb128_in1k.py rename to configs/mobileone/mobileone-s3_8xb32_in1k.py diff --git a/configs/mobileone/mobileone-s4_8xb128_in1k.py b/configs/mobileone/mobileone-s4_8xb32_in1k.py similarity index 100% rename from configs/mobileone/mobileone-s4_8xb128_in1k.py rename to configs/mobileone/mobileone-s4_8xb32_in1k.py From 383666f6d0bb68019f7bcae3b85342e4fba888f9 Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 20:09:35 +0800 Subject: [PATCH 10/19] update configs --- .../imagenet_bs256_coslr_coswd_300e.py | 40 +++++++++ configs/mobileone/mobileone-s0_8xb32_in1k.py | 84 +++++++++---------- configs/mobileone/mobileone-s1_8xb32_in1k.py | 51 +++++++++-- configs/mobileone/mobileone-s2_8xb32_in1k.py | 68 +++++++++++++-- configs/mobileone/mobileone-s3_8xb32_in1k.py | 70 ++++++++++++++-- configs/mobileone/mobileone-s4_8xb32_in1k.py | 68 +++++++++++++-- 6 files changed, 302 insertions(+), 79 deletions(-) create mode 100644 configs/_base_/schedules/imagenet_bs256_coslr_coswd_300e.py diff --git a/configs/_base_/schedules/imagenet_bs256_coslr_coswd_300e.py b/configs/_base_/schedules/imagenet_bs256_coslr_coswd_300e.py new file mode 100644 index 00000000000..318e0315743 --- /dev/null +++ b/configs/_base_/schedules/imagenet_bs256_coslr_coswd_300e.py @@ -0,0 +1,40 @@ +# optimizer +optim_wrapper = dict( + optimizer=dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.0001)) + +# learning policy +param_scheduler = [ + # warm up learning rate scheduler + dict( + type='LinearLR', + start_factor=0.001, + by_epoch=True, + begin=0, + end=5, + # update by iter + convert_to_iter_based=True), + # main learning rate scheduler + dict( + type='CosineAnnealingLR', + T_max=295, + eta_min=1.0e-6, + by_epoch=True, + begin=5, + end=300), + dict( + type='CosineAnnealingParamScheduler', + param_name='weight_decay', + eta_min=0.00001, + by_epoch=True, + begin=0, + end=300) +] + +# train, val, test setting +train_cfg = dict(by_epoch=True, max_epochs=300, val_interval=1) +val_cfg = dict() +test_cfg = dict() + +# NOTE: `auto_scale_lr` is for automatically scaling LR, +# based on the actual training batch size. +auto_scale_lr = dict(base_batch_size=256) diff --git a/configs/mobileone/mobileone-s0_8xb32_in1k.py b/configs/mobileone/mobileone-s0_8xb32_in1k.py index ceeb21b7fe2..413fca3d295 100644 --- a/configs/mobileone/mobileone-s0_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s0_8xb32_in1k.py @@ -1,56 +1,50 @@ _base_ = [ '../_base_/models/mobileone/mobileone_s0.py', '../_base_/datasets/imagenet_bs32_pil_resize.py', + '../_base_/schedules/imagenet_bs256_coslr_coswd_300e.py', '../_base_/default_runtime.py' ] -# dataset settings -train_dataloader = dict(batch_size=128) -val_dataloader = dict(batch_size=128) -test_dataloader = dict(batch_size=128) - # schedule settings -optim_wrapper = dict( - optimizer=dict(type='SGD', lr=0.1, momentum=0.9, weight_decay=0.0001), - paramwise_cfg=dict(bias_decay_mult=0., norm_decay_mult=0.), -) - -# learning policy -param_scheduler = [ - # warm up learning rate scheduler - dict( - type='LinearLR', - start_factor=0.001, - by_epoch=True, - begin=0, - end=5, - # update by iter - convert_to_iter_based=True), - # main learning rate scheduler - dict( - type='CosineAnnealingLR', - T_max=295, - eta_min=1.0e-6, - by_epoch=True, - begin=5, - end=300), - dict( - type='CosineAnnealingParamScheduler', - param_name='weight_decay', - eta_min=0.00001, - by_epoch=True, - begin=0, - end=300) +optim_wrapper = dict(paramwise_cfg=dict(norm_decay_mult=0.)) + +val_dataloader = dict(batch_size=256) +test_dataloader = dict(batch_size=256) + +base_train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='RandomResizedCrop', scale=224, backend='pillow'), + dict(type='RandomFlip', prob=0.5, direction='horizontal'), + dict(type='PackClsInputs') ] -# train, val, test setting -train_cfg = dict(by_epoch=True, max_epochs=300, val_interval=1) -val_cfg = dict() -test_cfg = dict() +import copy # noqa: E402 -# NOTE: `auto_scale_lr` is for automatically scaling LR, -# based on the actual training batch size. -auto_scale_lr = dict(base_batch_size=1024) +# modify start epoch's RandomResizedCrop.scale to 160 +train_pipeline_1e = copy.deepcopy(base_train_pipeline) +train_pipeline_1e[1]['scale'] = 160 +_base_.train_dataloader.dataset.pipeline = train_pipeline_1e -# runtime setting -custom_hooks = [dict(type='EMAHook', momentum=5e-4, priority='ABOVE_NORMAL')] +# modify 37 epoch's RandomResizedCrop.scale to 192 +train_pipeline_37e = copy.deepcopy(base_train_pipeline) +train_pipeline_37e[1]['scale'] = 192 + +# modify 112 epoch's RandomResizedCrop.scale to 224 +train_pipeline_112e = copy.deepcopy(base_train_pipeline) +train_pipeline_112e[1]['scale'] = 224 + +custom_hooks = [ + dict( + type='SwitchTrainAugHook', + action_epoch=37, + pipeline=train_pipeline_37e), + dict( + type='SwitchTrainAugHook', + action_epoch=112, + pipeline=train_pipeline_112e), + dict( + type='EMAHook', + momentum=5e-4, + priority='ABOVE_NORMAL', + update_buffers=True) +] diff --git a/configs/mobileone/mobileone-s1_8xb32_in1k.py b/configs/mobileone/mobileone-s1_8xb32_in1k.py index b14c7c17d1b..bf575a456ec 100644 --- a/configs/mobileone/mobileone-s1_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s1_8xb32_in1k.py @@ -1,15 +1,50 @@ _base_ = [ '../_base_/models/mobileone/mobileone_s1.py', '../_base_/datasets/imagenet_bs32_pil_resize.py', - '../_base_/schedules/imagenet_bs256_coslr.py', + '../_base_/schedules/imagenet_bs256_coslr_coswd_300e.py', '../_base_/default_runtime.py' ] -# dataset settings -train_dataloader = dict(batch_size=128) -val_dataloader = dict(batch_size=128) -test_dataloader = dict(batch_size=128) +# schedule settings +optim_wrapper = dict(paramwise_cfg=dict(norm_decay_mult=0.)) -# NOTE: `auto_scale_lr` is for automatically scaling LR, -# based on the actual training batch size. -auto_scale_lr = dict(base_batch_size=1024) +val_dataloader = dict(batch_size=256) +test_dataloader = dict(batch_size=256) + +base_train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='RandomResizedCrop', scale=224, backend='pillow'), + dict(type='RandomFlip', prob=0.5, direction='horizontal'), + dict(type='PackClsInputs') +] + +import copy # noqa: E402 + +# modify start epoch's RandomResizedCrop.scale to 160 +train_pipeline_1e = copy.deepcopy(base_train_pipeline) +train_pipeline_1e[1]['scale'] = 160 +_base_.train_dataloader.dataset.pipeline = train_pipeline_1e + +# modify 37 epoch's RandomResizedCrop.scale to 192 +train_pipeline_37e = copy.deepcopy(base_train_pipeline) +train_pipeline_37e[1]['scale'] = 192 + +# modify 112 epoch's RandomResizedCrop.scale to 224 +train_pipeline_112e = copy.deepcopy(base_train_pipeline) +train_pipeline_112e[1]['scale'] = 224 + +custom_hooks = [ + dict( + type='SwitchTrainAugHook', + action_epoch=37, + pipeline=train_pipeline_37e), + dict( + type='SwitchTrainAugHook', + action_epoch=112, + pipeline=train_pipeline_112e), + dict( + type='EMAHook', + momentum=5e-4, + priority='ABOVE_NORMAL', + update_buffers=True) +] diff --git a/configs/mobileone/mobileone-s2_8xb32_in1k.py b/configs/mobileone/mobileone-s2_8xb32_in1k.py index dca0d4d3e58..ce3bcae4e47 100644 --- a/configs/mobileone/mobileone-s2_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s2_8xb32_in1k.py @@ -1,15 +1,67 @@ _base_ = [ '../_base_/models/mobileone/mobileone_s2.py', '../_base_/datasets/imagenet_bs32_pil_resize.py', - '../_base_/schedules/imagenet_bs256_coslr.py', + '../_base_/schedules/imagenet_bs256_coslr_coswd_300e.py', '../_base_/default_runtime.py' ] -# dataset settings -train_dataloader = dict(batch_size=128) -val_dataloader = dict(batch_size=128) -test_dataloader = dict(batch_size=128) +# schedule settings +optim_wrapper = dict(paramwise_cfg=dict(norm_decay_mult=0.)) -# NOTE: `auto_scale_lr` is for automatically scaling LR, -# based on the actual training batch size. -auto_scale_lr = dict(base_batch_size=1024) +val_dataloader = dict(batch_size=256) +test_dataloader = dict(batch_size=256) + +import copy # noqa: E402 + +bgr_mean = _base_.data_preprocessor['mean'][::-1] +base_train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='RandomResizedCrop', scale=224, backend='pillow'), + dict(type='RandomFlip', prob=0.5, direction='horizontal'), + dict( + type='RandAugment', + policies='timm_increasing', + num_policies=2, + total_level=10, + magnitude_level=7, + magnitude_std=0.5, + hparams=dict(pad_val=[round(x) for x in bgr_mean])), + dict(type='PackClsInputs') +] + +# modify start epoch RandomResizedCrop.scale to 160 +# and RA.magnitude_level * 0.3 +train_pipeline_1e = copy.deepcopy(base_train_pipeline) +train_pipeline_1e[1]['scale'] = 160 +train_pipeline_1e[3]['magnitude_level'] *= 0.3 +_base_.train_dataloader.dataset.pipeline = train_pipeline_1e + +import copy # noqa: E402 + +# modify 137 epoch's RandomResizedCrop.scale to 192 +# and RA.magnitude_level * 0.7 +train_pipeline_37e = copy.deepcopy(base_train_pipeline) +train_pipeline_37e[1]['scale'] = 192 +train_pipeline_37e[3]['magnitude_level'] *= 0.7 + +# modify 112 epoch's RandomResizedCrop.scale to 224 +# and RA.magnitude_level * 1.0 +train_pipeline_112e = copy.deepcopy(base_train_pipeline) +train_pipeline_112e[1]['scale'] = 224 +train_pipeline_112e[3]['magnitude_level'] *= 1.0 + +custom_hooks = [ + dict( + type='SwitchTrainAugHook', + action_epoch=37, + pipeline=train_pipeline_37e), + dict( + type='SwitchTrainAugHook', + action_epoch=112, + pipeline=train_pipeline_112e), + dict( + type='EMAHook', + momentum=5e-4, + priority='ABOVE_NORMAL', + update_buffers=True) +] diff --git a/configs/mobileone/mobileone-s3_8xb32_in1k.py b/configs/mobileone/mobileone-s3_8xb32_in1k.py index 89343d5d861..7e12da3d176 100644 --- a/configs/mobileone/mobileone-s3_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s3_8xb32_in1k.py @@ -1,15 +1,67 @@ _base_ = [ '../_base_/models/mobileone/mobileone_s3.py', - '../_base_/datasets/imagenet_bs64_pil_resize.py', - '../_base_/schedules/imagenet_bs256_coslr.py', + '../_base_/datasets/imagenet_bs32_pil_resize.py', + '../_base_/schedules/imagenet_bs256_coslr_coswd_300e.py', '../_base_/default_runtime.py' ] -# dataset settings -train_dataloader = dict(batch_size=128) -val_dataloader = dict(batch_size=128) -test_dataloader = dict(batch_size=128) +# schedule settings +optim_wrapper = dict(paramwise_cfg=dict(norm_decay_mult=0.)) -# NOTE: `auto_scale_lr` is for automatically scaling LR, -# based on the actual training batch size. -auto_scale_lr = dict(base_batch_size=1024) +val_dataloader = dict(batch_size=256) +test_dataloader = dict(batch_size=256) + +import copy # noqa: E402 + +bgr_mean = _base_.data_preprocessor['mean'][::-1] +base_train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='RandomResizedCrop', scale=224, backend='pillow'), + dict(type='RandomFlip', prob=0.5, direction='horizontal'), + dict( + type='RandAugment', + policies='timm_increasing', + num_policies=2, + total_level=10, + magnitude_level=7, + magnitude_std=0.5, + hparams=dict(pad_val=[round(x) for x in bgr_mean])), + dict(type='PackClsInputs') +] + +# modify start epoch RandomResizedCrop.scale to 160 +# and RA.magnitude_level * 0.3 +train_pipeline_1e = copy.deepcopy(base_train_pipeline) +train_pipeline_1e[1]['scale'] = 160 +train_pipeline_1e[3]['magnitude_level'] *= 0.3 +_base_.train_dataloader.dataset.pipeline = train_pipeline_1e + +import copy # noqa: E402 + +# modify 137 epoch's RandomResizedCrop.scale to 192 +# and RA.magnitude_level * 0.7 +train_pipeline_37e = copy.deepcopy(base_train_pipeline) +train_pipeline_37e[1]['scale'] = 192 +train_pipeline_37e[3]['magnitude_level'] *= 0.7 + +# modify 112 epoch's RandomResizedCrop.scale to 224 +# and RA.magnitude_level * 1.0 +train_pipeline_112e = copy.deepcopy(base_train_pipeline) +train_pipeline_112e[1]['scale'] = 224 +train_pipeline_112e[3]['magnitude_level'] *= 1.0 + +custom_hooks = [ + dict( + type='SwitchTrainAugHook', + action_epoch=37, + pipeline=train_pipeline_37e), + dict( + type='SwitchTrainAugHook', + action_epoch=112, + pipeline=train_pipeline_112e), + dict( + type='EMAHook', + momentum=5e-4, + priority='ABOVE_NORMAL', + update_buffers=True) +] diff --git a/configs/mobileone/mobileone-s4_8xb32_in1k.py b/configs/mobileone/mobileone-s4_8xb32_in1k.py index 1984ef351de..814df72e00a 100644 --- a/configs/mobileone/mobileone-s4_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s4_8xb32_in1k.py @@ -1,15 +1,65 @@ _base_ = [ '../_base_/models/mobileone/mobileone_s4.py', - '../_base_/datasets/imagenet_bs64_pil_resize.py', - '../_base_/schedules/imagenet_bs256_coslr.py', + '../_base_/datasets/imagenet_bs32_pil_resize.py', + '../_base_/schedules/imagenet_bs256_coslr_coswd_300e.py', '../_base_/default_runtime.py' ] -# dataset settings -train_dataloader = dict(batch_size=128) -val_dataloader = dict(batch_size=128) -test_dataloader = dict(batch_size=128) +# schedule settings +optim_wrapper = dict(paramwise_cfg=dict(norm_decay_mult=0.)) -# NOTE: `auto_scale_lr` is for automatically scaling LR, -# based on the actual training batch size. -auto_scale_lr = dict(base_batch_size=1024) +val_dataloader = dict(batch_size=256) +test_dataloader = dict(batch_size=256) + +bgr_mean = _base_.data_preprocessor['mean'][::-1] +base_train_pipeline = [ + dict(type='LoadImageFromFile'), + dict(type='RandomResizedCrop', scale=224, backend='pillow'), + dict(type='RandomFlip', prob=0.5, direction='horizontal'), + dict( + type='RandAugment', + policies='timm_increasing', + num_policies=2, + total_level=10, + magnitude_level=7, + magnitude_std=0.5, + hparams=dict(pad_val=[round(x) for x in bgr_mean])), + dict(type='PackClsInputs') +] + +import copy # noqa: E402 + +# modify start epoch RandomResizedCrop.scale to 160 +# and RA.magnitude_level * 0.3 +train_pipeline_1e = copy.deepcopy(base_train_pipeline) +train_pipeline_1e[1]['scale'] = 160 +train_pipeline_1e[3]['magnitude_level'] *= 0.3 +_base_.train_dataloader.dataset.pipeline = train_pipeline_1e + +# modify 137 epoch's RandomResizedCrop.scale to 192 +# and RA.magnitude_level * 0.7 +train_pipeline_37e = copy.deepcopy(base_train_pipeline) +train_pipeline_37e[1]['scale'] = 192 +train_pipeline_37e[3]['magnitude_level'] *= 0.7 + +# modify 112 epoch's RandomResizedCrop.scale to 224 +# and RA.magnitude_level * 1.0 +train_pipeline_112e = copy.deepcopy(base_train_pipeline) +train_pipeline_112e[1]['scale'] = 224 +train_pipeline_112e[3]['magnitude_level'] *= 1.0 + +custom_hooks = [ + dict( + type='SwitchTrainAugHook', + action_epoch=37, + pipeline=train_pipeline_37e), + dict( + type='SwitchTrainAugHook', + action_epoch=112, + pipeline=train_pipeline_112e), + dict( + type='EMAHook', + momentum=5e-4, + priority='ABOVE_NORMAL', + update_buffers=True) +] From 89094502a057eb2e7144ad7fc2b84e6a39a05158 Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Fri, 21 Oct 2022 18:24:19 +0800 Subject: [PATCH 11/19] update configs --- configs/mobileone/README.md | 14 ++++----- configs/mobileone/mobileone-s0_8xb32_in1k.py | 30 -------------------- configs/mobileone/mobileone-s1_8xb32_in1k.py | 12 ++++++++ 3 files changed, 17 insertions(+), 39 deletions(-) diff --git a/configs/mobileone/README.md b/configs/mobileone/README.md index 1bb31a1d5b6..0b6d1361feb 100644 --- a/configs/mobileone/README.md +++ b/configs/mobileone/README.md @@ -18,15 +18,11 @@ Efficient neural network backbones for mobile devices are often optimized for me | Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download | | :------------: | :-----------------------------: | :----------------------------: | :-------: | :-------: | :--------------------------------------------------: | :-----------------------------------------------------: | -| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.36 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220915-007ae971.pth) | -| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.76 | 92.77 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_3rdparty_in1k_20220915-473c8469.pth) | -| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.39 | 93.63 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_3rdparty_in1k_20220915-ed2e4c30.pth) | -| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 77.93 | 93.89 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_3rdparty_in1k_20220915-84d6a02c.pth) | -| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.30 | 94.37 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_3rdparty_in1k_20220915-ce9509ee.pth) | - -*Models with * are converted from the [official repo](https://github.com/apple/ml-mobileone). The config files of these models are only for validation. We don't ensure these config files' training accuracy and welcome you to contribute your reproduction results.* - -*Because the [official repo.](https://github.com/apple/ml-mobileone) does not give a strategy for training and testing, the test data pipline of [RepVGG](https://github.com/open-mmlab/mmclassification/tree/master/configs/repvgg) is used here, and the result is about 0.1 lower than that in the paper. Refer to [this issue](https://github.com/apple/ml-mobileone/issues/2).* +| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.56 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220915-007ae971.pth) | +| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.72 | 92.77 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_3rdparty_in1k_20220915-473c8469.pth) | +| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.35 | 93.63 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_3rdparty_in1k_20220915-ed2e4c30.pth) | +| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 78.05 | 93.89 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_3rdparty_in1k_20220915-84d6a02c.pth) | +| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.71 | 94.37 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_3rdparty_in1k_20220915-ce9509ee.pth) | ## How to use diff --git a/configs/mobileone/mobileone-s0_8xb32_in1k.py b/configs/mobileone/mobileone-s0_8xb32_in1k.py index 413fca3d295..be56b86c3ce 100644 --- a/configs/mobileone/mobileone-s0_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s0_8xb32_in1k.py @@ -11,37 +11,7 @@ val_dataloader = dict(batch_size=256) test_dataloader = dict(batch_size=256) -base_train_pipeline = [ - dict(type='LoadImageFromFile'), - dict(type='RandomResizedCrop', scale=224, backend='pillow'), - dict(type='RandomFlip', prob=0.5, direction='horizontal'), - dict(type='PackClsInputs') -] - -import copy # noqa: E402 - -# modify start epoch's RandomResizedCrop.scale to 160 -train_pipeline_1e = copy.deepcopy(base_train_pipeline) -train_pipeline_1e[1]['scale'] = 160 -_base_.train_dataloader.dataset.pipeline = train_pipeline_1e - -# modify 37 epoch's RandomResizedCrop.scale to 192 -train_pipeline_37e = copy.deepcopy(base_train_pipeline) -train_pipeline_37e[1]['scale'] = 192 - -# modify 112 epoch's RandomResizedCrop.scale to 224 -train_pipeline_112e = copy.deepcopy(base_train_pipeline) -train_pipeline_112e[1]['scale'] = 224 - custom_hooks = [ - dict( - type='SwitchTrainAugHook', - action_epoch=37, - pipeline=train_pipeline_37e), - dict( - type='SwitchTrainAugHook', - action_epoch=112, - pipeline=train_pipeline_112e), dict( type='EMAHook', momentum=5e-4, diff --git a/configs/mobileone/mobileone-s1_8xb32_in1k.py b/configs/mobileone/mobileone-s1_8xb32_in1k.py index bf575a456ec..2cf9c08fa0e 100644 --- a/configs/mobileone/mobileone-s1_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s1_8xb32_in1k.py @@ -11,10 +11,19 @@ val_dataloader = dict(batch_size=256) test_dataloader = dict(batch_size=256) +bgr_mean = _base_.data_preprocessor['mean'][::-1] base_train_pipeline = [ dict(type='LoadImageFromFile'), dict(type='RandomResizedCrop', scale=224, backend='pillow'), dict(type='RandomFlip', prob=0.5, direction='horizontal'), + dict( + type='RandAugment', + policies='timm_increasing', + num_policies=2, + total_level=10, + magnitude_level=7, + magnitude_std=0.5, + hparams=dict(pad_val=[round(x) for x in bgr_mean])), dict(type='PackClsInputs') ] @@ -23,15 +32,18 @@ # modify start epoch's RandomResizedCrop.scale to 160 train_pipeline_1e = copy.deepcopy(base_train_pipeline) train_pipeline_1e[1]['scale'] = 160 +train_pipeline_1e[3]['magnitude_level'] *= 0.1 _base_.train_dataloader.dataset.pipeline = train_pipeline_1e # modify 37 epoch's RandomResizedCrop.scale to 192 train_pipeline_37e = copy.deepcopy(base_train_pipeline) train_pipeline_37e[1]['scale'] = 192 +train_pipeline_1e[3]['magnitude_level'] *= 0.2 # modify 112 epoch's RandomResizedCrop.scale to 224 train_pipeline_112e = copy.deepcopy(base_train_pipeline) train_pipeline_112e[1]['scale'] = 224 +train_pipeline_1e[3]['magnitude_level'] *= 0.3 custom_hooks = [ dict( From d0b2c9d3fe14187580968c32e3aed25e75f0db73 Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Thu, 10 Nov 2022 22:12:20 +0800 Subject: [PATCH 12/19] update links --- configs/mobileone/README.md | 10 ++-- configs/mobileone/metafile.yml | 85 ++++++++++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 15 deletions(-) diff --git a/configs/mobileone/README.md b/configs/mobileone/README.md index 0b6d1361feb..19b531a3e64 100644 --- a/configs/mobileone/README.md +++ b/configs/mobileone/README.md @@ -18,11 +18,11 @@ Efficient neural network backbones for mobile devices are often optimized for me | Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download | | :------------: | :-----------------------------: | :----------------------------: | :-------: | :-------: | :--------------------------------------------------: | :-----------------------------------------------------: | -| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.56 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220915-007ae971.pth) | -| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.72 | 92.77 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_3rdparty_in1k_20220915-473c8469.pth) | -| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.35 | 93.63 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_3rdparty_in1k_20220915-ed2e4c30.pth) | -| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 78.05 | 93.89 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_3rdparty_in1k_20220915-84d6a02c.pth) | -| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.71 | 94.37 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_3rdparty_in1k_20220915-ce9509ee.pth) | +| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.56 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.json) | +| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.72 | 92.77 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.json) | +| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.35 | 93.63 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.json) | +| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 78.05 | 93.89 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) | +| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.71 | 94.37 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) | ## How to use diff --git a/configs/mobileone/metafile.yml b/configs/mobileone/metafile.yml index 676c30b4be4..6c84a182f1f 100644 --- a/configs/mobileone/metafile.yml +++ b/configs/mobileone/metafile.yml @@ -20,8 +20,8 @@ Models: In Collection: MobileOne Config: configs/mobileone/mobileone-s0_8xb32_in1k.py Metadata: - FLOPs: 1091227648 # 1.09G - Parameters: 5293272 # 5.29M + FLOPs: 274136576 # 0.27G + Parameters: 2078504 # 2.08M Results: - Dataset: ImageNet-1k Task: Image Classification @@ -36,8 +36,8 @@ Models: In Collection: MobileOne Config: configs/mobileone/mobileone-s1_8xb32_in1k.py Metadata: - FLOPs: 863491328 # 8.6G - Parameters: 4825192 # 4.82M + FLOPs: 823839744 # 8.23G + Parameters: 4764840 # 4.76M Results: - Dataset: ImageNet-1k Task: Image Classification @@ -52,8 +52,8 @@ Models: In Collection: MobileOne Config: configs/mobileone/mobileone-s2_8xb32_in1k.py Metadata: - FLOPs: 1344083328 - Parameters: 7884648 + FLOPs: 1296478848 + Parameters: 7808168 Results: - Dataset: ImageNet-1k Task: Image Classification @@ -68,8 +68,8 @@ Models: In Collection: MobileOne Config: configs/mobileone/mobileone-s3_8xb32_in1k.py Metadata: - FLOPs: 1951043584 - Parameters: 10170600 + FLOPs: 1893842944 + Parameters: 10078312 Results: - Dataset: ImageNet-1k Task: Image Classification @@ -84,8 +84,8 @@ Models: In Collection: MobileOne Config: configs/mobileone/mobileone-s4_8xb32_in1k.py Metadata: - FLOPs: 3052580688 - Parameters: 14951248 + FLOPs: 2979222528 + Parameters: 14838352 Results: - Dataset: ImageNet-1k Task: Image Classification @@ -96,3 +96,68 @@ Models: Converted From: Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s4_unfused.pth.tar Code: https://github.com/apple/ml-mobileone + - Name: mobileone-s0_8xb32_in1k + In Collection: MobileOne + Config: configs/mobileone/mobileone-s0_8xb32_in1k.py + Metadata: + FLOPs: 274136576 # 0.27G + Parameters: 2078504 # 2.08M + Results: + - Dataset: ImageNet-1k + Task: Image Classification + Metrics: + Top 1 Accuracy: 71.36 + Top 5 Accuracy: 89.87 + Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth + - Name: mobileone-s1_8xb32_in1k + In Collection: MobileOne + Config: configs/mobileone/mobileone-s1_8xb32_in1k.py + Metadata: + FLOPs: 823839744 # 8.6G + Parameters: 4764840 # 4.82M + Results: + - Dataset: ImageNet-1k + Task: Image Classification + Metrics: + Top 1 Accuracy: 75.76 + Top 5 Accuracy: 92.77 + Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.pth + - Name: mobileone-s2_8xb32_in1k + In Collection: MobileOne + Config: configs/mobileone/mobileone-s2_8xb32_in1k.py + Metadata: + FLOPs: 1296478848 + Parameters: 7808168 + Results: + - Dataset: ImageNet-1k + Task: Image Classification + Metrics: + Top 1 Accuracy: 77.39 + Top 5 Accuracy: 93.63 + Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.pth + - Name: mobileone-s3_8xb32_in1k + In Collection: MobileOne + Config: configs/mobileone/mobileone-s3_8xb32_in1k.py + Metadata: + FLOPs: 1893842944 + Parameters: 10078312 + Results: + - Dataset: ImageNet-1k + Task: Image Classification + Metrics: + Top 1 Accuracy: 77.93 + Top 5 Accuracy: 93.89 + Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth + - Name: mobileone-s4_8xb32_in1k + In Collection: MobileOne + Config: configs/mobileone/mobileone-s4_8xb32_in1k.py + Metadata: + FLOPs: 2979222528 + Parameters: 14838352 + Results: + - Dataset: ImageNet-1k + Task: Image Classification + Metrics: + Top 1 Accuracy: 79.30 + Top 5 Accuracy: 94.37 + Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth From e225e7a83037878b97495b62b95a75c16dea0a5b Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Fri, 11 Nov 2022 11:54:47 +0800 Subject: [PATCH 13/19] update readme --- configs/mobileone/README.md | 191 ++++++++++++++++++++------------- configs/mobileone/metafile.yml | 18 ++-- 2 files changed, 127 insertions(+), 82 deletions(-) diff --git a/configs/mobileone/README.md b/configs/mobileone/README.md index 19b531a3e64..79b18b99574 100644 --- a/configs/mobileone/README.md +++ b/configs/mobileone/README.md @@ -4,118 +4,163 @@ -## Abstract +## Introduction -Efficient neural network backbones for mobile devices are often optimized for metrics such as FLOPs or parameter count. However, these metrics may not correlate well with latency of the network when deployed on a mobile device. Therefore, we perform extensive analysis of different metrics by deploying several mobile-friendly networks on a mobile device. We identify and analyze architectural and optimization bottlenecks in recent efficient neural networks and provide ways to mitigate these bottlenecks. To this end, we design an efficient backbone MobileOne, with variants achieving an inference time under 1 ms on an iPhone12 with 75.9% top-1 accuracy on ImageNet. We show that MobileOne achieves state-of-the-art performance within the efficient architectures while being many times faster on mobile. Our best model obtains similar performance on ImageNet as MobileFormer while being 38x faster. Our model obtains 2.3% better top-1 accuracy on ImageNet than EfficientNet at similar latency. Furthermore, we show that our model generalizes to multiple tasks - image classification, object detection, and semantic segmentation with significant improvements in latency and accuracy as compared to existing efficient architectures when deployed on a mobile device. +Mobileone is proposed by apple and based on reparameterization. On the apple chips, the accuracy of the model is close to 0.76 on the ImageNet dataset when the latency is less than 1ms. Its main improvements based on [RepVGG](../repvgg) are fllowing: + +- Reparameterization using Depthwise convolution and Pointwise convolution instead of normal convolution. +- Removal of the residual structure which is not friendly to access memory.
-## Results and models +## Abstract -### ImageNet-1k +
-| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download | -| :------------: | :-----------------------------: | :----------------------------: | :-------: | :-------: | :--------------------------------------------------: | :-----------------------------------------------------: | -| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.56 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.json) | -| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.72 | 92.77 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.json) | -| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.35 | 93.63 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.json) | -| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 78.05 | 93.89 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) | -| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.71 | 94.37 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) | +Show the paper's abstract + +
+Efficient neural network backbones for mobile devices are often optimized for metrics such as FLOPs or parameter count. However, these metrics may not correlate well with latency of the network when deployed on a mobile device. Therefore, we perform extensive analysis of different metrics by deploying several mobile-friendly networks on a mobile device. We identify and analyze architectural and optimization bottlenecks in recent efficient neural networks and provide ways to mitigate these bottlenecks. To this end, we design an efficient backbone MobileOne, with variants achieving an inference time under 1 ms on an iPhone12 with 75.9% top-1 accuracy on ImageNet. We show that MobileOne achieves state-of-the-art performance within the efficient architectures while being many times faster on mobile. Our best model obtains similar performance on ImageNet as MobileFormer while being 38x faster. Our model obtains 2.3% better top-1 accuracy on ImageNet than EfficientNet at similar latency. Furthermore, we show that our model generalizes to multiple tasks - image classification, object detection, and semantic segmentation with significant improvements in latency and accuracy as compared to existing efficient architectures when deployed on a mobile device. +
+ +
## How to use -The checkpoints provided are all `training-time` models. Use the reparameterize tool to switch them to more efficient `inference-time` architecture, which not only has fewer parameters but also less calculations. +The checkpoints provided are all `training-time` models. Use the reparameterize tool or `switch_to_deploy` interface to switch them to more efficient `inference-time` architecture, which not only has fewer parameters but also less calculations. -### Use tool + -Use provided tool to reparameterize the given model and save the checkpoint: +**Predict image** -```bash -python tools/convert_models/reparameterize_model.py ${CFG_PATH} ${SRC_CKPT_PATH} ${TARGET_CKPT_PATH} +Use `classifier.backbone.switch_to_deploy()` interface to switch the MobileOne to a inference mode. + +```python +>>> import torch +>>> from mmcls.apis import init_model, inference_model +>>> +>>> model = init_model('configs/mobileone/mobileone-s0_8xb32_in1k.py', 'https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth') +>>> predict = inference_model(model, 'demo/demo.JPEG') +>>> print(predict['pred_class']) +sea snake +>>> print(predict['pred_score']) +0.4539405107498169 +>>> +>>> # switch to deploy mode +>>> model.backbone.switch_to_deploy() +>>> predict_deploy = inference_model(model, 'demo/demo.JPEG') +>>> print(predict_deploy['pred_class']) +sea snake +>>> print(predict_deploy['pred_score']) +0.4539395272731781 ``` -`${CFG_PATH}` is the config file path, `${SRC_CKPT_PATH}` is the source chenpoint file path, `${TARGET_CKPT_PATH}` is the target deploy weight file path. +**Use the model** -For example: +```python +>>> import torch +>>> from mmcls.apis import init_model +>>> +>>> model = init_model('configs/mobileone/mobileone-s0_8xb32_in1k.py', 'https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth') +>>> inputs = torch.rand(1, 3, 224, 224).to(model.data_preprocessor.device) +>>> # To get classification scores. +>>> out = model(inputs) +>>> print(out.shape) +torch.Size([1, 1000]) +>>> # To extract features. +>>> outs = model.extract_feat(inputs) +>>> print(outs[0].shape) +torch.Size([1, 768]) +>>> +>>> # switch to deploy mode +>>> model.backbone.switch_to_deploy() +>>> out_deploy = model(inputs) +>>> print(out.shape) +torch.Size([1, 1000]) +>>> assert torch.allclose(out, out_deploy) # pass without error +``` + +**Train/Test Command** + +Place the ImageNet dataset to the `data/imagenet/` directory, or prepare datasets according to the [docs](https://mmclassification.readthedocs.io/en/1.x/user_guides/dataset_prepare.html#prepare-dataset). + +Train: ```shell -python ./tools/convert_models/reparameterize_model.py ./configs/mobileone/mobileone-s0_8xb32_in1k.py https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220811-db5ce29b.pth ./mobileone_s0_deploy.pth +python tools/train.py configs/mobileone/mobileone-s0_8xb32_in1k.py ``` -To use reparameterized weights, the config file must switch to **the deploy config files**. +Download Checkpoint: -```bash -python tools/test.py ${Deploy_CFG} ${Deploy_Checkpoint} --metrics accuracy +```shell +wget https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth ``` -For example of using the reparameterized weights above: +Test use unfused model: ```shell -python ./tools/test.py ./configs/mobileone/deploy/mobileone-s0_deploy_8xb32_in1k.py mobileone_s0_deploy.pth --metrics accuracy +python tools/test.py configs/mobileone/mobileone-s0_8xb32_in1k.py mobileone-s0_8xb32_in1k_20221110-0bc94952.pth ``` -### In the code +Reparameterize checkpoint: -Use the API `switch_to_deploy` of `MobileOne` backbone to to switch to the deploy mode. Usually called like `backbone.switch_to_deploy()` or `classificer.backbone.switch_to_deploy()`. +```shell +python ./tools/convert_models/reparameterize_model.py ./configs/mobileone/mobileone-s0_8xb32_in1k.py mobileone-s0_8xb32_in1k_20221110-0bc94952.pth mobileone_s0_deploy.pth +``` -For Backbones: +Test use fused model: -```python -from mmcls.models import build_backbone -import torch +```shell +python tools/test.py configs/mobileone/deploy/mobileone-s0_deploy_8xb32_in1k.py mobileone_s0_deploy.pth +``` + + + +### Reparameterize Tool + +Use provided tool to reparameterize the given model and save the checkpoint: + +```bash +python tools/convert_models/reparameterize_model.py ${CFG_PATH} ${SRC_CKPT_PATH} ${TARGET_CKPT_PATH} +``` -x = torch.randn( (1, 3, 224, 224) ) -backbone_cfg=dict(type='MobileOne', arch='s0') -backbone = build_backbone(backbone_cfg) -backbone.init_weights() -backbone.eval() -outs_ori = backbone(x) +`${CFG_PATH}` is the config file path, `${SRC_CKPT_PATH}` is the source chenpoint file path, `${TARGET_CKPT_PATH}` is the target deploy weight file path. -backbone.switch_to_deploy() -outs_dep = backbone(x) +For example: -for out1, out2 in zip(outs_ori, outs_dep): - assert torch.allclose(out1, out2) +```shell +wget https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth +python ./tools/convert_models/reparameterize_model.py ./configs/mobileone/mobileone-s0_8xb32_in1k.py mobileone-s0_8xb32_in1k_20221110-0bc94952.pth mobileone_s0_deploy.pth ``` -For ImageClassifiers: +To use reparameterized weights, the config file must switch to [**the deploy config files**](./deploy/). -```python -from mmcls.models import build_classifier -import torch -import numpy as np - -cfg = dict( - type='ImageClassifier', - backbone=dict( - type='MobileOne', - arch='s0', - out_indices=(3, ), - ), - neck=dict(type='GlobalAveragePooling'), - head=dict( - type='LinearClsHead', - num_classes=1000, - in_channels=1024, - loss=dict(type='CrossEntropyLoss', loss_weight=1.0), - topk=(1, 5), - )) - -x = torch.randn( (1, 3, 224, 224) ) -classifier = build_classifier(cfg) -classifier.init_weights() -classifier.eval() -y_ori = classifier(x, return_loss=False) - -classifier.backbone.switch_to_deploy() -y_dep = classifier(x, return_loss=False) - -for y1, y2 in zip(y_ori, y_dep): - assert np.allclose(y1, y2) +```bash +python tools/test.py ${Deploy_CFG} ${Deploy_Checkpoint} ``` +For example of using the reparameterized weights above: + +```shell +python ./tools/test.py ./configs/mobileone/deploy/mobileone-s0_deploy_8xb32_in1k.py mobileone_s0_deploy.pth +``` + +For more configurable parameters, please refer to the [API](https://mmclassification.readthedocs.io/en/1.x/api/generated/mmcls.models.backbones.MobileOne.html#mmcls.models.backbones.MobileOne). + +## Results and models + +### ImageNet-1k + +| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download | +| :------------: | :-----------------------------: | :----------------------------: | :-------: | :-------: | :--------------------------------------------------: | :-----------------------------------------------------: | +| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.34 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.json) | +| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.72 | 92.54 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.json) | +| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.37 | 93.34 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.json) | +| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 78.06 | 93.83 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) | +| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.69 | 94.46 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) | + ## Citation ```bibtex diff --git a/configs/mobileone/metafile.yml b/configs/mobileone/metafile.yml index 6c84a182f1f..48c11da751e 100644 --- a/configs/mobileone/metafile.yml +++ b/configs/mobileone/metafile.yml @@ -106,7 +106,7 @@ Models: - Dataset: ImageNet-1k Task: Image Classification Metrics: - Top 1 Accuracy: 71.36 + Top 1 Accuracy: 71.34 Top 5 Accuracy: 89.87 Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth - Name: mobileone-s1_8xb32_in1k @@ -119,8 +119,8 @@ Models: - Dataset: ImageNet-1k Task: Image Classification Metrics: - Top 1 Accuracy: 75.76 - Top 5 Accuracy: 92.77 + Top 1 Accuracy: 75.72 + Top 5 Accuracy: 92.54 Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.pth - Name: mobileone-s2_8xb32_in1k In Collection: MobileOne @@ -132,8 +132,8 @@ Models: - Dataset: ImageNet-1k Task: Image Classification Metrics: - Top 1 Accuracy: 77.39 - Top 5 Accuracy: 93.63 + Top 1 Accuracy: 77.37 + Top 5 Accuracy: 93.34 Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.pth - Name: mobileone-s3_8xb32_in1k In Collection: MobileOne @@ -145,8 +145,8 @@ Models: - Dataset: ImageNet-1k Task: Image Classification Metrics: - Top 1 Accuracy: 77.93 - Top 5 Accuracy: 93.89 + Top 1 Accuracy: 78.06 + Top 5 Accuracy: 93.83 Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth - Name: mobileone-s4_8xb32_in1k In Collection: MobileOne @@ -158,6 +158,6 @@ Models: - Dataset: ImageNet-1k Task: Image Classification Metrics: - Top 1 Accuracy: 79.30 - Top 5 Accuracy: 94.37 + Top 1 Accuracy: 79.69 + Top 5 Accuracy: 94.46 Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth From b52f7dd6ae88242527aee744dd6670714ef59a0c Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Fri, 11 Nov 2022 13:18:09 +0800 Subject: [PATCH 14/19] update vis_scheduler --- tools/visualizations/vis_scheduler.py | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/tools/visualizations/vis_scheduler.py b/tools/visualizations/vis_scheduler.py index 87d076dcba6..5e08af39bdc 100644 --- a/tools/visualizations/vis_scheduler.py +++ b/tools/visualizations/vis_scheduler.py @@ -41,6 +41,7 @@ def __init__(self, by_epoch): self.by_epoch = by_epoch self.lr_list = [] self.momentum_list = [] + self.wd_list = [] self.task_id = 0 self.progress = Progress(BarColumn(), MofNCompleteColumn(), TextColumn('{task.description}')) @@ -66,6 +67,8 @@ def after_train_iter(self, runner, batch_idx, data_batch, outputs): self.lr_list.append(runner.optim_wrapper.get_lr()['lr'][0]) self.momentum_list.append( runner.optim_wrapper.get_momentum()['momentum'][0]) + self.wd_list.append( + runner.optim_wrapper.param_groups[0]['weight_decay']) def after_train(self, runner): self.progress.stop() @@ -80,9 +83,9 @@ def parse_args(): '--parameter', type=str, default='lr', - choices=['lr', 'momentum'], + choices=['lr', 'momentum', 'wd'], help='The parameter to visualize its change curve, choose from' - '"lr" and "momentum". Defaults to "lr".') + '"lr", "wd" and "momentum". Defaults to "lr".') parser.add_argument( '-d', '--dataset-size', @@ -192,7 +195,12 @@ def simulate_train(data_loader, cfg, by_epoch): runner.train() - return param_record_hook.lr_list, param_record_hook.momentum_list + param_dict = dict( + lr=param_record_hook.lr_list, + momentum=param_record_hook.momentum_list, + wd=param_record_hook.wd_list) + + return param_dict def main(): @@ -250,13 +258,15 @@ class FakeDataloader(list): rich.print(dataset_info + '\n') # simulation training process - lr_list, momentum_list = simulate_train(data_loader, cfg, by_epoch) + param_dict = simulate_train(data_loader, cfg, by_epoch) + param_list = param_dict[args.parameter] + if args.parameter == 'lr': - param_list = lr_list + param_name = 'Learning Rate' + elif args.parameter == 'momentum': + param_name = 'Momentum' else: - param_list = momentum_list - - param_name = 'Learning Rate' if args.parameter == 'lr' else 'Momentum' + param_name = 'Weight Decay' plot_curve(param_list, args, param_name, len(data_loader), by_epoch) if args.save_path: From 45fb57c566b0f5ebe7e4206a2c32152efe286641 Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Mon, 14 Nov 2022 11:01:09 +0800 Subject: [PATCH 15/19] update metafile --- configs/mobileone/README.md | 14 +++--- configs/mobileone/metafile.yml | 80 ---------------------------------- 2 files changed, 7 insertions(+), 87 deletions(-) diff --git a/configs/mobileone/README.md b/configs/mobileone/README.md index 79b18b99574..ce187e262d5 100644 --- a/configs/mobileone/README.md +++ b/configs/mobileone/README.md @@ -153,13 +153,13 @@ For more configurable parameters, please refer to the [API](https://mmclassifica ### ImageNet-1k -| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download | -| :------------: | :-----------------------------: | :----------------------------: | :-------: | :-------: | :--------------------------------------------------: | :-----------------------------------------------------: | -| MobileOne-s0\* | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.34 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.json) | -| MobileOne-s1\* | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.72 | 92.54 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.json) | -| MobileOne-s2\* | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.37 | 93.34 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.json) | -| MobileOne-s3\* | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 78.06 | 93.83 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) | -| MobileOne-s4\* | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.69 | 94.46 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) | +| Model | Params(M) | Flops(G) | Top-1 (%) | Top-5 (%) | Config | Download | +| :----------: | :-----------------------------: | :----------------------------: | :-------: | :-------: | :---------------------------------------------------: | :------------------------------------------------------: | +| MobileOne-s0 | 5.29(train) \| 2.08 (deploy) | 1.09 (train) \| 0.28 (deploy) | 71.34 | 89.87 | [config (train)](./mobileone-s0_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s0_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_8xb32_in1k_20221110-0bc94952.json) | +| MobileOne-s1 | 4.83 (train) \| 4.76 (deploy) | 0.86 (train) \| 0.84 (deploy) | 75.72 | 92.54 | [config (train)](./mobileone-s1_8xb32_in1k.py) \| [config (deploy)](./deploy/mobileone-s1_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_8xb32_in1k_20221110-ceeef467.json) | +| MobileOne-s2 | 7.88 (train) \| 7.88 (deploy) | 1.34 (train) \| 1.31 (deploy) | 77.37 | 93.34 | [config (train)](./mobileone-s2_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s2_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_8xb32_in1k_20221110-9c7ecb97.json) | +| MobileOne-s3 | 10.17 (train) \| 10.08 (deploy) | 1.95 (train) \| 1.91 (deploy) | 78.06 | 93.83 | [config (train)](./mobileone-s3_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s3_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_8xb32_in1k_20221110-c95eb3bf.pth) | +| MobileOne-s4 | 14.95 (train) \| 14.84 (deploy) | 3.05 (train) \| 3.00 (deploy) | 79.69 | 94.46 | [config (train)](./mobileone-s4_8xb32_in1k.py) \|[config (deploy)](./deploy/mobileone-s4_deploy_8xb32_in1k.py) | [model](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) \| [log](https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_8xb32_in1k_20221110-28d888cb.pth) | ## Citation diff --git a/configs/mobileone/metafile.yml b/configs/mobileone/metafile.yml index 48c11da751e..2a480dcdcda 100644 --- a/configs/mobileone/metafile.yml +++ b/configs/mobileone/metafile.yml @@ -16,86 +16,6 @@ Collections: Version: v1.0.0rc1 Models: - - Name: mobileone-s0_3rdparty_8xb32_in1k - In Collection: MobileOne - Config: configs/mobileone/mobileone-s0_8xb32_in1k.py - Metadata: - FLOPs: 274136576 # 0.27G - Parameters: 2078504 # 2.08M - Results: - - Dataset: ImageNet-1k - Task: Image Classification - Metrics: - Top 1 Accuracy: 71.36 - Top 5 Accuracy: 89.87 - Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s0_3rdparty_in1k_20220915-007ae971.pth - Converted From: - Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s0_unfused.pth.tar - Code: https://github.com/apple/ml-mobileone - - Name: mobileone-s1_3rdparty_8xb32_in1k - In Collection: MobileOne - Config: configs/mobileone/mobileone-s1_8xb32_in1k.py - Metadata: - FLOPs: 823839744 # 8.23G - Parameters: 4764840 # 4.76M - Results: - - Dataset: ImageNet-1k - Task: Image Classification - Metrics: - Top 1 Accuracy: 75.76 - Top 5 Accuracy: 92.77 - Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s1_3rdparty_in1k_20220915-473c8469.pth - Converted From: - Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s1_unfused.pth.tar - Code: https://github.com/apple/ml-mobileone - - Name: mobileone-s2_3rdparty_8xb32_in1k - In Collection: MobileOne - Config: configs/mobileone/mobileone-s2_8xb32_in1k.py - Metadata: - FLOPs: 1296478848 - Parameters: 7808168 - Results: - - Dataset: ImageNet-1k - Task: Image Classification - Metrics: - Top 1 Accuracy: 77.39 - Top 5 Accuracy: 93.63 - Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s2_3rdparty_in1k_20220915-ed2e4c30.pth - Converted From: - Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s2_unfused.pth.tar - Code: https://github.com/apple/ml-mobileone - - Name: mobileone-s3_3rdparty_8xb32_in1k - In Collection: MobileOne - Config: configs/mobileone/mobileone-s3_8xb32_in1k.py - Metadata: - FLOPs: 1893842944 - Parameters: 10078312 - Results: - - Dataset: ImageNet-1k - Task: Image Classification - Metrics: - Top 1 Accuracy: 77.93 - Top 5 Accuracy: 93.89 - Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s3_3rdparty_in1k_20220915-84d6a02c.pth - Converted From: - Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s3_unfused.pth.tar - Code: https://github.com/apple/ml-mobileone - - Name: mobileone-s4_3rdparty_8xb32_in1k - In Collection: MobileOne - Config: configs/mobileone/mobileone-s4_8xb32_in1k.py - Metadata: - FLOPs: 2979222528 - Parameters: 14838352 - Results: - - Dataset: ImageNet-1k - Task: Image Classification - Metrics: - Top 1 Accuracy: 79.30 - Top 5 Accuracy: 94.37 - Weights: https://download.openmmlab.com/mmclassification/v0/mobileone/mobileone-s4_3rdparty_in1k_20220915-ce9509ee.pth - Converted From: - Weights: https://docs-assets.developer.apple.com/ml-research/datasets/mobileone/mobileone_s4_unfused.pth.tar - Code: https://github.com/apple/ml-mobileone - Name: mobileone-s0_8xb32_in1k In Collection: MobileOne Config: configs/mobileone/mobileone-s0_8xb32_in1k.py From 87ae5260d6a57e2603b43ccdc8b2e0437a420afb Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Thu, 17 Nov 2022 17:12:56 +0800 Subject: [PATCH 16/19] update configs --- configs/mobileone/mobileone-s1_8xb32_in1k.py | 12 +++++------- configs/mobileone/mobileone-s2_8xb32_in1k.py | 12 +++++------- configs/mobileone/mobileone-s3_8xb32_in1k.py | 12 +++++------- configs/mobileone/mobileone-s4_8xb32_in1k.py | 12 +++++------- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/configs/mobileone/mobileone-s1_8xb32_in1k.py b/configs/mobileone/mobileone-s1_8xb32_in1k.py index 2cf9c08fa0e..52c8442e82d 100644 --- a/configs/mobileone/mobileone-s1_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s1_8xb32_in1k.py @@ -47,13 +47,11 @@ custom_hooks = [ dict( - type='SwitchTrainAugHook', - action_epoch=37, - pipeline=train_pipeline_37e), - dict( - type='SwitchTrainAugHook', - action_epoch=112, - pipeline=train_pipeline_112e), + type='SwitchRecipeHook', + schedule=[ + dict(action_epoch=37, pipeline=train_pipeline_37e), + dict(action_epoch=112, pipeline=train_pipeline_112e), + ]), dict( type='EMAHook', momentum=5e-4, diff --git a/configs/mobileone/mobileone-s2_8xb32_in1k.py b/configs/mobileone/mobileone-s2_8xb32_in1k.py index ce3bcae4e47..547ae9952d1 100644 --- a/configs/mobileone/mobileone-s2_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s2_8xb32_in1k.py @@ -52,13 +52,11 @@ custom_hooks = [ dict( - type='SwitchTrainAugHook', - action_epoch=37, - pipeline=train_pipeline_37e), - dict( - type='SwitchTrainAugHook', - action_epoch=112, - pipeline=train_pipeline_112e), + type='SwitchRecipeHook', + schedule=[ + dict(action_epoch=37, pipeline=train_pipeline_37e), + dict(action_epoch=112, pipeline=train_pipeline_112e), + ]), dict( type='EMAHook', momentum=5e-4, diff --git a/configs/mobileone/mobileone-s3_8xb32_in1k.py b/configs/mobileone/mobileone-s3_8xb32_in1k.py index 7e12da3d176..b0ef416469f 100644 --- a/configs/mobileone/mobileone-s3_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s3_8xb32_in1k.py @@ -52,13 +52,11 @@ custom_hooks = [ dict( - type='SwitchTrainAugHook', - action_epoch=37, - pipeline=train_pipeline_37e), - dict( - type='SwitchTrainAugHook', - action_epoch=112, - pipeline=train_pipeline_112e), + type='SwitchRecipeHook', + schedule=[ + dict(action_epoch=37, pipeline=train_pipeline_37e), + dict(action_epoch=112, pipeline=train_pipeline_112e), + ]), dict( type='EMAHook', momentum=5e-4, diff --git a/configs/mobileone/mobileone-s4_8xb32_in1k.py b/configs/mobileone/mobileone-s4_8xb32_in1k.py index 814df72e00a..8c31f2400a8 100644 --- a/configs/mobileone/mobileone-s4_8xb32_in1k.py +++ b/configs/mobileone/mobileone-s4_8xb32_in1k.py @@ -50,13 +50,11 @@ custom_hooks = [ dict( - type='SwitchTrainAugHook', - action_epoch=37, - pipeline=train_pipeline_37e), - dict( - type='SwitchTrainAugHook', - action_epoch=112, - pipeline=train_pipeline_112e), + type='SwitchRecipeHook', + schedule=[ + dict(action_epoch=37, pipeline=train_pipeline_37e), + dict(action_epoch=112, pipeline=train_pipeline_112e), + ]), dict( type='EMAHook', momentum=5e-4, From e6874bb9a29e98959409064c0c89a801ad8728ac Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Fri, 18 Nov 2022 18:35:46 +0800 Subject: [PATCH 17/19] rebase --- mmcls/engine/hooks/switch_aug_hook.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mmcls/engine/hooks/switch_aug_hook.py b/mmcls/engine/hooks/switch_aug_hook.py index f2415bf52f1..224699c50b5 100644 --- a/mmcls/engine/hooks/switch_aug_hook.py +++ b/mmcls/engine/hooks/switch_aug_hook.py @@ -143,7 +143,6 @@ def _is_lastest_switch_hook(self, runner): # get the latest swict hook based on the current iter. dataset_length = len(runner.train_loop.dataloader) cur_iter = runner.train_loop.iter - min_gap, min_gap_idx = float('inf'), -1 for i, switch_hook_obj in enumerate(switch_hook_objs): # use iter to calculate @@ -173,7 +172,6 @@ def _do_switch(self, runner, action_milestone_str=''): if self.pipeline != 'unchange': self._switch_train_loader_pipeline(runner) runner.logger.info(f'Switch train pipeline{action_milestone_str}.') - if self.loss != 'unchange': self._switch_train_loss(runner) runner.logger.info(f'Switch train loss{action_milestone_str}.') From 1d7ab3c89f6a6b3b8c2c55c75b66861f0f6a2b9f Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Tue, 18 Oct 2022 22:46:45 +0800 Subject: [PATCH 18/19] fix ci From 220816eb26fa0f53b5cf85c1399d60180071a831 Mon Sep 17 00:00:00 2001 From: Ezra-Yu <18586273+Ezra-Yu@users.noreply.github.com> Date: Fri, 18 Nov 2022 18:38:41 +0800 Subject: [PATCH 19/19] rebase --- mmcls/engine/hooks/switch_aug_hook.py | 227 --------- .../test_hooks/test_switch_aug_hook.py | 429 ------------------ 2 files changed, 656 deletions(-) delete mode 100644 mmcls/engine/hooks/switch_aug_hook.py delete mode 100644 tests/test_engine/test_hooks/test_switch_aug_hook.py diff --git a/mmcls/engine/hooks/switch_aug_hook.py b/mmcls/engine/hooks/switch_aug_hook.py deleted file mode 100644 index 224699c50b5..00000000000 --- a/mmcls/engine/hooks/switch_aug_hook.py +++ /dev/null @@ -1,227 +0,0 @@ -# Copyright (c) OpenMMLab. All rights reserved. -from typing import Optional, Sequence - -from mmcv.transforms import Compose -from mmengine.hooks import Hook -from mmengine.model import is_model_wrapper - -from mmcls.models.utils import RandomBatchAugment -from mmcls.registry import HOOKS, MODELS - -DATA_BATCH = Optional[Sequence[dict]] - - -@HOOKS.register_module() -class SwitchTrainAugHook(Hook): - """switch configuration during the training, including data pipeline, batch - augments and loss. - - Args: - action_epoch (int): switch train augments at the epoch of action_epoch. - Defaults to None. - action_iter (int): switch train augments at the iter of action_iter. - Defaults to None. - pipeline (dict, str, optional): the new train pipeline. - Defaults to 'unchange', means not changing the train pipeline. - train_augments (dict, str, optional): the new train augments. - Defaults to 'unchange', means not changing the train augments. - loss (dict, str, optional): the new train loss. - Defaults to 'unchange', means not changing the train loss. - - Example: - >>> # in config - >>> # deinfe new_train_pipeline, new_train_augments or new_loss - >>> custom_hooks = [ - >>> dict( - >>> type='SwitchTrainAugHook', - >>> action_epoch=37, - >>> pipeline=new_train_pipeline, - >>> train_augments=new_train_augments, - >>> loss=new_loss),] - >>> - >>> # switch data augments by epoch - >>> switch_hook = dict( - >>> type='SwitchTrainAugHook', - >>> pipeline=new_pipeline, - >>> action_epoch=5) - >>> runner.register_custom_hooks([switch_hook]) - >>> - >>> # switch train augments and loss by iter - >>> switch_hook = dict( - >>> type='SwitchTrainAugHook', - >>> train_augments=new_train_augments, - >>> loss=new_loss, - >>> action_iter=5) - >>> runner.register_custom_hooks([switch_hook]) - - - Note: - This hook would modify the ``model.data_preprocessor.batch_augments`` - , ``runner.train_loop.dataloader.dataset.pipeline`` and - ``runner.model.head.loss`` fields. - """ - priority = 'NORMAL' - - def __init__(self, - action_epoch=None, - action_iter=None, - pipeline='unchange', - train_augments='unchange', - loss='unchange'): - - if action_iter is None and action_epoch is None: - raise ValueError('one of `action_iter` and `action_epoch` ' - 'must be set in `SwitchTrainAugHook`.') - if action_iter is not None and action_epoch is not None: - raise ValueError('`action_iter` and `action_epoch` should ' - 'not be both set in `SwitchTrainAugHook`.') - - if action_iter is not None: - assert isinstance(action_iter, int) and action_iter >= 0, ( - '`action_iter` must be a number larger than 0 in ' - f'`SwitchTrainAugHook`, but got action_iter: {action_iter}') - self.by_epoch = False - if action_epoch is not None: - assert isinstance(action_epoch, int) and action_epoch >= 0, ( - '`action_epoch` must be a number larger than 0 in ' - f'`SwitchTrainAugHook`, but got action_epoch: {action_epoch}') - self.by_epoch = True - - self.action_epoch = action_epoch - self.action_iter = action_iter - - self.pipeline = pipeline - if pipeline != 'unchange': - self.pipeline = Compose(pipeline) - self._restart_dataloader = False - - self.train_augments = train_augments - if train_augments is not None and train_augments != 'unchange': - self.train_augments = RandomBatchAugment(**train_augments) - - self.loss = MODELS.build(loss) if loss != 'unchange' else loss - - def before_train(self, runner) -> None: - """before run setting. If resume form a checkpoint, check whether is - the latest processed hook, if True, do the switch process. - - Args: - runner (Runner): The runner of the training, validation or testing - process. - """ - # if this hook is the latest switch hook obj in the previously - # unfinished tasks, then do the switch process before train - if runner._resume and self._is_lastest_switch_hook(runner): - action_milestone_str = ' after resume' - self._do_switch(runner, action_milestone_str) - - def before_train_epoch(self, runner): - """do before train epoch.""" - if self.by_epoch and runner.epoch + 1 == self.action_epoch: - action_milestone_str = f' at Epoch {runner.epoch + 1}' - self._do_switch(runner, action_milestone_str) - - def after_train_iter(self, - runner, - batch_idx: int, - data_batch: DATA_BATCH = None, - outputs: Optional[dict] = None) -> None: - """do before train iter.""" - if not self.by_epoch and runner.iter + 1 == self.action_iter: - action_milestone_str = f' at Iter {runner.iter + 1}' - self._do_switch(runner, action_milestone_str) - - def _is_lastest_switch_hook(self, runner): - """a helper function to judge if this hook is the latest processed - switch hooks with the same class name in a runner.""" - # collect all the switch_hook with the same class name in a list. - switch_hook_objs = [ - hook_obj for hook_obj in runner._hooks - if isinstance(hook_obj, SwitchTrainAugHook) - ] - - # get the latest swict hook based on the current iter. - dataset_length = len(runner.train_loop.dataloader) - cur_iter = runner.train_loop.iter - min_gap, min_gap_idx = float('inf'), -1 - for i, switch_hook_obj in enumerate(switch_hook_objs): - # use iter to calculate - if switch_hook_obj.by_epoch: - exe_iter = switch_hook_obj.action_epoch * dataset_length - else: - exe_iter = switch_hook_obj.action_iter - - gap = cur_iter - exe_iter - if gap < 0: - # this hook have not beend executed - continue - elif gap > 0 and min_gap > gap: - # this hook have been executed and is closer to cur iter - min_gap = gap - min_gap_idx = i - - # return if self is the latest executed switch hook - return min_gap_idx != -1 and self is switch_hook_objs[min_gap_idx] - - def _do_switch(self, runner, action_milestone_str=''): - """do the switch aug process.""" - if self.train_augments != 'unchange': - self._switch_batch_augments(runner) - runner.logger.info(f'Switch train aug{action_milestone_str}.') - - if self.pipeline != 'unchange': - self._switch_train_loader_pipeline(runner) - runner.logger.info(f'Switch train pipeline{action_milestone_str}.') - if self.loss != 'unchange': - self._switch_train_loss(runner) - runner.logger.info(f'Switch train loss{action_milestone_str}.') - - def _switch_batch_augments(self, runner): - """switch the train augments.""" - model = runner.model - if is_model_wrapper(model): - model = model.module - - model.data_preprocessor.batch_augments = self.train_augments - - def _switch_train_loader_pipeline(self, runner): - """switch the train loader dataset pipeline.""" - train_loader = runner.train_loop.dataloader - if hasattr(train_loader.dataset, 'pipeline'): - # for dataset - if self.pipeline is not None: - train_loader.dataset.pipeline = self.pipeline - elif hasattr(train_loader.dataset, 'datasets'): - # for concat dataset wrappers - for ds in train_loader.dataset.datasets: - ds.pipeline = self.pipeline - elif hasattr(train_loader.dataset.dataset, 'pipeline'): - # for other dataset wrappers - train_loader.dataset.dataset.pipeline = self.pipeline - else: - raise ValueError( - 'train_loader.dataset or train_loader.dataset.dataset' - ' must have pipeline') - - # The dataset pipeline cannot be updated when persistent_workers - # is True, so we need to force the dataloader's multi-process - # restart. This is a very hacky approach. - if hasattr(train_loader, 'persistent_workers' - ) and train_loader.persistent_workers is True: - train_loader._DataLoader__initialized = False - train_loader._iterator = None - self._restart_dataloader = True - else: - # Once the restart is complete, we need to restore - # the initialization flag. - if self._restart_dataloader: - train_loader._DataLoader__initialized = True - - def _switch_train_loss(self, runner): - """switch the train loss.""" - model = runner.model - if is_model_wrapper(model): - model = model.module - - assert hasattr(model.head, 'loss') - model.head.loss = self.loss diff --git a/tests/test_engine/test_hooks/test_switch_aug_hook.py b/tests/test_engine/test_hooks/test_switch_aug_hook.py deleted file mode 100644 index cb9acb79089..00000000000 --- a/tests/test_engine/test_hooks/test_switch_aug_hook.py +++ /dev/null @@ -1,429 +0,0 @@ -# Copyright (c) OpenMMLab. All rights reserved. -import copy -import logging -import random -import tempfile -from typing import List -from unittest import TestCase -from unittest.mock import MagicMock, patch - -import torch -import torch.nn as nn -from mmcv.transforms import BaseTransform -from mmengine.dataset import BaseDataset, ConcatDataset, RepeatDataset -from mmengine.logging import MMLogger -from mmengine.model import BaseDataPreprocessor, BaseModel -from mmengine.runner import Runner -from mmengine.utils import digit_version -from torch.utils.data import DataLoader - -from mmcls.engine import SwitchTrainAugHook -from mmcls.models.losses import LabelSmoothLoss -from mmcls.models.utils.batch_augments import CutMix, Mixup, RandomBatchAugment -from mmcls.structures import ClsDataSample -from mmcls.utils import register_all_modules - -register_all_modules() - - -class MockDataPreprocessor(BaseDataPreprocessor): - """mock preprocessor that do nothing.""" - - def forward(self, data, training): - - return data['imgs'], ClsDataSample() - - -class ExampleModel(BaseModel): - - def __init__(self): - super(ExampleModel, self).__init__() - self.data_preprocessor = MockDataPreprocessor() - self.conv = nn.Linear(1, 1) - self.bn = nn.BatchNorm1d(1) - self.test_cfg = None - - def forward(self, batch_inputs, data_samples, mode='tensor'): - batch_inputs = batch_inputs.to(next(self.parameters()).device) - return self.bn(self.conv(batch_inputs)) - - def train_step(self, data, optim_wrapper): - outputs = {'loss': 0.5, 'num_samples': 1} - return outputs - - -class ExampleDataset(BaseDataset): - - def load_data_list(self) -> List[dict]: - return [i for i in range(10)] - - def __getitem__(self, idx): - results = dict(imgs=torch.tensor([1.0], dtype=torch.float32)) - return results - - def __len__(self): - return 10 - - -class TestSwitchTrainAugHook(TestCase): - DEFAULT_CFG = dict(action_epoch=1, action_iter=None) - - def setUp(self): - # optimizer - self.optim_wrapper = dict(optimizer=dict(type='SGD', lr=0.1)) - # learning policy - self.epochscheduler = dict( - type='MultiStepLR', by_epoch=True, milestones=[1]) - self.iterscheduler = dict( - type='MultiStepLR', by_epoch=False, milestones=[1]) - - self.tmpdir = tempfile.TemporaryDirectory() - self.loader = DataLoader(ExampleDataset(), batch_size=2) - - def test_init(self): - # check action_epoch and action_iter both set - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['action_iter'] = 3 - with self.assertRaises(ValueError): - SwitchTrainAugHook(**cfg) - - # check action_epoch and action_iter both None - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['action_epoch'] = None - with self.assertRaises(ValueError): - SwitchTrainAugHook(**cfg) - - # check action_epoch > 0 - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['action_epoch'] = -1 - with self.assertRaises(AssertionError): - SwitchTrainAugHook(**cfg) - - # check action_iter > 0 - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['action_epoch'] = None - cfg['action_iter'] = '3' - with self.assertRaises(AssertionError): - SwitchTrainAugHook(**cfg) - - # test by_epoch is True - cfg = copy.deepcopy(self.DEFAULT_CFG) - hook_obj = SwitchTrainAugHook(**cfg) - self.assertTrue(hook_obj.by_epoch) - self.assertEqual(hook_obj.action_epoch, 1) - self.assertEqual(hook_obj.action_iter, None) - self.assertEqual(hook_obj.pipeline, 'unchange') - self.assertEqual(hook_obj.train_augments, 'unchange') - self.assertEqual(hook_obj.loss, 'unchange') - - # test by_epoch is False - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['action_epoch'] = None - cfg['action_iter'] = 3 - hook_obj = SwitchTrainAugHook(**cfg) - self.assertFalse(hook_obj.by_epoch) - self.assertEqual(hook_obj.action_epoch, None) - self.assertEqual(hook_obj.action_iter, 3) - - # test pipeline, loss - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [dict(type='LoadImageFromFile')] - cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.1) - hook_obj = SwitchTrainAugHook(**cfg) - self.assertIsInstance(hook_obj.pipeline, BaseTransform) - self.assertIsInstance(hook_obj.loss, LabelSmoothLoss) - - # test pieline is [], and train_augments - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [] - train_cfg = dict(augments=[ - dict(type='Mixup', alpha=0.8), - dict(type='CutMix', alpha=1.0) - ]) - cfg['train_augments'] = train_cfg - hook_obj = SwitchTrainAugHook(**cfg) - self.assertIsInstance(hook_obj.pipeline, BaseTransform) - self.assertIsInstance(hook_obj.train_augments, RandomBatchAugment) - - def test_before_train_epoch(self): - # test call once in epoch loop - runner = self.init_runner() - switch_hook_cfg1 = copy.deepcopy(self.DEFAULT_CFG) - switch_hook_cfg1['type'] = 'SwitchTrainAugHook' - runner.register_custom_hooks([switch_hook_cfg1]) - with patch.object(SwitchTrainAugHook, '_do_switch') as mock: - runner.train() - mock.assert_called_once() - - # test mutil call in epoch loop - runner = self.init_runner() - switch_hook_cfg2 = copy.deepcopy(switch_hook_cfg1) - switch_hook_cfg3 = copy.deepcopy(switch_hook_cfg1) - switch_hook_cfg2['action_epoch'] = 2 - switch_hook_cfg3['action_epoch'] = 3 - runner.register_custom_hooks( - [switch_hook_cfg1, switch_hook_cfg2, switch_hook_cfg3]) - with patch.object(SwitchTrainAugHook, '_do_switch') as mock: - runner.train() - self.assertEqual(mock.call_count, 3) - - def test_before_train_iter(self): - # test call once in iter loop - runner = self.init_runner(by_epoch=False) - switch_hook_cfg1 = copy.deepcopy(self.DEFAULT_CFG) - switch_hook_cfg1['type'] = 'SwitchTrainAugHook' - switch_hook_cfg1['action_epoch'] = None - switch_hook_cfg1['action_iter'] = 1 - runner.register_custom_hooks([switch_hook_cfg1]) - with patch.object(SwitchTrainAugHook, '_do_switch') as mock: - runner.train() - mock.assert_called_once() - - # test mutil call in iter loop - runner = self.init_runner(by_epoch=False) - switch_hook_cfg2 = copy.deepcopy(switch_hook_cfg1) - switch_hook_cfg3 = copy.deepcopy(switch_hook_cfg1) - switch_hook_cfg2['action_iter'] = 2 - switch_hook_cfg3['action_iter'] = 3 - runner.register_custom_hooks( - [switch_hook_cfg1, switch_hook_cfg2, switch_hook_cfg3]) - with patch.object(SwitchTrainAugHook, '_do_switch') as mock: - runner.train() - self.assertEqual(mock.call_count, 3) - - def test_do_switch(self): - # test switch train augments - runner = MagicMock() - cfg = copy.deepcopy(self.DEFAULT_CFG) - train_cfg = dict(augments=[ - dict(type='Mixup', alpha=0.5), - dict(type='CutMix', alpha=0.9) - ]) - cfg['train_augments'] = train_cfg - hook_obj = SwitchTrainAugHook(**cfg) - with patch.object(SwitchTrainAugHook, '_switch_train_loss') as m_loss: - with patch.object(SwitchTrainAugHook, - '_switch_train_loader_pipeline') as m_pipe: - hook_obj._do_switch(runner) - m_loss.assert_not_called() - m_pipe.assert_not_called() - runner_batchaug = runner.model.data_preprocessor.batch_augments - self.assertIsInstance(runner_batchaug, RandomBatchAugment) - self.assertEqual(len(runner_batchaug.augments), 2) - self.assertIsInstance(runner_batchaug.augments[0], Mixup) - self.assertEqual(runner_batchaug.augments[0].alpha, 0.5) - self.assertIsInstance(runner_batchaug.augments[1], CutMix) - self.assertEqual(runner_batchaug.augments[1].alpha, 0.9) - - # test switch data aug - runner = MagicMock() - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [dict(type='LoadImageFromFile')] - hook_obj = SwitchTrainAugHook(**cfg) - with patch.object(SwitchTrainAugHook, - '_switch_batch_augments') as m_batch: - with patch.object(SwitchTrainAugHook, - '_switch_train_loss') as m_loss: - hook_obj._do_switch(runner) - m_batch.assert_not_called() - m_loss.assert_not_called() - runner_pipeline = runner.train_loop.dataloader.dataset.pipeline - self.assertIsInstance(runner_pipeline, BaseTransform) - self.assertEqual(len(runner_pipeline.transforms), 1) - - # test with persistent_workers=True - if digit_version(torch.__version__) >= digit_version('1.8.0'): - runner = MagicMock() - loader = DataLoader( - ExampleDataset(), persistent_workers=True, num_workers=1) - runner.train_loop.dataloader = loader - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [ - dict(type='LoadImageFromFile'), - dict(type='Resize', scale=256) - ] - hook_obj = SwitchTrainAugHook(**cfg) - hook_obj._do_switch(runner) - runner_pipeline = runner.train_loop.dataloader.dataset.pipeline - self.assertIsInstance(runner_pipeline, BaseTransform) - self.assertEqual(len(runner_pipeline.transforms), 2) - - # test with ConcatDataset warpper - runner = MagicMock() - loader = DataLoader( - ConcatDataset([ExampleDataset(), - ExampleDataset()])) - runner.train_loop.dataloader = loader - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [ - dict(type='LoadImageFromFile'), - dict(type='Resize', scale=256) - ] - hook_obj = SwitchTrainAugHook(**cfg) - hook_obj._do_switch(runner) - for i in range(2): - runner_dataset = runner.train_loop.dataloader.dataset.datasets[i] - runner_pipeline = runner_dataset.pipeline - self.assertIsInstance(runner_pipeline, BaseTransform) - self.assertEqual(len(runner_pipeline.transforms), 2) - - # test with RepeatDataset warpper - runner = MagicMock() - loader = DataLoader(RepeatDataset(ExampleDataset(), 3)) - runner.train_loop.dataloader = loader - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [ - dict(type='LoadImageFromFile'), - dict(type='Resize', scale=256) - ] - hook_obj = SwitchTrainAugHook(**cfg) - hook_obj._do_switch(runner) - runner_pipeline = runner.train_loop.dataloader.dataset.dataset.pipeline - self.assertIsInstance(runner_pipeline, BaseTransform) - self.assertEqual(len(runner_pipeline.transforms), 2) - - # test switch loss - runner = MagicMock() - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.2) - hook_obj = SwitchTrainAugHook(**cfg) - with patch.object(SwitchTrainAugHook, - '_switch_batch_augments') as m_batch: - with patch.object(SwitchTrainAugHook, - '_switch_train_loader_pipeline') as m_pipe: - hook_obj._do_switch(runner) - m_batch.assert_not_called() - m_pipe.assert_not_called() - runner_loss = runner.model.head.loss - self.assertIsInstance(runner_loss, nn.Module) - self.assertTrue(runner_loss.label_smooth_val, 0.2) - - # test both - runner = MagicMock() - cfg = copy.deepcopy(self.DEFAULT_CFG) - cfg['pipeline'] = [dict(type='LoadImageFromFile')] - cfg['loss'] = dict(type='LabelSmoothLoss', label_smooth_val=0.2) - cfg['train_augments'] = dict(augments=[dict(type='Mixup', alpha=0.5)]) - hook_obj = SwitchTrainAugHook(**cfg) - with patch.object(SwitchTrainAugHook, - '_switch_batch_augments') as m_batch: - with patch.object(SwitchTrainAugHook, - '_switch_train_loader_pipeline') as m_pipe: - with patch.object(SwitchTrainAugHook, - '_switch_train_loss') as m_loss: - hook_obj._do_switch(runner) - m_batch.assert_called_once() - m_pipe.assert_called_once() - m_loss.assert_called_once() - - def create_patch(self, object, name): - patcher = patch.object(object, name) - thing = patcher.start() - self.addCleanup(patcher.stop) - return thing - - def test_before_train(self): - # test not resume - runner = self.init_runner(resume=False, epoch=2) - hook_obj1 = SwitchTrainAugHook(action_epoch=4) - hook_obj2 = SwitchTrainAugHook(action_epoch=7) - runner.register_custom_hooks([hook_obj1, hook_obj2]) - mock_hook1 = self.create_patch(hook_obj1, '_do_switch') - mock_hook2 = self.create_patch(hook_obj2, '_do_switch') - runner.call_hook('before_train') - mock_hook1.assert_not_called() - mock_hook2.assert_not_called() - - # test resume from no processed switch hook - runner = self.init_runner(resume=True, epoch=2) - hook_obj1 = SwitchTrainAugHook(action_epoch=4) - hook_obj2 = SwitchTrainAugHook(action_epoch=7) - runner.register_custom_hooks([hook_obj1, hook_obj2]) - mock_hook1 = self.create_patch(hook_obj1, '_do_switch') - mock_hook2 = self.create_patch(hook_obj2, '_do_switch') - runner.call_hook('before_train') - mock_hook1.assert_not_called() - mock_hook2.assert_not_called() - - # test resume from epoch processed switch hook - runner = self.init_runner(resume=True, epoch=5) - hook_obj1 = SwitchTrainAugHook(action_epoch=2) - hook_obj2 = SwitchTrainAugHook(action_epoch=7) - hook_obj3 = SwitchTrainAugHook(action_epoch=3) - runner.register_custom_hooks([hook_obj1, hook_obj2, hook_obj3]) - mock_hook1 = self.create_patch(hook_obj1, '_do_switch') - mock_hook2 = self.create_patch(hook_obj2, '_do_switch') - mock_hook3 = self.create_patch(hook_obj3, '_do_switch') - runner.call_hook('before_train') - mock_hook1.assert_not_called() - mock_hook2.assert_not_called() - mock_hook3.assert_called_once() - - # test resume from iter processed switch hook - runner = self.init_runner(resume=True, iter=15, by_epoch=False) - hook_obj1 = SwitchTrainAugHook(action_iter=2) - hook_obj2 = SwitchTrainAugHook(action_iter=12) - hook_obj3 = SwitchTrainAugHook(action_epoch=18) - runner.register_custom_hooks([hook_obj1, hook_obj2, hook_obj3]) - mock_hook1 = self.create_patch(hook_obj1, '_do_switch') - mock_hook2 = self.create_patch(hook_obj2, '_do_switch') - mock_hook3 = self.create_patch(hook_obj3, '_do_switch') - runner.call_hook('before_train') - mock_hook1.assert_not_called() - mock_hook2.assert_called_once() - mock_hook3.assert_not_called() - - # test resume from epoch loop and iter hook - runner = self.init_runner(resume=True, epoch=1) # i epoch = 5 iter - hook_obj1 = SwitchTrainAugHook(action_iter=2) - hook_obj2 = SwitchTrainAugHook(action_iter=15) - hook_obj3 = SwitchTrainAugHook(action_iter=7) - runner.register_custom_hooks([hook_obj1, hook_obj2, hook_obj3]) - mock_hook1 = self.create_patch(hook_obj1, '_do_switch') - mock_hook2 = self.create_patch(hook_obj2, '_do_switch') - mock_hook3 = self.create_patch(hook_obj3, '_do_switch') - runner.call_hook('before_train') - mock_hook1.assert_called_once() - mock_hook2.assert_not_called() - mock_hook3.assert_not_called() - - def init_runner(self, resume=False, epoch=None, iter=None, by_epoch=True): - if by_epoch: - runner = Runner( - model=ExampleModel(), - work_dir=self.tmpdir.name, - train_dataloader=self.loader, - optim_wrapper=self.optim_wrapper, - param_scheduler=self.epochscheduler, - train_cfg=dict(by_epoch=True, max_epochs=3), - default_hooks=dict(logger=None), - log_processor=dict(window_size=1), - experiment_name=f'test_{resume}_{random.random()}', - default_scope='mmcls') - else: - runner = Runner( - model=ExampleModel(), - work_dir=self.tmpdir.name, - train_dataloader=self.loader, - optim_wrapper=self.optim_wrapper, - param_scheduler=self.iterscheduler, - train_cfg=dict(by_epoch=False, max_iters=3), - default_hooks=dict(logger=None), - log_processor=dict(window_size=1), - experiment_name=f'test_{resume}_{random.random()}', - default_scope='mmcls') - runner._resume = resume - dataset_length = len(self.loader) - if epoch and by_epoch: - runner.train_loop._epoch = epoch - runner.train_loop._iter = epoch * dataset_length - if iter and not by_epoch: - runner.train_loop._iter = iter - return runner - - def tearDown(self) -> None: - # `FileHandler` should be closed in Windows, otherwise we cannot - # delete the temporary directory. - logging.shutdown() - MMLogger._instance_dict.clear() - self.tmpdir.cleanup()