想象你是业主,一套老房子的墙体施工图早已丢失。你请来装修队,却只能说清“家具怎么摆”:沙发靠东墙、书桌在南窗下、冰箱紧挨餐桌——而你想知道的是:墙在哪里,门朝哪边开?这就是 GES 算法面对的世界:手里只有观测数据(家具摆放),要反推变量间的因果结构(墙与门向)。 装修队的策略很朴素:先大胆试砌几堵墙(加边阶段),看居住体验(评分)是否变好;砌得差不多后,再回头拆掉让房子更拥挤的墙(删边阶段)。每一步都端着“改造评分表”(BIC 准则):改动让分涨就保留,让分跌就放弃。由于方案太多,装修队只能一堵一堵地试,这就是“贪婪”。 最后交付的不是唯一“定稿图”,而是一份“标准施工图”(CPDAG):能确定的墙画实线,方向未定的画虚线——纯观测数据下,有些方向天生无法区分。GES 的价值,正是在爆炸的候选结构中用“贪心 + 评分”逼近真实因果骨架。

岗位关联

对 AI 从业者而言,GES 是从观测数据自动学习因果结构的核心算法之一。掌握它能帮你把“相关关系”升级为“可验证的因果假设”,在因果推断、可解释 AI 和反事实分析岗位上获得核心竞争力。

目录
  1. 可视化演示
  2. 算法背景
  3. 等价类概念
  4. 评分函数
  5. 加边阶段
  6. 删边阶段
  7. 算法流程
  8. 优势局限
  9. 对比应用
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示GES贪婪等价搜索算法,从空图出发前向加边最大化BIC评分提升,再后向删边优化,在马尔可夫等价类CPDAG空间搜索最优因果结构。

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

GES贪婪等价搜索 前向加边:从空图出发,每步选BIC增益最大的Insert操作 后向删边:从当前图出发,每步选BIC增益最大的Delete操作 等价类搜索:在CPDAG空间搜索,保证评分等价性 GES: Empty → Insert(max BIC) → Delete(max BIC) → CPDAG GES在等价类空间贪婪搜索最优因果结构

算法背景

装修队接到的第一个任务是勘察现场。他们面对一间“只摆家具、不见墙体”的房间:沙发、书桌、冰箱、餐桌彼此挨着,看起来都有些关系——可到底是“隔墙相望”还是“同一房间”?业主只给了几百张不同角度的照片(样本数据),没有任何施工记录。

因果发现要做的,正是从这类观测数据中恢复变量间的因果图:每个变量是一个“房间”,每条边是一堵“墙”,箭头方向是“门只能朝哪边开”。难题有两个:其一,观测数据只能暴露相关性,而相关不等于因果;其二,候选墙体方案的数量实在太惊人,远远超出人力枚举的极限。

对 p 个变量,可能的 DAG 结构数量随 p 呈超指数爆炸。大到什么程度呢?就像几十个房间的豪宅,可能的墙体布置比宇宙中的原子还多,逐一遍历绝无可能。于是 GES 选择了一条务实的路:从某个初始方案出发,每次只改动一堵墙,并借助评分判断改动好坏——这种“只看眼前一步”的策略,正是贪婪搜索。

之所以必须“贪婪”,就是因为户型方案海实在太大。装修队不奢望一步到位,而是相信“每一步都变好一点,最后总不会太差”——这正是 GES 的设计哲学。

∣Sp∣≈2O(p2)\displaystyle |\mathcal{S}_p| \approx 2^{O(p^{2})}
公式告诉我们:候选因果图的数量随变量数爆炸增长,穷举不可行,必须靠贪心近似搜索。
  1. 结构空间全体候选 DAG 结构的数量级,决定了搜索的困难程度:
    ∣Sp∣|\mathcal{S}_p|
  2. 变量个数数据集中的变量数量,对应户型中待确定的“房间”数:
    pp
  3. 指数上界候选有向边总数在 p² 量级,每条边可取可不取,造成组合爆炸:
    O(p2)O(p^{2})

等价类概念

勘察几天后,装修队发现一个有趣的规律:有些墙体方案看起来完全不同,但住起来的采光和动线一模一样。比如客厅与书房之间的墙,无论门开在左边还是右边,只要“书房必须经过客厅才能到达”这一连通事实不变,居住体验就无法区分。

对应到因果图上:两个 DAG 只要骨架相同(把有向边全部变成无向边后的连通结构)、V 结构相同(A→B←C 这样“两头指向中间”的局部模式,且 A、C 之间没有边),它们在任意观测数据上产生的概率分布就完全相同——任何数据都无法把两者区分开。它们属于同一个马尔可夫等价类。

装修队从此不再纠结“门开左还是开右”:只要采光格局相同,就当同一类处理。最终交图时,把能确定方向的门画实线,把闹不清的画虚线——GES 输出的 CPDAG,就是这份诚实标注的“标准施工图”。

G1∼G2  ⟺  Sk(G1)=Sk(G2)  ∧  V(G1)=V(G2)\displaystyle G_{1} \sim G_{2} \iff \text{Sk}(G_{1}) = \text{Sk}(G_{2}) \;\land\; \text{V}(G_{1}) = \text{V}(G_{2})
等价类的意思是:两张 DAG 只要“底图连通”和“箭头对撞位置”一致,数据就拿它们没办法,必须把这类图捆在一起看待。
  1. 等价符号表示两个 DAG 在任意观测数据上产生相同分布,统计上不可区分:
    G1∼G2G_{1} \sim G_{2}
  2. 骨架一致把有向边全部改成无向边后,变量间的连通关系完全相同:
    Sk(G1)=Sk(G2)\text{Sk}(G_{1}) = \text{Sk}(G_{2})
  3. V 结构一致形如 A→B←C 的碰撞结构集合完全相同,这是方向能否确定的关键:
    V(G1)=V(G2)\text{V}(G_{1}) = \text{V}(G_{2})
  4. 同时满足两个条件必须同时成立,缺一不可,共同定义马尔可夫等价:
    ∧\land

评分函数

面对五花八门的墙体方案,装修队必须有一本统一的“改造评分表”,否则没法比较两堵新墙谁更值得砌。评分表回答两个问题:新方案住起来舒服吗?工程量大不大、成本高不高?

GES 使用的评分表是 BIC(贝叶斯信息准则,Bayesian Information Criterion)。它把“住得舒服”量化为对数似然:模型解释数据解释得越好,分越高;把“工程成本”量化为参数复杂度:图的边越多、条件概率表越庞大,扣分越狠。

BIC 属于“一致的”评分:样本量足够大时,真实等价类的评分永远不会被其他类压过。这一点是 GES 搜索能够收敛到正确方向的地基。加边与删边阶段,每走一步都在问同一句话:“这堵墙加(拆)之后,BIC 是涨还是跌?”

BIC(G∣D)=ln⁡P(D∣θ^,G)−d2ln⁡n\displaystyle \text{BIC}(G \mid \mathcal{D}) = \ln P(\mathcal{D} \mid \hat{\theta}, G) - \frac{d}{2} \ln n
BIC 的直觉是:一个方案的价值 = 它解释数据的本事 − 它自身复杂度的代价,两者相减得到最终评分,分越高越好。
  1. 对数似然模型在最优参数估计下解释观测数据的对数概率,反映拟合度:
    ln⁡P(D∣θ^,G)\ln P(\mathcal{D} \mid \hat{\theta}, G)
  2. 参数个数模型中全部条件概率分布的自由参数总数,反映结构复杂度:
    dd
  3. 惩罚力度样本量的对数乘以参数个数的一半,控制对复杂模型的扣分:
    d2ln⁡n\frac{d}{2} \ln n
  4. 作差求分用拟合收益减去复杂度代价,得到最终的 BIC 评分:
    BIC=拟合度−复杂度惩罚\text{BIC} = \text{拟合度} - \text{复杂度惩罚}

加边阶段

装修队第一阶段叫“大胆砌墙”:在任意两个房间之间试着砌一堵新墙、开一扇新门(加一条有向边),每砌一堵立刻掏出评分表打分。注意不能砌出环形走廊——一旦 A 房经 B 房到 C 房再绕回 A 房,户型就不再是合理分区(不允许成环)。

加边阶段是典型的贪心:遍历所有候选边,分别计算“加上这条边后的 BIC 增量 ΔBIC”,挑出增量最大的那一条真正加上,然后重复这个过程。

当所有候选边带来的最大增量都不超过零时,加边阶段终止。此时图停在了一个“评分小山丘”上——但尚未登顶之前,谁也不知道这座山丘是不是最高的。就像装修队一口气砌了十几堵墙,偶尔也会砌错一堵:单看涨分,等整体格局定下来却成了采光杀手。别担心,第二阶段的“拆墙工”正在进场。

ΔSe+=BIC(G∪{e})−BIC(G)>0\displaystyle \Delta S_{e^{+}} = \text{BIC}(G \cup \{e\}) - \text{BIC}(G) > 0
加边被接受的条件:这条候选边能让新图的 BIC 评分比旧图更高,才会被真正砌上。
  1. 候选边当前图中尚未出现的一条有向边,即“待砌的新墙”:
    ee
  2. 增广图假设把候选边加入后得到的新因果图:
    G∪{e}G \cup \{e\}
  3. 评分增量新图与旧图 BIC 评分的差值,衡量这一步改动值不值得:
    ΔSe+=BIC(G∪{e})−BIC(G)\Delta S_{e^{+}} = \text{BIC}(G \cup \{e\}) - \text{BIC}(G)
  4. 接受条件只有增量大于零的边才会被采纳,否则放弃该候选:
    >0> 0

删边阶段

砌墙阶段结束后,装修队并未交工,而是退后三步重新审视整张平面图:有些墙当初“单独看”是涨分的,但放进整体格局后反而成了累赘。于是“细心拆墙”开始:把每条已砌的墙轮流拆掉,看评分怎么变。

删边阶段同样贪心:对当前图中每条已存在的边 e,计算删除后的评分增量;如果删除某条边后 BIC 反而上升,就说明它是冗余边——拆!每轮只拆掉“删后增益最大”的那条边,直到拆任何边都不能让评分变得更好为止。

删边有个漂亮的理论性质:加边与删边的评分变化互为相反数——先把边 e 砌上再拆掉,收益正好抵消。这保证删边阶段不会把搜索“带偏”,而是对加边阶段留下的局部最优点做精细修正,在评分一致条件下能收敛到全局最优等价类。拆墙工人每拆一堵都要反问评分表:“拆了它,住得更舒服吗?”直到拆无可拆,装修队才确信自己真正站在了评分山顶。

e∗=arg⁡max⁡e∈E  [BIC(G∖{e})−BIC(G)]\displaystyle e^{*} = \arg\max_{e \in E} \; \left[ \text{BIC}(G \setminus \{e\}) - \text{BIC}(G) \right]
删边阶段的目标是找出“拆掉后更值”的边:删除冗余边能减少参数,让惩罚项变小,整体评分上升。
  1. 现有边集当前图中所有已存在的有向边,即“已砌的墙”:
    EE
  2. 删除候选从当前图中临时去掉一条边后得到的新图:
    G∖{e}G \setminus \{e\}
  3. 删边增益删除后的评分减去删除前的评分,正值表示拆得值:
    BIC(G∖{e})−BIC(G)\text{BIC}(G \setminus \{e\}) - \text{BIC}(G)
  4. 贪心选择遍历整个边集,选增益最大且为正的边优先拆除:
    e∗=arg⁡max⁡e∈E(⋅)e^{*} = \arg\max_{e \in E} (\cdot)

算法流程

把整场装修拍成一部短片:清晨的空房子(空图)→ 工人们来回比划、砌墙(加边阶段)→ 监理退后打量、下令拆墙(删边阶段)→ 最后交出一张标准施工图(CPDAG)。

GES 的完整流程是:第一步,从一个没有边的初始 DAG 出发;第二步,反复执行加边阶段,每次添加“能让 BIC 增量最大且不形成环”的边,直到无法再提分;第三步,反复执行删边阶段,每次删除“能让 BIC 提升最大”的边,直到无法再提分;第四步,把最终的 DAG 骨架与可定向的 V 结构整理成 CPDAG 输出。

两个阶段各自的终止条件完全对称——没有任何单边操作能继续提升评分:

从复杂度看,每轮都要枚举 O(p²) 个候选操作,每个候选做 O(n) 量级的评分拟合,整体开销约 O(k·p²·n)。如此简单的“导演指令”——涨分就动、不涨就停——却能从天文数字级的户型方案里摸出一张靠谱施工图,这正是贪心策略的力量。

max⁡e∈CΔSe≤0  ⇒  当前阶段收敛\displaystyle \max_{e \in \mathcal{C}} \Delta S_{e} \leq 0 \;\Rightarrow\; \text{当前阶段收敛}
收敛条件很简洁:连“最好的单步改动”都不再带来正收益,说明当前已经爬到局部山顶,可以切换阶段或结束搜索。
  1. 候选操作集当前可执行的全部加边或删边操作:
    C\mathcal{C}
  2. 最大增益所有候选操作中最大的评分变化值:
    max⁡e∈CΔSe\max_{e \in \mathcal{C}} \Delta S_{e}
  3. 非正判断最大增益都不超过零,说明任何改动都不再有益:
    ≤0\leq 0
  4. 阶段收敛触发该条件后,当前阶段停止并进入下一阶段:
    ⇒收敛\Rightarrow \text{收敛}

优势局限

对比别的装修队,这套“先砌后拆”的打法优势明显:它不会一上来就拍脑袋拆掉一堆墙(像某些只做局部检验的队伍),而是先充分探索再回头修正,全局风险更小。更重要的是,它的评分表有统计保证。

当样本量足够大时,GES 能以概率 1 收敛到真实的因果等价类——这个性质称为一致性。换句话说,只要数据够多、模型假设合理,它几乎不会把因果骨架搞错。

局限同样真实:一是计算开销大,变量增多后候选枚举与评分拟合相当昂贵;二是贪心搜索可能陷入局部最优,数据有噪声时可能停在某个“小山顶”;三是最本质的一点——等价类内部的方向,仅靠观测数据永远无法区分。装修队不是神:它能确定哪些墙存在、哪些门确定朝向,但对那些“怎么画都说得通”的虚线墙,只能诚实标注“待业主砸墙验证”。这份诚实,正是因果发现与单纯相关性建模的最大区别。

lim⁡n→∞P(E^n=E∗)=1\displaystyle \lim_{n \to \infty} P(\hat{\mathcal{E}}_n = \mathcal{E}^{*}) = 1
一致性定理:随着样本量趋向无穷,GES 输出的等价类与真实等价类相等的概率趋近于 1,这是它比许多启发式方法更可靠的原因。
  1. 样本量观测数据包含的记录条数,越多提供的信息越充分:
    nn
  2. 估计结果基于 n 条样本由 GES 学到的因果等价类:
    E^n\hat{\mathcal{E}}_n
  3. 真实答案数据背后真实存在的那一个因果等价类:
    E∗\mathcal{E}^{*}
  4. 概率极限n 趋向无穷时,估计结果等于真实答案的概率趋近于 1:
    lim⁡n→∞P(⋅)=1\lim_{n \to \infty} P(\cdot) = 1

对比应用

装修市场上还有一支风格迥异的队伍——PC 施工队。它不拿评分表,而是拿着“探测器”:对每一对房间做条件独立性检测,凡是被证明“中间隔着墙”(条件独立)的,直接拆掉候选墙;最后再根据 V 结构确定门朝哪边开。

PC 算法的思路是“由全到空”:从完全图开始逐步删边;GES 的思路是“由空到满再回头”:从空图开始逐步加边再删边。两者方法论不同:PC 依赖独立性检验的先后次序,误差可能累积;GES 依赖评分的一致性,对搜索路径更鲁棒,但计算量更大、更慢。

工程上,causal-learn、pcalg、TETRAD 都内置了 GES 与 PC 的实现,可直接调用。选哪支队伍?户型小、时间紧、数据质量高,PC 更省时;户型大、追求统计保证、算力充裕,GES 更稳妥。聪明的团队常常让两支队伍互相印证——它们都说“有墙”的地方,才真正值得砸墙验证。

TGES≈O(k⋅p2⋅n)\displaystyle T_{\text{GES}} \approx O(k \cdot p^{2} \cdot n)
复杂度对比:GES 总开销近似为候选数(p²)、样本量(n)与迭代轮数(k)的乘积;PC 的检验次数也在 p² 量级,但单次检验通常比评分拟合便宜,小数据上更轻快。
  1. 候选数量每轮迭代要枚举的加边/删边候选在 p² 量级:
    O(p2)O(p^{2})
  2. 样本遍历每次评分计算需要扫过 n 条样本做回归拟合:
    nn
  3. 迭代轮数加边与删边两阶段的循环次数,通常远小于变量数:
    kk
  4. 总开销三者相乘给出整体的渐进计算复杂度:
    O(k⋅p2⋅n)O(k \cdot p^{2} \cdot n)

AI 应用场景

金融风控中的可解释归因

阶段:可解释建模 / 因果特征工程

解决的问题:传统评分卡或树模型只能输出违约概率,说不清“为什么”。GES 从用户历史行为与征信观测数据中学习变量间的因果骨架,把“收入低→负债率高→逾期”这类因果链条显式画出来,为模型决策提供可复核的解释。

具体案例:某消费金融公司对 30 个特征、约 200 万样本运行 GES,输出 CPDAG 显示“负债率”是“逾期”的直接父节点,而“年龄”只是经由“收入”间接影响逾期。风控团队据此把负债率纳入贷前硬规则,拒绝率下降 12% 且坏账率没有上升。

电商转化瓶颈的因果诊断

阶段:归因分析 / 数据驱动决策

解决的问题:转化率下滑时,业务方常靠经验猜原因,容易把相关变量当成因果变量。用 GES 从埋点日志中学习“页面加载时长、跳出率、加购率、转化率”的因果图,定位真正的因变量。

具体案例:某电商团队对过去 90 天漏斗数据跑 GES,发现“加载时长→跳出率→转化率”是一条直达因果路径,而“促销位数量”与转化率只是相关关系。优化加载速度后转化率回升了 8%。

医学观察性研究的混杂识别

阶段:观察性数据分析 / 混杂控制

解决的问题:电子病历数据不允许随机实验,药物与疗效之间的因果关系常被年龄、并发症等混杂因素干扰。GES 帮助研究者先学习整个变量系统的因果图,再决定统计模型需要调整哪些变量。

具体案例:在回顾性队列研究中,用 GES 对“用药剂量、肝功能指标、年龄、转归”建模,CPDAG 提示“肝功能”是“用药”与“转归”的共同祖先,需纳入调整集,从而避免了混杂偏倚。

Python 演示

python120 行
import itertoolsimport numpy as np  def bic_score(data, parents, node):    """node 在给定父节点集合下的 BIC 局部评分"""    n = data.shape[0]    y = data[:, node]    if not parents:        X = np.ones((n, 1))        beta = np.array([y.mean()])    else:        X = np.column_stack([np.ones(n)] + [data[:, p] for p in parents])        beta, _, _, _ = np.linalg.lstsq(X, y, rcond=None)    resid = y - X @ beta    sigma2 = resid @ resid / n    ll = -n / 2 * (np.log(2 * np.pi * sigma2) + 1)    num_params = len(parents) + 1    return ll - num_params / 2 * np.log(n)  class GES:    """简化版 GES: 交替执行加边与删边, 直到评分无法提升"""     def __init__(self, data):        self.data = data        self.num_vars = data.shape[1]        self.adj = np.zeros((self.num_vars, self.num_vars), dtype=int)     def dag_ok(self, adj):        """拓扑排序判环: 能访问所有节点即为 DAG"""        indeg = adj.sum(axis=0)        stack = [i for i in range(self.num_vars) if indeg[i] == 0]        visited = 0        while stack:            u = stack.pop()            visited += 1            for v in range(self.num_vars):                if adj[u, v]:                    indeg[v] -= 1                    if indeg[v] == 0:                        stack.append(v)        return visited == self.num_vars     def total_score(self, adj):        """整图的 BIC 评分按变量分解求和"""        score = 0.0        for v in range(self.num_vars):            pa = [u for u in range(self.num_vars) if adj[u, v]]            score += bic_score(self.data, pa, v)        return score     def run(self):        improved = True        while improved:            improved = False            base = self.total_score(self.adj)            best_gain = 0.0            best_adj = None            # 加边阶段: 枚举所有候选有向边            for u, v in itertools.permutations(range(self.num_vars), 2):                if self.adj[u, v]:                    continue                cand = self.adj.copy()                cand[u, v] = 1                if not self.dag_ok(cand):                    continue                gain = self.total_score(cand) - base                if gain > best_gain:                    best_gain = gain                    best_adj = cand            if best_adj is not None:                self.adj = best_adj                improved = True            base = self.total_score(self.adj)            best_gain = 0.0            best_adj = None            # 删边阶段: 拆掉让评分提升的冗余边            for u, v in itertools.permutations(range(self.num_vars), 2):                if not self.adj[u, v]:                    continue                cand = self.adj.copy()                cand[u, v] = 0                gain = self.total_score(cand) - base                if gain > best_gain:                    best_gain = gain                    best_adj = cand            if best_adj is not None:                self.adj = best_adj                improved = True        return self.adj  def generate_data(n_samples=500, seed=42):    """合成线性高斯数据: 真实因果结构为 0->2, 1->2, 2->3"""    rng = np.random.default_rng(seed)    data = rng.normal(size=(n_samples, 4))    data[:, 2] = 0.8 * data[:, 0] + 0.7 * data[:, 1] + rng.normal(scale=0.3, size=n_samples)    data[:, 3] = 0.9 * data[:, 2] + rng.normal(scale=0.3, size=n_samples)    return data  def main():    data = generate_data()    model = GES(data)    learned = model.run()    true = np.zeros((4, 4), dtype=int)    true[0, 2] = true[1, 2] = true[2, 3] = 1    sk_true = (true + true.T) > 0    sk_learned = (learned + learned.T) > 0    match = int(np.sum(sk_true & sk_learned))    print("真实结构(行->列表示因果边):")    print(true)    print("GES 学习到的结构:")    print(learned)    print("骨架匹配边数: %d / 3" % match)  if __name__ == "__main__":    main()
目的:用 BIC 评分实现简化版 GES,在合成线性高斯数据上从空图出发自动学习因果结构,并输出骨架匹配数。
  1. return ll - num_params / 2 * np.log(n)这就是 BIC 的“拟合减去惩罚”:惩罚项随参数个数和样本量增长,防止模型过度复杂。
  2. stack = [i for i in range(self.num_vars) if indeg[i] == 0]拓扑排序的起点:找所有“没有入边”的节点,为无环校验做准备。
  3. cand = self.adj.copy(); cand[u, v] = 1加边阶段构造候选图:临时添上一条边,但还没决定是否保留。
  4. if not self.dag_ok(cand): continue无环约束:砌墙不能砌出环形走廊,否则候选直接作废。
  5. gain = self.total_score(cand) - base计算单步评分增益,对应正文中的 ΔBIC。
  6. if gain > best_gain贪心决策:只记住当前增益最大的候选边。
  7. data[:, 2] = 0.8 * data[:, 0] + 0.7 * data[:, 1] + ...按已知 DAG 合成数据,让“真实答案”可知,便于验证学习效果。

常见误区

误区GES 输出的 CPDAG 里画着箭头,应该所有边的方向都确定了,可以直接拿去做因果解释了吧?

正解CPDAG 中只有 V 结构及其传递方向是确定的,其余边用虚线表示方向未定。同属一个等价类的 DAG 在观测数据上完全不可区分。就像装修图上画虚线的墙,在没有砸墙验证(干预)之前,谁也说不准门到底朝哪边开。

为何常见:看到箭头就默认方向确定,忽略了 CPDAG 中实线与虚线的语义差别。

误区BIC 评分高说明这张图与真实因果关系非常接近,可以放心用它做决策。

正解BIC 衡量的是“拟合度 − 复杂度”,是统计模型选择标准,不是因果正确性的裁判。一张高分图可能在因果方向上完全相反(例如等价类内 X→Y 与 Y→X 评分相同)。装修评分高只说明“住着顺眼且不超预算”,不代表墙的承重布局真的合理。

为何常见:把评分等同于因果真理,忽略了等价类中方向不可识别这一根本事实。

误区既然叫加边阶段,就一条一条加,把所有能加的边都加上,最后再删。

正解加边阶段是贪婪的:每步只加“评分增益最大”的一条边,只要最大增益 ≤ 0 就停止。大多数候选边加进来反而掉分,根本不会被选中。装修队不会把每面墙都砌上——有的墙砌了采光更差,评分表会立刻喊停。

为何常见:把“贪婪”误解为“统统都要”,事实上贪心恰恰是“择优录取、见好就收”。

误区GES 和 PC 都是因果发现算法,数据足够时它们输出的 CPDAG 应该一模一样。

正解GES 是评分搜索法,PC 是基于条件独立检验的约束法,理论基础不同,有限样本下对同一数据可能输出不同的 CPDAG。即便数据无限大且都一致,也只是“收敛到同一等价类”,不代表每一步细节相同。

为何常见:以为所有因果发现算法最终答案相同,忽视了方法论差异和有限样本带来的不确定性。

误区只要样本量足够大,GES 就一定能找到所有边和所有边的方向。

正解观测数据只能把因果结构识别到等价类级别。等价类内部的方向、以及未观测混杂导致的反向因果,是数据的物理极限。GES 的一致性结论说的是“收敛到真实等价类”,而不是“唯一确定每个方向”。想定方向,必须引入干预实验或领域知识。

为何常见:对一致性定理的误读:一致收敛 ≠ 全部方向可识别。

面试题

Q1. GES 算法的两个阶段顺序是什么?

单选题 · easy

  • A.先删边再加边两阶段
  • B.先加边再删边两阶段← 正确答案
  • C.同时加边和删边同步
  • D.只加边不需要删边

答案:B。GES 是“先加边后删边”的两阶段算法:加边阶段从空图出发大胆加边,删边阶段再回头剔除冗余边,最终以收敛的 CPDAG 输出。A、C、D 都与 GES 的两阶段设计不符,故选 B。

Q2. GES 中“等价类”指的是什么?

单选题 · easy

  • A.骨架与 V 结构一致的 DAG 族← 正确答案
  • B.评分完全相同的一批变量集
  • C.边数完全相等的 DAG 集合
  • D.条件概率全部相同的图组

答案:A。马尔可夫等价类的定义是“骨架相同且 V 结构相同”,这样的多个 DAG 在观测数据上不可区分。B 混淆了评分与等价类,C 忽略了方向和 V 结构,D 说的是参数而非结构,故选 A。

Q3. BIC 评分公式由哪两部分构成?

单选题 · mid

  • A.似然项加上谱聚类惩罚项
  • B.似然项减去参数复杂度惩罚项← 正确答案
  • C.先验概率乘以似然函数项
  • D.误差平方和加上常数项

答案:B。BIC 由对数似然项与复杂度惩罚项组成:拟合越好分越高,参数越多扣分越多,二者相减得到总分。A 的谱聚类、C 的先验、D 的误差平方和都不属于 BIC 的构成项,故选 B。

Q4. GES 加边阶段如何选择要加的边?

单选题 · mid

  • A.任选一条无环候选边即可
  • B.选择无环且评分增益最大的边← 正确答案
  • C.选择边数最少的无环候选边
  • D.随机抽一条边评估一次就加

答案:B。加边阶段每一步遍历所有候选边,做无环校验后选择“评分增益最大”的边添加,是典型的贪心策略。A、D 太随意,C 与评分无关,故选 B。

Q5. GES 相比 PC 算法最重要的特点是?

单选题 · mid

  • A.基于评分的全局等价类搜索← 正确答案
  • B.基于条件独立检验快速删边
  • C.不需要对数据做任何统计假设
  • D.一定能输出唯一确定的方向

答案:A。GES 是基于评分的搜索方法,在等价类空间上贪心加边删边,靠 BIC 一致性保证统计收敛;PC 则是基于条件独立的约束法。B 描述的是 PC,C 不成立(仍需可分解一致的评分假设),D 错在方向并非全部可识别,故选 A。

Q6. 删边阶段为什么有时删边反而涨分?

单选题 · hard

  • A.删边后参数变少惩罚项减小← 正确答案
  • B.删边后样本量自动增大了
  • C.删边后数据拟合度必然提高
  • D.删边后评分公式变化规律了

答案:A。删边看似“少了一堵墙”,但参数减少使 BIC 的惩罚项变小;若被删边原本就是冗余的,拟合损失很小,整体评分反而上升。B、C、D 都不符合 BIC 构成原理,故选 A。

Q7. CPDAG 中包含哪些信息?

多选题 · mid

  • A.变量之间的邻接骨架← 正确答案
  • B.一部分确定方向的边← 正确答案
  • C.每个变量的完整参数表
  • D.所有可能的边方向组合

答案:AB。CPDAG 保留骨架(无向底图)和已确定方向的边(来自 V 结构与传递),虚线边表示方向未定。它不包含参数表(C 错),也不枚举所有方向的组合(D 错),故选 A、B。

Q8. 下列哪些属于 GES 的局限性?

多选题 · hard

  • A.可能陷入局部最优解← 正确答案
  • B.无法区分等价类内方向← 正确答案
  • C.可以完美处理隐藏混杂
  • D.候选操作枚举开销较大← 正确答案

答案:ABD。GES 作为贪心搜索可能陷入局部最优;等价类内部方向不可识别;候选边枚举与评分拟合开销大。它并不能处理未观测混杂(C 错),故选 A、B、D。

Q9. GES 最终输出结果是什么形式?

单选题 · easy

  • A.一个完全有向的 DAG
  • B.一张部分有向无环图← 正确答案
  • C.一份条件独立性列表
  • D.一组评分值分布表

答案:B。GES 输出对应等价类的 CPDAG:能确定方向的边画实线,不能确定的画虚线,而不是某个唯一的完全有向 DAG。A、C、D 均不符合输出形式,故选 B。

Q10. 哪些情形会降低 GES 学习结果的准确性?

多选题 · hard

  • A.样本量过小导致评分不稳← 正确答案
  • B.存在未观测的混杂变量← 正确答案
  • C.变量间关系高度非线性← 正确答案
  • D.数据集变量名称不规范

答案:ABC。样本过小使评分不稳定,未观测混杂破坏因果可识别性,非线性关系超出线性评分假设,都会降低准确率。变量名称不影响学习结果(D 错),故选 A、B、C。