본문 바로가기
파이썬 프로그래밍/Numpy 딥러닝

2. 선형회귀 구현하기(실습)

by Majestyblue 2022. 1. 3.

저번시간에 X = [1, 2, 3], Y = [3, 5, 7] 의 입력(X)와 출력(Y)가 있을 때 이 둘의 관계를 Y = wx + b로 가정하고 w, b를 구하기 위해 도함수를 구하고 경사하강법을 이용한다고 하였다.

 

입력값 input X, 목표값 target Y, weight W, bias B를 Numpy로 표현하면 아래와 같다. 

(아래 learning_rate는 추후에 설명)

import numpy as np

np.random.seed(220102)

input = np.array([[1.],
                  [2.],
                  [3.]])

target = np.array([[3.], 
                   [5.], 
                   [7.]])

W = np.random.randn(1, 1) # [[0.97213265]] correct value = 2
B = np.random.randn(1, 1) # [[0.28608494]] correct value = 1

learning_rate = 0.001

 

 

 

1. 순방향 계산(forward) -> 예측(Predict)

함수 g(W, B)의 정의

순방향 계산으로 g(W, B)의 출력값이 Y와 같도록 학습해야 한다. 

 

 

2. 오차 함수(loss function) L(g(W, B)는 아래와 같이 정의하였었다.

오차함수 L(W, B)의 정의

 

 

순방향 계산(pred)와 오차값(loss)를 표현한 코드는 아래와 같다.

def linear_forward(X, Y, W, B):
    XW = np.dot(X, W)
    pred = XW + B
    
    loss = np.mean(np.power(Y - pred, 2))
    return pred, loss

 

 

3. 오차값의 기울기 즉 오차함수의 도함수를 구한다. 이전 시간에 아래와 같이 

    (헷갈린다면 선형회귀 구현하기(이론) 편을 같이 보길 바란다)

 

오차 함수의 W, B에 대한 도함수를 구하였었다. 이를 파이썬 코드로 표현해 보자.

def loss_gradient(X, Y, W, B):
    XWB = np.dot(X, W) + B
    
    #∂L(g(X,W,B)) / ∂g(X,W,B)
    dL_dg = 2*(XWB - Y)
    
    #∂g(X,W,B) / ∂W
    dg_dW = np.transpose(X, (1, 0))
    
    #∂L(G(X, W, B)) / ∂W
    dL_dW = np.dot(dg_dW, dL_dg)
    
    #∂L(g(X,W,B)) / dB
    dL_dB = np.sum(dL_dg, axis=0)
    
    return dL_dW, dL_dB

 

경사하강법(Gradient descent)를 아래와 같이 표현했었다. 

 

 

도함수값을 구하고, 경사하강법을 적용하는 것을 반복한다면 목표로 하는 W, B를 구할 수 있을 것이다.

아래는 경사하강법을 이용한 학습과정이다.

for i in range(100):
    dL_dW, dL_dB = loss_gradient(input, target, W, B)
    W = W + -1*learning_rate * dL_dW
    B = B + -1*learning_rate * dL_dB

 

 

학습전과 학습 후를 살펴보자.

#학습 전

pred :  [[1.25821759]
	 [2.23035024]
	 [3.20248289]]
loss :  8.375300661720344
before weight : [[0.97213265]] correct value = 2
before bias : [[0.28608494]] correct value = 1



#학습 후
pred :  [[2.83083978]
	 [4.88249283]
	 [6.93414589]]
loss :  0.015586626144295427
after weight :  [[2.05165305]]
after bias :  [[0.77918673]]

 

 

정말 학습이 잘 되고 있는지 눈으로 확인해 볼 수 있다.

loss-weight 그래프를 그려보자. 대략 2 부근에서 loss값은 최소가 될 것이다. 코드와 결과는 아래와 같다.

weights = np.arange(0, 5, 0.2)
loss_array = []

for weight in weights:
    _, loss = linear_forward(input, target, weight, B)
    loss_array.append(loss)

fig = plt.figure() 
ax = plt.axes(xlim=(-0, 4))
ax.plot(weights, loss_array)
ax.set_xlabel('weight')
ax.set_ylabel('loss')
plt.show()

 

 

학습 시 W가 어떻게 변하는지 애니매이션을 통해 관찰해 보자

(https://toyourlight.tistory.com/3 여기 링크 참고)

일단 훈련 부분을 수정해야 한다. W의 훈련 결과를 저장해야 한다.

train_W = []
for i in range(100):
    dL_dW, dL_dB = loss_gradient(input, target, W, B)
    W = W + -1*learning_rate * dL_dW
    B = B + -1*learning_rate * dL_dB
    train_W.append(W)

 

 

애니메이션을 설정한다.

redDot, = plt.plot([], [], 'ro')


def animate(frame):
    _, loss = linear_forward(input, target, frame, B)
    redDot.set_data(frame, loss)
    return redDot

ani = FuncAnimation(fig, animate, frames=train_W)
    
plt.show()

 

 

 

빨간 점은 학습될 때 마다 갱신되는 W 값이다. B도 동일하게 구할 수 있다.

 

아래는 전체 코드이다.

 

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
#import matplotlib.animation as animation

np.random.seed(220102)

input = np.array([[1.],
                  [2.],
                  [3.]])

target = np.array([[3.], 
                   [5.], 
                   [7.]])

W = np.random.randn(1, 1) # [[0.97213265]] correct value = 2
B = np.random.randn(1, 1) # [[0.28608494]] correct value = 1

learning_rate = 0.001

def linear_forward(X, Y, W, B):
    XW = np.dot(X, W)
    pred = XW + B
    
    loss = np.mean(np.power(Y - pred, 2))
    return pred, loss

def loss_gradient(X, Y, W, B):
    XWB = np.dot(X, W) + B
    
    #∂L(g(X,W,B)) / ∂g(X,W,B)
    dL_dg = 2*(XWB - Y)
    
    #∂g(X,W,B) / ∂W
    dg_dW = np.transpose(X, (1, 0))
    
    #∂L(G(X, W, B)) / ∂W
    dL_dW = np.dot(dg_dW, dL_dg)
    
    #∂L(g(X,W,B)) / dB
    dL_dB = np.sum(dL_dg, axis=0)
    
    return dL_dW, dL_dB


pred, loss = linear_forward(input, target, W, B)
"""
print('pred : ', pred)
print('loss : ', loss)
pred :  [[1.25821759]
 [2.23035024]
 [3.20248289]]
loss :  8.375300661720344

"""

"""
dL_dW, dL_dB = loss_gradient(input, output, W, B)
print('before weight : ', W)
print('before bias : ', B)
before weight : [[0.97213265]] correct value = 2
before bias : [[0.28608494]] correct value = 1
"""


train_W = []
for i in range(100):
    dL_dW, dL_dB = loss_gradient(input, target, W, B)
    W = W + -1*learning_rate * dL_dW
    B = B + -1*learning_rate * dL_dB
    train_W.append(W)


"""
print('after weight : ', W)
print('after bias : ', B)
after weight :  [[2.05165305]]
after bias :  [[0.77918673]]
"""

pred, loss = linear_forward(input, target, W, B)
"""
print('pred : ', pred)
print('loss : ', loss)
pred :  [[2.83083978]
 [4.88249283]
 [6.93414589]]
loss :  0.015586626144295427

"""

weights = np.arange(0, 5, 0.2)
loss_array = []

for weight in weights:
    _, loss = linear_forward(input, target, weight, B)
    loss_array.append(loss)

fig = plt.figure() 
ax = plt.axes(xlim=(-0, 4))
ax.plot(weights, loss_array)
ax.set_xlabel('weight')
ax.set_ylabel('loss')

redDot, = plt.plot([], [], 'ro')

def animate(frame):
    _, loss = linear_forward(input, target, frame, B)
    redDot.set_data(frame, loss)
    return redDot

ani = FuncAnimation(fig, animate, frames=train_W)

#FFwriter = animation.FFMpegWriter(fps=10)
#ani.save('animation.mp4', writer = FFwriter)
    
plt.show()

 

 

3. Pytorch로 위에 작성한 코드가 올바른지 확인해 보자. 

학습이 너무 느려 learning_rate를 0.001 -> 0.01로 조절하였다. 

 

기존 Numpy 코드에서는 기울기값을 매번 새로 정의하지만

Pytorch에서는 기울기값(grad)가 누적되므로 .grad.zero_()로 매번 초기화시켜주어야 한다.

 

이것을 주의하면서 확인해 보자.

import torch
import numpy as np

np.random.seed(220102)

input = np.array([[1.],
                  [2.],
                  [3.]])

target = np.array([[3.], 
                   [5.], 
                   [7.]])

W = np.random.randn(1, 1) # [[0.97213265]] correct value = 2
B = np.random.randn(1, 1) # [[0.28608494]] correct value = 1

input = torch.tensor(input, requires_grad = False)
target = torch.tensor(target, requires_grad = False)
W = torch.tensor(W, requires_grad=True)
B = torch.tensor(B, requires_grad=True)

learning_rate = 0.01

pred = torch.matmul(input, W) + B
"""
print('before pred : ', pred)
before pred :  tensor([[1.2582],
	 		[2.2304],
	 		[3.2025]], dtype=torch.float64, grad_fn=<AddBackward0>)
"""


for i in range(100):
    pred = torch.matmul(input, W) + B
    loss = torch.mean(torch.pow((pred - target), 2))

    loss.backward()

    W.data = W.data - learning_rate * W.grad.data
    B.data = B.data - learning_rate * B.grad.data
    
    W.grad.zero_()
    B.grad.zero_()
    
pred = torch.matmul(input, W) + B
"""
print('after pred : ', pred)
after pred :  tensor([[2.9034],
	 		[4.9793],
	 		[7.0551]], dtype=torch.float64, grad_fn=<AddBackward0>)
"""