출처 : https://programmers.co.kr/skill_check_assignments/133 (프로그래머스 과제관)
일단 [머신러닝] 미술 작품 분류하기 과제관을 선사해주신 프로그래머스 사이트에 감사인사 올립니다...
문제는 programmers 사이트(https://programmers.co.kr)의 과제관에서 확인하실 수 있습니다.
필자는 오로지 풀이에 대해서만 작성할 것이며 상업 및 영리의 목적이 아닌 오로지 공부용임을 밝힙니다.
코드 : https://github.com/youngjun0627/image_classification
우선 과제를 살펴보면 dataset은 train과 test 두개로 나눠진다. 필자는 dataset을 무조건 csv파일 형식으로 저장하고 custom-dataset class를 만들기 때문에 우선 train 폴더의 형식대로 csv파일은 만든다.
import os
import csv
import torch
import cv2
def train_validation_split(train_csv_path, validation_csv_path, split_ratio, train_path = '../train'):
train_csv = csv.writer(open(train_csv_path, 'w', encoding='utf-8-sig', newline=''))
validation_csv = csv.writer(open(validation_csv_path, 'w', encoding='utf-8-sig', newline=''))
index = 1 # k-fold를 위해서
#X_train = []
#Y_train = []
for label in os.listdir(train_path):
image_root_path = os.path.join(train_path, label)
for image_name in os.listdir(image_root_path):
image_path = os.path.join(image_root_path, image_name)
if index%(int(1/split_ratio))!=0:
train_csv.writerow([image_path, label])
else:
validation_csv.writerow([image_path, label])
index+=1
test_csv도 위와 마찬가지로 만들어주면 된다.
train dataset을 train과 validation으로 나눴기 때문에 안그래도 부족한 train 데이터셋이 더 줄어들었다. 이대로 진행하면 overfitting나서 분명 test에 대한 퍼포먼스가 안 좋을 것이다. 따라서 data augmentation을 넣어주자!!
import albumentations
from albumentations.pytorch import ToTensor
def create_train_transform(flip,\
noise,\
cutout,\
resize,\
size = 224):
translist=[]
### resize
if resize:
translist+=[albumentations.Resize(size+30,size+30)] ## original image width : 300
translist+=[albumentations.RandomCrop(size,size,always_apply=True)]
if flip:
translist+=[albumentations.OneOf([
albumentations.HorizontalFlip(),
albumentations.RandomRotate90(),
albumentations.VerticalFlip()],p=0.5)]
### noise
if noise:
translist+=[albumentations.OneOf([
albumentations.MotionBlur(blur_limit=5),
albumentations.GaussNoise(var_limit=(5.0,30.0))], p=0.5)]
### cutout
if cutout:
translist+= [albumentations.Cutout(max_h_size = int(size * 0.2), max_w_size = int(size * 0.2), num_holes = 1,p=0.3)]
### normalized & totensor
translist+=[albumentations.Normalize(mean = (0.4875, 0.5115, 0.5364), std = (0.2432, 0.2359, 0.2503))] # 위에서 구한 값으로
translist+=[ToTensor()]
transform = albumentations.Compose(translist)
return transform
사실 필자는 이보다 더 많은 data augmentation을 넣어주었다(cutmix, color jitter 등). 그러나 무조건 data augmentation이 많은 것보다는 데이터에 맞게 augmentation을 해주는 것이 중요하기 때문에 위처럼 약간의 augmentation만 넣어주고 dataset을 토대로 mean값과 std값을 구해 normalize 해주었다(dataset.py에 있음).
그리고 dataset 클래스를 만들어 주었는데, 이때 dataset의 클래스 불균형을 계산하여 loss에 weight를 넣어줘야하므로 이 또한 계산을 미리해주자(dataset.py에 있음).
과거 딥러닝 공부해본 사람들은 알겠지만 inceptionnet, resnet 등 층을 깊게 쌓아 퍼포먼스를 올리는 방식을 선호했다. 그러나 최근 2019년 efficientnet 출시로 완전 뒤집혔다. 단순히 층을 깊게 쌓는 것보다 데이터셋에 맞게 width, depth, channel을 조절하여 모델을 개선하면 적은 파라미터수로 최상의 퍼포먼스를 이끌어 준다는 것이다. 이와 관련된 논문은 아래 reference로 써놓겠다(나중에 논문리뷰할거임).
따라서 필자는 efficientnet을 사용하였고 efficientnet은 b0~b7까지 데이터 셋의 resolution에 따라 다르므로 이 또한 맞춰서 사용해야된다. 예를 들어 b0는 224x224이다.
간단하게 timm 라이브러리를 사용하여 efficientnet을 갖고 올수 있으므로 코드는 생략하겠다.
일단 2번에서 언급한 class 불균형을 맞춰주기 위해 loss에 weight을 넣어주었다. 그리고 필자는 한가지 더 추가하였는데 바로 labelsmoothing 기술이다. 사실 이 기술은 overconfidence를 막기위한 기술로 레이블이 1이면 0.8~0.9 이런식으로 cross entropy를 계산하는 것이다. 그러나 과거 필자가 binary classification와 multi-class classification을 진행했을 때 overfitting 또한 막아주는 것을 확인하였고 이를 loss function에 추가하기로 하였다. 해당 코드는 loss.py에 있다.
해당 과제는 image classification을 목표로 오로지 accuracy로만 퍼포먼스를 측정한다. 그러나 필자는 보다 정확한 측정을 위하여 f1-score으로 퍼포먼스를 측정하여 최상의 퍼포먼스를 보여준 모델로 추론작업을 진행했다.
사실 미술 작품 분류하기 과제는 프로그래머스의 데브매칭을 통해 출제된 문제이다. 필자는 해당 데브매칭에 참가하여 테스트를 치뤘고, public score : 100, total(private포함) score: 96.0로 25등을 하였다. 해당 글에서는 나의 코드를 설명할 것이고 보다 높은 퍼포먼스를 보여준 좋은 코드를 보고싶다면 프로그래머스 과제관에 나와있는 우수코드를 참고하면 좋을 듯 하다.
해당 데브매칭을 통해서 탑프로그래머스 연락이 왔다. 나에게 이런 기회가... 그리고 프로그래머스 매니저님과 전화연결을 통해 컨설팅을 받을 수 있었다. 현재 나의 부족한 점과 AI 엔지니어로서 필요한 커리어들을 말씀해주시며 많은 조언을 받을 수 있었다. 단순히 AI엔지니어 해야지라고 생각했는데, 어떻게 커리어를 쌓아야 할지 구체적으로 생각을 해야 될거같다.
다음에는 flask를 이용해서 해당 모델을 서빙해보고자 한다. 과거 tomcat(java)을 했던 기억을 토대로 이번엔 flask(python)를 이용해 이미지를 분류해주는 간단한 웹페이지 제작을 해볼 것이다.