n234_interpreting-ml-model


学習目標
  • 部の領域依存図を表示および説明することができる.
  • Shap値マップを使用して、
  • 個の予測事例を記述することができる.
  • 機械学習予測モデルを構築した後、アナリストは「予測モデルの結果が何を意味するか」の結果を説明しなければならない.
    しかし,高性能モデルを作成するための組合せモデル(ランダム森林,加速)やブラックボックス(複雑な機械学習モデル)については,線形モデルと比較してその結果の理解と信頼は困難である.
  • 不透明な「ブラックボックス」モデル:DNN、ランダムforrest、グラデーションガイドを考慮できます.通常、多くの予測変数と複雑な変換が使用されます.多くのパラメータがあるものもあります.通常、これらのモデルで発生することは、特にターゲットとの関連を可視化または理解することが困難である.しかし,予測の精度は他のモデルよりはるかに高い可能性がある.最近,ブラックボックスモデルの透明度を高めるために,学習過程の一部としての技法など様々な研究が行われている.予測に加えて,モデルの記述と学習後の可視化特性も透明性を向上させる方法である.
  • つまり、次の関係が確立されます.
  • の複雑なモデル->理解しにくいが、性能は良好である.
  • シンプルモデル->わかりやすいですが、パフォーマンスが不足しています.
  • ランダムforestとboostingでは,特性重要度(feature importance)値を容易に得ることができ,いくつかの特性がモデルの性能に重要で,よく用いられる情報だけを見ることができる.
    次の2つの予測モデルの結果解釈方法は、モデルに拘束されず(モデル-不明)興味のある特性がターゲットにどのように影響するかを示します.
    PDP(Partial Dependence Plot)
    :ターゲット全体の平均値に及ぼす特性の影響(ICE曲線の平均値)
    ICE curve(Individual Conditional Expectation)
    :各観測目標に及ぼす特性の影響
    SHAP (Global)
    :プロパティ全体がターゲット全体に与える影響
    SHAP (Local)
    :すべての特性が各観測目標に与える影響

    部分領域依存図


    予測モデルを構築する際、どのような特性(feature)が予測モデルの目標変数(target variable)にどのような影響を及ぼすか(個別の特性と目標観の関係)は、回帰と分類問題に使用できるグローバル(Global)方法論である.

    PDP 1特性

    !pip install pdpbox
    
    from pdpbox.pdp import pdp_isolate, pdp_plot
    
    feature = 'annual_inc'
    
    #선형회귀 모델
    isolated = pdp_isolate(
        model=linear, 
        dataset=X_val, 
        model_features=X_val.columns, 
        feature=feature,
        grid_type='percentile', # default='percentile', or 'equal'
        num_grid_points=10 # default=10
    )
    
    pdp_plot(isolated, feature_name=feature);
    
    #부스팅 모델
    isolated = pdp_isolate(
        model=boosting, 
        dataset=X_val_encoded, 
        model_features=X_val_encoded.columns, 
        feature=feature
    )
    
    pdp_plot(isolated, feature_name=feature);
    #일부분 확대
    pdp_plot(isolated, feature_name=feature)
    plt.xlim((20000,150000));

    PDP 10個の独立条件展開曲線


    個別条件期待値(ICE)
    :目標値が観測値の興味に従って変化する曲線
    局所依存図(PDP)
    :ICEの線平均値に焦点を当てたグローバルメソッド
    pdp_plot(isolated
             , feature_name=feature
             , plot_lines=True # ICE plots
             , frac_to_plot=0.001 # or 10 (# 10000 val set * 0.001)
             , plot_pts_dist=True) 
    
    plt.xlim(20000,150000);

    PDP 2プロパティの使用(インタラクティブ)

    from pdpbox.pdp import pdp_interact, pdp_interact_plot
    
    features = ['annual_inc', 'fico_range_high']
    
    interaction = pdp_interact(
        model=boosting, 
        dataset=X_val_encoded,
        model_features=X_val.columns, 
        features=features
    )
    
    pdp_interact_plot(interaction, plot_type='grid', 
                      feature_names=features);

    PDP 3 D(Plotly使用)

    features
    
    # 2D PDP dataframe
    interaction.pdp
    
    type(interaction.pdp)	#pandas.core.frame.DataFrame
    
    # 위에서 만든 2D PDP를 테이블로 변환(using Pandas, df.pivot_table)하여 사용
    
    pdp = interaction.pdp.pivot_table(
        values='preds', # interaction['preds']
        columns=features[0], 
        index=features[1]
    )[::-1] # 인덱스를 역순으로 만드는 slicing입니다
    
    # 양단에 극단적인 annual_inc를 drop 합니다
    pdp = pdp.drop(columns=[1764.0, 1500000.0])
    
    
    import plotly.graph_objs as go
    
    surface = go.Surface(
        x=pdp.columns, 
        y=pdp.index, 
        z=pdp.values
    )
    
    
    layout = go.Layout(
        scene=dict(
            xaxis=dict(title=features[0]), 
            yaxis=dict(title=features[1]), 
            zaxis=dict(title=target)
        )
    )
    
    fig = go.Figure(surface, layout)
    fig.show()

    PDP符号化カテゴリ


    エンコーダ(Ordinal Encoder,Target Encoder)を使用すると、学習後にPDPを描画する際に符号化値が現れるため、カテゴリ特性の実際の値を特定するのは難しいので、PDPに符号化する前にカテゴリ値をどのように表示するかを見てみましょう.
    
    import seaborn as sns
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.pipeline import make_pipeline
    
    df = sns.load_dataset('titanic')
    df['age'] = df['age'].fillna(df['age'].median())
    df = df.drop(columns='deck') # NaN 77%
    df = df.dropna()
    
    target = 'survived'
    features = df.columns.drop(['survived', 'alive'])
    
    X = df[features]
    y = df[target]
    
    
    pipe = make_pipeline(
        OrdinalEncoder(), 
        RandomForestClassifier(n_estimators=100, random_state=42, n_jobs=-1)
    )
    pipe.fit(X, y);
    
    
    encoder = pipe.named_steps['ordinalencoder']
    X_encoded = encoder.fit_transform(X)
    rf = pipe.named_steps['randomforestclassifier']
    
    import matplotlib.pyplot as plt
    from pdpbox import pdp
    
    
    feature = 'sex'
    for item in encoder.mapping:
        if item['col'] == feature:
            feature_mapping = item['mapping'] # Series
            
    feature_mapping = feature_mapping[feature_mapping.index.dropna()]
    category_names = feature_mapping.index.tolist()
    category_codes = feature_mapping.values.tolist()
    
    pdp.pdp_plot(pdp_dist, feature)
    
    plt.xticks(category_codes, category_names);
    # 2D PDP
    features = ['sex', 'age']
    
    interaction = pdp_interact(
        model=rf, 
        dataset=X_encoded, 
        model_features=X_encoded.columns, 
        features=features
    )
    
    pdp_interact_plot(interaction, plot_type='grid', feature_names=features);
    # 2D PDP -> Seaborn Heatmap으로 그리기 위해 데이터프레임으로 만듭니다
    pdp = interaction.pdp.pivot_table(
        values='preds', 
        columns=features[0], 
        index=features[1]
    )[::-1]
    
    pdp = pdp.rename(columns=dict(zip(category_codes, category_names)))
    plt.figure(figsize=(6,5))
    sns.heatmap(pdp, annot=True, fmt='.2f', cmap='viridis')
    plt.title('PDP decoded categorical');

    SHAP(SHapley Additive exPlanations)_shapley values




    これは,単一観測値から特性の寄与度(特徴属性)を計算する方法であり,個別観測値の特性と目標変数との関係を理解するための地域的方法論である.
    SHAP値は1つの予測において方向と大きさで変数の影響度を表す.

    各種SHAP plot


    force_plot


    force graphは、shapパッケージが単一モデル予測を可視化する基本的な方法であり、特定のデータ(すなわち、データ全体)のShapley値を1次元平面に整列させることによってこれらのデータを表示する.
    回帰
    
    !pip install shap
    
    row = X_test.iloc[[1]]  # 중첩 brackets을 사용하면 결과물이 DataFrame입니다
    
    # 실제 집값
    y_test.iloc[[1]] # 2번째 데이터를 사용했습니다	# 323000.0
    
    # 모델 예측값
    model.predict(row)	# 341878.50142523
    
    # 모델이 이렇게 예측한 이유를 알기 위하여
    # SHAP Force Plot을 그려보겠습니다.
    
    import shap
    
    explainer = shap.TreeExplainer(model)
    shap_values = explainer.shap_values(row)
    
    shap.initjs()
    shap.force_plot(
        base_value=explainer.expected_value, 
        shap_values=shap_values,
        features=row
    )
  • Red矢印:予測値をより高くする変数の影響(SHAP値)
  • を記述する.
  • Blu-ray矢印:逆に、現在の予測値の低い変数は
  • に影響します.
    各矢印のサイズは
  • です.変数の影響量
  • です.
  • 基準値(左上隅):
  • 出力値:モデルの予測(0.64)、影響が最も大きい変数値を以下に示す.
    =>forceplotは
  • の有効な予測要約を提供します.
    100個のテストサンプルの累積Shapley Value
    # 100개 테스트 샘플에 대해서 각 특성들의 영향을 봅니다. 샘플 수를 너무 크게 잡으면 계산이 오래걸리니 주의하세요.
    shap_values = explainer.shap_values(X_test.iloc[:100])
    shap.initjs()
    shap.force_plot(explainer.expected_value, shap_values, X_test.iloc[:100])

    summary_plot

    shap_values = explainer.shap_values(X_test.iloc[:300])
    shap.summary_plot(shap_values, X_test.iloc[:300])

    violin
    shap.summary_plot(shap_values, X_test.iloc[:300], plot_type="violin")
  • blue:プロパティ値自体が小さい場合は
  • red:属性値が大きい場合は
  • lat:分散性が大きく、陰、陽の影響が大きい;
  • 基岩:影響は大きくありません.
  • 分と表示する部分は異常値
  • である.
    bar
    shap.summary_plot(shap_values, X_test.iloc[:300], plot_type="bar")

    >分類
    row = X_test.iloc[[3160]]
    
    ## UnicodeDecoderError 발생시 xgboost 1.1-> 1.0 다운그레이드 (conda install -c conda-forge xgboost=1.0)
    import xgboost
    import shap
    
    explainer = shap.TreeExplainer(model)
    row_processed = processor.transform(row)
    shap_values = explainer.shap_values(row_processed)
    
    shap.initjs()
    shap.force_plot(
        base_value=explainer.expected_value, 
        shap_values=shap_values, 
        features=row, 
        link='logit' # SHAP value를 확률로 변환해 표시합니다.
    )
    #예측을 SHAP그래프를 통해 설명하는 함수
    def explain(row_number):
        positive_class = 'Fully Paid'
        positive_class_index = 1
    
        # row 값을 변환합니다
        row = X_test.iloc[[row_number]]
        row_processed = processor.transform(row)
    
        # 예측하고 예측확률을 얻습니다 
        pred = model.predict(row_processed)[0]
        pred_proba = model.predict_proba(row_processed)[0, positive_class_index]
        pred_proba *= 100
        if pred != positive_class:
            pred_proba = 100 - pred_proba
    
        # 예측결과와 확률값을 얻습니다
        print(f'이 대출에 대한 예측결과는 {pred} 으로, 확률은 {pred_proba:.0f}% 입니다.')
        
        # SHAP를 추가합니다
        shap_values = explainer.shap_values(row_processed)
    
        # Fully Paid에 대한 top 3 pros, cons를 얻습니다
        feature_names = row.columns
        feature_values = row.values[0]
        shaps = pd.Series(shap_values[0], zip(feature_names, feature_values))
        pros = shaps.sort_values(ascending=False)[:3].index
        cons = shaps.sort_values(ascending=True)[:3].index
    
        # 예측에 가장 영향을 준 top3
        print('\n')
        print('Positive 영향을 가장 많이 주는 3가지 요인 입니다:')
        
        evidence = pros if pred == positive_class else cons
        for i, info in enumerate(evidence, start=1):
            feature_name, feature_value = info
            print(f'{i}. {feature_name} : {feature_value}')
    
        # 예측에 가장 반대적인 영향을 준 요인 top1
        print('\n')
        print('Negative 영향을 가장 많이 주는 3가지 요인 입니다:')
        
        evidence = cons if pred == positive_class else pros
        for i, info in enumerate(evidence, start=1):
            feature_name, feature_value = info
            print(f'{i}. {feature_name} : {feature_value}')
    
        # SHAP
        shap.initjs()
        return shap.force_plot(
            base_value=explainer.expected_value, 
            shap_values=shap_values, 
            features=row, 
            link='logit'	# SHAP value를 확률로 변환해 표시합니다.
        )
        
        
        explain(3160)

    PDP、SHAP、フィーチャーインポートの区別


    すべての関連属性のグローバル説明
  • Feature Importances
  • Drop-Column Importances
  • Permutaton Importances
  • ターゲットに関連する各プロパティのグローバルな説明.
  • Partial Dependence plots
  • 単一観測値の地域性(ローカル)について説明する
  • Shapley Values
  • [reference]
    機械学習解釈力シリーズ第1弾:人工知能と機械学習の必要条件を信頼し、解釈力!
    [Python]SHAP(SHAPley Additive Explanations)Decision plot説明
    解釈可能な区分
    何が大切なの!特性の重要性