Applied Predictive Modeling(3) - (Feature, Permutation, Drop-Column) Importance & Boosting (Gradient Boosting)

2021. 3. 7. 15:37[AI]/Machine Learning

Learned Stuff

Key Points

  • Feature Importance

 

  • Permutation Importance

 

  • Drop Column Importance

 

  • Boosting
    • Gradient Boosting (Regression & Classification)

New Stuff

[Feature Importance]

  • 어떤 Feature가 Target을 예측하는데 큰 영향을 미치는지 확인해보는 여러 방법들 중 하나

 

  • Mean Decrease Impurity 를 계산한 값
    • 1에 가까울수록 중요한 feature / 0에 가까울수록 덜 중요한 feature
    • Feature Importance의 합은 1

 

  • Gini Impurity 복습 (Entropy 도 비슷한 개념)
    • $Gini \ Index = 1-P(YES)^2-P(NO)^2$
    • $Gini \ Impurity(weighted \ impurity) = P_{split1}\times Gini_{split1} + P_{split2}\times Gini_{split2}$

 

Calculation

  1. Split 되는 Node 의 Weighted Impurity Decrease를 계사한다. (Child Node가 2개라고 가정)
    • $\frac{N_t}{N}(Gini \ Index_{(Parent)} - \frac{N_{tR}}{N_t} \times Gini \ Index_{(Child \ Right)} - \frac{N_{tL}}{N_t} \times Gini \ Index_{(Child \ Left)})$
      • $N_t$ : Parent Node 에 들어가는 Sample 갯수
      • $N$ : 해당 Tree Model에 쓰인 모든 Sample 갯수
      • $N_{tL}$ : Child Node (왼쪽) 에 들어가는 Sample 갯수
      • $N_{tR}$ : Child Node (오른쪽) 에 들어가는 Sample 갯수

 

  1. 같은 Feature 끼리의 Weighted Impurity Decrease는 합친다.

 

  1. Normalize (정규화) 시킨다.
    • 이 때 정규화한 모든 Weighted Impurity Decrease의 합은 1이 된다.

 

Example

  • Kaggle 의 'Heart Failure Clinical Records' Data를 가지고 간단한 예시를 들어보겠습니다.

 

Feature Importance 확인하기

 

Code

# Data 불러오기
df = pd.read_csv('/content/heart_failure_clinical_records_dataset.csv')

# DEATH_EVENT (Target) - 0 : 생존 / 1 : 사망
X_train = df.drop('DEATH_EVENT',axis=1)
y_train = df['DEATH_EVENT']

# Decision Tree Model에 적용
clf = DecisionTreeClassifier(max_depth=3,random_state=42)
clf.fit(X_train, y_train)

# Feature Importance 바로 구하기 

# Normalize 하기 전
feat_importance = clf.tree_.compute_feature_importances(normalize=False)
print("feat importance = " + str(feat_importance))

# Normalize 한 뒤 
print("normalized feature importances : ", clf.feature_importances_)

 

Output

 

Tree Visualization

 

Code

out = export_graphviz(clf)
display(graphviz.Source(out))

 

Output

Tree Visualization

 

1. Calculation (Before Normalization)

 

Code

# 1st branch node + 3rd branch node (left) + 3rd branch node (right)
X11_feature_importance = \
(299/299) * (0.436 - (76/299) * 0.284 - (223/299) * 0.252) + \
(37/299) * (0.418 - (25/37) * 0.269 - (12/37) * 0.486) + \
(41/299) * (0.497 - (31/41) * 0.487 - (10/41) * 0.18)

# 2nd branch node
X8_feature_importance = \
(76/299) * (0.284 - (39/76) * 0.097 - (37/76) * 0.418)

# 2nd branch node
X7_feature_importance = \
(223/299) * (0.252 - (182/223) * 0.142 - (41/223) * 0.497)

# 3rd branch node (left) + 3rd branch node (right)
X4_feature_importance = \
(39/299) * (0.097 - (32/39) * 0 - (7/39) * 0.408) + \
(182/299) * (0.142 - (20/182) * 0.42 - (162/182) * 0.094)

# 확인
print(f'X11 feature importance 계산값 : {X11_feature_importance}')
print(f'X11 feature importance 코드로 구한 값 : {feat_importance[11]}')
print()
print(f'X8 feature importance : {X8_feature_importance}')
print(f'X8 feature importance 코드로 구한 값 : {feat_importance[8]}')
print()
print(f'X7 feature importance : {X7_feature_importance}')
print(f'X7 feature importance 코드로 구한 값 : {feat_importance[7]}')
print()
print(f'X4 feature importance : {X4_feature_importance}')
print(f'X4 feature importance 코드로 구한 값 : {feat_importance[4]}')
print()

 

Output

Feature Importance Comparison (Before Normalization)

 

Analysis

  • Gini Index 값이 소수점 3자리로 반올림한 값으로 계산했기 때문에 오차가 발생

 

2. Calculation (After Normalization)

 

Code

# Normalization 적용
tot = X11_feature_importance + X8_feature_importance + X7_feature_importance + X4_feature_importance

X11_feature_importance_norm = X11_feature_importance / tot
X8_feature_importance_norm = X8_feature_importance / tot
X7_feature_importance_norm = X7_feature_importance / tot
X4_feature_importance_norm = X4_feature_importance / tot

# 확인
print(f'X11 feature importance 계산값 : {X11_feature_importance_norm}')
print(f'X11 feature importance 코드로 구한 값 : {clf.feature_importances_[11]}')
print()
print(f'X8 feature importance : {X8_feature_importance_norm}')
print(f'X8 feature importance 코드로 구한 값 : {clf.feature_importances_[8]}')
print()
print(f'X7 feature importance : {X7_feature_importance_norm}')
print(f'X7 feature importance 코드로 구한 값 : {clf.feature_importances_[7]}')
print()
print(f'X4 feature importance : {X4_feature_importance_norm}')
print(f'X4 feature importance 코드로 구한 값 : {clf.feature_importances_[4]}')
print()

 

Output

Feature Importance Comparison (After Normalization)

 

Analysis

  • 위와 마찬가지로 반올림으로 인한 오차로 값에 차이가 발생
    • Gini Index를 반올림하지 않고 계산한다면 일치할 것

 

  • Feature Importance 순서
    • 12번째 feature (time) : 0.792
    • 8번째 feature (serum_creatinine) : 0.134
    • 5번째 feature (ejection_fraction) : 0.042
    • 9번째 feature (serum_sodium) : 0.031

 

Feature Importance Visualization

 

Code

# 어떤 feature인지 확인
print(X_train.columns[[11,7,4,8]],end='\n\n')

# Feature Importance 시각화

importances = pd.Series(clf.feature_importances_, X_train.columns)

importances.sort_values().plot.barh();

 

Output

Feature Importance Visualization

 

Analysis

  • 상위 4개 중요한 Feature을 제외한 나머지 Feature들의 중요도는 0이 나옵니다.

 

  • 단점 : 어떤 Feature가 Target에 영향을 주는지 알 수는 있지만 디테일하게 알 수는 없습니다.
    • Ex) 해당 Feature값이 증가/감소하면 Target 값이 어떻게 변하는지 알 수 없습니다.

 

[Permutation Importance]

  • 관심있는 Feature에만 Noise를 주고 예측하였을 때 성능이 얼마큼 감소하는지를 측정해 특성의 중요도를 보는 방법

 

Steps

  1. 관심있는 Feature을 고른다.

  2. 해당 Feature의 순서를 섞는다.

  3. 순서를 섞지 않았을 때와 섞었을 때의 성능 차이가 많이 날수록 해당 Feature가 중요하다는 것을 의미한다.

 

Code

  • 위에서와 같은 Dataset을 이용하겠습니다.

  • Colab 에서는 pip install을 해야합니다.

 

pip install eli5
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)

import eli5
from eli5.sklearn import PermutationImportance

# permuter 정의
permuter = PermutationImportance(
    clf, # model
    scoring='accuracy', # metric
    n_iter=5, # 다른 random seed를 사용하여 5번 반복
    random_state=42
)

# Train Data를 permuter 에 fit
# Train / Validation / Test Data 개별적으로 fitting 해서 각 data 별 중요도를 확인할 수도 있음
permuter.fit(X_train, y_train);

# 특성별 score 확인
eli5.show_weights(
    permuter, 
    top=None, # top n 지정 가능, None 일 경우 모든 특성 
    feature_names=list(X_train.columns) # list 형식으로 넣어야 합니다
)

 

Output

Permutation Importance

 

Analysis

  • Feature Importance와 비슷한 순서를 가지는 것을 확인할 수 있습니다.

 

[Drop Column Importance]

  • 특정 Feature을 포함시켰을 때와 포함시키지 않았을 때의 성능을 비교해 중요도를 보는 방법

 

  • Feature을 제외시켜야 하기 때문에 확인할 때 model에 fit을 두번 시켜야 합니다.
    • fit 1 : 특정 Column 포함시켰을 때
    • fit 2 : 특정 Column 포함시키지 않았을 때

 

  • 따라서, N개의 Feature가 있다면 N+1 번 model에 fit을 해주어야 합니다.

 

Code

  • 위와 같은 Dataset을 사용하겠습니다.

 

# X_train / y_train 설정
X_train = df.drop('DEATH_EVENT',axis=1)
y_train = df['DEATH_EVENT']

# Decision Tree Classifier Model 에 적용
clf1 = DecisionTreeClassifier(max_depth=3,random_state=42)
clf1.fit(X_train, y_train)

# 모든 feature을 포함했을 때의 accuracy score 값
original_accuracy = clf1.score(X_train,y_train)

# feature을 한개씩 drop 시켰을 때의 accuracy score difference 구하기
for i in range(len(X_train.columns)) :
  new_X_train = X_train.drop(X_train.columns[i],axis=1)

  clf = DecisionTreeClassifier(max_depth=3,random_state=42)
  clf.fit(new_X_train, y_train)

  print(f'Dropped "{X_train.columns[i]}" column, accuracy difference : {original_accuracy - clf.score(new_X_train,y_train)}')
  print('-----------------------------------------------------')

 

Output

Accuracy Difference Results

 

Analysis

  • 'time' feature가 포함되었을 때와 포함되지 않았을 때의 accuracy 차이가 크므로 중요한 feature라는 것을 알 수 있습니다.
  • 그 다음으로는 'serum_creatinine' & 'serum_sodium' feature 의 accuracy 차이가 큽니다.

 

[Boosting]

  • Boosting 또는 Bagging 모두 앙상블 모델을 기반으로 만들어지지만 Tree를 만드는 방법에서 차이가 발생합니다.
    • Bagging (Random Forest) : 독립적인 Tree를 생성
    • Boosting (Gradient Boosting) : 이전에 만들어진 Tree가 이후에 만들어진 Tree에 영향을 줌

 

Gradient Boosting

  • Residual 을 가지고 Tree을 업데이트하는 방식
    • Residual : 예측값과 실제값의 차이(거리)를 의미

 

1. Regression Model

 

Steps

  1. Target의 평균을 구하고 Initial Residual (Observed Value - Predicted Value (지금 과정에서는 평균값)) 을 구합니다.

 

  1. Initial Residual 가 Target인 Tree Model을 만듭니다.

 

  1. 특정 Leaf Node에 sample 이 2개 이상일 경우 이 들의 평균으로 Initial Residual을 대체합니다.
    • Initial Residual --> Updated Resdidual

 

  1. Learning Rate을 설정해주고 새로운 Predicted Value를 가지고 새로운 Residual를 구합니다.
    • $New \ Initial \ Residual = Observed \ Value - Predicted \ Value$
    • $Predicted \ Value = Target \ Average + \sum_i^jLr \times Updated \ Residuals_i$
      • Lr (Learning Rate) 은 설정하기 나름 (보통 0.1로 설정)

 

  1. 새로운 Residuals을 가지고 새로운 Tree Model을 만들고 트리 최대 설정 갯수 (hyperparameter) 까지 2~4번 과정을 반복합니다.

 

  1. 최종 예측값은 아래와 같은 식으로 구할 수 있습니다.
    • $Final \ Predicted \ Value = Target \ Average + \sum_i^jLr \times Updated \ Residuals_i$

 

Example

  • 위와 동일한 Data를 사용하지만 축소시켜서 예시를 들어보겠습니다.
    • Target : ejection_fraction

 

Data

  • 예시로 사용될 Data는 아래와 같습니다.
    Dataset

 

1st Tree

  • 첫번째 Tree는 아래와 같습니다.
    • Residual (실제 Target 과 실제 Target의 평균의 차이) 가 Target인 Model
      1st Tree

 

1st Tree Leaf Node Samples

  • 첫번째 Tree의 Leaf Node에 들어가는 Sample은 다음과 같습니다.
    1st Tree Leaf Node Samples

 

Updated Data

  • 해당 Data에 첫번째 Residual을 Leaf Node 별 평균으로 구해보면 다음과 같습니다.
    1st Tree Updated Data

 

2nd Tree

  • 새로운 Residual을 가지고 구한 두번째 Tree는 아래와 같습니다.
    • $New \ Residual = Observed \ Value - Predicted \ Value$
    • $Predicted \ Value = Target \ Average + Lr \times First \ Updated \ Residuals$
      • Lr = 0.1로 설정
        2nd Tree

 

2nd Tree Leaf Node Samples

  • 두번째 Tree의 Leaf Node에 들어가는 Sample은 다음과 같습니다.
    2nd Tree Leaf Node Samples

 

Updated Data

  • 해당 Data에 두번째 Residual을 Leaf Node 별 평균으로 구해보면 다음과 같습니다.
  • 두개의 Tree Model로 아래 공식을 활용해서 구한 최종 예측값은 다음과 같습니다.
    • $Final \ Predicted \ Value = Target \ Average + \sum_i^jLr \times Updated \ Residuals_i$
      2nd Tree Updated Data

 

Analysis

  • Tree 갯수를 늘릴수록 Residual 값이 줄어듭니다.
    • 하지만, Tree 갯수를 너무 높게 설정하면 과적합 Issue가 발생할 수도 있습니다.
    • Hyperparamter Tuning을 통해 최적화를 진행해야 합니다.

 

  • 실제 Modeling 과정에서는 Train Data 에 학습을 시키고 Validation Data로 검증 단계를 거쳐야합니다.
    • 또는 Cross-Validation 방법

 

Code

from sklearn.ensemble import GradientBoostingRegressor

# X_train / y_train
# X_validation / y_validation Dataset 이 있다고 가정

reg = GradientBoostingRegressor(random_state= ,n_estimators=, learning_rate= , loss= , criterion= , min_samples_split= , min_samples_leaf= , max_depth= , max_features= )

# Train data에 fit
reg.fit(X_train,y_train)
# Parameters

# random_state : random_seed 설정

# n_estimators : # of tree 설정 (default : 100)

# learning_rate : # learning rate 설정 (default : 0.1)

# loss : # loss 함수 설정 (default : 'ls')
    # ls : least squares
    # lad : least absolute deviation
    # huber : combination of ls & lad
    # quantile : quantile regression

# criterion : split 할 때 쓰이는 함수 (default : 'friedman_mse')
    # friedman_mse : mse with improvedment score by Friedman
    # mse : mean squared error
    # mae : mean absolute error

# min_samples_split : split 하기 위한 최소한의 sample 수 (default : 2)

# min_samples_leaf : leaf node가 되기 위한 최소한의 sample 수 (default : 1)

# max_depth : max depth 설정 (default : 3)

# max_features : 사용할 feature의 최대 갯수 (default : auto)
    # auto : max_features = data의 총 feature 갯수
    # sqrt : max_features = data의 총 feature 갯수의 루트값
    # log2 : max_features = data의 총 feature 갯수에 로그를 취한 값
# Attributes

# 특성 중요도 구하기
reg.feature_importances_

# 예측값 구하기 (on Validation Dataset)
reg.predict(X_validation)

2. Classification Model

 

Steps

  1. Target에 관한 Logistic 함수를 바탕으로 Probability 를 구합니다. 이 값은 Regression Model에서의 평균값과 비슷한 개념이라고 보면 됩니다.
    • $Target \ Probability = \frac{e^{log(odds)}}{1 + e^{log(odds)}}$
    • $Target Log(odds) = log(\frac{number \ of \ YES}{number \ of \ NO})$

 

  1. Initial Residual 을 구합니다.
    • Initial Residual = Observed Values - Predicted Value
    • 첫번재 트리에 쓰일 Predicted Value 는 1번 과정에서 구한 Target Probability 값입니다.

 

  1. Initial Residual 가 Target인 Tree Model을 만듭니다.

 

  1. 특정 Leaf Node에 sample 이 2개 이상일 경우 아래 공식을 활용해서 Initial Residual을 대체합니다.
    • Initial Residual --> Updated Residual
    • $Updated \ Residual = \frac{\sum Initial \ Residual_i}{\sum[Previous \ Predicted \ Probability \times (1 - Previous \ Predicted \ Probability)]}$
    • Regression Model에서는 sample 들의 평균으로 Initial Residual을 대체했습니다.

 

  1. Learning Rate을 설정해주고 새로운 Predicted Value를 가지고 새로운 Residual를 구합니다.
    • $New \ Initial \ Residual = Target \ Probability - Predicted \ Probability$
    • $Predicted \ Probability = \frac{e^{Predicted \ log(odds)}}{1 + e^{Predicted \ log(odds)}}$
    • $Predicted \ log(odds) = Target \ Log(odds) + \sum_i^jLr \times Updated \ Residuals_i$
      • Lr (Learning Rate) 은 설정하기 나름 (보통 0.1로 설정)

 

  1. 새로운 Residuals을 가지고 새로운 Tree Model을 만들고 트리 최대 설정 갯수 (hyperparameter) 까지 2~4번 과정을 반복합니다.

 

  1. 최종 예측값은 아래와 같은 식으로 구할 수 있습니다.
    • $Final \ Predicted \ Probability = \frac{e^{Final \ Predicted \ log(odds)}}{1 + e^{Final \ Predicted \ log(odds)}}$
    • $Final \ Predicted \ Log(odds) = Target \ Log(odds) + \sum_i^jLr \times Updated \ Residuals_i$

 

Example

  • 위와 동일한 Data를 사용하지만 축소시켜서 예시를 들어보겠습니다.
    • Target : high_blood_pressure

 

Data

  • 예시로 사용될 Data는 아래와 같습니다.
    Dataset

 

1st Tree

  • 첫번째 Tree는 아래와 같습니다.
    • Residual (실제 Target 과 실제 Target Probability의 차이) 가 Target인 Model
      1st Tree

 

1st Tree Leaf Node Samples

  • 첫번째 Tree의 Leaf Node에 들어가는 Sample은 다음과 같습니다.
    1st Tree Leaf Node Samples

 

Updated Data

  • 해당 Data에 아래 공식을 활용해서 첫번째 Initial Residual을 대체하였습니다.
    • $First \ Updated \ Residuals = \frac{\sum First \ Initial \ Residual_i}{\sum[Previous \ Probability \times (1 - Previous \ Probability)]}$
    • 이때의 Previous Probability는 Target Probability, 즉 전부다 $\frac{e^{log(3/7)}}{1 + e^{log(3/7)}} = 0.3$ 입니다.

 

  • 첫번째 Tree의 Residuals을 가지고 아래 공식에 적용한 뒤 예측값을 구하고 2번째 Initial Residual를 구했습니다.
    • $Second \ Initial \ Residual = Target \ Probability - Predicted \ Probability$
    • $Predicted \ Probability = \frac{e^{Predicted \ log(odds)}}{1 + e^{Predicted \ log(odds)}}$
    • $Predicted \ Log(odds) = Target \ Log(odds) + \sum_i^jLr \times \ Updated \ Residuals$
      2nd Tree Updated Data

 

2nd Tree

  • 새로운 Initial Residual을 가지고 구한 두번째 Tree는 아래와 같습니다.
    2nd Tree

 

2nd Tree Leaf Node Samples

  • 두번째 Tree의 Leaf Node에 들어가는 Sample은 다음과 같습니다.
    2nd Tree Leaf Node Samples

 

Updated Data

  • 해당 Data에 아래 공식을 활용해서 두번째 Initial Residual을 대체하였습니다.
    • $Second \ Updated \ Residual = \frac{\sum Second \ Initial \ Residual_i}{\sum[Previous \ Predicted \ Probability \times (1 - Previous \ Predicted \ Probability)]}$

 

  • 두개의 Tree Model로 아래 공식을 활용해서 구한 최종 예측값은 다음과 같습니다.
    • $Final \ Predicted \ Probability = \frac{e^{Predicted \ log(odds)}}{1 + e^{Predicted \ log(odds)}}$
    • $Final \ Predicted \ Log(odds) = Target \ Log(odds) + \sum_i^jLr \times \ Updated \ Residuals$
      2nd Tree Updated Data

 

Analysis

  • Regression Model과 마찬가지로 Tree 갯수를 늘릴수록 Residual 값이 줄어듭니다.
    • 하지만, Tree 갯수를 너무 높게 설정하면 과적합 Issue가 발생할 수도 있습니다.
    • Hyperparamter Tuning을 통해 최적화를 진행해야 합니다.

 

  • 현재에서는 threshold가 0.5로 잡혀있지만 threshold tuning을 통해 accuracy를 높일수도 있습니다.

 

  • 실제 Modeling 과정에서는 Train Data 에 학습을 시키고 Validation Data로 검증 단계를 거쳐야합니다.
    • 또는 Cross-Validation 방법

 

Code

from sklearn.ensemble import GradientBoostingClassifier

# X_train / y_train
# X_validation / y_validation Dataset 이 있다고 가정

clf = GradientBoostingClassifier(random_state= ,n_estimators=, learning_rate= , loss= , criterion= , min_samples_split= , min_samples_leaf= , max_depth= , max_features= )

# Train data에 fit
clf.fit(X_train,y_train)
# Parameters

# loss : loss function (default : 'deviance')
    # deviance : logistic regression
    # exponential : AdaBoost algorithm 적용 (못 맞춘 sample에 가중치를 주어 더 잘 맞추게 하는 알고리즘)

# 나머지는 Gradient Boosting Regressor와 같음
# Attributes

# Gradient Boosting Regressor와 같음
728x90