선형회귀 문제를 pytorch를 활용하여 풀어보기¶
학습 목표¶
- pytorch로 구현하는 딥러닝 기본에 대해 알아본다.
- pytorch를 이용하여 기본 선형회귀 모델을 만들어본다.
데이터¶
- 다섯명의 사람
- 신장과 체중은 다음과 같다.
- (166, 58.7), (176, 75.7), (171, 62.1), (173, 70.4), (169, 60.1)
- 주어진 신장으로부터 체중을 예측하는 머신러닝 모델을 만들어보기
In [27]:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import torch
In [28]:
height = [166,176, 171, 173, 169]
weight = [58.7, 75.7, 62.1, 70.4, 60.1]
sns.scatterplot(x=height, y=weight)
Out[28]:
<Axes: >
선형 회귀 구하기¶
- 산포도는 신장과 체중사이에서 일정한 관계가 있다.
- 다음과 같이 표현된 것은 1차 함수로 근사할 수 있을 것이다.
- 다섯개의 점과 가장 가까운 1차 함수를 구하는 것을 우리는 선형회귀라 말할 수 있다.
경사하강법¶
- 딥러닝 프로그래밍은 수학적으로 '경사하강법'으로 불리는 알고리즘에 기반하게 된다.
- 편미분, 선형대수와 같은 수학적 지식이 필요.
- 꼭대기에서 제일 낮은 지점까지 이동하는 방법.
- 경사하강법은 가장 작은 손실(오차)값을 구하는 문제 '최솟값을 구하는 문제'로 변경
- 예측 함수를 이용하여 출력 값을 구하면 원래 정답과 비교하여 손실(loss)을 계산한다. 이를 토대로 경사를 계산하여 경사값 변경하고, 파라미터(W)와 B(바이어스)를 조금씩 수정해 간다.
- 경사값에 작은 정수(학습률) lr를 곱해, 그 값만큼 W, B를 줄여나간다.
- 손실함수(loss)는 예측 함수에 따라 적절한 것을 고른다.
- 회귀 모델의 경우, 평균제곱오차(MSE)를 선택했다.
In [29]:
dat1 = np.array(height)
dat2 = np.array(weight)
dat1, dat2
Out[29]:
(array([166, 176, 171, 173, 169]), array([58.7, 75.7, 62.1, 70.4, 60.1]))
In [30]:
# 여러 개의 2차원 데이터 배열(예: 이미지 채널)을 하나의 3차원 배열로 결합할 때 사용
# NumPy의 dstack 함수를 사용하여 두 배열(dat1과 dat2)을 세 번째 축(깊이)을 따라 쌓습니다.
dat_all = np.dstack([dat1, dat2])
dat_all
Out[30]:
array([[[166. , 58.7], [176. , 75.7], [171. , 62.1], [173. , 70.4], [169. , 60.1]]])
In [31]:
### 데이터를 평균값을 0으로 되도로 변환해 준다.
X = dat1 - dat1.mean()
Y = dat2 - dat2.mean()
X와 Y를 텐서 변수로 변환하기¶
In [32]:
X = torch.tensor(X).float()
Y = torch.tensor(Y).float()
# 결과확인
print(X)
print(Y)
tensor([-5., 5., 0., 2., -2.]) tensor([-6.7000, 10.3000, -3.3000, 5.0000, -5.3000])
예측 함수 : Yp = W * X + B¶
- W와 B의 정의
- W와 B는 경사 계산을 위해 requires_grad=True로 설정.
- requires_grad = True를 통해 미분 대상의 텐서가 됨.
In [33]:
W = torch.tensor(1.0, requires_grad = True).float()
B = torch.tensor(1.0, requires_grad = True).float()
- 두 변수의 초깃값을 1.0으로 설정.
예측값 Yp의 계산¶
In [34]:
def pred(X):
return W * X + B
In [35]:
# 예측 값 계산
Yp = pred(X)
# 결과 확인
print(Yp)
tensor([-4., 6., 1., 3., -1.], grad_fn=<AddBackward0>)
손실함수 계산¶
In [36]:
def mse(Yp, Y):
loss = ( (Yp - Y) ** 2 ).mean()
return loss
손실 계산¶
In [41]:
loss = mse(Yp, Y)
print(loss)
tensor(13.3520, grad_fn=<MeanBackward0>)
In [42]:
# W = torch.tensor(1.0, requires_grad = True).float()
# B = torch.tensor(1.0, requires_grad = True).float()
In [43]:
# 경사값 확인
# W는 일반적으로 가중치(Weight) 텐서를, B는 편향(Bias) 텐서를 나타냅니다.
# .grad 속성은 이 텐서들에 대한 손실 함수의 기울기(gradient)를 저장합니다.
# 경사는 손실 함수를 해당 파라미터(W 또는 B)로 편미분한 값입니다.
# 이 값은 해당 파라미터가 손실 함수에 미치는 영향의 방향과 크기를 나타냅니다.
print(W.grad)
print(B.grad)
None None
In [44]:
# 경사 계산은 backward()로 호출로 가능
# 보통 loss.backward() 메서드를 호출한 후에 이 경사 값들이 계산됩니다.
loss.backward()
In [45]:
# 경사값 확인
print(W.grad)
print(B.grad)
tensor(-19.0400) tensor(2.0000)
파라미터 수정¶
- 경사 계산 후, 그 값에 일정한 학습률 lr(0.01이나 0.001과 같은 값으로 설정)을 곱하여, 원래의 파라미터에서 빼준다.
In [46]:
# 학습률 정의
lr = 0.001
# 경사를 기반으로 파라미터 수정
W = W - lr * W.grad
B = B - lr * B.grad
In [47]:
print(W)
print(B)
tensor(1.0190, grad_fn=<SubBackward0>) tensor(0.9980, grad_fn=<SubBackward0>)
- 초깃값이 W, B가 모두 1이었다. W는 증가하는 방향으로, B는 감소하는 방향으로 조금씩 변화했다.
- 이 계산을 반복해서 W와 B를 최적의 값으로 수렴시키는 것이 경사 하강법이다.
- 가중치(Weight)와 편향(bias) 초기화
- 경사 하강법 구현
- SGD는 경사 하강법의 일정, lr은 학습률(learning rate)를 의미)
- SGD - 확률적 경사 하강법(Stochastic Gradient Descent)
- 점진적 학습의 대표적 알고리즘
- 학습용 데이터 세트에서 샘플 하나씩 꺼내(랜덤) 손실 함수의 경사를 따라 최적의 모델을 찾는 알고리즘
투자한 시간과 게임 캐릭터 능력치 향상. 상관관계 예측¶
- 4시간 투자한다면 게임 캐릭터 능력 향상은 어떻게 될까?
In [48]:
# 도구 임포트
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
In [49]:
# 초기화
W = torch.zeros(1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)
In [50]:
# optimizer 설정
optimizer = optim.SGD([W, b], lr=0.01)
# 경사 하강법 반복
num_epochs = 200
# 학습 기록을 위한 배열 초기화
hist = np.zeros( (0, 2))
- 1차 함수의 계수와 정수항 W와 B는 초기상태로 변경.
- 반복 계산하는 횟수는 num_epochs라는 이름의 변수로 총 500회 설정.
- 학습률은 lr = 0.001
시간(hours) | 시간당능력치향상(p) |
---|---|
1 | 200 |
2 | 400 |
3 | 600 |
In [51]:
# 데이터
X = torch.FloatTensor([[1], [2], [3]])
Y = torch.FloatTensor([[200], [400], [600]])
In [52]:
X, Y
Out[52]:
(tensor([[1.], [2.], [3.]]), tensor([[200.], [400.], [600.]]))
In [53]:
def mse(Yp, Y):
loss = ( (Yp - Y) ** 2 ).mean()
return loss
In [54]:
# 루프 처리
for epoch in range(num_epochs + 1):
# 예측 계산
Yp = W * X + B
# 손실 계산
loss = mse(Yp, Y)
# Gradient 초기화
optimizer.zero_grad()
# 경사 계산 - 역전파를 통한 기울기 계산
loss.backward()
# 파라미터 업데이트
optimizer.step()
# 손실 기록
if (epoch % 10 == 0):
item = np.array([epoch, loss.item()])
hist = np.vstack( ( hist, item ))
print(f"epoch = {epoch} loss = {loss:.4f}")
epoch = 0 loss = 185869.2656 epoch = 10 loss = 26191.6309 epoch = 20 loss = 3690.8821 epoch = 30 loss = 520.2183 epoch = 40 loss = 73.4279 epoch = 50 loss = 10.4696 epoch = 60 loss = 1.5975 epoch = 70 loss = 0.3473 epoch = 80 loss = 0.1712 epoch = 90 loss = 0.1464 epoch = 100 loss = 0.1429 epoch = 110 loss = 0.1424 epoch = 120 loss = 0.1423 epoch = 130 loss = 0.1423 epoch = 140 loss = 0.1423 epoch = 150 loss = 0.1423 epoch = 160 loss = 0.1423 epoch = 170 loss = 0.1423 epoch = 180 loss = 0.1423 epoch = 190 loss = 0.1423 epoch = 200 loss = 0.1423
In [55]:
hist
Out[55]:
array([[0.00000000e+00, 1.85869266e+05], [1.00000000e+01, 2.61916309e+04], [2.00000000e+01, 3.69088208e+03], [3.00000000e+01, 5.20218323e+02], [4.00000000e+01, 7.34279480e+01], [5.00000000e+01, 1.04695635e+01], [6.00000000e+01, 1.59749067e+00], [7.00000000e+01, 3.47343534e-01], [8.00000000e+01, 1.71174988e-01], [9.00000000e+01, 1.46359429e-01], [1.00000000e+02, 1.42860174e-01], [1.10000000e+02, 1.42365798e-01], [1.20000000e+02, 1.42302439e-01], [1.30000000e+02, 1.42292589e-01], [1.40000000e+02, 1.42291188e-01], [1.50000000e+02, 1.42285168e-01], [1.60000000e+02, 1.42285168e-01], [1.70000000e+02, 1.42285168e-01], [1.80000000e+02, 1.42285168e-01], [1.90000000e+02, 1.42285168e-01], [2.00000000e+02, 1.42285168e-01]])
In [56]:
# 최종 파리미터 확인
print("W = ", W.data.numpy())
print("B = ", B.data.numpy())
# 손실 확인
print(f"초기 상태 : 손실 : {hist[0,1]:.4f}")
print(f"최종 상태 : 손실 : {hist[-1,1]:.4f}")
W = [199.57222] B = 0.998 초기 상태 : 손실 : 185869.2656 최종 상태 : 손실 : 0.1423
In [57]:
plt.plot(hist[ :, 0], hist[:, 1], '--b')
Out[57]:
[<matplotlib.lines.Line2D at 0x7f35449b9a20>]
실습해 보기 1¶
- optim.SGD에서 momentum=0.9로 변경후, 확인해 보자.(hist는 hist2로 변경)
- 그래프를 그려보자.
실습해 보기 2¶
- 신장과 체중은 다음과 같다. (166, 58.7), (176, 75.7), (171, 62.1), (173, 70.4), (169, 60.1)
- 주어진 신장으로부터 체중을 예측하는 머신러닝 모델을 만들어보기
- SGD의 최적화 함수 클래스에서 momentum으로 불리는 것을 설정했을 때, 학습이 빠르게 되어있음을 확인할 수 있다.
- 최적화 함수 또는 파라미터 변경으로 알고리즘 최적화 가능하다.