[Kaggle] 안전 운전자 예측 / LightGBM
- 미션 : 보험사에서 제공한 고객 데이터를 활용해 운전자가 보험을 청구할 확률 예측
- 문제유형 : 이진분류
- 평가지표 : 정규화된 지니계수
- 사용 모델 : LightGBM
- 캐글 노트북 : https://www.kaggle.com/jinkwonskk/i-lgb-modeling
안전 운전자 예측 경진대회 성능 개선 I / LGB Modeling
Explore and run machine learning code with Kaggle Notebooks | Using data from Porto Seguro’s Safe Driver Prediction
www.kaggle.com
먼저 LightGBM이란?
LightGBM (Light Gradient Boosting Machine)은 Microsoft에서 개발한 그래디언트 부스팅 프레임워크입니다.
기존의 그래디언트 부스팅 기법을 기반으로 하면서 속도와 효율성을 향상시킨 알고리즘으로,
대용량 데이터셋에 대한 빠른 학습 속도와 높은 효율성이 특징입니다.
LightGBM은 다양한 분야에서 분류, 회귀 및 랭킹 작업에 널리 사용됩니다.
# LightGBM의 특징!
- Leaf-wise 트리 성장: 대부분의 부스팅 알고리즘은 레벨별(level-wise)로 트리를 성장시키는 반면, LightGBM은 데이터 손실을 최소화하는 리프를 우선적으로 성장시키는 Leaf-wise 방식을 채택하여 더 나은 정확도를 제공할 수 있습니다.
굳이 많은 노드를 생성할 필요없이, 성능 개선에 유리한 쪽으로 모델을 만들어간다!
# 코드
# 안전 운전자 예측 경진대회 성능 개선 I : LightGBM 모델
import pandas as pd
# 데이터 경로
data_path = '/kaggle/input/porto-seguro-safe-driver-prediction/'
train = pd.read_csv(data_path + 'train.csv', index_col='id')
test = pd.read_csv(data_path + 'test.csv', index_col='id')
submission = pd.read_csv(data_path + 'sample_submission.csv', index_col='id')
## 피처 엔지니어링
### 데이터 합치기
all_data = pd.concat([train, test], ignore_index=True)
all_data = all_data.drop('target', axis=1) # 타깃값 제거
all_features = all_data.columns # 전체 피처
### 명목형 피처 원-핫 인코딩
from sklearn.preprocessing import OneHotEncoder
# 명목형 피처
cat_features = [feature for feature in all_features if 'cat' in feature]
# 원-핫 인코딩 적용
onehot_encoder = OneHotEncoder()
encoded_cat_matrix = onehot_encoder.fit_transform(all_data[cat_features])
### 파생 피처 추가
# '데이터 하나당 결측값 개수'를 파생 피처로 추가
all_data['num_missing'] = (all_data==-1).sum(axis=1)
# 명목형 피처, calc 분류의 피처를 제외한 피처
remaining_features = [feature for feature in all_features
if ('cat' not in feature and 'calc' not in feature)]
# num_missing을 remaining_features에 추가
remaining_features.append('num_missing')
# 분류가 ind인 피처
ind_features = [feature for feature in all_features if 'ind' in feature]
is_first_feature = True
for ind_feature in ind_features:
if is_first_feature:
all_data['mix_ind'] = all_data[ind_feature].astype(str) + '_'
is_first_feature = False
else:
all_data['mix_ind'] += all_data[ind_feature].astype(str) + '_'
all_data['mix_ind']
cat_count_features = []
for feature in cat_features+['mix_ind']:
val_counts_dict = all_data[feature].value_counts().to_dict()
all_data[f'{feature}_count'] = all_data[feature].apply(lambda x:
val_counts_dict[x])
cat_count_features.append(f'{feature}_count')
cat_count_features
### 필요 없는 피처 제거
from scipy import sparse
# 필요 없는 피처들
drop_features = ['ps_ind_14', 'ps_ind_10_bin', 'ps_ind_11_bin',
'ps_ind_12_bin', 'ps_ind_13_bin', 'ps_car_14']
# remaining_features, cat_count_features에서 drop_features를 제거한 데이터
all_data_remaining = all_data[remaining_features+cat_count_features].drop(drop_features, axis=1)
# 데이터 합치기
all_data_sprs = sparse.hstack([sparse.csr_matrix(all_data_remaining),
encoded_cat_matrix],
format='csr')
### 데이터 나누기
num_train = len(train) # 훈련 데이터 개수
# 훈련 데이터와 테스트 데이터 나누기
X = all_data_sprs[:num_train]
X_test = all_data_sprs[num_train:]
y = train['target'].values
### 정규화 지니계수 계산 함수
import numpy as np
def eval_gini(y_true, y_pred):
# 실제값과 예측값의 크기가 같은지 확인 (값이 다르면 오류 발생)
assert y_true.shape == y_pred.shape
n_samples = y_true.shape[0] # 데이터 개수
L_mid = np.linspace(1 / n_samples, 1, n_samples) # 대각선 값
# 1) 예측값에 대한 지니계수
pred_order = y_true[y_pred.argsort()] # y_pred 크기순으로 y_true 값 정렬
L_pred = np.cumsum(pred_order) / np.sum(pred_order) # 로렌츠 곡선
G_pred = np.sum(L_mid - L_pred) # 예측 값에 대한 지니계수
# 2) 예측이 완벽할 때 지니계수
true_order = y_true[y_true.argsort()] # y_true 크기순으로 y_true 값 정렬
L_true = np.cumsum(true_order) / np.sum(true_order) # 로렌츠 곡선
G_true = np.sum(L_mid - L_true) # 예측이 완벽할 때 지니계수
# 정규화된 지니계수
return G_pred / G_true
# LightGBM용 gini() 함수
def gini(preds, dtrain):
labels = dtrain.get_label()
return 'gini', eval_gini(labels, preds), True # 반환값
## 하이퍼파라미터 최적화
### 데이터셋 준비
import lightgbm as lgb
from sklearn.model_selection import train_test_split
# 8:2 비율로 훈련 데이터, 검증 데이터 분리 (베이지안 최적화 수행용)
X_train, X_valid, y_train, y_valid = train_test_split(X, y,
test_size=0.2,
random_state=0)
# 베이지안 최적화용 데이터셋
bayes_dtrain = lgb.Dataset(X_train, y_train)
bayes_dvalid = lgb.Dataset(X_valid, y_valid)
### 하이퍼파라미터 범위 설정
# 베이지안 최적화를 위한 하이퍼파라미터 범위
param_bounds = {'num_leaves': (30, 40),
'lambda_l1': (0.7, 0.9),
'lambda_l2': (0.9, 1),
'feature_fraction': (0.6, 0.7),
'bagging_fraction': (0.6, 0.9),
'min_child_samples': (6, 10),
'min_child_weight': (10, 40)}
# 값이 고정된 하이퍼파라미터
fixed_params = {'objective': 'binary',
'learning_rate': 0.005,
'bagging_freq': 1,
'force_row_wise': True,
'random_state': 1991}
### (베이지안 최적화용) 평가지표 계산 함수 작성
def eval_function(num_leaves, lambda_l1, lambda_l2, feature_fraction,
bagging_fraction, min_child_samples, min_child_weight):
'''최적화하려는 평가지표(지니계수) 계산 함수'''
# 베이지안 최적화를 수행할 하이퍼파라미터
params = {'num_leaves': int(round(num_leaves)),
'lambda_l1': lambda_l1,
'lambda_l2': lambda_l2,
'feature_fraction': feature_fraction,
'bagging_fraction': bagging_fraction,
'min_child_samples': int(round(min_child_samples)),
'min_child_weight': min_child_weight,
'feature_pre_filter': False}
# 고정된 하이퍼파라미터도 추가
params.update(fixed_params)
print('하이퍼파라미터:', params)
# LightGBM 모델 훈련
lgb_model = lgb.train(params=params,
train_set=bayes_dtrain,
num_boost_round=2500,
valid_sets=bayes_dvalid,
feval=gini,
early_stopping_rounds=300,
verbose_eval=False)
# 검증 데이터로 예측 수행
preds = lgb_model.predict(X_valid)
# 지니계수 계산
gini_score = eval_gini(y_valid, preds)
print(f'지니계수 : {gini_score}\n')
return gini_score
### 최적화 수행
from bayes_opt import BayesianOptimization
# 베이지안 최적화 객체 생성
optimizer = BayesianOptimization(f=eval_function, # 평가지표 계산 함수
pbounds=param_bounds, # 하이퍼파라미터 범위
random_state=0)
# 베이지안 최적화 수행
optimizer.maximize(init_points=3, n_iter=6)
### 결과 확인
# 평가함수 점수가 최대일 때 하이퍼파라미터
max_params = optimizer.max['params']
max_params
# 정수형 하이퍼파라미터 변환
max_params['num_leaves'] = int(round(max_params['num_leaves']))
max_params['min_child_samples'] = int(round(max_params['min_child_samples']))
# 값이 고정된 하이퍼파라미터 추가
max_params.update(fixed_params)
max_params
## 모델 훈련 및 성능 검증
from sklearn.model_selection import StratifiedKFold
# 층화 K 폴드 교차 검증기 생성
folds = StratifiedKFold(n_splits=5, shuffle=True, random_state=1991)
# OOF 방식으로 훈련된 모델로 검증 데이터 타깃값을 예측한 확률을 담을 1차원 배열
oof_val_preds = np.zeros(X.shape[0])
# OOF 방식으로 훈련된 모델로 테스트 데이터 타깃값을 예측한 확률을 담을 1차원 배열
oof_test_preds = np.zeros(X_test.shape[0])
# OOF 방식으로 모델 훈련, 검증, 예측
for idx, (train_idx, valid_idx) in enumerate(folds.split(X, y)):
# 각 폴드를 구분하는 문구 출력
print('#'*40, f'폴드 {idx+1} / 폴드 {folds.n_splits}', '#'*40)
# 훈련용 데이터, 검증용 데이터 설정
X_train, y_train = X[train_idx], y[train_idx] # 훈련용 데이터
X_valid, y_valid = X[valid_idx], y[valid_idx] # 검증용 데이터
# LightGBM 전용 데이터셋 생성
dtrain = lgb.Dataset(X_train, y_train) # LightGBM 전용 훈련 데이터셋
dvalid = lgb.Dataset(X_valid, y_valid) # LightGBM 전용 검증 데이터셋
# LightGBM 모델 훈련
lgb_model = lgb.train(params=max_params, # 최적 하이퍼파라미터
train_set=dtrain, # 훈련 데이터셋
num_boost_round=2500, # 부스팅 반복 횟수
valid_sets=dvalid, # 성능 평가용 검증 데이터셋
feval=gini, # 검증용 평가지표
early_stopping_rounds=300, # 조기종료 조건
verbose_eval=100) # 100번째마다 점수 출력
# 테스트 데이터를 활용해 OOF 예측
oof_test_preds += lgb_model.predict(X_test)/folds.n_splits
# 모델 성능 평가를 위한 검증 데이터 타깃값 예측
oof_val_preds[valid_idx] += lgb_model.predict(X_valid)
# 검증 데이터 예측확률에 대한 정규화 지니계수
gini_score = eval_gini(y_valid, oof_val_preds[valid_idx])
print(f'폴드 {idx+1} 지니계수 : {gini_score}\n')
print('OOF 검증 데이터 지니계수 :', eval_gini(y, oof_val_preds))
## 예측 및 결과 제출
submission['target'] = oof_test_preds
submission.to_csv('submission.csv')
# 새롭게 알게된 점
1. 피처엔지니어링 과정에서 결측값, 고유값 개수를 피처로 생성해도 성능개선에 도움을 주기도 한다.
2. OOF 예측을 활용해 다양한 훈련, 테스트 데이터를 통해 다양한 훈련 모델을 만들고 각 예측값을 반영하여 하나의 예측값을 만든다.
3. 베이지안 최적화 객체를 만들고 필요한 변수(평가지표 계산 함수, 하이퍼파라미터 범위 등)들을 전달하는 과정에 대해서 알 수 있었다.
4. 시계열 데이터는 중간에 OOF 예측 과정에서 데이터를 셔플하면 안된다.