이번에 알아볼 불균형 데이터 처리 방법은
ADASYN입니다.
1. ADASYN이란?
- ADASYN은 Adaptive Synthetic Sampling Approach for Imbalanced Learning 의 약자로, 불균형한 데이터셋에서 소수 클래스의 데이터를 보강하여 학습 성능을 개선하기 위해 사용되는 오버샘플링 기법을 말합니다.
- ADASYN의 주요 특징과 동작 방식은 다음과 같습니다.
1) 소수 클래스 샘플의 밀도 계산
* 먼저 각 소수 샘플 XI에 대해, 최당 샘플의 k-nearest neighbor 중 대다수 클래스 샘플의 비율을 측정합니다. 이를 통해 각 샘플이 결정 경계 근처에 위치하는지를 파악합니다.
2) 가중치 분포 계산
* 각 소수 클래스 샘플의 ri 비율을 기반으로, 소수 클래스 샘플을 얼마나 많이 생성할지를 결정하는 가중치를 계산합니다. 가중치는 생성할 샘플 수의 비율을 결정하는데 사용됩니다.
* 이때 정규화 가중치를 G라고 하며,
3) 샘플 생성
* 각 소수 클래스 샘플 xi의 근처에 새로운 샘플을 생성하는 단계입니다.
2. ADASYN을 활용해 Credit Card Fraud Detection 해보기
- 먼저 데이터를 openml 라이브러리를 활용해서 불러와주고 feature와 target을 분리합니다.
# 필요한 라이브러리들 임포트
from sklearn.datasets import fetch_openml
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 데이터 로드
data = fetch_openml ( data_id= 44235 , as_frame= True )
df = pd.DataFrame ( data.data , columns=data.feature_names )
df [ 'Class' ] = data.target.astype ( int )
# 입력과 타겟 분리
X = df.drop ( 'Class' , axis= 1 )
y = df [ 'Class' ]
- 이후 데이터를 standard scaling 해준 뒤, train, validation, test로 데이터를 stratify 를 활용해서 클래스 비율을 맞춰 나누어줍니다.
# 데이터 표준화
scaler = StandardScaler ()
X_scaled = scaler.fit_transform ( X )
# Train, Test 데이터 분리 (stratify 적용)
X_trainval , X_test , y_trainval , y_test = train_test_split ( X_scaled , y , test_size= 0.2 , random_state= 42 , stratify=y )
# Train, Validation 데이터 분리 (stratify 적용)
X_train , X_val , y_train , y_val = train_test_split ( X_trainval , y_trainval , test_size= 0.25 , random_state= 42 , stratify=y_trainval ) # test_size=0.25 to maintain 60/20/20 split
# 클래스별 개수와 비율 계산
train_counts = y_train.value_counts ()
val_counts = y_val.value_counts ()
test_counts = y_test.value_counts ()
# 각 데이터셋의 클래스 개수와 비율을 DataFrame으로 생성
split_distribution_df = pd.DataFrame ({
'Dataset' : [ 'Train' , 'Validation' , 'Test' ],
'Class 0 Count' : [ train_counts [ 0 ], val_counts [ 0 ], test_counts [ 0 ]],
'Class 1 Count' : [ train_counts [ 1 ], val_counts [ 1 ], test_counts [ 1 ]],
'Class 0 Proportion' : [ train_counts [ 0 ] / len ( y_train ), val_counts [ 0 ] / len ( y_val ), test_counts [ 0 ] / len ( y_test )],
'Class 1 Proportion' : [ train_counts [ 1 ] / len ( y_train ), val_counts [ 1 ] / len ( y_val ), test_counts [ 1 ] / len ( y_test )]
})
# 결과 출력
split_distribution_df
- 이제 ADASYN을 적용해서 train 데이터가 균형된 데이터가 되도록 만들어줍니다. 이번에 beta는 1로 설정해서 학습데이터가 균등 분포가 되도록 만들어보겠습니다. (불균형이 이렇게 심화된 데이터에서는 추천드리지 않고 beta 값을 grid search로 적절히 조절해야하지만, 이번에는 균형으로 한번 맞춰보겠습니다)
from imblearn.over_sampling import ADASYN
# ADASYN 적용
adasyn = ADASYN ( random_state= 42 )
X_train_resampled , y_train_resampled = adasyn.fit_resample ( X_train , y_train )
# ADASYN 적용 후 클래스 개수와 비율 계산
train_resampled_counts = y_train_resampled.value_counts ()
train_resampled_proportion = y_train_resampled.value_counts ( normalize= True )
# Train, Validation, Test 데이터셋의 클래스 개수와 비율을 DataFrame으로 생성
resampled_distribution_df = pd.DataFrame ({
'Dataset' : [ 'Train (Original)' , 'Train (ADASYN)' , 'Validation' , 'Test' ],
'Class 0 Count' : [ train_counts [ 0 ], train_resampled_counts [ 0 ], val_counts [ 0 ], test_counts [ 0 ]],
'Class 1 Count' : [ train_counts [ 1 ], train_resampled_counts [ 1 ], val_counts [ 1 ], test_counts [ 1 ]],
'Class 0 Proportion' : [ train_counts [ 0 ] / len ( y_train ), train_resampled_proportion [ 0 ], val_counts [ 0 ] / len ( y_val ), test_counts [ 0 ] / len ( y_test )],
'Class 1 Proportion' : [ train_counts [ 1 ] / len ( y_train ), train_resampled_proportion [ 1 ], val_counts [ 1 ] / len ( y_val ), test_counts [ 1 ] / len ( y_test )]
})
# 결과 출력
resampled_distribution_df
- 이제 이렇게 구축된 데이터를 바탕으로 학습을 진행하겠습니다. 분류기는 xgboost classfier이고 기존에 알아봤었던 8개의 평가지표를 가지고 비교해보았습니다.
import pandas as pd
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score , precision_score , recall_score , f1_score , roc_auc_score , average_precision_score , matthews_corrcoef , cohen_kappa_score
# XGBoost 모델 초기화 및 학습
model = XGBClassifier ( use_label_encoder= False , eval_metric= 'logloss' , random_state= 42 )
model.fit ( X_train_resampled , y_train_resampled )
# Test 데이터에 대한 예측
y_test_pred = model.predict ( X_test )
y_test_proba = model.predict_proba ( X_test )[:, 1 ]
# Test 결과를 DataFrame으로 저장
test_results_df = pd.DataFrame ({
"Metric" : [ "Accuracy" , "Precision" , "Recall" , "F1 Score" , "AUROC" , "AUPRC" , "MCC" , "Kappa" ],
"Test Set" : [
accuracy_score ( y_test , y_test_pred ),
precision_score ( y_test , y_test_pred ),
recall_score ( y_test , y_test_pred ),
f1_score ( y_test , y_test_pred ),
roc_auc_score ( y_test , y_test_proba ),
average_precision_score ( y_test , y_test_proba ),
matthews_corrcoef ( y_test , y_test_pred ),
cohen_kappa_score ( y_test , y_test_pred )
]
})
# 결과 출력
print ( "Test Set Results:" )
test_results_df
- 이번에는 비교를 위해 ADASYN을 적용하지 않은 train 데이터 셋을 가지고 분류를 해보겠습니다.
import pandas as pd
from xgboost import XGBClassifier
from sklearn.metrics import accuracy_score , precision_score , recall_score , f1_score , roc_auc_score , average_precision_score , matthews_corrcoef , cohen_kappa_score
# ADASYN을 적용하지 않은 train 데이터로 XGBoost 모델 학습
model_no_adasyn = XGBClassifier ( use_label_encoder= False , eval_metric= 'logloss' , random_state= 42 )
model_no_adasyn.fit ( X_train , y_train )
# Test 데이터에 대한 예측
y_test_pred_no_adasyn = model_no_adasyn.predict ( X_test )
y_test_proba_no_adasyn = model_no_adasyn.predict_proba ( X_test )[:, 1 ]
# Test 결과를 DataFrame으로 저장 (ADASYN 적용하지 않은 모델)
test_results_no_adasyn_df = pd.DataFrame ({
"Metric" : [ "Accuracy" , "Precision" , "Recall" , "F1 Score" , "AUROC" , "AUPRC" , "MCC" , "Kappa" ],
"Test Set (No ADASYN)" : [
accuracy_score ( y_test , y_test_pred_no_adasyn ),
precision_score ( y_test , y_test_pred_no_adasyn ),
recall_score ( y_test , y_test_pred_no_adasyn ),
f1_score ( y_test , y_test_pred_no_adasyn ),
roc_auc_score ( y_test , y_test_proba_no_adasyn ),
average_precision_score ( y_test , y_test_proba_no_adasyn ),
matthews_corrcoef ( y_test , y_test_pred_no_adasyn ),
cohen_kappa_score ( y_test , y_test_pred_no_adasyn )
]
})
# 결과 출력
print ( "Test Set Results (No ADASYN):" )
test_results_no_adasyn_df
- 이제 보기 좋게 두 학습 결과를 하나의 데이터프레임으로 합친 뒤 결과를 비교해보겠습니다.
# 두 데이터프레임을 Metric 열을 기준으로 병합
merged_results_df = pd.merge (
test_results_df ,
test_results_no_adasyn_df ,
on= "Metric" ,
how= "inner"
)
# 결과 출력
print ( "Comparison of Test Set Results (With and Without ADASYN):" )
merged_results_df
- 결과를 비교해석해보면
* 정밀도 적인 측면에서는 ADASYN을 적용하지 않은 것이 더 좋았고
* 재현율적인 측면에서는 ADASYN을 적용한 것이 조금 더 좋았습니다.
* AUROC적인 측면에서는 ADASYN을 적용한 것이 조금 성능향상을 불러일으킬 수 있었고
* AUPRC에서는 큰 차이를 볼 수 없고
* MCC나 Kappa에서는 ADASYN을 적용하지 않은 것이 더 좋다고 볼 수 있겠습니다.
종합해보면 ADASYN을 위 설정처럼 사용하더라도 소수클래스를 탐지하는 측면에서는 효과를 볼 수 있지만, 정상 클래스를 비정상으로 분류해버리는 오류가 더 발생할 수 있기에 이 부분에서는 Trade Off를 따져보아야 합니다.
댓글