洋食の日記

洋食のことではなく、技術メモを書きます。たまにどうでも良いことも書きます。

RandSVDを使った主成分分析によるMNISTデータセットの可視化

RandSVDを作ったので、特異値分解固有値分解)をベースにした機械学習アルゴリズムを実装していこうと思っている。 まずは、主成分分析によるMNISTデータセットの可視化を行った。 特異値分解と主成分分析の関係は、そのまま「主成分分析 特異値分解」でググると沢山でてくるので、ネットの海を汚さないためにも、ここでは割愛させて頂きました。

MNISTデータセットは、LIBSVMのページからダウンロードした。

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

LibSVMLoaderで、MNISTデータセットを読み込み、中心化したデータセットを、RandSVDで特異値分解する。 得られた射影行列で2次元に射影し、GnuplotRBでプロットする。

require 'libsvmloader'
require 'randsvd'
require 'gnuplotrb'

# データを読み込む。
samples, labels = LibSVMLoader.load_libsvm_file('mnist.scale.t', stype: :dense)

# 中心化する。
nb_samples, = samples.shape
mean_vec = samples.mean(0)
centered = samples - mean_vec.repeat(nb_samples, 0)

# 特異値分解する。
nb_subspace_dimensions = 2
nb_power_iterations = 3
_u, _s, vt = RandSVD.gesvd(centered, 
                           nb_subspace_dimensions, nb_power_iterations)
projection_matrix = vt.transpose
#
# ※もちろん共分散行列を作っても良い。
# covariance_matrix = centered.transpose.dot(centered) / (nb_samples - 1)
# u, _s, _vt = RandSVD.gesvd(covariance_matrix, 
#                            nb_subspace_dimensions, nb_power_iterations)
# projection_matrix = u
#

# 低次元空間に射影する。
projected = centered.dot(projection_matrix)

# データをラベル別に色分けしてプロットする。
datasets = labels.uniq.map do |lid|
  x = projected.col(0).map.with_index { |e, n| e if labels[n] == lid }
  y = projected.col(1).map.with_index { |e, n| e if labels[n] == lid }
  GnuplotRB::Dataset.new([x.to_a, y.to_a], title: lid.to_s)
end

plot = GnuplotRB::Plot.new(*datasets, with: 'points', title: 'MNIST')
plot.to_png('mnist.png', size: [640, 480])

結果はこんな感じで上手くいってる。

f:id:yoshoku:20170908003948p:plain

自分がRubyで実現したいもので、共通化できる処理をGemにすると、自宅だけでなく会社とか他所のPCで使えて便利だし、もしかしたら同じ問題で悩んでいる遠い友人の手助けにもなるかもしれない。