静かなる名辞

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


決定木回帰、ランダムフォレスト回帰、SVRを可視化してみた

はじめに

 最近回帰モデルで遊んでいるのですが、決定木系の回帰に好印象が持てなくなりました。

 だって、決定木ってオーバーフィット番長ですよ? 回帰とは名ばかりのカクカクの回帰曲線が出てくることは目に見えています。

 「そんなあなたのためにランダムフォレスト」という、アンサンブル番長な手法もありますが、以前分類で検討した感じだと、木の本数を相当増やしてもSVMなどで得られるなめらかな分離境界とは違う結果になる・・・という結論を得ているので、あまり信頼できません。

www.haya-programming.com

 ぶっちゃけた話、決定木はデータの可視化くらいにしか使えないし、ランダムフォレストは高次元・スパース・高ノイズ・非線形みたいな条件の悪いときには健闘するものの、低次元で普通のタスクを解くにはちょっと・・・な面がある、という認識、というか印象です。

 じゃあ、実際はどうなんでしょう?

 ということで、定番のサイン波の回帰で決定木回帰、ランダムフォレスト回帰、SVRを可視化します。

 目次


スポンサーリンク


実験

 それぞれで回帰して、プロットします。ついでに、決定係数 R^2も計算して表示します。

  y=\sin(x)\ (0\leq x \leq 15)で、学習データのサンプル数は50個(等間隔)、標準偏差0.3の正規分布をまぶしております。テストデータは同じxの区間で500点、真値としてはノイズなしの純正正弦波を持ってきます。

 後の細かいことはコードと結果を見て理解してください。

 コード

import numpy as np
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.svm import SVR

def test_reg(reg, name, X_train, y_train, X_test, y_test, ax):
    reg.fit(X_train, y_train)
    y_pred = reg.predict(X_test)
    score = reg.score(X_test, y_test)

    x_train = X_train.ravel()
    x_test = X_test.ravel()
    ax.scatter(x_train, y_train,
               label="学習データ(真値+ノイズ)", c="g")
    ax.plot(x_test, y_test, label="真値")
    ax.plot(x_test, y_pred, label="予測値")
    ax.set_title("{0}  score:{1:.4f}".format(name, score))
    ax.legend()

def main():
    x_train = np.linspace(0, 15, 50)
    X_train = x_train.reshape(-1, 1)
    rand = np.random.RandomState(seed=0)
    y_train = (np.sin(x_train) + 
               rand.normal(scale=0.3, size=x_train.shape))

    x_test = np.linspace(0, 15, 500)
    X_test = x_test.reshape(-1, 1)
    y_test = np.sin(x_test)

    dtr = DecisionTreeRegressor()
    rfr = RandomForestRegressor(n_estimators=100, n_jobs=-1)
    svr = SVR()

    fig, axes = plt.subplots(figsize=(14,7), nrows=3, ncols=1)
    plt.subplots_adjust(wspace=1, hspace=0.5)
    for reg, ax in zip([dtr, rfr,  svr], axes):
        test_reg(reg, reg.__class__.__name__,
                 X_train, y_train, X_test, y_test, ax)
    plt.savefig("result.png")

if __name__ == "__main__":
    main()

結果

 次の図のようになりました。

回帰の結果
回帰の結果

 決定木は一見して分かる通り、かくかくですね。というか、最近傍補間に限りなく近い何か。回帰を名乗るのはおこがましいと思う。

 ランダムフォレストは気持ち改善する程度。バギングの力でデータの中間点以外でも階段の段が作れる、データそのものにぴったり載っからなくてもよくなるため、多少滑らかになるという性質を持っています。評価指標は(決定木と比べて)けっこう劇的に改善していますが、見た目はこの程度か感が否めないような・・・。

 SVRは見た目、評価指標ともに最高と言って良いと思います。汎化性能のちからで違和感のない回帰になっております。

考察

 決定木がこんななのは原理的に仕方ない。ランダムフォレストも仕方ない。

 ランダムフォレストに関しては、高次元での挙動次第で使うかどうか考えることになります。割と直感的にわかる低次元と違って高次元空間はいろいろ面白い(カオス)世界、そしてランダムフォレストの得意分野なので、うまく汎化が効けば有効な可能性は十分にあります。

 でも可視化して評価する方法がないので、そしてやるとしたら評価指標頼りになってまったく面白くないので、とりあえずこの記事ではやらない。

 SVRに関しては良いですね。今回はデフォルトパラメータで奇跡的にほどよくフィットしましたが。実用的にはチューニング必須。

まとめ

 決定木を回帰に使うと、あんまり狙ったような回帰にはなりません。

 ランダムフォレストだと多少緩和されます。SVRは狙ったような回帰ができます。

 どちらにせよ評価指標はそこそこ出るので、そして直感的に正しそうな回帰は実はそこまで重要視されないというシチュエーションも多いと思うので、実用的には評価指標見て選べば良いです。

 余談ですが、このまとめを書き始めたとき「誰だよ決定木を回帰に使おうとか最初に考えた奴」とか書こうとして、すぐ「あっ、Leo Breiman先生(CART法とランダムフォレストの生みの親)だ・・・」と思ってやめました。