はじめに
LIBSVMとLIBLINEARに実装されているサポートベクターマシン(Support Vector Machine, SVM)のアルゴリズムを、Rumaleのインターフェースで利用するためのGem、Rumale::SVMを作成した。
rumale-svm | RubyGems.org | your community gem host
Rumaleは、もともとSVMKitという名前だった。SVMKitは「RubyでSVMを実装するとしたらどうなるか?」からスタートしている。この経緯から、Rumaleに実装されているSVMによる分類器や回帰は、Rubyで確率的勾配降下法により実装されている。一方で、Scikit-learnをはじめ多くの機械学習ライブラリでは、SVMの実装にはLIBSVMとLIBLINEARを利用することが多い。Rumaleは、SVMKitと異なり、SVMにこだわらない機械学習ライブリであるため、その他の機械学習ライブリと同様にLIBSVMとLIBLINEARによるSVMを扱えるようにしたい、と考えていた。
使い方
Rumale::SVMをインストールすれば、Numo::NArrayやRumaleといった必要なGemがインストールされる。別で外部ライブラリをインストールする必要もない。
$ gem install rumale-svm
Rumaleと同様の使い方ができる。LIBSVM DATAにあるpendigitsデータセットで、線形SVMによる分類器を交差検定してみる。
require 'rumale' require 'rumale/svm' # LIBSVM形式のpendigitsデータセットを読み込む. samples, labels = Rumale::Dataset.load_libsvm_file('pendigits') # LIBLINEARな線形SVM分類器を定義する. svc = Rumale::SVM::LinearSVC.new(reg_param: 8.0, dual: false, fit_bias: true, random_seed: 1) # 5-交差検定を行う. ev = Rumale::EvaluationMeasure::Accuracy.new kf = Rumale::ModelSelection::StratifiedKFold.new(n_splits: 5, shuffle: false, random_seed: 1) cv = Rumale::ModelSelection::CrossValidation.new(estimator: svc, splitter: kf, evaluator: ev) report = cv.perform(samples, labels) # 結果を表示する. mean_score = report[:test_score].inject(:+) / kf.n_splits puts("Mean accuracy: %.4f" % mean_score)
これを実行すると、以下のようになる。LIBLINEARな線形SVM分類器を、Rumaleで交差検定できている。
$ wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/pendigits $ ruby rumale_svm_cv.rb Mean accuracy: 0.9493
Rumaleの他の機能との組み合わせをもう一つ。分類器の定義と交差検定の部分を以下のように変更してみる。
#... 省略 # Random featureでRBFカーネルを近似して, LIBLINEARな線形SVM分類器で分類する. rbf = Rumale::KernelApproximation::RBF.new(gamma: 0.0001, n_components: 512, random_seed: 1) pipeline = Rumale::Pipeline::Pipeline.new(steps: { trns: rbf, clsf: svc }) # 5-交差検定を行う. ev = Rumale::EvaluationMeasure::Accuracy.new kf = Rumale::ModelSelection::StratifiedKFold.new(n_splits: 5, shuffle: false, random_seed: 1) cv = Rumale::ModelSelection::CrossValidation.new(estimator: pipeline, splitter: kf, evaluator: ev) #... 省略
これを実行すると、以下のようになる。
$ ruby rumale_svm_cv.rb Mean accuracy: 0.9960
Rumale::SVMではこの他、カーネルSVMによる回帰やOne-class SVM、ロジスティック回帰など、LIBSVMとLIBLINEARにあるアルゴリズムは利用できるようにした。これは、sklearn.svmにあるものと同様である。
※ ちなみに最初の線形SVM分類器の例で、Rumale::LinearModel::SVCで同様の結果を得ようと思うと以下のようになる。実行速度は、LIBLINEARをベースにしたRumale::SVM::LinearSVCのほうが圧倒的に速い。
svc = Rumale::LinearModel::SVC.new(reg_param: 8e-5, max_iter: 5000, batch_size: 20, fit_bias: true, random_seed: 1)
おわりに
RumaleにあるSVMを、LIBSVMとLIBLINEARを利用したモノにすることは、以前より考えていた。ただ、RubyでLIBSVMやLIBLINEARを叩くライブラリは他にもあり、どうしようかなと思っていた。それらライブラリは直接的にNumo::NArrayな配列を利用できないので、まず、Numo::NArrayでLIBSVMとLIBLINEARを扱うライブラリを作成した。
- numo-libsvm | RubyGems.org | your community gem host
- numo-liblinear | RubyGems.org | your community gem host
そして、これらをRumaleなインターフェースでRumaleとともに使える形にまとめた。SVMにLIBSVMとLIBLINEARを利用するようになって、ある意味、Rumaleも機械学習ライブラリらしくなった。
この他「scikit-learn-contribのような拡張ライブラリをRumaleで作るとしたらどうなるか?」ということも考えており、それも合わせて達成できた。作ってみた感じ、インタフェースを合わせればRumaleと一体的に使えるので、便利だった。Rubyはオープンクラスなので、案外、拡張性みたいなのが、他の言語の機械学習ライブラリと比較して強みになるかもしれない(具体的な考えはないけど)。