본문 바로가기
Lectures/BoostCamp -Naver

1-1. faster rcnn train/ inference코드 리뷰 & Config 수정 (부스트 캠프 제공) (부스트 캠프 제공) ( 순한맛 )

by yooom 2024. 1. 20.

목차
1. mmcv 2.x ver code flow 리뷰 ( 순한맛 )
1-1. faster rcnn train/ inference코드 리뷰 & Config 수정 (부스트 캠프 제공)
1-2. faster rcnn의 scheduler 잡지식

2. mmcv 2.x config 파일 사용법 ( 순한맛 )
2-1. cascade rcnn 사용법
2-2. ConvNext 사용법 (mask rcnn, fp16 error)

3. mmcv 외부 라이브러리 사용법 ( 안순한맛 ) // DINO, CoDETR은 MMCV 3.x ver
3-1. swin b / L 사용법
3-2. UniverseNet 사용법
3-3. FocalNet 사용법

4. Optional
4-1. wandb 사용법, 사용 안 하는 법
4-2. val 나누기 (random split, stratified group k fold)
4-3. loss function 바꾸기, cross entropy에 가중치 바꾸기, nms→soft-nms 바꾸기,
4-4. ensemble. nms→wbf로 추론 하기
4-5. correlation matrix hitmap 제작
4-6. val data에 bbox, gt_box 띄우기

 

faster_rcnn_train.ipynb 파일이다.

 

train

# 모듈 import

from mmcv import Config
from mmdet.datasets import build_dataset
from mmdet.models import build_detector
from mmdet.apis import train_detector
from mmdet.datasets import (build_dataloader, build_dataset,
                            replace_ImageToTensor)
from mmdet.utils import get_device

 

classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", 
           "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing")

# config file 들고오기
cfg = Config.fromfile('./configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py')

root='../../../dataset/'

# dataset config 수정
cfg.data.train.classes = classes
cfg.data.train.img_prefix = root
cfg.data.train.ann_file = root + 'train_fold1.json' # train json 정보
cfg.data.train.pipeline[2]['img_scale'] = (512,512) # Resize

cfg.data.val.classes = classes
cfg.data.val.img_prefix = root
cfg.data.val.ann_file = root + 'val_fold1.json' # train json 정보
cfg.data.val.pipeline[2]['img_scale'] = (512,512) # Resize

cfg.data.test.classes = classes
cfg.data.test.img_prefix = root
cfg.data.test.ann_file = root + 'test.json' # test json 정보
cfg.data.test.pipeline[1]['img_scale'] = (512,512) # Resize

cfg.data.samples_per_gpu = 16

cfg.seed = 2022
cfg.gpu_ids = [0]
cfg.work_dir = './work_dirs/faster_rcnn_r50_fpn_1x_trash'

cfg.model.roi_head.bbox_head.num_classes = 10

cfg.optimizer_config.grad_clip = dict(max_norm=35, norm_type=2)
cfg.checkpoint_config = dict(max_keep_ckpts=3, interval=1)
cfg.device = get_device()

 

# build_dataset
datasets = [build_dataset(cfg.data.train)]

 

# dataset 확인
datasets[0]

 

# 모델 build 및 pretrained network 불러오기
model = build_detector(cfg.model)
model.init_weights()

 

# 모델 학습
train_detector(model, datasets, cfg, distributed=False, validate=False)

이렇게 구성 돼 있다. 아~ 어렵다 어려워.

이게 무슨 의미인지 하나하나 보자.

 

 

###############  여기서 부터 보자  ###############

from mmcv import Config
from mmdet.datasets import build_dataset
from mmdet.models import build_detector
from mmdet.apis import train_detector
from mmdet.datasets import (build_dataloader, build_dataset,
                            replace_ImageToTensor)
from mmdet.utils import get_device



classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", 
           "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing")

# config file 들고오기
cfg = Config.fromfile('./configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py') # config파일 = 쓰기 쉽게 만들어둔 모델 = 그냥 갖다 쓰자

root='../../../dataset/'  #train, test 데이터가 있는 폴더로 지정해주고 마지막에 / 를 더해주자.

# dataset config 수정
cfg.data.train.classes = classes
cfg.data.train.img_prefix = root
cfg.data.train.ann_file = root + 'train_fold1.json' # train json 정보 : json은 어떻게 구성되는지 다음 포스팅에서 다룸
cfg.data.train.pipeline[2]['img_scale'] = (512,512) # Resize #이제 config가 뭔지 관찰해보자. 

cfg.data.test.classes = classes
cfg.data.test.img_prefix = root
cfg.data.test.ann_file = root + 'test.json' # test json 정보
cfg.data.test.pipeline[1]['img_scale'] = (512,512)# Resize
#cfg로 불러온 자료에서 data에 접근해서 test에 접근해서 
#pipeline의 두 번째 인덱스에 접근해서 'img_scale' 딕셔너리 key에 접근해서 size를 변경하겠다는 거다.

cfg.data.samples_per_gpu = 64  # batch size다. 커맨드창에 nvidia-smi를 쳐서 gpu memory를 관찰하자.

cfg.seed = 2022
cfg.gpu_ids = [0]  # 첫 번재 GPU를 쓰겠다는 뜻. 다중 GPU를 쓴다면 변경해주자.
cfg.work_dir = './work_dirs/faster_rcnn_r50_fpn_1x_trash' # epoch마다 훈련 weight 자료인 pth를 저장하는 위치다.

cfg.model.roi_head.bbox_head.num_classes = 10 # 이번 대회는 10개 class로 분류해야한다. 마지막 FN층을 정의한다.

cfg.optimizer_config.grad_clip = dict(max_norm=35, norm_type=2) # 급격한 grad를 보정해준다. 물론 35를 넘지 않는 거는 아니다. 억제하는 정도인 것 같다.
cfg.checkpoint_config = dict(max_keep_ckpts=3, interval=1) # morm_type=2는 L2 loss다. max_keep_ckpts=3는 30epoch 훈련시킨다면 28,29,30 epoch의 pth만 저장한다.
cfg.device = get_device()

이제 cfg = Config.fromfile('./configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py') 에서

이 주소로 따라 들어가서 모델이 어떻게 선언돼있는지 살펴보자.

먼저 살펴볼 곳은

cfg.model.roi_head.bbox_head.num_classes = 10 과

cfg.data.train.pipeline[2]['img_scale'] = (512,512) 부분이다.

./configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py 로 가자.

여기로 들어가면 덩그러니 이것만 적혀있다.

 

4개의 요소가 있다.

models → 모델을 저기서 정의해놨다.

datasets → 데이터 받아오는 방식

schedules → learning rate 조절, optimizer 선언

default_runtime → 음..(?) // 나중에 val 나눌 때 접근할 일이 생긴다.

 

cfg.model.roi_head.bbox_head.num_classes = 10 이걸 보면, model에 접근한다.

 models에 들어가자

여기서 model에서 roi_head에 접근해서 bbox_head에 접근해서 num_classes의 값을 10으로 바꾸는 거다.

밑에 코드를 하나씩 따라가보자.

뭔가 많이 선언 돼있다.

# model settings
model = dict(
    type='FasterRCNN',
    backbone=dict(
        type='ResNet',
        depth=50,
        num_stages=4,
        out_indices=(0, 1, 2, 3),
        frozen_stages=1,
        norm_cfg=dict(type='BN', requires_grad=True),
        norm_eval=True,
        style='pytorch',
        init_cfg=dict(type='Pretrained', checkpoint='torchvision://resnet50')),
    neck=dict(
        type='FPN',
        in_channels=[256, 512, 1024, 2048],
        out_channels=256,
        num_outs=5),
    rpn_head=dict(
        type='RPNHead',
        in_channels=256,
        feat_channels=256,
        anchor_generator=dict(
            type='AnchorGenerator',
            scales=[8],
            ratios=[0.5, 1.0, 2.0],
            strides=[4, 8, 16, 32, 64]),
        bbox_coder=dict(
            type='DeltaXYWHBBoxCoder',
            target_means=[.0, .0, .0, .0],
            target_stds=[1.0, 1.0, 1.0, 1.0]),
        loss_cls=dict(
            type='CrossEntropyLoss', use_sigmoid=True, loss_weight=1.0),
        loss_bbox=dict(type='L1Loss', loss_weight=1.0)),
    roi_head=dict(
        type='StandardRoIHead',
        bbox_roi_extractor=dict(
            type='SingleRoIExtractor',
            roi_layer=dict(type='RoIAlign', output_size=7, sampling_ratio=0),
            out_channels=256,
            featmap_strides=[4, 8, 16, 32]),
        bbox_head=dict(
            type='Shared2FCBBoxHead',
            in_channels=256,
            fc_out_channels=1024,
            roi_feat_size=7,
            num_classes=80,         ######## 바꿔야하는 값 ########
            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,
            loss_cls=dict(
                type='CrossEntropyLoss', use_sigmoid=False, loss_weight=1.0),
            loss_bbox=dict(type='L1Loss', loss_weight=1.0))),
    # model training and testing settings
    train_cfg=dict(
        rpn=dict(
            assigner=dict(
                type='MaxIoUAssigner',
                pos_iou_thr=0.7,
                neg_iou_thr=0.3,
                min_pos_iou=0.3,
                match_low_quality=True,
                ignore_iof_thr=-1),
            sampler=dict(
                type='RandomSampler',
                num=256,
                pos_fraction=0.5,
                neg_pos_ub=-1,
                add_gt_as_proposals=False),
            allowed_border=-1,
            pos_weight=-1,
            debug=False),
        rpn_proposal=dict(
            nms_pre=2000,
            max_per_img=1000,
            nms=dict(type='nms', iou_threshold=0.7),
            min_bbox_size=0),
        rcnn=dict(
            assigner=dict(
                type='MaxIoUAssigner',
                pos_iou_thr=0.5,
                neg_iou_thr=0.5,
                min_pos_iou=0.5,
                match_low_quality=False,
                ignore_iof_thr=-1),
            sampler=dict(
                type='RandomSampler',
                num=512,
                pos_fraction=0.25,
                neg_pos_ub=-1,
                add_gt_as_proposals=True),
            pos_weight=-1,
            debug=False)),
    test_cfg=dict(
        rpn=dict(
            nms_pre=1000,
            max_per_img=1000,
            nms=dict(type='nms', iou_threshold=0.7),
            min_bbox_size=0),
        rcnn=dict(
            score_thr=0.05,
            nms=dict(type='nms', iou_threshold=0.5),
            max_per_img=100)
        # soft-nms is also supported for rcnn testing
        # e.g., nms=dict(type='soft_nms', iou_threshold=0.5, min_score=0.05)
    ))

이제 대충 코드가 눈에 보일 것 같다.

 

num_classes는 바꿨고

cfg.data.train.pipeline[2]['img_scale'] = (512,512) 이걸 찾는데 없다.

이번에는 base에 있는 datasets에 들어가자

_base_ 폴더에 있는 datasets, models, schedules가 중요하다.

 

바로 img_scale을 선언하는 부분이 있다. 그런데 어떻게 접근하는지 잘 보자.

# dataset settings
dataset_type = 'CocoDataset'
data_root = 'data/coco/'
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),
    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']),
]

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,
        ann_file=data_root + 'annotations/instances_train2017.json',
        img_prefix=data_root + 'train2017/', #### cfg 로 이 부분도 수정하고 있을 거다! ####
        pipeline=train_pipeline),
    val=dict(
        type=dataset_type,
        ann_file=data_root + 'annotations/instances_val2017.json',
        img_prefix=data_root + 'val2017/',
        pipeline=test_pipeline),
    test=dict(
        type=dataset_type,
        ann_file=data_root + 'annotations/instances_val2017.json',
        img_prefix=data_root + 'val2017/',
        pipeline=test_pipeline))
evaluation = dict(interval=1, metric='bbox')

 

나는 멍청해서 이것도 이해하는데 한참 걸렸었다.

나같은 사람은 세상에 많을 테니 그림으로 하나 더 얹어놨다.

cfg.data.train.pipeline[2]['img_scale'] = (512,512) 이제 이해할 수 있다 !
cfg로 faster_rcnn_r50_fpn_1x_coco.py 파일에 접근했고
_base_로 coco_detection.py 파일에 접근해서
data에 접근하고 

train에 접근하고

pipeline = train_pipeline에 접근해서

리스트 2번째 딕셔너리에 접근해서
'img_scale' key에 접근하는 거다.

 

이제 모델 수정 부분은 마쳤다.

 

이제 daseline의 아래쪽을 좀 더 보자

datasets = [build_dataset(cfg.data.train)] # data.train에 저장된 root경로와 augmentation 방법을 가져온다

datasets[0]

model = build_detector(cfg.model)
model.init_weights() # pretrain 된 pth를 가져오는데 모델과 pth의 깊이나 형태가 다르면 메세지가 뜬다

train_detector(model, datasets[0], cfg, distributed=False, validate=False) 이걸 실행하면 훈련된다.

 

순한맛 faster rcnn train의 코드 리뷰, config 뜯어보기는 끝

 

 

이번에는 inference를 보자

inference

import mmcv
from mmcv import Config
from mmdet.datasets import (build_dataloader, build_dataset,
                            replace_ImageToTensor)
from mmdet.models import build_detector
from mmdet.apis import single_gpu_test
from mmcv.runner import load_checkpoint
import os
from mmcv.parallel import MMDataParallel
import pandas as pd
from pandas import DataFrame
from pycocotools.coco import COCO
import numpy as np



classes = ("General trash", "Paper", "Paper pack", "Metal", "Glass", 
           "Plastic", "Styrofoam", "Plastic bag", "Battery", "Clothing")

# config file 들고오기
cfg = Config.fromfile('./configs/faster_rcnn/faster_rcnn_r50_fpn_1x_coco.py')  # 같은 cfg를 불러온다

root='../../../dataset/'

epoch = 'latest' # 이건 './work_dirs/faster_rcnn_r50_fpn_1x_trash' 이 주소에 저장된 pth의 이름 !

# dataset config 수정
cfg.data.test.classes = classes
cfg.data.test.img_prefix = root
cfg.data.test.ann_file = root + 'test.json'
cfg.data.test.pipeline[1]['img_scale'] = (512,512) # Resize
cfg.data.test.test_mode = True

cfg.data.samples_per_gpu = 4

cfg.seed=2021
cfg.gpu_ids = [1]
cfg.work_dir = './work_dirs/faster_rcnn_r50_fpn_1x_trash'

cfg.model.roi_head.bbox_head.num_classes = 10

cfg.optimizer_config.grad_clip = dict(max_norm=35, norm_type=2)
cfg.model.train_cfg = None


# build dataset & dataloader
dataset = build_dataset(cfg.data.test)
data_loader = build_dataloader(
        dataset,
        samples_per_gpu=1,
        workers_per_gpu=cfg.data.workers_per_gpu,
        dist=False,
        shuffle=False)
        
        
# checkpoint path
checkpoint_path = os.path.join(cfg.work_dir, f'{epoch}.pth') # 여기서 원하는 pth파일이 들어가진다!

model = build_detector(cfg.model, test_cfg=cfg.get('test_cfg')) # build detector
checkpoint = load_checkpoint(model, checkpoint_path, map_location='cpu') # ckpt load

model.CLASSES = dataset.CLASSES
model = MMDataParallel(model.cuda(), device_ids=[0])

output = single_gpu_test(model, data_loader, show_score_thr=0.05)

설명할 게 없다. 끝

728x90

댓글