静かなる名辞

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


【python】複数の特徴をまとめるFeatureUnion

 単一の入力データから、複数の処理方法で幾つもの異なる特徴量が得られる・・・というシチュエーションがある。

 この場合、「どれが最善か」という観点でどれか一つを選ぶこともできるけど、そうすると他の特徴量の情報は捨ててしまうことになる。総合的な性能では他に一歩譲るが、有用な情報が含まれている特徴量がある・・・というような場合は、ちょっと困る。

 こういう状況で役に立つのがFeatureUnion。特徴抽出や次元削減などのモデルを複数まとめることができる。

 結果はConcatenateされる。Concatenateというのがわかりづらい人もいると思うけど、たとえば手法1で10次元、手法2で20次元の特徴量ベクトルが得られたら、これをそのまま横に繋げて30次元のベクトルとして扱うということ。

sklearn.pipeline.FeatureUnion — scikit-learn 0.20.1 documentation

 ちなみに、こいつはsklearn.pipeline以下に存在する。Pipelineの兄弟みたいな扱い。引数の渡し方とかもほとんど同じである。

 簡単に試してみよう。digitsの分類を行うことにする。PCA+GaussianNB, LDA+GNB, FeatureUnion(PCA, LDA)+GNBの3パターンでスコアを見比べる。

import warnings
warnings.filterwarnings('ignore')

from sklearn.datasets import load_digits
from sklearn.decomposition import PCA
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
from sklearn.naive_bayes import GaussianNB
from sklearn.pipeline import Pipeline, FeatureUnion
from sklearn.model_selection import cross_validate, StratifiedKFold

def main():
    digits = load_digits()
    
    pca = PCA(n_components=30)
    lda = LDA()
    gnb = GaussianNB()
    
    pca_gnb = Pipeline([("pca", pca), ("gnb", gnb)])
    lda_gnb = Pipeline([("lda", lda), ("gnb", gnb)])
    pca_lda_gnb = Pipeline([("reduction", FeatureUnion([("pca", pca),
                                                        ("lda", lda)])),
                            ("gnb", gnb)])

    scoring = {"p": "precision_macro",
               "r": "recall_macro",
               "f":"f1_macro"}

    for name, model in zip(["pca_gnb", "lda_gnb", "pca_lda_gnb"], 
                           [pca_gnb, lda_gnb, pca_lda_gnb]):

        skf = StratifiedKFold(shuffle=True, random_state=0)
        scores = cross_validate(model, digits.data, digits.target,
                                cv=skf, scoring=scoring)
        
        p = scores["test_p"].mean()
        r = scores["test_r"].mean()
        f = scores["test_f"].mean()
        print(name)
        print("precision:{0:.3f} recall:{1:.3f} f1:{2:.3f}".format(p,r,f))

if __name__ == "__main__":
    main()

 結果は、

pca_gnb
precision:0.947 recall:0.944 f1:0.945
lda_gnb
precision:0.955 recall:0.953 f1:0.953
pca_lda_gnb
precision:0.959 recall:0.957 f1:0.957

 ちょっと微妙だけど、誤差ではないみたい。このように比較的手軽に性能を改善できることがわかる(効くかどうかはケースバイケースだけど)。