把因果模型想象成一座工厂厂房的供水管线路:进水总闸、增压泵、反应釜、出水口,每个部件都由管道连接,有些管道还会互相扰动。你想弄清“把某个阀门 X 调到刻度 x 时,釜内温度会怎样变”,有两种做法。一种是蹲在控制室翻历史记录,只挑出 X 恰好等于 x 的时刻来看釜温,这叫观察;另一种是戴上手套走进车间,把 X 的阀杆拆下来,换上固定输出 x 的恒压器,让 X 被焊死在 x,再看下游如何反应,这叫干预。在结构因果模型(SCM)中,干预写作 do(X=x)。本文就用这座厂房的管道语言,把 do(X=x) 的每一步拆开。
掌握 do(X=x),你才能从历史数据中回答“如果我把策略改成 X 会怎样”,这是政策评估、医学研究和商业决策场景中因果推断的基石。
目录
可视化演示
展示K近邻算法的分类过程,移动查询点观察最近K个邻居的投票结果,调节K值影响分类决策。
本节配有交互动效(进入视口后自动加载,约 3KB)。
K近邻分类决策过程 青色为A类样本,橙色为B类,红色为C类 连线表示K个最近邻居,白色点为查询位置 y = mode(K nearest) 在画布上移动鼠标改变查询点位置
观察与干预:从被动看到主动改
厂房里的总闸时常忽大忽小,反应釜的温度跟着乱跳。如果你想弄清“阀门 X 调到刻度 x 时,釜温会怎样”,每天蹲在控制室翻历史记录,只挑出 X 恰好等于 x 的时刻来看釜温,这是观察;另一种做法是走进车间,把 X 的阀杆拆下来,换上一个恒定输出 x 的稳压器,让 X 固定下来,再看釜温,这是干预。
SCM 用两套记号区分它们。观察是条件概率 \(P(Y=y|X=x)\),意思是“在所有已经发生的数据里,X 恰好是 x 的那些日子,Y 平均是什么”。干预写成 \(P(Y=y|do(X=x))\),意思是“我强行把 X 设成 x 之后,Y 会变成什么”。多了一个 do 记号,表达的内容就从“看”变成了“做”。
一般说来,这两个概率并不相等。管道图中存在同时影响 X 和 Y 的上游因素 Z,比如原材料水分。观察时,X=x 的样本已经背着 Z 的信息;干预时,Z 到 X 的支路被焊死,Z 的波动不再能通过 X 乔装改扮成 X 的效应。
所以,因果问题的起点不是问“数据说了什么”,而是问“我改了哪个阀门”。
- 左侧观察\(\Pr(Y=y\mid X=x)\) 表示从历史数据中筛选 X=x 的样本,再统计 Y=y 的比例。
- 右侧干预\(\Pr(Y=y\mid \mathrm{do}(X=x))\) 表示在修改后的模型中强制 X=x 后,Y=y 的概率。
- do 记号\(\mathrm{do}(X=x)\) 表示一个物理动作,不是普通条件;它是“拧阀门”这件事的数学签名。
- 不等号\(\neq\) 强调一般情况下两者不相等,因为共同上游因素会让观察到的关联混入虚假成分。
结构方程替换:把机制改成常数
在 SCM 中,每个变量都有一根“出生管道”——结构方程。变量 X 不是凭空产生的,而是由它的父节点 Pa_X 和外生误差 U_X 通过函数 f_X 算出来的。这就像阀门的开度本来由上游水压和阀门口径共同决定,公式写出来就是 \(X = f_X(Pa_X, U_X)\)。
do(X=x) 的核心动作,是把 X 的结构方程整个替换成常数 \(X=x\)。注意,不是让 f_X 刚好输出 x,也不是在方程右边加一个常数,而是把“X 如何产生”的机制直接换成“X 就是 x”。因此,X 原来的误差项 U_X 也不再参与:阀门被用手卡死了,它自身的抖动自然消失。
把方程换成常数,就像拆掉阀门的手动连杆,把阀位直接锁死。上游再怎么波动,也传不进 X 的内部了。
- do 前缀\(\mathrm{do}(X=x)\) 标记一次干预,而不是一次筛选。
- 原始机制\(X = f_X(Pa_X, U_X)\) 是干预前 X 的生成方式,依赖父节点和误差。
- 替换箭头\(\longrightarrow\) 表示修改后的 SCM 中,旧方程被新方程取代。
- 常数赋值\(X = x\) 是新的结构方程,X 不再由任何变量决定。
切断父节点链路:拆掉上游管道
SCM 不只包含方程,还伴随一张有向无环图。图中每个箭头表示“父节点直接影响子节点”。do(X=x) 在图上还有一个非常直观的体现:删除所有指向 X 的边,也就是把父节点到 X 的管道全部拆除。
删除之后,X 从原来的“中间环节”变成一个根节点。它不再接收来自祖先的任何信息,但 X 指向下游的边仍然保留。用图论的记号,干预后的图记为 \(G_{\overline{X}}\),上划线表示“把进入 X 的箭头都切除”。
图上的删除,对应管道工程里的“断管”。断管之后,上游总闸再怎么波动,X 的读数都纹丝不动;真正影响 X 的,只剩下我们人为施加的常数 x。
- 原图边集\(\mathrm{Edges}(G)\) 表示原始因果图中的所有有向边。
- 干预后的图\(G_{\overline{X}}\) 是执行 do(X=x) 之后得到的修改图。
- 集合差\(\setminus\) 表示从原边集中移除某些边。
- 被删的边\(\{Pa_X \to X\}\) 是所有父节点指向 X 的边,代表上游到 X 的管道。
下游变量更新:让结果重新传播
切断管道后,故事并没有结束。X 固定为 x,它的下游变量 Y 该怎么办?回到 SCEM 的设定:除 X 之外,其他变量的结构方程一律保留。Y 仍然由自己的方程 \(Y = f_Y(Pa_Y, U_Y)\) 生成,只不过在计算 Pa_Y 时,如果里面包含 X,就把 X 的值换成 x。
这就像反应釜下游的水泵本身没有更换,它的电机功率、管道口径都还是老样子;但进入水泵的水量变成了恒定值 x,所以水泵出口的压力会随之重新稳定在新的水平。下游的随机误差 U_Y 仍然存在,因此 Y 不会变成一个死数,它还是会有波动,只是波动的来源不再包含 X 的原有父节点。
下游的传播像涟漪:X 的固定值顺着箭头一层层向前传,每个后代都按自己的“脾气”重新计算。这就是干预后分布产生的过程。
- 后代机制\(Y = f_Y(Pa_Y, U_Y)\) 表示 Y 的生成方程没有被 do 操作改变。
- 父集\(Pa_Y\) 是 Y 的直接原因集合,可能包括干预变量 X。
- 替换规则\(X \;\leftarrow\; x\) 表示在计算后代时,X 的取值固定为 x。
- 误差保留\(U_Y\) 仍然保留,说明 Y 依然有自己的随机性。
上游不受影响:干预是局部手术
既然 do(X=x) 拆掉了 Pa_X 到 X 的管道,那么上游父节点本身的分布会不会也跟着变?答案是:不会。在有向无环图中,没有从 X 指回祖先的箭头,所以干预 X 不会反向改变祖先的生成机制。
这一点很像外科手术:医生给心脏安一个起搏器,把心跳固定成某个节律,但并不会因此改变病人过去的病历和家族基因。上游变量 Pa_X 仍然按照原来的结构方程生成,它们的分布保持不变。真正改变的是“Pa_X 与 X 之间的关联”——干预后 X 不再受 Pa_X 驱动,两者在修改图中变成独立关系。
干预不是给机器装一个“后悔药”,它只能在箭头方向上影响未来。正因如此,我们才能把 X 的效应从上游混杂中干净地切出来。
- 左侧分布\(\Pr(Pa_X = p \mid \mathrm{do}(X=x))\) 是干预环境下父节点取值的概率。
- 右侧原始分布\(\Pr(Pa_X = p)\) 是没有做任何干预时父节点的概率。
- 等号\(=\) 表示两者相等,说明干预不会反向改变祖先。
- 条件中的 do\(\mid \mathrm{do}(X=x)\) 强调这是“动手之后”的环境,而不是普通筛选。
因果效应分离:量化回答“如果”
最后的问题是:do(X=x) 之后,我们到底怎么算 Y 的变化?答案藏在“截断乘积公式”里。原始模型中,所有变量的联合分布可以写成每个变量在给定父节点下的条件概率之积。do(X=x) 会把这个乘积中 X 自己的因子删掉,并把所有出现 X 的地方替换成 x,其余变量仍然保留自己的条件概率。
这个公式看起来复杂,但思想很简单:把 X 的出生管道剪断,不让它的旧机制参与计算;下游各变量按自己的机制继续算,最后把除了 Y 以外的变量都加总或积分掉,就得到干预后的 Y 分布。
有了这套公式,我们就能回答“如果我把 X 设定为 x,Y 会怎样”。这正是政策评估和商业决策最需要的答案:不是描述已经发生的事,而是预测一个尚未发生的动作。
- 左侧目标\(\Pr(Y=y\mid \mathrm{do}(X=x))\) 是干预后 Y 取值为 y 的概率。
- 边缘化\(\sum_{\mathbf{v}\setminus\{Y\}}\) 表示把 Y 之外的所有变量取值求和,得到 Y 自己的边际分布。
- 连乘\(\prod_{V \neq X}\) 表示遍历除 X 以外的每个变量,保留它们的生成机制。
- 代入限制\(\Pr(v \mid pa_V)\big|_{X=x}\) 表示在每个条件概率中,如果父集含 X,就用 x 替换它。
AI 应用场景
城市交通限行政策评估
阶段:政策因果效应估计
解决的问题:传统方式直接比较限行日与不限行日的空气质量,会被天气、经济活动和节假日等混杂因素干扰。使用 SCM 后,把“限行强度”作为干预变量 X,用 do(X=x) 模拟强制实行不同限行等级,就能分离出限行对空气质量的真实影响。
具体案例:某城市建立交通因果图,节点包括气温、车辆出行量、限行等级、污染物浓度。需要回答“如果全市限行等级提高到 2 级,PM2.5 会下降多少”时,数据科学家用 do(限行等级=2) 替换其结构方程,切断“出行需求→限行等级”的反向驱动,得到政策效果。
临床药物剂量优化
阶段:临床干预效果评估
解决的问题:电子病历中记录到的用药剂量往往由患者病情决定,病情越重剂量越高,直接比较不同剂量的疗效会混淆。do(剂量=d) 相当于在虚拟世界中强制所有患者使用剂量 d,从而得到“如果把剂量统一设为 d”的疗效。
具体案例:医疗 AI 团队用 SCM 建模血压、心功能、药物剂量和心血管事件。估计 do(剂量=标准量) 与 do(剂量=加量) 的事件率之差,辅助临床决策。
电商促销策略仿真
阶段:营销策略仿真与决策
解决的问题:观察数据里,促销折扣往往受库存、竞争和季节性影响;直接看“打了 8 折时的销量”会把促销策略和其他因素混在一起。do(折扣=0.8) 把折扣从“被业务自然决定”变成“人为固定”,再观察销量反应。
具体案例:电商平台构建因果图,包含季节、竞对价格、折扣、流量和销售额。决策团队用 do(折扣=0.8) 和 do(折扣=0.6) 对比,决定大促最优折扣。
Python 演示
import random class LinearSCM: def __init__(self, seed=2024): self.rng = random.Random(seed) def structural_u(self): return self.rng.gauss(0, 1) def structural_x(self, u): # 观察模式下 X 由父节点 U 和噪声决定 return 1.5 * u + self.rng.gauss(0, 0.2) def structural_y(self, x, u): # Y 同时受 X 和混杂节点 U 影响 return 2.0 * x + 3.0 * u + self.rng.gauss(0, 0.2) def sample_observation(self): u = self.structural_u() x = self.structural_x(u) y = self.structural_y(x, u) return u, x, y def sample_intervention(self, x_fixed): # do(X=x): 不调用 structural_x,切断 U -> X u = self.structural_u() y = self.structural_y(x_fixed, u) return u, y def average(values): return sum(values) / len(values) def main(): model = LinearSCM(seed=7) n = 5000 obs_x = [] obs_y = [] for _ in range(n): u, x, y = model.sample_observation() if abs(x - 2) < 0.15: obs_x.append(x) obs_y.append(y) do_y = [model.sample_intervention(2)[1] for _ in range(n)] print("观察近似 E[Y|X=2]: %.2f" % average(obs_y)) print("干预 E[Y|do(X=2)]: %.2f" % average(do_y)) if __name__ == "__main__": main()
def structural_x(self, u):这是 X 的原始结构方程,观察模式下由父节点 U 驱动。def structural_y(self, x, u):Y 的机制同时接收 X 和混杂 U,体现“下游照常传播”。def sample_intervention(self, x_fixed):do 操作:不再调用 structural_x,相当于把 U→X 的管道切断。if abs(x - 2) < 0.15:观察式条件:只挑出 X 恰好接近 2 的历史样本。do_y = [model.sample_intervention(2)[1] for _ in range(n)]反复执行 do(X=2),得到干预后的 Y 分布。
常见误区
误区看着公式 \(P(Y|X=x)\),以为只要按 X 筛数据就是在做干预;甚至把回归中“控制 X”当成 do(X=x)。
正解do 是动手术,不是筛数据。给定 X=x 只是观察,管道图里 \(Z \to X\) 和 \(X \to Y\) 都原封不动;do(X=x) 会拆掉 \(Z \to X\) 的管道并给 X 接上恒压源。好比“看到某位司机刚好超速”和“强制所有司机超速”是两回事。
为何常见:两者都写成“X=x”,初学者很容易被相同符号迷惑,忽略背后是否改变了生成机制。
误区觉得既然改变了 X,那么 X 的所有父节点和祖先也应该被“带动”,于是认为 do(X=x) 会影响整个因果图。
正解在有向无环图中,箭头只从祖先指向后代,X 不会反向影响祖先。do(X=x) 是局部手术,只切掉进入 X 的管道,祖先的分布保持不变。上游水压不会因为你拧紧了下游的一个阀门而回到过去重新变化。
为何常见:日常语言里常说“牵一发动全身”,让人误以为干预一定会向上游扩散;但因果图的方向性规定了这种扩散只发生在下游。
误区看到结构方程 \(X = f_X(Pa_X, U_X)\),以为 do(X=x) 只是把 \(f_X\) 输出改成 x,但 U_X 仍然会让 X 抖动。
正解do(X=x) 是把整个结构方程替换成常数 \(X=x\),U_X 不再进入 X。X 的取值为确定性的 x。下游变量 Y 的方程里自己的误差 U_Y 仍然保留,所以 Y 依然随机,但 X 本身不再抖动。
为何常见:人们容易把“修改输入”和“替换机制”混淆;do 操作恰恰是替换机制,而不是调整输入。
误区有人认为只要做随机分组,就等于实现了 do(X=x),SCM 和 do 记号只是多余的数学包装。
正解随机对照实验确实能物理实现“固定 X”的干预,但现实中有大量场景无法随机分组,比如强制所有人吸烟或提高全城限行等级。SCM 的价值在于:即使只有观察数据,只要因果图假设成立,就能用 do 运算推出干预效果,相当于把随机实验在虚拟世界中重演一遍。
为何常见:初学因果推断时容易把实验方法和数学框架对立起来,忽略 SCM 在无法做实验时推导因果效应的能力。
面试题
Q1. 在结构因果模型中,do(X=x) 对 X 的处理方式是?
- A.把 X 的取值筛选出来
- B.把 X 的结构方程替换成常数← 正确答案
- C.删除所有从 X 出发的边
- D.给 X 增加一个随机噪声
答案:B。do(X=x) 的核心是把 X 的结构方程整条替换成常数 x,因此 X 不再受父节点和自身误差影响。删除从 X 出发的边是错的,因为 X 到下游的因果路径必须保留。
Q2. do(X=x) 与 X=x 的最本质区别是?
- A.前者主动固定 X,后者只是筛选← 正确答案
- B.前者改变生成机制,后者只是筛选
- C.前者影响所有变量,后者只影响下游
- D.后者能完全消除混杂因素的影响
答案:A。选项 A 和 B 都接近,但 B 更准确:do(X=x) 修改的是 X 的生成机制,而 X=x 只是从数据中筛选。选项 C 说“影响所有变量”不对,干预不影响上游;D 也不对,观察本身不消除混杂。
Q3. 干预 do(X=x) 后,X 与其父节点 Pa_X 之间的边会?
- A.保留原有边与方向
- B.方向反转强度保留
- C.上游指向 X 的边删除← 正确答案
- D.X 指向父节点形成环
答案:C。do 操作会在图上删除所有进入 X 的边,也就是 Pa_X → X 被移除。方向反转或保留都是错误说法;有向无环图中也不允许 X 指向父节点。
Q4. do(X=x) 后,通常分布保持不变的是哪一个变量?
- A.下游结果变量 Y
- B.上游父节点 Pa_X← 正确答案
- C.干预目标变量 X
- D.后代变量 Z
答案:B。干预只沿着有向图向下游传播,不改变祖先的分布。X 的分布变成退化分布,Y 和后代变量会因为 X 的新值而变化,所以只有 Pa_X 保持不变。
Q5. 用观察数据估计 E[Y|do(X=x)] 时,为切断混杂路径,通常需要?
- A.直接平均 X=x 的 Y 值
- B.控制后门准则变量集← 正确答案
- C.删除所有下游子节点
- D.交换 X 与 Y 的条件
答案:B。后门准则是从观察数据估计因果效应的重要工具:控制能阻断 X 与 Y 之间非因果路径的变量集,使 \(P(Y|do(X=x))\) 能从条件分布中恢复。直接平均会混入混杂,删除下游节点会破坏因果路径,交换条件则改变问题。
Q6. do(X=x) 之后,X 的取值来自?
- A.由父节点和噪声共同决定
- B.按历史分布随机抽样
- C.固定为干预常数 x← 正确答案
- D.由下游节点反馈决定
答案:C。do(X=x) 把 X 的原始结构方程替换成常数 x,因此 X 的取值就是固定的 x,不再由父节点、噪声或任何历史分布决定,更没有下游反馈。
Q7. 在 do(X=x) 的图手术中,会做哪些操作?
- A.将 X 的结构方程替换成常数← 正确答案
- B.删除 Pa_X 指向 X 的边← 正确答案
- C.修改 X 祖先的参数
- D.让下游按新的 X 值传播← 正确答案
答案:ABD。do(X=x) 一方面修改 X 的方程,另一方面在图上删除进入 X 的边,然后让 X 的新值沿保留的 X→后代路径继续传播。祖先的参数不会被修改,所以 C 错误。
Q8. 关于干预分布与观察分布,下列说法正确的是?
- A.有混杂时两者通常不同← 正确答案
- B.do 分布是修改模型后的分布← 正确答案
- C.观察分布永远等于干预分布
- D.do 操作能回答“如果把 X 设为 x”的问题← 正确答案
答案:ABD。有共同上游混杂时,\(P(Y|X=x)\) 和 \(P(Y|do(X=x))\) 通常不同;do 分布建立在删边和替换方程后的修改模型上,因此能回答反事实式的“如果”。C 显然错误。
Q9. 截断乘积公式中,对每个非 X 变量 V 的因子是?
- A.P(v | pa_V) 并在 X=x 下计算← 正确答案
- B.P(pa_V | v) 并在所有变量上求和
- C.P(v) 与父节点无关
- D.P(x | pa_X) 并令 x 固定
答案:A。截断乘积公式保留每个非 X 变量的条件概率 \(P(v|pa_V)\),并把其中出现的 X 替换为 x;X 自己的因子被删除。选项 B 方向反了,C 忽略父节点,D 描述的是 X 的旧因子。
Q10. 在药物剂量场景中,do(D=d) 相比条件 D=d 的主要优势是?
- A.切断健康状况对剂量的驱动← 正确答案
- B.保留药物对疗效的传导← 正确答案
- C.消除所有测量误差
- D.回答将剂量设为 d 的干预效果← 正确答案
答案:ABD。do(D=d) 消除了“健康状况→剂量”的反向驱动,同时保留“剂量→疗效”的因果路径,因此能回答“如果统一把剂量设为 d 会怎样”。C 不现实,测量误差仍然存在于其他变量中。