let us not love with words or tongue but actions and truth.

IT/파이썬

Imbalanced Dataset에서의 over sampling과 cross validation

sarah0518 2020. 12. 9. 14:24

지금 분석 하려고 하는 내용은 복잡하니, 개괄적인 내용만 미리 정리해보겠습니다.

 

우선 Imbalanced Dataset를 모델링시키기 위해서는 아래와 같은 순서로 진행합니다.

  1. StratifiedKFold기법을 적용하여, train과 test dataset으로 쪼개고
  2. Train dataset의 Minority class를 over sampling하고
  3. over sampling 된 traing dataset으로 모델을 Traning 시킨 후
  4. 원래 데이터의 test dataset을 통해 test한 후 모델설명력을 평균 내는 것 (cross validation의 원리)

왜 imbalanced dataset에서는 위와 같이 복잡하게 진행할까라고 하시는 분들은 아래 설명을 보시면 조금 이해가 될거 같습니다.

www.marcoaltini.com/blog/dealing-with-imbalanced-data-undersampling-oversampling-and-proper-cross-validation

 

Dealing with imbalanced data: undersampling, oversampling and proper cross-validation

PhD in Data Science, Founder of  HRV4Training , Traveler, Passionate Runner, Immigrant ​More info here

www.marcoaltini.com

(우리와 같은 궁금증에 대해서 올려놓은 글이니 참고만하셔요)

> datascience.stackexchange.com/questions/16247/cross-validation-plus-oversampling

 

Cross validation plus oversampling?

I am quite new to machine learning and python as well. I faced an imbalanced dataset and wanna use cross validation and oversamopling like the figure shown. I realised the Python function below ca...

datascience.stackexchange.com

위의 두 사이트를 정리해 보자면 실제 우리가 oversampling을 하고 그 다음 cross validation을 할 때,

실제, validation set이나, test set이 oversampling되지 않는 것에 유의하자! 라는 얘기죠.

만약!

oversampling 된 노란표시 데이터가, 아래 그림과 같이 training set에도 그리고 validation set에도 들어가게 된다면, 

과적합될 가능성이 있는겁니다. 

 

따라서 위의 이슈를 해결하기 위해서는 아래와 같이 k-fold로 나눈 데이터 셋의 training dataset만을 oversampling 시키고, 순수한(원 상태의) validation set만을 테스트 할 때 사용하는 것이죠. 

(더 상세한 설명은 링크 공유된 사이트를 참조하시길)

위 사이트에서는 해당 logic이 R코드로 되어있어, 저는 파이썬으로 정리해보려고 합니다.

우선 아래와 같이 사전작업을 먼저 할게요.

 

K-fold split을 위해 x_origin, y_origin dataframe을 array형태로 바꿔줍니다.

1
2
3
4
5
6
7
8
import numpy as np
# K-fold split을 위해 x_origin, y_origin dataframe을 array형태로 바꿔줍니다. 
# 혹시 array형태로 바꾸지 않고 k-fold split을 할 수 있는 로직을 아시는 분은 공유부탁드려요.
x_arr=np.asarray(x_origin)
y_arr=np.asarray(y_origin)

from sklearn.model_selection import StratifiedKFold
kf = StratifiedKFold(n_splits=5)
cs

 

그 다음은, 모델링 관련된 함수를 만들어 주는데, 저는 random forest 분류모델을 쓰겠습니다.

아래 함수 안에 추가하고 싶은 다양한 분류모델링을 넣어주셔도 됩니다. 

1
2
3
4
def clf():
    rfc = RandomForestClassifier(max_depth=5, random_state=333,
                                 criterion = "entropy",n_estimators = 5, max_features='auto')
    return rfc
cs

 

아래에서 minority class는 target값이 1일 때 입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
col_list=x_arr.columns
 
from sklearn.metrics import accuracy_score
from sklearn.metrics import recall_score 
from sklearn.metrics import precision_score 
from sklearn.metrics import f1_score 
from sklearn.metrics import roc_curve
 
a_rfc=[] # accuracy
r_rfc=[] # recall
p_rfc=[] # precision
f_rfc=[] # f-score
ro_rfc=[] # roc
 
#for 문 안에서 이전에 설정한 n_split=5의 값으로 데이터를 split해 줍니다.
for train_index, test_index in kf.split(x_arr, y_arr):
 
    x_train, x_test = x_arr[train_index], x_arr[test_index]
    y_train, y_test = y_arr[train_index], y_arr[test_index]
    x_test=pd.DataFrame(x_test, columns=col_list)
    # array concat
    y_train2=y_train.reshape(y_train.shape[0],1)
    trset=np.concatenate([x_train,y_train2],axis=1)
 
    # over sampling
    # 여기서는 중복추출을 통해 단순히 minority class 데이터를 2배로 뻥튀기 시키겠습니다.
    # 원하신다면, 이 부분에는 smote관련 코드를 넣어도 됩니다. 
    trset_df=pd.DataFrame(trset, columns=col_list)
    over=trset_df[trset_df['target']==1]
    # minority class를 그대로 가져와서만든 over라는 dataset과 trset_df와 합쳐줍니다.
    trset_over=pd.concat([trset_df, over], axis=0)
 
    # split
    # 이제 최종적인 train dataset에서 x & y(타겟) 변수로 나눠줍니다.
    x_train_fin=trset_over.drop(['target'],axis=1)
    y_train_fin=trset_over['target']
 
    #clf    
    rfc = clf()
    rfc.fit(x_train_fin, y_train_fin)
    pred_rfc = rfc.predict(x_test)
   
    #score
    accuracy_rfc = accuracy_score(y_test,pred_rfc)
    a_rfc.append(accuracy_rfc)
 
    recall_rfc = recall_score(y_test,pred_rfc)
    r_rfc.append(recall_rfc)
 
    precision_rfc = precision_score(y_test,pred_rfc)
    p_rfc.append(precision_rfc)
 
    f1_rfc = f1_score(y_test,pred_rfc)
    f_rfc.append(f1_rfc)
 
    roc_rfc = roc_curve(y_test,pred_rfc)
    ro_rfc.append(roc_rfc)
cs

 

이 결과 5개의 scoring결과가 저장된 list안에는 각 iteration별 score점수가 append 되어있을거에요.따라서, iteration별 score 점수의 평균을 내준다면, 우리가 cross-validation결과로 얻고싶어하는 값을 얻을 수 있습니다.

1
np.mean(a_rfc), np.mean(p_rfc), np.mean(r_rfc), np.mean(f_rfc), np.mean(ro_rfc) 
cs

별로 좋은 결과는 아니지만, 참고만 하시라고 공유드립니다.

 

참고로 dummy classification과 비교할 수 있는데, imblanced 인것을 감안해서 아래와 같이 baseline을 확인하시면 됩니다.

1
2
3
4
from sklearn.dummy import DummyClassifier
dummy = DummyClassifier(strategy='stratified', random_state=333)
dummy.fit(x_origin, y_origin['target'])
cv_accuracy(dummy, x_origin, y_origin['target'] )
cs

cv_accuracy등은 함수로 아래와 같이 만들어 놓으시면 쓰기 편하답니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from sklearn.model_selection import cross_val_score
def cv_recall(model, x, y):
    scores = cross_val_score(model, x, y, cv=5, scoring='recall'); scores
    print("Mean: {:.3f}\nStd: {:.3f}\nMin: {:.3f}\nMax: {:.3f}".format(scores.mean(), scores.std(), scores.min(), scores.max()))
    
def cv_f1(model, x, y):
    scores = cross_val_score(model, x, y, cv=5, scoring='f1'); scores
    print("Mean: {:.3f}\nStd: {:.3f}\nMin: {:.3f}\nMax: {:.3f}".format(scores.mean(), scores.std(), scores.min(), scores.max()))
    
def cv_precision(model, x, y):
    scores = cross_val_score(model, x, y, cv=5, scoring='precision'); scores
    print("Mean: {:.3f}\nStd: {:.3f}\nMin: {:.3f}\nMax: {:.3f}".format(scores.mean(), scores.std(), scores.min(), scores.max()))
    
def cv_accuracy(model, x, y):
    scores = cross_val_score(model, x, y, cv=5, scoring='accuracy'); scores
    print("Mean: {:.3f}\nStd: {:.3f}\nMin: {:.3f}\nMax: {:.3f}".format(scores.mean(), scores.std(), scores.min(), scores.max()))
    
def cv_roc(model, x, y):
    scores = cross_val_score(model, x, y, cv=5, scoring='roc_auc'); scores
    print("Mean: {:.3f}\nStd: {:.3f}\nMin: {:.3f}\nMax: {:.3f}".format(scores.mean(), scores.std(), scores.min(), scores.max()))
cs