馬爾科夫鏈蒙特卡洛算法(python)


參考文獻:

本文參考瞭如上的文獻、視頻,以下圖片部分來自於參考文獻截圖

1 蒙特卡洛算法

1.1 基本思想

蒙特卡洛方法是在計算總體均值、總體方差、總體分位數等數字特徵時,有時由於計算複雜性難以計算,於是採用樣均值、樣本方差、樣本分位數來估計相應總體數字特徵的一種方法。總的來說就是採用樣本估計總體的一種統計方法

1.2 蒙特卡洛積分

1.2.1 求π\pi

隨機在正方形上撒點,落在圓中的概率爲圓的面積除以正方形面積,即爲p=π/4p=\pi/4,所以π=4×p\pi=4\times p
在這裏插入圖片描述

  • 使用蒙特卡洛計算的code
import numpy as np

samples = 1000
x = np.random.uniform(-1,1,samples)
y = np.random.uniform(-1,1,samples)
counts = len(np.where(x**2+y**2<1)[0])
p = counts/samples
pi_out = 4*p
print(pi_out)

(注意我沒有采用for循環,因爲for太慢了,我直接使用數組來計算會極大提高速度)
在這裏插入圖片描述
樣本越大誤差越小,誤差跟1/samples1/\sqrt{samples}成正比

1.2.2 求積分

1.2.2.1 一維積分

看一下步驟:
在這裏插入圖片描述
上面實際上就是將積分變爲求和,我們跟梯形面積法求積分對比一下,
在這裏插入圖片描述
梯形面積法求積分如下
abf(x)dxi=1nf(xi)Δxi\int_a^bf(x)dx\simeq\sum_{i=1}^nf(x_i)\Delta x_i
這裏Δxi\Delta x_i表示步長, 不難發現,蒙特卡洛撒點積分的方法相當於(ba)/n=Δxi(b-a)/n=\Delta x_i,表示每個步長是一樣的,即對應的梯形中的高是一樣的.(將積分區間等分)。但是值得注意的是,這裏的xix_i(即第i個x的值)是隨機撒點的,所以會出現比較大的誤差(比如撒的點集中在0附近,那麼值就會偏小;集中在1附近,值就會偏大),而我們常規的小面積代替積分的方法中,xix_i是遞增的,因此不會存在點集中在某個值附近的情況,這使得我們的誤差會比蒙特卡洛撒點的方法小。不過,如果撒的點很多,也就是nn\sim\infty,那麼從大數定理出發,得到的值是跟真值一樣的。

1) 先計算一個簡單的積分
S=01xdxS=\int_0^1xdx
我們可以使用蒙特卡洛數值撒點(隨機撒點),那麼上面變爲求和
Sxi=01xiΔxi=(10)/n×i=0nxiS\simeq\sum_{x_i =0}^1x_i\Delta x_i=(1-0)/n\times \sum_{i=0}^nx_i
其中n是樣本數,
以下使用三種方法實現積分。順序分別對應:蒙特卡洛法,梯形面積法和調用函數(調用的正是蒙特卡洛程序)第三個跟第一個程序是一樣的。
python code實現如下:

import numpy as np
import mcint
#蒙特卡洛撒點
samples = 1000
x = np.random.uniform(0,1,samples)  #x 是0,1之間隨機撒點
S = (1-0)/samples*np.sum(x)
print(S)
#梯形面積積分
x2 = np.linspace(0,1,num=samples)  # x2是遞增的
delta_x = 1./samples
S2 = delta_x*np.sum(x2)
print(S2)

#mcint
def integrand(x):
    return x

def sampler():
    while True:
        x = np.random.random()   #生成(0,1)的數
        yield x

result,error = mcint.integrate(integrand,sampler(),measure=1.0,n=samples)
print(result,error)

在這裏插入圖片描述
第一個方法跟解析解的真實值0.5很接近,而第二個方法完全相等,這是因爲y=x本來就是一條斜線,構成一個梯形,所以samples=2也會使得第二個方法=0.5。因此對於一維積分而言,後者比蒙特卡洛隨機撒點更好。
對mcint這個函數做一個簡單解釋,具體可以看上面的鏈接。measure是你要積分的範圍,n是樣本數(默認=100),integrand是被積函數,sampler()是樣本生成器(因爲使用的是yield)
關於誤差跟樣本數之間的關係,如下(參考從馬爾可夫鏈到蒙特卡洛-Metropolis方法(Python)):
在這裏插入圖片描述
2)計算積分
S=24x2dxS=\int_{-2}^4x^2dx

import numpy as np
import mcint

#蒙特卡洛
samples = 1000
x = np.random.uniform(-2,4,samples)
S = (4+2)/samples*np.sum(x**2)
print(S)

#梯形面積法
x2 = np.linspace(-2,4,num=samples)
delta_x = (4+2)/samples
S2 = delta_x*np.sum(x2**2)
print(S2)

#mcint
def integrand(x):
    return x**2

def sampler():
    while True:
        x = np.random.uniform(-2,4)   #生成(-2,4)的數
        yield x

result,error = mcint.integrate(integrand,sampler(),measure=6.0,n=samples)
print(result,error)

生成三次,可以看到,梯形面積法最接近真實值(24)最接近,誤差最小(因爲它的誤差1/n2,1/n\sim 1/n^2,而蒙特卡洛法\sim 1/\sqrt{n})。原因也說過了,梯形面積法中xix_i是遞增的,因此是均勻的,只有當蒙特卡洛撒點是足夠多的時候纔會出現各處分佈均勻的情況。
在這裏插入圖片描述
調用函數mcint有一個要求,就是你必須知道積分區間,即上面的measure。

1.2.2.2 高維積分

具體步驟如下
在這裏插入圖片描述
上面可以看到,要使用蒙特卡洛求多維積分,首先要知道多維積分區間構成的體積VV(二維就是面積),也就是直接令被積函數爲1,然後求積分。有時候這個體積跟原被積函數一樣難求,那麼就不能用蒙特卡洛了。所以這種情況針對的是VV很好求的情況,比如正方體,扇形,柱體等等。
1)計算積分
0101y2(x2+y2)dxdy\int_0^1\int_0^{\sqrt{1-y^2}}(x^2+y^2)dxdy
1.1)使用蒙特卡洛方法
這個積分區間不難看出是1/4圓的面積(x正的那部分),所以實際上我們知道V=π/4V=\pi/4.也就是
0101y2dxdy=π/4\int_0^1\int_0^{\sqrt{1-y^2}}dxdy=\pi/4
當然,不知道=π/4\pi/4也沒事,可以很快積分出來。首先令被積函數爲1,那麼
V=0101y2dxdy=011y2dyV=\int_0^1\int_0^{\sqrt{1-y^2}}dxdy=\int_0^1\sqrt{1-y^2}dy
(實際上這裏已經很容易看出來是函數1y2y1/4\sqrt{1-y^2}與y軸的構成的面積,即1/4圓。)上面的積分使用一個技巧,(很多鏈接可以找到這個積分方法,如求不定積分)。
由於根號下要求爲正,而1y21-y^2又小於等於1,所以1y21-y^2要求在[0,1]區間中,這讓我們想起了三角函數,所以令y=sinθ,θ(π/2,π/2)y=\sin\theta,\theta\in(-\pi/2,\pi/2),所以
V=01cos2θdθ=...=y1y201+1/2arcsin(y)010.7854V=\int_0^1\cos^2\theta d\theta=...=y\sqrt{1-y^2}\big|_0^1+1/2\arcsin(y)\big|_0^1\simeq0.7854
上面我直接給出了積分的結果。
使用蒙特卡洛積分法的code如下:

import numpy as np

#蒙特卡洛撒點
samples = 100
y = np.random.uniform(0,1,samples)
x = np.random.uniform(0,1-y**2)
V = 0.7854
S = V/samples*np.sum(x**2+y**2)
print(S)

在這裏插入圖片描述
1.2)使用梯形求和法

ans = 0
step_x = 0.01
step_y = 0.01
for y in np.arange(0,1,step_y):
    for x in np.arange(0,y,step_x):
        ans = ans + (x**2+y**2)*step_x*step_y
print(ans)

在這裏插入圖片描述
我上面使用兩層for是爲了看得更加明白,實際上建議使用數組,計算更快。
1.3)調用mcint積分
再次說明,這跟蒙特卡洛是一樣的

import numpy as np
import mcint

samples = 10000
def integrand(x):
    return (x[0]**2 + x[1]**2)

def sampler():
    while True:
        y = np.random.random()
        x = np.random.random()
        if x**2+y**2 <= 1:
            yield (x,y)

result, error = mcint.integrate(integrand, sampler(), measure=np.pi/4,n=samples)
print(result,error)

在這裏插入圖片描述

1.3 蒙特卡洛期望估計

如一開始所述,蒙特卡洛的最大運用並不是在積分上,而是在使用樣本估計總體的數字特徵上,比如求期望。這在機器學習等方面有很大應用。
在這裏插入圖片描述
上面的步驟具體如下:

  • 1, 如果樣本服從某個概率分佈p(x)p(x),那麼通過概率分佈p(x)p(x)來撒點,比如服從均勻分佈,則用rand.uniform()撒點(得到的點有相同的概率),服從高斯分佈則用高斯分佈器來撒點。
  • 2, 求這些點的期望,這個估計的期望用來代替真實的期望值。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章