๐Ÿฃ Pytorch Tutorial

๊ฐฑ์Šคํƒ€ยท2023๋…„ 9์›” 20์ผ
0
post-thumbnail

10๊ฐœ์˜ ํด๋ž˜์Šค(butterfly, dog, spider, horse, sheep, cow, cat, squirrel, elephant, chicken)๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋ถ„๋ฅ˜ํ•˜๋Š” ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด๋ดค๋‹ค. pytorch์—์„œ ์ œ๊ณตํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ–ˆ๋‹ค.

1. ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ

2. dataset.py

(1) ๊ตฌ์กฐ

  • ์ƒ์„ฑ์ž
  • len ๋ฉ”์†Œ๋“œ
  • getitem ๋ฉ”์†Œ๋“œ

(2) ํ•„์š”์„ฑ

  1. ๋กœ์ปฌ, url ๋“ฑ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์šด๋กœ๋“œ
  2. ๋ฐ์ดํ„ฐ ๊ฒฝ๋กœ, ๋ฐ์ดํ„ฐ ๋ ˆ์ด๋ธ”(Ground Truth) ๋ฆฌ์ŠคํŠธ ์ €์žฅ
  3. ๋ฐ์ดํ„ฐ ๊ธธ์ด ์ €์žฅ
  4. ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต(transform) : augmentation, tensor

main.py ํŒŒ์ผ์—์„œ ํ•œ๊บผ๋ฒˆ์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ, ๋ชจ๋“ˆ์„ฑ์ด ๋–จ์–ด์ง€๊ธฐ ๋•Œ๋ฌธ์— ์ปค์Šคํ…€ ๋ฐ์ดํ„ฐ์…‹ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋ฐ€ํ‚คํŠธ์ฒ˜๋Ÿผ ํŽธํ•˜๊ฒŒ ์กฐ๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค!

(3) ๊ตฌํ˜„

1. Import Dependencies

import os								# download data 
import pandas as pd						# get label
from PIL import Image 					# process image
from torch.utils.data import Dataset	# inherit Dataset to make CustomDataset

2. Class CustomDataset

class CustomDataset(Dataset):			# inherit Dataset
	
    ## 1. ์ƒ์„ฑ์ž
	def __init__(self, root = './datasets', transform = transform, mode = 'train'):	
    	# Initialize member variables
        self.root = root 
        self.transform = transform 
        self.mode = mode 
        
        if self.mode == 'train':
        	self.dataset_path = os.path.join(self.root, 'train_images')
            self.annotations_file = pd.read_csv(os.path.join(self.root, 'train.csv'))
    
    	elif self.mode == 'val': 
        	self.dataset_path = os.path.join(root, 'val_images')
            self.annotations_file = pd.read_csv(os.path.join(root, 'val.csv')
            
       	else: 
        	raise NotImplementedError(f"Mode {self.mode} is not implemented yet...")
        
        # Get image name
        image_names = self.annotations_file.iloc[:,0].tolist()		# Image names in first column 
        
        # Load image path 
        self.image_paths = []
        
        for i in image_names: 
        	path = os.path.join(self.dataset_path, i)
            self.image_paths.append(path)
       
       # Get corresponding labels 
       self.labels = self.annotations_file.iloc[:,1].tolist()
       
       # Get class names 
       self.class_names = list(set(self.labels))

	## 2. len 
    
    def __len__(self):
    	return len(self.image_paths)
    
    ## 3. getitem 
    def __getitem__(self, idx): 
    	images = PIL.open(self.image_paths[idx])
        label = self.labels[idx]
        
        # Transform image to tensor 
        if self.transform:
        	image = self.transform(image)
        
        return image, label 
    
dataset = CustomDataset()

3. main.py

(0) Import Dependencies

  • torch + name space ์ค„์ผ ์ˆ˜ ์žˆ๋„๋ก torch ํ•˜์œ„ ๋ชจ๋“ˆ๋“ค
  • CustomDataset
  • argparse (CLI์—์„œ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ ๋ฐ›์€ argument parsing)
  • tqdm (์ง„ํ–‰ ๋ฐ” ํ‘œ์‹œ)
import torch 
from torch.utils.data import DataLoader 
import torchvision 
from torchvision import transforms
from utils.dataset import CustomDataset 
from torchvision.models import vgg16
import argparse
from tqdm import tqdm 
from torch.utils.tensorboard import SummaryWriter
import numpy as np 

(1) Transform

  • input image์— ๋Œ€ํ•œ augmentation, tensor ๋ณ€ํ™˜ ๋“ฑ ์„ค์ •
  1. ๊ธฐ๋ณธ transform
# Define Transforms 
transform = transforms.Compose([
    transforms.Resize([224, 224]),
    transforms.ToTensor()
    ])
  1. Dataset ๊ฐ์ฒด ์ƒ์„ฑ
# Define Dataset 
train_dataset = CustomDataset(root = './datasets', transform = transform, mode = 'train')
val_dataset = CustomDataset(root = './datasets', transform = transform, mode = 'val')
print(f"Length of train, validation dataset: {len(train_dataset)}, {len(val_dataset)}")
  1. RGB channel์˜ ํ‰๊ท , ํ‘œ์ค€ํŽธ์ฐจ ๊ณ„์‚ฐ
# Get Mean, Standard Deviation of RGB Channel 
def get_mean_std(dataset):
    mean_RGB = [np.mean(image.numpy(), axis = (1, 2)) for image, _ in dataset]
    std_RGB = [np.std(image.numpy(), axis = (1, 2)) for image, _ in dataset]

    mean_R = np.mean([r for (r, _, _) in mean_RGB])
    mean_G = np.mean([g for (_, g, _) in mean_RGB])
    mean_B = np.mean([b for (_, _, b) in mean_RGB])
    
    std_R = np.mean([r for (r, _, _) in std_RGB])
    std_G = np.mean([g for (_, g, _) in std_RGB])
    std_B = np.mean([b for (_, _, b) in std_RGB])
    
    return [mean_R, mean_G, mean_B], [std_R, std_G, std_B]

train_mean_rgb, train_std_rgb = get_mean_std(train_dataset)
val_mean_rgb, val_std_rgb = get_mean_std(val_dataset)
  1. transform ์ด์šฉ Data Augmentation
# Data Augmentation 
transform_train = transforms.Compose([
    transforms.Resize([256, 256]),
    transforms.RandomCrop([224, 224]),
    transforms.ColorJitter(brightness = 0.2, contrast = 0.2, saturation = 0.2, hue = 0.2),
    transforms.RandomHorizontalFlip(p = 1),
    transforms.RandomVerticalFlip(p = 1),
    transforms.ToTensor(),
    transforms.Normalize(train_mean_rgb, train_std_rgb)
])

transform_val = transforms.Compose([
    transforms.Resize([256, 256]),
    transforms.RandomCrop([224, 224]), 
    transforms.Normalize(val_mean_rgb, val_std_rgb)
])
    

(2) Define Dataset

dataset.py ๋ชจ๋“ˆ์—์„œ ์ •์˜ํ•œ CustomDataset ํด๋ž˜์Šค๋ฅผ ์ด์šฉํ•˜์—ฌ ์‹ค์ œ train, validation dataset์„ ์ •์˜ํ•œ๋‹ค.
์ฆ‰, ๋ชจ๋ธ์— ๋„ฃ๊ธฐ ์œ„ํ•ด, ๋ฐ์ดํ„ฐ์…‹์„ ๋‹ค์šด๋กœ๋“œ ๋ฐ›๊ณ , Input(image)์™€ Output(label) ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•œ๋‹ค.

train_dataset = CustomDataset(root = './datasets', transform = transform_train, mode = 'train')
val_dataset = CustomDataset(root = './datasets', transform = transform_val, mode = 'val')

(3) DataLoader

torch.utils.data ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ DataLoader ํด๋ž˜์Šค๋ฅผ ๋ถˆ๋Ÿฌ์™€์„œ train, validation dataset์„ ๋ฐฐ์น˜ ๋‹จ์œ„๋กœ ๋กœ๋“œํ•œ๋‹ค. ์ด๋•Œ shuffle ์œ ๋ฌด(shuffle = True), ๋ฐ์ดํ„ฐ์— ์‚ฌ์šฉํ•  ์„œ๋ธŒ ํ”„๋กœ์„ธ์Šค์˜ ์ˆ˜(num_workers = 0)๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

train_dataloader = DataLoader(train_dataset, batch_size = 32, shuffle = True, num_workers = 0)
val_dataloader = DataLoader(val_dataset, batch_size = 32, shuffle = False, num_workers = 0)

๋ชจ๋ธ์„ ํ•™์Šต์‹œํ‚ค๊ธฐ ์œ„ํ•ด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํ…์„œ๋กœ ๋ณ€ํ™˜ํ•ด์•ผํ•œ๋‹ค. ํ…์„œ๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ ์ „์— ๋ฌธ์ž๋กœ ๋œ ๋ ˆ์ด๋ธ”์„ ์ •์ˆ˜ ์ธ๋ฑ์Šค๋กœ ๋ฐ”๊ฟ”์•ผํ•œ๋‹ค.

# Index to change labels 
index_for_class = {'butterfly': 0, 'dog': 1, 'spider': 2, 'horse': 3, 'sheep' : 4, 
                   'cow': 5, 'cat': 6, 'squirrel': 7, 'elephant': 8, 'chicken': 9}

(4) Device

torch๋Š” device ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•œ๋‹ค. CUDA๋Š” NVIDIA GPU์—์„œ ์‹คํ–‰๋˜๋„๋ก ์„ค๊ณ„๋œ ์†Œํ”„ํŠธ์›จ์–ด ํ™˜๊ฒฝ์ด๋‹ค. CUDA๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด NVIDIA GPU์˜ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ๋Šฅ๋ ฅ์„ ์ตœ๋Œ€ํ•œ์œผ๋กœ ํ™œ์šฉํ•˜์—ฌ ๋”ฅ๋Ÿฌ๋‹ ๋„คํŠธ์›Œํฌ์˜ ํ•™์Šต ์‹œ๊ฐ„์„ ๋งŽ์ด ๋‹จ์ถ•์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

# Device 
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f"Using {device} for inference")

(5) Model

torch์—์„œ ์ œ๊ณตํ•˜๋Š” ํŒจํ‚ค์ง€๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ชจ๋ธ ์ธ์Šคํ„ด์Šค๋ฅผ ๋งŒ๋“ค๊ณ , ๋ชจ๋ธ์„ device๋กœ ๋ณด๋‚ธ๋‹ค.

# Model 
model = vgg16(pretrained = True)
model.classifier[6] = torch.nn.Linear(in_features = 4096, out_features = 10)
model.to(device)

(6) Loss, Optimizer

  • criterion : ์ธ๊ณต์‹ ๊ฒฝ๋ง์„ ํ•™์Šต์‹œํ‚ฌ๋•Œ ํ˜„์žฌ ๋ชจ๋ธ์ด ์–ผ๋งˆ๋‚˜ ๊ตฌ๋ฆฐ์ง€(?) ํ‰๊ฐ€ํ•  ์ˆ˜ ์žˆ๋Š” ์ง€ํ‘œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.
  • optimizer : ์ด ์ง€ํ‘œ๊ฐ€ ๋ฐ”๋กœ loss์ธ๋ฐ, ๋ถ„๋ฅ˜ ๋ฌธ์ œ์—์„œ๋Š” ์ฃผ๋กœ ํฌ๋กœ์Šค์—”ํŠธ๋กœํ”ผ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.
    ๋˜ํ•œ ๋ชจ๋ธ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ๋•Œ ๋‹ค์–‘ํ•œ ๊ฒฝ์‚ฌํ•˜๊ฐ•๋ฒ•๋“ค์ด ์กด์žฌํ•œ๋‹ค. (Adam, SGD, Mini-batch ๋“ฑ)
# Loss, Optimizer 
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr = 0.0001)

(7) Train Loop

  • Epoch : ์—ํฌํฌ๋Š” ์ „์ฒด ๋ฐ์ดํ„ฐ์…‹์„ ํ•œ๋ฒˆ ํ›‘๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค. ๋”ฐ๋ผ์„œ N ์—ํฌํฌ์˜ ๊ฒฝ์šฐ, ์ „์ฒด ๋ฐ์ดํ„ฐ์…‹์— ๋Œ€ํ•ด ํ•™์Šต์ด ์ด N๋ฒˆ ์ง„ํ–‰๋˜์—ˆ์Œ์„ ๋œปํ•œ๋‹ค. for loop์„ ํ†ตํ•ด 10๋ฒˆ์˜ ์—ํฌํฌ์— ๊ฑธ์ณ ๋ชจ๋ธ์„ ํ•™์Šต์‹œํ‚จ๋‹ค.
  • Batch : ๊ฐ ์—ํฌํฌ ๋‚ด์—์„œ ๋ฐฐ์น˜ ๋‹จ์œ„๋กœ ํ•™์Šต์„ ์ง„ํ–‰ํ•œ๋‹ค. ์ด๋•Œ ๋ฐฐ์น˜์˜ ํฌ๊ธฐ๋Š” DataLoader์—์„œ ์ง€์ •ํ•œ๋Œ€๋กœ 32์ด๋‹ค. image์™€ label์„ ๊ฐ๊ฐ device๋กœ ๋„˜๊ฒจ์ฃผ๊ณ , image๋ฅผ model์•ˆ์— ๋„ฃ์–ด์„œ ์˜ˆ์ธก์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์ด๋•Œ ํŒŒ๋ผ๋ฏธํ„ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋Š” ๊ธฐ์ค€(criterion)์€ ์•ž์„œ ํฌ๋กœ์Šค ์—”ํŠธ๋กœํ”ผ๋กœ ์ง€์ •ํ–ˆ๋‹ค. ์—ญ์ „ํŒŒ๋ฅผ ์‹œ์ผœ์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋งˆ๋‹ค ์†์‹คํ•จ์ˆ˜์˜ ๊ธฐ์šธ๊ธฐ๋ฅผ ๊ตฌํ•˜๊ณ , ๊ฒฝ์‚ฌํ•˜๊ฐ•๋ฒ• ์‹œ ์‚ฌ์šฉํ•˜๋Š” optimizer์— ๋”ฐ๋ผ ์†์‹คํ•จ์ˆ˜๊ฐ’์ด ์ž‘์•„์ง€๋Š” ๋ฐฉํ–ฅ์œผ๋กœ ์ตœ์ ํ™”๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค. ์ €์žฅ๋œ ๊ธฐ์šธ๊ธฐ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ ๋‹ค์Œ๋ฒˆ ๋ฐฐ์น˜ ์‹œ ์ƒˆ๋กญ๊ฒŒ ๊ธฐ์šธ๊ธฐ๋ฅผ ๊ณ„์‚ฐํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค.
# Train Loop 
for ep in range(10):
    model.train()       # Set model in training mode
    
    for batch, (images, labels) in enumerate(tqdm(train_dataloader)):
        # Make labels to indices
        label_to_index = [index_for_class[label] for label in labels]
        labels = torch.tensor(label_to_index).to(device)
        
        # Send (images, labels) to device 
        images = images.to(device)
        
        # Compute prediction and loss
        outputs = model(images)
        print(outputs.shape, labels.shape)
        
        loss = criterion(outputs, labels)
        writer.add_scalar("loss", loss.item(), ep)
        
        # Backpropagation 
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()
        
        if batch % 10 == 0:
            print(f"Epoch : {ep}, Loss : {loss.item()}")

(8) Validation

๋ชจ๋ธ์„ validation mode๋กœ ์„ค์ •ํ•œ๋‹ค. torch.no_grad() ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €๋ฅผ ํ†ตํ•ด validation ์‹œ์—๋Š” gradient ๊ณ„์‚ฐ์„ ๋น„ํ™œ์„ฑํ™”ํ•˜์—ฌ ํŒŒ๋ผ๋ฏธํ„ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜์ง€ ์•Š๋Š”๋‹ค. (๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ)

# Validation 
model.eval()                # Set model in validation mode 
total = 0 
correct = 0 

with torch.no_grad():       # No gradients computed during validation mode 
    for (images, labels) in val_dataloader:
        total += len(images)
        
        # Make labels to indices 
        label_to_index = [index_for_class[label] for label in labels]
        labels = torch.tensor(label_to_index).to(device)
        
        # Send (images, labels) to device 
        images = images.to(device)
        
        # Forward pass : Compute prediction and loss 
        outputs = model(images)
        _, pred = torch.max(outputs, dim = 1)
        
        correct += (pred == labels).sum().items()
        
    print(f"After training epoch {ep}, Validation Accuracy : {correct / total}")

4. ์ถœ์ฒ˜

  1. YBIGTA DS ์ž๋ฃŒ
  2. pytorch tutorial
profile
๋ฐฐ์›Œ์„œ ๋‚จ์ค„๊ฑฐ์—์š”!

0๊ฐœ์˜ ๋Œ“๊ธ€