Swin OD model Training : using mmdet

4 분 소요

Swin Transformer - Object Detection

수행 환경

  • python 3.7 (anaconda)
  • cuda 10.1
  • torch 1.7.0

custom dataset

  • num_classes : 11
  • coco dataset format

Setting

  • https://github.com/SwinTransformer/Swin-Transformer-Object-Detection

    • 위 코드를 활용하여 모델 학습 및 테스트
# bash

# 선택 : venv 사용
python -m venv venv_swin
source venv_swin/bin/activate

# git clone
git clone https://github.com/SwinTransformer/Swin-Transformer-Object-Detection.git

# install packages in local
cd Swin-Transformer-Object-Detection
python setup.py develop

# install custom pycocotools
pip uninstall pycocotools -y
pip install mmpycocotools

# install mmcv
pip install mmcv-full

# 그런데 이후에 mmcv에서 cuda compiler를 찾지 못하는 오류 발생
# 이 때 환경이 아나콘다여서 conda로 간단히 해결
conda install cudatoolkit=11.0 -c pytorch -y

# 안 될 경우 시도
# pip install --no-cache-dir mmcv-full -f https://download.openmmlab.com/mmcv/dist/cu110/torch1.7.0/index.html

Set config

  • mmdet을 사용하기 위해서는, config를 변경해야 한다.
  • https://mmdetection.readthedocs.io/en/latest/tutorials/config.html
    • 요기 세부 내용이 정말 잘 정리되어 있다. 상세하고, 강력하다.
      • 사실 여기 보고 하는게 더 좋은거 같긴 한데… 영어가 눈에 안 들어오는 날도 있으니까 글을 작성한다. (사실은 혹시 다음에 쓸 때 나 편하려고)
  • 이번 모델 학습을 위해 내가 수정해야 하는 config는 크게 두개이다.
    • Swin-Transformer-Object-Detection/configs/swin/cascade_mask_rcnn_swin_small_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py
      • 전체 training을 관장하는 config
    • Swin-Transformer-Object-Detection/configs/base/datasets/custom_instance.py
      • coco dataset 형식로 제작된 custom dataset을 활용하기 위한 config

Config

# python

# Swin-Transformer-Object-Detection/configs/swin/cascade_mask_rcnn_swin_small_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py

_base_ = [
    # 기존에 작성된 base config를 상속받는 식으로 mmdet은 구성되어 있음.
    # 각 dict에 _delete_=True를 설정하지 않으면, base config의 내용이 구성되고, 그 위에 아래 config가 덮어씌워짐. 만약, 덮어 씌워지지 않는 base config에서 정의된 값은 "없어지지 않고 남아있다." => 삽질하기 쉬움
    '../_base_/models/cascade_mask_rcnn_swin_fpn.py',
    '../_base_/datasets/custom_instance.py',
    '../_base_/schedules/schedule_1x.py', '../_base_/default_runtime.py'
]

model = dict(
    backbone=dict(
        embed_dim=96,
        depths=[2, 2, 18, 2],
        num_heads=[3, 6, 12, 24],
        window_size=7,
        ape=False,
        drop_path_rate=0.2,
        patch_norm=True,
        use_checkpoint=False
    ),
    neck=dict(in_channels=[96, 192, 384, 768]),
    roi_head=dict(
        bbox_head=[
            dict(
                type='ConvFCBBoxHead',
                num_shared_convs=4,
                num_shared_fcs=1,
                in_channels=256,
                conv_out_channels=256,
                fc_out_channels=1024,
                roi_feat_size=7,
                # num_classes 수정
                num_classes=11,
                bbox_coder=dict(
                    type='DeltaXYWHBBoxCoder',
                    target_means=[0., 0., 0., 0.],
                    target_stds=[0.1, 0.1, 0.2, 0.2]),
                reg_class_agnostic=False,
                reg_decoded_bbox=True,
                # Single GPU : BNSync => BN
                norm_cfg=dict(type='BN', requires_grad=True),
                # batch size를 2로만 설정해서 VRAM 16GB인 T4가 전부 점유되는 모델이므로, Group Normalization을 사용하는 것도 나쁘지 않아보임.
                # norm_cfg=dict(type='GN', num_groups=16, requires_grad=True),
                loss_cls=dict(
                    type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
                loss_bbox=dict(type='GIoULoss', loss_weight=10.0)),
            dict(
                type='ConvFCBBoxHead',
                num_shared_convs=4,
                num_shared_fcs=1,
                in_channels=256,
                conv_out_channels=256,
                fc_out_channels=1024,
                roi_feat_size=7,
                # num_classes 수정
                num_classes=11,
                bbox_coder=dict(
                    type='DeltaXYWHBBoxCoder',
                    target_means=[0., 0., 0., 0.],
                    target_stds=[0.05, 0.05, 0.1, 0.1]),
                reg_class_agnostic=False,
                reg_decoded_bbox=True,
                # Single GPU : BNSync => BN
                norm_cfg=dict(type='BN', requires_grad=True),
                # batch size를 2로만 설정해서 VRAM 16GB인 T4가 전부 점유되는 모델이므로, Group Normalization을 사용하는 것도 나쁘지 않아보임.
                # norm_cfg=dict(type='GN', num_groups=16, requires_grad=True),
                loss_cls=dict(
                    type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
                loss_bbox=dict(type='GIoULoss', loss_weight=10.0)),
            dict(
                type='ConvFCBBoxHead',
                num_shared_convs=4,
                num_shared_fcs=1,
                in_channels=256,
                conv_out_channels=256,
                fc_out_channels=1024,
                roi_feat_size=7,
                # num_classes 수정
                num_classes=11,
                bbox_coder=dict(
                    type='DeltaXYWHBBoxCoder',
                    target_means=[0., 0., 0., 0.],
                    target_stds=[0.033, 0.033, 0.067, 0.067]),
                reg_class_agnostic=False,
                reg_decoded_bbox=True,
                # Single GPU : BNSync => BN
                norm_cfg=dict(type='BN', requires_grad=True),
                # batch size를 2로만 설정해서 VRAM 16GB인 T4가 전부 점유되는 모델이므로, Group Normalization을 사용하는 것도 나쁘지 않아보임.
                # norm_cfg=dict(type='GN', num_groups=16, requires_grad=True),
                loss_cls=dict(
                    type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
                loss_bbox=dict(type='GIoULoss', loss_weight=10.0))],
        # 이 부분 놓치면 계속 에러 발생
        mask_head=dict(num_classes=11)
    ))

img_norm_cfg = dict(
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)

# augmentation strategy originates from DETR / Sparse RCNN
train_pipeline = [
    dict(type='LoadImageFromFile'),
    dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
    dict(type='RandomFlip', flip_ratio=0.5),
    dict(type='AutoAugment',
         policies=[
             [
                 dict(type='Resize',
                      img_scale=[(480, 1333), (512, 1333), (544, 1333), (576, 1333),
                                 (608, 1333), (640, 1333), (672, 1333), (704, 1333),
                                 (736, 1333), (768, 1333), (800, 1333)],
                      multiscale_mode='value',
                      keep_ratio=True)
             ],
             [
                 dict(type='Resize',
                      img_scale=[(400, 1333), (500, 1333), (600, 1333)],
                      multiscale_mode='value',
                      keep_ratio=True),
                 dict(type='RandomCrop',
                      crop_type='absolute_range',
                      crop_size=(384, 600),
                      allow_negative_crop=True),
                 dict(type='Resize',
                      img_scale=[(480, 1333), (512, 1333), (544, 1333),
                                 (576, 1333), (608, 1333), (640, 1333),
                                 (672, 1333), (704, 1333), (736, 1333),
                                 (768, 1333), (800, 1333)],
                      multiscale_mode='value',
                      override=True,
                      keep_ratio=True)
             ]
         ]),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='Pad', size_divisor=32),
    dict(type='DefaultFormatBundle'),
    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
]
data = dict(train=dict(pipeline=train_pipeline))

checkpoint_config = dict(interval=1, out_dir='models/cascade_swin_s_2nd')

optimizer = dict(_delete_=True, type='AdamW', lr=0.0001, betas=(0.9, 0.999), weight_decay=0.05,
                 paramwise_cfg=dict(custom_keys={'absolute_pos_embed': dict(decay_mult=0.),
                                                 'relative_position_bias_table': dict(decay_mult=0.),
                                                 'norm': dict(decay_mult=0.)}))
lr_config = dict(step=[27, 33])
runner = dict(type='EpochBasedRunner', max_epochs=20)

# NVIDIA APEX 사용이 불가하면 아래 부분 주석처리 필요
# V100 이상 GPU 사용시 FP32 / FP16을 섞어 손쉽게 효율적으로 학습 수행

# do not use mmdet version fp16
# fp16 = None
# optimizer_config = dict(
#     type="DistOptimizerHook",
#     update_interval=1,
#     grad_clip=None,
#     coalesce=True,
#     bucket_size_mb=-1,
#     use_fp16=True,
# )

Config : Custom data

# python

dataset_type = 'CocoDataset'

# 11 classes
classes = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k')

# dataset path
data_root = 'data/custom_dataset/'
img_norm_cfg = dict(
    mean=[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375], to_rgb=True)
train_pipeline = [
    dict(type='LoadImageFromFile'),
    dict(type='LoadAnnotations', with_bbox=True, with_mask=True),
    dict(type='Resize', img_scale=(1333, 800), keep_ratio=True),
    dict(type='RandomFlip', flip_ratio=0.5),
    dict(type='Normalize', **img_norm_cfg),
    dict(type='Pad', size_divisor=32),
    dict(type='DefaultFormatBundle'),
    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels', 'gt_masks']),
]
test_pipeline = [
    dict(type='LoadImageFromFile'),
    dict(
        type='MultiScaleFlipAug',
        img_scale=(1333, 800),
        flip=False,
        transforms=[
            dict(type='Resize', keep_ratio=True),
            dict(type='RandomFlip'),
            dict(type='Normalize', **img_norm_cfg),
            dict(type='Pad', size_divisor=32),
            dict(type='ImageToTensor', keys=['img']),
            dict(type='Collect', keys=['img']),
        ])
]
data = dict(
    samples_per_gpu=2,
    workers_per_gpu=2,
    train=dict(
        type=dataset_type,
        classes=classes,

        ann_file=data_root + 'annotations/train_annotations.json',
        img_prefix=data_root + 'train/',
        pipeline=train_pipeline),
    val=dict(
        type=dataset_type,
        classes=classes,
        ann_file=data_root + 'annotations/valid_annotations.json',
        img_prefix=data_root + 'val/',
        pipeline=test_pipeline),
    test=dict(
        type=dataset_type,
        classes=classes,
        ann_file=data_root + 'annotations/test_annotations.json',
        img_prefix=data_root + 'test/',
        pipeline=test_pipeline))

evaluation = dict(metric=['bbox', 'segm'])

Train

  • swin-small pretrained weight 활용
# bash

# download pretrained model
wget https://github.com/SwinTransformer/storage/releases/download/v1.0.0/swin_small_patch4_window7_224.pth

# 리눅스 쉘 변수 할당
CFG_FILE_PATH=configs/swin/cascade_mask_rcnn_swin_small_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py
DET_CKPT_FILE_PATH=swin_small_patch4_window7_224.pth

# train!
python tools/train.py $CFG_FILE_PATH --cfg-options model.pretrained=$DET_CKPT_FILE_PATH

결과 확인

  • log는 work_dir/ 이하에서 확인 가능

Inference

# python

from mmdet.apis import init_detector, inference_detector, show_result_pyplot
import mmcv

# 실행 Dir : Swin-Transformer-Object-Detection/
# 위에서 설정한 config
config_file = 'configs/swin/cascade_mask_rcnn_swin_small_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py'
#
checkpoint_file = 'models/cascade_swin_s_2nd/epoch_12.pth'


# build the model from a config file and a checkpoint file
model = init_detector(config_file, checkpoint_file, device='cuda:0')

img = 'test.jpg'

# 결과 확인 가능
result = inference_detector(model, img)

# result 시각화
show_result_pyplot(model, img, result)

사용 / 글 작성 후기

  • 사실 압도적인 시간을 소요하는 부분은 custom dataset 제작 소요시간이었음.
    • (+ 환경 구축 : cuda + 기타 등등)
  • GPU 하나 두고 가상환경마다 다른 버전 cuda를 사용할 수 있게 설정할 수도 있다고 하던데.. 찾아봐야겠다.
  • 위 config의 schedule이 36에폭까지 수행하는 것으로 설정되어 있었지만, 시간의 압박으로 12에폭까지만 일단 수행하였다.
    • (데이터셋 약 15000장, batch size 2로 수행하였으나 1에폭당 약 3시간 소요..)
  • 36에폭까지는 도저히 시간이 안될 것 같아서 차라리 swin-tiny 기반인 모델을 학습시킬까 고민 중.
    • https://github.com/SwinTransformer/Swin-Transformer-Object-Detection#cascade-mask-r-cnn
    • Lr Schd이 1x => 12 epoch / 3x => 36 epoch

댓글남기기