ML - 14. mini project _ CREDIT CARD FRAUD DETECTION

jaam._.mini·2024년 1월 31일
0

프로젝트 소개

  • 주제 : 신용카드 부정 사용자 검출
  • 데이터 : https://www.kaggle.com/MLG-ULB/CREDITCARDFRAUD
  • 개념
    • 신용카드와 같은 금융데이터들은 구하기가 어려움
    • 금융 데이터들의 데이터는 또한 다루기 쉽지 않음
    • 그러나 지능화되어가는 현대 범죄에 맞춰 사전 이상 징후 검출 등 금융 기관이 많은 노력을 기울이고 있음
      이 데이터 역시 센서를 이용한 사람의 행동 과정 유추처럼 머신러닝의 이용 분야 중 하나
  • 개요
    • 신용카드 사기 검출 분류용 데이터

    • 데이터에 class라는 이름의 컬럼이 사기 유무를 의미

    • calss 컬럼의 불균형이 극심해서 전체 데이터의 약 0.172%가 1(사기 Fraud)를 가짐

      • Class : Fraud 여유 (1 이면 Fraud)
      • Amount : 거래금액





데이터 확인


# 1) 데이터 읽기
import pandas as pd

data_path = './14. mini project_creditcard.csv'
raw_data = pd.read_csv(data_path)
raw_data.head()
# 2) 특성

raw_data.columns.values
# 3) 데이터 라벨 확인 (Class : 사기 유무)

raw_data['Class'].value_counts()

Frauds 0.17 % of the dataset

# 5) 데이터 선정
X = raw_data.iloc[:, 1:-1] # Time, Class 컬럼 제외
y = raw_data.iloc[:, -1] # 모든 행의 마지막 컬럼을 선택

X.shape, y.shape

((284807, 29), (284807,))



train_test_split 의 인자들 (https://wikidocs.net/193722)

  • stratify=y로 지정하면 레이블 데이터 y에 따라 학습 데이터셋과 테스트 데이터셋의 클래스 비율이 유지
  • random_state : 데이터를 나눌 때 사용되는 난수 시드, 이 값을 지정하지 않으면, 매번 실행할 때마다 다른 결과를 얻을 수 있음
  • test_size=0.2로 지정하면 전체 데이터셋의 20%를 테스트 데이터셋으로 사용
  • train_size : 학습 데이터셋의 크기 결정 (기본값은 None으로, 학습 데이터셋 크기를 1 - test_size로 결정)
  • shuffle : 데이터를 섞을지 여부를 결정
# 6) 데이터 나누기

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=13, stratify=y)
# 7) 나눈 데이터의 불균형 정도 확인 (y_train 원소의 갯수 세기)

import numpy as np

# 원소의 갯수 세기 : unique + return_counts
# return_counts=True : 각 원소의 중복 갯수가 담긴 배열이 반환/원소가 각각 몇개 존재하는지 확인
tmp = np.unique(y_train, return_counts=True)

tmp, tmp[1], tmp[1]/len(y_train)*100
# 8) 나눈 데이터의 불균형 정도 확인 (y_test 원소의 갯수 세기)

import numpy as np

tmp = np.unique(y_test, return_counts=True)

tmp, tmp[1], tmp[1]/len(y_test)*100 # %를 구한 것

(무식 ver.) 데이터 분석


# 1) 분류기 성능 return 함수 설정

from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score, roc_auc_score)

def get_clf_eval(y_test, pred):
    acc = accuracy_score(y_test, pred) # 정확도 : 정확하게 정답을 맞힌 비중
    pre = precision_score(y_test, pred) # 정밀도 : positive 예측치 중 실제 positive 관측치 비중
    re = recall_score(y_test, pred) # 재현율 : positive 관측치 중에서 실제로 예측된 비중
    f1 = f1_score(y_test, pred) # Precision과 recall의 조화평균(정밀도, 재현율 -> 평균)
    auc = roc_auc_score(y_test, pred) # 모델의 성능

    return acc, pre, re, f1, auc



from sklearn.metrics import confusion_matrix

def print_clf_eval(y_test, pred):
    confusion = confusion_matrix(y_test, pred)
    acc, pre, re, f1, auc = get_clf_eval(y_test, pred)

    print('=> confusion metrix')
    print(confusion)
    print('==================')

    print('Accuracy : {0:.4f}, Precision : {1:.4f} '.format(acc, pre))
    print('Recall : {0:.4f}, F1 : {1:.4f}, AUC : {2:.4f} '.format(re, f1, auc))

# (https://coduking.com/entry/ROC-curve-AUC-%EA%B0%9C%EB%85%90-%EB%B0%8F-sklearn-%EC%BD%94%EB%93%9C)
# (https://coduking.com/entry/%EB%B6%84%EB%A5%98%EB%AC%B8%EC%A0%9C-%EC%84%B1%EB%8A%A5%ED%8F%89%EA%B0%80-%EC%A7%80%ED%91%9C-Accuracy-Recall-Precision-F1-score-titanic-%EC%8B%A4%EC%8A%B5)
# 2) Logistic Regression

from sklearn.linear_model import LogisticRegression

lr_clf = LogisticRegression(random_state=13, solver='liblinear')
lr_clf.fit(X_train, y_train)
lr_pred = lr_clf.predict(X_test)

print_clf_eval(y_test, lr_pred)

Accuracy가 99.92%로 보이지만,
실제 1중에서 몇개를 맞췄는지 보는 Recall 의 값이 59%에 불과함
-> Fraud 검출을 못했다고 봐야 함
-> 더 성능을 끌어 올려야 함

# 3) Decision Tree

from sklearn.tree import DecisionTreeClassifier

dt_clf = DecisionTreeClassifier(random_state=13, max_depth=4)
dt_clf.fit(X_train, y_train)
dt_pred = dt_clf.predict(X_test)

print_clf_eval(y_test, dt_pred)

DecisionTreeClassifier 의 결과는 106개 중 42개가 틀렸고 71.62% 로 나옴.
이전 보다 높음

# 4) Random Forest

from sklearn.ensemble import RandomForestClassifier

rf_clf = RandomForestClassifier(random_state=13, n_jobs=-1, n_estimators=100)
rf_clf.fit(X_train, y_train)
rf_pred = rf_clf.predict(X_test)

print_clf_eval(y_test, rf_pred)

recall이 조금더 올라감.
이전보다 덜 틀린 38개

# 5) LightGBM

from lightgbm import LGBMClassifier

lgbm_clf = LGBMClassifier(n_estimators=1000, num_leaves=64, n_jobs=-1, boost_from_average=False)
lgbm_clf.fit(X_train, y_train)
lgbm_pred = lgbm_clf.predict(X_test)

print_clf_eval(y_test, lgbm_pred)

성능이 조-금 좋아진 느낌.

(한걸음 전진ver.) 분석

  • 은행 입장에서는 Recall이 좋을 것이다.
  • 사용자 입장에서는 Precision이 좋겠지.
  • 왜? ->>

  • get_clf_eval : 성능지표
# 1) 모델, 데이터를 주고 성능을 출력하는 함수

def get_result(model, X_train, y_train, X_test, y_test):
    model.fit(X_train, y_train)
    pred = model.predict(X_test)

    return get_clf_eval(y_test, pred)
# 2) 여러개 모델의 성능을 정리 -> DataFrame 반환

def get_result_pd(models, model_names, X_train, y_train, X_test, y_test):
    col_name = ['accuracy', 'precision', 'recall', 'f1', 'roc_auc']
    tmp = []

    for model in models:
        tmp.append(get_result(model, X_train, y_train, X_test, y_test))

    return pd.DataFrame(tmp, columns=col_name, index=model_names)
# 3) 4개의 분류모델 > 표 (정리)

import time

models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LinearReg', 'DecisionTree', 'RandomForest', 'LightGBM']

start_time = time.time()
results = get_result_pd(models, model_names, X_train, y_train, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results

(데이터 정리 & 크기 조정 / scaling ver.) 분석

  • 그래프로 'Amount'의 값이 어떻게 분포되어 있는지 확인
# 1) raw_data의 Amount 컬럼 확인

plt.figure(figsize=(10,5))
sns.distplot(raw_data['Amount'], color='b')
plt.show()
raw_data['Amount'].values

array([149.62, 2.69, 378.66, ..., 67.88, 10. , 217. ])

raw_data['Amount'].values.reshape(-1,1)
# reshape : https://domybestinlife.tistory.com/149
raw_data.iloc[:, 1:-2] # Time, Amount, Class 삭제

StandardScaler 를 통해,
'Amount'가 몰려있는 상태의 편형성을 바꿔보고 싶음

# 2) Amount + StandardScaler

from sklearn.preprocessing import StandardScaler

scaler = StandardScaler()
amount_n = scaler.fit_transform(raw_data['Amount'].values.reshape(-1,1))

raw_data_copy = raw_data.iloc[:, 1:-2]
raw_data_copy['Amount_Scaler'] = amount_n # StandardScaler 학습한 컬럼 생성
raw_data_copy.head()
# 3) 데이터 나누기 >> 재평가

X_train, X_test, y_train, y_test = train_test_split(raw_data_copy, y, test_size=0.3, random_state=13, stratify=y)

models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LinearReg', 'DecisionTree', 'RandomForest', 'LightGBM']

start_time = time.time()
results = get_result_pd(models, model_names, X_train, y_train, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results
  • models 안에 model이 넘어올건데,
    그 모델에다가 predict를 시키고, predict_proba를 X_test에 대해서 시켜 줌
    왜? ROC 커브를 그리려면 '확률값(predict_proba)'이 있어야 하기 때문

  • 대각선 그리는 방법 : plt.plot([0,1], [0,1], 'k--', label='random quess')

# 4) 모델별 ROC 커브

from sklearn.metrics import roc_curve

def draw_roc_curve(models, model_names, X_test, y_test):
    plt.figure(figsize=(10,10))

    for model in range(len(models)):
        pred = models[model].predict_proba(X_test)[:, 1]
        fpr, tpr, thresholds = roc_curve(y_test, pred)
        plt.plot(fpr, tpr, label=model_names[model])
	# 대각선
    plt.plot([0,1], [0,1], 'k--', label='random quess')
    plt.title('ROC')
    plt.legend()
    plt.grid()
    plt.show()

# draw_roc_curve(models, model_names, X_test, y_test)
from sklearn.metrics import roc_curve

def draw_roc_corve(models,model_names,  X_test, y_test):
    plt.figure(figsize=(10, 10))
    
    for model in range(len(models)):
        pred = models[model].predict_proba(X_test)[:, 1]
        fpr, tpr, thresholds = roc_curve(y_test, pred)
        plt.plot(fpr, tpr, label=model_names[model])

    plt.plot([0, 1], [0, 1], 'k--', label='random quess')
    plt.title('ROC')
    plt.legend()
    plt.grid()
    plt.show()

draw_roc_corve(models, model_names, X_test, y_test)
  • log 함수를 적용해 보겠음!
    log 함수 : 높은 값은 상대적으로 낮게 잡아주고 낮은 값은 그대로 사용

# 5) log scale 확인

amount_log = np.log1p(raw_data['Amount'])

raw_data_copy['Amount_Scaler'] = amount_log
raw_data_copy.head()
# 6) 분포(displot) 확인
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(10,5))
sns.distplot(raw_data_copy['Amount_Scaler'], color='r')
plt.show()
# 7) 성능 확인

X_train, X_test, y_train, y_test = train_test_split(raw_data_copy, y, test_size=0.3, random_state=13, stratify=y)
start_time = time.time()
results = get_result_pd(models, model_names, X_train, y_train, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results

데이터의 Outlier를 정리

# 1) 특이한 데이터 확인
import seaborn as sns

plt.figure(figsize=(10,7))
sns.boxplot(data=raw_data[['V13', 'V14', 'V15']]);

# 2) Outlier를 정리하기 위해 Outlier의 인덱스를 파악하는 코드

def get_outlier(df=None, column=None, weight=1.5):
    fraud = df[df['Class']==1][column]
    # 25% 지점
    quantile_25 = np.percentile(fraud.values, 25)
    # 75% 지점
    quantile_75 = np.percentile(fraud.values, 75)

    iqr = quantile_75 - quantile_25
    iqr_weight = iqr * weight # (weight = 1.5)
    lowest_val = quantile_25 - iqr_weight
    highest_val = quantile_75 + iqr_weight
	# 제거할 outlier_index 를 설정
    outlier_index = fraud[(fraud < lowest_val) | (fraud > highest_val)].index

    return outlier_index
# 3) 파악하는 코드 작성했으니, Outlier 찾기

get_outlier(df=raw_data, column='V14', weight=1.5)

Index([8296, 8615, 9035, 9252], dtype='int64')

# 4) Outlier 제거 전에 전체 개수 확인
raw_data_copy.shape

(284807, 29)

# 5) Outlier 제거
outlier_index = get_outlier(df=raw_data, column='V14', weight=1.5)
raw_data_copy.drop(outlier_index, axis=0, inplace=True) # 행제거 (axis=0)
raw_data_copy.shape

(284803, 29)

# 6) Outlier 제거 후 데이터 다시 나누기 >> 재평가
X = raw_data_copy

raw_data.drop(outlier_index, axis=0, inplace=True)
y = raw_data.iloc[:, -1]

X_train, X_test, y_train, y_test = train_test_split(raw_data_copy, y, test_size=0.3, random_state=13, stratify=y)

models = [lr_clf, dt_clf, rf_clf, lgbm_clf]
model_names = ['LinearReg', 'DecisionTree', 'RandomForest', 'LightGBM']

start_time = time.time()
results = get_result_pd(models, model_names, X_train, y_train, X_test, y_test)

print('Fit time : ', time.time() - start_time)
results

SMOTE Oversampling

  • 데이터의 불균형이 극심할 때 불균형한 두 클래스의 분포를 강제로 맞춰보는 작업
  • 언더샘플링 : 많은 수의 데이터를 적은 수의 데이터로 강제로 조정
  • 오버샘플링 :
    • 원본데이터의 피처 값들을 아주 약간 변경하여 증식
    • 대표적으로 SMOTE(Synthetic Minority Over-sampling Technique) 방법이 있음
    • 적은 데이터 세트에 있는 개별 데이터를 k-최근접이웃 방법으로 찾아서 데이터의 분포 사이에 새로운 데이터를 만드는 방식
    • imbalanced-learn 이라는 Python pkg가 있음
!pip install imbalanced-learn
profile
비전공자의 데이터 공부법

0개의 댓글