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
- 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 갯수
- $\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)})$
- 같은 Feature 끼리의 Weighted Impurity Decrease는 합친다.
- 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
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
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
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
Analysis
- 상위 4개 중요한 Feature을 제외한 나머지 Feature들의 중요도는 0이 나옵니다.
- 단점 : 어떤 Feature가 Target에 영향을 주는지 알 수는 있지만 디테일하게 알 수는 없습니다.
- Ex) 해당 Feature값이 증가/감소하면 Target 값이 어떻게 변하는지 알 수 없습니다.
[Permutation Importance]
- 관심있는 Feature에만 Noise를 주고 예측하였을 때 성능이 얼마큼 감소하는지를 측정해 특성의 중요도를 보는 방법
Steps
-
관심있는 Feature을 고른다.
-
해당 Feature의 순서를 섞는다.
-
순서를 섞지 않았을 때와 섞었을 때의 성능 차이가 많이 날수록 해당 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
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
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
- Target의 평균을 구하고 Initial Residual (Observed Value - Predicted Value (지금 과정에서는 평균값)) 을 구합니다.
- Initial Residual 가 Target인 Tree Model을 만듭니다.
- 특정 Leaf Node에 sample 이 2개 이상일 경우 이 들의 평균으로 Initial Residual을 대체합니다.
- Initial Residual --> Updated Resdidual
- 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로 설정)
- 새로운 Residuals을 가지고 새로운 Tree Model을 만들고 트리 최대 설정 갯수 (hyperparameter) 까지 2~4번 과정을 반복합니다.
- 최종 예측값은 아래와 같은 식으로 구할 수 있습니다.
- $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
- Residual (실제 Target 과 실제 Target의 평균의 차이) 가 Target인 Model
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
- Lr = 0.1로 설정
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
- $Final \ Predicted \ Value = Target \ Average + \sum_i^jLr \times Updated \ Residuals_i$
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
- 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})$
- Initial Residual 을 구합니다.
- Initial Residual = Observed Values - Predicted Value
- 첫번재 트리에 쓰일 Predicted Value 는 1번 과정에서 구한 Target Probability 값입니다.
- Initial Residual 가 Target인 Tree Model을 만듭니다.
- 특정 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을 대체했습니다.
- 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로 설정)
- 새로운 Residuals을 가지고 새로운 Tree Model을 만들고 트리 최대 설정 갯수 (hyperparameter) 까지 2~4번 과정을 반복합니다.
- 최종 예측값은 아래와 같은 식으로 구할 수 있습니다.
- $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
- Residual (실제 Target 과 실제 Target Probability의 차이) 가 Target인 Model
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와 같음
'[AI] > Machine Learning' 카테고리의 다른 글
Tree Based Model(3) - Evaluation Metrics for Classification (0) | 2021.03.07 |
---|---|
Tree Based Model(4) - Model Selection (0) | 2021.03.07 |
Applied Predictive Modeling(1) - Choosing ML Problems (0) | 2021.03.07 |
Applied Predictive Modeling(2) - Data Wrangling (0) | 2021.03.07 |
Applied Predictive Modeling(4) - XGBoost / Light GBM & Interpreting ML Model (0) | 2021.03.07 |