洋食の日記

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

SVMKitに単純ベイズ分類器を追加した

はじめに

Ruby 25周年の記念日に何かSVMKitをバージョンアップしたくて準備を進めていた。無事に単純ベイズ(Naive Bayes, NB)分類器を追加した状態でバージョンアップできた。Scikit-learnにならってGaussian、Multinomial、Bernoulliの各種分布のアルゴリズムを追加した。この他の変更点は、Factorization Machineによる分類器に、ロス関数を選択するパラメータを追加して、hingeとlogisticを選べるようにした。

svmkit | RubyGems.org | your community gem host

使い方

まずSVMKitをインストールする。線形代数の計算で使用しているNumo::NArrayもインストールされる。

$ gem install svmkit

次に、データを用意する。今回は、単純ベイズということから、LIBSVM DATAより、文書データセットであるnews20を取得した。 TFにより重み付けされた文書ベクトルのデータセットとなる。

$ wget https://www.csie.ntu.edu.tw/~cjlin/libsvmtools/datasets/multiclass/news20.t.bz2
$ bunzip2 news20.t.bz2

Multinomialな単純ベイズの分類精度を、5交差検定でF値でみる場合、以下のようになる。

require 'svmkit'

# LIBSVM形式のnews20データを読み込む.
# ※やや大きいファイルなため全体的に遅め.
samples, labels = SVMKit::Dataset.load_libsvm_file('news20.t')

# Multinomialな単純ベイズを定義する.
# 平滑化パラメータは基本的には1.0で良い.
mnb = SVMKit::NaiveBayes::MultinomialNB.new(smoothing_param: 1.0)

# 評価尺度はマクロ平均な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: mnb, splitter: kf, evaluator: ev)
report = cv.perform(samples, labels)

# 結果を出力する.
mean_f_score = report[:test_score].inject(:+) / kf.n_splits
puts(sprintf("Mean F1-Score: %.1f%%", 100.0 * mean_f_score))

これを実行すると以下の様になる。

$ ruby svmkit_nb_cv.rb
Mean F1-Score: 73.0%

ちなみに、線形SVMでは、以下のようになる(コメントは一部省略)。 TFによる文書ベクトルの文書分類では、Multinomialな単純ベイズが有効であることがわかる。

require 'svmkit'

samples, labels = SVMKit::Dataset.load_libsvm_file('news20.t')

# 線形SVMによる分類器を作成し,それをone-vs-restで多値分類器にする.
svc = SVMKit::LinearModel::SVC.new(reg_param: 1.0, max_iter: 1000, random_seed: 1)
ovr_svc = SVMKit::Multiclass::OneVsRestClassifier.new(estimator: svc)

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: ovr_svc, splitter: kf, evaluator: ev)
report = cv.perform(samples, labels)

mean_f_score = report[:test_score].inject(:+) / kf.n_splits
puts(sprintf("Mean F1-Score: %.1f%%", 100.0 * mean_f_score))
$ ruby svmkit_svc_cv.rb
Mean F1-Score: 69.1%

おわりに

Ruby 25周年おめでとうございます!!