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]

# 注意:返回的是类别索引,不是概率值
注意:Categorical返回的是整数索引(0到K-1),不是one-hot编码。我曾经在这里吃过亏,以为返回的是概率向量,结果拿去做矩阵乘法时维度对不上。

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) 的值
核心要点:Normal分布之所以常用,是因为中心极限定理。大量独立随机变量的和趋近于正态分布。我在做A/B测试时,经常用Normal近似二项分布,计算效率高很多。

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())
实用技巧:Gamma分布可以用来建模方差的先验。在贝叶斯线性回归中,我经常用Gamma作为精度参数(1/方差)的先验分布。

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 不确定性量化
为什么log_prob比prob更重要? 因为计算机处理加法比乘法稳定得多。多个概率相乘容易下溢(变成0),而对数概率相加就不会。在训练模型时,我们几乎只用log_prob。

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帮你算好了。

避坑指南:我曾经在计算后验时,忘了把先验的参数和数据的计数加起来,结果后验分布完全不对。记住:共轭先验下,后验参数 = 先验参数 + 数据计数。

好了,今天的内容就到这。离散分布和连续分布是概率编程的「砖块」,后面的贝叶斯模型就是用这些砖块搭起来的。下一章咱们继续聊剩下的分布,以及怎么用它们构建更复杂的模型。