洋食の日記

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

Rumaleの線形モデルの実装を修正した

はじめに

気がつくと全然ブログを書いていなかった。ver. 0.14で多層パーセプトロンを追加して細かい修正とかを重ねて、今はver. 0.17.0になった。ver. 0.17.0での大きな変更は、Rumale::LinearModel以下を刷新したことにある。

rumale | RubyGems.org | your community gem host

-->8-- 以下ただのポエムです -->8--

LinearModelの変更

Rumaleは以前はSVMKitという名前で開発を進めていた。Rubyでそれなりに動くSupport Vector MachineSVM)を実装してみようというのが始まりで、そこから色々な機械学習アルゴリズムを追加してRumaleとなった。

SVMKitは、線形SVMを実装するところから始まったので、LinearModelは、わりと実験的な実装になっていた。LinearModel以下にあるアルゴリズムは、確率的勾配降下法(Stochastic Gradient Descent, SGD)により最適化を行う。SGDの学習率に、学習率を適応的に計算するAdamというアルゴリズムに、Nesterov momentumを組み合わせたNadamをデフォルトで用いていた(AdaGradやRMSpropといった他のアルゴリズムも選択可能)。Adamなどは、深層学習の文脈で発展したもので、単層のシンプルな線形モデルでは、オーバースペックなところがあった。実際に、普通のSGD+momentumでも十分な分類精度が得られていた(データセットによっては学習率の調整が必要になるが)。

また、Elastic-net回帰の実装が難しい構成になっていた。Elastic-netは、L2正則化とL1正則化を混合したものだが、SGDで実装しようと思うと、重みベクトルを更新したのちに、L2正則化とL1正則化を順に加える形になる。実装しようと考えたSGDのL1正則化アルゴリズムでは、学習率が必要になったため、Nadamでは難しかった。

そこで、まず、AdaGradやAdamの利用は廃してシンプルなSGD+momentumだけに修正し、アルゴリズムにあわせて、L2正則化とL1正則化を加える構成に変更した。これを用いて実装したElastic-net回帰を、ver. 0.16.1でリリースした。そして、ver. 0.17.0で全ての線形モデルに、新しい実装を適用した。あわせて、学習回数を決めるmax_iterパラメータを、epoch数とした。与えられた訓練データ全体を学習プロセスに投入したものが、1 epochとなる。

LinearModelを新しくしたことで、SVMやLogistic回帰にも、L1正則化や、Elastic-net同様にL2正則化とL1正則化を混合したものを加えることができるようになった。おおよそ LIBLINEAR と同様のことができるようになったので、SVMKitとしてもよかったのかも。

おわりに

この他、ver. 0.15.0で、FeatureHasherやHashVectorizerといった、特徴抽出のためのクラスを追加した。特徴ベクトルをHashのArrayで渡すと、Rumaleで使える Numo::DFloatで返すものである。文書分類などを実装するさいに便利だと思う。

encoder = Rumale::FeatureExtraction::HashVectorizer.new
x = encoder.fit_transform([
  { foo: 1, bar: 2 },
  { foo: 3, baz: 1 }
])

# > pp x
# Numo::DFloat#shape=[2,3]
# [[2, 0, 1],
#  [0, 1, 3]]

LinearModelの修正により、Rumaleも、いい意味で、オーソドックスな機械学習ライブラリになってきた。アルゴリズムの数も多くなってきたのもあり、今はユーザーガイドの執筆に時間をかけようと考えている。

github.com