機械と学習する

統計解析、機械学習について学習したことをまとめていきます

Juliaを使ってギブスサンプリングで線形回帰(多項式回帰)する

【概要】

  • Juliaの練習として、線形回帰(いわゆる「ベイズ線形回帰」)を書いてみた
  • 解析解とGibbs samplingによる近似解を合わせて導出
  • 雑だけど、Pythonとの比較もした

【目次】


はじめに

良い良いとすごく評判のJulia言語を触ってみました。

ここでは、Juliaの練習として、線形回帰(多項式回帰)をベイズ的に推論する「ベイズ線形回帰モデル」の推論をやってみます。 ベイズ線形回帰の推論は、行列演算、制御構文(ループ)、描画あたりが全て含まれていて良い題材と思います。

また、私が普段使っているプログラミング言語であるPythonとの比較として、(厳密ではないですが)計算速度の定量的な比較と、使ってみた感想を定性的な比較として列挙してみます。

間違いなどあったら指摘していただけると助かります。

【トップに戻る】

実験環境

実験環境はMac mini (Apple M1チップ)でJupyter labで書いています。環境構築について詳しくは私の書いた以下のQiita記事を見てください。

qiita.com

【トップに戻る】

線形回帰モデル

線形回帰モデルは、入力ベクトル\mathbf{x}と重みベクトル\mathbf{w}の線型結合で目的変数yを表現するモデルです。

当ブログでは、線形回帰モデルについては何度か扱っていますので、モデルの詳細については以下の記事などを合わせて参照していただけたらと思います。

learning-with-machine.hatenablog.com

では、今回扱うモデルを定義していきます。

システムモデル

今回は、システムノイズはないとして、以下の通り入力ベクトル\mathbf{x}と重みベクトル\mathbf{w}の線型結合とします。

f:id:hippy-hikky:20210906143522p:plain

ここで、入力ベクトルは以下の通り、k次の冪乗を特徴量としています。

f:id:hippy-hikky:20210906143650p:plain

観測モデル

上記のシステムモデルに対して、観測時にはガウスノイズが乗ってくるとします。

f:id:hippy-hikky:20210906143940p:plain

ここで、観測ノイズの分散\sigma_y^2は既知のパラメータとします。

重みベクトルの事前分布

重みベクトル\mathbf{w}の事前分布は、以下の通り各次元独立な多変量ガウス分布とします。

f:id:hippy-hikky:20210906144240p:plain

同時分布

ということで全体を結合して、今回推論するモデルは以下の通りです。

f:id:hippy-hikky:20210906144523p:plain

【トップに戻る】

モデルの推論

線形回帰モデルは重みベクトルの事前分布にガウス分布を利用すれば解析的に解を導出できます。

ですが、プログラミングの練習として、ここではギブスサンプリングによる近似解も合わせて計算します。

解析解

複数の観測データを使って上記モデルを推論した結果としての重みベクトル\mathbf{w}の事後分布p(\mathbf{w} | \mathbf{Y}, \mathbf{X})は以下の記事にあるように導出できます。(以下の記事では手書きで導出過程を書いています。汚い手書きでごめんなさい)

learning-with-machine.hatenablog.com

近似解(Gibbs sampling)

MCMCアルゴリズム(ギブスサンプリング)を利用して近似解も推論してみます。

今回は、単変量ガウス分布からのサンプルは容易だが、多変量ガウス分布からのサンプルは難しいと仮定して、\mathbf{w}の各次元について独立にサンプルすることにします。

i次元の重みw_iのサンプルを取得するにはi次元以外の重み\mathbf{w}_{-i}を固定とした条件付き分布を導出する必要がありますが、その導出については以下の記事に導出過程を掲載しています。(これも汚い手書きでごめんなさい)

learning-with-machine.hatenablog.com

【トップに戻る】

実装

トイデータ

今回扱うトイデータは、3次の多項式に従う以下の関数(オレンジ線)からサンプルします。サンプルしたデータは青点で描画しています。

f:id:hippy-hikky:20210906151244p:plain

実装で詰まったところ

Distributions.jlMvNormalを利用して多変量ガウス分布に従う乱数を生成するところで、解析的に算出した共分散行列を入力したら以下のエラーが出てしまいました。

ERROR: PosDefException: matrix is not Hermitian; Cholesky factorization failed.

ここの議論でSymmetric関数を通せば良いということは分かったのですが、Symmetric関数を通しても(通す前と)行列の各値は同じように見えますし。。。エルミート行列とか全然理解していないので、よく分からず対応したままです。

推論結果

推論に利用したコードは以下のリンクを参照してください。

Juliaでの実装 → gibbs_sampling_regression_julia.ipynb · GitHub

Pythonでの実装 → gibbs_sampling_regression_python.ipynb · GitHub

推論した\mathbf{w}の事後分布p(\mathbf{w} | \mathbf{Y}, \mathbf{X})に基づいて関数を100本サンプルして上記のトイデータのグラフに重ねた結果は以下の通りです。

まずは解析解

f:id:hippy-hikky:20210906151734p:plain

こちらは近似解

f:id:hippy-hikky:20210906151754p:plain

解析解と近似解は(当然ですが)同じような結果になってくれました。上記のリンク先のnotebooには\mathbf{w}の分布も合わせて掲載しており、それを見ても同じような結果になっていることが分かります。

データが少ないところは、関数のバリエーションが広がっているということが分かります。なお、これは予測分布ではないので注意です。

Pythonとの比較

計算速度の比較。

解析解[sec] ギブスサンプリング 10,000サンプル[sec]
Python 0.021 6.91
Julia(コンパイル込み) 6.27 2.10
Julia(コンパイル無し) 0.0003 0.40

コンパイル込み」とは、Jupyterで最初に関数を実行した際の計算時間です。「コンパイル無し」とは、2度目に実行した結果です。(という理解であっているんでしょうか??)

訂正的な比較。

  • Juliaはmatlabぽい
    • 公式ページにも書いてある
  • 行列演算が直接書けるので、数式を比較的そのまま書ける気がする

Pythonの方が仕事でも何年も使っているので当然すんなり書けるのですが、Juliaは数式に近い形でコードを書けるような気がするので、書きやすいかもなーという感じはしました。が、正直Juliaはまだよくわかってないです。

最適化の余地はまだまだあるのでしょうけど、ループが入るような計算は期待通りJuliaがだいぶ速そうです。これは仕事でもJuliaを推していった方が良いかも。

ただ、繰り返しが入らないような単発の処理についてだけ見ると、Pythonの方が処理は速く終わりました。コンパイルしてるからなんでしょうけど。ほんとに単発でしか実行しない処理なら別ですが、パラメータを変えて何度か試すような場合などJuliaで事前にコンパイルしておくと、この点も解消されるのかなと思いました。

【トップに戻る】

おわりに

ということで、Juliaを使って線形回帰モデルを書いてみました。

最適化の余地はどちらもあると思いますが、同程度に雑に書いたJuliaコードとPythonコードの計算時間を比較すると、 forループを使うような処理の場合はコンパイル時間含めてもJuliaの方が速そうでした。ものに依るのでしょうけど。 速度以外にも、Juliaでは行列演算が素直に書けるの良さそうに感じました(matlabみたい)。

【トップに戻る】

参考文献

  1. 須山敦志, 機械学習プロフェッショナルシリーズ ベイズ深層学習, 講談社, 2019

  1. C.M.ビショップ(著), パターン認識機械学習 上 (Pattern Recognition and Machine Learning), Springer, 2007

【トップに戻る】