전에 배웠던 Feature Importance는 각 특성이 증가/감소 시 모델의 예측값이 어떻게 변하는지와 같이 모델의 예측에 어떻게 영향을 주는지 알 수 없었다.
밑에 나올 ICE plot, PDP를 통해, 특정 관측치에 대해, 특정 특성값을 변화시킬 때 모델의 예측 양상이 어떻게 변하는지 파악해보자!
🌀 Individual Conditional Expectation (ICE) plot
특정 feature를 변화시켰을 때, 모델의 예측값이 어떻게 변화하는지를 개별 datapoint별로 시각화한 그래프
개별 datapoint의 예측값 변화를 보기 때문에 모델의 예측값이 어떤 이유로 변화하는지를 추적할 수 있다.
- 데이터에 등장하는 x의 모든 값에 하나씩, 해당 변수의 모든 샘플을 그 값으로 대체
- 이미 학습시킨 모델을 통해 각 샘플에 대한 예측값 구함.
- 구한 후, 평균을 내지 않고 모든 선을 그림
def get_ice_plot_data(data, data_index, target_feature, target_feature_range):
datapoint = data.iloc[[data_index]].copy() # 예측 양상의 변화를 확인하고자하는 데이터 가져오기
results = []
for target_value in target_feature_range:
datapoint[feature] = target_value # 특징의 값을 바꿔넣어본다.
datapoint_pred_proba = model.pred_proba(datapoint)[:,1] # 모델이 특징값이 바뀐 데이터로부터 확률 예측
results.append(datapoint_pred_proba.item()) # 각 확률을 리스트에 담아 주기
results = np.array(results)
results -= results[0] # 첫번째를 0으로 기준으로, 변화량을 보여줌.
return results
# 4개의 특징 ICE plot 그려보자.
target_feature = ['JobSatisfaction', 'DistanceFromHome', 'JobInvolvement', 'EnvironmentSatisfaction']
fig, axs = plt.subplots(2,2, figsize=(10,8))
for ax, feature in zip(axs.flatten(), target_feature):
target_feature_range = range(x_test[target].min(), x_test[target].max()+1)
ax.plot(get_ice_plot_data(x_test, 100, target_feature, target_feature_range))
ax.set_title(feature)
하나의 ICE plot을 그려본 후, 모델이 해당 특성에 대해 전반적으로 어떻게 분석하고 이해하고 있는지 판단할 수 없다.
Ice plot은 각 인덱스에서의 특정 특징들에 대해 모델의 예측값이 어떻게 변하는지 각 데이터 포인트를 시각화한 것이고, PDP는 모든 데이터포인트를 고려해서, 특정 특징들에 대해 모델의 예측값이 어떻게 변하는지를 평균적으로 나타낸 것이다.
🌀 Partial Dependence Plot (PDP) : 부분의존도 그림
partial : 다른 변수들의 영향을 모두 고정한 상황에서, 해당 변수를 한 단위 증가시킬 때 변화하는 target의 크기
gradient boosting 결과를 해석하려면?
- 선형 회귀는 회귀계수로 해석할 수 있지만, tree based model은 해석 불가능.
- tree based model은 대신, PDP 사용해 개별 특성과 target관계를 통해 해
PDP : 평균 ICE plot
- X의 모든 값에 하나씩, 해당 변수의 모든 샘플을 그 값으로 대체
- 이미 학습 시킨 모델을 통해 각 샘플에 대한 예측값 구함
- 구한 예측값의 평균을 냄.
- 각 값에 대해 평균 예측값이 어떻게 변화하는지 나타냄.
특정 feature값을 변화시켰을 때, 모델의 예측값이 어떻게 변화하는지를 평균적으로 시각화한 그래프 (모든 데이터 포인트를 고려)
모든 datapoint를 고려하기 때문에 ICE plot과 달리, 모델이 feature 값에 어떻게 반응하는지를 전반적으로 파악하기에 유용하다.
PDP 해석 시 주의할 점
- PDP는 각 특성 간의 독립성을 전제로 한다. -> 서로 강한 상관관계가 있는 경우, 잘못된 예측을 할 수 있다.
- 샘플 간 성질이 매우 다를 경우 -> 예측값들을 평균낸 값들로 영향을 보는 것이기 때문에, 샘플들의 서로 다른 성질들이 반영되지 못한다.
PDP를 손쉽게 그려주는 pdpbox
라이브러리 이용
pdp_isolate
: 하나의 특성에 대한 PDP 시각화 가능grid_type
- percentile : 특성의 분포에 따라 특성값 지점 선택
- equal : 동일한 간격으로 지점 선택
num_grid_points
: 몇 개의 특성값 지점을 선택하여 모델의 변화 양상을 확인해볼 것인지 선택
자세한 설명은 공식 문서 참고
from pdpbox.pdp import pdp_isolate, pdp_plot
isolated = pdp_isolate(model=model,
dataset=x_test,
model_features=x_test.columns,
feature='age',
grid_type='percentile', # defualt='percentile' or 'equal'
num_grid_points=10) # default=10
pdp_plot
을 사용해 PDP를 50개의 ICE plot과 함께 그려보자.
pdp_plot(isolated, # PDP를 그릴 정보를 담고 있는 객체
feature_name='age',
plot_lines=True # ICE plots
frac_to_plot=50 # ICE plots 개수 (float type 입력시, 전체 데이터 개수 중, plotting할 데이터 개수)
plot_pts_dist=True) # ICE plots에 해당하는 datapoint의 분포 시각화 옵
y축은 변화량을 나타냄.
2개 특성의 변화에 따른 PDP를 heatmap 형태로 시각화해보자!
from pdpbox.pdp import pdp_interact, pdp_interact_plot
# 나이, 주당 근무시간의 특성의 변화에 따른 pdp 확인
target_features = ['age', 'hours-per-week']
interaction = pdp.interact(model=model,
dataset=x_test,
model_features=x_test.columns,
features=['age','hours-per-week'])
pdp_interact_plot(interaction, plot_type='grid', feature_names=target_features)
값들은 변화량이 아니라, 예측값 자체인 것 주의!!
범주형 특성에 대해서 PDP그려보자.
- 범주형 특성은 학습 시 인코딩되어 수치형으로 변환되는데, 수치형으로 변환되기 전 범주 값을 PDP에 제공하여 이해하기 쉬운 형태로 시각화해보자.
target_feature = 'marital-status'
# 학습 시 사용한 encoder로부터 각 특성값들을 어떤 수치값으로 매핑했는지 확인
mappings = encoder.mapping
# mappings 에서 우리가 보고자 하는 타겟 속성의 mapping 꺼내오기
mapping_data = list(filter(lambda x: x['col'] == target_feature, mappings))
# mappings리스트에서 각 요소를 하나씩 추출하면서 lambda함수를 적용해 결과가 true인 요소들만 모아서 새로운 리스트 반환
mapping_data
>> [{'col': 'marital-status', 'mapping': Married-civ-spouse 1
Widowed 2
Divorced 3
Never-married 4
Separated 5
Married-spouse-absent 6
Married-AF-spouse 7
NaN -2
dtype: int64, 'data_type': dtype('O')}]
maps = mapping_data[0]['mapping']
# 인코딩된 후의 값을 기준으로 pdp 그려보기
pdp_list = pdp_isolate(model=model,
dataset=x_val_encoded,
model_features=x_val.columns,
feature=target_feature,
cust_grid_points = [-2,1,2,3,4,5,6,7]) # 특성값찍어볼 지점
pdp_plot(pdp_dist, target_feature)
# 인코딩 후의 값 pdp
인코딩 전의 pdp도 그려보자. xticks
이용!
encoded_features = maps.values.tolist()
original_features = maps.index.tolist()
pdp_plot(pdp_dist, target_feature)
# x축에 표시될 값을 encoded features에서 original features로 매핑만 해주면 인코딩 전의 pdp!!
plt.xticks(encoded_features, original_features, rotation=90)
cf) SHAP : 게임이론에 나오는 이론으로써, 전체 성과에서 개인이 얼마나 공헌했는지를 수치로 나타내는 표현이다.
- Shapley Values는 가능한 모든 조합을 고려한 후, 플레이어의 평균 예상 한계 기여도를 의미한다.
- 특성 간의 영향을 고려한 계산이 가능하고, 음의 관계도 표현할 수 있다. ( 개념상으로는 permutation importance보다 정확하다.)
- But, 관측치 하나마다 특성의 영향도가 다르게 계산될 수 있다.
PDP나, 특성 중요도와 달리, SHAP은 데이터 샘플 하나에 대해 설명한다.
전체 관측치에 대한 개별 특성들의 설명 : PDP, Ice plot
개별 관측치에 대한 설명 : SHAP
🌀 ML 모델 해석
전체 데이터(Global) | 개별 데이터(Local) | |
---|---|---|
특정 모델(Model-Specific) | - 트리 기반 모델 (DT, RandomForest, XGB…) : 변수중요도, tree plot - 선형회귀, 로지스틱회귀 : 회귀계수 (회귀계수끼리 비교하려면 스케일링 된 데이터로 모델링해야함.) |
- |
모든 모델(Model-Agnostic) | - Drop column / Permutation Importance - PDP - SHAP summery_plot (모델의 예측 결과를 각 피쳐별로 기여도 계산) |
- ICE plot SHAP force_plot |
“전체 성능이 뛰어난 모델을 생성하셨어요! 그렇다면, 이 모델에서 중요한 요인이 무엇인가요?” | “왜 이 고객은 신용도가 낮게 예측되었나요?”, “이 고객의 소득수준이 높아진다면 신용도가 높게 평가되나요?” |
이러한 방법을 사용하여 모델의 설명력을 높일 수 있다.
[Reference]
'Machine Learning' 카테고리의 다른 글
[Applied Predictive Modeling 4] Special Classification Problem (0) | 2023.04.17 |
---|---|
[Applied Predictive Modeling 3] ML Problem Framing (0) | 2023.04.17 |
[Applied Predictive Modeling 1] Interpretable ML (0) | 2023.04.17 |
[The Based Models 4] Model Tuning (0) | 2023.04.15 |
[The Based Models 3] Preprocessing (0) | 2023.04.15 |