이미지 로딩 중...

Overfitting 방지 및 Early Stopping 완벽 가이드 - 슬라이드 1/9
A

AI Generated

2025. 11. 17. · 4 Views

Overfitting 방지 및 Early Stopping 완벽 가이드

머신러닝 모델이 학습 데이터에만 과하게 적응하는 Overfitting 문제를 해결하고, Early Stopping 기법으로 최적의 학습 시점을 찾는 방법을 실전 예제와 함께 알아봅니다.


목차

  1. Overfitting의 개념과 문제점
  2. Early Stopping의 원리와 구현
  3. Dropout을 활용한 정규화
  4. L1/L2 정규화로 가중치 제어하기
  5. 데이터 증강으로 학습 데이터 늘리기
  6. 교차 검증으로 모델 성능 정확히 평가하기
  7. 배치 정규화로 학습 안정화하기
  8. 학습률 스케줄링으로 최적점 찾기

1. Overfitting의 개념과 문제점

시작하며

여러분이 시험공부를 할 때 교재의 예제 문제만 반복해서 풀다가 실제 시험에서 낮은 점수를 받은 경험이 있나요? 머신러닝 모델도 똑같은 문제를 겪습니다.

학습 데이터만 너무 열심히 외워서 새로운 데이터를 만나면 제대로 작동하지 않는 거죠. 이런 현상을 Overfitting(과적합)이라고 부릅니다.

학습 데이터에서는 정확도가 99%인데, 실제 사용할 때는 60%밖에 안 나오는 당황스러운 상황이 바로 이것 때문입니다. 실무에서 모델을 배포했는데 성능이 기대에 못 미친다면, 가장 먼저 의심해봐야 할 것이 바로 Overfitting입니다.

지금부터 이 문제가 왜 발생하고 어떻게 해결하는지 차근차근 알아보겠습니다.

개요

간단히 말해서, Overfitting은 모델이 학습 데이터의 패턴뿐만 아니라 노이즈까지 모두 외워버리는 현상입니다. 마치 시험 문제의 답을 이해하는 게 아니라 통째로 암기하는 것과 같습니다.

이 문제는 특히 모델이 복잡하거나 학습 데이터가 부족할 때 자주 발생합니다. 예를 들어, 고객의 구매 패턴을 예측하는 모델을 만들 때, 학습 데이터 속 특정 고객들의 개인적인 습관까지 모두 학습해버리면 새로운 고객에게는 전혀 맞지 않는 예측을 하게 됩니다.

전통적으로는 모델을 최대한 오래 학습시키는 것이 좋다고 생각했습니다. 하지만 이제는 "적절한 시점에 멈추는 것"이 더 중요하다는 것을 알게 되었습니다.

Overfitting의 핵심 특징은 세 가지입니다. 첫째, 학습 데이터 정확도는 계속 올라가지만 검증 데이터 정확도는 어느 순간부터 떨어집니다.

둘째, 모델이 학습 데이터의 세부 사항까지 지나치게 기억합니다. 셋째, 일반화 능력이 떨어져서 새로운 데이터에 대한 예측력이 낮아집니다.

이러한 특징들을 이해하면 문제를 조기에 발견하고 대응할 수 있습니다.

코드 예제

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

# 간단한 데이터 생성 - 실제로는 노이즈가 포함된 데이터
np.random.seed(42)
X = np.sort(np.random.rand(50, 1) * 10, axis=0)
y = np.sin(X).ravel() + np.random.randn(50) * 0.5  # 노이즈 추가

# 학습/테스트 데이터 분리 - 실전에서 필수!
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 1차 모델 (단순) vs 15차 모델 (복잡) 비교
poly_1 = PolynomialFeatures(degree=1)
poly_15 = PolynomialFeatures(degree=15)

X_train_poly_1 = poly_1.fit_transform(X_train)
X_train_poly_15 = poly_15.fit_transform(X_train)

# 모델 학습
model_simple = LinearRegression().fit(X_train_poly_1, y_train)
model_complex = LinearRegression().fit(X_train_poly_15, y_train)

# 학습 데이터와 테스트 데이터에서의 성능 비교
print(f"단순 모델 - 학습 오차: {mean_squared_error(y_train, model_simple.predict(X_train_poly_1)):.4f}")
print(f"복잡 모델 - 학습 오차: {mean_squared_error(y_train, model_complex.predict(X_train_poly_15)):.4f}")

설명

이것이 하는 일: 위 코드는 Overfitting이 실제로 어떻게 발생하는지 눈으로 확인할 수 있도록 단순한 모델과 복잡한 모델을 비교합니다. 첫 번째로, 노이즈가 포함된 사인 곡선 데이터를 생성합니다.

실제 세상의 데이터는 항상 노이즈가 있기 때문에 현실적인 상황을 만들기 위해 np.random.randn(50) * 0.5로 무작위 노이즈를 추가합니다. 이 노이즈는 센서 오차나 측정 오류 같은 실제 상황을 모방한 것입니다.

그 다음으로, 데이터를 학습용과 테스트용으로 나눕니다. 이것이 정말 중요한 이유는, 모델이 본 적 없는 데이터에서 얼마나 잘 작동하는지 확인해야 실제 성능을 알 수 있기 때문입니다.

test_size=0.3은 전체 데이터의 30%를 테스트용으로 남겨둔다는 뜻입니다. 세 번째로, 1차 다항식 모델(직선)과 15차 다항식 모델(복잡한 곡선)을 각각 만들어 학습시킵니다.

1차 모델은 단순해서 데이터의 전반적인 패턴만 학습하지만, 15차 모델은 너무 복잡해서 학습 데이터의 모든 굴곡을 따라가려고 합니다. 마지막으로, 두 모델의 학습 데이터 오차를 출력합니다.

여기서 놀라운 사실을 발견하게 됩니다. 복잡한 모델의 학습 오차가 훨씬 낮지만, 테스트 데이터에서는 오히려 성능이 나쁠 수 있습니다.

이것이 바로 Overfitting의 증거입니다. 여러분이 이 코드를 실행하면 복잡한 모델이 학습 데이터의 노이즈까지 모두 학습했다는 것을 확인할 수 있습니다.

실무에서는 항상 학습 데이터와 테스트 데이터의 성능을 함께 확인해야 하며, 두 지표의 간격이 크다면 Overfitting을 의심해야 합니다.

실전 팁

💡 모델의 학습 곡선을 항상 시각화하세요. 학습 손실은 계속 줄어드는데 검증 손실이 증가하기 시작하는 지점이 보이면 그것이 Overfitting의 시작입니다.

💡 데이터가 부족할 때는 데이터 증강(Data Augmentation)을 활용하세요. 이미지라면 회전, 크롭, 밝기 조절 등으로 데이터를 늘릴 수 있습니다.

💡 모델의 복잡도를 줄이는 것도 방법입니다. 신경망의 레이어 수를 줄이거나, 의사결정 트리의 깊이를 제한하면 Overfitting을 줄일 수 있습니다.

💡 Dropout, L1/L2 정규화 같은 기법들을 적극 활용하세요. 이들은 모델이 특정 특성에만 의존하지 않도록 강제합니다.

💡 교차 검증(Cross Validation)을 사용하면 모델의 일반화 성능을 더 정확하게 평가할 수 있습니다. 특히 데이터가 적을 때 유용합니다.


2. Early Stopping의 원리와 구현

시작하며

여러분이 운동을 할 때 언제 멈춰야 할지 고민한 적 있나요? 너무 일찍 멈추면 효과가 없고, 너무 오래 하면 부상을 입습니다.

머신러닝 모델 학습도 마찬가지입니다. 많은 개발자들이 모델을 수백, 수천 번 학습시키면서 "언제 멈춰야 하지?"라는 고민에 빠집니다.

너무 일찍 멈추면 성능이 덜 나오고, 너무 오래 학습하면 Overfitting에 빠지기 때문입니다. 바로 이 문제를 해결하는 것이 Early Stopping입니다.

검증 데이터의 성능을 지켜보다가 더 이상 개선되지 않으면 자동으로 학습을 멈추는 똑똑한 방법입니다.

개요

간단히 말해서, Early Stopping은 검증 데이터의 성능이 일정 기간 동안 개선되지 않으면 학습을 자동으로 중단하는 기법입니다. 마치 운동할 때 심박수를 체크하면서 적절한 시점에 멈추는 것과 같습니다.

이 기법은 특히 신경망 학습처럼 오래 걸리는 작업에서 시간과 자원을 절약하는 데 매우 효과적입니다. 예를 들어, 이미지 분류 모델을 학습시킬 때 원래 1000번 반복할 계획이었지만, 500번째에서 검증 손실이 더 이상 개선되지 않는다면 거기서 멈추는 것이 현명합니다.

전통적으로는 미리 정한 횟수만큼 무조건 학습했습니다. 하지만 이제는 검증 데이터의 피드백을 실시간으로 받아서 동적으로 학습을 조절할 수 있습니다.

Early Stopping의 핵심 파라미터는 세 가지입니다. 첫째, patience(인내심) - 성능 개선이 없어도 얼마나 기다릴 것인지.

둘째, min_delta - 얼마나 개선되어야 "의미 있는 개선"으로 볼 것인지. 셋째, restore_best_weights - 멈췄을 때 가장 좋았던 시점의 가중치로 되돌릴 것인지.

이 세 가지를 적절히 조절하면 최적의 모델을 얻을 수 있습니다.

코드 예제

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import EarlyStopping
import numpy as np

# 샘플 데이터 생성 (실제로는 여러분의 데이터를 사용)
X_train = np.random.randn(1000, 20)  # 1000개 샘플, 20개 특성
y_train = np.random.randint(0, 2, 1000)  # 이진 분류
X_val = np.random.randn(200, 20)
y_val = np.random.randint(0, 2, 200)

# 간단한 신경망 모델 구축
model = keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=(20,)),
    layers.Dropout(0.3),  # Overfitting 방지를 위한 Dropout
    layers.Dense(32, activation='relu'),
    layers.Dense(1, activation='sigmoid')  # 이진 분류
])

model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

# Early Stopping 콜백 설정 - 핵심!
early_stopping = EarlyStopping(
    monitor='val_loss',  # 검증 손실을 모니터링
    patience=10,  # 10번 동안 개선 없으면 중단
    min_delta=0.001,  # 최소 0.001 이상 개선되어야 의미 있다고 판단
    restore_best_weights=True,  # 가장 좋았던 가중치로 복원
    verbose=1  # 중단 시 메시지 출력
)

# 모델 학습 - Early Stopping이 자동으로 작동
history = model.fit(
    X_train, y_train,
    validation_data=(X_val, y_val),
    epochs=100,  # 최대 100번까지만 학습 시도
    batch_size=32,
    callbacks=[early_stopping],  # 콜백 적용
    verbose=0
)

print(f"실제 학습된 epoch 수: {len(history.history['loss'])}")

설명

이것이 하는 일: 이 코드는 Keras의 Early Stopping 콜백을 사용하여 모델이 과적합되기 전에 자동으로 학습을 중단하는 방법을 보여줍니다. 첫 번째로, 샘플 데이터와 간단한 신경망 모델을 만듭니다.

실제로는 여러분의 데이터셋을 사용하면 되고, 모델 구조도 문제에 맞게 조정하면 됩니다. Dropout 레이어를 추가한 것은 추가적인 Overfitting 방지 장치입니다.

그 다음으로, EarlyStopping 콜백을 설정합니다. monitor='val_loss'는 검증 데이터의 손실 값을 지켜본다는 뜻입니다.

만약 정확도를 기준으로 하고 싶다면 monitor='val_accuracy'로 바꾸면 됩니다. patience=10은 10번의 epoch 동안 개선이 없어도 참고 기다린다는 의미입니다.

너무 짧으면 조기에 멈출 수 있고, 너무 길면 Overfitting될 수 있으니 적절한 값을 찾아야 합니다. 세 번째로, min_delta=0.001은 0.001 이상 개선되어야 "의미 있는 개선"으로 간주한다는 설정입니다.

너무 작은 개선은 무시하고 실질적인 개선만 카운트하겠다는 뜻입니다. restore_best_weights=True는 정말 중요한데, 학습을 멈췄을 때 마지막 가중치가 아니라 가장 성능이 좋았던 시점의 가중치로 되돌려줍니다.

마지막으로, model.fit()callbacks=[early_stopping]을 전달하여 학습 중에 자동으로 Early Stopping이 작동하도록 합니다. epochs=100으로 설정했지만, 실제로는 훨씬 일찍 멈출 수 있습니다.

학습이 끝난 후 len(history.history['loss'])를 출력하면 실제로 몇 번 학습했는지 확인할 수 있습니다. 여러분이 이 코드를 사용하면 수동으로 학습을 모니터링할 필요 없이 자동으로 최적의 시점에 학습이 멈춥니다.

실무에서는 patience 값을 5~20 사이로 설정하는 것이 일반적이며, 데이터셋의 크기와 모델의 복잡도에 따라 조정합니다.

실전 팁

💡 patience 값은 너무 작게 설정하지 마세요. 학습 과정에서 일시적으로 성능이 정체되는 구간이 있을 수 있으므로 최소 5 이상을 권장합니다.

💡 restore_best_weights=True는 거의 항상 사용하세요. 이것이 없으면 과적합된 마지막 모델을 그대로 사용하게 됩니다.

💡 학습률 스케줄러(Learning Rate Scheduler)와 함께 사용하면 효과가 배가됩니다. 학습률을 점진적으로 낮추면서 Early Stopping을 적용하세요.

💡 여러 지표를 함께 모니터링하세요. val_loss와 val_accuracy를 함께 확인하면 모델의 상태를 더 정확히 파악할 수 있습니다.

💡 ModelCheckpoint 콜백과 함께 사용하면 학습 중 최고 성능의 모델을 파일로 저장할 수 있어 더욱 안전합니다.


3. Dropout을 활용한 정규화

시작하며

여러분이 팀 프로젝트를 할 때 한 사람에게만 의존하면 그 사람이 빠졌을 때 팀 전체가 무너지는 경험을 해보셨나요? 신경망도 비슷한 문제가 있습니다.

특정 뉴런에만 너무 의존하면 일반화 능력이 떨어집니다. 많은 개발자들이 신경망의 레이어를 깊게 쌓으면서 "왜 학습 데이터에서는 잘 되는데 실제로는 안 될까?"라는 문제에 부딪힙니다.

모델이 학습 데이터의 특정 패턴에만 과하게 의존하기 때문입니다. 이 문제를 해결하는 간단하면서도 강력한 방법이 바로 Dropout입니다.

학습 중에 무작위로 일부 뉴런을 꺼버려서 모델이 골고루 학습하도록 만드는 기법입니다.

개요

간단히 말해서, Dropout은 학습 중에 무작위로 일부 뉴런의 출력을 0으로 만들어서 특정 뉴런에 과도하게 의존하지 않도록 하는 정규화 기법입니다. 마치 축구팀이 주전 선수 몇 명이 빠져도 경기를 할 수 있도록 훈련하는 것과 같습니다.

이 기법은 특히 큰 신경망에서 Overfitting을 효과적으로 방지합니다. 예를 들어, 이미지 인식 모델을 만들 때 특정 픽셀 조합에만 의존하지 않고 다양한 특징을 학습하도록 유도할 수 있습니다.

매 학습 단계마다 다른 뉴런들이 비활성화되므로, 모델은 어떤 뉴런이 꺼져도 작동할 수 있는 강건한 표현을 학습합니다. 전통적으로는 가중치 감쇠(Weight Decay)나 L2 정규화를 사용했습니다.

하지만 Dropout은 더 직관적이고 효과적이어서 현대 딥러닝에서 필수적인 기법이 되었습니다. Dropout의 핵심 특징은 세 가지입니다.

첫째, 학습 시에만 적용되고 예측 시에는 모든 뉴런을 사용합니다. 둘째, dropout rate(보통 0.2~0.5)를 조절하여 강도를 조절할 수 있습니다.

셋째, 앙상블 효과가 있어서 마치 여러 모델을 동시에 학습하는 것 같은 효과를 냅니다. 이러한 특징들이 Dropout을 강력한 정규화 도구로 만듭니다.

코드 예제

from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

# MNIST 데이터 로드 (손글씨 숫자 인식)
(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()

# 데이터 전처리 - 정규화 및 평탄화
X_train = X_train.reshape(-1, 784).astype('float32') / 255.0
X_test = X_test.reshape(-1, 784).astype('float32') / 255.0

# Dropout을 사용하는 모델
model_with_dropout = keras.Sequential([
    layers.Dense(512, activation='relu', input_shape=(784,)),
    layers.Dropout(0.3),  # 30%의 뉴런을 무작위로 끔
    layers.Dense(256, activation='relu'),
    layers.Dropout(0.3),  # 각 레이어마다 Dropout 적용
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.2),  # 출력층에 가까울수록 낮은 비율 사용
    layers.Dense(10, activation='softmax')  # 10개 클래스 분류
])

# Dropout 없는 비교 모델
model_without_dropout = keras.Sequential([
    layers.Dense(512, activation='relu', input_shape=(784,)),
    layers.Dense(256, activation='relu'),
    layers.Dense(128, activation='relu'),
    layers.Dense(10, activation='softmax')
])

# 두 모델 컴파일
for model in [model_with_dropout, model_without_dropout]:
    model.compile(
        optimizer='adam',
        loss='sparse_categorical_crossentropy',
        metrics=['accuracy']
    )

# Dropout 모델 학습
history_dropout = model_with_dropout.fit(
    X_train, y_train,
    validation_split=0.2,  # 20%를 검증용으로
    epochs=20,
    batch_size=128,
    verbose=0
)

print(f"Dropout 모델 - 학습 정확도: {history_dropout.history['accuracy'][-1]:.4f}")
print(f"Dropout 모델 - 검증 정확도: {history_dropout.history['val_accuracy'][-1]:.4f}")

설명

이것이 하는 일: 이 코드는 Dropout을 적용한 모델과 적용하지 않은 모델을 비교하여 Dropout의 효과를 확인합니다. 첫 번째로, MNIST 손글씨 숫자 데이터셋을 로드하고 전처리합니다.

28x28 이미지를 784차원 벡터로 펼치고, 픽셀 값을 0~1 사이로 정규화합니다. 이 전처리는 신경망 학습의 필수 단계입니다.

그 다음으로, Dropout을 포함한 모델을 정의합니다. layers.Dropout(0.3)은 해당 레이어의 출력 중 30%를 무작위로 0으로 만듭니다.

중요한 것은 각 학습 배치마다 다른 뉴런들이 비활성화된다는 점입니다. 첫 번째 레이어에서는 512개 뉴런 중 약 154개가 매번 무작위로 꺼집니다.

세 번째로, 비교를 위해 Dropout이 없는 동일한 구조의 모델도 만듭니다. 두 모델의 파라미터 수는 같지만, Dropout의 유무에 따라 학습 방식과 결과가 크게 달라집니다.

네 번째로, 두 모델을 학습시킵니다. validation_split=0.2는 학습 데이터의 20%를 검증용으로 분리합니다.

Dropout이 있는 모델은 학습 중에 매번 다른 "부분 네트워크"를 학습하는 것과 같으므로, 학습 정확도는 Dropout 없는 모델보다 낮을 수 있습니다. 하지만 검증 정확도와 테스트 정확도는 더 높게 나옵니다.

마지막으로, 학습 결과를 출력합니다. 일반적으로 Dropout 모델은 학습 정확도와 검증 정확도의 차이가 작습니다.

예를 들어, Dropout 없는 모델이 "학습 98%, 검증 92%"라면, Dropout 모델은 "학습 95%, 검증 94%"처럼 더 균형 잡힌 결과를 보입니다. 여러분이 이 코드를 실행하면 Dropout이 Overfitting을 얼마나 효과적으로 방지하는지 직접 확인할 수 있습니다.

실무에서는 보통 0.2~0.5 사이의 dropout rate를 사용하며, 레이어가 클수록, 데이터가 적을수록 높은 비율을 적용합니다.

실전 팁

💡 입력 레이어 바로 다음에는 낮은 dropout rate(0.10.2)를 사용하고, 중간 레이어에서는 0.30.5를 사용하세요. 출력 레이어 직전에는 더 낮게 설정합니다.

💡 Dropout은 학습 시간을 늘릴 수 있으므로 batch size를 키우거나 learning rate를 조금 높이는 것도 고려하세요.

💡 CNN에서는 Spatial Dropout을 사용하세요. 일반 Dropout은 개별 픽셀을, Spatial Dropout은 전체 특성 맵을 끄므로 이미지 데이터에 더 효과적입니다.

💡 Batch Normalization과 함께 사용할 때는 Dropout을 Batch Normalization 뒤에 배치하세요. 순서가 중요합니다.

💡 예측할 때는 Dropout이 자동으로 비활성화됩니다. 하지만 Monte Carlo Dropout을 사용하면 예측의 불확실성도 측정할 수 있습니다.


4. L1/L2 정규화로 가중치 제어하기

시작하며

여러분이 짐을 쌀 때 너무 많은 물건을 넣으면 가방이 터질 것 같은 경험을 해보셨나요? 신경망의 가중치도 마찬가지입니다.

너무 큰 값을 가지면 모델이 불안정해지고 과적합됩니다. 많은 개발자들이 모델을 학습시킨 후 가중치를 확인해보면 어떤 값은 100, 어떤 값은 0.001처럼 극단적인 경우를 발견합니다.

이런 불균형한 가중치는 모델의 일반화 능력을 해칩니다. 이 문제를 해결하는 수학적으로 우아한 방법이 L1/L2 정규화입니다.

손실 함수에 가중치의 크기를 페널티로 추가하여 모델이 "적당한" 가중치 값을 학습하도록 유도합니다.

개요

간단히 말해서, L1/L2 정규화는 손실 함수에 가중치의 크기에 대한 벌칙(penalty)을 추가하여 가중치가 너무 커지는 것을 방지하는 기법입니다. 마치 세금처럼, 큰 가중치를 사용하면 그만큼 손실이 증가하므로 모델은 작은 가중치를 선호하게 됩니다.

L2 정규화(Ridge)는 가중치의 제곱합을 페널티로 추가하여 모든 가중치를 골고루 작게 만듭니다. L1 정규화(Lasso)는 가중치의 절댓값 합을 페널티로 추가하여 불필요한 가중치를 완전히 0으로 만들어 특성 선택 효과가 있습니다.

실무에서는 L2가 더 일반적으로 사용되지만, 특성이 많고 중요한 특성만 골라내야 할 때는 L1이 유용합니다. 전통적으로는 복잡한 수식으로 정규화를 구현해야 했습니다.

하지만 현대 딥러닝 프레임워크는 한 줄의 코드로 정규화를 적용할 수 있게 해줍니다. L1/L2 정규화의 핵심 특징은 세 가지입니다.

첫째, 정규화 강도를 조절하는 람다(lambda) 파라미터가 있습니다. 둘째, L2는 가중치를 0에 가깝게, L1은 정확히 0으로 만듭니다.

셋째, 정규화는 모델의 구조를 바꾸지 않고도 Overfitting을 방지할 수 있습니다. 이러한 특징들이 정규화를 머신러닝의 기본 도구로 만듭니다.

코드 예제

from tensorflow import keras
from tensorflow.keras import layers, regularizers
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split

# 분류 문제를 위한 샘플 데이터 생성
X, y = make_classification(
    n_samples=1000, n_features=50, n_informative=20,
    n_redundant=10, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

# L2 정규화를 사용하는 모델 (가장 일반적)
model_l2 = keras.Sequential([
    layers.Dense(128, activation='relu', input_shape=(50,),
                 kernel_regularizer=regularizers.L2(0.01)),  # L2 정규화 적용
    layers.Dense(64, activation='relu',
                 kernel_regularizer=regularizers.L2(0.01)),
    layers.Dense(1, activation='sigmoid')
])

# L1 정규화를 사용하는 모델 (특성 선택에 유용)
model_l1 = keras.Sequential([
    layers.Dense(128, activation='relu', input_shape=(50,),
                 kernel_regularizer=regularizers.L1(0.01)),  # L1 정규화
    layers.Dense(64, activation='relu',
                 kernel_regularizer=regularizers.L1(0.01)),
    layers.Dense(1, activation='sigmoid')
])

# L1 + L2 결합 (ElasticNet)
model_l1_l2 = keras.Sequential([
    layers.Dense(128, activation='relu', input_shape=(50,),
                 kernel_regularizer=regularizers.L1L2(l1=0.01, l2=0.01)),
    layers.Dense(64, activation='relu',
                 kernel_regularizer=regularizers.L1L2(l1=0.01, l2=0.01)),
    layers.Dense(1, activation='sigmoid')
])

# L2 모델 학습 및 평가
model_l2.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
history = model_l2.fit(X_train, y_train, validation_split=0.2,
                       epochs=50, batch_size=32, verbose=0)

# 가중치 크기 확인
weights = model_l2.layers[0].get_weights()[0]
print(f"L2 정규화 모델의 평균 가중치 크기: {np.abs(weights).mean():.6f}")
print(f"0에 가까운 가중치 비율: {(np.abs(weights) < 0.01).sum() / weights.size * 100:.2f}%")

설명

이것이 하는 일: 이 코드는 L1, L2, 그리고 L1+L2 정규화를 각각 적용한 모델을 만들고, 정규화가 가중치에 미치는 영향을 확인합니다. 첫 번째로, make_classification을 사용하여 50개의 특성을 가진 분류 데이터를 생성합니다.

그 중 20개는 유용한 특성이고 10개는 중복된 특성입니다. 이런 설정은 실제 데이터에서 모든 특성이 똑같이 중요하지 않은 상황을 모방합니다.

그 다음으로, L2 정규화 모델을 정의합니다. kernel_regularizer=regularizers.L2(0.01)이 핵심입니다.

0.01은 정규화 강도를 나타내는데, 이 값이 클수록 가중치를 더 강하게 제한합니다. 너무 크면 모델이 underfitting될 수 있고, 너무 작으면 정규화 효과가 없습니다.

일반적으로 0.001~0.1 사이의 값을 시도해봅니다. 세 번째로, L1 정규화와 L1L2 결합 모델도 만듭니다.

L1 정규화는 일부 가중치를 정확히 0으로 만들어서 불필요한 특성을 자동으로 제거하는 효과가 있습니다. 예를 들어, 50개 특성 중 30개의 가중치가 0이 되면 실질적으로 20개 특성만 사용하는 것입니다.

L1L2 결합은 두 방법의 장점을 모두 취합니다. 네 번째로, L2 모델을 학습시킵니다.

학습 과정에서 모델은 원래 손실(분류 오차)뿐만 아니라 가중치 크기도 함께 최소화하려고 합니다. 이것은 수식으로 표현하면 Total Loss = Classification Loss + 0.01 * Σ(weight²)입니다.

마지막으로, 학습된 모델의 가중치를 확인합니다. 정규화를 적용하지 않은 모델과 비교하면 가중치의 평균 크기가 훨씬 작고, 분포도 더 균일합니다.

또한 0에 가까운 가중치의 비율을 계산하여 L1 정규화의 특성 선택 효과를 확인할 수 있습니다. 여러분이 이 코드를 사용하면 데이터와 모델에 맞는 최적의 정규화 강도를 찾을 수 있습니다.

실무에서는 0.001, 0.01, 0.1 등 여러 값을 시도해보고 검증 데이터에서 가장 좋은 성능을 내는 값을 선택합니다.

실전 팁

💡 정규화 강도는 학습률과 함께 조정해야 합니다. 학습률이 높으면 정규화 강도도 높여야 균형이 맞습니다.

💡 모든 레이어에 같은 정규화를 적용할 필요는 없습니다. 보통 큰 레이어에 더 강한 정규화를 적용합니다.

💡 bias(편향) 항에는 정규화를 적용하지 마세요. kernel_regularizer는 가중치만, bias_regularizer는 편향만 제어합니다. 대부분의 경우 편향은 그대로 둡니다.

💡 배치 정규화(Batch Normalization)를 사용하면 L2 정규화의 필요성이 줄어듭니다. 둘 다 사용할 경우 정규화 강도를 낮추세요.

💡 정규화 효과를 시각화하려면 가중치 분포를 히스토그램으로 그려보세요. 정규화된 모델은 0 근처에 집중된 분포를 보입니다.


5. 데이터 증강으로 학습 데이터 늘리기

시작하며

여러분이 외국어를 배울 때 같은 교재만 반복해서 보면 실제 대화에서는 어려움을 겪는 경험을 해보셨나요? 머신러닝 모델도 마찬가지로 다양한 데이터를 봐야 실전에서 잘 작동합니다.

많은 개발자들이 "데이터가 부족해서 모델 성능이 안 나와요"라는 고민을 합니다. 특히 이미지 분류나 음성 인식처럼 라벨링된 데이터를 모으기 어려운 분야에서 이 문제가 심각합니다.

하지만 기존 데이터를 조금씩 변형해서 새로운 학습 샘플을 만들 수 있다면 어떨까요? 바로 이것이 데이터 증강(Data Augmentation)의 아이디어입니다.

원본 데이터의 본질은 유지하면서 다양성을 추가하는 똑똑한 방법입니다.

개요

간단히 말해서, 데이터 증강은 기존 데이터를 회전, 크롭, 밝기 조절 등으로 변형하여 학습 데이터를 인위적으로 늘리는 기법입니다. 마치 같은 사진을 다른 각도, 다른 조명에서 다시 찍는 것과 같습니다.

이 기법은 특히 이미지 데이터에서 강력한 효과를 발휘합니다. 예를 들어, 고양이 사진 1000장으로 학습하는 대신, 각 사진을 회전, 좌우 반전, 확대/축소하면 실질적으로 10000장 이상의 데이터를 확보할 수 있습니다.

중요한 것은 이렇게 만든 데이터들이 모두 "진짜 같은" 변형이어야 한다는 점입니다. 고양이를 상하 반전하는 것은 비현실적이지만, 좌우 반전은 자연스럽습니다.

전통적으로는 데이터를 미리 증강해서 저장했습니다. 하지만 현대적인 방법은 학습 중에 실시간으로 증강하는 것입니다.

이렇게 하면 매 epoch마다 조금씩 다른 데이터를 보게 되어 더 강력한 일반화 효과를 얻을 수 있습니다. 데이터 증강의 핵심 특징은 세 가지입니다.

첫째, 데이터의 본질적 의미는 바꾸지 않으면서 외형만 변경합니다. 둘째, 학습 시에만 적용하고 테스트 시에는 원본 데이터를 사용합니다.

셋째, 도메인 지식이 중요합니다 - 의료 영상과 일반 사진은 다른 증강 기법이 필요합니다. 이러한 특징들을 이해하면 효과적인 증강 전략을 세울 수 있습니다.

코드 예제

from tensorflow import keras
from tensorflow.keras import layers
import tensorflow as tf
import numpy as np

# 데이터 증강 레이어 정의 - 모델의 일부로 포함
data_augmentation = keras.Sequential([
    layers.RandomFlip("horizontal"),  # 좌우 반전 (50% 확률)
    layers.RandomRotation(0.1),  # ±10% (±36도) 회전
    layers.RandomZoom(0.1),  # ±10% 확대/축소
    layers.RandomContrast(0.1),  # 대비 조절
    layers.RandomTranslation(0.1, 0.1),  # 수평/수직 이동
])

# CIFAR-10 데이터 로드 (자동차, 비행기 등 10개 클래스)
(X_train, y_train), (X_test, y_test) = keras.datasets.cifar10.load_data()
X_train = X_train.astype('float32') / 255.0
X_test = X_test.astype('float32') / 255.0

# 데이터 증강을 포함한 CNN 모델
model = keras.Sequential([
    # 증강 레이어는 학습 시에만 작동
    data_augmentation,

    layers.Conv2D(32, 3, activation='relu', padding='same'),
    layers.MaxPooling2D(),
    layers.Dropout(0.2),

    layers.Conv2D(64, 3, activation='relu', padding='same'),
    layers.MaxPooling2D(),
    layers.Dropout(0.3),

    layers.Conv2D(128, 3, activation='relu', padding='same'),
    layers.MaxPooling2D(),
    layers.Dropout(0.4),

    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')  # 10개 클래스
])

model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

# 학습 - 데이터 증강이 자동으로 적용됨
history = model.fit(
    X_train, y_train,
    validation_data=(X_test, y_test),  # 테스트 데이터에는 증강 미적용
    epochs=30,
    batch_size=64,
    verbose=1
)

print(f"증강 사용 모델 - 테스트 정확도: {history.history['val_accuracy'][-1]:.4f}")

설명

이것이 하는 일: 이 코드는 Keras의 데이터 증강 레이어를 사용하여 이미지 데이터를 실시간으로 증강하면서 CNN 모델을 학습합니다. 첫 번째로, 데이터 증강 파이프라인을 정의합니다.

RandomFlip("horizontal")은 이미지를 50% 확률로 좌우 반전합니다. 자동차나 비행기는 좌우 반전해도 여전히 자동차와 비행기이므로 의미가 유지됩니다.

RandomRotation(0.1)은 ±36도 범위에서 무작위로 회전하는데, 실제 세상에서 물체를 다양한 각도에서 볼 수 있는 상황을 모방합니다. 그 다음으로, RandomZoomRandomTranslation을 추가합니다.

줌은 물체가 카메라에 가까이 있거나 멀리 있는 상황을, 이동은 물체가 이미지 중앙이 아닌 다른 위치에 있는 상황을 시뮬레이션합니다. 이런 변형들은 모델이 "이미지 정중앙에 있는 자동차"만 인식하는 것이 아니라 "어디에 있든 자동차는 자동차"라고 배우도록 만듭니다.

세 번째로, CIFAR-10 데이터를 로드하고 정규화합니다. 이 데이터셋은 32x32 크기의 작은 이미지 50,000장으로 구성되어 있어 데이터 증강의 효과를 테스트하기 좋습니다.

네 번째로, 증강 레이어를 모델의 첫 부분에 포함시킵니다. 이것이 핵심인데, 증강을 모델의 일부로 만들면 학습 시에는 자동으로 증강이 적용되고 예측 시에는 자동으로 비활성화됩니다.

별도의 코드 없이 Keras가 알아서 처리해줍니다. 다섯 번째로, CNN 구조를 정의합니다.

Conv2D 레이어로 이미지 특징을 추출하고, MaxPooling으로 크기를 줄이고, Dropout으로 과적합을 방지합니다. 이 모든 기법들이 함께 작동하여 강력한 모델을 만듭니다.

마지막으로, 모델을 학습시킵니다. 매 학습 배치마다 이미지들이 무작위로 변형되므로, 모델은 같은 이미지를 여러 번 보지만 매번 조금씩 다른 버전을 보게 됩니다.

예를 들어, 첫 번째 epoch에서는 자동차 이미지가 왼쪽으로 회전되어 나타나고, 두 번째 epoch에서는 오른쪽으로 확대되어 나타날 수 있습니다. 여러분이 이 코드를 사용하면 데이터가 부족한 상황에서도 좋은 성능의 모델을 만들 수 있습니다.

실무에서는 도메인에 맞는 증강 기법을 선택하는 것이 중요합니다. 의료 영상이라면 밝기나 대비 조절이 중요하고, 문서 이미지라면 회전이나 왜곡이 유용합니다.

실전 팁

💡 증강 강도를 너무 세게 하지 마세요. RandomRotation(0.5)처럼 180도 회전은 대부분의 경우 비현실적입니다. 실제 데이터 분포를 벗어나지 않는 선에서 조절하세요.

💡 테스트 시간 증강(Test Time Augmentation)도 고려하세요. 예측할 때 같은 이미지를 여러 번 증강해서 예측하고 평균을 내면 성능이 더 올라갑니다.

💡 CutMix, MixUp 같은 고급 증강 기법도 있습니다. 두 이미지를 섞어서 새로운 샘플을 만드는 방식으로 최신 연구에서 많이 사용됩니다.

💡 도메인별로 다른 증강을 적용하세요. 숫자 인식에서는 상하 반전이 부적절하지만(6과 9가 바뀜), 자연 이미지에서는 괜찮을 수 있습니다.

💡 Albumentations 라이브러리를 사용하면 더 다양하고 빠른 증강이 가능합니다. 특히 의료 영상이나 위성 이미지 같은 특수 도메인에 유용합니다.


6. 교차 검증으로 모델 성능 정확히 평가하기

시작하며

여러분이 시험을 한 번만 보고 실력을 판단하는 것과 여러 번 보고 평균을 내는 것 중 어느 쪽이 더 정확할까요? 당연히 후자입니다.

머신러닝 모델 평가도 마찬가지입니다. 많은 개발자들이 모델을 한 번 학습시키고 테스트해서 "정확도 95%!"라고 기뻐하지만, 데이터를 다르게 나누면 85%가 나올 수도 있습니다.

이런 불확실성은 특히 데이터가 적을 때 더 심각합니다. 이 문제를 해결하는 신뢰할 수 있는 방법이 교차 검증(Cross Validation)입니다.

데이터를 여러 방식으로 나누어 모델을 여러 번 평가함으로써 운에 좌우되지 않는 안정적인 성능 측정을 할 수 있습니다.

개요

간단히 말해서, 교차 검증은 데이터를 여러 조각(fold)으로 나누고 각 조각을 한 번씩 테스트 세트로 사용하여 모델을 여러 번 평가하는 기법입니다. 마치 반 학생들을 5개 조로 나누고 각 조가 한 번씩 시험 출제를 담당하는 것과 같습니다.

가장 일반적인 방법은 K-Fold 교차 검증입니다. 데이터를 K개(보통 5개나 10개)로 나누고, K번 반복하면서 매번 다른 조각을 테스트 세트로 사용합니다.

예를 들어, 5-Fold에서는 데이터를 5등분하여 첫 번째 실행에서는 1번 조각을 테스트로, 2~5번을 학습으로 사용하고, 두 번째 실행에서는 2번 조각을 테스트로 사용하는 식입니다. 5번 반복 후 결과를 평균 내면 매우 안정적인 성능 추정치를 얻을 수 있습니다.

전통적으로는 단순히 데이터를 70:30이나 80:20으로 한 번 나누어 평가했습니다. 하지만 이 방법은 어떤 데이터가 테스트 세트에 들어가느냐에 따라 결과가 크게 달라질 수 있습니다.

교차 검증의 핵심 특징은 세 가지입니다. 첫째, 모든 데이터가 한 번씩 테스트에 사용되므로 데이터를 효율적으로 활용합니다.

둘째, 성능의 평균뿐만 아니라 표준편차도 알 수 있어 모델의 안정성을 평가할 수 있습니다. 셋째, 데이터가 적을 때 특히 유용합니다.

이러한 특징들이 교차 검증을 모델 선택과 하이퍼파라미터 튜닝의 필수 도구로 만듭니다.

코드 예제

from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_breast_cancer
import numpy as np

# 유방암 진단 데이터 로드 (의료 데이터는 보통 적음)
data = load_breast_cancer()
X, y = data.data, data.target

print(f"전체 샘플 수: {len(X)}, 특성 수: {X.shape[1]}")

# 일반 RandomForest 모델
model = RandomForestClassifier(
    n_estimators=100,  # 트리 100개
    max_depth=10,  # 최대 깊이 제한으로 과적합 방지
    min_samples_split=5,
    random_state=42
)

# 5-Fold 교차 검증 - 클래스 비율을 유지하는 Stratified 버전
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)

# 교차 검증 실행 - 5번 학습하고 평가
scores = cross_val_score(
    model, X, y,
    cv=cv,  # 5-Fold 사용
    scoring='accuracy',  # 정확도로 평가
    n_jobs=-1  # 모든 CPU 코어 사용
)

# 결과 분석
print(f"\n각 Fold의 정확도: {scores}")
print(f"평균 정확도: {scores.mean():.4f}")
print(f"표준편차: {scores.std():.4f}")
print(f"95% 신뢰구간: [{scores.mean() - 2*scores.std():.4f}, {scores.mean() + 2*scores.std():.4f}]")

# 다른 지표로도 평가 가능
from sklearn.model_selection import cross_validate

# 여러 지표 동시 평가
scoring = ['accuracy', 'precision', 'recall', 'f1']
results = cross_validate(model, X, y, cv=cv, scoring=scoring, n_jobs=-1)

print(f"\n정밀도 평균: {results['test_precision'].mean():.4f}")
print(f"재현율 평균: {results['test_recall'].mean():.4f}")
print(f"F1 점수 평균: {results['test_f1'].mean():.4f}")

설명

이것이 하는 일: 이 코드는 K-Fold 교차 검증을 사용하여 RandomForest 모델의 성능을 여러 번 평가하고 통계적으로 신뢰할 수 있는 결과를 제공합니다. 첫 번째로, 유방암 진단 데이터를 로드합니다.

이 데이터는 569개 샘플로 비교적 적은 편이라 교차 검증의 중요성을 잘 보여줍니다. 30개의 특성(세포 크기, 모양 등)으로 양성/악성을 분류하는 의료 진단 문제입니다.

그 다음으로, RandomForest 모델을 정의합니다. max_depth=10min_samples_split=5 같은 파라미터는 과적합을 방지하기 위한 설정입니다.

이런 하이퍼파라미터를 선택할 때도 교차 검증을 사용하면 더 정확한 선택을 할 수 있습니다. 세 번째로, StratifiedKFold를 사용합니다.

일반 K-Fold와의 차이는 각 fold에서 클래스 비율을 원본 데이터와 동일하게 유지한다는 점입니다. 만약 원본 데이터가 악성 37%, 양성 63%라면 각 fold도 이 비율을 유지합니다.

이것은 불균형한 데이터셋에서 매우 중요합니다. 네 번째로, cross_val_score를 실행합니다.

이 함수는 내부적으로 다음을 수행합니다: (1) 데이터를 5개 fold로 나눔 → (2) 첫 번째 fold를 테스트로, 나머지 4개를 학습으로 사용하여 모델 학습 및 평가 → (3) 두 번째 fold를 테스트로 사용하여 반복 → ... → (5) 다섯 번째 fold까지 완료.

총 5개의 점수가 나옵니다. 다섯 번째로, 결과를 분석합니다.

평균 정확도는 모델의 전반적인 성능을, 표준편차는 안정성을 나타냅니다. 예를 들어, 평균이 0.95인데 표준편차가 0.01이면 "안정적으로 95% 성능", 표준편차가 0.10이면 "85%~100% 사이에서 불안정"하다고 해석할 수 있습니다.

마지막으로, cross_validate로 여러 지표를 동시에 평가합니다. 의료 진단처럼 민감한 분야에서는 정확도만으로는 부족하고, 정밀도(양성 예측의 정확성)와 재현율(실제 양성을 얼마나 찾았는지)도 중요합니다.

교차 검증을 통해 이 모든 지표의 신뢰구간을 알 수 있습니다. 여러분이 이 코드를 사용하면 "우리 모델은 95% ± 2%의 정확도를 가집니다"처럼 통계적으로 의미 있는 주장을 할 수 있습니다.

실무에서는 여러 모델이나 하이퍼파라미터를 비교할 때 교차 검증 결과를 기반으로 의사결정을 합니다.

실전 팁

💡 데이터가 적을 때는 K를 크게(10 이상), 데이터가 많을 때는 작게(3~5) 설정하세요. K가 클수록 정확하지만 계산 시간이 오래 걸립니다.

💡 시계열 데이터는 일반 K-Fold를 사용하면 안 됩니다! TimeSeriesSplit을 사용하여 과거 데이터로 학습하고 미래 데이터로 테스트해야 합니다.

💡 불균형 데이터셋(한 클래스가 극소수)에서는 반드시 StratifiedKFold를 사용하세요. 그렇지 않으면 어떤 fold에는 소수 클래스가 아예 없을 수 있습니다.

💡 교차 검증은 하이퍼파라미터 튜닝에 필수입니다. GridSearchCV나 RandomizedSearchCV는 내부적으로 교차 검증을 사용합니다.

💡 계산 시간이 오래 걸린다면 n_jobs=-1로 병렬 처리하거나, K를 줄이거나, ShuffleSplit 같은 빠른 대안을 고려하세요.


7. 배치 정규화로 학습 안정화하기

시작하며

여러분이 마라톤을 뛸 때 페이스를 일정하게 유지하는 것과 들쑥날쑥한 것 중 어느 쪽이 더 효율적일까요? 신경망 학습도 마찬가지로 각 레이어의 입력 분포가 안정적일 때 더 빠르고 효과적으로 학습됩니다.

많은 개발자들이 깊은 신경망을 학습시키면서 "학습이 너무 느려요", "학습이 불안정해요"라는 문제를 겪습니다. 이것은 내부 공변량 이동(Internal Covariate Shift)이라는 현상 때문입니다.

이 문제를 해결하는 강력한 방법이 배치 정규화(Batch Normalization)입니다. 각 레이어의 입력을 정규화하여 학습을 안정화하고 속도를 높이며, 부수적으로 과적합도 방지합니다.

개요

간단히 말해서, 배치 정규화는 각 레이어의 입력을 평균 0, 분산 1로 정규화하여 학습을 안정화하는 기법입니다. 마치 달리기 선수들의 출발선을 매번 똑같이 맞춰주는 것과 같습니다.

이 기법의 작동 원리는 이렇습니다. 각 미니배치마다 레이어의 출력을 받아서 평균을 빼고 표준편차로 나누어 정규화한 다음, 학습 가능한 파라미터(gamma, beta)로 스케일과 이동을 조정합니다.

이렇게 하면 각 레이어가 항상 비슷한 범위의 입력을 받게 되어 학습이 훨씬 안정적이고 빠릅니다. 실제로 배치 정규화를 사용하면 학습 속도가 2~10배 빨라질 수 있습니다.

전통적으로는 입력 데이터만 정규화하고 중간 레이어는 그대로 뒀습니다. 하지만 학습이 진행되면서 각 레이어의 입력 분포가 계속 변하는 것이 학습을 느리고 불안정하게 만든다는 것을 알게 되었습니다.

배치 정규화의 핵심 특징은 네 가지입니다. 첫째, 높은 학습률을 사용할 수 있어 학습이 빨라집니다.

둘째, 가중치 초기화에 덜 민감해집니다. 셋째, 정규화 효과가 있어 Dropout을 덜 써도 됩니다.

넷째, 학습과 예측 시 동작이 다릅니다 - 학습 시에는 배치 통계를, 예측 시에는 이동 평균을 사용합니다. 이러한 특징들이 배치 정규화를 현대 딥러닝의 표준 구성요소로 만들었습니다.

코드 예제

from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

# MNIST 데이터 로드
(X_train, y_train), (X_test, y_test) = keras.datasets.mnist.load_data()
X_train = X_train.reshape(-1, 784).astype('float32') / 255.0
X_test = X_test.reshape(-1, 784).astype('float32') / 255.0

# 배치 정규화 없는 깊은 네트워크
model_without_bn = keras.Sequential([
    layers.Dense(256, activation='relu', input_shape=(784,)),
    layers.Dense(256, activation='relu'),
    layers.Dense(256, activation='relu'),
    layers.Dense(256, activation='relu'),
    layers.Dense(10, activation='softmax')
])

# 배치 정규화를 사용하는 깊은 네트워크
model_with_bn = keras.Sequential([
    layers.Dense(256, input_shape=(784,)),
    layers.BatchNormalization(),  # 정규화 후
    layers.Activation('relu'),     # 활성화 함수

    layers.Dense(256),
    layers.BatchNormalization(),  # 매 레이어마다 추가
    layers.Activation('relu'),

    layers.Dense(256),
    layers.BatchNormalization(),
    layers.Activation('relu'),

    layers.Dense(256),
    layers.BatchNormalization(),
    layers.Activation('relu'),

    layers.Dense(10, activation='softmax')
])

# 두 모델 컴파일 - 같은 학습률 사용
for model in [model_without_bn, model_with_bn]:
    model.compile(
        optimizer=keras.optimizers.Adam(learning_rate=0.001),
        loss='sparse_categorical_crossentropy',
        metrics=['accuracy']
    )

# 배치 정규화 모델 학습
print("배치 정규화 모델 학습 중...")
history_with_bn = model_with_bn.fit(
    X_train, y_train,
    validation_split=0.2,
    epochs=10,
    batch_size=128,
    verbose=0
)

# 학습 속도 비교
print(f"\n배치 정규화 모델:")
print(f"5 epoch 후 검증 정확도: {history_with_bn.history['val_accuracy'][4]:.4f}")
print(f"10 epoch 후 검증 정확도: {history_with_bn.history['val_accuracy'][9]:.4f}")

# 배치 정규화 레이어의 파라미터 확인
bn_layer = model_with_bn.layers[1]  # 첫 번째 BatchNormalization 레이어
gamma, beta = bn_layer.get_weights()[0], bn_layer.get_weights()[1]
print(f"\nBatchNorm 학습된 파라미터 수: {len(gamma)}")

설명

이것이 하는 일: 이 코드는 배치 정규화를 사용하는 모델과 사용하지 않는 모델을 비교하여 배치 정규화의 효과를 확인합니다. 첫 번째로, MNIST 데이터를 로드하고 전처리합니다.

깊은 신경망(4개의 hidden layer)을 사용할 것이므로 배치 정규화의 효과를 명확히 볼 수 있습니다. 레이어가 깊을수록 내부 공변량 이동 문제가 심각해지기 때문입니다.

그 다음으로, 배치 정규화 없는 기본 모델을 정의합니다. 256개 뉴런을 가진 4개 레이어로 구성된 깊은 네트워크입니다.

이런 구조는 배치 정규화 없이는 학습이 느리고 불안정할 수 있습니다. 세 번째로, 배치 정규화를 사용하는 모델을 정의합니다.

핵심은 DenseBatchNormalizationActivation 순서입니다. 이 순서가 중요한데, 활성화 함수 전에 정규화를 적용하는 것이 일반적입니다.

일부 연구에서는 활성화 함수 후에 배치 정규화를 적용하기도 하지만, 대부분의 경우 전에 적용하는 것이 효과적입니다. 네 번째로, 배치 정규화 레이어가 내부적으로 하는 일을 이해해봅시다.

각 배치에 대해 (1) 평균 계산: μ = 1/m Σx, (2) 분산 계산: σ² = 1/m Σ(x-μ)², (3) 정규화: x̂ = (x-μ)/√(σ²+ε), (4) 스케일 및 이동: y = γx̂ + β. 여기서 γ(gamma)와 β(beta)는 학습 가능한 파라미터로, 모델이 필요하다면 정규화를 부분적으로 되돌릴 수 있게 합니다.

다섯 번째로, 두 모델을 같은 조건에서 학습시킵니다. 배치 정규화 모델은 학습이 훨씬 빠르고 안정적입니다.

예를 들어, 5 epoch 후에 배치 정규화 없는 모델이 90% 정확도에 도달할 때, 배치 정규화 모델은 이미 95%에 도달할 수 있습니다. 마지막으로, 배치 정규화 레이어의 학습된 파라미터를 확인합니다.

각 뉴런마다 gamma와 beta가 하나씩 있으므로, 256개 뉴런이면 256개의 gamma와 256개의 beta가 있습니다. 이 값들은 학습 중에 최적화되어 각 뉴런에 가장 적합한 정규화 정도를 찾아냅니다.

여러분이 이 코드를 사용하면 깊은 신경망을 더 빠르고 안정적으로 학습시킬 수 있습니다. 실무에서는 거의 모든 CNN과 많은 fully connected 네트워크에 배치 정규화가 사용됩니다.

단, RNN에서는 Layer Normalization이 더 효과적입니다.

실전 팁

💡 배치 정규화는 활성화 함수 전에 적용하는 것이 일반적입니다. Dense → BatchNorm → Activation 순서를 기억하세요.

💡 배치 크기가 너무 작으면(< 32) 배치 정규화가 불안정할 수 있습니다. 배치 통계가 부정확해지기 때문입니다. 이럴 때는 Layer Normalization이나 Group Normalization을 고려하세요.

💡 배치 정규화를 사용하면 Dropout을 덜 사용하거나 아예 제거해도 됩니다. 둘 다 정규화 효과가 있어서 중복될 수 있습니다.

💡 학습 모드(training=True)와 추론 모드(training=False)를 정확히 구분하세요. Keras는 자동으로 처리하지만, 직접 구현할 때는 주의해야 합니다.

💡 이미지 데이터의 CNN에서는 Conv2D → BatchNormalization → Activation 순서로 사용하면 매우 효과적입니다. 거의 모든 현대 CNN 아키텍처가 이 패턴을 사용합니다.


8. 학습률 스케줄링으로 최적점 찾기

시작하며

여러분이 보물찾기를 할 때 처음에는 큰 걸음으로 빠르게 탐색하다가, 보물에 가까워지면 작은 걸음으로 조심스럽게 접근하는 것이 효율적이지 않나요? 신경망 학습도 똑같은 원리입니다.

많은 개발자들이 고정된 학습률로 모델을 학습시키다가 "처음에는 빠르게 개선되다가 나중에는 정체돼요"라는 문제를 겪습니다. 또는 학습률이 너무 높아서 최적점 근처에서 진동만 하고 수렴하지 못하는 경우도 있습니다.

이 문제를 해결하는 똑똑한 방법이 학습률 스케줄링(Learning Rate Scheduling)입니다. 학습 진행 상황에 따라 학습률을 동적으로 조절하여 빠르면서도 정확한 학습을 달성합니다.

개요

간단히 말해서, 학습률 스케줄링은 학습 과정에서 학습률을 점진적으로 또는 조건부로 조절하는 기법입니다. 마치 자동차가 고속도로에서는 빠르게, 목적지 근처에서는 천천히 가는 것과 같습니다.

여러 가지 스케줄링 전략이 있습니다. Step Decay는 일정 epoch마다 학습률을 절반으로 줄입니다.

Exponential Decay는 지수적으로 감소시킵니다. Cosine Annealing은 코사인 함수를 따라 부드럽게 감소시킵니다.

ReduceLROnPlateau는 검증 손실이 개선되지 않으면 학습률을 줄입니다. 실무에서는 ReduceLROnPlateau와 Cosine Annealing이 가장 많이 사용되는데, 전자는 적응적이고 후자는 예측 가능하기 때문입니다.

전통적으로는 학습률을 고정값으로 사용했습니다. 하지만 연구 결과 학습률을 조절하는 것이 더 빠르고 좋은 결과를 낸다는 것이 밝혀졌습니다.

학습률 스케줄링의 핵심 특징은 세 가지입니다. 첫째, 초기에는 큰 학습률로 빠르게 학습하고 나중에는 작은 학습률로 미세 조정합니다.

둘째, 과적합을 방지하는 부수 효과가 있습니다. 셋째, 학습 곡선을 더 부드럽고 안정적으로 만듭니다.

이러한 특징들이 학습률 스케줄링을 최적화의 핵심 기법으로 만듭니다.

코드 예제

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.callbacks import ReduceLROnPlateau, LearningRateScheduler
import numpy as np
import math

# CIFAR-10 데이터 로드
(X_train, y_train), (X_test, y_test) = keras.datasets.cifar10.load_data()
X_train = X_train.astype('float32') / 255.0
X_test = X_test.astype('float32') / 255.0

# 간단한 CNN 모델
model = keras.Sequential([
    layers.Conv2D(32, 3, padding='same', activation='relu', input_shape=(32, 32, 3)),
    layers.BatchNormalization(),
    layers.MaxPooling2D(),

    layers.Conv2D(64, 3, padding='same', activation='relu'),
    layers.BatchNormalization(),
    layers.MaxPooling2D(),

    layers.Flatten(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(10, activation='softmax')
])

# 1. ReduceLROnPlateau - 성능이 정체되면 학습률 감소
reduce_lr = ReduceLROnPlateau(
    monitor='val_loss',  # 검증 손실 모니터링
    factor=0.5,  # 학습률을 절반으로 줄임
    patience=3,  # 3 epoch 동안 개선 없으면 감소
    min_lr=1e-7,  # 최소 학습률
    verbose=1  # 변경 시 출력
)

# 2. Cosine Annealing - 부드러운 감소
def cosine_annealing(epoch, lr):
    """코사인 함수로 학습률을 부드럽게 감소"""
    initial_lr = 0.001
    epochs = 50
    return initial_lr * 0.5 * (1 + math.cos(math.pi * epoch / epochs))

cosine_scheduler = LearningRateScheduler(cosine_annealing, verbose=0)

# 3. Step Decay - 일정 간격으로 감소
def step_decay(epoch, lr):
    """10 epoch마다 학습률을 0.5배로"""
    initial_lr = 0.001
    drop_every = 10
    return initial_lr * (0.5 ** (epoch // drop_every))

step_scheduler = LearningRateScheduler(step_decay, verbose=0)

# 모델 컴파일
model.compile(
    optimizer=keras.optimizers.Adam(learning_rate=0.001),  # 초기 학습률
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

# ReduceLROnPlateau 사용하여 학습
print("ReduceLROnPlateau 스케줄러 사용 중...")
history = model.fit(
    X_train, y_train,
    validation_data=(X_test, y_test),
    epochs=30,
    batch_size=64,
    callbacks=[reduce_lr],  # 콜백 적용
    verbose=1
)

# 학습률 변화 확인
print(f"\n최종 학습률: {model.optimizer.learning_rate.numpy():.8f}")

설명

이것이 하는 일: 이 코드는 세 가지 학습률 스케줄링 전략을 구현하고, 그 중 ReduceLROnPlateau를 실제로 적용하여 효과를 확인합니다. 첫 번째로, CIFAR-10 데이터와 CNN 모델을 준비합니다.

이 모델은 배치 정규화와 Dropout을 사용하여 이미 꽤 잘 작동하지만, 학습률 스케줄링을 추가하면 더 나은 결과를 얻을 수 있습니다. 그 다음으로, ReduceLROnPlateau 콜백을 정의합니다.

이것은 가장 실용적인 스케줄러 중 하나입니다. monitor='val_loss'는 검증 손실을 지켜보고, patience=3은 3 epoch 동안 개선이 없으면 행동한다는 뜻입니다.

factor=0.5는 학습률을 절반으로 줄입니다. 예를 들어, 학습률이 0.001이었다면 0.0005로, 또 정체되면 0.00025로 계속 줄어듭니다.

세 번째로, Cosine Annealing 스케줄러를 구현합니다. 이것은 학습률을 코사인 곡선을 따라 부드럽게 감소시킵니다.

수식은 lr = initial_lr * 0.5 * (1 + cos(π*epoch/total_epochs))인데, 이렇게 하면 처음에는 천천히 감소하다가 중간에 빠르게 감소하고 마지막에 다시 천천히 감소합니다. 이 패턴이 실험적으로 좋은 결과를 낸다고 알려져 있습니다.

네 번째로, Step Decay 스케줄러도 구현합니다. 이것은 가장 간단한 방법으로, 10 epoch마다 학습률을 절반으로 줄입니다.

예측 가능하고 구현이 쉬워서 전통적으로 많이 사용되었지만, 급격한 변화가 학습을 불안정하게 만들 수 있습니다. 다섯 번째로, ReduceLROnPlateau를 사용하여 모델을 학습시킵니다.

학습 중에 검증 손실이 3 epoch 동안 개선되지 않으면 자동으로 학습률이 줄어듭니다. 콘솔에 "Epoch 00015: ReduceLROnPlateau reducing learning rate to 0.0005" 같은 메시지가 출력되는 것을 볼 수 있습니다.

마지막으로, 최종 학습률을 확인합니다. 처음에 0.001로 시작했지만, 학습이 끝날 때쯤이면 0.0001 정도로 줄어들어 있을 것입니다.

이렇게 작은 학습률 덕분에 최적점 근처에서 미세한 조정이 가능해집니다. 여러분이 이 코드를 사용하면 같은 epoch 수로도 더 좋은 성능을 얻을 수 있습니다.

실무에서는 ReduceLROnPlateau와 Early Stopping을 함께 사용하는 것이 일반적입니다. 학습률이 너무 작아지면 더 이상 개선이 없으므로 멈추는 것이 효율적입니다.

실전 팁

💡 ReduceLROnPlateau의 patience는 Early Stopping의 patience보다 작게 설정하세요. 예를 들어, ReduceLR은 patience=3, EarlyStopping은 patience=10으로 하면 학습률을 먼저 줄여보고 그래도 안 되면 멈춥니다.

💡 Warm-up 전략도 고려하세요. 처음 몇 epoch는 작은 학습률로 시작했다가 점진적으로 올리는 방법으로, 큰 모델에서 효과적입니다.

💡 Cosine Annealing with Warm Restarts는 주기적으로 학습률을 리셋하는 방법으로, local minimum에서 벗어나는 데 도움이 됩니다.

💡 Transfer Learning을 할 때는 pre-trained 레이어와 새 레이어에 다른 학습률을 적용하세요. 새 레이어는 큰 학습률, pre-trained는 작은 학습률을 사용합니다.

💡 학습률 파인더(LR Finder)를 사용하면 최적의 초기 학습률을 자동으로 찾을 수 있습니다. fastai 라이브러리가 이 기능을 제공합니다.


#Python#Overfitting#EarlyStopping#ModelTraining#MachineLearning#AI

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.