Post

[Python] ML-LightGBM

[Python] ML-LightGBM

개념


데이터가 수백만 건에 달하는 빅데이터 시대, 전통적인 Gradient Boost는 너무 느립니다. 이때 혜성처럼 등장하여 Kaggle과 현업을 휩쓴 모델이 바로 LightGBM입니다. 이름처럼 가볍고 빠른 이 모델의 강력함은 어디서 나오는지 분석합니다.

LightGBM은 Microsoft에서 개발한 빠르고 효율적인 Gradient Boosting 프레임워크입니다. 기존 Gradient Boost의 느린 속도 문제를 두 가지 핵심 기술로 해결했습니다.


lightgbm

Level-wise(좌)는 같은 깊이의 모든 노드를 분할하지만, Leaf-wise(우)는 손실이 가장 큰 Leaf 하나만 집중 분할합니다.

두 가지 핵심 기술

1. GOSS (Gradient-based One-Side Sampling)

1
2
3
4
5
기울기(잔차)가 큰 샘플 → 전부 사용 (학습에 중요)
기울기가 작은 샘플 → 일부만 랜덤 선택

→ 정보 손실 최소화하면서 샘플 수를 줄임
→ 학습 속도 향상

2. EFB (Exclusive Feature Bundling)

1
2
3
4
5
동시에 0이 아닌 값을 가지지 않는 희소 변수들을 하나로 묶음

예: 원-핫 인코딩된 변수들은 동시에 1이 되지 않음
→ 여러 변수를 하나로 묶어 변수 수를 효과적으로 줄임
→ 메모리 및 계산 효율 향상

Leaf-wise vs Level-wise 트리 성장

1
2
3
4
5
6
7
8
9
Level-wise (일반적인 방식):
같은 깊이의 모든 노드를 분할
→ 균형 잡힌 트리
→ 과적합 위험 낮음

Leaf-wise (LightGBM):
손실 감소가 가장 큰 Leaf 하나만 분할
→ 불균형하지만 정확한 트리
→ 빠른 수렴, 과적합 주의 (max_depth 또는 num_leaves로 제한)

언제 사용하는가

사용해야 할 때

상황이유
대용량 데이터 (수십만 건 이상)GOSS + EFB로 XGBoost보다 훨씬 빠른 학습
학습 속도가 중요한 실무 환경동일 성능 기준 가장 빠른 Boosting 계열
범주형 변수가 많을 때One-Hot Encoding 없이 category 타입으로 직접 처리
메모리가 제한적인 환경EFB로 변수 수를 효과적으로 압축
캐글 등 경진대회에서 빠른 실험이 필요할 때빠른 반복 학습으로 하이퍼파라미터 탐색 용이

사용하지 말아야 할 때

상황이유
소규모 데이터 (수천 건 이하)Leaf-wise 성장으로 과적합 위험이 높아짐
모델 해석이 핵심인 경우블랙박스 모델로 개별 예측 설명 어려움
이미지, 텍스트, 시계열딥러닝 계열이 더 적합

실무 활용 사례

1
2
3
4
금융: 대규모 신용카드 사기 탐지 (실시간성 + 대용량)
커머스: 수백만 고객의 구매 예측 및 추천
광고: 수억 건 로그 기반 CTR(클릭률) 예측
에너지: 대규모 센서 데이터 기반 설비 이상 탐지

핵심 파라미터

파라미터설명기본값
n_estimators트리 수100
learning_rate학습률0.1
num_leaves트리당 최대 리프 수 (중요)31
max_depth최대 깊이 (-1: 제한 없음)-1
min_child_samples리프 노드의 최소 샘플 수20
subsample샘플 샘플링 비율1.0
colsample_bytree변수 샘플링 비율1.0

num_leaves가 LightGBM의 핵심 파라미터입니다. 2^max_depth보다 작게 설정하는 것이 권장됩니다.


실습 — Titanic 생존자 예측

전처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd
import numpy as np
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[['Survived', 'Pclass', 'Sex', 'Embarked']] = \
    titanic[['Survived', 'Pclass', 'Sex', 'Embarked']].astype('category')
titanic['Age'] = titanic['Age'].astype('int')
titanic = pd.get_dummies(titanic, columns=['Pclass', 'Sex', 'Embarked'], drop_first=True)

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)

모델 학습

1
2
3
4
5
6
7
8
9
10
11
12
from lightgbm import LGBMClassifier

LGB = LGBMClassifier(
    n_estimators=200,
    learning_rate=0.05,
    num_leaves=31,
    min_child_samples=20,
    random_state=0,
    verbose=-1
)

LGB.fit(X_train, y_train)

조기 종료 적용

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

X_tr, X_val, y_tr, y_val = train_test_split(X_train, y_train, test_size=0.2, random_state=0)

LGB_ES = LGBMClassifier(
    n_estimators=1000,
    learning_rate=0.05,
    num_leaves=31,
    random_state=0,
    verbose=-1
)

LGB_ES.fit(
    X_tr, y_tr,
    eval_set=[(X_val, y_val)],
    callbacks=[lgb.early_stopping(stopping_rounds=50, verbose=False)]
)

print(f"최적 트리 수: {LGB_ES.best_iteration_}")

범주형 변수 직접 처리

LightGBM의 강점 중 하나입니다. One-Hot Encoding 없이 범주형 변수를 그대로 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
from lightgbm import LGBMClassifier

# One-Hot Encoding 없이 원본 데이터 사용
titanic_cat = pd.read_csv('./Data/Titanic.csv')
titanic_cat['FamSize'] = titanic_cat['SibSp'] + titanic_cat['Parch']
use_cols = ['Survived', 'Pclass', 'Sex', 'Age', 'FamSize', 'Fare', 'Embarked']
titanic_cat = titanic_cat[use_cols].dropna(subset=['Age'])
titanic_cat['Age'] = titanic_cat['Age'].astype('int')

# 범주형 변수를 category 타입으로만 지정
cat_features = ['Pclass', 'Sex', 'Embarked']
for col in cat_features:
    titanic_cat[col] = titanic_cat[col].astype('category')

y_cat = titanic_cat['Survived']
X_cat = titanic_cat.drop('Survived', axis=1)
X_tr_cat, X_te_cat, y_tr_cat, y_te_cat = train_test_split(X_cat, y_cat, test_size=0.25, random_state=0)

LGB_cat = LGBMClassifier(n_estimators=200, random_state=0, verbose=-1)
LGB_cat.fit(X_tr_cat, y_tr_cat,
            categorical_feature=cat_features)  # 범주형 변수 지정

print(f"정확도 : {LGB_cat.score(X_te_cat, y_te_cat) * 100:.2f}%")
# One-Hot Encoding 결과와 성능 비교 가능

성능 평가

1
2
3
4
5
6
7
8
9
10
11
from sklearn.metrics import accuracy_score, confusion_matrix

pred = LGB.predict(X_test)
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])

print(f"정확도 : {accuracy_score(y_test, pred) * 100:.2f}%")
print(f"민감도 : {sensitivity * 100:.2f}%")
print(f"특이도 : {specificity * 100:.2f}%")
1
2
3
정확도 : 83.80%
민감도 : 75.00%
특이도 : 89.32%

장단점

장점

  • XGBoost보다 빠른 학습 속도
  • 메모리 효율이 높음
  • 범주형 변수 자동 처리 지원
  • 대용량 데이터에 적합

단점

  • 소규모 데이터에서 Leaf-wise 성장으로 과적합 위험
  • num_leaves 등 LightGBM 고유 파라미터 이해 필요
  • XGBoost보다 직관적 이해가 어려움

This post is licensed under CC BY 4.0 by the author.