선형 회귀 (Linear regression) 란, 데이터의 특징 변수와 목표 변수 사이의 선형 관계를 분석하여 데이터에 포함되지 않은 새로운 데이터의 결과를 연속적인 숫자 값으로 예측하는 것이다.
신경망의 관점에서 선형 회귀는, 입력층의 특징 변수(x)가 출력층의 예측 변수(y)로 mapping되는 것이다. 데이터의 특성을 가장 잘 표현하는 y = a*x + b 라는 관계식을 도출하며 입력층의 뉴런과 출력층의 뉴런이 연결되며, a(가중치)와 b(바이어스)를 찾는 과정이 학습이다.
☆ (주의) PyTorch tensor와 모델을 다루다 보면
RuntimeError: expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu에러가 뜰 때가 많다. 이는 모델과 텐서가 다른 device (CPU/GPU)에 저장되어서 발생하는 문제이다. 그래서 반드시 device를 하나로 정의하고 모델과 텐서를 한 device에 저장해주는 것이 중요하다.
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model.to(device) a_tensor = a_tensor.to(device)
학습을 시키다 보면 손실 값이 31034959.0 처럼 굉장히 크게 나올 때가 있다. 손실 값이 크게 나오는 원인에는 지나치게 큰 학습률, 데이터의 노이즈, 지나치게 작은 에폭 수 등이 있는데 데이터 표준화를 통해서도 손실 값을 줄일 수 있다.
데이터 표준화에도 여러가지 방법이 있지만 독립 변수와 목표 변수를 표준 정규 분포 (평균 = 0, 분산 = 1)로 스케일링 하는 방법을 사용해 본다. sklearn의 StandardScaler를 이용해서 스케일링 할 수 있다.
# train_x = x of training sample [numpy array]
# train_y = y of training sample [numpy array]
from sklearn.preprocessing import StandardScaler
scaler_x = StandardScaler()
scaler_y = StandardScaler()
train_x_scaled = scaler_x.fit_transform(train_x.reshape(-1, 1))
train_y_scaled = scaler_y.fit_transform(train_y.reshape(-1, 1))
Q. 어차피 똑같은 StandardScaler() 를 이용하는데 굳이 scaler_x = StandardScaler() scaler_y = StandardScaler() 과 같이 따로 하는 이유가 무엇일까?
스케일링을 다 했다면 이제 모델을 학습시킬 수 있는 tensor로 저장하자.
import torch
x_tensor = torch.tensor(train_x_scaled, dtype=torch.float32).view(-1, 1)
y_tensor = torch.tensor(train_y_scaled, dtype=torch.float32).view(-1, 1)
# Don't forget to save tensors in "device"
x_tensor.to(device)
y_tensor.to(device)
import torch.nn as nn
# nn.Module이라는 부모 class의 기능을 상속받은 자식 class
class LinearRegressionModel(nn.Module):
def __init__(self): # 생성자 메서드
super(LinearRegressionModel, self).__init__() # 부모 class인 nn.Module의 생성자를 호출하여 상속받은 모든 초기화 작업을 수행
self.linear = nn.Linear(1,1) # class 속성 : 입력과 출력 차원이 모두 1인 선형 계층을 생성하는 PyTorch 함수. 모델의 구조와 파라미터를 정의한다.
def forward(self, x): # 순전파 메서드 : 특징 변수 x를 받아 self.linear 속성을 통해 예측 변수 y를 계산하고 반환
y = self.linear(x)
return y
# 인스턴스 생성
model = LinearRegressionModel()
model.to(device) # 반드시 device를 지정해 줄 것!
# 보충 설명
# 생성자 메서드 = 클래스의 인스턴스가 생성될 때 자동으로 호출되어 인스턴스 변수를 초기화하고 초기 설정 작업 수행
# 순전파 메서드 = 모델이 입력 데이터를 받아 출력을 계산하는 과정
그러나 모델을 만들었다고 끝이 아니다. 모델이 데이터에 맞게 학습하여 가중치와 바이어스값을 "적절히" 찾으려면, 데이터에 맞게 잘 학습하고 있는지 평가할 지표가 필요하다. 이 때 사용할 수 있는 것이 손실 함수 (loss function) 이다.
손실 함수는 목표 변수와 예측 변수 사이의 차이인 오차를 측정하는 함수이다. 따라서 이 손실 함수의 값이 가장 작도록 한다면, 모델이 데이터에 맞게 잘 학습된 것이라고 할 수 있다. 오차의 값은 양수와 음수가 혼재해 있어 상쇄되면 정확한 오차를 알기 어렵기 때문에, 보통은 제곱 혹은 절대값을 취해 더하는 방식으로 구하게 된다.
손실 함수에도 다양한 종류가 있지만, 여기에서는 평균 제곱 오차 (Mean Squared Error; MSE) 손실 함수를 다루기로 한다. ( 는 linear regression의 형태에 의한 것이다. 는 가중치, 는 바이어스)
MSE loss function =
아래 코드를 통해서 손실 함수를 생성할 수 있다.
loss_function = nn.MSELoss()
학습이 잘 되고 있는지 평가할 지표인 손실함수까지 준비했으니, 본격적으로 데이터에 잘 맞도록 모델을 학습시키는 "최적화"를 하게 된다. 머신러닝에서 최적화 알고리즘 역시 다양하지만, 여기서는 확률적 경사하강법을 이용해 보도록 한다.
import torch.optim as optim
optimizer = optim.SGD(model.parameters(), lr=0.01)
에폭은 모델이 전체 데이터를 한 번 완전히 학습하는 과정이다. 보통은 동일한 데이터 셋으로 여러 번의 에폭을 통해 모델을 반복 학습한다. 하지만 에폭 수가 너무 많으면 overfitting 될 수 있으므로, 적당한 epoch을 선택해서 학습해야 한다.
epochs = 100
loss_list = []
for e in range(epochs):
y = model(x_tensor) # prediced value
loss = loss_function(y, y_tensor)
optimizer.zero_grad() # Initializing the gradient
loss.backward() # 현재 loss에 대한 gradient 계산 (역전파 수행)
optimizer.step() # Updating the weight based on the gradient
loss_list.append(loss.item())
여기서 weight, bias 값을 매 단계마다 출력해보고 싶다면 아래와 같은 코드를 추가하면 된다.
for name, param in model.named_parameters():
print('{} = {}'.format(name, param.data))
Optimization이 끝나면 y = a*x + b 식이 피팅되어서 a(가중치), b(바이어스) 결과 값이 나올 것이다.
weight = model.linear.weight.item() # a
bias = model.linear.bias.item() # b
그러나 유의해야 할 점!!!!!
위에서 데이터 표준화를 진행했기 때문에 a, b의 값도 표준화된 값이다. 따라서 기존의 스케일로 변경해줘야 한다.
orig_w = weight*(scaler_y.scale_[0] / scaler_x.scale_[0])
orig_b = bias*scaler_y.scale_[0] + scaler_y.mean_[0] - orig_w*scaler_x.mean_[0]
이제 테스트 데이터로 모델이 예측을 잘 하는지 테스트해야 한다.
먼저 테스트 데이터를 표준화한다.
# test = test sample [numpy array]
test_scaled = scaler_x.transform(test.reshape(-1, 1))
test_tensor = torch.tensor(test_scaled, dtype=torch.float32).view(-1, 1).to(device)
표준화한 테스트 데이터를 모델을 사용하여 예측한다.
테스트 데이터를 사용할 때에는 모델을 평가 모드로 전환하여 예측해야 한다.
model.eval() # 평가 모드로 전환
with torch.no_grad(): # gradient 계산 비활성화
prediction_scaled = model(test_tensor)
그리고 표준화를 해제하면 테스트 데이터로 예측된 결과를 확인할 수 있다.
prediction = scaler_y.inverse_transform(prediction_scaled.cpu().numpy())
https://velog.io/@kupulau/%EC%86%90%EC%8B%A4-%ED%95%A8%EC%88%98