Tensorflow

캐글 분류 문제: Credit card fraud detection

카카오그래놀라 2020. 11. 9. 20:27

케라스 번역: 클래스간 데이터 수가 불균형한 데이터셋에서의 분류

캐글 신용카드 사기 탐지 데이터셋 사례를 통해서

Imbalanced classification: credit card fraud detection

 

Author: fchollet
Date created: 2020/05/31
Last modified: 2020/05/31
Description: 클래스 간 데이터 수가 매우 불균형한 데이터를 다루는 방법에 대해 배워봅시다. (클래스 간 데이터 수가 매우 불균형한 데이터를 이하에서는, imbalanced data라고 하겠습니다.) Demonstration of how to handle highly imbalanced classification problems.

- Keras

- Github

- Colab

 

소개

이번 에제는 캐글 - 신용카드 사기 탐지 데이터 셋을 활용하여 imbalanced dataset에서 분류 모델을 학습하는 방법에 대해 배워보겠습니다.

This example looks at the Kaggle Credit Card Fraud Detection dataset to demonstrate how to train a classification model on data with highly imbalanced classes.

 

우선, CSV 파일을 벡터화 합니다.

First, vectorize the CSV data
import csv
import numpy as np

# Get the real data from https://www.kaggle.com/mlg-ulb/creditcardfraud/
# 해당 URL에서 데이터셋을 다운로드 받고, 다운받은 경로를 입력합니다.
fname = "/Users/fchollet/Downloads/creditcard.csv" # 자신의 경로를 사용하세요!

all_features = []
all_targets = []
with open(fname) as f:
    
    # enumerate는 리스트에서 데이터를 호출할 때, 인덱스와 데이터를 함께 반환하는 함수입니다.
    for i, line in enumerate(f):
        if i == 0:
            print("HEADER:", line.strip()) # strip()은 불러온 문자열에서 앞,뒤 공백을 제거해 주는 함수입니다.
            continue  # 데이터의 첫번째 줄은 header(column 이름)이기 때문에 continue합니다.
        
        fields = line.strip().split(",")
        all_features.append([float(v.replace('"', "")) for v in fields[:-1]])
        all_targets.append([int(fields[-1].replace('"', ""))])
        
        if i == 1: # 첫번째 데이터만 Example로 출력해서 확인해봅시다.
            print("EXAMPLE FEATURES:", all_features[-1])

# np.array로 만들어 벡터화합시다.
features = np.array(all_features, dtype="float32")
targets = np.array(all_targets, dtype="uint8")
print("features.shape:", features.shape)
print("targets.shape:", targets.shape)

# HEADER: "Time","V1","V2","V3","V4", ...... ,"V26","V27","V28","Amount","Class"
# EXAMPLE FEATURES: [0.0, -1.3598071336738, ............ -0.0210530534538215, 149.62]

# 데이터셋은 284807개이며, 데이터마다 30개의 column을 가지고 있습니다.
# features.shape: (284807, 30)

# target값은 0 또는 1로 표현됩니다.
# targets.shape: (284807, 1)


 

Validation set을 준비합니다.

# 데이터셋의 뒷부분 20%를 validation set으로 지정합니다.
num_val_samples = int(len(features) * 0.2)

train_features = features[:-num_val_samples]
train_targets = targets[:-num_val_samples]

val_features = features[-num_val_samples:]
val_targets = targets[-num_val_samples:]

print("training 데이터 수:", len(train_features))
print("validation 데이터 수:", len(val_features))

# training 데이터 수: 227846
# validation 데이터 수: 56961

 

타켓값의 클래스간 불균형을 확인해봅시다.

Analyze class imbalance in the targets
# np.bincount는 배열 값들의 빈도수를 계산해주는 함수입니다.
counts = np.bincount(train_targets[:, 0])

print(f"트레인 셋 중 사기(fraud)인 데이터 개수: {counts[1]}개")
print(f"트레인 셋 중 사기(fraud) 비율: {round(100 * float(counts[1]) / len(train_targets), 2)}%")
# round 함수는 반올림 함수입니다. 소수점 둘째 자리로 맞췄습니다.

# 트레인 셋 중 사기(fraud)인 데이터 개수: 417개
# 트레인 셋 중 사기(fraud) 비율: 0.18%


weight_for_0 = 1.0 / counts[0]
weight_for_1 = 1.0 / counts[1]
# 0(정상)과 1(사기,fraud)의 weight를 구합니다.
# 0의 개수에 0의 weight를 곱하면 1
# 1의 개수에 1의 weight를 곱해도 1. weight를 곱하면, 서로간의 비중이 동일해집니다.

 

Training 셋을 표준화합니다.

Normalize the data using training set statistics
# 원문에서는 normalization이라 하였지만,
# 코드는 standardization이라 표준화로 번역하였습니다.

# 표준화: 데이터마다 전체 데이터셋의 평균을 빼고, 표준편차로 나눕시다.
mean = np.mean(train_features, axis=0)
train_features -= mean
val_features -= mean
std = np.std(train_features, axis=0)
train_features /= std
val_features /= std

 

이진 분류 모델을 만듭니다.

Build a binary classification model
from tensorflow import keras

# 모델을 만들 때, input_shape와 ouput_shape를 항상 유의!
model = keras.Sequential(
    [
        keras.layers.Dense(
            256, activation="relu", input_shape=(train_features.shape[-1],)
        ),
        keras.layers.Dense(256, activation="relu"),
        keras.layers.Dropout(0.3),
        keras.layers.Dense(256, activation="relu"),
        keras.layers.Dropout(0.3),
        keras.layers.Dense(1, activation="sigmoid"),
    ]
)
model.summary()

 

class_weight를 사용하여, Model을 훈련합시다.

Train the model with class_weight argument
# train 과정 동안, 각종 metrics를 확인합시다.
metrics = [
    keras.metrics.FalseNegatives(name="fn"),
    keras.metrics.FalsePositives(name="fp"),
    keras.metrics.TrueNegatives(name="tn"),
    keras.metrics.TruePositives(name="tp"),
    keras.metrics.Precision(name="precision"),
    keras.metrics.Recall(name="recall"),
]

model.compile(
    optimizer=keras.optimizers.Adam(1e-2), loss="binary_crossentropy", metrics=metrics
)

# epoch마다 모델 weight 값(앞의 class_weight가 아닙니다.)을 저장하는 callback을 추가합시다.
callbacks = [keras.callbacks.ModelCheckpoint("fraud_model_at_epoch_{epoch}.h5")]
class_weight = {0: weight_for_0, 1: weight_for_1}

model.fit(
    train_features,
    train_targets,
    batch_size=2048,
    epochs=30,
    verbose=2,
    callbacks=callbacks,
    validation_data=(val_features, val_targets),
    class_weight=class_weight, # 앞서 구만 class_weight를 인자에 넣어줍시다.
)

 

결론

Conclusions

모델 훈련이 끝나고, 앞서 준비해둔 56,961건의 validation 데이터에 대해 예측해봅시다.

 

그 결과,

  • 전체 사기거래 중, 66개를 정확하게 Fraud로 식별했습니다.
  • 9건의 사기거래를 정상거래로 잘못 분류했습니다.
  • 441건의 정상거래를 사기거래로 잘못 분류했습니다.

 

 사기거래를 정상거래로 잘못 분류하는 경우보다, 정상거래를 사기거래로 잘못 분류한 사례가 훨씬 많습니다. 이는 앞서 우리가 class_weight를 적용했기 때문입니다.

 현실에서 정상거래를 사기거래로 판단했을 때의 손실(거래중단에 따른 불편함)보다, 사기거래를 정상거래로 판단했을 때의 손실(실제 돈의 피해)이 훨씬 크기 때문에 이러한 class_weight를 적용하는 것입니다.

 혹시, 다음 번에 정상적인 온라인 거래에서 신용카드가 거부된다면, 이러한 이유였음을 기억해주신다면 감사하겠습니다!

 

At the end of training, out of 56,961 validation transactions, we are:

- Correctly identifying 66 of them as fraudulent

- Missing 9 fraudulent transactions

- At the cost of incorrectly flagging 441 legitimate transactions

In the real world, one would put an even higher weight on class 1, so as to reflect that False Negatives are more costly than False Positives.

Next time your credit card gets declined in an online purchase -- this is why.