# 吉布斯採樣(Gibbs sampling) {%hackmd @themes/orangeheart %} <style> .likecoin-button { position: relative; width: 100%; max-width: 485px; max-height: 240px; margin: 0 auto; } .likecoin-button > div { padding-top: 49.48454%; } .likecoin-button > iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } </style> ###### tags: `Numerical Methods` 對於單變數函數進行隨機抽樣或找出其累積分配函數(CDF),我們可以利用反函數進行處理;但若同樣以反函數對於雙變數函數處理,取出隨機變數或找出 CDF,會發現結果會十分複雜。不過,透過吉布斯採樣,我們可以解決上面的問題:假設有一個聯合機率分配,可以先假定 $x$ 服從某個分配並對其抽樣,同時也假定 $y$ 也服從該分配,也就是說取其條件機率分配。我們可以透過下面的例子來了解:$X$ 代表性別,$Y$ 則是施打疫苗的種類 $$ \begin{array}{cccc}\hline Y/X & \textbf{male} & \textbf{female} & \textbf{total} \\ \textbf{AZ} &0.25& 0.15 &0.40\\ \textbf{BNT} &0.10 & 0.35 & 0.45 \\ \textbf{MVC} &0.10&0.05 & 0.15\\ \hline & 0.45 & 0.55 & 1.00\\\hline \end{array} $$ 上表中的 $0.40$ 、$0.45$ 與 $0.15$ 三者代表的意義是在給定疫苗的種類下,施打該疫苗的的人數加總(所有性別),記成 $\mathbb{P} (Y= \text{vaccine})$;而若給定男性的情況下,施打高端(MVC)的機率為 $0.10/0.45 = 0.22$,稱作條件機率分配,記作 $\mathbb{P} (Y = \text{vaccine}\mid \text{sex})$。了解完簡單的機率分配後,我們就可以來了幾吉布斯採樣的內容了。 ## 吉布斯採樣的內容與性質 - 有相對應的組合,即給定 $x$ 可以對應到 $y$,反之亦可得到組合。 - 必須以容易、簡單的方式進行抽樣 而抽樣的步驟如下: ### 步驟 **1** 設定初始值$x$ 與 $y$ → $(x_0,y_0)$ ### 步驟 2 利用 $x_1$ 從 $f(X|Y=y_0)$ 取樣,接著用 $y_1$ 從 $f(Y|X=x_1)$ 取樣,得到 $(x_1,y_1)$ ### 步驟 **3** 利用 $x_2$ 從 $f(X|Y=y_1)$ 取樣,接著用 $y_2$ 從 $f(Y|X=x_2)$ 取樣,得到 $(x_2,y_2)$ ### 步驟 **4** 重複步驟 2與步驟 3 $n$ 次. ### 步驟 **5** 確認其是否收斂(converge) ```julia using Interact, WebIO using Distributions, Plots, LinearAlgebra, Statistics μ = [1,1] σ = [2 0; 0 3] min_N = 50 max_N = 2000 draw0 = [rand(Normal(0, 1), max_N)'; rand(Normal(0, 1), max_N)']; @manipulate for num=min_N:max_N, corr=-1:0.1:1 correlation = [1 corr; corr 1] covariance = σ*correlation*σ C = cholesky(covariance) draw = C.L * draw0[:, 1:num] .+ μ # take care of the mean, std, and correlation x = draw[1,:] y = draw[2,:] layout = @layout [a{0.6w,0.4h} _ b{0.6w,0.6h} c{0.4w, 0.6h}] default(fillcolor=:lightgrey, markercolor=:white, grid=false, legend=false) plot(layout=layout, link=:both, size=(400, 400), margin=-10Plots.pt) scatter!(x,y, subplot=2, framestyle =:box) histogram!([x y], subplot=[1 3], orientation=[:v :h], framestyle=:none, bins=min(num, 100), normalize=true) end ``` 上圖並非利用吉布斯採樣的方法產生的。若要實踐吉布斯採樣,我們可以依據上面的步驟勾勒出想法。 ## ⌨️ 演練 假設 $x$ 與 $y$ 服從二元常態分配,其聯合機率分配如下 $$ f_{X Y}(x, y)=\frac{1}{2 \pi \sigma_{x} \sigma_{y} \sqrt{1-\rho^{2}}} \exp \left(-\frac{1}{2\left(1-\rho^{2}\right)}\left[\left(\frac{x-\mu_{x}}{\sigma_{x}}\right)^{2}+\left(\frac{y-\mu_{y}}{\sigma_{y}}\right)^{2}-2 \rho \frac{\left(x-\mu_{x}\right)\left(y-\mu_{y}\right)}{\sigma_{x} \sigma_{y}}\right]\right) $$ 其中 $\mu_x, \mu_y \in \mathbb{R}$,$\sigma_x, \sigma_y>0$, $\rho \in (-1,1)$,且均為一常數(constant)。其邊際機率分配為: $$ \begin{aligned}f_{X}(x) &=\frac{1}{\sqrt{2 \pi} \sigma_{x}} e^{-\frac{1}{2}\left(\frac{x-\mu_{x}}{\sigma_{x}}\right)} \\f_{Y}(y) &=\frac{1}{\sqrt{2 \pi} \sigma_{y}} e^{-\frac{1}{2}\left(\frac{y-\mu_{y}}{\sigma_{y}}\right)}\end{aligned} $$ 條件機率分配則是 $$ \begin{aligned}&(X \mid Y=y) \sim N\left(\mu_{x}+\rho \frac{\sigma_{x}}{\sigma_{y}}\left(y-\mu_{y}\right), \quad \sigma_{x}^{2}\left(1-\rho^{2}\right)\right) \\&(Y \mid X=x) \sim N\left(\mu_{y}+\rho \frac{\sigma_{y}}{\sigma_{x}}\left(x-\mu_{x}\right), \quad \sigma_{y}^{2}\left(1-\rho^{2}\right)\right)\end{aligned} $$ 假設 $\mu_x = \mu_y = 0$,$\sigma_x = \sigma_y = 1$,$\rho = \rho$。請根據上述機率分配寫出一個進行吉布斯抽樣的函式。首先我們要先決定初始值: ```julia init = [4,3] # Set the initial number of x0 and y0 ``` 接著我們產生一個由初始值組成的陣列用來做等一下的運算。 ```julia x = ones(1+skip_num+n) * init[1] # Create an array of x0 = 4 y = ones(1+skip_num+n) * init[2] # Create an array of y0 = 3 ## ## 1001-element Vector{Float64}: ## 3.0 ## 3.0 ## 3.0 ## ⋮ ## 3.0 ## 3.0 ## 3.0 ``` 上面的`skip_num` 代表我們想要丟棄的值(burn-in points),原因在於如果一開始的初始值在該分配發生機率極小的地方,例如常態分配的最左邊或最右邊,當它收斂回來的時候所經過的值都不會是我們想要的。接著我們寫一個迴圈,利用**取代(replace)陣列元素**的方式進行抽樣。此處不透過`append!` 的原因在於,當我們使用`append!` 時,迴圈每跑一次就要重新檢測陣列長度,這樣會增加運算時間,也會佔記憶體。 ```julia for i in range(2,1+skip_num+n) x[i] = rand(Normal(ρ * y[i-1], 1 - ρ ^ 2)) # Sample x1 by given y0 y[i] = rand(Normal(ρ * x[i], 1 - ρ ^ 2)) # Sample y1 by given x1 end ``` 我們可以來看看`x` 跟 `y` 長怎樣(上面設定 `ρ = 0.2`,`n = 1000` ,`skip_num = 100` )。 ```julia x ## 1101-element Vector{Float64}: ## 4.0 ## -0.04705256736833596 ## 1.3052037427528813 ## -0.06677596402993682 ## ⋮ ## -0.05473150108855082 ## 0.5575666596686812 ## -0.8040756956021986 y ## 1101-element Vector{Float64}: ## 3.0 ## 1.136943703212021 ## 0.1674852002420904 ## ⋮ ## 1.236202469133087 ## -1.5420349774737756 ## 0.0679277556673149 ``` 看起來好像是我們想要的結果,但我們有個地方要檢測一下,就是 $x$ 跟 $y$ 的相關係數(correlation)。 ```julia # using Pkg; Pkd.add("Statistics") using Statistics cor(x, y) ## ## 0.2596178739181784 ``` 誒?怎麼跟我們設定的有點差距啊!不過出現這個結果並不意外,我們試著把樣本數調整到 1000000,並重新計算其相關係數: ```julia cor(x, y) ## ## 0.20038077119079106 ``` 很明顯的,當樣本數越多的時候,相關係數就會越接近我們設定的值。不過接下來還有一個問題:那我們之前設定的`skip_num` 到哪去了?可以看到我們陣列中其實總共有1101筆資料,但其中有100筆(上面設定的burn-in points)是我們不要的資料,所以我們必須把它從陣列裡頭清除(利用`deleteat!`)。 ```julia deleteat!(x, 2:skip_num) deleteat!(y, 2:skip_num) ``` 不過其實可以直接使用索引將這些burn-in points取走: ```julia x = x[1+skip, n] y = y[1+skip, n] ``` 這樣得到的結果就會是在有burn-in points之下我們要的資料了。最後,我們就用`@manipulate` 建立一個具有互動功能的可視化圖型。 ```julia using Distributions, Plots, LinearAlgebra, Statistics, Interact, WebIO @manipulate for n=100:2000, ρ = -0.9:.1:0.9, μy = 0, σx = 1 rng = Xoshiro(1234) skip_num = 100 init = [4,3] x = ones(1+skip_num+n) * init[1] y = ones(1+skip_num+n) * init[2] for i in range(2,1+skip_num+n) x[i] = rand(rng, Normal(ρ * σx * (y[i-1] - μy), σx^2 * (1 - ρ ^ 2))) y[i] = rand(rng, Normal(μy + ρ * (1 / σx) * x[i], 1 - ρ ^ 2)) end deleteat!(x, 2:skip_num) deleteat!(y, 2:skip_num) # Create the suplot in the layout layout = @layout [a{0.6w,0.4h} _ b{0.6w,0.6h} c{0.4w, 0.6h}] # Set plot default default(fillcolor=:lightgrey, markercolor=:white, grid=false, legend=false) plot(layout=layout, link=:both, size=(400, 400), margin=-10Plots.pt) # Show scatter plot, located in position 2 scatter!(x,y, subplot=2, framestyle =:box) # Show scatter plot, located in position 1 and 3, and make position 3 rotate through 90 degree histogram!([x y], subplot=[1 3], orientation=[:v :h], framestyle=:none, bins=min(n, 100), normalize=true) end ``` 注意到我們在程式碼上加上了`rng = Xoshiro(1234)` 的功能,便是讓結果可以是「疊加」的:當 $n$ 增加時,圖型會如何改變。 <div class="likecoin-embed likecoin-button"> <div></div> <iframe scrolling="no" frameborder="0" src="https://button.like.co/in/embed/xiaolong70701/button?referrer=hackmd.io"></iframe> </div>