用Python入門不明覺厲的馬爾可夫鏈蒙特卡羅(附案例代碼)

2020-12-05 大數據文摘

大數據文摘作品

編譯:Niki、張南星、Shan LIU、Aileen

這篇文章讓小白也能讀懂什麼是人們常說的Markov Chain Monte Carlo。

在過去幾個月裡,我在數據科學的世界裡反覆遇到一個詞:馬爾可夫鏈蒙特卡洛(Markov Chain Monte Carlo , MCMC)。在我的研究室、podcast和文章裡,每每遇到這個詞我都會「不明覺厲」地點點頭,覺得這個算法聽起來很酷,但每次聽人提起也只是有個模模糊糊的概念。

我屢次嘗試學習MCMC和貝葉斯推論,而一拿起書,又很快就放棄了。無奈之下,我選擇了學習任何新東西最佳的方法:應用到一個實際問題中。

通過使用一些我曾試圖分析的睡眠數據和一本實操類的、基於應用教學的書(《寫給開發者的貝葉斯方法》,我最終通過一個實際項目搞明白了MCMC。

《寫給開發者的貝葉斯方法》

https://github.com/CamDavidsonPilon/Probabilistic-Programming-and-Bayesian-Methods-for-Hackers

和學習其他東西一樣,當我把這些技術性的概念應用於一個實際問題中而不是單純地通過看書去了解這些抽象概念,我更容易理解這些知識,並且更享受學習的過程。

這篇文章介紹了馬爾可夫鏈蒙特卡洛在Python中入門級的應用操作,這個實際應用最終也使我學會使用這個強大的建模分析工具。

此項目全部的代碼和數據:

https://github.com/WillKoehrsen/ai-projects/blob/master/bayesian/bayesian_inference.ipynb

這篇文章側重於應用和結果,因此很多知識點只會粗淺的介紹,但對於那些想了解更多知識的讀者,在文章也嘗試提供了一些學習連結。

案例簡介

我的智能手環在我入睡和起床時會根據心率和運動進行記錄。它不是100%準確的,但現實世界中的數據永遠不可能是完美的,不過我們依然可以運用正確的模型從這些噪聲數據提取出有價值的信息。

典型睡眠數據

這個項目的目的在於運用睡眠數據建立一個能夠確立睡眠相對於時間的後驗分布模型。由於時間是個連續變量,我們無法知道後驗分布的具體表達式,因此我們轉向能夠近似後驗分布的算法,比如馬爾可夫鏈蒙特卡洛(MCMC)。

選擇一個概率分布

在我們開始MCMC之前,我們需要為睡眠的後驗分布模型選擇一個合適的函數。一種簡單的做法是觀察數據所呈現的圖像。下圖呈現了當我入睡時時間函數的數據分布。

睡眠數據

每個數據點用一個點來表示,點的密度展現了在固定時刻的觀測個數。我的智能手錶只記錄我入睡的那個時刻,因此為了擴展數據,我在每分鐘的兩端添加了數據點。如果我的手錶記錄我在晚上10:05入睡,那麼所有在此之前的時間點被記為0(醒著),所有在此之後的時間點記為1(睡著)。這樣一來,原本大約60夜的觀察量被擴展成11340個數據點。

可以看到我趨向於在10:00後幾分鐘入睡,但我們希望建立一個把從醒到入睡的轉變用概率進行表達的模型。我們可以用一個簡單的階梯函數作為模型,在一個精確時間點從醒著(0)變到入睡(1),但這不能代表數據中的不確定性。

我不會每天在同一時間入睡,因此我們需要一個能夠模擬出這個個漸變過程的函數來展現變化當中的差異性。在現有數據下最好的選擇是logistic函數,在0到1之前平滑地移動。下面這個公式是睡眠狀態相對時間的概率分布,也是一個logistic公式。

在這裡,β (beta) 和 α (alpha) 是模型的參數,我們只能通過MCMC去模擬它們的值。下面展示了一個參數變化的logistic函數。

一個logistic函數能夠很好的擬合數據,因為在logistic函數中入睡的概率在逐漸改變,捕捉了我睡眠模式的變化性。我們希望能夠帶入一個具體的時間t到函數中,從而得到一個在0到1之間的睡眠狀態的概率分布。我們並不會直接得到我是否在10:00睡著了的準確答案,而是一個概率。創建這個模型,我們通過數據和馬爾可夫鏈蒙特卡洛去尋找最優的alpha和beta係數估計。

馬爾可夫鏈蒙特卡洛

馬爾可夫鏈蒙特卡羅是一組從概率分布中抽樣,從而建立最近似原分布的函數的方法。因為我們不能直接去計算logistic分布,所以我們為係數(alpha 和 beta)生成成千上萬的數值-被稱為樣本-去建立分布的一個模擬。MCMC背後的基本思想就是當我們生成越多的樣本,我們的模擬就更近似於真實的分布。

馬爾可夫鏈蒙特卡洛由兩部分組成。蒙特卡洛代表運用重複隨機的樣本來獲取一個準確答案的一種模擬方法。蒙特卡洛可以被看做大量重複性的實驗,每次更改變量的值並觀察結果。通過選擇隨機的數值,我們可以在係數的範圍空間,也就是變量可取值的範圍,更大比例地探索。下圖展示了在我們的問題中,一個使用高斯分布作為先驗的係數空間。

能夠清楚地看到我們不能在這些圖中一一找出單個的點,但通過在更高概率的區域(紅色)進行隨機抽樣,我們就能夠建立最近似的模型。

馬爾可夫鏈(Markov Chain)

馬爾可夫鏈是一個「下個狀態值只取決於當前狀態」的過程。(在這裡,一個狀態指代當前時間係數的數值分配)。一個馬爾可夫鏈是「健忘」的,因為如何到達當前狀態並不要緊,只有當前的狀態值是關鍵。如果這有些難以理解的話,讓我們來設想一個每天都會經歷的情景--天氣。

如果我們希望預測明天的天氣,那麼僅僅使用今天的天氣狀況我們就能夠得到一個較為合理的預測。如果今天下雪,我們可以觀測有關下雪後第二天天氣的歷史數據去預測明天各種天氣狀況的可能性。馬爾可夫鏈的定義就是我們不需要知道一個過程中的全部歷史狀態去預測下一節點的狀態,這種近似在許多現實問題中都很有用。

把馬爾可夫鏈(Markov Chain)和蒙特卡洛(Monte Carlo),兩者放到一起,就有了MCMC。MCMC是一種基於當前值,重複為概率分布係數抽取隨機數值的方法。每個樣本都是隨機的,但是數值的選擇也受當前值和係數先驗分布的影響。MCMC可以被看做是一個最終趨於真實分布的隨機遊走。

為了能夠抽取alpha 和 beta的隨機值,我們需要為每個係數假設一個先驗分布。因為我們沒有對於這兩個係數的任何假設,我們可以使用正太分布作為先驗。正太分布,也稱高斯分布,是由均值(展示數據分布),和方差(展示離散性)來定義的。下圖展示了多個不同均值和離散型的正態分布。

具體的MCMC算法被稱作Metropolis Hastings。為了連接我們的觀察數據到模型中,每次一組隨機值被抽取,算法將把它們與數據進行比較。一旦它們與數據不吻合(在這裡我簡化了一部分內容),這些值就會被捨棄,模型將停留在當前的狀態值。

如果這些隨機值與數據吻合,那麼這些值就被接納為各個係數新的值,成為當前的狀態值。這個過程會有一個提前設置好的迭代次數,次數越多,模型的精確度也就越高。

把上面介紹的整合到一起,就能得到在我們的問題中所需進行的最基本的MCMC步驟:

為logistic函數的係數alpha 和beta選擇初始值。基於當前狀態值隨機分配給alpha 和beta新的值。檢查新的隨機值是否與觀察數據吻合。如果不是,捨棄掉這個值,並回到上一狀態值。如果吻合,接受這個新的值作為當前狀態值。重複步驟2和3(重複次數提前設置好)。這個算法會給出所有它所生成的alpha 和beta值。我們可以用這些值的平均數作為alpha 和beta在logistic函數中可能性最大的終值。MCMC不會返回「真實」的數值,而是函數分布的近似值。睡眠狀態概率分布的最終模型將會是以alph和beta均值作為係數的logistic函數。

Python實施

我再三思考模擬上面提到的細節,最終我開始用Python將它們變成現實。觀察一手的結果會比閱讀別人的經驗貼有幫助得多。想要在Python中實施MCMC,我們需要用到PyMC3貝葉斯庫,它省略了很多細節,方便我們創建模型,避免迷失在理論之中。

通過下面的這些代碼可以創建完整的模型,其中包含了參數alpha 、beta、概率p以及觀測值observed,step變量是指特定的算法,sleep_trace包含了模型創建的所有參數值。

with pm.Model() as sleep_model:# Create the alpha and beta parameters # Assume a normal distribution alpha = pm.Normal('alpha', mu=0.0, tau=0.05, testval=0.0) beta = pm.Normal('beta', mu=0.0, tau=0.05, testval=0.0) # The sleep probability is modeled as a logistic function p = pm.Deterministic('p', 1. / (1. + tt.exp(beta * time + alpha))) # Create the bernoulli parameter which uses observed data to inform the algorithm observed = pm.Bernoulli('obs', p, observed=sleep_obs) # Using Metropolis Hastings Sampling step = pm.Metropolis() # Draw the specified number of samples sleep_trace = pm.sample(N_SAMPLES, step=step);

為了更直觀地展現代碼運行的效果,我們可以看一下模型運行時alpha和beta生成的值。

這些圖叫做軌跡圖,可以看到每個狀態都與其歷史狀態相關,即馬爾可夫鏈;同時每個值劇烈波動,即蒙特卡洛抽樣。

使用MCMC時,常常需要放棄軌跡圖中90%的值。這個算法並不能立即展現真實的分布情況,最初生成的值往往是不準確的。隨著算法的運行,後產生的參數值才是我們真正需要用來建模的值。我使用了一萬個樣本,放棄了前50%的值,但真正在行業中應用時,樣本量可達成千上萬個、甚至上百萬個。

通過足夠多的迭代,MCMC逐漸趨近於真實的值,但是估算收斂性並不容易。這篇文章中並不會涉及到具體的估算方法(方法之一就是計算軌跡的自我相關性),但是這是得到最準確結果的必要條件。PyMC3的函數能夠評估模型的質量,包括對軌跡、自相關圖的評估。

pm.traceplot(sleep_trace, ['alpha', 'beta'])pm.autocorrplot(sleep_trace, ['alpha', 'beta'])

軌跡圖(左)和自相關性圖(右)

睡眠模型

建模、模型運行完成後,該最終結果上場了。我們將使用最終的5000個alpha和beta值作為參數的可能值,最終創建了一個單曲線來展現事後睡眠概率:

基於5000個樣本的睡眠概率分布

這個模型能夠很好的代表樣本數據,並且展現了我睡眠模式的內在變異性。這個模型給出的答案並不是簡單的「是」或「否」,而是給我們一個概率。舉個例子,我們可以通過這個模型找出我在特定時間點睡覺的概率,或是找出我睡覺概率超過50%的時間點:

9:30 PM probability of being asleep: 4.80%.10:00 PM probability of being asleep: 27.44%.10:30 PM probability of being asleep: 73.91%.The probability of sleep increases to above 50% at 10:14 PM.

雖然我希望在晚上10點入睡,但很明顯大多時候並不是這樣。我們可以看到,平均來看,我的就寢時刻是在晚上10:14。

這些值是基於樣本數據的最有可能值,但這些概率值都有一定的不確定性,因為模型本身就是近似的。為了展現這種不確定性,我們可以使用所有的alpha、beta值來估計某個時間點的睡覺概率,而不是使用平均值,並且把這些概率值展現在圖中。

晚上10:00睡覺的概率分布

這些結果能夠更好地展現MCMC模型真正在做的事情,即它並不是在尋找單一的答案,而是一系列可能值。貝葉斯推論在現實世界中非常有用,因為它是對概率進行了預測。我們可以說存在一個最可能的答案,但其實更準確的回覆應當是:每個預測都有一系列的可能值。

起床模型

同樣我可以用我的起床數據創建類似的模型。我希望能夠在鬧鐘的幫助下總能在早上6:00起床,但實際上並不如此。下面這張圖展現了基於觀測值我起床的最終模型:

基於5000個樣本的起床事後概率

可以通過模型得出我在某個特定時間起床的概率,以及我最有可能起床的時間:

Probability of being awake at 5:30 AM: 14.10%. Probability of being awake at 6:00 AM: 37.94%. Probability of being awake at 6:30 AM: 69.49%.The probability of being awake passes 50% at 6:11 AM.

看來我需要一個更生猛的鬧鐘了….

睡眠的時間

出於好奇以及實踐需求,最後我想創建的模型是我的睡眠時間模型。首先,我們需要尋找到一個描述數據分布的函數。事先,我認為應該是正態函數,但無論如何我們需要用數據來證明。

睡眠時間長短分布

正態分布的確能夠解釋大部分數據,但是圖中右側的異常值卻無法得到解釋(當我睡懶覺的時候)。我們可以用兩個單獨的正態分布來代表兩種模式,但我要用偏態分布。偏態分布有三個參數:平均值、偏離值,以及alpha傾斜值。這三個參數的值都需要從MCMC算法中得到。下面的代碼創建了模型,並且使用了Metropolis Hastings抽樣。

with pm.Model() as duration_model:# Three parameters to sample alpha_skew = pm.Normal('alpha_skew', mu=0, tau=0.5, testval=3.0) mu_ = pm.Normal('mu', mu=0, tau=0.5, testval=7.4) tau_ = pm.Normal('tau', mu=0, tau=0.5, testval=1.0) # Duration is a deterministic variable duration_ = pm.SkewNormal('duration', alpha = alpha_skew, mu = mu_, sd = 1/tau_, observed = duration) # Metropolis Hastings for sampling step = pm.Metropolis() duration_trace = pm.sample(N_SAMPLES, step=step)

現在,我們可以使用三個參數的平均值來建立最有可能的分布模型了。下圖為基於數據的最終偏態分布模型。

時長模型

模型看上去很完美!通過模型可以找出我至少睡多長時長的可能性,以及我最經常的睡覺時長:

Probability of at least 6.5 hours of sleep = 99.16%.Probability of at least 8.0 hours of sleep = 44.53%.Probability of at least 9.0 hours of sleep = 10.94%.The most likely duration of sleep is 7.67 hours.

結論

我想再次強調,完成這個項目讓我體會到解決問題的重要性,尤其是有現實應用意義的項目!在我嘗試使用馬爾可夫鏈蒙特卡洛來端到端建立貝葉斯推論的時候,我重新熟悉了許多基礎知識,並且非常享受這個過程。

我不僅了解到自身需要改進的習慣,而且當別人在談論MCMC和貝葉斯推論時,我終於真的明白他們在談論什麼了!數據科學正是關於持續不斷地在你自己的知識庫中輸入新的工具,而這最有效的辦法就是發現一個問題,然後應用你所學的去解決問題!

原文連結:

https://towardsdatascience.com/markov-chain-monte-carlo-in-python-44f7e609be98

相關焦點

  • 一汽轎車有個不明覺厲工作室 專門研究黑科技!
    本期稿件主角,就是他們……  不明覺厲工作室  在一汽轎車辦公大樓內,不明覺厲工作室那是大名鼎鼎,團隊成員不僅年輕,還擅長研究黑科技。  一個名字聽上去頗為網紅時髦的工作室,為何被贊?一汽轎車奔騰開發院不明覺厲創新工作室負責人李彥奇,揭開了謎底。  「我們工作室是2017年底策劃的,次年1月18日正式成立。」
  • 又一「不明覺厲」的實驗室落戶科學城
    又一「不明覺厲」的實驗室落戶科學城 2021-01-09 12:17 來源:澎湃新聞·澎湃號·政務
  • 不明覺厲!來看看科學家發明的「永動機」
    下面這組圖就是科學家們發明的「永動機」,看起來不明覺厲,不過可以明確的說,他們都是偽永動機。為什麼說永動機是不可能實現的?因為它違反了熱力學基本原理的設想。因為,完全將不同種形式互相轉化而無損失的熱機,這種熱機無冷凝器,只有單一的熱源,它從這個單一的熱源吸收的熱量,可以全部用來做功,而不引起其他變化,人們把這種想像中的熱機稱為第二類永動機。
  • 一組科學家發明的偽永動機,不明覺厲
    下面是一組科學家們發明的偽永動機,不明覺厲!
  • 【算法系列】凸優化的應用——Python求解優化問題(附代碼)
    推薦閱讀   Sklearn包含的常用算法  隨機森林算法入門(python)  下降方法:坐標下降、梯度下降、次梯度下降>  機器學習算法Python實現--邏輯回歸  機器學習算法Python實現--線性回歸分析  【機器學習算法系列】機器學習中梯度下降法和牛頓法的比較  【機器學習算法系列】如何用Apriori尋找到繁雜數據之間的隱藏關係  後臺回復「代碼
  • 機器學習之統計採樣方法和馬爾可夫過程(馬爾可夫鏈蒙特卡羅方法)匯總
    為了顯示Pij是由狀態ai經過一步轉移至aj的概率馬爾可夫鏈的有限維分布    首先,記pj(0) = P{X0 = aj}, aj ∈ I. j=1,2,. (0時刻時,系統處於aj狀態下的概率)    稱其為馬爾可夫鏈的初始分布    再看馬爾可夫鏈在任一時刻n∈T1的一維分布 pj(n) = P{Xn = aj
  • 簡潔清晰解釋馬爾可夫鏈蒙特卡洛方法
    如果先驗和似然概率分布是正態分布的,我們能夠用函數描述後驗分布。這稱為封閉形式的解決方案。這種類型的貝葉斯如下所示。正如您所看到的,後驗分布由先驗分布和可能分布兩者組成,最終在中間某處。但是當概率不是那麼漂亮時呢?當概率看起來更像這樣時會發生什麼?
  • 馬爾可夫鏈告訴你
    馬爾可夫鏈是一個相當常見、相當簡單的對隨機過程進行統計建模的方式。它們被應用在很多領域,從文本生成到金融建模。一個比較流行的例子是 SubredditSimulator,它使用馬爾可夫鏈自動創建整個 subreddit 的內容。
  • 不明覺厲的一個組織!魔獸世界血色十字軍詳解-不明覺厲的一個組織...
    不明覺厲的一個組織,原本都是善良的人類,因為狂熱而導致最終的墮落。最後附上一句血色十字軍的話。
  • 《死亡擱淺》不明覺厲 「拔叔」:遊戲很有趣但完全沒搞懂
    (圖片來源:大粵網•香港提供)【大粵網.香港】曾經合金裝備系列的研發人,人稱「MGS之父」的小島秀夫,在2015年12月離開Kojima後自行成立工作室並宣布開發《死亡擱淺(Death Stranding)》至今已經兩年多了,儘管遊戲釋出了一段又一段的預告,但玩家們依舊看得一臉懵逼,只能高呼不明覺厲不知道小島在搞啥鬼。
  • 小外時間靜止救公主,公主無以為報願以身相許,一吻定情不明覺厲
    小外時間靜止救公主,公主無以為報願以身相許,一吻定情不明覺厲小外與降龍觀察中遇到了一場沙塵暴,沙塵暴的肆虐讓蘑菇人都被吹到了空中,沒人抬的轎子也就會順勢跌落懸崖,小外急中生智使用了發明時間靜止,空中的樹葉和石子都被定格,小外和降龍走近懸崖將轎子拖回了岸邊,看來轎子中的公主並無大礙,只是公主立刻就明白了自己被小外所救,立刻衝出轎子吻了小外,無以為報的公主決定以身相許,可是面對蘑菇公主的回報小外卻開心不起
  • 形象透徹理解馬爾可夫鏈
    讓我們再次強調馬爾可夫鏈在處理隨機動力學時對問題建模的強大作用,被用於各種領域,例如排隊理論,優化電信網絡的性能;統計信息,眾所周知的"馬爾可夫鏈蒙特卡羅";生物學,生物種群進化的建模;計算機科學,隱馬爾可夫模型是資訊理論和語音識別等領域的重要工具。
  • 全方位解剖《最強大腦》:讓網友不明覺厲的節目(圖)
    不過,因為節目組的各種消息都處於封閉狀態,《最強大腦》在讓無數觀眾「不明覺厲」的同時,也產生了各種誤讀和好奇。昨天,記者對節目進行了深入打探,為你揭開《最強大腦》的面紗一角。2.0版的益智節目?NO!
  • 13個小案例輕鬆認識python
    python流行一段時間了,開始的就感覺只是一種新的語言罷了,可是這個世界總是對新事物非常尊崇,平時用的不多,看起來也沒多大動力,結合著平時講VB(信息技術《算法與程序設計》9講)的套路,用案例簡單自學下python,這13個小案例不像網絡上的圖形處理等那麼酷炫,不能做出酷炫的作品,也沒有涉及基礎的算法,但也是python的基礎吧
  • 除了不明覺厲和圍觀吃瓜,前沿科技理論還可以這樣用!
    說了那麼多物理學、量子力學、動力學等理工科發展成果,是不是有點不明覺厲?其實投資理論也有巨大的突破,其中的代表之一就是近年來受到海外投資機構廣泛使用的風險均衡策略。風險均衡策略(Risk Parity)是現代投資組合理論最新的發展成果,最早由磐安公司(PanAgora)的錢恩平博時提出。
  • Python趣味打怪:60秒學會一個例子,147段代碼助你從入門到大師
    不要害怕學習的過程枯燥無味,這裡有程式設計師jackzhenguo打造的一份中文Python「糖果包」:147個代碼小樣,60秒一口,營養又好玩,從Python基礎到機器學習盡皆囊括。入門簡單如十進位轉二進位,盡顯Python簡潔之美:In [1]: bin(10)Out[1]: '0b1010'冬天到了,就算沒有點亮手繪技能,也能用簡單幾行代碼繪出漫天雪花:
  • 《小灰教你零基礎學python》-Python入門語言
    程式語言有很多,咱們就學簡單強大的python即可。Python是一種清晰而強大的面向對象程式語言,不過還沒入門的小白不要想多了哈,不是你的對象(女朋友?)Python目前是分成2個大版本,python2 和python3,python是完全免費的,所以不用擔心版權問題,因為python2已經廢棄,所以咱們這套課程完全基於python3。
  • 蒙特卡羅模擬法
    蒙特卡羅模擬法蒙特卡羅模擬法是一種使用隨機數和概率來解決問題的計算方法,它又稱隨機抽樣或統計試驗法,整體思路是(模擬——抽樣——估值),工作原理是不斷抽樣、逐漸逼近。通俗說就是建立一個模型用來模擬某事件,然後不斷隨機取樣,進行很多次實驗後,得出一個接近真實值的估值。
  • 蒙特卡羅方法入門
    轉自:數學中國如涉版權請加編輯微信iwish89聯繫哲學園鳴謝本文通過五個例子,介紹蒙特卡羅方法(Monte
  • python入門第四課:列表的排序、元素遍歷
    本教程使用的課本是《Python編程:從入門到實踐》,作者:[美] Eric Matthes本節介紹列表的操作,包括列表的排序、元素遍歷等操作。一、列表的排序有時候我們需要按升序或降序排列列表的元素,可以用sort()方法,sort方法默認是升序,如果加個參數,變成sort(reverse=True)就會按降序排列,見下面的代碼:Mylists = [2,58,64,21,33,5,8,9,4,15,23,45,60,88