Swin OD model Training : using mmdet
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
- Swin-Transformer-Object-Detection/configs/swin/cascade_mask_rcnn_swin_small_patch4_window7_mstrain_480-800_giou_4conv1f_adamw_3x_coco.py
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
댓글남기기