洋食の日記

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

Rumaleに多次元尺度構成法を追加した

はじめに

Rumaleに多次元尺度構成法(Multidimensional Scaling: MDS)による次元削減を実装した。MDSには様々なアルゴリズムがあるが、Rなどでも実装されている Scaling by MAjorizing a COmplicated Function(SMACOF)による方法を採用した。

rumale | RubyGems.org | your community gem host

MDSは、データの可視化に用いられることが多い。距離で表されるデータの関係を、低次元空間に保存する(nonmetricなMDSもあるが今回はmetricなものだけを実装した)。MDSは、古典的な可視化手法で、t-SNEが人気な現在でも広く利用される。

使い方

Rumaleはgemコマンドでインストールできる。Numo::NArrayに依存している。データのplotにNumo::Gnuplotを使いたいので、これもインストールする。※別途gnuplotをインストールする必要がある

$ brew install gnuplot
$ gem install rumale numo-gnuplot 

LIBSVM Dataにあるデータセットを可視化したいので、red-datasetsもインストールする。

$ brew install red-datasets

USPSという手書きの数字データセットをMDSにより可視化してみる。

require 'rumale'
require 'numo/linalg/autoloader'
require 'datasets-numo-narray'
require 'numo/gnuplot'

# 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]

# デフォルトではデータ間の距離にはユークリッド距離を用いる.
# verboseでtrueを指定すると, 最適化過程の数値を出力する.
mds = Rumale::Manifold::MDS.new(max_iter: 500, verbose: true, random_seed: 1)
low_samples = mds.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: 'mds.png')
  plot(*plots)
end

これを実行すると以下のような可視化結果が得られる。 ※sampleメソッドによるランダムなサブサンプリングが、実行毎に変わるので、必ず同じものが得られるとは限らない。

f:id:yoshoku:20190628202026p:plain

手書き数字のデータセットが、同じクラスではある程度かたまりながら分布していることがわかる。ただし、異なるクラスのデータが重なっている部分も多い。単純な、ユークリッド距離によるk-近傍法による分類では、誤分類がある程度発生することが予想できる。といった感じで、特徴空間でのデータ分布構造を確認することができる。

おわりに

ここしばらく、Rumaleでは、教師なし学習アルゴリズムの追加を行った。一度手を休めて、リファクタリングや性能向上を進めたいと思う。

github.com