我自己是數學菜逼,所以我在學習數學之類的內容的時候,我基本上會去找視頻看,雖然視頻比較耗時間,但數學真的很難,沒辦法,菜逼一個。好在在b站上找到一位數學老師有這個視頻講解,真的救命呀!!!放下視頻鏈接https://www.bilibili.com/video/BV17D4y1o7J2?p=1&vd_source=4451d7e9f1ccf3c1318002d60666d680。下面記錄一下筆記。
蒙特卡洛積分主要原理是通過隨機抽樣來估算。假設我們有一個概率密度(分佈)函數如下,是某學校女生身高的分佈
假設我們想算身高平方的均值,f(x)是概率密度函數,g(x)=x2,身高平方的均值也就是
但是因爲這個積分解析式不一定能解出來,我們可以在概率密度函數的分佈中抽取樣本X,也就是在某學校女生中抽樣,然後計算g(X),進而算術平均
但在數學中如何找到可以服從概率密度分佈的樣本呢,這裏需要用到累積分佈函數
由於累積分佈函數從0到1,它的函數圖像如下
我們在累積分佈函數的縱座標上隨機取一個數i,找到它在橫座標上對應的值xi,這個xi就是符合概率密度函數的樣本了,xi的求解也就是累積分佈函數的逆函數了,這個也就是重要性採樣了
到這裏就可以解出來了。
我們經常看到在圖形學中的渲染公式中那一長串的被積函數,然後就突然變成了一長串除以pdf了,那這個除以pdf是怎麼來的呢?
假設我們要求f(x)在(a,b)上的定積分(面積),g(x)是一個概率密度函數,我們可以把這個式子變形
我們可以把f(x)/g(x)看成一個整體z(x),那這樣不就是對應我們前面的列子了嗎,就相當於變成了求z(x)的均值了,然後我們就可以採樣得出結果了,有一些細節就是概率密度函數的積分爲1,也就是
然後我們的式子就變成這樣了
對,這樣就變成了我們經常看到的除以pdf的那種樣子了。
下面舉一個實例,求解y=x2在(0,8)之間的面積,用定積分的解析式很好求,但這裏用蒙特卡洛重要採樣來解積分。首先pdf是隨便的,這裏取概率密度函數p(x)=3/8*x2,儘量貼合原積分函數,然後我們求出累積分佈函數P(x)=x3/8,按照上面的講解我們求出累積分佈函數的逆函數P-1(x)=2*x1/3。代碼中只採樣了一個點,但得到的結果還是比較準確的
#include <iostream> #include <random> #include <iomanip> double random_double(double min, double max) { // 使用rd來生成一個真正的隨機數生成器 std::random_device rd; // 使用該隨機數生成器初始化Mersenne Twister引擎 std::mt19937 engine(rd()); // 創建一個分佈,在min和max之間均勻分佈 std::uniform_real_distribution<> distrib(min, max); // 生成一個均勻分佈的隨機數 return distrib(engine); } inline double pdf(double x) { return 3 * x * x / 8; } int main() { int N = 1; auto sum = 0.0; for (int i = 0; i < N; i++) { //這裏就是用累積分佈函數的逆函數來求解符合概率密度函數的分佈 auto x = 2*pow(random_double(0, 1), 1.0 / 3.0); sum += x * x / pdf(x); } std::cout << std::fixed << std::setprecision(12); std::cout << "I = " << sum / N << '\n'; }
結果如下: