概率性深度学*-全-
概率性深度学*(全)
原文:Probabilistic Deep Learning
译者:飞龙
前言
前言
感谢您购买我们的书籍。我们希望这本书能为您揭示深度学*(DL)的内部机制,并为您提供一些关于如何在工作中使用概率深度学*方法灵感的启示。
我们三位作者在统计学方面都有背景。我们于 2014 年一起开始了深度学*的旅程。我们对它如此着迷,以至于深度学*至今仍然是我们职业生涯的中心。深度学*有着广泛的应用范围,但我们特别着迷于将深度学*模型与统计学中使用的概率方法相结合的力量。根据我们的经验,对概率深度学*潜力的深入理解需要深入了解底层方法和实践经验。因此,我们在本书中试图在这两种成分之间找到一个良好的平衡。
在这本书中,我们旨在在讨论涉及的方法之前,提供一些清晰的想法和示例应用。你们也有机会通过使用随书附带的 Jupyter 笔记本,实际应用所有讨论的方法。我们希望你们通过阅读这本书学到的东西,能和我们写作时学到的一样多。祝你们玩得开心,保持好奇心!
致谢
我们想要感谢所有帮助我们撰写这本书的人。特别感谢我们的开发编辑玛琳娜·迈克尔斯,她设法教会了一群瑞士人和德国人如何写出几百字以下的句子。没有她,你们就没有乐趣去解读文本。还要感谢我们的校对员弗朗西斯·伯兰,她在文本(以及公式中)发现了无数的错误和不一致(也要感谢你!)。我们在技术方面也得到了 Al Krinkler 和 Hefin Rhys 的大力支持,使笔记本中的文本和代码更加一致,更容易理解。还要感谢我们的项目编辑迪尔德丽·希姆;我们的校对员凯里·黑尔斯;以及我们的审稿编辑亚历山大·德拉戈萨夫利奇。我们还要感谢审稿人,他们在书的各个阶段提供了非常有价值的反馈:Bartek Krzyszycha、Brynjar Smári Bjarnason、David Jacobs、Diego Casella、Francisco José Lacueva Pérez、Gary Bake、Guillaume Alleon、Howard Bandy、Jon Machtynger、Kim Falk Jorgensen、Kumar Kandasami、Raphael Yan、Richard Vaughan、Richard Ward 和 Zalán Somogyváry。
最后,我们还想感谢理查德·谢泼德,他为本书提供了许多优秀的图形和插图,使书籍不那么枯燥,更加友好。
我,奥利弗,想感谢我的合作伙伴莱娜·奥本代克,在我长时间工作于这本书时,她的耐心。我还感谢来自“Tatort”观看俱乐部的朋友们,每周日晚上 8:15 提供食物和陪伴,并在写作这本书时帮助我避免发疯。
我,Beate,想要感谢我的朋友们,不仅仅是因为他们帮助我写这本书,而是因为他们与我分享了屏幕之外的快乐时光——首先是我的伴侣 Michael,还有臭名昭著的 Limmat BBQ 小组,以及苏黎世之外的朋友们和家人,尽管有 Rösti-Graben,即通往大州的国界,甚至还有中间的大湖,他们仍然与我共度休闲时光。
我,Elvis,想要感谢在撰写这本书的激动人心时刻支持我的人,不仅在专业上,而且在私下里,无论是品一杯美酒还是踢一场足球。
我们,Tensor Chiefs,很高兴我们一起走到了这本书的结尾。我们期待新的科学旅程,但也期待不那么紧张的时间,那时我们不仅为了工作,也为了乐趣而相聚。
关于这本书
在这本书中,我们希望将支撑深度学*(DL)的概率原理带给更广泛的读者。最终(几乎),深度学*(DL)中的所有神经网络(NNs)都是概率模型。
有两个强大的概率原理:最大似然和贝叶斯。最大似然(亲切地称为 MaxLike)支配着所有传统深度学*(DL)。将网络视为使用最大似然原理训练的概率模型,可以帮助你提升网络的性能(就像谷歌从 WaveNet 过渡到 WaveNet++时所做的那样)或生成惊人的应用(例如,OpenAI 使用 Glow,一个生成逼真面孔的网络)。在需要网络表示“我不确定”的情况下,贝叶斯方法就派上用场了。(奇怪的是,传统的神经网络无法做到这一点。)本书的副标题“使用 Python、Keras 和 TensorFlow Probability”反映了这样一个事实,即你真的应该亲自动手编写一些代码。
应该阅读这本书的人
这本书是为那些喜欢理解深度学*(DL)底层概率原理的人所写。理想情况下,你应该有一些深度学*(DL)或机器学*(ML)的经验,并且不应该对数学和 Python 代码感到过于恐惧。我们没有省略数学,并且在代码中总是包含了示例。我们相信数学与代码更相得益彰。
这本书的组织结构:路线图
本书分为三部分,共涵盖八章。第一部分解释了传统的深度学*(DL)架构以及神经网络(NNs)的技术训练过程。
-
第一章--设定场景,并介绍概率深度学*(DL)。
-
第二章--讨论网络架构。我们涵盖了全连接神经网络(fcNNs),这是一种全能型网络,以及卷积神经网络(CNNs),它们非常适合图像处理。
-
第三章--展示了神经网络如何拟合数百万个参数。我们尽量简化,展示了在可以想到的最简单网络上的梯度下降和反向传播--线性回归。
第二部分专注于将神经网络作为概率模型使用。与第三部分相比,我们讨论了最大似然方法。这些方法背后是所有传统深度学*(DL)。
-
第四章——探讨了最大似然(MaxLike),这是机器学*和深度学*的基本原理。我们首先将这个原理应用于分类和(简单的回归问题)。
-
第五章——介绍了 TensorFlow Probability (TFP),这是一个用于构建深度概率模型的框架。我们用它来解决像计数数据这样的不太简单的回归问题。
-
第六章——从更复杂的回归模型开始。最后,我们解释了如何使用概率模型来掌握描述人类面部图像等复杂分布。
第三部分介绍了贝叶斯神经网络。贝叶斯神经网络允许你处理不确定性。
-
第七章——阐述了贝叶斯深度学*的必要性,并解释了其原理。我们再次通过简单的线性回归示例来解释贝叶斯原理。
-
第八章——展示了如何构建贝叶斯神经网络。在这里,我们涵盖了两种称为 MC(蒙特卡洛)dropout 和变分推理的方法。
如果你已经有深度学*的经验,你可以跳过第一部分。此外,第六章的第二部分(从 6.3 节开始)描述了正态流。你不需要了解这些来理解第三部分的内容。6.3.5 节在数学上有点复杂,所以如果你不感兴趣,可以跳过。8.2.1 和 8.2.2 节也是如此。
关于代码
本书包含许多源代码示例,无论是编号列表还是与普通文本混排。在两种情况下,源代码都以 fixed-width font,如 this 的格式呈现,以将其与普通文本区分开来。
代码示例是从 Jupyter 笔记本中提取的。这些笔记本包含额外的解释,并且大多数都包括一些你应该做的练*,以更好地理解本书中介绍的概念。你可以在 GitHub 上的这个目录中找到所有代码:github.com/tensorchiefs/dl_book/。一个不错的起点是在这个目录 tensorchiefs.github.io/dl_book/,在那里你可以找到笔记本的链接。笔记本是按照章节编号的。例如,nb_ch08_02 是第八章的第二本笔记本。
本书中的所有示例,除了 nb_06_05,都是使用 TensorFlow v2.1 和 TensorFlow Probability (TFP) v0.8 测试的。描述计算图的笔记本 nb_ch03_03 和 nb_ch03_04 在 TensorFlow v1 中更容易理解。对于这些笔记本,我们还包含了 TensorFlow 的两个版本。nb_06_05 笔记本只适用于 TensorFlow v1,因为我们需要在该版本的 TensorFlow 中提供的权重。
你可以在 Google 的 Colab 或本地执行这些笔记本。Colab 非常棒;你只需点击一个链接,然后在云端玩转代码。无需安装——你只需要一个浏览器。我们强烈建议你这样去做。
TensorFlow 仍在快速发展,我们无法保证代码在几年后仍能运行。因此,我们提供了一个 Docker 容器(github.com/oduerr/dl_book_docker/),您可以使用它来执行所有 notebooks,除了 nb_06_05 以及 nb_ch03_03 和 nb_ch03_04 的 TensorFlow 1.0 版本。如果您想在本地使用 notebooks,这是必走之路。
liveBook 讨论论坛
购买《概率深度学*》包括免费访问由 Manning Publications 运营的私人网络论坛,您可以在论坛上对书籍发表评论,提出技术问题,并从作者和其他用户那里获得帮助。要访问论坛,请访问livebook.manning.com/book/probabilistic-deep-learning-with-python/welcome/v-6/。您还可以在livebook.manning.com/#!/discussion上了解更多关于 Manning 论坛和行为准则的信息。
曼宁对读者的承诺是提供一个平台,在这里读者之间以及读者与作者之间可以进行有意义的对话。这并不是对作者参与特定数量活动的承诺,作者对论坛的贡献仍然是自愿的(并且未付费)。我们建议您尝试向作者提出一些挑战性的问题,以免他们的兴趣转移!只要书籍在印刷中,论坛和先前讨论的存档将可通过出版社的网站访问。
关于作者
Oliver Dürr 是德国康斯坦茨应用科学大学的教授,教授数据科学。Beate Sick 在 ZHAW 担任应用统计学教授,并在苏黎世大学担任研究员和讲师,同时在苏黎世联邦理工学院(ETH Zurich)担任讲师。Elvis Murina 是一位研究科学家,负责本书伴随的广泛练*。
Dürr 和 Sick 都是机器学*和统计学的专家。他们监督了关于深度学*的众多学士、硕士和博士论文,并计划并实施了多个研究生和硕士学位的深度学*课程。所有三位作者自 2013 年以来一直在使用深度学*方法,并在教授该主题以及开发概率深度学*模型方面拥有丰富的经验。
关于封面插图
《概率深度学*》封面上的插图被标注为“丹萨·德·伊勒·奥塔希提”,或塔希提岛的舞者。这幅插图取自雅克·Grasset de Saint-Sauveur(1757-1810)的作品集,名为《不同国家的服饰》,1788 年在法国出版。每一幅插图都是手工精心绘制和着色的。Grasset de Saint-Sauveur 收藏中的丰富多样性生动地提醒我们,200 年前世界的城镇和地区在文化上是如何截然不同的。彼此孤立,人们说着不同的方言和语言。在街道或乡村,仅凭他们的服饰,就可以轻易地识别出他们居住的地方以及他们的职业或社会地位。
自那以后,我们的着装方式已经改变,而当时区域间的多样性,如此丰富,现在已经逐渐消失。现在很难区分不同大陆的居民,更不用说不同的城镇、地区或国家了。也许我们是以更丰富多彩的个人生活——当然,是更丰富多彩、节奏更快的技术生活——为代价,换取了文化多样性。
在难以区分一本计算机书与另一本计算机书的时代,曼宁通过基于两百年前丰富多样的区域生活所设计的书封面,庆祝了计算机行业的创新精神和主动性,这些封面由 Grasset de Saint-Sauveur 的画作赋予新生。
第一部分. 深度学*基础
本书的第一部分为你提供了一个对概率深度学*(DL)的初步高级理解,以及你可以用它来处理哪些类型的任务。你将了解用于回归(你可以用它来预测一个数字)的不同神经网络架构,以及用于分类(你可以用它来预测一个类别)的架构。你将获得设置 DL 模型的实际经验,学*如何调整这些模型,以及如何控制训练过程。如果你在深度学*方面还没有丰富的经验,你应该在继续学*第二部分的概率深度学*模型之前,完整地学*第一部分。
1 概率深度学*简介
本章涵盖
-
什么是概率模型?
-
深度学*是什么以及何时使用它?
-
比较传统机器学*和深度学*方法在图像分类中的应用
-
曲线拟合和神经网络的基本原理
-
比较非概率模型和概率模型
-
概率深度学*是什么以及为什么它有用

深度学*(DL)是当今数据科学和人工智能领域最热门的话题之一。深度学*自从 2012 年随着 GPU 的广泛应用而变得可行以来,但你可能已经在日常生活的各个领域处理深度学*技术了。当你与数字助手进行语音交流时,当你使用免费的 DeepL 翻译服务(DeepL 是一家基于深度学*生产翻译引擎的公司)将一种语言翻译成另一种语言,或者当你使用像 Google 这样的搜索引擎时,深度学*正在幕后施展其魔法。许多最先进的深度学*应用,如文本到语音翻译,通过使用概率深度学*模型来提升其性能。此外,自动驾驶汽车等安全关键应用使用基于贝叶斯理论的概率深度学*变体。
在本章中,你将获得对深度学*及其概率变体的初步高级介绍。我们使用简单的例子来讨论非概率模型和概率模型之间的区别,并突出概率深度学*模型的一些优点。我们还给你一个关于与概率深度学*的贝叶斯变体一起工作时你将获得的第一印象。在本书的剩余章节中,你将学*如何实现深度学*模型以及如何调整它们以获得更强大的概率变体。你还将了解使你能够构建自己的模型和理解高级现代模型的基本原理,以便你可以根据自身目的进行适配。
1.1 概率模型初探
让我们先了解一下概率模型可能的样子以及如何使用它。我们用一个日常生活中的例子来讨论非概率模型和概率模型之间的区别。然后我们用同一个例子来突出概率模型的一些优点。
在我们的车里,我们大多数人使用的是卫星导航系统(简称 satnav,即 GPS),它告诉我们如何从 A 地到 B 地。对于每条建议的路线,satnav 还会预测所需的旅行时间。这种预测的旅行时间可以理解为一种最佳猜测。你知道当你从 A 地到 B 地走相同的路线时,有时需要更多的时间,有时需要更少的时间。但标准的 satnav 是非概率的:它只预测旅行时间的一个值,不会告诉你可能的值范围。例如,看看图 1.1 左侧的面板,你看到两条从纽约的 Croxton 到现代艺术博物馆(MoMA,也在纽约)的路线,预测的旅行时间是 satnav 基于以前的数据和当前道路状况的最佳猜测。
让我们想象一个更高级的卫星导航系统,它使用概率模型。它不仅为你提供一个最佳猜测的旅行时间,还捕捉了该旅行时间的不确定性。对于给定路线的旅行时间概率预测以分布的形式提供。例如,看看图 1.1 的右侧面板。你看到两个高斯钟形曲线描述了两条路线的预测旅行时间分布。
了解这些预测旅行时间的分布能给你带来什么好处?想象你是一名纽约出租车司机。在 Croxton,一位艺术经销商上了你的出租车。她想要参加一场 25 分钟后开始的盛大艺术品拍卖会,如果她准时到达,会给你丰厚的小费(500 美元)。这可是相当大的激励!
你的 satnav 工具提出了两条路线(见图 1.1 的左侧面板)。作为一个第一反应,你可能倾向于选择上面的路线,因为对于这条路线,它估计的旅行时间是 19 分钟,比另一条路线的 22 分钟短。但幸运的是,你总是拥有最新的设备,你的 satnav 使用的是概率模型,不仅输出平均旅行时间,还输出整个旅行时间分布。更好的是,你知道如何利用输出的旅行时间分布。

图 1.1 satnav 的旅行时间预测。在地图的左侧,你看到的是一个确定性版本——只报告一个数字。在右侧,你看到两条路线的旅行时间概率分布。
你意识到,在你当前的情况下,平均旅行时间并不是很有趣。真正对你来说重要的是以下问题:哪条路线你更有可能得到 500 美元的小费?为了回答这个问题,你可以查看图 1.1 右侧的分布。经过快速目测分析,你得出结论,即使平均旅行时间更长,选择下方的路线你得到小费的机会更大。原因是下方路线的分布较窄,其中对应于 25 分钟以下旅行时间的分布比例更大。为了用硬性数据支持你的评估,你可以使用带有概率模型的导航工具来计算两种分布到达 MoMA 少于 25 分钟的概率。这个概率对应于图 1.1 中虚线左侧曲线下面积的比例,这表明 25 分钟是一个关键值。让工具从分布中计算概率,你知道选择下方的路线时得到小费的机会是 93%,而选择上方的路线时只有 69%。
正如在这个出租车司机例子中讨论的那样,概率模型的主要优势是它们可以捕捉大多数实际应用中的不确定性,并为决策提供必要的信息。概率模型的其他应用例子包括自动驾驶汽车或数字医学概率模型。你还可以使用概率深度学*(DL)生成与观察数据相似的新数据。一个著名的有趣应用是创建看起来真实不存在的人的面孔。我们将在第六章中讨论这一点。在深入了解曲线拟合部分之前,让我们先从宏观的角度看看深度学*(DL)。
1.2 深度学*(DL)的初步了解
深度学*(DL)究竟是什么呢?当被要求给出一个简短的电梯式介绍时,我们会说它是一种基于人工神经网络(NN)的机器学*(ML)技术,并且它松散地受到人脑工作方式的启发。在我们给出自己对深度学*(DL)的定义之前,我们首先想给你一个关于人工神经网络(NN)外观的初步概念(见图 1.2)。

图 1.2 一个包含三个隐藏层的人工神经网络(NN)模型示例。输入层包含与我们描述输入所需数量相等的神经元。
在图 1.2 中,你可以看到一个典型的传统人工神经网络(NN),它包含三个隐藏层以及每层中的几个神经元。同一层的每个神经元都与下一层的每个神经元相连。
人工神经网络受到大脑的启发,大脑由多达数十亿个神经元组成,处理例如视觉或听觉等所有感官感知。大脑中的神经元并不与每个其他神经元相连,信号通过神经元分层网络进行处理。你可以在图 1.2 中看到类似分层网络结构的人工神经网络。虽然生物神经元在处理信息方面相当复杂,但人工神经网络中的神经元是其生物对应物的简化和抽象。
要对人工神经网络有一个初步的了解,你最好将神经元想象成一个数字的容器。输入层中的神经元相应地持有输入数据的数字。例如,这些输入数据可以是客户的年龄(以年为单位)、收入(以美元为单位)和身高(以英寸为单位)。后续层中的所有神经元都接收来自前一层的连接神经元的加权值之和作为它们的输入。一般来说,不同的连接并不同等重要,但具有权重,这些权重决定了输入神经元值对下一层神经元值的影响。(这里我们省略了输入在神经元内部进一步转换的情况。)深度学*模型是神经网络,但它们也有大量的隐藏层(不仅仅是图 1.2 中的例子中的三个)。
人工神经网络中的权重(神经元之间连接的强度)需要学*以完成当前任务。为此学*步骤,你使用训练数据并调整权重以最佳地拟合数据。这一步骤被称为拟合。只有完成拟合步骤后,你才能使用模型对新数据进行预测。
设置深度学*系统总是一个两阶段的过程。在第一步中,你选择一个架构。在图 1.2 中,我们选择了一个包含三个层的网络,其中每一层的每个神经元都与下一层的每个神经元相连。其他类型的网络有不同的连接方式,但原理保持不变。在下一步中,你调整模型的权重,以便最好地描述训练数据。这一调整步骤通常使用称为梯度下降的程序来完成。你将在第三章中了解更多关于梯度下降的内容。
注意,这个两步程序对深度学*来说并不特别,它也存在于标准的统计建模和机器学*中。拟合的潜在原理对于深度学*、机器学*和统计学是相同的。我们坚信,你可以通过使用在过去几个世纪中在统计学领域获得的知识而受益良多。这本书承认了传统统计学的遗产,并在此基础上构建。因此,你可以通过观察像线性回归这样简单的东西来理解深度学*的许多内容,我们将在本章介绍线性回归,并在整本书中将其作为易于理解的例子使用。你将在第四章中看到,线性回归已经是一个概率模型,它为每个样本提供的信息不仅仅是一个预测输出值。在第四章中,你将学*如何选择一个合适的分布来模拟结果值的变异性。在第五章中,我们将向你展示如何使用 TensorFlow Probability 框架来拟合这样的概率深度学*模型。然后你可以将这种方法转移到新的情境中,允许你设计和拟合适当的概率深度学*模型,这些模型不仅提供高性能的预测,而且还能捕捉数据的噪声。
1.2.1 一个成功的故事
深度学*已经革命性地改变了那些迄今为止用传统机器学*方法难以掌握,但人类却容易解决的领域,例如识别图像中的对象(计算机视觉)和处理书面文本(自然语言处理),或者更普遍地说,任何类型的感知任务。图像分类远非仅仅是学术问题,它被用于各种应用:
-
人脸识别
-
在 MRI 数据中对脑肿瘤进行诊断
-
为自动驾驶汽车识别路标
尽管深度学*在不同应用领域都展现出了其潜力,但可能最容易理解的是在计算机视觉领域。因此,我们使用计算机视觉通过其最大的成功故事之一来激发深度学*。
2012 年,深度学*在 Alex Krizhevsky(来自杰弗里·辛顿的实验室)使用基于深度学*的模型在国际知名的 ImageNet 竞赛中击败所有竞争对手时引起了轰动。在这个竞赛中,来自领先计算机视觉实验室的团队在包含约 100 万张图像的大数据集上训练他们的模型,目的是教会这些模型区分 1000 种不同的图像内容类别。这些类别的例子包括船只、蘑菇和豹子。在竞赛中,所有训练好的模型都必须列出针对一组新测试图像的五种最可能的类别。如果正确的类别不在提出的类别中,则测试图像被视为错误(见图 1.3,它展示了基于深度学*的方法是如何横扫图像分类的)。

图 1.3 深度学*在 ImageNet 竞赛中的令人印象深刻的结果
在深度学*进入竞赛之前,最好的程序的错误率约为 25%。2012 年,Krizhevsky 首次使用深度学*,将错误率大幅降低(降低了 10%,降至约 15%)。仅仅一年后,2013 年,几乎所有竞争者都开始使用深度学*,到 2015 年,基于深度学*的不同模型达到了人类水平,大约为 5%。你可能想知道为什么人类在 20 张图片中会错误分类 1 张(5%)。一个有趣的事实:数据集中有 170 种不同的狗品种,这使得人类正确分类图像变得有些困难。
1.3 分类
让我们来看看非概率性、概率性和贝叶斯概率性分类之间的区别。深度学*因其优于传统方法而闻名,尤其是在图像分类任务中。在深入细节之前,我们想通过一个人脸识别问题来给你一个深度学*方法和更传统的人脸识别方法之间的差异和共性的感觉。作为旁注,人脸识别实际上是让我们最初接触深度学*的应用。
作为统计学家,我们与一些计算机科学同事合作,在树莓派微型计算机上进行了人脸识别项目。计算机科学家通过嘲笑我们使用的统计方法的时代来挑战我们。我们接受了挑战,并提出了深度学*来解决我们的人脸识别问题,这让他们感到惊讶。这个第一个项目的成功触发了许多其他联合深度学*项目,我们的兴趣也随之增长,开始深入研究这些模型的基本原理。
让我们来看一个具体任务。Sara 和 Chantal 在假期中一起旅行并拍了好多照片,每张照片至少有她们中的一个。任务是创建一个程序,可以查看照片并确定照片中的两位女士中哪一位在照片中。为了获得训练数据集,我们标记了 900 张照片,每位女士 450 张,并附上了照片中女士的名字。你可以想象,从第一眼看上去,图像可能会有很大的不同,因为女士们可能从不同的角度被拍摄,可能是笑着或疲惫的,可能是盛装打扮或休闲的,或者可能是在一个糟糕的发型日。尽管如此,对你来说,这个任务相当简单。但对于计算机来说,图像只是一个像素值的数组,编程它来区分两位女士远非易事。
1.3.1 图像分类的传统方法
图像分类的传统方法并不是直接从图像的像素值开始,而是通过两步过程来处理分类任务。首先,该领域的专家定义出对图像分类有用的特征。这样一个特征的简单例子就是所有像素的平均强度值,这可以用来区分夜间拍摄的照片和白天拍摄的照片。通常这些特征更为复杂,并且针对特定任务进行定制。在人脸识别问题中,你可以考虑一些容易理解的特征,比如鼻子的长度、嘴巴的宽度,或者眼睛之间的距离(图 1.4)。

图 1.4 Chantal(左)的眼睛间距较大,嘴巴相对较小。Sara(右)的眼睛间距较小,嘴巴相对较大。
但这些高级特征往往很难确定,因为需要考虑许多方面,例如表情、尺度、接收角度或光照条件。因此,非深度学*(non-DL)方法通常使用更不可解释的低级特征,如 SIFT 特征(尺度不变特征变换),捕捉局部图像属性,如放大或旋转,这些属性对变换是不变的。例如,你可以考虑边缘检测器:如果图像被旋转或缩放,边缘不会消失。
就这个简单的例子来说,特征工程,即定义和从图像中提取对分类重要的属性,是一项复杂且耗时的任务。这通常需要高水平的专业知识。在许多计算机视觉应用(如人脸识别)中的(缓慢)进步主要是由构建新的和更好的特征所驱动的。
注意:在处理实际的分类任务之前,你需要从所有图像中提取所有这些特征。
在特征提取步骤之后,这些特征值代表每个图像。为了从图像的特征表示中识别 Sara 或 Chantal,你需要选择并拟合一个分类器。
这样的分类模型的任务是什么?它应该能够区分不同的类别标签。为了可视化这个想法,让我们想象一个图像只由两个特征来描述:比如说,眼睛的距离和嘴巴的宽度。(我们意识到在大多数实际情况下,对图像的良好描述需要许多更多的特征。)
由于女性不总是正面呈现,而是从不同的视角呈现,因此对于同一女性,眼睛之间的明显距离并不总是相同的。嘴巴的明显宽度可能会变化更大,这取决于女性是否在笑或做出飞吻。当用这两个特征来表示被描绘的女性的每一张图像时,特征空间可以通过二维图来可视化。一个轴表示眼睛距离,另一个轴显示嘴巴宽度(见图 1.5)。每个图像都表示为一个点;Sara 的图像用 S 标记,Chantal 的图像用 C 标记。

图 1.5 由特征嘴巴宽度和眼睛距离构成的二维空间。每个点代表由这两个特征描述的图像(S 代表 Sara,C 代表 Chantal)。虚线是分隔两个类别的决策边界。
你可以这样考虑一个非概率分类模型:模型定义了决策边界(见图 1.5 中的虚线),将特征空间分割成不同的区域。每个区域对应一个类别标签。在我们的例子中,我们确定了 Sara 区域和 Chantal 区域。现在你可以使用这个决策边界来对新图像进行分类,这些新图像你只知道两个特征的值:如果二维特征空间中的对应点最终落在 Sara 区域,你将其分类为 Sara;否则,分类为 Chantal。
你可能从你的数据分析经验中知道一些如下所示的方法,你可以用它们进行分类。(如果你不熟悉这些方法,请不要担心。)
-
逻辑回归或多项式回归
-
随机森林
-
支持向量机
-
线性判别分析
大多数分类模型,包括列出的方法和深度学*(DL),都是参数模型,这意味着模型有一些参数决定了边界的走向。模型只有在用某些数字替换这些参数之后,才能准备进行实际的分类或类别概率预测。拟合就是关于如何找到这些数字以及如何量化这些数字的确定性。
将模型拟合到一组具有已知类别标签的训练数据中,确定了参数的值并固定了特征空间中的决策边界。根据分类方法和参数的数量,这些决策边界可以是简单的直线或带有波动的复杂边界。你可以将设置分类方法的传统工作流程总结为三个步骤:
-
定义并从原始数据中提取特征
-
选择参数模型
-
通过调整参数来拟合分类模型到数据
为了评估模型的性能,你使用一个在训练过程中未使用的验证数据集。在人脸识别的例子中,验证数据集将包括 Chantal 和 Sara 的新图像,这些图像不是训练数据集的一部分。然后你可以使用训练好的模型来预测类别标签,并使用正确分类的百分比作为(非概率性)性能指标。
根据具体情况,一种或另一种分类方法将在验证数据集上取得更好的结果。然而,在经典图像分类中,成功最重要的因素不是分类算法的选择,而是提取的图像特征的质量。如果提取的特征对不同类别的图像具有不同的值,你将在特征空间中看到相应点的清晰分离。在这种情况下,许多分类模型都表现出很高的分类性能。
以区分 Sara 和 Chantal 为例,你经历了传统的图像分类工作流程。为了获得好的特征,你首先必须认识到这两位女士的嘴巴宽度和眼睛距离不同。有了这些特定的特征,你看到构建一个好的分类器很容易。然而,要区分其他两位女士,这些特征可能不起作用,你需要重新开始特征开发过程。这是使用定制特征时的一个常见缺点。
1.3.2 图像分类的深度学*方法
与传统的图像分类方法相比,深度学*(DL)方法直接从原始图像数据开始,并且只使用像素值作为模型输入特征。在这种图像特征表示中,像素的数量定义了特征空间的维度。对于一个 100 × 100 像素的低分辨率图片,这已经相当于 10,000 个像素。
除了高维度之外,主要挑战是两张图片的像素相似性并不一定意味着这两张图片对应于相同的类别标签。图 1.6 说明了同一列中的图像显然对应于同一类,但在像素级别上是不同的。同时,图 1.6 中同一行的图像显示出高像素相似性,但并不对应于同一类。

图 1.6 左列显示了同一类狗的两个图像。右列显示了同一类桌子(table)的两个图像。在像素级别比较图片时,同一列中的两个图像比同一行中的两个图像更不相似,即使一行中的一个图像显示的是狗,而另一个图像显示的是桌子。
深度学*(DL)的核心思想是通过将适当特征的构建纳入拟合过程来替代具有挑战性和耗时特征工程任务。此外,深度学*(DL)不能做任何魔法,因此,类似于传统的图像分析,特征必须从手头的像素值构建。这是通过深度学*(DL)模型的隐藏层完成的。
每个神经元将其输入组合以产生新的值,并且以这种方式,每一层都产生输入的新特征表示。使用许多隐藏层允许神经网络(NN)将原始数据到结果之间的复杂转换分解为一系列简单的转换。当从一层到另一层时,你会得到越来越抽象的图像表示,这更适合区分不同的类别。你将在第二章中了解更多关于这一点,你将看到在深度学*(DL)模型的拟合过程中,会学*到一系列越来越复杂的特征。这然后允许你区分不同的类别,而无需手动指定适当的特征。
深度学*(Deep learning)的定位(Branding DL)
在机器学*(ML)的早期,神经网络(NNs)已经存在,但技术上无法训练具有许多层的深度神经网络,这主要是因为缺乏计算能力和训练数据。随着技术障碍的解决,已经发现了一些技巧,使得训练具有数百层的神经网络成为可能。
为什么我们谈论深度学*(DL)而不是人工神经网络(NNs)?深度学*(DL)比人工神经网络(NNs)更受欢迎。这听起来可能有些不尊重,但这样的重新定位可能是一次明智的举动,特别是由于神经网络在过去的几十年中没有实现承诺,因此获得了一些不良声誉。我们使用具有许多隐藏层的“深度”神经网络。这导致在特征构建中有一个深层次的结构,使得随着层次结构的每一步上升,特征变得更加抽象。
在定义架构之后,网络可以被理解为一个包含数百万参数的参数模型。该模型接受输入 x 并产生输出 y。这对于每个深度学*(DL)模型(包括强化学*)都是正确的。深度学*(DL)建模工作流程可以总结为两个步骤:
-
定义深度学*(DL)模型架构
-
将深度学*(DL)模型拟合到原始数据
下一节将讨论非概率和概率分类模型的意义,以及你可以从概率分类模型的贝叶斯变体中获得哪些好处。
1.3.3 非概率分类
让我们先看看非概率分类。为了使其简单易懂,我们再次使用图像分类的例子。图像分类的目标是预测给定图像对应的类别。在 1.2 节中提到的 ImageNet 竞赛中,有 1,000 个不同的类别。在人脸识别的例子中,只有两个类别:Chantal 和 Sara。
在非概率图像分类中,你只能得到每个图像的预测类别标签。更准确地说,一个非概率图像分类器接收一个图像作为输入,然后只预测类别标签作为输出。在人脸识别的例子中,它可能会输出 Chantal 或 Sara。你也可以将非概率模型视为一个没有不确定性的确定性模型。用概率的视角来看非概率模型,它似乎总是确定的。非概率模型以 100%的概率预测图像属于一个特定的类别。
想象一种情况,图像显示 Chantal 染发,颜色与 Sara 相同,头发覆盖了 Chantal 的脸。对于人类来说,很难判断图像显示的是 Chantal 还是 Sara。但非概率分类器仍然提供了一个预测的类别标签(例如,Sara),并没有表明任何不确定性。或者想象一个更加极端的情况,你提供了一张既不是 Chantal 也不是 Sara 的图像(见图 1.7)。分类器会给出哪种预测?你希望分类器告诉你它无法做出可靠的预测。但非概率分类器仍然给出 Chantal 或 Sara 作为预测,而没有给出任何不确定性的提示。为了应对处理困难或新颖情况这样的挑战,我们转向概率模型及其贝叶斯变体。这些模型可以表达它们的不确定性,并指出可能不可靠的预测。

图 1.7 人脸识别的非概率图像分类器接收一个图像作为输入,并输出一个类别标签。这里预测的类别标签是 Chantal,但只有上面的图像真正显示了 Chantal。下面的图像显示的是一个既不是 Chantal 也不是 Sara 的女士。
1.3.4 概率分类
在概率分类中,特别之处在于你不仅得到对类别标签的最佳猜测,还能得到分类的不确定性度量。这种不确定性通过概率分布来表示。在人脸识别的例子中,概率分类器会接收一张人脸图像,然后输出对 Chantal 和 Sara 的概率。这两个概率加起来等于 1(见图 1.8)。

图 1.8 一个用于人脸识别的概率图像分类器以图像为输入,并输出每个类别标签的概率。在上部面板中,图像显示的是 Chantal,分类器预测 Chantal 类别的概率为 0.85,Sara 类别的概率为 0.15。在下部面板中,图像显示的不是 Chantal 也不是 Sara,分类器预测 Chantal 类别的概率为 0.8,Sara 类别的概率为 0.2。
为了给出最佳的单个猜测,你会选择概率最高的类别。通常认为预测类别的概率是预测的不确定性。当所有图像都足够类似于训练数据时,这种情况是成立的。
但在现实中,情况并不总是如此。想象一下,你向分类器提供了一张既不是 Chantal 也不是 Sara 的图像。分类器除了为 Chantal 或 Sara 的类别分配概率外别无选择。但你希望分类器通过为两个可能的但错误的类别分配更多或更少的相等概率来显示其不确定性。不幸的是,当使用概率 NN 模型工作时,这通常不是情况。相反,通常还会为其中一个可能的但错误的类别分配相当高的概率(参见图 1.8)。为了解决这个问题,在我们的书籍第三部分,我们通过采用贝叶斯方法扩展概率模型,这可以添加额外的不确定性,你可以用它来检测新类别。
1.3.5 贝叶斯概率分类
贝叶斯模型的好处是,这些模型可以表达其预测的不确定性。在我们的面部识别示例中,非贝叶斯概率模型预测了一个结果分布,包括 Chantal 的概率和 Sara 的概率,总和为 1。但模型对分配的概率有多确定?贝叶斯模型可以回答这个问题。在本书的第三部分,你将详细了解这是如何完成的。在此阶段,我们只需注意,你可以多次询问贝叶斯模型,并得到不同的答案。这反映了模型内在的不确定性(参见图 1.9)。如果你不明白如何为相同的输入得到这些不同的模型输出,请不要担心。你将在本书的第三部分学到这一点。
贝叶斯模型的优点在于,这些模型可以通过不同预测集的大范围分布来指示不可靠的预测(参见图 1.9 的下部面板)。这样,你就有更好的机会识别出如图 1.9 下部面板中的年轻女士这样的新类别,她既不是 Chantal 也不是 Sara。

图 1.9 一个用于人脸识别的贝叶斯概率图像分类器以图像为输入,并输出两个类别标签的概率分布集。在上面的面板中,图像显示的是 Chantal,预测的概率集都预测了 Chantal 的高概率和相应地 Sara 的低概率。在下方的面板中,图像显示的是既不是 Chantal 也不是 Sara 的女士,因此分类器预测了不同的概率集,表明高度的不确定性。
1.4 曲线拟合
我们想在介绍章节的结尾讨论概率和非概率深度学*回归任务中的差异。回归有时也被称为曲线拟合。这让人想起了以下内容:
深度学*所有的令人印象深刻的成就都归结为仅仅是曲线拟合。
--Judea Pearl, 2018
当我们听说 Judea Pearl,2011 年获得声望极高的图灵奖(计算机科学的诺贝尔奖)的获得者,声称深度学*仅仅是曲线拟合(与简单的分析如线性回归几个世纪以来所做的相同的曲线拟合),起初我们感到惊讶,甚至有点冒犯。他怎么能对我们的研究主题如此不尊重,毕竟,它在实践中展示了如此令人印象深刻的成果?我们相对的平静可能是因为我们不是计算机科学家,而是有物理学和统计数据分析的背景。曲线拟合对我们来说不仅仅是曲线拟合。然而,对他的声明进行第二次思考,我们可以看到他的观点:深度学*和曲线拟合在许多方面具有相同的基本原理。
1.4.1 非概率曲线拟合
让我们先仔细看看传统曲线拟合方法的非概率方面。粗略地说,非概率曲线拟合是穿过数据点的科学。以最简单的线性回归形式,你将一条直线穿过数据点(见图 1.10)。在那张图中,我们假设只有一个特征,x,来预测一个连续变量,y。在这个简单的情况下,线性回归模型只有两个参数,a 和b:
y = a ⋅ x + b

图 1.10 血压(SBP)示例的散点图和回归模型。点代表测量数据点;直线是线性模型。对于三个年龄值(22,47,71),水平线的位置表示对 SBP 的最佳猜测预测值(11,139,166)。
在定义模型之后,需要确定参数 a 和b,以便模型能够实际用于预测给定x时y的单个最佳猜测值。在机器学*和深度学*的背景下,这一步寻找良好参数值的过程被称为训练。但网络是如何训练的呢?简单线性回归和深度学*模型的训练是通过将模型的参数拟合到训练数据来完成的——也就是曲线拟合。
注意,参数的数量可能会有很大的差异,从一维线性回归案例中的 2 个到高级深度学*模型中的 5 亿个不等。整个过程与线性回归相同。你将在第三章中学*如何拟合非概率线性回归模型的参数。
那么,当我们说非概率模型拟合到数据时,我们指的是什么?让我们通过一个具体的例子来看一下模型y = a ⋅ x + b,这个例子是根据年龄x预测血压y。图 1.10 是 33 位美国女性的收缩压(SBP)与年龄的图表。图 1.10 显示了a = 1.70 和b = 87.7(实线)的具体实现。在非概率模型中,对于每个年龄值,你只能得到这个年龄女性收缩压的一个最佳猜测值。在图 1.10 中,这通过三个年龄值(22 岁、47 岁和 71 岁)来展示,其中预测的最佳猜测值(111、139 和 166)由虚线水平线的位置表示。
1.4.2 概率曲线拟合
当你将概率模型拟合到相同的数据时,你会得到什么?你不会只得到一个关于血压的最佳猜测值,而会得到一个完整的概率分布。这告诉你,相同年龄的女性可能会有不同的收缩压(见图 1.11)。在非概率线性回归中,预测 22 岁女性的收缩压为 111(见图 1.10)。现在,当查看 22 岁女性的预测分布时,接* 111(分布的峰值)的收缩压值比远离 111 的值更有可能。

图 1.11 展示了收缩压(SBP)示例的散点图和回归模型。点代表测量数据点。在每个年龄值(22 岁、47 岁、71 岁)处,拟合高斯分布来描述这些年龄组女性可能的收缩压值的概率分布。对于这三个年龄值,显示了预测的概率分布。实线表示对应于 16 至 90 岁之间所有分布的平均值的位置。上下的虚线表示模型预期 95%的所有值所在的区间。
图 1.10 中的实线表示 16 至 90 岁之间所有分布对应平均值的位臵。图 1.11 中的实线与图 1.10 中的回归线完全吻合,该回归线是由非概率模型预测得出的。与平均值平行的虚线表示一个区间,模型预计在这个区间内 95%的个体收缩压值(SBP)都会出现。
你如何找到非概率模型和概率模型中参数的最佳值?技术上,你使用一个损失函数来描述模型与(训练)数据拟合得有多差,然后通过调整模型的权重来最小化它。你将在第三章、第四章和第五章中了解损失函数以及如何使用这些函数来拟合非概率或概率模型。你将看到非概率模型和概率模型损失函数之间的区别。
讨论的线性回归模型当然是简单的。我们主要用它来解释在转向复杂深度学*(DL)模型时保持不变的基本原理。在实际应用中,你通常不会假设数据之间存在线性依赖关系,你也不总是想假设数据的变化保持恒定。在第二章中,你将看到设置一个能够模拟非线性关系的神经网络是多么容易。在第四章和第五章中,你将看到构建一个能够模拟具有非线性行为和变化变化的回归任务的概率模型也不是很难(见图 1.12)。为了评估训练好的回归模型的表现,你应该始终使用一个在训练过程中未使用的验证数据集。在图 1.12 中,你可以看到对一个新的验证集的预测,这表明模型能够捕捉到数据中的非线性行为以及数据变化的变化。

图 1.12 展示了来自(非贝叶斯)概率回归模型的散点图和验证数据预测。该模型是在一些具有x和y之间非线性依赖关系以及非恒定数据变化的模拟数据上拟合的。实线表示所有预测分布的平均值位臵。上、下虚线表示模型预计 95%的所有值都将出现的区间。
如果我们使用模型来预测训练数据范围之外的 x 值的输出结果会怎样呢?您可以在查看图 1.12 时获得初步的了解,其中我们只有 -5 到 25 之间的数据,但展示了 -10 到 30 更宽范围内的预测结果。看起来模型对其从未见过数据的范围内的预测特别自信。这很奇怪,并不是模型所期望的特性!模型不足的原因是它只捕捉了数据变化——它没有捕捉到拟合参数的不确定性。在统计学中,有几种已知的方法可以捕捉这种不确定性;贝叶斯方法是其中之一。当与深度学*模型一起工作时,贝叶斯方法是可行且合适的。您将在本书的最后两章中了解到这一点。
1.4.3 贝叶斯概率曲线拟合
贝叶斯深度学*模型的主要卖点是其能够在模型未训练的新颖情况下发出警报。对于一个回归模型来说,这对应于外推,意味着您在训练数据范围之外的数据范围内使用模型。在图 1.13 中,您可以看到一个贝叶斯变种的神经网络,它产生了图 1.12 中所示的拟合结果。值得注意的是,只有神经网络的贝叶斯变体在离开训练数据范围时提高了不确定性。这是一个很好的特性,因为它可以表明您的模型可能产生不可靠的预测。

图 1.13 显示了贝叶斯概率回归模型的散点图和验证数据预测。该模型是在一些具有 x 和 y 之间非线性依赖性和非恒定数据变化的模拟数据上拟合的。实线表示所有预测分布的均值位置。上、下虚线表示模型预期 95% 的所有值所在的区间。
1.5 何时使用和何时不使用深度学*?
最*,深度学*已经取得了几个非凡的成功故事。因此,您可能会问自己是否应该忘记传统的机器学*方法,转而使用深度学*。答案取决于具体情况和任务。在本节中,我们将讨论何时不应使用深度学*以及深度学*有哪些用途。
1.5.1 不宜使用深度学*的情形
深度学*通常有数百万个参数,因此通常需要大量数据来训练。如果您只能访问有限数量的特征来描述每个实例,那么深度学*并不是一个合适的选择。这包括以下应用:
-
根据学生在高中时的成绩预测他们在大学第一年的成绩
-
根据一个人的性别、年龄、BMI(体重指数)、血压和血液胆固醇浓度预测下一年内发生心脏病发作的风险
-
根据乌龟的体重、身高和脚的长度来分类乌龟的性别
此外,在只有少量训练数据且你知道哪些特征决定了感兴趣的结果(并且你很容易从原始数据中提取这些特征)的情况下,你应该选择这些特征,并以此为基础构建一个传统的机器学*模型。例如,假设你从不同法国和荷兰足球运动员的收藏中获取图像。你知道法国队的球衣总是蓝色,而荷兰队的球衣总是橙色。如果你的任务是开发一个区分这两个队伍的球员的分类器,那么最好决定图像中蓝色像素(法国队)的数量是否大于橙色像素(荷兰队)的数量。所有其他似乎可以区分两个队伍的特征(例如,例如,发色)都会增加噪声而不是帮助对新图像的分类。因此,提取和使用额外的特征作为你的分类器可能不是一个好主意。
1.5.2 何时使用深度学*
深度学*是当每个实例都由复杂原始数据(如图像、文本或声音D)描述,且难以确定表征不同类别的关键特征时的情况下的首选方法。深度学*模型能够从原始数据中提取特征,这些特征通常优于依赖于手工特征的模型。图 1.14 展示了深度学**期改变游戏规则的各种任务。

图 1.14 机器学*长期无法触及,而深度学**期成功解决的各项任务
1.5.3 何时使用概率模型,何时不使用?
在本书中,你会发现对于大多数深度学*模型,可以设置模型的概率版本。你基本上可以免费获得概率版本。在这些情况下,你只能通过使用概率变体来获得收益,因为它不仅提供了从模型的非概率版本中获得的信息,而且还提供了对决策至关重要的附加信息。如果你使用概率模型的贝叶斯变体,你还有额外的优势,即获得一个包括模型参数不确定性的度量。拥有不确定性度量对于识别模型可能产生不可靠预测的情况尤为重要。
1.6 本书你将学到什么
本书为您提供了概率深度学*的实战入门。我们将提供练*和代码演示作为 Jupyter 笔记本,让您获得经验,从而更深入地理解概念。为了从本书中受益,您应该已经知道如何运行简单的 Python 程序,以及如何将模型拟合到数据上(如线性回归这样的简单模型即可)。为了深入理解更高级的部分(标题结尾后有星号标记),您应该熟练掌握中级数学,如矩阵代数和微分微积分,以及中级统计学,如解释概率分布。您将学*如何
-
使用 Keras 框架实现具有不同架构的深度学*模型
-
实现一个概率深度学*模型,从给定的输入预测整个结果的分布
-
对于给定的任务,使用最大似然原理和 TensorFlow Probability 框架选择合适的输出分布和损失函数
-
设置灵活的概率深度学*模型,例如目前用于图像生成和文本到语音翻译的当前最先进模型
-
构建能够表达不确定性的贝叶斯深度学*模型变体,让您能够识别不可靠的预测
我们将在下一章介绍不同的深度学*架构。
摘要
-
机器学*(ML)方法是为了使计算机能够从数据中学*而发明的。
-
人工神经网络(NNs)是机器学*方法,它们从原始数据开始,并将特征提取过程作为模型的一部分。
-
深度学*(DL)方法是一种称为深度神经网络(NNs),因为它们具有大量的层。
-
在感知任务中,如抓取图像内容或文本到语音翻译,深度学*优于传统的机器学*方法。
-
曲线拟合是一种技术,它将模型(曲线或分布)拟合到数据上。
-
深度学*(DL)方法和曲线拟合相似,并依赖于相同的原则。这些原则是本书的核心。理解这些原则可以让您在准确性、校准以及量化预测的不确定性度量方面构建性能更好的深度学*模型。
-
概率模型超越了单值预测,并捕捉真实数据的变异性以及模型拟合的不确定性,这有助于做出更好的决策。
-
概率模型的贝叶斯变体可以帮助识别不可靠的预测。
2 神经网络架构
本章涵盖
-
需要根据不同的数据类型使用不同的网络类型
-
使用完全连接神经网络处理类似表格的数据
-
使用二维卷积神经网络处理类似图像的数据
-
使用一维卷积神经网络处理有序数据

大多数深度学*模型都是基于一种或多种类型的层:完全连接、卷积和循环。深度学*模型的成功在很大程度上取决于为特定问题选择正确的架构。
如果你想要分析没有结构的数据,例如 Excel 表格中的表格数据,那么你应该考虑使用完全连接网络。如果数据具有特殊的局部结构,如图像,那么卷积神经网络(NNs)是你的朋友。最后,如果数据是序列性的,如文本,那么最简单的选择是使用一维卷积网络。本章为你概述了在深度学*中使用的不同架构,并提供了一些关于何时使用哪种架构类型的提示。
2.1 完全连接神经网络(fcNNs)
在深入探讨不同深度学*架构的细节之前,让我们看看图 2.1。回想一下我们在第一章中讨论的典型传统人工神经网络的架构。可视化的神经网络有三个隐藏层,每个层包含九个神经元。层内的每个神经元都与下一层的每个神经元相连。这就是为什么这种架构被称为密集连接神经网络或完全连接神经网络(fcNN)。


2.1.1 激发人工神经网络设计的生物学
神经网络的设计灵感来源于大脑的工作方式。你不应该过分强调这一点;这只是一个大致的灵感。大脑是一个由神经元组成的网络。人脑大约有 1000 亿个神经元,平均每个神经元连接大约 10000 个其他神经元。让我们看看大脑的基本单元——神经元(见图 2.2)。


图 2.2 展示了一个非常简化的神经元草图。它通过其树突接收来自其他神经元的信号。一些输入具有激活作用,而一些输入具有抑制作用。接收到的信号在神经元的细胞体中积累并处理。如果信号足够强,神经元就会放电。这意味着它产生一个信号,该信号被传输到轴突末端。每个轴突末端都连接到另一个神经元。一些连接可能比其他连接更强,这使得将信号转换到下一个神经元更容易。经验和学*可以改变这些连接的强度。计算机科学家从生物大脑细胞中推导出一个数学抽象:图 2.3 中所示的人工神经元。

图 2.3 大脑细胞(人工神经元)的数学抽象。值 z 是通过输入值 p,x[1] 到 xp 的加权和以及一个偏置项 b(该偏置项的输入为 1)来计算的,该偏置项将输入的加权总和向上或向下移动。值 y 通过应用激活函数从 z 计算得出。
人工神经元接收一些数值输入值,x**[i],这些值与一些相应的数值权重 wi 相乘。为了累积输入,确定输入的加权总和加上一个偏置项 b(该偏置项的输入为 1)作为 z = x[1] ∙ w[1] +x[1] ∙ w[1] + ⋯ + x**[p] ⋅ w**[p] + 1 ⋅ b。请注意,这个公式与线性回归中使用的公式相同。然后你可以通过一个非线性激活函数,即所谓的 S 型函数,进一步转换得到的 z 值,该函数将 z 转换为一个介于 0 和 1 之间的数(见图 2.4)。该函数由以下公式给出:

如图 2.4 所示,z 的较大正值导致接* 1 的值,而具有较大绝对值的负值导致接* 0 的值。在这种情况下,结果值 y 可以解释为神经元放电的概率。或者,在分类的背景下,为某一类别的概率。如果你想要构建一个二元分类器(具有 0 和 1 作为可能的类别),它接受几个数值特征 x**[i],并生成类别 1 的概率,那么你可以使用单个神经元。如果你有统计学背景,这可能会让你感到熟悉,实际上,在统计学中,具有单个神经元的网络也被称为逻辑回归。但如果你从未听说过逻辑回归,无需担心。

图 2.4 S 型函数 f 将任意数 z 转换为介于 0 和 1 之间的数。
2.1.2 开始实现神经网络
要开始使用深度学*,你需要了解基本的数据结构,张量,以及操作这些实体的软件包。
张量:深度学*中的基本实体
观察图 2.3 中神经元的数学抽象,你可能会问,“输入什么,输出什么?”假设图 2.3 中的 p=3,那么你会看到三个数字(x1、x2 和 x3)进入神经元,一个数字离开神经元。这三个数字可以被视为一个只有一个索引的数组。更复杂的神经网络可以接受一个灰度图像作为输入,例如大小为 64×32 的图像,这也可以表示为一个数组。但这次数组有两个索引。第一个索引 i 的范围是从 0 到 63,第二个索引 j 的范围是从 0 到 31。
进一步来说,假设你有一个包含红色、绿色和蓝色三种颜色的彩色图像。对于这样的图像,每个像素都有x,y 坐标和三个额外的值。图像可以存储在一个有三个索引(i, j, c)的数组中。将其推向极端,假设你将一整堆 128 个彩色图像输入到网络中。这些可以存储在一个(b, x, y , c)的数组中,其中b的范围是从 0 到 127。此外,你还可以将图 2.3 中的三个权重视为一个只有一个索引的数组,从 0 到 2。
事实上,深度学*中的所有量都可以放入数组中。在深度学*的背景下,这些数组被称为张量,从抽象的角度来看,深度学*中所发生的一切都是对张量的操作。张量拥有的索引数被称为维度、阶数,有时也称为秩(所以不要混淆 D)。阶数为 0 的张量,如图 2.3 中神经元的输出,没有索引。低阶张量也有特殊的名称:
-
阶数为 0 的张量被称为标量。
-
一阶张量被称为向量。
-
二阶张量被称为矩阵。
张量的形状定义了每个索引可以有多少个值。例如,如果你有一个 64×32 像素的灰度图像,张量的形状是(64,32)。这就是你在使用深度学*时需要了解的所有关于张量的知识。但请注意,当你谷歌搜索张量时,你可能会找到一些令人恐惧的东西,比如通过其变换属性给出的数学定义。别担心。在深度学*的背景下,张量只是一个具有特殊结构的数据容器,例如,例如向量或矩阵。如果你对向量和矩阵感到不安全,那么查看 François Chollet 的《Python 深度学*》第 2 版(Manning,2017)的第二章是值得的,可以在mng.bz/EdPo找到深入的解释。
软件工具
随着用于操作张量的软件框架的可用性,深度学*获得了巨大的普及。在这本书中,我们主要使用 Keras(keras.io/)和 TensorFlow(www.tensorflow.org/)。目前,这两个框架是最常被深度学*从业者使用的。TensorFlow 是由 Google 开发的开源框架,它为深度学*提供了强大的支持。Keras 是一个用户友好的、高级神经网络 API,用 Python 编写,可以在 TensorFlow 之上运行,允许快速原型设计。
为了完成这本书中的练*,我们建议您使用 Google Colab 环境(colab.research.google.com)作为在浏览器中运行的云解决方案。深度学*最重要的框架、包和工具已经安装,您可以立即开始编码。如果您想在您的计算机上安装深度学*框架,我们建议您遵循 Chollet 在其书籍第三章中给出的描述,请参阅 mng.bz/NKPN .
-
要深入了解 TensorFlow,Martin Görner 的教程是一个很好的起点:
www.youtube.com/watch?v=vq2nnJ4g6N0. -
要了解更多关于 Keras 的信息,我们推荐访问网站
keras.io/.
我们使用 Jupyter 笔记本(jupyter.org/)为您提供一些动手练*和代码示例。Jupyter 笔记本允许将 Python、TensorFlow 和 Keras 代码与文本和 Markdown 混合。笔记本组织在包含文本或代码的单元格中。这使您可以通过仅更改一个单元格中的代码来玩转代码。在许多练*中,我们提供了大量代码,您可以在单独的单元格中使用自己的代码进行实验。您也可以随意更改代码的任何位置;您不会破坏任何东西。虽然深度学*通常涉及大量数据集并需要巨大的计算能力,但我们提炼了简单的示例,以便您可以交互式地使用笔记本。我们使用以下图标来指示书中您应该打开 Jupyter 笔记本并执行相关代码的位置:
![]()
您可以直接在 Google Colab 中打开这些笔记本,在那里您可以在浏览器中编辑和运行它们。Colab 很棒,但您需要在线才能使用它。另一个选项(适合离线工作)是使用提供的 Docker 容器。有关如何安装 Docker 的详细信息,请参阅 tensorchiefs.github.io/dl_book/ . 在 Jupyter 笔记本中,我们使用以下图标来指示您应该返回这本书的位置:
![]()
设置第一个神经网络模型以识别假钞
让我们开始,进行第一个深度学*实验。在这个实验中,你使用一个单一的人工神经元来区分真钞和假钞。
动手时间 打开 mng.bz/lGd6 ,您将找到一个数据集,该数据集通过两个特征和一个类别标签 y 描述了 1,372 张钞票。 |
|---|
两个图像特征基于小波分析,这是传统图像分析中常用的一种方法。通常将输入值和目标值存储在两个不同的张量中。输入数据集包含 1,372 个实例,由两个特征描述,你可以将这些特征组织在一个 2D 张量中。第一个维度通常描述样本。这个轴被称为轴 0。对于这个例子,你有一个形状为(1372,2)的 2D 张量。目标值是真实的类别标签,可以存储在一个形状为(1372)的第二个 1D 张量中。
深度学*模型通常在图形卡上运行,也称为图形处理单元(GPU)。这些 GPU 具有有限的内存。因此,你不能一次性处理整个数据集。数据被分成更小的批次,这些批次只包含整个数据集的一个子集。这些批次被称为小批量,一个典型的小批量包含的实例数量是 32、64 或 128。在我们的纸币示例中,我们使用形状为 128 的小批量。
由于纸币仅由两个特征描述,你可以在 2D 特征空间中轻松地看到真实和假纸币的位置(见图 2.5)。此外,两个类别的边界不是由一条直线分开的。

图 2.5:真实和假纸币的(训练)数据点
让我们使用一个具有 sigmoid 激活函数(也称为逻辑回归)的单个神经元作为分类模型(见图 2.6)。我们将分离图 2.5 所示数据中的假纸币和真纸币。
在我们定义 Keras 代码之前,让我们先考虑所需的张量结构。网络中包含什么?如果你使用单个训练数据点,它是一个有两个条目(下一节将讨论如何处理偏置D)的向量。如果你取 128 个这样的向量的批次,你将得到一个 2 阶(矩阵)的张量,形状为(128,2)。通常在定义网络时不会指定批次大小。在这种情况下,你使用None作为批次大小。如图 2.6 所示,输入由一个具有 sigmoid 激活的单个神经元处理。

图 2.6:一个具有单个神经元的全连接神经网络。输入层中的两个节点对应于描述每个纸币的两个特征。输出层有一个节点,对应于类别 1(假纸币)的概率。
注意:在这里,我们仅简要讨论我们深度学*实验所需的主要构建块。要了解 Keras,请参考 Keras 网站keras.io/以及由 Keras 的创造者 François Chollet 所著的《Python 深度学*》一书。
在列表 2.1 中,我们使用顺序模式定义 NN 模型。在顺序模型定义中,层是依次添加的。一个层的输出是下一个层的输入,依此类推;因此,通常不需要指定层的输入形状。第一层是一个例外,这里你需要指定输入的形状。
在底层,Keras 将模型转换为张量操作。在我们的简单模型列表 2.1 中,Dense(1)密集层接收维度为(batch_size,2)的输入张量X,与 2×2 矩阵W相乘,并加上一个偏置项 b。这给出一个长度为batch_size的向量。如果你觉得这很奇怪,可以查看 Chollet 的《Python 深度学*》一书的第二章,了解更多关于矩阵乘法的信息。mng.bz/EdPo
在定义模型之后,需要编译模型,并指定使用的损失函数和优化函数。这里我们使用损失函数crossentropy,它常用于分类,并量化正确类别的预测效果。你将在第四章中了解更多关于损失函数的内容。最后但同样重要的是,我们通过迭代训练过程优化模型的权重,这被称为随机梯度下降(SGD),在第三章中有详细讨论。拟合过程的目标是调整模型权重,以使损失最小化。在每个小批量之后更新模型权重,这里包含 128 个实例。对整个训练集的一次遍历称为一个 epoch;这里我们训练了 400 个 epochs。
列表 2.1 定义一个输入后只有一个神经元的 NN
model = Sequential() ❶
model.add( Dense(1, ❷
batch_input_shape=(None, 2), ❸
activation='sigmoid') ❹
)
sgd = optimizers.SGD(lr=0.15) ❺
model.compile(
loss='binary_crossentropy',
optimizer=sgd ❻
)
history = model.fit(X, *y*, epochs=400, ❼
batch_size=128) ❽
❶ Sequential 开始定义网络。
❷ 在网络中添加了一个具有单个神经元的新的层;因此,在Dense(1)中是 1
❸ 输入是一个大小为(批大小,2)的张量。使用 None,我们现在不需要指定批大小。
❹ 选择如图 2.4 所示的激活函数 sigmoid
❺ 编译模型,这标志着模型定义的结束
❻ 定义并使用随机梯度下降优化器
❼ 使用存储在x和y中的数据训练模型 400 个 epochs
❽ 将批大小固定为 128 个示例
实践时间 当在mng.bz/lGd6笔记本中运行代码时,你会观察到损失逐渐减少,准确率逐渐提高。这表明训练工作正常。 |
|---|
让我们使用训练好的网络进行预测,并查看输出。在图 2.7 中,你可以看到基于特征x[1]和 x2 的系统评估一张纸币是假币的概率。
图 2.7 中背景的阴影表示具有相应两个特征值的实例的预测概率。白色表示特征空间中两个类别的概率都是 0.5 的位置。一侧的点被分类为一类,另一侧的点被分类为另一类。这个边界被称为决策边界。如你所见,它是一条线。这不是巧合,而是具有 sigmoid 激活函数的单个人工神经元的一般特性。在二维特征空间中,决策边界是一条直线。它不是曲线,也没有波动。如果你有三个特征,边界是一个平面(没有波动),并且对于超过三个维度的特征空间,它保持没有波动的对象,这被称为超平面。

图 2.7 输入层之后只有一个神经元的神经网络产生一个线性决策边界。二维特征空间中背景的阴影表示假钞的概率。右侧叠加了训练数据,显示线性决策曲线在真钞和假钞之间的边界上并不完美地拟合。
但在纸币示例中,两个类别之间的真实边界是曲线的。因此,单个神经元不适合根据其两个特征来模拟假钞的概率。为了获得更灵活的模型,我们在输入层和输出层之间引入了一个额外的层(见图 2.8)。这个层被称为隐藏层,因为它的值不是直接观察到的,而是由输入层的值构建的。
在这个例子中,隐藏层包含八个神经元;每个神经元都接收相同输入特征的加权总和,但权重不同。加权总和随后通过激活函数进行转换。你可以将这些隐藏层中的神经元视为输入的新表示。最初,它由两个值(特征)表示,现在则由八个值(特征)表示:八个神经元的输出。这有时被称为特征扩展。你可以在隐藏层中使用不同数量的神经元,这是神经网络设计的一部分。
输出层给出了实例是真实或假钞的概率。你已经看到,在二元分类问题中,一个神经元就足够了,因为知道一个类的概率 p 就固定了另一个类的概率为 1 - p。你还可以在输出层使用两个神经元:一个神经元模拟第一个类的概率,另一个神经元模拟第二个类的概率。这种输出层设计可以推广到具有两个以上类别的分类任务。在这种情况下,输出层的神经元数量与分类问题中的类别数量相同。每个神经元代表一个类别,你希望将神经元的输出解释为该类的概率。这可以通过softmax函数来完成。softmax函数将加权求和 zi 转换为概率 pi,通过设置

这确保了值在 0 和 1 之间,并且进一步地,它们的总和为 1。因此,你可以将π解释为类别 i 的概率。softmax 中的“软”表示,网络不是对可能的类别之一做出硬性判断,而是可以给其他类别分配较小的概率。

图 2.8 由八个节点组成的一个隐藏层的 fcNN。输入层有两个节点,对应于钞票数据集中的两个特征,输出层有两个节点,对应于两个类别(真实和假钞)。
训练数据的y向量也需要改变,以便与两个输出兼容。如果示例属于类别fake,则y = 1,对于类别real,y = 0。现在你希望标签描述两个可能的输出。一张真实的钞票应该有输出值 p0 = 1 和 p1 = 0,而一张假钞,其值 p0 = 0 和 p1 = 1。这可以通过y的一热编码来实现。你从一个与类别数量一样多的零向量开始(这里有两个)。然后设置一个条目为 1。对于y = 0,你将第 0 个条目设置为 1,这样你就有了向量(1, 0),而对于y = 1,你有了向量(0, 1)。关于 fcNN 的架构,请参阅图 2.8,以及相应的 Keras 代码,请参阅列表 2.2。
列表 2.2 定义具有一个隐藏层的网络
model = Sequential()
model.add(Dense(8, batch_input_shape=(None, 2),
activation=’sigmoid’)) ❶
model.add(Dense(2, activation='softmax')) ❷
# compile model
model.compile(loss='categorical_crossentropy',
optimizer=sg*D*)
❶ 具有八个神经元的隐藏层定义
❷ 具有两个输出神经元的输出层
如图 2.9 所示,网络现在产生一个弯曲的决策表面,并且它更好地能够将训练数据中的两个类别分开。

图 2.9 全连接神经网络产生一个弯曲的决策边界。二维特征空间中背景的阴影显示了由包含八个神经元并使用特征 x[1] 和 x[2] 作为输入的全连接神经网络预测的假钞的概率。右侧叠加了训练数据,显示了弯曲的决策边界更好地符合真钞和假钞之间的边界。
实践时间:只需在mng.bz/lGd6的钞票笔记本中添加更多隐藏层,即可成为深度学*俱乐部的一员。这比机器学*要简单得多(见图 2.10) |
|---|

图 2.10 工作中的深度学*专家。灵感来源于mng.bz/VgJP。
但是,在添加一个额外的层时发生了什么?原则上,与我们对第一个隐藏层讨论的相同。您可以将添加的隐藏层中的神经元值视为输入的新特征表示。但有一个区别:深层中的特征不是直接从输入构建的,而是从前一层构建的。例如,在第二个隐藏层中,特征是从第一个隐藏层的特征构建的(见图 2.12)。这种特征的层次化构建通常很有效,因为它允许您从第一层学*基本特征,这些特征可以用作下一层几个更复杂特征的组成部分。
通过堆叠许多层,您允许神经网络构建层次化和复杂的特征,这些特征在从一层到另一层的过程中变得越来越抽象和特定于任务。由于每层的神经元数量(以及隐藏层的数量)是设计的一部分,您需要决定这个数量是基于您问题的复杂性和您的经验,还是它是由其他成功的深度学*者报告的。
在深度学*中,好消息是您不需要预先定义确定如何从前一层的特征构建当前层特征的权重。神经网络在训练过程中学*这一点。您也不需要单独训练每一层,但通常您会整体训练神经网络,这被称为端到端训练。这有一个优点,即某一层的改变会自动触发所有其他层的适应。在第三章中,您将了解这个训练过程是如何工作的。
2.1.3 使用全连接神经网络(fcNN)进行图像分类
现在让我们使用你新学的技能来构建一个更大的网络,并看看它在对手写数字进行分类的任务上的表现。不同的科学学科有不同的模型系统,用于衡量它们的方法:分子生物学家使用一种名为 C. Elegance 的线虫;进行社会网络分析的人使用 Zachary Karate Club,最后,与深度学*相关的工作者使用著名的 MNIST 数字数据集。这个基准数据集包含 70,000 个手写数字,可以从mng.bz/xW8W 获取。所有图像都具有 28 × 28 像素,并且是灰度图像,像素值介于 0 到 255 之间。图 2.11 显示了数据集的前四幅图像。

图 2.11 MNIST 数据集的前四个数字--用于基准测试神经网络进行图像分类的标准数据集。
这个数据集在机器学*社区中广为人知。如果你开发了一种新的图像分类算法,你通常也会报告它在 MNIST 数据集上的性能。MNIST 图像是灰度图像,每个像素的灰度值由 0 到 255 范围内的整数定义。为了进行公平的比较,数据有一个标准的分割:60,000 个图像用于训练网络,10,000 个用于测试。在 Keras 中,你可以用一行代码下载整个数据集(见列表 2.3)。你还可以下载本节的相关 MNIST 笔记本(你可以在以后工作),网址为mng.bz/AAJz 。
简单的神经网络无法处理 2D 图像,但需要一个 1D 输入向量。因此,你首先将 28 × 28 的图像展平成一个大小为 28 · 28 = 784 的向量。输出应该指示输入图像是否是 0 到 9 中的数字之一。更精确地说,你希望模型表示网络认为给定输入图像是某个特定数字的概率。为此,输出层有十个神经元(每个数字一个)。你再次使用激活函数softmax来确保计算出的输出可以被解释为概率(介于 0 到 1 之间的数字),总和为 1。在这个例子中,我们还包括了隐藏层。图 2.12 显示了网络的简化版本以及 Keras 中相应模型的定义,如列表 2.4 所示。

图 2.12 一个具有两个隐藏层的全连接神经网络。在 MNIST 示例中,输入层有 784 个值对应于 28 × 28 像素,输出层有 10 个节点对应于 10 个类别。
列表 2.3 加载 MNIST 数据
from tensorflow.keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = \
mnist.load_data() ❶
X_train = x_train[0:50000] / 255 ❷
Y_train = y_train[0:50000]
Y_train = to_categorical(*y*_train,10) ❸
X_val=x_train[50000:60000] / 255 ❹
...
❶ 加载 MNIST 训练集(60,000 个图像)和测试集
❷ 使用 50,000 个图像进行训练,除以 255,使像素值在 0 到 1 的范围内
❸ 将作为整数从 0 到 9 给出的标签存储为 one-hot 编码向量
❹ 我们对验证集也做同样的处理。
注意:我们在这个列表中不使用测试集。
此外,在我们存储网络 y_train 标签的地方,我们将这些转换为长度为 10 的分类数据,以匹配输出。1 被转换为 (0,1,0,0,0,0,0,0,0,0)。这被称为 one-hot 编码。在下一个列表中,你可以看到一个小 fcNN 使用 one-hot 编码的标签 Y_train.。
列表 2.4 MNIST 数据的 fcNN 定义
model = Sequential()
model.add(Dense(100, batch_input_shape=(None, 784), ❶
activation='sigmoid'))
model.add(Dense(50, activation='sigmoid')) ❷
model.add(Dense(10, activation='softmax')) ❸
model.compile(loss='categorical_crossentropy',
optimizer='adam', ❹
metrics=['accuracy']) ❺
history=model.fit(X_train_flat, Y_train,
batch_size=128,
epochs=10,
validation_data=(X_val_flat, Y_val)
)
❶ 第一个隐藏层有 100 个神经元,连接到输入大小 28 × 28 像素
❷ 第二个密集层有 50 个神经元
❸ 第三层连接到 10 个输出神经元
❹ 使用比 SGD 更快的不同优化器(见第三章)
❺ 跟踪训练过程中的准确率(正确分类的训练和验证示例的分数)
实践时间现在打开 MNIST 笔记本 mng.bz/AAJz ,运行它,并尝试理解代码。 |
|---|
当查看损失曲线随迭代次数的变化过程(见图 2.13)时,你可以观察到模型拟合了数据。训练好的 fcNN 在验证集上的性能大约为 97%,这并不坏,但最先进的技术大约为 99%。

图 2.13 在不同的训练步骤中,准确率训练(顶部)和损失(底部)的增加
玩深度学*游戏并堆叠更多层。常用的另一个技巧是将隐藏层中的 sigmoid 激活函数替换为更简单的:ReLU。ReLU 代表修正线性单元,对于它所做的事情来说,这个名字相当长。它只是将小于零的值固定为零,而将大于零的值保持不变(见图 2.14)。在隐藏层中使用非线性激活函数是至关重要的,因为当使用线性激活函数时,你可以用一个层来替换层堆叠。这是因为通过一个线性层相当于矩阵乘法,你可以用一个矩阵乘法来替换一系列矩阵乘法。要在 Keras 中更改激活函数,只需将 sigmoid 替换为 relu。如果你愿意,你可以在笔记本 mng.bz/AAJz 中更改激活函数。

图 2.14 ReLU 和 sigmoid 激活函数的比较
让我们做一个小的实验,研究在你将这些像素值输入网络之前对像素值进行洗牌会发生什么。图 2.15 显示了与图 2.11 相同的数字,但这次是随机洗牌的。

图 2.15 在图 2.11 中相同的数字(5, 0, 4, 1)在像素洗牌后的样子
对于每张图像,像素都按照相同的方式进行洗牌。即使看过成千上万的训练示例,你也很难说出正确的数字。网络还能学会识别这些数字吗?
实践时间 尝试在 MNIST 笔记本中运行代码并与之互动 mng.bz/2XN0。你观察到了什么? |
|---|
注意:只跟随笔记本直到你达到“CNN 作为 MNIST 数据的分类模型”这一部分。我们稍后会研究 CNN,然后再回到笔记本。
你可能会达到与原始图像相同的准确度(在统计波动范围内)。一开始这可能会让人感到惊讶。但看看全连接神经网络的网络架构,输入的顺序根本不重要。因为网络没有邻*像素的概念,所以没有类似邻域的东西。因此,人们也把全连接神经网络称为排列不变神经网络,因为其性能不依赖于数据是否被排列(打乱D)。然而,真实的图像数据并不是排列不变的,邻*像素往往具有相似值。如果你打乱图像,人们将难以识别它们。此外,显示相同数字的两个图像不需要显示相同的像素值。你可以稍微移动(平移)图像,它仍然显示相同的对象。
人类在视觉任务上表现出色,但在处理打乱顺序的图像时却存在问题,这表明进化已经找到了利用图像数据特殊属性的方法。虽然全连接神经网络(fcNNs)在处理类似电子表格的数据时表现良好,其中列的顺序并不重要,但当顺序或空间对齐很重要时,如卷积神经网络(convolutional NNs),有更好的架构。原则上,全连接神经网络可以用于图像处理,但你需要很多层和巨大的训练数据集,以便网络学*到邻*像素倾向于具有相同值,以及图像是平移不变的。
2.2 用于图像数据卷积神经网络
即使只有一个隐藏层的全连接神经网络也能表示任何函数,但很快就会变得太大,包含太多的参数,通常没有足够的数据来拟合它们。深度学*(DL)的许多进展都围绕着创建不同的架构,这些架构更有效地利用数据的结构。对于图像数据,这种架构之一就是卷积神经网络。
对于只有一个隐藏层的全连接神经网络示例(见图 2.8),我们讨论了可以将隐藏层中的神经元数量视为从输入构建的新特征数量。这意味着如果你想解决复杂问题,你需要隐藏层中有大量的神经元。但隐藏层中的神经元越多,你需要学*的参数就越多,你需要更多的训练数据。堆叠层让模型以层次化的方式学*特定任务的特征。这种方法比全连接神经网络构建复杂特征所需的参数更少,因此对数据的需求也更低。
你在上一个章节中学到,如果你为全连接神经网络(fcNN)添加更多的隐藏层,你就能得到更多。深入挖掘是增强神经网络性能的一个很好的技巧,这也是深度学*之所以得名的原因。你还了解到,全连接神经网络忽略了图像中像素的邻*结构。这表明可能存在一个更好的神经网络架构来分析图像数据。实际上,深度学*在计算机视觉领域的成功也离不开一些额外的架构技巧,这些技巧利用了关于图像数据局部结构的知识。
为图像数据等局部相关数据定制神经网络的最重要组成部分是所谓的卷积层。在本节中,我们将解释卷积层是如何工作的。主要由卷积层组成的神经网络被称为卷积神经网络(CNN),并且具有极其广泛的应用范围,包括:
-
图像分类,例如区分卡车和路标
-
视频数据预测,例如生成天气预报的未来雷达图像
-
基于图像或视频数据的生产线质量监控
-
在组织病理切片中对不同肿瘤的分类和检测
-
图像中不同对象的分割
2.2.1 CNN 架构中的主要思想
让我们专注于图像数据,并讨论一种专门的网络架构,该架构考虑了图像内部的高度局部结构(见图 2.16)。在 2012 年,Alex Krizhevsky 在国际知名的 ImageNet 竞赛中使用了这种架构,这为深度学*进入计算机视觉领域带来了突破。

图 2.16:图像可以被分解成局部模式,如边缘、纹理等。
我们现在将深入探讨卷积神经网络(CNN)的架构,并讨论它们是如何获得这个名字的。让我们看看 CNN 的主要思想:不是连接两个连续层之间的所有神经元,而只是相邻像素的小块区域连接到下一层的神经元(见图 2.17)。通过这个简单的技巧,网络架构内置了图像的局部结构。这个技巧也减少了神经网络中的权重数量。如果你只考虑例如 3 × 3 像素的小块区域作为连接到下一层神经元的局部模式(见图 2.18),那么你只需要学* 10 个权重,即加权求和z = x[1] ⋅ w[1] - x[2] ∙ w[2] + ⋯ + x[9] ∙ w[9] + b,这是下一神经元的输入。

图 2.17:全连接神经网络(fcNN)(左侧)或卷积神经网络(CNN)(右侧)的输入图像与第一隐藏层神经元之间的连接。这种表示忽略了偏差项。
如果你有过经典图像分析的经验,那么你知道这个想法根本不新鲜。你在这里做的是所谓的卷积。

图 2.18 展示了将一个 6×6 的灰度图像与一个 3×3 的核进行卷积,步长为 1 且不进行填充时,得到的输出是一个 4×4 的特征图。核在所有 16 个可能的位置上应用,以确定激活图的 16 个值。输入中用粗实线和虚线标记了两个可能的核位置。激活图中相应像素的位置也用粗实线和虚线标记。CNN 通过将像素值与叠加的核值相乘并添加所有项(假设偏差为 0)来计算结果值。
看一下图 2.18,其中你可以看到一个 6×6 像素的小图像和一个 3×3 的核 1,核具有预定义的权重。你可以通过每次移动 1 个像素(称为stride=1)来在图像上滑动核。在每个位置,你计算图像像素和叠加核权重的逐元素乘积。然后,将这些值相加得到加权总和 z = x[1] ∙ w[1] +x[2] ∙ w[2] + ⋯ +x**[k] ∙ w**[k] + b ,其中 k 是连接到每个神经元的像素数量,b 是偏差项。计算出的值 z 是输出矩阵的单个元素。将核移到图像上的下一个位置后,你可以计算下一个输出值 z,依此类推。我们将这种结果输出称为激活图或特征图。
在图 2.18 的例子中,我们从一个 6×6 的图像开始,将其与一个 3×3 的核进行卷积,得到一个 4×4 的激活图。在图像上滑动核并要求整个核在每个位置完全位于图像内,可以得到一个尺寸减小的激活图。例如,如果你在所有边都有一个 3×3 的核,结果激活图中会去掉一个像素;如果是 5×5 的核,则会去掉两个像素。如果你希望在应用卷积后保持相同的尺寸,你可以使用输入图像的零填充(称为padding='same',列表 2.5 中卷积层的参数;如果你不想使用零填充,参数将是padding='valid')。
在 CNN 中,核权重是通过学*得到的(参见第三章)。因为你在每个位置使用相同的核,所以有共享权重,在我们的例子中,你只需要学* 3×3=9 个权重来计算整个激活图。通常,如果需要学* 10 个权重,也会包括一个偏差项。要交互式地将不同的核应用于真实图像,请参阅setosa.io/ev/image-kernels/。
激活图中的值能告诉你什么?如果你将核应用于图像中所有可能的位置,你只会得到高信号,在这些位置下,底层图像显示了核的模式。将输出组装成图像,可以得到一个映射,显示了核模式出现在图像中的哪些位置。这就是为什么结果图像通常被称为特征图或激活图的原因。
同一激活图中的每个神经元具有相同数量的输入连接,连接的权重也相同。(你很快就会看到实际应用中使用不止一个核。)每个神经元连接到输入(前一层)的不同区域,这意味着同一特征图内的每个神经元都在寻找相同的模式,但位置不同。图 2.19 展示了这一概念,该图像由矩形区域组成,在这些区域上应用了具有垂直边缘模式的核。我们使用这种技术在图像处理中使用,例如,增强图像的边缘或使其模糊。访问setosa.io/ev/image-kernels/以了解不同核对更复杂图像的影响。
在图 2.19 中,你可以看到垂直边缘核(从亮到暗)在图像中的三个位置。在图像位置中,垂直边缘从亮到暗,你会得到一个高值(在激活图中显示为深灰色像素)。在图像位置中,垂直边缘从暗到亮,你会得到一个低值(在激活图中显示为浅灰色像素)。在图像中没有垂直边缘的位置,结果值既不高也不低(在激活图中显示为中灰色像素)。在显示的滤波器中,如果权重总和为 1,则如果输入是具有恒定灰度值的图像块,激活图中的值将为零。
2.2.2 最小 CNN,适合边缘爱好者
让我们想象一个对包含垂直边缘的图像感到兴奋的艺术爱好者。你的任务是预测一组条纹图像,看艺术爱好者是否会喜欢这些图像。该集合中的一些图像有水平边缘,而另一些有垂直边缘。为了识别具有垂直条纹的图像,一个垂直边缘检测模型将非常出色。为此目的,你可能想要做类似于图 2.19 中描述的事情,并使用预定义的垂直边缘滤波器进行卷积,使用结果特征图中的最大值作为分数,表示艺术爱好者是否会喜欢该图像。

图 2.19 展示了将一个 3×3 核与类似垂直边缘的权重模式(左上面板)与由平方区域组成的图像(左下和右面板)进行卷积的结果,这会产生一个特征图,突出显示输入图像中垂直边缘的位置(右上面板)。在左面板中,数字表示核的加权值(左上)和图像的像素值(左下)。
在传统的图像分析中,当感兴趣的特性已知并且可以描述为局部模式时,通常使用预定义的核进行卷积。在这种情况下,不使用这种传统的图像分析方法就显得有些愚蠢。但让我们假设你不知道艺术爱好者喜欢垂直边缘,而你只有他们喜欢和不喜欢的一组图像列表。你想要学*核中可用于卷积的权重值。图 2.20 显示了相应的网络架构,其中核的大小为 5 × 5。结果隐藏层是一个特征图。
![2-20.png]
图 2.20 仅由一个特征图组成,包含一个隐藏层的 CNN。作为一个池化值,你取特征图内所有值的最大值。你添加一个密集层来确定输出中两个可能的类别标签的概率。
为了检查这个特征图是否表明图像包含核模式,你取特征图的最大值。从这个值,你想要预测艺术爱好者喜欢图像的概率。你已经看到了如何做到这一点:你添加一个单层的全连接层,具有两个输出节点,并使用 softmax 激活来确保两个输出值可以作为两个类别的概率(艺术爱好者喜欢图像;艺术爱好者不喜欢图像)。这加起来为 1。这个小 CNN 网络(第一隐藏层中的特征图)是通过图像与核的卷积得到的。分类是在图 2.20 右侧显示的全连接部分完成的。这种架构可能是人们能想到的最小的 CNN 之一。要使用 TensorFlow 和 Keras 建模图像数据,你需要创建形式为 4D 的张量:
(batch, height, width, channels)
批量维度对应于一个批次中的图像数量。接下来的两个元素定义了图像的高度和宽度,单位为像素。最后一个维度定义了通道数。(一个典型的 RGB 彩色图像有 3 个通道。这意味着一个包含 128 个彩色图像的批次,每个图像有 256 行和 256 列,可以存储在一个形状为(128, 256, 256, 3)的张量中。)
你可以用几行 Keras 代码设置、训练和评估 CNN 模型(见列表 2.5)。你需要的是包含水平或垂直条纹的图像数据集及其相应的类别标签。这可以很容易地模拟。
实践时间 打开边缘爱好者笔记本,访问mng.bz/1zEj,并遵循那里的说明来模拟图像数据和拟合模型。检查哪些核权重被学*,以及这些是否形成垂直边缘。如果你无法重现结果,不要担心;只需重新进行训练,直到你得到结果。研究激活函数和池化方法的影响。 |
|---|
model = Sequential()
model.add(Convolution2D(1,(5,5),padding='same',\ ❶
input_shape=(pixel,pixel,1)))
model.add(Activation('linear')) ❷
# take the max over all values in the activation map
model.add(MaxPooling2D(pool_size=(pixel,pixel))) ❸
model.add(Flatten()) ❹
model.add(Dense(2)) ❺
model.add(Activation('softmax')) ❻
# compile model and initialize weights
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
# train the model
history=model.fit(X_train, Y_train,
validation_data=(X_val,Y_val),
batch_size=64,
epochs=15,
verbose=1,
shuffle=True)
❶ 使用一个大小为 5 × 5 的核,具有相同填充的卷积层
❷ 添加线性激活函数(传递所有值)
❸ MaxPooling 层提取特征图的最大值。
❹ 将前一层输出展平成一个向量
❺ 一个包含两个神经元的密集层预测两个标签的概率。
❻ 添加 softmax 激活函数来计算两个类别的概率
注意:在列表中,使用padding='same'的卷积层意味着输出特征图的大小与输入图像相同。
在你使用边缘爱好者笔记本进行实验的mng.bz/1zEj时,你可能已经注意到,垂直边缘核并不总是被学*到;有时会学*到水平边缘核。这是完全可以接受的,因为数据集由具有水平或垂直边缘的图像组成,任务只是区分水平和垂直边缘。没有找到水平边缘表明图像只包含垂直边缘。
在这个边缘爱好者例子中,使用预定义的核或学*核的权重可能没有太大区别。但在更现实的应用中,最佳区分模式有时很难预先定义,而学*最优核权重是 CNN 的一个巨大优势。在第三章中,你将学*如何训练模型权重。
2.2.3 CNN 架构的生物灵感
边缘爱好者这个例子只是一个玩具,你可能会认为在真实的大脑中肯定没有喜欢边缘的神经元。相反,确实如此!人类和动物大脑中的所谓视觉皮层确实有喜欢边缘的神经元。两位生物学家 Hubel 和 Wiesel 因在 1981 年发现这一点而获得了诺贝尔生理学或医学奖。他们发现这一点的过程非常有趣。而且,正如研究中的常见情况一样,其中涉及了很多运气。
在 20 世纪 50 年代末,Hubel 和 Wiesel 想要研究猫视觉皮层中由于刺激引起的神经元活动的相关性。为此,他们对猫进行了麻醉,并在它面前屏幕上投影了一些图像。他们选择了一个神经元来测量电信号(见图 2.21)。然而,实验似乎没有成功,因为他们无法在向猫展示不同图像时观察到神经元放电。他们更换了投影仪中的幻灯片,换成了频率越来越高的幻灯片。最后,由于幻灯片卡住了,他们摇晃了投影仪,神经元开始放电。通过这种方式,他们发现视觉皮层不同位置的神经元在边缘以不同方向滑过猫眼视网膜时会被激活。

图 2.21 展示了 Hubel 和 Wiesel 的实验设置,他们发现了在猫的视觉皮层中,当向猫展示移动边缘时,会响应的神经元。
脑科学研究持续发展,现在众所周知,在 Hubel 和 Wiesel 进行实验的大脑区域(称为 V1 区域),所有神经元对不同视网膜区域上的相对简单的刺激形式都有反应。这不仅适用于猫,也适用于其他动物和人类。还知道,大脑其他区域的神经元(称为 V2、V4 和 IT)对越来越复杂的视觉刺激有反应,例如整个面部(见图 2.22)。研究表明,神经元的信号是从一个区域传到另一个区域的。此外,只有大脑一个区域的神经元的一部分连接到下一个区域的神经元。通过神经元的连接,不同神经元的激活以层次化的方式结合,这使得神经元能够在视网膜上对越来越大的区域和越来越复杂的视觉刺激做出反应。

图 2.22 大脑视觉皮层的组织。不同区域的神经元对越来越大的感受野和越来越复杂的刺激有反应。
注意:你很快就会看到,更深层次的 CNN 架构在某种程度上受到了从简单结构到复杂结构的层次检测的启发。然而,这种类比不应过分强调;大脑并不是为了形成 CNN 而连接起来的。
2.2.4 构建和理解 CNN
更现实的图像分类任务无法通过如图 2.20 所示的简单 CNN 架构来处理,该架构仅学*检测像边缘这样的单个局部图像模式。即使是简单的图像分类任务,如区分 MNIST 数据集中的 10 个数字,也需要学*大量的更复杂的图像特征。你可能已经能猜到如何做到这一点:深入挖掘是主要秘诀。但在深入挖掘之前,你需要拓宽视野,并在第一层添加更多核。

图 2.23 输入图像与六个不同核的卷积产生了六个激活图。如果输入图像只有一个通道(a),那么每个核也只有一个通道。如果输入图像有三个通道(b),那么每个滤波器也有三个通道。
每个核可以学*另一组权重,因此对于每个核,你都会在隐藏层中得到另一个激活图(见图 2.23)。如果输入不仅有 1 个通道,而是有 d 个通道,那么核也需要有 d 个通道来计算激活图。对于彩色图像,d = 3,对应于(红、绿、蓝),一个有效的核可以是绿色通道中垂直边缘活跃,而在蓝色和红色通道中水平边缘活跃的核。核矩阵再次定义了加权求和的权重,这决定了激活图中相应位置的神经元输入。
现在我们来谈谈全连接神经网络(fcNN)和卷积神经网络(CNN)之间的类比。全连接神经网络为每个神经元学*一组新的权重(学*过程在第三章中讨论),这些权重将前一层输入结合到一个新的值,这个值可以看作是图像的特征(例如,参见图 2.8)。在全连接神经网络中,你可以通过添加层来加深网络,其中一层中的所有神经元都连接到下一层中的所有神经元。从这个意义上说,CNN 中的核集或激活图的数量对应于全连接神经网络一层中的神经元数量。如果你想在 CNN 中加深网络,你需要添加更多的卷积层。这意味着你学*的核再次应用于前一层激活图的堆叠。
图 2.25 说明了这个原理。在该图中,你可以看到一个具有 3 个卷积层的 CNN。在激活图堆叠上的卷积与具有多个通道输入的卷积没有区别。在图 2.23 中,仅从 3 通道输入图像中生成了 6 个激活图。然而,只学* 6 个核并不常见。典型的情况是在第一层学* 32 个核,甚至更多的核。(通常,从一层到下一层核的数量会加倍。)为了减少 CNN 中的权重数量,在下一轮卷积之前对激活图进行下采样也是常见的做法。这通常是通过用一个 2×2 的神经元块替换激活图中的最大激活来完成的。我们称这一步为最大池化。
当向 CNN 添加更多层时,神经元在原始图像中看到的区域会变大。我们称之为感受野,它由所有与神经元通过所有中间层连接的原始图像中的像素组成。根据图像大小和核大小(通常在四到十个层之后),所有神经元都连接到整个输入图像。尽管如此,激活 CNN 不同层中神经元的图像模式复杂性随着每一层的增加而提高。
当检查哪些图像或图像部分可以在 CNN 的不同层中激活一个神经元时,靠*输入的层对简单的图像模式(如边缘)做出反应,而靠*输出的层将这些简单模式组合成更复杂的模式(参见图 2.24)。

图 2.24 展示了 CNN 不同卷积层中激活神经元图像模式的层次结构:简单的模式(如边缘)组合成局部对象(如眼睛或耳朵),这些对象进一步组合成更高级的概念(如猫)。该图使用得到了 François Chollet 的《Python 深度学*》(Manning, 2017)一书的许可。
卷积层的数量以及每层的核数量是 CNN 中的调整参数。当问题的复杂性增加时,通常需要更多的卷积层和每层的更多核。在 CNN 的最后一个卷积层中,我们得到了输入的新表示。将此层的所有神经元展平成一个向量,结果得到一个具有与最后一个卷积层中神经元数量一样多的图像特征的新特征表示(见图 2.25)。我们最终又回到了之前的情况:输入由一个图像特征向量描述。但这次,特征是训练核的结果。现在你可以添加几个密集连接层来构建预测。

图 2.25 一个具有三个卷积层和三个全连接层的 CNN。每个卷积层中的特征图数量表示学*到的核集数量。全连接部分每层的元素数量表示学*到的加权集数量。
让我们在 MNIST 数据上尝试一个 CNN。在列表 2.6 中,你可以看到具有卷积层和全连接层的 CNN 的定义。
实践时间 再次打开 MNIST 笔记本 mng.bz/AAJz,将具有两个卷积层的 CNN 拟合到 MNIST 数据上(参见笔记本的第二部分)。然后比较与使用 fcNN 所达到的性能。玩转代码并执行一个排列实验以检查图像中像素的顺序对 CNN 性能的影响。 |
|---|
# define CNN with 2 convolution blocks and 2 fully connected layers
model = Sequential()
model.add(Convolution2D(8,kernel_size,\ ❶
padding='same',input_shape=input_shape))
model.add(Activation('relu')) ❷
model.add(Convolution2D(8, kernel_size,padding='same')) ❶
model.add(Activation('relu')) ❷
model.add(MaxPooling2D(pool_size=pool_size)) ❸
model.add(Convolution2D(16, kernel_size,padding='same')) ❹
model.add(Activation('relu')) ❷
model.add(Convolution2D(16,kernel_size,padding='same')) ❹
model.add(Activation('relu')) ❷
model.add(MaxPooling2D(pool_size=pool_size)) ❺
model.add(Flatten()) ❻
model.add(Dense(40))
model.add(Activation('relu')) ❷
model.add(Dense(nb_classes)) ❼
model.add(Activation('softmax')) ❽
# compile model and initialize weights
model.compile(loss='categorical_crossentropy',
optimizer='adam',
metrics=['accuracy'])
# train the model
history=model.fit(X_train, Y_train,
batch_size=128,
epochs=10,
verbose=2,
validation_data=(X_val, Y_val)
)
❶ 使用一个大小为 3 × 3 的八个核的卷积层
❷ 将 relu 激活函数应用于特征图
❸ 此最大池化层具有 2 × 2 的池化大小和 2 的步长。
❹ 使用一个大小为 3 × 3 的 16 个核的卷积层
❺ 此最大池化层将 14 × 14 × 16 的输入张量转换为 7 × 7 × 16 的输出张量。
❻ 将前一层输出展平,得到长度为 784(7 × 7 × 16)的向量
❼ 输出 nb_classes(此处为 10)
❽ 使用 softmax 将 10 个输出转换为 10 个预测概率
第一个卷积层使用八个具有相同填充的核,结果输出特征图的大小与输入图像相同。在 MNIST 情况下,输入图像的大小为 28 × 28 × 1 像素。结果八个特征图每个的大小为 28 × 28。经过第一次池化后,输入的形状为 28 × 28 × 8,输出形状为 14 × 14 × 8。
从您在 mng.bz/AAJz 的 MNIST 笔记本中的实验中,您已经了解到,对于这个图像分类任务,使用 CNN(大约 99%)比使用 fcNN(大约 96%)更容易实现更高的性能。排列实验表明,图像中像素的排列确实很重要:当在原始图像数据上训练时(99%),CNN 的表现比在图像数据的随机版本上训练时(95%)要好得多。这支持了这样一个观点,即 CNN 在图像相关任务中表现优异的秘密在于其架构考虑了图像的局部顺序。在继续之前,让我们回顾一下并强调 CNN 在处理图像数据时的某些优势:
-
局部连通性利用了图像数据的局部信息。
-
在 CNN 中,您需要的权重参数比在 fcNN 中少。
-
CNN 在很大程度上对图像内的平移是不变的。
-
CNN 的卷积部分允许网络以层次结构学*特定任务的抽象图像特征。
下一个成功使用深度学*分析的特殊数据类型是显示排序的数据。让我们看看下一个。
2.3 有序数据的一维 CNN
有序数据可以是文本(理解为单词或字符的序列),时间序列(如苏黎世的每日最高温度),声音或任何其他有序数据。这些算法的应用包括以下内容:
-
文档和时间序列分类,例如识别文章的主题或书籍的作者
-
序列比较,例如估计两个文档或两个股票交易者的相似程度
-
序列到序列学*,例如将英语句子解码成法语
-
情感分析,例如将推文或电影评论的情感分类为正面或负面
-
时间序列预测,例如根据最*的天气数据预测某个地点的未来天气
2.3.1 时间排序数据的格式
要使用 TensorFlow 和 Keras 模型有序数据,您需要将数据作为 3D 张量提供:
(batch, timestep, input_feature)
batch 维度指定了在一次批量中处理的序列数量。在一个批量中使用许多序列只是为了性能原因。批量中的序列是独立处理的。这与之前的 CNN 情况相同,其中批量中的图像也是独立处理的。在计算损失函数时,这些不同序列的结果会被平均。
让我们来看一个例子。你想预测明天的日最高气温。你有 12 年的历史数据,并且想考虑最后 10 天的数据来预测明天的气温。你选择了一个批大小为 128。在这种情况下,输入张量的形状如下:(128, 10, 1)。让我们细化模型。也许五个附*城市的日最高气温中包含的信息可以帮助你的预测。你也考虑了这些温度,这导致输入张量的形状为(128, 10, 6)。
有序数据的另一个应用领域是文本分析。假设你想分析以字符为输入的文本。timestep 维度指定了字符在序列中的位置。input_feature 维度则保存了每个序列和时间步的实际值。
再来看另一个例子。假设你想在字符级别分析小写的文本数据。批次的第三个序列以“hello.”开头。你可以通过使用字母在字母表中的位置来编码“hello”,即(8, 5, 11, 11, 14)。以这种方式编码字符意味着存在一种人为的顺序。因此,在深度学*中,分类数据使用独热编码来处理。有关独热编码的更详细描述,请参阅mng.bz/7Xrv。在这个 3D 输入张量中,这个序列的前两个元素将是:
input [2,0,:] = (0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
# a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,z,x,z
#the 1 at position 8 indicates the 8th character in the alphabet, which is h
input [2,1,:] = (0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
# a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,z,x,z
#the 1 at position 5 indicates the 5th character in the alphabet, which is e
当你有有限数量的字符需要以字符级别对文本进行建模时,独热编码是有用的。如果你在词级别建模文本并执行独热编码,你需要与单词数量一样多的维度的向量。这些向量会变得相当大且稀疏,因此,找到一种更密集的表示更好。这是我们称之为嵌入的步骤,它产生了一个新的表示,将单词作为数字向量。在 Chollet 的书中第六章mng.bz/qMGr中,你可以找到将文本转换为向量的更多细节和一些 Keras 的示例代码。
2.3.2 有序数据有什么特别之处?
对于文本和其他有序数据,序列通常具有一些特定的属性。第一个属性是通常没有时间上的自然起点概念。稍微哲学一点,如果你现在不考虑大爆炸,而考虑正常的时间跨度,就没有明显的起点。这导致所有物理定律都必须在时间上保持不变。如果你打乒乓球,球的轨迹和 15 世纪时是一样的。第二个特殊性是时间序列数据通常包括长距离依赖关系。考虑以下字符串(取自维基百科关于康德的条目en.wikipedia.org/wiki/Immanuel_Kant):
康德于 1724 年 4 月 22 日出生于东普鲁士的柯尼斯堡的一个路德派新教普鲁士家庭。......[省略数千字]。一个常见的说法是,康德一生从未超过 16 公里(9.9 英里)离开过柯尼斯堡。......[省略数千字]。康德的健康状况一直不佳,病情恶化,他在 __________ 去世。
下一个词是以下哪一个的概率:a)光剑,b)伦敦,或 c)柯尼斯堡?这表明在序列数据中存在相当长的依赖关系。此外,在有序数据中也可以找到长距离依赖。例如,如果你考虑地球上任意地点的每日最高气温,那么在 365 个数据点之后,出现相似气温的可能性相当大(至少比 182 天后更有可能)。有序数据的最后一个特殊属性特别出现在时间序列中,其中存在过去、现在和未来的概念。在这种情况下,未来不会影响过去。这个特性对于天气预报中的温度时间序列来说是正确的,但不是对于情感分析任务。
一个最优的网络应该在设计中融入这些硬事实,这样它就不需要学*它们。在因果网络的情况下,架构确保只有过去的信息对现在有影响。同样,在图像卷积网络的情况下,具有共享权重的架构确保模型对小的空间位移是不变的。
2.3.3 时间序列数据的架构
我们经常使用所谓的循环神经网络(RNNs)如长短期记忆网络(LSTMs)来分析时间序列数据。这些网络在概念上比 CNNs 复杂一些。此外,RNNs 的训练也稍微困难一些。在许多应用中,RNNs 可以被 CNNs 替代,或者如最*的研究论文所指出(Bai 等人,arxiv.org/abs/1803.01271):
我们得出结论,序列建模与循环网络之间的常见关联应该被重新考虑,卷积网络应该被视为序列建模任务的起点。
由于你不需要在本书的其余部分使用 RNNs,请参考 Chollet 的书籍第六章,mng.bz/mBea,并继续使用 CNNs 进行序列建模。
使用 CNNs 处理时间序列数据
处理时间序列数据的另一种方法是使用一维(1D)卷积神经网络。在这些一维网络中,时间被当作一个空间维度来处理。你可以使用这些一维卷积网络来完成各种特定于序列的任务,如情感分析。在这里,我们展示了它们在预测时间序列中的应用。
对于时间序列数据,未来不应对现在或过去有任何影响。更进一步,也不应有明显的起始时间。你可以将学*到的卷积核应用于任意大小的序列。因此,这些核可以在序列上滑动,不对任何时间点进行特殊处理。只允许过去和当前时间点影响当前或未来结果预测的称为因果。你可以通过只让前一个或当前时间点的值影响当前值的预测来轻松应用因果要求。这导致了所谓的因果卷积。图 2.26 展示了输入值 10、20、30 与大小为 2 的 1D 卷积核(权重值为 1 和 2)的简单卷积示例。

图 2.26 使用权重为 1、2 的核对值 10、20、30 进行简单因果卷积。卷积后的数字 50 只依赖于过去和当前值(10、20),而不依赖于未来的值 30。
你在图 2.26 中可以看到,第二层(上层)的元素较少。为了使所有层的大小相同,我们在输入层的开始处添加了零填充。在这种情况下,图 2.26 变成了图 2.27。

图 2.27 对于值 10、20、30 和核 1、2 的简单因果卷积,使用 0 填充
实践时间 如果你想更好地理解一维卷积的工作原理,你可以通过笔记本 mng.bz/5aBO 进行学*。在这个笔记本中,我们还介绍了时间膨胀因果卷积,允许长距离时间依赖。 |
|---|
现在你已经看到了在深度学*中使用的最基本的建筑模块:全连接、卷积和循环神经网络。这些只是构建模块,你可以将它们组合使用。例如,你可以将图像输入到卷积网络中,然后使用循环神经网络产生有序输出作为文本。这种组合已被成功用于为图像创建标题。
摘要
-
全连接神经网络(fcNN)由堆叠的神经元层组成。
-
在全连接神经网络(fcNN)中,每个神经元都与前一层中的每个神经元相连。
-
神经元的输入是由前一层中连接的神经元的加权总和给出的。
-
那个加权的总和通过一个激活函数计算神经元的输出。
-
激活函数的非线性是至关重要的,因为否则堆叠的层可以被一层所替代。
-
在隐藏层中使用
relu激活函数。与 sigmoid 激活函数相比,它已知可以产生更有效的训练。 -
在进行分类时,在输出层使用
softmax作为激活函数。softmax函数的输出可以解释为某个类标签的概率。 -
卷积神经网络(CNN)由卷积部分和全连接部分组成,其中卷积部分从输入数据中提取特征,全连接部分将特征组合到 CNN 的输出中。
-
卷积神经网络(CNN)的卷积部分由堆叠的特征图层组成。
-
特征图中的每个神经元只连接到前一个特征图的一个小区域。这反映了图像数据的局部结构。
-
深度神经网络的高性能依赖于它们学*给定任务的层次化最优特征这一事实。
-
当神经网络架构利用数据已知结构时,神经网络工作得最好,这样它们就不必从头开始学*这些结构。因此,
-
如果你的数据来自图像(或具有其他二维结构),请使用二维 CNN,通过局部连接和共享权重来利用图像的局部结构。
-
如果你的数据来自序列,尽可能使用一维卷积;否则,使用循环神经网络(RNN)。
-
如果你没有特定的结构,请使用全连接神经网络(fcNN)。
- 在深度学*和计算机视觉领域,人们使用“核”这个词,但有时你也会看到“滤波器”这个术语,它可以作为同义词使用。
曲线拟合的 3 个原则
本章涵盖
-
如何拟合参数模型
-
损失函数是什么以及如何使用它
-
线性回归,所有神经网络之母
-
梯度下降作为优化损失函数的工具
-
使用不同框架实现梯度下降

深度学*模型之所以闻名,是因为它们在计算机视觉和自然语言处理等广泛的任务中优于传统的机器学*(ML)方法。从上一章,你已经知道深度学*模型的关键成功因素是其深层的层次结构。深度学*模型拥有数百万可调整的参数,你可能想知道如何调整这些参数以使模型表现最优。解决方案惊人地简单。它已经在许多传统的机器学*方法中得到了应用:你首先定义一个损失函数,该函数描述了模型在训练数据上的表现有多糟糕,然后调整模型的参数以最小化损失。这个过程被称为拟合。
在具有简单损失函数的机器学*模型中,通常可以提供一个公式,让你从数据中计算出最优的参数值。然而,对于复杂模型来说并非如此。对于复杂模型,开发复杂的优化过程花费了几十年时间。深度学*几乎完全使用一种确定参数的方法:这被称为梯度下降。在本章中,你会发现梯度下降是一种惊人的简单技术。这在深度学*中仍然是一种奇迹,因为这种技术在深度学*中效果如此之好,而更高级的优化过程却失败了。
所有深度学*所需的组件——一个具有权重和损失函数的模型,用于将权重拟合到(训练)数据中——在像线性回归这样的更简单模型中已经存在。为了给你一个清晰的梯度下降方法图景,我们逐步演示了它是如何用于拟合一个简单的线性回归模型的。实际上,这是可能的最小神经网络(NN)。我们介绍了损失函数,这是最常用于拟合线性回归模型的函数,并展示了如何确定最小化这个损失函数的参数值。将线性回归视为机器和深度学*的“Hello world”。让我们从深度学*的角度来审视线性回归。
3.1 曲线拟合中的“Hello world”
让我们看看一个简单的线性回归模型。想象一下,你是一名初级医疗助理,你和一位妇科医生一起工作,她要求你在常规检查中如果患者出现异常的收缩压(SBP),就通知她。为此,她提供给你一张表格,显示每个年龄段的正常 SBP 值范围。根据这张表格,SBP 随年龄增加是正常的。这引起了你的好奇心。你想知道数据是否与过去常规检查中测量的实际数据一致。为了跟进这一点,你希望查看一些其他数据。幸运的是,你被允许使用患者的数据进行内部分析。
作为第一步,你随机选择一组 33 名患者,他们在至少一次就诊中被医生诊断为健康。从每位选定的患者中,你记录下在常规检查中记录的年龄和血压信息,其中患者被诊断为健康。为了对数据有一个印象,你可以制作一个散点图,其中绘制收缩压(SBP)值与年龄的关系(见图 3.1)。

图 3.1 显示收缩压(SBP)与年龄(女性)关系的散点图
从图 3.1 中,你可以看到大多数 SBP 值都在 100 到 220 之间。散点图显示,与你的上司给你的表格一样,确实存在一种趋势,表明对于健康的女性来说,血压随着年龄的增长而增加。表格给出每个年龄的正常血压范围也是合理的,因为年龄和 SBP 之间的关系远非确定性。(两个年龄相似的健康女性可以有相当不同的血压值。)因此,表格中的信息似乎与你的观察结果一致。
现在,你希望更进一步,寻找一个描述血压如何随年龄变化的模型。独立于血压的个体差异,似乎平均而言,SBP 值以某种方式与女性的年龄呈线性增加。你可以手动通过这些点画一条直线,得到类似于图 3.1 中的线条。我们可以用线性模型来描述这样的直线
y = a ⋅ x + b
其中,a 代表直线的斜率,b是 y 轴的截距。另一种方式(更类似于深度学*)是查看图 3.2 中所示图形的模型。

图 3.2 线性回归模型(y = a · x + b)作为一个全连接神经网络(fcNN)或计算图
这可能是你能想到的最小的全连接网络(fcNN)。同时,它也是第一章中图 1.2 所示更复杂网络的一部分。此外,它图形化地表示了当你有 x 的值并且知道参数值 a 和b时,你需要采取的计算步骤来计算 y 的值:
-
将x乘以a(a ⋅ x)
-
将 1 乘以b(b ⋅ 1)
-
将两个结果相加得到y = a ⋅ x + b
无论你将模型解释为线方程还是作为 fcNN,只有当 a 和 b 的值固定时,你才能使用模型根据给定的 x 值计算y的均值估计。因此,a 和 b 被称为模型的参数。因为参数以线性方式进入模型,所以我们称该模型为线性模型,并且因为只有一个输入特征x,我们称它为简单线性模型。你如何得到描述数据的线性模型中参数 a(斜率)和b(截距)的最佳适当值?
3.1.1 基于损失函数拟合线性回归模型
你可以用你的直觉手动在散点图中的点之间画一条直线。如果你这样做,你可能会得到图 3.1 中显示的类似直线,斜率a = 1,截距b = 100。现在你可以使用这个模型进行预测,比如,例如,对于一个 31 岁的新的病人。
-
如果你使用模型的网络或方程表示,将 31 乘以 1 并加上 100,得到预测的平均 SBP 值为 131。
-
如果你使用图 3.1 中拟合线的图形表示,你从x = 31 垂直向上直到达到拟合线,然后水平向左直到达到 y 轴。
从这里,你可以读取拟合值大约为
。拟合值通常用顶部的帽子表示(参见图 3.3 和表 3.1)。为了方便起见,我们经常省略这些帽子,并且在报告估计或拟合参数值时经常忽略帽子。
如果你要求不同的人进行目测分析,并让他们在图 3.1 中的点之间画一条直线,你可能会得到相似但不完全相同的线性模型。为了决定不同的建议模型如何拟合数据,并最终找到最佳拟合模型,你需要一个清晰和量化的标准。

图 3.3 测量数据点(点状)和线性模型(直线)。数据点和线性模型(垂直线)之间的差异称为残差。你看到右边的“帽子”了吗?
在图 3.3 中,我们展示了实际数据点,y**[i](点状),以及与模型预测值(许多垂直线)之间的差异。统计学家将这些观察值和估计值之间的差异称为残差。
表 3.1 图 3.3 前 5 个条目的数据和导出量,计算时斜率为 1,截距为 100
| x | y | Ŷ = a · x + b | Residual | Squared residual |
|---|---|---|---|---|
| 22 | 131 | 122 | 009 | 081 |
| 41 | 139 | 141 | 0-2 | 004 |
| 52 | 128 | 152 | -24 | 576 |
| 23 | 128 | 123 | 005 | 025 |
| 41 | 171 | 141 | 030 | 900 |
一个著名的规则,最早由高斯在 1790 年代使用,最早由勒让德在 1805 年发表,是平方误差准则。你可能从入门统计学课程中记得这个。简单地选择 a 和 b,使得平方误差之和最小。为什么是平方而不是绝对或立方值?我们将在第四章中稍后讨论这一点。这通常被称为残差平方和(RSS)或简称 RSS。让我们计算图 3.3 中 5 个数据点的 RSS,假设 a = 1 和 b = 100。
将“平方残差”列中的所有项相加,得到 RSS 为 1,586。一般来说,RSS 的计算如下:

RSS 衡量的是观测到的目标值 y**[i] 和模型值 yˆi 之间的偏差。它是一个衡量模型拟合数据好坏的准则。因此,很明显,最佳拟合模型具有最小的 RSS。如果模型完美地拟合所有数据点,RSS 为 0;否则,它大于 0。
你可以从血压示例中的图表(图 3.1)中看到,RSS 不能为 0,因为直线不会穿过所有点。但什么是可接受的 RSS?这几乎是不可能的,因为 RSS 随着用于拟合的额外数据点的增加而增加(这些点不直接位于回归线上)。
但是,使用更多数据时,拟合不会变得更差。如果你将 RSS 除以数据点的数量,你得到的是模型值 yˆi 与观测值 y**[i] 的平均平方偏差。这个量不会系统地随着数据点的数量增加(或减少)。这被称为平均平方误差(MSE)。因为数据点的数量 n 是常数,MSE 只是 RSS 除以 n,对于同一模型,MSE 的最小值与 RSS 相同(见方程式 3.1)。计算出的 RSS 为 1,586,转换为 MSE 为 1,586 / 5,等于 317.2。如果你得到的 MSE 值是每个模型血压值与观测血压值偏差的√(1586/5)=17.8。
通常你不会在每个数据点上犯相同的错误。因为误差对损失的贡献是平方的,一些大的误差需要通过许多小的误差来平衡。均方误差(MSE)不依赖于数据的样本大小,并且比均方和(RSS)有更好的解释,所以它通常更受欢迎。我们最后以自豪的心情来看我们的第一个损失函数——均方误差(MSE):
方程式 3.3

仔细观察公式可以发现,这个损失函数衡量的是可观测的目标值 y**[i] 和模型值 yˆi 的平方偏差的平均值。模型参数 a 和 b 是损失函数的变量,你想要找到使损失最小化的值。除了参数之外,损失函数还依赖于数据 x 和 y。
现在,让我们看看一些代码(参见列表 3.1)。为了确定整个数据集(而不仅仅是 5 个点)的损失值,你需要将模型参数固定到某些值(这里 a = 1 和 b = 100),并使用方程 3.1 中的公式来计算 MSE 损失。(在深度学*中,我们大量使用线性代数来加速计算。)你可以在 Python 中如下编写平方残差向量:
(y − (a ⋅ x + b))² =(y − a ⋅ x − b)²
这个表达式对应于表 3.1 的最后一列,作为一个向量。
注意,我们省略了行号 i。现在,y 和 x 是向量;因此,y − a ⋅ x 也是一个向量。如果你仔细观察这个表达式,可能会对维度的兼容性感到疑惑。如果 y − a ⋅ x 是一个向量,你怎么能向(或从)它添加(或减去)一个标量 b 呢?这被称为广播,基本上发生的事情是 b 也会被转换成一个与 y 长度相同的向量,所有元素都具有值 b。参见 François Chollet 的《Python 深度学*》(Manning, 2017)第 2.3.2 节,mng.bz/6Qde 。下面的列表显示了如何在 Python 中计算均方误差(MSE)。
列表 3.1 使用 NumPy 在 Python 中计算 MSE
*a* = 1
b = 100
y_hat = a*x + *b* ❶
r = (*y* - y_hat) ❷
MSE = np.sum(np.square(r)) / len(*y*) ❸
MSE
❶ 表 3.1 的第 3 列(注意向量表示和广播)
❷ 表 3.1 的第 4 列
❸ 从表 3.1 的第 5 列计算出的 MSE(在数据集的数据点数量上创建、求和并除以数据点的数量)。
实践时间 打开笔记本 mng.bz/oPOZ 并逐步执行代码,以了解如何模拟一些数据,将其拟合到线性回归模型中,并确定具有指定参数值的线性模型的 MSE。你很快就会到达笔记本中的第一个练*,由笔形图标指示。在这个练*中,我们要求你尝试不同的参数值。目标是手动找到产生最小的 MSE 的 a 和 b 参数值。 |
|---|
如前所述,你可以使用笔记本中 33 位已知年龄和测量血压的妇女的数据。有了这些信息,你可以轻松地计算具有 a = 1 和 b = 100 的建议模型的损失。
让我们找到最佳拟合线性模型!在图 3.1 中,你看到在这个例子中,你找不到穿过所有数据点的直线。因此,不可能将损失减少到零。相反,你想要找到使 MSE 最小的 a 和 b 参数值。
简单线性回归模型中最佳参数估计的闭式解
现在,你将看到一个公式的推导,该公式允许你直接从训练数据中计算最优参数值。找到损失函数的最小值需要找到使一阶导数为 0 的变量值。这需要解决以下两个关于 a 和 b 的条件:

你可以将计算作为练*来做,这将导致一个包含两个方程和两个未知数(参数 a(斜率)和 b(截距))的线性方程组。

一旦你至少有两个具有不同 x**[i] 值的数据点,你就可以通过将训练数据的值代入公式来直接确定解,从而得到参数值。
这被称为闭式解。在之前的公式中,a 和 b 上方的帽子表示这些值是从数据中估计出来的。这个结果很简单。它允许你直接计算最优参数估计值 â 和 bˆ,以最小化均方误差(MSE)。为此,你只需要数据(x 和 y)。在我们的血压示例中,这给出了斜率的值 â = 1.1 和截距的值 b̂ = 87.67。
对于这个例子,推导出确定最优参数值的公式相当简单(见侧边栏)。这仅因为模型具有简单的损失函数,并且我们处理的数据量很少。如果你处理的是复杂的模型,如神经网络(NNs),则无法直接从训练数据中计算优化损失的参数(如我们在侧边栏中所做的那样)。阻碍参数直接计算的另一个原因是数据集很大。在深度学*中,通常数据点太多,没有闭式解。在这种情况下,你需要使用迭代方法。为此,我们使用梯度下降,我们将在下一节中解释。
3.2 梯度下降法
梯度下降是机器学*中最常用的迭代优化方法,也是深度学*(DL)中几乎所有优化的常用方法。你将学*这种方法是如何工作的,首先是一个参数,然后是两个参数。请放心,同样的方法也可以用来优化深度学*模型中的数百万个参数。
3.2.1 具有一个自由模型参数的损失
为了让你对梯度下降有一个清晰的了解,我们想从这样一个特殊情况开始,即你只需要优化一个模型参数。为此,我们将继续使用我们的血压示例,并假设你以某种方式知道截距的最优值为 b = 87.6。为了最小化损失函数,你只需要找到第二个参数 a 的值。在这种情况下,我们之前讨论的损失函数如下所示:

基于这个方程,很容易从训练数据中计算每个提议的参数 a 对应的损失 x 和 y。
梯度下降的直觉
图 3.4 绘制了损失与参数 a 的值之间的关系。在图中,你可以看到对于 a 的值,损失的最低点略大于 1。

图 3.4:损失(见方程 3.1)与自由回归模型参数 a 的关系图,其中 b 固定为最优值。在 a = 0 的位置,切线以虚线绘制;在 a = 2 的位置,切线以虚线绘制。
你如何系统地找到使损失最小的参数 a 的值?想象一下,损失函数是一个一维景观,其中来自一维世界的盲探索者想要到达最小值,这可能对你有所帮助。如果景观像图 3.4 中的损失一样呈碗状,这将是一项相当容易的任务。即使探索者是盲目的,只能探索局部环境,也很清楚需要朝向下方指示的方向行走。但是,这与值 a 有什么关系呢?这取决于局部斜率。如果在图 3.4 中探索者的位置局部斜率为负(见 a = 0 处虚线切线的负斜率),则朝向更大值 a 的方向迈步会指向最小值。在具有正局部斜率的位置(见 a = 2 处虚线切线的正斜率),朝向更小值 a 的方向迈步会指向最小值。
函数的导数给出了切线的斜率。因为我们关注的是依赖性损失,所以这个导数被称为梯度,表示为 grad**[a](Loss)。其符号(正或负)指示损失函数增加的方向。
你可以在图 3.4 中看到,曲线在接*最小值时更平缓,而在远离该点的点处变得更陡峭。斜率越陡,梯度的绝对值就越大。如果你的探索者是一位数学家,你应该提供这样的建议:在 grad**[a](Loss) 函数的负号指示的方向上迈步。步长将与梯度的绝对值成比例。当探索者远离最小值,斜率较陡时,应该迈大步。为了避免越界,当接*最小值,斜率变得平缓时,需要采取更小的步长。只有在最小值处,斜率才为零。
你如何选择比例因子,即学*率 η(eta)?这听起来可能有些挑剔,但很快你就会看到,正确选择学*率(η)可能是使用梯度下降进行成功优化的最关键因素。
盲目漫游的数学家的例子应该能帮助你培养如何系统地调整参数 a 以到达损失函数最小值的感觉。这里提供的冗长描述可以转化为关于如何迭代改变参数值的清晰数学指令,这在梯度下降中被称为更新规则。
梯度下降中的更新规则
这是根据梯度下降优化过程得到的参数更新公式:
a**[t+][1] − a**[t] − η ⋅ grad**[a](Loss) 方程 3.2
这个更新规则总结了梯度下降的迭代过程:你首先对参数值 a(例如,a[0] = 0)进行随机猜测。然后确定损失函数关于参数 a 的梯度值。grad**[a](Loss)中的负号表示参数 a 需要改变的方向,以便朝着减小损失的方向前进。(你将在第 3.4 节中看到如何计算梯度。)更新步长与梯度的绝对值和学*率η(eta)成正比。方程 3.2 确保你朝着减小损失的方向迈出一步。你重复这一步对参数 a 的逐步更新,直到收敛。使用找到的参数 a 模型会导致最适合数据的模型。
在更新公式(方程 3.2)中,你应该选择多大的学*率η(eta)?你可能倾向于使用大的学*率,以便更快地达到最小值。
实践时间 再次打开笔记本mng.bz/oPOZ,在练* 1 之后继续编写代码。这让你看到如何通过封闭公式确定斜率和截距的值,以及如何实现梯度下降方法来调整一个参数,当第二个参数固定时。在笔记本的末尾,你会找到一个第二个练*,你的任务是研究学*率对收敛的影响。做一下。 |
|---|
你观察到了什么?你可能看到,随着学*率的增大,损失函数越来越大,而不是减小。它最终在几次迭代后达到 NaN 或无穷大。发生了什么?
看一下图 3.5,它显示了损失与参数 a 的依赖关系,即我们血压示例中的斜率。(截距固定在其最优值 87.6。)剩下的唯一任务是找到参数 a 的最优值。这是通过梯度下降来完成的。在图 3.5 中,你可以看到从第一次猜测a[1] = 0.5 出发,使用三种不同的学*率时 a 值的发展情况。

图 3.5 展示了方程 3.1 的损失与自由回归模型参数 a 的曲线图。它显示了以a = 0.5 为起始点,不同学*率下的 5 步梯度下降的结果。当学*率为 0.00010 时,最小值在 5 步内大致达到,没有超过最小值。当学*率为 0.00030 时,最小值在大约 5 步后达到,但两次超过了最小值的位置。当学*率为 0.00045 时,参数 a 的更新总是超过最小值的位置。在这种情况下,a 的更新值越来越远离最小值。相应的损失无限制地增长。
在图 3.5 中,你可以看到学*率是一个关键的超参数。如果你选择一个过小的值,需要很多更新步骤来找到最优模型参数。然而,如果学*率过大(见图 3.5,右面板),则无法收敛到损失最小的最优参数值 a 的位置。如果学*率过大,损失会随着每次更新而增加。这会导致数值问题,导致 NaN 或无穷大。当你观察到损失变为无穷大或 NaN 时,有句话叫做,“保持冷静,降低学*率。”下次当你看到训练集中的损失越来越高而不是越来越低时,尝试降低学*率(最初将其除以 10 是一个好的猜测)。
3.2.2 具有两个自由模型参数的损失
现在将参数b固定在其最优值的某种人为条件移除,同时优化两个参数a和b的损失函数。我们已经计算出了最优值 a = 1.1 , b = 87.67 。在图 3.6 中,你可以看到血压例子中不同值 a 和 b 的损失函数。

图 3.6 血压数据不同值 a 和 b 的损失函数。你可以看到损失函数的形状更像是峡谷而不是碗。底部的等值线表示损失值相等的位置。
如果你回忆一下图 3.4 中 1D 损失函数的抛物线形状,你可能会对 2D 损失表面不是碗形,而更像峡谷感到惊讶。实际上,在大多数线性回归的例子中,损失表面看起来更像峡谷,或者更准确地说,是一个拉长的碗。尽管这些损失表面确实有最小值,但在某些方向上是平的。类似峡谷的损失表面很难优化,因为这些需要许多优化步骤才能达到最小值(本章后面将给出一个更详细的例子)。
让我们通过一个线性回归的模拟例子来解释变量 a 和b的优化,这个例子有一个形状良好的碗形损失函数,而不是峡谷形。稍后,你会在血压例子中看到损失函数类似峡谷的形状。
实践时间 打开笔记本 mng.bz/nPp5 并完成它,直到标题为“梯度”的部分。你将看到如何模拟一个简单线性回归拟合的数据,其中损失函数具有碗形形状。图 3.7 是用这个笔记本的代码生成的,显示了模拟数据集的损失表面。实际上,它看起来像是一个碗。 |
|---|

图 3.7 对于模拟数据的不同值a和b的损失函数。你可以看到损失函数的形状更像一个碗。底部的等高线表示损失值相等的位置。大点表示起始值的位置(a 和b = 3)。右侧显示了该区域的放大图。
通过梯度下降的优化过程与一个变量保持固定的情况相似。让我们从a = 3 和b = 3 的初始值开始;该位置的损失函数是 6.15。优化的第一步是找到哪个方向显示最陡下降,然后在该方向上向下迈一小步。
如果你非常接*初始起始点,你会看到碗看起来像一个盘子;这对于碗上的任何点都是正确的。如果你被要求从a = 3 和b = 3 开始向下迈一小步,你可能同意应该朝向中心的方向。你如何计算这个方向?让我们用一个弹珠来确定这个方向。
我们将弹珠放在右上角(a = 3,b = 3)。弹珠只能沿着作用在其上的合力方向滚动。你可能还记得高中物理,合力是基于不同方向上作用的力(见图 3.8)。当某个方向上的力变强时,该方向上的斜率就越陡。我们知道方向 a 的斜率是 grad_a。因此,弹珠在方向 a 上的力与该方向上的负斜率或梯度(如在 1D 示例中)成正比:
f**[a] ∝ − grad**[a]
对于物理爱好者,力由弹珠的质量乘以重力加速度(ɡ ≈ 9.81 m / s²)乘以 grad_a 给出。同样,方向b上的力是
f**[b] ∝ − grad**[b]
弹珠上的总力由这个方程给出:

如果你现在释放弹珠,它将沿着最陡下降的方向滚动一步,这通常不是沿着单个轴 a 或b(见图 3.8)。学*率η乘以梯度给出步长。对于具体例子,这导致一个新的坐标,其更新公式为:
a**[t+1] = a**[t] - η ⋅ grad**[a]
b**[t+1] = b**[t] - η ⋅ grad**[b] 方程式 3.3

图 3.8 拖拽弹珠的力和最陡下降方向的结果方向
这表明,为了确定 2D 参数空间中的新位置,你需要计算一阶导数两次。首先,你将b固定在 3,只改变 a。用更数学的话来说,“你计算损失函数关于 a 的偏导数。”你保持参数b不变,对损失函数(均方误差)关于 a 求导,得到:

这与一维情况下的公式相同(见方程式 3.1)。你可能已经注意到,我们在平方误差中交换了观测到的y和拟合值 ax + b。然而,这既没有改变损失值,也没有改变梯度值,但它简化了符号和微分。为了确定损失对 b 的偏导数,你可以保持参数 a 不变,并对b进行微分:

现在,你可以将这些数字代入更新公式(见方程式 3.3),以获得a和b的新值。如果你选择足够小的学*率,你将达到损失函数的最小值。如果你有一个类似碗的结构,你将很快达到最小值。但对于具有平坦最小值的峡谷状景观,你最终需要付出更多努力才能达到最小值——这需要更长的时间。
实践时间 再次打开笔记本 mng.bz/nPp5 并从“梯度”部分继续工作。你会看到如何通过梯度公式计算损失相对于参数的梯度,以及如何通过更新公式更新参数值。要手动执行梯度下降法,你需要重复更新步骤,直到你接*损失函数的最小值。提供的代码使用笔记本开头模拟的数据。在笔记本的末尾,你将达到由笔符号指示的练*,你的任务是手动执行血压数据的梯度方法,并比较所需步骤数与模拟数据的情况。你会发现血压情况下需要更多的步骤。损失函数具有类似峡谷的形状,而不是模拟数据中的类似碗的形状。 |
|---|
注意,虽然使用弹珠探测最速下降的方向,但真实的弹珠会遵循不同的路径。这是因为真实的弹珠会聚集一些动量,因此在移动时不会遵循最速下降。有一些高级方法包括在梯度下降中引入动量的概念,但标准梯度下降没有动量。这在优化中常常被误解。
与简单的 1D 示例类似,学*率是一个关键参数。希望你在练*中已经注意到了这一点,并正确设置了它。另一个问题是,无论起始值如何,你是否总能达到最小值。回想一下你在做什么:你从一个特定的位置开始,计算局部最陡下降的方向,然后朝那个方向迈出一步。步长取决于陡峭程度和学*率。下降越陡峭,梯度越大,对应的步长也越大。接*最小值时,曲线变得越平坦,梯度越小。这意味着你应该采取小步以避免超调。图 3.9 非常显著地展示了这一点,对于更复杂的景观。

图 3.9 一个在梯度下降中徘徊的人。在某个位置,他寻找最陡下降的方向。请注意,这个徘徊者看不到整个景观;他只知道自己的高度和局部最陡下降的方向。然后他沿着最陡下降的方向稍微下降一点(取决于学*率和陡峭程度)。你相信他会到达山谷吗?这幅图灵感来源于斯坦福深度学*课程(cs231n)。
关于你的简单线性回归问题,如果你看图 3.7,你会看到损失函数的形状像一个碗,只有一个最小值。我们称之为凸问题。很明显,并且可以证明,无论起始值如何,只要学*率足够小,梯度下降(在凸问题中)总能找到(单个)最小值。
一个深度学*(DL)模型比线性回归模型要灵活得多。损失函数看起来比一个高维的碗要复杂得多。它更像是图 3.9 中展示的带有徘徊者的景观,有几个局部最小值(就像湖泊)。因此,简单的梯度下降算法能够很好地拟合这些复杂的深度学*模型,这真是一个奇迹。一些最*的研究表明,通常深度学*模型损失景观中的所有最小值都是同样好的,因此陷入局部最小值并不是真正的问题。
3.3 特殊深度学*技巧
简单的线性回归已经包含了训练深度学*模型所需的所有基本成分。你只需要再了解三个特殊的技巧:
-
使用小批量梯度下降在具有数百万数据点的典型深度学*设置中计算损失函数
-
使用随机梯度下降(SGD)的变体来加速学*
-
自动化微分过程
前两个技巧很简单;第三个稍微复杂一些。但别担心,我们会慢慢来。让我们先从简单的开始。
3.3.1 小批量梯度下降
第一个技巧被称为小批量梯度下降。它允许你在具有数百万数据点的典型深度学*设置中计算损失函数。正如你所见,损失函数依赖于所有数据点:

同样适用于梯度。在深度学*中,你通常在图形处理单元(GPU)上执行计算。这些设备内存有限,通常太小,无法容纳所有数据。一种解决方案是使用随机选择的数据点子集(一个迷你批次)来*似计算损失函数及其梯度。使用迷你批次计算出的梯度有时高于使用所有数据点时得到的梯度,有时则低于。这就是为什么迷你批次梯度下降也被称为随机梯度下降(SGD)。
通常情况下,使用这种迷你批次程序计算出的梯度与使用所有数据点计算出的梯度没有系统地偏离。统计学家称这为无偏估计的梯度。当使用这种无偏估计时,参数的值会像以前一样更新。你甚至可以使用大小为 1 的迷你批次,并在每个训练实例之后更新权重。事实上,有些人将 SGD 的名称限制在每次只使用一个数据点来计算梯度的情况下,并将描述的方法称为迷你批次梯度下降。
深度神经网络可能有数百万个参数,而且已知损失景观的形状远非看起来像是一个光滑的碗(它是一个非凸问题)。对于非凸问题,梯度下降过程可能会陷入局部最小值,就像图 3.9 中的漫游者最终在湖里弄湿了,而不是达到山谷底部的全局最小值。尽管如此,SGD 在拟合深度学*模型方面效果很好。
到目前为止,还没有完全理解为什么简单的梯度下降程序在深度学*中效果如此之好。有人提出,其中一个原因是,你不需要达到绝对的全局最小值,但许多其他最小值也足够好。甚至有人提出,深度学*中的梯度下降倾向于找到对未见的新数据泛化良好的解决方案,比全局最小值做得更好。深度学*仍然处于起步阶段;它有效,但我们不知道为什么。迷你批次梯度下降是深度学*秘方的主要成分之一。
3.3.2 使用 SGD 变体加速学*
第二个技巧稍微不那么引人注目,它处理 SGD 的变体以加速学*。如果你使用的是血压数据而不是模拟数据来做练*,你可能会想知道对于像线性回归这样的简单问题,是否真的需要 100,000 次迭代。有一些更高级的优化方法也只计算局部梯度,不使用进一步的导数,但进行了一些更或更聪明的技巧。这些方法应用了使用先前迭代值的启发式方法来加速性能。你可以参考弹珠,其中动量取决于先前步骤中最陡峭梯度的方向。
两种突出的算法是 RMSProb 和 Adam。它们包含在所有深度学*框架中,如 TensorFlow、Keras、PyTorch 等。这些方法与 SGD 在本质上没有区别,但在加速学*过程中非常有用,这是通过考虑最后几次更新来实现的。但为了理解深度学*的原理,你不需要知道这些 SGD 变体的细节。因此,我们只提供一个参考,它出色地解释了这些技术:distill.pub/2017/momentum/。
3.3.3 自动微分
第三个技巧是自动微分。虽然可以手动计算线性回归的梯度,但对于深度学*模型来说,这实际上是不可能的。幸运的是,存在几种自动进行微分的方法。这种方法在 Chollet 的《Python 深度学*》一书中得到了很好的解释:
在实践中,一个神经网络函数由许多连在一起的张量操作组成……,每个操作都有一个简单、已知的导数。
微积分告诉我们,可以使用以下恒等式来推导这样的函数链,这个恒等式被称为链式法则:
(f ’(g(x)))’ = f '(g(x)) ⋅ g '(x)
当将链式法则应用于计算神经网络梯度值时,我们得到一个称为反向传播(有时也称为逆模式微分)的算法。反向传播从最终的损失值开始,从输出层反向工作到输入层,在途中迭代地应用链式法则来计算损失相对于每个模型参数的梯度。
你可以使用这些梯度通过使用更新规则来计算更新后的参数值。请注意,本章前面介绍过的更新公式(见方程式 3.2)对每个参数都是有效的,无论模型有多少个参数。基本上,你需要的是每一层中函数的梯度以及链式法则来将这些梯度粘合在一起。现代深度学*框架知道如何自动应用链式法则。它们也知道神经网络中使用的基函数的梯度。在拟合深度学*模型时,你通常不需要关心这些细节。但让我们仍然这样做,以便了解底层发生了什么。
3.4 深度学*框架中的反向传播
在本书的大部分内容中,你使用高级库 Keras。这个库抽象掉了讨厌的细节,让你可以快速构建复杂模型。但正如每个建筑师都应该知道如何砌砖以及建筑力学的限制一样,深度学*实践者应该理解底层原理。所以,让我们动手实践吧!
深度学*库可以根据它们处理自动微分的方式分组。解决梯度下降所需梯度计算的两种方法如下:
-
静态图框架
-
动态图形框架
类似于 Theano 的静态图形框架是深度学*中最先使用的框架。但它们使用起来有点笨拙,目前已被 PyTorch 等动态框架所取代或增强。我们首先描述静态框架,因为它能给您一个很好的内部工作原理的印象。TensorFlow v2.x 可以处理这两种方法。然而,在 TensorFlow v2.x 中,静态图形被隐藏得相当多,您通常不会遇到这些。因此,我们现在在配套的笔记本 mng.bz/vxmp 中切换回 TensorFlow v1.x。请注意,这个笔记本在提供的 Docker 容器中无法运行。我们建议您在 Colab 中运行它。如果您想在 TensorFlow v2.x 中查看计算图,可以使用笔记本:mng.bz/4AlR。然而,正如所说,计算图在 TensorFlow v2.0 中隐藏得更多一些。
3.4.1 静态图形框架
静态图形框架使用两步程序。在第一步中,用户定义计算,例如乘以 x,加 b,并调用这个 y_hat_,等等。在底层,这会产生一个名为计算图的结构的结构。列表 3.2 的第一步显示了构建是如何进行的,但代码尚未执行。代码描述了使用 TensorFlow v1.x 构建线性回归问题的静态图形的构建阶段(也请参阅笔记本 mng.bz/vxmp)。
列表 3.2 TensorFlow 中计算图的构建
# *x*,y are one dimensional numpy arrays
# Defining the graph (construction phase)
tf.reset_default_graph() ❶
a_ = tf.Variable(0.0, name='a_var') ❷
b_ = tf.Variable(139.0, name='b_var') ❷
x_ = tf.constant(x, name='x_const') ❸
y_ = tf.constant(*y*, name='y_const') ❸
y_hat_ = a_*x_ + *b*_ ❹
mse_ = tf.reduce_mean(tf.square(*y*_ - y_hat_)) ❹
writer = tf.summary.FileWriter("linreg/",
tf.get_default_graph()) ❺
writer.close() ❺
❶ 从头开始构建一个新的图形
❷ 具有初始值的变量可以在以后进行优化。我们这样命名它们,以便在图形中看起来更美观。
❸ 固定的张量常数,它们持有数据值
❹ 符号操作在计算图中创建新的节点。
❺ 为可视化编写图形
列表 3.2 定义了 TensorFlow 中线性回归问题的静态计算图的构建。在定义了图形之后,它可以被写入磁盘。这是在列表的最后两行中完成的。
注意:Google 提供了一个名为 TensorBoard 的组件,您可以在其中可视化计算图。要了解更多关于 TensorBoard 的信息,您可以参考 Chollet 的书籍,链接为 mng.bz/QyJ6。
图 3.10 显示了计算图的输出。此外,我们还添加了一些注释(用箭头表示)。如果您想重现它,只需遵循笔记本 mng.bz/vxmp 中的步骤。
向计算图致敬,冥想,感受张量的流动!TensorFlow 从下到上布局计算图。让我们从左下角的变量 a_(在图中命名为 a_var)开始。这对应于列表 3.2 的第 2 行。这个变量乘以常数一维张量(向量)x_(在图中命名为 x_const),它包含 33 个值。这是在计算图的 Mul 节点中完成的。这是在列表 3.2 的第 6 行的a_*x_小部分中定义的。

图 3.10 使用列表 3.2 构建的静态图,在 TensorBoard 中显示,并带有一些额外的注释(由箭头指示)。
通常,在图 3.10 中,边是流动的张量,节点是像乘法这样的操作。乘法之后,a 乘以 x 的结果仍然是一个包含 33 个值的 1 维张量。沿着图向上,b 被添加,y 被减去,最后表达式被平方。当进入平均节点时,我们仍然有一个包含 33 个值的 1 维张量,然后这些值被求和并除以 33。
图 3.10 中的一个细节是来自左侧的常数 Const。通常,深度学*中的张量比一维张量更复杂;例如,二维张量(矩阵)。然后平均节点需要知道是按行还是按列平均。
在你遍历了计算图之后,现在让我们让数值a = 0 和b = 139 通过图流动(139 是血压的平均值,斜率a = 0 意味着模型预测每个女性的这个平均值,而不考虑她的年龄)。为此,我们需要图的实例化/具体化——在 TensorFlow 术语中,会话。下一个列表显示了这一点。
列表 3.3 让张量流动,前向传播
sess = tf.Session() ❶
res_val = sess.run(loss_, {a_:0,b_:139}) ❷
print(res_val) ❸
sess.close() ❹
❶ 开始会话(获取内存和其他资源)
❷ 让变量a = 0 和b = 139 通过图流动,并将数值存储在 res_val 中
❸ 打印 673.4545
❹ 最后,始终关闭会话。
现在计算图已经定义,TensorFlow 计算梯度变得很容易。这里的损失是均方误差(MSE)(见方程 3.1),它在图的顶部计算。你在图 3.10 中显示的计算图中看到,MSE 可以通过执行几个基本操作来计算:加法、减法、乘法和平方。对于梯度下降更新规则,你需要 MSE 相对于a和b的梯度。微积分中的链式法则保证可以通过沿着图回溯来计算。你回溯到 a(或 b)的过程中经过的每个节点都会贡献一个额外的因子。这个因子是节点输出相对于其输入的导数。它被称为局部梯度。
现在,您将逐步看到如何通过一个具体的例子来完成这个过程。这个例子表明,当您通过梯度更新规则降低均方误差(MSE)时,拟合效果会更好。此外,您可以通过两种方式计算梯度:直接通过方程 3.3 和 3.4,以及通过计算图中的反向传播逐步计算。
我们通过使用随机梯度下降法(SGD)的血压示例来说明这个拟合过程。为了使讨论简单,我们使用批大小为 1(选择一个训练示例)并手动进行一步更新。我们从平均血压 139 和斜率 0(b = 139 和斜率 a = 0)开始;请参见图 3.11 中的实线。对于 SGD 的第一轮,我们随机选择一个患者,比如编号 15。这位患者是 x = 58 岁,血压为 y = 153(请参见图 3.11 中的唯一一个填充数据点)。初始模型预测该年龄的血压为 139。通过数据点和模型预测之间的垂直线来可视化剩余量。当前数据点的损失是剩余量的平方。我们现在更新模型参数 a 和 b 以降低所选患者的损失。为此,我们使用更新规则(见方程 3.2),我们需要计算损失相对于两个模型参数 a 和 b 的梯度。
让我们以两种方式计算梯度。首先,您使用公式计算梯度,这些梯度是在 n = 1,a = 0,b = 139,x = 58,和 y = 153 的情况下得到的:

和


图 3.11 展示了我们只在 SGD 拟合的第一轮中使用一个数据点的血压数据集。这个选择的数据点以填充点的方式可视化。初始线性模型由实线表示,斜率 a = 0,截距 b = 139。虚线表示第一次更新后的线性模型,斜率 a = 0.3,截距略大于 b = 139。
知道这些关于均方误差损失相对于参数 a 和 b 的梯度值都是负值,这告诉您需要增加参数值以降低损失,以便更接*选择的数据点。您可以通过查看图 3.11 来验证这一点。模型值在 x = 58 的位置应该上升以接*观察到的值 y = 158。这可以通过两种方式实现:通过增加截距和增加斜率。我们通过使用方程 3.2 中的公式,学*率 η = 0.0002 来更新 a 和 b 的值,得到:
b**[t+1] = b**[t] − η ⋅ grad**[b] = 139 − 0.0002 ⋅ ( −28) = 139.0056
和
a**[t][+1] = a**[t] − η ⋅ grad**[b] = 0 − 0.0002 ⋅ ( −1624) = 0.3248
图 3.11 中的虚线显示了具有更新值 a 和 b 的结果线性模型。现在让我们通过使用计算图以 TensorFlow 的方式更新参数值,然后检查我们是否得到相同的结果。

图 3.12 对于具有一个数据点(x = 58, y = 153)和初始参数值 a = 0 和 b = 139 的混凝土血压示例的前向和反向传播。图的左侧显示了前向传播中的流动值;右侧显示了反向传播中梯度的流动值。
你首先开始计算所谓的正向传播中的损失。中间结果写在图 3.12 的左侧。你逐步进行计算,并给中间项命名以跟踪它们。(你将在反向传播中需要它们。)你从左下角开始,将 a 乘以 x:ax = a ⋅ x = 0 ⋅ 58 = 0。然后你加上 b = 139,得到 139。继续向上图工作,你减去 y = 153,得到 r = −14。然后你平方它以得到 s = 196。
在这种情况下(n = 1),平均操作不起作用。它只是恒等式(在图 3.12 中用 I 表示),最终损失是 196。
让我们通过反向路径来计算损失函数相对于参数的偏导数。你需要跟踪图 3.12 中显示的中间量(s, r, abx, 和 ax)。因此,你根据你在返回参数 a 和 b 的过程中传递的这些中间量来构建 MSE 损失的偏导数。为了确定损失函数 MSE 相对于 b 的偏导数,你只需沿着图从 MSE 走到 b 并在返回的过程中乘以局部梯度。局部梯度是某个操作的结果相对于其输入值的导数:

让我们验证这确实是 MSE 相对于 b 的梯度。我们将像 ∂s 这样的符号视为变量(这会让真正的数学家感到震惊),并在公式的右侧取消这些变量:

你可以看到偏导数乘积中的项是局部梯度。为了计算局部梯度,你需要基本操作(如图 3.13 所示)的导数。

图 3.13 线性回归示例中反向传播的局部梯度。圆圈包含你需要进行的操作,它们是平方 (^),加法 (+),减法 (-) 和乘法 (·)。
让我们使用当前值(x = 58, y = 153, a = 0, 和 b = 139)进行计算,并确定计算 MSE 相对于 b 的偏导数所需的项:

你从计算图的顶部开始:

下一个局部梯度是

这给出了沿b轴的损失梯度:

将局部梯度相乘,得到的值与封闭公式相同。同样,您可以计算关于 a 的梯度。然而,您不需要从头开始沿着图走,而是可以走捷径,从-28 的值开始,乘以∂ax / ∂a = 58,得到-1,624。这是关于 a 的 MSE 梯度的预期值。当 TensorFlow(或任何使用静态图的深度学*框架,如 Theano)计算梯度时,在底层使用这种静态图方法。
实践时间 打开笔记本 mng.bz/XPJ9 并验证局部梯度的数值。请注意,我们仅为了演示反向传播过程,计算了所有中间值和梯度。如果您想使用梯度下降法获取拟合的参数值,您可以使用笔记本中的示例代码,如mng.bz/vxmp所示。 |
|---|
通常您不需要手动在梯度下降更新公式中应用梯度。相反,您可以使用tf.train.GradientDescentOptimizer()之类的优化器。列表 3.4 显示了图的优化器。调用它一次将执行一次给定学*率的梯度下降步骤。这显示在笔记本mng.bz/vxmp的末尾。
列表 3.4 TensorFlow 中计算图的拟合
train_op_ = tf.train.GradientDescentOptimizer(
learning_rate=0.0004).minimize(loss_) ❶
with tf.Session() as sess: ❷
sess.run(tf.global_variables_initializer()) ❸
for i in range(80000): ❹
_, mse, a_val, b_val =
sess.run([train_op_, loss_, a_, b_]) ❺
if (i % 5000 == 0): ❻
print(a_val, b_val, mse)
❶ 向图中添加一个额外的操作以优化 MSE
❷ 在退出时关闭会话并释放所有分配的资源
❸ 初始化所有变量
❹ 设置梯度下降步骤数为 80,000
❺ 运行 train_op 和 mse_、a_、b_
❻ 限制打印每 5,000 条记录
如列表所示,这需要相当多的代码。因此,开发了更高级的框架与 TensorFlow 一起工作;Keras 就是这样一种框架,它可以在 TensorFlow(和其他)深度学*库之上工作。Keras 也包含在 TensorFlow 发行版中,因此您不需要安装任何东西就可以使用它。Chollet 的《Python 深度学*》一书详细介绍了 Keras。
您可以将线性回归视为一个简单的 NN,它有一个没有应用激活函数的密集层。(Keras 使用“线性”一词表示没有激活。)列表 3.4 的第二行,Dense 层,包含了使用参数 a 和偏置参数b对x进行加权线性组合的指令,以确定输出层中唯一节点的输出 ax + b。此外,您可以使用四行 Keras 代码构建整个图和优化器,如下所示。
列表 3.5 Keras 中计算图的构建
model = Sequential() ❶
model.add(Dense(1,input_dim=1, activation='linear')) ❷
opt = optimizers.SGD(lr=0.0004)
model.compile(loss='mean_squared_error',optimizer=opt)
❶ 开始构建模型
❷ 添加一个没有激活函数的单个密集层
在这个列表中添加一个没有激活函数的密集层是线性回归(也参见列表 2.1 和图 3.2)。下面的列表展示了 Keras 中用于拟合计算图的训练过程。
列表 3.6 在 Keras 中拟合计算图
for i in range(0,80000):
model.fit(x=x,y=y,batch_size=33,
epochs=1,
verbose = 0)
a,b=model.get_weights()
if i % 5000==0:
mse=np.mean(np.square(model.predict(x).reshape(len(x),)-y))
print("Epoch:",i,"slope=",a[0][0],"intercept=",b[0],"MSE=",mse
实践时间 打开笔记本 mng.bz/yyEp 以查看列表 3.5 和 3.6 的完整代码。大胆尝试并玩转这些代码。 |
|---|
3.4.2 动态图框架
静态库的主要问题是由于两步程序(首先构建图然后执行它),调试相当繁琐。在动态图框架中,图是即时定义和评估的。因此,你可以在代码的每个点上访问真实的数值。这在调试时具有巨大的优势。此外,静态图的一个缺点是,你不能包含条件语句和循环,以便对不同的输入动态响应。Chainer 和 Torch 是最早允许这种动态计算的框架之一。Torch 的缺点是宿主语言是 Lua,这是一种不太常用的编程语言。2017 年,Torch 从 Lua 转换为 PyTorch,许多深度学*从业者开始使用这个框架。作为回应,TensorFlow 现在也包含了动态图的可能性,称为 eager 执行。
列表 3.7 显示了使用 eager 执行的线性回归问题的 TensorFlow 代码(也参见笔记本 mng.bz/MdJQ)。框架不再需要构建静态图。你可以在任何点停止,每个张量都有一个与之关联的值。在 eager 模式下,TensorFlow 仍然需要计算单个操作的梯度,但这是在执行代码的同时并行的。TensorFlow 在内部将用于计算梯度的中间值存储在一个名为 tape 的实体中。
列表 3.1 使用 TF.eager 的线性回归
a = tf.Variable(0.) ❶
b = tf.Variable(139.0) ❶
et*a* = 0.0004 ❷
for i in range(80000):
with tf.GradientTape() as tape: ❸
y_hat = a*x + *b* ❸
loss = tf.reduce_mean((*y*_hat - *y*)**2) ❸
grad_a, grad_b = tape.gradient(loss, [a,b]) ❸❹
a.assign(a - eta * grad_a) ❸❺
b.assign(b - eta * grad_b) ❸❻
if (i % 5000 == 0): ❸
... ❼
❶ 将 a 和 b 设置为变量,以便以后可以优化它们
❷ 设置学*率
❸ 记录在此范围内计算梯度所需的所有信息
❹ 计算相对于 a 和 b 的损失
❺ 应用更新公式 3.3 并将 a 赋予新的值
❻ 打印代码被省略
❼ 应用更新公式 3.3 并将 b 赋予新的值
如你所见,代码的构建和执行之间没有分离。with tf.GradientTape() as tape: 这行代码告诉 TensorFlow 使用所谓的带子机制跟踪所有微分。由于存储中间值需要一些时间,你只希望在真正需要的时候才这样做。这对于调试和开发复杂的网络来说是非常好的。然而,这也需要付出代价。特别是如果你使用了很多小操作,即时方法可能会变得相当慢。但是有一个解决方案。如果你把所有相关的代码放在一个函数中,并用 @tf.function 装饰器装饰这个函数,那么函数中的代码就会被编译成一个图,然后运行得更快。
在 TensorFlow v2.0 中,你拥有了开发时的即时执行和在生产中的基于图框架的最佳结合。以下笔记本包含了一个如何使用 @tf.function 的示例;更多细节可以在 www.tensorflow.org/guide/function 找到。
除了讨论过的笔记本外,我们还提供了一个使用 Python 中的 autograd 库来自动计算梯度的笔记本;请参阅笔记本 mng.bz/aR5j 。在下一章中,我们将真正开始我们的旅程,并遇到第一个可以从中推导出损失函数的原理——最大似然原理(MaxLike)。
摘要
-
线性回归是所有参数模型的母亲,也是你可以想到的最小的神经网络(NNs)之一。
-
你可以通过最小化一个损失函数来拟合参数模型,该损失函数量化了模型与数据之间的偏差。
-
均方误差(MSE)是回归模型的一个合适的损失函数。
-
梯度下降是一种寻找最小化损失函数的参数值的方法。梯度下降是一种简单、通用的方法,只要损失函数是可微的,你就可以用它来处理所有类型的参数模型。
-
使用梯度下降,每个参数都是迭代和独立地从其他参数中更新的。这需要确定损失函数相对于每个参数的梯度。此外,你还需要定义一个学*率。
-
调整学*率(既不要太低也不要太高)对于成功拟合至关重要。在使用深度学*(DL)时,你可以执行梯度下降的随机版本(SGD),它基于数据的一个随机子集(小批量)来估计梯度,而不是使用所有数据。
-
深度学*(DL)框架,如 TensorFlow 或 Keras,使用反向传播来确定所需的梯度。
-
深度学*中使用的优化器是 SGD 的变体,通过利用过去的梯度来加速学*过程。
第二部分. 概率深度学*模型的极大似然方法
本书第二部分专注于使用神经网络(NN)作为概率模型。你可能还记得第一章中非概率模型和概率模型之间的主要区别。非概率模型只输出一个关于结果的最佳猜测,而概率模型预测所有可能结果的整个概率分布。在出租车司机示例中(见第 1.1 节),给定路线的旅行时间预测结果分布是高斯分布。但到目前为止,你还没有学*如何为概率模型设置神经网络。你将在本书的这一部分学*不同的方法来实现这一点。
在分类的情况下,你已经知道如何获取结果的概率分布。在假钞示例中(见第 2.1 节),你设置了一个神经网络,它对一个给定的钞票预测了伪造类和真实类的概率。在 MNIST 分类示例中(见第 2.1.3 节和第 2.2.4 节),你使用了不同的神经网络架构来预测一个手写数字的十个可能类别的概率。为此,你定义了一个神经网络,其最后一层有与类别数量相等的节点。此外,你使用了 softmax 激活函数来确保输出可以解释为概率:介于零和一之间的值,且总和为 1。因此,分类神经网络在结构上就是概率模型。
关于回归问题?在第三章中,我们探讨了线性回归。结果发现,所有回归问题都可以被视为概率模型。虽然分类问题根据定义是概率模型,但回归问题需要稍作解释才能成为概率模型。
在本书的这一部分,你将学*如何为不同的回归任务选择一个合适的分布,以及如何通过神经网络来估计其参数。为什么我们如此兴奋地介绍概率模型呢?回想一下第 1.1 节中的出租车司机。他通过使用概率卫星导航(GPS)增加了获得小费的机会。还有很多其他应用,在这些应用中,量化预测的不确定性是至关重要的。想象一下,例如,一个深度学*模型,它以你的胸部 X 光片作为输入,并输出两种治疗方案之一:手术或等待观察。现在,将你的图像输入到模型中。非概率模型输出手术。概率模型输出 51%的概率认为手术是最佳治疗方案,49%的概率认为仅仅等待观察是最佳治疗方案。嗯,我们猜测,对于这样的不确定预测,如果你想要进行手术,你可能会重新考虑。至少,你希望得到一位真正的医生的第二意见,这位医生可能也会注意到这种不典型的高不确定性背后的原因。概率模型的第二个重要特性是,计算损失函数和评估概率模型性能的方式是唯一且独特的。这种独特的方式就是似然函数。
在这部分,你将了解最大似然方法,并了解如何将其用于确定分类任务和回归任务中合适的损失函数。正如你将看到的,最大似然方法是一个非常强大且直观的原则。你将学*如何使用最大似然方法来处理更高级的概率模型,例如计数数据的预测模型、文本到语音模型,或者生成逼真面部图像的模型。实际上,最大似然原理几乎隐藏在深度学*中使用的所有损失函数背后。
4 使用似然方法构建损失函数
本章涵盖
-
使用最大似然方法估计模型参数
-
确定分类问题的损失函数
-
确定回归问题的损失函数

在上一章中,你看到了如何通过使用随机梯度下降(SGD)优化损失函数来确定参数值。这种方法也适用于具有数百万个参数的深度学*模型。但我们是如何得到损失函数的呢?在线性回归问题中(见第 1.4 节和第 3.1 节),我们使用了均方误差(MSE)作为损失函数。我们不认为最小化数据点与曲线之间的平方距离是一个坏主意。但为什么使用平方而不是,例如,绝对差异呢?
事实上,在处理概率模型时,有一个普遍有效的方法来推导损失函数。这种方法被称为最大似然方法(MaxLike)。你会发现,对于线性回归,MaxLike 方法在某种假设下给出了均方误差(MSE)作为损失函数,我们将在本章详细讨论。
关于分类,你使用了一个称为分类交叉熵的损失函数(见第 2.1 节)。什么是分类交叉熵?你最初是如何得到它的?你可能能猜到通过哪种方法可以推导出这个损失函数。结果是,似然函数是你的朋友。它在分类任务中提供了交叉熵作为合适的损失函数。
4.1 MaxLike 原理简介:所有损失函数之母
MaxLike 是几乎所有深度学*(DL)和机器学*(ML)应用背后“秘密”的关键,这在图 4.1 中有所体现。

图 4.1 揭示了机器学*(图中为 ML)和深度学*(DL)中几乎所有损失函数的秘密。图片来源:www.instagram.com/neuralnetmemes/
为了演示这个原理,我们从深度学*(DL)领域之外的一个简单例子开始。考虑一个骰子,其中一个面显示美元符号($),其余五个面显示点(见图 4.2)。

图 4.2 一个骰子,一面显示美元符号,其余面显示点
如果你掷骰子,美元符号出现的概率是多少(我们这里假设骰子是公平的)?平均来说,每六次中出现一次美元符号。因此,看到美元符号的概率是 p = 1/6。不出现美元符号而看到点的概率是 5/6 = 1 - p。让我们掷骰子十次。你只看到一次美元符号,九次看到点的概率是多少?首先,假设你在第一次掷骰子时看到美元符号,在接下来的九次掷骰子时看到点。你可以将这个情况写成字符串:
$.........
该特定序列发生的概率将是 ⅙ ⋅ ⅚ ⋅ ⅚ ⋅ ⅚ ⋅ ⅚ ⋅ ⅚ ⋅ ⅚ ⋅ ⅚ ⋅ ⅚ ⋅ ⅚ = ⅙ ⋅ ⅚⁹ = 0.032,或者用 p =⅙ 表示为 p¹ ∗(1 −p)^(10−1) 。如果我们只要求在十次掷币中出现一个美元符号和九个点(无论位置如何)的概率,我们必须考虑以下所有十个结果:
$.........
.$........
..$.......
...$......
....$.....
.....$....
......$...
.......$..
........$.
.........$
要发生,这十个不同的序列中每一个都有相同的概率 p ⋅(1 −p)⁹ 。这意味着观察到的十个序列中有一个的概率为 10 ⋅ p ⋅(1 −p)⁹ 。在我们的例子中,我们使用 p = 1/6,得到 0.323 作为在十次掷币中发生一次美元符号的概率。你可能会产生好奇心,想知道在十次掷币中出现两个美元符号的概率是多少?特定顺序(例如 $$........)的概率是 p² ⋅ (1 −p )⁸ 。结果是,现在有 45 种可能的方式 1 来重新排列像 $.$....... 或 $..$....... 这样的字符串:因此,两个美元符号和八个点的总概率是 45 ⋅(⅙)² ⋅(⅚)⁸ = 0.2907 。
我们计数掷出带有美元符号的成功次数的掷币实验是称为二项实验的一般类实验的例子。在二项实验中,你计数 n 次试验中的成功次数(在这里是掷骰子),其中所有试验都是相互独立的,并且每次试验成功的概率相同。在 n 次试验的二项实验中,成功次数不是固定的,但通常可以取 0 到 n 之间的任何值(如果 p 不是正好为 0 或 1)。因此,成功次数 k 被称为随机变量。为了强调 k 是来自二项分布的随机变量,可以写成:
- k ∼ binom(n, p)
- 符号读作“来自”或“分布类似于”一个二项分布,其中 n 等于尝试次数,p 等于单次尝试成功的概率。在本书的上下文中,如何推导出某个 k 的概率并不那么重要。但有一个名为
binom.pmf的 SciPy 函数可以用来计算这个概率,其参数k等于成功次数,n等于尝试次数,p等于单次尝试成功的概率。使用这个函数,我们可以绘制在 10 次掷币中出现 0 到 10 个美元符号的概率图。请参见列表 4.1 中的代码和图 4.3 中的结果。

图 4.3 10 次掷骰子中观察到的美元符号数量的概率分布。单个掷币中出现美元符号的概率为 p = 1/6。一个和两个美元符号出现的概率(0.323 和 0.2907)与手工计算的结果相同。此图使用列表 4.1 中的代码创建。
列表 4.1 使用binom函数计算 0 到 10 次掷出美元符号的概率
from scipy.stats import binom
ndollar = np.asarray(np.linspace(0,10,11)\ ❶
, dtype='int')
pdollar_sign = binom.pmf(k=ndollar, n=10, p=1/6) ❷
plt.stem(ndollar, pdollar_sign)
plt.xlabel('Number of dollar signs')
plt.ylabel('Probability')
❶ 成功次数(掷出的美元符号),从 0 到 10 共 11 个整数
❷ 投掷出 0、1、2……美元符号的概率,每个符号的概率为 p = 1/6
到目前为止,一切顺利。你可能记得这是从你的概率课程中学到的。现在我们换个角度。考虑以下情况:你在一个赌场里玩一个游戏,如果你看到美元符号,你就能赢。你知道有特定数量的面(0 到 6)带有美元符号,但你不知道具体有多少。你观察到十次投掷骰子的结果,其中有两次出现了美元符号。你会猜骰子上有多少个美元符号?当然不可能是零,因为你观察到了美元符号,另一方面,也不可能是六,因为你没有观察到点数。但什么是一个好的猜测呢?
再次查看列表 4.1,你突然有了一个天才的想法。简单地再次计算在十次投掷中观察到两个美元符号的概率,但这次假设你的骰子不仅有带美元符号的一面,而是有两面。然后假设你的骰子有三面带美元符号,再次确定在十次投掷中看到两个美元符号的概率,依此类推。你的观察数据是固定的(十次投掷中出现两个美元符号),但你的数据生成假设模型从没有美元符号的骰子变为有 1、2、3、4、5 或 6 个美元符号的骰子。在一次投掷中观察到美元符号的概率可以看作是骰子模型中的一个参数。这个参数对于不同的骰子模型取值为 p = 0/6, 1/6, 2/6, ……, 6/6。对于这些骰子模型中的每一个,你可以确定在十次投掷中观察到两个美元符号的概率,并在图表中绘制出来(见图 4.4)。
实践时间打开mng.bz/eQv9。运行代码直到你达到第一个练*。对于这个练*,你的任务是确定如果你考虑一个有 0、1、2、3、4、5 或所有 6 个面都带有美元符号的骰子,在 10 次骰子投掷中观察到美元符号两次的概率。绘制计算出的概率会产生图 4.4 中的图表。 |
|---|

图 4.4:不同骰子上的美元符号数量在 n=10 次投掷中观察到 k=2 个美元符号的可能性
我们在图 4.4 中看到了什么?从左边开始,如果你掷出的骰子上没有美元符号,那么你在十次投掷中观察到两个美元符号的概率为零。嗯,这是预期的。接下来,计算在骰子上只有一个美元符号(p = 1/6)的情况下,在十次投掷中观察到两次美元符号的概率。这个概率接* 0.3。如果你假设骰子上有两个美元符号,那么在十次投掷中观察到两个美元符号的概率大约为 0.20,依此类推。你会猜骰子上有多少个美元符号?你会猜一个,因为只有一个美元符号的骰子在十次投掷中产生两个美元符号的概率最高。恭喜!你刚刚发现了最大似然原理。
MaxLike 口诀:选择模型的参数(s),使得观察到的数据具有最高的似然。
在我们的例子中,参数模型是二项分布,它有两个参数:每次试验的成功概率 p 和进行的试验次数 n。我们观察到在 n = 10 次投掷中有 k = 2 个美元符号。模型的参数是 p,即单次掷骰子显示美元符号的概率。不同数量美元符号的似然在图 4.4 中显示。我们选择最大似然值(p = 1/6),对应于骰子上有一个美元符号。
这里有一个小的细微差别。图 4.4 中的概率是未归一化的概率,因为这些概率相加不等于 1,而是等于一个常数因子。在我们的例子中,这个因子是 0.53。因此,这些概率在严格意义上不是概率,因为它们必须加起来等于 1。这就是我们说似然而不是概率的原因。但我们可以仍然使用这些似然进行排序,我们选择产生最高似然的模型作为最可能的模型。此外,对于像我们这样的简单情况,我们可以将每个似然除以所有似然的和,将它们转换为适当的概率。让我们回顾一下在 MaxLike 方法中确定最佳参数值的步骤:
-
你需要一个模型来描述观察数据的概率分布,该模型有一个或多个参数。
在这里,数据是掷骰子十次时看到美元符号的次数。二项分布的参数 p 是骰子显示美元符号的概率 p(显示美元符号的骰子面数除以六)。
-
你使用该模型来确定在假设模型中参数的不同值时,得到观察数据的似然。
在这里,你计算了当假设骰子有 0、1、2、3、4、5 或 6 个美元面时,在十次投掷中得到两个美元符号的概率。
-
你选择使观察到的数据的似然最大的参数值作为最佳参数值。这也被称为 MaxLike 估计器。
在这里,ML 估计器是骰子有一面是美元符号。
4.2 为分类问题推导损失函数
在本节中,我们向您展示如何应用 MaxLike 原则来推导分类问题的损失函数,并揭开“分类交叉熵”这个十美元词的神秘面纱。结果证明,这个量计算起来相当简单,您将看到如何使用它来对您的模型进行合理性检查。
4.2.1 二分类问题
让我们回顾一下第二章中的假钞例子(参见列表 2.2,此处重复列出)。
列表 2.2 定义具有两个隐藏层的分类网络
model = Sequential()
model.add(Dense(8, batch_input_shape=(None, 2),
activation='sigmoid'))
model.add(Dense(2, activation='softmax'))
# compile model
model.compile(loss='categorical_crossentropy', ❶
optimizer=sg*D*)
❶ 这里解释了列表 2.2 中的 'categorical_crossentropy' 损失。
在第二章中,你也为所有分类问题使用了'categorical_crossentropy':全连接神经网络(fcNN)、应用于 MNIST 手写数字分类问题的卷积神经网络(CNN)以及用于检测艺术品条纹的 CNN。这个损失函数通常用于深度学*中的分类问题。
为了解释'categorical_crossentropy',让我们从纸币的例子开始。在这种情况下,第一个神经元(在图 4.5 中标为 p0)的输出是模型“认为”给定输入x属于类别 0(真实纸币)的概率。其他神经元的输出(在图中标为 p1)是x描述假类别的概率。当然,p0 和 p1 的总和为 1。这是由softmax激活函数(见第二章)保证的。

图 4.5 展示了由特征x[1]和x[2]描述的用于识别纸币的分类网络,该网络有两个输出,分别给出真实纸币的概率(p[0])和假币的概率(p[1])。这与第二章中的图 2.8 相同。
我们现在使用最大似然原理来推导损失函数。观察到的数据的似然性是什么?在分类问题中,训练数据以成对的形式出现(x**[i] 和 y**[i] )。在纸币的例子中,x**[i] 是一个有两个条目的向量,y**[i] 是示例的真实类别(纸币是假的还是真的)。CNN(见图 4.5)接收输入x并输出每个可能类别的概率。这些概率定义了给定x的条件概率分布(CPD)(见下文侧边栏中的图)。观察到的(真实)结果类别y**[i] 的似然性由真实类别y**[i] 的 CPD 给出。对于具有已知权重和给定输入x**[i] 的 NN,如果真实类别是y**[i] = 0,则似然性由 p0(x**[i] )给出;如果真实类别是y**[i] = 1,则似然性由输出 p1(xi)给出。
重要:请记住:对于给定的训练示例(x**[i] , y**[i] ),分类模型的似然性就是网络分配给正确类别 yi 的概率。
例如,一个训练良好的网络在训练示例来自类别 1(假币)时,会返回一个高 p1 值。整个训练集的概率是多少?这里我们假设训练集中的所有示例都是相互独立的。因此,整个训练数据集的概率就是各个概率的乘积。例如,想象你掷一个标准的骰子两次,并询问第一次掷出 1 和第二次掷出 2 的概率。这很简单,因为 1/6 · 1/6,因为掷骰子是独立的。这可以一直进行下去,一般来说,整个训练集的似然性是所有单个示例的乘积。我们一个接一个地看每个训练示例的概率,然后取所有这些概率的乘积。
我们也可以按以下顺序排列概率:首先,在我们的例子中,我们取真实纸币(对于y = 0)并将预测 p0 相乘。然后,我们取假币并将 p1 相乘。假设你在训练集中有五张纸币。前三个例子来自真实纸币,最后两个例子来自假币。从 NN 为所有纸币得到属于类别零(p[0])或类别一(p[1])的概率。然后,五张纸币的似然度为

方程式中的Π表示取乘积,而σ表示取和。一般来说,这可以写成:
方程式 4.1
我们也可以用稍微不同的方式解释方程 4.1,基于对输出y的概率分布进行公式化(参见侧边栏)。因为这种观点可以帮助你从更一般的角度理解机器学*方法,所以我们将在以下侧边栏中给出这种解释。
使用参数概率模型进行分类损失的 MaxLike 方法
图 4.5 中具有固定权重的 NN 在输入特定输入x时,输出所有可能的类别标签y的概率。这可以写成输出y的概率分布,它取决于输入值x和 NN 的权重:

Y = k 表示随机变量y取特定值 k。在方程中,你可以进一步将竖线读作“给定”或“条件”。竖线右侧是所有给定信息,这些信息用于确定竖线左侧变量的概率。在这里,你需要知道输入x和 NN 的权重 W 的值来计算 NN 的输出,即 p0 和 p1。因为概率分布依赖于x,所以它被称为条件概率分布(CPD)。通常你会看到这个方程的简化版本,它省略了竖线右侧的部分(要么只有 W = w,要么x = x和 W = w),并假设这是不言而喻的。

这种输出y只能取 0 或 1 值的概率分布称为伯努利分布,它只有一个参数 p。据此,你可以直接计算 p0 = 1 - p1。以下图显示了二元输出y的概率分布。

二元变量y的概率分布,也称为伯努利分布
图 4.5 中的 NN 为每个输入计算 p0 和 p1。对于 n 个数据点,数据的概率或似然度是计算出的正确类别的概率的乘积(见方程式 4.1)。MaxLike 原则告诉你应该调整网络权重 w,使得似然度最大化,如下所示:

从原则上讲,我们现在已经完成了。我们可以通过调整网络的权重来最大化方程 4.1。您不需要手动完成这项工作,但可以使用任何框架,如 TensorFlow 或 Keras。这两个框架都可以进行(随机)梯度下降。
仍然存在一个实际问题。方程 4.1 中的 p0 和 p1 是介于 0 和 1 之间的数,其中一些可能很小。在 0 到 1 的范围内乘以许多数会导致数值问题(参见列表 4.2)。
列表 4.2 在 0 到 1 之间乘以许多数时的数值不稳定性
import numpy as np
vals100 = np.random.uniform(0,1,100) ❶
vals1000 = np.random.uniform(0,1,1000) ❷
x100 = np.product(vals100)
x1000 = np.product(vals1000)
x100, x1000 ❸
❶ 随机乘以 0 到 1.0 之间的 100 个数。
❷ 随机乘以 0 到 1.0 之间的 1,000 个数。
❸ 对于 1,000 个数的一个典型结果(7.147335361549675e-43, 0.0)给出的是 0。
如果我们取超过几百个例子,乘积接*零,由于计算机中浮点数的有限精度,它被视为零。DL 使用典型的 float32 浮点数类型,对于这些类型,紧邻零的最小数大约是 10^-45。
有一个技巧可以解决这个问题。在方程 4.1 中,您可以将似然的对数替换掉P(Training)。取对数会改变函数的值,但不会改变达到最大值的位置。作为旁注,最大值保持不变的性质是由于对数函数x是一个随着x增大而严格增长的函数。具有这种性质的函数被称为严格单调函数。在图 4.6 中,您可以看到一个任意函数f(x)及其对数 log(f(x))。f(x)和 log(f(x))的最大值都在x ≈ 500 处达到。

图 4.6 一个具有非负值的任意函数 f(x)(实线)及其对数(虚线)。虽然取对数后最大值(大约为 2)会变化,但达到最大值的位置(大约为 500)无论是否取对数都保持不变。
通过取对数,您获得了什么?任何数的乘积的对数是这些数的对数之和,这意味着 log(A ⋅ B) = log (A) + log (B)。这个公式可以扩展到任意多个项:log (A ⋅ B ⋅ C ⋅ ...) = log (A) + log (B) + log (C),因此方程 4.1 中的乘积变成了对数之和。让我们看看这对数值稳定性的影响。
你现在添加 0 到 1 之间的数字的对数。对于 1,你得到对数 (1) = 0;对于 0.0001,你得到对数 (0.0001) ≈ −4。唯一可能出现的数值问题是,如果你真的得到了正确类别的概率为 0,那么零的对数是负无穷大。为了防止这种情况,有时会在概率中添加一个非常小的数,比如 10E−20。但在这里我们不必担心这种极不可能的情况。如果你将列表 4.2 从乘积改为对数和,会发生什么?你得到一个数值稳定的计算结果(见列表 4.3)。
列表 4.3 通过取对数来修复数值不稳定性
log_x100 = np.sum(np.log(vals100)) ❶
log_x1000 = np.sum(np.log(vals1000))
log_x100, log_x1000 ❷
❶ 与列表 4.2 中相同的采样数字的乘积变成了对数和。
❷ 对于 1,000 个数字的典型结果(-89.97501927715012,-987.8053926732027)给出了一个有效值。
使用对数技巧将最大似然方程(4.1)转换为这里所示的最大对数似然方程:

这种对数技巧不依赖于你使用的底数,但了解 Keras 使用自然对数来计算损失函数是有用的。我们现在几乎到了终点,只剩下两个小细节。
在方程 4.2 中,你添加了与训练数据一样多的数字。因此,方程 4.2 依赖于训练数据数量 n。为了得到一个不系统依赖于训练数据数量的量,你可以将方程除以 n。在这种情况下,你考虑每个观察的平均对数似然。最后一点是,深度学*框架通常被构建为最小化损失函数,而不是最大化。因此,你最小化 log(P(Training))而不是最大化 log(P(Training))。哇!你已经推导出了二元分类模型的负对数似然(NLL)函数,这也被称为交叉熵。你将在下一节中看到交叉熵这个名字的由来。在达到推导二元分类器损失函数的目标后,让我们再次写下它:

你现在可以验证这确实是深度学*中需要最小化的量。
实践时间打开 http://mng.bz/pBY5 并运行代码,其中你创建了一个包含仅两个类别(0 和 1)的 MNIST 数字子集。在这个第一个练*中,你的任务是使用model.evaluate函数确定未训练模型的交叉熵损失,并解释你获得的价值。 |
|---|
使用未训练的模型,你达到了大约 0.7 的交叉熵。当然,这是一个随机结果,因为网络的权重有它们的初始随机值,还没有进行过训练。期望从 0.7 有一些随机偏差。你能用你刚刚学到的知识解释 0.7 这个值吗?网络一开始一无所知。你期望的命中率是多少?大约 50%对吗?让我们计算ln(0.5)。那是 0.69,看起来很合适。(Keras 使用自然对数来计算损失函数。)
具有一个输出节点的两类分类损失函数
对于像银行票据示例中那样的两个特殊类别,存在一个网络只有一个输出神经元的可能性。在这种情况下,输出是类别 p1 的概率。其他类别的概率,p[0],由 p[0] = 1 - p[1] 给出。因此,我们不需要对 yi 进行 one-hot 编码。它要么是类别 0 的 y**[i] = 0,要么是类别 1 的 y**[i] = 1。利用这一点,我们可以将方程 4.3 重写为:

与此方程不同,我们不需要检查示例属于哪个类别。如果示例 i 属于类别 1(y**[i] = 1),则取包含 p[1] 的第一部分。否则,如果示例 i 属于类别 0(y**[i] = 0),则激活第二部分,其中 p[0] = 1 - p[1]。
4.2.2 具有两个以上类别的分类问题
如果你有多于两个类别会发生什么?你可能认为没有什么特别的,你是对的。你已经在第二章的几个练*中做过这件事,当时你处理了在 MNIST 数据集中区分十个数字的任务。回想一下,在为 MNIST 任务设置深度学*模型时,你使用了与二分类模型相同的损失:loss='categorical_crossentropy'。让我们看看你如何使用最大似然方法推导损失函数,并证明使用交叉熵是合适的。
回想一下你在二分类任务中进行的概率建模。你使用了一个有两个输出节点的神经网络(见图 4.5),为每个输入提供了对应于类别 0 和 1 的 p 0 和 p 1 概率。你可以将这些两个概率解释为二分类任务的 CPD 参数(参见本章第一个侧边栏中的图)。这个 CPD 的模型是伯努利分布(参见本章第一个侧边栏)。原则上,伯努利分布只需要一个参数:类别一的概率,p 1。这个参数由第二个输出节点给出。你可以通过 p 0 = 1 - p 1 从 p 1 推导出类别 0 的概率;softmax激活函数的使用确保了神经网络的第一输出返回 p 0。在遵循最大似然方法时,二分类任务的损失由伯努利分布的均方误差 NLL 给出(参见方程 4.2 和 4.3)。
让我们使用相同的程序处理多于两个类别的分类任务。在 MNIST 示例中,你有十个类别,你使用一个有十个输出节点的神经网络,每个类别一个。回想一下,例如,我们在第二章中用于 MNIST 手写数字分类任务的架构,如图 2.12 所示,我们在图 4.7 中重复了它。

图 4.7 一个具有两个隐藏层的全连接神经网络(fcNN)。对于 MNIST 示例,输入层有 784 个值,对应于 28 × 28 像素。输出层有十个节点,每个类别一个。
在这个 MNIST 任务中,你想要区分十个类别(0,1,……,9)。因此,你设置了一个具有十个输出节点的神经网络,每个节点提供输入对应于相应类别的概率。这十个概率定义了 MNIST 分类模型中 CPD 的十个参数。分类 CPD 的模型称为多项式分布,它是伯努利分布扩展到多于两个类别的结果。在十个类别的 MNIST 分类任务中,多项式 CPD 可以表示如下:

根据权重 w,神经网络为每个输入图像 x 产生十个输出值,这些值定义了分配给每个可能结果的相应多项式 CPD 的参数。
在训练神经网络之前,你用小的随机值初始化权重。未训练的神经网络将每个类别的概率分配得接* 1/10,类似于均匀分布(见图 4.8),无论通过神经网络的图像是什么。

图 4.8 展示了一个均匀概率分布,将概率 0.1 分配给十个类别标签。对于十个类别标签的未训练分类神经网络,其 CPD 将与这种均匀分布相似,无论分类图像的标签是什么。
如果你然后用几个带标签的图像来训练神经网络,CPD 已经包含了一些信息。例如,通过标签 2 的图像得到的 CPD 可能看起来像图 4.9 中展示的那样。

图 4.9 展示了与标签 2 对应的多项式条件概率分布(CPD),该图像通过了一个神经网络(实线表示的分布)。在这里,真实标签的分布(虚线)将概率 1 分配给真实的标签 2。
让我们看看图 4.9。这个观察到的标签 2 图像的似然性是什么?似然性是 CPD 分配给标签的概率,这里大约是 0.3。请注意,只有 CPD 分配给正确标签的概率才对似然性有贡献。如果你用你的神经网络分类了几个图像,并且知道每个图像的真实标签,你可以确定联合负对数似然(NLL)。为此,对每个图像,使用以下方程评估 CPD 分配给正确类别标签的概率:

每个观察的平均 NLL 是 NLL 除以样本数。这再次导致了交叉熵的公式:

如果你使用一个-hot 编码向量 ^(true) p**[i] 来表示示例 i 的真实类别,你可以将这个表达式写得更紧凑。向量 ^(true) p**[i] 对于训练示例 i 的真实类别是 1,对于其他成分是 0(见图 4.9)。

你期望从为 MNIST 任务设置的未训练的 NN 中损失值是多少?暂停一下,在你继续阅读之前,试着自己找出答案。
在完整的 MNIST 数据集中,你有十个类别。一个未训练的网络会将大约 1/10 分配给每个类别。这导致所有类别的 p**[i] ≈ 1/10,我们得到损失的值约为 2.3。为了调试你的训练,始终检查这个数字对于分类问题来说是一个好的实践。
实践时间打开mng.bz/OMJK并运行代码,直到你达到由笔形图标指示的练*,然后做这个练*。在练*中,你的任务是使用未训练的 CNN 在 MNIST 图像上做出的数字预测,并手动计算损失值。你会发现你得到一个接*我们之前计算出的 2.3 的值。 |
|---|
4.2.3 NLL、交叉熵和 Kullback-Leibler 散度之间的关系
你可能想知道为什么深度学*的人把分类问题中的 NLL 称为交叉熵。你可能也想知道在分类问题中,是否可以通过真实值和预测值之间的差异来量化拟合的“坏度”,类似于回归中的 MSE。在本节中,你将了解这两个问题的答案。
统计学和信息理论中熵的含义
我们在信息理论、统计物理和统计学等不同学科中使用熵这个术语。在这里,你将了解第一个实例,其中它被用来描述概率分布的信息内容。你会发现,了解分类中损失函数的交叉熵术语的起源是有用的。
让我们从熵这个术语开始。熵的基本思想是分布能告诉你多少关于手头数量的信息,以及还剩下多少不确定或惊喜?在下面的方程中,你可以通过分布的扩散或“粗糙度”来衡量熵 H。它被定义为

看一下图 4.9 中的两个分布。CPD(黑色)相当平坦。你不会学到太多关于可能的类别标签。你可以将其推向极端,并考虑均匀分布(见图 4.8)。实际上,可以证明均匀分布具有最大的熵。对于每个 p**[i] = 1/10 的 10 个类别,熵等于 H = −10 ⋅ 1/10 ⋅ log (1/10) ≈ 2.3。真实分布(图 4.9,虚线)告诉你尽可能多的关于标签的信息。分布只有一个峰值,你可以 100%确定真实类别标签是 2。相应的熵是 H = 0,因为对于正确的类别,你有一个 pi = 1,并且因此 p**[i] ⋅ log(p**[i] ) = 1 ⋅ 0 = 0 。
交叉熵和 Kullback-Leibler 散度
如果你有两个分布 q 和 p,并且根据分布 p 计算给定分布 q 的对数期望值,交叉熵就会发挥作用。对于离散情况,如下所示:

使用交叉熵,你可以比较两个分布。
Kullback-Leibler 散度作为分类中的 MSE 对应物
让我们再次检查图 4.9,它显示了预测的 CPD(黑色线条)和真实分布(虚线),这是经过 one-hot 编码的真实标签。如果模型是完美的,预测的 CPD 将与真实分布相匹配。让我们尝试量化模型的“坏处”,这应该是当前预测 CPD 与真实分布之间距离的某种度量。一种简单的方法是在每个类别标签上确定其 CPD 与真实概率的差异,可能将其平方,然后取平均值。这将在回归中模仿 MSE,即真实值与预测值之间平方差的期望值。
但请注意,MSE 并不能给出分类的最大似然估计。对于分类,减去真实值与预测值并不对应于最大似然原理。在这里,你需要比较两个分布:真实分布和预测分布。为此,通常使用 Kullback-Leibler (KL) 散度。KL 散度是概率对数差异的期望值。通过使用一些基本的对数微积分规则和期望值的定义,你可以证明 KL 散度与交叉熵相同:

如前所述的推导中所示,真实分布与预测分布之间的 KL 散度简化为真实分布的熵与交叉熵之和。因为第一个项(真实分布的熵)为零(图 4.9 中的虚线分布),所以如果你最小化交叉熵,你实际上是在最小化 KL 散度。你将在本书中再次遇到 KL 散度。但在此刻,让我们欣赏 KL 散度对于分类来说就像 MSE 对于回归一样。与交叉熵和 KL 散度一样:两个概率分布有不同的角色,如果你交换这两个分布,你会得到不同的结果。为了表示 KL (^(true) p ||^(pred) p ) ≠ KL (^(pred) p ||^(true) p ) ,我们用两个竖线表示。顺便说一下,这不是一个好主意。为什么?(在继续阅读之前尝试回答这个问题。)好吧,^(true) p**[i] 主要为零,取零的对数不是一个好主意,因为它返回负无穷大。
4.3 推导回归问题的损失函数
在本节中,你使用最大似然原理推导回归问题的损失函数。你首先回顾了第三章中的血压示例,其中输入是一个美国健康女性的年龄,输出是她收缩压(SBP)的预测。在第三章中,你使用了一个没有隐藏层的简单神经网络来建模输入和输出之间的线性关系。作为损失函数,你使用了均方误差(MSE)。这种损失函数的选择是通过一些手舞足蹈的论据来解释的。没有给出任何硬事实来证明这个损失函数是一个好的选择。在本节中,你将看到 MSE 作为损失函数直接源于最大似然原理。此外,使用最大似然原理,我们可以超越 MSE 损失,并使用非恒定噪声来建模数据,这在统计学界被称为异方差性。不要害怕;理解最大似然原理会使建模(而不是拼写)异方差性变得轻而易举。
4.3.1 使用没有隐藏层和一个输出神经元的神经网络来建模输入和输出之间的线性关系
让我们回到第三章中的血压示例。在那个应用中,你使用了一个简单的线性回归模型 ŷ =a ⋅ x − b ,来估计当给定女性的年龄 x 时收缩压 y 的值。训练或拟合这个模型需要你调整参数 a 和 b,使得得到的模型“最佳拟合”观察到的数据。在图 4.10 中,你可以看到观察到的数据以及一条相当好地穿过数据的线性回归线,但它可能不是最好的模型。在第三章中,你使用了 MSE 作为损失函数来量化模型拟合数据的好坏。回想一下方程式 3.1:

这个损失函数依赖于这样一个观点:模型与数据之间的偏差应该通过求和平方残差来量化。给定这个损失函数,你通过随机梯度下降法(SGD)确定了最优的参数值。
在第三章中,我们通过一个论据介绍了 MSE 损失,即如果平方残差之和最小,则拟合是最优的。在下面的内容中,你将看到如何使用最大似然原理以理论上的方式推导线性回归任务的适当损失。
提前剧透:你会发现最大似然法会导致 MSE 损失。
让我们通过最大似然法(MaxLike)推导回归任务的损失函数。对于一个简单的回归模型,我们只需要一个简单的神经网络(见图 4.11),没有隐藏层。当使用线性激活函数时,这个神经网络编码了输入 x 和输出之间的线性关系:out = a · x + b 。

图 4.10 展示了血压示例的散点图和回归模型。点代表测量数据点,直线是线性模型。数据点和模型之间的垂直差异是残差。

图 4.11 简单线性回归作为一个没有隐藏层的 fcNN。该模型直接从输入计算输出,即 out = a · x + b。
回归问题中的训练数据以 n 对 (x**[i] , y**[i] ) 的形式出现。在血压的例子中,x**[i] 表示第 i 个女性的年龄,而 y**[i] 是第 i 个女性的真实收缩压。如果你为神经网络(NN)的权重选择某些数字,比如 a = 1 和 b = 100,那么对于给定的输入,你可以计算出,比如 x = 50 和拟合值 ŷ = 1 ⋅ 50 + 100 = 150。你可以把这理解为模型的最佳猜测。在我们的数据集中,我们有一个 50 岁的女性,但她的血压是 183,而不是 150。这并不意味着我们的模型是错误的或者可以进一步改进,因为你不会期望所有相同年龄的女性都有相同的血压。
正如分类一样,在回归中也是如此。神经网络的输出不是当输入具有特定值 x 时你期望观察到的值 y。在分类中,神经网络的输出不是一个类标签,而是所有可能类标签的概率,这些概率是拟合概率分布的参数(见第一个侧边栏中的图)。在回归中,神经网络的输出不是具体的值 y 本身,而是拟合的连续概率分布的参数。在这里,我们使用正态分布;在第五章中,我们还会使用不同的分布,如泊松分布。为了回顾正态分布的性质,请参见下一个侧边栏。
正态分布的回顾
让我们回顾一下如何处理遵循正态分布的连续变量 y。首先,更仔细地看看正态分布的密度:N(μ, σ)。参数 μ 决定了分布的中心,而 σ 决定了分布的扩散(见图以下)。你经常看到类似的东西
Y ~ N(μ, σ)
这表明随机变量 y(例如,一定年龄的血压)遵循正态分布。这样的随机变量 y 有以下概率密度函数:

这在下图中得到了可视化:

正态分布的密度,其中 μ 是中心,σ 是分布的扩散
观察这个图可以让人预见到 y 在 μ 附*有高概率值,而在 μ 附*有低概率值。这种直觉是正确的。但是,与离散变量的概率分布(见图 4.2)相比,读取连续变量 y 的概率分布(见前一个图)还是有点困难。
在离散变量的情况下,概率分布由分离的柱状图组成,对应于离散的输出值。对于离散概率分布,柱状图的高度直接对应于概率,这些概率加起来为 1。连续变量可以取无限多个可能值,对于像 π ~ = 3.14159265359 这样的确切值,概率为零。因此,概率只能定义在值的一个区域内。观察值 y 在 a 和 b 之间的概率,y ∈ a,b],由密度曲线在 a 和 b 之间的面积给出(见图中阴影区域)。所有可能值的范围有一个概率为 1;因此,概率密度曲线下的面积总是 1。

正态分布变量 y 的密度,其中密度曲线下的阴影区域给出了 y 在 a 和 b 之间取值的概率
您可以使用 MaxLike 原理来调整用于执行线性回归的 NN 的两个权重 w = (a, b)(见图 4.11)。但观察到的数据的似然性是什么?对于回归,回答这个问题比分类稍微困难一些。记住,在分类中,您可以从具有参数 pi 的概率分布中确定每个观察值的概率或似然性。NN 控制这些参数(见图 4.11)。在回归中,观察值 y**[i] 是连续的。在这里,您需要与正态分布一起工作。(除了正态分布之外,有时其他分布也足够好,我们将在第五章中处理那些,但现在我们坚持使用正态分布。)
正态分布有两个参数:μ 和 σ 。首先,我们固定参数 σ(比如说,我们将其设置为 σ = 20)并让 NN 只控制参数 μ x 。在这里,下标 x 提醒我们这个参数依赖于 x。它由网络确定,因此 μ x 依赖于网络的参数(权重)。图 4.11 中的简单网络产生 μ**[x] = ax + b 对 x 的线性依赖。图 4.12 用粗线展示了这一点。老年女性的平均血压较高。权重 (a, b) 本身是通过(拟合)最大化数据的似然性来确定的。数据的似然性是什么?我们从单个数据点 (x**[i] , y**[i] ) 开始。例如,检查 22 岁女性收缩压为 131 的数据点。对于那个年龄,网络预测的平均值为 μ**[x] = 111;σ 的范围是固定的。

图 4.12 血压示例的散点图和回归模型。点代表测量的数据点,直线是线性模型。钟形曲线是条件概率分布,条件是观察到的值 x。
换句话说,一个 22 岁的女性的血压(根据模型)最有可能是 111。但其他值也是可能的。血压值 y 的概率密度 f (y, μ) = 111, σ = 20) 在不同的血压值 y 上分布,围绕 111 这个值,通过正态分布(如图 4.12 中阴影灰色区域所示,再次在图 4.13 中显示)。

图 4.13 条件正态密度函数 f。垂直条的高度表示在这个模型下特定值的可能性。
在我们的观察中,这位女性的血压为 131。与离散情况一样,我们将概率 p (y | x, a, b) = p (y | x, w) 重新解释为给定参数 w 的数据发生的可能性,我们现在将概率密度 f ( y | x, μ, σ ) = f (y | x, w) 解释为观察到的数据发生的可能性。因为它来自概率密度,所以在连续情况下,可能性也是一个连续函数。在我们的具体情况下,这个观察到的可能性由以下密度给出:

参见图 4.13 中的垂直条。对于每个输入值 x,输出 y 遵循另一个正态分布。例如,对于一个 47 岁的女性,收缩压为 110,参数是 μ**[x] = 139。对应于这个血压的可能性的确定由 f ( y = 110; μ = 139, σ = 20) 决定。因为正态概率分布通过 μ**[x[i]] = a ⋅ x**[i] + b 依赖于值 x**[i],它通常被称为条件概率分布(CPD)。与分类情况一样,所有点(我们假设独立性)的可能性由单个可能性的乘积给出:

这种可能性只取决于参数 a 和 b。最大化它的值 â 和 bˆ 是我们最好的猜测,并且被称为 MaxLike 估计。这也是它们为什么被称为“帽子”的原因。实际上,我们再次取对数并最小化 NLL:

你在第三章中看到,你可以通过 SGD 找到最小化损失函数的网络权重。现在让我们这样做。我们必须在 Keras 中定义一个新的损失函数。你可以在 Keras 中通过定义一个函数来实现自定义损失函数,该函数接受真实值和网络预测作为输入。如图 4.11 所示,线性回归被定义为一个简单的网络,它预测a ⋅ x**[i] + b,其中权重a给出斜率,偏置b给出截距。方程式 4.4 中的损失函数的损失编码如下所示(你可以在笔记本中找到此代码)。
实践时间打开mng.bz/YrJo并运行代码,以了解如何使用 MaxLike 方法确定线性回归模型中的参数值。为此,NLL 被定义为通过 SGD 最小化的损失函数。 |
|---|
列表 4.4 估计 MaxLike 解
def my_loss(*y*_true,y_pre*D*): ❶
loss = -tf.reduce_sum(tf.math.log(f(*y*_true,y_pre*D*))) ❷
return loss
model = Sequential() ❸
model.add(Dense(1, activation='linear', ❸
batch_input_shape=(None, 1)))
model.compile(loss=my_loss,optimizer="adam")
❶ 定义一个自定义损失函数
❷ 计算所有损失的加和(见方程式 4.4)
❸ 设置一个与线性回归等价的神经网络;包含一个线性激活和一个偏置项
在第三章中,你已经通过 SGD 最小化了 MSE 损失函数,并得到了a = 1.1 和b = 87.8 作为最优参数值。实际上,这里显示的 MaxLike 估计与第三章的 MSE 方法相同。对于详细的推导,请参阅以下侧边栏。
基于 MaxLike 的线性回归均方误差损失函数推导
让我们一步一步地跟随 MaxLike 方法来推导经典线性回归任务的损失。MaxLike 方法告诉你,你需要找到神经网络中权重 w 的这些值。这里 w = (a, b)(参见侧边栏中标题为“使用参数概率模型的 MaxLike 方法对分类损失进行分类”的图),这些值最大化了观察数据的似然。数据以 n 对(xi, y**[i] )的形式提供。以下乘积显示了数据的似然:

最大化这个乘积会导致与最小化相应的 NLL(见第 4.1 节)相同的结果。

现在将正态密度函数的表达式(参见侧边栏中的第二个方程,“正态分布回顾”)代入。

然后,让我们使用规则 log( c ⋅ d ) =log ( c ) + log( d ) 和 log( e**^g ) = g ,以及 ( c − d )² =( d − c )² 的事实:

添加一个常数不会改变最小值的位置,并且因为第一个项相对于
a 和 b 是常数,我们可以省略它:


乘以一个常数因子也不会改变最小值的位置。我们可以自由地乘以常数因子 2 ⋅ σ²/n,这样我们最终得到均方误差损失公式:

通过这种方式,我们已经推导出了损失函数,我们需要最小化它以找到权重的最优值。请注意,你只需要假设 σ[2] 是常数;你不需要推导出 σ[2] 的值来推导经典线性回归模型的损失函数:

哇!我们完成了寻找 a 和 b 参数值以最小化平方残差之和的任务。这可以通过最小化均方误差(MSE)来实现。最大似然(MaxLike)方法确实引导我们得到了一个损失函数,这个损失函数实际上就是均方误差(MSE)!在简单线性回归的情况下,拟合值是 ŷ**[i] = μ**[x[i]] = a ⋅ x**[i] + b,从而得到:

让我们回顾一下到目前为止我们所做的工作。首先,我们使用神经网络来确定概率分布的参数。其次,我们选择正态分布来模拟我们的数据。正态概率分布有两个参数:μ 和 σ。我们保持 σ 不变,并仅使用最简单的模型——线性回归来模拟 μ[x[i]],即:μ**[x[i]] = a ⋅ x**[i] + b。对应于 x 值(年龄)的 y 值(SBP)分布类似于正态分布:
Y**[x[i]] ~ N μ**[x[i]] =a ⋅ x**[i] + b , σ²
这意味着 y 是一个来自具有均值 μ[x[i]] 和标准差 σ 的正态分布的随机变量。我们可以以几种方式扩展这种方法:
-
我们可以选择除了正态分布以外的其他概率分布。实际上,在某些情况下,正态分布是不够的。以计数数据为例。正态分布总是包含负值。但某些数据,如计数数据,没有负值。我们在第五章中处理这些情况。
-
我们可以使用一个完整的神经网络(NN)来代替线性回归来建模 μ[x[i]],这在下一节中会进行说明。
-
我们不需要坚持数据在整个输入范围内的可变性是恒定的假设,也可以通过神经网络来模拟 σ,并允许,例如,不确定性增加。我们在 4.3.3 节中这样做。
4.3.2 使用具有隐藏层的神经网络来模拟输入和输出之间的非线性关系
没有隐藏层的神经网络(见图 4.11)模拟输入和输出之间的线性关系:out = a ⋅ x + b。现在你可以扩展这个模型,并使用一个或多个隐藏层的神经网络来模拟 μ**[x]。我们仍然假设方差 σ² 是常数。使用图 4.15 中的神经网络,对于每个输入 x,你模拟了由以下给出的输出整个条件概率分布(CPD):
Y**[x[i]] ∼ N(μ**[x[i]] , σ²)
如果您在您的 NN 中添加至少一个隐藏层,您会看到这些 CPD 的均值 μ[x[i]] 不需要沿直线(参见图 4.15)。在列表 4.5 中,您可以看到如何从一个具有正弦形状的函数中模拟一些数据,并将具有三个隐藏层和 MSE 损失函数的 NN 拟合到数据,从而得到一个拟合良好的非线性曲线(参见图 4.14)。
列表 4.5 使用 MSE 损失函数来模拟 fcNN 中的非线性关系
x,y = create_random_data(n=300) ❶
model = Sequential() ❷
model.add(Dense(1, activation='relu',
batch_input_shape=(None, 1)))
model.add(Dense(20, activation='relu'))
model.add(Dense(50,activation='relu'))
model.add(Dense(20, activation='relu'))
model.add(Dense(1, activation='linear'))
opt = optimizers.Adam(lr=0.0001)
model.compile(loss='mean_squared_error',optimizer=opt)
history=model.fit(x, *y*, ❸
batch_size=n,
epochs=10000,
verbose=0,
)
❶ 创建一些随机数据(参见图 4.14)
❷ 定义具有 3 个隐藏层和 ReLU 激活的 fcNN
❸ 使用 MSE 损失函数拟合 NN
通过这种扩展,您可以模拟输入和输出之间的任意复杂非线性关系,例如,例如正弦波(参见图 4.14)。

图 4.14 使用具有三个隐藏层和 MSE 损失函数的 fcNN 将正弦曲线(实线)拟合到数据点(见点)
这是如何工作的?图 4.11 中的模型只能绘制直线模型。为什么稍微扩展的 NN(参见图 4.15)能够模拟如此复杂的曲线?在第二章中,我们讨论了隐藏层如何使我们能够以非线性方式从输入特征构造新的特征。例如,一个具有一个隐藏层和八个神经元的 NN(参见图 4.15)允许 NN 从输入 x 构造八个新的特征。

图 4.15 扩展线性回归。隐藏层中的八个神经元给出了计算输出“out”的特征。
然后,NN 模拟这些新特征与结果之间的线性关系。损失函数的推导保持不变,并导致 MSE 损失公式:

在具有隐藏层的神经网络的情况下(例如,参见图 4.12),模拟的输出 ŷ**[i] = f**[NN]4.11 是输入 x**[i] 和神经网络中所有权重的一个相当复杂的函数。这与由没有隐藏层的神经网络编码的简单线性模型(参见图 4.11)的唯一区别是,其中拟合值是权重和输入的简单线性函数 ŷ**[i] = f**[NN]4.6 = a ⋅ x**[i] + b 。
4.3.3 使用具有额外输出的神经网络进行具有非恒定方差的回归任务
经典线性回归中的一个假设是同方差性,意味着结果的方差不依赖于输入值 x。因此,您只需要一个输出节点来计算条件正态分布的第一个参数 μ x(参见图 4.11 和 4.15)。如果您还允许第二个参数,σx ,依赖于 x,那么您需要一个具有第二个输出节点的神经网络。如果输出 CPD 的方差不是常数而是依赖于 x,我们称之为异方差性。
从技术上讲,你可以通过添加第二个输出节点(见图 4.16)轻松实现这一点。因为图 4.15 中的神经网络也有一个隐藏层,它允许输入和输出之间存在非线性关系。输出层中的两个节点提供了 CPD N(μ**[x] , σ**[x]²) 的参数值 μ x 和 σx。当在输出层中使用线性激活函数时,你可以得到负的和正的输出值。因此,第二个输出不是直接作为标准差 σx,而是作为 log(σ**[x])。然后从 out2 计算标准差,即 σ**[x] = e^(out[2]),确保 σx 是一个非负数。

图 4.16 你可以使用具有两个输出节点的神经网络来控制条件结果分布 N( μ x, σ x) 的参数 μ x 和 σ x,以进行具有非常数方差的回归任务。
因为经典的线性回归假设方差 σ[2] 是常数(称为同方差性假设),你可能会怀疑,如果你想要允许 σ[2](称为异方差性)变化,这会使事情变得更加复杂。但幸运的是,情况并非如此。同方差性假设仅在损失函数的推导中使用,以消除包含 σ[2] 的项,导致 MSE 损失。但如果你不假设方差是常数,你就不能进行这一步。损失仍然由方程 4.4 中定义的 NLL 给出

使用以下损失

如果你想要像传统统计学那样解析地解决这个问题,σ 非常数的事实会导致问题。但如果你放弃闭式解,优化这个损失根本不是问题。你又可以再次使用 SGD 机制来调整权重以实现最小损失。在 TensorFlow 或 Keras 中,你可以通过定义一个自定义损失,然后使用这个自定义损失进行拟合过程(参见列表 4.6),该过程使用方程 4.8 中的损失函数。
为什么你不需要知道方差的真实值
在向学生介绍 MaxLike 原则时,我们经常被问到这样的问题:“如果没有地面真实值,你如何确定方差?”让我们再次看看图 4.16 中的架构。该网络有两个输出:一个直接对应于结果分布的期望值 μ[x[i]],另一个对应于结果分布的标准差 σ x**[i] 的变换。虽然选择更接* yi 值的 μ[x[i]] 感觉上似乎是自然的,但你可能会想知道在没有给出作为地面真实值的信息的情况下,你如何拟合,例如,结果分布的标准差。你只有每个输入 x**[i] 的观察结果 yi。但 MaxLike 方法非常美妙,并且正在为你完成这项工作!
回想一下血压数据集(见图 4.12),其中假设数据在整个年龄范围内的分布是相同的。让我们暂时忘记回归任务,转向一个更简单的任务:你只想通过正态分布来建模 45 岁女性的血压。让我们想象这四位女性血压大约都是 131(比如,130.5、130.7、131、131.8)。作为 45 岁年龄段的血压分布,你可能想使用一个具有参数值 μ[x[i]] 在观测(均值)值 131 的正态钟形曲线,且 σ x**[i] 接*零。这应该会在 131 的观测值上产生最大可能性(参见下图中左侧面板)。感觉上很自然,μ[x[i]] 是由真实值 yi 决定的。但是,你将如何处理数据表现出高变异性的情况?
例如,假设你数据集中的四位 45 岁女性的血压分别为 82、114、117 和 131。在这种情况下,你可能不会想使用图左侧面板所示的高斯钟形曲线,因为只有血压为 131 的观测值有较高的可能性;其他三个观测值有极小的可能性,导致整体(联合)可能性很小。为了最大化联合可能性,使用所有四个观测值都有合理高可能性的高斯曲线会更好(参见下图中右侧面板)。

在几乎没有任何数据变异性的情况下,最大化四个观测值 SBP 的联合可能性(左侧),其中所有四个观测值都接* 131,或者在大数据变异性的情况下(右侧),四个观测值 SBP 为 82、114、117 和 131,差异很大。线的长度表示观测 SBP 值的可能性。
可选练* 打开 mng.bz/YrJo 并逐步执行代码,直到你达到练* 2,然后完成练* 2。绘制一个正态分布,并像前一个侧边栏中的图所示,绘制观测值的可能性。手动调整正态分布的参数值以实现最大联合可能性或最小 NLL。开发代码,通过梯度下降确定最佳参数。 |
|---|
如果你做了练*,你会看到当曲线的分布与数据相似时,似然性最大化,确实,当使用四个观察值的方差作为参数σx 时,它达到最大。这意味着你用神经网络建模的N(μ**[x[i]],σ**[x[i]])分布尽可能反映了数据的分布。在回归的情况下,情况更复杂,因为你在不同的x值上有相邻的点。参数σ[x[i]]在相邻的x值上不能有完全不同的值,但它应该是一个平滑的曲线。如果网络有足够的灵活性,那么在观察结果分布较大的区域(例如,图 4.17 中x = 5 和x = 15 附*),你应该使用一个相当宽的条件正态分布,即较大的参数σ[x[i]]。相反,在分布较小的区域(例如,图 4.17 中x = 0 附*),σ[x[i]]应该较小。这样,似然方法允许你估计条件正态分布的参数,而不需要这些值作为确切的值。
让我们暂停一下,回顾一下原始问题。你为什么一开始就认为你有均值的确切值,而不是方差?你拥有的只是数据和从数据中假设生成的模型。在我们的情况下,模型是高斯分布N(μ**[x[i]],σ**[x[i]]),网络确定其参数μ[x[i]],σ[x[i]]。你优化神经网络的权重,使数据的似然性最大化。均值也没有确切的值。神经网络只会估计条件分布的μ和σ,这两个都是估计值。μ和σ都没有确切的值。没有勺子。
列表 4.6 来自方程 4.5 的非线性异方差回归模型
import math
def my_loss(*y*_true,y_pre*D*): ❶
mu=tf.slice(*y*_pred,[0,0],[-1,1]) ❷
sigma=tf.math.exp(tf.slice(*y*_pred,[0,1],[-1,1])) ❸
a=1/(tf.sqrt(2.*math.pi)*sigma)
b1=tf.square(mu-y_true)
b2=2*tf.square(sigma)
b=b1/b2
loss = tf.reduce_sum(-tf.math.log(a)+b,axis=0)
return loss
model = Sequential() ❹
model.add(Dense(20, activation='relu',batch_input_shape=(None, 1)))
model.add(Dense(50, activation='relu'))
model.add(Dense(20, activation='relu'))
model.add(Dense(2, activation='linear'))
model.compile(loss=my_loss,\
optimizer="adam",metrics=[my_loss]) ❺
❶ 定义自定义损失
❷ 提取第一列用于μ
❸ 提取第二列用于σ
❹ 定义一个具有 3 个隐藏层的神经网络,如列表 4.4 所示,但现在有 2 个输出节点
❺ 使用自定义损失进行拟合
你现在可以通过不仅绘制拟合值的曲线,还绘制拟合值加减 1 或 2 倍拟合标准差的曲线来绘制拟合。这说明了拟合 CPD(见图 4.17)的分布变化。

图 4.17 拟合值遵循正弦形状的曲线。实线中间线给出拟合的μ x的位置,其标准差在变化。两条细的外线对应于 95%预测区间(μ - 2σ,μ + 2σ)。我们使用具有三个隐藏层、两个输出节点和自定义损失的神经网络来拟合数据点(见点)。
你可以任意设计这个网络深度和宽度,以模拟x和y之间的复杂关系。如果你只想允许输入和输出之间存在线性关系,你应该使用没有隐藏层的 NN。
可选练* 打开mng.bz/GVJM并运行代码,直到你达到由笔形图标指示的第一个练*。你将看到如何模拟图 4.17 中显示的数据以及如何拟合不同的回归模型。在这个练*中,你的任务是尝试不同的激活函数。 |
|---|
你现在已经看到了如何使用 MaxLike 方法推导损失函数。你只需要为你的数据定义一个参数模型。如果你想开发一个预测模型,那么你需要选择一个 CPD p(y|x)的模型。CPD 给出了观测结果的似然。要按照深度学*的方式遵循这种建模方法,你设计一个 NN,该 NN 输出概率分布的参数(或可以从这些参数推导出的值)。其余的工作由 TensorFlow 或 Keras 完成。你使用 SGD 来找到 NN 中权重的值,这些权重导致模型参数值最小化 NLL。在许多情况下,NLL 被作为预定义的损失函数提供,例如交叉熵或 MSE 损失。但你也可以通过定义一个与 NLL 对应的自定义损失函数来处理任意似然。你在列表 4.6 中看到了一个自定义损失函数,它定义了具有非常数方差的回归问题的 NLL。
摘要
-
在最大似然(MaxLike)方法中,你调整模型的参数,使得产生的模型以比所有具有不同参数值的其他模型更高的概率产生观测数据。
-
MaxLike 方法是一个多功能的工具,用于拟合模型的参数。它在统计学中得到广泛应用,并提供了一个坚实的理论框架来推导损失函数。
-
要使用 MaxLike 方法,你需要为观测数据定义一个参数概率分布。
-
离散变量的似然由离散概率分布给出。
-
连续结果的似然由连续密度函数给出。
-
MaxLike 方法包括:
-
定义观测数据的(离散或连续)概率分布的参数模型
-
对于观测数据最大化似然(或最小化 NLL)
-
-
要开发一个预测模型,你需要选择一个给定输入x的输出y的条件概率分布(CPD)的模型。
-
使用 MaxLike 方法进行分类任务基于伯努利或多项式概率分布,如 CPD,导致 Keras 和 TensorFlow 中已知的分类标准损失,即交叉熵。
-
Kullback-Leibler(KL)散度是预测和真实 CPD 之间的度量。最小化它具有与最小化交叉熵相同的效果。从这个意义上讲,KL 散度是分类模型中均方误差(MSE)的对应物。
-
使用基于正态概率分布(如 CPD)的线性回归任务的 MaxLike 方法会导致均方误差(MSE)损失。
-
对于具有常数方差的线性回归,我们可以将网络的输入 x 的输出解释为条件正态分布 N(μ**[x], σ) 的参数 μ**[x]。
-
非常数方差回归可以通过与正态概率模型的负对数似然(NLL)相对应的损失函数来拟合,其中两个参数(均值和标准差)都依赖于输入 x,并且可以通过具有两个输出的神经网络来计算,该输出产生条件概率分布(CPD)N(μ**[x], σ**[x])。
-
通过引入隐藏层,可以使用均方误差(MSE)损失来拟合非线性关系。
-
通常,可以在神经网络框架中通过将神经网络的输出解释为概率分布的参数来最大化任意 CPD 的似然。
1.虽然这对本书的其余部分并不重要,但如果您对如何得到数字 45 感兴趣,它是所有排列 10!经过(除以)不可区分排列数校正后的数量。也就是说,10! / (2! · 8!) = 45 在我们的情况下。更多细节,例如,请参阅mng.bz/gyQe。
5 TensorFlow Probability 中的概率深度学*模型
本章涵盖了
-
概率深度学*模型简介
-
新数据上的负对数似然作为适当的性能指标
-
为连续和计数数据拟合概率深度学*模型
-
创建自定义概率分布

在第三章和第四章中,你遇到了一种固有的数据不确定性。例如,在第三章中,你在血压的例子中看到,年龄相同的两位女性可以有相当不同的血压。即使是同一位女性,在同一个星期内不同时间测量的血压也可能不同。为了捕捉这种数据固有的变异性,我们使用了条件概率分布(CPD):P(y|x)。通过这个分布,你通过模型捕捉了 y 的输出变异性。在深度学*社区中,为了指代这种固有的变异性,使用了术语“随机不确定性”。这个术语“随机”来源于拉丁语单词 alea,意为骰子,正如“Alea iacta est”(骰子已掷出)。
在本章中,我们进一步关注开发和评估概率模型以量化随机不确定性。为什么你应该关注不确定性?这不仅仅关乎理论上的繁琐;它对于基于预测做出关键或昂贵的决策具有实际的重要性。想想看,比如一个纽约出租车司机在 25 分钟内将艺术品经销商送到一个即将开始的盛大艺术品拍卖会的情况。如果她准时到达,艺术品经销商承诺给予丰厚的小费(500 美元)。这对出租车司机来说很重要!幸运的是,他拥有最新的设备,他的旅行时间预测工具基于一个概率模型,该模型为旅行时间预测提供概率建议。
工具提出了两条通往拍卖会的路线。路线 1 预测的平均旅行时间为μ[1] = 19 分钟,但不确定性很高(标准差σ[1] = 12 分钟),而路线 2 的平均旅行时间为μ[2] = 22 分钟,但不确定性很小(标准差σ[2] = 2 分钟)。即使路线 2 的平均旅行时间比路线 1 长得多,选择路线 2 获得小费的概率约为 93%1。选择平均旅行时间为 19 分钟的路线 1 获得小费的概率约为 69%2。但这样的信息只能从一个预测所有可能旅行时间的可靠概率分布的概率模型中得出。
从第四章,你知道如何拟合概率模型。你使用神经网络(NN)来确定预测 CPD(条件概率分布)的参数。原则上,开发概率深度学*模型是容易的:
-
你选择一个合适的分布模型来预测结果。
-
你设置一个 NN,其输出节点数量与模型参数数量相同。
-
你从选定的分布中推导出负对数似然(NLL)函数,并使用该函数作为损失函数来训练模型。
注意:为了使我们的术语一致,当我们谈论输出时,我们指的是 NN 最后一层的节点;当我们谈论结果时,我们指的是目标变量 y。
第四章重点介绍了用于拟合模型的极大似然(MaxLike)方法。这种方法导致 NLL 作为损失函数。你可以手动确定所选概率分布的 NLL。我们在第四章的第 4.2 节中针对分类问题,在第 4.3 节中针对标准回归问题进行了这种操作。但正如你在第四章中看到的,我们很快就需要一些微积分和编程来推导出 NLL 作为损失函数。
在本章中,你将了解 TensorFlow Probability(TFP),这是 TensorFlow 的一个扩展,它使得在不要求你手动定义相应的损失函数的情况下轻松拟合概率深度学*模型变得容易。你将看到如何使用 TFP 进行不同的应用,并且你将获得对幕后发生的事情的直观理解。拟合概率模型允许你轻松地结合你的领域知识:你只需选择一个合适的结果分布(在图 5.1 中,它被描绘为深度学*机器中的分布板)并因此模拟现实世界数据的随机性。
在本章中,你还将为不同的任务开发高性能的概率深度学*模型。概率模型可以通过两种方式优化。首先,我们选择一个合适的架构,例如图 5.1 中的网络架。 (我们在第二章中讨论了这一点。)其次,这也是本章的重点,我们通过选择合适的结果分布来增强模型。但一个人最初如何确定概率模型的性能呢?你会发现选择最佳性能的概率模型的准则非常简单!在新数据上具有最低 NLL 的模型是最佳性能的概率预测模型。
5.1 评估和比较不同的概率预测模型
概率预测模型的目标是在新数据上产生准确的概率预测。这意味着在给定的 x 处预测的 CPD 应该尽可能匹配观察到的分布。正确的衡量标准很简单。你已经在第四章中看到了它;它是训练数据上最小化的 NLL。但现在,它是在未用于训练的新数据(测试数据)上评估的。在测试数据上的 NLL 越低,模型对新数据的预期性能越好。甚至可以证明,在评估模型的预测性能时,测试 NLL 是最优的。3

图 5.1 深度学*(DL)中概率建模的基本思想。网络确定概率分布的参数。我们使用强大的最大似然原理来拟合模型。在图中,结果由正态分布建模,其中神经网络用于控制一个参数(参见选定的最后一个带有单个输出节点的板),通常是平均值。
在模型开发的过程中,你通常会调整你的模型。在这个过程中,你反复将几个模型拟合到你的训练数据上,并在验证数据上评估其性能。最后,你选择在验证数据上预测性能最高的模型。但是,当你总是使用相同的验证数据来检查模型的性能时,你可能会在验证数据上过度拟合。因此,在深度学*(DL)以及机器学*(ML)中,使用三个数据集是一个好的实践:
-
一个用于拟合模型的训练数据集
-
一个用于检查模型预测性能的验证数据集
-
一个在模型选择过程中任何一点都没有被触及的测试数据集,并且只用于评估最终模型的预测性能
有时候,无法获得所有三个数据集。在统计学中,只使用训练数据和测试数据是很常见的,其中测试数据扮演了验证和测试数据的角色。在机器学*和统计学中,常用的另一种方法是交叉验证。在该技术中,你反复将训练数据分成两部分,一部分用于训练,另一部分用于验证。因为这种交叉验证过程需要我们多次重复耗时较大的训练过程,所以深度学*者通常不采用这种技术。
警告 在统计学中,有时我们只使用一个数据集。为了仍然能够评估在相同数据上开发的预测模型的表现,经过长时间的发展,一些复杂的方法仍然在一些统计学术界中使用。这些方法考虑了模型在拟合过程中看到了数据这一事实,并应用了校正来考虑这一点。这些方法包括,例如,赤池信息准则(AIC)或贝叶斯信息准则(BIC)。不要混淆。如果你有一个验证集,你不需要这些方法。
5.2 介绍 TensorFlow Probability (TFP)
在本节中,你将了解一种方便的方法来将概率模型拟合到你的数据上。为此,我们引入了建立在 TensorFlow 之上并针对概率深度学*模型定制的 TFP。使用 TFP 允许你以结果分布模型的方式思考。它让你免于为 NN 输出手工制作合适的损失函数。TFP 提供了特殊的层,你可以插入一个分布。它让你能够在不设置任何公式或函数的情况下计算观测数据的似然性。在上一个章节中,你设置了没有 TFP 的概率模型,这是可能的,但有时比较繁琐。记得第四章中的程序吗?
-
你为结果选择一个合适的分布。
-
你设置一个具有与所选结果分布中参数数量相同的输出的神经网络(NN)。
-
你定义一个损失函数,该函数产生 NLL。
在线性回归的情况下,你选择高斯分布作为 CPD(条件概率分布)的模型(参见图 5.2,展示了血压的例子)。在这种情况下,你的 CPD 是 N(μ**[x], σ**[x]),它只有两个参数:均值 (μ**[x]) 和标准差 (σx)。对于高斯分布,你可以定义一个 95%的预测区间,这个区间覆盖了 95%的观测结果。95%预测区间的边界 q^(2.5%) , q^(97.5%) ,通常是 0.025 和 0.975 分位数。这些量也被称为 2.5%和 97.5%的分位数。0.975 分位数 (q^(97.5%)) 是结果分布中这样一个值,即 97.5%的所有观测结果都小于或等于这个值。在正态分布 N(μ**[x], σ**[x]) 中,97.5%的分位数由以下公式给出:
q^(97.5%) = μ**[x] + 1.96 ⋅ σ**[x] ≈ μ**[x] + 2 ⋅ σ**[x] .

图 5.2 展示了血压例子的散点图和回归模型。钟形曲线是结果 SBP(收缩压)的条件概率分布(CPD),它是基于观测值 x(年龄)的。实线水平条的长度表示 22 岁女性观测到 SBP 为 131 的似然性。
如果你假设标准差是常数,那么损失函数的推导就变得相当简单。这是因为当最小化负对数似然(NLL)时,你可以忽略所有依赖于标准差的部分。经过一些推导后,发现最小化平均 NLL 与最小化平均均方误差(MSE)是相同的(参见第四章侧边栏“基于 MaxLike 的线性回归中 MSE 损失的推导”):

其中 x 是我们例子中的年龄。在斜率和截距优化后,你可以从残差中推导出常数标准差。你需要知道这个标准差来得到一个具有已知 CPD P(Y|X = x) = N(y ; μ**[x] , σ) 的概率模型,这允许你确定观测结果的似然性。
但如果你想让标准差依赖于x,损失函数的推导会变得更加复杂。在最小化 NLL 时,依赖于方差的似然中的项不能再被忽略。结果损失不再是 MSE,而是一个更复杂的表达式(推导过程见第四章侧边栏“基于 MaxLike 的线性回归中 MSE 损失的推导”):4

这在 Keras 中不是一个标准的损失函数;因此,你需要定义一个自定义损失函数,然后将其编译到模型中。在第四章中,你看到了如何推导 NLL 以及如何使用自定义损失函数。手动做这件事可以让你对整个拟合过程有一个很好的理解和完全的控制。但这也可能是容易出错的,并且并不真正方便。因此,你现在将了解 TFP,它可以大大促进你的工作。
TFP 允许你专注于模型部分。在概率模型中思考时,你试图找到一个预测模型,该模型对新数据预测 CPD,并赋予观察到的结果高似然。因此,为了衡量概率模型的性能,你使用观察数据的联合似然。因此,你使用负对数似然作为概率模型“坏度”的度量,这也是为什么你使用 NLL 作为损失函数的原因。
请参阅图 5.2,其中 CPD 是一个具有常数方差σ的正态N(μ**[x], σ)分布的例子。参数由μ**[x] = a · x + b 模型,这是其标准形式的线性回归。
你将在本章后面看到,对于其他任务,如使用低平均计数建模离散计数数据,正态分布并不是最佳选择。你可能想为 CPD 选择另一个分布族;例如,泊松分布。在 TFP 框架内,这并不是什么大问题。你可以简单地更改分布。你将在本章的几个示例中看到如何做到这一点。
实践时间 打开mng.bz/zjNw 。在阅读时逐步查看笔记本。 |
|---|
由于概率分布是捕捉概率模型中不确定性的主要工具,让我们来看看如何使用 TFP 分布。TFP 提供了一系列快速增长的分布。正态分布是你可能最熟悉的分布族,所以让我们从定义 TFP 中的正态分布开始(参见列表 5.1)。我们将从中采样,然后确定采样值的似然。
列表 5.1 使用 TFP 正态分布
import tensorflow_probability as tfp
tfd = tfp.distributions
d = tfd.Normal(loc=[3], scale=1.5) ❶
x = d.sample(2) ❷
px = d.prob(x) ❸
print(x)
print(px)
❶ 创建一个均值为 3,标准差为 1.5 的一维正态分布
❷ 从正态分布中采样两个实现
❸ 计算定义的正态分布中每个采样值的似然
您可以使用 TFP 分布用于不同的目的。请参阅表 5.1 和笔记本,了解您可以应用于 TFP 分布的一些重要方法。
表 5.1 TFP 分布的重要方法 a
| TensorFlow 分布的方法 | 描述 | 在dist = tfd.Normal(loc=1.0, scale=0.1)上调用方法时的数值结果 |
|---|---|---|
sample(n) |
从分布中采样 n 个数字 | dist.sample(3).numpy()``array([1.0985107, 1.0344477, 0.9714464], dtype = float32)注意,这些是随机数。 |
prob(value) |
返回值的似然(对于连续结果的模型的情况是对数概率密度)或概率(对于离散结果的模型的情况) | dist.prob((0,1,2)).numpy()``array([7.694609e-22, 3.989423e+00, 7.694609e-22], dtype = float32) |
log_prob(value) |
返回值的对数似然或对数概率 | dist.log_prob((0,1,2)).numpy()``array([-48.616352, 1.3836466, -48.616352], dtype = float32) |
cdf(value) |
返回累积分布函数(CDF),这是给定值(张量)的总和或积分 | dist.cdf((0,1,2)).numpy()``array([7.619854e-24, 5.000000e-01, 1.000000e+00], dtype = float32) |
mean() |
返回分布的均值 | dist.mean().numpy()1.0 |
stddev() |
返回分布的标准差 | dist.stddev().numpy()0.1 |
a 更多方法,请参阅 mng.bz/048p 。 |
5.3 使用 TFP 建模连续数据
在本节中,您将使用 TFP 进行线性预测模型。您将调整线性模型以最佳地拟合显示相当复杂变异性的数据。
在进行第一次模型选择实验时,最好从一些可以完全控制数据结构的模拟数据开始。您可以通过将模拟数据随机分成三部分来获取训练、验证和测试数据,然后锁定测试数据。可视化合成训练和验证数据表明它看起来有点像鱼(见图 5.3)。测试数据不应被触及;您甚至不应查看它!图 5.3 没有显示测试数据。如果您得到这些数据并手动尝试在数据上绘制平滑曲线,您可能会得到一条直线。但还有更多需要注意的事情:数据的可变性不是一个常数,而是在x范围内变化的。在本章和下一章中,您将学*如何设置考虑这些特性的概率模型。

图 5.3 合成数据集的训练(左)和测试(右)数据的可视化
5.3.1 使用恒定方差拟合和评估线性回归模型
假设你想要为图 5.3 所示的数据开发一个概率预测模型。在检查数据后,第一个想法是采用线性模型。让我们使用 TFP 框架来设置一个概率线性回归模型(见列表 5.2)。为此,你选择一个正态分布 N(μ**[x], σ[2]) 作为 CPD。
假设我们考虑标准的线性回归设置,其中只有参数 μ x 依赖于输入 x,并且标准差是一个常数。在 TFP 中如何处理常数标准差?你在第四章中看到(见标题为“基于 MaxLike 的线性回归 MSE 损失的推导”的侧边栏)恒定方差值不会影响线性模型的估计。因此,你可以自由选择任何恒定方差值;例如,当使用 tfd.Normal``() 时选择 1(见列表 5.2)。相应地,你的神经网络只需要估计参数 μ x 的一个输出。
TFP 允许你混合 Keras 层与 TFP 分布。你可以通过 tfp.layers.DistributionLambda 层将神经网络的输出与一个分布连接起来。这个层接受两个东西:一个分布及其参数的值。从技术上讲,像 tf.distributions.Normal 这样的分布是 tf.distributions.Distribution 的一个实现。因此,你可以调用表 5.1 中显示的方法。分布参数(如正态分布的 loc (μ) 和 scale (σ))由前一层的值给出。
在列表 5.2 中,你看到如何使用 tfp.layers.DistributionLambda 层来构建 CPD P(y|x, w)。在这里,我们选择一个具有位置参数 μ x 的正态分布 N(μ**[x], σ[2]),该参数依赖于 x,以及一个固定尺度参数 σ**[x] = 1。相应的 NLL 直接由 CPD 分配给观察到的结果 y 的概率给出:-log(P(y|x, w))。因为 tfp.layers.DistributionLambda 的结果 distr 是 tfd.Distribution 类型,这转化为 -distr.log_prob(y)(见表 5.1 的第二行)。因此,TFP 让你免于推导和编程适合你模型的适当损失函数。
列表 5.2 使用 TFP 进行具有常数方差的线性回归
from tensorflow.keras.layers import Input
from tensorflow.keras.layers import Dense
from tensorflow.keras.layers import Concatenate
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
def NLL(*y*, distr):
return -distr.log_prob(*y*) ❶
def my_dist(params): ❷
return tfd.Normal(loc=params, scale=1)
# set the sd to the fixed value 1
inputs = Input(shape=(1,))
params = Dense(1)(inputs) ❸
dist = tfp.layers.DistributionLambda(my_dist)(params) ❹
model_sd_1 = Model(inputs=inputs, outputs=dist) ❺
model_sd_1.compile(Adam(), loss=NLL) ❻
❶ 在拟合分布 distr 下计算观察到的 y 的 NLL
❷ 使用最后一层的输出(params)作为分布的参数
❸ 设置具有一个输出节点的神经网络
❹ 调用一个分布层以使用参数 params 调用函数 my_dist
❺ 将神经网络的输出与一个分布连接起来
❻ 使用 NLL 作为损失函数编译模型
在列表 5.2 中的 TFP 代码中,你已经拟合了一个线性回归模型。但它是概率模型吗?概率模型需要为每个输入x提供整个 CPD。在高斯 CPD 的情况下,这不仅需要估计均值μ x,还需要标准差σ。在标准的线性回归情况下,方差是独立于x位置选择的常数。在这种情况下,我们可以通过残差的方差来估计σ[2]。这意味着你首先需要拟合线性模型,然后才能确定用于所有 CPD 的方差。
现在,你已经准备好使用训练好的模型对你的验证数据进行一些概率预测。对于每个测试点,你将预测一个高斯 CPD。为了可视化模型在验证数据上的表现,你可以绘制验证数据以及 CPD 预测的均值,μ x(见图 5.4 中的实线),以及 CPD 均值加减两倍标准差的位置,μ**[x[i]] ± 2 ⋅ σ**[x],对应于 0.025 和 0.975 分位数(见图 5.4 中的虚线)。

图 5.4 线性回归的合成验证数据以及预测的概率模型,这与没有隐藏层的 NN 相同。CPD 的均值μx 由 NN 建模,标准差假设为常数。黑色实线表示μxi 的位置,虚线表示 0.025 和 0.975 分位数的位置。
|
| 实践时间 打开 mng.bz/zjNw 。逐步执行代码以解决练* 1,并在阅读时跟随代码。
-
使用 TFP 来拟合具有常数标准差的线性模型。
-
哪个常数标准差会产生最低的验证 NLL?
|

图 5.5 验证数据以及预测的高斯 CPDs。使用的模型是具有常数数据变异性的线性模型(见列表 5.2)。黑色实线表示均值的定位,虚线表示 0.025 和 0.975 分位数的定位。对于四个选定的数据点(实心点),显示了预测的 CPD。相应的似然性由连接点与曲线的实线段表示。
当你视觉上检查拟合(见图 5.4)时,你可能会对均值的定位(黑色实线)感到满意。但结果分布的变化范围并没有被捕捉到。这重要吗?当然重要。为了更好地看到这一点,让我们检查 CPD 与数据分布的匹配程度。
一个匹配的 CPD 可以为观察到的结果分配高似然。为了可视化,让我们为四个随机选择的测试点这样做:(-0.81, -6.14), (1.03, 3.42), (4.59, 6.68), 和 (5.75, 17.06)(见图 5.5 中的填充圆圈)。图 5.5 中选定点x的位置由四条虚线垂直线指示。训练好的 NN 从x值预测 CPD。你得到四个具有不同均值但相同标准差的高斯分布。每个 CPD 为给定测试点x**[test]的输入的所有可能结果y提供概率分布:P(y|μ**[x[test]] , σ)。CPD 分配给实际观察到的y**[test]的似然由连接填充点与曲线的横线表示:P(y**[test]|μ**[x[test]] , σ)。
在第一次查看图 5.5 时,模型似乎为真实结果分配了合理的似然。为了量化这一点,你可以确定每个测试点分配给真实观察的似然,并通过验证集中所有点的平均 NLL 来总结,在这种情况下:5
NLL(恒定标准差模型) = 3.53
你如何得到一个能更好地预测高斯 CPD 的模型?记住,模型越好,验证集的 NLL(负对数似然)就越低。对于给定的标准差,一个点如果位于高斯 CPD 的中心,就会得到最优似然。好的,四个选定的点(见图 5.5)实际上并不位于它们 CPD 的中心。如果那样的话,它们会位于粗体线上,但它们相当接*。高斯的一个第二个调整参数是标准差。
5.3.2 使用非恒定标准差的线性回归模型进行拟合和评估
让我们尝试为考虑数据变化分布的合成数据获得更好的预测模型(见图 5.3)。你如何调整你的模型以允许具有非恒定标准差的高斯 CPD?没问题!只需允许模型学*数据变化!你仍然假设正态分布作为 CPD,但这次你想要学*两个参数。
NN 的输出原则上可以取从负无穷大到正无穷大的任何值。确保标准差始终为正的一种方法是将 NN 的输出通过指数函数。我们之前已经这样做过了。一种流行的替代方法是使用softplus函数。在图 5.6 中,你可以看到softplus函数在指数(exp)函数旁边。与指数函数不同,softplus函数在大的x值上线性增加。

图 5.6 softplus函数与指数(exp)函数的比较。这两个函数将任意值映射到正值。
这只需要对我们 TFP 代码进行微小改动。现在你有两个输出节点(见列表 5.3 中的第 5 行),并且你告诉分布层由 NN 推导出的位置和尺度参数。
列表 5.3 用于线性回归的非常数方差浅层 NN
def NLL(*y*, distr):
return -distr.log_prob(*y*) ❶
def my_dist(params):
return tfd.Normal(
loc=params[:,0:1], ❷
scale=1e-3 + ❸
tf.math.softplus(0.05 * params[:,1:2])) ❸
inputs = Input(shape=(1,))
params = Dense(2)(inputs) ❹
dist = tfp.layers.DistributionLambda(my_dist)(params) ❷❸
model_monotoic_sd = Model(inputs=inputs, outputs=dist)
model_monotoic_sd.compile(Adam(learning_rate=0.01), loss=NLL)
❶ 计算模型的 NLL
❷ 第一个输出节点定义了均值(loc)。
❸ 第二个输出通过软加函数定义了标准差(尺度)。为了确保尺度非负,添加了一个小的常数。
❹ 设置具有两个输出节点的 NN
让我们显示拟合的模型以及数据(见图 5.7)。但这并不是你期望的结果!确实,方差在整个x范围内并没有被建模为常数。但虚线,表示μ**[x[i]] ± 2 ⋅ σ**[x],只是大致遵循潜在的数据变异性。

图 5.7 合成验证数据以及预测的概率模型,其中 CPD 的均值和标准差由没有隐藏层的 NN 建模。黑色实线表示均值的位臵,虚线表示 0.025 和 0.975 分位数的位置。
你能猜到发生了什么吗?让我们看看列表 5.3 中的代码,并思考所使用的没有隐藏层的 NN 架构。输出层的两个节点线性依赖于输入:out[1] = a ⋅ x + b 和 out[2] = c ⋅ x + b。在列表 5.3 中可以看到,第一个输出给出了loc参数的值,第二个输出通过softplus函数来确定scale参数。因此,标准差仅单调地依赖于输入,这意味着拟合的标准差不可能遵循数据的非单调方差结构。你可以通过平均验证 NLL(见标题为“结果:单调方差”的笔记本)来量化预测性能:
NLL(单调方差) = 3.55
这与使用常数方差得到的价值 3.53 相当。你可能会期望“单调方差”模型有更好的表现,或者至少是相等的性能,因为这个模型更灵活,并且也能够学*到常数方差(即“常数方差”模型)。因此,性能的小幅下降表明略微过拟合,意味着训练集偶然显示出略微增加的方差。
你如何改进模型?你需要允许标准差以更灵活的方式依赖于 x。在上一个章节中,你看到了深度学*实现灵活性的简单配方:堆叠更多层。如果你只是在输入和两个输出节点之间引入一个隐藏层,你也会允许均值以非线性的方式依赖于 x。你实际上并不想这样做,因为那样你就不会得到线性回归模型了。但你可以选择一个直接将输入与控制均值的第一个输出(out1)连接的架构,并在输入和第二个输出(控制标准差)之间放置一个或多个隐藏层(见图 5.8)。

图 5.8 一个神经网络架构(左)用于高斯 CPD 的两个参数 μx, σx。第一个输出(out1)确定 CPD 的均值 μx,该均值线性依赖于 x。第二个输出(out2)控制 CPD 的标准差 σx,该标准差以灵活的方式依赖于 x。在列表 5.4 的笔记本中,有三个隐藏层。
要在图 5.8 中编码架构,你可以使用 TFP 和 Keras。Keras 的功能性 API 在编码复杂架构时提供了更大的灵活性,其中不同的输出节点可以以非常不同的方式从输入中计算。但这要求你将每个层的输出保存到张量变量中,并定义(对于每个层)它接收哪个张量作为输入(参见列表 5.4)。图 5.8 中所示的架构对应于列表 5.4 中所示的代码,其中你选择了三个隐藏层:一个有 30 个节点,两个有 20 个节点,位于输入和第二个输出之间。
列表 5.4 使用具有隐藏层的神经网络进行具有非恒定方差的线性回归
def NLL(*y*, distr):
return -distr.log_prob(*y*)
def my_dist(params):
return tfd.Normal(loc=params[:,0:1],
scale=1e-3 +
tf.math.softplus(0.05 * params[:,1:2]))
inputs = Input(shape=(1,))
out1 = Dense(1)(inputs) ❶
hidden1 = Dense(30,activation="relu")(inputs)
hidden1 = Dense(20,activation="relu")(hidden1)
hidden2 = Dense(20,activation="relu")(hidden1)
out2 = Dense(1)(hidden2) ❷
params = Concatenate()([out1,out2]) ❸
dist = tfp.layers.DistributionLambda(my_dist)(params)
model_flex_sd = Model(inputs=inputs, outputs=dist)
model_flex_sd.compile(Adam(learning_rate=0.01), loss=NLL)
❶ 第一个输出模型化均值;没有使用隐藏层。
❷ 第二个输出模型化分布的扩散;使用了三个隐藏层。
❸ 结合均值和扩散的输出
|
| 实践时间 打开 mng.bz/zjNw 。逐步执行代码以解决练* 2 和 3,并在阅读时跟随代码。
-
拟合具有非恒定标准差的线性回归模型。
-
你如何选择最佳模型?
-
预测 CPD 在训练数据范围之外看起来如何?
|
结果的概率拟合显示在图 5.9 中。现在一切看起来都非常完美!平均而言,输入和结果之间存在线性关系,但不同输入的结果变异性不同。这通过图 5.9 中两条虚线的距离反映出来,其中大约 95%的所有数据应该落在其中,而这个模型似乎做到了。此外,对于四个测试点(-0.81, -6.14)、(1.03, 3.42)、(4.59, 6.68)、(5.75, 17.06)预测的 CPD P(y|μ**[x[test]], σ) 和由此产生的似然 P(y**[test]|μ**[x[test]], σ) 看起来现在非常好!就此刻享受一下,适当的高斯 CPD 标准差如何能提高概率模型预测真实结果的似然(比较图 5.5、5.7 和 5.9)。

图 5.9 展示了验证数据以及允许灵活数据变异性的线性模型的预测高斯 CPD。黑色实线表示均值的位置,虚线表示 0.025 和 0.975 分位数的位置。对于四个选定的数据点(实心点),显示了 CPD。相应的似然由实线水平线段表示。与图 5.5 比较的动画版本可以在mng.bz/K2JP找到。
为了量化预测性能,你可以再次确定验证数据的平均负对数似然(参见笔记本中的“结果:灵活标准差”主题):
验证平均负对数似然(灵活标准差)= 3.11
根据验证数据的平均负对数似然,你会选择具有灵活高斯 CPD 标准差的最后一个模型作为获胜者。让我们干净利落地完成这项工作,并确保你在报告平均测试负对数似然时避免过拟合陷阱。使用你的获胜模型再次确定平均测试负对数似然,它是
测试平均负对数似然(灵活标准差)= 3.15
让我们回顾并总结一下你学到了什么。如果你想开发一个概率预测模型,你的目标是预测新数据上的正确 CPD。为了评估预测的 CPD 是否确实描述了新数据中结果的分布,你计算了验证数据的平均负对数似然。但 MSE 或平均绝对误差(MAE)这些已建立的性能指标如何?这两个性能指标量化了预测均值(平均)偏离实际观察值的程度。但它们完全忽略了 CPD 的方差,这意味着它们没有评估整个 CPD 的正确性。因此,如果你想评估概率模型的质量,单独报告 MSE 或 MAE 是不合适的。我们建议你始终通过验证负对数似然来报告概率回归模型的性能,并仅提供额外的指标,如验证 MSE 和 MAE(或分类情况下的准确率)。
5.4 使用 TensorFlow Probability 模型建模计数数据
在设置概率模型时,最具挑战性的任务是选择正确的分布模型来表示结果。结果的数据类型在这个游戏中起着重要作用。在血压的例子中,结果是连续的。给定一个女性的特定年龄,你通过连续正态分布 N(μ , σ ) 来建模可能的血压值,该分布有两个参数:均值 μ 和标准差 σ。在 MNIST 例子中,结果是分类的,你的任务是预测从原始图像中显示数字 0、1、2、3、4、5、6、7、8 或 9 的概率(见第二章)。对 10 个可能的数字的预测概率构成了分类结果分布,这被称为多项式分布,具有 10 个参数 p0, p1, ..., p9,它们的总和为 1。
在本节中,你将学*如何建模计数结果。有许多用例需要建模计数数据;例如,计算给定图像 x**[i] 上的 y**[i] 人数,或者预测一篇博客文章在未来 24 小时内收到的评论数量,或者根据某些特征 x**[i](如一天中的时间、日期等)在某个小时内发生的交通事故中杀死的鹿的数量。你将有机会进行一项预测交通事故数量的案例研究。我们建议你在学*本节内容时逐步浏览以下笔记本。
实践时间 打开 mng.bz/90xx 。逐步浏览笔记本,并尝试理解代码。测试数据上的平均 NLL 绝对误差和线性回归以及泊松回归的 RMSE 是多少? |
|---|
让我们从计数数据分析的经典示例开始,该示例来自 stats.idre.ucla.edu/r/dae/zip/。你的任务是预测在州立公园钓鱼的团体捕获的鱼的数量(y)。我们有一个包含 250 个团体的小型数据集,我们称之为露营者数据。这些个人团体参观了州立公园,并提供了以下信息:
-
组里有多少人?
-
组里有多少孩子?
-
这个团体是否带着露营车来公园?
为了设置计数数据的概率模型,你将了解两种只能输出整数的新分布,因此适合计数数据:泊松分布和零膨胀泊松 (zIP) 分布。泊松分布有一个参数;ZIP 分布有两个参数。简而言之,你将很快了解它们的含义。要使用这些分布模型之一,通过概率深度学*模型预测计数结果,你遵循标准的 TFP 程序:使用具有适当架构和容量的神经网络将输入转换为预测的 CPD,然后在 TFP 中选择与所选结果预测相对应的分布(见图 5.10)。

图 5.10 使用概率深度学*进行计数数据建模。网络确定概率分布的参数。使用最大似然原理拟合模型。在示例中,结果是计数数据。这里它通过泊松分布建模,其中 NN 用于控制其速率参数λ(参见选定的最后一个带有单个输出节点的板),这给出了平均值和方差。
在学*本章之前,你可能会首先想用标准 NN 和输出节点上的线性激活函数来拟合露营车数据,然后使用 MSE 损失训练 NN。实际上,在这种情况下,许多人使用线性回归模型,但这并不是最佳解决方案!你可以尝试使用笔记本中提供的代码进行这种天真方法:mng.bz/jgMz。
为了检查模型的性能,你可以将预测的结果分布与实际结果进行比较。让我们选取测试观察值 31 和 33 的预测结果分布(见图 5.11)。观察值 31 是一个捕获了五条鱼并具有以下特征的群体:使用了活饵,有露营车,四个人,一个孩子。观察值 33 是一个捕获了零条鱼并具有以下特征的群体:使用了活饵,没有露营车,四个人,两个孩子。观察 31 和 33 的预测 CPD 显示,观察到的结果(五条鱼和零条鱼)在这两个观察中都有相当好的可能性(图 5.11 中的虚线)。但是,根据这些 CPD,捕获负数条鱼也有相当高的可能性。常识告诉你,捕获-2 条鱼是非常不可能的。计数数据的线性回归模型的另一个问题是,预测的结果分布是连续的,但可能捕获的鱼的数量是整数——你不能捕获半条鱼。

图 5.11 露营车数据中测试观察值 31(左)和 33(右)的预测正态分布。虚线表示预测均值的位臵。粗虚线表示观察到的结果的似然性:31(捕获五条鱼)和 33(zero 条鱼捕获)。
为了在一个图表中同时比较观察结果与所有测试点的预测 CPD,你需要在每个测试点绘制一个 CPD。这样的图表看起来会相当拥挤。而不是整个 CPD,图 5.12 只显示了均值(实线)、2.5%(下虚线)和 97.5%分位数(上虚线)来表征它。由于多个特征,不再可能将特征放在横轴上(如简单线性回归中)。相反,预测的 CPD 均值被绘制在横轴上。图 5.11 中显示的 31 号和 33 号观测值被突出显示。再次,预测捕获的鱼数量为负的问题显而易见。下虚线低于零,甚至代表预测捕获的鱼数量均值的实线在某些区域也低于零。此外,在拟合良好的情况下,实线应该穿过观察数据点的均值,这显然不是情况。该图表只显示了观察数据,而不是其均值,但仍然可以看出预测的均值(高达 8)大于数据平均值。此外,由虚线表示的 95%预测区间对于预测的少量鱼来说似乎不合理地大。

图 5.12 线性回归展示了预测 CPD 与观察数据的比较。测试露营者数据中观察到的捕获的鱼数与预测的捕获鱼数均值进行比较。实线描述了预测 CPD 的均值。虚线代表 0.025 和 0.975 分位数,形成 95%预测区间的边界。突出显示的点对应于捕获零条鱼的观测值 33(左侧)和捕获五条鱼的观测值 31(右侧)。
假设高斯 CPD 进行线性回归显然存在一些缺陷。但哪种分布更合适呢?这正是问题所在!即使意识到你正在处理计数数据,也有不同的计数数据选项。在最简单的情况下,计数数据可以用泊松分布来描述。
5.4.1 计数数据的泊松分布
在深度学*之前的时代,大约在 1900 年,拉迪斯拉夫·冯·博特基维茨(Ladislaus von Bortkiewicz)想要模拟普鲁士军队 14 个骑兵军团每年被马踢死的士兵数量。作为训练数据,他们有 20 年的数据。在他的第一本统计学著作《小数定律》(Das Gesetz der kleinen Zahlen)中,他使用泊松分布来模拟每年被马踢死的士兵数量,这种分布是以西莫恩·德尼·泊松(Siméon Denis Poisson)的名字命名的。为了使我们的讨论不那么血腥,每分钟桶中的雨滴数量也可以用泊松分布来模拟。或者,在我们的情况下,每个州立公园露营者小组捕获的鱼的数量。在所有这些例子中,你计算每单位的事件数量;这些单位通常是时间单位,但也可以是其他单位,如每 10 万居民中的谋杀案数量。
在现实生活中,我们经常不得不处理随机性,并且每次(单位)观察到的事件数量并不总是相同的。但让我们假设平均每单位发生两个事件(每年每支部队两个阵亡士兵、每分钟桶中的两个雨滴或每次停留捕获的两个鱼)。图 5.13 展示了可能观察到的结果的概率分布。事件数量的平均值是确定分布的唯一所需信息(见方程 5.1)。这种分布为每个可能的结果分配一个概率:观察到一个单位零事件、一个单位一个事件、两个单位的事件,等等。因为它是一个概率分布,所有概率的总和为 1。

图 5.13 表示,在平均每单位发生两个事件(两个阵亡士兵、两个桶中的雨滴或两次捕获的鱼)的情况下,观察到一或两个事件的可能性相当高。这种概率分布来自笔记本 nb_ch05_02.ipynb。
图 5.13 显示,每(时间)单位发生两个事件的可能性相当高,超过 0.25,但其他可能的结果也有一定的概率。事件数量的平均值起着重要作用,因为它定义了泊松分布的唯一参数,通常称为速率,通常用符号 λ 表示。在我们的情况下,λ = 2,但 λ 是每单位事件数量的平均值,因此 λ 不总是整数。如果你好奇,泊松分布有一个公式,定义了计数事件的可能值 k 的每个值的概率。观察到的概率是

其中 k! 表示 k 的阶乘(k ! = 1 ⋅ 2 ⋅ 3⋅ ... ⋅ k)。请注意,与高斯 P(y = k) 相比,我们现在有一个实际的概率,而不仅仅是密度(如高斯)。为了强调这一点,有时它也被称为概率质量函数。泊松分布的一个显著特性是 λ 不仅定义了分布的中心(期望值),还定义了方差。因此,事件的平均数 λ 是固定分布所需的所有内容。
在这个列表中,你可以看到如何使用 TFP 来建模泊松分布。
列表 5.5 TFP 中的泊松分布
dist = tfd.poisson.Poisson(rate = 2) ❶
vals = np.linspace(0,10,11) ❷
p = dist.prob(vals) ❸
print(Dist.mean().numpy()) ❹
print(Dist.stddev().numpy()) ❺
❶ 速率参数为 2 的泊松分布
❷ 图 5.13 中 x 轴的整数值从 0 到 10
❸ 计算值的概率
❹ 均值,得到 2.0
❺ 标准差,得到 sqrt(2.0) = 1.41 ...
现在你已经有一个适合计数数据的适当分布。我们使用神经网络估计分布的参数 rate。我们使用一个非常简单的网络,没有隐藏层,其任务是预测一个泊松 CPD,只需要确定 rate 参数。泊松分布中的参数 rate 是零或一个正实数。因为具有线性激活的神经网络的输出可能是负数,所以我们使用指数函数作为激活函数,在将其用作速率之前。(你也可以选择使用 softplus 激活,如果你愿意的话。)这个网络在下面的列表中显示。
列表 5.6 简单的泊松回归用于捕获的鱼的数量
inputs = Input(shape=(X_train.shape[1],))
rate = Dense(1, ❶
activation=tf.exp)(inputs) ❷
p_y = tfp.layers.DistributionLambda(tfd.Poisson)(rate)
model_p = Model(inputs=inputs, outputs=p_y) ❸
def NLL(*y*_true, y_hat): ❹
return -y_hat.log_prob(*y*_true)
model_p.compile(Adam(learning_rate=0.01), loss=NLL)
model_p.summary()
❶ 我们使用输出的指数来建模速率。
❷ 定义一个具有一个输出的单层
❸ 将神经网络和输出层粘合在一起。注意,输出 p_y 是一个 tf.distribution。
❹ 第二个参数是模型的输出,因此是一个 TFP 分布。它就像调用 log_prob 来计算所需计算 NLL 的观察值的对数概率一样简单。
如果你已经逐步通过了笔记本中的代码,你会注意到使用泊松分布的解决方案比使用线性回归要好。泊松回归的均方根误差(RMSE)大约为 7.2,低于线性回归的 8.6。但更重要的是,泊松回归的 NLL 为 2.7,而线性回归为 3.6,因此要低得多。
让我们更仔细地看看泊松预测的结果,并使用拟合的泊松模型来预测测试观察值 31 和 33 的 CPD(见图 5.14)。观察值 31 和 33 的预测结果分布分别是速率[31] = 5.56 和速率[33] = 0.55 的泊松分布。对于两个观察值(五条鱼和零条鱼)的观测结果,似然度相当好(图 5.11 中的虚线)。

图 5.14 测试观测值 31(左)和 33(右)的预测泊松分布。粗虚线表示观测结果的似然性。
要在一个图表中同时比较所有测试点的观测结果与预测的 CPD,你可以再次绘制观测到的鱼的数量与预测的鱼的平均数量的对比图(见图 5.15),并将预测 CPD 的平均值用实线表示,预测 CPD 的 2.5%和 97.5%分位数用虚线表示。你可能想知道为什么分位数曲线不光滑,但在预测 CPD 的位置取整数值(见图 5.15 的右侧面板)。答案在于泊松分布的本质和分位数的定义:泊松模型只能为整数值分配概率。例如,97.5%分位数定义为分布中只能为整数的值,对于这个值,97.5%的值都小于或等于这个值。
如何阅读概率模型的诊断图
这里有一些阅读用于评估概率模型性能的诊断图的提示。注意,在图 5.12、5.15 和 5.17 中,观测结果是以预测 CPD 的平均值进行对比(而不是像只有一个变量的简单线性回归那样与输入特征对比)。因为在露营者数据中我们有四个特征,所以不可能使用单个输入特征作为横坐标,图形表示变得更加复杂。还请注意,不同的输入特征组合不仅可以产生相同的 CPD 预测平均值,而且可能具有不同的分位数。在这些情况下,你会在相同的平均值位置上有几个不同的 CPD,但分位数不同,导致在相同的x位置上有多个分位数。数据越多,观察到这种情况的概率就越高。在第六章中,你会看到这样的例子。此外,分位数(图中的虚线)的跳跃不是人为的。因为 x 轴不再代表输入变量的平滑变化,这样的跳跃是可能的。同样地,分位数不需要平滑变化(见图 5.17)。分位数不需要显示单调行为的事实已经在简单的线性回归示例中可见(见图 5.9)。CPD 平均值的曲线(图 5.15 中的实线)始终是主对角线,因为它绘制的是平均值与平均值的关系。

图 5.15 针对露营者示例的泊松回归预测结果。测试样本中捕获的鱼的数量与预测的捕获鱼的平均数量进行对比。为了表示预测的 CPD,实线描绘了 CPD 的平均值。虚线代表 0.025 和 0.975 分位数,从而得到 95%预测区间的边界。
注意,与线性回归模型不同,泊松模型预测的结果分布只分配概率给可以实际观察到的值:捕到的非负整数鱼的数量。在理想概率预测模型的情况下,观察值的平均值应该对应于实线,95% 的所有点应该位于两条虚线之间。根据这些标准,泊松模型似乎相当合理,至少对于大多数数据来说是这样。但仍然,可能还有改进的空间。
看起来有很多组完全没有捕到鱼。怎么会有这么多不幸的渔民呢?我们只能猜测,但也许露营团体只是以钓鱼装备为借口,喝大量的啤酒,根本不去钓鱼。因此,可能有两种原因导致带着零鱼离开州立公园:运气不好或根本不去钓鱼。一个明确考虑那些懒惰渔民的模型是零膨胀模型。我们将在下一节讨论该模型。
5.4.2 将泊松分布扩展到零膨胀泊松 (zIP) 分布
零膨胀泊松 (zIP) 分布考虑了存在许多零的事实,这些零比泊松分布中预期的零的数量要多。在我们的例子中,这些是根本不去钓鱼的懒惰露营团体。在 ZIP 分布中,你可以通过引入零生成过程来模拟零的过剩,如下所示:你掷一枚硬币。硬币有概率 p 显示正面。如果是这样,你有一个懒惰的露营团体,他们捕到的鱼为零。如果不是,你有一个常规的钓鱼团体,你可以使用泊松分布来预测捕到的鱼的数量。为了以 TFP 方式将你的计数数据与 ZIP 结果模型拟合,你设置了一个具有 ZIP 分布的神经网络。
很遗憾,TFP 还没有提供 ZIP 分布。但是,根据现有的 TFP 分布定义一个新的自定义函数相当简单(参见列表 5.7)。ZIP 需要两个参数:
-
产生额外零的概率 p
-
泊松分布的速率
ZIP 函数接受来自神经网络的两个输出节点:一个用于速率,一个用于 p。如列表 5.7 所示,我们对输出 out 的第一个组件 out[:,0:1] 应用指数变换以获得正的速率值,并对神经网络输出 out_2 应用 sigmoid 变换以获得 p 的值在 0 到 1 之间。
列表 5.7 ZIP 分布的自定义分布
def zero_inf(out):
rate = tf.squeeze(tf.math.exp(out[:,0:1])) ❶
s = tf.math.sigmoid(out[:,1:2]) ❷
probs = tf.concat([1-s, s], axis=1) ❸
return tfd.Mixture(
cat=tfd.Categorical(probs=probs), ❹
components=[
tfd.Deterministic(loc=tf.zeros_like(rate)), ❺
tfd.Poisson(rate=rate), ❻
])
❶ 第一个组件编码速率。我们使用指数函数来保证值大于 0,并使用挤压函数来平坦化张量。
❷ 第二个组件编码零膨胀;使用 sigmoid 函数将值挤压在 0 和 1 之间。
❸ 0 或泊松分布的两个概率
❹ tfd.Categorical 允许创建两个组件的混合。
❺ 零作为一个确定性的值
❻ 从泊松分布中抽取的值
网络随后就变成了一个没有隐藏层和两个输出节点的简单网络。以下列表显示了设置网络的代码。
列表 5.8ZIP 分布前的 NN
## Definition of the custom parameterized distribution
inputs = tf.keras.layers.Input(shape=(X_train.shape[1],))
out = Dense(2)(inputs) ❶
p_y_zi = tfp.layers.DistributionLambda(*z*ero_inf)(out)
model_zi = Model(inputs=inputs, outputs=p_y_zi)
❶ 一个没有激活的密集层。转换在 zero_inf 函数中完成。
你现在可以使用拟合的 ZIP 模型来预测测试数据集中的观测值的概率分布。让我们使用拟合的 ZIP 模型来预测测试观测值 31 和 33 的 CPD(见图 5.16)。

图 5.16 测试观测值 31(左)和 33(右)的预测 ZIP 分布。粗虚线表示观测结果的似然性。
预测 ZIP CPD 最显著的特征是在 0 处有一个大的峰值(见图 5.16)。这是由于零膨胀过程相对于泊松过程模拟了更多的零。观察到的结果(五条鱼和零条鱼)的似然度对于两个观测值都相当好(见图 5.16 中的虚线)。
要在一个图中同时比较所有测试点的观察结果与预测的 CPD,你可以再次绘制观察到的鱼的数量与预测的捕鱼平均数量(见图 5.17)。为了表示预测 CPD 的形状,我们绘制预测 CPD 的平均值(见图 5.17 中的实线)和预测 CPD 的 2.5%和 97.5%分位数(见图 5.17 中的虚线)。在 ZIP 模型中,2.5%分位数在整个范围内保持在零。这意味着对于所有组,预测的 ZIP CPD 将高于 2.5%的概率分配给零这个结果,这与观察到的零的数量很高很好地对应。

图 5.17 ZIP 模型对露营示例的回归预测结果。测试样本中观察到的捕鱼数量与预测的捕鱼平均数量进行对比。为了表示预测的 CPD,实线表示 CPD 的平均值。虚线代表 0.025 和 0.975 分位数,从而得到 95%预测区间的边界。
在图 5.17 中,观察到的值的平均值似乎接*实线,而 95%的点都位于两条虚线之间。通过测试 NLL 来量化 ZIP 模型的表现,结果表明零膨胀模型优于线性回归和泊松模型(见表 5.2)。
表 5.2 不同模型在验证数据上的预测性能比较。如果你运行笔记本,可能会得到略微不同的值。这里涉及一些随机性,而且数据量不是很多。但整体行为应该大致相同。
| 线性回归 | 泊松 | 零膨胀 | |
|---|---|---|---|
| RMSE | 8.6 | 7.2 | 7.3 |
| MAE | 4.7 | 3.1 | 3.2 |
| NLL | 3.6 | 2.7 | 2.2 |
对于拟合渔猎团体的数据,没有明确的共识表明哪个模型是整体上最好的。表 5.2 显示,泊松分布就 RMSE 和 MAE 而言是最好的。ZIP 模型在 NLL 方面表现最佳。但如第 5.2 节所述,为了衡量概率预测性能,您应该使用平均测试 NLL。因此,ZIP 模型是三个模型测试中最好的概率模型。请注意,严格来说,NLL 只应在离散模型(泊松或 ZIP 作为 CPD)之间进行比较,而不应在连续和离散模型(高斯或泊松作为 CPD)之间进行比较。是否有更好的模型?可能存在一个 NLL 更低的模型,但您无法确定。在这个应用中,NLL 没有理论上的下限。
最后,为了完整性,我们想指出,处理计数数据还有第三种方法,那就是使用所谓的负二项分布。像 ZIP 分布一样,它是一个具有两个参数的分布,不仅允许计数均值依赖于输入,还允许计数的标准差依赖于输入。
摘要
-
概率模型为每个输入预测一个完整的条件概率分布(CPD)。
-
预测的 CPD 为每个可能的输出y分配一个预期的概率。
-
负对数似然(NLL)衡量 CPD 与实际结果分布的匹配程度。
-
当训练概率模型时,您使用 NLL 作为损失函数。
-
您使用 NLL 对新数据进行测量和比较不同概率模型的预测性能。
-
使用合适的 CPD 选择可以增强您模型的性能。
-
对于连续数据,一个常见的选择是正态分布。
-
对于计数数据,常见的分布选择是泊松分布、负二项分布或零膨胀泊松分布(zIP)。
1.这个概率可以按以下方式计算:
import tensorflow_probability as tfp
dist = tfp.distributions.Normal(loc=22, scale=2)
dist.cdf(25) #0.933
2.概率可以按以下方式计算:
import tensorflow_probability as tfp
dist = tfp.distributions.Normal(loc=19, scale=12)
dist.cdf(25) #0.691
3.仅为了给您一个证明的思路,在这个证明中,假设数据是从一个真实分布生成的。在实践中,真实分布是未知的。然后可以证明 NLL 是一个所谓的正确得分,只有当预测分布等于真实分布时,它才达到其最小值。
4.与这里提到的第四章(第 4.3 节)中的侧边栏相反,我们除以 n。这是可以的,因为它不会改变最小值的位置。
5.参见标题为“结果:常量 sigma”的笔记本。
6 野外的概率深度学*模型
本章涵盖
-
最先进模型中的概率深度学*
-
现代架构中的灵活分布
-
用于灵活 CPD 的概率分布混合
-
归一化流用于生成如面部图像等复杂数据

许多现实世界的数据,如声音样本或图像,来自复杂和高维分布。在本章中,你将学*如何定义复杂的概率分布,这些分布可以用来模拟现实世界的数据。在前两章中,你学*了如何设置与易于处理的分布一起工作的模型。你使用具有高斯条件概率分布(CPD)的线性回归模型或具有作为 CPD 的分布的泊松模型进行了工作。(也许你发现自己处于本章顶部的图中,那里有 Ranger 站在一个有家畜的保护区内,但世界上的动物比你到目前为止所工作的动物更野。)你还学*了足够多的关于不同类型的家畜概率模型的知识,以便加入我们,进入处理复杂 CPD 的最先进模型的世界。
模拟复杂分布的一种方式是简单分布的混合,例如正态分布、泊松分布或逻辑分布,这些分布你在前几章中已经了解过。混合模型被用于最先进的网络中,如谷歌的并行 WaveNet 或 OpenAI 的 PixelCNN++,以模拟输出。
-
WaveNet 从文本生成听起来逼真的语音。
-
PixelCNN++生成看起来逼真的图像。
在本章的一个案例研究中,我们给你机会设置自己的混合模型,并使用这些模型超越最*公开描述的预测模型。你还学*了一种模拟这些复杂分布的另一种方法:所谓的归一化流。归一化流(NFs)允许你学*从简单分布到复杂分布的转换。在简单的情况下,这可以通过一种称为变量变换的统计方法来完成。你将在第 6.3.2 节中学*如何应用这种方法,你将看到 TensorFlow Probability(TFP)通过所谓的双射器支持这种方法。
通过将变量变换方法与深度学*相结合,您可以学*到在现实世界应用中遇到的相当复杂和高维的分布。例如,复杂机器的传感器读数是高维数据。如果您有一台正在正常工作的机器,您可以学*相应的“机器正常”分布。学*了这个分布之后,您可以持续检查机器产生的传感器数据是否仍然来自“机器正常”分布。如果传感器数据来自“机器正常”分布的概率低,您可能需要检查这台机器。这种应用被称为新颖性检测。但您还可以进行更多有趣的应用,例如建模人脸图像的分布,然后从这个分布中采样以创建不存在的人的逼真人脸。您可以想象这样的面部图像分布相当复杂。您还会用这个分布做其他有趣的事情,比如给莱昂纳多·迪卡普里奥画一个络腮胡或者在不同人之间进行变形。听起来很复杂吗?好吧,它确实有点复杂,但好消息是它使用的是您迄今为止(以及本书剩余部分将继续使用)所使用的相同原理——最大似然原理(MaxLike)。
6.1 先进深度学*模型中的灵活概率分布
在本节中,您将了解如何使用灵活的概率分布来构建深度学*中的先进模型。到目前为止,您已经遇到了不同的概率分布,例如连续变量(美国女性数据中的血压)的正态分布或均匀分布,分类变量(MNIST 数据中的十个数字)的多项分布,或者是计数数据(露营者数据中捕获的鱼的数量)的泊松分布和零膨胀泊松分布(zIP)。
定义分布的参数数量通常是分布灵活性的一个指标。例如,泊松分布只有一个参数(通常称为速率)。ZIP 分布有两个参数(速率和混合比例),在第五章中,您看到当使用 ZIP 分布而不是泊松分布作为 CPD 时,您可以为露营者数据实现更好的模型。根据这一标准,多项分布特别灵活,因为它具有尽可能多的参数值(或者更准确地说,少一个参数,因为概率需要加起来等于一)。在 MNIST 示例中,您使用图像作为输入来预测分类结果的多元 CPD。预测的多元 CPD 有十个(或者更准确地说,九个)参数,为我们提供了十个可能类别的概率(见图 6.1)。

图 6.1 具有十个类别的多项分布:MN(p[0] , p[1] , p[2] , p[3] , p[4] , , p[5] , p[6] , p[7] , p[8] , p[9])
事实上,在卷积神经网络(CNNs)进行数字分类中使用多项分布已成为深度学*模型在现实世界中的第一个且最广泛使用的应用。1998 年,当时在 AT&T 贝尔实验室工作的 Yann LeCun 实现了一个用于 ZIP 代码识别的 CNN。这被称为 LeNet-5。
6.1.1 多项分布作为一种灵活的分布
2016 年,一个需要灵活分布的现实世界任务示例是谷歌的 WaveNet。该模型可以从文本生成听起来非常逼真的合成语音。前往cloud.google.com/text-to-speech/查看您选择的文本的演示。其架构基于 1D 因果卷积,就像你在 2.3.3 节中看到的那样,以及它们的特殊化形式——扩张卷积,这在笔记本mng.bz/8pVZ中有展示。如果你对架构感兴趣,你可能还想阅读博客文章mng.bz/EdJo。
WaveNet 直接在原始音频上工作,通常使用 16 kHz(16 千赫兹)的采样率,即每秒 16,000 个样本。但你也可以使用更高的采样率。对于每个时间点 t 的音频信号,然后进行离散化(通常使用 16 位进行此操作)。例如,时间 t 的音频信号 x**[t] 取离散值从 0 到 2¹⁶ − 1 = 65,535。但本章有趣的部分是概率部分(来自第五章中图 5.1 右侧的概率架上的内容)。在 WaveNet 中,我们假设 xt 只依赖于时间较早的样本的音频信号。这给我们:
P(x**[t]) = P(x**[t]|x**[t][−1] , x**[t][−2] ,... x[0])
你可以从如图 6.2 所示的前一个值中采样 xt 值,然后确定未来值的概率分布。这类模型被称为自回归模型。请注意,你查看的是一个概率模型,你可以预测整个可能结果的分布:P(x**[t]) 这让你能够确定在预测分布下观察到的值 x**[t] 的似然或概率。欢迎回家!你可以使用古老的 MaxLike 原理来拟合这类模型。

图 6.2 WaveNet 原理。在顶部,时间 t 的离散声音值 xt 被预测为来自时间较早的值(在底部)。前往mng.bz/NKJN查看此图的动画版本,展示 WaveNet 如何通过连续应用方程 6.1 来创建未来的样本。
但你选择哪种分布来表示 P(x**[t])?结果 x 可以取从 0 到 2¹⁶ − 1 = 65,535 的所有离散值。你并不真正知道这些值的分布看起来会是什么样子。它可能不会像正态分布那样;这会暗示有一个典型值,并且结果值的概率会随着距离这个典型值的增加而迅速下降。你需要一种更灵活的分布类型。
在原则上,你可以用多项式分布来表示 65,536 个不同的值,其中你为每个可能的值估计一个概率。这忽略了值的顺序(0 < 1 < 2 < . . . < 65,535),但它确实是灵活的,因为你可以为 65,536 个可能的每个值估计一个概率。唯一的限制是这些预测概率需要加起来等于 1,这可以通过 softmax 层轻松实现。WaveNet 论文的作者 Oord 等人选择了这条路,但在他们这样做之前,他们在对原始声音值进行非线性变换后,将信号的深度从 16 位(编码 65,536 个不同的值)降低到 8 位(编码 256 个不同的值)。总的来说,Deep Mind 的人训练了一个具有 softmax 输出的膨胀因果 1D 卷积神经网络,预测了 256 个类别的多项式 CPD,并将其称为 WaveNet。
现在,你可以从学*到的分布中抽取新的样本。为此,你向训练好的 WaveNet 提供一个音频值的起始序列,x[0] , x[1] , . . ., x**[t][−1] ,然后 WaveNet 将预测一个多项式 CPD:P(x**[t]) = P(x**[t]|x**[t][−1] , x**[t][−2] ,... x[0])。然后,从这个多项式 CPD 中采样下一个音频值 x**[t] 。你可以通过提供 x[1] , x[2] , . . ., x**[t][−1] 并从结果 CPD 中采样下一个值 x**[t][+1] 来继续这个过程。
让我们来看看另一个突出的自回归模型:OpenAI 的 PixelCNN。这是一个可以根据“之前”的像素预测像素的网络。对于 WaveNet 来说,音频值的顺序仅仅是时间,而对于图像,没有自然的方式来对像素进行排序。例如,你可以像阅读文本一样按文本中的字符顺序排序,从左到右,从上到下读取。然后,这些模型可以根据所有之前的像素 xt'(t' < t)来采样一个特定颜色的像素 xt。你再次拥有与方程 6.1 中相同的结构,其中 xt 现在是一个像素值。
你如何训练这些模型?可以采取与 WaveNet 相同的方法,用 8 位对像素值进行编码,这限制了输出到 256 个可能值,并在 256 个单热编码的分类变量输出上使用 softmax。这确实是在 PixelCNN 中做到的。
一年后,在 2017 年初,OpenAI 的工程师改进了 PixelCNN,这在名为“PixelCNN++:通过离散化逻辑混合似然和其他修改改进 PixelCNN”(见arxiv.org/abs/1701.05517)的论文中有报道。什么!你不知道“离散化逻辑混合似然”是什么意思?不用担心。你很快就会了解到。现在,让我们仅仅欣赏一下,有了这种新的 CPD,OpenAI 通过测试 NLL 为 2.92,比原始 PixelCNN 的 NLL 3.14 有了改进。在那篇关于 PixelCNN++的论文之后,谷歌工程师也将 WaveNet 增强为并行 WaveNet(见arxiv.org/abs/1711.10433)。在众多改进中,他们从多项式 CPD 切换到离散化逻辑混合分布作为 CPD。(你稍后会看到这意味着什么。)当设置并行 WaveNet 模型时,这是一项相当多的工作,但现在有了 TensorFlow Probability,它相当简单,你将在下一节中看到。
6.1.2 理解离散化的逻辑混合
在这两个应用中,WaveNet 和 PixelCNN,都需要从 0 预测到上限值(通常是 255 或 65,535)。这就像计数数据,但有最大值。为什么不采用像泊松分布这样的计数分布,并钳位最大值呢?在原则上这应该是可以的,但结果证明分布需要更复杂。因此,在论文中使用了分布的混合。PixelCNN++论文中用于混合的分布是离散化的逻辑函数。
让我们展开离散化的逻辑混合。你知道正态分布的密度是钟形,逻辑分布的密度实际上看起来非常相似。图 6.3 展示了左侧具有不同scale参数值的逻辑函数密度,以及右侧相应的累积分布函数(CDF)。实际上,逻辑 CDF 与第二章中使用的 sigmoid 激活函数相同。查看以下可选笔记本,了解更多关于逻辑函数的信息。

图 6.3 使用 tfd.Logistic(loc=1, scale=scale)创建的三个逻辑函数,scale参数的值为 0.25、1.0 和 2.0。左侧是概率密度函数(PDF),右侧是累积概率密度函数(CDF)。
|
可选练* 打开 mng.bz/D2Jn 。笔记本展示了第 6.3、6.4 和 6.5 图以及列表 6.2 的代码。
-
与本文并行阅读。
-
改变分布的参数,看看曲线如何变化。
|
在 WaveNet 和 PixelCNN 模型中,结果是离散的。因此,适当的 CDF 应该模拟离散(而不是连续)值。但是,逻辑分布是用于没有上下限的连续值。因此,我们需要将逻辑分布离散化,并将值夹在可能的范围内。在 TFP 中,可以使用QuantizedDistribution函数来完成此操作。QuantizedDistribution函数接受一个概率分布(如图 6.4 中的内部分布)并创建其量化版本。附带的笔记本中的可选练*详细说明了使用QuantizedDistribution的细节。

图 6.4 参数loc=1和scale=0.25的逻辑函数的量化版本
为了处理更灵活的分布,我们混合了几个量化逻辑分布(见图 6.5)。对于混合,可以使用一个类别分布来确定混合的不同分布的权重(混合比例)。以下列表显示了一个示例。
列表 6.1 混合两个量化分布
locs = (4.0,10.0) ❶
scales = (0.25, 0.5) ❷
probs = (0.8, 0.2) ❸
dists = tfd.Logistic(loc=locs, scale=scales) ❹
quant = quantize(Dists, bits=4) ❺
quant_mixture = tfd.MixtureSameFamily( ❻
mixture_distribution=tfd.Categorical(probs=probs), ❼
components_distribution=quant)
❶ 将两个基本分布的中心放在 4 和 10
❷ 两个基本分布的扩散
❸ 将第一个分布(在 4.0 处)的 80%和第二个分布的 20%混合
❹ 两个独立分布
❺ 两个独立分布的量化版本
❻ 两个分布的混合
❼ 使用 80%和 20%的类别分布进行混合。
图 6.5 显示了得到的分布。这个分布适用于像素值(在 PixelCNN 的情况下)和声音振幅(在 WaveNet 的情况下)等数据。如果你不混合两个分布,而是混合四个或十个分布,你可以轻松地构建更多更灵活的结果分布。

图 6.5 混合两个逻辑分布时得到的离散分布(代码见列表 6.2,用于生成这些图表)
如果你想要在自己的网络中使用这个分布而不是,比如说,泊松分布,你可以从列表 6.2 的末尾复制并粘贴函数quant_mixture_logistic。这个函数来自QuantizedDistribution的 TensorFlow 文档。
对于每个混合成分,神经网络需要估计三个参数:成分的位置和扩散以及成分的权重。如果你在混合中使用 num 个逻辑分布成分,那么神经网络的输出需要具有 3 · num 个输出节点:每个成分三个,分别控制位置、扩散和权重。请注意,函数quant_mixture_logistic期望一个没有激活的输出(在 Keras 中默认如此)。以下列表显示了如何使用此函数进行具有两个成分的混合。在这种情况下,网络有六个输出。
列表 6.2 将quant_mixture_logistic()用作分布
def quant_mixture_logistic(out, bits=8, num=3):
loc, un_scale, logits = tf.split(out, ❶
num_or_size_splits=num,
axis=-1)
scale = tf.nn.softplus(un_scale) ❷
discretized_logistic_dist = tfd.QuantizedDistribution(
distribution=tfd.TransformedDistribution( ❸
distribution=tfd.Logistic(loc=loc, scale=scale),
bijector=tfb.AffineScalar(shift=-0.5)),
low=0.,
high=2**bits - 1.)
mixture_dist = tfd.MixtureSameFamily( ❹
mixture_distribution=tfd.Categorical(logits=logits),
components_distribution=discretized_logistic_dist)
return mixture_dist
inputs = tf.keras.layers.Input(shape=(100,))
h1 = Dense(10, activation='tanh')(inputs)
out = Dense(6)(h1) ❺
p_y = tfp.layers.DistributionLambda(quant_mixture_logistic)(out)
❶ 将输出分成大小为 3 的块
❷ 转换为正数值,以适应缩放
❸ 将分布向右移动 0.5
❹ 使用 logits,无需对概率进行归一化
❺ 网络的最后一层。控制混合模型的参数:每个组件三个(这里为 2·3)。保持默认的线性激活,不要限制值范围。确保正值变换的是上面的 softplus 函数。
6.2 案例研究:巴伐利亚道路伤亡
让我们将上一节关于混合的知识应用到案例研究中,以展示使用适当的灵活概率分布作为条件结果分布的优势。由于训练像 PixelCNN 这样的神经网络需要相当多的计算资源,所以我们这里使用一个中等大小的数据集。该数据集描述了德国巴伐利亚州 2002 年至 2011 年在道路上的鹿相关交通事故。它统计了在任何 30 分钟期间巴伐利亚境内被杀的鹿的数量。我们之前曾使用这个数据集在其他研究中分析计数数据。它最初来自zenodo.org/record/17179 。表 6.1 包含了一些预处理后的数据集行。1
表 6.1 巴伐利亚一些与鹿有关的交通事故行
| 野生 | 年份 | 时间 | 白天 | 星期 |
|---|---|---|---|---|
| 0 | 2002.0 | 0.000000 | 夜间上午 | 星期日 |
| 0 | 2002.0 | 0.020833 | 夜间上午 | 星期日 |
| . . . | . . . | . . . | . . . | . . . |
| 1 | 2002.0 | 0.208333 | 夜间上午 | 星期日 |
| 0 | 2002.0 | 0.229167 | 日出前上午 | 星期日 |
| 0 | 2002.0 | 0.270833 | 日出前上午 | 星期日 |
列的含义如下:
-
野生 --在巴伐利亚交通事故中遇害的鹿的数量。
-
年份 --年份(训练集为 2002 年至 2009 年,测试集为 2010 年至 2011 年)。
-
时间 --事件发生的天数(从 2002 年 1 月 1 日开始,以 0 计)。这些数字以一天的分数来衡量。时间分辨率,30 分钟,对应于 1/48 的分数,即 0.020833(见第二行)。
-
白天 --白天相对于日落和日出的时间。数据集中包含以下级别:夜间上午、日出前上午、日出后上午、上午、下午、日落前下午、日落后下午和夜间下午,分别对应夜间、日出前、日出后、早晨、下午等时间。
-
星期 --从星期日到星期六的星期几;假日编码为星期日。
|
实践时间 打开 mng.bz/B2O0 。笔记本包含加载鹿交通事故案例研究数据集所需的所有内容。
-
使用本节所学的一切来开发针对目标变量(wilD)的概率深度学*模型。你应该在测试集上获得低于 1.8 的 NLL。
-
一个真正的挑战是 NLL 低于 1.6599,这是一个通过复杂的统计模型获得的价值(参见 Sandra Siegfried 和 Torsten Hothorn 的作品,见
mng.bz/dygN)。 -
笔记本中给出了一个解决方案(尽量做得更好)。比较你的结果与解决方案。
|
祝你打猎愉快!如果你在测试集上得到一个低于 1.65 的 NLL,给我们发个信息,我们可能会一起写篇论文。
6.3 随波逐流:归一化流(NFs)简介
在第 6.1 节中,你看到了一种灵活的方式来通过提供简单基分布的混合来建模复杂分布。当你的分布处于低维空间时,这种方法效果很好。在 PixelCNN++和平行 WaveNet 的情况下,应用任务是回归问题,因此条件结果分布是一维的。
但如何设置和拟合一个灵活的高维分布呢?例如,考虑 256 × 256 × 3 = 195,840 像素的颜色图像,它定义了一个 195,840 维的空间,其中每个图像都可以用一个点来表示。如果你在这个空间中随机选择一个点,那么你很可能会得到一个看起来像噪声的图像。这意味着像面部图像这样的真实图像的分布只覆盖了一个子区域,这可能不容易定义。你如何从可以从中抽取面部图像的 195,840 维分布中学*?使用 NFs!简而言之,NF 通过从简单的高维分布到复杂的分布学*一个转换(流)。在一个有效的分布中,在离散情况下概率需要加起来等于 1,或者在连续情况下积分需要等于 1,并且这些需要被归一化。NF 中的流保持了这种归一化属性。因此,得名归一化流或简称 NF。
在本节中,我们解释 NFs 是如何工作的。你会发现 NFs 是概率模型,你可以使用与上一章中相同的 MaxLike 方法来拟合。你还将能够使用拟合的分布来生成不存在的人的逼真面孔,或者将你的面部图像与布拉德·皮特的图像进行变形,例如。
NFs 在高维空间中特别有用。因为很难想象一个超过三个维度的空间,所以我们用低维来解释 NFs。但不用担心,我们会在本节末尾讨论高维面部图像的分布。
NFs 是什么?它们有什么好处?基本思想是,NF 可以在事先没有选择合适的分布族或设置多个分布的混合的情况下拟合一个复杂的分布(如图 6.6 所示)。

图 6.6 参数概率密度估计的草图。每个点(x1, x2)被分配一个概率密度。我们选择了参数θ来匹配数据点(点)。
概率密度允许你从这个分布中进行采样。在面部图像分布的情况下,你可以从这个分布中生成面部图像。生成的面孔不是来自训练数据(或者更准确地说,从学*到的分布中抽取训练样本的机会很小)。
因此,NFs 属于生成模型类别。其他著名的生成模型包括生成对抗网络(GANs)和变分自编码器(VAEs)。GANs 在创建不存在的人脸图像方面可以生成相当令人印象深刻的结果。访问 mng.bz/rrNB 查看这样的生成图像。如果你想了解更多,Jakub Langr 和 Vladimir Bok 合著的《GANs in Action》(Manning, 2019)提供了对 GANs 的易于理解和全面的介绍(见 mng.bz/VgZP)。但正如你稍后将会看到的,NFs 也可以生成看起来很真实的图像。
与 GANs 和 VAEs 相比,NFs 是概率模型,它们真正学*了概率分布,并允许每个样本确定相应的概率(似然度 D)。假设你已经使用 NF 学*了面部图像的分布,并且你有一个图像 x,然后你可以通过 p(x) 向 NF 询问该图像的概率是多少?这有相当有用的应用,比如新颖性检测。
在新颖性检测中,你想要确定一个数据点是否来自某个分布,或者它是否是一个原始(新颖)的数据点。例如,你记录了机器(比如说,喷气发动机)在正常条件下的数据。这可以是非常高维的数据,比如振动光谱。然后你训练一个 NF 来表示“机器正常”的分布。当机器运行时,你不断检查数据来自“机器正常”分布的概率。如果这个概率很低,这表明机器可能工作不正常,出了问题。但在我们进入高维数据之前,让我们从低维数据开始我们的 NFs 之旅。
6.3.1 NFs 的基本原理
在图 6.7 的左面板中,你看到一个一维数据集。这个数据是统计学中一个非常著名的数据集。它包含了黄石国家公园老忠实喷泉两次喷发之间的 272 个等待时间。在图 6.7 的右面板中,你看到一个二维的人工数据集。想象一下,你的统计学老师问你这些数据来自哪个分布。你会怎么回答?是高斯分布、威布尔分布还是对数正态分布?即使在左边的单维情况下,上述任何分布都不适合。但因为你是一个优秀的读者,你记得第 6.1 节,并提出了例如两个高斯分布的混合。这对于相当简单的分布,如图 6.7 左面板中所示,是有效的,但对于真正高维和复杂的分布,这种方法就失效了。

图 6.7 两个数据集:左边是 1D 中的一个真实数据集(两个间歇泉喷发之间的等待时间),右边是人工数据集。你知道产生这种数据的概率分布吗?我们不知道。
该怎么办呢?记住那句老话,“如果山不来佛祖,佛祖就去见山”?图 6.8 展示了 NF 的主要思想。将来自高斯的数据进行变换,使得最终数据看起来像来自复杂的分布。这是通过变换函数 g(z) 实现的。另一方面,描述 x 中数据的复杂函数通过函数 g^(−1)(x) 转换为 z。
NF 的主要任务是找到这些变换:g(z) 和 g^(−1)(x)。我们暂时假设我们已经找到了这样的函数对:g 和 g^(−1) 。我们希望从它得到两件事。首先,它应该使我们能够从复杂的函数 P**[x](x) 中采样,允许应用于生成新的、看起来逼真的面部图像。其次,它应该允许我们计算给定 x 的概率 P**[x](x),允许应用于新颖性检测等应用。

图 6.8 NF 原理。数据 x 的复杂 PDF p**^x(x) 通过变换函数 x = g(z) 转换为容易的高斯分布,其 PDF 为 P**[z](z) = N(z; 0, 1)。
让我们从第一个任务开始,看看我们如何使用 g 来对新示例 x 进行采样。记住,你不能直接从 P**[x](x) 中采样 x,因为你不知道 P**[x](x)。但对于简单的分布 P**[z](z),你知道如何抽取样本。这很简单!如果简单分布是高斯分布,你可以使用 TFP 通过 z=fd.Normal(0,1).sample() 来实现。然后你应用变换函数 g 来得到相应的样本 x = g(z)。所以,第一个任务就解决了。
那第二个任务呢?某个样本 x 的概率有多大?你不能直接计算 p**^x(x),但你可以通过 z = g^(−1)(x) 将 x 转换回 z,对于这个 z,你知道概率 P**[z](z)。有了 P**[z](z),你可以计算 x 的概率。在简单分布 P**[z](z) 是高斯分布的情况下,确定数字 z 的概率很容易:使用 tfd.Normal(0,1).prob(*z*)。
我们能否使用任何变换函数 g?在这里,情况并非如此。为了找出变换所需属性,让我们考虑从 z 到 x 再返回的循环。让我们举一个例子。如果我们从一个固定的值开始,比如说 z = 4,那么我们会使用 g 来得到相应的 x 值,x = g(4),然后再次从 x 返回到 z,z = g^(−1)(x)。你应该再次得到 z = g^(−1)(x) = g^(−1)(g(4)) = 4 的值。这必须适用于所有 z 的值。这就是为什么我们称 g^(−1) 为 g 的逆。
并不是所有函数 g 都能找到一个逆函数 g^(−1) 。如果一个函数 g 有逆函数 g^(−1) ,那么 g 被称为双射。一些函数,例如 g(z) = z²,仅在有限的数据范围内是双射;例如,在这里,对于正值。 (你能告诉我逆函数是什么吗?) g 必须是双射。此外,我们希望实现高效的流程。在接下来的章节中,你将了解数学细节及其对高效实现的影响。
6.3.2 概率的变量变换技术
在本节中,你首先学*如何在一维中使用 NF 方法。这是统计学家所说的变量变换技术,用于正确地变换分布。这是所有 NF 的核心方法,其中(通常)将多个这样的变换层堆叠成深度 NF 模型。为了解释 NF 模型单层中的情况,我们首先从一维分布的变换开始。稍后,在第 6.3.5 节中,我们将从一维问题推广到高维。为了编写这样的 NF 模型,我们使用 TFP 和特别地使用 TFP 的 bijector 包(例如,见列表 6.3)。所有 TFP bijector 类都是关于双射变换的,它们将变量变换技术应用于正确变换概率分布。
让我们从简单开始。考虑变换 x = g(z) = z² 并选择 z 在 0 和 2 之间均匀分布。(我们称这个例子为这一节中的简单例子。)函数 g^(−1)(x) = √x 满足 g^(−1)(g(x)) = √(z²) = z 对于选择的 z 范围。顺便说一句,如果 z 从 -1 到 1(一个正范围)均匀选择,那就不会是可能的(需要正范围)。但现在,如果我们使用在 0 和 2 之间均匀分布的 z,那么 x = g(z) = z² 的分布看起来如何?看看你是否能猜出来。
在统计学中检查某事的一种方法是在模拟中始终尝试它。为了从均匀分布中模拟 100,000 个数据点,你可能使用 tdf.Uniform (0,2).sample(1000) 。取这些值,将它们平方,并绘制直方图 (plt.hist)。解决方案在下面的笔记本中给出。但先试试看你是否能自己完成。
实践时间 打开 mng.bz/xWVW 。这个笔记本包含本章变量变换/TFP.bijectors 练*的配套代码。在阅读本节时跟随它。 |
|---|
可能这个结果与你的第一直觉有点相反。让我们检查一下当将平方变换应用于均匀分布样本时会发生什么。在图 6.9 中,你可以看到一个平方变换函数(实线粗曲线)的图表,以及在水平轴上的 100 个样本(由刻度表示),这些样本是从 0 到 2 之间的均匀分布中抽取的。相应的直方图显示在图表上方。要平方每个样本(刻度),你可以从刻度垂直向上到平方函数,然后击中它,然后水平向左移动。变换后的值是垂直轴上的刻度。如果你对所有的 z 样本都这样做,你将得到垂直轴上刻度的分布。请注意,刻度在 0 附*的区域比在 4 附*的区域密集。相应的直方图显示在右侧。通过这个程序,可以清楚地看出,变换函数可以在变换函数平坦的区域将样本挤压在一起,并在变换函数陡峭的区域将样本分开。

图 6.9 将一个平方变换函数(实线粗曲线)应用于从均匀分布(水平轴上的刻度)中抽取的 100 个 z 样本,得到变换后的 100 个 x 样本(垂直轴上的刻度)。图上方的直方图和右侧的直方图分别显示了 z 和 x 样本的分布。
这种直觉还意味着线性函数(具有恒定的斜率但不同的偏移量)不会改变分布的形状,只会改变值。因此,如果你想从一个简单的分布转换到一个具有更复杂形状的分布,你需要一个非线性变换函数。
变换函数 g 的另一个重要特性是它需要是单调的,以便是双射的。2 这意味着样本保持相同的顺序(没有超越)。对于单调递增的变换函数,从 z[1] < z[2] 总是跟随 x[1] < x2。如果变换函数是单调递减的,那么 z[1] < z[2] 总是意味着 x[1] < x[2] 。这一特性还表明,在 x[1] 和 x[2] 之间,你总是有与在 z[1] = g(x[1]) 和 z[2] = g(x[2]) 之间相同的样本数量。
对于 NF,我们需要一个公式来描述变换。现在我们已经建立了变换的直观模型,让我们完成最后一步,从样本和直方图到概率密度的转换。现在,我们保留的是某个区间的概率,而不是某个区间的样本数量(见图 6.10)。严格来说(在这本书中我们相当粗心),P[z](z*)是一个概率密度。所有概率密度都是归一化的,这意味着密度下的面积是 1。当使用变换从一个分布转换到另一个分布时,这种归一化是保留的;因此,称为“归一化流”。你不会丢失概率;这就像质量守恒原理。这种保留属性不仅适用于密度曲线下的整个区域,也适用于更小的区间。

图 6.10 理解变换。需要保留的面积 P[z](z)|dz| = p[x](x)|dx|(图中阴影部分)。
要从概率密度值 p[x](x) 到接* x 的值的真实概率,我们必须查看密度曲线 p[x](x) 在长度为 dx 的小区间下的面积。我们通过将 p[x](x) 与 dx 相乘得到这样一个概率:p[x](x)dx。对于 z 也是如此,其中 p[z](z)dz* 是一个概率。这两个概率需要相同。在图 6.10 中,你可以看到变换。曲线下的阴影区域需要相同。
从这个方程我们得到:3
p[z](z) ⋅ |dz| = p[x](x) ⋅ |dx|
这个方程确保在变换过程中没有概率丢失(质量是守恒的)。我们可以解这个方程得到:
p[x](x) = p[z](z) ⋅ |dz / dx|
p[x](x) = p[z](z) ⋅ |dx / dz|^(−1)
在这里,我们交换了分子dz和分母dx。这是可以的,并且有更严格的数学支持。
p[x](x) = p[z](z) ⋅ |dg(z) / dz|^(−1) 其中 x = g(z) .
p[x](x) = p[z](z) ⋅ |g'(z)|^(−1)
p[x](x) = p[z](g^(−1)(x)) ⋅ |g'(g(−1)(*x*))|(−1) 其中 z = g^(−1)(x)
方程 6.2 非常著名,并且有自己的名字:它被称为变量变换公式。变量变换公式确定了变换后的变量 x = g(z) 的概率密度 p**[x]。你需要确定导数 dg(z)/dz 和逆变换函数,然后你可以使用方程 6.2 来确定 p**^x(x)。项 |dz /dx| 描述了从 z 到 x 时长度的变化(如图 6.10 中水平轴上区间的长度)。这确保了图 6.10 中的阴影区域保持不变。我们需要绝对值来覆盖变换函数递减的情况。在这种情况下,|dz /dx| 将是负数。当从 x 到 z 时,长度按相反的方式缩放:
|dz/dx| = 1 / |dx/dz|
让我们花点时间回顾一下你到目前为止学到的内容。如果我们有一个从 z 到 x 的可逆变换 g(z),以及从 x 到 z 的逆函数 g^(−1)(x),方程 6.2 告诉我们在变换下概率分布如何变化。知道变换 g(z) 以及其导数 g'(z) 和 g^(−1)(x),我们可以应用 NF。我们将在下一节中解决如何学*这些流,g 和 g -1 的问题。但首先,让我们将公式应用于初始示例,看看如何使用 TFP 的 Bijector 类非常优雅地完成这项工作。
在初始示例中,我们假设 z 在这个区间内均匀分布在 0 和 2 之间,p**[z](z) = 1/2,因此分布是归一化的。让我们来计算这个例子中的数学问题,其中 x = g(z) = z²。当 z = g^(−1)(x) = √x 和 g'(g^(−1)(x)) = 2 ⋅ g^(−1)(x) 时,方程 6.2 变为
p**[x](x) = p**[z](g^(−1)(x)) ⋅ |g'(g(−1)(*x*))|(−1)
p**[x](x) = 1/2 ⋅ |2 ⋅ √x|^(−1) = 1/4 ⋅ √x
这看起来与模拟相同(参见图 6.11)。

图 6.11 比较了模拟得到的 x = z² 的密度(直方图)和假设 z 均匀分布时使用方程 6.2 的解析推导(曲线实线)
结果表明,TFP 对变量变换有很好的支持。变量变换的核心是一个双射变换函数 g。tfp.bijector 包含所有关于双射的内容,我们之前在本节中介绍了它。让我们先看看 TFP 中的双射(参见下面的列表和相关的笔记本 mng.bz/xWVW)。
列表 6.3 一个初等双射
tfb = tfp.bijectors
g = tfb.Square() ❶
g.forward(2.0) ❷
g.inverse(4.0) ❸
❶ 这是一个简单的双射,从 z 变换到 z²。
❷ 产生 4
❸ 产生 2
在列表中,一个双射函数g将一个分布转换成另一个。第一个(通常是简单的)分布被称为基础分布或源分布,在这个源分布上应用双射函数g。得到的分布被称为转换分布或目标分布。接下来的列表展示了我们的简单示例如何在 TFP 中实现。
列表 6.4 TFP 中的简单示例
g = tfb.Square() ❶
db = tfd.Uniform(0.0,2.0) ❷
mydist = tfd.TransformedDistribution( ❸
distribution=db, bijector=g)
xs = np.linspace(0.001, 5,1000)
px = mydist.prob(xs) ❹
❶ 双射函数;这里是一个平方函数
❷ 基础分布;这里是一个均匀分布
❸ 将基础分布和双射函数组合成一个新的分布
❹ TransformedDistribution 表现得像通常的分布。
注意,我们不需要自己实现变量变换公式。TFP 为我们完成了这项工作!如果我们想创建自己的双射函数,我们需要实现变量变换公式。
6.3.3 将 NF 拟合到数据
在本节中,您将学*使用 NF 建模复杂分布的第一步,并且使用 TFP 双射函数这相当简单。我们首先限制自己使用一维分布。我们通过只使用一个流g来实现这一点。在下一节中,我们将深入探讨并链式连接几个这样的流,以允许更灵活地建模复杂分布。然后在第 6.3.5 节中,我们将使用流来处理高维分布。在本节中,您将学*这些流,这些流由一个参数化的双射函数g给出。
揭秘警告:您通过古老的 MaxLike 原则确定流的参数。
我们如何通过 NF 建模分布?如果您的数据x具有复杂的未知分布p**[x](x),那么使用双射变换函数g通过将变量z通过一个简单的基础分布x = g(z)转换来得到x。如果您知道要使用什么变换g,那么您就没事了。您可以使用您在第 6.3.2 节中学到的方 法来应用它。对于每个样本x,p**^x(x)的似然由转换值p**[x](x**[i]) = p**[z](g^(−1)(x**[i])) ⋅ |g'(g(−1)(*x**[i]*))|(−1)给出。但您如何知道要使用哪个双射变换g呢?
解决方案之一:询问老式的统计学家。他们会启动 EMACS,并在第一步中,将一个简单的模型(如高斯分布)拟合到数据上。当然,高斯分布不足以拟合复杂的分布。经验丰富的统计学家然后会盯着模型与数据之间的差异,做一些只有他们和他们的祭司阶层完全理解的魔法。最后,他们会咕哝着说:“孩子,在你的数据上应用对数变换;然后你可以用高斯分布拟合你的数据。”于是你就可以去实现一个使用tfb.Exp()的流了。在继续阅读之前,请思考一下为什么要在使用指数之前进行冥想。
答案是统计学家给了你如何从复杂分布到简单高斯分布的变换,z = ɡ^(−1)(x) = log(x)。因此,从简单分布到复杂分布的流程 g 由对数的逆,即指数 x = ɡ(z) = exp(z) 给出。
第二种解决方案:你意识到我们生活在 21 世纪,拥有计算机能力,可以通过数据驱动的方式找到双射变换 ɡ,它将一个变量 z 转换为你选择的简单基分布 p**[z](z),到感兴趣的变量 x = ɡ(z)。知道了流程,g 允许你确定复杂的分布 p**[x](x) = p**[z](z) ⋅ |ɡ '(z)| ^(−1) = p**[z](ɡ^(−1)(x)) ⋅ |ɡ '(ɡ^(−1)(x))| ^(−1)(见方程 6.2)。
数据驱动方法的关键思想是,你设置一个灵活的双射变换函数 g,它具有可学*的参数 θ。如何确定这些参数的值?通常的方式——使用最大似然法。你有训练数据 x**[i],你可以通过计算单个训练样本 i 的似然度 p**[x](x**[i] ) = p**[z](g^(−1)(x**[i] )) ⋅ |g'(g^(−1)(x**[i] ))|^(−1) 来计算,以及通过乘以所有单个似然贡献来计算所有数据点的联合似然度!。在实践中,你在训练数据中最小化!。就是这样!
让我们从一个非常简单的例子开始。我们的第一个可学*流程是线性的,只涉及两个参数:a 和 b g(x) = a ⋅ z + b。在列表 6.5 中,你可以看到我们使用了一个仿射双射。只是为了明确这个术语,一个仿射函数 g(x) = a ⋅ z + b 是线性函数 g(x) = a ⋅ z 加上一个偏移量 b。在这本书中,我们有点放松;当我们说“线性”时,我们通常是指“仿射”。当然,对于如此简单的流程,你无法做太多复杂的事情。
在 6.9 图的讨论中,我们已指出,当使用线性变换函数时,分布的形状保持不变。现在我们想要学*从 z ∼ N (0, 1) 到 x ∼ N (5, 0.2) 的变换。因为这两个分布都是钟形,一个(仿射)线性变换就能做到这一点。下面的列表显示了完整的代码,它也在配套的笔记本中。
列表 6.5 TFP 中的一个简单示例
*a* = tf.Variable(1.0) ❶
b = tf.Variable(0.0) ❶
bijector = tfb.AffineScalar(shift=a, scale=b) ❷
dist = tfd.TransformedDistribution(Distribution=
tfd.Normal(loc=0,scale=1),bijector=bijector)
optimizer = tf.keras.optimizers.Adam(learning_rate=0.1)
for i in range(1000):
with tf.GradientTape() as tape:
loss = -tf.reduce_mean(Dist.log_prob(X)) ❸
gradients = tape.gradient(loss, ❹
dist.trainable_variables)
optimizer.apply_gradients(
zip(gradients, dist.trainable_variables)) ❺
❶ 定义变量
❷ 使用由两个变量定义的仿射变换来设置流程
❸ 数据的 NLL
❹ 计算可训练变量的梯度
❺ 将梯度应用于更新变量
训练几个 epoch 后,结果为 a ≈ 0.2 和 b ≈ 5,并将 N (0, 1) 分布的变量转换为 N (5, 0.2) 分布的变量(参见笔记本 mng.bz/xWVW 中的结果)。当然,这样的简单(仿射)线性变换对于将高斯转换为更复杂的分布来说太简单了。
6.3.4 通过链式流进行更深入的建模
你在 6.3.3 节中看到,线性流只能改变基本分布的形状和拉伸,但它不能改变分布的形状。因此,线性变换高斯的结果,再次,是一个高斯(参数已改变)。在本节中,你将学*一种建模目标分布的方法,该分布与基本分布的形状非常不同。这使得你可以模拟复杂现实世界的分布,例如老忠实喷泉两次喷发之间的等待时间。你会发现这使用 TFP 相当简单。
我们如何创建可以改变分布形状的流?记住深度学*中的“傻瓜规则”——堆叠更多层(如第 2.1.2 节所述)。还要记住,在神经网络中的层之间,你使用非线性激活函数;否则,深层堆叠的层可以被一层替代。对于 NF 来说,这个规则告诉你不要只使用一个流,而是一系列流(层之间的非线性很重要,我们稍后会回到这一点)。你从 z 开始,沿着 k 个变换的链到 x:z = z[o] → z[1] → z[2] ⋯→z**[k] = x。图 6.12 展示了这个变换。

图 6.12 通过一系列简单的变换,可以创建出用于模拟复杂分布所需的复杂变换。从右到左,从标准高斯分布 z0 ~ N(0,1) 通过连续变换变为具有双峰形状的复杂分布(在左侧)。
让我们看看从 z[o] → z[1] → z[2] 的两个变换链,以理解一般公式。你知道概率分布 p**[z[0]](z[0]),但你如何确定概率分布 p**[z[2]](z[2])?让我们一步一步来做,并在每一步中使用变量变换公式(方程 6.2)。
首先确定概率分布 p**[z[1]](z[1])。通过变换 z[0] 得到 z 1,z[1] = g1。为了使用变量变换公式,你需要确定导数 g 1' 和逆函数 g 1-1。然后,可以通过方程 6.2 确定分布 p**[z[1]](z[1]),即 p**[z[1]](z[1]) = p**[z[0]](z[0]) ⋅ | ɡ[1]'(z[0])|^(−1) 。按照这种方式,你可以确定变换变量的概率密度 pz 2,即 p**[z[2]](z[2]) = p**[z[1]](z[1]) ⋅ |ɡ[2] '(z[1])|^(−1) ,其中你可以将前面的公式代入 p**[z[1]](z[1]),得到链式流:
p**[z[2]](z[2]) = p**[z[0]](z[0]) ⋅ |ɡ[1] '(z[0])|^(−1) ⋅ |ɡ[2] ' (z[1])|^(−1)
通常,操作对数概率比操作概率更方便。对前面的公式取对数(并使用对数规则 log(a**^p) = p ⋅ log(a),其中 a = ɡ**[i] ' (z**[i] −1) 和 p = −1)得到以下结果:
log( p**[z[2]](z[2])) = log( p**[z[0]](z[0])) − log(| ɡ[1] '( z[0])|) − log(| ɡ[2] '( z[1])|)
对于完整的流程(x = zk),此公式推广到:

要计算概率 p**^x(x),只需在图 6.12 的链中从 x = z**[k] → z**[k][−1] ... → z[o] 追溯,并求和 −log(|ɡ**[i] '(z**[i] [−1])| 项。让我们在 TFP 中构建这样的链。
在 TFP 中创建双射算子链非常方便:只需使用 tfp.bijectors 包中的 Chain(bs) 类,并传递一个 Bijectors bs 列表。结果仍然是一个双射算子。所以,我们只需要简单地链式连接几个仿射标量双射算子,或者我们就完成了?我们还没有完全完成。仿射标量变换只会平移和缩放一个分布。一种思考方式是,这种仿射变换在图 6.9 中是一条直线。没有改变分布形状的可能性。
如果你想改变分布的形状,你需要做什么?你可以在堆叠的线性流程之间引入一些非线性双射算子,或者你可以使用非线性双射算子代替线性双射算子。让我们选择第一种方法。
你需要选择一个非线性参数化变换函数,然后可以通过最大似然方法找到参数值。有许多可能的变换函数(双射算子)可以这样做。查看 mng.bz/AApz 。许多双射算子要么没有参数,如 softplus,要么限制 z 或 x 的允许范围。SinhArcsinh 双射算子有一个复杂的名字,但看起来很有希望:它有两个参数,skewness 和 tailweight,如果 tailweight>0,则对 x 和 z 没有约束。图 6.13 显示了某些参数下的该双射算子。对于 tailweight=1 和 skewness=1,它看起来相当非线性,并且使用这些参数,我们不需要限制 x 和 y 的范围。因此,我们使用它来拟合 Old Faithful 数据(见列表 6.6)。请注意,TFP 包中可能有其他满足要求的双射算子。

图 6.13 不同参数值下的双射算子 SinhArcsinh
让我们构建一个链并添加 AffineScalar 双射算子之间的 SinhArcsinh 双射算子。这将在以下列表中完成。
列表 6.6 TFP 中的简单 Old Faithful 双射算子示例
num_bijectors = 5 ❶
bs=[]
for i in range(num_bijectors):
sh = tf.Variable(0.0)
sc=tf.Variable(1.0)
bs.append(tfb.AffineScalar(shift=sh, scale=sc)) ❷
skewness=tf.Variable(0.0)
tailweight=tf.Variable(1.0)
bs.append(tfb.SinhArcsinh(skewness,tailweight)) ❸
bijector = tfb.Chain(bs) ❹
dist = tfd.TransformedDistribution(Distribution=
tfd.Normal(loc=0,scale=1),bijector=bijector)
❶ 层数数量
❷ 仿射标量变换
❸ SinhArcsinh 作为非线性
❹ 从双射算子列表创建双射算子链
访问笔记本mng.bz/xWVW以查看用于 Old Faithful 喷泉等待时间的这一系列双射算子,它产生了图 6.14 中的直方图。顺便说一句,在图 6.12 中,你可以看到从N(0,1)到 Old Faithful 等待时间分布的一些步骤。

图 6.14 Old Faithful 喷泉等待时间的直方图(填充柱状图),以及拟合的密度分布(实线)。直方图没有显示出像高斯这样的简单分布的形状。五层流动很好地捕捉了数据的特征(实线)。
到目前为止,我们考虑了一维数据,但这种方法是否也适用于高维数据?想象一下,例如,图像数据,其中每个图像的维度为 256 × 256 × 3(高度 × 宽度 × 通道)。同样,我们感兴趣的是学*这种图像数据的分布,以便从中采样。结果证明,这种流动方法也适用于高维。唯一的原理区别是,双射算子不再是单维函数,而是具有与你的数据一样多的维度。
在接下来的两节中,我们将 NF 方法扩展到高维。如果你对这种方法的应用比数学上的变化更感兴趣,你可以跳过这些章节,直接跳到 6.3.7 节。但如果你想知道细节,请继续阅读!
6.3.5 高维空间之间的转换*
让我们制定一个任务,对高维数据的分布(如图像)进行建模,这样我们就可以使用流动方法来拟合分布。首先,我们将图像数据展平,以接收(对于每张图像)具有 196,608 个条目的向量x。这些向量生活在 196,608 维空间中,并且具有未知的分布p**[x](x),这可能是复杂的。现在,你可以选择一个简单的基分布p**[z](z),例如高斯分布。任务是找到一个变换g(z),将向量z转换为向量x。我们需要一个双射变换 g,因此x和z的维度必须相同。让我们看看这种变换在三维空间中的样子,这意味着我们处理的数据点是x = (x[1] , x[2] , x[3])和z = ( z[1] , z[1] , z[3])。变换x = g(z)看起来是这样的:

对于一维流动,主要公式是

并且术语|dg(z) / dz|被识别为从z到x时长度的变化。在三维中,我们需要考虑体积的变化。对于四维及以上,体积的变化现在是变换 g 的超体积的变化。从现在起,我们只称之为体积,无论我们是否有长度或面积。
在一维公式(方程 6.3)中,标量导数 |dg(z) / dz| 被替换为一个偏导数矩阵,这被称为雅可比矩阵。为了理解雅可比矩阵,我们首先回顾一下偏导数的定义。一个关于三个变量 z[1], z[2], z[3] 的函数 ɡ(z[1] , z[2] , z[3]) 对 z[2] 的偏导数表示为 ∂ ɡ(z[1] , z[2] , z[3]) / ∂z[2] 。为了举一个简单的例子,当 ɡ ( z[1] , z[2] , z[3] ) = 42 ⋅ z[2] + sinh(exp(z[1] /z[3])) 时,∂ ɡ(z[1] , z[2] , z[3]) / ∂z[2] 是多少呢?幸运的是,答案是 42。这很简单!关于 z1 和 z3 的偏导数将会更复杂。如果函数 ɡ(z[1] , z[2] , z[3]) 只返回一个数字,我们考虑 g 返回一个向量,比如说,有三个分量。如果你需要一个例子,这个函数 g 可以是一个具有三个输入和输出神经元的全连接网络(fcNN)。对于这个例子,雅可比矩阵如下所示:

在一维情况下,你需要在变量变换公式(方程 6.3)中确定导数 |dg(z)/dz| 的绝对值。在更高维的情况下,实际上你需要用雅可比矩阵行列式的绝对值来替换这个项。高维数据的变量变换公式看起来如下所示:

你不知道什么是行列式或者忘记了它?不用担心。你需要知道的是,对于三角矩阵(如方程 6.5 中所示),你可以通过计算对角元素的乘积来计算行列式。当然,如果三角矩阵下方的某些(或所有)非对角元素也为零,这也是正确的。无论如何,你不必自己计算行列式。
每个 TFP 双射实现方法 log_det_jacobian(*z*),流或流的链可以按照之前描述的方法计算。计算行列式相当耗时。然而,有一个很好的技巧可以加快计算速度。如果一个矩阵是所谓的三角矩阵,那么行列式是对角元素的乘积。如何在雅可比矩阵中得到零?如果函数 gk 不依赖于变量 zi,那么函数 gk 对 zi 的偏导数将得到零。如果我们构建的流使得 g 1(z1, z2, z3) 与 z2, z3 无关,g2(z1, z2,z3) 与 z3 无关,那么之前的矩阵如下所示:

这个矩阵是一个三角矩阵,因此可以通过对角元素的乘积来获得行列式。三角雅可比矩阵的一个好性质是,您不需要计算非对角项(在方程 6.5 中以灰色显示)来确定行列式。这些非对角项在第一个项 p**[z](z) = p**[z](ɡ^(−1)(x))(方程 6.4)中起作用,但不是第二个项,|det(∂g(z) / ∂z)|^(−1) 。为了模拟复杂分布,可能需要使用复杂函数来模拟这些非对角项。幸运的是,如果这些表达式很复杂,这根本不是问题,因为您不需要计算它们的导数。要获得这样一个好的三角雅可比矩阵,只需确保 ɡ**[i](z) 与 z**[j] 独立,其中 j > i。
我们已经看到,导致三角雅可比矩阵的双射函数便于处理。但它们是否足够灵活以模拟所有类型的复杂分布?幸运的是,答案是肯定的!在 2005 年,博加切夫和他的同事们证明了对于任何 D 维分布对(x 的复杂分布和 z 的简单基分布),您都可以找到将一个分布转换成另一个分布的三角双射函数。
6.3.6 使用网络控制流
现在您将看到网络和 NFs 的强大组合。基本思想是使用神经网络来模拟 D 维双射函数的 gi 组分 #
ɡ(z) = (ɡ1, ɡ2,..., ɡ**[D](z[1] ,... z**[D]))。上一节的讨论为我们提供了一些关于如何设计用于模拟双射函数 g 的不同分量 gi 的神经网络的指导:
-
我们希望双射函数具有三角雅可比矩阵,这确保了 gi 与 zj 独立(其中 j > i),即 ɡ**[i](z[1] , z[2] ,..., z**[D]) = ɡ**[i](z[1] ,... z**[i] )。
-
我们希望雅可比矩阵的对角元素易于计算:
∂ ɡ**[i](z[1] ,... z**[i] ) /∂ ɡ**[i] .
-
对于雅可比矩阵下三角中的非对角元素,不需要计算这些函数的偏导数。它们可以相当复杂。
-
最后,但同样重要的是,我们需要一个可逆变换。
让我们关注列表中的第一项,并写出三角双射函数 g(z) 的分量:
x[1] = ɡ1 = ɡ1
x[2] = ɡ2 = ɡ2
....
x**[D] = ɡ**[D](z[1] , z[2] ... z**[D]) = ɡ**[D](z[1] ,... z**[D])
下一个问题是我们应该使用哪些参数函数来表示 gi?在这里,前面列表中的第二和第三条准则发挥作用。你应该设计 gi,使得与雅可比矩阵对角元素相对应的偏导数 ∂ ɡ**[i](z[1] ,..., z**[i] ) /∂ ɡ**[i] 容易计算。对于线性函数,导数是容易计算的。让我们选择 gi 在 z**[i] 上是线性的:
x**[i] = ɡ**[i](z[1] , z[2] ,..., z**[i] ) = b + a ⋅ z**[i]
注意,gi 在 z[1] , z[2] ,..., z**[i] [−1] 上可以是非线性的。这意味着截距 b 和斜率 a 可以是这些 z 元素的复杂函数:b**[i] = b**[i](z[1] , z[2] ,..., z**[i] [−1]) 和 a**[i] = a**[i](z[1] , z[2] ,..., z**[i] [−1])。这导致
x**[i] = ɡ**[i](z[1] , z[2] ,..., z**[i] ) = b**[i](z[1] , z[2] ,..., z**[i] [−1]) + a**[i](z[1] , z[2] ,..., z**[i] [−1]) ⋅ z**[i]
因为 b**[i] = b**[i](z[1] , z[2] ,..., z**[i] [−1]) 和 a**[i] = a**[i](z[1] , z[2] ,..., z**[i] [−1]) 可以是复杂函数,你可以使用神经网络来模拟这些。这是一个已知的事实,具有至少一个隐藏层的神经网络足够灵活,可以适应任何函数,因此 a 和 b 可以以复杂的方式依赖于提供的 z 元素。在一维情况下,你需要一个单调递增或递减的函数来确保双射性。这可以通过确保斜率不为零来保证。在多维情况下,而不是斜率,你现在需要确保雅可比矩阵的行列式不为零。我们通过确保对角线上的所有项都大于零来实现这一点。为此,你可以使用与第四章中建模正标准差相同的技巧:你不会直接使用神经网络输出的 α**[i](z[1] , z[2] ,..., z**[i] [−1]) 作为斜率,而是首先将其通过指数函数。这得到 α**[i] = exp(α**[i](z[1] , z[2] ,..., z**[i] [−1])),在这种情况下,方程式 6.6 变为
x**[i] = ɡ**[i](z[1] , z[2] ,..., z**[i] ) = b**[i](z[1] , z[2] ,..., z**[i] [−1]) + exp(α**[i](z[1] , z[2] ,..., z**[i] [−1])) ⋅ z**[i] 方程式 6.7
计算雅可比矩阵的行列式是容易的。只需计算 gi 对 zi 的偏导数的乘积:

如您所见,矩阵是容易计算的!行列式是正项的乘积,因此也是正的。
如前所述,方程式 6.6 中的模型允许高效实现 NF 模型。在文献中,这类模型有时也被称为逆自回归模型。名称“自回归”表明,回归模型对于变量 x**[i] 的输入仅依赖于同一变量的先前观察值 x[1] ,..., x**[j][−1](因此得名“auto”)。你在第 6.1.1 节中看到了 WaveNet 和 PixelCNN 自回归模型的例子。但流模型本身并不是自回归的,因为 x**[i] = ɡ**[i](z[1] , z[2] ,..., z**[i] ) 是由前一个(和当前)的 z 值决定的,而不是由 x 决定的。尽管如此,还存在一种联系,使得这些模型被称为逆自回归模型。如果你对细节感兴趣,你可能想看看这篇博客文章 mng.bz/Z26P。
要使用 fcNN 实现这样的 NF 模型,你需要 D 个不同的网络,每个网络从不同的输入计算 ai 和 bi(见方程式 6.7):z[1] , z[2] ,..., z**[i] 对于 i ∈ {1 , 2 ,...,D }。拥有 D 个网络将需要许多参数,而且,从 D 个网络中进行采样也会花费相当多的时间。为什么不使用一个接受所有 z[1] , z[2] ,..., z**[D] 输入并一次性输出所有 ai 和 bi 值的网络,从而可以一次性计算出所有 z[1] , z[2] ,..., z**[D] 值呢?花点时间来想一个答案?
答案是,全连接神经网络(fcNN)违反了 gi 与 zi 在 j > i 时相互独立的条件:即 ɡ**[i](z[1] , z[2] ,..., z**[D]) = ɡ(z[1] ,..., z**[i] ),因此不能产生三角雅可比矩阵。但有一个解决方案。存在一种特殊的网络,称为自回归网络,它隐藏了部分连接以确保输出节点 ai 不依赖于输入节点 zi 在 j > i 的情况下。幸运的是,你可以使用 TFP 的 tfp.bijectors.AutoregressiveNetwork,这保证了该属性。这种网络首次在名为“Masked Autoencoder for Distribution Estimation(MADE)”的论文中描述(见 arxiv.org/abs/1502.03509)。
让我们看看在 D = 4 维度下这种网络的训练过程。在训练过程中,我们从观察到的四维 x 转换到四维 z,其中我们得到似然 p**[x](x) = p**[z](z) = p**[z](ɡ^(−1)(x))。为此,我们依赖于
x**[i] = ɡ**[i](z[1] , z[2] ,..., z**[i] ) = b**[i](z[1] , z[2] ,..., z**[i] [−1]) + exp(α**[i](z[1] , z[2] ,..., z**[i] [−1])) ⋅ z**[i] 方程式 6.7 (重复D)
然后,我们需要解方程式 6.7 来得到 zi ,得到:

这是一个顺序过程,因此训练不能并行化,速度相对较慢。然而,在测试阶段,它很快。4
Laurent Dinh 等人,在名为“使用真实 NVP 进行密度估计”的论文中介绍了一种构建可逆流的不同方法,该论文可在arxiv.org/abs/1605.08803找到。在这篇论文中,他们提出了一种称为真实非体积保持流或 Real NVP 的流。名称非体积保持表示这种方法(如三角形流)可以具有不等于一的雅可比行列式,因此可以改变体积。与方程 6.6 中的设计相比,他们的 Real NVP 设计要简单得多(如图 6.15 所示)。当将图 6.15 与方程 6.6 进行比较时,你可以看到 Real NVP 架构是三角形流的简化且稀疏版本。如果你使用三角形流,并将前 d 个维度设置为b = 0 和a = 0,然后让剩余的维度 a 和b仅依赖于 z 的前 d 个分量,那么你最终会得到一个 Real NVP 模型。Real NVP 架构不如完全三角形双射器灵活,但它允许快速计算。

图 6.15 真实 NVP 模型的架构。前 d 个分量z[1] , z[2] ,..., z**[d]保持未变换,得到x[1] = z[1] , x[2] = z[2] ,..., x**[d] = z**[d]。x的其余分量x**[d+1] ,... ,x仅依赖于z的前 d 个分量(z[1] , z[2] ,..., z**[d]),并按以下方式变换:x**[i] = ɡ**[i](z[1] , z[2] ,..., z**[d]) = b**[i](z[1] , z[2] ,..., z**[d]) + exp(α**[i](z[1] , z[2] ,..., z**[d])) ⋅ z**[i],对于i = d + 1 ,...,D;这种乘法用⊙表示。
图 6.15 在一个真实 NVP 模型中,前 d 个分量直接从z传递到x(参见图 6.15 以及以下示例),并进一步用于训练一个神经网络,该网络为剩余坐标x**[i](对于i = d + 1 ,...,D)输出 ai 和 bi。
在 Real NVP 中,首先选择一个 d,其范围在 1 和你问题的维度 D(z 和 x 的维度)之间。为了简化讨论,让我们选择 D = 5 和 d = 2。但是,当然,这些结果也适用于一般情况。流程是从 z(遵循简单的分布)到 x(遵循复杂的分布)。前 d 个分量(这里 d = 2)直接从 z 传递到 x(参见方程 6.8 的前两行)。现在这些 d(这里两个),z1 和 z2,是输入到一个计算 ai 和 bi 的神经网络(参见方程 6.7),得到斜率 a**[i] = exp(α**[i] ) 和线性变换 x**[i] = b**[i] + a**[i] ⋅ z**[i] 的平移 b,其中 i ∈ {3, 4, 5 }(参见方程 6.8 的第 3-5 行)。神经网络有两个头部作为结果。两者都有 D - d(这里,5 - 2 = 3)个节点。一个头部是 b1, b2, b3,另一个头部是 a1, a2, a3。接下来的三个变换变量是通过以下方式确定的:
x[1] = ɡ1 = z[1]
x[2] = ɡ2 = z[2]
x[3] = ɡ3 = b3 + exp(α3) ⋅ z[3]
x[4] = ɡ4 = b4 + exp(α4) ⋅ z[4]
x[5] = ɡ5 = b5 + exp(α5) ⋅ z[5]
这是一种类似于我们之前使用的仿射变换,缩放和平移项由一个神经网络控制,但这次,神经网络只获取 z 的前 d = 2 个分量作为输入。这个网络满足作为双射和三角形的条件吗?让我们逆流而行,从 x 到 z。这得到:

此外,雅可比矩阵具有所需的三角形形式,即使列 > d 中的所有非对角元素为零。你只需要计算对角元素来确定行列式,这对于 NF 方法是必需的。

方程中的非零、非对角元素以灰色阴影表示,因为我们不需要它们。这一部分的流程称为耦合层。
在 Real NVP 中,第一个 d 维度不受流的影响,这有点奇怪。但我们可以通过额外的层来发挥它们的作用。因为我们无论如何都想堆叠更多的层,所以在到达下一层之前,让我们重新排列 zi 分量。重新排列是可逆的,雅可比矩阵的行列式为 1。我们可以使用 TFP bijector tfb.Permute() 来重新排列。在列出 6.7 的相关代码中,我们使用了五对耦合层和排列(也请参阅以下笔记本)。
|
| 实践时间 打开 mng.bz/RArK 。该笔记本包含代码,展示了如何在一个玩具数据集上使用香蕉形状的 2D 分布的 Real NVP 流。
-
执行代码并尝试理解它
-
玩转隐藏层的数量
|
列表 6.7 实际 NVP TFP 的简单示例
bijectors=[] ❶
num_blocks = 5 ❷
h = 32 ❸
for i in range(num_blocks):
net = tfb.real_nvp_default_template(
[h, h]) ❹
bijectors.append(
tfb.RealNVP(shift_and_log_scale_fn=net,
num_masked=num_maske*D*)) ❺
bijectors.append(tfb.Permute([1,0])) ❻
self.nets.append(net)
bijector = tfb.Chain(list(reversed(bijectors[:-1])))
self.flow = tfd.TransformedDistribution( ❼
distribution=tfd.MultivariateNormalDiag(loc=[0., 0.]),
bijector=bijector)
❶ 将 num_blocks 个耦合排列添加到 bijectors 列表中
❷ NF 模型中的隐藏层数量
❸ 隐藏层的大小
❹ 定义网络
❺ 使用网络参数进行平移和流
❻ 坐标排列
❼ 使用两个独立的高斯分布的z分布
因此,你现在已经看到了如何使用网络构建流。技巧是保持一切可逆,并以一种方式构建流,使得雅可比矩阵的行列式可以轻松计算。最后,让我们看看 Glow 架构,并尝试从归一化流中采样逼真的面部图像。
6.3.7 与流一起玩乐:采样人脸
现在你来到了有趣的部分。OpenAI 在开发 NF 模型方面做了很多出色的工作,他们称之为 Glow 模型。你可以用它来创建看起来逼真的面孔和其他图像。Glow 模型与 Real NVP 模型类似,但有一些调整。主要变化是将排列替换为 1 × 1 卷积。
在本节中,我们现在处理图像数据。在 6.3.5 节之前的例子中,我们处理了 1D 标量数据。在 6.3.5 节和 6.3.6 节中,我们使用了 D 维数据(对于z和x),但数据仍然是简单的向量。如果我们想对图像进行操作,我们需要处理张量以考虑其 2D 结构。因此,我们现在必须操作形状为(h, w, d)的张量x和z,它们定义了高度(h)、宽度(w)和颜色通道数(D),而不是向量。
我们如何在张量上应用类似 Real NVP 的流?回想一下向量的 Real NVP 架构(见图 6.15 和方程 6.8)。在张量的情况下,前 d 个通道(现在是 d 个二维切片)不受变换的影响,但作为 CNN 的输入。这个 CNN 定义了输入剩余通道的变换。
正如常规 CNN 架构一样,随着深入网络,高度和宽度减小,通道数增加。背后的想法是找到更抽象的表示。但在 NF 模型中,输入和输出需要具有相同的维度。因此,如果高度和宽度减半,通道数将增加四倍。
在输出层,高度和宽度为 1,深度由输入h ⋅ w ⋅ d的值数给出。更多细节,请参阅 Kingma 和 Dhariwal 撰写的论文“Glow: Generative Flow with Invertible 1x1 Convolutions”,您可以在arxiv.org/abs/1807.03039找到或查看官方 GitHub 仓库github.com/openai/glow。
底线是,一个具有 (h, w, d) 维度的图像 z,通常是 (256,256,3),被转换成一个长度为 h ⋅ w ⋅ d 的向量,通常是 196,608,其中每个维度来自一个独立的 N(0,1) 分布的高斯。这个向量又可以重塑成一个具有 (256 × 256 × 3) 维度的彩色图像 x。
该网络是在 30,000 张名人图像上训练的。训练花费了一些时间,但幸运的是,可以下载预训练的权重。让我们来玩玩这个。打开以下笔记本,在阅读本节文本的同时跟随它。
|
| 实践时间 打开 mng.bz/2XR0 。该笔记本包含下载预训练 Glow 模型权重的代码。强烈建议使用 Colab 版本,因为权重大约有 1GB。此外,由于权重存储在 TensorFlow 1 中,我们使用 TF 1 版本的 Colab。笔记本打开后:
-
样本随机人脸
-
操作人脸
-
在两个面部之间进行变形
-
让莱昂纳多微笑
|
首先,从学*到的面部图像分布中采样一个随机人脸。你可以通过采样一个包含 196,608 个独立高斯分布的向量 z 来完成这个操作,然后将这个向量 x = g(z) 转换成一个可以重塑为面部图像的向量。这样做,通常会发现一些伪影。为了避免这种情况并获得更逼真的“正常”人脸图像,人们不会从 N(0,1) 中抽取,而是从方差较小的高斯分布,如 N(0,0.7) 中抽取,以更接*中心。这降低了得到不寻常外观面部图像的风险。
另一个有趣的应用是混合人脸。图 6.17 展示了基本思想。你从一个图像开始,比如说贝昂丝的图像 x[1]。然后使用流来计算相应的向量 z[1] = ɡ^(−1)(x[1])。然后取第二个图像,比如说莱昂纳多·迪卡普里奥,并计算相应的 z2。现在让我们混合这两个向量。我们有一个变量 c,其范围在 0 到 1 之间,描述了迪卡普里奥的内容。对于 c = 1,它是迪卡普里奥;对于 c = 0,它是贝昂丝。在 z 空间中,混合由 z**[c] = c ⋅ z[2] + (1− c) z[1] 给出。对公式的另一种看法是重新排列它:z**[c] = c ⋅ z[2] + (1− c)z[1] = z[1] + c(z[2] − z[1]) = z[1] + c Δ。然后 Δ 是 z[2] 和 z[1] 之间的差异。参见图 6.16 以了解这种解释。

图 6.16 z 空间中混合的示意图。注意,z 空间是高维的,而不是如图所示的 2D。我们在Δ = z2 − z1 指向迪卡普里奥的方向上从贝昂丝进行线性插值移动。
我们从 c = 0(贝昂丝)开始,沿着 Δ 的方向移动到迪卡普里奥。然后你使用 NF x**[c] = ɡ(z**[c]) 从 z 空间移动到 x 空间。对于某些 c 的值,结果图像 xc 在图 6.17 中显示。

图 6.17 从碧昂丝到莱昂纳多·迪卡普里奥的变形。值从左到右依次为,c = 0(100%碧昂丝),c = 0.25,c = 0.5,c = 0.75,和 c = 1(100%迪卡普里奥)。动画版本可在youtu.be/JTtW_nhjIYA找到。
图 6.17 的优点是,对于所有中间的 xc 值,面部看起来多少有些逼真。问题是我们在高维空间中能否找到其他有趣的方向?结果证明,是的。
CelebA 数据集标注了 40 个类别,例如有胡须、大鼻子、双下巴、微笑等。我们能用这些来找到胡须方向吗?我们取所有标记为胡须的图像的平均位置,称之为 z1,然后取所有标记为无胡须的图像的平均值,称之为 z2。现在让我们希望△ = z[1] - z[2]确实是一个有胡须的方向。这些方向已经被 OpenAI 计算出来,我们可以在笔记本中使用它们。让我们试一试,给迪卡普里奥长出胡须。结果如图 6.18 所示。

图 6.18 给莱昂纳多·迪卡普里奥长胡须。值从左到右依次为,c = 0(原始,无胡须),c = 0.25,c = 0.5,c = 0.75,和 c = 1。您可以在youtu.be/OwMRY9MdCMc找到动画版本。
这非常令人着迷,因为胡须信息在流的训练过程中没有被使用。只有在训练完成后,在潜在空间中才找到了胡须的方向。让我们尝试理解为什么在潜在z空间中的移动会在x空间中产生有效的图像。看看从碧昂丝到莱昂纳多·迪卡普里奥的变形示例。在 196,608 维x空间中有两个点。为了更好地理解,让我们再次打开第 6.3.5 节中讨论的 2D 示例。请随意打开笔记本mng.bz/RArK并向下滚动到单元格,理解混合。2D 示例中的z分布是由两个独立的高斯分布产生的(见图 6.19 的左侧),而x分布看起来像一把飞镖(见图 6.19 的右侧)。

图 6.19 展示了复杂x分布(右侧所示)和潜在z分布(左侧)。学*到的 Real NVP 流将潜在空间转换为x空间。z空间中的直线对应于x空间中曲线的移动。
我们从x空间中的两个点开始:在高维示例中,那是碧昂丝和迪卡普里奥。在我们的 2D 示例中,这些点是(0.6,0.25)和(0.6,-0.25),在图 6.19 中标有星号。然后我们使用逆流来确定z空间中相应的 z1 和 z2 点,在图 6.19 的左侧标有星号。在z空间中,然后我们沿着直线从 z1 移动到 z2。(这就是你在图 6.16 中看到的内容。)在图 6.19 中,你可以看到左侧的线完全在分布内。我们不移动到没有训练数据的区域(灰色点)。现在,我们将线转换回真实数据的x空间。你可以在图 6.19 的右侧看到,结果线现在是弯曲的,并且仍然停留在有数据的区域。在高度空间中也会发生同样的情况,这就是为什么贝昂丝和迪卡普里奥之间的所有点看起来都像真实人脸的原因。
如果我们直接连接x空间中的两个点会发生什么?我们会离开已知点的区域(见图 6.19 中的虚线)。在高维空间中也会发生同样的情况,产生的图像不会像真实图像。那么山羊胡呢?再次,我们在潜在空间z中沿着直线移动,而不离开分布。我们从有效点(迪卡普里奥)开始,沿着某个方向(在我们的例子中,是山羊胡)移动,而不离开z分布。
摘要
-
实际世界的数据需要复杂分布。
-
对于分类数据,多项分布提供了最大的灵活性,但缺点是参数很多。
-
对于具有许多可能值的离散数据(如计数数据),多项分布是无效的。
-
对于简单的计数数据,泊松分布是合适的。
-
对于包括计数数据在内的复杂离散数据,混合离散逻辑分布已在野外成功应用,如 PixelCNN++和 parallel WaveNet。
-
正态流(NF)是建模复杂分布的另一种方法。
-
NFs 基于学*一个转换函数,该函数从简单的基分布到感兴趣的复杂真实世界分布。
-
可以通过 NN 实现强大的 NF。
-
你可以使用基于 NN 的 NF 来模拟高维复杂分布,如人脸。
-
你还可以使用 NF 模型从学*到的分布中采样数据。
-
TFP 提供了以 NF 为中心的
bijector包。 -
正如第四章和第五章所述,在 NFs 的学*中,最大似然(MaxLike)原理起到了作用。
1.如果你感兴趣,预处理是使用统计软件 R 完成的;脚本可在mng.bz/lGg6找到。我们想感谢苏黎世大学的 Sandra Siegfried 和 Torsten Hothorn 为我们提供帮助和 R 脚本的初始版本。
2.更精确地说,它需要严格单调。
-
我们取绝对值(|dz| 和 |dx|),因为 dz 和 dx 可能是负数。
-
事实上,自回归流也存在。由于不同的权衡,这些网络在训练时速度快,但在预测时速度慢。结果发现 WaveNet 就是一种自回归流。
第三部分:概率深度学*模型的贝叶斯方法
在本书的第三部分,你将了解贝叶斯深度学*模型。你会发现,当你遇到新情况时,贝叶斯模型变得尤为重要。贝叶斯模型是一种特殊的概率模型,它增加了额外的不确定性。
在本书的第二部分,你学*了如何设置非贝叶斯概率神经网络模型。这些概率模型允许你描述数据中固有的不确定性。如果存在随机性,你总是需要处理数据中的固有不确定性,这意味着观察到的结果不能完全由输入确定。这种不确定性被称为随机不确定性。
但是,实际上,模型中还存在另一种固有的不确定性。这种不确定性被称为认识论不确定性。认识论不确定性发生的原因是,在没有任何怀疑的情况下无法估计模型参数。这是因为我们没有无限的训练数据。通常,我们只有不涵盖所有可能情况的训练数据。例如,考虑一个训练用来根据性别、年龄、咖啡消费量、酒精血液水平和室内温度来预测人的反应时间的模型。你收集了学生志愿者的数据来训练你的模型。你在学生测试集上评估性能,效果相当不错。现在你想要将你的模型应用于医院的病人。你期望你的模型能工作吗?不一定。但即便如此,你的模型仍然会做出一些预测,尽管这些预测可能完全错误。在本书的最后一部分,你将学*如何设计你的模型,以便你可以识别出你的模型预测不可靠的情况。
7 贝叶斯学*
本章涵盖
-
将外推识别为深度学*的阿基里斯之踵
-
贝叶斯建模的温和介绍
-
模型不确定性的概念,称为认知不确定性
-
贝叶斯方法作为处理参数不确定性的最先进方法

本章介绍了贝叶斯模型。除了似然方法之外,贝叶斯方法是最重要的方法来拟合概率模型的参数并估计相关的参数不确定性。贝叶斯建模方法还包含一种额外的不确定性,称为认知不确定性。您将看到,引入认知不确定性可以导致更好的预测性能,并且更恰当地量化预测结果分布的不确定性。当将预测模型应用于训练期间未见过的情境时,认知不确定性变得尤为重要。在回归中,这被称为外推。
您在本章中会看到,如果这些传统非贝叶斯模型用少量数据进行训练或用于外推,它们不会表达不确定性。但贝叶斯模型会。因此,当您有少量训练数据或无法排除遇到训练中未见过的情境时(如房间里的象,参见本章开头的图),使用贝叶斯方法会更好。您还会看到,即使是像赢得 ImageNet 挑战的顶级深度学*模型,在分类象方面通常表现很好,但这些模型无法正确分类房间里的象。相反,它们会预测一个错误的类别,通常概率很高。在处理新情境时无法传达不确定性并因此产生不可靠的预测,这是非贝叶斯深度学*模型的一个严重缺陷。另一方面,贝叶斯深度学*模型具有表达不确定性的能力。
在本章中,您将学*贝叶斯建模方法并将其应用于简单模型;例如,将抛硬币以贝叶斯方式作为类似 Hello World 的示例。此外,您还将应用贝叶斯建模到线性回归。结果证明,像神经网络这样的更复杂模型需要对贝叶斯建模方法进行*似。我们将在第八章中介绍这些内容。本章是关于理解贝叶斯建模原理的。在深入贝叶斯建模方法之前,让我们看看传统的非贝叶斯神经网络模型有什么问题。
7.1 非贝叶斯深度学*的问题:房间里的象
在本节中,你会发现深度学*模型有时会(带着天真和自信)讲述一个完全错误的故事。我们展示了两个例子,一个是回归,另一个是分类,其中非贝叶斯深度学*失败了。对于回归,我们提供了一个简单的一维玩具示例,你将立即理解它失败的原因。对于分类,例子是通常的图像分类,因此更复杂,但原则是一样的。
通常,传统的深度学*模型的预测在应用于训练中使用的相同数据时非常可靠,这可能会让你产生一种虚假的安全感。贝叶斯建模有助于提醒你潜在的错误预测。然而,在解决传统神经网络模型的弱点之前,我们首先回顾一下深度学*的成功。
让我们回到深度学*还没有取得突破的时代。我们是在 2012 年;iPhone 才五岁。我们正在看的是:
-
没有可能实现具有合理性能的文本到语音转换。
-
计算机不擅长识别你的手写体。
-
需要一个语言学团队来开发一个翻译程序。
-
机器无法理解照片。
在图 7.1 中,你可以看到两个可以被 VGG16 网络正确分类的例子。VGG16 网络是在 2014 年引入的,是早期开始图像分类深度学*革命的神经网络。你可以使用以下笔记本来自行进行分类。

图 7.1:深度学*的良好案例。左侧图像展示了一只被正确分类为猴面犬的狗。右侧图像展示了一头被分类为象种(长鼻象)的大象。狗的图像来自mng.bz/PABn,大象的图像来自mng.bz/JyWV。
实践时间 打开mng.bz/1zZj并遵循笔记本。它为这一节生成了本章的图像。尝试理解笔记本中发生的事情。 |
|---|
所有工作都已完成。在深度学*中没有问题需要修复,我们完成了。但并非如此。为了了解深度学*中的一个重要未满足的挑战,请看图 7.2 左边的图像。图像清楚地显示了一些大象。然而,在图 7.1 中将大象正确分类为某种大象种类的同一网络,在图 7.2 的图像上却完全失败了——深度学*模型没有看到房间里的大象!

图 7.2 深度学*的一个糟糕案例。在 ImageNet 数据上训练的高性能 VGG16-CNN 未能看到房间里的大象!图像中对象的五个最高排名类别预测是马车、购物车、宫殿、电车和贡多拉;大象没有被找到!这张图像是训练集的外推。在虚线右侧的回归问题(外推)中,在没有数据的区域没有不确定性。
为什么深度学*模型看不到大象?这是因为用于拟合深度学*模型的训练集中没有房间内的大象图片。不出所料,训练集中的大象图片显示这些动物在自然环境中。这是一个典型的深度学*模型失败的情况:当面对在训练阶段没有见过的新的类别或情况时。不仅深度学*模型,当测试数据与训练数据不来自同一分布时,传统的机器学*(ML)模型也会遇到麻烦。夸张一点说,深度学*关键依赖于这个“大谎言”:
P(train) = P(test) = “大谎言”
假设 P(train) = P(test) 的条件始终成立;但在现实中,训练数据和测试数据并不经常来自相同的分布。想象一下,例如,你用一个旧相机拍摄的照片来训练一个图像分类模型,而现在你想要分类你用新相机拍摄的照片。
依赖于这种有问题的假设,即训练数据和测试数据之间没有系统性差异,是深度学*和机器学*的一般性主要弱点。显然,作为人类,我们显然以不同的方式学*。世界上没有哪个孩子在学*了大象的样子后,不会在图 7.2 中看到大象。有人推测为什么会这样。例如,朱迪亚·珀尔(Judea Pearl)建议,更智能和鲁棒的深度学*的关键是包括因果关系结构。到目前为止,深度学*只能利用数据中的统计相关性。目前,还没有明确的答案来解决这个问题。
即使我们的目标只是利用深度学*中的统计相关性,我们也面临一个严重的问题:网络不会告诉我们它在房间里的大象有问题。它不知道自己不知道。它只是将大象在行中的图片错误地分配到错误的类别,有时甚至以高概率分配。现在,想象一下你自己坐在一辆自动驾驶汽车里。你感到舒服吗?
看图 7.2 的右侧,我们可以看到这种缺乏识别的原因。网络在数据充足的区域对不确定性进行了完美的分配。不确定性是通过方差来衡量的,除了均值外还进行了建模。在第五章中,我们称这种类型的不确定性为随机不确定性。现在关注由外推指示的区域中的值x。例如,在x = 20 时,数据没有分散,接*零的不确定性是正确的,但当移动到更大的值并进入没有数据(外推)的区域时,问题就开始了。网络简单地取最后的不确定性值并将其外推到它尚未见过的区域,从而引用了《星际迷航》的座右铭:“勇敢地走向无人去过的地方。”好吧,也许太勇敢了。
类似地,大象问题可以看作是一个外推问题。当我们只有一个变量x时,我们可以在一条线上绘制它,并且很容易看出我们离开数据区域的情况。对于大象的情况,这就不那么容易了。我们不再有一个单一的实值量x,现在我们有像素数的平方(宽度为 256 的图像有 65,535 个值)。当你离开数据空间时,很难看出这一点。但是,这确实是一个需要解决的问题(再次想象坐在自动驾驶汽车里)。我们在第 7.2 节中讨论了这一点。
作为另一个例子,想象你想要将大量资金投资于某种投资,你的深度学*模型预测了巨大的回报。你难道不想知道你的深度学*模型对其预测结果分布是否确定吗?在另一个例子中,想象一个用于对医学组织学样本进行分类的深度学*系统。在这里,你也想知道预测类概率的确定性如何。(在不确定预测的情况下,你可以让医生更仔细地检查样本。)
网络如何告诉我们它感到不确定呢?解决方案是引入一种新的不确定性——认知不确定性。认知不确定性来源于古希腊单词“episte-me-”,意为知识。它反映了当你离开数据区域时的不确定性。在实践中,这种不确定性通过模型参数的不确定性来建模,因此有时也称为参数或模型不确定性。在第 7.2 节中,我们讨论了一种统计思维方式,称为贝叶斯推理。贝叶斯推理使我们能够对这种不确定性进行建模。
7.2 第一次接触贝叶斯方法
在本节中,我们试图通过一个直观的例子来理解贝叶斯统计学的原理,然后看看贝叶斯方法是如何对问题的认知不确定性进行建模的。
-
在第 7.2.1 节中,我们通过允许不仅仅有一个解,而是一系列解来扩展标准线性回归模型。
-
在 7.2.2 节中,我们再次审视 7.2.1 节中的直观解决方案,并用贝叶斯术语描述它,以介绍贝叶斯术语。
7.2.1 贝叶斯模型:黑客的方法
为了了解概率预测模型的认知不确定性意味着什么,让我们从一个简单的例子开始,该例子展示了黑客拟合贝叶斯模型的方法。我们使用四个点(见图 7.3)拟合一个概率线性回归模型。在这个模型中,我们假设数据有一个恒定的分散度,σ = 3,因此模型是
P(y|x, (a, b)) = N(y ;μ = a ⋅ x + b ,σ = 3) 公式 7.1
在图 7.3 中,你看到了两个与两个不同参数集 (a, b) 对应的线性回归模型示例。

图 7.3 线性回归和数据。左侧列显示了指示参数值 a 和 b 的线性模型,其中均值线是实线,2.5% 和 97.5% 分位数。右侧列显示了结果 P(y|x, (a, b)) = N(y; μ = a · x + b, σ = 3) 的拟合条件预测分布,用颜色编码表示。在上排中,参数值对应于最大似然(MaxLike)值 aml 和 bml。
观察图 7.3,我们首先关注左部分。在上左部分,我们看到通过最大似然(MaxLike)原则拟合的线。如您从笔记本 mng.bz/wBrP 中回忆起,MaxLike 是 0.064,使 MaxLike 最大的参数值是 a = 2.92 和 b = –1.73。图的下部分还有另一条线,用参数值 a = 1.62 和 b = –1.73 绘制,其似然为 0.056。当你被迫只给出一个 a 和 b 的值时,使用最大化似然(图中的上部分)的值是绝对有意义的。但也许完全忽略其他参数并不是一个好主意。考虑这些参数,但不要像信任最佳(MaxLike)解决方案那样信任它们。
如何衡量你能够信任一组参数 (a, b) 的程度?为什么不采用似然 P(D|(a, b)) 呢?似然与在假设我们的模型参数由值 a 和 b 给定时观察到的数据 D 的概率成正比。因此,我们希望按其似然比例对每个模型(由某些值 a 和 b 定义)进行加权。然而,你应该将这些正确的权重归一化,以便它们的总和为 1。为了实现这一点,我们使用归一化似然 p**[n](D|(a, b)) 作为权重:

实际上,我们计算似然之和
并将似然 P(D|(a, b)) 除以该数以获得归一化版本 p**[n](D|(a, b)*)。图 7.4 显示了不同 a 和 b 值的归一化似然。
实践时间 打开 mng.bz/wBrP 。这个笔记本包含了我们称之为贝叶斯黑客方法的代码,它生成了本章本节中的图。在阅读正文内容时,跟随笔记本,直到你看到分析解之前的“返回正文”符号。 |
|---|

图 7.4 对于真实值 s = 3 和不同斜率 (a) 和截距 (b) 参数值的观测数据的标准化似然。似然 pn(D|(a, b)) 是标准化的,意味着所有像素的总和为 1。
现在我们来看图 7.3 的右侧。我们感兴趣的数量是什么?它是观察给定 x 值的 y 值的概率,该模型是在某些训练数据 D 上训练的 P(y|x,D)。对于这个数量,我们考虑了所有可能的 P(y|x, (a, b)) 值。图 7.3 右上角显示了一个使用 a 和 b 的 MaxLike 估计的例子。另一个使用不同 a 和 b 值的例子显示在图 7.3 右下角。我们现在将成千上万的 P(y|x, (a, b)) 与不同的 a 和 b 值相加,并用标准化似然 p**[n](D|(a,b)) 加权,以得到给定 x 的 y 的预测分布。图 7.5 展示了这个原理。

图 7.5 等号左边的图像显示了概率回归模型 P(y|x, (a, b)),每个模型对应一组不同的参数 (a, b)。图像左边的因素表示在相应模型下观测到的四个数据点 D 的标准化似然 p**[n](D|(a, b))。将不同的模型 P(y|x, (a, b)) 相加,并用标准化似然 p**[n](D|(a, b)) 加权,结果就是等号右边显示的贝叶斯预测模型。
用更数学的语言来说

求和遍历所有可能的参数 a 和 b 的值。让我们再次看看方程,也许从稍微不同的角度。模型 P(D|(a,b)) 由参数 a 和 b 决定。数据只用来确定具有特定参数值 a 和 b 的标准化概率(σ = 3 已给出)。因为 a 和 b 是连续量,我们实际上是在 a 和 b 上积分。但我们甚至更粗心;我们只是在约 30 个不同的 a 和 b 值上评估方程 7.2。列表 7.1 提供了对应于方程的代码,图 7.6 显示了由此得到的预测分布 P(y|x,D)。1

图 7.6 使用左侧颜色编码显示的四个数据点训练的贝叶斯线性回归模型的预测分布,右侧通过条件分布显示在两个不同的 x 位置(右侧的线条所示)。你可以清楚地看到,当离开有数据的 x 区域时,不确定性会增大。
对应于方程 7.2 的代码
pyx = np.zeros((nbins_c, nbins_c), dtype=np.float32) ❶
for a in np.linspace(amin, amax, nbins): ❷
for *b* in np.linspace(bmin, bmax, nbins):
p = getProb(a,b) ❸
pyx += pre_distribution(a,b) * getProb(a,b)
❶ 从一张空白画布开始
❷ 对所有参数 a 进行循环
❸ 根据数据获取参数 a 和 b 的概率
7.2.2 我们刚才做了什么?
你将在第 7.3 节中看到,我们在第 7.2.1 节中进行的实验实际上得到了一个称为贝叶斯统计学的坚实理论的良好支持。在第 7.2.1 节中,你看到了拟合贝叶斯模型的一种黑客方法。它不仅使用了对权重的一个 MaxLike 估计,而且还使用了一个可能的权重分布。这个关于 w 的分布由归一化似然 p**[n](D|w) = C ⋅ P(D|w) 给出,其中 C 是归一化常数。
当拟合预测模型时,你主要对给定输入 x 的结果 y 的预测分布感兴趣。这是我们第四章中引入的 CPD(条件概率分布):P(y|x, D)。方程 7.2 告诉你如何以黑客的方式获得这个预测分布。你取 900 个可能的权重向量 w**[i] = (a, b),对应于 30 个不同值 a 和 b 的可能组合,然后进行以下求和:

对于权重,我们使用归一化似然 p**[n](D|w) = C ⋅ P(D|w)。参数 w**[i] = (a, b) 是连续值。如果你想要在数学上更准确,你应该使用积分而不是求和。为了得到对结果 y 的连续权重预测 CPD P(y|x,D),你应该对所有可能的权重进行如下积分:

让我们尝试确定与第四章和第五章中之前概率模型相比发生了什么变化,这些变化是由 MaxLike 方法引起的。
首先,一个新事物是,在训练好的神经网络中,你不是与固定权重工作,而是与权重分布 p**[n](D|w) 工作。权重分布 p**[n](D|w) 在贝叶斯处理中被称为后验分布,因为它是在(后)你看到一些数据 D 之后推导出来的。为了强调这是在看到数据之后,我们用 P(w|D) 表示后验。这是在数据 D 的条件下权重 w 的概率。我们将在下一节数学上推导这个方程。这个名字后验某种程度上暗示了在看到一些数据之前也存在某种先验分布。你将在下一节学*关于先验分布的内容。但就目前而言,请注意,在前面的小实验中,我们使用的是均匀先验。在下一节,你还将学*如何从先验参数分布得到后验分布。
之前的概率模型只预测了 CPD,它只捕捉了数据内在变异性中的随机不确定性。贝叶斯模型预测了一个包含两种不确定性的 CPD(见方程 7.3):数据内在变异性中的随机不确定性和参数值的不确定性,这种不确定性被参数 P(w|D) 的概率分布所捕捉。稍后,你会看到,在原则上,认知不确定性可以减少到零(如果你有无限数量的训练数据,这些数据涵盖了所有可能的情况)。你不能通过更多的数据来减少随机不确定性。你还会看到,在(罕见的)认知不确定性为零的情况下,结果 CPD,P(y|x, D),与第四章和第五章中的最大似然 CPD 相同。但在我们 7.2.1 节的小例子中,我们只有四个数据点,因此认知不确定性不是零,最大似然模型(见图 7.3)与贝叶斯模型(见图 7.6)看起来相当不同。在最大似然方法中,模型为每个输入 x 预测一个 CPD,该 CPD 具有恒定的宽度(也在训练期间没有数据可用的 x 范围内)。在贝叶斯模型中,预测的 CPD 在外推到新的 x 范围时会变得更宽。这是一种相当好的行为。你的不确定性应该在离开已知领域时增加!我们将在 7.3.3 节稍后回到这个点,并比较我们简单的线性回归例子中的 ML 方法与贝叶斯方法。但首先,让我们更仔细地看看贝叶斯方法。
7.3 贝叶斯概率模型的方法
通过概率分布来设置包含其参数值不确定性的模型的想法相当古老。托马斯·贝叶斯牧师(见图 7.7)在 18 世纪开发了这种方法。如今,统计学中有一个分支被称为贝叶斯统计学。

图 7.7 托马斯·贝叶斯(1701-1761)是一位英国统计学家、哲学家和长老会牧师。这张图片来自维基百科(en.wikipedia.org/wiki/Thomas_Bayes),可能并没有展示贝叶斯本人。但由于没有其他肖像可用,如果需要贝叶斯的图片,它总是被使用。
贝叶斯方法是一种确立的、清晰且详尽的拟合概率模型的方法,这些模型可以捕捉不同类型的不确定性。它是一种进行统计学和解释概率的替代方法。在主流统计学(所谓的频率统计学)中,概率是通过分析重复(频繁)的测量来定义的。更确切地说,概率是在进行无限次重复时相对频率的理论极限。相比之下,在贝叶斯统计学中,概率是以信念程度来定义的。一个结果或参数的某个特定值越有可能发生,对其的信念程度就越高。这个看似松散的想法也导致了概率的有效定义。
在贝叶斯方法中,没有计算机的帮助,只能解决最简单的问题。因此,在 20 世纪的大部分时间里,这种方法都处于停滞状态。但现在,随着计算能力的增强,这种方法被频繁使用。它是一种强大的方法,特别是当你有一些你想要建模的先验知识时。
在 7.3.1 节中,你将学*如何使用贝叶斯方法进行拟合过程。我们概述了在贝叶斯统计中使用的重要术语和数学定律。在 7.3.2 节中,你将使用所学技能来拟合一个 Hello World 贝叶斯模型。
7.3.1 使用贝叶斯模型进行训练和预测
贝叶斯统计中最著名的公式之一被称为贝叶斯定理。它甚至出现在软件公司 HP Autonomy 在剑桥办公室悬挂的霓虹灯牌上(见图 7.8)。除了爱因斯坦的 E = m ⋅ c²,没有多少其他数学公式达到了这样的知名度。

图 7.8 贝叶斯定理定义了如何从逆条件概率 P(B|A)、P(A)和 P(B)推导出 P(A|B)。(此图来自mng.bz/7Xnv 。)
贝叶斯定理将四个概率联系起来:给定 B 的 A 的条件概率 P(A|B),该条件概率的倒数,给定 A 的 b 的概率 P(b|A),以及 A 和 b 的无条件概率 P(A)和 P(B)。在本节末尾的侧边栏中,你会看到贝叶斯定理的推导是简单的。但让我们首先用它来拟合一个贝叶斯概率模型。为此,你将用模型的参数θ代替 A,用 D(代表数据)代替 B。这产生了一个更有用的贝叶斯定理形式:

方程 7.5 中的量非常突出,以至于它们都有名字:
-
P(θ|D) --后验(给定数据D的参数θ的某个值的概率)
-
P(D|θ) --逆(称为似然D)
-
P(θ) --先验
-
P(D) --该量(也称为边缘似然或证据)
注意,在贝叶斯解释中,参数θ的某个值并不是确定无疑的,而是由概率分布P(θ)描述的不确定性。这个分布P(θ)定义了每个参数值θ的概率。某个参数值θ的概率可以解释为对该参数值的信念程度。2
贝叶斯定理是拟合贝叶斯(或贝斯)模型的核心,因为它提供了从数据中学*模型参数后验分布的指导。这对应于在非贝叶斯模型拟合中,给定训练数据 D 找到最大似然参数值θ**[maxLik],但现在我们得到了整个分布P(θ|D)。了解参数θ的后验分布是概率贝叶斯模型所需的一切。现在你可以确定预测分布:

在上一节中,我们使用暴力方法*似了这个预测分布,其中我们只考虑了连续参数θ的一些离散值θ i。在先前的回归示例中,参数是斜率和截距,θ**[i] = (a**[i] , b**[i])。

为了解释预测分布的公式,记住最大似然模型中的预测分布是有帮助的:

我们为 CPD 选择了参数θ的分布,然后使用数据来确定θ**[maxLik]。给定θ**[maxLik],所有可能的输出y都遵循 CPD P(y|x**[test], θ**[maxLik]),因此你可以在计算θ**[maxLik]后忘记数据。使用贝叶斯方法,你不会处理与单个(优化D)参数值对应的 CPD,而是对许多 CPD(P(y|x**[test], θ**[i]))进行加权平均(权重为P(θ**[i]|D)),具有不同的参数值θ**[i],以获得预测分布(例如,参见图 7.5)。
为了获得更深入的理解,想象以下例子。你管理着一个主动管理的基金,你需要对未来某只股票价值的概率预测,因此你想要 P(y|x**[stock], θ**[i])。你的团队中有几位专家。每位专家 i 提供一个略微不同的概率预测 P(y|x**[stock] , θ**[i])。从这些预测中,你能做的最好的事情就是平均预测的 CPDs,并给每个 P(y|x**[stock] , θ**[i]) 一个适当的权重。这个权重应该与专家模型在过去给定股票数据 D 上的表现(似然 P(D|θ**[i] ))成比例。进一步,你还需要加入你对这些专家模型的一般主观判断(先验 P(θ**[i] ))。或者,如果你不愿意评判专家,你也可以给每个专家相同的先验主观判断(先验是常数)。这给了你未归一化的后验分布(见方程 7.5)。归一化后,这告诉你使用后验 P(θ**[i]|D) 作为权重,得到:

在这个观点中,贝叶斯模型是一个加权集成模型——我们使用群众的智慧,但权衡个别专家的贡献。为了明确不同分布的术语,我们在表 7.1 中收集了这些分布,以及相应的公式和一些解释。
表 7.1 本章使用的概率分布
| 名称 | 公式 | 备注/示例 |
|---|---|---|
| 似然 | P(D|θ) | D 是可能的数据,可以是单个量(如抛硬币)或成对的数据(如线性回归或典型的深度学*设置)。y**[i] 是在例子 i 中取的具体结果值。抛硬币:数据 D 是不同投掷的结果(正面 y = 1 或反面 y = 0)。单次投掷的似然 P(y**[i] , θ) 由伯努利分布确定:θ 是正面的似然,1 - θ 是反面的似然。线性回归:数据 D 现在是成对 (x**[i] , y**[i] )。参数 θ 由斜率 a 和截距 b 组成。单个例子的似然由 P(y**[i]|x**[i] , (a, b)) 给出,由正态分布确定 (y**[i]|x**[i] ) ∼ N(a ⋅ x[1] + b , σ)。 |
| 先验 | P(θ) | 在贝叶斯设置中,参数根据先验分布分布。在看到数据之前,你需要定义这个分布。在陈述你的先验信念时存在一定程度的主观性。一些例子包括:抛硬币:P(θ) = U(θ ; 0, 1)。 (你不相信任何人。正面出现的概率 θ 是均匀分布的。)回归:P(a) = N(a ; 0, 1)。贝叶斯网络:P(w) = N(w ; 0, 1)(权重是先验标准正态分布D)。 |
| 后验 | P(θ | D) |
| MaxLike 设置中的预测分布 A.k.a CPD(条件概率分布)对于结果 y 或无条件结果概率分布(如果它不是从 x 预测的) | P(y | θ**[MaxLike] , x)P(y |
| 贝叶斯设置中的预测分布(也称为后验预测分布)又称 CPD(条件概率分布)或无条件结果概率分布(如果它不是从 x 预测的) | P(y | x, D) |
注意:关于如何阅读表 7.1 中公式的简单提示。管道符号(|)右侧的内容是“从”部分。管道符号左侧的内容是“到”部分。您应从右到左阅读这些术语。有时在心中在公式下方画一条箭头有助于使其更清晰。因此,P(D|θ) 是从参数 θ 得到数据 D 的概率。如果房间里有人在场,可以说,“P(D|θ) 是给定参数 θ 的数据 D 的概率。”但思想是自由的,思考您喜欢的内容。
贝叶斯定理(方程 7.5)允许你在拥有一些数据 D 的情况下,了解参数分布 P(θ|D) 的 θ。因此,P(θ|D) 被称为后验,因为你在看到数据之后确定它(名字后验来自拉丁语中的“post”,意为“之后”)。但如何推导 P(θ|D) 呢?你需要确定在参数 θ 的模型下观察到的数据 P(D|θ) 的似然性。此外,你需要知道先验 P(θ) 和证据 P(D)。因为你的训练数据 D 是固定的,P(D) 是一个常数。意识到 P(D) 是一个常数,会让你得出后验分布与似然性和先验的乘积成比例的结论:P(θ|D) ∝ P(D|θ) ⋅ P(θ),这也就是所谓的贝叶斯咒语。
贝叶斯咒语 后验与似然性和先验的乘积成比例。
这表明证据 P(D) 只是为了确保后验概率的总和(或积分)为 1。在数学上,通常更方便使用贝叶斯咒语,并通过以下要求在计算比例之后进行计算:
∫ P(θ|D)dθ = 1
固定 P(D) 是容易的,但如何选择先验 P(θ) 呢?如果你对参数值没有先验知识,例如,你可以使用均匀分布,给每个参数值相同的概率。这意味着你选择的先验是:P(θ) = const。在这种情况下,后验分布 P(θ|D) 与似然性成比例。为什么?因为 P(θ|D) 是一个概率分布;因此,它必须积分到 1,因此,如果先验是常数,后验 P(θ|D) 就由归一化的似然性给出。这正是我们在黑客示例中使用的,我们在方程 7.2 中使用了不同参数值的归一化似然性来加权 CPDs 的贡献。这里再次展示:

贝叶斯定理的推导
贝叶斯定理可以从乘法法则推导如下:
P(a, B) = P(A|B) ⋅ P(B)
简而言之,乘法法则表明,给定事件 A 和b同时发生的联合概率 P(a, b),等于事件 A 发生的概率 P(A),乘以当 A 发生时,b发生的概率 P(B|A)。从左到右阅读这些方程:P(B|A)是从 A 到b,或者更一般地,P(to|from)。这说得通!例如,考虑在海滩散步时找到牡蛎的概率 P(oyster = 0.2。一个牡蛎含有珍珠的概率是 P(pearl|oyster) = 0.01。
从这里你可以计算出找到含有珍珠的牡蛎的概率,通过
P(oyster.with.pearl = P(oyster) ⋅ P(pearl|oyster) = 0.2 ⋅ 0.001 = 0.0002。让我们推导贝叶斯公式。
P(θ|D) = P(D|θ) ⋅ P(θ) / P(D)
你需要使用前一个方程中的乘法法则,并意识到你可以消去 a 和 b。这样做得到这个方程:
P(B) ⋅ P(A|B) = P(B, A) = P(a, B) = P(A) ⋅ P(B|A)
将两边除以 P(B) 得到贝叶斯定理:
P(A|B) = P(A) ⋅ P(B|A) / P(B)
那很简单!鉴于贝叶斯定理的力量,推导过程就像吃蛋糕一样简单。然而,其他强大公式的推导,如 E =mc²,却要复杂一些。
在贝叶斯回归问题(以及后来的深度贝叶斯神经网络)中,我们想要预测每个输入的联合概率分布 p(y|x)。但在我们到达那里之前,让我们首先尝试拟合一个单一(无条件)分布 P(y)。
7.3.2 将抛硬币作为贝叶斯模型的“Hello World”示例
让我们使用第 7.3.1 节中学*的贝叶斯统计概念来拟合你的第一个贝叶斯模型。为了保持简单,让我们假设你想要预测抛硬币实验的结果。两种可能的结果是正面(y = 1)和反面(y = 0)。你想要确定两种可能结果的前瞻分布 P(y)。
注意,在这本书的多数其他例子中,你有一些输入,并且你已经根据输入值估计了结果分布。在这些类型的例子中,你估计的是结果的条件概率分布。在这个抛硬币的例子中,你没有输入变量。得到正面的概率不依赖于任何外部变量;你总是抛同一个硬币。因此,你只需要估计结果的无条件概率分布。
如果你知道这是一个公平的硬币,预测抛硬币例子的(无条件)结果分布就很容易。对于一个公平的硬币,前瞻分布将正面分配 0.5 的概率,反面分配 0.5 的概率。这种概率结果捕捉了硬币实验中固有的随机不确定性——你不知道你会得到正面还是反面。另一方面,认知不确定性为零。你肯定知道正面的概率是 0.5;毕竟,它是一个公平的硬币。
在概率模型方面,你将预测分布描述为具有二元结果 y(正面:y = 1,反面:y = 0)的伯努利分布。它只有一个参数 θ,对应于得到正面的概率,所以 Θ = P(y = 1)。如果你有一个公平的硬币,参数 θ 是 θ = 0.5。但 θ 可以取其他值。图 7.9 的左侧显示了固定 θ 的预测分布。
让我们假设这个硬币来自一个可疑的赌徒,你不能假设它是一个公平的硬币。你也不能说出 θ 的确切值。这意味着你需要估计正面的概率:Θ = P(y = 1)。
为了生成一些训练数据,你抛硬币三次,并且每次都观察到正面:D = (1, 1, 1)。哎呀,第一印象是你得到了一个不公平的硬币。但在三次抛掷之后你能有多确定?我们稍后会谈到贝叶斯处理,但首先,让我们用非贝叶斯方法来看看你能得到什么。
抛硬币例子的最大似然方法
让我们使用传统的非贝叶斯最大似然方法来拟合伯努利模型到抛硬币实验的结果。为了拟合模型,你使用你的训练数据:D = (y[1] = 1, y[2] = 1, y[3] = 1)。基于这些数据,伯努利模型中参数 θ 的最佳值是多少(见图 7.9)?

图 7.9 二元变量 y 的伯努利分布,参数为 θ(左)。在右侧,你看到通过观察连续三次正面得出的最大似然方法的预测分布。
最大似然估计的正面概率(y = 1)是通过观察到的正面次数(n1)除以抛掷次数(n)来计算的:θ**[MaxLik] = n[1] / n = 3 / 3 = 1。标准差由 sd(θ**[MaxLik]) =θ**[MaxLik] ⋅ (1 − θ**[MaxLik]) = 1 ⋅ 0 = 0 给出。(要查看标准差公式的推导,请参阅 mng.bz/mB9a 。)这意味着最大似然估计将概率设为 1,并且随机不确定性为零(sd(θ**[MaxLik]) = 0),分配给结果,正面。在这种情况下,预测分布(见图 7.9 右侧)不包含任何不确定性。得到的模型表明,抛硬币总是显示正面。在只看到三次抛掷之后,这是一个相当冒险的声明!
抛硬币例子的贝叶斯方法
让我们采用贝叶斯方法来拟合伯努利模型到抛硬币实验的结果。你假设你需要对参数 θ 的某些不确定性进行考虑。毕竟,你只有三个点的训练数据!而不是估计参数 θ 的单个(最优)值,你的目标是确定参数的后验分布。再次看看贝叶斯公式(方程 7.5):
P(θ|D) = P(D|θ) P(θ) / P(D)
其中 P(θ|D) 是后验概率,P(D|θ) 是似然函数,P(θ) 是先验概率,P(D) 是边缘似然(用于归一化)。这告诉你需要确定联合似然 P(D|θ),并且先验 P(θ) 和 P(D) 作为归一化常数。联合似然是什么?你将所有三个观察到的似然相乘,从而得到联合似然:
P(D|θ) = P(y = 1) ⋅ P(y = 1) ⋅ P(y = 1) = θ ⋅ θ ⋅ θ = θ³
现在,我们应该选择什么样的先验?你知道参数 θ 必须在零和一之间,因为它是每次抛掷得到正面的概率。让我们假设零和一之间的所有 θ 值都是等可能的,并采用均匀分布。因为 θ 可以取零和一之间的任何值,所以 P(θ) 是一个连续概率分布,需要积分到一(参见图 7.11 的上部分)。
但在处理连续分布和积分以推导解析解之前,我们再次使用暴力方法。为此,我们建议你遵循笔记本中的代码并做练*。
通过暴力*似求解抛硬币示例的贝叶斯解
|
| 实践时间 打开mng.bz/5a6O 。这个笔记本展示了如何通过暴力方法以贝叶斯方式拟合伯努利分布。
-
假设均匀先验,使用暴力方法进行抛硬币实验。
-
调查在大训练数据集情况下参数后验形状的发展。
|
要使用暴力方法,我们在 19 个网格点(θ[1] = 0.05 , θ[2] = 1, θ[3] = 0.15 ,..., θ[19] = 0.95)上采样先验 P(θ)。在这个暴力方法中,我们只有 θ 1 的一组离散值,因此我们可以用总和代替积分,用概率代替概率密度。这是因为我们假设先验 P(θ) 的所有 19 个值具有相同的概率:P(θ) = 1/19 ≈ 0.052632(参见图 7.10 的左上部分)。

图 7.10 先验分布(左上部分)和后验分布(右上部分)。下部分显示了结果的 CPD(条件概率分布)(正面为 1,反面为 0)。这些图是通过暴力方法对抛硬币实验进行创建的。
在计算后验参数分布之前,让我们看看在看到任何数据之前,结果的预测分布看起来如何。因为先验给出了所有 θ i 值相同的概率,所以你期望正面和反面的概率是相等的。让我们检验我们的直觉,并使用这个方程推导出预测分布:

在抛硬币示例中,我们称模型参数为 θ 而不是 w,我们没有输入 x,这导致:

将 P(y = 1|θ**[i] ) = θ**[i](正面的似然)和 P(θ) = 1/19 ≈ 0.052632(先验)代入,得到 P(y = 1) = 0.5,相应地,P(y = 0) = 1 − P(y = 1) = 0.5。这正是我们预期的:正面和反面的概率各为 50%(参见图 7.10 的左下部分)。你可以使用这里给出的贝叶斯咒语确定未归一化的似然:
贝叶斯咒语 后验是似然乘以先验的比例。
让我们计算每个 19 个网格点 θ i 的非规范化后验(见表 7.2):

要得到规范化后的后验值,你需要将每个非规范化后验值除以所有值的总和:

对于得到的结果,查看图 7.10 中的右上角图。正如预期的那样,在看到三次正面后,后验分布倾向于接*一的 θ i。但后验分布仍然给小于一的 θ 值分配一些概率,允许硬币在每次投掷时不一定总是出现正面。现在你有了所有基于后验确定预测分布的要素:

表 7.2 收集在表中的暴力结果。每一行对应一个网格点。列包含参数值(theta)、联合似然(jointlik)、prior、非规范化后验(unnorm_post)和 post。

将 P(y = 1 | θ**[i] ) = θ**[i](正面的似然)和 P(θ**[i]|D)(来自表 7.2 的最后一列的后验)代入
P(y = 1 | D) = 0.78
和
P(y = 0 | D) = 1 −P(y = 1 | D) = 0.22
根据基于后验的这种预测分布,你可以预期有 78% 的概率出现正面,22% 的概率出现反面(见图 7.10 的右下角图)。
解析地解决抛硬币例子的贝叶斯解决方案
对于抛硬币的例子,拟合贝叶斯伯努利模型是一个如此简单的问题,以至于你可以精确地解决它。不过,让我们看看它是如何工作的。
对于先验分布,你再次使用均匀分布,给每个可能的 θ 分配相同的先验概率。因为我们希望 P(θ) 是一个有效的概率分布,所以 P(θ) 需要积分等于一。因此,先验参数分布是 P(θ) = 1,对于 θ 在零和一之间。要在连续先验下推导先验预测分布,你可以从方程 7.6 开始:

但在这里你没有输入特征 x,你处于看到数据之前的情况,这意味着你也没有数据 D,你需要使用先验 P(θ),而不是后验 P(D|θ),得到:
P(y) = ∫ [θ] P(y|θ) · pn(θ) dθ
方程 7.6 的无条件情况下的变体
因此,在看到数据之前预测结果 y = 1 的预测概率可以确定如下:
![]() |
(我们使用方程 7.4。) |
|---|---|
![]() |
(我们进行积分并得到 θ 的反导数 ½ θ²。) |
![]() |
(我们输入数字。) |
![]() |
(我们使用逆概率来计算 P(y = 0)。) |
哎呀!这成功了,你得到了与暴力方法相同的先验预测分布(见图 7.11 的左下角面板)。为了确定后验,你将再次使用贝叶斯公式:
P(θ|D) = P(D|θ) ⋅ P(θ) / P(D)

图 7.11 分析方法下抛硬币实验的先验和后验分布(上部分)以及相应的预测分布(下部分)。
这个公式中的项有以下名称:
P(θ|D) = P(D|θ)
-
P(θ|D) 是后验。
-
P(D|θ) 是似然。
-
P(θ) 是先验。
-
P(D) 是边缘似然(用于归一化)。
这个公式告诉你,如果你想确定未归一化的后验,你需要确定联合似然 P(D|θ) 和先验 P(θ)。你已经有了先验 P(θ) = 1。那么联合似然是什么?与暴力方法相同。让我们回顾一下。你有三个观察结果(三次正面),得到联合似然:
P(D|θ) = P(y = 1) ⋅ P(y = 1) ⋅ P(y = 1) = θ ⋅ θ ⋅ θ = θ³
现在你已经拥有了计算参数分布后验所需的一切。为了帮助理解,你可以使用贝叶斯咒语:后验与似然乘以先验成正比。我们将在下一步确定归一化常数 C。
P(θ|D) = 后验 = C ⋅ 似然 ⋅ 先验 = C ⋅ θ³ ⋅ 1
让我们推导确保后验积分为一的归一化常数 C:

将 C = 4 代入你上面推导的后验公式,得到后验 P(θ|D) = 4 ⋅ θ³。让我们享受你推导出的后验(见图 7.11,右上部分)。形状看起来与暴力方法得到的结果相似,倾向于接* 1 的 θ 值。值得注意的是,后验仍然捕捉到一些不确定性;它并不声称硬币总是落在正面。在这种情况下,后验在图 7.11 中看起来像 1 处的尖锐峰值。
让我们推导后验下的预测分布。考虑到后验参数分布,你可能会预期它为正面(y = 1)分配比反面(y = 0)更高的概率。你通过使用方程 7.6 的一个版本来推导后验预测分布,但在这里我们没有 x,方程看起来是这样的:

方程 7.6 对于无条件设置(我们没有任何 x)
在抛硬币的例子中,这导致

当将 ∫[0]¹ P(Y = 1|θ) = ∫[0]¹ θ ⋅ 4 ⋅ θ³ dθ 和 P(θ|D) = 4 ⋅ θ³ 代入时,你得到
| P(Y = 1 |D) = ∫[0]¹ P(Y = 1|θ) ⋅ P(θ|D) dθ | (我们使用方程 7.4.) |
|---|---|
| P(Y = 1 |D) = ∫[0]¹ θ ⋅ 4 ⋅ θ³ dθ = 4/5 ⋅ θ⁵ |[0]¹ | (我们进行积分,得到 4θ⁴ 的反导数 /5 θ⁵。) |
| P(Y = 1 |D) = 4/5 ⋅ 1⁵ − 4/5 ⋅ θ ⁵ = 0.8 | (我们代入数字。) |
| P(Y = 0) = 1 − P(Y =1) = 0.2 | (我们使用逆概率来计算 P(Y = 0).) |
再次,你得到的结果与暴力*似相似(但更精确)。贝叶斯预测分布对结果留有一定的不确定性,并给出了观察尾巴的 20%概率(见图 7.11,右下角面板)。在只观察了三次投掷后,这似乎是合理的。在这种情况下,贝叶斯模型似乎比 MaxLike 模型更可取,后者预测出头的概率为 100%(见图 7.9,右面板)。
硬币投掷示例和笔记本中的练*得出的要点
-
在简单的模型,如伯努利模型中,可以对后验和预测分布进行解析推导。使用暴力方法,你可以*似解析解。暴力方法的优势在于,当积分变得困难时,它仍然有效。请注意,暴力方法也不是万能的。它不能用于像神经网络这样的复杂问题。
-
与先验相比,后验给导致观察数据更高似然率的参数值分配了更多的质量(概率)。
-
与 MaxLike 方法不同,在贝叶斯方法中,你不会只选择具有最高概率的一个参数值来推导预测分布。相反,你会在所有可能的预测分布上平均,这些分布由相应参数值的后验概率加权。训练数据集越大,后验分布的分散就越小(如笔记本
mng.bz/5a6O中所示)。 -
训练集越大,先验的影响越小(如笔记本中所示)。
-
对于大型训练数据集,后验分布是围绕 MaxLike 参数估计的一个狭窄(高斯)分布。
关于先验选择的(有趣)事实
你在回归和硬币投掷示例中看到了如何训练一个简单的贝叶斯模型。在第八章中,你将看到如何训练贝叶斯深度学*模型。但在这样做之前,让我们先对贝叶斯方法建立一些信心,并消除对先验使用的担忧。在贝叶斯建模中,你不仅需要为结果的 CPD 选择一个分布,而且在看到任何数据之前,你还需要为先验P(θ)选择一个分布。
在获得一些训练数据后,你可以通过使用贝叶斯定理(方程 7.5)来确定后验P(θ|D)。然而,先验分布仍然有一定的影响,因为你是用它来确定后验的。这种贝叶斯训练程序在通常内向的统计学家中引发了一场大讨论。反贝叶斯阵营的主要论点是
-
使用先验引入了主观性。通过先验,你可以给某些参数范围赋予高概率。结果,你会将后验拉向这个范围。
-
“让数据说话”更科学。
另一方面,贝叶斯阵营认为
-
所有合理的先验都会导致类似的模型,这些模型在大型数据集上都会收敛到使用最大似然方法得到的模型。
-
先验的“向先验收缩”效应有助于避免假阳性结果。
为了支持他们的第二个论点,贝叶斯统计学家选择了一项经验研究(发表在科学生物学期刊上),该研究以非贝叶斯方式进行分析,并得出结论:漂亮的父母比不漂亮的父母有更多的女儿(p 值为 0.015)。a 这个结果被解释为进化效应,因为对于女性来说,比男性更有利于长得漂亮。这些结果被报道在公共媒体上,例如“每日邮报”,在那里你可以找到著名和美丽的父母和他们第一个女儿的照片(见mng.bz/6Qoe 。如果你启用了广告拦截器,链接可能无法工作D)。
数据来自一项英国研究,研究人员要求教师对学生的吸引力进行评分。四十年后,这些长大成人的学生被问及自己孩子的性别。非贝叶斯分析发现,与不那么吸引人的父母相比,漂亮的父母中女儿的比例显著更高。著名贝叶斯统计学家安德鲁·杰尔曼通过使用先验,对父母吸引力对其子女性别的影响给予了较高的概率,重新分析了这些数据。他通过以下事实来证明其先验选择的合理性:所有其他已知的影响子女性别的因素(例如,例如,父母在怀孕期间的压力)也都是小的。他的分析得出结论,父母的吸引力不会影响生女孩的可能性。
为了支持先验的使用,杰尔曼进行了一项模拟研究,研究中有小到中等的效果大小和相当小的样本量。在大多数模拟运行中,以非贝叶斯方式分析这些数据导致了非显著的结果。但由于其随机性,一些运行导致了显著的结果。在这些显著结果中,40%的报告效应指向了错误的方向!而且如果结果引人注目,它就会被发表。因此,杰尔曼认为,使用保守的先验作为正则化方法进行贝叶斯分析更为合理。
深度学*模型使用偏好小权重值的先验来正则化权重是否合理?我们认为是的,并给出一些理由:
-
经验表明,经过训练的神经网络通常具有较小的权重。
-
较小的权重导致输出(在分类中,概率)不那么极端,这对于未经训练的模型来说是理想的。
-
预测模型的一个已知属性是,向损失函数中添加一个偏好较小权重的组件,通常有助于提高预测性能。这种方法也被称为正则化或非贝叶斯神经网络中的权重衰减。
a p 值估计的是由于纯偶然性发现观察到的效应(或更强的效应)的概率。通常,p 值低于 0.05 的发现被称为具有统计学意义。
7.3.3 重新审视贝叶斯线性回归模型
在本章的开头,您看到了如何以黑客的方式做贝叶斯线性回归。在黑客的方式中,我们使用了具有无限大先验的贝叶斯模型来处理两个参数a和b。这个模型有一个很好的特性,即在没有训练数据的外推范围内获得更多的不确定性(参见图 7.12 的右侧)。而传统的通过 MaxLike 方法拟合的模型则不是这样(参见图 7.12 的左侧)。

图 7.12 在假设已知数据分布,σ = 3 的情况下,用概率线性回归模型拟合了四个数据点。在左侧,您可以看到 MaxLike 模型;在右侧,是具有无限大先验的贝叶斯模型。实线代表预测结果分布的均值,虚线表示 CPD 的 2.5%和 97.5%分位数。
由于只有贝叶斯模型能够在离开已知领域时表达增强的不确定性,因此似乎拟合贝叶斯模型比拟合传统的基于 MaxLike 的模型更好。这在图 7.12 中的两个拟合中是显而易见的。当观察预测 CPD 的宽度时,贝叶斯模型在离开有数据范围时预测了一个更宽的 CPD。但是,在训练数据的插值范围内,贝叶斯模型的不确定性也比 MaxLike 模型高。因为模型拟合仅依赖于四个数据点,贝叶斯模型较高的不确定性可能更符合实际情况,这也支持我们选择贝叶斯模型。另一方面,大的分散也可能只是由于用于模型参数(斜率和截距)的宽泛先验(均匀分布)。为了调查贝叶斯模型是否为条件结果分布提供更现实的预测,让我们进行一些实验来回答以下问题:
-
预测分布如何依赖于先验和训练数据量?
-
贝叶斯模型是否比传统的基于 MaxLike 的模型有更好的预测性能?
为了回答这两个问题,你可以进行以下实验:
-
从具有σ = 3 的线性数据生成过程中模拟出几个不同大小的训练数据集(例如,2、4、20 和 100 个数据点)。然后对所有训练集拟合 MaxLike 模型和三个贝叶斯模型:一个具有均匀先验,一个具有标准正态先验,一个具有以 0.1 为尺度的均值中心正态分布。检查结果 CPD 如何随着先验宽度的变化而变化。同时检查贝叶斯 CPD 的宽度是否随着训练数据的增加而减小,并逐渐接* MaxLike 模型的 CPD。
-
调查不同训练集大小的情况下,贝叶斯模型或基于最大似然(MaxLike)的模型的预测性能是否更好。从训练集的 x 值范围内采样一个训练集和一个测试集。然后拟合贝叶斯模型和最大似然模型,并确定测试集的负对数似然(NLL)。(预测性能更好的模型会产生更低的测试 NLL。)为了得到可靠的结果,你应该使用大的测试数据集,并重复整个过程几次,以便比较获得的测试 NLLs 的结果分布和均值。
在开始这些实验之前,你需要确保可以在合理的时间内完成它们。不幸的是,贝叶斯拟合的暴力方法太慢了。如果我们知道解析解,我们可以大大加快拟合过程。你在 7.3.2 节中的抛硬币例子中看到,确定解析贝叶斯解需要解决一些积分。你能够在抛硬币例子中解决这些积分,因为你只有一个模型参数,并且似然和先验分布都很简单。但在具有许多参数的更复杂模型中,这些积分会迅速变得复杂。结果发现,贝叶斯通常不能解析求解,而必须求助于模拟或*似。这也是贝叶斯模型在大量计算能力出现之前并不受欢迎的原因。你将在第八章中了解更多关于这些*似的内容。
对于一个简单的线性回归模型,例如黑客的例子,仍然可以推导出贝叶斯解。为此,你需要假设数据方差 σ 2 是已知的(就像我们在黑客的例子中所做的那样)。进一步,你需要为模型参数的斜率和截距使用高斯先验。但即便如此,推导过程已经相当冗长。因此,你可以跳过数学推导,直接实现计算后验和预测分布的解析公式(参见本章最后一份笔记本)。Christopher M. Bishop 的《模式识别与机器学*》中给出了公式的完整处理和推导,你可以通过mng.bz/oPWZ 访问。
使用解析表达式,你可以检查是否可以重现 7.2 节中贝叶斯黑客例子的结果。在黑客的例子中,我们使用了均匀先验。这可以通过在解析解中将高斯先验的尺度参数设置为无穷大(或非常大)来实现。如果你想自己验证,我们建议你从本章开头的贝叶斯黑客之道笔记本的最后部分开始(参见mng.bz/qMEr)。现在,你已经掌握了快速、解析的贝叶斯解,你可以在下面的笔记本中执行两个建议的实验。
|
| 实践时间 打开mng.bz/nPj5。这个笔记本生成了图 7.13 中的图表,并回答以下问题:
-
预测分布如何依赖于先验和训练数据量?
-
贝叶斯模型是否比传统的基于 MaxLike 的模型有更好的预测性能?
|
使用这个笔记本,你可以调查这两个问题。让我们看看第一个问题:预测分布如何依赖于先验和训练数据量?简短的回答是,如果先验分布不是极其狭窄,那么先验分布的选择并不关键。训练数据量越大,贝叶斯模型就越接* MaxLike 模型。在笔记本中,你可以一步步地解决这个问题。
在解析解的高斯先验中,你可以设置均值和标准差。作为先验的均值,你选择零,因为你不知道截距或斜率是正数还是负数。主要的调整参数是先验的尺度σ0。将其设置为较大的数值,如σ[0] = 10000,相当于平坦的先验,而将其设置为较小的数值,如σ[0] = 0.1,则会在零均值周围产生尖锐的先验。在图 7.13 中,你可以看到σ[0] = 0.1 的先验会产生一个斜率接* 1 的预测分布,其宽度相对较小,并且在显示的x范围内不发生变化。另一方面,σ0 = 1 的先验会产生一个与σ[0] = 10000 的平坦先验相对应的 CPD。这表明均值为 0、标准差为 1 的高斯先验对结果的拟合没有大的偏差。这些陈述对所有大小的训练数据集都有效(见图 7.13 的上行和下行)。训练数据量大小的主要影响是,当使用较大的训练数据集时,贝叶斯模型的认知不确定性降低。在大训练数据集的极限情况下,不确定性纯粹是随机的,贝叶斯模型产生的 CPD 与基于 MaxLike 的模型相同(见图 7.13 的最右侧列)。

图 7.13 展示了先验尺度σ0 对贝叶斯线性模型(前三个列)中得到的预测 CPD 的影响,以及基于 MaxLike 的线性模型对应的 CPD(最右侧列)。贝叶斯模型使用给定的数据标准差为 3,斜率和截距的先验为高斯分布,均值为 0,标准差分别为 0.1、1 和 10,000(见图表标题)。在上行中,训练数据包含 4 个数据点;在下行中,包含 20 个数据点。
让我们转向第二个问题:贝叶斯模型是否比传统的基于最大似然模型有更好的预测性能?简短的答案是肯定的!如第五章所述,测试负对数似然(NLL)是量化比较不同模型预测性能的适当度量:越低越好。为了调查贝叶斯和最大似然模型在测试 NLL 上的差异,并超出随机变化,你需要在 100 个模型上平均测试 NLL,每个模型使用新生成数据。图 7.14 显示了结果。你可以看到贝叶斯模型优于基于最大似然模型。当使用贝叶斯模型而不是传统的基于最大似然模型时,训练数据集越小,预测性能的提升就越大。

图 7.14 通过测试 NLL(越低越好)比较贝叶斯模型和基于最大似然线性模型的预测性能。两个模型都使用给定的数据方差。贝叶斯模型使用均值为 0 和尺度为 1 的高斯分布来处理斜率和截距。
有没有直观的解释说明为什么贝叶斯方法优于最大似然方法?还记得第 7.3.1 节中的金融专家的例子吗?贝叶斯方法使用了许多人的智慧,而最大似然方法依赖于单个最佳专家的专长。如果数据很少,听取许多专家的意见是有意义的。
在下一章转向贝叶斯神经网络之前,让我们总结一下你应该从贝叶斯建模的介绍中吸取的内容。贝叶斯模型可以通过概率分布捕捉关于其参数值的认知不确定性。要采取贝叶斯方法,你需要为参数分布选择一个先验。先验可以是常数(均匀分布D)或钟形(正态分布,通常均值为零),旨在包含先验知识或正则化模型。当训练贝叶斯模型时,后验被确定。用于训练的数据越多,后验的分布(方差)越小,这表明参数(认知)不确定性降低。如果你有一个大的训练数据集,你的贝叶斯模型会产生与最大似然模型相似的结果。
摘要
-
数据中的固有不确定性,称为随机不确定性,可以用第四章到第六章中介绍的概率方法进行建模。
-
此外,贝叶斯概率模型也捕捉了认知不确定性。
-
认知不确定性是由对模型参数的不确定性引起的。
-
非贝叶斯模型在离开已知基础时无法表达不确定性。(这些模型无法谈论房间里的大象。)
-
贝叶斯模型在预测外推区域或训练数据不足的情况下可以表达不确定性。
-
在贝叶斯模型中,每个参数都被一个分布所取代。
-
在拟合贝叶斯模型之前,你需要选择一个先验分布。
-
贝叶斯咒语是:“后验是先验乘以似然。”这是贝叶斯定理的一个结果。
-
与固有的随机数据不确定性相反,通过扩展训练数据,你可以减少认知模型参数的不确定性,从而得到一个方差更低的后验分布。
-
如果训练数据有限,贝叶斯模型比非贝叶斯变体显示出更好的预测性能。
-
对于那些对统计回归模型有一定经验的人来说,这可能会引起共鸣,他们可能会查看图 7.6 中的预测带。确实,当计算包含条件正态分布均值参数置信区间的预测区间时,你会得到一个类似的结果。对于不是太大太复杂的模型,这种模型参数的置信区间可以通过贝叶斯统计方法计算出来。但对于具有数百万参数的复杂非线性深度学*模型,这些非贝叶斯的不确定性度量需要重新采样方法以及多次重新拟合神经网络,这太耗时了,以至于不可行。
-
在这本书中,我们对数学符号的使用比较宽松。如果参数 θ 是一个连续量,我们应该称之为概率密度而不是概率,但在这里以及本书的其余部分,我们并没有这样做。
8 贝叶斯神经网络
本章涵盖了
-
适配贝叶斯神经网络(BNNs)的两种方法
-
贝叶斯神经网络(BNNs)的变分推断(VI)*似
-
贝叶斯神经网络(BNNs)的蒙特卡洛(MC)dropout *似
-
TensorFlow Probability(TFP)的变分层来构建基于 VI 的 BNNs
-
使用 Keras 在 BNNs 中实现 MC dropout

在本章中,你将了解两种高效的*似方法,这些方法允许你使用贝叶斯方法对概率深度学*模型进行建模:变分推断(VI)和蒙特卡洛 dropout(也称为 MC dropout)。在设置贝叶斯深度学*模型时,你将贝叶斯统计与深度学*相结合。(在本章开头的图中,你可以看到贝叶斯统计学的创始人托马斯·贝叶斯牧师和深度学*领袖及奠基人之一杰弗里·辛顿的合成肖像。)有了这些*似方法,用许多参数拟合贝叶斯深度学*模型变得可行。正如第七章所讨论的,贝叶斯方法还处理了非贝叶斯概率深度学*模型中未包含的认识论不确定性。
在第四章、第五章和第六章中,你使用深度概率分类和回归模型进行了工作。这些模型通过预测结果的整体分布来捕捉数据固有的(随机)不确定性。在第七章中,你学*了另一种称为认识论不确定性的额外不确定性,它捕捉了结果分布的参数不确定性。当使用深度学*模型在新情况下进行预测时(回想一下房间里的大象),这种认识论不确定性变得至关重要。如果你看不到大象,你至少希望知道至少有什么地方不对劲。
你也看到了上一章,适配贝叶斯概率模型让你可以量化参数不确定性,而且更重要的是,它提供了更好的预测(负对数似然或 NLL 更低),尤其是在训练数据较少的情况下。不幸的是,如果你从小的玩具例子转向现实世界的深度学*任务,贝叶斯方法很快就会变得缓慢,实际上变得不可能。
在本章中,你将学*一些*似方法,这些方法允许拟合概率深度学*模型的贝叶斯变体。这为你提供了一个工具,可以检测预测结果分布是否不确定。贝叶斯深度学*模型的优势在于,它们可以通过表达更大的认识论不确定性来检测新情况,这导致结果不确定性更大。你会发现,贝叶斯深度学*回归模型在外推状态下报告更大的不确定性,而贝叶斯深度学*分类模型对新型类别提高不确定性标志,表明它们的预测不可靠。
8.1 贝叶斯神经网络(BNNs)
让我们将第七章中描述的贝叶斯方法应用于神经网络(NN)。图 8.1(左侧)展示了所有贝叶斯网络之母:贝叶斯线性回归。与标准概率线性回归相比,权重不是固定的,而是遵循一个分布 P(θ|D)。我们完全没有理由不能继续使用分布而不是单个权重来构建一个神经网络。图 8.1(右侧)也展示了一个这样的简单贝叶斯神经网络(BNN)。实际上,解决一个深度贝叶斯神经网络并不那么容易,但 TensorFlow Probability(TFP)为你提供了正确的工具。

图 8.1 通过一个没有隐藏层且只有一个输出节点的简单神经网络,展示了贝叶斯简单线性回归问题的图形表示。这提供了对结果期望值的估计(左侧)。在贝叶斯神经网络线性回归的变体中,分布代替了斜率(a)和截距(b)。这同样适用于深度网络,从而产生贝叶斯神经网络(BNN)。这样的网络的一个简单例子在右侧展示。
在第 7.3 节中,我们使用解析方法来解决一个简单的线性回归问题。这个解决方案仅当参数 σ x,描述数据的分布,不依赖于 x,且必须预先知道时才可行。这种解析方法不适用于具有隐藏层的贝叶斯神经网络,因为它们太复杂了。你可能想知道我们是否应该再次回到第七章(第 7.2 节)中提到的贝叶斯黑客方法。原则上,这种方法也适用于更深的贝叶斯神经网络。然而,速度仍然是问题:如果我们直接从具有两个参数的贝叶斯线性回归到具有 5000 万个权重的神经网络,这将花费太长时间。
为了理解这一点,回顾第七章中提到的暴力方法,我们评估了变量 a 在 nbins = 30 个值。我们还评估了变量 b 在 nbins = 30 个值。总共,我们在 nbins² = 900 种不同的组合中评估了变量 a 和 b。对于一个有 5000 万个参数的网络,暴力方法需要评估后验概率在 nbins⁵⁰ million 种不同的组合。让我们满足于只有 nbins = 10。那么我们就有 10^(50,000,00) 次评估。如果你能每秒进行 10 亿次评估,那也需要 10^(50,000,00) / 10⁹ = 10^(49,999,991) 秒。即使是对于一个小型网络,有 100 个权重,也需要 10¹⁰⁰ /10⁹ = 10⁹¹ 秒(见以下有趣的事实)。
有趣的事实 前往 www.wolframalpha.com/ 并在搜索框中输入 10⁹¹ seconds。解决方案:评估所有网格点需要 3.169 ⋅ 10⁸³ 年——大约是宇宙年龄的 10 亿倍!
对于贝叶斯神经网络,既不是解析方法也不是蛮力方法能解决问题。接下来是什么?有一个叫做马尔可夫链蒙特卡洛(MCMC,简称)的方法。这种方法比蛮力方法更有效地采样参数值。第一个 MCMC 算法是 20 世纪 50 年代和 70 年代开发的 Metropolis-Hastings 算法。世界上最大的技术专业组织,享有盛誉的 IEEE(电气和电子工程师协会),将这种方法列为 20 世纪科学和工程领域十大最具影响力的算法之一(见mng.bz/vxdp)。它具有这样的优势,即如果计算足够,它是精确的。它也适用于小问题,比如 10 到 100 个变量(或在我们的语言中是权重),但不适用于具有数百万个权重的更大网络,如深度学*模型。
我们能做些什么来获得一个合理的时间来获得归一化后验的*似?有两种方法:一种是变分推断(VI)贝叶斯;另一种是蒙特卡洛(MC)dropout。变分贝叶斯方法被整合到 TFP 中,提供了 Keras 层来进行 VI。MC dropout 是一种简单的方法,也可以在 Keras 中实现。
8.2 变分推断(VI)作为*似贝叶斯方法
在无法确定贝叶斯神经网络的分析解或使用 MCMC 方法的情况下,你需要使用技术来*似贝叶斯模型。在本节中,你将了解这种*似方法——变分推断(VI)。在第 8.2.1 节中,我们详细推导了 VI *似。在第 8.2.2 节中,我们使用 VI *似来处理一个简单的线性回归示例。因为我们有这个示例的分析解(见第 7.3.3 节),我们可以判断 VI *似的质量。
你可以使用 VI *似方法来处理各种深度学*模型。在第 8.5 节中,你用它来处理两个案例研究:一个用于回归问题,另一个用于分类问题。为了理解这种方法的基本思想,并能够将其与一些精确方法进行比较,我们在第七章的相同简单线性回归问题上展示了这种方法。鼓励你在阅读文本的同时跟随笔记本。
实践时间打开mng.bz/4A5R,在阅读文本的同时跟随笔记本。这个笔记本是关于贝叶斯方式的线性回归。它展示了解析方法、VI,以及如何使用 TFP。尝试理解笔记本中发生的事情。 |
|---|
深度学*中贝叶斯方法的核心理念是,在贝叶斯神经网络(BNNs)中,每个权重都被一个分布所替代。通常,这是一个相当复杂的分布,并且这些分布在不同权重之间不是独立的。VI 贝叶斯方法背后的想法是,通过一个简单的分布,称为变分分布,来*似复杂的权重后验分布。
通常人们使用高斯作为参数分布;当使用 TFP 时,这也默认使用。高斯变分分布由两个参数定义:均值和方差。网络不是学*单个权重值 w,而是必须学*权重分布的两个参数:高斯的均值w**[μ]和方差w**[σ](见图 8.2)。除了用于*似后验的变分分布的类型外,我们还需要定义一个先验分布。一个常见的选择是将标准正态分布N(0, 1)作为先验。

图 8.2 一个具有两个隐藏层的贝叶斯网络。这里的权重现在遵循一个分布,而不是固定的权重。
第 8.2.1 节和第 8.2.2 节内容较为复杂,但能为你提供一些关于 VI *似推导的见解。如果你对推导不感兴趣,可以跳过这些章节。在第 8.3 节中,你将学*如何使用 TFP 的 VI 方法实现。
8.2.1 查看 VI 的内部机制*
VI 自 2013 年底阿姆斯特丹大学的 Kingma 和 Welling 发明变分自动编码器以来,在深度学*(DL)中得到了应用。我们这里使用的 VI 实现(称为反向传播的贝叶斯算法)在 Google DeepMind 科学家 Blundell 及其同事的论文“神经网络中的权重不确定性”中提出(arxiv.org/abs/1505.05424)。TFP 有效地整合了这一方法,你稍后将会看到。但首先让我们理解 VI 原理的内部机制。在图 8.3 中,你可以看到该原理的草图。

图 8.3 变分推断(VI)的原理。左侧较大的区域描述了所有可能的分布空间,左上角的点代表后验P(θ[1] |D),对应于右侧面板中的虚线密度。在左侧面板中,内部区域描述了可能的变分分布q**[λ](θ[1])的空间。通过内部循环中的点表示的优化变分分布P(θ[1] |D),对应于右侧面板中显示的实线密度,其与后验的最小距离由虚线表示。
实际的标准化后验分布,P(θ|D),是无法访问的。原因是需要解决的积分,以确保后验分布是归一化的(参见第 7.3.2 节),是高维的。如第 8.1 节所述,我们无法使用暴力*似,因为它太慢了。此外,这样的高维积分也太复杂,无法解析求解。
为了理解图 8.3 中不同参数的意义,让我们假设一个深度贝叶斯神经网络。参数 θ 替代了非贝叶斯神经网络变体的权重。贝叶斯网络中的参数 θ 不是固定的,而是遵循一个分布。
图 8.3 的左侧面板显示了可能分布的抽象空间,左上角的点代表后验 P(θ|D)。我们不是直接确定后验,而是用一个简单、变分的分布,q**[λ](θ),如高斯分布(参见图 8.3 右侧面板中的钟形密度)来*似它。有无数个高斯分布,但它们只是所有可能分布的一个子集。(在图 8.3 的左侧面板中,它被标记为带有 q**[λ](θ) 标签的小区域。)VI 的工作是调整变分参数 λ,使得 q**[λ](θ[1]) 尽可能接*真实后验 P(θ|D)。图的右侧再次显示了这种情况,针对单个 θ 1。1 维后验分布被一个 1 维高斯变分分布*似。对于每个高斯分布,你有两个参数:λ = (μ , σ)。这些被称为变分参数。
你希望变分分布尽可能接*真实后验分布,或者用更数学的说法:最小化良好分布与真实分布之间的距离。你可以通过操作变分参数 λ = (μ , σ) 来调整良好分布的形状。
可能有必要回顾所有术语。表 8.1 给出了 VI 的重要术语。你应该了解所有术语,除了最后一行。我们在第 8.2.2 节中介绍了最后一行的参数 w。
表 8.1 VI 中使用的不同术语
| Term | In the simple example | Name | Remarks |
|---|---|---|---|
| θ | θ = (a, b) = (slope, intercept) | Parameters | θ(theta) isn’t fixed but follows a distribution.In the non-Bayes case, θ is fixed and is identical to the tunable parameters w of the network. |
| P(θ|D) | P(a|D) | Posterior distribution | Usually not tractable. |
| q**[λ](θ) | N(a ; μ**[a] , σ**[a])N(b ; μ**[b] , σ**[b]) | Variational approximation | Tractable functions, such as an independent Gaussian for each θ i. |
| λ | λ = (μ**[a] , σ**[a] , μ**[b] , σ**[b]) | Variational parameters | Parameters of the variational distribution that approximate the posterior distribution. |
| ω | w = (w[0] ,w[1] , w[2] , w[3])λ = (w[0] , sp(w[1]), w[2] , sp(w[3])) | 可调参数 | 贝叶斯网络中的优化参数。快捷方式 sp(w1) 是指 softplus 函数,以产生正参数,这是高斯标准差所需的。 |
但你可以使用什么度量来描述两个分布之间的相似性或差异?也许更重要的是,你如何测量到一个你不知道的后验分布的差异?嗯,Kullback-Leibler (KL) 散度是这两个问题的答案。你已经在第 4.2 节中遇到了 KL 散度。让我们使用它,并写出 P(θ|D) 和 q**[λ](θ) 之间的 KL 散度的公式。如果你记得第 4.2 节,KL 散度是不对称的。如果你幸运并且选择了正确的顺序,KL [q**[λ](θ)||P(θ|D)],未知的后验分布就会消失,你需要的表达式如下:
λ^* = argmin {KL [q**[λ](θ) || P(θ)] − E**[θ∼ qλ] ) [log(P(D|θ)]}
λ^* = argmin {KL [q**[λ](θ) || P(θ)] − E**[θ∼ qλ] ) [log(P(D|θ)]} 方程 8.1
如果你以不同的方式开始,并从 KL [P(θ|D)|| q**[λ](θ)] 开始,你将无法得到方程 8.1 的可用表达式。(在侧边栏中,你可以找到方程 8.1 的推导)。这个方程看起来比实际要可怕一些。让我们更仔细地看看方程 8.1 中的两个项。
优化方程的推导
让我们推导方程 8.1。这涉及到一点微积分,所以如果你不喜欢这个,可以自由跳过。另一方面,这可能会很有趣,所以让我们开始吧。
我们从变分*似 q**[λ](θ) 和真实后验 P(θ|D) 之间的 KL 散度开始。奇怪的是,我们不知道真实后验。但是,正如几行之后所显示的,如果我们计算 KL [q**[λ](θ)|| P(θ|D)] 而不是 KL [P(θ|D)|| q**[λ](θ)],真实后验就会消失,你只需要计算的是变分分布和已知先验分布之间的 KL 散度。
让我们写出 KL 散度的定义,并用 D 表示数据。如果你记不起如何写出两个函数 f 和 g 的 KL 散度,也许这个经验法则会帮到你:它是“向后单独向下”,意味着 KL [f(θ)||g(θ)] 中的第二个函数 g 将单独位于分母中。以下 KL 散度的定义不应该让你感到太惊讶:
KL [q**[λ](θ)|| P(θ|D)] = ∫ q**[λ](θ) log( q**[λ](θ) / P(θ|D) ) dθ
P(θ|D) 是第二个函数(“在后面”),因此它只在积分中单独出现一次(“单独”),并且它位于分母(“下面”)。(你也可以查阅 KL 散度的定义。)现在,有一些代数操作。请随意使用笔和纸跟随步骤。我们首先使用条件概率的定义,P(θ|D) = P(θ|D) / P(D):
KL [q**[λ](θ)|| P(θ|D)] = ∫ q**[λ](θ) log( q**[λ](θ) / (P(θ|D) / P(D) ) ) dθ
然后,我们使用对数运算规则 log(A ⋅ B) = log(A) + log(B) 和 log(B /A) = −log(A /B) 将积分分成两部分:
KL [q**[λ](θ)|| P(θ|D)] = ∫ q**[λ](θ) log P(D) dθ − ∫ q**[λ](θ) log( P(θ|D) / q**[λ](θ) ) dθ
因为 log P(D) 不依赖于 θ,我们可以将其放在积分之前:
KL [q**[λ](θ)|| P(θ|D)] = log P(D) ⋅ ∫ q**[λ](θ) dθ − ∫ q**[λ](θ) log( P(θ|D)/ q**[λ](θ) ) dθ
由于 q**[λ](θ) 是概率密度,对于所有概率密度,积分等于 1,因此我们有 ∫ q**[λ](θ) dθ = 1 :
KL [q**[λ](θ)|| P(θ|D)] = log P(D) − ∫ q**[λ](θ) log( P(θ|D) / q**[λ](θ) ) dθ
第一项不依赖于变分参数 λ;因此,你需要最小化的只是 −∫ q**[λ](θ) log( P(θ|D) / q**[λ](θ) ) dθ。因此,最优值 λ 是:
λ^* = argmin {− ∫ q**[λ](θ) log( P(θ|D)) / q**[λ](θ) ) dθ }
现在,让我们将其整理成方程 8.1 的形式,其中 P(θ|D) = P(D|θ) ⋅ P(θ)
λ^* = argmin{− ∫ q**[λ](θ) log(P(D|θ) ⋅ P(θ) / q**[λ](θ) ) dθ }
并且使用对数运算规则:
λ^* = argmin {∫ q**[λ](θ) log( q**[λ](θ) / P(θ) ) dθ − ∫ q**[λ](θ) ⋅ log P(D|θ) dθ }
第一项是变分分布与先验分布之间的 KL 散度的定义:KL [q**[λ](θ)|| P(θ)](记住“向下回溯”)。第二项是函数 log P(D|θ) 的期望的定义。所以最终我们有
λ^* = argmin {KL[q**[λ](θ)|| P(θ)] − E**[θ∼q[λ]] [log(P(D|θ)]}
并且完成了方程 8.1 表达式的推导。这并不难,对吧?
因为我们想最小化方程 8.1,第一个项需要尽可能小。它又是 KL 散度,但这次,它是变分*似q**[λ](θ)和先验P(θ)之间的。因为 KL 散度(某种程度上)是一个距离,这个项希望*似分布q**[λ](θ)尽可能接*先验分布P(θ)。在贝叶斯神经网络中,先验通常选择为零附*,所以这个项确保了分布q**[λ](θ)以小值为中心。因此,我们也称第一个项为正则化项。它倾向于以零为中心的θ分布。选择远离零的狭窄先验可能会导致性能不佳。
第二个项,E**[θ∼q[λ]] [log(P(D|θ)],是一个老朋友。它计算给定参数θ的对数(P(D|θ))的期望值。参数θ根据变分分布q**[λ](θ)分布,该分布由变分参数λ确定。但无论如何,E**[θ∼q[λ]] [log(P(D|θ)]是对对数(P(D|θ))的期望。你认识这个亲爱的朋友吗?好吧,也许,更仔细地看看。如果抽取次数趋于无穷大,期望就等于均值。但均值有时比期望更容易理解,所以我们选择均值,并从q**[λ]中抽取参数θ用于网络。
现在,看看图 8.3 的右侧。如果你从分布中抽取(例如,对于θ 1),你会得到θ 1 = 2。或者,在我们的线性回归例子中,你从q**[λ](a, b)中随机选择 a 和b。然后,给定θ = (a, b),你计算在数据 D 中观察到的对数概率。在假设数据像高斯分布围绕均值μ = a ⋅ x[1] + b且具有固定标准差σ的线性回归例子中,你会得到

哦,你好。欢迎回来,我亲爱的朋友对数似然!我一开始怎么没看到你呢!让我们把它确定下来。第二个项(包括减号),−E**[θ∼q[λ]] [log(P(D|θ)],是平均 NLL,我们总是喜欢最小化它。平均是在θ的不同值上进行的,这些值是从q**[λ](θ)中抽取的。
总结一下,在方程 8.1 中,我们希望根据θ的概率平均的 NLL 最小,同时限制参数θ的变分分布不要离先验P(θ)太远。
8.2.2 将 VI 应用于玩具问题
恭喜你没有跳过到第 8.3 节!在这个部分我们还有一些数学内容需要消化。我们现在将变分推断(VI)方法应用于我们的玩具问题,即贝叶斯回归。你已经在第 7.2.1 节(通过黑客方法和暴力解决)和第 7.3.3 节(通过解析解决)中看到了贝叶斯回归的应用。作为提醒,简单线性回归的概率模型贝叶斯变体为 P(y|x, (a, b)) = N(y ; μ = a ⋅ x + b , σ = 3)。我们假设,和之前一样,标准差 σ ,它捕捉了随机不确定性,是已知的。不要纠结于为什么我们设 σ = 3;我们只是选择它以便图表看起来更美观。
在贝叶斯变体中,我们首先需要为两个模型参数(θ = (a, b))定义先验分布,其中斜率 a 和截距 b。和之前一样,我们选择正态分布 N(0, 1)。然后,我们定义变分分布 q**[λ](θ),该分布被调整以*似后验 P(θ|D)。原则上,变分分布可能是一个复杂对象,但在这里我们保持简单,选择两个独立的正态分布。
斜率参数 a (a ∼ N(μ**[a] , σ**[a])) 从第一个高斯分布中抽取,参数 b (b ∼ N(μ**[b] , σ**[b])) 从第二个高斯分布中抽取。这使我们剩下四个变分参数,λ = (μ**[a] , σ**[a] , μ**[b] , σ**[b]),我们通过优化来确定这些参数。我们使用随机梯度下降法来优化向量 w = (μ**[a] , w[1] , μ**[b] , w[3])。尺度参数 σ a 和 σ b 需要是正数,我们不希望限制 w1 和 w3 的值。因此,我们像在第 5.3.2 节中做的那样,将 w1 和 w3 通过 softplus 函数传递。在列表 8.1 中,你可以看到相应的代码:sigma_a = tf.math.softplus(w[1]) 和 sigma_b = tf.math.softplus(w[3]) 。
图 8.4 显示了网络。它是一个小网络,因为我们想将它与第 7.2.1 节中的暴力方法进行比较。

图 8.4 简单线性回归模型。左边是一个非贝叶斯神经网络,右边是一个具有变分推断(VI)*似的贝叶斯模型。在 VI 模型中,权重 a 和 b 被高斯分布所取代,这些分布由变分参数 λ = (μ**[a] , σ**[a] , μ**[b] , σ**[b])参数化。
任务是调整变分参数以最小化方程 8.1。我们通过梯度下降法确定参数 w = (μ**[a] , w[1] , μ**[b] , w[3])。但在我们开始编码并最小化方程 8.1 之前,让我们更仔细地看看这个方程中的损失函数,以便更好地理解最小化过程中的情况:
loss[VI] = loss[KL] + loss[NLL] = KL [q**[λ](θ)|| P(θ)] − E[θ][∼]**[qλ] [log(P(D|θ)] 方程 8.2
因为我们在变分*似q**[λ](a)和q**[λ](b)以及先验中使用了标准正态N(0, 1)高斯,所以变分高斯N(μ , σ)和先验N(0, 1)之间的 KL 散度可以解析地计算。(我们省略了推导过程,因为它相当繁琐,而且没有增加太多洞察。)从这一点,我们得到
损失[KL] = KL [q**[λ](w)||P(w)] = KL [N(μ ,σ)||N(0, 1) = −1/2(1 + log(σ²) − μ² − σ²) 方程式 8.3
不相信这个?看看 VI 部分之后的笔记本,你可以在那里通过数值验证这一点。
对于方程式 8.2 中的第二个损失[NLL]项,我们需要计算 NLL 的期望值:E[θ][∼]**[qλ] [log(P(D|θ)]。这次我们不太幸运。这个项不能以封闭形式计算。因此,我们通过平均不同的θ的-log(P(D|θ)来*似期望值,这些θ可以从θ ∼ q**[λ]中采样。但需要多少个θ样本呢?好吧,结果证明,通常一个样本就足够了(我们稍后会回到这个点)。
为了更好地理解,让我们看看列表 8.1 中的代码,并想象一下通过图 8.4 所示的 NN 进行的前向传递,包括在训练集中评估损失。让我们从一个包含四个固定值的向量 w 开始:w = (μ**[a] , w[1] , μ**[b] , w[3])。这些值通过λ = (μ**[a] , sp(w[1]), μ**[b] , sp(w[3]))控制变分参数(参见列表 8.1 中sigma_a、mu_a、sigma_sig和mu_sig的计算),其中 sp 是 softplus。变分分布N(μ**[a] , σ**[a])和N(μ**[b] , σ**[b])现在是固定的,我们可以通过方程式 8.3 计算正则化损失成分(参见方程式 8.2 中的第一个成分)。作为一个起点,我们为所有四个参数选择 0.1,得到loss_kl = -0.5(参见列表 8.1)。接下来,我们计算损失函数的 NLL 部分:

其中 N(y ; μ , σ) 是正态分布的密度函数。为了*似这个 NLL 项,我们对 a、b 采样一个值。使用这个样本,我们现在固定在一个非贝叶斯 NN 中,你可以像往常一样计算 NLL(参见列表 8.1)。首先,你选择适当的 TFP 分布来表示结果:
y_prob = tfd.Normal(loc = *x* · a + *b*, scale = sigma)
然后,有了正确的分布,你通过以下方式计算 NLL,即通过求和所有训练示例:
loss_nll = -tf.reduce_sum(*y*_prob.log_prob(*y*tensor))
我们将两个损失成分(loss_kl和loss_nll)相加以得到最终的损失。我们完成了吗?几乎。我们有 TensorFlow 的力量,可以计算损失相对于(w.r.t.) w = (μ**[a] , w[1] , μ**[b] , w[3])的导数,并更新这些值。但现实很残酷,存在一个微妙的问题。
假设我们想要计算损失关于权重 μ a = w[0] 的导数,这给出了斜率 a ~ N(μ**[a] , σ**[a]) 分布的均值。在图 8.5 的左侧,你可以看到从其变分分布 N(μ**[a] , σ**[a]) 中采样斜率参数 a 的相关计算图的部分。参数 b 的图部分类似。你还记得从第三章中,你必须计算输出 w 关于输入的局部梯度。很简单。只需计算 N(μ**[a] , σ**[a]) 的密度关于 μ**[a] 的导数。等等--a 是从高斯分布中采样的。我们如何通过采样变量来计算导数?这是不可能的,因为 a 的值是随机的,我们不知道在哪个位置取正态密度的导数。

图 8.5 重参数化技巧。因为反向传播(此处显示为 ∂ /∂μ**[a])不能通过随机变量如 a ~ N(μ**[a] , σ**[a])(左图)进行,所以我们使用重参数化技巧(右图)。我们不是从 a ~ N(μ**[a] , σ**[a]) 中采样 a,而是计算 a = μ**[a] + σ**[a] ⋅ ϵ,其中 ϵ 是从标准正态分布采样的,即 ϵ ∼ N(0, 1),它没有可调参数。最终,当使用重参数化技巧(右)时,a 再次根据 a ~ N(μ**[a] , σ**[a]) 正态分布,但我们可以反向传播以获得 ∂ /∂μ**[a] 和 ∂ /∂σ**[a],因为随机变量 ϵ ∼ N(0, 1) 不需要更新。
我们无法计算 a ~ N(μ**[a] , σ**[a]) 关于 σ**[a] 或 μ**[a] 的导数。一切都没了?在 2013 年,Kingma 和 Welling 找到了这个困境的解决方案,但许多人也独立地找到了这个解决方案。与其从 a ~ N(μ**[a] , σ**[a]) 中采样,你可以计算 a**[rep] = μ**[a] + σ**[a] ⋅ ϵ,然后采样 ϵ ∼ N(0, 1)。你可以在笔记本 mng.bz/4A5R 中检查 a**[rep] ∼ N(μ**[a] , σ**[a]) 或 μ**[a]。 (我们不需要关于 ε 的反向传播。)图 8.5 显示了右侧的重参数化。现在我们完成了,我们有一个可以计算梯度的有效解决方案。这个列表显示了完整的代码。
列表 8.1 使用变分推断 (VI) 对简单线性回归示例进行计算(完整代码)
w_0=(1.,1.,1.,1.) ❶
log = tf.math.log
w = tf.Variable(w_0)
e = tfd.Normal(loc=0., scale=1.) ❷
ytensor = *y*.reshape([len(*y*),1])
for i in range(epochs):
with tf.GradientTape() as tape:
mu_*a* = w[0] ❸
sig_*a* = tf.math.softplus(w[1]) ❹
mu_b = w[2] ❺
sig_b = tf.math.softplus(w[3]) ❻
l_kl = -0.5*(1.0 + ❼
log(sig_a**2) - sig_a**2 - mu_a**2 + ❼
1.0 + log(sig_b**2) - sig_b**2 - mu_b**2) ❼
*a* = mu_a + sig_a * e.sample() ❽
*b* = mu_b + sig_b * e.sample() ❾
y_prob = tfd.Normal(loc=x*a+b, scale=sigma)
l_nll = \
-tf.reduce_sum(*y*_prob.log_prob(*y*tensor)) ❿
loss = l_nll + l_kl
grads = tape.gradient(loss, w)
logger.log(i, i, w, grads, loss, loss_kl, loss_nll)
w = tf.Variable(w - lr*grads) ⓫
❶ 向量 w 的初始条件
❷ 噪声项,用于变分技巧
❸ 控制参数 a 的中心
❹ 控制参数 a 的分布范围
❺ 控制参数 b 的中心
❻ 控制参数 b 的分布范围
❽ KL 散度与高斯先验
❽ 使用重参数化技巧采样 a ~ N(mu_a, sigma_a)
❾ 样本 b ~ N(mu_b, sigma_b)
❿ 计算负对数似然 (NLL)
⓫ 梯度下降
在图 8.6 中,你可以看到参数 μ a 和 μ b 在通过变分推断方法*似贝叶斯模型训练过程中的收敛情况,这些值与我们没有使用*似方法解析得到的值非常接*(参见第 7.3.3 节)。

图 8.6 在几个周期中收敛到解析解的变分参数 μ a(上曲线)和 μ b(下曲线)
让我们回顾一下在简单回归示例中如何估计变分参数。估计过程最小化了为变分推断方法表达式(方程 8.2,此处再次展示)推导出的损失:
loss[VI] = loss[KL] + loss[NLL] = KL [q[λ](θ)|| P(θ*)] − *E[θ][∼][qλ]* [log(P(D|θ)] 方程 8.2 (重复D)
我们使用梯度下降来优化变分参数 λ = (μ**[a] , sp(w[1]), μ**[b] , sp(w[3])). 期望 E[θ][∼]**[qλ] [log(P(D|θ)] 可以通过不同 log(P(D|θ) 值的平均值来*似,每个值对应于从 qλ 中采样的不同 θ。原则上,必须从分布 qλ 中抽取无限多个不同的 θ 并取平均值,以完美地再现期望。这是大数定律。然而,在实际意义上,只进行一次 θ 的抽样;然后,将得到的 log(P(D|θ) 作为 E[θ][∼]**[qλ] [log(P(D|θ)] 的*似。
在我们的示例中,我们绘制了一个单个实现 θ = (a, b)。然后我们取方程 8.1 关于变分参数(位置,μ a,μ b,以及分布 a ∼ N(μ**[a] , σ**[a]) 和 a ∼ N(μ**[b] , σ**[b]) 的尺度,σ**[a] ,σ**[b])的梯度。梯度没有指向最速下降的正确方向。只有无限多个梯度的平均值才能做到这一点。这个算法有意义吗?让我们看看单个抽样是否仍然有意义。

图 8.7 训练变分参数。训练一个贝叶斯神经网络意味着学* θ = (a, b) 的分布。最简单的贝叶斯神经网络(如图 8.4 所示)只有两个参数,a 和 b,对于这些参数,使用变分推断,假设高斯分布:a ∼ N(μ**[a] , σ**[a]), a ∼ N(μ**[b] , σ**[b])。在这里,位置参数 μ a 和 μ b 在几个训练周期中显示出来。点表示给定周期中变分参数( μ a, μ b)的当前值。还显示了负梯度(从点指向的线)。youtu.be/MC_5Ne3Dj6g 提供了一个动画版本。
虽然梯度是有噪声的,但它仍然找到了最小值。通过在许多迭代中求和来计算更精确的梯度是不必要的,而且会浪费计算资源。然而,方向会更准确,箭头(如图 8.7 和观看动画)的波动会更小,但一个粗糙且快速的估计也是可以接受的,并且计算得更快。用仅一次评估来替换期望的技巧也用于 DL 研究中的不同场合,如强化学*或变分自动编码器。
8.3 使用 TensorFlow Probability 进行变分推断
TFP 有几种方法来构建 VI BNNs,所以让我们首先看看在 TFP 中构建 VI 网络有多简单。有一个类,tfp.layers.DenseReparameterization,你可以像使用标准的 Keras 层一样使用它,一层接一层地堆叠来构建一个完全连接的贝叶斯网络。列表 8.2 显示了图 8.2 中所示网络的代码。你能猜出这个贝叶斯网络有多少个参数吗?请注意,偏差项没有变分分布,因此只有一个权重。
列表 8.2 设置具有三层结构的 VI 网络
model = tf.keras.Sequential([
tfp.layers.DenseReparameterization(1, input_shape=(None,1)),
tfp.layers.DenseReparameterization(2),
tfp.layers.DenseReparameterization(3)
])
在默认设置下,DenseReparameterization 将偏差作为一个固定参数而不是分布。因此,偏差(如图 8.4 中的从 1 到输出的边缘)只携带一个参数。从 x 到输出的节点使用高斯分布建模,需要两个参数(一个用于中心,一个用于尺度),因此第一层有三个参数需要学*。同样,第二层有两个偏差项和两个边缘,导致 2 + 2 ⋅ 2 = 6 个参数。最后,最后一层有三个偏差和六个边缘,导致 3 + 2 ⋅ 6 = 15 个参数。总共,这个网络有 24 个参数。采用贝叶斯方法大约需要比标准神经网络多两倍的参数数量。作为一个小练*,将代码复制到笔记本中,并命名为 model.summary()。
TFP 也非常灵活。让我们重新构建 7.3.3 节中的线性回归问题,其中我们将斜率 a 和截距替换为分布,并且假设 σ 是已知的。为了与 TFP 兼容,我们需要将模型转换为变分贝叶斯网络(见图 8.4)。为了完全定义我们的网络,我们还需要声明两个变量 a 和 b 的先验分布。我们选择正态分布 N(0,1)。
你可以在列表 8.3 中找到网络的代码。与 TFP 的默认设置相反,我们希望偏差也是一个分布。因此,我们需要通过在DenseReparameterization层的构造函数中设置bias_prior_fn=normal和bias_posterior_fn=normal来覆盖它。此外,TFP 层目前存在以下奇特之处(暂不称之为错误)。这个损失(如方程 8.1 所示)由通常的 NLL 和一个称为 KL 散度的附加项组成。通常,你使用整体训练数据的总和来取 KL 散度和 NLL(参见列表 8.3)。然而,在深度学*中,计算每个训练示例的平均 NLL(总和 NLL 除以训练示例的数量)是非常常见的。这是可以的。我们只需要损失函数的最小值,这不会因为除以一个常数而改变。(我们到目前为止的所有示例中都这样做过。)但现在,KL 散度也需要转换为我们训练示例的平均值,而 TFP 目前并没有这样做。深入挖掘 TFP 的文档,你会在DenseReparameterization(mng.bz/Qyd6)以及相应的卷积层(Convolution1DReparameterization、Convolution2DReparameterization和Convolution3DReparameterization)中找到一些有些神秘的声明:
在进行小批量随机优化时,确保将此损失缩放,以便每个 epoch 只应用一次(例如,如果 kl 是批中每个元素的损失总和,你应该将 kl / num_examples_per_epoch 传递给你的优化器)。
为了解决这个问题,需要将 KL 项也除以训练数据数量(num)。可以通过以下代码行来完成(下一个列表显示了这种修复):
kernel_divergence_fn=lambda q, p, _: tfp.distributions.kl_divergence(q, p) / (num · 1.0)
列表 8.3:从图 8.4 编码我们的简单网络
def NLL(*y*, distr): ❶
return -distr.log_prob(*y*)
def my_dist(mu): ❶
return tfd.Normal(loc=mu[:,0:1], scale=sigma)
kl = tfp.distributions.kl_divergence
divergence_fn=lambda q, p, _: kl(q, p) / (num * 1.0) ❷
model = tf.keras.Sequential([
tfp.layers.DenseReparameterization(1,
kernel_divergence_fn=divergence_fn,
bias_divergence_fn=divergence_fn, ❸
bias_prior_fn= \
tfp.layers.util.default_multivariate_normal_fn, ❸
bias_posterior_fn= \
tfp.layers.util.default_mean_field_normal_fn() ❸
),
tfp.layers.DistributionLambda(my_dist)
])
sgd = tf.keras.optimizers.SGD(lr=.005)
model.compile(loss=NLL, optimizer=sg*D*)
❶ 对于具有固定方差的高斯分布,通常的 NLL 损失
❷ 重缩放 KL 散度项(对 TFP 的一种错误修复)
❸ TFP 通常不假设偏差上有分布;我们在这里覆盖了这一点。
TFP 还提供了用于构建使用变分推断(VI)的卷积贝叶斯神经网络(BNN)的层。对于常规的 2D 卷积,这些层被称为Convolution2DReparameterization。还有 1D 和 3D 变体,分别命名为Convolution1DReparameterization和Convolution3DReparameterization。此外,对于密集和卷积 BNN,TFP 中还有一些特殊类和一些高级 VI 方法。最值得注意的是,DenseFlipout可以用作DenseReparameterization的替代品。DenseFlipout使用一种加快学*的技巧。翻转技巧也适用于卷积(例如,参见Convolution2DFlipout)。翻转方法在 Wen, P. Vicol 等人发表的论文中有详细描述,该论文可在arxiv.org/abs/1803.04386找到。在笔记本mng.bz/MdmQ中,你使用这些层来构建 CIFAR-10 数据集的 BNN(见图 8.9)。
8.4 MC dropout 作为一种*似贝叶斯方法
在第 8.3 节中,您首次了解了通过变分推断(VI)*似的一个贝叶斯神经网络(BNN)。VI 允许您通过学*每个权重的*似后验分布来拟合贝叶斯深度学*模型。在 TFP 中,默认使用高斯分布来*似后验。与它们的非贝叶斯版本相比,这些 BNN 具有两倍多的参数,因为每个权重都被一个由两个参数(均值和标准差)定义的高斯权重分布所取代。VI 让我们能够拟合一个具有大量参数的 BNN 是非常棒的。但如果从非贝叶斯神经网络到其贝叶斯变体时参数数量不会加倍那就更好了。幸运的是,使用一个名为 MC dropout 的简单方法就可以实现这一点。(MC 代表蒙特卡洛,暗示其中涉及一个随机过程,就像蒙特卡洛赌场一样。)2015 年,一名博士生 Yarin Gal 能够证明 dropout 方法与 VI 相似,允许我们*似 BNN。但在转向 dropout 作为贝叶斯方法之前,让我们看看 dropout 是如何被引入的。
8.4.1 训练过程中使用的经典 dropout
训练过程中的 dropout 被引入作为一种防止神经网络过拟合的简单方法。(这甚至是 Srivastava 等人于 2014 年介绍该方法时的论文标题。)它是如何工作的?在训练过程中进行 dropout 时,您将神经网络中随机选择的某些神经元设置为 0。您在每个更新运行中都这样做。因为您实际上丢弃了神经元,所以从丢弃的神经元起始的所有连接的权重同时被丢弃(见图 8.8)。

图 8.8 三个神经网络:a)显示了包含所有神经元的完整神经网络,b)和 c)显示了两种变薄的神经网络版本,其中一些神经元被丢弃。丢弃神经元等同于将这些神经元起始的所有连接设置为 0。
在 Keras 中,这可以通过在权重层之后添加一个 dropout 层并给 dropout 提供一个概率 p(在这里我们使用 p *来表示这是一个 MC drop 概率,它将权重设置为 0)作为参数来实现(见以下列表)。在训练过程中,dropout 通常仅在全连接层中使用。
列表 8.4 定义和训练带有 dropout 层的分类卷积神经网络
model = Sequential()
model.add(Convolution2D(16,kernel_size,padding='same',\
input_shape=input_shape))
model.add(Activation('relu'))
model.add(Convolution2D(16,kernel_size,padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=pool_size))
model.add(Convolution2D(32,kernel_size,padding='same'))
model.add(Activation('relu'))
model.add(Convolution2D(32,kernel_size,padding='same'))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=pool_size))
model.add(Flatten())
model.add(Dense(100))
model.add(Activation('relu'))
model.add(Dropout(0.5)) ❶
model.add(Dense(100))
model.add(Activation('relu'))
model.add(Dropout(0.5)) ❶
model.add(Dense(nb_classes))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy',optimizer='adam',\
metrics=['accuracy'])
❶ 以 0.5 的概率将前一层中每个神经元设置为 0 的 dropout 层
在我们更详细地讨论 dropout 方法之前,您可以通过以下笔记本轻松地让自己相信 dropout 成功地防止了过拟合。在笔记本中,您为具有 50,000 张图像和 10 个类别的 CIFAR-10 数据集开发了一个分类卷积神经网络(CNN)(见图 8.9)。

图 8.9 CIFAR-10 数据集中十个类别的示例图像
|
| 实践时间 打开 mng.bz/XP29 。这个笔记本使用(经典)dropout 来对抗在训练用于 CIFAR-10 分类深度学*模型时的过拟合。
-
检查训练数据的损失曲线是否在带和不带 dropout 的情况下有效
-
检查带和不带 dropout 的准确度
|
在笔记本 mng.bz/4A5R 中,你看到训练过程中的 dropout 可以防止过拟合,甚至可以提高准确度。为此练*,请看图 8.10,其中显示了笔记本的结果。
![8-10.png]
图 8.10 展示了在训练过程中使用和不使用 dropout 时在 CIFAR-10 上实现的成果。在测试时,权重是固定的(没有 MC dropout)。当使用 dropout 时,验证数据的准确度更高(左面板),并且验证损失与训练损失之间的距离要小得多(右面板)。这表明 dropout 可以防止过拟合。
让我们看看训练过程中的 dropout 是如何工作的。在每个训练循环中,你应用 dropout 并获得 NN 的另一个更薄版本(见图 8.10)。为什么 dropout 有助于防止过拟合?一个原因是你训练了许多更薄的 NN 版本,这些版本比完整神经网络有更少的参数。在每一步中,你只更新未被丢弃的权重。总的来说,你不仅训练了一个网络的单个版本,而且训练了一个共享权重的 NN 薄版本集合。另一个原因是使用 dropout 时学*的复杂特征更少。因为 dropout 迫使神经网络处理缺失信息,它产生了更稳健和独立的特征。
如何在测试时使用经过 dropout 训练的神经网络?很简单。你回到具有固定权重的完整神经网络。只有一个细节:你需要将学*到的权重值按 w^* = p^* ⋅ w 的方式降低权重。这种降低权重的做法是为了考虑到在训练过程中,每个神经元平均接收到的输入比完整神经网络少 p *。因此,连接比没有应用 dropout 时更强。在应用阶段,没有 dropout,因此你通过乘以 p * 来降低过强的权重。幸运的是,你不需要手动调整权重;Keras 会处理这个问题。当在测试时使用经过 dropout 训练的神经网络并应用预测命令对新输入进行操作时,Keras 会做正确的事情。
8.4.2 训练和测试时使用的 MC dropout
如你在 8.4.1 节中看到的,训练过程中的 dropout 可以轻易地增强预测性能。因此,它很快在深度学*社区中变得流行,并且仍然被广泛使用。但 dropout 还有更多可以提供的。在测试时开启 dropout,你可以将其用作贝叶斯神经网络!让我们看看这是如何工作的。
在贝叶斯神经网络(BNN)中,每个权重都被一个分布所代替。使用变分推断(VI)时,你使用了一个具有两个参数(均值和标准差)的高斯权重分布。当使用 dropout 时,你也有一个权重分布,但现在权重分布更简单,本质上只包含两个值:0 或 w(见图 8.11)。
dropout 概率 p 不是一个参数,而是在定义神经网络时固定的(例如,p^ = 0.3)。dropout 概率是一个调整参数:如果你使用p^* = 0.3 时没有得到好的结果,你可以尝试另一个 dropout 率。MC dropout 方法中权重分布的唯一参数是 w 的值(见图 8.11,详情请参阅 Yarin Gal 和 Zoubin Ghahramani 在arxiv.org/abs/1506.02157上发表的 MC dropout 论文)。学*参数 w 的过程与通常一样。你在训练时开启 dropout,并使用常规的 NLL 损失函数,通过调整权重来最小化它,这通常是通过随机梯度下降(SGD)来实现的。
作为附带说明,在 Gal 的 dropout 论文中,损失包括负对数似然(NLL)和一个额外的正则化项,该正则化项惩罚大的权重。我们将跳过这个正则化项,因为该领域的多数实践者都不需要它。在实践中,这根本不是必需的。在 Gal 的框架中,dropout 类似于 8.2 节中提到的从变分推断(VI)拟合贝叶斯神经网络(BNN)的方法,但这次使用的是图 8.11 中的分布,而不是 VI 方法中通常使用的高斯分布。通过更新 w 值,你实际上学*的是*似权重后验的权重分布(见图 8.11)。

图 8.11 使用 MC dropout 的(简化D)权重分布。在定义神经网络时,dropout 概率 p*是固定的。这个分布中唯一的参数是 w 的值。
你如何使用 dropout 训练的神经网络作为 BNN?对于所有贝叶斯模型,你通过在权重分布上取平均来获得预测分布(参见方程 7.6)。
p ( y | x**[test] , D ) = ∑[i] p ( y | x**[test] , w [i] ) ⋅ p ( w**[i] | D ) 方程式 8.4
要在 dropout 训练的神经网络中获得预测分布,你需要在测试时间也开启 dropout。然后,使用相同的输入,你可以做出几个预测,每个预测都来自神经网络的不同 dropout 变体。你以这种方式得到的预测将接*方程 8.4 中的预测分布,正如你将在下一行看到的那样。更精确地说,你对相同的输入 x**[test] 进行 T 次条件概率分布 (CPD) p ( y | x**[test] , w [i] ) 的预测。对于每个预测,你得到不同的 CPDs,(p ( y | x , w [i] )), 对应于一个采样的权重星座 wi(参见以下案例研究)。这被称为 MC dropout,因为对于每个预测,你通过另一个随机丢弃神经元的稀疏版本的前向传递来进行预测。然后,你可以将 dropout 预测组合成一个贝叶斯预测分布:

这是对方程 8.4 的经验*似。得到的预测分布捕捉了认知不确定性和随机不确定性。
要在 Keras 中使用 MC dropout,有两种选择。在第一种选择中,你只需通过在模型定义中将训练阶段设置为 true 来创建你的网络。更优雅的方法是使其可选择的,即在测试时间是否使用 dropout。下一个列表显示了如何在 Keras 中这样做(并参见笔记本 mng.bz/MdmQ)。
列表 8.5 获取 MC dropout 预测
import tensorflow.keras.backend as K
model_mc_pred = K.function([model_mc.input, K.learning_phase()],
[model_mc.output]) ❶
T= 5 ❷
for i in range(0,T):
print(model_mc_pred([x_train[0:1],0])[0]) ❸
for i in range(0,T):
print(model_mc_pred([x_train[0:1],1])[0]) ❹
❶ 定义一个函数,该函数使用模型输入和学*阶段作为输入,并返回模型输出。重要的是学*阶段。当设置为 0 时,所有权重都固定,并在测试时间通过 dropout p* 调整;当设置为 1 时,在测试时间使用 dropout。
❷ 定义 dropout 预测的数量
❸ 每个 T 次预测
❹ 对于相同输入的每个 T 次预测都是不同的,因为它们是从不同的稀疏神经网络版本中确定的,对应于不同的 dropout 运行。
8.5 案例研究
让我们回到我们最初的问题,这个问题引导我们研究贝叶斯神经网络。回想一下第 7.1 节中的例子,在那里我们因为这样的图像不是训练集的一部分,所以找不到房间里的大象。让我们看看贝叶斯建模是否可以帮助在遇到训练数据中不存在的新情况时表达适当的不确定性(参见图 8.12,我们在此处重复第七章中的内容)。

图 8.12 深度学*中的不良案例。在 ImageNet 数据上训练的高性能 VGG16 CNN 没有看到大象。五个最高排名的类别预测是 horse_cart、shopping_cart、palace、streetcar 和 gondola——大象没有被找到!在回归问题中,在无数据区域没有不确定性(参见右侧的 Extrapolation 以及图 4.18)。
8.5.1 外推回归案例研究
让我们先通过一个案例研究来处理回归任务(见图 8.12 的右侧),并比较这两种方法:第 8.4 节中的 VI 网络和上一节中的蒙特卡洛*似。我们还在例子中包含了一种非贝叶斯方法。你可以使用与第 4.3.2 节和第 4.3.3 节中相同的合成正弦数据来拟合一个具有灵活方差的非线性回归模型。要自己完成这项工作,请阅读以下笔记本。
|
| 实践时间 打开mng.bz/yyxp。在这个笔记本中,你研究贝叶斯神经网络在回归任务中为外推提供的优势。你使用合成数据来拟合不同的概率神经网络:
-
你拟合了一个非贝叶斯神经网络。
-
你拟合了两个贝叶斯神经网络,一个通过变分推断(VI),另一个通过 dropout。
-
你研究了神经网络表达的不确定性。
|
作为验证数据,你使用了 400 个例子,x的值在-10 到 30 之间,以 0.1 的步长间隔。这个范围不仅覆盖了训练数据的范围,你期望模型以低不确定性估计参数,而且还覆盖了训练数据范围之外的区域,你期望有更高的认知不确定性。以这种方式设置案例研究后,你可以检查模型是否能够在进入外推范围(小于-3 或大于 30)时表达增加的不确定性。不同的概率神经网络模型会产生相当不同的结果,正如你将看到的。
为了比较三种方法,让我们看看结果预测分布。你在第 7.3 节中看到,你可以通过方程 8.5 来计算贝叶斯模型的预测分布。(这与方程 7.6 中的公式相同,但我们通常在神经网络中用 w 代替θ。)
P(y | x**[test] , D) = ∫[θ] P(y | x**[test] , θ) ⋅ P(θ|D) dθ 方程 8.5
这个方程告诉我们需要对网络可能的总权重配置 w 进行平均。我们通过从网络的不同配置(w)中抽取样本 y ~ P(y | x**[test] , D) 来*似这个积分。注意,在采样过程中,你不需要关心后验权重 P(θ|D)。原因是你会自动更多地采样那些具有更高后验概率 P(θ|D) 的 w。
让我们更详细地看看如何确定给定输入 x**[i] 的 P(y|x**[i] , D) 的结果分布。为了研究结果分布,我们需要查看训练好的模型并从网络中抽取 T 个样本。你会发现三个不同的概率神经网络模型之间有一些差异。

图 8.13 展示了回归案例研究中使用的三个模型的采样过程草图。在最后一列(右边),选择网络的一条边,显示其分布。其余列显示了 T 次不同运行中不同网络的实现。在每次运行中,输入x**[i]是相同的,边的值根据它们的分布进行采样。在上行中,你看到的是非贝叶斯方法,其中所有实现的神经网络都是相同的。在中行,你看到的是 VI-贝叶斯方法,其中边的值是从高斯分布中采样的。在右下角,是 MC dropout 方法,其中边的值来自二元分布。对于动画版本,请参阅youtu.be/mQrUcUoT2k4(对于 VI);youtu.be/0-oyDeR9HrE(对于 MC dropout);以及youtu.be/FO5avm3XT4g(对于非贝叶斯)。
在非贝叶斯神经网络中,每个连接 c 都有一个固定的权重。为了与贝叶斯术语保持一致,我们称这个值为θ c。我们希望用一个概率分布来表示这个值,因此我们赋予固定权重θ c 的概率为 1(见图 8.13 左上角的图)。从这些概率分布(每个连接有一个分布)中采样总是得到相同的神经网络(见图 8.13 的第一行,第一列到第三列)。因此,对于每个 T 次运行,对于相同的输入x,神经网络得到相同的参数对于高斯N(y ; μ**[x, w[t]] , σ**[x, w[t]]) = N(y ; μ**[x, w] , σ**[x, w])(见表 8.2)。为了得到经验结果分布,你可以从这个高斯分布中采样:y ∼ N(μ**[x, w] , σ**[x, w])。在表 8.2 中,因为μ**[x, w]和σ**[x, w]在所有行中都是相同的,所以在所有运行中,位置x处的预测结果分布P(y|x,w) = N(y ; μ**[x] , σ**[x])。在这种情况下,我们知道结果分布是N(y ; μ**[x] , σ**[x])。为了与贝叶斯方法做同样的事情,我们仍然每次运行采样一个值,因此对于每个x位置从结果分布中得到 T 个值(见图 8.14 上面板左边的图)。
表 8.2 展示了通过变分推断(VI)训练的贝叶斯神经网络在正弦回归任务中预测的条件概率分布(CPDs)。每一行对应 T 个预测中的一个,对于所有 400 个x值,都得到一个高斯 CPD N ( μ**[x] , σ**[x] )。N(μ x, σ x)可以从中采样。
| 预测编号 | x[1] = −10 | x[2] = −9.9 | . . . | x[400] = 30 |
|---|---|---|---|---|
| 1 | y ∼ N ( μ [x][1] ,[w][1] , σ [x][1] ,[w][1] | y ∼ N ( μ [x][2] ,[w][1] , σ [x][2] ,[w][1] | . . . | y ∼ N ( μ**[x][400] , [w][1] , σ**[x][400] , [w][1] ) |
| 2 | y ∼ N ( μ**[x][1] [w][2] , σ**[x][1] ,[w][2] ) | y ∼ N ( μ**[x][2] ,[w][2] , σ**[x][2] ,[w][2] ) | . . . | y ∼ N ( μ**[x][400] ,[w][2] , σ**[x][400] ,[w][2] ) |
| . . . | . . . | . . . | . . . | . . . |
| T | y ∼ N ( μ**[x][1] ,[w][T] , σ**[x][1] [w][T] ) | y ∼ N ( μ**[x][2] ,[w][T] , σ**[x][2] ,[w][T] ) | . . . | y ∼ N ( μ**[x][400] ,[w][T] , σ**[x][400] ,[w][T] ) |
贝叶斯变分推理神经网络将固定的权重 θ**[c] 替换为具有均值 μ**[c] 和标准差 σ**[c] 的高斯分布(见图 8.13 中间行,最右侧列)。在测试时间,你从这些权重分布中采样 T 次,总是得到连接的略微不同的值(见图 8.13 的第二行,第一到第三列,其中连接的粗细表示采样值是否大于权重分布的均值)。因此,神经网络在 T 次运行中产生略微不同的高斯 N( μ**[x] ,[w][t] , σ**[x] ,[w][t] )参数,我们将这些参数收集在表 8.2 的 T 行中。为了得到经验结果分布,你可以从所有这些确定的高斯分布中采样:y ∼ N ( μ**[x] ,[w][t] , σ**[x] ,[w][t] )。如果你每次运行采样一个值,你将在每个 x 位置得到 T 个结果值(见图 8.14,上面板中间图)。

图 8.14 预测分布。顶行中的实线显示了三个模型(经典神经网络、变分贝叶斯神经网络和 dropout 贝叶斯神经网络)的结果分布的五个样本。第二行显示了汇总统计:实线代表均值,上下虚线表示 95% 预测区间的上下边界。
贝叶斯蒙特卡洛 dropout 神经网络将每个固定的权重 θ c 替换为二进制分布(见图 8.13 最后一列的第三行)。在测试时间,你从这些权重分布中采样 T 次,总是得到连接的零或 wc 的值(见图 8.13 的第三行,第一到第三列)。为了得到经验结果分布,你可以从所有这些确定的高斯分布中采样:y ∼ N ( μ**[x] ,[w] , σ**[x] ,[w] )(见图 8.14,上面板右侧图)。
让我们使用 T 个预测的结果,首先探索所有三个模型的输出不确定性。图 8.14 显示了不同方法的结果。在图 8.14 的第一行中,对于表 8.2 中 T 行的前 5 行,画了一条线连接不同位置的不同的采样输出值 y。在第二行中,所有 T 个结果通过平均值(实线)和 95%预测区间(虚线)进行总结。下方的虚线对应于 2.5%分位数,上方的对应于 97.5%分位数。对于非贝叶斯方法,你总是从相同的高斯分布 N(μ x, σ x) 中采样。因此,原则上,你可以计算这个高斯分布的虚线为 y = μ**[x] ± 1.96 ⋅ σ**[x]。对于贝叶斯方法,情况并非如此,因为我们不知道输出分布的解析形式,因此必须从样本中采样并计算分位数。
总结一下,让我们最后看一下图 8.14 的最后一行,比较贝叶斯与非贝叶斯方法。中心线表示在给定数据的情况下,你的值 y 的均值位置。在所有方法中,它都遵循数据。虚线表示我们预期 95%数据所在的区域。在我们有训练数据的区域,所有方法都产生相似的结果。在数据分布也大的区域,CPD 的分布范围捕捉到的不确定性很大。因此,所有模型都能够模拟随机不确定性。当我们离开有数据的区域进入外推区域时,非贝叶斯方法失败了。它在一个不切实际很窄的区域假设了 95%的数据。总是一场灾难,真是太遗憾了!然而,贝叶斯方法知道它们不知道什么,并在离开已知领域时表达它们的不确定性。
8.5.2 带有新类别的分类案例研究
让我们重新审视房间里的大象问题,其中你面临一个分类任务。当将一个新的输入图像呈现给训练好的分类神经网络时,对于训练过程中看到的每个类别,你都会得到一个预测概率。为了预测大象图像(见图 8.12),我们在 7.1 节中使用了在 ImageNet 上训练的 VGG16 卷积神经网络。ImageNet 有 1,000 个类别,包括不同种类的大象。但 VGG16 网络无法在房间里找到大象,这意味着大象不在前五个类别中。一个合理的解释是,训练数据集中的大象图像从未包括房间里的大象。对于这样的图像(见图 8.12 的左侧面板),我们要求神经网络模型离开已知领域并进行外推。使用贝叶斯神经网络会有帮助吗?贝叶斯神经网络可能也不会看到大象,但它应该能够更好地表达其不确定性。不幸的是,你无法尝试,因为 ImageNet 数据集非常大,你需要在强大的 GPU 机器上花费几天时间来训练 VGG16 卷积神经网络的贝叶斯版本。
让我们在更小的规模上做,这样您就可以在笔记本中自己进行实验mng.bz/MdmQ。您可以使用只有 50,000 张图像和 10 个类别的 CIFAR-10 数据集(见图 8.15)。

图 8.15 CIFAR-10 数据集中十个类别的示例图像(与图 8.9 相同)
您可以使用 CIFAR-10 数据集的一部分来训练一个贝叶斯 CNN 并对其进行一些实验。但您如何设计一个实验来识别 NN 模型在离开已知领域时是否表达不确定性?让我们走向极端,看看当您向训练好的 CNN 提供不属于训练数据的类别的图像时会发生什么。为此,您可以在仅九个类别上训练一个 CNN;例如,不包含马这个类别(从训练数据中移除所有显示马的图像)。当向训练好的 NN 展示马图像时,它估计了它在训练时训练的类别的概率。所有这些类别都是错误的,但 NN 仍然不能将所有类别的概率分配为零,因为输出需要加起来等于一,这是通过 softmax 层强制执行的。您是否可以通过贝叶斯方法找出您是否可以信任基于预测分布的分类?您可以在下面的笔记本中自己进行实验。
|
| 实践时间 打开mng.bz/MdmQ。在这个具有新类别的分类案例研究中,您研究 BNN 在分类任务中可以提供哪些优势。您使用 CIFAR-10 数据集中 10 个类别中的 9 个类别的训练数据来拟合不同的概率性神经网络:
-
您拟合了一个非贝叶斯神经网络。
-
您拟合了两个贝叶斯神经网络(BNN),一个通过变分推断(VI)拟合,另一个通过 MC dropout 拟合。
-
您比较了使用不同神经网络所达到的性能。
-
您研究 NN 表达的不确定性。
-
您使用不确定性来检测新的类别。
|
在测试时间,一个传统的用于分类的概率性卷积神经网络(CNN)为每个输入图像提供一个多项式概率分布(MN 在方程中)。在我们的情况下,一个拟合了九个结果类别的多项式分布(见图 8.16)。k 个类别的概率为这个多项式分布提供了参数:MN(p[1] , p[2] ,⋯, p**[k]).

图 8.16 九个类别的多项式分布:MN( p[1] , p[2] , p[3] , p[4] , p[5] , p[6] , p[7] , p[8] , p[9] )
与回归情况一样,让我们首先看看三个不同的概率性神经网络模型的预测 CPD。在非贝叶斯神经网络中,您有固定的权重,对于一张输入图像,您得到一个多项式 CPD:CPD p ( y | x , w ) = MN( p[1] ( x , w ), ..., p[9] ( x , w )(见表 8.3)。如果您预测同一图像 T 次,您总是得到相同的结果。表 8.3 的每一行都是相同的。
表 8.3 使用 9 个类别中的 9 个类别训练的 CIFAR-10 分类任务的概率 CNN 的预测分布。每个预测是一个具有 9 个参数(类别 1、2、……、9 的概率)的多项式 CPD。
| predict_no | Image ξ[1] with known class | Image ξ[2] with unknown class |
|---|---|---|
| 1 | y ∼ MN(p1,..., p9) | y ∼ MN(p1,..., p9) |
| 2 | y ∼ MN(p1,..., p9) | y ∼ MN(p1,..., p9) |
| . . . | . . . | . . . |
| T | y ∼ MN(p1,..., p9) | y ∼ MN(p1,..., p9) |
在使用变分推断(VI)拟合的贝叶斯神经网络中,固定权重被替换为高斯分布。在测试时间,你通过预测相同的输入图像 T 次来从这些加权分布中进行采样。对于每次预测,你得到一个多项式 CPD:P(x, w**[t]) = MN(p1,..., p9). 每次预测图像时,你得到一个不同的 CPD (P(y|x, w**[t])),对应于采样的权重星座 wt。这意味着对应表 8.3 的所有行都是不同的。
在使用 MC dropout 拟合的贝叶斯神经网络中,你用二进制分布替换固定权重。从现在起,这就像 VI 的情况一样。对于每次预测,你得到一个多项式 CPD:P(x, w) = MN(p1,..., p9). 每次预测图像时,你得到一个不同的 CPD P(y|x, w**[t]),对应于采样的权重星座 wi。这意味着对应表 8.3 的所有行都是不同的。
总结和可视化分类模型中的不确定性
绘制表 8.3(多项式 CPD 的参数)中一行的预测概率,可以得到图 8.17 的第二、第三和第四行的图。随机不确定性在类别的分布中表示,如果一个类别得到一个概率为 1,则该分布为零。认知不确定性在某一类预测概率的分布中表示,如果分布为零,则认知不确定性为零。非贝叶斯神经网络无法表示认知不确定性(你不能为同一图像得到不同的预测),但贝叶斯神经网络可以。
首先,让我们看看两个已知类别的网络结果。在图 8.17 的左侧面板中,你可以看到所有网络正确地分类了飞机的图像。在两个贝叶斯神经网络中,你可以看到图中几乎没有变化,这表明分类相当确定。MC dropout 贝叶斯 CNN 比 VI CNN 显示出更多的不确定性,并且也分配了一些概率给鸟类类别。当查看图像时,这是可以理解的。
如果我们转向未知类别,图 8.17 中的所有预测都是错误的。但你可以看到,贝叶斯神经网络能更好地表达它们的确定性。当然,贝叶斯网络在其 T 次运行中也会预测一个错误的类别。但我们在每次 T 次运行中看到的是,分布变化很大。在比较 VI 和 MC dropout 方法时,再次显示 MC dropout 有更多的变化。此外,MC dropout 和 VI 预测的概率分布形状看起来也不同。在 VI 的情况下,分布看起来非常钟形。这可能是因为 VI 中使用的高斯权重分布。依赖于伯努利分布权重的 MC dropout 产生了更丰富的预测分布形状。现在让我们尝试使用 9 维预测分布来量化预测的不确定性(参见表 8.3 和图 8.17)。

图 8.17 上半部分:提供给训练 CNN 的图像包括来自已知类别飞机的图像(左)和来自未知类别马匹的图像(右)。第二行图:非贝叶斯神经网络产生的相应预测分布。第三行图:通过 VI 产生的贝叶斯神经网络的相应预测分布。第四行图:通过 MC dropout 产生的贝叶斯神经网络的相应预测分布。
非贝叶斯分类神经网络中的不确定性度量
在传统、非贝叶斯神经网络的情况下,对于一张图像,你得到一个 CPD(参见图 8.17 的第二行)。你将图像分类到概率最高的类别:p**[pred] = max( p**[k] )。CPD 仅表达随机不确定性,如果某个类别的概率为 1 而其他所有类别的概率为 0,则这种不确定性将为零。你可以使用 p**[pred] 作为确定性的度量,或者使用 −log( p**[pred] ) 作为不确定性的度量,这就是众所周知的 NLL:
NLL = −log( p**[pred] )
另一个常用于随机不确定性的度量(不仅使用预测类别的概率)是熵,这在第四章中你已经遇到过。在使用非贝叶斯神经网络时,没有认知不确定性。以下是公式:
熵:H = −∑⁹[k][=1] p**[k] ⋅ log( p**[k] )
贝叶斯分类神经网络中的不确定性度量
对于每张图像,你预测 T 个多项式 CPD:MN(p1,..., p9)(参见表 8.3 和图 8.17 的第三行和第四行)。对于每个类别 k,你可以确定平均概率 p^[k]* = 1/T ∑*^T[t=1]* p^[kt]*。你将图像分类到平均概率最高的类别 p^[pred]* = max( p^**[k]* )。
在文献中,关于如何最好地量化同时捕捉认知和随机贡献的不确定性没有共识;实际上,这仍然是一个开放的研究问题(至少如果你有超过两个类别)。请注意,这个平均概率已经捕捉到了一部分认知不确定性,因为它是由所有 T 个预测确定的。平均化导致概率向远离一或零的极端概率偏移。你可以使用 −log( p^**[pred]* ) 作为不确定性:
NLL^* = −log( p^**[pred]* )
基于平均概率值 p*k,在所有 T 次运行中平均得到的熵是一个更成熟的不确定性度量。但同样,你也可以使用多维概率分布的总方差(各个类别的方差的和)来量化不确定性:
-
熵: H^ = −∑⁹[k][=1] p^[k]* ⋅ log( p^[k]* )
-
总方差:V**[tot] = ∑⁹[k][=1] var( p**[k] ) = ∑⁹[k][=1] ∑^T**[t=1] ( p**[kt] − p^**[k]* )²
使用不确定性度量来过滤掉可能被错误分类的图像
让我们看看这些不确定性度量是否可以帮助我们提高预测性能。你在第七章的结尾看到,回归中的预测性能,通过测试 NLL 来衡量,对于贝叶斯神经网络(BNN)确实比非贝叶斯神经网络(non-BNN)要好,至少对于所研究的简单线性回归任务是这样。但现在,让我们转向分类。我们又可以再次查看测试 NLL,但在这里我们想要关注另一个问题。我们想要检查是否可以识别不确定的示例,并且如果从测试样本中移除这些示例可以增强准确率。想法是未知类别的图像应该特别显示出高不确定性。因此,如果我们能够过滤掉这些错误分类的图像,就可以提高准确率。
要检查我们是否可以识别被错误分类的图像,你可以进行一个过滤实验(参见mng.bz/MdmQ)。为此,你选择一个不确定性度量,然后根据这个度量对分类图像进行排序。你从具有最低不确定性的测试图像开始分类,并确定三个 CNN 变体所达到的准确率。然后,你按照不确定性的顺序添加图像(首先是具有最小不确定性的图像),并在添加每张图像后,再次确定所得到的准确率。你可以在图 8.18 中看到结果。在添加具有最高不确定性的图像后,考虑所有测试样本,达到以下准确率:非贝叶斯 CNN 为 58%,VI 贝叶斯 CNN 为 62%,MC dropout 贝叶斯 CNN 为 63%。这听起来相当糟糕,但请记住,所有测试样本中有 10%来自必须按定义导致错误分类的未知类别。

图 8.18 如果考虑越来越多的具有更高不确定性的图像,准确性会降低。每个图表中的最高准确性对应于只考虑最确定的测试图像时的准确性(100%准确性)。然后,随着不确定性的增加,添加图像。实线曲线对应于非贝叶斯 CNN,点线曲线对应于 MC dropout 贝叶斯 CNN,虚线曲线对应于 VI 贝叶斯 CNN。左侧列显示了考虑 5,000 个最确定图像时的曲线;右侧列显示了 10,000 个图像的整个测试数据集的曲线。在最后一行,只能使用贝叶斯 CNN 进行过滤,因为在非贝叶斯 CNN 中无法计算方差。
当将测试样本仅限制在已知类别时,非贝叶斯 CNN 的测试准确率为 65%,VI 贝叶斯 CNN 为 69%,MC dropout 贝叶斯 CNN 为 70%(见mng.bz/MdmQ)。
让我们回到问题:BNN 的不确定性是否更适合识别潜在的错误分类?答案是肯定的!您可以在图 8.18 中看到,非贝叶斯 CNN 的准确性可以通过转向贝叶斯变体来提高。此外,在 VI 和 MC dropout 贝叶斯 CNN 之间,您可以看到性能差异。MC dropout 明显优于 VI。这可能是因为 VI 与单峰高斯权重分布一起工作,而 MC dropout 与伯努利分布一起工作。您已经在图 8.17 中看到,VI 倾向于产生钟形且相当窄的预测分布,而 MC dropout 对于相同的示例产生更宽且不对称的分布,部分看起来几乎是双峰的(具有两个峰值的分布)。我们假设,在不久的将来,当可以轻松地在更复杂的分布而不是高斯分布中工作 VI 时,VI 将实现与 MC dropout 相同或更好的性能。从非贝叶斯到贝叶斯神经网络的切换很容易完成,无论您选择进行 VI 还是 MC dropout,都可以标记潜在的错误分类,并在回归和分类中实现更好的预测性能。
摘要
-
标准神经网络(NN)无法表达它们的不确定性。它们无法谈论房间里的大象。
-
贝叶斯神经网络(BNN)可以表达它们的不确定性。
-
贝叶斯神经网络(BNN)通常比它们的非贝叶斯变体表现更好。
-
与标准神经网络(NN)相比,贝叶斯神经网络(BNN)可以更好地识别新颖类别,因为它结合了认知和随机不确定性。
-
变分推断(VI)和蒙特卡洛 dropout(MC dropout)是*似方法,允许您拟合深度贝叶斯神经网络(BNN)。
-
TensorFlow Probability (TFP) 提供了通过变分推断(VI)拟合贝叶斯神经网络(BNN)的易于使用的层。
-
MC dropout 可用于 Keras 中拟合 BNN。
术语和缩写词表
| 缩写/术语 | 定义/含义 |
|---|---|
| 随机不确定性 | 无法进一步减少的数据固有的不确定性。例如,你无法预测硬币会落在哪一边。 |
| API | 应用程序编程接口。 |
| 贝叶斯咒语 | 后验与似然乘以先验成正比。 |
| BNN | 贝叶斯神经网络。一种其权重被分布替换的神经网络。通过变分推断(VI)或蒙特卡洛 dropout(MC dropout)来解决。 |
| 贝叶斯概率模型 | 可以通过描述分布的所有参数来表示其认知不确定性的概率模型。 |
| 贝叶斯统计学观点 | 在贝叶斯统计学的观点中,参数θ不是固定的,而是遵循一个分布。 |
| 贝叶斯定理 | P(A |
| 贝叶斯学* | P(θ |
| 反向传播 | 一种高效计算损失函数相对于神经网络权重梯度的方法。 |
| Bijector | TFP 包中用于正则化流(NF)所需的可逆(双射)函数。 |
| CIFAR-10 | 包含 60,000 个 10 类 32 × 32 彩色图像的流行基准数据集。 |
| CNN | 卷积神经网络。一种特别适合视觉应用的神经网络。 |
| 计算图 | 编码神经网络中所有计算的图。 |
| 缩写/术语 | 定义/含义 |
| CPD | 条件概率分布。我们也不严谨地称一个结果(例如,一个人的年龄)的密度P(y |
| 交叉熵 | 分类任务中负对数似然(NLL)的另一个名称。 |
| 确定性模型 | 一种非概率模型,它不返回结果分布,而只返回一个最佳猜测。 |
| Dropout | Dropout 指的是随机删除神经网络中的节点。训练期间的 Dropout 通常会产生表现出较少过拟合的神经网络。在测试时间(参见 MC dropout)期间执行 Dropout 也被解释为 BNN 的*似。 |
| DL | 深度学*。 |
| 外推法 | 超出模型训练数据范围。 |
| 认知不确定性 | 由模型参数的不确定性引起的模型不确定性。原则上,可以通过提供更多数据来减少这种不确定性。 |
| fcNN | 全连接神经网络。 |
| Glow | 基于 NF 的特定 CNN 网络,用于生成逼真的面部。 |
| ImageNet | 一个包含 1,000 个类别的 1 百万个标记图像的著名数据集。 |
| 雅可比矩阵 | 多维函数或多个变量变换的雅可比矩阵是其所有一阶偏导数的矩阵。 |
| 雅可比行列式 | 雅可比矩阵的行列式。它用于计算变换过程中的体积变化,并且对于 NF 是必需的。 |
| Keras | Keras 是一个高级神经网络 API,我们在本书中使用它配合 TensorFlow。 |
| KL 散度 | 一种衡量两个概率密度函数(PDFs)之间距离的度量。 |
| 似然 | 从由参数值θ指定的密度函数中采样的概率 P(D |
| 损失函数 | 一种量化模型不良程度的函数,以及在深度学*模型训练过程中优化的函数。 |
| MAE | 均绝对误差。MAE 是一种性能度量,它是残差的绝对值的平均值。它不足以量化概率模型(这里应使用 NLL 作为性能度量)。 |
| MaxLike | 最大似然。 |
| MaxLike 学* | 一种基于似然的方法,用于确定模型的参数值θ(例如,神经网络中的权重)。目标是最大化观察数据的似然 P(D |
| ML | 机器学*。 |
| MC dropout | 摩尔卡洛 dropout。这指的是测试时间中的 dropout。这是一种被解释为贝叶斯神经网络(BNN)*似的方法。 |
| MNIST | 更确切地说,是手写数字的 MNIST 数据库。一个包含 60,000 个 28×28 灰度 10 类(数字 0-9)的数据集。 |
| MSE | 均方误差。MSE 是一种性能度量,它是残差的平方的平均值。它不足以量化概率模型(这里应使用 NLL 作为性能度量)。 |
| NF | 归一化流。NF 是一种基于神经网络的拟合复杂概率分布的方法。 |
| NLL | 负对数似然。在拟合概率模型时用作损失函数。验证集上的 NLL 是量化概率模型预测性能的最佳度量。 |
| NN | 神经网络。 |
| 观察到的结果 | 对于某个实例 i 测量的观察到的结果或y[i]值。在概率模型中,我们旨在根据一些描述实例 i 的特征来预测y的条件概率分布。有时y[i]也被称为“真实”值。我们不赞成这种说法,因为在存在随机不确定性的情况下,没有真正的结果。 |
| 概率密度函数。PDF 有时也被称为概率密度分布。参见 CPD 以了解条件版本。 | |
| PixelCNN++ | 一种捕捉像素值概率分布的特定卷积神经网络模型。“++版本”使用高级条件概率分布(CPD)以提高性能。 |
| Posterior | 在看到数据 D 后参数 θ 的分布 P(θ |
| Posterior predictive distribution | 给定数据 D,由贝叶斯概率模型产生的 CPD P(y |
| Prediction interval | 预期包含所有数据中一定比例的区间,通常是 95%。 |
| Prior | 在看到任何数据 D 之前分配给模型参数 θ 的分布 P(θ )。 |
| Probabilistic model | 返回结果分布的模型。 |
| Residuals | 观测值 y**[i] 与确定性模型输出 yˆi(结果的期望值)之间的差异。 |
| RMSE | 均方根误差。均方误差的平方根。 |
| RealNVP | 一种称为真实非体积保持的特定 NF 模型。 |
| softmax | 一种强制神经网络输出总和为 1 的激活函数,它可以被解释为概率。 |
| softplus | 一种激活函数,应用后确保值为正。 |
| SGD | 随机梯度下降。 |
| Tensor | 多维数组。这是深度学*中的主要数据结构。 |
| TF | TensorFlow 是本书中用于深度学*的低级库。 |
| The big lie of DL | 假设 P(train) = P(test),意味着测试数据来自与训练数据相同的分布。在许多深度学*/机器学*应用中,这被假设但往往并不真实。 |
| TFP | TensorFlow Probability。一个用于促进深度学*概率建模的 TF 扩展。 |
| VGG16 | 一种具有特定架构的传统卷积神经网络,在 2014 年 ImageNet 竞赛中排名第二。它通常与在 ImageNet 数据上训练后从图像中提取特征的权重一起使用。 |
| VI | 变分推断。一种可以证明它产生*似贝叶斯神经网络的方法。 |
| w.r.t. | 缩写,意为“关于”。 |
| WaveNet | 一种用于文本到语音的特定神经网络模型。 |
| ZIP | 零膨胀泊松分布。一种针对计数数据且关注值 0 过多的特殊分布。 |






浙公网安备 33010602011771号