[Python] ML-LightGBM
[Python] ML-LightGBM
LightGBM 개념
LightGBM은 Microsoft에서 개발한 빠르고 효율적인 Gradient Boosting 프레임워크. 기존 Gradient Boost의 느린 속도 문제를 두 가지 핵심 기술로 해결
1
2
3
4
5
6
기존 Gradient Boost의 문제:
→ 모든 샘플과 모든 변수를 탐색 → 느림
LightGBM의 해결책:
→ GOSS로 중요 샘플만 선택
→ EFB로 희소 변수를 묶어 변수 수 축소
두 가지 핵심 기술
1. GOSS (Gradient-based One-Side Sampling)
1
2
3
4
5
기울기(잔차)가 큰 샘플 → 전부 사용 (학습에 중요)
기울기가 작은 샘플 → 일부만 랜덤 선택
→ 정보 손실 최소화하면서 샘플 수를 줄임
→ 학습 속도 향상
2. EFB (Exclusive Feature Bundling)
1
2
3
4
5
6
7
동시에 0이 아닌 값을 가지지 않는 희소 변수들을 하나로 묶음
즉, 서로 동시에 나타나지 않는 변수들을 하나로 합쳐서, 모델이 공부해야 할 과목 수를 줄여주는 기술
-> 굳이 고려하지 않아도 되는 경우의 수를 제외하는 방법.
예: 원-핫 인코딩된 변수들은 동시에 1이 되지 않음
→ 여러 변수를 하나로 묶어 변수 수를 효과적으로 줄임
→ 메모리 및 계산 효율 향상
Leaf-wise vs Level-wise 트리 성장
1
2
3
4
5
6
7
8
9
Level-wise (일반적인 방식):
같은 깊이의 모든 노드를 분할
→ 균형 잡힌 트리, 과적합 위험 낮음
Leaf-wise (LightGBM):
손실 감소가 가장 큰 Leaf 하나만 분할
→ 불균형하지만 정확한 트리
→ 빠른 수렴, 소규모 데이터에서 과적합 주의
→ num_leaves로 복잡도 제어
언제 사용하는가
사용해야 할 때
| 상황 | 이유 |
|---|---|
| 대용량 데이터 (수십만 건 이상) | GOSS + EFB로 XGBoost보다 훨씬 빠른 학습 |
| 학습 속도가 중요한 실무 환경 | 동일 성능 기준 가장 빠른 Boosting 계열 |
| 범주형 변수가 많을 때 | One-Hot Encoding 없이 category 타입으로 직접 처리 |
| 메모리가 제한적인 환경 | EFB로 변수 수를 효과적으로 압축 |
| 빠른 실험이 필요할 때 | 빠른 반복 학습으로 하이퍼파라미터 탐색 용이 |
사용하지 말아야 할 때
| 상황 | 이유 |
|---|---|
| 소규모 데이터 (수천 건 이하) | Leaf-wise 성장으로 과적합 위험 높아짐 |
| 모델 해석이 핵심인 경우 | 블랙박스 모델로 개별 예측 설명 어려움 |
| 이미지, 텍스트, 시계열 | 딥러닝 계열이 더 적합 |
실무 활용 사례
1
2
3
4
금융 : 대규모 신용카드 사기 탐지 (실시간성 + 대용량)
커머스 : 수백만 고객의 구매 예측 및 추천
광고 : 수억 건 로그 기반 CTR(클릭률) 예측
에너지 : 대규모 센서 데이터 기반 설비 이상 탐지
Parameters
| Parameter | 설명 | Default |
|---|---|---|
n_estimators | 트리 수 | 100 |
learning_rate | 학습률 | 0.1 |
num_leaves | 트리당 최대 리프 수 (핵심) | 31 |
max_depth | 최대 깊이 (-1: 제한 없음) | -1 |
min_child_samples | Leaf 노드의 최소 샘플 수 | 20 |
subsample | 행 샘플링 비율 | 1.0 |
colsample_bytree | 변수 샘플링 비율 | 1.0 |
num_leaves: 트리당 최대 Leaf 수 — LightGBM의 핵심 파라미터- 값 변화별 효과
- 클수록 → 복잡한 모델, 과적합 ↑, 학습 시간 증가
- 작을수록 → 단순한 모델, 과적합 ↓
2^max_depth보다 작게 설정 권장- 소규모 데이터: 31 이하, 대규모 데이터: 64~128 시도
- 값 변화별 효과
learning_rate: 각 트리의 기여도 축소 계수- 값 변화별 효과
- 클수록 → 빠른 수렴, 과적합 위험 ↑
- 작을수록 → 안정적인 수렴,
n_estimators더 필요
- 실무 권장:
0.05~0.1+ Early Stopping 조합
- 값 변화별 효과
min_child_samples: Leaf 노드에 필요한 최소 샘플 수- 값 변화별 효과
- 클수록 → 분할 제한 → 과적합 ↓
- 작을수록 → 잦은 분할 → 과적합 ↑
- 소규모 데이터에서 과적합 방지 시 20~50으로 증가
- 값 변화별 효과
subsample/colsample_bytree: 행/열 샘플링 비율- 값 변화별 효과
1.0보다 작으면 → 무작위성 추가 → 과적합 ↓, 학습 빠름- 실무 권장:
0.7~0.9
- 값 변화별 효과
실습 — Titanic 생존자 예측
I. Library & Data Load
1
2
3
4
5
6
7
8
9
10
11
12
import warnings
warnings.filterwarnings(action = 'ignore') # 경고 메시지 숨김
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from lightgbm import LGBMClassifier
import lightgbm as lgb
from sklearn.metrics import accuracy_score, confusion_matrix
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'AppleGothic'
1
titanic = pd.read_csv('./Data/Titanic.csv') # 데이터 로드
II. Preprocessing
II-I. Feature Engineering
1
2
3
4
5
6
7
8
9
10
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) # 원-핫 인코딩
II-II. Train & Test Split
1
2
3
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) # 훈련/테스트 분할 (75:25)
III. Model Train
1
2
3
4
5
6
7
8
9
10
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) # 모델 학습
IV. Early Stopping
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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_}") # 최적 트리 수 (조기 종료 시점)
V. 범주형 변수 직접 처리
LightGBM의 강점 — One-Hot Encoding 없이 범주형 변수를 그대로 사용 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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') # 정수형으로 변환
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) # 범주형 변수 직접 지정 (One-Hot 불필요)
print(f"정확도 : {LGB_cat.score(X_te_cat, y_te_cat) * 100:.2f}%")
VI. Evaluation Score
1
2
3
4
5
6
7
8
9
pred = LGB.predict(X_test) # 클래스 예측
cfx = confusion_matrix(y_test, pred) # 혼동 행렬 계산
# [0,0]=TN, [0,1]=FP, [1,0]=FN, [1,1]=TP
sensitivity = cfx[1, 1] / (cfx[1, 0] + cfx[1, 1]) # 민감도: TP / (TP + FN)
specificity = cfx[0, 0] / (cfx[0, 0] + cfx[0, 1]) # 특이도: TN / (TN + FP)
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.
