[금융 AI] AI 기반 금융 사기 탐지(4) - AutoEncoder 기반 신용카드 사기 거래 탐지 모델
비지도 학습 방법 중 하나인 오토인코더는 금융 사기 거래 탐지 분야에서도 그 가치를 발휘한다. 오토인코더는 핵심 특징을 유지하면서 데이터를 효과적으로 압축하고 복원하는 방법을 학습한다. 이러한 특성 때문에 오토인코더는 효과적인 차원 축소 도구로 사용되며, 잡음 제거나 이상 탐지 등에도 적용된다.
오토인코더는 본질적으로 고차원 입력의 저차원 표현을 생성하는데 신경망을 사용한다. 주성분 분석과 유사하지만, 비선형 활성화 함수를 사용할 때 주성분 분석의 선형적 제한을 극복한다. 두가지 주요 부분인 인코더와 디코더를 포함하고, 인코더는 주어진 데이터의 압축 표현을 발견하는 역할을 하고, 디코더는 원래의 입력을 재구성하는데 사용된다.
위 원리에 따라 이번 실습에서는 비지도 학습법인 오토인코더를 사용해 잠재적인 사기 패턴을 가진 신용카드 거래 탐지 모델링을 해보자.
import warnings
warnings.filterwarnings("ignore")
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import seaborn as sns
from sklearn.model_selection import train_test_split
from keras.models import Model, load_model
from keras.layers import Input, Dense,LeakyReLU,BatchNormalization
from keras.callbacks import ModelCheckpoint
from keras import regularizers
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import roc_curve, auc, precision_recall_curve
먼저 필요 라이브러리를 호출한다.
d = pd.read_csv('/kaggle/input/creditcardfraud/creditcard.csv')
지난 머신러닝 기반 탐지 모델링 시 활용한 데이터를 동일하게 사용한다.
num_nonfraud = np.sum(d['Class'] == 0) # 'Class' 열에서 값이 0인 비사기 거래의 개수를 계산
num_fraud = np.sum(d['Class'] == 1) # 'Class' 열에서 값이 1인 사기 거래의 개수를 계산
fraud_label = 'Fraud' # 사기 거래를 나타내는 레이블
nonfraud_label = 'Non-Fraud' # 비사기 거래를 나타내는 레이블
fraud_count = [num_fraud]
nonfraud_count = [num_nonfraud]
plt.bar(fraud_label, fraud_count, color='dodgerblue', label='Fraud')
plt.bar(nonfraud_label, nonfraud_count, color='lightgrey', label='Non-Fraud')
plt.xlabel('Transaction Type')
plt.ylabel('Count')
plt.title('Number of Fraud vs Non-Fraud Transactions')
plt.legend()
plt.show()
사기거래와 정상거래를 count하여 그래프로 확인한다.
data = d.drop(['Time'], axis=1)
data['Amount'] = StandardScaler().fit_transform(data[['Amount']])
X = data.drop(['Class'],axis=1)
Y = data.Class
스케일링을 진행한 뒤, 독립변수와 종속변수로 분할한다.
# Autoencoder의 파라미터 설정
input_dim = X.shape[1] # 입력 차원 설정
encoding_dim = 128 # 인코딩 차원 설정
num_epoch = 30 # 에포크 횟수 설정
batch_size = 256 # 배치 크기 설정
input_layer = Input(shape=(input_dim, )) # 입력 레이어 정의
# 인코더 레이어 정의
encoder = Dense(encoding_dim,
activation="tanh",
activity_regularizer=regularizers.l1(10e-5)
)(input_layer) # 인코딩 레이어 설정 및 활성화 함수, 정규화 설정
encoder = BatchNormalization()(encoder) # 배치 정규화 적용
encoder = LeakyReLU(alpha=0.2)(encoder) # LeakyReLU 활성화 함수 적용
encoder = Dense(int(encoding_dim/2),
activation="relu"
)(encoder) # 인코딩 레이어 설정 및 활성화 함수
encoder = BatchNormalization()(encoder) # 배치 정규화 적용
encoder = LeakyReLU(alpha=0.1)(encoder) # LeakyReLU 활성화 함수 적용
encoder = Dense(int(encoding_dim/4),
activation="relu"
)(encoder) # 인코딩 레이어 설정 및 활성화 함수
encoder = BatchNormalization()(encoder) # 배치 정규화 적용
# 디코더 레이어 정의
decoder = LeakyReLU(alpha=0.1)(encoder) # LeakyReLU 활성화 함수 적용
decoder = Dense(int(encoding_dim/4),
activation='tanh'
)(decoder) # 디코딩 레이어 설정 및 활성화 함수
decoder = BatchNormalization()(decoder) # 배치 정규화 적용
decoder = LeakyReLU(alpha=0.1)(decoder) # LeakyReLU 활성화 함수 적용
decoder = Dense(int(encoding_dim/2),
activation='tanh'
)(decoder) # 디코딩 레이어 설정 및 활성화 함수
decoder = BatchNormalization()(decoder) # 배치 정규화 적용
decoder = LeakyReLU(alpha=0.1)(decoder) # LeakyReLU 활성화 함수 적용
decoder = Dense(input_dim,
# activation='relu'
)(decoder) # 디코딩 레이어 설정
autoencoder = Model(inputs=input_layer,
outputs=decoder
) # Autoencoder 모델 정의
autoencoder.compile(optimizer='adam',
loss='mean_squared_error',
metrics=['mae','mse']
) # 모델 컴파일
위 코드는 AutoEncoder의 설정과 인코더 및 디코더 레이어를 정의하는 작업이다. 주석을 통해 각 줄이 어떤 역할을 하는지 설명하였는데, 다시 한 번 정리해보자.
- 입력 차원을 설정한다. X.shape[1]은 입력 데이터의 열 개수를 나타낸다.
- 인코딩 차원을 설정한다. 이는 인코더 레이어에서 출력되는 차원을 의미한다.
- 에포크 횟수를 설정한다. 모델이 훈련할 때 전체 데이터셋을 몇 번 반복할지를 결정한다.
- 배치 크기를 설정한다. 한 번에 모델이 처리할 데이터의 개수를 뜻한다.
- 인코더와 디코더 레이어를 정의한다. Dense 함수를 사용하여 레이어를 생성하고 활성화 함수, 정규화 등을 설정한다. LeakyReLU 함수를 사용하여 LeakyReLU 활성화 함수를 적용한다. BatchNormalization 함수를 사용하여 배치 정규화를 적용한다.
- Autoencoder 모델을 정의한다. Model 함수를 사용하여 입력 레이어와 출력 레이어를 지정하여 모델을 생성한다.
- 모델을 컴파일한다. compile 함수를 사용하여 옵티마이저, 손실 함수, 평가 지표 등을 설정한다. 이렇게 작성된 코드는 Autoencoder 모델을 설정하고 컴파일하는 작업을 수행한다.
checkpointer = ModelCheckpoint(filepath="autoencoder_model.h5",
verbose=0,
save_best_only=True
)
모델의 가중치를 저장하기 위해 ModelCheckpoint 객체를 생성한다.
history = autoencoder.fit(X,
X,
epochs=num_epoch,
batch_size=batch_size,
shuffle=True,
verbose=1,
callbacks=[checkpointer]
).history
AutoEncoder 모델에 대해 fit 메서드를 호출하여 모델을 훈련시킨다.
# MAE 그래프
plt.subplot(121)
plt.plot(history['mae'], c='dodgerblue', lw=3)
plt.title('Mean Absolute Error (MAE)')
plt.ylabel('MAE')
plt.xlabel('Epoch')
plt.legend(['Train'], loc='upper right')
# MSE 그래프
plt.subplot(122)
plt.plot(history['mse'], c='dodgerblue', lw=3)
plt.title('Mean Squared Error (MSE)')
plt.ylabel('MSE')
plt.xlabel('Epoch')
plt.legend(['Train'], loc='upper right')
plt.subplots_adjust(wspace=0.3)
plt.gcf().set_size_inches(12, 5)
plt.show()
모델 훈련 과정에서의 MAE, MSE의 변화를 그래프로 확인한다.
pred_X = autoencoder.predict(X)
mse_X = np.mean(np.power(X - pred_X, 2), axis=1)
mae_X = np.mean(np.abs(X - pred_X), axis=1)
data['mse_X'] = mse_X
data['mae_X'] = mae_X
n = 1000
df = data.sort_values(by='mae_X', ascending=False)
top_n_df = df.head(n)
accuracy = top_n_df[top_n_df['Class'] == 1].shape[0] / n
print('Top-{} 정확도: {}'.format(n, accuracy))
-->Top-1000 정확도: 0.329
훈련된 오토인코더를 사용하여 테스트 세트를 재구성한다. 재구성 오차를 계산하고, 상위 N개의 샘플을 추출하여 해당 샘플들 중 사기 클래스의 비율을 계산해 Top-N 정확도를 평가한다.
mask = (data['Class'] == 0)
X_train, X_test = train_test_split(X, test_size=0.3, random_state=520)
X_fraud = X[~mask]
pred_test = autoencoder.predict(X_test)
pred_fraud = autoencoder.predict(X_fraud)
mse_test = np.mean(np.power(X_test - pred_test, 2), axis=1)
mse_fraud = np.mean(np.power(X_fraud - pred_fraud, 2), axis=1)
mae_test = np.mean(np.abs(X_test - pred_test), axis=1)
mae_fraud = np.mean(np.abs(X_fraud - pred_fraud), axis=1)
mse_df = pd.DataFrame()
mse_df['Class'] = [0] * len(mse_test) + [1] * len(mse_fraud)
mse_df['MSE'] = np.hstack([mse_test, mse_fraud])
mse_df['MAE'] = np.hstack([mae_test, mae_fraud])
mse_df = mse_df.sample(frac=1).reset_index(drop=True)
markers = ['o', '^']
markers = ['o', '^']
colors = ['dodgerblue', 'red']
labels = ['Non-fraud', 'Fraud']
plt.figure(figsize=(14, 5))
plt.subplot(121)
for flag in [1, 0]:
temp = mse_df[mse_df['Class'] == flag]
plt.scatter(temp.index,
temp['MAE'],
alpha=0.7,
marker=markers[flag],
c=colors[flag],
label=labels[flag])
plt.title('Reconstruction MAE')
plt.ylabel('Reconstruction MAE')
plt.xlabel('Index')
plt.subplot(122)
for flag in [1, 0]:
temp = mse_df[mse_df['Class'] == flag]
plt.scatter(temp.index,
temp['MSE'],
alpha=0.7,
marker=markers[flag],
c=colors[flag],
label=labels[flag])
plt.legend(loc=[1, 0], fontsize=12)
plt.title('Reconstruction MSE')
plt.ylabel('Reconstruction MSE')
plt.xlabel('Index')
plt.show()
MAE와 MSE를 보면 양성 및 음성 샘플 간에 상당한 차이가 있음을 알 수 있다. 이는 이 알고리즘이 우수한 이상 탐지 능력을 가지고 있음을 증명하는데, 일부 정상 샘플은 여전히 이상 탐지를 통해 분리하기 어려워 모델의 개선이 더 필요함을 시사한다.