3. 基于约束的算法(PC算法):PC算法原理、条件独立性检验、骨架学习与方向确定
大家好,今天我们来聊聊因果发现里一个非常经典的算法——PC算法。说实话,我刚入行那会儿,面对一堆变量,最头疼的就是怎么理清它们之间的因果关系。后来接触了PC算法,才算是找到了一个靠谱的切入点。
PC算法全称是Peter-Clark算法,名字取自两位作者。它属于基于约束的因果发现方法。说白了,它的核心思想就是:利用条件独立性检验,一步步剔除不相关的边,最后确定因果方向。你想想看,如果两个变量在给定其他变量后仍然相关,那它们之间很可能有直接的因果联系。
核心逻辑一句话:先找骨架(无向图),再定方向(有向图)。
3.1 PC算法的基本原理
PC算法的流程其实很直观。我个人习惯把它拆成两大步:
- 骨架学习:从完全无向图开始,通过条件独立性检验,逐步删除不相关的边。
- 方向确定:利用V结构(collider)和传播规则,给边赋予方向。
嗯,这里要注意,PC算法有一个重要假设:因果充分性——即所有共同原因都被观测到了。如果存在隐藏的混淆变量,PC算法可能会出问题。我在项目中遇到过这种情况,后面会细说。
下面这张图展示了PC算法的整体流程,我特意画了张SVG图,方便你理解:
3.2 条件独立性检验
条件独立性检验是PC算法的基石。说白了,就是问一个问题:给定变量集Z,X和Y还独立吗?
常用的检验方法有几种,我整理了一个表格:
| 数据类型 | 常用检验方法 | 适用场景 |
|---|---|---|
| 连续变量 | 偏相关系数 + Fisher Z检验 | 假设数据服从多元正态分布 |
| 离散变量 | G²检验 / 卡方检验 | 列联表分析,样本量要够大 |
| 混合类型 | 条件互信息 + 置换检验 | 非参数方法,计算量较大 |
我在项目中遇到过一个问题:数据明明是连续的,但分布严重偏态。这时候用偏相关系数就不太靠谱了。我建议你遇到这种情况,先做一下正态性检验,不行就换非参数方法。
小技巧:条件独立性检验的显著性水平α怎么选?我个人习惯从0.05开始,如果样本量小(比如少于200),可以放宽到0.1。但别太大,否则假阳性会很多。
3.3 骨架学习
骨架学习的流程是这样的:
- 从一个完全无向图开始,所有变量两两相连。
- 设置条件集大小k=0,检验所有相邻节点对的条件独立性。
- 如果X和Y在给定某个条件集Z时独立,就删除X-Y之间的边。
- 逐步增大k,重复检验,直到没有边可删或k达到上限。
嗯,这里有个关键点:条件集Z只能从X和Y的邻居节点中选。为什么?因为如果Z包含非邻居节点,可能会引入虚假的独立性。我记得有一次调试代码,发现结果总是不对,查了半天才发现是条件集选错了。
下面是一个简单的Python伪代码示例,帮你理解骨架学习的逻辑:
def skeleton_learning(data, alpha=0.05):
# 初始化完全无向图
nodes = data.columns
graph = {n: set(nodes) - {n} for n in nodes}
k = 0
while True:
edges_to_remove = []
for x in nodes:
for y in graph[x]:
if y <= x: # 避免重复
continue
# 找邻居集
neighbors = graph[x] & graph[y]
if len(neighbors) < k:
continue
# 遍历所有大小为k的条件集
for cond_set in combinations(neighbors, k):
p_value = conditional_independence_test(data, x, y, cond_set)
if p_value > alpha:
edges_to_remove.append((x, y))
break
# 删除边
for x, y in edges_to_remove:
graph[x].remove(y)
graph[y].remove(x)
if not edges_to_remove:
k += 1
if k > max_degree(graph):
break
return graph
避坑指南:我曾经在骨架学习阶段踩过一个坑——条件集大小k增长太快。如果数据维度高,k=2或3时计算量就爆炸了。建议设置一个最大条件集大小,比如不超过5。另外,样本量太少时,高阶条件独立性检验的可靠性会急剧下降。
3.4 方向确定
骨架学习完成后,我们得到的是一个无向图。接下来要确定边的方向。PC算法主要靠两个机制:
3.4.1 V结构(Collider)识别
V结构是指 X → Y ← Z 这样的模式。怎么识别?看骨架中是否存在 X—Y—Z 这样的路径,且X和Z在骨架中不相连。如果X和Z在给定Y时变得相关(即条件独立检验不成立),那么Y就是一个collider。
举个例子:假设我们有三个变量——下雨(R)、洒水器(S)、草地湿(W)。骨架中R—W—S相连,但R和S不相连。如果给定W时R和S变得相关,那么W就是collider,方向为 R → W ← S。
3.4.2 方向传播规则
确定了V结构后,剩下的边怎么定方向?PC算法使用以下规则:
- 规则1:如果存在 X → Y — Z,且X和Z不相邻,则定向为 Y → Z(避免产生新的V结构)。
- 规则2:如果存在 X → Y → Z,且X — Z存在,则定向为 X → Z(避免产生环)。
- 规则3:如果存在 X — Y — Z,且存在X → W ← Z,W是Y的邻居,则定向为 Y → Z。
这些规则说白了就是:不能产生环,不能产生新的V结构。我刚开始学的时候觉得这些规则很绕,后来写代码实现了一遍,发现其实就是几个if-else判断。
重要提醒:PC算法只能识别到马尔可夫等价类。什么意思?就是有些方向是确定不了的,比如 X → Y 和 X ← Y 在统计上可能无法区分。这时候算法会保留无向边,你需要用领域知识或干预实验来进一步确定。
3.5 实际应用中的注意事项
最后,分享几个我在实际项目中积累的经验:
- 样本量问题:PC算法对样本量要求较高。我建议连续变量至少200个样本,离散变量至少500个。样本太少时,条件独立性检验的效力很低。
- 高维数据:变量数超过50时,PC算法的计算量会急剧上升。可以考虑先用特征选择降维,或者使用并行版本(如PC-stable算法)。
- 缺失值处理:PC算法本身不支持缺失值。我习惯先做插补,或者用基于似然的方法处理。
- 结果验证:拿到因果图后,一定要用领域知识验证一下。我曾经跑出一个结果,显示“温度影响冰淇淋销量”,但常识告诉我其实是“温度同时影响两者”。后来发现是遗漏了混淆变量。
好了,PC算法的核心内容就这些。说白了,它就是一个“先删边、再定向”的过程。理解了这个逻辑,你就能在实际项目中灵活运用了。
公众号:蓝海资料掘金营,微信deep3321