AI/ML 详解:因果推断

Day 36 · 2026-06-22
面向:有编程经验的非 AI 方向工程师

因果 vs 相关Causation vs Correlation

核心区分混杂
一句话类比

监控面板上 CPU 和延迟两条曲线总是一起涨——但你无法从曲线本身判断是 CPU 导致延迟、延迟导致 CPU,还是某个隐藏的共同上游(一波流量激增)同时推高了两者。这个隐藏上游就是混杂变量(confounder),相当于系统里一个没被画进依赖图的共享依赖。看到相关就下因果结论,等于看到两个服务一起抖动就断定 A 调用了 B。

它解决什么问题 + 工作机制

统计相关回答的是观察问题:"已经看到 X 高,Y 大概率也高"——数学上是条件概率 P(Y | X)。因果回答的是干预问题:"我主动把 X 调高,Y 会变吗"——Judea Pearl 用 do 算子把它写成 P(Y | do(X))。两者的差距全在混杂:当 Z 同时影响 X 和 Y,X 与 Y 即使毫无因果关系也会高度相关。

机制上,混杂打开了一条后门路径(backdoor path):X ← Z → Y。相关性把"X→Y 的真实效应"和"经由 Z 的伪关联"混在一起。识别因果的核心动作,就是堵住后门——通过分层、回归、匹配等手段"控制住 Z",只留下 X→Y 这条直接通路。这也是著名的 Simpson 悖论的根源:不控制 Z 时整体趋势,可能和每个分组内的趋势完全相反。

混杂的因果图(DAG):Z 是后门

      Z 混杂变量
        ↙     ↘
X 处理 — ? → Y 结果

X 与 Y 的相关 = 真实效应(X→Y) + 后门伪关联(X←Z→Y)
控制住 Z = 堵住后门,剩下的才是因果
代码示例
import pandas as pd, numpy as np
import statsmodels.formula.api as smf

np.random.seed(0)
n = 2000
Z = np.random.normal(size=n)          # 混杂:比如"用户本身的活跃度"
X = Z + np.random.normal(size=n)      # Z 推高 X(活跃用户更常用新功能)
Y = Z + np.random.normal(size=n)      # Z 也推高 Y,X 对 Y 真实效应=0
df = pd.DataFrame({"X": X, "Y": Y, "Z": Z})

# 不控制 Z:看似 X 强烈影响 Y(伪关联)
print(smf.ols("Y ~ X", df).fit().params["X"])      # ≈ 0.5
# 控制 Z(堵后门):X 的系数塌回真实值 0
print(smf.ols("Y ~ X + Z", df).fit().params["X"])  # ≈ 0
常见误区 + 实践场景
"数据量够大,相关就能当因果用"——错得越大越离谱。样本越大,伪关联的置信区间越窄,你会更"确信"一个虚假结论。大数据修不了混杂偏差,它只是把偏差估计得更精确。能修的只有设计:随机化、或显式建模混杂。
📌 超级个体场景:你发现"用了 AI 助手的那几周产出更高",先别归因于工具。问一句——是不是那几周本来就状态好(混杂 Z = 精力/项目难度),导致你既愿意用 AI、又产出高?想验证因果,得让"用不用 AI"独立于你的状态,比如随机指定某些任务用、某些不用。
Takeaway + 思考题
💡 相关是 P(Y|X),因果是 P(Y|do(X))——两者的全部差距,藏在你没画进依赖图的那些共同上游里。
🤔 回想一个你最近基于"数据相关性"做出的判断:有没有一个隐藏的共同原因,能同时解释你看到的两端?

潜在结果框架Potential Outcomes / Rubin Model

理论框架反事实
一句话类比

反事实(counterfactual)就是"如果我当时没上线这个 deploy,会怎样"。问题在于:你无法对同一台服务器、在同一时刻,既部署又不部署。这正是 A/B test 的心智模型,但它有个无法回避的硬伤——每个个体你只能看到一条平行宇宙,另一条永远是缺失数据

它解决什么问题 + 工作机制

Rubin 的潜在结果框架给每个个体 i 定义两个值:Y_i(1)(接受处理时的结果)和 Y_i(0)(不接受时的结果)。个体因果效应 = Y_i(1) − Y_i(0)。麻烦在于:i 要么被处理、要么没被处理,你永远只能观测到其中一个,另一个是反事实。这就是 因果推断的根本难题(Fundamental Problem of Causal Inference)——它本质是个缺失数据问题

既然个体效应不可观测,我们退而求其次估平均ATE = E[Y(1) − Y(0)]。但天真地拿"被处理组均值 − 未处理组均值"会有选择偏差:选择接受处理的人本就和别人不同。随机化是破局钥匙——随机分配让"是否处理"独立于潜在结果,两组在处理前统计上无差异,于是组间差就是无偏的 ATE。这也是为什么 RCT(随机对照试验)是因果推断的黄金标准。

个体Y(0) 不处理Y(1) 处理个体效应
A(被处理)? 反事实8不可知
B(未处理)5? 反事实不可知
每行总有一半是缺失的红格 → 个体效应永远看不全,只能靠随机化估群体平均
代码示例
import numpy as np
np.random.seed(1)
n = 5000
T = np.random.binomial(1, 0.5, n)   # 随机分配处理:关键!独立于个体
Y0 = np.random.normal(50, 10, n)        # 不处理时的潜在结果
tau = 4.0                               # 真实因果效应 +4
Y1 = Y0 + tau
Y = np.where(T == 1, Y1, Y0)        # 只能观测到被分配的那一支

# 因为随机化,组间差就是无偏 ATE 估计
ate = Y[T == 1].mean() - Y[T == 0].mean()
print(round(ate, 2))   # ≈ 4.0,逼近真实 tau
常见误区 + 实践场景
"我有处理组和对照组,直接比均值就是效应"——只有随机分配时才成立。观察数据里两组天然不可比(selection bias),直接比均值得到的是"相关",不是 ATE。框架的价值不在公式,而在逼你说清反事实是什么、它为什么缺失。
📌 决策辅助场景:给自己的个人项目设计"微型 RCT"。想知道"番茄钟到底有没有用",别只在状态差的日子用它——用抛硬币随机决定每天用不用,两周后比两组的产出。随机化把混杂(当天状态)自动均摊到两组,省去你建一堆控制变量。
Takeaway + 思考题
💡 因果推断的根本难题是缺失数据:你永远只能看到每个个体的一条平行宇宙,随机化是把另一条"借"回来的唯一干净办法。
🤔 你工作中哪个"显然有效"的习惯,其实从没在"不做它"的反事实下检验过?怎么给它设计一个最小随机实验?

工具变量Instrumental Variables (IV)

准实验识别策略
一句话类比

当你无法做随机实验、又有甩不掉的混杂时,去找一个天然的随机器——一个外部推力,它改变"是否处理",本身和结果、和混杂都无关。类比:你的灰度系统用随机 seed 分配 feature flag,谁拿到新功能是随机的、与用户画像无关。于是你可以把"被随机分到 flag"当成杠杆,撬出功能本身的因果效应——即使用户用不用功能是自选的。

它解决什么问题 + 工作机制

未观测混杂时,"控制 Z"这招失效——你根本测不到 Z。工具变量(instrument)Z 绕开它,靠三个条件:(1) 相关性——Z 确实影响处理 X(Z→X 够强);(2) 排他性(exclusion)——Z 只能经由 X 影响 Y,没有别的通路;(3) 独立性——Z 与未观测混杂无关,相当于"随机分配"。

机制是两阶段最小二乘(2SLS):第一阶段用 Z 预测 X,得到 X 中仅由 Z 驱动的那部分变动(这部分"干净"、不含混杂);第二阶段用这个干净的预测值去解释 Y。直觉上,你只用工具带来的那一点外生扰动来估效应,把被混杂污染的其余变动统统丢掉。代价:估计量方差更大,且条件 (2)(3) 无法从数据检验,只能靠领域论证——这是 IV 最脆弱处。

代码示例
import numpy as np, pandas as pd
from linearmodels.iv import IV2SLS  # pip install linearmodels

np.random.seed(2)
n = 4000
Z = np.random.normal(size=n)              # 工具:外生随机推力
U = np.random.normal(size=n)              # 未观测混杂,测不到
X = 0.8*Z + U + np.random.normal(size=n)  # X 受 Z 和混杂 U 共同影响
Y = 2.0*X + 3*U + np.random.normal(size=n)# 真实效应=2,但 U 污染了 OLS
df = pd.DataFrame({"Y": Y, "X": X, "Z": Z})

# OLS 被混杂带偏(远离 2);IV 用 Z 撬出干净效应
iv = IV2SLS.from_formula("Y ~ 1 + [X ~ Z]", df).fit()
print(round(iv.params["X"], 2))   # ≈ 2.0,逼近真实因果效应
常见误区 + 实践场景
"弱工具也能凑合"——非常危险。当 Z→X 很弱(弱工具,weak instrument),第一阶段几乎没信息,2SLS 估计会剧烈放大偏差和方差,结果可能比直接 OLS 还离谱。用前务必看第一阶段的 F 统计量(经验阈值常取 > 10)。
📌 跨学科思考场景:经济学里"上学年限对收入的影响"用出生季度当工具(Angrist & Krueger)——入学年龄政策让不同季度出生的孩子被动多读/少读一点书,这个"季度"与个人能力无关,是天然随机器。学会识别生活中的"准随机事件"(政策切换、地理边界、抽签),是把观察数据当实验用的核心功夫。
Takeaway + 思考题
💡 工具变量是"借来的随机性"——找一个只撬动处理、不碰结果的外生杠杆,用它那一点干净扰动估因果。
🤔 你关心的某个"X 是否导致 Y"问题里,世界上有没有一个近似随机地改变了 X、却与 Y 无直接关系的事件?

双重差分Difference-in-Differences (DiD)

准实验面板数据
一句话类比

你给某个 shard 改了配置(处理组),另一个 shard 没动(对照组)。直接看处理组改前/改后的差不行——因为整个集群这期间可能因为流量季节性一起在涨。DiD 的做法:(处理组前后差) 减去 (对照组前后差),把"两组共同经历的时间趋势"减掉,剩下的才是配置改动的净效应。

它解决什么问题 + 工作机制

当处理不是随机分配、但你有处理前后两个时间点和一个没被处理的对照组时,DiD 能识别效应。它同时消掉两类偏差:第一次差分(前后)消掉了每个组不随时间变的固定差异(比如处理组本来基线就高);第二次差分(处理组 vs 对照组)消掉了两组共享的时间趋势(大盘的季节性波动)。两次相减后,留下的就是处理的因果效应。

核心假设是平行趋势(parallel trends)若没有处理,处理组和对照组本会沿着平行的轨迹变化。这是 DiD 全部可信度的来源,也无法直接证明——只能用处理前多期数据看两条线是否一直平行来旁证。经典案例是 Card & Krueger (1994) 最低工资研究:新泽西涨了最低工资、隔壁宾州没涨,用快餐店就业的双重差分,发现就业并未如传统理论预测的下降。

平行趋势:反事实 = 对照组趋势平移到处理组

就业 │               处理组(实际)
     │    - - - - 处理组(反事实)
     │              对照组
     │    
     └──────────────→ 时间
        处理前  | 处理后
DiD 效应 = 实际● 与 反事实○ 的垂直差
反事实 = 对照组的变化量,平移到处理组起点
代码示例
import pandas as pd
import statsmodels.formula.api as smf

# treat: 是否处理组  post: 是否处理后时期
df = pd.DataFrame({
    "y":     [20, 22, 18, 25],   # 对照前后 + 处理前后
    "treat": [0,  0,  1,  1],
    "post":  [0,  1,  0,  1],
})
# 交互项 treat:post 的系数 = DiD 因果效应
m = smf.ols("y ~ treat + post + treat:post", df).fit()
print(m.params["treat:post"])
# = (25-18) - (22-20) = 7 - 2 = 5  ← 减掉了大盘+2 的趋势
常见误区 + 实践场景
"找个对照组套上 DiD 就行"——对照组选错,平行趋势不成立,结论全废。若处理组在处理前就已经在加速上升(趋势本就不平行),DiD 会把这部分自然差异误算成因果效应。务必用处理前的多期数据画图,确认两条线一直平行("事前趋势检验")。
📌 个人项目场景:想评估"换了新笔记系统后,我的周复盘质量是否提升"。别只看自己换前换后——找一个没换系统但情况相似的维度当对照(比如另一类一直没动的笔记),用 DiD 减掉"这段时间你整体更投入"的大盘趋势,才不会把进步全归功于工具。
Takeaway + 思考题
💡 DiD 用"减两次"剥掉固定组间差和共同时间趋势,但它的全部可信度押在"平行趋势"这条无法证明、只能旁证的假设上。
🤔 你想归因的某个改变里,有没有一个"没经历这个改变、但本会同步波动"的对照?没有它,你怎么知道改变后的变化不只是大盘趋势?

深入资源Further Reading

深入思考Deep Questions

1. 既然随机对照试验(RCT)是因果黄金标准,为什么还要发明 IV、DiD 这些"准实验"方法?它们在因果可信度的阶梯上处于什么位置?
因为 RCT 常常做不了:不道德(不能随机让人吸烟)、不可行(不能随机给某些州涨最低工资)、太贵或太慢。准实验是"退而求其次找老天爷帮你随机"——IV 借一个外生事件当随机器,DiD 借一个对照组减掉趋势。可信度阶梯大致:RCT > 自然实验/IV/DiD/断点回归 > 控制混杂的回归/匹配 > 纯相关。越往下,越依赖无法用数据检验的假设(排他性、平行趋势),可信度越脆。关键认知:因果强度不取决于模型多复杂,而取决于识别假设有多可信。一个简单 DiD 配一个无懈可击的对照组,胜过一个塞满控制变量、却堵不住未观测混杂的花哨回归。这与分布式系统里"正确性来自不变量,而非代码量"同构。
2. 机器学习模型预测准得不得了,为什么 ML 工程师仍然容易在因果问题上栽跟头?预测和因果到底是不是一回事?
完全不是一回事,这是 ML 实践最深的陷阱之一。预测优化的是"给定我观测到的 X,Y 是多少"——它乐于利用一切相关性,包括混杂带来的伪关联,因为那也能降 loss。因果问的是"我去改 X,Y 会怎样"——此时伪关联全是毒药。经典翻车:模型发现"住院病人里哮喘患者死亡率更低",预测层面没错(哮喘病人被更密集监护了),但若据此得出"哮喘保护肺炎病人"的因果结论并据以分流,会害死人——这里"监护强度"是混杂。一个 AUC 0.99 的模型,照样可能在"干预后会发生什么"上给出灾难性建议。所以可解释性、特征重要性都不等于因果归因:SHAP 值高只说明这个特征对预测有用,不说明改它能改结果。要做决策(而非预测),必须切换到因果框架。
3. 「平行趋势」和「排他性约束」都是无法被数据证明、只能靠论证支撑的假设。这种"依赖不可检验假设"的状况,和你熟悉的工程领域有什么可类比之处?怎么对待它?
这和分布式系统里依赖无法穷尽测试的不变量高度同构——你永远没法测出"在所有网络分区下都线性一致",只能靠协议设计论证 + 大量旁证(Jepsen 测试、形式化验证)。对待方式也可迁移:(1) 把假设显式写出来,别埋在代码/模型里——因果分析里就是明确声明"我假设平行趋势/排他性";(2) 找间接证据加固——DiD 用事前多期趋势图,IV 用相关的安慰剂检验(placebo test:对一个理应无效应的伪结果跑同样分析,若也出"效应"说明假设有问题);(3) 敏感性分析——问"假设违背到什么程度,结论才翻转",DoWhy 的"反驳"步骤就是干这个;(4) 诚实标注不确定性。核心心态:因果结论永远是"在某假设下成立"的条件命题,工程师的成熟体现在清楚自己押了哪些注、押得多重,而非假装没押注。
4. 把因果阶梯(关联→干预→反事实)和当下的大语言模型对照,LLM 处在哪一层?这对"AI 能不能真正理解世界"意味着什么?
Pearl 的因果阶梯三层:关联(看到 X 会怎样)、干预(做 X 会怎样)、反事实(如果当初不做 X 会怎样)。LLM 在海量文本上学的是关联层——它建模 token 的联合分布,本质是极强的相关性引擎。它能复述人类写下的因果知识(因为训练语料里有因果陈述),但这不等于它自己在做干预/反事实推理;面对训练分布外的新干预,它常给出听起来合理却因果错误的答案。这正是当前的活跃争论:有人认为纯关联学习触不到上面两层,必须引入显式因果结构;也有人认为足够规模 + 工具(能真正做实验、调 API 改变世界状态的 agent)能逼近干预层。对"AI 超级个体"的启示:用 LLM 做预测/生成/检索是它的主场,但做因果决策(这个改动会带来什么后果)时,要把它当成提假设的助手,真正的因果判断仍需你用本文的框架去验证——人机协同的分工线,恰好划在因果阶梯上。
5. 本文四个工具(控制混杂的回归、潜在结果、IV、DiD)该怎么选?给你一个真实问题,决策路径是什么?
决策树大致:能随机化吗?→ 能就做 RCT(用潜在结果框架算 ATE),别想别的。不能随机,但混杂都能观测到吗?→ 能就用回归/匹配控制它们(堵后门),但要诚实问"真的全观测到了?"——通常没有。有未观测混杂,但能找到一个外生工具吗?→ 能且工具够强(第一阶段 F>10)、排他性可论证,就用 IV。有处理前后的面板数据和一个可信对照组吗?→ 有且事前趋势平行,就用 DiD。三者常组合:先 DiD 拿主估计,再用 IV 或敏感性分析做稳健性检查。落到 BigCat 的真实场景——"新工作流是否提升了我的产出":首选给任务随机指派用不用(微型 RCT);做不到就找一个一直没变的对照维度跑 DiD。永远先问"我的反事实是什么、它为什么可信",工具只是回答这个问题的手段。