はじめに
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' # LIBSVM形式のデータを読み込む. samples, labels = SVMKit::Dataset.load_libsvm_file('pendigits') # 決定木を定義する. # 木の深さ(max_depth)などがパラメータとしてあるが、指定しなければ、 # 学習データに合わせて成長する(※過学習する恐れがあることに注意). dt = SVMKit::Tree::DecisionTreeClassifier.new(random_seed: 1) # 評価尺度はマクロ平均なF値で. ev = SVMKit::EvaluationMeasure::FScore.new(average: 'macro') # 5-交差検定で評価する. 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') # ランダム森を定義する. # 決定木と同様のパラメータの他に、作成する決定木の数(n_estimators)と、ランダムに選択する特徴数(max_features)を指定できる. # 指定しない場合、Scikit-learnと同様に、n_estimators=10、max_features=Math.sqrt(n_features)となる. # これらパラメータ(とくにmax_features)は最適値を探った方が良い. 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)
そんな感じで、つまらないものですが、よろしくお願い致します。