그래디언트 부스팅
더 많은 작업
- Gradient boosting; 그래디언트 부스팅, 그레이디언트 부스팅, GBM
- 손실 함수의 음의 그래디언트(유사 잔차)를 맞추는 약한 학습기, 주로 회귀 트리를 하나씩 더해 가며 예측 함수를 함수 공간에서 경사 하강으로 최적화하는 부스팅 기법
그래디언트 부스팅(Gradient boosting)은 지금까지 만든 모델이 틀린 부분을 다음 모델이 고쳐 나가도록 약한 학습기를 차례로 더하는 부스팅 기법을 임의의 미분 가능한 손실 함수로 일반화한 방법이다. 매 단계 "손실을 가장 빨리 줄이는 방향", 즉 예측값에 대한 손실의 음의 그래디언트를 계산하고, 그 값을 목표로 작은 회귀 트리를 학습해 더한다. 제곱 오차에서는 이 음의 그래디언트가 잔차와 같아 "잔차를 맞추는 트리를 계속 더한다"는 직관이 그대로 성립한다. 트리를 기본 학습기로 쓰는 경우를 GBDT(gradient boosted decision trees) 또는 GBM(gradient boosting machine)이라고도 한다.
제롬 프리드먼(Jerome Friedman)이 2001년 〈Greedy function approximation: A gradient boosting machine〉에서 정식화했다.[1] 이후 XGBoost, LightGBM, CatBoost 같은 고성능 구현이 나오면서 표 형태의 정형 데이터 예측에서 가장 먼저 시도하는 모델이 되었다. XGBoost 논문은 2015년 캐글 블로그에 공개된 우승 해법 29개 가운데 17개가 XGBoost를 썼다고 밝혔고,[2] 2022년 연구에서도 중간 크기(약 1만 개 표본) 정형 데이터 벤치마크에서 트리 기반 모델이 딥 러닝 모델보다 앞섰다.[3]
- 1990년 샤파이어(Robert Schapire)가 약한 학습기를 결합해 강한 학습기를 만들 수 있음을 보였고,[4] 1997년 프로인트(Yoav Freund)와 샤파이어의 AdaBoost가 오분류 표본의 가중치를 키우는 방식으로 이를 실용화했다.[5]
- 2000년 프리드먼, 헤이스티(Trevor Hastie), 팁시라니(Robert Tibshirani)는 AdaBoost가 지수 손실을 최소화하는 단계적 가법 모형(forward stagewise additive modeling)임을 보였다.[6] 손실 함수만 바꾸면 다른 부스팅을 만들 수 있다는 관점이다.
- 그래디언트 부스팅은 이 관점을 일반화해, 어떤 미분 가능한 손실이든 그래디언트만 계산하면 같은 절차로 부스팅할 수 있게 했다.[1] 이듬해 매 단계 학습 데이터의 일부만 뽑아 쓰는 확률적 그래디언트 부스팅이 나왔다.[7]
학습 데이터 와 손실 가 있을 때, 예측 함수 를 가법 모형
으로 만든다. 은 약한 학습기, 은 학습률(축소, shrinkage)이다. 학습 데이터에서의 예측값 벡터 를 매개변수로 보면, 경험 손실 를 줄이는 가장 가파른 방향은 각 점에서의 음의 그래디언트
이다. 이것을 유사 잔차(pseudo-residual)라 한다. 하지만 이 방향은 학습 데이터 점에서만 정의되므로, 새 입력에도 쓸 수 있게 을 최소 제곱으로 가장 잘 근사하는 회귀 트리 을 학습해 그 방향 대신 쓴다. 신경망이 매개변수 공간에서 경사 하강법을 한다면, 그래디언트 부스팅은 함수 공간에서 경사 하강을 하고 그 한 걸음을 트리로 근사하는 셈이다.
- 1. 상수 하나로 초기화한다: . 제곱 오차면 평균, 로그 손실이면 로그 오즈다.
- 2. 에 대해 반복한다.
- (가) 유사 잔차 을 계산한다.
- (나) 에 회귀 트리를 학습해 잎 영역 , 를 얻는다.
- (다) 잎마다 손실을 직접 최소화하는 값을 구한다:
- (라) 갱신한다:
(다) 단계의 잎 값 재계산이 프리드먼이 TreeBoost라고 부른 부분이다. 트리 구조는 제곱 오차로 정하되 잎 값은 실제 손실에 맞춘다.[1]
| 문제 | 손실 | 유사 잔차 | 비고 |
|---|---|---|---|
| 회귀 | 잔차 그대로 | ||
| 회귀(강건) | 잎 값은 잔차의 중앙값 | ||
| 회귀(강건) | 후버 손실 | 작은 잔차는 그대로, 큰 잔차는 로 자름 | 이상치에 덜 민감 |
| 분위수 회귀 | 핀볼 손실 | 또는 | 예측 구간 추정 |
| 이진 분류 | 로그 손실, | 는 로그 오즈. 잎 값은 뉴턴 한 걸음 | |
| 다중 분류 | 소프트맥스 교차 엔트로피 | 반복마다 클래스 수만큼 트리 | |
| 순위 학습 | LambdaMART 등 | 쌍별·목록별 그래디언트 | 검색 순위 |
- 학습률(축소): 각 트리의 기여를 배로 줄인다. scikit-learn 문서는 학습률을 0.1 이하로 작게 두고 조기 종료가 걸릴 만큼 트리 수를 넉넉히 주라는 헤이스티 등의 권고를 소개한다.[8]
- 트리 크기: 잎 수나 깊이를 작게 둔다. 깊이 트리는 최대 개 변수의 상호작용을 표현한다.
- 부분 표본 추출: 매 반복 학습 데이터의 일부만 비복원 추출로 써서 분산을 줄이고 속도를 높인다(확률적 그래디언트 부스팅).[7] scikit-learn 문서는 전형적인 값으로 0.5를 든다.[8] 랜덤 포레스트처럼 특성 일부만 쓰는 열 표본 추출도 함께 쓴다.
- 잎 값 정규화: 잎 가중치에 L1·L2 벌점을 주고, 잎에 필요한 최소 표본 수나 최소 헤시안 합을 둔다.
- 조기 종료: 검증 손실이 일정 반복 동안 좋아지지 않으면 멈춘다. 트리 수를 고르는 가장 실용적인 방법이다.
XGBoost는 목적 함수에 트리 복잡도 벌점을 넣고 손실을 2차까지 테일러 전개해 잎 값과 분할 기준을 닫힌 꼴로 구한다.[2] 번째 트리 를 더할 때의 목적 함수는
이고(는 잎 수, 는 잎 값), 1차·2차 도함수 , 로 근사하면
가 된다. 잎마다 독립인 2차식이므로 최적 잎 값과 그때의 목적 함수 값은
이다. 한 잎을 왼쪽 과 오른쪽 로 나눌 때의 이득은
이고 이득이 가장 큰 분할을 고른다. 는 분할 하나를 정당화하는 최소 이득 역할을 해 가지치기가 된다. 사용자 정의 손실도 만 주면 된다. 그래디언트만 쓰는 1차 방법과 달리 뉴턴 방법에 해당하므로 "뉴턴 부스팅"이라고도 한다.
- 정확한 탐욕 탐색: 특성마다 값을 정렬해 가능한 모든 분할점을 훑는다. 정확하지만 데이터가 크면 느리다.
- 히스토그램(구간화): 특성 값을 256개 정도의 구간으로 미리 나눠, 구간별 합만으로 분할을 찾는다. 비용이 표본 수 대신 구간 수에 비례하고, 형제 노드의 히스토그램은 부모에서 빼서 얻는다. LightGBM, XGBoost
tree_method="hist", scikit-learnHistGradientBoosting이 이 방식이다. scikit-learn 문서는 표본이 수만 개를 넘으면 히스토그램 방식이 기존 구현보다 자릿수가 달라질 만큼(orders of magnitude) 빠를 수 있다고 설명한다.[8] - 가중 분위수 스케치: XGBoost는 헤시안을 가중치로 한 분위수로 후보 분할점을 정하는 근사 알고리즘을 제안했다.[2]
- 결측값 처리: 분할마다 결측값을 왼쪽·오른쪽 중 이득이 큰 쪽으로 보내는 기본 방향을 학습한다(XGBoost의 희소성 인지 분할). 결측값 대치 없이 바로 학습할 수 있다.
- 잎 우선(leaf-wise) 성장: 깊이와 상관없이 손실 감소가 가장 큰 잎부터 나눈다(LightGBM 기본). 같은 잎 수에서 손실을 더 줄이지만 깊고 불균형한 트리가 되어 작은 데이터에서 과적합되기 쉽다. 깊이 우선(level-wise)은 한 층씩 고르게 나눈다(XGBoost 기본
grow_policy="depthwise"). - GOSS와 EFB: LightGBM은 그래디언트가 큰 표본은 모두 남기고 작은 표본은 일부만 뽑아 쓰는 GOSS(Gradient-based One-Side Sampling), 동시에 0이 아닌 값을 거의 갖지 않는 희소 특성들을 하나로 묶는 EFB(Exclusive Feature Bundling)를 도입해, 기존 GBDT보다 학습을 최대 20배 넘게 빠르게 하면서 정확도는 거의 같게 유지했다고 보고했다.[9]
- 순서 부스팅과 대칭 트리: CatBoost 연구진은 모든 학습 표본의 목표값으로 만든 모델로 다시 그 표본의 그래디언트를 계산하면 일종의 목표 누수가 생겨 예측 분포가 학습·시험에서 달라진다(예측 이동, prediction shift)고 지적하고, 표본에 무작위 순서를 매겨 앞선 표본만으로 계산하는 순서 부스팅(ordered boosting)과 같은 원리의 범주형 목표 통계 인코딩을 제안했다. 기본 학습기로는 한 층의 모든 노드가 같은 분할 기준을 쓰는 대칭(oblivious) 트리를 쓴다.[10] 대칭 트리는 추론이 빠르고 과적합에 강한 편이다.
| 구분 | XGBoost | LightGBM | CatBoost | scikit-learn HistGradientBoosting |
|---|---|---|---|---|
| 개발 | 천톈치(Tianqi Chen) 등, DMLC | 마이크로소프트 | 얀덱스 | scikit-learn 개발진 |
| 대표 논문 | KDD 2016[2] | NIPS 2017[9] | NeurIPS 2018[10] | LightGBM에서 영감을 받은 구현[8] |
| 트리 성장 | 깊이 우선(기본), 손실 우선 선택 가능 | 잎 우선 | 대칭 트리 | 잎 우선(max_leaf_nodes)
|
| 분할 탐색 | 정확, 근사, 히스토그램(기본 auto = hist) |
히스토그램, GOSS, EFB | 히스토그램 | 히스토그램 |
| 범주형 특성 | 지원(enable_categorical, 히스토그램 방식에서) |
지원(categorical_feature) |
핵심 기능. 순서 목표 통계, 범주 조합 자동 생성 | 지원(categorical_features, 범주 수가 max_bins 미만)
|
| 결측값 | 기본 방향 학습 | 지원 | 지원 | 지원 |
| 주요 인자 | max_depth(기본 6), eta(0.3), lambda, min_child_weight |
num_leaves(기본 31), learning_rate(0.1), min_child_samples |
depth(기본 6), l2_leaf_reg(3.0), iterations |
max_iter, learning_rate, max_leaf_nodes
|
| 강점 | 생태계·분산 학습·GPU, 문서화 | 대용량에서 빠른 학습, 메모리 효율 | 범주형이 많은 데이터, 기본값으로도 안정적 | 추가 의존성 없음, scikit-learn 파이프라인과 통합 |
| 주의 | 기본 학습률 0.3이 커서 조기 종료와 함께 낮추는 것이 보통 | 작은 데이터에서 과적합, num_leaves 제한 필요 |
학습이 상대적으로 느릴 수 있음 | 표본이 1만 개 이상이면 조기 종료가 기본으로 켜짐 |
위 기본값은 XGBoost 3.x, LightGBM 4.7, CatBoost 1.2, scikit-learn 1.7 기준이다.
| 구분 | 랜덤 포레스트 | 그래디언트 부스팅 |
|---|---|---|
| 앙상블 방식 | 배깅: 독립적인 깊은 트리를 병렬로 만들어 평균 | 부스팅: 얕은 트리를 순차로 더함 |
| 주로 줄이는 것 | 분산 | 편향(학습률·표본 추출로 분산도 조절) |
| 트리 수를 늘리면 | 성능이 수렴할 뿐 과적합되지 않는다 | 계속 늘리면 과적합되므로 조기 종료가 필요 |
| 튜닝 민감도 | 낮다. 기본값으로도 쓸 만하다 | 높다. 학습률, 트리 크기, 반복 수를 함께 맞춰야 한다 |
| 학습 병렬화 | 트리 단위로 쉬움 | 트리 안의 분할 탐색만 병렬화 |
| 일반적인 성능 | 좋은 기준선 | 튜닝하면 대개 더 높다 |
앙상블 기법 전반은 해당 문서 참고.
제곱 오차에서 유사 잔차는 잔차이므로, scikit-learn의 회귀 트리를 기본 학습기로 몇 줄이면 된다. 당뇨병 데이터(442개 표본)로 scikit-learn 구현과 비교한다(scikit-learn 1.7에서 실행).
import numpy as np
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import GradientBoostingRegressor
X, y = load_diabetes(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=0)
class TinyGBM:
"""제곱 오차 손실의 그래디언트 부스팅: 음의 그래디언트 = 잔차"""
def __init__(self, n_estimators=200, learning_rate=0.05, max_depth=3):
self.M, self.nu, self.depth = n_estimators, learning_rate, max_depth
def fit(self, X, y):
self.f0 = y.mean() # F_0: 손실을 최소로 하는 상수
F = np.full(len(y), self.f0)
self.trees = []
for _ in range(self.M):
r = y - F # 유사 잔차 -dL/dF
tree = DecisionTreeRegressor(max_depth=self.depth, random_state=0).fit(X, r)
F += self.nu * tree.predict(X) # 축소(shrinkage) 적용
self.trees.append(tree)
return self
def predict(self, X):
return self.f0 + self.nu * sum(t.predict(X) for t in self.trees)
rmse = lambda a, b: np.sqrt(np.mean((a - b) ** 2))
mine = TinyGBM().fit(X_tr, y_tr)
skl = GradientBoostingRegressor(n_estimators=200, learning_rate=0.05, max_depth=3,
random_state=0).fit(X_tr, y_tr)
print("평균 예측 RMSE:", round(rmse(y_te, np.full(len(y_te), y_tr.mean())), 2))
print("TinyGBM RMSE: ", round(rmse(y_te, mine.predict(X_te)), 2))
print("sklearn RMSE: ", round(rmse(y_te, skl.predict(X_te)), 2))
평균 예측 RMSE: 70.46
TinyGBM RMSE: 62.19
sklearn RMSE: 62.14
두 결과가 조금 다른 것은 scikit-learn이 분할 기준으로 프리드먼 MSE를 기본으로 쓰기 때문이다.
같은 데이터를 학습 60%, 조기 종료용 검증 20%, 최종 평가용 시험 20%로 나눠 네 구현을 돌린다. 조기 종료에 쓴 검증 세트로 성능을 보고하면 낙관적으로 치우치므로 시험 세트를 따로 둔다(XGBoost 3.2, LightGBM 4.7, CatBoost 1.2.10, scikit-learn 1.7에서 실행).
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from sklearn.ensemble import HistGradientBoostingClassifier
import xgboost as xgb
import lightgbm as lgb
from catboost import CatBoostClassifier
X, y = load_breast_cancer(return_X_y=True)
# 학습 60%, 조기 종료용 검증 20%, 최종 평가용 시험 20%
X_tmp, X_te, y_tmp, y_te = train_test_split(X, y, test_size=0.2, stratify=y, random_state=0)
X_tr, X_va, y_tr, y_va = train_test_split(X_tmp, y_tmp, test_size=0.25, stratify=y_tmp, random_state=0)
hgb = HistGradientBoostingClassifier(
learning_rate=0.05, max_iter=1000, early_stopping=True, # 내부에서 검증 세트를 따로 뗀다
validation_fraction=0.2, n_iter_no_change=50, random_state=0).fit(X_tmp, y_tmp)
xgbm = xgb.XGBClassifier(
n_estimators=1000, learning_rate=0.05, max_depth=4, subsample=0.8,
colsample_bytree=0.8, reg_lambda=1.0, tree_method="hist",
early_stopping_rounds=50, eval_metric="logloss")
xgbm.fit(X_tr, y_tr, eval_set=[(X_va, y_va)], verbose=False)
lgbm = lgb.LGBMClassifier(
n_estimators=1000, learning_rate=0.05, num_leaves=15, subsample=0.8,
subsample_freq=1, colsample_bytree=0.8, verbose=-1)
lgbm.fit(X_tr, y_tr, eval_set=[(X_va, y_va)],
callbacks=[lgb.early_stopping(50, verbose=False)])
cat = CatBoostClassifier(iterations=1000, learning_rate=0.05, depth=6,
early_stopping_rounds=50, verbose=0, random_seed=0)
cat.fit(X_tr, y_tr, eval_set=(X_va, y_va))
for name, m, n_used in [("sklearn HGB", hgb, hgb.n_iter_),
("XGBoost", xgbm, xgbm.best_iteration + 1),
("LightGBM", lgbm, lgbm.best_iteration_),
("CatBoost", cat, cat.get_best_iteration() + 1)]:
auc = roc_auc_score(y_te, m.predict_proba(X_te)[:, 1])
print(f"{name:12s} 시험 AUC={auc:.4f} 반복 수={n_used}")
sklearn HGB 시험 AUC=0.9785 반복 수=244
XGBoost 시험 AUC=0.9815 반복 수=126
LightGBM 시험 AUC=0.9854 반복 수=209
CatBoost 시험 AUC=0.9825 반복 수=359
표본 569개짜리 쉬운 데이터라 차이는 무의미하며, 순위는 시드와 분할에 따라 바뀐다. API 차이를 보이려는 예다. LightGBM 4.7은 fit의 eval_set 대신 eval_X·eval_y를 쓰라는 사용 중단 경고를 낸다. scikit-learn 구현의 반복 수는 조기 종료가 걸린 시점까지의 전체 반복 수다.
- 1단계: 학습률을 0.05~0.1 정도로 두고 트리 수는 넉넉히, 조기 종료로 반복 수를 정한다.
- 2단계: 트리 크기(
max_depth3~8, LightGBM이면num_leaves)와 잎 최소 표본 수를 조정한다. 성능에 가장 큰 영향을 준다. - 3단계: 행·열 표본 추출 비율(0.5~1.0)과 L1·L2 벌점을 조정한다.
- 4단계: 마지막으로 학습률을 낮추고 트리 수를 늘려 조금 더 짜낸다.
교차 검증과 베이지안 최적화 같은 탐색 도구를 함께 쓴다. 범주형 특성이 많으면 CatBoost를, 표본이 수백만 개 이상이면 히스토그램 방식과 GPU 학습을 먼저 고려한다. 시계열은 무작위 분할 대신 시간 순서로 검증 세트를 나눠야 누수가 없다.
- 특성 중요도: 분할 횟수 기준(split)과 이득 합 기준(gain)이 있으며, 둘의 순위가 다를 수 있고 값의 범위가 넓은 특성에 치우치기 쉽다.
- SHAP: 트리 구조를 이용해 섀플리 값을 다항 시간에 정확히 계산하는 TreeSHAP이 개별 예측의 기여도를 준다.[11] XGBoost, LightGBM, CatBoost 모두 SHAP 값 계산을 지원한다(설명 가능한 인공지능).
- 단조 제약: 금리가 오르면 연체 확률이 내려가지 않아야 한다처럼 도메인 지식을 단조 제약으로 넣을 수 있다(세 라이브러리 모두 지원).
- 트리를 순서대로 만들어야 하므로 트리 사이 병렬화가 안 되고, 하이퍼파라미터에 민감하다.
- 트리의 예측은 계단 함수라 학습 데이터 범위 밖으로 외삽하지 못한다. 추세가 있는 시계열에서는 차분이나 선형 모델과의 결합이 필요하다.
- 이미지, 음성, 자연어 같은 비정형 데이터에서는 딥 러닝에 크게 뒤진다. 이런 데이터는 신경망으로 만든 임베딩을 특성으로 넣어 쓰기도 한다.
- 이상치에 맞춰 잔차를 계속 쫓으므로 잡음 많은 레이블에서 과적합되기 쉽다. 강건 손실, 작은 학습률, 조기 종료로 완화한다.
- ↑ 1.0 1.1 1.2 Jerome H. Friedman, 〈Greedy function approximation: A gradient boosting machine〉, Annals of Statistics 29(5), 2001, pp. 1189~1232. doi:10.1214/aos/1013203451
- ↑ 2.0 2.1 2.2 2.3 Tianqi Chen, Carlos Guestrin, 〈XGBoost: A Scalable Tree Boosting System〉, KDD 2016. arXiv:1603.02754
- ↑ Léo Grinsztajn, Edouard Oyallon, Gaël Varoquaux, 〈Why do tree-based models still outperform deep learning on tabular data?〉, NeurIPS 2022 Datasets and Benchmarks. arXiv:2207.08815
- ↑ Robert E. Schapire, 〈The strength of weak learnability〉, Machine Learning 5(2), 1990. doi:10.1007/BF00116037
- ↑ Yoav Freund, Robert E. Schapire, 〈A Decision-Theoretic Generalization of On-Line Learning and an Application to Boosting〉, Journal of Computer and System Sciences 55(1), 1997. doi:10.1006/jcss.1997.1504
- ↑ Jerome Friedman, Trevor Hastie, Robert Tibshirani, 〈Additive logistic regression: a statistical view of boosting〉, Annals of Statistics 28(2), 2000. doi:10.1214/aos/1016218223
- ↑ 7.0 7.1 Jerome H. Friedman, 〈Stochastic gradient boosting〉, Computational Statistics & Data Analysis 38(4), 2002, pp. 367~378. doi:10.1016/S0167-9473(01)00065-2
- ↑ 8.0 8.1 8.2 8.3 scikit-learn 사용자 안내서, Ensembles: Gradient boosting
- ↑ 9.0 9.1 Guolin Ke 외, 〈LightGBM: A Highly Efficient Gradient Boosting Decision Tree〉, NIPS 2017. NeurIPS
- ↑ 10.0 10.1 Liudmila Prokhorenkova 외, 〈CatBoost: unbiased boosting with categorical features〉, NeurIPS 2018. arXiv:1706.09516
- ↑ Scott M. Lundberg 외, 〈From local explanations to global understanding with explainable AI for trees〉, Nature Machine Intelligence 2, 2020. doi:10.1038/s42256-019-0138-9
- Trevor Hastie, Robert Tibshirani, Jerome Friedman, 《The Elements of Statistical Learning》 2판, Springer, 2009, 10장 Boosting and Additive Trees.
- XGBoost 문서, XGBoost Parameters
- LightGBM 문서, Parameters Tuning