AI·ML

[ML수업] 7주차 실습1: Ensemble Learning (Similarity between models, Voting ensemble, Averaging predictions, stacking)

SubjectOwner 2023. 11. 21. 15:51
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pylab as plt
%matplotlib inline
import warnings
warnings.filterwarnings("ignore")



# Read Data
from sklearn.datasets import load_breast_cancer

cancer = load_breast_cancer()
cancer_features = pd.DataFrame(data=cancer.data, columns=cancer.feature_names)
#cancer_features

'''
***유방암 진단 데이터***      
- 타겟 데이터: 종양이 악성(malignant, 0) or 양성(benign, 1)
- 속성 데이터: 유방암 진단 사진으로부터 측정한 종양(tumar)의 특징값(30 개)
'''
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(cancer_features, cancer.target, random_state=0)

 

 

## Similarity between models
 
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score
from sklearn.metrics import roc_auc_score
from sklearn.metrics import auc

clfs = []
LR=LogisticRegression(random_state=0); clfs.append(LR)
DT=DecisionTreeClassifier(random_state=0); clfs.append(DT)
MLP=MLPClassifier(random_state=0); clfs.append(MLP)
KNN=KNeighborsClassifier(); clfs.append(KNN)
SVM=SVC(probability=True, random_state=0); clfs.append(SVM)

pred_results = [] #리스트에 값이 다 저장됨.
# 모델별로 성능이 나옴

for clf in clfs :
    pred = clf.fit(X_train, y_train).predict_proba(X_test)[:,1]
    name = type(clf).__name__
    score = roc_auc_score(y_test, pred)
    pred_results.append(pd.Series(pred, name=f'{name} \n({score:.4f})'))
    print("{:30s} {}".format(name, score))

ensemble_results = pd.concat(pred_results, axis=1)
plt.figure(figsize = (8,6))
g = sns.heatmap(ensemble_results.corr(),annot=True, cmap='Blues')
g.set_title("Correlation between models")
plt.show()

LogisticRegression             0.9930817610062893
DecisionTreeClassifier         0.8939203354297695
MLPClassifier                  0.9681341719077569
KNeighborsClassifier           0.9607966457023062
SVC                            0.9842767295597485

 

 

corr = (ensemble_results.corr().sum()-1)/(ensemble_results.corr().shape[0]-1) #상관관계를 보는 코드
names = corr.index.str[:-10]
aucs = np.array(corr.index.str[-7:-1]).astype(float)
df = pd.DataFrame({'model': names, 'auc': aucs, 'cor': corr})        

plt.figure(figsize=(8,6))
g = sns.scatterplot(x="cor", y="auc", data=df, s=40, color='red')
for line in range(0, df.shape[0]):
     g.text(df.cor[line]+0.003, df.auc[line]-0.001, 
            df.model[line], horizontalalignment='left', 
            size='medium', color='black', weight='semibold')
        
plt.xlim((df.cor.min()-0.01,df.cor.max()+0.01))
plt.ylim((df.auc.min()-0.01,df.auc.max()+0.01))
plt.xlabel('Mean Agreement')
plt.ylabel('ROC-AUC')
plt.grid()
plt.show()

 

이걸 통해서 ??모델 별 성능? 유사성? 을 알 수 있다. 

그 다음에 해야 할 거:

첫번째로 성능이 가장 좋은 로지스틱~ 를 고르고

두번째로 로지스틱이랑 제일 이질적인 것 중에서 성능이 양호한 걸 골라라 예를들어 SVC

 

## Voting ensemble  
*평가지표가 accuracy, f1-score 등일 경우 사용*
from sklearn.ensemble import VotingClassifier

# 위에서 평가한 모든 모델을 앙상블하는 경우

# 여러 모델을 앙상블하기 위해 VotingClassifier를 생성합니다.
# estimators 매개변수에는 [('모델 이름', 모델 객체), ...] 형식으로 모델과 모델 이름을 지정합니다.
# 매개변수 중 하드는 ???, 소프트는 평균낸다는 뜻
voting = VotingClassifier(
#    estimators = [('logit', LR), ('Decision Tree',DT)], voting='hard')                  
    estimators = [(type(clf).__name__, clf) for clf in clfs], voting='hard') 

voting.fit(X_train, y_train).score(X_test, y_test)
#0.951048951048951

 

# 가장 성능이 낮은 DT를 제외하고 앙상블할 경우
voting = VotingClassifier(
    estimators = [('lr', LR), ('mlp', MLP), ('knn', KNN), ('svm', SVM)], voting='hard')
voting.fit(X_train, y_train).score(X_test, y_test)

 

 

??? 이게 무슨 코드인지는 모르겠음.

from sklearn.ensemble import VotingClassifier

voting = VotingClassifier(
    estimators = [('lr', LR), ('mlp', MLP), ('dt', DT)], voting='hard')
voting.fit(X_train, y_train).score(X_test, y_test)

 

 

***Plotting Decision Regions***
from itertools import product

X = X_train.values[:, [0, 1]]
y = y_train

LR.fit(X, y)
MLP.fit(X, y)
DT.fit(X, y)
voting.fit(X, y)

# Plotting decision regions
x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                     np.arange(y_min, y_max, 0.1))
f, axarr = plt.subplots(2, 2, sharex='col', sharey='row', figsize=(10, 8))
for idx, clf, tt in zip(product([0, 1], [0, 1]),
                        [LR, MLP, DT, voting],
                        ['Logistic Regression', 'Neural Networks', 'Decision Tree', 'Hard Voting']):
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    axarr[idx[0], idx[1]].contourf(xx, yy, Z, alpha=0.4)
    axarr[idx[0], idx[1]].scatter(X[:, 0], X[:, 1], c=y, s=20, edgecolor='k')
    axarr[idx[0], idx[1]].set_title(tt)
plt.show()

#로지스틱은 직선, 뉴럴은 곡선, 디시전트리는 지지직하면서 과적합이 심함
#짜란 보팅을 하니까 뭐 괜찮아짐

 

 

## Averaging predictions  
*평가지표가 roc-auc, logloss 등일 경우 사용*
 
 

***Arithmetic mean (산술평균)***
averaging = VotingClassifier(
    estimators = [('lr', LR), ('mlp', MLP), ('dt', DT)], voting='soft')
averaging.fit(X_train, y_train)

print('AUC =', roc_auc_score(y_test, averaging.predict_proba(X_test)[:,1]))
#AUC = 0.9909853249475892

 

***Geometric mean*(기하평균)
from scipy.stats.mstats import gmean

pred_lr = LR.fit(X_train, y_train).predict_proba(X_test)[:,1]
pred_mlp = MLP.fit(X_train, y_train).predict_proba(X_test)[:,1]
pred_dt = DT.fit(X_train, y_train).predict_proba(X_test)[:,1]

print('AUC = ', roc_auc_score(y_test, gmean([pred_lr, pred_mlp, pred_dt], axis=0)))

 

***Power mean***  (조화평균)
 

(코드 없음)

 

***All-in-one***
from tqdm import tqdm, tqdm_notebook
from itertools import combinations

# 이 코드에서 가중평균은 빠져있다.
max_score = 0
for p in tqdm_notebook([0, 1, 2.56]):  # p==1:산술평균, p=0:기하평균, 그 외:멱평균(주의:멱평균은 과적합 가능성이 높음)    
    for i in range(2, len(clfs)+1):
        for models in combinations(clfs, i):
            if p == 0:
                pred_mean = gmean([clf.fit(X_train, y_train).predict_proba(X_test)[:,1] for clf in models], axis=0)
            else:
                preds = [clf.fit(X_train, y_train).predict_proba(X_test)[:,1] for clf in models]
                pred_mean = (np.sum(np.array(preds)**p, axis=0) / len(models))**(1/p)
            score = roc_auc_score(y_test, pred_mean)
            if max_score < score:
                best_avg_ensemble = (p, models, score)
                max_score = score

# 최적의 앙상블 파라미터(p), 모델 조합, 그리고 AUC 점수를 출력합니다.
p, models, score = best_avg_ensemble
print('p = {}\nmodels = {}\nauc = {}'.format(p, '●'.join([type(clf).__name__ for clf in models]), score))

'''
p = 0
models = LogisticRegression●SVC
auc = 0.9930817610062894
'''

이 코드는 다양한 가중평균 파라미터(p)와 모델 조합을 시도하여 최고의 AUC 점수를 찾습니다. 루프를 사용하여 모델을 훈련하고 예측을 수행하며, 가중평균을 계산합니다. 최적의 앙상블 파라미터(p), 모델 조합, 그리고 AUC 점...

## Stacking
* 2-layer stacking, 3-layer stacking을 둘 다 해보고 더 성능이 높은 것을 사용하는게 좋음.
 
***2-layer stacking***

 

# stacking 함수를 사용하여 모델 스태킹을 수행합니다.
# - models: 모델 리스트
# - X_train, y_train: 훈련 데이터와 레이블
# - X_test: 테스트 데이터
# - regression: 회귀 모델인 경우 True, 분류 모델인 경우 False
# - needs_proba: 확률 예측이 필요한 경우 True, 클래스 레이블 예측이 필요한 경우 False
# - n_folds: 교차 검증의 폴드 수
# - stratified: 분류 작업의 경우 클래스 레이블에 대한 계층화 샘플링을 사용할지 여부
# - shuffle: 데이터를 섞을지 여부
# - random_state: 재현성을 보장하기 위한 난수 시드 값
# - verbose: 실행 정보 출력 (0: 출력 안 함, 1: 요약 정보 출력, 2: 자세한 정보 출력)

from vecstack import stacking

models = clfs
S_train, S_test = stacking(models,                     # list of models
                           X_train, y_train, X_test,   # data
                           regression=False,           # classification task (if you need 
                                                       #     regression - set to True)
                           needs_proba=True,           # predict class labels (if you need 
                                                       #     probabilities - set to True) 
                           n_folds=5,                  # number of folds
                           stratified=True,            # stratified split for folds
                           shuffle=True,               # shuffle the data
                           random_state=0,             # ensure reproducibility
                           verbose=2)                  # print all info
                           
'''
task:         [classification]
n_classes:    [2]
metric:       [log_loss]
mode:         [oof_pred_bag]
n_models:     [5]

model  0:     [LogisticRegression]
    fold  0:  [0.17301165]
    fold  1:  [0.14248485]
    fold  2:  [0.11332626]
    fold  3:  [0.05356999]
    fold  4:  [0.19577129]
    ----
    MEAN:     [0.13563281] + [0.04958357]
    FULL:     [0.13572055]

model  1:     [DecisionTreeClassifier]
    fold  0:  [2.51467349]
    fold  1:  [3.39234385]
    fold  2:  [2.12021491]
    fold  3:  [2.96830087]
    fold  4:  [2.96830087]
    ----
    MEAN:     [2.79276680] + [0.43606857]
    FULL:     [2.79211399]
...
    ----
    MEAN:     [0.21282349] + [0.04062646]
    FULL:     [0.21290883]
'''

 

 


아래는 S_train을 사용하여 메타 모델을 훈련하고, 이 메타 모델을 통해 S_test 데이터에 대한 예측을 수행한 후 ROC AUC 점수를 계산하는 코드 예제입니다.  ROC AUC 점수는 이 모델의 성능을 나타내며, 출력으로 표시됩니다.

(ROC AUC(Receiver Operating Characteristic Area Under the Curve) 점수는 이진 분류 모델의 성능을 측정하는 지표 중 하나입니다. ROC 곡선(Receiver Operating Characteristic Curve) 아래의 면적을 나타내며 이 면적이 클수록 모델의 성능이 더 우수하다고 판단됩니다. )

X_train.shape, S_train.shape, X_test.shape, S_test.shape
#((426, 30), (426, 10), (143, 30), (143, 10))

meta_model = LR.fit(S_train, y_train)
roc_auc_score(y_test, meta_model.predict_proba(S_test)[:,1])
#0.9955974842767296

 

***3-layer stacking***
# level-1: LR, DT, MLP, KNN, SVM

models = clfs
S_train, S_test = stacking(models,                     # list of models
                           X_train, y_train, X_test,   # data
                           regression=False,           # classification task (if you need 
                                                       #     regression - set to True)
                           needs_proba=True,           # predict class labels (if you need 
                                                       #     probabilities - set to True) 
                           metric=accuracy_score,      # metric: callable
                           n_folds=3,                  # number of folds
                           stratified=True,            # stratified split for folds
                           shuffle=True,               # shuffle the data
                           random_state=0,             # ensure reproducibility
                           verbose=0)                  # print all info
# level-2: LR, MLP, DT
# Level-3: Voting

averaging = VotingClassifier(estimators = [('lr', LR), ('mlp', MLP), ('dt', DT)], voting='soft')
averaging.fit(S_train, y_train)
roc_auc_score(y_test, averaging.predict_proba(S_test)[:,1])
# 0.9935010482180294

 

***using `sklearn`***
위의 코드와 성능이 또 다르게 나옴. 위의 2개, 아래 3개 총 5개의 코드를 돌려보고 가장 상넝이 좋은 것을 채택해야겠다.
from sklearn.ensemble import StackingClassifier


# 2-layer stacking

estimators = [(type(clf).__name__, clf) for clf in clfs]
stk_clf = StackingClassifier(
    estimators=estimators, final_estimator=LR, cv=5)

stk_clf.fit(X_train, y_train)
roc_auc_score(y_test, stk_clf.predict_proba(X_test)[:,1])
#0.9907756813417191


# 3-layer stacking (Level-3: Voting)

layer_one_estimators = [(type(clf).__name__, clf) for clf in clfs]
voting = VotingClassifier(estimators = [('lr', LR), ('mlp', MLP), ('dt', DT)], voting='soft')
stk_clf = StackingClassifier(estimators=layer_one_estimators, final_estimator=voting, cv=5)

stk_clf.fit(X_train, y_train)
roc_auc_score(y_test, stk_clf.predict_proba(X_test)[:,1])
# 0.9926624737945493


# 3-layer stacking (Level-3: LR)

layer_one_estimators = [(type(clf).__name__, clf) for clf in clfs]
layer_two_estimators = [('lr', LR), ('mlp', MLP), ('dt', DT)]

layer_two = StackingClassifier(estimators=layer_two_estimators, final_estimator=LR)
stk_clf = StackingClassifier(estimators=layer_one_estimators, final_estimator=layer_two, cv=5)

stk_clf.fit(X_train, y_train)
roc_auc_score(y_test, stk_clf.predict_proba(X_test)[:,1])
#0.9920335429769392

 

 

(참고)

3-레이어 스태킹과 2-레이어 스태킹의 주요 차이점은 스태킹에 참여하는 모델의 수와 계층(layer)의 개수에 있습니다.

2-레이어 스태킹 (2-Layer Stacking):

2-레이어 스태킹은 두 개의 계층(layer)으로 구성됩니다.
첫 번째 계층(layer-1)에서는 여러 다양한 기본 모델(예: 로지스틱 회귀, 의사결정 트리, 랜덤 포레스트 등)이 훈련되고 예측을 수행합니다.
두 번째 계층(layer-2)에서는 첫 번째 계층의 예측 결과를 사용하여 최종 예측을 수행하는 메타 모델(예: 로지스틱 회귀)이 훈련됩니다.
최종 예측은 메타 모델이 계산한 결과입니다.
3-레이어 스태킹 (3-Layer Stacking):

3-레이어 스태킹은 세 개의 계층(layer)으로 구성됩니다.
첫 번째 계층(layer-1)에서는 여러 다양한 기본 모델이 훈련되고 예측을 수행합니다.
두 번째 계층(layer-2)에서는 첫 번째 계층의 예측 결과를 사용하여 중간 예측을 수행하는 중간 모델이 훈련됩니다.
세 번째 계층(layer-3)에서는 중간 모델의 예측 결과를 사용하여 최종 예측을 수행하는 메타 모델이 훈련됩니다.
최종 예측은 메타 모델이 계산한 결과입니다.
주요 차이점은 2-레이어 스태킹에서는 중간 예측을 수행하는 중간 모델이 없고, 메타 모델이 바로 첫 번째 계층의 예측 결과를 사용하여 최종 예측을 수행하는 반면, 3-레이어 스태킹에서는 중간 예측을 수행하는 중간 모델이 추가되어 계산 과정이 더 복잡해집니다. 3-레이어 스태킹은 보다 복잡한 앙상블 구조를 만들어내며, 모델 간의 상호작용을 더 잘 캡처할 수 있을 수 있지만, 계산 비용이 더 많이 들 수 있습니다.