Pytorch_DNN 실습 (2)

DNN 구현(2)

실습 목차

    1. 학습(training)
      • 1-1. 손실 함수와 최적화 알고리즘
      • 1-2. 학습 과정
      • 1-3. 활성화 함수와 가중치 초기화의 중요성
    1. 추론과 평가(inference & evaluation)
      • 2-1. 학습한 딥러닝 모델로 테스트 데이터 추론
      • 2-2. 학습한 딥러닝 모델의 평가

환경 설정

  • 패키지 설치 및 임포트
!pip install scikit-learn==1.3.0 -q
!pip install torch==2.0.1 -q
!pip install torchvision==0.15.2 -q
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 10.8/10.8 MB 82.5 MB/s eta 0:00:00
[?25h
import numpy as np # 기본적인 연산을 위한 라이브러리
import matplotlib.pyplot as plt # 시각화를 위한 라이브러리
from tqdm.notebook import tqdm # 상태 바를 나타내기 위한 라이브러리

import torch # PyTorch 라이브러리
import torch.nn as nn # 모델 구성을 위한 라이브러리
import torch.optim as optim # optimizer 설정을 위한 라이브러리
from torch.utils.data import Dataset, DataLoader # 데이터셋 설정을 위한 라이브러리

import torchvision # PyTorch의 컴퓨터 비전 라이브러리
import torchvision.transforms as T # 이미지 변환을 위한 모듈

from sklearn.metrics import precision_score, recall_score, f1_score, roc_auc_score # 성능지표 측정
# seed 고정
import random
import torch.backends.cudnn as cudnn

def random_seed(seed_num):
    torch.manual_seed(seed_num)
    torch.cuda.manual_seed(seed_num)
    torch.cuda.manual_seed_all(seed_num)
    np.random.seed(seed_num)
    cudnn.benchmark = False
    cudnn.deterministic = True
    random.seed(seed_num)

random_seed(42)

데이터 셋 개요 </b>

  • 데이터 셋: MNIST 데이터베이스(Modified National Institute of Standards and Technology database)
  • 데이터 셋 개요: MNIST는 숫자 0부터 9까지의 이미지로 구성된 손글씨 데이터셋입니다. 총 6만 개의 학습 데이터와 1만 개의 숫자 데이터로 이루어져 있으며 [이미지]와 [숫자에 대한 라벨]로 구성됩니다.
  • 데이터 셋 저작권: CC BY-SA 3.0
  • MNIST - 위키피디아
# 데이터를 불러올 때, 필요한 변환(transform)을 정의합니다.
mnist_transform = T.Compose([
    T.ToTensor(), # 텐서 형식으로 변환
])
# torchvision 라이브러리를 사용하여 MNIST 데이터 셋을 불러옵니다.
download_root = './MNIST_DATASET'

train_dataset = torchvision.datasets.MNIST(download_root, transform=mnist_transform, train=True, download=True) # train dataset 다운로드
test_dataset = torchvision.datasets.MNIST(download_root, transform=mnist_transform, train=False, download=True) # test dataset 다운로드
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to ./MNIST_DATASET/MNIST/raw/train-images-idx3-ubyte.gz


100%|██████████| 9912422/9912422 [00:00<00:00, 352246981.65it/s]


Extracting ./MNIST_DATASET/MNIST/raw/train-images-idx3-ubyte.gz to ./MNIST_DATASET/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to ./MNIST_DATASET/MNIST/raw/train-labels-idx1-ubyte.gz


100%|██████████| 28881/28881 [00:00<00:00, 93253036.05it/s]


Extracting ./MNIST_DATASET/MNIST/raw/train-labels-idx1-ubyte.gz to ./MNIST_DATASET/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to ./MNIST_DATASET/MNIST/raw/t10k-images-idx3-ubyte.gz


100%|██████████| 1648877/1648877 [00:00<00:00, 158592262.81it/s]

Extracting ./MNIST_DATASET/MNIST/raw/t10k-images-idx3-ubyte.gz to ./MNIST_DATASET/MNIST/raw

Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz





Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to ./MNIST_DATASET/MNIST/raw/t10k-labels-idx1-ubyte.gz


100%|██████████| 4542/4542 [00:00<00:00, 4011482.16it/s]

Extracting ./MNIST_DATASET/MNIST/raw/t10k-labels-idx1-ubyte.gz to ./MNIST_DATASET/MNIST/raw
# 데이터 셋을 학습 데이터 셋과 검증 데이터 셋으로 분리합니다.
total_size = len(train_dataset)
train_num, valid_num = int(total_size * 0.8), int(total_size * 0.2) # 8 : 2 = train : valid
print("Train dataset 개수 : ",train_num)
print("Validation dataset 개수 : ",valid_num)
train_dataset,valid_dataset = torch.utils.data.random_split(train_dataset, [train_num, valid_num]) # train - valid set 나누기
Train dataset 개수 :  48000
Validation dataset 개수 :  12000
# 앞서 선언한 Dataset을 인자로 주어 DataLoader를 선언합니다.
batch_size = 32

train_dataloader = DataLoader(train_dataset, batch_size = batch_size, shuffle = True)
valid_dataloader = DataLoader(valid_dataset, batch_size = batch_size, shuffle = False)
test_dataloader = DataLoader(test_dataset, batch_size = batch_size, shuffle = False)
# 최종 모델 코드
class DNN(nn.Module):
  def __init__(self, hidden_dims, num_classes, dropout_ratio, apply_batchnorm, apply_dropout, apply_activation, set_super):
    if set_super:
      super().__init__()

    self.hidden_dims = hidden_dims
    self.layers = nn.ModuleList()

    for i in range(len(self.hidden_dims) - 1):
      self.layers.append(nn.Linear(self.hidden_dims[i], self.hidden_dims[i+1]))

      if apply_batchnorm:
        self.layers.append(nn.BatchNorm1d(self.hidden_dims[i+1]))

      if apply_activation:
        self.layers.append(nn.ReLU())

      if apply_dropout:
        self.layers.append(nn.Dropout(dropout_ratio))

    self.classifier = nn.Linear(self.hidden_dims[-1], num_classes)
    self.softmax = nn.LogSoftmax(dim = 1)

  def forward(self, x):
    """
    Input and Output Summary

    Input:
      x: [batch_size, 1, 28, 28]
    Output:
      output: [batch_size, num_classes]

    """
    x = x.view(x.shape[0], -1)  # [batch_size, 784]

    for layer in self.layers:
      x = layer(x)

    x = self.classifier(x) # [batch_size, 10]
    output = self.softmax(x) # [batch_size, 10]
    return output

  def weight_initialization(self, weight_init_method):
    for m in self.modules():
      if isinstance(m, nn.Linear):
        if weight_init_method == 'gaussian':
          nn.init.normal_(m.weight)
        elif weight_init_method == 'xavier':
          nn.init.xavier_normal_(m.weight)
        elif weight_init_method == 'kaiming':
          nn.init.kaiming_normal_(m.weight)
        elif weight_init_method == 'zeros':
          nn.init.zeros_(m.weight)

        nn.init.zeros_(m.bias)

  def count_parameters(self):
    return sum(p.numel() for p in self.parameters() if p.requires_grad)

1. 학습(training)

💡 목차 개요: 앞서 구현한 Dataset, DataLoader 그리고 custom model을 이용하여 딥러닝 모델을 학습합니다.
  • 1-1. 손실 함수와 최적화 알고리즘
  • 1-2. 학습 과정
  • 1-3. 활성화 함수와 가중치 초기화의 중요성

1-1 손실 함수와 최적화 알고리즘

torch.nn, torch.optim를 사용하여 편리하게 손실 함수와 최적화 알고리즘을 구현할 수 있습니다.

📝 설명: torch.nn을 이용하여 손실 함수 구현

torch.nn 모듈은 다양한 손실 함수들을 제공합니다. 원하는 손실 함수가 없을 경우, 손실을 계산하는 코드를 직접 구현하여 스칼라 tensor를 반환하는 손실 함수를 직접 구현할 수도 있습니다. 0 ~ 9까지의 클래스를 갖는 MNIST 숫자 이미지 데이터를 분류하기 위해 NLLLoss를 불러와 실습해보도록 하겠습니다.

  • torch.nn.NLLLoss
  • torch.nn.MSELoss
  • torch.nn.L1Loss
  • torch.nn.BCELoss
  • torch.nn.CrossEntropyLoss
  • ..

📚 참고할만한 자료:

  • Loss Functions - PyTorch 공식 문서: 소개한 손실 함수 이 외에도 다양한 손실 함수가 존재합니다. 홈페이지에서 불러올 수 있는 손실 함수를 확인할 수 있습니다.
criterion = nn.NLLLoss()

📝 설명: torch.optim에 구현된 최적화 알고리즘 사용

torch.optim 모듈은 다양한 최적화 알고리즘을 제공합니다. 실습에서는 Adam 알고리즘을 사용해 실습해보도록 하겠습니다.

  • torch.optim.SGD
  • torch.optim.Adam
  • torch.optim.Adagrad
  • torch.optim.RMSprop
  • ..

torch.optim을 통해 최적화 알고리즘을 구현할 경우, 선언한 모델의 가중치를 필수적으로 선언해야 합니다. 이 외에도 PyTorch의 optimizer에는 주요 인자들이 존재합니다.

  • lr: 학습률(learning rate) 하이퍼 파라미터입니다.
  • weight_decay: L2 regularization에 사용되는 하이퍼 파라미터입니다.

📚 참고할만한 자료:

lr = 0.001
hidden_dim = 128
hidden_dims = [784, hidden_dim * 4, hidden_dim * 2, hidden_dim]
model = DNN(hidden_dims = hidden_dims, num_classes = 10, dropout_ratio = 0.2, apply_batchnorm = True, apply_dropout = True, apply_activation = True, set_super = True)
optimizer = optim.Adam(model.parameters(), lr = lr)

1-2 학습 과정

PyTorch를 사용하여 딥러닝 모델을 학습합니다. 학습을 진행하며, 검증 데이터 셋에 대해 loss가 감소하지 않고 patience만큼 계속 증가한다면 학습을 중단합니다.

def training(model, dataloader, train_dataset, criterion, optimizer, device, epoch, num_epochs):
  model.train()  # 모델을 학습 모드로 설정
  train_loss = 0.0
  train_accuracy = 0

  tbar = tqdm(dataloader)
  for images, labels in tbar:
      images = images.to(device)
      labels = labels.to(device)

      # 순전파
      outputs = model(images)
      loss = criterion(outputs, labels)

      # 역전파 및 weights 업데이트
      optimizer.zero_grad()
      loss.backward()
      optimizer.step()

      # 손실과 정확도 계산
      train_loss += loss.item()
      # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
      _, predicted = torch.max(outputs, 1)
      train_accuracy += (predicted == labels).sum().item()

      # tqdm의 진행바에 표시될 설명 텍스트를 설정
      tbar.set_description(f"Epoch [{epoch+1}/{num_epochs}], Train Loss: {loss.item():.4f}")

  # 에폭별 학습 결과 출력
  train_loss = train_loss / len(dataloader)
  train_accuracy = train_accuracy / len(train_dataset)

  return model, train_loss, train_accuracy

def evaluation(model, dataloader, valid_dataset, criterion, device, epoch, num_epochs):
  model.eval()  # 모델을 평가 모드로 설정
  valid_loss = 0.0
  valid_accuracy = 0

  with torch.no_grad(): # model의 업데이트 막기
      tbar = tqdm(dataloader)
      for images, labels in tbar:
          images = images.to(device)
          labels = labels.to(device)

          # 순전파
          outputs = model(images)
          loss = criterion(outputs, labels)

          # 손실과 정확도 계산
          valid_loss += loss.item()
          # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
          _, predicted = torch.max(outputs, 1)
          valid_accuracy += (predicted == labels).sum().item()

          # tqdm의 진행바에 표시될 설명 텍스트를 설정
          tbar.set_description(f"Epoch [{epoch+1}/{num_epochs}], Valid Loss: {loss.item():.4f}")

  valid_loss = valid_loss / len(dataloader)
  valid_accuracy = valid_accuracy / len(valid_dataset)

  return model, valid_loss, valid_accuracy


def training_loop(model, train_dataloader, valid_dataloader, criterion, optimizer, device, num_epochs, patience, model_name):
    best_valid_loss = float('inf')  # 가장 좋은 validation loss를 저장
    early_stop_counter = 0  # 카운터
    valid_max_accuracy = -1

    for epoch in range(num_epochs):
        model, train_loss, train_accuracy = training(model, train_dataloader, train_dataset, criterion, optimizer, device, epoch, num_epochs)
        model, valid_loss, valid_accuracy = evaluation(model, valid_dataloader, valid_dataset, criterion, device, epoch, num_epochs)

        if valid_accuracy > valid_max_accuracy:
          valid_max_accuracy = valid_accuracy

        # validation loss가 감소하면 모델 저장 및 카운터 리셋
        if valid_loss < best_valid_loss:
            best_valid_loss = valid_loss
            torch.save(model.state_dict(), f"./model_{model_name}.pt")
            early_stop_counter = 0

        # validation loss가 증가하거나 같으면 카운터 증가
        else:
            early_stop_counter += 1

        print(f"Epoch [{epoch + 1}/{num_epochs}], Train Loss: {train_loss:.4f}, Train Accuracy: {train_accuracy:.4f} Valid Loss: {valid_loss:.4f}, Valid Accuracy: {valid_accuracy:.4f}")

        # 조기 종료 카운터가 설정한 patience를 초과하면 학습 종료
        if early_stop_counter >= patience:
            print("Early stopping")
            break

    return model, valid_max_accuracy
num_epochs = 100
patience = 3
scores = dict()
device = 'cuda:0' # gpu 설정
model_name = 'exp1'
init_method = 'kaiming' # gaussian, xavier, kaiming, zeros

model = DNN(hidden_dims = hidden_dims, num_classes = 10, dropout_ratio = 0.2, apply_batchnorm = True, apply_dropout = True, apply_activation = True, set_super = True)
model.weight_initialization(init_method)
model = model.to(device)

criterion = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr = lr)

model, valid_max_accuracy = training_loop(model, train_dataloader, valid_dataloader, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy
  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [1/100], Train Loss: 0.3167, Train Accuracy: 0.9047 Valid Loss: 0.1187, Valid Accuracy: 0.9624



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [2/100], Train Loss: 0.1651, Train Accuracy: 0.9492 Valid Loss: 0.1065, Valid Accuracy: 0.9676



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [3/100], Train Loss: 0.1317, Train Accuracy: 0.9603 Valid Loss: 0.0841, Valid Accuracy: 0.9755



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [4/100], Train Loss: 0.1094, Train Accuracy: 0.9650 Valid Loss: 0.0752, Valid Accuracy: 0.9779



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [5/100], Train Loss: 0.0967, Train Accuracy: 0.9691 Valid Loss: 0.0757, Valid Accuracy: 0.9772



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [6/100], Train Loss: 0.0833, Train Accuracy: 0.9734 Valid Loss: 0.0633, Valid Accuracy: 0.9802



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [7/100], Train Loss: 0.0742, Train Accuracy: 0.9764 Valid Loss: 0.0741, Valid Accuracy: 0.9781



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [8/100], Train Loss: 0.0677, Train Accuracy: 0.9786 Valid Loss: 0.0685, Valid Accuracy: 0.9794



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [9/100], Train Loss: 0.0613, Train Accuracy: 0.9805 Valid Loss: 0.0705, Valid Accuracy: 0.9787
Early stopping
# Batch normalization을 제외하고 학습을 진행합니다.
model_name = 'exp2'
init_method = 'kaiming' # gaussian, xavier, kaiming, zeros

model = DNN(hidden_dims = hidden_dims, num_classes = 10, dropout_ratio = 0.2, apply_batchnorm = False, apply_dropout = True, apply_activation = True, set_super = True)
model.weight_initialization(init_method)
model = model.to(device)

optimizer = optim.Adam(model.parameters(), lr = lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, valid_dataloader, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy
  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [1/100], Train Loss: 0.2841, Train Accuracy: 0.9133 Valid Loss: 0.1649, Valid Accuracy: 0.9492



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [2/100], Train Loss: 0.1352, Train Accuracy: 0.9594 Valid Loss: 0.1144, Valid Accuracy: 0.9667



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [3/100], Train Loss: 0.1040, Train Accuracy: 0.9689 Valid Loss: 0.1040, Valid Accuracy: 0.9702



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [4/100], Train Loss: 0.0860, Train Accuracy: 0.9734 Valid Loss: 0.0874, Valid Accuracy: 0.9752



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [5/100], Train Loss: 0.0732, Train Accuracy: 0.9791 Valid Loss: 0.0930, Valid Accuracy: 0.9737



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [6/100], Train Loss: 0.0622, Train Accuracy: 0.9809 Valid Loss: 0.0856, Valid Accuracy: 0.9777



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [7/100], Train Loss: 0.0581, Train Accuracy: 0.9824 Valid Loss: 0.1063, Valid Accuracy: 0.9726



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [8/100], Train Loss: 0.0543, Train Accuracy: 0.9832 Valid Loss: 0.0846, Valid Accuracy: 0.9768



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [9/100], Train Loss: 0.0489, Train Accuracy: 0.9847 Valid Loss: 0.0873, Valid Accuracy: 0.9780



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [10/100], Train Loss: 0.0412, Train Accuracy: 0.9878 Valid Loss: 0.0890, Valid Accuracy: 0.9766



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [11/100], Train Loss: 0.0398, Train Accuracy: 0.9875 Valid Loss: 0.1178, Valid Accuracy: 0.9717
Early stopping
# Dropout을 제외하고 학습을 진행합니다.
# dropout_ratio는 하이퍼 파라미터입니다. 최적의 dropout_ratio에 따라 모델의 성능이 달라질 수 있습니다.
model_name = 'exp3'
init_method = 'kaiming' # gaussian, xavier, kaiming, zeros

model = DNN(hidden_dims = hidden_dims, num_classes = 10, dropout_ratio = 0.2, apply_batchnorm = True, apply_dropout = False, apply_activation = True, set_super = True)
model.weight_initialization(init_method)
model = model.to(device)

optimizer = optim.Adam(model.parameters(), lr = lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, valid_dataloader, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy
  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [1/100], Train Loss: 0.2232, Train Accuracy: 0.9326 Valid Loss: 0.1130, Valid Accuracy: 0.9651



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [2/100], Train Loss: 0.1074, Train Accuracy: 0.9664 Valid Loss: 0.0851, Valid Accuracy: 0.9732



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [3/100], Train Loss: 0.0787, Train Accuracy: 0.9744 Valid Loss: 0.0864, Valid Accuracy: 0.9725



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [4/100], Train Loss: 0.0622, Train Accuracy: 0.9800 Valid Loss: 0.0773, Valid Accuracy: 0.9770



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [5/100], Train Loss: 0.0536, Train Accuracy: 0.9829 Valid Loss: 0.0746, Valid Accuracy: 0.9764



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [6/100], Train Loss: 0.0446, Train Accuracy: 0.9851 Valid Loss: 0.0719, Valid Accuracy: 0.9788



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [7/100], Train Loss: 0.0388, Train Accuracy: 0.9869 Valid Loss: 0.0631, Valid Accuracy: 0.9807



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [8/100], Train Loss: 0.0329, Train Accuracy: 0.9892 Valid Loss: 0.0686, Valid Accuracy: 0.9794



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [9/100], Train Loss: 0.0303, Train Accuracy: 0.9900 Valid Loss: 0.0648, Valid Accuracy: 0.9819



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [10/100], Train Loss: 0.0286, Train Accuracy: 0.9905 Valid Loss: 0.0647, Valid Accuracy: 0.9810
Early stopping

1-3 활성화 함수와 가중치 초기화의 중요성

활성화 함수와 가중치 초기화가 딥러닝 모델에 끼치는 영향을 실습을 통해 알아봅니다.

📝 설명: 활성화 함수가 없다면 딥러닝 모델은 단순히 입력 데이터와 가중치의 선형 변환(linear transform)일 뿐

활성화 함수가 없는 딥러닝 모델은 기본적으로 선형 분류기와 다르지 않으며, 복잡한 패턴을 학습하는 데에 제한적일 수 있습니다.

활성화 함수가 없다면 각각의 레이어는 선형 변환만을 수행하며, 여러 개의 선형 변환을 결합한 것은 결국 하나의 선형 변환과 다를 바 없습니다. 활성화 함수는 딥러닝 모델에 비선형성(non-linearity)를 도입하여, 모델이 더 복잡한 패턴을 학습할 수 있게 합니다. 비선형 활성화 함수로 인해 딥러닝 모델은 선형 변환 이상의 복잡한 함수를 표현하고 학습할 수 있게 됩니다.

따라서, 딥러닝 모델에서는 활성화 함수가 필요합니다.

📚 참고할만한 자료:

# 활성화 함수(activation function)를 제외하고 학습을 진행합니다.
model_name = 'exp4'
init_method = 'kaiming' # gaussian, xavier, kaiming, zeros

model = DNN(hidden_dims = hidden_dims, num_classes = 10, dropout_ratio = 0.2, apply_batchnorm = True, apply_dropout = True, apply_activation = False, set_super = True)
model.weight_initialization(init_method)
model = model.to(device)

optimizer = optim.Adam(model.parameters(), lr = lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, valid_dataloader, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy
  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [1/100], Train Loss: 0.4630, Train Accuracy: 0.8637 Valid Loss: 0.3509, Valid Accuracy: 0.9004



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [2/100], Train Loss: 0.3793, Train Accuracy: 0.8895 Valid Loss: 0.3289, Valid Accuracy: 0.9067



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [3/100], Train Loss: 0.3622, Train Accuracy: 0.8942 Valid Loss: 0.3360, Valid Accuracy: 0.9044



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [4/100], Train Loss: 0.3492, Train Accuracy: 0.8986 Valid Loss: 0.3289, Valid Accuracy: 0.9070



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [5/100], Train Loss: 0.3429, Train Accuracy: 0.8988 Valid Loss: 0.3186, Valid Accuracy: 0.9136



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [6/100], Train Loss: 0.3375, Train Accuracy: 0.9021 Valid Loss: 0.3203, Valid Accuracy: 0.9107



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [7/100], Train Loss: 0.3290, Train Accuracy: 0.9050 Valid Loss: 0.3236, Valid Accuracy: 0.9113



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [8/100], Train Loss: 0.3284, Train Accuracy: 0.9042 Valid Loss: 0.3165, Valid Accuracy: 0.9123



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [9/100], Train Loss: 0.3233, Train Accuracy: 0.9063 Valid Loss: 0.3102, Valid Accuracy: 0.9147



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [10/100], Train Loss: 0.3206, Train Accuracy: 0.9070 Valid Loss: 0.3112, Valid Accuracy: 0.9143



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [11/100], Train Loss: 0.3139, Train Accuracy: 0.9088 Valid Loss: 0.3161, Valid Accuracy: 0.9122



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [12/100], Train Loss: 0.3108, Train Accuracy: 0.9101 Valid Loss: 0.3173, Valid Accuracy: 0.9129
Early stopping

📝 설명: 딥러닝 모델의 모든 가중치를 ‘0’으로 초기화하면 학습이 불가합니다.

딥러닝 모델의 가중치를 ‘0’으로 초기화하면, 모든 뉴런의 출력값은 동일하게 ‘0’으로 계산됩니다. 이 경우, 역전파 단계에서 chain rule이 계산되는 값에 ‘0’이 곱해지므로 gradient가 0으로 계산돼 학습이 불가능합니다.

수식적으로 살펴보겠습니다.

$o_{1} = w_{1} \cdot x$,

$o_{2} = w_{2} \cdot x$

$O = w_{o1} \cdot o_{1} + w_{o2} \cdot o_{2}$

위와 같은 연산이 있다고 가정하겠습니다. 이 경우, 가중치 $w_{1}$에 대한 손실 함수 $L$의 편미분은 다음과 같이 계산됩니다.

$\frac{\partial L}{\partial w_{1}} = \frac{\partial L}{\partial O} \frac{\partial O}{\partial o_{1}} \frac{\partial o_{1}}{\partial w_{1}}$

이 때, 모든 가중치가 ‘0’으로 초기화된다면, $\frac{\partial L}{\partial w_{1}} = \frac{\partial L}{\partial O} \frac{\partial O}{\partial o_{1}} \frac{\partial o_{1}}{\partial w_{1}}$과 $\frac{\partial L}{\partial w_{2}} = \frac{\partial L}{\partial O} \frac{\partial O}{\partial o_{2}} \frac{\partial o_{2}}{\partial w_{2}}$은 모두 ‘0’의 값을 가지게 되어 경사 하강법에서 사용되는 gradient가 ‘0’이 됩니다. 이에 따라 가중치의 변화가 존재하지 않으므로, 학습이 불가능합니다.

📚 참고할만한 자료:

model_name = 'exp5'
init_method = 'zeros' # gaussian, xavier, kaiming, zeros

model = DNN(hidden_dims = hidden_dims, num_classes = 10, dropout_ratio = 0.2, apply_batchnorm = True, apply_dropout = True, apply_activation = True, set_super = True)
model.weight_initialization(init_method)
model = model.to(device)

optimizer = optim.Adam(model.parameters(), lr = lr)
model, valid_max_accuracy = training_loop(model, train_dataloader, valid_dataloader, criterion, optimizer, device, num_epochs, patience, model_name)
scores[model_name] = valid_max_accuracy
  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [1/100], Train Loss: 2.3017, Train Accuracy: 0.1116 Valid Loss: 2.3008, Valid Accuracy: 0.1133



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [2/100], Train Loss: 2.3015, Train Accuracy: 0.1121 Valid Loss: 2.3007, Valid Accuracy: 0.1133



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [3/100], Train Loss: 2.3015, Train Accuracy: 0.1121 Valid Loss: 2.3006, Valid Accuracy: 0.1133



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [4/100], Train Loss: 2.3015, Train Accuracy: 0.1121 Valid Loss: 2.3007, Valid Accuracy: 0.1133



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [5/100], Train Loss: 2.3015, Train Accuracy: 0.1121 Valid Loss: 2.3007, Valid Accuracy: 0.1133



  0%|          | 0/1500 [00:00<?, ?it/s]



  0%|          | 0/375 [00:00<?, ?it/s]


Epoch [6/100], Train Loss: 2.3015, Train Accuracy: 0.1121 Valid Loss: 2.3007, Valid Accuracy: 0.1133
Early stopping

검수내역 figure_crop.png

# 실험별 validation accuracy 최댓값
print(f"BatchNorm, Dropout 사용 모델: {scores['exp1']:.4f}")
print(f"BatchNorm 제거 모델: {scores['exp2']:.4f}")
print(f"Dropout 제거 모델: {scores['exp3']:.4f}")
print(f"Activation Function 제거 모델: {scores['exp4']:.4f}")
print(f"가중치를 0으로 초기화한 모델: {scores['exp5']:.4f}")
BatchNorm, Dropout 사용 모델: 0.9802
BatchNorm 제거 모델: 0.9780
Dropout 제거 모델: 0.9819
Activation Function 제거 모델: 0.9147
가중치를 0으로 초기화한 모델: 0.1133

2. 추론과 평가(inference & evaluation)

💡 목차 개요: 딥러닝 모델의 학습이 완료되었다면 모델의 성능을 평가해야 합니다. 학습한 모델을 통해 테스트 데이터에 대해 추론하고 다양한 메트릭으로 성능을 측정합니다.
  • 2-1. 학습한 딥러닝 모델로 테스트 데이터 추론
  • 2-2. 학습한 딥러닝 모델의 평가
# BatchNorm, Dropout을 사용한 모델을 로드합니다.
model = DNN(hidden_dims = hidden_dims, num_classes = 10, dropout_ratio = 0.2, apply_batchnorm = True, apply_dropout = True, apply_activation = True, set_super = True)
model.load_state_dict(torch.load("./model_exp1.pt"))
model = model.to(device)

2-1 학습한 딥러닝 모델로 테스트 데이터 추론

앞서 선언한 테스트 MNIST 데이터 셋에 대해 추론을 진행합니다.

model.eval()
total_labels = []
total_preds = []
total_probs = []
with torch.no_grad():
    for images, labels in test_dataloader:
        images = images.to(device)
        labels = labels

        outputs = model(images)
        # torch.max에서 dim 인자에 값을 추가할 경우, 해당 dimension에서 최댓값과 최댓값에 해당하는 인덱스를 반환
        _, predicted = torch.max(outputs.data, 1)

        total_preds.extend(predicted.detach().cpu().tolist())
        total_labels.extend(labels.tolist())
        total_probs.append(outputs.detach().cpu().numpy())

total_preds = np.array(total_preds)
total_labels = np.array(total_labels)
total_probs = np.concatenate(total_probs, axis= 0)

2-2 학습한 딥러닝 모델의 평가

모델이 예측한 결과값과 라벨을 사용하여 모델의 성능을 평가합니다.

# precision, recall, f1를 계산합니다.
precision = precision_score(total_labels, total_preds, average='macro')
recall = recall_score(total_labels, total_preds, average='macro')
f1 = f1_score(total_labels, total_preds, average='macro')

# AUC를 계산합니다.
# 모델의 출력으로 nn.LogSoftmax 함수가 적용되어 결과물이 출력되게 됩니다. sklearn의 roc_auc_score 메서드를 사용하기 위해서는 단일 데이터의 클래스들의 확률 합은 1이 되어야 합니다. 이를 위해 확률 matrix에 지수 함수를 적용합니다.
total_probs = np.exp(total_probs)
auc = roc_auc_score(total_labels, total_probs, average='macro', multi_class = 'ovr')

print(f'Precision: {precision}, Recall: {recall}, F1 Score: {f1}, AUC: {auc}')
Precision: 0.9815567035682374, Recall: 0.9815391552489962, F1 Score: 0.9815275156151371, AUC: 0.9997172022898146

#Reference

Required Package

torch == 2.0.1

torchvision == 0.15.2

sklearn == 1.3.0