Intro
61번이라는 엄청난 제출 횟수에도 불구하고 아쉬움이 많이 남은 대회였다.
이번글을 통해서 코드를 살펴보고 어떤 부분을 추후에 보완하면 좋을지 생각해보려고 한다.
우선 대회 결과는 다음과 같다.
🚩Public score
🚩Private score
아쉽지만 목표한 상위 10%안에 들어가지 못했다.
아래는 대회를 위해서 작성한 코드 및 깃허브 주소이다.
https://github.com/Eumgill98/DAICON_4D-Blcok-AI
Idea
1. Data
우선 대회의 데이터의 핵심은 Train data와 Test data의 형태가 달랐다는 점이다.
쉽게말하자면 Train data의 경우 뒷 배경이 존재하지 않는 그래픽형태의 블록 사진이었고
Test data의 경우 현실세계의 배경을 가진 상황에서 촬영된 블록 사진이었다.
(데이터예시는 저작권상 문제가 있을 수 있으니 궁금한 사람은 위에 있는 데이콘 주소를 통해서 살펴보기를 바란다)
따라서 이를 전처리하는 파이프라인을 생각해야했다.
우리는 랜덤적으로 배경이미지를 수집하여서 아래 코드를 활용해 이미지를 생성하는 코드를 작성하였다
#가상의 배경과 합성 코드
import os
import cv2
import numpy as np
import pandas as pd
import random
from glob import glob
from tqdm import tqdm
#데이터 경로
DATA_PATH = "../data" #원본데이터 경로
BACKGROUND = "../data/background/" #배경화면 경로
SAVE_PATH = "../data/Compose_train/" #저장경로
TRAIN_DF = "../data/train.csv" #train csv 원본경로 - 합성한 사진 라벨을 위해
#랜덤시드
def seed_everything(seed):
random.seed(seed)
os.environ['PYTHONHASHSEED'] = str(seed)
np.random.seed(seed)
#시드 고정
seed_everything(41) # Seed 고정
#이미지 합성
def make_picture(imgs):
background_list = glob(os.path.join(BACKGROUND, '*.jpg')) #배경이미지 리스트
background_num = random.randint(0, len(background_list)-1) #배경이미지 랜덤 설정
randombackground = background_list[background_num]
img = cv2.imread(imgs)
thresh = 190 #사진 추출 기준점 지정 (0~255) -> 결과물에 따라 조정필요!!
img_bin = np.array(img[:, :, 0] <thresh, dtype=np.uint8) * 255
img_blur = cv2.medianBlur(img_bin, 5)
img_filter = img.copy()
img_filter[img_blur == 0] = 0
retval, labels, stats, centroids = cv2.connectedComponentsWithStats(img_blur)
stats = np.array(stats)
stats[0, -1] = 0
label_idx = np.argmax(stats[:, -1])
img_mask = labels == label_idx
img_final = img_filter.copy()
img_final[~img_mask] = (0, 0, 0)
img_back = cv2.resize(cv2.imread(randombackground), img.shape[::-1][1:])
img_back[img_mask] = (0, 0, 0)
img_filter += img_back
return img_filter
#메인
if __name__ == "__main__":
#SAVE경로 만들기(없다면 만들기)
try:
os.makedirs(SAVE_PATH)
except OSError:
if not os.path.isdir(SAVE_PATH):
raise
train_df = pd.read_csv(TRAIN_DF)
Compos_df = pd.DataFrame(columns=train_df.columns)
img_list = sorted(glob(os.path.join(DATA_PATH, 'train/*.jpg'))) #원본 사진불러오기
for idx, imgs in enumerate(tqdm((img_list))):
img = make_picture(imgs) #이미지 합성
cv2.imwrite(SAVE_PATH+imgs[-15:-4]+'_'+'compose'+'.jpg', img)
#데이터프레임에 저장
img_info = pd.DataFrame({'id' : (imgs[-15:-4]+'_'+'compose').replace("\\", '/'),
'img_path' : ('.'+imgs[7:-4]+'_'+'compose'+'.jpg').replace("\\", '/'),
'A': train_df.loc[idx]['A'],
'B': train_df.loc[idx]['B'],
'C': train_df.loc[idx]['C'],
'D': train_df.loc[idx]['D'],
'E': train_df.loc[idx]['E'],
'F': train_df.loc[idx]['F'],
'G': train_df.loc[idx]['G'],
'H': train_df.loc[idx]['H'],
'I': train_df.loc[idx]['I'],
'J': train_df.loc[idx]['J']}
,index=[idx])
Compos_df = Compos_df.append(img_info)
#데이터프레임 저장
Compos_df.to_csv(SAVE_PATH+'Compos_train.csv')
print(f'모든작업완료, csv와 이미지는 {SAVE_PATH}에 저장되었습니다.')
코드를 살펴보면 새롭게 생성된 데이터(사진)의 라벨을 저장한것을 확인할 수 있는데
이렇게 만들어진 데이터만 활용할 것이라면 원래의 라벨을 그대로 사용하면되지만
원본데이터 + 새로 합성한 데이터를 활용할 방법도 고려하였기 때문에 따로 csv로 추가해 주었다.
또한 이러한 Train data의 단점을 보완하기 위해서 Augmentation 기법에 대해서도 고민이 필요했다.
2. Augmentation
Augmentation의 경우
1. albumentations의 다양한 기능을 활용하여 랜덤적으로 적용하는 방법
2. 이미지 전처리 과정에서 한번에 Random Augmentation이 적용된 이미지를 생성하는 방법
이렇게 두가지 방법으로 접근하였다.
1번 방법의 경우 데이터에 배경사진을 합성처리한 이미지를 활용하여
아래 코드를 Customdata set을 구성활때 적용하였다.
train_transform = A.Compose([
A.Resize(CFG['IMG_SIZE'], CFG['IMG_SIZE']),
A.OneOf([
A.NoOp(),
A.RandomResizedCrop(height=CFG['IMG_SIZE'], width=CFG['IMG_SIZE']),
A.RandomRotate90(),
A.IAAAffine(),
A.VerticalFlip(),
], p = 1.0),
A.Cutout(num_holes=8, max_h_size=300, max_w_size=300, p=0.3),
A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225), max_pixel_value=255.0, always_apply=False, p=1.0),
ToTensorV2()
])
RandomCrop, 회전, IAAA변환, 수직 변환, 아무것도 적용x 한 선택지중 하나를 적용하고
여기에 Cutout을 랜덤적(30%)으로 적용하여 train data를 구성하였다.
2번 방법의 경우에는
https://github.com/Eumgill98/DAICON_4D-Blcok-AI/blob/main/utils/preprocessing.py
위의 코드를 활용하여 이미지를 합성하는 과정에서 랜덤적으로 Augmentaion을 적용하여 이미지를 생성하였다.
결론 :
두 방법의 성능 차이는 크게 나타나지 않았다.
따라서 학습 시간을 고려한다면 그리고 다양한 방법으로의 확장성을 생각한다면 1번 방법의 접근이 용이할것으로 생각된다.
또한 1번 방법의 경우 학습과정에서 변형이 있기 때문에 모델 학습 시간은 조금더 걸릴 수 있다.
하지만 2번 방법도 이미지를 생성하는 시간이 발생하기 때문에 사실상 비슷할 것이다.
Model
61번의 제출은 대부분 model을 선택하는 시간에 활용되었다.
Baseline code에 나와있는 efficientnet을 활용했을 때도 제법 괜찮은 성능을 보여주었기 때문에
- Efficientnet b4~5
- Efficientnet v2
등의 모델을 활용해 보았다.
그러나 성능이 87~ 89사이를 (리더보드의) 계속 맴돌았기 때문에 고민이 있었다.
따라서 다양한 모델을 사용해 보고 이를 제출해 보았다.
- 사용된 단일 모델의 목록
1. Efficientnet b4~5
2. Efficientnet v2
3. Vit base 16
4. Densenet 201
5. Resnet 계열 모델
....
그러나 리더보드의 성능은 비슷비슷했으면
특히 Vit의 경우에는 데이터셋의 크기가 작았기 때문에 성능이 좋게 나오지 않았다.
또한, 컴퓨팅의 한계로인해서 빠른 학습이 불가능했기 때문에 (RTX 3060 6GB)
배치사이즈를 늘릴 수가 없었고
많은 모델을 빠르게 탐구하는 방법은 효율적이지 못했다.
Voting
따라서 단 시간내에 적은 배치사이즈로 성능을 낼 수 있는 방법이 없을까? 고민하던 중
많은 모델을 , 적은 epoch으로 학습하여 보팅하는 방법을 선택했다.
따라서,
단일 모델 학습 과정 중 성능이 좋았던 10개의 모델을 선택해서 5에폭이라는 짧은 학습을 진행했다
여기서도 2가지의 방법을 통해서 접근하여 비교하였다.
1. 각 에폭마다 모델을 저장하고 50개의 모델을 형성해 보팅하는 방법
2. 각 에폭마다 모델을 활용해 (5개) 보팅하여 단일 모델을 형성하고 이렇게 형성한 단일 모델 10개를 보팅하는 방법
두 방법모두 어차피 50개의 모델은 존재하여야 하기때문에 시간의 차이는 없었다.
하지만 성능에서는 0.08정도를 보였다.
2번 방법이 조금더 높은 성능을 보였다.
즉, 5에폭으로 보팅하여 성능이 개선된 단일 모델을 만들고 이를 통해서 다시 10개의 다른 모델들을 보팅하는 방법이 비교적으로 높은 성능을 보인 것이다.
이러한 보팅 방법을 통해서 0.9의 성능을 깨고 0.918이라는 스코어를 달성할 수 있었다.
정리
아쉬운점 :
1.
너무 모델에 집중하여 상세한 DEA를 수행하지 못했다.
2.
컴퓨팅 자원의 아쉬움이 절실하게 느껴졌다.
하루종일 모델을 돌린다고 노트북이 혹사 당했다. 개선이 필요하다...
3.
상위권에 위치한 분들의 코드를 살펴보니 다양한 Augmentaion 기법들이 성능을 결정한 것 같다.
나도 Cutmix나 Mosaic 방법들을 적용해보려 했지만 시간상 스킵하였는데 무조건 활용하여야겠다.
배운점 :
1.
이미지 처리의 전체적은 흐름과 모델링 방식을 확실히 습득할 수 있었다.
또한 Mutil labeling 이라는 새로운 task를 경험함으로 좀 더 image task에 다양성을 확보 할 수 있었다.
다음 대회에는 좀 더 확실하고 완벽한 파이프 라인을 구성해야겠다..