그래디언트 디센트(Gradient decent)

1. 손실함수


손실함수(Loss funcion)란 참값과 예측값의 편차 제곱의 형태이며 식은 다음과 같다.

$$L(w) = \left( f_{true}(t) - f_w(t) \right)^2$$

여기서 $w$는 Neural Network의 parameter이다.

이 손실함수를 최소화하기 위하여 그래디언트 디센트를 이용한다.



2. 그래디언트 디센트


손실함수를 각각의 parameter로 편미분하면

$$\nabla_w L(w) = \left( {\partial L(w) \over \partial w_1}, {\partial L(w) \over \partial w_2}, {\partial L(w) \over \partial w_3}, \cdots \right)$$

위와 같이 되는데 이는 손실함수를 증가시키는 방향이다.

따라서 손실함수를 감소시키는 식을 정의하게 되는데 이를 그래디언트 디센트라 한다.

$$w' = w - \alpha \nabla_w L(w)$$


이를 예시를 들어 코딩해보자.

임의의 함수를 우리가 원하는 데이터와 일치시키는 parameter를 구하는 것이 목표이다.

$f_w(x_1, x_2) = w_1x_1 - w_2x_2 + 1$

위 함수의 초기 parameter가 $w_1 = 0.5, w_2 = 1.2$일 때 $f_2(1, 2) = 1$이 되는 parameter를 구해보자.

import numpy as np

def f_w(wx):
    w1w2 = w
    x1x2 = x
    f = w1*x1 - w2*x2 + 1
    return f

def Loss_f(wx):
    L = (1.0 - f_w(wx)) ** 2
    return L

우선 우리가 정한 함수를 정의하고 이에따른 손실함수도 정의한다.

def gradient(wx):
    dw = 1e-6
    w1 = np.array([w[0] + dww[1]])
    w2 = np.array([w[0], w[1] + dw])
    gra_w1 = (Loss_f(w1x) - Loss_f(wx))/dw
    gra_w2 = (Loss_f(w2x) - Loss_f(wx))/dw
    return np.array([gra_w1gra_w2])

편미분을 기본 도함수의 정의에 의해 근사하여 구한다.

def main():
    w = np.array([0.51.2])
    x = np.array([1.02.0])
    alpha = 0.01
    print("init : "f_w(wx))
    for i in range(50):
        w = w - alpha * gradient(wx)
        print("w : "w)
        print("update : "f_w(wx))

마지막 main 함수에서 그래디언트 디센트를 적용하여 parameter를 업데이트한다.


init :  -0.8999999999999999

w :  [0.53799999 1.12399996]   

update :  -0.709999930011628   

w :  [0.57219998 1.05559992]   

update :  -0.5389998670176523  

w :  [0.60297997 0.99403989]   

update :  -0.3850998103146366  

. . .

w :  [0.8780405  0.44391599]

update :  0.990208523498682


50회 실행한 결과는 w1 = 0.8780, w2 = 0.4439이며 함수의 결과값이 0.9902로 1에 매우 가까워진 것을 알 수 있다.

하지만 이러한 방식으로는 parameter의 개수가 많아지면 계산이 힘들어지므로 보통 Tensorflow나 Pytorch를 이용하여 자동 미분 라이브러리를 사용한다.



3. pytorch를 이용한 구현


import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

import numpy as np
import matplotlib.pyplot as plt

뉴럴 네트워크 모델을 구현해 주는 부분이다.

한 층에 128개의 노트로 구성되며 입력과 출력의 데이터는 1개씩이므로 다음과 같이 구성한다.

Model 클래스의 상위 클래스인 nn.Module를 상속받아 그 기능을 사용할 수 있도록 한다.

출력은 음의 무한대에서 양의 무한대까지 가능하도록 ReLu 함수를 사용하지 않고 그대로 출력한다.

class Model(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(1128)
        self.fc2 = nn.Linear(128128)
        self.fc3 = nn.Linear(128128)
        self.fc4 = nn.Linear(1281bias=False)

    def forward(selfx):
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = F.relu(self.fc3(x))
        x = self.fc4(x)
        return x

실제 가상의 함수를 정의하고 그 함수에 노이즈를 추가하여 임의의 데이터를 생성하는 함수이다.

def true_fun(X):
    noise = np.random.rand(X.shape[0]) * 0.4 - 0.2
    return np.cos(1.5 * np.pi * X) + X + noise

결과를 plot하는 함수이다.

Model 함수는 입출력이 tensor 형태이므로 이에 맞게 바꿔준다.

torch.from_numpy(x) 는 넘파이 배열을 텐서 형태로 바꿔주고

.float()에서 실수값 데이터로 바꿔주고

.unsqueeze(1)에서 열벡터로 바꿔준다.

마찬가지로 Model 출력을 .detach().numpy()로 넘파이 배열로 변환하여 plot한다. 

def plot_results(model):
    x = np.linspace(05100)
    input_x = torch.from_numpy(x).float().unsqueeze(1)
    plt.plot(x, true_fun(x), label="Truth")
    plt.plot(x, model(input_x).detach().numpy(), label="Prediction")
    plt.legend(loc='lower right',fontsize=15)
    plt.xlim((05))    
    plt.ylim((-15))
    plt.grid()

임의의 data 1000개를 뽑아 그 중 32개의 mini-batch를 뽑아 Gradient Decent를 적용한다.

optim.Adam의 Ir이 GD의 $\alpha$와 같은 값이다.

def main():
    data_x = np.random.rand(1000) * 5
    model = Model()
    optimizer = optim.Adam(model.parameters(), lr=0.001)

    for step in range(10000):
        batch_x = np.random.choice(data_x, 32)
        batch_x_tensor = torch.from_numpy(batch_x).float().unsqueeze(1)
        pred = model(batch_x_tensor)

        batch_y = true_fun(batch_x)
        truth = torch.from_numpy(batch_y).float().unsqueeze(1)
        loss = F.mse_loss(pred, truth)
        
        optimizer.zero_grad() 
        loss.mean().backward()
        optimizer.step()

    plot_results(model)
    plt.show()

1000번의 보정 결과이다.

5000번의 보정 결과이다.

보정 횟수를 늘릴 수록 데이터에 근접한 함수가 만들어지는것을 알 수 있다.



Comments