静かなる名辞

pythonとプログラミングのこと

【python】sklearnのVarianceThresholdを試してみる

はじめに

 VarianceThresholdは名前の通り、分散がしきい値以下の特徴量を捨てます。

sklearn.feature_selection.VarianceThreshold — scikit-learn 0.19.1 documentation

 これといってすごいところはありませんが、気楽に使えそうなので試してみました。

 目次

とりあえず試す

 しきい値の設定でどれだけ特徴量のshapeが減るか見てみました。データは20newsgroupsです。
Pipelineにしてあるのは、あとでこれを使って分類のチューニングをしてみるためです。

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_selection import VarianceThreshold
from sklearn.pipeline import Pipeline

def test_shape():
    news20 = fetch_20newsgroups()
    cv = CountVectorizer(min_df=0.005,
                         max_df=0.5,
                         stop_words="english")
    vth = VarianceThreshold()
    pl = Pipeline([("cv", cv), ("vth", vth)])
    for v in [0.0,0.05,0.1,0.15]:
        pl.set_params(vth__threshold=v)
        print(pl.fit_transform(news20.data).shape)

if __name__ == "__main__":
    test_shape()

 関連記事:


 結果は、

(11314, 3705)
(11314, 1476)
(11314, 859)
(11314, 573)

 なるほど。
 (実際にはいろいろ試してちょうど良いshapeの減り具合になる値を探しています。これを使うならそういう作業が必要になると思います)

分類を試してみる

 これをうまく設定すると、分類精度が上がったりするのでしょうか?

import pandas as pd
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.preprocessing import FunctionTransformer
from sklearn.feature_selection import VarianceThreshold
from sklearn.naive_bayes import GaussianNB
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV

def convert(x):
    return x.toarray()
def test_best_v():
    news20 = fetch_20newsgroups()
    cv = CountVectorizer(min_df=0.005,
                         max_df=0.5,
                         stop_words="english")
    vth = VarianceThreshold()
    sparse_to_dense = FunctionTransformer(func=convert,
                                          accept_sparse=True)
    gnb = GaussianNB()
    pl = Pipeline([("cv", cv),
                   ("vth", vth),
                   ("s2d", sparse_to_dense),
                   ("gnb", gnb)])
    
    params = {"vth__threshold":[0.0,0.05,0.1,0.15]}
    
    clf = GridSearchCV(pl, params, 
                       return_train_score=False,
                       n_jobs=-1)
    clf.fit(news20.data, news20.target)
    cv_result_df = pd.DataFrame(clf.cv_results_)
    df = cv_result_df[["param_vth__threshold", 
                       "mean_score_time", 
                       "mean_test_score"]]
    print(df)

if __name__ == "__main__":
    test_best_v()

 ただ単にナイーブベイズに入れて性能を見ているだけですが、かなり色々なテクニックを使っているコードなので、初見だと読みづらいと思います。

  • FunctionTransformer:

 ナイーブベイズが疎行列を受け付けてくれないので変換している。こんな関数ラムダ式で良いじゃんと思う向きもあるかもしれませんが、GridSearchCVでn_jobs=-1を指定するためにはトップレベル関数として定義してあげる必要があります(中でpickleを使うので)

  • GridSearchCV:

 return_train_score=Falseにすると速くなります。

  • pd.DataFrame

 GridSearchCV.cv_results_はそのままpandas.DataFrameに変換できるとドキュメントに書いてあるので、それを使ってpandasで取り扱っています。

 走らせた結果は、

  param_vth__threshold  mean_score_time  mean_test_score
0                    0         9.646958         0.656178
1                 0.05         4.504278         0.587149
2                  0.1         2.804257         0.512551
3                 0.15         1.909767         0.453244

 改善する訳ではない。CountVectorizerのmin_dfで予めゴミ変数を削っていること、スパースな空間なので分散が低くてもそれはそれで構わず、ナイーブベイズが意外とスパースに強いのも相まって優秀に働いていることが原因でしょう。

 それより注目すべきはmean_score_timeで、今回のデータで変数を削っていくと、しきい値を0.05上げるたびに、0.07ポイントくらいの性能低下と引き換えに半減するような傾向です。性能と時間のトレードオフになったときは、これをいじって調整する手はあるのかも。

まとめ

 微妙といえば微妙だし、データによっては効くのかもしれない気もします。とりあえず確実に速くはなります。性能はあまり重視していないとき、気楽に変数を捨てて速くするのに使えそうです。