机器学习探索指南-全-

机器学习探索指南(全)

原文:Grokking Machine Learning

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

前言

你认为机器学习复杂且难以掌握吗?不是!读这本书吧!

路易斯·塞拉诺在用简单的英语解释事物方面是一位大师。我第一次遇见他是在他在 Udacity 教授机器学习时。他让我们的学生感觉到所有机器学习就像加减数字一样简单。最重要的是,他让材料变得有趣。他为 Udacity 制作的视频非常吸引人,并且仍然是该平台上最受欢迎的内容之一。

这本书更好!即使是最害怕的人也会喜欢书中呈现的材料,因为塞拉诺揭开了机器学习界一些最被保守的秘密。他一步一步地引导你了解该领域每个关键算法和技术。即使你不喜欢数学,你也可以成为机器学习的爱好者。塞拉诺最大限度地减少了我们这些硬核学者所喜爱的数学“kauderwelsch”,而是依靠直觉和实用的解释。

这本书的真正目标是赋予你掌握这些方法的能力。因此,书中充满了有趣的练习,让你有机会亲自尝试那些神秘(而现在已被揭秘)的技术。你更愿意沉迷于最新的 Netflix 电视剧,还是愿意花时间将机器学习应用于计算机视觉和自然语言理解的问题?如果是后者,这本书就是为你准备的。我无法表达用最新的机器学习技术玩耍是多么有趣,看到你的电脑在你的监督下施展魔法是多么令人兴奋。

由于机器学习是过去几年涌现的最热门的技术之一,你现在将能够利用你新获得的能力在工作中发挥作用。几年前,《纽约时报》宣称世界上只有 10,000 名机器学习专家,有数百万个空缺职位。这种情况至今仍然如此!通过这本书的学习,你将成为一名专业的机器学习工程师。你将保证拥有当今世界上最受欢迎的技能之一。

通过这本书,路易斯·塞拉诺出色地解释了复杂的算法,并使它们几乎对每个人来说都易于理解。但他并没有妥协深度。相反,他通过一系列启发性的项目和练习,专注于通过赋予读者能力。从这个意义上说,这不是一个被动的阅读。要充分利用这本书,你必须付出努力。在 Udacity,我们有一句话:你看别人锻炼是不会减肥的。要真正理解机器学习,你必须学会将其应用于现实世界的问题。如果你准备好了,这本书就是你的——无论你是谁!

西班牙语:Sebastian Thrun,博士

Udacity 创始人

斯坦福大学客座教授

前言

未来已经到来,而这个未来的名字就是机器学习。它在几乎每个行业中都有应用,从医学到银行,从自动驾驶汽车到订购我们的咖啡,对机器学习的兴趣每天都在迅速增长。但机器学习究竟是什么呢?

大多数时候,当我阅读机器学习书籍或参加机器学习讲座时,我看到的要么是一片复杂的公式海洋,要么是一片代码的海洋。长期以来,我以为这就是机器学习,而且机器学习似乎只属于那些对数学和计算机科学都有扎实知识的人。

然而,我开始将机器学习与其他学科进行比较,比如音乐。音乐理论和实践是复杂的学科。但当我们想到音乐时,我们不会想到乐谱和音阶;我们会想到歌曲和旋律。然后我开始思考,机器学习是否也是如此?它真的只是一堆公式和代码,还是它背后也有一个旋律?

图片

图 FM.1 音乐不仅仅是音阶和音符。在所有技术细节的背后,都有一个旋律。同样,机器学习不仅仅是公式和代码。也有一个旋律,在这本书中,我们就是唱出这个旋律。

带着这样的想法,我开始了理解机器学习旋律的旅程。我盯着公式和代码看了几个月。我画了许多图表。我在餐巾纸上乱涂乱画,并向我的家人、朋友和同事展示。我在大小不同的数据集上训练模型。我进行了实验。过了一段时间,我开始聆听机器学习的旋律。突然之间,一些非常漂亮的画面开始在脑海中形成。我开始编写与所有机器学习概念相符的故事。旋律、画面、故事——这就是我享受学习任何主题的方式,而这本书中分享的就是这些旋律、这些画面和这些故事。我的目标是让每个人都能完全理解机器学习,这本书就是这一旅程中的一步——一个我很高兴你能和我一起迈出的步伐!

致谢

首先和最重要的是,我要感谢我的编辑,玛琳娜·迈克尔斯,没有她这本书就不可能存在。她的组织、细致的编辑和宝贵的意见帮助塑造了《掌握机器学习》。我要感谢玛兰·巴塞、伯特·贝茨和曼宁团队的其他成员,感谢他们的支持、专业精神、好主意和耐心。我要感谢我的技术校对员,雪莉·亚普和卡尔滕·斯特罗贝克;我的技术发展编辑,克里斯·阿蒂;以及那些给予我宝贵反馈和纠正我许多错误的书评人。我要感谢生产编辑凯里·黑尔斯,校对员帕梅拉·亨特,图形编辑詹妮弗·休尔,校对员贾森·埃弗雷特,以及整个生产团队,感谢他们使这本书成为现实。我要感谢劳拉·蒙特亚亚,她在包容性语言和 AI 伦理方面给予了我帮助,感谢迭戈·赫南德斯,他为代码添加了宝贵的贡献,以及克里斯蒂安·皮克翁,他在仓库和软件包的技术方面给予了巨大的帮助。

我感谢塞巴斯蒂安·特鲁恩,他为民主化教育做出了卓越的工作。优达学城是我第一次有机会向全世界发声教授的地方,我要感谢我在那里遇到的优秀同事和学生。我要感谢亚历杭德罗·佩尔多莫和 Zapata Computing 团队,是他们带我进入了量子机器学习的世界。还要感谢我在谷歌和苹果遇到的许多优秀领导和同事,他们在我的职业生涯中发挥了关键作用。特别感谢罗伯托·奇帕尼和 Paper Inc.团队,让我成为他们大家庭的一员,并感谢他们在教育社区中所做的出色工作。

我要感谢我的许多学术导师,他们塑造了我的职业生涯和我的思维方式:玛丽·法尔克·德·洛萨达和她在哥伦比亚数学奥林匹克团队的工作,我在那里第一次爱上了数学,有机会遇到伟大的导师,并建立了持续一生的友谊;我的博士导师,谢尔盖·福明,他在我的数学教育和我的教学方法上发挥了关键作用;我的硕士导师,伊恩·古尔登;南特尔和弗朗索瓦·贝热龙、布鲁斯·萨甘和费德里科·阿尔迪拉,以及我有机会与之共事的许多教授和同事,特别是在滑铁卢大学、密歇根大学、蒙特利尔的魁北克大学和约克大学;最后,我要感谢理查德·霍西诺和 Quest 大学团队以及学生,他们帮助我测试和改进这本书中的材料。

向所有书评人致谢:阿尔·佩泽夫斯基、阿尔伯特·诺古斯·萨巴特、阿米特·兰巴、比尔·米切尔、博尔科·久尔科维奇、达尼埃莱·安德烈斯、埃里克·萨珀、郝刘、杰里米·R·洛斯切德、胡安·加布里埃尔·博诺、凯·恩格尔哈特、克日什托夫·卡米切克、马修·马戈利斯、马蒂亚斯·布施、迈克尔·布莱特、米拉德·达格多尼、波利娜·凯塞尔曼、托尼·霍尔德罗伊德和瓦莱丽·帕哈姆-汤普森,你们的建议帮助使这本书变得更好。

我要感谢我的妻子,卡罗琳娜·拉索,她在这一过程中的每一步都给予了我爱和关怀;我的母亲,塞西莉亚·埃雷拉,她用爱抚养我长大,并总是鼓励我追随我的激情;我的奶奶,玛鲁亚,她是我从天堂俯瞰我的天使;我的挚友,亚历杭德罗·莫拉莱斯,他总是支持我;以及那些照亮我人生道路、丰富我生活的朋友们,我衷心感谢你们,并用我全部的心爱你们。

YouTube、博客、播客和社交媒体给了我机会与世界各地的成千上万的杰出灵魂建立联系。好奇心旺盛、对学习充满无尽热情的人,慷慨分享知识和见解的同行教育工作者,组成一个每天激励我并给我继续教学和学习能量的电子部落。对于那些与世界分享知识或每天努力学习的人,我感谢你们。

我要感谢任何努力使这个世界变得更加公平和平静的人。对于那些为正义、和平、环境和地球上每个人的平等机会而奋斗的人,无论他们的种族、性别、出生地、条件还是选择,我由衷地感谢你们。

最后,但同样重要的是,本书献给你,读者。你选择了学习的道路,提升的道路,在不适中感到舒适的道路,这是值得赞扬的。我希望这本书是你追随激情、创造更美好世界道路上的一个积极步骤。

关于本书

本书教你两件事:机器学习模型及其使用方法。机器学习模型有多种类型。其中一些返回确定性答案,例如是或否,而另一些则返回概率答案。一些使用方程式;另一些使用条件语句。它们共同的一点是,它们都会返回一个答案或预测。包含返回预测的模型的机器学习分支被称为预测机器学习。这正是本书所关注的机器学习类型。

本书组织结构:路线图

章节类型

本书有两种类型的章节。其中大多数(第 3、5、6、8、9、10、11 和 12 章)每章包含一种机器学习模型。每章中对应的研究都相当详细,包括例子、公式、代码和练习题供你解决。其他章节(第 4、7 和 13 章)包含用于训练、评估和改进机器学习模型的有用技术。特别是,第十三章包含一个基于真实数据集的端到端示例,你将能够应用在前几章中获得的所有知识。

推荐学习路径

你可以使用两种方式来使用这本书。我推荐的方式是按顺序逐章阅读,因为你会发现学习模型和学习训练这些模型的技巧之间的交替是很有成效的。然而,另一种学习路径是首先学习所有模型(第 3、5、6、8、9、10、11 和 12 章),然后学习训练这些模型的技巧(第 4、7 和 13 章)。当然,因为我们都以不同的方式学习,你也可以创建自己的学习路径!

附录

这本书包含三个附录。附录 A 包含了每章练习的解答。附录 B 包含了一些有用的正式数学推导,但比本书的其他部分更技术性。附录 C 包含了一些建议的参考资料和资源,如果你想要进一步加深理解的话。

要求和学习目标

本书为你提供了一个预测机器学习的坚实基础框架。为了最大限度地利用本书,你应该有一个视觉思维和良好的基础数学理解,例如直线图、方程和基本概率。如果你知道如何编码,特别是 Python,这将很有帮助(尽管不是必需的),因为本书中你将有机会在真实数据集上实现和应用多个模型。阅读完本书后,你将能够做到以下几件事情:

  • 描述预测机器学习中最重要的模型及其工作原理,包括线性回归和逻辑回归、朴素贝叶斯、决策树、神经网络、支持向量机和集成方法。

  • 识别它们的优点和缺点以及它们使用的参数。

  • 识别这些模型在现实世界中的应用,并制定将机器学习应用于你想要解决的任何特定问题的潜在方法。

  • 学习如何优化这些模型,比较它们,并改进它们,以构建我们能够构建的最好的机器学习模型。

  • 编写模型代码,无论是手动编写还是使用现有的包,并使用它们在真实数据集上进行预测。

如果你有一个特定的数据集或问题在心中,我邀请你思考如何将本书中学到的知识应用到它上面,并以此作为起点来实施和实验你自己的模型。

我非常兴奋能够与你一起开始这段旅程,我希望你也会同样兴奋!

其他资源

本书是自包含的。这意味着除了前面描述的要求之外,我们需要的每个概念都在书中介绍。然而,我包含了许多参考文献,如果你想要更深入地理解这些概念或想要探索更广泛的话题,我建议你查阅这些参考文献。这些参考文献都在附录 C 中,也可以通过这个链接访问:serrano.academy/grokking-machine-learning

尤其是这本书的材料中包含了我的几项资源。在我的个人页面serrano.academy上,你可以找到很多以视频、帖子、代码等形式存在的材料。这些视频也发布在我的 YouTube 频道www.youtube.com/c/LuisSerrano上,我推荐你查看。实际上,这本书的大部分章节都有相应的视频,我建议你在阅读章节时观看。

我们将编写代码

在这本书中,我们将用 Python 编写代码。然而,如果你的计划是在不编写代码的情况下学习概念,你仍然可以忽略代码而跟随本书。不过,我建议你至少浏览一下代码,以便熟悉它。

这本书附带了一个代码仓库,大多数章节都会给你机会从头开始编写算法或使用一些非常流行的 Python 包来构建适合给定数据集的模型。GitHub 仓库是www.github.com/luisguiserrano/manning,我在书中链接了相应的笔记本。在仓库的 README 中,你可以找到成功运行代码所需的安装包说明。

我们在这本书中使用的主要 Python 包如下:

  • NumPy:用于存储数组并执行复杂的数学计算

  • Pandas:用于存储、操作和分析大型数据集

  • Matplotlib:用于绘图

  • Turi Create:用于存储、操作和训练机器学习模型

  • Scikit-Learn:用于训练机器学习模型

  • Keras (TensorFlow):用于训练神经网络

关于代码

这本书包含了许多与普通文本并行的源代码示例。在两种情况下,源代码都以固定宽度字体如这样this格式化,以将其与普通文本区分开来。有时代码也会加粗以突出显示与章节中先前步骤不同的代码,例如当新功能添加到现有代码行时。

在许多情况下,原始源代码已经被重新格式化;我们添加了换行并重新调整了缩进,以适应书中的可用页面空间。此外,当代码在文本中描述时,源代码中的注释通常也会从列表中移除。代码注释伴随着许多列表,突出显示重要概念。

这本书中的示例代码可以在 Manning 网站(www.manning.com/books/grokking-machine-learning)和 GitHub(www.github.com/luisguiserrano/manning)上下载。

liveBook 讨论论坛

购买《Grokking Machine Learning》包括免费访问由 Manning Publications 运营的私人网络论坛,您可以在论坛中就本书发表评论、提出技术问题,并从作者和其他用户那里获得帮助。要访问论坛,请访问 livebook.manning.com/#!/book/grokking-machine-learning/discussion。您还可以在 livebook.manning.com/#!/discussion 了解更多关于 Manning 的论坛和行为准则。

Manning 对读者的承诺是提供一个场所,让读者之间以及读者与作者之间可以进行有意义的对话。这并不是对作者参与特定数量活动的承诺,作者对论坛的贡献是自愿的(且未付费)。我们建议您尝试向作者提出一些挑战性的问题,以免他的兴趣转移!只要本书有售,论坛和先前讨论的存档将可通过出版社的网站访问。

关于作者

路易斯·G·塞拉诺是 Zapata Computing 量子人工智能领域的科研人员。他之前曾在谷歌担任机器学习工程师,在苹果公司担任首席人工智能教育者,并在 Udacity 担任人工智能和数据科学内容总监。路易斯在密歇根大学获得数学博士学位,在滑铁卢大学获得数学学士和硕士学位,并在加拿大魁北克省蒙特利尔大学的组合与数学信息实验室担任博士后研究员。路易斯维护了一个关于机器学习的流行 YouTube 频道,拥有超过 85,000 名订阅者,观看次数超过 400 万次,并且是人工智能和数据科学会议的常客演讲者。

1 什么是机器学习?它只是由计算机完成的常识

本章内容

  • 什么是机器学习

  • 机器学习难吗?(剧透:不难)

  • 我们在这本书中学到了什么

  • 人工智能是什么,它与机器学习有何不同

  • 人类是如何思考的,我们如何将这些想法注入到机器中

  • 一些现实生活中的基本机器学习实例

图片

我非常高兴能与你一起踏上学习之旅!

欢迎来到这本书!我非常高兴能与你一起加入理解机器学习的旅程。从高层次来看,机器学习是一个计算机以类似于人类的方式解决问题和做出决策的过程。

在这本书中,我想给你传达一个信息:机器学习很简单!你不需要有深厚的数学和编程背景就能理解它。你确实需要一些基本的数学知识,但主要成分是常识、良好的视觉直觉,以及将学习这些方法应用于你热爱的任何事物并希望在世界中做出改进的愿望。我写这本书时非常开心,因为我喜欢增长对这个主题的理解,我希望你在阅读它并深入研究机器学习时也能感到兴奋!

机器学习无处不在

机器学习无处不在。这句话似乎每天都在变得更加真实。我很难想象生活中有任何方面不能通过机器学习以某种方式得到改善。对于任何需要重复操作或查看数据并得出结论的工作,机器学习都能提供帮助。在过去的几年里,由于计算能力的提升和数据收集的普遍性,机器学习经历了巨大的增长。仅举几个机器学习的应用实例:推荐系统、图像识别、文本处理、自动驾驶汽车、垃圾邮件识别、医疗诊断……这个列表还在继续。也许你有一个目标或一个你想要产生影响(或者你可能已经做到了!)的领域!很可能,机器学习可以应用于那个领域——也许这正是你来到这本书的原因。让我们一起来看看吧!

我需要深厚的数学和编程背景来理解机器学习吗?

No. 机器学习需要想象力、创造力和视觉思维。机器学习是关于捕捉世界中出现模式,并使用这些模式来对未来进行预测。如果你喜欢寻找模式和发现相关性,那么你可以从事机器学习。如果我要告诉你我已经戒烟,并且开始多吃蔬菜和锻炼,你预测我的健康状况在一年后会发生什么?或许会变好。如果我要告诉你我已经从穿红色毛衣换成了绿色毛衣,你预测我的健康状况在一年后会发生什么?或许变化不会很大(可能会,但不是基于你提供的信息)。发现这些相关性和模式正是机器学习的内容。唯一的区别在于,在机器学习中,我们将公式和数字附加到这些模式上,以便让计算机发现它们。

进行机器学习需要一些数学和编码知识,但你不需要成为专家。如果你在任何一个领域或两个领域都是专家,你的技能肯定会得到回报。但如果你不是,你仍然可以学习机器学习,并在学习过程中掌握数学和编码。在这本书中,我们会在需要的时候介绍我们需要的所有数学概念。至于编码,你在机器学习中需要编写多少代码取决于你。机器学习的工作范围从整天编码的人到完全不编码的人都有。许多软件包、API 和工具帮助我们用最少的编码来进行机器学习。每天,机器学习对世界上每个人来说都更加容易获得,我很高兴你加入了这趟列车!

将公式和代码视为一种语言时,它们变得很有趣

在大多数机器学习书籍中,算法都是通过公式、导数等方式进行数学解释的。虽然这些方法的精确描述在实践中效果很好,但一个单独的公式可能比说明性更强。然而,就像音乐乐谱一样,一个公式可能隐藏在混乱背后的美丽旋律。例如,让我们看看这个公式:Σ[i]⁴[=1]i。乍一看,它看起来很丑,但它代表一个非常简单的求和,即 1 + 2 + 3 + 4。那么关于Σ[i]^n[=1]w[i]呢?这仅仅是许多(n)个数的总和。但当我想到许多数的总和时,我更愿意想象像 3 + 2 + 4 + 27 这样的东西,而不是 1 Σ[i]^n[=1]w[i]。每次我看到一个公式,我立刻就得想象一个它的小例子,然后我的脑海中画面就更加清晰了。当我看到像P(A|B)这样的东西时,我脑海中会想什么?那是一个条件概率,所以我想到一些类似“在另一个事件 B 已经发生的情况下,事件 A 发生的概率”的句子。例如,如果 A 代表今天的雨,B 代表居住在亚马逊雨林,那么公式P(A|B) = 0.8 就简单地意味着“在我们居住在亚马逊雨林的情况下,今天下雨的概率是 80%。”

如果你确实喜欢公式,不用担心——这本书中仍然有它们。但它们将出现在说明它们的示例之后。

同样的现象也发生在代码上。如果我们从远处看代码,它可能看起来很复杂,我们可能很难想象有人能把所有这些内容都装进脑子里。然而,代码只是一系列步骤,通常每个步骤都很简单。在这本书中,我们将编写代码,但我们会将其分解成简单的步骤,并且每个步骤都会通过示例或插图进行仔细解释。在前几章中,我们将从头开始编写我们的模型代码,以了解它们是如何工作的。然而,在后面的章节中,模型会变得更加复杂。对于这些,我们将使用 Scikit-Learn、Turi Create 或 Keras 等包,这些包已经以清晰和强大的方式实现了大多数机器学习算法。

好吧,那么机器学习究竟是什么呢?

要定义机器学习,首先让我们定义一个更广泛的概念:人工智能。

人工智能是什么?

人工智能(AI)是一个通用术语,我们将其定义为以下内容:

人工智能:计算机可以做出决策的所有任务的集合

在许多情况下,计算机通过模仿人类决策的方式做出这些决策。在其他情况下,它们可能模仿进化过程、遗传过程或物理过程。但总的来说,每次我们看到计算机通过自己解决问题时,无论是开车、在两点之间找到路线、诊断病人还是推荐电影,我们都在看到人工智能。

机器学习是什么?

机器学习类似于人工智能,并且它们的定义经常被混淆。机器学习(ML)是人工智能的一部分,我们将其定义为以下内容:

机器学习 是计算机可以基于数据做出决策的所有任务的集合

这是什么意思?让我用图 1.1 中的图表来解释。

图片

图 1.1 机器学习是人工智能的一部分。

让我们回顾一下人类是如何做决定的。一般来说,我们通过以下两种方式来做决定:

  • 通过使用逻辑和推理

  • 通过使用我们的经验

例如,想象一下我们正在尝试决定买什么车。我们可以仔细查看汽车的特征,如价格、油耗和导航,并试图找出最适合我们预算的最佳组合。这就是使用逻辑和推理。如果我们向所有朋友询问他们拥有的汽车以及他们喜欢和不喜欢的地方,我们就会形成一份信息列表,并使用这份列表来做决定,那么我们就是在使用经验(在这种情况下,我们的朋友的经历)。

机器学习代表了第二种方法:使用我们的经验来做决定。在计算机术语中,经验的术语是数据。因此,在机器学习中,计算机基于数据做决定。因此,每次我们让计算机仅使用数据解决问题或做决定时,我们就是在做机器学习。通俗地说,我们可以这样描述机器学习:

机器学习是常识,只是由计算机来完成。

从使用任何必要手段解决问题到仅使用数据解决问题,对于计算机来说可能感觉像是一小步,但对于人类来说却是一大步(图 1.2)。曾经,如果我们想让计算机执行一项任务,我们必须编写一个程序,即一系列计算机需要遵循的指令。这个过程对于简单任务来说是好的,但有些任务对于这个框架来说太复杂了。例如,考虑识别图像中是否包含苹果的任务。如果我们开始编写一个计算机程序来开发这个任务,我们很快就会发现这很困难。

图片

图 1.2 机器学习涵盖了所有计算机基于数据做出决策的任务。就像人类基于以往的经验做决定一样,计算机也可以基于以往的数据做决定。

让我们退一步,提出以下问题。作为人类,我们是如何学会苹果的外观的呢?我们学习大多数单词的方式并不是通过有人向我们解释它们的意思;我们是通过重复来学习的。在我们童年时期,我们看到了许多物体,成年人会告诉我们这些物体的名称。为了学习苹果是什么,我们在多年间看到了许多苹果,同时听到“苹果”这个词,直到有一天它突然明白了,我们就知道了苹果是什么。在机器学习中,这就是我们让计算机去做的事情。我们向计算机展示许多图像,并告诉它哪些图像包含苹果(这构成了我们的数据)。我们重复这个过程,直到计算机捕捉到构成苹果的正确模式和属性。在过程的最后,当我们向计算机提供一张新图像时,它可以使用这些模式来确定图像中是否包含苹果。当然,我们仍然需要编程计算机以便它能够捕捉到这些模式。为此,我们有几种技术,这些技术我们将在本书中学习。

现在我们已经进入这个话题了,那么什么是深度学习呢?

与机器学习是人工智能的一部分一样,深度学习是机器学习的一部分。在上一节中,我们了解到我们有几种技术用来让计算机从数据中学习。其中一种技术表现非常出色,因此它有一个自己的研究领域,称为“深度学习”(DL),我们如下定义,如图 1.3 所示:

深度学习 使用称为“神经网络”的某些对象的机器学习领域

神经网络是什么?我们将在第十章中学习它们。深度学习可以说是最常用的机器学习类型,因为它效果非常好。如果我们正在查看任何前沿应用,例如图像识别、文本生成、玩围棋或自动驾驶汽车,那么我们很可能以某种方式看到了深度学习。

图 1-3

图 1.3 深度学习是机器学习的一部分。

换句话说,深度学习是机器学习的一部分,而机器学习又是人工智能的一部分。如果这本书是关于交通的,那么 AI 就是车辆,ML 就是汽车,DL 就是法拉利。

我们如何让机器通过数据做出决策?记住-制定-预测框架

在上一节中,我们讨论了机器学习是由一系列技术组成的,我们使用这些技术让计算机根据数据做出决策。在本节中,我们将学习根据数据做出决策的含义以及一些这些技术是如何工作的。为此,让我们再次分析人类用来根据经验做出决策的过程。这就是所谓的“记住-制定-预测框架”,如图 1.4 所示。机器学习的目标是教会计算机以相同的方式、遵循相同的框架进行思考。

人类是如何思考的?

当我们作为人类需要根据我们的经验做出决定时,我们通常使用以下框架:

  1. 我们记住过去类似的情况。

  2. 我们制定一个一般规则。

  3. 我们使用这个规则来预测未来可能发生的事情。

例如,如果问题是,“今天会下雨吗?”,做出猜测的过程如下:

  1. 我们记住上周大部分时间都在下雨。

  2. 我们制定在这个地方,大部分时间都在下雨。

  3. 我们预测今天会下雨。

我们可能对或错,但至少我们是在尝试根据我们所拥有的信息做出尽可能准确的预测。

图片

图 1.4 记住-制定-预测框架是我们在这本书中使用的主体框架。它包括三个步骤:(1)我们记住以前的数据;(2)我们制定一个一般规则;(3)我们使用这个规则对未来做出预测。

一些机器学习术语——模型和算法

在我们深入探讨更多说明机器学习中使用的技术示例之前,让我们定义一些在这本书中经常使用的有用术语。我们知道在机器学习中,我们让计算机通过数据学习如何解决问题。计算机解决问题的方法是通过使用数据来构建一个模型。什么是模型?我们定义模型如下:

模型:一组规则,代表我们的数据,可以用来做出预测

我们可以将模型视为使用一组规则来模拟现实,尽可能接近地模仿现有数据。在上一个章节中关于下雨的例子中,模型是我们对现实的表示,这是一个大部分时间都在下雨的世界。这是一个只有一个规则的简单世界:大部分时间都在下雨。这种表示可能准确或不准确,但根据我们的数据,这是我们能够构建的现实的最准确表示。我们后来使用这个规则来对未见数据做出预测。

算法是我们用来构建模型的过程。在当前示例中,这个过程很简单:我们观察了多少天下雨,并意识到这是多数情况。当然,机器学习算法可能比这复杂得多,但最终,它们总是由一系列步骤组成。我们关于算法的定义如下:

算法:用于解决问题或执行计算的过程或一系列步骤。在这本书中,算法的目标是构建一个模型。

简而言之,模型是我们用来做出预测的工具,算法是我们用来构建模型的方法。这两个定义很容易混淆,并且经常被互换使用,但为了保持它们清晰,让我们看看一些例子。

人类使用的模型的一些例子

在本节中,我们关注机器学习的一个常见应用:垃圾邮件检测。在下面的例子中,我们将检测垃圾邮件和非垃圾邮件。非垃圾邮件也被称为ham

垃圾邮件和正常邮件垃圾邮件是用于垃圾邮件或不受欢迎的电子邮件的常用术语,例如连锁信件、促销等。这个术语来自 1972 年蒙提·派森的一个喜剧片段,其中餐厅菜单上的每一项都包含斯帕姆作为配料。在软件开发者中,术语正常邮件用于指非垃圾邮件。

示例 1:一个令人烦恼的电子邮件朋友

在这个例子中,我们的朋友鲍勃喜欢给我们发邮件。他发的大部分邮件都是垃圾邮件,形式为连锁信件。我们开始对他有点烦恼。现在是星期六,我们刚刚收到鲍勃发来的邮件通知。在不看邮件的情况下,我们能猜出这封邮件是垃圾邮件还是正常邮件吗?

为了找出这一点,我们使用记住-制定-预测的方法。首先,让我们记住,比如说,我们从鲍勃那里收到的最后 10 封电子邮件。这是我们数据。我们记住其中 6 封是垃圾邮件,其余 4 封是正常邮件。从这个信息中,我们可以制定以下模型:

模型 1:鲍勃发送给我们的每 10 封电子邮件中有 6 封是垃圾邮件。

这条规则将成为我们的模型。请注意,这条规则不一定需要是正确的。它可能是极其错误的。但鉴于我们的数据,这是我们所能想到的最好的,所以我们将接受它。在这本书的后面部分,我们将学习如何评估模型并在需要时改进它们。

现在我们有了我们的规则,我们可以用它来预测邮件是否是垃圾邮件。如果鲍勃的 10 封电子邮件中有 6 封是垃圾邮件,那么我们可以假设这封新邮件有 60%的可能性是垃圾邮件,40%的可能性是正常邮件。根据这个规则判断,认为这封邮件是垃圾邮件会更安全。因此,我们预测这封邮件是垃圾邮件(图 1.5)。

再次,我们的预测可能是错误的。我们打开邮件后可能会意识到它是一封正常邮件。但我们已经尽我们所能做出了预测。这正是机器学习的全部内容。

你可能会想,我们能做得更好吗?我们似乎是以相同的方式判断鲍勃的每一封电子邮件,但可能有更多的信息可以帮助我们区分垃圾邮件和正常邮件。让我们尝试更深入地分析电子邮件。例如,让我们看看鲍勃发送邮件的时间,看看是否能找到某种模式。

图片

图 1.5 一个非常简单的机器学习模型

示例 2:一个季节性的令人烦恼的电子邮件朋友

让我们更仔细地看看鲍勃上个月发送给我们的电子邮件。更具体地说,我们将查看他发送邮件的日期。以下是带有日期和垃圾邮件或正常邮件信息的电子邮件:

  • 星期一:正常邮件

  • 星期二:正常邮件

  • 星期六:垃圾邮件

  • 星期日:垃圾邮件

  • 星期日:垃圾邮件

  • 星期三:正常邮件

  • 星期五:正常邮件

  • 星期六:垃圾邮件

  • 星期二:正常邮件

  • 星期四:正常邮件

现在情况不同了。你能看到某种模式吗?似乎鲍勃在星期内发送的每一封邮件都是正常邮件,而在周末发送的每一封邮件都是垃圾邮件。这很有道理——也许在工作日他发送的是工作邮件,而在周末,他有时间发送垃圾邮件,并决定自由地漫游。因此,我们可以制定一个更明智的规则或模型,如下所示:

模型 2:鲍勃在工作日发送的每一封邮件都是正常邮件,而在周末发送的邮件都是垃圾邮件。

现在,让我们看看今天是星期几。如果今天是星期天,而我们刚刚收到了鲍勃的邮件,那么我们可以非常有信心地预测他发送的邮件是垃圾邮件(图 1.6)。我们做出这个预测,并且不看邮件就把它送到垃圾桶,继续我们的日子。

图 1.6 一个稍微复杂一些的机器学习模型

示例 3:事情变得越来越复杂了!

现在,假设我们继续使用这个规则,有一天我们在街上看到鲍勃,他问,“你为什么没来参加我的生日派对?”我们不知道他在说什么。结果上上周日他给我们发了一份生日派对的邀请,我们错过了!我们为什么错过了?因为他是在周末发的,我们假设它会是垃圾邮件。看起来我们需要一个更好的模型。让我们回到鲍勃的邮件上——这是我们记住的步骤。让我们看看我们是否能找到一种模式。

  • 1 KB:正常邮件

  • 2 KB:正常邮件

  • 16 KB:垃圾邮件

  • 20 KB:垃圾邮件

  • 18 KB:垃圾邮件

  • 3 KB:正常邮件

  • 5 KB:正常邮件

  • 25 KB:垃圾邮件

  • 1 KB:正常邮件

  • 3 KB:正常邮件

我们看到了什么?看起来大邮件往往是垃圾邮件,而小邮件往往是正常邮件。这很有道理,因为垃圾邮件通常有大的附件。

因此,我们可以制定以下规则:

模型 3:任何大小为 10 KB 或更大的邮件是垃圾邮件,任何小于 10 KB 的邮件是正常邮件。

现在我们已经制定了规则,我们可以做出预测。我们查看今天收到的鲍勃的邮件,大小是 19 KB。因此,我们得出结论,它是垃圾邮件(图 1.7)。

图 1.7 另一个稍微复杂一些的机器学习模型

这就是故事的结尾吗?远远不是。

但在我们继续之前,请注意,为了做出我们的预测,我们使用了星期几和邮件的大小。这些都是特征的例子。特征是本书中最重要概念之一。

特征 模型可以用来做出预测的数据的任何属性或特征

你可以想象,还有很多其他特征可以用来判断一封邮件是垃圾邮件还是正常邮件。你能想到更多吗?在接下来的段落中,我们将看到更多特征。

示例 4:更多?

我们的两种分类器都很好,因为它们排除了大邮件和周末发送的邮件。每一个都恰好使用这两个特征中的一个。但如果我们想要一个同时使用这两个特征的规则呢?以下规则可能有效:

模型 4:如果一封邮件的大小大于 10 KB 或者它是在周末发送的,那么它被分类为垃圾邮件。否则,它被分类为正常邮件。

模型 5:如果邮件是在工作日发送的,那么它的大小必须大于 15 KB 才能被分类为垃圾邮件。如果它是在周末发送的,那么它的大小必须大于 5 KB 才能被分类为垃圾邮件。否则,它被分类为正常邮件。

或者我们可以变得更复杂。

模型 6:考虑星期几的数字,其中星期一是 0,星期二是 1,星期三是 2,星期四是 3,星期五是 4,星期六是 5,星期天是 6。如果我们加上星期几和电子邮件的大小(以 KB 计),并且结果是 12 或更多,那么电子邮件会被归类为垃圾邮件(图 1.8)。否则,它会被归类为正常邮件。

图 1.8 一个更复杂的机器学习模型

所有这些都是有效的模型。我们可以通过增加复杂层或查看更多特征来创建更多和更多的模型。现在的问题是,哪个是最好的模型?这正是我们需要计算机帮助的地方。

机器使用的模型的一些例子

目标是让计算机以我们的思维方式思考,即使用记忆-制定-预测框架。简而言之,以下是计算机在每个步骤中执行的操作:

记住:查看一个巨大的数据表。

制定:通过许多规则和公式创建模型,并检查哪个模型最适合数据。

预测:使用模型对未来数据进行预测。

这个过程与我们之前章节中做的不太一样。这里的重大进步是,计算机可以通过遍历许多公式和规则的组合,快速构建模型,直到找到一个与现有数据很好地匹配的模型。例如,我们可以构建一个具有以下特征的垃圾邮件分类器:发件人、日期和时间、单词数量、拼写错误数量,以及诸如 buywin 这样的特定单词的出现。一个模型可能看起来像以下逻辑陈述:

模型 7

  • 如果邮件有两个或更多的拼写错误,那么它会被归类为垃圾邮件。

  • 如果它有一个大于 10 KB 的附件,它会被归类为垃圾邮件。

  • 如果发件人不在我们的联系名单中,它会被归类为垃圾邮件。

  • 如果它包含 buywin 这两个词,它会被归类为垃圾邮件。

  • 否则,它会被归类为正常邮件。

它也可能看起来像以下公式:

模型 8:如果 (大小) + 10 (拼写错误数量) – (“mom”一词的出现次数) + 4 (“buy”一词的出现次数) > 10,那么我们将消息归类为垃圾邮件(图 1.9)。否则,我们将其归类为正常邮件。

图 1.9 一个由计算机发现的更复杂的机器学习模型

现在的问题是,哪个是最好的规则?快速的答案是那个最适合数据的规则,尽管真正的答案是那个最好推广到新数据的规则。最终,我们

可能会得到一个复杂的规则,但计算机可以制定它并快速使用它进行预测。我们的下一个问题是,我们如何构建最好的模型?这正是本书的主题。

摘要

  • 机器学习很简单!任何人都可以学习并使用它,无论他们的背景如何。所需的一切就是学习的愿望和伟大的实施想法!

  • 机器学习非常有用,并且它被广泛应用于各个学科。从科学到技术,再到社会问题和医学,机器学习正在产生影响,并将继续这样做。

  • 机器学习是常识,由计算机完成。它模仿人类思考的方式,以便快速且准确地做出决策。

  • 就像人类根据经验做出决策一样,计算机可以根据之前的数据做出决策。这正是机器学习的核心所在。

机器学习使用记住-制定-预测框架,如下所示:

  • 记住:查看之前的数据。

  • 制定:基于这些数据构建一个模型或规则。

  • 预测:使用模型对未来数据进行预测。

机器学习的两种类型

在本章中

  • 三种不同的机器学习类型:监督学习、无监督学习和强化学习

  • 标签数据和未标记数据之间的区别

  • 回归和分类之间的区别以及它们的使用方法

正如我们在第一章中学到的,机器学习对计算机来说是常识。机器学习大致模仿了人类根据经验做出决策的过程,即基于以往的数据做出决策。自然地,编程计算机模仿人类思维过程是具有挑战性的,因为计算机被设计用来存储和处理数字,而不是做出决策。这正是机器学习旨在解决的问题。机器学习根据要做出的决策类型分为几个分支。在本章中,我们将概述这些分支中最重要的一些。

机器学习在许多领域都有应用,例如以下:

  • 根据房屋的大小、房间数量和位置预测房价

  • 根据昨天的股价和市场其他因素预测今天的股市价格

  • 根据电子邮件中的文字和发件人检测垃圾邮件和非垃圾邮件

  • 根据图像中的像素识别图像为面孔或动物

  • 处理长文本文档并输出摘要

  • 向用户推荐视频或电影(例如,在 YouTube 或 Netflix 上)

  • 构建与人类互动并回答问题的聊天机器人

  • 训练自动驾驶汽车自行在城市中导航

  • 诊断患者为生病或健康

  • 根据地理位置、购买力和兴趣将市场划分为相似的群体

  • 玩像象棋或围棋这样的游戏

尝试想象我们如何在每个这些领域中应用机器学习。注意,这些应用虽然不同,但可以用类似的方法解决。例如,预测房价和预测股价可以使用类似的技术。同样,预测一封电子邮件是否为垃圾邮件以及预测信用卡交易是否合法或欺诈也可以使用类似的技术。那么,根据用户的相似性对应用程序的用户进行分组呢?这听起来与预测房价不同,但可以类似地根据主题对报纸文章进行分组。那么下棋呢?这听起来与所有其他先前应用都不同,但可能像下围棋一样。

根据它们操作的方式,机器学习模型被分为不同的类型。机器学习模型的主要三个家族是

  • 监督学习

  • 无监督学习

  • 强化学习

在本章中,我们概述了所有三个概念。然而,在这本书中,我们只关注监督学习,因为它是最自然的学习起点,并且可以说是目前最常用的。查阅文献中的其他类型,并了解它们,因为它们都很有趣且很有用!在附录 C 的资源中,你可以找到一些有趣的链接,包括作者创建的几个视频。

标记数据和未标记数据有什么区别?

什么是数据?

我们在第一章中讨论了数据,但在我们进一步讨论之前,让我们首先明确我们在这本书中提到的数据的定义。数据仅仅是信息。每次我们有一个包含信息的表格时,我们就有数据。通常,我们表格中的每一行都是一个数据点。比如说,我们有一个宠物数据集。在这种情况下,每一行代表不同的宠物。表格中的每个宠物都由该宠物的某些特征来描述。

那么什么是特征?

在第一章中,我们将特征定义为数据的属性或特征。如果我们的数据在一个表格中,特征就是表格的列。在我们的宠物例子中,特征可能是大小、名字、类型或重量。特征甚至可以是宠物图像中像素的颜色。这就是描述我们的数据的内容。尽管如此,一些特征是特殊的,我们称它们为标签

标签?

这个问题稍微有点复杂,因为它取决于我们试图解决的问题的上下文。通常,如果我们试图根据其他特征预测一个特定的特征,那么这个特征就是标签。如果我们试图根据宠物的信息预测宠物的类型(例如,猫或狗),那么标签就是宠物的类型(猫/狗)。如果我们试图根据症状和其他信息预测宠物是否生病或健康,那么标签就是宠物的状态(生病/健康)。如果我们试图预测宠物的年龄,那么标签就是年龄(一个数字)。

预测

我们一直在使用自由预测的概念,但现在让我们将其明确下来。预测机器学习模型的目标是猜测数据中的标签。模型做出的猜测称为预测

既然我们已经知道了标签是什么,我们可以理解主要有两种类型的数据:标记数据和未标记数据。

标记和未标记数据

标记数据是带有标签的数据。未标记数据是没有任何标签的数据。标记数据的例子是一个带有记录邮件是否为垃圾邮件或工作相关的列的电子邮件数据集。未标记数据的例子是一个没有我们感兴趣预测的特定列的电子邮件数据集。

在图 2.1 中,我们看到包含宠物图像的三个数据集。第一个数据集有一列记录宠物的类型,第二个数据集有一列指定宠物的重量。这两个是标记数据的例子。第三个数据集仅包含图像,没有标签,因此是无标签数据。

图片

图 2.1 标记数据是带有标签或标记的数据。这个标签可以是类型或数字。无标签数据是没有标签的数据。左侧的数据集是标记的,标签是宠物的类型(狗/猫)。中间的数据集也是标记的,标签是宠物的重量(以磅为单位)。右侧的数据集是无标签的。

当然,这个定义包含一些歧义,因为根据问题,我们决定一个特定的特征是否可以作为标签。因此,确定数据是否标记或无标签,很多时候取决于我们试图解决的问题。

标记和无标签数据产生了两种不同的机器学习分支,称为监督学习无监督学习,这些将在接下来的三个部分中定义。

监督学习:与标记数据一起工作的机器学习分支

我们可以在当今一些最常见应用中找到监督学习,包括图像识别、各种形式的文本处理和推荐系统。监督学习是一种使用标记数据的机器学习类型。简而言之,监督学习模型的目标是预测(猜测)标签。

在图 2.1 的示例中,左侧的数据集包含狗和猫的图像,标签是“狗”和“猫”。对于这个数据集,机器学习模型将使用先前数据来预测新数据点的标签。这意味着,如果我们引入一个没有标签的新图像,模型将猜测该图像是狗还是猫,从而预测数据点的标签(图 2.2)。

图片

图 2.2 一个监督学习模型预测新数据点的标签。在这种情况下,数据点对应于狗,监督学习算法被训练来预测这个数据点确实对应于狗。

如果您还记得第一章中我们学习用于做决策的框架是“记住-制定-预测”。这正是监督学习的工作方式。模型首先记住狗和猫的数据集。然后它制定一个模型或规则,即它认为构成狗和猫的特征。最后,当一个新的图像进来时,模型对它认为的图像标签做出预测,即狗或猫(图 2.3)。

图片

图 2.3 一个监督学习模型遵循第一章中提到的“记住-制定-预测”框架。首先,它记住数据集。然后,它制定构成狗和猫的规则。最后,它预测新的数据点是否是狗或猫。

现在,请注意在图 2.1 中,我们有两种类型的标记数据集。在中间的数据集中,每个数据点都标记了动物的重量。在这个数据集中,标签是数字。在左侧的数据集中,每个数据点都标记了动物的类型(狗或猫)。在这个数据集中,标签是状态。数字和状态是我们将在监督学习模型中遇到的两种类型的数据。我们称第一种类型为数值数据,第二种类型为分类数据

数值数据是任何使用数字的数据类型,例如 4、2.35 或-199。数值数据的例子包括价格、大小或重量。

分类数据是任何使用类别或状态的类型的数据,例如男/女或猫/狗/鸟。对于这类数据,我们有一个有限集合的类别可以与每个数据点关联。

这导致了以下两种类型的监督学习模型:

回归模型是预测数值数据的模型类型。回归模型的输出是一个数字,例如动物的重量。

分类模型是预测分类数据的模型类型。分类模型的输出是一个类别,或一个状态,例如动物的类型(猫或狗)。

让我们看看两个监督学习模型的例子,一个是回归,一个是分类。

模型 1:房价模型 (回归)。在这个模型中,每个数据点是一栋房屋。每栋房屋的标签是它的价格。我们的目标是,当一栋新的房屋(数据点)上市时,我们希望预测其标签,即它的价格。

模型 2:电子邮件垃圾邮件检测模型 (分类)。在这个模型中,每个数据点是一封电子邮件。每封电子邮件的标签是垃圾邮件或非垃圾邮件。我们的目标是,当一封新的电子邮件(数据点)进入我们的收件箱时,我们希望预测其标签,即它是垃圾邮件还是非垃圾邮件。

注意模型 1 和模型 2 之间的区别。

  • 房价模型是一个可以从许多可能性中返回数字的模型,例如$100、$250,000 或$3,125,672.33。因此,它是一个回归模型。

  • 与此相反,垃圾邮件检测模型只能返回两件事:垃圾邮件或非垃圾邮件。因此,它是一个分类模型。

在接下来的小节中,我们将更详细地介绍回归和分类。

回归模型预测数字

正如我们之前讨论的,回归模型是我们想要预测的标签是一个数字的模型。这个数字是基于特征预测的。在房屋的例子中,特征可以是描述房屋的任何东西,例如大小、房间数量、距离最近学校的距离或社区的犯罪率。

其他可以使用回归模型的地方包括:

  • 股市:根据其他股票价格和其他市场信号预测某只股票的价格

  • 医学:根据患者的症状和病史预测患者的预期寿命或预期恢复时间

  • 销售:根据客户的人口统计信息和过去的购买行为预测客户预期的消费金额

  • 视频推荐:根据用户的人口统计信息和用户观看的其他视频预测用户预期观看视频的时间

用于回归的最常见方法是线性回归,它使用线性函数(直线或类似对象)根据特征进行预测。我们在第三章中学习了线性回归。其他用于回归的流行方法包括决策树回归,我们在第九章中学习,以及几种集成方法,如随机森林、AdaBoost、梯度提升树和 XGBoost,我们在第十二章中学习。

分类模型预测一个状态

分类模型是那些我们想要预测的标签属于有限状态集合的模型。最常见的分类模型预测“是”或“否”,但许多其他模型使用更大的状态集合。我们在图 2.3 中看到的例子是分类的一个例子,因为它预测宠物的类型,即“猫”或“狗”。

在电子邮件垃圾邮件识别的例子中,模型根据电子邮件的特征预测电子邮件的状态(即垃圾邮件或正常邮件)。在这种情况下,电子邮件的特征可以是邮件上的单词、拼写错误的数量、发件人或任何描述电子邮件的其他内容。

另一个常见的分类应用是图像识别。最流行的图像识别模型将图像中的像素作为输入,并输出对图像内容的预测。两个最著名的图像识别数据集是 MNIST 和 CIFAR-10。MNIST 包含大约 60,000 张 28×28 像素的黑白手写数字图像,这些图像被标记为 0 到 9。这些图像来自多个来源的组合,包括美国人口普查局和美国高中生手写数字的存储库。MNIST 数据集可以在以下链接找到:yann.lecun.com/exdb/mnist/ CIFAR-10 数据集包含 60,000 张 32×32 像素的不同事物的彩色图像。这些图像被标记为 10 个不同的对象(因此其名称中的 10),即飞机、汽车、鸟类、猫、鹿、狗、青蛙、马、船只和卡车。该数据库由加拿大高级研究研究所(CIFAR)维护,可以在以下链接找到:www.cs.toronto.edu/~kriz/cifar.html

分类模型的一些其他强大应用如下:

  • 情感分析:根据评论中的词语预测电影评论是正面还是负面

  • 网站流量:根据用户的 demographics 和他们与网站的过往互动预测用户是否会点击链接

  • 社交媒体:根据用户的 demographics、历史和共同朋友预测用户是否会成为朋友或与另一个用户互动

  • 视频推荐:根据用户的 demographics 和他们观看的其他视频预测用户是否会观看视频

本书的大部分内容(第 5、6、8、9、10、11 和 12 章)涵盖了分类模型。在这些章节中,我们学习了感知器(第五章)、逻辑分类器(第六章)、朴素贝叶斯算法(第八章)、决策树(第九章)、神经网络(第十章)、支持向量机(第十一章)和集成方法(第十二章)。

无监督学习:与无标签数据一起工作的机器学习分支

无监督学习也是一种常见的机器学习类型。它与监督学习的不同之处在于数据是无标签的。换句话说,机器学习模型的目标是从没有标签或预测目标的数据集中提取尽可能多的信息。

这样的数据集可能是什么,我们能用它做什么?原则上,我们可能比使用有标签的数据集能做的少一些,因为我们没有标签来预测。然而,我们仍然可以从无标签的数据集中提取大量信息。例如,让我们回到图 2.1 最右侧数据集上的猫和狗的例子。这个数据集由猫和狗的图像组成,但没有标签。因此,我们不知道每张图像代表哪种宠物类型,所以无法预测新图像是否对应狗或猫。然而,我们还可以做其他事情,例如确定两张图片是否相似或不同。这是无监督学习算法所做的事情。无监督学习算法可以根据相似性对图像进行分组,即使不知道每个组代表什么(图 2.4)。如果做得恰当,算法可以将狗图像与猫图像分开,甚至可以根据品种将它们各自分组!

图片

图 2.4 无监督学习算法仍然可以从数据中提取信息。例如,它可以把相似元素分组在一起。

事实上,即使标签存在,我们仍然可以在我们的数据上使用无监督学习技术进行预处理,并更有效地应用监督学习方法。

无监督学习的主要分支是聚类、维度约简和生成学习。

聚类算法 根据相似性将数据分组到簇中的算法

维度约简算法 简化我们的数据,并用更少的特征忠实描述它的算法

生成算法 可以生成与现有数据相似的新数据点的算法

在接下来的三个小节中,我们将更详细地研究这三个分支。

聚类算法将数据集分割成相似组

正如我们之前所述,聚类算法是将数据集分割成相似组的算法。为了说明这一点,让我们回到“监督学习”部分中的两个数据集——住房数据集和垃圾邮件数据集——但想象一下它们没有标签。这意味着住房数据集没有价格,而邮件数据集没有关于邮件是否为垃圾邮件或非垃圾邮件的信息。

让我们从住房数据集开始。我们能用这个数据集做什么?这里有一个想法:我们可以以某种方式根据相似性对房屋进行分组。例如,我们可以根据位置、价格、大小或这些因素的组合来分组。这个过程被称为聚类。聚类是无监督机器学习的一个分支,它包括将我们的数据集中的元素分组到簇中的任务,其中所有数据点都是相似的。

现在,让我们看看第二个例子,邮件数据集。因为数据集没有标签,我们不知道每封邮件是否为垃圾邮件。然而,我们仍然可以对我们数据集应用一些聚类。聚类算法根据邮件的不同特征将我们的图像分割成几个不同的组。这些特征可能是信息中的单词、发件人、附件的数量和大小,或者邮件内部的链接类型。聚类数据集后,一个人(或人与监督学习算法的组合)可以根据“个人”、“社交”和“促销”等类别对这些簇进行标记。

例如,让我们看看表 2.1 中的数据集,其中包含九封我们想要聚类的邮件。数据集的特征是邮件的大小和收件人数量。

表 2.1 包含大小和收件人数量的邮件表

邮件 大小 收件人
1 8 1
2 12 1
3 43 1
4 10 2
5 40 2
6 25 5
7 23 6
8 28 6
9 26 7

用肉眼来看,我们似乎可以根据收件人数量将邮件分组。这将导致形成两个簇:一个包含两个或更少收件人的邮件,另一个包含五个或更多收件人的邮件。我们也可以尝试根据大小将它们分成三个组。但你可以想象,随着表格越来越大,用眼睛识别组变得越来越困难。如果我们绘制数据呢?让我们在图表中绘制邮件,其中水平轴记录大小,垂直轴记录收件人数量。这给我们带来了图 2.5 中的图表。

图 2.5 邮件数据集的图表。水平轴对应邮件的大小,垂直轴对应收件人数量。我们可以在这个数据集中看到三个定义良好的簇。

在图 2.5 中,我们可以看到三个定义良好的簇,这些簇在图 2.6 中被突出显示。

图 2.6 我们可以根据大小和收件人数量将电子邮件聚类成三类。

最后这一步就是聚类的全部内容。当然,对于我们人类来说,一旦有了图表,很容易就能目测出三个组。但对于计算机来说,这项任务并不容易。此外,想象一下,如果我们的数据包含数百万个点,有数百或数千个特征。当特征超过三个时,人类无法看到簇,因为它们会处于我们无法可视化的维度。幸运的是,计算机可以为具有多行和多列的巨大数据集进行此类聚类。

聚类的一些其他应用如下:

  • 市场细分:根据人口统计和以往购买行为将客户分成组,为这些组创建不同的营销策略

  • 遗传学:根据基因相似性将物种聚类成组

  • 医学成像:将图像分割成不同的部分以研究不同类型的组织

  • 视频推荐:根据人口统计和以往观看的视频将用户分成组,并据此向用户推荐其组中其他用户观看的视频

更多关于无监督学习模型

在本书的其余部分,我们不涉及无监督学习。然而,我强烈建议您自学。以下是一些最重要的聚类算法。附录 C 列出了更多(包括一些我的视频)的算法,您可以在其中详细了解这些算法。

  • K-均值聚类:此算法通过选择一些随机质心并将它们移动到越来越接近点的位置来分组点,直到它们处于正确的位置。

  • 层次聚类:此算法首先将最近的点分组在一起,并继续这种方式,直到我们有一些定义良好的组。

  • 基于密度的空间聚类(DBSCAN):此算法从高密度区域开始将点分组在一起,同时将孤立点标记为噪声。

  • 高斯混合模型:此算法不会将一个点分配给一个簇,而是将点的分数分配给现有的每个簇。例如,如果有三个簇,A、B 和 C,那么算法可以确定一个特定点的 60%属于组 A,25%属于组 B,15%属于组 C。

维度降低简化数据而不丢失太多信息

维度降低是一个有用的预处理步骤,我们可以将其应用于大量简化我们的数据,然后再应用其他技术。例如,让我们回到住房数据集。想象一下,特征如下:

  • 尺寸

  • 卧室数量

  • 卫生间数量

  • 社区犯罪率

  • 到最近学校的距离

这个数据集有五个数据列。如果我们想将数据集转换成一个列更少、信息损失不多的简单数据集,会怎么样呢?让我们通过常识来做这件事。仔细看看这五个特征。你能看到任何简化它们的方法——也许是将它们分组到一些更小、更一般的类别中?

仔细观察后,我们可以发现前三个特征是相似的,因为它们都与房屋的大小有关。同样,第四和第五个特征彼此相似,因为它们与社区的素质有关。我们可以将前三个特征浓缩成一个大的“大小”特征,第四和第五个特征浓缩成一个大的“社区素质”特征。我们如何浓缩大小特征呢?我们可以忽略房间和卧室的数量,只考虑大小,我们可以加上卧室和浴室的数量,或者可能取这三个特征的某种组合。我们也可以以类似的方式浓缩区域素质特征。降维算法将找到好的方法来浓缩这些特征,尽可能减少信息损失,同时尽可能保持我们的数据完整,以便在简化数据的同时便于处理和存储(图 2.7)。

图 2.7

图 2.7 降维算法帮助我们简化数据。在左侧,我们有一个具有许多特征的房屋数据集。我们可以使用降维来减少数据集中的特征数量,同时损失的信息很少,并得到右侧的数据集。

如果我们只是在减少数据中的列数,为什么称之为降维?数据集中列数的术语是维度。想想看:如果我们的数据有一列,那么每个数据点就是一个数字。一组数字可以绘制成一条线上的点集,这条线恰好是一维的。如果我们的数据有两列,那么每个数据点由一对数字组成。我们可以想象一对数字的集合就像一个城市中的点集,第一个数字是街号,第二个数字是大道。地图上的地址是二维的,因为它们在平面上。当我们的数据有三列时会发生什么?在这种情况下,每个数据点由三个数字组成。我们可以想象,如果我们的城市中的每个地址都是一个建筑,那么前两个数字是街道和大道,第三个数字是建筑中的楼层。这看起来更像是一个三维城市。我们可以继续这样想。四个数字呢?好吧,现在我们真的无法可视化它,但如果我们可以,这个点集将看起来像四维城市中的地方,以此类推。想象四维城市最好的方式是通过想象一个有四列的表格。那么一百维城市呢?这将是一个有 100 列的表格,其中每个人都有一个由 100 个数字组成的地址。当我们思考高维时,我们可以在图 2.8 中看到这种心理图像。因此,当我们从五维降到二维时,我们将五维城市简化为二维城市。这就是为什么称之为降维。

图片

图 2.8 如何想象高维空间:一维就像一条街道,其中每栋房子只有一个号码。二维就像一个平面城市,其中每个地址有两个号码,一个街道和一个大道。三维就像一个有建筑的城市,其中每个地址有三个号码:一个街道,一个大道和一个楼层。四维就像一个有四个号码的想象中的地方。我们可以将高维想象为另一个想象中的城市,其中地址有我们需要的那么多坐标。

简化我们数据的其他方法:矩阵分解和奇异值分解

看起来聚类和降维完全不同,但实际上它们并不那么不同。如果我们有一个充满数据的数据表,每一行对应一个数据点,每一列对应一个特征。因此,我们可以使用聚类来减少数据集中的行数,使用降维来减少列数,如图 2.9 和 2.10 所示。

图片

图 2.9 通过将多个行分组为一个,聚类可以用来简化我们的数据,减少数据集中行的数量。

图片

图 2.10 可以通过减少数据集中列的数量来简化我们的数据,从而进行降维。

你可能想知道,我们是否可以同时减少行和列?答案是肯定的!我们可以通过两种常见的方法来实现这一点:矩阵分解奇异值分解。这两个算法将大数据矩阵表示为较小矩阵的乘积。

类似 Netflix 这样的地方广泛使用矩阵分解来生成推荐。想象一个大的表格,其中每一行对应一个用户,每一列对应一部电影,矩阵中的每个条目都是用户对电影的评分。通过矩阵分解,可以提取某些特征,例如电影类型、电影中的演员等等,并能够根据这些特征预测用户对电影的评分。

奇异值分解用于图像压缩。例如,黑白图像可以看作是一个大数据表,其中每个条目包含对应像素的强度。奇异值分解使用线性代数技术简化这个数据表,从而允许我们简化图像,并使用更少的条目存储其简化版本。

生成式机器学习

生成式 机器学习是机器学习中最令人惊讶的领域之一。如果你看到过计算机生成的超逼真的人脸、图像或视频,那么你就看到了生成式机器学习的实际应用。

生成学习领域包括一些模型,这些模型在给定一个数据集的情况下,可以输出看起来像原始数据集样本的新数据点。这些算法被迫学习数据的外观以产生相似的数据点。例如,如果数据集包含人脸图像,那么算法将产生看起来逼真的面孔。生成算法已经能够创造出极其逼真的图像、绘画等等。它们还生成了视频、音乐、故事、诗歌以及许多其他奇妙的事物。最受欢迎的生成算法是生成对抗网络(GANs),由伊恩·古德费洛及其合作者开发。其他有用且流行的生成算法包括由 Kingma 和 Welling 开发的变分自编码器,以及由杰弗里·辛顿开发的受限玻尔兹曼机(RBMs)。

如你所想,生成学习相当困难。对于人类来说,确定一张图片是否显示了一只狗比画一只狗要容易得多。这项任务对计算机来说同样困难。因此,生成学习中的算法很复杂,需要大量的数据和计算能力才能使它们工作得很好。因为这本书是关于监督学习的,所以我们不会详细讨论生成学习,但在第十章中,我们将了解一些这些生成算法是如何工作的,因为它们倾向于使用神经网络。附录 C 包含了一些资源推荐,包括作者的一个视频,如果你想进一步探索这个话题的话。

什么是强化学习?

强化学习是一种不同类型的机器学习,其中没有提供数据,我们必须让计算机执行一个任务。不是数据,模型接收一个环境和应该在这个环境中导航的智能体。智能体有一个目标或一系列目标。环境有奖励和惩罚,引导智能体做出正确的决策以达到其目标。这一切听起来可能有点抽象,但让我们看看一个例子。

示例:网格世界

在图 2.11 中,我们看到一个网格世界,其中有一个机器人在左下角。那就是我们的智能体。目标是到达网格右上角的宝箱。在网格中,我们还可以看到一个山丘,这意味着我们不能通过那个方块,因为机器人不能爬山。我们还看到一个龙,如果机器人敢降落在它的方块上,龙将会攻击机器人,这意味着我们的目标之一是不降落在那里。这就是游戏。为了给机器人提供如何进行的信息,我们记录一个分数。分数从零开始。如果机器人到达宝箱,我们就获得 100 分。如果机器人遇到龙,我们就会失去 50 分。为了确保我们的机器人移动得快,我们可以这样说,对于机器人每走一步,我们就失去 1 分,因为机器人走路时会消耗能量。

图 2.11 展示了我们的智能体是一个机器人的网格世界。机器人的目标是找到宝箱,同时避开龙。山丘代表机器人无法通过的地方。

训练这个算法的方法,非常粗略地说,是这样的:机器人开始四处走动,记录它的分数并记住是什么步骤带它到那里。在某个时候,它可能会遇到龙,失去很多分数。因此,它学会了将龙方块及其附近的方块与低分数联系起来。在某个时候,它也可能击中宝箱,并学会将那个方块及其附近的方块与高分数联系起来。经过长时间玩这个游戏,机器人将很好地了解每个方块的好坏,并且它可以沿着跟随方块直到宝箱的路径前进。图 2.12 展示了

a possible path, although this one is not ideal, because it passes too close to the dragon. Can you think of a better one?

图片

图 2.12 这里是机器人可能采取的寻找宝箱的路径。

当然,这是一个非常简短的说明,强化学习还有很多其他内容。附录 C 推荐了一些进一步学习的资源,包括一个深度强化学习的视频。

强化学习有许多尖端应用,包括以下内容:

  • 游戏:最近在教计算机如何赢得游戏(如围棋或象棋)的进步,使用了强化学习。此外,已经教会代理赢得像Breakout超级马里奥这样的 Atari 游戏。

  • 机器人技术:强化学习被广泛用于帮助机器人执行拾取箱子、打扫房间或甚至跳舞等任务!

  • 自动驾驶汽车:强化学习技术被用来帮助汽车执行许多任务,例如路径规划或在特定环境中行为。

摘要

  • 存在几种机器学习类型,包括监督学习、无监督学习和强化学习。

  • 数据可以是标记的或未标记的。标记数据包含一个特殊特征或标签,我们旨在预测。未标记数据不包含此特征。

  • 监督学习用于标记数据,并包括构建模型来预测未见数据的标签。

  • 无监督学习用于未标记的数据,并包括简化我们的数据而不丢失大量信息的算法。无监督学习通常用作预处理步骤。

  • 两种常见的监督学习算法被称为回归和分类。

    • 回归模型是那些答案可以是任何数字的模型。

    • 分类模型是那些答案属于某种类型或类别的模型。

  • 两种常见的无监督学习算法是聚类和维度约简。

    • 聚类用于将数据分组到相似的簇中,以提取信息或使其更容易处理。

    • 维度约简是一种通过合并某些相似特征并尽可能少地丢失信息来简化我们的数据的方法。

    • 矩阵分解和奇异值分解是其他可以通过减少行和列的数量来简化我们的数据的算法。

  • 生成式机器学习是一种创新的无监督学习类型,它由生成与我们的数据集相似的数据组成。生成模型可以绘制逼真的面孔,创作音乐,并写诗。

  • 强化学习是一种机器学习类型,其中代理必须在一个环境中导航并达到目标。它在许多尖端应用中得到了广泛的应用。

练习

练习 2.1

对于以下每个场景,说明它是一个监督学习还是无监督学习的例子。解释你的答案。在模糊的情况下,选择一个,并解释你为什么选择它。

  1. 在社交网络上的推荐系统,向用户推荐潜在的朋友

  2. 在新闻网站上,将新闻分为主题的系统

  3. Google 自动补全功能的句子

  4. 在在线零售商上的推荐系统,根据用户的过去购买历史向用户推荐购买商品

  5. 一家信用卡公司中用于捕捉欺诈交易的系统

练习 2.2

对于以下机器学习的应用,你会使用回归还是分类来解决它?解释你的答案。在存在歧义的情况下,选择一个,并解释你为什么选择它。

  1. 在线商店预测用户在其网站上将花费多少钱

  2. 语音助手解码语音并将其转换为文本

  3. 从特定公司买卖股票

  4. YouTube 向用户推荐视频

练习 2.3

你的任务是构建一辆自动驾驶汽车。至少给出三个你必须解决的机器学习问题来构建它。在每个例子中,解释你是否使用监督学习/无监督学习,如果是监督学习,是否使用回归或分类。如果你使用其他类型的机器学习,解释是哪一种,以及为什么。

3 画一条接近我们点的线:线性回归

在本章

  • 什么是线性回归

  • 通过一组数据点拟合一条线

  • 在 Python 中编写线性回归算法

  • 使用 Turi Create 构建线性回归模型以预测真实数据集中的房价

  • 什么是多项式回归

  • 通过非线性数据拟合更复杂的曲线

  • 讨论线性回归在现实世界中的应用实例,例如医疗应用和推荐系统

图片

在本章中,我们将学习线性回归。线性回归是一种强大且广泛使用的方法,用于估计值,例如房屋价格、某种股票的价值、个人的预期寿命,或者用户观看视频或花费在网站上的时间。你可能之前已经见过线性回归,它包括许多复杂的公式,如导数、方程组和行列式。然而,我们也可以以更图形化和更少公式化的方式看待线性回归。在本章中,为了理解线性回归,你只需要具备可视化点和线移动的能力。

假设我们有一些点,它们大致看起来像是在形成一条线,如图 3.1 所示。

图片

图 3.1 一些看起来大致形成一条线的点

线性回归的目标是画出尽可能接近这些点的线。你会画一条什么样的线来接近这些点?图 3.2 中的那条线怎么样?

将这些点想象成镇上的房子,我们的目标是建造一条穿过镇子的路。我们希望这条线尽可能接近这些点,因为镇上的居民都希望住在靠近路的地方,我们的目标是尽可能满足他们的需求。

图片

图 3.2 一条经过点的线

我们也可以想象这些点为固定在地板上的磁铁(因此它们不能移动)。现在想象在它们上面扔一根直金属棒。棒会移动,但由于磁铁的吸引,它最终会达到一个平衡位置,尽可能接近所有点。

当然,这可能会导致很多歧义。我们是要一条大致靠近所有房子的路,还是可能真的靠近一些房子而远离其他房子?以下是一些随之而来的问题:

  • 我们所说的“大致形成一条线的点”是什么意思?

  • 我们所说的“经过点非常接近的线”是什么意思?

  • 我们如何找到这样的线?

  • 这在现实世界中有什么用?

  • 这为什么是机器学习?

在本章中,我们将回答所有这些问题,并构建一个线性回归模型来预测真实数据集中的房价。

您可以在以下 GitHub 仓库中找到本章的所有代码:github.com/luisguiserrano/manning/tree/master/Chapter_3_Linear_Regression.

问题:我们需要预测房子的价格

假设我们是负责销售新房子的房地产经纪人。我们不知道价格,但想通过与其他房子的比较来推断它。我们查看可能影响价格的房屋特征,如大小、房间数量、位置、犯罪率、学校质量和距离商业区。最终,我们希望得到一个公式,包含所有这些特征,从而给出房子的价格,或者至少是一个良好的估计。

解决方案:为房价构建回归模型

让我们尽可能使用一个简单的例子。我们只看一个特征——房间数量。我们的房子有四个房间,附近有六座房子,分别有一、二、三、五、六和七个房间。它们的价格显示在表 3.1 中。

表 3.1 房屋表格,包含房间数量和价格。房屋 4 是我们试图推断价格的房屋。

房间数量 价格
1 150
2 200
3 250
4 ?
5 350
6 400
7 450

你会给房屋 4 一个什么价格,仅基于这个表格上的信息?如果你说是 300 美元,那么我们做出了相同的猜测。你可能注意到了一个模式,并利用它来推断房子的价格。你在脑海中做的事情是线性回归。让我们更深入地研究这个模式。你可能已经注意到,每次增加一个房间,房子的价格就会增加 50 美元。更具体地说,我们可以将房子的价格视为两个东西的组合:一个基础价格为 100 美元,以及每个房间的额外费用 50 美元。这可以总结为一个简单的公式:

价格 = 100 + 50(房间数量)

我们在这里做的是提出一个由公式表示的模型,它根据特征(即房间数量)给出房价的预测。每间房的价格称为该相应特征的权重,基础价格称为模型的偏差。这些都是机器学习中的重要概念。我们在第一章和第二章中学习了一些,但让我们通过从这个问题的角度来定义它们来刷新我们的记忆。

特征:数据点的特征是我们用来进行预测的属性。在这种情况下,特征是房子的房间数量、犯罪率、房子的年龄、大小等。在我们的案例中,我们决定使用一个特征:房子的房间数量。

标签:这是我们尝试从特征中预测的目标。在这种情况下,标签是房子的价格。

模型:机器学习模型是一个规则或公式,它从特征中预测标签。在这种情况下,模型是我们找到的价格方程。

预测:预测是模型的输出。如果模型说,“我认为四间房的房子将花费 300 美元”,那么预测就是 300。

权重:在对应于模型的公式中,每个特征都乘以一个相应的因子。这些因子是权重。在之前的公式中,唯一的特征是房间数,其相应的权重是 50。

偏差:正如你所见,对应于模型的公式有一个与任何特征都不相连的常数。这个常数被称为偏差。在这个模型中,偏差是 100,它对应于房子的基础价格。

现在的问题是,我们是如何得出这个公式的?或者更具体地说,我们是如何让计算机得出这个权重和偏差的?为了说明这一点,让我们看看一个稍微复杂一点的例子。因为这是一个机器学习问题,我们将使用我们在第二章中学到的记住-制定-预测框架来处理它。更具体地说,我们将记住其他房子的价格,制定一个价格模型,并使用这个模型来预测新房子的价格。

记忆步骤:查看现有房子的价格

为了更清楚地看到这个过程,让我们看看一个稍微复杂一些的数据集,比如表 3.2 中的数据集。

表 3.2 房屋及其房间数和价格的一个稍微复杂的数据集

房间数 价格
1 155
2 197
3 244
4 ?
5 356
6 407
7 448

这个数据集与之前的一个类似,但现在价格并不遵循一个很好的模式,即每个价格比前一个高 50 美元。然而,它离原始数据集并不远,因此我们可以预期一个类似的模式应该很好地近似这些值。

通常,当我们得到一个新的数据集时,我们首先做的事情是绘制它。在图 3.3 中,我们可以看到一个坐标系统中的点,其中水平轴代表房间数,垂直轴代表房子的价格。

图 3.3 表 3.2 中数据集的散点图。水平轴代表房间数,垂直轴代表房子的价格。

公式步骤:制定一个估算房价的规则

表 3.2 中的数据集与表 3.1 中的数据集非常接近,所以目前我们可以安全地使用相同的公式来计算价格。唯一的区别是,现在的价格并不完全符合公式,我们有一个小的误差。我们可以将方程写成以下形式:

价格 = 100 + 50(房间数) + (小误差)

如果我们想要预测价格,我们可以使用这个方程。尽管我们不确定我们会得到实际值,但我们知道我们很可能会接近。现在的问题是,我们是如何找到这个方程的?最重要的是,计算机是如何找到这个方程的?

让我们回到图表,看看方程在这里的含义。如果我们查看所有垂直(y)坐标是 100 加上 50 倍水平(x)坐标的点会发生什么?这组点形成了一条斜率为 50,y截距为 100 的直线。在我们展开前面的陈述之前,这里有一些斜率、y截距和直线方程的定义。我们将在“斜率和y截距快速入门”部分更详细地探讨这些内容。

斜率 直线的斜率是衡量其陡峭程度的度量。它是通过将上升量除以运行量(即上升了多少单位,除以向右移动了多少单位)来计算的。这个比率在整个直线上是恒定的。在机器学习模型中,这是相应特征的权重,它告诉我们当我们增加一个单位的特征值时,我们期望标签值上升多少。如果直线是水平的,那么斜率为零,如果直线向下倾斜,那么斜率为负。

y截距 直线的y截距是直线与垂直(y)轴交叉的高度。在机器学习模型中,它是偏差,告诉我们当所有特征都精确为零的数据点中的标签值是什么。

线性方程 这是一条直线的方程。它由两个参数给出:斜率和y截距。如果斜率是my截距是b,那么直线的方程是y = mx + b,直线由满足该方程的所有点(x,y)形成。在机器学习模型中,x是特征的值,y是标签的预测。模型的权重和偏差分别是mb

现在,我们可以分析这个方程了。当我们说直线的斜率是 50 时——这意味着每次我们给房子增加一个房间,我们估计房子的价格将上涨 50 美元。当我们说直线的y截距是 100 时,这意味着一个(假设的)零房间房子的价格估计将是基础价格 100 美元。这条直线在图 3.4 中绘制。

图 3.4 我们构建的模型是尽可能接近所有房子的直线。

现在,在所有可能的直线(每条直线都有自己的方程)中,我们为什么选择这条特定的直线?因为它接近这些点。可能还有更好的选择,但至少我们知道这条直线是好的,而不是一条根本不接近这些点的直线。现在我们回到了原始问题,我们有一组房子,我们想要建造一条尽可能靠近它们的路。

我们如何找到这条直线?我们将在本章后面讨论这个问题。但就目前而言,让我们假设我们有一个水晶球,给定一些点,找到最接近它们的直线。

预测步骤:当市场上出现一栋新房子时,我们该怎么办?

现在,让我们使用我们的模型来预测四间房的房屋价格。为此,我们将数字四作为特征代入公式,得到以下结果:

价格 = 100 + 50 · 4 = 300

因此,我们的模型预测这所房子的价格为$300。这也可以通过图 3.5 中的线图来直观地看出。

图片

图 3.5 我们现在的任务是预测四间房房屋的价格。使用模型(线),我们推断出这所房子的预测价格为$300。

如果我们有更多变量怎么办?多元线性回归

在前面的章节中,我们学习了一个基于一个特征(房间数量)预测房屋价格的模型。我们可以想象许多其他有助于预测房屋价格的特征,例如面积、邻居区的学校质量以及房屋年龄。我们的线性回归模型能否容纳这些其他变量?当然可以。当唯一的特征是房间数量时,我们的模型通过将特征乘以它们的对应权重并加上偏差来预测价格。如果我们有更多特征,我们只需要将它们乘以它们的对应权重并将它们加到预测价格上。因此,房屋价格的模型可能看起来像这样:

价格 = 30(房间数量)+ 1.5(面积)+ 10(学校质量)- 2(房屋年龄)+ 50

在这个方程中,为什么除了房屋年龄对应的权重外,所有权重都是正的?原因是其他三个特征(房间数量、面积和学校质量)与房屋价格呈正相关。换句话说,因为大房子和位置好的房子价格更高,这个特征值越高,我们预计房屋价格也越高。然而,因为我们想象到老房子通常更便宜,所以年龄特征与房屋价格呈负相关

如果一个特征的权重为零怎么办?这种情况发生在特征与价格无关时。例如,想象一个测量邻居姓氏以字母“A”开头的数量的特征。这个特征对房屋价格的影响主要不大,因此我们预计在合理的模型中,对应这个特征的权重要么是零,要么非常接近零。

以类似的方式,如果一个特征的权重非常高(无论是正还是负),我们将其解释为模型在告诉我们这个特征在确定房屋价格方面很重要。在前一个模型中,似乎房间数量是一个重要的特征,因为它的权重最大(绝对值)。

在第二章的“降维简化数据而不丢失太多信息”部分中,我们将数据集的列数与数据集所在的维度联系起来。因此,具有两列的数据集可以表示为平面上的点集,具有三列的数据集可以表示为三维空间中的点集。在这样的数据集中,线性回归模型对应于一条线,而不是一个尽可能接近点的平面。想象一下,房间里有很多苍蝇在静止状态下飞来飞去,我们的任务是尽可能接近所有苍蝇通过一张巨大的纸板。这是具有三个变量的多元线性回归。对于具有更多列的数据集,问题变得难以可视化,但我们总是可以想象一个具有许多变量的线性方程。

在本章中,我们主要处理只有一个特征的线性回归模型的训练,但如果有更多特征,程序是相似的。我鼓励你在心中记住这个事实,并想象你如何将我们接下来的每个陈述推广到具有多个特征的情况。

一些出现的问题和一些快速回答

好吧,你的脑海中可能充满了许多问题。让我们解决一些(希望是所有)它们!

  1. 如果模型出错会怎样?

  2. 你是如何想出预测价格的公式的?如果我们不是有六套房屋,而是有成千上万套房屋,我们会怎么做?

  3. 假设我们已经构建了这个预测模型,然后市场上开始出现新的房屋。有没有一种方法可以用新信息更新模型?

本章回答了所有这些问题,但这里有一些快速回答:

  1. 如果模型出错会怎样?

    该模型正在估算房屋的价格,因此我们预计它几乎每次都会犯一个小错误,因为很难击中确切的价格。训练过程包括找到在数据点处使错误最小的模型。

  2. 你是如何想出预测价格的公式的?如果我们不是有六套房屋,而是有成千上万套房屋,我们会怎么做?

    是的,这正是我们在本章中要解决的主要问题!当我们只有六套房屋时,绘制一条接近它们的线的问题很简单,但如果我们有成千上万的房屋,这项任务就变得困难了。在本章中,我们为计算机设计了一个算法或程序,以便找到一条好的线。

  3. 假设我们已经构建了这个预测模型,然后市场上开始出现新的房屋。有没有一种方法可以用新信息更新模型?

    绝对!我们将构建一个模型,使其在出现新数据时可以轻松更新。这始终是机器学习中要寻找的东西。如果我们构建的模型需要每次新数据到来时都重新计算整个模型,那么它将不会很有用。

如何让计算机绘制这条线:线性回归算法

现在我们来探讨本章的主要问题:我们如何让计算机绘制一条非常接近点的线?我们这样做的方式与我们在机器学习中做很多事情的方式相同:一步一步。从一个随机的线开始,找出一种方法,通过将其移近点来稍微改进这条线。重复这个过程多次, voilà,我们就得到了想要的线。这个过程被称为线性回归算法。

这个步骤可能听起来很愚蠢,但它确实非常有效。从一个随机的线开始。在数据集中选择一个随机点,并将线稍微移近这个点。重复这个过程多次,每次都在数据集中选择一个随机点。以这种几何方式查看线性回归算法的伪代码如下。图 3.6 展示了该图示。

线性回归算法(几何)的伪代码

输入:平面上的点数据集

输出:一条通过点的线

步骤:

  • 选择一条随机线。

  • 重复多次:

    • 选择一个随机数据点。

    • 将线稍微移近那个点。

  • 返回你得到的线。

图 3.6 线性回归算法的示意图。我们从左上角的一个随机线开始,最终在左下角得到一条很好地拟合数据集的线。在每一个阶段,都会发生两件事:(1)我们选择一个随机点,(2)这个点要求线靠近它。经过多次迭代,线将处于良好的位置。这个图只有三个迭代,用于说明目的,但在现实生活中,需要更多的迭代。

那是高级概述。为了更详细地研究这个过程,我们需要深入了解数学细节。让我们首先定义一些变量。

  • p: 数据集中房屋的价格

  • :房屋的预测价格

  • r: 房间数量

  • m: 每间房的价格

  • b: 房屋的基本价格

为什么预测价格上方的帽子,?在本书中,帽子表示这是我们模型预测的变量。这样,我们可以从预测价格中区分出数据集中房屋的实际价格。

因此,预测价格为基础价格加上每间房的价格乘以房间数量的线性回归模型方程是

= mr + b.

这是一种公式化的说法

预测价格 = (每间房的价格)(房间数量) + 房屋的基本价格。

为了了解线性回归算法,想象我们有一个模型,其中每间房的价格是 40 美元,房屋的基本价格是 50 美元。这个模型使用以下公式预测房屋的价格:

= 40 · r + 50

为了说明线性回归算法,想象一下在我们的数据集中有一所两居室的房子,价格为 150 美元。这个模型预测房子的价格是 50 + 40 * 2 = 130 美元。这不是一个糟糕的预测,但它低于房子的价格。我们如何改进这个模型?看起来模型的错误在于认为房子太便宜。也许模型的基础价格低,或者每间房子的价格低,或者两者都低。如果我们都稍微增加一点,我们可能会得到更好的估计。让我们将每间房子的价格增加 0.50 美元,基础价格增加 1 美元。(我随机选择了这些数字)。新的方程如下:

图片 = 40.5 * r + 51

新预测的房价为 40.5 * r + 51 = 132。因为 132 比 150 更接近,所以我们的新模型对这个房子的预测更好。因此,它对这个数据点是一个更好的模型。我们不知道它是否对其他数据点也是一个更好的模型,但现在我们不必担心这一点。线性回归算法的想法是重复前面的过程多次。线性回归算法的伪代码如下:

线性回归算法的伪代码

输入:一组数据点集

输出:一个适合该数据集的线性回归模型

程序:

  • 选择一个具有随机权重和随机偏差的模型。

  • 重复多次:

    • 选择一个随机数据点。

    • 稍微调整权重和偏差,以改善该特定数据点的预测。

  • 返回你获得的模型。

你可能会有一些疑问,比如以下:

  • 我应该如何调整权重?

  • 我应该重复算法多少次?换句话说,我如何知道何时完成?

  • 我如何知道这个算法是有效的?

我们在本章中回答了所有这些问题。在“平方技巧”和“绝对技巧”部分,我们学习了一些有趣的技巧来找到调整权重的良好值。在“绝对误差”和“平方误差”部分,我们看到了误差函数,它将帮助我们决定何时停止算法。最后,在“梯度下降”部分,我们介绍了一种称为梯度下降的强大方法,它证明了为什么这个算法是有效的。但首先,让我们从在平面上移动线开始。

斜率和 y 轴截距的快速课程

在“公式步骤”部分,我们讨论了直线的方程。在本节中,我们学习如何操作这个方程来移动我们的线。回想一下,直线的方程有两个组成部分:

  • 斜率

  • y 轴截距

斜率告诉我们线的陡峭程度,y 轴截距告诉我们线的位置。斜率定义为上升量除以运行量,y 轴截距告诉我们线与 y 轴(垂直轴)相交的位置。在图 3.7 中,我们可以看到一个例子。这条线的方程如下:

y = 0.5 * x + 2

图片

图 3.7 方程式为 y = 0.5x + 2 的线具有斜率 0.5(左侧)和 y 截距 2(右侧)。

这个方程式意味着什么?这意味着斜率是 0.5,而 y 截距是 2。

当我们说斜率是 0.5 时,这意味着当我们沿着这条线行走时,每向右移动一个单位,我们向上移动 0.5 个单位。如果完全不向上移动,斜率可以是零;如果向下移动,斜率可以是负数。垂直线的斜率是未定义的,但幸运的是,这些在线性回归中不太可能出现。许多线条可以具有相同的斜率。如果我画出与图 3.7 中的线平行的任何线条,这条线在向右移动一个单位时也会上升 0.5 个单位。这就是 y 截距的作用所在。y 截距告诉我们线在哪里切割 y 轴。这条线在高度 2 处切割 x 轴,这就是 y 截距。

换句话说,线的斜率告诉我们线指向的方向,而 y 截距告诉我们线的位置。请注意,通过指定斜率和 y 截距,线就被完全确定了。在图 3.8 中,我们可以看到具有相同 y 截距的不同线条,以及具有相同斜率的不同线条。

图 3.8 斜率和 y 截距的一些示例。在左侧,我们看到几条具有相同截距但斜率不同的线条。请注意,斜率越高,线越陡峭。在右侧,我们看到几条具有相同斜率但 y 截距不同的线条。请注意,y 截距越高,线所在的位置越高。

在我们当前的住房示例中,斜率代表每间房的价格,而 y 截距代表房屋的基准价格。让我们记住这一点,当我们操作这些线条时,思考这对我们的房价模型做了什么。

从斜率和 y 截距的定义中,我们可以得出以下结论:

改变斜率

  • 如果我们增加一条线的斜率,这条线将逆时针旋转。

  • 如果我们减少一条线的斜率,这条线将顺时针旋转。

这些旋转是在图 3.9 中显示的枢轴上进行的,即线和 y 轴的交点。

改变 y截距

  • 如果我们增加一条线的 y 截距,这条线将向上平移。

  • 如果我们降低一条线的 y 截距,这条线就会向下平移。

图 3.9 说明了这些旋转和平移,当我们想要调整我们的线性回归模型时,这些将很有用。

图 3.9 左侧:增加斜率会使线逆时针旋转,而减少斜率会使线顺时针旋转。右侧:增加 y 截距会使线向上平移,而减少 y 截距会使线向下平移。

如前所述,一般来说,直线的方程写作 y = mx + b,其中 xy 分别对应水平和垂直坐标,m 对应斜率,b 对应 y 轴截距。在本章中,为了与符号一致,我们将方程写作 图片 = mr + b,其中 图片 对应预测价格,r 对应房间数量,m(斜率)对应每间房的价格,by 轴截距)对应房屋的基准价格。

将直线移近一组点的一个简单技巧,一次移动一个点

回想一下,线性回归算法由重复一个步骤组成,即我们移动直线使其更接近一个点。我们可以通过旋转和平移来实现这一点。在本节中,我们学习了一个称为 简单技巧 的技巧,该技巧包括稍微旋转并平移直线,使其朝向点的方向移动,从而使其更接近(图 3.10)。

图片

图 3.10 我们的目标是通过旋转和平移直线一小量,使其更接近点。

正确移动直线到点的技巧是确定点相对于直线的位置。如果点在直线上方,我们需要将直线向上平移,如果点在直线下方,我们需要将直线向下平移。旋转稍微困难一些,但由于旋转中心是直线与 y 轴的交点,我们可以看到如果点在直线上方且在 y 轴右侧,或者点在直线下方且在 y 轴左侧,我们需要将直线逆时针旋转。在其他两种情况下,我们需要将直线顺时针旋转。这些情况总结在以下四个案例中,如图 3.11 所示:

案例 1:如果点位于直线上方且在 y 轴右侧,我们将直线逆时针旋转并将其向上平移。

案例 2:如果点位于直线上方且在 y 轴左侧,我们将直线顺时针旋转并将其向上平移。

案例 3:如果点位于直线下方且在 y 轴右侧,我们将直线顺时针旋转并将其向下平移。

案例 4:如果点位于直线下方且在 y 轴左侧,我们将直线逆时针旋转并将其向下平移。

图片

图 3.11 四种情况。在这些情况中,我们必须以不同的方式旋转并平移直线,以使直线更接近相应的点。

现在我们有了这四种情况,我们可以编写简单技巧的伪代码。但首先,让我们明确一些符号。在本节中,我们一直在谈论方程为 y = mx + b 的直线,其中 m 是斜率,by 轴截距。在房屋示例中,我们使用了以下类似的符号:

  • 坐标为 (r, p) 的点对应有 r 间房且价格为 p 的房屋。

  • 斜率 m 对应每间房的价格。

  • y 轴截距 b 对应房屋的基准价格。

  • 预测值 = mr + b 对应于房屋的预测价格。

简单技巧的伪代码

输入:

  • 斜率为 my 截距为 b、方程为 = mr + b 的线

  • 坐标为 (r, p) 的点

输出:

  • 方程为 = m'r + b 的线,该线更接近点

程序:

选择两个非常小的随机数,并称它们为 η[1] 和 η[2](希腊字母 eta)。

情况 1:如果点位于线上且在 y 轴的右侧,我们将线逆时针旋转并向上平移:

  • η[1] 加到斜率 m 上。得到 m**' = m + η[1]。

  • η[2] 加到 y 截距 b 上。得到 b**' = b + η[2]。

情况 2:如果点位于线上且在 y 轴的左侧,我们将线顺时针旋转并向上平移:

  • 从斜率 m 中减去 η[1]。得到 m**' = mη[1]。

  • η[2] 加到 y 截距 b 上。得到 b**' = b + η[2]。

情况 3:如果点位于线下且在 y 轴的右侧,我们将线顺时针旋转并向下平移:

  • 从斜率 m 中减去 η[1]。得到 m**' = mη[1]。

  • y 截距 b 中减去 η[2]。得到 b**' = bη[2]。

情况 4:如果点位于线下且在 y 轴的左侧,我们将线逆时针旋转并向下平移:

  • η[1] 加到斜率 m 上。得到 m**' = m + η[1]。

  • y 截距 b 中减去 η[2]。得到 b**' = bη[2]。

返回:方程为 = m'r + b'. 的线

注意,对于我们的例子,向斜率中添加或减去一个小数意味着增加或减少每间房的价格。同样,向 y 截距中添加或减去一个小数意味着增加或减少房屋的基本价格。此外,因为 x 坐标是房间数,这个数永远不会是负数。因此,在我们的例子中,只有情况 1 和 3 是重要的,这意味着我们可以用口语化的语言总结简单技巧如下:

简单技巧

  • 如果模型给出的房价低于实际价格,则将每间房的价格和房屋的基本价格中加上一个小随机数。

  • 如果模型给出的房价高于实际价格,则从每间房的价格和房屋的基本价格中减去一个小随机数。

这个技巧在实践中取得了一些成功,但离最佳移动线的方法还远。可能会出现一些问题,例如以下问题:

  • 我们能否为 η[1] 和 η[2] 选择更好的值?

  • 我们能否将四个情况合并为两个,或者可能是一个?

两个问题的答案都是肯定的,我们将在接下来的两个部分中看到这一点。

平方技巧:将我们的线移近其中一个点的一个更巧妙的方法

在本节中,我将向您展示一种有效的方法,使直线更接近一个点。我称这个技巧为 square trick。回想一下,简单技巧基于四个基于点相对于直线位置的场景。square trick 将这四个场景简化为一个,通过找到正确的符号(+或-)的值来添加到斜率和 y-截距,使直线始终更接近点。

我们从 y-截距开始。注意以下两个观察结果:

  • 观察 1:在简单技巧中,当点在直线上方时,我们向 y-截距添加一小部分。当它在直线下方时,我们减去一小部分。

  • 观察 2:如果一个点在直线上方,值 (价格与预测价格的差值)是正的。如果它在直线下方,这个值就是负的。这个观察结果在图 3.12 中得到了说明。

图 3.12 左:当点在直线上方时,价格大于预测价格,因此差值是正的。右:当点在直线下方时,价格小于预测价格,因此差值是负的。

将观察 1 和观察 2 结合起来,我们得出结论,如果我们把差值 添加到 y-截距,直线将始终朝向点移动,因为当点在直线上方时这个值是正的,当点在直线下方时这个值是负的。然而,在机器学习中,我们总是希望采取小步骤。为了帮助我们做到这一点,我们引入了机器学习中的一个重要概念:学习率。

学习率 在训练模型之前我们选择的一个非常小的数字。这个数字帮助我们确保通过训练,我们的模型以非常小的量发生变化。在这本书中,学习率将用 η,希腊字母 eta 表示。

由于学习率很小,因此 η() 的值也很小。这是我们添加到 y-截距的值,以使直线朝着点的方向移动。

需要添加到斜率中的值类似,但稍微复杂一些。注意以下两个观察结果:

  • 观察 3:在简单技巧中,当点处于场景 1 或 4(在垂直轴上方且在直线上方,或在垂直轴下方且在直线下方)时,我们逆时针旋转直线。否则(场景 2 或 3),我们顺时针旋转。

  • 观察 4:如果一个点 (r, p) 在垂直轴右侧,那么 r 是正的。如果点在垂直轴左侧,那么 r 是负的。这个观察结果在图 3.13 中得到了说明。注意,在这个例子中,r 从不会是负的,因为它代表的是房间数。然而,在一般例子中,一个特征可能是负的。

图 3.13 左:当点在 y-轴左侧时,房间数是负的。右:当点在 y-轴右侧时,房间数是正的。

考虑值 r(). 当 r 都为正或都为负时,此值是正的。这正是情况 1 和 4 中发生的情况。同样,r() 在情况 2 和 3 中为负。因此,根据观察 4,这是我们需要添加到斜率中的量。我们希望这个值很小,所以再次,我们将其乘以学习率,并得出结论,将 η**r() 添加到斜率中,将始终使线向点的方向移动。

我们现在可以编写平方技巧的伪代码如下:

平方技巧的伪代码

输入:

  • 斜率为 my-截距 b,方程为 = mr + b

  • 坐标为 (r, p) 的点

  • 一个小的正值 η(学习率)

输出:

  • 方程为 = m'r + b**' 且更接近点的线

程序:

  • η**r() 添加到斜率 m。得到 m**' = m + η**r()(这会旋转线)。

  • η() 添加到 y-截距 b。得到 b**' = b + η()(这会平移线)。

返回:方程为 = m'r + b**' 的线

我们现在准备好用 Python 编写这个算法!本节的代码如下:

下面是平方技巧的代码:

def square_trick(base_price, price_per_room, num_rooms, price, learning_rate):
    predicted_price = base_price + price_per_room*num_rooms           ❶
    base_price += learning_rate*(price-predicted_price)               ❷
    price_per_room += learning_rate*num_rooms*(price-predicted_price) ❸
    return price_per_room, base_price

❶ 计算预测值

❷ 翻译该行

❸ 旋转线

绝对技巧:另一个有用的技巧,用于将线移得更接近点

平方技巧是有效的,但另一个有用的技巧,我们称之为 绝对技巧,是简单技巧和平方技巧之间的中间技巧。在平方技巧中,我们使用了两个量,(价格 - 预测价格)和 r(房间数),帮助我们把这四种情况简化为一种。在绝对技巧中,我们只使用 r 来帮助我们把这四种情况简化为两种。换句话说,这里是绝对技巧:

绝对技巧的伪代码

输入:

  • 斜率为 my-截距 b,方程为 = mr + b

  • 坐标为 (r, p) 的点

  • 一个小的正值 η(学习率)

输出:

  • 方程为 = m'r + b**' 且更接近点的线

程序:

情况 1:如果点在线上方(即,如果 p > ):

  • η**r 添加到斜率 m。得到 m**' = m + η**r(如果点在 y 轴右侧,则使直线逆时针旋转,如果点在 y 轴左侧,则使直线顺时针旋转)。

  • η 添加到 y 截距 b。得到 b**' = b + η(这使直线向上移动)。

情况 2:如果点在直线下方(即,如果 p < ):

  • 从斜率 m 中减去 η**r。得到 m**' = mη**r(如果点在 y 轴右侧,则使直线顺时针旋转,如果点在 y 轴左侧,则使直线逆时针旋转)。

  • 从 y 截距 b 中减去 η。得到 b**' = bη(这使直线向下移动)。

返回:方程 = m'r + b**'

这是绝对值技巧的代码:

def absolute_trick(base_price, price_per_room, num_rooms, price, learning_rate):
    predicted_price = base_price + price_per_room*num_rooms
    if price > predicted_price:
        price_per_room += learning_rate*num_rooms
        base_price += learning_rate
    else:
        price_per_room -= learning_rate*num_rooms
        base_price -= learning_rate
    return price_per_room, base_price

我鼓励你验证添加到每个权重中的数量确实具有正确的符号,就像我们使用平方技巧所做的那样。

线性回归算法:重复使用绝对值或平方技巧多次,使直线更接近点

现在我们已经完成了所有艰苦的工作,我们准备开发线性回归算法!这个算法以一系列点作为输入,并返回一条很好地拟合这些点的直线。这个算法包括从随机值开始为我们的斜率和 y 截距,然后重复多次使用绝对值或平方技巧来更新它们的步骤。以下是伪代码:

线性回归算法的伪代码

输入:

  • 房屋数量和价格的数据集

输出:

  • 模型权重:每间房的价格和基础价格

程序:

  • 以斜率和 y 截距的随机值开始。

  • 重复多次:

    • 随机选择一个数据点。

    • 使用绝对值或平方技巧更新斜率和 y 截距。

每次循环迭代被称为一个epoch,我们在算法开始时设置这个数字。简单的技巧主要用于说明,但如前所述,它并不非常有效。在现实生活中,我们使用绝对值或平方技巧,这要有效得多。事实上,尽管两者都常用,但平方技巧更受欢迎。因此,我们将使用这个技巧,但如果你更喜欢,也可以使用绝对值技巧。

这是线性回归算法的代码。请注意,我们使用了 Python 随机包来生成随机数作为我们的初始值(斜率和 y 截距)以及在选择循环内的点:

import random ❶
def linear_regression(features, labels, learning_rate=0.01, epochs = 1000):
    price_per_room = random.random()
    base_price = random.random() ❷
    for epoch in range(epochs):  ❸
        i = random.randint(0, len(features)-1) ❹
        num_rooms = features[i]
        price = labels[i]
        price_per_room, base_price = square_trick(base_price,
                                                  price_per_room,
                                                  num_rooms,
                                                  price,
                                                  learning_rate=learning_rate) ❺
    return price_per_room, base_price

❶ 导入随机包以生成(伪)随机数

❷ 为斜率和 y 截距生成随机值

❸ 多次重复更新步骤

❹ 在我们的数据集上随机选择一个点

❺ 应用平方技巧将直线移动到我们的点附近

下一步是运行这个算法来构建一个适合我们数据集的模型。

加载数据并绘制它

在本章中,我们使用 Matplotlib 和 NumPy 这两个非常有用的 Python 包加载数据和绘制模型图。我们使用 NumPy 存储数组并执行数学运算,而使用 Matplotlib 进行绘图。

我们首先将数据集的特征和标签编码到表 3.2 中,如下所示:

import numpy as np
features = np.array([1,2,3,5,6,7])
labels = np.array([155, 197, 244, 356, 407, 448])

接下来我们绘制数据集。在仓库中,我们有一些用于绘制文件的函数utils.py,你可以查看一下。数据集的绘图显示在图 3.14 中。注意点确实看起来接近形成一条线。

图 3.14 表 3.2 中点的绘图

在我们的数据集中使用线性回归算法

现在,让我们将算法应用于拟合这些点的线条。以下代码行运行算法,使用特征、标签、学习率为 0.01,epoch 数为 10,000。结果是图 3.15 中显示的绘图。

linear_regression(features, labels, learning_rate = 0.01, epochs = 10000)

图 3.15 表 3.2 中点的绘图以及我们用线性回归算法获得的线条

图 3.15 显示了每间房(四舍五入)的价格为 51.05 美元,基础价格为 99.10 美元的线条。这并不远于我们在本章早期用肉眼估算的 50 美元和 100 美元。

为了可视化这个过程,让我们更详细地看看进度。在图 3.16 中,你可以看到一些中间线条。注意线条开始时离点很远。随着算法的进行,它逐渐缓慢地更好地拟合每次。注意在最初(前 10 个 epoch),线条快速地向好的解决方案移动。在第 50 个 epoch 之后,线条很好,但仍然没有完美地拟合点。如果我们让它运行整个 10,000 个 epoch,我们会得到一个非常好的拟合。

图 3.16 展示了我们算法中一些线条的绘制,随着我们接近更好的解决方案。第一幅图显示了起点。第二幅图显示了线性回归算法的前 10 个 epoch。注意线条是如何逐渐靠近拟合点的。第三幅图显示了前 50 个 epoch。第四幅图显示了第 51 个 epoch 到第 10,000 个 epoch(最后一个 epoch)。

使用模型进行预测

现在我们有一个闪亮的线性回归模型,我们可以用它来做出预测!回想一下本章开头,我们的目标是预测四间房的房子的价格。在上一节中,我们运行了算法,得到了斜率(每间房的价格)为 51.05 和y截距(房子的基础价格)为 99.10。因此,方程如下:

模型对有r = 4 个房间的房子的预测是

注意 303.30 并不远于我们在本章开头用肉眼估算的 300 美元!

通用线性回归算法(可选)

这一节是可选的,因为它主要关注用于通用 dataset 的更抽象算法的数学细节。然而,我鼓励你阅读它,以便熟悉在大多数机器学习文献中使用的符号。

在前面的章节中,我们概述了针对只有一个特征的 dataset 的线性回归算法。但正如你可以想象的那样,在现实生活中,我们将处理具有许多特征的 dataset。为此,我们需要一个通用算法。好消息是通用算法与我们在本章中学到的特定算法没有太大区别。唯一的区别是每个特征都是按照斜率更新的方式更新的。在住房示例中,我们有一个斜率和一个 y-截距。在一般情况中,可以想象有许多斜率,但仍然只有一个 y-截距。

一般情况将包含一个包含 m 个点和 n 个特征的 dataset。因此,该模型有 m 个权重(可以将其视为斜率的推广)和一个偏差。符号如下:

  • 数据点是 x((1)),*x*((2)),…,x(m^)。每个点都是 x(i^) = (x[1](i),*x*[2](i),…,x[n](i^)) 的形式。

  • 相应的标签是 y[1],y[2],…,y[m]。

  • 模型的权重为 w[1],w[2],…,w[n]。

  • 模型的偏差为 b

一般平方技巧的伪代码

输入:

  • 方程为 ŷ = w[1]x[1] + w[2]x[2+] + … + w[n]x[n] + b 的模型

  • 坐标为 (x, y) 的点

  • 一个小的正值 η(学习率)

输出:

  • 一个方程为 ŷ = w[1]'**x[1] + w[2]'**x[2] + … + w[n]'**x[n] + b**' 的模型,该模型更接近点

程序:

  • η(yŷ) 添加到 y-截距 b。得到 b**' = b + η(yŷ)。

  • 对于 i = 1, 2, …, n

    • η**x(yŷ) 添加到权重 w[i]。得到 w[i]' = w[i]+ η**r(yŷ)。

返回:方程为 ŷ = w[1]'**x[1] + w[2]'**x[2] + … + w[n]'**x[n] + *b**' 的模型

一般线性回归算法的伪代码与“线性回归算法”部分中的相同,因为它包含对一般平方技巧的迭代,所以我们将其省略。

我们如何衡量我们的结果?误差函数

在前面的章节中,我们开发了一种直接的方法来找到最佳拟合线。然而,很多时候使用直接方法来解决机器学习中的问题是很困难的。一种更间接但更机械的方法是使用误差函数。误差函数是一个指标,告诉我们我们的模型表现如何。例如,看看图 3.17 中的两个模型。左边的是一个不好的模型,而右边的是一个好的模型。误差函数通过给左边的坏模型分配一个大的值,给右边的好模型分配一个小的值来衡量这一点。误差函数有时在文献中也被称为损失函数代价函数。在这本书中,我们除了在某些更常用的名称需要的情况下,通常称之为误差函数。

图 3.17 两个模型,一个不好的(在左边)和一个好的(在右边)。不好的模型被分配了一个大的误差,而好的模型被分配了一个小的误差。

现在的问题是,我们如何为线性回归模型定义一个好的误差函数?我们有两种常见的方法来做这件事,称为绝对误差平方误差。简而言之,绝对误差是数据集中点到直线的垂直距离之和,平方误差是这些距离的平方之和。

在接下来的几节中,我们将更详细地了解这两个误差函数。然后我们看看如何使用称为梯度下降的方法来减少它们。最后,我们绘制其中一个

在我们现有的例子中查看误差函数,看看梯度下降法如何快速帮助我们减小它。

绝对误差:一个通过添加距离来告诉我们模型好坏的指标

在本节中,我们来看绝对误差,这是一个衡量我们模型好坏的指标。绝对误差是数据点与直线之间的距离之和。为什么叫绝对误差?为了计算每个距离,我们取标签与预测标签之间的差值。这个差值可以是正的或负的,取决于点是在直线上方还是下方。为了将这个差值转换成一个始终为正的数,我们取它的绝对值。

根据定义,一个好的线性回归模型是线接近点的模型。在这种情况下,“接近”是什么意思?这是一个主观问题,因为接近一些点的线可能离其他点很远。在这种情况下,我们更愿意选择一个对一些点非常接近而对其他点较远的线,还是尝试选择一个对所有点都有些接近的线?绝对误差帮助我们做出这个决定。我们选择的线是使绝对误差最小化的线,即每个点到线的垂直距离之和最小的线。在图 3.18 中,你可以看到两条线,它们的绝对误差被描绘为垂直线段的总和。左侧的线绝对误差较大,而右侧的线绝对误差较小。因此,在这两条线之间,我们会选择右侧的那条线。

图片

图 3.18 绝对误差是点与线之间的垂直距离之和。注意,左侧的坏模型绝对误差较大,而右侧的好模型绝对误差较小。

平方误差:一个度量,通过添加距离的平方来告诉我们我们的模型有多好

平方误差与绝对误差非常相似,除了我们不是取标签和预测标签之间差异的绝对值,而是取平方。这总是将数字转换为正数,因为平方一个数字总是使其变为正数。这个过程在图 3.19 中得到了说明,其中平方误差被描绘为点与线之间长度的平方的面积之和。你可以看到左侧的坏模型平方误差较大,而右侧的好模型平方误差较小。

图片

图 3.19 平方误差是点与线之间垂直距离的平方之和。注意,左侧的坏模型平方误差较大,而右侧的好模型平方误差较小。

如前所述,在实践应用中,平方误差比绝对误差使用得更普遍。为什么?因为平方的导数比绝对值要平滑得多,这在训练过程中非常有用。

在现实生活中,平均绝对误差和(根)均方误差更为常见

在本章中,我们使用绝对误差和平方误差进行说明。然而,在实践中,平均绝对误差平均平方误差使用得更为普遍。它们的定义方式类似,只是我们计算的是平均值而不是总和。因此,平均绝对误差是点到线的垂直距离的平均值,平均平方误差是这些距离平方的平均值。为什么它们更常见呢?想象一下,如果我们想使用两个数据集来比较误差或模型,一个有 10 个点,另一个有 100 万个点。如果误差是每个点的数量之和,那么在 100 万个点的数据集中,误差可能要高得多,因为我们正在添加更多的数字。如果我们想正确比较它们,我们就在误差的计算中使用平均值,以获得一条线与每个点平均距离的度量。

为了说明目的,另一个常用的错误是均方根误差,或简称为RMSE。正如其名所示,这是均方误差的平方根。它用于匹配问题中的单位,并帮助我们更好地了解模型在预测中产生的误差有多大。为什么会这样呢?想象以下场景:如果我们试图预测房价,那么价格和预测价格的单位,例如,是美元($)。平方误差和均方误差的单位是美元的平方,这不是一个常见的单位。如果我们取平方根,那么我们不仅得到正确的单位,而且还能更准确地了解模型每套房子偏离多少美元。比如说,如果均方根误差是$10,000,那么我们可以预期模型在每次预测中都会产生大约$10,000 的误差。

梯度下降:如何通过缓慢下降从山上减少误差函数

在本节中,我将向您展示如何使用类似于我们缓慢下降从山上使用的方法来减少任何之前的误差。这个过程使用导数,但好消息是:您不需要理解导数。我们已经在之前的“平方技巧”和“绝对技巧”部分中使用了它们。每次我们“在这个方向上移动一小步”,我们都在后台计算误差函数的导数,并使用它来给我们一个移动线的方向。如果您喜欢微积分并且想看到使用导数和梯度的整个算法推导,请参阅附录 B。

让我们退一步,从远处看看线性回归。我们想要做什么?我们想要找到最适合我们数据的线。我们有一个称为误差函数的度量,它告诉我们一条线离数据有多远。因此,如果我们能尽可能减少这个数字,我们就能找到最佳拟合线。这个过程在数学的许多领域都很常见,被称为“最小化函数”,即找到函数可能返回的最小可能值。这就是梯度下降发挥作用的地方:它是一种很好的最小化函数的方法。

在这种情况下,我们试图最小化的函数是模型误差(绝对值或平方值)。一个小小的警告是,梯度下降并不总是找到函数的精确最小值,但它可能会找到非常接近的值。好消息是,在实践中,梯度下降在找到函数值较低的点方面既快又有效。

梯度下降是如何工作的?梯度下降相当于从山上下来。假设我们发现自己站在一个名叫埃罗斯特的高山上。我们希望下山,但雾很大,我们只能看到大约一米的距离。我们该怎么办?一个好方法是环顾四周,找出我们能够迈出一步的方向,这样我们就能下降得最多。这个过程在图 3.20 中得到了说明。

图片

图 3.20 我们站在埃罗斯特山顶上,希望到达底部,但我们看得不是很远。一种下山的办法是看看我们能够迈出一步的所有方向,并找出哪一个能让我们下降得最多。这样我们就离底部更近了一步。

当我们找到这个方向时,我们迈出一小步,因为这一步是在最大下降方向上迈出的,所以很可能会下降一小段距离。我们只需要重复这个过程很多次,直到我们(希望)到达底部。这个过程在图 3.21 中得到了说明。

图片

图 3.21 从山上下来的方法是朝着让我们下降最多的方向迈出那一步,并且长时间地继续这样做。

为什么我说“希望”呢?因为这个过程有很多注意事项。我们可能会到达底部,或者我们可能会到达一个山谷,然后我们就无处可去了。我们现在不处理这个问题,但我们有一些技术可以减少这种情况发生的概率。在附录 B“使用梯度下降训练神经网络”中,概述了一些这些技术。

在这里,我们故意忽略了很多数学内容,这些内容在附录 B 中有更详细的解释。但我们在本章中做的是精确的梯度下降。为什么会这样呢?梯度下降的工作原理如下:

  1. 从山上某个地方开始。

  2. 找到迈出这一小步的最佳方向。

  3. 走出这一小步。

  4. 重复步骤 2 和 3 多次。

这可能看起来很熟悉,因为在“线性回归算法”部分中,在定义绝对值和平方技巧之后,我们以以下方式定义了线性回归算法:

  1. 从任何一条线开始。

  2. 使用绝对值或平方技巧找到移动我们的线一点的最佳方向。

  3. 在这个方向上稍微移动这条线。

  4. 重复步骤 2 和 3 多次。

图 3.22 每个山上的点都对应一个不同的模型。下面的点代表误差小的良好模型,而上面的点代表误差大的不良模型。目标是下山。下山的方式是从某个地方开始,并持续采取使我们下山的步骤。梯度将帮助我们决定采取哪个方向的步骤可以帮助我们最大限度地下降。

这种心理图示如图 3.22 所示。唯一的区别是,这个误差函数看起来更像一个山谷而不是一座山,我们的目标是下降到最低点。山谷中的每个点都对应于一些试图拟合我们的数据的模型(线)。这个点的高度是由该模型给出的误差。因此,不良模型在顶部,良好模型在底部。我们试图尽可能低。每一步都使我们从一个模型到一个稍微好一点的模型。如果我们采取这样的步骤很多次,我们最终会得到最好的模型(或者至少,相当不错的一个!)。

绘制误差函数和知道何时停止运行算法

在本节中,我们看到的是我们在“使用线性回归算法在我们的数据集中”部分中较早进行的训练的误差函数图。这个图为我们提供了关于训练此模型的有用信息。在存储库中,我们还绘制了在“平均绝对值和(根)均方误差...”部分中定义的均方根误差函数(RMSE)。计算 RMSE 的代码如下:

def rmse(labels, predictions):
    n = len(labels)
    differences = np.subtract(labels, predictions)
    return np.sqrt(1.0/n * (np.dot(differences, differences)))

点积 为了编码 RMSE 函数,我们使用了点积,这是一种轻松地写出两个向量中对应项乘积之和的方法。例如,向量(1,2,3)和(4,5,6)的点积是 1 · 4 + 2 · 5 + 3 · 6 = 32。如果我们计算一个向量与自身的点积,我们得到该向量元素平方和。

我们误差的图示如图 3.23 所示。注意它在大约 1,000 次迭代后迅速下降,之后变化不大。这个图为我们提供了有用的信息:它告诉我们,对于这个模型,我们只需要运行训练算法 1,000 或 2,000 次,而不是 10,000 次,仍然可以得到相似的结果。

图 3.23 我们运行示例的均方根误差的图。注意算法在超过 1,000 次迭代后成功减少了这个误差。这意味着我们不需要运行这个算法 10,000 次,因为大约有 2,000 次就能完成任务。

通常,误差函数为我们提供了足够的信息来决定何时停止运行算法。通常,这个决定是基于我们可用的时间和计算能力。然而,在实际应用中,通常还会使用其他有用的基准,如下所示:

  • 当损失函数达到我们预先设定的某个值时

  • 当损失函数在几个时期内没有显著下降时

我们是一次训练一个点还是多个点?随机和批量梯度下降

在“如何让计算机绘制这条线”的部分,我们通过重复执行一个步骤多次来训练线性回归模型。这个步骤包括选择一个点并将线移动到该点。在“我们如何衡量我们的结果”的部分,我们通过计算误差(绝对值或平方值)并使用梯度下降来减少它来训练线性回归模型。然而,这个误差是在整个数据集上计算的,而不是逐点计算。为什么是这样?

事实上,我们可以通过逐点迭代或对整个数据集进行迭代来训练模型。然而,当数据集非常大时,这两种选择都可能很昂贵。我们可以练习一种有用的方法,称为 *小批量学习**,它包括将我们的数据分成许多小批量。在线性回归算法的每次迭代中,我们选择一个小批量,然后调整模型权重以减少该小批量中的误差。在每个迭代中使用一个点、一个小批量点或整个数据集的决定产生了三种类型的梯度下降算法。当我们每次使用一个点时,它被称为 随机梯度下降。当我们使用小批量时,它被称为 小批量梯度下降。当我们使用整个数据集时,它被称为 批量梯度下降。这个过程在附录 B “使用梯度下降训练模型”中进行了更详细的说明。

真实应用:使用 Turi Create 预测印度房价

在本节中,我将向你展示一个真实的应用案例。我们将使用线性回归来预测印度海得拉巴的房价。我们使用的数据集来自 Kaggle,这是一个流行的机器学习竞赛网站。本节的代码如下:

这个数据集有 6,207 行(每行代表一栋房子)和 39 列(特征)。正如你所想象的,我们不会手动编写算法。相反,我们使用 Turi Create,这是一个流行的且非常有用的包,其中实现了许多机器学习算法。在 Turi Create 中存储数据的主要对象是 SFrame。我们首先使用以下命令将数据下载到 SFrame 中:

data = tc.SFrame('Hyderabad.csv')

表格太大,但你可以看到表 3.3 中的前几行和列。

表 3.3 海得拉巴房价数据集的前五行和七列

价格 面积 卧室数量 二手房 维护人员 健身房 游泳池
30000000 3340 4 0 1 1 1
7888000 1045 2 0 0 1 1
4866000 1179 2 0 0 1 1
8358000 1675 3 0 0 0 0
6845000 1670 3 0 1 1 1

在 Turi Create 中训练线性回归模型只需要一行代码。我们使用来自线性回归包的 create 函数。在这个函数中,我们只需要指定目标(标签),即价格,如下所示:

model = tc.linear_regression.create(data, target='Price')

训练可能需要一些时间,但训练完成后,它会输出一些信息。其中之一是均方根误差。对于这个模型,RMSE 的数量级为 3,000,000。这是一个较大的 RMSE,但这并不意味着模型做出了糟糕的预测。它可能意味着数据集中有许多异常值。正如你可以想象的那样,房价可能取决于数据集中没有的其他许多特征。

我们可以使用这个模型来预测面积为 1,000 平方米,有三个卧室的房子的价格如下:

house = tc.SFrame({'Area': [1000], 'No. of Bedrooms':[3]})
model.predict(house)
Output: 2594841

模型输出,面积为 1,000 平方米,有三个卧室的房子的价格为 2,594,841。

我们也可以使用较少的特征来训练模型。create 函数允许我们输入作为数组想要使用的特征。以下代码行训练了一个名为 simple_model 的模型,它使用面积来预测价格:

simple_model = tc.linear_regression.create(data, features=['Area'], target='Price')

我们可以使用以下代码行来探索这个模型的权重:

simple_model.coefficients

输出给出了以下权重:

  • 斜率:9664.97

  • y-截距:-6,105,981.01

截距是偏差,面积系数是当我们在面积和价格上绘制时线的斜率。点的绘制与相应模型的关系如图 3.24 所示。

图 3.24 针对面积和价格限制的海得拉巴房价数据集。这条线是我们仅使用面积特征来预测价格所得到的模型。

我们可以在这个数据集中做很多事情,我邀请你继续探索。例如,通过查看模型的权重来探索哪些特征比其他特征更重要。我鼓励你查看 Turi Create 文档([apple.github.io/turicreate/docs/api/](https://apple.github.io/turicreate/docs/api/)),了解你可以执行的其他函数和技巧来改进这个模型。

如果数据不是线性的呢?多项式回归

在前面的章节中,我们学习了如何在数据接近直线的情况下找到最佳拟合线。但如果我们发现数据并不呈线性呢?在本节中,我们将学习一种称为多项式回归的强大扩展,它帮助我们处理数据更复杂的情况。

一种特殊的曲线函数:多项式

要学习多项式回归,首先我们需要了解什么是多项式。多项式是一类在模拟非线性数据时很有用的函数。

我们已经看到过多项式了,因为每条线都是 1 次多项式。抛物线是 2 次多项式的例子。形式上,多项式是一个可以表示为该变量幂的倍数之和的函数。变量x的幂是 1,xx²,x³,……。请注意,前两个是x⁰ = 1 和x¹ = x。因此,以下都是多项式的例子:

  • y = 4

  • y = 3x + 2

  • y = x² – 2x + 5

  • y = 2x³ + 8x² – 40

我们定义多项式的次数为多项式表达式中最高次幂的指数。例如,多项式y = 2x³ + 8x² – 40 的次数是 3,因为 3 是变量x被提升到的最高指数。请注意,在例子中,多项式的次数是 0,1,2 和 3。0 次多项式始终是一个常数,1 次多项式是我们在本章之前看到的线性方程。

多项式的图像看起来很像振荡几次的曲线。振荡的次数与多项式的次数有关。如果一个多项式的次数是d,那么该多项式的图像是一条最多振荡d – 1 次(对于d > 1)的曲线。在图 3.25 中,我们可以看到一些多项式示例的图像。

图 3.25 多项式是有助于我们更好地模拟数据的函数。以下是 0 到 3 次多项式的图像。请注意,0 次多项式是一条水平线,1 次多项式是任何一条直线,2 次多项式是抛物线,而 3 次多项式是一条振荡两次的曲线。

从图中可以看出,0 次多项式是平坦的直线。1 次多项式是斜率不为 0 的直线。2 次多项式是二次函数(抛物线)。3 次多项式看起来像是一条振荡两次的曲线(尽管它们可能振荡的次数更少)。一个 100 次多项式的图像会是什么样的呢?例如,y = x¹⁰⁰ – 8x⁶² + 73x²⁷ – 4x + 38 的图像?我们得画出来才能知道,但可以肯定的是,它是一条最多振荡 99 次的曲线。

非线性数据?没问题:让我们尝试将其拟合到多项式曲线上

在本节中,我们来看一下如果我们的数据不是线性的(即,看起来不像形成一条线),并且我们想要将其拟合到多项式曲线上会发生什么。假设我们的数据看起来像图 3.26 的左侧。无论我们尝试多少次,我们都无法真正找到一个适合这些数据的良好直线。没问题!如果我们决定拟合一个 3 次多项式(也称为三次多项式),那么我们就会得到图 3.26 右侧的曲线,它比数据拟合得更好。

图 3.26 多项式回归在建模非线性数据时很有用。如果我们的数据看起来像图中的左侧部分,那么很难找到一条很好地拟合它的线。然而,正如你可以从图的右侧部分看到的那样,一条曲线可以很好地拟合数据。多项式回归帮助我们找到这条曲线。

训练多项式回归模型的过程与训练线性回归模型的过程类似。唯一的区别是我们需要在应用线性回归之前向我们的数据集中添加更多列。例如,如果我们决定将图 3.26 中的数据拟合到 3 次多项式,我们需要添加两列:一列对应特征的平方,另一列对应特征的立方。如果您想更详细地研究这个问题,请查看第四章中的“使用 Turi Create 进行多项式回归、测试和正则化”部分,其中我们学习了一个在抛物线数据集中进行多项式回归的例子。

在训练多项式回归模型时有一个小的注意事项,那就是我们必须在训练过程之前决定多项式的度数。我们如何决定这个度数?我们想要一条线(度数 1)、一个抛物线(度数 2)、一个立方(度数 3),还是某个 50 度的曲线?这个问题很重要,我们将在第四章中处理它,当我们学习过拟合、欠拟合和正则化时!

参数和超参数

参数和超参数是机器学习中一些最重要的概念,在本节中,我们将学习它们是什么以及如何区分它们。

正如我们在本章中看到的,回归模型由其权重和偏差定义——即模型的参数。然而,在训练模型之前,我们可以调整许多其他旋钮,例如学习率、迭代次数、度(如果考虑多项式回归模型),以及许多其他。这些被称为超参数

在本书中,我们学习的每个机器学习模型都有一些定义良好的参数和超参数。它们往往容易混淆,所以区分它们的经验法则是:

  • 在训练过程之前设置的任何数量都是超参数。

  • 在训练过程中创建或修改的任何数量都是参数。

回归的应用

机器学习的影响不仅在于其算法的强大,还在于其有用应用的广泛性。在本节中,我们将看到线性回归在现实生活中的应用。在每个例子中,我们概述问题,学习一些特征来解决它,然后让线性回归施展其魔法。

推荐系统

机器学习被广泛应用于生成一些最知名应用中的良好推荐,包括 YouTube、Netflix、Facebook、Spotify 和 Amazon。在这些推荐系统中,回归扮演着关键角色。因为回归预测一个数量,我们生成良好推荐所需要做的只是找出哪个数量最能指示用户互动或用户满意度。以下是一些更具体的例子。

视频和音乐推荐

生成视频和音乐推荐的方法之一是预测用户将观看视频或听歌曲的时间长度。为此,我们可以创建一个线性回归模型,其中数据的标签是每个用户观看每首歌曲的分钟数。特征可以是关于用户的统计数据,例如他们的年龄、位置和职业,但它们也可以是行为数据,例如他们点击或与之互动的其他视频或歌曲。

产品推荐

商店和电子商务网站也使用线性回归来预测其销售额。一种方法是预测顾客在商店的花费。我们可以使用线性回归来完成这项工作。要预测的标签可以是用户花费的金额,特征可以是人口统计和行为数据,类似于视频和音乐推荐。

医疗保健

回归在医疗保健中有许多应用。根据我们想要解决的问题,预测正确的标签是关键。以下是一些例子:

  • 基于患者的当前健康状况预测其寿命

  • 根据当前症状预测住院时间长度

摘要

  • 回归是机器学习的一个重要部分。它包括使用标签数据训练算法,并使用它来对未来(未标记)数据进行预测。

  • 标签数据是带有标签的数据,在回归的情况下,这些标签是数字。例如,这些数字可以是房价。

  • 在数据集中,特征是我们用来预测标签的性质。例如,如果我们想预测房价,特征就是描述房屋并可能决定价格的一切,例如大小、房间数量、学校质量、犯罪率、房屋年龄以及到高速公路的距离。

  • 用于预测的线性回归方法在于为每个特征分配一个权重,并将相应的权重乘以特征,再加上一个偏差。

  • 从图形上看,我们可以将线性回归算法视为尝试通过一条线尽可能接近一组点。

  • 线性回归算法的工作方式是先从一个随机的线开始,然后逐渐将其移动到每个被错误分类的点附近,以尝试正确分类它们。

  • 多项式回归是线性回归的推广,其中我们使用曲线而不是直线来模拟我们的数据。这在我们的数据集是非线性的情况下特别有用。

  • 回归有众多应用,包括推荐系统、电子商务和医疗保健。

练习

练习 3.1

一个网站已经训练了一个线性回归模型来预测用户将在网站上花费的分钟数。他们获得的公式是

= 0.8d + 0.5m + 0.5y + 0.2a + 1.5

其中 是预测的分钟数,而 dmya 是指示变量(即,它们只取 0 或 1 的值),定义如下:

  • d 是一个变量,表示用户是否在台式电脑上。

  • m 是一个变量,表示用户是否在移动设备上。

  • y 是一个变量,表示用户是否年轻(21 岁以下)。

  • a 是一个变量,表示用户是否为成年人(21 岁或以上)。

示例:如果用户 30 岁且使用台式电脑,则d = 1,m = 0,y = 0,a = 1。

如果一个 45 岁的用户从手机上查看网站,他们预计会在网站上花费多少时间?

练习 3.2

假设我们在一个医学数据集上训练了一个线性回归模型。该模型预测患者的预期寿命。对于数据集中的每个特征,模型都会分配一个权重。

a) 对于以下数量,说明你认为附加到该数量的权重是正数、负数还是零。注意:如果你认为权重是一个非常小的数,无论是正数还是负数,你可以说零。

  1. 患者每周锻炼的小时数

  2. 患者每周吸烟的数量

  3. 有心脏问题的家庭成员数量

  4. 患者的兄弟姐妹数量

  5. 患者是否曾住院

b) 该模型也有偏差。你认为偏差是正数、负数还是零?

练习 3.3

以下是一个包含房屋尺寸(平方英尺)和价格(美元)的数据集。

尺寸 (s) 奖金 (p)
房屋 1 100 200
房屋 2 200 475
房屋 3 200 400
房屋 4 250 520
房屋 5 325 735

假设我们已经训练了模型,其中基于尺寸预测房价如下:

= 2s + 50

  1. 计算此模型在数据集上的预测结果。

  2. 计算此模型的平均绝对误差。

  3. 计算此模型的均方根误差。

练习 3.4

我们的目标是使用本章学到的技巧将方程 ŷ = 2x + 3 的线移动得更接近点 (x, y) = (5, 15)。对于以下两个问题,使用学习率 η = 0.01。

  1. 将上面的线通过绝对值技巧修改得更接近点。

  2. 将上面的线通过平方技巧修改得更接近点。

4 优化训练过程:欠拟合、过拟合、测试和正则化

在本章中

  • 什么是欠拟合和过拟合

  • 避免过拟合的一些解决方案:测试、模型复杂度图和正则化

  • 使用 L1 和 L2 范数计算模型的复杂度

  • 在性能和复杂度方面选择最佳模型

图片

本章与本书中的大多数章节不同,因为它不包含特定的机器学习算法。相反,它描述了机器学习模型可能遇到的一些潜在问题以及有效的实际解决方法。

想象一下,你已经学习了一些优秀的机器学习算法,并且准备将它们应用到实践中。你作为一名数据科学家开始工作,你的第一个任务是针对客户数据集构建一个机器学习模型。你构建了模型并将其投入生产。然而,一切都不顺利,模型在预测方面表现不佳。发生了什么?

结果表明,这个故事很常见,因为我们的模型可能会出现很多问题。幸运的是,我们有几种技术来改进它们。在本章中,我向你展示了在训练模型时经常出现的两个问题:欠拟合和过拟合。然后,我展示了避免欠拟合和过拟合模型的一些解决方案:测试和验证、模型复杂度图和正则化。

让我们用以下类比来解释欠拟合和过拟合。假设我们必须为考试做准备。在学习过程中可能会出现很多问题。也许我们没有学习足够的内容。这是无法修复的,我们很可能会在考试中表现不佳。如果我们学习了很多,但方式错误呢?例如,我们决定逐字逐句地记住整本教科书,而不是专注于学习。我们会在考试中表现得好吗?很可能会不好,因为我们只是简单地记住了所有内容而没有真正学习。当然,最好的选择是以正确的方式为考试做准备,并且以能够回答我们之前没有见过的关于该主题的新问题。

在机器学习中,欠拟合很像考试前没有学习足够的内容。它发生在我们试图训练一个过于简单的模型,而这个模型无法学习数据。过拟合则很像为了考试而记住整本教科书而不是学习。它发生在我们试图训练一个过于复杂的模型,而这个模型只是记住数据而不是真正学习它。一个好的模型,既不过拟合也不欠拟合,就像考试前已经很好地学习了。这对应于一个能够正确学习数据并在未见过的新的数据上做出良好预测的好模型。

另一种思考欠拟合和过拟合的方式是当我们有一个任务在手。我们可以犯两个错误。我们可以过度简化问题,并提出一个过于简单的解决方案。我们也可以过度复杂化问题,并提出一个过于复杂的解决方案。

想象一下,如果我们的任务是杀死图 4.1 中所示的哥斯拉,而我们只带着苍蝇拍来战斗。这是一个过度简化的例子。这种方法对我们来说不会顺利,因为我们低估了问题,并且没有做好准备。这就是欠拟合:我们的数据集很复杂,而我们只带着一个简单的模型来建模。该模型将无法捕捉数据集的复杂性。

相比之下,如果我们任务是杀死一只小苍蝇,而我们使用火箭筒来完成这项工作,这就是一个过度复杂化的例子。是的,我们可能会杀死苍蝇,但也会摧毁所有手头的东西,并使自己处于危险之中。我们高估了问题,我们的解决方案并不好。这就是过拟合:我们的数据很简单,但我们试图将其拟合到一个过于复杂的模型中。该模型将能够拟合我们的数据,但它会记住它而不是学习它。我第一次学习过拟合时,我的反应是,“嗯,这没什么问题。如果我使用一个过于复杂的模型,我仍然可以对我的数据进行建模,对吧?”正确,但过拟合的真正问题是试图让模型对未见过的数据进行预测。预测结果可能会非常糟糕,正如我们在本章后面看到的。

图片

图 4.1 欠拟合和过拟合是在训练我们的机器学习模型时可能出现的两个问题。左:当我们将手头的问题过度简化,并试图用简单的解决方案来解决它时,就会发生欠拟合,例如试图用苍蝇拍杀死哥斯拉。右:当我们过度复杂化问题的解决方案,并试图用极其复杂的解决方案来解决它时,就会发生过拟合,例如试图用火箭筒杀死一只苍蝇。

正如我们在第三章的“参数和超参数”部分中看到的,每个机器学习模型都有超参数,这些是我们训练模型之前可以旋转和调整的旋钮。为我们的模型设置正确的超参数至关重要。如果我们设置了一些错误的参数,我们很容易出现欠拟合或过拟合。本章中介绍的技术有助于我们正确调整超参数。

为了使这些概念更清晰,我们将通过一个数据集和几个不同的模型来举例说明,这些模型是通过改变一个特定的超参数创建的:多项式的度数。

你可以在以下 GitHub 仓库中找到本章的所有代码:github.com/luisguiserrano/manning/tree/master/Chapter_4_Testing_Overfitting_Underfitting

使用多项式回归的欠拟合和过拟合的例子

在本节中,我们看到了同一数据集中过拟合和欠拟合的例子。仔细观察图 4.2 中的数据集,并尝试拟合一个多项式回归模型。

(见第三章“如果数据不在一条线上怎么办?”部分)。让我们思考一下什么样的多项式可以拟合这个数据集。会是直线、抛物线、三次多项式,或者可能是 100 阶多项式?回想一下,多项式的次数是最高次幂。例如,多项式 2x¹⁴ + 9x⁶ – 3x + 2 的次数是 14。

图 4.2 在这个数据集中,我们训练了一些模型,并展示了训练问题,如欠拟合和过拟合。如果你要拟合这个数据集的多项式回归模型,你会使用什么类型的多项式:直线、抛物线,还是其他?

我认为这个数据集看起来很像一个开口向下的抛物线(一个悲伤的面孔)。这是一个二阶多项式。然而,我们是人类,我们只能凭肉眼判断。计算机无法做到这一点。计算机需要尝试多项式次数的许多值,并从中选择最佳的一个。假设计算机将尝试用一阶、二阶和十阶多项式来拟合它。当我们用一阶(直线)、二阶(二次)和十阶(最多振荡九次)多项式拟合这个数据集时,我们得到了图 4.3 中所示的结果。

图 4.3 将三个模型拟合到同一数据集。模型 1 是一阶多项式,即一条直线。模型 2 是二阶多项式,或称二次多项式。模型 3 是十阶多项式。哪一个看起来像是最佳拟合?

在图 4.3 中,我们看到三个模型,模型 1、模型 2 和模型 3。注意,模型 1 过于简单,因为它试图用一条直线拟合一个二次数据集。我们不可能找到一条好的直线来拟合这个数据集,因为这个数据集根本不像一条直线。因此,模型 1 是欠拟合的一个明显例子。相比之下,模型 2 很好地拟合了数据。这个模型既不过拟合也不欠拟合。模型 3 非常完美地拟合了数据,但它完全错过了重点。数据集应该看起来像一个带有一些噪声的抛物线,而该模型绘制了一个非常复杂、十次方的多项式,它设法通过了每一个点,但没有捕捉到数据的本质。模型 3 是过拟合的一个明显例子。

为了总结前面的推理,这里有一个我们在本章以及本书许多其他章节中使用的观察结果:非常简单的模型往往欠拟合。非常复杂的模型往往过拟合。目标是找到一个既不太简单也不太复杂,并且能够很好地捕捉我们数据本质的模型。

我们即将进入挑战性的部分。作为人类,我们知道最佳拟合由模型 2 提供。但计算机看到的是什么?计算机只能计算误差函数。你可能还记得第三章中我们定义了两个误差函数:绝对误差和平方误差。为了视觉清晰,在这个例子中我们将使用绝对误差,即点到曲线的距离绝对值的总和的平均值,尽管相同的论点也可以用于平方误差。对于模型 1,点远离模型,因此这个误差很大。对于模型 2,这些距离很小,所以误差很小。然而,对于模型 3,距离为零,因为所有点都落在实际曲线上!这意味着计算机将认为完美的模型是模型 3。这并不好。我们需要一种方法告诉计算机最佳模型是模型 2,而模型 3 是过度拟合。我们该如何做呢?我鼓励你在几分钟内放下这本书,自己思考一些想法,因为这个问题有几种解决方案。

我们如何让计算机选择正确的模型?通过测试

确定一个模型是否过度拟合的一种方法是通过测试,这正是我们在本节中要做的。测试一个模型包括从数据集中选择一小部分点,并选择不使用它们来训练模型,而是用来测试模型的表现。这个点集被称为测试集。剩余的点集(大多数),我们用它来训练模型,被称为训练集。一旦我们在训练集上训练了模型,我们就使用测试集来评估模型。这样,我们确保模型擅长泛化到未见过的数据,而不是记住训练集。回到考试的类比,让我们想象以这种方式进行培训和测试。假设我们为考试准备的书中最后有 100 个问题。我们选择 80 个来训练,这意味着我们学习

仔细阅读,查找答案,并学习它们。然后我们使用剩下的 20 个问题来测试自己——我们尝试在不看书的情况下回答它们,就像在考试环境中一样。

现在,让我们看看这种方法在我们的数据集和模型中是如何表现的。请注意,模型 3 的真正问题不在于它不拟合数据;而在于它对新数据的泛化能力不好。换句话说,如果你在数据集上训练了模型 3,并且出现了新的点,你会信任模型用这些新点做出良好的预测吗?可能不会,因为模型只是记住了整个数据集而没有抓住其本质。在这种情况下,数据集的本质是它看起来像向下开口的抛物线。

在图 4.4 中,我们在我们的数据集中画了两个白色三角形,代表测试集。训练集对应的是黑色圆圈。现在让我们详细检查这个图,看看这三个模型在训练集和测试集上的表现如何。换句话说,让我们检查模型在这两个数据集中产生的误差。我们将这两个误差称为训练误差测试误差

图 4.4 的顶部行对应于训练集,底部行对应于测试集。为了说明误差,我们从点到模型画了垂直线。平均绝对误差就是这些线长度的平均值。看顶部行,我们可以看到模型 1 有大的训练误差,模型 2 有小的训练误差,而模型 3 有极小的训练误差(实际上为零)。因此,模型 3 在训练集上做得最好。

图 4.4 我们可以使用这个表格来决定我们希望模型有多复杂。列代表三个模型,分别是 1 次、2 次和 10 次方的模型。列代表训练误差和测试误差。实心圆圈是训练集,白色三角形是测试集。每个点的误差可以看作是从点到曲线的垂直线。每个模型的误差是这些垂直长度的平均值给出的平均绝对误差。注意,随着模型复杂度的增加,训练误差会下降。然而,测试误差在增加后会下降然后再上升。从这个表中,我们得出结论,在这三个模型中,最好的一个是模型 2,因为它给我们带来了低的测试误差。

然而,当我们到达测试集时,情况发生了变化。模型 1 仍然有大的测试误差,这意味着这只是一个坏模型,在训练集和测试集上都表现不佳:它欠拟合。模型 2 有小的测试误差,这意味着它是一个好模型,因为它很好地拟合了训练集和测试集。然而,模型 3 却产生了大的测试误差。因为它在拟合测试集上做得如此糟糕,而在拟合训练集上做得如此好,我们得出结论,模型 3 过拟合了。

让我们总结一下到目前为止我们已经学到的内容。

模型可以

  • 欠拟合:使用一个对我们数据集来说过于简单的模型。

  • 良好拟合数据:使用一个对我们数据集来说复杂度合适的模型。

  • 过拟合:使用一个对我们数据集来说过于复杂的模型。

在训练集中

  • 欠拟合模型表现不佳(训练误差大)。

  • 良好的模型表现良好(训练误差小)。

  • 过拟合模型表现非常好(训练误差非常小)。

在测试集中

  • 欠拟合模型表现不佳(测试误差大)。

  • 良好的模型表现良好(测试误差小)。

  • 过拟合模型表现不佳(测试误差大)。

因此,判断一个模型是否欠拟合、过拟合或表现良好,是查看训练集和测试集的错误。如果两个错误都高,那么它欠拟合。如果两个错误都低,那么它是一个好的模型。如果训练错误低而测试错误高,那么它过拟合。

我们如何选择测试集,它应该有多大?

这里有一个问题。我从哪里得到这两个新点?如果我们在一个数据始终流动的生产环境中训练模型,那么我们可以选择一些新点作为我们的测试数据。但如果我们没有获取新点的方法,而我们只有 10 个点的原始数据集呢?当这种情况发生时,我们就牺牲一些数据,将其用作测试集。需要多少数据?这取决于我们有多少数据以及我们希望模型做得有多好,但在实践中,任何从 10%到 20%的值似乎都工作得很好。

我们可以使用测试数据来训练模型吗?不可以。

在机器学习中,我们始终需要遵循一个重要的规则:当我们把数据分成训练集和测试集时,我们应该使用训练数据来训练模型,而且在训练模型或对模型的超参数做出决策时,绝对不应该触碰测试数据。未能这样做很可能会导致过拟合,甚至

如果人类没有注意到。在许多机器学习竞赛中,团队提交了他们认为很棒的模型,但它们在测试一个秘密数据集时却惨败。这可能是因为训练模型的数据科学家(可能是无意中)使用了测试数据来训练它们。事实上,这条规则非常重要,我们将把它作为本书的金科玉律。

金科玉律 你绝不应该使用你的测试数据来训练。

现在,这似乎是一个容易遵循的规则,但正如我们将看到的,这是一个很容易意外违反的规则。

实际上,我们在本章中已经违反了金科玉律。你能告诉我在哪里吗?我鼓励你回去找到我们违反规则的地方。我们将在下一节中看到。

我们在哪里违反了金科玉律,以及我们如何纠正它?验证集

在本节中,我们看到我们违反了金科玉律,并学习了一种称为验证的技术,它将帮助我们解决问题。

我们在“我们如何让计算机选择正确的模型”这一节中违反了金科玉律。回想一下,我们有三个多项式回归模型:一个是 1 次方,一个是 2 次方,还有一个是 10 次方,我们不知道该选择哪一个。我们使用训练数据来训练这三个模型,然后使用测试数据来决定选择哪个模型。我们不应该使用测试数据来训练我们的模型或对其或其超参数做出任何决策。一旦我们这样做,我们就有可能过拟合!每次我们构建一个过度适应数据集的模型时,我们都有可能过拟合。

我们能做什么?解决方案很简单:我们将数据集进一步分割。我们引入一个新的集合,即验证集,然后我们使用它来对我们的数据集做出决策。总的来说,我们将数据集分割成以下三个集合:

  • 训练集:用于训练所有我们的模型

  • 验证集:用于决定使用哪个模型

  • 测试集:用于检查我们的模型表现如何

因此,在我们的例子中,我们会增加两个用于验证的点,查看验证错误应该有助于我们决定使用最佳的模型是模型 2。我们应在最后使用测试集来查看模型的表现。如果模型不好,我们应该丢弃一切,从头开始。

在测试集和验证集的大小方面,通常使用 60-20-20 分割或 80-10-10 分割——换句话说,60%用于训练,20%用于验证,20%用于测试,或者 80%用于训练,10%用于验证,10%用于测试。这些数字是任意的,但它们通常效果不错,因为它们为训练保留了大部分数据,但仍然允许我们在足够大的数据集上测试模型。

决定模型复杂度的数值方法:模型复杂度图

在前面的章节中,我们学习了如何使用验证集来帮助我们决定在三个不同的模型中哪个是最好的。在本节中,我们将了解一个称为模型复杂度图的图表,它帮助我们决定在更多模型中的选择。想象一下,我们有一个不同且复杂得多的数据集,我们正在尝试构建一个多项式回归模型来拟合它。我们想要决定模型度数在 0 到 10(包含)之间的哪个数值。正如我们在前面的章节中看到的,决定使用哪个模型的方法是选择具有最小验证错误的模型。

然而,绘制训练和测试错误图可以给我们提供一些有价值的信息,并帮助我们检查趋势。在图 4.5 中,你可以看到一个图表,其中水平轴代表模型中多项式的度数,垂直轴代表错误的值。菱形代表训练错误,圆圈代表验证错误。这就是模型复杂度图。

图 4-5

图 4.5 模型复杂度图是帮助我们确定模型理想复杂度的有效工具,以避免欠拟合和过拟合。在这个模型复杂度图中,水平轴表示几个多项式回归模型的程度,从 0 到 10(即模型的复杂度)。垂直轴表示错误,在这种情况下是由平均绝对误差给出的。请注意,随着我们向右移动,训练错误开始很大,然后减小。这是因为我们的模型越复杂,它就能更好地拟合训练数据。然而,验证错误开始很大,然后减小,然后再次增加——非常简单的模型无法很好地拟合我们的数据(它们欠拟合),而非常复杂的模型可以拟合我们的训练数据,但不能拟合我们的验证数据,因为它们过拟合。中间的一个快乐点是我们模型既不欠拟合也不过拟合,我们可以使用模型复杂度图找到它。

注意,在图 4.5 中的模型复杂度图中,验证错误的最低值出现在程度 4 处,这意味着对于这个数据集,最佳拟合模型(在我们考虑的模型中)是一个四次多项式回归模型。查看图的左侧,我们可以看到当多项式的程度较小时,训练和验证错误都很大,这表明模型欠拟合。查看图的右侧,我们可以看到训练错误越来越小,但验证错误越来越大,这表明模型过拟合。最佳点发生在大约 4 的位置,这是我们选择的模型。

模型复杂度图的优点之一是,无论我们的数据集有多大,或者我们尝试了多少不同的模型,它总是看起来像两条曲线:一条始终下降(训练错误)和一条下降后再次上升(验证错误)。当然,在一个大而复杂的数据集中,这些曲线可能会波动,行为可能更难发现。然而,模型复杂度图始终是数据科学家寻找图中良好位置并决定模型复杂度以避免欠拟合和过拟合的有用工具。

为什么我们需要这样的图表,如果我们只需要选择具有最低验证错误的模型呢?这种方法在理论上是正确的,但在实践中,作为一名数据科学家,你可能对你的问题、约束条件和基准有更深入的了解。例如,如果你看到具有最小验证错误的模型仍然相当复杂,而有一个更简单的模型,其验证错误仅略高,你可能更倾向于选择后者。一位伟大的数据科学家能够将理论工具与他们对用例的知识相结合,构建出最佳且最有效的模型。

避免过拟合的另一种替代方案:正则化

在本节中,我们讨论另一种有用的技术,用于避免模型过拟合,而无需测试集:正则化。正则化依赖于我们在“使用多项式回归的欠拟合和过拟合示例”部分中做出的相同观察,我们得出结论,简单模型往往欠拟合,而复杂模型往往过拟合。然而,在先前的方法中,我们测试了多个模型,并选择了性能和复杂性最佳平衡的那个模型。相比之下,当我们使用正则化时,我们不需要训练多个模型。我们只需训练一次模型,但在训练过程中,我们不仅要提高模型性能,还要降低其复杂性。做到这一点的关键是同时衡量性能和复杂性。

在我们深入细节之前,让我们讨论一个用于思考衡量模型性能和复杂性的类比。想象我们有三座房子,它们都存在相同的问题——屋顶漏水(图 4.6)。三位屋顶工来修理,每人修理一座房子。第一位屋顶工使用绷带,第二位使用屋顶瓦片,第三位使用钛。从我们的直觉来看,似乎第二位屋顶工是最好的,因为第一位屋顶工过于简化了问题(欠拟合),而第三位屋顶工过于复杂化(过拟合)。

图片

图 4.6 欠拟合和过拟合的类比。我们的问题是一个破损的屋顶。我们有三位可以修理它的屋顶工。屋顶工 1 带着绷带来,屋顶工 2 带着屋顶瓦片来,屋顶工 3 带着一块钛。屋顶工 1 过于简化了问题,因此代表欠拟合。屋顶工 2 使用了良好的解决方案。屋顶工 3 过于复杂化了解决方案,因此代表过拟合。

然而,我们需要用数字来做出决定,所以让我们做一些测量。衡量屋顶工性能的方法是他们在修理屋顶后漏水量有多少。他们的评分如下:

性能(以漏水量计)

Roofer 1: 1000 mL 水

Roofer 2: 1 mL 水

Roofer 3: 0 mL 水

看起来屋顶工 1 的表现很糟糕,因为屋顶仍在漏水。然而,在屋顶工 2 和 3 之间,我们选择哪一个?也许屋顶工 3,因为他的表现更好?性能指标还不够好;它正确地排除了屋顶工 1,但它错误地告诉我们选择屋顶工 3,而不是屋顶工 2。我们需要一个衡量他们复杂性的指标来帮助我们做出正确的决定。衡量他们复杂性的一个好指标是他们修理屋顶所收取的费用,以美元计。价格如下:

复杂性(以价格计)

Roofer 1: $1

Roofer 2: $100

Roofer 3: $100,000

现在我们可以看出屋顶工 2 比屋顶工 3 更好,因为他们有相同的表现,但屋顶工 2 收费更低。然而,屋顶工 1 是最便宜的——为什么我们不选择这个呢?看起来我们需要做的是结合性能和复杂度的度量。我们可以加上屋顶漏水的量和价格,得到以下结果:

性能 + 复杂度

屋顶工 1:1001

屋顶工 2:101

屋顶工 3:100,000

现在很明显,屋顶工 2 是最好的,这意味着同时优化性能和复杂度可以得到既好又尽可能简单的结果。这就是正则化的目的:用两个不同的误差函数来衡量性能和复杂度,并将它们相加得到一个更鲁棒的误差函数。这个新的误差函数确保我们的模型表现良好且不太复杂。在接下来的章节中,我们将更详细地介绍如何定义这两个误差函数。但在那之前,让我们看看另一个过拟合的例子。

另一个过拟合的例子:电影推荐

在本节中,我们学习了一种更微妙的方式,模型可能会过拟合——这次与多项式的度数无关,而是与特征的数量和系数的大小有关。想象一下,我们有一个电影流媒体网站,我们正在尝试构建一个推荐系统。为了简单起见,假设我们只有 10 部电影:M1, M2, …, M10。一部新电影 M11 出现了,我们希望基于之前的 10 部电影构建一个线性回归模型来推荐电影 11。我们有一个 100 个用户的数据库。对于每个用户,我们有 10 个特征,这些特征是用户观看每部原始 10 部电影的时间(以秒为单位)。如果用户没有观看电影,那么这个量就是 0。每个用户的标签是用户观看电影 11 的时间量。我们希望构建一个适合这个数据集的模型。鉴于模型是线性回归模型,预测用户将观看电影 11 的时间的方程是线性的,它看起来如下:

ŷ = w[1]x[1] + w[2]x[2] + w[3]x[3]+ w[4]x[4] + w[5]x[5] + w[6]x[6] + w[7]x[7] + w[8]x[8] ++ w[9]x[9] + w[10]x[10] + b

其中

  • ŷ 是模型预测用户将观看电影 11 的时间量,

  • x[i] 是用户观看电影 i 的时间量,对于 i = 1, 2, …, 10,

  • w[i] 是与电影 i 相关的权重,并且

  • b 是偏差。

现在我们来测试我们的直觉。在以下两个模型(由它们的方程给出)中,哪一个(或哪几个)看起来可能存在过拟合?

模型 1ŷ = 2x[3] + 1.4x[7] – 0.5x[7] + 4

模型 2ŷ = 22x[1] – 103x[2] – 14x[3] + 109x[4] – 93x[5] + 203x[6] + 87x[7] – 55x[8] + 378x[9] – 25x[10] + 8

如果你像我一样思考,模型 2 似乎有点复杂,它可能是过度拟合的。这里的直觉是,用户观看电影 2 的时间乘以-103 然后加到其他数字上以获得预测是不太可能的。这可能很好地拟合数据,但它肯定看起来像是在记忆数据而不是学习数据。

相比之下,模型 1 看起来要简单得多,并且它提供了一些有趣的信息。从大多数系数为零的事实来看,除了电影 3、7 和 9 的系数外,它告诉我们与电影 11 相关的只有这三部电影。此外,从电影 3 和 7 的系数为正的事实来看,模型告诉我们,如果一个用户观看了电影 3 或电影 7,那么他们很可能也会观看电影 11。因为电影 9 的系数为负,所以如果一个用户观看了电影 9,他们不太可能观看电影 11。

我们的目标是拥有像模型 1 这样的模型,并避免像模型 2 这样的模型。但不幸的是,如果模型 2 产生的误差比模型 2 小,那么运行线性回归算法将选择模型 2。我们该怎么办?这正是正则化发挥作用的地方。我们首先需要的是一个告诉我们模型 2 比模型 1 复杂得多的度量。

衡量模型复杂度的方法:L1 和 L2 范数

在本节中,我们学习了两种衡量模型复杂度的方法。但在那之前,让我们看看上一节中的模型 1 和模型 2,并尝试找出一些对模型 1 低而对模型 2 高的公式。

注意到具有更多系数或系数值更高的模型往往更复杂。因此,任何符合这一点的公式都可以工作,例如以下公式:

  • 系数的绝对值之和

  • 系数的平方和

第一个被称为L1 范数,第二个被称为L2 范数。它们源自一个更一般的L^P 空间理论,该理论以法国数学家亨利·勒贝格的名字命名。我们使用绝对值和平方来消除负系数;否则,大负数会与大的正数相抵消,我们可能会得到一个非常复杂模型的小值。

但在我们开始计算范数之前,有一个小技术问题:模型中的偏差不包括在 L1 和 L2 范数中。为什么?好吧,模型中的偏差正是我们期望用户在观看前 10 部电影都没有观看的情况下观看电影 11 的秒数。这个数字与模型的复杂度无关;因此,我们让它保持原样。模型 1 和 2 的 L1 范数计算如下。

回想一下,模型的方程如下:

模型 1ŷ = 2x[3] + 1.4x[7] – 0.5x[7] + 8

模型 2ŷ = 22x[1] – 103x[2] – 14x[3] + 109x[4] – 93x[5] + 203x[6] + 87x[7] – 55x[8] + 378x[9] – 25x[10] + 8

L1 范数:

  • 模型 1: |2| + |1.4| + |–0.5| = 3.9

  • 模型 2: |22| + |–103| + |–14| + |109| + |–93| + |203| + |87| + |–55| + |378| + |–25| = 1,089

L2 范数:

  • 模型 1: 2² + 1.4² + (–0.5)² = 6.21

  • 模型 2: 22² + (–103)² + (–14)² + 109² + (-93)² + 203² + 87² + (–55)² + 378² + (–25)² = 227,131

如预期,模型 2 的 L1 和 L2 范数都比模型 1 的相应范数大得多。

L1 和 L2 范数也可以通过对多项式的系数取绝对值之和或平方之和来计算,除了常数系数。让我们回到本章开头提到的例子,我们的三个模型分别是 1 次幂的多项式(一条直线)、2 次幂的多项式(一个抛物线)和 10 次幂的多项式(一个振荡 9 次曲线)。想象一下它们的公式如下:

  • 模型 1: ŷ = 2x + 3

  • 模型 2: ŷ = –x² + 6x – 2

  • 模型 3: ŷ = x⁹ + 4x⁸ – 9x⁷ + 3x⁶ – 14x⁵ – 2x⁴ – 9x³ + x² + 6x + 10

L1 和 L2 范数的计算如下:

L1 范数:

  • 模型 1: |2| = 2

  • 模型 2: |–1| + |6| = 7

  • 模型 3: |1| + |4| + |–9| + |3| + |–14| + |–2| + |–9| + |1| + |6| = 49

L2 范数:

  • 模型 1: 2² = 2

  • 模型 2: (–1)² + 6² = 37

  • 模型 3: 1² + 4² + (–9)² + 3² + (–14)² + (–2)² + (–9)² + 1² + 6² = 425

现在我们已经拥有了两种衡量模型复杂性的方法,让我们开始训练过程。

修改误差函数以解决问题:lasso 回归和岭回归

现在我们已经完成了大部分繁重的工作,我们将使用正则化训练一个线性回归模型。我们有两个衡量模型的标准:一个是性能的衡量(误差函数),另一个是复杂性的衡量(L1 或 L2 范数)。

记得在屋顶工人的类比中,我们的目标是找到一个既提供高质量又低复杂度的屋顶工人。我们通过最小化两个数的和来实现这一点:质量的衡量和复杂性的衡量。正则化就是将同样的原则应用到我们的机器学习模型上。为此,我们有两个量:回归误差和正则化项。

回归误差:衡量模型质量的一个指标。在这种情况下,它可以是我们在第三章中学到的绝对误差或平方误差。

正则化项:衡量模型复杂性的一个指标。它可以是指模型的 L1 或 L2 范数。

我们想要最小化的量,以找到一个既好又不复杂的模型,是修改后的误差,定义为这两个量的和,如下所示:

误差 = 回归误差 + 正则化项

正则化如此普遍,以至于根据使用的范数,模型本身有不同的名称。如果我们使用 L1 范数训练我们的回归模型,该模型被称为lasso 回归。Lasso 代表“最小绝对收缩和选择算子”。误差函数如下:

Lasso 回归误差 = 回归误差 + L1 范数

如果我们使用 L2 norm 来训练模型,它被称为 ridge regression。这个名字 ridge 来自于误差函数的形状,因为当我们绘制它时,将 L2 norm 项添加到回归误差函数中,将尖锐的角变成了平滑的谷。误差函数如下:

Ridge regression error = Regression error + L2 norm

Lasso 和 ridge regression 在实践中都表现良好。选择使用哪一个取决于我们将在接下来的章节中了解的一些偏好。但在我们到达那里之前,我们需要处理一些细节,以确保我们的正则化模型能够良好工作。

调整模型中的性能和复杂度:正则化参数

因为训练模型的过程涉及尽可能减少成本函数,所以原则上,使用正则化的模型应该具有高性能和低复杂度。然而,存在一些拉锯战——试图让模型表现更好可能会使其更复杂,而试图降低模型的复杂度可能会使其表现更差。幸运的是,大多数机器学习技术都为数据科学家提供了可调节的旋钮(超参数),以构建最佳模型,正则化也不例外。在本节中,我们看看如何使用超参数在性能和复杂度之间进行调节。

这个超参数被称为 正则化参数,其目标是确定模型训练过程是否应该强调性能或简单性。正则化参数用λ表示,即希腊字母 lambda。我们将正则化项乘以λ,加到回归误差上,并使用这个结果来训练我们的模型。新的误差如下:

Error = Regression error + λ Regularization term

将λ的值设为 0 会取消正则化项,因此我们最终得到与第三章中相同的回归模型。将λ的值设得很大会导致一个简单的模型,可能度数较低,可能不太适合我们的数据集。选择一个合适的λ值至关重要,为此,验证是一种有用的技术。通常选择 10 的幂,如 10、1、0.1、0.01,但这种选择有些随意。在这些中,我们选择使我们的模型在验证集中表现最好的那个。

L1 和 L2 正则化对模型系数的影响

在本节中,我们将看到 L1 和 L2 正则化之间的关键差异,并得到一些在不同场景中使用哪一个的想法。乍一看,它们看起来很相似,但它们对系数产生的影响很有趣,而且,根据我们想要的模型类型,决定使用 L1 还是 L2 正则化可能是关键的。

让我们回到我们的电影推荐示例,其中我们正在构建一个回归模型来预测用户观看电影的时间(以秒为单位),给定该用户观看 10 部不同电影的时间。想象一下我们已经训练了模型,得到的方程如下:

模型ŷ = 22x[1] – 103x[2] – 14x[3] + 109x[4] – 93x[5] + 203x[6] + 87x[7] – 55x[8] + 378x[9] – 25x[10] + 8

如果我们添加正则化并再次训练模型,最终得到的模型将更简单。以下两个性质可以通过数学证明:

  • 如果我们使用 L1 正则化(lasso 回归),最终得到的模型将具有更少的系数。换句话说,L1 正则化将一些系数变为零。因此,我们可能会得到一个类似 ŷ = 2x[3] + 1.4x[7] – 0.5x[9] + 8 的方程。

  • 如果我们使用 L2 正则化(ridge 回归),最终得到的模型将具有更小的系数。换句话说,L2 正则化会缩小所有系数,但很少将它们变为零。因此,我们可能会得到一个类似 ŷ = 0.2x[1] – 0.8x[2] – 1.1x[3] + 2.4x[4] – 0.03x[5] + 1.02x[6] + 3.1x[7] – 2x[8] + 2.9x[9] – 0.04x[10] + 8 的方程。

因此,根据我们想要得到的方程类型,我们可以决定使用 L1 还是 L2 正则化。

当决定是否要使用 L1 或 L2 正则化时,可以遵循的一个快速规则是:如果我们有很多特征并且想要去除其中大部分,L1 正则化非常适合这种情况。如果我们只有很少的特征并且认为它们都相关,那么我们需要 L2 正则化,因为它不会去除我们的有用特征。

在“另一个过拟合示例:电影推荐”部分中,我们研究的一个问题中,我们有很多特征,L1 正则化可以帮助我们。在这个模型中,每个特征对应一部电影,我们的目标是找到与我们感兴趣的电影相关的少数几部电影。因此,我们需要一个大多数系数为零,只有少数几个系数不为零的模型。

我们应该使用 L2 正则化的一个例子是“使用多项式回归的欠拟合示例”部分开头的一个多项式示例。对于这个模型,我们只有一个特征:x。L2 正则化会给我们一个具有小系数的良好多项式模型,它不会振荡得很厉害,因此不太容易过拟合。在“使用 Turi Create 进行多项式回归、测试和正则化”部分,我们将看到一个适合使用 L2 正则化的多项式示例。

与本章(附录 C)相关的资源指向了一些地方,你可以深入挖掘为什么 L1 正则化将系数变为零,而 L2 正则化将它们变为小数的原因。在下一节中,我们将学习如何获得对此的直观理解。

一种直观地看待正则化的方式

在本节中,我们学习 L1 和 L2 范数在惩罚复杂度方面的不同。本节主要基于直观,并通过示例进行阐述,但如果你想看到它们背后的形式化数学,请参阅附录 B,“使用梯度下降进行正则化。”

当我们试图理解机器学习模型如何运作时,我们应该超越错误函数。错误函数会说,“这是错误,如果你减少它,你最终会得到一个好的模型。”但这就像说,“成功生活的秘诀是尽可能少犯错误。”一个积极的信息不是更好吗,比如,“这些都是你可以做的事情来改善你的生活”,而不是“这些都是你应该避免的事情”?让我们以这种方式看看正则化。

在第三章中,我们学习了绝对值和平方技巧,这让我们对回归有了更清晰的了解。在训练过程的每个阶段,我们只是选择一个点(或几个点),并将线移动到这些点附近。重复这个过程多次,最终会得到一个好的线拟合。我们可以更具体地重复第三章中定义的线性回归算法。

线性回归算法的伪代码

输入: 点的集合数据集

输出: 一个适合该数据集的线性回归模型

程序:

  • 选择一个具有随机权重和随机偏差的模型。

  • 重复多次:

    • 选择一个随机数据点。

    • 稍微调整权重和偏差以改善该数据点的预测。

  • 享受你的模型吧!

我们能否用同样的推理来理解正则化?是的,我们可以。

为了简化问题,让我们假设我们正处于训练过程中,我们想要使模型更简单。我们可以通过减少系数来实现这一点。为了简单起见,让我们假设我们的模型有三个系数:3、10 和 18。我们能否通过小幅度减少这三个系数?当然可以,这里有两种方法来做这件事。这两种方法都需要一个小的数字 λ,我们现在将其设置为 0.01。

方法 1: 从每个正参数中减去 λ,并将 λ 添加到每个负参数中。如果它们为零,则保持不变。

方法 2: 将所有参数乘以 1 – λ。注意这个数字接近 1,因为 λ 很小。

使用方法 1,我们得到数字 2.99、9.99 和 17.99。

使用方法 2,我们得到数字 2.97、9.9 和 17.82。

在这种情况下,λ 非常像学习率。事实上,它与正则化率密切相关(有关详细信息,请参阅附录 B 中的“使用梯度下降进行正则化”)。请注意,在这两种方法中,我们都在缩小系数的大小。现在,我们只需要在算法的每个阶段重复缩小系数。换句话说,这就是我们现在如何训练模型:

输入: 点的集合数据集

输出: 一个适合该数据集的线性回归模型

程序:

  • 选择一个具有随机权重和随机偏差的模型。

  • 重复多次:

    • 选择一个随机数据点。

    • 稍微调整权重和偏差以改善该特定数据点的预测。

    • 使用方法 1 或方法 2 略微缩小系数

  • 享受你的模型吧!

如果我们使用方法 1,我们正在用 L1 正则化或 lasso 回归训练模型。如果我们使用方法 2,我们正在用 L2 正则化或 ridge 回归训练它。对此有一个数学上的解释,在附录 B,“使用梯度下降进行正则化”中描述。

在上一节中,我们了解到 L1 正则化倾向于将许多系数变为 0,而 L2 正则化倾向于减小它们但不会将它们变为零。这一现象现在更容易看到。假设我们的系数是 2,正则化参数为λ = 0.01。注意如果我们使用方法 1 来缩小我们的系数,并且重复这个过程 200 次会发生什么。我们得到以下数值序列:

2 → 1.99 → 1.98 → ··· → 0.02 → 0.01 → 0

在我们的训练 200 个 epoch 之后,系数变为 0,并且再也没有变化。现在让我们看看如果我们再次应用方法 2,再进行 200 次,并且使用相同的学习率η = 0.01 会发生什么。我们得到以下数值序列:

2 → 1.98 → 1.9602 → ··· → 0.2734 → 0.2707 → 0.2680

注意到系数急剧下降,但并没有变成零。事实上,无论我们运行多少个 epoch,系数永远不会变成零。这是因为当我们多次将一个非负数乘以 0.99 时,这个数永远不会变成零。这如图 4.7 所示。

图 4.7 L1 和 L2 正则化都缩小了系数的大小。L1 正则化(左侧)做得更快,因为它减去一个固定值,所以它最终可能变成零。L2 正则化需要更长的时间,因为它将系数乘以一个很小的因子,所以它永远不会达到零。

使用 Turi Create 进行多项式回归、测试和正则化

在本节中,我们看到了 Turi Create 中带有正则化的多项式回归的示例。以下是本节的代码:

我们从我们的数据集开始,如图 4.8 所示。我们可以看到最适合这些数据的曲线是一个开口向下的抛物线(一个悲伤的脸)。因此,这不是我们可以用线性回归解决的问题——我们必须使用多项式回归。数据集存储在一个名为data的 SFrame 中,前几行如表 4.1 所示。

图 4.8 数据集。注意,它的形状是一个向下开口的抛物线,所以使用线性回归效果不好。我们将使用多项式回归来拟合这个数据集,并使用正则化来调整我们的模型。

表 4.1 我们数据集的前四行

x y
3.4442185152504816 6.685961311021467
–2.4108324970703663 4.690236225597948
0.11274721368608542 12.205789026637378
-1.9668727392107255 11.133217991032268

在 Turi Create 中进行多项式回归的方法是向我们的数据集中添加许多列,对应于主要特征的幂,然后对此扩展数据集应用线性回归。如果主要特征是,比如说,x,那么我们添加具有 x²、x³、x⁴ 等值的列。因此,我们的模型正在寻找 x 幂的线性组合,这恰好是 x 上的多项式。如果包含我们数据的 SFrame 被称为 data,我们使用以下代码添加到 x¹⁹⁹ 的幂的列。结果数据集的前几行和列显示在表 4.2 中。

for i in range(2,200):
    string = 'x^'+str(i)
    data[string] = data['x'].apply(lambda x:x**i)

表 4.2 我们数据集的前四行和最左边的五列。标记为 x^k 的列对应于变量 x^k,其中 k = 2、3 和 4。数据集有 200 列。

x y x⁴
3.445 6.686 11.863 40.858 140.722
–2.411 4.690 5.812 –14.012 33.781
0.113 12.206 0.013 0.001 0.000
–1.967 11.133 3.869 –7.609 14.966

现在,我们将线性回归应用于这个有 200 列的大数据集。请注意,这个数据集中的线性回归模型看起来像是列中变量的线性组合。但是,因为每个列对应一个单项式,所以得到的模型看起来像是在变量 x 上的多项式。

在我们训练任何模型之前,我们需要使用以下行代码将数据分成训练集和测试集:

train, test = data.random_split(.8)

现在,我们的数据集被分成两个数据集,训练集称为 train,测试集称为 test。在存储库中指定了一个随机种子,所以我们总是得到相同的结果,尽管在实际中这不是必需的。

在 Turi Create 中使用正则化的方法是简单的:我们只需要在训练模型时在 create 方法中指定参数 l1_penaltyl2_penalty。这个惩罚正是我们在“调节模型性能和复杂度”部分中引入的正则化参数。0 的惩罚意味着我们没有使用正则化。因此,我们将使用以下参数训练三个不同的模型:

  • 无正则化模型:

    • l1_penalty=0

    • l2_penalty=0

  • L1 正则化模型:

    • l1_penalty=0.1

    • l2_penalty=0

  • L2 正则化模型:

    • l1_penalty=0

    • l2_penalty=0.1

我们使用以下三行代码来训练模型:

model_no_reg = tc.linear_regression.create(train, target='y', l1_penalty=0.0, l2_penalty=0.0)
model_L1_reg = tc.linear_regression.create(train, target='y', l1_penalty=0.1, l2_penalty=0.0)
model_L2_reg = tc.linear_regression.create(train, target='y', l1_penalty=0.0, l2_penalty=0.1)

第一个模型没有正则化,第二个模型使用 L1 正则化,参数为 0.1,第三个模型使用 L2 正则化,参数为 0.1。结果函数的图示如图 4.9 所示。注意,在这个图中,训练集的点用圆圈表示,测试集的点用三角形表示。

图片

图 4.9 展示了针对我们数据集的三个多项式回归模型。左侧的模型没有正则化,中间的模型具有 L1 正则化,参数为 0.1,右侧的模型具有 L2 正则化,参数为 0.1。

注意,没有正则化的模型非常吻合训练点,但它很混乱,并且没有很好地拟合测试点。L1 正则化的模型在训练集和测试集上都表现不错。但 L2 正则化的模型在训练集和测试集上都做得很好,并且似乎真正捕捉到了数据的形状。

还要注意,对于这三个模型,边界曲线在端点处有点疯狂。这是完全可以理解的,因为端点处的数据较少,当没有数据时,模型不知道该怎么做是很自然的。我们应该始终通过模型在我们数据集边界内的表现来评估模型,我们不应该期望模型在那些边界之外表现良好。即使我们人类可能也无法在模型边界之外做出良好的预测。例如,你认为这个曲线在数据集之外会是什么样子?它会继续作为向下开口的抛物线吗?它会像正弦函数一样永远振荡吗?如果我们不知道这些,我们就不应该期望模型知道。因此,尝试忽略图 4.9 中端点的奇怪行为,并关注模型在数据所在区间内的行为。

要找到测试误差,我们使用以下代码行,并指定相应的模型名称。此代码行返回最大误差和均方根误差(RMSE)。

model.predict(test)

模型的测试 RMSE 如下:

  • 无正则化模型:699.03

  • L1 正则化模型:10.08

  • L2 正则化模型:3.41

没有正则化的模型 RMSE 非常大!在其他两个模型中,具有 L2 正则化的模型表现要好得多。以下是一些需要思考的问题:

  1. 为什么 L2 正则化的模型比 L1 正则化的模型表现更好?

  2. 为什么 L1 正则化的模型看起来很平坦,而 L2 正则化的模型却成功地捕捉到了数据的形状?

这两个问题的答案类似,要找到答案,我们可以查看多项式的系数。这些可以通过以下代码行获得:

model.coefficients

每个多项式都有 200 个系数,所以我们在这里不会显示所有系数,但在表 4.3 中你可以看到三个模型的前五个系数。你注意到了什么?

表 4.3 我们三个模型中多项式的第一个五个系数。注意,没有正则化的模型具有大的系数,具有 L1 正则化的模型具有非常接近 0 的系数,而具有 L2 正则化的模型具有小的系数。

系数 model_no_reg model_L1_reg model_L2_reg
x⁰ = 1 8.41 0.57 13.24
x¹ 15.87 0.07 0.87
x² 108.87 –0.004 –0.52
x³ –212.89 0.0002 0.006
x –97.13 –0.0002 –0.02

为了解释表 4.3,我们看到三个模型的预测都是 200 次方的多项式。第一项如下所示:

  • 没有正则化的模型:ŷ = 8.41 + 15.87x + 108.87x² – 212.89x³ – 97.13x⁴ + …

  • 具有 L1 正则化的模型:ŷ = 0.57 + 0.07x – 0.004x² + 0.0002x³ – 0.0002x⁴ + …

  • 具有 L2 正则化的模型:ŷ = 13.24 + 0.87x – 0.52x² + 0.006x³ – 0.02x⁴ + …

从这些多项式中,我们可以看到以下情况:

  • 对于没有正则化的模型,所有系数都很大。这意味着多项式是混沌的,不适合进行预测。

  • 对于具有 L1 正则化的模型,除了常数项(第一个项)之外的所有系数都非常小——几乎为 0。这意味着对于接近零的值,多项式看起来非常像方程 ŷ = 0.57 的水平线。这比之前的模型要好,但仍然不适合进行预测。

  • 对于具有 L2 正则化的模型,随着次数的增长,系数会变小,但仍然不是那么小。这为我们提供了一个相当不错的多项式来进行预测。

概述

  • 当涉及到训练模型时,会出现许多问题。出现频率较高的两个问题是欠拟合和过拟合。

  • 当我们使用一个非常简单的模型来拟合我们的数据集时,会发生欠拟合。当我们使用过于复杂的模型来拟合我们的数据集时,会发生过拟合。

  • 通过使用测试数据集,我们可以有效地区分过拟合和欠拟合。

  • 为了测试一个模型,我们将数据分为两个集合:训练集和测试集。训练集用于训练模型,测试集用于评估模型。

  • 机器学习的黄金法则是不使用我们的测试数据来训练或在我们的模型中做出决策。

  • 验证集是我们数据集的另一部分,我们用它来决定模型中的超参数。

  • 欠拟合的模型在训练集和验证集上表现不佳。过拟合的模型在训练集上表现良好,但在验证集上表现不佳。一个好的模型在训练集和验证集上都会表现良好。

  • 模型复杂度图用于确定模型的正确复杂度,以确保它不会欠拟合或过拟合。

  • 正则化是减少机器学习模型过拟合的一个重要技术。它包括在训练过程中将复杂度的度量(正则化项)添加到误差函数中。

  • L1 和 L2 范数是正则化中最常用的两种复杂度度量。

  • 使用 L1 范数会导致 L1 正则化,或套索回归。使用 L2 范数会导致 L2 正则化,或岭回归。

  • 当我们的数据集具有众多特征,并且我们希望将其中许多特征变为零时,建议使用 L1 正则化。当我们的数据集特征较少,我们希望将它们变得很小但不为零时,建议使用 L2 正则化。

练习

练习 4.1

我们在同一数据集上训练了四个模型,具有不同的超参数。在下面的表中,我们记录了每个模型的训练和测试误差。

模型 训练误差 测试误差
1 0.1 1.8
2 0.4 1.2
3 0.6 0.8
4 1.9 2.3
  1. 对于这个数据集,你会选择哪个模型?

  2. 哪个模型看起来像是在欠拟合数据?

  3. 哪个模型看起来像是在过度拟合数据?

练习 4.2

我们得到了以下数据集:

x y
1 2
2 2.5
3 6
4 14.5
5 34

我们训练多项式回归模型,预测y的值为ŷ,其中

ŷ = 2x² – 5x + 4.

如果正则化参数λ = 0.1,并且我们用来训练这个数据集的误差函数是平均绝对值(MAE),确定以下内容:

  1. 我们模型(使用 L1 范数)的套索回归误差

  2. 我们模型(使用 L2 范数)的岭回归误差

5 使用线来分割我们的点:感知器算法

在本章

  • 什么是分类

  • 情感分析:如何使用机器学习判断句子是快乐的还是悲伤

  • 如何画一条线来分隔两种颜色的点

  • 感知器是什么,我们如何训练它

  • 在 Python 和 Turi Create 中编码感知器算法

在本章中,我们学习机器学习的一个分支,称为 分类。分类模型与回归模型类似,它们的目的是根据特征预测数据集的标签。区别在于,回归模型旨在预测一个数字,而分类模型旨在预测一个状态或类别。分类模型通常被称为 分类器,我们将交替使用这些术语。许多分类器预测两种可能的状态之一(通常是是/否),尽管也可以构建预测更多可能状态的分类器。以下是一些流行的分类器示例:

  • 一个预测用户是否会观看特定电影的推荐模型

  • 一个预测电子邮件是垃圾邮件还是正常邮件的电子邮件模型

  • 一个预测患者是生病还是健康的医疗模型

  • 一个预测图像是否包含汽车、鸟、猫或狗的图像识别模型

  • 一个预测用户是否说了特定命令的语音识别模型

分类是机器学习中的一个热门领域,本书的大部分章节(第 5、6、8、9、10、11 和 12 章)都讨论了不同的分类模型。在本章中,我们学习 感知器 模型,也称为 感知器分类器,或简单地称为 感知器。感知器类似于线性回归模型,因为它使用特征的线性组合来做出预测,并且是神经网络(我们在第十章中学习)的构建块。此外,训练感知器的过程与训练线性回归模型的过程类似。就像我们在第三章中用线性回归算法做的那样,我们以两种方式开发感知器算法:使用我们可以多次迭代的技巧,以及定义一个我们可以通过梯度下降最小化的误差函数。

本章我们学习的主要分类模型示例是 情感分析。在情感分析中,模型的目的是预测句子的情感。换句话说,模型预测句子是快乐的还是悲伤的。例如,一个好的情感分析模型可以预测句子“我感觉太棒了!”是一个快乐的句子,而句子“多么糟糕的一天!”是一个悲伤的句子。

情感分析在许多实际应用中被使用,例如以下:

  • 当公司分析客户与技术支持之间的对话时,以评估对话的质量

  • 当分析品牌数字存在(如社交媒体上的评论或与其产品相关的评论)的语气时

  • 当像 Twitter 这样的社交平台在事件发生后分析特定人群的整体情绪时

  • 当投资者使用对公司公开情绪的预测来预测其股价时

我们如何构建一个情感分析分类器呢?换句话说,我们如何构建一个机器学习模型,它以句子作为输入,并以输出告诉我们句子是快乐还是悲伤。这个模型当然会犯错误,但我们的想法是构建它,使其尽可能少犯错误。让我们放下书本几分钟,想想我们如何构建这种类型的模型。

这里有一个想法。快乐句子往往包含快乐词汇,如wonderfulhappyjoy,而悲伤句子往往包含悲伤词汇,如awfulsaddespair。一个分类器可以由字典中每个单词的“快乐”分数组成。快乐词汇可以给予正分数,而悲伤词汇可以给予负分数。中性词汇如the可以给予零分。当我们将一个句子输入到我们的分类器中时,分类器只是简单地将句子中所有单词的分数相加。如果结果是正的,那么分类器就会得出结论,这个句子是快乐的。如果结果是负的,那么分类器就会得出结论,这个句子是悲伤的。现在的目标是找到字典中所有单词的分数。为此,我们使用机器学习。

我们刚才构建的模型类型被称为感知器模型。在本章中,我们将学习感知器的正式定义以及如何通过找到所有单词的完美分数来训练它,以便我们的分类器尽可能少犯错误。

训练感知器的过程被称为感知器算法,它与我们在第三章学到的线性回归算法并没有太大的区别。以下是感知器算法的思路:为了训练模型,我们首先需要一个包含许多句子及其标签(快乐/悲伤)的数据集。我们通过为所有单词分配随机分数来开始构建我们的分类器。然后,我们多次遍历数据集中的所有句子。对于每个句子,我们稍微调整分数,以便分类器改进对该句子的预测。我们如何调整分数呢?我们使用一种称为感知器技巧的方法来调整分数,这在“感知器技巧”这一节中会学到。另一种训练感知器模型的方法是使用误差函数,就像我们在第三章中所做的那样。然后我们使用梯度下降来最小化这个函数。

然而,语言很复杂——它有细微差别、双关语和讽刺。如果我们把一个词简化为一个简单的分数,我们不会丢失太多信息吗?答案是肯定的——我们会丢失很多信息,而且我们无法通过这种方式创建一个完美的分类器。好消息是,使用这种方法,我们仍然可以创建一个大多数时候都是正确的分类器。以下是一个证明我们使用的方法不可能总是正确的例子。句子“我不悲伤,我很快乐”和“我不快乐,我很悲伤”有相同的单词,但意义完全不同。因此,无论我们给单词赋予什么分数,这两个句子都将获得相同的分数,因此分类器将对他们做出相同的预测。它们有不同的标签,所以分类器必须在对其中一个句子做出错误判断。

解决这个问题的方法是为分类器构建一个考虑单词顺序的模型,或者甚至考虑其他事物,如标点符号或习语。一些模型,如隐马尔可夫模型(HMM)、循环神经网络(RNN)或长短期记忆网络(LSTM)在序列数据上取得了巨大成功,但我们将不会在本书中包括它们。然而,如果您想探索这些模型,附录 C 中您可以找到一些非常有用的参考资料。

您可以在以下 GitHub 仓库中找到本章所有代码:github.com/luisguiserrano/manning/tree/master/Chapter_5_Perceptron_Algorithm.

问题:我们身处一个外星星球,而我们不知道他们的语言!

想象以下场景:我们是宇航员,刚刚降落在遥远的星球上,那里居住着一群未知的外星人。我们希望能与外星人交流,但他们说的是一种我们不懂的奇怪语言。我们注意到外星人有两种情绪,快乐和悲伤。我们与他们交流的第一步是确定他们情绪是快乐还是悲伤。换句话说,我们希望构建一个情感分析分类器。

我们设法与四个外星人交上了朋友,并开始观察他们的情绪,研究他们说的话。我们注意到其中两个是快乐的,两个是悲伤的。他们也反复重复相同的句子。他们的语言似乎只有两个词:aackbeep。我们根据他们说的句子和他们的情绪形成了以下数据集:

数据集:

  • 外星人 1

    • 情绪:快乐

    • 句子:“Aack, aack, aack!”

  • 外星人 2:

    • 情绪:悲伤

    • 句子:“Beep beep!”

  • 外星人 3:

    • 情绪:快乐

    • 句子:“Aack beep aack!”

  • 外星人 4:

    • 情绪:悲伤

    • 句子:“Aack beep beep beep!”

突然,第五个外星人进来了,它说,“Aack beep aack aack!”我们真的无法判断这个外星人的情绪。根据我们所知,我们应该如何预测这个外星人的情绪(图 5.1)?

我们预测这个外星人很开心,尽管我们不知道他们的语言,但单词 aack 在快乐句子中似乎出现得更频繁,而单词 beep 在悲伤句子中似乎出现得更频繁。也许 aack 代表着积极的意义,比如“快乐”或“幸福”,而 beep 可能代表着悲伤的意义,比如“绝望”或“悲伤”。

图 5.1 我们的外星人数据集。我们记录了他们的情绪(快乐或悲伤)和他们反复说的句子。现在又来了一位第五个外星人,说了一个不同的句子。我们预测这个外星人快乐还是悲伤?

这个观察结果产生了我们的第一个情感分析分类器。这个分类器通过以下方式进行预测:它计算单词 aackbeep 出现的次数。如果 aack 出现的次数多于 beep,则分类器预测句子是快乐的。如果它更少,则分类器预测句子是悲伤的。当两个单词出现次数相同会发生什么?我们没有依据来判断,所以我们说默认预测是句子是快乐的。实际上,这类边缘情况很少发生,所以它们不会给我们造成大问题。

我们刚刚构建的分类器是一个感知器(也称为线性分类器)。我们可以用分数或权重来表示它,如下所示:

情感分析分类器

给定一个句子,为以下单词分配以下分数:

分数:

  • Aack: 1 分

  • Beep: -1 分

规则:

通过将句子上所有单词的分数相加来计算句子的分数,如下所示:

  • 如果分数为正或零,预测句子是快乐的。

  • 如果分数为负,预测句子是悲伤的。

在大多数情况下,绘制我们的数据是有用的,因为有时会出现漂亮的模式。在表 5.1 中,我们有四个外星人,以及他们说了多少次 aackbeep 以及他们的情绪。

表 5.1 我们的外星人数据集,他们说的句子和他们的情绪。我们将每个句子分解为单词 aack 和 beep 出现的次数。

句子 Aack Beep 情绪
Aack aack aack! 3 0 快乐
Beep beep! 0 2 悲伤
Aack beep aack! 2 1 快乐
Aack beep beep beep! 1 3 悲伤

图表由两个轴组成,横轴(x 轴)和纵轴(y 轴)。在横轴上,我们记录了 aack 出现的次数,在纵轴上,记录了 beep 的出现次数。这个图表可以在图 5.2 中看到。

图 5.2 外星人的数据集图。在横轴上,我们绘制了单词 aack 出现的次数,在纵轴上,绘制了单词 beep 的出现次数。

注意,在图 5.2 的图中,快乐的异形位于右下角,而悲伤的异形位于左上角。这是因为右下角是句子中 aack 出现次数多于 beep 的区域,而左上角区域则相反。实际上,所有 aackbeep 出现次数相同的句子形成的线将这两个区域分开,如图 5.3 所示。这条线的方程如下:

aack = #beep

或者等价地,这个方程:

aack – #beep = 0

在本章中,我们将使用带有不同下标的变量 x 来表示一个词在句子中出现的次数。在这种情况下,x[aack] 表示单词 aack 出现的次数,而 x[beep] 表示单词 beep 出现的次数。

使用这种表示法,分类器的方程变为 x[aack] – x[beep] = 0,或者等价地,x[aack] = x[beep] . 这是平面上的一条直线方程。如果看起来不这样,想想直线方程 y = x,除了用 x 代替,我们用 x[aack],用 y 代替 x[beep] . 为什么不使用 xy 像我们高中时做的那样呢?我愿意这样做,但不幸的是,我们稍后需要 y(用于预测)。因此,让我们将 x[aack]-轴视为水平轴,将 x[beep]-轴视为垂直轴。与这个方程一起,我们有两个重要的区域,我们称之为 正区域负区域。它们的定义如下:

正区域:平面上 x[aack] – x[beep] ≥ 0 的区域。这对应于单词 aack 出现的次数至少与单词 beep 相等的句子。

负区域:平面上 x[aack] – x[beep] < 0 的区域。这对应于单词 aack 出现的次数少于单词 beep 的句子。

我们创建的分类器预测,正区域中的每个句子都是快乐的,而负区域中的每个句子都是悲伤的。因此,我们的目标是找到可以将尽可能多的快乐句子放入正区域,尽可能多的悲伤句子放入负区域的分类器。对于这个小型示例,我们的分类器完美地完成了这项工作。但这并不总是如此,但感知器算法将帮助我们找到能够真正出色完成这项工作的分类器。

在图 5.3 中,我们可以看到对应于分类器和正负区域的线。如果你比较图 5.2 和图 5.3,你可以看到当前的分类器很好,因为所有快乐句子都在正区域,所有悲伤句子都在负区域。

现在我们已经构建了一个简单的情感分析感知器分类器,让我们看看一个稍微复杂一点的例子。

图 5.3 分类器是分割快乐和悲伤点的对角线。这条线的方程是 x[aack] = x[beep](或者等价地,x[aack]x[beep] = 0),因为这条线对应于所有水平和垂直坐标相等的点。快乐区域是aack出现次数大于或等于beep出现次数的区域,而悲伤区域是aack出现次数少于beep出现次数的区域。

一个稍微复杂一些的星球

在本节中,我们看到一个更复杂的例子,它引入了感知器的一个新方面:偏差。在我们能够与第一个星球上的外星人交流之后,我们被派往第二个星球,那里的外星人有稍微复杂一些的语言。我们的目标仍然是相同的:用他们的语言创建一个情感分析分类器。新星球上的语言有两种词:crackdoink。数据集显示在表 5.2 中。

为这个数据集构建分类器似乎比上一个数据集要困难一些。首先,我们应该给crackdoink这些词分配正分还是负分?让我们拿一支笔和一张纸,尝试构建一个可以正确分离这个数据集中快乐和悲伤句子的分类器。查看图 5.4 中的这个数据集的图表可能会有所帮助。

表 5.2 外星词汇的新数据集。再次,我们记录了每个句子,该句子中每个单词出现的次数以及外星人的情绪。

句子 Crack Doink 情绪
Crack! 1 0 悲伤
Doink doink! 0 2 悲伤
Crack doink! 1 1 悲伤
Crack doink crack! 2 1 悲伤
Doink crack doink doink! 1 3 快乐
Crack doink doink crack! 2 2 快乐
Doink doink crack crack crack! 3 2 快乐
Crack doink doink crack doink! 2 3 快乐

图 5-4

图 5.4 外星词汇新数据集的图表。注意,快乐的点倾向于在上方和右侧,而悲伤的点在下方和左侧。

这个分类器的想法是计算句子中的单词数量。注意,只有一个、两个或三个单词的句子都是悲伤的,而四个和五个单词的句子是快乐的。那就是分类器!它将三个单词或更少的句子分类为悲伤,将四个或更多单词的句子分类为快乐。我们还可以用更数学的方式表达这一点。

情感分析分类器

给定一个句子,给以下单词分配以下分数:

分数:

  • Crack:一分

  • Doink:一分

规则:

通过将句子上所有单词的分数相加来计算句子的分数。

  • 如果分数是 4 或以上,预测该句子是快乐的。

  • 如果分数是 3 或以下,预测该句子是悲伤的。

为了让它更简单,让我们通过使用 3.5 的截止值稍微改变一下规则。

规则:

通过计算句子上所有单词的分数来计算句子的分数。

  • 如果分数为 3.5 或更多,预测句子是快乐的。

  • 如果分数小于 3.5,预测句子是悲伤的。

这个分类器再次对应于一条线,该线在图 5.5 中进行了说明。

图 5.5 新数据集的外星人分类器。它再次是一条分割快乐和悲伤外星人的线。

在上一个例子中,我们得出结论,单词 aack 是一个快乐的词,而单词 beep 是一个悲伤的词。在这个例子中会发生什么?看起来单词 crackdoink 都是快乐的,因为它们的分数都是正的。那么,句子 “Crack doink” 为什么是一个悲伤的句子?因为它没有足够的单词。这个星球上的外星人有着独特的个性。说话不多的是悲伤的外星人,而说话很多的是快乐的外星人。我们可以这样解释:这个星球上的外星人天生就是悲伤的,但他们可以通过多说话来摆脱悲伤。

在这个分类器中,另一个重要元素是截止值或阈值 3.5。分类器使用这个阈值进行预测,因为分数高于或等于阈值的句子被分类为快乐,而分数低于阈值的句子被分类为悲伤。然而,阈值并不常见,我们而是使用 偏置 的概念。偏置是阈值的相反数,我们将其加到分数上。这样,分类器可以计算分数,如果分数是非负的,则返回快乐的预测,如果分数是负的,则返回悲伤的预测。作为最后的符号变化,我们将单词的分数称为 权重。我们的分类器可以表示如下:

情感分析分类器

给定一个句子,为单词分配以下权重和偏置:

权重:

  • Crack: 一分

  • Doink: 一分

偏置: –3.5 分

规则:

通过将句子上所有单词的权重和偏置相加来计算句子的分数。

  • 如果分数大于或等于零,预测句子是快乐的。

  • 如果分数小于零,预测句子是悲伤的。

分类器的分数方程,以及图 5.5 中的直线方程如下:

crack + #doink – 3.5 = 0

注意,定义一个阈值为 3.5 且偏置为 –3.5 的感知器分类器与以下两个等式相同,因为这两个等式是等价的:

  • crack + #doink ≥ 3.5

  • crack + #doink – 3.5 ≥ 0

我们可以使用与上一节类似的表达法,其中 x[crack] 是单词 crack 出现的次数,x[doink] 是单词 doink 出现的次数。因此,图 3.5 中的直线方程可以写成

x[crack] + x[doink] – 3.5 = 0.

这条线也将平面分为正区域和负区域,定义如下:

正区域:平面上 x[crack] + x[doink] – 3.5 ≥ 0 的区域

负区域:平面上满足 x[crack] + x[doink] – 3.5 < 0 的区域

我们的分类器是否需要始终正确?不

在前两个例子中,我们构建了一个始终正确的分类器。换句话说,这个分类器将两个快乐的句子分类为快乐,将两个悲伤的句子分类为悲伤。这在实践中并不常见,尤其是在包含许多点的数据集中。然而,分类器的目标是将点尽可能准确地分类。在图 5.6 中,我们可以看到一个包含 17 个点(8 个快乐和 9 个悲伤)的数据集,使用单条线无法完美地将其分成两部分。然而,图中的线做得很好,只错误地分类了三个点。

图 5.6 这条线很好地分割了数据集。注意,它只犯了一个错误:两个在快乐区域,一个在悲伤区域。

更通用的分类器和定义线的不同方法

在本节中,我们获得了对感知器分类器的更一般性的看法。暂时让我们称我们的单词 1 和 2,以及跟踪它们出现的变量为 x[1] 和 x[2]。前两个分类器的方程如下:

  • x[1] – x[2] = 0

  • x[1] + x[2] – 3.5 = 0

感知器分类器的方程的一般形式是 ax[1] + bx[2] + c = 0,其中 a 是单词 1 的得分,b 是单词 2 的得分,c 是偏置。这个方程对应于一条线,将平面分成两个区域,如下所示:

正区域:平面上满足 ax[1] + bx[2] + c ≥ 0 的区域

负区域:平面上满足 ax[1] + bx[2] + c < 0 的区域

例如,如果单词 1 的得分为 4,单词 2 的得分为-2.5,偏置为 1.8,那么这个分类器的方程是

4x[1] – 2.5x[2] + 1.8 = 0,

以及正负区域分别是满足 4x[1] – 2.5x[2] + 1.8 ≥ 0 和 4x[1] – 2.5x[2] + 1.8 < 0 的区域。

旁白:平面上线和区域的方程在第三章中,我们使用方程 y = mx + bxy 轴的平面上定义了线。在本章中,我们使用方程 ax[1] + bx[2] + c = 0 在 x[1] 和 x[2] 轴的平面上定义它们。它们有什么不同?它们都是定义线的完全有效的方法。然而,第一个方程对线性回归模型很有用,而第二个方程对感知器模型(以及一般地,对其他分类算法,如逻辑回归、神经网络和支持向量机,我们将在第 6、10 和 11 章分别看到)很有用。为什么这个方程对感知器模型更好?以下是一些优点:

  • 方程ax[1] + bx[2] + c = 0 不仅定义了一条线,而且清楚地定义了两个区域,正区域和负区域。如果我们想要有相同的线,但正负区域相反,我们会考虑方程 –ax[1] – bx[2] – c = 0。

  • 使用方程ax[1] + bx[2] + c = 0,我们可以画出垂直线,因为垂直线的方程是x = c或 1x[1] + 0x[2] – c = 0。尽管垂直线在线性回归模型中不常见,但在分类模型中确实会出现。

图 5.7 一个分类器由方程ax[1] + bx[2] + c = 0、正区域和负区域定义。如果我们想翻转正负区域,我们只需要取权重和偏差的相反数。在左边,我们有方程ax[1] + bx[2] + c = 0 的分类器。在右边,区域翻转且方程为 –ax[1] – bx[2] – c = 0 的分类器。

步进函数和激活函数:获取预测的紧凑方式

在本节中,我们学习了一种数学捷径来获得预测。然而,在学习这个之前,我们需要将所有数据转换为数字。注意,我们数据集中的标签是“快乐”和“悲伤”。我们分别记录为 1 和 0。

本章中我们构建的两个感知器分类器都是使用 if 语句定义的。具体来说,分类器根据句子的总得分预测“快乐”或“悲伤”;如果这个得分是正数或零,分类器预测“快乐”,如果是负数,分类器预测“悲伤”。我们有一个更直接的方法将得分转换为预测:使用步进函数

步进函数:当输出非负时返回 1,当输出为负时返回 0 的函数。换句话说,如果输入是x,那么

  • step(x) = 1 if x ≥ 0

  • step(x) = 0 if x < 0

图 5.8 显示了步进函数的图形。

图 5.8 步进函数在感知器模型的研究中很有用。当输入为负时,步进函数的输出为 0,否则为 1。

使用步进函数,我们可以轻松地表达感知器分类器的输出。在我们的数据集中,我们使用变量y来指代标签,就像我们在第三章中所做的那样。模型对标签的预测表示为y**ˆ。感知器模型的输出以紧凑的形式表示为

ŷ = step(ax[1] + bx[2] + c).

步进函数是激活函数的一个特例。激活函数是机器学习中的一个重要概念,尤其是在深度学习中,它将在第六章和第十章中再次出现。激活函数的正式定义将在稍后给出,因为它的全部力量是在构建神经网络时使用的。但就目前而言,我们可以将激活函数视为一个可以将得分转换为预测的函数。

如果我有超过两个单词会发生什么?感知器分类器的一般定义

在本节开头提到的两个外星例子中,我们为拥有两个单词的语言构建了感知器分类器。但我们可以构建拥有任意多个单词的分类器。例如,如果我们有一个包含三个单词的语言,比如 aackbeepcrack,分类器将根据以下公式进行预测:

ŷ = step(ax[aack] + bx[beep] + cx[crack] + d),

其中 abc 分别是单词 aackbeepcrack 的权重,d 是偏差。

正如我们所见,对于拥有两个单词的语言的情感分析感知器分类器可以表示为分割快乐和悲伤点的平面上的线。对于拥有三个单词的语言的情感分析分类器也可以用几何方式表示。我们可以想象这些点生活在三维空间中。在这种情况下,每个轴对应于单词 aackbeepcrack,一个句子对应于空间中的一个点,其三个坐标是三个单词出现的次数。图 5.9 展示了一个例子,其中包含 aack 五次,beep 八次和 crack 三次的句子对应于坐标为 (5, 8, 3) 的点。

图 5.9 将包含三个单词的句子绘制为空间中的一个点。在这种情况下,包含单词 aack 五次,beep 八次和 crack 三次的句子绘制在坐标为 (5,8,3) 的点上。

使用平面来分离这些点。平面的方程是 ax[aack] + bx[beep] + cx[crack] + d,这个平面在图 5.10 中被展示出来。

图 5.10 将三个单词的句子数据集绘制在三维空间中。分类器用一个分割空间的平面表示。

我们可以为拥有尽可能多单词的语言构建情感分析感知器分类器。假设我们的语言有 n 个单词,我们称之为 1, 2, … , n。我们的数据集由 m 个句子组成,我们称之为 x^((1)), x^((2)), … , x(m^)。每个句子 x^((1)) 都有一个标签 y[i],如果句子是快乐的,则标签为 1,如果是悲伤的,则标签为 0。我们记录每个句子的方式是使用每个 n 个单词出现的次数。因此,每个句子对应数据集中的一行,可以看作是一个向量,或一个由数字 x(i^) = (x[1](i^), x[2](i^), … , x[n](i^)) 组成的 n-元组,其中 x[j](i^) 是单词 j 在第 i 个句子中出现的次数。

感知器分类器由 n 个权重(分数)组成,每个权重对应我们语言中的 n 个单词,还有一个偏差。权重表示为 w[i] 和偏差 b。因此,分类器对句子 x(i^) 的预测是

ŷ[i] = step(w[1]x[1](i^) + w[2]x[2](i^) + … +w[n]x[n](i^) + b).

与只有两个单词的分类器可以用一条线在平面上分割成两个区域一样,可以用一个平面在三维空间中分割成两个区域,具有n个单词的分类器也可以用几何表示。不幸的是,我们需要n维来看到它们。人类只能看到三维,所以我们可能需要想象一个(n-1)-维平面(称为超平面)将n-维空间分割成两个区域。

然而,我们无法从几何上想象它们并不意味着我们不能很好地理解它们是如何工作的。想象一下,如果我们的分类器建立在英语语言的基础上。每个单词都会被分配一个权重。这相当于遍历词典,并为每个单词分配一个快乐分数。结果可能看起来像这样:

权重(分数):

  • A: 0.1 points

  • Aardvark: 0.2 points

  • Aargh: –4 points

  • Joy: 10 points

  • Suffering: –8.5 points

  • ...

  • Zygote: 0.4 points

偏差:

  • –2.3 points

如果这些是分类器的权重和偏差,要预测一个句子是快乐还是悲伤,我们只需加上句子上所有单词的分数(包括重复的)。如果结果是高于或等于 2.3(偏差的负值),则预测该句子为快乐;否则,预测为悲伤。

此外,这种表示法适用于任何例子,而不仅仅是情感分析。如果我们有一个不同的问题,不同的数据点、特征和标签,我们可以使用相同的变量来编码它。例如,如果我们有一个医疗应用,我们试图使用n个权重和偏差来预测患者是生病还是健康,我们仍然可以称标签为y,特征为x[i],权重为w[i],偏差为b

偏差、y-截距和安静外星人的内在情绪

到目前为止,我们已经很好地理解了分类器的权重意味着什么。具有正权重的单词是快乐的,具有负权重的单词是悲伤的。具有非常小的权重(无论是正还是负)的单词是更中性的单词。然而,偏差意味着什么呢?

在第三章中,我们指定了房价回归模型中的偏差是房屋的基础价格。换句话说,它是假设房屋房间数为零(一个工作室?)的假设房屋的预测价格。在感知器模型中,偏差可以解释为空句子的分数。换句话说,如果一个外星人什么都没说,这个外星人快乐还是悲伤?如果一个句子没有单词,它的分数就是偏差。因此,如果偏差是正的,那么什么都没说的外星人就是快乐的,如果偏差是负的,那么同样的外星人就是悲伤的。

几何上,正偏见和负偏见的区别在于原点(坐标为(0,0)的点)相对于分类器的位置。这是因为坐标为(0,0)的点对应于没有单词的句子。在具有正偏见的分类器中,原点位于正区域,而在具有负偏见的分类器中,原点位于负区域,如图 5.11 所示。

图 5.11 左:分类器具有负偏见,或正阈值y截距)。这意味着不说任何话的外星人落入悲伤区域,并被分类为悲伤。右:分类器具有正偏见,或负阈值。这意味着不说任何话的外星人落入快乐区域,并被分类为快乐。

我们能否考虑具有正偏见或负偏见的情感分析数据集?以下两个例子如何?

示例 1(正偏见):一个产品的在线评论数据集

想象一个数据集,我们记录了在亚马逊上特定产品的所有评论。其中一些是正面的,一些是负面的,根据它们收到的星级来区分。你认为空评论的评分会是什么?根据我的经验,差评往往包含很多单词,因为客户感到沮丧,他们描述了他们的负面经历。然而,许多正面评论是空的——客户只是给出了一个好的评分,而不需要解释他们为什么喜欢这个产品。因此,这个分类器可能具有正偏见。

示例 2(负偏见):与朋友对话的数据集

想象一下,我们记录了我们与朋友的全部对话并将它们分类为快乐或悲伤的对话。如果有一天我们遇到一个朋友,而我们的朋友说绝对没有任何话,我们想象他们可能是在生我们的气,或者他们非常沮丧。因此,空句子被分类为悲伤。这意味着这个分类器可能具有负偏见。

我们如何确定一个分类器是好是坏?误差函数

现在我们已经定义了什么是感知器分类器,我们的下一个目标是了解如何训练它——换句话说,我们如何找到最适合我们数据的感知器分类器?但在学习如何训练感知器之前,我们需要学习一个重要的概念:如何评估它们。更具体地说,在本节中,我们学习一个有用的误差函数,它将告诉我们感知器分类器是否适合我们的数据。与第三章中绝对误差和平方误差对线性回归有效一样,这个新的误差函数对于不适合数据的分类器将很大,而对于适合数据的分类器将很小。

如何比较分类器?误差函数

在本节中,我们学习如何构建一个有效的错误函数,帮助我们确定特定感知器分类器的优劣。首先,让我们测试我们的直觉。图 5.12 显示了同一数据集上的两个不同的感知器分类器。分类器被表示为一条有两条明确边界的线,一边是快乐,另一边是悲伤。显然,左侧的是一个不好的分类器,而右侧的是一个好的分类器。我们能想出一个衡量它们好坏的方法吗?换句话说,我们能给每个分类器分配一个数字,使得左侧的分类器分配一个高数字,而右侧的分类器分配一个低数字?

图 5.12

图 5.12 左:一个不好的分类器,它并没有很好地分割点。右:一个好的分类器。我们能想出一个错误函数,将高数字分配给不好的分类器,将低数字分配给好的分类器吗?

接下来,我们看到对这个问题的不同回答,每个都有其优缺点。其中之一(剧透:第三个)是我们用来训练感知器的。

错误函数 1:错误数量

评估分类器的最简单方法是通过计算它犯的错误数量——换句话说,通过计算它错误分类的点数。

在这个例子中,左侧的分类器有一个 8 的错误率,因为它错误地将四个快乐点预测为悲伤,并将四个悲伤点预测为快乐。好的分类器有一个 3 的错误率,因为它错误地将一个快乐点预测为悲伤,并将两个悲伤点预测为快乐。这如图 5.13 所示。

图 5.13

图 5.13 我们通过计算每个分类器错误分类的点数来评估这两个分类器。左侧的分类器错误分类了八个点,而右侧的分类器错误分类了三个点。因此,我们得出结论,右侧的分类器对于我们的数据集来说是一个更好的选择。

这是一个好的错误函数,但并不是一个很好的错误函数。为什么?它告诉我们何时有错误,但它不衡量错误的严重程度。例如,如果一个句子是悲伤的,而分类器给它评分为 1,那么分类器就犯了错误。然而,如果另一个分类器给它评分为 100,那么这个分类器就犯了更大的错误。从几何上观察这一点,如图 5.14 所示。在这张图片中,两个分类器都将一个悲伤点错误分类为快乐。然而,左侧的分类器将线靠近点,这意味着悲伤点离悲伤区域不远。相比之下,右侧的分类器将点定位得非常远离其悲伤区域。

图 5.14

图 5.14 两个分类器误分类了点。然而,右侧的分类器犯的错误比左侧的分类器大得多。左侧的点离边界不远,因此它离悲伤区域也不远。然而,右侧的点离悲伤区域很远。理想情况下,我们希望一个误差函数将更高的误差分配给右侧的分类器,而不是左侧的分类器。

我们为什么关心测量误差的严重程度呢?仅仅计数它们不是足够了吗?回想一下我们在第三章中用到的线性回归算法。更具体地说,回想一下“梯度下降”这一节,我们使用梯度下降来减少这个误差。减少误差的方法是通过逐步减小误差,直到我们达到一个误差很小的点。在线性回归算法中,我们小幅调整直线,并选择误差减少最多的方向。如果我们通过计数误分类点的数量来计算误差,那么这个误差将只取整数值。如果我们小幅调整直线,误差可能根本不会减少,我们也不知道该朝哪个方向移动。梯度下降的目标是通过在函数减少最多的方向上采取小步骤来最小化函数。如果函数只取整数值,这相当于试图从阿兹特克阶梯上下降。当我们处于一个平坦的台阶上时,我们不知道该走哪一步,因为函数在任意方向上都不会减少。这如图 5.15 所示。

图 5-15

图 5.15 通过小步下降来最小化误差函数就像从山上走下来一样。然而,为了做到这一点,误差函数不能是平坦的(就像右侧的那个),因为在平坦的误差函数中,小步不会减少误差。一个好的误差函数就像左侧的那个,我们可以很容易地看到必须使用哪个方向来迈步以略微减少误差函数。

我们需要一个函数来衡量误差的大小,并将更高的误差分配给远离边界的误分类点,而不是那些靠近边界的点。

误差函数 2:距离

在图 5.16 中区分两个分类器的方法是考虑点到直线的垂直距离。注意,对于左侧的分类器,这个距离很小,而对于右侧的分类器,距离很大。

这个误差函数更有效。这个误差函数所做的是:

  • 正确分类的点会产生 0 误差。

  • 误分类的点会产生一个等于该点到直线距离的误差。

图 5-16

图 5.16 一种有效衡量分类器如何误分类点的办法是测量该点到直线的垂直距离。对于左边的分类器,这个距离较小,而对于右边的分类器,这个距离较大。

让我们回到本节开头我们拥有的两个分类器。我们计算总误差的方法是添加所有数据点的误差,如图 5.17 所示。这意味着我们只关注误分类点,并添加这些点到直线的垂直距离。请注意,不良分类器的误差较大,而良好分类器的误差较小。

图 5-17

图 5.17 为了计算分类器的总误差,我们将所有误差相加,这些误差是误分类点到直线的垂直距离。左边的分类器误差较大,而右边的分类器误差较小。因此,我们得出结论,右边的分类器更好。

这几乎是我们将要使用的误差函数。为什么我们不使用这个呢?因为点到直线的距离是一个复杂的公式。它包含一个平方根,因为我们使用勾股定理来计算它。平方根具有复杂的导数,一旦我们应用梯度下降算法,就会增加不必要的复杂性。我们不需要承担这种复杂性,因为我们可以创建一个更容易计算但仍然能够捕捉误差函数本质的误差函数:对误分类的点返回一个误差,并根据误分类点离边界的距离来改变误差的大小。

误差函数 3:得分

在本节中,我们将了解如何构建感知器的标准误差函数,我们称之为感知器误差函数。首先,让我们总结一下我们希望在误差函数中拥有的性质如下:

  • 正确分类点的误差函数为 0。

  • 误分类点的误差函数是一个正数。

    • 对于接近边界的误分类点,误差函数值较小。

    • 对于远离边界的误分类点,误差函数值较大。

  • 它由一个简单的公式给出。

回想一下,分类器对正区间的点预测标签为 1,对负区间的点预测标签为 0。因此,一个误分类点要么是正区间的标签为 0 的点,要么是负区间的标签为 1 的点。

为了构建感知器误差函数,我们使用得分。具体来说,我们使用以下得分的性质:

得分的性质:

  1. 边界上的点得分为 0。

  2. 正区间的点具有正得分。

  3. 负区间的点具有负得分。

  4. 接近边界的点具有低幅度的得分(即低绝对值的正或负得分)。

  5. 远离边界的点具有高幅值的得分(即,高绝对值的高或低得分)。

对于一个错误分类的点,感知机错误希望分配一个与其到边界的距离成比例的值。因此,远离边界的错误分类点的错误必须很高,而接近边界的错误分类点的错误必须很低。观察性质 4 和 5,我们可以看到,远离边界的点的得分绝对值总是很高,而接近边界的点的得分绝对值总是很低。因此,我们定义错误为错误分类点的得分的绝对值。

更具体地说,考虑将权重 ab 分配给单词 aackbeep 的分类器,并具有偏差 c。此分类器对具有 x[aack] 次出现的单词 aackx[beep] 次出现的单词 beep 的句子进行预测 ŷ = step(ax[aack] + bx[beep] + c)。感知机错误定义为以下:

句子的感知机错误

  • 如果句子被正确分类,错误为 0。

  • 如果句子被错误分类,错误为 |x[aack] + bx[beep] + c|.

在一般场景中,其中符号定义如“如果我有超过两个单词会发生什么?”一节中所述,以下为感知机错误的定义:

点的感知机错误(一般)

  • 如果点被正确分类,错误为 0。

  • 如果点被错误分类,错误为 |w[1] x[1] +w[2] x[2] + … +w[n]x[n] + b|.

平均感知机错误:计算整个数据集错误的一种方法

为了计算整个数据集的感知机错误,我们取所有点对应的所有错误的平均值。我们也可以选择取总和,尽管在本章中我们选择取平均值并称之为平均感知机错误

为了说明平均感知机错误,让我们看一个例子。

示例

考虑由四个句子组成的语料库,其中两个标记为快乐,两个标记为悲伤,如表 5.3 所示。

表 5.3 外星人新的数据集。同样,我们记录了每个句子,该句子中每个单词出现的次数以及外星人的情绪。

句子 标签(情绪)
1 0 悲伤
0 1 快乐
啦哔哔哔 1 3 快乐
啦哔哔 啦 啦 3 2 悲伤

我们将在该数据集上比较以下两个分类器:

分类器 1

权重:

    • a* = 1
  • b = 2

偏差c = –4

句子得分: 1x[aack] + 2x[beep] – 4

预测ŷ = step(1x[aack] + 2x[beep] – 4)

分类器 2

权重:

    • a* = –1
  • b = 1

偏差c = 0

句子得分: –x[aack] + x[beep]

预测ŷ = step(–x[aack] + x[beep])

点和分类器可以在图 5.18 中看到。乍一看,哪一个看起来像是一个更好的分类器?看起来分类器 2 更好,因为它正确地将所有点分类,而分类器 1 有两个错误。现在让我们计算错误,并确保分类器 1 的错误率高于分类器 2。

图 5.18 在左边我们有分类器 1,在右边我们有分类器 2。

两个分类器的预测结果在表 5.4 中计算。

表 5.4 我们的四个句子的数据集及其标签。对于两个分类器中的每一个,我们都有得分和预测。

句子 (x[aack], x[beep]) 标签y 分类器 1 得分 1x[aack] + 2x[beep] – 4 分类器 1 预测step(1x[aack] + 2x[beep] – 4) 分类器 1 错误 分类器 2 得分– x[aack] + 2x[beep] 分类器 2 预测step(–x[aack] + 2 x[beep]) 分类器 2 错误
(1,0) 悲伤 (0) –3 0 (正确) 0 –1 0 (正确) 0
(0,1) 快乐 (1) -2 0 (错误) 2 1 1 (正确) 0
(1,3) 快乐 (1) 3 1 (正确) 3 2 1 (正确) 0
(3,2) 悲伤 (0) 3 1 (错误) 0 –1 0 (正确) 0
平均感知器错误 1.25 0

现在来计算错误。注意,分类器 1 只错误地将句子 2 和句子 4 分类。句子 2 是快乐的,但它被错误地分类为悲伤的,而句子 4 是悲伤的,但它被错误地分类为快乐的。句子 2 的错误是得分的绝对值,即|–2| = 2。句子 4 的错误是得分的绝对值,即|3| = 3。其他两个句子没有错误,因为它们被正确分类。因此,分类器 1 的平均感知器错误为

1/4(0 + 2 + 0 + 3) = 1.25。

分类器 2 没有错误——它正确地分类了所有点。因此,分类器 2 的平均感知器错误为 0。我们据此得出结论,分类器 2 优于分类器 1。这些计算的总结在表 5.4 和图 5.19 中显示。

图 5.19 分类器 1 的错误率为 1.25,而分类器 2 的错误率为 0。因此,我们得出结论,分类器 2 优于分类器 1。

现在我们知道了如何比较分类器,让我们继续寻找其中最好的一个,或者至少是一个相当好的一个。

如何找到一个好的分类器?感知器算法

要构建一个好的感知器分类器,我们将遵循与第三章中我们遵循的线性回归相似的方法。这个过程被称为感知器算法,它包括从一个随机的感知器分类器开始,并逐渐改进它,直到我们得到一个好的分类器。感知器算法的主要步骤如下:

  1. 从一个随机的感知器分类器开始。

  2. 稍微改进分类器。(重复多次)。

  3. 通过测量感知器错误来决定何时停止循环运行。

我们首先在循环内部开发步骤,这是一种用于略微改进感知器分类器的技术,称为感知器技巧。它与我们在第三章的“平方技巧”和“绝对技巧”部分学到的技巧类似。

感知器技巧:略微改进感知器的方法

感知器技巧是一个微小的步骤,帮助我们从一个感知器分类器到一个略微更好的感知器分类器。然而,我们将首先描述一个不那么雄心勃勃的步骤。就像我们在第三章中所做的那样,我们首先关注一个点,并尝试改进该点的分类器。

有两种方式来观察感知器步骤,尽管两者都是等价的。第一种方式是几何方式,我们将分类器视为一条线。

感知器技巧的伪代码(几何)

  • 情况 1:如果点被正确分类,保持线不变。

  • 情况 2:如果点被错误分类,将线稍微移近该点。

为什么这会起作用?让我们来思考一下。如果点被错误分类,这意味着它位于线的错误一侧。将线移近它可能不会将其移到正确的一侧,但至少会使它更接近线,因此更接近线的正确一侧。我们重复这个过程很多次,所以可以想象有一天我们能够将线移过点,从而正确分类它。这个过程在图 5.20 中展示。

我们还有一种代数方法来观察感知器技巧。

图 5.20

图 5.20 情况 1(左):一个被正确分类的点告诉线保持原位。情况 2(右):一个被错误分类的点告诉线向它靠近。

感知器技巧的伪代码(代数)

  • 情况 1:如果点被正确分类,保持分类器不变。

  • 情况 2:如果点被错误分类,这意味着它产生了一个正误差。调整权重和偏置的一小部分,以便这个误差略微减小。

几何方法是一个更直观的方式来可视化这个技巧,但代数方法是一个更容易发展这个技巧的方式,所以我们将从代数方法来看。首先,让我们用我们的直觉来考虑。想象一下,我们有一个针对整个英语语言的分类器。我们尝试这个分类器对句子“我很悲伤”,它预测这个句子是快乐的。这显然是错误的。我们可能在哪里出错?如果预测这个句子是快乐的,那么这个句子必须得到了一个正分。这个句子不应该得到正分——它应该得到一个负分才能被分类为悲伤。分数是它的单词 悲伤 的分数总和,加上偏差。我们需要降低这个分数,使句子稍微悲伤一些。如果我们只稍微降低它,分数仍然是正的,这也是可以的。我们的希望是,通过多次运行这个过程,总有一天能将分数变成负数,并正确地分类我们的句子。降低分数的方法是降低它的所有部分,即单词 悲伤 的权重以及偏差。我们应该降低它们多少?我们通过一个等于我们在第三章“平方技巧”部分学到的学习率的量来降低它们。

类似地,如果我们的分类器将句子“我很高兴”错误地分类为悲伤句子,那么我们的做法是略微增加单词 高兴 的权重以及偏差,增加的量等于学习率。

让我们用一个数值例子来说明这一点。在这个例子中,我们使用的学习率为 η = 0.01。想象一下,我们有一个与上一节相同的分类器,即具有以下权重和偏差的分类器。我们将它称为“坏分类器”,因为我们的目标是改进它。

坏分类器

权重:

  • 啊哈a = 1

  • 哔哔b = 2

偏差c = –4

预测ŷ = step(x[aack] + 2x[beep] 4)

以下句子被模型错误分类,我们将用它来改进权重:

句子 1: “哔哔 啊哈 啊哈 哔哔 哔哔 哔哔。”

标签:悲伤(0)

对于这个句子,aack 出现的次数是 x[aack] = 2,beep 出现的次数是 x[beep] = 5。因此,分数是 1 · x[aack] + 2 · x[beep] – 4 = 1 · 2 + 2 · 5 – 4 = 8,预测是 ŷ = step(8) = 1。

句子应该有一个负得分,以被分类为悲伤。然而,分类器给它一个得分为 8,这是正的。我们需要降低这个得分。降低得分的一种方法是从 aack 的权重减去到 beep 的权重,以及偏置,从而得到新的权重,我们称之为 a**' = 0.99,b**' = 1.99,以及新的偏置 c**' = 4.01。然而,考虑一下:单词 beep 出现的次数比单词 aack 多得多。在某种程度上,beep 对句子的得分比 aack 更关键。我们可能需要比 aack 的得分降低 beep 的权重更多。让我们通过将每个单词的权重减少学习率乘以单词在句子中出现的次数来降低每个单词的权重。换句话说:

  • 单词 aack 出现了两次,所以我们将它的权重减少两倍的学习率,即 0.02。我们得到新的权重 a**' = 1 – 2 · 0.01 = 0.98。

  • 单词 beep 出现了五次,所以我们将它的权重减少五倍的学习率,即 0.05。我们得到新的权重 b**' = 2 – 5 · 0.01 = 1.95。

  • 偏置只加一次到得分上,所以我们通过学习率,即 0.01 来减少偏置。我们得到新的偏置 c**' = –4 – 0.01 = –4.01。

除此之外,我们不是从每个权重中减去学习率,而是减去学习率乘以单词在句子中出现的次数。真正的理由是微积分。换句话说,当我们开发梯度下降法时,误差函数的导数迫使我们这样做。这个过程在附录 B 的第“使用梯度下降训练分类模型”部分有详细说明。

新的改进分类器如下:

改进的分类器 1

权重:

  • Aacka**' = 0.98

  • 哔哔声b**' = 1.95

偏置c**' = –4.01

预测ŷ = step(0.98x[aack] + 1.95x[beep] – 4.01)

让我们验证这两个分类器的错误。记住,错误是得分的绝对值。因此,糟糕的分类器产生的错误为 |1 · x[aack] + 2 · x[beep] – 4| = |1 · 2 + 2 · 5 – 4| = 8。改进的分类器产生的错误为 |0.98 · x[aack] + 1.95 · x[beep] – 4.01| = |0.98 · 2 + 1.95 · 5 – 4.01| = 7.7。这是一个更小的错误,所以我们确实改进了该点的分类器!

我们刚才开发的案例包含一个带有负标签的错误分类点。如果错误分类点有一个正标签会发生什么?程序是相同的,只是不是从权重中减去一个量,而是加上它。让我们回到糟糕的分类器,并考虑以下句子:

句子 2:“Aack aack。”

标签:快乐

这个句子的预测是 ŷ = step(x[aack] + 2x[beep] – 4) = step(2 + 2 · 0 – 4) = step(–2) = 0。因为预测是悲伤的,所以句子被错误分类。这个句子的分数是 –2,为了将这个句子分类为快乐,我们需要分类器给它一个正分数。感知器技巧将通过以下方式增加这个 –2 分数的权重和偏差:

  • 单词 aack 出现两次,所以我们将权重增加两倍的学习率,即 0.02。我们得到新的权重 a' = 1 + 2 · 0.01 = 1.02。

  • 单词 beep 出现零次,所以我们不会增加其权重,因为这个词与句子无关。

  • 偏差只加到分数上一次,所以我们通过学习率,或 0.01,增加偏差。我们得到新的偏差*c**' = –4 + 0.01 = –3.99。

因此,我们新的改进分类器如下:

改进的分类器 2

权重

  • Aack: a**' = 1.02

  • Beep: b**' = 2

偏差: *c**' = –3.99

预测ŷ = step(1.02x[aack] + 2x[beep] – 3.99)

现在我们来验证错误。因为不良分类器给句子一个 –2 的分数,所以错误是 |–2| = 2。第二个分类器给句子一个分数为 1.02x[aack] + 2x[beep] – 3.99 = 1.02 · 2 + 2 · 0 – 3.99 = –1.95,错误为 1.95。因此,改进的分类器在这个点上比不良分类器有更小的错误,这正是我们所期望的。

让我们总结这两个情况,并得到感知器技巧的伪代码。

感知器技巧的伪代码

输入

  • 一个具有权重 a, b, 和偏差 c 的感知器

  • 一个坐标为 (x[1], x[2]) 和标签 y 的点

  • 一个小的正值 η(学习率)

输出

  • 一个具有新权重 a', b', 和偏差 c' 的感知器

步骤

  • 感知器在这一点上的预测是 ŷ = step(ax[1] + bx[2] + c)。

  • 情况 1:如果 ŷ = y

    • 返回原始感知器,权重 a', b', 和偏差 c'
  • 情况 2:如果 ŷ = 1 且 y = 0:

    • 返回具有以下权重和偏差的感知器:

      • a' = a – η**x[1]

      • b' = b – η**x[2]

      • c' = c – η**x[1]

  • 情况 3:如果 ŷ = 0 且 y = 1:

    • 返回具有以下权重和偏差的感知器:

      • a' = a + η**x[1]

      • b' = b – η**x[2]

      • c' = c + η**x[1]

如果感知器正确分类了点,输出感知器与输入相同,它们都产生一个错误为 0。如果感知器错误分类了点,输出感知器产生的错误比输入感知器小。

以下是一个简洁的技巧来压缩伪代码。注意,yŷ 对于感知器技巧中的三个情况是 0, –1 和 +1。因此,我们可以总结如下:

感知器技巧的伪代码

输入

  • 一个具有权重 a, b, 和偏差 c 的感知器

  • 一个坐标为 (x[1], x[2]) 和标签 y 的点

  • 一个小的值 η(学习率)

输出

  • 一个具有新权重 a', b', 和偏差 c' 的感知器

步骤

  • 感知器在点上的预测是 ŷ = step(ax[1] + bx[2] + c)。

  • 返回具有以下权重和偏差的感知器:

    • a' = a + η(y - ŷ)x[1]

    • b' = b + η(y - ŷ)x[2]

    • c' = c + η(y - ŷ)

重复感知器技巧多次:感知器算法

在本节中,我们学习感知器算法,该算法用于在数据集上训练感知器分类器。回想一下,感知器技巧允许我们稍微改进一个感知器,以便在一点上做出更好的预测。感知器算法包括从一个随机的分类器开始,并连续改进它,多次使用感知器技巧。

正如我们在本章中看到的,我们可以用两种方式来研究这个问题:几何和代数。在几何上,数据集由平面上用两种颜色着色的点给出,分类器是一条试图分割这些点的线。图 5.21 包含了一组快乐和悲伤的句子数据集,就像我们在本章开头看到的。算法的第一步是画一条随机的线。很明显,图 5.21 中的线并不代表一个很好的感知器分类器,因为它没有很好地分割快乐和悲伤的句子。

图 5.21 每个点都会告诉分类器如何让自己过得更好。被正确分类的点会告诉线保持不动。被错误分类的点会告诉线稍微向它们移动。

感知器算法的下一步是随机选择一个点,例如图 5.22 中的点。如果点被正确分类,则直线保持不变。如果点被错误分类,则直线会稍微靠近该点,从而使直线更好地适应该点。它可能对其他点不再是一个好的适应,但现在这并不重要。

图 5.22 如果我们将感知器技巧应用于一个分类器和错误分类的点,分类器会稍微向该点移动。

可以想象,如果我们重复这个过程很多次,最终我们会得到一个好的解决方案。这种方法并不总是能带我们到最好的解决方案。但在实践中,这种方法通常能找到一个好的解决方案,如图 5.23 所示。我们称之为感知器算法

图 5.23 如果我们多次应用感知器技巧,每次随机选择一个点,我们可以想象我们会得到一个大多数点都能正确分类的分类器。

我们运行算法的次数是 epoch 的数量。因此,这个算法有两个超参数:epoch 的数量和学习率。感知器算法的伪代码如下:

感知器算法的伪代码

输入:

  • 一个由 1 和 0 标记的点集

  • 一个 epoch 的数量,n

  • 一个学习率 η

输出:

  • 由一组权重和偏差组成的感知器分类器,它适合数据集

程序:

  • 从感知器分类器的权重和偏差的随机值开始。

  • 重复多次:

    • 选择一个随机数据点。

    • 使用感知器技巧更新权重和偏差。

返回值: 带有更新后的权重和偏差的感知器分类器。

我们应该运行循环多长时间?换句话说,我们应该使用多少个周期?以下是一些帮助我们做出决定的准则:

  • 运行循环固定次数,这可能是基于我们的计算能力,或者我们拥有的时间量。

  • 运行循环,直到错误低于我们事先设定的某个阈值。

  • 运行循环,直到错误在一定的周期数内没有显著变化。

通常情况下,如果我们有足够的计算能力,运行它比所需的次数多几次是可以的,因为一旦我们有一个拟合良好的感知器分类器,它往往不会改变很多。在“编码感知器算法”部分,我们编码感知器算法并通过测量每一步的错误来分析它,这样我们就能更好地了解何时停止运行它。

注意,对于某些情况,例如图 5.24 中所示的情况,在数据集中找到一条线来分开两个类别是不可能的。这是可以的:目标是找到一条尽可能少出错的线来分开数据集(如图中所示),感知器算法在这方面做得很好。

图 5-24

图 5.24 一个包含两个类别的数据集,这两个类别无法用一条线分开。然后,感知器算法被训练以找到尽可能好地分开它们的线。

梯度下降

你可能会注意到训练这个模型的过程看起来非常熟悉。事实上,它与我们在第三章中用线性回归所做的是相似的。回想一下,线性回归的目的是将一条线尽可能接近一组点。在第三章中,我们通过从一个随机线开始,并逐步靠近点来训练我们的线性回归模型。我们用从山顶(埃里斯特山)向下走的小步来类比这个过程。山顶上每个点的海拔是平均感知器误差函数,我们将其定义为绝对误差,或平方误差。因此,从山顶向下走相当于最小化误差,这相当于找到最佳拟合线。我们称这个过程为梯度下降,因为梯度正是指向最大增长方向的向量(所以它的负方向指向最大减少方向),在这个方向上迈出一步将使我们向下走得更远。

在本章中,发生的是同样的事情。我们的问题有一点不同,因为我们不希望将直线尽可能接近一组点。相反,我们希望以最佳方式绘制一条将两组点分开的线。感知器算法是一个从随机线开始,并逐步移动以构建更好分离器的过程。从山上下降的类比也适用于这里。唯一的不同之处在于,在这个山上,每个点的海拔高度是我们在第“如何比较分类器?错误函数”节中学到的平均感知器误差。

随机梯度下降和批量梯度下降

在本节中,我们开发感知器算法的方式是每次取一个点,并调整感知器(直线)以更好地适应该点。这被称为一个 epoch。然而,正如我们在第三章“我们是一次训练一个点还是多个点?”节中处理线性回归时所做的,更好的方法是每次取一批点,并一次性调整感知器以更好地适应这些点。极端情况是,一次性取集合中的所有点,并一次性调整感知器以更好地适应所有点。在第三章“我们是一次训练一个点还是多个点?”节中,我们称这些方法为随机梯度下降小批量梯度下降批量梯度下降。在本节中,我们使用带有小批量梯度下降的正式感知器算法。数学细节出现在附录 B,“使用梯度下降训练分类模型”节中,其中使用小批量梯度下降对感知器算法进行了全面描述。

编写感知器算法

现在我们已经为我们的情感分析应用开发了感知器算法,在本节中我们将编写它的代码。首先,我们将从头开始编写代码以适应我们的原始数据集,然后我们将使用 Turi Create。在现实生活中,我们总是使用一个包,几乎没有必要自己编写算法。然而,至少编写一些算法是好的——把它想象成做长除法。虽然我们通常不用计算器做长除法,但我们在高中时不得不这样做是好的,因为现在当我们用计算器做长除法时,我们知道背后发生了什么。本节的代码如下,我们使用的数据集显示在表 5.5 中:

表 5.5 外星人数据集,他们说过的话以及他们的情绪。

Aack Beep Happy/Sad
1 0 0
0 2 0
1 1 0
1 2 0
1 3 1
2 2 1
2 3 1
3 2 1

让我们先定义我们的数据集为一个 NumPy 数组。特征对应于两个数字,分别对应aackbeep的出现。标签为快乐句子的 1,悲伤句子的 0。

import numpy as np
features = np.array([[1,0],[0,2],[1,1],[1,2],[1,3],[2,2],[2,3],[3,2]])
labels = np.array([0,0,0,0,1,1,1,1])

这给我们带来了图 5.25 中的图表。在这个图表中,快乐句子是三角形,悲伤句子是正方形。

图 5.25 我们的数据集的图表。三角形是快乐的异形,正方形是悲伤的异形。

编写感知器技巧

在本节中,我们编写感知器技巧的代码。我们将使用随机梯度下降(一次一个点)来编写它,但我们也可以使用小批量或批量梯度下降来编写它。我们首先编写得分函数和预测函数。这两个函数接收相同的输入,即模型的权重、偏置和一个数据点的特征。得分函数返回模型对该数据点的评分,预测函数如果评分大于或等于零则返回 1,如果评分小于零则返回 0。对于此函数,我们使用第三章中“绘制误差函数和知道何时停止运行算法”部分中定义的点积。

def score(weights, bias, features):
    return np.dot(features, weights) + bias ❶

❶ 计算权重和特征之间的点积,加上偏置,并应用步函数

要编写预测函数,我们首先编写步函数。预测是分数的步函数。

def *step*(x):
    if x >= 0:
        return 1
    else:
        return 0

def prediction(weights, bias, features):
    return *step*(score(weights, bias, features)) ❶

❶ 查看分数,如果它是正数或零,则返回 1;如果是负数,则返回 0

接下来,我们编写一个点的误差函数。回想一下,如果点被正确分类,则误差为零;如果点被错误分类,则误差等于分数的绝对值。此函数以模型的权重和偏置以及数据点的特征和标签作为输入。

def error(weights, bias, features, label):
    pred = prediction(weights, bias, features)
    if pred == label: ❶
        return 0
    else: ❷
        return np.abs(score(weights, bias, features))

❶ 如果预测等于标签,则点被正确分类,这意味着误差为零。

❷ 如果预测与标签不同,则点被错误分类,这意味着误差等于分数的绝对值。

我们现在编写一个用于平均感知器误差的函数。此函数计算我们数据集中所有点的误差的平均值。

def mean_perceptron_error(weights, bias, features, labels):
    total_error = 0
    for i in range(len(features)): ❶
        total_error += error(weights, bias, features[i], labels[i])
    return total_error/len(features) ❷

❶ 遍历我们的数据,并对每个点,加上该点的误差,然后返回这个误差

❷ 将误差总和除以点的数量以获得平均感知器误差。

现在我们有了误差函数,我们可以继续编写感知器技巧的代码。我们将编写该节“感知器技巧”末尾找到的算法的简化版本。然而,在笔记本中,你可以找到两种编码方式,第一种使用if语句检查点是否被正确分类。

def perceptron_trick(weights, bias, features, label, learning_rate = 0.01):
    pred = prediction(weights, bias, features)
    for i in range(len(weights)):
        weights[i] += (label-pred)*features[i]*learning_rate ❶
    bias += (label-pred)*learning_rate
    return weights, bias

❶ 使用感知器技巧更新权重和偏置

编写感知器算法

既然我们已经有了感知器技巧,我们可以编写感知器算法了。回想一下,感知器算法包括从一个随机的感知器分类器开始,并多次重复感知器技巧(多达 epoch 的数量)。为了跟踪算法的性能,我们还会在每个 epoch 跟踪平均感知器误差。作为输入,我们有数据(特征和标签)、学习率,我们默认设置为 0.01,以及 epoch 的数量,我们默认设置为 200。感知器算法的代码如下:

def perceptron_algorithm(features, labels, learning_rate = 0.01, epochs = 200):
    weights = [1.0 for i in range(len(features[0]))] ❶
    bias = 0.0
    errors = [] ❷
    for epoch in range(epochs): ❸
        error = mean_perceptron_error(weights, bias, features, labels) ❹
        errors.append(error)
        i = random.randint(0, len(features)-1) ❺
        weights, bias = perceptron_trick(weights, bias, features[i], labels[i])❻
    return weights, bias, errors

❶ 将权重初始化为 1,将偏置初始化为 0。如果您愿意,也可以将它们初始化为小的随机数。

❷ 一个用于存储误差的数组

❸ 重复该过程,直到 epoch 的数量

❹ 计算平均感知器误差并将其存储

❺ 在我们的数据集中随机选择一个点

❻ 将感知器算法应用于更新模型基于该点的权重和偏置

现在我们来在我们的数据集上运行算法吧!

perceptron_algorithm(features, labels)
Output: ([0.6299999999999997, 0.17999999999999938], -1.0400000000000007)

输出显示我们获得的权重和偏置如下:

  • aack的权重:0.63

  • beep的权重:0.18

  • 偏置:-1.04

我们可能会有不同的答案,因为算法中我们选择点的随机性。为了使存储库中的代码始终返回相同的答案,随机种子被设置为零。

图 5.26 显示了两个图表:左边是线拟合,右边是误差函数。对应于结果感知器的线是粗线,它正确地分类了每个点。较细的线是对应于每个 200 个 epoch 后获得的感知器的线。注意,在每个 epoch,线都成为点的更好拟合。随着 epoch 数量的增加,误差降低(主要是),直到在约 140 个 epoch 时达到零,这意味着每个点都被正确分类。

图 5.26 左:我们结果分类器的图。注意,它正确地分类了每个点。右:误差图。注意,我们运行感知器算法的 epoch 越多,误差越低。

那就是感知器算法的代码!正如我之前提到的,在实践中,我们通常不会手动编写算法,而是使用一个包,如 Turi Create 或 Scikit-Learn。这就是我们在下一节要介绍的内容。

使用 Turi Create 编写感知器算法

在本节中,我们学习在 Turi Create 中编写感知器算法。代码与之前的练习在同一个笔记本中。我们的第一个任务是导入 Turi Create 并创建一个包含我们数据的 SFrame,如下所示:

import turicreate as tc

datadict = {'aack': features[:,0], 'beep':features[:,1], 'prediction': labels}
data = tc.SFrame(datadict)

接下来,我们创建并训练我们的感知器分类器,使用logistic_classifier对象和create方法,如以下代码所示。输入是数据集和包含标签(目标)的列名。

perceptron = tc.logistic_classifier.create(data, target='prediction')

输出:

+-----------+----------+--------------+-------------------+
| Iteration | Passes   | Elapsed Time | Training Accuracy |
+-----------+----------+--------------+-------------------+
| 1         | 2        | 1.003120     | 1.000000          |
| 2         | 3        | 1.004235     | 1.000000          |
| 3         | 4        | 1.004840     | 1.000000          |
| 4         | 5        | 1.005574     | 1.000000          |
+-----------+----------+--------------+-------------------+
SUCCESS: Optimal solution found.

注意,感知器算法运行了四个时期,在最后一个时期(实际上,在所有时期),它的训练准确率为 1。这意味着数据集中的每个点都被正确分类。

最后,我们可以使用以下命令查看模型的权重和偏差:

perceptron.coefficients

这个函数的输出显示了以下感知器的权重和偏差:

  • aack的权重:2.70

  • beep的权重:2.46

  • 偏差:-8.96

这些结果与我们手动获得的结果不同,但两个感知器在数据集中都工作得很好。

感知器算法的应用

感知器算法在现实生活中有许多应用。几乎每次我们需要用是或否来回答问题,而答案是从以前的数据中预测出来的,感知器算法都可以帮助我们。以下是感知器算法在现实生活中的应用实例。

垃圾邮件过滤器

就像我们根据句子中的单词预测句子是快乐还是悲伤一样,我们可以根据电子邮件中的单词预测电子邮件是否是垃圾邮件。我们还可以使用其他特征,如下所示:

  • 电子邮件长度

  • 附件大小

  • 发件人数量

  • 我们是否有任何联系人是发件人

目前,感知器算法(及其更高级的对应物,逻辑回归和神经网络)以及其他分类模型,大多数最大的电子邮件提供商都将其用作垃圾邮件分类管道的一部分,并取得了很好的效果。

我们也可以使用感知器算法等分类算法对电子邮件进行分类。将电子邮件分类为个人、订阅和促销是同一个问题。甚至对一封电子邮件的潜在回复也是分类问题,只是现在我们使用的标签是电子邮件的回复。

推荐系统

在许多推荐系统中,向用户推荐视频、电影、歌曲或产品归结为一个是否的问题。在这些情况下,问题可以是以下任何一种:

  • 用户是否会点击我们推荐的视频/电影?

  • 用户是否会观看我们推荐的整个视频/电影?

  • 用户是否会听我们推荐的歌?

  • 用户是否会购买我们推荐的商品?

特征可以是任何东西,从人口统计(用户的年龄、性别、位置),到行为(用户看了哪些视频,听了哪些歌曲,买了哪些产品?)。你可以想象用户向量会很长!为此,需要大量的计算能力和非常巧妙的算法实现。

Netflix、YouTube 和 Amazon 等公司以及其他许多公司,在他们的推荐系统中使用感知器算法或更高级的分类模型。

医疗保健

许多医疗模型也使用感知器算法等分类算法来回答以下问题:

  • 患者是否患有特定的疾病?

  • 某种治疗方法对病人有效吗?

这些模型的特征通常是病人所患的症状和他们的医疗史。对于这类算法,需要非常高的性能水平。为病人推荐错误的治疗方案比推荐一个用户不会观看的视频要严重得多。对于这种分析,请参考第七章,其中我们讨论了准确度以及其他评估分类模型的方法。

计算机视觉

感知器算法等分类算法在计算机视觉中得到了广泛应用,特别是在图像识别中。想象一下,我们有一张图片,我们想要教会计算机判断这张图片是否包含一只狗。这是一个分类模型,其特征是图像的像素。

感知器算法在 MNIST 等精心制作的图像数据集上表现良好,MNIST 是一个手写数字数据集。然而,对于更复杂的图像,它表现不佳。对于这些,人们使用由许多感知器组合而成的模型。这些模型恰当地被称为多层感知器,或神经网络,我们将在第十章中详细学习它们。

摘要

  • 分类是机器学习的一个重要部分。它与回归类似,因为它包括使用标记数据训练算法,并使用它来对未来的(未标记)数据进行预测。与回归的不同之处在于,在分类中,预测是类别,例如是/否、垃圾邮件/非垃圾邮件等。

  • 感知器分类器通过为每个特征和偏差分配一个权重来工作。数据点的得分是权重和特征的乘积之和,加上偏差。如果得分大于或等于零,分类器预测为是。否则,它预测为否。

  • 对于情感分析,感知器由字典中每个单词的得分以及一个偏差组成。快乐词通常会有一个正得分,而悲伤词会有一个负得分。中性词如the可能最终会有一个接近零的得分。

  • 偏差帮助我们判断一个空句子是快乐还是悲伤。如果偏差是正的,那么空句子就是快乐的,如果是负的,那么空句子就是悲伤的。

  • 从图形上看,我们可以将感知器视为一条试图分离两类点的线,这些点可以看作是两种不同颜色的点。在更高维度的空间中,感知器是一个分隔点的超平面。

  • 感知器算法通过从一个随机的线开始,然后慢慢移动它以更好地分隔点来工作。在每一次迭代中,它选择一个随机点。如果点被正确分类,线不会移动。如果它被错误分类,那么线会稍微靠近一点,以越过它并正确分类。

  • 感知器算法有众多应用,包括垃圾邮件检测、推荐系统、电子商务和医疗保健。

练习

练习 5.1

以下是一个数据集,包含已测试 COVID-19 阳性或阴性的患者。他们的症状包括咳嗽(C)、发热(F)、呼吸困难(B)和疲劳(T)。

咳嗽(C) 发热(F) 呼吸困难(B) 疲劳(T) 诊断(D)
患者编号 1 X X X 病人
患者编号 2 X X X 病人
患者编号 3 X X X 病人
患者编号 4 X X X 病人
患者编号 5 X X 健康
患者编号 6 X X 健康
患者编号 7 X 健康
患者编号 8 X 健康

构建一个感知器模型来分类这个数据集。

提示:你可以使用感知器算法,但你可能能够直接找到一个有效的感知器模型。

练习 5.2

考虑一个感知器模型,该模型将点 (x[1], x[2]) 分配给预测 ŷ = step(2x[1] + 3x[2] – 4)。该模型具有方程 2x[1] + 3x[2] – 4 = 0 的边界线。我们有一个点 p = (1, 1) 且标签为 0。

  1. 验证点 p 被模型错误分类。

  2. 计算模型在点 p 处产生的感知器误差。

  3. 使用感知器技巧获得一个新的模型,该模型仍然错误分类 p 但产生更小的误差。你可以使用 η = 0.01 作为学习率。

  4. 计算新模型在点 p 处的预测,并验证感知器误差小于原始误差。

练习 5.3

感知器在构建逻辑门(如 AND 和 OR)方面特别有用。

  1. 构建一个模拟 AND 门的感知器。换句话说,构建一个感知器来拟合以下数据集(其中 x[1], x[2] 是特征,y 是标签):

    x[1] x[2] y
    0 0 0
    0 1 0
    1 0 0
    1 1 1
  2. 类似地,根据以下数据集构建一个模拟 OR 门的感知器:

    x[1] x[2] y
    0 0 0
    0 1 1
    1 0 1
    1 1 1
  3. 证明不存在一个感知器可以模拟以下数据集给出的 XOR 门:

    x[1] x[2] y
    0 0 0
    0 1 1
    1 0 1
    1 1 0

6 一种连续的分割点方法:逻辑分类器

在本章

  • 分类模型中硬任务和软任务之间的区别

  • Sigmoid 函数,一种连续的激活函数

  • 离散感知器与连续感知器,也称为逻辑分类器

  • 用于分类数据的逻辑回归算法

  • 在 Python 中编码逻辑回归算法

  • 在 Turi Create 中使用逻辑分类器分析电影评论的情感

  • 使用 softmax 函数构建超过两个类别的分类器

在上一章中,我们构建了一个分类器,用于判断一个句子是快乐还是悲伤。但正如我们可以想象的那样,有些句子比其他句子更快乐。例如,“我感觉很好”和“今天是我一生中最美好的一天!”这两个句子都是快乐的,但第二个句子比第一个句子快乐得多。不是很好吗?有一个分类器不仅能预测句子是快乐还是悲伤,还能给出句子快乐的评分——比如说,一个告诉我们第一个句子有 60%快乐的分类器,第二个句子有 95%快乐的分类器?在这一章中,我们定义了逻辑分类器,它正是这样做的。这个分类器给每个句子分配一个从 0 到 1 的分数,这样句子越快乐,它收到的分数就越高。

简而言之,逻辑分类器是一种模型,它的工作方式与感知器分类器非常相似,除了它返回的是 0 到 1 之间的一个数字。在这种情况下,目标是给最悲伤的句子分配接近 0 的分数,给最快乐的句子分配接近 1 的分数,给中性的句子分配接近 0.5 的分数。这个 0.5 的阈值在实践中很常见,尽管是任意的。在第七章中,我们将看到如何调整它以优化我们的模型,但本章我们使用 0.5。

本章依赖于第五章,因为我们在这里开发的算法在技术差异之外是相似的。确保你很好地理解第五章将有助于你理解本章的内容。在第五章中,我们使用一个错误函数描述了感知器算法,该函数告诉我们感知器分类器有多好,以及一个迭代步骤,该步骤将分类器转变为一个稍微好一点的分类器。在这一章中,我们学习逻辑回归算法,它以类似的方式工作。主要区别如下:

  • 步函数被一个新的激活函数所取代,该函数返回介于 0 和 1 之间的值。

  • 感知器错误函数被一个新的基于概率计算的错误函数所取代。

  • 感知器技巧被一个新的技巧所取代,该技巧基于这个新的错误函数改进了分类器。

旁白:在本章中,我们进行了大量的数值计算。如果你跟随方程式,可能会发现你的计算与书中所述的数值略有不同。书中在方程式的最后一位数进行四舍五入,而不是在步骤之间。然而,这应该对最终结果的影响非常小。

在本章末尾,我们将所学知识应用于 IMDB(www.imdb.com)网站上真实的电影评论数据集。我们使用逻辑分类器来预测电影评论是正面还是负面。

本章的代码可在以下 GitHub 仓库中找到:github.com/luisguiserrano/manning/tree/master/Chapter_6_Logistic_Regression.

逻辑分类器:感知器分类器的连续版本

在第五章中,我们介绍了感知器,这是一种使用数据特征进行预测的分类器。预测可以是 1 或 0。这被称为离散感知器,因为它从离散集合(包含 0 和 1 的集合)中返回一个答案。在本章中,我们学习连续感知器,它返回一个介于 0 和 1 之间的任何数值。连续感知器的一个更常见的名称是逻辑分类器。逻辑分类器的输出可以解释为分数,逻辑分类器的目标是分配尽可能接近点的标签——标签为 0 的点应得到接近 0 的分数,标签为 1 的点应得到接近 1 的分数。

我们可以像离散感知器一样可视化连续感知器:用一条线(或高维平面)分隔两个数据类别。唯一的区别是,离散感知器预测线的一侧的所有内容都有标签 1,另一侧有标签 0,而连续感知器根据点相对于线的位置分配一个从 0 到 1 的值。线上的每个点都得到 0.5 的值。这个值意味着模型无法决定句子是快乐还是悲伤。例如,在正在进行的情感分析示例中,句子“今天是星期二”既不快乐也不悲伤,因此模型会分配一个接近 0.5 的分数。正区间的点得到大于 0.5 的分数,其中正方向上离 0.5 线更远的点得到更接近 1 的值。负区间的点得到小于 0.5 的分数,其中离线更远的点得到更接近 0 的值。没有点得到 1 或 0 的值(除非我们考虑无穷远的点),如图 6.1 所示。

图 6.1

图 6.1 左:感知器算法训练一个离散感知器,其中预测值为 0(快乐)和 1(悲伤)。右:逻辑回归算法训练一个连续感知器,其中预测值是介于 0 和 1 之间的数字,表示预测的幸福程度。

为什么我们称之为分类而不是回归,鉴于逻辑分类器并不是输出一个状态本身,而是一个数字?原因是,在评分之后,我们可以将它们分为两类,即得分在 0.5 或更高的点和得分低于 0.5 的点。从图形上看,这两类点被边界线分开,就像感知器分类器一样。然而,我们用来训练逻辑分类器的算法被称为逻辑回归算法。这个符号有点奇特,但我们将保持原样以匹配文献。

分类的一种概率方法:sigmoid 函数

我们如何稍微修改上一节中的感知器模型,为每个句子得到一个分数,而不是简单的“快乐”或“悲伤”?回想一下我们在感知器模型中是如何进行预测的。我们通过分别评分每个单词并将评分相加,再加上偏差来评分每个句子。如果评分是正的,我们预测句子是快乐的;如果评分是负的,我们预测句子是悲伤的。换句话说,我们所做的是将阶跃函数应用于评分。阶跃函数在非负评分时返回 1,在负评分时返回 0。

现在我们做类似的事情。我们取一个接收分数作为输入并输出 0 到 1 之间数字的函数。如果分数是正的,则数字接近 1;如果分数是负的,则数字接近 0。如果分数是零,则输出是 0.5。想象一下,如果你能将整个数轴压缩到 0 和 1 之间的区间,它看起来就像图 6.2 中的函数。

图片

图 6.2 Sigmoid 函数将整个数轴映射到区间(0,1)。

许多函数可以在这里帮助我们,在这种情况下,我们使用一个称为sigmoid的函数,用希腊字母sigmaσ)表示。sigmoid 的公式如下:

图片

这里真正重要的是公式本身,而是函数所做的工作,即将实数线压缩到区间(0,1)。在图 6.3 中,我们可以看到阶跃函数和 sigmoid 函数的图形比较。

图片

图 6.3 左:用于构建离散感知器的阶跃函数。对于任何负输入,它输出 0;对于任何正或零输入,它输出 1。它在零处有间断。右:用于构建连续感知器的 sigmoid 函数。对于负输入,它输出小于 0.5 的值;对于正输入,它输出大于 0.5 的值。在零处,它输出 0.5。它在任何地方都是连续且可微的。

S 形函数在一般情况下,由于以下几个原因,通常比阶梯函数更好。连续预测比离散预测提供了更多信息。此外,当我们进入微积分时,S 形函数的导数比阶梯函数要好得多。阶梯函数的导数为零,除了原点,在那里它是未定义的。在表 6.1 中,我们计算了 S 形函数的一些值,以确保函数按我们的预期工作。

表 6.1 在 S 形函数下的某些输入及其输出。注意,对于大的负输入,输出接近 0,而对于大的正输入,输出接近 1。对于输入 0,输出是 0.5。

x σ(x)
–5 0.007
–1 0.269
0 0.5
1 0.731
5 0.993

逻辑分类器的预测是通过将 S 形函数应用于得分得到的,它返回一个介于 0 和 1 之间的数字,正如之前提到的,在我们的例子中,这可以解释为句子快乐的概率。

在第五章中,我们为感知器定义了一个误差函数,称为感知器误差。我们使用这个感知器误差来迭代构建感知器分类器。在本章中,我们遵循相同的程序。连续感知器的误差与离散预测器的误差略有不同,但它们仍然有相似之处。

数据集和预测

在本章中,我们使用与第五章相同的用例,其中我们有一个外星语言的句子数据集,标签为“快乐”和“悲伤”,分别用 1 和 0 表示。本章的数据集与第五章略有不同,如表 6.2 所示。

表 6.2 句子的数据集及其快乐/悲伤标签。坐标是句子中单词 aack 和 beep 的出现次数。

单词 坐标(#aack, #beep) 标签
句子 1 Aack beep beep aack aack. (3,2) 悲伤 (0)
句子 2 Beep aack beep. (1,2) 快乐 (1)
句子 3 Beep! (0,1) 快乐 (1)
句子 4 Aack aack. (2,0) 悲伤 (0)

我们使用的模型具有以下权重和偏差:

逻辑分类器 1

  • Aack的权重:a = 1

  • Beep的权重:b = 2

  • 偏差:c = –4

我们使用与第五章相同的符号,其中变量 x[aack] 和 x[beep] 分别跟踪 aackbeep 的出现次数。感知器分类器会根据公式 ŷ = step(ax[aack] + bx[beep] + c) 进行预测,但由于这是一个逻辑分类器,它使用 S 形函数而不是阶梯函数。因此,其预测为 ŷ = σ(ax[aack] + bx[beep] + c)。在这种情况下,预测如下:

预测ŷ = σ(1 · x[aack] + 2 · x[beep] – 4)

因此,分类器对我们的数据集做出了以下预测:

  • 句子 1ŷ = σ(3 + 2 · 2 – 4) = σ(3) = 0.953。

  • 句子 2ŷ = σ(1 + 2 · 2 – 4) = σ(1) = 0.731。

  • 句子 3ŷ = σ(0 + 2 · 1 – 4) = σ(–2) = 0.119。

  • 句子 4ŷ = σ(2 + 2 · 0 – 4) = σ(–2) = 0.119。

“快乐”和“悲伤”类别的边界是方程x[aack] + 2x[beep] – 4 = 0 的线,如图 6.4 所示。

图 6.4 表 6.2 中数据集的预测图。注意点 2 和 4 被正确分类,但点 1 和 3 被错误分类。

这条线将平面分为正(快乐)和负(悲伤)区域。正区域由预测值高于或等于 0.5 的点组成,负区域由预测值小于 0.5 的点组成。

误差函数:绝对误差、平方误差和对数损失

在本节中,我们为逻辑分类器构建了三个误差函数。一个好的误差函数应该具备哪些特性?以下是一些例子:

  • 如果一个点被正确分类,误差将是一个小数。

  • 如果一个点被错误分类,误差将是一个大数。

  • 对于一组点的分类器的误差是所有点误差的总和(或平均值)。

许多函数满足这些属性,我们将看到其中的三个;绝对误差、平方误差和对数损失。在表 6.3 中,我们有与我们的数据集中句子对应的四个点的标签和预测,以下是其特征:

  • 线上的点被预测为 0.5。

  • 位于正区域的点被预测为高于 0.5,并且一个点在这个方向上离线越远,其预测值就越接近 1。

  • 位于负区域的点被预测为低于 0.5,并且一个点在这个方向上离线越远,其预测值就越接近 0。

表 6.3 四个点——两个快乐和两个悲伤及其预测,如图 6.4 所示。注意点 1 和 4 被正确分类,但点 2 和 3 没有被分类。一个好的误差函数应该将小的误差分配给正确分类的点,将大的误差分配给分类不良的点。

标签 预测 误差?
1 0 (悲伤) 0.953 应该很大
2 1 (快乐) 0.731 应该很小
3 1 (快乐) 0.119 应该很大
4 0 (悲伤) 0.119 应该很小

注意到在表 6.3 中,点 2 和 4 得到了一个接近标签的预测,因此它们应该有小的误差。相比之下,点 1 和 3 得到了一个远离标签的预测,因此它们应该有大的误差。具有这种特定属性的三个误差函数如下:

误差函数 1:绝对误差

绝对误差与我们在第三章中为线性回归定义的绝对误差相似。它是预测值与标签之间差异的绝对值。正如我们所见,当预测值远离标签时,误差很大;而当它们接近时,误差很小。

误差函数 2:平方误差

再次,就像在线性回归中一样,我们也有平方误差。这是预测值和标签之间的差的平方,它以与绝对误差相同的原因工作。

在我们继续之前,让我们计算表 6.4 中点的绝对误差和平方误差。请注意,点 2 和 4(正确分类)有小的误差,而点 1 和 3(错误分类)有较大的误差。

表 6.4 我们附上了表 6.3 中点的绝对误差和平方误差。请注意,正如我们所希望的,点 2 和 4 有小的误差,而点 1 和 3 有较大的误差。

标签 预测标签 绝对误差 平方误差
1 0 (悲伤) 0.953 0.953 0.908
2 1 (快乐) 0.731 0.269 0.072
3 1 (快乐) 0.119 0.881 0.776
4 0 (悲伤) 0.119 0.119 0.014

绝对误差和平方误差可能会让你想起回归中使用的误差函数。然而,在分类中,它们并不那么广泛使用。最流行的是我们接下来要看到的下一个函数。为什么它更受欢迎?与下一个函数相关的数学(导数)工作得更好。此外,这些误差都非常小。事实上,无论分类有多差,它们都小于 1。原因是介于 0 和 1 之间的两个数字之间的差(或差的平方)最多为 1。为了正确训练模型,我们需要误差函数能够取比这更大的值。幸运的是,第三个误差函数可以为我们做到这一点。

误差函数 3:log loss

log loss 是连续感知器最广泛使用的误差函数。本书中的大多数误差函数名称中都包含“误差”一词,但这个函数的名称中却包含“loss”(损失)。名称中的 log 部分来源于公式中使用的自然对数。然而,log loss 的真正灵魂是概率。

连续感知器的输出是介于 0 和 1 之间的数字,因此它们可以被认为是概率。模型为每个数据点分配一个概率,即该点快乐的概率。从这个结果中,我们可以推断出该点悲伤的概率,即快乐概率的 1 减去。例如,如果预测值为 0.75,这意味着模型认为该点有 0.75 的概率是快乐的,有 0.25 的概率是悲伤的。

现在,这里有一个主要的观察结果。模型的目标是为快乐点(标签为 1 的点)分配高概率,为悲伤点(标签为 0 的点)分配低概率。请注意,一个点悲伤的概率是它快乐概率的 1 减去。因此,对于每个点,让我们计算模型为其标签分配的概率。对于我们的数据集中的点,相应的概率如下:

  • 点 1

    • 标签 = 0 (悲伤)

    • 预测(快乐概率)= 0.953

    • 成为该标签的概率:1 – 0.953 = 0.047

  • 第 2 点:

    • 标签 = 1(快乐)

    • 预测(快乐的概率)= 0.731

    • 成为该标签的概率:0.731

  • 第 3 点:

    • 标签 = 1(快乐)

    • 预测(快乐的概率)= 0.119

    • 成为该标签的概率:0.119

  • 第 4 点:

    • 标签 = 0(悲伤)

    • 预测(快乐的概率)= 0.119

    • 成为该标签的概率:1 – 0.119 = 0.881

注意,第 2 点和第 4 点是分类良好的点,模型分配了很高的概率认为它们是自己的标签。相比之下,第 1 点和第 3 点是分类不良的点,模型分配了很低的概率认为它们是自己的标签。

与感知器分类器相比,逻辑回归分类器不给出明确的答案。感知器分类器会说,“我 100%确信这个点是快乐的”,而逻辑回归分类器会说,“你的点有 73%的概率是快乐的,27%的概率是悲伤的。”虽然感知器分类器的目标是尽可能多地正确,但逻辑回归分类器的目标是给每个点分配具有最高可能性的正确标签。这个分类器将概率 0.047、0.731、0.119 和 0.881 分配给四个标签。理想情况下,我们希望这些数字更高。我们如何衡量这四个数字呢?一种方法是将它们相加或取平均值。但由于它们是概率,自然的方法是相乘。当事件是独立时,它们同时发生的概率是它们概率的乘积。如果我们假设这四个预测是独立的,那么这个模型分配给标签“悲伤、快乐、快乐、悲伤”的概率是这四个数字的乘积,即 0.047 · 0.731 · 0.119 · 0.881 = 0.004。这是一个非常小的概率。我们希望一个更适合这个数据集的模型会产生更高的概率。

我们刚才计算的概率似乎是我们模型的良好度量,但它有一些问题。例如,它是由许多小数相乘得到的。许多小数的乘积往往非常小。想象一下,如果我们的数据集有一百万个点,概率将是这些在 0 到 1 之间的一个百万个数的乘积。这个数字可能非常小,以至于计算机可能无法表示它。此外,操作一个百万个数的乘积极其困难。我们是否有办法将它转换成更容易操作的东西,比如和?

幸运的是,我们有一个方便的方法将产品转换为和——使用对数。对于整本书,我们只需要了解对数的一个特性,那就是它可以将产品转换为和。更具体地说,两个数的乘积的对数是这些数的对数之和,如下所示:

ln(a · b) = ln(a) + ln(b)

我们可以使用以 2、10 或 e 为底的对数。在本章中,我们使用自然对数,其底数为 e。然而,如果我们使用任何其他底数的对数,也可以得到相同的结果。

如果我们将自然对数应用于我们的概率乘积,我们得到

ln(0.047 · 0.731 · 0.119 · 0.881) = ln(0.047) + ln(0.731) + ln(0.119) + ln(0.881) = –5.616.

一个小细节。注意结果是一个负数。实际上,这总是会发生,因为 0 到 1 之间的数的对数总是负数。因此,如果我们取概率乘积的负对数,它总是正数。

对数损失定义为概率乘积的负对数,这也就是概率的负对数之和。此外,每个加数都是该点的对数损失。在表 6.5 中,你可以看到每个点的对数损失的计算。通过将所有点的对数损失相加,我们得到总对数损失为 5.616。

表 6.5 计算数据集中点的对数损失。注意,分类良好的点(2 和 4)具有较小的对数损失,而分类不良的点(1 和 3)具有较大的对数损失。

标签 预测标签 是其标签的概率 对数损失
1 0 (悲伤) 0.953 0.047 –ln(0.047) = 3.049
2 1 (快乐) 0.731 0.731 –ln(0.731) = 0.313
3 1 (快乐) 0.119 0.119 –ln(0.119) = 2.127
4 0 (悲伤) 0.119 0.881 –ln(0.881) = 0.127

注意,确实,分类良好的点(2 和 4)具有较小的对数损失,而分类不良的点具有较大的对数损失。原因是如果数字x接近 0,则–ln(x)是一个大数,但如果x接近 1,则–ln(x)是一个小数。

总结一下,计算对数损失的步骤如下:

  • 对于每个点,我们计算分类器给出其标签的概率。

    • 对于快乐点,这个概率就是得分。

    • 对于悲伤点,这个概率是得分减去 1。

  • 我们将这些概率相乘,以获得分类器对这些标签给出的总概率。

  • 我们将自然对数应用于该总概率。

  • 乘积的对数是各个因子的对数之和,因此我们得到一个对数之和,每个点一个。

  • 我们注意到所有项都是负数,因为小于 1 的数的对数是负数。因此,我们将所有项乘以–1 以得到正数的和。

  • 这个和就是我们的对数损失。

对数损失与交叉熵的概念密切相关,交叉熵是衡量两个概率分布之间相似度的一种方法。关于交叉熵的更多细节可以在附录 C 的参考文献中找到。

对数损失的公式

一个点的对数损失可以简化为一个漂亮的公式。回想一下,对数损失是点是其标签(快乐或悲伤)的概率的负对数。模型对每个点的预测是 ŷ,即点是快乐的概率。因此,根据模型,点是悲伤的概率是 1 – ŷ。因此,我们可以将对数损失写成以下形式:

  • 如果标签是 0:对数损失 = –ln(1 – ŷ)

  • 如果标签是 1:对数损失 = –ln(ŷ)

因为标签是 y,之前的 if 语句可以简化为以下公式:

对数损失 = –y ln(ŷ) – (1 – y) ln(1 – ŷ)

之前的公式之所以有效,是因为如果标签是 0,第一个加数是 0,如果标签是 1,第二个加数是 0。当我们提到点的对数损失或整个数据集的对数损失时,我们使用术语 对数损失。数据集的对数损失是每个点的对数损失的总和。

使用对数损失比较分类器

现在我们已经确定了对逻辑分类器的误差函数,即对数损失,我们可以用它来比较两个分类器。回想一下,我们本章中使用的分类器由以下权重和偏差定义:

逻辑分类器 1

  • Aack 权重:a = 1

  • Beep 权重:b = 2

  • 偏差:c = –4

在本节中,我们将其与以下逻辑分类器进行比较:

逻辑分类器 2

  • Aack 权重:a = –1

  • Beep 权重:b = 1

  • 偏差:c = 0

每个分类器做出的预测如下:

  • 分类器 1ŷ = σ(x[aack] + 2x[beep] – 4)

  • 分类器 2ŷ = σ(–x[aack] + x[beep])

两个分类器的预测记录在表 6.6 中,数据集和两个边界线的图示在图 6.5 中。

表 6.6 计算数据集中点的对数损失。注意,分类器 2 的预测比分类器 1 的预测更接近点的标签。因此,分类器 2 是一个更好的分类器。

标签 分类器 1 预测 分类器 2 预测
1 0 (悲伤) 0.953 0.269
2 1 (快乐) 0.731 0.731
3 1 (快乐) 0.119 0.731
4 0 (悲伤) 0.881 0.119

图 6.5 左:一个犯两个错误的糟糕分类器。右:一个正确分类所有四个点的良好分类器。

从表 6.6 和图 6.5 的结果来看,很明显分类器 2 比分类器 1 好得多。例如,在图 6.5 中,我们可以看到分类器 2 正确地将两个快乐句子定位在正区域,将两个悲伤句子定位在负区域。接下来,我们比较对数损失。回想一下,分类器 1 的对数损失是 5.616。我们应该得到一个更小的对数损失,因为这是一个更好的分类器。

根据公式 对数损失 = –y ln(ŷ) – (1 – y) ln(1 – ŷ),我们数据集中每个点的对数损失如下:

  • 点 1y = 0, ŷ = 0.269:

    • 对数损失 = ln(1 – 0.269) = 0.313
  • 点 2y = 1, ŷ = 0.73:

    • 对数损失 = ln(0.721) = 0.313
  • 点 3y = 1, ŷ = 0.73:

    • 对数损失 = ln(731) = 0.313
  • 点 4y = 0, ŷ = 0.119:

    • 对数损失 = ln(1 – 0.119) = 0.127

数据集的总对数损失是这四个值的总和,为 1.067。请注意,这个值远小于 5.616,这证实了分类器 2 确实比分类器 1 好得多。

如何找到一个好的逻辑分类器?逻辑回归算法

在本节中,我们学习如何训练逻辑分类器。这个过程与训练线性回归模型或感知器分类器的过程类似,包括以下步骤:

  • 从一个随机的逻辑分类器开始。

  • 重复多次:

    • 略微改进分类器。
  • 测量对数损失以决定何时停止循环。

算法的关键是循环内部的步骤,它包括略微改进逻辑分类器。这个步骤使用了一个称为逻辑技巧的技巧。逻辑技巧与感知器技巧类似,如下一节所示。

逻辑技巧:略微改进连续感知器的方法

回想第五章中提到的感知器技巧,它包括从一个随机的分类器开始,依次选择一个随机点,并应用感知器技巧。它有以下两种情况:

  • 案例 1:如果点被正确分类,保持线不变。

  • 案例 2:如果点被错误分类,将线稍微移近该点。

逻辑技巧(如图 6.6 所示)与感知器技巧类似。唯一不同的是,当点被正确分类时,我们将线远离点。它有以下两种情况:

  • 案例 1:如果点被正确分类,稍微将线移离该点。

  • 案例 2:如果点被错误分类,稍微将线移近该点。

图片

图 6.6 在逻辑回归算法中,每个点都有发言权。被正确分类的点告诉线远离,进入正确的区域更深。被错误分类的点告诉线靠近,希望有一天能位于线的正确一侧。

为什么我们要将线移动到正确分类的点之外?如果点被正确分类,这意味着它在相对于线的正确区域内。如果我们把线移得更远,我们就把点推向正确的区域更深。因为预测是基于点与边界线的距离,对于位于正(快乐)区域的点,如果点离线更远,预测会增加。同样,对于位于负(悲伤)区域的点,如果点离线更远,预测会降低。因此,如果点的标签是 1,我们是在增加预测(使其更接近 1),如果点的标签是 0,我们是在减少预测(使其更接近 0)。

例如,看看分类器 1 和我们的数据集中的第一句话。回想一下,这个分类器的权重 a = 1,b = 2,以及偏置 c = –4。这句话对应于坐标点 (x[aack], x[beep]) = (3,2),标签 y = 0。我们对这个点的预测是 ŷ = σ(3 + 2 · 2 – 4) = σ(3) = 0.953。预测值与标签相差甚远,因此误差很高:实际上,在表 6.5 中,我们计算出的误差是 3.049。这个分类器犯的错误是认为这句话比实际情况更快乐。因此,为了调整权重以确保分类器降低对这个句子的预测,我们应该大幅降低权重 ab 和偏置 c

使用相同的逻辑,我们可以分析如何调整权重以改进其他点的分类。对于数据集中的第二句话,标签是 y = 1,预测值是 0.731。这是一个好的预测,但如果我们想改进它,我们应该稍微增加权重和偏置。对于第三句话,因为标签是 y = 1,预测值是 ŷ = 0.119,我们应该大幅增加权重和偏置。最后,对于第四句话,标签是 y = 0,预测值是 ŷ = 0.119,因此我们应该稍微降低权重和偏置。这些总结在表 6.7 中。

表 6.7 计算数据集中点的对数损失。注意,被正确分类的点(2 和 4)对数损失很小,而分类不良的点(1 和 3)对数损失很大。

标签 y 分类器 1 预测 y 如何调整权重 ab 和偏置 c yŷ
1 0 0.953 大幅降低 –0.953
2 1 0.731 稍微增加 0.269
3 1 0.119 大幅增加 0.881
4 0 0.119 稍微降低 –0.119

以下观察结果可以帮助我们确定我们想要添加到权重和偏置中的完美数量,以改进预测:

  • 观察 1:表 6.7 的最后一列是标签减去预测的值。注意这个表中最后两列之间的相似性。这表明我们应该更新权重和偏置的量应该是yŷ的倍数。

  • 观察 2:想象一个句子,其中单词aack出现了 10 次,而beep出现了 1 次。如果我们要向这两个单词的权重中添加(或减去)一个值,那么认为aack的权重应该更新得更多是有意义的,因为这个单词对句子的整体得分更为关键。因此,我们应该更新aack权重的量应该乘以x[aack],而我们应该更新beep权重的量应该乘以x[beep] .

  • 观察 3:我们更新权重和偏置的量也应该乘以学习率η,因为我们想确保这个数字很小。

将三个观察结果结合起来,我们得出以下更新权重是一个很好的集合:

  • a = a + η(yŷ)x[1]

  • b = b + η(yŷ)x[2]

  • c = c + η(yŷ)

因此,逻辑技巧的伪代码如下。注意它与我们第五章“感知器技巧”部分末尾学习的感知器技巧的伪代码是多么相似。

逻辑技巧的伪代码

输入:

  • 一个具有权重a, b,和偏置c的逻辑分类器

  • 坐标为(x[1], x[2])和标签y的点

  • 一个小的值η(学习率)

输出:

  • 一个具有新权重a', b',和偏置c'的感知器,至少与输入一样好

程序:

  • 感知器在点上的预测是ŷ = σ(ax[1] + bx[2] + c).

返回:

  • 具有以下权重和偏置的感知器:

    • a = a + η(y - ŷ)x[1]

    • b = b + η(y - ŷ)x[2]

    • c = c + η(y - ŷ)

我们在逻辑技巧中更新权重和偏置的方式并非巧合。它来自于应用梯度下降算法来减少对数损失。数学细节在附录 B 的第“使用梯度下降训练分类模型”部分中描述。

为了验证逻辑技巧在我们的案例中是否有效,让我们将其应用于当前数据集。实际上,我们将分别对四个点应用这个技巧,看看每个点会如何修改模型的权重和偏置。最后,我们将比较更新前后的对数损失,以验证它确实已经减少。对于以下计算,我们使用学习率η = 0.05。

使用每个句子更新分类器

使用第一句话:

  • 初始权重和偏置:a = 1, b = 2, c = –4

  • 标签:y = 0

  • 预测:0.953

  • 初始对数损失:–0 · ln(0.953) – 1 ln(1 – 0.953) = 3.049

  • 点的坐标:x[aack] = 3, x[beep] = 2

  • 学习率:η = 0.01

  • 更新后的权重和偏置:

    • a' = 1 + 0.05 · (0 – 0.953) · 3 = 0.857

    • b' = 2 + 0.05 · (0 – 0.953) · 2 = 1.905

    • c' = –4 + 0.05 · (0 – 0.953) = –4.048

  • 更新预测:ŷ = σ(0.857 · 3 + 1.905 · 2 – 4.048 = 0.912. (注意,预测降低,因此现在更接近标签 0)。

  • 最终对数损失:–0 · ln(0.912) – 1 ln(1 – 0.912) = 2.426. (注意,错误从 3.049 减少到 2.426)。

其他三个点的计算显示在表 6.8 中。注意,在表中,更新后的预测始终比初始预测更接近标签,最终对数损失始终小于初始对数损失。这意味着无论我们使用哪个点进行逻辑回归技巧,我们都会改进该点的模型并减少最终对数损失。

表 6.8 所有点的预测计算、对数损失、更新权重和更新预测。

坐标 标签 初始预测 初始对数损失 更新权重: 更新预测 最终对数损失
1 (3,2) 0 0.953 3.049 a’ = 0.857b’ = 1.905c’ = –4.048 0.912 2.426
2 (1,2) 1 0.731 0.313 a’ = 1.013b’ = 2.027c’ = –3.987 0.747 0.292
3 (0,1) 1 0.119 2.127 a’ = 1b’ = 2.044c’ = –3.956 0.129 2.050
4 (2,0) 0 0.119 0.127 a’ = 0.988b’ = 2c’ = –4.006 0.127 0.123

在本节的开头,我们讨论了逻辑回归技巧也可以通过几何方式可视化,即相对于点移动边界线。更具体地说,如果点被错误分类,则将线移近点;如果点被正确分类,则将线移远点。我们可以通过在表 6.8 中的四种情况下绘制原始分类器和修改后的分类器来验证这一点。在图 6.7 中,你可以看到这四个图。在每一个图中,实线是原始分类器,虚线是通过应用逻辑回归技巧得到的分类器,使用突出显示的点。注意,正确分类的点 2 和 4 将线推开,而错误分类的点 1 和 3 将线移近它们。

图 6.7 将逻辑回归技巧应用于每个四个数据点。注意,对于正确分类的点,线远离点,而对于错误分类的点,线移近点。

重复多次逻辑回归技巧:逻辑回归算法

逻辑回归算法是我们用来训练逻辑分类器的算法。与感知器算法通过多次重复感知器技巧一样,逻辑回归算法通过多次重复逻辑回归技巧来实现。伪代码如下:

逻辑回归算法的伪代码

输入:

  • 标记为 1 和 0 的点数据集

  • 许多个时代,n

  • 学习率 η

输出:

  • 由一组权重和偏差组成的逻辑分类器,该分类器适合数据集

程序:

  • 从随机值开始,为逻辑分类器的权重和偏差设置初始值。

  • 重复多次:

    • 选择一个随机数据点。

    • 使用逻辑技巧更新权重和偏差。

返回:

  • 使用更新后的权重和偏差的感知器分类器

正如我们之前看到的,逻辑技巧的每一次迭代要么将线移动到错误分类点的附近,要么将其移动到正确分类点的附近。

随机梯度下降、小批量梯度下降和批量梯度下降

逻辑回归算法,与线性回归和感知器一样,是基于梯度下降的另一种算法。如果我们使用梯度下降来减少对数损失,那么梯度下降步骤就变成了逻辑技巧。

通用逻辑回归算法不仅适用于具有两个特征的数据集,也适用于具有我们想要的任何数量特征的数据集。在这种情况下,就像感知器算法一样,边界不会看起来像一条线,而会像更高维空间中分割点的更高维超平面。然而,我们不需要可视化这个更高维的空间;我们只需要构建一个具有与我们的数据中特征数量一样多的权重的逻辑回归分类器。逻辑技巧和逻辑算法以与我们之前章节中相同的方式更新权重。

就像我们之前学过的算法一样,在实践中,我们不会一次只选择一个点来更新模型。相反,我们使用小批量梯度下降——我们取一批点并更新模型以更好地适应这些点。对于完全通用的逻辑回归算法和利用梯度下降对逻辑技巧的彻底数学推导,请参阅附录 B,第“使用梯度下降训练分类模型”节。

编写逻辑回归算法

在本节中,我们将看到如何手动编写逻辑回归算法。本节的代码如下:

我们将在第五章中使用的数据集上测试我们的代码。数据集如表 6.9 所示。

表 6.9 我们将用逻辑分类器拟合的数据集

Aack x[1] Beep x[2] 标签 y
1 0 0
0 2 0
1 1 0
1 2 0
1 3 1
2 2 1
2 3 1
3 2 1

加载我们小型数据集的代码如下,数据集的图表如图 6.8 所示:

import numpy as np
features = np.array([[1,0],[0,2],[1,1],[1,2],[1,3],[2,2],[2,3],[3,2]])
labels = np.array([0,0,0,0,1,1,1,1])

图 6.8 我们数据集的图表,其中快乐的句子由三角形表示,悲伤的句子由正方形表示。

手动编写逻辑回归算法

在本节中,我们展示了如何手动编写逻辑技巧和逻辑回归算法。更一般地,我们将为具有n个权重的数据集编写逻辑回归算法。我们使用的符号如下:

  • 特征:x[1], x[2], … , x[n]

  • 标签:y

  • 权重:w[1], w[2], … , w[n]

  • 偏置:b

特定句子的分数是该句子中每个单词的权重(w[i])乘以该单词出现的次数(x[i])的总和的 sigmoid 函数,再加上偏置(b)。注意,我们使用求和符号表示

.

  • 预测:ŷ = σ(w[1]x[1] + w[2]x[2] + … + w[n]x[n] + b) = σ(Σ[i]^n[=1]w[i] x[i] + b).

对于我们当前的问题,我们将x[aack]和x[beep]分别称为x[1]和x[2]。它们对应的权重是w[1]和w[1],偏置是b

我们首先编写 sigmoid 函数、分数和预测的代码。回忆一下 sigmoid 函数的公式

def sigmoid(x):
    return np.exp(x)/(1+np.exp(x))

对于分数函数,我们使用特征和权重之间的点积。回忆一下,向量(x[1], x[2], … , x[n])和(w[1], w[2], … , w[n])之间的点积是w[1] x[1] + w[2] x[2] + … + w[n] x[n]。

def score(weights, bias, features):
    return np.dot(weights, features) + bias

最后,回忆一下,预测是 sigmoid 激活函数应用于分数。

def prediction(weights, bias, features):
    return sigmoid(score(weights, bias, features))

现在我们有了预测,我们可以继续计算 log loss。回忆一下,log loss 的公式是

log loss = –y ln(ŷ) – (1 – y) ln(1 – y).

让我们按照以下方式编写该公式:

def log_loss(weights, bias, features, label):
    pred = prediction(weights, bias, features)
    return -label*np.log(pred) - (1-label)*np.log(1-pred)

我们需要整个数据集上的 log loss,因此我们可以将所有数据点相加,如下所示:

def total_log_loss(weights, bias, features, labels):
    total_error = 0
    for i in range(len(features)):
        total_error += log_loss(weights, bias, features[i], labels[i])
    return total_error

现在我们准备编写逻辑回归技巧和逻辑回归算法的代码。在超过两个变量的情况下,回忆一下,第i个权重的逻辑回归步骤是以下公式,其中η是学习率:

  • w[i] → w[i] + η(yŷ)x[i] for i = 1, 2, … , n

  • bb + η(yŷ) for i = 1, 2, … , n.

def logistic_trick(weights, bias, features, label, learning_rate = 0.01):
    pred = prediction(weights, bias, features)
    for i in range(len(weights)):
        weights[i] += (label-pred)*features[i]*learning_rate
        bias += (label-pred)*learning_rate
    return weights, bias

def logistic_regression_algorithm(features, labels, learning_rate = 0.01, epochs = 1000):
    utils.plot_points(features, labels)
    weights = [1.0 for i in range(len(features[0]))]
    bias = 0.0
    errors = []
    for i in range(epochs):
        errors.append(total_log_loss(weights, bias, features, labels))
        j = random.randint(0, len(features)-1)
        weights, bias = logistic_trick(weights, bias, features[j], labels[j])
    return weights, bias

现在我们可以运行逻辑回归算法来构建一个适合我们数据集的逻辑分类器,如下所示:

logistic_regression_algorithm(features, labels)
([0.46999999999999953, 0.09999999999999937], -0.6800000000000004)

我们获得分类器具有以下权重和偏置:

  • w[1] = 0.47

  • w[2] = 0.10

  • b = –0.68

分类器的图(以及每个 epoch 的先前分类器的图)如图 6.9 所示。

图 6.9 结果逻辑分类器的边界

在图 6.10 中,我们可以看到所有 epoch 对应的分类器图表(左)和 log 损失图表(右)。在中间分类器图表中,最后一个对应的是深色线。从 log 损失图表中可以看出,随着我们运行算法的 epoch 数增加,log 损失急剧下降,这正是我们想要的。此外,log 损失永远不会为零,即使所有点都被正确分类。这是因为对于任何点,无论分类得多好,log 损失永远不会为零。这与第五章中的图 5.26 形成对比,在那里感知器损失在所有点都被正确分类时确实达到了零值。

图 6.10 左:逻辑回归算法所有中间步骤的图表。注意我们从一个糟糕的分类器开始,逐渐向一个好的分类器(粗线)移动。右:错误图。注意随着我们运行逻辑回归算法的 epoch 数增加,错误率降低。

实际应用:使用 Turi Create 对 IMDB 评论进行分类

在本节中,我们看到了逻辑分类器在情感分析中的实际应用。我们使用 Turi Create 构建一个分析 IMDB 网站上电影评论的模型。本节的代码如下:

首先,我们导入 Turi Create,下载数据集,并将其转换为 SFrame,我们称之为movies,如下所示:

import turicreate as tc
movies = tc.SFrame('IMDB Dataset.csv')

数据集的前五行显示在表 6.10 中。

表 6.10 IMDB 数据集的前五行。评论列包含评论文本,情感列包含评论的情感,可以是积极或消极。

评论 情感
另一位评论者提到... 积极
一部精彩的小制作... 积极
我觉得这是一个度过美好一天的好日子... 积极
基本上,有一个家庭里有一个小... 消极
Petter Mattei 的《金钱时代里的爱情》是一部... 积极

数据集有两列,一列是评论,作为字符串,另一列是情感,作为积极或消极。首先,我们需要处理字符串,因为每个单词都需要成为一个不同的特征。Turi Create 内置的text_analytics包中的count_words函数对于这个任务很有用,因为它将一个句子转换成一个包含单词计数的字典。例如,句子“to be or not to be”被转换成字典{‘to’:2, ‘be’:2, ‘or’:1, ‘not’:1}。我们添加一个名为words的新列,包含这个字典,如下所示:

movies['words'] = tc.text_analytics.count_words(movies['review'])

我们数据集的新列的前几行显示在表 6.11 中。

表 6.11 词语列是一个字典,其中记录了评论中的每个词语及其出现次数。这是我们的逻辑分类器的特征列。

评论 情感 词语
另一位评论者提到... 积极 {'if': 1.0, 'viewing': 1.0, 'comfortable': 1.0, ...
一部美好的小制作... 积极 {'done': 1.0, 'every': 1.0, 'decorating': 1.0, ...
我觉得这是一个度过美好的一天... 积极 {'see': 1.0, 'go': 1.0, 'great': 1.0, 'superm ...
基本上,有一个家庭,一个小孩... 消极 {'them': 1.0, 'ignore': 1.0, 'dialogs': 1.0, ...
彼得·马泰利的《金钱时代里的爱情》是... 积极 {'work': 1.0, 'his': 1.0, 'for': 1.0, 'anxiously': ...

我们已经准备好训练我们的模型了!为此,我们使用 logistic_classifier 包中的 create 函数,在其中我们指定目标(标签)为 sentiment 列,特征为 words 列。请注意,目标以包含标签的列的名称命名的字符串形式表示,但特征以包含每个特征的列名称的字符串数组形式表示(以防我们需要指定多个列),如下所示:

model = tc.logistic_classifier.create(movies, features=['words'], target='sentiment')

现在我们已经训练了我们的模型,我们可以查看词语的权重,使用 coefficients 命令。我们得到的表格有几列,但我们关心的是 indexvalue 列,它们显示了词语及其权重。前五项如下:

  • (截距):0.065

  • if: –0.018

  • viewing: 0.089

  • comfortable: 0.517

  • become: 0.106

第一个,称为截距,是偏差。因为模型的偏差是正的,所以空评论是积极的,正如我们在第五章的“偏差、y-截距和安静外星人的固有情绪”一节中学到的。这很有道理,因为给电影打负分的用户倾向于留下评论,而许多给电影打正分的用户则不留任何评论。其他词语是中性的,所以它们的权重意义不大,但让我们探索一些词语的权重,例如 wonderfulhorriblethe,如下所示:

  • wonderful: 1.043

  • horrible: –1.075

  • the: 0.0005

As we see, the weight of the word wonderful is positive, the weight of the word horrible is negative, and the weight of the word the is small. This makes sense: wonderful is a positive word, horrible is a negative word, and the is a neutral word.

作为最后一步,让我们找出最积极和消极的评论。为此,我们使用模型对所有电影进行预测。这些预测将存储在一个名为 predictions 的新列中,使用以下命令:

movies['prediction'] = model.predict(movies, output_type='probability')

根据模型,让我们找出最积极和最消极的电影。我们通过以下方式对数组进行排序:

最积极评论:

movies.sort('predictions')[-1]

输出: “在我看来,很多人不知道实际上 Blade 是一部与 X-Men 相当的超级英雄电影……”

最多负面评论:

movies.sort('predictions')[0]

输出:“甚至比原来的更无趣……”

我们可以做很多事情来改进这个模型。例如,一些文本处理技术,如删除标点符号和大小写,或删除停用词(如 theandofit),通常会给我们更好的结果。但是,看到我们只需要几行代码就能构建自己的情感分析分类器真是太棒了!

将多个类别分类:softmax 函数

到目前为止,我们已经看到连续感知器将两个类别分类为快乐和悲伤。但是,如果我们有更多类别呢?在第五章的结尾,我们讨论了对于离散感知器来说,在两个以上类别之间进行分类是困难的。然而,使用逻辑分类器来做这件事很容易。

想象一个包含三个标签的图像数据集:“狗”、“猫”和“鸟”。要构建一个分类器,预测每个图像的这三个标签之一,就需要为每个标签构建三个分类器,每个标签一个。当有新的图像进来时,我们用这三个分类器中的每一个来评估它。对应于每个动物的分类器返回一个概率,表示图像是相应的动物。然后,我们将图像分类为返回最高概率的分类器对应的动物。

然而,这并不是做这件事的理想方式,因为这个分类器返回的是一个离散的答案,例如“狗”、“猫”或“鸟”。如果我们想要一个返回三种动物概率的分类器呢?比如说,一个答案可能是这样的形式:“10%狗,85%猫,5%鸟。”我们这样做是使用 softmax 函数。

softmax 函数的工作原理如下:回想一下,逻辑分类器使用两步过程进行预测——首先它计算一个分数,然后它将 sigmoid 函数应用于这个分数。现在让我们忘记 sigmoid 函数,输出分数。现在想象三个分类器返回以下分数:

  • 狗分类器:3

  • 猫分类器:2

  • 鸟分类器:-1

我们如何将这些分数转换为概率呢?好吧,这里有一个想法:我们可以进行归一化。这意味着将这些数字除以它们的总和,即五,使它们加起来等于一。当我们这样做时,我们得到狗的概率为 3/5,猫的概率为 2/5,鸟的概率为-1/5。这可以工作,但并不理想,因为图像是鸟的概率是一个负数。概率必须始终为正,所以我们需要尝试不同的方法。

我们需要的是一个始终为正且递增的函数。指数函数非常适合这个。任何指数函数,如 2x,3x 或 10^x,都可以完成这项工作。默认情况下,我们使用函数 ex,它具有美好的数学特性(例如,*e*x 的导数也是 e^x)。我们将此函数应用于分数,得到以下值:

  • 狗分类器:e³ = 20.085

  • 猫分类器:e² = 7.389

  • 鸟分类器:e^(–1) = 0.368

现在,我们做之前做过的事情——归一化,或者除以这些数字的总和,使它们加起来为 1。总和是 20.085 + 7.389 + 0.368 = 27.842,所以我们得到以下结果:

  • 狗的概率:20.085/27.842 = 0.721

  • 猫的概率:7.389/27.842 = 0.265

  • 鸟的概率:0.368/27.842 = 0.013

这些是我们三个分类器给出的三个概率。我们使用的函数是 softmax,其通用版本如下:如果我们有 n 个分类器,它们输出 n 个分数 a[1],a[2],… ,a[n],得到的概率是 p[1],p[2],… ,p[n],其中

图片

这个公式被称为 softmax 函数。

如果我们只对两个类别使用 softmax 函数会发生什么?我们会得到 sigmoid 函数。为什么不作为练习来验证这一点呢?

摘要

  • 连续感知器,或逻辑分类器,与感知器分类器类似,除了它们不会做出 0 或 1 这样的离散预测,而是预测 0 到 1 之间的任何数字。

  • 逻辑分类器比离散感知器更有用,因为它们提供了更多信息。除了告诉我们分类器预测了哪个类别,它们还提供了一个概率。一个好的逻辑分类器会给标签为 0 的点分配低概率,给标签为 1 的点分配高概率。

  • 对数损失是逻辑分类器的误差函数。它对每个点分别计算,作为分类器分配给其标签的概率的自然对数的负数。

  • 分类器在数据集上的总对数损失是每个点的对数损失的加和。

  • 逻辑技巧接受一个标记数据点和边界线。如果点被错误分类,则将线移近点;如果点被正确分类,则将线移远点。这比感知器技巧更有用,因为感知器技巧在点被正确分类时不会移动线。

  • 逻辑回归算法用于将逻辑分类器拟合到标记数据集。它包括从一个具有随机权重的逻辑分类器开始,并持续随机选择一个点,应用逻辑技巧以获得一个略微更好的分类器。

  • 当我们有多个类别要预测时,我们可以构建多个线性分类器,并使用 softmax 函数将它们组合起来。

练习

练习 6.1

一位牙医在患者数据集上训练了一个逻辑分类器,以预测他们是否有蛀牙。模型确定患者有蛀牙的概率是

σ(d + 0.5c – 0.8),

其中

  • d 是一个变量,表示患者过去是否有过其他蛀牙,并且

  • c 是一个变量,表示患者是否吃糖果。

例如,如果一个病人吃了糖果,那么 c = 1,如果没有吃,那么 c = 0。那么一个去年治疗过蛀牙且现在吃糖果的病人今天有蛀牙的概率是多少?

练习 6.2

考虑将点 (x[1], x[2]) 分配给预测 ŷ = σ(2x[1] + 3x[2] – 4) 的逻辑分类器,以及点 p = (1, 1) 带有标签 0。

  1. 计算模型对点 p 的预测 ŷ

  2. 计算模型在点 p 处产生的对数损失。

  3. 使用逻辑技巧获得一个产生更小对数损失的新的模型。你可以使用 η = 0.1 作为学习率。

  4. 找到新模型在点 p 处的预测,并验证获得的对数损失是否小于原始值。

练习 6.3

使用练习 6.2 中的模型,找到一个预测值为 0.8 的点。

提示:首先找到给出预测值为 0.8 的分数,并记住预测是 ŷ = σ(score)。

7 如何衡量分类模型?准确率及其朋友

在本章中

  • 模型可能犯的错误类型:假阳性和假阴性

  • 将这些错误放入表格中:混淆矩阵

  • 准确率、召回率、精确率、F 分数、敏感性和特异性是什么,以及它们如何用于评估模型

  • ROC 曲线是什么,它是如何同时跟踪敏感性和特异性的

图片

本章与前面两章略有不同——它不专注于构建分类模型;相反,它专注于评估它们。对于机器学习专业人士来说,能够评估不同模型的性能与能够训练它们一样重要。我们很少在数据集上训练单个模型;我们训练几个不同的模型,并选择表现最好的一个。我们还需要确保模型在投入生产前具有良好的质量。模型的质量并不总是容易衡量,在本章中,我们学习了几种评估我们的分类模型的技术。在第四章中,我们学习了如何评估回归模型,因此我们可以将本章视为其类比,但针对的是分类模型。

测量分类模型性能的最简单方法是通过计算其准确率。然而,我们将看到准确率并不能描绘出整个画面,因为一些模型虽然准确率很高,但本质上并不好。为了解决这个问题,我们将定义一些有用的指标,例如精确率和召回率。然后我们将它们结合成一个新且更强大的指标,称为 F 分数。这些指标被数据科学家广泛用于评估他们的模型。然而,在其他学科,如医学中,使用其他类似的指标,例如敏感性和特异性。使用这两个最后的指标,我们将能够构建一个称为接收者操作特征(ROC)曲线的曲线。ROC 曲线是一个简单的图表,它为我们提供了对模型的深刻见解。

准确率:我的模型有多正确?

在本节中,我们讨论准确率,这是分类模型最简单和最常用的度量。模型的准确率是指模型正确的时间百分比。换句话说,它是正确预测的数据点数与总数据点数的比率。例如,如果我们对一个包含 1,000 个样本的测试数据集进行模型评估,并且模型正确预测了样本的标签 875 次,那么这个模型的准确率为 875/1000 = 0.875,或 87.5%。

准确率是评估分类模型最常见的方法,我们应该始终使用它。然而,有时准确率并不能完全描述模型的性能,正如我们很快将看到的。让我们首先看看本章我们将研究的两个例子。

两个模型的例子:冠状病毒和垃圾邮件

在本章中,我们使用我们的指标评估了两个数据集上的几个模型。第一个数据集是患者医疗数据集,其中一些患者已被诊断为冠状病毒。第二个数据集是电子邮件数据集,这些电子邮件已被标记为垃圾邮件或非垃圾邮件。正如我们在第一章中学到的,垃圾邮件是指垃圾邮件,而非垃圾邮件是指非垃圾邮件。在第八章中,当我们学习朴素贝叶斯算法时,我们将更详细地研究这样的数据集。在本章中,我们不是构建模型,而是将模型作为黑盒使用,并根据它们预测正确或错误的数据点数量来评估它们。这两个数据集都是完全虚构的。

医疗数据集:一组被诊断为冠状病毒的病人

我们的第一组数据集是一个包含 1,000 名患者的医疗数据集。其中,10 人被诊断为冠状病毒,其余 990 人被诊断为健康。因此,这个数据集中的标签是“生病”或“健康”,对应于诊断。模型的目标是根据每个患者的特征预测诊断。

电子邮件数据集:一组被标记为垃圾邮件或非垃圾邮件的电子邮件

我们的第二个数据集是一个包含 100 封电子邮件的数据集。其中,40 封是垃圾邮件,其余 60 封是非垃圾邮件。这个数据集中的标签是“垃圾邮件”和“非垃圾邮件”,模型的目标是根据电子邮件的特征预测标签。

一个超级有效却又超级无用的模型

准确率是一个非常有用的指标,但它能否描绘出模型的完整图景呢?答案是否定的,我们将通过一个例子来阐述这一点。目前,让我们专注于冠状病毒数据集。我们将在下一节回到电子邮件数据集。

假设一位数据科学家告诉我们以下内容:“我开发了一种冠状病毒检测方法,只需 10 秒钟即可运行,不需要任何检查,并且准确率为 99%!”我们应该感到兴奋还是怀疑?我们可能会怀疑。为什么?我们很快就会看到,计算模型的准确率有时并不足够。我们的模型可能准确率为 99%,但可能完全无用。

我们能否想象一个完全无用的模型,该模型可以预测我们数据集中的冠状病毒,并且在这个数据集中 99%的时间都是正确的?回想一下,我们的数据集包含 1,000 名患者,其中 10 人有冠状病毒。请随意放下这本书,思考一下如何构建一个可以检测冠状病毒的模型,并且在这个数据集中 99%的时间都是正确的。

这可能是一个这样的模型:简单地将每个患者诊断为健康。这是一个简单的模型,但仍然是一个模型;这是一个预测所有事物属于一个类别的模型。

这个模型的准确率是多少呢?嗯,在 1,000 次尝试中,它有 10 次错误,990 次正确。这给出了 99%的准确率,正如我们承诺的那样。然而,这个模型等同于在一场全球大流行期间告诉每个人他们都健康,这是非常糟糕的!

那么,我们模型的问题是什么?问题是错误并不平等,有些错误比其他错误代价更高,正如我们将在下一节中看到的。

如何解决准确率问题?定义不同类型的错误以及如何衡量它们

在前一节中,我们构建了一个具有高准确率的无用模型。在本节中,我们研究出了什么问题。也就是说,我们研究在那个模型中计算准确率的问题,并介绍了一些略微不同的指标,这些指标将给我们提供对这个模型更好的评估。

我们需要研究的第一件事是错误的类型。在下一节中,我们看到某些错误比其他错误更关键。然后在“存储正确和错误分类点”到“召回率、精确率或 F 分数”的章节中,我们学习了不同的指标,这些指标比准确率更能捕捉这些关键错误。

“将分类点放入表格”到“召回率、精确率或 F 分数”,我们学习了不同的指标,这些指标比准确率更能捕捉这些关键错误。

假阳性和假阴性:哪一个更糟糕?

在许多情况下,错误总数并不能告诉我们关于模型性能的所有信息,我们需要深入挖掘并以不同的方式识别不同类型的错误。在本节中,我们看到两种错误类型。冠状病毒模型可能犯的两种错误类型是什么?它可以诊断一个健康的人为生病,或者一个生病的人为健康。在我们的模型中,我们按照惯例将生病患者标记为正例。这两种错误类型被称为假阳性和假阴性,如下所示:

  • 假阳性:被错误地诊断为生病的人

  • 假阴性:被错误地诊断为健康的人

在一般设置中,一个假阳性是一个具有负标签的数据点,但模型错误地将其分类为正类。一个假阴性是一个具有正标签的数据点,但模型错误地将其分类为负类。自然地,正确诊断的案例也有名称,如下所示:

  • 真阳性:被诊断为生病的人

  • 真阴性:被诊断为健康的人

在一般设置中,一个真阳性是一个具有正标签的数据点,它被正确分类为正类,一个真阴性是一个具有负标签的数据点,它被正确分类为负类。

现在,让我们看看电子邮件数据集。假设我们有一个模型,它预测每封邮件是垃圾邮件还是正常邮件。我们将垃圾邮件视为正例。因此,我们的两种错误类型如下:

  • 假阳性:被错误地分类为垃圾邮件的正常邮件

  • 假阴性:被错误地分类为垃圾邮件的正常邮件

正确分类的邮件如下:

  • 真阳性:被正确分类为垃圾邮件的垃圾邮件

  • 真阴性:被正确分类为正常邮件的正常邮件

图 7.1 展示了模型的图形表示,其中垂直线是边界,线左侧的区域是负区域,线右侧的区域是正区域。三角形是带有正标签的点,圆圈是带有负标签的点。上面定义的四个量如下:

  • 线右侧的三角形:真阳性

  • 线左侧的三角形:假阴性

  • 线右侧的圆圈:假阳性

  • 线左侧的圆圈:真阴性

图 7-1

图 7.1 展示了两个在现实生活中广泛使用且我们将在本章中使用的模型示例。在左边,是一个冠状病毒模型,其中人们被诊断为健康或生病。在右边,是一个垃圾邮件检测模型,其中电子邮件被分类为垃圾邮件或正常邮件。对于每个模型,我们都突出了一些错误,并将它们分为假阳性和假阴性。

注意到图 7.1 中的两个模型都产生了以下量:

  • 三个真阳性

  • 四个真阴性

  • 一个假阳性

  • 两个假阴性

要了解冠状病毒模型和垃圾邮件模型之间的区别,我们需要分析在假阳性和假阴性之间哪一个更糟糕。让我们分别对每个模型进行这种分析。

分析冠状病毒模型中的假阳性和假阴性

让我们停下来思考一下。在冠状病毒模型中,哪一种错误听起来更糟糕:假阳性还是假阴性?换句话说,什么更糟糕:错误地将健康患者诊断为生病,还是将生病患者诊断为健康?让我们假设,当我们诊断患者为健康时,我们会让他们不带治疗回家,而当我们诊断患者为生病时,我们会让他们进行更多测试。错误地诊断健康人可能只是一个小麻烦,因为这意味着健康人将不得不进行额外的测试。然而,错误地诊断生病的人意味着生病的人将得不到治疗,他们的病情可能会恶化,他们可能会潜在地感染许多人。因此,在冠状病毒模型中,假阴性比假阳性要糟糕得多

分析垃圾邮件模型中的假阳性和假阴性

现在我们将对垃圾邮件模型进行同样的分析。在这种情况下,让我们假设如果我们的垃圾邮件分类器将一封邮件分类为垃圾邮件,那么这封邮件就会被自动删除。如果它将其分类为正常邮件,那么邮件就会被发送到我们的收件箱。哪种错误听起来更糟糕:假阳性还是假阴性?换句话说,将一封正常邮件错误地分类为垃圾邮件并删除它,或者将一封垃圾邮件错误地分类为正常邮件并发送到收件箱,哪种更糟糕?我想我们可以同意,删除一封好邮件比将垃圾邮件发送到收件箱更糟糕。我们收件箱中偶尔出现的垃圾邮件可能会让人烦恼,但删除一封正常邮件可能是一场灾难!想象一下,如果我们的奶奶给我们发了一封非常友好的邮件,告诉我们她烤了饼干,而我们的过滤器删除了它,我们心里会感到多么的悲伤。因此,在垃圾邮件模型中,假阳性比假阴性要糟糕得多

这就是两种模型的不同之处。在冠状病毒模型中,假阴性结果更糟糕,而在垃圾邮件模型中,假阳性结果更糟糕。在这两种模型中测量准确性的问题在于,准确性将这两种错误视为同等严重,并且无法区分它们。

在“一个超级有效但超级无用的模型”这一节中,我们有一个例子,这个模型诊断出每个病人都是健康的。这个模型在 1000 名病人中只犯了 10 个错误。然而,这 10 个都是假阴性,这是非常糟糕的。如果这 10 个是假阳性,那么这个模型会好得多。

在接下来的章节中,我们将设计两个新的度量标准,类似于准确性。第一个度量标准帮助我们处理假阴性更糟糕的模型,第二个度量标准帮助我们处理假阳性更糟糕的模型。

将正确和错误分类的点存储在表格中:混淆矩阵

在前面的子节中,我们学习了假阳性、假阴性、真阳性和真阴性。为了跟踪这四个实体,我们将它们放在一起,组成一个恰如其分的表格,称为混淆矩阵。对于二元分类模型(预测两个类别的模型),混淆矩阵有两行两列。在行中我们写上真实标签(在医疗例子中,这是人的状况,生病或健康),而在列中我们写上预测标签(人的诊断,生病或健康)。一般混淆矩阵如图 7.1 所示,具体的一个例子是这两个数据集中模型的具体混淆矩阵,如表 7.2 至 7.5 所示。这被称为混淆矩阵,因为它使得我们很容易看出模型是否混淆了两个类别,即阳性(生病)和阴性(健康)。

表 7.1 混淆矩阵帮助我们计算每个类别被正确预测的次数以及每个类别被错误地与其他类别混淆的次数。在这个矩阵中,行代表标签,列代表预测。对角线上的元素被正确分类,而对角线外的元素没有被正确分类。

人的状况 预测为阳性 预测为阴性
阳性 真阳性数量 假阴性数量
阴性 假阳性数量 真阴性数量

对于我们现有的模型(将每个病人都诊断为健康的模型),从现在起我们称之为冠状病毒模型 1,其混淆矩阵如图 7.2 所示。

表 7.2 我们冠状病毒模型的混淆矩阵帮助我们深入分析模型,区分两种类型的错误。此模型产生了 10 个假阴性错误(将生病的人诊断为健康),没有假阳性错误(将健康的人诊断为生病)。请注意,模型产生了过多的假阴性,这是在这种情况下最糟糕的错误类型,这表明此模型并不很好。

冠状病毒模型 1 诊断为生病(预测为阳性) 诊断为健康(预测为阴性)
病人(阳性) 0(真阳性数量) 10(假阴性数量)
健康(阴性) 0(假阳性数量) 990(真阴性数量)

对于有更多类别的复杂问题,我们有一个更大的混淆矩阵。例如,如果我们的模型将图像分类为河马、鸟类、猫和狗,那么我们的混淆矩阵是一个四阶矩阵,其中行代表真实标签(动物的类型),列代表预测标签(模型预测的动物类型)。这个混淆矩阵也具有这样的性质,即正确分类的点被计在对角线上,而错误分类的点被计在对角线外。

回忆:在所有阳性例子中,我们正确分类了多少个?

现在我们知道了两种类型的错误,在本节中,我们将学习一个将给冠状病毒模型 1 带来更低分数的指标。我们已经确定,这个模型的问题在于它给我们太多的假阴性,即它将太多的病人诊断为健康。

让我们暂时假设我们根本不在乎假阳性。比如说,如果模型将一个健康的人诊断为生病,这个人可能需要额外做一次测试或隔离一段时间,但这根本不是问题。当然,这并不是实际情况;假阳性也是昂贵的,但现在让我们假装它们不是问题。在这种情况下,我们需要一个指标来替代准确率,并且这个指标重视找到阳性病例,而对错误地将阴性病例分类则不太关心。

要找到这个指标,我们需要评估我们的目标是什么。如果我们想治愈冠状病毒,那么我们真正想要的是以下内容:在世界上所有生病的人中,我们希望找到他们所有人。我们偶然找到不生病的人没关系,只要我们找到所有生病的人。这是关键。这个新指标,称为召回率,精确地衡量了这一点:在生病的人中,我们的模型诊断出多少是正确的?

在更一般的术语中,召回率找到具有正标签的数据点中的正确预测比例。这是真阳性的数量除以总正数。冠状病毒模型 1 在 10 个正数中有 0 个真阳性,所以它的召回率是 0/10 = 0。另一种说法是,真阳性数除以真阳性数和假阴性数的总和,如下所示:

与此相反,假设我们有一个第二个模型,称为冠状病毒模型 2。这个模型的混淆矩阵显示在表 7.3 中。这个第二个模型比第一个模型犯的错误更多——总共犯了 50 个错误,而第一个模型只犯了 10 个。第二个模型的准确率是 950/1000 = 0.95,或者说 95%。从准确率来看,第二个模型不如第一个模型好。

然而,第二个模型正确诊断了 10 个生病患者中的 8 个和 1000 人中的 942 个。换句话说,它有两个假阴性和 48 个假阳性。

表 7.3 第二个冠状病毒模型的混淆矩阵

冠状病毒模型 2 诊断为生病 诊断为健康
生病 8(真阳性) 2(假阴性)
健康 48(假阳性) 942(真阴性)

这个模型的召回率是真阳性数(8 个正确诊断的生病患者)除以总正数(10 个生病患者),即 8/10 = 0.8,或者说 80%。从召回率来看,第二个模型要好得多。为了清晰起见,让我们如下总结这些计算:

冠状病毒模型 1:

真阳性(被诊断为生病并送进行更多测试的生病患者)= 0

假阴性(被诊断为健康并送回家的生病患者)= 10

召回率 = 0/10 = 0%

冠状病毒模型 2:

真阳性(被诊断为生病并送进行更多测试的生病患者)= 8

假阴性(被诊断为健康并送回家的生病患者)= 2

召回率 = 8/10 = 80%

在冠状病毒模型这样的模型中,假阴性比假阳性更昂贵,它们是高召回率模型

现在我们有了更好的指标,我们能否像欺骗准确度那样欺骗这个指标?换句话说,我们能否构建一个具有完全召回率的模型?好吧,准备好惊喜吧,因为我们能。如果我们构建一个将每个病人都诊断为生病的模型,这个模型将有 100%的召回率。然而,这个模型也很糟糕,因为它虽然零假阴性,但假阳性太多,以至于它不是一个好的模型。看来我们仍然需要更多的指标来正确评估我们的模型。

精确度:在我们分类为正例的例子中,我们正确分类了多少个?

在上一节中,我们学习了召回率,这是一个衡量我们的模型在假阴性方面表现如何的指标。这个指标对冠状病毒模型很有用——我们已经看到这个模型不能承受太多的假阴性。在本节中,我们将学习一个类似的指标,精确度,它衡量我们的模型在假阳性方面的表现。我们将使用这个指标来评估垃圾邮件模型,因为这个模型不能承受太多的假阳性。

正如我们在召回率中所做的那样,为了提出一个指标,我们首先需要定义我们的目标。我们希望有一个不会删除任何正常邮件的垃圾邮件过滤器。如果它不是删除邮件,而是将它们发送到垃圾邮件箱中。那么我们需要查看那个垃圾邮件箱,并希望我们看不到一封正常邮件。因此,我们的指标应该精确地衡量这一点:在我们垃圾邮件箱中的邮件中,有多少实际上是垃圾邮件?换句话说,在预测为垃圾邮件的邮件中,实际上有多少是垃圾邮件?这是我们指标,我们称之为精确度

更正式地说,精确度只考虑被标记为正面的数据点,并在其中考虑有多少是真阳性。因为预测为正面的数据点是真阳性和假阳性的并集,所以公式如下:

图片

记住,在我们的 100 封邮件数据集中,有 40 封是垃圾邮件,60 封是正常邮件。假设我们训练了以下两个模型,称为垃圾邮件模型 1 和垃圾邮件模型 2。它们的混淆矩阵分别显示在表 7.4 和 7.5 中。

表 7.4 第一个垃圾邮件模型的混淆矩阵

垃圾邮件模型 1 预测为垃圾邮件 预测为正常邮件
垃圾邮件 30 (真阳性) 10 (假阴性)
正常邮件 5 (假阳性) 55 (真阴性)

表 7.5 第二个垃圾邮件模型的混淆矩阵

垃圾邮件模型 2 预测为垃圾邮件 预测为正常邮件
垃圾邮件 35 (真阳性) 5 (假阴性)
正常邮件 10 (假阳性) 50 (真阴性)

从准确度来看,似乎这两个模型都相当好——它们都正确预测了 85%的时间(100 封邮件中有 85 封正确)。然而,乍一看,第一个模型似乎比第二个模型好,因为第一个模型只删除了五封正常邮件,而第二个模型删除了十封。现在让我们按照以下方式计算精确度:

垃圾邮件模型 1:

  • 真阳性(删除的垃圾邮件)= 30

  • 假阳性(删除的普通邮件)= 5

  • 精确率 = 30/35 = 85.7%

垃圾邮件模型 2:

  • 真阳性(删除的垃圾邮件)= 35

  • 假阳性(删除的普通邮件)= 10

  • 精确率 = 35/45 = 77.7%

正如我们所想:精确率给第一个模型比第二个模型更高的分数。我们得出结论,像垃圾邮件模型这样的模型,其中假阳性比假阴性更昂贵,是高精确率模型。那么为什么第一个模型比第二个模型好?第二个模型删除了 10 封好(普通)邮件,但第一个模型只删除了其中 5 封。第二个模型可能清理了比第一个模型更多的垃圾邮件,但这不能弥补它删除的 5 封普通邮件。

现在,就像我们欺骗准确率和召回率一样,我们也可以欺骗精确率。考虑以下垃圾邮件过滤器:一个永远不会检测到任何垃圾邮件的垃圾邮件过滤器。这个模型的精确率是多少?这很复杂,因为删除的垃圾邮件为零(零真阳性)和删除的普通邮件为零(零假阳性)。我们不会尝试将零除以零,因为这本书会燃烧起来,但按照惯例,没有错误假阳性的模型精确率为 100%。但是,当然,一个什么都不做的垃圾邮件过滤器不是一个好的垃圾邮件过滤器。

这表明,无论我们的指标有多好,它们总是可以被欺骗。这并不意味着它们不起作用。准确率、精确率和召回率是数据科学家工具箱中有用的工具。取决于我们决定哪些对我们的模型更有用,通过决定哪些错误比其他错误更昂贵。始终小心,不要在评估不同指标之前就认为模型是好的。

将召回率和精确率结合作为优化两者的一种方式:F 分数

在本节中,我们讨论 F 分数,这是一个结合了召回率和精确率的指标。在前面的章节中,我们看到了两个例子,即冠状病毒模型和垃圾邮件模型,其中错误否定或错误肯定更重要。然而,在现实生活中,两者都很重要,即使它们的重要性程度不同。例如,我们可能希望一个模型不会误诊任何病人,但也不会误诊太多健康人,因为误诊一个健康人可能涉及不必要的痛苦测试,甚至是不必要的手术,这可能会对他们的健康产生负面影响。同样,我们可能希望一个模型不会删除我们的任何好邮件。但为了成为一个好的垃圾邮件过滤器,它仍然需要捕捉到大量的垃圾邮件;否则,它就毫无用处。F 分数有一个伴随的参数β,所以更常见的术语是F[β]-分数。当β = 1 时,它被称为F[1]-分数。

计算 F 分数

我们的目标是找到一个介于召回率和精确率之间的指标。首先想到的是召回率和精确率的平均值。这会有效吗?会的,但这并不是我们选择的方案,有一个基本原因。一个好的模型应该具有好的召回率和好的精确率。如果一个模型,比如说,召回率为 50%,精确率为 100%,那么平均值为 75%。这是一个不错的分数,但模型可能并不好,因为 50%的召回率并不理想。我们需要一个指标,它的行为类似于平均值,但更接近这两个数值中的最小值。

类似于两个数平均值的量称为调和平均数。两个数 a 和 b 的平均值是(a + b)/2,它们的调和平均数是 2ab/(a + b)。调和平均数具有以下性质:它总是小于或等于平均值。如果 a 和 b 相等,可以快速检查它们的调和平均数等于它们两者,就像平均值一样。但在其他情况下,调和平均数会更小。让我们看一个例子:如果 a = 1 且 b = 9,它们的平均值是 5。调和平均数是图片

F[1]-分数定义为精确率和召回率的调和平均,如下所示:

图片

如果这两个数值都较高,F[1]-分数也会较高。然而,如果其中一个数值较低,F[1]-分数将会较低。F[1]-分数的目的是衡量召回率和精确率是否都较高,并在其中一个分数较低时发出警报。

计算F[β]-分数

在前一小节中,我们学习了F[1]-分数,这是一个结合召回率和精确率的分数,用于评估模型。然而,有时我们希望召回率高于精确率,或者相反。因此,当我们结合这两个分数时,我们可能希望给其中一个分数赋予更多的权重。这意味着有时我们可能希望一个既关注假阳性也关注假阴性的模型,但更重视其中一个。例如,冠状病毒模型对假阴性非常关注,因为人们的生命可能取决于对病毒的准确识别,但它仍然不希望产生过多的假阳性,因为我们可能不想浪费过多的资源重新检测健康的人。垃圾邮件模型对假阳性非常关注,因为我们真的不希望删除好的电子邮件,但也不希望产生过多的假阴性,因为我们不希望我们的收件箱被垃圾邮件消息所充斥。

这就是 F[β]-分数发挥作用的地方。F[β]-分数的公式可能一开始看起来很复杂,但一旦我们仔细观察,它确实做了我们想要的事情。F[β]-分数使用一个称为 β(希腊字母贝塔)的参数,它可以取任何正数值。β 的作用就像一个旋钮,我们可以转动它来强调精确率或召回率。更具体地说,如果我们把 β 旋钮滑到零,我们得到完全的精确率;如果我们把它滑到无穷大,我们得到完全的召回率。一般来说,β 的值越低,我们越强调精确率,而 β 的值越高,我们越强调召回率。

F[β]-分数的定义如下(其中精确率为 P,召回率为 R):

图片

让我们通过观察一些 β 的值来仔细分析这个公式。

案例 1β = 1

β 等于 1 时,F[β]-分数变为以下形式:

图片

这与考虑召回率和精确率同等重要的 F[1]-分数相同。

案例 2β = 10

β 等于 10 时,F[β]-分数变为以下形式:

图片

这可以写成

图片

这与 F[1]-分数类似,但请注意它对 R 的重视程度远高于 P。为了看到这一点,请注意当 β 趋向于 ∞ 时,F[β]-分数的极限是 R。因此,当我们想要一个介于召回率和精确率之间的分数,且更重视召回率时,我们选择一个大于 1 的 β 值。值越大,我们对召回率的重视程度就越高,对精确率的重视程度就越低。

案例 3β = 0.1

β 等于 0.1 时,F[β]-分数变为以下形式:

图片

就像之前一样,我们可以写成

图片

这与案例 2 中的公式类似,但这个公式给 P 的重视程度更高。因此,当我们想要一个介于召回率和精确率之间的分数,且更重视精确率时,我们选择一个小于 1 的 β 值。值越小,我们对精确率的重视程度就越高,对召回率的重视程度就越低。在极限情况下,我们说 β = 0 给我们的是精确率,而 β = ∞ 给我们的是召回率。

召回率、精确率或 F 分数:我们应该使用哪一个?

现在,我们如何将召回率和精确率付诸实践?当我们有一个模型时,它是高召回率还是高精确率的模型?我们使用 F 分数吗?如果是这样,我们应该选择哪个 β 值?这些问题的答案取决于我们,作为数据科学家。对我们来说,了解我们试图解决的问题非常重要,以便决定哪种错误,即假阳性或假阴性,代价更高。

在前两个例子中,我们可以看到,由于冠状病毒模型需要更多地关注召回率而不是精确度,我们应该选择一个较大的 b 值,比如说,例如,2。相比之下,垃圾邮件模型需要更多地关注精确度而不是召回率,因此我们应该选择一个较小的 β 值,比如说,0.5。为了更多练习分析模型和估计应该使用哪些 β 值,请参阅章节末尾的练习 7.4。

评估我们模型的有用工具:接收者操作特征(ROC)曲线

在“如何解决准确度问题?”这一节中,我们学习了如何使用精确度、召回率和 F 分数等指标来评估模型。我们还了解到,评估模型的主要挑战之一在于存在多种类型的错误,并且不同类型的错误具有不同的重要性水平。我们学习了两种类型的错误:假阳性和假阴性。在某些模型中,假阴性比假阳性代价更高,而在某些模型中则相反。

在本节中,我将向您介绍一种有用的技术,用于根据模型在假阳性和假阴性方面的表现来评估模型。此外,这种方法有一个重要的特性:一个旋钮,允许我们逐渐在表现良好的假阳性模型和表现良好的假阴性模型之间切换。这种技术基于一个称为 接收者操作特征(ROC)曲线 的曲线。

在学习 ROC 曲线之前,我们需要介绍两个新的指标,称为特异性和灵敏度。实际上,其中之一是新的。另一个,我们之前已经见过。

灵敏度和特异性:评估我们模型的新方法

在“如何解决准确度问题?”这一节中,我们定义了召回率和精确度作为我们的指标,并发现它们是衡量我们模型对假阴性和假阳性的有用工具。然而,在本节中,我们使用两个不同但非常相似的指标:灵敏度特异性。它们与之前的指标有相似的作用,但当我们需要构建 ROC 曲线时,它们对我们更有用。此外,尽管精确度和召回率在数据科学家中更广泛使用,但灵敏度和特异性在医学领域更为常见。灵敏度和特异性定义为以下内容:

灵敏度(真阳性率):测试识别正标签点的容量。这是真阳性数与总阳性数的比率。(注意:这与召回率相同)。

图片

特异性(真阴性率):测试识别负标签点的容量。这是真阴性数与总阴性数的比率。

图片

如我所述,灵敏度与召回率相同。然而,特异性与精确度不同(每个名称在不同的学科中都很流行,因此我们在这里都使用它们)。我们将在“召回率是灵敏度,但精确度和特异性不同”这一节中更详细地了解这一点。

在冠状病毒模型中,灵敏度是指模型正确诊断的患病人数占所有患病人数的比例。特异性是指模型正确诊断的健康人数占所有健康人数的比例。我们更关心正确诊断患病人群,因此我们需要冠状病毒模型具有高灵敏度

在垃圾邮件检测模型中,灵敏度是指我们正确删除的垃圾邮件占所有垃圾邮件的比例。特异性是指我们正确发送到收件箱的普通邮件占所有普通邮件的比例。因为我们更关心正确检测普通邮件,所以我们需要垃圾邮件检测模型具有高特异性

为了阐明前面的概念,让我们看看我们正在工作的图形示例。也就是说,让我们计算图 7.2(与图 7.1 相同)中我们两个模型的特异性和灵敏度。

图 7.2 左边是一个冠状病毒模型,其中人们被诊断为健康或患病;右边是一个垃圾邮件检测模型,其中电子邮件被分类为垃圾邮件或普通邮件

如我们之前所见,这两个模型产生了以下数量:

  • 三个真阳性

  • 四个真阴性

  • 一个假阳性

  • 两个假阴性

现在我们来计算这些模型的特异性和灵敏度。

计算特异性

在这种情况下,我们计算灵敏度的方法是:在所有阳性点中,模型正确分类了多少个?这相当于问:在所有三角形中,有多少个位于线的右侧?共有五个三角形,模型正确地将三个位于线的右侧的三角形分类,因此灵敏度是 3/5,等于 0.6,或 60%。

计算灵敏度

我们计算特异性的方法是:在所有阴性点中,模型正确分类了多少个?这相当于问:在所有圆形中,有多少个位于线的左侧?共有五个圆形,模型正确地将四个位于线的左侧的圆形分类,因此特异性是 4/5,等于 0.8,或 80%。

接收者操作特征(ROC)曲线:一种优化模型灵敏度和特异性的方法

在本节中,我们将看到如何绘制接收者操作特征(ROC)曲线,这将为我们提供关于模型的大量信息。简而言之,我们将缓慢修改模型并记录每个时间步长模型的灵敏度和特异性。

我们对模型需要做的第一个也是唯一的一个假设是,它返回的预测是一个连续值,即概率。这在像逻辑分类器这样的模型中是正确的,其中预测不是一个类别,例如正/负,而是一个介于 0 和 1 之间的值,例如 0.7。我们通常用这个值来选择一个阈值,例如 0.5,并将预测值高于或等于阈值的每个点分类为阳性,其他点分类为阴性。然而,这个阈值可以是任何值——它不必是 0.5。我们的程序包括从 0 到 1 改变这个阈值,并记录每个阈值值下模型的灵敏度和特异性。

让我们来看一个例子。我们计算三个不同阈值(0.2、0.5 和 0.8)的灵敏度和特异性。在图 7.3 中,我们可以看到每个阈值下有多少点位于线的左侧和右侧。让我们详细研究一下。记住,灵敏度是所有阳性中的真阳性比率,特异性是所有阴性中的真阴性比率。还要记住,对于这些中的每一个,总共有五个阳性,五个阴性。

阈值 = 0.2

真阳性数量:4

灵敏度:五分之五

真阴性数量:3

特异性:五分之四

阈值 = 0.5

真阳性数量:3

灵敏度:五分之四

真阴性数量:4

特异性:五分之五

阈值 = 0.2

真阳性数量:2

灵敏度:五分之四

真阴性数量:5

特异性:五/五 = 1

注意,低阈值会导致许多阳性预测。因此,我们将有很少的假阴性,这意味着高灵敏度分数,以及许多假阳性,这意味着低特异性分数。同样,高阈值意味着低灵敏度分数和高特异性分数。当我们从低阈值移动到高阈值时,灵敏度降低,特异性增加。这是一个重要的观点,我们将在本章后面的部分讨论,当我们到达决定模型最佳阈值的时候。

现在我们准备构建 ROC 曲线。首先,我们考虑一个阈值为 0,并逐渐以小的间隔增加这个阈值,直到它达到 1。对于阈值的每次增加,我们恰好通过一个点。阈值的具体值并不重要——重要的是在每一步,我们恰好通过一个点(这是可能的,因为所有点都给出了不同的分数,但这不是一般性的要求)。因此,我们将这些步骤称为 0、1、2、...、10。在你的脑海中,你应该想象图 7.3 中的垂直线从 0 开始,缓慢地从左向右移动,每次扫过一个点,直到达到 1。这些步骤记录在表 7.6 中,包括每个步骤的真阳性、真阴性、灵敏度和特异性。

注意到的一点是,在第一步(步骤 0)中,线位于阈值 0。这意味着模型将每个点都分类为正样本。所有正样本也被分类为正样本,因此每个正样本都是真阳性。这意味着在时间步 0,敏感度为 5/5 = 1。但由于每个负样本都被分类为正样本,没有真阳性,因此特异性为 0/5 = 0。同样,在最后一步(步骤 10)中,阈值为 1,我们可以检查出由于每个点都被分类为负样本,敏感度现在为 0,特异性为 1。为了清晰起见,图 7.3 中的三个模型在表 7.6 中分别突出显示为时间步 4、6 和 8。

图片

图 7.3 移动阈值对敏感性和特异性的影响。在左侧,我们有一个低阈值的模型;在中间,我们有一个中等阈值的模型;在右侧,我们有一个高阈值的模型。对于每个模型,都有五个正样本和五个负样本。每个模型由一条垂直线表示。模型预测位于线右侧的点为正样本,位于线左侧的点为负样本。对于每个模型,我们计算了真阳性和真阴性的数量,即正确预测的正样本和负样本的数量。我们使用这些数据来计算敏感性和特异性。请注意,随着阈值的增加(即,随着垂直线从左向右移动),敏感度下降,特异性上升。

表 7.6 在提高我们的阈值的过程中所有的时间步,这是构建 ROC 曲线的重要步骤。在每个时间步,我们记录真阳性和真阴性的数量。然后,我们通过将真阳性的数量除以正样本总数来计算模型的特异性。作为最后一步,我们通过将真阴性的数量除以负样本总数来计算特异性。

步骤 真阳性 敏感度 真阴性 特异性
0 5 1 0 0
1 5 1 1 0.2
2 4 0.8 1 0.2
3 4 0.8 2 0.4
4 4 0.8 3 0.6
5 3 0.6 3 0.6
6 3 0.6 4 0.8
7 2 0.4 4 0.8
8 2 0.4 5 1
9 1 0.2 5 1
10 0 0 5 1

作为最后一步,我们绘制了敏感性和特异性值。这是 ROC 曲线,如图 7.4 所示。在这个图中,每个黑色点对应一个时间步(点内指示),水平坐标对应敏感度,垂直坐标对应特异性。

图片

图 7.4 在这里,我们可以看到对应于我们正在进行的示例的 ROC 曲线,它为我们提供了关于模型的大量信息。高亮的点对应于将阈值从 0 移动到 1 时获得的 timesteps,每个点都标有 timesteps。在横轴上,我们记录了每个 timesteps 的模型敏感性,在纵轴上,我们记录了特异性。

一个告诉我们模型有多好的指标:AUC(曲线下的面积)

正如我们在本书之前所见,评估机器学习模型是一项非常重要的任务,在本节中,我们将讨论如何使用 ROC 曲线来评估模型。为此,我们已经完成了所有工作——剩下的只是计算曲线下的面积,即 AUC。在图 7.5 的顶部,我们可以看到三个模型,其中预测值由横轴给出(从 0 到 1)。在底部,你可以看到三个相应的 ROC 曲线。每个方块的尺寸是 0.2 乘以 0.2。每个曲线下的方块数量分别是 13、18 和 25,这相当于曲线下的面积是 0.52、0.72 和 1。

注意,一个模型最好的 AUC 是 1,这对应于图右边的模型。一个模型最差的 AUC 是 0.5,因为这意味着模型的表现与随机猜测一样好。这对应于图左边的模型。中间的模型是我们的原始模型,AUC 为 0.72。

图片

图 7.5 在这个图中,我们可以看到 AUC,即曲线下的面积,是一个很好的指标,可以用来确定模型的好坏。AUC 越高,模型越好。在左边,我们有一个 AUC 为 0.52 的坏模型。在中间,我们有一个 AUC 为 0.72 的好模型。在右边,我们有一个 AUC 为 1 的伟大模型。

那么 AUC 为零的模型呢?这有点棘手。AUC 为零的模型对应于一个将每个点都分类错误的模型。这是一个坏模型吗?实际上,这是一个非常好的模型,因为要修复它,我们只需要翻转所有正负预测,就能得到一个完美的模型。这就像有一个人每次在回答是非问题时都撒谎一样。要让他们说实话,我们只需要翻转他们所有的答案。这意味着在二元分类模型中,最坏的情况是 AUC 为 0.5,因为这对应于一个撒谎 50%的人。他们给我们没有信息,因为我们永远不知道他们在说真话还是撒谎!顺便说一句,如果我们有一个 AUC 小于 0.5 的模型,我们可以翻转正负预测,从而得到一个 AUC 大于 0.5 的模型。

如何使用 ROC 曲线做出决策

ROC 是一个强大的图形,它为我们提供了关于模型的大量信息。在本节中,我们学习如何使用它来改进我们的模型。简而言之,我们使用 ROC 调整模型中的阈值,并将其应用于选择最适合我们用例的最佳模型。

在本章的开头,我们介绍了两个模型,即冠状病毒模型和垃圾邮件检测模型。这些模型非常不同,因为我们看到,冠状病毒模型需要高敏感性,而垃圾邮件检测模型需要高特异度。每个模型都需要一定程度的敏感性和特异度,这取决于我们要解决的问题。假设我们处于以下情况:我们正在训练一个应该具有高敏感性的模型,但我们得到了一个低敏感性而高特异度的模型。我们是否有办法牺牲一些特异度以换取一些敏感性?

答案是肯定的!我们可以通过调整阈值来权衡特异性和敏感性。回想一下,当我们首次定义 ROC 曲线时,我们注意到阈值越低,模型的敏感性越高而特异度越低,反之,阈值越高,模型的敏感性越低而特异度越高。当对应的阈值垂直线位于最左侧时,所有点都被预测为阳性,因此所有阳性都是真阳性,而当时垂直线位于最右侧时,所有点都被预测为阴性,因此所有阴性都是真阴性。当我们向右移动这条线时,我们失去了一些真阳性并获得了某些真阴性,因此敏感性降低而特异度提高。请注意,当阈值从 0 移动到 1 时,我们在 ROC 曲线上向上并向左移动,如图 7.6 所示。

图片

图 7.6 模型的阈值与敏感性和特异度有很大关系,这种关系将帮助我们为我们的模型选择完美的阈值。在左侧,我们有我们的模型,在右侧,是对应的 ROC 曲线。当我们增加或减少阈值时,我们改变模型的敏感性和特异度,这种变化通过在 ROC 曲线上的移动来表示。

为什么会发生这种情况呢?阈值告诉我们我们在分类一个点时在哪里划线。例如,在冠状病毒模型中,阈值告诉我们我们在决定一个人是否需要进一步检测或回家时在哪里划线。低阈值的模型是指即使有轻微症状也会让人进行额外检测的模型。高阈值的模型是指需要人们表现出强烈症状才会让人进行更多检测的模型。因为我们希望捕捉到所有生病的人,所以我们希望这个模型的阈值低,这意味着我们希望这个模型具有高敏感性。为了清晰起见,在图 7.7 中,我们可以看到之前使用的三个阈值,以及它们在曲线中的对应点。

如果我们想让我们的模型具有高敏感性,我们只需将阈值推向左侧(即减小它),直到我们到达曲线上具有我们想要的敏感度的点。请注意,模型可能会失去一些特异性,这是我们付出的代价。相比之下,如果我们想要更高的特异性,我们将阈值推向右侧(即增加它),直到我们到达曲线上具有我们想要的特异性的点。同样,在这个过程中,我们会失去一些敏感性。曲线告诉我们我们获得了多少以及失去了多少,因此作为数据科学家,这是一个帮助我们决定模型最佳阈值的伟大工具。在图 7.8 中,我们可以看到一个具有更大数据集的更一般化的例子。

图 7.7 模型阈值与其 ROC 曲线的平行关系。左侧的模型具有高阈值、低敏感性和高特异性。中间的模型在阈值、敏感性和特异性方面具有中等值。右侧的模型具有低阈值、高敏感性和低特异性。

图 7.8 在这个更一般化的场景中,我们可以看到一个 ROC 曲线和三个与之对应的点,分别对应三个不同的阈值。如果我们想要选择一个具有高特异性的阈值,我们选择左侧的一个。对于具有高敏感性的模型,我们选择右侧的一个。如果我们想要一个既有较高敏感性又有较高特异性的模型,我们选择中间的一个。

如果我们需要一个高敏感性的模型,例如冠状病毒模型,我们会选择右侧的点。如果我们需要一个高特异性的模型,例如垃圾邮件检测模型,我们可能会选择左侧的点。然而,如果我们想要相对较高的敏感性和特异性,我们可能会选择中间的点。作为数据科学家,我们有责任对问题有足够的了解,以便正确地做出这个决定。

召回率是敏感性,但精确性和特异性是不同的

在这一点上,你可能想知道我们如何能够立刻记住所有这些术语。答案是,它们很难不混淆。大多数数据科学家(包括作者)经常需要快速在维基百科上查找它们,以确保它们不会混淆。我们可以使用助记符来帮助我们记住哪个是哪个。

例如,当我们想到召回率时,想想一个制造了具有致命设计缺陷的汽车的公司。他们需要找到所有有缺陷的汽车并将它们召回。如果他们意外地得到了一些非有缺陷的汽车,他们只需将它们退回。然而,找不到一辆有缺陷的汽车将是可怕的。因此,召回率关注的是找到所有正标签的示例。这代表了一个具有高召回率的模型。

另一方面,如果我们为这家汽车公司工作,并且我们做得有点过头,开始召回所有的汽车,我们的老板可能会过来对我们说:“嘿,你送来修理的汽车太多了,我们的资源快用完了。你能更挑剔一点,只送那些有故障的吗?”然后我们需要在模型中添加精确度,并尝试只找到那些有故障的汽车,即使我们不小心错过了其中一些(希望不会!)。这代表了一个具有高精确度的模型。

当谈到特异性和灵敏度时,想象一个地震传感器,每次有地震时都会发出蜂鸣声。这个传感器非常灵敏。如果隔壁房子里的蝴蝶打喷嚏,传感器也会发出蜂鸣声。这个传感器肯定会捕捉到所有的地震,但它也会捕捉到许多其他不是地震的东西。这代表了一个具有高灵敏度的模型。

现在,让我们想象这个传感器有一个旋钮,我们将它的灵敏度调到最低。现在,传感器只有在有大量移动时才会发出蜂鸣声。当传感器发出蜂鸣声时,我们知道那是地震。问题是它可能会错过一些较小或中等的地震。换句话说,这个传感器对地震非常特异,所以它不太可能对其他任何事情发出蜂鸣声。这代表了一个具有高特异性的模型。

如果我们回顾前面的四个段落,我们可能会注意到以下两点:

  • 回忆和灵敏度非常相似。

  • 精确度和特异性非常相似。

至少,召回率和灵敏度有相同的目的,即测量有多少假反例。同样,精确度和特异性也有相同的目的,即测量有多少假正例。

结果表明,回忆和灵敏度实际上是同一件事。然而,精确度和特异性并不相同。尽管它们测量的不是同一指标,但它们都会惩罚那些有大量误报的模型。如何记住所有这些指标呢?一个图形化的启发式方法可以帮助我们记住召回率、精确度、灵敏度和特异性。在图 7.9 中,我们看到一个混淆矩阵,包含四个量:真正例、真反例、假正例和假反例。如果我们关注顶部行(标记为正的例子),我们可以通过将左列的数字除以两列数字之和来计算召回率。如果我们关注最左边的列(预测为正的例子),我们可以通过将顶行的数字除以两行数字之和来计算精确度。如果我们关注底部行(标记为负的例子),我们可以通过将左列的数字除以两列数字之和来计算特异性。换句话说

  • 召回率和灵敏度对应于顶部行。

  • 精确度对应于最左边的列。

  • 特异性对应于底部行。

图片

图 7.9 混淆矩阵的顶部行给出了召回率和灵敏度:真正例数与真正例数和假阴性数之和的比率。最左侧的列给出了精度:真正例数与真正例数和假阳性数之和的比率。底部行给出了特异性:假阳性数与假阳性数和真阴性数之和的比率。

总结起来,这些量在我们的两个模型中如下:

医学模型:

  • 召回率和灵敏度:在生病的人(阳性)中,有多少被正确诊断为生病?

  • 精度:在诊断为生病的人中,实际上有多少是生病的?

  • 特异性:在健康的人(阴性)中,有多少被正确诊断为健康?

邮件模型:

  • 召回率和灵敏度:在垃圾邮件(阳性)中,有多少被正确删除?

  • 精度:在删除的邮件中,实际上有多少是垃圾邮件?

  • 特异性:在垃圾邮件(阴性)中,有多少被正确发送到收件箱?

摘要

  • 能够评估一个模型与能够训练一个模型一样重要。

  • 我们可以使用几个重要的指标来评估一个模型。在本章中我们学习的是准确度、召回率、精度、F 分数、特异性和灵敏度。

  • 准确度计算正确预测与总预测之间的比率。它是有用的,但在某些情况下可能会失败,尤其是在正负标签不平衡的情况下。

  • 错误分为两类:假阳性和假阴性。

    • 假阳性是一个负标签点,模型错误地预测为阳性。

    • 假阴性是一个正标签点,模型错误地预测为阴性。

  • 对于某些模型,假阴性和假阳性被赋予不同的重要性级别。

  • 召回率和精度是评估模型的有用指标,尤其是在模型对假阴性和假阳性赋予不同重要性的情况下。

    • 召回率衡量模型正确预测的正点数。当模型产生许多假阴性时,召回率会很低。因此,在我们不希望有太多假阴性的模型中,如医学诊断模型,召回率是一个有用的指标。

    • 精度衡量模型预测为正点的点中,实际上有多少是正点。当模型产生许多误报时,精度会很低。因此,在我们不希望有太多误报的模型中,如垃圾邮件模型,精度是一个有用的指标。

  • F[1]-分数是一个有用的指标,它结合了召回率和精度。它返回一个介于召回率和精度之间的值,但更接近于两者中较小的一个。

  • F[β]-分数是 F[1]-分数的一种变体,其中可以调整参数 β 以给予精度或召回更高的权重。β 的值越高,召回的重要性就越大,而 β 的值越低,精度的重要性就越大。F[β]-分数特别适用于评估精度或召回比另一个更重要,但我们仍然关心这两个指标的模型。

  • 灵敏度和特异性是两个有用的指标,帮助我们评估模型。它们在医学领域被高度使用。

    • 灵敏度,或真正正例比率,衡量模型正确预测了多少个正例。当模型产生许多假阴性时,灵敏度会很低。因此,在医疗模型中,我们不想意外地让许多健康患者得不到治疗时,灵敏度是一个有用的指标。

    • 特异性,或真正负例比率,衡量模型正确预测了多少个负例。当模型产生许多假阳性时,特异性会很低。因此,在医疗模型中,我们不想意外地治疗或对健康患者进行进一步侵入性测试时,特异性是一个有用的指标。

  • 召回率和灵敏度是同一回事。然而,精度和特异性不是同一回事。精度确保大多数预测的正例确实是正例,而特异性检查大多数真正负例是否已经被检测到。

  • 当我们提高模型中的阈值时,我们降低其灵敏度并提高其特异性。

  • ROC,或接收者操作特征曲线,是一个有用的图表,帮助我们跟踪模型在每个不同阈值下的灵敏度和特异性。

  • ROC(接受者操作特征曲线)也帮助我们确定模型的好坏,使用曲线下的面积,或 AUC。AUC 越接近 1,模型越好。AUC 越接近 0.5,模型越差。

  • 通过观察 ROC 曲线,我们可以根据模型对每种值的预期来决定使用什么阈值,以同时获得灵敏度和特异性的良好值。这使得 ROC 曲线成为评估和改进模型最受欢迎和最有用的一种方式。

练习

练习 7.1

一个视频网站已经确定一个特定的用户喜欢动物视频,而且绝对没有其他喜好。在下图中,我们可以看到当用户登录网站时收到的推荐。

如果这是我们关于模型的所有数据,回答以下问题:

  1. 模型的准确度是多少?

  2. 模型的召回率是多少?

  3. 模型的精度是多少?

  4. 模型的 F[1]-分数是多少?

  5. 你会说这是一个好的推荐模型吗?

练习 7.2

找出以下混淆矩阵中医疗模型的灵敏度和特异性:

预测患病 预测健康
患病 120 22
健康 63 795

练习 7.3

对于以下模型,确定哪种错误更严重,是假阳性还是假阴性。基于此,确定在评估每个模型时,我们应该强调哪个指标,是精确度还是召回率。

  1. 一种电影推荐系统,预测用户是否会观看电影

  2. 一种用于自动驾驶汽车中的图像检测模型,用于检测图像中是否包含行人

  3. 一种预测用户是否向其下达命令的家庭语音助手

练习 7.4

我们被赋予以下模型:

  1. 一种基于汽车摄像头图像检测行人的自动驾驶汽车模型

  2. 一种基于患者症状诊断致命疾病的医学模型

  3. 一种基于用户之前观看的电影的电影推荐系统

  4. 一种根据语音命令确定用户是否需要帮助的语音助手

  5. 一种基于电子邮件中单词确定电子邮件是否为垃圾邮件的垃圾邮件检测模型

我们被赋予的任务是使用 F[β]-分数来评估这些模型。然而,我们没有给出要使用的 β 值。您会使用什么 β 值来评估每个模型?

8 利用概率达到极致:朴素贝叶斯模型

在本章中

  • 什么是贝叶斯定理

  • 相关事件和独立事件

  • 先验概率和后验概率

  • 根据事件计算条件概率

  • 使用朴素贝叶斯模型来预测一封电子邮件是否为垃圾邮件或正常邮件,基于邮件中的文字

  • 在 Python 中编码朴素贝叶斯算法

图片

朴素贝叶斯是一种重要的机器学习模型,用于分类。朴素贝叶斯模型是一个纯粹的概率模型,这意味着预测是一个介于 0 和 1 之间的数字,表示标签为正的概率。朴素贝叶斯模型的主要组成部分是贝叶斯定理。

贝叶斯定理在概率论和统计学中起着根本的作用,因为它有助于计算概率。它基于这样一个前提:我们收集关于一个事件的信息越多,我们对其概率的估计就越准确。例如,假设我们想要找到今天会下雪的概率。如果我们没有关于我们所在的位置和现在是哪一年的信息,我们只能给出一个模糊的估计。然而,如果我们得到信息,我们就可以对概率做出更好的估计。想象一下,我告诉你我在想一种动物,并希望你能猜出它是什么。我想的动物是狗的概率是多少?鉴于你不知道任何信息,这个概率相当小。然而,如果我现在告诉你我想的动物是家养宠物,这个概率就会大大增加。然而,如果我现在告诉你我想的动物有翅膀,这个概率现在为零。每次我告诉你一条新信息,你对它是狗的概率估计就会越来越准确。贝叶斯定理就是将这种逻辑形式化并放入公式中的方法。

更具体地说,贝叶斯定理回答了“在发生 x 的情况下,Y 的概率是多少?”这个问题,这被称为条件概率。你可以想象,回答这类问题在机器学习中很有用,因为如果我们能回答“在给定特征的情况下,标签为正的概率是多少?”这个问题,我们就有一个分类模型。例如,我们可以通过回答“给定包含的单词,这句话是快乐的概率是多少?”这个问题来构建情感分析模型(就像我们在第六章中做的那样)。然而,当我们有太多的特征(在这种情况下,是单词)时,使用贝叶斯定理计算概率会变得非常复杂。这就是朴素贝叶斯算法拯救我们的地方。朴素贝叶斯算法通过简化这种计算来帮助我们构建所需的分类模型,称为朴素贝叶斯模型。它被称为朴素贝叶斯是因为为了简化计算,我们做出了一个稍微有些天真且不一定正确的假设。然而,这个假设帮助我们得到了一个很好的概率估计。

在本章中,我们看到了贝叶斯定理与一些现实生活中的例子结合使用。我们首先研究一个有趣且略带惊讶的医疗案例。然后,我们深入探讨朴素贝叶斯模型,通过将其应用于机器学习中的一个常见问题:垃圾邮件分类。最后,我们用 Python 编写算法,并在真实的垃圾邮件数据集中使用它进行预测。

本章的所有代码都可以在这个 GitHub 仓库中找到:github.com/luisguiserrano/manning/tree/master/Chapter_8_Naive_Bayes

病了还是健康?一个以贝叶斯定理为主角的传奇故事

考虑以下场景。你的(有点疑病症的)朋友给你打电话,接下来的对话是这样的:

:你好!

朋友:嗨。我有个可怕的消息!

:哎呀,怎么了?

朋友:我听说了一种可怕且罕见的疾病,我去医生那里做了检测。医生说她将进行一个非常准确的测试。然后今天,她给我打电话告诉我,我检测呈阳性!我肯定有病!

哎呀!你该对你的朋友说些什么呢?首先,让我们让他冷静下来,并试图弄清楚他是否可能患有这种疾病。

:首先,让我们冷静下来。医学中会出错。让我们看看你实际上患病的可能性有多大。医生说测试的准确率有多高?

朋友:她说准确率达到了 99%。这意味着我患病的可能性是 99%!

:等等,让我们看看所有的数字。不考虑测试结果,患病的可能性有多大?有多少人患有这种疾病?

朋友:我在网上看到,平均每 10,000 人中就有一个人患有这种疾病。

:好的,让我拿张纸(把朋友放在一边)。

让我们停下来做一个测验。

测验:你认为你朋友测试呈阳性时患病的概率在什么范围内?

  1. 0–20%

  2. 20–40%

  3. 40–60%

  4. 60–80%

  5. 80–100%

让我们计算这个概率。总结一下,我们有以下两条信息:

  • 测试正确率是 99%。更准确地说(我们向医生确认过这一点),平均来说,在每 100 个健康人中,测试正确诊断了 99 人,在每 100 个病人中,测试也正确诊断了 99 人。因此,在健康人和病人中,测试的准确率都是 99%。

  • 平均来说,每 10,000 人中就有 1 人患病。

让我们做一些粗略的计算,看看概率会是多少。这些计算总结在图 8.1 所示的混淆矩阵中。为了参考,我们可以随机选择一百万人的一个群体。平均来说,每 10,000 人中就有 1 人患病,所以我们预计这 100 人中有 1 人患病,999,900 人健康。

首先,让我们对这 100 个生病的进行测试。因为测试正确率是 99%,我们预计这 100 人中有 99 人会被正确诊断为生病——即 99 个测试呈阳性的病人。

现在,让我们对这 999,900 个健康的人进行测试。测试有 1%的错误率,所以我们预计这 999,900 个健康人中会有 1%的人被误诊为生病。这意味着有 9,999 个健康人测试呈阳性。

这意味着测试呈阳性的总人数是 99 + 9,999 = 10,098 人。在这些人中,只有 99 人是生病的。因此,在你朋友测试呈阳性时,他生病的概率是 99/10.098 = 0.0098,或者 0.98%。这还不到 1%!所以我们可以回到我们朋友身边。

:别担心,根据你给我的数据,你测试呈阳性时生病的概率不到 1%!

朋友:哦,我的天哪,真的吗?这真是太好了,谢谢你!

:不用谢我,感谢数学(眨眼)。

让我们总结一下我们的计算。以下是我们的事实:

  • 事实 1:在每 10,000 人中,有 1 人患有疾病。

  • 事实 2:在每 100 个接受测试的病人中,有 99 个测试呈阳性,1 个测试呈阴性。

  • 事实 3:在每 100 个接受测试的健康人中,有 99 个测试呈阴性,1 个测试呈阳性。

我们选择了一百万人的样本群体,如图 8.1 所示,分解如下:

  • 根据事实 1,我们预计在我们的样本人群中,有 100 人患病,999,900 人健康。

  • 根据事实 2,在 100 个病人中,有 99 个测试呈阳性,1 个测试呈阴性。

  • 根据事实 3,在 999,900 个健康人中,有 9,999 个测试呈阳性,989,901 个测试呈阴性。

图 8.1 在我们的 100 万患者中,只有 100 人患病(底部行)。在诊断为患病的 10,098 人中(左侧列),实际上只有 99 人患病。其余的 9,999 人都是健康的,却被误诊为患病。因此,如果我们的朋友被诊断为患病,他更有可能属于那 9,999 个健康的(左上角)而不是那 99 个患病的(左下角)。

由于我们的朋友测试结果呈阳性,他必须在图 8.1 的左侧列。这一列有 9,999 个被误诊为患病的健康人和 99 个被正确诊断为患病的患者。你的朋友患病的概率是 99/9,999 = 0.0089,这不到 1%。

这有点令人惊讶,如果测试正确率是 99%,那么为什么会这么错误?好吧,如果测试只有 1%的时间出错,那么它并不差。但是,因为每 10,000 人中就有一个人患有这种疾病,这意味着一个人患病的概率是 0.01%。更有可能的是,成为那 1%被误诊的人群,还是成为那 0.01%患病的人群?尽管 1%是一个小群体,但它比 0.01%大得多。测试有问题;它的错误率比患病率大得多。我们在第七章的“两个模型示例:冠状病毒和垃圾邮件”部分也有类似的问题——我们不能依赖准确性来衡量这个模型。

一种看待这个问题的方式是使用树状图。在我们的图中,我们从左侧的一个根开始,它分支成两种可能性:你的朋友是患病或健康。这两种可能性中的每一种又分支成两种更进一步的可能性:你的朋友被诊断为健康或被诊断为患病。这个树状图在图 8.2 中展示,并附有每个分支的患者数量。

图片

图 8.2 可能性的树状图。每个患者可以是患病或健康。对于每一种可能性,患者可以被诊断为患病或健康,这给我们提供了四种可能性。我们从一百万患者开始:其中 100 人患病,其余的 999,900 人健康。在这 100 个患病的人中,有一个人被误诊为健康,其余的 99 人被正确诊断为患病。在 999,900 个健康患者中,有 9,999 人被误诊为患病,其余的 989,901 人被正确诊断为健康。

从图 8.2 中,我们再次可以看到,如果你的朋友测试结果呈阳性,他患病的概率是 99/99+9,999 = 0.0098,前提是他只能位于右侧的第一和第三组。

贝叶斯定理的序言:先验、事件和后验

现在我们已经拥有了陈述贝叶斯定理的所有工具。贝叶斯定理的主要目标是计算一个概率。一开始,如果我们手中没有任何信息,我们只能计算出初始概率,我们称之为先验概率。然后,发生了一个事件,它给我们提供了信息。在这个信息之后,我们对想要计算的概率有了更好的估计。我们称这个更好的估计为后验概率。先验、事件和后验在图 8.3 中得到了说明。

先验 初始概率

事件 发生的事情,它给我们提供信息

后验 使用先验概率和事件计算出的最终(更准确)的概率

下面是一个例子。想象一下,我们想要找出今天下雨的概率。如果我们一无所知,我们只能对概率给出一个粗略的估计,这就是先验概率。如果我们四处看看,发现我们身处亚马逊雨林(这个事件),那么我们可以给出一个更加精确的估计。事实上,如果我们身处亚马逊雨林,今天很可能下雨。这个新的估计就是后验概率。

图 8.3 先验、事件和后验。先验是“原始”概率,即当我们知道很少信息时计算的概率。事件是我们获得的信息,它将帮助我们完善对概率的计算。后验是“加工后”的概率,或者是我们拥有更多信息时计算出的更加准确的概率。

在我们正在进行的医学例子中,我们需要计算一个病人生病的概率。先验概率、事件和后验概率如下:

  • 先验:最初,这个概率是 1/10,000,因为我们没有其他信息,除了知道每 10,000 个病人中有一个人生病。这个 1/10,000,或者说 0.0001,就是先验概率。

  • 事件:突然,新的信息出现了。在这种情况下,病人做了一次测试,并且测试结果呈阳性。

  • 后验:在测试结果呈阳性之后,我们重新计算病人生病的概率,结果是 0.0098。这就是后验概率。

贝叶斯定理是概率论和机器学习最重要的基石之一。它的重要性如此之高,以至于有多个领域以它的名字命名,例如贝叶斯学习贝叶斯统计学贝叶斯分析。在本章中,我们将学习贝叶斯定理以及由此派生出的一个重要分类模型:朴素贝叶斯模型。简而言之,朴素贝叶斯模型做的是大多数分类模型都会做的事情,即从一组

特征。模型以概率的形式返回答案,这个概率是使用贝叶斯定理计算得出的。

用例:垃圾邮件检测模型

本章我们研究的用例是一个垃圾邮件检测模型。这个模型帮助我们区分垃圾邮件和正常邮件。正如我们在第一章和第七章所讨论的,垃圾邮件是指垃圾邮件,而正常邮件是指非垃圾邮件。

基于朴素贝叶斯模型,我们可以输出一封邮件是垃圾邮件或正常邮件的概率。这样,我们可以将概率最高的垃圾邮件直接发送到垃圾邮件文件夹,其余的保留在收件箱中。这个概率应该取决于邮件的特征,例如其文字、发件人、大小等。对于本章,我们只考虑文字作为特征。这个例子与我们在第五章和第六章研究的情感分析例子并没有太大的不同。这个情感分析分类器的关键是每个单词在垃圾邮件中出现的概率。例如,单词“彩票”在垃圾邮件中出现的概率比“会议”要高。这个概率是我们计算的基础。

寻找先验概率:任何一封邮件是垃圾邮件的概率

一封邮件是垃圾邮件的概率是多少?这是一个难题,但让我们尝试做出一个粗略的估计,我们称之为先验概率。我们查看当前的收件箱,统计垃圾邮件和正常邮件的数量。想象一下,在 100 封邮件中,有 20 封是垃圾邮件,80 封是正常邮件。因此,20%的邮件是垃圾邮件。如果我们想要做出一个合理的估计,我们可以认为“据我们所知”,新邮件是垃圾邮件的概率是 0.2。这是先验概率。计算过程在图 8.4 中进行了说明,其中垃圾邮件用深灰色表示,正常邮件用白色表示。

图片

图 8.4 我们有一个包含 100 封邮件的数据集,其中 20 封是垃圾邮件。对一封邮件是垃圾邮件的概率的估计是 0.2。这是先验概率。

寻找后验概率:已知邮件包含特定单词时,邮件是垃圾邮件的概率

当然,并不是所有的邮件都是同等重要的。我们希望利用邮件的特性来做出一个更合理的猜测。我们可以使用许多特性,如发件人、大小或邮件中的单词。对于这个应用,我们只使用邮件中的单词。然而,我鼓励你在思考这个例子时,考虑如何使用其他特性。

假设我们找到一个特定的单词,比如说“彩票”,这个单词在垃圾邮件中比在正常邮件中出现的频率更高。这个单词代表我们的事件。在垃圾邮件中,“彩票”这个单词出现在 15 封邮件中,而在正常邮件中只出现在 5 封邮件中。因此,在包含“彩票”这个单词的 20 封邮件中,有 15 封是垃圾邮件,5 封是正常邮件。因此,包含“彩票”这个单词的邮件是垃圾邮件的概率正好是 15/20 = 0.75。这就是后验概率。计算这个概率的过程在图 8.5 中进行了说明。

图片

图 8.5 我们移除了(灰色显示)不包含单词lottery的邮件。突然之间,我们的概率发生了变化。在包含单词lottery的邮件中,有 15 封是垃圾邮件,5 封是正常邮件,所以包含单词lottery的邮件是垃圾邮件的概率是 15/20 = 0.75。

就这样:我们计算了包含单词lottery的邮件是垃圾邮件的概率。为了总结:

  • 先验概率是 0.2。这是在不知道任何关于邮件信息的情况下,邮件是垃圾邮件的概率。

  • 事件是邮件包含单词lottery。这有助于我们更好地估计概率。

  • 后验概率是 0.75。这是在知道邮件包含单词lottery的情况下,邮件是垃圾邮件的概率。

在这个例子中,我们通过计数邮件并除以来计算概率。这主要是为了教学目的,但在现实生活中,我们可以使用一个公式来计算这个概率的捷径。这个公式被称为贝叶斯定理,我们将在下一部分看到。

数学刚刚发生了什么?将比率转换为概率

一种可视化前一个例子方法是使用所有四种可能性的树形图,就像我们在图 8.2 中的医疗例子中所做的那样。可能性是邮件是垃圾邮件或正常邮件,以及它是否包含单词lottery。我们这样绘制它:我们从根节点开始,它分为两个分支。上面的分支对应垃圾邮件,下面的分支对应正常邮件。每个分支再分为两个更小的分支,即邮件包含单词lottery和不包含时。树形图如图 8.6 所示。注意,在这个树形图中,我们还指出了在总共 100 封邮件中,每个特定组有多少封邮件。

图片

图 8.6 可能性树。根节点分为两个分支:垃圾邮件和正常邮件。然后每个分支再分为两个分支:当邮件包含单词lottery时,和不包含时。

一旦我们有了这个树形图,并且想要计算包含单词lottery的邮件是垃圾邮件的概率,我们只需移除所有不包含单词lottery的分支。这如图 8.7 所示。

图片

图 8.7 从之前的树形图中,我们移除了不包含单词lottery的两个分支。在最初的 100 封邮件中,我们剩下 20 封包含lottery。由于这 20 封邮件,其中 15 封是垃圾邮件,我们得出结论,包含单词lottery的邮件是垃圾邮件的概率是 0.75。

现在,我们有 20 封邮件,其中 15 封是垃圾邮件,5 封是正常邮件。因此,包含单词lottery的邮件是垃圾邮件的概率是图片

但我们已经计算过了,那么图表有什么好处呢?除了使事情更简单之外,好处是通常我们所拥有的信息是基于概率的,而不是基于邮件的数量。很多时候,我们不知道垃圾邮件或正常邮件的数量。我们所知道的是以下信息:

  • 一封邮件是垃圾邮件的概率是 图片

  • 垃圾邮件包含单词lottery的概率是 图片

  • 一封正常邮件包含单词lottery的概率是 图片

  • 问题:包含单词lottery的邮件是垃圾邮件的概率是多少?

首先,让我们检查这些信息是否足够。我们知道邮件是正常邮件的概率吗?嗯,我们知道邮件是垃圾邮件的概率是 图片。唯一的另一种可能性是邮件是正常邮件,所以它必须是补集,即 图片。这是一条重要的规则——互补概率规则。

互补概率规则 对于事件E,事件E的补集,表示为Ec,是与*E*相反的事件。*E*c 的概率是 1 减去E的概率,即,

P(E^c) = 1 − P(E)

因此,我们有以下:

  • 图片:邮件是垃圾邮件的概率

  • 图片:邮件是正常邮件的概率

现在我们来看其他信息。垃圾邮件包含单词lottery的概率是 图片。这可以理解为,邮件包含单词lottery且邮件是垃圾邮件的概率是 0.75。这是一个条件概率,条件是邮件是垃圾邮件。我们用竖线表示条件,所以这可以写成P('lottery'|spam)。这个条件的补集是P(no 'lottery'|spam),即垃圾邮件不包含单词lottery的概率。这个概率是 1 – P('lottery'|spam)。这样,我们可以计算出其他概率,如下所示:

  • 图片:垃圾邮件包含单词lottery的概率。

  • 图片:垃圾邮件不包含单词lottery的概率。

  • 图片:正常邮件包含单词lottery的概率。

  • 图片:正常邮件不包含单词lottery的概率。

我们接下来要做的是找到两个事件同时发生的概率。更具体地说,我们想要以下四个概率:

  • 一封邮件是垃圾邮件且包含单词lottery的概率

  • 一封邮件是垃圾邮件且不包含单词lottery的概率

  • 一封邮件是正常邮件且包含单词lottery

  • 一封邮件是正常邮件且不包含单词lottery的概率

这些事件被称为事件的交集,并用符号∩表示。因此,我们需要找到以下概率:

  • P('lottery'spam)

  • P(no 'lottery'spam)

  • P('lottery'ham)

  • P(no 'lottery'ham)

让我们看看一些数字。我们知道图片,或者说 100 封邮件中有 20 封是垃圾邮件。在这 20 封中,图片包含单词lottery。最后,我们将这两个数字相乘,图片乘以图片,得到图片,这与图片相同,即包含单词lottery的垃圾邮件的比例。我们所做的是以下:我们将邮件是垃圾邮件的概率乘以垃圾邮件包含单词lottery的概率,以获得邮件是垃圾邮件且包含单词lottery的概率。垃圾邮件包含单词lottery的概率正是条件概率,即邮件包含单词lottery的条件概率。这导致了概率的乘法法则。

概率的乘法法则 对于事件EF,它们交集的概率是 F 在 E 条件下的条件概率乘以 F 的概率,即P(EF) = P(E|F) ∩ P(F)。

现在我们可以按照以下方式计算这些概率:

  • 图片

  • 图片

  • 图片

  • 图片

这些概率总结在图 8.8 中。请注意,边上的概率乘积是右侧的概率。此外,请注意,所有这些四个概率的总和为 1,因为它们涵盖了所有可能的场景。

图片

图 8.8 8.6 图中的同一棵树,但现在加入了概率。从根节点出发,有两个分支,一个用于垃圾邮件,一个用于正常邮件。在每个分支中,我们记录相应的概率。每个分支再次分为两个叶子节点:一个用于包含单词lottery的邮件,另一个用于不包含它的邮件。在每个分支中,我们记录相应的概率。请注意,这些概率的乘积是每个叶子节点右侧的概率。例如,对于顶部的叶子节点,1/5 · 3/4 = 3/20 = 0.15。

我们几乎完成了。我们想要找到P(spam|'lottery'),即在邮件包含单词lottery的情况下邮件是垃圾邮件的概率。在我们刚刚研究的四个事件中,只有两个事件出现了单词lottery。因此,我们只需要考虑这些,即:

  • 图片

  • 图片

换句话说,我们只需要考虑图 8.9 中显示的两个分支——第一个和第三个,即包含单词lottery的邮件。

图片

图 8.9 从图 8.8 中的树中,我们移除了不包含单词 lottery 的两个分支。

第一个是一个邮件是垃圾邮件的概率,第二个是邮件是正常邮件(ham)的概率。这两个概率相加不等于 1。然而,因为我们现在生活在一个邮件包含单词 lottery 的世界里,所以这两个是唯一可能的场景。因此,它们的概率应该相加等于 1。此外,它们应该仍然保持相同的相对比例。解决这个问题的方式是归一化——找到两个彼此之间保持相同相对比例的数字,就像 图片图片 那样,但它们的和为 1。找到这些数字的方法是将它们各自除以它们的和。在这种情况下,数字变成了 图片图片。它们简化为 3/4 和 1/4,这就是我们想要的概率。因此,我们得出以下结论:

  • 图片

  • 图片

这正是我们在计算邮件时得出的结论。为了总结这些信息,我们需要一个公式。我们有两个概率:一封邮件是垃圾邮件并且包含单词 lottery 的概率,以及一封邮件是垃圾邮件但不包含单词 lottery 的概率。为了使它们相加等于 1,我们对它们进行了归一化。这相当于将它们各自除以它们的和。用数学术语来说,我们做了以下操作:

图片

如果我们记得这两个概率是什么,使用乘法法则,我们得到以下结果:

图片

为了验证,我们将数字代入得到:

图片

这就是贝叶斯定理的公式!更正式地说:

贝叶斯定理 对于事件 E 和 F,

图片

因为事件 F 可以分解为两个不相交的事件 F|EF|E^c,所以

图片

那么对于两个单词呢?朴素贝叶斯算法

在上一节中,我们计算了当邮件包含关键字 lottery 时,邮件是垃圾邮件的概率。然而,字典中包含许多更多的单词,我们希望计算邮件包含几个单词时,邮件是垃圾邮件的概率。正如你所想象的那样,计算变得更加复杂,但在本节中,我们学习了一个帮助我们估计这个概率的技巧。

通常,这个技巧帮助我们基于两个事件而不是一个事件来计算后验概率(并且它很容易推广到两个以上事件)。它基于以下前提:当事件是独立的,两个事件同时发生的概率是它们概率的乘积。事件并不总是独立的,但假设它们有时是独立的,这有助于我们做出良好的近似。例如,想象以下场景:有一个有 1000 人的岛屿。岛上的居民中有一半(500 人)是女性,十分之一(100 人)有棕色眼睛。你认为有多少居民是女性且有棕色眼睛?如果我们只知道这些信息,我们就无法找出答案,除非我们亲自计数。然而,如果我们假设性别和眼睛颜色是独立的,那么我们可以估计,十分之一的一半的人口是由女性和棕色眼睛的人组成。也就是说,图片的人口。因为总人口是 1000 人,我们对于女性棕色眼睛人数的估计是图片人。也许我们会去岛屿上并发现情况并非如此,但根据我们所知最好的情况,50 是一个很好的估计。有人可能会说,我们对性别和眼睛颜色独立性的假设是天真的,也许它确实是,但这是在给定信息的情况下我们能做出的最佳估计。

在上一个例子中我们使用的规则是独立概率的乘法法则,它表述如下:

独立概率的乘法法则 如果两个事件 EF 是独立的,即一个事件的发生不会以任何方式影响另一个事件的发生,那么两个事件同时发生的概率(事件的交集)是每个事件概率的乘积。换句话说,

P(EF) = P(E) · P(F).

回到电子邮件的例子。在我们计算出包含“彩票”一词的电子邮件是垃圾邮件的概率后,我们注意到另一个词“销售”也经常出现在垃圾邮件中。我们想要找出包含“彩票”和“销售”两个词的电子邮件是垃圾邮件的概率。我们首先计算包含“销售”一词的垃圾邮件和正常邮件的数量,发现它在 20 封垃圾邮件中的 6 封和 80 封正常邮件中的 4 封中出现。因此,概率如下(如图 8.10 所示):

图片

图片

图 8.10 在与“彩票”一词类似的计算中,我们查看包含“销售”一词的电子邮件。在这些(未灰显的)电子邮件中,有六个垃圾邮件和四个正常邮件。

可以再次使用贝叶斯定理来得出结论,即一个电子邮件包含单词 销售 时是垃圾邮件的概率为 0.6,我鼓励你自己进行计算。然而,更重要的问题是:一个电子邮件同时包含单词 彩票销售 时是垃圾邮件的概率是多少?在我们进行这个计算之前,让我们先找出一个电子邮件包含单词 彩票销售 时它是垃圾邮件的概率。这应该很容易:我们查看所有电子邮件,找出有多少垃圾邮件包含这两个单词。

然而,我们可能会遇到没有包含单词 彩票销售 的电子邮件的问题。我们只有 100 封电子邮件,当我们试图在这封电子邮件上找到两个单词时,我们可能没有足够的样本来正确估计概率。我们能做什么呢?一个可能的解决方案是收集更多的数据,直到我们拥有如此多的电子邮件,以至于有可能其中一些包含这两个单词。然而,可能的情况是我们无法收集更多的数据,所以我们只能利用我们已有的数据。这就是天真假设能帮到我们的地方。

让我们尝试以与我们在本节开头估计岛屿上棕色眼睛女性数量相同的方式估计这个概率。我们知道,根据前一小节,单词 彩票 出现在垃圾邮件中的概率是 0.75。在本节较早的部分,销售 出现在垃圾邮件中的概率是 0.6。因此,如果我们天真地假设这两个单词的出现是独立的,那么这两个单词同时出现在垃圾邮件中的概率是 0.75 · 0.3 = 0.225。以类似的方式,因为我们计算出包含单词 彩票 的火腿邮件的概率是 0.0625,包含单词 销售 的概率是 0.05,那么包含这两个单词的火腿邮件的概率是 0.0625 · 0.05 = 0.003125。换句话说,我们已经做了以下估计:

  • P('彩票', '销售'|垃圾邮件) = P('彩票'|垃圾邮件) P('销售'|垃圾邮件) = 0.75 · 0.3 = 0.225

  • P('彩票' , '销售'|火腿) = P('彩票'|火腿) P('销售'|火腿) = 0.0625 · 0.05 = 0.003125

我们所做天真假设如下:

天真假设:电子邮件中出现的单词彼此完全独立。换句话说,一个特定单词在电子邮件中的出现不会以任何方式影响另一个单词的出现。

很可能,这种天真假设是不正确的。一个词的出现有时会极大地影响另一个词的出现。例如,如果一个电子邮件包含单词 ,那么在这个电子邮件中出现 胡椒 的可能性就更大,因为它们经常一起出现。这就是我们的假设之所以天真。然而,实际上这个假设效果很好,它极大地简化了我们的数学计算。这被称为概率乘法定律,如图 8.11 所示。

现在我们有了概率的估计值,我们继续寻找包含单词lotterysale的垃圾邮件和正常邮件的期望数量。

  • 因为有 20 封垃圾邮件,且垃圾邮件同时包含这两个单词的概率是 0.45,所以同时包含这两个单词的垃圾邮件的期望数量是 20 · 0.225 = 4.5。

  • 同样,有 80 封正常邮件,且正常邮件同时包含这两个单词的概率是 0.00325,所以同时包含这两个单词的正常邮件的期望数量是 80 · 0.00325 = 0.25。

图片

图 8.11 假设有 20%的邮件包含单词lottery,10%的邮件包含单词sale。我们做出一个简单的假设,即这两个单词相互独立。在这个假设下,包含这两个单词的邮件的百分比可以估计为 2%,即 20%和 10%的乘积。

之前的计算表明,如果我们只将数据集限制为包含单词lotterysale的邮件,我们预计其中 4.5 封是垃圾邮件,0.25 封是正常邮件。因此,如果我们随机从中选择一封,选择到垃圾邮件的概率是多少?这看起来可能比整数更难,但如果我们看图 8.12,这可能会更清楚。我们有 4.5 封垃圾邮件和 0.25 封正常邮件(这正好是四分之一封邮件)。如果我们扔一个飞镖,它落在邮件上,那么它落在垃圾邮件上的概率是多少?嗯,邮件的总数(或者如果你愿意这样想象,总面积)是 4.5 + 0.25 = 4.75。因为 4.5 是垃圾邮件,飞镖落在垃圾邮件上的概率是 4.5/4.75 = 0.9474。这意味着包含单词lotterysale的邮件有 94.74%的概率是垃圾邮件。这相当高!

图片

图 8.12 我们有 4.5 封垃圾邮件和 0.25 封正常邮件。我们扔一个飞镖,它击中了一封邮件。击中垃圾邮件的概率是多少?答案是 94.74%。

我们在这里使用概率论,运用贝叶斯定理,除了事件

  • E = lotterysale

  • F = spam

得到公式

图片

然后,我们(天真地)假设单词lotterysale在垃圾邮件(和正常邮件)中的出现是独立的,得到以下两个公式:

P(lotterysale|spam) = P(lottery|spam) · P(sale|spam)

P(lotterysale | ham) = P(lottery | ham) · P(sale | ham)

将它们代入前面的公式,我们得到

图片

最后,代入以下值:

图片

我们得到

图片

那么,如果有超过两个单词呢?

在一般情况下,邮件有n个单词x[1],x[2],… ,x[n]。贝叶斯定理表明,给定邮件包含单词x[1],x[2],… ,x[n],邮件是垃圾邮件的概率是

图片

在前面的方程中,我们移除了交集符号,并用逗号替换了它。朴素假设是所有这些单词的出现是独立的。因此,

P(x[1], x[2], … , x[n] | spam) = P(x[1] | spam) P(x[2] | spam) … P(x[n] | spam)

P(x[1], x[2], … , x[n] | ham) = P(x[1] | ham) P(x[2] | ham) … P(x[n] | ham).

将最后三个方程组合起来,我们得到

右侧的每个量都很容易估计为电子邮件数量的比率。例如,P(x[i] | spam) 是包含单词 x[i] 的垃圾邮件数量与垃圾邮件总数之间的比率。

作为一个小例子,假设电子邮件中包含单词 lotterysalemom。我们检查单词 mom 并注意到它在 20 封垃圾邮件中只出现了一次,在 80 封正常邮件中有 10 次。因此,P('mom'|spam) = 1/20 和 P('mom'|ham) = 1/8。使用与上一节相同的单词 lotterysale 的概率,我们得到以下结果:

注意,将单词 mom 加入方程中,将垃圾邮件的概率从 94.74%降低到 87.80%,这是有道理的,因为这个单词在正常邮件中比在垃圾邮件中更可能出现。

使用真实数据构建垃圾邮件检测模型

现在我们已经开发出了算法,让我们挽起袖子编写朴素贝叶斯算法。Scikit-Learn 等几个包对这个算法有很好的实现,我鼓励你看看它们。然而,我们将手动编写它。我们使用的数据集来自 Kaggle,有关下载链接,请查看附录 C 中本章的资源。以下是本节的代码:

对于这个例子,我们将介绍一个用于处理大型数据集的有用包,称为 Pandas(要了解更多信息,请查看第十三章中的“使用 Pandas 加载数据集”部分)。在 pandas 中存储数据集的主要对象是 DataFrame。要将我们的数据加载到 Pandas DataFrame 中,我们使用以下命令:

import pandas
emails = pandas.read_csv('emails.csv')

在表 8.1 中,你可以看到数据集的前五行。

这个数据集有两个列。第一列是电子邮件的文本(包括其主题行),以字符串格式表示。第二列告诉我们电子邮件是否是垃圾邮件(1)或正常邮件(0)。首先我们需要做一些数据预处理。

表 8.1:我们电子邮件数据集的前五行。文本列显示了每封电子邮件中的文本,垃圾邮件列显示如果电子邮件是垃圾邮件则为 1,如果是正常邮件则为 0。请注意,前五封电子邮件都是垃圾邮件。

文本 垃圾邮件
主题:自然吸引你的企业... 1
主题:股票交易枪手 fanny i... 1
主题:难以置信的新家园制作简单... 1
主题:4 色打印特殊请求添加... 1
主题:没有钱,获取软件 cds... 1

数据预处理

让我们先从将文本字符串转换为单词列表开始。我们使用以下函数来完成这项工作,该函数使用lower()函数将所有单词转换为小写,并使用split()函数将单词转换为列表。我们只检查每个单词是否出现在电子邮件中,而不管它出现多少次,所以我们将其转换为集合,然后再转换为列表。

def process_email(text):
text = text.lower()
return list(set(text.split()))

现在我们使用apply()函数将此更改应用于整个列。我们将新列命名为 emails['words']。

emails['words'] = emails['text'].apply(process_email)

修改后的电子邮件数据集的前五行如表 8.2 所示。

表 8.2 带有新列“Words”的电子邮件数据集,其中包含电子邮件中的单词列表(不重复)和主题行

文本 垃圾邮件 单词
主题:自然吸引你的企业... 1 [letsyou, all, do, but, list, is, information,...
主题:股票交易枪手 fanny i... 1 [not, like, duane, trading, libretto, attainde...
主题:难以置信的新家园制作简单... 1 [im, have, $, take, foward, all, limited, subj...
主题:4 色打印特殊请求添加... 1 [color, azusa, pdf, printable, 8102, subject:,...
主题:没有钱,获取软件 cds... 1 [get, not, have, all, do, subject:, be, by, me...

寻找先验概率

首先,我们需要找到一封电子邮件是垃圾邮件的概率(即先验概率)。为此,我们计算垃圾邮件的数量,并将其除以总邮件数量。请注意,垃圾邮件的数量是垃圾邮件列中条目的总和。以下行将完成这项工作:

sum(emails['spam'])/len(emails)
0.2388268156424581

我们推断出,电子邮件是垃圾邮件的先验概率大约为 0.24。这是如果我们对电子邮件一无所知时,电子邮件是垃圾邮件的概率。同样,电子邮件是正常邮件的先验概率大约为 0.76。

使用贝叶斯定理寻找后验概率

我们需要找到垃圾邮件(和正常邮件)包含特定单词的概率。我们同时为所有单词计算这些概率。以下函数创建了一个名为 model 的字典,记录了每个单词,以及该单词在垃圾邮件和正常邮件中出现的次数:

model = {}

for index, email in emails.iterrows():
    for word in email['words']:
        if word not in model:
            model[word] = {'spam': 1, 'ham': 1}
        if word in model:
            if email['spam']:
                model[word]['spam'] += 1
            else:
                model[word]['ham'] += 1

注意,计数初始化为 1,因此实际上我们记录了垃圾邮件和正常邮件各多一次的出现次数。我们使用这个小技巧来避免出现零计数,因为我们不希望意外地除以零。现在让我们按照以下方式检查字典的一些行:

model['lottery']
{'ham': 1, 'spam': 9}

model['sale']
{'ham': 42, 'spam': 39}

这意味着单词lottery出现在 1 封正常邮件和 9 封垃圾邮件中,而单词sale出现在 42 封正常邮件和 39 封垃圾邮件中。尽管这个字典没有包含任何概率,但可以通过将第一个条目除以两个条目的总和来推断这些概率。因此,如果一封邮件包含单词 lottery,那么它是垃圾邮件的概率是 ,如果它包含单词 sale,那么它是垃圾邮件的概率是

实现朴素贝叶斯算法

算法的输入是邮件。它遍历邮件中的所有单词,并对每个单词计算包含该单词的垃圾邮件概率和正常邮件概率。这些概率是使用我们在上一节中定义的字典计算的。然后我们乘以这些概率(朴素假设)并应用贝叶斯定理来找到包含特定邮件中单词的邮件是垃圾邮件的概率。使用此模型进行预测的代码如下:

def predict_naive_bayes(email):
    total = len(emails)                                      ❶
    num_spam = sum(emails['spam'])
    num_ham = total - num_spam
    email = email.lower()                                    ❷
    words = set(email.split())
    spams = [1.0]
    hams = [1.0]
    for word in words:
        if word in model:
            spams.append(model[word]['spam']/num_spam*total) ❸
            hams.append(model[word]['ham']/num_ham*total)
    prod_spams = np.long(np.prod(spams)*num_spam)            ❹
    prod_hams = np.long(np.prod(hams)*num_ham)
    return prod_spams/(prod_spams + prod_hams)               ❺ 

❶ 计算总邮件数、垃圾邮件数和正常邮件数

❷ 通过将其转换为单词列表(小写)来处理每封邮件

❸ 对于每个单词,计算包含该单词的邮件是垃圾邮件(或正常邮件)的条件概率,作为一个比率

❹ 将所有之前的概率乘以邮件是垃圾邮件的先验概率,并称这个结果为 prod_spams。对 prod_hams 执行类似的过程。

❺ 对这两个概率进行归一化,使它们相加等于 1(使用贝叶斯定理),并返回结果

你可能会注意到,在前面的代码中,我们使用了另一个小的技巧。每个概率都乘以数据集中邮件的总数。这个因素不会影响我们的计算,因为该因素同时出现在分子和分母中。然而,它确实确保了我们的概率乘积对于 Python 来说不是太小,可以处理。

现在我们已经建立了模型,让我们通过在邮件上进行预测来测试它,如下所示:

predict_naive_bayes('Hi mom how are you')
0.12554358867163865

predict_naive_bayes('meet me at the lobby of the hotel at nine am')
0.00006964603508395

predict_naive_bayes('buy cheap lottery easy money now')
0.9999734722659664

predict_naive_bayes('asdfgh')
0.2388268156424581

它似乎工作得很好。像“hi mom how are you”这样的邮件被判定为垃圾邮件的概率很低(大约 0.12),而像“buy cheap lottery easy money now”这样的邮件被判定为垃圾邮件的概率非常高(超过 0.99)。注意,最后一封邮件没有包含字典中的任何单词,其概率为 0.2388,这正是先验概率。

进一步工作

这是对朴素贝叶斯算法的快速实现。但对于更大的数据集和更大的邮件,我们应该使用一个包。像 Scikit-Learn 这样的包提供了对朴素贝叶斯算法的优秀实现,具有许多可调整的参数。探索这个和其他包,并在所有类型的数据集上使用朴素贝叶斯算法!

摘要

  • 贝叶斯定理是概率论、统计学和机器学习中广泛使用的技术。

  • 贝叶斯定理包括根据先验概率和事件来计算后验概率。

  • 先验概率是在信息非常有限的情况下对概率的基本计算。

  • 贝叶斯定理使用事件来对所讨论的概率做出更好的估计。

  • 当想要将先验概率与几个事件结合起来时,使用朴素贝叶斯算法。

  • “天真”这个词来源于我们做出了一个天真的假设,即所讨论的事件都是独立的。

练习

练习 8.1

对于每一对事件 A 和 B,确定它们是独立的还是依赖的。对于 (a) 到 (d),提供数学证明。对于 (e) 和 (f),提供口头证明。

抛掷三个公平的硬币:

  1. A: 第一个落地为正面。B: 第三个落地为反面。

  2. A: 第一个落地为正面。B: 三次抛掷中有奇数个正面。

    抛掷两个骰子:

  3. A: 第一个显示 1。B: 第二个显示 2。

  4. A: 第一个显示 3。B: 第二个显示的值比第一个高。

    对于以下内容,提供口头证明。假设对于这个问题,我们生活在一个有季节的地方。

  5. A: 外面在下雨。B: 今天是星期一。

  6. A: 外面在下雨。B: 今天是六月。

练习 8.2

我们必须定期去一个办公室办理一些文件。这个办公室有两个职员,Aisha 和 Beto。我们知道 Aisha 每周工作三天,Beto 工作其他两天。然而,每周的日程都会改变,所以我们永远不知道 Aisha 在哪三天,Beto 在哪两天。

  1. 如果我们在一个随机日子里去办公室,Aisha 是职员的可能性是多少?

    我们从外面看,注意到职员穿着红色的毛衣,尽管我们无法确定谁是职员。我们经常去那个办公室,所以知道 Beto 比 Aisha 更经常穿红色。事实上,Aisha 每三天穿一次红色(三分之一的时间),Beto 每两天穿一次红色(一半的时间)。

  2. 知道今天职员穿着红色毛衣的情况下,Aisha 是职员的可能性是多少?

练习 8.3

以下是一组测试过 COVID-19 阳性或阴性的病人的数据集。他们的症状是咳嗽(C)、发烧(F)、呼吸困难(B)和疲劳(T)。

咳嗽(C) 发烧(F) 呼吸困难(B) 疲劳(T) 诊断
病人 1 X X X 病人
病人 2 X X X 病人
病人 3 X X X 病人
病人 4 X X X 病人
病人 5 X X 健康
病人 6 X X 健康
病人 7 X 健康
病人 8 X 健康

本练习的目的是构建一个朴素贝叶斯模型,从症状预测诊断。使用朴素贝叶斯算法找到以下概率:

注意:对于以下问题,我们没有提到的症状对我们来说完全未知。例如,如果我们知道病人有咳嗽,但没有提到他们的发烧,这并不意味着病人没有发烧。

  1. 患者咳嗽的情况下生病的概率

  2. 患者不疲劳的情况下生病的概率

  3. 患者咳嗽且有发烧的情况下生病的概率

  4. 患者咳嗽且有发烧,但没有呼吸困难的情况下生病的概率

9 通过提问来分割数据:决策树

在本章

  • 决策树是什么

  • 使用决策树进行分类和回归

  • 使用用户信息构建应用推荐系统

  • 准确率、基尼指数和熵,以及它们在构建决策树中的作用

  • 使用 Scikit-Learn 在大学录取数据集上训练决策树

图片

在本章中,我们介绍决策树。决策树是强大的分类和回归模型,同时也为我们提供了大量关于数据集的信息。就像我们在本书中学到的先前模型一样,决策树使用标记数据进行训练,我们想要预测的标签可以是类别(用于分类)或值(用于回归)。在本章的大部分内容中,我们专注于分类决策树,但在本章的末尾,我们描述了回归决策树。然而,这两种类型树的结构和训练过程是相似的。在本章中,我们开发了几个用例,包括一个应用推荐系统和预测大学录取的模型。

决策树遵循直观的过程进行预测——这与人类的推理非常相似。考虑以下场景:我们想决定今天是否应该穿夹克。决策过程看起来像什么?我们可能会向外看并检查是否在下雨。如果下雨,那么我们肯定会穿夹克。如果不下雨,那么我们可能会检查温度。如果天气热,那么我们就不穿夹克,但如果天气冷,那么我们就穿夹克。在图 9.1 中,我们可以看到这个决策过程的图形,其中决策是通过从上到下遍历树来做出的。

图片

图 9.1 用于决定在给定的一天是否需要穿夹克的决策树。我们通过遍历树向下并选择与每个正确答案对应的分支来做出决定。

我们的决策过程看起来像一棵树,但它是倒置的。树由顶点组成,称为节点和边。在最顶部,我们可以看到根节点,从中伸出两个分支。每个节点要么有两个或零个分支(边)从中伸出,因此我们称它为二叉树。有两个分支从中伸出的节点称为决策节点,没有分支从中伸出的节点称为叶子节点叶子。这种节点、叶子和边的排列就是我们所说的决策树。树是计算机科学中的自然对象,因为计算机将每个过程分解成一系列二进制操作。

最简单的决策树,称为决策桩,由一个决策节点(根节点)和两个叶子组成。这代表了一个简单的是或否问题,基于此我们立即做出决定。

决策树的深度是根节点下方的层数。另一种衡量方法是根节点到叶子节点的最长路径的长度,其中路径的长度由其包含的边数来衡量。图 9.1 中的树深度为 2。决策树桩的深度为 1。

以下是到目前为止我们所学的定义总结:

决策树 基于是或否问题并由二叉树表示的机器学习模型。该树有一个根节点、决策节点、叶子节点和分支。

根节点 树的最顶层节点。它包含第一个是或否问题。为了方便起见,我们称它为

决策节点 我们模型中的每个是或否问题都由一个决策节点表示,从这个节点延伸出两个分支(一个用于“是”答案,另一个用于“否”答案)。

叶子节点 没有分支从其延伸出的节点。这些代表我们在遍历树后所做的决策。为了方便起见,我们称它们为叶子

分支 从每个决策节点延伸出的两个边,对应于节点中问题的“是”和“否”答案。在本章中,按照惯例,左边的分支对应“是”,右边的分支对应“否”。

深度 决策树中的层数。或者,它也是从根节点到叶子节点的最长路径上的分支数。

在本章中,节点被绘制为边缘圆润的矩形,分支中的答案以菱形表示,叶子以椭圆形表示。图 9.2 展示了决策树的一般外观。

图片

图 9.2 一个带有根节点、决策节点、分支和叶子的常规决策树。请注意,每个决策节点都包含一个是或否问题。从每个可能的答案中,延伸出一个分支,可以引导到另一个决策节点或叶子。这棵树深度为 2,因为从叶子到根的最长路径穿过两个分支。

我们是如何构建这棵树的?为什么我们问的是这些问题?我们还可以检查是否是星期一,是否看到外面的红色汽车,或者我们是否饿了,然后构建以下决策树:

图片

图 9.3 我们可以用第二个(可能不是那么好)的决策树来决定在给定的一天是否要穿夹克

当涉及到决定是否穿夹克时,我们认为哪棵树更好:树 1(图 9.1)还是树 2(图 9.3)?嗯,作为人类,我们有足够的经验来判断树 1 对于这个决定比树 2 好得多。计算机会如何知道呢?计算机本身没有经验,但它们有类似的东西,那就是数据。如果我们想像计算机一样思考,我们可以只是遍历所有可能的树,尝试每一棵树一段时间——比如说,一年——然后通过计算我们使用每一棵树做出正确决定的次数来比较它们的性能。我们可以想象,如果我们使用树 1,我们大多数日子都是正确的,而如果我们使用树 2,我们可能会在寒冷的一天没有穿夹克,或者在极其炎热的一天穿上了夹克。计算机所需要做的就是遍历所有树,收集数据,并找到最好的那一棵,对吧?

几乎是!不幸的是,即使是对于计算机来说,搜索所有可能的树以找到最有效的一棵树也会花费非常长的时间。但是幸运的是,我们有使这种搜索变得更快的方法,因此,我们可以使用决策树进行许多奇妙的应用,包括垃圾邮件检测、情感分析和医疗诊断。在本章中,我们将介绍一种快速构建良好决策树的方法。简而言之,我们一次构建一个节点,从顶部开始。为了选择与每个节点相对应的正确问题,我们考虑所有可能的问题,并选择那些出现次数最多的一个问题。这个过程如下所示:

选择一个好的第一个问题

我们需要为树的根选择一个好的第一个问题。什么是一个好的问题,可以帮助我们决定在给定的一天是否要穿夹克?最初,它可以是任何问题。让我们假设我们为第一个问题想出了五个候选问题:

  1. 天气在下雨吗?

  2. 外面冷吗?

  3. 我饿吗?

  4. 外面有红色的车吗?

  5. 是星期一吗?

在这五个问题中,哪一个看起来最适合帮助我们决定是否应该穿夹克?我们的直觉告诉我们,最后三个问题对我们做出决定没有帮助。让我们假设,根据经验,我们发现前两个问题中,第一个更有用。我们使用这个问题开始构建我们的树。到目前为止,我们有一个简单的决策树,或者称为决策桩,由那个单一的问题组成,如图 9.4 所示。

图 9.4 一个简单的决策树(决策桩),它只包含一个问题:“天气在下雨吗?”如果答案是肯定的,我们做出的决定是穿上夹克。

我们能否做得更好?想象一下,当我们开始注意到下雨时,穿夹克总是正确的决定。然而,有些天不下雨,不穿夹克就不是正确的决定。这就是问题 2 出现的时候。我们用这个问题来帮助我们,以下是这样做的:在我们确认没有下雨之后,然后我们检查

温度,如果天气冷,我们就决定穿夹克。这使得树的左叶子变成一个节点,从这个节点延伸出两个叶子,如图 9.5 所示。

图片

图 9.5:比图 9.4 中的决策树稍微复杂一些,我们在其中选择了一片叶子并将其分割成两个更进一步的叶子。这与图 9.1 中的树是相同的。

现在我们有了我们的决策树。我们能做得更好吗?也许我们可以通过向我们的树中添加更多的节点和叶子来实现。但到目前为止,这个树已经非常有效了。在这个例子中,我们使用我们的直觉和经验来做决定。在本章中,我们将学习一个仅基于数据构建这些树的算法。

你可能会在脑海中涌现出许多问题,例如以下这些问题:

  1. 你究竟是如何决定提出哪个最佳问题的?

  2. 总是选择最佳可能问题的过程实际上能让我们构建出最佳决策树吗?

  3. 为什么我们不构建所有可能的决策树,然后从中选择最好的一个呢?

  4. 我们会编写这个算法吗?

  5. 我们在现实生活中可以在哪里找到决策树?

  6. 我们可以看到决策树是如何用于分类的,但它们又是如何用于回归的呢?

本章回答了所有这些问题,但这里有一些快速的答案:

  1. 你究竟是如何决定提出哪个最佳问题的?

    我们有几种方法可以做到这一点。最简单的一种是使用准确度,这意味着:哪个问题能让我更频繁地做出正确的决定?然而,在本章中,我们还学习了其他方法,例如基尼指数或熵。

  2. 总是选择最佳可能问题的过程实际上能让我们构建出 最佳 决策树吗?

    实际上,这个过程并不能保证我们得到最佳可能的树。这就是我们所说的贪婪算法。贪婪算法是这样工作的:在每一个点上,算法做出最佳可能的选择。它们通常工作得很好,但并不是在每一个时间步都做出最佳可能的选择就能得到最佳的整体结果。有时候,提出一个较弱的问题可能会以某种方式对我们的数据进行分组,最终我们得到一个更好的树。然而,构建决策树的算法通常工作得非常好且非常快,所以我们就这样接受了。看看本章中我们看到的所有算法,并尝试找出去除贪婪属性的方法来改进它们!

  3. 为什么我们不构建所有可能的决策树,然后从中选择最好的一个呢?

    可能的决策树数量非常大,尤其是如果我们的数据集有很多特征。遍历所有这些树会非常慢。在这里,找到每个节点只需要在特征上做线性搜索,而不是在所有可能的树上搜索,这使得它更快。

  4. 我们会编码这个算法吗?

    这个算法可以手动编码。然而,我们会看到,由于它是递归的,编码可能会有些繁琐。因此,我们将使用一个有用的包 Scikit-Learn,用真实数据构建决策树。

  5. 我们可以在现实生活中找到决策树吗?

    在许多地方!它们在机器学习中被广泛使用,不仅因为它们工作得非常好,而且因为它们为我们提供了大量关于数据的信息。决策树被使用的某些地方包括在推荐系统中(推荐视频、电影、app、购买产品等),在垃圾邮件分类中(决定一封邮件是否为垃圾邮件),在情感分析中(决定一句话是快乐还是悲伤),以及在生物学中(决定患者是否生病或帮助识别物种或基因组类型中的某些层次结构)。

  6. 我们可以看到决策树是如何进行分类的,但它们又是如何进行回归的呢?

    回归决策树看起来与分类决策树完全一样,只是叶子节点不同。在分类决策树中,叶子节点有类别,如是或否。在回归决策树中,叶子节点有值,如 4,8.2,或-199。我们的模型做出的预测是在向下遍历树时到达的叶子节点。

本章我们将研究的第一个用例是机器学习中的一个流行应用,也是我最喜欢的之一:推荐系统。

本章的代码可在以下 GitHub 仓库中找到:github.com/luisguiserrano/manning/tree/master/Chapter_9_Decision_Trees

问题:我们需要根据用户可能下载的内容向他们推荐 app

推荐系统是机器学习中最为常见和激动人心的应用之一。你是否好奇 Netflix 是如何推荐电影的,YouTube 是如何猜测你可能观看的视频,或者 Amazon 是如何展示你可能感兴趣购买的产品?这些都是推荐系统的例子。一个简单且有趣的方式来观察推荐问题,就是将它们视为分类问题。让我们从一个简单的例子开始:我们自己的使用决策树的 app 推荐系统。

假设我们想要构建一个系统,向用户推荐他们可以在以下选项中下载的 app。我们商店中有以下三个 app(图 9.6):

  • 原子计数器:一个用于计算你身体中原子数量的 app

  • 蜂巢寻找者:一个将你的位置映射并找到最近蜂巢的 app

  • 检查棋局:一个用于寻找澳大利亚棋手的 app

图 9.6 我们推荐的三款应用:Atom Count,一款用于计算你体内原子数量的应用;Beehive Finder,一款用于定位你位置附近蜂箱的应用;以及 Check Mate Mate,一款用于寻找你所在地区澳大利亚棋手的应用

训练数据是一个表格,包含用户使用的平台(iPhone 或 Android)、他们的年龄以及他们下载的应用(在现实生活中有更多平台,但为了简单起见,我们假设只有这两种选项)。我们的表格包含六个人,如表 9.1 所示。

表 9.1 一个应用商店的用户数据集。对于每个客户,我们记录他们的平台、年龄和他们下载的应用。

平台 年龄 应用
iPhone 15 Atom Count
iPhone 25 Check Mate Mate
Android 32 Beehive Finder
iPhone 35 Check Mate Mate
Android 12 Atom Count
Android 14 Atom Count

给定这个表格,你会向以下三位客户推荐哪个应用?

  • 客户 1:一位 13 岁的 iPhone 用户

  • 客户 2:一位 28 岁的 iPhone 用户

  • 客户 3:一位 34 岁的 Android 用户

我们应该采取以下措施:

客户 1:一位 13 岁的 iPhone 用户。对于这位客户,我们应该推荐 Atom Count,因为从这些青少年客户(三个)来看,年轻人似乎更倾向于下载 Atom Count。

客户 2:一位 28 岁的 iPhone 用户。对于这位客户,我们应该推荐 Check Mate Mate,因为从数据集中两位 iPhone 用户(年龄分别为 25 岁和 35 岁)来看,他们都下载了 Check Mate Mate。

客户 3:一位 34 岁的 Android 用户。对于这位客户,我们应该推荐 Beehive Finder,因为在数据集中有一位 32 岁的 Android 用户下载了 Beehive Finder。

然而,逐个客户进行操作似乎是一项繁琐的工作。接下来,我们将构建一个决策树来一次性处理所有客户。

解决方案:构建一个应用推荐系统

在本节中,我们将看到如何使用决策树构建一个应用推荐系统。简而言之,构建决策树的算法遵循以下步骤:

  1. 确定哪些数据对于决定推荐哪个应用最有用。

  2. 这个特征将数据分割成两个更小的数据集。

  3. 对每个较小的数据集重复步骤 1 和 2。

换句话说,我们所做的是决定两个特征(平台或年龄)中哪一个更成功地决定了用户会下载哪个应用,并选择这个作为我们的决策树的根。然后,我们遍历分支,始终选择该分支数据中最具决定性的特征,从而构建我们的决策树。

构建模型的第一步:提出最佳问题

构建我们的模型的第一步是找出最有用的特征:换句话说,最有用的提问。首先,让我们稍微简化我们的数据。让我们称 20 岁以下的人为“青年”,20 岁及以上的人为“成人”(别担心——我们很快就会回到原始数据集,在“使用连续特征(如年龄)划分数据”部分)。我们的修改后的数据集如表 9.2 所示。

表 9.2 表 9.1 中数据集的简化版本,其中年龄列已简化为两个类别,“青年”和“成人”

平台 年龄 应用
iPhone 青年 Atom Count
iPhone 成人 Check Mate Mate
Android 成人 Beehive Finder
iPhone 成人 Check Mate Mate
Android 青年 Atom Count
Android 青年 Atom Count

决策树的基本构建块是形式为“用户是否使用 iPhone?”或“用户是否是青年?”的问题。我们需要其中之一作为树的根。我们应该选择哪一个?我们应该选择最能确定他们下载的应用的问题。为了决定哪个问题在这方面更好,让我们比较它们。

第一个问题:用户使用的是 iPhone 还是 Android?

这个问题将用户分为两组,iPhone 用户和 Android 用户。每组都有三个用户。但我们需要跟踪每个用户下载了哪个应用。快速查看表 9.2 可以帮助我们注意到以下内容:

  • 在 iPhone 用户中,有一个人下载了 Atom Count,两个人下载了 Check Mate Mate。

  • 在 Android 用户中,有两个人下载了 Atom Count,一个人下载了 Beehive Finder。

结果决策树桩如图 9.7 所示。

图 9.7 如果我们按平台划分我们的用户,我们得到以下划分:iPhone 用户在左侧,Android 用户在右侧。在 iPhone 用户中,有一个人下载了 Atom Count,两个人下载了 Check Mate Mate。在 Android 用户中,有两个人下载了 Atom Count,一个人下载了 Beehive Finder。

现在,让我们看看如果我们按年龄划分会发生什么。

第二个问题:用户是青年还是成人?

这个问题将用户分为两组,青年和成人。再次,每组都有三个用户。快速查看表 9.2 可以帮助我们注意到每个用户下载了什么,如下所示:

  • 青年用户都下载了 Atom Count。

  • 在成人用户中,有两个人下载了 Atom Count,一个人下载了 Beehive Finder。

结果决策树桩如图 9.8 所示。

图 9.8 如果我们按年龄划分我们的用户,我们得到以下划分:青年在左侧,成人在右侧。在青年用户中,所有三个都下载了 Atom Count。在成人用户中,一个人下载了 Beehive Finder,两个人下载了 Check Mate Mate。

从图 9.7 和 9.8 中看,哪一个看起来是更好的分割?似乎第二个(基于年龄)更好,因为它注意到了所有三个年轻人下载了 Atom Count 的事实。但我们需要计算机来确定年龄是一个更好的特征,所以我们会给它一些数字来比较。在本节中,我们学习三种比较这两种分割的方法:准确率、吉尼不纯度和熵。让我们从第一个开始:准确率。

准确率:我们的模型有多正确?

我们在第七章学习了准确率,但这里有一个简短的回顾。准确率是正确分类的数据点数占总数据点数的比例。

假设我们只被允许问一个问题,并且必须通过这个问题来确定向用户推荐哪个应用。我们有以下两个分类器:

  • 分类器 1:询问“你使用什么平台?”然后根据这个信息确定推荐哪个应用

  • 分类器 2:询问“你多大年龄?”然后根据这个信息确定推荐哪个应用

让我们更仔细地看看分类器。关键观察结果是:如果我们必须通过只问一个问题来推荐一个应用,我们最好的选择是查看所有给出相同答案的人,并推荐他们中最常见的应用。

分类器 1:你使用什么平台?

  • 如果答案是“iPhone”,那么我们会注意到在所有 iPhone 用户中,大多数下载了 Check Mate Mate。因此,我们向所有 iPhone 用户推荐 Check Mate Mate。我们正确率是三分之二

  • 如果答案是“Android”,那么我们会注意到在所有 Android 用户中,大多数下载了 Atom Count,所以我们向所有 Android 用户推荐这个应用。我们正确率是三分之二

分类器 2:你多大年龄?

  • 如果答案是“年轻”,那么我们会注意到所有年轻人下载了 Atom Count,所以我们做出这个推荐。我们正确率是三次三

  • 如果答案是“成人”,那么我们会注意到在所有成人中,大多数下载了 Check Mate Mate,所以我们推荐这个应用。我们正确率是三分之二

注意到分类器 1 有四次六次正确,分类器 2 有五次六次正确。因此,对于这个数据集,分类器 2 更好。在图 9.9 中,你可以看到这两个分类器及其准确率。注意,问题被重新措辞,以便它们有是或否的答案,这不会改变分类器或结果。

图 9.9 中,分类器 1 使用平台,分类器 2 使用年龄。为了在每个叶节点上进行预测,每个分类器都会选择该叶节点样本中最常见的标签。分类器 1 有四次是正确的,分类器 2 有五次是正确的。因此,基于准确率,分类器 2 表现更好。

吉尼不纯度指数:我的数据集有多多样?

基尼不纯度指数,或基尼指数,是我们比较平台和年龄分割的另一种方法。基尼指数是数据集中多样性的度量。换句话说,如果我们有一个所有元素都相似的集合,这个集合的基尼指数就低,如果所有元素都不同,它就有大的基尼指数。为了清晰起见,考虑以下两组 10 个彩色球(其中任何两个相同颜色的球都是不可区分的):

  • 集合 1:八个红色球,两个蓝色球

  • 集合 2:四个红色球,三个蓝色球,两个黄色球,一个绿色球

集合 1 看起来比集合 2 更纯净,因为集合 1 主要包含红色球和一些蓝色球,而集合 2 有许多不同的颜色。接下来,我们制定一个不纯度的度量标准,将低值分配给集合 1,将高值分配给集合 2。这个不纯度度量标准依赖于概率。考虑以下问题:

如果我们从集合中随机抽取两个元素,它们颜色不同的概率是多少?这两个元素不需要是不同的;我们允许重复抽取同一个元素。

对于集合 1,这个概率较低,因为集合中的球颜色相似。对于集合 2,这个概率较高,因为集合多样化,如果我们抽取两个球,它们很可能是不同颜色的。

不同颜色的。让我们计算这些概率。首先,注意根据互补概率定律(见第八章“数学刚刚发生了什么?”部分),我们抽取两个不同颜色球的概率是 1 减去我们抽取两个相同颜色球的概率:

P(抽取两个不同颜色的球) = 1 – P(抽取两个相同颜色的球)

现在让我们计算抽取两个相同颜色球的概率。考虑一个一般的集合,其中球有 n 种颜色。让我们称它们为颜色 1,颜色 2,一直到颜色 n。因为两个球必须属于这 n 种颜色之一,所以抽取两个相同颜色球的概率是抽取每种颜色两个球的概率之和:

P(抽取两个相同颜色的球) = P(两个球都是颜色 1) + P(两个球都是颜色 2) + … + P(两个球都是颜色 n)

我们在这里使用的是不相交概率的求和规则,该规则如下:

不相交概率的求和规则 如果两个事件 EF 是不相交的,即它们永远不会同时发生,那么其中一个发生(事件的并集)的概率是每个事件概率的和。换句话说,

P(EF) = P(E) + P(F)

现在,让我们计算两个球颜色相同的概率,对于每种颜色。注意,我们完全独立地抽取每个球。因此,根据独立概率的乘法法则(第八章的“数学发生了什么?”部分),两个球都是颜色 1 的概率是抽取一个球且它为颜色 1 的概率的平方。一般来说,如果 p[i] 是我们随机抽取一个球且它为颜色 i 的概率,那么

P(两个球都是颜色 i) = p[i]²。

将所有这些公式放在一起(图 9.10),我们得到

P(抽取两个不同颜色的球) = 1 – p[1]² – p[2]² – … – p[n]²。

这个最后的公式是集合的基尼指数。

图 9.10 基尼不纯度指数的计算总结

最后,随机抽取一个颜色为 i 的球的概率是颜色为 i 的球的数量除以球的总数。这导致了基尼指数的正式定义。

基尼不纯度指数 在一个包含 m 个元素和 n 个类别的集合中,其中 a[i] 个元素属于第 i 类,基尼不纯度指数是

Gini = 1 – p[1]² – p[2]² – … – p[n]²,

其中 p[i] = a[i] / m。这可以解释为如果我们从集合中随机抽取两个元素,它们属于不同类别的概率。

现在,我们可以计算我们两个集合的基尼指数。为了清晰起见,集合 1 的基尼指数计算在图 9.11 中展示(红色和蓝色被黑色和白色替代)。

集合 1:{红色,红色,红色,红色,红色,红色,红色,红色,蓝色,蓝色}(八个红色球,两个蓝色球)

集合 2:{红色,红色,红色,红色,蓝色,蓝色,蓝色,黄色,黄色,绿色}

注意,实际上集合 1 的基尼指数大于集合 2 的基尼指数。

图 9.11 具有八个黑色球和两个白色球的集合的基尼指数计算。注意,如果正方形的总面积为 1,则抽取两个黑色球的概率是 0.8²,抽取两个白色球的概率是 0.2²(这两个用阴影部分表示)。因此,抽取两个不同颜色球的概率是剩余的面积,即 1 – 0.8² – 0.2² = 0.32。这就是基尼指数。

我们如何使用基尼指数来决定两种数据分割方式(年龄或平台)哪种更好?显然,如果我们能将数据分割成两个更纯净的数据集,我们就进行了更好的分割。因此,让我们计算每个叶子的标签集合的基尼指数。查看图 9.12,以下是叶子的标签(我们用每个应用的名称的第一个字母来缩写每个应用):

分类器 1(按平台分类):

  • 左侧叶子(iPhone):

  • 右侧叶子(Android):

分类器 2(按年龄分类):

  • 左侧叶子(年轻人):

  • 右侧叶子(成年人):

集合 {A, C, C}, {A, A, B}, 和 {B, C, C} 的基尼指数都是相同的:。集合 {A, A, A} 的基尼指数为 。一般来说,纯集合的基尼指数总是 0。为了衡量分割的纯度,我们平均两个叶子的基尼指数。因此,我们得到以下计算:

分类器 1(按平台划分):

平均基尼指数 = 1/2(0.444+0.444) = 0.444

分类器 2(按年龄划分):

平均基尼指数 = 1/2(0.444+0) = 0.222

图 9.12 按平台和年龄划分数据集的两种分割方式及其基尼指数计算。注意,按年龄划分数据集给我们两个更小的数据集,它们的平均基尼指数更低。因此,我们选择按年龄划分数据集。

我们得出结论,第二次分割更好,因为它具有更低的平均基尼指数。

旁白:基尼不纯度指数不应与基尼系数混淆。基尼系数在统计学中用于计算国家的收入或财富不平等。在这本书中,每当提到基尼指数时,我们指的是基尼不纯度指数。

熵:另一种衡量多样性的度量,在信息理论中有着广泛的应用

在本节中,我们学习集合中另一种同质性的度量——其熵,它基于熵的物理概念,在概率和信息理论中非常重要。为了理解熵,我们来看一个稍微奇怪的概率问题。考虑与上一节相同的两组彩色球,但将颜色视为一个有序集合。

  • 集合 1:{红色,红色,红色,红色,红色,红色,红色,红色,蓝色,蓝色}(八个红色球,两个蓝色球)

  • 集合 2:{红色,红色,红色,红色,蓝色,蓝色,蓝色,黄色,黄色,绿色}(四个红色球,三个蓝色球,两个黄色球,一个绿色球)

现在,考虑以下场景:我们在一个袋子里有集合 1,我们开始从这个袋子中取出球,并且立即将我们刚刚取出的每个球放回袋子中。我们记录我们取出的球的颜色。如果我们这样做 10 次,想象一下我们得到以下序列:

  • 红色,红色,红色,蓝色,红色,蓝色,蓝色,红色,红色,红色

这里是定义熵的主要问题:

按照前一段描述的程序进行操作,我们得到定义集合 1 的确切序列 {红色,红色,红色,红色,红色,红色,红色,红色,蓝色,蓝色} 的概率是多少?

这个概率并不很大,因为我们必须非常幸运才能得到这个序列。让我们来计算它。我们有八个红色球和两个蓝色球,所以得到红色球的概率是 和得到蓝色球的概率是 。因为所有的抽取都是独立的,得到所需序列的概率是

这非常小,但你能想象集合 2 对应的概率吗?对于集合 2,我们是从一个包含四个红色球、三个蓝色球、两个黄色球和一个绿色球的袋子中取球,并希望获得以下序列:

  • 红色,红色,红色,红色,蓝色,蓝色,蓝色,黄色,黄色,绿色。

这几乎是不可能的,因为我们有很多颜色,每种颜色的球并不多。这个概率,以类似的方式计算,是

.

集合越多样化,我们通过一次取一个球的方式获取原始序列的可能性就越小。相比之下,最纯的集合,其中所有球的颜色都相同,这种方式很容易获得。例如,如果我们的原始集合有 10 个红色球,每次我们随机取一个球,球就是红色的。因此,获取序列 {红色,红色,红色,红色,红色,红色,红色,红色,红色,红色} 的概率是 1。

在大多数情况下,这些数字都非常小——而且这里只有 10 个元素。想象一下,如果我们的数据集有一百万个元素,我们将处理极其小的数字。当我们必须处理非常小的数字时,使用对数是最好的方法,因为它们提供了一种方便的方式来表示小数。例如,0.000000000000001 等于 10^(-15),所以它在 10 为底的对数是 -15,这是一个更容易处理的好数字。

熵的定义如下:我们从通过一次取一个元素的方式从我们的集合中恢复初始序列的概率开始,这些元素可以重复取。然后我们取对数,并除以集合中元素的总数。因为决策树处理二元决策,我们将使用以 2 为底的对数。我们取对数的负值是因为非常小的数字的对数都是负的,所以我们乘以 -1 来将其转换为正数。因为我们取了负值,所以集合越多样化,熵就越高。

现在我们可以计算两个集合的熵,并使用以下两个恒等式来展开它们:

  • log(ab) = log(a) + log(b)

  • log(a^c) = c log(a)

集合 1: {红色,红色,红色,红色,红色,红色,红色,红色,蓝色,蓝色}(八个红色球,两个蓝色球)

集合 2: {红色,红色,红色,红色,蓝色,蓝色,蓝色,黄色,黄色,绿色}

注意到集合 2 的熵大于集合 1 的熵,这意味着集合 2 比集合 1 更具多样性。以下是对熵的正式定义:

在一个包含 m 个元素和 n 个类别的集合中,其中 a[i] 个元素属于第 i- 个类别,熵是

我们可以使用熵来决定在两种分割数据的方式(平台或年龄)中哪一种更好,就像我们使用 Gini 指数时做的那样。经验法则是,如果我们能够将数据分割成两个具有更少组合熵的数据集,我们就已经进行了更好的分割。因此,让我们计算每个叶子标签集合的熵。再次查看图 9.12,以下是叶子的标签(我们用每个应用程序名称的第一个字母来缩写每个应用程序):

分类器 1(按平台):

左叶子:{A, C, C}

右叶子:{A, A, B}

分类器 2(按年龄):

左叶子:{A, A, A}

右叶子:{B, C, C}

集合 {A, C, C}, {A, A, B}, 和 {B, C, C} 的熵都相同:。集合 {A, A, A} 的熵为 。一般来说,所有元素都相同的集合的熵总是 0。为了测量分割的纯度,我们平均两个叶子标签集合的熵,如下所示(如图 9.13 所示):

分类器 1(按平台):

平均熵 = 1/2(0.918 + 0.918) = 0.918

分类器 2(按年龄):

平均熵 = 1/2(0.918+0) = 0.459

图 9.13 通过平台和年龄分割数据集的两种方式,以及它们的熵计算。注意,通过年龄分割数据集给我们带来了两个平均熵更低的小数据集。因此,我们再次选择通过年龄分割数据集。

因此,我们再次得出结论,第二个分割更好,因为它具有更低的平均熵。

熵是概率论和统计学中的一个极其重要的概念,因为它与信息论有很强的联系,这主要归功于克劳德·香农的工作。事实上,一个称为 信息增益 的重要概念正是熵的变化。要了解更多关于这个主题的信息,请参阅附录 C 中的视频和博客文章,它们更详细地介绍了这个主题。

不同大小的类别?没问题:我们可以取加权平均

在前面的章节中,我们学习了如何通过最小化平均 Gini 不纯度指数或熵来实现最佳分割。然而,想象一下,你有一个包含八个数据点的数据集(在训练决策树时,我们也将这些数据点称为样本),并将其分割成两个大小分别为六和二的数据集。正如你可能想象的那样,较大的数据集在 Gini 不纯度指数或熵的计算中应该占更大的比重。因此,我们考虑加权平均,在每一个叶子节点,我们分配对应叶子的点数比例。因此,在这种情况下,我们会将第一个 Gini 不纯度指数(或熵)的权重设为 6/8,第二个的权重为 2/8。图 9.14 展示了一个加权平均 Gini 不纯度指数和加权平均熵的示例。

图 9.14 将大小为八的数据集分割成两个大小分别为六和二的数据集。为了计算平均 Gini 指数和平均熵,我们将左侧数据集的指数按 6/8 加权,右侧数据集的指数按 2/8 加权。这导致加权 Gini 指数为 0.333,加权熵为 0.689。

现在我们已经学习了三种方法(准确率、Gini 指数和熵)来选择最佳分割,我们所需做的就是多次迭代此过程以构建决策树!这将在下一节中详细介绍。

建模的第二步:迭代

在上一节中,我们学习了如何使用一个特征以最佳方式分割数据。这是决策树训练过程中的主要内容。要完成构建我们的决策树,我们只剩下多次迭代这一步骤。在本节中,我们将学习如何做到这一点。

使用三种方法,准确率、Gini 指数和熵,我们决定使用“年龄”特征进行最佳分割。一旦我们进行这个分割,我们的数据集就被分割成两个数据集。这两个数据集的分割,包括它们的准确率、Gini 指数和熵,如图 9.15 所示。

图 9.15 当我们按年龄分割我们的数据集时,我们得到两个数据集。左侧的数据集有三个用户下载了原子计数,右侧的数据集有一个用户下载了蜂巢计数,两个用户下载了检查者计数。

注意左侧的数据集是纯的——所有标签都相同,其准确率为 100%,其 Gini 指数和熵都为 0。我们无法再对此数据集进行分割或改进分类。因此,此节点成为叶子节点,当我们到达该叶子时,我们返回预测“原子计数”。

右侧的数据集仍然可以分割,因为它有两个标签:“蜂巢计数”和“检查者计数”。我们已经使用了年龄特征,所以让我们尝试使用平台特征。结果我们发现我们很幸运,因为安卓用户下载了蜂巢计数,而两个 iPhone 用户下载了检查者计数。因此,我们可以使用平台特征来分割这个叶子,并获得图 9.16 中所示的决策节点。

图 9.16 我们可以使用平台来分割图 9.15 中的右叶子,从而获得两个纯数据集。每个数据集的准确率都是 100%,Gini 指数和熵都是 0。

在这次分割后,我们就完成了,因为我们无法进一步改进我们的分割。因此,我们获得了图 9.17 中的树。

图 9.17 结果决策树有两个节点和三个叶子。此树正确预测了原始数据集中的每个点。

这是我们过程的结束,我们已经构建了一个能够对整个数据集进行分类的决策树。我们几乎有了算法的所有伪代码,除了下一节中我们将看到的某些最终细节。

最后一步:何时停止构建树以及其他超参数

在上一节中,我们通过递归分割我们的数据集来构建决策树。每次分割都是通过选择最佳特征来进行的。这个特征是通过以下任何一种指标找到的:准确率、基尼指数或熵。我们完成分割,当数据集对应于每个叶节点的部分是纯的——换句话说,当它上面的所有样本都有相同的标签时。

在这个过程中可能会出现许多问题。例如,如果我们继续分割我们的数据时间过长,我们可能会陷入一个极端情况,即每个叶节点包含非常少的样本,这可能导致严重的过拟合。防止这种情况的方法是引入一个停止条件。这个条件可以是以下任何一种:

  1. 如果准确率、基尼指数或熵的变化低于某个阈值,则不要分割节点。

  2. 如果一个节点少于一定数量的样本,则不要分割该节点。

  3. 只有当两个结果叶节点都至少包含一定数量的样本时,才分割一个节点。

  4. 在达到一定深度后停止构建树。

所有这些停止条件都需要一个超参数。更具体地说,这些是前四个条件的超参数:

  1. 准确率(或基尼指数、熵)的最小变化量

  2. 一个节点必须具有的最小样本数才能进行分割

  3. 叶节点中允许的最小样本数

  4. 树的最大深度

我们选择这些超参数的方式要么是经验,要么是通过运行穷举搜索,在搜索中我们寻找不同超参数的组合,并选择在验证集上表现最好的一个。这个过程被称为网格搜索,我们将在第十三章的“调整超参数以找到最佳模型:网格搜索”部分更详细地研究它。

决策树算法:如何构建决策树并使用它进行预测

现在我们终于准备好陈述决策树算法的伪代码,这允许我们训练一个决策树来拟合数据集。

决策树算法的伪代码

输入:

  • 一个包含其相关标签的样本训练数据集

  • 用于分割数据的指标(准确率、基尼指数或熵)

  • 一个(或多个)停止条件

输出:

  • 一个适合数据集的决策树

程序:

  • 添加一个根节点,并将其与整个数据集关联。这个节点具有级别 0。称它为叶节点。

  • 重复直到每个叶节点都满足停止条件:

    • 选择最高层的一个叶节点。

    • 遍历所有特征,并选择一个根据所选指标以最佳方式分割该节点对应样本的特征。将该特征与节点关联。

    • 这个特征将数据集分割成两个分支。为每个分支创建两个新的叶节点,并将相应的样本与每个节点关联。

    • 如果停止条件允许分裂,则将节点转换为决策节点,并在其下方添加两个新的叶子节点。如果节点的级别是i,则两个新的叶子节点位于i + 1 级别。

    • 如果停止条件不允许分裂,则该节点变为叶子节点。为此叶子节点,关联其样本中最常见的标签。该标签就是叶子节点的预测。

返回:

  • 获得了决策树。

要使用此树进行预测,我们只需按照以下规则遍历它:

  • 向下遍历树。在每个节点,继续沿着由特征指示的方向前进。

  • 当到达叶子节点时,预测就是与该叶子节点关联的标签(在训练过程中与该叶子节点关联的样本中最常见的标签)。

这就是我们使用之前构建的应用推荐决策树进行预测的方式。当新用户到来时,我们检查他们的年龄和平台,并采取以下行动:

  • 如果用户是年轻人,那么我们推荐他们使用 Atom Count。

  • 如果用户是成年人,那么我们检查他们的平台。

    • 如果平台是 Android,那么我们推荐使用 Beehive Count。

    • 如果平台是 iPhone,那么我们推荐 Check Mate Mate。

除此之外,在训练决策树时,文献中包含诸如 Gini 增益和信息增益等术语。Gini 增益是指叶子节点的加权 Gini 不纯度指数与我们要分裂的决策节点的 Gini 不纯度指数(熵)之间的差异。类似地,信息增益是指叶子节点的加权熵与根节点的熵之间的差异。训练决策树最常见的做法是通过最大化 Gini 增益或信息增益。然而,在本章中,我们通过最小化加权 Gini 指数或加权熵来训练决策树。训练过程完全相同,因为决策节点的 Gini 不纯度指数(熵)在整个分裂该决策节点的过程中是恒定的。

超越了是/否这类问题

在“解决方案:构建应用推荐系统”这一节中,我们学习了如何构建一个决策树,该决策树适用于一个非常具体的案例,其中每个特征都是分类的二进制特征(意味着它只有两个类别,例如用户的平台)。然而,几乎相同的算法可以用来构建具有更多类别的分类特征决策树(例如狗/猫/鸟)以及具有数值特征(例如年龄或平均收入)的决策树。需要修改的主要步骤是我们分裂数据集的步骤,在本节中,我们将向您展示如何进行。

使用非二进制分类特征(如狗/猫/鸟)分割数据。

回想一下,当我们想要根据二进制特征分割数据集时,我们只需提出一个形式为“特征 X 吗?”的简单是或否问题。例如,当特征是平台时,一个问题可以是“用户是 iPhone 用户吗?”如果我们有一个具有超过两个类别的特征,我们只需提出几个问题。例如,如果输入是一个可能是狗、猫或鸟的动物,那么我们提出以下问题:

  • 这动物是狗吗?

  • 这动物是猫吗?

  • 这动物是鸟吗?

无论特征有多少个类别,我们都可以将其分割成几个二进制问题(图 9.18)。

图 9.18 当我们有一个非二进制特征时,例如有三个或更多可能类别的特征,我们将其转换为几个二进制(是或否)特征,每个类别一个。例如,如果特征是狗,对三个问题“它是狗吗?”、“它是猫吗?”和“它是鸟吗?”的回答分别是“是”、“否”和“否”。

每个问题都以不同的方式分割数据。为了确定三个问题中哪一个给我们提供了最佳的分割,我们使用与“建立模型的第一步”部分中相同的方法:准确率、基尼指数或熵。将非二进制分类特征转换为几个二进制特征的过程称为独热编码。在第十三章的“将分类数据转换为数值数据”部分中,我们看到它在真实数据集中被使用。

使用连续特征(如年龄)分割数据

回想一下,在我们简化数据集之前,“年龄”特征包含数字。让我们回到原始表格,并在那里构建一个决策树(表 9.3)。

表 9.3 我们的原始应用推荐数据集,包含平台和用户的(数值)年龄。这与表 9.1 相同。

平台 年龄 应用
iPhone 15 原子计数
iPhone 25 检查者
安卓 32 蜂巢寻找者
iPhone 35 检查者
安卓 12 原子计数
安卓 14 原子计数

这个想法是将年龄列转换为几个形式为“用户是否小于 X?”或“用户是否大于 X?”的问题。看起来我们似乎有无限多的问题要问,因为数字是无限的,但请注意,许多这些问题以相同的方式分割数据。例如,询问“用户是否小于 20?”和“用户是否小于 21?”会给我们相同的分割。实际上,只有七种可能的分割方式,如图 9.19 所示。

图 9.19 按年龄分割用户的七种可能方式的图形。注意,只要截止点位于连续年龄之间(除了第一个和最后一个截止点),放置截止点的位置无关紧要。

按照惯例,我们将选择连续年龄之间的中点作为分割的年龄。对于端点,我们可以选择任何随机值,只要它不在区间内。因此,我们有七个可能的问题将数据分割成两个集合,如表 9.4 所示。在此表中,我们还计算了每个分割的准确率、基尼不纯度指数和熵。

注意,第四个问题(“用户是否小于 20 岁?”)给出了最高的准确率,最低的加权基尼指数,以及最低的加权熵,因此这是使用“年龄”特征可以做出的最佳分割。

表 9.4:我们可以选择的七个可能的问题,每个问题都有相应的分割。在第一个集合中,我们放入小于截止年龄的用户,在第二个集合中,放入大于截止年龄的用户。

问题 第一组(是) 第二组(否) 标签 加权准确率 加权基尼不纯度指数 加权熵
用户是否小于 7 岁? empty 12, 14, 15, 25, 32, 35 {}, 3/6 0.611 1.459
用户是否小于 13 岁? 12 14, 15, 25, 32, 35 {A}, 3/6 0.533 1.268
用户是否小于 14.5 岁? 12, 14 15, 25, 32, 35 {A,A} 4/6 0.417 1.0
用户是否小于 20 岁? 12, 14, 15 25, 32, 35 {A,A,A}, 5/6 0.222 0.459
用户是否小于 28.5 岁? 12, 14, 15, 25 32, 35 {A,A,A,C}, 4/6 0.416 0.874
用户是否小于 33.5 岁? 12, 14, 15, 25, 32 35 {A,A,A,C,B}, 4/6 0.467 1.145
用户是否小于 100 岁? 12, 14, 15, 25, 32, 35 empty {A,A,A,C,B,C},{} 3/6 0.611 1/459

在表中进行计算,并验证你是否得到相同的答案。这些基尼指数的整个计算过程在以下笔记本中:github.com/luisguiserrano/manning/blob/master/Chapter_9_Decision_Trees/Gini_entropy_calculations.ipynb

为了清晰起见,让我们对第三个问题的准确率、加权基尼不纯度指数和加权熵进行计算。注意,这个问题将数据分割成以下两个集合:

  • 集合 1(小于 14.5 岁)

    • 年龄:12, 14

    • 标签:

  • 集合 2(14.5 岁及以上):

    • 年龄:15, 25, 32, 25

    • 标签:

准确率计算

集合 1 中最常见的标签是“A”,集合 2 中是“C”,因此我们将为每个相应的叶子节点做出这些预测。在集合 1 中,每个元素都被正确预测,而在集合 2 中,只有两个元素被正确预测。因此,这个决策树桩在六个数据点中的四个是正确的,准确率为 4/6 = 0.667。

在接下来的两个计算中,请注意以下内容:

  • 集合 1 是纯的(其所有标签都相同),因此其基尼不纯度指数和熵都是 0。

  • 在集合 2 中,标签为“A”、“B”和“C”的元素比例分别为 1/4、1/4 和 2/4 = 1/2。

加权 Gini 不纯度指数计算

集合 {A, A} 的 Gini 不纯度指数为 0。

集合 {A, C, B, C} 的 Gini 不纯度指数为

两个 Gini 不纯度指数的加权平均为

准确度计算

集合 {A, A} 的熵为 0。

集合 {A, C, B, C} 的熵为

两个熵的加权平均为

一个数值特征变成一系列是或否的问题,这些可以测量并与来自其他特征的另一些是或否的问题进行比较,以选择那个决策节点最佳的问题。

旁注:这个应用推荐模型非常小,所以我们完全可以手动完成。但是,要查看代码,请查看这个笔记本:github.com/luisguiserrano/manning/blob/master/Chapter_9_Decision_Trees/App_recommendations.ipynb。该笔记本使用 Scikit-Learn 包,我们将在“使用 Scikit-Learn 构建决策树”一节中详细介绍。

决策树的图形边界

在本节中,我将向您展示两件事:如何从几何角度(在二维空间中)构建决策树,以及如何在流行的机器学习包 Scikit-Learn 中编码决策树。

回想一下,在分类模型中,例如感知器(第五章)或逻辑分类器(第六章),我们绘制了将标签为 0 和 1 的点分开的模型边界,结果是一条直线。决策树的边界也很理想,当数据是二维的时,它是由垂直线和水平线的组合形成的。在本节中,我们通过一个例子来说明这一点。考虑图 9.20 中的数据集,其中标签为 1 的点为三角形,标签为 0 的点为正方形。水平和垂直轴分别称为 x[0] 和 x[1]。

图 9.20 一个包含两个特征 (x[0] 和 x[1]) 和两个标签(三角形和正方形)的数据集,我们将在这个数据集上训练决策树

如果您必须仅使用一条水平线或垂直线来分割这个数据集,您会选择哪条线?根据您用来衡量解决方案有效性的标准,可能会有不同的线。让我们先选择一条垂直线 x[0] = 5。这将使它右侧主要是三角形,左侧主要是正方形,除了两个错误分类的点,一个是正方形,一个是三角形(图 9.21)。尝试检查所有其他可能的垂直线和水平线,使用您最喜欢的指标(准确度、Gini 指数和熵)进行比较,并验证这是最佳分割点的线。

现在让我们分别看看每一半。这次,很容易看出在 x[1] = 8 和 x[1] = 2.5 的两条水平线将分别在左右两侧完成任务。这些线完全将数据集划分为正方形和三角形。图 9.22 展示了结果。

图 9.21 如果我们只能使用一条垂直线或水平线以最佳方式对数据进行分类,我们会使用哪一条?基于准确率,最佳分类器是 x[0] = 5 的垂直线,我们将它右侧的所有点分类为三角形,左侧的所有点分类为正方形。这个简单的分类器正确分类了 8 个点中的 10 个,准确率为 0.8。

图 9.22 图 9.21 中的分类器将我们留在了两个数据集,每个数据集位于垂直线的两侧。如果我们必须使用一条垂直线或水平线来对每个数据集进行分类,我们会选择哪一条?如图所示,最佳选择是在 x[1] = 8 和 x[1] = 2.5 的水平线。

我们在这里所做的是构建一个决策树。在每一个阶段,我们从两个特征(x[0] 和 x[1])中选取,并选择最佳分割数据的阈值。实际上,在下一小节中,我们使用 Scikit-Learn 在这个数据集上构建相同的决策树。

使用 Scikit-Learn 构建决策树

在本节中,我们学习如何使用一个流行的机器学习包 Scikit-Learn(简称 sklearn)来构建决策树。本节的代码如下:

我们首先将数据集作为名为 dataset 的 Pandas DataFrame 加载,如以下代码行所示:

import pandas as pd
dataset = pd.DataFrame({
    'x_0':[7,3,2,1,2,4,1,8,6,7,8,9],
    'x_1':[1,2,3,5,6,7,9,10,5,8,4,6],
    'y': [0,0,0,0,0,0,1,1,1,1,1,1]})

现在我们将特征与标签分开,如下所示:

features = dataset[['x_0', 'x_1']]
labels = dataset['y']

要构建决策树,我们创建一个 DecisionTreeClassifier 对象,并使用 fit 函数,如下所示:

decision_tree = DecisionTreeClassifier()
decision_tree.fit(features, labels)

我们使用 utils.py 文件中的 display_tree 函数获得了树的图,如图 9.23 所示。

图 9.23 深度为 2 的决策树,对应图 9.22 中的边界。它有三个节点和四个叶子。

注意,图 9.23 中的树与图 9.22 中的边界完全对应。根节点对应于第一个垂直线 x[0] = 5,线两侧的点对应两个分支。图左侧和右侧的 x[1] = 8.0 和 x[1] = 2.5 的两条水平线对应两个分支。此外,在每个节点我们都有以下信息:

  • 基尼指数:该节点标签的基尼不纯度指数

  • 样本:对应该节点的数据点(样本)数量

  • :该节点两个标签数据点的数量

如您所见,这个树是使用基尼指数训练的,这是 Scikit-Learn 的默认值。要使用熵来训练它,我们可以在构建DecisionTree对象时指定它,如下所示:

decision_tree = DecisionTreeClassifier(criterion='entropy')

在训练树时,我们可以指定更多的超参数,我们将在下一节中通过一个更大的示例看到。

真实应用:使用 Scikit-Learn 建模学生录取

在本节中,我们使用决策树来构建一个预测研究生院录取的模型。数据集可以在 Kaggle 上找到(参见附录 C 中的链接)。与“决策树的图形边界”部分一样,我们将使用 Scikit-Learn 来训练决策树,并使用 Pandas 来处理数据集。本节的代码如下:

数据集具有以下特征:

  • GRE 分数:一个 340 分以下的数字

  • TOEFL 分数:一个 120 分以下的数字

  • 大学排名:一个从 1 到 5 的数字

  • 目的陈述强度(SOP):一个从 1 到 5 的数字

  • 本科平均成绩点(CGPA):一个从 1 到 10 的数字

  • 推荐信强度(LOR):一个从 1 到 5 的数字

  • 研究经验:布尔变量(0 或 1)

数据集中的标签是录取概率,这是一个介于 0 和 1 之间的数字。为了得到二元标签,我们将录取概率为 0.75 或更高的每个学生视为“录取”,其他学生视为“未录取”。

加载数据集到 Pandas DataFrame 并执行此预处理步骤的代码如下:

import pandas as pd
data = pd.read_csv('Admission_Predict.csv', index_col=0)
data['Admitted'] = data['Chance of Admit'] >= 0.75
data = data.drop(['Chance of Admit'], axis=1)

结果数据集的前几行如表 9.5 所示。

表 9.5 一个包含 400 名学生及其标准化考试分数、成绩、大学排名、推荐信、目的陈述和其被录取到研究生院机会的信息的数据集

GRE 分数 TOEFL 分数 大学排名 SOP LOR CGPA 研究 录取
337 118 4 4.5 4.5 9.65 1 True
324 107 4 4.0 4.5 8.87 1 True
316 104 3 3.0 3.5 8.00 1 False
322 110 3 3.5 2.5 8.67 1 True
314 103 2 2.0 3.0 8.21 0 False

正如我们在“决策树的图形边界”部分所看到的,Scikit-Learn 要求我们分别输入特征和标签。我们将构建一个名为features的 Pandas DataFrame,包含除了“录取”列之外的所有列,以及一个名为labels的 Pandas Series,只包含“录取”列。代码如下:

features = data.drop(['Admitted'], axis=1)
labels = data['Admitted']

现在我们创建一个DecisionTreeClassifier对象(我们称之为dt)并使用fit方法。我们将使用 Gini 指数进行训练,如以下所示,因此不需要指定criterion超参数,但请继续使用熵进行训练,并将结果与这里得到的结果进行比较:

from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier()
dt.fit(features, labels)

要进行预测,我们可以使用predict函数。例如,以下是我们对前五个学生进行预测的方法:

dt.predict(features[0:5])
Output: array([ True, True, False, True, False])

然而,我们刚刚训练的决策树过度拟合了。一种观察这个现象的方法是使用score函数,并意识到它在训练集上的得分是 100%。在本章中,我们不会测试模型,而是尝试构建一个测试集并验证这个模型是否过度拟合。另一种观察过度拟合的方法是绘制树形图,并注意其深度为 10(见笔记本)。在下一节中,我们将了解一些有助于我们防止过度拟合的超参数。

在 Scikit-Learn 中设置超参数

为了防止过度拟合,我们可以使用我们在“最后一步:何时停止构建树和其他超参数”部分中学到的某些超参数,例如以下内容:

  • max_depth:允许的最大深度。

  • max_features:在每次分割时考虑的最大特征数(当特征太多且训练过程太长时很有用)。

  • min_impurity_decrease:节点的不纯度必须高于此阈值才能分割节点。

  • min_impurity_split:当节点的不纯度低于此阈值时,节点变为叶节点。

  • min_samples_leaf:叶节点所需的最小样本数。如果一个分割留下的叶节点样本数少于这个数量,则不执行分割。

  • min_samples_split:分割节点所需的最小样本数。

尝试调整这些参数以找到一个好的模型。我们将使用以下参数:

  • max_depth = 3

  • min_samples_leaf = 10

  • min_samples_split = 10

dt_smaller = DecisionTreeClassifier(max_depth=3, min_samples_leaf=10, min_samples_split=10)
dt_smaller.fit(features, labels)

结果树如图 9.24 所示。请注意,在这棵树中,所有指向右边的边对应于“False”,而指向左边的边对应于“True”。

图片

图 9.24 学生录取数据集中训练的深度为 3 的决策树

在每个叶节点给出的预测是该叶节点中大多数节点的标签。在笔记本中,每个节点都分配了一个颜色,从橙色到蓝色。橙色节点是具有更多标签 0 的节点,蓝色节点是具有标签 1 的节点。请注意,白色叶节点中标签 0 和 1 的点数相同。对于这个叶节点,任何预测的性能都相同。在这种情况下,Scikit-Learn 默认选择列表中的第一个类别,在这种情况下是 false。

要进行预测,我们使用predict函数。例如,让我们预测以下数字的学生的录取情况:

  • GRE 分数:320

  • TOEFL 分数:110

  • 大学评级:3

  • SOP:4.0

  • LOR:3.5

  • CGPA:8.9

  • 研究:0(无研究)

dt_smaller.predict([[320, 110, 3, 4.0, 3.5, 8.9, 0]])
Output: array([ True])

树预测该学生会获得录取。

从这个树中,我们可以推断出关于我们数据集的以下信息:

  • 最重要的特征是第六列(X[5]),对应于 CGPA,即成绩。截止分数是 10 分中的 8.735 分。事实上,根节点右侧的大多数预测都是“录取”,左侧的是“不录取”,这表明 CGPA 是一个非常强的特征。

  • 在此特征之后,最重要的两个特征是 GRE 分数(X[0])和托福分数(X[1]),都是标准化测试。事实上,在取得好成绩的学生中,大多数人很可能被录取,除非他们在 GRE 上表现不佳,如图 9.24 中从左数第六片叶子所说明的那样。

  • 除了成绩和标准化测试之外,树中出现的唯一其他特征是 SOP,即目的陈述的强度。它位于树的下方,并且没有太多地改变预测。

然而,请记住,树的构建本质上是贪婪的,即在每一个点上它都选择最顶端的特征。但这并不保证特征的选择是最佳的。例如,可能存在一个特征组合非常强大,但没有任何一个单独强大,树可能无法捕捉到这一点。因此,尽管我们得到了一些关于数据集的信息,但我们不应立即丢弃树中未出现的特征。在特征选择时,一个好的特征选择算法,如 L1 正则化,将非常有用。

回归决策树

在本章的大部分内容中,我们使用了决策树进行分类,但如前所述,决策树也是很好的回归模型。在本节中,我们将看到如何构建决策树回归模型。本节的代码如下:

考虑以下问题:我们有一个应用,我们想要预测用户每周使用应用的天数来衡量他们的互动水平。我们拥有的唯一特征是用户的年龄。数据集显示在表 9.6 中,其图表在图 9.25 中。

表 9.6 一个包含八个用户、他们的年龄以及他们与我们应用互动的小数据集。互动是通过他们在一周内打开应用的天数来衡量的。

年龄 互动
10 7
20 5
30 7
40 1
50 2
60 1
70 5
80 4

从这个数据集中,似乎我们有三个用户群体。年轻用户(10 岁、20 岁、30 岁)经常使用应用,中年用户(40 岁、50 岁、60 岁)不太使用,而老年用户(70 岁、80 岁)有时使用。因此,这样的预测是有意义的:

  • 如果用户年龄为 34 岁或以下,每周的参与度为 6 天。

  • 如果用户年龄在 35 岁到 64 岁之间,每周的参与度为 1 天。

  • 如果用户年龄为 65 岁或以上,每周的参与度为 3.5 天。

图 9.25

图 9.25 表 9.6 中数据集的图表,其中水平轴对应用户的年龄,垂直轴对应他们每周与该应用的互动天数

回归决策树的预测看起来类似于这个,因为决策树将我们的用户分成组,并为每个组预测一个固定值。分割用户的方式是通过使用特征,这与我们在分类问题中所做的方式完全一样。

幸运的是,用于训练回归决策树的算法与我们用于训练分类决策树的算法非常相似。唯一的区别是,对于分类树,我们使用了准确率、基尼指数或熵,而对于回归树,我们使用平均平方误差(MSE)。平均平方误差可能听起来很熟悉——我们在第三章的“我们如何衡量我们的结果?误差函数”部分使用了它来训练线性回归模型。

在我们深入算法之前,让我们从概念上思考一下。想象一下,你必须将一条线尽可能紧密地拟合到图 9.25 中的数据集。但是有一个限制——这条线必须是水平的。我们应该在哪里拟合这条水平线呢?将其拟合在数据集的“中间”是有意义的——换句话说,在标签的平均高度,即 4。这是一个非常简单的分类模型,它将相同的预测值 4 分配给每个点。

现在,让我们更进一步。如果我们必须使用两个水平段,我们应该如何将它们尽可能紧密地拟合到数据上?我们可能会有几个猜测,其中一个是将 35 岁左边的点设置一个较高的栏,而将 35 岁右边的点设置一个较低的栏。这代表了一个决策树桩,它提出了问题:“你小于 35 岁吗?”并根据用户如何回答这个问题来分配预测。

如果我们能够将这两个水平段各自再分成两个——我们应该将它们定位在哪里呢?我们可以继续遵循这个过程,直到我们将用户分解成几个标签非常相似的组。然后,我们预测该组中所有用户的平均标签。

我们将用户分解成几个标签非常相似的组。然后,我们预测该组中所有用户的平均标签。

我们刚才遵循的过程是训练回归决策树的过程。现在让我们更正式一些。回想一下,当一个特征是数值时,我们考虑所有可能的分割方式。因此,分割年龄特征的可能是使用以下截止值:15,25,35,45,55,65 和 75。每个这些截止值都给我们两个较小的数据集,我们称之为左数据集和右数据集。现在我们执行以下步骤:

  1. 对于每个较小的数据集,我们预测标签的平均值。

  2. 我们计算预测的平均平方误差。

  3. 我们选择给出最小平方误差的截止点。

例如,如果我们的截止点是 65 岁,那么两个数据集如下所示:

  • 左侧数据集:65 岁以下的用户。标签为{7, 5, 7, 1, 2, 1}。

  • 右侧数据集:65 岁或以上的用户。标签为{5,4}。

对于每个数据集,我们预测标签的平均值,左侧为 3.833,右侧为 4.5。因此,前六个用户的预测值为 3.833,最后两个为 4.5。现在,我们按照以下方式计算 MSE:

在表 9.7 中,我们可以看到每个可能的截止点所得到的值。完整的计算在笔记本本节的末尾。

表 9.7 使用截止点按年龄分割数据集的九种可能方式。每个截止点将数据集分割成两个更小的数据集,对于这两个数据集,预测值由标签的平均值给出。均方误差(MSE)是通过计算标签和预测值之间差异的平方的平均值来计算的。请注意,具有最小 MSE 的分割是在 35 岁的截止点获得的。这为我们决策树中的根节点提供了信息。

截止点 左侧标签集 右侧标签集 左侧预测集 右侧预测集 MSE
0 {} None 4.0 5.25
15 7.0 3.571 3.964
25 6.0 3.333 3.917
35 6.333 2.6 1.983
45 5.0 3.0 4.25
55 4.4 3.333 4.983
65 3.833 4.5 5.167
75 4.0 4.0 5.25
100 {} 4.0 none 5.25

最佳截止点为 35 岁,因为它给出了最小的均方误差预测。因此,我们在回归决策树中建立了第一个决策节点。接下来的步骤是以相同的方式递归地继续分割左侧和右侧数据集。我们不会手动进行,而是像之前一样使用 Scikit-Learn。

首先,我们定义我们的特征和标签。我们可以使用数组来完成这项工作,如下所示:

features = [[10],[20],[30],[40],[50],[60],[70],[80]]
labels = [7,5,7,1,2,1,5,4]

现在,我们使用DecisionTreeRegressor对象构建一个最大深度为 2 的回归决策树,如下所示:

from sklearn.tree import DecisionTreeRegressor
dt_regressor = DecisionTreeRegressor(max_depth=2)
dt_regressor.fit(features, labels)

得到的决策树如图 9.26 所示。第一个截止点为 35 岁,正如我们之前已经计算出的。接下来的两个截止点为 15 岁和 65 岁。在图 9.26 的右侧,我们还可以看到这些四个数据子集的预测值。

图 9.26 左侧:Scikit-Learn 中得到的决策树。此树有三个决策节点和四个叶子节点。右侧:此决策树做出的预测图。注意,截止年龄为 35 岁、15 岁和 65 岁,对应树中的决策节点。预测值为 7、6、1.33 和 4.5,对应树中的叶子节点。

应用

决策树在现实生活中有许多有用的应用。决策树的一个特殊之处在于,除了预测之外,它们还为我们提供了大量关于数据的信息,因为它们以分层结构组织数据。很多时候,这些信息的价值与预测能力相当,甚至更高。在本节中,我们将看到决策树在以下领域的实际应用示例:

  • 医疗保健

  • 推荐系统

决策树在医疗保健领域被广泛使用

决策树在医学领域被广泛使用,不仅用于预测,还用于识别预测中的决定性特征。你可以想象,在医学领域,一个只说“患者生病”或“患者健康”的黑盒是不够的。然而,决策树提供了大量关于预测原因的信息。患者可能因为症状、家族病史、习惯或许多其他因素而生病。

决策树在推荐系统中非常有用

在推荐系统中,决策树同样非常有用。最著名的推荐系统问题之一,Netflix 奖项,就是借助决策树获得的。2006 年,Netflix 举办了一场竞赛,旨在构建最佳推荐系统以预测用户对电影的评分。2009 年,他们向获奖者颁发了 100 万美元美元奖金,获奖者通过改进 Netflix 算法使其提升了超过 10%。他们这样做的方式是使用梯度提升决策树来结合超过 500 个不同的模型。其他推荐引擎也使用决策树来研究用户的参与度,并找出最佳确定参与度的人口统计特征。

在第十二章中,我们将学习更多关于梯度提升决策树和随机森林的知识。目前,想象它们最好的方式是作为许多决策树共同工作以做出最佳预测的集合。

摘要

  • 决策树是重要的机器学习模型,用于分类和回归。

  • 决策树的工作方式是通过询问关于数据的数据的二进制问题,并根据这些问题的答案做出预测。

  • 构建分类决策树的算法包括找到最佳决定标签的特征,并迭代这一步骤。

  • 我们有几种方法可以判断一个特征是否最佳地决定了标签。在本章中我们学习到的三种方法是准确率、基尼不纯度指数和熵。

  • 基尼不纯度指数衡量集合的纯度。因此,每个元素都有相同标签的集合具有基尼不纯度指数为 0。每个元素都有不同标签的集合具有接近 1 的基尼不纯度指数。

  • 熵是衡量集合纯度的另一种度量。所有元素都具有相同标签的集合熵为 0。一半元素具有一个标签,另一半具有另一个标签的集合熵为 1。在构建决策树时,分割前后的熵差称为信息增益。

  • 构建回归决策树的算法与用于分类的算法类似。唯一的区别是我们使用均方误差来选择最佳特征以分割数据。

  • 在二维空间中,回归树图看起来像是几条水平线的并集,其中每条水平线是对特定叶节点中元素的预测。

  • 决策树的应用范围非常广泛,从推荐算法到医学和生物学的应用。

练习

练习 9.1

在以下垃圾邮件检测决策树模型中,确定来自您母亲的电子邮件,主题为“请去商店,有促销活动”,是否会被分类为垃圾邮件。

练习 9.2

我们的目标是构建一个决策树模型,以确定信用卡交易是否为欺诈。我们使用以下信用卡交易数据集,具有以下特征:

  • :交易的值。

  • 已批准供应商:信用卡公司有一份已批准供应商的名单。此变量表示供应商是否在该名单中。

已批准供应商 欺诈
交易编号 1 $100 未批准
交易编号 2 $100 已批准
交易编号 3 $10,000 已批准
交易编号 4 $10,000 未批准
交易编号 5 $5,000 已批准
交易编号 6 $100 已批准

根据以下规格构建决策树的第一节点:

  1. 使用基尼不纯度指数

  2. 使用熵

练习 9.3

以下是一个 COVID-19 检测结果为阳性或阴性的患者数据集。他们的症状是咳嗽 (C)、发烧 (F)、呼吸困难 (B) 和疲劳 (T)。

咳嗽 (C) 发烧 (F) 呼吸困难 (B) 疲劳 (T) 诊断
患者编号 1 X X X 病人
患者编号 2 X X X 病人
患者编号 3 X X X 病人
患者编号 4 X X X 病人
患者编号 5 X X 健康
患者编号 6 X X 健康
患者编号 7 X 健康
患者编号 8 X 健康

使用准确率,构建一个高度为 1 的决策树(决策树桩),对数据进行分类。这个分类器在数据集上的准确率是多少?

10 结合构建块以获得更多力量:神经网络

在本章

  • 什么是神经网络

  • 神经网络的架构:节点、层、深度和激活函数

  • 使用反向传播训练神经网络

  • 神经网络训练中可能遇到的问题,例如梯度消失问题和过拟合

  • 提高神经网络训练的技术,例如正则化和 dropout

  • 使用 Keras 训练用于情感分析和图像分类的神经网络

  • 将神经网络用作回归模型

图片

在本章,我们学习神经网络,也称为多层感知器。神经网络是(如果不是最受欢迎的)最流行的机器学习模型之一。它们非常有用,以至于该领域有自己独特的名称:深度学习。深度学习在机器学习的最前沿领域有众多应用,包括图像识别、自然语言处理、医学和自动驾驶汽车。从广义上讲,神经网络旨在模仿人脑的工作方式。它们可能非常复杂,如图 10.1 所示。

图片

图 10.1 一个神经网络。它可能看起来很复杂,但在接下来的几页中,我们将揭开这个图像的神秘面纱。

图 10.1 中的神经网络可能因为有很多节点、边等看起来令人畏惧。然而,我们可以用更简单的方式理解神经网络。一种看待它们的方式是将它们视为感知器(我们在第五章和第六章中学过)的集合。我喜欢将神经网络看作是线性分类器的组合,这些分类器产生了非线性分类器。在低维度中,线性分类器看起来像是线或平面,而非线性分类器看起来像是复杂的曲线或表面。在本章中,我们讨论神经网络背后的直觉以及它们如何工作的细节,我们还编写神经网络代码并使用它们进行图像识别等几个应用。

神经网络在分类和回归中很有用。在本章中,我们主要关注分类神经网络,但也学习了使它们适用于回归所需的小改动。首先,一些术语。回想一下,在第五章中,我们学习了感知器,在第六章中,我们学习了逻辑分类器。我们还了解到它们被称为离散和连续感知器。为了刷新你的记忆,离散感知器的输出是 0 或 1,而连续感知器的输出是(0,1)区间内的任何数字。为了计算这个输出,离散感知器使用步函数(第五章中的“步函数和激活函数”部分),而连续感知器使用 sigmoid 函数(第六章中的“一种概率方法进行分类:sigmoid 函数”部分)。在本章中,我们将这两个分类器都称为感知器,并在需要时,我们会指明我们是在谈论离散感知器还是连续感知器。

本章的代码可在以下 GitHub 仓库中找到:github.com/luisguiserrano/manning/tree/master/Chapter_10_Neural_Networks

神经网络示例:一个更复杂的外星世界

在本节中,我们通过第五章和第六章中熟悉的情感分析示例来学习神经网络。场景如下:我们发现自己在一个由外星人居住的遥远星球上。他们似乎说一种由两个单词组成的语言,aackbeep,我们想要构建一个机器学习模型,帮助我们根据他们所说的单词来判断外星人是否快乐或悲伤。这被称为情感分析,因为我们需要构建一个模型来分析外星人的情绪。我们记录了一些外星人的谈话,并设法通过其他方式确定他们是否快乐或悲伤,并据此构建了表 10.1 所示的数据集。

表 10.1 我们的数据集,其中每一行代表一个外星人。第一列代表他们所说的句子。第二列和第三列代表句子中每个单词出现的次数。第四列代表外星人的情绪。

句子 Aack Beep 情绪
Aack 1 0 悲伤
Aack aack 2 0 悲伤
Beep 0 1 悲伤
Beep beep 0 2 悲伤
Aack beep 1 1 快乐
Aack aack beep 2 1 快乐
Beep aack beep 1 2 快乐
Beep aack beep aack 2 2 快乐

这个数据集看起来相当不错,我们应该能够将分类器拟合到这些数据上。让我们先绘制它,如图 10.2 所示。

图 10.2 表 10.1 中数据集的绘图。水平轴对应于单词aack出现的次数,垂直轴对应于单词beep出现的次数。快乐的面孔对应于快乐的异形,悲伤的面孔对应于悲伤的异形。

从图 10.2 来看,我们似乎无法将线性分类器拟合到这些数据上。换句话说,画一条将快乐和悲伤的面孔分开的线是不可能的。我们能做什么?我们已经学习了其他可以完成这项工作的分类器,例如朴素贝叶斯分类器(第八章)或决策树(第九章)。但在这个章节中,我们坚持使用感知器。如果我们的目标是分离图 10.2 中的点,并且一条直线做不到,那么什么比一条直线更好?以下是什么?

  1. 两条直线

  2. 一条曲线

这些是神经网络的例子。让我们首先看看为什么第一个,使用两条直线的分类器,是一个神经网络。

解决方案:如果一条直线不够,可以使用两条直线来分类你的数据集

在本节中,我们探讨一个使用两条直线来分割数据集的分类器。我们有多种方法可以画两条直线来分割这个数据集,其中一种方法如图 10.3 所示。让我们称它们为直线 1 和直线 2。

不等式图

图 10.3 在我们的数据集中,快乐和悲伤的点不能由一条直线分开。然而,画两条直线可以很好地将它们分开——位于两条直线之上的点可以分类为快乐,其余的点为悲伤。以这种方式结合线性分类器是神经网络的基础。

我们可以定义我们的分类器如下:

情感分析分类器

如果一个句子对应点位于图 10.3 中所示的两条直线之上,则将其分类为快乐。如果它至少低于一条直线,则将其分类为悲伤。

现在,让我们加入一些数学。我们能想到这两条直线的两个方程吗?许多方程都可以工作,但让我们使用以下两个(其中x[a]是句子中单词aack出现的次数,x[b]是单词beep出现的次数)。

  • 第 1 条直线:6x[a] + 10x[b] – 15 = 0

  • 第 2 条直线:10x[a] + 6x[b] – 15 = 0

aside: 我们是如何找到这些方程的?注意,第 1 行通过点(0, 1.5)和(2.5, 0)。因此,斜率,定义为水平轴的变化除以垂直轴的变化,恰好是方程图y轴截距——即直线与垂直轴相交的高度是 1.5。因此,这条直线的方程是方程图。通过操作这个方程,我们得到 6x[a] + 10x[b] – 15 = 0。我们可以用类似的方法找到第 2 条直线的方程。

因此,我们的分类器变成了以下形式:

情感分析分类器

如果以下两个不等式都成立,则句子被分类为快乐:

  • 不等式 1:6x[a] + 10x[b] – 15 ≥ 0

  • 不等式 2:10x[a] + 6x[b] 15 ≥ 0

如果其中至少有一个失败,那么句子就被分类为悲伤。

作为一致性检查,表 10.2 包含了两个方程式各自的值。在每个方程式的右侧,我们检查方程式的值是否大于或等于 0。最右侧的列检查两个值是否都大于或等于 0。

表 10.2 与表 10.1 相同的 dataset,但有一些新的列。第四列和第六列对应于我们的两条线。第五列和第七列检查每条线在每个数据点上的方程式是否给出非负值。最后一列检查得到的两个值是否都是非负的。

句子 阿克 方程式 1 方程式 1 ≥ 0? 方程式 2 方程式 2 ≥ 0? 两个方程式 ≥ 0
阿克 1 0 –9 –5
阿克 阿克 2 0 –3 5
0 1 –5 –9
哔哔 0 2 5 3
阿克 哔 1 1 1 1
阿克 阿克 哔 1 2 11 7
哔哔 阿克 哔 2 1 7 11
哔哔哔 2 2 17 17

注意,表 10.2(是/否)最右侧的列与表 10.1(快乐/悲伤)最右侧的列相匹配。这意味着分类器成功地将所有数据正确分类。

为什么是两条线?幸福不是线性的吗?

在第五章和第六章中,我们根据分类器的方程式推断出了关于语言的一些信息。例如,如果单词aack的权重是正的,我们得出结论,它很可能是一个快乐的词。那么现在呢?我们能否从这个包含两个方程式的分类器中推断出关于语言的信息?

我们可以这样考虑两个方程式,也许在外星人的星球上,幸福不是一个简单的线性事物,而是基于两件事。在现实生活中,幸福可以基于许多事情:它可以是基于拥有充实的事业和快乐的家庭生活以及桌上的食物。它也可以是基于拥有咖啡和甜甜圈。在这种情况下,让我们假设幸福的两个方面是事业和家庭。对于一个外星人来说,要快乐,他需要拥有两者

结果表明,在这种情况下,职业幸福和家庭幸福都是简单的线性分类器,每个分类器都由两条线中的一条描述。让我们假设第 1 行对应职业幸福,第 2 行对应家庭幸福。因此,我们可以将外星人的幸福视为图 10.4 中的图表。在这个图表中,职业幸福和家庭幸福通过 AND 运算符连接,该运算符检查两者是否都为真。如果它们都是真的,那么外星人就是快乐的。如果其中任何一个失败,外星人就不快乐。

图 10.4 幸福分类器由职业幸福分类器、家庭幸福分类器和 AND 运算符组成。如果职业和家庭幸福分类器都输出 Yes,那么幸福分类器也输出 Yes。如果其中任何一个输出 No,那么幸福分类器也输出 No。

家庭和职业幸福分类器都是感知器,因为它们由一条线的方程给出。我们能将这个 AND 运算符转换成另一个感知器吗?答案是肯定的,我们将在下一小节中看到如何做到这一点。

图 10.4 开始看起来像神经网络。只需再走几步,再加上一点数学,我们就能得到本章开头图 10.1 中的样子。

将感知器的输出组合到另一个感知器中

图 10.4 暗示了一种感知器的组合,其中我们将两个感知器的输出作为第三个感知器的输入。这就是神经网络是如何构建的,在本节中,我们将看到其背后的数学原理。

在第五章的“阶跃函数和激活函数”部分,我们定义了阶跃函数,当输入为负时返回 0,当输入为正或零时返回 1。请注意,因为我们使用了阶跃函数,所以这些是离散感知器。使用这个函数,我们可以将家庭和职业幸福分类器定义为以下:

职业幸福分类器

权重:

  • 嘟嘟声:6

  • 哔哔声:10

偏差:-15

句子得分:6x[a] + 10x[b] – 15

预测F = step(6x[a] + 10x[b] – 15)

家庭幸福分类器

权重:

  • 嘟嘟声:10

  • 哔哔声:6

偏差:-15

句子得分:10x[a]+6x[b] – 15

预测C = step(10x[a]+6x[b] – 15)

下一步是将职业和家庭幸福分类器的输出连接到一个新的幸福分类器。尝试验证以下分类器是否工作。图 10.5 包含两个表格,分别展示了职业和家庭分类器的输出,以及一个包含前两列是职业和家庭分类器的输入和输出,最后一列是家庭分类器输出的第三个表格。图 10.5 中的每个表格对应一个感知器。

图 10.5 包含三个感知器分类器,一个用于职业幸福,一个用于家庭幸福,还有一个将前两个结合起来的幸福分类器。职业和家庭感知器的输出是幸福感知器的输入。

幸福分类器

权重:

  • 职业:1

  • 家庭:1

偏差:-1.5

句子得分:1 · C + 1 · F – 1.5

预测ŷ = step(1 · C + 1 · F – 1.5)

这种分类器的组合构成了一个神经网络。接下来,我们将看到如何使其看起来像图 10.1 中的图像。

感知器的图形表示

在本节中,我将向您展示如何以图形方式表示感知器,这导致了神经网络图形表示的产生。我们称它们为神经网络,因为它们的基本单元,即感知器,大致类似于神经元。

一个神经元由三个主要部分组成:细胞体、树突和轴突。从广义上讲,神经元通过树突接收来自其他神经元的信号,在细胞体中处理它们,并通过轴突发送信号以被其他神经元接收。这与感知器形成对比,感知器接收数字作为输入,并应用数学

对它们进行的操作(通常由一个与激活函数组合的和组成),并输出一个新的数字。这个过程如图 10.6 所示。

图 10.6 一个感知器松散地基于神经元。左图:一个神经元及其主要组成部分:树突、细胞体和轴突。信号通过树突进入,在细胞体中处理,并通过轴突发送到其他神经元。右图:感知器。左边的节点对应于数值输入,中间的节点执行数学运算并输出一个数字。

更正式地说,回想一下第五章和第六章中感知器的定义,其中我们有了以下实体:

  • 输入x[1],x[2],…,x[n]

  • 权重w[1],w[2],…,w[n]

  • 偏置b

  • 激活函数:对于离散感知器是阶跃函数,对于连续感知器是 Sigmoid 函数。(在本章的后面我们将学习其他新的激活函数。)

  • 预测:由公式 ŷ = f(w[1] x[1] + w[2] x[2] + … + w[n] x[n] + b) 定义,其中 f 是相应的激活函数

这些在图中的位置如图 10.7 所示。在左侧,我们有输入节点,在右侧,我们有输出节点。输入变量放在输入节点上。最后的输入节点不包含变量,但它包含值为 1。权重位于连接输入节点和输出节点的边路上。对应于最终输入节点的权重是偏置。计算预测的数学运算发生在输出节点内部,并且该节点输出预测。

例如,由方程 ŷ = σ(3x[1] – 2x[2] + 4x[3] + 2) 定义的感知器如图 10.7 所示。注意,在这个感知器中,以下步骤被执行:

  • 输入与相应的权重相乘并相加,得到 3x[1] – 2x[2] + 4x[3]。

  • 将偏置添加到前面的方程中,得到 3x[1] – 2x[2] + 4x[3] + 2。

  • 将 Sigmoid 激活函数应用于得到输出 ŷ = σ(3x[1] – 2x[2] + 4x[3] + 2)。

图 10.7 感知器的视觉表示。输入(特征和偏置)作为左边的节点出现,权重和偏置位于连接输入节点到中间主节点的边上。中间的节点对权重和输入进行线性组合,加上偏置,并应用激活函数,在这种情况下是 Sigmoid 函数。输出是公式 ŷ = σ(3x[1] – 2x[2] + 4x[3] + 2) 给出的预测。

例如,如果这个感知器的输入是点 (x[1], x[2], x[3]) = (1, 3, 1),那么输出是 σ(3 · 1 – 2 · 3 + 4 · 1 + 2) = σ(3) = 0.953。

如果这个感知器是用阶跃函数而不是 Sigmoid 函数定义的,输出将是 step(3 · 1 – 2 · 3 + 4 · 1 + 2) = step(3) = 1。

这种图形表示使得感知器容易串联,正如我们在下一节中看到的。

神经网络的图形表示

正如我们在上一节中看到的,神经网络是感知器的串联。这种结构旨在松散地模拟人脑,其中几个神经元的输出成为另一个神经元的输入。同样,在神经网络中,几个感知器的输出成为另一个感知器的输入,如图 10.8 所示。

图片

图 10.8 神经网络旨在(松散地)模拟大脑的结构。左:神经元在大脑内部以某种方式连接,使得一个神经元的输出成为另一个神经元的输入。右:感知器以某种方式连接,使得一个感知器的输出成为另一个感知器的输入。

我们在上一节中构建的神经网络,其中我们将职业感知器和家庭感知器与幸福感知器串联起来,如图 10.9 所示。

注意到图 10.9 中的图中,职业和家庭感知器的输入被重复了。一种更清晰的方式来表达,其中这些输入不会重复,如图 10.10 所示。

注意到这三个感知器使用了阶跃函数。我们这样做只是为了教育目的,因为在现实生活中,神经网络永远不会使用阶跃函数作为激活函数,因为这会使我们无法使用梯度下降(更多内容请参阅“训练神经网络”部分)。然而,Sigmoid 函数在神经网络中却被广泛使用,在“不同的激活函数”部分,我们将学习一些实际中使用的其他有用的激活函数。

图片

图 10.9 当我们将职业和家庭感知器的输出连接到幸福感知器时,我们得到一个神经网络。这个神经网络使用阶跃函数作为激活函数。

图片

图 10.10 图 10.9 中图表的清理版本。在这个图表中,特征 x[a] 和 x[b],以及偏置没有重复。相反,它们各自连接到右侧的两个节点,很好地将三个感知器组合到同一个图表中。

神经网络的边界

在第五章和第六章中,我们研究了感知器的边界,这些边界由线给出。在本节中,我们看看神经网络边界是什么样的。

从第五章和第六章回顾,离散感知器和连续感知器(逻辑分类器)都有一个由定义它们的线性方程给出的线性边界。离散感知器根据点位于线的哪一侧将预测 0 和 1 分配给点。连续感知器将 0 到 1 之间的预测分配给平面上的每个点。线上的点得到 0.5 的预测,线一侧的点得到高于 0.5 的预测,而线另一侧的点得到低于 0.5 的预测。图 10.11 说明了对应于方程 10x[a] + 6x[b] – 15 = 0 的离散和连续感知器。

图片

图 10.11 感知器的边界是一条线。左:对于离散感知器,线一侧的点被预测为 0,另一侧的点被预测为 1。右:对于连续感知器,所有点都被预测在区间(0,1)内。在这个例子中,最左边的点得到的预测接近 0,最右边的点得到的预测接近 1,而在线上的点得到的预测为 0.5。

我们也可以以类似的方式可视化神经网络的输出。回想一下,具有步进激活函数的神经网络的输出如下:

  • 如果 6x[a] + 10x[b] – 15 ≥ 0 且 10x[a] + 6x[b] – 15 ≥ 0,则输出为 1。

  • 否则,输出为 0。

在图 10.12 的左侧,使用两条线来表示这个边界。请注意,它是由两个输入感知器边界和偏置节点的组合表示的。使用步进激活函数得到的边界用虚线表示,而使用 sigmoid 激活函数得到的边界是一条曲线。

要更仔细地研究这些边界,请查看以下笔记本:github.com/luisguiserrano/manning/blob/master/Chapter_10_Neural_Networks/Plotting_Boundaries.ipynb。在这个笔记本中,使用步进和 sigmoid 激活函数绘制了两条线和两个神经网络的边界,如图 10.13 所示。

图片

图 10.12 构建神经网络时,我们使用两个感知器和一个偏置节点(用一个总是输出值为 1 的分类器表示)的输出连接到第三个感知器。结果分类器的边界是输入分类器边界的组合。在左侧,我们看到使用阶跃函数得到的边界,这是一条折线。在右侧,我们看到使用 sigmoid 函数得到的边界,这是一条曲线。

图片

图 10.13 分类器边界的图示。顶部:两个线性分类器,职业(左侧)和家庭(右侧)分类器。底部:两个神经网络,使用阶跃函数(左侧)和 sigmoid 函数(右侧)。

注意,具有 sigmoid 激活函数的神经网络实际上并不很好地拟合整个数据集,因为它错误地将点(1,1)分类了,如图 10.13 的右下角所示。尝试以某种方式改变权重,使其很好地拟合这个点。(参见本章末尾的练习 10.3)。

完全连接神经网络的通用架构

在前面的章节中,我们看到了一个小型神经网络的例子,但在现实生活中,神经网络要大得多。节点按层排列,如图 10.14 所示。第一层是输入层,最后一层是输出层,所有介于两者之间的层都称为隐藏层。节点和层的排列称为神经网络的架构。层的数量(不包括输入层)称为神经网络的深度。图 10.14 中的神经网络深度为 3,其架构如下:

  • 一个大小为 4 的输入层

  • 一个大小为 5 的隐藏层

  • 一个大小为 3 的隐藏层

  • 一个大小为 1 的输出层

图片

图 10.14 神经网络的一般架构。节点被划分为层,其中最左侧的层是输入层,最右侧的层是输出层,所有介于两者之间的层都是隐藏层。同一层中的所有节点都连接到下一层中的所有(非偏置)节点。

神经网络通常绘制时不包含偏置节点,但假设它们是架构的一部分。然而,我们不计入架构中的偏置节点。换句话说,层的尺寸是该层中非偏置节点的数量。

注意到在图 10.14 中的神经网络中,同一层中的每个节点都连接到下一层中的每个(非偏置)节点。此外,非连续层之间没有连接。这种架构称为全连接。对于某些应用,我们使用不同的架构,其中并非所有连接都存在,或者某些节点在非连续层之间连接——请参阅“更复杂方言的其他架构”部分,了解其中的一些内容。然而,在本章中,我们构建的所有神经网络都是全连接的。

将神经网络边界想象成图 10.15 中所示的那样。在这个图中,你可以看到每个节点对应的分类器。注意,第一隐藏层由线性分类器组成,每个后续层的分类器比前一个层的稍微复杂一些。

图片

图 10.15 我喜欢这样可视化神经网络。每个节点都对应一个分类器,这个分类器有一个明确的边界。第一隐藏层中的节点都对应线性分类器(感知器),因此它们被绘制成线条。每个层的节点边界是由前一层边界组合而成的。因此,每个隐藏层的边界变得越来越复杂。在这个图中,我们移除了偏差节点。

一个很好的工具来玩以理解神经网络的是 TensorFlow Playground,可以在playground.tensorflow.org找到。那里有几个图形数据集可用,你可以用不同的架构和超参数训练神经网络。

训练神经网络

在本章中,我们看到了神经网络的一般外观,以及它们并不像听起来那么神秘。我们如何训练这样的怪物呢?从理论上讲,这个过程并不复杂,尽管它可能计算成本很高。我们有几个技巧和启发式方法可以使用来加速这个过程。在本节中,我们学习这个训练过程。训练神经网络与其他模型(如感知器或逻辑分类器)的训练并没有太大区别。我们首先随机初始化所有权重和偏差。接下来,我们定义一个错误函数来衡量神经网络的性能。最后,我们反复使用错误函数来调整模型的权重和偏差,以减少错误函数。

错误函数:衡量神经网络性能的一种方式

在本节中,我们学习用于训练神经网络的错误函数。幸运的是,我们之前已经见过这个函数——第六章“逻辑分类器”部分中的对数损失函数。回忆一下,对数损失的公式是

对数损失 = –y ln(ŷ) – (1 – y) ln(1 – ŷ),

其中 y 是标签,ŷ 是预测。

作为复习,使用对数损失函数进行分类问题的一个很好的原因是,当预测和标签接近时,它返回一个较小的值,而当它们相距较远时,它返回一个较大的值。

反向传播:训练神经网络的关键步骤

在本节中,我们学习训练神经网络过程中的最重要步骤。回忆一下,在第三章、第五章和第六章(线性回归、感知器算法和逻辑回归)中,我们使用梯度下降来训练我们的模型。对于神经网络也是如此。训练算法被称为反向传播算法,其伪代码如下:

反向传播算法的伪代码

  • 使用随机权重和偏置初始化神经网络。

  • 重复多次:

    • 计算损失函数及其梯度(即相对于每个权重和偏置的导数)。

    • 沿着梯度相反的方向迈一小步,以减少损失函数的小量。

  • 你获得的权重对应于一个(很可能)很好地拟合数据的神经网络。

神经网络的损失函数很复杂,因为它涉及到预测的对数,而预测本身是一个复杂的函数。此外,我们需要计算相对于许多变量的导数,对应于神经网络的每个权重和偏置。在附录 B“使用梯度下降训练神经网络”中,我们回顾了具有一个隐藏层或任意大小的神经网络的反向传播算法的数学细节。在附录 C 中,可以看到一些推荐资源,以深入了解深层神经网络的反向传播数学。在实践中,像 Keras、TensorFlow 和 PyTorch 这样的优秀包已经以极高的速度和性能实现了此算法。

回想一下,当我们学习线性回归模型(第三章)、离散感知器(第五章)和连续感知器(第六章)时,这个过程总是有一个步骤,我们需要移动一条线以更好地模拟我们的数据。这种类型的几何图形对于神经网络来说更难可视化,因为它发生在更高的维度中。然而,我们仍然可以形成反向传播的心理图景,为此,我们需要专注于神经网络的一个节点和一个数据点。想象一下图 10.16 右边的分类器。这个分类器是从左边的三个分类器得到的(底部的一个对应于偏置,我们用总是返回预测为 1 的分类器来表示)。结果分类器错误地分类了点,如图所示。从三个输入分类器中,第一个很好地分类了点,但其他两个没有。因此,反向传播步骤将增加对应于顶部分类器的边的权重,并减少对应于底部两个分类器的权重。这确保了结果分类器将更像顶部的一个,因此,它对点的分类将得到改善。

图片

图 10.16 反向传播的心理图景。在训练过程的每一步,边的权重都会更新。如果一个分类器很好,它的权重会增加一小部分,如果它不好,它的权重会减少。

可能的问题:从过拟合到梯度消失

在实践中,神经网络工作得非常好。但是,由于它们的复杂性,许多问题出现在它们的训练过程中。幸运的是,我们可以为最紧迫的问题提供一个解决方案。一个问题

神经网络存在的问题是过拟合——非常大的架构可能会潜在地记住我们的数据,而没有很好地泛化。在下一节中,我们将看到一些在训练神经网络时减少过拟合的技术。

神经网络可能还会遇到另一个严重的问题,那就是梯度消失。请注意,sigmoid 函数在两端非常平坦,这表明其导数(曲线的切线)过于平坦(见图 10.17)。这意味着它们的斜率非常接近于零。

图片

图 10.17 sigmoid 函数在两端平坦,这意味着对于大的正负值,其导数非常小,这阻碍了训练。

在反向传播过程中,我们组合了许多 sigmoid 函数(这意味着我们反复将 sigmoid 函数的输出作为另一个 sigmoid 函数的输入)。正如预期的那样,这种组合导致导数非常接近于零,这意味着反向传播中采取的步骤非常小。如果这种情况发生,我们可能需要很长时间才能得到一个好的分类器,这是一个问题。

我们有几个解决梯度消失问题的方案,到目前为止,其中最有效的一个是改变激活函数。在“不同的激活函数”这一节中,我们学习了一些新的激活函数,以帮助我们处理梯度消失问题。

训练神经网络的技巧:正则化和 dropout

如前所述,神经网络容易过拟合。在本节中,我们讨论了一些在神经网络训练过程中减少过拟合量的技术。

我们如何选择正确的架构?这是一个困难的问题,没有具体的答案。通常的做法是选择一个比我们可能需要的更大的架构,然后应用技术来减少网络可能有的过拟合量。在某种程度上,这就像挑选一条裤子,你唯一的选择要么太小要么太大。如果我们选择太小的裤子,我们几乎无能为力。另一方面,如果我们选择太大的裤子,我们可以用皮带调整使其更合身。这并不理想,但这是我们目前唯一的选择。根据数据集选择正确的架构是一个复杂的问题,目前在这个方向上正在进行大量研究。想了解更多,请参阅附录 C 中的资源。

正则化:通过惩罚更高的权重来减少过拟合的方法

如我们在第四章所学,我们可以使用 L1 和 L2 正则化来减少回归和分类模型中的过拟合,神经网络也不例外。在神经网络中应用正则化的方式与在线性回归中应用的方式相同——通过向误差函数中添加一个正则化项。如果我们进行 L1 正则化,正则化项等于正则化参数(λ)乘以我们模型中所有权重(不包括偏置)的绝对值之和。如果我们进行 L2 正则化,那么我们取平方和而不是绝对值。例如,在“具有示例的神经网络”部分中的示例神经网络,其 L2 正则化误差为

对数损失 + λ · (6² + 10² + 10² + 6² + 1² + 1²) = 对数损失 + 274λ。

Dropout:确保少数强节点不会主导训练

Dropout 是一种用于减少神经网络过拟合的有趣技术,为了理解它,让我们考虑以下类比:想象我们习惯用右手,喜欢去健身房。过了一段时间,我们开始注意到我们的右二头肌长得很多,但左边的却没有。然后我们开始更加关注我们的训练,并意识到因为我们习惯用右手,所以我们总是用右手拿起杠铃,没有让左手得到足够的锻炼。我们决定不能再这样下去了,于是采取了一个激进的措施。有些日子我们决定把右手绑在背后,强迫自己整个训练过程不用右手。这样之后,我们开始看到左臂开始按照预期增长。现在,为了让两只手臂都得到锻炼,我们这样做:每天去健身房之前,我们抛两个硬币,一个代表每只手臂。如果左边的硬币正面朝上,我们就把左臂绑在背后,如果右臂的硬币正面朝上,我们就把右臂绑在背后。有些日子我们会用两只手臂工作,有些日子只用一只,有些日子则不用(可能是腿部训练日)。硬币的随机性将确保,平均来看,我们几乎同样地锻炼两只手臂。

Dropout 使用这个逻辑,除了使用臂之外,我们在神经网络中训练权重。当一个神经网络有太多节点时,一些节点会捕捉到数据中的模式,这些模式对于做出良好的预测是有用的,而其他节点则会捕捉到嘈杂或不相关的模式。Dropout 过程在每个 epoch 随机移除一些节点,并对剩余的节点执行一次梯度下降步骤。通过在每个 epoch 移除一些节点,我们可能会移除那些已经捕捉到有用模式的节点,从而迫使其他节点承担更多的责任。

更具体地说,dropout 过程为每个神经元附加一个小的概率p。在训练过程的每个 epoch 中,每个神经元以概率p被移除,神经网络只训练剩余的神经元。Dropout 仅在隐藏层中使用,而不是在输入或输出层中。dropout 过程在图 10.18 中说明,其中在每个训练的四个 epoch 中移除了一些神经元。

Dropout 过程如图 10.18 所示。在不同的 epoch 中,我们随机选择节点从训练中移除,以给所有节点一个更新其权重的机会,避免少数单个节点主导训练。

Dropout 在实践中有很大的成功,我鼓励你在每次训练神经网络时都使用它。我们用于训练神经网络的包使其易于使用,正如我们将在本章后面看到的那样。

图片

图 10.18 dropout 过程。在不同的 epoch 中,我们随机选择节点从训练中移除,以给所有节点一个更新其权重的机会,避免少数单个节点主导训练。

不同的激活函数:双曲正切(tanh)和修正线性单元(ReLU)

正如我们在“潜在问题”部分看到的那样,sigmoid 函数有点太平坦,这会导致梯度消失问题。解决这个问题的一个方法是使用不同的激活函数。在本节中,我们介绍了两个对改进我们的训练过程至关重要的不同激活函数:双曲正切(tanh)和修正线性单元(ReLU)

双曲正切(tanh)

由于其形状,双曲正切函数在实践中往往比 sigmoid 函数表现更好,其公式如下:

图片

Tanh 比 sigmoid 稍微平坦一些,但形状仍然相似,如图 10.19 所示。它对 sigmoid 有所改进,但仍然存在梯度消失问题。

图片

图 10.19 神经网络中使用的三种不同的激活函数。左:sigmoid 函数,用希腊字母sigma表示。中:双曲正切,或 tanh。右:修正线性单元,或 ReLU。

修正线性单元(ReLU)

在神经网络中常用的一种更受欢迎的激活函数是修正线性单元,或 ReLU。这个很简单:如果输入是负数,输出是零;否则,输出等于输入。换句话说,它对非负数保持不变,并将所有负数转换为零。对于x ≥ 0,ReLU(x) = x,对于x < 0,ReLU(x) = 0。ReLU 是解决梯度消失问题的良好解决方案,因为当输入为正时,其导数为 1,因此,它在大型神经网络中得到了广泛的应用。

这些激活函数的伟大之处在于我们可以在同一个神经网络中组合不同的激活函数。在最常见的架构之一中,每个节点都使用 ReLU 激活函数,除了最后一个节点,它使用 sigmoid。在最后使用 sigmoid 的原因是,如果我们的问题是分类问题,神经网络的输出必须在 0 到 1 之间。

具有多个输出的神经网络:softmax 函数

到目前为止,我们使用的神经网络只有一个输出。然而,使用我们在第六章“将数据分类到多个类别:softmax 函数”部分学到的 softmax 函数构建一个产生多个输出的神经网络并不困难。softmax 函数是 sigmoid 的多变量扩展,我们可以用它将分数转换为概率。

最好的方式是通过一个例子来说明 softmax 函数。想象一下,我们有一个神经网络,其任务是确定图像中是否包含河马、鸟、猫或狗。在最后一层,我们有四个节点,每个节点对应一种动物。我们不是将 sigmoid 函数应用于来自前一层的分数,而是将 softmax 函数应用于所有这些分数。例如,如果分数是 0、3、1 和 1,softmax 返回以下结果:

这些结果表明神经网络强烈相信该图像对应于一只鸟。

超参数

与大多数机器学习算法一样,神经网络使用许多超参数,我们可以微调这些超参数以使它们工作得更好。这些超参数决定了我们的训练方式,即我们希望这个过程持续多长时间,以什么速度进行,以及我们如何选择将数据输入到模型中。神经网络中最重要的几个超参数如下:

  • 学习率 η:我们在训练过程中使用的步长大小

  • 训练轮数:我们用于训练的步数

  • 批量 vs. 小批量 vs. 随机梯度下降:一次进入训练过程的数据点数量——也就是说,我们是逐个输入点,批量输入,还是同时全部输入?

  • 架构

    • 神经网络中的层数

    • 每层的节点数

    • 每个节点使用的激活函数

  • **正则化参数****:

    • L1 或 L2 正则化

    • 正则化项 λ

  • Dropout 概率 p

我们以与其他算法相同的方式调整这些超参数,使用诸如网格搜索等方法。在第十三章中,我们通过一个实际例子更详细地阐述了这些方法。

在 Keras 中编码神经网络

现在我们已经学习了神经网络背后的理论,是时候将它们付诸实践了!已经编写了许多用于神经网络的优秀软件包,例如 Keras、TensorFlow 和 PyTorch。这三个都非常强大,在本章中,我们将使用 Keras,因为它简单易用。我们将为两个不同的数据集构建两个神经网络。第一个数据集包含具有两个特征和标签 0 和 1 的点。该数据集是二维的,因此我们将能够查看模型创建的非线性边界。第二个数据集是图像识别中常用的数据集,称为 MNIST(修改后的国家标准与技术研究院)数据集。MNIST 数据集包含我们可以使用神经网络进行分类的手写数字。

二维空间中的图形示例

在本节中,我们将在图 10.20 所示的数据集上使用 Keras 训练一个神经网络。该数据集包含两个标签,0 和 1。标签为 0 的点被绘制为正方形,而标签为 1 的点被绘制为三角形。请注意,标签为 1 的点主要位于中心,而标签为 0 的点位于边缘。对于这种类型的数据集,我们需要一个具有非线性边界的分类器,这使得它成为神经网络的一个很好的例子。本节的代码如下:

图 10.20 神经网络非常适合非线性可分集。为了测试这一点,我们将在这个圆形数据集上训练一个神经网络。

在我们训练模型之前,让我们查看我们数据中的某些随机行。输入将被称为 x,具有特征 x_1 和 x_2,输出将被称为 y。表 10.3 包含一些样本数据点。该数据集有 110 行。

表 10.3 包含 110 行、两个特征和标签 0 和 1 的数据集

x_1 x_2 y
–0.759416 2.753240 0
–1.885278 1.629527 0
... ... ...
0.729767 –2.479655 1
–1.715920 –0.393404 1

在我们构建和训练神经网络之前,我们必须进行一些数据预处理。

对我们的数据进行分类:将非二进制特征转换为二进制

在这个数据集中,输出是一个介于 0 到 1 之间的数字,但它代表两个类别。在 Keras 中,建议对这种类型的输出进行分类。这仅仅意味着标签为 0 的点现在将具有标签[1,0],而标签为 1 的点现在将具有标签[0,1]。我们使用以下 to_categorical 函数来完成这项操作:

from tensorflow.keras.utils import to_categorical
categorized_y = np.array(to_categorical(y, 2))

新的标签称为 categorized_y

神经网络的架构

在本节中,我们构建了此数据集的神经网络架构。决定使用哪种架构并不是一门精确的科学,但通常建议比实际的小一点更好。对于这个数据集,我们将使用以下架构,包含两个隐藏层(图 10.21):

  • 输入层

    • 大小:2
  • 第一个隐藏层

    • 大小:128

    • 激活函数:ReLU

  • 第二个隐藏层

    • 大小:64

    • 激活函数:ReLU

  • 输出层

    • 大小:2

    • 激活函数:softmax

图 10.21 我们将用于分类数据集的架构。它包含两个隐藏层:一个有 128 个节点,另一个有 64 个节点。它们之间的激活函数是 ReLU,最终的激活函数是 softmax。

此外,我们将在隐藏层之间添加 dropout 层,dropout 概率为 0.2,以防止过拟合。

在 Keras 中构建模型

在 Keras 中构建神经网络只需要几行代码。首先,我们按照以下方式导入必要的包和函数:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Activation

现在,让我们定义模型,其架构与我们之前小节中定义的架构相同。首先,我们使用以下行定义模型:

model = Sequential()                                       ❶
model.add(Dense(128, activation='relu', input_shape=(2,))) ❷
model.add(Dropout(.2))                                     ❸
model.add(Dense(64, activation='relu'))                    ❹
model.add(Dropout(.2))
model.add(Dense(2, activation='softmax'))                  ❺

❶ 定义模型

❷ 添加第一个隐藏层,并使用 ReLU 激活函数

❸ 添加一个概率为 0.2 的 dropout

❹ 添加第二个隐藏层,并使用 ReLU 激活函数

❺ 添加输出层,并使用 softmax 激活函数

一旦定义了模型,我们就可以编译它,如下所示:

model.compile(loss = 'categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

compile函数中的参数如下:

  • loss = 'categorical_crossentropy':这是损失函数,我们将其定义为对数损失。因为我们的标签有多列,我们需要使用对数损失函数的多变量版本,称为分类交叉熵。

  • optimizer = 'adam':像 Keras 这样的包有很多内置技巧可以帮助我们以最佳方式训练模型。添加优化器到我们的训练中总是一个好主意。其中一些最好的是 Adam、SGD、RMSProp 和 AdaGrad。尝试使用其他优化器进行相同的训练,看看它们的性能如何。

  • metrics = ['accuracy']:随着训练的进行,我们会在每个 epoch 得到模型表现的报告。此标志允许我们定义在训练期间想要看到的指标,在这个例子中,我们选择了准确率。

当我们运行代码时,我们会得到架构和参数数量的摘要,如下所示:

Model: "sequential"
_________________________________________________________________
Layer (type)         Output Shape                Param #
=================================================================
dense (Dense)        (None, 128)                 384
_________________________________________________________________
dropout (Dropout)    (None, 128)                 0
_________________________________________________________________
dense_1 (Dense)      (None, 64)                  8256
_________________________________________________________________
dropout_1 (Dropout)  (None, 64)                  0
_________________________________________________________________
dense_2 (Dense)      (None, 2)                   130
=================================================================
Total params: 8,770
Trainable params: 8,770
Non-trainable params: 0
_________________________________________________________________

前一个输出中的每一行代表一个层(为了描述目的,dropout 层被视为单独的层)。列对应于层的类型、形状(节点数)和参数数量,这正好是权重数加上偏置数。此模型总共有 8,770 个可训练参数。

训练模型

对于训练,只需要一行简单的代码,如下所示:

model.fit(x, categorized_y, epochs=100, batch_size=10)

让我们检查 fit 函数的每个输入。

  • x and categorized_y:分别是特征和标签。

  • epochs:我们在整个数据集上运行反向传播的次数。在这里,我们做了 100 次。

  • batch_size:我们用于训练模型的批次的长度。在这里,我们以 10 个批次向模型介绍我们的数据。对于如此小的数据集,我们不需要分批输入,但在这个例子中,我们这样做是为了展示。

随着模型的训练,它会在每个时代输出一些信息,即损失(误差函数)和准确率。为了对比,注意第一个时代损失高、准确率低,而最后一个时代在这两个指标上都有更好的结果:

Epoch 1/100
11/11 [==============================] - 0s 2ms/step - loss: 0.5473 - accuracy: 0.7182
...
Epoch 100/100
11/11 [==============================] - 0s 2ms/step - loss: 0.2110 - accuracy: 0.9000

模型在训练集上的最终准确率为 0.9。这很好,尽管请记住,准确率必须在测试集中计算。这里我不会做,但尝试将数据集分成训练集和测试集,并重新训练这个神经网络,看看你获得什么测试准确率。图 10.22 显示了神经网络边界的图。

图片

图 10.22 我们训练的神经网络分类器的边界。注意它正确地分类了大多数点,但有少数例外。

注意,模型成功地将数据分类得相当好,将三角形包围起来,将正方形留在外面。由于数据噪声,它犯了一些错误,但这没关系。有缺陷的边界暗示了轻微的过拟合,但总的来说,这似乎是一个不错的模型。

训练用于图像识别的神经网络

在本节中,我们学习如何训练用于图像识别的神经网络。我们使用的数据集是 MNIST,这是一个流行的图像识别数据集,包含从 0 到 9 的 70000 个手写数字。每张图像的标签是对应的数字。每个灰度图像都是一个 28x28 的数字矩阵,数值介于 0 到 255 之间,其中 0 代表白色,255 代表黑色,介于两者之间的任何数值代表灰色。本节的代码如下:

加载数据

此数据集在 Keras 中预先加载,因此很容易将其加载到 NumPy 数组中。实际上,它已经被分成了大小为 60,000 和 10,000 的训练集和测试集。以下代码行将它们加载到 NumPy 数组中:

from tensorflow import keras
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

在图 10.23 中,你可以看到数据集中的前五张图像及其标签。

图片

图 10.23 MNIST 中一些手写数字的示例及其标签

数据预处理

神经网络接收向量作为输入而不是矩阵,因此我们必须将每个 28x28 的图像转换成长度为 28²=784 的长向量。我们可以使用reshape函数来完成此操作,如下所示:

x_train_reshaped = x_train.reshape(-1, 28*28)
x_test_reshaped = x_test.reshape(-1, 28*28)

与前面的例子一样,我们也必须对标签进行分类。因为标签是一个介于 0 到 9 之间的数字,我们必须将其转换成一个长度为 10 的向量,其中对应标签的项是一个 1,其余都是 0。我们可以通过以下代码行来完成这个操作:

y_train_cat = to_categorical(y_train, 10)
y_test_cat = to_categorical(y_test, 10)

这个过程在图 10.24 中得到了说明。

图片

图 10.24 在训练神经网络之前,我们以以下方式预处理图像和标签。我们将矩形图像通过连接行塑造成一个长向量。然后我们将每个标签转换成一个长度为 10 的向量,其中对应标签的位置只有一个非零项。

构建和训练模型

我们可以使用与前面模型相同的架构,只是稍作修改,因为现在的输入大小是 784。在下面的代码行中,我们定义了模型及其架构:

model = Sequential()
model.add(Dense(128, activation='relu', input_shape=(28*28,)))
model.add(Dropout(.2))
model.add(Dense(64, activation='relu'))
model.add(Dropout(.2))
model.add(Dense(10, activation='softmax'))

现在我们以 10 个批次的规模编译并训练模型 10 个周期,如图所示。这个模型有 109,386 个可训练参数,所以在你的电脑上训练 10 个周期可能需要几分钟。

model.compile(loss = 'categorical_crossentropy', optimizer='adam', metrics=['accuracy'])
model.fit(x_train_reshaped, y_train_cat, epochs=10, batch_size=10)

观察输出,我们可以看到模型的训练准确率为 0.9164,这很好,但让我们评估测试准确率以确保模型没有过拟合。

评估模型

我们可以通过在测试数据集上进行预测并将它们与标签进行比较来评估测试集的准确率。神经网络输出长度为 10 的向量,其中包含它分配给每个标签的概率,因此我们可以通过查看这个向量中最大值的项来获得预测,如下所示:

predictions_vector = model.predict(x_test_reshaped)
predictions = [np.argmax(pred) for pred in predictions_vector]

当我们将这些与标签进行比较时,我们得到了 0.942 的测试准确率,这相当不错。我们可以通过更复杂的架构,如卷积神经网络(更多内容将在下一节中介绍),做得更好,但知道即使是一个小型全连接神经网络,我们也能在图像识别问题上做得相当好。

现在我们来看一些预测结果。在图 10.25 中,我们可以看到一个正确的预测(左)和一个错误的预测(右)。注意,错误的预测是一个写得很差的数字 3 的图像,它也有一点像数字 8。

图片

图 10.25 左:一个被神经网络正确分类的 4 的图像。右:一个被错误分类为 8 的 3 的图像。

通过这个练习,我们可以看到,使用 Keras 的几行代码训练这样一个大型神经网络的过程非常简单!当然,这里还有很多其他可以做的事情。玩转这个笔记本,向神经网络添加更多层,改变超参数,看看你能将这个模型的测试准确率提高到多高!

用于回归的神经网络

在本章中,我们看到了如何使用神经网络作为分类模型,但神经网络作为回归模型同样有用。幸运的是,我们只需要对分类神经网络进行两项小的调整,就可以得到一个回归神经网络。第一项调整是从神经网络中移除最终的 sigmoid 函数。这个函数的作用是将输入转换为 0 到 1 之间的数字,因此如果我们移除它,神经网络将能够返回任何数字。第二项调整是将误差函数更改为绝对误差或均方误差,因为这些是回归相关的误差函数。其他所有内容都将保持不变,包括训练过程。

函数的作用是将输入转换为 0 到 1 之间的数字,因此如果我们移除它,神经网络将能够返回任何数字。第二项调整是将误差函数更改为绝对误差或均方误差,因为这些是回归相关的误差函数。其他所有内容都将保持不变,包括训练过程。

例如,让我们看看第 10.7 节“感知器的图形表示”中的图 10.7 中的感知器。这个感知器做出预测 ŷ = σ(3x[1] – 2x[2] + 4x[3] + 2)。如果我们移除 sigmoid 激活函数,新的感知器将做出预测 ŷ = 3x[1] – 2x[2] + 4x[3] + 2。这个感知器在图 10.26 中展示。注意,这个感知器代表了一个线性回归模型。

图 10.26 如果我们从感知器中移除激活函数,我们将分类模型转换为线性回归模型。线性回归模型可以预测任何数值,而不仅仅是 0 到 1 之间的一个数值。

为了说明这个过程,我们在 Keras 上使用一个熟悉的数据集:海得拉巴的房价数据集进行神经网络训练。回想一下,在第三章的“实际应用:使用 Turi Create 预测印度房价”部分,我们训练了一个线性回归模型来拟合这个数据集。这一部分的代码如下:

加载数据集和将数据集拆分为特征和标签的详细信息可以在笔记本中找到。我们将使用的神经网络架构如下:

  • 输入层大小为 38(数据集的列数)

  • 一个大小为 128 的隐藏层,带有 ReLU 激活函数和 0.2 的 dropout 参数

  • 一个大小为 64 的隐藏层,带有 ReLU 激活函数和 0.2 的 dropout 参数

  • 输出层大小为 1,没有激活函数

model = Sequential()
model.add(Dense(38, activation='relu', input_shape=(38,)))
model.add(Dropout(.2))
model.add(Dense(128, activation='relu'))
model.add(Dropout(.2))
model.add(Dense(64, activation='relu'))
model.add(Dropout(.2))
model.add(Dense(1))

为了训练神经网络,我们使用均方误差函数和 Adam 优化器。我们将使用 10 个批次的 10 个 epoch 进行训练,如下所示:

model.compile(loss = 'mean_squared_error', optimizer='adam')
model.fit(features, labels, epochs=10, batch_size=10)

这个神经网络在训练数据集上报告了 553,542.5 的均方根误差。通过添加测试集并调整架构来进一步研究这个模型,看看你能将其改进多少!

更复杂数据集的其他架构

神经网络在许多应用中都非常有用,可能比目前任何其他机器学习算法都要有用。神经网络最重要的特性之一是其多功能性。我们可以以非常有趣的方式修改架构,以更好地适应我们的数据和解决问题。要了解更多关于这些架构的信息,请参阅安德鲁·特拉斯特(Andrew Trask)的《Grokking Deep Learning》(Manning, 2019)以及附录 C 中或serrano.academy/neural-networks/提供的视频集。

神经网络如何“看”:卷积神经网络(CNN)

正如我们在本章中学到的,神经网络在图像处理方面非常出色,我们可以在许多应用中使用它们,如下所示:

  • 图像识别:输入是一个图像,输出是图像上的标签。以下是一些用于图像识别的著名数据集:

    • MNIST:28-by-28 灰度图像中的手写数字

    • CIFAR-10:彩色图像,有 10 个标签,如飞机、汽车等,在 32-by-32 的图像中

    • CIFAR-100:类似于 CIFAR-10,但有 100 个标签,如水生哺乳动物、花卉等

  • 语义分割:输入是一个图像,输出不仅包括图像中找到的物体的标签,还包括它们在图像中的位置。通常,神经网络将这个位置输出为图像中的一个边界矩形。

在“训练用于图像识别的神经网络”这一节中,我们构建了一个小型、全连接的神经网络,它对 MNIST 数据集的分类效果相当不错。然而,对于更复杂的图像,如图片和面部,这样的神经网络表现不会很好,因为将图像转换为长向量会丢失很多信息。对于这些复杂的图像,我们需要不同的架构,这就是卷积神经网络帮助我们的地方。

关于神经网络的详细信息,请参阅附录 C 中的资源,但以下是他们工作的大致概述。想象一下,我们有一个大图像需要处理。我们取一个较小的窗口,比如 5-by-5 或 7-by-7 像素,然后将其在大型图像上滑动。每次通过时,我们应用一个称为卷积的公式。因此,我们最终得到一个稍微小一点的过滤图像,它在某种程度上总结了上一个图像——这是一个卷积层。卷积神经网络由几个这样的卷积层和一些全连接层组成。

当涉及到复杂的图像时,我们通常不会从头开始训练神经网络。一种有用的技术称为迁移学习,它包括从一个预训练的网络开始,并使用我们的数据来调整其一些参数(通常是最后一层)。这种技术通常效果很好,且计算成本较低。InceptionV3、ImageNet、ResNet 和 VGG 等网络都是由拥有强大计算能力的公司和研究机构训练的,因此我们强烈推荐使用它们。

神经网络如何说话:循环神经网络(RNN)、门控循环单元(GRU)和长短期记忆网络(LSTM)

神经网络最迷人的应用之一是当我们能让它们与我们交谈或理解我们所说的话时。这涉及到倾听我们说的话或阅读我们写的内容,分析它,并能够做出回应或行动。计算机理解和处理语言的能力被称为自然语言处理。神经网络在自然语言处理方面取得了许多成功。本章开头提到的情感分析示例就是自然语言处理的一部分,因为它涉及到理解句子并确定它们是否具有积极或消极的情感。正如你可以想象的那样,还存在许多更前沿的应用,例如以下内容:

  • 机器翻译:将句子从一种语言翻译成另一种语言。

  • 语音识别:解码人类语音并将其转换为文本。

  • 文本摘要:将大量文本总结成几段。

  • 聊天机器人:一种可以与人类交谈并回答问题的系统。这些系统尚未完善,但有用的聊天机器人通常在特定主题下运行,例如客户支持。

对于处理文本来说,最有效的架构是循环神经网络,以及它们的更高级版本,称为长短期记忆网络(LSTM)和门控循环单元(GRU)。为了理解它们是什么,想象一个神经网络,其中输出被作为输入的一部分重新连接到网络中。这样,神经网络就有了记忆,当训练得当,这种记忆可以帮助它们理解文本中的主题。

神经网络如何绘制画作:生成对抗网络(GAN)

神经网络最迷人的应用之一是生成。到目前为止,神经网络(以及本书中大多数其他机器学习模型)在预测机器学习方面表现良好,即能够回答诸如“那是什么价格?”或“这是猫还是狗?”等问题。然而,近年来,在被称为生成机器学习的迷人领域取得了许多进展。生成机器学习是机器学习的一个领域,它教会计算机如何创造事物,而不仅仅是回答问题。绘画、作曲或写故事等行为代表了理解世界的一个更高层次。

毫无疑问,在过去的几年里,最重要的进步之一是生成对抗网络(GANs)的发展。在图像生成方面,生成对抗网络展现出了令人着迷的结果。GANs 由两个相互竞争的网络组成,即生成器和判别器。生成器试图生成看起来真实的图像,而判别器则试图区分真实图像和伪造图像。在训练过程中,我们向判别器提供真实图像以及生成器生成的伪造图像。当应用于人脸数据集时,这个过程产生了一个能够生成一些非常逼真的人脸的生成器。事实上,它们看起来如此逼真,以至于人类往往很难区分它们。你可以通过一个 GAN 来测试自己——www.whichfaceisreal.com

摘要

  • 神经网络是一种用于分类和回归的强大模型。神经网络由一组按层组织的感觉器组成,其中一层输出的结果作为下一层的输入。它们的复杂性使它们能够在其他机器学习模型难以应用的领域中取得巨大成功。

  • 神经网络在许多领域都有前沿的应用,包括图像识别和文本处理。

  • 神经网络的基本构建块是感知器。感知器接收几个值作为输入,通过将输入乘以权重、添加偏差并应用激活函数来输出一个值。

  • 流行的激活函数包括 sigmoid、双曲正切、softmax 和修正线性单元(ReLU)。它们用于神经网络中的层之间,以打破线性并帮助我们构建更复杂的边界。

  • Sigmoid 函数是一个简单的函数,它将任何实数映射到 0 和 1 之间的区间。双曲正切函数类似,但输出是-1 和 1 之间的区间。它们的目标是将我们的输入挤压到一个小区间内,以便我们的答案可以被解释为类别。它们主要用于神经网络中的最终(输出)层。由于它们的导数平坦,它们可能会导致梯度消失问题。

  • ReLU 函数是一个将负数映射到 0,非负数映射到自身的函数。它在减少梯度消失问题方面取得了巨大成功,因此比 sigmoid 函数或双曲正切函数更多地用于神经网络训练。

  • 神经网络具有非常复杂的结构,这使得它们难以训练。我们用来训练它们的称为反向传播的过程已经取得了巨大成功。反向传播包括对损失函数求导,并找到所有关于模型所有权重的偏导数。然后我们使用这些导数迭代地更新模型的权重,以提高其性能。

  • 神经网络容易过拟合和其他问题,如梯度消失,但我们可以使用正则化和 dropout 等技术来帮助减少这些问题。

  • 我们有一些有用的包来训练神经网络,例如 Keras、TensorFlow 和 PyTorch。这些包使我们能够非常容易地训练神经网络,因为我们只需要定义模型的架构和误差函数,它们会负责训练。此外,它们有许多内置的先进优化器,我们可以利用这些优化器。

练习

练习 10.1

以下图像显示了一个所有激活都是 sigmoid 函数的神经网络。

这个神经网络会对输入(1,1)做出什么预测?

练习 10.2

正如我们在练习 5.3 中学到的,不可能构建一个模拟 XOR 门的感知器。换句话说,不可能使用感知器拟合以下数据集并获得 100%的准确率:

x[1] x[2] y
0 0 0
0 1 1
1 0 1
1 1 0

这是因为数据集不是线性可分的。使用深度为 2 的神经网络,构建一个模拟之前显示的 XOR 门的感知器。作为激活函数,使用步函数而不是 sigmoid 函数以获得离散输出。

提示:使用训练方法来做这个可能会很困难;相反,尝试凭直觉调整权重。尝试(或在网上搜索如何)使用 AND、OR 和 NOT 门构建一个 XOR 门,并使用练习 5.3 的结果来帮助你。

练习 10.3

在“神经网络图形表示”部分的结尾,我们看到了图 10.13 中的神经网络,其激活函数与表 10.1 中的数据集不匹配,因为点(1,1)被错误分类。

  1. 验证这是否是这种情况。

  2. 改变权重,使神经网络能够正确分类每个点。

11 以风格寻找边界:支持向量机和核方法

在本章

  • 支持向量机是什么

  • 对于一个数据集,哪个线性分类器有最好的边界

  • 使用核方法构建非线性分类器

  • 在 Scikit-Learn 中编码支持向量机和核方法

图片

专家建议在尝试分离鸡数据集时使用核方法。

在本章中,我们讨论了一个强大的分类模型,称为支持向量机(简称 SVM)。SVM 与感知器类似,因为它使用线性边界将两个类别的数据集分开。然而,SVM 的目标是找到尽可能远离数据集中点的线性边界。我们还介绍了核方法,当与 SVM 结合使用时非常有用,并且可以帮助使用高度非线性边界来分类数据集。

在第五章,我们学习了线性分类器,或感知器。对于二维数据,这些由一条线定义,该线将具有两个标签的点组成的数据集分开。然而,我们可能已经注意到,许多不同的线可以分离一个数据集,这引发以下问题:我们如何知道哪条是最好的线?在图 11.1 中,我们可以看到三种不同的线性分类器,它们将这个数据集分开。你更喜欢分类器 1、2 还是 3?

图片

图 11.1 三种正确分类我们的数据集的分类器。我们应该选择分类器 1、2 还是 3?

如果你说是分类器 2,我们同意。这三条线都很好地分离了数据集,但第二条线放置得更好。第一条和第三条线与一些点非常接近,而第二条线与所有点都保持距离。如果我们稍微调整这三条线,第一条和第三条可能会越过一些点,在这个过程中错误地分类一些点,而第二条线仍然会正确地分类所有点。因此,分类器 2 比分类器 1 和 3 更稳健。

这就是支持向量机发挥作用的地方。SVM 分类器使用两条平行线而不是一条线。SVM 的目标有两个;它试图正确地分类数据,并且试图尽可能地将线分开。在图 11.2 中,我们可以看到三个分类器的两条平行线,以及它们的中间线作为参考。分类器 2 中的两条外部(虚线)线彼此距离最远,这使得这个分类器成为最好的一个。

图片

图 11.2 我们将我们的分类器绘制为两条尽可能远的平行线。我们可以看到分类器 2 是两条平行线之间距离最远的那一个。这意味着分类器 2 中的中间线是位于这些点之间位置最好的那一条。

我们可能想要将支持向量机(SVM)可视化为中心的那条线,它试图尽可能远离这些点。我们也可以想象成两条外部平行的线,试图尽可能远离彼此。在本章中,我们将根据不同情况在不同时间使用这两种可视化方法,因为每种方法在特定情况下都很有用。

我们如何构建这样的分类器?我们可以用与之前类似的方式来做,只是使用一个稍微不同的误差函数和稍微不同的迭代步骤。

注意:在本章中,所有分类器都是离散的,即它们的输出是 0 或 1。有时它们通过预测 ŷ = step(f(x)) 来描述,有时通过边界方程 f(x) = 0 来描述,即尝试将我们的数据点分为两个类别的函数的图像。例如,预测 ŷ = step(3x[1] + 4x[2] – 1) 的感知器有时仅通过线性方程 3x[1] + 4x[2] – 1 = 0 来描述。在本章的一些分类器中,尤其是在“使用非线性边界训练 SVM:核方法”这一节中,边界方程不一定是一个线性函数。

在本章中,我们主要在单维和二维数据集(线上的点或平面上的点)上看到这种理论。然而,支持向量机在更高维度的数据集上同样表现良好。一维中的线性边界是点,二维中的线性边界是线。同样,三维中的线性边界是平面,而在更高维度中,它们是比点所在空间低一维的超平面。在这些所有情况下,我们都试图找到离点最远的边界。在图 11.3 中,你可以看到一维、二维和三维边界的示例。

图 11.3 展示了单维、二维和三维数据集的线性边界。在一维中,边界由两个点形成,在二维中由两条线形成,在三维中由两个平面形成。在每种情况下,我们都试图尽可能地将这两者分开。中间的边界(点、线或平面)为了清晰起见进行了说明。

本章的所有代码都存放在这个 GitHub 仓库中:github.com/luisguiserrano/manning/tree/master/Chapter_11_Support_Vector_Machines

使用新的误差函数构建更好的分类器

如同机器学习模型中常见的那样,SVM 是通过一个误差函数来定义的。在本节中,我们将看到 SVM 的误差函数,它非常特殊,因为它试图同时最大化两件事:点的分类和线的距离。

要训练一个 SVM,我们需要为两个尽可能分开的线构建一个分类器的错误函数。当我们思考构建错误函数时,我们应该始终问自己:“我们希望模型实现什么?”以下是我们想要实现的两件事:

  • 每条线都应该尽可能好地分类点。

  • 这两条线应该尽可能远。

错误函数应该惩罚任何未能实现这些目标的模型。因为我们想要两件事,所以我们的 SVM 错误函数应该是两个错误函数的和:第一个惩罚被错误分类的点,第二个惩罚彼此太近的线。因此,我们的错误函数可以看起来像这样:

错误 = 分类错误 + 距离错误

在接下来的两个部分中,我们将分别开发这两个术语中的每一个。

分类错误函数:尝试正确分类点

在本节中,我们学习分类错误函数。这是推动分类器正确分类点的错误函数的一部分。简而言之,这个错误是这样计算的。因为分类器由两条线组成,我们把它们看作是两个独立的离散感知器(第五章)。然后我们计算这个分类器的总错误,即两个感知器错误的和(第五章“如何比较分类器?错误函数”部分)。让我们看一个例子。

SVM 使用两条平行线,幸运的是,平行线有相似的方程;它们有相同的权重但不同的偏置。因此,在我们的 SVM 中,我们使用中心线作为参考框架 L,其方程为w[1]x[1] + w[2]x[2] + b = 0,并构建两条线,一条在上面,一条在下面,相应的方程为:

  • L+:w[1]x[1] + w[2]x[2] + b = 1,并且

  • L–:w[1]x[1] + w[2]x[2] + b = –1

例如,图 11.4 显示了三条平行线 L、L+和 L–,它们的方程如下:

  • L:2x[1] + 3x[2] – 6 = 0

  • L+:2x[1] + 3x[2] – 6 = 1

  • L–:2x[1] + 3x[2] – 6 = –1

图 11.4 我们的主要线 L 是中间的那条。我们通过稍微改变 L 的方程来构建两个平行且等距的线 L+和 L–。

我们现在的分类器由线 L+和 L–组成。我们可以把 L+和 L–看作是两个独立的感知器分类器,它们各自的目标都是正确分类点。每个分类器都有自己的感知器错误函数,因此分类函数定义为这两个错误函数的和,如图 11.5 所示。

图 11.5 现在我们分类器由两条线组成,错误分类点的误差是根据这两条线来衡量的。然后我们将两个误差相加以获得分类误差。请注意,误差不是如图所示垂直线段的长度,而是与它成比例。

注意到在支持向量机(SVM)中,两条线都必须很好地对点进行分类。因此,位于两条线之间的点总是被其中一条线错误分类,所以它不被 SVM 视为正确分类的点。

回忆第五章“如何比较分类器?误差函数”部分的内容,离散感知器的误差函数,在点(p, q)处的预测 ŷ = step(w[1]x[1] + w[2]x[2] + b),由以下给出:

  • 0 如果点被正确分类,并且

  • |w[1]x[1] + w[2]x[2] + b| 如果点被错误分类

例如,考虑标签为 0 的点(4,3)。这个点在图 11.5 中的两个感知器都被错误分类。请注意,两个感知器给出了以下预测:

  • L+: ŷ = step(2x[1] + 3x[2] – 7)

  • L–: ŷ = step(2x[1] + 3x[2] – 5)

因此,它相对于这个 SVM 的分类误差是

|2 · 4 + 3 · 3 – 7| + |2 · 4 + 3 · 3 – 5| = 10 + 12 = 22。

距离误差函数:试图尽可能地将我们的两条线分开

现在我们已经创建了一个衡量分类误差的误差函数,我们需要构建一个检查两条线之间距离的误差函数,如果这个距离很小,则发出警报。在本节中,我们讨论了一个出人意料简单的误差函数,当两条线靠近时它很大,当它们远离时它很小。

这个误差函数被称为距离误差函数,我们之前已经见过;它是我们在第四章“修改误差函数以解决问题”部分学到的正则化项。更具体地说,如果我们的线的方程是 w[1]x[1] + w[2]x[2] + b = 1 和 w[1]x[1] + w[2]x[2] + b = –1,那么误差函数就是 w[1]² + w[2]²。为什么?我们将利用以下事实:两条线之间的垂直距离恰好是 ,如图 11.6 所示。如果你想要计算出这个距离计算的细节,请查看本章末尾的练习 11.1。

图 11.6 两条平行线之间的距离可以根据线的方程计算。

了解这一点后,请注意以下内容:

  • w[1]² + w[2]² 较大时, 较小。

  • w[1]² + w[2]² 较小时, 较大。

因为我们希望线尽可能远,这个项 w[1]² + w[2]² 是一个好的错误函数,因为它为不良分类器(那些线很近的分类器)提供了大值,而对于良好分类器(那些线很远的分类器)则提供小值。

在图 11.7 中,我们可以看到两个 SVM 分类器的示例。它们的方程如下:

  • SVM 1:

    • L+: 3x[1] + 4x[2] + 5 = 1

    • L–: 3x[1] + 4x[2] + 5 = –1

  • SVM 2:

    • L+: 30x[1] + 40x[2] + 50 = 1

    • L–: 30x[1] + 40x[2] + 50 = 1

它们的距离错误函数如下所示:

  • SVM 1:

    • 距离错误函数 = 3² + 4² = 25
  • SVM 2:

    • 距离错误函数 = 30² + 40² = 2500

注意,从图 11.7 中还可以看出,SVM 2 中的线比 SVM 1 中的线要近得多,这使得 SVM 1 成为一个更好的分类器(从距离的角度来看)。SVM 1 中线的距离为 ,而在 SVM 2 中为

图 11.7 左:一个 SVM,其中线之间的距离为 0.4,错误为 25。右:一个 SVM,其中线之间的距离为 0.04,错误为 2500。注意,在这个比较中,左边的分类器比右边的分类器要好得多,因为线彼此之间的距离更远。这导致距离错误更小。

将两个错误函数相加以获得错误函数

现在我们已经建立了一个分类错误函数和一个距离错误函数,让我们看看如何将它们结合起来构建一个错误函数,帮助我们确保实现了两个目标:将点很好地分类,并且两条线彼此间隔很远。

为了获得这个错误函数,我们将分类错误函数和距离错误函数相加,得到以下公式:

错误 = 分类错误 + 距离错误

一个良好的 SVM 必须尝试尽可能少地犯分类错误,同时尝试使线尽可能远。

图 11.8 左:一个良好的 SVM,由两条间隔良好的线组成,能够正确分类所有点。中:一个不良的 SVM,错误分类了两个点。右:一个不良的 SVM,由两条太靠近的线组成。

在图 11.8 中,我们可以看到同一数据集的三个 SVM 分类器。左边的分类器是一个好的分类器,因为它很好地分类了数据,并且线之间间隔很远,减少了错误的可能性。中间的那个分类器犯了一些错误(因为顶部线下方有一个三角形,底部线上方有一个正方形),所以它不是一个好的分类器。右边的分类器正确地分类了点,但线太靠近了,所以它也不是一个好的分类器。

我们希望我们的 SVM 更关注分类还是距离?C 参数可以帮助我们

在本节中,我们学习了一种有用的技术来调整和改进我们的模型,这涉及到引入 C 参数。当我们要训练一个比距离(或相反)更关注分类的 SVM 时,使用 C 参数。

到目前为止,似乎我们构建一个好的 SVM 分类器所要做的只是跟踪两件事。我们想确保分类器尽可能少犯错误,同时保持线尽可能分开。但如果我们不得不为了对方的利益而牺牲一方怎么办?在图 11.9 中,我们对同一个数据集有两个分类器。左边的一个犯了一些错误,但线分得很开。右边的一个没有错误,但线太靠近了。我们应该选择哪一个?

图片

图 11.9 这两个分类器各有优点和缺点。左边的一个由间隔良好的线组成(优点),但它错误地分类了一些点(缺点)。右边的一个由太靠近的线组成(缺点),但它正确地分类了所有点(优点)。

结果表明,这个答案取决于我们正在解决的问题。有时我们希望有一个尽可能少犯错误的分类器,即使线很近,有时我们希望有一个即使犯一些错误也能保持线分开的分类器。我们如何控制这一点?我们使用一个我们称之为 C 参数的参数。我们通过将分类错误乘以 C 来稍微修改错误公式,得到以下公式:

错误公式 = C · (分类错误) + (距离错误)

如果 C 很大,那么错误公式主要由分类错误决定,因此我们的分类器更关注正确分类点。如果 C 很小,那么公式主要由距离错误决定,因此我们的分类器更关注保持线分开。

图片

图 11.10 中不同的 C 值在具有良好间隔线的分类器和正确分类点的分类器之间切换。左边的分类器 C 值较小(0.01),线间隔良好,但犯了错误。右边的分类器 C 值较大(100),正确分类了点,但线太靠近了。中间的分类器犯了一个错误,但找到了两条间隔良好的线。

在图 11.10 中,我们可以看到三个分类器:一个 C 值大的分类器可以正确分类所有点,一个 C 值小的分类器可以保持线间隔良好,还有一个 C = 1 的分类器试图两者兼顾。在现实生活中,C 是一个超参数,我们可以通过使用模型复杂度图(第四章中“决定我们的模型应该有多复杂的一种数值方法”部分)或我们对所解决问题的了解、数据和对模型的知识来调整 C。

在 Scikit-Learn 中编码支持向量机

现在我们已经学习了 SVM 是什么,我们准备编写一个 SVM 并用它来建模一些数据。在 Scikit-Learn 中,编写 SVM 简单,这就是我们在本节中学到的。我们还学习了如何在代码中使用 C 参数。

编写简单的 SVM

我们首先在样本数据集中编写一个简单的 SVM,然后我们将添加更多参数。数据集称为 linear.csv,其图表显示在图 11.11 中。本节的代码如下:

我们首先从 Scikit-Learn 的 svm 包中导入,并按以下方式加载数据:

from sklearn.svm import SVC

图片

图 11.11 几乎线性可分的数据集,有一些异常值

然后,如以下代码片段所示,我们将数据加载到两个 Pandas DataFrame 中,分别命名为 featureslabels,然后定义我们的模型 svm_linear 并对其进行训练。我们获得的准确率为 0.933,图表显示在图 11.12 中。

svm_linear = SVC(kernel='linear')
svm_linear.fit(features, labels)

图片

图 11.12 我们在 Scikit-Learn 中构建的 SVM 分类器的图表由一条线组成。该模型的准确率为 0.933。

C 参数

在 Scikit-Learn 中,我们可以轻松地将 C 参数引入模型。这里我们训练并绘制了两个模型,一个具有非常小的值 0.01,另一个具有较大的值 100,如下代码和图 11.13 所示:

svm_c_001 = SVC(kernel='linear', C=0.01)
svm_c_001.fit(features, labels)

svm_c_100 = SVC(kernel='linear', C=100)
svm_c_100.fit(features, labels)

图片

图 11.13 左侧的分类器 C 值较小,很好地分隔了点之间的线,但犯了一些错误。右侧的分类器 C 值较大,没有犯错误,尽管线过于接近一些点。

我们可以看到,C 值较小的模型并不太强调正确分类点,它犯了一些错误,这在它的低准确率(0.867)中很明显。在这个例子中很难看出,但这个分类器非常强调线尽可能远离点。相比之下,C 值较大的分类器试图正确分类所有点,这反映在其更高的准确率上。

使用非线性边界的 SVM 训练:核方法

正如我们在本书的其他章节中看到的,并非每个数据集都是线性可分的,很多时候我们需要构建非线性分类器来捕捉数据的复杂性。在本节中,我们研究了一种与 SVM 相关的强大方法,称为 核方法,它帮助我们构建高度非线性分类器。

如果我们有一个数据集,发现我们无法用线性分类器将其分离,我们该怎么办?一个想法是向这个数据集添加更多列,并希望更丰富的数据集是线性可分的。核方法包括以巧妙的方式添加更多列,在这个新数据集上构建一个线性分类器,然后在保持对(现在是非线性的)分类器的跟踪的同时移除我们添加的列。

这听起来可能有些复杂,但我们有一个很好的几何方法来观察这种方法。想象一下,数据集在二维空间中,这意味着输入有两个列。如果我们添加一个第三列,数据集现在就是三维的,就像你手中的点突然以不同的高度飞入空间一样。也许如果我们以巧妙的方式提高这些点的高度,我们可以用平面将它们分开。这就是核方法,它在图 11.14 中得到了说明。

图像

图 11.14 左:这个集合不能用一条线分开。中:我们在三维空间中观察它,然后提高两个三角形并降低两个正方形。右:我们新的数据集现在可以用一个平面分开。(来源:使用 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher)。

核、特征图和算子理论

核方法背后的理论来自数学中的一个领域,称为算子理论。核是一个相似性函数,简而言之,是一个告诉我们两个点是否相似或不同的函数(例如,接近或远离)。核可以产生一个特征图,这是我们的数据集所在空间与(通常是)更高维空间之间的映射。

不需要完整的核和特征图理论来理解分类器。如果你想要深入了解这些内容,请参阅附录 C 中的资源。为了本章的目的,我们将核方法视为向我们的数据集添加列以使点可分的一种方式。例如,图 11.14 中的数据集有两个列,x[1]和x[2],我们添加了第三个列,其值为x[1]x[2]。等价地,它也可以看作是将平面上的点(x[1], x[2])映射到空间中的点(x[1], x[2], x[1]x[2])的函数。一旦点属于 3-D 空间,我们就可以使用图 11.14 右边的平面来分离它们。要更详细地研究这个例子,请参阅本章末尾的练习 11.2。

本章中我们看到的两个核及其相应的特征图是多项式核径向基函数(RBF)核。它们都以不同但非常有效的方式向我们的数据集添加列。

利用多项式方程为我们带来好处:多项式核

在本节中,我们讨论多项式核,这是一种有用的核,可以帮助我们建模非线性数据集。更具体地说,核方法帮助我们使用如圆、抛物线和双曲线等多项式方程来建模数据。我们将通过两个示例来说明多项式核。

示例 1:一个圆形数据集

对于我们的第一个示例,让我们尝试对表 11.1 中的数据集进行分类。

表 11.1 一个小数据集,如图 11.15 所示

x[1] x[2] y
0.3 0.3 0
0.2 0.8 0
–0.6 0.4 0
0.6 –0.4 0
–0.4 –0.3 0
0 –0.8 0
–0.4 1.2 1
0.9 –0.7 1
–1.1 –0.8 1
0.7 0.9 1
–0.9 0.8 1
0.6 –1 1

该图如图 11.15 所示,其中标签为 0 的点以正方形绘制,标签为 1 的点以三角形绘制。

当我们查看图 11.15 中的图时,很明显一条线无法将正方形和三角形分开。然而,一个圆可以(如图 11.16 所示)。现在的问题是,如果支持向量机只能绘制线性边界,我们如何绘制这个圆?

图 11.15 表 11.1 中数据集的图。请注意,它不能被一条线分开。因此,这个数据集是核方法的好候选。

图 11.16 核方法为我们提供了一个具有圆形边界的分类器,这些点被很好地分隔开。

为了绘制这个边界,让我们思考。什么特征可以区分正方形和三角形?从观察图中可以看出,三角形比圆离原点更远。测量到原点距离的公式是两个坐标平方和的平方根。如果这些坐标是 x[1] 和 x[2],那么这个距离是 。让我们先不考虑平方根,只考虑 x[1]² + x[2]²。现在让我们在表 11.1 中添加一个包含这个值的列,看看会发生什么。结果数据集如图 11.2 所示。

表 11.2 我们在表 11.1 中添加了一个额外的列。这一列由前两列值的平方和组成。

x[1] x[2] x[1]² + x[2]² y
0.3 0.3 0.18 0
0.2 0.8 0.68 0
–0.6 0.4 0.52 0
0.6 –0.4 0.52 0
–0.4 –0.3 0.25 0
0 –0.8 0.64 0
–0.4 1.2 1.6 1
0.9 –0.7 1.3 1
–1.1 –0.8 1.85 1
0.7 0.9 1.3 1
–0.9 0.8 1.45 1
0.6 –1 1.36 1

在查看表 11.2 之后,我们可以看到趋势。所有标记为 0 的点都满足坐标平方和小于 1 的条件,而标记为 1 的点满足这个和大于 1 的条件。因此,分离这些点的坐标方程正是x[1]² + x[2]² = 1。请注意,这不是一个线性方程,因为变量被提升到大于一的幂。事实上,这正是圆的方程。

想象这种几何方式如图 11.17 所示。我们的原始集位于平面中,无法用一条线将两个类别分开。但如果我们把每个点(x[1],x[2])提升到高度x[1]² + x[2]²,这相当于将这些点放入方程z = x[1]² + x[2]²的抛物面中(图中所示)。我们提升每个点的距离正是该点到原点的距离的平方。因此,正方形提升的量很小,因为它们靠近原点,而三角形提升的量很大,因为它们远离原点。现在正方形和三角形相距很远,因此我们可以用高度为 1 的水平平面将它们分开——换句话说,就是方程z = 1 的平面。作为最后一步,我们将所有东西投影到平面上。抛物面和平面的交点变成了方程x[1]² + x[2]² = 1 的圆。请注意,这个方程不是线性的,因为它包含二次项。最后,这个分类器做出的预测是ŷ = step(x[1]² + x[2]² – 1)。

图 11.17 核方法。步骤 1:我们从一组非线性可分的数据集开始。步骤 2:然后我们将每个点提升到其到原点的距离的平方。这创建了一个抛物面。步骤 3:现在三角形很高,而正方形很低。我们通过高度为 1 的平面来分离它们。步骤 4:我们将所有东西投影下来。抛物面和平面的交点形成一个圆。这个圆的投影给出了我们分类器的圆形边界。(来源:图像由 Golden Software, LLC 的 Grapher™辅助创建;www.goldensoftware.com/products/grapher)。

示例 2:修改后的 XOR 数据集

圆形并不是我们唯一可以绘制的图形。让我们考虑一个非常简单的数据集,如图 11.3 表所示,并在图 11.18 中绘制。这个数据集类似于练习 5.3 和 10.2 中对应的 XOR 运算符。如果你想在原始 XOR 数据集上解决相同的问题,你可以在本章末尾的练习 11.2 中做到这一点。

表 11.3 修改后的 XOR 数据集

x[1] x[2] y
–1 –1 1
–1 1 0
1 –1 0
1 1 1

要看出这个数据集不是线性可分的,请看图 11.18。两个三角形位于一个大正方形的对角,而两个正方形位于剩余的两个角上。不可能画一条线将三角形和正方形分开。然而,我们可以使用多项式方程来帮助我们,这次我们将使用两个特征的乘积。让我们将对应于乘积 x[1]x[2] 的列添加到原始数据集中。结果如表 11.4 所示。

表 11.4 我们在表 11.3 中添加了一列,该列由前两列的乘积组成。注意,表中最右边两列之间有很强的关系。

x[1] x[2] x[1] x[2] y
–1 –1 1 1
–1 1 –1 0
1 –1 –1 0
1 1 1 1

注意到对应于乘积 x[1]x[2] 的列与标签列非常相似。我们现在可以看到,对于这个数据集,一个好的分类器具有以下边界方程:x[1]x[2] = 1。这个方程的图是水平和垂直轴的并集,这是因为要使乘积 x[1]x[2] 为 0,我们需要 x[1] = 0 或 x[2] = 0。这个分类器做出的预测由 ŷ = step(x[1]x[2]) 给出,对于平面的东北部和西南部的点,预测为 1,其他地方为 0。

图 11.18 表 11.3 中数据集的图。将正方形与三角形分开的分类器的边界方程为 x[1]x[2] = 0,这对应于水平和垂直轴的并集。

超越二次方程:多项式核

在前两个例子中,我们使用了多项式表达式来帮助我们分类一个非线性可分的数据集。在第一个例子中,这个表达式是 x[1]² + x[2]²,因为对于靠近原点的点,这个值很小,而对于远离原点的点,这个值很大。在第二个例子中,表达式是 x[1]x[2],这有助于我们在平面的不同象限中分离点。

我们是如何找到这些表达式的?在一个更复杂的数据集中,我们可能没有时间查看图表并直观地找到一个有助于我们的表达式。我们需要一个方法,换句话说,一个算法。我们将考虑所有可能的二次(二次)单变量,包含 x[1] 和 x[2]。这些是以下三个单变量:x[1]²,x[1]x[2],和 x[2]²。我们称这些新变量为 x[3],x[4],和 x[5],并将它们视为与 x[1] 和 x[2] 完全无关。让我们将此应用于第一个例子(圆)。添加了这些新列的表 11.1 数据集如表 11.5 所示。

我们现在可以构建一个分类器来对增强后的数据集进行分类。训练 SVM 的方法是使用上一节学到的技术。我鼓励你使用 Scikit-Learn,Turi Create 或你选择的任何包来构建这样的分类器。通过检查,这里有一个有效的分类器方程:

0x[1] + 0x[2] + 1x[3] + 0x[4] + 1x[5] – 1 = 0

表 11.5 我们在表 11.1 中增加了三行,每一行对应于两个变量x[1]和x[2]上的每个二次单项式。这些单项式是x[1]²,x[1] x[2],和x[2]²。

x[1] x[2] x[3] = x[1]² x[4] = x[1] x[2] x[5] = x[2]² y
0.3 0.3 0.09 0.09 0.09 0
0.2 0.8 0.04 0.16 0.64 0
–0.6 0.4 0.36 –0.24 0.16 0
0.6 –0.4 0.36 –0.24 0.16 0
–0.4 –0.3 0.16 0.12 0.09 0
0 –0.8 0 0 0.64 0
–0.4 1.2 0.16 –0.48 1.44 1
0.9 –0.7 0.81 –0.63 0.49 1
–1.1 –0.8 1.21 0.88 0.64 1
0.7 0.9 0.49 0.63 0.81 1
–0.9 0.8 0.81 –0.72 0.64 1
0.6 –1 0.36 –0.6 1 1

记住x[3] = x[1]²和x[4] = x[2]²,我们得到所需的圆的方程,如下所示:

x[1]² + x[2]² = 1

如果我们要像之前那样从几何角度可视化这个过程,它会变得稍微复杂一些。我们美好的二维数据集变成了五维数据集。在这个数据集中,标记为 0 和 1 的点现在相距很远,可以用四维超平面分开。当我们将其投影到二维时,我们得到所需的圆。

多项式核产生了一个将二维平面映射到五维空间的映射。这个映射是将点(x[1],x[2])映射到点(x[1],x[2],x[1]²,x[1]x[2],x[2]²)。因为每个单项式的最大次数是 2,所以我们说这是二次多项式核。对于多项式核,我们总是必须指定次数。

如果我们使用的是更高次数的多项式核,比如k,我们应该在数据集中添加哪些列?对于给定变量集中的每个单项式,我们添加一个列,其次数小于或等于k。例如,如果我们使用x[1]和x[2]上的三次多项式核,我们添加的列对应于以下单项式:{x[1],x[2],x[1]²,x[1]x[2],x[2]²,x[1]³,x[1]²x[2],x[1]x[2]²,x[2]³}。我们也可以以相同的方式为更多变量做这件事。例如,如果我们使用x[1],x[2],和x[3]上的二次多项式核,我们添加的列具有以下单项式:{x[1],x[2],x[3],x[1]²,x[1]x[2],x[1]x[3],x[2]²,x[2]x[3],x[3]²}。

利用高维空间中的峰值来为我们带来好处:径向基函数(RBF)核

我们将要看到的下一个核是径向基函数核。这个核在实践中非常有用,因为它可以帮助我们使用以每个数据点为中心的某些特殊函数构建非线性边界。为了介绍 RBF 核,让我们首先看看图 11.19 中显示的一维示例。这个数据集不是线性可分的——正方形正好位于两个三角形之间。

图片

图 11.19 一个一维数据集,线性分类器无法对其进行分类。注意,线性分类器是一个将线分为两部分的点,并且没有点可以定位在直线上,使得所有三角形都在一边,正方形在另一边。

我们将构建这个数据集的分类器的方式是想象在每个点上构建一座山或一个山谷。对于标记为 1 的点(三角形),我们将放置一座山,对于标记为 0 的点(正方形),我们将放置一个山谷。这些山和山谷被称为径向基函数。结果图显示在图 11.20 的顶部。现在,我们画出一个山脉,使得在每个点上,高度是那个点上的所有山和山谷高度的总和。我们可以在图 11.20 的底部看到结果山脉。最后,分类器的边界对应于这个山脉高度为零的点,即底部突出显示的两个点。这个分类器将两个点之间的任何东西分类为正方形,并将区间外的任何东西分类为三角形。

图片

图 11.20 使用具有 RBF 核的 SVM 在一维数据集中分离非线性数据集。顶部:我们在每个标记为 1 的点处画一座山(径向基函数),在每个标记为 0 的点处画一个山谷。底部:我们在顶部图中的径向基函数上添加。结果函数与轴相交两次。两个交点是我们 SVM 分类器的边界。我们将它们之间的每个点分类为正方形(标签 0),并将每个外部点分类为三角形(标签 1)。

这(以及下一节中围绕它的数学)是 RBF 核的精髓。现在让我们使用它在一个二维数据集中构建一个类似的分类器。

要在平面上构建山脉和山谷,想象平面就像一张毯子(如图 11.21 所示)。如果我们捏住毯子上的那个点并抬起它,我们就会得到山脉。如果我们压下它,我们就会得到山谷。这些山脉和山谷是径向基函数。它们被称为径向基函数,因为函数在一点的值只依赖于该点与中心之间的距离。我们可以在任何我们喜欢的点上抬起毯子,这样就为每个点给出一个不同的径向基函数。径向基函数核(也称为 RBF 核)产生一个映射,使用这些径向函数以帮助我们分离数据集的方式向我们的数据集添加几列。

图片

图 11.21 径向基函数由在特定点抬起平面组成。这是我们用来构建非线性分类器的函数族。(来源:使用 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher)。

我们如何将这个用作分类器呢?想象一下以下情况:我们在图 11.22 的左侧有数据集,其中,像往常一样,三角形代表标签为 1 的点,而正方形代表标签为 0 的点。现在,我们在每个三角形上抬起平面,在每个正方形上压下平面。我们得到了图 11.22 右侧所示的三维图。

要创建分类器,我们在高度 0 处画一个平面,并将其与我们的表面相交。这相当于观察高度为 0 的点的曲线。想象一下,如果有一个有山脉和大海的地形。曲线将对应于海岸线,即水和陆地相交的地方。这条海岸线是图 11.23 左侧所示的曲线。然后我们将所有东西投影回平面,得到我们想要的分类器,如图 11.23 右侧所示。

这就是 RBF 核背后的想法。当然,我们必须发展数学,我们将在接下来的几节中这样做。但原则上,如果我们能想象抬起和压下一张毯子,然后通过观察特定高度的点的边界来构建分类器,那么我们就能理解 RBF 核是什么。

图片

图 11.22 左:平面上一个非线性可分的数据集。右:我们已使用径向基函数抬起每个三角形并降低每个正方形。注意,现在我们可以通过一个平面来分离数据集,这意味着我们的修改后的数据集是线性可分的。(来源:使用 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher)。

图片

图 11.23 左:如果我们看高度为 0 的点,它们形成一条曲线。如果我们把高点看作陆地,低点看作海洋,这条曲线就是海岸线。右:当我们把点投影(展平)回平面时,海岸线现在是我们区分三角形和正方形的分类器。(来源:使用 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher

对径向基函数的更深入探讨

径向基函数可以存在于任意数量的变量中。在本节的开头,我们看到了一维和二维的例子。对于一维,最简单的径向基函数公式是 y = e^(−x²)。这看起来像一条线上的一个隆起(图 11.24)。它看起来很像一个标准的正态(高斯)分布。标准的正态分布类似,但它有一个略微不同的公式,因此其下的面积是 1。

图片

图 11.24 一个径向基函数的示例。它看起来很像一个正常的(高斯)分布。

注意这个隆起发生在 0 处。如果我们想让它出现在任何不同的点,比如说 p,我们可以平移公式,得到 y = e^(−(xp)²)。例如,以点 5 为中心的径向基函数正好是 y = e^(−(x − 5)²)。

对于两个变量,最基本径向基函数的公式是 z = e^(−(x² + y²)),它看起来像图 11.25 中所示的图表。再次,你可能注意到它看起来很像一个多元正态分布。它再次是多元正态分布的一个修改版本。

图片

图 11.25 在两个变量上的径向基函数。它再次看起来很像一个正态分布。(来源:使用 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher

这个隆起正好发生在点(0,0)处。如果我们想让它出现在任何不同的点,比如说(p, q),我们可以平移公式,得到 y = e^(−[(xp)² +(yq)²])。例如,以点(2, –3)为中心的径向基函数正好是 y = e^(−[(x − 2)² +(y + 3)²])。

对于 n 个变量,基本径向基函数的公式是 y = e^(−(x[1]²+ ··· +x[n]²))。我们无法在 n + 1 维度中绘制图表,但如果我们想象挤压一个 n-维度的毯子并用手指提起它,那就是它的样子。然而,因为我们使用的算法完全是数学的,所以计算机可以轻松地在任意多的变量中运行它。像往常一样,这个 n-维度的隆起以 0 为中心,但如果我们想让它以点 (p[1], …, p[n])为中心,公式是 y = e^(−[(x[1] - p[1])²+ ··· +(x[n] - p[n])²])

测量点之间距离的一个度量:相似度

要使用 RBF 内核构建 SVM,我们需要一个概念:相似度的概念。我们说两个点相似,如果它们彼此靠近,如果不相似,如果它们彼此远离(图 11.26)。换句话说,两个点之间的相似度如果它们彼此靠近就很高,如果它们彼此远离就很低。如果一对点是同一个点,那么相似度为 1。在理论上,两个无限远的点之间的相似度为 0。

图 11.26 两个彼此靠近的点被定义为具有高相似度。两个彼此远离的点被定义为具有低相似度。

现在我们需要找到一个相似度的公式。正如我们所看到的,两个点之间的相似度随着它们之间距离的增加而降低。因此,许多相似度的公式都会工作,只要它们满足这个条件。因为我们在这个部分使用指数函数,所以我们定义如下。对于点 pqpq 之间的相似度如下:

similarity(p,q)= e(distance)(p,q^)²

这个看起来是一个复杂的相似度公式,但有一个非常好的方法来看待它。如果我们想要找到两个点之间的相似度,比如说 pq,这个相似度正是以 p 为中心的径向基函数在点 q 处应用的高度。也就是说,如果我们把毯子在点 p 处捏起并提起,那么点 q 处的毯子高度就会很高,如果 q 靠近 p,而如果 qp 很远,那么高度就会很低。在图 11.27 中,我们可以看到一个变量的情况,但想象一下通过毯子类比在任何数量的变量中。

图 11.27 相似度定义为径向基函数中点的位置,其中输入是距离。请注意,距离越高,相似度越低,反之亦然。

使用 RBF 内核训练 SVM

现在我们已经拥有了使用 RBF 内核训练 SVM 的所有工具,让我们看看如何将这些工具组合起来。让我们首先看看图 11.19 显示的简单数据集。数据集本身在表 11.6 中。

表 11.6 图 11.19 所示的一维数据集。请注意,它不是线性可分的,因为标签为 0 的点正好位于两个标签为 1 的点之间。

x y (标签)
1 –1 1
2 0 0
3 1 1

正如我们所见,这个数据集不是线性可分的。为了使其线性可分,我们将添加几个列。我们添加的三个列是相似度列,它们记录了点之间的相似度。具有 x 坐标 x[1] 和 x[2] 的两个点之间的相似度测量为 e^((x[1] + x[2])²),如“在更高维中使用凸起以获得优势”部分所示。例如,点 1 和点 2 之间的相似度为 e^((−1 −0)²) = 0.368。在 Sim1 列中,我们将记录点 1 与其他三个点之间的相似度,依此类推。扩展后的数据集如表 11.7 所示。

表 11.7 我们通过添加三个新列扩展了表 11.6 中的数据集。每个列对应于所有点相对于每个点的相似度。这个扩展后的数据集存在于四维空间中,并且是线性可分的。

x 模拟 1 模拟 2 模拟 3 y
1 –1 1 0.368 0.018 1
2 0 0.368 1 0.368 0
3 1 0.018 0.368 1 1

这个扩展后的数据集现在是线性可分的!许多分类器可以分离这个集合,但特别是以下边界方程的分类器:

ŷ = step(Sim1 – Sim2 + Sim3)

让我们通过预测每个点的标签来验证,如下所示:

  • 点 1: ŷ = step(1 – 0.368 + 0.018) = step(0.65) = 1

  • 点 2: ŷ = step(0.368 – 1 + 0.368) = step(–0.264) = 0

  • 点 3: ŷ = step(0.018 – 0.368 + 1) = step(0.65) = 1

此外,因为 Sim1=e^((x + 1)²),Sim2=e^((x − 0)²),Sim3=e^((x − 1)²),所以我们的最终分类器做出以下预测:

ŷ = step(e^((x + 1)²) − e^(x²) + e^((x − 1)²))

现在,让我们在二维空间中执行相同的程序。本节不需要代码,但计算量很大,所以如果您想查看它们,可以在以下笔记本中找到:github.com/luisguiserrano/manning/blob/master/Chapter_11_Support_Vector_Machines/Calculating_similarities.ipynb.

表 11.8 一个简单的二维数据集,如图 11.28 所示。我们将使用具有 RBF 核的支持向量机来分类这个数据集。

x[1] x[2] y
1 0 0 0
2 –1 0 0
3 0 –1 0
4 0 1 1
5 1 0 1
6 –1 1 1
7 1 –1 1

考虑表 11.8 中的数据集,我们已将其图形化分类(图 11.22 和 11.23)。为了方便,它再次在图 11.28 中绘制。在这个图中,标签为 0 的点以正方形表示,标签为 1 的点以三角形表示。

注意,在表 11.8 的第一列和图 11.28 中,我们给每个点都编了号。这不是数据的一部分;我们这样做只是为了方便。现在我们将向这个表添加七个列。这些列是每个点相对于其他点的相似度。例如,对于点 1,我们添加一个名为 Sim1 的相似度列。这个列中每个点的条目是那个点与点 1 之间的相似度。让我们计算其中一个,例如,与点 6 的相似度。根据勾股定理,点 1 和点 6 之间的距离如下:

图片

图片

图 11.28 表 11.8 中数据集的图表,其中标签为 0 的点为正方形,标签为 1 的点为三角形。注意,正方形和三角形无法用一条线分开。我们将使用具有 RBF 核的支持向量机(SVM)用曲线边界将它们分开。

因此,相似度精确地是

similarity(point 1, point 6)= e(distance)(q,p^)² = e^(–2) = 0.135.

这个数字位于第 1 行和 Sim6 列(以及对称地,第 6 行和 Sim1 列)。在表中填写更多值以使你自己相信这是正确的,或者查看计算整个表的笔记本。结果如表 11.9 所示。

表 11.9 我们在表 11.8 的数据集中增加了七个相似度列。每一列都记录了与其他六个点的相似度。

x[1] x[2] Sim1 Sim2 Sim3 Sim4 Sim5 Sim6 Sim7 y
1 0 0 1 0.368 0.368 0.368 0.368 0.135 0.135 0
2 –1 0 0.368 1 0.135 0.135 0.018 0.368 0.007 0
3 0 –1 0.368 0.135 1 0.018 0.135 0.007 0.368 0
4 0 1 0.368 0.135 0.018 1 0.135 0.368 0.007 1
5 1 0 0.368 0.018 0.135 0.135 1 0.007 0.368 1
6 –1 1 0.135 0.368 0.007 0.367 0.007 1 0 1
7 1 –1 0.135 0.007 0.368 0.007 0.368 0 1 1

注意以下事项:

  1. 每个点与自身之间的相似度总是 1。

  2. 对于每对点,当它们在图中靠近时相似度较高,当它们相距较远时相似度较低。

  3. 由 Sim1 到 Sim7 列组成的表是对称的,因为点 p 和点 q 之间的相似度与点 q 和点 p 之间的相似度相同(因为它只取决于点 p 和点 q 之间的距离)。

  4. 点 6 和点 7 之间的相似度显示为 0,但实际上并非如此。点 6 和点 7 之间的距离是图片,因此它们的相似度为 e^(–8) = 0.00033546262,四舍五入后为 0,因为我们使用了三位有效数字。

现在,让我们继续构建我们的分类器!注意,对于小表 11.8 中的数据,没有线性分类器可以工作(因为点不能被一条线分开),但在具有更多特征(列)的更大表 11.9 中,我们可以拟合这样的分类器。我们继续拟合 SVM 到这些数据。许多 SVM 可以正确分类这个数据集,在笔记本中,我使用了 Turi Create 来构建一个。然而,一个更简单的分类器也可以工作。这个分类器有以下权重:

  • x[1] 和 x[2] 的权重为 0。

  • Sim p 的权重为 1,其中 p = 1, 2, 和 3。

  • Sim p 的权重为 -1,其中 p = 4, 5, 6, 和 7。

  • 偏差为 b = 0。

我们发现分类器将标签 -1 添加到对应于标签 0 的列,并将 +1 添加到对应于标签 1 的列。这相当于在标签 1 的任何点上添加一个山丘,在标签 0 的每个点上添加一个山谷,就像图 11.29 中所示。为了从数学上验证这一点,取表 11.7,加上 Sim4、Sim5、Sim6 和 Sim7 列的值,然后减去 Sim1、Sim2 和 Sim3 列的值。你会注意到前三个行得到一个负数,后四个行得到一个正数。因此,我们可以使用阈值 0,我们就有了一个可以正确分类这个数据集的分类器,因为标签 1 的点得到一个正分数,而标签 0 的点得到一个负分数。使用阈值 0 等同于使用图 11.29 中图表的 coastline 来分离点。

如果我们插入相似性函数,我们获得的分类器如下:

ŷ = step(−e^(x[1]² + x[2] ²) −e^((x[1] + 1)² + x[2]²) −e^(x[1]² + (x[2] + 1)²) +e^(x[1]² + (x[2] − 1)²) +e^((x[1] − 1)² + x[2]²) +e^((x[1] + 1)² + (x[2] − 1)²) +e^((x[1] − 1)² + (x[2] + 1)²))

总结来说,我们找到了一个非线性可分的数据集。我们使用径向基函数和点之间的相似性向数据集中添加了几个列。这有助于我们在更高维的空间中构建一个线性分类器。然后我们将高维线性分类器投影到平面上,以获得我们想要的分类器。我们可以在图 11.29 中看到得到的曲线分类器。

图 11.29 在这个数据集中,我们升高了每个三角形并降低了每个正方形。然后我们在高度 0 处绘制了一个平面,该平面将正方形和三角形分开。该平面在曲面上形成一个曲线边界。然后我们将所有内容投影回二维,这个曲线边界就是将我们的三角形和正方形分开的边界。边界在右侧绘制。(来源:由 Golden Software, LLC 的 Grapher™ 辅助创建的图像;www.goldensoftware.com/products/grapher)。

使用 RBF 内核的过拟合和欠拟合:gamma 参数

在本节的开始部分,我们提到存在许多不同的径向基函数,即平面上的每一个点对应一个。实际上还有更多。其中一些在平面上某一点抬起并形成一个狭窄的表面,而另一些则形成一个宽大的表面。一些例子可以在图 11.30 中看到。在实践中,我们希望调整径向基函数的宽度。为此,我们使用一个称为gamma 参数的参数。当 gamma 值较小时,形成的表面非常宽,而当它较大时,表面则非常狭窄。

图 11.30 gamma 参数决定了表面的宽度。注意,对于小的 gamma 值,表面非常宽,而对于大的 gamma 值,表面则非常狭窄。(来源:由 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher

Gamma 是一个超参数。回想一下,超参数是我们用来训练模型的具体规格。我们调整这个超参数的方法是使用我们之前看到的方法,例如模型复杂度图(第四章中的“决定模型复杂度的数值方法”部分)。不同的 gamma 值往往会过拟合或欠拟合。让我们回顾一下本节开头提到的三个不同 gamma 值的例子。这三个模型在图 11.31 中进行了展示。

图 11.31 展示了使用 RBF 核和不同 gamma 值的三个 SVM 分类器。(来源:由 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher

注意,对于非常小的 gamma 值,模型会过拟合,因为曲线过于简单,并且不能很好地对数据进行分类。对于较大的 gamma 值,模型会严重过拟合,因为它为每个三角形构建一个小山,为每个正方形构建一个小山谷。这使得它几乎将所有东西都分类为正方形,除了三角形周围的区域。中等值的 gamma 似乎效果很好,因为它构建了一个足够简单且能正确分类点的边界。

当我们添加 gamma 参数时,径向基函数的方程变化不大——我们只需将指数乘以 gamma。在一般情况下,径向基函数的方程如下:

y = e^(−γ[(x[1] − p[1])²+ ··· +( x[n] + p[n])²])

不要过于担心学习这个公式——只需记住,即使在更高维的情况下,我们创建的峰值可以是宽的或窄的。通常,有一种方法可以编码它并使其工作,这就是我们在下一节中要做的。

编写核方法

现在我们已经学习了 SVM 的核方法,接下来我们将在 Scikit-Learn 中编写代码,并使用多项式和 RBF 核在一个更复杂的数据集上训练模型。要在 Scikit-Learn 中使用特定核训练 SVM,我们只需在定义 SVM 时添加核作为参数即可。本节的代码如下:

  • 注意: SVM_graphical_example.ipynb

  • 数据集:

    • one_circle.csv

    • two_circles.csv

编写多项式核以分类圆形数据集

在本小节中,我们将学习如何在 Scikit-Learn 中编写多项式核的代码。为此,我们使用一个名为 one_circle.csv 的数据集,如图 11.32 所示。

图片

图 11.32 一个圆形数据集,包含一些噪声。我们将使用具有多项式核的 SVM 来分类这个数据集。

注意到除了少数异常值外,这个数据集基本上是圆形的。我们训练了一个 SVM 分类器,其中我们指定 kernel 参数为 polydegree 参数为 2,如下一代码片段所示。我们想要 degree 为 2 的原因是圆的方程是一个二次多项式。结果如图 11.33 所示。

svm_degree_2 = SVC(kernel='poly', degree=2)
svm_degree_2.fit(features, labels)

图片

图 11.33 具有二次多项式核的 SVM 分类器

注意到这个具有二次多项式核的 SVM 成功构建了一个大致圆形的区域来包围数据集,正如预期的那样。

编写 RBF 核以分类由两个相交圆组成的数据集,并调整 gamma 参数

我们已经画了一个圆,但让我们变得更复杂一些。在本小节中,我们将学习如何编写几个具有 RBF 核的 SVM 来分类具有两个相交圆形状的数据集。这个数据集称为 two_circles.csv,如图 11.34 所示。

图片

图 11.34 由两个相交圆组成的数据集,包含一些异常值。我们将使用具有 RBF 核的 SVM 来分类这个数据集。

要使用 RBF 核,我们指定 kernel = 'rbf'。我们还可以指定一个 gamma 的值。我们将训练四个不同的 SVM 分类器,gamma 的值分别为 0.1、1、10 和 100,如以下所示:

svm_gamma_01 = SVC(kernel='rbf', gamma=0.1)  ❶
svm_gamma_01.fit(features, labels)

svm_gamma_1 = SVC(kernel='rbf', gamma=1)     ❷
svm_gamma_1.fit(features, labels)

svm_gamma_10 = SVC(kernel='rbf', gamma=10)   ❸
svm_gamma_10.fit(features, labels)

svm_gamma_100 = SVC(kernel='rbf', gamma=100) ❹
svm_gamma_100.fit(features, labels)

❶ Gamma = 0.1

❷ Gamma = 1

❸ Gamma = 10

❹ Gamma = 100

图片

图 11.35 具有不同 gamma 值的 RBF 核的四个 SVM 分类器

四个分类器出现在图 11.35 中。注意,当 gamma = 0.1 时,模型略微欠拟合,因为它认为边界是一个椭圆形,并犯了一些错误。gamma = 1 提供了一个很好的模型,能够很好地捕捉数据。当我们达到 gamma = 10 时,我们可以看到模型开始过拟合。注意它如何尝试正确分类每个点,包括异常值,它单独包围每个异常值。当我们达到 gamma=100 时,我们可以看到一些严重的过拟合。这个分类器只围绕每个三角形用一个小的圆形区域,并将其他所有内容分类为正方形。因此,对于这个模型,gamma = 1 在我们尝试的值中似乎是最好的。

摘要

  • 支持向量机(SVM)是一种分类器,它由拟合两条平行线(或超平面)组成,并尝试使它们尽可能远地分开,同时仍然尝试正确分类数据。

  • 构建支持向量机的方法是使用一个包含两个项的错误函数:两个感知器错误的和,每个平行线一个,以及距离错误,当两条平行线相距较远时距离错误较高,当它们靠得很近时距离错误较低。

  • 我们使用 C 参数来调节在正确分类点和尝试使线间距最大化之间的平衡。这在训练时很有用,因为它让我们对我们的偏好有了控制权,即如果我们想构建一个能够很好地分类数据的分类器,或者一个具有良好间距边界的分类器。

  • 核方法是一种构建非线性分类器的有用且非常强大的工具。

  • 核方法包括使用函数帮助我们将数据集嵌入到更高维的空间中,在那里点可能更容易用线性分类器进行分类。这相当于以巧妙的方式向我们的数据集添加列,使增强后的数据集线性可分。

  • 有几种不同的核函数可用,例如多项式核和径向基函数核。多项式核允许我们构建如圆、抛物线和双曲线等多项式区域。径向基函数核允许我们构建更复杂的曲线区域。

练习

练习 11.1

(此练习完成了“距离错误函数”部分所需的计算。)

证明具有方程 w[1]x[1] + w[2]x[1] + b = 1 和 w[1]x[1] + w[2]x[1] + b = –1 的两条线的距离恰好是

练习 11.2

正如我们在练习 5.3 中所学,不可能构建一个模仿 XOR 门的感知器模型。换句话说,不可能用感知器模型(以 100% 的准确率)拟合以下数据集:

x[1] x[2] y
0 0 0
0 1 1
1 0 1
1 1 0

这是因为数据集不是线性可分的。支持向量机(SVM)也有同样的问题,因为 SVM 也是一个线性模型。然而,我们可以使用核函数来帮助我们。我们应该使用什么核函数将这个数据集转换为线性可分的数据集?转换后的 SVM 会是什么样子?

提示:查看“利用多项式方程为你带来好处”部分中的示例 2,它解决了一个非常类似的问题。

12 结合模型以最大化结果:集成学习

在本章中

  • 集成学习的概念以及它是如何将弱分类器组合成更强的分类器的

  • 使用随机森林以随机方式组合分类器

  • 使用提升方法以更巧妙的方式组合分类器

  • 一些最受欢迎的集成方法:随机森林、AdaBoost、梯度提升和 XGBoost

图片

在学习了众多有趣且实用的机器学习模型之后,自然会想知道是否可以将这些分类器结合起来。幸运的是,我们可以做到,在本章中,我们将学习几种通过组合弱模型来构建更强模型的方法。本章中我们学习的两种主要方法是集成学习和提升学习。简而言之,集成学习是通过随机构建几个模型并将它们结合起来。而提升学习则通过战略性地选择每个模型来关注前一个模型的错误,以更智能地构建这些模型。这些集成方法在重要的机器学习问题中展现出的结果是巨大的。例如,Netflix Prize,该奖项授予了最适合 Netflix 大量观众数据的最佳模型,由一个使用不同模型集成的团队赢得。

在本章中,我们学习了一些最强大和最受欢迎的集成学习和提升模型,包括随机森林、AdaBoost、梯度提升和 XGBoost。其中大部分模型用于分类,而一些则用于回归。然而,大多数集成方法在这两种情况下都适用。

一些术语:在这本书中,我们根据其任务将机器学习模型称为模型,有时也称为回归器或分类器。在本章中,我们引入了“学习器”这个术语,它也指代机器学习模型。在文献中,当谈论集成方法时,常用“弱学习器”和“强学习器”这两个术语。然而,机器学习模型和学习者之间并没有区别。

本章的所有代码都可在以下 GitHub 仓库中找到:github.com/luisguiserrano/manning/tree/master/Chapter_12_Ensemble_Methods

在朋友的帮助下

让我们通过以下类比来可视化集成方法:想象一下,我们必须参加一个包含 100 个不同主题(包括数学、地理、科学、历史和音乐)的真假题考试。幸运的是,我们可以叫上我们的五个朋友——Adriana、Bob、Carlos、Dana 和 Emily——来帮助我们。有一个小限制,就是他们都全职工作,没有时间回答所有 100 个问题,但他们非常乐意帮助我们回答其中的一部分。我们可以使用什么技术来获取他们的帮助?以下有两种可能的技术:

技术 1:对于每个朋友,挑选几个随机问题,并让他们回答这些问题(确保每个问题至少有一个朋友回答)。在得到回应后,通过选择那些回答该问题的人中最受欢迎的选项来回答测试。例如,如果我们的两个朋友在问题 1 上回答“True”,一个朋友回答“False”,那么我们回答问题 1 为“True”(如果有平局,我们可以随机选择一个获胜的回应)。

技术 2:我们给 Adriana 考试,并要求她只回答她最有把握的问题。我们假设这些答案是好的,并将它们从测试中删除。现在我们将剩余的问题给 Bob,并给予相同的指示。我们继续这样做,直到将问题传递给所有五个朋友。

技术 1 类似于 Bagging 算法,技术 2 类似于 Boosting 算法。更具体地说,Bagging 和 Boosting 使用一组称为“弱学习器”的模型,并将它们组合成一个“强学习器”(如图 12.1 所示)。

图片

图 12.1 集成方法包括将多个弱学习器结合在一起构建强学习器。

Bagging:通过从数据集中随机抽取点(有放回)来构建随机集合。在每个集合上训练不同的模型。这些模型是弱学习器。然后,通过投票(如果是分类模型)或平均预测(如果是回归模型)来形成强学习器。

Boosting:首先训练一个随机模型,这是第一个弱学习器。在整个数据集上评估它。缩小预测良好的点,放大预测不良的点。在这个修改后的数据集上训练第二个弱学习器。我们继续这样做,直到构建了几个模型。将它们组合成强学习器的方式与 Bagging 相同,即通过投票或平均弱学习器的预测。更具体地说,如果学习器是分类器,强学习器预测弱学习器预测的最常见的类别(因此称为“投票”),如果有平局,则随机选择。如果学习器是回归器,强学习器预测弱学习器给出的预测的平均值。

本章中的大多数模型都使用决策树(无论是回归还是分类)作为弱学习器。我们这样做是因为决策树非常适合这种类型的方法。然而,当你阅读本章时,我鼓励你思考如何结合其他类型的模型,例如感知器和 SVMs。

我们花费了一整本书的时间构建非常好的学习器。为什么我们想要结合多个弱学习器,而不是一开始就构建一个强学习器呢?一个原因是集成方法已被证明比其他模型过度拟合的情况要少得多。简而言之,一个模型过度拟合是很容易的,但如果你有多个针对同一数据集的模型,它们的组合过度拟合会更少。从某种意义上说,如果有一个学习器犯了错误,其他的学习器往往会纠正它,并且平均来说,它们会表现得更好。

在本章中,我们学习了以下模型。第一个是一个集成算法,最后三个是提升算法:

  • 随机森林

  • AdaBoost

  • 梯度提升

  • XGBoost

所有这些模型都适用于回归和分类。为了教育目的,我们将前两个作为分类模型学习,后两个作为回归模型学习。分类和回归的过程类似。然而,请阅读每个模型,并想象它们在两种情况下是如何工作的。要了解所有这些算法在分类和回归中的工作方式,请参阅附录 C 中链接的视频和阅读材料,其中详细解释了这两种情况。

集成:随机地将一些弱学习器组合起来构建强学习器

在本节中,我们将看到最著名的集成模型之一:随机森林。在随机森林中,弱学习器是在数据集的随机子集上训练的小决策树。随机森林在分类和回归问题中都表现良好,其过程类似。我们将通过一个分类示例来了解随机森林。本节的代码如下:

我们使用了一个小的垃圾邮件和正常邮件数据集,类似于我们在第八章中使用朴素贝叶斯模型时使用的数据集。数据集显示在表 12.1 中,并在图 12.2 中绘制。数据集的特征是电子邮件中“彩票”和“销售”单词出现的次数,而“是/否”标签表示电子邮件是否为垃圾邮件(是)或正常邮件(否)。

表 12.1 邮件垃圾邮件和正常邮件表,以及每个邮件中“彩票”和“销售”单词出现的次数

Lottery Sale Spam
7 8 1
3 2 0
8 4 1
2 6 0
6 5 1
9 6 1
8 5 0
7 1 0
1 9 1
4 7 0
1 3 0
3 10 1
2 2 1
9 3 0
5 3 0
10 1 0
5 9 1
10 8 1

图 12.2 图 12.2 表 12.1 中数据集的绘图。垃圾邮件用三角形表示,正常邮件用正方形表示。水平和垂直轴分别表示“彩票”和“销售”单词出现的次数。

首先,对决策树进行(过度)拟合

在我们深入了解随机森林之前,让我们将决策树分类器拟合到这些数据上,看看它的表现如何。因为我们已经在第九章中学过这个内容,图 12.3 只显示了最终结果,但我们可以在笔记本中看到代码。在图 12.3 的左侧,我们可以看到实际的树(相当深!),在右侧,我们可以看到由这个决策树定义的边界。注意,它很好地拟合了数据集,训练准确率达到 100%,尽管它显然过拟合了。过拟合可以在模型试图正确分类的两个异常值上观察到,而没有注意到它们是异常值。

图片

图 12.3 左:一个分类我们的数据集的决策树。右:由这个决策树定义的边界。注意,它很好地分割了数据,尽管它暗示了过拟合,因为一个好的模型会将两个孤立点视为异常值,而不是试图正确地分类它们。

在接下来的几节中,我们将看到如何通过拟合随机森林来解决这个过拟合问题。

手动拟合随机森林

在本节中,我们将学习如何手动拟合随机森林,尽管这只是为了教育目的,因为在实际操作中并不是这样做的。简而言之,我们从数据集中随机选择子集,并在每个子集上训练一个弱学习器(决策树)。一些数据点可能属于多个子集,而另一些可能不属于任何子集。这些子集的组合构成了我们的强学习器。强学习器进行预测的方式是通过让弱学习器进行投票。对于这个数据集,我们使用三个弱学习器。因为数据集有 18 个点,所以让我们考虑三个包含 6 个数据点的子集,如图 12.4 所示。

图片

图 12.4 构建随机森林的第一步是将我们的数据分成三个子集。这是图 12.2 中所示的数据集分割。

接下来,我们继续构建我们的三个弱学习器。在每个子集上拟合一个深度为 1 的决策树。回想一下第九章的内容,深度为 1 的决策树只包含一个节点和两个叶子。它的边界由一条尽可能好地分割数据集的单个水平或垂直线组成。弱学习器如图 12.5 所示。

图片

图 12.5 形成我们的随机森林的三个弱学习器都是深度为 1 的决策树。每个决策树拟合图 12.4 中对应的三个子集之一。

我们通过投票将这些组合成一个强学习器。换句话说,对于任何输入,每个弱学习器预测一个 0 或 1 的值。强学习器做出的预测是三个中最常见的输出。这种组合可以在图 12.6 中看到,其中弱学习器在顶部,强学习器在底部。

注意,随机森林是一个好的分类器,因为它正确分类了大多数点,但它允许一些错误,以避免过度拟合数据。然而,我们不需要手动训练这些随机森林,因为 Scikit-Learn 有相应的函数,我们将在下一节中看到。

图片

图 12.6 获得随机森林预测的方法是通过结合三个弱学习者的预测。在顶部,我们可以看到图 12.5 中的三个决策树边界。在底部,我们可以看到三个决策树如何投票以获得相应随机森林的边界。

在 Scikit-Learn 中训练随机森林

在本节中,我们展示了如何使用 Scikit-Learn 训练随机森林。在下面的代码中,我们使用了RandomForestClassifier包。首先,我们的数据存储在两个名为featureslabels的 Pandas DataFrame 中,如下所示:

from sklearn.ensemble import RandomForestClassifier
random_forest_classifier = RandomForestClassifier(random_state=0, n_estimators=5, max_depth=1)
random_forest_classifier.fit(features, labels)
random_forest_classifier.score(features, labels)

在之前的代码中,我们指定了想要五个弱学习者,使用n_estimators超参数。这些弱学习者再次是决策树,我们使用max_depth超参数指定了它们的深度为 1。模型的图示如图 12.7 所示。注意这个模型犯了一些错误,但设法找到了一个好的边界,其中垃圾邮件是那些有很多“彩票”和“销售”等词语出现的邮件(图示的右上角),而 ham 邮件是那些这些词语出现不多的邮件(图示的左下角)。

图片

图 12.7 使用 Scikit-Learn 获得的随机森林边界。请注意,它很好地分类了数据集,并将两个误分类的点视为异常值,而不是尝试正确分类它们。

Scikit-Learn 还允许我们可视化和绘制单个弱学习者(请参阅笔记本中的代码)。弱学习者如图 12.8 所示。请注意,并非所有弱学习者都有用。例如,第一个将每个点分类为 ham。

图片

图 12.8 通过 Scikit-Learn 获得的五个弱学习者组成的随机森林。每个都是一个深度为 1 的决策树。它们组合形成了图 12.7 中显示的强学习者。

在本节中,我们使用了深度为 1 的决策树作为弱学习者,但通常,我们可以使用任何深度的树。尝试通过改变max_depth超参数重新训练此模型,使用更深的决策树,看看随机森林会是什么样子!

AdaBoost:以巧妙的方式结合弱学习者以构建强学习者

提升与袋装类似,我们通过结合多个弱学习器来构建一个强学习器。不同之处在于,我们不是随机选择弱学习器。相反,每个学习器都是通过关注先前学习器的弱点来构建的。在本节中,我们将学习一种由 Freund 和 Schapire 于 1997 年开发的强大提升技术,称为 AdaBoost(参见附录 C 获取参考文献)。AdaBoost 代表自适应提升,它适用于回归和分类。然而,我们将在一个分类示例中使用它,该示例非常清楚地说明了训练算法。

在 AdaBoost 中,就像在随机森林中一样,每个弱学习器都是深度为 1 的决策树。与随机森林不同,每个弱学习器都是在整个数据集上训练的,而不是在数据集的一部分上。唯一的注意事项是,在每个弱学习器训练后,我们通过扩大被错误分类的点来修改数据集,这样未来的弱学习器就会更加关注这些点。简而言之,AdaBoost 的工作原理如下:

训练 AdaBoost 模型的伪代码

  • 在第一个数据集上训练第一个弱学习器。

  • 对每个新的弱学习器重复以下步骤:

    • 在训练完一个弱学习器后,点被修改如下:

      • 被错误分类的点被放大。
    • 在这个修改后的数据集上训练一个新的弱学习器。

在本节中,我们通过一个示例更详细地开发这个伪代码。我们使用的这个数据集有两个类别(三角形和正方形),如图 12.9 所示。

图 12-9

图 12.9 我们将使用 AdaBoost 进行分类的数据集。它有两个标签,分别由三角形和正方形表示。

AdaBoost 的总体图景:构建弱学习器

在接下来的两个小节中,我们将看到如何构建一个 AdaBoost 模型来拟合图 12.9 中所示的数据集。首先,我们构建将组合成一个强学习器的弱学习器。

第一步是将每个点的权重分配为 1,如图 12.10 的左侧所示。接下来,我们在该数据集上构建一个弱学习器。回想一下,弱学习器是深度为 1 的决策树。深度为 1 的决策树对应于最佳分割点的水平或垂直线。有几个这样的树可以完成这项工作,但我们将选择一个——图 12.10 中间所示的垂直线,它正确分类了左侧的两个三角形和右侧的五个正方形,并错误分类了右侧的三个三角形。下一步是将三个错误分类的点放大,以便在未来的弱学习器眼中赋予它们更多的重视。为了放大它们,回想一下,每个点最初有一个权重为 1。我们定义这个弱学习器的缩放因子为正确分类的点数除以错误分类的点数。在这种情况下,缩放因子是 7/3 = 2.33。我们继续通过这个缩放因子将每个错误分类的点进行缩放,如图 12.10 的右侧所示。

图片

图 12.10 AdaBoost 模型第一个弱学习器的拟合。左侧:数据集,其中每个点被分配一个权重为 1。中间:最佳拟合此数据集的弱学习器。右侧:缩放后的数据集,我们通过 7/3 的缩放因子放大了错误分类的点。

现在我们已经构建了第一个弱学习器,我们以同样的方式构建下一个。第二个弱学习器如图 12.11 所示。图象的左侧是缩放后的数据集。第二个弱学习器是最佳拟合这个数据集的弱学习器。我们这是什么意思呢?因为点有不同的权重,我们希望弱学习器中正确分类的点的权重之和是最高的。这个弱学习器是

图 12.11 中间的水平线。我们现在继续计算缩放因子。我们需要稍微修改其定义,因为现在点有权重。缩放因子是正确分类的点的权重之和与错误分类的点的权重之和的比率。第一个项是 2.33 + 2.33 + 2.33 + 1 + 1 + 1 + 1 = 11,第二个是 1 + 1 + 1 = 3。因此,缩放因子是 11/3 = 3.67。我们继续将三个错误分类的点的权重乘以这个 3.67 的因子,如图 12.11 的右侧所示。

图片

图 12.11 AdaBoost 模型第二个弱学习器的拟合。左侧:图 12.10 中的缩放后的数据集。中间:最佳拟合缩放后数据集的弱学习器——这意味着,对于正确分类的点的权重之和最大的弱学习器。右侧:新的缩放后的数据集,我们通过 11/3 的缩放因子放大了错误分类的点。

我们继续这样下去,直到我们构建了我们想要的那么多弱学习器。对于这个例子,我们只构建了三个弱学习器。第三个弱学习器是一条垂直线,如图 12.12 所示。

图片

图 12.12 AdaBoost 模型中第三个弱学习器的拟合。左:来自图 12.11 的缩放数据集。右:最适合此缩放数据集的弱学习器。

这就是我们构建弱学习器的方法。现在,我们需要将它们组合成一个强学习器。这与我们使用随机森林所做的方法类似,但需要使用更多的数学知识,如下一节所示。

将弱学习器组合成强学习器

现在我们已经构建了弱学习器,在本节中,我们学习了一种有效的方法将它们组合成一个强学习器。想法是让分类器进行投票,就像它们在随机森林分类器中所做的那样,但这次,好的学习器比差的学习器有更多的发言权。如果分类器真的很差,那么它的投票实际上将是负数。

要理解这一点,想象我们有三个朋友:真诚的特蕾莎、不可预测的乌尔伯特和说谎的伦尼。真诚的特蕾莎几乎总是说实话,说谎的伦尼几乎总是撒谎,不可预测的乌尔伯特大约一半的时间说实话,另一半时间撒谎。在这三个朋友中,哪一个是最没有用的?

我认为,真诚的特蕾莎非常可靠,因为她几乎总是说实话,所以我们可以信任她。在其他人中,我更喜欢说谎的伦尼。如果他几乎总是在我们问他一个是非问题时撒谎,我们就简单地接受他告诉我们的相反事实,我们大多数时候都会是正确的!另一方面,如果我们不知道不可预测的乌尔伯特是在说实话还是在撒谎,那么他对我们来说就没有任何用处。在这种情况下,如果我们给每个朋友说的话分配一个分数,我会给真诚的特蕾莎一个很高的正分,给说谎的伦尼一个很高的负分,给不可预测的乌尔伯特一个零分。

现在想象我们的三个朋友是在一个有两个类别的数据集上训练的弱学习器。真诚的特蕾莎是一个准确率非常高的分类器,说谎的伦尼是一个准确率非常低的分类器,不可预测的乌尔伯特是一个准确率接近 50%的分类器。我们想要构建一个强学习器,其预测是通过三个弱学习器的加权投票得到的。因此,我们给每个弱学习器分配一个分数,这就是学习者的投票在最终投票中将占多少权重。此外,我们想要以下方式分配这些分数:

  • 真诚的特蕾莎分类器得到一个很高的正分。

  • 不可预测的乌尔伯特分类器得到一个接近零的分数。

  • 说谎的伦尼分类器得到一个很高的负分。

换句话说,弱学习器的分数是一个具有以下属性的数字:

  1. 当学习器的准确率大于 0.5 时为正数

  2. 当模型的准确率为 0.5 时为 0

  3. 当学习者的准确度小于 0.5 时,它是负数

  4. 当学习者的准确度接近 1 时,它是一个大的正数

  5. 当学习者的准确度接近 0 时,它是一个大的负数

为了得到一个满足上述 1-5 个特性的弱学习者的良好得分,我们使用概率论中的一个流行概念,称为logit对数概率,我们将在下面讨论。

概率、赔率和对数概率

你可能在赌博中看到过,人们从不提及概率,但他们总是谈论赔率。这些赔率是什么?在以下意义上,它们与概率相似:如果我们多次运行一个实验并记录特定结果发生的次数,那么这个结果发生的概率是它发生的次数除以我们运行实验的总次数。这个结果的对数概率是它发生的次数除以它没有发生的次数。

例如,掷骰子得到 1 的概率是 1/6,但赔率是 1/5。如果一匹马在每四场比赛中赢三场,那么这匹马赢得比赛的概率是 3/4,赔率是 3/1=3。赔率的公式很简单:如果事件的概率是x,那么赔率是。例如,在骰子例子中,概率是 1/6,赔率是

.

请注意,因为概率是一个介于 0 和 1 之间的数,所以赔率是一个介于 0 和∞之间的数。

现在让我们回到我们的原始目标。我们正在寻找一个满足上述 1-5 个特性的函数。概率函数很接近,但还不够,因为它只输出正值。将概率转换为满足上述 1-5 个特性的函数的方法是取对数。因此,我们得到了对数概率,也称为 logit,其定义如下:

图 12.13 显示了对数概率函数的图像。请注意,这个函数满足 1-5 个特性。

因此,我们所需做的就是使用对数概率函数来计算每个弱学习者的得分。我们将此对数概率函数应用于准确度。表 12.2 包含了一些弱学习者的准确度和该准确度的对数概率。请注意,正如预期的那样,准确度高的模型具有高的正得分,准确度低的模型具有高的负得分,而准确度接近 0.5 的模型得分接近 0。

图 12.13 该曲线显示了对数概率函数相对于准确度的图像。请注意,对于准确度较小的值,对数概率是一个非常大的负数,而对于准确度较高的值,它是一个非常大的正数。当准确度为 50%(或 0.5)时,对数概率恰好为零。

表 12.2 展示了弱分类器的几个准确度值,以及使用对数几率计算出的相应分数。请注意,准确度非常低的模型会得到很大的负分数,准确度非常高的值会得到很大的正分数,而接近 0.5 的准确度值会得到接近 0 的分数。

准确度 对数几率(弱学习器的分数)
0.01 –4.595
0.1 –2.197
0.2 –1.386
0.5 0
0.8 1.386
0.9 2.197
0.99 4.595

组合分类器

既然我们已经确定对数几率是定义所有弱学习器分数的方法,我们就可以继续将它们组合起来构建强学习器。回想一下,弱学习器的准确度是正确分类的点分数总和除以所有点分数总和,如图 12.10-12.12 所示。

  • 弱学习器 1:

    图片

  • 弱学习器 2:

    图片

  • 弱学习器 3:

    图片

强学习器的预测是通过弱分类器的加权投票得到的,其中每个分类器的投票是其分数。一个简单的方法是将弱学习器的预测值从 0 和 1 改为-1 和 1,并将每个预测值乘以弱学习器的分数,然后相加。如果得到的预测值大于或等于零,则强学习器预测为 1,如果为负,则预测为 0。投票过程如图 12.14 所示,预测结果如图 12.15 所示。注意,在图 12.15 中,得到的分类器正确地分类了数据集中的每个点。

图片

图 12.14 展示了如何在 AdaBoost 模型中将弱学习器组合成一个强学习器。我们使用对数几率对每个弱学习器进行评分,并根据它们的评分进行投票(评分越高,该学习器的投票权重越大)。底部图中的每个区域都包含弱学习器评分的总和。请注意,为了简化计算,弱学习器的预测值是+1 和-1,而不是 1 和 0。

图片

图 12.15 展示了如何获得 AdaBoost 模型的预测。一旦我们添加了来自弱学习器的分数(如图 12.14 所示),如果分数总和大于或等于 0,则分配预测值为 1,否则分配预测值为 0。

在 Scikit-Learn 中编码 AdaBoost

在本节中,我们将展示如何使用 Scikit-Learn 训练一个 AdaBoost 模型。我们使用与“手动拟合随机森林”部分相同的垃圾邮件数据集进行训练,并在图 12.16 中进行了绘制。我们继续使用上一节中的以下笔记本:

图片

图 12.16 在此数据集中,我们使用 Scikit-Learn 训练一个 AdaBoost 分类器。这是来自“Bagging”部分的相同垃圾邮件数据集,其中特征是单词“彩票”和“垃圾邮件”出现的次数,垃圾邮件用三角形表示,正常邮件用正方形表示。

数据集包含两个 Pandas DataFrame,分别称为featureslabels。训练使用 Scikit-Learn 中的AdaBoostClassifier包完成。我们指定此模型将使用六个弱学习器,如以下所示:

from sklearn.ensemble import AdaBoostClassifier
adaboost_classifier = AdaBoostClassifier(n_estimators=6)
adaboost_classifier.fit(features, labels)
adaboost_classifier.score(features, labels)

结果模型的边界在图 12.17 中绘制。

我们可以更进一步,探索六个弱学习者和它们的分数(参见笔记本中的代码)。它们的边界在图 12.18 中绘制,并且正如笔记本中所示,所有弱学习者的分数都是 1。

图片

图 12.17 AdaBoost 分类器在图 12.16 的垃圾邮件数据集上的结果。请注意,分类器很好地拟合了数据集,并且没有过度拟合太多。

图片

图 12.18 我们 AdaBoost 模型中的六个弱学习器。每一个都是深度为 1 的决策树。它们组合成图 12.17 中的强学习器。

注意,图 12.17 中的强学习器是通过将图 12.18 中每个弱学习者的分数设为 1,并让它们投票得到的。

梯度提升:使用决策树构建强大的学习器

在本节中,我们讨论梯度提升,这是目前最受欢迎且最成功的机器学习模型之一。梯度提升与 AdaBoost 类似,因为弱学习器是决策树,每个弱学习器的目标是从前一个学习者的错误中学习。梯度提升与 AdaBoost 之间的一个区别在于,在梯度提升中,我们允许决策树的深度超过 1。梯度提升可用于回归和分类,但为了清晰起见,我们使用回归示例。要了解更多信息,请查看附录 C 中视频和阅读材料的链接。本节的代码如下:

我们使用的例子与第九章中“决策树回归”部分相同的例子,我们在其中研究了某些用户与一个应用的互动程度。特征是用户的年龄,标签是用户与应用互动的天数(表 12.3)。数据集的图示在图 12.19 中。

图片

图 12.19 表 12.3 中用户互动数据集的图示。横轴代表用户的年龄,纵轴代表用户每周使用我们应用的天数。

表 12.3 一个包含八个用户、他们的年龄以及他们与我们应用的互动的小数据集。互动是通过他们在一周内打开应用的天数来衡量的。我们将使用梯度提升来拟合这个数据集。

特征(年龄) 标签(互动)
10 7
20 5
30 7
40 1
50 2
60 1
70 5
80 4

梯度提升的想法是我们将创建一个适合这个数据集的树序列。我们现在将使用的两个超参数是树的数目,我们将其设置为五,以及学习率,我们将其设置为 0.8。第一个弱学习器很简单:它是最适合数据集的深度为 0 的决策树。深度为 0 的决策树只是一个将相同的标签分配给数据集中每个点的节点。因为我们最小化的误差函数是均方误差,所以这个预测的最优值是标签的平均值。这个数据集标签的平均值是 4,所以我们的第一个弱学习器是一个将预测值 4 分配给每个点的节点。

下一步是计算残差,即标签与第一个弱学习器做出的预测之间的差异,并拟合一个新的决策树来这些残差。正如你所看到的,这是在训练一个决策树来填补第一个树留下的空缺。标签、预测和残差在表 12.4 中显示。

第二个弱学习器是一个拟合这些残差的树。树可以深到我们想要的程度,但在这个例子中,我们将确保所有弱学习器的深度最多为 2。这棵树在图 12.20(及其边界)中显示,其预测在表 12.4 的最右边列中。这棵树是使用 Scikit-Learn 获得的;请参阅笔记本以获取过程。

图片

图 12.20 梯度提升模型中的第二个弱学习器。这个学习器是一个深度为 2 的决策树,如图左所示。这个弱学习器的预测在右边的图上显示。

表 12.4 第一个弱学习器的预测是标签的平均值。第二个弱学习器被训练来拟合第一个弱学习器的残差。

特征(年龄) 标签(互动) 弱学习器 1 的预测 残差 弱学习器 2 的预测
10 7 4 3 3
20 5 4 2 2
30 7 4 3 2
40 1 4 –3 –2.667
50 2 4 –2 –2.667
60 1 4 –3 –2.667
70 5 4 1 0.5
80 4 4 0 0.5

这种想法是继续这种方式,计算新的残差并训练一个新的弱学习器来拟合这些残差。然而,有一个小的注意事项——为了计算前两个弱学习器的预测值,我们首先将第二个弱学习器的预测值乘以学习率。回想一下,我们使用的学习率是 0.8。因此,前两个弱学习器的组合预测是第一个的预测值(4)加上第二个预测值的 0.8 倍。我们这样做是因为我们不希望过度拟合,即训练数据拟合得太好。我们的目标是模仿梯度下降算法,通过逐渐接近解,这就是我们通过乘以学习率所实现的。新的残差是原始标签减去前两个弱学习器的组合预测。这些在表 12.5 中计算。

表 12.5 标签、前两个弱学习器的预测值以及残差。第一个弱学习器的预测值是标签的平均值。第二个弱学习器的预测值显示在图 12.20 中。组合预测等于第一个弱学习器的预测值加上学习率(0.8)乘以第二个弱学习器的预测值。残差是标签与第一个两个弱学习器的组合预测之间的差值。

标签 弱学习器 1 的预测值 弱学习器 2 的预测值 弱学习器 2 乘以学习率的预测值 弱学习器 1 和 2 的预测值 残差
7 4 3 2.4 6.4 0.6
5 4 2 1.6 5.6 –0.6
7 4 2 1.6 5.6 1.4
1 4 –2.667 –2.13 1.87 –0.87
2 4 –2.667 –2.13 1.87 0.13
1 4 –2.667 –2.13 1.87 –0.87
5 4 0.5 0.4 4.4 0.6
4 4 0.5 0.4 4.4 –0.4

现在我们可以继续在新的残差上拟合一个新的弱学习器,并计算前两个弱学习器的组合预测。我们通过将第一个弱学习器的预测值加上 0.8(学习率)乘以第二个和第三个弱学习器的预测值之和来获得这个值。我们为想要构建的每个弱学习器重复这个过程。我们可以使用 Scikit-Learn 中的GradientBoostingRegressor包(代码在笔记本中)来代替手动操作。接下来的几行代码显示了如何拟合模型并进行预测。请注意,我们已将树的深度设置为最多 2 层,树的数量设置为 5,学习率设置为 0.8。用于此的超参数是max_depthn_estimatorslearning_rate。此外,请注意,如果我们想要五棵树,我们必须将n_estimators超参数设置为 4,因为第一棵树不计入。

from sklearn.ensemble import GradientBoostingRegressor
gradient_boosting_regressor = GradientBoostingRegressor(max_depth=2, n_estimators=4, learning_rate=0.8)
gradient_boosting_regressor.fit(features, labels)
gradient_boosting_regressor.predict(features)

结果强学习器的图示如图 12.21 所示。请注意,它在预测值方面做得很好。

图片

图 12.21 我们梯度提升回归器中强学习器预测的图示。请注意,该模型很好地拟合了数据集。

然而,我们可以更进一步,实际上绘制我们获得的五个弱学习器。这些细节在笔记本中,五个弱学习器如图 12.22 所示。请注意,最后一个弱学习器的预测值远小于第一个,因为每个弱学习器都在预测前一个的误差,而这些误差在每一步都会越来越小。

图片

图 12.22 梯度提升模型中的五个弱学习器。第一个是一个深度为 0 的决策树,它总是预测标签的平均值。每个后续的弱学习器都是一个深度最多为 2 的决策树,它拟合先前弱学习器给出的预测的残差。请注意,弱学习器的预测值越来越小,因为当强学习器的预测值越来越接近标签时,残差也会越来越小。

最后,我们可以使用 Scikit-Learn 或手动计算来查看预测如下:

  • 年龄 = 10,预测 = 6.87

  • 年龄 = 20,预测 = 5.11

  • 年龄 = 30,预测 = 6.71

  • 年龄 = 40,预测 = 1.43

  • 年龄 = 50,预测 = 1.43

  • 年龄 = 60,预测 = 1.43

  • 年龄 = 70,预测 = 4.90

  • 年龄 = 80,预测 = 4.10

XGBoost:一种极端的梯度提升方法

XGBoost,代表极端梯度提升,是最受欢迎、最强大和最有效的梯度提升实现之一。由陈天奇和卡洛斯·古斯特林于 2016 年创建(参见附录 C 获取参考文献),XGBoost 模型通常优于其他分类和回归模型。在本节中,我们将讨论 XGBoost 的工作原理,使用与“梯度提升:使用决策树构建强学习器”部分相同的回归示例。

XGBoost 使用决策树作为弱学习器,就像我们在之前学习的提升方法中一样,每个弱学习器都是设计来关注前一个的弱点。更具体地说,每棵树都是构建来拟合先前树预测的残差。然而,也有一些小的不同,比如我们构建树的方式,我们使用一个称为相似度得分的度量。此外,我们添加了一个剪枝步骤来防止过拟合,其中如果树的分支不满足某些条件,我们会移除这些分支。在本节中,我们将更详细地介绍这一点。

XGBoost 相似度得分:在集合中衡量相似性的新而有效的方法

在本小节中,我们将看到 XGBoost 的主要构建块,这是一种衡量集合元素相似度的方法。这个指标恰当地被称为相似度得分。在我们学习它之前,让我们做一个小的练习。在以下三个集合中,哪一个具有最大的相似度,哪一个具有最小的相似度?

  • 集合 1

  • 集合 2

  • 集合 3

如果你认为集合 2 的相似度最高,而集合 1 的相似度最低,你的直觉是正确的。在集合 1 中,元素彼此之间非常不同,因此这一组的相似度最低。在集合 2 和 3 之间,情况并不那么明确,因为这两个集合都有相同的元素,但出现的次数不同。然而,集合 2 中数字七出现了三次,而集合 3 中只出现了一次。因此,在集合 2 中,元素比集合 3 更均匀,或者说更相似。

为了量化相似度,考虑以下指标。给定一个集合{a[1],a[2],…,a[n]},相似度得分是元素总和的平方除以元素的数量,即公式。让我们计算上面三个集合的相似度得分,如下所示:

公式

注意,正如预期的那样,集合 2 的相似度得分是最高的,而集合 1 的得分最低。

注意:这个相似度得分并不完美。有人可能会认为集合{1, 1, 1}比集合{7, 8, 9}更相似,但集合{1, 1, 1}的相似度得分是 3,而集合{7, 8, 9}的相似度得分是 192。然而,对于我们的算法来说,这个得分仍然有效。相似度得分的主要目标是能够很好地分离大值和小值,正如我们将在当前示例中看到的那样。

与相似度得分相关联的超参数λ有助于防止过拟合。当使用时,它被添加到相似度得分的分母中,给出了公式公式。例如,如果λ = 2,集合 1 的相似度得分现在是公式。在我们的示例中,我们不会使用λ超参数,但在我们到达代码时,我们将看到如何将其设置为任何我们想要的值。

构建弱学习器

在本小节中,我们将看到如何构建每个弱学习器。为了说明这个过程,我们使用了“梯度提升”部分中的相同示例,如表 12.3 所示。为了方便起见,相同的数据集显示在表 12.6 的最左两列中。这是一个应用程序用户的数据集,其中特征是用户的年龄,标签是他们每周与该应用程序互动的天数。该数据集的图示如图 12.19 所示。

表 12.6 与表 12.3 中相同的数据库,包含用户、他们的年龄以及他们每周与我们应用互动的天数。第三列包含 XGBoost 模型中第一个弱学习器的预测。这些预测默认都是 0.5。最后一列包含残差,即标签与预测之间的差异。

特征(年龄) 标签(参与度) 第一个弱学习器的预测 残差
10 7 0.5 6.5
20 5 0.5 4.5
30 7 0.5 6.5
40 1 0.5 0.5
50 2 0.5 1.5
60 1 0.5 0.5
70 5 0.5 4.5
80 4 0.5 3.5

训练 XGBoost 模型的过程与训练梯度提升树的过程类似。第一个弱学习器是一棵对每个数据点预测 0.5 的树。在构建这个弱学习器之后,我们计算残差,即标签与预测标签之间的差异。这两个量可以在表 12.6 的最后两列中找到。

在开始构建剩余的树之前,让我们决定我们希望它们有多深。为了保持这个例子的小型化,我们再次使用最大深度为 2。这意味着当我们达到深度 2 时,我们停止构建弱学习器。这是一个超参数,我们将在“在 Python 中训练 XGBoost 模型”部分中更详细地了解。

要构建第二个弱学习器,我们需要将决策树拟合到残差上。我们使用相似度得分来完成这项工作。通常情况下,在根节点,我们有整个数据集。因此,我们首先计算整个数据集的相似度得分,如下所示:

现在,我们继续使用与决策树相同的方式,根据年龄特征以所有可能的方式分割节点。对于每个分割,我们计算对应于每个叶子的子集的相似度得分并将它们相加。这就是该分割对应的组合相似度得分。得分如下:

根节点分割,数据集{6.5, 4.5, 6.5, 0.5, 1.5, 0.5, 4.5, 3.5},相似度得分=98:

  • 在 15 处分割:

    • 左节点:{6.5}; 相似度得分:42.25

    • 右节点:{4.5, 6.5, 0.5, 1.5, 0.5, 4.5, 3.5}; 相似度得分:66.04

    • 组合相似度得分:108.29

  • 在 25 处分割:

    • 左节点:{6.5, 4.5}; 相似度得分:60.5

    • 右节点:{6.5, 0.5, 1.5, 0.5, 4.5, 3.5}; 相似度得分:48.17

    • 组合相似度得分:108.67

  • 在 35 处分割:

    • 左节点:{6.5, 4.5, 6.5}; 相似度得分:102.08

    • 右节点:{0.5, 1.5, 0.5, 4.5, 3.5}; 相似度得分:22.05

    • 组合相似度得分:124.13

  • 在 45 处分割:

    • 左节点:{6.5, 4.5, 6.5, 0.5}; 相似度得分:81

    • 右节点:{1.5, 0.5, 4.5, 3.5}; 相似度得分:25

    • 组合相似度得分:106

  • 在 55 处分割:

    • 左节点:{6.5, 4.5, 6.5, 0.5, 1.5}; 相似度得分:76.05

    • 右节点:{0.5, 4.5, 3.5}; 相似度得分:24.08

    • 组合相似度得分:100.13

  • 在 65 处分割:

    • 左节点:{6.5, 4.5, 6.5, 0.5, 1.5, 0.5}; 相似度得分:66.67

    • 右节点:{4.5, 3.5}; 相似度得分:32

    • 组合相似度得分:98.67

  • 在 75 处分割:

    • 左节点:{6.5, 4.5, 6.5, 0.5, 1.5, 0.5, 4.5}; 相似度得分:85.75

    • 右节点:{3.5}; 相似度得分:12.25

    • 组合相似度得分:98

如这些计算所示,具有最佳组合相似度得分的分割是在年龄=35 岁。这将作为根节点的分割。

接下来,我们以相同的方式在每个节点的数据集上进行分割。

左节点的分割,数据集为{6.5, 4.5, 6.5},相似度得分为 102.08:

  • 在 15 处分割:

    • 左节点:{6.5}; 相似度得分:42.25

    • 右节点:{4.5, 6.5}; 相似度得分:60.5

    • 相似度得分:102.75

  • 在 25 处分割:

    • 左节点:{6.5, 4.5}; 相似度得分:60.5

    • 右节点:{6.5}; 相似度得分:42.25

    • 相似度得分:102.75

两个分割给出了相同的组合相似度得分,因此我们可以使用其中的任何一个。让我们使用 15 处的分割。现在,转向右节点。

右节点的分割,数据集为{0.5, 1.5, 0.5, 4.5, 3.5},相似度得分为 22.05:

  • 在 45 处分割:

    • 左节点:{0.5}; 相似度得分:0.25

    • 右节点:{1.5, 0.5, 4.5, 3.5}; 相似度得分:25

    • 相似度得分:25.25

  • 在 55 处分割:

    • 左节点:{0.5, 1.5}; 相似度得分:2

    • 右节点:{0.5, 4.5, 3.5}; 相似度得分:24.08

    • 相似度得分:26.08

  • 在 65 处分割:

    • 左节点:{0.5, 1.5, 0.5}; 相似度得分:2.08

    • 右节点:{4.5, 3.5}; 相似度得分:32

    • 相似度得分:34.08

  • 在 75 处分割:

    • 左节点:{0.5, 1.5, 0.5, 4.5}; 相似度得分:12.25

    • 右节点:{3.5}; 相似度得分:12.25

    • 相似度得分:24.5

从这里,我们得出结论,最佳分割是在年龄=65 岁。树现在深度为 2,所以我们停止生长,因为这正是我们在算法开始时决定的。结果树以及节点处的相似度得分如图 12.23 所示。

图片

图 12.23 我们 XGBoost 分类器中的第二个弱学习器。对于每个节点,我们可以看到基于年龄特征的分割,该节点对应的标签,以及每组标签的相似度得分。每个节点选择的分割是最大化叶子节点组合相似度得分的分割。对于每个叶子节点,你可以看到相应的标签及其相似度得分。

这(几乎)就是我们的第二个弱学习器。在我们继续构建更多弱学习器之前,我们需要再进行一步操作来帮助减少过拟合。

树剪枝:通过简化弱学习器来减少过拟合的一种方法

XGBoost 的一个很好的特性是它不太容易过拟合。为此,它使用几个超参数,这些超参数在“在 Python 中训练 XGBoost 模型”部分中详细描述。其中之一,最小分割损失,如果结果节点的综合相似度得分与原始节点的相似度得分没有显著差异,则阻止分割发生。这种差异称为相似度增益。例如,在我们的树根节点中,相似度得分是 98,节点的综合相似度得分是 124.13。因此,相似度增益是 124.13 – 98 = 26.13。同样,左侧节点的相似度增益是 0.67,右侧节点的相似度增益是 12.03,如图 12.24 所示。

图片

图 12.24 在图 12.23 的左侧,我们有与图 12.23 相同的树,额外的一条信息:相似度增益。我们通过从每个节点的相似度得分中减去叶子节点的综合相似度得分来获得这个值。我们只允许具有高于 1(我们的最小分割损失超参数)的相似度增益的分割,因此其中一个分割不再被允许。这导致右侧的剪枝树,现在它变成了我们的弱学习器。

我们将最小分割损失设置为 1。使用这个值,唯一被阻止的分割是左侧节点(年龄≤15)的分割。因此,第二个弱学习器看起来就像图 12.24 右侧的树。

进行预测

现在我们已经构建了第二个弱学习器,是时候用它来进行预测了。我们通过从任何决策树中获取预测的方式获得预测,即通过平均相应叶子的标签。第二个弱学习器的预测可以在图 12.25 中看到。

现在,我们来计算前两个弱学习器的组合预测。为了避免过拟合,我们使用与梯度提升中相同的技巧,即乘以

通过学习率对所有的弱学习器(除了第一个)进行预测。这是为了模拟梯度下降法,在经过几次迭代后,我们逐渐收敛到一个好的预测。我们使用的学习率是 0.7。因此,前两个弱学习器的组合预测等于第一个弱学习器的预测加上第二个弱学习器预测乘以 0.7。例如,对于第一个数据点,这个预测是

0.5 + 5.83 * 0.7 = 4.58。

图片

图 12.25 在我们的 XGBoost 模型中被剪枝后的第二个弱学习器。这是与图 12.24 相同的树,带有其预测。每个叶子的预测是相应叶子标签的平均值。

表 12.7 的第五列包含前两个弱学习器的组合预测。

表 12.7:标签、前两个弱学习者的预测和残差。综合预测是通过将第一个弱学习者的预测(总是 0.5)加上学习率(0.7)乘以第二个弱学习者的预测得到的。残差是标签和综合预测之间的差异。

标签(参与度) 弱学习者 1 的预测 弱学习者 2 的预测 弱学习者 2 乘以学习率 综合预测 残差
7 0.5 5.83 4.08 4.58 2.42
5 0.5 5.83 4.08 4.58 0.42
7 0.5 5.83 4.08 4.58 2.42
1 0.5 0.83 0.58 1.08 –0.08
2 0.5 0.83 0.58 1.08 0.92
1 0.5 0.83 0.58 1.08 –0.08
5 0.5 4 2.8 3.3 1.7
4 0.5 4 2.8 3.3 0.7

注意,综合预测比第一个弱学习者的预测更接近标签。下一步是迭代。我们计算所有数据点的新的残差,为它们拟合一棵树,修剪树,计算新的综合预测,并以此类推。我们想要的树的数量是另一个超参数,我们可以在开始时选择。为了继续构建这些树,我们求助于一个有用的 Python 包,称为xgboost

在 Python 中训练 XGBoost 模型

在本节中,我们学习如何使用xgboost Python 包训练模型以适应当前数据集。本节的代码与前一节相同,如下所示:

在我们开始之前,让我们回顾一下为该模型定义的超参数:

estimators 数量:弱学习者的数量。注意:在xgboost包中,第一个弱学习者不计入估计器之中。对于本例,我们将其设置为 3,这将给我们四个弱学习者。

最大深度:每个决策树(弱学习者)允许的最大深度。我们将其设置为 2。

lambda 参数:添加到相似度分数分母中的数字。我们将其设置为 0。

最小分割损失:允许分割发生的相似度分数的最小增益。我们将其设置为 1。

学习率:从倒数第二个弱学习者的预测乘以学习率。我们将其设置为 0.7。

使用以下代码行,我们导入包,构建一个名为XGBRegressor的模型,并将其拟合到我们的数据集:

import xgboost
from xgboost import XGBRegressor
xgboost_regressor = XGBRegressor(random_state=0,
                                 n_estimators=3,
                                 max_depth=2,
                                 reg_lambda=0,
                                 min_split_loss=1,
                                 learning_rate=0.7)
xgboost_regressor.fit(features, labels)

模型的图表显示在图 12.26 中。注意,它很好地拟合了数据集。

图 12.26:我们的 XGBoost 模型预测的图表。注意,它很好地拟合了数据集。

xgboost包还允许我们查看弱学习者,它们出现在图 12.24 中。以这种方式获得的树已经将标签乘以学习率 0.7,与图 12.25 中手动获得的树的预测值和图 12.27 中左侧第二棵树的预测值相比,这一点很清楚。

图片

图 12.27 形成我们 XGBoost 模型中强学习者的四个弱学习者。注意,第一个总是预测 0.5。其他三个在形状上相当相似,这是一个巧合。然而,请注意,每棵树的预测值都在减小,因为每次我们都在拟合更小的残差。此外,请注意第二个弱学习者是我们图 12.25 中手动获得的同一棵树,唯一的区别是,在这棵树中,预测值已经乘以了学习率 0.7。

因此,为了获得强学习者的预测值,我们只需要添加每棵树的预测值。例如,对于一个 20 岁的用户,预测值如下:

  • 弱学习者 1:0.5

  • 弱学习者 2:4.08

  • 弱学习者 3:1.22

  • 弱学习者 4:-0.57

因此,预测值为 0.5 + 5.83 + 1.22 – 0.57 = 5.23。其他点的预测值如下:

  • 年龄 = 10;预测值 = 6.64

  • 年龄 = 20;预测值 = 5.23

  • 年龄 = 30;预测值 = 6.05

  • 年龄 = 40;预测值 = 1.51

  • 年龄 = 50;预测值 = 1.51

  • 年龄 = 60;预测值 = 1.51

  • 年龄 = 70;预测值 = 4.39

  • 年龄 = 80;预测值 = 4.39

集成方法的应用

集成方法是当今最实用的机器学习技术之一,因为它们在相对较低的成本下表现出极高的性能。集成方法应用最广泛的地方之一是机器学习挑战,例如 Netflix 挑战。Netflix 挑战是 Netflix 组织的一项比赛,他们将一些数据匿名化并公开。参赛者的目标是构建一个比 Netflix 本身更好的推荐系统;最佳系统将赢得一百万美元。获胜团队使用了一个强大的集成学习者的组合来获胜。有关更多信息,请参阅附录 C 中的参考文献。

概述

  • 集成方法包括训练多个弱学习者并将它们组合成一个强大的学习者。它们是构建强大模型的有效方法,这些模型在真实数据集上取得了显著成果。

  • 集成方法可用于回归和分类。

  • 集成方法主要有两种类型:Bagging 和 Boosting。

  • Bagging,或称为自助聚合,包括在数据随机子集上构建连续的学习者,并将它们组合成一个强大的学习者,该学习者基于多数投票进行预测。

  • Boosting 包括构建一系列的学习者,其中每个学习者专注于前一个学习者的弱点,并将它们组合成一个强大的分类器,该分类器基于学习者的加权投票进行预测。

  • AdaBoost、梯度提升和 XGBoost 是三种先进的提升算法,它们在真实数据集上产生了很好的结果。

  • 集成方法的用途非常广泛,从推荐算法到医学和生物学中的应用。

练习

练习 12.1

通过三个弱学习器LL[1]、L[2]和L[3]形成了一个强提升学习器L。它们的权重分别是 1、0.4 和 1.2。对于特定的一个点,L[1]和L[2]预测其标签为正,而L[3]预测其为负。学习器L对这个点的最终预测是什么?

练习 12.2

我们正在对大小为 100 的数据集训练一个 AdaBoost 模型。当前的弱学习器正确分类了 100 个数据点中的 68 个。我们在最终模型中将分配给这个学习器的权重是多少?

13 将一切付诸实践:数据工程和机器学习的真实案例

在本章

  • 清洗和预处理数据,使其可由我们的模型读取

  • 使用 Scikit-Learn 训练和评估多个模型

  • 使用网格搜索为我们的模型选择良好的超参数

  • 使用 k 折交叉验证以便能够同时使用我们的数据进行训练和验证

图片

在整本书中,我们学习了一些监督学习中最重要的算法,并有机会编码它们,并使用它们在多个数据集上进行预测。然而,在真实数据上训练模型的过程需要更多步骤,这正是本章所讨论的内容。

数据科学家最基本的工作之一是清洗和预处理数据。这一点至关重要,因为计算机无法完全完成这项工作。要正确地清洗数据,需要具备良好的数据知识和解决问题的能力。在本章中,我们将探讨一些用于清理和预处理数据的重要技术。然后,我们将更仔细地研究特征,并应用一些特征工程,使它们为模型做好准备。作为下一步,我们将模型分为训练集、验证集和测试集,在数据集上训练多个模型,并评估它们。这样,我们就能为这个数据集选择表现最佳的模型。最后,我们学习了一些重要的方法,例如网格搜索,以找到我们模型的最佳超参数。

我们将这些步骤应用于学习机器学习技术的流行数据集:泰坦尼克号数据集。在下一节中,我们将深入探讨这个数据集。本章包含大量的编码。我们使用的两个 Python 包是 Pandas 和 Scikit-Learn,我们在整本书中广泛使用了它们。Pandas 包非常适合处理数据,包括打开文件、加载数据以及将其组织成表格,称为 DataFrame。Scikit-Learn 包非常适合训练和评估模型,它包含了我们在这本书中学到的大多数算法的稳健实现。

我们将在本章中使用的代码和数据集如下:

泰坦尼克号数据集

在本节中,我们加载数据并对其进行研究。加载数据和处理数据是数据科学家的一项关键技能,因为模型的成功高度依赖于输入数据的预处理方式。我们使用 Pandas 包来完成这项工作。

在本章中,我们使用了一个流行的机器学习学习示例:泰坦尼克号数据集。从高层次来看,这个数据集包含了关于泰坦尼克号上许多乘客的信息,包括他们的姓名、年龄、婚姻状况、登船港口和舱位等级。最重要的是,它还包含了乘客的生还信息。这个数据集可以在 Kaggle(www.kaggle.com)上找到,这是一个拥有大量数据集和竞赛的流行在线社区,我强烈推荐你查看。

备注:我们使用的这个数据集是一个历史数据集,正如你可能想象的那样,它包含了 1912 年的许多社会偏见。历史数据集不提供修订或额外抽样的机会,以反映当前的社会规范和对世界的理解。这里的一些例子包括非二元性别的缺失、对性别和社会阶级的乘客的不同待遇,以及许多其他情况。我们将像对待数字表一样评估这个数据集,因为我们相信它是一个非常丰富且常用的数据集,用于构建模型和进行预测。然而,作为数据科学家,我们有责任始终警惕数据中的偏见,例如关于种族、性别认同、性取向、社会地位、能力、国籍、信仰等方面的偏见,并尽我们所能确保我们构建的模型不会延续历史偏见。

我们数据集的特征

我们使用的泰坦尼克号数据集包含了 891 名乘客的姓名和信息,包括他们是否生还。以下是数据集的列:

  • 乘客 ID:一个标识每个乘客的数字,从 1 到 891

  • 姓名:乘客的全名

  • 性别:乘客的性别(男性或女性)

  • 年龄:乘客的年龄,以整数表示

  • 舱位等级:乘客所乘坐的舱位等级:头等舱、二等舱或三等舱

  • 兄弟姐妹和配偶数量:乘客的兄弟姐妹和配偶的数量(如果乘客独自旅行,则为 0)

  • 父母和子女数量:乘客的父母和子女的数量(如果乘客独自旅行,则为 0)

  • 船票号:乘客的船票号码

  • 票价:乘客支付的英镑票价

  • 船舱:乘客所乘坐的船舱

  • 启航港:乘客登船的港口:“C”代表瑟堡,“Q”代表昆士敦,“S”代表南安普顿

  • 生还:乘客是否生还的信息(1 代表生还,0 代表未生还)

使用 Pandas 加载数据集

在本节中,我们将学习如何使用 Pandas 打开数据集并将其加载到 DataFrame 中,这是 Pandas 用来存储数据表的对象。我已经从www.kaggle.com下载了数据,并将其存储为名为 titanic.csv 的 CSV(逗号分隔值)文件。在我们对 Pandas 进行任何操作之前,我们必须使用以下命令导入 Pandas:

import pandas

现在我们已经加载了 Pandas,我们需要加载数据集。对于存储数据集,Pandas 使用两个对象:DataFrameSeries。它们本质上是一样的,区别在于 Series 用于只有一列的数据集,而 DataFrame 用于多列数据集。

我们可以使用以下命令将数据集作为 DataFrame 加载:

raw_data = pandas.read_csv('./titanic.csv', index_col="PassengerId")

这个命令将数据集存储到名为 raw_data 的 Pandas DataFrame 中。我们称之为原始数据,因为我们的目标是清理和预处理它。一旦我们加载它,我们就可以看到前几行看起来像表 13.1。一般来说,Pandas 会添加一个额外的列,编号数据集中的所有元素。因为数据集已经包含了这种编号,我们可以通过指定 index_col="PassengerId" 来将这个索引设置为该列。因此,我们可能会看到在这个数据集中,行是从 1 开始编号,而不是像实践中更常见的从 0 开始。

表 13.1 泰坦尼克号数据集包含关于泰坦尼克号乘客的信息,包括他们是否幸存。这里我们使用 Pandas 打开数据集并打印其行和列。注意它有 891 行和 12 列。

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
1 0 3 布劳恩,欧文·哈里斯先生 22.0 1 0 A/5 21171 7.2500 NaN S
2 1 1 库明斯,约翰·布拉德利夫人(佛罗伦萨·布里格斯·... 38.0 1 0 PC 17599 71.2833 C85 C
3 1 3 海金恩,莱娜小姐 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
... ... ... ... ... ... ... ... ... ... ... ...
890 1 1 贝尔,卡尔·豪厄尔先生 26.0 0 0 111369 30.0000 C148 C
891 0 3 杜利,帕特里克先生 32.0 0 0 370376 7.7500 NaN Q

保存和加载数据集

在我们开始研究我们的数据集之前,这里有一个小步骤将帮助我们。在每个部分的末尾,我们将数据集保存为 CSV 文件,并在下一部分的开头再次加载它。这样我们就可以放下书本或退出 Jupyter Notebook,在任何检查点稍后回来继续工作,而无需重新运行所有命令。对于如此小的数据集,重新运行命令并不是什么大问题,但想象一下如果我们正在处理大量数据。序列化和保存数据在那里很重要,因为它节省了时间和处理能力。

这里是每个部分末尾保存的数据集名称:

  • “泰坦尼克号数据集”:raw_data

  • “清理我们的数据集”:clean_data

  • “特征工程”:preprocessed_data

保存和加载的命令如下:

tablename.to_csv('./filename.csv', index=None)
tablename = pandas.read_csv('./filename.csv')

当 Pandas 加载数据集时,它会添加一个索引列,为每个元素编号。我们可以忽略这个列,但在保存数据集时,我们必须设置参数 index=None 以避免保存不必要的索引列。

该数据集已经有一个名为 PassengerId 的索引列。如果我们想将其作为 Pandas 中的默认索引列,我们可以在加载数据集时指定 index_col='PassengerId'(但我们不会这样做)。

使用 Pandas 研究我们的数据集

在本节中,我将教你一些研究我们数据集的有用方法。第一个是长度函数,或 len。此函数返回数据集中的行数,如下所示:

len(raw_data)
Output: 891

这意味着我们的数据集有 891 行。要输出列名,我们使用 DataFrame 的 columns 属性,如下所示:

raw_data.columns
Output: Index(['PassengerId', 'Survived', 'Pclass', 'Name', 'Sex', 'Age', 'SibSp', 'Parch', 'Ticket', 'Fare', 'Cabin', 'Embarked'], dtype='object')

现在,让我们探索一个列。使用以下命令,我们可以探索幸存列:

raw_data['Survived']
Output:
0, 1, 1, 1, 0, .., 0, 1, 0, 1, 0
Name: Survived, Length: 891, dtype: int64

第一列是乘客的索引(1 到 891)。第二列如果是乘客未幸存,则为 0,如果是乘客幸存,则为 1。然而,如果我们想添加两列——例如姓名和年龄——我们可以使用 next 命令:

raw_data[['Name', 'Age']]

这样就会返回只包含这两列的 DataFrame。

现在假设我们想找出有多少乘客幸存。我们可以使用 sum 函数对幸存列的值进行求和,如下所示:

sum(raw_data['Survived'])
Output: 342

这表明在我们的数据集中,891 名乘客中只有 342 名幸存。

这只是 Pandas 为处理数据集提供的所有功能的一小部分。请访问pandas.pydata.org的文档页面以了解更多信息。

清理我们的数据集:缺失值及其处理方法

现在我们已经知道如何处理 DataFrame,我们将讨论一些清理数据集的技术。为什么这很重要?在现实生活中,数据可能很混乱,将混乱的数据输入模型通常会导致模型效果不佳。在训练模型之前,数据科学家充分探索数据集并进行一些清理,以便为模型准备数据是非常重要的。

我们遇到的第一问题是带有缺失值的数据集。由于人为或计算机错误,或者简单地由于数据收集问题,数据集并不总是包含所有值。试图将模型拟合到带有缺失值的数据集可能会产生错误。在缺失数据方面,泰坦尼克号数据集也不例外。例如,让我们看看我们数据集中的舱位列,如下所示:

raw_data['Cabin']
Output:
0     NaN
1     C85
2     NaN
3     C123
4     NaN
      ...
886   NaN
887   B42
888   NaN
889   C148
890   NaN
Name: Cabin, Length: 891, dtype: object

一些船舱名称是存在的,例如 C123 或 C148,但大多数值都是 NaN。NaN,或“不是一个数字”,意味着条目可能缺失、不可读,或者简单地是另一种无法转换为数字的类型。这可能是由于文书错误造成的;可以想象,“泰坦尼克号”的记录很旧,一些信息已经丢失,或者他们一开始就没有为每位乘客记录船舱号码。无论如何,我们都不希望数据集中有 NaN 值。我们现在面临一个决策点:我们应该处理这些 NaN 值还是完全删除该列?首先,让我们检查数据集中每一列有多少 NaN 值。我们的决策将取决于对这个问题的回答。

为了找出每一列中有多少值是 NaN,我们使用is_na(或is_null)函数。如果条目是 NaN,is_na函数返回 1,否则返回 0。因此,如果我们对这些值求和,我们就可以得到每一列中 NaN 条目的数量,如下所示:

raw_data.isna().sum()
Output:
PassengerId    0
Survived       0
Pclass         0
Name           0
Sex            0
Age          177
SibSp          0
Parch          0
Ticket         0
Fare           0
Cabin        687
Embarked       2

这告诉我们,唯一有缺失数据的列是年龄列,缺失 177 个值;船舱列,缺失 687 个值;以及登船列,缺失 2 个值。我们可以使用几种方法处理缺失数据,并且我们将为这个数据集的不同列应用不同的方法。

删除有缺失数据的列

当一列缺失太多值时,对应特征可能对我们的模型没有用处。在这种情况下,船舱似乎不是一个好的特征。在 891 行中,有 687 行没有值。这个特征应该被删除。我们可以使用 Pandas 中的drop函数来完成这个操作。我们将创建一个新的 DataFrame,名为clean_data,以存储我们即将清理的数据:

clean_data = raw_data.drop('Cabin', axis=1)

drop函数的参数如下:

  • 我们想要删除的列的名称

  • 当我们想要删除列时,axis参数为 1,当我们想要删除行时,axis参数为 0

然后,我们将这个函数的输出分配给变量clean_data,表示我们想要用新的 DataFrame 替换旧的 DataFrame,即名为data的 DataFrame,并删除该列。

如何不丢失整个列:填充缺失数据

我们并不总是想删除有缺失数据的列,因为我们可能会丢失重要的信息。我们也可以用有意义的值填充数据。例如,让我们看一下下面的年龄列:

clean_data['Age']
Output:
0      22.0
1      38.0
2      26.0
3      35.0
4      35.0
       ...
886    27.0
887    19.0
888     NaN
889    26.0
890    32.0
Name: Age, Length: 891, dtype: float64

如我们之前所计算的,年龄列中只有 177 个值缺失,而总共有 891 个值,这并不多。这个列是有用的,所以我们不要删除它。那么我们可以对这些缺失值做些什么呢?我们可以做很多事情,但最常见的是用其他值的平均值或中位数来填充它们。让我们做后者。首先,我们使用中值函数计算中值,得到 28。接下来,我们使用fillna函数,用我们给出的值填充缺失值,如下一代码片段所示:

median_age = clean_data["Age"].median()
clean_data["Age"] = clean_data["Age"].fillna(median_age)

第三列缺失值的是登船港口(Embarked),缺失了两个值。我们在这里能做什么呢?由于这些是字母而不是数字,我们无法使用平均值。幸运的是,在 891 个乘客中只有两行缺失这个数字,所以我们在这里并没有丢失太多信息。我的建议是将所有在登船港口列中没有值的乘客归入同一个类别。我们可以将这个类别称为 U,代表“未知”。以下代码行将完成这项操作:

clean_data["Embarked"] = clean_data["Embarked"].fillna('U')

最后,我们可以将这个 DataFrame 保存为名为 clean_titanic_data 的 CSV 文件,以便在下一节中使用:

clean_data.to_csv('./clean_titanic_data.csv', index=None)

特征工程:在训练模型之前转换数据集中的特征

现在我们已经清洗了数据集,我们离能够训练模型更近了。然而,我们仍然需要进行一些重要的数据处理,这些内容在本节中可以看到。首先是数据类型从数值到分类的转换,反之亦然。其次是特征选择,我们手动决定哪些特征需要移除以提高我们模型的训练效果。

回想第二章,特征有两种类型:数值型和分类型。数值型特征是存储为数字的特征。在本数据集中,年龄、票价和等级等特征都是数字。分类型特征包含几个类别或等级。例如,性别特征包含两个类别:女性和男性。登船港口特征包含三个类别,C 代表瑟堡,Q 代表皇后镇,S 代表南安普顿。

正如我们在整本书中看到的,机器学习模型以数字作为输入。如果是这样,我们如何输入“女性”这个词或“Q”这个字母呢?我们需要有一种方法将分类特征转换为数值特征。而且,信不信由你,有时我们可能对将数值特征作为分类特征感兴趣,以帮助我们进行训练,例如将它们放入桶中,例如,年龄 1-10,11-20 等等。我们将在“将数值数据转换为分类数据”这一节中更详细地介绍这一点。

更进一步,当我们想到一个特征,比如乘客等级(称为 Pclass),这究竟是一个数值特征,还是一个分类特征?我们应该将等级视为介于一和三之间的数字,还是将其视为三个等级:头等舱、二等舱和三等舱?我们将在本节中回答所有这些问题。

在本节中,我们将 DataFrame 称为preprocessed_data。该数据集的前几行如表 13.2 所示

表 13.2 清洗后的数据集的前五行。我们将继续预处理这些数据以进行训练。

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Embarked
1 0 3 Braund, Mr. Owen Harris 男性 22.0 1 0 A/5 21171 7.2500 S
2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... 女性 38.0 1 0 PC 17599 71.2833 C
3 1 3 Heikkinen, Miss. Laina 女性 26.0 0 0 STON/O2. 3101282 7.9250 S
4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 S
5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 S

将分类数据转换为数值数据:One-hot encoding

As was mentioned previously, machine learning models perform a lot of mathematical operations, and to perform mathematical operations in our data, we must make sure all the data is numerical. If we have any columns with categorical data, we must turn them into numbers. In this section, we learn a way to do this effectively using a technique called one-hot encoding.

But before we delve into one-hot encoding, here’s a question: why not simply attach a different number to each one of the classes? For example, if our feature has 10 classes, why not number them 0, 1, 2,..., 9? The reason is that this forces an order in the features that we may not want. For example, if the Embarked column has the three classes C, Q, and S, corresponding to Cherbourg, Queenstown, and Southampton, assigning the numbers 0, 1, and 2 to these would implicitly tell the model that the value of Queenstown is between the values of Cherbourg and Southampton, which is not necessarily true. A complex model may be able to deal with this implicit ordering, but simpler models (such as linear models, for example) will suffer. We’d like to make these values more independent of each other, and this is where one-hot encoding comes in.

One-hot encoding works in the following way: First, we look at how many classes the feature has and build as many new columns. For example, a column with two categories, female and male, would turn it into two columns, one for female and one for male. We can call these columns gender_male and gender_female for clarity. Then, we look at each passenger. If the passenger is female, then the gender_female column will have a 1, and the gender_male column will have a 0. If the passenger is male, then we do the opposite.

What if we have a column with more classes, such as the embarked column? Because that column has three classes (C for Cherbourg, Q for Queenstown, and S for Southampton), we simply make three columns called embarked_c, embarked_q, and embarked_s. In that way, if a passenger embarked in, say, Southampton, the third column will have a 1 and the other two a 0. This process is illustrated in figure 13.1.

Figure 13.1 One-hot encoding our data to turn it all into numbers for the machine learning model to read. On the left, we have columns with categorical features such as gender or port of embarkment. On the right, we have turned these categorical features into numerical features.

Pandas 函数get_dummies帮助我们进行独热编码。我们用它来创建一些新列,然后将这些列附加到数据集上,我们绝不能忘记删除原始列,因为那个信息是冗余的。接下来是进行性别和登船列独热编码的代码:

gender_columns = pandas.get_dummies(data['Sex'], prefix='Sex')                 ❶
embarked_columns = pandas.get_dummies(data["Pclass"], prefix="Pclass")

preprocessed_data = pandas.concat([preprocessed_data, gender_columns], axis=1) ❷
preprocessed_data = pandas.concat([preprocessed_data, embarked_columns], axis=1)

preprocessed_data = preprocessed_data.drop(['Sex', 'Embarked'], axis=1)        ❸

❶ 创建包含独热编码列的列

❷ 将数据集与新创建的列连接

❸ 从数据集中删除旧列

有时这个过程可能会很昂贵。想象一下有一个有 500 个类别的列。这将向我们的表格中添加 500 个新列!不仅如此,行将非常稀疏,即它们将包含大部分零。现在想象如果我们有很多每个有数百个类别的列——我们的表格将变得太大而无法处理。在这种情况下,作为数据科学家,使用你的标准来做出决定。如果有足够的计算能力和存储空间来处理数千甚至数百万个列,那么独热编码就没有问题。如果这些资源有限,也许我们可以将类别合并以产生更少的列。例如,如果我们有一个有 100 种动物类型的列,我们可以将它们合并成六个列,分别由哺乳动物、鸟类、鱼类、两栖动物、无脊椎动物和爬行动物组成。

我们能否对数值特征进行独热编码?如果是的话,我们为什么要这样做?

显然,如果一个特征有男性或女性等类别,我们最好的策略是对其进行独热编码。然而,还有一些数值特征,我们仍然可能想要考虑进行独热编码。让我们以 Pclass 列为例。这个列有 0、1、2 三个类别,分别对应头等舱、二等舱和三等舱。我们应该将其保留为数值特征,还是将其独热编码为三个特征,Pclass1、Pclass2 和 Pclass3?这当然是有争议的,我们可以在两个方面都提出很好的论据。有人可能会争论,如果我们不希望无谓地扩大数据集,如果这不会给模型带来性能上的潜在提升,我们就不想这样做。我们可以使用一个经验法则来决定是否将一个列拆分成几个列。我们可以问自己:这个特征是否与结果直接相关?换句话说,增加这个特征的价值是否会增加乘客生存的可能性?人们可能会想象,等级越高,乘客生存的可能性就越大。让我们通过一些计数来验证这一点(参见笔记本中的代码),如下所示:

  • 在头等舱中,62.96%的乘客幸存。

  • 在二等舱中,40.38%的乘客幸存。

  • 在三等舱中,55%的乘客幸存。

注意,第二等舱乘客的生存可能性最低。因此,增加(或减少)舱位并不一定自动提高生存机会。出于这个原因,我建议将这个特征独热编码如下:

categorized_pclass_columns = pd.get_dummies(preprocessed_data['Pclass'], prefix='Pclass')
preprocessed_data = pd.concat([preprocessed_data, categorized_pclass_columns], axis=1)
preprocessed_data = preprocessed_data.drop(['Pclass'], axis=1)

将数值数据转换为分类数据(以及我们为什么要这样做):分箱

在上一节中,我们学习了如何将分类数据转换为数值数据。在本节中,我们将看到如何进行相反的操作。我们为什么要这样做呢?让我们看看一个例子。

让我们看看年龄列。它很棒,是数值型的。机器学习模型回答以下问题:“年龄在多大程度上决定了在泰坦尼克号上的生存?”想象一下,我们有一个用于生存的线性模型。这样的模型最终会得出以下两个结论之一:

  • 乘客越老,他们生存的可能性就越大。

  • 乘客越老,他们生存的可能性就越小。

然而,这总是如此吗?如果年龄和生存之间的关系并不那么简单直接呢?如果乘客在 20 到 30 岁之间生存的可能性最高,而其他所有年龄组的可能性都很低呢?如果最低的生存可能性是在 20 到 30 岁之间呢?我们需要给模型所有的自由来决定哪些年龄决定了乘客更有可能或不太可能生存。我们能做什么?

许多非线性模型可以处理这个问题,但我们应该仍然修改年龄列,使其给模型更多的自由来探索数据。我们可以采取的一个有用技术是将年龄进行分组,即,将它们分成几个不同的桶。例如,我们可以将年龄列转换为以下形式:

  • 从 0 岁到 10 岁

  • 从 11 岁到 20 岁

  • 从 21 岁到 30 岁

  • 从 31 岁到 40 岁

  • 从 41 岁到 50 岁

  • 从 51 岁到 60 岁

  • 从 61 岁到 70 岁

  • 从 71 岁到 80 岁

  • 81 岁或以上

这与独热编码类似,因为它会将年龄列转换为九个新的列。执行此操作的代码如下:

bins = [0, 10, 20, 30, 40, 50, 60, 70, 80]
categorized_age = pandas.cut(preprocessed_data['Age'], bins)
preprocessed_data['Categorized_age'] = categorized_age
preprocessed_data = preprocessed_data.drop(["Age"], axis=1)

特征选择:去除不必要的特征

在“删除缺失数据的列”的子节中,我们在我们的表中删除了一些列,因为它们有太多的缺失值。然而,我们也应该删除一些其他列,因为它们对于我们的模型不是必要的,甚至更糟,它们可能会完全破坏我们的模型!在本节中,我们将讨论哪些特征应该被删除。但在那之前,先看看这些特征,想想哪一个可能会对我们的模型有害。它们如下所示:

  • PassengerID:对应每个乘客的唯一编号

  • Name:乘客的全名

  • Sex(两个类别):乘客的性别,男性或女性

  • Age(几个类别):乘客的年龄,以整数表示

  • Pclass(几个类别):乘客所乘坐的等级:头等、二等或三等

  • SibSP:乘客的兄弟姐妹和配偶的数量(如果乘客独自旅行,则为 0)

  • Parch:乘客的父母和孩子的数量(如果乘客独自旅行,则为 0)

  • Ticket:票号

  • Fare:乘客支付的票价(以英镑为单位)

  • Cabin:乘客所乘坐的客舱

  • Embarked:乘客登船的港口:C 代表瑟堡,Q 代表皇后镇,S 代表南安普顿

  • Survived:乘客是否幸存的信息(1 表示幸存,0 表示未幸存)

首先,让我们看看名字特征。我们应该在我们的模型中考虑它吗?绝对不应该,原因如下:每位乘客都有一个不同的名字(可能有一些非常少的例外,但它们并不重要)。因此,模型将训练成仅仅学习幸存乘客的名字,而无法告诉我们关于它尚未见过的新的乘客的名字。这个模型只是在记忆数据——它并没有从其特征中学习到任何有意义的东西。这意味着它过度拟合了,因此,我们应该完全去掉“名字”这一列。

票据和乘客 ID 特征与名字特征有同样的问题,因为每个乘客都有一个独特的值。我们也将去掉这两个列。drop函数将帮助我们完成这项工作,如下所示:

preprocessed_data = preprocessed_data.drop(['Name', 'Ticket', 'PassengerId'], axis=1)

关于幸存特征——我们不应该也去掉它吗?当然!在训练数据集时保留幸存列会导致过度拟合,因为模型将简单地使用这个特征来确定乘客是否幸存。这就像在考试中作弊一样,看答案。我们不会现在从数据集中去掉它,因为我们在稍后分割数据集为特征和标签用于训练时将会去掉它。

如往常一样,我们可以将这个数据集保存为 csv 文件 preprocessed_titanic_data.csv,以便在下一节中使用。

训练我们的模型

现在我们已经对数据进行了预处理,我们可以开始在不同模型上训练数据。我们应该从这本书中学到的哪些模型中选择:决策树、支持向量机、逻辑分类器?这个答案在于评估我们的模型。在本节中,我们将看到如何训练几个不同的模型,在验证数据集上评估它们,并选择最适合我们数据集的最佳模型。

如往常一样,我们从上一节保存数据所在的文件中加载数据,如下所示。我们将它称为data

data = pandas.read_csv('preprocessed_titanic_data.csv')

表 13.3 包含了预处理数据的前几行。请注意,并不是所有列都显示出来,因为总共有 27 列。

表 13.3 我们预处理数据的前五行,准备输入到模型中。请注意,它有 21 列,比之前多得多。这些额外的列是在我们对一些现有特征进行独热编码和分箱时创建的。

Survived SibSp Parch Fare Sex_female Sex_male Pclass_C Pclass_Q Pclass_S Pclass_U ... Categorized_age_(10, 20]
0 1 0 7.25000 0 1 0 0 1 0 ... 0
1 1 0 71.2833 1 0 1 0 0 0 ... 0
1 0 0 7.9250 1 0 0 0 1 0 ... 0
1 1 0 53.1000 1 0 0 0 1 0 ... 0
0 0 0 8.0500 0 1 0 0 1 0 ... 0

旁白 如果你运行笔记本中的代码,你可能会得到不同的数字。

将数据分为特征和标签,以及训练和验证

我们的数据库是一个包含特征和标签的表格。我们需要执行两个分割。首先,我们需要将特征从标签中分离出来,以便将其输入到模型中。接下来,我们需要形成一个训练集和一个测试集。这就是本小节所涵盖的内容。

为了将数据集分割成两个称为featureslabels的表格,我们使用drop函数如下:

features = data.drop(["Survived"], axis=1)
labels = data["Survived"]

接下来,我们将数据分为训练集和验证集。我们将使用 60%的数据进行训练,20%的数据进行验证,20%的数据进行测试。对于数据分割,我们使用 Scikit-Learn 函数train_test_split。在这个函数中,我们使用test_size参数指定我们想要的验证数据百分比。输出是四个称为features_train, features_test, labels_train, labels_test的表格。

如果我们想要将数据分成 80%的训练数据和 20%的测试数据,我们将使用以下代码:

from sklearn.model_selection import train_test_split
features_train, features_test, labels_train, labels_test = 
    train_test_split(features, labels, test_size=0.2)

然而,因为我们想要 60%的训练数据,20%的验证数据和 20%的测试数据,我们需要两次使用train_test_split函数:一次用于分离训练数据,另一次用于分割验证集和测试集,如下所示:

features_train, features_validation_test, labels_train, labels_validation_test = 
    train_test_split(features, labels, test_size=0.4)
features_validation, features_test, labels_validation, labels_test = 
    train_test_split(features_validation_test, labels_validation_test, test_size=0.5)

旁白 你可能会发现在笔记本中我们在这个函数中指定了一个固定的random_state。原因是train_test_split在分割数据时会打乱数据。我们固定随机状态以确保我们总是得到相同的分割。

我们可以检查这些数据框的长度,并注意到训练集的长度是 534,验证集的长度是 178,测试集的长度是 179。现在,回想一下第四章中提到的黄金法则,那就是永远不要使用我们的测试数据来训练或对我们的模型做出决策。因此,我们将测试集留到最后,当我们决定使用什么模型时再使用。我们将使用训练集来训练模型,并使用验证集来决定选择哪个模型。

在我们的数据集上训练几个模型

我们终于到了有趣的部分:训练模型!在本节中,我们将看到如何在 Scikit-Learn 中仅用几行代码就训练几个不同的模型。

首先,我们开始训练一个逻辑回归模型。我们可以通过创建一个LogisticRegression实例并使用fit方法来实现,如下所示:

from sklearn.linear_model import LogisticRegression
lr_model = LogisticRegression()
lr_model.fit(features_train, labels_train)

让我们再训练一个决策树、一个朴素贝叶斯模型、一个支持向量机、一个随机森林、一个梯度提升树和一个 AdaBoost 模型,如下面的代码所示:

from sklearn.tree import DecisionTreeClassifier, GaussianNB, SVC, 
    RandomForestClassifier, GradientBoostingClassifier, AdaBoostClassifier

dt_model = DecisionTreeClassifier()
dt_model.fit(features_train, labels_train)

nb_model = GaussianNB()
nb_model.fit(features_train, labels_train)

svm_model = SVC()
svm_model.fit(features_train, labels_train)

rf_model = RandomForestClassifier()
rf_model.fit(features_train, labels_train)

gb_model = GradientBoostingClassifier()
gb_model.fit(features_train, labels_train)

ab_model = AdaBoostClassifier()
ab_model.fit(features_train, labels_train)

哪个模型更好?评估模型

现在我们已经训练了一些模型,我们需要选择最好的一个。在本节中,我们使用不同的指标来评估它们,使用验证集进行评估。回想一下,在第四章中我们学习了准确率、召回率、精确率和F [1] 分数。为了刷新你的记忆,定义如下:

准确率 正确标记的点数与总点数的比率。

回收率 在具有正标签的点中,正确分类的点的比例。换句话说,回收率 = 真阳性 / (真阳性 + 假阴性),其中真阳性是真实阳性的数量,假阴性是假阴性的数量。

精确率 在被分类为正类的点中,正确分类的点的比例。换句话说,精确率 = 真阳性 / (真阳性 + 假阳性),其中假阳性是假阳性的数量。

F[1]-分数 精确率和回收率的调和平均数。这是一个介于精确率和回收率之间的数字,但它更接近两者中的较小者。

测试每个模型的准确率

让我们先评估这些模型的准确率。Scikit-Learn 中的score函数将执行此操作,如下所示:

lr_model.score(features_validation, labels_validation)
Output:
0.7932960893854749

我们为所有其他模型计算准确率,并得到以下结果,我已经将其四舍五入到两位数(有关整个过程的详细信息,请参阅笔记本):

准确率

  • 逻辑回归: 0.77

  • 决策树: 0.78

  • 朴素贝叶斯: 0.72

  • 支持向量机: 0.68

  • 随机森林: 0.7875

  • 梯度提升: 0.81

  • AdaBoost: 0.76

这表明在这个数据集中,最佳模型是梯度提升树,因为它在验证集上给出了最高的准确率(81%,这对于泰坦尼克号数据集来说是个好成绩)。这并不令人惊讶,因为这个算法通常表现非常好。

您可以遵循类似的程序来计算回收率、精确率和 F[1]分数。我将让您计算回收率和精确率,我们将一起计算 F[1]分数。

测试每个模型的 F[1]-分数

这是检查 F[1]分数的方法。首先,我们必须使用predict函数输出模型的预测,然后我们使用f1_score函数,如下所示:

lr_predicted_labels = lr_model.predict(features_validation) ❶
f1_score(labels_validation, lr_predicted_labels) ❷
Output:
0.6870229007633588

❶ 使用模型进行预测

❷ 计算 F[1]分数

如前所述,我们可以为所有模型执行此操作,并得到以下结果:

F[1]-分数

  • 逻辑回归: 0.69

  • 决策树: 0.71

  • 朴素贝叶斯: 0.63

  • 支持向量机: 0.42

  • 随机森林: 0.68

  • 梯度提升: 0.74

  • AdaBoost: 0.69

再次,梯度提升树以 0.74 的 F[1]-分数获胜。鉴于其数值远高于其他模型,我们可以安全地得出结论,在这八个模型中,梯度提升树是最佳选择。请注意,基于树的模型总体表现良好,这在数据集高度非线性这一点上并不令人惊讶。训练一个神经网络和一个 XGBoost 模型在这个数据集上也会很有趣,我鼓励你这样做!

测试模型

使用验证集比较模型后,我们最终做出了决定,并选择了梯度提升树作为这个数据集的最佳模型。不要感到惊讶;梯度提升树(及其近亲 XGBoost)在大多数比赛中获胜。但为了看看我们是否真的做得很好,或者我们是否意外地过拟合,我们需要对这个模型进行最后的测试:我们需要在尚未接触过的测试集上测试这个模型。

首先,让我们评估准确率,如下所示:

gb_model.score(features_test, labels_test)
Output:
0.8324022346368715

现在,让我们看看F[1]分数,如下所示:

gb_predicted_test_labels = gb_model.predict(features_test)
f1_score(labels_test, gb_predicted_test_labels)
Output:
0.8026315789473685

这些分数对于泰坦尼克号数据集来说相当不错。因此,我们可以放心地说我们的模型是好的。

然而,我们训练这些模型时没有触及它们的超参数,这意味着 Scikit-Learn 为它们选择了某些标准超参数。我们有没有办法找到模型的最佳超参数?在下一节中,我们将了解到这一点。

调整超参数以找到最佳模型:网格搜索

在上一节中,我们训练了几个模型,并发现梯度提升树在它们中表现最好。然而,我们没有探索许多不同的超参数组合,所以我们还有改进训练的空间。在本节中,我们看到了一种有用的技术,可以在许多超参数组合中搜索,以找到适合我们数据的好模型。

梯度提升树在泰坦尼克号数据集上的性能几乎达到了可能达到的最高水平,所以我们不妨让它保持原样。然而,性能较差的 SVM 表现最差,准确率为 69%,F[1]分数为 0.42。然而,我们仍然相信 SVMs,因为它们是一个强大的机器学习模型。也许这个 SVM 表现不佳是由于它使用的超参数。可能存在更好的超参数组合,它们可以工作得更好。

旁白:在本节中,我们做出了一些参数选择。有些是基于经验,有些是基于标准实践,还有些是任意的。我鼓励你尝试使用你决定的任何选择遵循类似的程序,并尝试超越当前模型的分数!

为了提高我们 SVM 的性能,我们使用了一种称为网格搜索的方法,它包括在不同超参数组合上多次训练我们的模型,并选择在验证集上表现最好的那个。

让我们从选择一个核函数开始。在实践中,我发现径向基函数(RBF)核通常表现良好,所以我们选择这个。回顾第九章,与 RBF 核相关的超参数是 gamma,它是一个实数。让我们尝试用两个 gamma 值来训练 SVM,即 1 和 10。为什么是 1 和 10?通常当我们搜索超参数时,我们倾向于进行指数搜索,所以我们可能会尝试 0.1、1、10、100、1000 等值,而不是 1、2、3、4、5。这种指数搜索覆盖了更大的空间,给我们找到良好超参数的机会更大,而且这种搜索在数据科学家中是一种标准实践。

再次回顾第九章,SVMs 关联的另一个超参数是 C 参数。让我们也尝试用 C = 1 和 C = 10 来训练模型。这给我们提供了以下四个可能的模型来训练:

模型 1:核函数 = RBF,gamma = 1,C = 1

模型 2:核函数 = RBF,gamma = 1,C = 10

模型 3:核函数 = RBF,gamma = 10,C = 1

模型 4:核函数 = RBF,gamma = 10,C = 10

我们可以用以下八行代码轻松地在我们的训练集中训练所有这些:

svm_1_1 = SVC(kernel='rbf', C=1, gamma=1)
svm_1_1.fit(features_train, labels_train)

svm_1_10 = SVC(kernel='rbf', C=1, gamma=10)
svm_1_10.fit(features_train, labels_train)

svm_10_1 = SVC(kernel='rbf', C=10, gamma=1)
svm_10_1.fit(features_train, labels_train)

svm_10_10 = SVC(kernel='rbf', C=10, gamma=10)
svm_10_10.fit(features_train, labels_train)

现在,我们使用准确率(另一个任意选择——我们也可以使用F[1]-分数、精确率或召回率)来评估它们。这些分数记录在表 13.4 中。

表 13.4 网格搜索方法对于搜索许多超参数组合并选择最佳模型非常有用。在这里,我们使用网格搜索来选择 SVM 中参数 C 和 gamma 的最佳组合。我们使用准确率来比较验证集中的模型。请注意,在这些模型中,最佳模型是 gamma = 0.1 且 C = 10 的模型,准确率为 0.72。

C = 1 C = 10
gamma = 0.1 0.69 0.72
gamma = 1 0.70 0.70
gamma = 10 0.67 0.65

从表 13.4 中可以看出,最佳准确率为 0.72,由 gamma = 0.1 和 C = 1 的模型给出。这比我们之前未指定任何超参数时获得的 0.68 有所改进。

如果我们有很多更多的参数,我们只需用它们制作一个网格,并训练所有可能的模型。请注意,随着我们探索更多的选择,模型的数量会迅速增加。例如,如果我们想要探索 gamma 的五个值和 C 的四个值,我们就必须训练 20 个模型(五个乘以四个)。我们还可以添加更多的超参数——例如,如果有一个我们想要尝试的第三个超参数,并且这个超参数有七个值,我们就必须总共训练 140 个模型(五个乘以四个乘以七个)。由于模型的数量增长如此之快,因此选择选择的方式非常重要,这样它就能很好地探索超参数空间,而无需训练大量模型。

Scikit-Learn 提供了一个简单的方法来做这件事:使用GridSearchCV对象。首先,我们将超参数定义为一个字典,其中字典的键是参数的名称,与该键对应的值是我们想要尝试的超参数值的列表。在这种情况下,让我们探索以下超参数组合:

核函数:RBF

C:0.01, 0.1, 1, 10, 100

gamma:0.01, 0.1, 1, 10, 100

以下代码将完成这项工作:

svm_parameters = {'kernel': ['rbf'],
                  'C': [0.01, 0.1, 1 , 10, 100],
                  'gamma': [0.01, 0.1, 1, 10, 100]
                 }                       ❶

svm = SVC()                              ❷

svm_gs = GridSearchCV(estimator = svm,
param_grid = svm_parameters)             ❸

svm_gs.fit(features_train, labels_train) ❹

❶ 一个包含我们想要尝试的超参数及其值的字典

❷ 一个没有超参数的常规 SVM

❸ 一个包含 SVM 和超参数字典的 GridSearchCV 对象

❹ 我们以与在 Scikit-Learn 中拟合常规模型相同的方式拟合 GridSearchCV 模型。

这将训练 25 个模型,这些模型包含超参数字典中给出的所有超参数组合。现在,我们从中选择最佳模型,并将其称为svm_winner。让我们按照以下方式计算这个模型在验证集上的准确率:

svm_winner = svm_gs.best_estimator_
svm_winner.score(features_validation, labels_validation)
Output:
0.7303370786516854

我们的最佳模型达到了 0.73 的准确率,这比原始的 0.68 要好。我们仍然可以通过运行更大的超参数搜索来改进这个模型,我鼓励你自己尝试。现在,让我们探索最后获胜的 SVM 模型使用了哪些超参数,如下所示:

svm_winner
Output:
SVC(C=10, break_ties=False, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma=0.01, kernel='rbf',
    max_iter=-1, probability=False, random_state=None, shrinking=True,
    tol=0.001, verbose=False)

获胜模型使用了 gamma = 0.01 和 C = 10 的 RBF 核。

挑战我鼓励你尝试在其他模型上使用网格搜索,看看你能提高多少获胜模型的准确性和F[1]分数!如果你有一个好分数,请在 Kaggle 数据集上运行它,并使用此链接提交你的预测:www.kaggle.com/c/titanic/submit

还有一件事:GridSearchCV结尾的CV是什么意思?它代表交叉验证,我们将在下一节中学习。

使用 K 折交叉验证来重复使用我们的数据作为训练和验证

在本节中,我们学习了一种替代本章中使用的传统训练-验证-测试方法的替代方法。它被称为k-折交叉验证,它在许多情况下都很有用,尤其是在我们的数据集较小的情况下。

在整个这个例子中,我们使用了 60%的数据进行训练,20%进行验证,最后的 20%用于测试。这在实践中是可行的,但似乎我们丢失了一些数据,对吧?我们最终只使用 60%的数据来训练模型,这可能会损害我们的模型,尤其是在数据集较小的情况下。K-折交叉验证是一种通过多次回收数据来使用所有数据用于训练和测试的方法。它的工作原理如下:

  1. 将数据分成k个相等(或几乎相等)的部分。

  2. 使用k次训练模型,使用k - 1 个部分作为训练集,剩下的一个作为验证集。

  3. 该模型的最终分数是k个步骤中验证分数的平均值。

图 13.2 展示了四折交叉验证的图片。

图 13.2 K-折交叉验证是一种有用的方法,它回收我们的数据,使其既用于训练又用于验证。在顶部,我们看到经典的训练-验证分割。在底部,我们看到一个四折交叉验证的示意图,我们将数据分成四个相等(或几乎相等)的部分。然后我们训练我们的模型四次。每次我们选择三个部分作为训练集,剩下的一个作为验证集。这个模型的分数是四个验证集上获得的四个分数的平均值。

这种方法是在GridSearchCV中使用的,可以通过输入svm_gs.cv_results_来检查这个过程的结果。我们在这里不会展示结果,因为它们非常长,但你可以查看笔记本中的结果。

摘要

  • Pandas 是一个有用的 Python 包,用于打开、操作和保存数据集。

  • 清理我们的数据是必要的,因为它可能存在一些问题,如缺失值。

  • 特征可以是数值的或分类的。数值特征是数字,例如年龄。分类特征是类别,或类型,例如狗/猫/鸟。

  • 机器学习模型只接受数字,因此为了将分类数据输入到机器学习模型中,我们必须将其转换为数值数据。一种方法是通过独热编码。

  • 在某些情况下,我们可能希望将我们的数值特征也视为分类特征,我们可以通过数据分箱来实现这一点。

  • 使用特征选择来去除我们数据中的不必要特征是很重要的。

  • Scikit-Learn 是一个用于训练、测试和评估机器学习模型的实用包。

  • 在训练模型之前,我们必须将数据分成训练、验证和测试。我们有 Pandas 函数来完成这项工作。

  • 网格搜索是一种用于寻找模型最佳超参数的方法。它包括在(有时是大量)超参数集上训练多个模型。

  • K-fold 交叉验证是一种用于回收数据并将其作为训练和验证使用的方法。它包括在不同数据部分上训练和测试多个模型。

练习

练习 13.1

仓库中包含一个名为 test.csv 的文件。这是一个包含更多乘客的泰坦尼克号的文件,但它没有“Survived”列。

  1. 按照本章所述,预处理这个文件中的数据。

  2. 使用任何模型来预测这个数据集的标签。根据你的模型,你认为有多少乘客幸存了?

  3. 比较本章中所有模型的性能,你认为测试集中有多少乘客实际上幸存了?

附录 A. 练习题解答

第二章:机器学习类型

对于本章的问题,你的答案不需要与我的一致。如果你对这些应用中使用的模型有不同想法,那可能是个好主意!我鼓励你在文献中查找它们,如果它们不存在,尝试实现它们。

练习 2.1

对于以下每个场景,说明它是否是监督学习或无监督学习的例子。解释你的答案。在模糊不清的情况下,选择一个并解释你为什么选择它。

  1. 一个社交网络上的推荐系统,向用户推荐潜在的朋友

  2. 一个新闻网站上按主题划分新闻的系统

  3. Google 自动补全功能的句子

  4. 一个在线零售商上的推荐系统,根据用户的过去购买历史向用户推荐购买商品

  5. 一个信用卡公司中捕捉欺诈交易的系统

解答

根据你对问题和数据集的解释,每个这些都可以被认为是监督学习或无监督学习的例子。完全没问题(并且鼓励!)有不同的答案,只要背后的推理是正确的。

  1. 这也是一个监督学习和无监督学习的例子。监督学习:对于特定用户,我们可以构建一个分类模型,其中其他每个用户的标签如果是潜在朋友则为正,如果不是潜在朋友则为负。

  2. 这也是一个监督学习和无监督学习的例子。监督学习:一个分类模型,其中每篇新闻文章的标签是主题,例如政治、体育或科学。无监督学习:我们可以对文章进行聚类,然后手动检查每个聚类的主题是否相似。如果相似,那么我们可以手动通过最常见的主题为每个聚类打标签。还有一些更高级的无监督学习技术,如潜在狄利克雷分配,你可以在这个视频中学习到:www.youtube.com/watch?v=T05t-SqKArY

  3. 这个例子更偏向于监督学习任务。我们可以构建一个分类模型,其中特征是用户最后输入的几个单词,标签是他们将要输入的下一个单词。这样,模型的预测就是我们将建议给用户的单词。

  4. 这与 a)类似,它可以被认为是一个监督学习或无监督学习问题。监督学习:对于特定用户,我们可以为所有产品构建一个分类模型,其中对于每个产品,我们预测用户是否会购买它。我们也可以构建一个回归模型,其中我们预测用户将在这个特定产品上花费多少钱。无监督学习:我们可以对用户进行聚类。如果一个用户购买了一个产品,我们可以向该聚类中的其他用户推荐相同的产品。我们也可以对产品进行聚类,如果一个用户购买了一个产品,我们推荐同一聚类中的产品。

  5. 这个任务更倾向于监督学习。我们可以构建一个分类模型,根据交易的特性预测某个交易是否是欺诈的。它也可以被视为一个无监督学习任务,其中我们聚类交易,那些被留下的异常值有更高的可能性是欺诈的。

练习 2.2

对于以下机器学习的应用,你会使用回归还是分类来解决它?解释你的答案。在存在歧义的情况下,选择一个并解释你为什么选择了它。

  1. 一个在线商店预测用户在其网站上会花费多少钱

  2. 一个语音助手解码语音并将其转换为文本

  3. 从特定公司买卖股票

  4. YouTube 向用户推荐视频

解答

  1. 回归,因为我们试图预测用户花费的金额,这是一个数值特征。

  2. 分类,因为我们试图预测用户所说的句子是否是针对 Alexa 的,这是一个分类特征。

  3. 这可能是回归或分类。如果我们试图预测预期的收益或预期的风险以帮助我们做出决策,那么它是回归。如果我们试图预测我们是否应该购买股票,那么它是分类。

  4. 这又可以再次是回归或分类。如果我们试图预测用户观看视频的时间以推荐它,那么它是回归。如果我们试图预测用户是否会观看视频,那么它是分类。

练习 2.3

你的任务是构建一辆自动驾驶汽车。至少给出三个你必须解决的机器学习问题示例。在每个例子中,解释你是否使用监督学习/无监督学习,如果是监督学习,是否使用回归或分类。如果你使用其他类型的机器学习,解释这些类型以及为什么。

解答

  • 一个分类模型,根据图像确定是否存在行人、停车标志、车道、其他车辆等。这是一个称为计算机视觉的机器学习大领域,我强烈建议你进一步探索!

  • 与前一个类似的分类模型,它根据汽车中所有不同传感器(激光雷达等)的信号来确定汽车周围的对象。

  • 一个机器学习模型,用于找到到达我们期望目的地的最近路径。这既不是精确的监督学习,也不是无监督学习。这里还可以使用一些更经典的的人工智能算法,例如 A*(A-星)搜索算法。

第三章:在点附近画线:线性回归

练习 3.1

一个网站已经训练了一个线性回归模型来预测用户将在网站上花费的分钟数。他们得到的公式是

= 0.8d + 0.5m + 0.5y + 0.2a + 1.5

其中 是预测的分钟数,而 dmya 是指示变量(即,它们只取 0 或 1 的值),定义如下:

  • d 是一个变量,表示用户是否在使用桌面设备。

  • m 是一个变量,表示用户是否在使用移动设备。

  • y 是一个变量,表示用户是否年轻(21 岁以下)。

  • a 是一个变量,表示用户是否是成年人(21 岁或以上)。

示例:如果用户 30 岁,并且使用桌面设备,那么 d = 1,m = 0,y = 0,a = 1。

如果一个 45 岁的用户用手机查看网站,他们预计会在网站上花费多少时间?

解答

在这种情况下,变量的值如下:

  • d = 0 因为用户不在桌面设备上。

  • m = 1 因为用户在使用移动设备。

  • y = 0 因为用户不是 21 岁以下。

  • a = 1 因为用户年龄超过 21 岁。

当我们将它们代入公式时,我们得到

= 0.8 · 0 + 0.5 · 1 + 0.5 · 0 + 0.2 · 1 + 1.5 = 2.2。

这意味着模型预测这位用户将在网站上花费 2.2 分钟。

练习 3.2

假设我们在一个医学数据集上训练了一个线性回归模型。该模型预测患者的预期寿命。模型会为我们的数据集中的每个特征分配一个权重。

a) 对于以下数量,说明你认为附加到这个数量的权重是正数、负数还是零。注意:如果你认为权重是一个非常小的数,无论是正数还是负数,你可以说零。

  1. 患者每周的锻炼小时数

  2. 患者每周吸烟的数量

  3. 患者家庭成员中患有心脏病的人数

  4. 患者的兄弟姐妹数量

  5. 患者是否曾住院

b) 模型还有一个偏差。你认为偏差是正数、负数还是零?

解答

a) 我们将基于一般的医学知识进行一些概括。对于特定的患者,以下情况不一定成立,但我们将假设它们对整个人群是成立的:

  1. 预期一个经常锻炼的患者比不锻炼的类似患者寿命更长。因此,这个权重应该是正数。

  2. 一位每周吸烟很多支的病人预期寿命会比不吸烟的类似病人短。因此,这个权重应该是负数。

  3. 一位有很多家庭成员患有心脏病的病人,患心脏病的可能性更高,因此他们预期寿命会比没有这些疾病的类似病人短。因此,这个权重应该是负数。

  4. 兄弟姐妹的数量通常与预期寿命无关,因此我们预计这个权重将是一个非常小的数字,或者为零。

  5. 一位过去曾经住院的病人可能之前就有健康问题。因此,他们的预期寿命会比之前从未住院的类似病人短。因此,这个权重应该是负数。当然,住院可能是因为不影响预期寿命的原因(例如骨折),但平均来说,我们可以这样说,如果病人过去曾经去过医院,他们有更高的可能性有健康问题。

b) 偏差是对一个所有特征都为零的病人的预测(即一个不吸烟、不运动、没有心脏病家族成员、没有兄弟姐妹且从未住院的病人)。因为这个病人预期会活很多年,所以这个模型的偏差必须是正数。

练习 3.3

以下是一组房屋数据集,包括面积(平方英尺)和价格(美元)。

面积(s) 奖金(p)
房屋 1 100 200
房屋 2 200 475
房屋 3 200 400
房屋 4 250 520
房屋 5 325 735

假设我们已经训练了一个模型,该模型根据房屋面积预测价格如下:

= 2s + 50

  1. 计算该模型在数据集上的预测结果。

  2. 计算该模型的平均绝对误差。

  3. 计算该模型的均方根误差。

解答

  1. 基于模型的预测价格如下:

    • 房屋 1: = 2 · 100 + 50 = 250

    • 房屋 2: = 2 · 200 + 50 = 450

    • 房屋 3: = 2 · 200 + 50 = 450

    • 房屋 4: = 2 · 250 + 50 = 550

    • 房屋 5: = 2 · 325 + 50 = 700

  2. 平均绝对误差是

  3. 均方误差是

因此,均方根误差是 √1550 = 39.37。

练习 3.4

我们的目标是使用本章学到的技巧将方程 ŷ = 2x + 3 的直线移动到点 (x, y) = (5, 15) 附近。对于以下两个问题,使用学习率 η = 0.01。

  1. 应用绝对技巧修改上述行,使其更接近该点。

  2. 应用平方技巧修改上述行,使其更接近该点。

解答

该模型在这一点上的预测是 ŷ = 2 · 5 + 3 = 13。

  1. 因为预测值是 13,小于标签值 15,所以这个点位于线下方。

    在这个模型中,斜率是 m = 2,y 截距是 b = 3。绝对技巧涉及将 x**η = 5 · 0.01 = 0.05 添加到斜率,将 η = 0.01 添加到 y 截距,从而得到具有以下方程的模型

    ŷ = 2.05x + 3.01.

  2. 平方技巧涉及将 (yŷ)x**η = (15 – 13) · 5 · 0.01 = 0.1 添加到斜率,将 (yŷ)η = (15 – 13) · 0.01 = 0.02 添加到 y 截距,从而得到具有以下方程的模型

    ŷ = 2.1x + 3.02.

第四章:优化训练过程:欠拟合、过拟合、测试和正则化

练习 4.1

我们在同一数据集上使用不同的超参数训练了四个模型。在下面的表中,我们记录了每个模型的训练和测试误差。

模型 训练误差 测试误差
1 0.1 1.8
2 0.4 1.2
3 0.6 0.8
4 1.9 2.3
  1. 对于这个数据集,你会选择哪个模型?

  2. 哪个模型看起来像是对数据进行欠拟合?

  3. 哪个模型看起来像是对数据进行过拟合?

解答

  1. 最佳模型是测试误差最小的模型,即模型 3。

  2. 模型 4 看起来像是对数据进行欠拟合,因为它有较大的训练和测试误差。

  3. 模型 1 和 2 看起来像是对数据进行过拟合,因为它们的训练误差小,但测试误差大。

练习 4.2

我们给出了以下数据集:

x y
1 2
2 2.5
3 6
4 14.5
5 34

我们训练的预测 y 值为 ŷ 的多项式回归模型

ŷ = 2x² 5x + 4.

如果正则化参数 λ = 0.1,并且我们用来训练这个数据集的错误函数是平均绝对误差(MAE),确定以下内容:

  1. 我们模型(使用 L1 范数)的 lasso 回归误差

  2. 我们模型(使用 L2 范数)的岭回归误差

解答

首先,我们需要找到预测值来计算模型的平均绝对误差。在下面的表中,我们可以找到使用公式 ŷ = 2x² 5x + 4 计算的预测值,以及预测值和标签之间的差的绝对值 |yŷ|。

x y ŷ y – ŷ
1 2 1 1
2 2.5 2 0.5
3 6 7 1
4 14.5 16 1.5
5 34 29 5

因此,平均绝对误差是第四行数字的平均值

  1. 首先,我们需要找到多项式的 L1 范数。这是非常数系数的绝对值之和,即 |2| + |–5| = 7。为了找到模型的 L1 正则化成本,我们需要将平均绝对误差和 L1 范数乘以正则化参数相加,得到 1.8 + 0.1 · 7 = 2.5。

  2. 以类似的方式,我们通过将非常数系数的平方相加来找到多项式的 L1 范数,即 2² + (–5)² = 29。与之前一样,模型的 L2 正则化成本是 1.8 + 0.1 · 29 = 4.7。

第五章:使用线分割我们的点:感知器算法

练习 5.1

以下是一个数据集,其中包含已测试 COVID-19 阳性或阴性的患者。他们的症状是咳嗽 (C)、发烧 (F)、呼吸困难 (B) 和疲劳 (T)。

咳嗽 (C) 发烧 (F) 呼吸困难 (B) 疲劳 (T) 诊断 (D)
患者编号 1 X X X 病例
患者编号 2 X X X 病例
患者编号 3 X X X 病例
患者编号 4 X X X 病例
患者编号 5 X X 健康
患者编号 6 X X 健康
患者编号 7 X 健康
患者编号 8 X 健康

构建一个分类此数据集的感知器模型。

提示:你可以使用感知器算法,但你可能能够通过直观地找到一个有效的感知器模型。

解答

如果我们计算每个患者有多少症状,我们会注意到,病例患者表现出三个或更多的症状,而健康患者表现出两个或更少的症状。因此,以下模型可以用来预测诊断 D

= step(C + F + B + T – 2.5)

练习 5.2

考虑将预测 ŷ = step(2x[1] + 3x[2] – 4) 分配给点 (x[1],x[2]) 的感知器模型。此模型具有方程 2x[1] + 3x[2] – 4 = 0 的边界线。我们有一个点 p = (1, 1),其标签为 0。

  1. 验证点 p 是否被模型错误分类。

  2. 计算模型在点 p 处产生的感知器误差。

  3. 使用感知器技巧获得一个新的模型,该模型仍然错误地分类 p,但产生的误差更小。你可以使用 η = 0.01 作为学习率。

  4. 找到新模型在点 p 处给出的预测,并验证得到的感知器误差小于原始误差。

解答

  1. p 的预测为

    ŷ = step(2x[1] + 3x[2] – 4) = step(2 · 1 + 3 · 1 – 4) = step(1) = 1。

    因为点的标签是 0,所以点被错误分类。

  2. 感知器误差是得分的绝对值。得分为 2x[1] + 3x[2] – 4 = 2 · 1 + 3 · 1 – 4 = 1,因此感知器误差为 1。

  3. 模型的权重是 2、3 和 –4,点的坐标是 (1, 1)。感知器技巧执行以下操作:

    • 将 2 替换为 2 – 0.01 · 1 = 1.99

    • 将 3 替换为 3 – 0.01 · 1 = 2.99

    • 将 –4 替换为 –1 – 0.01 · 1 = –4.01

    因此,新模型是预测 ŷ = step(1.99x[1] + 2.99x[2] – 4.01) 的模型。

  4. 注意,在我们当前的位置,新的预测是 ŷ = step(1.99x[1] + 2.99x[2] – 4.01) = step(0.97) = 0,这意味着模型仍然错误地分类了该点。然而,新的感知器误差是 |1.99 · 1 + 2.99 · 1 – 4.01| = 0.97,这小于之前的误差 1。

练习 5.3

感知器对于构建逻辑门(如 AND 和 OR)特别有用。

  1. 构建一个模拟 AND 门的感知器。换句话说,构建一个感知器来拟合以下数据集(其中 x[1],x[2] 是特征,y 是标签):

    x[1] x[2] y
    0 0 0
    0 1 0
    1 0 0
    1 1 1
  2. 类似地,构建一个感知器来模拟 OR 门,给定以下数据集:

    x[1] x[2] y
    0 0 0
    0 1 1
    1 0 1
    1 1 1
  3. 证明对于以下数据集,不存在模拟 XOR 门的感知器:

    x[1] x[2] y
    0 0 0
    0 1 1
    1 0 1
    1 1 0

解答

为了简单起见,我们在下面的图中绘制了数据点。

注意,感知器分类器恰好是上述图中将黑白点分开的线。

对于 AND 和 OR 数据集,我们可以很容易地用线分开黑白点,如以下所示。

  1. 许多方程适用于分离 AND 数据集的线。我们将选择方程为 x[1] + x[2] – 1.5 的线。因此,对这一数据集进行分类的感知器做出的预测是 ŷ = step(x[1] + x[2] – 1.5)。

  2. 类似地,对于 OR 数据集,许多方程都适用,我们选择方程为 x[1] + x[2] – 0.5 的线。预测的方程是 ŷ = step(x[1] + x[2] – 0.5)。

  3. 注意,使用单条线无法分离 XOR 数据集。因此,没有感知器模型能够完美地拟合 XOR 数据集。然而,感知器的组合可以分离这个数据集。这些也被称为多层感知器,或神经网络,我们将在第十章中看到它们。如果你好奇,可以看看练习 10.2。

第六章:分割点的连续方法:逻辑分类器

练习 6.1

一位牙医在一个患者数据集上训练了一个逻辑分类器,以预测他们是否有蛀牙。模型确定患者有蛀牙的概率是

σ(d + 0.5c – 0.8),

其中

  • d 是一个变量,表示患者过去是否有过蛀牙,并且

  • c 是一个变量,表示患者是否吃糖果。

例如,如果患者吃糖果,则 c = 1,如果不吃,则 c = 0。那么,一个既吃糖果又去年接受过蛀牙治疗的患者今天有蛀牙的概率是多少?

解答

如果患者吃糖果,则 c = 1。如果患者去年接受了蛀牙治疗,则 d = 1。因此,根据模型,患者有蛀牙的概率是

σ(1 + 0.5 · 1 – 0.8) = σ(0.7) = 0.668。

练习 6.2

考虑将预测 ŷ = σ(2x[1] + 3x[2] – 4) 分配给点 (x[1], x[2]) 的逻辑分类器,以及点 p = (1, 1) 的标签为 0。

  1. 计算模型对点 p 的预测 ŷ

  2. 计算模型在点 p 处产生的对数损失。

  3. 使用逻辑技巧获得一个产生更小对数损失的新的模型。你可以使用 η = 0.1 作为学习率。

  4. 找到新模型在点 p 处的预测值,并验证得到的对数损失小于原始值。

解答

  1. 预测值为 ŷ = σ(2 · 1 + 3 · 1 – 4) = σ(1) = 0.731

  2. 对数损失为

    log loss = –y ln (ŷ) – (1 – y) ln (1 – ŷ)

    = –0 ln (0.731) – (1 – 0) ln (1 – 0.731)

    = 1.313。

  3. 回想一下,对于预测 ŷ = σ(w[1]x[1] + w[1]x[1] + b) 的逻辑回归模型,感知器技巧给出了以下新的权重:

    • w[1]' = w[1] + η(yŷ) x[1] 对于 i = 1,2

    • b**' = b + η(yŷ) 对于 i = 1,2

    这些是插入到先前公式中的值:

    • y = 0

    • ŷ = 0.731

    • w[1] = 2

    • w[2] = 3

    • b = –4

    • η = 0.1

    • x[1] = 1

    • x[2] = 1

    我们得到了以下新的权重,用于我们的分类器:

    • w[1]' = 2 + 0.1 · (0 – 0.731) · 1 = 1.9269

    • w[2]' = 3 + 0.1 · (0 – 0.731) · 1 = 2.9269

    • b = –4 + 0.1 · (0 – 0.731) = –4.0731

    因此,我们的新分类器是做出预测 ŷ = σ(1.9269x[1] + 2.9269x[2] – 4.0731) 的那个。

    在点 p 处的预测是 ŷ =σ(1.9269 · 1 + 2.9269 · 1 – 4.0731) = 0.686。注意,因为标签是 0,预测值从原始的 0.731 提高到了实际的 0.686。

  4. 这个预测的对数损失为 –y ln(ŷ) – (1 – y) ln(1 – ŷ) = –0 ln(0.686) – (1 – 0) ln(1 – 0.686) = 1.158。注意,这个值小于原始的对数损失 1.313。

练习 6.3

使用第 6.2 节中的第一个模型,构建一个预测值为 0.8 的点。

提示:首先找到给出预测值为 0.8 的分数,并记住预测值是 ŷ = σ(分数)。

解答

首先,我们需要找到一个分数,使得 σ(分数) = 0.8。这相当于

回想一下,对于点 (x[1], x[2]),分数是 2x[1] + 3x[2] – 4。许多点 (x[1], x[2]) 满足分数为 1.386,但为了方便起见,我们特别选择一个 x[2] = 0 的点。我们需要解方程 2x[1] + 3 · 0 – 4 = 1.386,其解为 x[1] = 2.693。因此,给出预测值为 0.8 的点是 (2.693, 0)。

第七章:如何衡量分类模型?准确率及其朋友

练习 7.1

一个视频网站已经确定一个特定用户喜欢动物视频,而且其他什么也不喜欢。在下一张图中,我们可以看到当用户登录网站时收到的推荐。

如果这是我们对模型拥有的所有数据,请回答以下问题:

  1. 模型的准确率是多少?

  2. 模型的召回率是多少?

  3. 模型的精确率是多少?

  4. 模型的 F[1]-score 是多少?

  5. 你会认为这是一个好的推荐模型吗?

解答

首先,让我们写出混淆矩阵。在这种情况下,我们将关于动物的视频标记为 positive,将推荐的视频标记为 predicted positive

  • 有四个推荐视频。其中三个是关于动物的,这意味着它们是好的推荐。另一个不是关于动物的,所以它是假阳性。

  • 有六个不推荐的视频。其中两个是关于动物的,本应被推荐。因此,它们是假阴性。其他四个不是关于动物的,所以不推荐它们是正确的。

因此,混淆矩阵如下:

预测阳性(推荐) 预测阴性(不推荐)
正面(关于动物) 3 2
负面(非关于动物) 1 4

现在我们可以计算指标了。

  1. 这是一个主观答案。具有这些指标的医学模型可能不够好。然而,如果一个推荐模型的准确度、精确度和召回率都相当不错,那么它被认为是一个好的模型,因为在推荐模型中犯几个错误并不那么关键。

练习 7.2

找出具有以下混淆矩阵的医学模型的灵敏度和特异性:

预测患病 预测健康
患病 120 22
健康 63 795

解答

灵敏度是正确预测的患病人数除以总患病人数。这是 120/142= 0.845。

特异性是正确预测的健康人数除以总健康人数。这是 795/858= 0.927。

练习 7.3

对于以下模型,确定哪种错误更严重,是假阳性还是假阴性。基于此,确定在评估每个模型时,我们应该强调哪个指标,是精确度还是召回率。

  1. 一个电影推荐系统,用于预测用户是否会观看电影。

  2. 在自动驾驶汽车中使用的图像检测模型,用于检测图像中是否包含行人。

  3. 一个家庭语音助手,预测用户是否向它下达了命令。

解答

注意:在所有以下模型中,假阴性和假阳性都是不好的,我们希望避免两者。然而,我们展示了一个关于哪个更糟糕的论点。这些都是概念性问题,所以如果你有不同的想法,只要你能很好地论证,它就是有效的!这些是在数据科学家团队中出现的讨论类型,拥有健康观点和支撑每个观点的论据非常重要。

  1. 在这个模型中,我们将用户想要观看的电影标记为正面。任何我们推荐用户不想要观看的电影都称为假阳性。当有用户想要观看的电影,但我们没有推荐时,这种情况称为假阴性。哪种情况更糟,假阴性还是假阳性?因为主页显示了众多推荐,而用户通常忽略大部分,所以这个模型有很多假阴性,这对用户体验影响不大。然而,如果有一部用户非常想看的好电影,向他们推荐它至关重要。因此,在这个模型中,假阴性比假阳性更糟,所以我们应该使用召回率来评估这个模型。

  2. 在这个模型中,我们将行人的存在标记为正面。当没有行人,但汽车认为有行人时,这种情况称为假阳性。当汽车没有检测到汽车前方行人时,这种情况称为假阴性。在假阴性的情况下,汽车可能会撞到行人。在假阳性的情况下,汽车可能会不必要地刹车,这可能会导致或不会导致事故。尽管两者都很严重,但撞到行人更糟糕。因此,在这个模型中,假阴性比假阳性更糟,所以我们应该使用召回率来评估这个模型。

  3. 在这个模型中,我们将语音命令标记为正面。当用户没有与语音助手交谈,但语音助手响应时,这种情况称为假阳性。当用户与语音助手交谈,但语音助手没有响应时,这种情况称为假阴性。作为一个个人选择,我更喜欢重复对我的语音助手说话,而不是让她突然对我说话。因此,在这个模型中,假阳性比假阴性更糟,所以我们应该使用精确度来评估这个模型。

练习 7.4

我们给出了以下模型:

  1. 一个基于汽车摄像头图像检测行人的自动驾驶汽车模型

  2. 一个基于患者症状诊断致命疾病的医学模型

  3. 一个基于用户之前观看的电影推荐电影的推荐系统

  4. 一个根据用户的语音命令确定用户是否需要帮助的语音助手

  5. 一个基于电子邮件中文字的垃圾邮件检测模型,用于判断一封电子邮件是否为垃圾邮件

我们的任务是使用F[β]-分数来评估这些模型。然而,我们没有给出使用β的值。您会使用什么值的β来评估每个模型?

解决方案

记住,对于精度比召回率更重要的模型,我们使用小值的βF[β]-分数。相比之下,对于召回率比精度更重要的模型,我们使用大值的βF[β]-分数。

note 如果你的得分与这个解决方案不同,那完全没问题,只要你有论据来说明精确度和召回率哪个更重要,以及你选择的 β 值。

  • 对于自动驾驶汽车和医疗模型,召回率非常重要,因为我们希望很少出现误判。因此,我会使用一个较大的 β 值,例如 4。

  • 对于垃圾邮件检测模型,精确度很重要,因为我们希望很少出现误报。因此,我会使用一个较小的 β 值,例如 0.25。

  • 对于推荐系统,召回率更重要(参见练习 7.3),尽管精确度也很重要。因此,我会使用一个较大的 β 值,例如 2。

  • 对于语音助手,精确度更重要,尽管召回率也很重要(参见练习 7.3)。因此,我会使用一个较小的 β 值,例如 0.5。

图片

第八章:充分利用概率:朴素贝叶斯模型

练习 8.1

对于每一对事件 A 和 B,确定它们是独立的还是依赖的。对于 (a) 到 (d),提供数学解释。对于 (e) 和 (f),提供口头解释。

抛掷三枚公平硬币:

  1. A:第一次投掷正面朝上。B:第三次投掷反面朝上。

  2. A:第一次投掷正面朝上。B:三次投掷中正面朝上的次数是奇数。

    抛掷两枚骰子:

  3. A:第一次显示 1。B:第二次显示 2。

  4. A:第一次显示 3。B:第二次显示的数值比第一次高。

    对于以下内容,提供口头解释。假设对于这个问题,我们生活在一个有季节的地方。

  5. A:外面在下雨。B:今天是星期一。

  6. A:外面在下雨。B:现在是六月。

Solution

以下的一些内容可以通过直觉推断出来。然而,在确定两个事件是否独立时,有时直觉会失效。因此,除非事件显然是独立的,否则我们将坚持检查两个事件 A 和 B 是否独立,如果 P(AB) = P(A) P(B)。

  1. 因为 A 和 B 对应于抛掷不同的硬币,所以它们是独立事件。

  2. 图片 因为公平硬币的翻转会产生两个等可能的情况。在计算 P(B) 时,我们将使用“h”代表正面,“t”代表反面。这样,事件“hth”对应于第一和第三次硬币投掷正面朝上,第二次投掷反面朝上。因此,如果我们抛掷三枚硬币,就有八种等可能的情况:{hhh, hht, hth, htt, thh, tht, tth, ttt}。图片 因为在八种等可能的情况(hhh, hht, hth, htt, thh, tht, tth, ttt)中,只有四种有奇数个正面,即 {hhh, htt, tht, tth}。图片 因为在八种可能的情况中,只有两种满足第一次投掷正面朝上且正面朝上的次数是奇数,即 {hhh, htt}。图片,因此事件 A 和 B 是独立的。

  3. 因为 A 和 B 对应于掷不同的骰子,所以它们是独立事件。

  4. ,因为它对应于掷骰子得到特定的值。的原因如下。注意,两个骰子得分的 36 种等可能的可能性是 {11, 12, 13, …, 56, 66}。在这其中,有六种情况两个骰子显示相同的值。剩下的 30 种对应于 15 种情况,其中第一个值更高,还有 15 种情况,第二个值更高,对称性使得如此。因此,有 15 种情况是第二个骰子的值比第三个骰子高,所以 的原因如下。如果第一个骰子落在 3 上,我们总共有六种等可能的情况,即 {31, 32, 33, 34, 35, 36}。在这六种情况中,有三个情况第二个数字更高。因此,。因为 P(A) P(B) ≠ P(AB),事件 A 和 B 是相关的。

  5. 对于这个问题,我们将假设 A 和 B 是独立的,也就是说,天气不依赖于星期几。这是一个合理的假设,鉴于我们对天气的了解,但如果我们想更确定,我们可以查看天气数据集并通过计算相应的概率来验证这一点。

  6. 因为我们已经假设我们生活在一个有季节变化的地方,所以在北半球六月是夏天,而在南半球是冬天。根据我们居住的地方,冬天可能比夏天下雨更多。因此,我们可以假设事件 A 和 B 是相关的。

练习 8.2

我们有一个办公室,我们必须定期去那里办理一些文件工作。这个办公室有两个职员,Aisha 和 Beto。我们知道 Aisha 每周工作三天,而 Beto 工作剩下的两天。然而,每周的日程都会变化,所以我们永远不知道 Aisha 会在哪三天,Beto 会在哪两天。

  1. 如果我们随机一天去办公室,Aisha 是职员的概率是多少?

    我们从外面看,注意到职员穿着一件红色毛衣,尽管我们无法确定是哪位职员。我们经常去那个办公室,所以知道 Beto 比 Aisha 更倾向于穿红色。事实上,Aisha 有三分之一的时间会穿红色,而 Beto 有二分之一的时间会穿红色。

  2. 如果我们知道今天职员穿着红色,Aisha 是职员的概率是多少?

解答

让我们为事件使用以下符号:

  • A:职员是 Aisha 的事件

  • B:职员是 Beto 的事件

  • R:职员穿着红色的事件

  1. 因为 Aisha 在办公室工作三天,Beto 工作两天,所以 Aisha 是职员的概率是 ,即 60%。此外,Beto 是职员的概率是 ,即 40%。

  2. 直觉上,因为贝托比艾莎更常穿红色,我们想象店员是艾莎的概率比部分 a) 中的概率低。让我们检查数学是否与我们一致。我们知道店员穿着红色,所以我们需要找到在店员穿着红色的情况下,店员是艾莎的概率。这是 P(A|R).

艾莎穿红色的概率是 图片,所以 图片。贝托穿红色的概率是 图片,所以 图片

我们可以使用贝叶斯定理来获得

图片

类似的计算表明,贝托是店员的可能性是 图片,或 50%。

实际上,艾莎是店员的可能性比部分 a) 中得到的小,所以我们的直觉是对的。

练习 8.3

以下是一个数据集,其中包含已测试为阳性或阴性的 COVID-19 患者。他们的症状是咳嗽 (C)、发烧 (F)、呼吸困难 (B) 和疲劳 (T)。

咳嗽 (C) 发烧 (F) 呼吸困难 (B) 疲劳 (T) 诊断
患者编号 1 X X X 病人
患者编号 2 X X X 病人
患者编号 3 X X X 病人
患者编号 4 X X X 病人
患者编号 5 X X 健康
患者编号 6 X X 健康
患者编号 7 X 健康
患者编号 8 X 健康

本练习的目的是构建一个朴素贝叶斯模型,从症状预测诊断。使用朴素贝叶斯算法找到以下概率:

备注:对于以下问题,未提及的症状对我们来说是完全未知的。例如,如果我们知道患者有咳嗽,但没有任何关于发烧的说明,并不意味着患者没有发烧。

  1. 在患者有咳嗽的情况下,患者生病的概率

  2. 在患者不疲劳的情况下,患者生病的概率

  3. 在患者有咳嗽和发烧的情况下,患者生病的概率

  4. 在患者有咳嗽和发烧,但没有呼吸困难的情况下,患者生病的概率

解答

对于这个问题,我们有以下事件:

  • C:患者有咳嗽的事件

  • F:患者发烧的事件

  • B:患者有呼吸困难的事件

  • T:患者疲劳的事件

  • S:患者被诊断为生病的事件

  • H:患者被诊断为健康的事件

此外,A^c 表示事件 A 的补集(对立事件)。例如,T^c 表示患者不疲劳的事件。

首先,让我们计算 P(S) 和 P(H)。注意,因为数据集包含四个健康和四个生病的患者,这两个(先验)概率都是 图片,或 50%。

  1. 因为有四个病人咳嗽,其中三个是生病的,图片,即 75%。

    同样地,我们可以用以下方式应用贝叶斯定理:首先,我们注意到有四个生病的病人,其中三个有咳嗽。我们还注意到图片,因为有四个健康病人,其中只有一个是咳嗽的。

    现在我们可以使用公式

    图片

  2. 因为有四个病人咳嗽,其中三个是生病的,图片,即 33.3%。

    我们也可以像以前一样使用贝叶斯定理。注意图片,因为四个生病的病人中只有一个不累。同样,四个健康病人中有两个不累,图片

    通过贝叶斯定理,

    图片

  3. CF 表示病人有咳嗽和发烧的事件,因此我们需要计算 P(S|CF)。

    回想一下部分 a)中的图片图片

    现在我们需要计算 P(F|S) 和 P(F|H)。注意,因为有四个生病的病人,其中三个有发烧,图片。同样,四个健康病人中有两个发烧,图片

    我们现在可以使用朴素贝叶斯算法来估计在病人咳嗽和发烧的情况下他们生病的概率。使用第八章“关于两个单词?朴素贝叶斯算法”部分中的公式,我们得到

    图片

  4. 对于这个练习,我们需要找到 P(S|CFB^c)

    注意,因为有四个生病的病人,其中只有一个是无呼吸困难,图片。同样,有四个健康病人,其中三个无呼吸困难,图片

    和以前一样,我们可以使用朴素贝叶斯算法。

图片

第九章:通过提问分割数据:决策树

练习 9.1

在以下垃圾邮件检测决策树模型中,确定来自你妈妈的标题为“请去商店,有促销活动”的电子邮件是否会被分类为垃圾邮件。

图片

解答

首先,我们检查发送者是否未知。因为发送者是我们的妈妈,所以发送者不是未知的。因此,我们选择右边的分支。我们必须检查电子邮件是否包含单词“sale”。电子邮件确实包含单词“sale”,所以分类器(错误地)将其分类为垃圾邮件。

练习 9.2

我们的目标是构建一个决策树模型来决定信用卡交易是否欺诈。我们使用以下信用卡交易数据集,具有以下特征:

  • :交易的价值。

  • 批准的供应商:信用卡公司有一份批准的供应商名单。这个变量表示供应商是否在这个名单上。

已批准的供应商 欺诈
交易 1 $100 未批准
交易 2 $100 已批准
交易 3 $10,000 已批准
交易 4 $10,000 未批准
交易 5 $5,000 已批准
交易 6 $100 已批准

根据以下规范构建决策树的第一节点:

  1. 使用基尼不纯度指数

  2. 使用熵

解决方案

在这两种情况下,最佳分割是通过使用已批准的供应商特征获得的,如下一图所示。

让我们称交易为 T[1], T[2], T[3], T[4], T[5], 和 T[6]。

首先,让我们看看我们可以做出的所有以下分割。使用已批准的供应商进行分割是容易的,因为这是一个有两个类别的分类变量。值列更复杂——我们可以用它以两种可能的方式分割数据。一种是在 $100 和 $5,000 之间的某个值作为截止值,另一种是在 $5,000 和 $10,000 之间的某个值作为截止值。总结一下,这些都是所有可能的分割:

  • 值 1:其中截止值介于 $100 和 $5,000 之间。这里的两个类别是 {T[1], T[2], T[6]} 和 {T[3], T[4], T[5]}。

  • 值 2:其中截止值是介于 $5,000 和 $10,000 之间的某个值。这里的两个类别是 {T[1], T[2], T[5], T[6]} 和 {T[3], T[4]}。

  • 已批准的供应商:两个类别是“已批准”和“未批准”,或者等价地,{T[2], T[3], T[5], T[6]} 和 {T[1], T[4]}。

  1. 让我们计算以下四个分割中的每个分割的基尼不纯度指数:

    值 1:介于 $100 和 $5,000 之间的截止值

    注意,对于第一类 {T[1], T[2], T[6]},欺诈列中的标签是 {“是”, “否”, “否”}。这个分割的基尼不纯度指数是

    注意,对于第二类 {T[3], T[4], T[5]},欺诈列中的标签是 {“否”, “是”, “是”}。这个分割的基尼不纯度指数是

    因此,这个分割的加权基尼不纯度指数是

    值 2:介于 $5,000 和 $10,000 之间的截止值

    对于第一类 {T[1], T[2], T[5], T[6]},欺诈列中的标签是 {“是”, “否”, “是”, “否”}。这个分割的基尼不纯度指数是

    注意,对于第二类 {T[3], T[4]},欺诈列中的标签是 {“否”, “是”}。这个分割的基尼不纯度指数是

    因此,这个分割的加权基尼不纯度指数是 .

    已批准的供应商:

    对于第一类 {T[2], T[3], T[5], T[6]},欺诈列中的标签是 {“否”, “否”, “是”, “否”}。这个分割的基尼不纯度指数是

    对于第二类 {T[1], T[4]},欺诈列中的标签是 {“是”, “是”}。这个分割的基尼不纯度指数是 1 – 1² = 0。

    因此,这个分割的加权基尼不纯度指数为 图片

    注意到这三个值中,最低的是 0.25,对应于“批准的供应商”列。这意味着分割这些数据的最佳方式是使用“批准的供应商”特征。

  2. 对于这部分,我们已经做了大部分繁重的工作。我们将遵循与部分 a) 相同的程序,只是在每个阶段计算熵而不是基尼不纯度指数。

    值 1:$100 和 $5,000 之间的截断值

    集合 {“是”,“否”,“否”} 的熵为 图片

    集合 {“否”,“是”,“是”} 的熵也是 图片

    因此,这个分割的加权熵为 图片

    值 2:$5,000 和 $10,000 之间的截断值

    集合 {“是”,“否”,“是”,“否”} 的熵为 图片

    集合 {“否”,“是”} 的熵为 图片

    因此,这个分割的加权熵为 图片

    批准的供应商:

    集合 {“否”,“否”,“是”,“否”} 的熵为 图片

    集合 {“是”,“是”} 的熵为 图片

    因此,这个分割的加权熵为 图片

    注意到在这些中,最小的熵是 0.541,对应于“批准的供应商”列。因此,再次分割这些数据的最佳方式仍然是使用“批准的供应商”特征。

练习 9.3

以下是一个患者数据集,这些患者已检测出 COVID-19 的阳性或阴性。他们的症状是咳嗽 (C)、发热 (F)、呼吸困难 (B) 和疲劳 (T)。

咳嗽 (C) 发热 (F) 呼吸困难 (B) 疲劳 (T) 诊断
患者编号 1 X X X 病人
患者编号 2 X X X 病人
患者编号 3 X X X 病人
患者编号 4 X X X 病人
患者编号 5 X X 健康
患者编号 6 X X 健康
患者编号 7 X 健康
患者编号 8 X 健康

使用准确率,构建一个高度为 1 的决策树(决策树桩),用于分类这些数据。这个分类器在数据集上的准确率是多少?

解决方案

让我们称患者为 P[1] 到 P[8]。病人将用“s”表示,健康的人用“h”表示。

首先注意到第一次分割可以是四个特征 C、F、B 和 T 中的任何一个。让我们首先计算基于特征 C 分割数据的分类器准确率,即基于问题“患者是否有咳嗽?”构建的分类器。

基于 C 特征的分割:

  • 有咳嗽的患者:{P[2], P[3], P[4], P[5]}。他们的标签是 {s, s, s, h}。

  • 无咳嗽的患者:{P[1], P[6], P[7], P[8]}。他们的标签是 {s, h, h, h}。

看这个,我们可以看到最准确的分类器(仅基于 C 特征)是将所有咳嗽的人分类为病态,而没有咳嗽的人分类为健康的分类器。这个分类器正确地将八名患者中的六名(三名病态和三名健康)分类,因此其准确率为 6/8,即 75%。

现在,让我们用同样的方法处理其他三个特征。

根据 F 特征进行分割:

  • 发烧的病人:{P[1], P[2], P[4], P[6], P[7]}. 他们的标签是 {s, s, s, h, h}.

  • 无发烧的病人:{P[3], P[5], P[8]}. 他们的标签是 {s, h, h}.

看这个,我们可以看到最准确的分类器(仅基于 F 特征)是将所有发烧的患者分类为病态,而没有发烧的患者分类为健康的分类器。这个分类器正确地将八名患者中的五名(三名病态和两名健康)分类,因此其准确率为 5/8,即 62.5%。

根据 B 特征进行分割:

  • 表现出呼吸困难的患者:{P[1], P[3], P[4], P[5]}. 他们的标签是 {s, s, s, h}.

  • 没有表现出呼吸困难的患者:{P[2], P[6], P[7], P[8]}. 他们的标签是 {s, h, h, h}.

看这个,我们可以看到最准确的分类器(仅基于 B 特征)是将所有表现出呼吸困难的患者分类为病态,而没有表现出呼吸困难的患者分类为健康的分类器。这个分类器正确地将八名患者中的六名(三名病态和三名健康)分类,因此其准确率为 6/8,即 75%。

根据 T 特征进行分割:

  • 疲劳的病人:{P[1], P[2], P[3], P[5], P[8]}. 他们的标签是 {s, s, s, h, h}.

  • 无疲劳的病人:{P[4], P[5], P[7]}. 他们的标签是 {s, h, h}.

看这个,我们可以看到最准确的分类器(仅基于 F 特征)是将所有疲劳的病人分类为病态,而没有疲劳的病人分类为健康的分类器。这个分类器正确地将八名患者中的五名(三名病态和两名健康)分类,因此其准确率为 5/8,即 62.5%。

注意,给我们带来最佳准确率的两个特征是 C(咳嗽)和 B(呼吸困难)。决策树将随机选择这两个中的一个。让我们选择第一个,C。使用 C 特征分割数据后,我们得到以下两个数据集:

  • 有咳嗽的病人:{P[2], P[3], P[4], P[5]}. 他们的标签是 {s, s, s, h}.

  • 无咳嗽的病人:{P[1], P[6], P[7], P[8]}. 他们的标签是 {s, h, h, h}.

这给我们提供了一个深度为 1 的树,该树以 75% 的准确率对数据进行分类。该树在下一张图中展示。

第十章:组合构建块以获得更多力量:神经网络

练习 10.1

下图展示了一个所有激活都是 Sigmoid 函数的神经网络。

这个神经网络会对输入(1,1)做出什么预测?

解答

让我们称中间节点的输出为η[1]和η[2]。这些计算如下:

h[1] = σ(1 · x[1] – 2 · x[2] – 1)

h[2] =σ(–1 · x[1] + 3 · x[2] – 1)

x[1] = 1 和x[2] = 1 代入,我们得到以下结果:

h[1] = σ(–2) = 0.119

h[2] = σ(1) = 0.731

最终层是

ŷ = σ(–1 · h[1] + 2 · h[2] + 1).

用之前得到的h[1]和h[2]的值替换,我们得到

ŷ = σ(–0.119 + 2 · 0.731 + 1) = σ(2.343) = 0.912.

因此,神经网络的输出是 0.912。

练习 10.2

正如我们在练习 5.3 中学到的,无法构建一个模仿 XOR 门的感知器。换句话说,无法使用感知器拟合以下数据集并获得 100%的准确率:

x[1] x[2] y
0 0 0
0 1 1
1 0 1
1 1 0

这是因为数据集不是线性可分的。使用深度为 2 的神经网络,构建一个模仿之前展示的 XOR 门的感知器。作为激活函数,使用步进函数而不是 sigmoid 函数以获得离散输出。

提示:使用训练方法来做这个可能很难;相反,尝试通过观察权重来尝试。尝试(或在网上搜索如何)使用 AND、OR 和 NOT 门构建一个 XOR 门,并使用练习 5.3 的结果来帮助你。

解答

注意以下 AND、OR 和 NOT 门的组合形成了一个 XOR 门(其中 NAND 门是 AND 门和 NOT 门的组合)。

下面的真值表说明了这一点。

x[1] x[2] h[1] = x[1] OR x[2] h[2] = x[1] NAND x[2] h[1] AND η[2] x[1] XOR x[2]
0 0 0 1 0 0
0 1 1 1 1 1
1 0 1 1 1 1
1 1 1 0 0 0

正如我们在练习 5.3 中所做的那样,这里有一些感知器,它们模仿了 OR、NAND 和 AND 门。NAND 门是通过取 AND 门中所有权重的反得到。

将这些组合在一起,我们得到下一张图中所示的神经网络。

我鼓励你验证这个网络确实模仿了 XOR 逻辑门。这是通过将四个向量(0,0)、(0,1)、(1,0)、(1,1)输入网络并验证输出为 0、1、1、0 来完成的。

练习 10.3

在“神经网络图形表示”部分的结尾,我们看到了图 10.13 中的神经网络,由于激活函数的原因,它不适合表 10.1 中的数据集,因为点(1,1)被错误分类。

  1. 验证这是否是情况。

  2. 改变权重,使神经网络能够正确分类每个点。

解答

  1. 对于点(x[a],x[b]) = (1, 1),预测如下:

    C = σ(6 · 1 + 10 · 1 – 15) = σ(1) = 0.731

    F = σ(10 · 1 + 6 · 1 – 15) = σ(1) = 0.731

    ŷ = σ(1 · 0.731 + 1 · 0.731 – 1.5) = σ(–0.39) = 0.404

    因为预测值更接近于 0 而不是 1,所以这个点被错误分类。

  2. 将最终节点的偏差降低到小于 2 · 0.731 = 1.461 的任何值都可以。例如,如果这个偏差是 1.4,那么在点(1,1)的预测值将高于 0.5。作为一个练习,我鼓励你验证这个新的神经网络是否正确地预测了剩余点的标签。

第十一章:以风格寻找边界:支持向量机和核方法

练习 11.1

(此练习完成了“距离误差函数”部分所需的计算。)

证明方程 w[1]x[1] + w[2]x[2] + b = 1 和 w[1]x[1] + w[2]x[2] + b = –1 的直线之间的距离恰好是

解答

首先,让我们这样称呼这些线:

  • L[1] 是方程 w[1]x[1] + w[2]x[2] + b = 1 的直线。

  • L[2] 是方程 w[1]x[1] + w[2]x[2] + b = –1 的直线。

注意,我们可以将方程 w[1]x[1] + w[2]x[2] + b = 0 重新写为 ,其斜率为 。任何与此线垂直的线的斜率为 。特别是,方程为 的线与 L[1] 和 L[2] 都垂直。我们将这条线称为 L[3]。

接下来,我们解出 L[3] 与每条线 L[1] 和 L[2] 的交点。L[1] 和 L[3] 的交点是以下方程的解:

我们可以将第二个方程代入第一个方程,得到

,

然后解出 x[1],得到

因此,因为 L[2] 中的每个点都有形式 L[1] 和 L[3] 的交点是坐标为 的点。

类似的计算将表明,L[2] 和 L[3] 的交点是坐标为 的点。

要找到这两个点之间的距离,我们可以使用勾股定理。这个距离是

如此。

练习 11.2

正如我们在练习 5.3 中学到的,不可能构建一个模仿 XOR 门的感知器模型。换句话说,不可能用感知器模型(以 100%的准确率)拟合以下数据集:

x[1] x[2] y
0 0 0
0 1 1
1 0 1
1 1 0

这是因为数据集不是线性可分的。支持向量机(SVM)也有同样的问题,因为 SVM 也是一个线性模型。然而,我们可以使用核函数来帮助我们。我们应该使用什么核函数将这个数据集转换为线性可分的数据集?转换后的 SVM 会是什么样子?

提示:查看“利用多项式方程为你带来好处”部分的例子 2,它解决了一个非常类似的问题。

解答

考虑到二次多项式核,我们得到以下数据集:

x[1] x[2] x[1]² x[1] x[2] x[2]² y
0 0 0 0 0 0
0 1 0 0 1 1
1 1 1 0 0 1
1 1 1 1 1 0

几个分类器在这个修改后的数据集上工作。例如,具有方程 ŷ = step(x[1] + x[2] – 2x[1]x[2] – 0.5) 的分类器正确地分类了数据。

第十二章:结合模型以最大化结果:集成学习

练习 12.1

由三个弱学习器 L[1]、L[2] 和 L[3] 组成的强学习器 L 通过三个弱学习器形成。它们的权重分别为 1、0.4 和 1.2。对于特定点,L[1] 和 L[2] 预测其标签为正,而 L[3] 预测其为负。学习器 L 对此点的最终预测是什么?

解答

因为 L[1] 和 L[2] 预测标签为正,而 L[3] 预测标签为负,所以投票总和为

1 + 0.4 – 1.2 = 0.2。

这个结果是正的,这意味着强学习器预测这个点的标签为正。

练习 12.2

我们正在对大小为 100 的数据集训练 AdaBoost 模型。当前的弱学习器正确分类了 68 个数据点中的 100 个。我们将为这个学习器在最终模型中分配什么权重?

解答

这个权重是 log 概率,即概率的自然对数。概率为 68/32,因为分类器正确分类了 68 个点,错误分类了剩余的 32 个。因此,分配给这个弱学习器的权重是

第十三章:付诸实践:数据工程和机器学习的真实案例

练习 13.1

仓库中包含一个名为 test.csv 的文件。这是一个包含更多 泰坦尼克号 乘客的文件,但它没有“Survived”列。

  1. 按照本章中的方法预处理此文件中的数据。

  2. 使用任何模型来预测此数据集中的标签。根据你的模型,你认为有多少乘客幸存了?

  3. 比较本章中所有模型的性能,你认为测试集中有多少乘客实际上幸存了?

解答

解答位于以下笔记本的末尾:github.com/luisguiserrano/manning/tree/master/Chapter_13_End_to_end_example.

附录 B. 梯度下降背后的数学:使用导数和斜率下山

在这个附录中,我们将介绍梯度下降的数学细节。这个附录相当技术性,理解它不是阅读本书其余部分所必需的。然而,它在这里是为了给那些希望了解一些核心机器学习算法内部工作原理的读者提供一个完整的感观。这个附录所需的数学知识比本书的其他部分要高。更具体地说,需要了解向量、导数和链式法则。

在第 3、5、6、10 和 11 章中,我们使用了梯度下降来最小化我们模型中的误差函数。更具体地说,我们使用了梯度下降来最小化以下误差函数:

  • 第三章:线性回归模型中的绝对值和平方误差函数

  • 第五章:感知器模型中的感知器误差函数

  • 第六章:逻辑分类器中的对数损失

  • 第十章:神经网络中的对数损失

  • 第十一章:SVM 中的分类(感知器)误差和距离(正则化)误差

正如我们在第 3、5、6、10 和 11 章中学到的,误差函数衡量模型做得有多糟糕。因此,找到这个误差函数的最小值——或者至少是一个非常小的值,即使它不是最小值——对于找到一个好的模型将是至关重要的。

我们使用的类比是下山——如图 B.1 所示的埃罗斯特山。场景如下:你正站在山顶的某个地方,你想到达这座山的底部。天气非常多云,所以你看不见你周围很远的地方。你能做的最好的事情是一步一步地从山上下来。你问自己,“如果我只迈出一步,我应该朝哪个方向迈出,才能下得最快?”你找到那个方向,迈出那一步。然后你再次提出相同的问题,再迈出另一步,你重复这个过程很多次。可以想象,如果你总是迈出一步,那一步能让你下得最快,那么你肯定能到达一个低洼的地方。你可能需要一点运气才能真正到达山底,而不是陷入山谷,但我们在“陷入局部最小值”这一节中会处理这个问题。

图 B.1 在梯度下降步骤中,我们想要从被称为埃罗斯特山的山上下来。

在接下来的几节中,我们将描述梯度下降背后的数学,并使用它来帮助我们通过减少它们的误差函数来训练几个机器学习算法。

使用梯度下降来减少函数

梯度下降的数学形式如下:假设你想要最小化函数 f(x[1], x[2], …, x[n]) 在 n 个变量 x[1], x[2], …, x[n] 上的值。我们假设这个函数在每个 n 个变量上都是连续且可导的。

我们目前位于点p,坐标为(p[1], p[2], …, p[n]),我们希望找到函数减少最多的方向,以便采取该步骤。这如图 B.2 所示。为了找到函数减少最多的方向,我们使用函数的梯度。梯度是由函数f相对于每个变量x[1], x[2], …, x[n]的偏导数组成的n-维向量。这个梯度表示为∇f,如下所示:

![AppB_01_E01.png]

梯度是一个指向最大增长方向的向量,即函数增加最多的方向。因此,梯度的负值是函数减少最多的方向。这是我们想要采取的步骤。我们使用在第三章中学到的学习率来确定步长的大小,我们用η表示它。梯度下降步骤包括在负梯度的方向上迈出长度为η|∇f|的步长。因此,如果我们的原始点是p,在应用梯度下降步骤后,我们得到点pηf。图 B.2 说明了我们为减少函数f所采取的步骤。

B-2

图 B.2 我们最初位于点p。我们朝着负梯度的方向迈出一步,最终到达一个新的点。这是函数减少最多的方向。(来源:使用 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher)。

现在我们知道了如何迈出一小步来略微减少函数,我们可以简单地重复这个过程多次来最小化我们的函数。因此,梯度下降算法的伪代码如下:

梯度下降算法的伪代码

目标:最小化函数f

超参数:

  • 迭代次数(重复次数)N

  • 学习率η

流程:

  • 选择一个随机点p[0]。

  • 对于i = 0, …, N – 1:

    • – 计算梯度∇f(p[i])。

    • – 选择点p[i][+1] = p[i] – ηf(p[i])。

  • 以点p[n]结束。

B-3

图 B.3 如果我们多次重复梯度下降步骤,我们有很大的机会找到函数的最小值。在这个图中,p[1]代表起点,p[n]代表使用梯度下降获得的点。(来源:使用 Golden Software, LLC 的 Grapher™辅助创建的图像;www.goldensoftware.com/products/grapher)。

这个过程总是找到函数的最小值吗?不幸的是,不是的。在尝试使用梯度下降最小化函数时可能会出现几个问题,例如陷入局部最小值(一个山谷)。我们将在“陷入局部最小值”这一节中学习一种非常有用的技术来处理这个问题。

使用梯度下降训练模型

现在我们知道了梯度下降如何帮助我们最小化(或者至少,找到函数的小值),在本节中我们将看到如何使用它来训练一些机器学习模型。我们将训练的模型如下:

  • 线性回归(来自第三章)。

  • 感知器(来自第五章)。

  • 逻辑分类器(来自第六章)。

  • 神经网络(来自第十章)。

  • 正则化(来自第四章和第十一章)。这不是一个模型,但我们仍然可以看到梯度下降步骤对使用正则化的模型产生的影响。

我们使用梯度下降训练模型的方式是让 f 成为模型的对应误差函数,并使用梯度下降来最小化 f。误差函数的值在数据集上计算。然而,正如我们在第三章的“我们是一次训练一个点还是多个点”部分、第六章的“随机、小批量和大批量梯度下降”部分以及第十章的“超参数”部分所看到的,如果数据集太大,我们可以通过将数据集分成大小大致相同的小批量来加速训练,并在每一步中选取不同的一个小批量来计算误差函数。

在本附录中,我们将使用以下记号。大多数术语已在第一章和第二章中介绍:

  • 数据集的大小,或行数,是 m

  • 数据集的维度,或列数,是 n

  • 数据集由特征标签组成。

  • 特征m 个向量 x[i] = (x[1](i),*x*[2](i),…,x[n](i^)),对于 i = 1,2,…,m

  • 标签 y[i],对于 i = 1,2,…,m

  • 模型n 个权重向量 w = (w[1],w[2],…,w[n]) 和偏置 b(一个标量)给出(除非模型是神经网络,它将具有更多的权重和偏置)。

  • 预测 ŷ,对于 i = 1,2,…,m

  • 模型的学习率η

  • 数据的小批量B[1],B[2],…,B[l],其中 l 是某个数字。每个小批量具有长度 q。一个小批量中的点(为了方便记号)表示为 x((1)),…,*x*(q),标签为 y[1],…,y[q]。

我们将用于训练模型的梯度下降算法如下:

机器学习模型训练的梯度下降算法

超参数:

  • 轮数(重复次数)N

  • 学习率 η

流程:

  • 随机选择权重 w[1],w[2],…,w[n] 和随机偏置 b

  • 对于 i = 0,…,N – 1:

    • 对于每个小批量 B[1],B[2],…,B[l]。

      • 计算特定小批量的误差函数 f(w, b)。

      • 计算梯度 梯度计算图

      • 将权重和偏差替换如下:

        • w[1] 被替换为 w*[1] 替换图

        • b 被替换为 b 替换图

在接下来的小节中,我们将详细说明以下每个模型和误差函数的此过程:

  • 带有平均绝对误差函数的线性回归模型(下一节)

  • 带有平均平方误差函数的线性回归模型(下一节)

  • 感知器模型带有感知器误差函数(“使用梯度下降训练分类模型”这一节)

  • 带有对数损失函数的逻辑回归模型(“使用梯度下降训练分类模型”这一节)

  • 带有对数损失函数的神经网络(“使用梯度下降训练神经网络”这一节)

  • 带有正则化的模型(“使用梯度下降进行正则化”这一节)

使用梯度下降来训练线性回归模型

在本节中,我们使用梯度下降来训练一个线性回归模型,使用我们之前学过的两个误差函数:平均绝对误差和平均平方误差。回顾第三章,在线性回归中,预测值 ŷ[1],ŷ[1],…,ŷ[q] 由以下公式给出:

感知器误差函数图

我们回归模型的目标是找到权重 w[1],…,w[n],它们产生的预测值与标签非常接近。因此,误差函数通过测量特定权重集下 ŷy 之间的距离来帮助。正如我们在第三章的“绝对误差”和“平方误差”一节中看到的,我们有两种不同的方式来计算这个距离。第一种是绝对值 |ŷy|,第二种是差异的平方 (yŷ)²。第一种产生了平均绝对误差,第二种产生了平均平方误差。让我们分别研究它们。

使用梯度下降来训练线性回归模型以减少平均绝对误差

在本小节中,我们将计算平均绝对误差函数的梯度,并使用它来应用梯度下降并训练一个线性回归模型。平均绝对误差是一种衡量 ŷy 之间距离的方法。它首次在第三章的“绝对误差”一节中定义,其公式如下:

平均绝对误差公式图

为了方便起见,我们将 MAE(w, b, x, y) 简写为 MAE。为了使用梯度下降来减少 MAE,我们需要计算梯度 ∇MAE,它是一个包含 n + 1 个关于 w[1],…,w[n],b 的偏导数的向量,

偏导数计算图

我们将使用链式法则来计算这些偏导数。首先,注意

权重和偏差替换图

函数 f(x) = |x| 的导数是符号函数 ,当 x 为正时为 +1,当 x 为负时为 -1(在 0 处未定义,但为了方便,我们可以将其定义为 0)。因此,我们可以将前面的方程重写为

为了计算这个值,让我们关注方程的最后一部分,即 。由于 ,因此

.

这是因为 w[j] 对 w[i] 的导数,当 j = i 时为 1,否则为 0。因此,在导数上替换,我们得到以下结果:

使用类似的分析,我们可以计算出 MAE(w, b) 对 b 的导数是

梯度下降步骤如下:

梯度下降步骤:

将 (w, b) 替换为 (w**', b**'),其中

注意到有趣的一点:如果小批量的大小 q = 1,并且只包含点 x = (x[1], x[2], …, x[n]),带有标签 y 和预测 ŷ,则步骤定义如下:

将 (w, b) 替换为 (w**', b**'),其中

  • w[j]' = w[j] + η sgn(yŷ)x[j]

  • b' = b + η sgn(yŷ)

这正是我们在第三章“简单技巧”一节中使用的简单技巧来训练我们的线性回归算法。

使用梯度下降法通过减少均方误差来训练线性回归模型

在本小节中,我们将计算均方误差函数的梯度,并利用它来应用梯度下降法并训练线性回归模型。均方误差是衡量 ŷy 之间距离的另一种方式。它首次在第三章“平方误差”一节中定义,其公式为

为了方便,我们将 MSE(w, b, x, y) 简写为 MSE。为了计算梯度 ∇MSE,我们可以遵循与之前描述的均方绝对误差相同的程序,只是 f(x) = x² 的导数是 2x。因此,MSEw[j] 的导数是

类似地,MSE(w, b) 对 b 的导数是

梯度下降步骤:

将 (w, b) 替换为 (w**', b**'),其中

注意再次,如果小批量的大小 q = 1,并且只包含点 x = (x[1], x[2], …, x[n]),带有标签 y 和预测 ŷ,则步骤定义如下:

将 (w, b) 替换为 (w**', b**'),其中

  • w[j]' = w[j] + η(yŷ)x[j]

  • b**' = b + η(yŷ)

这正是我们在第三章“平方技巧”一节中使用的平方技巧来训练我们的线性回归算法。

使用梯度下降法训练分类模型

在本节中,我们学习如何使用梯度下降来训练分类模型。我们将训练的两个模型是感知器模型(第五章)和对数回归模型(第六章)。每个模型都有自己的误差函数,因此我们将分别开发它们。

使用梯度下降来训练感知器模型以减少感知器误差

在本小节中,我们将计算感知器误差函数的梯度,并使用它来应用梯度下降并训练感知器模型。在感知器模型中,预测是 ŷ[1], ŷ[2], …, ŷ[q],其中每个 ŷ[i] 是 0 或 1。为了计算预测,我们首先需要记住第五章中引入的步骤函数 step(x)。这个函数将任何实数 x 作为输入,如果 x < 0 则输出 0,如果 x ≥ 0 则输出 1。它的图示在图 B.4 中。

图 B.4 步骤函数。对于负数,它输出 0,对于非负数,它输出 1。

模型给每个点一个 分数。具有权重 (w[1], w[2], …, w[n]) 和偏置 b 的模型给点 x(i^) = (x[1](i^), x[n](i^), …, x[n](i^)) 的分数是

预测 ŷ[i] 由以下公式给出:

换句话说,如果分数为正,预测为 1,否则为 0。

感知器误差函数被称为 PE(w, b, x, y),我们将它简称为 PE。它首次在第五章的“如何比较分类器?误差函数”一节中定义。根据构造,如果模型做出了错误的预测,它就是一个大数,如果模型做出了正确的预测,它就是一个小数(在这种情况下,实际上为 0)。误差函数的定义如下。

  • PE(w, b, x, y) = 0 如果 ŷ = y

  • PE(w, b, x, y) = |score(w, b, x)| 如果 ŷ y

换句话说,如果点被正确分类,误差为零。如果点被错误分类,误差是分数的绝对值。因此,分数绝对值低的错误分类点产生低误差,而分数绝对值高的错误分类点产生高误差。这是因为点的分数的绝对值与该点与边界的距离成正比。因此,误差低的点是靠近边界的点,而误差高的点是远离边界的点。

为了计算梯度 ∇PE,我们可以使用之前的相同规则。我们应该注意的一件事是,绝对值函数 |x| 的导数在 x ≥ 0 时为 1,在 x < 0 时为 0。这个导数在 0 处未定义,这是我们计算中的一个问题,但在实践中,我们可以任意将其定义为 1 而不会出现任何问题。

在第十章中,我们介绍了 ReLU(x)(修正线性单元)函数,当 x < 0 时为 0,当 x ≥ 0 时为 x。请注意,有两种方式可能导致点被错误分类:

  • 如果 y = 0 且 ŷ = 1。这意味着 score(w, b, x) ≥ 0。

  • 如果 y = 1 且 ŷ = 0。这意味着 score(w, b, x) < 0。

因此,我们可以方便地将感知器误差重写为

或者更详细地说,作为

现在我们可以使用链式法则来计算梯度 ∇PE。一个重要的观察结果是,我们将使用,并且读者可以验证的是,ReLU(x)的导数是步函数 step(x)。这个梯度是

这可以重写为

这看起来很复杂,但实际上并不难。让我们分析上一个表达式的右侧的每一项。请注意,step(score(w, b, x)) 当且仅当 score(w, b, x) > 0 时为 1,否则为 0。这正是 ŷ = 1 的时候。同样,step(–score(w, b, x)) 当且仅当 score(w, b, x) < 0 时为 1,否则为 0。这正是 ŷ = 0 的时候。因此

  • 如果 ŷ[i] = 0 且 y[i] = 0:

    y[i] x[j](i^)) step(−score(w, b, x)) + (1 − y[i]) x[j](i^) step(score(w, b, x)) = 0

  • 如果 ŷ[i] = 1 且 y[i] = 1:

    y[i] x[j](i^)) step(−score(w, b, x)) + (1 − y[i]) x[j](i^) step(score(w, b, x)) = 0

  • 如果 ŷ[i] = 0 且 y[i] = 1:

    y[i] x[j](i^)) step(−score(w, b, x)) + (1 − y[i]) x[j](i^) step(score(w, b, x)) = −x[j](i^))

  • 如果 ŷ[i] = 1 且 y[i] = 0:

    y[i] x[j](i^)) step(−score(w, b, x)) + (1 − y[i]) x[j](i^) step(score(w, b, x)) = x[j](i^))

这意味着在计算 ∂PE/∂x[j] 时,只有来自错误分类点的项会添加值。

以类似的方式,

因此,梯度下降步骤定义为以下:

梯度下降步骤:

将 (w, b) 替换为 (w**', b**'),其中

  • w[j]' = w[j] + η Σ[i]^q[=1] −y[i] x[j](i^)step(−score(w, b, x)) + (1 − y[i]) x[j](i^) step(score(w, b, x)), 并且

  • b' = b + η Σ[i]^q[=1] −y[i]step(−score(w, b, x)) + (1− y[i])step(score(w, b, x))

再次,查看上一个表达式的右侧

  • 如果 ŷ[i] = 0 且 y[i] = 0:

    y[i]step(−score(w, b, x)) + (1− y[i])step(score(w, b, x)) = 0

  • 如果 ŷ[i] = 1 且 y[i] = 1:

    y[i]step(−score(w, b, x)) + (1− y[i])step(score(w, b, x)) = 0

  • 如果 ŷ[i] = 0 且 y[i] = 1:

    y[i]step(−score(w, b, x)) + (1− y[i])step(score(w, b, x)) = −1

  • 如果 ŷ[i] = 1 且 y[i] = 0:

    y[i]step(−score(w, b, x)) + (1− y[i])step(score(w, b, x)) = 1

所有这些可能并不意味太多,但可以编写代码来计算梯度的所有条目。再次注意,如果小批量的大小 q = 1,并且只包含点 x = (x[1],x[2],…,x[n]),具有标签 y 和预测 ŷ,则步骤定义如下:

梯度下降步骤:

  • 如果点被正确分类,则不要改变 wb

  • 如果点具有标签 y = 0 并被分类为 ŷ = 1:

    • w 替换为 w' = wη**x

    • b 替换为 b' = wη

  • 如果点具有标签 y = 1 并被分类为 ŷ = 0:

    • w 替换为 w' = w + η**x

    • b 替换为 b' = w + η

注意,这正是第五章中“感知器技巧”部分描述的感知器技巧。

使用梯度下降法训练逻辑回归模型以减少对数损失

在本小节中,我们将计算对数损失函数的梯度,并使用它来应用梯度下降并训练逻辑回归模型。在逻辑回归模型中,预测值是 ŷ[1],ŷ[2],…,ŷ[q],其中每个 ŷ[i] 是介于 0 和 1 之间的某个实数。为了计算预测值,我们首先需要记住第六章中引入的 sigmoid 函数 σ(x)。该函数将任何实数 x 作为输入,并输出介于 0 和 1 之间的某个数字。如果 x 是一个很大的正数,那么 σ(x) 接近 1。如果 x 是一个很大的负数,那么 σ(x) 接近 0。sigmoid 函数的公式是

图片 AppB_04_E08.png

σ(x) 的图像如图 B.5 所示。

逻辑回归模型的预测值正是 sigmoid 函数的输出,即对于 i = 1, 2, …, q,它们被定义为以下:

图片 AppB_04_E09.png

图片 B-51

图 B.5 Sigmoid 函数始终输出介于 0 和 1 之间的数字。对于负数,输出接近 0,对于正数,输出接近 1。

对数损失表示为 LL(w, b, x, y),我们将简写为 LL。这个错误函数最初在第六章的“数据集和预测”部分中定义。它类似于感知器错误函数,因为按照构造,如果模型做出了错误的预测,它就是一个很大的数字;如果模型做出了好的预测,它就是一个小的数字。对数损失函数定义为

图片 AppB_05_E01.png

我们可以继续使用链式法则计算梯度 ∇LL。在此之前,请注意,sigmoid 函数的导数可以写成 σ**'(x) = σ(x)|1 – σ(x)|。关于最后计算的详细情况,可以使用微分的有理数法则来计算,具体留给读者。使用这个,我们可以计算 ŷ[i] 对 w[j] 的导数。因为 ŷ[i] = σ(Σ[i]n[=1]*w*[j]*x*[j](i) + b)),所以根据链式法则,

图片 AppB_05_E02.png

现在,让我们继续开发对数损失。再次使用链式法则,我们得到

并且通过之前对 ∂ŷ[i]/∂w[j] 的计算,

简化后得到

这进一步简化为

类似地,对 b 求导,我们得到

因此,梯度下降步骤变为以下:

梯度下降步骤:

将 (w, b) 替换为 (w**', b**'), 其中

  • *w**' = w + ηΣ[i]q=1*x*(i)

  • *b**' = b + ηΣ[i]^q=1

注意当小批量的大小为 1 时,梯度下降步骤变为以下:

将 (w, b) 替换为 (w**', b**'), 其中

  • w**' = w + η(y[i] – ŷ[i])x*(i^)

  • b**' = b + η(y*[i] – ŷ[i])

这正是我们在第六章“如何找到一个好的逻辑分类器?”中学习到的逻辑回归技巧。

使用梯度下降训练神经网络

在第十章的“反向传播”一节中,我们介绍了反向传播——训练神经网络的步骤。这个过程包括重复梯度下降步骤以最小化 log 损失。在本节中,我们将看到如何实际计算导数以执行这个梯度下降步骤。我们将在这个深度为 2 的神经网络(一个输入层、一个隐藏层和一个输出层)中执行此过程,但示例足够大,可以展示这些导数是如何在一般情况下计算的。此外,我们将只对单个点的误差应用梯度下降(换句话说,我们将进行随机梯度下降)。然而,我鼓励你计算出具有更多层的神经网络的导数,并使用点的小批量(小批量梯度下降)。

在我们的神经网络中,输入层由 m 个输入节点组成,隐藏层由 n 个隐藏节点组成,输出层由一个输出节点组成。本节中的符号如下

子节不同,为了简化,如下(并在图 B.6 中说明):

  • 输入是坐标为 x[1], x[2], …, x[m] 的点。

  • 第一隐藏层具有权重 V[ij] 和偏置 b[j],其中 i = 1, 2, …, mj = 1, 2, …, n

  • 第二隐藏层具有权重 W[j],其中 j = 1, 2, …, n,以及偏置 c

图 B.6 使用具有一个隐藏层和 sigmoid 激活函数的神经网络计算预测的过程

输出的计算方式如下两个方程:

为了简化导数的计算,我们使用以下辅助变量 r[j] 和 s

这样,我们可以计算以下偏导数(回忆一下 sigmoid 函数的导数是 σ**'(x) = σ**'(x)[1 – σ(x)],以及 log 损失是 L(y, ŷ) = – y ln(ŷ) – (1 – y)ln(1 – ŷ)——为了方便起见,我们将其称为 L):

  1. 图片

  2. 图片

  3. 图片

  4. 图片

  5. 图片

    为了简化我们的计算,请注意,如果我们乘以方程 1 和 2 并使用链式法则,我们得到

  6. 图片

    现在,我们可以使用链式法则和方程 3–7 来计算关于权重和偏差的 log 损失的导数,如下所示:

  7. 图片

  8. 图片

  9. 图片

  10. 图片

使用前面的方程,梯度下降步骤如下:

神经网络的梯度下降步骤:

图片

之前的方程相当复杂,即使是更多层的神经网络的反向传播方程也是如此。幸运的是,我们可以使用 PyTorch、TensorFlow 和 Keras 来训练神经网络,而无需计算所有导数。

使用梯度下降进行正则化

在第四章的“修改误差函数以解决我们的问题”部分,我们学习了正则化作为降低机器学习模型过拟合的一种方法。正则化包括向误差函数中添加一个正则化项,这有助于减少过拟合。这个项可以是模型中使用的多项式的 L1 或 L2 范数。在第十章的“训练神经网络的技巧”部分,我们学习了如何通过添加类似的正则化项来应用正则化以训练神经网络。后来,在第十一章的“距离误差函数”部分,我们学习了 SVM 的距离误差函数,这确保了分类器中的两条线彼此靠近。距离误差函数与 L2 正则化项具有相同的形式。

然而,在第四章的“直观地看待正则化”部分,我们学习了一种更直观的方式来理解正则化。简而言之,每个使用正则化的梯度下降步骤都会略微减少模型系数的值。让我们看看这一现象背后的数学原理。

对于具有权重 w[1]、w[2]、…、w[n] 的模型,正则化项如下:

  • L1 正则化:W[1] = |w[1]| + |w[2]| + … + |w[n]|

  • L2 正则化:W[2] = w[1]² + w[2]² + … + w[n]²

回想一下,为了不剧烈改变系数,正则化项乘以一个正则化参数 l。因此,当我们应用梯度下降时,系数的修改如下:

  • L1 正则化:w[i] 被替换为 w[i] – ∇ W[1]

  • L2 正则化:w[i] 被替换为 w[i] – ∇ W[2]

其中 ∇ 表示正则化项的梯度。换句话说,,. 由于 ,和 ,那么梯度下降步骤如下:

正则化的梯度下降步骤:

  • L1 正则化:将 a[i] 替换为

  • L2 正则化:将 a[i] 替换为

注意到这个梯度下降步骤总是减小系数 a[i] 的绝对值。在 L1 正则化中,如果 a[i] 是正的,我们从 a[i] 中减去一个小的值;如果它是负的,我们则加上一个小的值。在 L2 正则化中,我们将 a[i] 乘以一个略小于 1 的数。

卡在局部最小值上:它是如何发生的,以及我们如何解决它

如附录开头所述,梯度下降算法不一定能找到函数的最小值。例如,看看图 B.7。假设我们想使用梯度下降法找到图中函数的最小值。由于梯度下降的第一步是从一个随机点开始,我们将从标记为“起点”的点开始。

图 B.7 我们站在标记为“起点”的点。函数的最小值是标记为“最小值”的点。我们能否通过梯度下降法达到这个最小值?

图 B.8 展示了梯度下降算法寻找最小值将采取的路径。请注意,它成功地找到了离那个点最近的局部最小值,但它完全错过了右边的全局最小值。

图 B.8 很遗憾,梯度下降法并没有帮助我们找到这个函数的最小值。我们确实有所下降,但我们在局部最小值(山谷)处卡住了。我们该如何解决这个问题?

我们该如何解决这个问题?我们可以使用许多技术来解决这个问题,在本节中,我们学习一个常见的称为 随机重启动 的方法。解决方案是简单地多次运行算法,每次从一个不同的随机点开始,并选择找到的最小值。在图 B.9 中,我们使用随机重启动找到一个函数的全局最小值(请注意,这个函数仅在图中所示区间内定义,因此该区间内的最低值确实是全局最小值)。我们选择了三个随机起点,一个用圆圈表示,一个用正方形表示,一个用三角形表示。请注意,如果我们在这三个点上的每个点上使用梯度下降法,正方形设法找到了函数的全局最小值。

图 B.9 随机重启动技术的说明。该函数仅在这一点上定义,有三个山谷,全局最小值位于第二个山谷中。在这里,我们使用三个不同的起点运行梯度下降算法:圆圈、正方形和三角形。请注意,正方形设法找到了函数的全局最小值。

这种方法仍然不能保证找到全局最小值,因为我们可能运气不佳,只选取了那些陷入山谷中的点。然而,如果我们有足够的随机起始点,我们找到全局最小值的几率就会大大提高。即使我们无法找到全局最小值,我们也可能仍然能够找到一个足够好的局部最小值,这将有助于我们训练出一个好的模型。

附录 C. 参考文献列表

这些参考文献也可以在 serrano.academy/grokking-machine-learning/ 找到。

一般参考文献

课程

博客和 YouTube 频道

书籍

  • 《模式识别与机器学习》,作者 Christopher Bishop:mng.bz/g1DZ

第一章

视频

第二章

视频

书籍

  • Grokking Deep Reinforcement Learning, by Miguel Morales:mng.bz/5Zy4

课程

第三章

代码

数据集

  • 海得拉巴住房数据集:

    • 创建者:Ruchi Bhatia

    • 日期:2020/08/27

    • 版本:4

    • mng.bz/nrdv获取

    • 许可证:CC0:公共领域

视频

第四章

代码

视频

第五章

代码

视频

第六章

代码

数据集

视频

第七章

视频

第八章

代码

数据集

视频

第九章

代码

数据集

  • 录入数据集

    • 创建者:Mohan S. Acharya

    • 日期:2018/03/03

    • 版本:2

    • 来自 mng.bz/aZlJ

    • 文章:Mohan S. Acharya, Asfia Armaan, 和 Aneeta S Antony,“比较回归模型预测研究生录取情况”,IEEE 国际数据科学计算智能会议(2019 年)。

    • 许可证:CC0:公共领域

视频

博客文章

  • 香农熵、信息增益和从桶中取球: mng.bz/g1lR

第十章

代码

数据集

视频

书籍

课程

博客文章

工具

第十一章

代码

视频

博客文章

第十二章

代码

视频

文章和博客文章

第十三章

代码

数据集

图形和图像图标

图形和图像图标由以下创作者提供 flaticon.com:

  • IFC—徒步旅行者,山脉: Freepik; 旗帜: Good Ware

  • 前言—音乐家: photo3idea_studio; 数学家: Freepik

  • 图 1.2—人物: Eucalyp; 计算机: Freepik

  • 图 1.4—人物: ultimatearm

  • 图 1.5, 1.6, 1.7, 1.8, 1.9—人物在电脑前: Freepik

  • 图 2.1, 2.2, 2.3, 2.4—狗: Freepik; 猫: Smashicons

  • 图 2.2, 2.3, 2.4—工厂: DinosoftLabs

  • 图 2.5, 2.6—信封: Freepik

  • 图 2.7—房屋: Vectors Market

  • 图 2.8—地图点: Freepik

  • 图 2.11, 2.12—机器人,山脉: Freepik; 龙兽: Eucalyp; 财宝: Smashicons

  • 图 3.3, 3.4, 3.5—房屋: Vectors Market

  • 图 3.20, 3.21, 3.22—徒步旅行者,山脉: Freepik; 旗帜,灯泡: Good Ware

  • 图 4.1—哥斯拉: Freepik; 打虫器: Smashicons; 飞行: Eucalyp; 火箭筒: photo3idea_studio

  • 图 4.6—房屋: Vectors Market; 绷带,瓦片: Freepik; 钛棒: Vitaly Gorbachev

  • 图 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.10, 5.11, 5.12, 5.13, 5.16, 5.17, 5.18, 5.19, 5.20, 5.21, 5.22, 5.23, 5.24, 6.1, 6.4, 6.5, 6.6, 10.2, 10.3—笑脸,哭脸,中性脸: Vectors Market

  • 图 5.15, 5.16—徒步旅行者: Freepik; 灯泡: Good Ware

  • 图 7.11(在练习中)——各种动物:surang;书籍:Good Ware;时钟、草莓、行星、足球:Freepik;视频:monkik

  • 图 8.4——厨师帽:Vitaly Gorbachev;小麦、面包:Freepik

  • 图 8.5、8.6、8.7、8.8、8.9、8.10、8.11、8.12、8.13——信封:Smashicons

  • 图 9.6、9.7、9.8、9.9、9.12、9.13、9.15、9.16、9.17——原子:mavadee;蜂巢:Smashicons;国际象棋骑士:Freepik

  • 图 9.13、9.17——手提包:Gregor Cresnar

  • 第十章中的笑话——肌肉手臂:Freepik

  • 第十一章中的笑话——玉米、鸡肉:Freepik;鸡肉:monkik

  • 第十二章中的笑话——鲨鱼、鱼:Freepik

  • 图 12.1——大机器人:photo3idea_studio;小机器人:Freepik

  • 第十三章中的笑话——船:Freepik

posted @ 2025-11-21 09:08  绝不原创的飞龙  阅读(29)  评论(0)    收藏  举报