Python3による日本語言語処理 (5)VotingClassifierによる異なるモデルのアンサンブル学習

##アンサンブル学習とは

自然言語処理での「感情分析」など、与えられた文章をいくつかのクラスに分類するための精度が高めの分類器が複数ある時にアンサンブル学習でこれらを組み合わせることでより強力なモデルを作れることもあります。

> アンサンブル学習
ランダムに解を出力する予測器，すなわち，予測精度が最悪の予測器よりは，高い精度で予測できる弱学習器 (weak learner)を組み合わせて高精度の学習器を構成する方法． バギングやブースティングといった手法が著名．（[朱鷺の杜Wiki アンサンブル学習](http://ibisforest.org/index.php?%E3%82%A2%E3%83%B3%E3%82%B5%E3%83%B3%E3%83%96%E3%83%AB%E5%AD%A6%E7%BF%92)）

現実で例えるなら、専門家一人に政策についてアドバイスをもらうよりも、複数人の専門家が話し合った方がいい政策提言ができるかもしれない、っていう感じです。ざっくり考えるなら、ここでいう専門家が学習器（ランダムフォレストやサポートベクターマシーン）で、複数の学習器から得られた結果（予測値）を組み合わせることがアンサンブル学習ということになります。
ちなみにRandomForestはそれ自体がアンサンブル学習器と呼ばれるのは、RandomForestが複数の決定木のだした結果から多数決的に予測値を求めているためです。
今回は概念的に異なる精度の高い複数のモデルをサクッと組み合わせられるVotingClassifierを調べてみます。

## VotingClassifierとは
scikit-learnのv0.17で実装されたsklearn.ensemble内のクラス。
> The idea behind the voting classifier implementation is to combine conceptually different machine learning classifiers and use a majority vote or the average predicted probabilities (soft vote) to predict the class labels. Such a classifier can be useful for a set of equally well performing model in order to balance out their individual weaknesses.

VotingClassifierは既にある程度高い精度を持つ異なるタイプのの学習器（たとえばRandomForest、LogisticRegressionとGausianNB(ナイーブベース分類器)など）のだした結果を多数決もしくは確率平均で決めるというもの。概念自体はとてもシンプルですが使うのも簡単で意外と強力です。

### Hard Vote 
複数のモデルで予測を行った時に予測されたラベルのうち、多数決となったものをそのまま採用する方法。
あるインプットXに対して、三つの学習器がそれぞれ次のように「これは1」「これは2」と異なる決定をしたらここでは多数派の「1」が取られ、X->1に分類されます。
学習器 1 -> class 1
学習器 2 -> class 1
学習器 3 -> class 2

### Weak Vote 
これはそれぞれの学習器があるクラスであると予測した確率に対してウェイトをかけ、その和を足すことで得られる確率平均が最も高くなったラベルが採択されます。詳しくは公式の例を参照してください。
[1.11.5.2. Weighted Average Probabilities (Soft Voting)](http://scikit-learn.org/stable/modules/ensemble.html#weighted-average-probabilities-soft-voting)

###VotingClassifierで注意すること
注意しなくてはいけないのは**equally well performing model**というのが落とし穴で、ここでうまく動かないモデルが混じったりするとせっかくvotingを行っても結果は向上しないこともあります。
[Why is my VotingClassifier accuracy less than my individual classifier?](http://stackoverflow.com/questions/39720836/why-is-my-votingclassifier-accuracy-less-than-my-individual-classifier)

##実際に使ってみる
公式ドキュメントにもあるように、とりあえずirisデータセットに対してVotingClassifierを使ってhard voteを行っている。

```voting_classifier.py
from sklearn import datasets
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import VotingClassifier

def executre_voting_classifier():
    #irisデータセットを読み込む
    iris = datasets.load_iris()
    X = iris.data[:, [0,2]]
    y = iris.target

    #分類器をセット。ここではロジスティック回帰、ランダムフォレスト分類器、ガウシアンナイーブベースを用いる。
    clf1 = LogisticRegression(random_state=1)
    clf2 = RandomForestClassifier(random_state=1)
    clf3 = GaussianNB()

    #アンサンブル学習器を作成。voting='hard'に設定し、単純な多数決で値を決めることにする。
    eclf = VotingClassifier(estimators=[('lr', clf1), ('rf', clf2), ('gnb', clf3)], voting='hard')

    for clf, label in zip([clf1, clf2, clf3, eclf], ['Logistic Regression', 'Random Forest', 'naive Bayes', 'Ensemble']):
        scores = cross_val_score(clf, X, y, cv=5, scoring='accuracy')
        print("Accuracy: %0.2f (+/- %0.2f) [%s]" % (scores.mean(), scores.std(), label))

    出力結果:
    Accuracy: 0.92 (+/- 0.03) [Logistic Regression]
    Accuracy: 0.91 (+/- 0.05) [Random Forest]
    Accuracy: 0.91 (+/- 0.06) [naive Bayes]
    Accuracy: 0.93 (+/- 0.06) [Ensemble]

```
確かに（微妙に）向上しています。
複数のパラメータを使ったモデルを組み合わせたり、これらのパラメータに対してGridSearchを行うことも簡単にできます。

```ensemble.py
clf1 = SVC(kernel='rbf', random_state=0, gamma=0.3, C=5 ,class_weight='balanced')
clf2 = LogisticRegression(C=5, random_state=0, class_weight='balanced')
clf3 = RandomForestClassifier(criterion='entropy', n_estimators=250, random_state = 1, max_depth = 20, n_jobs=2, class_weight='balanced')

eclf = VotingClassifier(estimators=[('svm', clf1), ('lr', clf2), ('rfc', clf3)], voting='hard')
    eclf.fit(X_train, y_train)
```

こんな感じで「組み合わせたらいい感じになりそうなモデルがいくつかあるんだけどなんとか簡単に組み合わせる方法ないかな」って時に有効なようです。