貝葉斯分析之利用線性迴歸模型理解並預測數據(一)

這一節主要講一元線性迴歸模型
在這裏插入圖片描述
問題:利用給定的數據建立 y 與 x 之間的線性模型 y=α+βxy=\alpha+\beta x ?

1. 構造出數據集

先導入相應的一系列庫

%matplotlib inline
import pymc3 as pm
import numpy as np
import pandas as pd
import scipy.stats as stats
import matplotlib.pyplot as plt
import seaborn as sns
palette = 'muted'
sns.set_palette(palette); sns.set_color_codes(palette)
np.set_printoptions(precision=2)
pd.set_option('display.precision', 2)
sns.set()

假設一個線性模型: y=2.5+0.9xy=2.5+ 0.9 * x,在生成數據時加一個擾動項(eps_real)

np.random.seed(1)
N = 100
alfa_real = 2.5
beta_real = 0.9
eps_real = np.random.normal(0, 0.5, size=N)

x = np.random.normal(10, 1, N)
y_real = alfa_real + beta_real * x 
y = y_real + eps_real

numpy.random.normal(loc=0.0, scale=1.0, size=None)
loc:float, 此概率分佈的均值(對應着整個分佈的中心center)
scale:float,此概率分佈的標準差(對應於分佈的寬度,scale越大越矮胖,scale越小越瘦高)
size:int or tuple of ints,輸出的shape,默認爲None,只輸出一個值(注意是個長度爲 size 的向量)

現在看看數據分佈與真實的線性迴歸模型

plt.figure(figsize=(10,5))
plt.subplot(1,2,1)
plt.plot(x, y, 'b.')
plt.xlabel('$x$', fontsize=16)
plt.ylabel('$y$', fontsize=16, rotation=0)
plt.plot(x, y_real, 'k')
plt.subplot(1,2,2)
sns.kdeplot(y)
plt.xlabel('$y$', fontsize=16)
plt.tight_layout()
plt.savefig('B04958_04_02.png', dpi=300, figsize=(5.5, 5.5))

在這裏插入圖片描述

2. 建立線性迴歸模型

with pm.Model() as model:
    alpha = pm.Normal('alpha', mu=0, sd=10)
    beta = pm.Normal('beta', mu=0, sd=1)
    epsilon = pm.HalfCauchy('epsilon', 5)

    mu = pm.Deterministic('mu', alpha + beta * x)
    y_pred = pm.Normal('y_pred', mu=mu, sd=epsilon, observed=y)
    
    start = pm.find_MAP() 
    step = pm.Metropolis() 
    trace = pm.sample(10000, step, start, nchains=1)
pm.traceplot(trace)

先看看模型擬合的效果,左圖爲核密度估計(Kernel Density Estimation,KDE)圖,右圖描述了每一步採樣過程中得到的採樣值。
在這裏插入圖片描述
注意:

  1. ϵ\epsilon 具有較好的混合度;
  2. KDE圖做了歸一化(使得概率密度積分爲1),因此縱軸數值有很大差別。

再分析一下 α\alpha, β\beta 自相關性:
在這裏插入圖片描述可以發現 α\alpha, β\beta 有很糟糕的自相關性。
請思考:爲什麼 α\alpha, β\beta 自相關性很強? 因爲在使用“最小二乘法”的條件下,不論用哪條擬合的直線都會經過一點,採樣點的均值點xˉ,yˉ( \bar{x},\bar{y})


基於均方誤差(SE,即對誤差平方求和)最小化來進行模型求解的方法稱爲“最小二乘法”。在線性迴歸中,最小二乘法就是試圖找到一條直線,使得所有樣本到直線上的歐式距離之和最小。
那麼這個求解方法到底是什麼呢?其實就是對 ww, bb 求導!
E(w,b)w=2(wi=1mxi2i=1m(yib)xi) \frac{\partial E_{(w,b)}} {\partial w} =2(w\sum_{i=1}^{m}x_i^2-\sum_{i=1}^{m}(y_i-b)x_i) E(w,b)b=2(mbi=1m(yiwxi)) \frac{\partial E_{(w,b)}} {\partial b} =2(mb-\sum_{i=1}^{m}(y_i - wx_i))
進行求解:
w=i=1myi(xixˉ)i=1mxi21m(i=1mxi)2 w = \frac{ \sum_{i=1}^{m} y_i(x_i-\bar x)}{\sum_{i=1}^{m} x_i^2- \frac{1}{m}(\sum_{i=1}^{m} x_i)^2} b=1mi=1m(yiwxi) b= \frac{1}{m}\sum_{i=1}^{m} (y_i - wx_i)
將求得的 ww, bb 帶入模型有:yˉ=wxˉ+b\bar y = w\bar x + b


由上可知,擬合直線的過程相當於將直線固定在數據的中心(均值點)上進行旋轉,斜率越大截距越小,因此兩個參數是相關的。
現將後驗畫出來,可以發現 α\alpha, β\beta 有較強自相關性。

sns.kdeplot(trace['alpha'], trace['beta'])
plt.xlabel(r'$\alpha$', fontsize=16)
plt.ylabel(r'$\beta$', fontsize=16, rotation=0)
plt.savefig('B04958_04_05.png', dpi=300, figsize=(5.5, 5.5));

在這裏插入圖片描述

3. 解決高自相關性

3.1 中心化或者標準化

x=xxˉ x^{'}=x-\bar{x}
中心化使得 xx^{'} 的中心在 0 附近,從而使得修改斜率時旋轉點變成了截距點,參數空間也會變得不那麼自相關。直觀上解釋就是不再必經均值點,即 yˉ=wxˉ+b\bar y = w\bar x + b 不再成立。

x=xxxsd x^{'}=\frac{x-x^{'}}{x_{sd}} y=yyysd y^{'}=\frac{y-y^{'}}{y_{sd}}
標準化好處之一是我們對數據使用了相同的弱先驗,而不必關心數據的具體值域有多大。

3.2 更換採樣方法

  1. NUTS算法
  2. Metropolis算法
with pm.Model() as model_n:
    alpha = pm.Normal('alpha', mu=0, sd=10)
    beta = pm.Normal('beta', mu=0, sd=1)
    epsilon = pm.HalfCauchy('epsilon', 5)
    
    mu = pm.Deterministic('mu', alpha + beta * x)

    y_pred = pm.Normal('y_pred', mu=mu, sd=epsilon, observed=y)
    
    start = pm.find_MAP() 
    # 更改採樣方法?
    step = pm.NUTS() 
    trace_n = pm.sample(2000, step=step, start=start, nchains=1)
pm.traceplot(trace_n, varnames)

看看模型擬合的效果,可以發現各個參數都有較好的混合度。
在這裏插入圖片描述
現在再看看參數的自相關性與最後結果
在這裏插入圖片描述
在這裏插入圖片描述

4. 對後驗進行解釋和可視化

注意:以下後驗均是基於更改採樣方法(採用NUTS算法)得出的後驗。

4.1 後驗的不確定性

plt.plot(x, y, 'b.');

idx = range(0, len(trace_n['alpha']), 10)
plt.plot(x, trace_n['alpha'][idx] + trace_n['beta'][idx] *  x[:,np.newaxis], c='gray', alpha=0.5);

plt.plot(x, alpha_m + beta_m * x, c='k', label='y = {:.2f} + {:.2f} * x'.format(alpha_m, beta_m))

plt.xlabel('$x$', fontsize=16)
plt.ylabel('$y$', fontsize=16, rotation=0)
plt.legend(loc=2, fontsize=14)
plt.show()

在這裏插入圖片描述
其中半透明的的直線表示後驗的不確定性,可以發現中間部分的不確定性較低,不過直線並沒有相交於一個點(後驗並不強制所有的直線都穿過均值點)。

4.2 預測值y^\hat y的 HPD 區間

ppc = pm.sample_ppc(trace_n, samples=1000, model=model_n)

plt.plot(x, y, 'b.')
plt.plot(x, alpha_m + beta_m * x, c='k', label='y = {:.2f} + {:.2f} * x'.format(alpha_m, beta_m))

sig0 = pm.hpd(ppc['y_pred'], alpha=0.5)[idx]
sig1 = pm.hpd(ppc['y_pred'], alpha=0.05)[idx]
plt.fill_between(x_ord, sig0[:,0], sig0[:,1], color='gray', alpha=1)
plt.fill_between(x_ord, sig1[:,0], sig1[:,1], color='gray', alpha=0.5)

plt.xlabel('$x$', fontsize=16)
plt.ylabel('$y$', fontsize=16, rotation=0)
plt.show()

對預測值進行採用,將50%HPD區間用深灰色區域表示,將95%HPD區間用淺灰色表示。
在這裏插入圖片描述

5. 皮爾遜相關係數

對於皮爾遜相關係數 r,我們需要了解以下幾點:

  1. 衡量兩個變量之間線性相關性(因此 r = 0 表示沒有線性關係,可能存在其他非線性關係);
  2. r=βσ(x)σ(y)r= \beta\frac{\sigma(x)}{\sigma(y)},其中 β\beta 爲斜率;
  3. 決定係數是皮爾遜相關係數的平方。

5.1 利用PyMC3計算 r

with pm.Model() as model_n:
    alpha = pm.Normal('alpha', mu=0, sd=10)
    beta = pm.Normal('beta', mu=0, sd=1)
    epsilon = pm.HalfCauchy('epsilon', 5)
    
    mu = alpha + beta * x
    y_pred = pm.Normal('y_pred', mu=mu, sd=epsilon, observed=y)

    rb = pm.Deterministic('rb', (beta * x.std() / y.std()) ** 2)

    y_mean = y.mean()
    ss_reg = pm.math.sum((mu - y_mean) ** 2)
    ss_tot = pm.math.sum((y - y_mean) ** 2)
    rss = pm.Deterministic('rss', ss_reg/ss_tot)

    start = pm.find_MAP()
    step = pm.NUTS()
    trace_n = pm.sample(2000, step=step, start=start, nchains=1)
pm.traceplot(trace_n)

在這裏插入圖片描述

在這裏插入圖片描述

5.2 根據多元高斯分佈計算 r

先看一下高斯分佈,其中標準差 σ=1\sigma=1

sigma_x1 = 1
sigmas_x2 = [1, 2]
rhos = [-0.99, -0.5, 0, 0.5, 0.99]

k, l = np.mgrid[-5:5:.1, -5:5:.1]
pos = np.empty(k.shape + (2,))
pos[:, :, 0] = k; pos[:, :, 1] = l

f, ax = plt.subplots(len(sigmas_x2), len(rhos), sharex=True, sharey=True, figsize=(12, 8))
# f.figure(figsize=(5, 1))
for i in range(2):
    for j in range(5):
        sigma_x2 = sigmas_x2[i]
        rho = rhos[j]
        cov = [[sigma_x1**2, sigma_x1*sigma_x2*rho], [sigma_x1*sigma_x2*rho, sigma_x2**2]]
        rv = stats.multivariate_normal([0, 0], cov)
        ax[i,j].contour(k, l, rv.pdf(pos))
        ax[i,j].plot(0, 0, 
        label="$\\sigma_{{x2}}$ = {:3.2f}\n$\\rho$ = {:3.2f}".format(sigma_x2, rho),  alpha=0)
        ax[i,j].legend()
ax[1,2].set_xlabel('$x_1$')
ax[1,0].set_ylabel('$x_2$')
plt.show()

在這裏插入圖片描述
由於並不知道這些標準差(組成的協方差矩陣),因此我們可以通過設置標準差的先驗,然後利用這些值手動構造協方差矩陣。

data = np.stack((x, y)).T

with pm.Model() as pearson_model:
    
    mu = pm.Normal('mu', mu=data.mean(0), sd=10, shape=2)
    
    sigma_1 = pm.HalfNormal('simga_1', 10)
    sigma_2 = pm.HalfNormal('sigma_2', 10)
    rho = pm.Uniform('rho', -1, 1)
    
    cov = pm.math.stack(([sigma_1**2, sigma_1*sigma_2*rho], [sigma_1*sigma_2*rho, sigma_2**2]))
    
    y_pred = pm.MvNormal('y_pred', mu=mu, cov=cov, observed=data)
    
    start = pm.find_MAP()
    step = pm.NUTS(scaling=start)
    trace_p = pm.sample(1000, step=step, start=start, nchains=1)

pm.traceplot(trace_p)
plt.show()

在這裏插入圖片描述

項目源碼:https://github.com/dhuQChen/BayesianAnalysis

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章