《Python貝葉斯分析》


  • 貝葉斯簡介

先驗:體現了對參數的了解,設定參數的分布

似然:引入觀測數據,反映在給定參數下,觀測數據的可信度

后驗:基於觀測數據,獲得參數的分布和可信度

貝葉斯定理:后驗正比於似然乘以先驗

  • 拋硬幣案例

以拋硬幣為例

參數為硬幣朝上的概率A(表示拋一次硬幣時正面朝上的可能性),定義為P(A),此為先驗(此處指的是概率P的概率)

似然為已知該參數A,觀測值B(N次拋硬幣正面朝上的次數)對應的概率,也就是已知硬幣朝上的概率,實驗中出現這個觀測值的概率

后驗則為通過一系列觀測值B,獲得參數A的分布

首先選擇似然分布,此處選擇了二項分布,表示N次拋硬幣實驗y次正面朝上的概率分布

(似然是指的觀測值的可能分布)

(此時二項分布表達式內既包含了參數A,也包含了觀測值B,也包含了試驗次數N)

實際上就是已知硬幣朝上的概率P(參數A),實驗了N次,求實驗中發生y次硬幣朝上(參數B)的概率

其次為參數A選擇先驗分布,其只與參數A直接相關

選用了beta分布,因為其輸出為0到1,與P一致

計算后驗,將似然去乘以先驗

由於只考慮了其正比的比例,所以可以把與A和B不相關的系數都去掉,得到的后驗分布表達式和先驗分布很相似

也是一個beta分布,此時就可以使先驗beta分布的系數和后驗beta分布的系數構成一個等式,后驗的分布就可以使用先驗的系數

  • 注意點

貝葉斯分析的結果是后驗分布而不是某個值,描述了根據給定數據和模型得到不同數值的可能性

后驗最可能的值是根據后驗分布的形態決定的(也就是后驗分布的峰值)

后驗分布的離散程度與我們對參數的不確定性有關,分布越離散,不確定性越大

給定足夠多的數據時,兩個不同先驗的貝葉斯模型會趨近於相同的結果,這個過程也稱之為收斂

不同后驗收斂到相同分布取決於數據和模型

  • 先驗的選擇

使用信息量較小的先驗,比如只使用先驗來限定其取值范圍等等,稱為正則化先驗

可以使用多個先驗來驗證先驗的正確性

  • 最大后驗密度區間(HPD)

包含一定比例概率密度的最小區間,一般有95%HPD或者98%HPD

如果HPD區間為[2,5],其含義是參數位於[2,5]的概率是0.95

在貝葉斯學派中,可以告訴我們參數取值的概率,這在頻率學中是不可能的

因為頻率學認為參數是一個固定值,置信區間只能包含或者不包含參數的真實值

def naive_hpd(post):
    sns.kdeplot(post)
    HPD = np.percentile(post, [2.5, 97.5])
    print(HPD)
    plt.plot(HPD, [0, 0], label='HPD {:.2f} {:.2f}'.format(*HPD), 
      linewidth=8, color='k')
    plt.legend(fontsize=16);
    plt.xlabel(r"$\theta$", fontsize=14)
    plt.gca().axes.get_yaxis().set_ticks([])

    
np.random.seed(1)
post = stats.beta.rvs(5, 11, size=1000)  #根據beta分布(5,11),生成數量為1000的array
naive_hpd(post)
plt.xlim(0, 1)
plt.show()

 

 實際上是找到2.5,97.5概率對應的值即可

  • 后驗預測檢查

一旦獲得后驗分布,就可以根據后驗獲得未來的數據來進行預測

通過比較未來數據和已有數據,可以進行一致性檢查

  •  拋硬幣代碼實現
theta_real = 0.35  #參數真值
trials = [0, 1, 2, 3, 4, 8, 16, 32, 50, 150]   # 實驗次數
data = [0, 1, 1, 1, 1, 4, 6, 9, 13, 48]  # 每次實驗的正面朝上的次數

beta_params = [(1, 1), (0.5, 0.5), (20, 20)]  # beta先驗分布的參數對
dist = stats.beta
x = np.linspace(0, 1, 100)    # 0到1之間均勻采樣100個數,用於計算后驗分布的概率密度
print(x)

for idx, N in enumerate(trials):  # 一次循環表示一次實驗過程
    print(idx)
    print(N)
    if idx == 0:
        plt.subplot(4,3, 2)
    else:
        plt.subplot(4,3, idx+3)
    y = data[idx]   #每一次實驗對應的實驗數據
    print(y)
    for (a_prior, b_prior), c in zip(beta_params, ('b', 'r', 'g')):  # 使用不同的先驗參數,繪制三張圖,分別對應藍色,紅色,綠色
        p_theta_given_y = dist.pdf(x, a_prior + y, b_prior + N - y)  # 將beta先驗分布參數更換為后驗分布的參數(此處就相當於引入了觀測值y及N),並求解x對應的概率密度,此處已經獲得了后驗分布的參數
        print(p_theta_given_y)
        plt.plot(x, p_theta_given_y, c)
        plt.fill_between(x, 0, p_theta_given_y, color=c, alpha=0.6)

    plt.axvline(theta_real, ymax=0.3, color='k')  # 用豎直黑線繪制出參數的真值
    plt.plot(0, 0, label="{:d} experiments\n{:d} heads".format(N, y), alpha=0)
    plt.xlim(0,1)
    plt.ylim(0,12)
    plt.xlabel(r"$\theta$") 
    plt.legend()
    plt.gca().axes.get_yaxis().set_visible(False)
plt.tight_layout()
plt.show()

 

 觀測值B包含了實驗次數(experiments)以及朝上次數(heads)

將值代入后驗分布的參數中,並均勻采樣參數A的可能值導入后驗分布中,即得到了參數A每個可能值的概率密度分布

核心為

p_theta_given_y = dist.pdf(x, a_prior + y, b_prior + N - y)

此處就實現了使用觀測值來更改先驗,使其變成后驗,從而得到參數的可能值分布

(參數的后驗由先驗和觀測值共同組成)

  •  自動的后驗

實際上通過分析方法獲得后驗分布在實際中是很難成立的

通過其他非分析解的方法,也可以對后驗進行計算

非馬爾可夫方法:

網格計算,二次近似,變分方法(用簡單的分布來近似后驗)

馬爾科夫方法:

馬爾科夫鏈蒙特卡洛(MCMC)

蒙特卡洛:一種隨機采樣的方法

馬爾科夫鏈:每個狀態轉移到其他狀態的概率只與當前狀態有關,此狀態鏈就成為馬爾科夫鏈

為了找到細節平衡的馬爾科夫鏈,可以使用Metropolis-Hasting算法采樣到一條鏈

該算法擁有自己的方式(即拒絕和接受的標准,采樣是隨機的,但是接受和拒絕的標准是人為的)(接收新的值的概率正比於新舊值之比)來確定采樣方向,從而得到一條采樣軌跡或者采樣鏈

采樣值就是后驗的近似,出現次數最多的值就是后驗中最可能的值

混合蒙特卡洛方法:避免采樣過慢且結果自相關,需要計算梯度

NUTS算法是其中一種HMC算法,不需要手動設置參數

  • 使用網格算法硬幣案例
def posterior_grid(grid_points=100, heads=6, tosses=9):
    """
    A grid implementation for the coin-flip problem
    """
    # 參數構成的網格(0和1之前構成的網格)
    grid = np.linspace(0, 1, grid_points)
    print(grid)

    # 定義先驗,將5重復網格數次
    prior = np.repeat(5, grid_points)  
    print(prior)
    #prior = (grid  <= 0.4).astype(int)  # truncated
    #prior = abs(grid - 0.5)  # "M" prior

    # 計算每個網格點的似然值,引入可觀測值和網格點
    likelihood = stats.binom.pmf(heads, tosses, grid)
    print(likelihood)

    # 計算似然乘以先驗,似然相當於就是更新先驗的一個參數因子
    unstd_posterior = likelihood * prior
    print(unstd_posterior)
    # 標准化后驗,使其值為1
    posterior = unstd_posterior / unstd_posterior.sum()
    print(posterior)
    return grid, posterior


points = 15
h, n = 1, 4
# 四次實驗只有一次正面朝上
grid, posterior = posterior_grid(points, h, n)
plt.plot(grid, posterior, 'o-')
plt.plot(0, 0, label='heads = {}\ntosses = {}'.format(h, n), alpha=0)
plt.xlabel(r'$\theta$', fontsize=14)
plt.legend(loc=0, fontsize=14)
plt.show()

網格:使用0到1的均布數據

[0. 0.07142857 0.14285714 0.21428571 0.28571429 0.35714286
0.42857143 0.5 0.57142857 0.64285714 0.71428571 0.78571429
0.85714286 0.92857143 1. ]

先驗:定義全為5的均勻分布

[5 5 5 5 5 5 5 5 5 5 5 5 5 5 5]

似然:引入觀測值(此處是引入試驗次數和朝上次數)設定一個似然分布,然后導入網格,即可獲得每個網格值對應的概率分布

[0. 0.22875885 0.35985006 0.41576426 0.41649313 0.37952936
0.31986672 0.25 0.17992503 0.11713869 0.0666389 0.03092461
0.00999584 0.0013536 0. ]

(似然可以看做是更新先驗的一個因素)

后驗:先驗乘以似然來更新先驗獲得后驗

[0. 1.14379425 1.79925031 2.07882132 2.08246564 1.89764681
1.59933361 1.25 0.89962516 0.58569346 0.3331945 0.15462307
0.04997918 0.00676801 0. ]

將后驗歸一化

[0. 0.08239883 0.12961782 0.14975809 0.15002063 0.1367063
0.11521584 0.09004988 0.06480891 0.0421933 0.0240033 0.01113903
0.0036005 0.00048757 0. ]

 

考慮網格數為15,實驗4次,朝上1次

 

 

 考慮網格數為1000,實驗200次,朝上85次

 

 

  •  求圓的面積蒙特卡洛代碼實現

蒙特卡洛主要用於表征隨機性

在變成為2R的正方形內隨機撒N個點

在正方形內畫一個半徑為R的圓,計算在圓內點的個數

(使用關於x,y的不等式來判斷其是否在圓內)

得出pi的估計值

圓內點數除以總點數等於圓面積與正方形面積之比(pi*R*R/4*R*R)

(使用點的個數來代表面積)

N = 10000

x, y = np.random.uniform(-1, 1, size=(2, N))
# 隨機撒點,為中心為原點的正方形
inside = (x**2 + y**2)  <= 1
# 計算位於圓內的點的個數
pi = inside.sum()*4/N
# 換算出pi
error = abs((pi - np.pi)/pi)* 100
outside = np.invert(inside)

plt.plot(x[inside], y[inside], 'b.')
plt.plot(x[outside], y[outside], 'r.')
plt.plot(0, 0, label='$\hat \pi$ = {:4.3f}\nerror = {:4.3f}%'.format(pi, error), alpha=0)
plt.axis('square')
plt.legend(frameon=True, framealpha=0.9, fontsize=16);
plt.show()

獲得pi值

 

  •  Metropolis-Hasting算法代碼實現
def metropolis(func, steps=10000):
    """A very simple Metropolis implementation"""
    samples = np.zeros(steps)
    # 指定一維的0矩陣,作為采樣鏈的初始值
    old_x = func.mean()
    # 計算beta分布的平均值,作為采樣的起始位置
    old_prob = func.pdf(old_x)
    # 計算采樣起始值的概率值,應該具有比較大的概率值,用於后續的比較
    for i in range(steps):   # 一次循環即采樣一次
        new_x = old_x + np.random.normal(0, 1)
        # 以beta分布平均值作為起始值,將正態分布作為更新的偏差值
        new_prob = func.pdf(new_x)
        # 計算采樣值對應的概率
        acceptance = new_prob/old_prob
        # 計算接受概率,即新舊測量值概率之比
        if acceptance >= np.random.random():  #隨機取值作為篩選條件,更新采樣值
            samples[i] = new_x
            old_x = new_x
            old_prob = new_prob
        else:
            samples[i] = old_x
    return samples

np.random.seed(345)
func = stats.beta(0.4, 2)
# 定義了一個指定參數的beta分布
samples = metropolis(func=func)
# 對beta分布進行采樣,返回一條采樣鏈
print(samples)
x = np.linspace(0.01, .99, 100)
y = func.pdf(x)
plt.xlim(0, 1)
plt.plot(x, y, 'r-', lw=3, label='True distribution')
# 真實的參數分布及其對應概率密度函數,也就是最先定義的beta分布
plt.hist(samples, bins=100, density=True, edgecolor='k', label='Estimated distribution')
# 根據采樣鏈,獲得其概率密度分布
plt.xlabel('$x$', fontsize=14)
plt.ylabel('$pdf(x)$', fontsize=14)
plt.legend(fontsize=14)
plt.show()

該算法的作用主要是

在給定一個分布(比如此處的beta分布)后,通過采樣獲得一條采樣鏈,該采樣鏈對應的概率密度分布與原分布基本一致

此處就實現了一個由分布到采樣數據之間的轉換過程

 

  •  基於pymc3的代碼實現
np.random.seed(123)
n_experiments = 4
theta_real = 0.35  
# 生成數據使用的真值,是需要估計的值
data = stats.bernoulli.rvs(p=theta_real, size=n_experiments)
# 按照二項分布及真值生成數據,作為了觀測數據
print(data)

if __name__ == '__main__':
  with pm.Model() as our_first_model:
      # 指定參數的先驗(也就是硬幣朝上的概率)
      # 按照先驗theta來生成采樣鏈
      theta = pm.Beta('theta', alpha=1, beta=1)
      # 定義似然的分布(也就是已知硬幣朝上的概率時,在實驗中硬幣朝上的概率分布,也就是觀測值的可能性),此處傳入了觀測值
      # 似然不會進行采樣,因為其是一個觀測變量
      # 似然表示的是觀測值的可能性,其基於參數和觀測值,作為先驗的更新因子來獲得后驗
      y = pm.Bernoulli('y', p=theta, observed=data)
      #y = pm.Binomial('theta',n=n_experimentos, p=theta, observed=sum(datos))
      start = pm.find_MAP()
      step = pm.Metropolis()
      trace = pm.sample(1000, step=step, start=start)
      # 從最大后驗值處進行采樣,此處獲得是參數theta的后驗采樣trace

  burnin = 100  # no burnin
  # 該參數用於處理老化,因為采樣的開始部分可能效果較差
  chain = trace[burnin:]
  # trace就是獲得的后驗鏈,由之前的采樣鏈計算得到
  pm.traceplot(chain)
  # 每一條采樣鏈的后驗概率密度及其采樣圖像
  pm.forestplot(chain)
  # 每一條采樣鏈的森林圖,表示了其一定的HDI區間
  pm.autocorrplot(chain)
  # 每一條采樣鏈的自相關圖像
  pm.plot_posterior(chain)
  # 后驗的hpd曲線
  plt.show()
  print(pm.summary(chain))
  # 獲得后驗的詳細參數

指定參數先驗

指定似然分布

對先驗進行采樣

計算出后驗

每一條采樣鏈的后驗概率密度及其采樣圖像

每條采樣鏈的森林圖

 

 

每一條采樣鏈的自相關圖像

 

 后驗的所有信息

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM