본문 바로가기
Python/ML&DL

[파이썬, Python] 머신러닝 - 6️⃣ LightGBM

by coding-choonsik 2023. 6. 18.
728x90
반응형
SMALL

📄 예제에 사용한 파일

credit.csv
3.04MB

 

1. credit 데이터셋 알아보기

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
credit_df = pd.read_csv('/content/drive/MyDrive/KDT/Python/4.머신러닝 딥러닝/credit.csv')
credit_df

▲ 컬럼들이  ... 으로 생략되어 보임

 

✅ 컬럼 수를 최대 30개로 보기

pd.set_option('display.max_columns', 30)
credit_df

 

✅ credit_df 정보 보기

credit_df.info()

 

✅ 개인정보와 같이 모델학습에 필요없는 피쳐를 삭제

credit_df.drop(['ID','Customer_ID','Name','SSN'], axis=1, inplace=True)

 

✅ Credit_Score 열의 데이터 종류와 데이터 개수 알아보기

credit_df['Credit_Score'].value_counts()

 

 

✅ Object형 데이터 라벨인코딩 하기

credit_df['Credit_Score'] = credit_df['Credit_Score'].replace({'Poor':0, 'Standard':1,'Good':2})
credit_df['Credit_Score']

 

 

✅ 수치형 데이터 정보 보기

 

✅ 리볼빙을 사용하는지에 대한 여부에 대한 신용점수를 시각화 하여 보기

sns.barplot(x='Payment_of_Min_Amount', y = 'Credit_Score', data=credit_df)

 

 

▲  NM : 간헐적 사용  ➡ 리볼빙 사용하지 않는것이 신용점수가 좋은 것으로 추측

 

 

✅ 직업별 신용점수를 시각화 하여 알아보기

plt.figure(figsize=(20,5))
sns.barplot(x='Occupation',y='Credit_Score', data=credit_df)

▲ 직업별 편차가 별로 없음

 

✅ 각 피쳐별로 상관관계를 알아보기 위해 히트맵으로 보기

plt.figure(figsize=(12,12))
sns.heatmap(credit_df.corr(), cmap='coolwarm', vmin=-1, vmax=1, annot=True)

 

1. Delay_from_due_date와 Credit_Score은 반비례 관계(연체일이 많을수록 신용도가 낮음)
2. Monthly_Balance와 Credit_Score는 비례 관계(월별 잔고가 높으면 신용도가 높음)
3. Credit_Utilization_Ratio와 Credit_Score는 약간의 비례관계(신용카드 사용률(한도내)이 높으면 신용도가 높음)

 

✅ credit_df의 정보 보기

credit_df.info()

 

 

✅ credit_df 컬럼 중 dtype이  object인 컬럼을 보기

for i in credit_df.columns:
  if credit_df[i].dtypes == 'O':
    print(i)
    
>>> Age
Occupation
Annual_Income
Num_of_Loan
Type_of_Loan
Num_of_Delayed_Payment
Outstanding_Debt
Credit_History_Age
Payment_of_Min_Amount
Amount_invested_monthly
Payment_Behaviour

 

✅ 데이터에서  '_'와같은 이상한 문자 없애기

for i in ['Age', 'Annual_Income', 'Num_of_Loan', 'Num_of_Delayed_Payment', 'Outstanding_Debt', 'Amount_invested_monthly']:
  credit_df[i] = pd.to_numeric(credit_df[i].str.replace('_',''))

 

✅  Credit_History_Age의 데이터를 개월수로 변경(예: 22 Years and 1 Months -> 22*12 + 1) 

# Months를 먼저 없앰
credit_df['Credit_History_Age'] = credit_df['Credit_History_Age'].str.replace(' Months','')

credit_df['Credit_History_Age']

 

# Years and 를 기준으로 split 후 expnad=True 옵션을 줘서 0열(Years)과 1열(Months)로 만듦
# pd.to_numeric()으로 숫자 변환 후 Years * 12 + Months
credit_df['Credit_History_Age'] = pd.to_numeric(credit_df['Credit_History_Age'].str.split(' Years and ', expand=True)[0]) * 12 + pd.to_numeric(credit_df['Credit_History_Age'].str.split(' Years and ', expand=True)[1])
credit_df.head()

 

credit_df.describe()

 

 

✅ Age가  0보다 작은 데이터 보기

credit_df[credit_df['Age'] < 0]

 

✅ Age가 0보다 큰 사람들만 저장하기

credit_df = credit_df[credit_df['Age'] >= 0]

 

✅ Age를 내림차순으로 정렬하고 30개 데이터 보기

credit_df['Age'].sort_values(ascending=True).tail(30)

▲ 나이의 데이터가 이상함(?)

 

✅ 나이를 boxplot으로 알아보기

sns.boxplot(y=credit_df['Age'])

▲ 데이터가 일렬로 있음(이상치라고 보기엔 애매)

 

 

✅  나이가100살 이상인 사람들의 데이터를 Age순으로 정렬

credit_df[credit_df['Age']>100].sort_values('Age')

 

✅ 나이가 120 미만인 사람들만 저장

credit_df = credit_df[credit_df['Age']<120]

 

✅ 수치형 데이터 보기

credit_df.describe()

 

✅ 은행 계좌가 20개 이상인 사람들의 비율 알아보기

len(credit_df[credit_df['Num_Bank_Accounts'] > 20]) / len(credit_df)
>>> 0.013029853207982847

# 은행계좌가 30개 이상인 사람들의 비율 - 20개와 차이 없음
len(credit_df[credit_df['Num_Bank_Accounts'] > 30]) / len(credit_df)
>>> 0.013029853207982847

# 은행계좌가 10개 이상인 사람들의 비율 - 20, 30개와 차이 없음
len(credit_df[credit_df['Num_Bank_Accounts'] > 10]) / len(credit_df)
>>> 0.013029853207982847

➡ 은행계좌가 10개 이상인 사람들의 데이터를 이상치로 판단

 

 

 

✅ 은행계좌가 10개 이하인 사람들만 저장하기

credit_df = credit_df[credit_df['Num_Bank_Accounts'] <=10]

credit_df.describe()

 

✅ 신용카드가 20개 이상인 사람들의 비율

len(credit_df[credit_df['Num_Credit_Card'] > 20]) / len(credit_df)
>>> 0.021975267379679145

# 신용카드가 10개 이상인 사람들의 비율
len(credit_df[credit_df['Num_Credit_Card'] > 10]) / len(credit_df)
>>> 0.022142379679144383

# 신용카드가 30개 이상인 사람들의 비율
len(credit_df[credit_df['Num_Credit_Card'] > 30]) / len(credit_df)
>>> 0.021724598930481284

➡ 신용카드가 10개 이상인 사람들의 데이터를 이상치로 판단

 

 

✅ 신용카드의 개수가 10개 이하인 사람들의 데이터만 저장하기

credit_df = credit_df[credit_df['Num_Credit_Card'] <=10]

credit_df.describe()

 

 

✅ 미국의 이자율이 40%인 것을 고려하여 이자율이 40보다 작은 데이터들만 저장

credit_df = credit_df[credit_df['Interest_Rate'] <=40]

 

✅ 대출 건수가 10개 이상인 사람들의 비율

len(credit_df[credit_df['Num_of_Loan'] > 10]) / len(credit_df)

>>> 0.005310350831374598  # 약 5%

 

대출받은 건수가 10개 이하이면서 0개 이상인 데이터들만 저장

credit_df = credit_df[(credit_df['Num_of_Loan'] <=10) &  (credit_df['Num_of_Loan'] >= 0)]

 

Delay_from_due_date가 0보다 큰 데이터들만 저장

credit_df = credit_df[credit_df['Delay_from_due_date'] >=0]

 

 Num_of_Delayed_Payment의 이상치를 정제

len(credit_df[credit_df['Num_of_Delayed_Payment'] > 10]) / len(credit_df)
>>> 0.6184971098265896

# 20개 이상인 데이터
len(credit_df[credit_df['Num_of_Delayed_Payment'] > 20]) / len(credit_df)
>>> 0.1169832094687586

# 30개 이상인 데이터
len(credit_df[credit_df['Num_of_Delayed_Payment'] > 30]) / len(credit_df)
>>> 0.007340122947059363

# 40개 이상인 데이터
len(credit_df[credit_df['Num_of_Delayed_Payment'] > 40]) / len(credit_df)
>>> 0.007340122947059363

# Num_of_Delayed_Payment가 0보다 크거나 같고 30개 이하인 데이터들만 저장
credit_df = credit_df[(credit_df['Num_of_Delayed_Payment'] <=30) &  (credit_df['Num_of_Delayed_Payment'] >= 0)]

# Num_Credit_Inquiries가 NaN이면 0으로 바꿔줌
credit_df['Num_Credit_Inquiries'] = credit_df['Num_Credit_Inquiries'].fillna(0)

 

✅ NaN값 알아보기

credit_df.isna().sum()

 

💡 Credit_History_Age, Amount_invested_monthly, Monthly_Balance 의 데이터가 NaN인 경우 어떤걸로 채울까??

 

sns.displot(credit_df['Credit_History_Age'])

▲0인 사람이 거의 없음. 평균이나 중앙값으로 결측치를 채우는 것이 낫다고 판단

 

sns.displot(credit_df['Amount_invested_monthly'])

▲ 0에 데이터가 몰려있음.

 

sns.displot(credit_df['Monthly_Balance'])

▲0인 사람이 거의 없음. 평균이나 중앙값으로 결측치를 채우는 것이 낫다고 판단

 

💡 NaN데이터를 중앙값으로 채우는 것이 낫다는 판단!

credit_df = credit_df.fillna(credit_df.median())

 

✅ 다시 NaN 데이터 알아보기

credit_df.isna().sum()

 

✅ Type_of_Loan에 대해 원 핫 인코딩 하기

 

# 1. 해당 열에 NaN값을 'No Loan'으로 대체
credit_df['Type_of_Loan'] = credit_df['Type_of_Loan'].fillna('No Loan')

# 2. 데이터의 'and'글자를 없앰
credit_df['Type_of_Loan'] = credit_df['Type_of_Loan'].str.replace('and ','')

# 3. ', '를 기준으로 데이터를 나누고 set을 이용하여 중복값을 제거함
type_list =set(credit_df['Type_of_Loan'].str.split(', ').sum())
type_list

# 4. type_list의 개수만큼 돌면서 각 i값에 해당하는 새로운 파생변수를 만듦 -> x값이 Type_of_Loan열에 있으면 1, 없으면 0으로 채워짐
for i in type_list:
  credit_df[i] = credit_df['Type_of_Loan'].apply(lambda x: 1 if i in x else 0)
  
# 5. Type_of_Loan 열을 지움
credit_df.drop('Type_of_Loan', axis=1, inplace=True)

✅ credit_df 정보 보기

credit_df.info()

▲dtype이 object형인 컬럼 3개

 

✅ Occupation열의 데이터 확인해보기

credit_df['Occupation'].value_counts()

▲ 데이터를 보기 좋게 다른 단어로 대체

 

# 'Unknown'으로 대체하기
credit_df['Occupation'] = credit_df['Occupation'].replace('_______','Unknown')

credit_df['Occupation'].value_counts()

 

 

✅ 'Payment_of_Amount'열 데이터 알아보기

credit_df['Payment_of_Min_Amount'].value_counts()

 

✅ 'Payment_Behavior' 열 데이터 알아보기

credit_df['Payment_Behaviour'].value_counts()

▲ 알 수 없는 글자를 다른 단어로 대체

# 'Unknown'으로 대체하기
credit_df['Payment_Behaviour'] = credit_df['Payment_Behaviour'].replace('!@9#%8','Unknown')

credit_df['Payment_Behaviour'].value_counts()

 

✅ object형 데이터 원 핫 인코딩 하기

credit_df = pd.get_dummies(credit_df, columns=['Occupation','Payment_Behaviour','Payment_of_Min_Amount'])

 

✅ train 데이터와 test 데이터 나누기

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(credit_df.drop('Credit_Score', axis=1), credit_df['Credit_Score'], test_size=0.2, random_state=10)

2. LightGBM(LGBM)

  • Tree 기반 학습 알고리즘인 gradient boosting 방식의 프레임워크
  • Decision Tree, Random Forest는 균형 트리 분할(level wise, 데이터를 중요하다고 생각되는 피쳐를 통해 *균등하게* 나누기 때문에 속도가 조금 느림)방식이라면, LGBM은 리프 중심 트리 분할(leaf wise)
  • GBM(Gradient Boosting Machine): 모델1을 통해 y를 예측하고, 모델2에 데이터를 넣어 다시 y를 예측, 모델3에 넣어 y를 예측 ...하는 방식을 사용
  • 학습하는데 걸리는 시간이 적음(빠른 속도)
  • 메모리 사용량이 상대적으로 적은편
  • 적은 데이터셋을 사용할 경우 과대적합의 가능성이 매우 큼(피쳐를 무작위로 선택하여 나누기 때문에 과적합될 가능성이 큼)
  • 사이킷런에서 제공하지 않고 따로 존재
  • 예측과 회귀 둘 다 사용 가능
 📍 일반적으로 데이터가 10000개 이상은 사용해야 함!

 

from lightgbm import LGBMClassifier

# 모델 객체 생성
base_model = LGBMClassifier(random_state=10)

# 학습
base_model.fit(X_train, y_train)

# 예측
pred1 = base_model.predict(X_test)

✅ 학습에 대한 평가

from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, roc_auc_score

# accuracy_score
accuracy_score(y_test, pred1)
>>> 0.7351324337831084

# confusion matrix
confusion_matrix(y_test, pred1)  # 데이터가 한쪽으로 치우쳐져 있지 않음
>>> array([[407, 148,  27],
       [145, 896,  91],
       [  3, 116, 168]])
       
       
print(classification_report(y_test, pred1))

 

✅ 예측 클래스 알아보기

credit_df['Credit_Score'].value_counts()  # 3개 중 한개를 선택하게 됨


>>> 
1    5581
0    2918
2    1503
Name: Credit_Score, dtype: int64

 

✅ 클래스별 예측 확률 구하기

proba1 = base_model.predict_proba(X_test)
proba1

▲ 3개의 클래스 중 어떤 클래스로 예측했는지에 대한 확률

6.98737764e-02,8.38143193e-01, 9.19830307e-02  # 2번째를 채택
>>> (0.0698737764, 0.838143193, 0.0919830307)

 

✅ roc_auc_score 확인하기(multi_class='ovr': 분류하는 클래스가 3개이기 때문에 OvR 방식을 채택)

roc_auc_score(y_test, proba1, multi_class='ovr')
>>> 0.8972566425279517

3. RandomizedSearchCV

  • 분류기를 결정하고 해당 분류기의 최적의 하이퍼 파라미터를 찾기 위한 방법
  • 튜닝하고싶은 파라미터를 지정하여 파라미터 값의 범위를 정하고, n_iter값을 설정하여 해당 수 만큼 Random하게 조합하여 반복 적용
# n_estimators: 반복 수행하는 트리의 수를 몇개로 잡을 것인지(기본값: 100), 값을 크게 지정하면 학습시간도 오래걸리며 과적합의 가능성이 커짐
# max_depth: 트리의 최대 깊이(기본값: -1, 최대깊이를 지정하지 않고 할 수 있는데 까지 깊이를 내려가게 함)
# num_leaves: 결정 트리에 있는 최대 리프 노드(말단 노드)의 개수, 작은 데이터셋의 경우 값을 작게 설정하는 것이 좋음
# learning_rate: 학습률(기본값: 0.1), 모델이 각 훈련 단계에서 이전 단계의 오차를 얼마나 반영하여 업데이트할지를 결정, 학습 속도를 조절하는 역할
# 높은 학습 속도는 모델이 빠르게 훈련 데이터에 적응할 수 있도록 하지만, 너무 높으면 수렴하지 못하고 오버슈팅(overshooting) 문제가 발생
# 반대로, 낮은 학습 속도는 수렴하는 데 더 많은 시간이 걸리지만, 더 정확한 모델을 얻을 수 있음

params = {
    'n_estimators': [100, 300, 500, 1000],
    'max_depth': [-1, 30, 50, 100],
    'num_leaves': [5, 10, 20, 50],
    'learning_rate': [0.01, 0.05, 0.1, 0.5]
}

# 모델 객체 생성
lgbm = LGBMClassifier(random_state=10)

from sklearn.model_selection import RandomizedSearchCV

# n_iter=30: 위 params 객체에서 조합을 30개를 만듦
rand_lgbm = RandomizedSearchCV(lgbm, params, n_iter=30, random_state=10)

# 학습
rand_lgbm.fit(X_train, y_train)

# cv_results_: 결과에 대한 리포트
rand_lgbm.cv_results_

 

▲ 각 조합 별 정확도

 

# best_params_: 최적의 조합을 알려줌
rand_lgbm.best_params_
>>> {'num_leaves': 20, 'n_estimators': 300, 'max_depth': 50, 'learning_rate': 0.01}

# 최적의 하이퍼 파라미터 조합으로 모델 학습
lgbm = LGBMClassifier(random_state=10,num_leaves=20, n_estimators=300, max_depth=50, learning_rate=0.01)

lgbm.fit(X_train, y_train)

# 클래스별 예측 확률
proba2 = lgbm.predict_proba(X_test)
proba2

 

✅ roc_auc_score 알아보기

 

roc_auc_score(y_test, proba2, multi_class='ovr')
>>> 0.9024448184054178

# 하이퍼 파라미터 튜닝 전: 0.8972566425279517
# 하이퍼 파라미터 튜닝 후: 0.9024448184054178(성능 향상!😊)
0.9024448184054178 - 0.8972566425279517
>>> 0.005188175877466117

 

728x90
반응형
LIST