はじめに
分類器の特性を把握するために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とランダムフォレスト。
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")
ランダムフォレストで斜めの分離超平面の図を出したサイトはここくらいしかないのでは? だからどうしたって話ですが。
簡単なので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")
面白いんですが、性能はいまいち悪そう。
ちなみに、base_estimatorのパラメータでコロコロ結果が変わります。パラメータ設定については、以下の2記事を参照してください。
【python】sklearnのAdaBoostをデフォルトパラメータで使ってはいけない - 静かなる名辞
AdaBoostとRandomForestの比較 - 静かなる名辞
ただの決定木もやっておきましょう。
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は飽きてきたので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")
あたりまえですが、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")
これ面白いですね。ところどころ凹凸がありますが、それでもぱっと見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")
大丈夫なんかこれ。決定木のアダブーストはランダムフォレストと比べて個人的にいまいち信頼していないのですが、こういうの見るとその思いが強まります。
決定木もやりましょう。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")
あははー、なにこれカオス。デフォルトのまま高次元で使うな、ということですね。
まとめ
元が64次元くらいでもだいぶ情報落ちするので、本当の高次元データでも使えるかというと微妙なのですが、それでもなんとなく傾向はつかめますし、面白かったです。
SVMとランダムフォレストはどっちも優秀ですね。