딥러닝 강의

딥러닝 강의 3편 - PyTorch 로 옮기기

나동빈

이번 시간부터는 PyTorch 를 써보겠습니다. 지난 시간에는 미분 코드를 직접 작성했지만, 층이 세 개만 되어도 이러한 방식으로는 감당이 어렵다는 점에서 라이브러리의 도움을 받아야 합니다.

설치하기

PyTorch 설치 명령어

pip install torch

설치가 완료되면 다음과 같이 확인해주시면 됩니다.

import torch
print(torch.__version__)
print("GPU:", torch.cuda.is_available())
GPU 항목이 False 로 나오더라도 이번 강의 내용은 모두 따라오실 수 있습니다. 예제의 규모가 작다는 점에서 CPU 만으로도 충분합니다.

텐서(Tensor)

PyTorch 의 기본 자료형입니다. 기본적으로 NumPy 배열과 거의 동일하지만 다음의 두 가지가 다릅니다.

항목NumPyPyTorch
GPU 사용불가능가능
미분직접 구현자동 계산
import torch

x = torch.tensor([1.0, 2.0, 3.0])
print(x * 2)
print(x.sum())

자동 미분

이 부분이 PyTorch 를 사용하는 가장 큰 이유입니다.

w = torch.tensor(1.0, requires_grad=True)
x = torch.tensor(3.0)

y = w * x          # y = 3w
y.backward()       # 미분을 수행합니다

print(w.grad)      # 3.0

requires_grad=True 옵션을 붙여주시면 해당 값에 대한 미분을 자동으로 계산해줍니다. 지난 시간에 d_W1 = X.T @ d_h 와 같은 구문을 직접 작성했지만, 이제는 그럴 필요가 없습니다.

XOR 문제 다시 풀어보기

지난 시간과 동일한 문제를 PyTorch 로 작성하면 다음과 같이 간결해집니다.

import torch
import torch.nn as nn

X = torch.tensor([[0.,0.],[0.,1.],[1.,0.],[1.,1.]])
Y = torch.tensor([[0.],[1.],[1.],[0.]])

model = nn.Sequential(
    nn.Linear(2, 4),
    nn.ReLU(),
    nn.Linear(4, 1),
)

loss_fn = nn.MSELoss()
opt = torch.optim.Adam(model.parameters(), lr=0.05)

for step in range(1000):
    pred = model(X)
    loss = loss_fn(pred, Y)

    opt.zero_grad()
    loss.backward()
    opt.step()

    if step % 200 == 0:
        print(f"{step}회 손실 {loss.item():.4f}")

print("\n결과")
with torch.no_grad():
    for x, y in zip(X, Y):
        print(x.tolist(), "→", round(model(x).item(), 3), "(정답", int(y), ")")

지난 시간의 40줄 정도 되던 소스코드가 15줄로 줄어들었습니다. 미분과 관련된 부분이 전부 사라진 덕분입니다.

학습 반복문의 세 줄

어떤 모델을 만드시더라도 다음의 세 줄은 항상 동일합니다.

opt.zero_grad()    # 이전 미분 값을 지웁니다
loss.backward()    # 이번 미분 값을 계산합니다
opt.step()         # 그 방향으로 가중치를 이동시킵니다

여기에서 zero_grad() 를 빠뜨리시면 미분 값이 계속 누적되어 학습이 제대로 진행되지 않습니다. 처음 배우시는 분들이 가장 많이 겪는 문제라는 점에서 반드시 기억해주시면 좋습니다.

자주 발생하는 오류

첫째, 텐서의 모양(Shape)이 맞지 않는 경우

RuntimeError: mat1 and mat2 shapes cannot be multiplied (4x2 and 4x1)

앞쪽 층의 출력 개수와 뒤쪽 층의 입력 개수가 일치해야 합니다. 중간중간에 print(x.shape) 를 넣어서 확인해보시면 됩니다.

둘째, 평가할 때 미분을 계산하는 경우

with torch.no_grad():
    pred = model(X)

학습이 끝난 이후에는 미분이 필요하지 않습니다. 이 구문을 빼시면 속도가 느려지고 메모리도 더 많이 사용하게 됩니다.

셋째, 학습률(Learning Rate)을 잘못 설정하는 경우

값이 너무 크면 손실이 요동치고, 너무 작으면 거의 줄어들지 않습니다. 저는 보통 0.001 에서 시작해서 손실 곡선을 확인하며 조절하는 방식을 사용합니다.

다음 편에서는 이미지 데이터를 다뤄보겠습니다. 합성곱(Convolution)이 왜 필요한지 보실 수 있습니다.