数学重学 - 21 概率分布

这是 数学重学路线图 阶段四的子页面

概率分布:每个可能结果的”剧本”


直觉入门:概率分布是什么?

想象你站在一个弹珠机前面

每颗弹珠落下,最终会落入某个槽里

概率分布就是描述”每个槽会接住多少比例弹珠”的完整地图

核心规则只有两条:

每个结果的概率 ≥ 0(不可能是负概率)

所有可能结果的概率加起来 = 1(弹珠总要落在某个地方)

类比:概率分布就像后端服务的流量分配表——每条路径分到多少流量,加起来就是 100%


离散分布:可数的结果

伯努利分布 Bernoulli

最简单的分布:一次试验,两种结果

成功概率 p,失败概率 1-p

比喻:一次 API 调用,要么成功(p),要么失败(1-p)

$P(X=1) = p, \quad P(X=0) = 1-p$

期望 $E(X) = p$,方差 $Var(X) = p(1-p)$

二项分布 Binomial B(n, p)

n 次独立伯努利试验中,成功 k 次的概率

公式:

$P(X=k) = C(n,k) \cdot p^k \cdot (1-p)^{n-k}$

其中 $C(n,k) = \frac{n!}{k!(n-k)!}$

直觉:n 次请求中恰好 k 次失败的概率是多少?

期望 $E(X) = np$,方差 $Var(X) = np(1-p)$

应用场景

100 次请求中出现 5 次超时的概率

1000 个用户中有多少会点击某个按钮

安全扫描 n 个端口,恰好 k 个开放

Python 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from scipy import stats
import matplotlib.pyplot as plt
import numpy as np

# 二项分布:100次请求,每次失败率5%
n, p = 100, 0.05
binom_dist = stats.binom(n, p)

x = np.arange(0, 20)
pmf = binom_dist.pmf(x)

plt.figure(figsize=(10, 5))
plt.bar(x, pmf, color='steelblue', alpha=0.8)
plt.xlabel('失败次数 k')
plt.ylabel('P(X=k)')
plt.title(f'二项分布 B({n}, {p}):100次请求中失败次数的概率')
plt.axvline(x=n*p, color='red', linestyle='--', label=f'期望 E(X)={n*p}')
plt.legend()
plt.show()

# 实用:至少10次失败的概率
prob_at_least_10 = 1 - binom_dist.cdf(9)
print(f"至少10次失败的概率: {prob_at_least_10:.4f}")

泊松分布 Poisson(λ)

单位时间/空间内事件发生次数

公式:

$P(X=k) = \frac{\lambda^k \cdot e^{-\lambda}}{k!}$

λ 是单位时间内的平均发生次数

直觉:如果平均每分钟来 5 个请求,那某一分钟来 10 个请求的概率是多少?

期望 $E(X) = \lambda$,方差 $Var(X) = \lambda$(期望=方差是泊松分布的标志)

与二项分布的关系:当 n 很大、p 很小、np=λ 时,二项分布近似泊松分布

应用场景

每分钟收到的请求数

每天发现的 Bug 数

每小时的安全告警数

DDoS 检测:请求数突然远超 λ

Python 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from scipy import stats
import matplotlib.pyplot as plt
import numpy as np

# 泊松分布:平均每分钟5个请求
lam = 5
poisson_dist = stats.poisson(lam)

x = np.arange(0, 20)
pmf = poisson_dist.pmf(x)

plt.figure(figsize=(10, 5))
plt.bar(x, pmf, color='coral', alpha=0.8)
plt.xlabel('事件次数 k')
plt.ylabel('P(X=k)')
plt.title(f'泊松分布 Poisson(λ={lam}):每分钟请求数分布')
plt.axvline(x=lam, color='red', linestyle='--', label=f'λ={lam}')
plt.legend()
plt.show()

# 实用:每分钟请求超过12个的概率(异常检测阈值)
prob_over_12 = 1 - poisson_dist.cdf(12)
print(f"请求超过12个/分钟的概率: {prob_over_12:.6f}")
print(f"如果发生了,大概率是异常!")

几何分布 Geometric

首次成功需要的试验次数

$P(X=k) = (1-p)^{k-1} \cdot p$

直觉:暴力破解密码,平均试多少次能成功?


连续分布:不可数的结果

均匀分布 Uniform(a, b)

区间 [a, b] 内每个值出现的概率密度相同

概率密度函数:$f(x) = \frac{1}{b-a}$,在 [a,b] 内

直觉:random() 生成 [0,1] 之间的随机数,就是均匀分布

应用

随机数生成(密码学基础)

负载均衡中的随机分配

AB 测试中的随机分组

正态分布 Normal(μ, σ²)

最重要的分布,自然界无处不在

钟形曲线:μ 决定中心位置,σ 决定宽窄

概率密度函数:

$f(x) = \frac{1}{\sigma\sqrt{2\pi}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}$

68-95-99.7 法则

68% 的数据落在 μ±1σ 内

95% 的数据落在 μ±2σ 内

99.7% 的数据落在 μ±3σ 内

直觉:API 响应时间的分布——大部分集中在均值附近,极快极慢的都少

中心极限定理 CLT

无论原始分布是什么,大量独立样本的均值近似正态分布

这就是正态分布如此重要的根本原因

直觉:100 台服务器各自的响应时间可能乱七八糟,但它们的平均响应时间一定接近正态

Python 代码(正态分布 + 68-95-99.7):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from scipy import stats
import matplotlib.pyplot as plt
import numpy as np

mu, sigma = 200, 30 # 响应时间:均值200ms,标准差30ms
norm_dist = stats.norm(mu, sigma)

x = np.linspace(mu - 4*sigma, mu + 4*sigma, 300)
pdf = norm_dist.pdf(x)

plt.figure(figsize=(12, 6))
plt.plot(x, pdf, 'k-', linewidth=2)

# 68-95-99.7 法则可视化
colors = ['#2196F3', '#4CAF50', '#FF9800']
labels = ['68% (μ±1σ)', '95% (μ±2σ)', '99.7% (μ±3σ)']
for i, (mult, color, label) in enumerate(zip([1, 2, 3], colors, labels)):
left, right = mu - mult*sigma, mu + mult*sigma
x_fill = np.linspace(left, right, 200)
plt.fill_between(x_fill, norm_dist.pdf(x_fill), alpha=0.3 - i*0.08,
color=color, label=label)

plt.xlabel('响应时间 (ms)')
plt.ylabel('概率密度')
plt.title('API响应时间的正态分布 N(200, 30²)')
plt.legend()
plt.show()

# 实用:超过300ms的概率
prob_slow = 1 - norm_dist.cdf(300)
print(f"响应时间超过300ms的概率: {prob_slow:.4f}")
print(f"(即 {prob_slow*100:.2f}% 的请求会超时)")

指数分布 Exponential(λ)

事件之间的时间间隔

概率密度函数:$f(x) = \lambda e^{-\lambda x}$,x ≥ 0

生存函数:$P(X > t) = e^{-\lambda t}$

期望 $E(X) = 1/\lambda$

无记忆性

$P(X > s+t \mid X > s) = P(X > t)$

已经等了 5 分钟没坏,再等 3 分钟坏的概率和一开始就等 3 分钟一样

直觉:服务器不会因为”运行了很久”就更容易崩溃(理想化假设)

应用场景

服务器故障间隔时间

两次请求之间的间隔

安全事件间的时间间隔

Python 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from scipy import stats
import matplotlib.pyplot as plt
import numpy as np

# 指数分布:平均每2小时发生一次故障,λ=0.5/小时
lam = 0.5
exp_dist = stats.expon(scale=1/lam) # scipy用scale=1/λ

x = np.linspace(0, 10, 300)
pdf = exp_dist.pdf(x)

plt.figure(figsize=(10, 5))
plt.plot(x, pdf, 'r-', linewidth=2, label='PDF')
plt.fill_between(x, pdf, alpha=0.3, color='red')
plt.xlabel('时间间隔 (小时)')
plt.ylabel('概率密度')
plt.title(f'指数分布 Exp(λ={lam}):故障间隔时间')

# 标注P(X>4)的区域
x_fill = np.linspace(4, 10, 100)
plt.fill_between(x_fill, exp_dist.pdf(x_fill), alpha=0.5, color='blue',
label=f'P(X>4h) = {1-exp_dist.cdf(4):.3f}')
plt.legend()
plt.show()

# 无记忆性验证
s, t = 2, 3
p_conditional = (1 - exp_dist.cdf(s+t)) / (1 - exp_dist.cdf(s))
p_direct = 1 - exp_dist.cdf(t)
print(f"P(X>{s+t}|X>{s}) = {p_conditional:.6f}")
print(f"P(X>{t}) = {p_direct:.6f}")
print(f"两者相等 → 无记忆性成立!")

对数正态分布 LogNormal

如果 ln(X) 服从正态分布,则 X 服从对数正态分布

特点:只取正值、右偏(有长尾)

直觉:大多数请求响应很快,但少数请求极慢——这就是对数正态

大数据应用:响应时间建模比正态分布更准确


分布之间的关系图谱

伯努利 → (重复n次) → 二项分布

二项分布 → (n→∞, p→0, np=λ) → 泊松分布

泊松分布(计数) ↔ 指数分布(间隔):同一枚硬币的两面

任何分布 → (样本均值, n→∞) → 正态分布(中心极限定理)


综合应用场景

安全方向

攻击事件建模

每天遭受的攻击次数 → 泊松分布

两次攻击之间的间隔 → 指数分布

异常检测阈值

正常流量建模为正态分布

超过 μ+3σ 的流量 → 告警(只有 0.15% 的正常流量会到这里)

暴力破解分析

几何分布估算破解所需尝试次数

代码示例——异常检测:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import numpy as np
from scipy import stats

# 模拟正常流量(正态分布)和攻击流量
np.random.seed(42)
normal_traffic = np.random.normal(loc=1000, scale=150, size=1000)
# 注入5个异常值(DDoS攻击)
attack_traffic = np.array([3000, 3500, 2800, 4000, 3200])
all_traffic = np.concatenate([normal_traffic, attack_traffic])

mu = np.mean(normal_traffic)
sigma = np.std(normal_traffic)
threshold = mu + 3 * sigma

anomalies = all_traffic[all_traffic > threshold]
print(f"正常流量: μ={mu:.0f}, σ={sigma:.0f}")
print(f"3σ阈值: {threshold:.0f}")
print(f"检测到 {len(anomalies)} 个异常值: {anomalies}")

大数据方向

请求量建模

每秒请求数 → 泊松分布

高峰期/低谷期分别建模

响应时间建模

对数正态分布比正态分布更贴合实际

P99 延迟 = 99分位数

数据质量

缺失值比例 → 二项分布

代码示例——响应时间分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

# 模拟响应时间(对数正态分布更真实)
np.random.seed(42)
response_times = np.random.lognormal(mean=5, sigma=0.5, size=10000)

# 计算关键指标
p50 = np.percentile(response_times, 50)
p95 = np.percentile(response_times, 95)
p99 = np.percentile(response_times, 99)

print(f"P50 (中位数): {p50:.1f} ms")
print(f"P95: {p95:.1f} ms")
print(f"P99: {p99:.1f} ms")

plt.figure(figsize=(10, 5))
plt.hist(response_times, bins=100, density=True, alpha=0.7, color='steelblue')
for pval, label, color in [(p50,'P50','green'), (p95,'P95','orange'), (p99,'P99','red')]:
plt.axvline(x=pval, color=color, linestyle='--', label=f'{label}={pval:.0f}ms')
plt.xlabel('响应时间 (ms)')
plt.ylabel('概率密度')
plt.title('响应时间分布(对数正态)')
plt.legend()
plt.xlim(0, 600)
plt.show()

后端方向

故障率分析(指数分布):

MTBF(平均故障间隔)= 1/λ

可用性 = MTBF / (MTBF + MTTR)

负载预测(正态/泊松):

根据历史数据拟合分布

预测峰值,做容量规划

灰度发布

新版本错误率是否符合预期 → 二项分布检验

代码示例——容量规划:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from scipy import stats
import numpy as np

# 每秒请求数服从泊松分布
# 过去一周高峰期平均 QPS = 500
lam = 500

# 需要保证 99.9% 的时间不过载
# 找到 99.9 分位数
capacity_needed = stats.poisson.ppf(0.999, lam)
print(f"平均 QPS: {lam}")
print(f"99.9% 时间不过载需要的容量: {capacity_needed:.0f} QPS")
print(f"建议预留 buffer: {capacity_needed/lam:.2f}x 平均值")

# 如果要保证 99.99%?
capacity_9999 = stats.poisson.ppf(0.9999, lam)
print(f"99.99% 不过载需要: {capacity_9999:.0f} QPS ({capacity_9999/lam:.2f}x)")

Python 综合实战:拟合数据到分布

给你一堆数据,怎么知道它服从什么分布?

方法1:直方图 + 理论曲线叠加

方法2:QQ 图(Quantile-Quantile Plot)

方法3:统计检验(Kolmogorov-Smirnov 检验)

综合代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

# 生成模拟数据(假装我们不知道它的分布)
np.random.seed(42)
data = np.random.exponential(scale=2.0, size=500)

# === 方法1:直方图 + 拟合 ===
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

# 尝试正态拟合
mu, std = stats.norm.fit(data)
x = np.linspace(0, max(data), 200)
axes[0].hist(data, bins=30, density=True, alpha=0.7)
axes[0].plot(x, stats.norm.pdf(x, mu, std), 'r-', linewidth=2)
axes[0].set_title(f'正态拟合: μ={mu:.2f}, σ={std:.2f}')

# 尝试指数拟合
loc, scale = stats.expon.fit(data)
axes[1].hist(data, bins=30, density=True, alpha=0.7)
axes[1].plot(x, stats.expon.pdf(x, loc, scale), 'r-', linewidth=2)
axes[1].set_title(f'指数拟合: λ={1/scale:.2f}')

# 尝试对数正态拟合
shape, loc_ln, scale_ln = stats.lognorm.fit(data, floc=0)
axes[2].hist(data, bins=30, density=True, alpha=0.7)
axes[2].plot(x, stats.lognorm.pdf(x, shape, loc_ln, scale_ln), 'r-', linewidth=2)
axes[2].set_title(f'对数正态拟合')

plt.tight_layout()
plt.show()

# === 方法2:QQ图检验正态性 ===
fig, ax = plt.subplots(figsize=(6, 6))
stats.probplot(data, dist="norm", plot=ax)
ax.set_title('QQ图:检验正态性\n(偏离直线越多,越不正态)')
plt.show()

# === 方法3:KS检验 ===
distributions = {
'正态': stats.norm(*stats.norm.fit(data)),
'指数': stats.expon(*stats.expon.fit(data)),
'对数正态': stats.lognorm(*stats.lognorm.fit(data, floc=0))
}

print("Kolmogorov-Smirnov 检验结果:")
print("-" * 45)
for name, dist in distributions.items():
ks_stat, p_value = stats.kstest(data, dist.cdf)
result = "✓ 不拒绝" if p_value > 0.05 else "✗ 拒绝"
print(f"{name:8s}: KS={ks_stat:.4f}, p={p_value:.4f}{result}")

练习题

题目1:二项分布应用

一个API的失败率是 2%,你连续调用 200 次

(a) 期望失败次数是多少?

(b) 恰好失败 5 次的概率?

(c) 失败超过 10 次的概率?(提示:用 1 - cdf(10)

参考答案:

1
2
3
4
5
6
from scipy import stats
n, p = 200, 0.02
dist = stats.binom(n, p)
print(f"(a) E(X) = {n*p}")
print(f"(b) P(X=5) = {dist.pmf(5):.6f}")
print(f"(c) P(X>10) = {1 - dist.cdf(10):.6f}")

题目2:泊松分布 + 异常检测

你的服务平均每分钟收到 8 个请求

(a) 某分钟收到 15 个请求,这异常吗?(计算 P(X≥15))

(b) 设定告警阈值为 P(X≥k) < 0.01,k 应该是多少?

参考答案:

1
2
3
4
5
6
7
from scipy import stats
lam = 8
dist = stats.poisson(lam)
print(f"(a) P(X>=15) = {1 - dist.cdf(14):.6f}")
# (b) 找最小的k使得P(X>=k)<0.01
k = dist.ppf(0.99) + 1
print(f"(b) 告警阈值 k = {k:.0f}")

题目3:指数分布 + 无记忆性

服务器平均每 4 小时故障一次(λ=0.25/小时)

(a) 2 小时内不故障的概率?

(b) 已经稳定运行了 3 小时,再撑 2 小时不故障的概率?(验证无记忆性)

参考答案:

1
2
3
4
5
6
7
8
9
from scipy import stats
lam = 0.25
dist = stats.expon(scale=1/lam)
p_a = 1 - dist.cdf(2)
print(f"(a) P(X>2) = {p_a:.4f}")
# (b) 无记忆性:P(X>5|X>3) = P(X>2)
p_b = (1 - dist.cdf(5)) / (1 - dist.cdf(3))
print(f"(b) P(X>5|X>3) = {p_b:.4f}")
print(f" 与P(X>2)相同: {abs(p_a - p_b) < 1e-10}")

题目4:正态分布 + 容量规划

服务响应时间服从 N(150, 40²) ms,SLA 要求 99% 的请求在 X ms 内返回

(a) X 应该设为多少?

(b) 如果优化后 σ 从 40 降到 20,X 变为多少?减少了多少?

参考答案:

1
2
3
4
5
6
7
8
from scipy import stats
# (a)
x_99 = stats.norm.ppf(0.99, loc=150, scale=40)
print(f"(a) SLA 阈值: {x_99:.1f} ms")
# (b)
x_99_new = stats.norm.ppf(0.99, loc=150, scale=20)
print(f"(b) 优化后阈值: {x_99_new:.1f} ms")
print(f" 减少了: {x_99 - x_99_new:.1f} ms")

题目5:综合——选择合适的分布

判断以下场景应该用什么分布建模,并说明理由:

(a) 每天新发现的安全漏洞数量

(b) 用户密码长度

(c) 两次系统崩溃之间的时间

(d) 1000次登录中成功的次数

(e) 单次登录是否成功

参考答案:

(a) 泊松分布——单位时间内的计数事件

(b) 正态分布——集中在某个均值附近,左右对称

(c) 指数分布——事件间隔时间

(d) 二项分布——n次独立试验中的成功次数

(e) 伯努利分布——单次试验,两种结果


本节小结

离散分布核心三兄弟:伯努利 → 二项 → 泊松

连续分布核心三姐妹:均匀 → 正态 → 指数

选分布的关键问题:

结果可数还是连续?

在数”次数”还是量”间隔”?

有没有固定的试验次数?

下一节 22-假设检验与AB测试 将用这些分布做推断


上一章 目录 下一章
20-描述统计与可视化 数学重学路线图 22-假设检验与AB测试