[Fix]: Fix multi-head config

pull/352/head
YuanLiuuuuuu 2022-07-06 08:18:46 +00:00 committed by fangyixiao18
parent 1e16016b27
commit 8910743c6e
5 changed files with 115 additions and 64 deletions

View File

@ -1,5 +1,10 @@
model = dict( model = dict(
type='Classification', type='ImageClassifier',
data_preprocessor=dict(
mean=[123.675, 116.28, 103.53],
std=[58.395, 57.12, 57.375],
to_rgb=True,
),
backbone=dict( backbone=dict(
type='ResNet', type='ResNet',
depth=50, depth=50,
@ -8,7 +13,7 @@ model = dict(
norm_cfg=dict(type='BN'), norm_cfg=dict(type='BN'),
frozen_stages=-1), frozen_stages=-1),
head=dict( head=dict(
type='MultiClsHead', type='mmselfsup.MultiClsHead',
pool_type='specified', pool_type='specified',
in_indices=[0, 1, 2, 3, 4], in_indices=[0, 1, 2, 3, 4],
with_last_layer_unpool=False, with_last_layer_unpool=False,

View File

@ -4,53 +4,77 @@ _base_ = [
'../_base_/schedules/sgd_steplr-100e.py', '../_base_/schedules/sgd_steplr-100e.py',
'../_base_/default_runtime.py', '../_base_/default_runtime.py',
] ]
# Multi-head linear evaluation setting
model = dict(backbone=dict(frozen_stages=4)) # lighting params, in order of BGR
EIGVAL = [55.4625, 4.7940, 1.1475]
EIGVEC = [
[-0.5836, -0.6948, 0.4203],
[-0.5808, -0.0045, -0.8140],
[-0.5675, 0.7192, 0.4009],
]
# dataset settings # dataset
img_norm_cfg = dict(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
train_pipeline = [ train_pipeline = [
dict(type='RandomResizedCrop', size=224), dict(type='LoadImageFromFile'),
dict(type='RandomHorizontalFlip'), dict(
type='RandomResizedCrop',
scale=224,
backend='pillow',
interpolation='bicubic'),
dict(type='RandomFlip', prob=0.5, direction='horizontal'),
dict( dict(
type='ColorJitter', type='ColorJitter',
brightness=0.4, brightness=0.4,
contrast=0.4, contrast=0.4,
saturation=0.4, saturation=0.4,
hue=0.), hue=0.),
dict(type='ToTensor'), dict(
dict(type='Lighting'), type='Lighting',
dict(type='Normalize', **img_norm_cfg), eigval=EIGVAL,
eigvec=EIGVEC,
),
dict(type='PackClsInputs')
] ]
test_pipeline = [ test_pipeline = [
dict(type='Resize', size=256), dict(type='LoadImageFromFile'),
dict(type='CenterCrop', size=224), dict(
dict(type='ToTensor'), type='ResizeEdge',
dict(type='Normalize', **img_norm_cfg), scale=256,
edge='short',
backend='pillow',
interpolation='bicubic'),
dict(type='CenterCrop', crop_size=224),
dict(type='PackClsInputs')
] ]
data = dict( train_dataloader = dict(dataset=dict(pipeline=train_pipeline))
train=dict(pipeline=train_pipeline), val=dict(pipeline=test_pipeline)) val_dataloader = dict(dataset=dict(pipeline=test_pipeline))
# MoCo v1/v2 linear evaluation setting
model = dict(
backbone=dict(out_indices=[0, 1, 2, 3], frozen_stages=4),
head=dict(in_indices=[1, 2, 3, 4]))
# optimizer # optimizer
optimizer = dict( optimizer = dict(
type='SGD', type='SGD', lr=0.01, momentum=0.9, weight_decay=1e-4, nesterov=True)
lr=0.01, optim_wrapper = dict(
momentum=0.9, type='OptimWrapper',
weight_decay=1e-4, optimizer=optimizer,
paramwise_options=dict(norm_decay_mult=0.), paramwise_cfg=dict(norm_decay_mult=0.0))
nesterov=True)
# learning rate scheduler
param_scheduler = [
dict(
type='MultiStepLR', by_epoch=True, milestones=[30, 60, 90], gamma=0.1)
]
# runtime settings # runtime settings
runner = dict(type='EpochBasedRunner', max_epochs=90) default_hooks = dict(
checkpoint=dict(type='CheckpointHook', interval=10, max_keep_ckpts=3))
# the max_keep_ckpts controls the max number of ckpt file in your work_dirs # evaluator
# if it is 3, when CheckpointHook (in mmcv) saves the 4th ckpt val_evaluator = dict(
# it will remove the oldest one to keep the number of total ckpts as 3 _delete_=True,
checkpoint_config = dict(interval=10, max_keep_ckpts=3) type='MultiHeadEvaluator',
metrics=dict(
head1=dict(type='mmcls.Accuracy', topk=(1, 5)),
head2=dict(type='mmcls.Accuracy', topk=(1, 5)),
head3=dict(type='mmcls.Accuracy', topk=(1, 5)),
head4=dict(type='mmcls.Accuracy', topk=(1, 5))))
# epochs
train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=90)

View File

@ -1,5 +1,5 @@
# Copyright (c) OpenMMLab. All rights reserved. # Copyright (c) OpenMMLab. All rights reserved.
from typing import Dict, List, Optional, Sequence, Union from typing import Dict, List, Sequence, Tuple, Union
import torch import torch
import torch.nn as nn import torch.nn as nn
@ -22,20 +22,29 @@ class MultiClsHead(ClsHead):
linear classifier at each stage to predict corresponding class scores. linear classifier at each stage to predict corresponding class scores.
Args: Args:
backbone (str, optional): Specify which backbone to use, only support backbone (str): Specify which backbone to use, only support
ResNet50. Defaults to 'resnet50'. ResNet50. Defaults to 'resnet50'.
in_indices (Sequence[int], optional): Input from which stages. in_indices (Sequence[int]): Input from which stages.
Defaults to (0, 1, 2, 3, 4). Defaults to (0, 1, 2, 3, 4).
pool_type (str, optional): 'adaptive' or 'specified'. If set to pool_type (str): 'adaptive' or 'specified'. If set to
'adaptive', use adaptive average pooling, otherwise use specified 'adaptive', use adaptive average pooling, otherwise use specified
pooling params. Defaults to 'adaptive'. pooling params. Defaults to 'adaptive'.
num_classes (int, optional): Number of classes. Defaults to 1000. num_classes (int): Number of classes. Defaults to 1000.
loss (Dict, optional): The Dict of loss information. Defaults to loss (dict): The Dict of loss information. Defaults to
'mmcls.models.CrossEntropyLoss' 'mmcls.models.CrossEntro): Whether to unpool the features
with_last_layer_unpool (bool, optional): Whether to unpool the features
from last layer. Defaults to False. from last layer. Defaults to False.
norm_cfg (Dict, optional): Dict to construct and config norm layer. cal_acc (bool): Whether to calculate accuracy during training.
init_cfg (Dict or List[Dict], optional): Initialization config dict. If you use batch augmentations like Mixup and CutMix during
training, it is pointless to calculate accuracy.
Defaults to False.
topk (int | Tuple[int]): Top-k accuracy. Defaults to ``(1, )``.
norm_cfg (dict): Dict to construct and config norm layer.
Defaults to ``dict(type='BN')``.
init_cfg (dict or List[dict]): Initialization config dict.
Defaults to ``[
dict(type='Normal', std=0.01, layer='Linear'),
dict(type='Constant', val=1, layer=['_BatchNorm', 'GroupNorm'])
]``
""" """
FEAT_CHANNELS = {'resnet50': [64, 256, 512, 1024, 2048]} FEAT_CHANNELS = {'resnet50': [64, 256, 512, 1024, 2048]}
@ -43,15 +52,16 @@ class MultiClsHead(ClsHead):
def __init__( def __init__(
self, self,
backbone: Optional[str] = 'resnet50', backbone: str = 'resnet50',
in_indices: Optional[Sequence[int]] = (0, 1, 2, 3, 4), in_indices: Sequence[int] = (0, 1, 2, 3, 4),
pool_type: Optional[str] = 'adaptive', pool_type: str = 'adaptive',
num_classes: Optional[int] = 1000, num_classes: int = 1000,
loss: Optional[Dict] = dict( loss: dict = dict(type='mmcls.CrossEntropyLoss', loss_weight=1.0),
type='mmcls.CrossEntropyLoss', loss_weight=1.0), with_last_layer_unpool: bool = False,
with_last_layer_unpool: Optional[bool] = False, cal_acc: bool = False,
norm_cfg: Optional[Dict] = dict(type='BN'), topk: Union[int, Tuple[int]] = (1, ),
init_cfg: Optional[Union[Dict, List[Dict]]] = [ norm_cfg: dict = dict(type='BN'),
init_cfg: Union[Dict, List[Dict]] = [
dict(type='Normal', std=0.01, layer='Linear'), dict(type='Normal', std=0.01, layer='Linear'),
dict(type='Constant', val=1, layer=['_BatchNorm', 'GroupNorm']) dict(type='Constant', val=1, layer=['_BatchNorm', 'GroupNorm'])
] ]
@ -80,10 +90,12 @@ class MultiClsHead(ClsHead):
for i in in_indices for i in in_indices
]) ])
self.cal_acc = cal_acc
self.topk = topk
# build loss # build loss
self.loss_module = MODELS.build(loss) self.loss_module = MODELS.build(loss)
def forward(self, feats): def forward(self, feats: Union[list, tuple]) -> list:
"""Compute multi-head scores. """Compute multi-head scores.
Args: Args:
@ -96,6 +108,7 @@ class MultiClsHead(ClsHead):
assert isinstance(feats, (list, tuple)) assert isinstance(feats, (list, tuple))
if self.with_last_layer_unpool: if self.with_last_layer_unpool:
last_feats = feats[-1] last_feats = feats[-1]
feats = self.multi_pooling(feats) feats = self.multi_pooling(feats)
if self.with_norm: if self.with_norm:
@ -109,7 +122,7 @@ class MultiClsHead(ClsHead):
return cls_score return cls_score
def loss(self, feats: Sequence[torch.Tensor], def loss(self, feats: Sequence[torch.Tensor],
data_samples: List[ClsDataSample], **kwargs) -> Dict: data_samples: List[ClsDataSample], **kwargs) -> dict:
"""Calculate losses from the extracted features. """Calculate losses from the extracted features.
Args: Args:
@ -131,11 +144,17 @@ class MultiClsHead(ClsHead):
target = torch.hstack( target = torch.hstack(
[data_sample.gt_label.label for data_sample in data_samples]) [data_sample.gt_label.label for data_sample in data_samples])
# compute loss # compute loss and accuracy
losses = dict() losses = dict()
for i, score in zip(self.in_indices, cls_score): for i, score in zip(self.in_indices, cls_score):
losses[f'loss.{i + 1}'] = self.loss_module(score, target) losses[f'loss.{i + 1}'] = self.loss_module(score, target)
losses[f'accuracy.{i + 1}'] = Accuracy.calculate(score, target) if self.cal_acc:
acc = Accuracy.calculate(score, target, topk=self.topk)
losses.update({
f'accuracy.{i+1}.top-{k}': a
for k, a in zip(self.topk, acc)
})
return losses return losses
def predict(self, feats: Sequence[torch.Tensor], def predict(self, feats: Sequence[torch.Tensor],

View File

@ -1,6 +1,8 @@
# Copyright (c) OpenMMLab. All rights reserved. # Copyright (c) OpenMMLab. All rights reserved.
from typing import List, Tuple, Union
import torch.nn as nn import torch.nn as nn
from mmcv.runner import BaseModule from mmengine.model import BaseModule
class MultiPooling(BaseModule): class MultiPooling(BaseModule):
@ -27,12 +29,13 @@ class MultiPooling(BaseModule):
POOL_DIMS = {'resnet50': [9216, 9216, 8192, 9216, 8192]} POOL_DIMS = {'resnet50': [9216, 9216, 8192, 9216, 8192]}
def __init__(self, def __init__(self,
pool_type='adaptive', pool_type: str = 'adaptive',
in_indices=(0, ), in_indices: tuple = (0, ),
backbone='resnet50'): backbone: str = 'resnet50') -> None:
super(MultiPooling, self).__init__() super().__init__()
assert pool_type in ['adaptive', 'specified'] assert pool_type in ['adaptive', 'specified']
assert backbone == 'resnet50', 'Now only support resnet50.' assert backbone == 'resnet50', 'Now only support resnet50.'
if pool_type == 'adaptive': if pool_type == 'adaptive':
self.pools = nn.ModuleList([ self.pools = nn.ModuleList([
nn.AdaptiveAvgPool2d(self.POOL_SIZES[backbone][i]) nn.AdaptiveAvgPool2d(self.POOL_SIZES[backbone][i])
@ -44,6 +47,6 @@ class MultiPooling(BaseModule):
for i in in_indices for i in in_indices
]) ])
def forward(self, x): def forward(self, x: Union[List, Tuple]) -> None:
assert isinstance(x, (list, tuple)) assert isinstance(x, (list, tuple))
return [p(xx) for p, xx in zip(self.pools, x)] return [p(xx) for p, xx in zip(self.pools, x)]

View File

@ -18,7 +18,7 @@ class TestMultiClsHead(TestCase):
fake_data_samples = [ClsDataSample().set_gt_label(1) for _ in range(2)] fake_data_samples = [ClsDataSample().set_gt_label(1) for _ in range(2)]
losses = head.loss(fake_in, fake_data_samples) losses = head.loss(fake_in, fake_data_samples)
print(losses) print(losses)
self.assertEqual(len(losses.keys()), 4) self.assertEqual(len(losses.keys()), 2)
for k in losses.keys(): for k in losses.keys():
assert k.startswith('loss') or k.startswith('accuracy') assert k.startswith('loss') or k.startswith('accuracy')
if k.startswith('loss'): if k.startswith('loss'):