特徴量選択　Null ImportanceとPermutation Importance

#はじめに
様々な特徴量を用いて、学習させた際度の特徴量が重要なのか、どの特徴量が重要でないのかわからないと、さらなるスコアアップのための改善ができないですよね。

この記事では、「Null Importance」「Permutation Importance」を用いてノイズを含む特徴量を見つけ出す方法を紹介していこうと思います。


# Null importance
Null Importanceの大まかな手順としてはこちら
１．RandomForestやXGBoost、LightGBMなどのfeature_importance関数を用いて特徴量重要度を出す

２、目的変数をシャッフルして、再び学習させfeature_importanceを出す
(あくまでランダムにシャッフルしているだけなので、信用性を増すためには複数回行う)

３、シャッフルして出した重要度（以下シャッフル重要度）とシャッフルなしで出した重要度（以下リアル重要度）を比較

では、コードで確認していきましょう



````python:null.py
# 必要なモジュールをインポート
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier


# データの取得
# sklearnのデータセットload_breast_cancerについて軽く解説
# 乳房塊の微細針吸引物（FNA）のデジタル画像中に存在する細胞核の特徴を捉えたものを特徴量として置き。その患者が陰性か陽性かを判定するというもの

cancer = load_breast_cancer()
X = pd.DataFrame(cancer.data, columns=cancer.feature_names)
y = cancer.target


#特徴量とその重要度を含むDFを作成
def get_feature_importances(X, y, shuffle=False):
    # 必要ならば目的変数をシャッフル
    if shuffle:
        y = np.random.permutation(y)

    # モデルの学習
    clf = RandomForestClassifier(random_state=42)
    clf.fit(X, y)

    # 特徴量の重要度を含むデータフレームを作成
    imp_df = pd.DataFrame()
    imp_df["feature"] = X.columns
    imp_df["importance"] = clf.feature_importances_
    return imp_df.sort_values("importance", ascending=False)

# 実際の目的変数でモデルを学習し、特徴量の重要度を含むデータフレームを作成
actual_imp_df = get_feature_importances(X, y, shuffle=False)

# 目的変数をシャッフルした状態でモデルを学習し、特徴量の重要度を含むデータフレームを作成
#シャッフル重要度の母数を増やすためにN_RUNS回学習を行う
N_RUNS = 100
null_imp_df = pd.DataFrame()
for i in range(N_RUNS):
    imp_df = get_feature_importances(X, y, shuffle=True)
    imp_df["run"] = i + 1
    null_imp_df = pd.concat([null_imp_df, imp_df])

#重要度を可視化する関数
def display_distributions(actual_imp_df, null_imp_df, feature):
    # ある特徴量に対する重要度を取得
    actual_imp = actual_imp_df.query(f"feature == '{feature}'")["importance"].mean()
    null_imp = null_imp_df.query(f"feature == '{feature}'")["importance"]

    # 可視化
    fig, ax = plt.subplots(1, 1, figsize=(6, 4))
    a = ax.hist(null_imp, label="Null importances")
    ax.vlines(x=actual_imp, ymin=0, ymax=np.max(a[0]), color='r', linewidth=10, label='Real Target')
    ax.legend(loc="upper right")
    ax.set_title(f"Importance of {feature.upper()}", fontweight='bold')
    plt.xlabel(f"Null Importance Distribution for {feature.upper()}")
    plt.ylabel("Importance")
    plt.show()



# 実データにおいて特徴量の重要度が高かった上位5位を表示
for feature in actual_imp_df["feature"][:5]:
    display_distributions(actual_imp_df, null_imp_df, feature)

````

可視化した結果がこちら

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/cbf90f3b-f66b-3d51-4354-17e561548fe7.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/942d573d-4730-c4b2-6084-45fce594bb09.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/663e05cb-0bea-3a44-afa7-ff533d9f691b.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/2d2967da-ec9d-1b99-c037-0e883d9c3508.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/4f840adf-fa62-864c-d84b-84d3ddeddf0e.png)

特徴量にノイズがない場合には
・上のグラフのように、シャッフル重要度とリアル重要度が分離

逆にノイズがある場合には
・青色のシャッフル重要度のヒストグラムの中に赤いバーが重なる

というように一目でわかります


それでは閾値（リアル重要度より低いシャッフル重要度がN_RUNの何％か）を設定して閾値を超える特徴量のみ取り出していきましょう


````python:null.py

# 閾値を設定
THRESHOLD = 80

# 閾値を超える特徴量を取得
imp_features = []
for feature in actual_imp_df["feature"]:
    actual_value = actual_imp_df.query(f"feature=='{feature}'")["importance"].values
    null_value = null_imp_df.query(f"feature=='{feature}'")["importance"].values
    percentage = (null_value < actual_value).sum() / null_value.size * 100
    if percentage >= THRESHOLD:
        imp_features.append(feature)

imp_features
# ['worst concave points', 'worst perimeter', 'worst radius', 'mean perimeter', 'worst concavity', 'mean radius', 'mean concavity', 'mean concave points', 'area error', 'worst compactness']

````
こうやって閾値を設定してやればノイズの影響が少ない重要度を抽出することができます。



# Permutation Importance

[参考記事](https://qiita.com/kenmatsu4/items/c49059f78c2b6fed0929)


手順としてはこう
１．普通に学習、予測、をして評価指標に通しそのスコア（以下リアルスコア）を保存
２．特徴量を一つだけシャッフルして、学習、予測させ、評価指標に通し、そのスコア（以下シャッフルスコア）を保存
３．リアルスコアとシャッフルスコアの差や割合を取って、判断材料とする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/13fe6a34-4c24-91e9-e468-beb8e828d50c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/9d75d3b4-9773-e7a5-5525-5923c694d4dc.png)


４．1,2,3をすべての特徴量で繰り返し行い比較
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/6fc77e5c-69f1-4bd5-b862-d2f469e42027.png)


悪化具合が大きいものほど、重要度が高い特徴量、少ないものはノイズを含む、または重要度が低い特徴量ということがわかります






## permutation importanceを利用する際の注意点
多重共線性、つまり相間の高い特徴量を同時にモデルに投入している際にはどちらかの特徴量があまり評価されないという問題が起こります。

例えば、ある商品の値段をそれぞれ米ドルと日本円で表した二つの特徴量があったとします。この二つは、非常に似た振る舞いをするので、前者をランダム化したとしても後者の特徴量によって前者から学習すべき内容がほとんど学習できてしますため、精度があまり下がりません。精度が下がらないので、その得著量がターゲットに対して高い説明力を持っていたとしても、重要度が低いと計算されてしまいます。

[多重共線性について](https://xica.net/vno4ul5p/)


permutation　importanceは原理さえりかいしてれば自分で好きなように作ることができますが、eli5というライブラリを使って少ないコード量で簡単に特徴量重要度を見ることができる方法を見つけたので、ここではその方法を紹介していこうと思います。（sklearnでもpermutation importance関数があるらしいので、そちらも調べてみるといいかもしれません）

````sample.py
from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import load_boston
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_spli
from sklearn.datasets import load_boston
#ボストンの住宅価格予想データを使う
boston = load_boston()
X = pd.DataFrame(boston.data, columns=boston.feature_names)
y = boston.target


X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=100)

model=RandomForestRegressor()
model.fit(X_train,y_train)

#おそらくデフォルトでははいっていないので、インストールしましょう
import eli5
from eli5.sklearn import PermutationImportance

perm = PermutationImportance(model, random_state=1).fit(X_test,y_test)
eli5.show_weights(perm, feature_names = X_test.columns.tolist())



````

と、こんな感じで、実行すると
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/468909/45985646-45e9-bbf7-b33b-fd59fbd99e63.png)




このような表が返ってきます。上から順に重要な特徴量を示しています。（±の後ろの数字は誤差の範囲をあらわしています）
AGE、CHAS、ZN、INDUSはWeightがマイナスになる可能性があります。これはその特徴量をシャッフルして学習させて際に、逆にスコアが上昇する可能性がある、ということを示しているので、ノイズである可能性が高い、ということがわかります。


#まとめ
どちらの手法も、特徴量選択において有用そうだけど、計算量や信憑性を考えると、permutation importanceの方が使いやすそうだと思いました（ライブラリもあるし）

まだ、feature_importances_関数についてどういうものなのか、よくわかっていないので、またそこら辺の知識を入れなおしてから、最適な特徴量選択法について考えていきたいと思います。



# 参考記事
[Feature Selection with Null Importances](https://www.kaggle.com/ogrellier/feature-selection-with-null-importances)
[Null importances による特徴量選択](https://qiita.com/daikon/items/9b4e398efb7628edea5f)
[eli5 documentation](https://eli5.readthedocs.io/en/latest/blackbox/permutation_importance.html)
[Permutation Importanceを使ってモデルがどの特徴量から学習したかを定量化する](https://www.datarobot.com/jp/blog/permutation-importance/)
[Python: 特徴量の重要度を Permutation Importance で計測する](https://blog.amedama.jp/entry/permutation-importance)