Developer's Development

3.2.20 [딥러닝] 최적 모델 학습 (최적화 함수) 본문

데이터 분석과 머신러닝, 딥러닝/딥러닝

3.2.20 [딥러닝] 최적 모델 학습 (최적화 함수)

mylee 2025. 8. 6. 14:03
최적화 함수

 

딥러닝 모델이 학습하는 과정에서 가장 중요한 요소 중 하나는 최적화 함수이다.

최적화 함수는 손실 함수의 값을 최소화하도록 모델의 가중치를 업데이트하는 역할을 한다.

최적화 함수의 선택은 모델의 학습 속도, 일반화 성능, 수렴 안정성에 큰 영향을 미친다.

대표적인 최적화 함수로 확률적 경사 하강법(SGD), 모맨텀, AdaGrad, RMSprop, Adam 등이 있다.

 

  • 확률적 경사 하강법 (SGD)

경사 하강법은 손실 함수의 기울기를 따라 가중치를 갱신하는 방식이다.

하지만 전체 데이터셋을 사용하면 계산량이 너무 많아져 학습속도가 느려진다.

이를 해결하기 위해 확률적 경사하강법은 매 반복마다 무작위로 한 개의 샘플 데이터셋을 추출하여 가중치를 갱신한다.

 

  • 모맨텀

경사 하강법이 단순히 기울기를 따르는 것이 아니라, 이전 업데이트 방향성을 고려하여 관성을 갖도록 하는 기법이다.

 

  • AdaGrad

학습률을 개별 파라미터마다 다르게 적용하는 기법이다.

자주 업데이트 되는 파라미터의 학습률은 작아지고, 드물게 업데이트되는 파라미터의 학습률은 커지도록 조정한다.

AdaGrad는 초반 학습 속도가 빠르지만, 기울기의 제곱합이 계속 누적되면서 학습률이 급격히 감소하여 학습이 멈출 수도 있다.

 

  • RMSprop

AdaGrad의 단점을 개선한 방법으로 최근 기울기의 크기를 지수적으로 가중평균하여 스케일링한다.

(이전의 기울기의 이동 평균을 사용하여 학습률을 조정한다.)

 

  • Adam

모멘텀과 RMSProps를 결합한 방법이다.

1차 모멘텀(기울기의 이동평균)과 2차 모멘텀(기울기 제곱의 이동평균)을 활용해 안정적인 학습이 가능하다.

 

실습

 

import numpy as np
import matplotlib.pyplot as plt

 

  • 간단한 SGD
data_sample = np.random.uniform(-2, 2, size=10)

def loss(x):
    return x ** 2

def gradient(x):
    return 2 * x
    
def sgd(lr=0.1, epochs=10):
    w = np.random.uniform(-2, 2)
    history = [w]

    for _ in range(epochs):
        sample = np.random.choice(data_sample)
        grad = gradient(sample)
        w -= lr * grad
        history.append(w)

    return history
    
history = sgd()
x_vals = np.linspace(-2, 2, 100)

plt.plot(x_vals, loss(x_vals), label='Loss Function')
plt.plot(history, loss(np.array(history)), 'ro-', label='SGD')

plt.legend()
plt.show()

 

  • 모맨텀 추가
def sgd_momentum(lr=0.1, momentum=0.9, epochs=10):
    w = np.random.uniform(-2, 2)
    v = 0
    history = [w]

    for _ in range(epochs):
        sample = np.random.choice(data_sample)
        grad = gradient(sample)
        v = momentum * v - lr * grad
        w -= lr * grad
        history.append(w)

    return history
    
sgd_history = sgd()
momentum_history = sgd_momentum()

x_vals = np.linspace(-2, 2, 100)
plt.plot(x_vals, loss(x_vals), label='Loss Function')

plt.plot(sgd_history, loss(np.array(sgd_history)), 'ro-', label='SGD')
plt.plot(momentum_history, loss(np.array(momentum_history)), 'bo-', label='Momentum')
plt.legend()
plt.show()

 

  • 학습률에 따른 SGD
learning_rates = [0.01, 0.1, 0.5]
histories = [sgd(lr=lr) for lr in learning_rates]

plt.figure(figsize=(5, 3))
for i, history in enumerate(histories):
    plt.plot(history, label=f'lr={learning_rates[i]}')
plt.legend()
plt.show()

학습률이 클수록 기울기의 변동성도 커진다

 

  • AdaGrad
def adagrad(lr=0.1, epsilon=1e-8, epochs=10):
    w = np.random.uniform(-2, 2)
    h = 0
    history = [w]

    for _ in range(epochs):
        grad = gradient(w)
        h  += grad ** 2
        w -= (lr / (np.sqrt(h) + epsilon)) * grad
        history.append(w)

    return history
    
sgd_history = sgd()
adagrad_history = adagrad()

x_vals = np.linspace(-2, 2, 100)
plt.plot(x_vals, loss(x_vals), label='Loss Function')

plt.plot(sgd_history, loss(np.array(sgd_history)), 'ro-', label='SGD')
plt.plot(adagrad_history, loss(np.array(adagrad_history)), 'bo-', label='AdaGrad')
plt.legend()
plt.show()

AdaGrad는 최종점을 찾기도 전에 중단됨

 

  • RMSprop
def rmsprop(lr=0.1, beta=0.9, epsilon=1e-8, epochs=10):
    w = np.random.uniform(-2, 2)
    h = 0
    history = [w]

    for _ in range(epochs):
        grad = gradient(w)
        h  += (beta * h) + ((1 - beta) * grad ** 2)
        w -= (lr / (np.sqrt(h) + epsilon)) * grad
        history.append(w)

    return history
    
adagrad_history = adagrad()
rmsprop_history = rmsprop()

plt.plot(range(11), adagrad_history, 'ro-', label='AdaGrad')
plt.plot(range(11), rmsprop_history, 'bo-', label='RMSprop')
plt.legend()
plt.show()

 

  • Adam
def adam(lr=0.1, beta1=0.9, beta2=0.999, epsilon=1e-8, epochs=10):
    w = np.random.uniform(-2, 2)
    m, v = 0, 0
    history = [w]

    for t in range(1, epochs):
        grad = gradient(w)

        m = beta1 * m + (1 - beta1) * grad
        v = beta2 * v + (1 - beta2) * grad ** 2
        m_hat = m / (1 - beta1 ** t)
        v_hat = v / (1 - beta2 ** t)
        w -= (lr / (np.sqrt(v_hat) + epsilon)) * m_hat

        history.append(w)

    return history
    
sgd_history = sgd()
adagrad_history = adagrad()
adam_history = adam()

x_vals = np.linspace(-2, 2, 100)
plt.plot(x_vals, loss(x_vals), label = 'Loss Function')

plt.plot(sgd_history, loss(np.array(sgd_history)), 'ro-', label='SGD')
plt.plot(adagrad_history, loss(np.array(adagrad_history)), 'bo-', label='AdaGrad')
plt.plot(adam_history, loss(np.array(adam_history)), 'go-', label='Adam')

plt.legend()
plt.show()

이동의 차이점이 있다!

 

  • PyTorch 활용 → SGD vs Adam 비교
import torch
import torch.nn as nn
import torch.optim as optim

torch.manual_seed(42)

X = torch.rand(100, 1) * 10
y = 3 * X + 5 + (torch.randn(100, 1) * 0.3)

model_sgd = nn.Linear(1, 1)
model_adam = nn.Linear(1, 1)

criterion = nn.MSELoss()
optim_sgd = optim.SGD(model_sgd.parameters(), lr=0.01)
optim_adam = optim.Adam(model_adam.parameters(), lr=0.01)

epochs = 100
losses_sgd = []
losses_adam = []

for epoch in range(epochs):
    optim_sgd.zero_grad()
    outputs = model_sgd(X)
    loss = criterion(outputs, y)
    loss.backward()
    optim_sgd.step()
    losses_sgd.append(loss.item())

for epoch in range(epochs):
    optim_adam.zero_grad()
    outputs = model_adam(X)
    loss = criterion(outputs, y)
    loss.backward()
    optim_adam.step()
    losses_adam.append(loss.item())

plt.plot(losses_sgd, label='SGD')
plt.plot(losses_adam, label='Adam')
plt.legend()
plt.show()