ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

概率论基础知识:从条件概率到A/B实验的工程决策

概率论基础知识:从条件概率到A/B实验的工程决策 1. 概率论到底在算什么把可能翻译成可计算的数接口错误率 0.3%日均调用 200 万次明天会不会恰好撞上一次失败64 位哈希表里存了 3 亿条记录碰撞的概率到底是万分之一还是百分之一A/B 实验跑了三天转化率涨了 1.2%这个数字是真信号还是噪声这几个问题横跨后端、存储、数据运营看起来八竿子打不着但拆到底层是同一件事概率论基础知识。我做过不少数据分析和线上容量评估的活最深的体会是——大部分人不是不会算而是根本没意识到眼前这个问题是个概率问题于是凭直觉拍了个数然后在某个深夜被现实教育。这篇文章不走教材路线。我会把样本空间、条件概率、随机变量、极限定理这些概念全部放到工程和业务的真实场景里讲一遍同时把那些直觉一定会骗你的地方单独拎出来说清楚。适合谁看写过代码但概率早还给老师的人、做数据分析和实验评估的人、以及所有需要在信息不全的情况下做决策的人。零基础也能读因为每一个抽象概念我都会先给生活类比再给可跑的代码。1.1 为什么工程问题到最后都会变成概率问题确定性思维有个隐含假设只要我的逻辑没写错、参数没填错结果就应该是可预测的。这个假设在单机、小数据量、低并发的时候勉强成立。一旦系统规模上去变量数量爆炸你就再也没法穷举所有情况了。这时候唯一能做的是描述各类结果出现的可能性有多大。举几个我实际遇到过的例子。评估一套推荐系统上线后的效果你不可能让同一个用户在完全相同的时刻既看旧版本又看新版本所以只能比较两个用户群体的平均指标而群体之间的差异里天然混着随机波动。做容量规划时单次请求的耗时不是固定值而是一条分布曲线你要是按平均值去配置机器那必然有一半的请求超时。做风控时一条规则命中了也不能直接判定为欺诈因为你还要考虑这个行为在正常用户里的基础发生率。这三个场景的共同点在于我们能观测到的只是随机变量的一次抽样而决策需要基于背后的分布。概率论提供的正是从样本倒推分布、从分布推算风险的整套工具。理解了这一层你就不会再把概率当成一门需要背公式的课而是当成一套日常决策的语言。1.2 样本空间与事件把模糊描述翻译成集合几乎所有概率计算的第一个错误都发生在建模阶段而不是计算阶段。而建模的核心动作就一件事把自然语言的描述翻译成明确的集合。三个基本概念样本空间 Ω一次试验所有可能结果的集合。掷一次骰子Ω {1,2,3,4,5,6}一次接口调用Ω {成功, 超时, 业务错误, 服务端异常}注意这里必须把失败拆开否则后面算不出超时占比。事件 A样本空间的一个子集也就是我们关心的那些结果的集合。比如点数大于 4对应 {5,6}。概率 P一个从事件到 [0,1] 实数的映射。这里最容易踩的坑是样本空间划分不完整或者重叠。比如你要评估用户是否会下单如果样本空间只写了 {下单, 不下单}后面的分析就做不下去了因为你没法区分浏览了但没买和压根没打开 App。我一般建议在建模阶段就把样本空间拆到互斥且穷尽业界叫 MECE宁可多几个状态也别留模糊地带。多状态带来的计算量增加是线性的而模糊地带带来的返工是指数级的。1.3 三条公理和它们的直接推论柯尔莫哥洛夫给出的三条公理是整个概率论的地基值得原样抄一遍非负性对任意事件 AP(A) ≥ 0。规范性P(Ω) 1。可列可加性若 A₁, A₂, … 两两互斥则 P(∪Aᵢ) ΣP(Aᵢ)。看着像废话但由它们能推出所有你在用的公式。比如两个事件 A、B不一定互斥有P(A ∪ B) P(A) P(B) − P(A ∩ B)很多人会顺口说成 P(A) P(B)这在 A、B 互斥的时候才成立。而现实中 A、B 往往不互斥。举个例子某天支付服务告警的概率是 0.05订单量下跌的概率是 0.08两者同时发生的概率是 0.03。那么至少出现一个异常的概率是 0.05 0.08 − 0.03 0.10而不是 0.13。这个减法看着微不足道但在做告警风暴归因的时候如果你不去重会系统性地高估故障范围。还有一个推论我用得很多叫容斥与补集P(A) 1 − P(Ā)。当你算至少出现一次很难、但算一次都不出现很容易时补集是救命工具。后面讲哈希碰撞、生日悖论用的都是这一招。2. 条件概率线上问题几乎都藏在这个前提里如果说有一块内容被误用得最狠那一定是条件概率和贝叶斯公式。我在评审数据报告时见过太多次这样的表述告警命中说明故障概率很高准确率 99%但你追问一句故障本身的发生率是多少对方就答不上来了。而这个被忽略的数字恰恰决定了结论对不对。2.1 条件概率的定义与乘法公式的真实含义条件概率的定义很简洁P(A|B) P(A ∩ B) / P(B)要求 P(B) 0它回答的是在已知 B 发生的前提下A 发生的可能性。这里的关键在于条件概率不是把 A 的概率改一改而是把样本空间从 Ω 缩小到了 B。分母 P(B) 就是缩小的动作分子 P(A ∩ B) 就是在新的样本空间里 A 占的部分。把定义式两边乘以 P(B)就得到乘法公式P(A ∩ B) P(A|B) · P(B) P(B|A) · P(A)这条式子在实际排查里非常有用。比如你观测到缓存命中率突然下降 20%你想知道是不是某个上游业务变更导致的。已知上游变更发生的概率是 0.2变更发生后命中率下降的概率是 0.6没变更的情况下命中率下降的概率是 0.05。那么命中率下降且发生了上游变更的概率是 0.6 × 0.2 0.12而下降但没变更的概率是 0.05 × 0.8 0.04。所以在这次下降事件里有 12/(124) 75% 的可能性是变更引起的。这个 75% 就是后面要讲的贝叶斯结论。要注意一个高频误区P(A|B) 和 P(B|A) 完全不是一回事。前者是变更导致下降后者是下降意味着变更。混淆这两个方向的概率是误判的主要来源之一。2.2 全概率公式把复杂场景切成互斥分支现实问题往往不能直接算出目标事件的概率但可以把它按某个维度切开。全概率公式干的就是这件事若 B₁, B₂, …, Bₙ 两两互斥且并集为 Ω则 P(A) Σ P(A|Bᵢ) · P(Bᵢ)用人话说把整体拆成几类互不重叠的情况分别算每种情况下目标发生的概率再按各类的占比加权求和。举个容量评估的例子。一个服务的请求来自三个渠道App占 60%平均耗时 120ms、小程序占 25%平均耗时 200ms、开放 API占 15%平均耗时 400ms。那么整体平均耗时就是渠道流量占比 P(Bᵢ)平均耗时 P(A|Bᵢ)加权贡献App0.60120 ms72 ms小程序0.25200 ms50 ms开放 API0.15400 ms60 ms合计1.00—182 ms看到没开放 API 只占 15% 的流量却贡献了 60ms接近总量的三分之一。如果你只盯着 App 的性能优化收益天花板是很低的。全概率公式的价值就在这儿它把整体平均这个模糊指标拆成了可归因、可排序的分项贡献。使用时唯一的要求是分组必须互斥且穷尽。如果允许一个请求同时属于两个渠道公式就不成立了。实操中我建议分组维度一次只用一个不要交叉否则很容易出现既漏又重的局面。2.3 贝叶斯公式与基率谬误一次告警误报率的完整推演把全概率公式代进条件概率的对称式里就得到了贝叶斯公式P(Bᵢ|A) P(A|Bᵢ) · P(Bᵢ) / Σ P(A|Bⱼ) · P(Bⱼ)它做的事是把先验知识和新证据结合起来更新我们的判断。P(Bᵢ) 是看到证据之前的判断先验P(Bᵢ|A) 是看到证据之后的判断后验。现在用一个真实的告警场景算一遍。假设线上真实故障的发生率 P(故障) 0.1%也就是一天里大约千分之一的时段处于故障状态告警规则对真实故障的召回率 P(告警|故障) 99%告警规则在正常状态下误报的概率 P(告警|正常) 5%一条单看指标很漂亮的规则99% 的召回、95% 的准确率这里说的是特异度。但真实命中率是多少项计算结果故障且告警0.99 × 0.0010.00099正常且告警0.05 × 0.9990.04995总告警概率0.00099 0.049950.05094告警中真故障的比例0.00099 / 0.05094≈ 1.94%也就是说每 100 条告警里只有不到 2 条是真故障剩下 98 条都是误报。这就是告警疲劳的数学根源。不是规则写得差而是故障的基率太低任何非零的误报率都会被低基率放大。我见过很多团队处理这个问题的方向是错的——他们拼命优化召回率把 99% 提到 99.9%但对这个比例的影响微乎其微。真正有效的是两条路降低误报率或者提高基率比如把多条弱信号聚合成一条复合告警或者缩短观测窗口让故障在窗口内的占比上升。前者是分母变小后者是分子变大效果都比压召回率来得直接。这个结论如果不靠贝叶斯推一遍光凭直觉是得出来的。3. 随机变量与分布选型九成的建模错误出在这一步算条件概率之前得先知道概率是多少。到了这一步我们会把试验结果映射成数字也就是随机变量。这一步看起来只是个形式化动作但分布选型的正确与否决定了后面所有计算的成败。而且这类错误往往不会立刻暴露等到数据量大了才突然离谱。3.1 离散与连续先分清你的数据属于哪一类随机变量分两类离散型取值可数。请求次数、失败条数、用户点击数、一段时间内到达的请求数。这类用一个概率质量函数PMF描述每个取值的概率。连续型取值在某个区间内连续。响应耗时、金额、温度、误差。这类用概率密度函数PDF描述注意单点概率为 0只有区间才有概率。为什么要强调这个区别因为很多人会拿离散的套路去套连续数据。最典型的错误是响应耗时等于 200ms 的概率是多少——这个问题在连续分布下答案就是 0。应该问的是响应耗时落在 180ms 到 220ms 之间的概率是多少。另外连续分布里有一个操作叫离散化就是把耗时切成若干个桶。这个操作本身没问题但桶的边界会影响结论。我在做耗时分析时踩过一次坑按 100ms 切桶时看不到任何异常改成按对数刻度10ms、30ms、100ms、300ms、1000ms切之后异常立刻显现出来了。原因很简单耗时本身近似对数正态分布线性切桶会把长尾挤在最后一个桶里信息全丢了。3.2 常见分布速查与适用场景对照下面这张表是我自己整理的工作用速查表列的都是在工程和数据场景里真正会碰到的分布分布参数均值方差典型适用场景伯努利ppp(1−p)单次点击、单次成功/失败二项n, pnpnp(1−p)n 次独立试验的成功次数、转化用户数泊松λλλ单位时间到达的请求数、故障次数几何p1/p(1−p)/p²首次成功所需的试验次数、重试次数超几何N, K, nnK/N—不放回抽样、缓存池命中均匀a, b(ab)/2(b−a)²/12哈希取模、随机分桶指数λ1/λ1/λ²事件间隔时间、连接空闲时长正态μ, σ²μσ²测量误差、聚合后的指标对数正态μ, σ²——响应耗时、文件大小、收入分布伽马α, βαβαβ²多个指数事件的总耗时贝塔α, β——转化率本身的不确定性建模选分布的时候我会问自己三个问题取值是次数还是时间事件之间是否独立有没有明显的长尾次数类问题独立的话优先看二项或泊松。泊松有个非常好用的性质期望等于方差。所以你可以拿样本均值和样本方差比一下如果方差远大于均值业内叫过离散说明事件之间有聚集泊松不合适得换成负二项或者混合模型。这个检查我基本每次做到达率分析都会跑一遍成本几行代码能挡掉不少低级错误。时间间隔类问题如果事件独立且发生率恒定间隔服从指数分布。指数分布有个著名的无记忆性已经等了 10 分钟接下来还需要等多久的分布和一开始就是一样的。这个性质在排队论里是关键假设但现实中用户行为往往不满足——用户等久了就会关掉页面这时候得换成带有耐心上限的分布。耗时类问题我个人的默认起点是对数正态。它的形状是正偏的右尾长正好符合大部分请求很快少量请求极慢的现实。只有在明确知道尾部的成因比如某个慢查询触发了超时重试时才会考虑用帕累托之类的重尾分布。3.3 期望与方差的工程含义期望 E[X] 和方差 Var(X) 这两个数字在工程语境里有非常具体的对应期望 长期的、平均意义上的成本。它是可加的而且不要求独立性E[X Y] E[X] E[Y]无论 X、Y 是否相关。这条期望线性性是被严重低估的工具很多看起来很吓人的问题用它可以一行解出来。比如 n 个用户各自独立地以概率 p 下单总下单数的期望就是 np不需要任何复杂的推导。方差 波动 / 不确定性 / 风险。它衡量的是结果偏离期望的幅度。方差不是线性可加的Var(X Y) Var(X) Var(Y) 2Cov(X, Y)。只有 X、Y 相互独立或至少不相关时Cov 项才为 0。这个区别在实际中很重要。举例一个服务的平均耗时是 100ms方差很小那你按 120ms 配超时就够用。但如果耗时均值同样是 100ms方差极大即使均值达标超时率依然可能很高。只看均值做容量规划必然会漏掉长尾引发的超时。我一般会同时看 P50、P90、P99 三个分位数因为分位数比方差更直观地告诉你会影响多少比例的用户。还有一个工具是协方差与相关系数Corr(X, Y) Cov(X, Y) / (σ_X · σ_Y)相关系数的取值范围是 [−1, 1]无量纲便于比较不同量纲的变量。但必须强调一句相关不等于因果。我在做归因分析时反复提醒自己这一点两个指标同涨同跌可能只是都被第三个因素驱动。要确定因果得靠实验或者结构化的假设检验光看相关系数是不够的。4. 大数定律与中心极限定理样本量到底要多少才够这一节解决的问题是我跑了一周的实验看到的差异能不能信这是数据分析和 A/B 实验里最核心的问题而答案完全建立在两个极限定理上。4.1 大数定律在说什么以及它不说什么大数定律说当样本量 n 趋于无穷时样本均值 X̄ 依概率收敛到总体均值 μ。通俗版样本足够大平均值就会接近真实值。这解释了为什么实验要跑够量。但它的边界比想象中窄得多有三个必须记住的点它不保证收敛速度。n 要多大才足够完全取决于方差。方差大收敛就慢。抛硬币收敛很快因为单次抛掷的方差只有 0.25而用户消费金额方差极大可能需要几十万样本才能稳定。它对单个样本无约束。大数定律说的是序列的均值不是说抛了十次正面之后该出反面了。这就是赌徒谬误的来源后面会专门说。它要求同分布。如果样本来自不同的分布比如把工作日和周末的流量混在一起算大数定律的前提就被破坏了算出来的均值没有明确含义。这就是为什么做指标对比时要先做时间维度上的分层。4.2 中心极限定理与置信区间的关系如果说大数定律告诉你均值会收敛中心极限定理则告诉你围绕均值有多大的波动以及这个波动长什么样。对独立同分布、方差有限的随机变量当 n 足够大时 (X̄ − μ) / (σ / √n) 近似服从标准正态分布 N(0, 1)这条定理有两个反直觉的地方我认为是它最有价值的点第一它不要求原始数据服从正态分布。原始数据可以是抛硬币、可以是极偏的长尾耗时只要 n 够大实践中 30 就已经不错样本均值就会呈现钟形分布。这就是为什么均值这个统计量如此通用它天然自带正态性。第二波动是随 √n 衰减的。要把误差减半样本量得翻两番。这个 1/√n 的规律解释了为什么实验后期提升样本量变得极其昂贵。从 1000 到 10000样本翻了 10 倍精度只提升约 3.16 倍。所以做实验设计时与其一味延长实验时间不如先从降低指标的方差入手比如用协变量调整、用 CUPED 之类的方差缩减技术效果通常是数倍的。有了 CLT置信区间就顺理成章了。当总体方差未知时用样本标准差 s 代替X̄ ± z · s / √n其中 95% 置信度对应 z 1.9699% 对应 2.58。4.3 用切比雪夫不等式估样本量CLT 好用但它是个近似而且在方差未知、样本量还不够大的时候不太可靠。这时候可以退一步用切比雪夫不等式它不需要任何分布假设P(|X − μ| ≥ kσ) ≤ 1 / k²它只要求方差存在。代价是非常保守。取 k 2它告诉你偏离超过 2 个标准差的概率不超过 25%而正态分布给出的实际值是约 4.6%。差了五倍多。所以切比雪夫适合兜底不适合精算。但我还是经常用它因为它在做粗略样本量估算时非常快。把不等式套到样本均值上可以得到P(|X̄ − μ| ≥ ε) ≤ σ² / (n · ε²)如果要让误差超过 ε 的概率小于 α只需要 n ≥ σ² / (α · ε²)。这个式子甚至不需要分布假设能给你一个数量级上的直觉方差翻倍样本量翻倍精度提高一倍样本量变成四倍。对于比例型指标比如转化率平方根公式更常用n ≈ z² · p(1 − p) / E²其中 E 是你能接受的误差幅度。取 p 0.5最保守95% 置信度不同误差下的样本量可接受误差 E所需样本量 n±5%385±3%1068±1%9604这张表值得记住。很多人做小流量实验只有几百个样本却想去判断 1% 的差异这在数学上就是不可能的——误差幅度比信号还大。我见过太多这样的实验结论被推翻根因都是这个。5. 概率陷阱清单直觉最容易骗人的几个地方概率论有个特殊性它不仅反直觉而且你越相信直觉错得越离谱。这一节我把几个最经典的陷阱和它们背后的数学原因放在一起每一个都能对应到实际工作中。5.1 生日悖论与哈希碰撞为什么 23 个人就够问题是这样的一个房间里有多少人才能让至少有两人生日相同的概率超过 50%直觉答案通常是 183365 的一半。正确答案是23。算一遍。用补集先算所有人生日都不同的概率。第一个人随便哪天第二个人要和他不同概率 364/365第三个人要跟前两个都不同概率 363/365……所以P(全不同) (365/365) × (364/365) × … × ((365−n1)/365)当 n 23 时这个乘积约等于 0.493所以至少两人生日相同的概率是 1 − 0.493 ≈ 0.507。当 n 70 时这个概率已经超过 99.9%。直觉错在哪因为我们习惯性地把自己代入想的是有人和我同生日那是 n/365 的量级但题目问的是任意两个人之间配对数按 n(n−1)/2 增长23 个人就有 253 对。数量的增长方式是平方级的不是线性的。这个结论直接对应哈希碰撞。对 64 位哈希空间用近似式 P ≈ 1 − exp(−n²/(2·2⁶⁴))要让碰撞概率达到 50%解出来 n ≈ 1.177 × 2³² ≈ 51 亿。想让碰撞概率低于百万分之一n 大约 2³² × √(2×10⁻⁶) ≈ 610 万条。这个数量级非常关键64 位哈希在几百万量级上基本安全在几十亿量级上就会开始出问题。做分布式 ID、去重、内容指纹的时候这个数字要心里有数。5.2 蒙提霍尔问题为什么换门更优三门问题三扇门一扇后面有车你选了一扇。主持人知道车在哪然后打开另一扇后面是羊的门问你换不换。正确答案是换胜率 2/3不换只有 1/3。解释这件事最清楚的方式是重新理解信息结构。你第一次选的时候选中的概率是 1/3选错的概率是 2/3。关键在于主持人打开门这个动作不是随机的他在利用自己的知识避开有车的门。所以如果你一开始选错了概率 2/3那么剩下的那扇没开的门必然有车换门就赢如果你一开始选对了概率 1/3换门就输。所以换门胜率就是 2/3。我一开始也想不明白后来把它推广到 100 扇门就想通了你选一扇主持人打开 98 扇羊门只剩两扇。这时候你还觉得是 50/50 吗显然不是因为主持人几乎是在替你排除错误选项。这个案例的工程意义在于当信息提供方拥有额外知识时他的行为本身就是信号。做推荐、做搜索排序、看监控告警都要考虑这一点。用户点击某一项可能是内容真的相关也可能只是因为排在前面系统给出某个结果可能是模型真的判断相关也可能是被某个先验偏置推上去的。忽略信息提供方的知识就会把选择偏差当成真实偏好。5.3 赌徒谬误与热手效应两头的坑赌徒谬误连出 5 次正面就觉得下一次该出反面了。这是错的——如果硬币是公平的每次抛掷相互独立第 6 次的正面概率还是 1/2。前面 5 次的结果不会欠你一次反面。热手效应的另一面是看到某个东西连续出现就觉得它正在状态会继续出现。这在独立事件上同样是错的。两个方向都错根源是同一个混淆了独立事件和有记忆的过程。判断的关键是问这个过程有没有反馈回路抛硬币没有所以独立。而篮球手感的争议在于投篮命中会影响信心信心会改变下次命中率所以可能真的存在弱相关——但这需要统计检验来确认不能凭感觉。在工作里这个陷阱的常见形式是看到一个接口昨天失败率突然上升就判断它进入了一个不稳定期。如果失败是独立随机的那昨天的上升不改变今天的分布。真正该做的是看失败是否有聚集性——用前面提到的方差/均值比检验或者做游程检验。有聚集才说明存在正反馈比如雪崩、重试风暴那才是真正需要干预的。5.4 辛普森悖论分组数据和整体数据打架这是数据分析里最阴险的一个坑因为它不需要你算错每一步计算都是对的结论却是反的。假设有两种方案 A 和 B按用户类型分成两组看转化率分组方案 A方案 B新用户93/100 93%87/100 87%老用户40/100 40%3/10 30%合计133/200 66.5%90/110 81.8%在每一个分组里A 都优于 B。但合并起来B 反而更好。原因在于两组样本量的分布不同。A 方案的样本中老用户占比 50%B 方案中老用户只占 9%。而老用户的整体转化率天然低得多。所以 B 的好成绩其实是靠把样本集中在高转化的新用户群体上堆出来的是一种混杂变量造成的假象。处理方式只有一条要么在分层内比较要么对权重做标准化。绝对不要把不同构成的群体直接合在一起比平均值。我在做渠道效果对比、版本效果评估时第一件事就是检查各组的用户构成是否一致。如果不一致先做分层或者用倾向得分加权、双重稳健估计这类方法做校正然后再比。6. 把公式落到代码里模拟验证与实验落地前面讲了不少公式和结论但坦白讲光看推导是记不住的而且很容易在某一步悄悄理解错。我自己的做法是每学一个反直觉的结论就写几行代码模拟一遍看数字能不能对上。对上了说明真懂了对不上说明某个前提理解有偏差。这个方法帮我纠过好几次概念错误。6.1 为什么要用模拟验证公式蒙特卡洛模拟有三个好处第一它不需要你会解析求解。有些问题解析解很难推但模拟起来只有几行。比如三个服务串联每个成功率 0.95整体成功率是多少——这个可以直接乘但如果每个服务有重试、有超时上限解析就很麻烦模拟反而简单。第二它能暴露你的前提假设错误。如果你模拟出来的数字和公式算出来的对不上百分之百是某个前提被你想当然了。这种反馈是最有效的学习信号。第三它能给出置信区间。模拟跑 N 次得到 N 个结果你不仅知道期望还知道波动范围。做容量规划时我关心的从来不是平均需要多少台机器而是95% 的情况下需要多少台这只有模拟才能直接给出来。6.2 四个可复现的验证脚本脚本一验证贝叶斯告警误报率。用 100 万次模拟看看告警中真故障的比例是不是接近 1.94%。import numpy as np rng np.random.default_rng(42) N 1_000_000 prevalence 0.001 # 真实故障基率 sensitivity 0.99 # 故障时告警的概率 false_alarm 0.05 # 正常时误报的概率 is_fault rng.random(N) prevalence alarm np.where( is_fault, rng.random(N) sensitivity, rng.random(N) false_alarm, ) print(告警总数:, alarm.sum()) print(告警中真故障比例:, is_fault[alarm].mean()) # 期望约 0.0194跑出来大约 0.0194和手算完全一致。这时候你对基率决定命中率这件事的理解就有实感了。脚本二验证蒙提霍尔。用最简洁的等价描述换门赢当且仅当第一次选错了。import numpy as np rng np.random.default_rng(0) N 200_000 car rng.integers(0, 3, N) # 车在哪扇门后 choice rng.integers(0, 3, N) # 初始选择 win_stay (choice car).mean() # 不换初始就选中 win_switch (choice ! car).mean() # 换初始没选中就一定赢 print(f不换胜率: {win_stay:.4f}) # ≈ 0.3333 print(f换门胜率: {win_switch:.4f}) # ≈ 0.6667这个等价关系是理解三门问题最省脑力的方式。你不需要模拟主持人的行为因为主持人永远打开羊门这个约束已经把换门和初始是否选错一一对应起来了。脚本三验证生日碰撞。向量化写法比逐个模拟快很多。import numpy as np rng np.random.default_rng(7) def collide_prob(n, days365, trials5000): # 生成 trials 组每组 n 个生日排序后看有没有相邻相等 b rng.integers(0, days, size(trials, n)) b.sort(axis1) return (np.diff(b, axis1) 0).any(axis1).mean() for n in (10, 23, 40, 70): print(n, round(collide_prob(n), 4))输出大约是 0.117、0.507、0.891、0.9992。23 对应 0.507这就是那个反直觉结论的来源。脚本四用 CLT 验证样本量的必要性。掷骰子是最经典的例子单次结果的分布是均匀的完全不正态。import numpy as np rng np.random.default_rng(1) n, trials 30, 20000 means rng.integers(1, 7, size(trials, n)).mean(axis1) print(均值:, means.mean()) # 理论 3.5 print(标准差:, means.std(ddof1)) # 理论 sqrt((35/12)/30) ≈ 0.3118跑出来的标准差约 0.31和理论值吻合。如果你把 n 改成 5会发现标准差的实测值偏离理论值更远因为 n 太小时 CLT 的近似还不够好。这个实验能让你直观感受到n ≥ 30这个经验值是怎么来的。6.3 A/B 实验里的概率思维落地最后说说这套知识在实验评估里的实际用法。我一般按这个顺序走第一步明确指标和它的方差。转化率类指标用 p(1−p) 估方差均值类指标用历史数据估样本标准差。这一步决定了后面所有计算。第二步算最小样本量。用 n ≈ 2(z_{α/2} z_β)² · σ² / Δ²其中 Δ 是你要检测的最小效应量。或者更简单直接用前面的比例公式表感受一下量级。第三步检查各组构成是否一致。这就是辛普森悖论的防线。用户类型、渠道、地域、新老客占比都得先对齐。不一致就分层或者加权。第四步看置信区间而不是只看点估计。提升了 1.2%这个说法信息量很低。真正该说出来的是提升 1.2%95% 置信区间 [−0.3%, 2.7%]这告诉你结论的方向都还没确定。第五步警惕多重比较。如果你同时看了 20 个指标那么至少有一个指标出现 p 0.05 的概率是 1 − 0.95²⁰ ≈ 64%。这不是发现这是概率的必然结果。要么事先指定主指标要么做 Bonferroni 之类的校正。我自己踩过的最大的一个坑是在早期做实验时只看点估计看到方向对了就上线。后来复盘发现那个提升完全在噪声范围内。从那时候起我给自己定了一条规矩任何结论必须带上区间没有区间的结论不出报告。这条规矩后来省下的返工时间远超当初多花的那几分钟计算。再补一个我个人很受用的小习惯遇到任何感觉不太对的数字先别急着解释先写十行代码模拟一下最朴素的模型看看模拟结果和实际观测的差距有多大。如果差距在正常波动范围内那就不需要解释也不值得追查。这个习惯能帮你从大量的假信号里脱身出来把精力留给真正的异常。
返回列表