PyMC3 概率編程入門

第一部分 編程準備

  1. 貝葉斯思維:和更傳統的統計推斷不同,貝葉斯推斷會保留不確定性,在貝葉斯派的世界觀中,概率是被解釋爲我們對一件事情發生的相信程度或者說信心(飛機事故,總統選舉)。

    需注意的是,我們每個人都可以給事件賦概率值,而不是存在某個唯一的概率值,因爲不同的人擁有不同的信息,因此他們對同一事件發生的信心也可以有不同的值,但這些不同並不說明其他人是錯誤的。

    • 飛機事故:綜合某航空公司過去十年出現飛機事故的次數,來推斷該公司出現飛機事故的概率;
    • 總統選舉:根據候選人的聲望、民意等等,推斷他能當上總統的概率。
  2. 貝葉斯推斷的工作方式:我們會隨着新的證據不斷更新之前的信念,但很少做出絕對的判斷,除非所有其他的可能都被一一排除(代碼 bug)。

    • 代碼 bug:“我的代碼通常會有 bug”,這是我的先驗信息,我現在要來驗證是否有 bug,於是我測試了一個簡單的案例,這個案例通過後,我再用一個稍微複雜的測試案例,再次通過了,接下來更難的測試案例也通過了,通過這一連串的測試,我開始覺得我的這段代碼很可能已經沒有 bug 了,於是我得到我的後驗概率,“我的這段代碼有 80% 的可能沒有 bug”。
  3. 相關的 Python 第三方庫安裝

    • Ipython
    • Numpy
    • Scipy
    • Pandas
    • Matplotlib
    • Seaborn
    • PyMC3
  4. Jupyter Notebook 安裝及配置使用

第二部分 概率編程入門實例——貝葉斯點估計

我們來以《貝葉斯統計》這本書是的一個習題作爲我們入門的實例,因爲學習 PyMC3 必然是要落地的,通過書上的問題能幫我們迅速拉進和 PyMC3 的距離,這裏選出第四章貝葉斯統計推斷中的關於貝葉斯點估計的習題來看一下。

1 觀察問題

在這裏插入圖片描述
這是第四章習題中的第 10 題,這裏把題目截圖放在這裏,我們看一看這個題目,它有五個樣本 X1 到 X5,而且已經有了觀測值然後這些樣本服從指數分佈 Exp(1 / θ),而 θ 的先驗分佈是逆伽馬分佈 Γ-1(10,100),然後我們要求什麼呢,我們要求參數 θ 的廣義最大似然估計(這裏後驗均值估計以及後驗方差就不求了),好,題目是這個樣子,我們現在使用編程來解決這個問題。

2 確定觀測數據

第二步,導入第三方庫,以及確定觀測數據:

import pymc3 as pm

X = [5, 12, 14, 10, 12]

3 定義模型與變量

接着第三步,定義模型,我們使用 pm.Model() 方法,以及 with 上下文管理器將該代碼塊中的變量都添加到 my_first_model 模型:

with pm.Model() as my_first_model:
    
    theta = pm.InverseGamma('theta', alpha=10, beta=100)
    X_obs = pm.Exponential('X_obs', lam=1 / pm_theta, observed=X)

然後我們定義 θ 隨機變量,它是逆伽馬分佈,PyMC3 也提供了對應的 API —— InverseGamma 類,這是對應的網址:https://docs.pymc.io/api/distributions/continuous.html#pymc3.distributions.continuous.InverseGamma,我們點進去看一下,界面如下面截圖所示,我們可以發現 InverseGamma 類有很多參數,但是隻有前兩個參數 alpha 和 beta 是我們需要傳入的,它們分別對應分佈中的 α 和 β,後面的這些參數都是可選的,不過對於它們我還並不太瞭解,需要再研究一下(老師對此問題進行了講解),後面的這些參數中,mu 參數表示逆伽馬分佈的期望,sigma 參數對應分佈的標準差,因爲 α 和 β 與分佈的期望和標準差之間可以通過計算進行轉換,因此當傳入參數 alpha 和 beta 後,就不必再傳入參數 mu 和 sigma,同理,反過來也是一樣,所以稱參數 mu 和 sigma 是可供替代的參數,還有一個參數 sd,這個參數其實也代表標準差,不過當它有傳入進來的值時即不爲 None 時,優先選擇 sd 而不是 sigma 作爲標準差。
在這裏插入圖片描述
還有一點需要注意的是,InverseGamma 類還有一個隱藏的必填參數,那就是 name,我們可以看到如果僅僅傳入 alpha 和 beta 參數運行時會出現報錯:
在這裏插入圖片描述
報錯信息告訴我們 __new__() 缺少一個參數,但是 InverseGamma 類的 __init__() 函數明明沒有 name 這樣一個必選參數啊,爲什麼會出現這樣的報錯信息,其實這是因爲 InverseGamma 類是一個多重繼承的類,它最開始的父類是 Distribution 類,這個類有一個 __new__ 函數,需要一個必填的字符串型的 name 參數,而 __new__ 函數會在 InverseGamma 類實例創建之前被調用,它的任務就是創建實例,此時缺少 name 參數 __new__ 函數就會報錯,關於 __new__ 函數的具體細節,大家可以參照這一篇博文:https://blog.csdn.net/qq_41020281/article/details/79638370
在這裏插入圖片描述
因此,我們需要傳入一個合適的名字,一般選擇變量名作爲 name,這樣將方便從輸出結果中進行查找對應的變量,如下所示:
在這裏插入圖片描述
剛纔說了,InverseGamma 類是一個繼承類,但是它也有着自己的類函數,分別是 logp()random(),實例化類時我們傳入了 alpha 參數和 beta 參數就有了分佈,而 logp() 函數可以幫助我們計算分佈的對數似然,這裏的似然就是我們的分佈密度,它的參數 value 就是分佈密度公式中的 x,還有 random() 函數可以幫助我們從分佈中抽取隨機的樣本。

不過在這裏我還遇到了一個很大的坑,關於這兩個函數我還沒有理解透,InverseGamma 類的這兩個類函數和其他的分佈類比如說 Normal 正態分佈類的同名類函數有着一定的差別,我懷疑這可能是因爲逆伽馬分佈有着形狀參數,比如說 α 就是形狀參數,因此它在 PyMC3 中的存在和運作形式和正態分佈這樣沒有形狀參數的分佈有一些不同 (經過與老師和同學的討論後,我發現我的猜想是錯誤的),我們通過打印他們的實例化對象的類型來觀察一下,發現它們的實例化對象在模型中被定義爲不同的類型,對於造成這樣差別的原因是否是形狀參數這個猜想只是我個人的想法,還有待驗證,需要繼續研究,關於形狀參數大家可以參照這篇博文:https://blog.csdn.net/weixin_30782871/article/details/95108032
在這裏插入圖片描述
實際上,爲什麼會產生這樣的不同是因爲逆伽馬分佈對應的函數是有界函數,而正太分佈對應的函數是無界函數,有界的意思是指分佈密度函數中的 x 被限定再一個範圍內,比如說逆伽馬分佈要求 x 大於 0,因此它對應的是有界函數,而正太分佈中的 x 可以取任何一個值,從負無窮取到正無窮都沒有問題,因此它對應的是無界函數,所以它對應的類型爲 pymc3.model.FreeRV,表示不受限制的隨機變量的意思(大家需要注意的是,這裏的有界函數和我們高中學的有界函數並不相同,官網的說法是 Automatic transforms of bounded RVs ——有界隨機變量的自動(代數)變換,所以只是在這裏使用有界函數的稱呼,該稱呼可能對大家造成一定程度的誤解,請大家諒解)。

一個例子可能還不能說明這個現象,我們再選取兩個有界函數對應的分佈——指數分佈和貝塔分佈,指數分佈要求 x 大於 0,而貝塔分佈要求 x 大於 0 小於 1,我們使用它們分別初始化隨機變量 RV 然後打印它們的類型,發現它們確實是 pymc3.model.TransformedRV 類型,這進一步佐證了我們的論述:
在這裏插入圖片描述
PyMC3 在生成變量的過程中爲了方便以後的抽樣,它會將有界變量自動轉換爲無界變量,而可以做到這一切的幕後黑手其實就是參數 transform,那些有界函數的對應的分佈在 PyMC3 中自帶有一個 transform 參數,在模型初始化有界隨機變量時,它就會被自動調用,將有界函數變換爲無界函數,而對應的類型也會變成 pymc3.model.TransformedRV,表示被代數變換後的變量的意思,我們來嘗試一下,看到底是不是這樣:
在這裏插入圖片描述
從上圖我們可以看到,當 transform 參數被設置爲 None 時,將不會發生從有界到無界的變換,打印出的類型也還是 pymc3.model.FreeRV,也可以通過調用 logp() 函數來得到對應的對數似然,然而,當不對 transform 參數進行設置時,對應的隨機變量就會自動轉變爲 pymc3.model.TransformedRV,而且還會出現 'TransformedRV' object has no attribute 'logp' 的報錯信息,因爲在這種情況下,雖然我們表面上沒有傳參給 transform,但實際上它自己會自動向 transform 參數傳入一個函數,接着調用該函數幫助逆伽馬分佈完成從有界到無界的華麗逆轉。

那 transform 參數接收到的這個函數到底是什麼呢,我們可以通過兩種方式查看:一種是打印模型的 FreeRV 列表,因爲從無界變換過來的變量會自動變成 FreeRV,在這裏就是 x_inverse_gamma 是 TransformedRV,而經過 log 變換後的 x_inverse_gamma_log__ 就是 FreeRV(注意,x_inverse_gamma 依然會保存在模型當中);另一種方法就是直接打印 transformation.name 從而得到進行代數變換的函數名(在這裏,我又產生了新的疑惑,爲什麼逆伽馬分佈經過 log 變換後會從有界變成無界呢)。
在這裏插入圖片描述
剛剛講了一下關於 InverseGamma 類的內容,現在來重新看一下寫在上面的這一步的代碼,讓我們回到初始問題上來:

with pm.Model() as my_first_model:
    
    theta = pm.InverseGamma('theta', alpha=10, beta=100)
    X_obs = pm.Exponential('X_obs', lam=1 / theta, observed=X)

確定了 θ 之後,我們要來確定需要擬合的變量,即 X_obs,它服從指數分佈 Exp(1 / θ),因此傳入的參數是 1 / theta,而參數 observed=X 是告訴模型這個變量的值是已經被觀測到了的,不會被擬合算法改變,而觀測值就是 X,在這裏,X 是一開始我們定義的列表。

這裏也給出 PyMC3 中的指數分佈類 Exponential 所對應的網址:
https://docs.pymc.io/api/distributions/continuous.html#pymc3.distributions.continuous.Exponential,該類和剛纔講的 InverseGamma 類差不多,沒有太大變化,可以說 PyMC3 所提供的分佈類基本上都可以套用上述的理解方式去學習,不過這個現象可能是表面上的,其中還是有許多坑的,大家慎踩。

4 統計推斷

定義了模型之後,我們就要開始對模型進行統計推斷了,這裏選擇 MAP 作爲推斷方法,那什麼是 MAP 呢,MAP(maximum a posteriori probability estimate)中文名稱叫極大似然估計,其實它就是《貝葉斯統計》這本書上所講的廣義最大似然估計,即後驗衆數估計,PyMC3 也提供了對應的函數 find_MAP(),不過它的計算方式和我們書上說的不同,因爲書上是通過分析計算的方式推斷出參數的估計值,但是這種方式一般只適合於標準分佈,而實際上我們碰到的很多分佈都是非標準分佈,很難或無法通過分析計算進行推斷,這個時候就需要通過 find_MAP() 所採用的數值優化的方法得出參數的估計值。

map_estimate = pm.find_MAP(model=my_first_model, start={'theta':10})

這裏先給出代碼,然後我們來看一下 find_MAP() 函數,這個函數的參數也比較多,接下來對這些參數來了解一下它們分別代表什麼意思:

find_MAP(start=None, vars=None, method="L-BFGS-B", return_raw=False, include_transformed=True,
			progressbar=True, maxeval=5000, model=None, *args, **kwargs)
  • start:表示開始進行數值優化的初始值的字典,默認是模型自帶的測試點,一般以 start={'theta':10} 的形式進行傳參的話,這就表示數值優化時參數 theta 從 10 開始迭代優化;

  • vars:表示需要優化的變量的列表,默認是認定模型內所有的連續型變量都要優化,比如說我們這裏的 theta 就需要進行優化,那我們就通過 vars=[theta] 的方式進行傳參;

  • method:表示進行數值優化時用到的優化算法,默認是 L-BFGS-B 算法,當然也可以使用其他算法比如說 BFGS、POWELL 等,不過這裏的這些算法實現時都是調用 scipy.optimize 的函數,因此也可以通過 fmin=scipy.optimize.fmin_l_bfgs_b 這樣的形式來告訴模型要用的優化算法,但是這種形式不推薦用了,因爲在以後的版本不會再用 fmin 這個參數了,所以推薦使用 method 參數;

  • return_raw:表示是否返回所有的輸出值,默認爲 False,剛剛也講了算法實現時都是調用 scipy.optimize 的函數,而這些函數的輸出內容一般比較多,因此選擇 False 可以幫助我們篩選掉一部分輸出;

  • include_transformed:表示是否輸出除原始變量外自動轉換的變量,默認爲 True,這個參數我也沒搞太懂,還需要研究,不過當它設爲 False 時,輸出確實只有一開始定義的變量了,比如說當它爲 True 時,輸出爲 {'theta_log__':array(……),'theta':array(……)},而當它爲 False 時輸出爲 {'theta':array(……)}(老師對該問題進行了講解),這個參數是指是否在輸出中包含代數變換後的變量,我們的變量 theta 服從逆伽馬分佈,因此屬於有界變量,PyMC3 會自動對其進行代數變換生成一個 FreeRV —— theta_log__,當 include_transformed 參數爲 True 時,就意味着打印 theta_log__,輸出爲 {'theta_log__':array(……),'theta':array(……)}

  • progressbar:表示是在命令行中顯示程序運行時的進度條,默認爲 True,進度條的樣式通常如下所示,logp 表示分佈在初始值時對應的對數似然,grad 表示數值優化時的梯度的一種輸出形式,對於這種輸出形式,我一開始簡單地以爲,grad 僅僅表示每輪迭代的梯度的平均值,但後來查看源碼後發現並不是這樣,不過至少確定一個事實,grad 是一個 numpy array 的二範數,以及 grad 確實和迭代的梯度緊密相關,因此在後文中統一以 grad 梯度稱呼它,我們也可以把它當成梯度來理解它,但是對於它到底是什麼的這個問題還有待研究:
    在這裏插入圖片描述
    這裏的 logp 雖然是負數,但因爲 log 函數是遞增函數,因此還是 logp 越大表示對應的似然越大,表示初始值越接近極大似然點估計的值。

    而 grad 梯度是在優化過程中得出的,它則是越小表示初始值離極大似然點估計的值越近,它可以簡單的理解爲導數,導數可正可負,因此進度條上對於 grad 梯度顯示的是一個二範數的形式,數學中的函數圖像的極值點對應的導數爲 0 ,因此當你看到你的進度條的 grad 梯度爲 0 時,那麼恭喜你,你的直覺非常準,初始值設置的非常好。

    我們來看一下另一個初始值對應的進度條,這樣對比一下可以幫助理解,可以看到,當初始值設置爲 1 時,它對應的 logp 是非常小的,而優化時得出的 grad 梯度是非常大的,這說明初始值 1 與極大似然點估計的值還有很大距離,每一輪迭代需要使用較大的梯度才能最終得到理想的點估計值:
    在這裏插入圖片描述

  • maxeval:表示數值優化過程中迭代的最大輪數,默認爲 5000,不過有時候用不到這麼多輪,比如說上面的兩個進度條顯示的分別時迭代了 6 輪和 10 輪;

  • model:表示當前的模型,如果 find_MAP() 函數是在 with pm.Model() 上下文管理器中,那這個參數就不用指定,如果不在則需要指定,否則會報錯;

  • *args, **kwargs:額外的參數將會傳給 scipy.optimize 的優化算法函數,幫助配置優化項。

關於 find_MAP() 函數,下面也給出了它在源碼中的說明和參數解釋:

def find_MAP(start=None, vars=None, method="L-BFGS-B", return_raw=False, include_transformed=True,
			progressbar=True, maxeval=5000, model=None, *args, **kwargs):
    
   """
   Finds the local maximum a posteriori point given a model.

   find_MAP should not be used to initialize the NUTS sampler. Simply call pymc3.sample() and it will automatically initialize NUTS in a better way.

   Parameters
   ----------
   start : `dict` of parameter values (Defaults to `model.test_point`)
   vars : list
       List of variables to optimize and set to optimum (Defaults to all continuous).
   method : string or callable
       Optimization algorithm (Defaults to 'L-BFGS-B' unless
       discrete variables are specified in `vars`, then
       `Powell` which will perform better).  For instructions on use of a callable,
       refer to SciPy's documentation of `optimize.minimize`.
   return_raw : bool
       Whether to return the full output of scipy.optimize.minimize (Defaults to `False`)
   include_transformed : bool
       Flag for reporting automatically transformed variables in addition
       to original variables (defaults to True).
   progressbar : bool
       Whether or not to display a progress bar in the command line.
   maxeval : int
       The maximum number of times the posterior distribution is evaluated.
   model : Model (optional if in `with` context)
   *args, **kwargs
       Extra args passed to scipy.optimize.minimize
   """

5 代碼整理與輸出

到了這裏,問題的基本代碼講解完畢,我們現在將它們整合到一起,然後輸出最終的結果,這也順便幫助我們回顧一下剛纔講解的整個過程:

import pymc3 as pm

X = [5, 12, 14, 10, 12]

with pm.Model() as my_first_model:
    theta = pm.InverseGamma('theta', alpha=10, beta=100)
    X_obs = pm.Exponential('X_obs', lam=1 / theta, observed=X)

map_estimate = pm.find_MAP(model=my_first_model, start={'theta':10})

print(map_estimate)

最後的輸出結果如下,theta 的點估計值是 9.56249988:
在這裏插入圖片描述
我們也會發現除 theta 外還有一個輸出值—— theta_log__,它就是 theta 經過代數變換後的變量,我們也可以驗證一下,發現確實是這樣,9.56249988 經過 log 變換,得到了 theta_log__的值—— 2.2578491866036345:
在這裏插入圖片描述
然後我們再比對一下《貝葉斯統計》這本書上該題的答案,我們可以看到答案是 9.653,這和我們的點估計結果非常接近,說明我們的代碼運行無誤,問題得到解決:
在這裏插入圖片描述
到這裏,這一部分的代碼就講解完畢了,我們通過編程的手段來解決了一個實際問題,雖然整個部分的代碼只有七行,但是它們內含的門道特別繞,需要去一點點地推敲琢磨,敲完這段代碼或許只需要幾分鐘,但理解它們可能需要幾個小時甚至更多時間。

第三部分 概率編程基礎實例——線性迴歸

1 觀察問題

 Y1,Y2,,Yn  N(μ,σ2) , σ ,  μ=α+β1X1+β2X2, α , βi  Xi ,  X1  X2 ,  N :{X11,X12,,X1n},{X21,X22,,X2n}, ,, α  β ,  σ , : \begin{aligned} &{設~ Y_1,Y_2,\cdots,Y_n ~爲從正態總體~ N(\mu, \sigma^2) ~中抽取的隨機樣本,~\sigma~代表觀察誤差,~而~\mu = \alpha + \beta_1 X_1 + \beta_2 X_2,~\alpha~是偏置項,~\beta_i~}\\ &{是隨機變量~X_i~的係數,~其中~X_1~和~X_2~都服從標準正太分佈,~且已經分別擁有~N~個觀測值:\{X_{11},X_{12},\cdots,X_{1n}\},\{X_{21},}\\ &{X_{22},\cdots,X_{2n}\},~並且我們已經知道所有參數的先驗分佈,其中,~α~和~β~的先驗爲零均值的正態先驗,~而~σ~的先驗分佈爲半 }\\ &{正態分佈,~如下: }\\ \end{aligned}

αN(0,100)βiN(0,100)σN(0,1) \alpha \sim N(0, 100) \\ \beta_i \sim N(0, 100) \\ \sigma \sim |N(0, 1)|

注:半正太分佈就是正態分佈的一半,即當 x < 某一閾值時,概率密度爲零,而當 x >= 某一閾值時,概率密度又符合以該閾值爲均值,方差不定的某一正態分佈,這裏設置閾值爲 0,具體信息可以參照知乎上的這個回答:https://www.zhihu.com/question/264514748

2 模擬觀測數據

確定參數 α, β 和 σ,並根據參數對分佈進行抽樣,用抽樣出來的樣本來模擬觀測值:

(Yμ)σN(0,1) \frac {(Y - \mu)} \sigma \sim N(0, 1) \\

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# 設置隨機數種子
np.random.seed(123)

# 設置參數
alpha = 1
sigma = 1
beta = [1, 2.5]

# 設置樣本數
N = 100

# 從標準正太分佈中抽取 N 個樣本,組成列表
X1 = np.random.randn(N)
X2 = np.random.randn(N)

# 通過對標準正太分佈的觀測值進行變換得到Y的觀測值
Y = alpha + beta[0]*X1 + beta[1]*X2 + np.random.randn(N)*sigma
print(Y)

打印模擬觀測值:

[ 2.22281624 -3.54547971  5.26434219  6.67776203  0.35352754  2.53012956
 -1.06432782 -4.99915898  3.23610032 -3.60158125 -0.16854353  4.59215468
  0.37846969  0.19813822  2.41876305  2.87469391  0.67461722  2.72521749
  6.14128801 -3.67278285 -0.20776514  0.55852497  0.1208312   3.68472816
  4.72444868 -1.7320584   3.07252625 -0.55401831  0.80844631 -1.14750883
 -3.34936358 -3.79482811 -2.9094419   1.17312754  4.15241265  1.20086992
  0.66293789  6.00880526  4.51169797  0.19951466 -1.57307065 -0.12246944
  4.02731676  1.43141046  3.16583658  0.13901221  5.16353053  3.12177984
 -0.08555217  3.95985823  2.20338345  1.50558393 -1.4684609  -6.18228679
 -0.77676823  2.40858849  3.86605102  1.5434985   5.96843077  4.67007818
  0.27195132  1.15070778  1.5278903   0.85138301  5.07156554 -0.79257515
  5.49059223 -1.68605517  1.21875225  0.93371224 -2.36742485 -1.34544424
  1.71853876 -0.52364574 -1.43598803  5.04993191  2.6297198  -2.07430594
 -1.68306145 -0.922825    1.23769957  2.77577685  4.63122684  0.44878096
 -1.0412723  -7.16107707 -2.75846086 -1.40433145  0.87097339  2.42143555
  1.51841977  0.3260448   2.63582276  0.98495392 -0.76617916 -5.35073574
  1.11799891 -0.22490381  0.68679074  0.89571852]

繪製散射圖:

%matplotlib inline

# 繪製散射圖,其中參數 1 和 2 分別代表子圖的行數和列數,一共有 1 * 2 個子圖像,通過 figszie 可以設置子圖的寬度和高度
fig1, ax1 = plt.subplots(1, 2, figsize=(10,5))
ax1[0].scatter(X1, Y)
ax1[0].set_xlabel('X1')
ax1[0].set_ylabel('Y')
ax1[1].scatter(X2, Y)
ax1[1].set_xlabel('X2')
ax1[1].set_ylabel('Y')

# 繪製立體散射圖
fig2 = plt.figure(2)
ax2 = Axes3D(fig2)
ax2.scatter(X1, X2, Y)
ax2.set_xlabel('X1')
ax2.set_ylabel('X2')
ax2.set_zlabel('Y')

在這裏插入圖片描述

3 定義模型與變量

import pymc3 as pm

# 定義一個新的模型對象,這個對象是模型中隨機變量的容器
basic_model = pm.Model()

# 用 with 語句定義一個上下文管理器,以 basic_model 作爲上下文,在這個上下文中定義的變量都被添加到這個模型
with basic_model:
    
    # 定義兩個具有正態分佈先驗和一個具有半正太分佈先驗的隨機性隨機變量
    pm_alpha = pm.Normal('alpha', mu=0, sd=10) # 正態分佈
    pm_beta = pm.Normal('beta', mu=0, sd=10, shape=2) # 正態分佈
    pm_sigma = pm.HalfNormal('sigma', sd=1) # 半正態分佈

    # 定義一個確定性隨機變量,確定性指這個變量的值完全由右端值確定(其實就和一般的變量一個意思)
    mu = pm_alpha + pm_beta[0]*X1 + pm_beta[1]*X2

    # 定義了 Y 的觀測值,這是一個特殊的觀測隨機變量,表示模型數據的可能性
    # 通過 observed 參數來告訴這個變量其值是已經被觀測到了的(就是 Y 變量),不會被擬合算法改變
    # 可以使用 numpy.ndarry 或 pandas.DataFrame 對象作爲數據參數傳入
    Y_obs = pm.Normal('Y_obs', mu=mu, sd=pm_sigma, observed=Y)

4 模型擬合

第一種方法 MAP 方法

極大後驗估計是貝葉斯學派的方法,關於極大似然估計與極大後驗估計可以參照:https://zhuanlan.zhihu.com/p/40024110

# 使用優化方法找到參數的極大後驗估計
# find_MAP 默認使用 L-BFGS-B 算法進行最優化運算,找到對數後驗分佈的最大值
map_estimate1 = pm.find_MAP(model=basic_model)
map_estimate2 = pm.find_MAP(model=basic_model, method="L-BFGS-B")
map_estimate3 = pm.find_MAP(model=basic_model, method="BFGS")
map_estimate4 = pm.find_MAP(model=basic_model, method="POWELL")
# 打印擬合值
print(map_estimate1)
print(map_estimate2)
print(map_estimate3)
print(map_estimate4)

第二種方法 MCMC 採樣方法

PyMC3 中提供瞭如下采樣器:NUTS,Metropolis,Slice,HamiltonianMC,and BinaryMetropolis,採樣器可以由 PyMC3 自動指定也可以手動指定,自動指定是根據模型中的變量類型決定的:

  • 二值變量:指定 BinaryMetropolis
  • 離散變量:指定 Metropolis
  • 連續變量:指定 NUTS

手動指定優先,可覆蓋自動指定。

(1)使用採樣器對後驗分佈進行採樣

關於 NUTS 採樣器可參照:https://zhuanlan.zhihu.com/p/59473302

關於 Slice 採樣器可參照:https://blog.csdn.net/baihaoli123/article/details/53886077

with basic_model:
    # 自動指定 NUTS 採樣器,並對後驗分佈進行 2000 次採樣
    trace1 = pm.sample(2000)
    
    # 手動指定 NUTS 採樣器,並對後驗分佈進行 2000 次採樣
    trace2 = pm.sample(2000, step=pm.NUTS())
    
    # 用 MAP 獲得初始點
    start = pm.find_MAP(method="POWELL")
    # 這裏給 sigma 變量指定 Slice 採樣器,那麼其他兩個參數(alpha 和 beta)的採樣器會自動指定 NUTS
    step = pm.Slice(vars=[pm_sigma])
    # 對後驗分佈進行 2000 次採樣
    trace3 = pm.sample(2000, step=step, start=start)
(2)查看 Trace 對象內容

通過採樣器收集到的採樣值存儲在 Trace 對象中,按照採樣值獲得的先後順序存儲,Trace 對象是一個字典,每一個元素對應一個參數

print("trace1:")
print("alpha", trace1['alpha'].shape)
print(trace1['alpha'], "\n")
print("beta", trace1['beta'].shape)
print(trace1['beta'], "\n")
print("sigma", trace1['sigma'].shape)
print(trace1['sigma'], "\n\n")

print("trace2:")
print("alpha", trace2['alpha'].shape)
print(trace2['alpha'], "\n")
print("beta", trace2['beta'].shape)
print(trace2['beta'], "\n")
print("sigma", trace2['sigma'].shape)
print(trace2['sigma'], "\n\n")

print("trace3:")
print("alpha", trace3['alpha'].shape)
print(trace3['alpha'], "\n")
print("beta", trace3['beta'].shape)
print(trace3['beta'], "\n")
print("sigma", trace3['sigma'].shape)
print(trace3['sigma'])
(3)繪製後驗採樣的趨勢圖

通過 traceplot 函數來繪製後驗採樣的趨勢圖,左側是每個隨機變量的邊際後驗的直方圖,使用核密度估計進行了平滑處理,右側是馬爾可夫鏈採樣值按順序繪製,其中,對於參數 beta 會有兩條後驗分佈直方圖和後驗採樣值

pm.traceplot(trace1)
pm.traceplot(trace2)
pm.traceplot(trace3)
(4)使用 summary 函數獲得後驗統計總結
pm.summary(trace1)
pm.summary(trace2)
pm.summary(trace3)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章