SGD, Momentum, AdaGrad, RMSprop, Adam
가중치 매개변수의 최적값을 탐색하는 최적화 방법, 가중치 매개변수 초깃값, 하이퍼파라미터 설정 방법 등 신경망에서 중요한 주제.
오버피팅의 대응책인 가중치 감소와 드롭아웃 등의 정규화 방법도 간략히 설명하고 구현.
마지막으로 최근 많은 연구에서 사용하는 배치 정규화도 짧게 알아보고자 함.
목차
- SGD (확률적 경사 하강법)
- Momentum (모멘텀)
- AdaGrad
- RMSprop
- Adam
- 각각의 시각화 코드 샘플
SGD (확률적 경사 하강법)
확률적 경사 하강법이란 단순한 방법인데, 매개변수의 기울기를 구해 매개변수 값을 갱신하는 일을 할 때
단순하고 구현이 쉽지만, 문제에 따라서 비효율적일 때가 있음.
- 등고선
- x축 방향으로 길쭉한 타원형 등고선이 나타남.
- x축 방향의 기울기는 완만하고, y축 방향의 기울기는 가파르다는 것을 시각적으로 보여줌.
- SGD 경로 (붉은색 선)
- 시작점(-7, 2)에서 출발한 경로는 y축을 따라 지그재그로 크게 흔들리며 최솟값 (0, 0)으로 천천히 수렴하는 것을 볼 수 있음.
즉, SGD가 비등방성 함수에 대해서 비효율적인지 명확하게 보여줌.
y축의 방향의 가파른 기울기 때문에 y값이 크게 변동하지만 x축 방향의 완만한 기울기 때문에 x값을 천천히 움직이기 때문

비등방성 함수란?
- 방향에 따라 다른 특성을 가지는 함수로 머신러닝 분야에서는 '성질'이 기울기를 의미함.

class SGD:
def __init__(self, lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
Momentum (모멘텀)
모멘텀은 '운동량'을 뜻하는 단어로 물리와 관계가 있음.
인스턴스 변수 v가 물체의 속도로, v는 초기화 시 아무 값도 담지 않고, 대신 update()가 처음 호출될 때 매개변수와 같은 구조의 데이터를 딕셔너리 변수로 저장.
SGD와 비교하면, 지그재그 정도가 덜하지만, 즉, x축의 힘은 크지만 위아래로 번갈아 받아서 상충하여 y축 방향의 속도는 안정적이지 않음.
x축 방향으로 빠르게 다가가 지그재그 움직임이 줄어듦.

import numpy as np
import matplotlib.pyplot as plt
# Momentum 클래스 (제공된 코드 그대로 사용)
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None # velocity (속도)
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
# 속도 업데이트: 이전 속도에 관성(momentum)을 주고, 현재 기울기 방향으로 가속
self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
# 위치 업데이트: 계산된 속도만큼 이동
params[key] += self.v[key]
AdaGrad
신경망 학습에서는 학습률(n) 값이 중요함. 이 값이 너무 작으면 학습 시간이 너무 길어지고, 반대로 너무 크면 발산하여 학습이 제대로 이뤄지지 않음.
이 학습률을 정하는 효과적 기술로 학습률 감소(learning rate decay)가 있음.
이는 학습을 진행하면서, 학습률을 점차 줄여가는 방법으로, 처음에는 크게 학습하다가 조금씩 작게 학습한다는 이야기로 실제 신경망 학습에 자주 쓰임
AdaGrad 방식은 각각의 매개변수에 맞춤형 값을 만들어서 적응적으로 학습률을 조정하며 학습을 진행.
과거의 기울기를 제곱하여 계속 더하기 때문에 학습을 진행할 수록 갱신 강도가 약해짐
즉, 매개변수가 크게 갱신된 원소는 학습률이 낮아짐.
NOTE
- 학습이 진행될수록 갱신 강도가 약해져 이를 개선한 RMSProp 방법이 존재.
- 이는 먼 과거의 기울기는 서서히 잊고 새로운 기울기 정보를 크게 반영
- 이를 지수이동 평균이라고 하여 기울기 반영 규모를 기하급수적으로 감소

# AdaGrad 클래스 (제공된 코드 그대로 사용)
class AdaGrad:
def __init__(self, lr=0.01):
self.lr = lr
self.h = None # 과거 기울기 제곱의 누적합
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
# h에 현재 기울기의 제곱을 누적
self.h[key] += grads[key] * grads[key]
# 매개변수 갱신: 학습률을 루트(h + 엡실론)으로 나누어 적응적으로 조절
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
RMSProp
위에서 말한대로 학습이 진행될수록 학습률이 낮아지는 문제를 보완

# RMSprop 클래스 (제공된 코드 그대로 사용)
class RMSprop:
"""RMSprop"""
def __init__(self, lr=0.01, decay_rate = 0.99):
self.lr = lr
self.decay_rate = decay_rate
self.h = None # 과거 기울기 제곱의 EMA (지수 이동 평균)
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
# h 업데이트: 이전 h 값에 decay_rate를 곱하고, 새 기울기 정보를 (1-decay_rate)만큼 반영
self.h[key] *= self.decay_rate
self.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]
# 매개변수 갱신: 학습률을 루트(h + 엡실론)으로 나누어 적응적으로 조절
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
Adam
모멘텀은 공이 그릇 바닥을 구르는 듯한 움직임을 보이고, AdaGrad는 매개변수의 원소마다 적응적으로 갱신 정도를 조정
Adam은 이 두 기법을 융합함
하이퍼파라미터의 편향 보정이 진행된다는 것이 Adam의 특징
Adam에서는 하이퍼파리미터를 3개 설정하는데, 학습률, 일차 모멘텀용 계수, 이차 모멘텀용 계수.
일차 모멘텀용 계수와 이차 모멘텀용 계수는 각각 0.9, 0.99이며, 이 값이면 많은 경우에 좋은 결과를 얻을 수 있음.
import numpy as np
import matplotlib.pyplot as plt
# Adam 클래스 (제공된 코드 그대로 사용, 주석 처리된 부분 정리)
class Adam:
"""Adam (http://arxiv.org/abs/1412.6980v8)"""
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.iter = 0
self.m = None
self.v = None
def update(self, params, grads):
if self.m is None:
self.m, self.v = {}, {}
for key, val in params.items():
self.m[key] = np.zeros_like(val)
self.v[key] = np.zeros_like(val)
self.iter += 1
# 학습률(lr_t)에 편향 보정(bias correction) 적용
lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)
for key in params.keys():
# m(1차 모멘트 추정치) 업데이트 (EMA of gradients)
self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
# v(2차 모멘트 추정치, 제곱 기울기의 EMA) 업데이트
self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
# 파라미터 업데이트: m을 sqrt(v)로 나누어 학습률 조절 및 편향 보정된 lr_t 사용
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)

시각화 코드 샘플
1. SGD 시각화 코드
# 1. 시각화 대상 함수와 기울기 정의
def function(params):
"""함수 f(x, y) = 1/20 * x^2 + y^2"""
x, y = params['x'], params['y']
return (1/20) * x**2 + y**2
def gradient(params):
"""함수의 기울기 (x/10, 2y)"""
x, y = params['x'], params['y']
grads = {'x': x / 10, 'y': 2 * y}
return grads
# 2. 시각화를 위한 등고선 데이터 생성
x_range = np.arange(-10, 10, 0.1)
y_range = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x_range, y_range)
Z = (1/20) * X**2 + Y**2
# 3. SGD 알고리즘 실행 및 경로 저장
# 시작점, 학습률(lr), 반복 횟수를 설정합니다.
initial_params = {'x': -7.0, 'y': 2.0}
params = initial_params.copy()
optimizer = SGD(lr=0.95) # lr을 조금 높여 지그재그 움직임을 더 명확하게 표현
path = [ (params['x'], params['y']) ]
for i in range(30):
grads = gradient(params)
optimizer.update(params, grads)
path.append( (params['x'], params['y']) )
path = np.array(path)
# 4. Matplotlib을 사용하여 그래프 그리기
plt.figure(figsize=(8, 8))
# 함수 등고선(컨투어) 그리기
plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 10), cmap='viridis')
# SGD 경로 그리기
plt.plot(path[:, 0], path[:, 1], 'o-', color='red', label='SGD Path')
# 시작점과 최솟값 표시
plt.plot(initial_params['x'], initial_params['y'], 'o', color='red', markersize=10, label='Start Point')
plt.plot(0, 0, '*', color='blue', markersize=10, label='Minimum (0,0)')
plt.title('SGD on $f(x, y) = \\frac{1}{20}x^2 + y^2$')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
plt.axis('equal') # x, y축의 스케일을 동일하게 맞춰 타원의 형태가 올바르게 보이게 함
plt.show()
2. 모멘텀 시각화 코드
# 1. 시각화 대상 함수와 기울기 정의 (이전과 동일)
def function(params):
"""함수 f(x, y) = 1/20 * x^2 + y^2"""
x, y = params['x'], params['y']
return (1/20) * x**2 + y**2
def gradient(params):
"""함수의 기울기 (x/10, 2y)"""
x, y = params['x'], params['y']
grads = {'x': x / 10, 'y': 2 * y}
return grads
# 2. 시각화를 위한 등고선 데이터 생성 (이전과 동일)
x_range = np.arange(-10, 10, 0.1)
y_range = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x_range, y_range)
Z = (1/20) * X**2 + Y**2
# 3. Momentum 알고리즘 실행 및 경로 저장
# 시작점, 학습률(lr), 반복 횟수를 설정합니다.
initial_params = {'x': -7.0, 'y': 2.0}
params = initial_params.copy()
# lr을 SGD보다 약간 낮추고 momentum=0.9 설정 (일반적인 값)
optimizer_mom = Momentum(lr=0.1, momentum=0.9)
path_mom = [ (params['x'], params['y']) ]
for i in range(30):
grads = gradient(params)
optimizer_mom.update(params, grads)
path_mom.append( (params['x'], params['y']) )
path_mom = np.array(path_mom)
# 4. Matplotlib을 사용하여 그래프 그리기
plt.figure(figsize=(8, 8))
# 함수 등고선(컨투어) 그리기
plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 10), cmap='viridis')
# Momentum 경로 그리기
plt.plot(path_mom[:, 0], path_mom[:, 1], 'o-', color='green', label='Momentum Path')
# 시작점과 최솟값 표시
plt.plot(initial_params['x'], initial_params['y'], 'o', color='green', markersize=10, label='Start Point')
plt.plot(0, 0, '*', color='blue', markersize=10, label='Minimum (0,0)')
plt.title('Momentum on $f(x, y) = \\frac{1}{20}x^2 + y^2$')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
plt.axis('equal') # x, y축의 스케일을 동일하게 맞춰 타원의 형태가 올바르게 보이게 함
plt.show()
3. AdaGrad 시각화 코드
# 1. 시각화 대상 함수와 기울기 정의 (이전과 동일)
def function(params):
"""함수 f(x, y) = 1/20 * x^2 + y^2"""
x, y = params['x'], params['y']
return (1/20) * x**2 + y**2
def gradient(params):
"""함수의 기울기 (x/10, 2y)"""
x, y = params['x'], params['y']
grads = {'x': x / 10, 'y': 2 * y}
return grads
# 2. 시각화를 위한 등고선 데이터 생성 (이전과 동일)
x_range = np.arange(-10, 10, 0.1)
y_range = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x_range, y_range)
Z = (1/20) * X**2 + Y**2
# 3. AdaGrad 알고리즘 실행 및 경로 저장
initial_params = {'x': -7.0, 'y': 2.0}
params = initial_params.copy()
# AdaGrad는 적응적 학습률 덕분에 SGD/Momentum보다 초기 lr을 크게 설정할 수 있습니다.
optimizer_ada = AdaGrad(lr=1.0) # 학습률 1.0 설정
path_ada = [ (params['x'], params['y']) ]
for i in range(30):
grads = gradient(params)
optimizer_ada.update(params, grads)
path_ada.append( (params['x'], params['y']) )
path_ada = np.array(path_ada)
# 4. Matplotlib을 사용하여 그래프 그리기
plt.figure(figsize=(8, 8))
# 함수 등고선(컨투어) 그리기
plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 10), cmap='viridis')
# AdaGrad 경로 그리기
plt.plot(path_ada[:, 0], path_ada[:, 1], 'o-', color='purple', label='AdaGrad Path')
# 시작점과 최솟값 표시
plt.plot(initial_params['x'], initial_params['y'], 'o', color='purple', markersize=10, label='Start Point')
plt.plot(0, 0, '*', color='blue', markersize=10, label='Minimum (0,0)')
plt.title('AdaGrad on $f(x, y) = \\frac{1}{20}x^2 + y^2$')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
plt.axis('equal') # x, y축의 스케일을 동일하게 맞춰 타원의 형태가 올바르게 보이게 함
plt.show()
4. RMSProp 시각화 코드
# 1. 시각화 대상 함수와 기울기 정의 (이전과 동일)
def function(params):
"""함수 f(x, y) = 1/20 * x^2 + y^2"""
x, y = params['x'], params['y']
return (1/20) * x**2 + y**2
def gradient(params):
"""함수의 기울기 (x/10, 2y)"""
x, y = params['x'], params['y']
grads = {'x': x / 10, 'y': 2 * y}
return grads
# 2. 시각화를 위한 등고선 데이터 생성 (이전과 동일)
x_range = np.arange(-10, 10, 0.1)
y_range = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x_range, y_range)
Z = (1/20) * X**2 + Y**2
# 3. RMSprop 알고리즘 실행 및 경로 저장
initial_params = {'x': -7.0, 'y': 2.0}
params = initial_params.copy()
# RMSprop 설정: lr=0.1, decay_rate=0.99 (일반적인 설정)
optimizer_rms = RMSprop(lr=0.4, decay_rate=0.99) # 시각화를 위해 lr을 0.4로 높임
path_rms = [ (params['x'], params['y']) ]
for i in range(30):
grads = gradient(params)
optimizer_rms.update(params, grads)
path_rms.append( (params['x'], params['y']) )
path_rms = np.array(path_rms)
# 4. Matplotlib을 사용하여 그래프 그리기
plt.figure(figsize=(8, 8))
# 함수 등고선(컨투어) 그리기
plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 10), cmap='viridis')
# RMSprop 경로 그리기
plt.plot(path_rms[:, 0], path_rms[:, 1], 'o-', color='darkorange', label='RMSprop Path')
# 시작점과 최솟값 표시
plt.plot(initial_params['x'], initial_params['y'], 'o', color='darkorange', markersize=10, label='Start Point')
plt.plot(0, 0, '*', color='blue', markersize=10, label='Minimum (0,0)')
plt.title('RMSprop on $f(x, y) = \\frac{1}{20}x^2 + y^2$')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
plt.axis('equal')
plt.show()
5. Adam 시각화 그래프
# 1. 시각화 대상 함수와 기울기 정의 (이전과 동일)
def function(params):
"""함수 f(x, y) = 1/20 * x^2 + y^2"""
x, y = params['x'], params['y']
return (1/20) * x**2 + y**2
def gradient(params):
"""함수의 기울기 (x/10, 2y)"""
x, y = params['x'], params['y']
grads = {'x': x / 10, 'y': 2 * y}
return grads
# 2. 시각화를 위한 등고선 데이터 생성 (이전과 동일)
x_range = np.arange(-10, 10, 0.1)
y_range = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(x_range, y_range)
Z = (1/20) * X**2 + Y**2
# 3. Adam 알고리즘 실행 및 경로 저장
initial_params = {'x': -7.0, 'y': 2.0}
params = initial_params.copy()
# Adam은 기본 설정값(lr=0.001, beta1=0.9, beta2=0.999)으로도 잘 작동합니다.
optimizer_adam = Adam(lr=0.1) # 시각화를 위해 lr을 0.1로 높여 수렴 속도를 빠르게 함
path_adam = [ (params['x'], params['y']) ]
for i in range(30):
grads = gradient(params)
optimizer_adam.update(params, grads)
path_adam.append( (params['x'], params['y']) )
path_adam = np.array(path_adam)
# 4. Matplotlib을 사용하여 그래프 그리기
plt.figure(figsize=(8, 8))
# 함수 등고선(컨투어) 그리기
plt.contour(X, Y, Z, levels=np.logspace(-1, 3, 10), cmap='viridis')
# Adam 경로 그리기
plt.plot(path_adam[:, 0], path_adam[:, 1], 'o-', color='teal', label='Adam Path')
# 시작점과 최솟값 표시
plt.plot(initial_params['x'], initial_params['y'], 'o', color='teal', markersize=10, label='Start Point')
plt.plot(0, 0, '*', color='blue', markersize=10, label='Minimum (0,0)')
plt.title('Adam on $f(x, y) = \\frac{1}{20}x^2 + y^2$')
plt.xlabel('x')
plt.ylabel('y')
plt.legend()
plt.grid(True)
plt.axis('equal')
plt.show()'AI > DeepLearning' 카테고리의 다른 글
| 배치 정규화 Batch Normalization + Pytorch 실습 (0) | 2025.11.13 |
|---|---|
| 신경망 학습에서 초기 가중치 설정 (0) | 2025.11.13 |
| 활성화 함수 계층 기본 구현 정리 (0) | 2025.11.09 |
| 경사하강법 (Gradient Descent) (1) | 2025.11.09 |
| 오차역전파 (0) | 2025.11.05 |