# 蒙地卡羅模擬之資料分析方法
:::info
本文主旨為介紹常見蒙地卡羅之資料處理方法
目的為增加蒙地卡羅所產生資料之可靠性與獲得更好的分析結果
文中會提到的技術有自相關函數、分箱及拔靴法
本文所使用之程式為 Python, Julia
:::
:::warning
本文中的程式僅作為示範,實際分析請自行撰寫以得到更正確的結果或更好的效能
:::
## 1. 自相關函數(Autocorrelation function)
由於馬可夫鍊蒙地卡羅方法的特性,下一個狀態完全取決於上一個狀態,因此相鄰的兩個狀態之間會有很強的相關性,但在做統計時數據需要有獨立性,要解決這個問題我們可以計算求得之觀測量的自相關函數,並透過積分該函數來取得這筆資料的自相關時間,若資料中的兩個數據之時間(步數)間隔大於2倍以上的自相關時間,我們就可以說這兩個數據的相關性是可忽略的,這個操作稱作去相關(decorrelate)
自相關函數定義為
\begin{equation}
f_A(\tau) = {\Gamma (\tau ) \over \Gamma (0)}
\end{equation}
其中
\begin{equation}
\Gamma (\tau)={1 \over N- \tau} \sum^{N- \tau-1}_{i=0}[(O[i]- \bar{O}) \times (O[i+ \tau] - \bar{O})]
\end{equation}
其中$O$為觀測量,$N$為總觀測數,$i$表示第$i$個觀測量,而$\tau$為兩觀測量的間距
>[!Note]
> 在統計學中,共變異數(協方差)的定義為
> \begin{equation}
> \mathrm{cov}(X,Y)=\langle (X-\bar{X})(Y-\bar{Y}) \rangle
> \end{equation}
> 其中$\bar{X}$表示$X$的平均值,共變異數表示了兩個變數間線性相關的趨勢,若其中$Y=X$即為變異數
> ::: warning
> 注意:共變異數為0不表示兩變數無關,只能表示兩變數沒有線性關係,其有可能有非線性關係,如$\mathrm{cov}(X,X^2)=0$
> :::
> 在隨機過程$\{X_t\}$中,自共變異數定義為
> \begin{equation}
> K_{XX}(t_1,t_2)=\mathrm{cov}(X_{t_1},X_{t_2})=\langle (X_{t_1}-\overline{X_{t_1}})(X_{t_2}-\overline{X_{t_2}}) \rangle
> \end{equation}
> 若$\{X_t\}$為弱平穩(WSS)過程(平均值與自共變異數不隨時間變化)則有
> \begin{equation}
> \overline{X_{t_1}}=\overline{X_{t_2}}=\overline{X}
> \end{equation}
> 及
> \begin{equation}
> K_{XX}(t_1,t_2)=K_{XX}(t_2-t_1,0) \equiv K_{XX}(t_2-t_1) = K_{XX}(\tau)
> \end{equation}
> 而熱平衡後的靜態(目標分布不隨時間改變)蒙地卡羅通常為弱平穩,將弱平穩下的自共變異數歸一化即得到上面定義之自相關函數$f_A(\tau)$
一般情況下,自相關函數呈指數衰減(exponential decay)
\begin{equation}
f_A(\tau) ∼ e^{-\tau / \tau_A}
\end{equation}
其中$\tau_A$為自相關時間
自相關時間可以透過積分自相關函數算出
\begin{equation}
\int^\tau_0 d\tau'f_A(\tau') ∼ \int^\tau_0 d\tau' e^{-\tau / \tau_A} ∼ \tau_A \ \text{if} \ \tau \ ≫ \tau_A
\end{equation}
在數值計算中,自相關函數之積分定義為
\begin{equation}
\Theta (\tau)={1 \over 2}+\sum^{\tau}_{\tau'=1}f_A(\tau')
\end{equation}
當$\tau$足夠大至$\Theta$之值穩定時,我們即可得到自相關時間$\tau_A=\Theta$,在實際運算中,我們將$\tau$選在$\Gamma(\tau)$無法被正確計算前的點($\Gamma(\tau)$不再滿足指數衰減)
>[!Note]
>我們有弱平穩自共變異數的定義
>\begin{equation}
K(\tau)=\langle (O_i- \bar{O})(O_{i+ \tau} - \bar{O})\rangle
\end{equation}
>可以注意到$K(\tau)=K(-\tau)$,$-\infty < \tau<\infty$。因此自相關函數的積分
>\begin{align}
\Theta=\sum^{\infty}_{\tau'=-\infty}f_A(\tau') &= f_A(0)+2\sum^{\infty}_{\tau'=1}f_A(\tau')\\
&= 2 \left( {1 \over 2}f_A(0)+\sum^{\infty}_{\tau'=1}f_A(\tau') \right)
\end{align}
>其中$f_A(0)=1$。而我們只考慮$\tau >\tau' \ge 0$,因此有上述積分形式之定義
:::warning
不同的觀測量有不同的自相關時間,因此在處理數據時應分開處理所有觀測量,或直接以自相關時間最大的觀測量為標準進行數據處理
:::
在做去相關時要求至少間隔2倍以上的自相關時間是由於計算自相關時間會有很大的誤差,其誤差可以用Madras-Sokal公式估算
\begin{equation}
(\Delta \Theta(\tau))^2={4\tau+2 \over N}\Theta(\tau)^2
\end{equation}
由於篇幅較長,此文章中僅對自相關函數之計算做示範,並不包含誤差估算
### Python 分析實作
```python=
import numpy as np
#定義Γ函數
def Gamma(A: np.ndarray, tau: int) -> float:
res = 0.0
N = len(A)
A_mean = np.mean(A)
for i in range((N-tau)):
res += ((A[i]-A_mean)*(A[i+tau]-A_mean))
return res/(N-tau)
#計算自相關函數(用來判斷tau需要多大)
def autofunc(A: np.ndarray) -> np.ndarray:
N = len(A)
res = np.zeros(N)
Gamma_0 = Gamma(A, 0)
for i in range(N):
res[i] = Gamma(A, i)/Gamma_0 # res[i] = f_A(i), 注意這裡包含 f_A(0)
return res
#計算自相關時間
def autotime(autofunc: np.ndarray, tau: int) -> np.ndarray:
return 0.5 + np.sum(autofunc[1:tau+1])
```
### Python 分析實作2(向量化版本)
```python=
import numpy as np
#向量化定義Γ函數
def Gamma_vec(A: np.ndarray, tau: int) -> float:
N = len(A)
A_mean = np.mean(A)
return np.mean((A[:N-tau] - A_mean) * (A[tau:] - A_mean))
#向量化計算自相關函數
def autofunc_vec(A: np.ndarray) -> np.ndarray:
N = len(A)
Gamma_0 = Gamma_vec(A, 0)
taus = np.arange(N) # 計算 f_A(0) 至 f_A(N-1)
return np.array([Gamma_vec(A, tau)/Gamma_0 for tau in taus])
```
### Julia 分析實作
:::warning
注意:Julia陣列編號由1開始
:::
```julia=
using Statistics
#定義Γ函數
function Gamma(A::Vector, tau::Int)
res = 0.0
N = length(A)
A_mean = mean(A)
for i in 1:(N-tau)
res += ((A[i]-A_mean)*(A[i+tau]-A_mean))
end
return res/(N-tau)
end
#計算自相關函數
function autofunc(A::Vector)
N = length(A)
res = zeros(N)
Gamma_0 = Gamma(A, 0)
for i in 1:N
res[i] = Gamma(A, i)/Gamma_0 # res[i] = f_A(i), 注意這裡沒有 f_A(0)
end
return res
end
#計算自相關時間
function autotime(autofunc::Vector, tau::Int)
return 0.5 + sum(autofunc[1:tau])
end
```
Julia本身擁有極為良好的編譯器,不用過度追求向量化
### 補充
>[!Tip]小技巧
>注意到離散化捲積有定義
>\begin{equation}
>y[n]=f[n] * g[n]=\sum^{M-1}_{m=0} f[n-m]g[m]
>\end{equation}
>對比$\Gamma$函數定義
>\begin{equation}
>\Gamma (\tau) = {1 \over N- \tau} \sum^{N- \tau-1}_{i=0}[(O[i]- \bar{O}) \times (O[i+ \tau] - \bar{O})]
>\end{equation}
>移項得
>\begin{equation}
>(N-\tau)\Gamma(\tau) = \sum^{N-\tau-1}_{i=0}O'[\tau+i]O'[i]
>\end{equation}
>其中$O'=O-\bar{O}$,可以看出其形式與離散化捲積類似,事實上我們僅需將左側$O'$做翻轉即與上述定義相同。而對於離散化捲積我們有傅立葉轉換
>\begin{equation}
>y[n]=f[n]*g[n] \leftrightarrow Y[f]=F[f]G[f]
>\end{equation}
>此兩函數在時域$(n)$做捲積相當於此兩函數的傅立葉轉換在頻域$(f)$相乘,因此我們有
>\begin{equation}
>y[n]=\mathcal{F^{-1}}(\mathcal{F}(f[n])\mathcal{F}(g[n]))
>\end{equation}
>而對於翻轉之$O'$我們可以將其想為$f(-x)$,而我們有
>\begin{equation}
>G(k)=\int^\infty_{-\infty}f(-x)e^{-2\pi ikx}dx=\int^\infty_{-\infty}f(u)e^{2\pi ikx}du=\overline{F(k)}
>\end{equation}
>因此自相關函數的計算簡化為
>\begin{equation}
>f_A(\tau)={1\over N \sigma_A^2}\mathcal{F^{-1}}(|\mathcal{F}(O')|^2)
>\end{equation}
>利用此方法可以將自相關函數計算的時間複雜度$\mathcal{O}(N^2)$減少至$\mathcal{O}(N\mathit{log}N)$,在資料量龐大時可以有效減少計算時間。
>:::warning
>要注意由於FFT方法有週期性邊界條件,且分母為$N$而非$N-\tau$,因此此方法在接近數據尾端時及$\tau$較大時有誤,如果有較大的資料量且$\tau_A \ll N$則偏差可以忽略,但仍建議使用補零法(Zero-padding)來避免此問題
>:::
>```julia=
>using FFTW
># 此程式未使用補零
>function autofunc_fft_large(data::Vector{Float64})
> N = length(data)
> x = data .- mean(data) # O - O_bar
> fx = fft(x)
> acf = ifft(abs2.(fx)) ./ N # FFT(O_rev) = FFT(O)*, normalize
> return real(acf[2:end] ./ acf[1]) # 自相關,跳過 tau=0
>end
>```
## 2. 分箱(Binning)
在做完去相關後,我們的數據量會大幅減少,會有許多數據被浪費掉,因此我們可以透過分箱的方式充分利用所有數據。分箱的方法為
* 若原資料$A$有自相關時間$\tau_A$,將連續的每$n\tau_A$個數據進行平均,其中$n \ge 2$
* 由上步得到新資料$O$,而$O$的數據量$N_O = {N_A \over n\tau_A}$
### Python 分析實作
```python=
import numpy as np
def bin(A: np.ndarray, ntau: int) -> np.ndarray:
return A.reshape(-1, ntau).mean(axis=1)
```
### Julia 分析實作
```julia=
using Statistics
function bin(A::Vector, ntau::Int)
return vec(mean(reshape(A, ntau, :), dims=1))
end
```
## 3. 拔靴法(Bootstrap)
蒙地卡羅模擬通常需要大量計算資源。若缺乏高效算法或足夠的運算能力,且觀測量具有較大的自相關時間,則為了取得足夠的資料量,往往必須花費極長的計算時間。
為了在有限的資源下,更好的估計物理量的誤差以獲得更好的分析結果,除了再花時間增加更多的數據外,我們可以透過一些數學方法來對數據進行處理,常見的方法有拔靴法及刀切法(Jackknife),這兩種方法都是對已有的資料進行重新抽樣,但運作原理不同,此文中僅對拔靴法進行示範
>[!Important]重點
>拔靴法能有效給出擁有更好標準差之資料,但並不會改變平均值,且即使此方法看似有數據造假之疑慮,但此方法並沒有憑空捏造數據,所有的數據皆由原始資料中產生。拔靴法僅透過增加數據量的方法改變資料的精確度(precision)----數據之間靠近的程度,而不會改變準確性(Accuracy)----數據與真實值接近的程度
拔靴法的操作流程如下
* 從總數據量$N$的原始資料$A$中均勻隨機且可重複的取出$N$個數據,其中$A=\{ a_1...a_N\}$,抽出後得$M=\{m_1...m_N\}$
* 重複上述步驟$\alpha N$次,其中$\alpha$通常為4~10間的整數 (抽樣次數的選擇非固定,視情況也可以改為如固定1000次等)
* 在上一步中,我們會得到$\alpha N$個資料集$\{M_1...M_{\alpha N}\}$,其中每個$M_i$皆含有$N$個數據
* 將每個$M_i$做平均得到$\alpha N$個數據$\{ \langle M_1 \rangle...\langle M_{\alpha N} \rangle \} \equiv G$
* 由$G$即可計算出拔靴後的標準差$\sigma_G$與平均值$\langle G \rangle$,應滿足$\langle G \rangle \approx \langle A \rangle$且$\sigma_G \lt \sigma_A$
>[!Note]
>若資料具有強關聯性或資料過少可能導致拔靴法結果不如預期,應先計算自相關函數確保拔靴法結果可靠性
其中$\alpha$的大小可以藉由持續加大$\alpha$看$\sigma_G$是否繼續變小,若沒有明顯差異則不需增加,減少計算量。若要在拔靴法中計算觀測量,則應利用步驟三之數據$M_i$進行計算得到$\alpha N$個觀測量$O_i$,並將每個$O_i$取平均得到有$\alpha N$個$\langle O_i \rangle$的資料集$G_O$,再進行第五步之分析
### Python 分析實作
```python=
def bootstrap(A: np.ndarray, alpha: int) -> np.ndarray:
N = len(A)
res = np.zeros(N*alpha)
for i in range(N*alpha):
r = np.random.choice(N, N)
res[i]=np.mean(A[r])
return np.array(res)
```
### Julia 分析實作
```julia=
using Statistics
function bootstrap(A::Vector, alpha::Int)
N = length(A)
res = zeros(N*alpha)
for i in 1:(N*alpha)
res[i] = mean(rand(A, N))
end
return res
end
```
>[!Warning]注意
>如果資料為2維或更多,在做拔靴時應確保其他維度所抽出之數據皆源於同一筆資料,否則將破壞拔靴法的正確性
>例:實做中,我們通常有資料集
>\begin{equation}
>A(t)=\begin{pmatrix}
a_{1}(t_1) & a_{1}(t_2) & \cdots & a_{1}(t_n) \\
a_{2}(t_1) & a_{2}(t_2) & \cdots & a_{2}(t_n) \\
\vdots & \vdots & \ddots & \vdots\\
a_{m}(t_1) & \cdots & \cdots & a_{m}(t_n)
\end{pmatrix}
>\end{equation}
>其中下標$i$表示第$i$筆資料,每筆資料皆有$n$個數據$for \ t\in\{t_1...t_n\}$
>常見的做法為抽取時一次抽取整組數據$M_i=\{ a_{i}(t_1), a_{i}(t_2)...a_{i}(t_n)\}$
## 參考資料
[1] Hsiao Ho, _Lattice Studies of the Composite Higgs Model on $ \mathrm{Sp(4)} $ Gauge Theories._ (2024)
[2] C.-J. David Lin, Kenji Ogawa, Hiroshi Ohkic and Eigo Shintani, _Lattice study of infrared behaviour in $ \mathrm{SU(3)} $ gauge theory with twelve massless flavours_, JHEP08(2012)096