【python】高次元の分離境界をなんとか2次元で見る

はじめに

 分類器の特性を把握するために2次元データで分離境界を見るということが行われがちですが、高次元空間における分離器の特性を正確に表している訳ではありません。
 ということがずっと気になっていたので、なんとか高次元空間で分類させて2次元で見る方法を考えます。

方法

 PCAで2次元に落とせれば、線形変換で逆変換もできるので、それでやります。当然ながら情報は落ちますし、2次元でもなんとか見える程度のデータしか扱えませんが、妥協します。
 sklearnならinverse_transformという便利なメソッドがあるので、簡単です。
 というあたりまで考えた上で、こんなコードを書きました。
show_hyperplane.py
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

def show_hyperplane(dataset, clf, filename):
    pca = PCA(n_components=2)
    X = pca.fit_transform(dataset.data)
    plt.scatter(X[:,0], X[:,1], c=dataset.target)

    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.01),
                         np.arange(y_min, y_max, 0.01))

    clf.fit(dataset.data, dataset.target)
    Z = clf.predict(
        pca.inverse_transform(np.c_[xx.ravel(), yy.ravel()]))
    plt.pcolormesh(xx, yy, Z.reshape(xx.shape),
                   alpha=0.03, shading="gouraud")
    plt.savefig(filename)
 汎用的に作ったので、これでいろいろなものを見てみようという算段です。

実験

 まずirisとSVM。
from show_hyperplane import show_hyperplane
from sklearn.datasets import load_iris
from sklearn.svm import SVC

iris = load_iris()
svm = SVC(C=50, gamma="scale")    
show_hyperplane(iris, svm, "iris_svm.png")
iris_svm.png
 特に興味深い知見は得られませんでした。
 次、irisとランダムフォレスト。
from show_hyperplane import show_hyperplane
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier

iris = load_iris()
rfc = RandomForestClassifier(n_estimators=500, n_jobs=-1)    
show_hyperplane(iris, rfc, "iris_rf.png")
iris_rf.png
 ランダムフォレストで斜めの分離超平面の図を出したサイトはここくらいしかないのでは? だからどうしたって話ですが。
 簡単なのでAdaBoostも試します。
from show_hyperplane import show_hyperplane
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier

iris = load_iris()
ada = AdaBoostClassifier(
    base_estimator=DecisionTreeClassifier(max_depth=4),
    n_estimators=200)    
show_hyperplane(iris, ada, "iris_ada.png")
iris_ada.png
 面白いんですが、性能はいまいち悪そう。
 ちなみに、base_estimatorのパラメータでコロコロ結果が変わります。パラメータ設定については、以下の2記事を参照してください。
 ただの決定木もやっておきましょう。
from show_hyperplane import show_hyperplane
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier

iris = load_iris()
dtc = DecisionTreeClassifier()
show_hyperplane(iris, dtc, "iris_tree.png")
iris_tree.png
 つまらない。
 さて、irisは飽きてきたのでdigitsで同じことをやります。こちらは何しろ元が64次元で、2次元に落とすとかなり重なり合うので、カオスな結果になってくれそうです。
 が、その前にshow_hyperplane.pyをいじります。元のままだといろいろうまくいかなかったからです。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA

def show_hyperplane(dataset, clf, filename):
    pca = PCA(n_components=2)
    X = pca.fit_transform(dataset.data)
    plt.scatter(X[:,0], X[:,1], s=5, c=dataset.target)

    x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, 0.3),
                         np.arange(y_min, y_max, 0.3))

    clf.fit(dataset.data, dataset.target)
    Z = clf.predict(
        pca.inverse_transform(np.c_[xx.ravel(), yy.ravel()]))
    plt.pcolormesh(xx, yy, Z.reshape(xx.shape),
                   alpha=0.05, shading="gouraud")
    plt.savefig(filename)
 よし、やろう。
 まずSVM。今回からついでに学習データに対するスコアを見ます。コメントで記します。
from show_hyperplane import show_hyperplane
from sklearn.datasets import load_digits
from sklearn.svm import SVC

digits = load_digits()
svm = SVC(C=0.1, gamma="scale")    
score = svm.fit(
    digits.data, digits.target).score(
        digits.data, digits.target)
print(score) # => 0.9744017807456873
show_hyperplane(digits, svm, "digits_svm.png")
digits_svm.png
 あたりまえですが、64→2次元で情報落ちしているので、こんなふうにしか見えません。それでも、後々出てくるやつに比べればまともな方です。
 次。ランダムフォレスト。
from show_hyperplane import show_hyperplane
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier

digits = load_digits()
rfc = RandomForestClassifier(n_estimators=500, n_jobs=-1)    
score = rfc.fit(
    digits.data, digits.target).score(
        digits.data, digits.target)
print(score) # => 1.0
show_hyperplane(digits, rfc, "digits_rfc.png")
digits_rfc.png
 これ面白いですね。ところどころ凹凸がありますが、それでもぱっと見SVMと同じくらい滑らかな分離超平面に見えます。高次元データほど強いというのもわかる気がします。
 アダブースト。
from show_hyperplane import show_hyperplane
from sklearn.datasets import load_digits
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import AdaBoostClassifier

digits = load_digits()
ada = AdaBoostClassifier(
    base_estimator=DecisionTreeClassifier(max_depth=3),
    n_estimators=200)    
score = ada.fit(
    digits.data, digits.target).score(
        digits.data, digits.target)
print(score) # 0.9660545353366722
show_hyperplane(digits, ada, "digits_ada.png")
digits_ada.png
 大丈夫なんかこれ。決定木のアダブーストはランダムフォレストと比べて個人的にいまいち信頼していないのですが、こういうの見るとその思いが強まります。
 決定木もやりましょう。irisではつまらなかったけど、こちらではどうなるでしょうか。
 
from show_hyperplane import show_hyperplane
from sklearn.datasets import load_digits
from sklearn.tree import DecisionTreeClassifier

digits = load_digits()
dtc = DecisionTreeClassifier()
score = dtc.fit(
    digits.data, digits.target).score(
        digits.data, digits.target)
print(score) # 1.0
show_hyperplane(digits, dtc, "digits_tree.png")
digits_tree.png
 あははー、なにこれカオス。デフォルトのまま高次元で使うな、ということですね。

まとめ

 元が64次元くらいでもだいぶ情報落ちするので、本当の高次元データでも使えるかというと微妙なのですが、それでもなんとなく傾向はつかめますし、面白かったです。
 SVMとランダムフォレストはどっちも優秀ですね。