洋食の日記

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

Rumaleに回帰分析のための計量学習手法MLKRを追加した

はじめに

Ruby機械学習ライブラリであるRumaleに、回帰分析のための計量学習手法である Metric Leaning for Kernel Regression (MLKR) を追加し、ver. 0.22.1 としてリリースした。

rumale | RubyGems.org | your community gem host

使い方

Rumaleはgemコマンドでインストールできる。主成分分析の固有値分解のためにNumo::Linalgを、実行結果の描画にNumo::Gnuplotを使うので、一緒にインストールする。

$ gem install rumale numo-linalg numo-gnuplot

Numo::Gnuplotのためにgnuplotを、Numo::LinalgのためにOpenBLASをインストールする。

$ brew install gnuplot openblas

MLKRは、教師あり特徴変換・次元削減と捉えることができる。後述するが、目的変数に合わせたデータ分布になるような変換・射影を行う。Rumaleではfitメソッドで学習し、transformメソッドで変換・射影を行う。

まずは、テストデータを主成分分析で変換した例を示す。

require 'numo/linalg/autoloader'
require 'rumale'

def make_regression(n_samples: 1000, n_features: 100, n_informative: 10, n_targets: 1)
  n_informative = [n_features, n_informative].min

  rng = Random.new(42)
  x = Rumale::Utils.rand_normal([n_samples, n_features], rng)

  ground_truth = Numo::DFloat.zeros(n_features, n_targets)
  ground_truth[0...n_informative, true] = 100 * Rumale::Utils.rand_uniform([n_informative, n_targets], rng)
  y = x.dot(ground_truth)
  y = y.flatten

  rand_ids = Array(0...n_samples).shuffle(random: rng)
  x = x[rand_ids, true].dup
  y = y[rand_ids].dup

  [x, y]
end

def mds_visualize(x, y, filename: 'tmp.png')
  # 多次元尺度構成法で二次元にマッピングする
  mds = Rumale::Manifold::MDS.new(random_seed: 2)
  z = mds.fit_transform(x)
  # Gnuplotで可視化結果を出力する
  y = (y - y.min) / (y.max - y.min)
  y = Numo::Int32.cast((y * 5.9).floor)
  plots = y.to_a.uniq.sort.map { |l| [z[y.eq(l), 0], z[y.eq(l), 1], t: l.to_s] }
  Numo.gnuplot do
    set(terminal: 'png')
    set(output: filename)
    plot('[-6:6] [-6:6]', *plots)
    plot(*plots)
  end
end

# テストデータを主成分分析で変換する
x, y = make_regression(n_samples: 500, n_informative: 4, n_features: 10)
pca = Rumale::Decomposition::PCA.new(n_components: 10)
z = pca.fit_transform(x, y)

# 結果を可視化する
mds_visualize(z, y, filename: 'pca.png')

描画の便宜上、目的変数を5段階に区切った。異なる目的変数を持つデータが混在しているのがわかる。

f:id:yoshoku:20201205095921p:plain

同様のデータをMLKRで変換してみる。

# テストデータをMLKRで変換する
x, y = make_regression(n_samples: 500, n_informative: 4, n_features: 10)
mlkr = Rumale::MetricLearning::MLKR.new(n_components: nil, init: 'pca')
z = mlkr.fit_transform(x, y)

# 結果を可視化する
mds_visualize(z, y, filename: 'pca.png')

主成分分析の場合と比較して、目的変数にそってデータが配置されているのがわかる。このMLKRで変換したデータで回帰を行えば、決定係数などが向上する。

f:id:yoshoku:20201205100322p:plain

MLKRのアルゴリズム

MLKRの目的関数は、とてもシンプルなもので、以下のようになる。カーネル関数(論文中ではガウスカーネルが用いられる、Rumaleの実装でもそのようにした)を重みとした目的変数の重み平均を推定値とし、目的変数と推定値の自乗誤差を目的関数とする。

\mathcal{L}=\sum_{i}(y_i-\hat{y}_i)^{2}
\hat{y}_{i}=\frac{\sum_{j\neq i} y_{j} k_{ij}}{\sum_{j\neq i}k_{ij}}

ガウスカーネルの計算には、二点間の距離が必要になるが、これがMLKRにより変換・射影したベクトル間の距離となる。こうすることで、カーネル関数による回帰分析において、誤差が小さくなるような変換を得られる。

proceedings.mlr.press

おわりに

計量学習では分類を想定したものが多い。MLKRは回帰分析を対象としていて、おもしろいなと思って実装した。Rumaleの計量学習の実装は、一旦これで打ち止めとしたい。計量学習自体が、教師なし・教師あり・半教師あり学習(もちろん深層学習も)と様々あり、きりがないというか、全てをカバーするなら別gemにしたほうが良いと考えた。MLKRの最適化の実装には、lbfgsb.rbを用いた(我ながら作ってよかった)。今後もRumaleでは、最適化にL-BFGS (-B) が使えるようなアルゴリズムを追加しようと考えている。

github.com