深夜的刑侦办公室里,侦探面前摊着一张巨大的嫌疑人关系网:名字之间画满了可能的连线。他接到的任务是——仅仅依靠一叠日常的“行为观测记录”,把“谁在暗中推动谁”的因果链条还原出来。因果看不见摸不着,但它会在数据里留下蛛丝马迹。PC算法就是一套无数学者打磨过的“因果刑侦手册”:先假设所有人都有可能互相影响(全连接图),然后逐一核对“不在场证明”(统计独立性检验),发现两个人毫无关联就果断划掉连线;接着通过“两个素不相识的人却共同认识同一个中间人”这类特殊结构,锁定连线的方向;最后用“因果不能循环、指证不能回环”的逻辑,把剩余方向一一补齐。整条破案流水线,就是PC算法的完整流程。

岗位关联

掌握PC算法,你就能从观测数据中直接画出因果结构图,为后续因果效应估计、风险归因与决策干预提供最底层的地图——这是因果推断从业者绕不开的第一块基石。

目录
  1. 可视化演示
  2. 算法总览:案件的宏观蓝图
  3. 前置准备:给侦探校准测谎仪
  4. 骨架发现:不在场证明逐一核对
  5. 方向判定:对撞结构的秘密
  6. 方向传播:口供互证,补全残余箭头
  7. 结果解读:案件报告里的留白
  8. 工程实现:让自动破案系统上岗
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示PC因果发现算法完整流程,从完全图出发通过条件独立性检验删边得到骨架,再利用v-结构碰撞子定向和Meek规则确定因果方向。

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

PC因果发现算法 骨架发现:从完全图出发,条件独立性检验删除无关边 v-结构定向:碰撞子X→Z←Y,Z不在分离集中则定向 Meek规则:已定向边传播方向约束,避免环和新碰撞 PC: Complete → CI-test → Skeleton → v-struct → Meek PC算法通过约束范式从数据中发现因果结构

算法总览:案件的宏观蓝图

刑警队接到一桩奇特的大案:他们要仅凭一份“城市行为观测记录”——比如每个人的出行轨迹、消费记录、社交活动——还原出“谁在背后推动谁”的完整因果链条。指挥官先把任务拆解成三步:先画出所有嫌疑人的关系网(不管方向),再找出“信息交汇点”确定部分箭头,最后用逻辑推理补齐全图。这份破案蓝图,就是PC算法的骨架。

PC算法的全称叫Peter-Clark算法,是因果发现领域最经典的“基于约束”方法。所谓“基于约束”,意思是它不猜方程、不拟合曲线,而是把“条件独立性”当作一票否决的证据:如果数据显示两个变量在控制其他条件后互不相干,那就把这条边划掉。它和“基于分数”的方法不同——后者像评委打分选最优模型,PC则像侦探取证排嫌疑。

从输入输出看,PC算法吃进一个观测数据矩阵(每列是一个变量,每行是一个样本),加上一个显著性水平α,吐出一个“部分有向无环图”(CPDAG)。它不关心影响的具体大小,只负责回答两个问题:哪些变量之间有关系?箭头指向哪个方向?这正是因果推断的第一张地图。

记住这张蓝图:先画网、再定向、后补全。接下来每一节,就是对这个破案过程的一次特写。

前置准备:给侦探校准测谎仪

出发之前,老刑警对新人反复叮嘱两件事:一是确认案情假设成立,二是把测谎仪校准到位。PC算法的“案情假设”就是忠实性假设(faithfulness):如果真实世界里两个变量之间没有因果路径,那么数据统计上就一定看不出它们有关联;反过来,统计上发现的任何关联,也都真实存在于因果结构之中。这个假设是整个删边逻辑的基石。

除了假设,还要选一把好用的“测谎仪”——条件独立性检验。它的任务是回答一个精确的问题:当我控制住一组变量 S 之后,X 和 Y 之间还存不存在统计联系?对于连续型数据,最常用的测谎仪是偏相关检验:先把 X、Y 分别对 S 做回归,取残差,再看两个残差的相关性。相关性越弱,越倾向于“独立”。

为了把“弱到什么程度算独立”量化,人们构造了一个t统计量:

校准完毕的测谎仪,即将投入大规模的“不在场证明”筛查中。

t=rXY⋅Sn−∣S∣−21−rXY⋅S2\displaystyle t = r_{XY \cdot S}\sqrt{\frac{n - |S| - 2}{1 - r_{XY \cdot S}^2}}
这个公式把偏相关系数转换成“标准尺子”上的读数,用来查一张叫“p值”的对照表。
  1. 偏相关系数 $r_{XY \cdot S}$控制住 S 之后,X 与 Y 之间净剩的相关强度,越接近 0 越独立。
    rXY⋅Sr_{XY \cdot S}
  2. 样本量修正 $n - |S| - 2$分子中的自由度调整,样本越少、控制变量越多,判断就越要谨慎。
    n−∣S∣−2n - |S| - 2
  3. 标准化 $\sqrt{\frac{n - |S| - 2}{1 - r^2}}$把偏相关系数放大到服从已知分布的标准尺度,这样就能查表得到 p 值。
    n−∣S∣−21−rXY⋅S2\sqrt{\frac{n - |S| - 2}{1 - r_{XY \cdot S}^2}}
  4. 查表判定算出 t 后查正态分布表得到 p 值。若 p > α,判为“独立”;若 p < α,判为“不独立”。
    p=2(1−Φ(∣t∣))p = 2\left(1 - \Phi(|t|)\right)

骨架发现:不在场证明逐一核对

侦探把完整关系网铺开:起初,每个嫌疑人都和其他所有人之间画了一条线,表示“暂不能排除关联”。然后审讯开始。第一轮,只问最简单的问题:“你们俩没有任何直接交集的证据吗?”这就是零阶条件独立性检验(d=0)。如果两人互不相干,直接划掉连线。第二轮,侦探换了个问法:“在某个证人在场的情况下,你们还有联系吗?”这就是一阶检验(d=1)。条件集中变量个数逐轮增加:d=0, 1, 2, …,直到网络不再变化。

每一轮删边,都必须登记一张“证据卡”:当时是用哪组条件变量证明他俩无关的。这张卡片叫分离集(Sepset),它记录了使两个变量条件独立的变量集合。别小看这张卡片,后面定向 v 结构时它是决定性证据。骨架发现阶段的算法逻辑可以用一句话概括:

经过若干轮筛查,全连接图瘦身成一个精简的“最小嫌疑网”——骨架。但此时所有线条都还没有方向,下一步才是关键。

若 p-value>α,则判定 X⊥ ⁣ ⁣ ⁣⊥Y∣S,并删除边 X−Y\displaystyle \text{若 } p\text{-value} > \alpha \text{,则判定 } X \perp\!\!\!\perp Y \mid S \text{,并删除边 } X - Y
这句话是整个PC算法最重要的“动作指令”:p值告诉我们有没有证据表明两个人有关,没证据就删边。
  1. p-value条件独立性检验返回的显著性概率,代表“数据给'独立'这个判断的支持度”。
    p-valuep\text{-value}
  2. 与 α 比较p值大于设定的显著性水平 α 时,说明没有足够证据推翻“独立”的零假设。
    p-value>αp\text{-value} > \alpha
  3. 独立性记号 $X \perp\!\!\!\perp Y \mid S$读作“在控制 S 的条件下 X 与 Y 独立”,这是删边的合法依据。
    X⊥ ⁣ ⁣ ⁣⊥Y∣SX \perp\!\!\!\perp Y \mid S
  4. 执行删边把骨架中 X 与 Y 之间的无向连线移除,并把 S 记入分离集。
    X−Y ⇒ 删边X - Y \ \Rightarrow\ \text{删边}

方向判定:对撞结构的秘密

侦探发现一个有趣的三角关系:A 和 B 素不相识、毫无交集,但他们居然都认识 C。在这个三角形里,C 是唯一的信息汇合点——A 和 B 各自独立地把情报汇入 C。这种“两个互不相干的人共同指向同一个中间人”的结构,在因果图中叫对撞子(v结构),它是定向最重要的天然路标:箭头必然指向中间那个点。

为什么?回到数据生成逻辑:如果真实结构是 X→Z←Y,那么 X 和 Y 在“出生”时是独立的,它们之间没有边;而 Z 同时受到两者的影响。反过来,如果数据告诉我们 X 和 Z 相关、Y 和 Z 相关,但 X 和 Y 相互独立,那么 X 和 Y 之间不可能有因果边,唯一的解释就是:它们俩共同“撞”向了 Z。把无向的 X—Z—Y 定向为 X→Z←Y,逻辑就闭合了。

正式判定条件需要精确表述——不仅要 X—Z—Y 在骨架上,还要检查 Z 是否出现在 X 和 Y 的分离集中:

v结构就像案件报告里最硬的物证,一块就能定死两个箭头。但剩下的“无向线头”还要靠规则推理。

若 X−Z−Y 且 Z∉Sep(X,Y),则定向为 X→Z←Y\displaystyle \text{若 } X - Z - Y \ \text{且} \ Z \notin \text{Sep}(X, Y) \text{,则定向为 } X \to Z \leftarrow Y
分离集检查是防止误判的闸门:如果 Z 本身就能解释 X 和 Y 的独立性,那就不是对撞结构。
  1. 路径存在 $X - Z - Y$骨架中 Z 同时连着 X 与 Y,构成 V 字形的两条无向边。
    X−Z−YX - Z - Y
  2. 分离集检查 $Z \notin \text{Sep}(X, Y)$分离集是让 X、Y 变独立的变量集合,如果 Z 不在其中,说明 X、Y 的独立性不需要依赖 Z。
    Z∉Sep(X,Y)Z \notin \text{Sep}(X, Y)
  3. 对撞判定X 与 Y 独立,却都与 Z 相关,唯一自洽的解释是箭头汇聚于 Z。
    X⊥ ⁣ ⁣ ⁣⊥Y 且 X⊥ ⁣ ⁣ ⁣ ⁣̸ ⁣⊥Z, Y⊥ ⁣ ⁣ ⁣ ⁣̸ ⁣⊥ZX \perp\!\!\!\perp Y \ \text{且} \ X \perp\!\!\!\!\not\!\perp Z, \ Y \perp\!\!\!\!\not\!\perp Z
  4. 定向写入把两条无向边写成有向边,得到对撞结构。
    X→Z←YX \to Z \leftarrow Y

方向传播:口供互证,补全残余箭头

有了几块物证,剩下的方向靠“口供一致性”推出来。如果 A 已经证实指向 B,而 B 和 C 之间还有一条未定向的连线,且 A 与 C 素无往来,那么侦探手册强制规定:方向只能是 B→C。为什么?因为如果 C 指向 B,就会凭空造出一个新的对撞子 A→B←C,这与证据矛盾;推理必须避免在已知边上引入新的对撞结构。

这套推理规则在学术上叫 Meek 规则,共四条。最常用的是前两条:R1 处理“已定向边 + 无向边”的链式传递,R2 处理“两条有向边 + 一条无向边”的闭合。它们共同遵守一条铁律:任何时刻不得把图推成有向环,因为因果不可能循环——你不可能既是自己祖父的父亲又是自己祖父的儿子。

R1 规则的逻辑链如下:

R2 则处理另一种常见场景:已知 X→Y→Z,且 X 与 Z 之间还有一条无向边,那么这条边必然定向为 X→Z,否则会出现环:

规则一条条套用,直到再也推不出新方向为止。最终剩下的图中,部分无向边是数据“诚实的沉默”——留给下一节解读。

X→Y−Z 且 X,Z不相邻 ⇒ X→Y→Z\displaystyle X \to Y - Z \ \text{且} \ X, Z \text{不相邻} \ \Rightarrow \ X \to Y \to Z
这条规则说的是:一旦某个方向被物证坐实,相邻的无向边会像多米诺骨牌一样被推倒。
  1. 链式前提 $X \to Y \to Z$已经有了完整的有向链。
    X→Y→ZX \to Y \to Z
  2. 待定向边 $X - Z$骨架上还残留的这条无向边必须补上方向。
    X−ZX - Z
  3. 无环约束推导若 Z→X 则会形成 X→Y→Z→X 的有向环,违反因果基本法则,故只能 X→Z。
    ⇒X→Z\Rightarrow X \to Z

结果解读:案件报告里的留白

经验丰富的侦探知道,有些案件无论证据多充分,都会留下“无法判定”的遗憾。如果数据显示 A 和 B 只是简单相关,且没有对撞结构提供方向线索,那么“A 推动了 B”和“B 推动了 A”在统计上就是一对双胞胎,长得一模一样,无法区分。PC 算法不会硬猜,而是把这类边保留为无向,输出一张“部分有向无环图”(CPDAG)。

学术上,这一对长得一模一样的图叫“马尔可夫等价类”:只要两个因果图的骨架相同、对撞结构集合相同,它们对所有观测数据的解释力就完全相同。CPDAG 正是所有这些等价图“共同事实”的浓缩——公共箭头是实心箭头,有争议的边保持无向。用集合论的语言:

读懂留白,是因果推断从业者的专业素养。下一节,我们把整套流程交给自动化系统,让它变成一把量产工具。

E(G)={G′ | 骨架(G′)=骨架(G) 且 对撞结构(G′)=对撞结构(G)}\displaystyle \mathcal{E}(\mathcal{G}) = \left\{ \mathcal{G}' \ \middle| \ \text{骨架}(\mathcal{G}') = \text{骨架}(\mathcal{G}) \ \text{且} \ \text{对撞结构}(\mathcal{G}') = \text{对撞结构}(\mathcal{G}) \right\}
等价类提醒我们:因果发现不是魔术,观测数据对某些方向天然“失明”,C D P A G 里的无向边就是诚实的留白。
  1. 等价类集合 $\mathcal{E}(\mathcal{G})$与图 G 不可区分的一族因果图的总称。
    E(G)\mathcal{E}(\mathcal{G})
  2. 骨架一致条件所有等价图拥有的边(无论方向)完全相同。
    骨架(G′)=骨架(G)\text{骨架}(\mathcal{G}') = \text{骨架}(\mathcal{G})
  3. 对撞结构一致条件所有等价图的 v 结构指向完全一致,这是方向信息的唯一锚点。
    对撞结构(G′)=对撞结构(G)\text{对撞结构}(\mathcal{G}') = \text{对撞结构}(\mathcal{G})
  4. CPDAG 的生成把等价类里所有图都同意的箭头保留,其余边标为无向,得到最终输出。
    CPDAG=⋂G′∈E公共箭头\text{CPDAG} = \bigcap_{\mathcal{G}' \in \mathcal{E}} \text{公共箭头}

工程实现:让自动破案系统上岗

手工推演在变量超过五个时就累得够呛,更别说真实场景里动辄几十上百个变量。好在PC算法早已被封装成成熟的软件库,就像把侦探手册变成了全自动“无人警局”:输入数据,选好测谎仪,设好置信度,一键出图。Python生态里最常用的是 causal-learn,R 语言中有 pcalg,都是学界验证过的工业级实现。

用起来只有三行核心逻辑:调用 pc() 函数,传入数据矩阵、显著性水平和独立性检验方法,然后打印返回的因果图对象。参数调优的核心旋钮就是 α——它控制删边的手感:α大,更容易“判独立”,图更稀疏;α小,更容易“保留关联”,图更密。此外还有是否启用“稳定版”(stable=True)这类细节选项,它能保证边的删除顺序不影响最终结果。

整个骨架发现过程的时间代价可以粗略表达为:

至此,破案手册全卷读完。从手工推演到自动化工具,你已经有能力独立完成一次完整的因果发现任务。

O(p2⋅2dmax⁡)\displaystyle O\left(p^2 \cdot 2^{d_{\max}}\right)
变量数 p 的平方对,乘以最多 $d_{\max}$ 个条件变量的组合数,这就是 PC 算法“逐户排查”的开销账单。
  1. 变量对数 $p^2$任意两个变量都要配对检查,变量越多检查越重。
    p2p^2
  2. 条件集组合 $2^{d_{\max}}$每个候选条件集都要做一次检验,条件变量组合是指数级的。
    2dmax⁡2^{d_{\max}}
  3. 最大条件阶数 $d_{\max}$实际推进到的最深验证轮次,因为边不断被删除它通常很小。
    dmax⁡d_{\max}
  4. 工程含义尽早删边能显著降本,这也是 PC 从低阶到高阶逐轮推进的设计初衷。
    O(p2⋅2dmax⁡)O\left(p^2 \cdot 2^{d_{\max}}\right)

AI 应用场景

电商增长分析:定位真正的“增长引擎”

阶段:因果结构学习 / 特征关系建模

解决的问题:运营团队积累了大量指标——优惠券发放量、浏览时长、加购率、复购率。传统相关性分析只能看到“这些数字一起涨跌”,却分不清谁是因谁是果:是发券拉动了复购,还是高复购用户恰好更容易领券?用PC算法对用户行为数据做骨架发现,可以直接排除大量虚假相关,只保留真正的因果链路。

具体案例:某电商对 2000 万条用户行为数据跑 PC,输出的因果图显示“优惠券发放→加购意愿→实际下单”是一条完整因果链,而“复购率”其实是被“满意度”驱动的结果而非原因。团队据此把营销资源从盲目撒券转向提升加购体验,次季复购率提升 12%。

医学观察性研究:撕掉虚假相关的伪装

阶段:因果方向确认 / 混杂因素识别

解决的问题:电子病历中,“服药”和“血压下降”高度相关,但真实世界可能是“生活方式改善”同时导致了两者;更隐蔽的是,某些变量(如“就诊频率”)会同时被疾病严重度和患者依从性影响,形成对撞子,制造出根本不存在的“相关”。PC 的 v 结构定向正好用来识别这类陷阱。

具体案例:某临床研究团队用 PC 分析高血压患者随访数据,发现“初次就诊时的血压值”与“后续用药依从性”之间存在对撞结构——它们共同指向“就诊频率”。如果不做因果发现直接回归,就会得到“初诊血压影响依从性”的错误结论;正确定向后,研究改为关注依从性的直接前因,从而修正了试验设计方案。

金融风控特征甄别:用因果图给模型瘦身

阶段:特征因果结构解读 / 可解释建模

解决的问题:风控模型里几百个特征高度相关,直接堆进模型既冗余又难解释,还可能把“结果变量”当作“原因特征”造成泄漏。PC 输出的 CPDAG 能帮算法工程师区分“根因特征”和“下游结果特征”,从而筛选出真正值得建模的变量。

具体案例:某消费金融公司对申请人特征做 PC 分析,发现“近三月查询次数”是“贷款审批结果”的下游结果而非原因(存在对撞路径),将其剔除出模型后,模型在保持 AUC 的同时变量数从 120 个降为 38 个,且通过监管对“特征可解释性”的审查。

Python 演示

python79 行
# 教学版 PC 算法:从观测数据中发现骨架并定向 v 结构import mathfrom itertools import combinationsimport numpy as np def ncdf(x):    # 标准正态分布的累积分布函数    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0))) def pcorr(x, y, Z):    # 偏相关系数: 控制 Z 后 x 与 y 的相关    if Z.shape[1] == 0:        return np.corrcoef(x, y)[0, 1]    bx, _, _, _ = np.linalg.lstsq(Z, x, rcond=None)    by, _, _, _ = np.linalg.lstsq(Z, y, rcond=None)    rx = x - Z.dot(bx)    ry = y - Z.dot(by)    return np.corrcoef(rx, ry)[0, 1] def ci_test(x, y, Z, n, alpha=0.05):    # 条件独立性检验: 返回 True 表示 x 与 y 在控制 Z 后独立    r = pcorr(x, y, Z)    k = Z.shape[1]    t = r * math.sqrt((n - k - 2) / max(1e-12, 1 - r * r))    p = 2.0 * (1.0 - ncdf(abs(t)))    return p > alpha def pc_skeleton(data, alpha=0.05):    # 第一步: 从全连接图出发逐阶删边, 得到骨架    n, p = data.shape    adj = [set(range(p)) - {i} for i in range(p)]    sep = {}    d = 0    while True:        changed = False        for i, j in combinations(range(p), 2):            if j not in adj[i]:                continue            for cond in combinations(list(adj[i] - {j}), d):                if ci_test(data[:, i], data[:, j], data[:, list(cond)], n, alpha):                    adj[i].remove(j)                    adj[j].remove(i)                    sep[(i, j)] = sep[(j, i)] = set(cond)                    changed = True                    break        if not changed or d > max(len(a) for a in adj):            break        d += 1    return adj, sep def orient_v(adj, sep):    # 第二步: 用 v 结构定向, 返回有向边集合    directed = set()    for z in range(len(adj)):        for x, y in combinations(list(adj[z]), 2):            if z not in sep.get((x, y), set()):                directed.add((x, z))                directed.add((y, z))    return directed def main():    rng = np.random.default_rng(42)    n = 3000    X = rng.normal(size=n)    Z = rng.normal(size=n)    Y = 0.8 * X + 0.6 * Z + rng.normal(size=n)    W = 0.5 * X + rng.normal(size=n)    data = np.column_stack([X, Y, Z, W])    adj, sep = pc_skeleton(data, alpha=0.05)    print("骨架邻接:")    for i in range(data.shape[1]):        print("  ", i, "->", sorted(adj[i]))    directed = orient_v(adj, sep)    print("v 结构定向的有向边:")    for a, b in sorted(directed):        print("  ", a, "->", b) if __name__ == "__main__":    main()
目的:用纯 NumPy 实现一个可运行的简化版 PC 算法,在已知真实结构(X→Y、Z→Y、X→W)的仿真数据上验证骨架发现与 v 结构定向两大核心步骤。
  1. adj = [set(range(p)) - {i} for i in range(p)]初始化全连接无向图,每个节点先与所有其他节点相连。
  2. for cond in combinations(list(adj[i] - {j}), d)枚举邻域中 d 个候选条件变量,这是“逐阶排查”的核心循环。
  3. if ci_test(data[:, i], data[:, j], data[:, list(cond)], n, alpha):执行条件独立性检验,返回 True 表示判为独立。
  4. sep[(i, j)] = sep[(j, i)] = set(cond)把使二者独立的条件集同时记入双向分离集,留给 v 结构定向使用。
  5. if not changed or d > max(len(a) for a in adj): break当一轮没有删掉任何边,或条件阶数超过邻域规模时,骨架发现终止。
  6. if z not in sep.get((x, y), set()):v 结构定向的闸门判断:中间节点不在分离集中,才允许定向。
  7. directed.add((x, z)); directed.add((y, z))把 X→Z 和 Y→Z 两条有向边加入结果集,完成对撞定向。

常见误区

误区PC 算法跑完拿到的就是“真正的因果图”,箭头指哪就一定是哪。看到还有无向边就觉得自己代码写错了。

正解PC 输出的是与数据一致的马尔可夫等价类(CPDAG),很多方向本来就无法用纯观测数据区分。侦探报告里写“现有证据无法判定方向”不是办案失败,而是证据约束下的诚实上限;看到无向边,应该先怀疑“数据是否有方向信息”,而不是“程序是否有 bug”。

为何常见:教程为了直观,总用恰好能完全定向的小图做演示,让初学者误以为定向是必然结局。

误区条件独立性检验的 p 值越小,说明两个变量的因果关系越强,p 值可以被当作因果效应大小的指标。

正解p 值是“反对独立假设”的证据强度,与因果效应的大小毫无关系。测谎仪只能告诉你“这人没说实话”,不能告诉你他偷了多少钱。效应大小要用后续的因果效应估计方法(如干预计算)来量化,p 值只是删边决策的输入。

为何常见:统计检验的思维惯性——人们习惯把显著性理解为重要性,而忘了它只是“意外性”的度量。

误区既然条件集越大检验越严谨,不如直接从 d=3、d=5 开始做条件独立性检验,省去前面几轮。

正解PC 的精髓就是 d 从 0 逐阶推进,利用“删掉的边越多,后续候选条件集越小”的正反馈来压缩计算量。跳过低阶直接上高阶,会让原本低阶就能删除的边白白参与大量无谓检验,还可能因为有限样本的噪声把真实边误删。低阶通过是继续高阶的前提,这是算法设计的一部分而非偷懒。

为何常见:直觉上“考虑条件越多越严谨”,但忽略了统计检验的样本消耗和组合爆炸风险。

误区看到 X—Z—Y 且 X 和 Y 独立,就直接定向为 X→Z←Y。这口诀省事,应该没错。

正解还差一个决定性步骤——检查 Z 是否在 Sep(X,Y) 分离集中。如果 Z 在分离集里,说明控制 Z 之后 X、Y 才独立,此时它们是“被 Z 解释的间接独立”,不是对撞结构。这就像侦探发现 A、B 虽无交集,但要先确认 C 是否就是那个“不在场证人”,如果 C 是证人,三角关系就另当别论了。

为何常见:口诀只记住了前半句,把最关键的前提条件遗漏了,导致误定向。

误区α 就像收音机音量,调大一点,算法就能检到更多信息,因果图更准。

正解α 是“判独立”的门槛:α 越大,越容易判独立,删边越狠,骨架越稀疏,容易漏掉真实边;α 越小,判独立越保守,边删得少,容易把虚假相关当成因果。它是个权衡旋钮,需要结合样本量、变量噪声水平和领域知识来选择,绝非越大越好。

为何常见:把“显著性水平”误解为“检验灵敏度”,忽略了它本质上是假阳性率的上限控制。

面试题

Q1. PC算法属于哪一类因果发现方法?

单选题 · easy

  • A.基于约束的方法← 正确答案
  • B.基于深度学习的回归
  • C.基于专家规则的推理
  • D.基于时间序列的预测

答案:A。PC 算法通过条件独立性检验来约束候选因果结构,属于基于约束的方法。它不像深度学习那样拟合预测函数,也不依赖人工专家规则,而是用统计检验作为一票否决的证据。

Q2. PC算法正确的执行顺序是?

单选题 · mid

  • A.方向传播、骨架发现、v结构定向
  • B.骨架发现、v结构定向、方向传播← 正确答案
  • C.v结构定向、骨架发现、方向传播
  • D.骨架发现、方向传播、v结构定向

答案:B。PC 算法必须先用独立性检验把全连接图删成骨架,再用 v 结构确定第一批方向,最后用 Meek 规则补全剩余方向。方向传播依赖已定方向的 v 结构,v 结构定向依赖骨架,顺序不可颠倒。

Q3. 条件独立性检验得到 p>α 时,通常意味着什么?

单选题 · mid

  • A.两个变量一定强相关
  • B.没有足够证据拒绝独立性← 正确答案
  • C.两个变量存在因果关系
  • D.样本量一定不足

答案:B。p>α 表示在显著性水平 α 下没有足够证据推翻“独立”的零假设,因此 PC 算法会判它们独立并删除边。这类似测谎仪显示“没有说谎迹象”,不代表绝对清白,只是证据不足。

Q4. 分离集(Sepset)在PC算法中的核心作用是?

单选题 · mid

  • A.记录使两变量独立的条件集← 正确答案
  • B.记录使两变量相关的条件集
  • C.存放所有待检验的变量对
  • D.存放方向传播的最终结果

答案:A。骨架发现阶段每删除一条边,都会记录当时使两变量条件独立的变量集合,这就是分离集。在 v 结构定向时,需要判断中间节点是否属于分离集,因此它直接决定定向的正确性。

Q5. 一个合法的 v 结构 X→Z←Y 需要满足哪些条件?

多选题 · mid

  • A.X 和 Y 之间有直接因果边
  • B.X 和 Y 之间没有直接边← 正确答案
  • C.Z 不在分离集 Sep(X,Y) 中← 正确答案
  • D.Z 在分离集 Sep(X,Y) 中

答案:BC。v 结构的本质是 X 和 Y 相互独立却共同指向 Z,所以 X、Y 之间不能有直接边;同时 Z 不能出现在 X、Y 的分离集中,否则“独立”可以被 Z 解释,就不是真正的对撞结构。

Q6. Meek 规则 R1 的正确表述是?

单选题 · hard

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

答案:A。R1 描述的是“一条有向边加一条无向边”的传播:X 已指向 Y,若 Y—Z 无向且 X、Z 不相邻,则必须定向为 Y→Z。否则 Z→Y 会新增对撞子 X→Y←Z,与现有骨架信息矛盾。

Q7. PC算法输出 CPDAG(部分有向无环图)意味着什么?

单选题 · mid

  • A.所有边的方向都被数据唯一确定
  • B.部分无向边方向无法由数据唯一确定← 正确答案
  • C.骨架发现阶段一定出现了错误
  • D.样本量不足导致算法彻底失败

答案:B。CPDAG 是马尔可夫等价类的浓缩表示。当多个因果图拥有相同骨架和对撞结构时,它们在数据上不可区分,有争议的边就保留为无向。这是数据的信息上限,不是算法缺陷。

Q8. 以下哪项不是 PC 算法的输入参数?

单选题 · easy

  • A.观测数据矩阵
  • B.显著性水平 α
  • C.独立性检验方法
  • D.真实的因果图← 正确答案

答案:D。PC 算法是从数据中发现因果结构,输入自然是数据、显著性水平和检验方法。如果已经知道真实因果图,就等于考试前拿到了答案,不需要再“发现”了。

Q9. PC算法正常工作依赖的核心假设包括?

多选题 · hard

  • A.忠实性假设(数据反映真实结构)← 正确答案
  • B.马尔可夫条件(无路径则条件独立)← 正确答案
  • C.样本量足够大(统计检验有效)← 正确答案
  • D.变量必须全部服从正态分布

答案:ABC。忠实性假设保证统计独立性对应图中无路径,马尔可夫条件保证图中无路径对应统计独立性,二者互为表里;大样本保证检验可靠。D 错,因为 PC 也支持卡方检验等非参数独立性检验,不限于正态分布。

Q10. 当忠实性假设被违反时(如存在确定性函数关系),PC算法可能发生什么?

多选题 · extreme

  • A.错误保留虚假的依赖边← 正确答案
  • B.错误删除真实的因果边← 正确答案
  • C.定向结果产生偏差← 正确答案
  • D.输出结果完全正确不受影响

答案:ABC。忠实性违背意味着数据里的独立性与因果结构脱钩:确定性关系可能让真实因果边被误判为独立而删除,也可能让虚假依赖被保留,后续 v 结构定向自然跟着出错。它动摇的是 PC 删边逻辑的根基,D 显然不成立。