本节摘要:概率论的起点是"把一次观察抽象成一个可重复的随机试验"。试验的全体可能结果构成样本空间 Ω,我们关心的结果子集叫事件,事件之间用并、交、差、对立四种运算互相组合。本节用"赌场押注"这一最古老的随机试验为主线,讲清 Ω 的两种形态(离散/连续)与事件运算的集合本质,最后用模拟实验演示"事件运算的布尔逻辑"如何变成可执行代码。
概率论研究的第一句话从来不是"概率是多少",而是"我在观察什么试验"。在赌场里,这个试验是掷骰子、转轮盘、发牌;在实验室里,是测寿命、量电压;在互联网上,是一次点击、一次转化。它们的共同结构是:试验可以在相同条件下重复进行,单次结果事先不可预知,但所有可能结果构成一个已知的集合。这个集合就是样本空间,记作 Ω。
以最经典的掷骰子为例:Ω = {1, 2, 3, 4, 5, 6}。任何一个"掷出 3 点"都叫一个样本点(用 ω 表示)。我们通常不关心单个样本点,而关心"结果落在某个子集里"这件事——"点数大于 3"对应 {4, 5, 6},"点数为偶数"对应 {2, 4, 6}。事件就是样本空间的子集,记为 A ⊆ Ω。事件发生,当且仅当试验结果 ω ∈ A。
这套"试验 → 样本空间 → 事件"的抽象链条,是 20 世纪前概率论没有解决的根本问题。在柯尔莫哥洛夫之前,概率学家讨论"概率"各说各话:古典派说"等可能结果的比值",频率派说"长期频率的极限",主观派说"信念程度"。柯尔莫哥洛夫 1933 年的功绩,就是先统一了"样本空间"这个地基,让所有流派都在同一张地图上谈概率。
样本空间长什么样,直接决定了后面用哪套数学工具。两个基本形态:
边界情况值得留意:一个试验的空间形态由"测量方式"决定,不由"现象本身"决定。同一颗骰子,记录点数得到离散空间;用传感器记录落地时的精确时间戳,得到连续空间。先想清楚你要记录什么,再写 Ω,这是建模的第一步。
有了子集的概念,事件之间的逻辑关系就变成集合运算。设 A、B 是两个事件:
四个运算里,并和交对应"或"和"且",是日常语言的直译;差和对立是它们的派生物。对立事件是概率计算里最常被低估的利器——后面 1.2 节会看到,"至少一个发生"这类问题算对立面往往省一半力气。
互斥与包含也是事件语言里的高频词:A、B 互斥指 A ∩ B = ∅(不可能同时发生);A ⊆ B 指 A 发生必然推出 B 发生。注意互斥不是独立——互斥说的是"交集为空",独立说的是"概率相乘",这两个概念极易混淆,1.3 节专门辨析。
事件运算的好处是它可以直接翻译成代码。下面用一个"赌场押注"的例子把四种运算全部跑一遍:同时掷两颗骰子,定义三个事件——A:"两颗点数之和为 7",B:"至少有一颗是 6",C:"两颗点数相同"。
import numpy as np from itertools import product # 样本空间:两颗骰子的全部有序结果 omega = list(product(range(1, 7), repeat=2)) print("样本空间大小 |Ω| =", len(omega)) # 36 # 事件 = 子集(用集合推导式构造) A = {o for o in omega if sum(o) == 7} # 和为 7 B = {o for o in omega if 6 in o} # 至少一颗 6 C = {o for o in omega if o[0] == o[1]} # 两颗相同 def show(name, ev): print(f"{name} = {sorted(ev)} 大小 {len(ev)}") show("A", A); show("B", B); show("C", C) show("A∪B", A | B) show("A∩C", A & C) show("A\\B", A - B) show("Bᶜ", set(omega) - B)
输出对照手算:|A|=6((1,6)…(6,1)),|B|=11,|C|=6,A∪B 的基数是 6+11−2=15((1,6) 和 (6,1) 被重复数了一次——**这正是 1.2 节容斥原理的预演**),A∩C=∅(和为 7 不可能点数相同),A\B={ (2,5),(5,2),(3,4),(4,3) },Bᶜ 有 25 个。事件运算在代码层面就是集合运算,一行一个。
纯等可能试验里,P(A) = |A| / |Ω| 是"有利点数除以总点数";但对"掷出均匀骰子"这类机制,我们更相信模拟的长期频率。把"事件是否发生"翻译成布尔数组,均值就是频率:
rng = np.random.default_rng(42) N = 1_000_000 d1 = rng.integers(1, 7, N) d2 = rng.integers(1, 7, N) def freq(mask): return mask.mean() print("P(和为7) 模拟 %.4f 手算 6/36=%.4f" % (freq(d1 + d2 == 7), 6/36)) print("P(至少一颗6) 模拟 %.4f 手算 11/36=%.4f" % (freq((d1 == 6) | (d2 == 6)), 11/36)) print("P(两颗相同) 模拟 %.4f 手算 6/36=%.4f" % (freq(d1 == d2), 6/36)) print("P(和为7且同点) 模拟 %.4f 手算 0" % freq((d1 + d2 == 7) & (d1 == d2)))
一百万次模拟与手算的差距在千分之一以内。注意代码里 (d1 == 6) | (d2 == 6) 与 & 的用法——布尔数组上的 | 和 & 就是事件运算的并和交,这正是"事件是集合、集合运算可执行"的最直接体现。这个"手算 + 模拟互证"的工作流将贯穿全册,从第 1 章一直用到第 9 章回归。
⚠️ 常见坑 1:样本空间写漏。建模第一步就少列可能结果,后面全错。自检口诀:把 Ω 列出来之后问自己一句"还有没有其他可能"。
⚠️ 常见坑 2:混淆互斥与独立。"两个事件不能同时发生"与"两个事件互不影响"是两回事,1.3 节用骰子例子给出反例。
⚠️ 常见坑 3:对离散空间套用连续思维。"P(掷出 3) 很小"在离散空间里可能很大——离散与连续的概率直觉完全不同,第 2 章专门处理。