静かなる名辞

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



【python】線形な分類器の比較

はじめに

 線形な分類器は癒やし

 やれ、RBFカーネルだ、決定木だ、ニューラルネットだ、深層学習だ、と流行り物に乗っかって、言うことを聞かない非線形な分類器をなんとかねじ伏せている綿牛たちは、きっと心が荒んでいるのでしょう。

 そんな私たちに、線形分類器は癒やしを提供してくれます。

 といってもいろいろありますよね。そこで比較してみることにしました。

実験

 sklearnの線形モデルは本当にたくさんあってどれを選べば良いのかわからないのですが(参考:API Reference — scikit-learn 0.19.2 documentation)、3つに絞りました。

  • 線形判別分析
  • ロジスティック回帰
  • 線形SVM

 「あっ、教科書に載ってる奴だ」というモデルたちですね。

 二次元で適当なデータを分類させて、決定関数を見ます。

 プログラムを以下に示します。以下のページを大いに参考にしました。

Classifier comparison — scikit-learn 0.19.2 documentation

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.linear_model import LogisticRegression 
from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score
from sklearn.datasets import make_blobs, make_moons

def main():
    moons_X, moons_y = make_moons(noise=0.2, random_state=0)
    blobs_X, blobs_y = make_blobs(centers=2, random_state=0)
    blobs2_X, blobs2_y = make_blobs(centers=3, random_state=1)
    blobs2_y[blobs2_y == 2] = 1

    lda = LinearDiscriminantAnalysis()
    logistic = LogisticRegression()
    svm = LinearSVC()

    fig, axes = plt.subplots(nrows=3, ncols=3)
    plt.subplots_adjust(hspace=0.5)
    cm = plt.cm.RdBu
    cm_bright = ListedColormap(['#FF0000', '#0000FF'])
    for i, (X, y) in enumerate([(moons_X, moons_y),
                                (blobs_X, blobs_y),
                                (blobs2_X, blobs2_y)]):

        x_min, x_max = X[:, 0].min()-0.5, X[:, 0].max()+0.5
        y_min, y_max = X[:, 1].min()-0.5, X[:, 1].max()+0.5

        xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.1),
                             np.arange(y_min, y_max, 0.1))

        for j, (cname, clf) in enumerate([("LDA", lda),
                                          ("LogisticRegression", logistic), 
                                          ("LinearSVM", svm)]):
            clf.fit(X, y)
            if hasattr(clf, "decision_function"):
                Z = clf.decision_function(np.c_[xx.ravel(), yy.ravel()])
            else:
                Z = clf.predict_proba(np.c_[xx.ravel(), yy.ravel()])[:, 1]
            Z = Z.reshape(xx.shape)
            axes[i,j].contourf(xx, yy, Z, 20, cmap=cm, alpha=.8)
            axes[i,j].scatter(X[:,0], X[:,1], c=y, s=20,
                              cmap=cm_bright, edgecolors="black")
            
            axes[i,j].set_title(cname)
            
            preds = clf.predict(X)
            axes[i,j].text(xx.max() - .3, yy.min() + .1,
                           "{:.3f}".format(accuracy_score(y, preds)),
                           size=10, horizontalalignment='right')

    plt.savefig("result.png")

if __name__ == "__main__":
    main()

結果

 行がデータ、列が分類機に対応。

 色の濃淡が決定関数の値か出力値のラベルの確率に対応。右下の値は学習に使ったデータでテストして計算した正解率です*1

実験結果のグラフ
実験結果のグラフ

 基本的に変わり映えがしない。そこがいい。

 と言っていては何の考察にもならないので、ざっくりと説明。

 えーっと、まず正解率の大小に大した意味はありません。誤差です。

 最上段の三日月状のデータはもともと線形分離できないようなデータです。どれも似通った決定関数になっています。

 中段は正規分布2つが微妙に重なっているようなデータで、ロジスティック回帰だけ微妙に傾き方が大きい気がします。正解率も(大した意味はないとはいえ)微妙に下がっている。何を思ってこうしたのか。

 最下段は正規分布3つを1つ+2つで2つのクラスに分けたデータです。どれも正解率100%になっていますが、LDAはデータ全体の傾向からそれっぽい決定関数を導いているのに対し、ロジスティック回帰とSVMはそういう配慮はないようです。線形判別分析がよくてロジスティック回帰やSVMが駄目という話ではないのですが、こういうデータに対しては単純な判別分析の方がそれっぽく動くということでしょうか。

まとめ

 この中からどれか選ぶとしたら? という建設的な話を期待した方には申し訳ありませんが、やっぱり「どれを選んでもそんなに変わらない」という結論になる気がします。

 強いて言えば、わかりやすい判別分析か、とりあえずSVM的な線形SVMの二択? ロジスティック回帰は非等分散、非正規分布のデータに対する有効性や、確率モデルとして取り扱えることにどこまで価値を見出すかによって使うか使わないかが決まる気がします。実際的には評価指標の比較をやっても良いと思いますが、正直そこまで劇的な差は出てこないと思う。

*1:交差検証とかはやっていません。この記事は癒やしが目的なので