モンテカルロ法による積分
このページでは、乱数を使った「計算」の例として、モンテカルロ法による数値積分を考える。
超球の体積の計算
誰でも知っているとおり、半径1の円の面積は, 半径1の球の体積は である。
これをさらに一般化して、 次元の半径1の球の体積は、
で与えられる。
ここではガンマ関数である。
例えば、の場合は、であり、次元の増加とともに、は一旦増加するが、以上では減少に転じる。
もしこの公式を知らなかったとしたら、次元球の体積をどのように見積もったら良いだろうか。
例えば、(二次元中の)半径1の円を考えてみよう。
そして、その面積をとする。
に0から1の範囲の一様乱数、も同様とすると、点は面積1の正方形の中に一様に分布するはずである。
そして、そのうちで、角度の扇形 に入っているものの割合は、正方形と扇形の面積の比率、すなわちとなるだろう。
そうすると、個の点を生成し、そのうち扇型に入っているものをカウント数した結果がとすれば、
すなわち、
と求まる。
ここでの発想のポイントは、ひとつには、面積が既知の図形(正方形)を基準にカウントすること、加えて、乱数の分布の一様性にある。
全く同様の発想で、次元の球の体積を求めることができるはずだ。
辺の長さが1の次元の立方体を考えると、その体積は1である。
その中に占める、次元球のの区画の割合から、超球の体積を推定するコードの例を以下に示す:
# coding: utf-8
import numpy as np
import math
d = 3
N=100000
cnt=0
for _ in range(N):
x = np.random.rand(d)
if np.linalg.norm(x) < 1:
cnt += 1
v = cnt/N * 2**d
print('v=',v)
このコードの中で、NumPyのnp.random.rand(d)
関数は、0から1の範囲の一様乱数をd
個生成し、そのリストを返す。
また、np.linalg.norm(x)
は、リストx
で与えられるベクトルの長さ(L2ノルム)を与える関数である。
練習:球の体積
実際に上のコードを動作させ、数学的な(厳密な)結果と比較してみなさい。
解説: 結果のゆらぎ
超球の体積をとすれば、ランダムに生成された点が超球の内部に位置する確率はである。
回のサンプリングを行った場合、点が超球の内部にある回数は二項分布で与えられるので、その平均は、、分散はである。
すると、体積の推定値のゆらぎは
となる。ここで、ゆらぎ(誤差)の依存性はには依存しない。
高次元での積分を、各次元ごとに個のデータ点を使って計算しようとすると、データ点の総数は程度で増大することになる。
例えば、台形法を使った場合の誤差は程度となるので、
であり、次元数が大きくなると、同じ程度の計算精度を担保するに必要となるは膨大となる。
言い換えると、高次元の問題になればなるほど、データ点数の観点からは、次元に関係なく程度で誤差(ゆらぎ)を減らすことができるモンテカルロ法が有利となってくる。
重点サンプリング
積分
の計算をモンテカルロ法で行う別の方法を考えてみよう。
ある素性の良い確率分布(提案分布)を用いて、
のように置き直すと、この積分は についての の平均を求める計算に帰着される。
よって、 の生成確率で乱数 を発生させることによってをサンプルすれば、
中心極限定理から
によって積分の近似値を得ることができる。
この方法は、による確率的な荷重によってサンプリングを行うことから、重点サンプリング(importance sampling)と呼ばれている。
いわば、提案分布を「ものさし」にして積分を評価するわけである。
では、上記の線に沿って 次元の超球の体積を求めてみよう。
まず、半径1の球を表すために、原点からの距離に依存した分布
と考えると、この体積積分が超球の体積となる。
他方で、提案分布としてガウス分布
を考えてみる(平均0、分散)。
そして、の確率密度を持つような次元の正規乱数を発生させ、
ならば
の、ならば の重み付けを行って、点をサンプルすることにしよう。
このとき、サンプル数総数をとし、を番目の重みとすれば、の積分、すなわち半径1の超球の体積 は
で求められる。
# coding: utf-8
import numpy as np
import math
def normal(r,d):
return math.exp(-r**2) / math.sqrt(math.pi**d)
d = 10
N=100000
wt=0.0
for _ in range(N):
x = np.random.normal(loc=0, scale=1/math.sqrt(2), size=d)
r = np.linalg.norm(x)
if r < 1:
wt += 1/normal(r,d)
v = wt / N
v0 = math.pi**(d/2)/math.gamma(d/2+1)
print('v=',v,v0)
解説: 高次元でのサンプリングの難しさ
上記のサンプル方法でどの程度効率的に積分が評価できるか考察してみよう。
簡単のため1次元の場合をまず考えて、 と置いてみる。
そして、サンプリングの効率(誤差)の指標として、 の分散を見積もることにする。
の平均(すなわち1次元球の体積)は、
である。
他方で、の二乗平均は
となる。
これから、標準偏差の関数としての分散
を数値計算してプロットすると、以下のようになる。
この値が小さいほど、より「的確」なサンプル値が得られるため、少ないサンプル回数でも良好な推定値が得られると期待できる。
プロットからは、には最適値があり、それを外れると分散が急激に増大することがわかる。
そして、最適なとは、提案分布が、になるべく近いかたちになるような値、と解釈できる。
次元の場合にも全く同様の議論が可能であって、
は次元球の体積 , すなわち
の二乗平均は、被積分関数が方向に依存しないことから、動径方向のみの積分を考えれば十分で、
となる。
これらを用いて、の相対的なゆらぎの指標
をの場合について計算し、プロットした例を以下に示す。
この様子からは、提案分布の選択の仕方によって、効率(計算誤差)は大きく左右されるだろうと予想される。
さらに、この傾向は次元の増大と共に一層顕著となる。
練習:サンプリング効率の比較
超球の体積を求める際、次元が大きくなると、サンプル点が球の内側に入って「採択」される割合が急激に減少するため、著しく計算の効率と精度が低下してしまうことが知られている。
重点サンプリングで体積を求めるコードの例では、
提案分布として分散が1/2の正規分布を用いたが、その分散を変えられるようにし、計算の効率や精度が分散の大小によってどのように変わるか実験してみなさい。