UCB-Data102-数据-推断与决策-全-

UCB Data102:数据、推断与决策(全)

原文:data102.org/ds-102-book/

译者:飞龙

协议:CC BY-NC-SA 4.0

数据、推理和决策

原文:data102.org/ds-102-book/

内容许可:Creative Commons Attribution Share Alike 4.0 International (CC-BY-SA-4.0) [内容许可:Creative Commons Attribution Share Alike 4.0 International (CC-BY-SA-4.0) 内容许可:Creative Commons Attribution Share Alike 4.0 International (CC-BY-SA-4.0)

这是一本为 Data 102 课程准备的草案教材,作为其他课程材料的补充。

本作品受CC BY-SA 4.0许可。

致谢

没有大量教师和学生的参与,他们开发了这些材料并在多个学期中不断改进,本课程及其内容将无法实现。

Jupyter Books 最初由 Sam Lau 和 Chris Holdgraf 创建,得到了加州大学伯克利分校数据科学教育计划和伯克利数据科学研究所的支持。

第一章:决策与假设检验

原文:data102.org/ds-102-book/content/chapters/01/intro/

本章内容正在完善中。

我们首先从二元决策开始探讨。

二进制决策与错误率

原文:data102.org/ds-102-book/content/chapters/01/decisions-and-errors

许多现实世界的数据科学问题最终归结为做出二元决策。例如:

  • 基于对某疾病在患者群体中的医学测试结果,哪些患者患有该疾病?

  • 在测试我网站新版本的结果中,这个版本是否会增加客户在我网站上停留更长时间和/或购买东西的机会?

  • 在我的数据集中,某些变量对之间是否存在“统计上显著的”关联?

尤其是在大多数现实世界的场景中,我们不仅仅想要分析一个单一的决策:我们想要评估在做出多个决策时会发生什么。为此,我们将关注我们可以使用的框架,这些框架帮助我们理解做出多个(通常是相关)决策的后果。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('tLPImMr5C-E')

在本章中,我们将关注做出二元(0/1)决策的情况。对于我们所做的每一个决策,我们假设存在某种现实或真相,它要么是 0 要么是 1。我们收集数据,并根据数据做出二元决策:决策是我们对现实的最佳猜测。我们将现实和决策分别缩写为\(R\)\(D\)。由于两者都是二元的,我们可以使用 2 x 2 表格来可视化所有可能的结果:

\(D=0\) \(D=1\)
\(R=0\)
\(R=1\)

我们所做的每一个决策都必须落在表格的四个单元格之一。在大多数现实世界的场景中,我们实际上不知道我们的决策最终落在哪一行:我们知道是否决定 \(D=0\)\(D=1\)(即它位于哪一列),但我们不知道现实的状态。

这种表格通常被称为混淆矩阵。关于是否将现实放在行或列中,并没有一个标准的约定,所以你可能会在其他地方看到它们被翻转。在这本书中,我们始终将现实放在行中,将决策放在列中。

我们为这四种情况分别使用了以下名称:真阳性(TP)、假阳性(FP)、真阴性(TN)和假阴性(FN)。

\(D=0\) \(D=1\)
\(R=0\) \(TN\) \(FP\)
\(R=1\) \(FN\) \(TP\)

每个名称的第一个词告诉我们决策是否正确(“真”)或错误(“假”)。第二个词告诉我们决策是 1(“阳性”)还是 0(“阴性”)。例如,“假阳性”是指决策 \(D=1\)(因为它是一个“阳性”),但实际上是错误的,所以 \(R=0\)(因为它是一个“假”的)。

理想情况下,我们的决策始终与现实相符,并落在表格的右上角(真阴性)或左下角(假阳性)的单元格中。在现实世界中,这并不总是可能的:我们可能会犯错误。根据问题,我们可能更倾向于避免一种错误而不是另一种。在本节的其余部分,我们将定义几种不同的方法来量化我们犯的错误,并了解它们之间的关系。

我们可以对单个决策进行很多丰富的分析,我们将在下一节关于假设检验的章节中探讨。现在,我们将从做出多个决策的情况开始,这将是本章的重点。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('6HksEjoX2RI')

做出多个决策

假设我们现在做出多个决策,并希望从整体上审视它们。这种情况可能以几种方式出现:

在单个数据集上测试多个假设:我们可能对从单个数据集中提出多个问题感兴趣。例如,假设我们在阿尔茨海默病研究中检查一个基因组学数据集。对于成千上万的遗传标记,我们希望了解该位置的突变是否与阿尔茨海默病的更高风险相关。

在多个数据集上测试一个假设:假设我们感兴趣的是研究使用他汀类药物与心脏病发作率之间是否存在关联。我们可以对许多研究进行荟萃分析,这些研究探讨了这个问题:每个研究都测试了相同的一个假设(即,服用他汀类药物是否与心脏病发作风险降低有关?),但研究对象不同。通过将研究视为一个集合来观察,我们可以尝试得出关于这种联系存在(或不存在)的更强结论。

修改 2x2 表格

当我们做出多个决策时,我们可能会有多个假阳性、真阴性等等。因此,我们将使用相同的 2x2 表格,但现在每个单元格将包含该条件发生的次数计数:

\(D=0\) \(D=1\)
\(R=0\) \(n_{00}\)n00​ \(n_{01}\)n01​
\(R=1\) \(n_{10}\)n10​ \(n_{11}\)n11​

例如,\(n_{01}\)n01​ 是现实为 0 而我们做出决策为 1 的次数。

有许多方法可以量化我们的决策与现实匹配得有多好(或有多坏)。在这本书中,我们将重点关注行级率,它量化我们在每一行中的表现(即,当现实为 0 和当现实为 1 时),以及列级率,它量化我们在每一列中的表现(即,当决策为 0 或决策为 1 时)。

行级率:量化当现实已知时我们的表现如何

假设我们想要评估在现实为 0 的情况下我们的决策过程有多好。在这种情况下,我们感兴趣的是上述 2x2 表格的上行。特别是,我们可以使用分数 \(\frac{n_{00}}{n_{00} + n_{01}}\)n00​+n01​n00​​ 来量化我们在这个情况下正确的频率。我们将这个行级率称为真阴性率(TNR):它量化在现实为 0 的情况下我们做出真阴性决策的频率。

我们还可以评估在现实为 1 的情况下的决策过程。在这种情况下,我们可以使用 \(\frac{n_{11}}{n_{10} + n_{11}}\)n10​+n11​n11​​ 来量化我们正确的频率。这被称为真阳性率(TPR):它衡量在现实为 1 的情况下我们做出真阳性决策的频率。

我们可以为上述两种情况定义相应的错误率,我们将称之为假阳性率(FPR)和假阴性率(FNR):

\(FPR = \frac{n_{01}}{n_{00} + n_{01}} \\ FNR = \frac{n_{10}}{n_{10} + n_{11}}\)FPR=n00​+n01​n01​​FNR=n10​+n11​n10​​(1)

我们可以用它们在 2x2 表中的对应行来可视化这些率:

row_wise_rates.png

作为例子,假设我们正在预测客户是否会购买产品。在这种情况下,0 对应于客户没有购买,1 对应于客户购买。以下是解释上述四个率的方法:

  • TNR:当客户没有购买产品时,我们(正确地)预测他们不会购买的频率是多少?

  • FPR:当客户没有购买产品时,我们(错误地)预测他们会购买的频率是多少?

  • TPR:当客户确实购买产品时,我们(正确地)预测他们会购买的频率是多少?

  • FNR:当客户确实购买产品时,我们(错误地)预测他们不会购买的频率是多少?

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('gmLZVBa9T3k')

列级率

行级率衡量我们在现实中的每个案例中的表现如何:换句话说,在表格的每一行中。我们还可以量化我们在做出特定决策时的表现。我们将重点关注错误发现比例(FDP),它量化了我们做出 1 的决策时出错频率:

fdp.png

我们还定义了错误遗漏比例(FOP),它量化了我们做出 0 的决策时出错频率:

\(FOP = \frac{n_{10}}{n_{00} + n_{10}}\)FOP=n00​+n10​n10​​(2)

对于这两个,我们可以定义它们的对立面(真正的遗漏比例和真正的发现比例),但很少用这些名称来称呼它们。

回到我们的产品购买预测示例,以下是解释列级率的方法:

  • FDP:当我们预测客户会购买产品时,他们最终没有购买产品的频率是多少?

  • FOP:当我们预测客户不会购买产品时,他们最终购买产品的频率是多少?

在整本书中,我们将主要关注 FDP 作为我们最感兴趣的列级率。

精确度

一个与之密切相关且广泛使用的列比率是精确度

\(\text{精确度} = \frac{n_{11}}{n_{01} + n_{11}} = 1 - FDP\)精确度=n01​+n11​n11​​=1−FDP(3)

精确度告诉我们,在我们做出决策为 1 的次数中,我们有多大的正确率。在产品购买预测的例子中,我们会这样解释:当我们预测客户会购买产品时,他们最终购买产品的频率是多少?

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('4FnHLfdHmJE')

示例:解释行和列的比率

假设我们正在测试大量患者的疾病。我们将使用 1 表示患者有疾病,使用 0 表示他们没有。这对应于标准的医疗术语,其中“阳性”测试表示我们认为某人患有疾病,而“阴性”测试表示我们认为他们没有疾病。

让我们为这个特定例子解释一些我们学到的定义。回想一下,对于这个例子,假阳性发生在测试结果为阳性(即测试表明患者有疾病),但实际上患者是健康的情况。

  • 假阳性率(FPR)是指测试结果为阳性的健康患者的百分比。它告诉我们:当我们测试健康患者时,测试有多大的可能是错误的?

  • 假阴性率(FNR)是指测试结果为阴性的患病患者的百分比。它告诉我们:当我们测试患病患者时,测试有多大的可能是错误的?

  • 假设发现比例(FDP)是指来自健康患者的阳性测试的百分比。它告诉我们:当测试结果为阳性时,它们有多大的可能是错误的?

  • 假遗漏比例(FOP)是指来自患病患者的阴性测试的百分比。它告诉我们:当测试结果为阴性时,它们有多大的可能是错误的?

直观地说,行错误率(FPR 和 FNR)描述了测试本身的特性:它们告诉我们测试在健康患者和患病患者身上的表现如何。作为一个具体例子,COVID-19 快速(抗原)测试对 Omicron BA.1 变种的估计 FPR 为 0.2%,FNR 为 38% (来源)。FPR 和 FNR 是测试技术和疾病本身的特性,无论测试是在病例激增期间还是低发期(病例较少)进行,只要测试技术和变种相同,这些特性都是相同的。

另一方面,列向的比率必须依赖于总体。作为一个具体的例子,让我们来考察 COVID-19 快速(抗原)检测的 FDP。在病毒在人群中较少见的低谷期,我们应该预期有相对较少的 COVID-19 患者,以及相对较多的健康患者。这意味着一些适度的百分比的正测试结果将是错误的(假阳性),因此我们的 FDR 可能会适度较高。但在病例激增期间,更多的人生病,我们应该预期更多的阳性测试结果来自 COVID-19 患者而不是健康人。因此,即使测试本身保持完全相同,我们的 FDP 也会更低。

接下来,我们将量化行向比率与列向比率之间关系的这种直觉。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('4V7bZ4z4jWg')

条件概率

上述定义的每个比率都是基于限制自己只看表格一半的想法。在行向比率的案例中,我们定义了基于只看 \(R=0\)(FPR 和 TNR)或 \(R=1\)(TPR 和 FNR)的量。在列向比率的案例中,我们只基于 \(D=1\) 定义 FDP。

这种基于有限结果集进行计算的想法应该很熟悉:这是一个你可能在考虑条件概率时已经见过的想法。考虑事件 \(A\)\(B\)。虽然 \(P(A)\) 描述了事件 \(A\) 发生的概率,\(P(A|B)\)P(A∣B) 描述了事件 \(A\) 在事件 \(B\) 发生条件下的条件概率:换句话说,如果我们限制自己只考虑事件 \(B\) 已经发生的情况,事件 \(A\) 发生的概率是多少?

从这个想法出发,我们可以将我们的行率解释为条件概率。由于每个都是在特定现实情况下定义的,我们知道它们是特定决策在现实条件下的概率:例如,真正阳性率(TPR)是 \(P(D=1|R=1)\)P(D=1∣R=1)。

类似地,假阳性率(FPR),它衡量我们的答案在现实为 0(\(D=1\))时错误的频率,可以解释为条件概率 \(P(D=1|R=0)\)P(D=1∣R=0)。

另一方面,列向率是在特定决策条件下定义的。假发现比例,它关注我们的阳性决策(\(D=1\))并衡量它们错误发生的频率(\(R=0\)),可以解释为条件概率 \(P(R=0|D=1)\)P(R=0∣D=1)。同样,假遗漏比例 (FOP) 是 \(P(R=1|D=0)\)P(R=1∣D=0).

总结如下:

错误率 条件概率
真阴性率 (TNR) \(P(D=0\mid R=0)\)P(D=0∣R=0)
假阳性率 (FPR) \(P(D=1\mid R=0)\)P(D=1∣R=0)
真阳性率 (TPR) \(P(D=1\mid R=1)\)P(D=1∣R=1)
假阴性率 (FNR) \(P(D=0\mid R=1)\)P(D=0∣R=1)
假发现比例 (FDP) \(P(R=0\mid D=1)\)P(R=0∣D=1)
假遗漏比例 (FOP) \(P(R=1\mid D=0)\)P(R=1∣D=0)

将这些行和列的比率解释为条件概率是有用的,因为它让我们能够将我们对条件概率的所有知识应用到更好地理解这些比率上。在之前的 COVID-19 检测示例中,我们探讨了行和列比率之间的直观联系,并引入了这种联系也取决于“\(R=1\)”有多普遍的想法。我们可以通过应用贝叶斯定理来形式化这种直觉。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('qqiPlQ1w_WE')

将行和列错误率相关联

考虑假发现比例(FDP),\(P(R=0|D=1)\)P(R=0∣D=1)和假阳性率(FPR),\(P(D=1|R=0)\)P(D=1∣R=0)。为了量化它们之间的联系,我们可以应用贝叶斯定理。正如我们将看到的,这种联系取决于现实世界中“1”有多普遍。我们将称这个量为患病率基础率,并使用符号\(\pi_1\)π1​:\(\pi_1 = P(R=1)\)π1​=P(R=1)。我们还将定义\(\pi_0 = P(R=0) = 1 - \pi_1\)π0​=P(R=0)=1−π1​。

现在,我们可以量化我们之前开始探索的这两个比率之间的关系。我们将使用贝叶斯定理和全概率定律:

\(\begin{align*} FDP &= P(R=0|D=1) \\ {\scriptsize{\text{(using Bayes’ rule)}}} &= \frac{P(D=1|R=0)P(R=0)}{P(D=1)} \\ {\scriptsize{\text{(Law of total probability)}}} &= \frac{P(D=1|R=0)P(R=0)}{P(D=1|R=0)P(R=0) + P(D=1|R=1)P(R=1)} \\ {\scriptsize{\text{(applying definitions)}}} &= \frac{FPR \cdot \pi_0}{FPR \cdot \pi_0 + TPR \cdot \pi_1} \\ {\scriptsize{\text{(dividing by the numerator)}}} &= \frac{1}{1 + \frac{TPR}{FPR} \frac{\pi_1}{\pi_0}} \end{align*}\)FDP(使用贝叶斯定理)(全概率公式)(应用定义)(分子除法) = P(R=0|D=1) = P(D=1) / P(D=1|R=0)P(R=0) = P(D=1|R=0)P(R=0) + P(D=1|R=1)P(R=1) / P(D=1|R=0)P(R=0) = FPR⋅π0 / (FPR⋅π0 + TPR⋅π1) = 1 + FPR / TPRπ0 / π1 / 1 (4)

如果你想复习这类计算,可以参考《数据 140 教科书》第 2.5 节

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('IFTY4ogb2_s')

示例:定量关联行率和列率

假设我们正在尝试构建一个预测是否下雨的模型。为了确保其性能良好,我们将在伯克利(那里夏天几乎不下雨)和佛罗里达州的迈阿密(那里夏天大约有一半的时间下雨)应用我们的算法。

你与一组数据科学家合作,其中一位提供了一个好的预测模型:在下雨的日子里,模型正确预测的次数是$97%$97%,而在不下雨的日子里,模型正确预测的次数是$96%$96%。你将这个模型展示给一位气象学家,他告诉你他们真正感兴趣的问题是:当模型预测下雨时,它有多准确?这对于人们信任模型很重要:如果模型对下雨天的预测大多不正确,那么人们就不会相信它。

我们首先将这些三个量解释到我们已建立起来的框架中。我们将用 1 表示下雨,用 0 表示没有下雨。

  • “在下雨的日子里,模型正确预测的次数是$97%\(97%”:这是一个行率,描述了当\)R=1$ 时我们做得有多好。换句话说,这告诉我们\(TPR=0.97\)(并且\(FNR = 1 - TPR = 0.03\)FNR=1−TPR=0.03)。

  • “在不下雨的日子里,模型正确率高达$96%\(96%”:这是行率,描述了当\)R=0$ 时我们的表现。换句话说,这告诉我们\(TNR=0.96\)(并且\(FPR = 1 - TNR = 0.04\)FPR=1−TNR=0.04)。

  • “当模型预测下雨时,它有多正确?”:这是一个列率,描述了当\(D=1\) 时我们的表现。换句话说,气象学家感兴趣的是最小化 FDP。

因此,为了满足气象学家的要求,我们需要从数据科学家提供的数据中计算出 FDP。这正是上述使用贝叶斯定理的计算所告诉我们的。我们将首先定义一个函数,用于从 TPR、FPR 和患病率中计算 FDP:

# Uses the formula derived above
def compute_fdp(tpr, fpr, prevalence):
    return 1 / (1 + (tpr/fpr) * (prevalence/(1-prevalence)))

miami_prevalence = 0.5
berkeley_prevalence = 0.01

当模型在迈阿密使用时,FDP 是多少?

miami_fdp = compute_fdp(tpr=0.97, fpr=0.04, prevalence=miami_prevalence)
miami_fdp

0.039603960396039604

这相当小:在迈阿密,当我们预测下雨时,我们可以期望大约\(96\%\)的时间是正确的。那么在伯克利呢?

berkeley_fdp = compute_fdp(tpr=0.97, fpr=0.04, prevalence=berkeley_prevalence)
berkeley_fdp

0.8032454361054766

这告诉我们,在伯克利夏天,当我们用这个模型预测下雨时,我们只能期望大约\(20\%\)的时间是正确的。尽管我们的模型在每个案例中都有超过\(95\%\)的准确性,但伯克利雨的患病率很低意味着我们很少下雨。这意味着我们有大量机会出现假阳性(在干燥的日子里),而真正阳性(在下雨的日子里)的机会却很少。因此,我们的假发现比例相当高。

我们也可以通过分析上述方程的渐近行为来看到这一点:

\(FDP = \frac{1}{1 + \frac{TPR}{FPR} \frac{\pi_1}{\pi_0}}\)FDP=1+FPRTPR​π0​π1​​1​(5)

  • 随着患病率\(\pi_1\)​变得非常小(接近 0),分数\(\pi_1/\pi_0\)​趋近于 0,FDP 趋近于$1/(1+0) = 1$1/(1+0)=1.

  • 随着患病率\(\pi_1\)​变得非常大(接近 1),分数\(\pi_1/\pi_0\)​趋近于\(\infty\),FDP 趋近于 0。

同样,我们可以看到,真正阳性率(TPR)的较大值和假阳性率(FPR)的较小值会导致较低的假发现比例(FDP)。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('aVwtgw0tQHI')

不同的错误类型:哪一个更糟糕?

假阳性和假阴性都是错误。通常,我们希望犯的错误尽可能少,但正如我们稍后将会看到的,我们经常陷入必须在这两种错误之间权衡的情况,即更多的假阳性和更少的假阴性,或者反之。在这种情况下,了解哪一个更可取(或“更不好”)是很重要的。

例如:假设 Spotify 正在尝试预测哪些客户将续订他们的订阅(1 表示续订,0 表示不续订)。对于预测不会续订的客户,他们计划发送一张 1 美元的优惠券,用于下个月的订阅。在这种情况下:

  • 假阳性对应于预测客户将续订(因此不发送优惠券),但随后客户没有续订(Spotify 失去了一个每月支付 12 美元的客户)。

  • 假阴性意味着预测客户不会续订(因此向他们发送优惠券),但客户实际上还是会续订(因此优惠券变得不必要,导致 Spotify 损失 1 美元)。

在这个例子中,我们可能会认为假阳性比假阴性更糟糕:假阴性意味着 Spotify 会浪费 1 美元,但假阳性意味着他们将会失去一个付费客户,而这个客户他们本可以用优惠券留住。

参考文献

  1. Murakami, M., Sato, H., Irie, T., Kamo, M., Naito, W., Yasutaka, T., & Imoto, S. (2022). COVID-19 快速抗原检测在奥密克戎变异株爆发期间的反应灵敏度10.1101/2022.06.13.22276325

假设检验

原文:data102.org/ds-102-book/content/chapters/01/hypothesis-testing

笔记本单元

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats

%matplotlib inline

sns.set()  # This helps make our plots look nicer

# These make our figures bigger
plt.rcParams['figure.figsize'] = (6, 4.5)
plt.rcParams['figure.dpi'] = 100

你可能发现回顾数据 8 教科书的第十一章测试假设和第十二章比较两个样本会有所帮助,这两章涵盖了假设检验的基础知识。

假设检验是一种特定的二元决策问题。尽管近年来零假设统计检验(NHST)框架受到了批评,但它仍然为从数据中做出决策提供了一个有用的框架。我们将在稍后探讨这些批评,但首先,这里有一个关于这个过程如何工作的快速复习:

  1. 确定你想要测试的观点,并决定:

    • 零假设:一个可以模拟数据或通过分析计算数据分布的随机模型

    • 备择假设:问题的观点

    • 检验统计量:从你的数据中计算出的量,帮助你在这两个假设之间做出决定(在这本书中,我们将始终使用惯例,即检验统计量的较大值应有利于备择假设,不失一般性)。

  2. 计算你数据上的检验统计量值。

  3. 通过模拟或分析计算零假设下的检验统计量分布。

  4. 计算 p 值:这是在零假设为真的情况下,获得一个等于或大于观察值的测试统计量的概率。

  5. 将该 p 值与某个阈值进行比较:如果 p 值小于阈值,则我们的测试统计量在零假设下非常不可能,因此我们的数据支持备择假设。如果 p 值较大,则数据支持零假设。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('g8NepbdUOBU')

假设检验与二元决策

我们将假设检验框架化为一个二元决策问题,其中我们在零假设和备择假设之间做出选择,并假设其中一个是真实的(注意,这种框架是有缺陷的;下面将进行更多说明)。在这种情况下,现实对应于哪个假设实际上是真实的,我们根据 p 值做出决策

为了将 p 值转换为二元决策,我们必须决定一些阈值:我们将在本章的剩余部分分析选择此阈值(或阈值)的不同方法,以及每个方法中编码的假设。

以下是关于惯例和词汇的一些重要说明:

  • 按照惯例,我们将始终将零假设定义为 0,将备择假设定义为 1。

  • 我们将以下术语互换使用(换句话说,它们都意味着同一件事):

    • “拒绝零假设”

    • “做出发现”

    • “做出 1 的决策” (\(D = 1\))

假设检验二元视角的局限性

在许多情况下,零假设和备择假设不一定是不相容的!当我们做出发现,或者等价地,拒绝零假设时,这只意味着如果我们假设零假设为真,我们观察到的数据是不太可能的。在许多情况下,这并不一定意味着备择假设是真实的!拒绝零假设通常只是向我们感兴趣的其他备择假设迈出的第一步。

例如,考虑一个血压药物试验,研究人员想要证明该药物可以降低血压。他们进行随机实验来测量药物的效果。他们精确地陈述他们的零假设,即药物对血压没有影响,为“在服用药物的群体和未服用药物的群体之间,血压变化分布没有差异”。由于他们感兴趣的是显示降低(而不仅仅是变化),他们将备择假设,即药物降低血压,陈述为“服用药物的群体血压变化分布低于未服用药物的群体”。

通常情况下,在这种情况下,考虑以下四个陈述:

  1. 零假设:治疗/对照组的结果遵循相同的分布

  2. 零假设的逻辑补:治疗/对照组的结果遵循相同的分布(零假设的直接逻辑逆)

  3. 备择假设:如果接受治疗,结果将遵循比未接受治疗时更低均值的分布

  4. 研究人员实际上想要展示的是:药物以临床上有意义和有益的方式降低血压

让我们可视化可能的结果空间,以帮助我们理解这四个陈述之间的关系:

nhst_outcomes_diagram.png

考虑我们拒绝零假设的情况。在不知道任何其他信息的情况下,如果我们想要做出二元决策,我们只能决定陈述 2 是正确的(即,真相位于右侧整个蓝色区域的某个地方)。但在实践中,我们希望在陈述 1(左侧橙色区域)和陈述 4(最小的深蓝色椭圆)之间做出二元决策:换句话说,我们想要表明要么没有效果,要么在我们感兴趣的方向上有有意义的效果。为了做到这一点,我们必须排除所有其他可能性。

我们可以通过明智的统计选择排除一些可能性。例如,研究人员决定进行单尾检验(即,使用备择假设“结果较低”)而不是双尾检验(即,使用备择假设“结果不同”),并选择反映这一点的检验统计量(例如,治疗/对照组均值之间的差异,而不是绝对差异)。这确保了我们的二元决策是在陈述 1,零假设(左侧的橙色区域)和陈述 3,他们选择的备择假设(右侧较大的蓝色椭圆)之间。

为了使我们的二元决策有意义,我们需要排除陈述 4(小蓝色椭圆)之外的可能性。为了做到这一点,我们必须确保良好的实验设计良好的统计实践。这包括避免混杂变量,确保我们的效应量在现实世界中是有意义的,避免诸如 p-hacking 和多重检验等做法(下一节将详细介绍这些),等等。

练习:对于以下每种可能的结果,确定它们在上述图中的位置。

  1. 药物持续降低血压,但降低的量在医学上不具意义(即,降低对体内任何其他生物过程或任何其他临床结果都没有有意义的影响)。

  2. 药物升高血压。

  3. 药物降低血压的量对某些人来说可能危险。

  4. 该药物最初是为了降低胆固醇而设计的,但在对胆固醇进行的昂贵且不成功的试验之后,制药公司的研究人员决定考虑其他十个因素,并发现治疗组的血压恰好较低。然而,这个发现是偶然的。

从这些例子中,我们可以看到进行假设检验并不是一个决定性的陈述:相反,它应该被视为更大过程中的一个步骤。

\(p\) 值与假阳性率连接

你可能觉得回顾数据 140 教科书中的第十五章(http://prob140.org/textbook/content/Chapter_15/01_Density_and_CDF.html)会有所帮助,该章涵盖了连续随机变量。

我们将通过几个练习来帮助我们理解 \(p\) 值与上一节中描述的错误率之间的关系。在这个过程中,我们将练习推理概率,并了解关于零假设下 \(p\) 值分布的一些重要事实。

练习 1:假设零假设是正确的。在这种情况下,获得一个小于 0.05 的 \(p\) 值的概率是多少?

解答:为了回答这个问题,我们将查看零假设下的测试统计量的分布,并使用我们所知道的关于概率的知识。如果零假设是正确的,那么测试统计量来自某个我们可以模拟或解析计算的分布。我们将如下直观地表示它。请注意,即使分布的形状非常不同,我们即将得出的所有结论都是真实的:我们只是在便于可视化。

f, ax = plt.subplots(1, 1, figsize=(5, 2))
x = np.linspace(-3.5, 3.5, 1000)
y = stats.norm.pdf(x)
ax.axis([-3.5, 3.5, -0.05, 0.41])
ax.axis('off')
ax.plot(x, y);

<Figure size 500x200 with 1 Axes>

获得一个小于 0.05 的 \(p\) 值意味着什么?这发生在我们的测试统计量足够大,根据零分布不太可能的情况下。具体来说,让我们选择一个特定的测试统计量 \(a\) 的值,选择这样一个值,使得曲线右侧的面积是 0.05:

f, ax = plt.subplots(1, 1, figsize=(5, 2))
x = np.linspace(-3.5, 3.5, 1000)
y = stats.norm.pdf(x)

a = stats.norm.isf(0.05)

ax.plot(x, y);
ax.plot([a, a], [0, stats.norm.pdf(a)], 'k')
x_gt_a = x[x > a]
ax.fill_between(x_gt_a, 0, stats.norm.pdf(x_gt_a), color='orange');
ax.annotate('$a$', [a - 0.12, -0.04])
ax.axis([-3.5, 3.5, -0.05, 0.41])
ax.axis('off');

<Figure size 500x200 with 1 Axes>

如果我们的测试统计量是 \(a\),那么 \(p\) 值就是得到大于或等于 \(a\) 的值的概率。这个概率是曲线右侧 \(a\) 下的面积。根据构造,这个面积是 0.05(换句话说,我们设置了事情,并选择了 \(a\) 以使其面积为 0.05)。

因此,一个等于 \(a\) 的测试统计量会导致一个 \(p\) 值为 0.05。我们还知道,任何大于 \(a\) 的测试统计量都会导致一个小于 0.05 的 \(p\) 值(因为面积会更小)。将所有这些放在一起,我们可以得出结论,任何大于或等于 \(a\) 的测试统计量都会导致一个小于或等于 0.05 的 \(p\) 值。

现在,如果零假设为真,那么获得一个大于或等于 \(a\) 的测试统计量的概率是多少?答案是零分布右侧 \(a\) 下的面积。根据构造,这个面积是 0.05。因此,获得一个小于或等于 \(p\) 值 0.05 的概率仅仅是 0.05!

总结我们所做的工作:

  1. 选择一个测试统计量值 \(a\),使得零分布右侧 \(a\) 下的面积是 0.05

  2. 确定获得“小于或等于 0.05 的 p 值”与“获得大于或等于\(a\) 的测试统计量”是等价的。

  3. 计算得出,如果零假设为真,获得大于或等于\(a\) 的测试统计量的概率是 0.05。

  4. 通过结合 2 和 3,发现如果零假设为真,获得小于或等于 0.05 的 p 值的概率是 0.05。

练习 3:假设零假设为真。在这种情况下,获得小于\(\gamma\)的 p 值的概率是多少(假设\(0 < \gamma \leq 1\))?注意这是对上一个问题的推广,使用\(\gamma\)代替特定的值 0.05

解答:在我们对上一个练习的解答过程中,我们使用的 0.05 这个值并没有什么特殊之处。即使我们选择 0 到 1 之间的任何其他阈值,我们的所有结论仍然有效。因此,我们可以安全地得出结论,这个概率是\(\gamma\)

练习 4:假设我们的\(p\) 值阈值为\(\gamma\)。这个测试的假阳性率是多少?

解答:我们知道以下事情:

  • 假阳性率是\(P(D=1 | R=0)\)

  • 在假设检验中,这是在零假设为真的情况下拒绝零假设的概率。

  • 当我们的\(p\) 值低于阈值时,我们拒绝零假设。

  • 从上一个练习开始:如果零假设为真,那么获得小于或等于 \(\gamma\) 的 p 值的概率是 \(\gamma\).

将这些事实结合起来,假阳性率是 \(\gamma\):换句话说,我们用于测试的 p 值阈值是该测试的假阳性率

练习 5:如果零假设为真,p 值的分布是什么?提示:答案是众所周知的分布。

解答:设 \(p\) 为 p 值(这是一个随机变量)。

在练习 3 中,我们证明了如果零假设为真,\(P(p \leq \gamma) = \gamma\)。这正是随机变量 \(p\) 的累积分布函数!换句话说,\(F_p(p) = p\),只要 \(0 \leq p \leq 1\)。这正是均匀分布的累积分布函数!因此,我们可以得出结论,如果零假设为真,p 值具有均匀分布。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('H0fXEIwFBNE')

加载中...

零假设分布下 p 值均匀性的证明(可选)

仅对本小节,我们将使用更精确的符号:随机变量将用大写字母表示,它们可以取的值将用小写字母表示,密度函数和累积分布函数将用相应的随机变量下标表示。关于这种符号的复习,请参阅数据 140 教科书。

此外,请注意随机变量符号 \(P\) 和概率符号 \(\mathbb{P}\) 之间的区别。

\(T\) 为表示我们的检验统计量的连续随机变量,并设 \(F_T\)FT​ 为在零假设下的 \(T\) 的累积分布函数(CDF):换句话说,\(F_T(t) = \mathbb{P}(T \leq t)\)。设 \(G_T\)​ 为 尾部累积分布函数\(G_T(t) = 1 - F_T(t) = \mathbb{P}(T \geq t)\)。在这里,我们使用 \(\geq\) 而不是 \(>\) 是不精确的,但由于 \(T\) 是一个连续随机变量,因此等式仍然成立,因为 \(\mathbb{P}(T = t) = 0\)

\(G_T\) 是一个尾部累积分布函数,但我们也可以将其视为一个函数:我们可以输入任何数字并得到一个介于 0 和 1 之间的数字。一般来说,我们可以将任何函数应用于一个随机变量,并因此得到另一个随机变量。例如,如果我们将函数 \(h(x) = 7x\) 应用于 \(T\),我们将得到一个新的随机变量 \(h(T)\),其值是 \(T\) 的七倍。因此,如果我们将函数 \(G_T\) 应用于随机变量 \(T\),我们得到另一个随机变量。注意 \(G_T(t)\),这是应用于特定数字 \(t\) 的函数,以及 \(G_T(T)\),这是应用于随机变量 \(T\) 的函数。

与特定检验统计量\(t\) 相关的\(p\) 值是\(\mathbb{P}(T \geq t)\):这仅仅是\(G_T(t)\)。p 值是一个随机变量\(P\),它依赖于随机变量\(T\):$ = G_T(T)$.

这个随机变量\(P\) 的累积分布函数(CDF)是什么?

\(\begin{align*} F_P(p) &= \mathbb{P}(P \leq p) \\ &= \mathbb{P}(G_T(T) \leq p) \end{align*}\) ​(1)

考虑函数 \(G_T^{-1}\),它是 \(G_T\) 的逆函数。GT 和它的逆函数都是单调非增的:换句话说,如果 \(a > b\),那么 \(G_T(a) \leq G_T(b)\),以及 \(G_T^{-1}(a) \leq G_T^{-1}(b)\)。因此,我们可以将函数 \(G_T^{-1}\) 应用于上述不等式的两边,这将反转不等式的方向:

\(\begin{align*} F_P(p) &= \mathbb{P}(G_T(T) \leq p) \\ &= \mathbb{P}(G_T^{-1}(G_T(T)) \geq G_T^{-1}(p)) \\ &= \mathbb{P}(T \geq G_T^{-1}(p)) \end{align*}\) ​(2)

这是指 T 大于某个值的概率:这就是尾部累积分布函数\(G_T\)GT​的定义。

\(\begin{align*} F_P(p) &= \mathbb{P}(T \geq G_T^{-1}(p)) \\ &= G_T(G_T^{-1}(p)) \\ &= p, \text{ for }0 \leq p \leq 1 \end{align*}\) ​(3)

我们可以通过微分来找到随机变量\(P\) 的概率密度函数,这给出的是\(f_P(p) = 1\), for \(0 \leq p \leq 1\):换句话说,在零假设下,\(P\) 是一个均匀随机变量。

注意,我们没有对检验统计量的分布做出任何假设:无论我们选择哪个检验统计量,这都是正确的。

简短版本

\(\begin{align*} G_T(t) &= \mathbb{P}(T \geq t) & \text{(定义尾部累积分布函数)}\\ P &= G_T(T) & \text{(定义 p 值)} \\ F_P(p) &= \mathbb{P}(P \leq p) & \text{(定义累积分布函数)}\\ &= \mathbb{P}(G_T(T) \leq p) & \\ &= \mathbb{P}(G_T^{-1}(G_T(T)) \geq G_T^{-1}(p)) & \text{(应用 }G_T^{-1}\text{ 到两边)} \\ &= \mathbb{P}(T \geq G_T^{-1}(p)) & \\ &= G_T(G_T^{-1}(p))) & \text{(使用 }G_T\text{ 的定义)} \\ &= p, \quad 0 \leq p \leq 1 \\ f_P(p) &= \frac{d}{dp}F_P(p) \\ &= 1, \quad 0 \leq p \leq 1 \\ P &\sim \mathrm{Uniform}(0, 1) \end{align*}\) ​(定义尾部累积分布函数)(定义 p 值)(定义累积分布函数)(应用 GT−1​到两边)(使用 GT​的定义)

示例:电子商务网站优化的 p 值阈值

这个例子将探讨我们选择\(p\) 值阈值如何导致我们在上一节中讨论的不同错误率之间的权衡。

假设我们正在探索使我们的电子商务网站对客户更具吸引力的方法。我们对网站进行了 100 种不同的变更(不同的颜色、字体、页面布局等),并且对于每一种变更,我们使用 A/B 测试来查看客户是否更有可能进行购买。我们定义以下零假设和备择假设:

  • 零假设:网站变更对客户是否购买没有影响。

  • 备择假设:变更增加了客户购买的机会。

对于每一次变更,我们将我们网站一半的用户随机分配到旧版本,另一半分配到新版本:因为这是一个随机实验,我们可以确定我们的变更是否导致客户购买产品更多。我们的测试统计量是处理组(网站新版本)中做出购买的人的百分比与控制组(网站旧版本)中相同百分比的差异。我们在零假设下模拟测试统计量,并为每个测试获得一个 p 值。这些 p 值在以下数据框中:

p_values = pd.read_csv('p_values.csv')
p_values[['pvalue']].head(3)

加载中...

我们可以使用条形图来可视化它们的分布,这给我们一个类似散点图的观点。每个点代表一个测试,x 轴代表 p 值,y 轴没有意义(它只是帮助分散点,以便更容易看到):

sns.stripplot(
    data=p_values, x='pvalue',
    alpha = 0.8, orient = "h",
)
plt.title("P-values (as we'd actually see them, without labels)");

<图大小 600x450,1 个坐标轴>

通常,我们无法确定每个变更是否实际上影响了客户行为。相反,我们必须根据 p 值来决定。特别是,我们的任务是,根据 p 值,决定哪些测试的数据支持零假设,哪些测试的数据支持备择假设。从 p 值的定义中,我们知道较小的 p 值应该支持备择假设,而较大的 p 值应该支持零假设。

如果我们神奇地知道每个变更的真实效果怎么办?在这种情况下,我们可以使用这个已知的真相来分析我们的决策过程,并评估我们做得如何。

这是我们将在整本书中多次使用的方法:在创建、设计和评估我们的算法时,我们将假设我们知道现实的“真实”值,这样我们就可以提供定量分析。然后,当我们将这些算法应用于现实世界(我们不知道现实)时,我们可以对我们的表现有信心。

is_alternative包含这 100 个 A/B 测试中每个测试的已知真实效果:

p_values.head(3)

加载中...

我们可以再次可视化 p 值,这次是根据网站更改是否实际上影响了客户的购买行为(即现实情况)进行分组。最上面一行包含原假设为真的点,最下面一行包含备择假设为真的点。

sns.stripplot(
    data=p_values, x='pvalue', y='is_alternative', hue='is_alternative', 
    alpha = 0.8, order = [0, 1], orient = "h",
)
plt.title('P-values, with ground truth labels');
print(plt.axis())
(-0.049319194537530316, 1.035869189459966, 1.5, -0.5) 

<Figure size 600x450 with 1 Axes>

我们现在可以看到,我们选择的任何特定阈值都会导致我们做出一些正确的决策和一些错误的决策。例如,假设我们使用 0.1 的 p 值阈值:

sns.stripplot(
    data=p_values, x='pvalue', y='is_alternative', hue='is_alternative',
    alpha = 0.8, order = [0, 1], orient = "h",
)
plt.vlines(0.1, -0.5, 1.5, color='black')
plt.axis([-0.05, 1.05, 1.5, -0.5])
plt.title('P-values, with ground truth labels');

<Figure size 600x450 with 1 Axes>

对于小于我们阈值的 p 值(位于线左侧),我们的决策是 1。所以:

  • 对于所有位于线左侧的橙色(底部)点,我们做出了正确的决策:在这种情况下,现实和我们的决策都是 1(备择假设),因此这些是真阳性

  • 对于所有位于线右侧的橙色(底部)点,我们做出了错误的决策:在这种情况下,现实是 1(备择假设),但我们的决策是 0(原假设)。因此,这些是假阴性

  • 对于所有位于线左侧的蓝色(顶部)点,我们做出了错误的决策:在这种情况下,现实是 0(原假设),但我们的决策是 1(备择假设)。因此,这些是假阳性

  • 对于所有位于线右侧的蓝色(顶部)点,我们做出了正确的决策:在这种情况下,现实和我们的决策都是 0(原假设),因此这些是真阴性

我们的目标应该是尽可能多地做出真阴性和真阳性,同时尽可能少地做出假阳性和假阴性。但从图中我们可以看到,这是一个权衡:当我们减少假阳性时,我们必然会增加假阴性。例如,假设我们希望完全没有假阴性。这意味着我们希望所有备择假设为真的测试的 p 值都低于我们的阈值(线左侧的所有橙色点)。让我们看看如果我们选择这样的阈值 0.42 会发生什么:

sns.stripplot(
    data=p_values, x='pvalue', y='is_alternative', hue='is_alternative', 
    alpha = 0.8, order = [0, 1], orient = "h",
)
plt.vlines(0.42, -0.5, 1.5, color='black')
plt.axis([-0.05, 1.05, 1.5, -0.5])
plt.title('P-values, with ground truth labels');

<Figure size 600x450 with 1 Axes>

尽管我们大幅降低了假阴性率,但不幸的是,我们的假阳性率增加了:现在有更多测试的原假设为真,但 p 值低于我们的阈值(线左侧的蓝色点)。

简单和复合假设

在进行假设检验时,我们的原假设和备择假设分为两类:

  • 简单假设是精确的,它表明测试统计量取某个特定值。例如,像“两组的平均值之间没有差异(\(\mu_1 - \mu_2 = 0\))”或“真实比例是 0.5(\(q = 0.5\))”这样的假设为测试统计量提供了一个单一值。

  • 相比之下,复合假设不那么具体,通常描述测试统计量大于、小于或等于某个参考值。例如,像“第一组的平均值大于第二组的平均值(\(\mu_1 - \mu_2 > 0\))”或“真实比例不等于 0.5(\(q \neq 0.5\))”这样的假设就是复合的。

在你迄今为止看到的大多数假设检验中,你可能已经使用了一个简单的零假设(即我们可以模拟或计算的特定假设),以及一个复合备择假设。稍后,我们将探讨当我们使用简单的备择假设时会发生什么。

从 p 值到决策

我们在上面已经看到,为了从单个 p 值中做出二元决策,我们必须使用某个阈值。我们将看到几种不同的方法来选择这样的阈值:

  • 经典的零假设显著性检验(NHST):在这里,我们根据我们期望的假阳性率选择一个阈值。例如,传统的(任意的)0.05 阈值对应于每次假设测试中产生假阳性的 5%的机会。在这种情况下,我们的零假设通常是一个明确指定的简单假设,但我们通常使用“模糊”的组合备择假设,例如“两组之间没有差异”。这意味着我们可以精确地分析当零假设为真时会发生什么(\(R=0\)),并推理假阳性率和真阴性率。然而,因为我们的备择假设是组合的,我们无法精确地定义备择情况会发生什么,所以我们通常不推理真阳性率和假阴性率(即对应于\(R=1\))的比率)。

  • 在奈曼-皮尔逊框架中,我们选择一个简单的备择假设,并推理真阳性率。

  • 在进行多次测试时,我们需要选择考虑所有进行测试的错误率的阈值。我们将在下一节中检查这些错误率以及它们为什么重要。

from IPython.display import YouTubeVideo
YouTubeVideo('WIrueFDjw64')

加载中...

sns.set_theme(style='whitegrid')

def make_null_alternative_plots(
    show_null=True, show_alternative=True, threshold=0.7, fill_alpha=0.3
):
    null_color = 'tab:red'
    alternative_color = 'tab:blue'
    null_mean = 0
    alt_mean = 2.5
    sigma = 1
    bounds = [-2, 5, -0.02, 0.42]
    null_right = 'red'
    null_left = 'gold'
    alternative_right = 'green'
    alternative_left = 'purple'

    f, ax = plt.subplots(1, 1, figsize=(6, 3), dpi=100)
    t = np.linspace(bounds[0], bounds[1], 1000)
    zero = np.zeros_like(t)
    null = stats.norm(null_mean, sigma).pdf(t)
    alternative = stats.norm(alt_mean, sigma).pdf(t)

    if show_null:
        ax.plot(t, null, lw=2, color=null_color, label='Null distr.')
        ax.fill_between(t, zero, null, t > threshold, color=null_right, alpha=fill_alpha)
        ax.fill_between(t, zero, null, t < threshold, color=null_left, alpha=fill_alpha)

    if show_alternative:
        ax.plot(t, alternative, lw=2, color=alternative_color, label='Alt distr.')
        ax.fill_between(t, zero, alternative, t > threshold, color=alternative_right, alpha=fill_alpha)
        ax.fill_between(t, zero, alternative, t < threshold, color=alternative_left, alpha=fill_alpha)

    ax.axvline(threshold, color='black', lw=2)
    ax.axis(bounds)
    ax.set_xlabel('$t$')
    ax.set_ylabel('$p(t)$')

    ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))

在不同的行内率之间进行权衡

\(p\) 值的分析帮助我们理解如果零假设为真会发生什么:我们之前看到,我们选择的\(p\) 值阈值是测试的假阳性率。但如果备择假设为真呢?例如,我们可能对构建一个测试感兴趣,我们希望假阴性率小于 0.1,或者我们可能想数值量化如果我们减少假阳性率 0.01,假阴性率会增加多少。

组合备择假设不支持我们上面所做的那种分析:只有当我们选择一个简单的备择假设时,我们才能对备择情况进行推理。因此,在本节的剩余部分,我们只考虑零假设和备择假设都简单的情况。

分析权衡的方法有很多,所以我们将关注其中的两种:

  1. 我们如何量化假阳性率和假阴性率之间的权衡?换句话说,我们如何衡量\(R=0\)\(R=1\) 时的行率之间的权衡?我们将使用接收者操作特征曲线,通常称为ROC 曲线,来回答这个问题。更多内容,请参阅二分类部分。

  2. 对于给定的假阳性率(即显著性阈值或\(p\)值阈值),我们应该选择哪个检验统计量来最大化功效,或者说最大化真正的阳性率?换句话说,如果我们为\(R=0\) 的情况固定一个期望的错误水平,那么当\(R=1\) 时,我们如何获得最佳性能?这是我们接下来要关注的问题。

理解假设检验中的功效-显著性权衡

当使用奈曼-皮尔逊引理时,我们的目标将是找到任何给定显著性水平的最强检验。换句话说,对于任何我们指定的期望的假阳性率(即,我们想要控制当原假设为真时的错误概率),我们想要找到具有最高可能功效的检验(即,我们想要最大化当备择假设为真时的成功概率)。

我们将通过选择一个最大化功效的检验统计量(即我们观察到的数据的函数)来实现这一点。而不是像以前那样计算一个\(p\)值,我们将通过将阈值应用于我们的检验统计量来做出决定:如果它高于我们的阈值,那么我们将拒绝原假设,如果它低于我们的阈值,我们将无法拒绝原假设。

在我们看到这个检验统计量之前,让我们看看几个有助于说明效力与显著性之间权衡的图表。假设我们观察到一个正态分布的单个数据点\(x\),我们的检验统计量是\(t=x\)。我们将假设\(t \sim \mathcal{N}(\mu, 1)\),对于某个均值\(\mu\)。如果我们的零假设表明\(\mu = 0\),那么我们可以写出\(t | H_0 \sim \mathcal{N}(0, 1)\)。因此,在零假设下,我们的 t 分布为:

make_null_alternative_plots(show_null=True, show_alternative=False)

<Figure size 600x300 with 1 Axes>

黑色垂直线表示一个任意选择的决策阈值:如果我们使用这个阈值,阴影区域显示了我们的测试的 FPR(右侧,红色)和 TNR(左侧,黄色)。我们可以看到,如果我们提高阈值,我们会得到更低的假阳性率(红色)。

但是,关于检验的效力呢?如果我们想遵循类似的过程来计算检验的效力(或 TPR),我们必须使用一个简单的备择假设。例如,一个复合备择假设如\(\mu > 0\),它不会给我们足够的信息来计算备择假设下的错误率。因此,我们将选择一个特定的备择假设:\(T | H_1 \sim \mathcal{N}(2.5, 1)\):

make_null_alternative_plots(show_null=False, show_alternative=True)

<Figure size 600x300 with 1 Axes>

对于这种备择假设的选择,我们现在可以看到 TPR(真阳性率)和 FNR(假阴性率)分别对应绿色(右侧)和紫色(左侧)的区域。如果我们提高阈值,我们将获得更低的 TPR(绿色)。

现在,我们将在同一张图上可视化这两个假设:

make_null_alternative_plots(show_null=True, show_alternative=True)

<图大小 600x300,1 个轴>

现在,我们可以看到我们的阈值在这两种情况下的影响:如果零假设为真,我们的错误概率是阈值右侧红色曲线下的面积(确保你让自己相信这是正确的)。如果备择假设为真,我们的错误概率是阈值左侧蓝色曲线下的面积。理想情况下,我们希望这两个区域都尽可能小,但在实践中,我们通常必须在这两者之间进行权衡。

Neyman-Pearson 引理通过选择一个好的检验统计量来帮助我们量化这种权衡。回想一下我们的测试框架:我们首先指定在零假设和备择假设下数据的分布(例如,在上面,我们选择了\(X | H_0 \sim \mathcal{N}(0, 1)\)\(X | H_1 \sim \mathcal{N}(2.5, 1)\))。然后,我们选择一个检验统计量来帮助我们区分零假设和备择假设。在上述非常简单的例子中,我们只观察到一个数据点,我们只是选择了我们观察到的数据点作为检验统计量。但在一般情况下,当我们观察到许多数据点并需要在更复杂的假设之间进行选择时,我们有多种可能的检验统计量可以选择。

注意,在上面,我们为每个假设定义了数据的分布:这些被称为似然函数。它们指定了在某个未知世界状态下我们观察到的数据的分布。在传统的假设检验设置中,我们只需要定义零假设下的似然(或者能够模拟它)。当我们想要推理关于功率时,我们需要指定备择假设下的似然。这通常需要我们对我们寻找的差异或效果的大小做出一个具体的假设:任何功率的计算都是基于这个假设的。

Neyman-Pearson

奈曼-皮尔逊引理表明,对于任何我们希望达到的显著性水平(即假阳性率),我们可以通过将似然比作为检验统计量来计算,然后将其与由我们所需的显著性水平确定的阈值进行比较,从而实现可能的最大功效。

给定观察到的数据 x(这通常可以代表多个观察值),以及原假设和备择假设的似然函数,我们计算检验统计量:

\(LR = \frac{p(x|H_1)}{p(x|H_0)}\) ​(5)

并将其与某个阈值η比较。如果似然比大于阈值,我们拒绝原假设,如果它低于阈值,我们则不拒绝。直观地说,这个测试询问:“与原假设相比,数据在备择假设下的可能性高多少倍?”我们的阈值然后决定了我们在决定拒绝原假设之前需要数据在备择假设下的可能性高出多少。例如,阈值为 2 意味着我们需要看到在备择假设下(相对于原假设)可能性是两倍的数据,我们才决定拒绝原假设。

在实践中使用奈曼-皮尔逊引理通常涉及以下步骤:

  1. 根据原假设和备择假设定义观察数据的似然函数。

  2. 确定所需的显著性水平(即你愿意容忍的假阳性率)。

  3. 使用显著性水平通过以下步骤计算一个阈值:(a) 将假阳性率表示为基于上述决策规则的条件概率,(b) 将其设置为所需的显著性水平,并(c) 解出阈值作为显著性水平的函数。

  4. 通过以下步骤计算检验的功效:(a) 将功效表示为基于上述决策规则的条件概率,并(b) 插入在上一步骤中计算出的阈值。

通过将步骤(3)中的阈值用任何所需的显著性水平来解出,我们可以使用步骤(4)来明确地关联功效和显著性水平之间的权衡。

多重假设检验

原文:data102.org/ds-102-book/content/chapters/01/multiple-tests

笔记本单元

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

%matplotlib inline

sns.set()  # This helps make our plots look nicer

# These make our figures bigger
plt.rcParams['figure.figsize'] = (6, 4.5)
plt.rcParams['figure.dpi'] = 100 

多重检验与复制危机

到目前为止,我们已经讨论了在评估单个假设检验时,如何考虑假阳性率(FPR)和真阳性率(TPR,或功效)。我们了解到,在使用传统的零假设显著性检验(NHST)框架时,我们选择一个 p 值阈值,这是我们每个测试的 FPR。如果我们选择一个简单的备择假设,我们可以通过查看在该简单备择假设下做出发现的概率来计算 TPR(即,在我们的测试统计量超过决策阈值的情况下,该简单备择假设的概率)。

但在很多情况下,仅仅控制假阳性率可能不足以达到我们期望的错误水平。为了帮助说明这一点,我们将考虑三位研究人员进行假设性研究,研究人们每年吃多少黄油和每年读多少本书之间的关联:

  • 研究员纳特进行了一次测试,观察了美国所有个人的随机样本。

  • 研究员斯坦进行了五十次测试,观察了来自美国每个州的五十个随机样本。

  • 研究员科林进行了 3,244 次测试,观察了来自美国每个县的 3,244 个随机样本。

假设他们三人都使用 0.05 的 p 值阈值:这意味着他们发现假阳性的概率只有 \(5\%\)。此外,由于研究的前提有些荒谬,我们可以安全地假设在每次测试中,零假设都是正确的:换句话说,黄油消费与阅读书籍之间没有相关性。如果是这样,每个人预期的假阳性数量是多少?

每位研究人员发现的假阳性数量是一个参数为 \(p=0.05\)\(n \in \{1, 50, 3244\}\) 的伯努利随机变量,具体取决于研究人员。因此,预期的假阳性数量是 \(np\)

  • 内特预期的假阳性数量是 \(0.05 \times 1 = 0.05\):这非常接近 0。

  • 斯坦预期的假阳性数量是 \(0.05 \times 50 = 2.5\).5:换句话说,即使零假设为真,斯坦也应该预期有 2-3 个州被误判为假阳性。

  • 科林预期的假阳性数量是 \(0.05 \times 3244 = 162.2\):科林应该预期平均有 162 个县被误判为假阳性。

这些假阳性可能会产生严重影响!如果斯坦的研究在新闻报道中被处理不当,可能会导致戏剧性的标题,例如“加利福尼亚和爱达荷州显示黄油消费与阅读之间存在强烈联系:小学应该提供更多黄油以提高阅读率吗?”虽然这个例子看起来有些荒谬,因为很明显这些关联是虚假的,但研究人员使用不良的统计实践时这种情况经常发生。

一个 0.05 的 p 值阈值意味着,当零假设为真时,我们应预期在\(5\%\) 的时间里,我们会错误地做出一个发现。当进行大量测试时,这种情况会累积起来。

当研究人员决定要测试哪些关联时,这个问题经常会发生。例如,一个研究人员可能对维生素 D 补充剂对整体健康的影响感兴趣。如果对数据的初步分析没有返回结果,研究人员可能会尝试看看这些效果是否对不同性别的人不同。如果这也找不到结果,研究人员可能会认为维生素 D 从阳光中的吸收取决于皮肤中的黑色素,所以他们可能会查看所有六种不同的Fitzpatrick 皮肤类型的效果。在这个时候,在可能是一个相当无害的测试序列中,研究人员已经进行了 9 个不同的测试,至少出现一个假阳性的概率是\(~1 - \left(1-0.05\right)⁹ \approx 0.37\)

多重测试的不同方法

我们已经看到,当我们在一个固定的\(p\) 值阈值下进行多次假设检验时,我们可以控制每个测试的 FPR,但我们不一定能控制多次测试中犯错误的速率。为了解决这个问题,我们将定义涉及我们进行的所有测试的错误率,并找到控制这些错误率的算法。我们将让\(m\) 表示假设检验的次数,并定义两个错误率:

  • 家族错误率(FWER)是任何一次\(m\) 次测试导致假阳性的概率。

  • 假发现率(FDR)是对于\(m\) 次测试的假发现比例(FDP)的期望值。

我们将探讨两种算法,这些算法可以应用于从所有\(m\) 次测试中获得的\(p\) 值:Bonferroni 校正,它控制 FWER,以及 Benjamini-Hochberg 过程,它控制 FDR。在这里,“控制”意味着我们保证错误率将低于我们选择的某个值。一旦我们描述了这些算法,我们将讨论两种算法之间的权衡,以及这些权衡如何与两种错误率的固有属性相关。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('6BrafO72h_w')

加载中...

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('ILLMDQkQl9A')

加载中...

随机性、FWER、FDR 和 FDP

到目前为止,我们主要关注的是假发现比例(FDP)。从现在开始,我们将更多地关注假发现率(FDR),因此理解这两者之间的区别很重要。在本节中,我们将采用频率主义方法,并假设我们的数据是随机的(而且因为我们的决策是基于我们的数据,所以它们也是随机的),现实是固定且未知的。

  • FDP 是我们为任何特定数据集获得的价值:我们获得数据,做出一系列决策,然后假发现比例是基于那些特定决策的。

  • FDR 是 FDP 的期望值,这里的期望值是对数据中的随机性进行取值。换句话说,FDR 是平均 FDP,平均所有可能的数据集(并按每个数据集的可能性进行加权)。

  • 类似地,对于任何特定的数据集,我们可以定义事件“至少发生了一个假阳性”。对于任何一系列决策,这个事件要么发生,要么不发生。家族错误率(FWER)是对于任何数据集该事件发生的概率。

换句话说,假发现比例是基于任何特定的数据集,而假发现率是所有可能数据集的平均 FDP。

已知与未知

在本节中,我们将讨论 FDP、FDR 和 FWER 作为评估决策过程的指标。但请记住,在现实中,我们只能观察到数据和基于数据的决策:现实对我们来说是未知的。这意味着在许多现实世界的场景中,我们实际上无法计算任何特定一系列决策的 FDP,因为这需要我们知道何时\(R=0\) 和何时\(R=1\)

因此,如果我们实际上无法计算 FDP,我们为什么要分析它?

关键在于 FDR(平均 FDP)提供了一种评估算法的方法。我们将讨论几个程序,并表明平均而言,它们的性能良好:换句话说,如果我们观察这些程序在数据随机性上的平均性能,我们可以从数学上证明 FDR 或 FWER 将低于某个特定水平。例如,我们将研究一个称为 Bonferroni correction 的程序,并表明如果我们使用它,我们的 FWER,即做出错误阳性的概率,将低于我们特定指定的某个阈值。

这个过程,首先定义在观测数据上操作以做出决策的算法,然后使用依赖于未知变量的指标来理论上评估这些算法,是我们将在整个课程中反复看到的东西。

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('G9EYjVfLLBU')

加载中...

Bonferroni correction

Bonferroni correction 是一种控制 FWER 的技术。在这个上下文中,当我们使用“控制”FWER 在水平α时,这仅仅意味着我们希望 FWER 低于或等于我们选择的某个值α。

该程序本身非常简单:它指出,如果我们想保证我们的 m 个测试的 FWER(家庭错误率)小于或等于α,那么我们只需要使用一个 p 值阈值为α/m 来对每个测试做出决策。例如,如果我们想保证 500 个测试的 FWER 为 0.01,那么我们应该为每个测试使用 p 值阈值为 0.01/5000=2×10−6。

让我们展示为什么这个公式是有效的。我们首先将建立一些我们需要的事实和定义。

首先,我们需要使用并集不等式,它指出对于事件\(A_1, \ldots, A_m\)A1​,…,Am​,有

\(P\left(\bigcup_{i=1}^m A_i\right) \leq \sum_{i=1}^m P(A_i).\right.\) (1)

非正式地说,这意味着如果我们把事件发生的独立概率相加,结果总是大于或等于这些事件并集的概率。直观上,这是正确的,因为在计算并集的概率时,我们必须有效地减去概率之间的重叠部分。

要使用并集界,我们将定义指示变量 \(T_1, \ldots, T_m\)​,其中 \(T_i\)Ti​ 是测试 \(i\) 结果为假阳性的事件。家族错误率是任何一项测试为假阳性的概率:换句话说,\(FWER = P(T_1 \cup T_2 \cup T_3 \cdots \cup T_m)\). 我们从上一节知道,如果我们为每个测试使用相同的 \(p\)-值阈值 \(\gamma\),那么 \(P(T_i) = \gamma\).

将所有内容综合起来,我们有:

\(\begin{align*} FWER &= P\left(\bigcup_{i=1}^m T_i\right) \\ &\leq \sum_{i=1}^m P\left(T_i\right) \\ &= m \gamma \end{align*}\) (2)

如果我们选择每个测试的 \(p\) 值阈值 \(\gamma\) 等于 \(\alpha/m\)m(记住 \(\alpha\) 是我们想要的 FWER),那么右侧就变成了 \(\alpha\),我们就能保证我们的 FWER 小于或等于 \(\alpha\)

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('zwydh-K6Sc4')

加载中...

控制 FDR

为什么控制 FDR 而不是 FWER?

假设我们进行 1,000,000 次测试,并希望将 FWER 控制在 0.01 的水平。如果我们使用 Bonferroni 方法,我们的 \(p\) 值阈值将是 10^(-8):只有当 \(p\) 值极其小的时候,我们才会做出发现!这是因为 FWER 是一个非常严格的标准。控制 FWER 意味着我们要确保在 \(m\) 次测试中产生任何假阳性的概率小于或等于 \(\alpha\):随着 \(m\) 的增大,这个要求变得越来越难以满足。这就是为什么随着 \(m\) 的增加,Bonferroni 校正的 p 值阈值会变得越来越小。

另一方面,控制 FDR 对少量错误更为宽容。在我们上面的例子中,在 0.01 水平上控制 FDR 意味着在我们做出的发现中,我们希望 1% 或更少的它们是错误的。这仍然比我们通过简单阈值化看到的只控制 FPR 更为严格,但随着 m 的增长,它更容易满足,而不需要施加如此极端低的 \(p\)-value 阈值。接下来,我们将看到一个达到这个中间点的算法。

Benjamini-Hochberg 程序

Benjamini-Hochberg (通常简称为 B-H) 程序比 Bonferroni 校正稍微复杂一些,但它也使用相同的 \(p\)-value 阈值对所有测试。关键是,我们使用 \(p\)-values 本身来确定阈值。以下是它的工作原理,对于一个期望的 FDR \(\alpha\):

  • 首先,对 \(p\)-values 进行排序,并按 \(k\) (即,第一个对应于 \(k=1\),第二个对应于 \(k=2\),以此类推,直到最后一个对应于 \(k=m\))

  • 对于每个排序的\(p\) 值,将其与值\(\frac{k\alpha}{m}\)mkα比较(即,最小的 p 值与\(\alpha/m\) 比较,第二小的与\(2\alpha/m\) 比较,以此类推,直到最大的与\(\alpha\)比较)

  • 找到仍然低于其比较值的最大的排序\(p\)

  • 使用该\(p\) 值作为阈值

# NO CODE

# VIDEO: B-H Algorithm Overview and Example
from IPython.display import YouTubeVideo
YouTubeVideo('w1yZTe7X1JM')

加载中...

Naive 阈值、Bonferroni 和 Benjamini-Hochberg 的视觉比较

考虑我们在上一节中查看的\(p\) 值。我们将添加一列k,它提供排序后的索引:

p_sorted = pd.read_csv('p_values.csv').sort_values('pvalue')

m = len(p_sorted)  # number of tests
k = np.arange(1, m+1)  # index of each test in sorted order

p_sorted['k'] = k 
p_sorted

加载中...

我们可以按顺序可视化\(p\) 值:

sns.scatterplot(p_sorted, x='k', y='pvalue', hue='is_alternative');

<Figure size 600x450 with 1 Axes>

在这里,\(x\) 轴是\(k\),即索引,而\(y\) 轴表示\(p\) 值。我们可以可视化两种技术的结果:

  • 如果我们为所有测试使用 0.05 的朴素\(p\) 值阈值,我们将获得 0.05 的 FPR。这个阈值是下面的黑线。

  • 如果我们使用 Bonferroni 校正并希望得到 0.05 的 FWER(即,做出任何假阳性的概率为 0.05),那么我们应该使用一个\(p\) 值阈值为\(\frac{0.05}{100} = 0.0005\)。这个阈值是下面的红色虚线。

我们可以看到,通过使用更保守的 Bonferroni 阈值(红色),我们留下了

desired_fwer = 0.05
sns.scatterplot(x=k, y=p_sorted['pvalue'], hue=p_sorted['is_alternative'])
plt.axhline(0.05, label='Naive thresholding', color='black')
plt.axhline(desired_fwer / m, label='Bonferroni', color='tab:red', ls='--')

plt.legend();

<Figure size 600x450 with 1 Axes>

在这个可视化中,Benjamini-Hochberg 过程是如何工作的?我们比较每个\(p\) 值与比较值\(\frac{k\alpha}{m}\)​,在这个可视化中是一个对角线。为了更好地看到正在发生的事情,我们还将放大一个更窄范围的\(p\) 值:

desired_fdr = 0.05
sns.scatterplot(x=k, y=p_sorted['pvalue'], hue=p_sorted['is_alternative'])
plt.plot(k, k/m * desired_fdr, label='B-H guide', color='cyan', ls=':')
plt.axis([-0.05, 30, -0.0005, 0.0305])
plt.legend();

<Figure size 600x450 with 1 Axes>

Benjamini-Hochberg 过程建议取小于比较值\(\frac{k\alpha}{m}\)​的最大\(p\) 值:在这种情况下,那就是索引 16 的点。这成为我们的\(p\) 值阈值,因此我们选择拒绝前 16 个\(p\) 值(排序后)。下面的图表显示了所有三个阈值:

desired_fdr = 0.05
desired_fwer = 0.05

# From visually inspecting the graph above, we saw that the 16th
#  p-value was the last one below the reference value (cyan line)
bh_threshold = p_sorted.loc[p_sorted['k'] == 16, 'pvalue'].iloc[0]

plt.axhline(
    bh_threshold, label=f'B-H threshold (FDR={desired_fdr})', color='tab:green', ls='-.'
)
plt.axhline(
    0.05, label='Naive threshold (FPR=0.05)', color='black', ls='-'
)
plt.axhline(
    desired_fwer / m, label=f'Bonferroni (FWER={desired_fwer})', color='tab:red', ls='--'
)

sns.scatterplot(x=k, y=p_sorted['pvalue'], hue=p_sorted['is_alternative'])
plt.plot(k, k/m * desired_fdr, label='B-H guide', color='cyan', ls=':')
plt.axis([-0.05, 42.5, -0.001, 0.06])
plt.legend();

<Figure size 600x450 with 1 Axes>

(可选)为什么 Benjamini-Hochberg 可以控制 FDR?

文本即将到来:请看视频

# NO CODE

# VIDEO: B-H Proof Sketch
from IPython.display import YouTubeVideo
YouTubeVideo('e10W3lJsBhc')

加载中...

比较和对比 FWER 和 FDR

现在我们已经看到了如何控制 FWER 和 FDR,我们将更好地理解在特定问题中何时每个可能更适合。回顾定义:

  • 家族错误率(FWER)是任何测试中出现假阳性的概率。

  • 假发现率(FDR)是预期的 FDP,或者说,是预期的不正确发现的比率。

假设我们进行了 100 万个测试(\(m=1000000\))。

如果我们希望 FWER 为 0.05,这意味着我们希望 100 万个测试中任何一个测试为假阳性的概率不超过 0.05。为了控制这个概率,我们需要非常保守:毕竟,即使是单个假阳性也意味着我们已经失败了。换句话说,控制 FWER 要求我们非常保守,并使用非常小的\(p\)值阈值。这通常会导致非常低的功效,或者说真正的阳性率(TPR),因为我们的\(p\)值阈值如此之低,以至于我们错过了许多真正的阳性。

另一方面,如果我们希望假发现率(FDR)为 0.05,这仅仅意味着在 100 万个测试中,我们平均希望 95%或更多的发现是正确的。我们可以通过一个不那么保守的阈值来实现这一点。换句话说,当我们控制 FDR 时,我们接受一些假阳性,作为回报,我们可以实现更高的真正阳性率(即,在\(R=1\))的情况下做得更好)。

这些解释如何转化为在现实世界应用中选择控制率?我们将通过两个例子来帮助说明差异。

  • 考虑一种罕见疾病的医学检测,这种疾病的唯一治疗方法是危险的手术。在这种情况下,假阳性可能导致患者不必要地接受手术,使患者的生命无谓地处于危险之中,并可能引发医疗创伤。另一方面,假阴性虽然由于缺乏治疗可能仍然具有潜在危害,但可能没有这么严重。在这种情况下,或者任何假阳性远比假阴性更严重的情况,控制家族错误发现率(FWER)可能是一个更好的选择,因为控制 FWER 倾向于假阴性而非假阳性。

  • 考虑一个对进行大量 A/B 测试以衡量各种网站更改是否提高购物者购买产品机会感兴趣的在线零售商。在这种情况下,假阳性的危害并不特别严重,我们可能能够容忍我们的发现中有 5%是错误的,尤其是如果这意味着有更好的机会找到能增加产品购买的机会。

注意,这两个例子都有点模糊!在第一个例子中,假阴性的成本强烈取决于治疗如何改善该条件患者的预后,是否存在后续测试等因素。同样,在第二个例子中,如果网站更改有辅助成本(开发者为进行更改所花费的时间和金钱,以及基于更改的用户对网站的看法变化等),那么这可能会影响我们是否可能更喜欢其中一个。

# NO CODE

# VIDEO: B-H Proof Sketch
from IPython.display import YouTubeVideo
YouTubeVideo('hD6zX8zZU_A')

加载中...

二元分类

原文:data102.org/ds-102-book/content/chapters/01/binary-classification

import numpy as np
import pandas as pd
from sklearn.metrics import confusion_matrix, roc_curve, precision_recall_curve

%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

二元分类

在二元分类中,就像上一节一样,我们也在尝试做出二元决策。一个关键的区别是,我们不是使用假设检验的语言,而是使用机器学习和分类的语言:我们有数据点,我们试图将它们分类为属于两个组中的一个,我们可以(不失一般性)称它们为类别 0 和类别 1。

在假设检验中,我们使用\(p\) 值来做决策,这些 p 值捕捉了在给定数据的情况下,我们的数据在某个零假设下被观察到的概率。在二元分类中,我们感兴趣的是在给定数据的情况下,我们的数据在类别 0 或类别 1 下被观察到的概率。大多数分类算法都会给出一些任意数。这些数字通常,但不总是,介于 0 和 1 之间,在这种情况下,我们可以将它们解释为属于类别 1 的概率。我们通常将一个阈值应用到这个数字上,以获得二元决策。

例如,逻辑回归产生概率:对于一个特定的数据点,逻辑回归分类器可能会预测它有 0.7 的概率属于类别 1。我们应用一些阈值(通常是,但不总是 0.5):如果我们的值高于阈值(就像这个例子一样),我们做出决策 1;否则,我们做出决策 0。

虽然在二元分类中获取决策的机制不同,但我们之前几节学到的重大思想仍然同样相关:特别是,我们可以通过理解行内和列内错误率来考虑涉及的权衡。在二元分类中,我们主要通过两种可视化来可视化和分析这些错误率:接收者操作特征(ROC)曲线和精确率-召回率曲线。

ROC 曲线:权衡行内率

ROC 曲线帮助我们可视化不同行内率之间的权衡。让我们用一个例子来具体说明。假设一家制造公司训练了一个分类器,该分类器使用计算机视觉(例如,一个视觉转换器)来预测其装配线上的每个产品是否为缺陷(1)或非缺陷(0)。他们在保留的测试集上获得了以下结果:

defects = pd.read_csv('manufacturing.csv')
defects.head()

加载中...

def formatted_confusion_matrix(y_true, y_pred):
    return pd.DataFrame(
        data=confusion_matrix(y_true, y_pred),
        index=['R=0', 'R=1'],
        columns=['D=0', 'D=1']
    )
formatted_confusion_matrix(defects['is_defective'], defects['predicted_prob'] > 0.5)

加载中...

formatted_confusion_matrix(defects['is_defective'], defects['predicted_prob'] > 0.2)

加载中...

就像之前一样,我们可以看到行内率之间的相同权衡:随着我们的 TPR 增加(即我们更好地捕捉到有缺陷的产品),我们的 FPR 也会增加(即我们对非缺陷产品的处理变得更差)。上面的例子显示了权衡中的两个点,但对于每个不同的可能阈值值都存在这样一个点。我们不必为每个阈值计算和手动审查混淆矩阵,而是可以通过使用scikit-learn中的roc_curve函数来可视化与每个可能阈值相关的 TPR 和 FPR:

fprs, tprs, thresholds_roc = roc_curve(defects['is_defective'], defects['predicted_prob'])

f, ax = plt.subplots(1, 1, figsize=(4, 4))
ax.plot(fprs, tprs)
ax.set_xlabel('FPR')
ax.set_ylabel('TPR')
ax.axis('equal')
ax.set_title('Receiver operating characteristic (ROC) curve');

<Figure size 400x400 with 1 Axes>

图上的每个点代表一个阈值。例如,大约在(0.2, 0.8)处的点告诉我们,如果我们选择那个特定的阈值值,我们将实现 0.2 的假阳性率和 0.8 的真正阳性率。

如果那个 FPR 过高,我们可以选择一个大约在(0.1, 0.4)处的阈值。这告诉我们,如果我们想要更小的 FPR 为 0.1,我们必须接受一个远低于 0.1 的 TPR。

这为我们提供了一种清晰可视化涉及权衡的方法,至少就行内率而言。

练习:编写代码以找到给我们一个尽可能接近 0.2 的 FPR 的阈值值。提示:使用thresholds_roc数组

精确率-召回率曲线和列内率

我们在前面几节中看到,仅靠行率并不能告诉我们整个故事!我们看到,在患病率低的情况下,即使我们有相对较高的行率,我们的列率也可能相当低。在这个例子中,阈值为 0.5 导致 TPR 和 TNR 接近 0.8(即,我们的算法对缺陷产品的准确率约为\(80\%\),对非缺陷产品的准确率也约为\(80\%\)),但预测“有缺陷”的错误率将超过一半!

display(formatted_confusion_matrix(defects['is_defective'], defects['predicted_prob'] > 0.5))
print(156/(36+156), 641/(167+641))

加载中...

0.8125 0.7933168316831684 

正如 ROC 曲线绘制了真正例率与假正例率的关系一样,精确度-召回率曲线绘制了精确度与召回率的关系。

记住之前提到的精确度是 FDP 的补数:换句话说,它描述了当我们做出\(D=1\) 的决定时,或者在这种情况下,当分类器将产品标记为缺陷时,我们做得有多好。召回率只是真正例率的另一个术语(我们也将它称为功率和灵敏度)。绘制这两个指标有助于我们理解行率和列率之间的权衡。让我们看看上面的例子是什么样的:

precisions, recalls, thresholds_pr = precision_recall_curve(defects['is_defective'], defects['predicted_prob'])

f, ax = plt.subplots(1, 1, figsize=(4, 4))
ax.plot(recalls, precisions)
ax.set_xlabel('Recall (TPR)')
ax.set_ylabel('Precision')
ax.axis('equal')
ax.set_title('Precision-recall curve');

<图大小为 400x400,包含 1 个坐标轴>

我们可以看到,这告诉我们一个互补的故事。我们可以看到,曲线的左侧非常嘈杂,在中间,随着召回率的提高,精确度提高到约 0.55,然后开始迅速下降,对于召回率更高的值。这意味着什么?

练习:解释为什么曲线的左侧看起来如此嘈杂。提示:考虑哪种阈值会导致非常低的召回率,然后解释这种阈值是如何影响我们精确度计算中的分母的。

练习:解释为什么精确度在召回率高于约 0.6 之前会随着召回率的增加而增加,然后又下降。

统计决策理论

data102.org/ds-102-book/content/chapters/01/decision-theory

内容许可:Creative Commons 知识共享署名-相同方式共享 4.0 国际 (CC-BY-SA-4.0) GitHub 仓库:ds-102/ds-102-book 编辑此页

迄今为止,我们一直使用错误率来帮助我们理解二进制决策中的权衡。在本节中,我们将介绍一个更通用的理论框架来理解和量化我们所犯的错误,并开始探索统计学的一个理论分支,即统计决策理论

记住我们的设置:我们有一些未知量\(\theta\),这是我们感兴趣的。我们收集数据\(x\)。我们的数据是随机的,并来自分布\(p(x|\theta)\)。我们使用数据来推理\(\theta\)。通常,我们希望使用数据来计算\(\theta\)的估计值,但有时我们可能想做一些不同的事情。为了描述“我们对数据进行的事情”,我们将使用符号\(\delta(x)\)。这代表了对数据应用某些程序\(\delta\)的结果。例如,\(\delta\)可能是许多数据点的样本平均值,或者是逻辑回归的结果。显然的下一个问题是:我们如何选择使用哪个程序\(\delta\)?我们将通过量化每个\(\delta\)的“好”程度来决定,然后尝试选择“最好”的一个。

“好”是一个非常抽象的概念:为了量化它,我们需要一个量化的指标来衡量我们的程序\(\delta\)的优劣(或者更精确地说,是好坏)。我们将称之为损失函数。在符号上,我们将用\(\ell(\theta, \delta(x))\)来表示与结果\(\delta(x)\)相关的损失,如果真实值是\(\theta\)。为了总结:

\(x\) 的概率分布 $\begin{align} \text{变量/符号} & \quad \text{它代表什么} \ \hline \theta & \quad \text{未知量/感兴趣的数量:参数} \ x & \quad \text{观测数据} \ p(x|\theta) & \quad \text{数据 \(x\) 的概率分布(依赖于 \(\theta\))} \ \delta(x) & \quad \text{从 \(x\) 计算出的决策或结果,通常是 \(\theta\) 的估计} \ \ell(\delta(x), \theta) & \quad \text{输出 \(\delta(x)\) 和真实参数 \(\theta\) 的损失(坏度)} \end{align}$ (1)

示例

这是一个非常抽象的定义:让我们通过几个例子使其更加具体。

二元决策:0-1 损失

在我们的第一个例子中,我们将回到我们的二元决策环境。在这种情况下:

  • 我们未知的参数 \(\theta\) 是二元的,并且对应于现实,我们一直称之为 \(R\)

  • 我们的数据 \(x\) 是我们用来计算 p 值的任何数据。

  • 决策 \(\delta\) 是一个二元决策,我们一直称之为 \(D\)

D = \(\delta(x)\) = 0 D = \(\delta(x)\) = 1
\(R=\theta=0\) TN 损失 FP 损失
\(R=\theta=1\) FN 损失 TP 损失

这里有一些具体的例子,以及每个这些量代表什么:

示例 未知 \(\theta\) / \(R\) 数据 \(x\) 决策 \(\delta\) / \(D\)
疾病检测 一个人是否有疾病 收集的临床数据(血液样本、生命体征等) 我们是否应该给这个人治疗这种疾病?
寻找油井 某个区域地下是否存在石油 地震传感器读数等 我们是否应该在这一点上钻油井?
产品推荐 用户是否会购买此产品? 用户行为,对类似产品的兴趣等 我们是否应该向用户推荐此产品?

注意,我们并没有真正讨论很多关于\(p(x|\theta)\)p(x∣θ),因为我们一直在直接使用\(\delta(x)\)δ(x):我们将在下一章中进一步讨论这个问题。

我们的目标函数将取决于我们正在解决的问题。由于在这个案例中,输入(\(\theta\)/\(R\)\(\delta\)/\(D\))都是二进制的,我们可以将损失写成类似于我们之前见过的 2x2 表格。如果两种错误(误报和漏报)同样糟糕,我们可以使用最简单的损失函数,即0-1 损失

\(\ell(\delta(x), \theta) = \begin{cases} 0 & \text{if }\theta = \delta(x) \\ 1 & \text{if }\theta \neq \delta(x) \end{cases}\) ​(2)

练习:假设我们有一个情况,误报比漏报糟糕五倍。你会如何编写损失函数?

连续决策:\(\ell_2\)ℓ2​损失

现在,假设我们的参数\(\theta\)是连续的,而\(\delta(x)\)是我们从数据中对参数的估计。为了使事情更具体,\(\theta\)可以是人群的平均身高,而\(x\) 可以是来自该人群的随机样本中的人的身高。在这种情况下,我们的损失不应该只是对与错:我们应该使用一个当我们的估计接近时更小,当我们的估计远离时更大的损失函数。

你可能之前已经见过一次:平方误差损失,也称为\(\ell_2\)ℓ2 损失

\(\ell(\delta(x), \theta) = \big(\delta(x) - \theta\big)²\) (3)

我们将在稍后对\(\ell_2\) 损失进行分析。

练习:假设我们有一个情况,高估比低估更糟糕。你将如何构建这个问题的损失函数?

已知和未知

在这一点上,你可能正在想:如果 \(\theta\) 未知,我们如何计算损失函数呢?重要的是要记住,当我们对真实数据应用 \(\delta(x)\) 时,我们并不知道 \(\theta\)。但此时,我们正在构建一些工具来帮助我们分析不同的过程。换句话说,我们正在努力达到一个可以回答“哪些过程最有可能给出接近 \(\theta\) 的估计?”这样的问题的地步。

固定与随机:在贝叶斯和经验主义方法中寻找平均损失

损失函数是 \(\delta(x)\) 的函数,它是特定数据 \(x\) 的过程结果,以及特定参数 \(\theta\)。这对我们来说并不特别有用:我们希望了解损失“平均”情况下的表现。但为了计算任何类型的平均值,我们需要决定什么是随机的,什么是固定的。这是一个重要的分岔点:我们可以选择贝叶斯路线或经验主义路线。让我们看看如果我们尝试每一条路线会发生什么:

经验主义损失分析

在频率派的世界里,我们假设我们的未知 \(\theta\)固定的。数据是唯一的随机部分。因此,我们将查看数据不同可能性下的平均值 \(x\)。由于数据来自分布 \(p(x|\theta)\),它依赖于 \(\theta\),我们应该预期这种“平均”会产生依赖于 \(\theta\) 的东西。我们将我们的平均值称为 频率派风险

$\begin{align} R(\theta) &= E_{x|\theta}\left[\ell(\delta(x), \theta)\right] \ &= \begin{cases} \displaystyle \sum_x \ell(\delta(x), \theta) p(x|\theta) & \text{if \(x\) discrete} \ \displaystyle \int \ell(\delta(x), \theta) p(x|\theta) dx & \text{if \(x\) continuous} \end{cases} \end{align}$ (4)

经验风险是参数 \(\theta\) 的函数。它告诉我们:对于 \(\theta\) 的特定值,如果我们对所有可能的数据集进行平均,那么程序 \(\delta\) 会做得有多糟糕?

贝叶斯损失分析

在贝叶斯世界中,我们假设我们的未知参数 \(\theta\)随机的。由于我们观察到了特定的数据集 \(x\),我们将对 \(\theta\) 中的随机性比对 \(x\) 中的随机性更感兴趣。因此,我们将基于我们获得的具体数据集进行条件化,并查看未知参数 \(\theta\) 在不同可能性下的平均值。我们将我们的平均值称为贝叶斯后验风险

$\begin{align} \rho(x) &= E_{\theta|x}\left[\ell(\delta(x), \theta)\right] \ &= \begin{cases} \displaystyle \sum_\theta \ell(\delta(x), \theta) p(\theta|x) & \text{if \(\theta\) discrete} \ \displaystyle \int \ell(\delta(x), \theta) p(\theta|x) d\theta & \text{if \(\theta\) continuous} \end{cases} \end{align}$ ​​(5)

贝叶斯风险是 \(x\) 的函数。它告诉我们:在观察到特定数据集 \(x\) 的条件下,程序 \(\delta\) 在所有可能的参数 \(\theta\) 值上的平均表现如何?

比较频率主义和贝叶斯风险

在操作上,这两者看起来有些相似:我们是在某个条件概率分布上平均损失。但从概念上讲,它们非常不同:频率主义风险固定参数,并对所有数据进行平均;贝叶斯后验风险固定数据,并对所有参数进行平均。

例子:对于 \(\ell_2\) 损失和偏差-方差权衡

让我们通过一个例子来计算使用 \(\ell_2\) 损失的频率主义风险。我们会发现结果可以给我们一些重要的见解。

\(\begin{align} R(\theta) &= E_{x|\theta}\left[\ell(\delta(x), \theta)\right] \\ &= E_{x|\theta}\Big[\big(\delta(x) - \theta\big)²\Big] \\ \end{align}\) (6)

为了让数学表达式在后续的计算中成立,我们将添加并减去项 \(E_{x|\theta}[\delta(x)]\). 在我们计算出结果之前,让我们思考一下这个项的含义。它是过程 \(\delta\) 的平均值:换句话说,对于特定的 \(\theta\),它告诉我们应该期望得到什么样的 \(\delta(x)\),这是在不同可能的 \(x\) 值上的平均值。

\(\begin{align} R(θ) &= E_{x|\theta}\Big[\big(\delta(x) - \theta\big)²\Big] \\ &= E_{x|\theta}\Big[\big( \delta(x) \overbrace{- E_{x|\theta}[\delta(x)] + E_{x|\theta}[\delta(x)]}^{=0} - \theta \big)²\Big] \\ &= E_{x|\theta}\Big[\big( \underbrace{\delta(x) - E_{x|\theta}[\delta(x)]}_{\text{预测值与平均预测值之差}} + \underbrace{E_{x|\theta}[\delta(x)] - \theta}_{\text{平均预测值与真实值之差}} \big)²\Big] \\ \end{align}\)

为了让数学表达式更容易阅读,我们将写作 \(\delta = \delta(x)\)\(\bar{\delta} = E_{x|\theta}[\delta(x)]\)

\(\begin{align} R(θ) &= E_{x|θ}\Big[\big( \delta(x) - E_{x|θ}[\delta(x)] + E_{x|θ}[\delta(x)] - θ \big)²\Big] \\ &= E_{x|θ}\Big[\big( \delta - \bar{\delta} + \bar{\delta} - θ \big)²\Big] \\ &= E_{x|θ}\Big[ \big(\delta - \bar{\delta}\big)² + \underbrace{2\big(\delta - \bar{\delta}\big)\big(\bar{\delta} - \theta\big)}_{=0} + \big(\bar{\delta} - \theta\big)² \Big] \\ &= E_{x|θ}\Big[\big(\delta - \bar{\delta}\big)²\Big] + E_{x|θ}\Big[\big(\bar{\delta} - \theta\big)²\Big] \\ &= \underbrace{E_{x|θ}\Big[\big(\delta - \bar{\delta}\big)²\Big]}_{\text{方差 of }\delta(x)} + \big(\underbrace{\bar{\delta} - \theta}_{\text{偏差 of }\delta(x))}\big)² \\ \end{align}\) ​​(8)

我们已经证明,对于\(\ell_2\)​损失,频率主义风险是两个项的和,称为方差偏差的平方。

方差\(E_{x|\theta}\Big[\big(\delta(x) - E_{x|\theta}[\delta(x)]\big)²\Big]\),回答了这样一个问题:当数据变化时,δ与它的平均值会有多远?一般来说,如果你的程序δ对数据的变动非常敏感,你的方差将会很高。

偏差\(E_{x|\theta}[\delta(x)] - \theta\),回答了这样一个问题:δ的平均值与真实参数θ的距离有多远?一般来说,如果你的程序δ很好地捕捉了预测θ的复杂性,你的偏差将会很低。

当试图降低风险(平均损失)时,大多数方法试图降低方差和/或偏差。许多用于估计和预测的方法试图处理方差和偏差之间的权衡:理想情况下,我们希望两者都尽可能小,但通常我们需要接受其中之一稍微大一些,以换取另一个的大幅减少。

贝叶斯风险

上面的两个风险是通过分别对数据\(x\) 或参数\(\theta\)取期望得到的。如果我们同时对两者取期望呢?贝叶斯风险正是这样:

\(\begin{align*} R(\delta) &= E_{x, \theta} [\ell(\delta(x), \theta)] \\ &= E_\theta [R(\theta)] \\ &= E_x [R(x)] \end{align*}\) (9)

其中最后两个不等式由 Fubini 定理得出(即,我们可以以任意顺序对期望值进行积分并得到相同的结果)。贝叶斯风险是一个单一数字,它总结了\(\delta\)的过程。这个名字有些误导:它实际上既不是贝叶斯也不是频率主义的。

第二章:贝叶斯推理

原文:data102.org/ds-102-book/content/chapters/02/intro/

本章仍在进行中。

本章将涵盖贝叶斯推理的基础知识。

参数估计和贝叶斯推理基础

原文:data102.org/ds-102-book/content/chapters/02/parameter-estimation

笔记本单元

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from IPython.display import YouTubeVideo
sns.set()
%matplotlib inline
from scipy import stats

您可能发现阅读数据 140 教科书的第二十章(链接)和第二十一章(链接)会有所帮助,这两章提供了类似材料的另一种阐述,但它们不是本章的先决条件。

我们将从一个简单的例子开始,这个例子突出了贝叶斯和频率主义方法之间的关键差异。回忆我们的设置:我们观察了一些数据 \(x_1, \ldots, x_n\)​,我们将使用它们来估计一些感兴趣的参数 \(\theta\)。在本节和接下来的几节中,我们将关注两个简单的例子,以帮助我们建立直觉:

  • 估计产品质量\(x_1, \ldots, x_n\)​ 是在线销售产品的评价(👍/1 或 👎/0),而 \(\theta\) 是介于 0 和 1 之间的一个数字,代表某人留下好评的概率。直观上,\(\theta\) 代表我们对产品质量的估计。

  • 估计平均身高\(x_1, \ldots, x_n\)​ 是随机样本中 \(n\) 个个体的身高,而 \(\theta\) 是该群体中个体的平均身高。

我们从第一个例子开始。

直觉和计算

假设你在寻找一款新的微波炉。你找到了两个选择,它们的价格大致相同:

  • 微波 A 有三个正面评价,没有负面评价

  • 微波 B 有 19 个正面评价和 1 个负面评价

你应该买哪一个?

直观上,并不一定有正确或错误的答案。但在这种情况下,大多数购物者可能会更倾向于购买微波 B。看到更多的数据点,即使有一个是负面的,也能让我们确信我们在评价中看到的结果是一致的。

我们将从频率派和贝叶斯派的角度计算性地处理这个问题,并看看每个方法如何很好地结合上述直觉。

当制定一个统计模型(无论选择贝叶斯派还是频率派方法)时,选择一个模型非常重要,以确保我们估计的参数满足以下标准:

  • 参数应该反映我们感兴趣估计的潜在数量。在这种情况下,我们将使用 \(\theta\) 来量化每款微波炉的“好”程度,然后我们可以简单地选择“更好”的微波炉(即具有更高 \(\theta\) 值的微波炉)。我们上面的选择,留下正面评价的概率,满足这个条件,因为更好的产品应该比更差的产品更有可能获得正面评价。

  • 参数应该清楚地符合我们的概率模型:换句话说,我们的概率模型应该将观测数据与参数联系起来。我们很快就会看到这是如何工作的。

我们的目标将是根据观测数据 \(x_1, \ldots, x_n\) ​来估计和得出关于参数 \(\theta\)的结论。

# NO CODE

# VIDEO
YouTubeVideo('P501SLnYtIE')

频率派参数估计:最大似然

在我们实际上进行任何参数估计之前,我们首先需要指定相关的概率分布。回想一下,在频率派设定中,我们假设数据(我们的二元评论,\(x_1, \ldots, x_n\)​)是随机的,但参数(生成正面评论的概率 \(\theta\))是固定且未知的。

在频率派设定中建立概率模型,我们需要的只是一个似然函数 \(p(x_i|\theta)\)。这告诉我们,在给定的\(\theta\)值下,数据点出现的可能性。由于我们的数据是二元的,而参数是介于 0 和 1 之间的一个数字,一个自然的选择是伯努利分布(有关伯努利分布的更多信息,请参阅数据 140 教科书维基百科):

\(x_i | \theta \sim \mathrm{Bernoulli}(\theta)\)xi​∣θ∼Bernoulli(θ)(1)

这种符号表示“在\(\theta\)的条件下,\(x_i\)​遵循参数为\(\theta\)的伯努利分布”。换句话说:

\(p(x_i|\theta) = \begin{cases} \theta & \text{ if }x_i = 1 \\ 1-\theta & \text{ if }x_i = 0 \end{cases}\) ​(2)

from IPython.display import YouTubeVideo
YouTubeVideo('ur6V1UyKChY')

加载中...

虽然基础概率对你来说应该很熟悉,但我们已经进行了一些重要的概念跳跃:

  • 这种符号表示一个参数族分布:不同的\(\theta\)值会导致\(x_i\)​的不同分布,但它们都属于伯努利分布族。

  • 由于我们在这个过程中旨在通过观察到的数据\(x_i\)​找到“良好”的\(\theta\)值,我们可以将其视为一个关于\(\theta\)的函数:对于任何我们考虑的\(\theta\)值,我们可以将我们的数据代入并得到如果该值是真实的,观察它的概率。

在频率主义设定中,估计\(\theta\)的最简单方法是使用最大似然估计(MLE)并选择使\(\theta\)最大化的值:换句话说,我们将选择使我们的数据看起来尽可能可能性的\(\theta\)的值。

我们将这样进行:

  1. 我们将写出所有数据点的似然,\(x_1, \ldots, x_n\)(我们已经在上面为单个点做了这件事)

  2. 我们将使用对数似然而不是似然。这会使计算稍微容易一些。为什么这是可以接受的?因为\(\log\)对数是一个单调递增函数,所以最大化\(\log(p(x|\theta))\)的相同\(\theta\)值也最大化了\(p(x|\theta)\)

  3. 为了找到\(\theta\)的最佳值,我们将对对数似然相对于\(\theta\)的导数进行求导,将其设置为 0,并求解。

  4. 我们将通过计算二阶导数并确认其为负值,以及检查边界来确认第 3 步给出了最大值(而不是最小值)。

1. 编写似然

我们所有数据点的似然是:

\(p(x_1, \ldots, x_n | \theta)\) (3)

我们假设我们的样本在参数\(\theta\)的条件下是条件独立同分布的(i.i.d.)。在我们的例子中,这意味着如果我们知道产品的质量如何,那么知道一条评论并不会告诉我们关于其他评论的任何信息。这意味着我们可以简化我们的似然函数:

\(\begin{align} p(x_1, \ldots, x_n|\theta) &= \prod_{i=1}^n p(x_i|\theta) \end{align}\) (4)

接下来,我们需要找出一种更便于数学表达的方式来书写单个点的似然函数,即\(p(x_i|\theta)\),比我们在上一节中写的那一种版本更方便。这里有一个表达相同意思的替代方法:

\(p(x_i|\theta) = \theta^{x_i}(1-\theta)^{1-x_i}\) ​(5)

通过将\(x_i=1\)\(x_i=0\) 代入,来让自己相信这是相同的。

现在我们有了这种便于书写的表达方式,当我们把它们全部相乘时,指数会相加:

\(\begin{align} p(x_1, \ldots, x_n|\theta) &= \prod_{i=1}^n p(x_i|\theta) \\ &= \prod_{i=1}^n \theta^{x_i}(1-\theta)^{1-x_i} \\ &= \theta^{\left[\sum_i x_i\right]}(1-\theta)^{\left[\sum_i (1-x_i)\right]} \\ \end{align}\) (6)

第一个指数简单表示正面评价的数量,第二个指数简单表示负面评价的数量。为了简化我们的表达式,我们将 \(k\) 定义为正面评价的数量 \(\left(k = \sum_i x_i\right)\),因此表达式变为:

\(\begin{align} p(x_1, \ldots, x_n|\theta) &= \theta^{k}(1-\theta)^{n-k} \\ \end{align}\) ​(7)

2. 取对数

\(\begin{align} \log p(x_1, \ldots, x_n|\theta) &= k\log\theta + (n-k)\log(1-\theta) \end{align}\) ​​(8)

3. 最大化

\(\begin{align} \frac{d}{d\theta} \log p(x_1, \ldots, x_n|\theta) &= 0 \\ \frac{d}{d\theta} \left[k\log\theta + (n-k)\log(1-\theta)\right] &= 0 \\ \frac{k}{\theta} - \frac{n-k}{1-\theta} &= 0 \\ \theta &= \frac{k}{n} \end{align}\) (9)

最后,我们得到了伯努利似然的最大似然估计:它仅仅是总评论中正面评论的比例。这是一个非常直观的结果!

在这一点上,我们可以看到为什么我们选择使用对数似然:相对于从步骤 1 的表达式求导,从步骤 2 的表达式求导更容易(如果你需要,可以通过计算导数来让自己相信这是真的)。

4. 验证最大值

\(\begin{align} \frac{d²}{d\theta²} \log p(x_1, \ldots, x_n|\theta) &= \frac{d}{d\theta} \left[\frac{k}{\theta} - \frac{n-k}{1-\theta}\right]\\ &= -\frac{k}{\theta²} - \frac{n-k}{(1-\theta)²}\\ &< 0, \end{align}\) (10)

其中最后一个陈述是正确的,因为\(n\)\(k\),和\(\theta\)都是正数,并且\(n \geq k\)

在许多情况下,检查边界值也可能是必要的,特别是如果似然函数在可能的θ值范围内单调递增或递减时。

应用最大似然估计

让我们回到我们比较微波 A 和 B 的例子。如果我们对每个都应用最大似然估计,我们得到\(\hat{\theta}_{A, \text{MLE}} = 3/3 = 1\)\(\hat{\theta}_{B,\text{MLE}} = 19/20 = 0.95\):换句话说,最大似然估计告诉我们微波 A 是更好的选择。

换句话说,使“只有 3 条正面评价”尽可能可能发生的\(\theta\)的值是\(\theta=1\)。同样,使“19 条正面评价和 1 条负面评价”尽可能可能发生的\(\theta\)的值是\(\theta=0.95\)

# NO CODE

# VIDEO
from IPython.display import YouTubeVideo
YouTubeVideo('W5Dq3VReuuQ') 

另一种方法:贝叶斯推理

在贝叶斯框架下,我们将假设未知参数\(\theta\)是一个随机变量。现在,我们不再寻找单个估计值,而是考虑在观察数据\(x\) 之后\(\theta\)的概率分布。这是条件分布\(p(\theta|x)\),我们称之为后验分布。在本章的其余部分,我们将学习如何计算和解释这个分布。它代表了我们看到数据\(x\) 后对\(\theta\)中的随机性的理解。我们将使用贝叶斯定理来计算它:

\(\begin{align} \underbrace{p(\theta|x)}_{\text{后验}} &= \frac{\overbrace{p(x|\theta)}^{\text{似然}}\,\, \overbrace{p(\theta)}^{\text{先验}}}{p(x)} \end{align}\) ​​(11)

在上一节中,当我们建立概率模型时,我们选择了一个描述我们的参数 \(\theta\) 和我们的数据 \(x\) 之间关系的似然函数。在贝叶斯公式中,我们还需要选择另一件事:一个 先验 \(p(\theta)\),它代表在我们看到任何数据之前对 \(\theta\) 的信念。通常,我们会根据领域知识或计算便利性来选择先验:我们将在下一节中看到一些这方面的例子。

注意,我们可以将这里的分母视为常数,因为 \(p(\theta|x)\)p(θ∣x) 是关于 \(\theta\) 的一个分布,而分母 \(p(x)\) 不依赖于 \(\theta\)。因此,我们通常将后验分布写成以下形式(回想一下,符号 \(\propto\) 表示“成比例于”):

\(\begin{align} p(\theta|x) &\propto p(x|\theta)p(\theta) \end{align}\) (12)

  1. 一旦我们得到了后验分布 \(p(\theta|x)\),我们如何使用它来选择一个单一的值 \(\theta\) 作为我们的估计值?

我们将在下一节回答这两个问题。

YouTubeVideo('p1lLJg98j-8')

选择先验分布:Beta 分布\(p(\theta)\)p(θ),它代表我们在看到任何数据之前对未知产品质量的信念\(\theta\),我们将选择贝塔分布。这并不是我们唯一可以选择的分布,但它恰好是一个特别方便的选择:我们稍后会看到原因。贝塔分布有两个参数,\(\alpha\)\(\beta\)。它定义为:

\(p(\theta) \propto \theta^{\alpha-1} (1-\theta)^{\beta-1}\) (13)

归一化常数相当复杂,但使用贝塔分布我们不需要知道它是什么。由于这是一个研究得很好的分布,我们可以通过查阅找到我们需要的任何关于它的信息。试着自己做一下:不进行任何计算,贝塔分布随机变量的期望值是多少?众数是什么?

归一化贝塔分布(可选)

我们如何计算这个分布的归一化常数呢?我们知道上面的公式缺少一个比例常数,我们将称之为\(Z\)

\(p(\theta) = \frac{1}{Z} \theta^{\alpha-1} (1-\theta)^{\beta-1}\) (14)

我们需要求解\(Z\)。作为一个概率密度函数(PDF),我们知道分布\(p(\theta)\)应该积分到 1。因此,

\(\begin{align*} \int_0¹ \frac{1}{Z} \theta^{\alpha-1} (1-\theta)^{\beta-1} \, d\theta &= 1 \\ \int_0¹ \theta^{\alpha-1} (1-\theta)^{\beta-1} \, d\theta &= Z \end{align*}\) ​(15)

这个积分难以计算,但我们查阅得知结果是称为贝塔函数的东西。关于这个函数的细节超出了本课程的范围,但它与伽马函数(将阶乘函数扩展到非整数值)有关。

换句话说,无论\(\alpha\)\(\beta\)的值是什么,分母总是具有相同的形式:一个贝塔函数。这就是为什么我们通常忽略它,只写出比例关系。

还要注意的是,\(Z\) 不依赖于 \(\theta\),但它依赖于参数 \(\alpha\)\(\beta\)

贝塔分布的直观理解

在我们计算后验分布之前,让我们先对贝塔分布的工作原理有一些直观的理解。维基百科页面(Wikipedia page)上有一些关于不同 \(\alpha\)\(\beta\) 值的贝塔分布样式的有用示例。让我们看看其中的一些示例。回想一下,我们可以使用 scipy.stats 中的分布来计算和可视化概率密度函数(PDF):

thetas = np.linspace(0, 1, 500)
beta_11 = stats.beta.pdf(thetas, 1, 1)
beta_72 = stats.beta.pdf(thetas, 7, 2)
beta_34 = stats.beta.pdf(thetas, 3, 4)

f, ax = plt.subplots(1, 1, figsize=(5, 3))
ax.plot(thetas, beta_11, label=r'$\alpha = 1, \beta = 1$')
ax.plot(thetas, beta_34, label=r'$\alpha = 3, \beta = 4$')
ax.plot(thetas, beta_72, label=r'$\alpha = 7, \beta = 2$')
ax.legend();

<Figure size 360x216 with 1 Axes>

我们可以看到,参数的不同值会导致贝塔分布形状的不同。特别是,当 \(\alpha\) 大于 \(\beta\) 时,较大的 \(\theta\) 值更有可能,反之亦然。我们还可以看到,Beta\((1, 1)\) 与在 \([0, 1]\) 上的均匀分布相同。尝试用不同的值进行实验:当 \(\alpha\)\(\beta\) 变大时,会发生什么变化?

YouTubeVideo('TAxSkVmFfg0')

计算后验分布

现在我们已经设置了先验分布,我们可以计算后验分布:

\(\begin{align} p(\theta|x) &\propto p(x|\theta)p(\theta) \\ &\propto \Bigg[\theta^{\left[\sum_i x_i\right]}(1-\theta)^{\left[\sum_i (1-x_i)\right]}\Bigg]\Big[\theta^{\alpha-1} (1-\theta)^{\beta-1}\Big] \\ &\propto \theta^{\alpha + \left[\sum_i x_i\right] - 1}(1-\theta)^{\beta + \left[\sum_i (1-x_i)\right] - 1} \end{align}\) ​​(16)

我们可以看出这也是一个贝塔分布,因为它看起来像 \(\theta^{\text{stuff}-1}(1-\theta)^{\text{other stuff}-1}\). 它有参数 \(\alpha + \sum_i x_i\)​ 和 \(\beta + \sum_i (1-x_i)\),因此我们可以将后验写成

\(\theta|x \sim \mathrm{Beta}\Bigg(\alpha + \sum_i x_i\,,\,\, \beta + \sum_i (1-x_i)\Bigg)\) (17)

通过这个观察,我们避免了需要计算分母 \(p(x)\)!

这是因为贝塔分布是伯努利分布的共轭先验。这意味着每当我们有伯努利似然时,如果我们从贝塔家族中选择一个先验,那么后验分布也将属于贝塔家族。

YouTubeVideo('ogpPyznMcus')

示例:先验和后验贝塔分布

笔记本单元

FIGURE_SIZE = (4.5, 3.5)
#def plot_beta_prior_and_posterior(r, s, m, y, show_map=False, show_lmse=False):
def plot_beta_prior_and_posterior(alpha, beta, pos_obs, neg_obs, show_map=False, show_lmse=False, ax=None):   
    x = np.linspace(0, 1, 100)
    prior = stats.beta.pdf(x, alpha, beta)

    alpha_new = alpha + pos_obs
    beta_new = beta + neg_obs
    posterior = stats.beta.pdf(x, alpha_new, beta_new)

    # You never have to memorize these: when making this notebook,
    # I found them on the wikipedia page for the Beta distribution:
    # https://en.wikipedia.org/wiki/Beta_distribution

    if show_lmse:
        x_lmse = (alpha_new)/(alpha_new + beta_new)
    else:
        x_lmse = None

    if show_map:
        x_map = (alpha_new - 1) / (alpha_new + beta_new - 2)
    else:
        x_map = None
    plot_prior_posterior(x, prior, posterior, (-0.02, 1.02),
                         prior_label=f'Prior: Beta({alpha}, {beta})',
                         posterior_label=f'Posterior: Beta({alpha_new}, {beta_new})',
                         x_map=x_map, x_lmse=x_lmse, ax=ax)

def plot_prior_posterior(x, prior, posterior, xlim, 
                         prior_label, posterior_label,
                         x_map=None, x_lmse=None, ax=None):

    if ax is None:
        f, ax = plt.subplots(1, 1, figsize=FIGURE_SIZE, dpi=80)
    ax.plot(x, prior, ls=':', lw=2.5, label = prior_label)
    ax.plot(x, posterior, lw=2.5, label = posterior_label)
    if x_map is not None:
        map_index = np.argmin(np.abs(x - x_map))
        posterior_map = posterior[map_index]
        label = f'MAP estimate: {x_map:0.2f}'
        ax.plot([x_map, x_map], [0, posterior_map], '--', lw=2.5, color='black', label=label)
    if x_lmse is not None:
        lmse_index = np.argmin(np.abs(x - x_lmse))
        posterior_lmse = posterior[lmse_index]
        label = f'LMSE estimate: {x_lmse:0.2f}'
        ax.plot([x_lmse, x_lmse], [0, posterior_lmse], '--', lw=1.5, color='red', label=label)
    ax.legend()
    ymax = max(max(prior[np.isfinite(prior)]), max(posterior[np.isfinite(posterior)]))
    ax.set_ylim(-0.3, ymax+0.3)
    ax.set_xlim(*xlim)
    ax.set_xlabel(r'$\theta/details>)
    ax.set_title(r'Prior $p(\theta)$ and posterior given observed data $x$: $p(\theta|x)/details>);

让我们可视化一些先验和后验 Beta 分布的例子。我们将使用plot_beta_prior_and_posterior函数,该函数接受先验参数(\(a\)\(b\))以及正负观察的数量(\(k\)\(n-k\)),并在同一张图上显示θ的后验分布。

我们从微波 A 开始,它有 3 条正面评论和 0 条负面评论。现在,我们将探索几种不同的选择并查看结果。稍后,我们将讨论哪种先验选择可能更适合这个问题。

首先,如果我们使用 Beta(1,1)先验,会发生什么?我们之前看到这等同于均匀先验。直观上,这个先验表示在观察任何评论之前,所有θ(在 0 和 1 之间)的值应该具有相同的可能性。

# Plots a beta posterior resulting from a Beta(1, 1) prior (the first two arguments) 
# and 3 positive observations and 0 negative observations (the second two arguments)
plot_beta_prior_and_posterior(1, 1, 3, 0)

<Figure size 360x280 with 1 Axes>

观察橙色密度,它将更高的概率分配给θ更大的值。如果我们使用不同的先验会怎样?我们将尝试 Beta(1,5)先验,这表示在观察任何评论之前,θ的较小值比较大值更有可能(即,我们在看到任何评论之前对产品质量持怀疑态度)。

plot_beta_prior_and_posterior(1, 5, 3, 0)

<Figure size 360x280 with 1 Axes>

改变先验分布极大地改变了后验分布。如果我们使用一个更加“怀疑”的先验会怎样?

plot_beta_prior_and_posterior(1, 10, 3, 0)

<Figure size 360x280 with 1 Axes>

我们可以看到,对于微波 A,我们选择的先验对后验分布有显著的影响。那么对于微波 B 呢?

plot_beta_prior_and_posterior(1, 1, 19, 1)
plot_beta_prior_and_posterior(1, 5, 19, 1)
plot_beta_prior_and_posterior(1, 10, 19, 1)

<图大小 360x280,1 个坐标轴><图大小 360x280,1 个坐标轴><图大小 360x280,1 个坐标轴>

在这里,先验分布仍然对后验分布有影响:先验越“怀疑”,后验分布就越向左偏移。但是,先验分布的影响远小于微波 A 的情况。这说明了贝叶斯推理中的一个普遍趋势:数据越少,先验分布通常对后验分布的影响越强

YouTubeVideo('2ky2q4NldHM')

点估计

一旦我们有了后验分布,我们通常会想要回答一个问题:我们如何使用这个分布来得到一个关于 \(\theta\) 的单一估计?这个单一数字通常被称为点估计

我们将针对问题 1 开始提供两个答案:最大后验(MAP)估计最小/最小均方误差(MMSE/LMSE)估计

MAP 估计是最大化后验分布的估计。记住,后验分布代表了我们对于未知参数 \(\theta\) 的信念。直观地说,MAP 估计是根据这种信念最有可能的 \(\theta\) 值。对于一个 Beta\((\alpha, \beta)\) 分布,我们可以查找分布的最大值(众数),发现它是 \(\frac{\alpha-1}{\alpha+\beta-2}\)​。

可选练习:推导 Beta 分布的众数。提示:不要像之前那样对 \(\theta\) 求 Beta 密度的最大值,而是先取对数。

LMSE 估计是后验分布的均值。它之所以有这个名字,是因为我们可以证明后验分布的均值最小化了均方误差:

\(\begin{align} \text{argmin}_a E_{p(\theta|x)}\left[(a-\theta)²\right] = E_{p(\theta|x)}\left[\theta\right] \end{align}\)argmina​Ep(θ∣x)​[(a−θ)2]=Ep(θ∣x)​[θ](18\((\alpha, \beta)\)(α,β) 分布的随机变量的均值是 \(\frac{\alpha}{\alpha+\beta}\)α+βα​.

YouTubeVideo('mx-zCxomd-8')
YouTubeVideo('rmziJOMmlu4')

选择先验参数\((5, 1)\),这有利于(分配更高的概率给)更大的 \(\theta\) 值。在这种情况下:

  • 微波 A 的质量后验是 Beta\((8, 1)\),并且最大似然估计值为 1。

  • 微波 B 的质量后验是 Beta\((24, 2)\),并且最大似然估计值为 0.96。在这个先验下,微波 A 看起来更好。

但是,如果我们相信大多数产品在经过审查数据证明之前都是不好的,我们可能会选择 Beta\((1, 5)\) 先验,这有利于较小的 \(\theta\) 值。在这种情况下:

  • 微波 A 的质量后验分布为 Beta\((4, 5)\):如果我们计算最大似然估计,我们得到 0.43。

  • 微波 B 的质量后验分布为 Beta\((20, 6)\),最大似然估计值为 0.79。因此,在这个先验下,微波 B 看起来更好。

从这里,我们可以得出结论,我们选择先验概率往往会对我们的结果产生重大影响。因此,选择能够反映你对世界假设和信念的先验概率非常重要,并且在分享你的结果时,要明确地与他人沟通这些假设。

另一种尝试:非共轭先验

为了了解为什么共轭先验如此有用,让我们看看如果我们选择不同的先验会发生什么。

假设我们选择以下先验 \(p(\theta) = \frac{\pi}{2} \cos\left(\frac{\pi}{2} \theta\right), \, \theta \in [0, 1]\):

thetas = np.linspace(0, 1, 500)

f, ax = plt.subplots(1, 1, figsize=(5, 3), dpi=80)
ax.plot(thetas, np.pi/2 * np.cos(thetas * np.pi/2), label=r'$\pi/2\cos(\pi \theta/2)$')
ax.set_xlabel(r'$\theta$')
ax.set_ylabel(r'$p(\theta)$')
ax.legend();

<Figure size 400x240 with 1 Axes>

np.trapz(np.pi/2 * np.cos(thetas * np.pi/2), thetas)

0.9999991742330661

我们可以尝试像之前一样计算后验概率:

\(\begin{align} p(\theta|x) &\propto p(x|\theta)p(\theta) \\ &\propto \Bigg[\theta^{\left[\sum_i x_i\right]}(1-\theta)^{\left[\sum_i (1-x_i)\right]}\Bigg]\cos\left(\frac{\pi}{2}\theta\right) \end{align}\) ​​(19)

这个分布看起来要复杂得多:我们根本无法将其简化为已知的分布。此外,无法以封闭形式计算归一化常数:我们需要使用数值积分。

这使得对分布进行推理变得困难,也意味着如果我们想计算关于它的感兴趣量(例如,对于 LMSE/MAP 估计的后验的均值/众数),我们必须应用数值积分。

一个连续的例子:估计身高

假设现在我们感兴趣的参数 \(\theta\) 是人群的平均身高,我们的数据 \(x_1, \ldots, x_n\)​ 是样本中个体的身高。对于这些连续数据,二项分布不再适用。我们可以选择很多种方法,但为了简单起见,我们将使用正态分布(也称为高斯分布):

\(x_i|\theta \sim \mathcal{N}(\theta, \sigma²)\) (20)

经验主义估计:高斯似然函数的最大似然估计

我们可以像之前一样通过类似的过程来找到高斯似然函数的最大似然估计。因为归一化常数 \(\frac{1}{\sigma\sqrt{2\pi}}\)​ 不依赖于 \(\theta\)(我们的目标是优化 \(\theta\),所以我们现在先不考虑它)。

\(\begin{align} p(x_i|\theta) & \frac{1}{\sigma\sqrt{2\pi}} \exp\left\{\frac{1}{2\sigma²}(x_i-\theta)²\right\} \\ p(x_1, \ldots, x_n|\theta) &= \left(\frac{1}{\sigma\sqrt{2\pi}}\right)^n \exp\left\{\sum_{i=1}^n \frac{1}{2\sigma²}(x_i-\theta)²\right\} \end{align}\) (21)

The log-likelihood is:对数似然为:

\(\begin{align} \log p(x_1, \ldots, x_n|\theta) &= n\log\left(\frac{1}{\sigma\sqrt{2\pi}}\right) + \sum_{i=1}^n \frac{1}{2\sigma²}(x_i-\theta)² \end{align}\) ​​(22)

\(\theta\) 求导并求解是直接的:

\(\begin{align} \sum_{i=1}^n -\frac{1}{\sigma²}(x_i-\theta) &= 0 \\ \theta &= \frac{1}{n} \sum_i x_i \end{align}\) ​​​(23)

再次,最大似然估计(MLE)给了我们一个非常直观的答案:使数据最有可能的参数(总体高度平均值)就是样本高度的均值。注意,我们的答案并不依赖于 \(\sigma\)

练习:假设我们感兴趣的不是总体平均数,而是总体标准差。总体标准差的 MLE 是多少?

高度贝叶斯推断

现在假设我们采用贝叶斯方法,并将\(\theta\)视为随机变量。就像之前一样,我们感兴趣的是后验分布:

\(p(\theta|x_1, \ldots, x_n) = \frac{p(x_1, \ldots, x_n|\theta)p(\theta)}{p(x_1, \ldots, x_n)}\) ​(24)

由于我们感兴趣的参数也是一个连续变量,我们可以尝试使用正态分布作为先验分布:

\(\theta \sim \mathcal{N}(\mu_0, \sigma_0²)\) (25)

注意,这个先验分布的参数,\(\mu_0\)​ 和 \(\sigma_0²\)​,与似然函数中的参数不同!这意味着我们的参数 \(\theta\),即总体均值,遵循具有某些均值 \(\mu_0\)​ 和某些方差 \(\sigma_0²\)​ 的正态分布。

虽然代数运算稍微复杂一些,但我们可以证明正态分布是正态分布均值的共轭先验。换句话说,θ的后验分布也是正态分布。

\(\theta | x_1, \ldots x_n \sim \mathcal{N}\left( \frac{1}{\frac{1}{\sigma_0²} + \frac{n}{\sigma²}}\left(\frac{\mu_0}{\sigma_0²} + \frac{\sum_{i=1}^n x_i}{\sigma²}\right), \left(\frac{1}{\sigma_0²} + \frac{n}{\sigma²}\right)^{-1} \right)\) (26)

从这个公式中快速解读出含义并不像从贝塔后验更新中那样容易。让我们尝试可视化一些先验和后验,看看我们是否能建立一些直观的理解:

YouTubeVideo('R_iqK9iFC1s')

笔记本单元

# You don't need to understand how this function is implemented.

def plot_gaussian_prior_and_posterior(μ_0, σ_0, xs, σ, range_in_σs=3.5, show_map=False, show_lmse=False):
    """
    Plots prior and posterior Normal distribution

    Args:
        μ_0, σ_0: parameters (mean, SD) of the prior distribution
        xs: list or array of observations
        σ: SD of the likelihood
        range_in_σs: how many SDs away from the mean to show on the plot
        show_map: whether or not to show the MAP estimate as a vertical line
        show_lmse: whether or not to show the LMSE/MMSE estimate as a vertical line
    """
    n = len(xs)
    posterior_σ = 1/np.sqrt(1/(σ_0**2) + n/(σ**2))
    posterior_mean = (posterior_σ**2) * (μ_0/(σ_0**2) + np.sum(xs)/(σ**2))

    # Compute range for plot
    posterior_min = posterior_mean - (range_in_σs * posterior_σ)
    posterior_max = posterior_mean + (range_in_σs * posterior_σ)
    prior_min = μ_0 - (range_in_σs * σ)
    prior_max = μ_0 + (range_in_σs * σ)

    xmin = min(posterior_min, prior_min)
    xmax = max(posterior_max, prior_max)
    x = np.linspace(xmin, xmax, 100)
    if show_lmse:
        x_lmse = posterior_mean
    else:
        x_lmse = None

    if show_map:
        x_map = posterior_mean
    else:
        x_map = None

    prior = stats.norm.pdf(x, μ_0, σ_0)
    posterior = stats.norm.pdf(x, posterior_mean, posterior_σ)

    plot_prior_posterior(x, prior, posterior, (xmin, xmax), 'Prior', 'Posterior',
                         x_map=x_map, x_lmse=x_lmse) 
small_sample = [6*12, 6*12+1, 5*12+9]
larger_sample = [6*12, 6*12+1, 5*12+9] * 10
plot_gaussian_prior_and_posterior(5*12+6, 1, small_sample, 1)

<Figure size 360x280 with 1 Axes>

plot_gaussian_prior_and_posterior(5*12+6, 1, larger_sample, 1)

<Figure size 360x280 with 1 Axes>

plot_gaussian_prior_and_posterior(6*12, 1, small_sample, 1)

<Figure size 360x280 with 1 Axes>

plot_gaussian_prior_and_posterior(6*12, 1, larger_sample, 1)

<Figure size 360x280 with 1 Axes>

就像之前一样,我们可以观察到,当我们拥有较少数据时,先验参数的影响更强烈。我们还可以看到,随着数据量的增加,后验分布变得更加狭窄。这是另一个普遍的事实:观察到的数据越多,我们的后验分布就越狭窄。

可选练习:既然你已经看到了在正常先验 + 正态似然模型中先验参数的影响,现在请对上述后验分布中参数如何相互作用给出一个直观的解释,对于 \(p(\theta | x_1, \ldots x_n)\).

贝叶斯层次模型

原文:data102.org/ds-102-book/content/chapters/02/hierarchical-models

import numpy as np
import pandas as pd
from scipy import stats
from IPython.display import YouTubeVideo
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

到目前为止,我们已经看到,当我们拥有想要融入模型的先验领域知识时,贝叶斯方法会很有用。我们还看到,选择先验的影响在很大程度上取决于我们拥有多少数据:数据越少,我们的结论就越倾向于先验。

在许多情况下,我们可能没有那么多外部先验知识,我们希望依靠数据集中较大的部分来帮助弥补数据集中较小的部分。我们将通过一个研究 1980 年至 1989 年间美国肾癌死亡率的例子,将这个(非常)抽象的概念具体化。本节使用的数据以及建模和分析的灵感来源于贝叶斯数据分析第 47-51 页。数据的清理版本来自Robin Ryder。请注意,该数据集存在严重偏差:它只包含白人男性的信息。我们将在本节后面讨论这个问题。

我们将逐步介绍为这个更复杂的数据集建立模型的过程,并在此过程中看到贝叶斯模型的几个优势和视角。

示例:理解肾癌死亡风险

在开始建模之前,我们必须首先理解数据。我们将重点关注以下列:

  • state:美国州名

  • Location:以字符串形式表示的县和州

  • fips,它为每个县提供联邦信息处理标准代码:这是一个标准标识符,通常可用于连接包含县级信息的数据集。

  • dcdc.2:分别代表 1980-1984 年和 1985-1989 年间的肾癌死亡人数

  • poppop.2:分别代表 1980-1984 年和 1985-1989 年的人口

kc_full = pd.read_csv('kidney_cancer_1980.csv', skiprows=4)
# There are many other interesting columns, but we'll focus on these:
kc = kc_full.loc[:, ['state', 'Location', 'dc', 'dc.2', 'pop', 'pop.2']]
kc.head()

加载中...

为简化起见,我们将分析重点放在 1980-1984 年。我们的目标是了解哪些县的人群面临最高的肾癌死亡风险。这有助于指导公共卫生行动,或揭示应调查和补救的潜在致癌物信息(例如,靠近化工厂等)。

kc['rate_nopool'] = kc['dc'] / kc['pop']
sns.histplot(kc, x='rate_nopool')

<Axes: xlabel='rate_naive', ylabel='Count'><Figure size 640x480 with 1 Axes>

由此可见,大多数县的发病率在十万分之一(0.00001)到万分之一(0.0001)之间,但有相当数量的县发病率为 0。

如果我们的目标仅仅是描述 1980-1984 年间每个县确切发生的情况,那么这个可视化可能就足够了。然而,我们的目标是以一种有助于指导公共卫生的方式,了解每个县的肾癌死亡风险。这促使我们定义感兴趣参数:对于县 \(i\),每个人死于肾癌的平均风险是多少?

直观上,我们可以看到,在人口非常少的县,这个数据集将无法很好地估计这个参数。例如,假设一个县只有 10 个人。由于我们关注的发病率接近万分之一,我们很可能观察到 0 例死亡,但这并不意味着这 10 个人的风险是 0!我们可以从经验上看到这一点:

sns.scatterplot(kc, x='pop', y='rate_nopool', alpha=0.1);

<Axes: xlabel='pop', ylabel='rate_naive'><Figure size 640x480 with 1 Axes>

对于较大的县,我们的估计值始终在 0.00002 到 0.00008 之间,但对于小县,估计值的变异性要大得多。

这里,我们面临的情况是拥有大量数据(实际上,我们的数据集包含了目标人群的完整普查数据),但我们试图量化的是一个相对罕见的现象。接下来,我们将看到贝叶斯推断如何帮助我们利用来自大县的更确定信息,来为小县做出良好的推断。

贝叶斯分层建模作为一种折中方案

我们可以将之前采用的方法视为一个极端:我们分别估计了每个县的死亡率,并且完全没有跨县汇集或共享任何信息。

在另一个极端,我们可以将所有县的数据全部合并在一起:

total_pop = kc['pop'].sum()
total_dc = kc['dc'].sum()
overall_rate = total_dc / total_pop
overall_rate

4.856485743364176e-05

虽然这提供了对总体发病率的一个良好估计,但它掩盖了各县之间的变异性,并阻碍了我们进行县级推断或找出最需要针对性干预的地区。

为了在这两个极端之间取得折中,我们将使用一个贝叶斯层次模型

  • 对于每个县,令\(\theta_i \in [0, 1]\)为一个随机变量,表示该县个体死于肾癌的风险。

  • 我们将为每个县使用相同的先验分布:一个 Beta\((a, b)\)分布(参数\(a\)\(b\) 的值尚未确定),但每个县都是一个独立的随机变量,并将拥有各自独立的后验分布。

  • \(y_i\)​为每个县的二项式随机变量,表示该县的肾癌死亡人数,其参数为\(n_i\)​(县人口数)和\(\theta_i\)​(县级风险)。

在符号表示上,我们可以将上述要点写作:

\(\begin{align*} \theta_i &\sim \mathrm{Beta}(a, b), & i \in \{1, 2, \ldots\} \\ y_i &\sim \mathrm{Binomial}(\theta_i, n_i), & i \in \{1, 2, \ldots, C\} \end{align*}\) ​(1)

我们在上一节中看到,如果随机变量序列 \(x_i\)​ 的似然函数服从伯努利分布 \((\theta)\),且 \(\theta\) 的先验分布服从 Beta 分布 \((a, b)\),那么 \(\theta\) 的后验分布就是 Beta 分布 \(\left(a + \sum x_i, b + n - \sum x_i\right)\)。我们还可以证明,如果随机变量 \(y\) 的似然函数服从二项分布 \((n, \theta)\),且 \(\theta\) 的先验分布服从 Beta 分布 \((a, b)\),那么 \(\theta\) 的后验分布就是 Beta 分布 \((a + y, b + n - y)\) 换句话说,正如 Beta 分布是伯努利似然函数的共轭先验一样,它也是二项似然函数的共轭先验。(它恰好也是几何似然函数的共轭先验!)

综合以上所有内容,我们现在可以计算后验分布。不同于单个参数 \(\theta\),我们现在有 \(C\) 个参数,\(\theta_1, \ldots, \theta_C\)​。后验分布是所有这些随机变量的联合分布,并以所有观测数据为条件:

\(\begin{align*} p(\theta_1, \ldots, \theta_C | y_1, \ldots, y_C) &\propto \overbrace{p(y_1, \ldots, y_c | \theta_1, \ldots, \theta_C)}^{\text{likelihood}}\, \overbrace{p(\theta_1, \ldots, \theta_C)}^{\text{prior}} \\ &= \prod_{i=1}^C \theta_i^{a+y_i-1}(1-\theta_i)^{b + n_i - y_i - 1} \end{align*}\) ​(2)

由此我们可以得出结论:我们可以为每个县独立计算后验概率:

\(\theta_i | y_i \sim \mathrm{Beta}(a + y_i, b + n_i - y_i)\) (3)

请注意,根据后验分布 \(p(\theta_1, \ldots, \theta_C | y_1, \ldots, y_C)\),每个县的参数分布都独立于其他所有县,因为联合分布可以写成边缘分布的乘积。但是,它们都共享相同的参数 \(a\)\(b\)

选择先验

正如前面的例子一样,我们现在面临一个至关重要的问题:如何选择 \(a\)\(b\)?我们将按照复杂度和精细度递增的顺序,考察四种方法:

  1. 无信息先验

  2. 有根据的猜测

  3. 经验贝叶斯

  4. 层次模型

无信息先验

如果我们对数据一无所知,可以选择一个无信息先验:换句话说,一个尽可能提供最少信息的先验。在这个例子中,我们可能会选择在 \([0, 1]\) 区间上的均匀分布(即 \(a = b = 1\))。虽然这避免了指定先验的问题,但它也并不是特别有用。如果我们使用这样一个弱先验,就等于说接近 0.8 的风险值与接近 0.0001 的风险值可能性相同:这显然与我们对该问题已有的认知不符。如果我们采用这种方法然后计算后验分布,会看到与之前无池化估计类似的结果。

有根据的猜测

一个好的先验分布应该编码我们对感兴趣量的认知。在本例中,我们所知的一切都来自我们在此所做的工作:上面我们估计的总比率约为万分之 4.9。如果我们选择 \(a = 5\)\(b = 9995\),那么先验的均值是 \(5 \times 10^{-5}\)。因此,一个可能的选择是 Beta\((5, 9995)\)

请注意,这在一定程度上是任意的:我们同样可以轻松地选择 \(a = 10\)\(b = 19990\),或者 \(a = 50\)\(b = 99950\),并得到相同的均值。

经验贝叶斯

经验贝叶斯是一种混合了贝叶斯学派和频率学派的方法,它使用频率学派的方法来寻找感兴趣量的猜测值或先验分布。在本例中,我们将使用频率学派的方法来更好地猜测 \(a\)\(b\)

具体来说,我们之前看到较小的县产生的朴素估计值变化太大。如果我们只看较大的县呢?我们将首先确定一个(有些任意的)大小县阈值。从上方的散点图可以看出,在达到一定规模后,非合并估计值似乎变化小得多。我们可以放大来确定一个阈值:

sns.scatterplot(kc, x='pop', y='rate_nopool', alpha=0.6);
plt.vlines(3e5, 0, 0.0004, color='black', ls='--')
plt.axis([0, 1e6, 0, 0.0004]);

<Figure size 640x480 with 1 Axes>

基于此,我们将使用 300,000 作为分界点(虚线),在估计先验分布参数时忽略小于此规模的县:

kc_large_counties = kc[kc['pop'] > 300000]
sns.histplot(kc_large_counties, x='rate_nopool')

<Axes: xlabel='rate_naive', ylabel='Count'><Figure size 640x480 with 1 Axes>

我们将使用这个经验分布(或者更准确地说,它的一个略微修改的版本)作为我们的先验分布:这就是经验贝叶斯中“经验”一词的由来。由于我们想使用贝塔先验,我们需要为这些数据拟合一个贝塔分布。换句话说,我们需要找到参数 \(a\)\(b\),它们能很好地拟合这一系列观测值(即上方的直方图)。

这正是我们在上一节中使用最大似然法解决的问题!我们将使用最大似然法为这些数据拟合一个贝塔分布。这次,我们不推导贝塔分布的最大似然估计量,而是使用 scipy 来为我们完成:

from scipy import stats
# The last two arguments tell scipy that it shouldn't try to shift or scale our beta distribution
a_hat, b_hat, loc_, scale_ = stats.beta.fit(kc_large_counties['rate_nopool'], floc=0, fscale=1)
print(a_hat, b_hat)
9.270228244533358 195581.04114706165 

使用这种方法,我们的先验分布将是 Beta\((9.27, 195581)\)。总结一下我们所做的:

  • 我们希望找到先验分布的参数 \(a\)\(b\),由于我们没有任何领域知识,我们将使用数据来帮助我们。

  • 我们确定信任来自大县的数据,但不信任来自小县的数据(因为它们的变化太大)。请注意,这是一个隐含的假设,可能会将我们引入歧途:例如,如果较大的县倾向于较低或较高的比率,那么使用它们来估计先验分布的参数就是个坏主意。

  • 我们仅查看了大县的朴素估计率,将贝塔分布拟合到这些数据上,然后将该贝塔分布用作我们的先验。

让我们比较一下这两种方法的结果。为了便于可视化,我们将查看每个县 LMSE 估计值的直方图:

a_guess, b_guess = 5, 19995  # educated guess
a_eb, b_eb = a_hat, b_hat  # empirical bayes

def compute_posterior(kc, prior_a, prior_b):
    posterior_a = prior_a + kc['dc']
    posterior_b = prior_b + (kc['pop'] - kc['dc'])
    return posterior_a, posterior_b
kc['posterior_a_guess'], kc['posterior_b_guess'] = compute_posterior(kc, a_guess, b_guess)
kc['posterior_a_eb'], kc['posterior_b_eb'] = compute_posterior(kc, a_eb, b_eb)

# For a Beta(a, b) distribution, the mean is a / (a + b)
kc['lmse_guess'] = kc['posterior_a_guess'] / (kc['posterior_a_guess'] + kc['posterior_b_guess'])
kc['lmse_eb'] = kc['posterior_a_eb'] / (kc['posterior_a_eb'] + kc['posterior_b_eb']) 
bins = np.linspace(0, 0.0003, 100)
sns.histplot(kc, x='lmse_eb', stat='density', label='Empirical Bayes prior', bins=bins)
sns.histplot(kc, x='lmse_guess',  stat='density', label='Educated guess prior', bins=np.linspace(0, 0.0003, 100))
plt.xlabel("LMSE estimate for each county's risk")
plt.legend()

<Figure size 640x480 with 1 Axes>

在两种情况下,我们都不再看到 0 的估计值。请注意,由于经验贝叶斯先验更强(即,由于\(a\)\(b\) 的值较大,变异性较小),后验估计值落在更窄的范围内。

完整层次模型

从哲学上讲,采用贝叶斯方法意味着我们决定将未知量视为随机变量,而不是固定参数。根据我们目前所见,在这个模型中,\(a\)\(b\) 是重要的未知量,对我们的推断结果有相当显著的影响。因此,我们现在尝试将它们视为随机变量。请注意,我们现在有两层未知的随机变量:\(\theta_i\)​,我们已经确定,是县级风险概率。\(a\)\(b\) 现在是美国层面的平均参数,反映了所有县的风险。这是层次模型的共同特征:一组变量与每个组(在本例中是县)的数据紧密相关,另一组变量代表更全局的信息(在本例中,是整个美国)。

但这引出了另一个建模问题:我们为\(a\)\(b\) 选择什么先验分布

遗憾的是,没有方便的分布可以作为贝塔分布参数的共轭先验。尽管我们可以使用多种可能的分布,但为了简单和方便起见,我们将使用均匀分布。然而,我们仍然面临如何选择该均匀分布参数的问题!既然我们知道 \(b >> a\),我们将做出一个有些随意的选择:设 \(a \sim \mathrm{Uniform}(0, 50)\)\(b \sim \mathrm{Uniform}(0, 300000)\)。然后我们可以写出完整的模型:

\(\begin{align*} a &\sim \mathrm{Uniform}(0, 50) \\ b &\sim \mathrm{Uniform}(0, 300000) \\ \theta_i &\sim \mathrm{Beta}(a, b), & i \in \{1, 2, \ldots\} \\ y_i &\sim \mathrm{Binomial}(\theta_i, n_i), & i \in \{1, 2, \ldots\} \end{align*}\) (4)

不幸的是,这里没有方便的方法来计算后验分布:我们必须借助近似技术。在下一节中,在我们开发了近似推断工具后,我们将回到这个模型。

肾癌数据集中的偏差

本节内容正在积极开发中,可能会发生变化。

如前所述,提供的数据集仅包含关于白人男性的数据。然而,肾癌风险并非均匀分布,正如美国国立卫生研究院国家癌症研究所的这份数据所示:

explorer-graph.png

由此,我们可以清楚地看到肾癌发病率存在种族差异。此外,我们观察到的任何县级效应也可能存在种族差异。我们该如何解决这个问题?不幸的是,在这种情况下,由于我们对数据收集方式了解不多,并且数据是很久以前收集的,我们无法改进我们的分析。

(可选)示例:用于系外行星宜居性的高斯混合模型

本节中的模型大致基于 Kulkarni 和 Desai 的论文使用高斯混合模型对系外行星进行分类中的工作。

在本节中,我们将探讨另一种层次模型,其中的分组不那么明确。这种模型通常被称为混合模型

系外行星数据

我们将使用一个系外行星数据集:即我们太阳系之外的行星。planets 数据框中的每一行都包含来自 NASA 的 517 颗系外行星的信息。我们将用它来探索哪些行星可能支持我们所知的生命。

该数据集包含以下列。

  • name: 系外行星的名称

  • orbital_period: 行星绕其恒星运行一周所需的天数

  • mass: 行星的质量,以地球质量的倍数表示(例如,第二颗行星,55 Cnc e,其质量是地球的 73.6%)

  • radius: 行星的半径,以地球半径的倍数表示(例如,第二颗行星,55 Cnc e,其半径几乎是地球的两倍)

  • star_temperature: 行星所绕恒星的温度,单位为开尔文

  • density: 行星的密度,单位为 g/cm³

请注意,在天文学中,更常见的是以木星而非地球为单位来测量质量和半径(木星的半径约为地球的 11 倍,质量约为地球的 317 倍),但我们使用基于地球的测量值,因为我们将以地球作为宜居性的标准。

planets = pd.read_csv('exoplanets.csv')
planets.head()

加载中...

我们将把分析重点放在半径上:

sns.histplot(data=planets, x='radius', stat='density', bins=20)

<Axes: xlabel='radius', ylabel='Density'><Figure size 640x480 with 1 Axes>

我们可以看到有两类行星:一类半径相对较小(更接近地球大小),另一类半径较大(更接近木星大小)。可以看出,如果我们尝试用一个单一的高斯分布来拟合这些数据,得到的拟合效果相当差:

radii = planets['radius']
radius_mean, radius_std = np.mean(radii), np.std(radii)

x = np.linspace(0, 25, 1000)

sns.histplot(data=planets, x='radius', stat='density', bins=20)
plt.plot(x, stats.norm.pdf(x, radius_mean, radius_std), color='tab:orange')

<Figure size 640x480 with 1 Axes>

从视觉上看,结果有些显而易见:数据明显分为两组,因此试图用一个高斯分布来拟合效果很差。相反,我们需要一个更复杂的模型,能够直接对这两组数据进行建模,并帮助我们分别推断每组数据的特性。

YouTubeVideo('lCbn8UrJ8-U')

(可选)高斯混合模型:为系外行星数据指定概率模型

文本即将发布:请观看视频

YouTubeVideo('KaD7uJeK_JI')

层次模型:一个更通用的定义

即将发布

图模型、概率分布与独立性

原文:data102.org/ds-102-book/content/chapters/02/graphical-models

import numpy as np
import pandas as pd
from scipy import stats
from IPython.display import YouTubeVideo

%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

图模型、概率分布与独立性

图模型

图模型 提供了一种使用视觉化方式表示贝叶斯层次模型的方法。这些模型有时也被称为贝叶斯网络,或 贝叶斯网

我们用节点(圆圈)表示每个随机变量,两个随机变量之间的有向边(箭头)表示子变量的分布以父变量为条件。绘制图模型时,我们通常从那些不依赖于任何其他变量的变量开始。这些变量通常是(但不总是)未观测到的感兴趣参数,例如本例中的 \(\theta\)。然后,我们继续为依赖于这些变量的每个变量绘制节点,依此类推。观测到的变量会以阴影表示。

我们将为之前章节中见过的三个示例绘制图模型:产品评价模型、肾癌模型和系外行星模型。

产品评价的图模型

在我们的产品评价模型中,有以下随机变量:

\(\begin{align} x_i | \theta &\sim \mathrm{Bernoulli}(\theta) \\ \theta &\sim \mathrm{Beta}(a, b) \end{align}\) ​​(1)

在这种情况下,这意味着我们首先为产品质量 \(\theta\) 建立一个节点,然后为每条评论 \(x_i\)​ 各建立一个节点,所有这些节点都依赖于 \(\theta\)。已观测到的评论节点 \(x_i\)​ 被涂上阴影,而隐藏(未观测到)的产品质量节点 \(\theta\) 则没有:

这种可视化表示通过清晰地展示每条评论 \(x_i\)​ 都依赖于质量 \(\theta\),向我们展示了模型的结构。但和之前一样,这个模型足够简单,我们早已知道这一点。接下来,我们将看一个更有趣的例子的图模型。

肾癌死亡风险的图模型

回顾肾癌死亡风险示例的完整层次模型:

\(\begin{align*} a &\sim \mathrm{Uniform}(0, 50) \\ b &\sim \mathrm{Uniform}(0, 300000) \\ \theta_i &\sim \mathrm{Beta}(a, b), & i \in \{1, 2, \ldots, C\} \\ y_i &\sim \mathrm{Binomial}(\theta_i, n_i), & i \in \{1, 2, \ldots, C\} \end{align*}\) (2)

  • \(y_i\)​ 表示第 \(i\) 个县的肾癌死亡人数(总人口为 \(n_i\)ni​)。

  • \(\theta_i\)​ 表示第 \(i\) 个县的肾癌死亡率。

  • \(a\)\(b\) 表示县级死亡率共享先验分布的参数。

为了绘制图模型,我们需要为每个随机变量绘制一个节点,并用箭头表示依赖关系。我们知道:

  • 我们需要为 \(a\) 设置一个节点,并为 \(b\) 设置一个节点。

  • 我们需要为每个 \(\theta_i\)​ 设置一个节点,并为每个 \(y_i\)yi​ 设置一个节点。

  • 每个 \(\theta_i\)​ 都依赖于 \(a\)\(b\)

  • 每个 \(y_i\)​ 都依赖于 \(\theta_i\)​ 和 \(n_i\)​。

  • 因为 \(n_i\)​ 是一个固定数字,我们将把它画成一个点。

因此,我们完整的图模型如下所示:

(可选)示例:系外行星模型的图模型

文本即将推出:请参阅视频

YouTubeVideo('e6CoEsLiMXc')

将图模型与概率分布关联起来

在我们之前绘制图模型时,我们为每个变量绘制一个节点,并从“顶部”开始,处理那些不依赖于任何其他变量的变量。然后我们逐步处理整个模型,最终到达观测变量。当查看图模型以推导模型中所有变量的联合分布时,我们遵循类似的过程。例如,在肾癌死亡率模型中,我们可以通过从根节点(即没有父节点的节点)开始,然后遍历它们的子节点,将联合分布写成一个乘积的形式。

因此,我们从 \(p(a)\)\(p(b)\) 开始,然后是 \(p(\theta_i | a, b)\)(对于 \(i \in \{1, \ldots, C\}\)),接着是 \(p(y_i | \theta_i)\)

\(p(a, b, \theta_1, \ldots, \theta_C, y_1, \ldots, y_C) = p(a)p(b) \prod_{i=1}^C p(\theta_i\mid a, b) p(y_i\mid\theta_i)\) (3)

以这种方式分解分布有助于我们理解和数学地证明图模型中的独立性与条件依赖关系,稍后我们将看到这一点。

YouTubeVideo('TzY3-EYwipk')

独立性与条件独立性

回顾:独立性与条件独立性

我们说两个随机变量 \(w\)\(v\)独立的,如果知道其中一个的值并不能告诉我们另一个的分布信息。在符号上,我们写作 \(w \perp\!\!\!\perp v\)。对于独立的随机变量 \(w\)\(v\),以下陈述均成立:

  • 如果 \(w\)\(v\) 相互独立(\(w \perp\!\!\!\perp v\)),那么联合分布 \(p(w, v)\)可以写成边缘分布的乘积:\(p(w, v) = p(w)p(v)\)

  • 如果 \(w\)\(v\) 相互独立(\(w \perp\!\!\!\perp v\)),那么条件分布等于边缘分布:\(p(w|v) = p(w)\)\(p(v|w) = p(v)\)

练习:利用条件分布的定义,证明上述两个条件在数学上是等价的。

我们说,给定第三个随机变量 \(u\),两个随机变量 \(w\)\(v\)条件独立的,如果当我们以 \(u\) 为条件时,知道 \(v\)\(w\) 其中一个的值,并不能告诉我们另一个的分布信息。在符号上,我们写作 \(w \perp\!\!\!\perp v \mid u\),其数学含义是 \(p(w, v \mid u) = p(w\mid u) p(v \mid u)\)

例如,假设 \(x_1\)​ 和 \(x_2\)​ 是从一个具有特定平均身高 \(\mu\) 的非常具体的人群中随机抽取的两个人的身高:这个人群可以是大学生、二年级学生、奥运游泳运动员,或者完全是其他某个群体。

如果我们知道 \(\mu\) 的值,那么 \(x_1\)​ 和 \(x_2\)​ 就是条件独立的,因为它们是来自已知均值 \(\mu\) 的同一分布的随机样本。例如,如果已知 \(\mu = 4'1''\),那么知道 \(x_1\)​ 并不会告诉我们任何关于 \(x_2\)​ 的信息。

假设我们不知道 \(\mu\) 的值。然后,我们发现 \(x_1 = 7' 1''\)。在这种情况下,我们可能会猜测这个“特定群体”很可能是一个身高非常高的群体,比如 NBA 球员。这将影响我们对 \(x_2\)​ 分布的信念(即,我们也应该预期第二个人很高)。因此,在这种情况下:

  • \(x_1\)​ 和 \(x_2\)​ 在给定 \(\mu\) 的条件下是条件独立的:\(x_1 \perp\!\!\!\perp x_2 \mid \mu\)

  • \(x_1\)​ 和 \(x_2\)​ 并非无条件独立:\(x_1 \perp\!\!\!\perp x_2\)​ 并不成立。

YouTubeVideo('WhqyUmqkSE8')

图模型中的独立性与条件独立性

图模型的结构可以告诉我们很多关于模型中变量间独立性关系的信息。具体来说,仅通过观察模型的结构,我们就能判断两个随机变量是无条件独立,还是在给定第三个变量的条件下条件独立。让我们通过几个例子来说明这一点。我们将从刚才看到的身高例子开始:

根据我们之前的推理,我们知道 \(x_1 \perp\!\!\!\perp x_2 \mid \mu\),但 \(x_1\)​ 和 \(x_2\)​ 并非无条件独立。对于图模型中这种配置下的任意三个变量,这一结论普遍成立。

练习:用数学方法证明上述结论。

解决方案:要证明 \(x_1\)​ 和 \(x_2\)​ 并非无条件独立,我们必须证明 \(p(x_1, x_2) \neq p(x_1)p(x_2)\)。我们可以通过考察所有三个变量的联合分布,然后对 \(\mu\) 进行边缘化来计算 \(p(x_1, x_2)\)

\(\begin{align*} p(x_1, x_2) &= \int p(x_1, x_2, \mu) d\mu \\ &= \int p(\mu) p(x_1 \mid \mu) p(x_2 \mid \mu) d\mu \end{align*}\) ​(4)

遗憾的是,无法对积分进行因式分解,以分离包含\(x_1\)​的项和包含\(x_2\)​的项,因此它无法分解。换句话说,通常上述积分不会等于\(p(x_1)p(x_2)\),因此这些变量并非无条件独立。

给定\(\mu\)的条件独立性如何?我们需要证明\(p(x_1, x_2\mid\mu) = p(x_1\mid\mu) p(x_2\mid\mu)\)

\(\begin{align*} p(x_1, x_2 \mid \mu) &= \frac{p(x_1, x_2, \mu)}{p(\mu)} \\ &= \frac{p(\mu) p(x_1 \mid \mu) p(x_2 \mid \mu)}{p(\mu)} \\ &= p(x_1 \mid \mu) p(x_2 \mid \mu) \end{align*}\) ​(5)

这个数学结果与我们上一节建立的直觉相符。

让我们来看另一个例子:

在这个例子中,\(x\)\(z\) 不是无条件独立的。直观上,我们可以看到 \(y\) 依赖于 \(x\),而 \(z\) 依赖于 \(y\),因此 \(x\)\(z\) 是相互依赖的。

但是,在给定 \(y\) 的条件下,\(x\)\(z\) 是条件独立的:从 \(x\)\(z\) 没有直接的箭头告诉我们,\(z\) 仅通过 \(y\) 依赖于 \(x\)

练习:用数学方法证明上述结果。

让我们来看第三个例子:

在这个例子中,\(x\)\(z\) 是无条件独立的,但在给定 \(y\) 的条件下,它们是条件依赖的。为什么?让我们看一个有助于我们理解这个结果的例子。假设:

  • \(y\) 表示我是否鼻塞。

  • \(x\) 表示我是否生病(感冒、流感、新冠等)。

  • \(z\) 表示我是否患有季节性过敏。

首先,我们可以看到描述与图模型相符:我是否鼻塞取决于我是否生病以及是否过敏。但是,生病和过敏彼此并不直接影响。换句话说,如果我对是否鼻塞一无所知,那么我的生病状态和过敏状态就是相互独立的。

现在,假设某天早晨我醒来时鼻子不通气(即 \(y=1\)),而我正试图判断自己是生病了还是过敏。我查看了天气预报,发现花粉浓度非常高。一听到这个消息,我就更加确信 \(z=1\)。但是,尽管天气预报并没有直接告诉我是否生病,但我对自己生病的信念却大幅下降:我的症状已经被“很可能过敏”这个解释解释掉了

换句话说,在给定 \(y\)(鼻塞)的某个值时,了解关于 \(z\)(过敏)的信息,就能让我获得关于 \(x\)(生病)分布的信息。这正是条件依赖的定义。

练习:用数学方法证明上述结果。

这些结果可以在d-分离贝叶斯球算法中得到形式化和推广。虽然该算法超出了本教科书的范围,但我们将在后续章节讨论因果关系时,探讨它的一个变体。

YouTubeVideo('lpujKeK90RM')

贝叶斯推断

原文:data102.org/ds-102-book/content/chapters/02/inference

import numpy as np
import pandas as pd
from scipy import stats
from IPython.display import YouTubeVideo

%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

贝叶斯推断

在本节中,我们将重点讨论如何在更复杂的贝叶斯模型中计算和使用后验分布。我们将首先讨论后验分布在贝叶斯推断中的用处,然后解释为何它们难以计算。接着,在下一节中,我们将学习如何使用抽样来近似分布。

为何我们需要后验分布

通常,我们需要后验分布以便能够对我们感兴趣的未知量 \(\theta\) 进行陈述和决策。我们看到,对于像产品评价模型或身高模型这样的简单模型,由于我们选择了共轭先验,精确计算后验分布很容易。

在产品评价示例中:

  • 我们感兴趣的参数 \(\theta\) 代表获得正面评价的概率。

  • 如果我们选择 Beta 先验,即 \(\theta \sim \mathrm{Beta}(\alpha, \beta)\),那么后验分布也属于 Beta 分布族:\(\theta | x \sim \mathrm{Beta}\left(\alpha + \sum x_i, \beta + n - \sum x_i\right)\)

  • 这使得确定诸如最大后验估计或最小均方误差估计等值变得容易,只需利用 Beta 分布的已知性质即可。

但如果我们的后验分布不具备如此便利的形式呢?在这种情况下,我们就必须自行计算后验分布(以及从中得出的任何估计值):

\(\begin{align} p(\theta|x) &= \frac{p(x|\theta)p(\theta)}{p(x)} \\ &= \frac{p(x|\theta)p(\theta)}{\int p(x|\theta)p(\theta)\,d\theta} \\ \end{align}\) ​​​(1)

通常,分母中的积分可能无法计算。我们称分母为归一化常数:它是一个常数,因为它不依赖于\(\theta\),并且它是归一化的,因为我们需要它来使分布或密度函数的总和或积分等于 1。

在下一节中,我们将看到几个例子来说明为什么计算归一化常数很困难,但首先,让我们看看三个例子,说明为什么我们首先可能需要知道它。

计算概率

假设我们想知道在给定观测数据的情况下,\(\theta\)大于 0.7 的概率。在这种情况下,我们可以建立一个积分来计算:

\(\begin{align} P(\theta > 0.7 | x) &= \int_{0.7}¹ p(\theta|x) \, dx \\ &= \int_{0.7}¹ \frac{p(x|\theta)p(\theta)}{p(x)} \, dx \\ &= \frac{1}{p(x)} \int_{0.7}¹ p(x|\theta)p(\theta) \, dx \end{align}\) ​​(2)

在最后一步中,我们利用了 p(x) 不依赖于 \(\theta\) 这一事实。

如果我们不知道 \(p(x)\),那么我们的概率将相差一个未知因子。例如,假设真实概率是 0.9,积分结果是 0.0009,而通常未知的分母 \(p(x)\) 是 0.001。在这种情况下,如果我们不知道归一化常数,就无法确定概率:我们总是会相差一个未知因子,这意味着我们的答案是无用的。

YouTubeVideo('WOS7iFlsN5c')

正在加载...

最大后验估计

假设我们想要计算最大后验估计:

\(\begin{align} \hat{\theta}_{MAP} &= \underset{\theta}{\operatorname{argmax}} p(\theta|x) \\ &= \underset{\theta}{\operatorname{argmax}} \frac{p(x|\theta)p(\theta)}{p(x)} \\ &= \underset{\theta}{\operatorname{argmax}} p(x|\theta)p(\theta) \\ \end{align}\) ​​(3)

在最后一步中,我们利用了 p(x) 不依赖于 \(\theta\) 这一事实。

如果 \(\theta\) 是低维且连续的,我们通常可以通过解析方法或有时通过数值方法进行优化。如果 \(\theta\) 是离散的且取值可能性不多,我们可以搜索所有可能的值。然而,如果 \(\theta\) 是离散的且可能取值的数量庞大到难以处理,那么我们需要搜索所有这些值,这将是不可行的。同样,如果 \(\theta\) 是高维的,那么搜索也可能变得难以处理。

总结来说:对于低维连续变量,或可能取值较少的离散随机变量,我们可以在不需要知道确切后验分布的情况下计算 MAP 估计。对于高维随机变量和/或可能取值众多的离散随机变量,这种方法将不再适用。

LMSE 估计

假设我们想要计算 LMSE 估计。回顾条件期望的定义(参见 Data 140 教科书,第九章第十五章):

\(\begin{align} \hat{\theta}_{LMSE} &= E_{\theta|x}[\theta] \\ &= \int \theta \cdot p(\theta|x) \, d\theta \\ &= \int \theta \cdot \frac{p(x|\theta)p(\theta)}{p(x)} \, d\theta \\ &= \frac{1}{p(x)} \int \theta \cdot p(x|\theta)p(\theta)\, d\theta \end{align}\) ​(4)

为了计算 LMSE 估计,我们需要计算分母\(p(x)\)。如果我们不知道它,那么我们的估计将会被一个未知的乘法因子所影响,从而使其实际上变得无用。

对于计算\(\theta\)的任何其他函数的期望值,或者涉及后验分布的任何其他概率,情况也是如此。例如,回答“根据后验分布,\(\theta\)的方差是多少?”这个问题,也会导致同样的问题。

总结来说:任何涉及后验分布的计算(概率、期望值等)都需要我们掌握完整的归一化分布:贝叶斯定理中的分子部分是不够的。

为何计算后验分布如此困难

在我们之前的产品评价模型或身高模型这类简单模型中,计算我们感兴趣的未知变量的精确后验分布是容易的。这是因为我们选择了共轭先验。但在大多数其他情况下,计算精确后验分布是困难的!以下是两个例子:

一维非共轭先验

让我们回到产品评价的例子,但这次,我们不使用 Beta 先验,而是选择 \(p(\theta) = \frac{2}{\pi}\cos\left(\frac{\pi}{2} \theta\right)\),其中 \(\theta \in [0, 1]\)

\(\begin{align} p(\theta|x) &\propto p(x|\theta)p(\theta) \\ &\propto \Big[\theta^{\left[\sum_i x_i\right]}(1-\theta)^{\left[\sum_i (1-x_i)\right]}\Big]\cos\left(\frac{\pi}{2}\theta\right) \end{align}\) ​​(5)

这个分布看起来要复杂得多:我们完全无法将其简化为一个已知的分布。因此,为了正确计算 \(p(\theta|x)\),我们需要找出归一化常数。这需要求解以下积分:

\(\begin{align} p(x) &= \int_0¹ \Big[\theta^{\left[\sum_i x_i\right]}(1-\theta)^{\left[\sum_i (1-x_i)\right]}\Big]\cos\left(\frac{\pi}{2}\theta\right)\,d\theta \end{align}\) ​(6)

这个积分难以用闭合形式求解。在这个具体例子中,由于是一维问题,我们可以利用数值积分。换句话说,对于特定的值序列 \(x_1, \ldots, x_n\)​,我们可以将它们代入并计算积分的数值近似值,然后以此求出归一化常数。如上所述,如果我们只关心最大后验估计,则不需要归一化常数,但如果没有它,我们就无法计算最小均方误差估计。

多维示例

考虑上次的系外行星模型:\(x_i\)​ 是行星 \(i\) 的(观测到的)半径,\(z_i\)​ 表示该行星属于第 0 组(小型,可能宜居的行星)还是第 1 组(大型,可能不宜居的行星),而 \(\mu_0\)​ 和 \(\mu_1\)​ 分别是这两组的平均半径。

\(\begin{align} z_i &\sim \mathrm{Bernoulli}(\pi) & i = 1, \ldots, n \\ \mu_k &\sim \mathcal{N}(\mu_p, \sigma_p) & k =0, 1 \\ x_i | z_i, \mu_0, \mu_1 &\sim \mathcal{N}(\mu_{z_i}, \sigma) & i = 1, \ldots, n\\ \end{align}\) ​(7)

我们可以写出似然函数和先验分布。为简化起见,我们将写作 \(\mathcal{N}(y; m, s) = \frac{1}{s \sqrt{2\pi}} \exp\left\{-\frac{1}{2s²}(y - m)²\right\}\)

\(\begin{align} p(z_i) &= \pi^{z_i}(1-\pi)^{1-z_i} \\ p(\mu_k) &= \mathcal{N}(\mu_k; \mu_p, \sigma_p) \\ p(x_i | z_i, \mu_0, \mu_1) &= \mathcal{N}(x_i; \mu_{z_i}, \sigma) \end{align}\) ​​(8)

我们可以尝试计算关于隐藏变量 \(z_i\)​、\(\mu_0\)​ 和 \(\mu_1\)μ1​ 的后验分布。我们将使用符号 \(z_{1:n}\)​ 来表示 \(z_1, \dots, z_n\)​(对于 \(x_{1:n}\)​ 同理)。

\(\begin{align} p(z_{1:n}, \mu_0, \mu_1 | x_{1:n}) &\propto p(\mu_0)p(\mu_1)\prod_i \left[p(z_i) p(x_i | z_i, \mu_0, \mu_1)\right] \end{align}\) ​​(9)

这个分布比我们目前所见过的任何分布都要复杂。它是关于 n+2 个随机变量(组标签 \(z_1, \ldots, z_n\)​ 以及两个组均值 \(\mu_0\)​ 和 \(\mu_1\)​)的联合分布。

计算归一化常数 \(p(x_{1:n})\) 需要复杂的求和与积分组合:

\(\begin{align} p(x_{1:n}) &= \sum_{z_1=0}¹ \sum_{z_2=0}¹ \ldots \sum_{z_n=0}¹ \int \int p(\mu_0)p(\mu_1)\prod_i \left[p(z_i) p(x_i | z_i, \mu_0, \mu_1)\right] d\mu_0 d\mu_1 \end{align}\) ​​​(10)

对于我们包含超过 500 颗行星的数据集,仅求和运算就需要完全无法承受的计算量:

2**517

429049853758163107186368799942587076079339706258956588087153966199096448962353503257659977541340909686081019461967553627320124249982290238285876768194691072

更糟糕的是,我们甚至无法计算标签 \(z_i\)​ 的最大后验概率估计:为了找到使分子最大化的那个,我们必须搜索所有 2⁵¹⁷ 种组合,这同样是完全不可行的。

即使在这个相当简单的、只有两个组的模型中,我们也发现精确推断是完全无望的:我们无法计算所有未知量的精确后验概率。在本节的剩余部分,我们将讨论如何通过后验分布的近似方法来绕过这个问题。

具体而言,我们的近似方法将利用已掌握的知识。我们知道,计算后验分布最困难的部分在于计算归一化常数 \(p(x)\)。因此,我们将构建从非归一化后验 \(p(x|\theta)p(\theta)\) 入手的方法,并利用它来给出实际后验 \(p(\theta|x) = \frac{p(x|\theta)p(\theta)}{p(x)}\)​ 的近似。虽然存在多个方法族可以提供此类近似,但在本教材中,我们将重点介绍使用样本来近似分布的方法。

YouTubeVideo('BlQ6IVoJ0X8')

正在加载...

基于采样的贝叶斯推断

原文:data102.org/ds-102-book/content/chapters/02/inference-with-sampling

import numpy as np
import pandas as pd
from scipy import stats
from IPython.display import YouTubeVideo

%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

基于采样的贝叶斯推断

用样本近似已知分布

我们之前已经看到,可以从数据点样本中计算出经验分布。在本节中,我们将使用样本来近似分布。

让我们从一个已知且易于计算的分布开始,用采样来近似它:Beta\((3, 4)\)。虽然这个例子有些“傻”,因为该分布很容易推理,但它将帮助我们理解采样如何为分布提供有用的近似。

 from scipy import stats

distribution = stats.beta(3, 4)
num_samples = 1000

# Compute the exact PDF:
t = np.linspace(0, 1, 500)
pdf = distribution.pdf(t)

# Draw 1000 samples, and look at the empirical distribution of those samples:
samples = distribution.rvs(num_samples)
f, ax = plt.subplots(1, 1)

sns.histplot(
    x=samples, stat='density', bins=20, 
    label=f'Empirical dist. of {num_samples} samples'
)
ax.plot(t, pdf, label='Beta(3, 4) pdf')
ax.legend();
/Users/ramesh/anaconda3/lib/python3.11/site-packages/seaborn/_oldcore.py:1119: FutureWarning: use_inf_as_na option is deprecated and will be removed in a future version. Convert inf values to NaN before operating instead.
  with pd.option_context('mode.use_inf_as_na', True): 

<Figure size 640x480 with 1 Axes>

我们可以看到,只要样本数量足够,它们就能很好地代表分布。我们可以使用样本的均值来近似分布的均值:

# The mean of a Beta(a, b) distribution is a/(a+b):
true_mean = 3 / (3 + 4)

approx_mean = np.mean(samples)
print(true_mean, approx_mean)
0.42857142857142855 0.42990793568940666 

同样地,我们可以使用样本的方差来估计分布的方差,依此类推。

虽然这种方法实现起来极其简单,但也有些“傻”:对于这个分布,任何我们可以用样本完成的操作也都可以通过解析方法完成。通常,我们想要近似的是那些难以处理、涉及无法计算归一化常数的复杂分布。所以,接下来我们将处理这类情况。

拒绝采样

拒绝采样的工作原理是生成大量提议样本,然后拒绝那些可能性低或不可能出现的样本。

作为热身,考虑一个二维分布,涉及\(\theta_1\)​和\(\theta_2\)​,它在单位圆上是均匀的:

\(\begin{align*} p(\theta|x) &\propto \begin{cases} 1 & \text{if } \theta_1² + \theta_2² \leq 1 \\ 0 & \text{otherwise} \end{cases} \end{align*}\) ​​(1)

假设我们想从这个分布中采样:换句话说,我们想从单位圆中均匀抽取一对随机变量(\(\theta_1\)​, \(\theta_2\)​),如下方的蓝色区域所示。

我们该如何着手做这件事呢?

θ_ = np.linspace(-1, 1, 1000)
semicircle = np.sqrt(1-θ_**2)
f, ax = plt.subplots(1, 1, figsize=(4, 4))
ax.fill_between(θ_, -semicircle, semicircle)
ax.set_xlabel(r'$\theta_1$')
ax.set_ylabel(r'$\theta_1$')
ax.axis('equal');

<尺寸为 400x400 的图,包含 1 个坐标轴>

在前一个例子中,我们可以轻松地使用stats.beta.rvs从贝塔分布中采样。不幸的是,对于上面所示的分布,没有等效的方法。

但是,我们可以通过抽取两个独立的均匀分布\((0, 1)\)样本来从单位正方形中采样。我们可以轻松检查样本是否落在单位圆内,如果落在外面就丢弃(或拒绝)它。以下是对多个样本进行此操作的效果图:

 # Number of samples
N = 400

# Samples in the unit square
samples = np.random.uniform(-1, 1, [N, 2])

# Which ones are inside the unit circle?
is_in_circle = (samples[:,0]**2 + samples[:, 1]**2) < 1
good_samples = samples[is_in_circle]
θ1 = good_samples[:, 0]
θ2 = good_samples[:, 1]
print('Variance of θ1 (estimated from samples): %.3f' % np.var(θ1))
Variance of θ1 (estimated from samples): 0.241 

源代码

f, ax = plt.subplots(1, 1, figsize=(4, 4))

ax.scatter(samples[:, 0], samples[:, 1], c=is_in_circle, cmap='viridis')
ax.set_xlabel(r'$\theta_1/details>)
ax.set_ylabel(r'$\theta_1/details>)
ax.axis('equal');

<Figure size 400x400 with 1 Axes>

这里,紫色(较深)的样本被拒绝,因此我们得到黄色(较浅)的样本,它们代表了上述分布。

YouTubeVideo('Nbszb5tGmFo')

加载中...

接下来,让我们思考如何从一个具有复杂概率密度的分布中进行采样。假设我们想从密度函数为 \(p(\theta|x) \propto \theta \cdot (1.5-\theta) \cdot \sin(\theta)\) 的分布中采样,其中 \(\theta \in [0,1.5]\)

t = np.linspace(-1, 2.5, 500)
def target(t):
    """The unnormalized distribution we want to sample from"""
    return t * (1.5-t) * np.sin(t) * ((t > 0) & (t < 1.5))

源代码

f, ax = plt.subplots(1, 1, figsize=(5, 3))
ax.plot(t, target(t))
ax.set_title('Target distribution (unnormalized)')
ax.set_xlabel(r'$\theta/details>)
ax.axis([-1,2.5,0,1]);

<Figure size 500x300 with 1 Axes>

我们如何能让这看起来像之前基于几何的例子呢?解决方案是一种名为拒绝采样的算法。有两种方式来理解它的工作原理:

  1. 第一种方法是从均匀分布开始生成样本,然后随机丢弃部分样本(而非像前例那样确定性丢弃)。直观来看,通过比较下图中的目标分布与 Uniform\((0, 1.5)\) 分布可知:对于极小的 \(\theta\) 值,我们应尝试丢弃更多样本;而在 0.5 到 1 之间,则应保留更多样本。换言之,每个样本的拒绝概率应取决于其在目标分布下的似然程度。

  2. 第二种方法是将我们的一维采样问题(即生成 \(\theta\) 的样本)转化为二维采样问题:首先从下图中橙色矩形区域采样,然后丢弃未落在蓝色曲线下方的样本。为此,我们必须先在 0 到 1.5 之间采样 \(\theta\) 值,再为每个 θ 值采样一个高度。如果高度过大(即高于该 θ 值对应的目标分布高度),则拒绝该样本。

x = np.linspace(-1, 2.5, 500)
def uniform_sampling_dist(t):
    """PDF of distribution we're sampling from: Uniform[0, 1.5]"""
    return stats.uniform.pdf(t, 0, 1.5)

来源

f, ax = plt.subplots(1, 1, figsize=(5, 3))
ax.plot(t, target(t), label='Target distribution (unnormalized)')
ax.plot(t, uniform_sampling_dist(t), label='Sampling or proposal distribution')
ax.axis([-1,2.5,0,1])
ax.legend()
ax.set_title('Distributions')
ax.set_xlabel(r'$\theta/details>);

<Figure size 500x300 with 1 Axes>

更准确地说,拒绝采样工作原理如下:给定未归一化的目标分布和提议分布,我们通过以下步骤从归一化后的目标分布生成样本:

  1. 从提议分布中生成样本。

  2. 对于上一步生成的每个样本,计算目标分布与提议分布的比值。该比值代表我们接受该样本的概率:目标分布值越大,接受概率越高;目标分布值越小,接受概率越低。为使该比值能被视作概率,我们要求目标分布值始终小于(或等于)提议分布值:通过选择合适的提议分布并对目标分布进行适当缩放,总能满足此条件。

  3. 随机接受或拒绝步骤 1 中的每个样本,其概率由步骤 2 中的比率决定。这可以通过为每个样本生成一个 Uniform\((0, 1)\) 随机变量,并接受该随机变量小于接受概率的样本来实现。在继续之前,请停下来并确信这是正确的! 丢弃所有被拒绝的样本。

  4. 被接受的样本将是来自与未归一化目标相对应的归一化密度的真实样本。

以下代码实现了拒绝采样。请注意,此函数中只有四行(实质性)代码,对应于上述四个步骤:

def rejection_sample_uniform(num_proposals=100):
    # Generate proposals for samples: these are θ-values.
    # We'll keep some and reject the rest.
    proposals = np.random.uniform(low=0, high=1.5, size=num_proposals)

    # Acceptance probability is the ratio of the two curves
    # These had better all be between 0 and 1!
    accept_probs = target(proposals) / uniform_sampling_dist(proposals)

    print('Max accept prob: %.3f' % np.max(accept_probs))

    # For each sample, we make a decision whether or not to accept.
    # Convince yourself that this line makes that decision for each
    # sample with prob equal to the value in "accept_probs"!
    accept = np.random.uniform(size=num_proposals) < accept_probs

    num_accept = np.sum(accept)
    print('Accepted %d out of %d proposals' % (num_accept, num_proposals))
    return proposals[accept]

让我们将其应用于上述目标分布,使用 rejection_sample_uniform(num_proposals=500)rejection_sample_uniform(num_proposals=10000)

源代码

f, axs = plt.subplots(1, 2, figsize=(8, 3), dpi=100)

# Run rejection sampling twice, once with many proposals and once with few proposals
samples_sparse = rejection_sample_uniform(num_proposals=500)
samples_dense = rejection_sample_uniform(num_proposals=10000)

# Plot a true histogram (comparable with density functions) using density=True
axs[0].hist(samples_sparse, bins=np.linspace(-0.25, 1.75, 40), density=True)
axs[1].hist(samples_dense, bins=np.linspace(-0.25, 1.75, 40), density=True)

# Where did this magic number 0.36 come from? What happens if you change it?
axs[0].plot(t, target(t) / 0.36)
axs[1].plot(t, target(t) / 0.36)

axs[0].set_title('Rejection sampling: 500 proposals')
axs[1].set_title('Rejection sampling: 10,000 proposals')
axs[0].axis([-0.25, 1.75, 0, 1.8])
axs[1].axis([-0.25, 1.75, 0, 1.8]); 
Max accept prob: 0.638
Accepted 175 out of 500 proposals
Max accept prob: 0.638
Accepted 3661 out of 10000 proposals 

<Figure size 800x300 with 2 Axes>

从结果中我们可以看到,当有足够多的提议样本时,拒绝采样能够正确且准确地为我们提供来自目标分布的样本。但是,它可能效率低下:我们每次接受的样本不到一半!确实,即使有 10,000 个提议样本(右侧),这些样本也不能完美地代表目标分布。

这种效率低下的问题是由拒绝样本引起的。请注意,最大接受概率仅为 0.64 左右。如果我们将提议分布按比例缩放 \(1/0.64 \approx 1.56\) 倍,那么总体上我们拒绝的样本会更少,同时仍能确保概率永远不会超过 1。

不幸的是,当处理高维问题时,效率低下的问题会变得严重得多!在处理高维分布时,空间中通常有更多区域的密度相对较低,这意味着会有更多的样本被拒绝。

作为第二个例子,如果我们想从取值在 \([0, \infty)\) 的目标分布中抽样,会发生什么?例如,假设我们的密度函数为 \(p(\theta|x) \propto \exp(-\theta) |\sin(2\theta)|\),其中 \(\theta \in [0, \infty)\)。我们不能使用均匀分布 \((a, b)\) 作为提议分布。为什么不行?因为对于任何 \(b\) 值,我们永远无法生成大于 \(b\) 的样本,因此我们的样本将无法匹配真实分布。

相反,我们可以使用正态分布或指数分布:

def decaying_target_distribution(t):
    """Unnormalized target distribution as described above"""
    return np.exp(-t) * np.abs(np.sin(2*t))

def sampling_distribution_exponential(t):
    """Sampling distribution: exponential distribution"""
    # stats.expon has a loc parameter which says how far to shift
    # the distribution from its usual starting point of θ=0
    return stats.expon.pdf(t, loc=0, scale=1.0)

def rejection_sample_exponential(num_proposals=500):
    """Rejection sampling with an exponential distribution with λ=1"""
    proposals = np.random.exponential(scale=1.0, size=num_proposals)
    accept_probs = decaying_target_distribution(proposals) / sampling_distribution_exponential(proposals)
    accept = np.random.uniform(0, 1, num_proposals) < accept_probs
    num_accept = np.sum(accept)
    print('Accepted %d out of %d proposals' % (num_accept, num_proposals))
    return proposals[accept]

samples = rejection_sample_exponential(num_proposals=1000)
plt.hist(samples, bins=np.linspace(0, 6, 100), density=True)
# Find how far the axis goes and draw the unnormalized distribution over it

tmin, tmax, _, _ = plt.axis()
t_inf = np.linspace(tmin, tmax, 100)

# Where did this magic number 0.6 come from? What happens if you change it?
plt.plot(t_inf, decaying_target_distribution(t_inf) / 0.6)
plt.show()
Accepted 591 out of 1000 proposals 

<Figure size 640x480 with 1 Axes>

总而言之,我们了解到拒绝采样可用于仅根据未归一化的目标分布进行抽样。尽管它可以用于从任何未归一化的目标分布中抽取样本,但我们最常将其(以及其他抽样方法)用于未归一化的目标分布,即后验分布的分子 \(p(\theta|x)p(\theta)\)

拒绝采样通过使用一个易于采样的提议分布来工作:虽然正态分布和均匀分布最为常见,但我们可以使用任何分布,只要 (a) 我们可以从中抽取样本,并且 (b) 我们可以保证它总是大于(或等于)目标分布的缩放版本。其工作原理是:(1) 从提议分布生成提议样本,(2) 计算目标分布与提议分布的比值作为接受概率,(3) 根据其接受概率接受每个样本。它通常效率低下,尤其是在高维情况下。

YouTubeVideo('S9korbhU4Wg')

加载中...

Markov Chain Monte Carlo

我们注意到,拒绝采样的低效性源于我们拒绝并丢弃了大量提议样本。确实,拒绝采样独立地生成每个样本,没有利用先前生成的样本中关于哪些区域概率较低、哪些区域概率较高的任何信息。

马尔可夫链蒙特卡洛 (MCMC) 方法采用了一种不同的策略,它通过生成一系列样本来工作。每个样本都依赖于前一个样本,这使我们能够生成更好的样本。我们将以这样一种方式构建样本序列,使其形成一个马尔可夫链,其稳态分布就是目标分布的真实归一化版本。

Markov Chains

关于马尔可夫链的讨论,请参阅 Data 140 教材第十章

Metropolis-Hastings (Optional)

进行中

Gibbs sampling

Gibbs 采样是一种专为处理高维后验分布而设计的算法。它通过迭代地对每个单独变量进行重采样来工作,条件是给定数据和其他所有随机变量。例如,让我们重新审视上面提到的单位圆上的均匀分布:

\(\begin{align*} p(\theta|x) &\propto \begin{cases} 1 & \text{if } \theta_1² + \theta_2² \leq 1 \\ 0 & \text{otherwise} \end{cases} \end{align*}\) ​(2)

假设我们已知 \(\theta_1\)​ 的一个特定值,例如 \(\theta_1 = 0.5\)。基于这个值,我们可以轻松推断 \(\theta_2|\theta_1=0.5\) 的分布:它在如下所示的垂直线上是均匀的:

源代码

θ_ = np.linspace(-1, 1, 1000)
semicircle = np.sqrt(1-θ_**2)
f, ax = plt.subplots(1, 1, figsize=(4, 4))
ax.fill_between(θ_, -semicircle, semicircle, alpha=0.5)
ax.plot([0.5, 0.5], [-np.sqrt(1-0.5**2), np.sqrt(1-0.5**2)], color='black', lw=1.5)
ax.set_xlabel(r'$\theta_1/details>)
ax.set_ylabel(r'$\theta_1/details>)
ax.axis('equal');

<尺寸为 400x400,包含 1 个坐标轴的图>

因此,给定 \(\theta_1\)​ 的任意特定值,我们可以通过从均匀分布中采样,轻松地基于该值为 \(\theta_2\)​ 抽取一个条件样本。经过一些代数运算,我们可以将其写为:

\(\theta_2 \mid \theta_1 \sim \mathrm{Uniform}\left(-\sqrt{1-\theta_1²}, \sqrt{1-\theta_1²}\right)\) (3)

根据对称性

请注意,尽管我们在本节中多次提及后验分布和基于数据的条件化,吉布斯采样是一种通用方法,可用于从任何高维目标分布中采样,只要其条件分布 \(p(\theta_i | \theta_1, \ldots, \theta_{i-1}, \theta_{i+1}, \ldots, \theta_n)\) 易于采样。

在 PyMC 中实现模型

我们花了大量时间进行代数推导,以得出评价模型的后验分布和估计值。此时,你可能会问:我们能否通过计算来完成大部分工作?事实证明答案是肯定的!PyMC 是一个用于贝叶斯推断的 Python 库。要使用它,你必须指定一个概率模型(就像我们刚刚看到的三个模型)和观测数据,然后它将计算所有未知变量的后验分布。

PyMC 中的产品评价模型

让我们在产品评价模型上尝试一下:

\(\begin{align} x_i | \theta &\sim \mathrm{Bernoulli}(\theta) \\ \theta &\sim \mathrm{Beta}(\alpha, \beta) \end{align}\) ​(4)

我们将从指定数据开始:微波炉 A 有 3 条正面评价和 0 条负面评价,微波炉 B 有 19 条正面评价和 1 条负面评价。

reviews_a = np.array([1, 1, 1])
reviews_b = np.append(np.ones(19), np.zeros(1))

然后,我们将定义模型:请仔细审阅下面的代码,确保你理解每一行背后的逻辑(除了最后一行):

import pymc as pm
import arviz as az

# Parameters of the prior
alpha = 1
beta = 5

with pm.Model() as model:
    # Define a Beta-distributed random variable called theta
    theta = pm.Beta('theta', alpha=alpha, beta=beta)

    # Defines a Bernoulli RV called x. Since x is observed, we
    # pass in the observed= argument to provide our data
    x = pm.Bernoulli('x', p=theta, observed=reviews_b)

    # This line asks PyMC to approximate the posterior.
    # Don't worry too much about how it works for now.
    trace = pm.sample(2000, chains=2, tune=1000, return_inferencedata=True)

trace
Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (2 chains in 2 jobs)
NUTS: [theta] 

正在加载...正在加载...

Sampling 2 chains for 1_000 tune and 2_000 draw iterations (2_000 + 4_000 draws total) took 1 seconds.
We recommend running at least 4 chains for robust computation of convergence diagnostics 

正在加载...

trace.posterior

正在加载...

花点时间查看输出,注意不同的属性。目前,我们最感兴趣的是来自后验分布的样本,它们位于 trace.posterior['theta'].values 中:

samples = trace.posterior['theta'].values.flatten()

f, ax = plt.subplots(1, 1, figsize=(4, 2))
ax.hist(samples, density=True);
ax.set_xlabel(r'$\theta$')
ax.set_ylabel('Density (from samples)');

<尺寸为 400x200 且包含 1 个坐标轴的图形>

(可选)PyMC 中的系外行星模型

planets = pd.read_csv('exoplanets.csv')
planets.shape

(517, 6)

让我们尝试一个更有趣的模型:我们用于系外行星的混合模型:

\(\begin{align} z_i &\sim \mathrm{Bernoulli}(\pi) & i = 1, \ldots, n \\ \mu_k &\sim \mathcal{N}(\mu_p, \sigma_p) & k =0, 1 \\ x_i | z_i, \mu_0, \mu_1 &\sim \mathcal{N}(\mu_{z_i}, \sigma) & i = 1, \ldots, n\\ \end{align}\) ​(5)

首先,我们需要一个名为“花式索引”的技巧。其工作原理如下:

example_zs = np.array([1, 0, 0, 1, 1, 0])
example_mus = np.array([1.3, 10.2])

means = example_mus[example_zs]
means

array([10.2, 1.3, 1.3, 10.2, 10.2, 1.3])

pi = 0.6      # Prior probability of a planet being in the large/uninhabitable group
sigma = 1.5   # SD of likelihood
mu_p = 5      # Mean of prior
sigma_p = 10  # Variance of prior: important to choose a large value here

with pm.Model() as model_exoplanet:

    # This defines a Bernoulli random variable called 'z' in our model.
    z = pm.Bernoulli('z', p=pi)

    # This creates an array of two random variables called 'mu'
    # (one for each group), because we used the shape=2 argument
    mu = pm.Normal('mu', mu=mu_p, sigma=sigma_p, shape=2)

    planet_means = mu[z]
    # this is the tricky bit with the indexing: we'll use the "fancy indexing" idea
    # from above
    x = pm.Normal('x', mu=planet_means, sigma=sigma, observed=planets['radius'])

    trace_exoplanet = pm.sample(2000, chains=2, tune=1000, return_inferencedata=True)

trace_exoplanet.posterior['mu'].values
Multiprocess sampling (2 chains in 2 jobs)
CompoundStep
>BinaryGibbsMetropolis: [z]
>NUTS: [mu] 

加载中...加载中...

Sampling 2 chains for 1_000 tune and 2_000 draw iterations (2_000 + 4_000 draws total) took 1 seconds.
There were 94 divergences after tuning. Increase `target_accept` or reparameterize.
We recommend running at least 4 chains for robust computation of convergence diagnostics
The rhat statistic is larger than 1.01 for some parameters. This indicates problems during sampling. See https://arxiv.org/abs/1903.08008 for details
The effective sample size per chain is smaller than 100 for some parameters.  A higher number is needed for reliable rhat and ess computation. See https://arxiv.org/abs/1903.08008 for details 

第三章:预测

原文:data102.org/ds-102-book/content/chapters/03/intro/

本章将介绍使用广义线性模型和非参数模型进行预测。

预测

原文:data102.org/ds-102-book/content/chapters/03/prediction

您可能会发现回顾《Data 8》教材的第十五章很有帮助,该章节探讨了使用线性回归和\(k\)-最近邻算法进行预测。

在许多情况下,我们的目标是预测:根据其他几个变量来预测一个变量。我们通常将预测变量称为 \(y\),而将其他变量称为 \(x\)。以下是现实场景中预测的几个例子:

  • 一家订阅制公司可能希望利用人口统计信息、历史使用数据和整体市场趋势(\(x\)),来预测某位客户下个月是否会续订其订阅(\(y\))。这些信息将帮助他们做出重要的商业决策,例如是否向该客户发送促销信息,或者通过汇总多个预测来估算公司下个月将带来多少收入。

  • 一位 YouTube 内容创作者可能希望预测其下一个视频的观看次数(\(y\)),利用其 YouTube 频道的信息(粉丝数等)以及视频本身的信息(时长、嘉宾数量、制作成本等)(\(x\))。当寻找希望了解视频广告覆盖人数的广告商时,这些信息会很有用。

在这些例子中,我们可以看到我们拥有多个预测变量 \(x\)(通常我们将其写作 \(x_1, x_2, \ldots, x_d\)​,或简写为一个向量 \(x\)),并且我们正在预测一个单一的目标变量 \(y\)(通常是标量值)。预测结果可以是二元的、离散的或连续的。

以下是我们将使用的通用框架:我们首先假设有一些已知的示例对,\((x_1, y_1), (x_2, y_2), \ldots, (x_n, y_n)\),其中每一对都包含数据点 \(i\) 的预测变量向量 \(x_i\)​,以及数据点 \(i\) 的预测目标已知值。在上述例子中,这可能是过去续约与未续约客户的历史信息,或是先前发布的、已知发布时观看次数和频道信息的视频数据。我们将利用这些点来学习 \(x\)\(y\) 之间的关系,然后将所学应用于新点 \(x_{n+1}, x_{n+2}, \ldots\)。任何用于学习该关系的点都被称为训练集

你之前已经多次见过这种模式的应用:

  • 在线性回归中,我们使用 \((x_1, y_1), \ldots, (x_n, y_n)\)拟合训练一个线性模型,然后使用学习到的系数对新数据点进行预测。

  • \(k\)-近邻分类中,我们会存储整个训练集,然后在分类(即预测新点的分类标签)时,找到训练集中\(k\) 个最近邻点,并通过它们标签的多数投票来作为我们的预测。

请注意,预测并不等同于因果关系!一位 YouTuber 可能会发现,他们最新视频的观看次数是预测下一个视频观看次数的有力指标,但这两者之间未必存在因果关系。然而,这并不意味着它在预测方面就不那么有用。因此,我们将专注于预测的构建与理解,暂时避免对因果关系或其缺失做出任何结论。在下一章中,我们将更深入地理解这两者之间的区别,并探讨何时能够真正进行因果推理。

回归复习

原文:data102.org/ds-102-book/content/chapters/03/regression-review

Notebook Cell

from IPython.display import YouTubeVideo

回归复习

本节是对线性回归的复习。您可能会发现复习《数据 100》教材的第 15.4 节会有所帮助,该节涵盖了相关内容。

回想一下,回归是监督学习的一种形式。给定一些数据 \(x\)(通常是标量或向量),我们试图预测一个单一值 \(y\)。您已经见过 \(y\) 是实数(线性回归)或二进制值 $\in {0, 1}(逻辑回归)的情况。让我们简要回顾一下线性回归的设置。

我们有一组数据 \((x_1, y_1), \ldots, (x_n, y_n)\)。我们试图根据 \(x_i\)​ 来预测 \(y_i\)​,但我们的预测不会完美。我们将使用符号 \(\hat{y}_i\)​ 来表示数据点 \(i\) 的预测值。我们首先讨论如何获得预测值,然后探讨预测值与实际观测值之间的关系。

一维

在一维情况下,我们拥有形式为 \((x_1, y_1), \ldots, (x_n, y_n)\) 的数据,其中每个 \(x_i\)​ 和每个 \(y_i\)​ 都是标量。我们构建一个线性预测

\(\hat{y}_i = ax_i + b,\) (1)

其中 \(a\) 是斜率,\(b\) 是截距。在一元线性回归中,我们从观测数据点 \((x_1, y_1), \ldots, (x_n, y_n)\) 计算出 \(a\)\(b\)

多维度

在多元线性回归中,数据形式仍为 \((x_1, y_1), \ldots, (x_n, y_n)\),但现在每个 \(x_i \in \mathbb{R}^d\) 都是一个 \(d\) 维向量。我们可以写作

\(x_i = \left(x_{i1}, x_{i2}, \ldots, x_{id}\right)\) (2)

该向量的每个分量对应于此数据点在我们预测中使用的不同方面。我们称其中每一个为预测变量特征

在多元线性回归中,我们对数据点 \(i\) 的预测值 \(\hat{y}_i\)​ 的构成方式如下:

\(\hat{y}_i = \sum_j \beta_j x_{ij}\) ​(3)

\(d\) 维向量 \(\beta = (\beta_1, \ldots, \beta_d)\) 包含了每个预测变量的系数:线性回归的核心就是确定这些系数。我们可以使用向量 \(\beta\)\(x_i\)​ 以向量符号表示:

\(\hat{y}_i = \beta^T x_i = x_i^T \beta\) (4)

我们可以将这种表示法更进一步,构建一个包含所有数据点和所有特征的 \(x\) 值的矩阵。

\(X = \begin{pmatrix} x_{11} & x_{12} & \cdots & x_{1d} \\ x_{21} & x_{22} & \cdots & x_{2d} \\ \vdots & \vdots & \ddots & \vdots \\ x_{n1} & x_{n2} & \cdots & x_{nd} \end{pmatrix}\) ​(5)

该矩阵中的一个元素 \(x_{ij}\)​,代表数据点 \(i\) 的特征 \(j\)。如果我们考虑整个预测向量 \(\hat{y} = \left(\hat{y}_1, \ldots, \hat{y}_n\right)\),我们可以用完全向量化的方式写出预测:

\(\hat{y} = X\beta\) (6)

我们可以在模型的上下文中解释每个系数 \(\beta_j\)​,如下所示:“如果 \(x_j\)​ 增加 \(t\),那么模型预测 \(y\) 应增加约 \(\beta_j \times t\)。”

YouTubeVideo('rEUf3bW32jM')

似然函数与损失函数

为了计算系数向量 \(\beta\),我们需要某种方式将基于 \(\beta\) 的预测值 \(\hat{y}_i\)​ 与实际观测值 \(y_i\)​ 联系起来。你已经见过两种实现方式:

  1. 预测值 \(\hat{y}\)​ 与观测值 \(y\) 之间的损失函数;我们可以最小化这个损失函数来找到 \(\beta\)

  2. 一个将误差 \(\epsilon = y - \hat{y}\)​ 描述为随机变量的概率模型,并试图在该模型下最大化数据的似然性。

损失函数

回想一下,在普通最小二乘线性回归中,我们试图找到使均方误差(MSE)最小化的 \(y\) 值。我们可以将 MSE 写成如下形式:

\(\text{MSE} = \frac{1}{n}\sum_i (y_i - \beta^T x_i)²\) (7)

我们也可以将其写作向量 \(y - \hat{y}\)​ 的 \(\ell_2\)​ 范数

\(\text{MSE} = \frac{1}{n}\left\|y - \hat{y}\right\|_2² = \frac{1}{n}\left\|y - X\beta\right\|_2²\) ​(8)

其中,对于任意向量 \(z\),其 \(\ell_2\)​ 范数定义为 \(\|z\|_2 = \sqrt{\sum_i z_i²}\)​​。

我们希望选择一个 \(\beta\) 值,使此式尽可能小:

\(\hat{\beta} = \text{argmin}_\beta \|y - X\beta\|_2²\) ​(9)

似然与噪声

本节内容将是后续构建的重要基础,请务必充分理解此处的内容!

我们也可以描述模型中的误差:

\(y_i = \beta^T x_i + \epsilon_i,\) (10)

其中 \(\epsilon_i \sim N(0, \sigma²)\) 是一个随机变量,代表观测值中的噪声或误差。我们也可以将此噪声向量化:记 \(\epsilon = (\epsilon_1, \ldots, \epsilon_n)\),使得向量 \(\epsilon\) 服从多元正态分布 \(\epsilon \sim N(0, \sigma² I_n)\)。于是我们可以写成:

\(y = X\beta + \epsilon,\) (11)

或者等价地,利用正态分布的性质,

\(y | \beta \sim N(X\beta, \sigma² I_n).\) (12)

我们可以将此方程理解为:平均预测值\(X\beta\)。该方程是一个似然模型:它告诉我们,在给定参数 \(\beta\) 的情况下,数据 \(y\) 的似然性。请注意,我们将 \(X\) 视为固定且已知的,因此没有与之关联的概率模型。我们通常将重点放在这个版本上,而不是前一个版本。

让我们思考一下通过选择正态似然函数所隐含的假设。回想一下,在正态分布下,我们极不可能看到距离均值超过 3\(\sigma\)的值。这意味着我们隐含地假设,我们观察到的大多数\(y\) 值将在平均预测\(X\beta\)的 3\(\sigma\)范围内。这有助于解释为什么线性回归对异常值敏感:一个点距离平均预测非常远的可能性非常小,但多个点都相对较远的可能性则要高得多。

该模型通常被称为普通最小二乘法,或 OLS

在此模型下,估计\(\beta\)的一个合理方法是选择使似然函数最大化的值。在选择\(\beta\)的值以最大化似然时,我们注意到实际上并不关心正态分布中的归一化常数。因此,我们可以写成:

\(\begin{align} \hat{\beta} &= \text{argmax}_\beta \exp\left\{-\frac{1}{2}(y - X\beta)^T(\sigma² I_n)^{-1}(y-X\beta)^T\right\} \\ &= \text{argmax}_\beta \exp\left\{-\frac{1}{2\sigma²}\|y - X\beta\|_2²\right\} \end{align}\) ​​(13)

正如我们之前在最大似然估计的初步探索中所做的那样,我们将利用\(\log\) 函数单调递增这一事实,并优化对数似然。此外,我们将使其成为一个最小化问题,而非最大化问题。一般来说,对于任何性质良好的函数\(f\)

\(\begin{align} \text{argmax}_\theta f(\theta) &= \text{argmax}_\theta \log(f(\theta)) \\ &= \text{argmin}_\theta \left[-\log(f(\theta))\right] \\ \end{align}\) ​​(14)

因此,我们可以写成:

\(\begin{align} \hat{\beta} &= \text{argmax}_\beta \exp\left\{-\frac{1}{2\sigma²}\|y - X\beta\|_2²\right\} \\ &= \text{argmin}_\beta \left[\frac{1}{2\sigma²}\|y - X\beta\|_2² \right]\\ &= \text{argmin}_\beta \|y - X\beta\|_2² \end{align}\) ​​​(15)

因此,我们发现最大化数据的高斯似然函数,完全等价于最小化平方损失。这在回归问题中是普遍成立的:我们可以通过选择损失函数并最小化它,或者选择对应的似然函数并最大化它,来得到相同的答案。

回归预测中的不确定性

重要的是要记住,在对新数据点进行预测时,存在多种不确定性来源。回想一下,我们的模型表明 \(\hat{y} = X\hat{\beta} + \epsilon\)。在进行预测时,我们对第一项 \(X\hat{\beta}\)​ 存在一些不确定性,因为我们估计的系数依赖于随机数据。我们还存在来自第二项的额外不确定性,这取决于模型估计的数据围绕平均预测值的变异性有多大。

逻辑回归

回想一下,在逻辑回归中,我们试图预测二元输出:\(y_i \in \{0, 1\}\)。我们试图预测 \(y_i\)​ 为 1 的概率,我们将其称为 \(\hat{y}\)​:

\(\hat{y}_i = \sigma(\beta^T x_i),\) (16)

其中 \(\sigma\) 是 sigmoid 函数,它将实数值转换为 0 到 1 之间的值。为了找到 \(\beta\),我们最小化二元交叉熵损失:

\(\hat{\beta} = \text{argmin}_\beta \sum_i -\left[ y_i \ln(\hat{y}_i) + (1-y_i) \ln(1-\hat{y}_i) \right]\) (17)

你将在讨论工作表中证明,如果我们假设 \(y\) 的似然模型是参数为 \(\sigma(\beta^T x_i)\) 的伯努利分布,那么最大化似然就等价于最小化二元交叉熵损失。

如需更深入地复习逻辑回归,请参阅 Data 100 教材的第二十三章。请注意,我们的符号略有不同:

  • 我们使用 \(\beta\) 而非 \(\theta\) 表示系数。

  • 我们使用 \(\hat{y}\)​ 表示预测值,而非 \(f_{\hat{\theta}}\)​。

YouTubeVideo('-xHu9FfVKqg')

正在加载...

使用 scikit-learn 拟合模型

你可能记得,拟合线性模型最简单的方法是使用 scikit-learn 中的 LinearRegression 模型。然后,我们可以分别使用 coef_intercept_ 属性来检查系数和截距,并使用 predict() 方法对新数据点进行预测。

import numpy as np
from sklearn.linear_model import LinearRegression
x = np.random.normal(size=[8, 2])
y = np.random.normal(size=8)

model = LinearRegression()
model.fit(x, y)
model.intercept_, model.coef_

(-0.0638684061258262, array([ 0.15201513, -0.62278861]))

在本章中,我们将使用两个新包来进行频率学派和贝叶斯范式下的回归分析。第一个是 statsmodels,它使用频率学派方法拟合线性模型;第二个是 Bambi,它使用 PyMC 在贝叶斯线性模型中进行近似推断。

已知、未知、随机与固定

在回归建模中,我们通常为观测到的目标值 \(y\) 使用概率模型,但我们倾向于假设预测变量 \(X\)固定且已知的。总结如下:

变量 描述 已知或未知? 固定或随机?(贝叶斯) 固定或随机?(频率学派)
\(x\) 预测变量 已知 固定且已知 固定且已知
\(y\) 目标值 训练集已知,测试集未知 随机 随机
\(\beta\) 系数 未知 随机 固定

广义线性模型

原文:data102.org/ds-102-book/content/chapters/03/glms

%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()

import pymc as pm
import bambi as bmb
import arviz as az
import statsmodels.api as sm

广义线性模型

在本节中,我们将学习一种称为广义线性模型(GLMs)的线性回归扩展。特别是,GLMs 与线性回归相似,但有两个重要扩展:

  1. 我们不再直接使用 \(X\beta\) 的结果作为平均预测,而是先应用一个称为逆连接函数或 \(g^{-1}\) 的非线性函数,使我们的平均预测变为 \(g^{-1}(X\beta)\)。虽然我们可以在此处使用任意函数,但我们将看到几个特别有用的例子。

  2. 我们不再假设数据似然模型是围绕平均预测的正态分布,而是允许任意的似然分布(但仍以平均预测 \(g^{-1}(X\beta)\) 为中心)。

我们将通过一个示例来演示这类模型为何有用,以及选择不同的逆连接函数和似然模型如何改变我们得到的预测结果。

从贝叶斯和频率学派视角看线性回归

在本节的其余部分,我们将使用一个包含自 2000 年以来各州建造的风力涡轮机数量的数据集,重点关注俄克拉荷马州。它包含以下列:

  • t_built:每年建造的涡轮机数量

  • t_cap:当年新增的发电容量

  • year:年份,存储为自 2000 年以来的年数

  • totals:自 2000 年以来该州建造的涡轮机总数

  • log_totals:总数的对数。

源代码

turbines = pd.read_csv('turbines.csv')
# The "year" column contains how many years since the year 2000
turbines['year'] = turbines['p_year'] - 2000
turbines = turbines.drop('p_year', axis=1)
turbines.head()

ok_filter = (turbines.t_state == 'OK') & (turbines.year >= 0)

# Turbines in Oklahoma from 2000 on
ok_filter = (turbines.t_state == 'OK') & (turbines.year >= 0)
ok_turbines = turbines[ok_filter].sort_values('year')
ok_turbines["totals"] = np.cumsum(ok_turbines["t_built"])
# Log-transform the counts, too
ok_turbines["log_totals"] = np.log(ok_turbines["totals"])
ok_turbines.head(n=5)

加载中...

ok_turbines.plot('year', 'totals');

<Figure size 640x480 with 1 Axes>

观察这些数据,我们可以立即看出线性回归可能不是一个好的选择:两个变量之间的关系似乎是指数关系而非线性关系。我们可以通过以下两种方式之一来解决这个问题:

  1. 对输出数据进行对数转换,这样我们预测的就是\(\log(\)涡轮机数量\()\),然后使用线性模型。

  2. 将指数关系纳入我们的模型。

我们将从第一种方法开始,看到它的效果相当不错,然后看看广义线性模型如何通过采用第二种方法帮助我们做得更好。

ok_turbines.plot('year', 'log_totals');

<Figure size 640x480 with 1 Axes>

除了第一年(\(t=0\),即 2000 年)的异常值外,线性模型在这里似乎是一个很好的拟合。

由于线性回归是一种统计模型,我们通过随机数据来估计未知量(模型系数),因此我们可以从频率学派或贝叶斯学派的范式来处理它。让我们看看在这两种设置下,对数计数的线性模型将如何工作。

涡轮机数据线性回归:频率学派方法

在频率主义范式中,我们将未知系数视为固定值,并使用最大似然(或类似技术)进行估计。虽然如前一节所见,使用scikit-learn是在频率主义范式中实现线性回归的完全有效的方法,但它不支持我们将在本章中使用的许多广义线性模型。因此,本章我们将改用statsmodels包(通过import statsmodels as sm导入)。

我们将主要使用sm.GLM类,它接收一个数组或序列作为y,一个数组或数据框作为X,以及一个模型族:我们稍后会探索更多模型族,但目前我们将坚持使用 OLS,可以通过sm.families.Gaussian()实现。为了在模型中包含截距项,我们需要通过应用sm.add_constant()函数来扩充数据。

上述模型如下所示:

gaussian_model_intercept = sm.GLM(
    np.log(ok_turbines.totals), sm.add_constant(ok_turbines.year),
    family=sm.families.Gaussian()
)
gaussian_results = gaussian_model_intercept.fit()
print(gaussian_results.summary())
 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                 totals   No. Observations:                   17
Model:                            GLM   Df Residuals:                       15
Model Family:                Gaussian   Df Model:                            1
Link Function:               Identity   Scale:                          1.1810
Method:                          IRLS   Log-Likelihood:                -24.472
Date:                Sat, 11 Oct 2025   Deviance:                       17.716
Time:                        13:37:17   Pearson chi2:                     17.7
No. Iterations:                     3   Pseudo R-squ. (CS):             0.9131
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const          3.2602      0.590      5.526      0.000       2.104       4.417
year           0.3023      0.047      6.435      0.000       0.210       0.394
============================================================================== 

模型输出在顶部显示了一些关于模型的有用信息,然后在底部显示了关于估计系数的信息:

  • coef,系数本身,

  • std err,即标准误(估计量的标准差)

  • z,系数在零假设为 0 的假设检验中的\(z\) 分数,

  • P>|z|,上述假设检验的\(p\) 值,以及

  • [0.0250.975],估计系数\(95\%\)置信区间的上下界。

该模型告诉我们,可以按如下方式预测任意年份\(t\) 中风机数量\(N\) 的对数:

\(\log(N) = 3.26 + 0.30 \times t\) (1)

在大多数情况下,我们更感兴趣的是实际的风机数量,而非其对数:因此,我们可以对等式两边取指数得到:

\(N = e^{3.26} e^{0.3t}\) (2)

这也告诉我们,根据这个模型,每年涡轮机的数量会乘以 \(e^{0.3}\),即大约 1.35 倍。

我们也可以将这一预测可视化:

ok_turbines['pred_freq'] = np.exp(3.2602 + 0.3023 * ok_turbines['year'])

plt.plot(ok_turbines['year'], ok_turbines['totals'], label='Actual count')
plt.plot(ok_turbines['year'], ok_turbines['pred_freq'], label='Predicted count')
plt.legend();

<Figure size 640x480 with 1 Axes>

我们可以看到,该模型的预测在 2000 年至 2016 年间是合理的,但之后开始与现实产生偏差。

涡轮机数据线性回归:贝叶斯方法

ok_turbines

加载中...

在贝叶斯范式中,我们将未知系数视为随机变量,并根据观测到的数据计算它们的后验分布。虽然我们可以在 PyMC 中实现整个模型,但 Bambi 包在 PyMC 之上提供了一个便捷的层,让我们可以用更少的代码指定模型。我们将使用 bmb.Model 类,它接收一个公式、一个数据框和一个模型族(目前,我们将坚持使用 gaussian 族进行普通最小二乘法)。公式的形式为 y ~ predictor1 + predictor2 + ...,其中每个变量都是所提供数据框中的列名。以下是我们如何实现上述模型:

# The y-values are in the column `log_totals`, and the x-values are in the column `year`. So:
gaussian_model = bmb.Model(formula='log_totals ~ year', data=ok_turbines, family="gaussian")
gaussian_trace = gaussian_model.fit(random_seed=0)
Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [sigma, Intercept, year] 

加载中...加载中...

Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 1 seconds. 

我们可以使用 arviz 库的 plot_posterior 函数查看结果:

az.plot_posterior(gaussian_trace)
plt.tight_layout()

<Figure size 2208x552 with 3 Axes>

前两行各自代表一个系数。第三行 log_totals_sigma 是数据点围绕平均预测值的估计标准差。左列显示了该系数的后验分布(通过对样本直方图使用核密度估计得出),右列显示了在哈密顿蒙特卡洛采样过程中获得的样本轨迹。

观察 year 系数(第二行)的后验分布,我们发现结果与频率学派版本相似:最大后验估计和最小均方误差估计都在 0.3 左右。通过检查直方图,我们应该预期在后验分布下,该系数通常会在 0.2 到 0.4 之间,超出此范围的值可能性相当低。这种不确定性是合理的:由于我们仅从十七个数据点估计这些系数,因此在我们估计的系数中存在一些不确定性是合理的。和之前一样,我们可以在数据背景下解释这些估计:每年,涡轮机的数量大约以 1.3 的倍数增长。

当我们使用采样进行推断时,可以将每个样本视为一条回归线:每个样本都有一个 intercept 值、一个 year 的系数值,以及一个残差的估计 \(\sigma\)。因此,我们可以将每个样本绘制成一条线:

def identity(x):
    return x

def plot_posterior_samples(trace, turbines_df, num_lines=40, show_logy=True):
    f, ax = plt.subplots(1, 1)
    intercept = trace.posterior['Intercept'].values.flatten()
    slope = trace.posterior['year'].values.flatten()
    indices = np.random.choice(np.arange(slope.size), num_lines, replace=False)
    if show_logy:
        y_func = identity
    else:
        y_func = np.exp

    for i in indices:
        pred = y_func(intercept[i] + slope[i] * turbines_df['year'])
        ax.plot(turbines_df['year'], pred, color='gray', alpha=0.3)

    ax.scatter(turbines_df['year'], y_func(turbines_df['log_totals']))
    ax.set_xlabel('Years since 2000')
    if show_logy:
        ax.set_ylabel('log(turbine count)')
    else:
        ax.set_ylabel('turbine count')
plot_posterior_samples(gaussian_trace, ok_turbines, show_logy=True)

<Figure size 640x480 with 1 Axes>

np.identity?
plot_posterior_samples(gaussian_trace, ok_turbines, show_logy=False)

<Figure size 640x480 with 1 Axes>

这些图表帮助我们看出,在对数尺度上,许多直线的斜率过高,这很可能是由于 \(t=0\) 处的离群值造成的。这对应于在观察计数时,2016 年之后出现了过快的指数增长。

需要注意的是,这仅向我们展示了平均预测 截距 + 系数 * 年份 的不确定性:它没有展示平均预测周围变异的不确定性。具体来说,请记住,我们通过假设 \(y \sim \mathcal{N}(X\beta, \sigma² I)\) 来获得线性回归的预测。这些线向我们展示了平均预测 \(X\beta\) 的不确定性,但没有展示估计的方差 \(\sigma\) 有多大:我们稍后会回到这一点。

练习你如何构建类似的图表来可视化之前频率学派结果的不确定性?

先验分布在哪里?

你可能已经注意到,我们刚刚实现了一个贝叶斯模型,但从未指定任何先验分布。在这种情况下,Bambi 会为我们选择“合理”的默认值,其灵感来源于 R 库 rstanarm。这些默认值被选为弱信息先验,它们不会对系数应为何值编码太多信息。

你可以在 rstanarm 文档中阅读更多关于这些默认选择的信息。

线性回归总结

我们看到线性回归可以在频率学派和贝叶斯学派两种范式中实现:归根结底,两种方法都为我们提供了估计的系数,以及一些不确定性的度量。我们可以使用这些系数来解释模型(如上所述),并为新的数据点进行预测。

我们看到,在使用线性回归进行预测时,我们首先将每个特征乘以其对应的系数,将它们全部相加以获得平均预测,并假设该平均值周围存在一些随机性。

为了从数据中确定系数,我们可以采用频率学派或贝叶斯学派的方法。在频率学派范式中,我们使用如最大似然估计(MLE)这样的频率学派方法进行估计;在贝叶斯学派范式中,我们则使用样本近似系数的后验分布(以观测数据为条件)。

在本章的剩余部分,我们将在频率学派和贝叶斯学派范式之间来回切换,以阐述各自的思想。

超越线性回归:广义线性模型

上述回归模型在 2016 年之前的年份中表现尚可,但它没有考虑到我们预测的变量是一个整数(意味着其取值为 \(0, 1, 2, 3, \ldots\))。当我们说 \(y|\beta \sim N(X\beta, \sigma² I)\),并对 \(y\) 使用对数变换数据时,我们隐含地假设了 \(y\) 永远不可能为 0。我们将首先提出一个问题:能否使用一种专门为此类数据设计的不同似然函数?在回答这个问题时,我们将探讨广义线性模型的两个例子:泊松回归和负二项回归。

泊松回归

您可能会发现复习《Data 140》教材的第 7.1 节会有所帮助,该节涵盖了泊松分布。

回想一下,泊松分布是一种关于计数和类计数值的分布。它有一个正的参数 \(\lambda\),代表其均值(同时也是方差)。

在泊松回归中,我们将假设每个观测值 \(y_i\)​ 服从泊松似然,并使用线性组合 \(x_i^T\beta\) 来帮助确定参数。泊松分布仅对正的参数值有定义,但 \(x_i^T\beta\) 可能为负。有几种方法可以将可能为负的值转换为正值,以便用作参数(例如,取绝对值、平方等),但受上一节中对输出进行对数变换的启发,我们将采用指数变换。

因此,我们将使用 \(\exp(x_i^T \beta)\) 作为均值。我们可以写出观测数据点 \(y_i\)​ 的似然函数:

\(y_i | \beta \sim \text{Poisson}(\exp(x_i^T \beta))\) (3)

和之前一样,我们可以使用这个似然模型在频率学派或贝叶斯学派的框架下估计系数 \(\beta\)。在本节中,我们将选择采用贝叶斯方法,主要是为了利用我们在上一节编写的 plot_posterior_samples 函数来帮助我们可视化模型中的不确定性。

使用 Bambi,从线性回归切换到泊松回归相当简单:

poisson_model = bmb.Model(formula='totals ~ year', data=ok_turbines, family='poisson')
poisson_trace = poisson_model.fit(random_seed=0)
Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [Intercept, year] 

正在加载...正在加载...

Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 1 seconds. 
az.plot_posterior(poisson_trace)
plt.tight_layout()

<尺寸为 1472x552,包含 2 个坐标轴的图形>

将此与高斯模型的结果进行比较,我们可以看到两个系数的后验分布显著变窄了。此外,year的系数值似乎小得多:之前的后验分布中心大约在 0.3,而现在整个分布似乎都紧密地集中在 0.183 附近。

np.exp(0.183)

1.2008144080808307

这仅对应于 \(20\%\) 的平均增长率,而我们之前模型的平均增长率为 $35%$3。较低的增长率似乎是合理的:回想一下,线性回归得出的曲线增长过快,尤其是在 2016 年之后。但是,结果中的确定程度令人担忧:考虑到我们只有 17 个数据点,我们的结果应该包含更多的不确定性。我们可以使用 plot_posterior_samples 函数将每个样本绘制成一条线/曲线,来可视化模型是多么“过度自信”:

plot_posterior_samples(poisson_trace, ok_turbines, show_logy=True)

<Figure size 640x480 with 1 Axes>

plot_posterior_samples(poisson_trace, ok_turbines, show_logy=False)

<Figure size 640x480 with 1 Axes>

和之前一样,重要的是要记住,这些线只向我们展示了平均预测 \(X\beta\) 中的估计不确定性:我们并没有可视化泊松模型本身所显示的任何不确定性。

为什么结果如此不同?

让我们思考一下选择泊松似然时所隐含的假设。特别是,泊松分布的一个重要特性是其均值等于其方差。因此,虽然线性模型可以允许平均预测为 \(\log(N) = 4\) 且方差为 1.5,但泊松分布要求均值和方差必须相同。

这个问题因泊松分布建模的是真实计数而非对数计数而加剧:换句话说,考虑 2018 年,当时涡轮机数量约为 4000。均值为 4000 的泊松分布方差为 4000,换句话说,标准差仅为 63 左右。

显然,泊松分布对于拟合这些数据来说是一个糟糕的选择!当一个模型假设的方差低于数据中实际存在的方差时(就像这个泊松模型所做的那样),我们称数据为过度离散:这意味着相对于模型的假设,数据过于分散。为了解决这个问题,我们应该选择一个不同的分布,它能够让我们同时控制方差和均值。

这在之前的正态似然中不是问题:因为正态分布有独立的均值和方差参数,我们可以分别选择它们来反映方差可能高于均值这一事实。

负二项回归

负二项分布也是一种计数分布,但它允许比泊松分布更复杂的情况。我们可以从以下两种方式之一来理解它:

  • 它是\(r\) 个[几何随机变量]之和,每个变量的参数为\(p\)(成功概率)。

  • 如果均值参数(上方的\(\lambda\))也是随机的,那么它就像泊松分布。

负二项分布有多种不同的参数化方法。我们如何选择使用哪一种?答案有两个:

  1. 我们需要一种允许我们选择均值的参数化方法,因为我们希望\(y_i\)​的均值为\(\exp(x_i^T \beta)\)(换句话说,我们想做几乎与泊松回归相同的事情,但我们希望对观测值使用负二项分布而非泊松分布)。

  2. 由于我们通过 Bambi 使用 PyMC,因此受限于它所支持的参数化方式。

尽管该分布的形式明显更复杂,且操作它需要更多工作,但在回归模型中使用它仅需对我们之前所做的进行微小改动:

negbin_model = bmb.Model(formula='totals ~ year', data=ok_turbines, family='negativebinomial')
negbin_trace = negbin_model.fit(random_seed=0)
Auto-assigning NUTS sampler...
Initializing NUTS using jitter+adapt_diag...
Multiprocess sampling (4 chains in 4 jobs)
NUTS: [alpha, Intercept, year] 

加载中...加载中...

Sampling 4 chains for 1_000 tune and 1_000 draw iterations (4_000 + 4_000 draws total) took 1 seconds. 
np.sqrt(4000)

63.245553203367585

ok_turbines

加载中...

az.plot_posterior(negbin_trace)
plt.tight_layout()

<Figure size 2208x552 with 3 Axes>

此处,year系数的后验分布范围更广,均值约为 0.24:

np.exp(0.24)

1.2712491503214047

这大约对应 27%的增长率。我们可以可视化推断中系数的不确定性

plot_posterior_samples(negbin_trace, ok_turbines, show_logy=True)

<Figure size 640x480 with 1 Axes>

plot_posterior_samples(negbin_trace, ok_turbines, show_logy=False)

<Figure size 640x480 with 1 Axes>

这似乎是一个整体上更好的拟合:各条线/曲线中的不确定性与观测数据相匹配,表明在给定观测数据的情况下,大多数预测线在某种程度上是合理的。再次重要的是要记住,这里可视化的不确定性仅在于平均预测,我们并未可视化负二项模型中估计方差的不确定性。

比较泊松模型和负二项模型,两者非常相似:

  • 两种模型都采用了特征和系数的线性组合,其系数值有些相似

  • 两种模型都使用指数函数对线性组合的结果进行了变换

关键区别在于我们对平均预测值周围观测数据点的似然模型:泊松模型假设了泊松分布,其方差由均值决定。而负二项模型则假设了负二项分布,其方差可以作为我们模型中的一个单独变量从数据中推断出来。这使得负二项模型能够更好地捕捉平均预测中的不确定性。

练习使用 statsmodels 在频率主义范式下实现泊松回归和负二项回归。结果相似吗?

提示:你可能会发现使用 sm.families.Poisson()sm.families.NegativeBinomial() 很有帮助。

广义线性模型

到目前为止,你已经在贝叶斯框架下看到了四种不同的回归模型:

  • 线性回归,用于预测实值输出

  • 逻辑回归,用于预测二元输出(分类)

  • 泊松回归,用于预测计数

  • 负二项回归,用于预测计数

让我们回顾一下它们的共同点和不同点:

  1. 对于所有四种模型,计算我们对 \(y_i\)​ 的预测都始于计算 \(x_i^T \beta\)。这部分是 \(x_i\)​ 的一个线性函数,即使我们之后会对其进行非线性处理。

  2. 每种模型都有一个不同的函数,我们用它来计算 \(y_i\)​ 从 \(x_i^T \beta\) 得出的平均值。由于这个函数将线性变换后的输入 \(x\) 与输出 \(y\) 联系起来,你可能会期望我们称它为链接函数:这很有道理。然而,惯例恰恰相反,我们称它为逆链接函数。正如你从这个名字可能猜到的那样,链接函数是逆链接函数的逆。

  3. 对于每一种情况,我们为数据点的似然使用了不同的分布。在所有情况下,该分布的均值始终是上述逆链接函数的输出。

下表总结了我们所见过的四个版本中,关于似然和链接函数的不同选择:

回归 逆链接函数 链接函数 似然
线性 恒等 恒等 高斯
逻辑回归 sigmoid logit 伯努利
泊松回归 指数 对数 泊松
负二项回归 指数 对数 负二项

这些思想构成了所谓广义线性模型(GLMs)的基础。一旦我们选择了链接函数和似然分布,我们的模型就完全确定了,并且我们可以近似\(\beta\)中系数的后验分布。

GLM 工作流程

使用 GLM 进行预测时,通常遵循以下一般步骤:

  1. 通过确定您要预测的内容(\(y\))以及用于预测它的内容(\(x\))来构建您的预测问题。这取决于您试图解决的实际问题,以及可能有哪些数据可以帮助您解决它。

  2. \((x, y)\)对的形式收集训练数据。这可能需要在公开数据、您有权访问的私有或专有数据集中进行搜索,甚至可能需要外出收集新数据或付费收集数据。重要的是确保您收集的任何数据\(x\) 对于预测\(y\) 是有用的。在这一步,确定您可能想要计算的任何特征也很有帮助,特别是那些可能从\(x\) 的现有列中推导出来的特征。

  3. 为您的数据选择一个合理的链接函数和似然模型。例如,如果您的输出是二元的,那么逻辑回归可能比较合适。如果您的输出是计数值,那么泊松回归或负二项回归可能是一个很好的选择。您可以在维基百科的广义线性模型页面上看到更多可能性。

  4. 使用训练数据拟合模型:这通常涉及使用 Bambi、statsmodels 或scikit-learn等软件包,通过计算确定在训练数据集上有效的模型系数。

  5. 检查模型是否确实对数据拟合良好:这一步通常称为模型检验,旨在确保模型能够并将在你的数据集上做出良好的预测。我们将在下一节深入探讨模型检验。

  6. 解释系数:广义线性模型(GLMs)的一个优势在于其系数具有直观的解释性。在线性回归中,系数告诉我们,如果对应的预测变量增加一定量,输出预测值会增加多少。我们在本节前面看到,在泊松回归和负二项回归(或对\(y\) 值进行对数变换的线性回归)中,系数表示的是乘以\(\exp(\beta)\)倍的倍增效应。在逻辑回归中,系数告诉我们对数几率增加了多少。

  7. 根据新的\(x\) 值,为\(y\) 未知的新数据生成新的预测。通常,我们使用预测模型来帮助预测未来的数据点。这些预测是通过将我们在步骤 4 中学到的系数乘以新数据点的预测变量,并应用逆链接函数来完成的。这通常由计算处理:大多数用于拟合模型的软件包也具备为新数据点进行预测的能力。

  8. 报告系数和新预测的不确定性:由于我们使用带有固有不确定性的随机数据来拟合模型,因此从这些数据中得出的任何结果也都具有不确定性。这包括我们在步骤 4 中拟合并在步骤 6 中解释的估计系数,以及我们在步骤 7 中做出的新预测。我们将在本章后面花更多时间讨论如何量化不确定性。

模型检验与评估

原文:data102.org/ds-102-book/content/chapters/03/model-checking

import numpy as np
import pandas as pd
from IPython.display import YouTubeVideo

import statsmodels.api as sm
import pymc as pm
import arviz as az
import bambi as bmb

%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns

# Turn off logging (console output) for PyMC
import logging
logging.getLogger("pymc").setLevel(logging.ERROR)

sns.set()

将模型拟合到数据时,一个重要的问题是:这个模型能很好地代表数据吗

在本节中,我们将从频率派和贝叶斯的角度来回答这个问题。

我们将从两个角度考虑模型评估:

  1. 我们的模型是否很好地拟合了我们用来拟合它们的数据(在预测的情况下,即我们的训练数据)?

  2. 我们的模型(尤其是预测模型)能否很好地泛化到新的、以前未见过的数据?

在制定和拟合模型时,回答这两个问题都很重要。在实践中,通常更容易先回答(并调试/迭代)第一个问题,然后再处理第二个问题。

YouTubeVideo('RlQNAjvm55A')

加载中...

频率派模型检验

在频率派框架下,我们常使用拟合优度指标来评估模型。让我们回顾一下用于预测俄克拉荷马州涡轮机数量的三个不同模型:

# Load and preprocess the data
turbines = pd.read_csv('turbines.csv')
# The "year" column contains how many years since the year 2000
turbines['year'] = turbines['p_year'] - 2000
turbines = turbines.drop('p_year', axis=1)
# Turbines in Oklahoma from 2000 on
ok_filter = (turbines.t_state == 'OK')
ok_turbines = turbines[ok_filter].sort_values('year')

ok_turbines = ok_turbines[ok_turbines['year'] > 0]
ok_turbines["totals"] = np.cumsum(ok_turbines["t_built"])
# Log-transform the counts, too
ok_turbines["log_totals"] = np.log(ok_turbines["totals"])
ok_turbines

加载中...

gaussian_model_intercept = sm.GLM(
    np.log(ok_turbines.totals), sm.add_constant(ok_turbines.year),
    family=sm.families.Gaussian()
)
gaussian_results = gaussian_model_intercept.fit()
print(gaussian_results.summary())
 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                 totals   No. Observations:                   17
Model:                            GLM   Df Residuals:                       15
Model Family:                Gaussian   Df Model:                            1
Link Function:               Identity   Scale:                          1.1810
Method:                          IRLS   Log-Likelihood:                -24.472
Date:                Sun, 12 Oct 2025   Deviance:                       17.716
Time:                        14:29:10   Pearson chi2:                     17.7
No. Iterations:                     3   Pseudo R-squ. (CS):             0.9131
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const          3.2602      0.590      5.526      0.000       2.104       4.417
year           0.3023      0.047      6.435      0.000       0.210       0.394
============================================================================== 
poisson_model_freq = sm.GLM(
    ok_turbines.totals, sm.add_constant(ok_turbines.year),
    family=sm.families.Poisson()
)
poisson_results = poisson_model_freq.fit()
print(poisson_results.summary())
 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                 totals   No. Observations:                   17
Model:                            GLM   Df Residuals:                       15
Model Family:                 Poisson   Df Model:                            1
Link Function:                    Log   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -755.42
Date:                Sun, 12 Oct 2025   Deviance:                       1366.3
Time:                        14:29:10   Pearson chi2:                 1.20e+03
No. Iterations:                     5   Pseudo R-squ. (CS):              1.000
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const          4.9697      0.023    219.386      0.000       4.925       5.014
year           0.1829      0.001    132.547      0.000       0.180       0.186
============================================================================== 
negbin_model_freq = sm.GLM(
    ok_turbines.totals, sm.add_constant(ok_turbines.year),
    family=sm.families.NegativeBinomial()
)
negbin_results = negbin_model_freq.fit()
print(negbin_results.summary())
 Generalized Linear Model Regression Results                  
==============================================================================
Dep. Variable:                 totals   No. Observations:                   17
Model:                            GLM   Df Residuals:                       15
Model Family:        NegativeBinomial   Df Model:                            1
Link Function:                    Log   Scale:                          1.0000
Method:                          IRLS   Log-Likelihood:                -134.14
Date:                Sun, 12 Oct 2025   Deviance:                       7.1483
Time:                        14:29:10   Pearson chi2:                     1.90
No. Iterations:                    11   Pseudo R-squ. (CS):             0.6999
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const          4.2059      0.544      7.725      0.000       3.139       5.273
year           0.2389      0.043      5.514      0.000       0.154       0.324
============================================================================== 
/Users/ramesh/anaconda3/lib/python3.11/site-packages/statsmodels/genmod/families/family.py:1367: ValueWarning: Negative binomial dispersion parameter alpha not set. Using default value alpha=1.0.
  warnings.warn("Negative binomial dispersion parameter alpha not " 

使用对数似然进行模型比较

我们比较这三种模型的第一种方法是查看对数似然\(\log(p(y|\beta, x))\)。我们在研究更简单的频率派模型时已经见过这个量。它描述了在给定特定系数\(\beta\)和预测变量\(x\) 的情况下,观测到\(y\) 值的可能性有多大。对于

对于这三种模型中的每一种,似然函数的具体形式各不相同:

  1. 第一个对数似然描述了在假设线性回归的情况下,观测到对数变换后的\(y\) 值的密度

  2. 第二个对数似然描述了在假设具有指数逆连接函数的泊松模型的情况下,观测到原始\(y\) 值作为计数的概率

  3. 第三个对数似然描述了在假设具有指数逆连接函数的负二项式模型的情况下,观测到原始\(y\) 值作为计数的概率

因此,我们可以将对数似然解释为:“根据特定模型,数据出现的可能性有多大?”

请注意,由于存在两个关键差异(连续与离散似然以及对数变换数据),我们无法直接将第一个模型的对数似然与后两个模型进行比较。但是,我们可以直接比较后两个对数似然,因为它们各自描述了根据两个不同模型,观测到相同观测值\(y_1, \ldots, y_n\)​的可能性有多大。正如我们在最大似然估计中看到的,有理由认为对数似然值更高的模型对数据的拟合更好。

比较这些模型,我们看到泊松模型的对数似然值为 -755,而负二项模型的对数似然值约为 -134。我们还知道,由于模型拟合的方式,这些值代表了各自模型族可能达到的最佳对数似然值。因此,我们可以合理地得出结论:在负二项式广义线性模型下,数据出现的可能性远高于在泊松广义线性模型下。

直观上,这与我们在贝叶斯设定中看到的情况一致:由于泊松似然的方差过低,远离预测平均值 \(\hat{y}\)​ 的值出现的可能性极低,因此模型为我们实际观测到的涡轮机计数分配了很低的概率。

请注意,任何模型的对数似然值本身并不能告诉我们该模型的性能。相反,它作为比较应用于同一数据集的不同模型时的相对度量是有用的。因此,一般来说,对数似然值告诉我们一个模型在特定数据集上的表现如何,并可用于比较不同模型对同一数据集的拟合程度。

使用卡方统计量进行模型评估

我们也可以使用卡方(\(\chi²\))统计量来评估模型:在这里,数值越大越好。虽然完整细节超出了本课程的范围,但一般来说,我们预期卡方统计量大致等于数据点数减去参数个数。在上面的例子中,我们有 17 个数据点(观测值)和两个参数(斜率和截距),因此我们预期值应接近 15。

我们可以看到,线性模型(对 \(y\) 值进行对数变换后)的卡方值为 17.7,非常接近 15,表明该模型能很好地拟合数据。而泊松模型的卡方值则高达 1200,表明拟合效果非常差。

卡方统计量背后的直觉

即将推出:请观看视频

YouTubeVideo('wTNETHYrUl8')

加载中...

贝叶斯模型检验

后验预测分布

考虑一个模型,其观测数据点为 \(x_{1:n} = x_1, \ldots, x_n\)​,并有一个未知参数,其似然函数为 \(p(x_{1:n}|\theta)\),先验分布为 \(p(\theta)\)。像往常一样,我们假设数据点在给定 \(\theta\) 的条件下是条件独立同分布的。到目前为止,我们一直专注于获得后验分布 \(p(\theta|x_{1:n})\)

我们也可以考虑一个新数据点在给定所有先前数据点(在边缘化掉未知参数 \(\theta\) 之后)的条件下的分布。这被称为后验预测分布,将是我们进行模型检验的主要工具:

\(p(x_{n+1} | x_1, \ldots, x_n)\) (1)

我们如何计算后验预测分布?回顾上一段,我们更了解在给定 \(\theta\) 的条件下 \(x_{n+1}\)​ 的分布,因此我们可以使用全概率公式:

\(p(x_{n+1}|x_{1:n}) = \int p(x_{n+1}|x_{1:n}, \theta)p(\theta|x_{1:n})\, d\theta\) (2)

我们可以简化第一个条件分布,因为给定 \(\theta\) 时,\(x_{n+1}\)​ 与 \(x_{1:n}\)​ 是条件独立的;换言之,\(p(x_{n+1}|x_{1:n}, \theta) = p(x_{n+1}|\theta)\)。第二个条件分布是 \(\theta\) 的后验分布。因此:

\(\begin{align*} p(x_{n+1}|x_{1:n}) &= \int p(x_{n+1}|x_{1:n}, \theta)p(\theta|x_{1:n})\, d\theta \\ &= \int p(x_{n+1}|\theta)p(\theta|x_{1:n})\, d\theta \\ &= \mathbb{E}_{\theta|x_{1:n}}\left[p(x_{n+1}|\theta)\right] \end{align*}\) ​(3)

实际上,精确计算这个积分与计算贝叶斯规则中的分母一样困难,因为我们必须对所有可能的\(\theta\)进行积分。对于高维\(\theta\)的情况,如前所述,积分可能无法计算!在实践中,我们通常用样本来近似后验分布,然后用它来近似后验预测分布。

具体来说,为了计算新数据点的后验预测分布,我们使用由样本定义的 \(\theta|x_{1:n}\)​ 的经验分布来计算上述期望。

换句话说,给定近似 \(p(\theta|x_{1:n})\) 的样本 \(\theta^{(1)}, \ldots, \theta^{(t)}\),我们可以:

  • 计算特定值 \(x_{n+1}\)的后验预测分布,通过对 \((1/t)\sum_{i=1}^t p(x_{n+1}|\theta^{(t)})\) 取平均。

  • 从后验预测分布中抽取一个样本,方法是:

    • 首先,从 \(\theta^{(1)}, \ldots, \theta^{(t)}\) 中均匀随机抽取一个样本,称之为 \(\theta^*\)

    • 然后从基于该样本的条件似然中抽取,\(p(x_{n+1}|\theta^*)\)

使用 PPD 检查回归

在贝叶斯广义线性模型中,我们观测到的数据点是 \(y\)\(y_1, \ldots, y_n\)​。因此,PPD 是 \(p(y_{n+1} | x_{n+1}, y_1, \ldots, y_n)\)。注意,由于预测/回归问题的性质,我们以对应的 \(x\) 值为条件。\(x\) 是固定且已知的,而 \(y_{n+1}\)​ 将依赖于 \(x_{n+1}\)​。另一种理解方式是尝试回答这个问题:“如果不知道 \(x_{n+1}\)​,你将如何预测 \(y_{n+1}\)​,并且这样的预测是否有用?”

PyMC 为我们处理了设置后验预测检查的所有细节,因此我们可以定义以下函数来帮助可视化其抽取的样本。最关键的一行是 model.predict(trace, kind='response'),它指示 PyMC 通过从后验预测分布中抽样,为每个 \(x\) 值生成预测。

练习:你可以传递给 model.predict() 函数的 kind= 参数还有哪些其他可能的值,它们分别有什么作用?

def show_posterior_predictive(
    model, trace, turbines_df, 
    is_log_y=False, num_lines=40, ax=None
):
    if ax is None:
        _, ax = plt.subplots(1, 1)
    N = turbines_df.shape[0]
    # Adds posterior predictive samples to the trace
    model.predict(trace, kind='response')

    y_name = model.response_component.response.name
    # a num_samples x 17 array with num_samples different
    # predictions from the PPC
    pred_arr = trace.posterior_predictive[y_name].values.reshape(-1, N)
    if is_log_y:
        pred_arr = np.exp(pred_arr)

    for i in range(num_lines):
        ax.plot(
            turbines_df['year'], pred_arr[i], alpha=0.1, color='green'
        )

    ax.scatter(turbines_df['year'], turbines_df['totals'])
    # This value is hard-coded to help us see the PPCs for all 3
    # models on the same scale
    ax.set_ylim([0, 30000])

现在,让我们使用这个函数来可视化上一节中我们查看的三个模型的 PPC。

# First, we need to set up and draw posterior samples for each model
gaussian_model = bmb.Model(formula='log_totals ~ year', data=ok_turbines, family='gaussian')
gaussian_trace = gaussian_model.fit(random_seed=0, progressbar=False)

poisson_model = bmb.Model(formula='totals ~ year', data=ok_turbines, family='poisson')
poisson_trace = poisson_model.fit(random_seed=0, progressbar=False)

negbin_model = bmb.Model(formula='totals ~ year', data=ok_turbines, family='negativebinomial')
negbin_trace = negbin_model.fit(random_seed=0, progressbar=False)

# Now, visualize the PPC using the function we just defined
f, axes = plt.subplots(1, 3, figsize=(10, 5))
show_posterior_predictive(gaussian_model, gaussian_trace, ok_turbines, is_log_y=True, ax=axes[0])
axes[0].set_title('Linear regression (log-xfm y)')

show_posterior_predictive(poisson_model, poisson_trace, ok_turbines, is_log_y=False, ax=axes[1])
axes[1].set_title('Poisson regression')

show_posterior_predictive(negbin_model, negbin_trace, ok_turbines, is_log_y=False, ax=axes[2])
axes[2].set_title('Negative binomial regression')

f.suptitle('Posterior predictive check: three methods for turbine prediction')
plt.tight_layout()

<尺寸为 1000x500 的图表,包含 3 个坐标轴>

每条绿线代表后验预测分布的一个样本。我们可以将这些视为模型根据其表征所设想的可能数据序列。

观察结果,我们可以立即得出几点发现:

  • 我们再次看到泊松回归过于自信:每个后验预测分布样本都落在(或极其接近)回归线上,观测到的变异性微乎其微。

  • 另外两个模型的轨迹均呈现上下波动,而真实模式是单调非递减的。这是我们模型的一个弱点:具体来说,我们假设\(y\) 值在条件上是独立同分布的。

  • 线性回归的样本表现出极大的变异性:我们可以看到其中许多预测显示 2015 年起风电装机量远超 15,000 台,这远高于实际观测值。这表明模型允许的变异性过大。

  • 负二项式模型的值域合理得多,至少在 2018 年之前如此。可以想象,在不同的政治经济环境下,俄克拉荷马州的风电装机量可能根据这些预测增加或减少。虽然该模型仍存在些许过度变异,但相比线性回归模型已合理许多。

注意以上所有分析都需要一定的主观判断和领域知识!在许多情况下,当每个唯一的\(x\) 值对应的不是单一观测\(y\) 值而是一组数据点时,我们可以从后验预测检查中得出量化评估,例如比较观测\(y\) 值的方差与后验预测样本的方差,但此类方法的细节已超出本课程范围。

练习:回顾 2000 年的异常值(\(t=0\))影响了我们的预测,可能导致所有三个模型的斜率估计偏高。尝试从数据集中移除该异常值后重新拟合上述三个模型。结果是否发生变化?如何变化及原因何在?

不确定性量化

原文:data102.org/ds-102-book/content/chapters/03/uncertainty-quantification

import numpy as np
import pandas as pd
from IPython.display import YouTubeVideo
from scipy import stats

import statsmodels.api as sm
import pymc as pm
import arviz as az
import bambi as bmb

%matplotlib inline

import matplotlib.pyplot as plt
import seaborn as sns

# Turn off logging (console output) for PyMC
import logging
logging.getLogger("pymc").setLevel(logging.ERROR)

sns.set()

我们处理的(几乎)每个数据集都存在一定程度的不确定性。从样本的随机性、测量误差、数据与其所代表的基础事实之间的差异,以及更多因素,都可能导致不确定性。

因此,任何我们使用数据计算得出的结果也会继承一些不确定性。所以,始终报告这种不确定性至关重要:

  • 如果我们推断一个参数或趋势,就应当报告推断中的不确定性。

  • 如果我们做出预测,就应当报告预测中的不确定性。

  • 如果我们得出结论,就应当报告结论中的不确定性。

尽管从频率派和贝叶斯视角看,不确定性量化的表现形式略有不同,但上述核心理念在任何情况下都成立。

频率派不确定性量化

在频率派框架下,我们将数据视为随机的,而将未知量视为固定的。因此,我们量化的任何不确定性都仅针对数据!我们不能做出诸如“未知量很可能在...范围内”或“参数为...的概率是...”这样的陈述。

尽管这些表述可能更直观,并且可能符合我们在量化不确定性时想要表达的意思,但它们只有在为未知量建立概率模型的背景下才有意义:我们必须采用贝叶斯方法才能做出此类表述。

在频率论框架下,我们量化不确定性的主要方式是利用估计量的分布。具体来说,当我们用估计量 \(\hat{\theta} = f(x_1, \ldots, x_n)\) 来估计某个参数 \(\theta\) 时,该估计量是随机的,因为它依赖于随机数据(即使 \(\theta\) 是固定的)。因此,我们可以利用 \(\hat{\theta}\) 的分布来量化不确定性。

我们已经知道,如果掌握了 \(\hat{\theta}\) 的分布,我们就可以构建一个置信区间,这能让我们了解如果观测到不同的数据集,我们的估计量可能会有多大变化。但在构建之前,我们需要先确定 \(\hat{\theta}\) 的分布。

YouTubeVideo('ZDqSXtm0vBM')

加载中...

利用中心极限定理进行不确定性量化

关于中心极限定理的更多内容,请参阅 Data 140 教材第十四章

如果我们想利用 \(\hat{\theta}\) 的分布来量化不确定性,那么首先要回答的问题是:“\(\hat{\theta}\) 的分布是什么?”

\(\theta\)为样本均值时,我们可以用统计学中最优美的结果之一来回答这个问题:中心极限定理。简而言之,该定理指出,若\(\hat{\mu}\)是数据点\(x_1, \ldots, x_n\)的样本均值,其中每个\(x_i\)的均值为\(\mu\)且(有限)方差为\(\sigma²\),则\(\hat{\mu}\)的分布会收敛于一个均值为\(\mu\)、方差为\(\sigma²/n\)的正态分布。

请注意,类似的结果适用于任何最大似然估计!虽然细节超出了本课程的范围,但可以证明,如果 \(\hat{\theta}_{MLE}\)​ 是基于数据点 \(x_1, \ldots, x_n\)​ 对某个参数 \(\theta\) 的最大似然估计,那么 \(\hat{\theta}_{MLE}\)​ 的分布收敛于正态分布,其均值为 \(\theta\),方差为 \(1/(nI(\theta))\)。这里,\(I(\theta)\) 是似然函数的费希尔信息,它量化了每个 \(x_i\)​ 为我们提供的关于 \(\theta\) 的信息量。

虽然具体细节(例如,费希尔信息)超出了本课程的范围,但关键要点很重要:

渐近地(即当观测数量变得非常大时),最大似然估计量的分布,如同样本均值的分布一样,收敛于正态分布

YouTubeVideo('TiqwK0YYrNo')

加载中...

使用自助法进行不确定性量化

你可能会发现复习《Data 8》教材的第 13.2 章会很有帮助,该章节涵盖了自助法。

在许多情况下,我们可能无法解析地计算出 \(\hat{\theta}\) 的分布。在这些情况中的某些部分,我们可以使用自助法来量化不确定性。

自助法的局限性

自助法在大多数情况下效果良好,但并非适用于所有情况。以下是一些关于何时使用(以及何时不使用)它的指导原则:

  • 当单个样本的存在与否不会显著改变估计值时(例如,对于最小值或最大值,这种情况则不适用),自助法是一个很好的选择。

  • 当数据点数量非常少时,自助法是一个很好的选择。

  • 当待估计参数数量 \(d\) 远小于数据点数量 \(n\) 时(例如,对于神经网络,我们估计的参数数量非常庞大,这种情况则不适用),自助法是一个很好的选择。

YouTubeVideo('hmQAc5VMBDY')

加载中...

YouTubeVideo('ZQfrPKruYLg')

加载中...

置信区间

你可能会发现回顾Data 8 教材的第 13.3 节会很有帮助,该节涵盖了置信区间。

置信区间基于估计量的分布,提供了关于我们估计器不确定性的度量。请记住,置信区间提供的是关于过程(而非固定未知参数位置)的保证。具体来说,对于一个 95%的置信区间,我们知道 95%的数据集会产生包含真实参数的区间。但我们不知道我们的区间恰好是幸运的 95%之一,还是不幸的 5%之一。

YouTubeVideo('Zn9NqVID-qg')

加载中...

GLM 中的不确定性:比较系数区间与预测区间

在量化广义线性模型(GLMs)中的不确定性时,必须牢记不确定性有多个来源。例如,考虑我们从数据 \((x_1, y_1), \ldots, (x_n, y_n)\) 计算得出的系数估计值 \(\hat{\beta}\)​。这些是给定观测数据的最大似然估计值,因此其本身具有固有的不确定性。换句话说,如果我们观测到不同的数据集,可能会得到一组不同的估计系数 \(\hat{\beta}\)​。

现在,考虑我们对一个新数据点的预测估计值 \(\hat{y}_{n+1}\)​。为简化起见,我们将重点讨论标量 \(x\) 的线性回归。在这种情况下,我们可以将新值 \(y_{n+1}\)​ 的分布写为:

\(\hat{y}_{n+1} \sim \mathcal{N}\left(\hat{\beta}_0 + \hat{\beta}_1 x_{n+1}\,,\, \sigma²\right)\) (1)

这里存在两种不确定性来源:(1) 系数估计量 \(\hat{\beta}\)​ 的不确定性;以及 (2) 观测值的不确定性及其偏离平均预测值(即预测线)的程度,这由 \(\sigma\) 量化。

因此,如果我们想为 \(\hat{\beta}_0\)​ 构建一个置信区间,我们只需要考虑第一种不确定性来源。但是,如果我们想为 \(\hat{y}_{n+1}\)​ 构建一个置信区间,我们需要考虑两种不确定性来源!这第二种区间通常被称为预测区间

使用自助法量化广义线性模型中的不确定性

文本即将推出:请参阅视频

YouTubeVideo('73I1wfQfmf4')

正在加载...

贝叶斯不确定性量化

在贝叶斯框架下,我们将未知参数视为随机变量。这意味着我们可以做出诸如“未知量很可能在 A 和 B 之间”的陈述,而这在频率主义框架下是无法实现的。

可信区间

这引出了可信区间的定义。对于参数 \(\theta\),一个 \(p\%\) 可信区间 \((a, b)\) 表示,在给定观测数据的情况下,参数 \(\theta\) 落在 \(a\)\(b\) 之间的概率为 \(p\%\)。例如,假设我们为 \(\theta\) 生成了一个 \(90\%\) 可信区间 \((0.3, 1.7)\)。这告诉我们,根据观测数据,\(\theta\) 在 0.3 和 1.7 之间的概率是 0.9,等价于 \(\int_{0.3}^{1.7} p(\theta|x_1, \ldots, x_n) d\theta = 0.9\)(假设 \(\theta\) 是连续的)。

请注意,这个定义比置信区间要简单得多!这是采用贝叶斯方法的一个优势:将未知量视为随机变量,使我们能够对其取特定值的概率做出直观的陈述。

YouTubeVideo('nGeZ7G34jPI')

正在加载...

然而,这个定义的一个问题是它不唯一!确实,对于给定的置信度/可信度水平,存在许多可能的可信区间。考虑以下分布,以及三个可能的 \(80\%\) 可信区间。对于所有这三个区间,蓝色阴影区域的总面积都是 \(80\%\),因此它们都是有效的 \(80\%\) 可信区间。你更倾向于哪一个?

中间的那个似乎最吸引人:它覆盖了最高密度的区域,并且也是最窄的。这引出了最高密度区间(HDI)的定义,有时也称为最高后验密度(HPD)区间。HDI 是给定可信度/置信度水平下最窄的区间。

练习:最高密度区间总是唯一的吗?如果不是,那么我们可以对后验分布施加什么约束来确保其唯一性?

我们可以看到,在第 3.3 节中看到的区间是 PyMC 自动为我们构建的 HDI。

YouTubeVideo('8ozFH1ZN7Qw')

加载中...

从样本构建最高密度区间

给定一个闭式后验分布,我们可以解析地找到 \(a\)\(b\) 以最小化区间宽度。但在实践中,我们很少能得到闭式后验分布:我们通常最终会用样本来近似它们。那么,我们如何用样本来找到 HDI 呢?

让我们用一个来自 Beta 分布随机变量的样本做一个简单的例子:

num_samples = 200
distribution = stats.beta(3, 4)
samples = distribution.rvs(num_samples)
samples[:5]

array([0.37568015, 0.3054924 , 0.40978748, 0.10349559, 0.17014057])

将这些样本称为\(\theta_1, \ldots, \theta_{100}​。我们可以通过排序样本,选择任意满足\)(j-i)/200 = 0.9$ 的\(i\)\(j\),并报告\([\theta_i, \theta_j]\)来构建一个\(90\%\)的可信区间:

sorted_samples = np.sort(samples)
(i1, j1) = (10, 190)
(i2, j2) = (15, 195)
credible_interval_1 = (sorted_samples[i1], sorted_samples[j1])
credible_interval_2 = (sorted_samples[i2], sorted_samples[j2])
print(credible_interval_1)
print(credible_interval_2)
(0.12893626829279511, 0.7639256588702239)
(0.16097668346313884, 0.8235097328985005) 

我们如何找到最高密度区间?我们可以简单地搜索所有宽度为\(200 \times 0.9 = 180\) 的区间,并找出最窄的那个。

credibility = 0.9
narrowest_start_so_far = -1
narrowest_width_so_far = np.inf
interval_samples = int(num_samples * credibility)
for start in range(20): # Make sure you understand why we can stop at 20!
    end = start + interval_samples
    width = sorted_samples[end] - sorted_samples[start]
    if width < narrowest_width_so_far:
        narrowest_start_so_far = start
        narrowest_width_so_far = width
print((
    sorted_samples[narrowest_start_so_far],  
    sorted_samples[narrowest_start_so_far + interval_samples]
))
(0.1028881402605869, 0.7219133542569751) 

预测的贝叶斯不确定性量化

正如在频率主义世界中一样,在贝叶斯广义线性模型中,我们也可以为预测构建可信区间!这些区间将使用后验预测密度构建,并继承未知系数推断中的不确定性,以及线性回归中类似\(\sigma\)等参数推断中的不确定性。

YouTubeVideo('9wtfEgpIn8k')

加载中...

非参数化方法

原文:data102.org/ds-102-book/content/chapters/03/nonparametric

定义

“参数化”和“非参数化”这两个术语有多种不同的含义。我们将定义其中两种,并只关注其中之一:

定义 1(统计学中更常用):这是我们在 Data 102 课程中重点关注的定义

  • 一种参数化方法是指我们对数据点之间的关系做出特定假设:例如,在线性回归中,我们假设目标变量 \(y\) 是输入预测变量 \(x_1, \ldots, x_n\)​ 的线性函数。

  • 一种非参数化方法是指我们不对目标变量与预测变量之间的关系做任何假设:相反,我们只致力于找到进行预测的最佳可能方式。

根据此定义,线性回归是参数方法的一个例子,因为我们假设(a)预测变量、系数和结果之间存在线性关系,并且(b)平均预测周围存在同方差性的正态分布噪声。另一方面,\(k\) 近邻是非参数方法的一个例子:我们不对\(x\)\(y\) 的分布做任何假设,我们的预测仅基于训练集中的\(k\) 个最近邻。

定义 2(计算机科学中更常用):

  • 参数模型是指参数数量(例如回归系数数量等)有限,且不随输入数据规模变化的模型。

  • 非参数模型是指参数数量无限,或随数据点数量增加的模型。

在深入了解非参数方法之前,我们将首先将它们置于本书迄今为止讨论过的参数方法(以及你可能在之前的统计学和数据科学课程中花费大量时间学习的参数方法)的背景下。

from IPython.display import YouTubeVideo
YouTubeVideo('DcjHq-5jpko')

加载中...

比较与对比:逻辑回归与\(k\) 近邻

为了理解这两种不同分类方法的优缺点,我们将创建一个简单的合成数据集,并比较两个我们已经熟悉的模型:逻辑回归(参数方法)和\(k\) 近邻分类(非参数方法)。

一个玩具数据集

我们将设置一个训练数据集,其点位于较小范围内,以及一个测试数据集,其点位于较大范围内,以帮助我们理解这些方法的泛化能力。

import numpy as np
import matplotlib.pyplot as plt

%matplotlib inline

import seaborn as sns
sns.set()
N_test = 500
N_train = 150

np.random.seed(2024)

# Create a training dataset
X_train = np.random.uniform(-1, 1, [N_train, 2])
y_train = (X_train[:, 0] * X_train[:, 1] > 0).astype(np.int64)

# Create a test dataset
X_test = np.random.uniform(-3, 3, [N_test, 2])
y_test = (X_test[:, 0] * X_test[:, 1] > 0).astype(np.int64)

def draw_results(x, color, plot_title='', ax=None, is_final=True):
    if ax is None:
        f, ax = plt.subplots(1, 1)
    img = ax.scatter(x[:, 0],x[:, 1], c=color, cmap='viridis', alpha=0.7);
    plt.colorbar(img, ax=ax)
    ax.set_title(plot_title)
    ax.axis('equal')
    ax.set_xlabel('$x_1$')
    ax.set_ylabel('$x_2$')
    if is_final:
        plt.tight_layout()
draw_results(X_train, color=y_train, plot_title='Training data')
draw_results(X_test, color=y_test, plot_title='Test data (ground truth)')

<Figure size 640x480 with 2 Axes><Figure size 640x480 with 2 Axes>

我们常称此类数据集为“玩具数据集”:它们有助于说明问题,但过于简化。真实数据集通常噪声更多、维度更高且更复杂。每当看到玩具数据集的示例时,务必记住你的数据集中可能存在额外的复杂性。

将逻辑回归应用于玩具数据集

\(p\) 为基于 \(x_1\)​ 和 \(x_2\)​ 预测 \(y\) 为 1 的概率。那么逻辑回归的公式是:

\(p = \sigma(\beta_1 x_1 + \beta_2 x_2) \\ \underbrace{\log\left(\frac{p}{1-p}\right)}_{\text{logit: inverse sigmoid}} = \beta_1 x_1 + \beta_2 x_2\) ​(1)

回想一下,逻辑回归试图找到一个关于 \(x_1\)​ 和 \(x_2\)​ 的线性函数作为决策边界。在这种情况下,这对应于在上图中画一条线。不幸的是,对于这个数据集,几乎不可能找到任何准确率超过 \(50\%\)(即随机猜测)的线性决策边界:花点时间,通过在上图中想象画线来让自己确信这一点。

def fit_and_predict_and_draw_results(
    model_class, model_args, model_name, X_train, X_test, y_train, y_test
):
    model = model_class(**model_args)
    model.fit(X_train, y_train)

    # Use the model to predict on the test set
    probs = model.predict_proba(X_test)[:, 1]
    y_hat = (probs > 0.5).astype(np.int64)

    f, axes = plt.subplots(1, 2, figsize=(10, 4.5))
    # Visualize the results
    draw_results(
        X_test, color=probs, ax=axes[0],
        plot_title=f"Predicted P(y=1) ({model_name})",
        is_final=False,
    )

    draw_results(
        X_test, color=y_hat, ax=axes[1],
        plot_title=f"Prediction ({model_name})"
    )

    accuracy = np.mean(y_test == y_hat)
    print(f"Accuracy on test set: {accuracy}")
    return model
from sklearn.linear_model import LogisticRegression
fit_and_predict_and_draw_results(
    LogisticRegression, dict(penalty=None, solver='lbfgs'), 'logistic regression', 
    X_train, X_test, y_train, y_test
);
Accuracy on test set: 0.626 

<尺寸为 1000x450 的图表,包含 4 个子图>

正如预期的那样,整体准确率接近 0.5(如果我们完全随机预测,也会得到这个结果)。对于这些数据,逻辑回归(不做任何特征工程)是一个糟糕的选择。

\(k\) 近邻分类器应用于玩具数据集

回想一下,\(k\) 近邻分类器根据训练集中\(k\)个最近邻点的标签对每个点进行分类。

from sklearn.neighbors import KNeighborsClassifier
fit_and_predict_and_draw_results(
    KNeighborsClassifier, dict(n_neighbors=3), 'kNN', 
    X_train, X_test, y_train, y_test
);
Accuracy on test set: 0.952 

<尺寸为 1000x450,包含 4 个坐标轴的图>

kNN 模型在这个数据集上表现好得多。最棒的是,我们无需进行任何特征工程或参数调优:它开箱即用。

from IPython.display import YouTubeVideo
YouTubeVideo('YCfdENsb_YI')

加载中...

from IPython.display import YouTubeVideo
YouTubeVideo('AfBgttM7wFc')

加载中...

逻辑回归与\(k\) 近邻的优缺点

让我们来探讨这两种方法的一些优缺点。请注意,这远非详尽列表!

逻辑回归

优点

  • 简单模型,每个输入特征只有一个参数(本例中为 2 个)。

  • 参数可以很容易地用有意义的方式解释:系数\(\beta_2\)​告诉你“如果特征\(x_2\)​增加一定量\(a\),则\(y=1\) 的对数几率增加\(\beta_2 a\)。”

  • 损失函数是凸函数,因此存在一个最佳答案,并且我们保证能够找到它。

  • 存储/保存模型的成本很低,因为只有少数几个参数。

缺点

  • 隐含地假设输入之间存在线性交互(例如,无法建模像\(y = \text{sign}(x_1 x_2)\)这样的关系)。

  • 建模复杂或非线性交互需要良好的特征工程。

  • 基础模型的复杂度有限:任何复杂/非线性交互都需要大量的特征工程。

\(k\)-最近邻

优点

  • 对数据不做任何假设(除了训练数据是代表性样本)

  • 易于实现和理解其工作原理

缺点

  • 预测结果可以解释,但我们无法进行比“这个新点看起来像我之前见过的这 5 个点,其中 3 个有 \(y=1\),所以我预测 \(y=1\)”更有意义的分析。

  • 在高维空间中效果不佳(但有一些解决方法)

  • 保存模型需要保存所有训练点(但有一些解决方法)

from IPython.display import YouTubeVideo
YouTubeVideo('SoDiaODgEDQ')

加载中...

决策树和随机森林

决策树

决策树是一种用于分类和回归的方法,它使用树状结构来决定对某个点预测什么值。我们从整个数据开始。在树的根节点,我们将根据 \(x_1\)​ 或 \(x_2\)​ 的特定值来分割数据。然后,我们对每个分割重复这个过程,根据需要构建尽可能深的树。我们分割点的目标是在树的叶子节点处创建尽可能同质的组(就 \(y\) 而言)。让我们看看如何为这个问题构建决策树。

  1. 我们的第一次分割是最困难的:我们可以看到,无论选择\(x_1\)​(或\(x_2\)​)的哪个值,分割的两边都会有一半黄点和一半紫点。但是,我们知道在\(x_1 > 0\) 的点和\(x_1 < 0\) 的点之间存在有意义的差异,因此我们选择在\(x_1 = 0\) 处进行第一次分割。(我们同样可以选择\(x_2 = 0\):选择\(x_1\)​是任意的,尽管阈值 0 的选择并非如此)。

  2. 让我们从考虑上述分割的一半开始:即\(x_1 > 0\) 的点,也就是上面图中的右半部分。对于这些点,在\(x_2 = 0\) 处有一个非常自然的分割,能给我们完全同质的组:分割线上方的所有点都是黄色的(\(y=1\)),下方的所有点都是紫色的(\(y=0\))。至此,我们完成了树这一分支的分割:两个子分支都是完全同质的。

  3. 接下来,我们将回到 \(x_1 < 0\) 的区域。结果表明,我们可以使用与之前相同的分割点,即在 \(x_2 = 0\) 处。通常,决策树赋予我们在树的这一侧进行不同分割的灵活性!只是在这个玩具数据集中,相同的子分割(\(x_2 = 0\))在我们原始分割(\(x_1 = 0\))的两侧都适用。

现在,我们完成了。这是整棵树:

要预测一个新点的 \(y\) 值,我们从根节点(顶部)开始,持续向下直到到达叶节点。这棵树最终非常对称,但在许多问题中情况并非如此。

from IPython.display import YouTubeVideo
YouTubeVideo('sjfqHalgC9E')

加载中...

在 scikit-learn 中的实现方式如下:

from sklearn.tree import DecisionTreeClassifier
fit_and_predict_and_draw_results(
    DecisionTreeClassifier, {}, 'decision tree', 
    X_train, X_test, y_train, y_test
);
Accuracy on test set: 0.99 

<尺寸为 1000x450 包含 4 个坐标轴的图>

该决策树近乎完美。

噪声数据:决策树何时失效

对于底层结构简单的问题,小型决策树效果很好。但如果我们的数据噪声更多呢?让我们随机翻转一些数据点(训练数据的 \(10\%\)),看看会发生什么:

y_train_noisy = y_train.copy()

pts_to_flip = np.random.random(N_train) < 0.1
y_train_noisy[pts_to_flip] = 1 - y_train_noisy[pts_to_flip]

draw_results(X_train, color=y_train_noisy, plot_title='Training data with noise')

<尺寸为 640x480 包含 2 个坐标轴的图>

现在有少数几个点的训练标签是错误的。让我们再次尝试拟合一个决策树:我们将在噪声数据上训练,但会在干净数据上测试,以便评估模型是否能克服噪声学习到真实模式。

# Note that we use the noisy training data from above by passing in `y_train_noisy`
fit_and_predict_and_draw_results(
    DecisionTreeClassifier, {}, 'decision tree trained on noisy data', 
    X_train, X_test, y_train_noisy, y_test
);
Accuracy on test set: 0.746 

<尺寸为 1000x450 包含 4 个坐标轴的图>

仅仅改变 \(10\%\) 的数据点就完全破坏了这棵树!我们的准确率下降到了大约 \(75\%\)。虽然这是一个示例数据集,但在现实世界的数据集中,看到 \(10\%\) 的数据被噪声污染并不罕见。

随机森林

我们将通过使用随机森林而非决策树来解决这个问题。随机森林基于决策树构建,并应用了两个关键思想:自助聚合(Bagging)随机特征选择

自助聚合(Bagging)

我们将通过使用多棵树而非仅一棵树来解决这个问题:这被称为集成学习。我们将分别训练每棵树,然后在进行预测时结合它们的决策。理想情况下,我们会为每棵树获取一个全新的数据集并分别训练。不幸的是,我们通常无法获得那么多独立的数据集,而且如果我们将训练数据集分成 100 份,就会损失可用于训练每棵树的宝贵数据。

但是,我们已经知道了一种解决方法:可以使用自助法!请注意,这里我们使用自助法的目的与之前完全不同:不是用它来量化不确定性,而是用它来减轻数据中噪声的影响。这被称为自助聚合(Bootstrap AGGregation),简称Bagging

随机特征选择

第二个重要思想是随机特征选择。在这个示例中,我们一直使用两个特征 \(x_1\)​ 和 \(x_2\)​。但在许多实际问题中,你可能拥有数百甚至数千个特征。使用我们上面的算法,要让决策树正确处理这么多特征,它必须非常深(因为每个相关特征都需要一个节点/分裂点)。由于我们使用多棵树,我们不需要每棵树都完美。因此,我们将为每棵树只选择一个特征子集。

在实践中,对于 \(K\) 个特征,人们通常在回归任务中每棵树使用 \(K/3\) 个特征,而在分类任务中每棵树使用 \(\sqrt{K}\)​ 个特征。

那么,随机森林算法的工作原理如下:我们独立训练大量决策树,其中每棵树都在数据的自助采样样本和较少的特征子集上进行训练。

X_train.shape

(150, 2)

from sklearn.ensemble import RandomForestClassifier
fit_and_predict_and_draw_results(
    RandomForestClassifier, {}, 'random forest trained on noisy data', 
    X_train, X_test, y_train_noisy, y_test
);
Accuracy on test set: 0.958 

<Figure size 1000x450 with 4 Axes>

随机森林达到了 \(95\%\) 的训练准确率。请记住,我们训练数据中的标签准确率仅为 \(90\%\),因此这里的 \(95\%\) 非常令人印象深刻!

这里有很多重要的细节我们尚未涉及。其中一些(例如,选择树的数量)是我们必须通过交叉验证等方法决定的超参数,而另一些(例如,在建树过程中每次迭代时如何决定最佳分割点)是算法的重要组成部分,超出了本课程的范围。

from IPython.display import YouTubeVideo
YouTubeVideo('CZObqvT_gWU')

加载中...

可解释性

我们如何解释我们开发的模型?让我们看看之前已经计算出的结果,但现在我们也将解释模型本身。

解释逻辑回归

在逻辑回归(或任何广义线性模型)中,我们解释模型的主要方式是查看系数。如前面章节所讨论的,我们可以通过“如果 \(x_i\)​ 增加 \(t\),那么 \(LinkFunction(y)\) 增加 \(\beta_i \times t\)来解释系数 \(\beta_i\)​。在逻辑回归的情况下,这对应于对数几率增加 \(t \times \beta_i\)​。因此,让我们通过使用 sklearn 线性模型的 .coef_ 属性来查看逻辑回归模型的系数:

logistic_model = fit_and_predict_and_draw_results(
    LogisticRegression, dict(penalty=None, solver='lbfgs'), 'logistic regression', 
    X_train, X_test, y_train, y_test
);
print('Coefficients:', logistic_model.coef_)
Accuracy on test set: 0.626
Coefficients: [[-0.03490349  0.05527346]] 

<Figure size 1000x450 with 4 Axes>

这告诉我们,对于 \(x_1\)​ 增加 1,对数几率减少 0.03。不幸的是,这种解释毫无意义,因为模型并未准确反映数据中的模式!这是一个重要的教训:模型的解释,充其量,只能和模型本身一样好。

使用特征工程解释逻辑回归

到目前为止,我们一直将逻辑回归描绘成对此问题的一个糟糕选择。但实际上,线性模型完全有能力在存在非线性模式时进行预测:我们只需要使用特征工程来定义好的特征。

对于这个特定问题,特征 \(x_1 \times x_2\)​ 将特别有用,因为它捕捉了对问题最重要的非线性交互作用。我们将其作为预测变量矩阵的第三列添加,然后尝试逻辑回归:

from sklearn.linear_model import LogisticRegression
# Create a new feature: x1 * x2
def add_mult_feature(X):
    """Returns an array like X, but with a new feature that's X1 * X2"""
    new_feature = X[:, 0] * X[:, 1]
    return np.hstack([X, new_feature[:, None]])

# Define new versions of X with the extra feature
X_train_feat = add_mult_feature(X_train)
X_test_feat = add_mult_feature(X_test)

logistic_model_feats = fit_and_predict_and_draw_results(
    LogisticRegression, dict(penalty=None, solver='lbfgs'), 'logistic regression w/ $x_1 * x_2$ feat', 
    X_train_feat, X_test_feat, y_train, y_test
);
Accuracy on test set: 0.996 

<Figure size 1000x450 with 4 Axes>

不出所料,这个模型的表现好得多!既然我们已经解决了模型准确性的问题,现在来看看是否能解释这些系数:

logistic_model_feats.coef_

array([[ 277.36746859, -56.97829336, 19397.32580951]])

这告诉我们,\(x_1\)​或\(x_2\)​单独增加只会导致对数几率发生相对较小的变化。但是,第三个特征\(x_1 \times x_2\)​的系数却大了三个数量级!我们可以将其解释为:\(x_1 \times x_2\)​的增加会导致对数几率大幅增加,这正是我们观察数据时发现的模式:\(x_1 \times x_2\)​为正值时标记为“1”,为负值时标记为“0”。

解释 k-最近邻的预测

观察 kNN 模型的预测结果时,要得出与之前使用 GLM 系数时相同的广泛结论要困难得多。要对 kNN 分类器的行为做出一般性陈述,我们必须能够理解所有的训练数据点。在这个简单的二维示例中,或许可以通过观察决策边界来实现。但在更高维度中,这就困难得多了!正因如此,我们说 kNN 分类器的可解释性较差。

与其试图整体解释模型,我们不如只关注单个预测。对于 kNN 分类器做出的任何单个预测,我们总是可以提供促成该决策的训练集中\(k\) 个最近邻点及其各自的标签。这通常能为了解预测为何以特定方式做出提供重要见解。我们将这些称为针对每个单独预测的解释

from sklearn.neighbors import KNeighborsClassifier
fit_and_predict_and_draw_results(
    KNeighborsClassifier, dict(n_neighbors=3), 'kNN', 
    X_train, X_test, y_train, y_test
);
Accuracy on test set: 0.952 

<尺寸为 1000x450 且包含 4 个坐标轴的图形>

考虑在 \((0.2, -2.9)\) 附近被错误分类的点。如果我们问为什么这个点被错误分类,答案就在训练集中。

draw_results(X_train, color=y_train, plot_title='Training data')

<Figure size 640x480 with 2 Axes>

我们可以看到,在我们的训练数据集中,第四象限的左下部分相当稀疏。如果我们考虑 \((0.2, -2.9)\) 处的点,我们可以看到它很可能与第三象限底部附近的“1”(黄色)点等距!这就是导致错误分类的原因。

因此,对于从 k-近邻分类器获得的任何预测,我们都可以得到一个解释,即使模型本身并不那么易于解释

解释决策树

决策树是可解释的吗?我们会看到答案取决于树的大小。我们将从在干净(无噪声)数据集上训练的树开始:

from sklearn.tree import DecisionTreeClassifier
tree_model = fit_and_predict_and_draw_results(
    DecisionTreeClassifier, {}, 'decision tree', 
    X_train, X_test, y_train, y_test
);
Accuracy on test set: 0.99 

<Figure size 1000x450 with 4 Axes>

from sklearn.tree import plot_tree

plt.figure(figsize=(12, 8))
plot_tree(tree_model, fontsize=12, filled=True, feature_names=['x1', 'x2']);

<Figure size 1200x800 with 1 Axes>

虽然由于初始分割的任意性,树的顶部有些反直觉,但我们可以看到较低的层很容易解释:我们在接近 0 的 \(x_1\)​ 和 \(x_2\)​ 值处进行分割。

那么,在数据的噪声版本上训练的树呢?

from sklearn.tree import DecisionTreeClassifier
tree_model_from_noisy_y = fit_and_predict_and_draw_results(
    DecisionTreeClassifier, {}, 'decision tree', 
    X_train, X_test, y_train_noisy, y_test
);
Accuracy on test set: 0.746 

<Figure size 1000x450 with 4 Axes>

plt.figure(figsize=(20, 20))
plot_tree(
    tree_model_from_noisy_y,
    fontsize=12, 
    filled=True, 
    feature_names=['x1', 'x2']
);

<Figure size 2000x2000 with 1 Axes>

这棵树非常难以解释!追踪任何单个预测都可能需要我们向下深入多达 15 层:而且,这仅仅是一个简单的二维玩具数据集!在更高维度的真实世界数据集中,树可能更深。

解释随机森林

不幸的是,随机森林甚至更糟:我们不是解释一个可能很大的树,而是必须同时解释数百棵树!这是随机森林的一个关键弱点:虽然它们通常能达到很高的准确率,但往往难以解释。

from IPython.display import YouTubeVideo
YouTubeVideo('5vrzxIyGU4w')

加载中...

黑盒模型的解释

即将推出

from IPython.display import YouTubeVideo
YouTubeVideo('1odBKmKSPG0')

加载中...

神经网络与反向传播

原文:data102.org/ds-102-book/content/chapters/03/neural-networks

神经网络是一类非常强大的方法,在计算机视觉和自然语言处理等领域已变得非常流行,在这些领域中,设计出好的特征可能具有挑战性。

虽然神经网络有着丰富的数学基础,但在本课程中,我们将重点关注大多数神经网络实现的核心计算思想之一:反向传播自动微分。虽然这些思想最初是为神经网络构思的,但现在它们也以许多其他方式被使用:像 PyMC 这样的库使用自动微分来进行高效的贝叶斯推断;等等。

一般来说,对于任何解决方案涉及计算梯度的问题,自动微分和反向传播都很有用!

前馈神经网络

正如我们已经看到的,线性回归是一个简单但强大的模型:为了从特征向量 \(x = (x_1, \ldots, x_k)\) 预测一个值 \(y\),线性回归使用以下公式:

\(y = Wx + b\) b(1)

这里,\(W\) 是一个系数向量,有时也称为权重,而 \(b\) 是一个我们称为截距或偏置的标量。正如我们在上一节中看到的,当 \(x\)\(y\) 之间的关系是非线性时,线性模型可能会失效。我们还看到,如果我们想要建模复杂、非线性的交互关系,同时仍然使用线性模型,就需要定义更复杂的特征。

受此启发,如果我们尝试使用另一层线性回归来为我们计算特征呢?它可能看起来像这样:

\(y = W_2(\overbrace{W_1 x + b_1}^\text{features}) + b_2\) ​(2)

此处,\(W_1\)​ 现在是一个 \(m \times k\) 的权重矩阵,矩阵-向量乘法与加法 \(W_1x + b_1\)​ 的结果是一个 \(m\) 维的特征向量。然后,我们使用向量 \(W_2\)​ 中的权重和标量 \(b_2\)​ 中的截距/偏置,对这些特征应用线性回归,从而得到 \(y\)

遗憾的是,这种方法行不通,因为它简化为单层线性回归。通过一些代数运算,我们可以将上述方程简化为 \(y = \big(W_2W_1\big)x + \big(W_2b_1 + b_2\big)\),这只不过是用一种不必要的复杂方式写出的线性回归。

为了防止简化为线性回归,我们可以应用一个非线性函数 \(f\) 作为计算特征的一部分:

\(y = W_2 \overbrace{f(W_1 x + b_1)}^\text{features} + b_2\) ​(3)

这就是目前最简单的神经网络,我们称之为具有一个隐藏层的前馈全连接网络(所谓的“隐藏层”就是计算 \(f(W_1 x + b_1)\)) 的结果)。

非线性函数 \(f\) 可以是任何函数,从 sigmoid 或 logistic 函数到 ReLU(受限线性单元)函数,即 \(f(z) = \max(0, z)\)

为了拟合线性回归模型,我们必须估计出良好的系数。从概率的角度来说,我们是通过...来做到这一点的。为了拟合神经网络,我们必须估计出良好的权重\(W_1, W_2, \ldots\)和偏置\(b_1, b_2, ldots\)

为了使我们的符号更简洁一些,我们将使用\(\theta\)来表示我们所有的参数:\(\theta = (W_1, W_2, b_1, b_2)\)。为了找到\(\theta\)的最佳值,我们将定义一个损失函数\(\ell(\theta, y)\),然后使用随机梯度下降法来最小化它。

from IPython.display import YouTubeVideo
YouTubeVideo('mgaohBtnub4')

加载中...

经验风险最小化

我们首先选择一个损失函数。通常,损失函数的选择取决于我们所要解决的问题,但两种常见的选择是平方误差损失(也称为\(\ell_2\) 损失)和二元交叉熵损失(BCE)。让我们考虑\(\ell_2\) 损失:

\(\begin{align*} \ell(\theta, y) &= (y - \hat{y})² \\ &= \left(y - \left[W_2 f(W_1 x + b_1) + b_2\right]\right)² \end{align*}\) ​(4)

我们将最小化平均损失:

\(\begin{align*} R(\theta) &= \frac{1}{n} \sum_{i=1}^n \left(y_i - \left[W_2 f(W_1 x_i + b_1) + b_2\right]\right)² \end{align*}\) ​(5)

这里,我们是在对训练集中数据的经验分布进行平均,这使其成为频率主义风险。最小化此损失的过程通常被称为经验风险最小化

回顾:随机梯度下降

关于随机梯度下降的更多内容,您可能会发现回顾 Data 100 教材的第章会有所帮助

(随机)梯度下降是一个强大的工具,只要我们能计算函数的梯度,它就能让我们找到任何函数的最小值。回想一下,梯度是一个关于每个参数的偏导数向量。在上面的例子中,我们的梯度将是

\(\nabla_\theta \ell (\theta, y) = \begin{pmatrix} \frac{\partial \ell}{\partial W_1}(\theta, y)\\ \frac{\partial \ell}{\partial W_2}(\theta, y)\\ \frac{\partial \ell}{\partial b_1}(\theta, y)\\ \frac{\partial \ell}{\partial b_2}(\theta, y) \end{pmatrix}\) ​(6)

梯度下降是一种优化过程,我们从参数的初始估计值 \(\theta^{(0)}\) 开始。然后我们反复应用以下更新来得到 \(\theta^{(1)}, \theta^{(2)}, \ldots\)

\(\theta^{(t+1)} = \theta^{(t)} - \alpha \nabla_\theta \ell(\theta^{(t)}, y)\) (7)

这里,\(\alpha\) 是学习率(通常是一个小的正数,有时也称为步长),而 \(y\) 是我们观测到的数据。在随机梯度下降中,我们不是使用所有数据来计算梯度,而是将数据分成批次,并在每次迭代中依次计算一个批次的梯度。

这意味着我们必须在每次迭代中计算梯度。因此,任何能让我们更快、更高效计算梯度的方法,都将使我们的整个优化过程更快、更高效。

from IPython.display import YouTubeVideo
YouTubeVideo('2g9dRaB6_XA')

加载中...

梯度与反向传播

反向传播是一种通过按特定顺序应用链式法则来高效计算梯度的算法,该顺序旨在避免冗余计算。为了理解其工作原理,我们将考虑一个包含三个变量的非常简单的损失函数。我们将使用链式法则手动计算梯度,然后看看反向传播如何能更高效地进行相同的计算。

使用链式法则计算梯度

考虑一个涉及三个变量 \(a\)\(b\)\(c\) 的非常简单的损失函数:

\(L(a, b, c) = (a + 3b)c²\) (8)

我们可以计算关于 \(a\)\(b\)\(c\) 的偏导数。为了更清晰地展示我们在何时何地使用链式法则,令 \(q = a+3b\)\(r = c²\),于是有 \(L = qr\)。偏导数如下:

\(\begin{align*} \frac{\partial L}{\partial a} &= \frac{\partial L}{\partial q}\cdot\frac{\partial q}{\partial a} = c² \cdot 1 \\ \frac{\partial L}{\partial b} &= \frac{\partial L}{\partial q}\cdot\frac{\partial q}{\partial b} = c² \cdot 3 \\ \frac{\partial L}{\partial c} &= \frac{\partial L}{\partial r}\cdot\frac{\partial r}{\partial c} = (a+3b) \cdot 2c \end{align*}\) ​(9)

即使在这个简单的例子中,我们也能看出其中涉及了一些冗余工作:在进行此计算时,我们需要计算两次 \(\frac{\partial L}{\partial q} = c²\)。在一个更复杂的表达式中,尤其是在有许多嵌套函数调用的情况下,冗余工作会变得严重得多。反向传播为我们提供了一种更高效计算这些梯度的方法。

from IPython.display import YouTubeVideo
YouTubeVideo('79p0iipN-_g')

加载中...

from IPython.display import YouTubeVideo
YouTubeVideo('79p0iipN-_g')

加载中...

反向传播:一个示例

与其将计算表示为代数表达式,不如将其表示为计算图。这是数学表达式的可视化表示。

给定 \(a\)\(b\)\(c\) 的具体数值,反向传播是一种计算损失和梯度(即所有偏导数)的高效方法,且没有冗余计算。

我们首先计算损失函数本身。这仅涉及执行图中箭头上方蓝色数字所指定的计算:

接下来,我们注意到,在上一节计算梯度时,我们的大多数表达式都是从损失函数开始,然后利用链式法则计算关于 \(q\)\(r\) 等变量的偏导数。让我们尝试将这些偏导数写在图上,看看是否能利用它们继续反向工作。

  1. 首先,我们从最简单的导数开始,即损失函数对其自身的导数:\(\frac{\partial L}{\partial L}\)​。结果就是 1!

  2. 接下来,我们将计算损失相对于 \(q\)(图中右上分支)的导数。我们是如何从 \(q\) 得到 \(L\) 的呢?我们将其乘以了 16(也就是说,对于这些特定的数值,\(L = 16q\))。因此,\(L\) 相对于 \(q\) 的偏导数就是 16。

  3. 沿着图的上半部分继续,现在我们可以计算关于 \(a\) 的导数。我们是如何从 \(a\) 得到 \(q\) 的?我们加了 9(也就是说,对于这些具体的数字,\(q = a + 9\)q=a+9)。因此,\(q\) 关于 \(a\) 的偏导数就是 1。但我们要计算的是 \(\frac{\partial L}{\partial a}\)​,而不是 \(\frac{\partial q}{\partial a}\)​。所以,我们将利用链式法则,乘以“到目前为止的导数”:即 \(\frac{\partial L}{\partial q}\)。因此,我们的答案是 \(\frac{\partial L}{\partial a} = 1 \cdot 16 = 16\)

  4. 接下来,我们来看图的 \(b\) 分支。根据与上述类似的推理,“乘以三”模块输出处的导数就是 16。我们如何利用它来计算关于 \(b\) 的导数呢?从 \(b\) 到该值,我们乘以了 3。因此,链式法则中对应的项是 3。我们将其与目前已有的值 16 相乘,得到 48。

  5. 最后,我们来看计算图底部的 \(c\) 分支。首先计算关于 \(r\) 的导数。与上面的步骤 2 类似,我们将 \(r\) 乘以 11 得到 \(L\),这意味着导数为 11。

  6. 最后只剩下“平方”模块。其输出相对于输入的导数是输入的两倍(即 \(\frac{\partial r}{\partial c} = 2c\))。由于输入是 4,这意味着我们的新项是 8,而此分支上的总导数为 \(11 \cdot 8 = 88\)

现在完成了!我们已经计算出了导数,如下方已完成的计算图所示,其中反向传播的中间结果和最终结果以红色标注在箭头下方:

from IPython.display import YouTubeVideo
YouTubeVideo('PqOz2vsfL14')

加载中...

反向传播

总的来说,成功运行反向传播只需要能够对损失函数的每个数学构建模块进行微分(别忘了,损失取决于预测值)。对于每个构建模块,我们需要知道如何计算前向传播(数学运算,如加法、乘法、平方等)和反向传播(乘以导数)。

(可选)PyTorch 中的反向传播

让我们看看在 PyTorch 代码中这是如何实现的。首先为 a、b 和 c 定义张量:张量是 PyTorch 的基本数据类型,类似于 NumPy 中的数组。

import torch

# Torch tensors are like numpy arrays
a = torch.tensor(2., requires_grad=True)
b = torch.tensor(3., requires_grad=True)
c = torch.tensor(4., requires_grad=True)

接着我们为 q 和 r 定义张量。请注意,每个张量既包含计算得到的值,也包含反向传播中计算梯度所需的必要操作:

q = a + 3 * b
r = c ** 2
print(q, r)
tensor(11., grad_fn=<AddBackward0>) tensor(16., grad_fn=<PowBackward0>) 

最后,我们定义损失函数:

L = q * r
L

tensor(176., grad_fn=<MulBackward0>)

计算完损失后,我们可以让 PyTorch 运行反向传播,并通过 .backward() 方法计算所有导数:

L.backward()

让我们看看每个输入的梯度:

print(a.grad, b.grad, c.grad)
tensor(16.) tensor(48.) tensor(88.) 

可以看到,结果与我们上面手动计算的结果完全吻合!

from IPython.display import YouTubeVideo
YouTubeVideo('GBwLIjFF3Rg')

加载中...

第四章:因果推断

原文:data102.org/ds-102-book/content/chapters/04/intro/

本章内容仍在编写中。

本章将涵盖因果推断的基础知识。

关联、相关性与因果关系

原文:data102.org/ds-102-book/content/chapters/04/association-correlation-causation

import pandas as pd
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_style('whitegrid')

相关性不一定意味着因果关系。” 你现在肯定已经听过很多次这句话了。在本节中,我们将理解关联、相关性和因果关系之间的关系。我们将探讨思考这些概念时可能出现的几个不同问题。

我们将首先探讨在思考关联、相关性和因果关系时可能犯的三个错误。然后,我们将回顾几种不同的衡量关联的方法,这些方法将在我们后续关于因果关系的讨论中使用。

预测 vs 因果关系

在许多应用中,我们可能对根据一个量预测另一个量感兴趣。我们已经见过这样的例子:广义线性模型(GLMs)帮助我们在给定许多其他变量的情况下预测一个变量。但重要的是要记住预测和因果关系之间的区别。下面这个例子就突出了这种差异。

假设我们有兴趣使用足球比赛电视转播的录音数据来预测在任何给定时间有多少人在鼓掌。直观上看,这似乎是一个我们可以做出相当不错预测的情况:更大的音频音量应该预示着更多人在鼓掌。然而,尽管我们的预测能力可能非常出色,但这完全不能暗示存在因果关系:事实上,我们观察到的数据(音频音量)是由我们试图预测的事物(人们鼓掌)引起的。

在许多情况下,我们的目标可能是预测能力,在这种情况下,这里的反向因果关系是可以接受的。然而,在我们试图推断因果关系的情况下,仅凭高预测能力是不够的。

将相关性(与关联性)误认为因果关系

陷入混淆关联性与因果关系的陷阱,往往比你想象的要容易。例如,考虑以下故事:

一个火星人来到地球,经过一年的研究后宣布发现了一个相关性:使用雨伞与淋湿之间存在关联——使用雨伞的人比不使用雨伞的人淋湿(即使只是裤子湿了)的概率更高。火星人推断,使用雨伞会导致人们被淋湿。

火星人观察到的相关性是真实存在的,但真实的因果关系比这稍微复杂一些:下雨时我们会使用雨伞,而下雨时我们也会被淋湿。虽然很容易指着这个例子嘲笑火星人有多愚蠢,但现实情况是,我们经常听到(甚至自己也会做出)类似的推断。

混杂变量

在因果关系推理中出现的一个关键问题是混杂变量的存在。

晒伤与冰淇淋销量

举个例子,假设我们收集了全年关于晒伤和冰淇淋销量的每日数据。我们发现数据中存在强烈的正相关关系:冰淇淋销量较高的日子,晒伤率也显著更高。显然,晒伤并不会导致冰淇淋销量增加,冰淇淋销量增加也不会导致晒伤。这个例子忽略了天气的混杂效应:炎热晴朗的天气会导致人们购买更多冰淇淋,同时也会导致更多晒伤。我们可以使用有向图来说明这一点,类似于我们在贝叶斯图模型中使用的那种:

在这里,边表示因果关系(我们将在下一节正式阐述这个概念)。例如,此图声称太阳会导致晒伤率的变化。就像火星人的例子一样,这个例子中混杂变量(晴朗天气)的存在是显而易见的。

相机与社交媒体点赞数

让我们看一个关系更复杂的例子。假设我们想知道使用高端数码单反相机是否会导致 Instagram 帖子的点赞数增加。如果我们收集数据并发现相机价格与点赞数之间存在强烈的正相关,这是否能让我们得出存在因果关系的结论?在这种情况下,答案是一个更模糊的“可能”。这里存在其他混杂因素:例如,已经拥有大量粉丝、资金雄厚的高知名度账户比业余账户更可能使用高端相机。在这种情况下,因果图可能看起来更像这样:

在没有关于账户规模和预算的进一步信息的情况下,很难确定因果关系。

虚假相关性

有时,无论我们是否试图得出关于因果关系的结论,我们观察到的相关性可能是虚假的:也就是说,它们可能只是偶然发生,或者由一系列可能否定我们想要得出的任何结论的混杂因素导致。泰勒·维根的虚假相关性网站上的一个示例如下:

我们可以为这两个变量绘制一个有向图:在这种情况下,它们之间没有边,因为两者之间没有因果关系,也没有混杂变量:

棉花糖实验

另一个例子是著名的“棉花糖实验”。在这项研究中,研究人员给孩子们一个选择:要么立即得到一颗棉花糖,要么等待 15 分钟后得到两颗棉花糖。他们对这些学生进行了长达 30 年的跟踪,发现那些愿意等待额外奖励的学生在生活中取得了更大的成功(根据研究人员的衡量标准)。尽管最初的研究人员告诫人们避免直接进行因果关系的解读,但最常见和最广泛的解释却是因果性的:即孩子延迟满足的能力(即为了获得双倍棉花糖奖励而等待 15 分钟)导致了他们一生的成功。学校因此实施了辅导项目,以帮助学生建立自控力,抵抗吃掉第一颗棉花糖的冲动。

然而,后续研究揭示了一个更为复杂和微妙的故事:虽然许多后续研究显示了关联性,但在控制了社会经济背景或其他自控力衡量指标等因素后,抵抗棉花糖诱惑的预测效应似乎会减弱或消失。许多科学家认为,这项研究真正衡量的并非自控力,而是对中产阶级或上层阶级成长环境的反应行为:换句话说,是对资源不匮乏环境的反应。

像这样的故事说明了从观察性研究中确定因果关系的重要性和困难性。如果我们能以某种方式确定,教导幼儿自我控制必定会对其最终的生活结果产生重大影响,那么这似乎是一个值得追求的政策目标。如果这种影响不受社会经济背景影响,那就更是如此。然而,众多的混杂变量和模糊的因果关系方向凸显了需要能在随机对照试验之外的环境中确定因果关系的方法。

辛普森悖论

混杂变量可能导致的一个反直觉问题是辛普森悖论。让我们从一个假设的例子开始:假设一组餐厅评论家用两年时间品尝了两家热门餐厅 A 和 B 的菜肴。他们对自己点的每道菜都给出👎(不喜欢)或👍(喜欢)的评分。他们在下表中总结了数据:

food = pd.read_csv('data/restaurants.csv')
food.pivot_table(
    values='count', index='Restaurant', columns='Dish rating', aggfunc=sum
)
/var/folders/xb/x1ncwczj3ld6c06v8p22z_7m0000gn/T/ipykernel_13554/711508461.py:2: FutureWarning: The provided callable <function sum at 0x10494ce00> is currently using DataFrameGroupBy.sum. In a future version of pandas, the provided callable will be used directly. To keep current behavior pass the string "sum" instead.
  food.pivot_table( 

加载中...

仅看这些数据,似乎他们更喜欢餐厅 B 的食物(80%的成功率,而餐厅 A 为 60%)。

但现在,假设我们了解到他们的数据收集于 2019 年至 2020 年,并且评论家在 2020 年比 2019 年苛刻得多,这可能是由于疫情引发的阴郁情绪。此外,我们得知餐厅 A 专营外卖和送餐服务,这是 2020 年最安全、最常见的订餐方式。

基于这个额外背景,让我们按年份细分数据:

food.pivot_table(
    values='count', index='Restaurant', 
    columns=['Year', 'Dish rating'], aggfunc=sum
)
/var/folders/xb/x1ncwczj3ld6c06v8p22z_7m0000gn/T/ipykernel_13554/4218979607.py:1: FutureWarning: The provided callable <function sum at 0x10494ce00> is currently using DataFrameGroupBy.sum. In a future version of pandas, the provided callable will be used directly. To keep current behavior pass the string "sum" instead.
  food.pivot_table( 

加载中...

观察这些数据,餐厅 A 现在在两年中都显得更优!这被称为辛普森悖论:从汇总数据看餐厅 B 更好,但当我们分别查看每年数据时,餐厅 A 看起来更好。我们该如何解决这个难题?

让我们花点时间看看这些数字:

  • 在 2019 年(前两列),餐厅 A 在评论家中的成功率为 100%(20/20),而餐厅 B 的成功率为 87.5%(70/80)。

  • 在 2020 年(最后两列),餐厅 A 的成功率为 55.6%(100/180),而餐厅 B 的成功率为 50%(10/20)。

为什么会发生这种情况?这是因为年份(即疫情)的混杂效应。两家餐厅的评分都因评论家在疫情期间的苛刻评分而大幅受损,但由于餐厅 A 在 2020 年接到了更多订单,其整体数据受到的影响更大。

在继续之前,让我们用因果推断的语言来框定这个问题,以便利用我们围绕混杂变量建立的直觉。我们的问题可以稍作改写为:评论者对餐厅的选择(A 还是 B)是否会影响他们是否喜欢食物(从而给出 👍 评分)?在此设定中,我们的处理是餐厅的选择:A 或 B。我们的结果是他们是否喜欢一道菜(即,是否给出 👍 评分)。混杂变量是他们点菜的时间。为什么这是一个混杂变量?因为它对处理和结果都有因果影响。在 2020 年点菜导致评论者更频繁地选择适合外卖的选项(B)。在 2020 年点菜也导致评论者给出正面评价(👍)的频率降低。

from IPython.display import YouTubeVideo
YouTubeVideo('4HI30EwghPk')

我们也可以像之前那样,用有向图来表示这个信息:

请注意,我们在这里使用了“年份”作为混杂变量,但这有点不精确:真正导致其他两个变量变化的并非年份本身,而是疫情。

疫情(在本例中以年份衡量)通过两种方式影响了数据:

  1. 在 2020 年,评论者对餐厅 A 的菜品评分更多。

  2. 在 2020 年,评论者要苛刻得多。

当查看按年份分开的数据时,我们可以看到他们在两个年份都更喜欢餐厅 A。但是,由于上述两个因素,在汇总数据时,餐厅 A 看起来更差。

请注意,尽管名称如此,这并非真正的悖论:这完全是由于混杂因素造成的。

(可选)使用 Baker-Kramer 图可视化辛普森悖论

我们可以将上面要点中的百分比可视化在图表上。让 \(x\) 轴代表疫情期间(混杂变量)2020 年点菜的百分比。让 \(y\) 轴代表喜欢的菜品比例(结果)。我们将为每个餐厅画一条线:不同的线向我们展示了处理的效果。

对于按年份细分的数据,每个表格向我们展示的数据中,2020 年点菜的比例始终为 0(对于 2019 年数据)或 1(对于 2020 年数据)。这对应于四个标记点(两个蓝色圆圈和两个橙色方块):

来源

f, ax = plt.subplots(1, 1, figsize=(3, 3))
ax.plot([0, 1], [20/20, 100/180], marker='o', ls='--', label="Restaurant A")
ax.plot([0, 1], [70/80, 10/20], marker='s', ls='--', label="Restaurant B")

ax.legend()
ax.set_xlabel("Proportion of dishes ordered in 2020")
ax.set_ylabel("Proportion of dishes liked")

plt.tight_layout();

<Figure size 300x300 with 1 Axes>

该图表显示,评论家们更偏爱餐厅 A(蓝线)而非餐厅 B(橙线)。但当我们查看上方第一个表格中的汇总数据时,我们实际上是在将餐厅 A(其菜品大多来自 2020 年)与餐厅 B(其菜品大多来自 2019 年)进行比较。这一点通过大圆圈/方块显示出来:

源代码

f, ax = plt.subplots(1, 1, figsize=(3, 3))
ax.plot([0, 1], [20/20, 100/180], marker='o', ls='--', label="Restaurant A")
ax.plot([0, 1], [70/80, 10/20], marker='s', ls='--', label="Restaurant B")

ax.legend()

ax.scatter(.9, .6, marker='o', s=400, color='tab:blue')
ax.scatter(.2, .8, marker='s', s=400, color='tab:orange')

ax.set_xlabel("Percent of dishes ordered in 2020")
ax.set_ylabel("Percentage of dishes liked")

plt.tight_layout();

<Figure size 300x300 with 1 Axes>

该图表被称为贝克-克雷默图:它突显了尽管餐厅 A 优于餐厅 B,但年份的混杂效应使得餐厅 A 看起来差得多。

伯克森悖论与碰撞因子

假设一位面包师决定将他最好的几条面包陈列在橱窗里,以帮助吸引顾客。对于他烤制的每一批面包,他都会对该批次面包的风味和外观进行评分(从 1 到 10)。风味和外观之间没有任何相关性,因此评分看起来像这样:

源代码

np.random.seed(2026)
flavor = np.random.normal(5, 2, 300)
appearance = np.random.normal(5, 2, 300)
f_gray, ax_gray = plt.subplots(1, 1, figsize=(3, 3))
ax_gray.scatter(flavor, appearance, color='gray', alpha=0.45)
ax_gray.axis([0, 10, 0, 10])
ax_gray.set_xlabel('Flavor')
ax_gray.set_ylabel('Appearance');

<Figure size 300x300 with 1 Axes>

随后,他决定将风味和外观的评分相加:对于任何总分超过 10 的批次,他都会在橱窗中展示该批次的一条面包。

源代码

 f_color, ax_color = plt.subplots(1, 1, figsize=(3, 3))
is_on_display = (flavor + appearance > 10)
ax_color.scatter(
    flavor[is_on_display], appearance[is_on_display], label='On display',
    color='tab:blue', marker='+', alpha=0.45
)
ax_color.scatter(
    flavor[~is_on_display], appearance[~is_on_display], label='In the back',
    color='tab:red', marker='x', alpha=0.45
)
ax_color.legend()
ax_color.plot([0, 10], [10, 0], 'k--')
ax_color.axis([0, 10, 0, 10])
ax_color.set_xlabel('Flavor')
ax_color.set_ylabel('Appearance'); 

<Figure size 300x300 with 1 Axes>

每天打烊时,他会把展示柜(如上图蓝色部分所示)里的面包送给朋友们吃。他的朋友们注意到外观和风味之间存在负相关:看起来更漂亮的面包往往味道更差。当他们向面包师提出这个问题时,面包师给他们看了第一张图,并告诉他们两者之间没有任何相关性。

谁是对的?

这与之前辛普森悖论的例子有一些相似之处:我们有两个变量(风味/外观),当引入第三个变量(展示)时,它们之间的关系发生了变化。但这里有一个非常重要的区别:因果关系的方向。如果我们为这些变量绘制因果图,它看起来会是这样:

由于因果关系的方向不同,展示柜在这里不是一个混杂因素:我们反而称其为碰撞因子

如果我们在分析中包含此类碰撞因子,就很可能得出错误的结论。在这里,面包师朋友们观察到的关联并不反映风味和外观之间的任何因果关系,而仅仅是由面包师如何选择哪些面包放入展示柜所导致的。

from IPython.display import YouTubeVideo
YouTubeVideo('9qNIKKxQzIU')

更多示例

参考文献

  1. Selvitella, A. (2017). 量子力学中的辛普森悖论。《数学物理杂志》,58(3)。10.1063/1.4977784

量化关联性

原文:data102.org/ds-102-book/content/chapters/04/quantifying-association

我们将通过考察几种量化关联性的方法来开启我们的因果推断之旅。

自始至终,我们假设有两个随机变量,\(Z\)\(Y\),有时还有第三个变量 \(X\)。尽管我们将要描述的大多数方法无论我们如何解释它们都可以使用,但当我们转向因果关系时,将 \(Z\) 视为处理变量或协变量,将 \(Y\) 视为结果变量,将 \(X\) 视为潜在的混杂变量,将有助于理解。

连续数据:相关性与回归

相关系数

有几种不同的方法来衡量相关性。最常见的是皮尔逊相关系数(也称为相关系数),通常用 \(r\)\(\rho\)ρ(希腊字母 rho)表示:

\(\rho_{ZY} = \frac{\text{cov}(Z, Y)}{\sqrt{\text{var}(Z)\text{var}(Y)}}\) ​(1)

这是衡量\(Z\)\(Y\) 之间线性相关性的一个良好指标。如需回顾皮尔逊相关系数,请参阅Data 8 教材Data 140 教材

线性回归

如果我们拟合一个线性模型,用\(Z\) 来预测\(Y\),其形式大致如下:

\(Y = \alpha + \beta Z + \varepsilon.\) (2)

通常,我们假设\(\varepsilon\)是均值为零的噪声,并附加性质协方差\((Z, \varepsilon) = 0\)。我们已多次讨论如何将此方程解释为预测模型,但现在我们将从一个略有不同的视角来看待它。

我们将这个方程视为对\(Z\)\(Y\) 之间关系的描述性解释,其中关系的核心部分是\(\beta\)。我们可以运用已开发的所有计算工具来拟合模型并计算\(\beta\),其解释受限于我们已经了解到的约束(例如,它无法捕捉非线性关联,可能受异常值影响等)。虽然通常将\(\beta\)描述为量化\(Z\)\(Y\) 的“效应”,但必须理解此处“效应”一词的局限性:线性回归只能告诉我们预测性效应,而非因果效应。

因此,我们将使用系数 \(\beta\) 作为量化 \(Z\)\(y\) 之间关系的手段。从我们假设协方差 \((Z, \varepsilon) = 0\) 出发,并利用协方差的性质,我们可以证明 \(\beta = \frac{\text{cov}(Z, Y)}{\text{var}(Z)}\)β​。由此,我们还可以证明 \(\beta = \rho_{ZY} \sqrt{\frac{\text{var}(Y)}{\text{var}(Z)}}\)(正如您可能在 Data 8 课程中通过实证所见)。

例如,假设我们想要量化个人受教育年限(\(Z\))与其收入(\(Y\))之间的关系。如果我们计算系数 \(\beta\),它将提供一种量化这两个变量之间关联的方法。

多元线性回归

假设我们现在想要量化两个变量 \(x\)\(y\) 之间的关系,但同时我们也想考虑或“控制”第三个变量 \(w\) 的影响。假设它们之间存在线性关系,我们可以扩展我们之前的关系式:

\(Y = \alpha + \beta Z + \gamma X + \varepsilon.\) (3)

在这种情况下,我们可以将 \(\beta\) 解释为衡量 \(Z\)\(Y\) 之间关联性的指标,同时控制调整第三个变量 \(X\) 的影响。

以下是一些关于线性回归的复习资源:

from IPython.display import YouTubeVideo
YouTubeVideo('qlOiejELwDA')

正在加载...

二元数据:(另一种)风险

相关系数和回归系数是衡量连续数值变量之间关联性的好方法,但如果我们的数据是分类数据呢?为简化起见,本节我们将仅限于讨论二元数据。我们将介绍三种在这种情况下常用的度量指标:风险差(RD)、风险比(RR)和比值比(OR)。

在处理分类数据时,我们通常会先通过列联表来可视化数据。我们在讨论辛普森悖论时已经见过一个这样的例子。

\(y=0\) \(y=1\)
\(z=0\) \(n_{00}\) \(n_{01}\)
\(z=1\) \(n_{10}\) \(n_{11}\)

请注意,这些表格与我们在课程开始时使用的 2x2 表格不同:在那里,行和列分别代表现实和我们的决策。而在这里,它们代表我们数据中两个不同的观测变量。与那些表格一样,关于将什么放在行中、什么放在列中,并没有标准惯例。

例如,假设我们感兴趣的是研究接种某种病毒的疫苗(\(z\))与感染该病毒(\(y\))之间的关系。我们将查看一项针对该疫苗进行的研究。我们将使用\(z=1\) 表示接种疫苗,\(y=1\) 表示感染病毒。在这种情况下,例如,\(n_{10}\)​将代表研究中接种了疫苗但未感染的人数。

我们将讨论的大多数指标都基于风险,它表示在给定特定\(z\) 值的情况下\(y=1\) 的概率:\(z=1\) 的风险是\(P(y=1 | z=1)\),而\(z=0\) 的风险是$P(y=1|z=0)。请注意,此定义与我们在第一章中学到的风险完全无关。

在我们的疫苗接种示例中,风险一词有一个直观的解释:它表示你在接种疫苗与否的情况下被感染的风险。

风险差(RD) 定义如下:

\(\begin{align} RD &= \underbrace{P(Y=1 \mid Z=1)}_{\text{Z=1 时的风险}} - \underbrace{P(Y=1 \mid Z=0)}_{\text{Z=0 时的风险}} \\ &= \quad\,\overbrace{\frac{n_{11}}{n_{10} + n_{11}}}^{} \quad\,\,-\quad\, \overbrace{\frac{n_{01}}{n_{00} + n_{01}}}^{} \end{align}\) ​(4)

回到疫苗的例子:如果疫苗按预期生效(即接种疫苗与感染之间存在强关联),那么接种后你被感染的风险应当降低,风险差值应是一个远离 0 的负数。反之,如果接种与感染之间几乎没有关联,那么这两项应该非常接近,风险差值应接近 0。

我们可以在数学上看到相同的事实。如果 \(Z\)\(Y\) 是独立的,那么 \(P(Y=1 \mid Z=1) = P(Y=1 \mid Z=0) = P(Y=1)\),因此这两项相等。这意味着它们相互抵消,风险差为 0。

风险比(RR),有时也称为相对风险,其定义与上述两个量之间的比率(而非差值)类似:

\(RR = \frac{P(Y=1 \mid Z=1)}{P(Y=1 \mid Z=0)}\) ​(5)

我们可以使用与上述类似的推理得出结论:当 \(Z\)\(Y\) 独立时,此比率应为 1。

第三种常用的度量是比值比(OR)。它是两个比值的比率,而每个比值本身也是一个比率:

\(OR = \frac{% \overbrace{P(Y=1|Z=1)/P(Y=0|Z=1)}^{\text{存在 }Z\text{ 时 }y\text{ 的比值比}}}{% \underbrace{P(Y=1|Z=0)/P(Y=0|Z=0)}_{\text{不存在 }Z\text{ 时 }y\text{ 的比值比}}}\) ​(6)

虽然这看起来更复杂,但我们可以证明它可以简化为:

\(OR = \frac{n_{00}}{n_{10}} \cdot \frac{n_{11}}{n_{01}}\) ​​(7)

from IPython.display import YouTubeVideo
YouTubeVideo('aBIcozKxogo')

正在加载...

因果关系与潜在结果

原文:data102.org/ds-102-book/content/chapters/04/causality-potential-outcomes

既然我们已经理解了关联和相关(及其局限性),现在就可以开始我们的因果推断之旅了。我们将从讨论因果关系的概念开始:它意味着什么,以及为什么重要。然后,我们将定义一种基于反事实推理的特定思考因果关系的方式,这将指导我们后续对因果推断的研究。

什么是因果关系?(可选)

要理解因果关系,让我们先探讨“原因”这个词的含义。请看以下句子:它们都使用了“导致”或“因为”这类词语,并且都是现代英语中的有效句子,但“原因”在每句话中的含义略有不同:

  1. 足球移动是因为我踢了它。

  2. 这对情侣分手是因为其中一方搬到了另一个国家。

  3. 我之所以成为今天的我,是因为我的父母。

  4. 重力导致物体落向地球。

  5. 唐氏综合征是由第 21 号染色体多出一条拷贝引起的。

  6. 人类燃烧化石燃料正在导致气候变化。

  7. 蛮族入侵导致了罗马帝国的灭亡。

  8. 吸烟会导致肺癌。

现代对“原因”一词的理解大多与希腊哲学家亚里士多德所称的“动因”或“有效因”一致:即一个动因或物体引发了另一个的变化。仔细观察上面的例子,如果我们局限于这种理解,就应该排除第 3 和第 4 句。在第 3 句中,被“导致”的是“今天的我”,这并不属于“变化”的范畴。同样,在第 4 句中,起“导致”作用的是“重力”,它不是一个具体的事物或物体,而是一个概念性的物理理论。请注意,这是一个主观判断!

理解因果关系的一种方式是将上述陈述分为确定性原因和概率性原因。这些意味着什么?如果 A 是 B 的确定性原因,那么每当 A 发生时,B 必然发生。相反,如果 A 是 B 的概率性原因,那么每当 A 发生时,B 发生的概率会增加。回到我们上面的例子,句子 1、5 和 6 都是确定性因果关系的陈述。这并不意味着其余句子(2、7 和 8)中的因果关系不真实!例如,考虑陈述 7:大多数历史学家认为,在蛮族入侵之前,罗马帝国已处于长期的衰落状态。然而,这些入侵仍然对帝国的覆灭产生了真实而具体的影响。

还有许多其他思考因果关系的方式:例如,我们可以将上述陈述分为实例因果关系和类别因果关系;单一原因和多重原因;简单行为和复合行为;等等。

因果关系为何重要?

理解因果关系很重要,因为它影响着人们在现实世界中做出的决策。例如:

  • 在健康领域,理解因果联系对于实践循证医学至关重要。如果我们知道某种行为会导致疾病,那么医生和/或公共卫生官员应建议人们避免该行为。同样,如果某种药物能使人们从某种疾病中康复,那么使用该药物治疗患有该疾病的人应成为最佳实践。

    另一方面,如果我们观察到某种行为与某些疾病之间存在关联,那么理解这种关联是否由因果联系引起就很重要。例如,假设一项研究显示服用昂贵的复合维生素与降低癌症风险之间存在关联。如果确实存在因果联系,那么医生应推荐服用复合维生素。但是,如果这种关联是由于混杂因素造成的(例如,更富裕的人能够获得更好的医疗保健,从而降低了癌症风险,并且也能获得昂贵的复合维生素),那么医生就不应推荐它们。

  • 在经济学和社会科学中,理解因果关系对政策选择具有重要影响。例如,传统观点认为提高最低工资会导致就业率下降,因此任何提高最低工资的政策都应仔细权衡工资增长与整体就业下降之间的利弊。然而,大卫·卡德在 1992 年的一项里程碑式研究表明,全国性最低工资的提高并未引起就业率的任何变化,这推动了支持提高最低工资的政策转变。

  • 在商业领域,理解因果关系对于做出良好决策至关重要。例如,了解某项广告或营销策略的改变是否会导致消费者行为的变化,是评估企业是否应该为该改变投入资金的一个非常有用的因素。

为什么因果关系难以确定?

正如我们在第 4.1 节中所见,由于混杂变量的存在,因果关系通常很难确定。

from IPython.display import YouTubeVideo
YouTubeVideo("UlI6hBz59oo")

正在加载...

潜在结果与反事实

这部分内容在《数据 8》教材第 12.2 节中从更概念化的角度有所涉及。

假设你发烧了,并决定服用一片阿司匹林。如果一小时后你的烧退了,我们如何知道是否是阿司匹林导致了你的退烧?为了帮助我们回答这个问题,请考虑以下思想实验:想象自己身处两个平行宇宙中。在宇宙 1 中,你服用了阿司匹林。在宇宙 0 中,你没有服用阿司匹林。如果你的发烧情况在两个宇宙中改善的程度相同,那么这似乎是强有力的证据,表明阿司匹林没有导致你退烧。另一方面,如果你的发烧在宇宙 1 中退了而在宇宙 0 中没有,那么这似乎是强有力的证据,表明阿司匹林确实导致了你的退烧。

在符号上,我们将使用 \(Y(1)\) 表示宇宙 1 中的结果(在本例中是你的体温),用 \(Y(0)\) 表示宇宙 0 中的结果(注:你可能也会看到人们使用上标,例如 \(Y⁰\)\(Y¹\),但在本课程中,我们将始终使用 \(Y(0)\)\(Y(1)\))。在任何现实世界的数据中,我们只能观察到其中之一:在上面的例子中,如果你决定服用阿司匹林,那么你将只能观察到 \(Y(1)\)。因此,另一个宇宙通常被称为反事实宇宙。这引出了因果推断的根本问题:我们永远无法为同一个个体同时观察到 \(Y(1)\)\(Y(0)\)

from IPython.display import YouTubeVideo
YouTubeVideo("G1VlgMGMP4c")

加载中...

符号

假设我们感兴趣的是某个特定处理是否会导致某个特定结果。我们主要将讨论限制在处理是二元的情况。我们关心的是对某个特定单元施加处理是否会导致结果的变化。这里的单元一词指的是我们研究中的个体。它们可以是个人、人群,甚至是非生物对象(例如,在研究肥料是否导致作物增产时,单元可能是田地,也可能是单株植物)。我们将使用以下符号:

  • \(Y_i(0)\)\(Y_i(1)\) 是单元 \(i\)潜在结果:它们分别代表单元 \(i\) 未接受处理或接受处理的那个宇宙中的结果。

  • \(Z_i\)Zi​ 告诉我们单元 \(i\) 是否接受了处理。

    • 例如,如果 \(Z_7 = 1\),这意味着单元 7 接受了处理,我们只能观测到 \(Y_7(1)\)(而无法观测到 \(Y_7(0)\))。
  • \(Y_{i, obs}\)​ 是“我们实际观测到的宇宙中的结果”的简写。

    • 换句话说,\(Y_{i, obs} = Y_i(Z_i)\)

    • \(Z_i\)Zi​为二元变量时,我们可以写作\(Y_{i,obs} = Z_i \cdot Y_i(1) + (1-Z_i) \cdot Y_i(0)\)

    • 我们有时会用符号\(Y_i\)​作为\(Y_{i,obs}\)​的简写。

    • 我们也会用符号\(Y_i(\cdot)\)作为对\(\big(Y_i(0), Y_i(1)\big)\)的简写。

我们可以用表格来可视化潜在结果:

单元 (\(i\)) 未处理时的结果 处理后的结果 是否处理?
1 \(Y_1(0)\) \(Y_1(1)\) \(Z_1\)
2 \(Y_2(0)\) \(Y_2(1)\) \(Z_2\)
3 \(Y_3(0)\) \(Y_3(1)\) \(Z_3\)
4 \(Y_4(0)\) \(Y_4(1)\) \(Z_4\)
5 \(Y_5(0)\) \(Y_5(1)\) \(Z_5\)

实际上,我们永远无法观察到完整的表格。相反,我们观察到的数据看起来更像是:

单元 未处理时的结果 处理后的结果 是否处理?
1 ? \(Y_1(1)\) \(Z_1=1\)
2 \(Y_2(0)\) ? \(Z_2=0\)
3 \(Y_3(0)\) ? \(Z_3=0\)
4 ? \(Y_4(1)\) \(Z_4=1\)
5 \(Y_5(0)\) ? \(Z_5=0\)

对于 \(Z_i\)​ 保持正确的直觉理解非常重要:这个变量并不代表处理本身;相反,它代表是否对单元 \(i\) 进行处理的决策。处理本身的效果体现在 \(Y_i(0)\)\(Y_i(1)\) 之间的差异上。我们来看两个例子,它们能让这个区别更加清晰。这些例子也预示了我们在接下来两节中将要进一步阐述的一些见解。

  • 在随机对照试验中,我们将受试单位随机分配到处理组和对照组。由于试验是随机的,处理/控制分配 \(Z_i\)​ 与潜在结果 \((Y_i(0), Y_i(1))\) 相互独立。这并不意味着处理与我们观察到的结果独立!(记住,观察到的结果是 \(Y_{i,obs} = Y_i(Z_i)\))它仅意味着在随机对照试验中,决定是否进行处理与潜在结果独立。我们将在下一节中看到这一事实如何非常有用。

  • 假设我们观察一项测量游艇拥有权对税收态度影响的观察性研究。这里,\(Z_i = 1\) Zi​=1 表示受访者 \(i\) 拥有一艘游艇;\(Y_i(0)\) 表示受访者 \(i\) 没有游艇时对税收的态度;而 \(Y_i(0)\) 表示受访者 \(i\) 拥有游艇时对税收的态度。在这种情况下,我们可以直观地看到,如果某人足够富有到能拥有一艘游艇,无论他们是否真的拥有(即,无论他们处于宇宙 1 还是宇宙 0),他们可能都倾向于更低的税收。

from IPython.display import YouTubeVideo
YouTubeVideo("k7w5D_ExSoE")

加载中...

from IPython.display import YouTubeVideo
YouTubeVideo("kyhj08m8oJg")

加载中...

平均处理效应

我们已经知道,我们永远无法得知单个单元 \(i\) 的差值 \(Y_i(1) - Y_i(0)\),因为我们只能观察到两者之一。因此,我们将转而关注平均处理效应,并用希腊字母 tau \(\tau\) 来表示:

\(\text{ATE} = \tau = E[Y_i(1) - Y_i(0)]\) (1)

请记住,\(Y_i(\cdot)\) 是独立同分布的随机变量,因此这里的期望是针对该分布中的随机性而言的。为方便起见,我们通常会在符号表示中省略下标 \(i\)

如果我们知道上表中所有的数据(包括标记为“?”的反事实结果),那么我们可以计算两列均值的差值来求得 ATE。但是,如果我们只对实际观察到的值取平均,那么我们实际上是在以处理决策为条件。换句话说,如果我们计算第二列(“未处理时的结果”)的平均值,它将是 \(E[Y(0) | Z = 0]\)。我们如何将这个条件期望与 ATE 中的无条件期望联系起来呢?我们已经知道如何做到这一点:利用塔性质(也称为迭代期望)。

\(\begin{align} \tau &= E[Y(1) - Y(0) | Z = 1]P(Z=1) + E[Y(1) - Y(0)|Z=0]P(Z=0) \\ &= \Big(\,\overbrace{E[Y(1) | Z = 1]}^{\text{observed}} - \overbrace{E[Y(0) | Z = 1]}^{\text{counterfactual}}\,\Big)P(Z=1) \, + \\ & \quad\,\Big(\,\underbrace{E[Y(1)|Z=0]}_{\text{counterfactual}} - \underbrace{E[Y(0)|Z=0]}_{\text{observed}}\,\Big)P(Z=0) \end{align}\) ​​(2)

仔细审视这四个项,我们发现其中两项(第一项和最后一项)对应我们能够观测到的数据。遗憾的是,中间两项是反事实项:如果 \(Z_i=1\),那么我们就无法观测到 \(Y_i(0)\)

在接下来的几节中,我们将探讨解决这个问题的方法。

from IPython.display import YouTubeVideo
YouTubeVideo("qmd3pnXvfao")

加载中...

结果:固定还是随机?

关于如何对潜在结果 \(Y_i(0)\)\(Y_i(1)\) 进行建模,存在两种不同的思想流派:

  1. 固定样本方法中,我们假设潜在结果 \(Y_i(0)\)\(Y_i(1)\) 是固定的,只有 \(Z_i\)​ 是随机的。我们观测到一个结果 \(Y_{i,obs} = Y_i(Z_i)\),这个结果是固定且已知的,而另一个结果是固定但未知的。

  2. 超总体模型中,我们假设元组 \((Z_i, Y_i(0), Y_i(1))\) 是随机且独立同分布的:换言之,存在一个关于处理和潜在结果的联合分布,且每个单位的数据(处理决策和潜在结果)都独立于其他单位的数据。

历史上,固定样本方法是为分析随机实验而发展起来的,至今仍是活跃的研究领域。超总体模型则较新,并在过去 40 年中得到了扩展。它可应用于随机实验和观察性研究。

在本书中,我们将重点讨论超总体模型。

稳定单位处理值假设 (SUTVA)

稳定单位处理值假设 (SUTVA) 是我们为了使用即将开发的方法而必须满足的一对假设。这些假设是:

参考文献

  1. Card, D. (1992). 利用工资的地区差异衡量联邦最低工资的影响。ILR 评论46(1),22–37。10.1177/001979399204600103

随机实验中的因果推断

原文:data102.org/ds-102-book/content/chapters/04/randomized-experiments

在本节中,我们将更深入地理解随机实验。回顾上一节中的潜在结果表:

单元 未接受处理时的结果 接受处理时的结果 是否接受处理?
1 ? \(Y_1(1)\) \(Z_1=1\)
2 \(Y_2(0)\) ? \(Z_2=0\)
3 \(Y_3(0)\) ? \(Z_3=0\)
4 ? \(Y_4(1)\) \(Z_4=1\)
5 \(Y_5(0)\) ? \(Z_5=0\)

随机化实验与潜在结果

在随机化实验中,我们通过将单元随机分配到处理组和对照组来避免处理混杂变量。换句话说,我们随机选择每个 \(Z_i\)​,并且使其独立于任何可能的 \(Y_i(0)\)\(Y_i(1)\)。用数学语言可以表示为:

\(\begin{align} \big(Y_i(0), Y_i(1)\big) \perp \!\!\! \perp Z_i \end{align}\) ​(1)

请记住,这并不意味着处理与观测结果无关!它仅意味着处理与潜在结果对是独立的。观测结果 \(Y_{i,obs} = Y_i(0)(1-Z_i) + Y_i(1) Z_i\)​ 总是依赖于处理决策。换句话说,知道处理决策 \(Z_i\)​ 总是能为我们提供关于我们观测到的是两个潜在结果中的哪一个\(Y_i(0)\)Yi​(0) 或 \(Y_i(1)\))的信息(除了在处理与结果完全无关这种无趣的情形之外),但是

例如,考虑一项双盲疫苗试验。我们可以思考某个特定受试者的潜在结果:它们代表了该受试者如果接种疫苗(\(Y_i(1)\))或未接种疫苗(\(Y_i(0)\))时会发生什么。这就是一对潜在结果,\(\big(Y_i(0), Y_i(1)\big)\)。接下来,考虑治疗决策\(Z_i\)​:这表示受试者是接种了疫苗(\(Z_i = 1\)Zi​=1)还是安慰剂(\(Z_i = 0\))。这两者是独立的:知道一个受试者是否接种了疫苗/安慰剂,并不能为我们提供关于那对潜在结果的信息:它只能告诉我们,在现实世界中我们观察到的是两者中的哪一个。

计算平均处理效应

你可能记得学过,在随机对照试验中,我们可以通过使用处理组和对照组之间的均值差来确定因果关系。现在让我们从数学上证明这一点。回顾上一节中我们对平均处理效应\(\tau\)的定义:

\(\tau = E[Y_i(1) - Y_i(0)] = E[Y_i(1)] - E[Y_i(0)]\) (2)

如果 \(Z_i\)​ 和 \(Y_i\)​ 相互独立,那么 \(E[Y_i(\cdot)] = E[Y_i(\cdot)|Z_i]\)。换言之,只要 \(Z_i\)​ 和 \(Y_i(\cdot)\) 相互独立,以 \(Z_i\) 为条件就不应改变期望值。

\(\begin{align} \tau &= E[Y_i(1)] - E[Y_i(0)] \\ &= E[Y_i(1)|Z_i=1] - E[Y_i(0)|Z_i=0] \quad{\scriptsize (\text{if }(Y_i(0), Y_i(1)) \perp \!\! \perp Z_i)} \end{align}\) ​​(3)

这两个术语分别对应处理组和对照组的平均结果。如果我们有 \(n\) 个观测值 \((Z_1, Y_{1,obs}), \ldots, (Z_n, Y_{n,obs})\),那么我们对平均处理效应(ATE)的经验估计即为:

\(\begin{align} \hat{\tau} &= \underbrace{\left[\frac{1}{n_1} \sum_{i: Z_i = 1} Y_i\right]}_{=\bar{Y}_{obs,1}} - \underbrace{\left[\frac{1}{n_0} \sum_{i: Z_i = 0} Y_i\right]}_{=\bar{Y}_{obs,0}}, \end{align}\) ​​(4)

其中 \(n_1\)​ 是处理组的单元数量,\(n_0\)​ 是未处理组的单元数量,而 \(\bar{Y}_{obs,1}\)​ 和 \(\bar{Y}_{obs,0}\)分别是处理组和对照组的均值。这个量 \(\hat{\tau}\)(您很可能在 Data 8 等课程中见过并使用过)有许多名称。以下是其中几个:

  • 均值差

  • 结果均值的简单差异 / 观测均值的简单差异 (SDO)

  • 内曼估计量

  • 表面因果效应\(\tau_{PF}\)​(prima facie 是拉丁语,意为“乍一看”)。

from IPython.display import YouTubeVideo
YouTubeVideo("dmrMZ5vERx4")
from IPython.display import YouTubeVideo
YouTubeVideo("0o_m_GIfe6I")

(可选)固定样本假设:费希尔与内曼

在本节中,我们将在固定样本假设下分析随机化实验和内曼估计量。回想一下,在这种设定中,我们假设 \(Z_i\)​(我们观察到的)是随机的,但 \(Y_i(0)\)\(Y_i(1)\)(未知的)是固定的。在这种情况下,上述的独立性陈述并不真正成立,因为 \(Y_i(0)\)\(Y_i(1)\) 并非随机变量。然而,我们仍然可以计算内曼估计量。我们将推导该估计量的一些性质,然后利用这些性质为估计的平均处理效应构建一个置信区间。最后,我们将探讨随机化实验中使用的两种不同的假设检验。

内曼估计量的性质

内曼估计量有两个有用的性质:首先它是无偏的,其次其方差可以用两组内的估计方差来界定:

\(\begin{align*} E[\hat{\tau}] &= \tau \\ \text{var}(\hat{\tau}) &\leq \frac{\hat{\sigma}_1²}{n_1} + \frac{\hat{\sigma}_0²}{n_0}, \end{align*}\) ​(5)

其中 \(\hat{\sigma}_k\)σ^k​ 是潜在处理结果 \(Y_1(k), \ldots, Y_n(k)\) 的样本标准差:

\(\begin{align*} \hat{\sigma}_k &= \frac{1}{n - 1} \sum_{i} \big(Y_i(k) - \bar{Y}(k)\big) \end{align*}\) ​(6)

由于这取决于我们无法观察到的反事实结果,我们通常会用数据中每个观察组内的样本方差来替代真实的样本方差,并称之为 \(\hat{V}\)

\(\begin{align*} \hat{V} &= \Bigg[\frac{1}{n_1} \underbrace{% \frac{1}{n_1 - 1} \sum_{i: Z_i = 1} \big(Y_i - \bar{Y}_{obs,1}\big)²}_{% \text{处理组样本标准差}}\Bigg] + \Bigg[\frac{1}{n_0} \underbrace{% \frac{1}{n_0 - 1} \sum_{i: Z_i = 0} \big(Y_i - \bar{Y}_{obs,0}\big)²}_{% \text{对照组样本标准差}}\Bigg] \end{align*}\) ​(7)

可以证明,在一定的正则性条件下,随着样本数量的增加,统计量 \(\frac{\hat{\tau} - \tau}{\sqrt{\hat{V}}}\)​ 的分布收敛于正态分布 \(N(0, \sigma²)\),其中 \(\sigma² < 1\)

平均处理效应的置信区间

基于上述事实,我们可以为 \(\tau\) 构建一个渐近有效的 \(95\%\) 置信区间,如下所示:

\(\begin{align*} \left(\hat{\tau} - 1.96\sqrt{\hat{V}}, \hat{\tau} + 1.96\sqrt{\hat{V}}\right) \end{align*}\) ​(8)

因果效应的假设检验

在经典的零假设显著性检验框架中,随机化试验中衡量因果效应时通常使用两种不同的零假设:

  1. Fisher 强零假设(也称为 Fisher 尖锐零假设)认为对于每个单元,处理效应 \(Y_i(1) - Y_i(0) = 0\)

  2. Neyman 弱零假设 认为平均处理效应为 0。换句话说,即使某些个体的处理效应为正,某些为负,它们平均起来为 0。

第一种是严格得多的零假设(因此得名“强零假设”),因为它声明所有处理效应均为 0。第二种则较为宽松,仅要求处理效应的平均值为 0。正因如此,强零假设通常更容易被拒绝。

为了检验奈曼弱零假设,我们构建检验统计量 \(\frac{\hat{\tau}}{\sqrt{\hat{V}}}\)。在奈曼弱零假设下,它应服从标准正态分布。

为了检验费希尔强零假设,我们需要一些更强的数学工具。我们将自己限制在处理和结果均为二值变量的情况,并使用一种称为置换检验的技术。在该技术中,我们随机打乱处理/对照标签,并以此构建结果差异的零分布(如需复习此技术,请参阅《数据 8》教材)。然而,费希尔精确检验并非随机打乱,而是考察每一个可能的置换,并以封闭形式计算 \(p\) 值。

(可选)随机化实验的复杂性

随机化实验面临若干挑战,使其在某些情况下不可行。以下是其中出现的一些问题。请注意,此列表远非详尽!

依从性

实施随机化实验要求实验单元遵循其处理/非处理分配。在某些实验中,这比其他实验更容易确保。请考虑以下示例:

  1. 一项旨在确定新肥料是否能提高作物产量的实验

  2. 一项双盲疫苗试验

  3. 一项关于每天使用正念应用程序至少 20 分钟是否能改善睡眠的实验

  4. 一项关于食用特定量巧克力是否能改善心脏功能的实验

对于前两者,我们可以保证处理会被正确遵循。作为实验者,我们知道如果某个单元(植物、人等)被分配到处理组,他们将接受处理。

然而,对于后两者,则较难确保。虽然我们可以要求研究中的受试者每天使用应用程序一定时间,但我们无法保证每位受试者都能完美遵循指示。

特别是在某些随机实验中,我们无法保证实验单位会遵从治疗分配。我们不能简单地移除不遵从的单位来解决这个问题,因为这可能会引入偏差和/或混杂因素。

外部效度

外部效度指的是随机实验的发现是否适用于更广泛的目标人群。这通常源于抽样偏差、实验中不反映真实世界条件的人为情境,或其他类似效应。

考虑以下示例:

假设我们想确定观看一段 15 分钟的视频,内容是关于概率常见误区和误解,是否有助于人们更好地判断所读新闻故事的有效性。我们招募了一组数据 102 课程的随机学生样本,随机分配一半观看视频,另一半不观看。然后我们评估学生们批判性评估几个新闻故事的能力。我们发现视频没有效果:无论是否观看视频,我们样本中的每个人都表现出色。

在这种情况下,我们的随机实验显示没有因果效应,但我们的样本并不能代表整体人群:我们可以预期,大多数概率专家——数据 102 课程的学生——已经了解 15 分钟视频中常见的误区。然而,在更广泛的人群中,情况可能并非如此!

伦理考量

随机实验可能并不总是符合伦理。假设我们想确定死刑是否具有威慑作用:换句话说,实施死刑是否会导致犯罪率下降?

随机分配死刑是极不道德的,任何随机实验都无法确定这一点。

观察性研究中的因果推断:无混杂性

原文:data102.org/ds-102-book/content/chapters/04/observational-studies-unconfoundedness

到目前为止,我们已经探讨了在任何数据集中衡量关联性的方法。我们也研究了如何在随机对照试验中得出因果结论。但在许多情况下,随机对照试验并不可行。其原因可能包括:

  • 伦理考量:假设我们相信某种疗法可能对人有害。为了在随机对照试验中验证这一信念,我们将不得不让受试者接受我们相信会伤害他们的疗法,这是极不道德的。在这种情况下,我们需要其他方法来得出因果结论:作为数据科学家和研究人员,我们有责任避免重蹈过去的错误

  • 成本:进行随机对照试验可能非常昂贵:我们必须找到能代表总体的随机选择的个体;对其中一半施加干预;并测量结果,这可能需要很长一段时间。在某些情况下,这可能是不可行的。

  • 样本量:由于成本考虑,随机对照试验可能没有足够的受试者来得出强有力的统计结论。

关于观察性研究有大量可用的数据。在本节中,我们将探讨如何利用这些数据来得出因果结论。正如我们在考察关联性时所看到的,试图得出因果结论时最常见的问题是存在混杂变量。我们将重点关注那些试图解释这些混杂变量的方法。

为何困难

在处理来自观察性研究的数据时,我们不能再简单地取均值差值。为了理解为什么不能,让我们回到餐厅的例子。我们将进行与之前相同的计算,但这次从潜在结果的角度出发。

考虑数据的科学表版本(其中我们将 👍 转换为 1,👎 转换为 0,因此平均值对应喜欢的百分比):

import pandas as pd

food = pd.read_csv('data/restaurants_counterfactuals.csv')
food.head()

加载中...

NaN 值表示我们无法观测到的缺失反事实结果:例如,菜 0 是从餐厅 A 点的,所以我们不知道评论家在那天会给餐厅 B 的菜打多少分。

以这种方式展示数据需要我们稍微不同地思考它,并突显了潜在结果框架的含义!特别是:

  • \(Z_i\)​ 对应评论家是从餐厅 A 还是餐厅 B 点菜 \(i\)

  • \(Y_i(A)\)\(Y_i(B)\) 代表假设情况:如果评论家从特定餐厅点了菜 \(i\),他们会喜欢它吗?

  • 年份是我们的混杂因素(或混杂变量):我们将使用 \(X_i\)​ 来表示菜 \(i\) 被点单的年份。

我们以这种方式表示数据,清楚地表明我们无法计算平均处理效应(ATE):

food_ATE = (food["Dish rating for B"] - food["Dish rating for A"]).mean()
food_ATE

nan

在试图理解餐厅选择与食物喜好之间的因果关系时,天真地使用观测均值的简单差值(SDO)可能会误导我们,正如我们已经看到的:

food_sdo = food["Dish rating for B"].mean() - food["Dish rating for A"].mean()
food_sdo

np.float64(0.20000000000000007)

混杂变量与超总体模型

在随机对照试验中,由于处理分配 \(Z_i\)​ 与潜在结果 \(\big(Y_i(0), Y_i(1)\big)\) 之间的独立性,我们可以使用均值差估计量。但是,在观察性研究中,这种独立性假设不再成立。我们在前几节中已经看到了几个这样的例子,其中混杂变量同时影响处理和结果。

在本节中,我们将重点介绍几种方法来回答这个问题:如果我们同时观察到混杂变量,我们能否确定处理是否对结果有影响?

我们的符号表示将与之前使用的类似,并且我们会为每个单元添加一个新变量 \(X_i\)​ 来表示混杂信息。对于每个单元,我们拥有独立同分布(即一个单元的元组与下一个单元的元组相互独立)的元组 \((Z_i, Y_i(0), Y_i(1), X_i)\)。这被称为超总体模型

条件平均处理效应与无混杂性

我们将定义一个新的量,称为条件平均处理效应。这类似于我们之前看到的平均处理效应,但条件是 \(X\) 的特定值:

\(\text{CATE} = \tau(x) = E[Y(1) - Y(0) | X = x]\) (1)

仅仅引入条件设定本身对我们没有帮助:如果我们使用塔性质来对 \(Z\) 进行条件设定以展开此式,我们仍然会得到反事实项。那么我们为什么要这样做呢?事实证明,我们只需要做一个假设就能让它帮助我们。这个假设被称为无混杂性

\(\big(Y(0), Y(1)\big) \perp \!\!\! \perp Z \mid X\) (2)

这是一个条件独立性的陈述。潜在结果 \(Y(0)\)\(Y(1)\) 可能并不独立于处理决策 \(Z\),但这个假设指出,如果我们知道混杂变量 \(X\) 的值,它们就是条件独立的。

在上面的餐厅例子中,无混杂性意味着在每一年内,是从 A 还是 B 点餐(处理)的决定,与 A 和 B 的菜品会获得的评分(潜在结果)是独立的。它并没有说餐厅与评分是条件独立的:这是一个微妙但重要的区别。

如果我们假设无混杂性,那么我们就可以安全地使用条件均值的差异来估计 CATE:

\(\begin{align} \tau(x) &= E[Y(1) - Y(0) | X = x] &\\ &= E[Y(1) | X = x] - E[Y(0) | X = x] & {\tiny\text{(linearity of (conditional) expectation)}}\\ &= E[Y(1) | X = x, Z = 1] - E[Y(0) | X = x, Z = 0] & {\tiny\text{(unconfoundedness)}} \end{align}\) ​​(3)

从 CATE 到 ATE

在大多数情况下,我们真正感兴趣的是平均处理效应(ATE)。我们如何从条件平均处理效应(CATE)得到 ATE 呢?我们可以使用迭代期望定律(塔性质):

\(E[Y(1) - Y(0)] = E_X[E_Y[Y(1)-Y(0) | X]]\) (4)

换句话说,

\(\begin{align} ATE &= E[\tau(X)] \\ &= \sum_x \tau(x) P(X=x) \end{align}\) ​​(5)

(如果 \(X\) 是连续的,我们将使用积分代替求和)。

这在实践中是怎样的呢?让我们回到餐厅的例子。我们不再计算整个数据集的均值差,而是分别计算,一次针对 2020 年,一次针对 2019 年:

food_2020 = food[food['Year'] == 2020]
food_2019 = food[food['Year'] == 2019]

food_sdo_2020 = food_2020["Dish rating for B"].mean() - food_2020["Dish rating for A"].mean()
food_sdo_2019 = food_2019["Dish rating for B"].mean() - food_2019["Dish rating for A"].mean()
print(f"{food_sdo_2020}, {food_sdo_2019}")
-0.05555555555555558, -0.125 

这些分别对应于条件平均处理效应(CATE)\(\tau(2020)\)\(\tau(2019)\)。我们可以用英语解释 \(\tau(2019)\) 为:“给定一道菜是在 2019 年点的,选择餐厅 B(而非 A)对评论家喜欢其食物的概率的因果效应是什么?”,对于 \(\tau(2020)\) 同理。

就像之前一样,我们看到这些值的符号与在整个数据上计算出的简单差异估计(SDO)相反:餐厅 A 在 2020 年和 2019 年更受欢迎。但是,由于评论家在 2020 年(总体评分较低)更多地从餐厅 A 点餐,餐厅 A 在汇总数据中看起来更差。

我们如何结合这些来估计平均处理效应(ATE)?我们需要边际概率 \(P(2020)\)\(P(2019)\)

p_2020 = (food['Year'] == 2020).mean()
p_2019 = 1 - p_2020
print(f"{p_2019=}, {p_2020=}")

est_food_ATE = food_sdo_2020 * p_2020 + food_sdo_2019 * p_2019
print("Estimated ATE:", est_food_ATE)
p_2019=np.float64(0.33333333333333337), p_2020=np.float64(0.6666666666666666)
Estimated ATE: -0.07870370370370372 

这是我们从观测数据中进行的首次因果推断!正式表述如下:

如果我们假设年份是餐厅选择对评价影响的唯一混杂因素,那么选择餐厅 A 会导致评论家对菜品的喜爱度提高约 8%

当我们假设年份是唯一的混杂因素时,我们就是在假设**无混杂性*:即给定年份,一对潜在结果(即关于他们评价的假设情况)与他们实际选择的餐厅无关。

请注意,我们不能笼统地断言因果关系:这个结论在很大程度上依赖于我们对无混杂性假设的使用。这通常是正确的:为了在随机对照实验之外得出因果推断,我们需要做出假设。在报告你的因果结论时,始终重要的是要说明你所做的假设!

结果回归

即将推出

逆倾向性加权

在逆倾向加权中,我们直观地试图通过基于治疗和混杂变量对结果变量进行重新加权,来抵消或补偿混杂变量的影响。为了说明这种效果,我们将使用一个包含二十个人的高血压治疗的合成数据集。数据包含三列:

  • treated,表示每个人是否接受了治疗

  • is_old,患者年龄是否大于 45 岁(1 表示是,0 表示否)

  • bp_change,该个体的(收缩压)血压下降了多少(单位:毫米汞柱)

bp = pd.read_csv('data/bp_treatment.csv')
bp

正在加载...

bp.groupby(['is_old', 'treated']).mean()

正在加载...

快速浏览数据,我们可以发现几个事实:

  • 大多数接受治疗的人是老年人

  • 几乎所有年轻人的血压都改善了(即下降了)

  • 大多数老年人的血压并未改善

  • 我们的数据点非常少。总共只有二十个人,一旦对数据进行切片,数量就更少了。例如,接受治疗的老年人只有两位。

为了说明问题,我们暂时忽略最后一点。前三点非常清楚地表明,年龄是治疗效应的一个混杂变量。量化混杂程度的一种方法是使用给定混杂变量下接受治疗的条件概率:这被称为倾向得分

bp.groupby(['is_old', 'treated']).count()

正在加载...

对于老年组,接受治疗的概率是 \(7/9 \approx 0.78\)。对于年轻组,概率是 \(4/11 \approx 0.36\)。我们可以将这些数据添加到表格中:

bp['propensity_score'] = bp['is_old'] * 7/9 + (1-bp['is_old']) * 4/11
bp

正在加载...

这告诉我们,考虑到第 0 号个体是年轻人,其接受治疗的概率为 0.36,依此类推。

重新加权

如果我们试图天真地使用上述数据计算因果效应,我们将会被误导:

treatment_mean_confounded = bp.loc[bp['treated'] == 1, 'bp_change'].mean()
control_mean_confounded = bp.loc[bp['treated'] == 0, 'bp_change'].mean()
treatment_mean_confounded - control_mean_confounded

np.float64(-0.6797979797979803)

这种效应似乎非常微小:仅下降了 0.68 毫米汞柱。这是由于混杂效应造成的:对照组中年轻人比例过高,而治疗组中年轻人比例过低(老年人则相反)。

如果我们尝试通过重新加权每个数据点来调整过度代表/代表不足的情况呢?让我们从观察治疗组开始。对于这个组,我们应该增加年轻人的权重(因为他们数量太少)。有很多方法可以做到这一点:其中之一是除以倾向得分。具体来说,我们将采用的方法将为我们提供ATE 的无偏估计,即使在存在混杂因素的情况下也是如此!

bp['outcome_reweighted'] = bp['bp_change'] / bp['propensity_score']
bp.loc[bp['treated'] == 0, 'outcome_reweighted'] = pd.NA  # only look at treatment group for now
bp

正在加载...

这显著增加了年轻组改善的权重(并且只非常轻微地增加了年长组改善的权重)。

那么对照组呢?在那里,我们希望增加老年人的权重。除以倾向得分会产生与我们期望相反的效果:相反,我们将除以 1 减去倾向得分。

control = bp['treated'] == 0
bp.loc[control, 'outcome_reweighted'] = (
    bp.loc[control, 'bp_change'] / (1 - bp.loc[control, 'propensity_score'])
)
bp

加载中...

现在,我们可以比较重新加权后的均值差异:

treatment_total = bp.loc[bp['treated'] == 1, 'outcome_reweighted'].sum()
control_total = bp.loc[bp['treated'] == 0, 'outcome_reweighted'].sum()

(treatment_total - control_total)/20

np.float64(-1.857142857142857)

我们可以看到,这能更好地捕捉真实效果!

逆倾向得分加权(IPW)

根据上述逻辑以及我们对表格的临时处理,我们现在可以定义 ATE 的 IPW(逆倾向加权)估计量:

\(\hat{\tau}_{IPW} = \frac{1}{n} \underbrace{% \sum_{i: Z_i = 1} \frac{Y_i}{e(X_i)} }_{\text{reweighted treated rows}} - \frac{1}{n} \underbrace{% \sum_{i: Z_i = 0} \frac{Y_i}{1-e(X_i)} }_{\text{reweighted control rows}}\) ​​​​(6)

这是一个特别有用的估计量,因为如果无混淆假设(也称为条件独立假设或 CIA)成立,那么这将是真实 ATE 的无偏估计\(E[\hat{\tau}_{IPW}] = \tau\)

直观上,为什么除以倾向得分(对于处理组)和 1 减去倾向得分(对于控制组)是合理的?我们试图找到那些不太可能最终进入其所在组的单元,并赋予它们更高的权重。回想一下,倾向得分及其补数是条件概率:

\(\begin{align*} e(x) &= P(Z=1 | X=x) \\ 1 - e(x) &= P(Z=0 | X=x) \end{align*}\) (7)

对于处理组,我们希望找到那些由于混杂因素而不太可能最终进入处理组的单元(在上面的例子中,这对应于找到更年轻的人)。这些单元的倾向得分会非常小,因此我们可以通过除以它来增加它们的权重。

对于控制组,我们希望找到那些由于混杂因素而不太可能最终进入控制组的单元(在上面的例子中,这对应于找到更年长的人)。这些单元的倾向得分会非常(接近 1),因此我们可以除以 1 减去倾向得分来增加它们的权重。

同样的过程会对组内过度代表的单元赋予较小的权重。

关于上述 IPW 估计量能给出真实 ATE 的无偏估计的证明,请参阅以下来源之一。在查看答案之前,尝试自己证明一下作为练习!

使用逻辑回归计算倾向得分

在这个例子中,我们的混杂因素是二元的,这使得通过简单算术计算倾向得分变得非常容易。但在许多应用中,混杂因素可能是连续的,或者我们可能有多个混杂因素。在这种情况下,计算倾向得分就不那么简单了。

在这些情况下,我们通常使用逻辑回归来预测它们。请注意,尽管这是您熟悉的逻辑回归,但使用它的目的却大不相同:在这里,我们并非试图进行预测或追求高预测精度。相反,我们只是用它来确定每一行(基于混杂因素)最终进入处理组的概率是多少?

这意味着我们无需担心将数据分割为测试集和训练集:相反,我们在整个数据集上拟合模型,并将预测概率用作倾向得分。

工具变量

原文:data102.org/ds-102-book/content/chapters/04/instrumental-variables

特别感谢Yan Shuo Tan,他撰写了本节的大部分内容。

回顾与介绍

简要回顾我们目前所学的内容:

  1. 我们定义了一个超总体模型,即 \((X_i,Z_i,Y_i(0),Y_i(1))\) 的分布:

    • \(Z\) 是(二元)处理决策,

    • \(Y(0)\)\(Y(1)\) 分别是该单元未接受/接受处理时的潜在结果,

    • \(X\) 是一个混杂变量(换句话说,它对 \(Z\)\(Y\) 都有因果效应)。到目前为止,我们尚不需要对这些变量的分布做任何一般性假设(只需假设其存在即可)。

  2. 我们定义了我们的关注量,即平均处理效应:\(\tau = E[Y(1) - Y(0)]\),它告诉我们处理的平均效果。我们看到,除非做出进一步的假设,否则这是无法估计的。

  3. 我们看到,在随机实验中,我们有以下情况:

    • 处理决策是随机的,因此与潜在结果无关

    • 换句话说,\(\big(Y(0),Y(1)\big)\perp\!\!\perp Z\)

在本节中,我们将探讨在存在未知混杂变量的情况下如何估计平均处理效应。我们将借助自然实验来帮助我们。请注意,你可能在 Data 8 课程中学习约翰·斯诺的霍乱研究时,已经见过自然实验

线性结构模型 (LSM)

在某些领域(如经济学),通常使用结构模型,这类模型对所有变量的联合分布施加了一些限制,从而使得模型参数的估计更为容易。

我们将使用线性结构模型来描述我们的结果 \(Y\) 与处理 \(Z\) 以及混杂变量 \(X\) 之间的关系:

\(Y = \alpha + \tau Z + \beta^TX + \epsilon,\) (1)

其中 \(\epsilon\) 均值为零,且独立于 \(Z\)\(X\)(在经济学中,我们说 \(\epsilon\)外生的)。我们有时进一步假设 \(\epsilon \sim \mathcal{N}(0,\sigma²)\),但这对于我们即将进行的任何分析都不是必需的。

注:通常,我们还会进一步添加结构方程 \(Z = f(X, \delta)\),其中 \(\delta\) 是一个外生噪声变量,而 \(f\) 编码了 \(X\)\(Z\) 之间的结构关系。我们不会深入到这个细节层面,但在阅读这个方程时,你应该假设 \(\textrm{Cov}(Z,X)\) 不一定为 0。

这和我们学习广义线性模型时见过的线性模型,以及你在之前课程中见过的模型并不完全相同!虽然看起来非常相似,但我们之前使用的线性模型是关于关联和预测的陈述,而这个线性结构模型则是关于干预和行动的陈述。

具体来说,该模型假设对于单元 \(i\),如果我们能设定 \(Z_i = 1\),我们将观测到 \(Y_i(1) = \tau + \beta^TX_i + \epsilon_i\);如果我们能设定 \(Z_i = 0\),我们将观测到 \(Y_i(0) = \beta^TX_i + \epsilon_i\)。(如果 \(Z\) 不是二元的,那么对于 \(Z\) 的每个可能取值都会有一个潜在结果。)这是一个微妙但重要的点,同时也将线性结构模型定位为潜在结果框架的一个特例!

由此,我们看到该模型中的平均处理效应是 \(\tau\)(你能证明这是真的吗?),并且,每个单元的个体处理效应也是

\(Y_i(1) - Y_i(0) = \tau.\) (2)

换句话说,线性结构模型隐含地假设了处理效应在所有单元中是恒定的。

因果图与线性结构模型

除了\(Z\)\(Y\)的因果效应外,线性结构模型还引入了一些新的内容。它断言了其他变量之间的因果关系,即告诉我们如果干预\(X\)\(Z\)\(Y\)将如何变化。

上述线性结构模型可以用图形表示如下:

需要提醒的是,从\(X\)指向\(Z\)\(Y\)的箭头断言了\(X\)同时导致\(Z\)\(Y\)(即干预\(X\)会改变\(Z\)\(Y\)的值),而从\(Z\)指向\(Y\)的箭头则断言了\(Z\)导致\(Y\)

混杂与遗漏变量偏误

在许多场景中,混杂情况很复杂,涉及许多不同的变量,可能无法收集、观察或描述所有这些变量。在这种情况下,我们必须假设 \(X\) 是未被观测到的。如果发生这种情况,那么和之前一样,我们会因为混杂而陷入困境。以下是一些例子。在每个例子中,我们只列出了一个可能的混杂因子 \(X\),但很可能还有更多:你能为每个例子至少想出一个吗?

处理 \(Z\) 结果 \(Y\) 可能的混杂因子 \(X\)
医疗保险 健康结果 社会经济背景
兵役情况 薪资水平 社会经济背景
家庭规模 母亲是否参与劳动力市场 社会经济背景
受教育年限 薪资水平 社会经济背景
吸烟 肺癌 社会经济背景

请注意,在大多数这些例子中,社会经济背景是一个混杂因子。这在经济学和计量经济学中尤其常见,本节中的大部分方法都源于这些领域。

让我们更精确地量化混杂效应。具体来说,我们将假设上述线性结构模型,然后看看当我们天真地尝试用 \(Z\)\(Y\) 拟合线性回归,而不考虑 \(X\) 时会发生什么。

\(\hat{\tau}_{OLS}\)​ 为最小二乘问题 \(\min_{\tau,\alpha} \mathbb{E}[(\alpha + \tau Z - Y)²]\) 的解。我们于是得到

\(\begin{align} \hat{\tau}_{OLS} & = \frac{\text{Cov}(Y,Z)}{\text{Var}(Z)} \\ & = \frac{\text{Cov}(\alpha + \tau Z + \beta^TX + \epsilon,Z)}{\text{Var}(Z)} \\ & = \frac{\text{Cov}(\tau Z,Z)}{\text{Var}(Z)} + \frac{\text{Cov}(\beta^TX,Z)}{\text{Var}(Z)} \\ & = \underbrace{\tau}_\text{真实 ATE} + \underbrace{\beta^T\frac{\text{Cov}(X,Z)}{\text{Var}(Z)}}_{\text{涉及 }X\text{ 的偏差}}. \end{align}\) ​​(3)

第二项是 \(\tau_{OLS}\)​ 估计量中的一个偏差:换句话说,它是真实值与估计量之间的差异,并且取决于被遗漏(即未观测到)的变量 \(X\)。因此,我们将此项 \(\beta^T\frac{\text{Cov}(X,Z)}{\text{Var}(Z)}\)​ 称为遗漏变量偏差

备注:\(\frac{\text{Cov}(Y,Z)}{\text{Var}(Z)}\)​ 是典型公式 \(\hat{\tau}_{OLS} = (Z^TZ)^{-1}Z^TY\) 的无限总体版本,其中我们现在使用 \(Z\)\(Y\) 来表示矩阵/向量。

为什么我们不能直接调整混杂因素? 存在这样的混杂因素是有问题的,因为为了避免遗漏变量偏差,我们需要观测到它们,并将其加入我们的回归中(由于多种原因,收集此类数据可能并不总是可行的)。此外,总可能存在我们未意识到的其他混杂因素,这使得我们的因果结论笼罩在无法摆脱的疑云之中。

工具变量

在随机实验和有时不切实际的非混杂性假设之间,是否存在一条中间道路?

一种可行的方法是,当自然界为我们提供了一个“部分”自然实验时,即我们拥有一个真正随机的“工具”,它能向感兴趣的处理变量中注入部分随机性元素。这就是工具变量的思想。我们将首先从数学上定义这个概念,然后通过几个例子说明其含义。

定义: 假设上述线性结构模型成立。我们进一步假设存在一个变量 \(W\),使得 \(Z = \alpha' + \gamma W + (\beta')^TX + \delta\),其中 \(\gamma \neq 0\)(相关性),\(W\) 独立于 \(X\)\(\delta\)\(\epsilon\)(外生性)。这样的 \(W\) 被称为工具变量

注: 这取代了之前的方程 \(Z = f(X,\delta)\)

现在让我们看看如何使用 \(W\) 来识别平均处理效应 \(\tau\)

\(\begin{align} \textrm{Cov}(Y,W) & = \textrm{Cov}(\alpha + \tau Z + \beta^TX + \epsilon,W) \\ & = \tau \textrm{Cov}(Z,W) \\ & = \tau \textrm{Cov}(\alpha' + \gamma W + (\beta')^TX + \delta, W) \\ & = \tau\gamma \textrm{Var}(W). \end{align}\) ​​(4)

第二个等式源于 \(W\) 的外生性。同时,对 \(Z\)\(W\) 进行类似计算可得

\(\textrm{Cov}(Z,W) = \gamma\textrm{Var}(W).\) (5)

将所有结果合并可得

\(\tau = \frac{\frac{\textrm{Cov}(Y,W)}{\textrm{Var}(W)}}{\frac{\textrm{Cov}(Z,W)}{\textrm{Var}(W)}}.\) (6)

换句话说,\(\tau\)WY 的(无限总体)回归系数与 WZ 的回归系数之比。

这引出了有限样本中平均处理效应(ATE)的工具变量估计量

\(\hat{\tau}_{IV} = \frac{\overbrace{(W^TW)^{-1}W^TY}^{\text{OLS coeff. of W for Y}}}{\underbrace{(W^TW)^{-1}W^TZ}_{\text{OLS coeff. of W for Z}}},\) (7)

这里再次(在不严格的意义上)使用符号,\(W\)\(Z\)\(Y\) 指的是观测值向量。如果\(\alpha' = 0\),那么这就是\(\tau\)的一个插件估计量,并且是一致的。

对二元\(W\) 的进一步解释:\(W\) 为二元变量时,我们可以证明

\(\tau = \frac{\mathbb{E}[Y|W=1] - \mathbb{E}[Y|W=0]}{\mathbb{E}[Z|W=1] - \mathbb{E}[Z|W=0]}.\) (8)

因此,我们可以将工具变量(IV)视为衡量\(W\)\(Y\) 的表观处理效应与\(W\)\(Z\) 的表观处理效应之比。

工具变量的因果图

\(W, Z, X\)\(Y\) 之间的关系可以表示为以下因果图:

如何解读此图:

  • \(W\) 指向 \(Z\) 的箭头表明 \(W\)\(Z\) 具有因果效应。

  • 没有任何箭头指向 \(W\),意味着 \(W\) 是外生的,即图中没有变量导致 \(W\),特别是 \(W\) 独立于 \(X\)

  • \(W\) 指向 \(Y\) 的箭头缺失,意味着 \(W\)\(Y\) 的唯一影响是通过 \(Z\) 实现的。

  • 我们将 \(W\)\(Z\)\(Y\) 标记为阴影,因为这些节点是已观测的;而 \(X\) 未标记阴影,因为它是潜在的(未观测的)。

请注意,为了使工具变量法有效,我们不需要知道甚至不需要意识到 \(X\) 是什么!无论存在多少混杂因素,或者我们是否能够列出所有混杂因素,只要我们能保证它们与工具变量没有任何因果关系(排除限制),工具变量法就会有效。

工具变量示例

让我们来探讨一下,对于上一节表格中的五个例子,我们可以使用什么作为工具变量。前四个例子取自计量经济学文献:

示例 1: \(Z\) 是医疗保险,\(Y\) 是健康结果,\(X\) 是社会经济背景。Baicker 等人(2013)利用 2008 年俄勒冈州通过抽签方式扩大医疗补助计划。这里的工具变量 \(W\) 是抽签分配。我们之前讨论过,由于依从性原因(只有一部分中签者实际加入了医疗补助计划),这是一个不完美的实验,因此工具变量法提供了一种克服此限制的方法。

示例 2: \(Z\) 是兵役,\(Y\) 是薪资,\(X\) 是社会经济背景。Angrist(1990)使用越南战争时期的征兵抽签作为工具变量 \(W\),并发现白人退伍军人的收入比非退伍军人低 15%。

示例 3: \(Z\) 是家庭规模,\(Y\) 是母亲的就业状况,\(X\) 是社会经济背景。Angrist 和 Evans (1998) 使用兄弟姐妹性别构成(即兄弟姐妹出生时的指定性别)作为工具变量。这是合理的,因为兄弟姐妹性别构成存在伪随机性。其依据是,在美国,有两个同性别的孩子的父母比有两个不同性别孩子的父母更有可能生育第三个孩子。

示例 4: \(Z\) 是受教育年限,\(Y\) 是薪资,\(X\) 是社会经济背景。Card (1993) 使用大学邻近度的地理差异作为工具变量。

示例 5: \(Z\) 是吸烟,\(Y\) 是肺癌,\(X\) 是社会经济背景。不幸的是,这个例子不太适合使用工具变量:尽管经过数十年的寻找,至今还没有人找到一个令人信服的工具变量。这引出了一个重要的教训:并非所有问题都适合使用工具变量,甚至自然实验!

正如我们在这些例子中看到的,有时你需要相当巧妙才能想出一个合适的工具变量。Joshua Angrist、David Card 和 Guido Imbens(这些例子中多次提到他们)在这方面非常出色:事实上,他们因其集体工作成果获得了诺贝尔经济学奖!

扩展

多重处理变量 / 工具变量,以及两阶段最小二乘法。

到目前为止,我们考虑的是标量处理变量和工具变量 \(Z\)\(W\)。也可以考虑向量值的工具变量和处理变量。为了将 IV 推广到这种设置,我们需要将前面章节中的 IV 估计量重新表述如下。

首先定义条件期望\(\tilde{Z} = \mathbb{E}[Z|W]\),并注意到\(\tilde{Z} = \alpha' + W\gamma\)

如果我们对\(Y\) 关于\(\tilde{Z}\)进行回归,得到的回归系数是

\(\begin{align} \frac{\textrm{Cov}(\tilde{Z},Y)}{\textrm{Var}(\tilde{Z})} & = \frac{\textrm{Cov}(\tilde{Z}, \alpha + \tau Z + \beta^TX + \epsilon)}{\textrm{Var}(\tilde{Z})} \\ & = \frac{\textrm{Cov}(\tilde{Z}, \tau Z)}{\textrm{Var}(\tilde{Z})} \\ & = \tau\frac{\textrm{Cov}(\tilde{Z}, Z)}{\textrm{Var}(\tilde{Z})} \\ & = \tau. \end{align}\) ​​(9)

此处,第二个等式成立是因为 \(W\) 独立于所有 \(X\)\(\epsilon\),而第四个等式成立则基于条件期望的一个性质(也可以通过展开 \(Z = \alpha' + \gamma W + (\beta')^TX + \delta\)来手动验证)。

在有限样本中,我们因此得到以下算法:

两阶段最小二乘法(2SLS):

  • 步骤一:将 \(Z\)\(W\) 进行回归,得到 \(\tilde{Z} = W\hat{\gamma} = W(W^TW)^{-1}W^TZ\)

  • 步骤二:将 \(Y\)\(\tilde{Z}\) 进行回归,得到 \(\hat{\tau}_{2SLS} = (\tilde{Z}^T\tilde{Z})^{-1}\tilde{Z}^TY\)

在标量设定下,很容易看出 \(\hat{\tau}_{2SLS} = \hat{\tau}_{IV}\)​,但这种表述方式的优势在于它直接适用于向量值的 \(Z\)\(W\)

(可选)工具变量的非参数视角

在本笔记中,我们已在结构线性模型的背景下介绍了工具变量。如果我们的模型是非线性的呢?

一个惊人的巧合是,对于二元处理 \(Z\),表达式

\(\tau = \frac{\mathbb{E}[Y|W=1] - \mathbb{E}[Y|W=0]}{\mathbb{E}[Z|W=1] - \mathbb{E}[Z|W=0]}.\) (10)

其意义超越了线性模型设定。这正是 Angrist 和 Imbens 于 1996 年发表的这篇开创性论文的主题。

第五章:尾界与集中不等式

原文:data102.org/ds-102-book/content/chapters/05/intro/

本章涵盖尾界和集中不等式。

尾界与集中不等式

原文:data102.org/ds-102-book/content/chapters/05/concentration

import numpy as np
from scipy import stats
from IPython.display import YouTubeVideo

import matplotlib
%matplotlib inline
import matplotlib.pyplot as plt
import seaborn as sns

sns.set()

在本节中,我们需要更精确一些,因此将使用大写字母表示随机变量,小写字母表示它们所取的值。

你可能需要回顾 Data 140 教材的 12.319.4 节。

动机与介绍

什么是集中不等式?

集中不等式提供了一种量化随机变量位于其分布尾部(即远离均值的分布两端部分)的可能性的方法。我们通常将这些不等式写成涉及随机变量 \(X\) 与其均值 \(E[X]\) 之间距离的形式。例如,考虑以下不等式:

\(P(|X-E[X]|\geq t) \leq \epsilon\) (1)

它考察的是随机变量 \(X\) 与其均值 \(E[x]\) 之间的距离,并指出该距离较大(具体来说,大于某个阈值 \(t\))的概率很小(具体来说,小于某个值 \(\delta\))。

这类不等式有助于理解任意随机变量的行为,以及使用随机变量的算法。例如,考虑一组独立同分布的随机变量 \(X_1, \ldots, X_n\)​,及其样本均值:

\(Y = \frac{1}{n} \sum_i X_i\) ​(2)

如果我们使用 \(Y\) 来估计 \(X_i\)​ 的均值,那么我们已经可以保证 \(Y\) 的平均值将是正确答案:\(E[Y] = E\left[(1/n) \sum_i X_i\right] = E[X_i]\)。但这个陈述并没有告诉我们偏离正确答案的可能性有多大。\(Y\) 有可能与 \(E[X_i]\) 相差甚远吗?发生这种情况的可能性有多大?

集中不等式为这类问题提供了答案:如果我们能保证

\(P(|Y - E[X_i]| \geq t) \leq \epsilon,\) (3)

那么我们就能对自己的结果更有信心。假设上述陈述对于 \(t=1\)\(\epsilon=0.01\) 成立。那么我们可以明确地陈述:“\(Y\) 有很大可能落在真实均值 \(E[X_i]\) 的 1 个单位范围内:其偏离更远的概率小于 \(1\%\)。”

如果 \(X_i\)​ 是从一个已知分布(如高斯分布、二项分布、泊松分布等)中抽取的,那么我们可以精确计算这些概率,也就不需要集中不等式了。当我们对一个随机变量的信息较少,并且希望做出无论具体分布如何都成立的强有力陈述时,这些不等式就很有用。

YouTubeVideo('KiD4SHKpVho')

加载中...

上面的不等式是一个双侧不等式,因为它同时考虑了上尾和下尾。有时,我们也会考虑单侧集中不等式,例如 \(\mathbb{P}(X-E[X] \geq t) \leq \epsilon\)\(\mathbb{P}(X-E[X] \leq -t) \leq \epsilon\)

为什么我们对集中不等式感兴趣?

YouTubeVideo('AmPJr9F90wQ')

加载中...

示例:单侧上尾界

我们将从考察一个随机变量 \(X\) 的上尾界开始,该变量具有未知密度 \(f\) 和相应的累积分布函数 \(F\)。具体来说,我们希望找到一个概率上界 \(\alpha\),使得在大多数情况下,\(X\) 的取值小于 \(\alpha\)。形式上,我们可以用以下两种方式之一来表达:

$\begin{align} P(X \geq \alpha) &\leq \epsilon \ P(X < \alpha) &\geq 1-\epsilon \end{align} (4)

对于较小的 \(\epsilon\) 值,我们可以将此陈述理解为:我们有信心 \(X\) 将低于 \(\alpha\)。因此,我们将使用以下术语:

  • \(\alpha\) 是我们的置信上界

  • \(\epsilon\) 是我们的失败概率,并且

  • \(1-\epsilon\) 是我们的置信水平

通常,我们可以将界限表示为 \(\alpha(\epsilon)\) 以明确显示其依赖性:我们指定某个期望的置信水平(或失败概率),并找到适用于该水平的界限。在实践中,我们希望这些界限尽可能小:我们总是可以选择 \(\infty\) 处的平凡界限,但这对我们没有用处。因此,我们希望 \(\alpha(\epsilon)\) 作为 \(\epsilon\) 的函数增长得相对缓慢。

假设我们选择 \(\epsilon = 0.05\),并且希望得到一个满足上述不等式的 \(\alpha(0.05)\) 值。如果 \(f\)\(F\) 已知,我们可以通过求解 \(\alpha\) 轻松计算出这个值(在此示例中,我们假设 \(X\) 是连续的,因此 \(P(X < \alpha) = P(X \leq \alpha)\)):

\(\begin{align*} P(X \leq \alpha) &\geq 1-\epsilon \\ F(\alpha) &\geq 1 - \epsilon \\ \alpha &\geq F^{-1}(1-\epsilon) \end{align*}\) ​(5)

换句话说,如果我们知道累积分布函数 \(F\)(以及其逆函数 \(F^{-1}\)),我们就可以计算出 \(\alpha(0.05) = F^{-1}(0.95)\)

如果我们不知道累积分布函数呢?如果我们对随机变量 \(X\) 一无所知,那么我们就束手无策了:我们无法做出任何适用于任意随机变量的关于 \(\alpha\) 的陈述。但是,我们将看到,如果我们做出一些假设,或者仅仅知道关于 \(X\) 的一些信息,那么我们就可以做出一些有趣的陈述。

示例:样本均值

集中不等式最常见的应用之一是样本均值。我们将回到独立同分布随机变量序列 \(X_1, \ldots, X_n\)​ 的例子,其均值为 \(\mu\),样本均值为 \(Y = (1/n)\sum X_i\)​。

马尔可夫不等式

我们首先假设 \(X\)非负的,并且我们知道其均值 \(E[X]\)。请注意,这些是相当弱的假设:如果我们知道 \(E[X] = 10\)\(X\) 是非负的,那么 \(X\) 的可能概率密度函数数量是巨大的!马尔可夫不等式对所有情况都成立,它告诉我们:

马尔可夫不等式:对于一个非负随机变量 \(X\) 和边界 \(\alpha > 0\),我们有

\(P(X \geq \alpha) \leq \frac{E[X]}{\alpha}.\) (6)

练习假设一台老丨虎丨机的赔付金额为 \(\$10\)。以下哪些可能是该机器恰好赔付 \(\$100\) 的概率值? \(2\%, 4\%, 5\%, 7\%\)

YouTubeVideo('DlY81PW4VEg')

加载中...

理解马尔可夫不等式的性质

让我们使用马尔可夫不等式来推导一个适用于任何非负随机变量的通用上置信边界。如果我们希望对于给定的 \(\epsilon\) 值(即期望的置信水平/失效概率),有 \(P(X \geq \alpha) \leq \epsilon\),那么我们有 \(\epsilon = E[X]/\alpha\),或者等价地 \(\alpha = E[X]/\epsilon\)。关于这个边界,我们可以得出一些重要的观察结果:

  • 随着 \(\epsilon\) 减小,\(\alpha\) 增大。请理解这其中的道理:如果我们希望边界 \(\alpha\) 失效的概率非常小(即随机变量取值超过该边界的概率非常小),那么我们就必须将边界设得非常大。

  • \(\epsilon\) 变小时,我们的界增长得有多快? 如上所述,一个好的界应该增长得相对缓慢。不幸的是,马尔可夫不等式提供了一个相对较差的界:该界以 \(O(\epsilon^{-1})\) 的速度增长,这对于大多数随机变量来说并不是一个非常紧的界。

示例:使用马尔可夫不等式的样本均值

给定 \(X_1, \ldots, X_n\)​ 及其样本均值 \(Y\),马尔可夫不等式在回答 Y 是否可能远离真实均值 \(\mu\) 这一问题上表现如何?

\(P(Y - \mu \geq \alpha) = P(Y \geq \mu + \alpha) \leq \frac{\mu}{\mu+\alpha}\) ​(7)

这是一个非常糟糕的界限!特别值得注意的是,它甚至没有随着样本数量\(n\) 的增加而改善。如果我们要求在给定置信水平\(\epsilon\)和给定样本数量\(n\) 下的置信上界\(\alpha(\epsilon, n)\),我们会发现这个界限也与\(n\) 无关:换句话说,无论我们收集多少样本,它都保持不变。这与我们对样本均值的直觉相悖:通常,我们期望收集的独立样本越多,结果就应该越好。这是我们在未来不等式分析中需要关注的一点。

练习

  1. 考虑一个参数为\(\lambda = 5\) 的泊松随机变量\(T\)。使用概率密度函数/累积分布函数,找到满足\(P(T \geq \alpha) \leq 0.05\) 的最小\(\alpha\)值。然后,使用马尔可夫不等式找出\(\alpha\)的一个上界。这说明了马尔可夫不等式在此例中的实用性如何?

  2. 现在,令 \(T_i \sim \mathrm{Poisson}(\lambda)\),其中 \(i = 1, 2, \ldots, n\),并令 \(S\)\(T_i\)​ 的样本均值。若 \(\lambda = 5\),请找出满足 \(P(S \geq \alpha) \leq 0.05\) 的最小值 \(\alpha\)提示:利用独立泊松随机变量之和仍服从泊松分布这一事实)。然后使用马尔可夫不等式找出 \(\alpha\) 的一个上界。与练习 1 的结果相比,差异如何?

提示:对于这两个练习,你可能会发现 scipy.stats.poisson.ppf 函数很有用。

正如我们将看到的,马尔可夫不等式给出的界如此宽松,部分原因在于它使用的信息非常有限。

视频示例:抛掷有偏硬币

YouTubeVideo('zq_JwBjlIbU')

加载中...

切比雪夫不等式与高阶矩

如果我们使用更多信息会怎样?具体来说,假设我们同时使用均值和方差。为此,考虑一个随机变量 \(X\),我们想为其限定尾概率,然后考虑随机变量 \(Z = (X-E[X])²\)。我们可以看到 \(Z\) 总是非负的,并且根据方差的定义,\(E[Z] = \mathrm{var}(X)\)。因此,如果对 \(Z\) 应用马尔可夫不等式,我们就得到了切比雪夫不等式:

切比雪夫不等式:考虑一个已知(有限)均值和方差的随机变量 \(X\)。对于任意 \(t > 0\)

\(\mathbb{P}(\vert X - E[X] \vert \ge t) \le \frac{\mathrm{var}(X)}{t²}\) ​(8)

证明:

\(\begin{align*} P(\vert X - E[X] \vert \geq t) &= P((X - E[X])² \geq t²) \\ &= P(Z \geq t²) \\ &\leq \frac{E[Z]}{t²} \\ &= \frac{\mathrm{var}(X)}{t²} \blacksquare \end{align*}\) (9)

理解切比雪夫不等式的性质

这表明,随机变量 X 不太可能远离其均值:距离 \(|X-E[X]|\) 较大(具体来说,大于 \(t\))的概率,与方差 \((X)\) 成正比地减小,与 \(t²\) 成反比地减小。

让我们使用切比雪夫不等式来推导一个通用的置信上界,该上界将适用于任何已知均值和方差的随机变量。如果我们希望对于给定的 \(\epsilon\) 值,满足 \(P(X \geq \alpha) \leq \epsilon\),我们需要先进行一些代数运算,然后才能应用切比雪夫不等式。

\(\begin{align*} P(X \geq \alpha) &= P(X - E[X] \geq \alpha - E[X]) \\ &\leq P(|X - E[X]| \geq \alpha - E[X]) \\ &\leq \frac{\mathrm{var}(X)}{(\alpha - E[X])²}, \end{align*}\) ​(10)

因此,我们有 \(\epsilon = \frac{\mathrm{var}(X)}{(\alpha - E[X])²}\)​。虽然我们可以使用二次公式来得到 \(\alpha(\epsilon)\) 的表达式,但这里最重要的观察是:

  • 界限 \(\alpha(\epsilon)\)\(X\) 的方差成正比。

  • \(\epsilon\) 变小时,我们的界限增长有多快? 界限 \(\alpha(\epsilon)\)α(ϵ) 与 \(\epsilon^{-1/2}\) 成正比(即,它以 \(O(\epsilon^{-1/2})\) 的速度增长),这比我们在马尔可夫不等式中看到的有了显著改进。问题依然存在:我们能做得更好吗?

示例:使用切比雪夫不等式的样本均值

给定 \(X_1, \ldots, X_n\)​ 及其样本均值 \(Y\),切比雪夫不等式在回答 Y 是否可能远离真实均值 μ 这一问题上表现如何?

\(P(|Y - \mu| > \alpha) \leq \frac{\mathrm{var}(Y)}{\alpha²} = \frac{\mathrm{var}(X_i)}{n\alpha²},\) (11)

这里我们利用 \(X_i\)​ 相互独立这一事实推导出 \(\mathrm{var}(Y) = \frac{1}{n²} \sum \mathrm{var}(X_i) = \mathrm{var}(X_i)/n\)

这显著优于马尔可夫不等式给出的界:它告诉我们,随着\(n\) 的增加,概率按\(1/n\) 的比例减小。如果我们想针对给定的置信水平\(\epsilon\)和给定的样本数量\(n\),求一个上置信界\(\alpha(\epsilon, n)\),情况会如何?

\(\begin{align*} \epsilon &= \frac{\mathrm{var}(X_i)}{n\alpha²} \\ \alpha &= \frac{\sigma_{X_i}}{\sqrt{n\epsilon}} \end{align*}\) ​​​​(12)

我们看到对\(\frac{1}{\sqrt{n}}\)​的依赖,这告诉我们随着样本数量的增加,我们的界以多快的速度缩小。

练习:再次考虑独立同分布的泊松随机变量 \(T_i\)​ 及其样本均值 \(S\)。使用切比雪夫不等式找出最小的 \(\alpha\) 值,使得 \(P(S \geq \alpha) \leq 0.05\)。你的结果与真实概率以及马尔可夫界相比如何?

视频示例:使用切比雪夫不等式抛掷有偏硬币

YouTubeVideo('WGdIZWKf9eQ')

加载中...

高阶矩界

切比雪夫不等式使用一阶矩和二阶矩(\(E[X]\)\(E[X²]\))。对于任何非负随机变量 \(X\) 和任何自然数 \(k\),我们可以对 \(X^k\) 应用马尔可夫不等式,从而得到高阶矩界

\(\mathbb{P}(X \geq \alpha) \leq \frac{E[X^k]}{\alpha^k}\) ​(13)

一些重要的观察:

  • 如果我们用此方法寻找如上所述的上置信界 \(\alpha(\epsilon)\),可以推导出该界以 \(O(\epsilon^{-1/k})\) 的速率增长,这对于较大的 \(k\) 值来说可能非常小。

  • 使用这类界需要关于随机变量 \(X\) 的额外信息:事实上,如果我们确切知道所有 \(k = 1, 2, \ldots\) 时的矩 \(E[X^k]\),那么我们很可能也就知道了其累积分布函数。

矩母函数与切尔诺夫方法

您可能会发现复习《Data 140》教材第 19.2 节会有所帮助。

回顾微积分知识,我们可以用泰勒级数将指数函数 \(e^x\) 表示为:

\(e^x = 1 + x + \frac{1}{2!} x² + \frac{1}{3!} x³ + \frac{1}{4!} x⁴ + \cdots,\) (14)

受此启发,并结合我们之前观察到的可以利用随机变量的高阶矩来建立更好的尾界,我们将研究随机变量 \(X\)矩母函数(或称 MGF):

\(M_X(\lambda) = E[e^{\lambda X}] = 1 + \lambda E[X] + \frac{\lambda²}{2!} E[X²] + \frac{\lambda³}{3!} E[X³] + \cdots\) (15)

关于矩母函数的一个非常重要的事实是:对于独立随机变量的和 \(Y = X_1 + \cdots + X_n\)​,其矩母函数 \(Y\) 等于每个 \(X_i\)​ 的矩母函数的乘积:

\(\begin{align*} M_Y(\lambda) &= E\left[e^{\lambda Y}\right] \\ &= E\left[e^{\sum_{i=1}^n \lambda X_i}\right] \\ &= E\left[\prod_{i=1}^n e^{\lambda X_i}\right] \\ &= \prod_{i=1}^n E\left[e^{\lambda X_i}\right] & \text{(by independence)} \\ &= \prod_{i=1}^n M_{X_i}(\lambda) \end{align*}\) ​(16)

Chernoff 界

由于 \(e^{\lambda X}\) 是一个非负随机变量,我们可以再次使用马尔可夫不等式来找到一个界:

\(P(X \geq \alpha) = P(E[e^{\lambda X}] \geq e^{\lambda \alpha}) ~ \leq ~ \frac{E[e^{\lambda X}]}{e^{\lambda \alpha}} ~ = ~ \frac{M_X(\lambda)}{e^{\lambda \alpha}}\) ​(17)

这是一族界限,每个正的 \(\lambda\) 对应一个。为了得到可能的最佳界限(即最小的),我们应该对所有非负的 \(\lambda\) 进行最小化:

$P(X \geq \alpha) ~ \leq ~ \min_{\lambda \ge 0} \frac{M_X(\lambda)}{e^{\lambda \alpha}} (18)

这就是切尔诺夫界

YouTubeVideo('YpzSdCD81qg')

加载中...

一些重要的观察:

  • 与之前我们看到的两个界限相比,这需要关于分布的更多信息。具体来说,如果我们知道一个分布的所有矩,在大多数情况下我们实际上就知道了这个分布。但是,我们很快会看到,如果我们能为一整类分布的矩母函数找到一个上界,它仍然是有用的:在这种情况下,我们可以用其上界来替换 \(M_X(\lambda)\)

  • \(\epsilon\) 变小时,我们的界增长得有多快? 求解上置信界 \(\alpha(\epsilon)\),我们看到 \(\epsilon = \frac{M_X(\lambda)}{e^{\lambda \alpha}}\)​。如果我们求解 \(\alpha\),会发现它随着 \(\log(\epsilon^{-1})\) 增长:这比我们之前的两个结果要慢得多!因此,如果我们能找到一类矩母函数已知(或有界,如之前的观察所示)的随机变量,那么我们就能对这些随机变量施加更紧的界。

示例:使用切尔诺夫界的样本均值

给定 \(X_1, \ldots, X_n\)​ 及其样本均值 \(Y\),切尔诺夫界在回答 Y 是否可能远离真实均值 \(\mu\) 的问题上表现如何?由于我们知道随机变量和的矩母函数,我们将对变量 \(nY = \sum X_i\)​ 进行界定:

\(P(Y - \mu \geq \alpha) = P(nY - n\mu \geq n\alpha) = P(nY \geq n(\mu + \alpha)) \leq \min_{\lambda > 0} \frac{\left[M_{X_i}(\lambda)\right]^n}{e^{\lambda n(\mu+\alpha)}}\) ​(19)

这甚至比切比雪夫界更好:随着\(n\) 的增加,概率按\(e^{-n}\) 的比例下降。

如果我们要求一个在给定置信水平 \(\epsilon\) 和给定样本数量 \(n\) 下的置信上限 \(\alpha(\epsilon, n)\) 呢?由于需要对 \(\lambda\)λ 进行隐式优化(具体来说,\(\lambda\) 的最优值将取决于 \(n\)),正确计算需要更大量的计算。但我们会看到,对置信水平 \(\epsilon\) 的依赖是 \(O(\log(\epsilon^{-1}))\),而对样本数量的依赖(与切比雪夫不等式一样)是 \(n^{-1/2}\)

练习:再次考虑独立同分布的泊松随机变量 \(T_i\)​ 及其样本均值 \(S\)。使用切尔诺夫边界求最小的 \(\alpha\) 值,使得 \(P(S \geq \alpha) \leq 0.05\)。你的结果与真实概率、马尔可夫边界以及切比雪夫边界相比如何?提示:你可以轻松查到泊松分布的矩母函数。

视频示例:使用切尔诺夫边界翻转有偏硬币

YouTubeVideo('YpzSdCD81qg')

加载中...

(可选)示例:比较已知高斯随机变量的界

给定阈值的概率界

现在,我们将已学的每个界应用于一个已知均值 \(\mu\) 和方差 \(\sigma²\) 的高斯随机变量,\(X \sim \mathcal{N}(\mu, \sigma²)\)。对于此示例,我们还可以计算精确的界并进行比较。

我们不能使用马尔可夫不等式,因为这个随机变量可以取负值。

因为 \(X - \mu\) 关于零对称,所以单侧的切比雪夫界为:

\(P(X - \mu \geq t) = \frac{1}{2}P(|X - \mu| \geq t) \leq \frac{\sigma²}{2t²}\) ​(20)

接下来,让我们应用切尔诺夫方法。令 \(Z = X - \mu \sim \mathcal{N}(0, \sigma²)\)。那么 \(Z\) 的矩母函数为 \(M_Z(\lambda) = e^{\sigma² \lambda²/2}\)。因此对于 \(\lambda \ge 0\)

\(\mathbb{P}(X - \mu > t) ~ \le ~ \min_{\lambda \geq 0} \exp \left( \frac{\sigma² \lambda²}{2} - \lambda t \right)\) (21)

我们如何对 \(\lambda\)λ 进行优化?认识到由于 \(\exp(x)\) 是一个递增函数,我们有

\(\min_{\lambda \geq 0} \exp \left( \frac{\sigma² \lambda²}{2} - \lambda t \right) = \exp \left(\min_{\lambda \geq 0} \left\{\frac{\sigma² \lambda²}{2} - \lambda t \right\} \right).\) (22)

指数中的量是一个二次函数 \(f(\lambda)\),可以使用二次公式求其最小值,得到 \(\lambda^* = \frac{t}{\sigma²}\)​,以及 \(f(\lambda^*) = \frac{-t²}{2\sigma²}\)​。

综合以上,我们得到

\(P(X-\mu > t) ~\leq ~\exp\left(-\frac{t²}{2\sigma²}\right)\) (23)

让我们将这两个边界与精确概率(我们可以使用累积分布函数计算)一起可视化:

sigma = 2

t_min = 3
t_max = 7
t = np.linspace(t_min, t_max , 500)

chernoff = np.exp(-0.5*((t/sigma)**2))
chebyshev = 0.5 * ((sigma/t)**2)
exact_probability = 1 - stats.norm.cdf(t/sigma)

fig, axes = plt.subplots(1,2, figsize = (8,3))
for i, ax in enumerate(axes):
    ax.plot(t, exact_probability, label='Exact', lw=2)
    ax.plot(t, chernoff, lw=2, label='Chernoff')
    ax.plot(t, chebyshev, lw=2, label='Chebyshev')
    ax.set_xlim(t_min, t_max)
    ax.set_xlabel('$t$')
    ax.set_ylabel('$t$')
    ax.legend()
    if i == 1:
        ax.semilogy()
        ax.set_ylabel('Probability (log scale)')
    else:
        ax.set_ylabel('Probability')
    ax.set_title(r'Bounds on $P(X - \mu \geq t)$ when $X \sim \mathcal{N}(\mu, 2²)$')
plt.tight_layout()

<尺寸为 800x300 且包含 2 个坐标轴的图形>

为给定概率设定阈值边界

假设我们不是给定一个阈值并要求计算超出该阈值的概率,而是想要相反的情况:我们想知道,给定一个期望的概率,一个边界或阈值,使得超出该边界的概率很小。这就是我们之前描述的上置信界。为了简化计算且不失一般性,我们假设 \(\mu = 0\)。如果我们为正态分布计算这个值,会发现:

  • 对于切比雪夫不等式,我们有 \(\epsilon = \frac{\sigma²}{2\alpha²}\)​,或者说 \(\alpha(\epsilon) = \frac{\sigma}{\sqrt{2\epsilon}}\)​。

  • 对于切尔诺夫界,我们有 \(\epsilon = \exp\left(-\frac{\alpha²}{2\sigma²}\right)\),或者等价地 \(\alpha(\epsilon) = \sigma\sqrt{2\log(1/\epsilon)}\)​。

  • 正如我们之前所见,精确概率是 \(F^{-1}(1-\epsilon)\)

sigma = 2

eps_min = 0
eps_max = 1
eps = np.linspace(eps_min, eps_max, 100)
eps = np.hstack([np.logspace(np.log10(eps[1]), -3, 10)[::-1], eps[1:]])

chernoff = sigma * np.sqrt(-2*np.log(eps))
chebyshev = sigma / np.sqrt(2*eps)
exact_probability = stats.norm.ppf(1-eps)

fig, axes = plt.subplots(1,2, figsize = (8,3))
for i, ax in enumerate(axes):
    ax.plot(eps, exact_probability, label='Exact', lw=2)
    ax.plot(eps, chernoff, lw=2, label='Chernoff')
    ax.plot(eps, chebyshev, lw=2, label='Chebyshev')
    ax.set_ylabel(r'Upper conf. bound $\alpha(\epsilon)$')
    ax.legend()
    if i == 1:
        ax.semilogx()
        ax.set_xlabel(r'$\epsilon$ (log scale)')
    else:
        ax.set_xlabel(r'$\epsilon$')
    ax.set_title(r'$P(X \geq \alpha) \leq \epsilon$ when $X \sim \mathcal{N}(0, 2²)$')
plt.tight_layout()

<Figure size 800x300 with 2 Axes>

两个边界显然都是宽松的(即与真实值相比过大),但对于较大的 \(t\) 值(或者等价地,对于较小的 \(\epsilon\) 值),切尔诺夫界要紧密得多。

对于正态随机变量,以这种方式计算切尔诺夫界和切比雪夫界有点多余,因为我们已经知道真实的概率。尽管如此,这有助于我们理解这些界的优劣程度,并且我们将在下一节中看到,即使在我们不完全知道矩母函数的情况下,我们也可以应用这些相同的界。

Hoeffding 不等式

到目前为止,我们已经看到利用随机变量的矩母函数可以得到极佳的尾概率界。但是,如果我们确切地知道矩母函数,那么我们也知道该随机变量的分布,在这种情况下,我们不妨直接使用累积分布函数(CDF)来求尾概率界。正如我们在前面的例子中看到的,即使是切尔诺夫界也不如使用实际的累积分布函数精确。

那么,切尔诺夫界(Chernoff bound)的意义何在?在许多情况下,我们可能不知道矩母函数的确切形式,但我们可以界定它:换句话说,我们可以找到关于 \(\lambda\) 的某个函数 \(h(\lambda)\),使得 \(M_X(\lambda) \leq \lambda ~ ~ \forall \lambda\)

其中一个例子是有界随机变量:即随机变量的取值仅在下界 \(a\) 和上界 \(b\) 之间。

Hoeffding 引理

Hoeffding 引理给出了任意有界随机变量的矩母函数(MGF)的上界。具体来说,给定一个随机变量 \(X\),其均值为 \(E[X]\),且取值在 \(a\)\(b\) 之间,我们有

\(M_X(\lambda) = E[e^{\lambda X}] \leq \exp\left\{\lambda E[X] + \frac{\lambda²(b-a)²}{8}\right\}\) (24)

关于该引理的证明和更多信息,请参阅霍夫丁引理维基百科文章

YouTubeVideo('40RQ_O0VK7M')

加载中...

霍夫丁不等式

我们可以对任何有界随机变量应用霍夫丁引理,并结合切尔诺夫界来限定尾概率。在本节剩余部分,我们将重点关注样本均值的特定情况,这正是霍夫丁不等式的核心内容。

\(X_1 ,X_2, \ldots, X_n\)​ 为定义在区间 \(a\)\(b\) 之间、相互独立(但不必同分布)的随机变量。则霍夫丁不等式表明:

\(P\left(\frac{1}{n}\sum_{i=1}^n (X_i - \mathbb{E}[X_i]) \geq t\right) \leq \exp\left(-\frac{2nt²}{(b-a)²}\right) \\ P\left(\frac{1}{n}\sum_{i=1}^n (X_i - \mathbb{E}[X_i]) \leq -t\right) \leq \exp\left(-\frac{2nt²}{(b-a)²}\right).\) (25)

我们也有双侧版本:

\(P\left(\left|\frac{1}{n}\sum_{i=1}^n (X_i - \mathbb{E}[X_i])\right| \geq t\right) \leq 2\exp\left(-\frac{2nt²}{(b-a)²}\right).\) (26)

\(X_i\)​ 同分布且均值为 \(\mu\) 的情况下,我们可以将第一个版本重写为:

\(\mathbb{P}\left(\left[\frac{1}{n}\sum_{i=1}^n X_i\right] - \mu \geq t\right) \leq \exp\left(-\frac{2nt²}{(b-a)²}\right) \\\) (27)

这是一个非常显著的结果!回想一下,中心极限定理告诉我们,类似的结果在渐近意义下成立:也就是说,对于较大的 \(n\) 值,样本均值会收敛于正态分布,因此,获得远离均值的值的概率会以 \(e^{-t²}\) 的速度减小。但这个结论对 \(n\)任何值都成立:无论 \(n\) 是大是小,或者 \(X_i\)​ 的分布是否“良好”,这对于任何有界随机变量的样本均值都是成立的。

我们也可以在不了解随机变量 \(X_i\)​ 分布的任何其他信息的情况下知道这一点,只需知道它们的一些界即可。

正如之前一样,我们可以得出一些观察结果:

  • \(\epsilon\) 变小时,我们的界增长得有多快? 求解上置信界 \(\alpha(\epsilon)\),我们看到 \(\epsilon = \frac{M_X(\lambda)}{e^{\lambda \alpha}}\)​。求解 \(\alpha\)(示例见下文),我们可以看到它随 \(\log(\epsilon^{-1})\) 增长:这比我们之前的两个结果要慢得多!因此,如果我们能找到一类矩母函数已知(或有界,如之前的观察)的随机变量,那么我们就可以对这些随机变量施加更紧的界。

请注意下方视频中的一个小错误:最后一个方程应为 \(P(Y \leq -t)\),而非 \(P(Y \leq t)\)

YouTubeVideo('f1tbEnldSt0')

加载中...

示例:使用霍夫丁不等式的样本均值

给定 \(X_1, \ldots, X_n\)​ 及其样本均值 \(Y\),霍夫丁不等式在回答 Y 是否可能远离真实均值 μ 这一问题上表现如何?回答这个问题只需我们陈述该不等式并分析其结果:

\(\mathbb{P}\left(Y - \mu \geq \alpha\right) \leq \exp\left(-\frac{2n\alpha²}{(b-a)²}\right) \\\) (28)

正如预期(因为霍夫丁不等式是利用切尔诺夫界推导出来的),我们看到了对 \(n\) 的相同依赖性:概率按 \(e^{-n}\) 的比例减小。如前所述,我们也看到了对 \(\alpha\) 的依赖性,其形式为 \(e^{-\alpha²}\),这与正态随机变量或中心极限定理中的情况一致。

如果我们要求在给定置信水平 \(\epsilon\) 下的置信上界 \(\alpha(\epsilon)\) 会怎样?

\(\begin{align*} \epsilon &= \exp\left(-\frac{2n\alpha²}{(b-a)²}\right) \\ (b-a)² \log(1/\epsilon) &= 2n\alpha² \\ \alpha &= |b-a|\sqrt{\frac{\log(1/\epsilon)}{2n}} \end{align*}\) ​​​(29)

我们可以看到,虽然这个界对 \(\epsilon\) 的依赖关系比我们在切比雪夫界中看到的要好得多,但它对 \(n\) 的依赖关系是相似的:这个界也按 \(n^{-1/2}\) 的比例减小

Hoeffding 不等式证明

不失一般性,如有必要,我们可以将 \(X_i\)​ 替换为 \(X_i - \mathbb{E}[X_i]\),因此我们可以假设对于每个 \(i\) 都有 \(\mathbb{E}[X_i] = 0\),以简化我们的计算。令 \(Z = \sum_{i=1}^n \frac{X_i}{n}\)​​,并注意到每一项都介于 \(a/n\)\(b/n\) 之间。那么

\(\begin{align*} M_Z(\lambda) &= \prod_{i=1}^n M_{X_i}(\lambda) \\ & \leq \prod_{i=1}^n \exp \left( \frac{(b-a)²}{8n²}\lambda² \right) \\ & = \exp \left( \frac{(b-a)²}{8n}\lambda² \right) \end{align*}\) ​(30)

应用切尔诺夫方法:

\(\begin{align*} P(Z > t) ~ & \le ~ \min_{\lambda \geq 0} \exp \left( \frac{(b-a)² \lambda²}{8n} - \lambda t \right) \\ & = \exp\left(-\frac{2nt²}{(b-a)²}\right). \end{align*}\) (31)

双侧界限的证明留作练习(和/或可以轻松查阅)。

YouTubeVideo('OgdPPGNrRdM')

正在加载...

集中不等式与置信区间

我们已经多次看到,如果我们有一个估计量 \(\hat{\theta}\),我们可以利用 \(\hat{\theta}\) 的分布来为该估计量构建置信区间。到目前为止,我们依赖的方法要么是自助法,要么是精确计算分布,要么是使用中心极限定理来近似它。

然而,我们刚刚讨论的所有方法都适用于任何随机变量!因此,我们可以使用其中任何一种方法来为估计量 \(\hat{\theta}\) 构建置信区间,特别是当该估计量是样本均值时(但即使不是也常常适用)。虽然其他方法完全有效,但在某些情况下,我们可能更倾向于使用集中不等式来构建置信区间:

  • 为何选择集中不等式而非精确计算分布? 正如我们已经讨论过的,在许多情况下,我们可能无法确切知道随机变量的分布,并且精确计算分布可能并不可行。

  • 为何选择集中不等式而非自助法? 使用自助法需要我们观察数据,并利用这些数据生成自助重采样。在某些情况下,如果我们希望在观察任何数据之前展示一个理论上的置信区间,使用集中不等式会很有帮助。

  • 为何选择集中不等式而非中心极限定理? 正如我们已经讨论过的,中心极限定理仅适用于较大的\(n\) 值,而我们在此推导出的结果适用于任何\(n\) 值,以及任何分布形态。

YouTubeVideo('uiwbn8DbCKk')

加载中...

posted @ 2026-02-05 08:56  绝不原创的飞龙  阅读(2)  评论(0)    收藏  举报