이미지 로딩 중...
AI Generated
2025. 11. 23. · 0 Views
Gradient Descent 최적화 알고리즘 완벽 가이드
머신러닝의 핵심인 Gradient Descent 알고리즘을 초급자 눈높이에서 쉽게 설명합니다. 경사 하강법의 원리부터 실전 활용법까지, 실무에 바로 적용할 수 있는 내용을 담았습니다.
목차
- Gradient_Descent_기본_개념
- Learning_Rate의_중요성
- Batch_Gradient_Descent
- Stochastic_Gradient_Descent
- Mini_batch_Gradient_Descent
- Momentum_기법
- AdaGrad_최적화
- RMSprop_알고리즘
1. Gradient_Descent_기본_개념
시작하며
여러분이 안개가 자욱한 산 정상에 서 있다고 상상해보세요. 어둠 속에서 가장 빠르게 산 아래로 내려가려면 어떻게 해야 할까요?
눈을 감고 주변의 경사를 손으로 더듬어보면서, 가장 가파르게 내려가는 방향으로 한 걸음씩 내딛는 것이죠. 바로 이것이 Gradient Descent(경사 하강법)의 핵심 원리입니다.
머신러닝에서는 '오차'라는 산을 내려가서 '최적의 모델'이라는 골짜기를 찾아가는 과정인 거예요. 모든 머신러닝 모델은 학습할 때 이 알고리즘을 사용합니다.
선형 회귀부터 복잡한 딥러닝 신경망까지, 모두 이 원리로 작동합니다. 한 번만 제대로 이해하면 머신러닝의 90%를 이해한 것이나 마찬가지예요.
개요
간단히 말해서, Gradient Descent는 함수의 최솟값을 찾기 위해 기울기(gradient)의 반대 방향으로 조금씩 이동하는 최적화 알고리즘입니다. 머신러닝 모델을 학습시킬 때, 우리는 '손실 함수(Loss Function)'라는 것을 최소화해야 합니다.
손실 함수는 모델의 예측값과 실제값의 차이를 나타내죠. 예를 들어, 집값을 예측하는 모델이 있다면, 예측한 집값과 실제 집값의 차이가 바로 손실입니다.
이 손실을 최소화하는 것이 우리의 목표예요. 기존에는 수학적으로 미분을 해서 직접 최솟값을 구했다면, 복잡한 실제 문제에서는 그게 불가능합니다.
변수가 수백만 개인 딥러닝 모델에서 손으로 계산할 수는 없으니까요. Gradient Descent의 핵심 특징은 세 가지입니다.
첫째, 반복적으로 조금씩 개선한다는 점(한 번에 완벽한 답을 찾지 않음). 둘째, 현재 위치에서의 기울기만 보고 다음 방향을 결정한다는 점(전체 지형을 몰라도 됨).
셋째, 학습률(learning rate)이라는 보폭 크기를 조절할 수 있다는 점입니다. 이러한 특징들 덕분에 아무리 복잡한 문제라도 점진적으로 해결할 수 있습니다.
코드 예제
import numpy as np
# 손실 함수: f(x) = x^2 (최솟값은 x=0일 때)
def loss_function(x):
return x ** 2
# 기울기 계산: f'(x) = 2x
def gradient(x):
return 2 * x
# Gradient Descent 구현
x = 10.0 # 시작점
learning_rate = 0.1 # 학습률 (보폭)
iterations = 50 # 반복 횟수
for i in range(iterations):
grad = gradient(x) # 현재 위치의 기울기 계산
x = x - learning_rate * grad # 기울기 반대 방향으로 이동
if i % 10 == 0:
print(f"Step {i}: x = {x:.4f}, loss = {loss_function(x):.4f}")
print(f"최종 결과: x = {x:.4f}")
설명
이것이 하는 일: 위 코드는 간단한 2차 함수 f(x) = x²의 최솟값을 Gradient Descent로 찾아가는 과정을 보여줍니다. x=10에서 시작해서 점점 x=0(최솟값)으로 다가가는 모습을 확인할 수 있어요.
첫 번째로, 시작점 x=10을 정하고 학습률 0.1을 설정합니다. 학습률은 '한 번에 얼마나 크게 이동할지'를 결정하는 중요한 값입니다.
너무 크면 최솟값을 지나쳐버리고, 너무 작으면 학습이 너무 느려집니다. 그 다음으로, 반복문이 실행되면서 현재 위치에서의 기울기를 계산합니다.
f'(x) = 2x이므로, x=10일 때 기울기는 20입니다. 기울기가 양수라는 것은 '오른쪽으로 올라가는 경사'를 의미하므로, 왼쪽(음의 방향)으로 이동해야 합니다.
그래서 x = x - learning_rate * grad 공식을 사용하는 거예요. 마지막으로, 이 과정을 50번 반복하면서 x는 10 → 8 → 6.4 → 5.12...
이런 식으로 점점 0에 가까워집니다. 손실값도 100 → 64 → 40.96...
이렇게 줄어들죠. 10번마다 결과를 출력해서 학습이 잘 되고 있는지 확인할 수 있습니다.
여러분이 이 코드를 사용하면 복잡한 수식 없이도 최적화 문제를 해결할 수 있습니다. 실제 머신러닝에서는 이 원리가 그대로 적용되되, x가 수백만 개의 파라미터로 확장됩니다.
선형 회귀, 로지스틱 회귀, 신경망 등 거의 모든 모델이 이 방식으로 학습됩니다. 한 번에 완벽한 답을 구하는 게 아니라 조금씩 개선해나간다는 점이 핵심입니다.
실전 팁
💡 학습 과정을 시각화하면 이해가 훨씬 쉽습니다. matplotlib으로 x값의 변화와 손실값의 감소를 그래프로 그려보세요. 손실이 계단식으로 줄어드는 모습을 보면 알고리즘이 작동하는 게 눈에 보입니다.
💡 학습이 잘 안 될 때는 learning_rate를 조절해보세요. 손실이 발산하면(계속 커지면) learning_rate를 0.01이나 0.001로 줄이고, 너무 느리면 0.5나 1.0으로 키워보세요.
💡 실무에서는 손실이 거의 변하지 않을 때 학습을 멈춥니다. if abs(prev_loss - current_loss) < 0.0001: break 같은 조건을 추가하면 불필요한 계산을 줄일 수 있어요.
💡 기울기가 0에 가까워지면 학습 속도가 느려집니다. 이게 정상입니다. 최솟값 근처에서는 천천히 접근하는 게 오히려 안정적이에요.
💡 여러 시작점에서 실험해보세요. x=-10, x=0, x=100 등 다양한 값에서 시작해도 모두 x=0으로 수렴하는 걸 확인하면 알고리즘에 대한 신뢰가 생깁니다.
2. Learning_Rate의_중요성
시작하며
여러분이 계단을 내려간다고 생각해보세요. 한 번에 한 칸씩 내려가면 안전하지만 느리고, 세 칸씩 뛰어내려가면 빠르지만 넘어질 위험이 있습니다.
너무 큰 보폭은 목적지를 지나쳐버릴 수 있고, 너무 작은 보폭은 목적지에 도착하는 데 평생이 걸릴 수도 있죠. 머신러닝에서 Learning Rate(학습률)가 바로 이 '보폭'입니다.
같은 모델, 같은 데이터라도 학습률 하나만 잘못 설정하면 학습이 완전히 실패할 수 있습니다. 실제로 많은 초보자들이 모델이 학습되지 않는다고 고민하는데, 알고 보면 학습률 문제인 경우가 많아요.
적절한 학습률을 찾는 것은 마치 골디락스의 죽 온도 찾기와 같습니다. 너무 뜨겁지도, 너무 차갑지도 않은 딱 적당한 온도를 찾아야 하는 거죠.
개요
간단히 말해서, Learning Rate는 Gradient Descent에서 한 번의 업데이트마다 파라미터를 얼마나 크게 변경할지를 결정하는 하이퍼파라미터입니다. 학습률이 중요한 이유는 크게 두 가지입니다.
첫째, 학습률이 너무 크면 손실 함수가 발산합니다. 최솟값을 찾아가는 게 아니라 오히려 멀어지는 거예요.
계단을 너무 크게 뛰어내려가다가 바닥을 뚫고 지하로 떨어지는 것과 같습니다. 둘째, 학습률이 너무 작으면 학습 속도가 극도로 느려집니다.
1시간이면 끝날 학습이 100시간 걸리게 되죠. 실무에서는 시간이 곧 돈이기 때문에 이것도 큰 문제입니다.
기존에는 학습률을 고정값으로 사용했다면, 최근에는 학습 과정에서 동적으로 조절하는 기법들이 많이 사용됩니다. Learning Rate Scheduling, Adaptive Learning Rate 등이 그 예죠.
학습률의 핵심 특징은 세 가지입니다. 첫째, 모델과 데이터에 따라 최적값이 다릅니다(보통 0.001~0.1 사이).
둘째, 초기에는 크게, 나중에는 작게 하는 게 효율적입니다(빠르게 접근 후 정밀하게 조정). 셋째, 잘못 설정하면 학습 자체가 불가능합니다.
이것이 바로 학습률이 가장 중요한 하이퍼파라미터로 불리는 이유입니다.
코드 예제
import numpy as np
import matplotlib.pyplot as plt
def loss_function(x):
return x ** 2
def gradient(x):
return 2 * x
# 세 가지 다른 학습률로 실험
learning_rates = [0.01, 0.1, 0.9] # 작음, 적절, 큼
colors = ['blue', 'green', 'red']
x_start = 10.0
for lr, color in zip(learning_rates, colors):
x = x_start
x_history = [x]
for i in range(50):
grad = gradient(x)
x = x - lr * grad # 학습률에 따라 이동 거리가 달라짐
x_history.append(x)
plt.plot(x_history, label=f'LR={lr}', color=color)
print(f"학습률 {lr}: 최종 x = {x:.4f}")
plt.xlabel('Iteration')
plt.ylabel('x value')
plt.legend()
plt.title('Learning Rate에 따른 수렴 속도 비교')
설명
이것이 하는 일: 위 코드는 동일한 문제를 세 가지 다른 학습률(0.01, 0.1, 0.9)로 해결하면서 그 차이를 시각적으로 보여줍니다. 학습률에 따라 수렴 속도와 안정성이 어떻게 달라지는지 직접 확인할 수 있어요.
첫 번째로, 세 가지 학습률을 리스트로 준비합니다. 0.01은 '작은' 학습률, 0.1은 '적절한' 학습률, 0.9는 '큰' 학습률을 대표합니다.
각각을 다른 색상으로 표시해서 그래프에서 쉽게 구분할 수 있게 했습니다. 그 다음으로, 각 학습률에 대해 동일한 시작점(x=10)에서 Gradient Descent를 실행합니다.
x = x - lr * grad 부분에서 lr 값에 따라 이동 거리가 달라지죠. lr=0.01이면 기울기의 1%만큼, lr=0.9면 90%만큼 이동합니다.
x_history 리스트에 매 단계의 x값을 저장해서 나중에 그래프로 그립니다. 마지막으로, 결과를 분석해보면 패턴이 명확합니다.
lr=0.01(파란색)은 아주 천천히 0으로 다가갑니다. 50번 반복해도 여전히 0에서 멀리 떨어져 있을 거예요.
lr=0.1(초록색)은 빠르고 안정적으로 수렴합니다. 10~20번 정도면 거의 0에 도달하죠.
lr=0.9(빨간색)는 진동하면서 수렴합니다. 0 주변을 왔다갔다하다가 결국 도달하는데, 학습률이 더 컸다면(예: 1.5) 아예 발산했을 겁니다.
여러분이 이 코드를 실행하면 학습률의 영향을 눈으로 확인할 수 있습니다. 실제 프로젝트에서 모델이 학습되지 않으면 가장 먼저 학습률을 의심해보세요.
TensorFlow나 PyTorch에서는 보통 0.001(1e-3)을 기본값으로 시작합니다. 그래프가 지그재그로 진동하면 학습률을 1/10로 줄이고, 손실이 거의 안 줄어들면 10배로 키워보는 식으로 튜닝합니다.
Learning Rate Finder 같은 도구를 사용하면 자동으로 최적 학습률을 찾을 수도 있어요.
실전 팁
💡 실무에서는 0.001부터 시작하세요. Adam 옵티마이저의 기본값이고, 대부분의 경우 안정적으로 작동합니다. 그 다음 필요에 따라 0.0001이나 0.01로 조정하면 됩니다.
💡 학습 초기 몇 번의 iteration에서 손실이 NaN(Not a Number)이 나오면 학습률이 너무 큰 겁니다. 즉시 1/10로 줄이세요.
💡 학습 곡선(loss curve)을 항상 모니터링하세요. TensorBoard나 wandb를 사용하면 실시간으로 볼 수 있습니다. 손실이 계단식으로 부드럽게 내려가면 적절한 학습률입니다.
💡 Learning Rate Scheduling을 활용하세요. 처음에는 0.1로 시작해서 학습이 정체되면 0.01, 0.001로 줄이는 방식(Step Decay)이 효과적입니다. Keras의 ReduceLROnPlateau 콜백이 자동으로 해줍니다.
💡 다양한 학습률을 한 번에 시도해보고 싶다면 Grid Search나 Random Search를 사용하세요. [0.0001, 0.001, 0.01, 0.1] 같은 후보 중에서 검증 손실이 가장 낮은 것을 선택하면 됩니다.
3. Batch_Gradient_Descent
시작하며
여러분이 설문조사를 한다고 상상해보세요. 전국 5천만 명의 의견을 파악하려면 어떻게 해야 할까요?
한 가지 방법은 5천만 명 모두에게 물어본 다음 평균을 내는 것입니다. 정확하지만 시간이 엄청 오래 걸리겠죠.
머신러닝에서 Batch Gradient Descent가 바로 이 방식입니다. 전체 데이터를 한 번에 다 보고 가장 정확한 방향으로 한 걸음 나아갑니다.
정확도는 최고지만, 데이터가 크면 속도가 느리다는 단점이 있어요. 초창기 머신러닝에서는 데이터가 작았기 때문에 이 방법이 표준이었습니다.
하지만 요즘처럼 수백만 개의 이미지나 텍스트를 다루는 시대에는 다른 대안들이 더 많이 쓰입니다.
개요
간단히 말해서, Batch Gradient Descent는 전체 훈련 데이터셋을 한 번에 사용하여 기울기를 계산하고 파라미터를 업데이트하는 방식입니다. 이 방법이 필요한 이유는 안정성과 정확성 때문입니다.
전체 데이터의 평균 기울기를 사용하기 때문에 노이즈가 적고, 최솟값을 향해 일관되게 나아갑니다. 예를 들어, 1000개의 데이터 포인트가 있다면 1000개를 모두 고려해서 '가장 평균적으로 좋은' 방향을 선택하는 거예요.
이렇게 하면 특정 이상치(outlier) 하나 때문에 잘못된 방향으로 가는 일이 없습니다. 기존에는 온라인 학습(한 번에 한 개씩)을 많이 사용했다면, Batch Gradient Descent는 오프라인 학습에 적합합니다.
전체 데이터가 메모리에 들어갈 수 있고, 높은 정확도가 필요한 경우에 사용되죠. 핵심 특징은 세 가지입니다.
첫째, 수렴이 안정적입니다(지그재그 없이 직선으로). 둘째, 전역 최솟값을 찾을 가능성이 높습니다(볼록 함수의 경우).
셋째, 계산 비용이 큽니다(데이터가 크면 한 번 업데이트에 몇 분씩 걸릴 수도). 이러한 특성 때문에 작은 데이터셋이나 정확도가 매우 중요한 과학 연구에서 주로 사용됩니다.
코드 예제
import numpy as np
# 간단한 선형 회귀 문제: y = 2x + 3
np.random.seed(42)
X = np.random.rand(100, 1) * 10 # 100개의 데이터 포인트
y = 2 * X + 3 + np.random.randn(100, 1) * 0.5 # 약간의 노이즈 추가
# 파라미터 초기화
w = 0.0 # 가중치 (기울기)
b = 0.0 # 편향 (절편)
learning_rate = 0.01
iterations = 100
for i in range(iterations):
# 전체 데이터에 대한 예측
y_pred = w * X + b
# 전체 데이터의 평균 기울기 계산 (핵심!)
dw = -(2/len(X)) * np.sum(X * (y - y_pred)) # w에 대한 기울기
db = -(2/len(X)) * np.sum(y - y_pred) # b에 대한 기울기
# 파라미터 업데이트
w = w - learning_rate * dw
b = b - learning_rate * db
# 10번마다 손실 출력
if i % 10 == 0:
loss = np.mean((y - y_pred) ** 2)
print(f"Iteration {i}: w={w:.4f}, b={b:.4f}, Loss={loss:.4f}")
print(f"최종 결과: y = {w:.4f}x + {b:.4f}")
설명
이것이 하는 일: 위 코드는 선형 회귀 문제를 Batch Gradient Descent로 해결합니다. y = 2x + 3이라는 실제 관계를 100개의 노이즈가 있는 데이터로부터 학습해서 찾아내는 거예요.
첫 번째로, 데이터를 생성합니다. X는 0~10 사이의 랜덤한 100개 값, y는 2*X+3에 약간의 노이즈를 더한 값입니다.
실제 세상의 데이터는 완벽하지 않으니까 노이즈를 추가했어요. 파라미터 w(가중치)와 b(편향)를 0으로 초기화하고, 학습을 통해 실제 값인 2와 3에 가까워지도록 만들 겁니다.
그 다음으로, 반복문 안에서 핵심 작업이 일어납니다. y_pred = w * X + b로 현재 파라미터로 전체 100개 데이터의 예측값을 계산합니다.
그리고 중요한 부분인 기울기 계산에서 np.sum()을 사용해서 100개 데이터 포인트 모두의 오차를 합산합니다. 2/len(X)는 평균을 내기 위한 것이고요.
이게 바로 'Batch'의 의미입니다 - 전체를 한꺼번에 처리하는 거죠. 마지막으로, 계산된 기울기로 파라미터를 업데이트합니다.
100번 반복하면서 w는 점점 2.0에, b는 3.0에 가까워집니다. 실제로 실행해보면 최종 결과가 w≈1.98, b≈3.05 같은 식으로 나올 거예요.
완벽하게 2.0과 3.0이 안 되는 이유는 데이터에 노이즈가 있기 때문입니다. 여러분이 이 코드를 사용하면 선형 회귀뿐 아니라 로지스틱 회귀, 간단한 신경망 등 다양한 모델에 적용할 수 있습니다.
실무에서는 scikit-learn이나 TensorFlow가 이 과정을 자동화해주지만, 원리를 이해하면 하이퍼파라미터 튜닝이나 디버깅이 훨씬 쉬워집니다. 데이터가 10만 개 이상이면 이 방식은 너무 느리니 Mini-batch나 SGD를 고려하세요.
하지만 데이터가 1만 개 미만이고 정확도가 중요하다면 Batch Gradient Descent가 여전히 좋은 선택입니다.
실전 팁
💡 메모리 문제가 생기면 데이터를 작게 샘플링해서 먼저 테스트하세요. 1000개로 잘 작동하는지 확인한 후 전체 데이터로 확장하면 시행착오를 줄일 수 있습니다.
💡 벡터화(vectorization)를 최대한 활용하세요. NumPy의 브로드캐스팅과 행렬 연산을 쓰면 for 루프보다 100배 이상 빠릅니다. 위 코드처럼 np.sum(X * (y - y_pred))가 루프보다 훨씬 효율적입니다.
💡 수렴 판정 조건을 추가하세요. if abs(prev_loss - loss) < 1e-6: break처럼 손실 변화가 미미하면 조기 종료하면 불필요한 계산을 막을 수 있습니다.
💡 GPU를 활용할 수 있다면 큰 배치에서도 빠릅니다. CuPy나 TensorFlow를 쓰면 NumPy 코드를 거의 그대로 GPU에서 실행할 수 있어요.
💡 정규화(Normalization)를 꼭 하세요. X의 값이 0~1 범위에 있으면 학습이 훨씬 안정적입니다. X = (X - X.mean()) / X.std() 한 줄이면 충분합니다.
4. Stochastic_Gradient_Descent
시작하며
여러분이 레스토랑 리뷰를 분석한다고 상상해보세요. 100만 개의 리뷰를 모두 읽고 나서 메뉴를 개선할지, 아니면 리뷰 하나를 읽을 때마다 즉시 개선해나갈지 선택할 수 있습니다.
후자가 훨씬 빠르게 반응할 수 있겠죠? Stochastic Gradient Descent(확률적 경사 하강법, SGD)가 바로 이 방식입니다.
한 번에 데이터 하나씩만 보고 파라미터를 업데이트합니다. Batch 방식보다 훨씬 빠르고, 큰 데이터셋에도 잘 작동해요.
온라인 광고, 추천 시스템, 실시간 주식 예측 등 빠른 업데이트가 필요한 분야에서 특히 유용합니다. 완벽한 정확도보다 빠른 학습이 더 중요할 때 진가를 발휘하죠.
개요
간단히 말해서, SGD는 전체 데이터셋 대신 무작위로 선택한 하나의 데이터 포인트만 사용해서 기울기를 계산하고 파라미터를 업데이트하는 방식입니다. SGD가 필요한 이유는 속도와 확장성입니다.
데이터가 100만 개라면 Batch Gradient Descent는 한 번 업데이트에 100만 개를 모두 계산해야 하지만, SGD는 1개만 보면 됩니다. 100만 배 빠른 셈이죠.
예를 들어, Netflix의 영화 추천 시스템처럼 데이터가 계속 들어오는 환경에서는 SGD가 거의 유일한 선택입니다. 기존의 Batch 방식이 '완벽한 방향을 찾아서 한 걸음'이라면, SGD는 '대략적인 방향으로 빠르게 여러 걸음'입니다.
놀랍게도 평균적으로는 같은 곳에 도착하는데 시간은 훨씬 덜 걸립니다. SGD의 핵심 특징은 세 가지입니다.
첫째, 업데이트가 노이즈가 많습니다(각 데이터가 다르니까 방향이 왔다갔다). 둘째, 지역 최솟값(local minima)을 탈출하기 쉽습니다(노이즈가 오히려 도움).
셋째, 온라인 학습이 가능합니다(데이터가 실시간으로 들어와도 됨). 이러한 특성 때문에 딥러닝의 표준 알고리즘으로 자리 잡았습니다.
코드 예제
import numpy as np
# 동일한 선형 회귀 문제
np.random.seed(42)
X = np.random.rand(100, 1) * 10
y = 2 * X + 3 + np.random.randn(100, 1) * 0.5
w = 0.0
b = 0.0
learning_rate = 0.01
epochs = 10 # 전체 데이터를 몇 번 볼지
for epoch in range(epochs):
# 데이터 순서를 매번 섞음 (중요!)
indices = np.random.permutation(len(X))
for i in indices:
# 단 하나의 데이터만 사용 (핵심!)
x_i = X[i]
y_i = y[i]
# 이 한 개 데이터에 대한 예측과 기울기
y_pred = w * x_i + b
dw = -2 * x_i * (y_i - y_pred)
db = -2 * (y_i - y_pred)
# 즉시 업데이트
w = w - learning_rate * dw
b = b - learning_rate * db
# 에포크마다 전체 손실 계산 (확인용)
y_pred_all = w * X + b
loss = np.mean((y - y_pred_all) ** 2)
print(f"Epoch {epoch}: w={w:.4f}, b={b:.4f}, Loss={loss:.4f}")
print(f"최종 결과: y = {w:.4f}x + {b:.4f}")
설명
이것이 하는 일: 위 코드는 Batch Gradient Descent와 같은 문제를 SGD로 해결합니다. 차이점은 100개 데이터를 한꺼번에 보는 게 아니라 하나씩 보면서 100번 업데이트한다는 점이에요.
첫 번째로, 에포크(epoch) 개념을 도입합니다. 1 에포크는 '전체 데이터를 한 번 다 본 것'을 의미합니다.
10 에포크면 100개 데이터를 10번씩 보는 거죠. 총 업데이트 횟수는 100 × 10 = 1000번입니다.
Batch 방식이 100번 업데이트하는 동안 SGD는 1000번 업데이트하니 훨씬 빠르게 수렴할 수 있어요. 그 다음으로, 매 에포크마다 np.random.permutation()으로 데이터 순서를 섞습니다.
이게 정말 중요한데, 순서대로 학습하면 패턴에 편향될 수 있거든요. 예를 들어 데이터가 정렬되어 있으면 초반에는 작은 값만, 후반에는 큰 값만 보게 되어 학습이 불안정해집니다.
무작위로 섞으면 이런 문제를 방지할 수 있어요. 마지막으로, 핵심 차이인 단일 데이터 처리 부분을 봅시다.
x_i, y_i 하나에 대해서만 기울기를 계산하고 즉시 업데이트합니다. 평균을 낼 필요도 없고(데이터가 1개니까), np.sum()도 필요 없습니다.
그냥 그 데이터가 시키는 대로 파라미터를 조금 움직이는 거예요. 100개 데이터가 각각 조금씩 다른 방향을 가리키지만, 평균적으로는 올바른 방향으로 가게 됩니다.
여러분이 이 코드를 실행하면 손실 곡선이 Batch 방식보다 덜 부드럽게 내려가는 걸 볼 수 있습니다. 들쭉날쭉하지만 전반적인 추세는 하락하죠.
실무에서는 데이터가 메모리에 안 들어갈 정도로 클 때 SGD를 씁니다. TensorFlow의 fit() 메서드는 기본적으로 Mini-batch SGD를 사용하지만, batch_size=1로 설정하면 순수 SGD가 됩니다.
온라인 학습이 필요한 상황(예: 사용자 행동 실시간 예측)에서는 SGD만이 유일한 답입니다.
실전 팁
💡 학습률을 Batch 방식보다 작게 설정하세요. 노이즈가 많아서 0.001 정도가 적당합니다. 0.01로 하면 너무 튀어서 수렴이 안 될 수 있어요.
💡 Learning Rate Decay를 꼭 사용하세요. 초기에는 0.01로 빠르게 접근하고, 후반에는 0.0001로 정밀하게 조정하는 식입니다. lr = initial_lr / (1 + decay_rate * epoch) 공식을 쓰면 됩니다.
💡 데이터를 매 에포크마다 섞는 걸 절대 잊지 마세요. shuffle=True 옵션을 항상 확인하세요. 안 섞으면 학습이 제대로 안 됩니다.
💡 Momentum이나 Adam 같은 개선된 옵티마이저를 고려하세요. 순수 SGD보다 훨씬 안정적으로 학습됩니다. 실무에서는 거의 항상 Adam을 씁니다.
💡 학습 곡선이 지그재그로 심하게 튄다면 정상입니다. 하지만 전혀 수렴하지 않으면 학습률을 1/10로 줄이거나 Mini-batch를 고려하세요.
5. Mini_batch_Gradient_Descent
시작하며
여러분이 설문조사를 다시 생각해보세요. 5천만 명 모두에게 물어보는 건 너무 느리고(Batch), 1명씩만 물어보는 건 너무 불안정합니다(SGD).
그렇다면 중간은 어떨까요? 매번 100명씩 무작위로 뽑아서 물어보는 겁니다.
Mini-batch Gradient Descent가 바로 이 절충안입니다. 전체 데이터를 작은 묶음(배치)으로 나누고, 각 배치로 파라미터를 업데이트합니다.
현대 딥러닝에서 가장 널리 쓰이는 방식이에요. PyTorch, TensorFlow, Keras 모두 기본적으로 이 방식을 사용합니다.
Batch의 안정성과 SGD의 속도를 동시에 얻을 수 있어서 실무에서는 사실상 표준이 되었죠.
개요
간단히 말해서, Mini-batch Gradient Descent는 전체 데이터를 작은 배치(보통 32, 64, 128개)로 나누고, 각 배치에 대해 기울기를 계산하여 파라미터를 업데이트하는 방식입니다. 이 방법이 중요한 이유는 실용성과 성능의 균형입니다.
배치 크기가 32라면 32개 데이터의 평균 기울기를 사용하므로 SGD보다 안정적이고, 전체 데이터를 쓰는 것보다 훨씬 빠릅니다. 예를 들어, ImageNet처럼 100만 개의 이미지를 학습할 때 배치 크기 256이면 약 4000번의 업데이트로 1 에포크를 완료할 수 있습니다.
Batch 방식이라면 1번, SGD라면 100만 번이었을 거예요. 기존의 SGD가 '개인의 의견'이라면, Mini-batch는 '소규모 표본 조사'입니다.
통계학에서 전체 모집단 대신 표본을 쓰는 것처럼, 잘 뽑은 작은 배치도 전체를 충분히 대표할 수 있습니다. 핵심 특징은 세 가지입니다.
첫째, GPU를 효율적으로 활용할 수 있습니다(병렬 처리에 최적). 둘째, 배치 크기로 안정성과 속도를 조절할 수 있습니다(하이퍼파라미터).
셋째, 메모리와 성능의 균형을 맞출 수 있습니다(너무 큰 배치는 메모리 부족). 이것이 딥러닝 혁명을 가능하게 한 핵심 기술 중 하나입니다.
코드 예제
import numpy as np
# 동일한 데이터
np.random.seed(42)
X = np.random.rand(100, 1) * 10
y = 2 * X + 3 + np.random.randn(100, 1) * 0.5
w = 0.0
b = 0.0
learning_rate = 0.01
batch_size = 16 # 배치 크기 (핵심!)
epochs = 20
for epoch in range(epochs):
# 데이터 섞기
indices = np.random.permutation(len(X))
# 배치 단위로 처리
for start_idx in range(0, len(X), batch_size):
# 배치 추출 (핵심!)
batch_indices = indices[start_idx:start_idx + batch_size]
X_batch = X[batch_indices]
y_batch = y[batch_indices]
# 배치에 대한 예측과 기울기
y_pred = w * X_batch + b
dw = -(2/len(X_batch)) * np.sum(X_batch * (y_batch - y_pred))
db = -(2/len(X_batch)) * np.sum(y_batch - y_pred)
# 업데이트
w = w - learning_rate * dw
b = b - learning_rate * db
# 에포크마다 손실 계산
if epoch % 5 == 0:
y_pred_all = w * X + b
loss = np.mean((y - y_pred_all) ** 2)
print(f"Epoch {epoch}: w={w:.4f}, b={b:.4f}, Loss={loss:.4f}")
print(f"최종 결과: y = {w:.4f}x + {b:.4f}")
print(f"총 업데이트 횟수: {epochs * (len(X) // batch_size)}")
설명
이것이 하는 일: 위 코드는 동일한 선형 회귀 문제를 배치 크기 16으로 해결합니다. 100개 데이터를 16개씩 묶어서 처리하므로 에포크당 약 6~7번 업데이트가 일어납니다.
첫 번째로, batch_size를 16으로 설정합니다. 이 숫자가 핵심 하이퍼파라미터인데, 너무 작으면(1~4) SGD처럼 불안정하고, 너무 크면(512 이상) Batch처럼 느려집니다.
일반적으로 32, 64, 128이 많이 쓰이고, GPU 메모리가 허용하는 한 크게 하는 게 좋습니다. 2의 거듭제곱(16, 32, 64...)을 쓰는 이유는 GPU 하드웨어 최적화 때문입니다.
그 다음으로, range(0, len(X), batch_size)로 배치를 순회합니다. start_idx가 0, 16, 32, 48...
이런 식으로 증가하면서 매번 16개씩 묶음을 만들죠. batch_indices = indices[start_idx:start_idx + batch_size]로 현재 배치의 인덱스를 가져오고, 이를 사용해 X_batch와 y_batch를 추출합니다.
이 부분이 Mini-batch의 핵심입니다. 마지막으로, 추출된 배치로 기울기를 계산하고 업데이트합니다.
코드가 Batch 방식과 거의 똑같지만, 차이점은 전체 X, y 대신 X_batch, y_batch를 쓴다는 것뿐입니다. 2/len(X_batch)로 배치 크기만큼 평균을 내고요.
20 에포크 동안 약 120~140번 업데이트가 일어나는데, Batch(20번)보다는 많고 SGD(2000번)보다는 적습니다. 여러분이 이 코드를 실행하면 손실이 SGD보다 부드럽게, Batch보다 빠르게 수렴하는 걸 볼 수 있습니다.
실무에서 PyTorch DataLoader를 쓸 때 batch_size=32처럼 지정하는 게 바로 이겁니다. GPU가 있다면 배치를 병렬로 처리해서 속도가 훨씬 빠릅니다.
배치 크기를 조정하면서 학습 속도와 안정성의 균형점을 찾는 게 중요해요. 메모리가 부족하면 배치 크기를 줄이고, 학습이 불안정하면 배치 크기를 키우세요.
실전 팁
💡 배치 크기는 32부터 시작하세요. 대부분의 경우 잘 작동하고, GPU 메모리도 적당히 사용합니다. 그 다음 64, 128로 실험해보세요.
💡 Batch Normalization을 사용한다면 배치 크기가 너무 작으면(2~4) 통계가 불안정합니다. 최소 16 이상은 유지하세요.
💡 큰 배치로 학습할 때는 학습률을 비례해서 키우세요. 배치가 2배면 학습률도 2배로 하는 'Linear Scaling Rule'이 효과적입니다. 배치 256이면 학습률 0.1, 배치 32면 0.0125 식으로요.
💡 마지막 배치는 크기가 작을 수 있습니다(100개를 16으로 나누면 마지막은 4개). drop_last=True 옵션으로 버릴 수도 있지만, 보통은 그냥 사용해도 문제없습니다.
💡 학습과 검증 데이터에서 같은 배치 크기를 쓸 필요는 없습니다. 검증 시에는 업데이트를 안 하니까 배치를 크게 해도 되고(256~512), 속도가 빨라집니다.
6. Momentum_기법
시작하며
여러분이 볼링공을 굴린다고 상상해보세요. 공은 처음에는 느리지만 점점 속도가 붙습니다.
약간의 턱이 있어도 관성 때문에 넘어가죠. 반대로 탁구공은 가볍고 빨라도 작은 장애물에 쉽게 막힙니다.
Gradient Descent도 마찬가지입니다. 순수 SGD는 탁구공처럼 이리저리 튀면서 지역 최솟값에 쉽게 갇힙니다.
Momentum은 볼링공처럼 '관성'을 더해서 안정적이고 빠르게 최솟값으로 나아가게 합니다. 딥러닝 초기에는 순수 SGD를 썼지만, 학습이 너무 불안정하고 느렸습니다.
Momentum이 도입되면서 신경망 학습이 획기적으로 개선되었고, 지금도 Adam 같은 최신 옵티마이저의 핵심 요소로 쓰이고 있어요.
개요
간단히 말해서, Momentum은 이전 업데이트 방향을 일정 비율로 유지하면서 현재 기울기를 더해주는 기법입니다. 마치 물리학의 관성처럼 작동하죠.
Momentum이 필요한 이유는 수렴 속도와 안정성입니다. 일반 SGD는 매번 새로운 기울기만 보기 때문에 방향이 왔다갔다합니다.
하지만 Momentum을 쓰면 '평균적인 방향'으로 꾸준히 나아갑니다. 예를 들어, 계곡 사이의 좁은 골짜기를 내려갈 때 일반 SGD는 양쪽 벽을 왔다갔다 부딪히지만, Momentum은 중앙으로 부드럽게 내려갑니다.
기존의 순수 기울기만 사용하는 방식이 '현재 상태만 보는 근시안적 접근'이라면, Momentum은 '과거의 흐름을 고려하는 지혜로운 접근'입니다. 마라톤 선수가 페이스를 유지하는 것과 비슷해요.
핵심 특징은 세 가지입니다. 첫째, 같은 방향의 기울기가 계속되면 속도가 빨라집니다(가속 효과).
둘째, 지역 최솟값(local minima)을 쉽게 탈출합니다(관성으로 넘어감). 셋째, 노이즈의 영향을 줄입니다(평균 효과).
이것이 바로 Momentum이 딥러닝에서 필수적인 이유입니다.
코드 예제
import numpy as np
# 동일한 선형 회귀 문제
np.random.seed(42)
X = np.random.rand(100, 1) * 10
y = 2 * X + 3 + np.random.randn(100, 1) * 0.5
w = 0.0
b = 0.0
learning_rate = 0.01
momentum = 0.9 # Momentum 계수 (핵심!)
# Velocity 초기화 (속도 개념)
vw = 0.0 # w에 대한 속도
vb = 0.0 # b에 대한 속도
epochs = 50
for epoch in range(epochs):
# 전체 데이터에 대한 기울기 (간단히 하기 위해 Batch 사용)
y_pred = w * X + b
dw = -(2/len(X)) * np.sum(X * (y - y_pred))
db = -(2/len(X)) * np.sum(y - y_pred)
# Momentum 업데이트 (핵심!)
vw = momentum * vw + learning_rate * dw # 이전 속도 * 0.9 + 현재 기울기
vb = momentum * vb + learning_rate * db
# 속도를 사용해서 파라미터 업데이트
w = w - vw
b = b - vb
if epoch % 10 == 0:
loss = np.mean((y - y_pred) ** 2)
print(f"Epoch {epoch}: w={w:.4f}, b={b:.4f}, Loss={loss:.4f}")
print(f"최종 결과: y = {w:.4f}x + {b:.4f}")
설명
이것이 하는 일: 위 코드는 Momentum을 추가한 Gradient Descent입니다. 일반 방식과의 차이는 velocity(속도) 변수를 도입해서 이전 방향을 기억한다는 점이에요.
첫 번째로, vw와 vb라는 새로운 변수를 0으로 초기화합니다. 이것들이 '속도'를 나타내는데, 물리학에서 속도는 이동 방향과 크기를 나타내죠.
처음에는 정지 상태(0)에서 시작합니다. momentum=0.9는 '이전 속도의 90%를 유지한다'는 의미입니다.
일반적으로 0.9나 0.99를 많이 씁니다. 그 다음으로, 핵심인 Momentum 업데이트를 봅시다.
vw = momentum * vw + learning_rate * dw에서 두 가지가 더해집니다. 첫째는 momentum * vw로 이전 속도의 90%를 유지하고, 둘째는 learning_rate * dw로 현재 기울기를 더합니다.
예를 들어, 이전에 오른쪽으로 가고 있었는데(vw > 0) 이번에도 오른쪽 기울기가 나오면(dw > 0) 속도가 더 빨라집니다. 반대로 왼쪽 기울기가 나오면(dw < 0) 속도가 줄어들죠.
마지막으로, w = w - vw로 속도만큼 파라미터를 이동합니다. 일반 Gradient Descent가 w = w - learning_rate * dw였던 것과 비교해보세요.
차이가 보이시나요? 일반 방식은 현재 기울기만 보지만, Momentum은 축적된 속도를 봅니다.
이 속도에는 과거의 모든 기울기가 지수 가중 평균으로 담겨 있어요. 여러분이 이 코드를 실행하면 수렴이 일반 방식보다 빠른 걸 확인할 수 있습니다.
같은 에포크 수에서 손실이 더 낮아지죠. 실무에서는 PyTorch의 torch.optim.SGD(momentum=0.9)나 TensorFlow의 SGD(momentum=0.9)로 간단히 사용할 수 있습니다.
특히 ResNet 같은 깊은 신경망을 학습할 때 Momentum은 거의 필수입니다. 지역 최솟값이 많은 복잡한 손실 함수에서도 관성 덕분에 전역 최솟값으로 잘 찾아갑니다.
실전 팁
💡 momentum 값은 0.9부터 시작하세요. 대부분의 경우 잘 작동하고, 논문에서도 기본값으로 많이 씁니다. 더 강한 관성이 필요하면 0.95나 0.99를 시도해보세요.
💡 학습 초기에는 관성이 쌓이지 않아서 효과가 적습니다. 10~20 에포크 정도 지나면 본격적으로 속도가 붙습니다. 인내심을 가지세요.
💡 Learning rate와 momentum의 조합이 중요합니다. momentum이 크면(0.99) learning rate를 작게(0.001), momentum이 작으면(0.5) learning rate를 크게(0.01) 하는 게 안정적입니다.
💡 Nesterov Momentum(NAG)이라는 개선 버전도 있습니다. 현재 위치가 아니라 '관성으로 갈 위치'에서 기울기를 계산하는 방식으로, 더 정확합니다. PyTorch에서 nesterov=True 옵션으로 쓸 수 있어요.
💡 손실 곡선이 진동하면서 수렴한다면 momentum이 너무 큰 겁니다. 0.9에서 0.8이나 0.7로 줄여보세요. 부드럽게 수렴해야 최적화가 잘 되는 겁니다.
7. AdaGrad_최적화
시작하며
여러분이 여러 과목을 공부한다고 상상해보세요. 수학은 이미 잘하지만 영어는 약하다면, 모든 과목에 같은 시간을 쓰는 게 효율적일까요?
아니죠. 영어에 더 많은 시간을 투자해야 합니다.
머신러닝 파라미터도 마찬가지입니다. 어떤 파라미터는 자주 업데이트되고(많이 학습됨), 어떤 건 거의 안 됩니다.
AdaGrad는 '덜 업데이트된 파라미터는 더 크게, 많이 업데이트된 파라미터는 작게' 조절하는 똑똑한 알고리즘입니다. 자연어 처리에서 특히 유용합니다.
자주 나오는 단어(the, is)와 희귀한 단어(quantum, photosynthesis)를 같은 학습률로 학습하면 비효율적이거든요. AdaGrad는 이 문제를 자동으로 해결합니다.
개요
간단히 말해서, AdaGrad는 각 파라미터의 과거 기울기 제곱을 누적하여, 많이 변한 파라미터는 학습률을 줄이고 적게 변한 파라미터는 학습률을 유지하는 적응적 학습률 알고리즘입니다. AdaGrad가 필요한 이유는 파라미터마다 최적 학습률이 다르기 때문입니다.
전통적인 방식은 모든 파라미터에 같은 학습률을 적용하지만, 실제로는 각 파라미터의 특성이 천차만별입니다. 예를 들어, 희소한 데이터(sparse data)를 다룰 때 자주 나타나는 특징은 이미 충분히 학습되었으니 학습률을 낮추고, 드물게 나타나는 특징은 학습률을 높게 유지해야 효율적입니다.
기존의 고정 학습률이 '일률적인 교육'이라면, AdaGrad는 '맞춤형 교육'입니다. 각 파라미터의 학습 이력을 보고 개별적으로 조절하는 거죠.
핵심 특징은 세 가지입니다. 첫째, 학습률이 파라미터별로 자동으로 조절됩니다(수동 튜닝 불필요).
둘째, 희소 데이터에서 뛰어난 성능을 보입니다(NLP, 추천 시스템). 셋째, 학습이 진행될수록 학습률이 계속 감소합니다(단점이기도 함).
이 마지막 특성 때문에 장기 학습에서는 문제가 될 수 있지만, 짧은 학습에서는 매우 효과적입니다.
코드 예제
import numpy as np
# 동일한 선형 회귀 문제
np.random.seed(42)
X = np.random.rand(100, 1) * 10
y = 2 * X + 3 + np.random.randn(100, 1) * 0.5
w = 0.0
b = 0.0
learning_rate = 0.5 # AdaGrad는 초기 학습률을 크게 설정 가능
epsilon = 1e-8 # 0으로 나누기 방지
# 기울기 제곱의 누적합
Gw = 0.0 # w에 대한 누적
Gb = 0.0 # b에 대한 누적
epochs = 100
for epoch in range(epochs):
# 기울기 계산
y_pred = w * X + b
dw = -(2/len(X)) * np.sum(X * (y - y_pred))
db = -(2/len(X)) * np.sum(y - y_pred)
# 기울기 제곱을 누적 (핵심!)
Gw += dw ** 2
Gb += db ** 2
# 적응적 학습률 적용 (핵심!)
adapted_lr_w = learning_rate / (np.sqrt(Gw) + epsilon)
adapted_lr_b = learning_rate / (np.sqrt(Gb) + epsilon)
# 파라미터 업데이트
w = w - adapted_lr_w * dw
b = b - adapted_lr_b * db
if epoch % 20 == 0:
loss = np.mean((y - y_pred) ** 2)
print(f"Epoch {epoch}: w={w:.4f}, b={b:.4f}, Loss={loss:.4f}")
print(f" 적응 학습률: w={adapted_lr_w:.6f}, b={adapted_lr_b:.6f}")
print(f"최종 결과: y = {w:.4f}x + {b:.4f}")
설명
이것이 하는 일: 위 코드는 AdaGrad로 선형 회귀를 해결하면서 각 파라미터의 학습률이 어떻게 자동으로 조절되는지 보여줍니다. Gw와 Gb에 기울기 제곱을 계속 쌓아가는 게 핵심이에요.
첫 번째로, Gw와 Gb를 0으로 초기화합니다. 이것들은 '기울기 제곱의 누적합'을 저장하는 변수입니다.
에포크가 진행될수록 계속 커지게 되어 있어요. epsilon은 아주 작은 값(0.00000001)인데, 나중에 분모로 쓸 때 0으로 나누는 걸 방지하기 위한 안전장치입니다.
초기 학습률은 0.5로 크게 설정했는데, AdaGrad가 알아서 줄여줄 거라서 크게 시작해도 괜찮습니다. 그 다음으로, 매 에포크마다 기울기를 계산한 후 Gw += dw ** 2로 제곱을 누적합니다.
예를 들어, 첫 번째 에포크에서 dw=10이면 Gw=100, 두 번째에서 dw=8이면 Gw=100+64=164 이런 식으로 계속 커집니다. 기울기가 클수록(파라미터가 많이 변할수록) 누적합이 빠르게 증가하죠.
마지막으로, 적응적 학습률을 계산합니다. learning_rate / (np.sqrt(Gw) + epsilon)에서 분모가 클수록(많이 업데이트되었을수록) 학습률이 작아집니다.
초기에는 Gw가 작아서 학습률이 거의 그대로지만, 나중에는 Gw가 커져서 학습률이 0.01, 0.001... 이렇게 줄어듭니다.
w와 b가 서로 다른 속도로 학습되는 걸 출력에서 확인할 수 있어요. 여러분이 이 코드를 실행하면 학습 초기에는 빠르게 수렴하고, 후반에는 천천히 미세 조정되는 모습을 볼 수 있습니다.
실무에서는 텍스트 데이터나 추천 시스템처럼 특징이 희소한 경우에 AdaGrad가 빛을 발합니다. TensorFlow의 tf.keras.optimizers.Adagrad()나 PyTorch의 torch.optim.Adagrad()로 쉽게 사용할 수 있어요.
하지만 학습이 너무 길어지면(수천 에포크) 학습률이 너무 작아져서 거의 학습이 안 되는 문제가 있습니다. 이걸 해결한 게 다음에 나올 RMSprop입니다.
실전 팁
💡 초기 학습률은 0.1~1.0 사이로 크게 시작하세요. AdaGrad가 자동으로 줄여주니까 처음엔 공격적으로 시작해도 됩니다. 일반 SGD에서는 위험한 0.5도 AdaGrad에서는 안전합니다.
💡 학습이 너무 일찍 멈추는 것 같으면(손실이 더 줄어들 여지가 있는데 멈춤) 학습률이 너무 빨리 줄어든 겁니다. epsilon을 1e-6이나 1e-7로 키워보세요.
💡 희소 데이터(0이 많은 데이터)에서 진가를 발휘합니다. Word2Vec, GloVe 같은 단어 임베딩이나 CTR 예측 같은 광고 추천에서 특히 효과적입니다.
💡 깊은 신경망보다는 얕은 모델에 적합합니다. CNN이나 RNN 같은 깊은 네트워크에서는 Adam이나 RMSprop이 더 낫습니다.
💡 학습 곡선을 모니터링하세요. 초기에 급격히 떨어지다가 중반부터 평평해지는 게 정상입니다. 너무 일찍 평평해지면 문제가 있는 거예요.
8. RMSprop_알고리즘
시작하며
여러분이 자전거를 타고 언덕을 내려간다고 상상해보세요. AdaGrad는 브레이크를 한 번 밟으면 계속 강해지는 자전거 같습니다.
처음엔 좋지만, 나중에는 브레이크가 너무 세서 거의 멈춰버리죠. RMSprop은 이 문제를 해결합니다.
'최근의' 기울기만 기억해서 오래된 기록은 서서히 잊어버립니다. 마치 사람의 기억처럼 최근 경험이 더 중요하게 작용하는 거예요.
Geoffrey Hinton 교수가 Coursera 강의에서 처음 소개한 이 알고리즘은 순식간에 딥러닝 커뮤니티에 퍼졌습니다. 정식 논문보다 강의 노트가 먼저 나온 흔치 않은 케이스죠.
현재 Adam 옵티마이저의 핵심 구성 요소이기도 합니다.
개요
간단히 말해서, RMSprop은 AdaGrad와 비슷하지만 기울기 제곱의 '이동 평균'을 사용하여 오래된 기울기의 영향을 줄이는 적응적 학습률 알고리즘입니다. RMSprop이 필요한 이유는 AdaGrad의 학습률 감소 문제를 해결하기 위해서입니다.
AdaGrad는 모든 과거 기울기를 영원히 누적하기 때문에 학습률이 단조 감소해서 결국 0에 가까워집니다. 하지만 RMSprop은 지수 가중 이동 평균(Exponential Moving Average)을 써서 최근 기울기에 더 큰 가중치를 둡니다.
예를 들어, 100 에포크 전의 기울기보다 최근 10 에포크의 기울기가 훨씬 중요하게 작용하는 거죠. 기존의 AdaGrad가 '모든 역사를 기억하는 코끼리'라면, RMSprop은 '최근 일을 잘 기억하는 인간'입니다.
과거는 서서히 잊고 현재에 집중하는 방식이에요. 핵심 특징은 세 가지입니다.
첫째, 학습률이 0으로 수렴하지 않습니다(장기 학습 가능). 둘째, 각 파라미터에 적응적 학습률을 적용합니다(AdaGrad 장점 유지).
셋째, 비정상적(non-stationary) 문제에 강합니다(데이터 분포가 바뀌어도 적응). 이것이 RMSprop이 딥러닝에서 가장 인기 있는 옵티마이저 중 하나가 된 이유입니다.
코드 예제
import numpy as np
# 동일한 선형 회귀 문제
np.random.seed(42)
X = np.random.rand(100, 1) * 10
y = 2 * X + 3 + np.random.randn(100, 1) * 0.5
w = 0.0
b = 0.0
learning_rate = 0.01
decay_rate = 0.9 # 이동 평균 감쇠율 (핵심!)
epsilon = 1e-8
# 기울기 제곱의 이동 평균
Ew = 0.0 # w에 대한 이동 평균
Eb = 0.0 # b에 대한 이동 평균
epochs = 100
for epoch in range(epochs):
# 기울기 계산
y_pred = w * X + b
dw = -(2/len(X)) * np.sum(X * (y - y_pred))
db = -(2/len(X)) * np.sum(y - y_pred)
# 이동 평균 업데이트 (핵심! AdaGrad와의 차이)
Ew = decay_rate * Ew + (1 - decay_rate) * (dw ** 2)
Eb = decay_rate * Eb + (1 - decay_rate) * (db ** 2)
# 적응적 학습률 적용
adapted_lr_w = learning_rate / (np.sqrt(Ew) + epsilon)
adapted_lr_b = learning_rate / (np.sqrt(Eb) + epsilon)
# 파라미터 업데이트
w = w - adapted_lr_w * dw
b = b - adapted_lr_b * db
if epoch % 20 == 0:
loss = np.mean((y - y_pred) ** 2)
print(f"Epoch {epoch}: w={w:.4f}, b={b:.4f}, Loss={loss:.4f}")
print(f" 적응 학습률: w={adapted_lr_w:.6f}, b={adapted_lr_b:.6f}")
print(f"최종 결과: y = {w:.4f}x + {b:.4f}")
설명
이것이 하는 일: 위 코드는 RMSprop으로 선형 회귀를 해결하면서 AdaGrad와의 핵심 차이인 이동 평균을 보여줍니다. Ew와 Eb가 무한정 커지지 않고 일정 범위에서 유지되는 게 포인트예요.
첫 번째로, decay_rate=0.9를 설정합니다. 이건 '이전 평균의 90%를 유지하고 현재 값의 10%를 더한다'는 의미입니다.
이 비율이 핵심인데, 보통 0.9나 0.99를 많이 씁니다. 0.9는 약 10개의 최근 기울기를 평균낸 효과이고, 0.99는 약 100개를 평균낸 효과입니다.
Ew와 Eb는 AdaGrad처럼 누적합이 아니라 '가중 평균'을 저장합니다. 그 다음으로, 핵심인 이동 평균 업데이트를 봅시다.
Ew = decay_rate * Ew + (1 - decay_rate) * (dw ** 2)에서 두 부분이 보이시나요? 첫째, decay_rate * Ew로 이전 평균의 90%를 가져오고, 둘째, (1 - decay_rate) * (dw ** 2)로 현재 기울기 제곱의 10%를 더합니다.
이렇게 하면 Ew는 최근 약 10개 에포크의 기울기 제곱을 반영하게 됩니다. AdaGrad처럼 무한정 커지지 않아요.
마지막으로, 적응적 학습률 계산은 AdaGrad와 같습니다. learning_rate / (np.sqrt(Ew) + epsilon)이죠.
하지만 Ew가 일정 범위에 머물기 때문에 학습률도 일정 범위에 머뭅니다. 예를 들어, Ew가 1.0 근처에서 안정되면 학습률은 0.01 / sqrt(1.0) ≈ 0.01로 유지됩니다.
AdaGrad처럼 0.0001, 0.00001... 이렇게 계속 줄어들지 않아요.
여러분이 이 코드를 실행하면 AdaGrad보다 학습이 안정적으로 오래 지속되는 걸 확인할 수 있습니다. 1000 에포크를 돌려도 여전히 학습이 진행되죠.
실무에서는 RNN이나 LSTM 같은 순환 신경망에서 특히 잘 작동합니다. Keras의 기본 옵티마이저가 RMSprop인 이유가 여기 있어요.
PyTorch에서는 torch.optim.RMSprop(lr=0.01, alpha=0.9)로 사용합니다. decay_rate는 alpha라는 이름으로 불립니다.
대부분의 경우 기본값인 0.9로도 충분하지만, 더 긴 기억이 필요하면 0.99를 시도해보세요.
실전 팁
💡 decay_rate는 0.9를 기본으로 하되, 학습이 너무 튀면 0.99로 키워서 더 부드럽게 만드세요. 반대로 너무 느리면 0.8이나 0.7로 줄여보세요.
💡 초기 학습률은 0.001부터 시작하는 게 안전합니다. RMSprop은 AdaGrad만큼 공격적이지 않아서 너무 큰 학습률은 위험합니다.
💡 Gradient Clipping과 함께 쓰면 효과적입니다. RNN에서 기울기 폭발(gradient explosion) 문제가 있을 때 if gradient > 5: gradient = 5 식으로 제한하세요.
💡 Adam 옵티마이저를 써보세요. RMSprop + Momentum을 결합한 거라서 대부분의 경우 더 좋습니다. torch.optim.Adam(lr=0.001)이 현재 업계 표준입니다.
💡 학습이 중간에 정체되면 learning rate를 1/10로 줄여보세요. RMSprop은 학습률 스케줄링과 궁합이 좋습니다. ReduceLROnPlateau 콜백을 추천합니다.
댓글 (0)
함께 보면 좋은 카드 뉴스
데이터 증강과 정규화 완벽 가이드
머신러닝 모델의 성능을 극대화하는 핵심 기법인 데이터 증강과 정규화에 대해 알아봅니다. 실무에서 바로 활용할 수 있는 다양한 기법과 실전 예제를 통해 과적합을 방지하고 모델 성능을 향상시키는 방법을 배웁니다.
ResNet과 Skip Connection 완벽 가이드
딥러닝 모델이 깊어질수록 성능이 떨어지는 문제를 해결한 혁신적인 기법, ResNet과 Skip Connection을 초급자도 이해할 수 있도록 쉽게 설명합니다. 실제 구현 코드와 함께 배워보세요.
CNN 아키텍처 완벽 가이드 LeNet AlexNet VGGNet
컴퓨터 비전의 기초가 되는 세 가지 핵심 CNN 아키텍처를 배웁니다. 손글씨 인식부터 이미지 분류까지, 딥러닝의 발전 과정을 따라가며 각 모델의 구조와 특징을 실습 코드와 함께 이해합니다.
CNN 기초 Convolution과 Pooling 완벽 가이드
CNN의 핵심인 Convolution과 Pooling을 초급자도 쉽게 이해할 수 있도록 설명합니다. 이미지 인식의 원리부터 실제 코드 구현까지, 실무에서 바로 활용 가능한 내용을 담았습니다.
TensorFlow와 Keras 완벽 입문 가이드
머신러닝과 딥러닝의 세계로 들어가는 첫걸음! TensorFlow와 Keras 프레임워크를 처음 접하는 분들을 위한 친절한 가이드입니다. 실무에서 바로 활용할 수 있는 핵심 개념과 예제를 통해 AI 모델 개발의 기초를 탄탄히 다져보세요.