在一场热闹的聚会上,小A和小B经常同时出现,朋友圈照片里也总有他俩。你能因此断定“小A喜欢小B”吗?也许他们只是共同朋友多,也许两人都在追求同一位女神——小A在女神面前献殷勤,小B也在女神面前刷存在感,而小A和小B彼此之间却毫无交集。真正的情感侦探都知道:相关不等于动心,方向才是爱情的真相。因果发现(causal discovery)做的正是这件事:从一堆观测数据里,还原“谁在影响谁”的因果网络。其中,基于约束的方法是一套严谨的“侦探工作流”:先用条件独立性检验筛掉虚假关系、画出可疑的关系网;再通过“情敌现形”式的碰撞器结构锁定第一批箭头方向;最后靠“八卦传播”式的推理规则,把剩余方向一个个推出来。这篇文章,我们就跟着这位情感侦探,学会如何从同框记录里严谨地确定因果方向。

岗位关联

方向确定是因果发现从“相关”走向“因果”的关键一跃。掌握它,你才能从数据中得到可解释的因果结论,而不是停留在相关性报表上。

目录
  1. 可视化演示
  2. 方向确定概论
  3. 骨架构建基础
  4. 碰撞器定向
  5. 传播定向规则
  6. PC算法解析
  7. 马尔可夫等价类
  8. 工程实践挑战
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

方向确定概论

作为新上任的情感侦探,你的第一课是:别急着下结论。委托人递来厚厚一沓“社交出席记录”——谁在哪场聚会上出现过。你发现小X和小Z每场必同框,于是兴奋地在报告上写“X喜欢Z”。老侦探摇了摇头:同框只能说明有关联,也许是Z在追X,也许是两人都爱参加同一类活动(共同原因)。在因果发现的世界里,道理一模一样:观测数据里的相关关系,永远无法直接告诉你箭头的指向。

因果发现用有向无环图(DAG)描述变量间的因果结构:X→Z表示X是Z的原因。基于约束的方法(constraint-based methods)是因果发现中的一大流派。它不像评分搜索那样“大海捞针”地找图,而是像侦探一样按证据链推进:先用条件独立检验过滤掉虚假的“同框”,得到一张无方向的骨架,再通过方向确定(orientation)规则给边赋上箭头。

约束方法的理论根基是因果马尔可夫条件(Causal Markov Condition):给定一个变量的直接原因(父节点)后,它与所有非后代变量条件独立。这等于说:只要你把“真正的心动对象”控住,当事人对其他人的反应就只剩随机波动。这个性质把“数据中的条件独立”与“图中的结构信息”一一挂钩——它既是删边依据,也是方向确定的出发点。

因果马尔可夫条件,就是侦探手册开篇的第一条铁律:想判断一段关系是不是“真感情”,先控制住可疑的中间人。有了这条铁律,你才能在后面的排查中,理直气壮地删掉那些“只是朋友”的边。

X⊥ ⁣ ⁣ ⁣⊥NonDesc(X)∣Pa(X)\displaystyle X \perp\!\!\!\perp \mathrm{NonDesc}(X) \mid \mathrm{Pa}(X)
因果马尔可夫条件:给定父节点后,变量与非后代条件独立。它是约束方法删边与定向的理论基石。
  1. 父节点集合$\mathrm{Pa}(X)$ 表示X的直接原因。在情感比喻里,父节点就是真正让X“心动”的因素。
    Pa(X)\mathrm{Pa}(X)
  2. 非后代集合$\mathrm{NonDesc}(X)$ 表示除X的后代外所有变量,即不受X因果影响的对象。
    NonDesc(X)\mathrm{NonDesc}(X)
  3. 条件独立$X \perp\!\!\!\perp \mathrm{NonDesc}(X)$ 表示在给定父节点后,X与这些变量统计独立。
    X⊥ ⁣ ⁣ ⁣⊥NonDesc(X)∣Pa(X)X \perp\!\!\!\perp \mathrm{NonDesc}(X) \mid \mathrm{Pa}(X)
  4. 因果应用若数据发现X与某变量在给定候选父节点下条件独立,则它们之间不存在直接因果边。
    X⊥ ⁣ ⁣ ⁣⊥Y∣Pa(X)⇒无直接边X \perp\!\!\!\perp Y \mid \mathrm{Pa}(X) \Rightarrow \text{无直接边}

骨架构建基础

侦探的第一份实操作业:画关系草稿图。先把所有“怀疑有直接联系”的人两两连一条虚线——不看方向,只看有没有边。这就像警察办案先画嫌疑人关系网:谁和谁有交集先连上,至于谁指挥谁,后面再说。草稿图画得越准,后面破案就越省力。

在因果图里,这张只有无向边的“草稿图”叫骨架(skeleton)。构建骨架的核心工具是条件独立性检验:如果X和Y在给定某个变量集合S后变成独立,就说明X和Y之间没有直接因果边,虚线可以擦掉;如果控制谁都无法让它们独立,这条边就要留住。

技术细节在于如何找条件集S。经典PC算法用“由小到大”的搜索策略:先做0阶(无条件)检验,再把条件集扩大到1个、2个……直到穷尽所有候选。条件独立的定义如下:

回到侦探视角:当你发现“小A和小B总是同框,但只要小C不在场,两人就毫无互动”,就能排除A和B之间有直接的“业务往来”。骨架阶段删掉的边越多,后续定向就越省力——这步做扎实,方向确定就成功了一半。

X⊥ ⁣ ⁣ ⁣⊥Y∣Z  ⟺  P(X,Y∣Z)=P(X∣Z)⋅P(Y∣Z)\displaystyle X \perp\!\!\!\perp Y \mid Z \iff P(X, Y \mid Z) = P(X \mid Z) \cdot P(Y \mid Z)
条件独立性:控制Z之后X与Y的信息不再互相传递。它是骨架删边的数学判据。
  1. 联合条件概率$P(X, Y \mid Z)$ 是给定Z条件下X和Y同时发生的概率。
    P(X,Y∣Z)P(X, Y \mid Z)
  2. 乘积分解$P(X \mid Z) \cdot P(Y \mid Z)$ 表示在Z条件下X、Y各自独立地发生的概率之积。
    P(X∣Z)⋅P(Y∣Z)P(X \mid Z) \cdot P(Y \mid Z)
  3. 独立性判据等式成立意味着给定Z后X与Y不再互相传递信息。
    X⊥ ⁣ ⁣ ⁣⊥Y∣ZX \perp\!\!\!\perp Y \mid Z
  4. 骨架决策找到这样的Z就删除X—Y边,并记录Z为分离集。
    删除边 X−Y\text{删除边 } X - Y

碰撞器定向

重大突破!你发现小X和小Y从不一起出现,但每当女神Z在场,两人都会活跃起来——一个抢着搬椅子,一个抢着递话筒。更妙的是,一旦Z不在,两人又回到“互不认识”。你一拍大腿:这不是巧合,这是情敌结构!

因果图里,这种“两个原因共同指向一个结果”的结构叫碰撞器(collider),也叫v-structure:X→Z←Y。它的统计指纹独一无二:X和Y无条件独立,但给定Z后反而条件依赖。原因在于X和Y都影响Z,当你“固定”Z时,X和Y之间就出现了由Z传导过来的信息竞争通道。

这个“独立→依赖”的翻转,是方向确定最关键的实锤。它的形式化表达如下:

老侦探把这招叫作“情敌现形法”:想确认两个看似无关的人是否指向同一个目标,就看看在“目标在场”时他们是否会反常地相关。这第一条箭头一旦落地,后面的方向就能连环解锁。

X⊥ ⁣ ⁣ ⁣⊥Y  ∧  X̸ ⁣⊥ ⁣ ⁣ ⁣⊥Y∣Z  ⟹  X→Z←Y\displaystyle X \perp\!\!\!\perp Y \;\wedge\; X \not\!\perp\!\!\!\perp Y \mid Z \;\Longrightarrow\; X \to Z \leftarrow Y
碰撞器判据:两个原因无条件独立,但给定共同结果后条件依赖。这是方向确定的第一类箭头来源。
  1. 无条件独立$X \perp\!\!\!\perp Y$ 表示两个原因原本独立(情敌互不相识)。
    X⊥ ⁣ ⁣ ⁣⊥YX \perp\!\!\!\perp Y
  2. 条件依赖$X \not\!\perp\!\!\!\perp Y \mid Z$ 表示给定共同结果Z后反而出现依赖(竞争关系暴露)。
    X̸ ⁣⊥ ⁣ ⁣ ⁣⊥Y∣ZX \not\!\perp\!\!\!\perp Y \mid Z
  3. 合取条件$\wedge$ 表示上述两个条件同时成立时触发推断。
    X⊥ ⁣ ⁣ ⁣⊥Y  ∧  X̸ ⁣⊥ ⁣ ⁣ ⁣⊥Y∣ZX \perp\!\!\!\perp Y \;\wedge\; X \not\!\perp\!\!\!\perp Y \mid Z
  4. 定向结论$\Longrightarrow$ 推出箭头指向Z,即Z是共同结果。
    X→Z←YX \to Z \leftarrow Y

传播定向规则

有了第一条实锤箭头,剩余虚线边不需要一个个蹲点。职业侦探的杀手锏是“八卦传播”:既然X→Z已实锤,又知道Z和W经常互动、且X与W毫无交集,那么Z指向W几乎是必然——否则就会凭空造出一个与证据冲突的“情敌结构”。

这就是Meek规则(R1–R4)的思想内核。它们把“已知箭头 + 无向边 + 无环约束”组合起来,用排除法为剩余边定向。四条规则中最核心的R1表述为:若X→Y且Y—Z是无向边,且X与Z不相邻,则只能定向为Y→Z。

为什么只能这样?若反着假设Z→Y,那么X→Y←Z就会构成碰撞器。但碰撞器已经在上一阶段被全网扫描过——Y并不是碰撞器。反向假设与已有证据冲突,因此只有Y→Z这一种解释。R1的公式如下:

Meek规则就像朋友圈的八卦推理:“A暗恋B”是实锤,“B和C走得很近”,“A和C根本不认识”——那B指向C还有什么悬念?四条规则合起来,能够完全定向所有可定向的边,构成方向确定的“完备推理机”。

X→Y  ∧  Y−Z  ∧  (X,Z∉E)  ⟹  Y→Z\displaystyle X \to Y \;\wedge\; Y - Z \;\wedge\; (X, Z \notin E) \;\Longrightarrow\; Y \to Z
Meek规则R1:已知X→Y、Y—Z无向且X与Z不相邻时,只能定向为Y→Z。方向沿网络传播。
  1. 已知箭头$X \to Y$ 是已确定方向的边(侦探手上的实锤)。
    X→YX \to Y
  2. 无向边$Y - Z$ 是尚不知道方向、等待定夺的边。
    Y−ZY - Z
  3. 不相邻条件$(X, Z \notin E)$ 表示X和Z之间没有边,排除了反向形成碰撞器的可能。
    X,Z∉EX, Z \notin E
  4. 传播结论$\Longrightarrow$ 推出Y→Z,方向沿路径向前传递一格。
    Y→ZY \to Z

PC算法解析

现在把侦探的手艺整理成一本标准化办案手册。PC算法(Peter-Clark)是目前最经典的基于约束的因果发现算法,把方向确定拆成三个清晰阶段:骨架发现、碰撞器识别、Meek传播。

第一阶段,骨架发现:算法从全连接图出发,对每对变量做条件独立性检验;每找到一个让X⊥Y|S成立的条件集S,就删除这条边并记录S(分离集),供后续定向使用。搜索遵循“从低阶到高阶”:先查无条件独立,再查1个条件变量、2个……直到穷尽所有候选。

骨架阶段的形式化判据如下:

第二阶段,碰撞器识别:对每个“三节点组”(X—Z—Y且X与Y不相邻),检查Z是否为碰撞器——用分离集判断:如果X⊥Y(无条件)且X不⊥Y|Z,则定向X→Z←Y。第三阶段,Meek规则传播剩余方向。三步走完,能识别的方向全部落袋。PC算法让“情感侦探”从凭直觉断案升级为标准化流水线:骨架发现是全城排查,碰撞器识别是锁定情敌,Meek传播是八卦连锁——可靠、高效、可复现。

∃ S⊆Adj(X)∖{Y}:  X⊥ ⁣ ⁣ ⁣⊥Y∣S  ⟹  删除边 X−Y\displaystyle \exists\, S \subseteq \mathrm{Adj}(X) \setminus \{Y\}: \; X \perp\!\!\!\perp Y \mid S \;\Longrightarrow\; \text{删除边 } X - Y
PC算法骨架判据:只要存在某个条件集S使X与Y条件独立,就删除该边。S从邻居集合中搜索。
  1. 邻接集合$\mathrm{Adj}(X)$ 是当前骨架中X的所有邻居。
    Adj(X)\mathrm{Adj}(X)
  2. 候选条件集$S \subseteq \mathrm{Adj}(X) \setminus \{Y\}$ 表示从X的邻居中排除Y后取子集。
    S⊆Adj(X)∖{Y}S \subseteq \mathrm{Adj}(X) \setminus \{Y\}
  3. 存在性检验$\exists\, S$ 表示只要存在任何一个这样的条件集使条件独立成立。
    ∃ S:X⊥ ⁣ ⁣ ⁣⊥Y∣S\exists\, S: X \perp\!\!\!\perp Y \mid S
  4. 删边动作满足上述存在性即删除边X—Y,算法进入下一对变量。
    删除边 X−Y\text{删除边 } X - Y

马尔可夫等价类

侦探也有认怂的时候。你查清了一条“三角暗恋”:X→Z←Y。可是,另一条链X→Y→Z呢?数据显示X和Y总同框、Y和Z也总同框、X和Z不同框……所有条件独立关系都对得上,但你永远无法用观测数据区分X→Y→Z、X←Y←Z和X←Y→Z这三种解释。这是一桩“数据无法定罪”的悬案。

数学上,这些共享相同条件独立关系的DAG构成一个马尔可夫等价类(Markov equivalence class)。图论给出简洁判据:两个DAG等价,当且仅当它们的骨架相同且碰撞器集合相同。也就是说,方向确定能解决的是“骨架 + 碰撞器”决定的部分,剩下的方向只能悬置。

老侦探会在结案报告里写清楚:“本案箭头已确定到最大可识别程度,剩余方向需补充干预实验或纵向跟踪数据。”在实际输出中,我们用部分有向无环图(CPDAG)表达等价类:可识别的边画成箭头,不可识别的边保持无向。敢于承认不确定性,才是真正专业的因果分析。

G1≡G2  ⟺  Skel(G1)=Skel(G2)  ∧  VStr(G1)=VStr(G2)\displaystyle \mathcal{G}_1 \equiv \mathcal{G}_2 \iff \mathrm{Skel}(\mathcal{G}_1) = \mathrm{Skel}(\mathcal{G}_2) \;\wedge\; \mathrm{VStr}(\mathcal{G}_1) = \mathrm{VStr}(\mathcal{G}_2)
马尔可夫等价条件:骨架相同且碰撞器集合相同的两个DAG,观测数据无法区分它们。
  1. 骨架相同$\mathrm{Skel}(\mathcal{G}_1) = \mathrm{Skel}(\mathcal{G}_2)$ 表示两个图的无向边集合完全一致。
    Skel(G1)=Skel(G2)\mathrm{Skel}(\mathcal{G}_1) = \mathrm{Skel}(\mathcal{G}_2)
  2. 碰撞器相同$\mathrm{VStr}(\mathcal{G}_1) = \mathrm{VStr}(\mathcal{G}_2)$ 表示两个图的v-structure集合一致。
    VStr(G1)=VStr(G2)\mathrm{VStr}(\mathcal{G}_1) = \mathrm{VStr}(\mathcal{G}_2)
  3. 等价判定$\iff$ 表示上述两个条件同时满足时两图属于同一等价类。
    G1≡G2\mathcal{G}_1 \equiv \mathcal{G}_2
  4. 方向边界等价类内的方向差异不可识别,只能输出到部分有向图为止。
    CPDAG=等价类表示\mathrm{CPDAG} = \text{等价类表示}

工程实践挑战

走出教科书,真实委托现场乱象丛生:目击记录只有一小段(有限样本),人群里藏着从未露面的神秘人(潜在混淆变量),而侦探自己还得决定“多像才算可疑”(显著性水平α)。方向确定在真实世界中远没有理论那么优雅。

有限样本下,条件独立性检验的统计功效下降,很容易误删或漏删边:样本越少,检验统计量的方差越大,p值的波动越剧烈。更棘手的是潜在混淆变量:一个没被观测的U同时影响X和Y,会让“条件独立”和“条件依赖”的检验结果失真——明明没有直接边却显示依赖,或者明明有边却被误判为独立。

条件独立性检验的常用统计量之一是基于列联表的G²似然比统计量。它衡量观测频数与“独立假设下期望频数”的偏离程度:

工程上的应对策略包括:样本少时适当放宽α(保守删边)、用BIC等准则校正多重比较、对可疑混淆场景改用FCI等对潜在混淆稳健的算法、以及做参数敏感性分析。方向确定不是一键生成的魔术,而是在噪声中最大化“真相暴露概率”的精细活。

G2=2∑x,y,zOxyzln⁡OxyzExyz\displaystyle G^2 = 2 \sum_{x,y,z} O_{xyz} \ln \frac{O_{xyz}}{E_{xyz}}
G²检验统计量:比较观测计数与独立假设下期望计数的偏离程度,超过临界值时拒绝独立性假设。
  1. 观测频数$O_{xyz}$ 是实际数据中(x, y, z)组合的计数。
    OxyzO_{xyz}
  2. 期望频数$E_{xyz}$ 是假设独立条件下应有的理论计数。
    ExyzE_{xyz}
  3. 对数似然比$\ln(O_{xyz} / E_{xyz})$ 度量每个格子的偏离方向与强度。
    ln⁡OxyzExyz\ln \frac{O_{xyz}}{E_{xyz}}
  4. 阈值判定$G^2$ 超过临界值时拒绝独立假设,反之则接受独立性。
    G2>χ临界2⇒拒绝独立G^2 > \chi^2_{\text{临界}} \Rightarrow \text{拒绝独立}

AI 应用场景

基因调控网络推断

阶段:数据预处理 → 因果发现建模 → 调控关系解析

解决的问题:传统差异表达分析只能列出“哪些基因在肿瘤里高表达”,给不出“哪个基因调控哪个基因”。基于约束方法从基因表达数据中推断调控方向,帮助锁定上游调控子。

具体案例:研究团队用PC算法分析乳腺癌RNA-seq数据,先构建基因共表达骨架,再通过碰撞器识别(两个转录因子共同调控某靶基因),定向出关键调控子FOXA1与靶基因GATA3的上下游关系,为后续功能实验提供候选靶点。

电商转化因果归因

阶段:业务数据采集 → 因果推理 → 增长策略决策

解决的问题:“领取优惠券的客户购买率更高”——是优惠券拉动购买,还是原本就想买的人才领券?相关性分析无法回答,方向确定可以。

具体案例:某电商平台用基于约束的方向确定分析“浏览详情页”“领取优惠券”“下单购买”三者的因果结构,发现“浏览→购买←领券”的碰撞器模式,确认优惠券确实促进了冲动消费,据此优化了发券策略。

工业设备故障根因定位

阶段:传感器时序处理 → 因果结构发现 → 根因诊断

解决的问题:风电机组SCADA系统中,多个传感器(温度、振动、转速)同时告警,传统规则只能报“系统异常”,无法定位根因。约束方法可以从告警数据中定向故障传播路径。

具体案例:工程师将传感器变量离散化后,用约束方法建立告警变量间的因果骨架并定向,锁定“轴承温度”是引发“齿轮箱振动”与“功率下降”的根因变量。维修团队据此优先更换轴承,故障停机时间下降了40%。

Python 演示

python125 行
"""基于约束方法:方向确定(简化演示) 模拟四变量因果图 X -> Z <- Y 且 Z -> W,完整演示PC算法方向确定三阶段:1. 骨架构建:条件独立性检验删边;2. 碰撞器定向:识别 v-structure;3. Meek 规则传播:沿网络推导剩余方向。"""import mathimport itertoolsimport numpy as np  def simulate_data(n_samples=2000, seed=42):    """生成 X -> Z <- Y 且 Z -> W 的模拟数据"""    rng = np.random.default_rng(seed)    x = rng.normal(0, 1, n_samples)    y = rng.normal(0, 1, n_samples)    z = 0.8 * x + 0.8 * y + rng.normal(0, 0.5, n_samples)    w = 0.8 * z + rng.normal(0, 0.5, n_samples)    return np.column_stack([x, y, z, w])  def _norm_cdf(x):    """标准正态分布累积分布函数(利用 erf)"""    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))  def partial_corr(data, i, j, cond):    """偏相关系数:控制 cond 后 i 与 j 的相关"""    cols = [i, j] + list(cond)    cov = np.cov(data[:, cols], rowvar=False)    try:        prec = np.linalg.inv(cov)    except np.linalg.LinAlgError:        return 1.0    r = -prec[0, 1] / math.sqrt(prec[0, 0] * prec[1, 1])    return float(np.clip(r, -1.0, 1.0))  def cond_indep(data, i, j, cond, alpha=0.05):    """Fisher Z 变换条件独立性检验:p > alpha 判独立"""    n = data.shape[0]    r = partial_corr(data, i, j, cond)    r = max(-0.9999, min(0.9999, r))    z = 0.5 * math.log((1 + r) / (1 - r))    denom = n - len(cond) - 3    if denom <= 0:        return False    p_value = 2.0 * (1.0 - _norm_cdf(abs(z) * math.sqrt(denom)))    return p_value > alpha  def build_skeleton(data, alpha=0.05):    """阶段一:逐级增大条件集的骨架构建"""    n_vars = data.shape[1]    edges = {(i, j) for i in range(n_vars) for j in range(i + 1, n_vars)}    for size in range(n_vars - 1):        for (i, j) in list(edges):            others = [k for k in range(n_vars) if k not in (i, j)]            for cond in itertools.combinations(others, size):                if cond_indep(data, i, j, list(cond), alpha):                    edges.discard((i, j))                    break    return edges  def orient_colliders(data, edges, alpha=0.05):    """阶段二:碰撞器定向(X丄Y 且 X不丄Y|Z -> X->Z<-Y)"""    directed = set()    unoriented = set(edges)    nodes = set()    for i, j in edges:        nodes.add(i)        nodes.add(j)    for z in nodes:        neighbors = [n for n in nodes                     if (min(n, z), max(n, z)) in unoriented]        for (x, y) in itertools.combinations(neighbors, 2):            if (min(x, y), max(x, y)) in unoriented:                continue            if cond_indep(data, x, y, [], alpha) and \               not cond_indep(data, x, y, [z], alpha):                directed.add((x, z))                directed.add((y, z))                unoriented.discard((min(x, z), max(x, z)))                unoriented.discard((min(y, z), max(y, z)))    return directed, unoriented  def apply_meek_rules(directed, unoriented, n_vars):    """阶段三:Meek规则R1传播(X->Y, Y-Z, X与Z不相邻 => Y->Z)"""    changed = True    while changed:        changed = False        for (x, y) in list(directed):            for z in range(n_vars):                if z == x or z == y:                    continue                if (min(y, z), max(y, z)) not in unoriented:                    continue                if (min(x, z), max(x, z)) in unoriented or \                   (x, z) in directed or (z, x) in directed:                    continue                directed.add((y, z))                unoriented.discard((min(y, z), max(y, z)))                changed = True    return directed, unoriented  def main():    data = simulate_data()    print("模拟数据:X -> Z <- Y 且 Z -> W(真实结构)")    skel = build_skeleton(data)    print("① 骨架构建:", sorted(skel))    directed, rest = orient_colliders(data, skel)    print("② 碰撞器定向:", sorted(directed))    print("   剩余无向边:", sorted(rest))    directed, rest = apply_meek_rules(directed, rest, data.shape[1])    print("③ Meek传播后:", sorted(directed))    print("   最终无向边:", sorted(rest))  if __name__ == "__main__":    main()
目的:用一个四变量模拟数据完整走一遍PC算法的方向确定三阶段——骨架构建、碰撞器定向、Meek规则传播,最终恢复出全部因果箭头。
  1. z = 0.5 * math.log((1 + r) / (1 - r))Fisher Z 变换,把相关系数映射到近似正态分布域,方便算p值
  2. p_value > alphap 值大于显著性水平时,判定“没有证据拒绝独立”
  3. edges.discard((i, j))找到使 X⊥Y|S 的条件集,果断删除这条边
  4. cond_indep(data, x, y, [], alpha)检查无条件独立,对应“情敌是否互不相识”
  5. not cond_indep(data, x, y, [z], alpha)检查给定 Z 后是否依赖,对应“女神在场是否暴露竞争”
  6. directed.add((x, z))碰撞器确认,定向 x→z,第一枚箭头落地
  7. if (min(x, z), max(x, z)) in unoriented确保 X 与 Z 不相邻,排除碰撞器后只能 Y→Z

常见误区

误区刚学完PC算法,觉得只要做一个条件独立性检验,就能立刻知道谁指向谁。跑完代码直接看相关性矩阵就想画箭头。

正解条件独立性检验只能回答“有没有边”,不能回答“指向哪边”。方向确定需要额外的两步:先靠碰撞器拿到第一批箭头,再用Meek规则传播。就像侦探看到两个人经常同框,只能说有关联,不能直接说谁喜欢谁。

为何常见:很多教程把“删边”和“定向”放在同一张流程图里讲,初学者误以为检验本身就输出箭头。

误区骨架里存留的无向边,是不是代表X和Y互相影响(双向因果)?

正解无向边只是“方向未知”,不代表双向因果。在部分有向图(CPDAG)里,无向边指的是“数据无法确定方向”,实际可能是X→Y或Y→X。双向箭头(↔)在因果发现里有另一套语义(通常表示存在未观测混淆),不是一回事。

为何常见:“无向”这个词容易让人联想到“可以双向”,但它其实只是“待定”。

误区模型输出了一些无向边,为了拿到完整DAG,就随便指定方向,或者用“领域经验”强行补全。

正解如果两个DAG属于同一等价类,观测数据在统计上完全无法区分它们,任意选择一个都是一种武断。专业做法是明确输出CPDAG并说明哪些边不可识别,或引入干预数据来打破等价类。

为何常见:下游任务经常需要完整DAG,导致从业者不愿意面对“方向识别不了”的现实。

误区把条件独立性检验的显著性水平固定在0.05,跑一遍PC算法就能得到可靠结果,不管样本多小、潜在混淆是否存在。

正解α决定删边严格程度。样本少时α过小会导致漏删(该删的边没删),α过大导致误删(真边被删)。存在潜在混淆时,标准PC算法的假设被违背,需要FCI等更稳健的方法。实际工程必须做多重比较校正和敏感性分析。

为何常见:"α=0.05"是统计学的默认习惯,但因果发现涉及海量检验,直接套用默认值会带来系统性偏差。

面试题

Q1. 基于约束的因果发现方法,其第一阶段输出的核心结果是什么?

单选题 · easy

  • A.完整有向无环图DAG
  • B.无向骨架图与边集← 正确答案
  • C.变量间相关系数矩阵
  • D.马尔可夫等价类列表

答案:B。基于约束方法(如PC算法)第一阶段通过条件独立性检验删边,输出无向骨架。方向确定在后续阶段进行。完整DAG是最终目标而非第一阶段输出,相关矩阵只是中间统计量。

Q2. 碰撞器(v-structure)的统计特征是什么?

单选题 · easy

  • A.无条件依赖,给定Z后仍依赖
  • B.无条件独立,给定Z后条件依赖← 正确答案
  • C.无条件独立,给定Z后仍独立
  • D.无条件依赖,给定Z后条件独立

答案:B。X→Z←Y中,两个原因X、Y在无条件下独立(情敌互不相识),给定共同结果Z后变成条件依赖(竞争关系暴露)。这正是方向确定中最可靠的“实锤”,对应“独立→依赖”的翻转。

Q3. 在PC算法中,“分离集”(separating set)的主要作用是什么?

单选题 · mid

  • A.用于初始化骨架图
  • B.供碰撞器定向使用← 正确答案
  • C.用于计算变量方差
  • D.替代条件独立性检验

答案:B。删边时记录的条件集S就是分离集。定向阶段判断“X⊥Y且X不⊥Y|Z”时,需要知道X与Y之间的边是在什么条件下被删除的,分离集是碰撞器识别的重要依据。

Q4. 以下哪些DAG与 X→Y→Z 属于同一个马尔可夫等价类?

多选题 · mid

  • A.X→Y→Z 与 X←Y→Z← 正确答案
  • B.X→Y→Z 与 X→Y←Z
  • C.X→Y→Z 与 X←Y←Z← 正确答案
  • D.X→Y→Z 与 X→Z→Y

答案:AC。X→Y→Z、X←Y→Z、X←Y←Z具有相同骨架(X—Y、Y—Z)且无碰撞器。X→Y←Z含碰撞器Y,不等价;X→Z→Y的骨架是X—Z、Z—Y,与原骨架不同。

Q5. Meek规则R1的完整表述是哪一项?

单选题 · mid

  • A.X→Y,Y—Z,X与Z相邻 ⇒ Y→Z
  • B.X→Y,Y—Z,X与Z不相邻 ⇒ Y→Z← 正确答案
  • C.X—Y,Y→Z,X与Z不相邻 ⇒ X→Y
  • D.X→Y,Y→Z,X与Z不相邻 ⇒ X→Z

答案:B。若反设Z→Y,则X→Y←Z构成碰撞器。但碰撞器已在上一阶段被全网扫描完毕,Y不是碰撞器,因此反设矛盾,只能定向Y→Z。R1本质是“排除法定向”。

Q6. 关于马尔可夫等价类的正确说法包括哪些?

多选题 · hard

  • A.等价DAG的骨架完全相同← 正确答案
  • B.等价DAG碰撞器集合相同← 正确答案
  • C.观测数据可区分等价DAG
  • D.等价类用CPDAG表示← 正确答案

答案:ABD。C错误——等价类的定义正是“观测数据无法区分”。A、B是等价判据的两个必要条件,D是标准表示方式。方向确定的最终输出上限就是CPDAG。

Q7. 当数据中存在未观测的混淆变量时,标准PC算法的方向确定可能失效,最根本的原因是什么?

单选题 · extreme

  • A.条件独立检验直接报错中断
  • B.虚假条件独立或依赖致误判← 正确答案
  • C.混淆变量会自动删除所有边
  • D.只有定向受影响,骨架不受

答案:B。潜在混淆U同时影响X与Y,使X与Y即使没有直接边也显示依赖(虚假相关),或掩盖真实依赖(虚假独立),破坏“独立⇒无直接边”和碰撞器判据的基础。PC算法默认无隐藏变量,此假设被违背时需改用FCI等方法。

Q8. 在方向确定中,显著性水平α的取值偏小时,最容易导致什么问题?

单选题 · easy

  • A.误删真实存在的因果边
  • B.保留过多实际独立的边← 正确答案
  • C.定向规则将完全失效
  • D.骨架构建跳过碰撞器检测

答案:B。α偏小意味着更难拒绝独立性假设,p值更难超过阈值,于是“判为独立”更困难,该删的边没删,骨架保留了过多实际独立的边。反之α偏大则容易误删真边。

Q9. 一份方向确定报告中保留了若干无向边,可能的原因包括哪些?

多选题 · hard

  • A.等价类内方向不可识别← 正确答案
  • B.样本量不足以支撑检测← 正确答案
  • C.噪声导致检验不显著← 正确答案
  • D.算法代码必然写错了

答案:ABC。无向边可能是马尔可夫等价类内的本质不可识别(A),也可能是样本量小导致检验功效不足(B),还可能是噪声使条件依赖检验不显著(C)。D是操作层面的工具问题,不属于方法论上的合理原因。

Q10. 给定三变量X、Y、Z的观测数据,若检验发现X⊥Y,且X不⊥Y|Z,方向应如何确定?

单选题 · mid

  • A.定向为X→Y链式结构
  • B.定向为Y→X分叉结构
  • C.定向为X→Z←Y碰撞结构← 正确答案
  • D.无法给出任何方向判断

答案:C。无条件独立加上条件依赖的“翻转”,是碰撞器的标志性指纹,方向只能是X→Z←Y(Z是X和Y的共同结果)。A、B都是链式/分叉结构,不具备这种统计特征。