연습프로젝트로 Pytorch 재활훈련1 -MNIST-

1. MNIST

 

Modified National Institute of Standards and Technology database(MNIST)

 

Yann LeCun교수가 만든 손글씨 데이터

 

손으로 쓰인 0에서 9까지의 숫자로 이루어진 이미지 데이터

 

각 이미지에는 어떤 숫자인지를 나타내는 정답 레이블 정보가 포함

 

 

이미지 데이터는 0에서 1까지의 값을 갖는 고정 크기의 28*28행렬

 

각 행렬의 원소는 픽셀의 밝기 정보

 

숫자가 클수록 검정색에 가깝고, 0에 가까울수록 흰색에 가깝고..

 

근데 반대로 클수록 흰색, 작을수록 검정색으로 나타내기도 하는듯..?

 

데이터의 레이블은 one hot encoding으로 길이가 10인 벡터로 이루어져 있다.

 

예를 들어 이미지가 숫자 4를 나타낸다면, [0,0,0,0,1,0,0,0,0,0]

 

 

2. 이미지 데이터의 구성요소

 

MNIST는 눈으로 보기에는 2차원 행렬 데이터로 보이지만, 실제로는 3차원 데이터

 

3차원 행렬은 [1,28,28]과 같은 형태로 각각 [Channel, Width, Height]를 나타낸다.

 

Width와 Height는 각각 가로와 세로를 의미하는데 Channel은 무엇을 뜻할까?

 

채널은 이미지를 구성하기 위한 색상 정보를 나타내며, 색을 표현하는 차원이다.

 

MNIST 데이터는 흑백계열만 사용하므로 단일 채널 Channel = 1이지만, 컬러이미지를 가지는 경우 Red, Green, Blue 3개의 채널을 사용해서 Channel = 3을 가진다.

 

Red, Green, Blue 3가지 색상의 적절한 조합으로 우리가 보는 색으로 나타난다.

 

당연히 Channel이 3보다 클수도 있다.

 

MNIST가 색상이 있는 이미지데이터였다면, [3,28,28]의 차원을 가질 것이다.

 

 

3. 모듈 불러오기

 

기본적으로 필요한 모듈은 주로 코드 상단에 작성

 

#Pytorch 라이브러리
import torch

#딥러닝 네트워크의 기본 구성요소를 포함한 torch.nn모듈
import torch.nn as nn

#딥러닝에 자주 사용되는 함수를 포함한 torch.nn.functional 모듈
import torch.nn.functional as F

#가중치 추정에 필요한 최적화 알고리즘을 포함한 torch.optim 모듈
import torch.optim as optim

#딥러닝에 자주 사용되는 데이터셋과 모델 구조 및 이미지 변환 기술을 포함한 torchvision
from torchvision import datasets, transforms

#시각화
from matplotlib import pyplot as plt
%matplotlib inline #주피터 노트북에서, 브라우저에서 바로 그림을 보려면 실행해야함

 

4. 분석환경 세팅

 

pytorch에서는 모델과 사용하는 데이터에 어떤 장비를 사용할지 지정해야하니, device를 미리 작성하는 것이 유용하다.

 

#cuda로 gpu를 사용할 수 있으면, is_cuda=True, 사용할 수 없다면 is_cuda=False
is_cuda = torch.cuda.is_available()

#is_cuda에 따라 device = cuda or cpu
device = torch.device('cuda' if is_cuda else 'cpu')

print('Current cuda device is', device)
#cpu 사용시, Current cuda device is cpu
#gpu 사용시, Current cuda device is cuda

 

5. hyperparameter 지정

 

모델 설계하기 전에 필요한 hyperparameter를 미리 지정

 

대표적으로 batch size는 가중치 1번 업데이트 시킬때 사용하는 데이터 샘플단위의 수

 

epoch_num은 학습데이터를 모두 사용하여 1번 학습하는 기본 단위

 

learning rate는 가중치 업데이트의 정도

 

#모델 가중치를 한번 업데이트 시킬때 사용되는 샘플 단위 개수(미니배치 사이즈)
batch_size = 50

#학습 데이터를 모두 사용하여 1번 학습하는 기본 단위 횟수 = 1epoch
epoch_num = 15

#가중치 업데이트의 정도
learning_rate = 0.0001

 

6. 데이터 불러오기

 

pytorch 모듈에 MNIST가 저장되어 있어서 쉽게 불러올 수 있다.

 

torchvision의 datasets 모듈에 datasets.MNIST로 데이터를 불러온다.

 

root 옵션은 MNIST 데이터를 다운받아 저장할 위치를 나타내고

 

train은 학습용으로 쓸건지, 테스트용으로 쓸건지 지정하고

 

download는 처음 다운받는건지, 아닌지

 

transform은 지정한 데이터를 지정한 함수로 전처리 시켜주는 옵션

 

transforms.ToTensor()는 데이터를 tensor로 바꿔주므로, 딥러닝 모델에 사용하기 위해 반드시 사용해야한다.

 

#root = MNIST 데이터를 저장할 위치

#train = True이면 학습데이터, False이면 테스트데이터

#download = True이면, 다운받아서 root에 지정한 폴더에 저장, False이면 다운받지 않는다

#transform = MNIST 데이터를 저장과 동시에 지정한 함수로 전처리
#transforms.ToTensor()는 이미지를 tensor로 바꿔준다.

train_data = datasets.MNIST(root = './data', train=True, download=True, transform = transforms.ToTensor())
test_data = datasets.MNIST(root = './data', train=False, transform = transforms.ToTensor())

#len()함수로 데이터 개수 확인
print('number of training data: ', len(train_data))
print('number of test data: ', len(test_data))

 

7. 데이터 확인

 

데이터의 일부를 시각화를 통해 간단히 확인해보기

 

image는 tensor인데, image.squeeze().numpy()로 numpy array로 바꿔줘야 plt.imshow()가 인식한다

 

#0번째 학습데이터와 정답을 저장
image, label = train_data[0]

print(image.shape)
#MNIST는 3차원 텐서로 [1,28,28]

#3차원 텐서를 2차원으로 줄이기 위해 image.squeeze()로 [28,28]로 바꿔줌

print(image.squeeze().shape)

#image tensor를 numpy array로 바꿔줘야 확인 가능
plt.imshow(image.squeeze().numpy(), cmap = 'gray')

plt.title('label : %s' %label)

plt.show()

 

 

 

8. 미니배치 구성하기

 

미리 지정한 배치 사이즈 batch_size로 미니 배치를 구성하는 단계

 

torch.utils.data.DataLoader은 손쉽게 배치를 구성하며 학습 과정을 반복 시행할때마다 미니 배치를 하나씩 불러오게 한다.

 

dataset은 미니 배치로 구성할 데이터를 지정

 

batch_size는 앞서 지정한 미니 배치의 사이즈

 

shuffle는 데이터의 순서를 랜덤으로 섞어서 미니 배치를 구성할지 여부

 

시계열 데이터가 아니라면, 딥러닝 모델이 데이터의 순서에 대해 학습하지 못하도록, 랜덤으로 섞는 것을 권장함

 

#torch.utils.data.DataLoader()은 손쉽게 배치를 구성하고, 
#학습 과정을 반복 시행할때마다 미니 배치를 하나씩 불러온다

#dataset = 미니 배치로 구성할 데이터
#batch_size = 미니 배치의 사이즈 = 50
#shuffle = 데이터의 순서를 랜덤으로 섞어서 미니 배치를 구성할지 여부
#시계열 데이터가 아니라면, 딥러닝이 데이터의 순서에 대해 학습하지 못하도록 랜덤으로 섞어주는 것을 권장
train_loader = torch.utils.data.DataLoader(dataset = train_data, batch_size = batch_size, shuffle = True)

test_loader = torch.utils.data.DataLoader(dataset = test_data, batch_size = batch_size, shuffle = True)

first_batch = train_loader.__iter__().__next__()

print('{:15s} | {:<25s} | {}'.format('name','type','size'))
print('{:15s} | {:<25s} | {}'.format('Num of Batch','',len(train_loader)))
print('{:15s} | {:<25s} | {}'.format('first_batch',str(type(first_batch)),len(first_batch)))
print('{:15s} | {:<25s} | {}'.format('first_batch[0]',str(type(first_batch[0])),first_batch[0].shape))
print('{:15s} | {:<25s} | {}'.format('first_batch[1]',str(type(first_batch[1])), first_batch[1].shape))

#1200개의 미니 배치 생성
#60000개의 train데이터에서, 배치 사이즈로 50을 설정했으니, 50*1200 = 60000
#first_batch[0]의 size가 [50,1,28,28]은 [batch_size, channel, width, height]
#데이터 1개가 [1,28,28]인데, 50개 쌓여서 [50,1,28,28]
#first_batch[1]에는 50개의 각 데이터마다 정답이 있어서 size가 [50]

"""
name            | type                      | size
Num of Batch    |                           | 1200
first_batch     | <class 'list'>            | 2
first_batch[0]  | <class 'torch.Tensor'>    | torch.Size([50, 1, 28, 28])
first_batch[1]  | <class 'torch.Tensor'>    | torch.Size([50])
"""

 

미니 배치 수는 1200개인데, train데이터가 60000개이고 배치 사이즈가 50이므로, 1200개 구성

 

first_batch[0]는 [50,1,28,28]인데, 데이터 하나가 [1,28,28]이고 이것이 50개 쌓여서 [50,1,28,28]

 

first_batch[1]에는 각 데이터마다 정답이 있어서 50개의 데이터에 대한 정답이 있으니 [50]

 

 

9. 모델 구성 -- __init__ 메소드 구성

 

pytorch의 모델 클래스는 __init__ 메소드와 forward 메소드를 반드시 작성해야함

 

가장 먼저 클래스의 __init__을 통해 모델에 사용되는 layer의 형태를 정의

 

주의할 점은, layer를 쌓으면서, 이전 layer의 출력 크기와 직후 layer의 입력 크기를 같게 설계해야한다는 점이다.

 

 

 

convolutional layer 2개와 fully connected layer로 설계

 

위 그림은 입력부터 출력까지 데이터 1개의 tensor 형태가 어떻게 변하는지 보여줌

 

데이터의 차원은 []으로 나타내고, filter의 차원은 ()으로 나타냈다.

 

예를 들어 [1,28,28]의 이미지가 첫번째 convolutional filter인 conv1 (1,3,3) 32개를 통과하면 [32,26,26]이 된다.

 

먼저 convolutional layer를 통과하는 이미지의 채널 크기는 filter의 개수와 같으므로, 

 

32개를 사용했으니 채널 크기는 32

 

출력 tensor의 가로 세로 크기는 $O = \frac{I+2P-F}{S} + 1 = 26$으로 결정된다.

 

여기서 F = 32로 filter size, P = padding의 크기, nn.Conv2d의 기본값이 0, S = stride의 크기, 기본값이 1

 

O는 feature map의 size

 

공식을 굳이 외울 필요는 없고.. print해보면서 입출력의 차원 크기가 잘 맞는지 확인해보면 된다

 

max pooling은 feature map의 가로 세로에만 영향을 주고, 2*2 maxpooling을 사용해서 feature의 size를 반감시킨다.

 

flatten 연산은 fully connected layer에 통과시키기 위해, 1차원 tensor로 줄이는 것이다.

 

마지막 FC2 연산은 task의 목적에 맞도록 출력 클래스의 분류 확률을 받기 위해 설계되는 것으로,

 

MNIST는 0~9까지 10개의 클래스로 분류하기 때문에 10의 길이로 설정한다.

 

--------------------------------------------------------------------------------------------------------------------------

 

기본적으로 pytorch 의 모델 클래스들은 nn.Module이라는 클래스를 상속받아 만들어진다.

 

#nn.Module은 pytorch 모델 클래스가 상속받는 모듈 클래스
class CNN(nn.Module):

 

class의 __init__ 메소드에 모델에 사용되는 layer들을 정의한다.

 

#nn.Module은 pytorch 모델 클래스가 상속받는 모듈 클래스
class CNN(nn.Module):
    
    #모델에 사용되는 layer들을 정의
    def __init__(self):

 

기본적으로 super() 함수로 다음과 같이 nn.Module의 속성을 모두 상속받는다.

 

#nn.Module은 pytorch 모델 클래스가 상속받는 모듈 클래스
class CNN(nn.Module):
    
    #모델에 사용되는 layer들을 정의
    def __init__(self):
        
        #nn.Module 클래스의 상속을 상속받는다.
        super(CNN,self).__init__()

 

첫번째 convolutional layer를 정의

 

pytorch에서 사용하는 conv layer는 기본적으로 nn.Conv2d를 사용

 

기본 옵션은 nn.Conv2d(in_channels = , out_channels = , kernel_size = , stride = )

 

in_channels는 입력 tensor의 채널 크기

 

out_channels는 출력 tensor의 채널 크기

 

kernel_size는 filter의 크기

 

stride, padding은 각각 stride, padding의 크기이고 기본값은 각각 1,0

 

 

kernel_size 같은 경우 값 하나를 주면 가로 세로가 같은 kernel을 만들고, (a,b) tuple 형태로 주면 b*a 형태의 filter를 만드는

 

#nn.Module은 pytorch 모델 클래스가 상속받는 모듈 클래스
class CNN(nn.Module):
    
    #모델에 사용되는 layer들을 정의
    def __init__(self):
        
        #nn.Module 클래스의 상속을 상속받는다.
        super(CNN,self).__init__()

        #in_channels, out_channels, kernel_size, stride

        #in_channels는 input tensor의 channel
        #out_channels는 output tensor의 channel
        #kernel_size는 kernel의 크기

        self.conv1 = nn.Conv2d(in_channels = 1, out_channels = 32, kernel_size = 3, stride = 1)

 

두번째로 통과할 conv2 layer를 정의

 

input tensor가 conv1을 통과하고 나오면 channel이 32인데...

 

이렇게 나온 tensor는 conv2를 통과하므로, conv2의 in_channels는 반드시 conv1의 out_channels와 같아야함

 

#nn.Module은 pytorch 모델 클래스가 상속받는 모듈 클래스
class CNN(nn.Module):
    
    #모델에 사용되는 layer들을 정의
    def __init__(self):
        
        #nn.Module 클래스의 상속을 상속받는다.
        super(CNN,self).__init__()

        #in_channels, out_channels, kernel_size, stride

        #in_channels는 input tensor의 channel
        #out_channels는 output tensor의 channel
        #kernel_size는 kernel의 크기, 값 하나로 주면 가로 세로가 같은 kernel, tuple로 주면 세로*가로 형태

        self.conv1 = nn.Conv2d(in_channels = 1, out_channels = 32, kernel_size = 3, stride = 1)

        #conv1을 통과한 tensor의 channel이 32이고 이것이 conv2를 통과하므로..
        #conv2의 in_channels는 32여야함
        self.conv2 = nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 3, stride = 1)

 

적절한 확률을 가지는 dropout layer를 정의한다.

 

nn.Dropout2d로 정의 가능

 

 

#nn.Module은 pytorch 모델 클래스가 상속받는 모듈 클래스
class CNN(nn.Module):
    
    #모델에 사용되는 layer들을 정의
    def __init__(self):
        
        #nn.Module 클래스의 상속을 상속받는다.
        super(CNN,self).__init__()

        #in_channels, out_channels, kernel_size, stride

        #in_channels는 input tensor의 channel
        #out_channels는 output tensor의 channel
        #kernel_size는 kernel의 크기, 값 하나로 주면 가로 세로가 같은 kernel, tuple로 주면 세로*가로 형태

        self.conv1 = nn.Conv2d(in_channels = 1, out_channels = 32, kernel_size = 3, stride = 1)

        #conv1을 통과한 tensor의 channel이 32이고 이것이 conv2를 통과하므로..
        #conv2의 in_channels는 32여야함
        self.conv2 = nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 3, stride = 1)

        #dropout layer 정의

        self.dropout1 = nn.Dropout2d(0.25)
        self.dropout2 = nn.Dropout2d(0.5)

 

 

fully connected layer를 정의

 

nn.Linear()함수를 이용해 정의 가능

 

 

in_feature는 들어오는 tensor의 size, out_feature는 output tensor의 size

 

fully connected layer1은 9216 크기의 tensor가 128 크기의 tensor로 변환

 

2번째 마지막 fully connected layer2는 이전 fc1의 out_features의 128이 들어가야하므로.. in_features = 128

 

나올때 out_features는 MNIST의 0~9인 분류 클래스 개수인 10으로 설정해야함

 

#nn.Module은 pytorch 모델 클래스가 상속받는 모듈 클래스
class CNN(nn.Module):
    
    #모델에 사용되는 layer들을 정의
    def __init__(self):
        
        #nn.Module 클래스의 상속을 상속받는다.
        super(CNN,self).__init__()

        #in_channels, out_channels, kernel_size, stride

        #in_channels는 input tensor의 channel
        #out_channels는 output tensor의 channel
        #kernel_size는 kernel의 크기, 값 하나로 주면 가로 세로가 같은 kernel, tuple로 주면 세로*가로 형태

        self.conv1 = nn.Conv2d(in_channels = 1, out_channels = 32, kernel_size = 3, stride = 1)

        #conv1을 통과한 tensor의 channel이 32이고 이것이 conv2를 통과하므로..
        #conv2의 in_channels는 32여야함
        self.conv2 = nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 3, stride = 1)

        #dropout layer 정의

        self.dropout1 = nn.Dropout2d(0.25)
        self.dropout2 = nn.Dropout2d(0.5)

        #fullyt connected layer 정의

        self.fc1 = nn.Linear(in_features = 9216, out_features = 128)

        #마지막 layer의 out_features는 MNIST의 분류 클래스가 0~9로 10개이므로.. 10으로
        #당연히 in_features는 이전 fc1의 out_features와 같게
        self.fc2 = nn.Linear(in_features = 128, out_features = 10)

 

10. 모델 구성 -forward 메소드 구성-

 

feed forward 연산을 수행하는 forward 메소드를 정의

 

입력 텐서가 x라고 생각하고 x가 conv1, conv2, maxpooling, flatten, fc1, fc2를 차례대로 통과하도록

 

여기서는 feature size를 다 알려줘서 에러가 없겠지만

 

직접 설계할때는 여기에 print(x.shape)를 해보면서... layer의 size를 직접 설정해야함

 

F.max_pool2d 옵션

 

 

 

torch.flatten 옵션

 

 

 

#nn.Module은 pytorch 모델 클래스가 상속받는 모듈 클래스
#__init__ 메소드와 forward 메소드를 반드시 설정해야함
class CNN(nn.Module):
    
    #모델에 사용되는 layer들을 정의
    def __init__(self):
        
        #nn.Module 클래스의 상속을 상속받는다.
        super(CNN,self).__init__()

        #in_channels, out_channels, kernel_size, stride

        #in_channels는 input tensor의 channel
        #out_channels는 output tensor의 channel
        #kernel_size는 kernel의 크기, 값 하나로 주면 가로 세로가 같은 kernel, tuple로 주면 세로*가로 형태

        self.conv1 = nn.Conv2d(in_channels = 1, out_channels = 32, kernel_size = 3, stride = 1)

        #conv1을 통과한 tensor의 channel이 32이고 이것이 conv2를 통과하므로..
        #conv2의 in_channels는 32여야함
        self.conv2 = nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 3, stride = 1)

        #dropout layer 정의

        self.dropout1 = nn.Dropout2d(0.25)
        self.dropout2 = nn.Dropout2d(0.5)

        #fullyt connected layer 정의

        self.fc1 = nn.Linear(in_features = 9216, out_features = 128)

        #마지막 layer의 out_features는 MNIST의 분류 클래스가 0~9로 10개이므로.. 10으로
        #당연히 in_features는 이전 fc1의 out_features와 같게
        self.fc2 = nn.Linear(in_features = 128, out_features = 10)
    
    #feed forward 연산을 수행하는 forward 메소드 정의
    def forward(self, x):
        
        x = self.conv1(x)
        
        x = self.conv2(x)
        
        #max_pooling
        x = F.max_pool2d(x,2)
        
        #flatten
        x = torch.flatten(x,1)

        x = self.fc1(x)

        x = self.fc2(x)

 

 

이렇게만 하면 끝이 아니고, layer 통과하는 중간중간에 activation function을 넣어줘야 비선형 deep layer가 된다는건 이미 배운 사실

 

자주쓰는건 ReLu함수이므로.. F.relu()함수를 사용

 

 

conv1, conv2, fc1 다음에 F.relu()를 사용해주자.

 

    #feed forward 연산을 수행하는 forward 메소드 정의

    #layer를 통과시키고
    #중간중간에 활성함수를 넣고

    def forward(self, x):
        
        x = self.conv1(x)

        x = F.relu(x)
        
        x = self.conv2(x)

        x = F.relu(x)

        #max_pooling
        x = F.max_pool2d(x,2)

        #flatten
        x = torch.flatten(x,1)

        x = self.fc1(x)

        x = F.relu(x)

        x = self.fc2(x)

 

그리고 중간에 과적합 방지를 위한 regularization으로 dropout layer를 넣어주기도 한다.

 

    #feed forward 연산을 수행하는 forward 메소드 정의

    #layer를 통과시키고
    #중간중간에 활성함수를 넣고
    #중간중간에 dropout 등을 넣어준다

    def forward(self, x):
        
        x = self.conv1(x)

        #activation
        x = F.relu(x)
        
        x = self.conv2(x)

        x = F.relu(x)

        #max_pooling
        x = F.max_pool2d(x,2)

        #dropout
        x = self.dropout1(x)

        #flatten
        x = torch.flatten(x,1)

        x = self.fc1(x)

        x = F.relu(x)

        x = self.dropout2(x)

        x = self.fc2(x)

 

마지막에 fc2 layer를 통과하고 나서, 분류 확률 벡터로 얻을려면 softmax 함수로 계산해줘야 한다.

 

F.log_softmax()를 사용하면, 일반 softmax보다 연산 속도를 높여줄 수 있다

 

 

 

최종 forward 메소드는...

 

    #feed forward 연산을 수행하는 forward 메소드 정의

    #layer를 통과시키고
    #중간중간에 활성함수를 넣고
    #중간중간에 dropout 등을 넣어준다

    def forward(self, x):
        
        x = self.conv1(x)

        #activation
        x = F.relu(x)
        
        x = self.conv2(x)

        x = F.relu(x)

        #max_pooling
        x = F.max_pool2d(x,2)

        #dropout
        x = self.dropout1(x)

        #flatten
        x = torch.flatten(x,1)

        x = self.fc1(x)

        x = F.relu(x)

        x = self.dropout2(x)

        x = self.fc2(x)

        #확률 벡터로 바꾸기 위해 softmax를 적용
        #log softmax를 사용하면 연산속도를 높여줄 수 있음
        output = F.log_softmax(x,dim=1)

        return output

 

 

11. optimizer, loss function 정의

 

model = CNN().to(device)를 수행하면, CNN 클래스의 인스턴스를 생성하고

 

.to(device)를 수행하면, 모델 인스턴스를 device에 올릴 수 있다

 

optimizer와 loss function을 정의

 

성능이 달라질 수 있으므로 적절한 loss와 optimizer를 여러개 사용해볼 수 있음

 

-------------------------------------------------------------------------------------------------------------------------------

 

optimizer에 따라 학습시간이 천지차이

 

adam이 확실히 빠르고 좋아서 거의 항상 무난한 선택

 

그런데 시간이 충분히 있으면 sgd가 더 좋을 수도 있다

 

sgd같은 경우 loss가 오차제곱합으로 strong peak에서는 더 잘 맞추려고 노력하지만 weak peak에서는 그렇지 않아서 weak peak부분 맞춰지는 속도가 느리다

 

-------------------------------------------------------------------------------------------------------------------------------

 

다중 클래스 분류 문제이므로 cross entropy를 사용

 

optim.Adam()으로 Adam optimizer 사용 가능

 

model.parameters()를 하면, 모델의 parameter를 가져오고

 

lr = learning_rate를 하면, learning rate를 optimizer에 넣어준다

 

#CNN 클래스로 모델 인스턴스 생성
#to(device)를 수행하면, 모델 인스턴스를 device에 올릴 수 있다

model = CNN().to(device)

#optimizer 정의
optimizer = optim.Adam(model.parameters(), lr = learning_rate)

#loss function
#다중 클래스 분류 문제이므로, cross entropy 사용
criterion = nn.CrossEntropyLoss()

 

생성한 모델이 어떤 형태인지 print()로 간단히 확인 가능

 

#모델 확인

print(model)

"""
CNN(
  (conv1): Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1))
  (dropout1): Dropout2d(p=0.25, inplace=False)
  (dropout2): Dropout2d(p=0.5, inplace=False)
  (fc1): Linear(in_features=9216, out_features=128, bias=True)
  (fc2): Linear(in_features=128, out_features=10, bias=True)
)
"""

 

 

12. 모델 학습

 

model.train()을 통해 모델을 학습모드로 바꿔줌

 

이유가 있었는데 생각이 안나네

 

언젠가 공부했던 자료 복습할때 다시 보는 날이 있겠지

 

#모델을 학습 모드로 바꿔준다
model.train()

 

전체 epoch_num만큼 반복 학습을 수행..

 

#모델을 학습 모드로 바꿔준다
model.train()

i = 0

#epoch_num만큼 반복학습 수행
for epoch in range(epoch_num):

 

각 epoch 내에서, train_loader를 순회하여, 배치마다 학습을 수행한다

 

#모델을 학습 모드로 바꿔준다
model.train()

i = 0

#epoch_num만큼 반복학습 수행
for epoch in range(epoch_num):
    
    #각 epoch내에서 train_loader를 순회
    for data,target in train_loader:

 

train_loader에는 배치, 배치내의 데이터 정답들이 들어있고

 

model이 gpu에 올라가있으면, 데이터도 gpu에 올려줘야함

 

#모델을 학습 모드로 바꿔준다
model.train()

i = 0

#epoch_num만큼 반복학습 수행
for epoch in range(epoch_num):
    
    #각 epoch내에서 train_loader를 순회
    for data,target in train_loader:
        
        #모델에 할당된 device에 data,target들도 할당해줘야함

        data = data.to(device)
        target = target.to(device)

 

optimizer.zero_grad()를 수행하면, 이전 반복에서 계산되어 저장된 gradient를 초기화해줌

 

그리고 output = model(data)를 수행하면, data를 model에 통과시켜 forward 메소드를 수행하여, output을 계산

 

#모델을 학습 모드로 바꿔준다
model.train()

i = 0

#epoch_num만큼 반복학습 수행
for epoch in range(epoch_num):
    
    #각 epoch내에서 train_loader를 순회
    for data,target in train_loader:
        
        #모델에 할당된 device에 data,target들도 할당해줘야함

        data = data.to(device)
        target = target.to(device)

        #optimizer에 저장된 gradient 초기화
        optimizer.zero_grad()

        #모델에 data 통과시켜 output 계산
        output = model(data)

 

criterion(output, target)을 수행하면, output과 target사이의 loss값을 계산해줌

 

loss.backward()를 수행하면, gradient를 계산

 

optimizer.step()를 수행하면, 계산한 gradient를 이용해 모델의 가중치를 업데이트

 

#모델을 학습 모드로 바꿔준다
model.train()

i = 0

#epoch_num만큼 반복학습 수행
for epoch in range(epoch_num):
    
    #각 epoch내에서 train_loader를 순회
    for data,target in train_loader:
        
        #모델에 할당된 device에 data,target들도 할당해줘야함

        data = data.to(device)
        target = target.to(device)

        #optimizer에 저장된 gradient 초기화
        optimizer.zero_grad()

        #모델에 data 통과시켜 output 계산
        output = model(data)

        #loss 계산
        loss = criterion(output,target)

        #gradient계산

        loss.backward()

        #가중치 업데이트
        optimizer.step()

        #학습이 잘 되고 있는지 1000번째 배치마다, loss가 어떻게 변하는지 확인
        if i % 1000 == 0:
            
            print('Train Step: {}\tLoss: {:.3f}'.format(i,loss.item()))
        
        i += 1

 

그리고 학습이 잘 되고 있는지 1000번째 배치마다 loss 값을 계산해서 출력해 확인해준다

 

다음이 기본 형태라고 생각

 

optimizer.zero_grad() > output = model(data) > loss = criterion(output,target) > loss.backward() > optimizer.step()

 

"""
Train Step: 0	Loss: 2.311
Train Step: 1000	Loss: 0.194
Train Step: 2000	Loss: 0.253
Train Step: 3000	Loss: 0.077
Train Step: 4000	Loss: 0.246
Train Step: 5000	Loss: 0.014
Train Step: 6000	Loss: 0.126
Train Step: 7000	Loss: 0.062
Train Step: 8000	Loss: 0.021
Train Step: 9000	Loss: 0.144
Train Step: 10000	Loss: 0.015
Train Step: 11000	Loss: 0.039
Train Step: 12000	Loss: 0.009
Train Step: 13000	Loss: 0.040
Train Step: 14000	Loss: 0.151
Train Step: 15000	Loss: 0.027
Train Step: 16000	Loss: 0.019
Train Step: 17000	Loss: 0.052
"""

 

일관적으로 줄어들지는 않더라도, 전반적으로 loss가 줄어들고 있어서 학습이 잘 진행되고 있다고 생각할 수 있다.

 

 

13. 모델 평가

 

학습이 끝나면, 테스트 데이터로 모델의 성능을 평가

 

먼저 model.eval()로 모델을 평가 모드로 바꿔줘야함

 

이러면 dropout도 안쓰고 batch normalization이 test용으로 바뀐다네

 

train모드랑 test모드에서 dropout이랑 batch normalization 등등이 달라야해서 나눠진다고 한걸로 기억이 대충 나는데..

 

자료가 있었는데 복습하다보면 다시 볼 수 있는 날이 있겠지

 

#모델을 평가모드로 전환
#dropout, batchnormalization을 test 모드로 바꿔줌
model.eval()

 

테스트 단계에서는 gradient를 계산하지 않도록 with torch.no_grad():를 사용해서 메모리를 절약해주고 계산속도를 증가시킴

 

안쓰면 GPU 터질수도 있음

 

#모델을 평가모드로 전환
#dropout, batchnormalization을 test 모드로 바꿔줌
model.eval()

correct = 0 #정답 개수

#gradient를 계산하지 않도록 해준다.
#계산속도 증가, 메모리 절약
with torch.no_grad():

 

test_loader를 순회해서 배치마다 평가를 수행

 

데이터와 target을 모델과 동일한 device에 올리고

 

#모델을 평가모드로 전환
#dropout, batchnormalization을 test 모드로 바꿔줌
model.eval()

correct = 0 #정답 개수

#gradient를 계산하지 않도록 해준다.
#계산속도 증가, 메모리 절약
with torch.no_grad():
    
    #test_loader를 순회해서 배치와 label을 가져오고
    for data, target in test_loader:
        
        #모델과 동일한 device에 할당
        data = data.to(device)
        target = target.to(device)

 

모델에 넣어서 output을 계산

 

#모델을 평가모드로 전환
#dropout, batchnormalization을 test 모드로 바꿔줌
model.eval()

correct = 0 #정답 개수

#gradient를 계산하지 않도록 해준다.
#계산속도 증가, 메모리 절약
with torch.no_grad():
    
    #test_loader를 순회해서 배치와 label을 가져오고
    for data, target in test_loader:
        
        #모델과 동일한 device에 할당
        data = data.to(device)
        target = target.to(device)

        #output 계산
        output = model(data)

 

 

output은 각 클래스 0,1,2,3,4,5,6,7,8,9에 대해 확률로 계산되어 있다.

 

가장 확률이 높은 부분을 찾아 그것의 인덱스를 계산해서 그것을 예측 클래스로 계산해줘야한다

 

그리고 prediction.eq(target.data)로 target과 prediction을 비교하여, correct에 더해줌

 

#모델을 평가모드로 전환
#dropout, batchnormalization을 test 모드로 바꿔줌
model.eval()

correct = 0 #정답 개수

#gradient를 계산하지 않도록 해준다.
#계산속도 증가, 메모리 절약
with torch.no_grad():
    
    #test_loader를 순회해서 배치와 label을 가져오고
    for data, target in test_loader:
        
        #모델과 동일한 device에 할당
        data = data.to(device)
        target = target.to(device)

        #output 계산
        output = model(data)

        #가장 확률이 높은 부분의 인덱스를 찾아 그것을 예측값 클래스로 전환

        prediction = output.data.max(1)[1]

        #prediction과 target값을 비교하여, correct에 더해준다
        correct += prediction.eq(target.data).sum()

 

 

전체 테스트 데이터에 대해 맞춘 개수의 비율을 통해 정확도 계산

 

#모델을 평가모드로 전환
#dropout, batchnormalization을 test 모드로 바꿔줌
model.eval()

correct = 0 #정답 개수

#gradient를 계산하지 않도록 해준다.
#계산속도 증가, 메모리 절약
with torch.no_grad():
    
    #test_loader를 순회해서 배치와 label을 가져오고
    for data, target in test_loader:
        
        #모델과 동일한 device에 할당
        data = data.to(device)
        target = target.to(device)

        #output 계산
        output = model(data)

        #가장 확률이 높은 부분의 인덱스를 찾아 그것을 예측값 클래스로 전환

        prediction = output.data.max(1)[1]

        #prediction과 target값을 비교하여, correct에 더해준다
        correct += prediction.eq(target.data).sum()

#전체 테스트 데이터 개수 대비 맞춘 개수 비율 계산

print('Test set: Accuracy: {:.2f}%'.format(100*correct/len(test_loader.dataset)))

"""
Test set: Accuracy: 99.05%
"""

 

참조

 

 

[Pytorch] model.eval() vs with torch.no_grad() (velog.io)

 

[Pytorch] model.eval() vs with torch.no_grad()

Pytorch 를 이용해서 모델을 학습한 뒤, 모델을 평가할 때 model.eval() 과 with torch.no_grad() 를 자주 사용한다. 그렇다면 이 두 명령어의 차이는 무엇일까?model.eval은 해당 모델의 모든 레이어가 eval mode

velog.io

 

 

TAGS.

Comments