이전 글에서 x가 1개일 때 H(x)=Wx+b에서의 선형 회귀가 아닌 x가 여러개일때의 선형회귀, H(x)=W1x1+W2x2+...+Wnxn+b를 살펴본다.
샘플(sample)
: 전체 훈련 데이터의 개수를 셀 수 있는 1개의 단위, 현재 샘플의 수는 총 5개
특성(feature)
: 각 샘플에서 yy를 결정하게 하는 각각의 독립 변수 x, 현재 특성은 3개
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# 훈련 데이터
x1_train = torch.FloatTensor([[73], [93], [89], [96], [73]])
x2_train = torch.FloatTensor([[80], [88], [91], [98], [66]])
x3_train = torch.FloatTensor([[75], [93], [90], [100], [70]])
y_train = torch.FloatTensor([[152], [185], [180], [196], [142]])
# 가중치 w와 편향 b 초기화
w1 = torch.zeros(1, requires_grad=True)
w2 = torch.zeros(1, requires_grad=True)
w3 = torch.zeros(1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# optimizer 설정
optimizer = optim.SGD([w1, w2, w3, b], lr=1e-5)
nb_epochs = 2000
for epoch in range(nb_epochs + 1):
# H(x) 계산: x_train의 개수만큼 w와 곱해주도록 작성해준 것을 확인 가능
hypothesis = x1_train * w1 + x2_train * w2 + x3_train * w3 + b
# cost 계산
cost = torch.mean((hypothesis - y_train) ** 2)
# cost로 H(x) 개선
optimizer.zero_grad()
cost.backward()
optimizer.step()
# 100번마다 로그 출력
if epoch % 100 == 0:
print('Epoch {:4d}/{} w1: {:.3f} w2: {:.3f} w3: {:.3f} b: {:.3f} Cost: {:.6f}'.format(
epoch, nb_epochs, w1.item(), w2.item(), w3.item(), b.item(), cost.item()
))
그러나 이 실제로 training data와 weight를 일일이 선언해 줄 수 없기 때문에, 행렬 곱셈 연산(벡터의 내적)을 사용한다.
H(x)=w1x1+w2x2+w3x3
1. 벡터 연산으로 표현
두 벡터를 W와 x로 표현한다면 H(x)=Wx로, x개 몇개이던 간에 x와 W 2개의 변수로 표현이 가능하다.
H(x)=XW+B
2. 행렬 연산으로 표현
결과적으로 훈련 데이터의 가설 연산을 3개의 변수(x, w, b)로만 표현이 가능해졌다.
이와 같은 벡터와 행렬 연산은 식을 간단하게 해줄 뿐만 아니라 다수의 샘플 병렬 연산이므로 속도의 이점도 가진다.
x_train을 일일이 구현하지 않고, 하나에 모든 샘플을 구현하였다. 5*3 행렬 X를 선언한 것이다.
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
# 훈련 데이터: x_train: 5*3 / y_train: 5*1
x_train = torch.FloatTensor([[73, 80, 75],
[93, 88, 93],
[89, 91, 80],
[96, 98, 100],
[73, 66, 70]])
y_train = torch.FloatTensor([[152], [185], [180], [196], [142]])
# 모델 초기화
W = torch.zeros((3, 1), requires_grad=True)
# 행렬의 곱셈이 성립되려면 곱셈의 좌측에 있는 행렬의 열의 크기와 우측에 있는 행렬의 행의 크기가 일치해야함
b = torch.zeros(1, requires_grad=True)
# optimizer 설정
optimizer = optim.SGD([W, b], lr=1e-5)
nb_epochs = 20
for epoch in range(nb_epochs + 1):
# H(x) 계산
# 편향 b는 브로드 캐스팅되어 각 샘플에 더해집니다.
hypothesis = x_train.matmul(W) + b # 가설을 행렬곱으로 간단히 정의
# cost 계산
cost = torch.mean((hypothesis - y_train) ** 2)
# cost로 H(x) 개선
optimizer.zero_grad()
cost.backward()
optimizer.step()
print('Epoch {:4d}/{} hypothesis: {} Cost: {:.6f}'.format(
epoch, nb_epochs, hypothesis.squeeze().detach(), cost.item()
))
행렬식으로 샘플을 훨씬 더 간단하게 변수 표현이 가능하고, 가설도 명료해졌다. 만약 사용자가 x의 수를 추가한다고 하더라도, 가설을 수정할 필요가 없게된다. cost func와 optimizer를 어떻게 사용할 것인지만 명시해 주면된다.
선형 회귀를 이해하기 위해 가설, 비용함수를 직접 정의해서 모델을 정의하였지만, 이미 파이토치에 모두 구현되어져있기 때문에 더 쉽게 구현이 가능하다.
1. 단순 선형 회귀 구현
y= 2x를 가정한 상태에서 모델에서 W와 b를 잘 찾는지 확인하는 것이 목표
import torch
import torch.nn as nn
import torch.nn.functional as F
# 데이터
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) # model에는 가중치 W와 b가 저장되어있음
print(list(model.parameters())) # 첫번째 값이 W, 두번째 값이 b가 랜덤 초기화
# 두 파라미터 모두 학습 대상이므로 requires_grad=True 설정
# optimizer 설정. 경사 하강법 SGD를 사용하고 learning rate를 의미하는 lr은 0.01
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) # model.parameters()로 W,b 전달
# 전체 훈련 데이터에 대해 경사 하강법을 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()
))
# 임의의 입력 4를 선언
new_var = torch.FloatTensor([[4.0]])
# 입력한 값 4에 대해서 예측값 y를 리턴받아서 pred_y에 저장
pred_y = model(new_var) # forward 연산
# y = 2x 이므로 입력이 4라면 y가 8에 가까운 값이 나와야 제대로 학습이 된 것
print("훈련 후 입력이 4일 때의 예측값 :", pred_y)
print(list(model.parameters()))
2. 다중 선형 회귀 구현
3개의 x로부터 하나의 y를 예측하는 문제로 H(x)=W1x1+W2x2+W3x3+b를 학습하는것이 목표
이제 nn.Linear()와 nn.functional.mse_loss()로 다중 선형 회귀를 구현한다. 사실 코드 자체는 달라지는 건 거의 없는데, nn.Linear()의 인자값과 학습률(learning rate)만 조절해 주었다.
import torch
import torch.nn as nn
import torch.nn.functional as F
# 데이터
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.
model = nn.Linear(3,1)
print(list(model.parameters()))
# optimizer 설정. 경사 하강법 SGD를 사용하고 learning rate를 의미하는 lr은 0.01
optimizer = torch.optim.SGD(model.parameters(), lr=1e-5)
# 전체 훈련 데이터에 대해 경사 하강법을 2,000회 반복
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()
))
# 임의의 입력 [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)
print(list(model.parameters()))
'논문 리뷰 > Neural Network' 카테고리의 다른 글
RNN, LSTM (0) | 2021.05.06 |
---|---|
Simple Linear Regression + Cost func / GD (0) | 2021.03.02 |
Neural Network + Activation func (0) | 2021.03.02 |
댓글