確率分布からサンプリングしたいが、正規分布や一様分布のようにライブラリが提供されていない場合にどうするか?逆関数法もその1つですが、棄却サンプリングはより直感的な手法です。
棄却サンプリングの原理
確率分布はを満たすべきですが、正規化定数が不明で、
ののみ既知である場合を考え、このからサンプリングしたいとします。もちろん正規化定数も既知でも問題ないのですが、おそらく不明の場合のほうが一般的だろうと思われます。
ここで全てのに対して
を満たす定数およびを定めます。このは与えられたものではなく、サンプリング時に設定するもので提案分布と呼びます。提案分布はサンプリングできるものを選択します。
視覚的には上図のように、を覆うようにおよびを定めます。具体的なサンプリングの手順はとてもシンプルです。
- からをサンプリングする
- の確率でを採択し、採択できなければ再びから新たなサンプルをとり、採択できるまで繰り返す。
手順2によって採択できない、つまり棄却されるサンプル数を少なくするため、提案分布はに近いことが望ましい。また、手順2で確率として解釈できるために、つまり]の範囲であるために式(2)の条件が必要なんですね。この手順によって、がどんな形状であろうとサンプリング可能です。提案分布によってサンプリングされやすい領域があったとしても採択確率が小さければ、つまりそこでが小さければ、棄却されやすくなるだけです。
非常にシンプルでわかりやすい手順です。ああ、こんなことでいいんだって感じ。しかしながら万能ではなく、特に高次元では適切な提案分布を定めることが難しく採択確率が低くなってしまうなどの問題があるようです。
サンプリング実験
さてコードを書いて実験してみました。からサンプリングしてみます。区間は]とします。わかりやすく提案分布は一様分布としました。つまりです。の最大値は1になるので、定数とします。以下のグラフが結果です。
からうまくサンプリングできていますね。
今回のコードです。なお今回はが対象区間で積分すると1になるようにしましたが、そうでない場合は、このコードのままグラフを描くとサンプリングが失敗しているように見えます。最初はコードにミスがあると思って悩んでしまいました。下のグラフはとした場合です。とサンプルのヒストグラムを1つのグラフに描くと形状一致していないように見えてしまいます。
# 棄却サンプリングの実験 import numpy as np import matplotlib.pyplot as plt def p(x): return np.sin(0.5*np.pi*x)**2 # 棄却サンプリング def sampling(): k = 2 # 採択するまでループ while True: # 提案分布q(z)からサンプリング z = np.random.uniform(-1, 1) # [0,kq(z)]の一様分布からサンプリング u = k*np.random.uniform(0, 0.5) # 棄却するか判定 if p(z) > u: return z x = np.linspace(-1, 1, 500) y = p(x) # サンプル数 N = 100000 samples = np.array([sampling() for i in range(N)]) plt.xlim(-1, 1) plt.hist(samples, bins=50,normed=True,alpha=0.5) plt.plot(x, y) plt.show()