From 99dc9325bd17d461be7e6545021b9639721d572a Mon Sep 17 00:00:00 2001 From: mzr1996 Date: Fri, 18 Jun 2021 11:04:41 +0800 Subject: [PATCH 1/7] Refactor unit tests folder structure. --- .../test_datasets/test_common.py} | 102 +----------------- .../test_datasets/test_dataset_wrapper.py | 83 ++++++++++++++ tests/test_data/test_datasets/test_utils.py | 21 ++++ .../test_pipelines/test_auto_augment.py | 0 .../test_pipelines/test_loading.py | 2 +- .../test_pipelines/test_transform.py | 22 ++-- .../test_mmdet_inference.py | 0 tests/{ => test_metrics}/test_losses.py | 0 tests/{ => test_metrics}/test_metrics.py | 0 .../test_backbones/test_mobilenet_v2.py | 0 .../test_backbones/test_mobilenet_v3.py | 0 .../test_backbones/test_regnet.py | 0 .../test_backbones/test_resnest.py | 0 .../test_backbones/test_resnet.py | 0 .../test_backbones/test_resnet_cifar.py | 0 .../test_backbones/test_resnext.py | 0 .../test_backbones/test_seresnet.py | 0 .../test_backbones/test_seresnext.py | 0 .../test_backbones/test_shufflenet_v1.py | 0 .../test_backbones/test_shufflenet_v2.py | 0 .../test_backbones/test_swin_transformer.py | 0 .../test_backbones/test_vgg.py | 0 .../test_backbones/test_vision_transformer.py | 0 tests/{ => test_models}/test_classifiers.py | 0 tests/{ => test_models}/test_heads.py | 0 tests/{ => test_models}/test_neck.py | 0 .../test_utils}/test_attention.py | 0 .../test_utils}/test_embed.py | 0 .../test_utils}/test_utils.py | 0 tests/{ => test_runtime}/test_eval_hook.py | 0 30 files changed, 117 insertions(+), 113 deletions(-) rename tests/{test_dataset.py => test_data/test_datasets/test_common.py} (68%) create mode 100644 tests/test_data/test_datasets/test_dataset_wrapper.py create mode 100644 tests/test_data/test_datasets/test_utils.py rename tests/{ => test_data}/test_pipelines/test_auto_augment.py (100%) rename tests/{ => test_data}/test_pipelines/test_loading.py (96%) rename tests/{ => test_data}/test_pipelines/test_transform.py (98%) rename tests/{ => test_downstream}/test_mmdet_inference.py (100%) rename tests/{ => test_metrics}/test_losses.py (100%) rename tests/{ => test_metrics}/test_metrics.py (100%) rename tests/{ => test_models}/test_backbones/test_mobilenet_v2.py (100%) rename tests/{ => test_models}/test_backbones/test_mobilenet_v3.py (100%) rename tests/{ => test_models}/test_backbones/test_regnet.py (100%) rename tests/{ => test_models}/test_backbones/test_resnest.py (100%) rename tests/{ => test_models}/test_backbones/test_resnet.py (100%) rename tests/{ => test_models}/test_backbones/test_resnet_cifar.py (100%) rename tests/{ => test_models}/test_backbones/test_resnext.py (100%) rename tests/{ => test_models}/test_backbones/test_seresnet.py (100%) rename tests/{ => test_models}/test_backbones/test_seresnext.py (100%) rename tests/{ => test_models}/test_backbones/test_shufflenet_v1.py (100%) rename tests/{ => test_models}/test_backbones/test_shufflenet_v2.py (100%) rename tests/{ => test_models}/test_backbones/test_swin_transformer.py (100%) rename tests/{ => test_models}/test_backbones/test_vgg.py (100%) rename tests/{ => test_models}/test_backbones/test_vision_transformer.py (100%) rename tests/{ => test_models}/test_classifiers.py (100%) rename tests/{ => test_models}/test_heads.py (100%) rename tests/{ => test_models}/test_neck.py (100%) rename tests/{test_backbones => test_models/test_utils}/test_attention.py (100%) rename tests/{test_backbones => test_models/test_utils}/test_embed.py (100%) rename tests/{test_backbones => test_models/test_utils}/test_utils.py (100%) rename tests/{ => test_runtime}/test_eval_hook.py (100%) diff --git a/tests/test_dataset.py b/tests/test_data/test_datasets/test_common.py similarity index 68% rename from tests/test_dataset.py rename to tests/test_data/test_datasets/test_common.py index 4492f3ad157..6e0a2a47a9e 100644 --- a/tests/test_dataset.py +++ b/tests/test_data/test_datasets/test_common.py @@ -1,18 +1,11 @@ -import bisect -import math -import random -import string import tempfile -from collections import defaultdict from unittest.mock import MagicMock, patch import numpy as np import pytest import torch -from mmcls.datasets import (DATASETS, BaseDataset, ClassBalancedDataset, - ConcatDataset, MultiLabelDataset, RepeatDataset) -from mmcls.datasets.utils import check_integrity, rm_suffix +from mmcls.datasets import DATASETS, BaseDataset, MultiLabelDataset @pytest.mark.parametrize( @@ -45,7 +38,6 @@ def test_datasets_override_default(dataset_name): pipeline=[], classes=('bus', 'car'), test_mode=True) - assert dataset.CLASSES != original_classes assert dataset.CLASSES == ('bus', 'car') # Test setting classes as a list @@ -54,7 +46,6 @@ def test_datasets_override_default(dataset_name): pipeline=[], classes=['bus', 'car'], test_mode=True) - assert dataset.CLASSES != original_classes assert dataset.CLASSES == ['bus', 'car'] # Test setting classes through a file @@ -68,7 +59,6 @@ def test_datasets_override_default(dataset_name): test_mode=True) tmp_file.close() - assert dataset.CLASSES != original_classes assert dataset.CLASSES == ['bus', 'car'] # Test overriding not a subset @@ -77,7 +67,6 @@ def test_datasets_override_default(dataset_name): pipeline=[], classes=['foo'], test_mode=True) - assert dataset.CLASSES != original_classes assert dataset.CLASSES == ['foo'] # Test default behavior @@ -258,92 +247,3 @@ def test_dataset_evaluation(): assert 'CR' in eval_results.keys() assert 'OF1' in eval_results.keys() assert 'CF1' not in eval_results.keys() - - -@patch.multiple(BaseDataset, __abstractmethods__=set()) -def test_dataset_wrapper(): - BaseDataset.CLASSES = ('foo', 'bar') - BaseDataset.__getitem__ = MagicMock(side_effect=lambda idx: idx) - dataset_a = BaseDataset(data_prefix='', pipeline=[], test_mode=True) - len_a = 10 - cat_ids_list_a = [ - np.random.randint(0, 80, num).tolist() - for num in np.random.randint(1, 20, len_a) - ] - dataset_a.data_infos = MagicMock() - dataset_a.data_infos.__len__.return_value = len_a - dataset_a.get_cat_ids = MagicMock( - side_effect=lambda idx: cat_ids_list_a[idx]) - dataset_b = BaseDataset(data_prefix='', pipeline=[], test_mode=True) - len_b = 20 - cat_ids_list_b = [ - np.random.randint(0, 80, num).tolist() - for num in np.random.randint(1, 20, len_b) - ] - dataset_b.data_infos = MagicMock() - dataset_b.data_infos.__len__.return_value = len_b - dataset_b.get_cat_ids = MagicMock( - side_effect=lambda idx: cat_ids_list_b[idx]) - - concat_dataset = ConcatDataset([dataset_a, dataset_b]) - assert concat_dataset[5] == 5 - assert concat_dataset[25] == 15 - assert concat_dataset.get_cat_ids(5) == cat_ids_list_a[5] - assert concat_dataset.get_cat_ids(25) == cat_ids_list_b[15] - assert len(concat_dataset) == len(dataset_a) + len(dataset_b) - assert concat_dataset.CLASSES == BaseDataset.CLASSES - - repeat_dataset = RepeatDataset(dataset_a, 10) - assert repeat_dataset[5] == 5 - assert repeat_dataset[15] == 5 - assert repeat_dataset[27] == 7 - assert repeat_dataset.get_cat_ids(5) == cat_ids_list_a[5] - assert repeat_dataset.get_cat_ids(15) == cat_ids_list_a[5] - assert repeat_dataset.get_cat_ids(27) == cat_ids_list_a[7] - assert len(repeat_dataset) == 10 * len(dataset_a) - assert repeat_dataset.CLASSES == BaseDataset.CLASSES - - category_freq = defaultdict(int) - for cat_ids in cat_ids_list_a: - cat_ids = set(cat_ids) - for cat_id in cat_ids: - category_freq[cat_id] += 1 - for k, v in category_freq.items(): - category_freq[k] = v / len(cat_ids_list_a) - - mean_freq = np.mean(list(category_freq.values())) - repeat_thr = mean_freq - - category_repeat = { - cat_id: max(1.0, math.sqrt(repeat_thr / cat_freq)) - for cat_id, cat_freq in category_freq.items() - } - - repeat_factors = [] - for cat_ids in cat_ids_list_a: - cat_ids = set(cat_ids) - repeat_factor = max({category_repeat[cat_id] for cat_id in cat_ids}) - repeat_factors.append(math.ceil(repeat_factor)) - repeat_factors_cumsum = np.cumsum(repeat_factors) - repeat_factor_dataset = ClassBalancedDataset(dataset_a, repeat_thr) - assert repeat_factor_dataset.CLASSES == BaseDataset.CLASSES - assert len(repeat_factor_dataset) == repeat_factors_cumsum[-1] - for idx in np.random.randint(0, len(repeat_factor_dataset), 3): - assert repeat_factor_dataset[idx] == bisect.bisect_right( - repeat_factors_cumsum, idx) - - -def test_dataset_utils(): - # test rm_suffix - assert rm_suffix('a.jpg') == 'a' - assert rm_suffix('a.bak.jpg') == 'a.bak' - assert rm_suffix('a.bak.jpg', suffix='.jpg') == 'a.bak' - assert rm_suffix('a.bak.jpg', suffix='.bak.jpg') == 'a' - - # test check_integrity - rand_file = ''.join(random.sample(string.ascii_letters, 10)) - assert not check_integrity(rand_file, md5=None) - assert not check_integrity(rand_file, md5=2333) - tmp_file = tempfile.NamedTemporaryFile() - assert check_integrity(tmp_file.name, md5=None) - assert not check_integrity(tmp_file.name, md5=2333) diff --git a/tests/test_data/test_datasets/test_dataset_wrapper.py b/tests/test_data/test_datasets/test_dataset_wrapper.py new file mode 100644 index 00000000000..d2b7a424428 --- /dev/null +++ b/tests/test_data/test_datasets/test_dataset_wrapper.py @@ -0,0 +1,83 @@ +import bisect +import math +from collections import defaultdict +from unittest.mock import MagicMock, patch + +import numpy as np + +from mmcls.datasets import (BaseDataset, ClassBalancedDataset, ConcatDataset, + RepeatDataset) + + +@patch.multiple(BaseDataset, __abstractmethods__=set()) +def construct_toy_dataset(length): + BaseDataset.CLASSES = ('foo', 'bar') + BaseDataset.__getitem__ = MagicMock(side_effect=lambda idx: idx) + dataset = BaseDataset(data_prefix='', pipeline=[], test_mode=True) + cat_ids_list = [ + np.random.randint(0, 80, num).tolist() + for num in np.random.randint(1, 20, length) + ] + dataset.data_infos = MagicMock() + dataset.data_infos.__len__.return_value = length + dataset.get_cat_ids = MagicMock(side_effect=lambda idx: cat_ids_list[idx]) + return dataset, cat_ids_list + + +def test_concat_dataset(): + dataset_a, cat_ids_list_a = construct_toy_dataset(10) + dataset_b, cat_ids_list_b = construct_toy_dataset(20) + + concat_dataset = ConcatDataset([dataset_a, dataset_b]) + assert concat_dataset[5] == 5 + assert concat_dataset[25] == 15 + assert concat_dataset.get_cat_ids(5) == cat_ids_list_a[5] + assert concat_dataset.get_cat_ids(25) == cat_ids_list_b[15] + assert len(concat_dataset) == len(dataset_a) + len(dataset_b) + assert concat_dataset.CLASSES == BaseDataset.CLASSES + + +def test_repeat_dataset(): + dataset, cat_ids_list = construct_toy_dataset(10) + repeat_dataset = RepeatDataset(dataset, 10) + assert repeat_dataset[5] == 5 + assert repeat_dataset[15] == 5 + assert repeat_dataset[27] == 7 + assert repeat_dataset.get_cat_ids(5) == cat_ids_list[5] + assert repeat_dataset.get_cat_ids(15) == cat_ids_list[5] + assert repeat_dataset.get_cat_ids(27) == cat_ids_list[7] + assert len(repeat_dataset) == 10 * len(dataset) + assert repeat_dataset.CLASSES == BaseDataset.CLASSES + + +def test_class_balanced_dataset(): + dataset, cat_ids_list = construct_toy_dataset(10) + + category_freq = defaultdict(int) + for cat_ids in cat_ids_list: + cat_ids = set(cat_ids) + for cat_id in cat_ids: + category_freq[cat_id] += 1 + for k, v in category_freq.items(): + category_freq[k] = v / len(cat_ids_list) + + mean_freq = np.mean(list(category_freq.values())) + repeat_thr = mean_freq + + category_repeat = { + cat_id: max(1.0, math.sqrt(repeat_thr / cat_freq)) + for cat_id, cat_freq in category_freq.items() + } + + repeat_factors = [] + for cat_ids in cat_ids_list: + cat_ids = set(cat_ids) + repeat_factor = max({category_repeat[cat_id] for cat_id in cat_ids}) + repeat_factors.append(math.ceil(repeat_factor)) + repeat_factors_cumsum = np.cumsum(repeat_factors) + repeat_factor_dataset = ClassBalancedDataset(dataset, repeat_thr) + assert repeat_factor_dataset.CLASSES == BaseDataset.CLASSES + assert len(repeat_factor_dataset) == repeat_factors_cumsum[-1] + for idx in np.random.randint(0, len(repeat_factor_dataset), 3): + assert repeat_factor_dataset[idx] == bisect.bisect_right( + repeat_factors_cumsum, idx) diff --git a/tests/test_data/test_datasets/test_utils.py b/tests/test_data/test_datasets/test_utils.py new file mode 100644 index 00000000000..e5c197c347e --- /dev/null +++ b/tests/test_data/test_datasets/test_utils.py @@ -0,0 +1,21 @@ +import random +import string +import tempfile + +from mmcls.datasets.utils import check_integrity, rm_suffix + + +def test_dataset_utils(): + # test rm_suffix + assert rm_suffix('a.jpg') == 'a' + assert rm_suffix('a.bak.jpg') == 'a.bak' + assert rm_suffix('a.bak.jpg', suffix='.jpg') == 'a.bak' + assert rm_suffix('a.bak.jpg', suffix='.bak.jpg') == 'a' + + # test check_integrity + rand_file = ''.join(random.sample(string.ascii_letters, 10)) + assert not check_integrity(rand_file, md5=None) + assert not check_integrity(rand_file, md5=2333) + tmp_file = tempfile.NamedTemporaryFile() + assert check_integrity(tmp_file.name, md5=None) + assert not check_integrity(tmp_file.name, md5=2333) diff --git a/tests/test_pipelines/test_auto_augment.py b/tests/test_data/test_pipelines/test_auto_augment.py similarity index 100% rename from tests/test_pipelines/test_auto_augment.py rename to tests/test_data/test_pipelines/test_auto_augment.py diff --git a/tests/test_pipelines/test_loading.py b/tests/test_data/test_pipelines/test_loading.py similarity index 96% rename from tests/test_pipelines/test_loading.py rename to tests/test_data/test_pipelines/test_loading.py index d3d913d72c1..4feaab25e6c 100644 --- a/tests/test_pipelines/test_loading.py +++ b/tests/test_data/test_pipelines/test_loading.py @@ -10,7 +10,7 @@ class TestLoading(object): @classmethod def setup_class(cls): - cls.data_prefix = osp.join(osp.dirname(__file__), '../data') + cls.data_prefix = osp.join(osp.dirname(__file__), '../../data') def test_load_img(self): results = dict( diff --git a/tests/test_pipelines/test_transform.py b/tests/test_data/test_pipelines/test_transform.py similarity index 98% rename from tests/test_pipelines/test_transform.py rename to tests/test_data/test_pipelines/test_transform.py index 9d751fa7521..7003e30cbdf 100644 --- a/tests/test_pipelines/test_transform.py +++ b/tests/test_data/test_pipelines/test_transform.py @@ -70,7 +70,7 @@ def test_resize(): # read test image results = dict() img = mmcv.imread( - osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color') original_img = copy.deepcopy(img) results['img'] = img results['img2'] = copy.deepcopy(img) @@ -220,7 +220,7 @@ def test_center_crop(): # read test image results = dict() img = mmcv.imread( - osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color') original_img = copy.deepcopy(img) results['img'] = img results['img2'] = copy.deepcopy(img) @@ -343,7 +343,7 @@ def test_normalize(): # read data results = dict() img = mmcv.imread( - osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color') original_img = copy.deepcopy(img) results['img'] = img results['img2'] = copy.deepcopy(img) @@ -371,9 +371,9 @@ def test_normalize(): def test_randomcrop(): ori_img = mmcv.imread( - osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color') ori_img_pil = Image.open( - osp.join(osp.dirname(__file__), '../data/color.jpg')) + osp.join(osp.dirname(__file__), '../../data/color.jpg')) seed = random.randint(0, 100) # test crop size is int @@ -517,9 +517,9 @@ def test_randomcrop(): def test_randomresizedcrop(): ori_img = mmcv.imread( - osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color') ori_img_pil = Image.open( - osp.join(osp.dirname(__file__), '../data/color.jpg')) + osp.join(osp.dirname(__file__), '../../data/color.jpg')) seed = random.randint(0, 100) @@ -900,7 +900,7 @@ def test_randomflip(): # read test image results = dict() img = mmcv.imread( - osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color') original_img = copy.deepcopy(img) results['img'] = img results['img2'] = copy.deepcopy(img) @@ -1077,7 +1077,7 @@ def test_color_jitter(): # read test image results = dict() img = mmcv.imread( - osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color') original_img = copy.deepcopy(img) results['img'] = img results['img2'] = copy.deepcopy(img) @@ -1123,7 +1123,7 @@ def test_lighting(): # read test image results = dict() img = mmcv.imread( - osp.join(osp.dirname(__file__), '../data/color.jpg'), 'color') + osp.join(osp.dirname(__file__), '../../data/color.jpg'), 'color') original_img = copy.deepcopy(img) results['img'] = img results['img2'] = copy.deepcopy(img) @@ -1165,7 +1165,7 @@ def reset_results(results, original_img): def test_albu_transform(): results = dict( - img_prefix=osp.join(osp.dirname(__file__), '../data'), + img_prefix=osp.join(osp.dirname(__file__), '../../data'), img_info=dict(filename='color.jpg')) # Define simple pipeline diff --git a/tests/test_mmdet_inference.py b/tests/test_downstream/test_mmdet_inference.py similarity index 100% rename from tests/test_mmdet_inference.py rename to tests/test_downstream/test_mmdet_inference.py diff --git a/tests/test_losses.py b/tests/test_metrics/test_losses.py similarity index 100% rename from tests/test_losses.py rename to tests/test_metrics/test_losses.py diff --git a/tests/test_metrics.py b/tests/test_metrics/test_metrics.py similarity index 100% rename from tests/test_metrics.py rename to tests/test_metrics/test_metrics.py diff --git a/tests/test_backbones/test_mobilenet_v2.py b/tests/test_models/test_backbones/test_mobilenet_v2.py similarity index 100% rename from tests/test_backbones/test_mobilenet_v2.py rename to tests/test_models/test_backbones/test_mobilenet_v2.py diff --git a/tests/test_backbones/test_mobilenet_v3.py b/tests/test_models/test_backbones/test_mobilenet_v3.py similarity index 100% rename from tests/test_backbones/test_mobilenet_v3.py rename to tests/test_models/test_backbones/test_mobilenet_v3.py diff --git a/tests/test_backbones/test_regnet.py b/tests/test_models/test_backbones/test_regnet.py similarity index 100% rename from tests/test_backbones/test_regnet.py rename to tests/test_models/test_backbones/test_regnet.py diff --git a/tests/test_backbones/test_resnest.py b/tests/test_models/test_backbones/test_resnest.py similarity index 100% rename from tests/test_backbones/test_resnest.py rename to tests/test_models/test_backbones/test_resnest.py diff --git a/tests/test_backbones/test_resnet.py b/tests/test_models/test_backbones/test_resnet.py similarity index 100% rename from tests/test_backbones/test_resnet.py rename to tests/test_models/test_backbones/test_resnet.py diff --git a/tests/test_backbones/test_resnet_cifar.py b/tests/test_models/test_backbones/test_resnet_cifar.py similarity index 100% rename from tests/test_backbones/test_resnet_cifar.py rename to tests/test_models/test_backbones/test_resnet_cifar.py diff --git a/tests/test_backbones/test_resnext.py b/tests/test_models/test_backbones/test_resnext.py similarity index 100% rename from tests/test_backbones/test_resnext.py rename to tests/test_models/test_backbones/test_resnext.py diff --git a/tests/test_backbones/test_seresnet.py b/tests/test_models/test_backbones/test_seresnet.py similarity index 100% rename from tests/test_backbones/test_seresnet.py rename to tests/test_models/test_backbones/test_seresnet.py diff --git a/tests/test_backbones/test_seresnext.py b/tests/test_models/test_backbones/test_seresnext.py similarity index 100% rename from tests/test_backbones/test_seresnext.py rename to tests/test_models/test_backbones/test_seresnext.py diff --git a/tests/test_backbones/test_shufflenet_v1.py b/tests/test_models/test_backbones/test_shufflenet_v1.py similarity index 100% rename from tests/test_backbones/test_shufflenet_v1.py rename to tests/test_models/test_backbones/test_shufflenet_v1.py diff --git a/tests/test_backbones/test_shufflenet_v2.py b/tests/test_models/test_backbones/test_shufflenet_v2.py similarity index 100% rename from tests/test_backbones/test_shufflenet_v2.py rename to tests/test_models/test_backbones/test_shufflenet_v2.py diff --git a/tests/test_backbones/test_swin_transformer.py b/tests/test_models/test_backbones/test_swin_transformer.py similarity index 100% rename from tests/test_backbones/test_swin_transformer.py rename to tests/test_models/test_backbones/test_swin_transformer.py diff --git a/tests/test_backbones/test_vgg.py b/tests/test_models/test_backbones/test_vgg.py similarity index 100% rename from tests/test_backbones/test_vgg.py rename to tests/test_models/test_backbones/test_vgg.py diff --git a/tests/test_backbones/test_vision_transformer.py b/tests/test_models/test_backbones/test_vision_transformer.py similarity index 100% rename from tests/test_backbones/test_vision_transformer.py rename to tests/test_models/test_backbones/test_vision_transformer.py diff --git a/tests/test_classifiers.py b/tests/test_models/test_classifiers.py similarity index 100% rename from tests/test_classifiers.py rename to tests/test_models/test_classifiers.py diff --git a/tests/test_heads.py b/tests/test_models/test_heads.py similarity index 100% rename from tests/test_heads.py rename to tests/test_models/test_heads.py diff --git a/tests/test_neck.py b/tests/test_models/test_neck.py similarity index 100% rename from tests/test_neck.py rename to tests/test_models/test_neck.py diff --git a/tests/test_backbones/test_attention.py b/tests/test_models/test_utils/test_attention.py similarity index 100% rename from tests/test_backbones/test_attention.py rename to tests/test_models/test_utils/test_attention.py diff --git a/tests/test_backbones/test_embed.py b/tests/test_models/test_utils/test_embed.py similarity index 100% rename from tests/test_backbones/test_embed.py rename to tests/test_models/test_utils/test_embed.py diff --git a/tests/test_backbones/test_utils.py b/tests/test_models/test_utils/test_utils.py similarity index 100% rename from tests/test_backbones/test_utils.py rename to tests/test_models/test_utils/test_utils.py diff --git a/tests/test_eval_hook.py b/tests/test_runtime/test_eval_hook.py similarity index 100% rename from tests/test_eval_hook.py rename to tests/test_runtime/test_eval_hook.py From 0322319485ffcf79bdc0bb9ceeba90bab50a044b Mon Sep 17 00:00:00 2001 From: mzr1996 Date: Fri, 18 Jun 2021 11:34:24 +0800 Subject: [PATCH 2/7] Remove label smooth and Vit test in `test_classifiers.py` --- tests/test_models/test_classifiers.py | 65 +-------------------------- 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/tests/test_models/test_classifiers.py b/tests/test_models/test_classifiers.py index e7194a760da..1dbd6e7ad53 100644 --- a/tests/test_models/test_classifiers.py +++ b/tests/test_models/test_classifiers.py @@ -127,7 +127,7 @@ def test_image_classifier_with_augments(): losses = img_classifier.forward_train(imgs, label) assert losses['loss'].item() > 0 - # Test not using cutmix and mixup in ImageClassifier + # Test not using train_cfg model_cfg = dict( backbone=dict( type='ResNet_CIFAR', @@ -156,66 +156,3 @@ def test_image_classifier_with_augments(): losses = img_classifier.forward_train(imgs, label) assert losses['loss'].item() > 0 - - -def test_image_classifier_with_label_smooth_loss(): - - # Test mixup in ImageClassifier - model_cfg = dict( - backbone=dict( - type='ResNet_CIFAR', - depth=50, - num_stages=4, - out_indices=(3, ), - style='pytorch'), - neck=dict(type='GlobalAveragePooling'), - head=dict( - type='MultiLabelLinearClsHead', - num_classes=10, - in_channels=2048, - loss=dict(type='LabelSmoothLoss', label_smooth_val=0.1)), - train_cfg=dict( - augments=dict( - type='BatchMixup', alpha=1., num_classes=10, prob=1.))) - img_classifier = ImageClassifier(**model_cfg) - img_classifier.init_weights() - imgs = torch.randn(16, 3, 32, 32) - label = torch.randint(0, 10, (16, )) - - losses = img_classifier.forward_train(imgs, label) - assert losses['loss'].item() > 0 - - -def test_image_classifier_vit(): - - model_cfg = dict( - backbone=dict( - type='VisionTransformer', - num_layers=12, - embed_dim=768, - num_heads=12, - img_size=224, - patch_size=16, - in_channels=3, - feedforward_channels=3072, - drop_rate=0.1, - attn_drop_rate=0.), - neck=None, - head=dict( - type='VisionTransformerClsHead', - num_classes=1000, - in_channels=768, - hidden_dim=3072, - loss=dict(type='CrossEntropyLoss', loss_weight=1.0, use_soft=True), - topk=(1, 5), - ), - train_cfg=dict( - augments=dict( - type='BatchMixup', alpha=0.2, num_classes=1000, prob=1.))) - img_classifier = ImageClassifier(**model_cfg) - img_classifier.init_weights() - imgs = torch.randn(2, 3, 224, 224) - label = torch.randint(0, 1000, (2, )) - - losses = img_classifier.forward_train(imgs, label) - assert losses['loss'].item() > 0 From ceb1e0ad1c634eeeab3b11f439b2b5ecac92d2b8 Mon Sep 17 00:00:00 2001 From: mzr1996 Date: Wed, 23 Jun 2021 11:03:10 +0800 Subject: [PATCH 3/7] Rename test_utils in dataset to test_dataset_utils --- .../test_datasets/{test_utils.py => test_dataset_utils.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/test_data/test_datasets/{test_utils.py => test_dataset_utils.py} (100%) diff --git a/tests/test_data/test_datasets/test_utils.py b/tests/test_data/test_datasets/test_dataset_utils.py similarity index 100% rename from tests/test_data/test_datasets/test_utils.py rename to tests/test_data/test_datasets/test_dataset_utils.py From dbad9b88ee6317b8279e74bfa175a5f956ce146d Mon Sep 17 00:00:00 2001 From: mzr1996 Date: Tue, 6 Jul 2021 16:08:31 +0800 Subject: [PATCH 4/7] Split test_models/test_utils/test_utils.py to multiple sub files. --- tests/test_models/test_utils/test_augment.py | 51 +++++ tests/test_models/test_utils/test_embed.py | 28 ++- .../test_utils/test_inverted_residual.py | 81 ++++++++ tests/test_models/test_utils/test_misc.py | 37 ++++ tests/test_models/test_utils/test_utils.py | 189 ------------------ 5 files changed, 196 insertions(+), 190 deletions(-) create mode 100644 tests/test_models/test_utils/test_augment.py create mode 100644 tests/test_models/test_utils/test_inverted_residual.py create mode 100644 tests/test_models/test_utils/test_misc.py delete mode 100644 tests/test_models/test_utils/test_utils.py diff --git a/tests/test_models/test_utils/test_augment.py b/tests/test_models/test_utils/test_augment.py new file mode 100644 index 00000000000..4002bb15501 --- /dev/null +++ b/tests/test_models/test_utils/test_augment.py @@ -0,0 +1,51 @@ +import torch + +from mmcls.models.utils import Augments + + +def test_augments(): + imgs = torch.randn(4, 3, 32, 32) + labels = torch.randint(0, 10, (4, )) + + # Test cutmix + augments_cfg = dict(type='BatchCutMix', alpha=1., num_classes=10, prob=1.) + augs = Augments(augments_cfg) + mixed_imgs, mixed_labels = augs(imgs, labels) + assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) + assert mixed_labels.shape == torch.Size((4, 10)) + + # Test mixup + augments_cfg = dict(type='BatchMixup', alpha=1., num_classes=10, prob=1.) + augs = Augments(augments_cfg) + mixed_imgs, mixed_labels = augs(imgs, labels) + assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) + assert mixed_labels.shape == torch.Size((4, 10)) + + # Test cutmixup + augments_cfg = [ + dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5), + dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.3) + ] + augs = Augments(augments_cfg) + mixed_imgs, mixed_labels = augs(imgs, labels) + assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) + assert mixed_labels.shape == torch.Size((4, 10)) + + augments_cfg = [ + dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5), + dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.5) + ] + augs = Augments(augments_cfg) + mixed_imgs, mixed_labels = augs(imgs, labels) + assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) + assert mixed_labels.shape == torch.Size((4, 10)) + + augments_cfg = [ + dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5), + dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.3), + dict(type='Identity', num_classes=10, prob=0.2) + ] + augs = Augments(augments_cfg) + mixed_imgs, mixed_labels = augs(imgs, labels) + assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) + assert mixed_labels.shape == torch.Size((4, 10)) diff --git a/tests/test_models/test_utils/test_embed.py b/tests/test_models/test_utils/test_embed.py index 77862a6eb4c..117cd7823b5 100644 --- a/tests/test_models/test_utils/test_embed.py +++ b/tests/test_models/test_utils/test_embed.py @@ -1,13 +1,39 @@ import pytest import torch -from mmcls.models.utils import PatchMerging +from mmcls.models.backbones import VGG +from mmcls.models.utils import HybridEmbed, PatchEmbed, PatchMerging def cal_unfold_dim(dim, kernel_size, stride, padding=0, dilation=1): return (dim + 2 * padding - dilation * (kernel_size - 1) - 1) // stride + 1 +def test_patch_embed(): + # Test PatchEmbed + patch_embed = PatchEmbed() + img = torch.randn(1, 3, 224, 224) + img = patch_embed(img) + assert img.shape == torch.Size((1, 196, 768)) + + # Test PatchEmbed with stride = 8 + conv_cfg = dict(kernel_size=16, stride=8) + patch_embed = PatchEmbed(conv_cfg=conv_cfg) + img = torch.randn(1, 3, 224, 224) + img = patch_embed(img) + assert img.shape == torch.Size((1, 729, 768)) + + +def test_hybrid_embed(): + # Test VGG11 HybridEmbed + backbone = VGG(11, norm_eval=True) + backbone.init_weights() + patch_embed = HybridEmbed(backbone) + img = torch.randn(1, 3, 224, 224) + img = patch_embed(img) + assert img.shape == torch.Size((1, 49, 768)) + + def test_patch_merging(): settings = dict( input_resolution=(56, 56), in_channels=16, expansion_ratio=2) diff --git a/tests/test_models/test_utils/test_inverted_residual.py b/tests/test_models/test_utils/test_inverted_residual.py new file mode 100644 index 00000000000..edfaa90ee7b --- /dev/null +++ b/tests/test_models/test_utils/test_inverted_residual.py @@ -0,0 +1,81 @@ +import pytest +import torch +from torch.nn.modules import GroupNorm +from torch.nn.modules.batchnorm import _BatchNorm + +from mmcls.models.utils import InvertedResidual, SELayer + + +def is_norm(modules): + """Check if is one of the norms.""" + if isinstance(modules, (GroupNorm, _BatchNorm)): + return True + return False + + +def test_inverted_residual(): + + with pytest.raises(AssertionError): + # stride must be in [1, 2] + InvertedResidual(16, 16, 32, stride=3) + + with pytest.raises(AssertionError): + # se_cfg must be None or dict + InvertedResidual(16, 16, 32, se_cfg=list()) + + # Add expand conv if in_channels and mid_channels is not the same + assert InvertedResidual(32, 16, 32).with_expand_conv is False + assert InvertedResidual(16, 16, 32).with_expand_conv is True + + # Test InvertedResidual forward, stride=1 + block = InvertedResidual(16, 16, 32, stride=1) + x = torch.randn(1, 16, 56, 56) + x_out = block(x) + assert getattr(block, 'se', None) is None + assert block.with_res_shortcut + assert x_out.shape == torch.Size((1, 16, 56, 56)) + + # Test InvertedResidual forward, stride=2 + block = InvertedResidual(16, 16, 32, stride=2) + x = torch.randn(1, 16, 56, 56) + x_out = block(x) + assert not block.with_res_shortcut + assert x_out.shape == torch.Size((1, 16, 28, 28)) + + # Test InvertedResidual forward with se layer + se_cfg = dict(channels=32) + block = InvertedResidual(16, 16, 32, stride=1, se_cfg=se_cfg) + x = torch.randn(1, 16, 56, 56) + x_out = block(x) + assert isinstance(block.se, SELayer) + assert x_out.shape == torch.Size((1, 16, 56, 56)) + + # Test InvertedResidual forward without expand conv + block = InvertedResidual(32, 16, 32) + x = torch.randn(1, 32, 56, 56) + x_out = block(x) + assert getattr(block, 'expand_conv', None) is None + assert x_out.shape == torch.Size((1, 16, 56, 56)) + + # Test InvertedResidual forward with GroupNorm + block = InvertedResidual( + 16, 16, 32, norm_cfg=dict(type='GN', num_groups=2)) + x = torch.randn(1, 16, 56, 56) + x_out = block(x) + for m in block.modules(): + if is_norm(m): + assert isinstance(m, GroupNorm) + assert x_out.shape == torch.Size((1, 16, 56, 56)) + + # Test InvertedResidual forward with HSigmoid + block = InvertedResidual(16, 16, 32, act_cfg=dict(type='HSigmoid')) + x = torch.randn(1, 16, 56, 56) + x_out = block(x) + assert x_out.shape == torch.Size((1, 16, 56, 56)) + + # Test InvertedResidual forward with checkpoint + block = InvertedResidual(16, 16, 32, with_cp=True) + x = torch.randn(1, 16, 56, 56) + x_out = block(x) + assert block.with_cp + assert x_out.shape == torch.Size((1, 16, 56, 56)) diff --git a/tests/test_models/test_utils/test_misc.py b/tests/test_models/test_utils/test_misc.py new file mode 100644 index 00000000000..deee9a5610f --- /dev/null +++ b/tests/test_models/test_utils/test_misc.py @@ -0,0 +1,37 @@ +import pytest +import torch + +from mmcls.models.utils import channel_shuffle, make_divisible + + +def test_make_divisible(): + # test min_value is None + result = make_divisible(34, 8, None) + assert result == 32 + + # test when new_value > min_ratio * value + result = make_divisible(10, 8, min_ratio=0.9) + assert result == 16 + + # test min_value = 0.8 + result = make_divisible(33, 8, min_ratio=0.8) + assert result == 32 + + +def test_channel_shuffle(): + x = torch.randn(1, 24, 56, 56) + with pytest.raises(AssertionError): + # num_channels should be divisible by groups + channel_shuffle(x, 7) + + groups = 3 + batch_size, num_channels, height, width = x.size() + channels_per_group = num_channels // groups + out = channel_shuffle(x, groups) + # test the output value when groups = 3 + for b in range(batch_size): + for c in range(num_channels): + c_out = c % channels_per_group * groups + c // channels_per_group + for i in range(height): + for j in range(width): + assert x[b, c, i, j] == out[b, c_out, i, j] diff --git a/tests/test_models/test_utils/test_utils.py b/tests/test_models/test_utils/test_utils.py deleted file mode 100644 index 31adea3a02e..00000000000 --- a/tests/test_models/test_utils/test_utils.py +++ /dev/null @@ -1,189 +0,0 @@ -import pytest -import torch -from torch.nn.modules import GroupNorm -from torch.nn.modules.batchnorm import _BatchNorm - -from mmcls.models.backbones import VGG -from mmcls.models.utils import (Augments, HybridEmbed, InvertedResidual, - PatchEmbed, SELayer, channel_shuffle, - make_divisible) - - -def is_norm(modules): - """Check if is one of the norms.""" - if isinstance(modules, (GroupNorm, _BatchNorm)): - return True - return False - - -def test_make_divisible(): - # test min_value is None - result = make_divisible(34, 8, None) - assert result == 32 - - # test when new_value > min_ratio * value - result = make_divisible(10, 8, min_ratio=0.9) - assert result == 16 - - # test min_value = 0.8 - result = make_divisible(33, 8, min_ratio=0.8) - assert result == 32 - - -def test_channel_shuffle(): - x = torch.randn(1, 24, 56, 56) - with pytest.raises(AssertionError): - # num_channels should be divisible by groups - channel_shuffle(x, 7) - - groups = 3 - batch_size, num_channels, height, width = x.size() - channels_per_group = num_channels // groups - out = channel_shuffle(x, groups) - # test the output value when groups = 3 - for b in range(batch_size): - for c in range(num_channels): - c_out = c % channels_per_group * groups + c // channels_per_group - for i in range(height): - for j in range(width): - assert x[b, c, i, j] == out[b, c_out, i, j] - - -def test_inverted_residual(): - - with pytest.raises(AssertionError): - # stride must be in [1, 2] - InvertedResidual(16, 16, 32, stride=3) - - with pytest.raises(AssertionError): - # se_cfg must be None or dict - InvertedResidual(16, 16, 32, se_cfg=list()) - - # Add expand conv if in_channels and mid_channels is not the same - assert InvertedResidual(32, 16, 32).with_expand_conv is False - assert InvertedResidual(16, 16, 32).with_expand_conv is True - - # Test InvertedResidual forward, stride=1 - block = InvertedResidual(16, 16, 32, stride=1) - x = torch.randn(1, 16, 56, 56) - x_out = block(x) - assert getattr(block, 'se', None) is None - assert block.with_res_shortcut - assert x_out.shape == torch.Size((1, 16, 56, 56)) - - # Test InvertedResidual forward, stride=2 - block = InvertedResidual(16, 16, 32, stride=2) - x = torch.randn(1, 16, 56, 56) - x_out = block(x) - assert not block.with_res_shortcut - assert x_out.shape == torch.Size((1, 16, 28, 28)) - - # Test InvertedResidual forward with se layer - se_cfg = dict(channels=32) - block = InvertedResidual(16, 16, 32, stride=1, se_cfg=se_cfg) - x = torch.randn(1, 16, 56, 56) - x_out = block(x) - assert isinstance(block.se, SELayer) - assert x_out.shape == torch.Size((1, 16, 56, 56)) - - # Test InvertedResidual forward without expand conv - block = InvertedResidual(32, 16, 32) - x = torch.randn(1, 32, 56, 56) - x_out = block(x) - assert getattr(block, 'expand_conv', None) is None - assert x_out.shape == torch.Size((1, 16, 56, 56)) - - # Test InvertedResidual forward with GroupNorm - block = InvertedResidual( - 16, 16, 32, norm_cfg=dict(type='GN', num_groups=2)) - x = torch.randn(1, 16, 56, 56) - x_out = block(x) - for m in block.modules(): - if is_norm(m): - assert isinstance(m, GroupNorm) - assert x_out.shape == torch.Size((1, 16, 56, 56)) - - # Test InvertedResidual forward with HSigmoid - block = InvertedResidual(16, 16, 32, act_cfg=dict(type='HSigmoid')) - x = torch.randn(1, 16, 56, 56) - x_out = block(x) - assert x_out.shape == torch.Size((1, 16, 56, 56)) - - # Test InvertedResidual forward with checkpoint - block = InvertedResidual(16, 16, 32, with_cp=True) - x = torch.randn(1, 16, 56, 56) - x_out = block(x) - assert block.with_cp - assert x_out.shape == torch.Size((1, 16, 56, 56)) - - -def test_augments(): - imgs = torch.randn(4, 3, 32, 32) - labels = torch.randint(0, 10, (4, )) - - # Test cutmix - augments_cfg = dict(type='BatchCutMix', alpha=1., num_classes=10, prob=1.) - augs = Augments(augments_cfg) - mixed_imgs, mixed_labels = augs(imgs, labels) - assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) - assert mixed_labels.shape == torch.Size((4, 10)) - - # Test mixup - augments_cfg = dict(type='BatchMixup', alpha=1., num_classes=10, prob=1.) - augs = Augments(augments_cfg) - mixed_imgs, mixed_labels = augs(imgs, labels) - assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) - assert mixed_labels.shape == torch.Size((4, 10)) - - # Test cutmixup - augments_cfg = [ - dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5), - dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.3) - ] - augs = Augments(augments_cfg) - mixed_imgs, mixed_labels = augs(imgs, labels) - assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) - assert mixed_labels.shape == torch.Size((4, 10)) - - augments_cfg = [ - dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5), - dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.5) - ] - augs = Augments(augments_cfg) - mixed_imgs, mixed_labels = augs(imgs, labels) - assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) - assert mixed_labels.shape == torch.Size((4, 10)) - - augments_cfg = [ - dict(type='BatchCutMix', alpha=1., num_classes=10, prob=0.5), - dict(type='BatchMixup', alpha=1., num_classes=10, prob=0.3), - dict(type='Identity', num_classes=10, prob=0.2) - ] - augs = Augments(augments_cfg) - mixed_imgs, mixed_labels = augs(imgs, labels) - assert mixed_imgs.shape == torch.Size((4, 3, 32, 32)) - assert mixed_labels.shape == torch.Size((4, 10)) - - -def test_embed(): - - # Test PatchEmbed - patch_embed = PatchEmbed() - img = torch.randn(1, 3, 224, 224) - img = patch_embed(img) - assert img.shape == torch.Size((1, 196, 768)) - - # Test PatchEmbed with stride = 8 - conv_cfg = dict(kernel_size=16, stride=8) - patch_embed = PatchEmbed(conv_cfg=conv_cfg) - img = torch.randn(1, 3, 224, 224) - img = patch_embed(img) - assert img.shape == torch.Size((1, 729, 768)) - - # Test VGG11 HybridEmbed - backbone = VGG(11, norm_eval=True) - backbone.init_weights() - patch_embed = HybridEmbed(backbone) - img = torch.randn(1, 3, 224, 224) - img = patch_embed(img) - assert img.shape == torch.Size((1, 49, 768)) From 19e745f6eecf3d529e8aa57ba183bb5f723ef374 Mon Sep 17 00:00:00 2001 From: mzr1996 Date: Tue, 6 Jul 2021 20:19:01 +0800 Subject: [PATCH 5/7] Add unit tests of classifiers and heads --- tests/test_models/test_classifiers.py | 94 +++++++++++++++++++++++++++ tests/test_models/test_heads.py | 70 ++++++++++++++++++-- 2 files changed, 159 insertions(+), 5 deletions(-) diff --git a/tests/test_models/test_classifiers.py b/tests/test_models/test_classifiers.py index 1dbd6e7ad53..c7c3c280f4a 100644 --- a/tests/test_models/test_classifiers.py +++ b/tests/test_models/test_classifiers.py @@ -1,10 +1,102 @@ +import os.path as osp +import tempfile +from copy import deepcopy +from unittest.mock import patch + +import numpy as np +import pytest import torch +from mmcls.models import CLASSIFIERS from mmcls.models.classifiers import ImageClassifier def test_image_classifier(): + model_cfg = dict( + type='ImageClassifier', + backbone=dict( + type='ResNet_CIFAR', + depth=50, + num_stages=4, + out_indices=(3, ), + style='pytorch'), + neck=dict(type='GlobalAveragePooling'), + head=dict( + type='LinearClsHead', + num_classes=10, + in_channels=2048, + loss=dict(type='CrossEntropyLoss'))) + + imgs = torch.randn(16, 3, 32, 32) + label = torch.randint(0, 10, (16, )) + + model_cfg_ = deepcopy(model_cfg) + model = CLASSIFIERS.build(model_cfg_) + + # test property + assert model.with_neck + assert model.with_head + + # test train_step + outputs = model.train_step({'img': imgs, 'gt_label': label}, None) + assert outputs['loss'].item() > 0 + assert outputs['num_samples'] == 16 + + # test val_step + outputs = model.val_step({'img': imgs, 'gt_label': label}, None) + assert outputs['loss'].item() > 0 + assert outputs['num_samples'] == 16 + + # test forward + losses = model(imgs, return_loss=True, gt_label=label) + assert losses['loss'].item() > 0 + # test forward_test + model_cfg_ = deepcopy(model_cfg) + model = CLASSIFIERS.build(model_cfg_) + pred = model(imgs, return_loss=False, img_metas=None) + assert isinstance(pred, list) and len(pred) == 16 + + single_img = torch.randn(1, 3, 32, 32) + pred = model(single_img, return_loss=False, img_metas=None) + assert isinstance(pred, list) and len(pred) == 1 + + # test pretrained + # TODO remove deprecated pretrained + with pytest.warns(UserWarning): + model_cfg_ = deepcopy(model_cfg) + model_cfg_['pretrained'] = 'checkpoint' + model = CLASSIFIERS.build(model_cfg_) + assert model.init_cfg == dict( + type='Pretrained', checkpoint='checkpoint') + + # test show_result + img = np.random.random_integers(0, 255, (224, 224, 3)).astype(np.uint8) + result = dict(pred_class='cat', pred_label=0, pred_score=0.9) + + with tempfile.TemporaryDirectory() as tmpdir: + out_file = osp.join(tmpdir, 'out.png') + model.show_result(img, result, out_file=out_file) + assert osp.exists(out_file) + + with tempfile.TemporaryDirectory() as tmpdir: + out_file = osp.join(tmpdir, 'out.png') + model.show_result(img, result, out_file=out_file) + assert osp.exists(out_file) + + def save_show(_, *args): + out_path = osp.join(tmpdir, '_'.join([str(arg) for arg in args])) + with open(out_path, 'w') as f: + f.write('test') + + p = patch('mmcv.imshow', save_show) + p.start() + model.show_result(img, result, show=True, win_name='img', wait_time=5) + assert osp.exists(osp.join(tmpdir, 'img_5')) + p.stop() + + +def test_image_classifier_with_mixup(): # Test mixup in ImageClassifier model_cfg = dict( backbone=dict( @@ -32,6 +124,7 @@ def test_image_classifier(): assert losses['loss'].item() > 0 # Considering BC-breaking + # TODO remove deprecated mixup usage. model_cfg['train_cfg'] = dict(mixup=dict(alpha=1.0, num_classes=10)) img_classifier = ImageClassifier(**model_cfg) img_classifier.init_weights() @@ -71,6 +164,7 @@ def test_image_classifier_with_cutmix(): assert losses['loss'].item() > 0 # Considering BC-breaking + # TODO remove deprecated mixup usage. model_cfg['train_cfg'] = dict( cutmix=dict(alpha=1.0, num_classes=10, cutmix_prob=1.0)) img_classifier = ImageClassifier(**model_cfg) diff --git a/tests/test_models/test_heads.py b/tests/test_models/test_heads.py index 9fa8e77da20..f7ded535430 100644 --- a/tests/test_models/test_heads.py +++ b/tests/test_models/test_heads.py @@ -4,7 +4,8 @@ import torch from mmcls.models.heads import (ClsHead, LinearClsHead, MultiLabelClsHead, - MultiLabelLinearClsHead, StackedLinearClsHead) + MultiLabelLinearClsHead, StackedLinearClsHead, + VisionTransformerClsHead) def test_cls_head(): @@ -25,14 +26,34 @@ def test_cls_head(): losses = head.loss(fake_cls_score, fake_gt_label) assert losses['loss'].item() > 0 - # test LinearClsHead - head = LinearClsHead(10, 100) - fake_cls_score = torch.rand(4, 10) + +def test_linear_head(): + + fake_features = torch.rand(4, 100) fake_gt_label = torch.randint(0, 10, (4, )) - losses = head.loss(fake_cls_score, fake_gt_label) + # test LinearClsHead forward + head = LinearClsHead(10, 100) + losses = head.forward_train(fake_features, fake_gt_label) assert losses['loss'].item() > 0 + # test init weights + head = LinearClsHead(10, 100) + head.init_weights() + assert abs(head.fc.weight).sum() > 0 + + # test simple_test + head = LinearClsHead(10, 100) + pred = head.simple_test(fake_features) + assert isinstance(pred, list) and len(pred) == 4 + + p = patch('torch.onnx.is_in_onnx_export', lambda: True) + p.start() + head = LinearClsHead(10, 100) + pred = head.simple_test(fake_features) + assert pred.shape == (4, 10) + p.stop() + def test_multilabel_head(): head = MultiLabelClsHead() @@ -95,3 +116,42 @@ def test_stacked_linear_cls_head(): losses = head.forward_train(fake_img, fake_gt_label) assert losses['loss'].item() > 0 + + +def test_vit_head(): + fake_features = torch.rand(4, 100) + fake_gt_label = torch.randint(0, 10, (4, )) + + # test vit head forward + head = VisionTransformerClsHead(10, 100) + losses = head.forward_train(fake_features, fake_gt_label) + assert not hasattr(head.layers, 'pre_logits') + assert not hasattr(head.layers, 'act') + assert losses['loss'].item() > 0 + + # test vit head forward with hidden layer + head = VisionTransformerClsHead(10, 100, hidden_dim=20) + losses = head.forward_train(fake_features, fake_gt_label) + assert hasattr(head.layers, 'pre_logits') and hasattr(head.layers, 'act') + assert losses['loss'].item() > 0 + + # test vit head init_weights + head = VisionTransformerClsHead(10, 100, hidden_dim=20) + head.init_weights() + assert abs(head.layers.pre_logits.weight).sum() > 0 + + # test simple_test + head = VisionTransformerClsHead(10, 100, hidden_dim=20) + pred = head.simple_test(fake_features) + assert isinstance(pred, list) and len(pred) == 4 + + p = patch('torch.onnx.is_in_onnx_export', lambda: True) + p.start() + head = VisionTransformerClsHead(10, 100, hidden_dim=20) + pred = head.simple_test(fake_features) + assert pred.shape == (4, 10) + p.stop() + + # test assertion + with pytest.raises(ValueError): + VisionTransformerClsHead(-1, 100) From a209b8747dabb0bb36725802963684b7ee06b819 Mon Sep 17 00:00:00 2001 From: mzr1996 Date: Wed, 7 Jul 2021 11:19:55 +0800 Subject: [PATCH 6/7] Use patch context manager. --- tests/test_models/test_classifiers.py | 9 ++++----- tests/test_models/test_heads.py | 28 +++++++++++---------------- 2 files changed, 15 insertions(+), 22 deletions(-) diff --git a/tests/test_models/test_classifiers.py b/tests/test_models/test_classifiers.py index c7c3c280f4a..5f44612b8c1 100644 --- a/tests/test_models/test_classifiers.py +++ b/tests/test_models/test_classifiers.py @@ -89,11 +89,10 @@ def save_show(_, *args): with open(out_path, 'w') as f: f.write('test') - p = patch('mmcv.imshow', save_show) - p.start() - model.show_result(img, result, show=True, win_name='img', wait_time=5) - assert osp.exists(osp.join(tmpdir, 'img_5')) - p.stop() + with patch('mmcv.imshow', save_show): + model.show_result( + img, result, show=True, win_name='img', wait_time=5) + assert osp.exists(osp.join(tmpdir, 'img_5')) def test_image_classifier_with_mixup(): diff --git a/tests/test_models/test_heads.py b/tests/test_models/test_heads.py index f7ded535430..d0768154951 100644 --- a/tests/test_models/test_heads.py +++ b/tests/test_models/test_heads.py @@ -47,12 +47,10 @@ def test_linear_head(): pred = head.simple_test(fake_features) assert isinstance(pred, list) and len(pred) == 4 - p = patch('torch.onnx.is_in_onnx_export', lambda: True) - p.start() - head = LinearClsHead(10, 100) - pred = head.simple_test(fake_features) - assert pred.shape == (4, 10) - p.stop() + with patch('torch.onnx.is_in_onnx_export', return_value=True): + head = LinearClsHead(10, 100) + pred = head.simple_test(fake_features) + assert pred.shape == (4, 10) def test_multilabel_head(): @@ -98,11 +96,9 @@ def test_stacked_linear_cls_head(): assert len(pred) == 4 # test simple test in tracing - p = patch('torch.onnx.is_in_onnx_export', lambda: True) - p.start() - pred = head.simple_test(fake_img) - assert pred.shape == torch.Size((4, 3)) - p.stop() + with patch('torch.onnx.is_in_onnx_export', return_value=True): + pred = head.simple_test(fake_img) + assert pred.shape == torch.Size((4, 3)) # test forward with full function head = StackedLinearClsHead( @@ -145,12 +141,10 @@ def test_vit_head(): pred = head.simple_test(fake_features) assert isinstance(pred, list) and len(pred) == 4 - p = patch('torch.onnx.is_in_onnx_export', lambda: True) - p.start() - head = VisionTransformerClsHead(10, 100, hidden_dim=20) - pred = head.simple_test(fake_features) - assert pred.shape == (4, 10) - p.stop() + with patch('torch.onnx.is_in_onnx_export', return_value=True): + head = VisionTransformerClsHead(10, 100, hidden_dim=20) + pred = head.simple_test(fake_features) + assert pred.shape == (4, 10) # test assertion with pytest.raises(ValueError): From 59d654f29ab7f8aa453dc7675bf08ff7a2c6ad30 Mon Sep 17 00:00:00 2001 From: mzr1996 Date: Wed, 7 Jul 2021 12:15:39 +0800 Subject: [PATCH 7/7] Add unit test of `is_tracing`, and add warning in `is_tracing` if torch verison is smaller than 1.6.0 --- mmcls/models/utils/helpers.py | 11 +++++++++-- tests/test_models/test_utils/test_misc.py | 24 ++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/mmcls/models/utils/helpers.py b/mmcls/models/utils/helpers.py index 90af9f3accf..110c7b5c2a9 100644 --- a/mmcls/models/utils/helpers.py +++ b/mmcls/models/utils/helpers.py @@ -1,11 +1,13 @@ import collections.abc +import warnings +from distutils.version import LooseVersion from itertools import repeat import torch def is_tracing() -> bool: - if hasattr(torch.jit, 'is_tracing'): + if LooseVersion(torch.__version__) >= LooseVersion('1.6.0'): on_trace = torch.jit.is_tracing() # In PyTorch 1.6, torch.jit.is_tracing has a bug. # Refers to https://github.com/pytorch/pytorch/issues/42448 @@ -13,7 +15,12 @@ def is_tracing() -> bool: return on_trace else: return torch._C._is_tracing() - return False + else: + warnings.warn( + 'torch.jit.is_tracing is only supported after v1.6.0. ' + 'Therefore is_tracing returns False automatically. Please ' + 'set on_trace manually if you are using trace.', UserWarning) + return False # From PyTorch internals diff --git a/tests/test_models/test_utils/test_misc.py b/tests/test_models/test_utils/test_misc.py index deee9a5610f..1a431498b5b 100644 --- a/tests/test_models/test_utils/test_misc.py +++ b/tests/test_models/test_utils/test_misc.py @@ -1,7 +1,9 @@ +from distutils.version import LooseVersion + import pytest import torch -from mmcls.models.utils import channel_shuffle, make_divisible +from mmcls.models.utils import channel_shuffle, is_tracing, make_divisible def test_make_divisible(): @@ -35,3 +37,23 @@ def test_channel_shuffle(): for i in range(height): for j in range(width): assert x[b, c, i, j] == out[b, c_out, i, j] + + +@pytest.mark.skipif( + LooseVersion(torch.__version__) < LooseVersion('1.6.0'), + reason='torch.jit.is_tracing is not available before 1.6.0') +def test_is_tracing(): + + def foo(x): + if is_tracing(): + return x + else: + return x.tolist() + + x = torch.rand(3) + # test without trace + assert isinstance(foo(x), list) + + # test with trace + traced_foo = torch.jit.trace(foo, (torch.rand(1), )) + assert isinstance(traced_foo(x), torch.Tensor)