ハミルトニアン・モンテカルロ(HMC)法のざっくりとした解説とPythonによる実装
ベイズモデリングが流行っている中で多くのRユーザーはStanを使って解析をしているんではないかと思います。そして、Stanはハミルトニアン・モンテカルロ(HMC)法と呼ばれる方法で事後分布からのサンプルを得ています。色々と解説記事はありますが、超ざっくりとHMCの原理をメモとして残しておくことにします。
ここで、基本的に私はHMCを伊庭先生のハミルトン4.pdf - Google ドライブこの資料で勉強したので、伊庭先生の資料を読めば私のこの記事は必要ないことをあらかじめ断っておきます((´^ω^)
なんでこれでいいの?という疑問は伊庭先生の資料で解決することでしょう((´^ω^)
記号の定義
まず、以下のように記号を定義します:
: データ
: 次元パラメータベクトル。は転置を表します。
: 事後分布(後にが別に出てくるので、ここではとしました)
上記の事後分布からHMCでサンプリングをすることを考えます。
ハミルトニアンとハミルトン方程式
まっっっったく詳しくないのですが、ハミルトニアンとは運動エネルギーとポテンシャルエネルギー(位置エネルギー)の和で定義される以下の物理量のことです。ここではと同じ次元のベクトルとします:
また、ハミルトン方程式と呼ばれるものが以下のように表現されます:
物理の言葉を借りると、は運動量ではある物体の位置を表しています。上記のハミルトン方程式はある物質に運動する力を与えてやることで、その物質の位置が変化するという現象を表したモデルになっています。このアナロジーは以下の説明でHMCを理解する上でとっても役立ちます。
さて、このとき以下のようにとの同時確率密度関数をハミルトニアンだけの関数だけの関数、つまり
という形で定義したとき、上記のハミルトン方程式に従っての値との値を変化させれば、それは確率密度関数からのサンプリングをしたこととみなせるという素晴らしい性質を持っています。ここがHMCのポイントだと思います。
ハミルトニアン方程式の解き方
一般に、リープ・フロッグ法と呼ばれる方法で解くようです。ここで次のように記号を導入します:
:番目のステップにおけるの値
:番目のステップにおけるの値
また、次のようにパラメータを導入します:
:状態変化を行う回数
:1ステップで状態変化をどの程度許すか
詳しい解説はグーグル先生にまかせて、以下に計算の流れを記します:
===========================================================
Step 0. 、またとに初期値を設定。
Step 1. 以下のようにを更新:
Step 2. 以下のようにを更新:
Step 3. 以下のようにを更新:
Step 4. としてStep. 1 - Step. 4をになるまで繰り返す。
===========================================================
ここで、ステップという普段見ない表記を導入していますが、中間のステップということを表現したいがために便宜的に導入したものです。
具体的なの形
上記のハミルトニアンの右辺に出てくるとを、具体的に以下のように指定してやります:
またのについて、指数関数の逆数
を指定してやります。
このときは以下のように:
上記の分布をよくみてください。平均0、分散の正規分布と目的の事後分布の掛け算になっています。掛け算で表現されるということは、つまりとは独立しているということです。
さて、はハミルトニアン方程式の状態変化の定常分布でした。加えて、上記の式をみるとは平均0、分散の正規分布に従います。つまり、とは独立にを平均0、分散の正規分布からサンプリングし、その値を用いてハミルトン方程式を解くことで新たなの値を得ます。
ここで、ハミルトン方程式と解く際に、現在のの値から状態を変化させるために以下のようにリープ・フロッグ法のStep 0. を変更します:
===========================================================
Step 0.
と設定
に平均0、分散の正規分布から発生させた値を設定
を現在のの値を設定
Step 1. 以降は上で述べた方法と同じで、ハミルトン方程式を解いて新たなを得ます。
===========================================================
以上の流れで得たを目的の事後分布からのサンプルとみなしたいところですが、上記のハミルトン方程式をリープ・フロッグ法を使って得ということは、コンピュータで”近似的”に解くということで、厳密解ではありません。そこで、ハミルトン方程式を解いて得たを、目的の分布からのサンプルの”候補”とみて、メトロポリス法のように以下のようなサンプルの受理・棄却のステップを設けてやります。ここでとを現在のの値、とをハミルトン方程式を解いてとの状態変化を行った後の値とします:
===========================================================
確率 で状態変化後の値に移動
===========================================================
このように、ハミルトン方程式を決定論的に値を決定するパートと上記の受理・棄却パートのように確率的に値を決定するパートがあるので、HMCはハイブリッド・モンテカルロと呼ばれたりもしています。
HMCのパラメータ
今まで見てきた中で、分析者が指定しなければならないパラメータが以下のように3つあります:
:運動量が従う正規分布の分散
:リープ・フロッグ法でハミルトン方程式を解くときの状態変化の回数
;リープ・フロッグ方でハミルトン方程式を解くときの1ステップあたりの状態変化の大きさ
を大きくすれば0から離れた値のが出やすくなり(物理的にはの運動が大きくなり)、それにともなっての値は大きく変わりやすく(物理的にはの位置が大きく移動しやすく)なります。の値を大きくすれば、状態変化をする回数が多くなるということなので、前の値のから異なる値へより変化していきます。の値を変更すると、状態変化の具合が変わります。
Stanに実装されているNUTSという手法は、上記のパラメータを自動化するもので、HMCの敷居を大きく下げました。
Pythonによる実装
PythonでHMCを実装します。
今回は、簡単に平均100、分散の正規分布からサンプリングをしたいと思います。
pythonのコードは以下のようになりました:
%matplotlib inline import numpy as np import pandas as pd import matplotlib.pyplot as plt # Log probability # ここでは平均mu, 標準偏差sigmaの正規分布 # -1をかけると物理でいうポテンシャルエネルギー def log_normal(x, mu, sigma): return -0.5*np.log(2*np.pi*sigma**2) - (x-mu)**2/(2*sigma**2) # Log probabilityの導関数 def d_log_normal(x, mu, sigma): return -(x-mu)/sigma**2 # 運動エネルギー def momentum(p, tau): return p**2/(2*tau**2) # 運動エネルギーの導関数 def d_momentum(p, tau): return p/tau**2 # ハミルトニアン # 運動エネルギーとポテンシャルエネルギーの和 def Hamiltonian(x, p, tau): global mu, sigma return momentum(p, tau) + (-1.*log_normal(x, mu, sigma)) # リープ・フロッグ法を事項する関数 def proceed_leapflog(epsilon, x, p, tau): global mu, sigma x += -0.5*epsilon*(-1.*d_momentum(p, tau)) p += epsilon*d_log_normal(x, mu, sigma) x += -epsilon*(-1.*d_momentum(p, tau)) return x, p # HMCを1ステップ実行するサブルーチン def proceed_HMC_iteration(x, tau, epsilon, T): p = np.random.normal(0, tau, size=1)[0] p_new = p x_new= x for t in range(T): x_new, p_new = proceed_leapflog(epsilon, x_new, p_new, tau) alpha = np.exp(Hamiltonian(x, p, tau) - Hamiltonian(x_new, p_new, tau)) u = np.random.uniform() if u < alpha: x_accepted = x_new else: x_accepted = x return x_accepted # HMCを実行する関数 def proceed_HMC(tau, epsilon, T, ite, init): # Initialize x = [init] for i in range(ite): x.append(proceed_HMC_iteration(x[i], tau, epsilon, T)) return x
以下のように目的の正規分布の平均を初期値と設定して実行してみます。
init = 100 theta = proceed_HMC(tau=2, epsilon=0.1, T=10, ite=2000, init=init) plt.plot(theta)
ちょっと自己相関が高い感じになってるのでリープ・フロッグ法でハミルトニアン方程式を解く際のステップ数を30にして、もっとの状態を変化を待ってみましょう。
init = 100 theta = proceed_HMC(tau=2, epsilon=0.1, T=30, ite=2000, init=init) plt.plot(theta)
自己相関が小さくなった気がしますね。
ここでちょっと意地悪をして、初期値の値を目的の分布のテールがほとんどない0からスタートしてみます。
init = 0 theta = proceed_HMC(tau=2, epsilon=0.1, T=30, ite=5000, init=init) plt.plot(theta)
すごいぜHMC。メトロポリス・ヘイスティングではこうはいかないと思います。
最後に
ベイズ推論をするにあたって、多くの場合で事後分布が解析的に解けないために事後分布に従う乱数(事後サンプル)を発生させ、それ使用して色々な分析をします。よく聞くマルコフ連鎖モンテカルロ(MCMC)法は、事後サンプルを得る方法群の名前です。ギブスサンプラーやメトロポリス・ヘイスティング法といった色々な種類のMCMC法が存在します。ベイズ推論において、Stanに実装されているHMCも事後サンプルを得るために使われる一つの方法です。要はどんな方法であれ、最終的に手元に"理論的に正しい"事後サンプルが手に入っていれば解析ができるので、それでよいのです。ただしそれぞれの方法には特徴があって、問題に合わせて使い分ける必要があるように思います。
世の中にはStanやBUGS、JAGSといったベイズ推論を実行することのできるとっても便利な言語があって、我々はそれらを使ってすぐにベイズ推論を行うことができるようになっています。モデリングの試行錯誤も楽ちんです。一から実装していては、モデルが変わるたびにコードを書き直さなければならないので試行錯誤が大変です。ただし、例えば最終的に構築されたモデルをに対してギブスサンプラーが構築できる時にはギブスサンプラーを使ったほうがサンプリング時間が圧倒的に短く、収束も早いことがあります。昔、以下の本の内容をStanで実装していて思い知らされました(この本の著者はマーケティング分野ではかなり有名な研究者です)。
Bayesian Statistics and Marketing (Wiley Series in Probability and Statistics)
- 作者: Peter E. Rossi,Greg M. Allenby,Rob McCulloch
- 出版社/メーカー: Wiley
- 発売日: 2005/12/09
- メディア: ハードカバー
- クリック: 4回
- この商品を含むブログ (7件) を見る
Stan便利だぜ、すげえぜ、と思いますが、他の方法の方が良いこともあるということは頭に入れて置かなければなりません。