3、概率分布(上):离散分布与连续分布
各位同学,今天我们来聊聊概率分布。说实话,这是整个贝叶斯推断的基石。你想想看,没有分布,哪来的先验?哪来的似然?哪来的后验?
我个人习惯把分布分成两大类:离散的和连续的。咱们今天先啃下这两块硬骨头,顺便把TFP里那些常用的属性和方法摸透。
3.1 离散分布:从抛硬币到计数事件
离散分布处理的是那些「可数」的随机变量。比如抛硬币的结果、掷骰子的点数、一天内收到的邮件数量。我刚开始做概率编程时,总觉得离散分布比连续分布简单,后来发现——嗯,坑也不少。
3.1.1 Bernoulli分布:最简单的二元选择
Bernoulli分布,说白了就是抛硬币。只不过这枚硬币可能是不公平的。参数只有一个:probs,表示事件发生的概率。
import tensorflow_probability as tfp
tfd = tfp.distributions
# 创建一个公平的伯努利分布
bernoulli = tfd.Bernoulli(probs=0.5)
# 采样10次
samples = bernoulli.sample(10)
print(samples.numpy()) # 输出类似 [0, 1, 0, 1, 1, 0, 0, 1, 0, 1]
# 计算某个观测值的对数概率
log_prob = bernoulli.log_prob(1.0)
print(log_prob.numpy()) # log(0.5) ≈ -0.693
probs传成整数0或1时,TFP会报错。记得用浮点数,比如0.5而不是1/2。
3.1.2 Categorical分布:多选一的场景
Categorical是Bernoulli的推广。Bernoulli只有两个选项,Categorical可以有K个选项。比如掷骰子,六个面,每个面概率1/6。
# 创建一个公平的六面骰子
categorical = tfd.Categorical(probs=[1/6, 1/6, 1/6, 1/6, 1/6, 1/6])
# 采样
samples = categorical.sample(5)
print(samples.numpy()) # 输出类似 [3, 0, 5, 2, 4]
# 注意:返回的是类别索引,不是概率值
3.1.3 Poisson分布:计数事件的利器
Poisson分布用来建模「单位时间内事件发生的次数」。比如:每小时路过你家门口的出租车数量、一天内网站收到的请求数。
参数只有一个:rate(也叫lambda),表示平均发生率。
# 假设平均每小时有5辆车经过
poisson = tfd.Poisson(rate=5.0)
# 模拟10个小时的观测
samples = poisson.sample(10)
print(samples.numpy()) # 输出类似 [4, 7, 3, 5, 6, 4, 8, 5, 3, 6]
# 计算概率质量函数
prob = poisson.prob(3.0)
print(prob.numpy()) # P(X=3) 的值
为什么会用Poisson?因为它有一个很好的性质:均值和方差都等于rate。你想想看,如果观测数据的均值≈方差,那Poisson就是个不错的候选模型。
3.2 连续分布:从实数轴到概率密度
连续分布处理的是连续值。比如身高、体重、温度、时间。这里要注意,连续分布的概率是用「密度」来描述的,不是「质量」。单个点的概率是0,但一段区间的概率可以算。
3.2.1 Normal分布:一切的中心
Normal分布,也叫高斯分布,是概率论里的C位选手。两个参数:loc(均值)和scale(标准差)。
# 标准正态分布 N(0, 1)
normal = tfd.Normal(loc=0.0, scale=1.0)
# 采样
samples = normal.sample(5)
print(samples.numpy()) # 输出类似 [-0.23, 1.45, -0.67, 0.89, -1.12]
# 计算对数概率密度
log_prob = normal.log_prob(0.5)
print(log_prob.numpy()) # log(PDF) 的值
3.2.2 Beta分布:概率的概率分布
Beta分布很有意思。它定义在[0,1]区间上,非常适合建模「概率」本身。两个参数:concentration1(alpha)和concentration0(beta)。
# Beta(2, 5) 分布
beta = tfd.Beta(concentration1=2.0, concentration0=5.0)
# 采样
samples = beta.sample(5)
print(samples.numpy()) # 输出在0到1之间
# 计算概率密度
prob = beta.prob(0.3)
print(prob.numpy())
我记得有一次做点击率预估,先验用Beta分布,后验还是Beta分布——这就是共轭先验的魅力。你想想看,计算多方便。
3.2.3 Gamma分布:等待时间的建模
Gamma分布是Poisson过程的「兄弟」。Poisson描述事件发生的次数,Gamma描述等待第k次事件发生的时间。两个参数:concentration(形状参数k)和rate(速率参数)。
# Gamma(2, 1) 分布
gamma = tfd.Gamma(concentration=2.0, rate=1.0)
# 采样
samples = gamma.sample(5)
print(samples.numpy()) # 输出为正数
# 计算对数概率密度
log_prob = gamma.log_prob(3.0)
print(log_prob.numpy())
3.2.4 Exponential分布:无记忆性的代表
Exponential分布是Gamma的一个特例(当concentration=1时)。它描述的是「两次事件之间的时间间隔」。最重要的性质是无记忆性:已经等了多久,不影响还要等多久。
# Exponential(rate=0.5)
exponential = tfd.Exponential(rate=0.5)
# 采样
samples = exponential.sample(5)
print(samples.numpy()) # 输出为正数
# 计算累积分布函数
cdf_val = exponential.cdf(2.0)
print(cdf_val.numpy()) # P(X ≤ 2.0)
3.3 分布的核心属性和方法
TFP里所有分布都共享一套接口。掌握了这些,你就能玩转所有分布。我总结成一张表,方便你查阅:
| 方法/属性 | 作用 | 返回值 | 使用场景 |
|---|---|---|---|
sample(n) |
从分布中采样n个样本 | Tensor | 生成模拟数据、蒙特卡洛估计 |
log_prob(x) |
计算x的对数概率(密度) | Tensor | 计算似然、损失函数 |
prob(x) |
计算x的概率(密度) | Tensor | 可视化、调试 |
cdf(x) |
计算累积分布函数 P(X ≤ x) | Tensor | 假设检验、分位数计算 |
mean() |
分布的期望值 | Tensor | 点估计、模型诊断 |
variance() |
分布的方差 | Tensor | 不确定性量化 |
3.4 实战小例子:用分布做简单推断
咱们来个简单的。假设你观测到10次抛硬币,其中7次正面。你想知道这枚硬币的正面概率是多少。
# 用Beta分布作为先验(假设先验是均匀的 Beta(1,1))
prior = tfd.Beta(concentration1=1.0, concentration0=1.0)
# 观测数据:7次正面,3次反面
# 后验分布是 Beta(1+7, 1+3) = Beta(8, 4)
posterior = tfd.Beta(concentration1=8.0, concentration0=4.0)
# 后验均值
print(posterior.mean().numpy()) # 8/(8+4) ≈ 0.667
# 后验的95%置信区间
lower = posterior.quantile(0.025)
upper = posterior.quantile(0.975)
print(f"95%区间: [{lower.numpy():.3f}, {upper.numpy():.3f}]")
你看,用分布做推断就是这么直观。先验 + 数据 = 后验。整个过程不需要复杂的数学推导,TFP帮你算好了。
好了,今天的内容就到这。离散分布和连续分布是概率编程的「砖块」,后面的贝叶斯模型就是用这些砖块搭起来的。下一章咱们继续聊剩下的分布,以及怎么用它们构建更复杂的模型。