Part 3: 随机变量的贝叶斯模型#
1、随机变量#
在之前的分析中,我们讨论的是对某项研究的“可重复性”这样一个单一事件。
同样的逻辑可以应用于更加抽象和一般性的随机变量进行分析。
假设为了研究可重复性问题,一个有能力且资金充足的研究团队计划进行一系列可重复性实验,他们希望知道这些实验成功重复的比例是多少。
首先我们来了解一个概念:胜率或成功率
想象你玩斗地主,有五局三胜,七局四胜这一说,一轮玩下来,就会出现胜率
然而,胜率并不是一成不变的,它会随着每次游戏的输赢而变化
在每一轮开始前,你并不会知道你这次的胜率是多少
在我们的例子中,假设计划对6项研究进行重复实验:
假设该团队对于任何研究成功复现的成功率为\( π,π \)是未知的且可能会变化,所以\( π \)是一个随机变量。
根据团队先前的经验以及心理学研究的现状,我们猜测其成功复现的成功率为\( π \)=50%。
他接下来可能成功复现的次数\(Y\)可能是0,可能是1,也可能是6,可以有7种可能的成功复现次数,\( Y∈ \) {0,1,2,3,4,5,6}。
思考一个问题:虽然我们知道他们的平均成功率为 \( π \)=50%,但问题在于,对于每一种复现成功的次数(1~6),其可能性分别是多少呢?
2、二项式模型#
由于每次重复实验,结果只有两种可能:成功 vs 失败
该团队总共进行6次重复实验,我们想要知道的是成功1次,成功2次,成功3次,…,的概率。
对于这种情况,我们可以用二项分布来分析。
该团队的成功率为\(π\),在\(π\)下某成功次数发生的概率可表示为:
\( f(y|π)=(^n_y)π^y(1-π)^{n-y} ~~~~~~for~y∈ \) {\( 0,1,2....,n \)} \( (^n_y)=\frac{n!}{y!(n-y)!} \)
\( π \)表示成功的可能性,\( y \)表示在n个试次中成功的次数,二项模型含有的前提假设是:
(1) 所有试次发生都是相互独立的 (2) 在每个试次中,成功的概率都是一个固定的值\(π\)
成功次数为0~6 的可能性可以分别写成:
\( f(Y=0|π=0.5)=(^6_0)0.5^0(1-0.5)^6 \)
\( f(Y=1|π=0.5)=(^6_1)0.5^1(1-0.5)^5 \)
…
\( f(Y=5|π=0.5)=(^6_5)0.5^5(1-0.5)^1 \)
\( f(Y=6|π=0.5)=(^6_6)0.5^6(1-0.5)^0 \)
我们可以使用代码帮助计算,其中P对应公式中的\( π \)。
st.binom.pmf(y, n, p)
In [1]:
# 导入数据加载和处理包:pandas
import pandas as pd
# 导入数字和向量处理包:numpy
import numpy as np
# 导入基本绘图工具:matplotlib
import matplotlib.pyplot as plt
# 导入高级绘图工具 seaborn 为 sns
import seaborn as sns
# 导入统计建模工具包 scipy.stats 为 st
import scipy.stats as st
# 设置APA 7的画图样式
plt.rcParams.update({
'figure.figsize': (4, 3), # 设置画布大小
'font.size': 12, # 设置字体大小
'axes.titlesize': 12, # 标题字体大小
'axes.labelsize': 12, # 轴标签字体大小
'xtick.labelsize': 12, # x轴刻度字体大小
'ytick.labelsize': 12, # y轴刻度字体大小
'lines.linewidth': 1, # 线宽
'axes.linewidth': 1, # 轴线宽度
'axes.edgecolor': 'black', # 设置轴线颜色为黑色
'axes.facecolor': 'white', # 轴背景颜色(白色)
'xtick.direction': 'in', # x轴刻度线向内
'ytick.direction': 'out', # y轴刻度线向内和向外
'xtick.major.size': 6, # x轴主刻度线长度
'ytick.major.size': 6, # y轴主刻度线长度
'xtick.minor.size': 4, # x轴次刻度线长度(如果启用次刻度线)
'ytick.minor.size': 4, # y轴次刻度线长度(如果启用次刻度线)
'xtick.major.width': 1, # x轴主刻度线宽度
'ytick.major.width': 1, # y轴主刻度线宽度
'xtick.minor.width': 0.5, # x轴次刻度线宽度(如果启用次刻度线)
'ytick.minor.width': 0.5, # y轴次刻度线宽度(如果启用次刻度线)
'ytick.labelleft': True, # y轴标签左侧显示
'ytick.labelright': False # 禁用y轴标签右侧显示
})
In[2]:
y = [0,1,2,3,4,5,6] # 成功次数
n = 6 # 重复研究总次数
p = 0.5 # 假设的成功概率
# 计算概率值
prob = st.binom.pmf(y, n, p)
result_table = pd.DataFrame({"成功次数":y, "概率":prob})
result_table
显然,当团队的成功概率为 0.5 时,其在六次研究中获得 y = 3 次成功的概率最高(p = 0.3125)。
In [3]:
# 绘制灰色竖线
for i, j in zip(y , prob):
plt.plot([i, i], [j, 0], 'gray', linestyle='-', linewidth=1, zorder=1, )
# 绘制黑色点(各成功率次数的成功率)
plt.scatter(y, prob, c='black')
plt.ylabel('$f(y|\pi)$')
plt.xlabel('y')
plt.xlim(-0.2,6.2)
plt.ylim(0,0.5)
plt.show()

3、概率质量密度函数(probability mass function, pmf)#
概率质量密度函数:是用来描述离散型随机变量在各特定取值上的概率。
从上图我们可以看出,成功次数y在不同的取值上的概率不同。
由于y的个数是有限的,并且是随机发生的,我们把y称为离散型随机变量,而y发生的概率\( f(y) \)则被称为概率质量函数。
对于离散型随机变量\( Y \),\( Y \)各取值的概率由\( f(y) \)指定:
\( f(y)=P(Y = y) \)
并且有如下性质:
对所有y的取值来说,\( 0 ≤ f(y) ≤ 1 \)
\( ∑_{ally} f(y)=1 \),y取值的所有概率之和为1
In [4]:
sum(result_table['概率'])
4、二项似然函数(The Binomial likelihood function)#
不同的信念
虽然我们认为该团队重复6个实验的成功率是50%,但并非所有人都这么认为。
乐观派认为该团队的成功概率为 0.8,表示对实验成功复现持高度信心。
悲观派则认为该团队的成功概率仅为 0.2,意味着对实验成功复现不太乐观。
成功的概率影响着他们对研究复现结果的预期:
如果团队的成功概率高,那么6次研究中成功复现的次数会更多。
反之,如果成功概率低,那么研究复现的失败次数就会更多。
我们可以计算持不同信念的人心中,该团队在6项研究中成功复现的次数的概率分布并画图。
In [5]:
y = [0,1,2,3,4,5,6] # 成功次数
n = 6 # 研究总次数
# 计算似然值
p = 0.5 # 根据以往战绩假设的成功概率
likelihood1 = st.binom.pmf(y, n, p)
p = 0.8 # Kasparov支持者眼中的成功概率
likelihood2 = st.binom.pmf(y, n, p)
p = 0.2 # 深蓝支持者眼中的成功概率
likelihood3 = st.binom.pmf(y, n, p)
result_table = pd.DataFrame({
"成功次数":y,
"本团队(pi=0.5)":likelihood1,
"悲观派(pi=0.2)":likelihood2,
"乐观派(pi=0.8)":likelihood3})
result_table
In [6]:
# 创建子图
fig, axs = plt.subplots(1, 3, figsize=(10, 4))
# 绘制三个图,每个子图类似原图
three_pi = ["Team itself ($\pi = 0.5$)","Optimists ($\pi = 0.8$)","Pessimists ($\pi = 0.2$)"]
likelihoods = [likelihood1, likelihood2, likelihood3]
for i, ax in enumerate(axs):
ax.scatter(y, likelihoods[i], c='black')
for xx, yy in zip(y, likelihoods[i]):
ax.plot([xx, xx], [yy, 0], 'gray', linestyle='-', linewidth=1, zorder=1)
# 添加facet
ax.set_title(three_pi[i])
ax.set_xlim(-0.2,6.2)
ax.set_ylim(0,0.4)
fig.supylabel('$f(y|\pi)$')
fig.supxlabel('y')
plt.tight_layout()
plt.show()
显然,对于乐观派来说,团队取得六次成功的概率远高于其他成功次数。而对于悲观派来说,团队全败的可能性远高于其他成功次数。
换句话说,若团队在6项研究中仅成功复现一次,这种情况在低成功率下(悲观派设想的情境)更可能出现,在高成功率下(乐观派设想的情境)几乎不可能出现。
那么团队成功重复的成功率,更可能(likelihood)是悲观派设想的那样(\( π=0.2 \))。
例如,在乐观派和悲观派眼中(不同成功率\( π \)下),6项研究只成功1次的可能性(即似然,likelihood)。
In [7]:
# 定义成功次数和研究总次数
y = 1 # 成功次数,作为数组处理以便向量化计算
n = 6 # 研究总次数
# 计算似然值,对于三种不同的成功概率 p
p_values = [0.5, 0.8, 0.2] # 定义三种成功率
likelihoods = [] # 用于存储每种成功率的似然值结果
for p in p_values:
likelihood = st.binom.pmf(y, n, p) # 使用st.binom.pmf计算似然值
likelihoods.append(likelihood) # 将结果添加到列表中
# 创建图形和子图
fig, ax = plt.subplots() # 此处应该是 plt.subplots() 而不是 plt.subplot()
ax.scatter(p_values, likelihoods, c='black')
# 设置x轴和y轴的限制应该在绘制线条之前完成,以避免重复设置
ax.set_xlim(-0.2, 1.2) # x轴范围根据p_values调整,最大不应超过1
ax.set_ylim(0, 0.5)
for xx, yy in zip(p_values, likelihoods):
ax.plot([xx, xx], [0, yy], 'gray', linestyle='-', linewidth=1, zorder=1)
# 注意这里的顺序是 [0, yy] 而不是 [yy, 0],因为我们希望从x轴画到对应的似然值
# 设置坐标轴标签,直接使用ax的方法,而不是fig的方法
ax.set_ylabel('$f(\pi|y)$') # 使用ax.set_ylabel而不是fig.supylabel
ax.set_xlabel('$\pi$') # 使用ax.set_xlabel而不是fig.supxlabel
plt.tight_layout() # 调整布局以避免标签重叠
plt.show()
5、似然函数#
当团队只成功复现一次时,该事件在不同成功率下出现的可能性可以写为:
\( f(Y=1|π=0.2)=(^6_1)0.2^1(1-0.2)^5 \)
\( f(Y=1|π=0.5)=(^6_1)0.5^1(1-0.5)^5 \)
\( f(Y=1|π=0.8)=(^6_1)0.8^1(1-0.8)^5 \)
因此,成功复现次数为1时的似然函数可以写成:
\( L(π|y=1)=f(y=1|π)=(^6_1)π^1(1-π)^{6-1}=6π(1-π)^5 \)
不同成功率下的似然:
\( π \) |
0.2 |
0.5 |
0.8 |
---|---|---|---|
\( f(π \vert y=1) \) |
0.3932 |
0.0938 |
0.0015 |
注意:
似然函数表示的是,在各种可能的成功率π下,成功次数Y=1的可能性,所以
该似然函数公式只取决于π
似然函数的总和加起来不为1(从条件概率的公式来看,似然函数的分母是不同的)
条件概率 vs 似然函数
当\( π \)是一定时,条件概率质量函数\( f(·|π) \)可以帮我们计算在π取值下(各种模型),不同的数据\( Y(e.g., y1,y2) \)发生的可能性。
\( f(y_1|π)~vs~f(y_2|π) \)
当\( Y=y \)一定时,似然函数\( L(·|y)=f(y|·) \)允许我们比较在各种不同的模型,即二项式的\( π \)取值\( (e.g., π_1,π_2) \)下,观察到这个数据y的可能性(relative likelihood)
\( L(π_1|y)与L(π_2|y) \)
即
\( f(y|π_1)与f(y|π_2) \)
在二项分布模型下:
进行n=6个重复实验时,成功次数与成功率的关系符合二项式模型,可以用如下的形式来表示:
\( Y|π\sim Bin(6,π) \)
\( f(y|π)=(^6_y)π^y(1-π)^{6-y}~~~~~~~for~y\in \) {0,1,2,3,4,5,6}
下图给出了几种\( π \)的取值,我们可以通过概率模型得到每种\( Y \)发生的可能性。
同时,我们可以看到,\( Y=1 \)(赢一次)这一特定的数据模式,在各个\( π \) 取值(模型)下的似然。
In [8]:
# Values for Y (number of successes in n trials)
y = np.arange(0, 7)
# Number of trials (n) and different probabilities (π values)
n = 6
pi_values = [0.2, 0.5, 0.8]
# Create subplots
fig, axs = plt.subplots(1, 3, figsize=(12, 5))
# Loop over each pi value to plot the corresponding binomial distribution
for i, pi in enumerate(pi_values):
# Calculate binomial probabilities for each y (number of successes)
likelihoods = st.binom.pmf(y, n, pi)
# Scatter plot of the likelihoods
axs[i].scatter(y, likelihoods, color='black', zorder=2)
# Draw gray vertical lines
for yy, likelihood in zip(y, likelihoods):
axs[i].plot([yy, yy], [0, likelihood], color='gray', linestyle='-', linewidth=1, zorder=1)
# Highlight y = 1 with a black line
axs[i].plot([1, 1], [0, st.binom.pmf(1, n, pi)], color='black', linewidth=3, zorder=3)
# Title with binomial parameters
axs[i].set_title(f'Bin({n},{pi})')
# Set y and x axis limits
axs[i].set_xlim(-0.5, 6.5)
axs[i].set_ylim(0, 0.5)
# Global labels
fig.supylabel(r'$f(y|\pi)$')
fig.supxlabel('y')
# Adjust layout for better fit
plt.tight_layout()
plt.show()

6、先验概率模型(Prior probability model)#
建立先验模型
从前面的描述可以看到,二项分布的参数π也可以变化,也可以成为一个随机变量。
例如,我们想当一个更有深度的观察者,融合了乐观派、悲观派和中立者三者关于π的估计。
但是,我们对三种观点的可能性有不同的信念。
假如我们总体上是一个乐观派,但不排除悲观派的观点,我们给三派观点分配了一定的概率(先验)。
例如,设定\( π_{0.2}=0.1 \),或者\( π_{0.2}=0.5 \)。但需要所有\( f(π) \)的总和为1。
\( π \) |
0.2 |
0.5 |
0.8 |
total |
---|---|---|---|---|
\( f(π) \) |
0.10 |
0.25 |
0.65 |
1 |
我们设定的π的数量也是可以变化的。
例如,我们还可以将一种非常悲观的可能性也纳入进来,认为该团队成功率为0.01,即\( π \)=0.01。
那么新形成的先验分布可能如下。
\( π \) |
0.01 |
0.2 |
0.5 |
0.8 |
total |
---|---|---|---|---|---|
\( f(π) \) |
0.10 |
0.10 |
0.25 |
0.55 |
1 |
7、后验概率模型(Posterior probability model)#
前述第一个先验模型,我们总体上是乐观的,认为团队高成功率的可能性很高 (\( π_{0.8}=0.65 \))。
\( π \) |
0.2 |
0.5 |
0.8 |
total |
---|---|---|---|---|
\( f(π) \) |
0.10 |
0.25 |
0.65 |
1 |
然而,最终结果发现:该团队只成功复现一次。
这个新的数据会如何改变我们的信念?
我们可以综合先验和似然,根据贝叶斯的思路,计算后验概率。
其中 团队成功复现的概率从\( π_{0.8}=0.65 \)降低为\( π_{0.8}=0.015 \)。意味着,他成功率为0.2的可能性是最大的\( π_{0.2}=0.617 \)。
左图为先验模型
中间的图为似然模型
右边的图为后验模型
In [9]:
# Pi values and corresponding data
pi_values = [0.2, 0.5, 0.8]
f_pi = [0.10, 0.25, 0.65] # Prior probabilities
L_pi_given_Y1 = [0.617, 0.367, 0.015] # Likelihoods given Y=1
posterior = [0.617, 0.367, 0.015] # Posterior, assumed same as likelihoods here
# Create subplots for prior, likelihood, and posterior
fig, axs = plt.subplots(1, 3, figsize=(10, 4))
# Prior Probability f(π)
axs[0].scatter(pi_values, f_pi, color='black', zorder=2)
for xx, yy in zip(pi_values, f_pi):
axs[0].plot([xx, xx], [0, yy], color='black', linewidth=3, zorder=1)
axs[0].set_title(r'Prior $f(\pi)$')
axs[0].set_xlim(0.15, 0.85)
axs[0].set_ylim(0, 0.7)
# Likelihood L(π|Y=1)
axs[1].scatter(pi_values, L_pi_given_Y1, color='black', zorder=2)
for xx, yy in zip(pi_values, L_pi_given_Y1):
axs[1].plot([xx, xx], [0, yy], color='black', linewidth=3, zorder=1)
axs[1].set_title(r'Likelihood $L(\pi|Y=1)$')
axs[1].set_xlim(0.15, 0.85)
axs[1].set_ylim(0, 0.7)
# Posterior Probability f(π|Y=1)
axs[2].scatter(pi_values, posterior, color='black', zorder=2)
for xx, yy in zip(pi_values, posterior):
axs[2].plot([xx, xx], [0, yy], color='black', linewidth=3, zorder=1)
axs[2].set_title(r'Posterior $f(\pi|Y=1)$')
axs[2].set_xlim(0.15, 0.85)
axs[2].set_ylim(0, 0.7)
# Set labels and layout
for ax in axs:
ax.set_xlabel(r'$\pi$')
fig.supylabel('Probability')
plt.tight_layout()
plt.show()

后验模型的计算过程
上图所表示的后验可写成:
\( f(π|y)=1 \)
表示当团队只成功复现一项研究时,他成功率\(π\)的概率分布
根据贝叶斯公式,我们可以进一步对后验概率公式进行展开:
\( posterior=\frac{prior*likelihood}{normalizing~constant} \)
\( f(π|y=1)=\frac{f(π)L(π|y=1)}{f(y=1)}~~~~~for~π\in 0.2,0.5,0.8 \)
\( f(π=0.2|y=1)=\frac{0.10×0.3932}{0.0637}≈0.617 \)
\( f(π=0.5|y=1)=\frac{0.25×0.0938}{0.0637}≈0.368 \)
\( f(π=0.8|y=1)=\frac{0.65×0.0015}{0.0637}≈0.015 \)
下表对后验概率模型进行了总结,我们可知,经过了先前只成功了一项研究的复现经历后,该团队取得成功(\( π=0.8 \))的可能性已经从0.65降到了0.015
\( π \) |
0.2 |
0.5 |
0.8 |
total |
---|---|---|---|---|
\( f(π) \) |
0.10 |
0.25 |
0.65 |
1 |
\( f(π \vert y=1 \)) |
0.617 |
0.368 |
0.015 |
1 |
补充材料
省略分母的计算
考虑到分母是一个常数,我们常常会成功率计算它
省略分母后验的计算可写成:
\( f(π=0.2|y=1)=c·0.10·0.3932∝0.039320 \) \( f(π=0.5|y=1)=c·0.25·0.0938∝0.023450 \) \( f(π=0.8|y=1)=c·0.65·0.0015∝0.000975 \)
∝表示成比例,尽管这些未经标准化的后验概率总和不等于1
0.039320+0.023450+0.000975=0.063745
In [10]:
# Pi values and corresponding unnormalized posterior data
pi_values = [0.2, 0.5, 0.8]
unnormalized_posterior = [0.03932, 0.02345, 0.000975] # Unnormalized posterior values
normalized_posterior = [val / sum(unnormalized_posterior) for val in unnormalized_posterior] # Normalized
# Create subplots for normalized and unnormalized posteriors
fig, axs = plt.subplots(1, 2, figsize=(8, 4))
# Normalized posterior
axs[0].scatter(pi_values, normalized_posterior, color='black', zorder=2)
for xx, yy in zip(pi_values, normalized_posterior):
axs[0].plot([xx, xx], [0, yy], color='black', linewidth=3, zorder=1)
axs[0].set_title('Normalized $f(\pi | y=1)$')
axs[0].set_xlim(0.15, 0.85)
axs[0].set_ylim(0, 0.7)
# Unnormalized posterior
axs[1].scatter(pi_values, unnormalized_posterior, color='black', zorder=2)
for xx, yy in zip(pi_values, unnormalized_posterior):
axs[1].plot([xx, xx], [0, yy], color='black', linewidth=3, zorder=1)
axs[1].set_title('Unnormalized $f(\pi | y=1)$')
axs[1].set_xlim(0.15, 0.85)
axs[1].set_ylim(0, 0.05)
# Set labels and layout
for ax in axs:
ax.set_xlabel(r'$\pi$')
fig.supylabel('Probability')
plt.tight_layout()
plt.show()

我们可以使用这些未经标准化的后验概率总和作为分母,来对后验概率进行标准化,会得到相同的计算结果。
\( f(π=0.2|y=1)=\frac{0.039320}{0.039320+0.023450+0.000975} \)
注意,分母为所有似然值的总和,因此后验概率的计算公式还可以写成:
\( f(π|y)=\frac{f(π)L(π|y)}{f(y)}=\frac{f(π)L(π|y)}{\sum _{allπ}f(π)L(π|y)} \)
Proportionality
既然\( f(y) \)是一个用来标准化的常数,它并不受\( π \)的影响,那么后验概率质量函数\( f(π|y) \)就与\( f(π) \)和\( L(π|y) \)成正比
\( f(π|y)=\frac{f(π)L(π|y)}{f(y)}∝f(π)L(π|y) \)
\( posterior∝prior·likelihood \)
😜这个性质很重要。因为分母的计算量往往比较大,需要遍历所有参数,如果参数不止一个,计算量可想而知。因此,如过能不计算分母也能计算后验,那么这样的方法(后面会介绍的MCMC算法)将会非常有实践意义。
8、Posterior simulation (with code)#
定义先验模型
定义多个可能的成功率
定义每个成功率出现的可能性 (注意,其和为1)
In [11]:
import pandas as pd
import numpy as np
# 定义可能的成功率
replicated = pd.DataFrame({'pi':[0.2, 0.5, 0.8]})
# 定义先验模型
prior = [0.10, 0.25, 0.65]
模拟在特定成功率下,6项研究中的成功次数
重复这个过程10000次
In [12]:
# 设置随机数种子保证可重复性
np.random.seed(84735)
# 从先验中抽取10000个 pi 值,并生成对应的y值
replicated_sim = replicated.sample(n=10000, weights=prior, replace=True)
replicated_sim['y'] = np.random.binomial(n=6, p=replicated_sim['pi'], size=len(replicated_sim))
replicated_sim.head(10)
In [13]:
#对pi的抽取情况进行总结
replicated_counts = replicated_sim['pi'].value_counts().reset_index()
replicated_counts.columns = ['pi','n']
replicated_counts['percentage'] = (replicated_counts['n']/len(replicated_sim))
replicated_counts = replicated_counts.sort_values(by='pi')
print(replicated_counts)
不同成功率下,不同成功次数的分布情况f(y|π)
In [14]:
# 导入绘图工具 seaborn
import seaborn as sns
# 通过 facegrid 方法根据不同变量绘制不同的图形
replicated_lik = sns.FacetGrid(replicated_sim,col="pi")
replicated_lik.map(sns.histplot,'y',stat='probability',discrete=True)
plt.tight_layout()
plt.show()
查看\(y=1\)时,对应的\(π\)的分布情况
In [15]:
replicated_post = replicated_sim[replicated_sim['y'] == 1].value_counts()
replicated_post
replicated_post = replicated_sim[replicated_sim['y'] == 1]
replicated_post_plot = sns.histplot(data = replicated_post, x="pi")
#plt.xticks(np.arange(0.2,0.8,0.3))
replicated_post_plot.set(xticks=[0.2,0.5,0.8])
sns.despine()
思考:频率学派(经典统计)会如何处理上述两个问题?
某项研究的可重复性
重复6次的成功率