洋食の日記

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

Rumaleに非負最小二乗法による回帰を追加した

はじめに

Ruby機械学習ライブラリであるRumaleに、非負最小二乗法(Non-nagtive Least Squares, NNLS)による線形回帰を追加して、ver. 0.22.3としてリリースした。

https://rubygems.org/gems/rumale/versions/0.22.3

使い方

Rumaleはgemコマンドでインストールできる。

$ gem install rumale

非負最小二乗法は、その名の通り、回帰係数の値が負にならないという制限がついた回帰手法である。 与えられた説明変数や目的変数が、負の値を含まない場合に有効であったりする。 普通の線形回帰で当てはまりが悪い場合に、検討してみると良い。 簡単なサンプルプログラムを、以下に示す。

require 'rumale'

rng = Random.new(1)
n_samples = 200
n_features = 100

# ランダムなデータを用意する.
x = Rumale::Utils.rand_normal([n_samples, n_features], rng)

# 非負の係数を用意する (本来は未知).
coef = Rumale::Utils.rand_normal([n_features, 1], rng)
coef[coef.lt(0)] = 0.0

# ノイズを加えつつダミーの目的変数を用意する.
noise = Rumale::Utils.rand_normal([n_samples, 1], rng)
y = x.dot(coef) + noise

# 訓練とテストに分割する.
x_train, x_test, y_train, y_test = Rumale::ModelSelection.train_test_split(x, y, test_size: 0.4, random_seed: 1)

# 非負最小二乗法を訓練し, 決定係数を計算する.
nnls = Rumale::LinearModel::NNLS.new(reg_param: 1e-4, random_seed: 1).fit(x_train, y_train)
puts(format("NNLS R2-Score: %.4f", nnls.score(x_test, y_test)))

# リッジ回帰を訓練し, 決定係数を計算する.
ridge = Rumale::LinearModel::Ridge.new(solver: 'lbfgs', reg_param: 1e-4, random_seed: 1).fit(x_train, y_train)
puts(format("Ridge R2-Score: %.4f", ridge.score(x_test, y_test)))

これを実行すると、以下の様になる。 決定係数が1に近いほど、回帰の当てはまりが良いといえる。 非負最小二乗法のほうが、リッジ回帰よりも決定係数の値が大きい。 わざと、回帰係数が非負のものを用意したので、当たり前なのだが、 非負値の制限を加えたほうが、当てはまりが良い場合がある。

$ ruby nnls.rb
NNLS R2-Score: 0.9478
Ridge R2-Score: 0.8602

また、非負最小二乗法による回帰は、スタッキングによる回帰において、 最終段階のメタ推定器に使用するのが有効であるという研究もある。

Breiman, L., "Stacked Regressions," Machine Learning, 24, pp. 49-64, 1996.

おわりに

非負最小二乗法による線形回帰を追加した。非負最小二乗法は、回帰係数の値域に制限を加えた回帰(Bounded-Variable Least-Squares)の一種といえる。Rumaleでは、非負最小二乗法を、制限付きの最適化ができるL-BFGS-Bで実装した。L-BFGS-Bのgemを作ったことで、実装できるアルゴリズムが広がった。その他、ver. 0.22.3では、native extensionsに、ガーベージコレクションによるsegmentation faultが起きる恐れのある箇所があり、それに対処したりした。

github.com