저번 블로그의 XGBoost, LightGBM 실습에 이어서 신용카드 사기 검출 실습을 하면서 공부를 해봤습니다~!
1. Import
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
os.chdir('./Data/creditcard')
credit_df=pd.read_csv('creditcard.csv')
#의미없는 Time 변수 제거
del credit_df['Time']
2. 데이터 분리
#데이터프레임 복사후 데이터 가공처리
def get_train_test_split(df=None):
#인자로 입력된 DataFrame의 사전 데이터 가공(Time 변수 삭제)이 완료된 복사 DataFrame 반환
df_copy=credit_df
#DataFrame의 맨 마지막 칼럼이 레이블, 나머지는 피처들
x_features=df_copy.iloc[:,:-1]
y_target=df_copy.iloc[:,-1]
#데이터 분리, stratify 기반으로 분리-train,test 비율 동일하게
from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test= train_test_split(x_features,y_target, test_size=0.3, random_state=0,stratify=y_target)
#데이터 반환
return x_train,x_test,y_train,y_test
x_train,x_test,y_train,y_test=get_train_test_split(credit_df)
Time 변수를 제거 후에 feature와 label을 분리하는데 분리 시 샘플링 방식은 나뉟stratify로 해줍니다.
stratify 값을 y_target으로 지정해주면 각각의class 비율(ratio)을 train / validation에 유지해 줍니다. (한 쪽에쏠려서 분배되는 것을 방지합니다)
# 레이블 값이 비율을 백분율로 환산하여 서로 비슷하게 분활됐는지 확인
print('학습 데이터 레이블 값 비율')
print(y_train.value_counts()/y_train.shape[0]*100)
print('테스트 데이터 레이블 값 비율')
print(y_test.value_counts()/y_test.shape[0]*100)
학습 데이터 레이블 값 비율
0 99.827451
1 0.172549
Name: Class, dtype: float64
테스트 데이터 레이블 값 비율
0 99.826785
1 0.173215
Name: Class, dtype: float64
amount 변수의 분포를 확인 후, 값이 한쪽으로 치우쳐 있다면 scaling을 진행하여 다시 예측해보겠습니다
데이터 분포도 변환 후 모델 학습/예측/평가
import seaborn as sns
plt.figure(figsize=(8,4))
plt.xticks(range(0,30000,1000), rotation=60)
#히시토그램 시각화
sns.distplot(credit_df['Amount'])
# 1000불 이하가 대부분이고, 2700불은 드물지만 존재함, 이를 stardardScaler로 표준 정규 분포 형태로 변환 후 예측성능 비교실시
전처리 작업으로 scaling 하는 함수를 만듭니다
def get_preprocessed_df(df=None):
from sklearn.preprocessing import StandardScaler
df_copy=credit_df
scaler=StandardScaler()
amount_n= scaler.fit_transform(df_copy['Amount'].values.reshape(-1,1))
#변환된 amount를 Amount_scaled로 이름 변경 후, DataFrame 맨 앞 컬럼으로 입력
df_copy.insert(0, "Amount_scaled",amount_n)
#기존 Time, amount 제거
df_copy.drop(["Amount"], axis=1, inplace=True)
return df_copy
scaling은 평균 0, 분산 1을 만드는 StandardScaler를 사용합니다.
스케일링 후 로지스틱, LightGBM을 다시 실행해 봅니다
#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)
print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)
print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)
### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85282 13]
[ 56 92]]
정확도 0.9992, 정밀도 0.8762, 재현율 0.6216, F1:0.7273, AUC:0.9647
### lightGBM 예측 성능 ###
오차 행렬
[[85290 5]
[ 36 112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.981
스케일링 전후 차이가 미비했습니다(교재와는 다른 결과를 보였습니다.
amount를 log을 취해서 정규분포와 근사하게 만든 후 다시 모델을 실행해 보겠습니다.
로지스틱 & LightGBM(amount log 변환 件)
def get_preprocessed_df(df=None):
df_copy=df.copy()
#넘파이의 log1p()를 이용해 amount를 로그 변환
amount_n= np.log1p(df_copy['Amount'])
df_copy.insert(0, "Amount_Scaled", amount_n)
df_copy.drop(["Amount"], axis=1, inplace=True)
return df_copy
df_copy=get_preprocessed_df(credit_df)
#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)
print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)
print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)
### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85282 13]
[ 56 92]]
정확도 0.9992, 정밀도 0.8762, 재현율 0.6216, F1:0.7273, AUC:0.9647
### lightGBM 예측 성능 ###
오차 행렬
[[85290 5]
[ 36 112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9812
amount의 log변환도 같은 위 결과와 같은 성능을 보였습니다.
개선이 미비한 원인 중 이상치의 존재일 가능성이 있으므로 이를 제거 후 성능 비교를 다시 해봅니다.
이상치 제거 후 성능 비교
-IQR로 이상치를 파악하기 전에 먼저 corr()를 이용해서 레이블의 중요 피처를 구별하여 해당되는 것들만 제거합니다.
# 상관도
import seaborn as sns
try:
del credit_df['Amount_scaled']
except:
pass
plt.figure(figsize=(16,16))
corr=credit_df.corr()
sns.heatmap(corr, cmap="RdBu")
레이블인 class와 상관관계가 높은 것은 v14,17인데, 이 중에서 v14 만 이상치 제거 실시합니다
import numpy as np
#이상치 인덱스를 반환하는 함수 생성
def get_outlier(df=None, column=None, weight=1.5):
#fraud에 해당하는 column 데이터만 추출, 1/4 분위와 3/4 분위 지점을 np.percentile로 구함
fraud = df[df['Class']==1][column]
quantile_25 = np.percentile(fraud.values, 25)
quantile_75 = np.percentile(fraud.values, 75)
#IQR을 구하고. IQR에 1.5를 곱해 최댓값과 최솟값 지점 구함
iqr= quantile_75-quantile_25
iqr_weight=iqr*weight
lowest_val=quantile_25-iqr_weight
highest_val=quantile_75+iqr_weight
#최대값보다 크거나, 최솟값보다 작은 값을 이상치 데이터로 설정하고 DataFram3 index 반환
outlier_index = fraud[(fraud< lowest_val) | (fraud> highest_val)].index
return outlier_index
get_outlier 함수를 통해 V14칼럼의 이상치를 제거해줍니다
outlier_index=get_outlier(df=credit_df, column='V14', weight=1.5)
print('이상치 데이터 인덱스:', outlier_index)
이상치 데이터 인덱스: Int64Index([8296, 8615, 9035, 9252], dtype='int64')
def get_preprocessed_df(df=None):
df_copy=df.copy()
#넘파이의 log1p()를 이용해 amount를 로그 변환
amount_n= np.log1p(df_copy['Amount'])
df_copy.insert(0, "Amount_Scaled", amount_n)
try:
df_copy.drop(["Time","Amount"], axis=1, inplace=True)
except:
df_copy.drop(["Amount"], axis=1, inplace=True)
#이상치 제거하는 로직 추가
outlier_index=get_outlier(df=df, column='V14', weight=1.5)
df_copy.drop(outlier_index, axis=0, inplace=True)
return df_copy
이상치를 제거한 로지스틱 VS LightGBM 성능 비교
#amount를 정규 분포 형태로 변환 후 로지스틱 회귀 및 LightGBM 수행
df_copy=get_preprocessed_df(credit_df)
x_train,x_test,y_train,y_test = get_train_test_split(df_copy)
print("### 로지스틱 회귀 예측 성능 ###")
lr_clf= LogisticRegression()
get_model_train_eval(lr_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)
print('\n')
print("### lightGBM 예측 성능 ###")
lightgbm_clf=LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1,boost_from_average=False)
get_model_train_eval(lightgbm_clf, ftr_train=x_train,ftr_test=x_test, tar_train=y_train,tar_test=y_test)
### 로지스틱 회귀 예측 성능 ###
오차 행렬
[[85281 14]
[ 57 91]]
정확도 0.9992, 정밀도 0.8667, 재현율 0.6149, F1:0.7194, AUC:0.9564
### lightGBM 예측 성능 ###
오차 행렬
[[85290 5]
[ 36 112]]
정확도 0.9995, 정밀도 0.9573, 재현율 0.7568, F1:0.8453, AUC:0.9790
이상치 제거 후 성능 비교를 해본 결과 눈에 띄는 개선 점은 없었습니다.
이번엔 smote(오버 샘플링)을 적용하여 모델 성능 비교를 해보겠습니다.
from imblearn.over_sampling import SMOTE
#smote는 train 데이터에만 적용한다
smote= SMOTE(random_state=0)
x_train_over,y_train_over= smote.fit_sample(x_train,y_train)
print("SMOTE 적용 전 학습용 피처/레이블 데이터 세트:", x_train.shape, y_train.shape)
print("SMOTE 적용 후 학습용 피처/레이블 데이터 세트:", x_train_over.shape, y_train_over.shape)
print("SMOTE 적용 후 레이블 값 분포: \n", pd.Series(y_train_over).value_counts())
SMOTE 적용 전 학습용 피처/레이블 데이터 세트: (199364, 29) (199364,)
SMOTE 적용 후 학습용 피처/레이블 데이터 세트: (398040, 29) (398040,)
SMOTE 적용 후 레이블 값 분포:
1 199020
0 199020
Name: Class, dtype: int64
smote는 학습 데이터 세트에만 적용시켜야 합니다. smote을 적용하여 199364건->398040건으로 2배 가까이 증가했습니다. 이 데이터를 기반으로 모델을 실행하겠습니다.
조금 많은 페이지였는데 중간마다 생각 좀 해야 할 부분들이 있어서 예상보다 진도를 많이 못 나간 것 같아요
중요한 건 진도보다는 이해가 중요하니까 이번만큼은 시간에 쫓기지 않고 임하려고 합니다
지금은 방법만을 배우고 있는 것 같아서 미완성이라는 느낌을 받습니다
내가 쓰고있는 알고리즘이 수학적으로 어떻게 연결이 되고 작동이 되는지를 알아야 비로소 속이 좀 풀릴 것 같아요 지금은 많은 기법들을 실습하고 이해하는데만 꽤 많은 시간이 걸릴 것 같더라고요 정말 많~~ 은 기법들이 있어서 깜짝 놀랐습니다 잠시 딴 얘기로 넘어갔네요; 다시 본론으로 와서
실습을 끝난 후에는 카카오브레인의 TGnet을 다시 봤습니다 초보 지식으로는 이해하는데 무리가 있었고 적용하기에도 힘들 것 같더군요
하지만 좀 깜짝놀랄만한 걸 알게 되었습니다 시간 안내 임베딩이라는 건데
정말 사람처럼 시간적인 개념을 학습 시켜서 방대한 시퀀스 데이터가 필요 없게끔 만드는 기술인 것 같아요
이런 개념이 딥러닝과 가깝다고 기재한 카카오브레인팀이 대단해 보였습니다
덕분에 저도 딥러닝에 대해서 좀더 다른 시각을 바라볼 수 있는 눈이 생긴 것 같아서 기뻤습니다
제 프로젝트에 이런 아이디어를 적용 할순 없는지 , 그리고 새로운 아이디어는 있는지 탐색을 하고 싶은 욕구가 오릅니다!
스태킹 기법은 말로만 들었지 실제로 실습해본 건 오늘이 처음이었습니다
예측한 데이터를 기반으로 다시 예측하는 과정이 흥미로웠습니다:) 그런데 서적에서는 실제 현실에서는 잘 사용되지 않는다는 말에 좀 아쉬웠습니다 ㅠ 예측을 다시 예측한다는 개념이 현실에서는 잘 맞아떨어지지 않은가 봅니다
그리고 문뜩 생각이 드는 게,
예측을 100%로 끌어올리는 미지의 숫자가 있진 않을까 라는..
몇십 년 후 사람을 뛰어넘는 인공지능이 나왔을 때
그 인공지능은 그 숫자를 찾아 내진 않을까 상상해봤습니다
회귀에서 잡음은 어쩔 수 없는 현상이라고 배웠는데 이 잡음을 찾아내야 한다면 뭘 고려해야 할까
평소에도 상상을 많이 하는 편이라서 공부하다가 나도 모르게 그쪽으로 빠집니다
아인슈타인? 잘 생각이 안 나지만 어느 수학자가 말하길 모든 현상은 수학적으로 표현 가능하다고 합니다
그러면 주식 예측이나 잡음처럼 예측하기 어려운 분야에 딱 들어맞는, 사회적 현상을 모두 고려한 최적의 숫자를 찾아낸다면 인공지능 개발자가 더이상 필요 없게 되진 않을까 합니다
공부 기록을 해야 하는데 적다 보면 맨날 생각의 흐름대로 흘러 가네요
다음번엔 좀 더 공부 기록 다운 기록을 적어보도록 노력해 보겠습니다
오늘 실습한 내용은 매일 포스팅 작업 중입니다 ~ 이번주 안으로 완성해서 이 블로그 글에 url 남기겠습니다
import pandas as pd
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import seaborn as sns
import matplotlib.pyplot as plt
#데이터 불러오기
cancer = load_breast_cancer()
data_df= pd.DataFrame(cancer.data, columns=cancer.feature_names)
data_df.head()
* voting의 인자는 estimators, voting
* estimators : 개별 분류기를 튜플로 담아 넣는다
* voting : hard or soft (defalt hard)
# 개별 분류기 객체 생성
lr_clf= LogisticRegression()
knn_clf=KNeighborsClassifier(n_neighbors=8)
#개별 모델을 앙상블 모델로 구현
#soft : 각 분류기에서 출력된 레이블의 확률을 평균 낸것들 가장 높은걸 최종결과값으로 선정
vo_clf =VotingClassifier(estimators=[("LR",lr_clf), ('KNN', knn_clf)], voting='soft')
x_train,x_test,y_train,y_test = train_test_split(cancer.data, cancer.target, test_size=0.2, random_state=156)
#votinclassifier 학습/예측/평가
vo_clf.fit(x_train, y_train)
pred=vo_clf.predict(x_test)
print('voting 분류기 정확도: {0:.4f}'.format(accuracy_score(y_test,pred)))
#개별 모델의 학습/예측/평가
classifiers= [lr_clf, knn_clf]
for classifier in classifiers:
classifier.fit(x_train,y_train)
pred=classifier.predict(x_test)
class_name= classifier.__class__.__name__
print('{0} 정확도: {1:.4f}'.format(class_name, accuracy_score(y_test,pred)))
랜덤포레스트
* 중첩되는 데이터 세트를 생성 (부트스트래핑)
* 부트스트랩: 통계학에서는 데이터 세트를 임의로 만들어서 개별 평균의 분포도를 측정하는 목적을 위한 샘플링 방식을 지칭
from sklearn.ensemble import RandomForestClassifier
#데이터 분리
x_train,x_test,y_train,y_test = train_test_split(cancer.data, cancer.target, test_size=0.2, random_state=156)
#랜덤 포레스트 학습 및 별도의 테스트 세트로 예측 성능 평가
rf_clf=RandomForestClassifier(random_state=0)
rf_clf.fit(x_train,y_train)
pred=rf_clf.predict(x_test)
accuracy = accuracy_score(y_test,pred)
print('랜덤 포레스트 정확도:{0:.4f}'.format(accuracy))
* 여러 개의 약한 학습기를 순차적으로 학습-예측 후 잘못 예측한 데이터에 가중치를 부여함으로써 오류개선
* 가중치 업데이트 방식은 경사 하강법이며 오류 값은 true-pred 값
* 오류값을 최소화하는 방향으로 도달
* 과적합에도 뛰어난 예측 성능 발휘, 수행시간의 증가
from sklearn.ensemble import GradientBoostingClassifier
import time
#데이터 불러오기
x_train,x_test,y_train,y_test = train_test_split(cancer.data, cancer.target, test_size=0.2, random_state=156)
#GBM 수행시간 측정을 위한 시작 시간 설정
start_time =time.time()
gb_clf=GradientBoostingClassifier(random_state=0)
gb_clf.fit(x_train,y_train)
pred=gb_clf.predict(x_test)
accuracy = accuracy_score(y_test, pred)
print('GBM 정확도: {0:.4f}'.format(accuracy))
print('GBM 수행시간: {0:.1f}초'.format(time.time()-start_time))
GBM 정확도: 0.9561
GBM 수행시간: 0.4초