1. 논문 소개

'Very Deep Convolutional Networks for Large-Scale Image Recognition'

Karen Simonyan, Andrew Zisserman (ICLR 2015)

 

 

(1) Abstract

- Convolution network 깊이가 large-scale image recognition에 미치는 영향에 대한 연구를 진행함

- 매우 작은 convolution filters (3 x 3)16 ~ 19 layers 아키텍처를 구성하여 상당한 성능 개선을 이룸

- 2014년 ILSVRC에서 1, 2위를 차지함

- 다른 데이터셋에서도 잘 일반화되어 sota 결과를 얻을 수 있었음

 

 

(2) Introduction

- ConvNet이 컴퓨터 비전 분야에서의 성능 향상에 도모하는 바가 커지면서, 2012년 AlexNet의 성능을 뛰어넘기 위한 노력을 많이 하고 있음

- 본 논문에서는 'depth'에 집중하기로 함. convolutional layer를 추가함으로써 점차 깊이를 증가시켜나간다.

 

 

(3) ConvNet Configurations

1) Architecture

- input : 224 x 224 RGB image (학습 데이터의 평균으로 빼주기)

 

- kernel : 3 x 3, stride : 1, padding : 1

- MaxPooling : 2 x 2, stride : 2

 

- FC layers : 4096 channels 2개, 마지막엔 1000-way (softmax layer)

- ReLU 사용

- LRN 사용 안 함 (오히려 그런 normalization이 성능 향상에 도움이 안 되었고, 메모리 소모와 연산 시간만 늘렸다고 함)

 

 

2) Configuration

Table 1 : conv<kernel 크기>-<채널 개수>

- 깊이 11 (8 conv, 3 FC) ~ 깊이 19 (16 conv, 3 FC)

 

- 깊이에 따른 파라미터 개수인데, 크게 차이나지 않음

 

- VGG16 구조 도식화

 

 

 

3) Discussion

- VGGNet은 이전의 ILSVRC 대회에서 우수한 성능을 보인 모델들과의 상당한 차이가 있음

- 첫번째 conv layer뿐만 아니라 전체 네트워크에 걸쳐 3 x 3의 매우 작은 receptive field를 사용한다.

 

- 작은 receptive field을 사용하는 것의 이점?

1) 작은 kernel을 여러 개 사용하는 것과 큰 kernel 한 개를 사용하는 것이 동일한 크기의 피처맵을 얻을 수 있음

2) decision function을 더 discriminative하게 만들 수 있음

3) 더 적은 파라미터가 필요함

 

 

예) 2개의 3 x 3 커널을 사용하는 것과 1개의 5 x 5 커널은 동일한 크기의 피처맵 생성!

 

입력 채널, 출력 채널 모두 C개라고 가정하면,

- 2개의 3 x 3 커널 : 2 * (3 * 3 * C * C) = 18C*C

- 1개의 5 x 5 커널 : (5 * 5 * C * C) = 25C * C

 

2개의 3 x 3 커널의 파라미터보다 1개의 5 x 5 커널의 파라미터 개수가 더 많다.

 

- 1 x 1 conv. layers 사용 : receptive field에 영향을 주지 않으면서, non-linearity 증가시키기 위해서 사용 (by. ReLU)

 

- GoogLeNet도 small convolutional filters (1 x 1, 5 x 5, 3 x 3)를 사용했다는 점이 유사하지만, VGGNet 보다 훨씬 복잡하여 계산량이 어마어마하다.

 

 

 

(4) Classification Framework

1) Training

- Mini batch gradient descent (batch size : 256, momentum : 0.9)

- L2 regularization : $5 \cdot 10 ^{-4}$

- Dropout : 0.5

- 초기 learning rate : $10^{-2}$ → validation score 개선 없는 경우 10배 감소시킴 (총 3회 감소)

 

- 네트워크 가중치의 초기화가 중요하므로, 얕은 모델부터 학습 시작하고, 학습 중에 레이어가 변경될 수 있도록 했음

- Random Initialization은 정규 분포 상에서 weight를 샘플링했음 (평균 0, 표준편차 $10^{-2}$)

 

 

2) Testing

- 입력 이미지의 크기에서 training scale을 S, testing scale을 Q라고 했을 때, 둘이 서로 같을 필요는 없음

- input image 크기에 따라 variable spatial resolution을 출력하기 때문에, class score map을 'sum-pooled'을 통해 spatially average 한다.

 

 


 

2. Summary

- 네트워크의 깊이와 모델 성능 영향에 집중

- Convolution 커널 사이즈를 3 x 3으로 고정

- 커널 사이즈가 크면 이미지 사이즈 축소가 급격하게 이뤄져서 더 깊은 층을 만들기 어렵고, 파라미터 개수와 연산량도 더 많이 필요

- 여러 개의 3X3 Convolution 연산을 수행하는 것이 더 뛰어난 Feature 추출 효과를 나타냄

 

- 개별 Block내에서는 동일한 커널 크기와 Channel 개수를 적용하여 동일한 크기의 feature map들을 생성

- 이전 Block 내에 있는 Feature Map 대비 새로운 Block내에 Feature Map 크기는 2배로 줄어 들지만 채널 수는 2배로 늘어남 (맨 마지막 block 제외)

https://pub.towardsai.net/the-architecture-and-implementation-of-vgg-16-b050e5a5920b

 

 


 

3. Code

!pip install torchsummary

import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

from torch.utils.data import DataLoader, Dataset, random_split
from torch.utils.data import random_split

from torchsummary import summary

import torchvision
from torchvision.utils import make_grid
import torchvision.transforms as tr

from tqdm import trange
# mean
tr_meanRGB = [ np.mean(x.numpy(), axis=(1, 2)) for x, _ in cifar10_tr ]

tr_meanR = np.mean([m[0] for m in tr_meanRGB])
tr_meanG = np.mean([m[1] for m in tr_meanRGB])
tr_meanB = np.mean([m[2] for m in tr_meanRGB])

# std
tr_stdRGB = [ np.std(x.numpy(), axis=(1, 2)) for x, _ in cifar10_tr ]

tr_stdR = np.std([m[0] for m in tr_stdRGB])
tr_stdG = np.std([m[1] for m in tr_stdRGB])
tr_stdB = np.std([m[2] for m in tr_stdRGB])

mean = [tr_meanR, tr_meanG, tr_meanB]
std = [tr_stdR, tr_stdG, tr_stdB]


tr_transform = tr.Compose([
    tr.ToTensor(),
    tr.Resize(128),  # 크기 조정
    tr.RandomHorizontalFlip(p=0.7), 
    tr.Normalize(mean=[0.49139965, 0.48215845, 0.4465309], std=[0.060528398, 0.061124973, 0.06764512])
])

te_transform = tr.Compose([
    tr.ToTensor(),
    tr.Resize(128),  # 크기 조정
    tr.Normalize(mean=[0.49139965, 0.48215845, 0.4465309], std=[0.060528398, 0.061124973, 0.06764512])
])

tr_meanRGB, tr_stdRGB 값을 구해서 이후에는 직접 Normalize에 넣어주는 방식으로 진행했음.

 

torch.manual_seed(11)

cifar10_tr = torchvision.datasets.CIFAR10(root='./data', download=True, train=True, transform=tr_transform)

val_size = 10000
train_size = len(cifar10_tr) - val_size
cifar10_tr, cifar10_val = random_split(cifar10_tr, [train_size, val_size])

cifar10_te = torchvision.datasets.CIFAR10(root='./data', download=True, train=False, transform=te_transform)

train 데이터를 train, valid로 나누어주었고, random_split을 사용했음

 

trainloader = DataLoader(cifar10_tr, batch_size=64, shuffle=True)
valloader = DataLoader(cifar10_val, batch_size=64, shuffle=False)
testloader = DataLoader(cifar10_te, batch_size=64, shuffle=False)

train, valid, test 데이터셋을 데이터로더로 넣어주고,

 

 

시각화

for images, _ in trainloader:
    print('images.shape:', images.shape)
    
    plt.figure(figsize=(16,8))
    plt.axis('off')
    plt.imshow(make_grid(images, nrow=16).permute((1, 2, 0)))
    
    break

 

데이터 확인

images, labels = next(iter(trainloader))
print(labels)
print(images.shape, labels.shape)

 

 

VGGNet 모델 구현

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

class VGGNet(nn.Module):
    
    def __init__(self, n_classes=10):
        super().__init__()
        
        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(64, 64, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        
        self.conv2 = nn.Sequential(
            nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(128, 128, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        
        self.conv3 = nn.Sequential(
            nn.Conv2d(128, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(256, 256, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        
        self.conv4 = nn.Sequential(
            nn.Conv2d(256, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        
        self.conv5 = nn.Sequential(
            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.Conv2d(512, 512, kernel_size=3, stride=1, padding=1),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        
        self.fc6 = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(512*4*4, 4096),
            nn.ReLU(),
        )
        
        self.fc7 = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(4096,4096),
            nn.ReLU(),
        )
        
        self.fc8 = nn.Sequential(
            nn.Linear(4096, n_classes),
        )
    
    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.conv5(x)
        x = x.view(x.size(0), -1)
        x = self.fc6(x)
        x = self.fc7(x)
        x = self.fc8(x)
        
        return x

 

 

학습 준비

model = VGGNet().to(device)
criterion = nn.CrossEntropyLoss()

optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=5, verbose=True)

# early stopping
early_stopping_epochs = 5
best_loss = float('inf')
early_stop_counter = 0

summary(model, input_size=(3, 128, 128))

PyTorch에서는 early stopping을 제공하고 있지 않기 때문에, 직접 구현해주었음

 

 

학습

epochs = 30

for epoch in range(epochs):
    model.train()
    train_loss, correct, total = 0.0, 0, 0
    
    # Train
    for data in trainloader:
        images, labels = data[0].to(device), data[1].to(device)
        
        outputs = model(images)
        
        optimizer.zero_grad()
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        train_loss += loss.item()
        predicted = torch.argmax(outputs, 1)
        
        total += labels.size()[0]
        correct += (predicted == labels).sum().item()
        
    print(f'Epoch [{epoch+1}/{epochs}] || Loss:{train_loss / len(trainloader)} || Accuracy:{100 * correct / total}')


    
    # Valid
    model.eval()
    valid_loss = 0.0
    
    for data in valloader:
        images, labels = data[0].to(device), data[1].to(device)
        outputs = model(images)
        loss = criterion(outputs, labels)
        valid_loss += loss.item()
    
    if valid_loss > best_loss:
        early_stop_counter += 1
    else:
        best_loss = valid_loss
        early_stop_counter = 0
    
    if early_stop_counter >= early_stopping_epochs:
        print("Early Stopping!")
        break

 

 

Test

correct, total = 0, 0

with torch.no_grad():
    model.eval()
    test_loss = 0.0
    
    for data in testloader:
        images, labels = data[0].to(device), data[1].to(device)
        outputs = model(images)
        test_loss += loss.item()
        
        _, predicted = torch.max(outputs.data, axis=1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
        
print(f"Test loss : {test_loss / len(testloader)}")
print(f"Accuracy : {correct / total:.2f}")

1. 논문 소개

'ImageNet Classification with Deep Convolutional Neural Networks'

Alex Krizhevsky, Ilya Sutskever, Geoffrey E. Hinton (NIPS 2012)

 

 

(1) Abstract

- ILSVRC(ImageNet Large-Scale Visual Recognition Challenge)-2010 : 120만 개의 고해상도 이미지를 1000개의 서로 다른 class로 분류하는 대회

 

- 테스트 데이터에서 top-1, top-5 error rates가 각각 37.5%, 17.0%를 기록하며, 기존의 sota보다 훨씬 우수한 결과를 얻었다.

(top-5 error rate란 모델이 예측한 최상위 5개 범주 가운데 정답이 없는 경우의 오류율이다.)

 

- 6,000만 개의 파라미터, 65만 개의 뉴런으로 신경망을 구성하였으며, 5개의 convolutional layers, 그 중에서 일부는 max pooling layer를 가진다. 마지막에 1000-way softmax fully connected layers 3개로 구성되어 있다.

 

- 훈련 속도를 높이기 위해 non-saturating neurons를 사용하였으며, GPU로 구현하였다.

 

- Fully connected layers에서 overfitting을 줄이기 위해 'Dropout'이라는 정규화 방법을 사용하였고, 이는 매우 효과적이었다.

 

- Dropout을 적용한 모델은 ILSVRC-2012 대회에서 top-5 test error rate를 15.3%를 달성하였는데, 이는 2위 error rate인 26.2%와의 간격이 매우 큰 결과이다.

https://medium.com/coinmonks/paper-review-of-alexnet-caffenet-winner-in-ilsvrc-2012-image-classification-b93598314160

 

 

 

 

(2) Introduction

- 현재 object recognition에서는 machine learning 방법을 많이 사용하고 있다. 이러한 object recognition 성능을 높이기 위해서는 1) larger dataset이 필요하며, 2) more powerful model을 학습시키고, 3) overfitting을 방지하기 위한 기술을 사용해야 한다.

 

- 이전까지의 label의 개수는 그래봤자 10개 남짓이었다. 이러한 simple recognition task에서는 적은 양의 데이터셋과 label-preserving transformation을 통한 augmentation으로도 충분한 인간 정도의 성능을 보였다.

 

- 대규모 데이터셋 (수백만 장의 이미지와 수천개의 objects)을 학습하기 위한 모델로, CNN을 제안한다.

standard feedforward neural networks에 비교했을 때, connections, parameters가 적기 때문에 훈련이 더 쉽다.

 

- CNN의 장점에도 불구하고, high resolution (고해상도) images에 대규모로 적용하기에는 비용이 많이 든다.

따라서, 'highly-optimized GPU implementation of 2D convolution'를 활용하여 매우 큰 deep learning CNN을 학습할 수 있도록 한다.

 

- 5개의 convolutional, 3개의 fully-connected layers를 가지고 있다. (이 중 하나의 레이어만 제거하더라도 떨어지는 성능이 나온다.)

 

 

 

 

(3) The Dataset

1) ImageNet

- 1,500만 장의 labeled high-resolution images

- 약 22,000개의 카테고리

 

 

2) ILSVRC (실제 사용한 데이터셋)

- ImageNet의 subset을 데이터셋으로 사용

- 각 카테고리 별로 1,000개의 이미지가 포함된 총 1,000개의 카테고리

- 약 120만 장의 training images + 5만 장의 validation images + 15만 장의 testing images

https://oi.readthedocs.io/en/latest/computer_vision/conf&dataset.html

 

 

 

3) 데이터 전처리

- ImageNet은 variable-resolution images로 구성되어 있음 (각 이미지 별로 해상도 차이가 있음)

: 일정한 입력 차원을 받기 위해서 256 x 256의 fixed resolution으로 down-sampling 진행

 

- 각각의 픽셀에서 training set의 평균값을 빼어 centered raw RGB 값으로 모델 학습

 

 

 

 

(4) The architecture

1) ReLU Nonlinearity

- 일반적으로 tanh, sigmoid를 활성화 함수로 사용했었는데, gradient descent에서의 saturating 문제로 ReLU를 처음 사용하였음

https://bskyvision.com/421

 

- tanh에서는 출력값이 [-1, 1] 범위에서 존재하여, 논문에서 말하는 'saturating' 상태가 된다.

입력값이 ∞ 또는 -∞가 될수록 기울기가 0에 가까워져 weight 학습이 잘 되지 않는다.

 

- 반면, ReLU는 [0, ∞] 범위에서 존재하여, non-saturating 함수이다.

입력값이 ∞이더라도 기울기가 0이 아니라 빠르게 수렴된다.

 

saturating 정의

 

 

Figure 1

 

- 실선은 ReLU를 사용했을 때, 점선은 tanh를 사용했을 때 25%의 error rate에 도달하기까지의 epoch을 나타낸 그래프이다.

확실히 실선인 ReLU가 더 빠른 속도로 도달했음을 확인할 수 있다.

 

 

 

2) Training on Multiple GPUs

- GPU 한 개로는 모델의 최대 크기에 제한이 있기 때문에, 모델을 두 개의 GPU로 나눠준다.

- host machine memory 없이도 GPU 간의 메모리에서 직접 read, write가 가능하다.

 

- 병렬 GPU은 기본적으로 kernel의 절반을 각 GPU에 배치하고, 특정 layer에서만 communicate한다.

(layer 3는 두 GPU에서 모든 커널을 입력 받고, layer 4는 본인 GPU에서만 입력을 받음)

Figure 2의 일부

- 교차 검증을 통해 GPU 연결 패턴을 결정한다.

 

Figure 3

- 224 x 224 x 3 이미지를 kernel size=11로 학습하여 나온 결과인 96개의 결과이다.

위쪽의 48개의 결과는 GPU1, 아래쪽의 48개의 결과는 GPU2로 학습된 결과이다.

GPU1은 색상보다는 형태에, GPU2는 색상과 관련된 피처를 학습하고 있다는, 즉 독립적으로 학습한다는 것을 보여준다.

 

 

 

3) Local Response Normalization

- Local Response Normalization을 통해 generalization을 더 높인다. (현재는 Batch Normalization)

 

- ReLU를 활성화 함수로 사용했을 때, 양수 입력에 대해서는 그대로 내보내지만 음수 입력에 대해서는 0을 내보낸다.

이렇게 되면, 그 다음 계층에 큰 값이 전달되고, 주변의 작은 값들은 덜 전달되어 학습이 잘 이뤄지지 않는다.

 

- 이런 현상을 방지하기 위해 LRN이 사용되었다.

 

 

- $a_{x,y}^{i}$ : 픽셀 (x, y)에 커널 i를 적용한 결과

- $b_{x,y}^{i}$ : LRN 결과

- $i$ : 현재 kernel

- $N$ : 총 kernel 개수

- $n$ : 이웃한 normalization 크기

 

n개의 이웃한 필터에서의 (x,y)의 결과를 제곱하여 더하면,

값이 클수록 원래 값보다 작게 만들어주고, 작을수록 원래 값보다 크게 만들어주는 normalization 작업인 것이다.

 

 

 

4) Overlapping Pooling

- 일반적으로 pooling은 겹치지 않게 하지만, AlexNet에서는 overlapping 해주었다. 

https://bskyvision.com/421

 

 

 

5) Overall Architecture

 

- 5개의 convolutional layers + 3개의 fully connected layers (1000-way softmax)

[ Input layer - Conv1 - MaxPool1 - Norm1 - Conv2 - MaxPool2 - Norm2 - Conv3 - Conv4 - Conv5 - MaxPool3 - FC1 - FC2 - Output layer ]

 

 

왼쪽은 가장 초기의 CNN 모델인 LeNet-5이며, 오른쪽은 AlexNet이다.

훨씬 더 복잡하고 커진 것을 알 수 있다.

 

(나중에 사람들이 다시 계산해보니 input layer에서 224 x 224가 아니라 227 x 227이 맞는 크기였다.)

 

 

 

 

(5) Reducing Overfitting

1) Data Augmentation

- overfitting을 막을 수 있는 가장 쉬운 방법은 label-preserving transformation을 활용한 dataset 확장이다.

 

- 본 논문에서 사용한 data augmentation 방법으로는 다음 두 가지가 있다.

: horizontal reflections (수평 반전), PCA를 활용한 RGB 픽셀 값 변화

 

- horizontal reflections

https://learnopencv.com/understanding-alexnet/

 

 

- PCA 활용한 RGB 픽셀값 변경

: 각 RGB 이미지 픽셀 벡터 $[I^R_{xy}, I^G_{xy}, I^B_{xy}]$에 대해서, 모든 이미지 픽셀에 대해 공분산 행렬을 계산한다.

고유값 $\lambda_i$은 RGB 공간의 주성분을 나타내고, 고유벡터 $p_i$은 각 성분의 분산을 나타낸다.

 

 

각 픽셀에 위의 값을 더해서, 주성분 방향으로 픽셀 값을 랜덤하게 변형한다.

 

 

2) Dropout

 

- 확률 0.5로 특정 hidden neuron의 출력값을 0으로 만들어준다.

- 순전파, 역전파 전달이 되지 않으므로 입력이 주어질 때마다 다른 아키텍처를 샘플링하는 것과 동일한 효과를 낸다. 즉, 여러 모델이 가중치를 학습하는 앙상블과 동일한 것이다!

 

 

 

(6) Results

- ILSVRC2010 test set의 Top-1 error rate, Top-5 error rate 결과이다.

Table 1

 

 

- ILSVRC-2012 validation and test sets에서의 Top-1 error rate, Top-5 error rate 결과이다.

(*는 2011 ImageNet으로 pretrained model)

Table 2

 

 


 

2. Summary

- Activation 함수로 ReLU 함수를 첫 사용

- MaxPooling 으로 Pooling 적용 및 Overlapping Pooling 적용

- Local Response Normalization (LRN) 사용

- Overfitting을 개선하기 위해서 Drop out Layer와 Weight의 Decay 기법 적용

- Data Augmentation 적용 (좌우 반전, Crop, PCA 변환 등)

 

https://unerue.github.io/computer-vision/classifier-alexnet.html

 

- 11x11, 5x5 사이즈의 큰 사이즈의 Kernel 적용. 이후 3x3 Kernel을 3번 이어서 적용

- Receptive Field가 큰 사이즈를 초기 Feature map에 적용하는 것이 보다 많은 feature 정보를 만드는데 효율적이라고 판단

- 하지만 많은 weight parameter 갯수로 인하여 컴퓨팅 연산량이 크게 증가 함. 이를 극복하기 위하여 병렬 GPU를 활용할 수 있도록 CNN 모델을 병렬화

 

 


 

 

3. Code

import numpy as np
import pandas as pd
import os

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

from torch.utils.data import DataLoader, Dataset, random_split
from torchsummary import summary

import torchvision
import torchvision.transforms as tr

from tqdm import trange

 

 

- 데이터 불러오기 (CIFAR10 데이터셋)

cifar10_tr = torchvision.datasets.CIFAR10(root='./data', download=True, train=True, transform=tr.Compose([tr.ToTensor()]))
cifar10_te = torchvision.datasets.CIFAR10(root='./data', download=True, train=False, transform=tr.Compose([tr.ToTensor()]))

meanRGB = [ np.mean(x.numpy(), axis=(1, 2)) for x, _ in cifar10_tr ]
stdRGB = [ np.std(x.numpy(), axis=(1, 2)) for x, _ in cifar10_tr ]

meanR = np.mean([m[0] for m in meanRGB])
meanG = np.mean([m[1] for m in meanRGB])
meanB = np.mean([m[2] for m in meanRGB])

stdR = np.mean([s[0] for s in stdRGB])
stdG = np.mean([s[1] for s in stdRGB])
stdB = np.mean([s[2] for s in stdRGB])

tr_transform = tr.Compose([
    tr.ToTensor(),
    tr.Resize(227),  # 크기 조정
    tr.RandomHorizontalFlip(),  # 좌우 반전
    tr.Normalize([meanR, meanG, meanB], [stdR, stdG, stdB])
])

te_transform = tr.Compose([
    tr.ToTensor(),
    tr.Resize(227),  # 크기 조정
    tr.Normalize([meanR, meanG, meanB], [stdR, stdG, stdB])
])

cifar10_tr.transform = tr_transform
cifar10_te.transform = te_transform

trainloader = DataLoader(cifar10_tr, batch_size=64, shuffle=True)
testloader = DataLoader(cifar10_te, batch_size=64, shuffle=False)

 

images, labels = next(iter(trainloader))
images.shape, labels.shape

(torch.Size([64, 3, 227, 227]), torch.Size([64])

 

 

- AlexNet

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

class AlexNet(nn.Module):

    def __init__(self, n_classes=10):
        super().__init__()

        self.conv1 = nn.Sequential(
            nn.Conv2d(3, 96, kernel_size=11, stride=4, padding='valid'),
            nn.ReLU(inplace=True),
            nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2), 
            nn.MaxPool2d(kernel_size=3, stride=2),
        )

        self.conv2 = nn.Sequential(
            nn.Conv2d(96, 256, kernel_size=5, stride=1, padding='same'),
            nn.ReLU(inplace=True),
            nn.LocalResponseNorm(size=5, alpha=0.0001, beta=0.75, k=2),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )

        self.conv3 = nn.Sequential(
            nn.Conv2d(256, 384, kernel_size=3, stride=1, padding='same'),
            nn.ReLU(inplace=True),
        )

        self.conv4 = nn.Sequential(
            nn.Conv2d(384, 384, kernel_size=3, stride=1, padding='same'),
            nn.ReLU(inplace=True),
        )

        self.conv5 = nn.Sequential(
            nn.Conv2d(384, 256, kernel_size=3, stride=1, padding='same'),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )

        self.fc6 = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
        )

        self.fc7 = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
        )

        self.fc8 = nn.Sequential(
            nn.Linear(4096, 10),

        )

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.conv4(x)
        x = self.conv5(x)
        x = x.view(x.size(0), -1)

        x = self.fc6(x)
        x = self.fc7(x)
        x = self.fc8(x)

        return x
model = AlexNet().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.2, patience=5, verbose=True)
summary(model, input_size=(3, 227, 227), batch_size=64)

 

 

- Training

epochs = 20
l_ = []
pbar = trange(epochs)

for i in pbar:
    train_loss, correct, total, = 0.0, 0, 0

    for data in trainloader:
        images, labels = data[0].to(device), data[1].to(device)
        outputs = model(images)

        optimizer.zero_grad()
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        train_loss += loss.item()
        _, predicted = torch.max(outputs.detach(), 1)

        total += labels.size()[0]
        correct += (predicted == labels).sum().item()

    l = train_loss / len(trainloader)
    l_.append(l)
    acc = 100 * correct / total
    
    pbar.set_postfix({
        'loss' : l,
        'train acc' : acc
    })
    

print(l_)

 

성능이 잘 나오지 않아서 tr.Resize(128)로 바꿔서 진행했다. (cifar 데이터셋 이미지 크기가 32 x 32로 작은 편임)

그대신 아래와 같이 fc6을 바꿔줘야 한다.

self.fc6 = nn.Sequential(
            nn.Dropout(0.5),
            nn.Linear(1024, 4096),
            nn.ReLU(inplace=True),
        )

loss = 0.772, train accuracy : 74.3

 

 

- Testing

correct, total = 0, 0

with torch.no_grad():
    model.eval()
    test_loss = 0.0
    
    for data in testloader:
        images, labels = data[0].to(device), data[1].to(device)
        outputs = model(images)
        test_loss += loss.item()
        
        _, predicted = torch.max(outputs.data, axis=1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
        
print(f"Test loss : {test_loss / len(testloader)}")
print(f"Accuracy : {correct / total:.2f}")

loss : 0.813, test accuracy : 71.80

 

+ Recent posts