この2ヶ月ほど業務でGradient Boosting Decision Tree(GBDT)を使用しており、性能の高さに感心し、決定木のアルゴリズムに興味を持ちました。決定木の分岐条件の作り方について調べた時に、性能向上に繋がりそうな改 善策を思いついたので、実装して本当に性能が向上するか試してみてみたくなりました。
決定木を作るためのアルゴリズムはID3, ID4.5, CARTなどいくつか存在します。私が調べた範囲では、どのアルゴリズムも
- 特徴量は全て独立変数であると考える
- 分岐条件を作る際は、データを最もよく分離できる特徴量を1つだけ選ぶ
点が共通していました。
個人的には、Neural Networkは人間の脳の内部構造を真似ることで人間の判断を再現しようとする方法、決定木は人間の判断順序を真似ることで人間の判断を再現しようとする方法であると解釈しています。ただ、人間が条 件分岐のような判断を行う際は、特徴の1箇所に注目するだけでなく、同じデータ間の複数の特徴を比較することも多いように思います。
極端な例を挙げると、四角形が横長か否かを判断する場合、人間は
のような判断をするでしょう。
他にも、去年の身長よりも今年の身長の方が大きかったら背が伸びている、あるトラックの最大積載量よりも実際の積載量が大きかったら過積載など、データ内の別の特徴間を比較して得られる情報は判断に有用であるように感じられます。
ということは、決定木の条件分岐に同データ内の特徴間の比較を加えると、より人間に近い判断ができるようになるのではないでしょうか。 実験で確かめてみます。
実験のため、まずCARTの決定木を実装しました。実装にあたってはこちらの実装を参考に、実験の都合でいくつか変更を加えました。
- 実装を簡単にするため
- 分岐の評価はジニ係数に固定
- 決定領域の可視化を諦め、可視化に使う変数を削除
- 実験を高速にするため
- 使いまわせる計算結果は使い回す
- max_depthの違いによる性能比較を決定木を作り直さずに行えるよう、後からmax_depthを増やして末端ノードだけ分割するメソッドを追加
- 同データ内の別の特徴量間の比較機能を追加
- 全特徴量ではなく、ペアを作って比較を行う特徴のindexをコンストラクタに指定する実装
実験に使った決定木のコードはdecision_tree.py
です。
(横幅, 縦幅)
のペアを1万組作り、9割を学習、1割を評価データとして、横幅のほうが大きいかどうか判定する決定木を学習させて精度を測りました。
コードはclassify_rect.py
です。実行すると、標準出力にタブ区切りで
max_depth sklearnの決定木の精度 実装した決定木の精度(データ内特徴量間比較なし) 実装した決定木の精度(データ内特徴量間比較あり)
が出力されます。
結果は以下のようになりました。
1 0.735 0.735 1.0
2 0.875 0.875 1.0
3 0.915 0.915 1.0
4 0.951 0.951 1.0
5 0.961 0.961 1.0
6 0.979 0.979 1.0
7 0.989 0.989 1.0
8 0.995 0.994 1.0
9 0.994 0.994 1.0
10 0.993 0.994 1.0
- sklearnと実装した決定木(特徴量比較なし)の結果がほぼ揃っていること
- 特徴量間の比較がない場合、木を深くしても完全には正しい分類をできないこと
- データ内の特徴量間の比較を追加すれば深さ1で100%正解できること
いずれも期待通りです。
問題設定の非現実性・この問題を決定木で解くことの是非はさておき、実装したコードは正しく動作しています。
もっと一般的なデータで効果を確認するため、MNISTで実験をしました。
全ての特徴が画素値で同質であり、データ内の特徴間比較が意味を持つ可能性があると考えたからです。
データセットをランダムに分割して9割を学習、1割を評価データとし、sklearnの決定木と、今回実装した決定木(データ内特徴量間比較あり)の評価精度を比較しました。
全画素を互いに比較することは現実的ではないため、28x28の中心の14x14の領域に絞り、行か列が同じ画素だけを比較する設定としました。
max_detphを1~30まで変えながら精度を測り、各モデルが最も高い精度を出した時の値を調べました。
実験に使ったコードはmnist_sklearn.py
, mnist_mytree.py
です。
モデル | 精度 | max_depth |
---|---|---|
sklearn.tree.DecisionTreeClassifier 1回目 | 0.8775714285714286 | 14 |
sklearn.tree.DecisionTreeClassifier 2回目 | 0.8772857142857143 | 21,27 |
sklearn.tree.DecisionTreeClassifier 3回目 | 0.8788571428571429 | 16 |
実装した決定木(データ内特徴間比較なし) | 0.8728571428571429 | 14 |
実装した決定木(データ内特徴間比較あり 中央14x14 同行のみ比較) | 0.8782857142857143 | 18 |
実装した決定木(データ内特徴間比較あり 中央14x14 同行・同列のみ比較) | 0.881 | 15 |
画像中央の14x14の領域で、縦横に画素感の比較を行った場合が最も良い精度となりました。ただし違いは小さく、誤差の可能性があります。誤差ではなかったとしても、改善幅は小さいのは確実です。
何度か精度を測って統計的に有意性を確認したいところですが、時間がかかるためまだ実施していません。
sklearnは1回の実験に20分程度、自前実装は3時間と、sklearnと自分で実装した決定木では(データ内の特徴間比較を行わなかった場合でも)学習速度に大きな差がありました。しかも、sklearnはmax_depthを変える度に1か>ら木を作り直しているのに対し、自前実装は1本の木の末端ノードだけを分割して深さを増やしながら精度を測っており、1本の木を学習させるためにかかる時間は3桁近い速度差がありました。
sklearnの速さに驚きました。分岐条件の探索を素直に実装すれば、計算量は データ数 * 特徴量の次元 * 特徴内のユニークな値の数
になりますが、sklearnは何かショートカットが入っているのでしょうか。ソースコー
ドを流し読みしただけでは分からなかったので、今度時間を取って調べてみようと思います。
MNISTの結果には期待していたので、明確な精度向上がなかったのは残念でした。9割を超えるような精度を期待していたのですが。。
時系列に同じ意味の特徴が入っているような場合には、今回の方法で精度向上の可能性があると今でも考えています。効果がありそうなデータセットを見つけたら再度試してみる予定です。
最終的にはRandom ForestやGBDTの中の決定木の条件分岐を少しリッチにして実課題の解決に繋げたいのですが、先は長そうです。