Python 贝叶斯分析第三版(一)

原文:annas-archive.org/md5/23c902bea72dd81eede00e0b57b70813

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章

概率思维

概率论无非是将常识简化为计算。——皮埃尔·西蒙·拉普拉斯

在这一章中,我们将学习贝叶斯统计的核心概念以及贝叶斯工具箱中的一些工具。我们会使用一些 Python 代码,但这一章大部分内容将是理论性的;我们在这里看到的大部分概念将在本书的许多部分中反复出现。这一章理论性较强,可能会让你这个编码者有些焦虑,但我认为它会为有效地将贝叶斯统计应用于你的问题铺平道路。

在这一章中,我们将涵盖以下主题:

  • 统计建模

  • 概率与不确定性

  • 贝叶斯定理与统计推断

  • 单参数推断和经典的抛硬币问题

  • 选择先验分布及为何人们通常不喜欢它们,但其实应该喜欢

  • 传达贝叶斯分析结果

1.1 统计学、模型和本书的方法

统计学是关于收集、整理、分析和解释数据的,因此统计知识对数据分析至关重要。在数据分析中使用了两种主要的统计方法:

  • 探索性数据分析(EDA):这涉及到数值汇总,例如均值、众数、标准差和四分位数范围。EDA 还涉及通过可视化检查数据,使用你可能已经熟悉的工具,比如直方图和散点图。

  • 推断统计:这是关于超越当前数据进行推理。我们可能想要了解某种特定现象,或者我们想要对未来(尚未观察到的)数据点进行预测,或者我们需要在多个竞争性解释之间做出选择,针对同一组观察数据。总之,推断统计让我们能够从有限的数据中提取有意义的见解,并基于分析结果做出明智的决策。

天作之合

本书的重点是如何进行贝叶斯推断统计,但我们也会借用探索性数据分析(EDA)中的一些思想来总结、解释、检查并传达贝叶斯推断的结果。

大多数入门级统计课程,至少对于非统计学专业的人来说,通常被教授为一系列的“配方”,大致如下:走进统计学的储藏室,挑选一罐罐头打开,加入数据,按个人口味调味,搅拌直到得到一个一致的 p 值,最好小于 0.05。 这些课程的主要目标是教你如何选择合适的罐头。我从不喜欢这种方法,主要是因为最常见的结果是一群困惑的人,甚至在概念层面也无法理解不同学习方法的统一性。我们将采取不同的方法:我们将学习一些配方,但它们是自制的,而非罐头食品;我们将学习如何混合新鲜的原料,适用于不同的统计场合,更重要的是,这将让你能将这些概念应用到本书中的例子之外的场景中。

采用这种方法是因为两个原因:

  • 本体论:统计学是一种建模方法,统一于概率论的数学框架下。采用概率方法能够提供一个统一的视角来看待那些看似截然不同的方法;统计方法和机器学习方法在概率视角下显得更加相似。

  • 技术性:现代软件,如 PyMC,使得从业者——就像你我一样——能够相对轻松地定义和解决模型。几年前,许多这样的模型是无法求解的,或者需要高水平的数学和技术精湛。

1.2 处理数据

数据是统计学和数据科学的核心要素。数据来源于多个渠道,例如实验、计算机模拟、调查和实地观察。如果我们负责生成或收集数据,首先仔细思考我们想要回答的问题以及我们将使用哪些方法是非常重要的,只有在此之后,我们才应该开始收集数据。统计学中有一个专门研究数据收集的分支,称为实验设计。在数据泛滥的时代,我们有时会忘记,收集数据并不总是便宜的。例如,虽然大型强子对撞机LHC)每天能产生数百 TB 的数据,但它的建造过程花费了数年的人工和智力劳动。

一般来说,我们可以认为数据生成的过程是随机的,因为存在本体论、技术性和/或认识论的不确定性,也就是说,系统本质上是随机的,技术性问题会增加噪声或限制我们以任意精度进行测量,和/或存在概念性局限遮蔽了我们无法看到的细节。基于这些原因,我们总是需要在模型的框架下解读数据,包括心智模型和形式化模型。数据不直接发声,只有通过模型才有意义。

在本书中,我们假设我们已经收集好了数据。我们的数据也将是干净且整洁的,而这在现实世界中是极少见的。我们作出这些假设是为了集中讨论本书的主题。我特别想强调,尤其是对于数据分析的新人来说,即使本书没有涉及,仍然有一些重要的技能需要你去学习和实践,以便能够成功地处理数据。

分析数据时,一个非常有用的技能是知道如何在编程语言中编写代码,例如 Python。由于我们生活在一个杂乱的世界中,数据更加杂乱,因此操控数据通常是必要的,编程有助于完成任务。即使你很幸运,数据非常干净整洁,编程仍然非常有用,因为现代贝叶斯统计主要通过像 Python 或 R 这样的编程语言进行。如果你想学习如何使用 Python 来清理和操控数据,可以参考 McKinney 的 Python for Data Analysis 一书 [2022]。

1.3 贝叶斯建模

模型是对某个系统或过程的简化描述,出于某些原因,我们对此系统或过程感兴趣。这些描述是刻意设计的,只捕捉系统中最相关的方面,而不解释每一个微小的细节。这也是为什么更复杂的模型不一定是更好的模型的原因之一。有许多不同种类的模型;在本书中,我们将只讨论贝叶斯模型。我们可以用三个步骤总结贝叶斯建模过程:

  1. 给定一些数据和关于这些数据如何生成的假设,我们通过结合称为概率分布的构建模块来设计一个模型。大多数时候,这些模型是粗略的近似,但大多数情况下,这正是我们所需要的。

  2. 我们使用贝叶斯定理将数据添加到我们的模型中,并推导出结合数据和假设的逻辑后果。我们说我们正在对模型进行条件化

  3. 我们根据不同的标准评估模型及其预测,包括数据、我们对该主题的专业知识,有时还会通过与其他模型进行比较。

通常,我们会发现自己在一个迭代的非线性方式中执行这三个步骤。我们会在任何时候重新追溯我们的步骤:也许我们犯了一个愚蠢的编码错误,或者我们找到了一种方法来改变模型并改进它,或者我们意识到需要添加更多的数据或收集不同种类的数据。

贝叶斯模型也被称为概率模型,因为它们是通过概率构建的。为什么是概率?因为概率是建模不确定性的非常有用的工具;我们甚至有充分的理由认为它们是正确的数学概念。所以让我们一起走进 叉路花园 [Borges, 1944]。

1.4 贝叶斯实践者的概率入门

在本节中,我们将讨论一些对于更好理解贝叶斯方法至关重要的通用概念和重要概念。未来的章节将根据需要介绍或详细说明其他与概率相关的概念。然而,对于概率论的详细学习,我强烈推荐 Blitzstein 的《Introduction to Probability》[2019]一书。已经熟悉概率论基本要素的读者可以跳过本节或快速浏览。

1.4.1 样本空间与事件

假设我们正在调查人们对自己所在地区天气的看法。我们问了三个人是否喜欢晴天,可能的回答是“是”或“否”。所有可能结果的样本空间可以用S表示,并包含八种可能的组合:

S = {(是, 是, 是), (是, 是, 否), (是, 否, 是), (否, 是, 是), (是, 否, 否), (否, 是, 否), (否, 否, 是), (否, 否, 否)}

在这里,样本空间中的每个元素代表三个人根据被问到的顺序所做出的回答。例如,(是, 否, 是)表示第一和第三个人回答了“是”,而第二个人回答了“否”。

我们可以将事件定义为样本空间的子集。例如,事件A就是所有三个人都回答“是”时发生的事件:

A = {(是, 是, 是)}

类似地,我们可以定义事件B为至少有一个人回答“否”的情况,然后我们将得到:

B = {(是, 是, 否), (是, 否, 是), (否, 是, 是), (是, 否, 否), (否, 是, 否), (否, 否, 是), (否, 否, 否)}

我们可以使用概率来衡量这些事件发生的可能性。假设所有事件发生的概率相等,那么事件A的概率,即所有三个人都回答“是”的事件概率为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file4.jpg

在这种情况下,A中只有一个结果,而S中有八个结果。因此,A的概率为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file5.jpg

同样,我们可以计算事件B的概率,这个事件表示至少有一个人回答“否”。由于B中有七个结果,而S中有八个结果,事件B的概率为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file6.jpg

将所有事件视为同样可能的事件只是一个特殊情况,它使得计算概率更为简便。这被称为朴素的概率定义,因为它具有局限性并依赖于强假设。然而,如果我们谨慎使用,它仍然是有用的。例如,并不是所有的“是-否”问题都有 50-50 的概率。再举个例子,看到一匹紫色的马的概率是多少?正确答案可以有很大的不同,具体取决于我们是在谈论一匹真实马的自然颜色、卡通中的马、一匹穿着游行服装的马,等等。无论事件是否等可能,整个样本空间的概率总是等于 1。我们可以通过计算来验证这一点:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file7.jpg

1 是概率能达到的最高值。说P(S) = 1 就意味着S不仅非常可能,它是确定的。如果S定义了所有可能发生的事情,那么S一定会发生。

如果一个事件是不可能的,那么它的概率就是 0。我们定义事件C为三个人都说“香蕉”的事件:

C = {(香蕉, 香蕉, 香蕉)}

由于C不是S的一部分,根据定义,它是无法发生的。可以把它看作是我们的调查问卷只有两个选项,yesno。根据设计,我们的调查限制了所有其他可能的选项。

我们可以利用 Python 包含集合的事实,并定义一个 Python 函数来按照它们的朴素定义计算概率:

代码 1.1

def P(S, A): 
    if set(A).issubset(set(S)): 
        return len(A)/len(S) 
    else: 
        return 0

我把玩这个函数的乐趣留给读者了。

一种有用的理解概率的方式是将概率视为分布在样本空间中的守恒量。这意味着如果一个事件的概率增加,其他一些事件的概率必须减少,以便总概率保持为 1。可以通过一个简单的例子来说明这一点。

假设我们问某人明天是否会下雨,可能的回答是“是”或“否”。可能的回答的样本空间是S = {是, 否}。表示明天会下雨的事件是A = {是}。如果P(A)是 0.5,那么事件A的补集事件的概率,即Phttps://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file8.jpg,也必须是 0.5。如果由于某种原因P(A)增加到 0.8,那么Phttps://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file9.jpg必须减少到 0.2。这个特性适用于互斥事件,即不能同时发生的事件。例如,明天不可能同时“下雨”和“不下雨”。你可能会反驳,早上可能下雨,下午不下雨。没错,但那是不同的样本空间!

到目前为止,我们避免了直接定义概率,而是展示了一些概率的性质以及计算方法。适用于非等可能事件的概率的一个一般定义如下。给定一个样本空间S,以及事件A,它是S的一个子集,概率是一个函数P,它以A为输入,返回一个介于 0 和 1 之间的实数作为输出。函数P有一些限制,这些限制由以下三个公理定义。请记住,公理是被认为为真的陈述,我们用它作为推理的起点:

  1. 事件的概率是一个非负实数。

  2. P(S) = 1

  3. 如果A1*, A2, …是互斥事件,意味着它们不能同时发生,那么P*(A1*, A2, …*) = P(A1) + P(A2) +

如果这是一本关于概率论的书,我们可能会专门用几页来展示这些公理的后果,并提供一些练习来操作概率。这将帮助我们熟练地操作概率。然而,我们的主要关注点不在这些话题上。我展示这些公理的动机仅仅是为了说明概率是一个定义明确的数学概念,并且有规则来支配它们的运算。它们是特定类型的函数,并且并不神秘。

1.4.2 随机变量

随机变量是一个将样本空间映射到实数ℝ的函数(见图 1.1)。假设我们关注的事件是骰子的点数,映射非常简单,我们将https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/dice_1.png与数字 1 关联,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/dice_2.png与 2,依此类推。另一个简单的例子是回答问题“明天会下雨吗?”,我们可以将“是”映射为 1,将“否”映射为 0。通常,随机变量使用大写字母表示,如X,而其结果使用小写字母表示,如x。例如,如果X表示一次骰子投掷,那么x表示某个特定的整数{1,2,3,4,5,6}。因此,我们可以写P(X = 3)来表示投掷骰子得到 3 的概率。我们也可以不指定x,例如,我们可以写P(X = x)来表示得到某个值x的概率,或者写P(Xx),表示得到小于或等于x的概率。

能够将符号如https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/dice_1.png或字符串如“yes”映射到数字上,使得分析变得更加简单,因为我们已经知道如何用数字进行数学运算。随机变量也很有用,因为我们可以在不直接考虑样本空间的情况下对它们进行操作。随着样本空间变得越来越复杂,这一特点变得愈加重要。例如,在模拟分子系统时,我们需要指定每个原子的位置信息和速度;对于像蛋白质这样复杂的分子,这意味着我们需要追踪成千上万甚至更多的数字。相反,我们可以使用随机变量来总结系统的某些属性,比如总能量或系统中某些原子之间的相对角度。

如果你仍然感到困惑,那也没关系。随机变量的概念刚开始可能显得过于抽象,但我们将在全书中看到许多例子,帮助你巩固这些概念。在继续之前,我想举一个类比,希望对你有帮助。随机变量的作用类似于 Python 函数的作用。我们通常将代码封装在函数中,这样就可以将复杂的数据操作存储、重用,并通过一次调用来隐藏。更进一步,当我们拥有多个函数时,有时可以通过多种方式组合它们,比如将两个函数的输出相加,或将一个函数的输出作为另一个函数的输入。我们也可以在没有函数的情况下完成这些操作,但将内部工作抽象化不仅让代码更简洁,还帮助理解和激发新的创意。随机变量在统计学中起着类似的作用。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file10.png

图 1.1:在一个包含 5 个元素的样本空间上定义的随机变量X,其中元素包括{S[1]https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file11.jpgS[5]},其可能的值为-1、2 和 π

样本空间与ℝ之间的映射是确定性的。这里没有涉及随机性。那么,为什么我们称其为随机变量呢?因为我们可以请求该变量的值,每次请求时,得到的数字都会不同。随机性来源于与事件相关的概率。在图 1.1中,我们通过圆圈的大小表示了P

两种最常见的随机变量类型是离散型和连续型。虽然不做正式定义,但我们可以说离散型变量只取离散值,通常使用整数表示,例如 1、5、42。连续型变量则取实数值,因此我们使用浮点数来表示它们,例如 3.1415、1.01、23.4214 等等。我们使用哪种类型取决于具体问题。如果我们询问人们最喜欢的颜色,答案可能是“红色”、“蓝色”和“绿色”。这是一个离散随机变量的例子。答案是类别间的——“红色”和“绿色”之间没有中间值。但如果我们研究光的吸收特性,那么像“红色”和“绿色”这样的离散值可能不够准确,转而使用波长可能更为合适。在这种情况下,我们会得到类似 650 纳米和 510 纳米的值,并且任何中间值也都可能出现,包括 579.1 纳米。

1.4.3 离散随机变量及其分布

我们可能不仅仅想计算所有三个人都回答“是”的概率,或者掷骰子得到 3 的概率,我们可能更感兴趣的是找到所有可能答案或骰子上所有可能数字的概率列表。一旦这个列表计算出来,我们可以通过可视化查看它,或者利用它来计算其他量,比如至少得到一个“否”的概率、得到奇数的概率,或者得到大于或等于 5 的数字的概率。这个列表的正式名称是概率 分布

我们可以通过掷骰子几次并记录每个数字出现的次数来获得骰子的经验概率分布。为了将每个值转化为概率,并使整个列表成为有效的概率分布,我们需要归一化这些计数。我们可以通过将每个数字出现的次数除以掷骰子的次数来实现这一点。

经验分布非常有用,我们将广泛使用它们。但我们不会再手动掷骰子,而是将使用先进的计算方法来为我们完成这项繁重的工作;这不仅能节省我们的时间和避免无聊,还能让我们轻松地从非常复杂的分布中获取样本。不过我们现在有点急于求成。我们的优先任务是集中精力研究理论分布,因为它们在统计学中占据核心地位,原因之一是它们能够构建概率模型。

正如我们所看到的,随机变量并没有什么随机或神秘之处;它们只是数学函数的一种类型。理论概率分布也是如此。我喜欢将概率分布与圆形进行比较。因为我们在上学之前就已经熟悉圆形了,所以我们对它们不感到害怕,它们也不会让我们觉得神秘。我们可以将圆定义为平面上与另一个点(称为圆心)等距的所有点的几何空间。我们可以进一步给出这个定义的数学表达式。如果我们假设圆心的位置无关紧要,那么半径为r的圆可以简单地描述为所有满足以下条件的点集(x,y):

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file12.jpg

从这个表达式中,我们可以看到,给定参数 r,圆就被完全定义了。这就是我们绘制它所需的所有信息,也是我们计算如周长(2πr)等性质所需的所有信息。

现在请注意,所有的圆看起来都非常相似,并且任何两个半径相同的圆基本上是相同的对象。因此,我们可以把圆的家族看作是其中每个成员都恰好通过半径 r 的值与其他成员区分开来的。

到目前为止,一切顺利,但我们为什么要谈论圆呢?因为这一切都可以直接应用于概率分布。圆和概率分布都有定义它们的数学表达式,这些表达式有我们可以改变的参数,用以定义概率分布家族中的所有成员。图 1.2 显示了一个名为 BetaBinomial 的概率分布的四个成员。在图 1.2中,条形的高度表示每个 x 值的概率。低于 1 或高于 6 的 x 值的概率为 0,因为它们超出了分布的支持范围。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file13.png

图 1.2:具有参数 αβ 的 BetaBinomial 分布的四个成员

这是 BetaBinomial 分布的数学表达式:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file14.jpg

pmf 代表概率质量函数。对于离散随机变量,pmf 是返回概率的函数。在数学符号中,如果我们有一个随机变量 X,那么 pmf(x) = P(X = x)。

理解或记住 BetaBinomial 的 pmf 对我们来说并不重要。我只是展示它,以便你能看到这仅仅是另一个函数;你输入一个数字,输出另一个数字。没什么奇怪的,至少原则上是这样。我必须承认,要完全理解 BetaBinomial 分布的细节,我们需要知道什么是 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file15.jpg,即二项系数,以及B是什么,即 Beta 函数。但这与展示 x² + y² = r²并没有根本区别。

数学表达式非常有用,因为它们简洁,我们可以利用它们推导出性质。但有时这可能会太复杂,即使我们擅长数学。可视化可能是一个很好的替代(或补充),帮助我们理解概率分布。我无法在纸面上完全展示,但如果你运行以下代码,你将获得一个交互式图,每次调整alphabetan的滑块时都会更新:

代码 1.2

pz.BetaBinomial(alpha=10, beta=10, n=6).plot_interactive()

图 1.3展示了该交互式图的静态版本。黑色的点代表每个随机变量值的概率,而虚线黑色线条仅作为视觉辅助线。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file16.png

图 1.3pz.BetaBinomial(alpha=10, beta=10, n=6).plot_interactive()的输出

在 x 轴上,我们有 BetaBinomial 分布的支持,即它可以取的值,x ∈{0*,1,2,3,4,5}。在 y 轴上,与这些值相关的概率。完整列表请参见表格 1.1*。

x 值概率
00.047
10.168
20.285
30.285
40.168
50.047

表格 1.1pz.BetaBinomial(alpha=10, beta=10, n=6)的概率

请注意,对于BetaBinomial(alpha=10, beta=10, n=6)分布,{0*,1,2,3,4,5}之外的值(例如 −1,0.5,π,*42)的概率为 0。

我们之前提到过,我们可以询问随机变量获取值,每次询问时,我们都会得到不同的数字。我们可以用 PreliZ [Icazatti et al., 2023]来模拟这一过程,PreliZ 是一个用于先验引导的 Python 库。例如,考虑以下代码片段:

代码 1.3

pz.BetaBinomial(alpha=10, beta=10, n=6).rvs()

这将给我们一个 0 到 5 之间的整数。是哪一个?我们不知道!但让我们运行以下代码:

代码 1.4

plt.hist(pz.BetaBinomial(alpha=2, beta=5, n=5).rvs(1000)) 
pz.BetaBinomial(alpha=2, beta=5, n=5).plot_pdf();

我们将得到类似于图 1.4的结果。即使我们无法从随机变量中预测下一个值,我们也可以预测获得任何特定值的概率,反过来,如果我们获取了许多值,我们也可以预测它们的整体分布。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file17.png

图 1.4:灰色的点表示 BetaBinomial 样本的概率质量函数(pmf)。浅灰色为从该分布中抽取的 1,000 次样本的直方图。

在本书中,我们有时会知道给定分布的参数,并希望从中获取随机样本。其他时候,我们将遇到相反的情况:我们会有一组样本,并希望估计该分布的参数。在这两种情境之间的来回切换将随着我们深入书中的内容而变得得心应手。

1.4.4 连续随机变量及其分布

可能最广为人知的连续概率分布是正态分布,也称为高斯分布。其概率密度函数为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file18.jpg

再次强调,我们仅展示这个表达式是为了揭开其中的神秘面纱。无需过多关注其细节,除了该分布有两个参数 μ,它控制曲线的峰值位置,和 σ,它控制曲线的扩展度以外。Figure 1.5 展示了来自高斯家族的 3 个示例。如果你想深入了解这个分布,我建议你观看这个视频:www.youtube.com/watch?v=cy8r7WSuT1I

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file19.png

Figure 1.5:高斯家族的三个成员

如果你一直在关注,你可能会注意到我们使用了概率密度函数pdf)而不是概率质量函数pmf)。这不是笔误——它们实际上是两个不同的概念。我们先退后一步,思考一下:离散概率分布的输出是概率。Figure 1.2 中的条形高度或 Figure 1.3 中的点的高度就是概率。每个条形或点的高度永远不会超过 1,并且如果你将所有的条形或点相加,你总会得到 1。让我们做同样的事情,但换成 Figure 1.5 中的曲线。首先需要注意的是,我们没有条形或点;我们有一个连续、平滑的曲线。所以,也许我们可以认为这个曲线是由非常细的条形组成的,这些条形细得我们为分布支持中的每一个实数值分配一条条形,测量每条条形的高度,然后进行无限求和。这是合理的吗?

是的,但我们从中获得的结果并不立刻显现出来。这个求和会得到精确的 1 吗?还是会得到一个较大的数字呢?这个求和是有限的吗?结果是否依赖于分布的参数?

要正确回答这些问题需要测度理论,而这只是一个非常非正式的概率学入门,因此我们不会深入探讨这个问题。但本质上,答案是,对于连续随机变量,我们只能为它可能取的每个单独值分配 0 的概率;相反,我们可以为它们分配密度值,然后我们可以计算一个值范围内的概率。因此,对于高斯分布,得到精确数值 -2 的概率,即 -2 后面跟着无限多个零的小数部分,概率为 0。但是得到介于 -2 和 0 之间的数字的概率是一个大于 0 且小于 1 的数。为了找出准确答案,我们需要计算以下内容:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file20.jpg

为了计算这个,我们需要用具体的数量来替代符号。如果我们将 pdf 替换为 Normal(0*,1),并且 a = −2,b = 0,我们将得到 P(−2 < X < 0)≈ 0.*477,这就是 Figure 1.6 中的阴影区域。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file21.png

图 1.6:黑线表示参数为 mu=0 和 sigma=1 的高斯分布的 pdf,灰色区域表示一个值大于 -2 且小于 0 的概率

你可能还记得,我们可以通过求矩形面积的和来近似一个积分,随着矩形底边长度的缩小,这种近似会变得越来越准确(请参阅维基百科上的 Riemann 积分 条目)。基于这个思路,并使用 PreliZ,我们可以估算 P(−2 < X < 0) 为:

代码 1.5

dist = pz.Normal(0, 1) 
a = -2 
b = 0 
num = 10 
x_s = np.linspace(a, b, num) 
base = (b-a)/num 
np.sum(dist.pdf(x_s) * base)

如果我们增加 num 的值,结果会得到更精确的近似值。

1.4.5 累积分布函数

我们已经看过了概率质量函数(pmf)和概率密度函数(pdf),但这些并不是描述分布的唯一方式。另一种选择是累积分布函数cdf)。随机变量 X 的 cdf 是函数 F[X],由 F**X = P(Xx) 给出。换句话说,cdf 是对这个问题的回答:得到小于或等于 x 的数值的概率是多少?在图 1.7 的第一列中,我们可以看到 BetaBinomial 分布的 pmf 和 cdf,在第二列中,则展示了高斯分布的 pdf 和 cdf。请注意,离散变量的 cdf 会“跳跃”,而连续变量的 cdf 则是平滑的。每次跳跃的高度代表一个概率——只需将其与点的高度进行比较。我们可以通过绘制连续变量的 cdf 来作为概率为零的视觉证明,任何连续变量的数值都没有“跳跃”,这等同于说这些跳跃的高度正好为零。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file22.png

图 1.7:BetaBinomial 分布的 pmf 及其对应的 cdf 和正态分布的 pdf 及其对应的 cdf

仅通过查看 cdf,就更容易找到某个数字小于,比如 1 的概率。我们只需要在 x 轴上找到 1 的值,向上移动直到穿过黑线,然后查看 y 轴的值。例如,在图 1.7 中,对于正态分布,我们可以看到该值位于 0.75 和 1 之间。假设它大约是 ≈ 0*.*85。这比使用 pdf 要难得多,因为我们需要将 1 以下的整个区域与总区域进行比较才能得到答案。人类在判断面积方面不如在判断高度或长度时准确。

1.4.6 条件概率

给定两个事件 AB,且 P(B) > 0,条件概率 P(A|B) 定义为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file23.jpg

P(A,B) 是事件 A 和事件 B 同时发生的概率。P(A|B) 被称为条件概率,它表示在已知(或假设、想象、假定等)B 发生的前提下,事件 A 发生的概率。例如,路面湿了的概率与已知下雨时路面湿了的概率是不同的。

条件概率可以大于、小于或等于无条件概率。如果知道 B 对我们理解 A 没有提供任何信息,那么 P(A|B) = P(A)。只有当 AB 互相独立时,这个关系才成立。相反,如果知道 B 给我们提供了关于 A 的有用信息,那么条件概率可能大于或小于无条件概率,具体取决于知道 B 是否让 A 更加可能或更不可能。让我们通过一个简单的例子来看看,假设我们掷一个公平的六面骰子。掷到数字 3 的概率是多少?P(骰子 = 3) = https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file25.jpg,因为对于一个公平的六面骰子,每个数字的机会是一样的。那么,假如我们已经知道掷到的是奇数,掷到数字 3 的概率是多少?P(骰子 = 3 | 骰子 = {1,3,5}) = https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file27.jpg,因为如果我们知道结果是奇数,那么可能的数字只有 {1*,3,5},而且它们的机会是相等的。最后,如果我们已经知道掷到的是偶数,掷到 3 的概率是多少?这是 P(骰子 = 3 | 骰子 = {2,4,6}) = 0,因为如果我们知道结果是偶数,那么唯一可能的数字是 {2,4,*6},所以掷到 3 的概率为 0。

如我们从这些简单的例子中可以看到,通过对观测数据进行条件化,我们正在改变样本空间。当我们询问 P(骰子 = 3) 时,我们需要评估样本空间 S = {1*,2,3,4,5,6},但当我们在已知掷到的是偶数的情况下进行条件化时,新的样本空间变为 T = {2,4,*6}。

条件概率是统计学的核心,无论你面对的问题是掷骰子还是构建自动驾驶汽车。

图 1.8 的中央面板使用灰度显示了联合分布p(A,B),其中较深的颜色表示较高的概率密度。我们可以看到联合分布呈现拉长的形态,表明A的值越高,B的值也越高,反之亦然。知道了A的值,就能推测出B的值,反之亦然。在图 1.8 的顶部和右侧边缘分别展示了边际分布 p(A)和p(B)。要计算A的边际分布,我们需要对p(A,B)进行对所有B值的平均,直观地说,这就像把二维对象(联合分布)投影到一维。B的边际分布也以类似的方式计算。虚线表示 3 个不同B值下的条件概率 p(A|B)。我们通过在给定B值时切割联合分布p(A,B)来得到它们。我们可以把这看作是在已观察到特定的B值时,A的分布。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file30.png

图 1.8:联合概率p(A,B)、边缘概率p(A)和p(B),以及条件概率p(A|B)之间关系的表示

1.4.7 期望值

如果X是一个离散的随机变量,我们可以计算其期望值,公式如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file31.jpg

这只是均值或平均值。

你可能已经习惯于计算样本或一组数字的均值或平均值,无论是手动、用计算器,还是使用 Python。但请注意,在这里我们讨论的不是一堆数字的均值,而是分布的均值。一旦我们定义了分布的参数,原则上可以计算其期望值。它们是分布的特性,就像圆的周长是圆的一个特性,定义圆的半径后就可以确定。

另一个期望值是方差,我们可以用它来描述分布的离散程度。方差在许多统计计算中自然出现,但在实践中,通常使用标准差,它是方差的平方根。原因是标准差的单位与随机变量相同。

均值和方差通常被称为分布的。其他的矩包括偏度,它告诉我们分布的偏斜程度,以及峰度,它告诉我们分布尾部或极值的行为[Westfall, 2014]。图 1.9 展示了不同分布及其均值μ、标准差σ、偏度γ和峰度的例子 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/K.png。请注意,对于某些分布,某些矩可能没有定义,或者它们可能是无穷大。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file32.png

图 1.9:四个分布及其前四个矩

现在我们已经了解了一些概率论的基本概念和术语,我们可以继续进入大家期待的时刻了。

1.4.8 贝叶斯定理

毫不拖延,让我们以它的威严,思考贝叶斯定理:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file33.jpg

好吧,这并不是什么令人印象深刻的东西,对吧?它看起来像是小学的公式,但引用理查德·费曼的话,这就是你需要了解的贝叶斯统计的全部内容。了解贝叶斯定理的来源将帮助我们理解它的意义。根据乘积规则,我们有:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file34.jpg

这也可以写成:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file35.jpg

由于左侧的项对于两个方程是相等的,我们可以将它们合并并写出:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file36.jpg

重新排列后,我们得到贝叶斯定理:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file37.jpg

为什么贝叶斯定理如此重要?让我们看看。

首先,它说p(θ|Y)不一定等于p(Y|θ)。这是一个非常重要的事实——一个即使是受过统计学和概率学训练的人也容易在日常情况下忽视的事实。我们通过一个简单的例子来澄清为什么这些量不一定相同。一个人是教皇的概率,给定这个人是阿根廷人,并不等同于,给定这个人是教皇,成为阿根廷人的概率。由于大约有 4700 万阿根廷人,而其中只有一个是现任教皇,我们有p(教皇 | 阿根廷人) ≈https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file39.jpg,同时我们也有p(阿根廷人 | 教皇) = 1。

如果我们将θ替换为“假设”,将Y替换为“数据”,贝叶斯定理告诉我们如何计算在给定数据Y的情况下,假设θ的概率,这也是你会在很多地方看到的贝叶斯定理的解释。但是,如何将一个假设转化为可以放入贝叶斯定理中的东西呢?嗯,我们通过使用概率分布来做到这一点。所以,一般来说,我们的假设是一个非常非常非常狭义的假设;如果我们谈论的是找到适合我们模型的参数的值,即概率分布的参数,那么我们会更精确。顺便说一句,不要试图将θ设定为“独角兽存在”的陈述,除非你愿意构建一个现实的独角兽存在的概率模型!

贝叶斯定理是贝叶斯统计的核心。正如我们在第二章中看到的,使用像 PyMC 这样的工具解放了我们每次构建贝叶斯模型时都必须显式书写贝叶斯定理的需要。然而,了解其各个部分的名称是很重要的,因为我们将不断引用它们,而且理解每个部分的含义也很重要,因为这有助于我们构建模型的概念。所以,让我现在带着标签重写贝叶斯定理:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file41.jpg

先验分布应当反映我们在看到数据之前对参数θ值的了解,Y。如果我们什么都不知道,就像乔恩·雪诺那样,我们可以使用平坦的先验,这不会传达太多信息。通常来说,我们可以比平坦的先验做得更好,正如我们在本书中将要学到的那样。先验的使用是为什么一些人仍然认为贝叶斯统计是主观的,尽管先验只是我们在建模时所做的另一种假设,因此它和其他假设(如似然)一样主观(或客观)。

似然是我们在分析中引入数据的方式。它是给定参数下数据的合理性表达。在某些文本中,你会发现有人称这个术语为采样模型、统计模型或仅仅是模型。我们将坚持使用“似然”这个名称,并将建模先验和似然的组合。

后验分布是贝叶斯分析的结果,反映了我们关于一个问题的所有已知信息(基于我们的数据和模型)。后验分布是模型参数的概率分布,而不是单一的值。这个分布是先验和似然之间的平衡。有一个著名的笑话:贝叶斯学家是那种模糊地期望看到一匹马,却只看到了一只驴,并坚信自己看到了骡子的人。听到这个笑话后,解释说如果似然和先验都很模糊,那么得到的后验反映的就是对看到骡子的模糊信念,而不是强烈的信念,这会大大破坏气氛。无论如何,我喜欢这个笑话,也喜欢它传达了后验作为先验和似然之间某种妥协的想法。从概念上讲,我们可以将后验视为根据(新)数据更新后的先验。理论上,一个分析的后验可以作为新分析的先验(但实际上,生活可能会更复杂)。这使得贝叶斯分析特别适用于分析按顺序提供的数据。一个例子可能是自然灾害的早期预警系统,它处理来自气象站和卫星的在线数据。有关更多细节,请阅读关于在线机器学习方法的资料。

最后一项是边际似然性,有时也称为证据。严格来说,边际似然性是观察数据的概率,取决于所有参数可能取值的平均值(按照先验分布规定)。我们可以将其表示为 ∫ [Θ]^(p(Y |θ)p(θ)dθ。我们直到第五章5 才会真正关心边际似然性。但目前,我们可以将其视为一个归一化因子,确保后验分布是一个合适的 pmf 或 pdf。如果忽略边际似然性,我们可以将贝叶斯定理写为一个比例关系,这也是一种常见的贝叶斯定理表示方式。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file42.jpg

理解贝叶斯定理中每个项的确切作用需要一些时间和实践,并且需要通过几个例子来帮助理解,但这就是本书其余部分的目的所在。

1.5 概率解释

概率可以通过各种有用的方式来解释。例如,我们可以认为 P(A) = 0*.*125 意味着如果我们多次重复调查,我们期望这三个人大约 12.5%的时间会回答“是”。我们正在将概率解释为长期实验的结果。这是一种非常常见且有用的解释。它不仅可以帮助我们思考概率,还可以提供一种经验方法来估计概率。我们想知道,如果汽车轮胎充气超过制造商推荐的标准,爆胎的概率是多少吗?只需充气大约 120 个轮胎,你就能得到一个不错的近似值。这通常被称为频率主义解释。

概率的另一种解释,通常称为主观或贝叶斯解释,认为概率可以被解释为个体对事件不确定性的度量。在这种解释下,概率与我们对世界的知识状态相关,并不一定基于重复试验。在这种概率定义下,提出关于火星上是否有生命的概率、电子质量为 9*.*1 × 10^(−31) kg 的概率,或者 1816 年 7 月 9 日布宜诺斯艾利斯是否是晴天的概率,都是有效且自然的。这些都是一次性事件。我们不能重新创造 1 百万个宇宙,每个宇宙都有一个火星,并检查其中有多少个发展出了生命。当然,我们可以做这个心理实验,只要长期频率仍然是一个有效的心理框架。

有时贝叶斯对概率的解释被描述为个人信念;我不喜欢这样。我认为这可能会导致不必要的混淆,因为信念通常与信仰或没有依据的主张有关。这种关联很容易让人认为贝叶斯概率,进而贝叶斯统计学,比其他方法不那么客观或不那么科学。我还认为,这种说法有助于产生对统计学中先验知识角色的混淆,让人们误以为客观或理性就意味着不使用先验信息。

贝叶斯方法与我们拥有的任何其他成熟的科学方法一样主观(或客观)。让我用一个例子来解释:火星上是否存在生命,答案是二元的,类似是或不是的问题。但考虑到我们无法确认这一事实,合理的做法是尝试找出火星上生命存在的可能性。为了回答这个问题,任何诚实且具有科学思维的人都会使用所有相关的火星地球物理数据、所有关于生命所需条件的生物化学知识等等。这个回答必然是关于我们知识状态的,不同的人可能会有不同的看法,甚至得出不同的概率。但至少,从原则上讲,他们都会能够为自己的数据、方法、建模决策等提供支持论据。关于火星生命的科学理性辩论不容许像“天使告诉我有小绿人”这样的论据。然而,贝叶斯统计学只是一种使用概率作为构建模块来做科学陈述的程序。

1.6 概率、不确定性和逻辑

概率可以帮助我们量化不确定性。如果我们对一个问题没有信息,那么可以合理地说每一个可能的事件发生的概率是相等的。这相当于对每一个可能的事件赋予相同的概率。在没有信息的情况下,我们的不确定性是最大的,我并不是随便这么说;这是我们可以通过概率来计算的。如果我们知道某些事件更可能发生,那么可以通过给这些事件赋予更高的概率,而其他事件赋予较低的概率来正式表示这一点。请注意,当我们在统计学中谈论事件时,并不仅仅局限于可能发生的事情,比如小行星撞击地球或我姨妈的 60 岁生日派对。事件只是一个变量可以取的任何可能值(或值的子集),比如你年龄超过 30 岁、萨赫托尔特的价格,或明年全球将售出的自行车数量。

概率的概念也与逻辑学有关。在经典逻辑下,我们只能有真或假的命题。在贝叶斯概率定义下,确定性只是一个特殊的情况:一个真实的命题的概率是 1,而一个虚假的命题的概率是 0。只有在拥有决定性数据表明有东西在生长、繁殖以及进行其他我们认为与生物体相关的活动时,我们才会给“火星上有生命”这一命题分配概率为 1。

然而,请注意,分配一个概率为 0 更难,因为我们总是可以认为火星上还有一些未探索的区域,或者我们在某些实验中犯了错误,或者有其他几个原因可能导致我们错误地认为火星上没有生命,即使实际上是有的。这与克劳梅尔法则有关,该法则指出我们应该将概率为 0 或 1 的命题保留给逻辑上真实或虚假的命题。有趣的是,可以证明,如果我们想要将逻辑扩展以包括不确定性,我们必须使用概率和概率理论。

如我们很快就会看到的,贝叶斯定理只是概率规则的逻辑结果。因此,我们可以将贝叶斯统计看作是逻辑的扩展,它在我们处理不确定性时非常有用。因此,采用贝叶斯方法的一个理由是承认不确定性是普遍存在的。我们通常不得不处理不完整或嘈杂的数据,我们本质上受到进化塑造的灵长类大脑的局限,等等。

贝叶斯精神

概率用于衡量我们对参数的不确定性,而贝叶斯定理是一个在有新数据的情况下正确更新这些概率的机制,希望能够减少我们的不确定性。

1.7 单一参数推断

现在我们知道了贝叶斯统计是什么,接下来让我们通过一个简单的例子来学习如何进行贝叶斯统计。我们将从推断一个单一的未知参数开始。

1.7.1 投硬币问题

投硬币问题,或者如果你想在聚会上显得更专业的话可以称其为 BetaBinomial 模型,是统计学中的一个经典问题,问题是这样的:我们多次掷硬币并记录正反面朝上的次数。基于这些数据,我们试图回答这样的问题:这枚硬币公平吗?或者,更一般地说,这枚硬币有多偏?虽然这个问题可能听起来很无聊,但我们不应该低估它。

投硬币问题是学习贝叶斯统计基础的一个很好的例子,因为它是一个简单的模型,我们可以轻松地解决和计算。此外,许多实际问题由二元的、互斥的结果组成,例如 0 或 1、正或负、奇数或偶数、垃圾邮件或非垃圾邮件、热狗或不是热狗、猫或狗、安全或不安全、健康或不健康。因此,即使我们在谈论硬币时,这个模型也适用于任何这些问题。为了估计硬币的偏向性,并且通常来说,回答贝叶斯环境中的任何问题,我们将需要数据和一个概率模型。对于这个例子,我们假设我们已经抛掷了几次硬币,并且我们有观察到的正面朝上的次数记录,所以数据收集部分已经完成。获得模型将需要更多的努力。由于这是我们的第一个模型,我们将明确地写出贝叶斯定理并完成所有必要的数学运算(不用担心,我保证这不会痛苦),并且我们将非常慢地进行。从 2 开始,我们将使用 PyMC 和我们的计算机来为我们做数学运算。

我们将做的第一件事是推广偏向的概念。我们将说,偏向为 1 的硬币总是会正面朝上,偏向为 0 的硬币总是会反面朝上,而偏向为 0.5 的硬币在一半的时间里会正面朝上,另一半时间会反面朝上。为了表示偏向,我们将使用参数θ,而为了表示多次投掷中正面朝上的总次数,我们将使用变量Y。根据贝叶斯定理,我们必须指定先验分布p(θ)和似然函数p(Y | θ),我们将使用这些。让我们从似然函数开始。

1.7.2 选择似然函数

假设只有两种可能的结果——正面或反面——同时假设一次硬币投掷不会影响其他投掷,也就是说,我们假设硬币投掷是相互独立的。我们进一步假设所有硬币投掷来自同一分布。因此,随机变量硬币投掷是独立同分布iid)变量的一个例子。我希望你同意这些假设对于我们的问题来说是非常合理的。基于这些假设,似然函数的一个合适候选是二项分布:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file43.jpg

这是一种离散分布,返回在N次硬币投掷(或一般的试验或实验)中得到y次正面(或一般的成功)的概率,前提是固定的θ值。

图 1.10 显示了来自二项分布族的九个分布;每个子图都有一个图例,表示参数的值。请注意,对于这个图,我没有省略 y 轴上的值。我这样做是为了让你自己检查,如果你将所有柱子的高度相加,你将得到 1,也就是说,对于离散分布,柱子的高度代表的是实际概率。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file44.png

Figure 1.10: 二项家族的九位成员

二项分布是似然性的合理选择。我们可以看到θ表示抛硬币时得到头的可能性有多大。当N=1 时更容易看到,但对于任何N的值,只需比较y=1(头)时θ的值与柱状图的高度即可。

1.7.3 选择先验分布

作为先验,我们将使用贝塔分布,这在贝叶斯统计中非常常见,外观如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file45.jpg

如果我们仔细观察,会发现贝塔分布与二项分布看起来很相似,除了第一项。 Γ是希腊大写 gamma 字母,代表伽玛函数,但这并不是真正重要的。对我们而言重要的是,第一项是一个归一化常数,确保分布积分为 1。从前述公式中可以看出,贝塔分布有两个参数,αβFigure 1.11 展示了贝塔家族的九位成员。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file46.png

Figure 1.11: 贝塔家族的九位成员

我喜欢贝塔分布及其所有可能的形状,但为什么我们要在我们的模型中使用它呢?使用贝塔分布来处理此类问题有许多理由。其中之一是贝塔分布限制在 0 到 1 之间,与我们的θ参数相同。一般来说,我们在想要建模二项变量的比例时使用贝塔分布。另一个原因是其多功能性。正如我们在Figure 1.11 中看到的,该分布采用多种形状(均限制在[0,1]区间内),包括均匀分布、类似正态分布和 U 形分布。

作为第三个原因,Beta 分布是二项分布(我们作为似然使用的分布)的共轭先验。共轭先验是指,当与给定的似然结合使用时,返回的后验具有与先验相同的函数形式。简单来说,每次我们使用 Beta 分布作为先验,二项分布作为似然时,我们将得到 Beta 作为后验分布。还有其他共轭先验的配对;例如,正态分布是其自身的共轭先验。多年来,贝叶斯分析一直局限于使用共轭先验。共轭性确保了后验的数学可解性,这一点非常重要,因为贝叶斯统计中一个常见的问题是我们最终得到一个无法解析求解的后验。在开发适合的计算方法解决概率方法之前,这曾是一个关键的障碍。从第 2 章开始,我们将学习如何使用现代计算方法解决贝叶斯问题,无论我们选择是否使用共轭先验。

1.7.4 获取后验

让我们记住,贝叶斯定理表明后验与似然和先验的乘积成正比。因此,对于我们的问题,我们需要将二项分布和 Beta 分布相乘:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file47.jpg

我们可以通过去掉所有与 θ 无关的项来简化这个表达式,结果仍然有效。因此,我们可以写成:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file48.jpg

重新排列它,并注意到这具有 Beta 分布的形式,我们得到:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file49.jpg

基于这个解析表达式,我们可以计算后验。图 1.12 显示了 3 个先验和不同试验次数下的结果。以下代码块展示了生成 图 1.12 的要点(省略了绘图所需的代码)。

代码 1.6

n_trials = [0, 1, 2, 3, 4, 8, 16, 32, 50, 150] 
n_heads = [0, 1, 1, 1, 1, 4, 6, 9, 13, 48] 
beta_params = [(1, 1), (20, 20), (1, 4)] 

x = np.linspace(0, 1, 2000) 
for idx, N in enumerate(n_trials): 
    y = n_heads[idx] 
    for (*α*_prior, *β*_prior) in beta_params: 
        posterior = pz.Beta(*α*_prior + y, *β*_prior + N - y).pdf(x)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file50.png

图 1.12:第一个子图显示了 3 个先验。其余的显示了随着新数据的到来,更新后的结果。

图 1.12 的第一个子图中,我们有零次试验,因此三条曲线表示我们的先验:

  • 均匀先验(黑色):这表示在先验中所有偏差的可能值都是等概率的。

  • 高斯型先验(深灰色):这围绕 0.5 进行集中,表示该先验与信息兼容,表明硬币正反面的概率大致相同。我们也可以说,这个先验与硬币公平的知识是兼容的。

  • 倾斜的先验(浅灰色):这将大部分权重放在尾部偏向的结果上。

其余的子图展示了后续试验的后验分布。每个子图的图例中标明了试验次数(或掷硬币次数)和正面朝上的次数。还有一个黑点在 0.35 处,表示θ的真实值。当然,在实际问题中,我们并不知道这个值,它在这里仅用于教学目的。图 1.12,可以帮助我们深入理解贝叶斯分析,因此拿起你的咖啡、茶或最喜欢的饮品,我们来花点时间理解它:

  • 贝叶斯分析的结果是一个后验分布——它不是一个单一值,而是给定数据和模型下的一个可能值的分布。

  • 最可能的值由后验分布的众数(分布的峰值)给出。

  • 后验分布的扩展与参数值的不确定性成正比;分布越广泛,我们的确定性就越低。

  • 直观上,当我们观察到更多支持某一结果的数据时,我们对结果的信心就更强。因此,即使数值上https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file51.jpg = https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file52.jpg = 0*.*5,在八次试验中看到四次正面朝上比在两次试验中看到一次正面朝上能更有信心地认为偏差是 0.5。这个直觉在后验分布中有所体现,您可以自己检查,如果注意观察第三和第六个子图中的(黑色)后验分布;虽然众数相同,但第三个子图的扩展(不确定性)比第六个子图更大。

  • 给定足够多的数据,两个或多个具有不同先验的贝叶斯模型将趋向于收敛到相同的结果。在无限数据的极限下,无论我们使用什么先验,所有的模型都将提供相同的后验分布。

  • 记住,无限是一个极限,而不是一个数字,因此从实际角度来看,对于有限且相对较小的数据点,我们可能得到几乎等同的后验分布。

  • 后验收敛到相同分布的速度取决于数据和模型。我们可以看到,从黑色先验(均匀分布)和灰色先验(偏向尾部)得到的后验分布收敛得更快,几乎相同,而从深灰色先验(偏向集中分布)得到的后验分布则收敛得较慢。即使经过 150 次试验,仍然很容易识别出深灰色后验分布与另外两个分布的区别。

  • 从图中不容易看出来的一点是,如果我们按顺序更新后验,就像一次性计算后验一样,最终会得到相同的结果。我们可以计算 150 次后验,每次加入一个新观察值,并将获得的后验作为新的先验,或者我们可以一次性计算 150 次掷硬币的后验。结果将完全相同。这个特性不仅非常合理,而且为我们提供了一种在获取新数据时更新估计值的自然方法,这种情况在许多数据分析问题中都很常见。

1.7.5 先验的影响

从前面的例子可以清楚地看出,先验可以影响推断。这是正常的——先验本来就应该这样做。也许最好根本不设置先验,那样建模不就更简单了吗?嗯,不一定。如果你不设置先验,别人会为你设置。有时这没问题——默认先验是有用的,也有它的作用——但有时最好能有更多的控制权。让我来解释一下。

我们可以认为每一个(统计)模型,不论是否是贝叶斯模型,都有某种类型的先验,即使先验没有明确设置。例如,许多在频率派统计中常用的程序可以看作是在某些条件下(如平坦先验)贝叶斯模型的特例。一种常见的参数估计方法被称为最大似然估计;这种方法避免设置先验,只通过找到最大化似然的单一值来工作。这个值通常通过在我们估计的参数名称上方加一个小帽子来表示,例如https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/hat_theta.png。与后验估计不同,后验估计是一个分布,而https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/hat_theta.png是一个点估计,是一个数值。对于抛硬币问题,我们可以通过解析方法计算出来:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file53.jpg

如果你回到图 1.12,你将能自己检查出黑色后验的模态(即与均匀/平坦先验对应的那个模态)与每个子图中计算得到的https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/hat_theta.png值一致。这不是巧合;这是因为设置均匀先验后,再取后验的模态相当于最大似然估计。

我们无法避免先验,但如果将其纳入分析中,我们可以获得一些潜在的好处。最直接的好处是我们得到一个后验分布,它是一个合理值的分布,而不仅仅是最可能的值。拥有一个分布比单一的点估计更具信息性,正如我们所看到的,分布的宽度与我们对估计的不确定性有关。另一个好处是,计算后验意味着对先验进行平均。这可以导致更难以过拟合的模型和更稳健的预测[Wilson 和 Izmailov, 2022]。

先验分布能为我们带来其他好处。从下一章开始,我们将使用数值方法来获得后验分布。这些方法看起来像魔法,直到它们不再有效。统计计算的民间定理指出:“当你遇到计算问题时,通常是模型出了问题”[Gelman,2008]。有时候,明智的先验选择可以使推断变得更容易或更快速。需要指出的是,我们并不提倡为了加速推断而特意设置先验,但通常情况下,通过考虑先验,我们可以得到更快速的模型。

先验的一个优点,有时被忽视了,就是必须考虑先验可能迫使我们更深入地思考我们要解决的问题以及我们所拥有的数据。有时候,建模过程本身就能带来更好的理解,不管我们最终如何拟合数据或做出预测。通过明确先验,我们能够得到更透明的模型,这意味着这些模型更容易被批评、调试(广义上讲)、向他人解释,并且有可能得到改善。

1.8 如何选择先验

初学贝叶斯分析的人(以及该范式的反对者)通常对如何选择先验感到有些紧张。通常,他们担心先验分布会使数据无法自主表达!没关系,但我们必须记住,数据并不会“说话”;充其量,数据只是低声细语。我们只能在模型的上下文中理解数据,包括数学模型和心理模型。科学史上有很多例子表明,相同的数据曾让人们对相同的话题产生不同的看法,即便你基于正式模型来形成观点,也会发生这种情况。

有些人喜欢使用非信息性先验(也称为平坦的、模糊的或扩散的先验)这一想法。这些先验对分析的影响最小。虽然在某些问题中使用它们是可行的,但真正推导出非信息性先验是很困难的,甚至是不可能的。此外,通常我们能够做得更好,因为我们通常拥有一些先验信息。

在本书中,我们将遵循 Gelman、McElreath、Kruschke 等人的建议,并偏好弱信息先验。对于许多问题,我们通常对一个参数可能取的值有所了解。我们可能知道某个参数只能取正值,或者知道它可能的范围,或者预期它接近零或在某个值的上下方。在这种情况下,我们可以使用先验来在模型中引入一些弱信息,而不必担心过于强势。因为这些先验有助于保持后验分布在合理的范围内,所以它们也被称为正则化先验。

信息性先验是非常强的先验,能传递大量信息。使用它们也是一个有效的选项。根据你的问题,从领域知识中找到优质的信息并将其转化为先验可能容易,也可能不容易。我曾经从事结构生物信息学工作。在这个领域,大家一直在使用贝叶斯和非贝叶斯方法,利用所有能够获取的先验信息来研究和预测蛋白质的结构。这是合理的,因为几十年来,我们通过数千个精心设计的实验收集了大量数据,因此我们手头有大量可信的先验信息。不使用这些信息简直荒谬!抛弃有价值的信息,绝对没有什么“客观”或“科学”可言。如果你有可靠的先验信息,就应该使用它。试想一下,如果每次汽车工程师需要设计一辆新车时,都必须从头开始,重新发明内燃机、车轮,甚至是汽车的基本概念,那该多么浪费时间!

PreliZ 是一个全新的 Python 库,用于先验知识的引出 [Mikkola et al., 2023, Icazatti et al., 2023]。它的使命是帮助你引出、表示和可视化你的先验知识。例如,我们可以让 PreliZ 计算一个满足一组约束条件的分布的参数。假设我们想找到一个 Beta 分布,其中 90% 的质量位于 0.1 和 0.7 之间,那么我们可以写:

代码 1.7

dist = pz.Beta() 
pz.maxent(dist, 0.1, 0.7, 0.9)

结果是一个具有参数α = 2*.5 和 β = 3.*6(四舍五入到小数点后一位)的 Beta 分布。pz.maxent 函数计算了在我们指定约束条件下的最大 分布。为什么是最大熵分布?因为这相当于在这些约束条件下计算最不具信息量的分布。默认情况下,PreliZ 会绘制如下所示的分布:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file54.png

图 1.13:最大熵 Beta 分布,90% 的质量位于 0.1 和 0.7 之间

由于先验引出有很多方面,PreliZ 提供了许多其他方法来引出先验。如果你有兴趣了解更多关于 PreliZ 的信息,可以查看 preliz.readthedocs.io 上的文档。

构建模型是一个迭代过程;有时迭代只需几分钟,有时则可能需要几年。可重复性很重要,模型中的透明假设有助于提高其可重复性。如果我们对某个特定的先验(或似然)没有把握,我们可以自由地为给定的分析使用多个先验(或似然);探索不同先验的效果也能带来有价值的信息。建模过程的一部分是质疑假设,先验(和似然)正是其中的一部分。不同的假设将导致不同的模型,可能还会得出不同的结果。通过使用数据和我们对问题的领域知识,我们能够比较不同的模型,并在必要时决定一个优胜者。第五章将专门讨论这个问题。由于先验在贝叶斯统计中的核心作用,我们将在面对新问题时继续讨论它们。因此,如果你对这个讨论有疑问并感到有些困惑,不用担心,保持冷静,别着急,很多人也困惑了几十年,这个讨论仍在继续。

1.9 贝叶斯分析的沟通

创建报告和传达结果是统计学和数据科学实践的核心内容。在本节中,我们将简要讨论在使用贝叶斯模型时,进行这项任务的一些特殊之处。在未来的章节中,我们将继续讨论这个重要问题的例子。

1.9.1 模型符号与可视化

如果你想传达分析结果,也应该传达你所使用的模型。表示概率模型的一种常见符号是:

θ ∼ Beta(α,β)
y ∼ Bin(n = 1*,p* = θ)

这就是我们用于抛硬币示例的模型。如你所记得,∼ 符号表示它左边的变量是一个随机变量,按照右边的分布进行分布。在许多上下文中,这个符号用来表示某个变量大致取某个值,但在谈论概率模型时,我们会把这个符号读作 按……分布。因此,我们可以说 θ 按 Beta 分布,参数为 αβ,而 y 按 Binomial 分布,参数为 n = 1 和 p = θ。同样的模型可以通过克鲁什克图形象地表示,如 图 1.14 所示。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file55.png

图 1.14:BetaBinomial 模型的克鲁什克图

在第一层,我们有生成 θ 值的先验,然后是似然,最后一行是数据 y。箭头表示变量之间的关系,符号 ∼ 表示变量的随机性质。书中的所有克鲁什克图都是使用 Rasmus Bååth 提供的模板制作的(www.sumsar.net/blog/2013/10/diy-kruschke-style-diagrams/)。

1.9.2 总结后验

贝叶斯分析的结果是后验分布,所有关于参数的信息(在给定模型和数据集的情况下)都包含在后验分布中。因此,通过总结后验,我们实际上是在总结模型和数据的逻辑结果。一种常见做法是报告每个参数的均值(或众数或中位数),以了解分布的位置,同时报告一些离散度量(如标准差),以了解我们估计的不确定性。标准差对于类似正态分布的分布效果良好,但对于其他类型的分布(如偏斜分布)可能会产生误导。

一种常用的设备来总结后验分布的广度是使用最高密度区间HDI)。HDI 是包含给定概率密度部分的最短区间。如果我们说某个分析的 95% HDI 是 [2*,*5],我们意味着根据我们的数据和模型,相关参数的值介于 2 到 5 之间,且其概率为 0.95。选择 95%、50% 或其他任何值并没有什么特别之处。我们可以自由选择 82% 的 HDI 区间。如果愿意,理想情况下,选择的依据应根据上下文而定,而不是自动的,但选择一个常见值(如 95%)也没问题。为了提醒这种选择的任意性,ArviZ 的默认值是 94%。

ArviZ 是一个用于贝叶斯模型探索性分析的 Python 包,提供了许多帮助我们总结后验的功能。其中一个功能是 az.plot_posterior,我们可以使用它生成一个包含θ的均值和 HDI 的图表。分布不必是后验分布,任何分布都可以使用。图 1.15 显示了来自 Beta 分布的随机样本的结果:

代码 1.8

np.random.seed(1) 
az.plot_posterior({'*θ*':pz.Beta(4, 12).rvs(1000)})

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file56.png

图 1.15:来自 Beta 分布的样本的 KDE 图,包含其均值和 94% HDI

不是置信区间

如果你熟悉频率学派的范式,请注意,HDI 与置信区间不同。在频率学派框架下,参数是由设计固定的;频率学派的置信区间要么包含真实参数值,要么不包含。而在贝叶斯框架中,参数是随机变量,因此我们可以谈论某个参数具有特定值或位于某个区间内的概率。置信区间的直觉性较差,容易被误解,人们常常把频率学派的置信区间当作贝叶斯可信区间来讨论。

1.10 总结

我们以简短的统计建模讨论开始了贝叶斯之旅,内容包括概率、条件概率、随机变量、概率分布和贝叶斯定理。然后我们用抛硬币问题作为借口,引入了贝叶斯建模和数据分析的基本概念。我们利用这个经典的玩具示例传达了贝叶斯统计学中一些最重要的思想,比如使用概率分布来构建模型并表示不确定性。我们试图揭开先验的神秘面纱,并将其与模型过程中的其他元素(如似然性)平等对待,甚至涉及到更多的元问题,比如我们为何要解决特定问题。

我们在本章最后讨论了贝叶斯分析结果的解释和沟通。我们假设存在一个真实的分布,这个分布通常是未知的(原则上也无法知道),我们从中获取一个有限的样本,可能是通过实验、调查、观察或模拟获得的。为了从真实分布中学习一些东西,鉴于我们只能观察到一个样本,我们构建了一个概率模型。概率模型有两个基本组成部分:先验和似然。使用模型和样本,我们进行贝叶斯推断并得到后验分布;这个分布封装了关于问题的所有信息,基于我们的模型和数据。从贝叶斯的角度看,后验分布是最重要的对象,一切其他内容都从它中推导出来,包括以后验预测分布形式呈现的预测。由于后验分布(以及从中推导的任何其他量)是模型和数据的结果,因此贝叶斯推断的有用性受到模型和数据质量的限制。最后,我们简要总结了贝叶斯数据分析的主要方面。在本书的其余部分,我们将再次回顾这些思想,将其吸收并作为更高级概念的框架。

在下一章,我们将介绍 PyMC,它是一个用于贝叶斯建模和概率机器学习的 Python 库,还会使用更多来自 ArviZ 的特性,这是一个用于贝叶斯模型探索性分析的 Python 库,以及 PreliZ,这是一个用于先验引导的 Python 库。

1.11 练习

我们不知道大脑是否以贝叶斯方式工作,或以大致贝叶斯方式工作,亦或是某种进化(或多或少)优化的启发式方法。尽管如此,我们知道通过接触数据、示例和练习来学习……你可能会说,人类从未真正学习,看看我们作为物种在战争或经济体系等方面的表现,这些体系优先考虑利润而非人民的福祉……不管怎样,我建议你在每章结束时做一下所提的练习:

  1. 假设你有一个罐子,里面有 4 颗果冻豆:2 颗是草莓味的,1 颗是蓝莓味的,1 颗是肉桂味的。你从罐子里随机抽取一颗果冻豆。

    1. 这个实验的样本空间是什么?

    2. 我们将事件A定义为抽到的果冻豆是草莓味的,将事件B定义为抽到的果冻豆不是肉桂味的。事件AB的概率分别是多少?

    3. 事件AB是互斥事件吗?为什么或者为什么不?

  2. 之前,我们定义了一个 Python 函数P来使用概率的简单定义计算事件的概率。将该函数推广,以计算当事件的概率不完全相等时的事件概率。使用这个新函数计算前面练习中事件AB的概率。提示:你可以传递一个第三个参数来表示每个事件的概率。

  3. 使用 PreliZ 探索 BetaBinomial 和高斯分布的不同参数。使用plot_pdfplot_cdfplot_interactive方法。

  4. 我们讨论了概率质量/密度函数和累积分布函数。但也有其他方式表示函数,比如百分位点函数 ppf。使用 PreliZ 的plot_ppf方法,绘制 BetaBinomial 和高斯分布的百分位点函数。你能解释 ppf 是如何与 cdf 和 pmf/pdf 相关的吗?

  5. 以下表达式中,哪一个对应于:在 1816 年 7 月 9 日已知的情况下,晴天的概率?

    1. p(sunny)

    2. p(sunny|July)

    3. p(sunny|9 of July of 1816)

    4. p(9^(th) of July of 1816|sunny)

    5. https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file57.jpg

  6. 我们展示了随机选择一个人并选出教皇的概率与教皇是人类的概率是不同的。在动画系列《未来都市》中,(太空)教皇是爬行动物。这会如何改变你之前的计算结果?

  7. 按照图 1.9中的示例,使用 PreliZ 计算 SkewNormal 分布的矩,参数组合不同。生成不同大小的随机样本,例如 10、100 和 1,000,看看是否能从样本中恢复出前两个矩的值(均值和方差)。你观察到什么?

  8. 对学生的 T 分布重复之前的练习。尝试ν的值,比如 2、3、500。你观察到什么?

  9. 在以下的概率模型定义中,识别先验和似然:

    https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file58.jpg

  10. 在前面的模型中,后验将有多少个参数?与掷硬币问题的模型进行比较。

  11. 为第 9 题中的模型写出贝叶斯定理。

  12. 假设我们有两枚硬币;当我们掷第一枚硬币时,正面朝上的概率为一半,反面朝上的概率也为一半。另一枚硬币是一枚加重硬币,总是朝上正面。如果我们随机选择其中一枚硬币并且掷出正面,那么这枚硬币是不公平的概率是多少?

  13. 尝试使用其他先验(beta_params)和其他数据(trialsdata)重新绘制图 1.12

  14. 阅读关于克伦威尔法则的 Wikipedia 文章:en.wikipedia.org/wiki/Cromwell%27s_rule

  15. 阅读关于概率和荷兰书的 Wikipedia 文章:en.wikipedia.org/wiki/Dutch_book

加入我们的社区 Discord 空间

加入我们的 Discord 社区,与志同道合的人一起学习,和超过 5000 名成员共同进步: packt.link/bayesian

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file1.png

第二章

概率编程

我们的魔像很少有实体形式,但它们通常也由硅中生长的黏土做成,作为计算机代码存在。—— 理查德·麦克埃尔里斯

现在我们对概率理论和贝叶斯统计有了非常基础的理解,接下来我们将学习如何使用计算工具构建概率模型。具体来说,我们将学习如何使用 PyMC 进行概率编程 [Abril-Pla 等人, 2023]。基本的思想是,我们通过代码来指定统计模型,然后 PyMC 将为我们求解这些模型。我们不需要显式地写出贝叶斯定理。这样做有两个原因。首先,许多模型无法得到解析解,因此我们只能使用数值方法来求解这些模型。其次,现代贝叶斯统计主要通过编写代码来完成。我们将看到,概率编程提供了一种有效的方法来构建和求解复杂模型,它让我们能够更多地关注模型设计、评估和解释,而少关注数学或计算的细节。

本章将涵盖以下主题:

  • 概率编程

  • PyMC 入门

  • 硬币抛掷问题重访

  • 总结后验分布

  • 高斯模型和学生 t 分布模型

  • 比较不同组别和效应大小

2.1 概率编程

贝叶斯统计从概念上讲非常简单。我们有已知未知,然后使用贝叶斯定理将后者条件化于前者。如果我们足够幸运,这个过程将减少对未知的 uncertainty。通常,我们把已知称为数据并将其视为常数,未知则称为参数并将其视为随机变量

尽管从概念上讲很简单,完全概率模型常常导致解析上不可处理的表达式。多年来,这一直是一个真正的问题,也是贝叶斯方法未能广泛应用于一些利基领域的主要原因之一。计算时代的到来和数值方法的发展,至少在原理上可以用来解决任何推断问题,已经极大地改变了贝叶斯数据分析的实践。我们可以将这些数值方法视为通用推断引擎。自动化推断过程的可能性催生了概率编程语言PPLs),它使得模型创建和推断之间有了清晰的分离。在 PPL 框架中,用户通过编写几行代码来指定完整的概率模型,然后推断过程会自动进行。

预计概率编程将在数据科学和其他学科中产生重大影响,能够使从业人员以更省时且更少出错的方式构建复杂的概率模型。我认为,编程语言对科学计算的影响可以通过六十多年前 Fortran 编程语言的引入来类比。虽然现在 Fortran 已经不再流行,但曾几何时,它被认为是革命性的。科学家们第一次摆脱了计算细节,开始更自然地专注于构建数值方法、模型和仿真。很有趣的是,有些人正在努力让 Fortran 再度焕发光彩,如果你有兴趣,可以查看他们的工作:fortran-lang.org/en

2.1.1 使用 PyMC 抛硬币

让我们回顾一下第一章中的抛硬币问题,这次使用 PyMC。我们将使用与那一章中相同的合成数据。因为我们生成了数据,所以我们知道以下代码块中 θ 的真实值,称为 theta_real。当然,对于实际数据集,我们没有这些知识:

代码 2.1

np.random.seed(123) 

trials = 4 
theta_real = 0.35 # unknown value in a real experiment 
data = pz.Binomial(n=1, p=theta_real).rvs(trials)

现在我们有了数据,我们需要指定模型。记住,这是通过指定似然性和先验来完成的。对于似然性,我们将使用参数 n = 1,p = θ 的二项分布;对于先验,我们将使用参数 α = β = 1 的 Beta 分布。具有此类参数的 Beta 分布等同于区间 [0, 1] 上的均匀分布。使用数学符号我们可以将模型写为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file59.jpg

这个统计模型几乎可以直接翻译为 PyMC:

代码 2.2

with pm.Model() as our_first_model: 
    *θ* = pm.Beta('*θ*', alpha=1., beta=1.) 
    y = pm.Bernoulli('y', p=*θ*, observed=data) 
    idata = pm.sample(1000)

代码的第一行创建了我们模型的容器。with 块中的所有内容将自动添加到 our_first_model 中。你可以把这看作是一种语法糖,它简化了模型的指定,因为我们不需要手动将变量分配给模型。第二行指定了先验。如你所见,语法紧密遵循了数学符号。第三行指定了似然性;语法与先验几乎相同,唯一不同的是我们使用 observed 参数传递数据。观测值可以作为 Python 列表、元组、NumPy 数组或 pandas DataFrame 传递。至此,我们完成了模型的指定!是不是很简洁?

我们还有一行代码需要解释。最后一行就是魔法发生的地方。背后这个看似无害的代码行,PyMC 正在自动化许多任务,简直就像是有成百上千的 oompa loompas 在为您唱歌并烤制美味的贝叶斯推断!嗯,虽然并非如此,但 PyMC 确实在自动化很多任务。暂时,我们将把这一行视作一个黑箱,它将为我们提供正确的结果。重要的是要理解,在底层,我们将使用数值方法来计算后验分布。原则上,这些数值方法能够解决我们可以编写的任何模型。我们为这种通用性所付出的代价是,结果将以来自后验的样本形式呈现。稍后,我们将能够证实这些样本来自一个 Beta 分布,正如我们在上一章学到的那样。由于数值方法是随机的,每次运行时样本都会有所不同。然而,如果推断过程按预期进行,这些样本将代表后验分布,因此我们将从这些样本中得出相同的结论。有关底层发生的详细情况以及如何检查样本是否值得信任,将在 第十章 中解释。

还有一件事:idata 变量是一个 InferenceData 对象,它是一个容器,包含 PyMC 生成的所有数据。我们将在本章稍后学习更多有关此的内容。

好的,在最后一行中,我们请求从后验中获得 1,000 个样本。如果您运行代码,您将看到类似这样的消息:

Auto-assigning NUTS sampler... Initializing NUTS using jitter+adapt_diag... Multiprocess sampling (4 chains in 4 jobs)
NUTS: [*θ*]
Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 1 second.

第一行和第二行告诉我们,PyMC 已经自动分配了 NUTS 采样器(一个非常适合连续变量的推断引擎),并使用了一种方法来初始化该采样器(这些方法需要对开始采样的位置进行一些初步猜测)。第三行说 PyMC 将并行运行四个链,因此我们将从后验中获得四个独立的样本。由于 PyMC 尝试将这些链并行化到机器上可用的处理器中,我们将以一个价格获得四个样本。链的确切数量是根据机器中的处理器数量计算的;您可以使用 chains 参数来更改 sample 函数的链数。接下来的这一行告诉我们每个采样器正在采样哪些变量。对于这个特定的例子,这一行并没有添加新信息,因为 NUTS 被用来采样我们唯一的变量 θ。然而,这并不总是这样,因为 PyMC 可以为不同的变量分配不同的采样器。PyMC 有规则来确保每个变量都与最佳的采样器关联。用户可以使用 sample 函数的 step 参数手动分配采样器,但您几乎不需要这么做。

最后,最后一行是一个进度条,显示了与采样器工作速度相关的几个指标,包括每秒的迭代次数。如果你运行代码,你会看到进度条更新得非常快。在这里,我们看到的是最后阶段,即采样器已经完成了它的工作。你会注意到,我们要求了 1,000 个样本,但 PyMC 实际上计算了 8,000 个样本。每条链有 1,000 个抽样用于调节采样算法(在本例中是 NUTS)。这些抽样默认会被丢弃;PyMC 使用它们来提高采样方法的效率和可靠性,这对于获得有用的后验近似是非常重要的。每条链还有 1,000 个有效抽样,共计 4,000 个。这些是我们将用作后验分布的样本。我们可以通过 tune 参数调整调节步骤的数量,通过 draw 参数调整抽样的数量。

更快的采样

在幕后,PyMC 使用 PyTensor,这是一个可以定义、优化和高效评估涉及多维数组的数学表达式的库。PyTensor 大大提高了 PyMC 的速度和性能。尽管有这些优势,但值得注意的是,PyMC 中的采样器是用 Python 实现的,这有时可能导致执行速度较慢。为了应对这个限制,PyMC 允许使用外部采样器。我推荐使用 nutpie,一个用 Rust 编写的采样器。有关如何从 PyMC 安装和调用 nutpie 的更多信息,请查阅 第 10 章。

2.2 总结后验分布

通常,在从后验分布中采样后,我们执行的第一项任务是检查结果的表现。ArviZ 的 plot_trace 函数非常适合这项任务:

代码 2.3

az.plot_trace(idata)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file60.png

图 2.1our_first_model 后验的追踪图

图 2.1 显示了调用 az.plot_trace 时的默认结果;我们为每个未观察到的变量获得两个子图。我们模型中唯一未观察到的变量是 θ。注意,y 是一个表示数据的观察变量;我们不需要对其进行采样,因为我们已经知道这些值。因此,我们只得到两个子图。左边是一个 核密度估计KDE)图;这类似于直方图的平滑版本。理想情况下,我们希望所有链条的 KDE 非常相似,就像 图 2.1 中所示。右边,我们得到每个采样步骤的个体值;每条链条对应一条线。理想情况下,我们希望它看起来杂乱无章,没有明显的模式,我们应该很难区分不同的链条。在 第 10 章中,我们提供了更多关于如何解读这些图表的细节。关键是,如果我们运行了许多链条,我们期望它们几乎无法区分。采样器表现得很好,我们可以信任这些样本。

与其他 ArviZ 函数一样,az.plot_trace 也有许多选项。例如,我们可以通过设置 combined=True 参数来生成所有链的单个 KDE 图,并通过设置 kind=rank_bars 来生成一个 排名

代码 2.4

az.plot_trace(idata, kind="rank_bars", combined=True)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file61.png

图 2.2our_first_model 后验的痕迹图,使用了选项 kind="rank_bars"combined=True

排名图是另一种检查我们是否可以信任样本的方法;对于这个图,我们为每条链生成一个直方图,并希望它们尽可能均匀,像在 图 2.2 中那样。由于随机抽样,一些小的均匀性偏差是可以接受的,但大的均匀性偏差则表明链正在探索后验的不同区域。理想情况下,我们希望所有的链都能探索整个后验。在 第十章 中,我们提供了如何解释排名图及其构建方法的更多细节。

ArviZ 提供了几种其他的图表来帮助解释后验,我们将在接下来的页面中看到它们。我们也可能希望获得后验的数值总结。我们可以使用 az.summary 来实现,它将返回一个 pandas DataFrame,如 表 2.1 所示。

代码 2.5

az.summary(idata, kind="stats").round(2)
均值标准差hdi_3%hdi_97%
θ0.340.180.030.66

表 2.1:总结统计数据

在第一列,我们有变量的名称,第二列是后验的均值,第三列是后验的标准差,最后两列是 94% 最高密度区间的下限和上限。因此,根据我们的模型和数据,我们认为 θ 的值很可能是 0.34,并且以 94% 的概率,它的真实值在 0.03 和 0.66 之间。我们也可以使用标准差报告一个类似的总结。标准差相比 HDI 的优点是它是一种更常见的统计量。但缺点是我们需要更小心地解释它,否则可能会得出毫无意义的结果。例如,如果我们计算均值 ± 2 个标准差,我们将得到区间 (-0.02, 0.7);上限值与我们从 HDI 得到的 0.66 相差不远,但下限实际上超出了 θ 的可能值(它应该在 0 和 1 之间)。

另一种直观总结后验的方法是使用 ArviZ 中的 az.plot_posterior 函数(见 图 2.3)。我们在前一章中已经用它处理了一个虚假的后验。现在,我们将使用它来处理一个真实的后验。

代码 2.6

az.plot_posterior(idata)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file62.png

图 2.3:该图显示了 θ 的后验分布及 94% HDI。

默认情况下,plot_posterior 为离散变量显示直方图,为连续变量显示核密度估计(KDE)。我们还可以得到分布的均值(可以通过point_estimate参数请求中位数或众数)和 94%的 HDI,该黑线位于图表底部。可以使用hdi_prob参数为 HDI 设置不同的区间值。这种类型的图表是由 John K. Kruschke 在他的伟大著作《Doing Bayesian Data Analysis》中首次提出的[Kruschke, 2014]。

2.3 基于后验的决策

有时候,仅仅描述后验是不够的。我们可能需要基于推断做出决策,并将连续估计转化为二分法:是-否,健康-生病,污染-安全,等等。例如,硬币是否公平?一个公平的硬币是θ值恰好为 0.5 的硬币。我们可以将 0.5 的值与 HDI 区间进行比较。从图 2.3中,我们可以看到 HDI 区间从 0.03 到 0.7,因此 0.5 包含在 HDI 内。我们可以将此解读为硬币可能有尾部偏向,但我们不能完全排除硬币实际上是公平的可能性。如果我们希望做出更明确的决策,我们需要收集更多的数据来减少后验的分布范围,或者可能需要重新定义一个更具信息性的先验。

2.3.1 Savage-Dickey 密度比

评估后验提供给某个特定值的支持力度的一种方法是比较该值下后验密度和先验密度的比值。这称为 Savage-Dickey 密度比,我们可以使用 ArviZ 通过az.plot_bf函数来计算:

代码 2.7

az.plot_bf(idata, var_name="*θ*", 
           prior=np.random.uniform(0, 1, 10000), ref_val=0.5);

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file63.png

图 2.4:该图显示了our_first_model的先验和后验;黑色点表示在参考值 0.5 处评估的值

图 2.4中我们可以看到,BF_01的值为 1.3,这意味着在后验分布下,θ = 0.5 的值比在先验分布下更有可能,概率是 1.3 倍。为了计算这个值,我们将后验在θ = 0.5 处的高度除以前验在θ = 0.5 处的高度。BF_10的值则是其倒数!-1- 1.3 ≈ 0.8。我们可以理解为在后验分布下,θ≠0.5的可能性是先验分布下的 0.76 倍。我们如何解读这些数字?要带有一丝怀疑……以下表格展示了 Kass 和 Raftery 在 1995 年提出的其中一种可能的解释:

BF_01解释
1 到 3.2不值一提
3.2 到 10实质性
10 到 100
> 100决定性

表 2.2:贝叶斯因子(BF_01)的解释

Savage-Dickey 密度比率是一种特定的计算方法,用来求解所谓的贝叶斯因子。我们将在 第五章 中学习更多关于贝叶斯因子及其注意事项。

2.3.2 实际等价区域

严格来说,观察到精确的 0.5(即带有无限多个零)的概率为零。此外,在实践中,我们通常不关心精确结果,而是关心某个范围内的结果。因此,在实践中,我们可以放宽对公平性的定义,认为一个公平的硬币的值应该是 接近 0.5。例如,我们可以说,区间 [0.45, 0.55] 内的任何值对于我们的目的来说,实际上都等同于 0.5。我们称这个区间为实际等价区域(ROPE)。一旦定义了 ROPE,我们就可以将其与 HDI 进行比较。我们可以得到至少三种情况:

  • ROPE 不与 HDI 重叠;我们可以说硬币是不公平的

  • ROPE 包含了整个 HDI;我们可以说硬币是公平的

  • ROPE 部分与 HDI 重叠;我们不能说硬币是公平的还是不公平的

如果我们选择将 ROPE 与参数的支持区域匹配,例如在硬币投掷示例中使用 [0, 1],我们将总是认为硬币是公平的。请注意,我们不需要收集数据来进行任何推断。

ROPE 的选择完全是任意的:我们可以选择任何我们想要的值。有些选择并没有什么实际意义。例如,对于硬币投掷示例,如果我们选择 ROPE 为 [0, 1],那么我们将总是认为硬币是公平的。更重要的是,我们不需要收集数据或进行任何分析来得出这个结论,这是一个微不足道的例子。更值得担心的是,在进行分析之后选择 ROPE。这是有问题的,因为我们可以调整结果来使它符合我们想要的结论。但如果我们要调整结果以符合我们的预期,那我们为什么还要做分析呢?ROPE 应该由领域知识来决定。

我们可以使用 plot_posterior 函数来绘制包含 HDI 区间和 ROPE 的后验分布。ROPE 显示为半透明的粗(灰色)线:

代码 2.8

az.plot_posterior(idata, rope=[0.45, .55])

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file65.png

图 2.5:该图展示了 θ 的后验分布和 94% HDI。ROPE 以一条粗的浅灰色线表示

另一个我们可以用来帮助决策的工具是将后验分布与参考值进行比较。我们可以使用 plot_posterior 来实现。如 图 2.6 所示,我们得到一条垂直的(灰色)线,并且可以看到后验分布在参考值之上和之下的比例:

代码 2.9

az.plot_posterior(idata, ref_val=0.5)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file66.png

图 2.6:该图展示了 θ 的后验分布和 94% HDI。参考值显示为灰色垂直线

关于 ROPE 使用的更详细讨论,可以阅读 Kruschke 的《Doing Bayesian Data Analysis》第十二章[2014]。该章节还讨论了如何在贝叶斯框架中进行假设检验及其注意事项,无论是在贝叶斯还是非贝叶斯设置下。

2.3.3 损失函数

如果你觉得这些 ROPE 规则听起来有些繁琐,并且想要更正式的东西,损失函数就是你所寻找的!为了做出一个好的决策,重要的是对相关参数的估计值具有尽可能高的精度,但同样重要的是要考虑犯错的代价。成本/收益的权衡可以通过数学化的方式使用损失函数来形式化。损失函数或其逆的名称在不同领域中各不相同,我们可能会遇到诸如成本函数、目标函数、适应度函数、效用函数等名称。无论名称如何,关键思想是使用一个函数来捕捉参数的真实值和估计值之间的差异。损失函数的值越大,估计结果越差(根据损失函数的定义)。一些常见的损失函数示例如下:

实际上,我们并不知道真实参数的值。相反,我们有一个后验分布形式的估计。因此,我们可以做的是找出一个 θ,使其最小化期望损失函数。所谓的期望损失函数是指在整个后验分布上平均的损失函数。

在以下代码块中,我们有两个损失函数:绝对损失(lossf_a)和二次损失(lossf_b)。我们将探索一个包含 200 个点的网格上的值。然后,我们会绘制这些曲线,并且还会包括最小化每个损失函数的 θ 值。以下代码块展示了没有绘图部分的 Python 代码:

代码 2.10

grid = np.linspace(0, 1, 200) 
*θ*_pos = idata.posterior['*θ*'] 
lossf_a = [np.mean(abs(i - *θ*_pos)) for i in grid] 
lossf_b = [np.mean((i - *θ*_pos)**2) for i in grid] 
for lossf, c in zip([lossf_a, lossf_b], ['C0', 'C1']): 
    ...

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file67.png

图 2.7:应用于 our_first_model 后验的绝对(黑色)和二次(灰色)损失函数

更有趣的是,从图 2.7可以看到,我们从绝对损失得到的值等于后验分布的中位数,而从二次损失得到的值等于后验分布的均值。你可以通过计算np.mean(θ_pos)np.median(θ_pos)来验证这一点。这并非巧合:不同的损失函数与不同的点估计有关。均值是最小化二次损失的点估计,中位数是最小化绝对损失的点估计,而众数是最小化 1-0 损失的点估计。

如果我们想正式一点,并且想要计算一个单点估计值,我们必须决定选择哪个损失函数。相反地,如果我们选择一个点估计值,我们实际上(可能是无意识地)选择了一个损失函数。显式选择损失函数的优点是我们可以根据自己的问题量身定制函数,而不是使用预定义的规则。我们常常会发现,做决策的成本是非对称的;例如,疫苗可能会引起免疫系统的过度反应,但接种疫苗的人,甚至未接种疫苗的人,所获得的益处通常远远超过风险,差距通常很大。因此,如果我们的任务要求,我们可以构建一个非对称的损失函数。还需要注意的是,由于后验分布是数值样本的形式,我们可以计算复杂的损失函数,这些函数不必受限于数学便利性或单纯的简化。下面的代码,以及由它生成的图 2.8,就是这个概念的一个简单示例:

代码 2.11

lossf = [] 
for i in grid: 
    if i < 0.5: 
        f = 1/np.median(*θ*_pos / np.abs(i**2 - *θ*_pos)) 
    else: 
        f = np.mean((i - *θ*_pos)**2 + np.exp(-i)) - 0.25 
    lossf.append(f)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file68.png

图 2.8:应用于our_first_model后验分布的一个奇怪损失函数

直到现在,我们一直在讨论贝叶斯统计和概率编程的主要概念,主要通过 BetaBinomial 模型,因为它的简洁性。在构建更复杂模型的过程中,我们现在将焦点转向高斯推理的领域。

2.4 高斯分布的深入探索

从数学角度来看,高斯分布非常吸引人。与高斯分布进行工作相对容易,许多应用于高斯分布的操作会返回另一个高斯分布。此外,许多自然现象可以使用高斯分布进行很好的近似;实际上,几乎每次我们测量某物的平均值时,只要样本量足够大,该平均值就会呈高斯分布。这种现象何时成立、何时不成立以及何时大致成立的细节在中心极限定理(CLT)中有详细阐述;你可能现在就想停下来,去查找一下这个非常核心的统计学概念(有意的恶搞)。

好吧,我们刚才说过,许多现象确实是平均值的结果。举个老套的例子,身高(几乎所有人的其他特征也是如此)是许多环境因素和许多遗传因素的综合结果,因此我们可以得到成年人的身高符合高斯分布。事实上,我们得到的是两个高斯分布的混合,这是女性和男性身高分布重叠的结果,但你大概明白了。总之,高斯分布是易于使用且在自然现象中普遍存在的;因此,许多你可能已经了解的统计方法假设数据是符合正态分布的。因此,学习如何建立这些模型很重要,而同样重要的是学会如何放宽正态性假设,这在贝叶斯框架和现代计算工具如 PyMC 中是非常容易的。

2.4.1 高斯推断

核磁共振(NMR)是一种强大的技术,用于研究分子以及诸如人类、向日葵和酵母等生物(毕竟,我们不过是一堆分子)。NMR 使得我们能够测量与有趣的不可观察分子性质相关的不同种类的可观察量 Arroyuelo 等人 [2021]。其中一种可观察量被称为化学位移,这个值仅能通过某些类型原子的核来获取。具体的细节属于量子化学的范畴,跟本讨论无关。就我们目前关心的来说,我们也可以测量一群人的身高、回家所需的平均时间或一袋袋橙子的重量。在这些例子中,变量是连续的,因此把它们看作是一个平均值加上一个离散度是有意义的。有时如果可能的值足够多,我们可以用高斯模型来处理离散变量;例如,倭黑猩猩非常好色,所以也许我们可以用高斯分布来建模我们表亲的性伴侣数量。

回到我们的例子,我们在图 2.9 中通过箱型图表示了 48 个化学位移值。我们可以看到,中位数(箱内的线)大约是 53,而四分位距(箱子的范围)大约是 52 和 55。我们还可以看到,有两个值远离其余数据(空心圆)。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file69.png

图 2.9:48 个化学位移值的箱型图。我们观察到有两个值超过 60,远离其余数据。

让我们暂时忘记这两个点,假设高斯分布是描述数据的一个合理模型。由于我们不知道均值或标准差,因此必须为它们设置先验分布。因此,一个合理的模型可以是:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file70.jpg

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/U.PNG(l,h)是lh之间的均匀分布,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/HN.PNG(σ[σ])是带有尺度σ[σ]的 HalfNormal 分布,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/N.PNG(μ,σ)是均值为μ,标准差为σ的高斯分布。HalfNormal 分布考虑的是以零为中心的正态分布的绝对值。图 2.10展示了该模型的图形表示。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file71.png

图 2.10: model_g的图形表示

如果我们不知道μσ的可能值,可以设置反映我们无知的先验分布。一种选择是将均匀分布的边界设置为l = 40,h = 75,这是一个比数据范围更大的范围。另一种选择是根据我们之前的知识选择一个范围。例如,我们可能知道这种类型的测量值不可能低于 0 或高于 100,因此可以将这些值作为均匀分布的边界。对于 HalfNormal 分布,在没有更多信息的情况下,我们可以选择一个相对于数据规模较大的值。表示在图 2.10中模型的 PyMC 代码是:

代码 2.12

with pm.Model() as model_g: 
    μ = pm.Uniform('μ', lower=40, upper=70) 
    σ = pm.HalfNormal('σ', sigma=5) 
    Y = pm.Normal('Y', mu=μ, sigma=σ, observed=data) 
    idata_g = pm.sample()

让我们看看后验分布的形态。图 2.11是通过 ArviZ 的plot_trace函数生成的。它每一行对应一个参数。对于该模型,后验分布是二维的,因此每一行展示一个边际分布。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file72.png

图 2.11: 使用az.plot_trace(idata_g)绘制的model_g后验分布

我们可以使用 ArviZ 的plot_pair函数查看二维后验分布的形态,并同时查看μσ的边际分布。请参见图 2.12

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file73.png

图 2.12: 使用az.plot_pair(idata_g, kind=’kde’, marginals=True)绘制的model_g后验分布

我们将打印汇总以供以后使用(参见表 2.3)。我们使用以下代码:

代码 2.13

az.summary(idata_g, kind="stats").round(2)
均值标准差hdi_3%hdi_97%
μ53.500.5252.5154.44
σ3.520.382.864.25

表 2.3: μσ的汇总统计

2.5 后验预测检查

贝叶斯工具包的一个优点是,一旦我们得到了后验p(θ|Y ),就可以用它来生成预测p()。在数学上,这可以通过计算来完成:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file74.jpg

这种分布被称为后验预测分布。它是预测性的,因为它用于进行预测,并且是后验的,因为它是使用后验分布计算的。因此,我们可以将其视为给定模型和观察数据后未来数据的分布。

使用 PyMC 获取后验预测样本非常简单;我们无需计算任何积分。只需要调用sample_posterior_predictive函数,并将InferenceData对象作为第一个参数传入。我们还需要传入model对象,并可以使用extend_inferencedata参数将后验预测样本添加到InferenceData对象中。代码如下:

代码 2.14

pm.sample_posterior_predictive(idata_g, model=model_g, 
                              extend_inferencedata=True)

后验预测分布的一个常见用途是进行后验预测检验。这是一组可以用来检查模型是否适合数据的测试。我们可以使用 ArviZ 中的plot_ppc函数来可视化后验预测分布和观测数据。代码如下:

代码 2.15

az.plot_ppc(idata_g, num_pp_samples=100)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file75.png

图 2.13:使用az.plot_ppc绘制的model_g后验预测检验

图 2.13中,黑色线条是数据的 KDE,灰色线条是从每一个 100 个后验预测样本中计算出的 KDE。灰色线条反映了我们对预测数据分布的不确定性。当数据点很少时,图像看起来会显得杂乱奇怪;这是常见的情况。默认情况下,ArviZ 中的 KDE 是在实际数据范围内估算的,并假设在数据范围之外为零。虽然有人可能认为这是一个 bug,但我认为它是一个特性,因为它反映了数据的一个特性,而不是过度平滑处理。

图 2.13中,我们可以看到模拟数据的均值略微向右偏移,并且模拟数据的方差似乎比实际数据大。这个差异的来源可以归因于我们选择的似然函数和两个偏离数据主体的观测值(在图 2.9中为空心点)。我们如何解释这个图?模型是错误的还是正确的?我们可以使用它吗,还是需要换一个模型?其实,这取决于情况。模型的解释、评估和批评始终是依赖于具体情境的。根据我在这种测量中的经验,我认为这个模型是对数据的一个合理且足够的表示,并且对于我大多数分析来说非常有用。不过,重要的是要记住,我们可能会找到其他更好地适应整个数据集的模型,包括那两个远离数据主体的观测值。让我们看看如何做到这一点。

2.6 鲁棒推断

我们对model_g可能有一个异议,那就是我们假设了正态分布,但数据中有两个点远离数据的主体。通过使用正态分布作为似然性,我们间接假设我们不期望看到大量远离主体的数据点。图 2.13 显示了将这些假设与数据结合的结果。由于正态分布的尾部随着离均值越远而迅速下降,正态分布(至少是人性化的正态分布)对这两个点的出现感到惊讶,并以两种方式做出反应,将其均值向这两个点移动,并增加其标准差。另一种直观的解释方式是,认为这些点在决定正态分布参数时有过大的权重。

那么,我们该怎么做呢?一个选项是检查数据中的错误。如果我们回溯步骤,可能会发现清理或预处理数据时代码出错,或者可以将这些假定的异常值与测量设备故障关联起来。不幸的是,这并不总是一个选项。很多时候,数据是由别人收集的,我们没有很好的记录它是如何收集、测量或处理的。无论如何,在建模之前检查数据总是一个好主意,这在一般情况下都是一种良好的做法。

另一个选项是将这些点声明为离群值并将其从数据中移除。识别数据集离群值的两条常见经验法则是:

  • 使用四分位间距(IQR):任何低于下四分位数 1.5 倍 IQR,或高于上四分位数 1.5 倍 IQR 的数据点,都被认为是离群值。

  • 使用标准差:任何低于或高于数据标准差N倍的数据点都被认为是离群值。通常N的值是 2 或 3。

然而,需要注意的是,像任何自动化方法一样,这些经验法则并不完美,可能会导致丢弃有效的数据点。

从建模的角度来看,我们可以将问题归咎于模型,而不是数据,并进行修改,正如下一节所解释的那样。一般来说,贝叶斯方法更倾向于通过使用不同的先验和似然性,将假设直接编码到模型中,而不是通过诸如去除离群值规则等临时启发式方法。

2.6.1 正常性的程度

有一个分布看起来非常类似于正态分布。它有三个参数:位置参数μ,尺度参数σ,以及正态性参数ν。这个分布被称为学生 t 分布。图 2.14展示了这一家族的成员。当ν = ∞时,分布就是正态分布,μ是均值,σ是标准差。当ν = 1 时,我们得到的是柯西分布或洛伦兹分布。ν的取值范围是从 0 到∞。这个值越小,分布的尾部越重。我们还可以说,ν值越小,峰度越高。峰度是第四阶矩,你可能还记得前一章中提到的。所谓“重尾”,是指在这种分布中,偏离均值的值比在正态分布中更为常见,换句话说,值的分布不像正态分布那样集中在均值附近。例如,95%的学生 t 分布值(μ = 0,σ = 1,ν = 1)会落在-12.7 到 12.7 之间。而对于正态分布(μ = 0,σ = 1,ν = ∞),这一范围是-1.96 到 1.96。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file76.png

图 2.14:学生 t 分布

学生 t 分布的一个非常有趣的特点是,当ν ≤ 1 时,它没有定义的均值。虽然从学生 t 分布中得到的任何有限样本都可以计算出经验均值,但理论上的分布本身没有定义均值的值。直观地说,可以理解为:分布的尾部非常重,以至于在任何时候我们可能从实数线上几乎任何位置抽取一个样本值,因此如果我们不断地得到数值,我们永远不会接近一个固定的值。相反,估计值将不断地游走。

什么自由度?

在大多数教科书中,学生 t 分布的参数ν被称为自由度参数。然而,我更倾向于遵循 Kruschke 的建议,将其称为正态性参数。这个名称更能描述该参数在分布中的作用,特别是在用于鲁棒回归时。

同样,这个分布的方差只有在ν > 2 时才被定义。因此,重要的是要注意,学生 t 分布的尺度与其标准差不同。随着ν趋近于无穷大,尺度和标准差会越来越接近。

2.6.2 正态模型的鲁棒版本

我们将通过将高斯分布替换为学生 t 分布来重写前面的模型(model_g)。由于学生 t 分布比高斯分布多了一个参数ν,因此我们需要指定一个额外的先验,对于这个模型我们决定使用指数分布,但其他限制在正区间的分布也可以适用。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file77.jpg

图 2.15 显示了该模型的图形表示

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file78.png

图 2.15model_t 的图形表示

让我们用 PyMC 编写这个模型;像往常一样,我们可以通过指定几行代码来(重新)编写模型。唯一需要注意的是,PyMC 中的指数分布默认使用均值的倒数作为参数化方式。我们将设置 ν 为均值为 30 的指数分布。从 图 2.14 中可以看到,ν = 30 的学生 t 分布与高斯分布相似(即使它并非真正的高斯分布)。事实上,从相同的图中我们可以看到,大部分的变化发生在较小的 ν 值上。因此,我们可以说,均值为 30 的指数先验是一种弱信息先验,告知模型我们大致认为 ν 应该在 30 附近,但也能容易地向较小或较大的值偏移。在许多问题中,估计 ν 并非直接关注的重点。

代码 2.16

with pm.Model() as model_t: 
    μ = pm.Uniform('μ', 40, 75) 
    σ = pm.HalfNormal('σ', sigma=10) 
    ν = pm.Exponential('ν', 1/30) 
    y = pm.StudentT('y', nu=ν, mu=μ, sigma=σ, observed=data) 
    idata_t = pm.sample()

比较 model_g 的轨迹(图 2.11) 与 model_t 的轨迹(图 2.16):

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file79.png

图 2.16:使用 az.plot_trace(idata_t) 绘制的 model_t 后验

现在,打印出 model_t 的总结。你应该得到类似于 表 2.4 的内容。将结果与 model_g 的结果进行比较。

均值标准差hdi_3%hdi_97%
μ53.020.3952.2753.71
σ2.210.421.463.01
ν4.945.451.0710.10

表 2.4μσν 的总结统计

在继续阅读之前,花点时间将前面的结果与 model_g 的结果进行比较,并找出两者之间的差异。你发现了什么有趣的地方吗?

两个模型之间的 μ 估计值相似,差异约为 ≈ 0*.5. σ 的估计值对于 model_g 为 ≈ 3.5, 对于 model_t 为 ≈ 2.2。这是因为学生 t 分布将较远离均值的值分配较小的权重。简单来说,学生 t 分布对远离均值的值不那么惊讶*。我们还可以看到 ν 的均值为 ≈ 5,这意味着我们有一个重尾分布,而非类似高斯分布的分布。

图 2.17 显示了 model_t 的后验预测检验。让我们将其与 model_g 的结果(*图 2.13)进行比较。使用学生 t 分布的模型产生的预测样本似乎在分布的峰值位置以及扩展范围上更好地拟合数据。请注意,样本如何从数据的主要部分延伸得很远,且一些预测样本看起来非常平坦。这是学生 t 分布期望看到远离均值或数据主体的数据点的直接结果。如果你查看用于生成 图 2.17 的代码,你会发现我们使用了 ax.set_xlim(40, 70)

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file80.png

图 2.17model_t 的后验预测检查

学生 t 分布使我们能够更 稳健地估计均值和标准差,因为异常值对 ν 的影响是减少,而不是拉动均值或增大标准差。因此,均值和尺度是通过加权靠近主体的数据点来估计的,而远离主体的数据点则权重较小。作为经验法则,对于 ν > 2 且 不太小 的值,我们可以将学生 t 分布的尺度视为去除异常值后的数据标准差的合理实用代理。这是一个经验法则,因为我们知道尺度并不是标准差。

2.7 InferenceData

InferenceData 是一个包含贝叶斯推断结果的丰富容器。现代的贝叶斯分析可能会生成多组数据,包括后验样本和后验预测样本。但是我们也有观察数据、先验样本,甚至是由采样器生成的统计量。所有这些数据以及更多内容都可以存储在一个 InferenceData 对象中。为了帮助组织这些信息,每一组数据都有其自己的组。例如,后验样本存储在 posterior 组中,观察数据存储在 observed_data 组中。

图 2.18 显示了 model_g 的 InferenceData 对象的 HTML 表示。我们可以看到 4 个组:posteriorposterior_predictivesample_statsobserved_data。除了 posterior 组外,其他组都已折叠。我们可以看到有两个坐标 chaindraw,其维度分别为 4 和 1000。我们还有 2 个变量 μσ

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file81.png

图 2.18model_g 的 InferenceData 对象

到目前为止,PyMC 已经生成了一个 InferenceData 对象,而 ArviZ 已使用该对象生成图表或数值摘要。但是,我们也可以操作 InferenceData 对象。一些常见的操作是访问特定的组。例如,要访问后验组,我们可以这样写:

代码 2.17

posterior = idata_g.posterior

这将返回一个 xarray 数据集。如果你不熟悉 xarray [Hoyer 和 Hamman, 2017]( docs.xarray.dev/en/stable/),可以把它想象成带标签的 NumPy 多维数组!这使得许多操作变得更简单,因为你不需要记住维度的顺序。例如,以下代码将返回链条 0 和链条 2 的第一次抽样:

代码 2.18

posterior.sel(draw=0, chain=[0, 2])

我们使用 sel 方法选择一系列值,例如从所有链条中选择前 100 次抽样:

代码 2.19

posterior.sel(draw=slice(0, 100))

此外,以下代码返回了在所有抽样和链条上计算的 μσ 的均值:

代码 2.20

posterior.mean()

同时,以下代码返回抽样的均值,即,它会返回 μσ 的四个值,每个链条一个:

代码 2.21

posterior.mean("draw")

我们通常不关心链和绘图,只想获得后验样本。在这种情况下,我们可以使用az.extract函数:

代码 2.22

stacked = az.extract(idata_g)

这将chaindraw合并为一个sample坐标,这可以使后续操作更为简便。默认情况下,az.extract作用于后验分布,但你也可以通过group参数指定其他组。你还可以使用az.extract来获取后验的随机样本:

代码 2.23

az.extract(idata_g, num_samples=100)

本书中我们将一直使用 InferenceData 对象,因此你将有时间熟悉它并在接下来的页面中了解更多。

2.8 组间比较

一种常见的统计分析是组间比较。我们可能会对患者对某种药物的反应、交通法规引入后车祸减少、不同教学方法下的学生表现等感兴趣。有时,这类问题会以假设检验的形式呈现,目标是声明某个结果具有统计学意义。仅仅依赖统计学显著性可能会带来很多问题:一方面,统计学显著性不等于实际意义;另一方面,单纯通过收集足够的数据,就可能宣布一个非常小的效应为显著。

假设检验的概念与 p 值的概念相关。这种联系并非根本性的,而是一种文化上的联系;人们习惯于这样思考,主要是因为这是大多数入门统计课程中教授的内容。长期以来有很多研究和论文表明,p 值经常被错误使用和解读,甚至是那些每天都在使用它们的人。我们不会进行假设检验,而是采取不同的途径,专注于估计效应量,即量化两组之间的差异。思考效应量的一个好处是,我们可以摆脱“是否有效?”或“是否有影响?”这样的简单是非问题,转而提出更细致的问题,如“效果如何?”或“效应有多大?”。

有时,在比较不同组时,人们会提到对照组和实验组。例如,当我们想要测试一种新药时,我们希望将新药(实验组)与安慰剂(对照组)进行比较。安慰剂效应是一种心理现象,患者在接受无效物质或治疗后,可能会感觉症状或状况有所改善。通过在临床试验中将药物与安慰剂组的效果进行比较,研究人员可以辨别药物是否真的有效。安慰剂效应是实验设计和统计分析中的一个广泛挑战的例子,表明在实验中考虑所有因素是困难的。

这种设计的一个有趣替代方案是,将新药与市面上最受欢迎或最有效的药物进行比较,用以治疗该疾病。在这种情况下,控制组不能是安慰剂;它应该是另一种药物。虚假的控制组是用统计学撒谎的一个绝佳方法。

例如,假设你为一家乳制品公司工作,这家公司想通过告诉父母这种酸奶可以增强免疫系统或帮助孩子更强壮来向孩子们推销过多含糖的酸奶。一种通过数据作假的方式是使用牛奶或甚至水作为对照组,而不是另一种更便宜、含糖量更少、市场推广较少的酸奶。把事情说成这样时,可能会显得很荒谬,但我正在描述的是在实际科学期刊上发表的真实实验。当有人说某物更难、更好、更快或更强时,记得问一下用于比较的基准是什么。

2.8.1 小费数据集

为了探讨本节的主题,我们将使用提示数据集[Bryant 和 Smith,1995]。我们想研究星期几对餐厅小费的影响。在这个例子中,不同的组别是不同的星期几。请注意,这里没有控制组或处理组。如果我们愿意,可以随意将某一天(例如星期四)设定为参考组或控制组。现在,让我们通过仅用一行代码将数据集加载为 pandas DataFrame 来开始分析。如果你不熟悉 pandas,tail 命令用于显示 DataFrame 的最后几行(见 表 2.5),你也可以尝试使用 head

代码 2.24

tips = pd.read_csv("data/tips.csv") 
tips.tail()
总账单小费性别吸烟者星期几餐次人数
23929.035.92男性星期六晚餐3
24027.182.00女性星期六晚餐2
24122.672.00男性星期六晚餐2
24217.821.75男性星期六晚餐2
24318.783.00女性星期四晚餐2

表 2.5:餐厅样本数据

在这个 DataFrame 中,我们只使用 daytip 两列。图 2.19 显示了使用 ridge 图展示的数据分布。这个图是用 ArviZ 绘制的。尽管 ArviZ 主要用于贝叶斯模型分析,但它的一些功能在数据分析中也很有用。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file82.png

图 2.19:按星期几分布的小费

我们将对数据进行一些小的预处理。首先,我们将创建一个 tip 变量,表示以美元为单位的小费。然后我们创建 idx 变量,一个类别虚拟变量,用数字编码星期几,也就是说,[0, 1, 2, 3] 代替 [’Thur’, ’Fri’, ’Sat’, ’Sun’]

代码 2.25

categories = np.array(["Thur", "Fri", "Sat", "Sun"]) 
tip = tips["tip"].values 
idx = pd.Categorical(tips["day"], categories=categories).codes

这个问题的模型几乎与model_g相同,唯一的区别是现在μσ将变成向量而非标量。PyMC 的语法在这种情况下非常有帮助:我们可以以矢量化的方式编写模型,而不是使用循环。

代码 2.26

with pm.Model() as comparing_groups: 
    μ = pm.Normal("μ", mu=0, sigma=10, shape=4) 
    σ = pm.HalfNormal("σ", sigma=10, shape=4) 

    y = pm.Normal("y", mu=μ[idx], sigma=σ[idx], observed=tip)

请注意我们如何为先验分布传递了一个shape参数。对于μ,这意味着我们指定了四个独立的https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/N.PNG(0*,10),而对于σ*,则是四个独立的https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/HN.PNG(10)。另外,请注意我们如何使用idx变量来正确索引传递给似然函数的μσ的值。

PyMC 提供了一种替代语法,要求指定坐标和维度。这种替代方法的优势在于它能更好地与 ArviZ 集成。

在这个例子中,我们有 4 个均值和 4 个标准差,因此我们使用shape=4。InferenceData 将具有 4 个索引0, 1, 2, 3,分别对应每个 4 天。然而,将这些数值索引与具体的日期关联起来是用户的任务。通过使用坐标和维度,我们以及 ArviZ 可以使用标签"Thur", "Fri", "Sat", "Sun"轻松地将参数映射到相应的日期。

我们将指定两个坐标;"days",其维度为"Thur", "Fri", "Sat", "Sun";和"days_flat",它将包含相同的标签,但根据每个观察值的顺序和长度重复。"days_flat"将在后续的后验预测测试中发挥作用。

代码 2.27

coords = {"days": categories, "days_flat":categories[idx]} 

with pm.Model(coords=coords) as comparing_groups: 
    μ = pm.HalfNormal("μ", sigma=5, dims="days") 
    σ = pm.HalfNormal("σ", sigma=1, dims="days") 

    y = pm.Gamma("y", mu=μ[idx], sigma=σ[idx], observed=tip, dims="days_flat") 

    idata_cg = pm.sample() 
    idata_cg.extend(pm.sample_posterior_predictive(idata_cg))

一旦后验分布被计算出来,我们就可以进行所有我们认为相关的分析。例如,我们可以进行后验预测测试。在 ArviZ 的帮助下,我们可以通过调用az.plot_ppc来实现。我们使用coordsflatten参数来为每一天获取一个子图。

代码 2.28

_, axes = plt.subplots(2, 2) 
az.plot_ppc(idata_cg, num_pp_samples=100, 
            coords={"days_flat":[categories]}, flatten=[], ax=axes)

从下图中可以看出,模型能够捕捉到分布的一般形状,但仍有一些细节难以捉摸。这可能是由于样本量相对较小,除了日期外,其他因素对小费的影响,或者两者的结合。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file83.png

图 2.20:Tips 数据集的后验预测检查

目前,我们认为该模型足够好,可以继续探索后验分布。我们可以从平均值的角度解释结果,然后找出哪些日期的平均值较高。但也有其他方法;例如,我们可能希望用后验均值差异来表达结果。此外,我们可能想要使用一些对我们受众来说更常见的效应大小度量,如优势概率或 Cohen’s d。在接下来的部分中,我们将解释这些替代方法。

2.8.2 Cohen’s d

测量效应大小的常见方法是 Cohen’s d,定义如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file84.jpg

由于我们有后验分布,因此可以计算 Cohen’s d 的分布。如果我们需要一个单一值,可以计算该分布的均值或中位数。

这个公式告诉我们,效应大小是均值差异与两组合并标准差的比值。通过取合并标准差,我们对均值差异进行了标准化。这一点非常重要,因为当差异为 1 且标准差为 0.1 时,效应大小大于标准差为 10 时相同差异的效应大小。Cohen’s d 可以被解释为 Z 得分(标准分数)。Z 得分是一个有符号的标准差数,表示一个值与被观测或测量的均值的差异。因此,0.5 的 Cohen’s d 可以被解释为两组之间差异为 0.5 个标准差。

即使均值差异已被标准化,我们仍然可能需要根据特定问题的上下文进行校准,才能判断某个给定值是大是小是中等。例如,如果我们习惯于对相同或相似的问题进行多次分析,我们可以习惯于 Cohen’s d 为 1。于是,当我们得到一个 Cohen’s d 为 2 时,我们就知道我们得到了重要的结果(或者是某处出错了!)。如果您还没有这种经验,您可以请教领域专家,获取他们宝贵的意见。

一个非常好的网页,可以探索 Cohen’s d 的不同值是什么样子的,网址是rpsychologist.com/d3/cohend。在该页面上,您还可以找到表示效应大小的其他方法;其中一些可能更为直观,例如超越概率,我们将在下一部分讨论。

2.8.3 超越概率

这是报告效应大小的另一种方式,它定义为从一个组中随机抽取的数据点大于从另一个组中随机抽取的一个数据点的概率。如果我们假设所使用的数据是正态分布的,那么我们可以使用以下公式通过 Cohen’s d 计算超越概率:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file85.jpg

Φ是累积正态分布,而δ是 Cohen’s d。

如果我们接受正态性假设,那么可以使用此公式从 Cohen’s d 的值计算超越概率。否则,我们可以直接通过从两个组中随机抽取样本并计算有多少次一个值大于另一个值来计算超越概率。这样,我们不需要 Cohen’s d 或假设正态性(请参见练习部分)。这是使用马尔可夫链蒙特卡洛MCMC)方法的一个优点;一旦我们从后验分布中获得样本,就可以通过这种方式计算许多量,通常比其他方法更为简单。

2.8.4 平均差异的后验分析

为了总结我们之前的讨论,让我们计算均值差异、Cohen’s d 和优势概率的后验分布,并将它们整合到一个图表中。图 2.21包含了大量信息。根据受众的不同,图表可能会过载,或者太拥挤。或许它适合你团队内部的讨论,但对于大众而言,去除某些元素或将信息分布在一个图表和一个表格,或者两个图表之间,可能会更方便。无论如何,在这里我们精确地展示它,以便你可以比较展示相同信息的不同方式,所以请花些时间思考这个图表。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file86.png

图 2.21:小费数据集的均值差异、Cohen’s d 和优势概率的后验分布

阅读图 2.21的一种方法是将零差异的参考值与 HDI 区间进行比较。只有一个案例,94%的 HDI 排除了参考值,也就是星期四和星期天的小费差异。在所有其他比较中,根据 HDI-参考值重叠标准,我们不能排除零差异。但即使是那个案例,平均差异也约为 0*.*5 美元。这个差异足够大吗?这个差异足够支持接受在星期天工作,并错过和家人或朋友共度时光的机会吗?这个差异足够让我们接受将小费平均分配到四天,并给每个女服务员和男服务员相同的小费金额吗?

简短的回答是,这类问题不能通过统计学来回答;它们只能通过统计学来提供信息。我希望你不会因为这个答案而感到失望,但除非我们在分析中包含所有对相关方重要的值,否则我们无法得到自动的答案。正式来说,这要求定义一个损失函数,或至少定义一个效应大小的阈值,这个阈值应该由这些值来决定。

2.9 总结

尽管贝叶斯统计在概念上很简单,但完全概率模型常常导致解析上不可处理的表达式。多年来,这一直是一个巨大的障碍,阻碍了贝叶斯方法的广泛应用。幸运的是,数学、统计学、物理学和计算机科学通过数值方法来拯救这一局面,这些方法在原则上能够解决任何推断问题。自动化推断过程的可能性促使了概率编程语言的发展,这些语言清晰地将模型定义和推断分开。PyMC 是一个用于概率编程的 Python 库,具有非常简单、直观且易读的语法,而且非常接近描述概率模型的统计语法。

我们通过重新回顾第一章中的抛硬币模型来介绍 PyMC 库,这次我们没有通过解析方法推导后验分布。PyMC 模型定义在上下文管理器中。为了将概率分布添加到模型中,我们只需写一行代码。分布可以组合使用,可以作为先验(未观察变量)或似然(已观察变量)。如果我们将数据传递给分布,它将成为似然。采样也可以通过一行代码实现。PyMC 允许我们从后验分布中获取样本。如果一切顺利,这些样本将代表正确的后验分布,因此它们将成为我们模型和数据的逻辑后果的表示。我们可以使用与 PyMC 配合使用的 Python 库 ArviZ 来探索 PyMC 生成的后验分布,ArviZ 可以帮助我们解释和可视化后验分布。在使用后验帮助我们做推理驱动的决策时,一种方法是将 ROPE 与 HDI 区间进行比较。我们还简要提到过损失函数的概念,这是量化在不确定性下做决策时的权衡和成本的一种正式方式。我们学到,损失函数和点估计是紧密相关的。

到目前为止,讨论仅限于一个简单的单参数模型。使用 PyMC 将其推广到任意数量的参数是非常简单的;我们通过高斯分布和学生 t 分布模型来展示如何做到这一点。高斯分布是学生 t 分布的特例,我们向你展示了如何使用后者在存在离群值的情况下进行稳健推断。在接下来的章节中,我们将探讨这些模型如何作为线性回归模型的一部分进行使用。我们使用高斯模型来比较不同组之间的差异。虽然这有时会以假设检验的方式进行框架化,但我们采取了另一种方法,将这一任务框架化为推断效应量的问题,我们通常认为这种方法更丰富、更有成效。我们还探讨了不同的效应量解释和报告方式。

通过本章及上一章的学习,我们已经准备好学习本书中最重要的概念之一——层级模型。那将是下一章的主题。

2.10 练习

  1. 使用 PyMC,将our_first_model中 Beta 分布的先验参数更改为与上一章相匹配的参数。将结果与上一章进行比较。

  2. 将模型our_first_model与先前的θ ∼ Beta(1*,1)的模型进行比较,再与具有先验θ* ∼ https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/U.PNG(0*,*1)的模型进行比较。后验分布相似还是不同?采样是更慢、更快还是相同?如果使用不同区间(如[-1, 2])的均匀分布会怎样?模型能运行吗?会遇到什么错误?

  3. PyMC 有一个函数pm.model_to_graphviz,可以用来可视化模型。使用它来可视化our_first_model模型。将结果与 Kruschke 图进行比较。使用pm.model_to_graphviz来可视化模型comparing_groups

  4. 阅读关于 PyMC 文档中煤矿灾难模型的内容(shorturl.at/hyCX2)。尝试自己实现并运行这个模型。

  5. 修改model_g,将均值的先验改为以经验均值为中心的高斯分布,并尝试一些合理的标准差值来调整这个先验。推断对这些变化的稳健性/敏感度如何?你认为使用高斯分布(一种无界分布,范围从-∞到∞)来建模像这样的有界数据(数据在 0 到 100 之间)怎么样?记住我们曾说过,不可能观察到低于 0 或高于 100 的值。

  6. 使用chemical_shifts.csv文件中的数据,计算有无异常值的经验均值和标准差。将这些结果与使用高斯分布和 Student’s t 分布的贝叶斯估计结果进行比较。你观察到了什么?

  7. 通过向chemical_shifts.csv中添加更多异常值来重复上一个练习,并使用这些新数据为model_gmodel_t计算新的后验分布。你观察到了什么?

  8. 探索idata_cg的 InferenceData 对象。

    • 它包含多少个组?

    • 使用sel方法检查特定日期参数μ的后验分布。

    • 计算星期四和星期日之间均值差异的分布。结果的 DataArray 的坐标和维度是什么?

  9. 对于提示示例,直接从后验分布计算优势概率(无需先计算 Cohen’s d)。你可以使用pm.sample_posterior_predictive()函数从每个组中采样。这与假设正态分布的计算结果有何不同?你能解释结果吗?

加入我们的社区 Discord 空间

加入我们的 Discord 社区,结识志同道合的人,并与 5000 多名成员一起学习:packt.link/bayesian

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file1.png

第三章

分层模型

分层模型是一个非常棒的想法——让我们做更多这样的模型吧!- 贝叶斯建模的禅意

第二章中,我们看到了一个小费的例子,其中数据中有多个组,每个组对应星期四、星期五、星期六和星期日。我们决定分别建模每个组。这样做有时是可以的,但我们应当注意我们的假设。通过独立建模每个组,我们假设这些组是互不相关的。换句话说,我们假设知道一天的小费信息并不能给我们提供其他一天的小费信息。这可能是一个过于强的假设。那么,是否有可能构建一个模型,允许我们在组之间共享信息呢?这不仅可能,而且正是本章的主要内容。幸运的是,你来了!

在本章中,我们将讨论以下主题:

  • 分层模型

  • 部分合并

  • 收缩

3.1 信息共享,共享先验

分层模型也被称为多级模型、混合效应模型、随机效应模型或嵌套模型。当数据可以被描述为分组或具有不同层级时,分层模型尤其有用,比如数据嵌套在地理区域中(例如,属于一个省的城市和属于一个国家的省),或者具有分层结构(如学生嵌套在学校内,或者病人嵌套在医院中),或者是对同一个体的重复测量。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file87.png

图 3.1:合并模型、非合并模型和分层模型之间的差异

分层模型是一个自然的方式,用于在不同组之间共享信息。在分层模型中,先验分布的参数本身也会赋予一个先验分布。这些更高层次的先验通常被称为超先验;“超”在希腊语中意味着“在……之上”。拥有超先验使得模型可以在组之间共享信息,同时仍允许组之间存在差异。换句话说,我们可以认为先验分布的参数属于一个共同的参数群体。图 3.1 显示了一个图示,展示了合并模型(单一组)、非合并模型(所有分开的组)和分层模型(也称为部分合并模型)之间的高层次差异。

分层模型的概念看起来可能过于简单,几乎是微不足道的,但它有着深远的影响。因此,在本章的其余部分,我们将通过不同的例子来理解它们的含义。我相信这些例子不仅能帮助你更好地理解这一概念,还能说服你,它是一个非常有用的工具,能够应用于你自己的问题。

3.2 分层转变

蛋白质是由 20 种叫做氨基酸的单位组成的分子。每种氨基酸可以在蛋白质中出现 0 次或更多次。就像旋律是由一系列音符定义的,蛋白质则是由一系列氨基酸定义的。一些音符的变化可能导致旋律的微小变化,而其他音符的变化则可能导致完全不同的旋律。蛋白质中也有类似的情况。研究蛋白质的一种方法是使用核磁共振(与医学成像使用的技术相同)。这项技术使我们能够测量多种量,其中之一叫做化学位移。你可能还记得我们在第 2章中看过一个使用化学位移的例子。

假设我们想将一种理论的化学位移计算方法与实验观察结果进行比较,以评估该理论方法是否能再现实验值。幸运的是,有人已经做了实验并进行了理论计算,我们只需要将它们进行比较。以下数据集包含了一组蛋白质的化学位移值。如果你查看cs_data数据框,你会发现它有四列:

  1. 第一列是一个代码,用来识别蛋白质(你可以通过在www.rcsb.org/输入该代码来获取有关该蛋白质的许多信息)

  2. 第二列是氨基酸的名称(你可能会注意到这里只有 19 个独特的名称;该数据集中缺少一种氨基酸)

  3. 第三列包含了化学位移的理论值(使用量子方法计算得出)

  4. 第四列包含了实验值

现在我们有了数据,接下来应该怎么做?一个选项是采用经验差异并拟合一个高斯分布,或者也许是学生 t 分布。因为氨基酸是一类化学化合物,假设它们都是相同的,并为所有差异估算一个单一的高斯分布是有道理的。但你可能会认为,有 20 种不同类型的氨基酸,每种都有不同的化学性质,因此一个更好的选择是拟合 20 个独立的高斯分布。我们应该怎么做?

让我们花点时间思考一下,哪个选项是最好的。如果我们将所有数据合并,估算结果会更准确,但我们无法从单个组(氨基酸)中获取信息。相反,如果我们将它们作为独立的组处理,我们将获得更详细的分析,但准确性较差。我们应该怎么做?

当有疑问时,什么都行!(不确定这是否是你生活中好的普遍建议,但我喜欢这首歌 www.youtube.com/watch?v=1di09XZUlIw)。我们可以建立一个层级模型;通过这种方式,我们允许在组级别进行估算,但有一个限制,即它们都属于一个更大的组或人群。为了更好地理解这一点,我们来为化学位移数据建立一个层级模型。

为了查看非层次模型(非汇聚模型)和层次模型之间的区别,我们将构建两个模型。第一个模型本质上与第 2 章中的comparing_groups模型相同:

代码 3.1

with pm.Model(coords=coords) as cs_nh: 
    μ = pm.Normal('μ', mu=0, sigma=10, dims="aa") 
    σ = pm.HalfNormal('σ', sigma=10, dims="aa") 
    y = pm.Normal('y', mu=μ[idx], sigma=σ[idx], observed=diff) 
    idata_cs_nh = pm.sample()

现在,我们将构建模型的层次版本。我们添加了两个超先验,一个用于μ的均值,另一个用于μ的标准差。我们将σ留空,不添加超先验;换句话说,我们假设观察值与理论值之间的方差对于所有组都是相同的。这是一个建模选择,你可能会遇到一个问题,觉得这种假设不可接受,并认为有必要为σ添加一个超先验;你可以自由这样做:

代码 3.2

with pm.Model(coords=coords) as cs_h: 
    # hyper_priors 
    μ_mu = pm.Normal('μ_mu', mu=0, sigma=10) 
    μ_sd = pm.HalfNormal('μ_sd', 10) 
    # priors 
    μ = pm.Normal('μ', mu=μ_mu, sigma=μ_sd, dims="aa") 
    σ = pm.HalfNormal('σ', sigma=10, dims="aa") 
    # likelihood 
    y = pm.Normal('y', mu=μ[idx], sigma=σ[idx], observed=diff) 
    idata_cs_h = pm.sample()

图 3.2 显示了cs_hcs_nh模型的图形表示。我们可以看到,cs_h多了一个层级,用于表示μ的超先验。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file88.png

图 3.2:化学位移数据的非层次(左)和层次(右)模型的图形表示。每个子图都是通过pm.model_to_graphviz(.)函数生成的

我们将使用 ArviZ 的plot_forest函数来比较结果。我们可以将多个模型传递给此函数。当我们希望比较不同模型中参数的值时,这非常有用,比如当前示例中所展示的。在图 3.3中,我们有一个包含 40 个估计均值的图,每个氨基酸(共 20 个)对应两个模型中的一个。我们还展示了它们的 94% HDI 和四分位距(分布的中央 50%)。垂直虚线表示根据层次模型得到的全局均值。该值接近零,正如预期的那样,理论值与实验值相符。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file89.png

图 3.3:层次和非层次模型的化学位移差异

该图的最相关部分是层次模型的估计值被拉向部分汇聚的均值,或者说它们相比于非汇聚的估计值被“收缩”。你还会注意到,这种效应在那些离均值较远的组(如PRO)中更加明显,而且不确定性与非层次模型的相当,甚至更小。估计值是部分汇聚的,因为我们为每个组都有一个估计,但各个组的估计值通过超先验相互约束。因此,我们得到了一种介于将所有化学位移放在一个组中和将每个氨基酸分为 20 个独立组之间的中间情况。各位女士、先生以及非二元性别流动者,这就是层次模型的美妙之处。

3.3 水质

假设我们想分析一个城市的水质,那么我们通过将城市划分为多个社区来进行采样。我们可能认为有两种选择来分析这些数据:

  • 将每个社区作为一个独立实体进行研究

  • 将所有数据汇总在一起,并将城市的水质估算为一个大的整体。

你可能已经注意到这里的模式。我们可以通过说我们获得了问题的更详细视图来证明第一个选项的合理性,否则如果我们对数据进行平均处理,问题可能会变得不可见或不那么明显。第二个选项的合理性可以通过说如果我们将数据汇总,我们就能获得更大的样本量,从而得到更准确的估计来证明。但是我们已经知道我们有第三个选项:我们可以做一个层次模型!

对于这个例子,我们将使用合成数据。我喜欢使用合成数据;它是理解事物的一个好方法。如果你不理解某个东西,就模拟它!合成数据有很多用途。在这里,我们将假设我们已经从同一城市的三个不同区域收集了水样,并测量了水中的铅含量;铅含量超过世界卫生组织建议值的样本标记为零,低于建议值的样本标记为一。这是一个非常简单的场景。在更现实的例子中,我们会有铅浓度的连续测量,并且可能会有更多的组。然而,针对我们当前的目的,这个例子足够揭示层次模型的细节。我们可以用以下代码生成合成数据:

代码 3.3

N_samples = [30, 30, 30] 
G_samples = [18, 18, 18] 
group_idx = np.repeat(np.arange(len(N_samples)), N_samples) 
data = [] 
for i in range(0, len(N_samples)): 
    data.extend(np.repeat([1, 0], [G_samples[i], N_samples[i]-G_samples[i]]))

我们正在模拟一个实验,我们测量了三个组,每个组包含一定数量的样本;我们将每组的样本总数存储在N_samples列表中。使用G_samples列表,我们记录每组中良好水质样本的数量。其余的代码仅用于生成一个包含零和一的数据列表。

这个问题的模型与我们用于硬币问题的模型类似,除了两个重要特征:

  • 我们定义了两个超先验,这些将影响 Beta 先验。

  • 我们不是对参数αβ设置超先验,而是通过μ(均值)和ν(Beta 分布的浓度或精度)来定义 Beta 分布。精度类似于标准差的倒数;ν的值越大,Beta 分布就越集中。在统计符号中,我们的模型如下所示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file90.jpg

请注意,我们使用下标i来表示模型中某些参数在不同组之间的取值。通过 Kruschke 图(见图 3.4),我们可以看到新模型相比于图 1.14,多了一个额外的层级。还要注意,在这个模型中,我们是用μν来参数化 Beta 先验分布,而不是使用αβ。这是贝叶斯统计中的常见做法,因为μναβ更直观。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file91.png

图 3.4:层次模型

让我们在 PyMC 中编写这个模型:

代码 3.4

with pm.Model() as model_h: 
    # hypyerpriors 
    μ = pm.Beta('μ', 1, 1) 
    ν = pm.HalfNormal('ν', 10) 
    # prior 
    *θ* = pm.Beta('*θ*', mu=μ, nu=ν, shape=len(N_samples)) 
    # likelihood 
    y = pm.Bernoulli('y', p=*θ*[group_idx], observed=data) 

    idata_h = pm.sample()

3.4 收缩效应

为了展示层次模型的主要后果,我需要你的帮助,请参与一个简短的实验。我需要你打印并保存通过az.summary(idata_h)计算出的摘要。然后,我希望你在对合成数据做些小调整后,再运行模型两次。记得每次运行后保存摘要。总的来说,我们会进行三次运行:

  • 一轮设置G_samples的所有元素为 18

  • 一轮设置G_samples的所有元素为 3

  • 最后一轮设置一个元素为 18,另外两个为 3

在继续之前,请花点时间思考一下这个实验的结果。重点关注每个实验中θ的估计均值。根据前两次运行的模型,你能预测第三种情况的结果吗?

如果我们把结果放在表格中,会得到大致如下的内容;记住,由于采样过程的随机性,可能会有小的变化:

G_samplesMean
18, 18, 180.6, 0.6, 0.6
3, 3, 30.11, 0.11, 0.11
18, 3, 30.55, 0.13, 0.13

表格 3.1:样本数据和对应的均值

在第一行中,我们看到对于一个由 30 个样本中挑选出的 18 个好样本组成的数据集,我们得到的θ均值是 0.6;请记住,现在θ的均值是一个包含 3 个元素的向量,每个组对应一个元素。然后,在第二行中,只有 30 个样本中的 3 个是好样本,θ的均值为 0.11。这些结果并不令人惊讶;我们的估计值与经验均值几乎相同。有趣的部分出现在第三行。我们没有得到来自前两行的θ均值的混合结果,如 0.6、0.11 和 0.11,而是得到了不同的值,即 0.55、0.13 和 0.13。

到底发生了什么?我们是不是哪里出错了?并不是那样。我们看到的现象是估计值已经向共同均值收缩了。这是完全正常的;实际上,这只是我们模型的一个结果。通过使用超先验,我们是从数据中估计 Beta 先验分布的参数。每个组都在向其他组提供信息,同时每个组也通过其他组的估计结果来获得信息。

图 3.5 显示了将μν的后验估计值代入 Beta 分布。换句话说,这是推断 Beta 先验分布的后验分布。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file92.png

图 3.5: 推断 Beta 先验分布的后验分布

为什么收缩是可取的?因为它有助于更稳定的推断。从多个方面来看,这类似于我们在学生 t 分布和异常值中看到的情况;使用重尾分布能够使模型对偏离均值的数据点更具鲁棒性。引入超先验会导致模型更为保守,使其对单个组中极端值的反应更为迟缓。想象一下,不同邻里之间的样本量不同,有的较小,有的较大;样本量越小,出现虚假结果的可能性越大。在极端情况下,如果你在某个邻里只取一个样本,可能会碰到整个邻里唯一的老旧铅管,或者相反,唯一的 PVC 管。在一种情况下,你会高估差的质量,而在另一种情况下,你会低估它。在层级模型下,一个组的错误估计会通过其他组提供的信息得到缓解。更大的样本量也能起到类似的效果,但往往这种方法不可行。

收缩的程度取决于数据;数据量更多的组会比数据量较少的组更强烈地拉动其他组的估计值。如果几个组相似,而某一个组不同,那么相似的组会将它们的相似性传递给其他组,并强化共同的估计,同时将不同组的估计值向它们拉拢;这正是我们在之前的例子中看到的情况。超先验在调节收缩量方面也起着作用。如果我们对组级分布有可信的信息,我们可以有效地使用信息性先验分布将估计值收缩到一个合理的值。

收缩

在层级模型中,共享相同超先验的组实际上通过超先验共享信息。这导致了收缩现象,也就是,单独的估计值会向共同的均值收缩。通过部分汇总数据,我们将组视为一种在独立组和单一大组之间的中间状态。

没有什么能阻止我们只使用两个组来构建层级模型,但我们更倾向于使用多个组。直观来看,原因是收缩的过程就像假设每个组都是一个数据点,我们在组层面上估计标准差。一般而言,除非我们有强有力的先验信息来指导我们的估计,否则我们不太信任数据点过少的估计。层级模型也是如此。

3.5 层级模型逐层构建

各种数据结构有助于层次化描述,可以涵盖多个层级。例如,考虑职业足球(足球)运动员。与许多其他运动一样,运动员有不同的职位。我们可能有兴趣估算每个运动员、每个职位以及所有职业足球运动员的技能指标。这种层次化结构在许多其他领域也能找到:

  • 医学研究:假设我们有兴趣估计不同药物治疗某种疾病的效果。我们可以根据患者的个人信息、疾病严重程度和其他相关因素对患者进行分类,并构建一个层次模型来估算每个子组的治愈或治疗成功的概率。然后,我们可以使用子组分布的参数来估算整个患者群体的治愈或治疗成功的总体概率。

  • 环境科学:假设我们有兴趣估计某种污染物对特定生态系统的影响。我们可以对生态系统内的不同栖息地(例如河流、湖泊、森林、湿地)进行分类,并构建一个层次模型来估算每个栖息地内的污染物水平分布。然后,我们可以使用栖息地分布的参数来估算整个生态系统中污染物水平的总体分布。

  • 市场研究:假设我们有兴趣了解不同地区消费者购买某一产品的行为。我们可以根据消费者的个人信息(例如年龄、性别、收入、教育)对其进行分类,并构建一个层次模型来估算每个子组的购买行为分布。然后,我们可以使用子组分布的参数来估算整个消费者群体的购买行为分布。

回到我们的足球运动员数据,我们收集了来自英超联赛法甲联赛德甲联赛意甲联赛西甲联赛的数据,数据覆盖了四年(2017 至 2020 年)。假设我们有兴趣了解每次射门的进球数。这通常是统计学家所说的成功率,我们可以通过二项模型来估算,模型中参数n是射门次数,观察值y是进球数。这样就剩下了一个未知的参数p。在之前的例子中,我们将此参数称为θ,并使用 Beta 分布对其建模。我们现在也会这样做,但采用层次化的方法。请参见图 3.6,它提供了整个模型的图形表示。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file93.png

图 3.6:足球运动员示例的层次模型。注意,与之前的层次模型相比,我们增加了一个层级。

在我们的模型中,θ表示每个球员的成功率,因此它是一个大小为n_players的向量。我们使用 Beta 分布来建模θ。Beta 分布的超参数将是μ[p]和ν[p]向量,这些向量的大小为 4,代表我们数据集中四个位置(后卫DF、中场MF、前锋FW和门将GK)。我们需要正确地索引向量μ[p]和ν[p],以匹配球员的总数。最后,我们将有两个全局参数,μν,表示职业足球运动员。

PyMC 模型在下面的代码块中定义。pm.Beta(’μ’, 1.7, 5.8)是在 PreliZ 的帮助下选择的先验,95%的质量分布在 0 到 0.5 之间。这是一个弱信息先验的示例,因为几乎没有疑问,0.5 的成功率是一个较高的值。体育统计数据研究得非常透彻,存在大量可用于定义更强先验的信息。对于这个示例,我们将使用这个先验。类似的理由也适用于先验pm.Gamma(’<https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/nu.PNG>’, mu=125, sigma=50),我们定义其为最大熵 Gamma 先验,90%的质量分布在 50 到 200 之间:

代码 3.5

coords = {"pos": pos_codes} 
with pm.Model(coords=coords) as model_football: 
    # Hyper parameters 
    μ = pm.Beta('μ', 1.7, 5.8) 
    ν = pm.Gamma('ν', mu=125, sigma=50) 
    # Parameters for positions 
    μ_p = pm.Beta('μ_p', 
                       mu=μ, 
                       nu=ν, 
                       dims = "pos") 
    ν_p = pm.Gamma('ν_p', mu=125, sigma=50, dims="pos") 
    # Parameter for players 
    *θ* = pm.Beta('*θ*', 
                    mu=μ_p[pos_idx], 
                    nu=ν_p[pos_idx]) 
    _ = pm.Binomial('gs', n=football.shots.values, p=*θ*, 
                    observed=football.goals.values) 

    idata_football = pm.sample()

图 3.7的顶部面板中,我们展示了全局参数μ的后验分布。后验分布接近于 0.1。这意味着,对于一名职业足球运动员(来自顶级联赛),进球的概率平均为 10%。这是一个合理的值,因为进球并非易事,并且我们没有区分位置,即我们考虑的是那些主要角色不是进球的球员。在中间面板中,我们展示了前锋位置的估计μ[p]值;如预期的那样,它高于全局参数μ。在底部面板中,我们展示了梅西的估计θ值,值为 0.17,高于全局参数μ和前锋位置μ[p]值。这也是可以预期的,因为梅西是世界上最优秀的足球运动员,他的主要角色是进球。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file94.png

图 3.7:全局参数的后验分布(顶部)、前向位置的均值(中部)以及梅西的θ参数(底部)

图 3.8 展示了参数 μ[p] 的后验分布的森林图。正如我们已经看到的,前锋位置的后验分布集中在 0.13 附近,并且是四个位置中最高的。这是有道理的,因为前锋的角色是进球以及助攻。μ[p] 的最低值出现在守门员位置。这是预期的,因为守门员的主要角色是阻止对方进球,而不是进球。值得注意的是,不确定性非常高;这是因为我们数据集中进球的守门员数量非常少,准确来说只有三名。后卫和中场的位置的后验分布大致处于中间,且中场略高于后卫。我们可以解释为,中场的主要角色是既要防守也要进攻,因此进球的概率比后卫高,但低于前锋。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file95.png

图 3.8:参数 μ 的后验分布

p,均值位置

你需要知道何时停止

我们可以根据需要创建具有任意层级的层级模型。但除非问题需要额外的结构,否则添加超过必要层级的层次不会提升模型或推断的质量。相反,我们会陷入超先验和超参数的迷网中,而无法为它们赋予任何有意义的解释。构建模型的目标是理解数据,因此,实用的模型通常是那些能够反映并利用数据结构的模型。

3.6 小结

本章介绍了本书中最重要的概念之一:层级模型。每当我们能在数据中识别出子群时,就可以构建层级模型。在这种情况下,我们不是将子群当作独立的实体,或忽略子群将它们视为单一群体,而是构建一个模型,在群体之间部分合并信息。部分合并的主要效果是,每个子群的估计会受到其他子群估计的影响。这种效应被称为收缩效应,一般来说,这是一个非常有用的技巧,通过让推断更加保守(因为每个子群通过将估计拉向自己来影响其他子群)和更具信息量,来帮助改进推断。我们在子群层面和群体层面都能获得估计。

借用 Python 禅意的表达方式,我们可以肯定地说,层次模型是一个 非常棒的主意,让我们做更多这样的事情! 在接下来的章节中,我们将继续构建层次模型,并学习如何使用它们来构建更好的模型。我们还将讨论层次模型与统计学和机器学习中普遍存在的过拟合/欠拟合问题的关系,内容将在第五章中进行探讨。在第十章中,我们将讨论在从层次模型中采样时可能遇到的一些技术问题,并探讨如何诊断和解决这些问题。

3.7 练习

  1. 用你自己的话解释以下概念,用两到三句话:

    • 完全汇总

    • 无汇总

    • 部分汇总

  2. 重复我们在model_h中进行的练习。这一次,不使用层次结构,使用一个简单的先验,比如 Beta(α = 1*,β = 1)。比较两种模型的结果。

  3. 创建一个层次化版本的 第二章 中的 tips 示例,通过在一周的天数之间进行部分汇总。将结果与没有层次结构时获得的结果进行比较。

  4. 图 3.7中的每个子面板,添加一条表示每个层级的经验均值的参考线,即全局均值、前向均值和梅西的均值。比较经验值和后验均值。你观察到什么?

  5. 氨基酸通常被分为极性非极性带电特殊等类别。构建一个类似于cs_h的层次模型,但包括一个氨基酸类别的组效应。将结果与本章中获得的结果进行比较。

加入我们的社区 Discord 空间

加入我们的 Discord 社区,与志同道合的人交流,并和超过 5000 名成员一起学习,链接地址:packt.link/bayesian

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file1.png

第四章

使用线性模型

在三百多年的科学历史中,唯一没有改变的或许只有一点:对简单的热爱。– 乔治·瓦根斯贝格

音乐——从古典作品到 The Ramones 的Sheena is a Punk Rocker,再到车库乐队的未被认可的热门歌曲以及皮亚佐拉的《自由探戈》——由重复出现的模式构成。相同的音阶、和弦组合、吉他即兴、旋律等反复出现,创造出一个美妙的音景,能够激发并调节人类可能体验到的所有情感。同样,统计学的世界也是建立在不断出现的模式上的,这些小的动机时不时地出现。在本章中,我们将关注其中一个最流行和有用的模式,即线性 模型(或者如果你愿意,可以叫做动机)。这是一个非常有用的模型,独立使用时效果显著,同时也是许多其他模型的基础。如果你曾经学过统计学课程,你可能听说过简单和多元线性回归、逻辑回归、方差分析(ANOVA)、协方差分析(ANCOVA)等方法。所有这些方法都是同一个基本模式——线性回归模型——的不同变体。

本章将涉及以下主题:

  • 简单线性回归

  • 负二项回归

  • 稳健回归

  • 逻辑回归

  • 变量的方差

  • 层次线性回归

  • 多元线性回归

4.1 简单线性回归

我们在科学、工程和商业中遇到的许多问题都具有以下形式:我们有一个变量X,我们想要建模或预测一个变量Y。重要的是,这些变量是成对的,如{(x[1],y[1]),(x[2],y[2]),https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file96.jpg,(x[n],y[n])}。在最简单的情况下,即简单线性回归中,XY都是一维连续随机变量。所谓连续,意味着该变量用实数表示。使用 NumPy,你将把这些变量表示为一维浮点数组。通常,人们称Y为因变量、预测变量或结果变量,X为自变量、预测因子或输入变量。

一些典型的线性回归模型应用场景如下:

  • 模拟土壤盐分与作物生产力之间的关系。然后,回答一些问题,比如:这种关系是线性的吗?这种关系有多强?

  • 找出各国平均巧克力消费量与该国诺贝尔奖得主数量之间的关系,并理解为何这种关系可能是虚假的。

  • 通过使用本地天气报告中的太阳辐射,预测你家的燃气账单(用于取暖和烹饪)。这个预测有多准确?

第二章中,我们看到了正态模型,我们定义它为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file97.jpg

线性回归的主要思想是通过添加一个预测变量X来扩展此模型,以估计均值μ

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file98.jpg

该模型表示变量 X 与变量 Y 之间存在线性关系。但由于噪声项 σ 的存在,这种关系并不是确定性的。此外,该模型表示 Y 的均值是 X 的线性函数,具有截距 α斜率 β。截距告诉我们当 X = 0 时 Y 的值,斜率则告诉我们 X 每变化一个单位,Y 会发生多少变化。由于我们不知道 αβσ 的具体值,因此我们为它们设定了先验分布。

在为线性模型设定先验时,我们通常假设它们是独立的。这个假设极大简化了先验设置,因为这样我们只需要设置三个先验,而不是一个联合先验。至少原则上,αβ 可以取实数线上的任何值,因此通常使用正态分布作为它们的先验。而因为 σ 是一个正数,通常为其使用半正态分布或指数分布作为先验。

截距的值可以根据不同的问题和领域知识有很大的变化。对于我曾处理过的许多问题,α 通常围绕 0 中心,且标准差不超过 1,但这只是我在一小部分问题上的经验(几乎是轶事性质的),并不是可以轻易迁移到其他问题的经验。通常,预测斜率(β)的先验值可能更为容易。例如,我们可能知道斜率的符号:例如,我们期望变量体重与身高变量平均呈正相关。对于 σ,我们可以将其设置为与变量 Y 的值同量级的大值,例如是其标准差的两倍。我们应该小心不要使用观察数据来推测先验;通常,数据用来避免使用过于严格的先验是可以接受的。如果我们对参数知之甚少,那么确保我们的先验具有模糊性是合乎逻辑的。如果我们希望使用更具信息量的先验,那么不应从观察数据中获取这些信息,而应从我们的领域知识中获得。

扩展常规模型

线性回归模型是常规模型的扩展,其中均值是作为预测变量的线性函数计算的。

4.2 线性单车模型

我们现在对贝叶斯线性模型有了一个大致的了解。让我们通过一个例子来巩固这个概念。我们将从一个非常简单的例子开始:我们有一座城市的温度记录和租赁自行车的数量。我们想要建立温度和租赁自行车数量之间的关系模型。图 4.1 显示了来自 UCI 机器学习库的共享单车数据集这两个变量的散点图(archive.ics.uci.edu/ml/index.php)。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file99.png

图 4.1:自行车共享数据集。摄氏温度与租用自行车数量的散点图

原始数据集包含 17,379 条记录,每条记录有 17 个变量。我们将只使用 359 条记录和两个变量,temperature(摄氏温度)和 rented(租用的自行车数量)。我们将使用 temperature 作为自变量(我们的 X),租用的自行车数量作为因变量(我们的 Y)。我们将使用以下模型:

代码 4.1

with pm.Model() as model_lb: 
    *α* = pm.Normal("*α*", mu=0, sigma=100) 
    *β* = pm.Normal("*β*", mu=0, sigma=10) 
    σ = pm.HalfCauchy("σ", 10) 
    μ = pm.Deterministic("μ", *α* + *β* * bikes.temperature) 
    y_pred = pm.Normal("y_pred", mu=μ, sigma=σ, observed=bikes.rented) 
    idata_lb = pm.sample()

花点时间逐行阅读代码,确保理解代码的含义。同时查看 图 4.2,以获得该模型的可视化表示。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file100.png

图 4.2:自行车共享数据集的贝叶斯线性模型

正如我们之前所说,这类似于一个正态模型,但现在均值被建模为温度的线性函数。截距是 α,斜率是 β。噪声项是 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/e.png,均值是 μ。这里唯一的新东西是 Deterministic 变量 μ。这个变量不是随机变量,而是一个确定性变量,它由截距、斜率和温度计算得出。我们需要指定这个变量,因为我们希望将其保存在 InferenceData 中以备后续使用。我们本可以写 μ = α + β * bikes.temperature,甚至可以写成 _ = pm.Normal('y_pred', mu=α + β * bikes.temperature, ...,模型会保持不变,但我们将无法将 μ 保存在 InferenceData 中。注意,μ 是一个与 bikes.temperature 长度相同的向量,它与数据集中的记录数相同。

4.2.1 解释后验均值

为了探索推断结果,我们将生成一个后验图,但会省略确定性变量 μ。我们这样做是因为如果不省略该变量,我们将得到很多图表,每个 temperature 值对应一个图表。我们可以通过将我们想要包含在图表中的变量名作为列表传递给 var_names 参数,或者像以下代码块中那样否定我们想要排除的变量:

代码 4.2

az.plot_posterior(idata_lb, var_names=['∼μ'])

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file101.png

图 4.3:自行车线性模型的后验图

图 4.3中,我们可以看到αβσ的边际后验分布。如果我们只读取每个分布的均值,比如μ = 69 + 7*.9X*,通过这些信息我们可以得出,温度为 0 时租赁自行车的预期数量为 69 辆,每升高 1 度温度,租赁的自行车数量增加 7.9 辆。因此,当温度为 28 度时,我们预期租赁 278 辆自行车,即 69 + 7*.9 ∗ 28 ≈ 278 辆。这是我们的预期值,但后验分布也告诉我们这个估计值的周围不确定性。例如,β的 94% HDI 为(6.1, 9.7),所以每升高 1 度温度,租赁的自行车数量可能增加 6 辆至约 10 辆。此外,即使我们忽略后验不确定性,只关注均值,我们仍然对租赁自行车数量有不确定性,因为我们有一个σ*值为 170。因此,如果我们说温度为 28 度时我们预期租赁 278 辆自行车,我们也不应该感到惊讶,实际数量可能在 100 到 500 辆之间。

现在,让我们创建一些图表,帮助我们可视化这些参数的综合不确定性。我们从两个图开始,展示均值(见图 4.4)。这两个图都是温度作为自变量时租赁自行车数量的均值图。不同之处在于我们如何表示不确定性。我们展示了两种常见的表示方法。在左侧子面板中,我们从后验分布中抽取 50 个样本,并将它们作为单独的线条绘制。在右侧子面板中,我们则采用所有可用的后验样本来计算 94%的 HDI。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file102.png

图 4.4:自行车线性模型的后验图

图 4.4中的图表传达的基本相同的信息,只是其中一个通过一组线条表示不确定性,另一个则通过阴影区域表示。请注意,如果你重复代码生成图表,你会得到不同的线条,因为我们正在从后验分布中采样。然而,阴影区域将保持不变,因为我们使用了所有可用的后验样本。如果我们进一步拟合模型,我们不仅会得到不同的线条,阴影区域也可能发生变化,并且不同运行之间的差异可能非常小;如果差异很大,可能需要增加抽样次数,或者模型和采样存在问题(有关指导,请参见第十章)。

不管怎样,为什么我们要展示两个略有不同的图,它们传达的是相同的信息呢?嗯,这是为了突出不同的方式来表示不确定性。哪种更好?像往常一样,这取决于具体的上下文。阴影区域是一个不错的选择;它非常常见,且计算和解释都很简单。除非有特定的原因需要展示单个后验样本,否则阴影区域可能是你首选的方式。但我们也许希望展示单个后验样本。例如,大多数线条可能覆盖某个区域,但我们得到一些斜率非常大的线条。阴影区域可能会掩盖这些信息。如果你在展示单个后验样本时,可能可以考虑将其做成动画,特别是当你在演示或视频中展示时(详见 Kale 等人 [2019]了解更多)。

向你展示图 4.4中的两个图的另一个原因是,你可以学习从后验中提取信息的不同方式。请注意接下来的代码块。为了清晰起见,我们省略了绘图代码,只展示核心计算:

代码 4.3

posterior = az.extract(idata_lb, num_samples=50) 
x_plot = xr.DataArray( 
    np.linspace(bikes.temperature.min(), bikes.temperature.max(), 50), 
    dims="plot_id" 
) 
mean_line = posterior["*α*"].mean() + posterior["*β*"].mean() * x_plot 
lines = posterior["*α*"] + posterior["*β*"] * x_plot 
hdi_lines = az.hdi(idata_lb.posterior["μ"]) 
...

你可以看到在第一行中,我们使用了az.extract。这个函数将chaindraw维度堆叠到一个单一的sample维度中,这在后续处理时可能会很有用。此外,我们使用num_samples参数从后验中请求一个子样本。默认情况下,az.extract会作用于后验组。如果你想从另一个组提取信息,可以使用group参数。在第二行中,我们定义了一个叫做x_plot的 DataArray,包含从最小到最大观测温度的等间距值。创建 DataArray 的原因是能够在接下来的两行中使用 Xarray 的自动对齐功能。如果我们使用 NumPy 数组,则需要添加额外的维度,这通常会令人困惑。为了更好地理解我的意思,最好的方式是定义x_plot = np.linspace(bikes.temperature.min(), bikes.temperature.max())并尝试重新绘制图形。在代码的第三行,我们计算了后验中μ的均值,针对每个x_plot的值;在第四行,我们计算了μ的个别值。在这两行中,我们本可以使用posterior[’μ’],但我们显式地重写了线性模型。我们这样做的目的是希望能帮助你更好地理解线性模型。

4.2.2 解释后验预测

如果我们不仅仅对期望值(均值)感兴趣,而是想从预测的角度来思考,也就是说,从租用自行车的角度来看怎么办?嗯,为此,我们可以进行后验预测采样。在执行下一行代码后,idata_lb将被填充一个新的组,posterior_predictive,其中包含一个变量y_pred,表示租用自行车数量的后验预测分布。

代码 4.4

pm.sample_posterior_predictive(idata_lb, model=model_lb, extend_inferencedata=True)

图 4.5 中的黑线代表租赁自行车的均值。这与 图 4.4 中的情况相同。新增元素包括代表租赁自行车中心 50% 的深灰色带(分位数 0.25 和 0.75),以及代表中心 94% 的浅灰色带(分位数 0.03 和 0.97)。您可能注意到我们的模型预测了一个负数自行车数量,这是没有意义的。但仔细思考后,我们会发现这是预期的,因为在 model_lb 中我们使用了正态分布来描述似然。一个非常简陋的 修正 可以是将预测值剪切为低于 0 的值,但那样很丑陋。在接下来的部分,我们将看到我们可以轻松改进这个模型,以避免不合理的预测。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file103.png

图 4.5:自行车线性模型的后验预测图

4.3 泛化线性模型

我们一直在使用的线性模型是更一般模型的特例,即广义线性模型GLM)。GLM 是线性模型的泛化,允许我们使用不同的分布来描述似然。在高层次上,我们可以将贝叶斯 GLM 写成:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file104.jpg

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/phi.png 是任意分布;一些常见情况包括正态分布、学生 t 分布、伽马分布和负二项分布。θ 表示分布可能具有的任何 辅助 参数,例如正态分布中的 σ。我们还有 f,通常称为反向链接函数。当 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/phi.png 是正态分布时,f 是恒等函数。对于伽马分布和负二项分布等分布,f 通常是指数函数。为什么我们需要 f?因为线性模型通常位于实数线上,但 μ 参数(或其等价物)可能在不同的定义域上。例如,负二项分布的 μ 定义为正值,因此我们需要对 μ 进行变换。指数函数是这种变换的一个好选择。我们将在本书中探讨几种 GLM。在阅读本书时,一个很好的练习是创建一个表格,每次看到新的 GLM 时,添加一行说明 phithetaf 是什么,以及关于何时使用该 GLM 的一些注释。好的,让我们从我们第一个具体的 GLM 示例开始。

4.4 计数自行车

如何改进 model_lb 以更好地适应自行车数据?需要注意两点:租赁自行车数量是离散的,并且其下界为 0。这通常被称为计数数据,指的是通过计数某物得出的数据。计数数据有时使用连续分布(如正态分布)来建模,特别是当计数数量较大时。但通常使用离散分布更为合适。两种常见的选择是泊松分布和 NegativeBinomial 分布。主要的区别是,对于泊松分布,均值和方差是相同的,但如果这不成立或甚至大致不成立,那么 NegativeBinomial 可能是一个更好的选择,因为它允许均值和方差不同。如果不确定,可以同时拟合泊松分布和 NegativeBinomial 分布,看看哪个模型更好。我们将在 第五章 中进行这一操作。但目前,我们将使用 NegativeBinomial 模型。

代码 4.5

with pm.Model() as model_neg: 
    *α* = pm.Normal("*α*", mu=0, sigma=1) 
    *β* = pm.Normal("*β*", mu=0, sigma=10) 
    σ = pm.HalfNormal("σ", 10) 
    μ = pm.Deterministic("μ", pm.math.exp(*α* + *β* * bikes.temperature)) 
    y_pred = pm.NegativeBinomial("y_pred", mu=μ, alpha=σ, observed=bikes.rented) 
    idata_neg = pm.sample() 
    idata_neg.extend(pm.sample_posterior_predictive(idata_neg))

PyMC 模型与之前的模型非常相似,但有两个主要区别。首先,我们使用 pm.NegativeBinomial 代替 pm.Normal 作为似然函数。NegativeBinomial 分布有两个参数:均值 μ 和离散参数 α。NegativeBinomial 的方差为 μ + https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file105.jpg,因此 α 的值越大,方差越大。第二个区别是,μ 现在是 pm.math.exp(α + β * bikes.temperature),而不是简单的 α + β * bikes.temperature,正如我们之前解释的那样,这需要将实数线转换为正的区间。

model_neg 的后验预测分布显示在 图 4.6 中。后验预测分布与我们在使用线性模型时获得的分布非常相似(图 4.5)。主要的区别是,现在我们不再预测负数的租赁自行车数量!我们还可以看到,预测的方差随着均值的增加而增大。这是预期之中的,因为 NegativeBinomial 的方差为 μ + https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file106.jpg

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file107.png

图 4.6:自行车 NegativeBinomial 线性模型的后验预测图

图 4.7 显示了 model_lb 的后验预测检验(左侧)和 model_neg 的后验预测检验(右侧)。我们可以看到,当使用正态分布时,最大的偏差是模型预测出租赁自行车数量为负数,但即使在正值范围内,我们也能看到拟合效果不太好。另一方面,NegativeBinomial 模型似乎更适合,尽管它并不完美。看右尾:预测值的尾部比观测值重。但也注意到,这种非常高的需求的概率较低。因此,总的来说,我们可以重新表述为,NegativeBinomial 模型比正态分布模型更好。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file108.png

图 4.7:自行车线性模型的后验预测检验

4.5 稳健回归

我曾经运行过一个复杂的分子系统模拟。在每一步模拟中,我都需要进行线性回归拟合作为中间步骤。我有理论和经验上的理由认为,在给定 X 值的情况下,我的 Y 值是条件正态分布的,所以我决定使用简单的线性回归来解决。但有时,模拟会生成一些远高于或低于数据主群的 Y 值。这完全破坏了我的模拟,我不得不重新启动它。

通常,这些与数据主群非常不同的值被称为异常值。我的模拟失败的原因是这些异常值拉扯回归线远离数据主群,而当我将这个估计传递到模拟的下一步时,事情就停止了。我通过我们亲爱的朋友——学生 t 分布解决了这个问题,正如我们在第二章中看到的,学生 t 分布比正态分布有更重的尾部。这意味着异常值对回归线的影响较小。这就是稳健回归的一个例子。

为了举例说明学生 t 分布为线性回归带来的稳健性,我们将使用一个非常简单且有趣的数据集:Anscombe 四重奏中的第三组数据。如果你不知道 Anscombe 四重奏是什么,可以在维基百科查看( en.wikipedia.org/wiki/Anscombe%27s_quartet )。

在接下来的模型model_t中,我们使用了一个移位的指数分布来避免接近 0 的值。未移位的指数分布对接近 0 的值赋予过多权重。根据我的经验,这对没有异常值或异常值适中的数据来说是可以的,但对于有极端异常值(或包含少量集群点)的数据,如 Anscombe 的第三组数据,最好避免这种低值。请对这一点,以及其他先前的建议持谨慎态度。默认值是一个不错的起点,但没有必要死守它们。其他常见的先验包括 Gamma(2, 0.1)和 Gamma(mu=20, sigma=15),它们与 Exponential(1/30)相似,但更少有接近 0 的值:

代码 4.6

with pm.Model() as model_t: 
    *α* = pm.Normal("*α*", mu=ans.y.mean(), sigma=1) 
    *β* = pm.Normal("*β*", mu=0, sigma=1) 
    σ = pm.HalfNormal("σ", 5) 
    ν_ = pm.Exponential("ν_", 1 / 29) 
    ν = pm.Deterministic("ν", ν_ + 1) 
    μ = pm.Deterministic("μ", *α* + *β* * ans.x) 
    _ = pm.StudentT("y_pred", mu=μ, sigma=σ, nu=ν, observed=ans.y) 
    idata_t = pm.sample(2000)

图 4.8中,我们可以看到根据model_t的稳健拟合和根据 SciPy 的linregress(此函数执行最小二乘回归)的非稳健拟合。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file109.png

图 4.8:根据model_t的稳健回归

虽然非稳健拟合试图妥协并包含所有点,但稳健贝叶斯模型model_t自动丢弃一个点,并拟合一条通过所有剩余点更接近的直线。我知道这是一个非常特殊的数据集,但其信息与其他数据集的结论相同;由于学生 t 分布的尾部更重,它对远离数据主群的点赋予较小的权重。

图 4.9中,我们可以看到对于大部分数据,我们得到了一个非常好的匹配。同时,注意到我们的模型预测了远离大多数数据的值,向两侧扩展,而不仅仅是像观察到的数据那样在大部分数据上方。就我们目前的目的而言,这个模型表现得相当不错,不需要进一步修改。然而,注意到对于某些问题,我们可能希望避免这种情况。在这种情况下,我们可能需要回过头来修改模型,使用截断的学生 t 分布将y_pred的可能值限制为正值。这部分留给读者作为练习。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file110.png

图 4.9model_t的后验预测检验

4.6 逻辑回归

逻辑回归模型是线性回归模型的推广,我们可以在响应变量为二元时使用该模型。该模型使用逻辑函数作为逆链接函数。在我们继续讨论模型之前,让我们先熟悉一下这个函数:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file111.jpg

对我们而言,逻辑函数的关键特性是,无论其自变量z的值如何,结果总是一个位于[0-1]区间的数字。因此,我们可以将这个函数看作是将通过线性模型计算得出的值压缩成可以输入伯努利分布的值的一种便捷方式。由于其特有的 S 形状,这个逻辑函数也被称为 sigmoid 函数,正如我们从图 4.10中看到的那样。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file112.png

图 4.10:逻辑函数

4.6.1 逻辑模型

我们几乎具备了将一个简单的线性回归转换为一个简单的逻辑回归所需的所有元素。我们从只有两个类别的情况开始,例如,垃圾邮件/非垃圾邮件、安全/不安全、多云/晴天、健康/生病,或热狗/非热狗。首先,我们通过声明预测变量y只能取两个值,即 0 或 1 来对这些类别进行编码,即y ∈{0,*1}。

从这个角度描述,问题听起来非常像我们在前几章使用的掷硬币问题。我们可能记得我们使用了伯努利分布作为似然函数。与掷硬币问题的不同之处在于,现在θ不会从 beta 分布生成,而是通过一个线性模型来定义,使用逻辑函数作为逆链接函数。省略先验分布后,我们有:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file113.jpg

我们将对经典的鸢尾花数据集应用逻辑回归,该数据集包含来自三种密切相关物种的花卉测量数据:setosa、virginica 和 versicolor。这些测量数据包括花瓣长度、花瓣宽度、萼片长度和萼片宽度。如果你想知道,萼片是经过改良的叶子,通常与保护花朵在花蕾中的功能有关。

我们将从一个简单的案例开始。假设我们只有两个类别,setosa 和 versicolor,并且只有一个独立变量或特征,sepal_length。我们希望根据花萼长度预测一朵花是 setosa 的概率。

如同常见的做法,我们将使用数字01setosaversicolor类别进行编码。使用 pandas,我们可以执行以下操作:

代码 4.7

df = iris.query("species == ('setosa', 'versicolor')") 
y_0 = pd.Categorical(df["species"]).codes 
x_n = "sepal_length" 
x_0 = df[x_n].values 
x_c = x_0 - x_0.mean()

与其他线性模型一样,中心化数据有助于采样。现在我们已经将数据转换为正确的格式,接下来我们可以使用 PyMC 构建模型:

代码 4.8

with pm.Model() as model_lrs: 
    *α* = pm.Normal("*α*", mu=0, sigma=1) 
    *β* = pm.Normal("*β*", mu=0, sigma=5) 
    μ = *α* + x_c * *β* 
    *θ* = pm.Deterministic("*θ*", pm.math.sigmoid(μ)) 
    bd = pm.Deterministic("bd", -*α* / *β*) 
    yl = pm.Bernoulli("yl", p=*θ*, observed=y_0) 

    idata_lrs = pm.sample()

model_lrs有两个确定性变量:θbdθ是将逻辑函数应用于变量μ的结果。bd是边界决策值,我们使用这个值来区分类别。我们稍后会详细讨论这一点。另一个值得注意的地方是,我们并没有自己编写逻辑函数,而是使用了 PyMC 提供的pm.math.sigmoid函数。

图 4.11 显示了model_lrs的结果:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file114.png

图 4.11:逻辑回归,model_lrs的结果

图 4.11 显示了花萼长度与为 versicolor 的概率θ(如果需要,也可以是为 setosa 的概率,1 − θ)的关系。我们对二元响应添加了一些抖动(噪音),以避免数据点重叠。黑色的 S 形线表示θ的平均值。这条线可以解释为在已知花萼长度的情况下,一朵花为 versicolor 的概率。半透明的 S 形带表示 94%的 HDI。垂直线又代表什么呢?这将是下一节的主题。

4.6.2 使用逻辑回归进行分类

我的母亲做了一道美味的菜叫做 sopa seca,基本上是一道以意大利面为主的菜肴,字面意思是“干汤”。虽然听起来可能像是个误称,甚至是个矛盾修饰法,但当你了解它的做法时,这道菜的名字就完全合理了(你可以在本书的 GitHub 仓库中查看这个食谱:github.com/aloctavodia/BAP3)。类似的事情也发生在逻辑回归中,尽管它的名字如此,但通常被当作一种解决分类问题的方法。让我们看看这种二元性的来源。

回归问题是关于根据一个或多个输入变量的值来预测输出变量的连续值。我们已经见过许多回归的例子,包括逻辑回归。然而,逻辑回归通常是以分类的形式讨论的。分类涉及根据一些输入变量为输出变量分配离散值(代表一个类别,比如 versicolor),例如,根据花萼长度判断一朵花是 versicolor 还是 setosa。

那么,逻辑回归是回归方法还是分类方法呢?答案是,它是一种回归方法;我们回归的是属于某个类别的概率,但它也可以用于分类。我们需要的只是一个决策规则:例如,如果θ ≥ 0*.*5,则将样本归为versicolor类,否则归为setosa类。图 4.11中的垂直线是边界决策,它被定义为使得 versicolor 的概率等于 0.5 时独立变量的值。我们可以通过分析计算出这个值,它等于−https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file115.jpg。这个计算基于模型的定义:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file116.jpg

根据逻辑函数的定义,当α + β**x = 0 时,θ = 0*.*5。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file117.jpg

通过重新排列,我们发现使得θ = 0*.5 的x*值是−https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file118.jpg

因为我们对αβ的值存在不确定性,所以我们对于边界决策的值也存在不确定性。这种不确定性在图 4.11中以垂直(灰色)带的形式表示,范围从≈5*.3 到≈5.*6。如果我们根据花萼长度进行花卉的自动分类(或任何可以在此模型框架下描述的类似问题),我们可以将花萼长度小于 5.3 的花归为 setosa 类,将花萼长度大于 5.6 的花归为 versicolor 类。对于花萼长度在 5.3 到 5.6 之间的花,我们将对其类别感到不确定,因此可以随机分配它们的类别,或者使用其他信息做出决策,包括让人类检查这些花卉。

总结本节内容:

  • θ的值通常来说是P(Y = 1|X)。从这个角度来看,逻辑回归是真正的回归方法;关键细节是,我们正在回归一个数据点属于类别 1 的概率,前提是给定特征的线性组合。

  • 我们正在建模一个二元变量的均值,它是[0-1]区间中的一个数字。因此,如果我们想将逻辑回归用于分类,我们需要引入一个规则,将这个概率转换为二分类赋值。例如,如果P(Y = 1) > 0*.*5,我们将该观测值分配给类别 1,否则分配给类别 0。

  • 值 0.5 并没有什么特别之处,除了它是 0 和 1 之间的中间值。当我们可以接受将数据点错误分类到任一方向时,这个边界是可以解释的。但这并不总是如此,因为错误分类的成本不一定是对称的。例如,如果我们尝试预测一个病人是否患有某种疾病,我们可能希望使用一个边界来最小化假阴性(患病但我们预测他们没有)或假阳性(未患病但我们预测他们患病)的数量。我们将在下一节中更详细地讨论这个问题。

4.6.3 解释逻辑回归的系数

在解释逻辑回归的系数时,我们必须小心。与简单线性模型不同,解释并不是那么直接。使用逻辑逆链接函数引入了一个非线性因素,我们必须考虑到这一点。如果β为正,则增加x会使p(y = 1)增加一定量,但这个量不是x的线性函数。相反,依赖关系是x值的非线性函数,这意味着xp(y = 1)的影响取决于x的值。我们可以在图 4.11中直观地展示这一点。我们不再得到一条常数斜率的直线,而是得到一条 S 形的曲线,斜率随x的变化而变化。

一些代数可以帮助我们进一步理解p(y = 1)随着x的变化有多少变化。基本的逻辑斯蒂模型是:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file119.jpg

逻辑回归的逆函数是 logit 函数,公式为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file120.jpg

结合这两个表达式,我们得到:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file121.jpg

记住,在我们的模型中,θp(y = 1),所以我们可以将之前的表达式改写为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file122.jpg

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file123.jpg 这一量被称为p = 1 的赔率。如果我们把p = 1 看作是成功,那么成功的赔率就是成功的概率与失败的概率之比。例如,掷一个公平的骰子得到 2 的概率是https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file124.jpg,而得到 2 的赔率是https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file125.jpg = https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file126.jpg = 0*.*2. 换句话说,每五次不成功的事件中就有一次成功事件。赔率通常被赌博者使用,因为它们比原始概率提供了更直观的投注思考工具。图 4.12展示了概率、赔率和 log-odds 之间的关系。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file127.png

图 4.12:概率、赔率和 log-odds 的关系

解释逻辑回归

在逻辑回归中,β系数(斜率)表示x变量增加一个单位时,log-odds 单位的增加。

从概率到赔率的转换是单调变化,意味着随着概率的增加,赔率也会增加,反之亦然。虽然概率限制在[0*,1]区间内,但赔率位于 0,∞)区间内。对数是另一种单调变化,而对数赔率则位于(−∞,*∞)区间内。

4.7 变量方差

我们一直使用线性模式来建模分布的均值,并且在上一节中,我们用它来建模交互效应。在统计学中,当误差的方差在所有观察值中不恒定时,我们称线性回归模型呈现异方差性。在这种情况下,我们可能需要考虑将方差(或标准差)作为因变量的(线性)函数。

世界卫生组织及其他全球卫生机构收集新生儿和幼儿的数据,并设计生长曲线标准。这些图表是儿科工具包的重要组成部分,也是衡量人群整体健康状况的标准,能够帮助制定健康相关政策、规划干预措施并监测其效果。这样的数据示例包括新生儿/幼儿女孩的身高(体长)与年龄(月龄)之间的关系:

代码 4.9

data = pd.read_csv("data/babies.csv") 
data.plot.scatter("month", "length")

为了对这些数据进行建模,我们将引入三个我们之前未见过的元素:

  • σ现在是预测变量的线性函数。因此,我们新增了两个参数,γδ。这两个参数是均值线性模型中αβ的直接类比。

  • 均值的线性模型是一个函数![。这只是一个简单的技巧,用于将线性模型拟合到曲线上。

  • 我们定义了一个MutableData变量,x_shared。为什么要这么做,很快就会明了。

我们的完整模型是:

代码 4.10

with pm.Model() as model_vv: 
    x_shared = pm.MutableData("x_shared", data.month.values.astype(float)) 
    *α* = pm.Normal("*α*", sigma=10) 
    *β* = pm.Normal("*β*", sigma=10) 
    γ = pm.HalfNormal("γ", sigma=10) 
    δ = pm.HalfNormal("δ", sigma=10) 

    μ = pm.Deterministic("μ", 𝛼 + *β* * x_shared**0.5) 
    σ = pm.Deterministic("σ", γ + δ * x_shared) 

    y_pred = pm.Normal("y_pred", mu=μ, sigma=σ, observed=data.length) 

    idata_vv = pm.sample()

图 4.13的左面板中,我们可以看到由黑色曲线表示的μ的均值,两个半透明的灰色带表示一个和两个标准差。在右面板中,我们看到了随着身长变化的估计方差。正如你所见,方差随着身长的增加而增大,这是我们预期的结果。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file129.png

图 4.13:左面板为model_vv的后验拟合。右面板是随着身长变化的均值估计方差。

现在我们已经拟合了模型,可能想使用模型来了解某个特定女孩的身高与分布的比较。回答这个问题的一种方法是询问模型关于 0.5 个月大婴儿的length变量的分布。我们可以通过从后验预测分布中进行采样,条件是身高为 0.5 来回答这个问题。使用 PyMC,我们可以通过采样pm.sample_posterior_predictive得到答案;唯一的问题是,默认情况下,这个函数会返回值,这些值是已经观察到的x值,即用于拟合模型的值。获取未观察值的预测值的最简单方法是定义一个MutableData变量(在这个例子中是x_shared),然后在采样后验预测分布之前更新这个变量的值,如下代码块所示:

代码 4.11

with model_vv: 
    pm.set_data({"x_shared": [0.5]}) 
    ppc = pm.sample_posterior_predictive(idata_vv) 
    y_ppc = ppc.posterior_predictive["y_pred"].stack(sample=("chain", "draw"))

现在,我们可以绘制出 2 周大女孩的预期身长分布,并计算其他量,如该身长女孩的百分位(见图 4.14)。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file130.png

图 4.14:0.5 个月时的身高预期分布。阴影区域表示 32%的累计质量

4.8 层次线性回归

第三章中,我们学习了层次模型的基本概念,这是一个非常强大的概念,它允许我们建模复杂的数据结构。层次模型使我们能够处理组层次的推断以及组层次以上的估计。如我们所见,这可以通过包含超先验来实现。我们还展示了组之间可以通过使用共同的超先验来共享信息,这提供了收缩效应,有助于正则化估计。

我们可以将这些相同的概念应用到线性回归中,得到层次线性回归模型。在本节中,我们将通过两个例子来阐明这些概念在实际场景中的应用,第一个使用的是合成数据集,第二个使用的是pigs数据集。

对于第一个例子,我创建了八个相关的组,其中有一个组只有一个数据点。我们可以从图 4.15 中看到数据的表现。如果你想了解更多关于这些数据是如何生成的,请访问 GitHub 仓库 github.com/aloctavodia/BAP3

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file131.png

图 4.15:层次线性回归示例的合成数据

首先,我们将拟合一个非层次模型:

代码 4.12

coords = {"group": ["A", "B", "C", "D", "E", "F", "G", "H"]} 

with pm.Model(coords=coords) as unpooled_model: 
    *α* = pm.Normal("*α*", mu=0, sigma=10, dims="group") 
    *β* = pm.Normal("*β*", mu=0, sigma=10, dims="group") 
    σ = pm.HalfNormal("σ", 5) 
    _ = pm.Normal("y_pred", mu=*α*[idx] + *β*[idx] * x_m, sigma=σ, observed=y_m) 

    idata_up = pm.sample()

图 4.16 显示了参数αβ的后验估计值。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file132.png

图 4.16unpooled_modelαβ的后验分布

正如图 4.16所示,组H的估计值与其他组的估计值非常不同。这是预期中的情况,因为对于组H,我们只有一个数据点,也就是说我们没有足够的信息来拟合一条直线。我们至少需要两个数据点;否则,模型会过度参数化,这意味着我们有比数据所能确定的更多的参数。

为了克服这种情况,我们可以提供更多的信息;我们可以通过使用先验或向模型中添加更多结构来实现这一点。让我们通过构建一个分层模型来添加更多结构。

这是分层模型的 PyMC 模型:

代码 4.13

with pm.Model(coords=coords) as hierarchical_centered: 
    # hyperpriors 
    *α*= pm.Normal("*α*_μ", mu=y_m.mean(), sigma=1) 
    *α*= pm.HalfNormal("*α*_σ", 5) 
    *β*= pm.Normal("*β*_μ", mu=0, sigma=1) 
    *β*= pm.HalfNormal("*β*_σ", sigma=5) 

    # priors 
    *α* = pm.Normal("*α*", mu=*α*, sigma=*α*, dims="group") 
    *β* = pm.Normal("*β*", mu=*β*, sigma=*β*, dims="group") 
    σ = pm.HalfNormal("σ", 5) 
    _ = pm.Normal("y_pred", mu=*α*[idx] + *β*[idx] * x_m, sigma=σ, observed=y_m) 

    idata_cen = pm.sample()

如果你运行hierarchical_centered,你会看到 PyMC 显示一条信息,类似于调整后有 149 次发散。增加 target_accept 或重新参数化。 这条信息意味着 PyMC 生成的样本可能不可信。到目前为止,我们假设 PyMC 总是返回我们可以无问题使用的样本,但事实并非总是如此。在第十章中,我们将进一步讨论为什么会这样,并提供一些诊断方法,帮助你识别这些情况以及解决潜在问题的建议。在这一节中,我们也会解释什么是发散现象。现在,我们只想说,当使用分层线性模型时,我们通常会遇到很多发散现象。

解决这个问题的简单方法是增加target_accept,正如 PyMC 亲切地建议的那样。它是pm.sample()的一个参数,默认值为 0.8,最大值为 1。如果你看到有发散现象,可以将这个参数设置为 0.85、0.9,甚至更高值,这有助于解决问题。但如果你已经设置到 0.99,仍然有发散现象,那么这种简单的方法可能就不奏效了,你需要采取其他措施。这就是重新参数化。那么,什么是重新参数化呢?重新参数化就是以一种不同的方式编写模型,但这在数学上与原始模型是等价的:你并没有改变模型,只是以另一种方式表达它。许多模型,如果不是所有模型,都可以用其他方式来编写。有时,重新参数化可以提高采样器的效率或模型的可解释性。例如,通过重新参数化,你可以消除发散现象。接下来我们将在下一节中讲解如何做到这一点。

4.8.1 分层模型:集中式与非集中式

层次线性模型有两种常见的参数化方法,分别是中心化和非中心化。hierarchical_centered模型使用的是中心化方法。该参数化方法的特点是,我们直接为各个小组估计参数;例如,我们明确地估计每个小组的斜率。相反,对于非中心化参数化方法,我们为所有小组估计一个共同的斜率,然后为每个小组估计一个偏移量。需要注意的是,我们仍然是在为每个小组建模斜率,但相对于共同的斜率,所获得的信息是相同的,只是表达方式不同。因为模型胜过千言万语,我们来看看hierarchical_non_centered

代码 4.14

with pm.Model(coords=coords) as hierarchical_non_centered: 
    # hyperpriors 
    *α*= pm.Normal("*α*_μ", mu=y_m.mean(), sigma=1) 
    *α*= pm.HalfNormal("*α*_σ", 5) 
    *β*= pm.Normal("*β*_μ", mu=0, sigma=1) 
    *β*= pm.HalfNormal("*β*_σ", sigma=5) 

    # priors 
    *α* = pm.Normal("*α*", mu=*α*, sigma=*α*, dims="group") 

    *β*_offset = pm.Normal("*β*_offset", mu=0, sigma=1, dims="group") 
    *β* = pm.Deterministic("*β*", *β*+ *β*_offset * *β*, dims="group") 

    σ = pm.HalfNormal("σ", 5) 
    _ = pm.Normal("y_pred", mu=*α*[idx] + *β*[idx] * x_m, sigma=σ, observed=y_m) 

    idata_ncen = pm.sample(target_accept=0.85)

区别在于,对于模型hierarchical_centered,我们定义了βhttps://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/N.PNG(β[μ],β*[σ]),而对于hierarchical_non_centered,我们则定义了β* = β[μ] + β[offset] * β[σ]。非中心化参数化更高效:当我运行模型时,只有 2 个发散点,而不是之前的 148 个。为了去除这些剩余的发散点,我们可能仍然需要增加target_accept。对于这个特定的案例,将其从 0.8 改为 0.85 效果非常好。要完全理解为什么这种重新参数化有效,你需要理解后验分布的几何结构,但这超出了本节的范围。别担心,我们会在第十章讨论这个问题。

现在我们的样本没有发散点了,我们可以回去分析后验分布。图 4.17展示了hierarchical_modelαβ的估计值。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file133.png

图 4.17hierarchical_non_centered模型中αβ的后验分布

小组H的估计值仍然具有较高的不确定性。但结果看起来比图 4.16中的要更合理;原因在于小组之间共享了信息。因此,即使我们没有足够的信息将一条线拟合到一个单一的点上,小组H受到了其他小组的影响。实际上,所有小组都在相互影响。这就是层次模型的强大之处。

图 4.18展示了八个小组的拟合线。我们可以看到,尽管我们设法将一条直线拟合到一个单一的点上,乍一看这可能显得奇怪甚至可疑,但这只是层次模型结构的结果。每一条线都受到其他小组的影响,因此我们并非真正地将一条线拟合到一个单一的点,而是将一条已被其他小组数据所影响的线拟合到该点。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file134.png

图 4.18hierarchical_non_centered的拟合线

4.9 多元线性回归

到目前为止,我们一直在处理一个因变量和一个自变量。然而,拥有多个自变量并将它们纳入模型是很常见的。一些例子可能是:

  • 葡萄酒的感知质量(因变量)和酸度、密度、酒精含量、残糖量、硫酸盐含量(自变量)

  • 学生的平均成绩(因变量)和家庭收入、从家到学校的距离、母亲的教育水平(类别变量)

我们可以很容易地将简单线性回归模型扩展到处理多个自变量。我们称这种模型为多元线性回归,或者较少使用的名称是多变量线性回归(不要与多元回归线性回归混淆,后者是指我们有多个因变量的情况)。

在多元线性回归模型中,我们将因变量的均值建模如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file135.jpg

使用线性代数符号,我们可以写出更简短的版本:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file136.jpg

X是一个n × k大小的矩阵,包含自变量的值,β是一个k大小的向量,包含自变量的系数,n是观测值的数量。

如果你对线性代数有些生疏,可能需要查看维基百科关于两个向量的点积及其矩阵乘法推广的文章:en.wikipedia.org/wiki/Dot_product。基本上,你需要知道的是,我们只是在使用一种更简洁、方便的方式来书写我们的模型:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file137.jpg

使用简单线性回归模型,我们找到了一条直线,能够(希望)解释我们的数据。而在多元线性回归模型下,我们得到的是一个维度为k的超平面。因此,多元线性回归模型本质上与简单线性回归模型相同,唯一的区别在于,现在β是一个向量,X是一个矩阵。

看一下下面的例子

对于多元线性回归模型,让我们回到自行车数据集。我们将使用当天的温度和湿度来预测租赁的自行车数量:

代码 4.15

with pm.Model() as model_mlb: 
    *α* = pm.Normal("*α*", mu=0, sigma=1) 
    *β*0 = pm.Normal("*β*0", mu=0, sigma=10) 
    *β*1 = pm.Normal("*β*1", mu=0, sigma=10) 
    σ = pm.HalfNormal("σ", 10) 
    μ = pm.Deterministic("μ", pm.math.exp(*α* + *β*0 * bikes.temperature + 
                                              *β*1 * bikes.hour)) 
    _ = pm.NegativeBinomial("y_pred", mu=μ, alpha=σ, observed=bikes.rented) 

    idata_mlb = pm.sample()

请花一点时间对比model_mlb(有两个自变量:temperaturehour)和model_neg(只有一个自变量:temperature)。唯一的区别是现在我们有了两个β系数,每个自变量对应一个系数。模型的其余部分相同。注意,我们本来可以写作β= pm.Normal("β1", mu=0, sigma=10, shape=2),然后在定义μ时使用β1[0]β1[1]。我通常会这么做。

如你所见,编写多元回归模型与编写简单回归模型并没有太大不同。不过,解释结果可能更加具有挑战性。例如,temperature的系数现在是β[0],而hour的系数是β[1]。我们仍然可以将系数解释为因变量在自变量变化一个单位时的变化量。但现在我们必须小心地指定我们正在讨论的是哪个自变量。例如,我们可以说,温度增加一个单位时,租赁自行车的数量增加β[0]个单位,同时保持hour的值不变。或者我们可以说,小时数增加一个单位时,租赁自行车的数量增加β[1]个单位,同时保持temperature的值不变。此外,某一变量的系数值取决于我们在模型中包含了哪些其他变量。例如,temperature的系数将根据是否将hour变量纳入模型而有所变化。

图 4.19 显示了模型model_neg(仅有temperature)和模型model_mldtemperaturehour)的β系数。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file138.png

图 4.19model_negmodel_mlb的缩放β系数

我们可以看到,temperature的系数在两个模型中是不同的。这是因为temperature对租赁自行车数量的影响取决于一天中的小时数。更进一步,β系数的值已经通过其对应的独立变量的标准差进行了缩放,因此我们可以使它们具有可比性。我们可以看到,一旦在模型中加入了hourtemperature对租赁自行车数量的影响变得更小。这是因为hour的影响已经解释了之前由temperature解释的部分租赁自行车数量的变化。在极端情况下,新增一个变量可能会使系数变为 0,甚至改变符号。我们将在下一章中进一步讨论这个问题。

4.10 总结

在本章中,我们学习了线性回归,旨在建立因变量与自变量之间的关系模型。我们了解了如何使用 PyMC 拟合线性回归模型,并如何解释结果和制作可以与不同受众分享的图表。

我们的第一个例子是一个具有高斯响应的模型。但随后我们看到这只是一个假设,我们可以轻松地将其更改为处理非高斯响应,例如使用负二项回归模型处理计数数据,或使用逻辑回归模型处理二元数据。我们还看到,当这样做时,我们还需要设置一个逆链接函数,将线性预测器映射到响应变量。使用 Student’s t 分布作为似然函数对于处理异常值很有用。我们花费了大部分章节将均值建模为自变量的线性函数,但我们了解到我们也可以建模其他参数,如方差。当我们有异方差数据时,这非常有用。我们学会了如何应用部分汇聚的概念来创建层次线性回归模型。最后,我们简要讨论了多元线性回归模型。

PyMC 通过修改一两行代码,使得实现各种贝叶斯线性回归变得非常简单。在下一章中,我们将学习更多关于线性回归的内容,并且我们将学习 Bambi,这是一个构建在 PyMC 之上的工具,它使得构建和分析线性回归模型变得更加容易。

4.11 练习

  1. 使用 howell 数据集(可在github.com/aloctavodia/BAP3获取),创建一个体重(x)与身高(y)之间的线性模型。排除 18 岁以下的受试者。解释结果。

  2. 对于四个受试者,我们得到了体重(45.73, 65.8, 54.2, 32.59),但没有身高。使用前面的模型,预测每个受试者的身高,并给出他们的 50%和 94% HDI。提示:使用pm.MutableData

  3. 重复练习 1,这次包括 18 岁以下的受试者。解释结果。

  4. 已知许多物种的体重与身高不成比例,而是与体重的对数成比例。使用这一信息来拟合 howell 数据(包括所有年龄段的受试者)。

  5. 查看附带的代码model_t2(以及与之相关的数据)。尝试不同的ν先验,例如未移位的指数分布和伽玛分布先验(它们在代码中有注释)。绘制先验分布,以确保你理解它们。一种简单的方法是调用pm.sample_prior_predictive()函数,而不是pm.sample()。你也可以使用 PreliZ。

  6. 使用petal_length变量重新运行model_lrs,然后使用petal_width变量。结果有哪些主要区别?在每种情况下,94% HDI 的宽度是多大?

  7. 重复前面的练习,这次使用 Student’s t 分布作为一个弱信息先验。尝试不同的ν值。

  8. 选择一个你感兴趣的数据集,并使用简单线性回归模型进行分析。一定要利用 ArviZ 函数探索结果。如果你没有找到感兴趣的数据集,可以尝试在线搜索,例如,在 data.worldbank.orgwww.stat.ufl.edu/~winner/datasets.html 查找。

加入我们的社区 Discord 空间

加入我们的 Discord 社区,与志同道合的人们一起学习,并与超过 5000 名成员共同成长,链接地址:packt.link/bayesian

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file1.png

第五章

比较模型

地图不是它所代表的领土,但如果它是正确的,它具有与领土相似的结构。 – 阿尔弗雷德·科尔齐布斯基

模型应当作为近似值来设计,帮助我们理解一个特定问题或一类相关问题。模型并不是为了完美复制真实世界而设计的。因此,所有模型都在某种意义上是错误的,就像地图不是领土一样。但并非所有模型都是同样错误的;有些模型在描述某个特定问题时会比其他模型更好。

在前几章中,我们将注意力集中在推断问题上,也就是如何从数据中学习参数值。在本章中,我们将重点关注一个互补的问题:如何比较针对同一数据的两个或多个模型。正如我们将要学习的,这既是数据分析中的一个核心问题,也是一个棘手的问题。在本章中,我们将保持示例简单,以便专注于模型比较的技术细节。在接下来的章节中,我们将把在这里学到的知识应用于更复杂的例子。

在本章中,我们将探讨以下主题:

  • 过拟合与欠拟合

  • 信息准则

  • 交叉验证

  • 贝叶斯因子

5.1 后验预测检查

我们之前介绍并讨论了后验预测检查,作为评估模型如何解释用于拟合模型的数据的一种方法。这种测试的目的是不是确定模型是否错误;我们早就知道这一点了!这个过程的目标是理解我们如何捕捉数据。通过进行后验预测检查,我们旨在更好地理解模型的局限性。一旦我们理解了局限性,我们可以简单地承认它们,或者通过改进模型来尝试去除它们。预计模型无法重现问题的所有方面,这通常不是问题,因为模型是为了特定目的构建的。由于不同的模型通常捕捉数据的不同方面,我们可以通过后验预测检查来比较模型。

让我们看一个简单的例子。我们有一个包含两个变量xy的数据集。我们将使用线性模型来拟合这些数据:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file139.jpg

我们还将使用二次模型来拟合数据,即一个比线性模型多一个项的模型。对于这个额外的项,我们只需将x的平方加上一个β系数:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file140.jpg

我们可以像往常一样在 PyMC 中编写这些模型;参考以下代码块。与我们之前见过的所有模型唯一的不同之处在于,我们向pm.sample传递了idata_kwargs="log_likelihood": True这个参数。这个额外的步骤将把对数似然值存储在InferenceData对象中,我们稍后将使用这个信息:

代码 5.1

with pm.Model() as model_l: 
    *α* = pm.Normal("*α*", mu=0, sigma=1) 
    *β* = pm.Normal("*β*", mu=0, sigma=10) 
    σ = pm.HalfNormal("σ", 5) 

    μ = *α* + *β* * x_c[0] 

    y_pred = pm.Normal("y_pred", mu=μ, sigma=σ, observed=y_c) 

    idata_l = pm.sample(2000, idata_kwargs={"log_likelihood": True}) 
    idata_l.extend(pm.sample_posterior_predictive(idata_l)) 

with pm.Model() as model_q: 
    *α* = pm.Normal("*α*", mu=0, sigma=1) 
    *β* = pm.Normal("*β*", mu=0, sigma=10, shape=order) 
    σ = pm.HalfNormal("σ", 5) 

    μ = *α* + pm.math.dot(*β*, x_c) 

    y_pred = pm.Normal("y_pred", mu=μ, sigma=σ, observed=y_c) 

    idata_q = pm.sample(2000, idata_kwargs={"log_likelihood": True}) 
    idata_q.extend(pm.sample_posterior_predictive(idata_q))

图 5.1 显示了两个模型的均值拟合。从视觉上看,两个模型似乎都对数据提供了合理的拟合。至少对我来说,要看出哪个模型最好并不那么容易。你怎么看?

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file141.png

图 5.1model_l(线性模型)和model_q(二次模型)的均值拟合

为了获得更多的见解,我们可以进行后验预测检查。图 5.2 显示了观察值和预测值的数据的 KDE(核密度估计)。在这里,很容易看出model_q,即二次模型,更好地拟合了数据。我们还可以看到,特别是在分布的尾部,有很多不确定性。这是因为我们数据点的数量很少。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file142.png

图 5.2:使用az.plot_ppc函数创建的model_lmodel_q的后验预测检查

后验预测检查是一个非常灵活的概念。我们可以通过许多方式比较观察值和预测值。例如,我们可以比较分布的密度,而不是直接比较密度,我们还可以比较总结统计量。在图 5.3的顶部面板中,我们展示了两个模型的均值分布。x 轴上的点表示观察值。我们可以看到,两个模型都很好地捕捉了均值,二次模型的方差更小。两个模型都能够很好地捕捉均值并不令人惊讶,因为我们显式地建模了均值。在底部面板中,我们展示了四分位间距的分布。这一比较则偏向于线性模型。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file143.png

图 5.3:使用az.plot_bpv函数创建的model_lmodel_q的后验预测检查

通常,与模型明确建模的内容正交的统计量对于评估模型来说会更具信息量。如果有疑问,评估多个统计量可能会更方便。一个有用的问题是,问问自己你对数据的哪些方面感兴趣。

为了生成图 5.3,我们使用了az.plot_bpv ArviZ 函数。生成该图的完整代码片段如下:

代码 5.2

idatas = [idata_l, idata_q] 

def iqr(x, a=-1): 
    """interquartile range""" 
    return np.subtract(*np.percentile(x, [75, 25], axis=a)) 

for idata in idatas: 
    az.plot_bpv(idata, kind="t_stat", t_stat="mean", ax=axes[0]) 

for idata in idatas: 
    az.plot_bpv(idata, kind="t_stat", t_stat=iqr, ax=axes[1])

请注意,我们使用了kind="t_stat"参数来指示我们将使用总结统计量。我们可以传递一个字符串,比如t_stat="mean",表示我们想要使用均值作为总结统计量。或者,我们也可以使用用户定义的函数,比如t_stat=iqr

你可能已经注意到,图 5.3 也包含了带有 bpv 值的图例。bpv 代表贝叶斯 p 值。这是一种以数值方式总结模拟数据和观察数据之间比较的方法。为了获得它们,选择一个汇总统计量 T,比如均值、中位数、标准差,或者你认为值得比较的任何东西。然后,计算观察数据 T[obs] 和模拟数据 T[sim] 的 T。最后,我们问自己这个问题:“T[sim] 小于或等于 T[obs] 的概率是多少?”如果观察值与预测值一致,那么期望值将为 0.5。换句话说,一半的预测值将低于观察值,一半将高于观察值。这个量被称为 贝叶斯 p 值

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file144.jpg

还有另一种计算贝叶斯 p 值的方法。我们可以使用整个分布,而不是使用汇总统计量。在这种情况下,我们可以问自己这个问题:“对于每个观察值,预测一个更小或相等的值的概率是多少?”。如果模型被很好地校准,那么这些概率应该对于所有观察值是相同的。因为模型能够同样好地捕捉所有观察值,所以我们应该期待一个均匀分布。ArviZ 可以帮助我们进行计算;这次我们需要使用带有 kind="p_value" 参数(这是默认值)的 az.plot_bpv 函数。图 5.4 显示了此计算的结果。白色线条表示期望的均匀分布,灰色带状区域显示了由于样本的有限大小而预期的偏差。可以看出,这些模型非常相似。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file145.png

图 5.4:通过 az.plot_bpv 函数创建的 model_lmodel_q 的后验预测检验

那些不是 p 值

对于那些熟悉 p 值及其在频率统计中的使用的人来说,有一些需要澄清的地方。关于这些 p 值的“贝叶斯”之处在于,我们并没有使用抽样分布,而是使用了后验预测分布。此外,我们并没有进行原假设检验,也没有试图声明一个差异是“显著的”。我们只是试图量化模型如何解释数据。

后验预测检验提供了一个非常灵活的框架,用于评估和比较模型,无论是使用图形,还是使用诸如贝叶斯 p 值之类的数值摘要,或两者的组合。这个概念足够通用,允许分析人员发挥想象力,寻找不同的方式来探索模型的预测,并使用最适合其建模目标的方式。

在接下来的章节中,我们将探索其他比较模型的方法。这些新方法可以与后验预测检验结合使用。

5.2 简单性与准确性之间的平衡

在选择不同解释时,有一个原则被称为奥卡姆剃刀。一般来说,这个原则规定,当有两个或更多等效的解释时,最简单的解释是首选解释。简洁性的一种常见标准是模型中的参数数量。

这一启发式方法有很多理由支持。我们不会讨论它们的具体内容,我们只会将它们视为一种合理的指导原则。

我们在比较模型时通常需要考虑的另一个因素是它们的准确性,也就是模型对数据的拟合程度。根据这一标准,如果我们有两个(或更多)模型,其中一个比另一个更好地解释了数据,那么这个模型就是首选模型。

直观上,似乎在比较模型时,我们倾向于偏好那些最能拟合数据且简单的模型。但如果这两个原则导致我们选择不同的模型,我们该怎么做呢?或者更一般地说,是否有一种量化的方法来平衡这两者的贡献?简短的回答是有,而且实际上有不止一种方法可以做到。但首先,让我们看一个例子,以便获得直觉。

5.2.1 多个参数(可能)导致过拟合

图 5.5展示了三个参数数量逐渐增加的模型。第一个(零次)只是一个常数值:无论 X 的值是多少,模型总是预测相同的 Y 值。第二个模型(一阶)是一个线性模型,就像我们在第四章中看到的那样。最后一个模型(五次)是一个五次多项式模型。我们将在第六章中更深入地讨论多项式回归,但目前我们只需要知道,该模型的核心形式是 α + β[0]x + β[0]x² + β[0]x³ + β[0]x⁴ + β[0]x⁵。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file146.png

图 5.5:简单数据集的三种模型

图 5.5中,我们可以看到,随着模型复杂度(参数数量)的增加,模型的准确度也得到了提升,这一变化反映在决定系数 R² 中。这是一种衡量模型拟合度的方式(欲了解更多信息,请阅读en.wikipedia.org/wiki/Coefficient_of_determination)。事实上,我们可以看到五次多项式完美地拟合了数据,得到了 R² = 1。

为什么五次多项式可以在没有误差的情况下拟合数据呢?原因在于我们有与数据相同数量的参数,也就是六个。因此,模型实际上只是作为一种表达数据的替代方式。模型并没有学习数据的模式,而是在记忆数据!这可能是个问题。察觉这一点的更简单方式是,思考当面对新的、未见过的数据时,记住数据的模型会发生什么。你认为会发生什么?

好吧,性能预期会很差,就像某人只是背了考试问题,却发现问题在最后一刻被更改了!这种情况在图 5.6中有所展示;这里,我们添加了两个新数据点。也许我们有资金进行新的实验,或者我们的老板刚刚给我们发送了新的数据。我们可以看到,原本能够完美拟合数据的 5 阶模型,现在在R²的度量下比线性模型表现得更差。从这个简单的例子中,我们可以看出,最适合的模型不一定是理想的模型。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file147.png

图 5.6:三个简单数据集的模型,外加两个新点

大致来说,当一个模型非常好地拟合了用于学习该模型参数的数据集,但在拟合新数据集时表现很差,我们就遇到了过拟合问题。这是分析数据时非常常见的一个问题。思考过拟合的一个有用方法是将数据集视为包含两个部分:信号和噪声。信号是我们希望从数据中捕捉到的(或学习到的)内容。如果我们使用某个数据集,那是因为我们认为其中有信号,否则这将是徒劳的。另一方面,噪声是没有用的,它是测量误差、数据生成或捕获方式的局限性、数据损坏等因素的产物。当模型过于灵活(对于一个数据集)到能够学习噪声时,就会发生过拟合。这导致信号被隐藏起来。

这是奥卡姆剃刀的一个实际论据,同时也是一个警告:至少在原则上,总是可以创建一个复杂到足以解释数据集中所有细节的模型,甚至是最不相关的细节——就像博尔赫斯故事中的制图师,他们制作了一张与帝国一样庞大的地图,完美复制了每一个细节。

5.2.2 参数过少导致欠拟合

继续使用相同的例子,但从复杂度的另一极来看,我们得到了 0 阶模型。这个模型仅仅是一个伪装成线性模型的高斯分布。这个模型只能捕捉到Y的均值,因此完全不关心X的值。我们说这个模型对数据进行了欠拟合。欠拟合的模型也可能具有误导性,尤其是在我们没有意识到这一点时。

5.3 预测准确性的度量

“一切应当尽可能简化,但不能简化得过度”是一句常被归因于爱因斯坦的话。就像健康饮食一样,建模时我们也需要保持平衡。理想情况下,我们希望有一个既不过度拟合也不欠拟合数据的模型。我们希望在简洁性和拟合优度之间找到某种平衡。

在前面的例子中,相对容易看出,0 阶模型过于简单,而 5 阶模型过于复杂。为了得到一个通用的方法,使我们能够对模型进行排序,我们需要将这种简单性和准确性之间的平衡形式化。

让我们来看几个对我们有用的术语:

  • 样本内准确度:使用与拟合模型相同的数据来衡量的准确度。

  • 样本外准确度:使用未用于拟合模型的数据来衡量的准确度。

样本内准确度的平均值通常会大于样本外准确度。这就是为什么一般来说,使用样本内准确度来评估模型会让我们误以为模型比实际更好。因此,使用样本外准确度是一个不错的选择,可以避免我们自我欺骗。然而,留下数据意味着我们将拥有更少的数据来训练模型,而这通常是我们不能奢侈的。因此,这个问题在数据分析中是一个核心问题,已有多个提案来解决它。两种非常流行的方法是:

  • 信息准则:这是一个总称,用来指代各种表达式,这些表达式将样本外准确度近似为样本内准确度加上一个惩罚模型复杂性的项。

  • 交叉验证:这是一种基于将可用数据分为不同子集的方法,这些子集交替用于拟合和评估模型。

让我们在接下来的部分更详细地讨论这两种方法。

5.3.1 信息准则

信息准则是一组密切相关的工具,用于从拟合优度和模型复杂度的角度比较模型。换句话说,信息准则形式化了我们在本章开始时发展起来的直觉。这些量的具体推导方式与一个叫做信息理论的领域有关([MacKay, 2003]),这是一个有趣的领域,但我们将追求一个更直观的解释。

衡量模型拟合数据好坏的一种方法是计算数据与模型预测值之间的均方根误差:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file148.jpg

E(y[i]|θ)是给定估计参数后的预测值。值得注意的是,这本质上是观察值和预测数据之间平方差的平均值。将误差平方可以确保差异不会相互抵消,并且相对于其他方法(例如计算绝对值),它能更强调较大的误差。

均方根误差可能你已经很熟悉了。它是一个非常流行的度量——流行到我们可能从未花时间思考它。但如果我们仔细想想,就会发现,原则上它并没有什么特别之处,我们完全可以设计出其他类似的表达式。当我们采用概率方法时,正如本书中所做的那样,一个更一般(和自然的)的表达式如下:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file149.jpg

也就是说,我们为每个n个观测值计算似然。我们使用和而不是乘积,因为我们在使用对数。为什么我们说这是自然的呢?因为我们可以认为,在为模型选择似然时,我们实际上在选择如何惩罚数据与预测之间的偏差。事实上,当 p(y[i]|θ) 是高斯分布时,上述表达式将与均方根误差成比例。

现在,让我们将注意力转向对几个特定信息准则的详细探索。

赤池信息量准则

赤池信息量准则AIC)是一个著名且广泛使用的信息准则,尤其在贝叶斯领域外,定义为:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file150.jpg

k 是模型参数的数量,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/hat_theta.png[mle] 是 θ 的最大似然估计。

最大似然估计是非贝叶斯方法中的常见做法,并且通常,当使用平坦的先验时,它等同于贝叶斯最大后验估计MAP)。需要注意的是,https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/hat_theta.png[mle] 是一个点估计,而不是一个分布。

因子 −2 只是一个常数,我们可以省略它,但通常不这么做。从实际的角度来看,重要的是,第一个项考虑了模型与数据的拟合程度,而第二个项则惩罚了模型的复杂度。因此,如果两个模型对数据的拟合程度相同,AIC 表示我们应该选择参数最少的模型。

AIC 在非贝叶斯方法中表现良好,但在其他情况下存在问题。一个原因是它没有使用θ的后验分布,因此丢失了信息。此外,从贝叶斯的角度来看,AIC 假设先验是平坦的,因此 AIC 与像本书中使用的那些信息丰富或稍微信息丰富的先验不兼容。另外,当使用信息丰富的先验或层次结构等结构时,模型中的参数数量并不是衡量模型复杂度的好方法,因为这些方法会减少有效参数的数量,也称为正则化。我们将在后面回到正则化的这个概念。

广泛适用的信息准则

广泛应用的信息准则 (WAIC) 类似于 AIC 的贝叶斯版本。它也有两个项,一个衡量拟合的好坏,另一个对复杂模型进行惩罚。但 WAIC 使用完整的后验分布来估计这两个项。以下表达式假设后验分布作为大小为S的样本(通过 MCMC 方法获得)来表示:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file151.jpg

第一个项与赤池准则类似,只不过它是在所有观测值和所有后验样本上进行评估的。第二个项有点难以解释,除非涉及到一些技术细节。但它可以被理解为有效参数的数量。从实践角度来看,重要的是 WAIC 使用整个后验分布(而非点估计)来计算这两个项,因此 WAIC 可以应用于几乎任何贝叶斯模型。

其他信息准则

另一个广泛使用的信息准则是偏差信息准则 (DIC)。如果我们使用bayes-o-meter^™,DIC 比 AIC 更具贝叶斯特性,但不如 WAIC。虽然仍然很流行,但已通过理论和实证研究表明,WAIC 和主要的 LOO(见下一节)比 DIC 更有用。因此,我们不推荐使用它。

另一个广泛使用的准则是贝叶斯信息准则 (BIC)。就像逻辑回归和我母亲的干汤一样,这个名字可能会让人误解。BIC 作为一种修正 AIC 问题的方法提出,作者也为其提出了贝叶斯的理论依据。但 BIC 并不完全是贝叶斯的,因为像 AIC 一样,它假设使用平坦先验,并使用最大似然估计。

但更重要的是,BIC 与 AIC 和 WAIC 在目标上有所不同。AIC、WAIC 和 LOO(见下一节)试图反映哪个模型能更好地推广到其他数据(预测精度),而 BIC 试图识别哪个是正确的模型,因此更与贝叶斯因子相关。

5.3.2 交叉验证

交叉验证是一种简单且在大多数情况下有效的模型比较方法。我们将数据划分为 K 个切片,尽量使这些切片在大小上保持一致(有时也会在其他特征上保持一致,如类别数量)。然后,我们使用 K-1 个切片来训练模型,使用剩下的一个切片来进行测试。这个过程是通过系统地反复省略每次训练集中的一个不同切片,并使用该切片作为评估集来完成的。直到完成 K 轮拟合与评估,可以在图 5.7中看到。模型的准确度将是 K 轮中每一轮准确度的平均值。这被称为 K 折交叉验证。最后,在执行完交叉验证后,我们使用所有数据进行最后一次拟合,这个模型就可以用来进行预测或其他目的。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file152.png

图 5.7:K 折交叉验证

当 K 等于数据点的数量时,我们得到的就是留一法交叉验证LOOCV),即每次都将模型拟合到除了一个数据点之外的所有数据点上。

交叉验证是机器学习中的常规实践,我们所描述的仅仅是这种实践的最基本方面。这里展示的模式还有很多变体。有关更多信息,您可以阅读 James 等人 [2023]或 Raschka 等人 [2022]的研究。

交叉验证是一个非常简单且有用的概念,但对于某些模型或大规模数据集,交叉验证的计算成本可能超出了我们的能力。许多人尝试找到更简单的计算量,比如信息准则。在接下来的章节中,我们将讨论一种通过对所有数据进行单次拟合来近似交叉验证的方法。

近似交叉验证

交叉验证是一个不错的想法,但它可能代价高昂,特别是像留一法交叉验证这样的变体。幸运的是,利用单次拟合的数据可以近似交叉验证!这一方法称为“帕累托平滑重要性采样留一法交叉验证”。这个名字非常长,因此在实践中我们通常简称它为 LOO。从概念上来说,我们尝试计算的是:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file153.jpg

这是期望的对数逐点预测密度(ELPD)。我们加上下标LOO-CV,以明确表示我们使用留一法交叉验证来计算 ELPD。[−i]表示我们省略了观测值i

这个表达式与后验预测分布的表达式非常相似。不同之处在于,现在我们要计算的是从没有包含观察值 y[i] 的后验分布中计算的后验预测分布。我们采取的第一个逼近方法是通过从后验分布中抽样来避免显式计算积分。因此,我们可以写成:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file154.jpg

这里,求和是对 S 后验样本进行的。在本书中,我们经常使用 MCMC 样本。因此,这种逼近方法对你来说应该不陌生。接下来是比较棘手的部分。

可以使用重要性抽样来逼近 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/Formula_03.PNG。我们不会详细讨论这种统计方法,但我们将看到重要性抽样是一种通过重新加权从另一个分布中获得的值来逼近目标分布的方法。当我们不知道如何从目标分布中抽样,但知道如何从另一个分布中抽样时,这种方法很有用。重要性抽样在已知分布比目标分布更宽泛时效果最好。

在我们的例子中,一旦模型拟合完成,已知分布就是所有观察值的对数似然。我们希望逼近的是如果我们去掉一个观察值后的对数似然。为此,我们需要估计每个观察值在确定后验分布中的“重要性”(或权重)。一个观察值的“重要性”与如果该观察值被移除时该变量对后验分布的影响成正比。直观地说,一个相对不太可能的观察值比一个预期中的观察值更为重要(或权重更大)。幸运的是,一旦我们计算了后验分布,这些权重就容易计算。实际上,观察值 i 对于 s 后验样本的权重是:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file155.jpg

这个 w[s] 可能不可靠。主要的问题是,有时一些 w[s] 可能会非常大,以至于它们主导了我们的计算,使得计算不稳定。为了控制这些极端的权重,我们可以使用 Pareto 平滑方法。这个解决方案包括将这些权重中的一部分替换为通过拟合 Pareto 分布获得的权重。为什么使用 Pareto 分布?因为理论表明,权重应该遵循这种分布。

因此,对于每个观察值 y[i],使用最大的权重来估计 Pareto 分布,并且使用该分布来替换这些权重为“平滑”后的权重。这个过程为 ELPD 的估计提供了稳健性,并且提供了一种诊断逼近的方法,即,能够发出警告,提示 LOO 方法可能出现问题。为此,我们需要关注 k 的值,k 是 Pareto 分布的一个参数。k 值大于 0.7 表明我们可能有非常有影响力的观察值。

5.4 使用 ArviZ 计算预测准确性

幸运的是,使用 ArviZ 计算 WAIC 和 LOO 非常简单。我们只需要确保推理数据包含对数似然组。使用 PyMC 计算后验时,可以通过执行pm.sample(idata_kwargs="log_likelihood": True)来实现这一点。现在,让我们看看如何计算 LOO:

代码 5.3

az.loo(idata_l)
 Computed from 8000 posterior samples and 33 observations log-likelihood matrix.

         Estimate       SE elpd_loo   -14.31     2.67
p_loo        2.40        -
------

Pareto k diagnostic values:
                         Count   Pct. (-Inf, 0.5]   (good)       33  100.0%
 (0.5, 0.7]   (ok)          0    0.0%
   (0.7, 1]   (bad)         0    0.0%
   (1, Inf)   (very bad)    0    0.0%

az.loo的输出分为两部分。在第一部分,我们得到一个包含两行的表格。第一行是 ELPD(elpd_loo),第二行是有效参数数(p_loo)。在第二部分,我们有 Pareto k 诊断。这是 LOO 近似可靠性的一种度量。k 值大于 0.7 表示我们可能有非常有影响力的观测值。在这种情况下,我们有 33 个观测值,且它们都是好的,因此我们可以信任这个近似。

要计算 WAIC,你可以使用az.waic;输出将类似,只是我们不会得到 Pareto k 诊断或任何类似的诊断信息。这是 WAIC 的一个缺点:我们无法获得任何关于近似可靠性的信息。

如果我们为二次模型计算 LOO,将会得到类似的输出,但 ELPD 值会更高(大约-4),这表明二次模型更好。

ELPD 的数值本身并不太有用,必须与其他 ELPD 值进行比较来解读。这就是为什么 ArviZ 提供了两个辅助函数来方便这种比较。我们先来看az.compare

代码 5.4

cmp_df = az.compare({"model_l": idata_l, "model_q": idata_q})
rankelpd_loop_looelpd_diffweightsedsewarningscale
model_q0-4.62.68012.360Falselog
model_l1-14.32.429.743.0e-142.672.65Falselog

在行中,我们列出了比较的模型,在列中,我们有:

  • rank: 模型的顺序(从最好到最差)。

  • elpd_loo: ELPD 的点估计。

  • p_loo: 有效的参数数。

  • elpd_diff: 最佳模型与其他模型之间的 ELPD 差异。

  • weight: 每个模型的相对权重。如果我们想通过结合不同的模型来进行预测,而不是仅选择一个模型,这将是我们应该赋予每个模型的权重。在这种情况下,我们看到多项式模型占用了所有的权重。

  • se: ELPD 的标准误差。

  • dse: 差异的标准误差。

  • warning: 关于高 k 值的警告。

  • scale: 计算 ELPD 时所用的尺度。

ArviZ 提供的另一个辅助函数是az.compareplot。这个函数提供了与az.compare相似的信息,但以图形方式呈现。图 5.8展示了该函数的输出。请注意:

  • 空心圆代表 ELPD 值,黑线是标准误差。

  • 最高的 ELPD 值通过一条垂直的虚线灰线标示,以便与其他值进行比较。

  • 对于所有模型,除了最优模型,我们还会得到一个三角形,表示每个模型与最优模型之间 ELPD 差值的大小。灰色误差条表示点估计之间差异的标准误差。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file156.png

图 5.8az.compareplot(cmp_df)的输出

使用 LOO(或 WAIC)最简单的方法是选择一个单一模型。只需选择 ELPD 值最高的模型。如果我们遵循这个规则,我们将不得不接受二次模型是最优的。即使考虑到标准误差,我们也可以看到它们并没有重叠。这给了我们一些确定性,表明这些模型确实足够不同。如果标准误差重叠,我们应该给出更细致的答案。

5.5 模型平均

模型选择因其简便性而具有吸引力,但我们可能忽视了模型中关于不确定性的信息。这有点类似于计算完整的后验分布,然后仅保留后验均值;这可能导致我们对自己所知的内容过于自信。

另一种方法是选择一个单一模型,但报告并分析不同的模型,以及计算出的信息标准、它们的标准误差,或许还包括后验预测检查。将所有这些数字和测试放在我们的实际问题背景下非常重要,这样我们和我们的观众可以更好地了解模型可能的局限性和缺陷。对于学术界的人来说,这些元素可以用来在论文、报告、论文等的讨论部分中增加内容。在行业中,这对向利益相关者提供有关模型、预测和结论的优缺点非常有用。

另一种可能性是对模型进行平均。通过这种方式,我们保留了每个模型拟合优度的不确定性。然后我们使用每个模型的加权平均值来获得一个元模型(以及元预测)。ArviZ 提供了一个用于此任务的函数 az.weight_predictions,它接受推断数据对象列表和权重列表作为参数。权重可以使用 az.compare 函数计算。例如,如果我们想要对我们一直在使用的两个模型进行平均,可以按照以下方式操作:

代码 5.5

idata_w = az.weight_predictions(idatas, weights=[0.35, 0.65])

图 5.9 显示了该计算的结果。浅灰色虚线是两个模型的加权平均,黑色实线是线性模型,灰色实线是二次模型。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file157.png

图 5.9:线性和二次模型的加权平均

还有其他方式来对模型进行平均,例如显式地构建一个元模型,将所有感兴趣的模型作为特定情况包含在内。例如,二阶多项式包含了线性模型作为特定情况,或者分层模型是两种极端模型之间的连续版本,一个是分组模型,另一个是非分组模型。

5.6 贝叶斯因子

LOO、交叉验证和信息准则的替代方法是贝叶斯因子。贝叶斯因子通常作为频率派假设检验的贝叶斯替代方案出现在文献中。

比较k个模型的贝叶斯方法是计算每个模型的边际 似然 p(y|M[k]),即给定模型M[k]时观察数据Y的概率。边际似然是贝叶斯定理的归一化常数。我们可以通过写出贝叶斯定理并明确表明所有推断都依赖于模型来看到这一点。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file158.jpg

其中,y是数据,θ是参数,M[k]是从k个竞争模型中选择的模型。

如果我们的主要目标是从一组模型中选择一个模型,即选择最佳模型,那么我们可以选择具有最大p(y|M[k])值的那个模型。如果我们假设所有模型的先验概率相同,这样做是可以的。否则,我们必须计算:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file159.jpg

如果我们的主要目标是比较模型,以确定哪些模型更可能且可能的程度,那么可以使用贝叶斯因子来实现:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file160.jpg

这是两个模型的边际似然比。BF[01]的值越高,分子中的模型(本例中的M[0])就越。为了方便解读贝叶斯因子,并将数字转化为文字,Harold Jeffreys 提出了一个解读贝叶斯因子的尺度,其中包括支持强度的不同级别(见表 5.1)。

贝叶斯因子支持度
1–3轶事
3–10中等
10–30强烈
30–100非常强烈
*>*100极端

表 5.1:分子中的模型M[0]的支持度

请记住,如果你得到的数值低于 1,那么支持的是M[1],即分母中的模型。对于这些情况,也有表格可供参考,但请注意,你可以简单地取获得值的倒数。

记住,这些规则只是约定——充其量只是简单的指南。结果应始终放在我们的具体问题背景下,并附上足够的细节,以便他人可以自行评估他们是否同意我们的结论。在粒子物理学中确保某些东西的证明,或者在法庭上,或者在面对即将发生的自然灾难时决定是否进行撤离的证明是不一样的。

5.6.1 一些观察结果

现在我们简要讨论一些关于边际似然的关键事实:

  • 优点:包括奥卡姆剃刀。参数较多的模型比参数较少的模型有更大的惩罚。直观的理由是,参数越多,先验相对于似然的扩展就越大。一个容易看出这种情况的例子是嵌套模型:例如,二次多项式“包含”了一次多项式和零次多项式模型。

  • 坏处:对于许多问题,边际似然无法进行解析计算。此外,数值近似通常是一项艰巨的任务,在最好的情况下需要专门的方法,而在最坏的情况下,估算结果要么不切实际,要么不可靠。事实上,MCMC 方法的流行在于它们可以在不计算边际似然的情况下获得后验分布。

  • 缺点:边际似然对每个模型中的参数先验分布非常敏感p(θ[k]|M[k]))。

需要注意的是,优点缺点是相关的。使用边际似然来比较模型是一个好主意,因为它已经对复杂模型进行了惩罚(这有助于我们防止过拟合),同时,先验的变化将影响边际似然的计算。起初,这听起来有些傻;我们已经知道先验会影响计算(否则我们可以直接避免使用它们)。但我们这里谈论的是先验的变化,这些变化在后验中可能只有小的影响,但对边际似然值有很大影响。

贝叶斯因子的使用常常是贝叶斯学派的分水岭。其计算的难度以及对先验的敏感性是反对其使用的一些论点。另一个原因是,像 p 值和假设检验一样,贝叶斯因子更倾向于二分法思维,而不是“效应大小”的估计。换句话说,我们不是在问自己像这样的问题:癌症治疗可以延长多少年的生命?而是最终问,治疗与不治疗之间的差异是否是“统计显著的”。请注意,这个最后的问题在某些背景下是有用的。关键是,在许多其他情况下,这种问题并不是我们感兴趣的问题;我们只对我们被教导去回答的问题感兴趣。

5.6.2 贝叶斯因子的计算

正如我们已经提到的,边际似然(以及由此得出的贝叶斯因子)通常无法以闭式形式提供,除非是某些特定模型。因此,已经设计了许多数值方法来计算它。其中一些方法非常简单且天真(radfordneal.wordpress.com/2008/08/17/the-harmonic-mean-of-the-likelihood-worst-monte-carlo-method-ever),以至于在实践中效果非常差。

分析上

对于某些模型,如 BetaBinomial 模型,我们可以解析计算边际似然。如果我们将该模型表示为:

θBeta(α,β)
yBin(n = 1*,p* = θ)

然后,边际似然将是:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file161.jpg

B 是贝塔函数(不要与 Beta 分布混淆),n 是实验次数,h 是成功次数。

由于我们只关心在两种不同模型下的边际似然相对值(对于相同的数据),我们可以省略二项系数 https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file162.jpg,因此我们可以写成:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file163.jpg

这个表达式已在下一个代码块中编写,但有些变化。我们将使用 betaln 函数,它返回 beta 函数的自然对数,通常在统计学中,计算使用对数尺度。这可以减少在处理概率时的数值问题。

对数尺度。这减少了处理概率时的数值问题。

代码 5.6

from scipy.special import betaln 

def beta_binom(prior, y): 
    """ 
    Calculate the marginal probability, analytically, for a BetaBinomial model. 
    prior : tuple 
      alpha and beta parameters for the beta prior 
    y : array 
      array with "1" and "0" corresponding to success and failure respectively 
    """ 
    alpha, beta = prior 
    h = np.sum(y) 
    n = len(y) 
    p_y = np.exp(betaln(alpha + h, beta + n - h) - betaln(alpha, beta)) 

    return p_y

本示例的数据由 100 次抛硬币实验组成,正反面数量相同。我们将比较两种模型,一种具有均匀先验,另一种具有围绕 θ = 0*.*5 的 更集中 先验:

代码 5.7

y = np.repeat([1, 0], [50, 50])  # 50 heads, 50 tails 
priors = ((1, 1), (30, 30))  # uniform prior, peaked prior

图 5.10 显示了两个先验分布。均匀先验是黑线,尖峰先验是灰线。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file164.png

图 5.10:均匀先验和尖峰先验

现在,我们可以为每个模型计算边际似然和贝叶斯因子,结果为 5:

代码 5.8

BF = beta_binom(priors[1], y) / beta_binom(priors[0], y) 
print(round(BF))
 5

我们看到,具有先验 beta(30*,30) 的模型,其浓度更高,支持度大约是 beta(1,1) 模型的 5 倍。这是可以预期的,因为第一个模型的先验集中在 θ = 0.*5 附近,而数据 Y 中正反面数量相同,即它们与 θ 约为 0.5 的值一致。

序列蒙特卡洛

序列蒙特卡洛SMC)方法是一种通过一系列连续阶段的采样方法,逐步连接一个易于采样的分布和感兴趣的后验分布。在实践中,起始分布通常是先验分布。SMC 采样器的副产品是边际似然的估计。

代码 5.9

models = [] 
idatas = [] 
for alpha, beta in priors: 
    with pm.Model() as model: 
        a = pm.Beta("a", alpha, beta) 
        yl = pm.Bernoulli("yl", a, observed=y) 
        idata = pm.sample_smc(random_seed=42) 
        models.append(model) 
        idatas.append(idata) 

BF_smc = np.exp( 
    idatas[1].sample_stats["log_marginal_likelihood"].mean() 
    - idatas[0].sample_stats["log_marginal_likelihood"].mean() 
) 
print(np.round(BF_smc).item())
 5.0

从前面的代码块中可以看出,SMC 也给出了贝叶斯因子 5,结果与解析计算一致!使用 SMC 计算边际似然的优点是我们可以将其应用于更广泛的模型,因为我们不再需要知道一个封闭形式的表达式。我们为这种灵活性付出的代价是更高的计算成本。此外,值得注意的是,SMC(使用独立的 Metropolis-Hastings 核心,如 PyMC 中实现的)并不像 NUTS 那样高效。随着问题的维度增加,更精确的后验估计和边际似然需要更多的后验样本。

对数空间

在计算统计学中,我们通常在对数空间中进行计算。这有助于提供数值稳定性和计算效率等。举个例子,参考前面的代码块;你可以看到我们计算了差异(而不是除法),然后在返回结果之前取了指数。

Savage–Dickey 比率

对于上述例子,我们比较了两个 BetaBinomial 模型。我们本可以比较两个完全不同的模型,但有时我们希望比较一个零假设H_0(或零模型)与一个备择假设H_1。例如,为了回答“这个硬币是否有偏?”的问题,我们可以将值θ = 0*.5(表示没有偏差)与允许θ变化的模型的输出进行比较。对于这种比较,零模型嵌套在备择模型中,这意味着零假设是我们正在构建的模型中的一个特定值。在这些情况下,计算贝叶斯因子非常简单,不需要任何特殊的方法。我们只需要比较在备择模型下评估零值(例如θ* = 0*.*5)时的先验和后验。我们可以从以下表达式中看到这一点:

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file165.jpg

仅当H[0]是H[1]的特定情况时,这才成立(statproofbook.github.io/P/bf-sddr)。接下来,让我们用 PyMC 和 ArviZ 来实现。我们只需要为一个模型进行先验和后验的采样。让我们尝试使用 Uniform 先验的 BetaBinomial 模型:

代码 5.10

with pm.Model() as model_uni: 
    a = pm.Beta("a", 1, 1) 
    yl = pm.Bernoulli("yl", a, observed=y) 
    idata_uni = pm.sample(2000, random_seed=42) 
    idata_uni.extend(pm.sample_prior_predictive(8000)) 

az.plot_bf(idata_uni, var_name="a", ref_val=0.5)

结果显示在图 5.11中。我们可以看到一个先验的 KDE(黑色)和一个后验的 KDE(灰色)。两个黑点显示我们在值 0.5 处评估了这两个分布。我们可以看到支持零假设的贝叶斯因子BF_01约为 8,我们可以将其解释为适度证据支持零假设(见表 5.1)。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file166.png

图 5.11:具有 Uniform 先验的 BetaBinomial 模型的贝叶斯因子

正如我们已经讨论过的,贝叶斯因子衡量的是哪个模型更好地解释了数据。这包括先验,即使先验对后验计算的影响相对较小。我们也可以通过将第二个模型与零模型进行比较,看到这种先验效应。

如果我们的模型是一个带有 Beta 先验(30, 30)的 BetaBinomial 模型,那么BF_01会更低(在 Jeffrey 尺度上为轶事证据)。这是因为根据这个模型,θ = 0*.5 的值在先验中比 Uniform 先验更有可能,因此先验和后验将更加相似。也就是说,在收集数据后,看到后验集中在 0.5 附近并不令人惊讶*。不要只相信我,我们来计算一下:

代码 5.11

with pm.Model() as model_conc: 
    a = pm.Beta("a", 30, 30) 
    yl = pm.Bernoulli("yl", a, observed=y) 
    idata_conc = pm.sample(2000, random_seed=42) 
    idata_conc.extend(pm.sample_prior_predictive(8000)) 

az.plot_bf(idata_conc, var_name="a", ref_val=0.5)

图 5.12 显示了结果。我们可以看到BF_01大约是 1*.6,这可以解释为支持零假设的轶事证据*(参见之前讨论的 Jeffreys 尺度)。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file167.png

图 5.12:具有尖峰先验的 BetaBinomial 模型的贝叶斯因子

5.7 贝叶斯因子与推断

到目前为止,我们已经使用贝叶斯因子来判断哪个模型在解释数据方面似乎更好,我们发现其中一个模型的表现大约是另一个的 5 倍更好

那么,来自这些模型的后验怎么样呢?它们有多不同?表 5.2 总结了这两种后验:

均值标准差hdi_3%hdi_97%
均匀0.50.050.40.59
尖峰0.50.040.420.57

表 5.2:使用 ArviZ 汇总函数计算的具有均匀和尖峰先验的模型统计数据

我们可以说结果非常相似;我们得到了相同的θ均值,而model_0的后验略宽,这是预期的,因为该模型具有较宽的先验。我们还可以检查后验预测分布,看看它们有多相似(见图 5.13)。

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file168.png

图 5.13:具有均匀和尖峰先验的模型的后验预测分布

在这个例子中,观察到的数据更符合model_1,因为其先验集中在正确的θ值附近,而model_0则对所有可能的θ值赋予相同的概率。这种模型之间的差异由贝叶斯因子捕捉到。我们可以说,贝叶斯因子衡量的是哪个模型作为整体更适合解释数据。这包括了先验的细节,无论模型预测多么相似。在许多情况下,比较模型时,我们并不关心这些细节,反而更倾向于评估模型预测的相似度。对于这些情况,我们可以使用 LOO。

5.8 正则化先验

使用信息丰富和弱信息先验是一种在模型中引入偏差的方法,如果做得恰当,这实际上是非常有益的,因为偏差可以防止过拟合,从而帮助模型进行具有良好泛化能力的预测。这种向模型中加入偏差元素以减少泛化误差而不影响模型充分建模问题能力的想法被称为正则化。这种正则化通常表现为对模型中某些参数值进行惩罚的项,例如在回归模型中对过大的系数进行惩罚。限制参数值是减少模型能够表示的数据量的一种方式,从而减少模型捕捉噪声而非信号的可能性。

这个正则化思想非常强大且有用,以至于它在贝叶斯框架之外也被多次发现。对于回归模型,且不局限于贝叶斯统计,两个流行的正则化方法是岭回归和 lasso 回归。从贝叶斯角度来看,岭回归可以解释为对线性模型的β系数使用正态分布作为先验,其中的标准差较小,使得系数趋向于零。从这个意义上讲,我们在本书中的每个线性模型(除了本章中使用 SciPy 的例子)实际上都在做类似岭回归的事情!

另一方面,lasso 回归可以从贝叶斯角度解释为通过从具有拉普拉斯先验的模型中计算的后验的最大后验估计(MAP),其中β系数使用拉普拉斯分布作为先验。拉普拉斯分布看起来类似于高斯分布,但在零点处有一个尖锐的峰值。你也可以将其解释为两个背对背的指数分布(试试pz.Laplace(0, 1).plot_pdf())。与高斯分布相比,拉普拉斯分布的概率质量更集中在零附近。使用这种先验的思想是提供正则化和变量选择。具体来说,由于我们在零点处有一个峰值,我们期望先验分布能够引入稀疏性,也就是说,我们创建一个具有大量参数的模型,而先验会自动使大多数参数为零,仅保留对模型输出有贡献的相关变量。

不幸的是,与岭回归不同,这一思想并不能直接从频率学派转化到贝叶斯学派。然而,确实存在一些贝叶斯先验可以用来引入稀疏性并执行变量选择,比如马鞍先验。如果你想了解更多关于马鞍先验和其他收缩型先验的信息,你可以参考 Piironen 和 Vehtari 的文章[2017],它可以在arxiv.org/abs/1707.01694找到。在下一章中,我们将进一步讨论变量选择。最后补充一句:值得注意的是,岭回归和 lasso 回归的经典版本对应于单点估计,而贝叶斯版本则产生完整的后验分布。

5.9 总结

在本章中,我们已经看到如何使用后验预测检查、信息准则、近似交叉验证和贝叶斯因子来比较模型。

后验预测检查是一个通用概念和实践,它帮助我们理解模型在捕捉数据的不同方面方面的表现。我们可以仅用一个模型或多个模型进行后验预测检查,因此我们可以将其用作模型比较的方法。后验预测检查通常通过可视化方式进行,但像贝叶斯值这样的数值摘要也能提供帮助。

好的模型在复杂度和预测准确性之间有一个良好的平衡。我们通过使用经典的多项式回归例子来展示这一特点。我们讨论了两种方法来估计外样本准确性,而不需要将数据排除在外:交叉验证和信息准则。从实际角度来看,信息准则是一类理论方法,旨在平衡两个方面的贡献:衡量模型如何拟合数据的度量和对复杂模型的惩罚项。我们简要讨论了 AIC,因其历史重要性,然后讨论了 WAIC,它是贝叶斯模型的更好方法,因为它考虑了整个后验分布,并使用更复杂的方法来计算有效参数数目。

我们还讨论了交叉验证,看到我们可以使用 LOO 来近似留一法交叉验证。WAIC 和 LOO 通常会产生非常相似的结果,但 LOO 可能更为可靠。所以我们推荐使用它。WAIC 和 LOO 都可以用于模型选择和模型平均。与其选择一个最佳模型,模型平均是通过加权平均所有可用的模型来实现的。

模型选择、比较和模型平均的另一种方法是贝叶斯因子,它是两个模型的边际似然比。贝叶斯因子的计算可能非常具有挑战性。在本章中,我们展示了使用 PyMC 和 ArviZ 计算贝叶斯因子的两种方法:一种是使用被称为顺序蒙特卡洛的方法,另一种是使用萨维奇–迪基比率。第一种方法可以用于任何模型,只要顺序蒙特卡洛提供了良好的后验分布。由于 PyMC 中 SMC 的当前实现,对于高维模型或层次模型来说,这可能会具有挑战性。第二种方法仅在原假设模型是备择模型的特定情况时才可使用。除了计算上具有挑战性外,贝叶斯因子的使用也存在问题,因为它们对先验设定非常(过度)敏感。

我们已经展示了贝叶斯因子和 LOO/WAIC 是回答两个相关但不同问题的工具。前者侧重于识别正确的模型,而后者则侧重于识别具有较低泛化损失的模型,即做出最佳预测的模型。这些方法都不是没有问题的,但 WAIC,特别是 LOO,在实践中比其他方法更为稳健。

5.10 练习

  1. 本练习涉及正则化先验。在生成x_c, y_c数据的代码中(见github.com/aloctavodia/BAP3),将order=2更改为其他值,例如order=5。然后,拟合model_q并绘制结果曲线。重复此步骤,但这次使用sd=100β先验,而不是sd=1,并绘制结果曲线。这些曲线有何不同?也试试使用sd=np.array([10, 0.1, 0.1, 0.1, 0.1])

  2. 重复之前的练习,但将数据量增加到 500 个数据点。

  3. 拟合一个三次模型(阶数 3),计算 WAIC 和 LOO,绘制结果,并与线性和二次模型进行比较。

  4. 使用 pm.sample_posterior_predictive() 重新运行 PPC 示例,但这次绘制 y 的值,而不是均值的值。

  5. 阅读并运行 PyMC 文档中的后验预测示例,链接:www.pymc.io/projects/docs/en/stable/learn/core_notebooks/posterior_predictive.html。特别注意共享变量和 pm.MutableData 的使用。

  6. 返回生成图 5.5图 5.6 的代码,并修改它以获取新的六个数据点集。直观评估不同的多项式如何拟合这些新数据集。将结果与本书中的讨论联系起来。

  7. 阅读并运行 PyMC 文档中的模型平均示例,链接:www.pymc.io/projects/examples/en/latest/diagnostics_and_criticism/model_averaging.html

  8. 使用均匀先验 Beta(1, 1) 和如 Beta(0.5, 0.5) 等先验计算硬币问题的贝叶斯因子。设定 15 次正面和 30 枚硬币。将此结果与本书第一章的推断结果进行比较。

  9. 重复上一个示例,我们比较贝叶斯因子和信息准则,但这次减少样本量。

加入我们的社区 Discord 空间

加入我们的 Discord 社区,结识志同道合的人,并与超过 5000 名成员一起学习,链接:packt.link/bayesian

https://github.com/OpenDocCN/freelearn-ml-pt2-zh/raw/master/docs/bys-anal-py-3e/img/file1.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值