はじめに
SVMKitに決定木(Decision Tree)とランダム森(Random Forest)による分類器を実装し、バージョンを0.2.6とした。
svmkit | RubyGems.org | your community gem host
決定木は、CARTをベースにした二分木によるものを実装した。Scikit-learnにならって、いわゆる木の剪定(pruning)は実装せず、木の深さなど木の成長に関するパラメータをあらかじめ指定して、過学習を防ぐアプローチ(事前剪定,pre-pruning)を採用した。このパラメータに関しては、「Pythonではじめる機械学習」を参考にして、木の最大の深さ(max_depth)、葉の最大数(max_leaf_nodes)、葉がもつサンプル数の最小値(min_samples_leaf)を実装した。
Pythonではじめる機械学習 ―scikit-learnで学ぶ特徴量エンジニアリングと機械学習の基礎
ランダム森は、決定木が実装できれば簡単に実装できる。ブートストラップサンプルしたデータで決定木を作成することを複数回おこない、分類の際は、これら複数の決定木で多数決をとる。決定木を成長させる際に、全ての特徴量を使わずに、ランダムに選んだ一部の特徴量のみを使用する。決定木は、学習データの変化に影響を受けやすいため、複数のもので多数決をとることで安定的な分類器を実現できる。また、ランダムに選択した一部の特徴量のみを使うため、互いに異なった決定木が作成される(※ランダムな特徴量の選択をしない場合、決定木によるバギングと同様となる)。決定木やランダム森のアルゴリズムの詳細については、「はじめてのパターン認識」がわかりやすい。
はじめてのパターン認識
使い方
まずSVMKitをインストールする。線形代数の計算で使用しているNumo::NArrayもインストールされる。
$ gem install svmkit
次に、データを用意する。今回は、LIBSVM DATAから、手書き数字のデータセットであるpendigitsをとってきた。
$ wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/pendigits
決定木の分類精度を、5交差検定でF値で確認する場合、以下のようになる。
require 'svmkit'
samples, labels = SVMKit::Dataset.load_libsvm_file('pendigits')
dt = SVMKit::Tree::DecisionTreeClassifier.new(random_seed: 1)
ev = SVMKit::EvaluationMeasure::FScore.new(average: 'macro')
kf = SVMKit::ModelSelection::StratifiedKFold.new(n_splits: 5, shuffle: true, random_seed: 1)
cv = SVMKit::ModelSelection::CrossValidation.new(estimator: dt, splitter: kf, evaluator: ev)
report = cv.perform(samples, labels)
mean_f_score = report[:test_score].inject(:+) / kf.n_splits
puts(sprintf("Mean F1-Score: %.3f", mean_f_score))
これを実行すると以下の様になる。
$ ruby svmkit_dtree_cv.rb
Mean F1-Score: 0.953
さらに、ランダム森の場合は、以下の様になる(一部コメントを省略)。
require 'svmkit'
samples, labels = SVMKit::Dataset.load_libsvm_file('pendigits')
rf = SVMKit::Ensemble::RandomForestClassifier.new(random_seed: 1)
ev = SVMKit::EvaluationMeasure::FScore.new(average: 'macro')
kf = SVMKit::ModelSelection::StratifiedKFold.new(n_splits: 5, shuffle: true, random_seed: 1)
cv = SVMKit::ModelSelection::CrossValidation.new(estimator: rf, splitter: kf, evaluator: ev)
report = cv.perform(samples, labels)
mean_f_score = report[:test_score].inject(:+) / kf.n_splits
puts(sprintf("Mean F1-Score: %.3f", mean_f_score))
これを実行すると以下の様になる。決定木よりもF値が改善されていることがわかる。
$ ruby svmkit_rforest_cv.rb
Mean F1-Score: 0.967
おわりに
決定木は、アルゴリズムとして線形代数的な計算をあまり含まず、Numo::NArrayを使用することによる高速化の恩恵が少ないため、少し実装を避ける気持ちがあった(今回の実装も、煩雑なコードになってしまい、動作も高速なものではない)。しかし、決定木とランダム森を実装したことで、代表的な分類器をそろえることができた。今後のSVMKitの開発は、K-meansクラスタリングや主成分分析などの、教師なし学習の実装に入る前に、全体的なリファクタリングを考えている。ひとまず代表的な分類器を実装することを優先したので、パラメータの値チェックなどが実装されていない。また、パフォーマンス的なことも一旦は無視している。ドキュメントはそろえる様に心がけているのですが...
File: README — Documentation for svmkit (0.2.6)
そんな感じで、つまらないものですが、よろしくお願い致します。