[AI/ML] nn.Module과 클래스로 구현하기
PyTorch의 nn.Linear와 F.mse_loss를 활용한 선형 회귀 및 다중 선형 회귀 구현 방법을 다룹니다. 클래스 기반 모델 구현과 경사하강법 최적화 과정, 학습률 조정의 중요성을 설명합니다.
개요
AI에 대한 기초를 다시 정리하고자 아래 내용을 읽고 정리했습니다.
PyTorch 기반 선형 회귀 구현하기
파이토치는 선형 회귀 모델을 간단하게 쓸 수 있게 제공합니다.
import torch
import torch.nn as nn # 신경망을 구축하기 위한 레이어
import torch.nn.functional as F
torch.manual_seed(330)
# 샘플 데이터(y = 2x) 추론 가능
x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])
# 선형 회귀이므로 input_dim=1, output_dim=1.
model = nn.Linear(1,1)
print(list(model.parameters())) # W, b 출력 확인이 가능합니다.
# [Parameter containing:
# tensor([[0.8401]], requires_grad=True), Parameter containing:
# tensor([-0.3560], requires_grad=True)]
경사하강법 Optimizer를 설정하고 학습률은 0.01로 설정합니다.
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 전체 훈련 데이터에 대해 경사 하강법을 2,000회 반복
nb_epochs = 2000
for epoch in range(nb_epochs+1):
# H(x) 계산
prediction = model(x_train)
# cost 계산
cost = F.mse_loss(prediction, y_train) # <== 파이토치에서 제공하는 평균 제곱 오차 함수
optimizer.zero_grad() # 현재 위치에서 gradient 초기화
cost.backward() # 비용함수 기반 기울기 미분
optimizer.step() # W와 b를 업데이트
if epoch % 100 == 0:
# 100번마다 로그 출력
print('Epoch {:4d}/{} Cost: {:.6f}'.format(
epoch, nb_epochs, cost.item()
))
Epoch 0/2000 Cost: 8.056903
Epoch 100/2000 Cost: 0.001479
Epoch 200/2000 Cost: 0.000914
...
Epoch 1500/2000 Cost: 0.000002
Epoch 1600/2000 Cost: 0.000001
Epoch 1700/2000 Cost: 0.000001
Epoch 1800/2000 Cost: 0.000000
Epoch 1900/2000 Cost: 0.000000
Epoch 2000/2000 Cost: 0.000000
# 입력 값
new_var = torch.FloatTensor([[4.0]])
pred_y = model(new_var) # forward 연산
print("훈련 후 입력이 4일 때의 예측값 :", pred_y)
# ----------------------------------------
훈련 후 입력이 4일 때의 예측값 : tensor([[7.9992]], grad_fn=<AddmmBackward0>)
실제 Parameter를 통해 확인해보면 y = 2x와 가까운 것을 확인할 수 있습니다.
print(list(model.parameters()))
# ----------------------------------------
[Parameter containing:
tensor([[1.9995]], requires_grad=True), Parameter containing:
tensor([0.0010], requires_grad=True)]
PyTorch 기반 다중 선형 회귀 구현하기
import torch
import torch.nn as nn # 신경망을 구축하기 위한 레이어
import torch.nn.functional as F
torch.manual_seed(330)
# 데이터
x_train = torch.FloatTensor([[73, 80, 75],
[93, 88, 93],
[89, 91, 90],
[96, 98, 100],
[73, 66, 70]])
y_train = torch.FloatTensor([[152], [185], [180], [196], [142]])
# input_dim=3, output_dim=1.
# 3개의 feature와 1개의 output
model = nn.Linear(3,1)
3개의 feature이므로 3개의 weight가 확인 가능합니다.
print(list(model.parameters()))
# -----------------------------------------------
[Parameter containing:
tensor([[ 0.4850, -0.2056, -0.5197]], requires_grad=True), Parameter containing:
tensor([-0.0195], requires_grad=True)]
학습률(learning rate)은 0.00001로 설정합니다. 파이썬 코드로는 1e-5로도 표기 가능합니다.
학습률을 줄이는 이유는 feature가 많아지면 발산할 수 있기 때문이며 이는 최소값을 찾기 어려워집니다.
- 학습률(learning rate)이 모델의 필요한 크기보다 높을 때, 기울기가 발산하는 현상을 볼 수 있습니다.
optimizer = torch.optim.SGD(model.parameters(), lr=1e-5)
# 학습코드는 동일하나 20,000번으로 확장
nb_epochs = 20000
for epoch in range(nb_epochs+1):
prediction = model(x_train)
cost = F.mse_loss(prediction, y_train) # <== 파이토치에서 제공하는 평균 제곱 오차 함수
# cost로 H(x) 개선하는 부분
# gradient를 0으로 초기화
optimizer.zero_grad()
# 비용 함수를 미분하여 gradient 계산
cost.backward() # backward 연산
# W와 b를 업데이트
optimizer.step()
if epoch % 100 == 0:
# 100번마다 로그 출력
print('Epoch {:4d}/{} Cost: {:.6f}'.format(
epoch, nb_epochs, cost.item()
))
# -------------------------
Epoch 2000/20000 Cost: 0.237656
Epoch 2100/20000 Cost: 0.236994
Epoch 2200/20000 Cost: 0.236355
Epoch 2300/20000 Cost: 0.235728
Epoch 2400/20000 Cost: 0.235128
...
Epoch 19700/20000 Cost: 0.194540
Epoch 19800/20000 Cost: 0.194407
Epoch 19900/20000 Cost: 0.194277
Epoch 20000/20000 Cost: 0.194148
# 임의의 입력 [73, 80, 75]를 선언
new_var = torch.FloatTensor([[73, 80, 75]])
# 입력한 값 [73, 80, 75]에 대해서 예측값 y를 리턴받아서 pred_y에 저장
pred_y = model(new_var)
print("훈련 후 입력이 73, 80, 75일 때의 예측값 :", pred_y)
# -------------------------
훈련 후 입력이 73, 80, 75일 때의 예측값 : tensor([[151.6743]], grad_fn=<AddmmBackward0>)
파라미터를 확인하면 다음과 같습니다.
print(list(model.parameters()))
# -------------------------
[Parameter containing:
tensor([[1.1379, 0.5662, 0.3111]], requires_grad=True), Parameter containing:
tensor([-0.0149], requires_grad=True)]
Python 클래스를 사용한 모델 구현 방식
선형 회귀 구현 방식
클래스를 사용한 모델 구현 형식은 대부분의 파이토치 구현체에서 사용하고 있는 방식입니다.
따라서 모델 코드를 읽기 위해선 반드시 숙지할 필요가 있습니다.
선형 회귀 모델을 class로 구현하면 다음과 같습니다.
model = nn.Linear(1, 1)→ 이전 구현 방식(참고)
import torch
import torch.nn as nn # 신경망을 구축하기 위한 레이어
import torch.nn.functional as F
class LinearRegressionModel(nn.Module): # torch.nn.Module을 상속받는 파이썬 클래스
def __init__(self): #
super().__init__()
self.linear = nn.Linear(1, 1) # 단순 선형 회귀이므로 input_dim=1, output_dim=1.
def forward(self, x):
return self.linear(x)
model = LinearRegressionModel()
class 형태의 모델은 nn.Module을 상속받습니다. 이는 Module이 가지고 있는 메소드 model.parameters(), model.to(device) 등을 상속받아 사용할 수 있으며 model(x) 를 호출했을 때 forward를 실행할 수 있게 합니다.
- forward는 예측을 수행 H(x) 식에 입력 x로부터 y를 얻는 것
- backward는 비용 함수로부터 기울기를 구하는 것.
훈련 루프는 동일합니다.
# optimizer 설정. 경사 하강법 SGD를 사용하고 learning rate를 의미하는 lr은 0.01
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 전체 훈련 데이터에 대해 경사 하강법을 2,000회 반복
nb_epochs = 2000
for epoch in range(nb_epochs+1):
# H(x) 계산
prediction = model(x_train)
# cost 계산
cost = F.mse_loss(prediction, y_train) # <== 파이토치에서 제공하는 평균 제곱 오차 함수
# cost로 H(x) 개선하는 부분
# gradient를 0으로 초기화
optimizer.zero_grad()
# 비용 함수를 미분하여 gradient 계산
cost.backward() # backward 연산
# W와 b를 업데이트
optimizer.step()
if epoch % 100 == 0:
# 100번마다 로그 출력
print('Epoch {:4d}/{} Cost: {:.6f}'.format(
epoch, nb_epochs, cost.item()
))
다중 선형 회귀 구현 방식
선형 회귀 구성과 모델을 선언하는 방식은 동일합니다.
import torch
import torch.nn as nn # 신경망을 구축하기 위한 레이어
import torch.nn.functional as F
torch.manual_seed(330)
# 데이터
x_train = torch.FloatTensor([[73, 80, 75],
[93, 88, 93],
[89, 91, 90],
[96, 98, 100],
[73, 66, 70]])
y_train = torch.FloatTensor([[152], [185], [180], [196], [142]])
class MultivariateLinearRegressionModel(nn.Module):
def __init__(self):
super().__init__()
self.linear = nn.Linear(3, 1) # 다중 선형 회귀이므로 input_dim=3, output_dim=1.
def forward(self, x):
return self.linear(x)
model = MultivariateLinearRegressionModel()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-5)
nb_epochs = 2000
for epoch in range(nb_epochs+1):
# H(x) 계산
prediction = model(x_train)
# model(x_train)은 model.forward(x_train)와 동일함.
# cost 계산
cost = F.mse_loss(prediction, y_train) # <== 파이토치에서 제공하는 평균 제곱 오차 함수
# cost로 H(x) 개선하는 부분
# gradient를 0으로 초기화
optimizer.zero_grad()
# 비용 함수를 미분하여 gradient 계산
cost.backward()
# W와 b를 업데이트
optimizer.step()
if epoch % 100 == 0:
# 100번마다 로그 출력
print('Epoch {:4d}/{} Cost: {:.6f}'.format(
epoch, nb_epochs, cost.item()
))
참고
- 학습률을 0.001로 입력하게 되면 값이 Not a Number 표현이 발생하는 것을 확인할 수 있습니다.
다중 선형 회귀는 입력값이 크고(73, 80, 75...) gradient도 크기 때문에 학습률 0.01까지 곱하면 한 스텝이 너무 커집니다. 즉 최적점을 넘어가는 것으로 컴퓨터 상의 숫자로 표기되지 않습니다. 추후 Pytorch 상으로 Learning Rate Scheduler 를 통해 Optimize할 수 있는 방법도 있습니다.
Epoch 1900/20000 Cost: nan
Epoch 2000/20000 Cost: nan
Epoch 2100/20000 Cost: nan
Epoch 2200/20000 Cost: nan
Epoch 2300/20000 Cost: nan
Epoch 2400/20000 Cost: nan
...
Epoch 19700/20000 Cost: nan
Epoch 19800/20000 Cost: nan
Epoch 19900/20000 Cost: nan
Epoch 20000/20000 Cost: nan