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(w, x):
w1, w2 = w
x1, x2 = x
f = w1*x1 - w2*x2 + 1
return f
def Loss_f(w, x):
L = (1.0 - f_w(w, x)) ** 2
return L
우선 우리가 정한 함수를 정의하고 이에따른 손실함수도 정의한다.
def gradient(w, x):
dw = 1e-6
w1 = np.array([w[0] + dw, w[1]])
w2 = np.array([w[0], w[1] + dw])
gra_w1 = (Loss_f(w1, x) - Loss_f(w, x))/dw
gra_w2 = (Loss_f(w2, x) - Loss_f(w, x))/dw
return np.array([gra_w1, gra_w2])
편미분을 기본 도함수의 정의에 의해 근사하여 구한다.
def main():
w = np.array([0.5, 1.2])
x = np.array([1.0, 2.0])
alpha = 0.01
print("init : ", f_w(w, x))
for i in range(50):
w = w - alpha * gradient(w, x)
print("w : ", w)
print("update : ", f_w(w, x))
마지막 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(1, 128)
self.fc2 = nn.Linear(128, 128)
self.fc3 = nn.Linear(128, 128)
self.fc4 = nn.Linear(128, 1, bias=False)
def forward(self, x):
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(0, 5, 100)
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((0, 5))
plt.ylim((-1, 5))
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
Post a Comment