贝叶斯统计/pymc 学习笔记(二)

Doing Bayesian Data Analysis 后面花了 8 章书讲解了如何用贝叶斯统计实现各种分析和预测任务,可见贝叶斯定理的应用有多广泛。今天读完了 Metric Predictor 的部分并且用 pymc 跑了一遍,在这里回顾一下学到了多少内容。这是我第一次读,而且我也不是统计专业的,有很多没有明白的地方就跳过了,可能笔记里有很多写得不对的地方,等以后把不懂的地方弄明白了再回来检查。

所谓的 Metric Predictor,就是可以比较大小的数值数据,例如身高,体重,成绩等。相关的统计任务有,对比两组数据是否属于同一分布,线性回归等。

注:文里的代码用的是 python3。

T-test

对比两组数据是否属于同一分布,传统统计学的方法是 T 测试,计算 t 值,p 值还有 F 值。贝叶斯统计最初的应用就是估计样本的分布,自然也适用于样本的对比。它的优势在于,我们不需要假定样本是正态分布,而可以使用可能更符合现实的 t 分布。当然用模拟的方法去求值效率上肯定没有直接解方程好了。

pymc 例子:

# 生成两组数据,这里设置两个不同的分布
from scipy import stats

mu1, std1 = 5, 5
group1 = stats.norm.rvs(loc=mu1, scale=std1, size=100)

mu2, std2 = 10, 3
group2 = stats.norm.rvs(loc=mu2, scale=std2, size=100)

然后定义测试模型,均值的先验定义为正态分布,方差定义为均匀分布:

# model
with pm.Model() as ttest:
    mu1e = pm.Normal('mu1', mu=group1.mean(), sd=.1)
    std1e = pm.Uniform('std1', lower=0, upper=10)
    g1 = pm.Normal('group1', mu=mu1e, sd=std1e, observed=group1)

    mu2e = pm.Normal('mu2', mu=group2.mean(), sd=.1)
    std2e = pm.Uniform('std2', lower=0, upper=10)
    g2 = pm.Normal('group2', mu=mu2e, sd=std2e, observed=group2)

    # 需要对比的部分,均值和标准差
    mean_dif = pm.Deterministic('Mean dif', mu1e-mu2e)
    std_dif = pm.Deterministic('Std dif', std1e-std2e)

MCMC,登场~

with ttest:
    start = pm.find_MAP()
    step = pm.Metropolis() # Metropolis 方法在书中有很生动的描述
    trace_ttest = pm.sample(10000, step, start, progressbar=False)

Sampling……Done!

pm.traceplot(trace_ttest[2000::50], vars=['Mean dif', 'Std dif'])
plt.tight_layout()

ttest.png

线性回归,一个自变量

处理线性回归问题,通常指定因变量 Y 属于一个正态分布:

\(Y \sim N(\mu, \sigma)\) , 其中 \(\mu = \alpha + \beta X\)

由于现实问题经常会有 outliner 出现,指定 Y 属于 t-分布会产生更加好的结果。

例子:

# Data
x = np.linspace(0, 20, 20)
a, b = 5, 3 # 需要估计的参数
y = b * x + a + stats.t.rvs(df=1, scale=3, size=np.size(x))

plt.plot(x, y, '.')
plt.xlim((-10, 30))
plt.ylim((-10, 80))

lr1.png

定义模型,用 T 分布来做 Y 的分布,这时,自由度 nu 的先验为指数分布,标准差为均匀分布, alpha 和 beta 为正态分布。如果对变量进行标准化,即使变量变为 0, 方差为 1,那么 alpha 和 beta 的先验均值为 0,但现在这个简单模型,可以从上图看到大概的值。

with pm.Model() as linreg:
    alpha = pm.Normal('alpha', mu=7, sd=2)
    beta = pm.Normal('beta', mu=2, sd=2)
    mu = beta * x + alpha
    nuMinusOne = pm.Exponential('nu', lam=1)
    nu = nuMinusOne + 1
    lam = pm.Uniform('lam', 0, 10)

    my = pm.T('Y', nu=nu, mu=mu, lam=lam, observed=y)

继续 MCMC,这次我们用 NUTS sampling,据说在变量数很多的时候能够大大改善效率。在这里是杀鸡用牛刀了。

with linreg:
    start = pm.find_MAP()
    step = pm.NUTS(scaling=start)
    trace_linreg = pm.sample(10000, step, start=start, progressbar=False)

看看结果,注意 a 和 b 的实际值为 5 和 3

pm.summary(trace_linreg[2000::100], vars=['alpha', 'beta'])
alpha:

  Mean             SD               MC Error         95% HPD interval
  -------------------------------------------------------------------
  
  5.320            1.870            nan              [2.114, 9.089]

  Posterior quantiles:
  2.5            25             50             75             97.5
  |--------------|==============|==============|--------------|
  
  2.319          4.181          4.991          6.765          9.403


beta:

  Mean             SD               MC Error         95% HPD interval
  -------------------------------------------------------------------
  
  3.116            0.149            nan              [2.852, 3.427]

  Posterior quantiles:
  2.5            25             50             75             97.5
  |--------------|==============|==============|--------------|
  
  2.852          3.027          3.117          3.218          3.427

用图片展示,颜色越深,代表确定性越高。

a_p = trace_linreg[2000::100]['alpha']
b_p = trace_linreg[2000::100]['beta']
x_p = np.linspace(-3, 23)
for i in range(len(a_p)):
    y_p = b_p[i] * x_p + a_p[i]
    plt.plot(x_p, y_p, 'k', alpha=.03)

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

plt.xlim((-10, 30))
plt.ylim((-10, 80))

py161752JW.png

线性回归,多个自变量

多个自变量和一个自变量的模型基本是一致的,只是要给每个自变量的 beta 值指定先验。这时候用变量标准化可以简化模型。

下面的例子的数据用的是 Andrew Ng 的 machine learning 课程里面的习题数据。

x = data['X'].flatten()
y = data['y'].flatten()

plt.plot(x, y, 'bx')
plt.xlim((-60, 50))
plt.ylim((-10, 50))

py16175Qei.png

因为变量只有一个,而且这个图看着也像是多项式函数的图像,所以先弄一些多项式 feature,然后再定义我们的模型。

# get polynomial features
x_mat = np.c_[x, (x**2), (x**3)]

# standarize the feature
x_mu = x_mat.mean(axis=0)
x_std = x_mat.std(axis=0)

x_mat[:, 0] = (x_mat[:, 0] - x_mu[0]) / x_std[0]
x_mat[:, 1] = (x_mat[:, 1] - x_mu[1]) / x_std[1]
x_mat[:, 2] = (x_mat[:, 2] - x_mu[2]) / x_std[2]

# also standardize the target
y_mu = y.mean()
y_std = y.std()

y_mat = (y - y_mu) / y_std

# Model
with pm.Model() as polymol:
    b0 = pm.Normal('b0', sd=50)
    b1 = pm.Normal('b1', sd=50, shape=(3,))

    zsigma = pm.Uniform('zsigma', 1.0E-5 , 1.0E-1)
    nuMinusOne = pm.Exponential('nu-1', lam=1/29)
    nu = nuMinusOne + 1

    mu = b0 + pm.dot(b1, x_mat.T)

    obv = pm.T('observed', mu=mu, nu=nu, lam=1/(zsigma**2), observed=y_mat)

    beta0_ = (y_std * b0) + y_mu - y_std * pm.sum(b1 * x_mu / x_std, axis=0)
    beta1_ = y_std * (b1 / x_std)

    beta0 = pm.Deterministic('beta0', beta0_)
    beta1 = pm.Deterministic('beta1', beta1_)

继续 MCMC,这个例子稍微复杂一点。其实全部用 NUTS 或者 Metropolis 也是可以的。

with polymol:
    start = pm.find_MAP() # Find starting value by optimization
    step1 = pm.NUTS([b1])
    step2 = pm.Metropolis([b0, zsigma, nu])
    trace = pm.sample(10000, [step1, step2], start, progressbar=False)

看看结果如何。

pm.summary(trace[2000::50], vars=['beta0', 'beta1'])
beta0:

  Mean             SD               MC Error         95% HPD interval
  -------------------------------------------------------------------
  
  5.238            0.602            0.056            [4.122, 6.366]

  Posterior quantiles:
  2.5            25             50             75             97.5
  |--------------|==============|==============|--------------|
  
  4.134          4.864          5.226          5.688          6.456


beta1:

  Mean             SD               MC Error         95% HPD interval
  -------------------------------------------------------------------
  
  0.384            0.038            0.004            [0.308, 0.456]
  0.010            0.001            0.000            [0.009, 0.011]
  0.000            0.000            0.000            [0.000, 0.000]

  Posterior quantiles:
  2.5            25             50             75             97.5
  |--------------|==============|==============|--------------|
  
  0.308          0.361          0.383          0.410          0.456
  0.009          0.010          0.010          0.011          0.011
  0.000          0.000          0.000          0.000          0.000

下图仍然是颜色越深的线,确定性越高。

beta0 = trace['beta0'][2000::50]
beta1 = trace['beta1'][2000::50]

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

x_t = np.linspace(-55, 55)
x_pred = np.c_[x_t, (x_t**2), (x_t**3)]

for i in range(len(beta0)):
    y_p = np.dot(beta1[i], x_pred.T) + beta0[i]
    plt.plot(x_pred[:, 0], y_p, 'k-', alpha=.02)

polyfit.png

可以看到,三次项的系数基本等于 0,说明这一项对预测几乎没有作用。这一点可以帮助我们选择哪些变量。另一个方法是,给每个变量乘一个参数,这个参数符合博努利分布,如果最后结果这个参数的 p 小于一个阀值就可以认为对应的变量影响力太小可以忽略。

以上。