배경
1차원 선형 함수에 대해서 직관 + 수동적인 방법으로 선형모델의 w, b 값을 찾아보았었다
그 과정에서 손실(loss)값이 줄어드는 방향으로 진행하였고 이것은 곧 선형대수에서 말하는 함수의 편미분(기울기) 값이 0인 부분을 찾는다는 것을 뜻한다
따라서, 이번엔 경사하강법(Gradient Descent)을 사용하여 모델을 트레이닝시켜보도록 한다
경사하강법(Gradient Descent)
f(x), f^(x) 를 비교하여, 손실 경향성(기울기)을 이용, 극값(global minimum)을 찾기 위한 방법론
→ 함수의 편미분(기울기)를 이용하므로 직관적이고 적용하기 쉽다는 장점
→ 단, epoch(세대) 이 진행되는 과정에서 학습률(learning rate)을 얼마나 반영할지, 다차원 함수의 경우 극값을 찾기 어려운 것을 어떻게 고려해야할지, 영향력이 작은 피쳐들을 어떻게 판별하고 제거해나갈지 등 다양한 이슈들이 남아있다
다차원 경사하강법 추가조사
선형함수의 극점은 단 한개이기 때문에 학습하기 좋았지만 실제 함수들은 더 복잡한 형태를 가진다. 다차원 함수에 경사하강법을 적용하는 것은 훨씬 복잡한 문제로 보인다
→ 피쳐가 많을경우 초기값을 잡기도 어렵고 피팅해나가기도 어렵다는 것을 지난 실험과 이번 실험을 통해 알 수 있었다
→ 글로벌 미니멈 지점(극점)을 찾길 원하는데 이때 함수(모델)의 기울기를 활용한다는 것을 배웠다. 하지만 로컬 미니멈들의 존재 여부가 극점을 찾는데 어려움이 생긴다
→ 추가적으로, (위 레퍼런스와 관련 자료들에서)기울기에 대한 편미분값인 곡률(기울기의 기울기)을 활용하여 극점 여부를 판단, 나아가서 모델 최적화를 할 수 있다는 것처럼 말하고 있는것 같다 (내용이 어려워 느낌적으로)
→ 헤세 함수, 헤세 행렬(곡률 정보)을 통해 극점인지, 로컬 미니멈인지 판단할 수 있다는 것 같다
실습
모델, 트레이닝 추상화 적용
import numpy as np | |
from lib.models.model import Model | |
class Linear(Model): | |
def __init__(self, w: float=.0, b:float=.0) -> None: | |
self.w = w | |
self.b = b | |
def get_params(self) -> dict: | |
return {'w': self.w, 'b': self.b} | |
def set_params(self, w: float, b:float) -> None: | |
self.w = w | |
self.b = b | |
def forward(self, x:np.array) -> np.array: | |
self._x = x | |
return self.w * x + self.b | |
def backward(self, grad:np.array) -> np.array: | |
self._d_w = self._x * grad | |
self._d_b = grad | |
return self.w * grad | |
def update(self, lr:float) -> None: | |
self.w -= lr * self._d_w.mean() | |
self.b -= lr * self._d_b.mean() |
class Model(object): | |
def __init__(self) -> None: | |
raise NotImplementedError | |
def set_params(self) -> None: | |
raise NotImplementedError | |
def get_params(self) -> dict: | |
raise NotImplementedError |
import pandas as pd | |
import numpy as np | |
import plotly.express as px | |
import os | |
import sys | |
root_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir, os.pardir)) | |
lib_path = os.path.join(root_path, "lib") | |
sys.path.append(root_path) | |
sys.path.append(lib_path) | |
from lib.models.model import Model | |
from lib.models.linear import Linear | |
from lib.models.training import Training | |
class Trainer(Training): | |
def __init__(self, model: Model, history: list = []) -> None: | |
self.model = model | |
self.history = history | |
def train(self, x: np.array, y: np.array, epochs: int, lr: float) -> None: | |
if len(x) != len(y): | |
raise ValueError(f"x and y must have the same length. Got {len(x)} and {len(y)} instead.") | |
if len(self.history) > 0: | |
self.history = [] | |
# TODO (개선)동일한 데이터로만 학습하고 있다. | |
for epoch in range(epochs): | |
y_pred = self.model.forward(x) | |
loss = self.forward(y_pred, y) | |
w = self.model.get_params()['w'] | |
b = self.model.get_params()['b'] | |
print(f"Epoch: {epoch} | Loss: {loss} | w={w:.4f} | b={b:.4f}") | |
self.model.backward(self.backward()) | |
self.model.update(lr) | |
self.history.append({'w':w, 'b':b, 'loss':loss, 'epoch':epoch}) | |
def forward(self, y_hat:np.array, y_true:np.array) -> np.array: | |
self._hat = y_hat | |
self._true = y_true | |
return ((y_hat - y_true)**2).mean() | |
def backward(self) -> np.array: | |
return 2*(self._hat - self._true) | |
def history_df(self) -> pd.DataFrame: | |
df = pd.DataFrame(self.history) | |
return df | |
def simulrate(self, xs: np.array, ys: np.array, epochs: int, lr: float): | |
""" | |
prerequisite: plotly, pandas, nbformat | |
피팅 과정을 시각화 | |
""" | |
df = pd.DataFrame(self.history, columns=['w','b']) | |
df = df.set_index(df.index.set_names('epoch')).reset_index() | |
df0 = df.copy() | |
df1 = df.copy() | |
df0['x'] = xs.min() | |
df1['x'] = xs.max() | |
df = pd.concat([df0, df1]).reset_index(drop=True) | |
df['y'] = df.w * df.x + df.b | |
fig = px.line(df, x='x', y='y', animation_frame="epoch", width=500, height=500) | |
fig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 0.1 | |
fig.layout.updatemenus[0].buttons[0].args[1]['transition']['duration'] = 0.1 | |
fig.layout.updatemenus[0].buttons[0].args[1]['frame']['redraw'] = True | |
fig.add_scatter(x=xs.flatten(), y=ys.flatten(), mode='markers', name='data', marker={'size':2}) | |
for i, frame in enumerate(fig.frames): | |
frame['layout']['title_text'] = f"Prediction: y = {self.history[i]['w']:.4f}x{'' if self.history[i]['b'] < 0 else '+'}{self.history[i]['b']:.4f}" | |
fig.update_layout(template='plotly_dark') | |
fig.show() | |
if __name__ == '__main__': | |
model = Linear(w=0, b=0) | |
t = Trainer(model) | |
xs = np.random.rand(1000) | |
ys = model.forward(xs) + np.random.normal(0, 0.1, 1000) | |
t.simulrate(xs, ys, epochs=200, lr=0.1) |
import numpy as np | |
class Training(): | |
def forward(self, x:np.array) -> np.array: | |
raise NotImplementedError | |
def backward(self, grad:np.array) -> np.array: | |
raise NotImplementedError |
# %% | |
from lib.models.linear import Linear | |
f = lambda x: x*2.0 + 1.0 | |
weight = 0. | |
bias = 0. | |
lin_model = Linear(w=weight, b=bias) | |
# %% | |
import numpy as np | |
xs = np.random.rand(1000, 1) # 1000x1 | |
ys = f(xs) + 0.1*np.random.randn(1000, 1) | |
import matplotlib.pyplot as plt | |
plt.scatter(xs, ys, marker='.') | |
plt.plot(xs, f(xs), label=f"y = 2x + 1") | |
plt.legend() | |
plt.show() | |
# %% | |
from lib.models.trainer import Trainer | |
trainer = Trainer(model=lin_model) | |
trainer.train(xs, ys, epochs=200, lr=0.2) | |
# %% | |
df = trainer.history_df() | |
df | |
# %% | |
trainer.simulrate(xs, ys, epochs=200, lr=0.2) |
'머신러닝' 카테고리의 다른 글
모델 학습 및 추론 과정(논리 흐름도) (0) | 2023.11.20 |
---|