[책] 펭귄브로의 3분 딥러닝: 파이토치맛

파이토치가 개발되기까지

딥러닝 알고리즘들은 행렬 연산을 반복하게 되는데, 기존 어느 영역의 알고리즘에서도 쉽게 볼 수 없던 많은 계산량이다. 실제 데이터를 이용해 학습할 때는 며칠은 기본이고 몇 달 이상 걸리는 경우도 있다.

이 연산량 문제를 해결하려고 딥러닝 초기 발전을 이끌던 MILA 연구실에서 GPU 를 사용해 행렬연산을 가속하는 Theano 라는 딥러닝 프레임워크를 발표했다.

Why Pytorch?

파이토치는 페이스북 주도로 여러 회사와 대학이 협력해 개발한 오픈소스 소프트웨어로, 머신러닝과 딥러닝 모델을 쉽게 구현할 수 있도록 많은 편의 기능을 제공하는 프레임워크이다.

파이토치는 실행 중에 그래프를 바꿀 수 있고, 이와 같은 동적 계산 그래프 dynamic computational graph 방식은 데이터에 유연한 모델을 만들 수 있다는 장점이 있다.

미래에는 자율주행차 , 게임, 인터넷 등 쌓이는 데이터에 실시간으로 대응하는 동적인 학습 방법이 중요해질 전망이다. 파이토치는 실행 도중 모델을 유연하게 관리할 수 있어 미래지향적이라고 할 수 있다.

텐서

텐서는 파이토치에서 다양한 수식을 계산하는 데 사용하는 가장 기본적인 자료 구조이다. 수학의 벡터나 행렬을 일반화한 개념으로서, 숫자들을 특정한 모양으로 배열한 것이다.

텐서에는 '차원' 또는 '랭크' 라는 개념이 있다.

  • 랭크 0: Scalar
  • 랭크 1: Vector
  • 랭크 2: Matrix
    .
    .

Autograd

Autograd 를 직역하면 '자동 기울기' 정도가 된다. 말 그대로 수식의 기울기를 자동으로 계산한다는 뜻이지만, 왜 이런 계산이 머신러닝이나 딥러닝에 관련이 있는 것일까?

앞 장에서 배웠듯, 머신러닝 모델은 입력된 데이터를 기반으로 학습한다. 아직 충분한 데이터를 보지 못해서 학습이 끝나지 않은 모델은 정답이 아닌 결과를 출력할 가능성이 크다.

이처럼 데이터에 대한 정답 Ground Truth 과 머신러닝 모델이 예측한 결과의 차이를 산술적으로 표현한 것을 거리 Distance 라고 한다.

그리고 학습 데이터로 계산한 거리들의 평균을 오차 loss 라고 일컫는다.

즉, 오차가 작은 머신러닝 모델일 수록 주어진 데이터에 대해 더 정확한 답을 낸다고 볼 수 있따.

오차를 최소화하는데는 여러 알고리즘들이 쓰이지만, 가장 유명하고 많이 쓰이는 알고리즘은 경사하강법 (Gradient Descent) 이다.

경사하강법이란, 오차를 수학 함수로 표현한 후 미분하여 이 함수의 기울기를 구해 오차의 최솟값이 있는 방향을 찾아내는 알고리즘이다. 복잡하지 않은 모델에서의 경사하강법은 numpy 같은 라이브러리만 사용해서 직접 구현할 수도 있지만, 복잡한 인공 신경망 모델에서는 어렵고 머리 아픈 계산을 여러 번 해줘야 한다.

다행히도 파이토치의 Autograd 는 미분 계산을 자동화해 경사하강법을 구현하는 수고를 덜어준다.

신경망 모델 구현하기

간단한 분류 모델 구현하기

분류의 목적은 '정답'을 맞추는 것이다.
기본적인 분류에 사용할 0 과 1 로 된 정답을 가진 비교적 단순한 데이터셋을 생성해보고, 신경망 모델을 학습해 입력된 데이터를 0 과 1 중 알맞은 카테고리로 분류해보자.

from sklearn.datasets import make_blobs
import matplotlib.pyplot as plot
import torch.nn.functional as F
n_dim = 2
x_train, y_train = make_blobs(n_samples=80, n_features=n_dim, centers=[[1,1],[-1,-1],[1,-1],[-1,1]], shuffle=True,
                             cluster_std=0.3)
x_test, y_test = make_blobs(n_samples=20, n_features=n_dim, centers=[[1,1],[-1,-1],[1,-1],[-1,1]], shuffle=True,
                             cluster_std=0.3)

사이킷런의 make_blobs() 를 이용해 데이터를 2차원 벡터 형태로 만들었다. 학습 데이터셋에는 80개, 실험 데이터셋에는 20개의 2차원 벡터 데이터를 담았다.

make_blobs() 함수가 만들어내는 레이블 데이터는 각 데이터 한 점 한 점이 몇 번쨰 클러스터에 속해 있는지 알려주는 인덱스이다.

def label_map(y_, from_, to_):
    y = numpy.copy(y_)
    for f in from_:
        y[y_ == f] = to_
    return y

y_train = label_map(y_train, [0,1], 0)
y_train = label_map(y_train, [2,3], 1)
y_test = label_map(y_test, [0,1], 0)
y_test = label_map(y_test, [2,3], 1)

우리가 학습시킬 신경망은 두 가지 레이블만 예측하는 매우 기본적인 모델이기에 4개의 레이블을 2개로 합쳐보자.

label_map() 이라는 함수를 구현해 0번이나 1번을 레이블로 가진 데이터는 전부 0번 레이블을 갖도록 바꿔준다. 그리고 2번이나 3번 레이블로 가진 데이터는 전부 1번 레이블을 갖도록 바꿔준다.

이 동작을 하고 나면 왼쪽 아래와 오른쪽 위의 데이터는 0을 레이블로, 그리고 왼쪽 위와 오른쪽 아래의 데이터는 1을 레이블로 갖게 된다.

def vis_data(x,y = None, c = 'r'):
    if y is None:
        y = [None]*len(x)
    for x_, y_ in zip(x,y):
        if y_ is None:
            plt.plot(x_[0], x_[1], '*', markerfacecolor='none', markeredgecolor=c)
        else:
            plt.plot(x_[0], x_[1], c+'o' if y_ == 0 else c+'+')
            
plt.figure()
vis_data(x_train, y_train, c='r')
plt.show()

대부분의 신경망 학습은 대량의 학습 데이터를 사용한다. 그러나 이렇게 거대한 학습 데이터를 매번 전부 이용해 오차를 구하고 학습하는 것은 비효율적이다. 그러므로 신경망 모델을 학습시킬 때는 전테 데이터셋을 배치 batch 라는 몇 개의 작은 세트로 나누는 게 보통이다. 한 번 오차를 구할 떄 모든 데이터를 사용하지 말고 하나의 배치만을 쓰도록 말이다.
이번 예제는 80개 뿐인 작은 학습 데이터를 이용하므로 데이터를 특별히 배치대로 나누지 않았다. 즉, 모든 데이터가 한 배치에 들어가 있다고 볼 수 있다.

x_train = torch.FloatTensor(x_train)
x_test = torch.FloatTensor(x_test)
y_train = torch.FloatTensor(y_train)
y_test = torch.FloatTensor(y_test)

방금 생성한 넘파이 벡터 형식 데이터를 파이토치 텐서로 바꿔준다.

파이토치에서 신경망은 보통 다음과 같이 신경망 모듈 (torch.nn.Module) 을 상속받는 파이썬 클래스로 정의한다.

nn.Module 을 상속받으면 파이토치 프레임워크에 있는 각종 도구를 쉽게 적용할 수 있다.

class NeuralNet(torch.nn.Module):
    def __init__(self, input_size, hidden_size):
        super(NeuralNet, self).__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.linear_1 = torch.nn.Linear(self.input_size, self.hidden_size)
        self.relu = torch.nn.ReLU()
        self.linear_2 = torch.nn.Linear(self.hidden_size,1)
        self.sigmoid = torch.nn.Sigmoid()
    def forward(self, input_tensor):
        linear1 = self.linear_1(input_tensor)
        relu = self.relu(linear1)
        linear2 = self.linear_2(relu)
        output = self.sigmoid(linear2)
        return output

Fashion MNIST

Fashion MNIST 는 28x28 픽셀 70000 개의 흑백 이미지로 구성되며 신발, 드레스, 가방 등 총 10가지 카테고리가 존재한다.

딥러닝 모델을 만들고 학습하기에 앞서 데이터를 모아서 가공하는 일 부터 시작해야 한다. 실제 서비스를 만들 때 엔지니어 시간의 대부분은 데이터를 가공하고 파이프라인을 만드는 데 쓰인다.

이미지 데이터를 다루기 위한 파이토치와 토치비전의 모듈에는 다음과 같은 것들이 있다.

  • torch.utils.data:
    데이터셋의 표준을 정의하고 데이터셋을 불러오고 자르고 섞는데 쓰는 도구들이 들어있는 모듈이다. 파이토치 모델을 학습시키기 위한 데이터셋의 표준을 torch.utils.data.Dataset 에 정의한다. Dataset 모듈을 상속하는 파생 클래스는 학습에 필요한 데이터를 로딩해주는 torch.utils.data.DataLoader 인스턴스의 입력으로 사용할 수 있다.
  • torchvision.datasets :
    torch.utils.data.Dataset 을 상속하는 이미지 데이터셋의 모음이다. 패션 아이템 데이터셋이 여기 들어 있다.
  • torchvision.transforms : 이미지 데이터셋에 쓸 수 있는 여러가지 변환 필터를 담고 있는 모듈이다. 예를 들어 텐서로 변환한다든지, 크기 조절, 크롭으로 이미지를 수정할 수 있고, 밝기, 대비 등을 조절하는 데 사용될 수도 있다.
  • torchvision.utils : 이미지 데이터를 저장하고 시각화하기 위한 도구가 들어있는 모듈이다.
from torchvision import datasets, transforms, utils
from torch.utils import data

먼저 이미지를 텐서로 바꿔주는 코드이다. 토치비전의 transforms 는 입력을 변환시키는 도구이다.

transform = transforms.Compose([transforms.ToTensor()])

다음은 토치비전 Transforms 에서 자주 쓰이는 기능이다.

  • ToTensor : 이미지를 파이토치 텐서로 변환.
  • Resize : 이미지 크기 조정.
  • Normalize : 주어진 평균과 표준편차를 이용해 정규화.
  • RandomeHorizontalFlip : 무작위로 이미지의 오른쪽과 왼쪽을 뒤집는 기능.
  • RandomCrop : 이미지를 무작위로 자르는 기능.

다음으로는 FashionMNIST 데이터셋을 가져올 차례이다.

토치비전의 datasets 패키지는 데이터셋을 내려받고, Compose 로 만들어 둔 이미지 변환 설정을 적용하는 데 쓰인다.

CNN

1960년대에 신경 과학자인 데이비드 휴벨과 토르스텐 비젤은 시각을 담당하는 신경세포를 연구했다. 먼저 고양이에게 선 같은 간단한 모양의 이미지를 보여주고 뇌가 어떻게 반응하는지 살폈다.

그 결과 서로 비슷한 이미지들은 고양이 뇌의 특정 부위를 지속적으로 자극하며, 서로 다른 이미지는 다른 부위를 자극한다는 사실을 발견했다.

이 연구는 이미지의 각 부분에 뇌의 서로 다른 부분이 반응하여 전체 이미지를 인식한다는 결론을 낳았다. 이미지가 머리에 들어올 떄 특징을 추출하는 부분이 있는 것이다.

이 현상에 영감을 받아 만든 신경망 모델이 바로 컨볼루션 신경망, 줄여서 CNN 이다. (합성곱 신경망이라고도 한다.)

컴퓨터가 보는 이미지

컴퓨터에서 모든 이미지는 픽셀값들을 가로, 세로로 늘어놓은 행렬로 표현할 수 있다.

일반적인 인공 신경망은 다양한 형태의 입력에 대한 확장성이 떨어진다. 예를 즐어 신발이 중심에 배치된 학습 데이터만 사용하면 신발이 옆으로 조금만 치우쳐져도 예측률이 급격히 떨어진다. 특징을 추출하는 가중치가 가운데만 집중하도록 학습되었기 때문이다.

컨볼루션

사람은 이미지를 보는 순간 이미지 속의 계층적인 특성을 곧바로 인식한다. 픽셀이나 선 같은 저수준의 특징에 집중하지 않더라도 배경, 질감, 움직임, 인식해야 하는 대상 등을 단번에 알아챈다.
무의식적으로 이미지 속의 점과 선 등의 특징을 보고 눈, 코, 입을 인식하고, 이 특징들을 모아 그 대상이 얼굴임을 인식하게 되는 식이다.

컨볼루션의 목적은 이처럼 계층적으로 인식할 수 있도록 단계마다 이미지의 특징을 추출하는 것이다. 각 단계에서는 이미지에 다양한 필터를 적용하여 윤곽선, 질감, 털 등 각종 특징을 추출한다.

필터를 적용할 때 이미지 왼쪽 위에서 오른쪽 밑까지 밀어가며 곱하고 더하는데, 이 작업을 컨볼루션이라고 한다.

예전에는 특징 추출 전문가가 수작업으로 필터들을 설계했는데, 이런 방식은 여러 어려움이 있었다. 그래서 CNN 은 이미지를 추출하는 필터를 학습한다. 필터가 하나의 작은 신경망인 것이다.

CNN 모델

CNN 모델은 일반적으로 컨볼루션 계층, 풀링 계층, 특징들을 모아 최종 분류하는 일반적인 인공 신경망 계층으로 구성된다.

컨볼루션 계층은 이미지의 특징을 추출하는 역할을 하며,
풀링 계층은 필터를 거친 여러 특징 중 가장 중요한 특징 하나를 골라낸다. 덜 중요한 특징을 버리기 떄문에 이미지의 차원이 감소한다. 풀고자 하는 문제에 따라 계층 구성을 달리 할 수 있으며, 컨볼루션 계층만으로 구성된 모델을 만들 수도 있다.

컨볼루션 연산은 이미지를 '겹치는 매우 작은 조각' 으로 쪼개어 필터 기능을 하는 작은 신경망에 적용한다. 이 신경망은 모든 조각에 동일하게 적용되며, 특징을 추출하기 때문에 컨볼루션 필터 혹은 커널이라고 부른다.

커널은 컨볼루션 계층 하나에 여러 개가 존재할 수 있다. 학습 시 필터 행렬의 값은 특징을 잘 뽑을 수 있도록 최적화된다.

컨볼루션은 오른쪽 아래로 움직이며 다음 이미지를 만든다. 움직일 때 한 칸씩 움직일 수도 있고, 여러 칸을 건너뛰게 할 수도 있다. 이 움직임을 조절하는 값을 스트라이드라고 한다.

컨볼루션을 거쳐 만들어진 새로운 이미지는 특징 맵이라고도 부른다. 컨볼루션 계층마다 여러 특징 맵이 만들어지며, 다음 단계인 풀링 계층으로 넘어가게 된다.

특징 맵의 크기가 크면 학습이 어렵고 과적합의 위험이 증가한다. 풀링 계층은 앞 계층에서 추출한 특징을 값 하나로 추려내서 특징 맵의 크기를 줄여주고 중요한 특징을 강조하는 역할을 한다. 풀링 역시 일종의 컨볼루션 연산이며, 필터가 지나갈 때마다 픽셀을 묶어서 평균이나 최댓값을 가져오는 간단한 연산으로 이뤄진다.

컨볼루션 계층과 풀링 계층을 여러 겹 쌓아, 각 단계에서 만들어진 특징 맵을 관찰하면 CNN 모델이 이미지를 계층적으로 인식하는 것을 볼 수 있다.

CNN은 사물이 조금만 치우처져도 인식하지 못하던 인공 신경망의 문제를 이미지 전체에 필터를 적용해 특징을 추출하는 방식으로 해결해준다.

게다가 이미지 크기만큼의 가중치를 가져야 하는 일반 인공 신경망과는 다르게 필터(커널) 만을 학습시키면 되어 훨씬 적은 계산량으로 효율적인 학습이 가능하다.

경쟁하며 학습하는 GAN

GAN (Generative adversarial network) : 적대적 생성 신경망

  1. GAN 은 생성하는 모델이다. 앞서 배운 CNN 이나 RNN 모델로는 새로운 이미지나 음성을 만들어낼 수 없다. GAN 은 새로운 이미지나 음성을 '창작' 하도록 고안되었따.

  2. GAN 은 적대적으로 학습한다. GAN 방법론에서는 가짜 이미지를 생성하는 생성자와 이미지의 진위를 판별하는 판별자가 번갈아 학습하며 경쟁적으로 학습을 진행한다.

  3. GAN 은 인공 신경망 모델이다. 생성자와 판별자 모두 신경망으로 되어 있따.

GAN 을 한 문장으로 종합하자면, 서로 대립하는 두 모델이 경쟁해 학습하는 방법론이다.

GAN 이 이토록 주목받고 미래지향적이라는 평가를 받는 이유는 바로 비지도학습 방식이라는 점이다. 세상에 존재하는 데이터는 기하급수적으로 증가하고 있으며 대부분의 데이터에는 정답이 없다. 그래서 사람이 모든 데이터를 일일이 가공하기는 불가능하다. GAN 은 비지도학습을 하여 사람의 손길을 최소화하며 학습할 수 있다.

생성자와 판별자

GAN 모델은 생성자와 판별자라는 주요 모듈 2가지로 구성된다.

GAN 모델엔 무작위 텐서로부터 여러가지 형태의 가짜 이미지를 생성해내는 생성자와 진짜 이미지와 가짜 이미지를 구분하는 판별자가 존재한다. 학습이 진행되면서 생성자는 판별자를 속이려고 점점 더 정밀한 가짜 이미지를 생성하며, 판별자는 학습 데이터에서 가져온 진짜 이미지와 생성자가 만든 가짜 이미지를 점점 더 잘 구별하게 된다. 마지막에 생성자는 진짜 이미지와 거의 흡사한 가짜 이미지를 만들 수 있게 된다.

//데이터 읽기 
import os
import torch
import torchvision
import torch.nn as nn
import torch.optim as optim
from torchvision import transforms, datasets
from torchvision.utils import save_image
import matplotlib.pyplot as plt

EPOCHS = 500
BATCH_SIZE = 100
USE_CUDA = torch.cuda.is_available()
DEVICE = torch.device("cuda" if USE_CUDA else "cpu")
print("다음 장치를 사용합니다. ", DEVICE)

trainset = datasets.FashionMNIST('./.data', train = True, download = True, transform = transforms.Compose([
                                                                                                           transforms.ToTensor(),transforms.Normalize((0.5,),(0.5,))
]))
train_loader = torch.utils.data.DataLoader(
    dataset = trainset, 
    batch_size = BATCH_SIZE, shuffle = True
)

생성자와 판별자 구현

데이터를 다 읽었으면 생성자와 판별자를 구현할 차례이다. 지금까지는 신경망 모델들을 모듈 (nn.Module) 클래스로 정의했기 때문에 모델의 복잡한 동작들을 함수로 정의할 수 있었다.

이번에 사용할 Sequential 클래스는 신경망을 이루는 각 층에서 수행할 연산들을 입력받아 차례대로 실행하는 역할을 한다.

생성자는 실제 데이터와 비슷한 가짜 데이터를 만들어내는 신경망이다. 생성자는 정규분포로부터 뽑은 64차원의 무작위 텐서를 입력받아 행렬곱과 활성화 함수 연산을 실행한다.

이때 결과값은 이미지가 될 것이므로 784차원, 즉 Fashion MNIST 의 이미지와 같은 차원의 텐서이다.

무작위 텐서를 입력하는 이유는 생성자가 실제 데이터의 '분포' 를 배우는 것이기 때문이다. 수학적으로, 그럴듯한 '가짜' 는 '진짜' 의 분포를 닮는다고 할 수 있다.

생성자는 정규분포같은 단순한 분포에서부터 실제 데이터의 복잡한 분포를 배운다.

G = nn.Sequential(
    nn.Linear(64,256),
    nn.ReLU(),
    nn.Linear(256,256),
    nn.ReLU(),
    nn.Linear(256, 784),
    nn.Tanh()
)

다음은 판별자를 구현할 차례이다. 판별자는 이미지의 크기인 784차원의 텐서를 입력받는다. 판별자 역시 입력된 데이터에 행렬곱과 활성화 함수를 실행시킨다. 판별자는 입력된 784차원의 텐서가 생성자가 만든 가짜 이미지인지, 혹은 실제 Fashion MNIST 의 이미지인지 구분하는 분류 모델이다.

D = nn.Sequential(
    nn.Linear(784, 256),
    nn.LeakyReLU(0.2),
    nn.Linear(256,256),
    nn.LeakyReLU(0.2),
    nn.Linear(256,1),
    nn.Sigmoid()
)

좋은 웹페이지 즐겨찾기