본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
이미지 로딩 중...
AI Generated
2025. 12. 6. · 13 Views
Gradient Boosting 원리 이해
머신러닝에서 가장 강력한 앙상블 기법 중 하나인 Gradient Boosting의 핵심 원리를 초급자도 이해할 수 있도록 단계별로 설명합니다. 약한 학습기들이 어떻게 협력하여 강력한 예측 모델을 만드는지 알아봅니다.
목차
- Boosting의_기본_개념
- 잔차를_학습하는_원리
- Gradient의_의미와_손실함수
- Learning_Rate의_역할
- 트리_깊이와_복잡도_조절
- XGBoost와_성능_최적화
- 과적합_방지_전략
- 실전_하이퍼파라미터_튜닝
1. Boosting의 기본 개념
어느 날 김개발 씨는 데이터 사이언스 팀에서 예측 모델을 만들고 있었습니다. 단일 의사결정 트리로는 정확도가 70%를 넘지 못했습니다.
"혹시 여러 개의 약한 모델을 조합하면 더 나아질까요?" 팀장님이 Boosting이라는 개념을 소개해 주었습니다.
Boosting은 여러 개의 약한 학습기를 순차적으로 학습시켜 강한 학습기를 만드는 앙상블 기법입니다. 마치 릴레이 경주에서 각 주자가 앞선 주자의 부족한 부분을 보완하며 달리는 것과 같습니다.
각 학습기는 이전 학습기가 틀린 부분에 집중하여 전체적인 예측 성능을 높입니다.
다음 코드를 살펴봅시다.
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
# 샘플 데이터 생성
X, y = make_classification(n_samples=1000, n_features=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# Gradient Boosting 모델 생성
# n_estimators: 순차적으로 학습할 약한 학습기의 개수
model = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1)
model.fit(X_train, y_train)
# 정확도 확인
accuracy = model.score(X_test, y_test)
print(f"모델 정확도: {accuracy:.4f}")
김개발 씨는 입사 6개월 차 데이터 분석가입니다. 오늘 맡은 업무는 고객 이탈을 예측하는 모델을 만드는 것이었습니다.
처음에는 단순한 의사결정 트리 하나로 시도해 보았지만, 결과는 만족스럽지 않았습니다. 선배 개발자 박시니어 씨가 다가와 화면을 살펴봅니다.
"단일 모델로는 한계가 있어요. Boosting을 써보는 게 어때요?" 그렇다면 Boosting이란 정확히 무엇일까요?
쉽게 비유하자면, Boosting은 마치 시험 오답 노트를 만드는 과정과 같습니다. 첫 번째 시험에서 틀린 문제를 집중적으로 공부하고, 두 번째 시험에서 또 틀린 문제를 다시 공부합니다.
이 과정을 반복하면 점점 더 많은 문제를 맞출 수 있게 됩니다. Boosting이 없던 시절에는 어땠을까요?
개발자들은 단일 모델의 성능 한계에 부딪혔습니다. 모델을 복잡하게 만들면 과적합이 발생하고, 단순하게 만들면 정확도가 떨어졌습니다.
이 딜레마를 해결할 방법이 필요했습니다. 바로 이런 문제를 해결하기 위해 Boosting이 등장했습니다.
Boosting을 사용하면 단순한 모델들을 조합하여 복잡한 패턴을 학습할 수 있습니다. 또한 각 단계에서 이전의 실수를 보완하므로 점진적으로 성능이 향상됩니다.
무엇보다 과적합 위험을 줄이면서도 높은 예측력을 얻을 수 있습니다. 위의 코드를 살펴보겠습니다.
먼저 GradientBoostingClassifier를 임포트합니다. 이것이 우리가 사용할 핵심 클래스입니다.
n_estimators=100은 100개의 약한 학습기를 순차적으로 학습시키겠다는 의미입니다. learning_rate=0.1은 각 학습기의 기여도를 조절하는 값입니다.
실제 현업에서는 어떻게 활용할까요? 예를 들어 은행에서 대출 심사 모델을 개발한다고 가정해봅시다.
고객의 신용 정보, 소득, 부채 비율 등 다양한 변수를 바탕으로 부실 가능성을 예측해야 합니다. Boosting을 활용하면 단일 모델보다 훨씬 정확한 예측이 가능합니다.
하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 너무 많은 학습기를 사용하는 것입니다.
이렇게 하면 과적합이 발생하고 학습 시간도 길어집니다. 따라서 적절한 n_estimators와 learning_rate를 찾는 것이 중요합니다.
다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 조언대로 Boosting을 적용한 결과, 정확도가 85%까지 올라갔습니다.
"와, 이렇게 차이가 나다니!" Boosting의 기본 개념을 이해하면 더 강력한 머신러닝 모델을 만들 수 있습니다. 다음 장에서는 Gradient Boosting이 정확히 어떤 원리로 작동하는지 알아보겠습니다.
실전 팁
💡 - n_estimators는 50에서 200 사이로 시작하고 교차 검증으로 최적값을 찾으세요
- learning_rate를 낮추면 더 많은 학습기가 필요하지만 일반화 성능이 좋아질 수 있습니다
2. 잔차를 학습하는 원리
김개발 씨는 Boosting의 기본 개념을 이해했지만, 한 가지 의문이 들었습니다. "각 학습기가 이전의 실수를 보완한다고 했는데, 구체적으로 어떻게 하는 거죠?" 박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다.
Gradient Boosting의 핵심은 **잔차(residual)**를 학습하는 것입니다. 첫 번째 모델이 예측한 값과 실제 값의 차이, 즉 오차를 다음 모델이 학습합니다.
마치 과녁에서 빗나간 방향과 거리를 계산하여 다음 화살을 쏠 때 보정하는 것과 같습니다.
다음 코드를 살펴봅시다.
import numpy as np
from sklearn.tree import DecisionTreeRegressor
# 실제 값
y_actual = np.array([100, 150, 200, 250, 300])
X = np.array([[1], [2], [3], [4], [5]])
# 첫 번째 모델: 단순 예측
model_1 = DecisionTreeRegressor(max_depth=1)
model_1.fit(X, y_actual)
pred_1 = model_1.predict(X)
# 잔차 계산: 실제값 - 예측값
residual_1 = y_actual - pred_1
print(f"첫 번째 예측: {pred_1}")
print(f"잔차(오차): {residual_1}")
# 두 번째 모델: 잔차를 학습
model_2 = DecisionTreeRegressor(max_depth=1)
model_2.fit(X, residual_1)
pred_2 = model_2.predict(X)
# 최종 예측: 첫 번째 예측 + 두 번째 예측
final_pred = pred_1 + pred_2
print(f"최종 예측: {final_pred}")
박시니어 씨가 화이트보드 앞에 섰습니다. "자, 간단한 예를 들어볼게요.
집값을 예측하는 모델을 만든다고 해봅시다." 첫 번째 모델이 어떤 집의 가격을 3억으로 예측했는데, 실제 가격은 3억 5천만원이었습니다. 여기서 오차, 즉 잔차는 5천만원입니다.
이 잔차가 바로 다음 모델이 학습해야 할 대상입니다. 그렇다면 왜 잔차를 학습할까요?
쉽게 비유하자면, 양궁 선수가 과녁을 맞추는 과정과 같습니다. 첫 번째 화살이 과녁의 왼쪽으로 10cm 빗나갔다면, 다음 화살을 쏠 때는 오른쪽으로 10cm 보정합니다.
이것이 바로 잔차를 학습하는 원리입니다. 위의 코드에서 이 과정이 어떻게 구현되는지 살펴봅시다.
먼저 첫 번째 의사결정 트리가 원본 데이터를 학습합니다. 하지만 이 모델은 단순하기 때문에 완벽한 예측을 하지 못합니다.
그래서 y_actual - pred_1으로 잔차를 계산합니다. 이 잔차가 두 번째 모델의 학습 대상이 됩니다.
두 번째 모델은 "첫 번째 모델이 얼마나 틀렸는지"를 예측합니다. 최종 예측은 첫 번째 예측에 두 번째 예측을 더한 값입니다.
이렇게 하면 첫 번째 모델의 오차가 보정됩니다. 이 과정을 수십 번, 수백 번 반복하면 어떻게 될까요?
각 단계마다 남아있는 오차가 조금씩 줄어듭니다. 마치 조각가가 돌덩이를 조금씩 깎아내어 정교한 조각상을 만드는 것처럼, Gradient Boosting도 오차를 조금씩 줄여나가며 정확한 예측 모델을 완성합니다.
실무에서 이 원리가 왜 중요할까요? 잔차 학습 원리를 이해하면 모델의 동작 방식을 파악할 수 있습니다.
예를 들어, 특정 데이터 포인트에서 오차가 계속 줄어들지 않는다면 그 데이터에 이상치가 있거나 모델이 학습하기 어려운 패턴이 있다는 것을 알 수 있습니다. 하지만 주의할 점이 있습니다.
잔차를 너무 완벽하게 학습하려고 하면 노이즈까지 학습하게 됩니다. 이것이 바로 과적합입니다.
그래서 learning_rate라는 장치를 사용하여 각 모델의 기여도를 조절합니다. 김개발 씨가 고개를 끄덕였습니다.
"아, 그래서 오차를 보정하는 거군요! 마치 시험에서 틀린 문제를 다시 푸는 것과 같네요." 잔차 학습은 Gradient Boosting의 핵심 원리입니다.
이 개념을 이해하면 다음에 배울 Gradient의 의미도 쉽게 이해할 수 있습니다.
실전 팁
💡 - 잔차는 실제값에서 예측값을 뺀 값으로, 모델이 얼마나 틀렸는지를 나타냅니다
- 각 단계의 모델은 독립적으로 학습하지 않고, 이전 모델의 오차를 보완합니다
3. Gradient의 의미와 손실함수
"그런데 왜 Gradient Boosting이라고 부르나요? Gradient가 뭔가요?" 김개발 씨의 질문에 박시니어 씨가 미소를 지었습니다.
"좋은 질문이에요. 사실 이게 핵심이거든요.
경사하강법 알아요?"
Gradient는 손실 함수의 기울기를 의미합니다. Gradient Boosting에서 잔차는 사실 손실 함수의 **음의 기울기(negative gradient)**입니다.
모델은 이 기울기 방향으로 조금씩 이동하며 손실을 최소화합니다. 마치 산에서 가장 가파른 내리막길을 따라 골짜기로 내려가는 것과 같습니다.
다음 코드를 살펴봅시다.
import numpy as np
# 평균제곱오차(MSE) 손실 함수
def mse_loss(y_true, y_pred):
return np.mean((y_true - y_pred) ** 2)
# MSE의 음의 기울기 = 잔차
def negative_gradient(y_true, y_pred):
# MSE를 y_pred로 미분하면 -2(y_true - y_pred)
# 상수를 무시하면 결국 잔차(y_true - y_pred)
return y_true - y_pred
y_true = np.array([100, 150, 200])
y_pred = np.array([90, 160, 180])
# 현재 손실
loss = mse_loss(y_true, y_pred)
print(f"현재 손실: {loss}")
# 음의 기울기 (= 잔차)
gradient = negative_gradient(y_true, y_pred)
print(f"음의 기울기(잔차): {gradient}")
# 이 방향으로 이동하면 손실이 줄어듦
new_pred = y_pred + 0.5 * gradient # learning_rate = 0.5
new_loss = mse_loss(y_true, new_pred)
print(f"업데이트 후 손실: {new_loss}")
박시니어 씨가 화이트보드에 산 모양을 그렸습니다. "경사하강법 기억나요?
산꼭대기에서 골짜기로 내려가는 가장 빠른 길을 찾는 방법이요." 김개발 씨가 대답했습니다. "네, 기울기가 가장 가파른 방향으로 조금씩 이동하는 거잖아요." "맞아요.
Gradient Boosting도 똑같은 원리예요." **손실 함수(Loss Function)**는 우리 모델이 얼마나 틀렸는지를 측정하는 함수입니다. 가장 흔히 쓰이는 것이 평균제곱오차(MSE)입니다.
이 값이 작을수록 모델의 예측이 정확하다는 뜻입니다. 그렇다면 Gradient는 무엇일까요?
Gradient는 손실 함수의 기울기입니다. 쉽게 말해, "예측값을 어느 방향으로 바꾸면 손실이 줄어들까?"에 대한 답입니다.
놀랍게도, MSE의 음의 기울기를 계산하면 바로 잔차가 나옵니다. 위의 코드에서 이것을 확인할 수 있습니다.
negative_gradient 함수를 보면, y_true에서 y_pred를 뺀 값, 즉 잔차를 반환합니다. 이것이 바로 MSE 손실 함수의 음의 기울기입니다.
수학적으로 증명할 수 있지만, 직관적으로도 이해가 됩니다. 예측값이 실제값보다 작으면 잔차는 양수입니다.
이 경우 예측값을 높여야 합니다. 반대로 예측값이 실제값보다 크면 잔차는 음수이고, 예측값을 낮춰야 합니다.
잔차의 방향이 곧 예측을 수정해야 할 방향입니다. learning_rate는 이 이동의 보폭을 결정합니다.
코드에서 0.5 * gradient를 더하는 것을 볼 수 있습니다. learning_rate가 0.5이므로 기울기 방향으로 절반만 이동합니다.
너무 크게 이동하면 최적점을 지나쳐버릴 수 있고, 너무 작게 이동하면 수렴이 느려집니다. 왜 굳이 Gradient를 사용할까요?
Gradient를 사용하면 MSE 외에 다른 손실 함수도 사용할 수 있습니다. 분류 문제에서는 로그 손실을 사용하고, 이상치에 강건한 모델을 만들고 싶으면 절대오차를 사용할 수 있습니다.
각 손실 함수의 기울기만 계산하면 됩니다. 김개발 씨가 감탄했습니다.
"그래서 Gradient Boosting이라고 부르는 거군요! 기울기를 따라 부스팅한다는 뜻이네요." Gradient의 의미를 이해하면 모델의 수학적 원리를 깊이 이해할 수 있습니다.
이것이 Gradient Boosting의 이론적 토대입니다.
실전 팁
💡 - MSE를 사용하면 음의 기울기가 정확히 잔차와 같습니다
- 다른 손실 함수를 사용하면 다른 형태의 기울기가 계산됩니다
4. Learning Rate의 역할
"저번에 learning_rate를 조절해보니까 결과가 많이 달라지더라고요. 근데 정확히 어떤 역할을 하는 건가요?" 김개발 씨가 실험 결과를 보여주며 물었습니다.
화면에는 learning_rate에 따라 달라지는 성능 그래프가 표시되어 있었습니다.
Learning Rate는 각 트리가 최종 예측에 기여하는 정도를 조절하는 하이퍼파라미터입니다. 낮은 값을 사용하면 각 트리의 영향력이 작아져서 더 많은 트리가 필요하지만, 일반화 성능이 좋아집니다.
마치 조심스럽게 한 걸음씩 걸으면 목적지에 더 정확히 도착하는 것과 같습니다.
다음 코드를 살펴봅시다.
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
import numpy as np
X, y = make_regression(n_samples=1000, n_features=10, noise=10, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# 높은 learning_rate: 빠르지만 불안정
model_high = GradientBoostingRegressor(n_estimators=50, learning_rate=1.0)
model_high.fit(X_train, y_train)
print(f"높은 LR(1.0) 점수: {model_high.score(X_test, y_test):.4f}")
# 낮은 learning_rate: 느리지만 안정적
model_low = GradientBoostingRegressor(n_estimators=200, learning_rate=0.1)
model_low.fit(X_train, y_train)
print(f"낮은 LR(0.1) 점수: {model_low.score(X_test, y_test):.4f}")
# 매우 낮은 learning_rate + 많은 트리
model_lower = GradientBoostingRegressor(n_estimators=500, learning_rate=0.05)
model_lower.fit(X_train, y_train)
print(f"더 낮은 LR(0.05) 점수: {model_lower.score(X_test, y_test):.4f}")
박시니어 씨가 화이트보드에 계단을 그렸습니다. "learning_rate를 계단 폭이라고 생각해봐요." 계단 폭이 넓으면 빨리 내려갈 수 있지만, 정확히 원하는 위치에 멈추기 어렵습니다.
반면 계단 폭이 좁으면 더 많은 걸음이 필요하지만, 원하는 위치에 정확히 멈출 수 있습니다. Gradient Boosting에서 learning_rate가 바로 이 계단 폭입니다.
각 트리가 예측을 수정할 때, learning_rate만큼 곱해서 더합니다. learning_rate가 0.1이면 트리의 예측값의 10%만 최종 예측에 반영됩니다.
나머지 90%는 다음 트리들이 채워나갑니다. 왜 이렇게 할까요?
모든 트리가 100%씩 기여하면 각 트리가 너무 큰 영향을 미칩니다. 훈련 데이터의 노이즈까지 학습하게 되어 과적합이 발생합니다.
learning_rate를 낮추면 각 트리의 영향력이 분산되어 더 안정적인 예측이 가능합니다. 위의 코드에서 세 가지 경우를 비교했습니다.
learning_rate가 1.0일 때는 50개의 트리만으로도 학습이 빠르게 진행됩니다. 하지만 성능이 최적이 아닐 수 있습니다.
learning_rate가 0.1일 때는 200개의 트리가 필요하지만, 일반적으로 더 좋은 성능을 보입니다. learning_rate와 n_estimators는 트레이드오프 관계입니다.
learning_rate를 낮추면 n_estimators를 높여야 합니다. 일반적으로 learning_rate는 0.01에서 0.3 사이의 값을 사용합니다.
실무에서는 0.1을 기본값으로 시작하는 경우가 많습니다. 하지만 주의할 점이 있습니다.
learning_rate가 너무 낮으면 학습 시간이 매우 길어집니다. 또한 너무 많은 트리를 사용하면 메모리 문제가 발생할 수 있습니다.
적절한 균형점을 찾는 것이 중요합니다. 김개발 씨가 메모를 했습니다.
"그럼 learning_rate를 낮추고 n_estimators를 높이는 게 무조건 좋은 건가요?" "꼭 그런 건 아니에요. 교차 검증으로 최적의 조합을 찾아야 해요." learning_rate는 모델의 안정성과 학습 속도를 조절하는 핵심 하이퍼파라미터입니다.
이 값을 잘 조절하면 과적합을 방지하면서도 높은 성능을 얻을 수 있습니다.
실전 팁
💡 - learning_rate는 0.01에서 0.3 사이로 시작하세요
- learning_rate를 절반으로 줄이면 n_estimators를 두 배로 늘려야 비슷한 성능을 얻습니다
5. 트리 깊이와 복잡도 조절
김개발 씨가 모델을 학습시키는데 시간이 너무 오래 걸렸습니다. "트리 개수도 줄이고, learning_rate도 높였는데 왜 이렇게 느리죠?" 로그를 살펴보니 각 트리의 깊이가 기본값인 3으로 설정되어 있었습니다.
max_depth는 각 트리의 최대 깊이를 제한하는 파라미터입니다. Gradient Boosting에서는 보통 얕은 트리를 사용합니다.
깊이가 3이면 최대 8개의 리프 노드를 가질 수 있습니다. 마치 퀴즈에서 예/아니오 질문을 3번만 하는 것과 같습니다.
다음 코드를 살펴봅시다.
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import make_classification
from sklearn.model_selection import cross_val_score
import numpy as np
X, y = make_classification(n_samples=2000, n_features=20, random_state=42)
# 다양한 트리 깊이 비교
depths = [1, 2, 3, 5, 7]
for depth in depths:
model = GradientBoostingClassifier(
n_estimators=100,
max_depth=depth,
learning_rate=0.1,
random_state=42
)
# 교차 검증으로 성능 측정
scores = cross_val_score(model, X, y, cv=5)
print(f"max_depth={depth}: 평균 정확도 {scores.mean():.4f} (+/- {scores.std()*2:.4f})")
# 실무에서 자주 쓰는 설정
best_model = GradientBoostingClassifier(
n_estimators=100,
max_depth=3, # 얕은 트리
min_samples_split=10, # 분할에 필요한 최소 샘플 수
min_samples_leaf=5 # 리프 노드의 최소 샘플 수
)
박시니어 씨가 설명했습니다. "Gradient Boosting에서 각 트리는 '약한 학습기'여야 해요.
너무 복잡하면 안 돼요." Random Forest와 달리 Gradient Boosting은 얕은 트리를 선호합니다. Random Forest에서는 각 트리가 독립적으로 예측하므로 깊고 복잡한 트리를 사용합니다.
하지만 Gradient Boosting에서는 트리들이 순차적으로 오차를 보완하므로, 각 트리는 단순해야 합니다. 왜 얕은 트리가 좋을까요?
쉽게 비유하자면, 여러 명의 전문가가 의견을 내는 상황을 생각해봅시다. 한 전문가가 모든 것을 결정하는 것보다, 여러 전문가가 각자의 의견을 조금씩 더하는 것이 더 균형 잡힌 결론을 만듭니다.
위의 코드에서 다양한 max_depth 값을 비교했습니다. 일반적으로 max_depth가 3에서 5 사이일 때 좋은 성능을 보입니다.
깊이가 1인 트리는 **결정 그루터기(decision stump)**라고 부르며, 가장 단순한 형태입니다. 깊이가 너무 깊으면 각 트리가 훈련 데이터에 과적합될 수 있습니다.
min_samples_split과 min_samples_leaf도 중요합니다. min_samples_split은 노드를 분할하기 위해 필요한 최소 샘플 수입니다.
min_samples_leaf는 리프 노드에 있어야 하는 최소 샘플 수입니다. 이 값들을 높이면 트리가 더 단순해지고 과적합이 줄어듭니다.
실무에서는 어떻게 설정할까요? 대부분의 경우 max_depth=3으로 시작합니다.
데이터가 복잡하면 4나 5로 늘리고, 과적합이 의심되면 2로 줄입니다. 교차 검증을 통해 최적의 값을 찾는 것이 가장 확실합니다.
김개발 씨가 깨달았습니다. "아, 그래서 기본값이 3인 거군요!
트리 하나하나는 약하지만, 많이 모이면 강해지는 거네요." "맞아요. 그게 바로 앙상블의 힘이에요." 트리 깊이를 적절히 조절하면 과적합을 방지하면서도 충분한 표현력을 가진 모델을 만들 수 있습니다.
다음 장에서는 이러한 설정들을 종합하여 XGBoost를 살펴보겠습니다.
실전 팁
💡 - max_depth는 3에서 시작하여 교차 검증으로 조절하세요
- min_samples_leaf를 5 이상으로 설정하면 노이즈에 강건해집니다
6. XGBoost와 성능 최적화
"팀장님이 XGBoost를 써보라고 하셨는데, sklearn의 GradientBoosting과 뭐가 다른 거예요?" 김개발 씨가 물었습니다. 박시니어 씨가 의자를 끌어당겼습니다.
"XGBoost는 Gradient Boosting의 최적화된 구현이에요. 속도도 빠르고 기능도 많아요."
**XGBoost(eXtreme Gradient Boosting)**는 Gradient Boosting을 더 빠르고 효율적으로 구현한 라이브러리입니다. 병렬 처리, 정규화, 결측치 자동 처리 등의 기능을 제공합니다.
캐글 대회에서 수많은 우승을 거둔 검증된 도구입니다.
다음 코드를 살펴봅시다.
import xgboost as xgb
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split, GridSearchCV
import numpy as np
X, y = make_classification(n_samples=5000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# XGBoost 모델 생성
model = xgb.XGBClassifier(
n_estimators=100,
max_depth=3,
learning_rate=0.1,
subsample=0.8, # 각 트리에 사용할 샘플 비율
colsample_bytree=0.8, # 각 트리에 사용할 피처 비율
reg_alpha=0.1, # L1 정규화
reg_lambda=1.0, # L2 정규화
random_state=42
)
model.fit(X_train, y_train, eval_set=[(X_test, y_test)], verbose=False)
# 성능 평가
accuracy = model.score(X_test, y_test)
print(f"XGBoost 정확도: {accuracy:.4f}")
# 피처 중요도 확인
importance = model.feature_importances_
print(f"상위 5개 중요 피처: {np.argsort(importance)[-5:][::-1]}")
박시니어 씨가 XGBoost의 역사를 간략히 설명했습니다. "2014년에 Tianqi Chen이라는 분이 만들었어요.
그 이후로 캐글 대회를 휩쓸었죠." XGBoost가 sklearn의 GradientBoosting보다 뛰어난 점은 무엇일까요? 첫째, 속도입니다.
XGBoost는 병렬 처리를 지원하여 대용량 데이터셋에서도 빠르게 학습합니다. CPU 코어를 여러 개 사용하여 트리 노드 분할을 동시에 계산합니다.
둘째, 정규화 기능이 내장되어 있습니다. 위의 코드에서 reg_alpha와 reg_lambda를 볼 수 있습니다.
reg_alpha는 L1 정규화(Lasso)이고, reg_lambda는 L2 정규화(Ridge)입니다. 이 값들이 크면 트리가 더 단순해져서 과적합이 줄어듭니다.
셋째, subsample과 colsample_bytree입니다. subsample은 각 트리를 학습할 때 전체 데이터의 일부만 사용합니다.
colsample_bytree는 각 트리에서 일부 피처만 사용합니다. 이것은 Random Forest의 아이디어를 차용한 것으로, 다양성을 높여 과적합을 방지합니다.
넷째, 결측치 자동 처리입니다. 데이터에 결측치가 있어도 XGBoost는 자동으로 처리합니다.
결측치가 있는 샘플을 왼쪽 또는 오른쪽 자식 노드로 보낼지 학습 과정에서 결정합니다. 실무에서 XGBoost를 사용할 때 주의할 점은 무엇일까요?
하이퍼파라미터가 많아서 튜닝이 복잡할 수 있습니다. GridSearchCV나 RandomizedSearchCV를 사용하여 최적의 조합을 찾는 것이 좋습니다.
또한 early_stopping을 활용하면 과적합을 방지하면서 학습 시간도 줄일 수 있습니다. 김개발 씨가 바로 실습을 시작했습니다.
"와, sklearn보다 확실히 빠르네요! 그리고 피처 중요도도 바로 볼 수 있어서 편해요." XGBoost는 Gradient Boosting의 실무 표준이 되었습니다.
LightGBM이나 CatBoost 같은 변형들도 있지만, XGBoost가 가장 널리 사용됩니다.
실전 팁
💡 - early_stopping_rounds를 사용하면 과적합을 자동으로 방지할 수 있습니다
- eval_set으로 검증 데이터를 설정하면 학습 과정을 모니터링할 수 있습니다
7. 과적합 방지 전략
김개발 씨의 모델이 훈련 데이터에서는 99% 정확도를 보였지만, 테스트 데이터에서는 75%로 떨어졌습니다. "분명히 잘 학습된 것 같은데 왜 이런 거죠?" 박시니어 씨가 진지한 표정으로 말했습니다.
"전형적인 과적합이에요."
**과적합(Overfitting)**은 모델이 훈련 데이터를 너무 완벽하게 외워버려서 새로운 데이터에 일반화하지 못하는 현상입니다. Gradient Boosting에서는 여러 가지 방법으로 과적합을 방지할 수 있습니다.
적절한 정규화와 조기 종료가 핵심입니다.
다음 코드를 살펴봅시다.
import xgboost as xgb
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
X, y = make_classification(n_samples=1000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# Early Stopping 적용
model = xgb.XGBClassifier(
n_estimators=1000, # 최대 1000개까지
max_depth=3,
learning_rate=0.1,
subsample=0.8,
colsample_bytree=0.8,
reg_lambda=1.0,
early_stopping_rounds=20, # 20번 연속 개선 없으면 중단
random_state=42
)
# 검증 데이터로 모니터링
model.fit(
X_train, y_train,
eval_set=[(X_train, y_train), (X_test, y_test)],
verbose=False
)
print(f"최적 트리 개수: {model.best_iteration}")
print(f"테스트 정확도: {model.score(X_test, y_test):.4f}")
박시니어 씨가 그래프를 그렸습니다. X축은 트리 개수, Y축은 오차입니다.
훈련 오차는 계속 감소하지만, 검증 오차는 어느 순간부터 증가합니다. "이 지점이 바로 과적합이 시작되는 곳이에요." Gradient Boosting은 본질적으로 과적합에 취약합니다.
왜냐하면 각 트리가 이전 트리의 오차를 보완하도록 학습되기 때문입니다. 충분히 많은 트리를 사용하면 훈련 데이터의 오차를 거의 0으로 만들 수 있습니다.
하지만 이것은 데이터의 노이즈까지 학습했다는 뜻입니다. Early Stopping이 가장 효과적인 해결책입니다.
위의 코드에서 early_stopping_rounds=20을 설정했습니다. 검증 데이터의 성능이 20번 연속으로 개선되지 않으면 학습을 중단합니다.
1000개까지 학습할 수 있지만, 실제로는 최적의 시점에서 멈춥니다. 정규화도 중요합니다.
reg_lambda(L2 정규화)와 reg_alpha(L1 정규화)를 사용하면 모델의 복잡도에 페널티를 부여합니다. 큰 가중치를 가진 모델은 손실 함수 값이 커지므로, 모델이 더 단순해지도록 유도됩니다.
subsample과 colsample_bytree도 과적합 방지에 도움이 됩니다. 전체 데이터와 피처의 일부만 사용하면 각 트리가 다양한 패턴을 학습하게 됩니다.
이것은 Random Forest의 배깅 아이디어를 차용한 것입니다. 실무에서 권장하는 과적합 방지 전략은 다음과 같습니다.
첫째, 충분히 많은 트리를 설정하고 early stopping을 사용하세요. 둘째, learning_rate를 낮추고 트리 개수를 늘리세요.
셋째, max_depth를 3에서 5 사이로 제한하세요. 넷째, subsample과 colsample_bytree를 0.7에서 0.9 사이로 설정하세요.
김개발 씨가 early stopping을 적용하자 테스트 정확도가 85%로 올라갔습니다. "아, 트리를 더 많이 추가하는 게 항상 좋은 건 아니군요!" 과적합 방지는 머신러닝에서 가장 중요한 기술 중 하나입니다.
Gradient Boosting의 다양한 정규화 기법을 잘 활용하면 견고한 모델을 만들 수 있습니다.
실전 팁
💡 - 항상 훈련/검증/테스트 세트를 분리하고 검증 세트로 모니터링하세요
- early_stopping_rounds는 10에서 50 사이로 설정하는 것이 일반적입니다
8. 실전 하이퍼파라미터 튜닝
"자, 이제 이론은 충분히 배웠으니 실전으로 가볼까요?" 박시니어 씨가 말했습니다. "실무에서는 하이퍼파라미터 튜닝이 모델 성능을 좌우해요.
어떻게 효율적으로 튜닝하는지 알려줄게요."
하이퍼파라미터 튜닝은 최적의 모델 설정을 찾는 과정입니다. Grid Search는 모든 조합을 시도하고, Random Search는 무작위로 샘플링합니다.
최근에는 Optuna 같은 베이지안 최적화 도구도 많이 사용됩니다.
다음 코드를 살펴봅시다.
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint, uniform
import xgboost as xgb
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
X, y = make_classification(n_samples=2000, n_features=20, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
# 탐색할 파라미터 범위 정의
param_dist = {
'n_estimators': randint(50, 300),
'max_depth': randint(2, 7),
'learning_rate': uniform(0.01, 0.29), # 0.01 ~ 0.30
'subsample': uniform(0.6, 0.4), # 0.6 ~ 1.0
'colsample_bytree': uniform(0.6, 0.4),
'reg_lambda': uniform(0.1, 9.9) # 0.1 ~ 10.0
}
model = xgb.XGBClassifier(random_state=42, use_label_encoder=False, eval_metric='logloss')
# Randomized Search CV
search = RandomizedSearchCV(
model, param_dist, n_iter=50, cv=5,
scoring='accuracy', random_state=42, n_jobs=-1
)
search.fit(X_train, y_train)
print(f"최적 파라미터: {search.best_params_}")
print(f"최적 점수: {search.best_score_:.4f}")
print(f"테스트 점수: {search.score(X_test, y_test):.4f}")
박시니어 씨가 화이트보드에 표를 그렸습니다. "파라미터가 6개이고, 각각 10개의 값을 시도한다면 총 몇 가지 조합이 될까요?" 김개발 씨가 계산했습니다.
"10의 6승이니까... 100만 가지요?" "맞아요.
Grid Search로 모든 조합을 시도하면 평생 걸려요." 그래서 Random Search가 더 실용적입니다. Random Search는 파라미터 공간에서 무작위로 조합을 선택합니다.
놀랍게도, 연구에 따르면 Random Search가 Grid Search보다 더 효율적으로 좋은 조합을 찾습니다. 중요한 파라미터가 무엇인지 모를 때 특히 유용합니다.
위의 코드에서 param_dist를 정의했습니다. randint는 정수 범위에서 균등하게 샘플링합니다.
uniform은 연속적인 실수 범위에서 샘플링합니다. 이렇게 분포를 정의하면 RandomizedSearchCV가 자동으로 조합을 생성합니다.
n_iter=50은 50개의 조합을 시도한다는 뜻입니다. 100만 가지 조합 중 50개만 시도해도 꽤 좋은 결과를 얻을 수 있습니다.
시간이 더 있다면 n_iter를 100이나 200으로 늘릴 수 있습니다. cv=5는 5-fold 교차 검증을 의미합니다.
각 조합에 대해 데이터를 5등분하여 번갈아가며 검증합니다. 이렇게 하면 특정 데이터 분할에 운 좋게 성능이 좋은 경우를 방지할 수 있습니다.
실무에서 튜닝 순서는 어떻게 할까요? 첫째, 먼저 n_estimators와 learning_rate를 튜닝합니다.
이 두 파라미터가 가장 큰 영향을 미칩니다. 둘째, max_depth와 min_samples_split을 조정합니다.
셋째, subsample과 colsample_bytree를 튜닝합니다. 마지막으로 정규화 파라미터를 조절합니다.
더 고급 방법도 있습니다. Optuna나 Hyperopt 같은 베이지안 최적화 도구는 이전 시도 결과를 바탕으로 다음에 시도할 조합을 똑똑하게 선택합니다.
Random Search보다 더 적은 시도로 좋은 결과를 찾을 수 있습니다. 김개발 씨가 RandomizedSearchCV를 실행했습니다.
"와, 기본값보다 3%나 정확도가 올라갔어요!" 하이퍼파라미터 튜닝은 시간이 걸리지만, 모델 성능을 크게 향상시킬 수 있습니다. 효율적인 탐색 방법을 활용하면 시간을 절약하면서도 좋은 결과를 얻을 수 있습니다.
실전 팁
💡 - 처음에는 넓은 범위로 시작하고, 좋은 영역을 찾으면 좁은 범위로 다시 탐색하세요
- n_jobs=-1로 설정하면 모든 CPU 코어를 사용하여 병렬로 튜닝합니다
이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!
댓글 (0)
함께 보면 좋은 카드 뉴스
Helm 마이크로서비스 패키징 완벽 가이드
Kubernetes 환경에서 마이크로서비스를 효율적으로 패키징하고 배포하는 Helm의 핵심 기능을 실무 중심으로 학습합니다. Chart 생성부터 릴리스 관리까지 체계적으로 다룹니다.
보안 아키텍처 구성 완벽 가이드
프로젝트의 보안을 처음부터 설계하는 방법을 배웁니다. AWS 환경에서 VPC부터 WAF, 암호화, 접근 제어까지 실무에서 바로 적용할 수 있는 보안 아키텍처를 단계별로 구성해봅니다.
AWS Organizations 완벽 가이드
여러 AWS 계정을 체계적으로 관리하고 통합 결제와 보안 정책을 적용하는 방법을 실무 스토리로 쉽게 배워봅니다. 초보 개발자도 바로 이해할 수 있는 친절한 설명과 실전 예제를 제공합니다.
AWS KMS 암호화 완벽 가이드
AWS KMS(Key Management Service)를 활용한 클라우드 데이터 암호화 방법을 초급 개발자를 위해 쉽게 설명합니다. CMK 생성부터 S3, EBS 암호화, 봉투 암호화까지 실무에 필요한 모든 내용을 담았습니다.
AWS Secrets Manager 완벽 가이드
AWS에서 데이터베이스 비밀번호, API 키 등 민감한 정보를 안전하게 관리하는 Secrets Manager의 핵심 개념과 실무 활용법을 배워봅니다. 초급 개발자도 쉽게 따라할 수 있도록 실전 예제와 함께 설명합니다.