洋食の日記

「だ・である」調ではなく「です・ます」調で書きはじめれば良かったなと後悔してる人のブログです

LIBSVMとLIBLINEARをRumaleなインターフェースで使えるRumale::SVMを作った

はじめに

LIBSVMとLIBLINEARに実装されているサポートベクターマシンSupport Vector Machine, SVM)のアルゴリズムを、Rumaleのインターフェースで利用するためのGem、Rumale::SVMを作成した。

rumale-svm | RubyGems.org | your community gem host

Rumaleは、もともとSVMKitという名前だった。SVMKitは「RubySVMを実装するとしたらどうなるか?」からスタートしている。この経緯から、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を利用したモノにすることは、以前より考えていた。ただ、RubyLIBSVMやLIBLINEARを叩くライブラリは他にもあり、どうしようかなと思っていた。それらライブラリは直接的にNumo::NArrayな配列を利用できないので、まず、Numo::NArrayでLIBSVMとLIBLINEARを扱うライブラリを作成した。

そして、これらをRumaleなインターフェースでRumaleとともに使える形にまとめた。SVMLIBSVMとLIBLINEARを利用するようになって、ある意味、Rumaleも機械学習ライブラリらしくなった。

この他「scikit-learn-contribのような拡張ライブラリをRumaleで作るとしたらどうなるか?」ということも考えており、それも合わせて達成できた。作ってみた感じ、インタフェースを合わせればRumaleと一体的に使えるので、便利だった。Rubyオープンクラスなので、案外、拡張性みたいなのが、他の言語の機械学習ライブラリと比較して強みになるかもしれない(具体的な考えはないけど)。

github.com