読者です 読者をやめる 読者になる 読者になる

静かなる名辞

pythonと読書

【python】sklearnの次元削減クラスSelectPercentileとSelectKBestについて

python 機械学習 sklearn

 ネットの巷には「sklearnで次元削減するのでとりあえずPCA使ってみました」という記事が溢れかえっているのだが、PCAは基本的に線形な世界でしか動かないので、扱うデータによっては「PCAかけてから分類器に入れたら精度がガタっと落ちました」みたいなことが起こり得る。こういう問題に対してはカーネルPCAという手法があり、SVMよろしくカーネル法を使って上手いこと計算してくれるらしい。また、非線形データをなんとかするアルゴリズムは他にもあるようだ。だけど、そんなややこしいもの使いたくないよね。よくわからないし。
 そんな駄目人間でも使える次元削減アルゴリズム(厳密には特徴選択と言うべきなんだけど)が、sklearnの提供するSelectPercentileとSelectKBestである。
 両者は共に、「次元ごとの効き目(分類するときどれくらい有益な情報を持っているか)」を算出し、効き目の高い次元を残すという動作をする。PCAみたいに次元を混ぜ合わせたりは特にしない(要するに分類に効く情報が入った次元とノイズしかない次元があるとき、前者だけを取り出す)。あと、基本的には是が非でも分類に使える情報を取ってくる奴なんで、教師なしでデータをクラスタリングして可視化したいみたいな状況で使うのは考え直した方が良いと思う。この辺の細かいところは公式ドキュメントを読んでください。

 使い方は超簡単。
 まずSelectPercentileの場合。

from sklearn.feature_selection import SelectPercentile

...

sp = SelectPercentile(k=50) #こう書くと次元数が半分になる
vectors = sp.fit_transform(vectors, labels) 

 次にSelectKBest。こっちも大して変わらない。

from sklearn.feature_selection import SelectKBest

...

skb = SelectKBest(k=250) #こう書くと上位250次元を取ってくる
vectors = skb.fit_transform(vectors, labels)

 特に頭を使う要素がなくて助かってます。

 ただ、実際にこのまま使うことはちょっとできない。この手法は正解ラベルを必要とする。要するに、データをテストデータと訓練データに分けてクロスバリデーションやるんだとか言ってるときに、特徴抽出の段階でデータ全体の正解ラベルを持ってきて貼り付けて次元削減に使っちゃうと、だいぶセコいんじゃね? と思うのである。

 なのでこういうときは、こういうもの(↓)を書いて、

class SelectClassifier:
    """
    欲しいメソッドとかあったら自分で追加してね!
    """
    def __init__(self, clf, selector="p", k=10):
        self.clf = clf
        if selector == "p":
            self.selector = SelectPercentile(k=k)
        elif selector == "kb":
            self.selector = SelectKBest(k=k)
        else:
            sys.exit("aho")
        self.k = k

    def fit(self, vectors, labels):
        if vectors.shape[1] > self.k:
            self.selector.fit(vectors, labels)
            vectors = self.selector.transform(vectors)
        self.clf.fit(vectors, labels)

    def predict(self, vectors):
        if vectors.shape[1] > self.k:
            vectors = self.selector.transform(vectors)
        return self.clf.predict(vectors)

 そしてこうやって使ってやる。

clf = SVC() #たとえば
clf = SelectClassifier(clf, "p", k=10)

 訓練データの正解ラベルを使う分には、文句を言われる筋合いはないはず。
 え、なんでわざわざベクトルの次元数がkの値より大きいことを確認する処理なんか書くのって? SelectKBestは指定したkより小さい次元数のベクトルを渡すと例外吐いて止まるんである。そんなの素通りさせてくれれば良いでしょ、と100回思ったけど、どうにもならないので愚直に書いた。この処理だとSelectPercentileで100次元以下のデータを扱うとき、次元数とkの値の兼ね合いで次元削減が実行されない可能性がある。けど、それまで真面目に対応する気力はなかった。

 これの使い所としては、たとえば「ン万次元あるけど、これ大体ノイズだよね」みたいなときに使えば良いでしょう。自然言語処理のBoWなんかを相手にするときは良さそうですね。PCAみたいに軸を混ぜ合わせる訳ではないので、「高次元のデータを低次元で上手く表現する」用途には使えないです。 

参考文献

sklearn.feature_selection.SelectPercentile — scikit-learn 0.18.1 documentation
sklearn.feature_selection.SelectKBest — scikit-learn 0.18.1 documentation
 具体的にどういう処理をして有用な次元/そうでない次元を決めているのかという話も載っています。