[PyTorch] 딥러닝 입문 – 간단한 신경망 만들기 ❸

이 글은    [Must Have] 텐초의 파이토치 딥러닝 특강   에서 발췌했습니다.

골든래빗 출판
이종민(텐초) 지음

딥러닝(Deep Learning)이 무엇인지, 어떤 기법이 있는지 알아보고 나서 파이토치(PyTorch) 기본 코딩 스타일을 알아봅니다. 이어서 딥러닝을 수행하는 프로세스와 최소한의 통계 지식, 시각화 기법을 알아봅니다. 빠르게 딥러닝을 알아가는 시간이 될 겁니다.

딥러닝 입문은 총 3개 장입니다. 1장에서 딥러닝 한눈에 살펴보기, 2장에서 인공 신경망 ANN 이해하기, 3장에서 간단한 신경망 만들기를 학습합니다.

간단한 신경망 만들기 ❸

 

가중치를 이용해 학습하는 신경망을 어떻게 만드는지 사인 함수 예측, 보스턴 집값 예측(회귀 분석), 손글씨 분류(다중분류) 신경망을 만들며 알아봅시다. 이번 장에서는 신경망의 동작뿐 아니라, 실제로 오차를 계산하고 직접 역전파해 가중치를 수정합니다. 또한 이전에 등장하지 않았던 손실 함수인 평균 제곱 오차와 크로스 엔트로피 오차에 대해서 설명합니다.

3장 간단한 신경망 만들기는 총 3편입니다.

 

3. 손글씨 분류하기 : 다중분류

바로 앞에서 다룬 보스턴 집값 예측에서는 집값 하나만 예측했습니다. 집값과 동시에 지역, 집주인 나이를 예측하려면 어떻게 해야 될까요? 각각을 학습하는 여러 모델을 만들어야 할까요? 아닙니다. 같은 데이터를 가지고 여러 모델을 학습하는 건 좀 낭비입니다. 값 하나를 출력해 출력값을 범위로 나눠 다른 정보를 예측하는 방법은 어떤가요? 예측하고자 하는 모든 정보를 숫자 하나에 집어넣는 방법 역시 적절하지 않아 보입니다. 여러 값을 출력하면 어떨까요! 집값, 지역, 나이를 예측하도록 모델을 설계하는 거죠. 그러면 모델 하나로 여러 정보를 예측할 수 있을 겁니다. 이렇듯 인공 신경망의 출력이 늘 하나일 필요는 없습니다.

 

▼ 단일 출력(좌)과 다중 출력(우)

 

또한 회귀와 분류는 값을 직접 예측하느냐, 어떤 범주에 들어가느냐의 문제이므로 서로 상관없어 보이지만 사실 회귀와 분류는 매우 비슷한 문제입니다. 신경망의 출력을 그대로 사용하면 회귀, 출력을 확률 분포로 바꿔주면 분류 문제가 됩니다.

 

▼ 회귀와 분류의 차이

 

회귀 모델과 분류 모델의 차이를 나타낸 그림입니다. 회귀 모델과 분류 모델은 신경망 구조가 완전히 동일하지만 분류 모델은 최종적으로 출력을 소프트맥스 함수를 통해 확률 분포로 변환합니다. 그대로 출력값을 이용해도 상관없지만 분류 문제에서 출력을 확률 분포로 바꿔주는 까닭은 출력값의 범위를 0과 1 사이로 제한해야 해석이 용이하기 때문입니다.

이번 절에서는 여러 출력을 갖는 신경망을 이용해 다중분류 문제를 알아보겠습니다. 0부터 9까지의 10개의 숫자를 손글씨로 표현한 손글씨 데이터셋을 사용해 실습하겠습니다.

 

▼ 실습 예제 소개

 

3.1 데이터 살펴보기

손글씨 데이터셋을 사용해 다중 출력을 연습해봅시다. MNIST는 0부터 9까지 10가지 손글씨 이미지로 구성된 데이터셋입니다. 우선은 데이터를 내려받고 모양을 살펴보겠습니다.

 

▼ 손글씨 데이터 살펴보기

import matplotlib.pyplot as plt

from torchvision.datasets.mnist import MNIST
from torchvision.transforms import ToTensor

# ❶ 학습용 데이터와 평가용 데이터 분리
training_data = MNIST(root="./", train=True, download=True, transform=ToTensor())
test_data = MNIST(root="./", train=False, download=True, transform=ToTensor())

print(len(training_data)) # 학습에 사용할 데이터 개수
print(len(test_data)) # 평가에 사용할 데이터 개수

for i in range(9): # 샘플 이미지 9개 출력
   plt.subplot(3, 3, i+1)
   plt.imshow(training_data.data[i])
plt.show()

60000
10000

 

학습에 사용할 데이터 개수, 평가에 사용할 데이터 개수, 그리고 9개의 샘플 이미지를 출력합니다.

 

❶ MNIST 객체를 초기화할 때 train 옵션에 따라 학습용 데이터를 불러올지, 평가용 데이터를 불러올지 결정합니다. True는 학습용, False는 평가용 데이터를 불러옵니다. download 옵션은 데이터를 내려받을지 결정하는 파라미터입니다. 데이터를 내려받고 싶으면 True를 넣어주세요. transform은 데이터를 변형하고 싶을 때 넣어주는 파라미터입니다. 파이토치 모델의 입력으로는 파이토치 텐서만을 이용할 수 있습니다. 파이토치는 최적화가 굉장히 복잡하게 정의되어 있어서 자료형이 다르면 동작할 수 없습니다. 지금은 모든 데이터가 파이썬 이미지 파일로 저장되어있으므로 ToTensor( ) 함수를 이용해 파이토치 텐서로 바꿔줄 겁니다. 파이토치는 텐서만을 입력으로 받기 때문입니다.

 

▼ 새로 등장한 함수

 

3.2 데이터 불러오기

파이토치에는 학습에 사용할 배치를 자동으로 반환하는 Dataloader( )라는 메서드가 있습니다. 원하는 배치 크기, 데이터를 섞어서 사용할지, CPU 코어를 몇 개나 이용할지 등을 지정할 수 있습니다. 파이토치가 제공하는 라이브러리를 이용해 코드를 작성해봅시다.

DataLoader 객체에 데이터를 넘겨주면 우리가 원하는 배치 크기, 셔플 여부 등을 간단하게 설정할 수 있습니다. 이번 코드에서는 파이토치 DataLoader 객체를 이용해 데이터로더를 만들겠습니다.

 

▼ 학습용 데이터와 평가용 데이터의 데이터로더 정의

from torch.utils.data.dataloader import DataLoader

train_loader = DataLoader(training_data, batch_size=32, shuffle=True)

# ❶ 평가용은 데이터를 섞을 필요가 없음
test_loader = DataLoader(test_data, batch_size=32, shuffle=False)

 

❶ 학습용 데이터를 섞지 않고 학습하면 하나의 범주만을 출력하도록 학습될 가능성이 있습니다. 예를 들면 학습용 데이터의 첫 6,000장의 이미지가 0을 나타내는 이미지라면, 모델은 계속해서 0을 출력하므로 나중에는 어떤 값이 들어와도 0이 나오게 됩니다. 따라서 학습용 데이터는 학습하기 전에 섞어주어서 모든 범주의 데이터가 골고루 나오게 해야 합니다. 그러나 평가용 데이터는 이미 학습이 된 모델을 이용해 순서대로 예측값과 정답을 비교하는 과정이므로 데이터를 섞을 필요가 없습니다.

학습용 데이터는 train_loader에, 평가용 데이터는 test_loader에 저장했습니다.

 

▼ 새로 등장한 함수

 

3.3 데이터 불러오기

데이터를 다루는 작업이 끝이 났으니, 이제는 이미지를 분류할 모델을 만들 시간이 됐습니다. 이미지 학습에 드는 계산량이 크므로 GPU를 이용하겠습니다. 학습이 끝난 모델을 저장하고, 불러온 다음 평가를 합니다. 학습 루프를 다음 같이 구성하겠습니다.

 

▼ 학습 루프

 

이미지는 가로축과 세로축으로 이루어져 있는 2차원 데이터입니다. 인공 신경망은 모든 값이 일렬로 나란히 있는 배열을 입력으로 갖습니다. 따라서 인공 신경망의 입력으로 2차원 이미지를 넣고 싶다면, 1차원으로 모양을 변경해야 합니다.

 

▼ 2차원 이미지를 1차원으로 변경

 

미리 설계한 학습 루프에 맞춰서 학습 코드를 작성해봅시다.

 

▼ 손글씨 분류 모델 학습하기

import torch
import torch.nn as nn

from torch.optim.adam import Adam

# ❶ 학습에 사용할 프로세서 지정
device = "cuda" if torch.cuda.is_available() else "cpu"

model = nn.Sequential(
   nn.Linear(784, 64),
   nn.ReLU(),
   nn.Linear(64, 64),
   nn.ReLU(),
   nn.Linear(64, 10)
)
model.to(device) # 모델의 파라미터를 GPU로 보냄

lr = 1e-3
optim = Adam(model.parameters(), lr=lr)

for epoch in range(20):
   for data, label in train_loader:
       optim.zero_grad()
       # ❷ 입력 데이터 모양을 모델의 입력에 맞게 변환
       data = torch.reshape(data, (-1, 784)).to(device)
       preds = model(data)

       loss = nn.CrossEntropyLoss()(preds, label.to(device)) # ❸ 손실 계산
       loss.backward() # 오차 역전파
       optim.step() # 최적화 진행

   print(f"epoch{epoch+1} loss:{loss.item()}")

# ❹ 모델을 MNIST.pth라는 이름으로 저장
torch.save(model.state_dict(), "MNIST.pth")

 

❶ CPU와 GPU 중 하나를 선택합니다. torch.cuda.is_available( )은 GPU를 사용할 수 있으면 True를, 사용할 수 없으면 False를 반환합니다.

 

▼ 새로 등장한 함수

 

❷ 이미지를 일렬로 펴주는 변환을 실행합니다. (-1, 784)의 -1은 개수를 상관하지 않겠다는 뜻입니다. MNIST의 이미지는 모두 28×28(784픽셀) 흑백 이미지이므로 채널에 관한 정보가 없습니다. 따라서 높이×너비 모양이므로, -1을 입력하면 배치 크기가 입력됩니다. 예를 들어 이미지 64장을 불러왔다면, 입력 텐서는 (64, 28, 28)과 같은 모양을 하고 있을 겁니다. 이제 이미지를 일렬로 펴주기 위해 (-1, 784) 모양으로 변환합니다. -1이므로, 즉 배치 크기인 64가 대입되어 최종적으로 (64, 784)와 같은 모양이 됩니다. MLP 모델은 벡터만을 입력받을 수 있기 때문에, 손글씨 이미지를 벡터로 변환해준다고 생각하면 됩니다. to( ) 메서드는 device에서 텐서를 계산하겠다는 의미입니다. device가 다른 두 텐서는 서로 연산이 불가능하기 때문에 모든 텐서의 device를 맞춰주세요.

 

▼ 새로 등장한 함수

 

❸ 손실 함수를 정의합니다. 회귀에는 MSE를, 분류에는 CE를 자주 사용합니다. MSE는 평균 제곱 오차로 두 값의 차이의 제곱을 나타내고, CE는 크로스 엔트로피 손실 함수로 확률 분포의 차이를 나타냅니다. 회귀는 임의의 값을 예측하는 것이기 때문에 MSE를, 분류는 확률 분포를 예측하는 것이므로 CE를 사용합니다.

 

💡크로스 엔트로피(Cross Entropy, CE): 교차 엔트로피라고도 합니다. 두 확률분포가 서로 얼마나 다른가를 나타내는 함수입니다.

 

집값 예측에 비해 학습 시간이 더 깁니다. 모델이 더 복잡해지고 더 많은 데이터를 사용했기 때문입니다.

 

❹ 소스 코드와 같은 경로에 MNIST.pth라는 파일(모델)이 생겼을 겁니다. 파이토치의 모델은 .pth 확장자로 저장됩니다.

 

💡TIP: 파이토치 모델의 확장자는 .pt와 .pth 모두 가능합니다. pth 확장자는 파이썬의 Path 파일과 확장자가 동일하기 때문에 피하자는 의견이 나오고 있습니다만 아직 표준으로 제정되지 않았기 때문에 현재는 둘 다 사용하고 있습니다.

 

▼ 새로 등장한 함수

 

3.4 모델 성능 평가하기

평가용 데이터셋을 이용해 분류의 정확도를 구해볼 겁니다. 모델을 불러와 평가용 데이터 중 몇 개를 정확하게 분류했는지 확인하겠습니다.

 

▼ 모델의 성능 평가

# ❶ 모델 가중치 불러오기
model.load_state_dict(torch.load("MNIST.pth", map_location=device))

num_corr = 0 # 분류에 성공한 전체 개수

with torch.no_grad(): # ❷ 기울기를 계산하지 않음
   for data, label in test_loader:
       data = torch.reshape(data, (-1, 784)).to(device)

       output = model(data.to(device))
       preds = output.data.max(1)[1] # ❸ 모델의 예측값 계산

       # ❹ 올바르게 분류한 개수
       corr = preds.eq(label.to(device).data).sum().item()
       num_corr += corr

print(f"Accuracy:{num_corr/len(test_data)}") # 분류 정확도 출력

Accuracy:0.9767

 

❶ 모델 파일을 불러옵니다. map_location은 불러올 위치를 말하는데 기본적으로 CPU에 불러오지만, 원하는 장치에 불러올 수 있습니다. 여러분의 환경에 따라 GPU를 갖고 있다면 GPU에, 없다면 CPU에 불러옵니다. 코랩에서 실습을 진행하고 계신 분들은 0장을 참고하여 런타임 종류를 설정해주세요. 아무 설정을 하지 않는다면 코랩은 CPU를 사용합니다.

❷ no_grad( )를 호출하면 기울기를 계산하지 않습니다. 학습할 때는 가중치를 업데이트하는 데 기울기가 필요했지만, 평가는 가중치를 바꿀 필요가 없으므로 기울기를 계산하지 않습니다. 메모리와 계산량이 줄어드니까 평가할 때는 반드시 호출합시다.

❸ max(1)[1]은 가장 높은 값을 갖는 위치를 반환합니다. 모든 텐서의 차원은 배치, 클래스 순서입니다. 즉, max(0)은 배치에서 가장 높은 값을, max(1)은 클래스 차원에서 가장 높은 값을 반환하라는 뜻입니다. 또한 max( )는 최대 예측값, 최대 예측값의 인덱스를 묶어서 리스트로 반환하므로 max(1)[1]을 호출하는 것으로 모든 배치에 대해 가장 높은 클래스값을 갖는 인덱스만을 가져옵니다.

❹ eq( )는 값이 같으면 1을, 다르다면 0을 반환하는 함수입니다. preds 안에 모델의 예측값이 들어 있기 때문에 label과 eq( ) 연산을 해주고 sum( ) 함수로 합을 구합니다. 실젯값과 예측값이 몇 개나 일치하는가를 알 수 있습니다. 코드가 복잡하니 다음 동작 순서를 참고하시기 바랍니다.

 

 

분류 정확도가 대략 97% 정도가 나왔습니다. 대부분의 이미지를 올바르게 분류할 수 있는 수치이므로 학습이 잘된 겁니다. 일반적으로 분류 정확도가 92% 이상이면 학습이 잘 이루어진 것으로 판단합니다. 80% 미만이면 모델을 제품화할 수 없습니다.

 

간단한 신경망 만들기 마무리

파이토치를 이용해 간단한 인공 신경망을 만들어보았습니다. 직접적인 값을 예측하면 회귀 문제, 어느 레이블에 속하는지에 대한 확률 분포를 예측하면 분류 문제입니다. 딥러닝 학습의 흐름을 명심해주세요.

 

 

되짚어보기

  1. 사인 함수 예측하기: 랜덤 계수와 3차 다항식 계수를 학습해 사인 함수를 예측합니다. 랜덤 계수를 사용하면 사인 함수와 전혀 닮지 않았지만 학습된 3차 다항식 계수를 사용하니 차이가 거의 없습니다. 계수가 3차 다항식의 함수값을 사인 함수와 비슷하도록 하기 때문에 좋은 결과가 난 겁니다.
  2. 보스턴 집값 예측하기(회귀 분석): 보스턴 집값 데이터를 간단한 MLP 모델을 이용해 학습합니다. 데이터가 순차적으로 순전파되는 신경망이므로, nn.Sequential 클래스를 이용해 신경망을 구성했습니다. 손실 함수로는 실제 집값과 모델이 예측한 집값의 평균 제곱오차를 사용했습니다. 실제 집값과 크게 차이가 나지 않았습니다.
  3. 손글씨 분류하기(다중분류): 손글씨 데이터를 MLP로 학습했습니다. MLP는 1차원 벡터만 입력받을 수 있기 때문에 2차원 이미지를 평탄화해서 신경망의 입력으로 사용했습니다. 파이토치가 제공하는 Dataset 객체와 Dataloader를 사용해 데이터를 호출했습니다. 손글씨를 학습한 후 정답과 예측 간의 크로스 엔트로피 오차를 확인해 정확도를 계산했습니다. 약 97% 정도의 정확도가 나왔습니다.

 

지금까지 ‘[PyTorch] 딥러닝 입문’을 통해 인공지능의 배경지식과 신경망을 알아보고 나서 간단한 신경망을 만들어보았습니다. 이미 배경지식을 아는 분은 쉬운 내용일 수 있지만, 다시 돌아보는 기회가 되어도 좋겠습니다. 파이토치와 딥러닝 시작에 도움이 되면 좋겠습니다.

책 내용 중 궁금한 점, 공부하다가 막힌 문제 등 개발 관련 질문이 있으시다면

언제나 열려있는 <[Must Have] 텐초의 파이토치 딥러닝 특강> 저자님의

카카오채널로 질문해주세요!

이종민(텐초) 

일본 JAIST 정보과학 석사. 동경의 딥러닝 엔지니어로 Ghelia에서 B2B 인공지능 솔루션을 개발 중이다. 딥러닝의 유용함을 널리 알리고자 유튜버로  활동하고 글을 쓰고 책을 집필합니다. 쉬운 그림을 이용해 10초만에 핵심을 전달하자는 의미에서 닉네임을 텐초로 지어 활동하고 있습니다.

– SNS : www.youtube.com/c/텐초

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내