SVMKitでは、線形代数ライブラリにNMatrixを使用していたが、パフォーマンスと将来性からNumo::NArrayに移行した。Numo::NArrayのメソッド等は、NMatrixともNumpyとも違うが、specを用意しておいて淡々と作業した。思い切った移行だけど、ユーザーも少ないので大丈夫と思いたい😅
つまらないものですが、よろしくお願い致します。
SVMKitでは、線形代数ライブラリにNMatrixを使用していたが、パフォーマンスと将来性からNumo::NArrayに移行した。Numo::NArrayのメソッド等は、NMatrixともNumpyとも違うが、specを用意しておいて淡々と作業した。思い切った移行だけど、ユーザーも少ないので大丈夫と思いたい😅
つまらないものですが、よろしくお願い致します。
rubocopするのを忘れがちで、vimで(自動で)できてくれると嬉しいので、syntasticを入れることにした。使ってるvimのバージョンは8.0.1200なので、nativeなpackage管理を使ってインストールすることにした。
パッケージを入れるためのディレクトリを作って、そこにsyntasticをgit cloneするだけでよい。
$ mkdir -p ~/.vim/pack/plugins/start $ cd ~/.vim/pack/plugins/start/ $ git clone --depth=1 https://github.com/vim-syntastic/syntastic.git
.vimrcは以下のとおり。設定については、公式のモノやカッコイイ感じのモノを参考にすると良い。
" set statusline=%F とかがあって set statusline+=%#warningmsg# set statusline+=%{SyntasticStatuslineFlag()} set statusline+=%* let g:syntastic_always_populate_loc_list = 0 let g:syntastic_auto_loc_list = 0 let g:syntastic_check_on_open = 1 let g:syntastic_check_on_wq = 1 " rubocop動かしますよ〜 let g:syntastic_ruby_checkers = ['rubocop']
ガリガリ書いてる段階で自動でrubocopが走るのが重い。もうちょっと考える。
Pure Rubyな機械学習ライブラリSVMKitにカーネルSVMを追加しました。カーネルSVMは、Pure Rubyでは速度的にツラいものがあるかな?と思っていたが、機械学習ライブラリとしては実装されているべきものなので追加した。※それ以前にLogistic Regressionを追加したけどブログに書くのを失念した...
svmkit | RubyGems.org | your community gem host
行列・ベクトルをあつかうのでNMatrixに依存する。
$ gem install nmatrix svmkit
Pythonのscikit-learnライクを意識してきたけど、今回はちょっと違うものにしてみた。 入力データとして、特徴ベクトルを与えるのが機械学習ライブラリでは一般的だが、SVMKitではカーネル行列を与える形にした。libsvmでいうところのprecomputed kernelという形式となる。 これは、世にある全てのカーネル関数を実装するのは難しいことと、ライブラリ利用者が適切なカーネルを選択できるように、という思いから。RBFカーネルやシグモイドカーネルは実装してある。
libsvm dataのサイトにあるpendigitsデータセットを読み込んで、分類するコードは以下の通り。 まずは、分類機の訓練から。
require 'svmkit' require 'libsvmloader' # libsvm形式の訓練データセットを読み込む。 samples, labels = LibSVMLoader::load_libsvm_file('pendigits', stype: :dense) # RBFカーネル行列を計算する。※パラメータγは0.005とした。 kernel_matrix = SVMKit::PairwiseMetric::rbf_kernel(samples, nil, 0.005) # カーネルSVMを用意する。 base_classifier = SVMKit::KernelMachine::KernelSVC.new(reg_param: 1.0, max_iter: 1000, random_seed: 1) # one-vs-restで多値分類器とする。 classifier = SVMKit::Multiclass::OneVsRestClassifier.new(estimator: base_classifier) # 分類器を訓練する。 classifier.fit(kernel_matrix, labels) # 分類器を保存する。 File.open('trained_classifier.dat', 'wb') { |f| f.write(Marshal.dump(classifier)) }
そして、訓練済みの分類器を読み込んで、テストデータを分類するコード。
require 'svmkit' require 'libsvmloader' # libsvm形式のテストデータセットを読み込む。 samples, labels = LibSVMLoader::load_libsvm_file('pendigits.t', stype: :dense) # カーネル行列を計算するために、訓練データセットも読み込む。※ラベル情報は不要 tr_samples, = LibSVMLoader::load_libsvm_file('pendigits', stype: :dense) # 訓練済みの分類器を読み込む。 classifier = Marshal.load(File.binread('trained_classifier.dat')) # テストデータ-訓練データ間でRBFカーネル行列を計算する。 kernel_matrix = SVMKit::PairwiseMetric::rbf_kernel(samples, tr_samples, 0.005) # テストデータのラベルを推定し、分類結果のAccuracyを出力する。 puts(sprintf("Accuracy: %.1f%%", 100.0 * classifier.score(kernel_matrix, labels)))
結果としてAccuracyは97.6%となった。
訓練データがこれで、
テストデータがこれ。
テストデータのラベルを、SVMKitのカーネルSVMで学習して推定すると、
こうじゃ!!うまく非線形データを分類できている。
つまらないものですが、よろしくお願い致します。
RubyにPythonのscikit-learnに相当するライブラリがない様なので、作ってみることにした。ひとまず、Support Vector Machine(SVM)による多値分類が実装できたので、gemとして公開することにした。今後、他の機械学習アルゴリズムも追加していく。
svmkit | RubyGems.org | your community gem host
行列・ベクトルをあつかうのでNMatrixに依存する。
$ gem install svmkit
インターフェースは、scikit-learnライクにした。libsvm dataのサイトにあるpendigitsデータセットを読み込んで、分類するコードは以下の通り。 まずは、分類機の訓練から。
require 'svmkit' require 'libsvmloader' # libsvm形式の訓練データセットを読み込む。 samples, labels = LibSVMLoader.load_libsvm_file('pendigits', stype: :dense) # 特徴量の値を[0,1]の範囲に正規化する。 normalizer = SVMKit::Preprocessing::MinMaxScaler.new normalized = normalizer.fit_transform(samples) # RBFカーネル空間に(カーネル近似法で)射影する。 transformer = SVMKit::KernelApproximation::RBF.new(gamma: 2.0, n_components: 1024, random_seed: 1) transformed = transformer.fit_transform(normalized) # ベースとなる2値分類のSVMを定義して、 base_classifier = SVMKit::LinearModel::PegasosSVC.new(penalty: 1.0, max_iter: 50, batch_size: 20, random_seed: 1) # それを、One-vs.-Rest法で多値化する。 classifier = SVMKit::Multiclass::OneVsRestClassifier.new(estimator: base_classifier) # そして、分類器を学習する。 classifier.fit(transformed, labels) # 各種モデルを保存する。 File.open('trained_normalizer.dat', 'wb') { |f| f.write(Marshal.dump(normalizer)) } File.open('trained_transformer.dat', 'wb') { |f| f.write(Marshal.dump(transformer)) } File.open('trained_classifier.dat', 'wb') { |f| f.write(Marshal.dump(classifier)) }
そして、訓練済みの分類器を読み込んで、テストデータを分類するコード。
require 'svmkit' require 'libsvmloader' # libsvm形式のテストデータセットを読み込む。 samples, labels = LibSVMLoader.load_libsvm_file('pendigits.t', stype: :dense) # 各種モデルを読み込む。 normalizer = Marshal.load(File.binread('trained_normalizer.dat')) transformer = Marshal.load(File.binread('trained_transformer.dat')) classifier = Marshal.load(File.binread('trained_classifier.dat')) # 正規化とカーネル空間への射影を行う。 normalized = normalizer.transform(samples) transformed = transformer.transform(normalized) # テストデータセットのラベルを推定する。 # predicted_labels = classifier.predict(transformed, labels) # Accuracyを出力する。 puts(sprintf("Accuracy: %.1f%%", 100.0 * classifier.score(transformed, labels)))
今後は、scikit-learn内のベーシックな手法を実装していきたい。ある程度、中身が充実してきたら、gem名を変えると思う。次は、決定木系のアルゴリズムの実装かなぁ。つまらないものですが、よろしくお願い致します。
RubyのLIBSVMバインドであるrb-libsvmと、libsvm形式のデータセットを読み書きするlibsvmloaderで、カーネル非線形SVMで分類する例です。カーネル非線形と明記するのは、libsvmの姉妹品であるliblinearが線形SVMなため。
まずLIBSVMそのものをインストールする必要がある。macでhomebrewなら次のとおり。
$ brew install libsvm
次にGemをインストールする。libsvmloaderがnmatrixに依存するので、一応nmatrixつけてみました。SciRubyにようこそ的なメッセージがでるのが良いよね〜。
source 'https://rubygems.org' gem 'nmatrix' gem 'rb-libsvm' gem 'libsvmloader'
$ bundle install
最後にサンプルで使うデータセットをlibsvmのサイトからダウンロードする。MNISTが有名だけど、大きいので、同じ手書き文字認識データセットのpendigitsを使う。
$ wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/pendigits $ wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/pendigits.t
「1. 訓練データセットで、SVMを学習する。2. テストデータセットを読み込んで、ラベルを推定する。 3. Accuracyを計算して出力する。」この一連の流れを一つにまとめた。
require 'libsvmloader' require 'libsvm' # 訓練データを読み込む samples, labels = LibSVMLoader.load_libsvm_file('pendigits') # 訓練データをrb-libsvmの特徴ベクトル形式に変換する examples = samples.each_row.map { |v| Libsvm::Node.features(v.to_a) } # カーネル非線形SVMのパラメータを設定する params = Libsvm::SvmParameter.new.tap do |p| p.cache_size = 1000 # キャッシュメモリサイズ [MB] p.svm_type = Libsvm::SvmType::C_SVC # SVM分類器 p.kernel_type = Libsvm::KernelType::RBF # RBFカーネル p.gamma = 0.0001 # RBFカーネルのパラメータ p.c = 1.0 # SVMの正則化パラメータ p.eps = 0.001 # SVMの最適化を終了する閾値 end # カーネル非線形SVMを訓練する problem = Libsvm::Problem.new problem.set_examples(labels.to_flat_a, examples) model = Libsvm::Model.train(problem, params) # テストデータを読み込む samples, labels = LibSVMLoader.load_libsvm_file('pendigits.t') # テストデータのラベルを推定する preds = samples.each_row.map { |v| model.predict(Libsvm::Node.features(v.to_a)) } # Accuracyを出力する n_hits = preds.map.with_index { |l, n| 1 if l == labels[n] }.compact.sum n_samples = labels.size accuracy = 100.0 * (n_hits / n_samples.to_f) puts(format('Accuracy = %.4f%% (%d/%d)', accuracy, n_hits, n_samples))
これを実行すると、pendigitsデータセットの学習と分類が行われて、バーンとAccuracyが表示される。
$ ruby hoge.rb Accuracy = 98.2847% (3438/3498)
RubyにはLIBSVMをバインドしたGemが他にもある。それぞれに使い方が異なるので、チームやプロジェクトにあったものを選ぶのが良いと思う。Pythonのscikit-learnmみたいな、デファクトスタンダードな機械学習ライブラリはRubyにはない(2017/09/16現在)。みんな自由にやってる感じ。でも、それはそれで良い雰囲気だと思う。そんなわけで、個人的にscikit-learnインスパイアなRubyのベーシックな機械学習ライブラリを作ることを考えている(特に野心はなく単純な興味から)。深層学習は「流行ってるから、もう誰かライブラリ作ってるでしょ〜」といった気持ち。
Deep learning hype in one picture
— Alex Lebrun (@lxbrun) 2017年9月15日
(NIPS conference registrations, 2002 through 2017) #nips2017 pic.twitter.com/APOrzY1gpD
ホントにスゴイ人気!!