Fork me on GitHub
  • Tutorials >
  • 예제로 배우는 파이토치(PyTorch)
Shortcuts

예제로 배우는 파이토치(PyTorch)

Author: Justin Johnson

번역: 박정환

이 튜토리얼에서는 PyTorch 의 핵심적인 개념을 예제를 통해 소개합니다.

본질적으로, PyTorch에는 2가지 주요한 특징이 있습니다:

  • NumPy와 유사하지만 GPU 상에서 실행 가능한 N차원 Tensor

  • 신경망을 구성하고 학습하는 과정에서의 자동 미분

완전히 연결된 ReLU 신경망을 예제로 사용할 것입니다. 이 신경망은 하나의 은닉층 (hidden layer)을 갖고 있으며, 신경망의 출력과 정답 사이의 유클리드 거리 (Euclidean distance)를 최소화하는 식으로 경사하강법(gradient descent)을 사용하여 무작위의 데이터를 맞추도록 학습할 것입니다.

Note

각각의 예제들은 이 페이지의 마지막 부분 에서 살펴볼 수 있습니다.

Tensors

준비 운동: NumPy

PyTorch를 소개하기 전에, 먼저 NumPy를 사용하여 신경망을 구성해보겠습니다.

NumPy는 N차원 배열 객체와 함께 이러한 배열들을 조작하기 위한 다양한 함수들을 제공합니다. NumPy는 과학적 분야의 연산을 위한 포괄적인 프레임워크 (Framework)입니다; NumPy는 연산 그래프(computation graph)나 딥러닝, 변화도(gradient)에 대해서는 알지 못합니다. 하지만 NumPy 연산을 사용하여 순전파 단계와 역전파 단계를 직접 구현함으로써, 2계층(two-layer)을 갖는 신경망이 무작위의 데이터를 맞추도록 할 수 있습니다:

# -*- coding: utf-8 -*-
import numpy as np

# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 무작위의 입력과 출력 데이터를 생성합니다.
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)

# 무작위로 가중치를 초기화합니다.
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)

learning_rate = 1e-6
for t in range(500):
    # 순전파 단계: 예측값 y를 계산합니다.
    h = x.dot(w1)
    h_relu = np.maximum(h, 0)
    y_pred = h_relu.dot(w2)

    # 손실(loss)을 계산하고 출력합니다.
    loss = np.square(y_pred - y).sum()
    print(t, loss)

    # 손실에 따른 w1, w2의 변화도를 계산하고 역전파합니다.
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.T.dot(grad_y_pred)
    grad_h_relu = grad_y_pred.dot(w2.T)
    grad_h = grad_h_relu.copy()
    grad_h[h < 0] = 0
    grad_w1 = x.T.dot(grad_h)

    # 가중치를 갱신합니다.
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2

PyTorch: Tensors

NumPy는 훌륭한 프레임워크지만, GPU를 사용하여 수치 연산을 가속화할 수는 없습니다. 현대의 심층 신경망에서 GPU는 종종 50배 또는 그 이상 의 속도 향상을 제공하기 때문에, 안타깝게도 NumPy는 현대의 딥러닝에는 충분치 않습니다.

이번에는 PyTorch의 기본적인 개념인 Tensor 에 대해서 알아보겠습니다. PyTorch Tensor는 개념적으로 NumPy 배열과 동일합니다: Tensor는 N차원 배열이며, PyTorch는 Tensor 연산을 위한 다양한 함수들을 제공합니다. NumPy 배열처럼 PyTorch Tensor는 딥러닝이나 연산 그래프, 변화도는 알지 못하며, 과학적 분야의 연산을 위한 포괄적인 도구입니다.

그러나 NumPy와는 달리, PyTorch Tensor는 GPU를 활용하여 수치 연산을 가속화할 수 있습니다. GPU에서 PyTorch Tensor를 실행하기 위해서는 단지 새로운 자료형으로 변환(Cast)해주기만 하면 됩니다.

여기에서는 PyTorch Tensor를 사용하여 2계층의 신경망이 무작위 데이터를 맞추도록 할 것입니다. 위의 NumPy 예제에서와 같이 신경망의 순전파 단계와 역전파 단계는 직접 구현하겠습니다.

# -*- coding: utf-8 -*-

import torch


dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPU에서 실행하려면 이 주석을 제거하세요.

# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 무작위의 입력과 출력 데이터를 생성합니다.
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 무작위로 가중치를 초기화합니다.
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)

learning_rate = 1e-6
for t in range(500):
    # 순전파 단계: 예측값 y를 계산합니다.
    h = x.mm(w1)
    h_relu = h.clamp(min=0)
    y_pred = h_relu.mm(w2)

    # 손실(loss)을 계산하고 출력합니다.
    loss = (y_pred - y).pow(2).sum().item()
    print(t, loss)

    # 손실에 따른 w1, w2의 변화도를 계산하고 역전파합니다.
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.t().mm(grad_y_pred)
    grad_h_relu = grad_y_pred.mm(w2.t())
    grad_h = grad_h_relu.clone()
    grad_h[h < 0] = 0
    grad_w1 = x.t().mm(grad_h)

    # 경사하강법(gradient descent)를 사용하여 가중치를 갱신합니다.
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2

Autograd

PyTorch: Tensor와 autograd

위의 예제들에서 우리는 신경망의 순전파 단계와 역전파 단계를 직접 구현하였습니다. 작은 2계층 신경망에서 역전파 단계를 직접 구현하는 것은 큰 일이 아니지만, 대규모의 복잡한 신경망에서는 매우 아슬아슬한 일일 것입니다.

다행히도, 자동 미분 을 사용하여 신경망에서 역전파 단계의 연산을 자동화할 수 있습니다. PyTorch의 autograd 패키지는 정확히 이런 기능을 제공합니다. Autograd를 사용할 때, 신경망의 순전파 단계는 연산 그래프 를 정의하게 됩니다; 이 그래프의 노드(node)는 Tensor, 엣지(edge)는 입력 Tensor로부터 출력 Tensor를 만들어내는 함수가 됩니다. 이 그래프를 통해 역전파를 하게 되면 변화도를 쉽게 계산할 수 있습니다.

이는 복잡하게 들리지만, 실제로 사용하는 것은 매우 간단합니다. 각 Tensor는 연산 그래프에서 노드로 표현됩니다. 만약 xx.requires_grad=True 인 Tensor면 x.grad 는 어떤 스칼라 값에 대한 x 의 변화도를 갖는 또 다른 Tensor입니다.

여기에서는 PyTorch Tensor와 autograd를 사용하여 2계층 신경망을 구현합니다; 이제 더 이상 신경망의 역전파 단계를 직접 구현할 필요가 없습니다:

# -*- coding: utf-8 -*-
import torch

dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPU에서 실행하려면 이 주석을 제거하세요.

# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 입력과 출력을 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
# requires_grad=False로 설정하여 역전파 중에 이 Tensor들에 대한 변화도를 계산할
# 필요가 없음을 나타냅니다. (requres_grad의 기본값이 False이므로 아래 코드에는
# 이를 반영하지 않았습니다.)
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 가중치를 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
# requires_grad=True로 설정하여 역전파 중에 이 Tensor들에 대한
# 변화도를 계산할 필요가 있음을 나타냅니다.
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
    # 순전파 단계: Tensor 연산을 사용하여 예상되는 y 값을 계산합니다. 이는 Tensor를
    # 사용한 순전파 단계와 완전히 동일하지만, 역전파 단계를 별도로 구현하지 않아도
    # 되므로 중간값들에 대한 참조(reference)를 갖고 있을 필요가 없습니다.
    y_pred = x.mm(w1).clamp(min=0).mm(w2)

    # Tensor 연산을 사용하여 손실을 계산하고 출력합니다.
    # loss는 (1,) 형태의 Tensor이며, loss.item()은 loss의 스칼라 값입니다.
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())

    # autograd를 사용하여 역전파 단계를 계산합니다. 이는 requires_grad=True를
    # 갖는 모든 Tensor에 대해 손실의 변화도를 계산합니다. 이후 w1.grad와 w2.grad는
    # w1과 w2 각각에 대한 손실의 변화도를 갖는 Tensor가 됩니다.
    loss.backward()

    # 경사하강법(gradient descent)을 사용하여 가중치를 수동으로 갱신합니다.
    # torch.no_grad()로 감싸는 이유는 가중치들이 requires_grad=True이지만
    # autograd에서는 이를 추적할 필요가 없기 때문입니다.
    # 다른 방법은 weight.data 및 weight.grad.data를 조작하는 방법입니다.
    # tensor.data가 tensor의 저장공간을 공유하기는 하지만, 이력을
    # 추적하지 않는다는 것을 기억하십시오.
    # 또한, 이를 위해 torch.optim.SGD 를 사용할 수도 있습니다.
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # 가중치 갱신 후에는 수동으로 변화도를 0으로 만듭니다.
        w1.grad.zero_()
        w2.grad.zero_()

PyTorch: 새 autograd 함수 정의하기

내부적으로, autograd의 기본(primitive) 연산자는 실제로 Tensor를 조작하는 2개의 함수입니다. forward 함수는 입력 Tensor로부터 출력 Tensor를 계산합니다. backward 함수는 어떤 스칼라 값에 대한 출력 Tensor의 변화도를 전달받고, 동일한 스칼라 값에 대한 입력 Tensor의 변화도를 계산합니다.

PyTorch에서 torch.autograd.Function 의 서브클래스(subclass)를 정의하고 forwardbackward 함수를 구현함으로써 사용자 정의 autograd 연산자를 손쉽게 정의할 수 있습니다. 그 후, 인스턴스(instance)를 생성하고 이를 함수처럼 호출하여 입력 데이터를 갖는 Tensor를 전달하는 식으로 새로운 autograd 연산자를 사용할 수 있습니다.

이 예제에서는 ReLU로 비선형적(nonlinearity)으로 동작하는 사용자 정의 autograd 함수를 정의하고, 2-계층 신경망에 이를 적용해보도록 하겠습니다:

# -*- coding: utf-8 -*-
import torch


class MyReLU(torch.autograd.Function):
    """
    torch.autograd.Function을 상속받아 사용자 정의 autograd Function을 구현하고,
    Tensor 연산을 하는 순전파와 역전파 단계를 구현하겠습니다.
    """

    @staticmethod
    def forward(ctx, input):
        """
        순전파 단계에서는 입력을 갖는 Tensor를 받아 출력을 갖는 Tensor를 반환합니다.
        ctx는 컨텍스트 객체(context object)로 역전파 연산을 위한 정보 저장에
        사용합니다. ctx.save_for_backward method를 사용하여 역전파 단계에서 사용할 어떠한
        객체도 저장(cache)해 둘 수 있습니다.
        """
        ctx.save_for_backward(input)
        return input.clamp(min=0)

    @staticmethod
    def backward(ctx, grad_output):
        """
        역전파 단계에서는 출력에 대한 손실의 변화도를 갖는 Tensor를 받고, 입력에
        대한 손실의 변화도를 계산합니다.
        """
        input, = ctx.saved_tensors
        grad_input = grad_output.clone()
        grad_input[input < 0] = 0
        return grad_input


dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # GPU에서 실행하려면 이 주석을 제거하세요.

# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 입력과 출력을 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# 가중치를 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)

learning_rate = 1e-6
for t in range(500):
    # 사용자 정의 Function을 적용하기 위해 Function.apply 메소드를 사용합니다.
    # 여기에 'relu'라는 이름을 붙였습니다.
    relu = MyReLU.apply

    # 순전파 단계: Tensor 연산을 사용하여 예상되는 y 값을 계산합니다;
    # 사용자 정의 autograd 연산을 사용하여 ReLU를 계산합니다.
    y_pred = relu(x.mm(w1)).mm(w2)

    # 손실을 계산하고 출력합니다.
    loss = (y_pred - y).pow(2).sum()
    print(t, loss.item())

    # autograde를 사용하여 역전파 단계를 계산합니다.
    loss.backward()

    # 경사하강법(gradient descent)을 사용하여 가중치를 갱신합니다.
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # 가중치 갱신 후에는 수동으로 변화도를 0으로 만듭니다.
        w1.grad.zero_()
        w2.grad.zero_()

TensorFlow: 정적 그래프(Static Graph)

PyTorch autograd는 Tensorflow와 많이 비슷해보입니다: 두 프레임워크 모두 연산 그래프를 정의하며, 자동 미분을 사용하여 변화도를 계산합니다. 두 프레임워크의 가장 큰 차이점은 Tensorflow의 연산 그래프는 정적 인데 반해, PyTorch는 동적 연산 그래프를 사용한다는 것입니다.

Tensorflow에서는 연산 그래프를 한 번 정의한 후 동일한 그래프를 계속해서 실행하며 가능한 다른 입력 데이터를 그래프에 전달합니다. PyTorch에서는 각각의 순전파 단계는 새로운 연산 그래프를 정의합니다.

정적 그래프는 그래프를 미리(upfront) 최적화할 수 있기 때문에 좋습니다; 예를 들어 프레임워크가 효율을 위해 일부 그래프 연산을 합치거나, 여러 GPU나 시스템(machine)에 그래프를 배포하는 전략을 제시하도록 결정할 수 있습니다. 만약 동일한 그래프를 계속 재사용하면, 초기의 값비싼 최적화 비용을 동일한 그래프를 반복하여 실행함으로써 상환(상쇄)할 수 있습니다.

정적 그래프와 동적 그래프는 제어 흐름(control flow) 측면에서도 다릅니다. 어떤 모델에서 각 데이터 지점(point)마다 다른 연산 연산을 수행하고 싶을 수 있습니다; 예를 들어 순환 신경망에서 각각의 데이터 지점마다 서로 다른 횟수만큼 펼칠(unroll) 수 있습니다; 이러한 펼침은 반복문(loop)으로 구현할 수 있습니다. 정적 그래프에서 반복문은 그래프의 일부가 돼야 합니다; 이러한 이유에서 Tensorflow는 그래프 내에 반복문을 포함하기 위해 tf.scan 과 같은 연산자를 제공합니다. 동적 그래프에서는 이러한 상황이 더 단순(Simple)해집니다: 각 예제에 대한 그래프를 즉석에서 작성하기 때문에, 일반적인 명령형(Imperative) 제어 흐름을 사용하여 각각의 입력에 따라 다른 계산을 수행할 수 있습니다.

위의 PyTorch autograd 예제와는 대조적으로, 여기에서는 TensorFlow를 사용하여 간단한 2계층 신경망을 구성해보겠습니다:

# -*- coding: utf-8 -*-
import tensorflow as tf
import numpy as np

# 먼저 연산 그래프를 구성합니다:

# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 입력과 정답(target) 데이터를 위한 플레이스홀더(placeholder)를 생성합니다;
# 이는 우리가 그래프를 실행할 때 실제 데이터로 채워질 것입니다.
x = tf.placeholder(tf.float32, shape=(None, D_in))
y = tf.placeholder(tf.float32, shape=(None, D_out))

# 가중치를 저장하기 위한 Variable을 생성하고 무작위 데이터로 초기화합니다.
# Tensorflow의 Variable은 그래프가 실행되는 동안 그 값이 유지됩니다.
w1 = tf.Variable(tf.random_normal((D_in, H)))
w2 = tf.Variable(tf.random_normal((H, D_out)))

# 순전파 단계: Tensorflow의 Tensor 연산을 사용하여 예상되는 y 값을 계산합니다.
# 이 코드가 어떠한 수치 연산을 실제로 수행하지는 않는다는 것을 유의하세요;
# 이 단계에서는 나중에 실행할 연산 그래프를 구성하기만 합니다.
h = tf.matmul(x, w1)
h_relu = tf.maximum(h, tf.zeros(1))
y_pred = tf.matmul(h_relu, w2)

# Tensorflow의 Tensor 연산을 사용하여 손실(loss)을 계산합니다.
loss = tf.reduce_sum((y - y_pred) ** 2.0)

# w1, w2에 대한 손실의 변화도(gradient)를 계산합니다.
grad_w1, grad_w2 = tf.gradients(loss, [w1, w2])

# 경사하강법(gradient descent)을 사용하여 가중치를 갱신합니다. 실제로 가중치를
# 갱신하기 위해서는 그래프가 실행될 때 new_w1과 new_w2 계산(evaluate)해야 합니다.
# Tensorflow에서 가중치의 값을 갱신하는 작업이 연산 그래프의 일부임을 유의하십시오;
# PyTorch에서는 이 작업이 연산 그래프의 밖에서 일어납니다.
learning_rate = 1e-6
new_w1 = w1.assign(w1 - learning_rate * grad_w1)
new_w2 = w2.assign(w2 - learning_rate * grad_w2)

# 지금까지 우리는 연산 그래프를 구성하였으므로, 실제로 그래프를 실행하기 위해 이제
# Tensorflow 세션(session)에 들어가보겠습니다.
with tf.Session() as sess:
    # 그래프를 한 번 실행하여 w1과 w2 Variable을 초기화합니다.
    sess.run(tf.global_variables_initializer())

    # 입력 데이터 x와 정답 데이터 y를 저장하기 위한 NumPy 배열을 생성합니다.
    x_value = np.random.randn(N, D_in)
    y_value = np.random.randn(N, D_out)
    for _ in range(500):
        # 그래프를 여러 번 실행합니다. 매번 그래프가 실행할 때마다 feed_dict
        # 인자에 x_value를 x에, y_value를 y에 할당(bind)하도록 명시합니다.
        # 또한, 그래프를 실행할 때마다 손실과 new_w1, new_w2 값을
        # 계산하려고 합니다; 이러한 Tensor들의 값은 NumPy 배열로 반환됩니다.
        loss_value, _, _ = sess.run([loss, new_w1, new_w2],
                                    feed_dict={x: x_value, y: y_value})
        print(loss_value)

nn 모듈

PyTorch: nn

연산 그래프와 autograd는 복잡한 연산자를 정의하고 도함수(derivative)를 자동으로 계산하는 매우 강력한 패러다임입니다; 하지만 규모가 큰 신경망에서는 autograd 그 자체만으로는 너무 낮은 수준(low-level)일 수 있습니다.

신경망을 구성할 때 종종 연산을 여러 계층 에 배열(arrange)하는 것으로 생각하는데, 이 중 일부는 학습 도중 최적화가 될 학습 가능한 매개변수 를 갖고 있습니다.

Tensorflow는 Keras, TensorFlow-Slim, 나 TFLearn 같은 패키지들이 연산 그래프를 더 높은 수준으로 추상화(higher-level abstraction)하여 제공하므로 신경망을 구축하는데 있어 유용합니다.

PyTorch에서는 nn 패키지가 동일한 목적으로 제공됩니다. nn 패키지는 신경망 계층(layer)들과 거의 동일한 Module 의 집합을 정의합니다. Module은 입력 Tensor를 받고 출력 Tensor를 계산하는 한편, 학습 가능한 매개변수를 갖는 Tensor 같은 내부 상태(internal state)를 갖습니다. nn 패키지는 또한 신경망을 학습시킬 때 주로 사용하는 유용한 손실 함수들도 정의하고 있습니다.

이번 예제에서는 nn 패키지를 사용하여 2계층 신경망을 구성해보겠습니다:

# -*- coding: utf-8 -*-
import torch

# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 입력과 출력을 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# nn 패키지를 사용하여 모델을 순차적 계층(sequence of layers)으로 정의합니다.
# nn.Sequential은 다른 Module들을 포함하는 Module로, 그 Module들을 순차적으로
# 적용하여 출력을 생성합니다. 각각의 Linear Module은 선형 함수를 사용하여
# 입력으로부터 출력을 계산하고, 내부 Tensor에 가중치와 편향을 저장합니다.
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, H),
    torch.nn.ReLU(),
    torch.nn.Linear(H, D_out),
)

# 또한 nn 패키지에는 널리 사용하는 손실 함수들에 대한 정의도 포함하고 있습니다;
# 여기에서는 평균 제곱 오차(MSE; Mean Squared Error)를 손실 함수로 사용하겠습니다.
loss_fn = torch.nn.MSELoss(reduction='sum')

learning_rate = 1e-4
for t in range(500):
    # 순전파 단계: 모델에 x를 전달하여 예상되는 y 값을 계산합니다. Module 객체는
    # __call__ 연산자를 덮어써(override) 함수처럼 호출할 수 있게 합니다.
    # 이렇게 함으로써 입력 데이터의 Tensor를 Module에 전달하여 출력 데이터의
    # Tensor를 생성합니다.
    y_pred = model(x)

    # 손실을 계산하고 출력합니다. 예측한 y와 정답인 y를 갖는 Tensor들을 전달하고,
    # 손실 함수는 손실 값을 갖는 Tensor를 반환합니다.
    loss = loss_fn(y_pred, y)
    print(t, loss.item())

    # 역전파 단계를 실행하기 전에 변화도를 0으로 만듭니다.
    model.zero_grad()

    # 역전파 단계: 모델의 학습 가능한 모든 매개변수에 대해 손실의 변화도를
    # 계산합니다. 내부적으로 각 Module의 매개변수는 requires_grad=True 일 때
    # Tensor 내에 저장되므로, 이 호출은 모든 모델의 모든 학습 가능한 매개변수의
    # 변화도를 계산하게 됩니다.
    loss.backward()

    # 경사하강법(gradient descent)를 사용하여 가중치를 갱신합니다. 각 매개변수는
    # Tensor이므로 이전에 했던 것과 같이 변화도에 접근할 수 있습니다.
    with torch.no_grad():
        for param in model.parameters():
            param -= learning_rate * param.grad

PyTorch: optim

지금까지는 (autograd의 추적 기록을 피하기 위해 torch.no_grad () 또는 .data 를 사용하는 식으로) 학습 가능한 매개변수를 갖는 Tensor를 직접 조작하며 모델의 가중치를 갱신하였습니다. 이것은 확률적 경사 하강법(SGD)과 같은 간단한 최적화 알고리즘에서는 크게 부담이 되지는 않지만, 실제로 신경망을 학습할 때는 주로 AdaGrad, RMSProp, Adam 등과 같은 좀 더 정교한 Optimizer를 사용하곤 합니다.

PyTorch의 optim 패키지는 최적화 알고리즘에 대한 아이디어를 추상화하고 일반적으로 사용하는 최적화 알고리즘의 구현체(implementation)를 제공합니다.

이 에제에서는 지금까지와 같이 nn 패키지를 사용하여 모델을 정의하지만, optim 패키지가 제공하는 Adam 알고리즘을 이용하여 모델을 최적화하겠습니다:

# -*- coding: utf-8 -*-
import torch

# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 입력과 출력을 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# nn 패키지를 사용하여 모델과 손실 함수를 정의합니다.
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, H),
    torch.nn.ReLU(),
    torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(reduction='sum')

# optim 패키지를 사용하여 모델의 가중치를 갱신할 Optimizer를 정의합니다.
# 여기서는 Adam을 사용하겠습니다; optim 패키지는 다른 다양한 최적화 알고리즘을
# 포함하고 있습니다. Adam 생성자의 첫번째 인자는 어떤 Tensor가 갱신되어야 하는지
# 알려줍니다.
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
    # 순전파 단계: 모델에 x를 전달하여 예상되는 y 값을 계산합니다.
    y_pred = model(x)

    # 손실을 계산하고 출력합니다.
    loss = loss_fn(y_pred, y)
    print(t, loss.item())

    # 역전파 단계 전에, Optimizer 객체를 사용하여 (모델의 학습 가능한 가중치인)
    # 갱신할 변수들에 대한 모든 변화도를 0으로 만듭니다. 이렇게 하는 이유는
    # 기본적으로 .backward()를 호출할 때마다 변화도가 버퍼(buffer)에 (덮어쓰지 않고)
    # 누적되기 때문입니다. 더 자세한 내용은 torch.autograd.backward에 대한 문서를
    # 참조하세요.
    optimizer.zero_grad()

    # 역전파 단계: 모델의 매개변수에 대한 손실의 변화도를 계산합니다.
    loss.backward()

    # Optimizer의 step 함수를 호출하면 매개변수가 갱신됩니다.
    optimizer.step()

PyTorch: 사용자 정의 nn.Module

때때로 기존 모듈의 구성(sequence)보다 더 복잡한 모델을 구성해야 할 때가 있습니다; 이럴 때는 nn.Module 의 서브클래스로 새 모듈을 정의하고, 입력 Tensor를 받아 다른 모듈 또는 Tensor의 autograd 연산을 사용하여 출력 Tensor를 만드는 forward 를 정의합니다.

이 예제에서는 2계층 신경망을 직접 정의한 nn.Module 서브클래스로 구현해보겠습니다:

# -*- coding: utf-8 -*-
import torch


class TwoLayerNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        생성자에서 2개의 nn.Linear 모듈을 생성하고, 멤버 변수로 지정합니다.
        """
        super(TwoLayerNet, self).__init__()
        self.linear1 = torch.nn.Linear(D_in, H)
        self.linear2 = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
        순전파 함수에서는 입력 데이터의 Tensor를 받고 출력 데이터의 Tensor를
        반환해야 합니다. Tensor 상의 임의의 연산자뿐만 아니라 생성자에서 정의한
        Module도 사용할 수 있습니다.
        """
        h_relu = self.linear1(x).clamp(min=0)
        y_pred = self.linear2(h_relu)
        return y_pred


# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 입력과 출력을 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# 앞에서 정의한 클래스를 생성하여 모델을 구성합니다.
model = TwoLayerNet(D_in, H, D_out)

# 손실 함수와 Optimizer를 만듭니다. SGD 생성자에 model.parameters()를 호출하면
# 모델의 멤버인 2개의 nn.Linear 모듈의 학습 가능한 매개변수들이 포함됩니다.
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
    # 순전파 단계: 모델에 x를 전달하여 예상되는 y 값을 계산합니다.
    y_pred = model(x)

    # 손실을 계산하고 출력합니다.
    loss = criterion(y_pred, y)
    print(t, loss.item())

    # 변화도를 0으로 만들고, 역전파 단계를 수행하고, 가중치를 갱신합니다.
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

PyTorch: 제어 흐름(Control Flow) + 가중치 공유(Weight Sharing)

동적 그래프와 가중치 공유의 예로, 매우 이상한 모델을 구현해보겠습니다: 각 순전파 단계에서 많은 은닉 계층을 갖는 완전히 연결(fully-connected)된 ReLU 신경망이 무작위로 0 ~ 3 사이의 숫자를 선택하고, 가장 안쪽(innermost)의 은닉층들을 계산하기 위해 동일한 가중치를 여러 번 재사용합니다.

이 모델에서는 일반적인 Python 제어 흐름을 사용하여 반복(loop)을 구현할 수 있으며, 순전파 단계를 정의할 때 단지 동일한 Module을 여러번 재사용함으로써 내부(innermost) 계층들 간의 가중치 공유를 구현할 수 있습니다.

이러한 모델을 Module을 상속받는 서브클래스로 간단히 구현해보겠습니다:

# -*- coding: utf-8 -*-
import random
import torch


class DynamicNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        생성자에서 순전파 단계에서 사용할 3개의 nn.Linear 인스턴스를 생성합니다.
        """
        super(DynamicNet, self).__init__()
        self.input_linear = torch.nn.Linear(D_in, H)
        self.middle_linear = torch.nn.Linear(H, H)
        self.output_linear = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
        모델의 순전파 단계에서, 무작위로 0, 1, 2 또는 3 중에 하나를 선택하고
        은닉층을 계산하기 위해 여러번 사용한 middle_linear Module을 재사용합니다.

        각 순전파 단계는 동적 연산 그래프를 구성하기 때문에, 모델의 순전파 단계를
        정의할 때 반복문이나 조건문과 같은 일반적인 Python 제어 흐름 연산자를 사용할
        수 있습니다.

        여기에서 연산 그래프를 정의할 때 동일 Module을 여러번 재사용하는 것이
        완벽히 안전하다는 것을 알 수 있습니다. 이것이 각 Module을 한 번씩만 사용할
        수 있었던 Lua Torch보다 크게 개선된 부분입니다.
        """
        h_relu = self.input_linear(x).clamp(min=0)
        for _ in range(random.randint(0, 3)):
            h_relu = self.middle_linear(h_relu).clamp(min=0)
        y_pred = self.output_linear(h_relu)
        return y_pred


# N은 배치 크기이며, D_in은 입력의 차원입니다;
# H는 은닉층의 차원이며, D_out은 출력 차원입니다.
N, D_in, H, D_out = 64, 1000, 100, 10

# 입력과 출력을 저장하기 위해 무작위 값을 갖는 Tensor를 생성합니다.
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# 앞서 정의한 클래스를 생성(instantiating)하여 모델을 구성합니다.
model = DynamicNet(D_in, H, D_out)

# 손실함수와 Optimizer를 만듭니다. 이 이상한 모델을 순수한 확률적 경사 하강법
# (stochastic gradient decent)으로 학습하는 것은 어려우므로, 모멘텀(momentum)을
# 사용합니다.
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
    # 순전파 단계: 모델에 x를 전달하여 예상되는 y 값을 계산합니다.
    y_pred = model(x)

    # 손실을 계산하고 출력합니다.
    loss = criterion(y_pred, y)
    print(t, loss.item())

    # 변화도를 0으로 만들고, 역전파 단계를 수행하고, 가중치를 갱신합니다.
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

Docs

Access comprehensive developer documentation for PyTorch

View Docs

Tutorials

Get in-depth tutorials for beginners and advanced developers

View Tutorials

Resources

Find development resources and get your questions answered

View Resources