PyTorch 기초 - 손실 계산과 모델 가중치 최적화

sp·2022년 2월 27일
0

PyTorch 기초

목록 보기
5/7
post-thumbnail

입력 텐서에 대해 정의한 모델을 통과시키면 출력 텐서가 나오게 됩니다. 그 다음에 할 일은 손실 함수를 통해서 원하는 결과와 얼마나 다른지 계산하고 역전파를 통해 모델의 가중치를 변화시키는 과정을 수행합니다. 이 포스트에서는 출력과 타겟 텐서 사이의 손실 함수를 계산하는 방법, optimizer를 통해 모델의 역전파를 수행하는 방법에 대해 알아보겠습니다.

먼저 사용할 모듈들을 불러오겠습니다.

import torch
import torch.nn as nn
import torch.optim as optim

손실 함수 사용하기

손실 함수는 nn 모듈에 정의되어 있고, 학습 과정의 코드에서는 해당되는 손실 함수를 선언해서 사용하면 됩니다. 또한 이전 모델의 연산과 같이 nn.functional에 정의되어 바로 함수로 사옹되고 있는데, 기능의 차이는 없지만, 손실 함수가 복잡해지거나 실험 등을 수행할 때 코드를 간단하게 바꿀 수 있다는 점에서 전자를 일반적으로 사용합니다.

그러면 자주 사용하는 손실 함수들을 알아보겠습니다. 먼저 다항함수를 사용하는 손실 함수들입니다.

torch.nn.MSELoss(...)
torch.nn.L1Loss(...)

여기서 MSE가 두 텐서에 대해 유클리안 거리를 통해 비교하는 방식으로 회귀나 영상 복구를 위한 손실 함수로 사용됩니다. L1 Loss는 각 값에 대해 제곱 대신 절댓값의 평균으로 볼 수 있습니다. 그러면 이 손실 함수를 사용해보겠습니다.

mse_criterion = nn.MSELoss()
x = torch.randn(8, 3)
y = torch.randn(8, 3)
print("mse:", mse_criterion(x, y))
mse: tensor(1.4848)

이전에 언급한 블록들처럼 손실 함수를 변수로 정의하고, 함수처럼 비교할 두 텐서를 매개변수로 넣어주면 됩니다. 여기서 중요한 점은 손실 함수에 입력하는 매개 변수의 순서입니다. 역전파를 수행하기 위해 학습을 위해 모델에 통과한 텐서를 첫 번째 매개변수로, 타켓 텐서를 두 번째 매개변수로 지정해야 한다는 점입니다.

다음은 로그나 지수함수를 사용하는 손실 함수들입니다.

nn.CrossEntropyLoss()
nn.BCELoss()
nn.BCELossWithLogits()

nn.CrossEntropyLoss는 각 클래스(채널)를 축으로 Softmax를 취해서 cross entropy를 계산합니다. 이 특성으로 multi-class 분류, 이미지 시맨틱 분할 등에 사용됩니다. 타켓 텐서로는 각 배치마다 1로 예상하는 인덱스 또는 확률 벡터로 나타낼 수 있습니다. 예를 들어서 코드로 나타내면 다음과 같습니다.

cross_entropy_criterion = nn.CrossEntropyLoss()

score = torch.randn(1, 10)
target1 = torch.tensor([5], dtype=torch.long)
target2 = torch.zeros((1, 10))
target2[0, 5] = 1
print("target2:", target2)

print("CE1 loss:", cross_entropy_criterion(score, target1))
print("CE2 loss:", cross_entropy_criterion(score, target2))
target2: tensor([[0., 0., 0., 0., 0., 1., 0., 0., 0., 0.]])
CE1 loss: tensor(2.1225)
CE2 loss: tensor(2.1225)

target1은 타겟 인덱스만 나타낸 것이고, target2는 이를 원-핫 벡터로 나타낸 것입니다. 이 둘은 같은 역할을 하고 있으므로 별도의 원-핫 인코딩 작업 없이 target1처럼 사용하는 것이 좋고, 혹시나 0과 1로만 이루어지지 않고, 특정 확률 벡터를 명시하고 싶을 때 target2처럼 사용하면 됩니다. shape의 관점으로 보자면 출력 텐서의 shape는 [batch size, channels, ...]이고, 타켓 텐서의 shape는 출력 텐서의 shape와 같거나 channels가 빠진 [batch size, ...]로 볼 수 있습니다.

nn.BCELoss는 Softmax 함수 없이 바로 Cross entropy를 계산합니다. 이 연산의 입력 텐서를 0과 1 사이로 미리 지정해야 할 필요가 있습니다.

nn.BCEWithLogitsLoss는 위의 입력 텐서를 0과 1 사이로 만들기 위해 Sigmoid를 적용한 값에 대해 Cross entropy를 계산합니다. 이런 편리함 때문에 nn.BCELoss보다 많이 사용됩니다. 보통 multi-label 분류 등에 사용됩니다. 아래 두 손실 함수는 입력과 출력 텐서의 크기와 같아야 합니다.

optimizer 사용하기

그 다음 과정으로는 계산한 손실을 기반으로 역전파 알고리즘을 수행하고 모델의 가중치를 조정하기 위해 optimizer를 사용하게 됩니다. 이 optimizer들은 PyTorch에서는 nn.optim에 정의되어 있습니다.

optimizer 또한 모멘텀, AdaGrad, RMSProp, Adam 등 많은 종류가 있습니다. 많은 경우에서는 Adam을 사용하기는 하는데, PyTorch의 optimizer들은 다음과 같이 사용할 수 있습니다.

torch.optim.SGD(params, lr=<required parameter>, ...)
torch.optim.RMSProp(params, lr=0.01, alpha=0.99, ...)
torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), ...)

optimizer도 기본적으로 학습 단계 초기에 미리 정의한 후에, 각 step에서 메서드를 호출하는 방식을 따르고 있습니다. 사용할 optimizer를 정했다면, 이를 정의해 보겠습니다.

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

optimizer 정의에 기본적으로 사용되는 인수는 모델의 파라메터와 학습률입니다. 모델의 파라메터는 미리 정의한 모델에 .parameters()를 붙여주면 됩니다. 그 외에 RMSProp이나 Adam은 학습률 이외에 모멘텀 등의 하이퍼파라메터를 명시할 수 있습니다.

그러면 학습 단계 중 출력 텐서와 타겟 텐서 사이로부터 손실을 계산한 상태에서, 모델의 파라메터를 조절하는 과정을 코드로 나타내보겠습니다.

optimizer.zero_grad()
loss.backward()
optimizer.step()
  • optimizer.zero_grad(): gradient를 명시적으로 0으로 설정합니다. (초기화로 생각하면 편함)

  • loss.backward(): 손실에 대해 역전파 알고리즘으로 각 가중치에 대한 gradient를 계산합니다.

  • optimizer.step(): 계산한 gradient를 바탕으로 모델의 가중치를 실질적으로 조절합니다.

이 과정까지 수행했을 때 실질적으로 1 step이 마무리되고, 각 미니배치마다 수행해 모든 데이터에 대해 수행되었을 때 1 epoch를 학습했다라고 합니다.

0개의 댓글