数学重学 - 16 概率论基础

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

📌 相关笔记:概率与统计

本页核心:概率论的基本语言和计算法则,从”数数”到”更新信念”


一、概率是什么?——从直觉开始

直觉比喻:概率就是”惊讶程度的反面”

太阳明天升起?概率≈1,你不惊讶

连续中两次彩票?概率极小,你非常惊讶

概率高 = 预期之中;概率低 = 出乎意料

正式定义(古典概型):

$$P(A) = \frac{\text{事件A的有利结果数}}{\text{所有等可能结果总数}}$$

前提条件:每个结果等可能出现

例子:掷一个公平骰子

总结果数 = 6

P(掷出偶数) = {2,4,6} / {1,2,3,4,5,6} = 3/6 = 0.5

概率的三条公理(柯尔莫哥洛夫):

公理1:P(A) ≥ 0(概率非负)

公理2:P(Ω) = 1(必然事件概率为1)

公理3:互斥事件可加 P(A∪B) = P(A) + P(B)(当A∩B=∅)

Python验证:掷骰子频率趋近概率

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import random

def dice_experiment(n_rolls):
"""掷n次骰子,观察偶数出现的频率"""
even_count = 0
for _ in range(n_rolls):
if random.randint(1, 6) % 2 == 0:
even_count += 1
return even_count / n_rolls

# 随着次数增加,频率趋近0.5(大数定律的直觉)
for n in [100, 1000, 10000, 100000]:
freq = dice_experiment(n)
print(f"掷{n:>7}次: 偶数频率 = {freq:.4f}")
# 掷 100次: 偶数频率 = 0.5200
# 掷 1000次: 偶数频率 = 0.4970
# 掷 10000次: 偶数频率 = 0.5012
# 掷 100000次: 偶数频率 = 0.4998

二、概率基本运算

2.1 加法规则(或事件)

一般形式:P(A∪B) = P(A) + P(B) - P(A∩B)

直觉:两个圆的面积之和,要减去重叠部分,否则算了两次

特例——互斥事件(A∩B=∅):P(A∪B) = P(A) + P(B)

例子:一副52张牌,抽到红心或国王的概率

P(红心) = 13/52, P(国王) = 4/52, P(红心国王) = 1/52

P(红心∪国王) = 13/52 + 4/52 - 1/52 = 16/52 ≈ 0.308

2.2 补事件

P(Ā) = 1 - P(A)

直觉:不想直接算”至少一个”,就算”一个都没有”再用1减

经典技巧:求”至少一次”的概率

P(至少一次) = 1 - P(一次都没有)

例子:掷3次骰子,至少出现一次6的概率

P(一次都不出6) = (5/6)³ ≈ 0.579

P(至少一次6) = 1 - 0.579 ≈ 0.421

2.3 乘法规则(且事件)

一般形式:P(A∩B) = P(A) × P(B|A)

独立事件特例:P(A∩B) = P(A) × P(B)

独立的直觉:A发不发生对B没有任何影响

Python验证加法规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 模拟一副牌
deck = [(suit, rank) for suit in ['红心','黑桃','方块','梅花']
for rank in range(1, 14)] # 1=A, 11=J, 12=Q, 13=K

hearts = {c for c in deck if c[0] == '红心'} # 13张
kings = {c for c in deck if c[1] == 13} # 4张
both = hearts & kings # 1张
either = hearts | kings # 16张

print(f"P(红心) = {len(hearts)}/52 = {len(hearts)/52:.4f}")
print(f"P(国王) = {len(kings)}/52 = {len(kings)/52:.4f}")
print(f"P(红心∩国王)= {len(both)}/52 = {len(both)/52:.4f}")
print(f"P(红心∪国王)= {len(either)}/52 = {len(either)/52:.4f}")
# 验证: 13/52 + 4/52 - 1/52 = 16/52 ✓

三、条件概率

3.1 定义

P(A|B) = P(A∩B) / P(B)

读作”在B已经发生的条件下,A发生的概率”

直觉比喻:缩小了样本空间

原来的宇宙是Ω,现在你知道B发生了,宇宙缩小到B

在B这个小宇宙里,A还占多大比例?

3.2 例子

掷两个骰子,已知总和≥10,两个都是5或6的概率?

总和≥10的情况:{(4,6),(5,5),(5,6),(6,4),(6,5),(6,6)} → 6种

两个都≥5的情况:{(5,5),(5,6),(6,5),(6,6)} → 4种

P = 4/6 = 2/3

3.3 独立事件判定

A和B独立 ⟺ P(A|B) = P(A) ⟺ P(A∩B) = P(A)×P(B)

直觉:知道B发生了也不改变你对A的判断

注意:互斥 ≠ 独立!互斥事件不独立(一个发生了另一个一定不发生)


四、贝叶斯定理——用证据更新信念

4.1 公式

$$P(A|B) = \frac{P(B|A) \times P(A)}{P(B)}$$

各部分的名字:

P(A) = 先验概率(没看到证据前的信念)

P(B|A) = 似然(如果A为真,看到B的可能性)

P(B) = 证据的总概率

P(A|B) = 后验概率(看到证据后更新的信念)

4.2 直觉比喻

你是一个侦探:

先验 = 嫌疑人有多可疑(根据背景信息)

似然 = 如果他是凶手,这个证据出现的概率有多大

后验 = 看到证据后,他是凶手的概率

贝叶斯就是”理性地根据新证据调整信念”

4.3 经典例子:检测阳性,真得病了吗?

场景设定:

某病患病率 P(病) = 0.1%(千分之一)

检测灵敏度 P(阳|病) = 99%(真有病时99%测出来)

检测特异度 P(阴|没病) = 95%(没病时95%测正常)→ P(阳|没病) = 5%(假阳性率)

问:测出阳性,真得病的概率?

计算:

P(阳) = P(阳|病)×P(病) + P(阳|没病)×P(没病)

P(阳) = 0.99×0.001 + 0.05×0.999 = 0.00099 + 0.04995 = 0.05094

P(病|阳) = 0.00099 / 0.05094 ≈ 1.94%

反直觉结论:即使检测99%准确,阳性者中只有不到2%真有病!

原因:患病的人太少了,假阳性的绝对数量远超真阳性

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
def bayes(prior_a, likelihood_b_given_a, likelihood_b_given_not_a):
"""
贝叶斯定理计算器
prior_a: P(A) 先验概率
likelihood_b_given_a: P(B|A)
likelihood_b_given_not_a: P(B|¬A)
返回: P(A|B) 后验概率
"""
p_b = likelihood_b_given_a * prior_a + \
likelihood_b_given_not_a * (1 - prior_a)
posterior = (likelihood_b_given_a * prior_a) / p_b
return posterior

# 医学检测例子
result = bayes(
prior_a=0.001, # 患病率0.1%
likelihood_b_given_a=0.99, # 灵敏度99%
likelihood_b_given_not_a=0.05 # 假阳性率5%
)
print(f"检测阳性后真患病概率: {result:.4f} = {result*100:.2f}%")
# 输出: 检测阳性后真患病概率: 0.0194 = 1.94%

# 如果复查第二次也阳性(用第一次的后验当新先验)
result2 = bayes(
prior_a=result,
likelihood_b_given_a=0.99,
likelihood_b_given_not_a=0.05
)
print(f"二次阳性后真患病概率: {result2:.4f} = {result2*100:.2f}%")
# 输出: 二次阳性后真患病概率: 0.2838 = 28.38%

五、安全领域应用

5.1 入侵检测误报率(贝叶斯思维)

IDS报警了,真被入侵的概率?

P(入侵) = 0.01%(日常被真入侵的概率很低)

P(报警|入侵) = 95%(真入侵时大概率报警)

P(报警|正常) = 2%(正常流量误报率)

P(入侵|报警) = (0.95×0.0001)/(0.95×0.0001+0.02×0.9999) ≈ 0.47%

结论:绝大多数IDS报警都是误报,这就是安全运营的痛

1
2
3
4
5
6
7
ids_result = bayes(
prior_a=0.0001,
likelihood_b_given_a=0.95,
likelihood_b_given_not_a=0.02
)
print(f"IDS报警后真入侵概率: {ids_result*100:.2f}%")
# 约0.47% —— 这就是为什么SOC团队需要多级过滤

5.2 密码碰撞概率

密码空间大小N,随机选k个密码,至少两个相同的概率

这就是生日问题的推广(详见 19-哈希数学

4位数字密码(N=10000),100人至少两人相同:P ≈ 1-e^(-100²/(2×10000)) ≈ 39.3%

5.3 社会工程钓鱼成功率

假设单次钓鱼成功率 p = 3%

发给1000人,至少1人上钩的概率?

P(至少1人) = 1 - (1-0.03)^1000 = 1 - 0.97^1000 ≈ 1.0(几乎必然成功)

1
2
3
4
5
6
7
8
9
10
p_single = 0.03
n_targets = 1000
p_at_least_one = 1 - (1 - p_single) ** n_targets
print(f"1000人中至少1人上钩: {p_at_least_one:.10f}")
# 0.9999999998 —— 几乎100%

# 即使p=0.1%,1000人也有63%概率至少1人上钩
p_low = 0.001
print(f"p=0.1%, 1000人至少1人上钩: {1-(1-p_low)**1000:.4f}")
# 0.6321

六、大数据应用:朴素贝叶斯分类器

6.1 原理

朴素假设:各特征条件独立(虽然通常不严格成立,但效果好)

分类规则:选使 P(类别|特征) 最大的类别

P(类别|特征1,特征2,…) ∝ P(类别) × ∏P(特征i|类别)

6.2 垃圾邮件过滤器

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
53
54
55
56
57
from collections import defaultdict
import math

class NaiveBayesSpamFilter:
"""极简朴素贝叶斯垃圾邮件过滤器"""
def __init__(self):
self.word_counts = {'spam': defaultdict(int), 'ham': defaultdict(int)}
self.class_counts = {'spam': 0, 'ham': 0}
self.vocab = set()

def train(self, emails):
"""emails: [(文本, 'spam'/'ham'), ...]"""
for text, label in emails:
self.class_counts[label] += 1
for word in text.lower().split():
self.word_counts[label][word] += 1
self.vocab.add(word)

def predict(self, text):
"""返回 'spam' 或 'ham'"""
words = text.lower().split()
total = sum(self.class_counts.values())
best_label, best_score = None, float('-inf')

for label in ['spam', 'ham']:
# log先验
score = math.log(self.class_counts[label] / total)
vocab_size = len(self.vocab)
total_words = sum(self.word_counts[label].values())

for word in words:
# 拉普拉斯平滑
count = self.word_counts[label].get(word, 0) + 1
score += math.log(count / (total_words + vocab_size))

if score > best_score:
best_score = score
best_label = label

return best_label

# 演示
training_data = [
("免费 领取 优惠券 点击 链接", "spam"),
("中奖 恭喜 获得 大奖 立即 领取", "spam"),
("贷款 低息 快速 审批 无抵押", "spam"),
("明天 会议 讨论 项目 进度", "ham"),
("代码 review 提交 合并 分支", "ham"),
("数据库 迁移 方案 评审 周三", "ham"),
]

clf = NaiveBayesSpamFilter()
clf.train(training_data)

test_emails = ["免费 领取 大奖", "明天 代码 review"]
for email in test_emails:
print(f"'{email}' → {clf.predict(email)}")

七、后端应用:系统可用性计算

7.1 可用性定义

可用性 = 正常运行时间 / 总时间

几个9的含义(一年365天):

等级 可用性 年停机时间 场景
2个9 99% 3.65天 个人项目
3个9 99.9% 8.76小时 一般业务
4个9 99.99% 52.6分钟 重要业务
5个9 99.999% 5.26分钟 金融/电信

7.2 冗余提升可用性

单机可用性 a = 99.9%(3个9)

n台并行冗余:系统不可用 = 所有机器都挂 = (1-a)^n

系统可用性 = 1 - (1-a)^n

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
33
34
35
36
37
38
39
def availability(single_a, n_replicas):
"""并行冗余系统可用性"""
sys_unavailable = (1 - single_a) ** n_replicas
sys_available = 1 - sys_unavailable
return sys_available

def downtime_per_year(avail):
"""年停机时间(分钟)"""
return (1 - avail) * 365.25 * 24 * 60

single = 0.999 # 单机3个9
print("=== 并行冗余提升可用性 ===")
for n in [1, 2, 3, 5]:
a = availability(single, n)
dt = downtime_per_year(a)
nines = -math.log10(1 - a) if a < 1 else float('inf')
print(f"{n}台冗余: 可用性={a:.10f}, "
f"年停机={dt:.4f}分钟, ≈{nines:.1f}个9")

# 1台冗余: 可用性=0.9990000000, 年停机=525.9600分钟, ≈3.0个9
# 2台冗余: 可用性=0.9999990000, 年停机=0.5260分钟, ≈6.0个9
# 3台冗余: 可用性=0.9999999990, 年停机=0.0005分钟, ≈9.0个9

import math

# 串联系统(所有环节都不能挂)
def serial_availability(components):
"""串联系统:总可用性 = 各组件可用性之积"""
result = 1.0
for a in components:
result *= a
return result

# 典型后端链路: nginx → app → db
chain = [0.999, 0.999, 0.999]
total = serial_availability(chain)
print(f"\n三层串联(各99.9%): 总可用性={total:.6f}")
print(f"年停机: {downtime_per_year(total):.1f}分钟")
# 总可用性=0.997003, 年停机≈1577分钟≈26小时

八、蒙特卡罗模拟

8.1 核心思想

用大量随机试验的频率来逼近概率或期望值

直觉比喻:不会算面积?往上面扔豆子,数落在里面的比例

适用场景:解析解太难求或不存在时

8.2 蒙特卡罗估算π

原理:在1×1正方形内随机撒点

落在半径=1的四分之一圆内的比例 ≈ π/4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import random

def monte_carlo_pi(n_points):
"""用蒙特卡罗方法估算π"""
inside = 0
for _ in range(n_points):
x = random.random()
y = random.random()
if x*x + y*y <= 1:
inside += 1
return 4 * inside / n_points

for n in [1000, 10000, 100000, 1000000]:
pi_est = monte_carlo_pi(n)
error = abs(pi_est - 3.14159265) / 3.14159265 * 100
print(f"n={n:>8}: π≈{pi_est:.6f}, 误差={error:.3f}%")

# n= 1000: π≈3.156000, 误差=0.460%
# n= 10000: π≈3.145200, 误差=0.115%
# n= 100000: π≈3.141080, 误差=0.016%
# n= 1000000: π≈3.141796, 误差=0.006%

8.3 蒙特卡罗解实际问题:系统容量规划

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import random

def simulate_request_overflow(n_simulations, avg_rps, max_capacity):
"""
模拟请求溢出概率
假设请求量服从泊松分布(λ=avg_rps)
"""
overflow_count = 0
for _ in range(n_simulations):
# 简易泊松随机数
actual_rps = sum(1 for _ in range(int(avg_rps * 3))
if random.random() < 1/3)
if actual_rps > max_capacity:
overflow_count += 1
return overflow_count / n_simulations

avg_rps = 100 # 平均每秒100个请求
max_capacity = 120 # 最大容量120 QPS
p_overflow = simulate_request_overflow(10000, avg_rps, max_capacity)
print(f"请求溢出概率: {p_overflow*100:.2f}%")

九、练习题

练习1:加法规则

一个班有40人,25人会Python,20人会Java,10人两种都会

随机选1人,会PythonJava的概率?

答案:P(Py∪Java) = 25/40 + 20/40 - 10/40 = 35/40 = 87.5%

练习2:补事件

系统有4个独立组件,每个可用性为99%

串联部署,系统至少有一个组件故障的概率?

P(全部正常) = 0.99^4 ≈ 0.9606

P(至少一个故障) = 1 - 0.9606 ≈ 3.94%

练习3:贝叶斯定理

WAF的拦截规则:P(拦截|攻击)=98%,P(拦截|正常)=1%,P(攻击)=0.5%

某请求被拦截,真是攻击的概率?

P(攻击|拦截) = (0.98×0.005) / (0.98×0.005 + 0.01×0.995)

= 0.0049 / (0.0049 + 0.00995) = 0.0049 / 0.01485 ≈ 33.0%

练习4:独立事件

3台独立服务器各有99.9%可用性,并行部署(任一可用即可)

系统可用性是多少?相当于几个9?

P(不可用) = (0.001)^3 = 10^(-9)

P(可用) = 1 - 10^(-9) = 0.999999999 → 9个9

练习5:蒙特卡罗

用蒙特卡罗方法估算:两个骰子之和为7的概率

1
2
3
4
5
6
7
import random

n = 100000
count = sum(1 for _ in range(n)
if random.randint(1,6) + random.randint(1,6) == 7)
print(f"P(和=7) ≈ {count/n:.4f}")
# 理论值: 6/36 = 1/6 ≈ 0.1667

十、本章小结

概率是对不确定性的量化,取值 [0, 1]

加法规则处理”或”,乘法规则处理”且”,补事件处理”至少”

条件概率缩小样本空间,贝叶斯定理用证据更新信念

贝叶斯在安全(IDS误报分析)和大数据(垃圾邮件过滤)中有直接应用

冗余系统可用性 = 1-(1-a)^n 是后端架构的基本算术

蒙特卡罗:算不了的概率就用大量随机试验去逼近

➡️ 下一页:17-图论基础


上一章 目录 下一章
15-求和公式与级数 数学重学路线图 17-图论基础