静かなる名辞

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


【python】sklearnのVotingClassifierを試す

 複数の分類器に分類を行わせ、その結果を平均した結果を得ればより正しい結果が得られるだろう・・・ということらしい。

sklearn.ensemble.VotingClassifier — scikit-learn 0.20.1 documentation

 先に結論を書いておくと、何種類かの分類器を入れてsklearnのdigitsデータで素直に試したところ、パラメタを適当に設定したSVMに勝てなかった。理由としては、

  • SVMで素直に分類すれば0.97とかのf1値が得られるデータだった
  • こうなるとかえって悪い奴が足を引っ張る

 これに尽きる。かといって効果なしというのも悲しいので、無理矢理効果が見られるようなシチュエーションを作ることにした。

 基本的には汎化性能に効いてくるはずなので、汎化性能が必要なようにデータを改造する。

digits = load_digits()
noised_data = digits.data + np.random.random(digits.data.shape)*15

 データ全体にノイズをまぶし、

X_train, X_test, y_train, y_test = train_test_split(
    noised_data, digits.target, test_size=0.8)

 全データの8割をテストに使う。2割しか学習に回されないので、少ないデータから良さげな分離平面を引くチカラが要求されるという訳。

 そしていろいろな分類器を突っ込んで実験する。

# coding: UTF-8

import numpy as np

from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.naive_bayes import GaussianNB as GNB
from sklearn.ensemble import RandomForestClassifier as RFC
from sklearn.ensemble import BaggingClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import VotingClassifier

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_recall_fscore_support


def main():
    digits = load_digits()
    noised_data = digits.data + np.random.random(digits.data.shape)*15

    X_train, X_test, y_train, y_test = train_test_split(
        noised_data, digits.target, test_size=0.8)

    svm =SVC(C=5, gamma=0.001, probability=True)
    lr = LogisticRegression()
    knn = KNN(n_jobs=-1)
    nb = GNB()
    rfc = RFC(n_estimators=500, n_jobs=-1)
    bgg = BaggingClassifier(n_estimators=300, n_jobs=-1)
    mlp = MLPClassifier(hidden_layer_sizes=(40, 20), max_iter=1000)

    estimators = list(zip(["svm","lr","knn","nb","rfc","bgg","mlp"],
                          [svm,lr,knn,nb,rfc,bgg,mlp]))

    for name, clf in estimators:
        clf.fit(X_train, y_train)
        preds = clf.predict(X_test)
        print(name)
        print("p:{0:.4f} r:{1:.4f} f1:{2:.4f}".format(
            *precision_recall_fscore_support(y_test, preds, average="macro")))

    for v in ["hard", "soft"]:
        vc_hard = VotingClassifier(estimators, voting=v)
        vc_hard.fit(X_train, y_train)
        preds = vc_hard.predict(X_test)
        print(v, "voting")
        print("p:{0:.4f} r:{1:.4f} f1:{2:.4f}".format(
            *precision_recall_fscore_support(y_test, preds, average="macro")))

if __name__ == "__main__":
    main()

 突っ込んだ分類器は、

  • svm

 soft voting(各分類器の推定確率の平均を取る)を使いたいときはprobability=Trueを設定すること

  • ロジスティック回帰
  • k近傍
  • ナイーブベイズ
  • ランダムフォレスト
  • バギング
  • 多層パーセプトロン

 の7種類。デフォルトのパラメタだと悲惨な結果になる奴もいるので、そういう奴には適当なパラメタを与えた。

 そして結果は、

svm
p:0.8709 r:0.8479 f1:0.8348
lr
p:0.7532 r:0.7580 f1:0.7542
knn
p:0.8440 r:0.8459 f1:0.8399
nb
p:0.8493 r:0.8460 f1:0.8428
rfc
p:0.8443 r:0.8354 f1:0.8235
bgg
p:0.7971 r:0.7943 f1:0.7879
mlp
p:0.6769 r:0.6712 f1:0.6668
hard voting
p:0.8649 r:0.8588 f1:0.8505
soft voting
p:0.8704 r:0.8718 f1:0.8675

 うまい具合に効いた、というかこの結果を得るまでに苦労があった。

 まぶすノイズが少ないとSVMやRandomForestが有利だが、まぶすノイズの量を増やすとナイーブベイズが相対的に際立ってくる*1とか新しい知見も得られたが、そのせいでノイズをまぶしすぎるとvoting classifierのスコアがナイーブベイズに負けるとか、色々あった。

 ノイズ量と学習に回すデータ数を調整して、なんとかvoting classifierが勝つように調整したというのが実情。実データに対する実用性はあるのか? けっこう微妙かもしれない・・・

*1:正規分布を仮定した確率モデルなのでそうなる理由はなんとなくわかるような気はする