《Python贝叶斯分析》


  • 贝叶斯简介

先验:体现了对参数的了解,设定参数的分布

似然:引入观测数据,反映在给定参数下,观测数据的可信度

后验:基于观测数据,获得参数的分布和可信度

贝叶斯定理:后验正比于似然乘以先验

  • 抛硬币案例

以抛硬币为例

参数为硬币朝上的概率A(表示抛一次硬币时正面朝上的可能性),定义为P(A),此为先验(此处指的是概率P的概率)

似然为已知该参数A,观测值B(N次抛硬币正面朝上的次数)对应的概率,也就是已知硬币朝上的概率,实验中出现这个观测值的概率

后验则为通过一系列观测值B,获得参数A的分布

首先选择似然分布,此处选择了二项分布,表示N次抛硬币实验y次正面朝上的概率分布

(似然是指的观测值的可能分布)

(此时二项分布表达式内既包含了参数A,也包含了观测值B,也包含了试验次数N)

实际上就是已知硬币朝上的概率P(参数A),实验了N次,求实验中发生y次硬币朝上(参数B)的概率

其次为参数A选择先验分布,其只与参数A直接相关

选用了beta分布,因为其输出为0到1,与P一致

计算后验,将似然去乘以先验

由于只考虑了其正比的比例,所以可以把与A和B不相关的系数都去掉,得到的后验分布表达式和先验分布很相似

也是一个beta分布,此时就可以使先验beta分布的系数和后验beta分布的系数构成一个等式,后验的分布就可以使用先验的系数

  • 注意点

贝叶斯分析的结果是后验分布而不是某个值,描述了根据给定数据和模型得到不同数值的可能性

后验最可能的值是根据后验分布的形态决定的(也就是后验分布的峰值)

后验分布的离散程度与我们对参数的不确定性有关,分布越离散,不确定性越大

给定足够多的数据时,两个不同先验的贝叶斯模型会趋近于相同的结果,这个过程也称之为收敛

不同后验收敛到相同分布取决于数据和模型

  • 先验的选择

使用信息量较小的先验,比如只使用先验来限定其取值范围等等,称为正则化先验

可以使用多个先验来验证先验的正确性

  • 最大后验密度区间(HPD)

包含一定比例概率密度的最小区间,一般有95%HPD或者98%HPD

如果HPD区间为[2,5],其含义是参数位于[2,5]的概率是0.95

在贝叶斯学派中,可以告诉我们参数取值的概率,这在频率学中是不可能的

因为频率学认为参数是一个固定值,置信区间只能包含或者不包含参数的真实值

def naive_hpd(post):
    sns.kdeplot(post)
    HPD = np.percentile(post, [2.5, 97.5])
    print(HPD)
    plt.plot(HPD, [0, 0], label='HPD {:.2f} {:.2f}'.format(*HPD), 
      linewidth=8, color='k')
    plt.legend(fontsize=16);
    plt.xlabel(r"$\theta$", fontsize=14)
    plt.gca().axes.get_yaxis().set_ticks([])

    
np.random.seed(1)
post = stats.beta.rvs(5, 11, size=1000)  #根据beta分布(5,11),生成数量为1000的array
naive_hpd(post)
plt.xlim(0, 1)
plt.show()

 

 实际上是找到2.5,97.5概率对应的值即可

  • 后验预测检查

一旦获得后验分布,就可以根据后验获得未来的数据来进行预测

通过比较未来数据和已有数据,可以进行一致性检查

  •  抛硬币代码实现
theta_real = 0.35  #参数真值
trials = [0, 1, 2, 3, 4, 8, 16, 32, 50, 150]   # 实验次数
data = [0, 1, 1, 1, 1, 4, 6, 9, 13, 48]  # 每次实验的正面朝上的次数

beta_params = [(1, 1), (0.5, 0.5), (20, 20)]  # beta先验分布的参数对
dist = stats.beta
x = np.linspace(0, 1, 100)    # 0到1之间均匀采样100个数,用于计算后验分布的概率密度
print(x)

for idx, N in enumerate(trials):  # 一次循环表示一次实验过程
    print(idx)
    print(N)
    if idx == 0:
        plt.subplot(4,3, 2)
    else:
        plt.subplot(4,3, idx+3)
    y = data[idx]   #每一次实验对应的实验数据
    print(y)
    for (a_prior, b_prior), c in zip(beta_params, ('b', 'r', 'g')):  # 使用不同的先验参数,绘制三张图,分别对应蓝色,红色,绿色
        p_theta_given_y = dist.pdf(x, a_prior + y, b_prior + N - y)  # 将beta先验分布参数更换为后验分布的参数(此处就相当于引入了观测值y及N),并求解x对应的概率密度,此处已经获得了后验分布的参数
        print(p_theta_given_y)
        plt.plot(x, p_theta_given_y, c)
        plt.fill_between(x, 0, p_theta_given_y, color=c, alpha=0.6)

    plt.axvline(theta_real, ymax=0.3, color='k')  # 用竖直黑线绘制出参数的真值
    plt.plot(0, 0, label="{:d} experiments\n{:d} heads".format(N, y), alpha=0)
    plt.xlim(0,1)
    plt.ylim(0,12)
    plt.xlabel(r"$\theta$") 
    plt.legend()
    plt.gca().axes.get_yaxis().set_visible(False)
plt.tight_layout()
plt.show()

 

 观测值B包含了实验次数(experiments)以及朝上次数(heads)

将值代入后验分布的参数中,并均匀采样参数A的可能值导入后验分布中,即得到了参数A每个可能值的概率密度分布

核心为

p_theta_given_y = dist.pdf(x, a_prior + y, b_prior + N - y)

此处就实现了使用观测值来更改先验,使其变成后验,从而得到参数的可能值分布

(参数的后验由先验和观测值共同组成)

  •  自动的后验

实际上通过分析方法获得后验分布在实际中是很难成立的

通过其他非分析解的方法,也可以对后验进行计算

非马尔可夫方法:

网格计算,二次近似,变分方法(用简单的分布来近似后验)

马尔科夫方法:

马尔科夫链蒙特卡洛(MCMC)

蒙特卡洛:一种随机采样的方法

马尔科夫链:每个状态转移到其他状态的概率只与当前状态有关,此状态链就成为马尔科夫链

为了找到细节平衡的马尔科夫链,可以使用Metropolis-Hasting算法采样到一条链

该算法拥有自己的方式(即拒绝和接受的标准,采样是随机的,但是接受和拒绝的标准是人为的)(接收新的值的概率正比于新旧值之比)来确定采样方向,从而得到一条采样轨迹或者采样链

采样值就是后验的近似,出现次数最多的值就是后验中最可能的值

混合蒙特卡洛方法:避免采样过慢且结果自相关,需要计算梯度

NUTS算法是其中一种HMC算法,不需要手动设置参数

  • 使用网格算法硬币案例
def posterior_grid(grid_points=100, heads=6, tosses=9):
    """
    A grid implementation for the coin-flip problem
    """
    # 参数构成的网格(0和1之前构成的网格)
    grid = np.linspace(0, 1, grid_points)
    print(grid)

    # 定义先验,将5重复网格数次
    prior = np.repeat(5, grid_points)  
    print(prior)
    #prior = (grid  <= 0.4).astype(int)  # truncated
    #prior = abs(grid - 0.5)  # "M" prior

    # 计算每个网格点的似然值,引入可观测值和网格点
    likelihood = stats.binom.pmf(heads, tosses, grid)
    print(likelihood)

    # 计算似然乘以先验,似然相当于就是更新先验的一个参数因子
    unstd_posterior = likelihood * prior
    print(unstd_posterior)
    # 标准化后验,使其值为1
    posterior = unstd_posterior / unstd_posterior.sum()
    print(posterior)
    return grid, posterior


points = 15
h, n = 1, 4
# 四次实验只有一次正面朝上
grid, posterior = posterior_grid(points, h, n)
plt.plot(grid, posterior, 'o-')
plt.plot(0, 0, label='heads = {}\ntosses = {}'.format(h, n), alpha=0)
plt.xlabel(r'$\theta$', fontsize=14)
plt.legend(loc=0, fontsize=14)
plt.show()

网格:使用0到1的均布数据

[0. 0.07142857 0.14285714 0.21428571 0.28571429 0.35714286
0.42857143 0.5 0.57142857 0.64285714 0.71428571 0.78571429
0.85714286 0.92857143 1. ]

先验:定义全为5的均匀分布

[5 5 5 5 5 5 5 5 5 5 5 5 5 5 5]

似然:引入观测值(此处是引入试验次数和朝上次数)设定一个似然分布,然后导入网格,即可获得每个网格值对应的概率分布

[0. 0.22875885 0.35985006 0.41576426 0.41649313 0.37952936
0.31986672 0.25 0.17992503 0.11713869 0.0666389 0.03092461
0.00999584 0.0013536 0. ]

(似然可以看做是更新先验的一个因素)

后验:先验乘以似然来更新先验获得后验

[0. 1.14379425 1.79925031 2.07882132 2.08246564 1.89764681
1.59933361 1.25 0.89962516 0.58569346 0.3331945 0.15462307
0.04997918 0.00676801 0. ]

将后验归一化

[0. 0.08239883 0.12961782 0.14975809 0.15002063 0.1367063
0.11521584 0.09004988 0.06480891 0.0421933 0.0240033 0.01113903
0.0036005 0.00048757 0. ]

 

考虑网格数为15,实验4次,朝上1次

 

 

 考虑网格数为1000,实验200次,朝上85次

 

 

  •  求圆的面积蒙特卡洛代码实现

蒙特卡洛主要用于表征随机性

在变成为2R的正方形内随机撒N个点

在正方形内画一个半径为R的圆,计算在圆内点的个数

(使用关于x,y的不等式来判断其是否在圆内)

得出pi的估计值

圆内点数除以总点数等于圆面积与正方形面积之比(pi*R*R/4*R*R)

(使用点的个数来代表面积)

N = 10000

x, y = np.random.uniform(-1, 1, size=(2, N))
# 随机撒点,为中心为原点的正方形
inside = (x**2 + y**2)  <= 1
# 计算位于圆内的点的个数
pi = inside.sum()*4/N
# 换算出pi
error = abs((pi - np.pi)/pi)* 100
outside = np.invert(inside)

plt.plot(x[inside], y[inside], 'b.')
plt.plot(x[outside], y[outside], 'r.')
plt.plot(0, 0, label='$\hat \pi$ = {:4.3f}\nerror = {:4.3f}%'.format(pi, error), alpha=0)
plt.axis('square')
plt.legend(frameon=True, framealpha=0.9, fontsize=16);
plt.show()

获得pi值

 

  •  Metropolis-Hasting算法代码实现
def metropolis(func, steps=10000):
    """A very simple Metropolis implementation"""
    samples = np.zeros(steps)
    # 指定一维的0矩阵,作为采样链的初始值
    old_x = func.mean()
    # 计算beta分布的平均值,作为采样的起始位置
    old_prob = func.pdf(old_x)
    # 计算采样起始值的概率值,应该具有比较大的概率值,用于后续的比较
    for i in range(steps):   # 一次循环即采样一次
        new_x = old_x + np.random.normal(0, 1)
        # 以beta分布平均值作为起始值,将正态分布作为更新的偏差值
        new_prob = func.pdf(new_x)
        # 计算采样值对应的概率
        acceptance = new_prob/old_prob
        # 计算接受概率,即新旧测量值概率之比
        if acceptance >= np.random.random():  #随机取值作为筛选条件,更新采样值
            samples[i] = new_x
            old_x = new_x
            old_prob = new_prob
        else:
            samples[i] = old_x
    return samples

np.random.seed(345)
func = stats.beta(0.4, 2)
# 定义了一个指定参数的beta分布
samples = metropolis(func=func)
# 对beta分布进行采样,返回一条采样链
print(samples)
x = np.linspace(0.01, .99, 100)
y = func.pdf(x)
plt.xlim(0, 1)
plt.plot(x, y, 'r-', lw=3, label='True distribution')
# 真实的参数分布及其对应概率密度函数,也就是最先定义的beta分布
plt.hist(samples, bins=100, density=True, edgecolor='k', label='Estimated distribution')
# 根据采样链,获得其概率密度分布
plt.xlabel('$x$', fontsize=14)
plt.ylabel('$pdf(x)$', fontsize=14)
plt.legend(fontsize=14)
plt.show()

该算法的作用主要是

在给定一个分布(比如此处的beta分布)后,通过采样获得一条采样链,该采样链对应的概率密度分布与原分布基本一致

此处就实现了一个由分布到采样数据之间的转换过程

 

  •  基于pymc3的代码实现
np.random.seed(123)
n_experiments = 4
theta_real = 0.35  
# 生成数据使用的真值,是需要估计的值
data = stats.bernoulli.rvs(p=theta_real, size=n_experiments)
# 按照二项分布及真值生成数据,作为了观测数据
print(data)

if __name__ == '__main__':
  with pm.Model() as our_first_model:
      # 指定参数的先验(也就是硬币朝上的概率)
      # 按照先验theta来生成采样链
      theta = pm.Beta('theta', alpha=1, beta=1)
      # 定义似然的分布(也就是已知硬币朝上的概率时,在实验中硬币朝上的概率分布,也就是观测值的可能性),此处传入了观测值
      # 似然不会进行采样,因为其是一个观测变量
      # 似然表示的是观测值的可能性,其基于参数和观测值,作为先验的更新因子来获得后验
      y = pm.Bernoulli('y', p=theta, observed=data)
      #y = pm.Binomial('theta',n=n_experimentos, p=theta, observed=sum(datos))
      start = pm.find_MAP()
      step = pm.Metropolis()
      trace = pm.sample(1000, step=step, start=start)
      # 从最大后验值处进行采样,此处获得是参数theta的后验采样trace

  burnin = 100  # no burnin
  # 该参数用于处理老化,因为采样的开始部分可能效果较差
  chain = trace[burnin:]
  # trace就是获得的后验链,由之前的采样链计算得到
  pm.traceplot(chain)
  # 每一条采样链的后验概率密度及其采样图像
  pm.forestplot(chain)
  # 每一条采样链的森林图,表示了其一定的HDI区间
  pm.autocorrplot(chain)
  # 每一条采样链的自相关图像
  pm.plot_posterior(chain)
  # 后验的hpd曲线
  plt.show()
  print(pm.summary(chain))
  # 获得后验的详细参数

指定参数先验

指定似然分布

对先验进行采样

计算出后验

每一条采样链的后验概率密度及其采样图像

每条采样链的森林图

 

 

每一条采样链的自相关图像

 

 后验的所有信息

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM