SJ_Koding

Pytorch, 이미지 분류 코드 자세히 이해하기 (1편) - 데이터 확인 본문

PyTorch Code/Pytorch

Pytorch, 이미지 분류 코드 자세히 이해하기 (1편) - 데이터 확인

성지코딩 2023. 11. 7. 17:39

1편 내용: import 문, SEED 고정, DataFrame화, rsplit, natsort, countplot

1편은 pytorch문법이 나오지는 않으나 반드시 필수적으로 처리해야하는 부분들입니다.

 

교내에서 진행한 AI경진대회에서 30개의 클래스로 이루어진 32 by 32 이미지를 분류하는 대회를 진행하였습니다.

해당 이미지는 500개로 시작하여 3개의 클래스 마다 50개씩 데이터 개수가 줄여져있습니다.

즉 500, 500, 500, 450, 450, 450, 400, 400, 400, ..., 50, 50, 50 개의 데이터로 불균형을 이룹니다.

 

해당 대회코드로 각각 무슨 역할을 하는 코드인지 하나하나 정리하면 좋을 것 같아 글을 작성하게 되었습니다.

* 데이터는 공개할 수 없음

 

환경: google colab pro
gpu: T4 (max: 15GB)
실험해본 것들 (★: 사용)
증강: AutoAugment(Cifar-10★, ImageNet), Cutout, Cutmix, Mixup
모델: customCNN, resnet9★, resnet18, efficientNet, PyramidNet+ShakeDrop, wide-resnet, mobileNet, EfficientNet-Lite
기법: OverSampling★, Test Time Augmentation★, Weighted CELoss

 


Import문 선언

# 표준 라이브러리
import os
import zipfile
from glob import glob

# 데이터 분석과 조작
import numpy as np
import pandas as pd

# 시각화
import matplotlib.pyplot as plt
import seaborn as sns

# 이미지 처리
import cv2
from PIL import Image
import albumentations as A

# 딥러닝 및 머신러닝
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from torch.autograd import Variable
import torchvision
import timm
from sklearn.metrics import accuracy_score

# 진행 상황 표시와 기타 유틸리티
from tqdm.notebook import tqdm
from natsort import natsorted
import random

import문은 다음과 같으며 각 라이브러리의 설명은 사용될 시점에 설명하겠습니다.


SEED 고정

def set_random_seed(seed):
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.cuda.manual_seed_all(seed)
    random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    torch.backends.cudnn.benchmark = True
    torch.backends.cudnn.deterministic = True
set_random_seed(7777)

seed고정은 모델의 재현성을 위해 반드시 필요한 장치입니다. 즉 난수 생성과정을 일관되게 만들이 위함입니다. 코드를 실행할 때 마다 동일한 난수 순서를 얻을 수 있게 되며 이는 모델 학습 및 실험의 일관성을 보장합니다. 경진대회 등에서 필수적입니다.

 

np.random.seed(seed)

  • 목적: 모든 난수 생성을 예측 가능한 순서로 만듭니다.
  • 영향: NumPy를 통해 생성되는 난수 시퀀스가 일정하게 유지됩니다.

torch.manual_seed(seed)

  • 목적: PyTorch 난수 생성을 예측 가능하게 합니다.
  • 영향: CPU 상에서 PyTorch를 통해 생성되는 난수가 일정합니다.

torch.cuda.manual_seed(seed)

  • 목적: 현재 GPU에서 PyTorch의 난수 생성을 예측 가능하게 합니다.
  • 영향: GPU에서의 PyTorch 연산을 위한 난수가 일정합니다.

torch.cuda.manual_seed_all(seed)

  • 목적: 멀티 GPU 환경에서 PyTorch의 모든 GPU에 대한 난수 생성을 예측 가능하게 합니다.
  • 영향: 모든 GPU에서 PyTorch 연산을 위한 난수가 일정합니다.

random.seed(seed)

  • 목적: Python 내장 random 모듈의 난수 생성을 예측 가능하게 합니다.
  • 영향: random 모듈을 통해 생성되는 난수가 일정합니다.

os.environ['PYTHONHASHSEED'] = str(seed)

  • 목적: Python 해시 함수의 재현 가능성을 보장합니다.
  • 영향: dict와 set과 같은 해시 기반 구조의 순서가 프로그램 실행 간 일정하게 유지됩니다.

torch.backends.cudnn.benchmark = True (재현성을 확실하게 하기 위해서는 False 권장)

  • 목적: PyTorch가 CuDNN을 사용할 때 성능을 최적화합니다.
  • 경고: 벤치마킹을 통해 가장 빠른 알고리즘을 선택하지만, 결과의 재현 가능성이 떨어질 수 있습니다.

torch.backends.cudnn.deterministic = True

  • 목적: CuDNN 연산의 결정론적 결과를 보장합니다.
  • 영향: 결과가 재현 가능하지만, 성능에 영향을 줄 수 있습니다.

DataFrame 만들기

저는 DataFrame을 통해 이미지의 경로와 라벨을 지정한 후 이를 기반으로 데이터를 다루는 것을 선호합니다. 하지만 데이터셋의 구조 및 형태는 테스크마다 많이 상이하므로 유동적으로 다뤄야합니다. 해당 테스크에서는 폴더명이 곧 클래스이며, 그 안에 이미지가 존재하는 형태입니다. 아래는 이 경우를 기반으로 한 코드입니다.

dic = dict()
dic['id'] = []
dic['img_path'] = []
dic['class'] = []

for path in natsorted(glob('./dataset/train_kaggle/*/*')):
    dic['id'].append(path.rsplit('/', 1)[1][:-4])
    dic['img_path'].append(path)
    dic['class'].append(path.rsplit('/', 2)[1])

pd.DataFrame(dic).to_csv('train.csv', index=False)

os라이브러리를 사용하면 더욱 확실하지만 저는 glob를 통해 경로를 가져오고, rsplit을 통해 추출하였습니다.

* rsplit('/', 2) 의 의미
예를들어 'ab/cd/ef/gh'라는 문자열이 있을 때, rsplit('/', 2)를 진행하면
<'/'를 기준으로 오른쪽부터 2번 잘라서 리스트로 만들어라! 라는 의미가 됩니다.>


input: 'ab/cd/ef/gh'
result: ['ab/cd', 'ef', 'gh']

 

그리고 natsorted라는 라이브러리가 있습니다. 파이썬은 .sort() 혹은 sorted()의 내장 함수가 있습니다. 하지만 문자열 정렬 시 사전순으로 정렬하기 때문에 '1' 다음 '2'가 아니라 '1'다음 '10'이 다음 순서대로 오게됩니다. 하지만 natsorted는 해당 문제를 해결합니다.

from natsort import natsorted # pip 설치 필요!

print(sorted(['1', '2', '10', '3', '11'])) # 결과: ['1', '10', '11', '2', '3']
print(natsorted(['1', '2',' 10', '3', '11'])) # 결과: ['1', '2', '3', '10', '11']

이번 테스크에서는 중요한 사항이 아니었지만 시계열 데이터이거나 디버깅이 필요할때 유용하게 사용됩니다.

 

클래스별 개수 시각화

df = pd.read_csv('train.csv')
df.sort_values('id').reset_index().drop('index', axis=1)

정상적으로 df가 저장되고 불러올 수 있음을 확인했습니다. 

plt.figure(figsize=(20, 5))
sns.countplot(df, x="class")

sns의 countplot을 사용하여 클래스별 개수를 시각화해봅니다. 개인적으로 seaborn의 라이브러를 주로 사용합니다. (예쁘거든요)

말씀드린 것 처럼 데이터는 위와같이 이루어져있습니다. class명은 공개되지 않았으며 숫자로만 이루어져있습니다.

 

데이터 확인

plt.figure(figsize=(30, 25))
cnt = 0
for i in range(6):
    for j in range(5):
        plt.subplot(6, 5, cnt+1)
        plt.title(f'Class: {cnt}')
        img = cv2.imread(df.loc[df['class']==cnt, 'img_path'].iloc[0])
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        plt.imshow(img)
        cnt += 1

for문을 통해 그리드형태로 데이터를 한 눈에 시각화 할 수 있습니다. 아래 글을 참고해주세요.

2022.01.07 - [Data analysis/Matplotlib] - Matplotlib 그래프 n by m 형태로 여러 개 출력하기 (subplot)

 

Matplotlib 그래프 n by m 형태로 여러 개 출력하기 (subplot)

Matplotlib을 사용하다보면 그래프를 세로로 나열하지 않고 아래의 그림과 같이 n by m 형태로 출력하고 싶은 경우가 대부분입니다. 이번 게시글은 Matplotlib의 subplot에 대해 알아봅니다. subplot은 plt.su

sjkoding.tistory.com

 

 

2편에서는 CustomDataset 클래스 생성, Data Augmentation, model 선언 (외부model 가져오기), 데이터 분할 등등

실제 pytorch문법을 살펴봅니다. 이때 각 pytorch문장들이 어떤 역할을 수행하는지 자세히 확인하겠습니다.

 

- 1편 끝 -