因果人工智能——构建因果图模型

这章内容包括:

  • 构建用于模拟数据生成过程(DGP)的因果有向无环图(causal DAG)
  • 将因果图用作沟通、计算和推理的工具
  • 使用 pgmpy 和 Pyro 构建因果 DAG
  • 利用因果 DAG 作为骨架训练概率机器学习模型

本章将用因果有向无环图(causal DAG)构建我们第一个数据生成过程(DGP)模型。因果 DAG 是一种没有环路的有向图,其中边表示因果关系。我们还将探讨如何利用因果 DAG 作为骨架训练统计模型。

3.1 因果 DAG 介绍

假设我们可以将数据生成过程(DGP)拆分为一组变量,变量值的组合表示 DGP 的一种可能状态。这些变量可以是离散的或连续的,可以是单变量,也可以是多变量向量或矩阵。

因果 DAG 是一个有向图,节点是这些变量,边则表示它们之间的因果关系。使用因果 DAG 表示 DGP 时,我们假设图中的边反映了 DGP 中的真实因果关系。

举例说明,回顾第二章的"投掷石块"数据生成过程。开始时,Jenny 和 Brian 分别有一定倾向去朝窗户投掷石块,窗户也有一定强度。如果任一人的倾向超过阈值,他们就会投掷。窗户是否破碎,取决于他们是否投掷以及窗户的强度。

环境配置说明

本章示例代码基于 pgmpy 0.1.24、pyro-ppl 1.8.6 和 DoWhy 0.11.1 版本。Python 的 Graphviz 库版本为 0.20.1,用于绘制 DAG 图像,使用时需安装 Graphviz 核心软件。如果暂时不想安装 Graphviz,可注释相关代码。

详细代码的 Jupyter 笔记本请参考书中链接:www.altdeep.ai/p/causalaib...

我们现在创建一个因果 DAG 来可视化这个过程。以下是表示数据生成过程的 Python 函数示例:

ini 复制代码
def true_dgp(jenny_inclination, brian_inclination, window_strength):     #1
    jenny_throws_rock = jenny_inclination > 0.5     #2
    brian_throws_rock = brian_inclination > 0.5   #2
    if jenny_throws_rock and brian_throws_rock:     #3
        strength_of_impact = 0.8    #3
    elif jenny_throws_rock or brian_throws_rock:     #4
        strength_of_impact = 0.6    #4
    else:     #5
        strength_of_impact = 0.0   #5
    window_breaks = window_strength < strength_of_impact     #6
    return jenny_throws_rock, brian_throws_rock, window_breaks
#1 输入变量为 0 到 1 之间的数值。
#2 Jenny 和 Brian 如果有倾向就投掷石块。
#3 如果两人都投掷,冲击强度为 0.8。
#4 如果其中一人投掷,冲击强度为 0.6。
#5 如果都不投掷,冲击强度为 0。
#6 当冲击强度大于窗户强度时,窗户破碎。

图 3.1 用因果 DAG 形象地展示了这个投掷石块的数据生成过程。在图中,每个节点代表数据生成过程中的一个随机变量,有向边代表因果关系(起始节点为原因,指向节点为结果)。

3.1.1 案例研究:交通方式的因果模型

本章中,我们将研究人们日常通勤交通方式选择的模型。这个例子会做出过于强烈(甚至近乎冒犯性)的假设,但这些假设有助于阐明模型构建的核心理念。相关代码和教程链接请见:www.altdeep.ai/p/causalaib...

假设你是一名城市规划顾问,试图模拟人们的人口统计背景、居住城市规模、职业状况与每天选择通勤方式之间的关系。

你可以将系统中的关键变量拆解为:

  • 年龄(A)--- 个体的年龄
  • 性别(S)--- 个体报告的性别(这里用"S"代替"G",因为"G"通常用作DAG中的节点)
  • 教育水平(E)--- 个体完成的最高教育或培训程度
  • 职业(O)--- 个体的职业
  • 居住地(R)--- 个体所在城市的规模
  • 交通方式(T)--- 个体偏好的出行方式

然后,基于领域知识,你可以思考这些变量之间的因果关系,可能的叙述如下:

  • 不同代际的教育标准不同。对于老年人来说,高中学历就足以获得中产阶级生活水平,但年轻人至少需要大学学历才能达到同样水平。因此,年龄(A)是教育水平(E)的原因。
  • 性别通常影响个体是否选择追求更高学历,因此性别(S)是教育水平(E)的原因。
  • 许多白领工作要求较高学历。比如医生、律师、会计等职业肯定需要较高教育水平。因此,教育水平(E)是职业(O)的原因。
  • 依赖高学历的白领职业往往集中在城市地区,因此教育水平(E)也是居住地(R)的原因。
  • 自雇者可能在家工作,因此不需要通勤;而有雇主的人则需要通勤。因此职业(O)是交通方式(T)的原因。
  • 大城市的人可能更倾向于步行或使用公共交通通勤,而小城镇的人则更多依赖汽车。因此居住地(R)是交通方式(T)的原因。

你可以根据对领域的理解或研究形成上述叙述,也可以咨询该领域的专家,比如专门研究该问题的社会学家。最终,这段叙述可以简化成图3.2所示的因果有向无环图(causal DAG)。

你可以使用以下代码来构建这个因果 DAG。

代码清单 3.2 在 pgmpy 中构建交通方式 DAG

python 复制代码
from pgmpy.models import BayesianNetwork

model = BayesianNetwork(    #1
       [
        ('A', 'E'),    #2
        ('S', 'E'),   #2
        ('E', 'O'),   #2
        ('E', 'R'),   #2
        ('O', 'T'),   #2
        ('R', 'T')    #2
     ]
)
#1 pgmpy 提供了一个 BayesianNetwork 类,可以通过添加边来构建模型。
#2 将因果有向无环图(DAG)以边的列表(元组)形式输入。

pgmpy 中的 BayesianNetwork 对象是基于 Python 中领先的图建模库 NetworkX 的 DiGraph 类构建的。

因果抽象与因果表示学习

建模中的抽象层次指的是模型中变量的细节和粒度水平。在图 3.2 中,数据中的变量与因果 DAG 中的变量之间存在映射关系,因为数据生成过程(DGP)产生的数据和因果 DAG 的抽象层次是一致的。

但数据中的变量可能处于不同的抽象层次。这在机器学习中尤为常见,我们常处理低层级的特征,比如像素。

当数据的抽象层次低于建模者希望操作的层次时,建模者必须利用领域知识推导出将在 DAG 中作为节点出现的高层次抽象变量。举例来说,医生可能关注一个高层的二元变量节点"肿瘤(有/无)",而数据本身是来自医学影像技术的像素矩阵等低层变量。

这时,医生需要对数据集中的每张图像进行人工标注,给出高层的肿瘤变量标签。或者,建模者可以用数学或逻辑等分析手段,将低层次抽象映射到高层次抽象,并且必须保证这种映射保留关于数据生成过程的因果假设。

将低层变量以因果严谨的方式抽象为高层变量的任务称为因果抽象。在机器学习中,"特征工程"指的是从低层特征计算出有用的高层特征,而因果抽象区别于特征工程的是其对因果严谨性的要求。相关因果抽象资料可以参考本书注释页:www.altdeep.ai/p/causalaib...

另一种从数据中的低层因果抽象学习高层抽象的方法是使用深度学习,这被称为因果表示学习。我们将在第5章简要涉及该话题。

3.1.2 为什么使用因果 DAG?

因果 DAG 是最广为人知的因果表示方法,但为了理解它的价值,先思考其他因果建模方式也很有帮助。一个替代方案是使用数学模型,比如常微分方程或偏微分方程,这在物理学和工程学中很常见。另一种选择是使用计算模拟器,例如气象学和气候科学中常用的模拟器。

相比之下,因果 DAG 对数据生成过程(DGP)所需的数学细节要求要低得多。因果 DAG 只需要你以图的形式指定"什么导致什么"。图结构便于人类思考,是理解复杂领域的首选方法。

实际上,使用因果 DAG 作为 DGP 表示有多个好处:

  • DAG 有助于沟通和可视化因果假设。
  • 我们拥有众多工具可用于 DAG 上的计算。
  • 因果 DAG 能够表示时间维度。
  • DAG 将因果关系与条件独立联系起来。
  • DAG 可以为概率机器学习模型提供结构骨架。
  • 这些概率机器学习模型中的参数是模块化的,并编码了因果不变性。

接下来我们逐一回顾这些优点。

3.1.3 DAG 有助于沟通和可视化因果假设

因果 DAG 是一种强有力的沟通工具。视觉传达信息的核心在于突出重要信息,同时舍弃次要信息。打个比方,参见图 3.3 中的两张伦敦地铁地图。左边的地图是地理位置精确的,而右边的简化地图忽略了地理细节,专注于各个车站相对于其他车站的位置------这实际上就足够帮助人们在伦敦找到路线。

图 3.3 说明了图形表示在视觉传达中的强大应用场景。例如,左侧的伦敦地铁地图在地理位置上是准确的,而右侧地图则舍弃了地理精确性,以更清晰地展示各车站相对于其他车站的位置。对于乘客来说,后者比地理精确的地图更实用。同样,因果 DAG 抽象掉了大量因果机制的细节,创建了一个简洁的表示,便于直观推理。

同样,因果 DAG 突出了因果关系,而忽略了其他内容。例如,投掷石块的 DAG 并未体现 Jenny 和 Brian 投掷行为如何通过条件逻辑组合导致窗户破裂。交通方式 DAG 也未说明变量类型。年龄(A)应该视为连续时间、整数岁数、还是年轻/中年/老年等类别,或者区间如 18--29、30--44、45--64、65 岁以上?交通方式(T)的类别有哪些?职业(O)变量是否是多维元组,如 {受雇、工程师、在家工作}?DAG 也没有反映哪些变量是数据中观测到的,以及数据点的数量。

因果 DAG 不展示因果机制

因果 DAG 也不展示因果之间的交互作用。例如,在老一代中,女性上大学的概率低于男性,而在年轻一代中情况正好相反。虽然年龄(A)和性别(S)都是教育(E)的原因,但你无法仅凭 DAG 看出年龄和性别如何交互影响教育。

更一般地说,DAG 无法传递因果机制的细节,也无法说明因果如何作用于结果。它们只明确了"什么导致什么",即因果的"是什么"。比如图 3.4 中各种逻辑门:输入的二进制值 A 和 B 根据逻辑门类型决定输出结果,但如果用因果 DAG 表示逻辑门,则所有逻辑门的因果 DAG 都是相同的。我们可以用因果 DAG 作为捕捉该逻辑的因果图模型的骨架,但在 DAG 中看不到具体逻辑。

图 3.4 中各种类型的逻辑门拥有相同的因果 DAG。

这既是因果 DAG 的优势,也是其局限。因果 DAG 通过传达"什么导致什么"来简化问题,但不涉及"如何导致"。然而,在某些情况下(如逻辑门),能够可视化"如何导致"是非常有价值的。

因果 DAG 表示因果假设

因果 DAG 反映了建模者对数据生成过程(DGP)的假设和信念,因为我们大多数时候无法直接观测到该过程。因此,因果 DAG 使我们能够将假设可视化,并与他人沟通。

除了可视化和沟通之外,因果 DAG 的优势还体现在数学和计算层面(将在后续小节中详细说明)。因果推断领域的研究者们对这些数学和计算性质在实际应用中的效益有不同看法,但大多数人一致认可因果假设可视化和沟通的根本价值。

因果 DAG 中编码的假设是强假设。让我们再次看看图 3.2 中的交通方式 DAG,在图 3.5 中重现。想想这个 DAG 的备选方案:在这个简单的六节点系统中,我们能绘制多少种可能的 DAG?答案是 3,781,503 种。因此,当我们用因果 DAG 传达对该系统的假设时,实际上是在众多 3,781,502 种备选方案中传达我们最优的选择。

图 3.5 交通方式选择的因果 DAG 模型。该 DAG 编码了这些变量之间存在和不存在关联的强假设。

那么其他备选的 DAG 呢?其中一些看起来也合理。比如,婴儿潮一代可能偏好小城镇生活,而千禧一代更倾向于城市生活,这意味着应该存在一条 A → R 的边。又如,性别规范决定了某些职业和行业中的偏好与机会,意味着存在 S → O 的边。假设年龄和性别仅通过教育间接影响职业和居住地是一个强假设,如果成立,将带来有用的推断结果。

但如果我们的因果 DAG 是错误的呢?鉴于存在 3,781,502 种竞争模型,它很可能是错的。在第4章,我们将用数据展示所选 DAG 中的因果假设何时不成立。

3.1.4 我们拥有众多工具用于 DAG 计算

有向图是数学和计算机科学中研究深入的对象,是基础的数据结构。计算机科学家利用图算法解决了许多实际问题,并理论上保证了算法的运行时间。数据科学和机器学习常用的编程语言都拥有实现这些算法的库,比如 Python 中的 NetworkX。这些流行库使得编写处理因果 DAG 的代码变得更加便捷。

当我们用因果 DAG 形式表示因果模型时,可以将所有理论和工具应用于建模问题。例如,在 pgmpy 中可以用数据训练因果 DAG,得到一个有向因果图模型。基于该模型,可以应用基于图的概率推断算法,如信念传播,用以估计图中变量的条件概率。有向图结构让这些算法在常见场景下无需针对特定问题或任务进行额外配置即可运行。

下一章我将介绍 d-分离(d-separation)概念,它是条件独立的图形抽象,也是因果推断中 do-演算理论的基础思想。d-分离关注的是在有向图中寻找节点之间的路径,这一点大多数优秀的图形库都默认支持。实际上,条件独立是因果 DAG 第三个优势的核心理念。

3.1.5 因果 DAG 能够表示时间

因果 DAG 隐含地表示时间关系。更技术地说,因果 DAG 提供了部分时间顺序,因为因果关系中的原因先于结果发生。

例如,考虑图 3.6。这张图描述了一个数据生成过程:云层变化(Cloudy)导致了天气感应喷灌器状态(Sprinkler)和降雨状态(Rain)的变化,这两者又共同导致草地湿度状态(Wet Grass)的变化。我们知道天气状态的变化会引起降雨和喷灌器启动,而这两者都会导致草地湿润的状态改变。然而,这只是部分时间排序,因为图中并未告诉我们喷灌器启动和降雨哪个先发生。

图 3.6 表示草地状态(湿润或干燥)的因果 DAG。该 DAG 给出了节点之间的部分时间顺序,因为因果关系中的原因先于结果发生。

图 3.6 中的部分时间排序看似简单,但请看图 3.7。可视化库可以利用图 3.7 左侧如"毛球"般复杂的 DAG 中的部分时间顺序,将其转换为右侧更易读的形式。

图 3.7 中,可视化库可以利用 DAG 的部分时间顺序,将左侧如"毛球"般复杂的 DAG 展开成右侧更易读的形式。

有时我们需要因果 DAG 更明确地表达时间。例如,在动态环境中建模因果关系时,如强化学习中使用的模型,就需要明确时间。此时,我们可以通过定义并标记模型中的变量来使时间显式化,如图 3.8 所示。我们也可以用类似"Δ"这样的区间变量来表示连续时间。第 12 章将提供一些具体示例。

图 3.8 如果我们需要因果 DAG 明确表达时间,可以在变量定义和节点标注中将时间显式化。我们可以用类似"Δ"这样的区间变量来表示连续时间。

因果 DAG 不允许存在任何环路。在某些因果系统中,放宽无环约束是合理的,比如存在反馈回路的系统,一些高级因果模型也允许环路。但坚持使用更简单的无环假设,使我们能够充分利用因果 DAG 的优势。

如果存在环路,有时可以将环路在时间上展开,使时间显式化,从而获得无环结构。比如图中 X ⇄ Y 的图形可以展开为 X₀ → Y₀ → X₁ → Y₁ ......。例如,供应、价格和需求之间可能存在环路,但你可以将其重写为时间点 0 的价格影响时间点 1 的供应和需求,进而影响时间点 2 的价格,依此类推。

图 3.9 交通方式 DAG 中的因果关系编码了关于条件独立性的关键假设。

请再次看图 3.9 中展示的交通方式 DAG。

DAG 中的六个变量具有联合分布 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( A , S , E , O , R , T ) P(A, S, E, O, R, T) </math>P(A,S,E,O,R,T)。回顾第二章中的链式法则,它说明任何联合概率都可以分解为一系列条件概率的乘积。例如,

链式分解适用于变量的任意排序。但我们不选择任意排序,而是选择因果 DAG 中的(部分)排序,因为该排序与我们对数据生成过程(DGP)变量因果流动的假设是一致的。

观察图 3.9,变量的排序为 {(A, S), E, (O, R), T},其中成对的变量 (A, S) 和 (O, R) 是无序的。如果我们任意确定一个顺序,比如 A 在 S 之前,O 在 R 之前,则得到如下排列:

接下来,我们将利用因果 DAG 进一步简化这个分解。每个因子都是一个条件概率,因此我们通过仅对 DAG 中每个节点的直接父节点进行条件化来简化这些因子。换句话说,对于每个变量,我们只考虑图中该变量的直接父节点,然后去掉条件概率中"|"右侧不属于这些直接父节点的所有变量。仅对父节点条件化后,我们得到如下简化形式:

这是怎么回事?为什么因果 DAG 会"神奇地"使得 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( s ∣ a ) = P ( s ) P(s \mid a) = P(s) </math>P(s∣a)=P(s)且 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( r ∣ o , e , s , a ) P(r \mid o, e, s, a) </math>P(r∣o,e,s,a)简化为 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( r ∣ e ) P(r \mid e) </math>P(r∣e)?正如第二章所述,声明 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( s ∣ a ) = P ( s ) P(s \mid a) = P(s) </math>P(s∣a)=P(s)和 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( t ∣ o , r , e , s , a ) = P ( t ∣ o , r ) P(t \mid o, r, e, s, a) = P(t \mid o, r) </math>P(t∣o,r,e,s,a)=P(t∣o,r)等价于说 S 和 A 是独立的,而在给定 O 和 R 的条件下,T 与 E、S 和 A 条件独立。换句话说,因果 DAG 为我们提供了一种方法,可以对数据生成过程(DGP)变量的联合概率分布施加条件独立约束。

为什么我们要关心条件独立?条件独立让建模工作更简单。举个例子,假设你要用预测模型来建模交通变量 T。用 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( t ∣ o , r , e , s , a ) P(t \mid o, r, e, s, a) </math>P(t∣o,r,e,s,a)表示的预测模型需要特征 O、R、E、S 和 A,而用 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( t ∣ o , r ) P(t \mid o, r) </math>P(t∣o,r) 表示的预测模型只需特征 O 和 R 来预测 T。后者模型需要学习的参数更少,拥有更多自由度,占用内存更少,训练速度更快,等等。

那么,为什么因果 DAG 赋予我们权利去施加条件独立?让我们对因果关系和条件独立之间的联系建立一些直觉。考虑用家族成员的遗传数据来推断个人信息的例子。比如,金州杀手案中,调查人员利用犯罪现场遗留的 DNA,在公共数据库中找到遗传亲属,通过对这些亲属的三角定位最终锁定了杀手。

假设你有一个直系亲属和一个远房亲戚,二者在同一血统线上。一旦考虑了近亲,这个远房亲戚还能为你提供额外的遗传信息吗?为了简化,假设我们只关注血型。假设近亲是你的父亲,远房亲戚是你的祖父,如图 3.10 所示。确实,你祖父的血型是你血型的一个原因。如果我们有大量祖父-孙子的血型数据,会发现两者有相关性。然而,你父亲的血型是更直接的原因,祖父血型与孙子血型的关系是通过你父亲传递的。因此,如果我们的目标是预测你的血型,而已有你父亲的血型作为预测变量,那么你祖父的血型不会提供额外的预测信息。由此可见,给定你父亲的血型,你的血型与祖父的血型是条件独立的。

图 3.10 因果关系蕴含条件独立。你的父系祖父的血型是你父亲血型的原因,而你父亲的血型又是你的原因。由于你父亲的血型已经包含了祖父血型能提供的关于你血型的所有信息,因此在给定你父亲血型的条件下,你的血型与父系祖父的血型是条件独立的。

因果关系使得相关变量之间产生条件独立性的这种性质,被称为因果马尔可夫性质(causal Markov property)。用图形术语来说,因果马尔可夫性质意味着变量在给定其图中父节点的条件下,与其非后代节点(例如祖先、叔叔/姑姑、表亲等)条件独立。

这种"非后代"定义的因果马尔可夫性质有时被称为局部马尔可夫性质(local Markov property)。一个等价的表达称为马尔可夫分解性质(Markov factorization property),其含义是如果因果 DAG 是正确的,则联合概率可以分解为变量在因果 DAG 中给定其父节点的条件概率的乘积:

如果我们的交通方式 DAG 是数据生成过程(DGP)的真实表示,那么局部马尔可夫性质应当成立。在下一章中,我们将学习如何用数据检验这一假设。

3.1.7 DAG 可为概率机器学习模型提供结构骨架

许多概率机器学习的建模方法都采用 DAG 作为模型结构。典型例子包括有向图模型(也称贝叶斯网络)和潜变量模型(如主题模型)。深度生成模型,比如变分自编码器(VAE),通常也有一个底层的有向图结构。

将概率机器学习模型构建在因果图之上的优势显而易见:你得到的是一个概率因果机器学习模型。它可以用数据训练,并可用于预测及其他推断,就像任何概率机器学习模型一样。此外,因其基于因果 DAG 构建,它本身就是因果模型,因此你可以利用它做因果推断。

基于骨架结构的另一个好处是模型中的参数是模块化的,并且编码了因果不变性。在探讨这一优势之前,我们先基于交通方式 DAG 构建一个图模型。

基于因果 DAG 构建概率机器学习模型

回顾之前关于交通变量联合概率分布的分解,基于交通 DAG 中变量的排序进行分解。

我们有一组因子 <math xmlns="http://www.w3.org/1998/Math/MathML"> { P ( a ) , P ( s ) , P ( e ∣ s , a ) , P ( o ∣ e ) , P ( r ∣ e ) , P ( t ∣ o , r ) } \{P(a), P(s), P(e|s,a), P(o|e), P(r|e), P(t|o,r)\} </math>{P(a),P(s),P(e∣s,a),P(o∣e),P(r∣e),P(t∣o,r)}。从这里开始,我们将沿用第二章中的"马尔可夫核"一词,称这些因子为因果马尔可夫核。

我们将通过在代码中实现这些因果马尔可夫核,然后将它们组合成一个整体模型,来构建概率机器学习模型。每个核的实现都能够在给定输入参数时返回一个概率值。例如,P(a) 会接受变量 A 的某个取值,并返回该取值的概率。同理, <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( t ∣ o , r ) P(t|o,r) </math>P(t∣o,r)会接受 T、O 和 R 的取值,并返回查询值 <math xmlns="http://www.w3.org/1998/Math/MathML"> t t </math>t 对应的概率。我们的实现还能够基于因果马尔可夫核进行生成。为此,这些实现需要参数将输入映射到输出。我们将采用标准统计学习方法从数据中拟合这些参数。

3.1.8 在因果 DAG 上训练模型

考虑交通方式 DAG 的数据生成过程(DGP)。这一过程会产生什么样的数据?

假设我们进行了针对 500 名个体的调查,获取了 DAG 中每个变量的取值。数据编码如下:

  • 年龄(A) --- 记录为"young"(年轻)表示 29 岁及以下,"adult"(成年)表示 30 至 60 岁(含),以及"old"(年长)表示 61 岁及以上
  • 性别(S) --- 个体自报性别,记录为"male"(男,M)、"female"(女,F)或"other"(其他,O)
  • 教育(E) --- 个体完成的最高教育或培训水平,记录为"high"(高中)或"uni"(大学学位)
  • 职业(O) --- 雇员("emp")或自雇工作者("self")
  • 居住地(R) --- 个体所在城市人口规模,记录为"小"("small")或"大"("big")
  • 交通方式(T) --- 个体偏好的交通工具,记录为"car"(汽车)、"train"(火车)或"other"(其他)

因果抽象的标注

在机器学习中,如何构思模型变量非常重要。举例来说,ImageNet 数据库包含 1400 万张图像,其中对种族类别的标签存在过时和冒犯性的问题。即便改名以减少冒犯,种族类别本身也随着时间和文化不断变化。那么,在预测算法中,什么才是"正确"的标签?

变量的定义不仅是政治或人口普查的问题。哲学家纳尔逊·古德曼(Nelson Goodman)通过一个简单的思维实验展示了标签的微小变动如何导致预测结果的矛盾。假设你经常搜集宝石,并记录每块宝石的颜色。结果你的数据中 100% 的宝石都是绿色。现在定义一个新标签"grue",意思是"如果观察时间早于现在则为绿色,否则为蓝色"。那么你的所有数据都是"绿色"或"grue",取决于你选用哪个标签。接着假设你通过对过去数据的外推来预测未来,那么你既可以预测下一块祖母绿宝石是绿色(基于过去所有都是绿色的数据),也可以预测下一块宝石是"grue"(即蓝色),基于过去所有都是"grue"的数据。显然,这样荒谬的标签你不会真正用,但该思维实验足以说明推断依赖于抽象方式。

在数据科学和机器学习中,常被鼓励盲目建模数据而不考虑数据生成过程(DGP)。通常默认变量名就是电子表格中的列或数据库表的属性。但如果可能,最好选择适合推断问题的抽象,并依据该抽象收集或编码数据。如果无法做到,也要牢记分析结果会依赖于他人如何定义变量。

第 7 章我将介绍"无操控则无因果"(no causation without manipulation)的思想,这为定义因果变量提供了有用的启发。

交通数据中的变量都是类别型变量。在这个简单的类别型案例中,我们可以依赖如 pgmpy 这样的图模型库。

代码清单 3.3 加载交通数据

ini 复制代码
import pandas as pd

url = 'https://raw.githubusercontent.com/altdeep/causalML/master/datasets/transportation_survey.csv'    #1
data = pd.read_csv(url)
data
#1 我们将用 read_csv 方法将数据加载到 pandas DataFrame 中。

该代码生成了图 3.11 中展示的 DataFrame。

图 3.11 交通模型背后的数据生成过程(DGP)数据示例。本例中,数据来自 500 份调查问卷。

我们在代码清单 3.2 中初始化的 BayesianNetwork 类提供了一个 fit 方法,用于学习因果马尔可夫核的参数。由于变量是类别型的,因果马尔可夫核以条件概率表的形式表示,由 pgmpy 的 TabularCPD 类实现。fit 方法通过数据拟合(学习)这些条件概率表的参数估计。

代码清单 3.4 学习因果马尔可夫核的参数

python 复制代码
from pgmpy.models import BayesianNetwork

model = BayesianNetwork(
      [
        ('A', 'E'),
        ('S', 'E'),
        ('E', 'O'),
        ('E', 'R'),
        ('O', 'T'),
        ('R', 'T')
     ]
)
model.fit(data)    #1
causal_markov_kernels = model.get_cpds()     #2
print(causal_markov_kernels)  #2
#1 BayesianNetwork 对象的 fit 方法从数据(pandas DataFrame)中估计参数。
#2 获取并查看 fit 学习到的因果马尔可夫核。

输出结果如下:

php 复制代码
[<TabularCPD representing P(A:3) at 0x7fb030dd1050>,
 <TabularCPD representing P(E:2 | A:3, S:2) at 0x7fb0318121d0>,
 <TabularCPD representing P(S:2) at 0x7fb03189fe90>,
 <TabularCPD representing P(O:2 | E:2) at 0x7fb030de85d0>,
 <TabularCPD representing P(R:2 | E:2) at 0x7fb030dfa890>,
 <TabularCPD representing P(T:3 | O:2, R:2) at 0x7fb0316c9110>]

接下来看看交通变量 T 的因果马尔可夫核结构。从打印的 causal_markov_kernels 列表中可见,T 是列表中的最后一项。

scss 复制代码
cmk_T = causal_markov_kernels[-1]
print(cmk_T)

输出结果如下:

scss 复制代码
+----------+---------+----------+---------+----------+
| O        | O(emp)  | O(emp)   | O(self) | O(self)  |
+----------+---------+----------+---------+----------+
| R        | R(big)  | R(small) | R(big)  | R(small) |
+----------+---------+----------+---------+----------+
| T(car)   | 0.70343 | 0.52439  | 0.44444 | 1.0      |
+----------+---------+----------+---------+----------+
| T(other) | 0.13480 | 0.08536  | 0.33333 | 0.0      |
+----------+---------+----------+---------+----------+
| T(train) | 0.16176 | 0.39024  | 0.22222 | 0.0      |
+----------+---------+----------+---------+----------+

注意,为了表格能完整显示,我对数字进行了截断。

cmk_T 是条件概率表形式的因果马尔可夫核 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T ∣ O , R ) P(T | O, R) </math>P(T∣O,R) 的实现,这是一种查找表,根据 T、O 和 R 的取值返回对应的概率值。例如, <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T = car ∣ O = emp , R = big ) = 0.7034 P(T = \text{car} \mid O = \text{emp}, R = \text{big}) = 0.7034 </math>P(T=car∣O=emp,R=big)=0.7034。注意这些是条件概率。对于每组 O 和 R 的取值组合,T 的三个可能结果的条件概率加和为 1。例如,当 <math xmlns="http://www.w3.org/1998/Math/MathML"> O = emp且 R = big O = \text{emp}且 R = \text{big} </math>O=emp且R=big 时, <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T = car ∣ O , R ) + P ( T = other ∣ O , R ) + P ( T = train ∣ O , R ) = 1 P(T=\text{car} \mid O, R) + P(T=\text{other} \mid O, R) + P(T=\text{train} \mid O, R) = 1 </math>P(T=car∣O,R)+P(T=other∣O,R)+P(T=train∣O,R)=1。

对于没有父节点的节点,其因果马尔可夫核就是一个简单的概率表。例如,打印 causal_markov_kernels[2] 会输出性别变量 S 的因果马尔可夫核(列表中的第三项):

scss 复制代码
+------+-------+
| S(F) | 0.517 |
+------+-------+
| S(M) | 0.473 |
+------+-------+
| S(O) | 0.010 |
+------+-------+

fit 方法通过计算数据中每个类别的比例来学习参数。我们也可以使用其他参数学习技术。

3.1.9 参数学习的不同方法

训练这些参数的方法有多种,下面介绍几种条件概率表中常用的参数训练方法。

最大似然估计(Maximum Likelihood Estimation)

我在 BayesianNetwork 模型对象的 fit 方法中使用的学习算法是最大似然估计(在第2章中有讨论)。这是默认的参数学习方法,因此调用 fit 时未特别指定"最大似然"。通常,最大似然估计的目标是找到使训练数据出现概率最大的参数。

对于类别型数据,最大似然估计等价于计算数据中各类别的频数比例。例如,参数 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( O = emp ∣ E = high ) P(O = \text{emp} \mid E = \text{high}) </math>P(O=emp∣E=high) 的计算方式为:

贝叶斯估计

在第2章中,我也介绍了贝叶斯估计。贝叶斯估计通常在数学上难以解析,需要依赖计算开销较大的算法(例如采样算法和变分推断)。一个关键的例外是共轭先验的情况,此时先验分布和目标(后验)分布具有相同的标准形式。这意味着代码实现可以通过简单的数学计算出目标分布的参数值,而无需复杂的贝叶斯推断算法。

例如,pgmpy 对类别型结果实现了 Dirichlet 共轭先验。在 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( O ∣ E = high ) P(O | E = \text{high}) </math>P(O∣E=high)中,对于每个 O 的取值都有对应的概率值,我们希望从数据中推断这些概率值。贝叶斯方法为这些概率值赋予先验分布。Dirichlet 分布是概率值集合的良好先验选择,因为它定义在单纯形上(一组介于零和一之间且加和为一的数)。更重要的是,它与类别分布(如 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( O ∣ E = high ) P(O | E = \text{high}) </math>P(O∣E=high))是共轭的,这意味着参数值的后验分布仍然是 Dirichlet 分布。这样,我们可以通过简单的数学结合数据中的计数和先验参数,计算概率值的点估计。pgmpy 会帮我们完成这些计算。

代码清单 3.5 使用 Dirichlet 共轭先验的贝叶斯点估计

ini 复制代码
from pgmpy.estimators import BayesianEstimator     #1

model.fit(
    data,
    estimator=BayesianEstimator,    #2
    prior_type="dirichlet",
    pseudo_counts=1    #3
) 

causal_markov_kernels = model.get_cpds()      #4
cmk_T = causal_markov_kernels[-1]     #4
print(cmk_T)  #4

#1 导入 BayesianEstimator 并在模型和数据上初始化。
#2 在 fit 方法中传入估计器对象。
#3 pseudo_counts 是 Dirichlet 先验的参数。
#4 提取因果马尔可夫核并查看 (P(T|O,R))。

前述代码会输出:

scss 复制代码
+----------+--------------------+-----+--------------------+----------+
| O        | O(emp)             | ... | O(self)            | O(self)  |
+----------+--------------------+-----+--------------------+----------+
| R        | R(big)             | ... | R(big)             | R(small) |
+----------+--------------------+-----+--------------------+----------+
| T(car)   | 0.7007299270072993 | ... | 0.4166666666666667 | 0.5      |
+----------+--------------------+-----+--------------------+----------+
| T(other) | 0.1362530413625304 | ... | 0.3333333333333333 | 0.25     |
+----------+--------------------+-----+--------------------+----------+
| T(train) | 0.1630170316301703 | ... | 0.25               | 0.25     |
+----------+--------------------+-----+--------------------+----------+

与最大似然估计相比,使用 Dirichlet 先验的贝叶斯估计起到了平滑作用。例如,最大似然估计可能得出小城镇中 100% 的自雇人士选择汽车通勤,这很可能是极端的结果。显然,有些自雇人士骑自行车通勤------只是我们没能调查到他们。一些小城市(如美国弗吉尼亚州的人口约 2.2 万的水晶城)有地铁站,我敢打赌这些城市里至少有几位创业者会乘坐火车。


因果建模者与贝叶斯学派

贝叶斯哲学不仅仅局限于参数估计。实际上,贝叶斯哲学与基于 DAG 的因果建模有许多共通之处。贝叶斯学派试图将主观信念、不确定性和先验知识编码到模型变量的"先验"概率分布中;因果建模者则试图将对数据生成过程的主观信念和先验知识编码为因果 DAG 的形式。这两种方法是兼容的。在给定因果 DAG 的情况下,你可以用贝叶斯方法推断基于该 DAG 构建的概率模型的参数,甚至可以对 DAG 本身进行贝叶斯推断,计算可能 DAG 的概率分布!

本书重点关注因果性,贝叶斯讨论保持简洁。但我们将使用 Pyro(及其 NumPy-JAX 版本 NumPyro)来实现因果模型;这些库提供了对模型和参数的完整贝叶斯推断支持。第 11 章将展示用因果图模型从零构建的因果效应贝叶斯推断示例。

其他参数估计方法

我们不必非得用条件概率表来表示因果马尔可夫核。在广义线性模型框架内,也存在用于建模类别型结果的模型。对于交通模型中的某些变量,我们甚至可能使用非类别型的结果。例如,年龄在调查中可能被记录为整数型结果。对于数值型变量,我们可以采用其他建模方法。你也可以使用神经网络结构来建模单个因果马尔可夫核。

参数假设指的是我们如何指定 DAG 中某个节点的输出类型(例如类别型或实数型),以及如何将父节点映射到该输出(例如使用查表法或神经网络)。需要注意的是,因果 DAG 中编码的因果假设与因果马尔可夫核的参数假设是解耦的。举例来说,当我们假设年龄是教育水平的直接原因,并在 DAG 中用一条边表示该关系时,并不需要决定年龄是作为有序类别集合、整数,还是出生以来经过的秒数等形式来处理。此外,我们也不必在此时决定使用条件类别分布还是回归模型。具体的实现步骤是在确定因果 DAG 并准备实现 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( E ∣ A , S ) P(E \mid A, S) </math>P(E∣A,S) 后进行。

同样地,当我们对训练好的因果模型进行预测和概率推断时,选择何种推断或预测算法虽重要,但这与我们关注的因果问题是分开的。这种分离简化了我们的工作。通常,我们可以独立于统计学、计算贝叶斯学和应用机器学习的知识,构建因果建模与推理的相关技能和知识体系。

3.1.10 存在潜变量时的参数学习

由于我们建模的是数据生成过程(DGP),而非直接建模数据,因此因果 DAG 中可能存在一些节点在数据中未被观测到。幸运的是,概率机器学习为我们提供了学习潜变量因果马尔可夫核参数的工具。

使用 pgmpy 学习潜变量

举例来说,假设交通调查数据中的教育变量 E 未被记录。pgmpy 提供了一个工具,使用结构化期望最大化算法(Structural Expectation Maximization,EM)来学习潜变量 E 的因果马尔可夫核参数,这是一种基于最大似然的参数学习变体。

代码清单 3.6 使用潜变量训练因果图模型

python 复制代码
import pandas as pd
from pgmpy.models import BayesianNetwork
from pgmpy.estimators import ExpectationMaximization as EM

url = 'https://raw.githubusercontent.com/altdeep/causalML/master/datasets/transportation_survey.csv'    #1
data = pd.read_csv(url)    #1
data_sans_E = data[['A', 'S', 'O', 'R', 'T']]    #2

model_with_latent = BayesianNetwork(
       [
        ('A', 'E'),
        ('S', 'E'),
        ('E', 'O'),
        ('E', 'R'),
        ('O', 'T'),
        ('R', 'T')
     ],
     latents={"E"}    #3
)

estimator = EM(model_with_latent, data_sans_E)     #4
cmks_with_latent = estimator.get_parameters(latent_card={'E': 2})    #4
print(cmks_with_latent[1].to_factor())    #5

#1 下载数据并转换为 pandas DataFrame。
#2 除去教育变量 E 外保留所有列。
#3 指定训练模型时哪些变量是潜变量。
#4 使用结构化期望最大化算法学习潜变量 E 的因果马尔可夫核。需指定潜变量的基数。
#5 打印学习到的潜变量 E 的因果马尔可夫核,以 factor 对象形式输出以便阅读。

打印结果为一个 factor 对象:

scss 复制代码
+------+----------+------+--------------+
| E    | A        | S    |   phi(E,A,S) |
+======+==========+======+==============+
| E(0) | A(adult) | S(F) |       0.1059 |
+------+----------+------+--------------+
| E(0) | A(adult) | S(M) |       0.1124 |
+------+----------+------+--------------+
| E(0) | A(old)   | S(F) |       0.4033 |
+------+----------+------+--------------+
| E(0) | A(old)   | S(M) |       0.2386 |
+------+----------+------+--------------+
| E(0) | A(young) | S(F) |       0.4533 |
+------+----------+------+--------------+
| E(0) | A(young) | S(M) |       0.6080 |
+------+----------+------+--------------+
| E(1) | A(adult) | S(F) |       0.8941 |
+------+----------+------+--------------+
| E(1) | A(adult) | S(M) |       0.8876 |
+------+----------+------+--------------+
| E(1) | A(old)   | S(F) |       0.5967 |
+------+----------+------+--------------+
| E(1) | A(old)   | S(M) |       0.7614 |
+------+----------+------+--------------+
| E(1) | A(young) | S(F) |       0.5467 |
+------+----------+------+--------------+
| E(1) | A(young) | S(M) |       0.3920 |
+------+----------+------+--------------+

这里 E 的取值为 0 和 1,是因为算法并不知道具体的标签名称。比如 0 可能代表"high"(高中),1 代表"uni"(大学),但要正确将潜变量估计方法得到的默认结果映射到实际标签,需要额外的假设。

潜变量与参数可识别性

在统计推断中,当理论上可以通过无限样本学习到参数的真实值时,该参数被称为"可识别"(identified)。若再多数据也无法更接近真实值,则称其为"不可识别"(unidentified)。遗憾的是,你的数据可能不足以学习因果 DAG 中潜变量的因果马尔可夫核。

如果我们不关注因果表示,可以限制自己只用那些潜变量参数可被数据识别的潜变量图模型。但我们必须构建能够代表数据生成过程的因果 DAG,即使数据无法使潜变量和参数可识别。

话虽如此,即使模型中存在不可识别参数,你仍可能识别出能回答因果问题的量。事实上,许多因果推断方法专注于在存在潜在"混杂变量"的情况下,对因果效应(原因对结果的影响程度)进行稳健估计。第11章将详细介绍这一内容。另一方面,即使参数可识别,回答因果问题的量也可能不可识别。第10章将详细讲解因果可识别性。

3.1.11 使用训练好的因果概率机器学习模型进行推断

一个变量集合的概率机器学习模型可以利用计算推断算法,在已知部分变量结果的条件下,推断其他变量的条件概率。对于有类别型输出的有向图模型,我们使用变量消除算法(在第2章介绍过)。

例如,假设我们想比较开车者与乘火车者的教育水平差异。我们可以使用变量消除算法计算并比较 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( E ∣ T ) 在 T = car P(E \mid T) 在 T = \text{car} </math>P(E∣T)在T=car和 <math xmlns="http://www.w3.org/1998/Math/MathML"> T = train T = \text{train} </math>T=train时的条件概率。

代码清单 3.7 对训练好的因果图模型进行推断

python 复制代码
from pgmpy.inference import VariableElimination    #1

inference = VariableElimination(model)     
query1 = inference.query(['E'], evidence={"T": "train"})
query2 = inference.query(['E'], evidence={"T": "car"})

print("train")
print(query1)
print("car")
print(query2)

#1 VariableElimination 是专门针对图模型的推断算法。

打印结果为"train"和"car"的概率表:

scss 复制代码
"train"
+---------+----------+
| E       |   phi(E) |
+=========+==========+
| E(high) |   0.6162 |
+---------+----------+
| E(uni)  |   0.3838 |
+---------+----------+

"car"
+---------+----------+
| E       |   phi(E) |
+=========+==========+
| E(high) |   0.5586 |
+---------+----------+
| E(uni)  |   0.4414 |
+---------+----------+

看起来开车者更可能拥有大学学历: <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( E = uni ∣ T = car ) > P ( E = uni ∣ T = train ) P(E = \text{uni} \mid T = \text{car}) > P(E = \text{uni} \mid T = \text{train}) </math>P(E=uni∣T=car)>P(E=uni∣T=train)。这种推断基于我们用 DAG 建立的因果假设,即大学教育间接决定了人们的通勤方式。

在 Pyro 这样的工具中,你需要更手动地操作推断算法。以下代码示例展示了如何用一种叫重要性采样(importance sampling)的概率推断算法推断 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( E ∣ T = "train" ) P(E \mid T = \text{"train"}) </math>P(E∣T="train")。首先,我们显式指定模型参数,而非用拟合方法估计。

代码清单 3.8 在 Pyro 中实现训练好的因果模型

ini 复制代码
import torch
import pyro
from pyro.distributions import Categorical

A_alias = ['young', 'adult', 'old']     #1
S_alias = ['M', 'F']     #1
E_alias = ['high', 'uni']    #1
O_alias = ['emp', 'self']   #1
R_alias = ['small', 'big']    #1
T_alias = ['car', 'train', 'other']    #1

A_prob = torch.tensor([0.3,0.5,0.2])    #2
S_prob = torch.tensor([0.6,0.4])    #2
E_prob = torch.tensor([[[0.75,0.25], [0.72,0.28], [0.88,0.12]],
                       [[0.64,0.36], [0.7,0.3], [0.9,0.1]]])     #2
O_prob = torch.tensor([[0.96,0.04], [0.92,0.08]])     #2
R_prob = torch.tensor([[0.25,0.75], [0.2,0.8]])     #2
T_prob = torch.tensor([[[0.48,0.42,0.1], [0.56,0.36,0.08]],
                       [[0.58,0.24,0.18], [0.7,0.21,0.09]]])     #2

def model():    #3
   A = pyro.sample('age', Categorical(probs=A_prob))    #3
   S = pyro.sample('gender', Categorical(probs=S_prob))    #3
   E = pyro.sample('education', Categorical(probs=E_prob[S][A]))    #3
   O = pyro.sample('occupation', Categorical(probs=O_prob[E]))    #3
   R = pyro.sample('residence', Categorical(probs=R_prob[E]))   #3
   T = pyro.sample('transportation', Categorical(probs=T_prob[R][O]))   #3  
   return {'A': A, 'S': S, 'E': E, 'O': O, 'R': R, 'T': T}     #3

pyro.render_model(model)     #4

#1 类别分布只返回整数,所以记录整数与类别名称的映射很有用。
#2 为简化起见,这里用了 pgmpy fit 方法学到的参数的近似值,但也可以通过训练得到参数。
#3 在 Pyro 中实现模型时,通过代码逻辑隐式指定因果 DAG。
#4 可以用 pyro.render_model() 生成模型隐含的因果 DAG 图。注意需预先安装 Graphviz。

pyro.render_model 函数会绘制出 Pyro 模型隐含的因果 DAG,如图 3.12 所示。

图 3.12 你可以通过 Pyro 的 pyro.render_model() 函数来可视化因果 DAG,前提是你已经安装了 Graphviz。

Pyro 提供了概率推断算法,如重要性采样,我们可以将其应用于我们的因果模型。

代码清单 3.9 在 Pyro 中对因果模型进行推断

python 复制代码
import numpy as np
import pyro
from pyro.distributions import Categorical
from pyro.infer import Importance, EmpiricalMarginal  #1
import matplotlib.pyplot as plt

conditioned_model = pyro.condition(    #2
    model,    #3
    data={'transportation': torch.tensor(1.)}    #3
)

m = 5000     #4
posterior = pyro.infer.Importance(    #5
    conditioned_model,    #5
    num_samples=m
).run()     #6

E_marginal = EmpiricalMarginal(posterior, "education")     #7
E_samples = [E_marginal().item() for _ in range(m)]   #7
E_unique, E_counts = np.unique(E_samples, return_counts=True)     #8
E_probs = E_counts / m    #8

plt.bar(E_unique, E_probs, align='center', alpha=0.5)     #9
plt.xticks(E_unique, E_alias)    #9
plt.ylabel('probability')    #9
plt.xlabel('education')     #9
plt.title('P(E | T = "train") - Importance Sampling')    #9

#1 使用了两个与推断相关的类:Importance 和 EmpiricalMarginal。
#2 `pyro.condition` 对模型进行条件化操作。
#3 输入模型和用于条件化的证据。证据是一个将变量名映射到具体值的字典。推断时需指定变量名,这也是调用 `pyro.sample` 时需要 `name` 参数的原因。这里条件化为 T="train"。
#4 我们将运行一个生成 m 个样本的推断算法。
#5 使用了一种名为重要性采样的推断算法。Importance 类构建该算法,输入条件化后的模型和样本数量。
#6 使用 `run` 方法执行随机过程算法。推断算法将基于条件变量(T)生成剩余变量的联合概率样本。
#7 我们关注教育(education)的条件概率分布,因此从后验分布中提取教育变量的样本。
#8 基于这些样本,我们进行蒙特卡洛估计,计算 (P(E | T = "train")) 的概率。
#9 绘制学习到的概率的柱状图。

该代码生成了图 3.13 所示的图表。所示概率与 pgmpy 模型的结果接近,但因算法不同以及参数估计值四舍五入至两位小数而略有差异。

此概率推断尚非因果推断------我们将在第7章开始看到结合因果推断与概率推断的示例。第8章将介绍如何利用概率推断实现因果推断。现在,我们先来看参数模块化的优势,以及参数如何编码因果不变性。

3.2 因果不变性与参数模块化

假设我们想建模海拔高度与温度之间的关系。二者显然相关:海拔越高,温度越低。然而,你也知道温度不会导致海拔升高,否则加热城市里的空气就会让城市飞起来。海拔是原因,温度是结果。

我们可以设计一个简单的因果 DAG,来捕捉温度与海拔及其他因素之间的关系,如图 3.14 所示。设 A 表示海拔,C 表示云层覆盖,L 表示纬度,S 表示季节,T 表示温度。图 3.14 中的 DAG 包含五个因果马尔可夫核: <math xmlns="http://www.w3.org/1998/Math/MathML"> { P ( A ) , P ( C ) , P ( L ) , P ( S ) , P ( T ∣ A , C , L , S ) } \{P(A), P(C), P(L), P(S), P(T | A, C, L, S)\} </math>{P(A),P(C),P(L),P(S),P(T∣A,C,L,S)}。

要在该有向无环图(DAG)上训练因果图模型,我们需要为每一个因果马尔可夫核(causal Markov kernel)学习参数。

3.2.1 机制独立性与参数模块化

在我们的温度DAG所依据的数据生成过程(DGP)中,存在一些潜在的热力学机制,这些机制体现在因果马尔可夫核上。例如,因果马尔可夫核 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T ∣ A , C , L , S ) P(T \mid A, C, L, S) </math>P(T∣A,C,L,S)是由基于物理的机制决定的条件概率,其中海拔(Altitude)、云量(Cloud cover)、纬度(Latitude)和季节(Season)共同驱动温度。这个机制与决定云量的机制(根据我们的DAG)是不同的。机制独立性指的正是不同机制之间的这种区分。

机制独立性带来一个性质,称为参数模块化。在我们的模型中,对于每一个因果马尔可夫核,我们选择了一个参数化的表示。如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T ∣ A , C , L , S ) 和 P ( C ) P(T \mid A, C, L, S) 和 P(C) </math>P(T∣A,C,L,S)和P(C) 属于不同的机制,那么我们对它们的表示也是对应不同机制的。这意味着我们可以改变其中一个机制的参数表示,而不用担心对其他机制的表示产生影响。这样的模块化在统计模型中很少见;通常你不能随意更改模型的一部分而期望其他部分不受影响。

这种性质在训练中非常有用。通常,训练模型时,所有参数是一起优化的。参数模块化意味着你可以分别训练每个因果马尔可夫核的参数,或者将它们当作互相独立的参数组同时训练,从而在训练过程中享受维度减少的好处。从贝叶斯的角度来看,这些参数集先验上是独立的(尽管在后验上通常是相关的)。这为给每个因果马尔可夫核的参数集使用独立先验分布提供了良好的因果学依据。

3.2.2 因果迁移学习、数据融合与不变预测

你可能不是气象学家,但你知道温度和海拔的关系跟气压、气候、阳光等因素有关。无论这种物理关系具体如何,这套物理规律在加德满都和埃尔帕索都是相同的。因此,当我们在仅从加德满都采集的数据上训练因果马尔可夫核时,实际上学到的是一个在加德满都和埃尔帕索之间都保持不变的因果机制。这种不变性有助于迁移学习;我们应该能用训练好的因果马尔可夫核对埃尔帕索的温度做出推断。

当然,利用因果不变性的前提有一些限制。例如,它假设你的因果模型是正确的,并且加德满都数据中包含了足够的信息来有效应用到埃尔帕索的机制上。

许多高级方法都依赖于因果不变性和机制独立性。例如,因果数据融合利用这一理念,通过结合多个数据集来学习因果模型;因果迁移学习用因果不变性在训练域外做因果推断;因果不变预测则在预测任务中利用这种不变性。更多参考见章节注释:www.altdeep.ai/p/causalaib...

3.2.3 用常识拟合参数

在温度模型中,我们对诱导 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T ∣ A , C , L , S ) P(T \mid A, C, L, S) </math>P(T∣A,C,L,S) 的物理机制有直觉。在非自然科学领域,比如计量经济学和其他社会科学中,系统的"物理"更抽象,难以明确定义。幸运的是,在这些领域,我们仍然可以依赖类似基于不变性的直觉。只要模型正确,我们仍假设因果马尔可夫核对应现实世界中不同的因果机制。例如,回想运输模型中 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T ∣ O , R ) P(T \mid O, R) </math>P(T∣O,R),我们仍假设其底层机制与其他机制不同;若 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T ∣ O , R ) P(T \mid O, R) </math>P(T∣O,R) 的机制发生变化,只有这部分应当改变,模型中其他核不应受影响。如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R ∣ E ) P(R \mid E) </math>P(R∣E) 的机制发生变化(R是居住地,E是教育程度),这只应影响 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R ∣ E ) P(R \mid E) </math>P(R∣E) ,而不应影响 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( T ∣ O , R ) P(T \mid O, R) </math>P(T∣O,R) 的参数。

这种不变性帮助我们不用统计学习,仅通过推理因果机制估计参数。例如,再看因果马尔可夫核 <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R ∣ E ) P(R \mid E) </math>P(R∣E)(R是居住地,E是教育)。我们尝试不用统计学习,基于常识推断该分布参数。

一般而言,未获得高中以上学历的人更可能留在家乡;而来自小城镇并取得大学学历的人,则更可能搬到大城市,利用其学历获得更高薪的工作。

现在考虑美国人口统计。假设网络搜索告诉你:80%的美国人口生活在城市地区( <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R = b i g ) = 0.8 P(R=big) = 0.8 </math>P(R=big)=0.8),95%的大学毕业者生活在城市地区( <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R = b i g ∣ E = u n i ) = 0.95 P(R=big|E=uni) = 0.95 </math>P(R=big∣E=uni)=0.95),而美国成年人口中有25%拥有大学学历( <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( E = u n i ) = 0.25 P(E=uni) = 0.25 </math>P(E=uni)=0.25)。通过简单计算,我们得到:

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R = s m a l l ∣ E = h i g h ) = 0.25 P(R=small|E=high) = 0.25 </math>P(R=small∣E=high)=0.25
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R = b i g ∣ E = h i g h ) = 0.75 P(R=big|E=high) = 0.75 </math>P(R=big∣E=high)=0.75
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R = s m a l l ∣ E = u n i ) = 0.05 P(R=small|E=uni) = 0.05 </math>P(R=small∣E=uni)=0.05
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> P ( R = b i g ∣ E = u n i ) = 0.95 P(R=big|E=uni) = 0.95 </math>P(R=big∣E=uni)=0.95

这种基于常识和不变性推算参数的方法,尤其适合于缺乏足够数据进行统计学习的场景。

3.3 你的因果问题决定了DAG的范围

当建模者第一次面对一个问题时,往往已经有一组可用的数据,而一个常见的错误是仅仅用这些数据中的变量来定义你的DAG。让数据决定DAG的范围看似方便,因为你不需要思考该包含哪些变量。但是,因果建模者关注的是数据生成过程(DGP),而不是数据本身。现实世界中的真实因果结构不会在意你的数据集中究竟测量了什么。在你的因果DAG中,应当包含所有因果相关的变量,无论它们是否在你的数据集中。

既然数据不能决定DAG的范围,那什么决定呢?虽然你的数据变量是固定的,但组成数据生成过程的变量仅受你想象力的限制。对于某个变量,你可以包含它的原因、这些原因的原因、再这些原因的原因......一直追溯到亚里士多德的"第一推动者",即一切的单一起因。幸运的是,我们不需要追溯那么远。下面来看一个你可以用来选取DAG中变量的步骤。

3.3.1 选择包含在DAG中的变量

回想一下,因果推断问题有多种类型。正如我在第1章提到的,因果效应推断是最常见的因果问题类型。我这里以因果效应推断为例,但这个流程适用于所有类型的因果问题。

  • 包含与你的因果问题核心相关的变量 --- 第一步是包含所有与你的因果问题核心相关的变量。如果你打算提出多个问题,就包含所有与这些问题相关的变量。举例来说,考虑图3.15。假设我们想询问变量V对U和Y的因果效应,那么这三个变量就成为我们首先包含在DAG中的变量。
  • 包含第一步中变量的任何共同原因 --- 添加第一步中包含变量的任何共同原因。在我们的例子里,你会从图3.15中的U、V和Y开始,追溯它们的因果血统,找出它们的共同祖先。这些共同祖先就是共同原因。在图3.16中,W0、W1和W2是V、U和Y的共同原因。

用正式的术语来说,如果存在一条从变量 Z 到变量 X 的有向路径且这条路径不经过变量 Y,同时又存在一条从变量 Z 到变量 Y 的有向路径且这条路径不经过变量 X,那么变量 Z 就是变量对 X 和 Y 的一个共同原因。

包含共同原因的正式原则称为因果充分性(causal sufficiency)。如果一个变量集合中没有遗漏该集合内任意一对变量之间的任何共同原因,则该变量集合被称为因果充分的。

此外,一旦你包含了某个共同原因,就不必再包含在同一路径上更早的共同原因。例如,图3.17展示了我们如何排除变量更早期的共同原因。

在图3.17中,变量 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 2 W_2 </math>W2 位于 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 0 W_0 </math>W0 到变量 Y 和 U 的路径上,但我们仍然包含 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 0 W_0 </math>W0,因为它有自己的路径通向 V。相比之下,虽然变量 C 是 V、Y 和 U 的共同原因,但 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 0 W_0 </math>W0 位于 C 通往 V、Y 和 U 的所有路径上,因此在包含了 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 0 W_0 </math>W0后,我们可以排除 C。类似地, <math xmlns="http://www.w3.org/1998/Math/MathML"> W 2 W_2 </math>W2使得我们可以排除 E,而 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 0 W_0 </math>W0 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 2 W_2 </math>W2 共同使我们可以排除 D。

  • 包含可能对因果推断统计分析有用的变量 --- 接下来,我们包含在你想要进行的因果推断的统计方法中可能有用的变量。例如,在图3.18中,假设你想估计 V 对 Y 的因果效应,你可能希望包含可能的"工具变量"。我们将在本书第4部分正式定义工具变量,但目前在因果效应问题中,工具变量是你感兴趣变量的父节点,它能帮助统计估计因果效应。在图3.18中,变量 Z 可以作为一个工具变量。你不需要为了因果充分性而包含 Z,但你可能会选择包含它,以便帮助量化因果效应。

图3.18 包含可能对因果推断统计分析有用的变量。图中,W 代表混杂变量,Z 是工具变量,X 是效应修饰变量,Y 是结果变量,V 是处理变量,U 是前门中介变量。

类似地, <math xmlns="http://www.w3.org/1998/Math/MathML"> X 0 X_0 </math>X0 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> X 1 X_1 </math>X1 也可能在分析中发挥作用,通过解释 Y 的其他变异来源,帮助减少因果效应统计估计中的方差。或者,我们可能关注因果效应的异质性,即因果效应如何在由 <math xmlns="http://www.w3.org/1998/Math/MathML"> X 0 X_0 </math>X0 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> X 1 X_1 </math>X1 定义的人群子集间变化。我们将在第11章更详细地讨论因果效应异质性。

  • 包含帮助DAG讲述完整故事的变量 --- 最后,包含任何能帮助DAG更好地作为交流工具的变量。考虑图3.19中的共同原因变量 D。

图3.19 包含帮助DAG讲述完整故事的变量。在这个例子中,尽管在第二步(图3.17)中我们排除了变量 D,但如果它具有交流价值,我们仍然可能希望包含 D。

在图3.17中,我们得出结论,包含了共同原因 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 0 W_0 </math>W0和 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 2 W_2 </math>W2 后,可以排除共同原因 D。但是,D 可能是领域专家理解该领域时的重要变量。虽然它在量化 V 对 U 和 Y 的因果效应时无关紧要,但如果忽略它可能会让人觉得别扭。如果是这样,包含它有助于DAG讲述一个更好的故事,展示关键变量如何与已包含的变量相关联。当你的因果DAG讲述了一个令人信服的故事时,你的因果分析也会更具说服力。

3.3.2 根据变量在推断中的角色包含变量到因果DAG中

许多因果推断领域的专家不强调用因果DAG的形式来表达他们的假设,而是根据变量在因果推断计算中的角色来指定一组相关变量。在计量经济学教学中,更常见的是关注变量在推断中的角色,而非单纯画出因果DAG。之前介绍过的角色包括"共同原因"、"工具变量"和"效应修饰变量"。我们将在第11章正式定义这些概念。

这里我要说明的是,这并不是一种互相竞争的范式。经济学家可能会说,他们关注的是在某些"效应修饰变量"条件下,变量 V 对 U 的因果效应,并计划使用"工具变量"来"调整共同原因的影响"。这些角色都对应着因果DAG中的结构;例如图3.19中, <math xmlns="http://www.w3.org/1998/Math/MathML"> W 0 , W 1 , W 2 W_0, W_1, W_2 </math>W0,W1,W2是 U 和 V 的共同原因,Z 是工具变量, <math xmlns="http://www.w3.org/1998/Math/MathML"> X 0 X_0 </math>X0 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> X 1 X_1 </math>X1​ 是效应修饰变量。假设具有这些角色的变量对你的因果效应估计分析重要,实际上就是隐含假设你的数据生成过程(DGP)遵循具有此结构的因果DAG。

实际上,给定一组变量及其角色,我们可以构建该集合所隐含的因果DAG。DoWhy 因果推断库向我们展示了如何做到这一点。

以下是代码示例,演示如何根据因果效应推断中的角色创建DAG:

python 复制代码
from dowhy import datasets
import networkx as nx
import matplotlib.pyplot as plt

sim_data = datasets.linear_dataset(     #1
    beta=10.0,
    num_treatments=1,    #2
    num_instruments=2,     #3
    num_effect_modifiers=2,     #4
    num_common_causes=5,     #5
    num_frontdoor_variables=1,   #6
    num_samples=100,
)

dag = nx.parse_gml(sim_data['gml_graph'])     #7
pos = {    #7
 'X0': (600, 350),    #7
 'X1': (600, 250),    #7
 'FD0': (300, 300),   #7
 'W0': (0, 400),   #7
 'W1': (150, 400),   #7
 'W2': (300, 400),   #7
 'W3': (450, 400),   #7
 'W4': (600, 400),   #7
 'Z0': (10, 250),    #7
 'Z1': (10, 350),    #7
 'v0': (100, 300),   #7
 'y': (500, 300)     #7
}    #7
options = {    #7
    "font_size": 12,    #7
    "node_size": 800,   #7
    "node_color": "white",   #7
    "edgecolors": "black",   #7
    "linewidths": 1,    #7
    "width": 1,    #7
}    #7
nx.draw_networkx(dag, pos, **options)    #7
ax = plt.gca()   #7
ax.margins(x=0.40)    #7
plt.axis("off")   #7
plt.show() #7

注释说明:

  • #1 datasets.linear_dataset 根据指定变量生成一个DAG。
  • #2 添加一个处理变量,比如图3.19中的 V。
  • #3 图3.19中的 Z 是工具变量示例,即因处理变量的原因,但其唯一通往结果变量的因果路径是通过处理变量。在这里我们创建两个工具变量。
  • #4 图3.19中的 <math xmlns="http://www.w3.org/1998/Math/MathML"> X 0 X_0 </math>X0 和 <math xmlns="http://www.w3.org/1998/Math/MathML"> X 1 X_1 </math>X1 是"效应修饰变量"的例子,它们帮助建模因果效应的异质性。DoWhy 将它们定义为结果的其他原因(但不一定非要如此)。这里我们创建两个效应修饰变量。
  • #5 我们添加5个共同原因,比如图3.19中的三个 <math xmlns="http://www.w3.org/1998/Math/MathML"> W 0 , W 1 , W 2 W_0, W_1, W_2 </math>W0,W1,W2。与图3.19中这些变量间的复杂结构不同,这里结构较为简单。
  • #6 前门变量位于处理与效应之间的路径上,比如图3.19中的 U。这里我们添加一个。
  • #7 这段代码提取图结构,创建绘图布局并绘制图。

该代码生成了图3.20中所示的DAG。

这种基于角色的方法生成了一个简单的模板因果DAG。它不会像图3.19那样体现细腻的结构,也会排除我们在第4步添加的有助于讲述故事的变量,比如图3.19中的 D。但它足以处理预定义的因果效应查询。当你和对因果DAG持怀疑态度但愿意讨论变量角色的合作者合作时,这是一个很好的工具。

但不要相信这种方法无DAG的说法。因果DAG其实隐含在变量角色假设背后。

这种模板方法同样可以用于其他因果查询。你也可以用这种方法先构建一个基础的因果DAG,然后在此基础上进一步完善,生成更细致的图结构。

3.4 展望:模型检验与将因果图与深度学习结合

构建因果DAG时的一个大问题是:"如果我的因果DAG是错的怎么办?"我们如何对所选的DAG有信心?下一章将探讨如何利用数据对因果DAG进行压力测试。一个关键的见解是,虽然数据永远无法证明一个因果DAG是正确的,但它可以帮助我们发现DAG何时是错误的。你还将学习因果发现(causal discovery),即一套从数据中学习因果DAG的算法。

本章中,我们使用pgmpy基于DAG结构构建了一个简单的因果图模型。在本书后续章节,你将看到如何构建利用神经网络和自动微分的更复杂的因果图模型。即使是在那些更复杂的模型中,因果马尔可夫性质以及因果DAG带来的不变性和参数模块化等优势依然成立。

总结

  • 因果有向无环图(DAG)可以表示我们对数据生成过程(DGP)的因果假设。
  • 因果DAG是一个有用的工具,用于可视化和传达你的因果假设。
  • DAG是计算机科学中的基础数据结构,支持许多快速算法,可用于因果推断任务。
  • DAG通过因果马尔可夫性质,将因果关系与条件独立性联系起来。
  • DAG可以为概率机器学习模型提供结构支架。
  • 我们可以使用多种统计参数学习方法,在DAG基础上训练概率模型,包括最大似然估计和贝叶斯估计。
  • 给定因果DAG,建模者可以选择多种因果马尔可夫核的参数化形式,从条件概率表、回归模型到神经网络等。
  • 因果充分变量集包含该集合中任意变量对之间的所有共同原因。
  • 构建因果DAG的步骤包括:先选出感兴趣的变量,扩展成因果充分集,加入对因果推断分析有用的变量,最后加入有助于DAG完整讲述故事的变量。
  • 每个因果马尔可夫核代表一个独立的因果机制,决定了子节点如何由其父节点确定(假设DAG正确)。
  • "机制独立性"指不同机制之间相互独立------对一个机制的改变不会影响其他机制。
  • 在因果DAG上构建生成模型时,每个因果马尔可夫核的参数表示了底层因果机制的编码。这带来了"参数模块化",使得你可以分别学习各参数集,甚至用常识推理来估计参数,而非依赖数据。
  • 每个因果马尔可夫核代表独立因果机制这一事实,为高级任务(如迁移学习、数据融合和不变预测)提供了不变性基础。
  • 你也可以通过变量在具体因果推断任务中的角色来指定DAG。
相关推荐
攻城狮7号3 分钟前
AI浪潮下的思辨:傅盛访谈之我见
人工智能·深度学习·agent
Francek Chen31 分钟前
【深度学习优化算法】02:凸性
人工智能·pytorch·深度学习·优化算法·凸函数
寻丶幽风36 分钟前
论文阅读笔记——Large Language Models Are Zero-Shot Fuzzers
论文阅读·pytorch·笔记·深度学习·网络安全·语言模型
要努力啊啊啊1 小时前
GQA(Grouped Query Attention):分组注意力机制的原理与实践《一》
论文阅读·人工智能·深度学习·语言模型·自然语言处理
原味奶茶_三分甜2 小时前
Qwen3高效微调
深度学习
嘻嘻哈哈OK啦5 小时前
day40打卡
人工智能·深度学习·机器学习
yzx9910138 小时前
Python开发系统项目
人工智能·python·深度学习·django
my_q10 小时前
机器学习与深度学习08-随机森林02
深度学习·随机森林·机器学习
不爱吃山楂罐头11 小时前
第三十三天打卡复习
python·深度学习
m0_6786933311 小时前
深度学习笔记25-RNN心脏病预测(Pytorch)
笔记·rnn·深度学习