これからマルコフ連鎖モンテカルロ法を勉強していこうと思っています。今回はその第一歩。モンテカルロ法というのは何か特定の手法を表すのではなく、乱数を用いて数値計算すること全般を指すようです。ですのでモンテカルロ法による積分とは、乱数を用いて積分を数値計算する、ということです。
が確率分布に従うなら、期待値は
と書けます。いま、ある関数の積分を考えます。区間]で一様分布する確率密度関数は、
ですから、これを使って
と変形できます。はに依存しませんから積分の外に出せます。積分から一部を出して一部を残すっていう不思議な式変形でちょっと戸惑いそうです。
さて、大数の法則*1より、が十分大きければサンプル平均は期待値に近づいていきますから、
と近似できます。ここではからサンプルした値です。よって、積分区間で一様分布するからサンプルをとることができれば、の積分が期待値計算により求められることになります。この考え方で積分の数値計算をする方法をモンテカルロ積分と呼びます。最初に知ったときはなるほどなあと随分感心してしまいました。
さて、モンテカルロ積分で
を求めてみます。以下の図のような関数、積分区間です。 区間]で一様分布する確率密度関数を用いて、
です。の値を変化させて、式(7)の解:との誤差を確認してみました。
きちんと近似できていることが確認できました。サンプル数が2桁増えると誤差が1桁減るような傾向です。
今回のコードです。サンプル数が少ないと値が安定しなかったので、繰り返し計算して平均を求めています。対数グラフで綺麗な直線が見たかったので繰り返し数は多め。
# モンテカルロ積分テスト import numpy as np import matplotlib.pyplot as plt # 積分対象の関数 def f(x): return np.sqrt(2*x+1) # Nサンプル発生させて積分を近似計算し、真値との誤差を計算(積分の真値は26.0/3.0) def err(N): p = np.random.uniform(0, 4, N) return np.abs(4*np.average(f(p)) - 26.0/3.0) # ランダムシードを固定 np.random.seed(0) # M回誤差を計算して平均をとる M = 100 error = [] # サンプリング数のリスト N = [10**i for i in range(2, 9)] for i in N: # 計算時間調整 # if i >= 1e5: # M = 10 error.append(np.average([err(i) for j in range(M)])) plt.yscale("log") plt.xscale("log") plt.grid(which="both") plt.xlabel("N") plt.ylabel("error") plt.plot(N, error) plt.show()
*1:大数の法則は弱法則、強法則とあってややこしい。高校の数学で習う程度の理解で困ることはなさそうか?いつかきちんと理解したい