Post

[Python] ML-CatBoost

[Python] ML-CatBoost

1. 왜 등장했는가

기존 Gradient Boosting 계열(XGBoost, LightGBM)은 범주형 특성을 직접 처리하지 못해
One-hot Encoding 등 별도 전처리가 필요했고, 이 과정에서 타깃 누수(Target Leakage) 문제가 발생했습니다.
CatBoost는 범주형 특성을 자체적으로 안전하게 처리하고 순서 기반 부스팅으로 편향을 줄인 알고리즘입니다. (Yandex, 2017)


2. 핵심 아이디어 — 범주형 특성을 직접 학습

CatBoost는 본질적으로 범주형 특성에 강한 Gradient Boosting입니다.

cb_idea

각 범주값을 타깃과의 통계적 관계로 수치화하되,
학습 순서를 무작위로 섞어 각 샘플이 자기보다 이전 샘플의 통계만 보도록 합니다.


3. 실제 예시로 보기 (분류 / 회귀)

예시 1 — 타이타닉 생존 예측 (분류)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
훈련 데이터:
┌────────┬────────┬─────┬──────────┬──────────┐
│ 이름   │ 성별   │ 나이 │ 객실 등급 │ 생존 여부 │
├────────┼────────┼─────┼──────────┼──────────┤
│ Alice  │ 여성   │  29  │   1등석   │    ✅    │
│ Bob    │ 남성   │  31  │   3등석   │    ❌    │
│ Carol  │ 여성   │  45  │   3등석   │    ✅    │
│ Dave   │ 남성   │  12  │   2등석   │    ✅    │
│ Eve    │ 남성   │  38  │   3등석   │    ❌    │
└────────┴────────┴─────┴──────────┴──────────┘

기존 방식: Sex 열을 get_dummies()로 변환 필요
  Sex_male = [0, 1, 0, 1, 1]

CatBoost 방식: cat_features=['Sex', 'Embarked', 'Pclass'] 지정만 하면 끝
  내부적으로 Ordered Target Statistics 계산
  → 전처리 코드 없이 원본 문자열 그대로 사용 가능

예시 2 — 상품 추천 (범주형 특성 많은 경우)

1
2
3
4
특성: 상품카테고리(100종), 브랜드(500종), 색상(20종), 지역(200종)

기존 방식: One-hot 후 820개 열 → 차원의 저주
CatBoost:  원본 그대로 입력 → 내부에서 효율적 처리

4. 알고리즘 구성 요소

cb_component

구성 요소설명비유
Ordered Boosting학습 순서를 무작위화해 편향 제거시험 답지 순서를 섞어 커닝 방지
Ordered Target Statistics범주형 특성을 타깃 통계로 안전하게 수치화카테고리별 합격률로 수치화
Symmetric Tree좌우 대칭 구조의 트리균형 잡힌 질문 트리
내장 범주형 처리cat_features 지정만으로 자동 처리번역기 내장

5. 어떻게 범주형을 처리하는가

5-1. 기존 방식의 문제 (Target Leakage)

\[\hat{x}_{i,k} = \frac{\sum_{j<i} \mathbf{1}[x_{j,k}=x_{i,k}] \cdot y_j}{\sum_{j<i} \mathbf{1}[x_{j,k}=x_{i,k}]}\]
1
2
3
4
5
6
문제: 단순 평균을 쓰면 샘플 자신의 타깃도 통계에 포함
  → 타깃 정보가 특성으로 누출 → 과적합

예시: "성별=여성" 평균 생존율 계산 시
  Alice(생존) 본인이 포함된 평균을 Alice 예측에 사용
  → 정답을 미리 알고 맞추는 것과 같음

5-2. Ordered Target Statistics (CatBoost 해결책)

1
2
3
4
5
6
7
8
9
10
학습 순서를 무작위로 섞은 후:

  샘플 순서: Dave → Bob → Carol → Alice → Eve

  Dave의 "성별=남성" 통계: (이전 샘플 없음) → prior 사용
  Bob의  "성별=남성" 통계: Dave만 참고 → 0/1 = 0.0
  Carol의 "성별=여성" 통계: (이전 여성 없음) → prior 사용
  Alice의 "성별=여성" 통계: Carol만 참고 → 1/1 = 1.0

  → 각 샘플이 자기 이전 샘플만 보므로 누수 없음

cb_encoding


6. CatBoost 장・단점

6-1. ✅ CatBoost 장점

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. 범주형 특성 자동 처리
   → get_dummies(), LabelEncoder() 불필요
   → cat_features 파라미터에 열 이름만 넣으면 끝

2. 타깃 누수 없음
   → Ordered Boosting으로 편향 제거
   → 더 신뢰할 수 있는 교차 검증 결과

3. 기본 파라미터로도 좋은 성능
   → 하이퍼파라미터 튜닝 없이도 경쟁력 있는 결과

4. 스케일링 불필요
   → 트리 기반

5. GPU 학습 지원

6-2. ❌ CatBoost가 약한 상황

1
2
3
4
5
6
7
8
1. 학습 속도
   → Ordered Boosting 연산 비용 → XGBoost/LightGBM보다 느릴 수 있음

2. 범주형 특성이 없는 경우
   → 순수 수치 데이터에서는 LightGBM과 큰 차이 없음

3. 메모리 사용
   → Ordered Boosting을 위해 여러 버전의 모델 유지

6-2-1. 범주형 고유값이 매우 많을 때 (High Cardinality)

고유값이 수천 개인 범주형 특성(예: 사용자 ID)은 주의가 필요합니다.

1
2
3
4
5
6
사용자 ID: 고유값 100만 개
→ Ordered Target Statistics 계산량 급증
→ 사전에 고빈도 기준으로 그루핑 권장

예시:
  userId → 등장 횟수 기준 Top 1000 / 나머지 "기타"

해결책 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 방법 1: max_cat_count로 고유값 수 제한
cat = CatBoostClassifier(max_cat_count=100, random_state=0)

# 방법 2: GridSearchCV로 최적값 탐색
from sklearn.model_selection import GridSearchCV

param_grid = {
    'iterations':   [100, 200, 300],
    'learning_rate':[0.01, 0.05, 0.1],
    'depth':        [4, 6, 8]
}
cat_cv = GridSearchCV(
    CatBoostClassifier(random_state=0, verbose=0),
    param_grid,
    cv=5,
    scoring='roc_auc'
)
cat_cv.fit(X_train, y_train, cat_features=cat_cols)
print(f"Best params : {cat_cv.best_params_}")
print(f"Best AUC    : {cat_cv.best_score_:.4f}")

7. 한눈에 요약

항목내용
알고리즘 유형지도학습 / 분류 & 회귀 모두 가능
핵심 아이디어Ordered Boosting + 범주형 자동 처리
기반 모델Symmetric Decision Tree
스케일링 필요?❌ 불필요
범주형 처리✅ 자동 (cat_features 지정)
핵심 파라미터iterations, learning_rate, depth
실전 사용범주형 특성 많을 때, 전처리 최소화하고 싶을 때

8. 다른 알고리즘과 무엇이 다른가

XGBoost vs LightGBM vs CatBoost

1
2
3
XGBoost:     범주형 인코딩 필요, Level-wise, 안정적
LightGBM:    범주형 직접 처리, Leaf-wise, 가장 빠름
CatBoost:    범주형 가장 잘 처리, Symmetric Tree, 편향 없음
항목XGBoostLightGBMCatBoost
범주형 처리❌ 인코딩 필요▲ 직접 처리✅ 가장 우수
타깃 누수⚠️ 위험⚠️ 위험✅ 없음
속도보통빠름보통~느림
기본 파라미터 성능보통보통✅ 우수
GPU 지원

cb_comparison


9. 코드로 보기 — 타이타닉 생존 예측

1
2
3
4
5
6
7
8
9
10
11
12
from catboost import CatBoostClassifier

cat = CatBoostClassifier(
    iterations = 300,         # 트리 수 (n_estimators에 해당)
    learning_rate = 0.05,     # 학습률(각 트리 기여도)
    depth = 6,                # 트리 깊이(대칭 트리라 중요)
    l2_leaf_reg=3,            # L2 정규화
    cat_features = cat_cols,  # 범주형 열 이름 목록
    eval_metric = 'AUC',
    random_seed = 0,
    verbose = 100             # 100 반복마다 출력 (0이면 출력 없음)
)
ParameterDefault역할과적합 방향
iterations1000트리 수클수록 과적합 ↑
learning_rate자동트리 기여도클수록 과적합 ↑
depth6트리 깊이클수록 과적합 ↑
l2_leaf_reg3.0L2 정규화클수록 과적합 ↓
border_count254수치형 분기 구간 수-
  • iterations : 트리를 몇 개 쌓을지
    • 값 변화별 효과
      • 클수록 → 더 정교한 학습, 과적합 ↑
      • Early Stopping과 함께 사용 권장
  • depth : Symmetric Tree의 깊이
    • 값 변화별 효과
      • CatBoost는 Symmetric Tree라 depth=6이면 2⁶=64개 리프
      • 기본값 6 권장, 10 이상은 과적합 위험
  • cat_features : 범주형 특성 열 지정
    • 열 이름(str) 또는 열 인덱스(int) 리스트
    • 지정하지 않으면 수치형으로 처리

9-1. 전처리 — 범주형 인코딩 불필요

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
from sklearn.model_selection import train_test_split

titanic = pd.read_csv('./Data/Titanic.csv')
titanic['FamSize'] = titanic['SibSp'] + titanic['Parch']

use_cols = ['Survived', 'Pclass', 'Sex', 'Age', 'FamSize', 'Fare', 'Embarked']
titanic = titanic[use_cols].dropna(subset=['Age'])
titanic['Age'] = titanic['Age'].astype(int)

# ✅ CatBoost는 get_dummies() 불필요 — 문자열 그대로 사용
# Pclass, Sex, Embarked를 범주형으로 지정만 하면 됨
cat_cols = ['Pclass', 'Sex', 'Embarked']
for col in cat_cols:
    titanic[col] = titanic[col].astype(str)

y = titanic['Survived']
X = titanic.drop('Survived', axis=1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)

# ⚠️ CatBoost는 트리 기반 → 스케일링 불필요
# StandardScaler 생략

Note: CatBoost는 범주형 특성을 문자열 그대로 받을 수 있습니다. get_dummies()LabelEncoder() 없이 cat_features에 열 이름만 지정하면 됩니다.


9-2. 모델 학습

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat = CatBoostClassifier(
    iterations = 300,         # 트리 수 (n_estimators에 해당)
    learning_rate = 0.05,     # 학습률(각 트리 기여도)
    depth = 6,                # 트리 깊이(대칭 트리라 중요)
    l2_leaf_reg=3,            # L2 정규화
    cat_features = cat_cols,  # 범주형 열 이름 목록
    eval_metric = 'AUC',
    random_seed = 0,
    verbose = 100             # 100 반복마다 출력 (0이면 출력 없음)
)

cat.fit(
    X_train, y_train,
    eval_set=(X_test, y_test),
    early_stopping_rounds=50
)

9-3. 평가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sklearn.metrics import (
    accuracy_score, confusion_matrix,
    classification_report, roc_auc_score
)

pred      = cat.predict(X_test)
pred_prob = cat.predict_proba(X_test)[:, 1]

cfx         = confusion_matrix(y_test, pred)
sensitivity = cfx[1, 1] / (cfx[1, 0] + cfx[1, 1])
specificity = cfx[0, 0] / (cfx[0, 0] + cfx[0, 1])
roc_auc     = roc_auc_score(y_test, pred_prob)

print(f"Accuracy    : {accuracy_score(y_test, pred) * 100:.2f}%")
print(f"Sensitivity : {sensitivity * 100:.2f}%")
print(f"Specificity : {specificity * 100:.2f}%")
print(f"ROC AUC     : {roc_auc:.4f}")
print()
print(classification_report(y_test, pred, target_names=['Died (0)', 'Survived (1)']))

9-4. 특성 중요도

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd
import matplotlib.pyplot as plt

importances = pd.Series(
    cat.get_feature_importance(),
    index=X.columns
).sort_values(ascending=True)

plt.figure(figsize=(7, 5))
importances.plot(kind='barh', color='steelblue')
plt.xlabel('Feature Importance')
plt.title('CatBoost Feature Importance')
plt.tight_layout()
plt.show()
This post is licensed under CC BY 4.0 by the author.