量化内在因果影响#
通过量化内在因果影响,我们回答了以下问题:
上游节点对目标节点的因果影响有多强,且这种影响并非继承自该上游节点的父节点?
自然地,下游节点对目标节点的内在因果影响为零。此方法基于以下论文:
Dominik Janzing, Patrick Blöbaum, Atalanti A Mastakouri, Philipp M Faller, Lenon Minorics, Kailash Budhathoki. Quantifying intrinsic causal contributions via structure preserving interventions Proceedings of The 27th International Conference on Artificial Intelligence and Statistics, PMLR 238:2188-2196, 2024
让我们考虑论文中的一个例子来理解这里衡量的影响类型。想象一个由三辆火车组成的时刻表:Train A
、Train B
和 Train C
。其中 Train C
的发车时间取决于 Train B
的到达时间,而 Train B
的发车时间取决于 Train A
的到达时间。假设 Train A
通常比 Train B
和 Train C
延误时间长得多。我们想回答的问题是:每辆火车对 Train C
延误的影响有多大?
虽然文献中有各种影响的定义,但我们关注的是内在因果影响,它衡量的是节点未从其父节点继承的影响,即节点噪声的影响。这样做的原因是,虽然 Train C
必须等待 Train B
,但 Train B
的延误主要继承自 Train A
。因此,Train A
应该被识别为对 Train C
的延误贡献最大的节点。
请参阅理解此方法部分,了解更多示例和详细信息。
如何使用#
为了演示该方法如何工作,我们按照上面的示例生成一些数据
>>> import numpy as np, pandas as pd, networkx as nx
>>> from dowhy import gcm
>>> X = abs(np.random.normal(loc=0, scale=5, size=1000))
>>> Y = X + abs(np.random.normal(loc=0, scale=1, size=1000))
>>> Z = Y + abs(np.random.normal(loc=0, scale=1, size=1000))
>>> data = pd.DataFrame(data=dict(X=X, Y=Y, Z=Z))
请注意 \(X\) 中“噪声”的较大标准差。
接下来,我们将使用 DoWhy 的 GCM 框架将因果关系建模为结构因果模型,并将其拟合到数据。此处,我们使用 auto 模块自动分配因果机制
>>> causal_model = gcm.StructuralCausalModel(nx.DiGraph([('X', 'Y'), ('Y', 'Z')])) # X -> Y -> Z
>>> gcm.auto.assign_causal_mechanisms(causal_model, data)
>>> gcm.fit(causal_model, data)
最后,我们可以询问祖先节点对感兴趣节点(例如 \(Z\))的内在因果影响。
>>> contributions = gcm.intrinsic_causal_influence(causal_model, 'Z')
>>> contributions
{'X': 8.736841722582117, 'Y': 0.4491606897202768, 'Z': 0.35377942123477574}
请注意,尽管此处我们使用了线性关系,但该方法也可以处理任意非线性关系。
解释结果: 我们估计了 \(Z\) 的祖先节点(包括其自身)对其方差(默认度量)的内在因果影响。这些贡献的总和等于 \(Z\) 的方差。正如我们在此所见,我们观察到 \(Z\) 方差的约 92% 来自 \(X\)。
理解此方法#
让我们看一个不同的示例,以进一步解释“内在”因果影响概念背后的直觉
组织了一场慈善活动,旨在为一家孤儿院募集资金。活动结束时,捐款箱被传递给每位参与者。由于捐款是自愿的,有些人可能出于各种原因不捐款。例如,他们可能没有现金。在这种情况下,一个仅仅将捐款箱传给其他参与者的人,最终并没有为集体捐款做出任何贡献。那么,每个人的贡献仅仅是他们捐赠的金额。
为了衡量源节点对目标节点的内在因果影响,我们需要一个函数因果模型。例如,我们可以假设每个节点的因果模型遵循加性噪声模型 (ANM),即 \(X_j := f_j (\textrm{PA}_j) + N_j\),其中 \(\textrm{PA}_j\) 是因果图中节点 \(X_j\) 的父节点,\(N_j\) 是独立的未观察到的噪声项。要计算 \(X_n\) 的祖先对其边缘分布的某个属性(例如方差或熵)的“内在”贡献,我们首先必须建立因果图,并从数据集中学习每个节点的因果模型。
考虑一个因果图 \(X \rightarrow Y \rightarrow Z\),如上面的代码示例所示,由以下 ANMs 诱导产生。
其中 \(N_w \sim \mathcal{N}(0, 1)\),对于所有 \(w \in \{X, Y, Z\}\),是标准正态噪声变量。
假设我们对每个变量对目标 \(Z\) 的方差的贡献感兴趣,即 \(\mathrm{Var}[Z]\)。如果没有噪声变量,那么所有贡献都可以归因于根节点 \(X\),因为所有其他变量都将是 \(X\) 的确定性函数。那么,每个变量对目标量 \(\mathrm{Var}[Z]\) 的内在贡献实际上就是相应噪声项的贡献。
为了计算“内在”贡献,我们还需要给定噪声变量子集 \(N_T\) 时 \(Z\) 的条件分布,即 \(P_{Z \mid N_T}\),其中 \(T \subseteq \{X, Y, Z\}\)。我们使用 ANM 来估计它们。为此,我们必须指定从噪声变量子集到目标的预测模型。下面,我们使用从噪声变量到 \(Z\) 的线性预测模型,量化 \(X, Y\) 和 \(Z\) 对 \(\mathrm{Var}[Z]\) 的内在因果影响。
>>> from dowhy.gcm.uncertainty import estimate_variance
>>> prediction_model_from_noises_to_target = gcm.ml.create_linear_regressor()
>>> node_to_contribution = gcm.intrinsic_causal_influence(causal_model, 'Z',
>>> prediction_model_from_noises_to_target,
>>> attribution_func=lambda x, _: estimate_variance(x))
在这里,我们在参数 attribution_func
中明确定义了方差作为我们感兴趣的属性。
注意
虽然使用方差作为不确定性估计器可以提供有关节点对目标平方偏差贡献的宝贵信息,但人们可能更感兴趣于其他量,例如绝对偏差。这也可以通过用自定义函数替换 attribution_func
来简单计算。
>>> mean_absolute_deviation_estimator = lambda x, y: np.mean(abs(x-y))
>>> node_to_contribution = gcm.intrinsic_causal_influence(causal_model, 'Z',
>>> prediction_model_from_noises_to_target,
>>> attribution_func=mean_absolute_deviation_estimator)
如果预测模型的选择不明确,预测模型参数也可以设置为“auto”。
关于使用均值进行归因的备注: 尽管 attribution_func
可以针对特定用例进行自定义,但并非所有定义都有意义。例如,使用均值不会产生任何有意义的结果。这是因为影响的估计方式基于 Shapley 值的概念。为了更好地理解这一点,我们可以看看 Shapley 值的一个通用属性,该属性表明 Shapley 值的总和(在我们的例子中是归因的总和)加起来等于 \(u(T) - u(\{\})\)。这里,\(u\) 是一个集合函数(在我们的例子中是 attribution_func
的期望),而 \(T\) 是所有参与者的完整集合(在我们的例子中是所有噪声变量)。
现在,如果我们使用均值,\(u(T)\) 就变成 \(\mathbb{E}_\mathbf{N}[\mathbb{E}[Y | \mathbf{N}]] = \mathbb{E}[Y]\),因为在图因果模型中,目标变量 \(Y\) 确定性地依赖于所有噪声变量 \(\mathbf{N}\)。类似地,\(u(\{\})\) 就变成 \(\mathbb{E}[Y | \{\}] = \mathbb{E}[Y]\)。这将导致 \(\mathbb{E}_\mathbb{N}[\mathbb{E}[Y | \mathbb{N}]] - \mathbb{E}[Y | \{\}] = 0\),即最终的归因接近于 0。更多详细信息,请参阅论文第 3.3 节。