単一の入力データから、複数の処理方法で幾つもの異なる特徴量が得られる・・・というシチュエーションがある。
この場合、「どれが最善か」という観点でどれか一つを選ぶこともできるけど、そうすると他の特徴量の情報は捨ててしまうことになる。総合的な性能では他に一歩譲るが、有用な情報が含まれている特徴量がある・・・というような場合は、ちょっと困る。
こういう状況で役に立つのが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
ちょっと微妙だけど、誤差ではないみたい。このように比較的手軽に性能を改善できることがわかる(効くかどうかはケースバイケースだけど)。