이전에 backward() 함수를 사용하여 기울기를 계산할 수 있음을 살펴봤습니다.
하지만 기울기만으로 모델을 학습시키는 것은 충분치 않습니다.
모델의 파라미터를 업데이트하는 방법도 알아야 합니다.
이때 옵티마이저가 필요합니다.
torch.optim 모듈에는 사용할 수 있는 여러 옵티마이저가 포함되어 있습니다.
대표적인 예로 optim.SGD와 optim.Adam이 있습니다.
옵티마이저를 초기화할 때 접근 가능한 모델 파라미터(model.parameters())를 전달하여
옵티마이저가 어떤 값을 최적화할 지 알려줍니다.
옵티마이저에는 학습률(lr) 매개 변수도 있는데 이는 각 단계에서 얼마나 큰 업데이트를 수행할 지 결정합니다.
여기서 잠시! 옵티마이저의 역할이 무엇인지 모델의 학습 과정을 통해 다시 떠올려봅시다.
MSE, MAE)epoch만큼 반복이제 간단한 선형 모델을 만들어보고 초기 예측 결과와 파라미터를 먼저 확인해보겠습니다.
그 다음은 역전파, 옵티마이저로 모델을 학습시킨 후 다시 모델의 예측 결과와 학습된 파라미터를 확인해보겠습니다.
먼저 필요한 모듈을 불러오겠습니다.
import torch
import torch.nn as nn
import torch.optim as optim
이번에 만들 모델은 데이터에 노이즈가 있음에도 오리지널 데이터를 예측하는 모델입니다.
따라서 y는 모든 원소가 1인 텐서로 하고 이 y에 노이즈를 섞어 x를 만들겠습니다.
x는 입력 텐서와 크기, 데이터 타입이 y와 동일하며 표준 정규 분포에서 추출된 난수로 채워진 텐서입니다.
# y 만들기
y = torch.ones(10, 5)
# x 생성을 위해 y에 노이즈 추가
x = y + torch.randn_like(y)
print("y is", y, "\n")
print("x is", x)
# 출력
y is tensor([[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.],
[1., 1., 1., 1., 1.]])
x is tensor([[-1.1354e-01, 1.9014e+00, 1.0312e+00, 3.0629e+00, 1.0582e+00],
[ 6.0861e-01, 1.7676e-01, -6.1808e-02, 1.4189e+00, 7.0285e-01],
[ 1.5628e+00, -3.7195e-01, 2.5482e+00, 1.0775e+00, 6.9995e-01],
[ 1.0583e+00, 7.7514e-01, 1.7547e-01, -4.6757e-01, 2.8601e+00],
[ 6.3708e-01, 9.6506e-04, -9.3495e-02, 2.9842e+00, 7.9916e-01],
[ 2.3509e+00, 8.7704e-01, 1.2485e-01, 1.0433e+00, -8.5369e-01],
[ 1.0512e+00, 1.0587e+00, 1.0901e+00, 2.5794e-01, 2.6553e-01],
[-6.3381e-01, 2.0558e-01, 8.6791e-03, 5.9766e-02, 2.1045e+00],
[ 3.2758e-01, 9.4100e-01, 1.0883e+00, 1.3567e+00, 2.4194e-01],
[ 1.2797e+00, 3.3573e+00, -1.5651e-01, 9.7730e-01, 1.8291e+00]])
이제 모델과 옵티마이저, 손실 함수를 정의할 수 있습니다.
모델은 선형 레이어로 구성하도록 하겠습니다.
class MultilayerPerceptron(nn.Module):
def __init__(self, input_size, hidden_size):
super(MultilayerPerceptron, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.linear = nn.Linear(self.input_size, self.hidden_size)
self.relu = nn.ReLU()
self.linear2 = nn.Linear(self.hidden_size, self.input_size)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
linear = self.linear(x)
relu = self.relu(linear)
linear2 = self.linear2(relu)
output = self.sigmoid(linear2)
return output
이제 예시 모델을 하나 만들고 옵티마이저와 손실 함수를 정의한 후 예측해보겠습니다.
이때의 예측은 학습이 전혀 되지 않은 초기 파라미터로 예측한 결과입니다.
# 모델 예시
model = MultilayerPerceptron(5, 3)
# 옵티마이저 정의
adam = optim.Adam(model.parameters(), lr=1e-1)
# 사전 정의된 손실 함수 사용
loss_function = nn.MSELoss()
# 예측 출력
y_pred = model(x)
print(y_pred, "\n")
print(loss_function(y_pred, y).item())
tensor([[0.4838, 0.4247, 0.3934, 0.3807, 0.6222],
[0.4866, 0.4219, 0.3952, 0.3880, 0.6156],
[0.6573, 0.4223, 0.5902, 0.3175, 0.7491],
[0.5094, 0.4956, 0.4513, 0.1935, 0.8229],
[0.4891, 0.4194, 0.3969, 0.3948, 0.6094],
[0.5234, 0.4200, 0.4344, 0.3790, 0.6394],
[0.5072, 0.4573, 0.4659, 0.3358, 0.6832],
[0.4471, 0.4618, 0.3697, 0.2902, 0.7042],
[0.4836, 0.4246, 0.3995, 0.3932, 0.6100],
[0.3918, 0.5190, 0.3479, 0.1982, 0.7883]], grad_fn=<SigmoidBackward0>)
0.29249364137649536
학습이 되지 않았으므로 예측 결과가 1에 가깝지 않은 것을 확인할 수 있습니다.
이 때의 MSE는 약 0.292입니다.
파라미터도 확인해보겠습니다.
list(model.parameters())
[Parameter containing:
tensor([[-0.2071, 0.4435, 0.2313, -0.4437, -0.3986],
[ 0.0398, 0.3450, -0.2399, -0.2333, 0.3811],
[ 0.3950, -0.3457, 0.3693, -0.3500, 0.1177]], requires_grad=True),
Parameter containing:
tensor([0.1912, 0.0150, 0.0854], requires_grad=True),
Parameter containing:
tensor([[-0.2371, -0.1995, 0.4703],
[ 0.2304, 0.2033, 0.0083],
[ 0.1175, -0.1359, 0.5302],
[-0.0724, -0.5518, -0.2288],
[ 0.0268, 0.4992, 0.4394]], requires_grad=True),
Parameter containing:
tensor([-0.0434, -0.3254, -0.4186, -0.4271, 0.4448], requires_grad=True)]
이제 이 모델이 가장 작은 손실을 찾을 수 있는지 살펴봅시다.
# 학습 반복 수 결정
n_epoch = 10
for epoch in range(n_epoch):
# 기울기 0으로 설정
adam.zero_grad()
# 모델 예측 가져오기
y_pred = model(x)
# 손실 계산
loss = loss_function(y_pred, y)
# 상태 출력
print(f"Epoch {epoch}: training loss: {loss}")
# 기울기 계산
loss.backward()
# 가중치 최적화
adam.step()
Epoch 0: training loss: 0.29249364137649536
Epoch 1: training loss: 0.22530561685562134
Epoch 2: training loss: 0.14835815131664276
Epoch 3: training loss: 0.08045881986618042
Epoch 4: training loss: 0.03580517694354057
Epoch 5: training loss: 0.01303208339959383
Epoch 6: training loss: 0.004201842471957207
Epoch 7: training loss: 0.0012982155894860625
Epoch 8: training loss: 0.00040363226435147226
Epoch 9: training loss: 0.00012991733092349023
학습이 반복될 때마다 loss가 감소하는 것을 확인할 수 있습니다.
모델의 업데이트된 파라미터를 확인해봅시다.
list(model.parameters())
[Parameter containing:
tensor([[ 0.5749, 1.2095, 0.9809, 0.3863, 0.4089],
[-0.5730, -0.1620, -0.6643, -0.6632, -0.0520],
[ 1.1777, 0.4070, 1.1535, 0.4234, 0.9343]], requires_grad=True),
Parameter containing:
tensor([ 1.0153, -0.4078, 0.9026], requires_grad=True),
Parameter containing:
tensor([[0.5414, 0.3673, 1.2965],
[1.0360, 0.7962, 0.8471],
[0.8769, 0.4346, 1.3443],
[0.7746, 0.0782, 0.6447],
[0.7771, 1.0744, 1.2470]], requires_grad=True),
Parameter containing:
tensor([0.6868, 0.4454, 0.3039, 0.4179, 1.1448], requires_grad=True)]
초기 파라미터와 비교하여 달라진 것을 확인할 수 있습니다.
이제 학습된 모델의 예측을 확인해보고 이 예측이 원본 y와 가까운 값을 갖는지 확인해보겠습니다.
y는 모두 1 값을 가졌었습니다.
y_pred = model(x)
y_pred
tensor([[1.0000, 1.0000, 1.0000, 0.9997, 1.0000],
[0.9966, 0.9951, 0.9980, 0.9836, 0.9986],
[1.0000, 1.0000, 1.0000, 0.9998, 1.0000],
[0.9999, 0.9998, 1.0000, 0.9987, 1.0000],
[0.9989, 0.9982, 0.9995, 0.9923, 0.9996],
[0.9995, 0.9994, 0.9998, 0.9966, 0.9998],
[0.9998, 0.9998, 0.9999, 0.9983, 0.9999],
[0.9897, 0.9852, 0.9925, 0.9627, 0.9952],
[0.9995, 0.9996, 0.9999, 0.9974, 0.9999],
[1.0000, 1.0000, 1.0000, 0.9999, 1.0000]], grad_fn=<SigmoidBackward0>)
예측이 1에 가까워진 것을 확인할 수 있습니다.
그럼 테스트 데이터를 만들고 이 테스트 데이터에 대해선 예측을 어떻게 수행하는지 확인해보겠습니다.
x2 = y + torch.randn_like(y)
y_pred = model(x2)
y_pred
tensor([[1.0000, 1.0000, 1.0000, 0.9999, 1.0000],
[0.9999, 0.9999, 1.0000, 0.9993, 1.0000],
[1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000, 0.9996, 1.0000],
[0.9999, 1.0000, 1.0000, 0.9996, 1.0000],
[1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000, 1.0000, 1.0000],
[1.0000, 1.0000, 1.0000, 0.9997, 1.0000],
[0.9993, 0.9978, 0.9996, 0.9910, 0.9997],
[0.9996, 0.9991, 0.9998, 0.9954, 0.9998]], grad_fn=<SigmoidBackward0>)
테스트 데이터에 대해서도 훌륭한 결과를 확인할 수 있습니다.
모델이 x의 노이즈를 필터링하는 방법을 거의 완벽하게 학습한 것 같습니다.