1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

特徴量削減が精度向上に寄与することがあるのか実際に検証してみた

Posted at

twitterで,精度向上のために特徴量削減をするのは意味がない,という話を聞いたので実際にどうなのかトイデータで試してみる

問題設定

データはsklearnのdatasetsで適当な人工データを作成する.
モデルとしてはsklearnのMLPClassifierを使用する.
特徴量削減のためのimportanceにはpermutation importanceを使用する.
精度の評価にはaucを使用する.
seedを変えながら複数回実験を行い,全特徴量を使用したケースと特徴量を削減したケースでどちらのaucが高いことが多いのかを調べる.
結果に影響を与えるのか調べるパラメータは,重要な特徴量数(n_informative)と削減後の特徴量数(n_use_feat)とする.
特徴量の数は20で固定する.

## データセット
x, y = datasets.make_classification(n_samples=1000,
                                    n_features=20,
                                    n_informative=n_informative,
                                    shuffle=True)

結果

全特徴量を使用した方が精度が良い場合もあれば,特徴量削減をした方が精度が良い場合もあった.

image.png
表の中の値は,全特徴量を使用した際のaucが特徴量削減をした際のaucに勝った割合.
0.5なら同点, 0.5よりも大きければ全特徴量の勝ち, 0.5より小さければ特徴量選択の勝ち.
n_informativeが大きい場合,n_use_featを減らすと精度が悪化しやすいが,
n_informativeが小さい場合には使用する特徴量数を削減した方が良い,という結果になった.

結論

特徴量削減が精度向上に寄与するケースが存在することを示せた. 
以下は,あくまで今回の実験設定での今回の実験範囲内での結果です. 鵜呑みにしないでください.
有益な特徴量が75%以上(n_informative=15)の場合は特徴量選択しなくていい. 削減すると有用な特徴量を削ってしまうリスクが上がるのかも.
有益な特徴量が40%を切った場合には特徴量選択した方が良さげ.

雑感

(優秀な人は有用な特徴量しか作らないので)精度向上のために特徴量削減をするのは意味がない,ということなのかなと思いました.

今回はmlpで実験を行いましたが,gbdtだとゴミ特徴量を完全に無視できることからmlpよりもノイズに強いと思うので同じような結果にはならないかもしれません.
(せっかくなのでtwitterでの意見と反対の結果が出やすいようにしようと思ってgbdtよりもノイズに弱そうなMLPを使用しました)

実験に使用したコード

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import KFold
from sklearn.metrics import roc_auc_score
from sklearn import datasets
from tqdm.auto import tqdm
import matplotlib.pyplot as plt

import warnings
warnings.filterwarnings('ignore')

def create(n_informative=2):
    x, y = datasets.make_classification(n_samples=1000,
                                        n_features=20,
                                        n_informative=n_informative,
                                        shuffle=True)
    x_train, y_train = x[::2], y[::2]
    x_test, y_test = x[1::2], y[1::2]
    return x_train, y_train, x_test, y_test

def experiment(
        seed = 0,
        n_informative = 2,
    ):
    np.random.seed(seed)
    
    ## 2値分類データセット
    x_train, y_train, x_test, y_test = create(n_informative)
    kf = KFold(5, shuffle = True)

    ## 5 foldのcross validationを行って permutation importanceを計算する
    n_permutation_importance_rand = 3 ## permutation importanceの計算に使用するpermutationの回数
    permutation_importance_records = []
    for tr_idx, va_idx in kf.split(x_train):
        model = MLPClassifier(x_train.shape[-1])
        model.fit(x_train[tr_idx], y_train[tr_idx])
        pred = model.predict_proba(x_train[va_idx])[:,1]
        base_auc = roc_auc_score(y_train[va_idx], pred) ## 普通に推論した時のvalidationのauc

        ## permutation importanceの計算
        for i in range(x_train.shape[1]):
            x_train_copy = x_train.copy()
            for k in range(n_permutation_importance_rand):
                x_train_copy[:,i] = np.random.permutation(x_train_copy[:,i])
                pred = model.predict_proba(x_train_copy[va_idx])[:,1]
                auc = roc_auc_score(y_train[va_idx], pred)
                diff = auc - base_auc ## importance
                permutation_importance_records.append([i, diff])

    ## permutation importanceの計算結果を統合して特徴量をdropする
    pi_df = pd.DataFrame(permutation_importance_records, columns = ["col_idx", "diff"])
    pi_df_agg = pi_df.groupby("col_idx")["diff"].mean()
    pi_sort = pi_df_agg.sort_values()
#     print("dropped : ", pi_sort[-n_drop:].index)

    ## 全特徴量を使用して精度を評価
    model = MLPClassifier(x_train.shape[-1])
    model.fit(x_train, y_train)
    pred = model.predict_proba(x_test)[:,1]
    base_auc = roc_auc_score(y_test, pred)

    ## permutation importanceの上位の特徴量を使用して精度を評価
    drop_aucs = {}
    for n_use in [5, 10, 13, 15, 17]:
        use_columns = pi_sort[:n_use].index
        model = MLPClassifier(len(use_columns))
        model.fit(x_train[:,use_columns], y_train)
        pred = model.predict_proba(x_test[:,use_columns])[:,1]
        drop_auc = roc_auc_score(y_test, pred)
        drop_aucs[n_use] = drop_auc

    return base_auc, drop_aucs

## seedを変えながら実験を回して, 全特徴量を使用した場合のauc と importanceで選択した特徴量だけを使用した場合のauc を計算する
recs = []
for n_informative in [2, 5, 8, 10, 15]: ## 有用な特徴量数. 全体の特徴量数は20で固定して実験を行う
    base_auc_rec = []
    drop_aucs_rec = []
    for seed in tqdm(range(42)): ## seedを変えながら実験する
        base_auc, drop_aucs = experiment(seed, n_informative)
        base_auc_rec.append(base_auc)
        drop_aucs_rec.append(drop_aucs)

    ## 実験結果の統合
    drop_aucs_df = pd.DataFrame(drop_aucs_rec)
    for c in drop_aucs_df.columns:
        win_rate = np.mean((np.array(base_auc_rec) - drop_aucs_df[c]) > 0)
        recs.append({"n_informative" : n_informative, "n_use_feat" : c, "auc_win_rate" : win_rate})

rec_df = pd.DataFrame(recs)
display(rec_df.pivot_table(index = "n_informative", columns = "n_use_feat", values = "auc_win_rate"))
1
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?