はじめに
機械学習アルゴリズムでは、固有値分解が逆行列計算といった、線形代数のテクニックを利用する場合がある。これらをRubyで使用するには、Numo::Linalgが適している。一方で、Numo::Linalgは、Numo::NArrayと異なり、BLAS/LAPACK系の外部ライブラリに依存するため、Rumaleでは依存することを避けてきた。ただ、今後のRumaleの拡張を考えると必要となる。そこで、以前にParallelを導入したときのように、別でNumo::Linalgをrequireしている場合にのみ、機能が有効になるよう実装することにした。
まず、試験的に、主成分分析で固有値分解を使うものを実装してみた。主成分分析は、主成分ベクトルを、共分散行列の固有値分解により求める方法がメジャーである。他にも主成分ベクトルを求めるアルゴリズムはあり、Rumaleでは不動点法によるアルゴリズムを実装していた。今回、solverオプションを追加して、固有値分解によるアルゴリズムを選択できるようにした。これを version 0.13.0 としてリリースした。
rumale | RubyGems.org | your community gem host
使い方
Rumaleは、gemコマンドでインストールできる。データセットの読み込みでred-datasetsを、主成分分析したデータの可視化にNumo::Gnuplotを使いたいので、あわせてインストールする。
$ gem install rumale red-datasets-numo-narray numo-gnuplot
そして、Numo::Linalgをインストールする。
$ gem install numo-linalg
USPSという手書き数字画像のデータセットを、主成分分析で二次元に射影して可視化する。
require 'rumale' require 'datasets-numo-narray' require 'numo/gnuplot' # Numo::Linalgを明示的にrequireしない限りは有効にならない. require 'numo/linalg/autoloader' # Numo::NArray形式でUSPSデータセットを読み込む. dataset = Datasets::LIBSVM.new('usps').to_narray labels = Numo::Int32.cast(dataset[true, 0]) samples = Numo::DFloat.cast(dataset[true, 1..-1]) # 適当にサブサンプリングした. rand_id = Array.new(samples.shape[0]) { |n| n }.sample(1000) labels = labels[rand_id] samples = samples[rand_id, true] # 共分散行列を固有値分解することによる主成分分析で、二次元空間にデータを射影する. # solverオプションに 'evd' を指定する(指定しなければコレまでどおり不動点法を使う). pca = Rumale::Decomposition::PCA.new(n_components: 2, solver: 'evd', random_seed: 1) low_samples = pca.fit_transform(samples) # Numo::GnuplotでPNGファイルに書き出す. x = low_samples[true, 0] y = low_samples[true, 1] plots = labels.to_a.uniq.sort.map { |l| [x[labels.eq(l)], y[labels.eq(l)], t: l.to_s] } Numo.gnuplot do set(terminal: 'png') set(output: 'pca.png') plot(*plots) end
以上のスクリプトを実行した結果は次のようになる。USPSを主成分分析で可視化するとだいたいこんな感じになる。上手く動いていることがわかる。
おわりに
Numo::Linalgを導入したので、カーネル主成分分析やカーネルRidge回帰など、固有値分解や逆行列計算を必要とする機械学習アルゴリズムを実装していこうと思っている。