无监督学习实用指南(一)
原文:
annas-archive.org/md5/5d48074db68aa41a4c5eb547fcbf1a69译者:飞龙
序言
机器学习的简要历史
机器学习是人工智能的一个子领域,计算机通过数据学习,通常是为了在某些狭义定义的任务上提高性能,而无需显式编程。机器学习 这个术语早在 1959 年就被创造出来了(由 AI 领域的传奇人物亚瑟·塞缪尔),但在 21 世纪,机器学习并没有取得多少主要的商业成功。相反,该领域仍然是一种学术界的小众研究领域。
在早期(上世纪六十年代),AI 社区中许多人对其未来持过于乐观的态度。当时的研究人员,如赫伯特·西蒙和马文·明斯基,声称 AI 将在几十年内达到人类水平的智能:¹
机器将在二十年内有能力完成人类能够完成的任何工作。
赫伯特·西蒙,1965
从三到八年内,我们将拥有一台具有平均人类智能的机器。
马文·明斯基,1970
研究人员过于乐观,专注于所谓的强人工智能 或 通用人工智能(AGI) 项目,试图构建能够解决问题、知识表示、学习和规划、自然语言处理、感知和运动控制的 AI 代理。这种乐观主义帮助吸引了来自国防部等主要参与者的重要资金,但这些研究人员所解决的问题过于雄心勃勃,最终注定失败。
AI 研究很少从学术界跨入工业界,随之而来的是一系列所谓的 AI 寒冬。在这些 AI 寒冬中(这是基于冷战时代核冬天的类比),对 AI 的兴趣和资金逐渐减少。偶尔会出现围绕 AI 的炒作周期,但很少有持久性。到了 1990 年代初,对 AI 的兴趣和资金已经达到了低谷。
AI 回归了,但为什么现在?
过去二十年中,AI 重新以全新的姿态出现——起初作为一种纯学术兴趣领域,现在已成为吸引大学和公司最聪明头脑的完整领域。
三个关键发展推动了这一复兴:机器学习算法的突破、大量数据的可用性以及超快速的计算机。
首先,研究人员不再专注于过于雄心勃勃的强人工智能项目,转而关注强人工智能的狭义子问题,也被称为弱人工智能 或 狭义人工智能。这种专注于改进狭义任务解决方案的做法导致了算法上的突破,为成功的商业应用铺平了道路。许多这些算法——通常最初在大学或私人研究实验室开发——很快被开源,加速了这些技术在工业界的采纳。
其次,数据捕获成为大多数组织的焦点,数字数据存储成本大幅下降。得益于互联网,大量数据也以前所未有的规模广泛公开和共享。
其次,云端计算的普及使得 AI 研究人员能够根据需求轻松、廉价地扩展其 IT 基础设施,而无需进行大规模的前期硬件投资。
应用人工智能的出现
这三股力量将 AI 从学术界推向工业界,每年吸引越来越高的兴趣和资金。AI 不再仅仅是一个理论上的兴趣领域,而是一个成熟的应用领域。Figure P-1 展示了 Google Trends 中机器学习兴趣的增长趋势图,涵盖了过去五年的时间。
图 P-1. 机器学习兴趣随时间变化图
现在,人工智能被视为一种突破性的横向技术,类似于计算机和智能手机的出现,将在未来十年对每一个行业产生重大影响。²
涉及机器学习的成功商业应用包括但不限于光学字符识别、电子邮件垃圾过滤、图像分类、计算机视觉、语音识别、机器翻译、群体分割与聚类、生成合成数据、异常检测、网络犯罪预防、信用卡欺诈检测、网络欺诈检测、时间序列预测、自然语言处理、棋盘游戏和视频游戏、文档分类、推荐系统、搜索、机器人技术、在线广告、情感分析、DNA 序列分析、金融市场分析、信息检索、问答系统和医疗决策。
过去 20 年来应用人工智能的主要里程碑
这些里程碑将 AI 从当时主要是学术讨论的话题带到了今天科技的主流位置。
-
1997 年:Deep Blue,一个自上世纪 80 年代中期开始研发的 AI 机器人,在一场备受关注的国际象棋比赛中击败了世界冠军加里·卡斯帕罗夫。
-
2004 年:DARPA 推出了 DARPA Grand Challenge,这是一项年度举办的自动驾驶挑战赛,在沙漠地区举行。2005 年,斯坦福获得了最高奖。2007 年,卡内基梅隆大学在城市环境中实现了这一壮举。2009 年,谷歌推出了自动驾驶汽车。到 2015 年,包括特斯拉、Alphabet 的 Waymo 和 Uber 在内的许多主要技术巨头都推出了资金充裕的主流自动驾驶技术项目。
-
2006 年:多伦多大学的 Geoffrey Hinton 提出了一种快速学习算法,用于训练多层神经网络,开启了深度学习革命。
-
2006: Netflix 启动了 Netflix 奖(Netflix Prize)竞赛,奖金高达一百万美元,挑战团队利用机器学习技术,将其推荐系统的准确性提高至少 10%。一个团队在 2009 年赢得了这一奖项。
-
2007: AI 在跳棋比赛中达到超人类水平,由阿尔伯塔大学的团队解决。
-
2010: ImageNet 启动了年度比赛——ImageNet 大规模视觉识别挑战(ILSVRC),团队使用机器学习算法在一个大型、经过良好筛选的图像数据集中正确检测和分类对象。这引起了学术界和技术巨头的重视。由于深度卷积神经网络的进展,2011 年的分类错误率从 25%降至 2015 年的几个百分点。这导致了计算机视觉和物体识别的商业应用。
-
2010: Microsoft 推出了 Xbox 360 的 Kinect。由 Microsoft Research 的计算机视觉团队开发,Kinect 能够跟踪人体动作并将其转化为游戏操作。
-
2010: Siri,最早的主流数字语音助手之一,被 Apple 收购,并作为 iPhone 4S 的一部分于 2011 年 10 月发布。最终,Siri 在 Apple 的所有产品中都推出。由卷积神经网络和长短期记忆递归神经网络驱动,Siri 执行语音识别和自然语言处理。随后,亚马逊、微软和谷歌进入竞争,分别发布了 Alexa(2014 年)、Cortana(2014 年)和 Google Assistant(2016 年)。
-
2011: IBM Watson,一个由 David Ferrucci 领导的团队开发的问答型 AI 代理程序,击败了前《危险边缘》获胜者 Brad Rutter 和 Ken Jennings。IBM Watson 现在被多个行业使用,包括医疗保健和零售。
-
2012: 由 Andrew Ng 和 Jeff Dean 领导的 Google Brain 团队,通过观看来自 YouTube 视频的未标记图像,训练神经网络识别猫。
-
2013: Google 赢得了 DARPA 机器人挑战赛,涉及半自主机器人在危险环境中执行复杂任务,如驾驶车辆、越过瓦砾、清除被堵入的入口、打开门和爬梯子。
-
2014: Facebook 发布了基于神经网络的 DeepFace 系统,可以以 97%的准确率识别面部。这接近人类水平的性能,比先前系统提高了 27%以上。
-
2015: AI 成为主流,并广泛出现在全球的媒体报道中。
-
2015: Google DeepMind 的 AlphaGo 击败了世界级职业选手樊麾的围棋比赛。2016 年,AlphaGo 又击败了李世石,2017 年又击败了柯洁。2017 年,名为 AlphaGo Zero 的新版本以 100 比 0 击败了以前的 AlphaGo 版本。AlphaGo Zero 采用无监督学习技术,仅通过与自己对弈就掌握了围棋。
-
2016 年:谷歌对其语言翻译系统 Google Translate 进行了重大改进,将其现有的基于短语的翻译系统替换为基于深度学习的神经机器翻译系统,将翻译错误率降低了多达 87%,接近人类水平的准确度。
-
2017 年:由卡内基梅隆大学开发的 Libratus 在无限制德州扑克头对头比赛中获胜。
-
2017 年:OpenAI 训练的机器人在 Dota 2 比赛中击败了职业玩家。
从狭义人工智能到通用人工智能
当然,将人工智能成功应用于狭义问题只是一个起点。在人工智能社区中,有一种越来越强烈的信念,即通过结合几个弱人工智能系统,我们可以开发出强人工智能。这种强人工智能或通用人工智能(AGI)代理将能够在许多广义任务上达到人类水平的表现。
在人工智能达到人类水平表现之后不久,一些研究人员预测这种强人工智能将超越人类智能,达到所谓的超级智能。达到这种超级智能的时间估计从现在起可能只需 15 年,也可能需要 100 年,但大多数研究人员认为,在未来几代人之内,人工智能将足够发展到达到这一水平。这一次,这是否又是像之前人工智能周期中看到的炒作,还是有所不同?
只有时间能说明一切。
目标与方法
到目前为止,大多数成功的商业应用程序(如计算机视觉、语音识别、机器翻译和自然语言处理)都涉及有标签数据的监督学习。然而,大多数世界数据是未标记的。
在这本书中,我们将涵盖无监督学习领域(这是机器学习的一个分支,用于发现隐藏的模式),并学习未标记数据中的潜在结构。根据许多行业专家的说法,比如 Facebook 的 AI 研究总监兼纽约大学教授杨立昆,无监督学习是人工智能的下一个前沿,可能是实现通用人工智能的关键。因此,无监督学习是当今人工智能领域最炙手可热的话题之一。
本书的目标是概述您在日常工作中应用这项技术所需的概念和工具,以便您能够发展出这种直觉。换句话说,这是一本应用性的书籍,将帮助您构建真实世界的系统。我们还将探讨如何高效地标记未标记的数据集,将无监督学习问题转化为半监督学习问题。
本书将采用实践方法,介绍一些理论,但主要侧重于将无监督学习技术应用于解决现实世界的问题。数据集和代码可在Jupyter notebooks on GitHub上找到。
凭借从本书中获得的概念理解和实践经验,您将能够将无监督学习应用于大型未标记数据集,以揭示隐藏模式,获取更深入的业务见解,检测异常,基于相似性对群组进行聚类,执行自动特征工程和选择,生成合成数据集等等。
先决条件
本书假定您具有一些 Python 编程经验,包括熟悉 NumPy 和 Pandas。
有关 Python 的更多信息,请访问官方 Python 网站。有关 Jupyter Notebook 的更多信息,请访问官方 Jupyter 网站。要复习大学水平的微积分、线性代数、概率和统计学,请阅读 Ian Goodfellow 和 Yoshua Bengio 的《深度学习》教材的第 I 部分。要复习机器学习,请阅读统计学习的要素。
路线图
本书分为四个部分,涵盖以下主题:
第 I 部分,无监督学习基础
监督学习和无监督学习之间的差异,流行的监督学习和无监督学习算法的概述,以及端到端的机器学习项目
第 II 部分,使用 Scikit-Learn 进行无监督学习
降维、异常检测和聚类和分组分割
提示
有关部分 I 和 II 中讨论的概念的更多信息,请参阅Scikit-learn 文档。
第 III 部分,使用 TensorFlow 和 Keras 进行无监督学习
表示学习和自动特征提取、自动编码器和半监督学习
第 IV 部分,使用 TensorFlow 和 Keras 进行深度无监督学习
受限玻尔兹曼机、深度信念网络和生成对抗网络
本书中使用的约定
本书中使用以下印刷约定:
斜体
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
常量宽度
用于程序清单,以及在段落中引用程序元素,如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。
常量宽度粗体
显示用户应该按字面意思输入的命令或其他文本。
常量宽度斜体
显示应替换为用户提供的值或上下文确定的值的文本。
提示
此元素表示提示或建议。
注意
此元素表示一般注释。
警告
此元素表示警告或注意事项。
使用代码示例
可以在GitHub上下载补充材料(代码示例等)。
本书旨在帮助您完成工作。一般情况下,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分内容,否则无需征得我们的许可。例如,编写一个使用本书中几个代码片段的程序无需许可。销售或分发包含奥莱利书籍示例的 CD-ROM 需要许可。引用本书回答问题并引用示例代码无需许可。将本书的大量示例代码整合到您产品的文档中需要许可。
我们感谢但不需要署名。署名通常包括标题、作者、出版商和 ISBN。例如:“使用 Python 进行无监督学习实战 作者 Ankur A. Patel(奥莱利)。版权所有 2019 Ankur A. Patel,978-1-492-03564-0。”
如果您觉得您使用的代码示例超出了合理使用范围或上述授权,请随时通过 permissions@oreilly.com 联系我们。
奥莱利在线学习
注意
近 40 年来,奥莱利传媒 为企业的成功提供技术和商业培训、知识和洞察。
我们独特的专家和创新者网络通过书籍、文章、会议和我们的在线学习平台分享他们的知识和专业知识。奥莱利的在线学习平台为您提供按需访问的现场培训课程、深度学习路径、交互式编码环境以及来自奥莱利和其他 200 多家出版商的大量文本和视频。更多信息,请访问 http://oreilly.com。
如何联系我们
请将有关本书的评论和问题发送至出版商:
-
奥莱利传媒公司
-
1005 Gravenstein Highway North
-
CA 95472 Sebastopol
-
800-998-9938(美国或加拿大)
-
707-829-0515(国际或本地)
-
707-829-0104(传真)
我们为本书设立了一个网页,列出勘误、示例和任何额外信息。您可以访问 http://bit.ly/unsupervised-learning。
要就本书发表评论或提出技术问题,请发送电子邮件至 bookquestions@oreilly.com。
有关我们的图书、课程、会议和新闻的更多信息,请访问我们的网站 http://www.oreilly.com。
在 Facebook 上找到我们:http://facebook.com/oreilly
在 Twitter 上关注我们:http://twitter.com/oreillymedia
在 YouTube 上观看我们:http://www.youtube.com/oreillymedia
¹ 这些观点在 1968 年启发了斯坦利·库布里克创作 2001:太空漫游 中的 AI 代理人 HAL 9000。
根据麦肯锡全球研究所的报告,到 2055 年,人们获得报酬的所有专业活动中超过一半可以实现自动化。
第一部分:无监督学习的基础知识
首先,让我们探索当前的机器学习生态系统以及无监督学习的定位。我们还将从头开始构建一个机器学习项目,涵盖基础内容,如设置编程环境、获取和准备数据、探索数据、选择机器学习算法和成本函数,以及评估结果。
第一章:机器学习生态系统中的无监督学习
大多数人类和动物的学习都是无监督学习。如果智能是一个蛋糕,无监督学习将是蛋糕,监督学习将是蛋糕上的糖衣,而强化学习将是蛋糕上的樱桃。我们知道如何制作糖衣和樱桃,但我们不知道如何制作蛋糕。在我们甚至考虑真正的 AI 之前,我们需要解决无监督学习问题。
伊恩·拉坤
在本章中,我们将探讨基于规则的系统与机器学习、监督学习与无监督学习之间的区别,以及每种方法的相对优势和劣势。
我们还将介绍许多流行的监督学习算法和无监督学习算法,并简要探讨半监督学习和强化学习如何融入其中。
基础机器学习术语
在我们深入探讨不同类型的机器学习之前,让我们先看一个简单且常用的机器学习示例,以帮助我们更具体地理解我们介绍的概念:电子邮件垃圾过滤器。我们需要构建一个简单的程序,输入电子邮件并正确地将它们分类为“垃圾邮件”或“非垃圾邮件”。这是一个直接的分类问题。
这里是一些机器学习术语的复习:这个问题的输入变量是电子邮件的文本。这些输入变量也被称为特征或预测变量或独立变量。我们试图预测的输出变量是标签“垃圾邮件”或“非垃圾邮件”。这也被称为目标变量、依赖变量或响应变量(或类,因为这是一个分类问题)。
AI 训练的示例集被称为训练集,每个单独的示例称为训练实例或样本。在训练过程中,AI 试图最小化其成本函数或错误率,或者更积极地说,最大化其价值函数—在本例中,是正确分类的电子邮件比例。AI 在训练期间积极优化以达到最小的错误率。它的错误率是通过将 AI 预测的标签与真实标签进行比较来计算的。
然而,我们最关心的是 AI 如何将其训练推广到以前从未见过的电子邮件上。这将是 AI 的真正测试:它能否使用在训练集示例中学到的知识正确分类它以前从未见过的电子邮件?这种泛化误差或样外误差是我们用来评估机器学习解决方案的主要指标。
这组以前从未见过的示例被称为测试集或保留集(因为这些数据被保留在训练之外)。如果我们选择有多个保留集(也许在训练过程中评估我们的泛化误差是明智的),我们可能会有用于评估我们进展的中间保留集,这些中间保留集称为验证集。
将所有这些结合起来,AI 在训练数据(经验)上进行训练,以提高在标记垃圾邮件(任务)中的错误率(性能),最终成功的标准是其经验如何推广到新的、以前从未见过的数据上(泛化误差)。
基于规则与机器学习
使用基于规则的方法,我们可以设计一个垃圾邮件过滤器,通过明确的规则捕捉垃圾邮件,比如标记使用“u”代替“you”,“4”代替“for”,“BUY NOW”等的电子邮件。但是随着坏人改变他们的垃圾邮件行为以逃避规则,这种系统在时间上会很难维护。如果我们使用基于规则的系统,我们将不得不经常手动调整规则,以保持系统的最新状态。而且,设置这种系统将非常昂贵——想象一下我们需要创建多少规则才能使其正常运行。
与基于规则的方法不同,我们可以使用机器学习来训练电子邮件数据,并自动创建规则以正确标记恶意电子邮件为垃圾邮件。这种基于机器学习的系统也可以随着时间的推移自动调整。这种系统的培训和维护成本要低得多。
在这个简单的电子邮件问题中,我们可能可以手工制定规则,但是对于许多问题来说,手工制定规则根本不可行。例如,考虑设计自动驾驶汽车——想象一下为汽车在每一个遇到的情况下如何行为制定规则,这是一个棘手的问题,除非汽车可以根据自己的经验学习和适应。
我们还可以将机器学习系统作为探索或数据发现工具,以深入了解我们尝试解决的问题。例如,在电子邮件垃圾邮件过滤器的示例中,我们可以学习哪些单词或短语最能预测垃圾邮件,并识别新出现的恶意垃圾邮件模式。
监督学习与非监督学习
机器学习领域有两个主要分支——监督学习和无监督学习——以及许多桥接这两者的子分支。
在监督学习中,AI 代理可以访问标签,这些标签可以用来改善其在某些任务上的表现。在电子邮件垃圾邮件过滤问题中,我们有一个包含每封电子邮件中所有文本的数据集。我们还知道哪些邮件是垃圾邮件或非垃圾邮件(所谓的标签)。这些标签在帮助监督学习 AI 区分垃圾邮件和其他邮件方面非常有价值。
在无监督学习中,没有标签可用。因此,AI 代理的任务并不是明确定义的,性能也不能如此清晰地衡量。考虑电子邮件垃圾邮件过滤器问题——这次没有标签。现在,AI 代理将尝试理解电子邮件的基本结构,将电子邮件数据库分成不同的组,使得组内的电子邮件彼此相似但与其他组的电子邮件不同。
这个无监督学习问题比监督学习问题的定义不太明确,对 AI 代理来说更难解决。但是,如果处理得当,解决方案将更为强大。
原因在于:无监督学习 AI 可能会发现几个后来标记为“垃圾邮件”的组,但 AI 也可能会发现后来标记为“重要”的组,或者归类为“家庭”、“专业”、“新闻”、“购物”等。换句话说,由于问题没有严格定义的任务,AI 代理可能会发现我们最初未曾寻找的有趣模式。
此外,这种无监督系统在未来数据中发现新模式的能力优于监督系统,使得无监督解决方案在前进时更加灵活。这就是无监督学习的力量。
监督学习的优势和劣势
监督学习在定义良好的任务和充足标签的情况下优化性能。例如,考虑一个非常大的对象图像数据集,其中每个图像都有标签。如果数据集足够大,并且我们使用正确的机器学习算法(即卷积神经网络)并且使用足够强大的计算机进行训练,我们可以构建一个非常好的基于监督学习的图像分类系统。
当监督学习 AI 在数据上进行训练时,它将能够通过比较其预测的图像标签与我们文件中的真实图像标签来测量其性能(通过成本函数)。AI 将明确尝试将这个成本函数最小化,使其在以前未见过的图像(从留存集)上的错误尽可能低。
这就是为什么标签如此强大——它们通过提供错误度量来指导 AI 代理。AI 使用这个错误度量随着时间的推移来提高其性能。没有这样的标签,AI 不知道它在正确分类图像方面有多成功(或不成功)。
然而,手动标记图像数据集的成本很高。即使是最好的策划图像数据集也只有数千个标签。这是一个问题,因为监督学习系统在对具有标签的对象图像分类方面表现非常出色,但在对没有标签的对象图像分类方面表现不佳。
尽管监督学习系统非常强大,但它们在将知识推广到以前未见过的实例上的能力也受到限制。由于世界上大多数数据都没有标签,因此使用监督学习时,AI 将其性能扩展到以前未见过的实例的能力是相当有限的。
换句话说,监督学习擅长解决狭义 AI 问题,但在解决更有雄心、定义不太明确的强 AI 类型问题时表现不佳。
无监督学习的优势和劣势
在狭义定义的任务中,有着明确定义的模式并且随时间变化不大以及具有充足可用的标记数据集时,监督学习将在效果上胜过无监督学习。
然而,对于那些模式未知或不断变化,或者我们没有足够大的标记数据集的问题,无监督学习确实表现出色。
无监督学习不依赖标签,而是通过学习其训练的数据的基本结构来工作。它通过试图用比数据集中可用示例数量显著较小的一组参数来表示其训练的数据来实现这一点。通过执行这种表示学习,无监督学习能够识别数据集中的不同模式。
在图像数据集示例中(这次没有标签),无监督学习的 AI 可能能够根据它们彼此的相似性以及与其余图像的不同性将图像识别并分组。例如,所有看起来像椅子的图像将被分组在一起,所有看起来像狗的图像将被分组在一起,依此类推。
当然,无监督学习的 AI 本身无法将这些组标记为“椅子”或“狗”,但现在相似的图像被分组在一起后,人类的标记任务变得简单得多。人类可以手动标记所有不同的组,标签将应用于每个组内的所有成员。
经过初步训练后,如果无监督学习的 AI 发现了不属于任何已标记组的图像,AI 将为未分类的图像创建单独的组,触发人类标记新的、尚未标记的图像组。
无监督学习使以前棘手的问题更易解决,并且在找到历史数据和未来数据中隐藏模式方面更为灵活。此外,我们现在有了一种处理世界上存在的大量未标记数据的 AI 方法。
尽管无监督学习在解决特定、狭义定义的问题方面不如监督学习熟练,但在解决更为开放的强 AI 类型问题和推广这种知识方面表现更佳。
同样重要的是,无监督学习可以解决数据科学家在构建机器学习解决方案时遇到的许多常见问题。
使用无监督学习来改善机器学习解决方案
机器学习的最近成功是由大量数据的可用性、计算硬件和基于云的资源的进步以及机器学习算法的突破推动的。但这些成功主要出现在狭义 AI 问题,如图像分类、计算机视觉、语音识别、自然语言处理和机器翻译领域。
要解决更雄心勃勃的 AI 问题,我们需要发挥无监督学习的价值。让我们探讨数据科学家在构建解决方案时面临的最常见挑战,以及无监督学习如何帮助解决这些挑战。
标记不足的数据
我认为 AI 就像建造一艘火箭。你需要一个巨大的引擎和大量的燃料。如果你有一个巨大的引擎和少量的燃料,你无法进入轨道。如果你有一个微小的引擎和大量的燃料,你甚至无法起飞。要建造一艘火箭,你需要一个巨大的引擎和大量的燃料。
Andrew Ng
如果机器学习是一艘火箭,数据就是燃料——没有大量数据,火箭是无法飞行的。但并非所有数据都是平等的。要使用监督算法,我们需要大量标记数据,这在生成过程中是困难且昂贵的。¹
使用无监督学习,我们可以自动标记未标记的示例。这里是它的工作原理:我们会对所有示例进行聚类,然后将标记示例的标签应用于同一聚类中的未标记示例。未标记的示例将获得它们与之最相似的已标记示例的标签。我们将在第五章中探讨聚类。
过拟合
如果机器学习算法根据训练数据学习了一个过于复杂的函数,它在从保留集(例如验证集或测试集)中获得的以前未见实例上可能表现非常糟糕。在这种情况下,算法过度拟合了训练数据——从数据中提取了太多的噪声,并且具有非常差的泛化误差。换句话说,该算法是在记忆训练数据,而不是学习如何基于其泛化知识。²
为了解决这个问题,我们可以将无监督学习引入作为正则化器。正则化是一种用来降低机器学习算法复杂度的过程,帮助其捕捉数据中的信号而不是过多地调整到噪声。无监督预训练就是这种正则化的形式之一。我们可以不直接将原始输入数据馈送到监督学习算法中,而是馈送我们生成的原始输入数据的新表示。
这种新的表示捕捉了原始数据的本质——真正的底层结构——同时在过程中减少了一些不太代表性的噪声。当我们将这种新的表示输入监督学习算法时,它需要处理的噪声较少,捕捉到更多的信号,从而改善其泛化误差。我们将在第七章探讨特征提取。
维度诅咒
尽管计算能力有所提升,大数据对机器学习算法的管理仍然颇具挑战性。一般来说,增加更多实例并不太成问题,因为我们可以利用现代的映射-减少解决方案(如 Spark)并行操作。然而,特征越多,训练就越困难。
在非常高维空间中,监督算法需要学习如何分离点并构建函数逼近,以做出良好的决策。当特征非常多时,这种搜索变得非常昂贵,无论是从时间还是计算资源的角度来看。在某些情况下,可能无法快速找到一个好的解决方案。
这个问题被称为维度诅咒,无监督学习非常适合帮助管理这一问题。通过降维,我们可以找到原始特征集中最显著的特征,将维度减少到一个更易管理的数量,同时在过程中几乎不丢失重要信息,然后应用监督算法来更有效地执行寻找良好函数逼近的搜索。我们将在第三章涵盖降维技术。
特征工程
特征工程是数据科学家执行的最关键任务之一。如果没有合适的特征,机器学习算法将无法在空间中有效分离点,从而不能在以前未见的示例上做出良好的决策。然而,特征工程通常非常耗时,需要人类创造性地手工设计正确类型的特征。相反,我们可以使用无监督学习算法中的表示学习来自动学习适合解决手头任务的正确类型的特征表示。我们将在第七章探索自动特征提取。
异常值
数据的质量也非常重要。如果机器学习算法在稀有的、扭曲的异常值上进行训练,其泛化误差将低于忽略或单独处理异常值的情况。通过无监督学习,我们可以使用降维技术进行异常检测,并分别为异常数据和正常数据创建解决方案。我们将在第四章构建一个异常检测系统。
数据漂移
机器学习模型还需要意识到数据中的漂移。如果模型用于预测的数据在统计上与模型训练时的数据不同,那么模型可能需要在更能代表当前数据的数据上重新训练。如果模型不重新训练或者没有意识到这种漂移,那么模型在当前数据上的预测质量将会受到影响。
通过使用无监督学习构建概率分布,我们可以评估当前数据与训练集数据的差异性——如果两者差异足够大,我们可以自动触发重新训练。我们将探讨如何构建这些数据判别器类型的内容在第十二章中。
对监督算法的更详细探讨
在我们深入研究无监督学习系统之前,让我们先看看监督学习算法及其工作原理。这将有助于我们理解无监督学习在机器学习生态系统中的位置。
在监督学习中,存在两种主要类型的问题:分类和回归。在分类中,AI 必须正确地将项目分类为两个或更多类别之一。如果只有两个类别,则该问题称为二元分类。如果有三个或更多类别,则该问题被归类为多类分类。
分类问题也被称为离散预测问题,因为每个类别都是一个离散的群体。分类问题也可能被称为定性或分类问题。
在回归中,AI 必须预测一个连续变量而不是离散变量。回归问题也可能被称为定量问题。
监督式机器学习算法涵盖了从非常简单到非常复杂的整个范围,但它们的目标都是最小化与数据集标签相关的某个成本函数或错误率(或最大化某个值函数)。
正如前面提到的,我们最关心的是机器学习解决方案在前所未见的情况下的泛化能力。选择监督学习算法非常重要,可以最大程度地减少这种泛化误差。
为了达到尽可能低的泛化误差,算法模型的复杂性应该与数据底层真实函数的复杂性相匹配。我们不知道这个真实函数究竟是什么。如果我们知道,我们就不需要使用机器学习来创建模型了——我们只需解决函数以找到正确答案。但由于我们不知道这个真实函数是什么,我们选择机器学习算法来测试假设,并找到最接近这个真实函数的模型(即具有尽可能低的泛化误差)。
如果算法模拟的内容比真实函数复杂度低,我们就欠拟合了数据。在这种情况下,我们可以通过选择能够模拟更复杂函数的算法来改善泛化误差。然而,如果算法设计了一个过于复杂的模型,我们就过拟合了训练数据,并且在以前从未见过的情况下表现不佳,增加了我们的泛化误差。
换句话说,选择复杂算法而不是简单算法并不总是正确的选择——有时简单才是更好的。每种算法都有其一系列的优点、弱点和假设,知道在给定你拥有的数据和你试图解决的问题时何时使用何种方法对于掌握机器学习非常重要。
在本章的其余部分中,我们将描述一些最常见的监督学习算法(包括一些实际应用),然后再介绍无监督算法。³
线性方法
最基本的监督学习算法模拟了输入特征与我们希望预测的输出变量之间的简单线性关系。
线性回归
所有算法中最简单的是线性回归,它使用一个模型假设输入变量(x)与单个输出变量(y)之间存在线性关系。如果输入与输出之间的真实关系是线性的,并且输入变量之间不高度相关(称为共线性),线性回归可能是一个合适的选择。如果真实关系更为复杂或非线性,线性回归将会欠拟合数据。⁴
因为它非常简单,解释算法模型的关系也非常直接。可解释性 对于应用机器学习非常重要,因为解决方案需要被技术和非技术人员在工业中理解和实施。如果没有可解释性,解决方案就会变成不可理解的黑匣子。
优点
线性回归简单、可解释,并且难以过拟合,因为它无法模拟过于复杂的关系。当输入和输出变量之间的基础关系是线性的时,它是一个极好的选择。
弱点
当输入和输出变量之间的关系是非线性的时,线性回归将欠拟合数据。
应用
由于人类体重与身高之间的真实基础关系是线性的,因此线性回归非常适合使用身高作为输入变量来预测体重,或者反过来,使用体重作为输入变量来预测身高。
逻辑回归
最简单的分类算法是 逻辑回归,它也是一种线性方法,但预测结果经过逻辑函数转换。这种转换的输出是类别概率——换句话说,实例属于各个类别的概率,每个实例的概率之和为一。然后将每个实例分配给其最有可能属于的类别。
优势
与线性回归类似,逻辑回归简单且可解释。当我们尝试预测的类别不重叠且线性可分时,逻辑回归是一个很好的选择。
弱点
当类别不是线性可分时,逻辑回归会失败。
应用场景
当类别大部分不重叠时,例如年幼儿童的身高与成年人的身高,逻辑回归效果很好。
基于邻居的方法
另一组非常简单的算法是基于邻居的方法。基于邻居的方法是惰性学习器,因为它们学习如何根据新点与现有标记点的接近程度来标记新点。与线性回归或逻辑回归不同,基于邻居的模型不会学习一个固定的模型来预测新点的标签;相反,这些模型仅基于新点到预先标记点的距离来预测新点的标签。惰性学习也称为基于实例的学习或非参数方法。
k 近邻算法
最常见的基于邻居的方法是 k 近邻算法 (KNN)。为了给每个新点贴上标签,KNN 查看 k 个最近的已标记点(其中 k 是整数),并让这些已标记的邻居投票决定如何给新点贴标签。默认情况下,KNN 使用欧氏距离来衡量最近的点。
k 的选择非常重要。如果 k 设置得非常低,KNN 变得非常灵活,可能会绘制非常微妙的边界并可能过度拟合数据。如果 k 设置得非常高,KNN 变得不够灵活,绘制出过于刚性的边界,可能会欠拟合数据。
优势
不同于线性方法,KNN 非常灵活,能够学习更复杂、非线性的关系。尽管如此,KNN 仍然简单且可解释。
弱点
当观测数和特征数量增加时,KNN 的表现较差。在这种高度密集且高维的空间中,KNN 变得计算效率低下,因为它需要计算新点到许多附近已标记点的距离,以预测标签。它无法依靠具有减少参数数量的高效模型进行必要的预测。此外,KNN 对 k 的选择非常敏感。当 k 设置过低时,KNN 可能过拟合;当 k 设置过高时,KNN 可能欠拟合。
应用场景
KNN 经常被用于推荐系统,比如用来预测电影品味(Netflix)、音乐喜好(Spotify)、朋友(Facebook)、照片(Instagram)、搜索(Google)和购物(Amazon)。例如,KNN 可以帮助预测用户会喜欢什么,基于类似用户喜欢的东西(称为协同过滤)或者用户过去喜欢的东西(称为基于内容的过滤)。
基于树的方法
而不是使用线性方法,我们可以让 AI 构建决策树,在这些实例中所有的实例都被分割或分层成许多区域,这些区域由我们的标签引导。一旦完成这种分割,每个区域对应于一个特定的标签类别(用于分类问题)或预测值范围(用于回归问题)。这个过程类似于让 AI 自动构建规则,其明确目标是做出更好的决策或预测。
单一决策树
最简单的基于树的方法是单一决策树,在这种方法中,AI 一次通过训练数据,根据标签创建数据分割规则,并使用这棵树对从未见过的验证或测试集进行预测。然而,单一决策树通常在将其在训练期间学到的内容推广到从未见过的情况时表现不佳,因为它通常在其唯一的训练迭代期间过拟合训练数据。
装袋
要改进单一决策树,我们可以引入自助聚合(更常被称为装袋),其中我们从训练数据中取多个随机样本实例,为每个样本创建一个决策树,然后通过平均这些树的预测来预测每个实例的输出。通过随机化样本和对多个树的预测结果进行平均——这种方法也被称为集成方法——装袋将解决由单一决策树导致的过拟合问题的一些方面。
随机森林
我们可以通过对预测变量进行采样来进一步改善过拟合。通过随机森林,我们像在装袋中那样从训练数据中取多个随机样本实例,但是,在每个决策树的每次分割中,我们基于预测变量的随机样本而不是所有预测变量进行分割。每次分割考虑的预测变量数量通常是总预测变量数量的平方根。
通过这种方式对预测变量进行采样,随机森林算法创建的树与彼此更少相关(与装袋中的树相比),从而减少过拟合并改善泛化误差。
提升法
另一种称为提升的方法用于创建多棵树,类似于装袋法,但是顺序构建树,使用 AI 从前一棵树学到的知识来改进后续树的结果。每棵树保持相当浅,只有几个决策分裂点,并且学习是逐步进行的,树与树之间逐步增强。在所有基于树的方法中,梯度提升机是表现最佳的,并且常用于赢得机器学习竞赛。⁵
优点
基于树的方法是预测问题中表现最佳的监督学习算法之一。这些方法通过逐步学习许多简单规则来捕捉数据中的复杂关系。它们还能够处理缺失数据和分类特征。
弱点
基于树的方法很难解释,特别是如果需要许多规则来做出良好的预测。随着特征数量的增加,性能也成为一个问题。
应用
梯度提升和随机森林在预测问题上表现出色。
支持向量机
我们可以使用算法在空间中创建超平面来分隔数据,这些算法由我们拥有的标签引导。这种方法被称为支持向量机(SVMs)。 SVMs 允许在这种分隔中存在一些违规情况——并非超空间中的所有点都必须具有相同的标签——但某一标签的边界定义点与另一标签的边界定义点之间的距离应尽可能最大化。此外,边界不一定是线性的——我们可以使用非线性核来更灵活地分隔数据。
神经网络
我们可以使用神经网络来学习数据的表示,神经网络由输入层、多个隐藏层和输出层组成。⁶ 输入层使用特征,输出层试图匹配响应变量。隐藏层是一个嵌套的概念层次结构——每个层(或概念)都试图理解前一层如何与输出层相关联。
使用这种概念层次结构,神经网络能够通过将简单的概念组合起来来学习复杂的概念。神经网络是函数逼近中最强大的方法之一,但容易过拟合且难以解释,我们将在本书后面更详细地探讨这些缺点。
深入探讨无监督算法
现在我们将注意力转向没有标签的问题。无监督学习算法将尝试学习数据的潜在结构,而不是尝试进行预测。
降维
一类算法——称为降维算法——将原始高维输入数据投影到低维空间,滤除不那么相关的特征并保留尽可能多的有趣特征。降维允许无监督学习 AI 更有效地识别模式,并更高效地解决涉及图像、视频、语音和文本的大规模计算问题。
线性投影
维度的两个主要分支是线性投影和非线性降维。我们将首先从线性投影开始。
主成分分析(PCA)
学习数据的基本结构的一种方法是确定在完整特征集中哪些特征对解释数据实例之间变异性最重要。并非所有特征都是相等的——对于某些特征,数据集中的值变化不大,这些特征在解释数据集方面不那么有用。对于其他特征,其值可能会有显著变化——这些特征值得更详细探讨,因为它们将更有助于我们设计的模型分离数据。
在PCA中,该算法在保留尽可能多的变化的同时找到数据的低维表示。我们得到的维度数量远远小于完整数据集的维度数(即总特征数)。通过转移到这个低维空间,我们会失去一些方差,但数据的基本结构更容易识别,这样我们可以更有效地执行诸如聚类之类的任务。
PCA 有几种变体,我们将在本书后面探讨。这些包括小批量变体,如增量 PCA,非线性变体,如核 PCA,以及稀疏变体,如稀疏 PCA。
奇异值分解(SVD)
学习数据的基本结构的另一种方法是将原始特征矩阵的秩降低到一个较小的秩,使得可以用较小秩矩阵中某些向量的线性组合来重建原始矩阵。这就是SVD。为了生成较小秩矩阵,SVD 保留具有最多信息(即最高奇异值)的原始矩阵向量。较小秩矩阵捕捉了原始特征空间的最重要元素。
随机投影
类似的降维算法涉及将高维空间中的点投影到远低于其维度的空间中,以保持点之间的距离比例。我们可以使用随机高斯矩阵或随机稀疏矩阵来实现这一点。
流形学习
PCA 和随机投影都依赖于将数据从高维空间线性投影到低维空间。与线性投影不同,执行数据的非线性变换可能更好——这被称为流形学习或非线性降维。
Isomap
Isomap是一种流形学习方法。该算法通过估计每个点及其邻居之间的测地线或曲线距离而不是欧氏距离来学习数据流形的内在几何结构。Isomap 将此用于将原始高维空间嵌入到低维空间。
t-分布随机近邻嵌入(t-SNE)
另一种非线性降维方法——称为t-SNE——将高维数据嵌入到仅具有两个或三个维度的空间中,使得转换后的数据可以可视化。在这个二维或三维空间中,相似的实例被建模为更接近,而不相似的实例被建模为更远。
字典学习
一种被称为字典学习的方法涉及学习底层数据的稀疏表示。这些代表性元素是简单的二进制向量(零和一),数据集中的每个实例都可以重构为代表性元素的加权和。这种无监督学习生成的矩阵(称为字典)大多数由零填充,只有少数非零权重。
通过创建这样一个字典,该算法能够有效地识别原始特征空间中最显著的代表性元素——这些元素具有最多的非零权重。不太重要的代表性元素将具有较少的非零权重。与 PCA 一样,字典学习非常适合学习数据的基本结构,这对于分离数据和识别有趣的模式将会有所帮助。
独立分量分析
无标签数据的一个常见问题是,许多独立信号被嵌入到我们所获得的特征中。使用独立分量分析(ICA),我们可以将这些混合信号分离成它们的个体组成部分。分离完成后,我们可以通过将我们生成的个体组成部分的某种组合相加来重构任何原始特征。ICA 在信号处理任务中通常用于(例如,识别繁忙咖啡馆音频剪辑中的个别声音)。
潜在狄利克雷分配
无监督学习还可以通过学习为什么数据集的某些部分相互类似来解释数据集。这需要学习数据集中的未观察元素——一种被称为*潜在狄利克雷分配(LDA)*的方法。例如,考虑一个文本文档,其中有许多词。文档内的这些词并非纯粹随机;相反,它们呈现出一定的结构。
这种结构可以建模为称为主题的未观察元素。经过训练后,LDA 能够用一小组主题解释给定的文档,每个主题都有一小组经常使用的单词。这是 LDA 能够捕捉的隐藏结构,帮助我们更好地解释以前结构不清晰的文本语料库。
注意
降维将原始特征集合减少到仅包含最重要的特征集合。然后,我们可以在这些较小的特征集上运行其他无监督学习算法,以发现数据中的有趣模式(参见下一节关于聚类的内容),或者如果有标签,我们可以通过向这些较小的特征矩阵输入来加快监督学习算法的训练周期,而不是使用原始特征矩阵。
聚类
一旦我们将原始特征集减少到一个更小、更易处理的集合,我们可以通过将相似的数据实例分组来找到有趣的模式。这被称为聚类,可以使用各种无监督学习算法来实现,并且可用于市场细分等现实应用中。
k-means
要进行良好的聚类,我们需要识别出不同的群组,使得群组内的实例彼此相似,但与其他群组内的实例不同。其中一种算法是k-means 聚类。使用这种算法,我们指定所需的群组数量k,算法将每个实例分配到这k个群组中的一个。它通过最小化群内变异性(也称为惯性)来优化分组,使得所有k个群组内的群内变异性之和尽可能小。
为了加速这一聚类过程,k-means 随机将每个观测分配到k个群组中的一个,然后开始重新分配这些观测,以最小化每个观测与其群组中心点或质心之间的欧氏距离。因此,不同运行的k-means(每次都从随机起点开始)将导致略有不同的观测聚类分配。从这些不同的运行中,我们可以选择具有最佳分离性能的运行,即所有k个群组内的总群内变异性之和最低的运行。⁷
层次聚类
另一种聚类方法——不需要预先确定特定群组数量的方法被称为层次聚类。层次聚类的一种版本称为聚合聚类,使用基于树的聚类方法,并构建所谓的树状图。树状图可以以图形方式呈现为倒置的树,其中叶子位于底部,树干位于顶部。
在数据集中,最底部的叶子是个体实例。然后,按照它们彼此的相似程度,层次聚类将这些叶子连接在一起——随着我们在颠倒的树上向上移动。最相似的实例(或实例组)会更早地连接在一起,而不那么相似的实例则稍后连接。通过这个迭代过程,所有实例最终链接在一起,形成了树的单一主干。
这种垂直描绘非常有帮助。一旦层次聚类算法运行完成,我们可以查看树状图并确定我们想要切割树的位置——我们在树干上切割得越低,留下的个体分支就越多(即更多的簇)。如果我们想要更少的簇,我们可以在树状图的更高处切割,靠近这颠倒树的顶部的单一主干。这种垂直切割的位置类似于在k-means 聚类算法中选择k簇的数量。⁸
DBSCAN
另一个更强大的聚类算法(基于点的密度)称为DBSCAN(具有噪声的基于密度的空间聚类应用程序)。给定我们在空间中的所有实例,DBSCAN 会将那些紧密聚集在一起的实例分组在一起,其中紧密定义为必须存在一定距离内的最小数量的实例。我们同时指定所需的最小实例数和距离。
如果一个实例在指定的距离内接近多个簇,则将其与其最密集的簇分组。任何不在另一个簇指定距离内的实例都被标记为异常值。
不像k-means 那样,我们不需要预先指定簇的数量。我们还可以拥有任意形状的簇。DBSCAN 在数据中典型的由异常值引起的扭曲问题上要少得多。
特征提取
通过无监督学习,我们可以学习数据原始特征的新表示——一个称为特征提取的领域。特征提取可用于将原始特征的数量减少到更小的子集,从而有效地执行降维。但是特征提取也可以生成新的特征表示,以帮助在监督学习问题上提高性能。
Autoencoders
要生成新的特征表示,我们可以使用前馈、非循环神经网络进行表示学习,其中输出层中的节点数量与输入层中的节点数量相匹配。这种神经网络被称为自编码器,有效地重构原始特征,利用隐藏层之间的学习新的表示。⁹
自编码器的每个隐藏层学习原始特征的表示,后续层基于前面层学习的表示构建。逐层,自编码器从简单表示中学习越来越复杂的表示。
输出层是原始特征的最终新学习表示。这个学习表示然后可以用作监督学习模型的输入,目的是改善泛化误差。
使用前馈网络的监督训练进行特征提取
如果有标签,另一种特征提取方法是使用前馈非递归神经网络,其中输出层试图预测正确的标签。就像自编码器一样,每个隐藏层学习原始特征的表示。
然而,在生成新表示时,该网络明确地由标签引导。为了从这个网络中提取原始特征的最终新学习表示,我们提取倒数第二层——即输出层之前的隐藏层。然后,可以将这个倒数第二层用作任何监督学习模型的输入。
无监督深度学习
在深度学习领域,无监督学习执行许多重要功能,其中一些我们将在本书中探讨。这个领域被称为无监督深度学习。
直到最近,深度神经网络的训练在计算上是棘手的。在这些神经网络中,隐藏层学习内部表示来帮助解决手头的问题。这些表示会随着神经网络在每次训练迭代中如何使用误差函数的梯度来更新各个节点的权重而不断改进。
这些更新计算成本很高,过程中可能会出现两种主要类型的问题。首先,误差函数的梯度可能变得非常小,由于反向传播依赖于这些小权重的乘积,网络的权重可能更新非常缓慢,甚至根本不更新,从而阻止网络的正确训练。¹⁰ 这被称为梯度消失问题。
相反,另一个问题是误差函数的梯度可能变得非常大;通过反向传播,网络中的权重可能会大幅度地更新,使得网络的训练非常不稳定。这被称为梯度爆炸问题。
无监督预训练
为了解决训练非常深、多层神经网络的困难,机器学习研究人员采用多阶段训练神经网络的方法,每个阶段涉及一个浅层神经网络。一个浅层网络的输出被用作下一个神经网络的输入。通常,这个流水线中的第一个浅层神经网络涉及无监督神经网络,但后续的网络是有监督的。
这个无监督部分被称为贪婪逐层无监督预训练。2006 年,Geoffrey Hinton 展示了成功应用无监督预训练来初始化更深神经网络管道的情况,从而开启了当前的深度学习革命。无监督预训练使得 AI 能够捕获原始输入数据的改进表示,随后监督部分利用这些表示来解决手头的具体任务。
这种方法被称为“贪婪”,因为神经网络的每个部分都是独立训练的,而不是联合训练。 “逐层”指的是网络的各层。在大多数现代神经网络中,通常不需要预训练。相反,所有层都使用反向传播联合训练。主要的计算机进步使得梯度消失问题和梯度爆炸问题变得更加可管理。
无监督预训练不仅使监督问题更容易解决,还促进了迁移学习。迁移学习涉及使用机器学习算法将从解决一个任务中获得的知识存储起来,以更快速且需要更少数据的方式解决另一个相关任务。
受限玻尔兹曼机
无监督预训练的一个应用例子是受限玻尔兹曼机(RBM),一个浅层的双层神经网络。第一层是输入层,第二层是隐藏层。每个节点与另一层的每个节点相连接,但节点与同一层的节点不连接——这就是约束的地方。
RBMs 可以执行无监督任务,如降维和特征提取,并作为监督学习解决方案的有用无监督预训练的一部分。RBMs 类似于自动编码器,但在某些重要方面有所不同。例如,自动编码器有一个输出层,而 RBM 则没有。我们将在本书的后续部分详细探讨这些及其他差异。
深度信念网络
RBMs 可以连接在一起形成多阶段神经网络管道,称为深度信念网络(DBN)。每个 RBM 的隐藏层被用作下一个 RBM 的输入。换句话说,每个 RBM 生成数据的表示,然后下一个 RBM 在此基础上构建。通过成功地链接这种表示学习,深度信念网络能够学习更复杂的表示,通常用作特征检测器。¹¹
生成对抗网络
无监督深度学习的一个重大进展是*生成对抗网络(GANs)*的出现,由 Ian Goodfellow 及其蒙特利尔大学的同事于 2014 年引入。GANs 有许多应用,例如,我们可以使用 GANs 创建接近真实的合成数据,如图像和语音,或执行异常检测。
在 GAN 中,我们有两个神经网络。一个网络——生成器——基于其创建的模型数据分布生成数据,该模型数据是通过接收的真实数据样本创建的。另一个网络——鉴别器——区分生成器创建的数据和真实数据分布的数据。
简单类比,生成器是伪造者,鉴别器是试图识别伪造品的警察。这两个网络处于零和博弈中。生成器试图欺骗鉴别器,让其认为合成数据来自真实数据分布,而鉴别器则试图指出合成数据是假的。
GAN(生成对抗网络)是无监督学习算法,因为生成器可以在没有标签的情况下学习真实数据分布的潜在结构。GAN 通过训练过程学习数据中的潜在结构,并使用少量可管理的参数高效捕捉这种结构。
这个过程类似于深度学习中的表征学习。生成器神经网络中的每个隐藏层通过从简单开始捕捉底层数据的表示,随后的层通过建立在较简单前层的基础上,捕捉更复杂的表示。
使用所有这些层,生成器学习数据的潜在结构,并利用所学,尝试创建几乎与真实数据分布相同的合成数据。如果生成器已经捕捉到真实数据分布的本质,合成数据将看起来是真实的。
使用无监督学习处理顺序数据问题
无监督学习也可以处理时间序列等顺序数据。一种方法涉及学习马尔可夫模型的隐藏状态。在简单马尔可夫模型中,状态完全可观察且随机变化(换句话说,随机)。未来状态仅依赖于当前状态,而不依赖于先前状态。
在隐藏马尔可夫模型中,状态仅部分可观察,但与简单马尔可夫模型一样,这些部分可观察状态的输出是完全可观察的。由于我们的观察不足以完全确定状态,我们需要无监督学习帮助更充分地发现这些隐藏状态。
隐藏马尔可夫模型算法涉及学习给定我们所知的先前发生的部分可观察状态和完全可观察输出的可能下一个状态。这些算法在涉及语音、文本和时间序列的顺序数据问题中具有重要的商业应用。
使用无监督学习进行强化学习
强化学习是机器学习的第三大主要分支,其中一个代理人根据它收到的奖励反馈,决定其在环境中的最佳行为(actions)。这种反馈称为强化信号。代理人的目标是随时间最大化其累积奖励。
尽管强化学习自 1950 年代以来就存在,但直到近年来才成为主流新闻头条。2013 年,现为谷歌所有的 DeepMind 应用强化学习实现了超越人类水平的表现,玩转多种不同的 Atari 游戏。DeepMind 的系统仅使用原始感官数据作为输入,并且没有关于游戏规则的先验知识。
2016 年,DeepMind 再次吸引了机器学习社区的想象力——这一次,基于强化学习的 AI 代理 AlphaGo 击败了李世石,世界顶级围棋选手之一。这些成功奠定了强化学习作为主流 AI 主题的地位。
如今,机器学习研究人员正在应用强化学习来解决许多不同类型的问题,包括:
-
股市交易中,代理人买卖股票(actions),并获得利润或损失(rewards)作为回报。
-
视频游戏和棋盘游戏中,代理人做出游戏决策(actions),并赢得或输掉(rewards)。
-
自动驾驶汽车中,代理人指导车辆(actions),并且要么保持在路线上,要么发生事故(rewards)。
-
机器控制中,代理人在其环境中移动(actions),并且要么完成任务,要么失败(rewards)。
在最简单的强化学习问题中,我们有一个有限问题——环境的状态有限,任何给定环境状态下可能的动作有限,并且奖励的数量也是有限的。在给定当前环境状态下,代理人采取的行动决定了下一个状态,代理人的目标是最大化其长期奖励。这类问题称为有限的马尔可夫决策过程。
然而,在现实世界中,事情并不那么简单——奖励是未知的和动态的,而不是已知的和静态的。为了帮助发现这个未知的奖励函数并尽可能地逼近它,我们可以应用无监督学习。利用这个近似的奖励函数,我们可以应用强化学习解决方案,以增加随时间累积的奖励。
半监督学习
尽管监督学习和无监督学习是机器学习的两个明显不同的主要分支,但每个分支的算法可以作为机器学习流水线的一部分混合在一起。¹² 通常,在我们想充分利用少数标签或者想从无标签数据中找到新的未知模式以及从标记数据中已知的模式时,我们会混合使用监督和无监督学习。这些类型的问题通过一种称为半监督学习的混合方式来解决。我们将在本书后续章节详细探讨这一领域。
无监督学习的成功应用
在过去的十年中,大多数成功的商业应用来自监督学习领域,但情况正在改变。无监督学习应用变得越来越普遍。有时,无监督学习只是改善监督应用的手段。其他时候,无监督学习本身就实现了商业应用。以下是迄今为止两个最大的无监督学习应用的更详细介绍:异常检测和群体分割。
异常检测
进行降维可以将原始的高维特征空间转化为一个转换后的低维空间。在这个低维空间中,我们找到了大多数点密集分布的地方。这部分被称为正常空间。远离这些点的点被称为离群点或异常,值得更详细地调查。
异常检测系统通常用于诸如信用卡欺诈、电汇欺诈、网络欺诈和保险欺诈等欺诈检测。异常检测还用于识别罕见的恶意事件,如对互联网连接设备的黑客攻击,对飞机和火车等关键设备的维护故障,以及由恶意软件和其他有害代理引起的网络安全漏洞。
我们可以将这些系统用于垃圾邮件检测,例如我们在本章前面使用的电子邮件垃圾过滤器示例。其他应用包括寻找如恐怖主义资金、洗钱、人口和毒品贩运以及军火交易等活动的不良行为,识别金融交易中的高风险事件,以及发现癌症等疾病。
为了使异常分析更加可管理,我们可以使用聚类算法将相似的异常分组在一起,然后基于它们所代表的行为类型手动标记这些聚类。通过这样的系统,我们可以拥有一个能够识别异常、将它们聚类到适当组中,并且利用人类提供的聚类标签向业务分析师推荐适当行动的无监督学习人工智能。
通过异常检测系统,我们可以将一个无监督问题逐步转换为半监督问题,通过这种集群和标记的方法。随着时间的推移,我们可以在未标记数据上运行监督算法,并行进行无监督算法。对于成功的机器学习应用程序,无监督系统和监督系统应该同时使用,相辅相成。
监督系统以高精度找到已知模式,而无监督系统发现可能感兴趣的新模式。一旦这些模式被无监督 AI 揭示,人类会对这些模式进行标记,将更多数据从未标记转换为已标记。
群体分割
通过聚类,我们可以根据行为相似性在市场营销、客户保持、疾病诊断、在线购物、音乐收听、视频观看、在线约会、社交媒体活动和文档分类等领域中对群体进行分割。在这些领域产生的数据量非常庞大,且数据只有部分被标记。
对于我们已经了解并希望加强的模式,我们可以使用监督学习算法。但通常我们希望发现新的模式和感兴趣的群体——对于这一发现过程,无监督学习是一个自然的选择。再次强调,这一切都是关于协同作用。我们应该同时使用监督和无监督学习系统来构建更强大的机器学习解决方案。
结论
在本章中,我们探讨了以下内容:
-
基于规则的系统和机器学习的区别
-
监督学习和无监督学习的区别
-
无监督学习如何帮助解决训练机器学习模型中的常见问题
-
常见的监督、无监督、强化和半监督学习算法
-
无监督学习的两个主要应用——异常检测和群体分割
在第二章中,我们将探讨如何构建机器学习应用程序。然后,我们将详细讨论降维和聚类,逐步构建异常检测系统和群体分割系统。
¹ 有像 Figure Eight 这样明确提供人在循环服务的初创企业。
² 欠拟合是在构建机器学习应用程序时可能出现的另一个问题,但这更容易解决。欠拟合是因为模型过于简单——算法无法构建足够复杂的函数逼近来为当前任务做出良好的决策。为了解决这个问题,我们可以允许算法增加规模(增加参数、执行更多训练迭代等)或者应用更复杂的机器学习算法。
³ 这个列表并非详尽无遗,但包含了最常用的机器学习算法。
⁴ 可能有其他潜在问题会使得线性回归成为一个不好的选择,包括异常值、误差项相关性以及误差项方差的非常数性。
⁵ 想要了解机器学习竞赛中梯度提升的更多信息,请查阅 Ben Gorman 的博客文章。
⁶ 想要了解更多关于神经网络的信息,请参阅 Ian Goodfellow、Yoshua Bengio 和 Aaron Courville 的《深度学习》(MIT Press)。
⁷ k-均值聚类的快速变体包括小批量k-均值,我们稍后在书中进行介绍。
⁸ 分层聚类默认使用欧几里得距离,但也可以使用其他相似度度量,比如基于相关的距离,我们稍后在书中详细探讨。
⁹ 有几种类型的自编码器,每种都学习不同的表示。这些包括去噪自编码器、稀疏自编码器和变分自编码器,我们稍后在书中进行探讨。
¹⁰ 反向传播(也称为误差反向传播)是神经网络使用的基于梯度下降的算法,用于更新权重。在反向传播中,首先计算最后一层的权重,然后用于更新前面层的权重。这个过程一直持续到第一层的权重被更新。
¹¹ 特征检测器学习原始数据的良好表示,帮助分离不同的元素。例如,在图像中,特征检测器帮助分离鼻子、眼睛、嘴等元素。
¹² Pipeline 指的是一种机器学习解决方案的系统,这些解决方案依次应用以实现更大的目标。
第二章:端到端机器学习项目
在我们详细探讨无监督学习算法之前,我们将回顾如何设置和管理机器学习项目,涵盖从获取数据到构建和评估模型以及实现解决方案的所有内容。我们将在本章使用监督学习模型——大多数读者应该对此有所了解——然后在下一章跳入无监督学习模型。
环境设置
在继续之前,让我们先设置数据科学环境。这个环境对于监督学习和无监督学习都是相同的。
注意
这些说明针对的是 Windows 操作系统的优化,但也提供了适用于 Mac 和 Linux 的安装包。
版本控制:Git
如果你还没有安装 Git,你需要安装它。Git 是一个用于代码版本控制的系统,本书中的所有代码示例都可以在 GitHub 仓库 的 Jupyter notebooks 中找到。请参阅 Roger Dudler 的 Git 指南,了解如何克隆仓库、添加、提交和推送更改,并使用分支进行版本控制。
克隆《实战无监督学习》Git 仓库
打开命令行界面(例如 Windows 上的命令提示符,Mac 上的终端等)。导航至你将存储无监督学习项目的目录。使用以下提示从 GitHub 克隆与本书相关的仓库:
$ git clone https://github.com/aapatel09/handson-unsupervised-learning.git
$ git lfs pull
或者,你可以访问 仓库 的 GitHub 网站,手动下载仓库供你使用。你可以 watch 或 star 该仓库以便随时了解更新。
一旦仓库被拉取或手动下载,使用命令行界面导航至 handson-unsupervised-learning 仓库。
$ cd handson-unsupervised-learning
对于接下来的安装步骤,我们将继续使用命令行界面。
科学计算库:Python 的 Anaconda 发行版
要安装 Python 和机器学习所需的科学计算库,请下载 Python 的 Anaconda 发行版(推荐使用版本 3.6,因为本书编写时版本 3.7 较新,不是所有我们将使用的机器学习库都支持该版本)。
创建一个孤立的 Python 环境,以便你可以为每个项目单独导入不同的库:
$ conda create -n unsupervisedLearning python=3.6 anaconda
这将创建一个名为 unsupervisedLearning 的孤立的 Python 3.6 环境——其中包含 Anaconda 发行版提供的所有科学计算库。
现在,激活它以便使用:
$ activate unsupervisedLearning
神经网络:TensorFlow 和 Keras
一旦激活 unsupervisedLearning,你需要安装 TensorFlow 和 Keras 来构建神经网络。TensorFlow 是由 Google 开源的项目,不是 Anaconda 发行版的一部分:
$ pip install tensorflow
Keras 是一个开源的神经网络库,它为我们提供了一个更高级的 API,用于在 TensorFlow 的底层函数上进行操作。换句话说,我们将在 TensorFlow(后端)之上使用 Keras,以便使用更直观的 API 调用来开发我们的深度学习模型:
$ pip install keras
梯度增强,第一版:XGBoost
接下来,安装一种称为 XGBoost 的梯度增强的版本。为了简化操作(至少对 Windows 用户而言),您可以导航到 handson-unsupervised-learning 存储库中的 xgboost 文件夹,并在那里找到包。
要安装该包,请使用 pip install:
cd xgboost
pip install xgboost-0.6+20171121-cp36-cp36m-win_amd64.whl
或者,根据您的系统下载正确版本的 XGBoost —— 32 位或 64 位版本。
在命令行界面中,导航到具有此新下载文件的文件夹。使用 pip install:
$ pip install xgboost-0.6+20171121-cp36-cp36m-win_amd64.whl
注意
您的 XGBoost WHL 文件名可能会略有不同,因为新版本的软件已公开发布。
安装成功后,回到 handson-unsupervised-learning 文件夹。
梯度增强,第二版:LightGBM
安装另一个梯度增强版本,Microsoft 的 LightGBM:
$ pip install lightgbm
聚类算法
让我们安装一些在本书后面将要使用的聚类算法。其中一个聚类包 fastcluster 是一个 C++ 库,具有 Python/SciPy 的接口。¹
可以使用以下命令安装这个 fastcluster 包:
$ pip install fastcluster
另一个聚类算法是 hdbscan,也可以通过 pip 安装:
$ pip install hdbscan
另外,为了时间序列聚类,让我们安装 tslearn:
$ pip install tslearn
交互式计算环境:Jupyter Notebook
Jupyter notebook 是 Anaconda 发行版的一部分,因此我们现在将其激活,以启动我们刚刚设置的环境。在输入以下命令之前,请确保您位于 handson-unsupervised-learning 存储库中(为了方便使用):
$ jupyter notebook
您应该看到浏览器打开并启动 http://localhost:8888/ 页面。必须启用 Cookie 才能正常访问。
现在我们准备构建我们的第一个机器学习项目。
数据概述
在本章中,我们将使用一个真实的数据集,该数据集包含 2013 年 9 月由欧洲持卡人进行的匿名信用卡交易。² 这些交易被标记为欺诈或真实,我们将使用机器学习构建欺诈检测解决方案,以预测从未见过的实例的正确标签。
此数据集高度不平衡。在 284,807 笔交易中,只有 492 笔是欺诈交易(0.172%)。这种低欺诈比例对于信用卡交易来说相当典型。
共有 28 个特征,全部为数值特征,没有分类变量。³ 这些特征不是原始特征,而是通过主成分分析得出的,我们将在 第 3 章 中探索这种降维方法,其将 28 个原始特征精简为主成分。
除了 28 个主成分外,我们还有三个其他变量——交易时间、交易金额以及交易的真实类别(如果是欺诈则为一,否则为零)。
数据准备
在可以使用机器学习训练数据并开发欺诈检测解决方案之前,我们需要为算法准备数据。
数据采集
任何机器学习项目的第一步是数据采集。
下载数据
下载数据集,并在 handson-unsupervised-learning 目录中将 CSV 文件放置在 /datasets/credit_card_data/ 文件夹中。如果您之前已经下载了 GitHub 仓库,则已在该仓库的此文件夹中有此文件。
导入必要的库
导入我们构建欺诈检测解决方案所需的 Python 库:
'''Main'''
import numpy as np
import pandas as pd
import os
'''Data Viz'''
import matplotlib.pyplot as plt
import seaborn as sns
color = sns.color_palette()
import matplotlib as mpl
%matplotlib inline
'''Data Prep'''
from sklearn import preprocessing as pp
from scipy.stats import pearsonr
from sklearn.model_selection import train_test_split
from sklearn.model_selection import StratifiedKFold
from sklearn.metrics import log_loss
from sklearn.metrics import precision_recall_curve, average_precision_score
from sklearn.metrics import roc_curve, auc, roc_auc_score
from sklearn.metrics import confusion_matrix, classification_report
'''Algos'''
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
import xgboost as xgb
import lightgbm as lgb
读取数据
current_path = os.getcwd()
file = '\\datasets\\credit_card_data\\credit_card.csv'
data = pd.read_csv(current_path + file)
预览数据
表格 2-1 显示数据集的前五行。您可以看到,数据已经正确加载:
data.head()
表格 2-1. 数据预览
| 时间 | V1 | V2 | V3 | V4 | V5 | |
|---|---|---|---|---|---|---|
| 0 | 0.0 | –1.359807 | –0.072781 | 2.536347 | 1.378155 | –0.338321 |
| 1 | 0.0 | 1.191857 | 0.266151 | 0.166480 | 0.448154 | 0.060018 |
| 2 | 1.0 | –1.358354 | –1.340163 | 1.773209 | 0.379780 | –0.503198 |
| 3 | 1.0 | –0.966272 | –0.185226 | 1.792993 | –0.863291 | –0.010309 |
| 4 | 2.0 | –1.158233 | 0.877737 | 1.548718 | 0.403034 | –0.407193 |
| 5 行 × 31 列 |
数据探索
接下来,让我们深入了解数据。我们将为数据生成摘要统计信息,识别任何缺失值或分类特征,并按特征计算不同值的数量。
生成摘要统计信息
表格 2-2 逐列描述数据。接下来的代码块列出了所有列名,以便参考。
data.describe()
表格 2-2. 简单的摘要统计
| 时间 | V1 | V2 | V3 | V4 | |
|---|---|---|---|---|---|
| 总数 | 284807.000000 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 |
| 均值 | 94813.859575 | 3.919560e–15 | 5.688174e–16 | –8.769071e–15 | 2.782312e–15 |
| 标准差 | 47488.145955 | 1.958696e+00 | 1.651309e+00 | 1.516255e+00 | 1.415869e+00 |
| 最小值 | 0.000000 | –5.640751e+01 | –7.271573e+01 | –4.832559e+01 | –5.683171e+00 |
| 25% | 54201.500000 | –9.203734e–01 | –5.985499e–01 | –8.903648e–01 | –8.486401e–01 |
| 50% | 84692.000000 | 1.810880e–02 | 6.548556e–02 | 1.798463e–01 | –1.984653e–02 |
| 75% | 139320.500000 | 1.315642e+00 | 8.037239e–01 | 1.027196e+00 | 7.433413e–01 |
| 最大值 | 172792.000000 | 2.454930e+00 | 2.205773e+01 | 9.382558e+00 | 1.687534e+01 |
| 31 列 x 8 行 |
data.columns
Index(['Time', 'V1,' 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10',
'V11', 'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19', 'V20', 'V21',
'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'Amount', 'Class'],
dtype='object')
data['Class'].sum()
正标签的总数,或欺诈交易,为 492。如预期,共有 284,807 个实例和 31 列——28 个数值特征(V1 至 V28),时间,金额和类别。
时间戳范围从 0 到 172,792,金额范围从 0 到 25,691.16,有 492 笔欺诈交易。这些欺诈交易也称为正案例或正标签(标记为一);正常交易称为负案例或负标签(标记为零)。
这 28 个数值特征目前尚未标准化,但我们很快将对数据进行标准化。标准化会将数据重新缩放,使其均值为零,标准差为一。
小贴士
一些机器学习解决方案对数据的规模非常敏感,因此通过标准化使所有数据在相同的相对比例上具有良好的机器学习实践。
另一种常见的数据缩放方法是归一化,它将数据重新缩放到零到一的范围内。与标准化数据不同,所有归一化数据都在正数范围内。
通过特征识别非数字值
一些机器学习算法无法处理非数字值或缺失值。因此,最佳实践是识别非数字值(也称为非数字或NaN)。
在缺失值的情况下,我们可以填充值——例如,用特征的平均值、中位数或众数替换缺失点——或用某个用户定义的值替换。对于分类值,我们可以对数据进行编码,以便所有分类值都用稀疏矩阵表示。然后,这个稀疏矩阵与数值特征结合。机器学习算法基于这个组合特征集进行训练。
以下代码显示,观察中没有 NaN 值,因此我们不需要填充或编码任何值:
nanCounter = np.isnan(data).sum()
Time 0
V1 0
V2 0
V3 0
V4 0
V5 0
V6 0
V7 0
V8 0
V9 0
V10 0
V11 0
V12 0
V13 0
V14 0
V15 0
V16 0
V17 0
V18 0
V19 0
V20 0
V21 0
V22 0
V23 0
V24 0
V25 0
V26 0
V27 0
V28 0
Amount 0
Class 0
dtype: int64
通过特征识别不同的值
为了更好地理解信用卡交易数据集,让我们按特征计算不同值的数量。
以下代码显示,我们有 124,592 个不同的时间戳。但是我们从之前知道总共有 284,807 个观测值。这意味着某些时间戳上有多次交易。
不出所料,只有两类——一类是欺诈,零类是非欺诈:
distinctCounter = data.apply(lambda x: len(x.unique()))
Time 124592
V1 275663
V2 275663
V3 275663
V4 275663
V5 275663
V6 275663
V7 275663
V8 275663
V9 275663
V10 275663
V11 275663
V12 275663
V13 275663
V14 275663
V15 275663
V16 275663
V17 275663
V18 275663
V19 275663
V20 275663
V21 275663
V22 275663
V23 275663
V24 275663
V25 275663
V26 275663
V27 275663
V28 275663
Amount 32767
Class 2
dtype: int64
生成特征矩阵和标签数组
让我们创建并标准化特征矩阵 X,并分离标签数组 y(欺诈为一,非欺诈为零)。稍后在训练期间,我们将把它们输入到机器学习算法中。
创建特征矩阵 X 和标签数组 Y
dataX = data.copy().drop([‘Class’],axis=1)
dataY = data[‘Class’].copy()
标准化特征矩阵 X
让我们重新缩放特征矩阵,使得每个特征(时间除外)的均值为零,标准差为一:
featuresToScale = dataX.drop(['Time'],axis=1).columns
sX = pp.StandardScaler(copy=True)
dataX.loc[:,featuresToScale] = sX.fit_transform(dataX[featuresToScale])
正如 Table 2-3 所示,标准化后的特征现在均值为零,标准差为一。
Table 2-3. Summary of scaled features
| Time | V1 | V2 | V3 | V4 | |
|---|---|---|---|---|---|
| count | 284807.000000 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 | 2.848070e+05 |
| mean | 94813.859575 | –8.157366e–16 | 3.154853e–17 | –4.409878e–15 | –6.734811e–16 |
| std | 47488.145955 | 1.000002e+00 | 1.000002e+00 | 1.000002e+00 | 1.000002e+00 |
| min | 0.000000 | –2.879855e+01 | –4.403529e+01 | –3.187173e+01 | –4.013919e+00 |
| 25% | 54201.500000 | –4.698918e–01 | –3.624707e–01 | –5.872142e–01 | –5.993788e–01 |
| 50% | 84692.000000 | 9.245351e–03 | 3.965683e–02 | 1.186124e–02 | –1.401724e–01 |
| 75% | 139320.500000 | 6.716939e–01 | 4.867202e–01 | 6.774569e–01 | 5.250082e–01 |
| max | 172792.000000 | 1.253351e+00 | 1.335775e+01 | 6.187993e+00 | 1.191874e+01 |
| 8 rows x 30 columns |
Feature Engineering and Feature Selection
在大多数机器学习项目中,我们应该将特征工程和特征选择视为解决方案的一部分。特征工程涉及创建新特征,例如从原始特征计算比率、计数或总和,以帮助机器学习算法从数据集中提取更强的信号。
特征选择涉及选择用于训练的特征子集,有效地从考虑中移除一些不太相关的特征。这有助于防止机器学习算法过度拟合数据集中的噪声。
对于这个信用卡欺诈数据集,我们没有原始特征。我们只有从 PCA 中得出的主成分,PCA 是一种我们将在第三章中探讨的降维形式。由于我们不知道任何特征代表什么,我们无法进行任何智能特征工程。
由于观测值(284,807)远远超过特征数(30),因此特征选择也是不必要的,这显著降低了过拟合的可能性。而且,正如 Figure 2-1 所示,特征之间的相关性只是轻微的。换句话说,我们没有冗余特征。如果有的话,我们可以通过降维来消除或减少冗余。当然,这并不奇怪。PCA 已经在这个信用卡数据集上执行过了,为我们消除了冗余。
检查特征之间的相关性
correlationMatrix = pd.DataFrame(data=[],index=dataX.columns,
columns=dataX.columns)
for i in dataX.columns:
for j in dataX.columns:
correlationMatrix.loc[i,j] = np.round(pearsonr(dataX.loc[:,i],
dataX.loc[:,j])[0],2)
Figure 2-1. Correlation matrix
Data Visualization
最后一步,让我们来可视化数据,以了解数据集的不平衡程度(Figure 2-2)。由于欺诈案例很少,这是一个难题;幸运的是,我们有整个数据集的标签:
count_classes = pd.value_counts(data['Class'],sort=True).sort_index()
ax = sns.barplot(x=count_classes.index, y=tuple(count_classes/len(data)))
ax.set_title('Frequency Percentage by Class')
ax.set_xlabel('Class')
ax.set_ylabel('Frequency Percentage')
Figure 2-2. Frequency percentage of labels
Model Preparation
现在数据准备好了,让我们为模型做准备。我们需要将数据分割为训练集和测试集,选择成本函数,并为k折交叉验证做准备。
将数据集分割为训练集和测试集
正如您可能从第一章中回忆起的,机器学习算法从数据中学习(即在数据上进行训练),以在以前未见过的案例上表现良好(即准确预测)。在这些以前未见过的案例上的表现被称为泛化误差——这是确定机器学习模型好坏的最重要指标。
我们需要设置我们的机器学习项目,以便从中学习的机器学习算法具有训练集。我们还需要一个测试集(以前未见过的案例),机器学习算法可以对其进行预测。这个测试集上的性能将是成功的最终标准。
让我们继续将我们的信用卡交易数据集分割为训练集和测试集。
X_train, X_test, y_train, y_test = train_test_split(dataX,
dataY, test_size=0.33,
random_state=2018, stratify=dataY)
现在我们有一个包含 190,280 个实例的训练集(原始数据集的 67%)和一个包含 93,987 个实例的测试集(剩余的 33%)。为了保持训练集和测试集中欺诈比例(约 0.17%)的一致性,我们设置了分层参数。我们还将随机状态设置为 2018,以便更容易地重现结果。⁴
我们将使用测试集来最终评估我们的泛化误差(也称为样本外误差)。
选择成本函数
在我们对训练集进行训练之前,我们需要一个成本函数(也称为错误率或值函数),将其传递给机器学习算法。机器学习算法将尝试通过从训练示例中学习来最小化这个成本函数。
由于这是一个监督分类问题——有两个类别——让我们使用二元分类对数损失(如方程式 2-1 所示),它将计算真实标签与基于模型的预测之间的交叉熵。
方程式 2-1. 对数损失函数
log loss= – 1N Σ i=1 N Σ j=1 M y i,j log ( p i,j )
其中N是观察数;M是类别标签数(在本例中为两个);log 是自然对数;[yi,j] 如果观察i属于类别j则为 1,否则为 0;[pi,j] 是观察i属于类别j的预测概率。
机器学习模型将为每笔信用卡交易生成欺诈概率。欺诈概率越接近真实标签(即欺诈为 1 或非欺诈为 0),对数损失函数的值越低。这是机器学习算法将尝试最小化的目标。
创建 k 折交叉验证集
为了帮助机器学习算法估计其在以前未见过的示例(测试集)上的性能,最佳做法是进一步将训练集分割为训练集和验证集。
例如,如果我们将训练集分为五分之一,我们可以在原始训练集的四分之一上进行训练,并通过对原始训练集的第五个切片进行预测来评估新的训练模型,称为验证集。
可以像这样训练和评估五次——每次留出一个不同的五分之一作为验证集。这被称为k折交叉验证,其中k在本例中为五。通过这种方法,我们将不是一个估计值,而是五个泛化误差的估计值。
我们将为五次运行中的每一次存储训练得分和交叉验证得分,并且我们将每次存储交叉验证预测。在所有五次运行完成后,我们将对整个数据集进行交叉验证预测。这将是测试集性能的最佳整体估计。
下面是如何为k折验证设置,其中k为五:
k_fold = StratifiedKFold(n_splits=5, shuffle=True, random_state=2018)
机器学习模型(第一部分)
现在我们准备构建机器学习模型。对于我们考虑的每个机器算法,我们将设置超参数,训练模型,并评估结果。
模型 #1:逻辑回归
让我们从最基本的分类算法开始,逻辑回归。
设置超参数
penalty = 'l2'
C = 1.0
class_weight = 'balanced'
random_state = 2018
solver = 'liblinear'
logReg = LogisticRegression(penalty=penalty, C=C,
class_weight=class_weight, random_state=random_state,
solver=solver, n_jobs=n_jobs)
我们将把惩罚设置为默认值 L2 而不是 L1。与 L1 相比,L2 对异常值不太敏感,并且将为几乎所有特征分配非零权重,从而产生一个稳定的解决方案。L1 将为最重要的特征分配高权重,并为其余特征分配接近零的权重,实际上在算法训练时执行特征选择。然而,由于权重在特征之间变化很大,所以 L1 解决方案对数据点的变化不如 L2 解决方案稳定。⁵
C 是正则化强度。如您可能还记得的来自第一章,正则化通过惩罚复杂性来帮助解决过拟合问题。换句话说,正则化越强,机器学习算法对复杂性的惩罚就越大。正则化促使机器学习算法更喜欢简单的模型而不是更复杂的模型,其他条件相等。
这个正则化常数 C 必须是一个正浮点数。数值越小,正则化越强。我们将保持默认值 1.0。
我们的信用卡交易数据集非常不平衡——在所有的 284,807 个案例中,只有 492 个是欺诈性的。随着机器学习算法的训练,我们希望算法更多地关注学习来自正标记交易的情况,换句话说,就是欺诈交易,因为在数据集中这样的交易很少。
对于这个逻辑回归模型,我们将设置class_weight为平衡。这向逻辑回归算法表示我们有一个类别不平衡的问题;算法在训练时将需要更重视正标签。在这种情况下,权重将与类别频率成反比;算法将给罕见的正标签(即欺诈)分配更高的权重,给更常见的负标签(即非欺诈)分配较低的权重。
随机状态固定为 2018,以帮助其他人——例如你,读者——复现结果。我们将保持默认的 solver liblinear。
训练模型
现在超参数已经设定好,我们将在每个五折交叉验证分割上训练逻辑回归模型,用训练集的四分之四来训练,并在留置的第五切片上评估性能。
当我们像这样训练和评估五次后,我们将计算成本函数——我们信用卡交易问题的对数损失——对训练集(即原始训练集的五分之四切片)和验证集(即原始训练集的五分之一切片)。我们还将存储每个五折交叉验证集的预测;到第五次运行结束时,我们将得到整个训练集的预测:
trainingScores = []
cvScores = []
predictionsBasedOnKFolds = pd.DataFrame(data=[],
index=y_train.index,columns=[0,1])
model = logReg
for train_index, cv_index in k_fold.split(np.zeros(len(X_train))
,y_train.ravel()):
X_train_fold, X_cv_fold = X_train.iloc[train_index,:], \
X_train.iloc[cv_index,:]
y_train_fold, y_cv_fold = y_train.iloc[train_index], \
y_train.iloc[cv_index]
model.fit(X_train_fold, y_train_fold)
loglossTraining = log_loss(y_train_fold,
model.predict_proba(X_train_fold)[:,1])
trainingScores.append(loglossTraining)
predictionsBasedOnKFolds.loc[X_cv_fold.index,:] = \
model.predict_proba(X_cv_fold)
loglossCV = log_loss(y_cv_fold,
predictionsBasedOnKFolds.loc[X_cv_fold.index,1])
cvScores.append(loglossCV)
print('Training Log Loss: ', loglossTraining)
print('CV Log Loss: ', loglossCV)
loglossLogisticRegression = log_loss(y_train,
predictionsBasedOnKFolds.loc[:,1])
print('Logistic Regression Log Loss: ', loglossLogisticRegression)
评估结果
下面的代码显示了五次运行中每次的训练对数损失和交叉验证对数损失。一般来说(但不总是),训练对数损失会低于交叉验证对数损失。因为机器学习算法直接从训练数据中学习,所以它在训练集上的表现(即对数损失)应该比在交叉验证集上好。请记住,交叉验证集包含了在训练过程中明确保留的交易。
Training Log Loss: 0.10080139188958696
CV Log Loss: 0.10490645274118293
Training Log Loss: 0.12098957040484648
CV Log Loss: 0.11634801169793386
Training Log Loss: 0.1074616029843435
CV Log Loss: 0.10845630232487576
Training Log Loss: 0.10228137039781758
CV Log Loss: 0.10321736161148198
Training Log Loss: 0.11476012373315266
CV Log Loss: 0.1160124452312548
注意
对于我们的信用卡交易数据集,重要的是要记住我们正在构建一个欺诈检测解决方案。当我们提到机器学习模型的性能时,我们指的是模型在数据集中预测欺诈的能力有多好。
机器学习模型为每笔交易输出一个预测概率,其中 1 表示欺诈,0 表示非欺诈。预测概率越接近 1,交易越可能是欺诈;越接近 0,交易越可能是正常的。通过将模型的预测概率与真实标签进行比较,我们可以评估模型的好坏。
对于五次运行中的每一次,它们的训练和交叉验证对数损失是相似的。逻辑回归模型没有表现出严重的过拟合;如果有的话,我们将会看到低的训练对数损失和相对较高的交叉验证对数损失。
由于我们存储了每个五折交叉验证集的预测结果,我们可以将这些预测合并成一个单一集合。这个单一集合与原始训练集相同,现在我们可以计算整个训练集的总体对数损失。这是对测试集上逻辑回归模型对数损失的最佳估计:
Logistic Regression Log Loss: 0.10978811472134588
评估指标
虽然对数损失是评估机器学习模型性能的好方法,但我们可能希望有更直观的方法来理解结果。例如,在训练集中的欺诈交易中,我们捕获了多少个?这就是召回率。或者,逻辑回归模型标记为欺诈交易的交易中,有多少是真正的欺诈交易?这就是模型的精确率。
让我们查看这些及其他类似的评估指标,以帮助我们更直观地理解结果。
注意
这些评估指标非常重要,因为它们使数据科学家能够直观地向不熟悉对数损失、交叉熵和其他成本函数的业务人员解释结果。将复杂结果尽可能简单地传达给非数据科学家是应用数据科学家必须掌握的基本技能之一。
混淆矩阵
在典型的分类问题(没有类别不平衡情况)中,我们可以使用混淆矩阵来评估结果,它是一个总结真正例、真负例、假正例和假负例数量的表格(图 2-3)。
图 2-3. 混淆矩阵
鉴于我们的信用卡交易数据集类别高度不平衡,使用混淆矩阵将是有意义的。例如,如果我们预测每笔交易都不是欺诈交易,我们将得到 284,315 个真负例,492 个假负例,零个真正例和零个假正例。我们在识别真正欺诈交易方面的准确率为 0%。在这种类别不平衡问题下,混淆矩阵未能有效捕捉到这种次优结果。
对于涉及更平衡类别的问题(即真正例数量大致与真负例数量相似),混淆矩阵可能是一个好的、直接的评估指标。考虑到我们的不平衡数据集,我们需要找到一个更合适的评估指标。
精确率-召回率曲线
对于我们的不平衡信用卡交易数据集,评估结果的更好方法是使用精确率和召回率。精确率是真正例的数量除以总的正例预测数量。换句话说,模型捕获了多少个欺诈交易?
精确率 = 真正例 + 假正例
高精度意味着——在所有我们的正面预测中——许多是真正例(换句话说,它具有较低的假阳性率)。
召回率是数据集中实际正例的数量中捕捉到的欺诈交易数量。换句话说,模型捕捉了多少欺诈交易?⁷
召回率 = 真正例 ∕ (真正例 + 假正例)
高召回率意味着模型捕捉到了大部分真正例(换句话说,它具有较低的假阴性率)。
高召回率但低精度的解决方案返回许多结果——捕捉到许多正例,但也有许多误报。高精度但低召回率的解决方案则恰恰相反;返回很少的结果——捕捉到数据集中所有正例的一部分,但其大多数预测是正确的。
如果我们的解决方案精度高但召回率低,那么找到的欺诈交易数量很少,但大多数确实是欺诈交易。
然而,如果解决方案精度低但召回率高,则会标记许多交易为欺诈,从而捕获大部分欺诈行为,但被标记的交易中大多数并非欺诈。
显然,两种解决方案都存在重大问题。在高精度-低召回率的情况下,信用卡公司会因为欺诈而损失很多钱,但不会因不必要地拒绝交易而激怒客户。在低精度-高召回率的情况下,信用卡公司会捕捉到很多欺诈行为,但肯定会因不必要地拒绝大量正常非欺诈交易而惹怒客户。
最佳解决方案需要具有高精度和高召回率,仅拒绝那些真正欺诈的交易(即高精度),并捕捉数据集中大部分的欺诈案例(高召回率)。
精度和召回率通常存在折衷,通常由算法设置的阈值决定,以将正例与负例分开;在我们的例子中,正例是欺诈,负例是非欺诈。如果阈值设置得太高,预测为正例的案例很少,导致高精度但低召回率。随着阈值的降低,预测为正例的案例增加,通常降低精度并增加召回率。
对于我们的信用卡交易数据集来说,可以把阈值看作是机器学习模型在拒绝交易方面的敏感性。如果阈值过高/严格,模型会拒绝很少的交易,但被拒绝的交易很可能是欺诈的。
阈值降低(即变得不那么严格),模型会拒绝更多交易,捕获更多的欺诈案例,但也不必要地拒绝更多正常案例。
精确率-召回率曲线的图形展示了精确率和召回率之间的权衡。为了评估精确率-召回率曲线,我们可以计算平均精度,即在每个阈值下达到的精确率的加权平均值。平均精度越高,解决方案越好。
注意
阈值的选择非常重要,并且通常需要业务决策者的输入。数据科学家可以向这些业务决策者展示精确率-召回率曲线,以确定阈值应该设定在何处。
对于我们的信用卡交易数据集,关键问题是如何平衡客户体验(即避免拒绝正常交易)与欺诈检测(即捕捉到欺诈交易)?没有业务输入,我们无法回答这个问题,但我们可以找到具有最佳精确率-召回率曲线的模型。然后,我们可以将该模型呈现给业务决策者,以设定适当的阈值。
接收者操作特征曲线
另一个很好的评估指标是接收者操作特征曲线下的面积(auROC)。接收者操作特征(ROC)曲线将真阳性率绘制在 Y 轴上,将假阳性率绘制在 X 轴上。真阳性率也可以称为灵敏度,假阳性率也可以称为 1-特异度。曲线越接近绘图的左上角,解决方案越好——绝对最优点的值为(0.0, 1.0),表示假阳性率为 0%,真阳性率为 100%。
要评估解决方案,我们可以计算这条曲线下的面积。auROC 越大,解决方案越好。
评估逻辑回归模型
现在我们了解了一些使用的评估指标,让我们使用它们更好地理解逻辑回归模型的结果。
首先,让我们绘制精确率-召回率曲线并计算平均精度:
preds = pd.concat([y_train,predictionsBasedOnKFolds.loc[:,1]], axis=1)
preds.columns = ['trueLabel','prediction']
predictionsBasedOnKFoldsLogisticRegression = preds.copy()
precision, recall, thresholds = precision_recall_curve(preds['trueLabel'],
preds['prediction'])
average_precision = average_precision_score(preds['trueLabel'],
preds['prediction'])
plt.step(recall, precision, color='k', alpha=0.7, where='post')
plt.fill_between(recall, precision, step='post', alpha=0.3, color='k')
plt.xlabel('Recall')
plt.ylabel('Precision')
plt.ylim([0.0, 1.05])
plt.xlim([0.0, 1.0])
plt.title('Precision-Recall curve: Average Precision = {0:0.2f}'.format(
average_precision))
图 2-4 展示了精确率-召回率曲线的图表。综合我们之前讨论的内容,你可以看到我们可以实现大约 80%的召回率(即捕获 80%的欺诈交易),精确率约为 70%(即模型标记为欺诈的交易中,70%确实是欺诈交易,而其余 30%则错误地被标记为欺诈)。
图 2-4. 逻辑回归的精确率-召回率曲线
我们可以通过计算平均精度将这条精确率-召回率曲线简化为一个数字,对于这个逻辑回归模型来说,这个平均精度为 0.73。目前我们还不能确定这个平均精度是好是坏,因为我们没有其他模型可以与我们的逻辑回归模型进行比较。
现在,让我们测量 auROC:
fpr, tpr, thresholds = roc_curve(preds['trueLabel'],preds['prediction'])
areaUnderROC = auc(fpr, tpr)
plt.figure()
plt.plot(fpr, tpr, color='r', lw=2, label='ROC curve')
plt.plot([0, 1], [0, 1], color='k', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic:
Area under the curve = {0:0.2f}'.format(areaUnderROC))
plt.legend(loc="lower right")
plt.show()
如图 Figure 2-5 所示,auROC 曲线为 0.97. 这个指标是评估逻辑回归模型优劣的另一种方式,它可以帮助您确定在保持尽可能低的误报率的情况下能够捕获多少欺诈。和平均精度一样,我们不知道这个 0.97 的 auROC 曲线是好还是坏,但一旦与其他模型进行比较,我们就会知道。
图 2-5. 逻辑回归的 auROC 曲线
机器学习模型(第二部分)
为了比较逻辑回归模型的优劣,让我们使用其他监督学习算法构建几个更多的模型。
模型 #2:随机森林
让我们从随机森林开始。
与逻辑回归一样,我们将设置超参数,训练模型,并使用精确-召回曲线和 auROC 评估结果。
设置超参数
n_estimators = 10
max_features = 'auto'
max_depth = None
min_samples_split = 2
min_samples_leaf = 1
min_weight_fraction_leaf = 0.0
max_leaf_nodes = None
bootstrap = True
oob_score = False
n_jobs = -1
random_state = 2018
class_weight = 'balanced'
RFC = RandomForestClassifier(n_estimators=n_estimators,
max_features=max_features, max_depth=max_depth,
min_samples_split=min_samples_split, min_samples_leaf=min_samples_leaf,
min_weight_fraction_leaf=min_weight_fraction_leaf,
max_leaf_nodes=max_leaf_nodes, bootstrap=bootstrap,
oob_score=oob_score, n_jobs=n_jobs, random_state=random_state,
class_weight=class_weight)
让我们从默认的超参数开始。估计器的数量设置为 10;换句话说,我们将建立 10 棵树,并在这 10 棵树上的结果上取平均值。对于每棵树,模型将考虑总特征数的平方根(在本例中,总共 30 个特征的平方根,即 5 个特征,向下取整)。
将 max_depth 设为 none 后,决策树会尽可能深地生长,在给定特征子集的情况下进行尽可能多的分裂。与逻辑回归相似,我们将随机状态设置为 2018 以保证结果的可复现性,并考虑到数据集不平衡将类别权重设置为平衡。
训练模型
我们将运行 k-折交叉验证五次,在四分之四的训练数据上进行训练,并在第五个切片上进行预测。我们将逐步存储预测结果:
trainingScores = []
cvScores = []
predictionsBasedOnKFolds = pd.DataFrame(data=[],
index=y_train.index,columns=[0,1])
model = RFC
for train_index, cv_index in k_fold.split(np.zeros(len(X_train)),
y_train.ravel()):
X_train_fold, X_cv_fold = X_train.iloc[train_index,:], \
X_train.iloc[cv_index,:]
y_train_fold, y_cv_fold = y_train.iloc[train_index], \
y_train.iloc[cv_index]
model.fit(X_train_fold, y_train_fold)
loglossTraining = log_loss(y_train_fold, \
model.predict_proba(X_train_fold)[:,1])
trainingScores.append(loglossTraining)
predictionsBasedOnKFolds.loc[X_cv_fold.index,:] = \
model.predict_proba(X_cv_fold)
loglossCV = log_loss(y_cv_fold, \
predictionsBasedOnKFolds.loc[X_cv_fold.index,1])
cvScores.append(loglossCV)
print('Training Log Loss: ', loglossTraining)
print('CV Log Loss: ', loglossCV)
loglossRandomForestsClassifier = log_loss(y_train,
predictionsBasedOnKFolds.loc[:,1])
print('Random Forests Log Loss: ', loglossRandomForestsClassifier)
评估结果
训练和交叉验证的对数损失结果如下:
Training Log Loss: 0.0003951763883952557
CV Log Loss: 0.014479198936303003
Training Log Loss: 0.0004501221178398935
CV Log Loss: 0.005712702421375242
Training Log Loss: 0.00043128813023860164
CV Log Loss: 0.00908372752510077
Training Log Loss: 0.0004341676022058672
CV Log Loss: 0.013491161736979267
Training Log Loss: 0.0004275530435950083
CV Log Loss: 0.009963232439211515
注意,训练集的训练对数损失要远远低于交叉验证的对数损失,这表明随机森林分类器在训练过程中在某种程度上对数据进行了过度拟合,尽管使用了大多数默认的超参数。
下面的代码显示了整个训练集上的对数损失(使用交叉验证预测):
Random Forests Log Loss: 0.010546004611793962
尽管它在某种程度上过度拟合了训练数据,但随机森林的验证对数损失约为逻辑回归的十分之一——相对于先前的机器学习解决方案,这是显著的改进。随机森林模型在正确标记信用卡交易中的欺诈方面表现更好。
Figure 2-6 显示了随机森林的精确-召回曲线。从曲线可以看出,该模型可以以大约 80% 的精确度捕获大约 80% 的欺诈情况。这比逻辑回归模型以 70% 精确度捕获的大约 80% 的欺诈情况更为显著。
图 2-6. 随机森林的精确-召回曲线
随机森林模型的平均精度为 0.79,明显优于逻辑回归模型的 0.73 平均精度。然而,随机森林的 auROC,如图 Figure 2-7,稍微差一些,为 0.93,而逻辑回归为 0.97。
图 2-7. 随机森林的 auROC 曲线
模型 #3:梯度提升机(XGBoost)
现在让我们使用梯度提升进行训练并评估结果。梯度提升有两个流行版本,一个是被称为 XGBoost,另一个是微软快速版本 LightGBM。让我们使用每个版本构建模型,首先是 XGBoost。⁸
设置超参数
我们将其设置为一个二元分类问题,并使用对数损失作为成本函数。我们将每棵树的最大深度设置为默认值六,并设置默认学习率为 0.3。对于每棵树,我们将使用所有观测值和所有特征;这些是默认设置。我们将设置随机状态为 2018,以确保结果的可重现性:
params_xGB = {
'nthread':16, #number of cores
'learning rate': 0.3, #range 0 to 1, default 0.3
'gamma': 0, #range 0 to infinity, default 0
# increase to reduce complexity (increase bias, reduce variance)
'max_depth': 6, #range 1 to infinity, default 6
'min_child_weight': 1, #range 0 to infinity, default 1
'max_delta_step': 0, #range 0 to infinity, default 0
'subsample': 1.0, #range 0 to 1, default 1
# subsample ratio of the training examples
'colsample_bytree': 1.0, #range 0 to 1, default 1
# subsample ratio of features
'objective':'binary:logistic',
'num_class':1,
'eval_metric':'logloss',
'seed':2018,
'silent':1
}
训练模型
与之前一样,我们将使用k-折交叉验证,在不同的四分之四的训练数据上训练,并在第五部分进行预测,总共进行五次运行。
对于五次运行中的每一次,梯度提升模型将训练多达两千轮,评估交叉验证的对数损失是否在进行中减少。如果交叉验证的对数损失在前两百轮停止改善,则训练过程将停止,以避免过拟合。训练过程的结果很详细,所以我们不会在这里打印它们,但可以通过 GitHub 上的代码 找到:
trainingScores = []
cvScores = []
predictionsBasedOnKFolds = pd.DataFrame(data=[],
index=y_train.index,columns=['prediction'])
for train_index, cv_index in k_fold.split(np.zeros(len(X_train)),
y_train.ravel()):
X_train_fold, X_cv_fold = X_train.iloc[train_index,:], \
X_train.iloc[cv_index,:]
y_train_fold, y_cv_fold = y_train.iloc[train_index], \
y_train.iloc[cv_index]
dtrain = xgb.DMatrix(data=X_train_fold, label=y_train_fold)
dCV = xgb.DMatrix(data=X_cv_fold)
bst = xgb.cv(params_xGB, dtrain, num_boost_round=2000,
nfold=5, early_stopping_rounds=200, verbose_eval=50)
best_rounds = np.argmin(bst['test-logloss-mean'])
bst = xgb.train(params_xGB, dtrain, best_rounds)
loglossTraining = log_loss(y_train_fold, bst.predict(dtrain))
trainingScores.append(loglossTraining)
predictionsBasedOnKFolds.loc[X_cv_fold.index,'prediction'] = \
bst.predict(dCV)
loglossCV = log_loss(y_cv_fold, \
predictionsBasedOnKFolds.loc[X_cv_fold.index,'prediction'])
cvScores.append(loglossCV)
print('Training Log Loss: ', loglossTraining)
print('CV Log Loss: ', loglossCV)
loglossXGBoostGradientBoosting = \
log_loss(y_train, predictionsBasedOnKFolds.loc[:,'prediction'])
print('XGBoost Gradient Boosting Log Loss: ', loglossXGBoostGradientBoosting)
评估结果
如以下结果所示,整个训练集上的对数损失(使用交叉验证预测)仅为随机森林的五分之一,逻辑回归的五十分之一。这是对前两个模型的显著改进:
XGBoost Gradient Boosting Log Loss: 0.0029566906288156715
如图 Figure 2-8 所示,平均精度为 0.82,略低于随机森林(0.79),但明显优于逻辑回归(0.73)。
图 2-8. XGBoost 梯度提升的精确-召回曲线
如图 Figure 2-9 所示,auROC 曲线为 0.97,与逻辑回归相同(0.97),比随机森林(0.93)更好。到目前为止,基于对数损失、精确-召回曲线和 auROC,梯度提升是三个模型中最好的。
图 2-9. XGBoost 梯度提升的 auROC 曲线
模型 #4:梯度提升机(LightGBM)
现在让我们使用另一个名为 LightGBM 的梯度提升版本进行训练。⁹
设置超参数
我们将其设置为二元分类问题,并使用对数损失作为成本函数。 我们将每棵树的最大深度设置为 4,并使用学习率为 0.1。 对于每棵树,我们将使用所有样本和所有特征; 这些是默认设置。 我们将使用一个树的默认叶子节点数(31),并设置一个随机状态以确保结果的可重现性:
params_lightGB = {
'task': 'train',
'application':'binary',
'num_class':1,
'boosting': 'gbdt',
'objective': 'binary',
'metric': 'binary_logloss',
'metric_freq':50,
'is_training_metric':False,
'max_depth':4,
'num_leaves': 31,
'learning_rate': 0.01,
'feature_fraction': 1.0,
'bagging_fraction': 1.0,
'bagging_freq': 0,
'bagging_seed': 2018,
'verbose': 0,
'num_threads':16
}
训练模型
与之前一样,我们将使用k-fold 交叉验证,并在这五次循环中进行存储验证集上的预测:
trainingScores = []
cvScores = []
predictionsBasedOnKFolds = pd.DataFrame(data=[],
index=y_train.index,columns=['prediction'])
for train_index, cv_index in k_fold.split(np.zeros(len(X_train)),
y_train.ravel()):
X_train_fold, X_cv_fold = X_train.iloc[train_index,:], \
X_train.iloc[cv_index,:]
y_train_fold, y_cv_fold = y_train.iloc[train_index], \
y_train.iloc[cv_index]
lgb_train = lgb.Dataset(X_train_fold, y_train_fold)
lgb_eval = lgb.Dataset(X_cv_fold, y_cv_fold, reference=lgb_train)
gbm = lgb.train(params_lightGB, lgb_train, num_boost_round=2000,
valid_sets=lgb_eval, early_stopping_rounds=200)
loglossTraining = log_loss(y_train_fold, \
gbm.predict(X_train_fold, num_iteration=gbm.best_iteration))
trainingScores.append(loglossTraining)
predictionsBasedOnKFolds.loc[X_cv_fold.index,'prediction'] = \
gbm.predict(X_cv_fold, num_iteration=gbm.best_iteration)
loglossCV = log_loss(y_cv_fold, \
predictionsBasedOnKFolds.loc[X_cv_fold.index,'prediction'])
cvScores.append(loglossCV)
print('Training Log Loss: ', loglossTraining)
print('CV Log Loss: ', loglossCV)
loglossLightGBMGradientBoosting = \
log_loss(y_train, predictionsBasedOnKFolds.loc[:,'prediction'])
print('LightGBM gradient boosting Log Loss: ', loglossLightGBMGradientBoosting)
对于五次运行中的每一次,梯度提升模型将训练多达两千轮,评估交叉验证对数损失是否在进行中减少。 如果交叉验证对数损失停止改善(在前两百轮中),则训练过程将停止以避免过拟合。 训练过程的结果很冗长,所以我们不会在这里打印出来,但可以通过 GitHub 上的代码 找到。
评估结果
下面的结果显示,整个训练集上的对数损失(使用交叉验证预测)与 XGBoost 相似,是随机森林的五分之一,是逻辑回归的五十分之一。 但与 XGBoost 相比,LightGBM 速度要快得多:
LightGBM Gradient Boosting Log Loss: 0.0029732268054261826
如 图 2-10 所示,平均精度为 0.82,与 XGBoost 相同(0.82),优于随机森林(0.79),远优于逻辑回归(0.73)。
图 2-10. LightGBM 梯度提升的精确度-召回率曲线
如 图 2-11 所示,auROC 曲线为 0.98,比 XGBoost(0.97),逻辑回归(0.97)和随机森林(0.93)都有所改进。
图 2-11. LightGBM 梯度提升的 auROC 曲线
使用测试集评估四个模型
到目前为止,在本章中,我们学习了如何:
-
设置机器学习项目的环境
-
获取、加载、探索、清洗和可视化数据
-
将数据集分割为训练集和测试集,并设置k-fold 交叉验证集
-
选择适当的成本函数
-
设置超参数并进行训练和交叉验证
-
评估结果
我们尚未探索如何调整超参数(即超参数微调过程),以改善每个机器学习解决方案的结果并解决欠拟合/过拟合问题,但是 GitHub 上的代码 将使您能够非常轻松地进行这些实验。
即使没有进行这样的精细调整,结果也很明显。根据我们的训练和k折交叉验证,LightGBM 梯度提升是最佳解决方案,紧随其后的是 XGBoost。随机森林和逻辑回归则较差。
让我们使用测试集作为四个模型的最终评估。
对于每个模型,我们将使用训练好的模型预测测试集交易的欺诈概率。然后,通过比较模型预测的欺诈概率与真实欺诈标签,计算每个模型的对数损失:
predictionsTestSetLogisticRegression = \
pd.DataFrame(data=[],index=y_test.index,columns=['prediction'])
predictionsTestSetLogisticRegression.loc[:,'prediction'] = \
logReg.predict_proba(X_test)[:,1]
logLossTestSetLogisticRegression = \
log_loss(y_test, predictionsTestSetLogisticRegression)
predictionsTestSetRandomForests = \
pd.DataFrame(data=[],index=y_test.index,columns=['prediction'])
predictionsTestSetRandomForests.loc[:,'prediction'] = \
RFC.predict_proba(X_test)[:,1]
logLossTestSetRandomForests = \
log_loss(y_test, predictionsTestSetRandomForests)
predictionsTestSetXGBoostGradientBoosting = \
pd.DataFrame(data=[],index=y_test.index,columns=['prediction'])
dtest = xgb.DMatrix(data=X_test)
predictionsTestSetXGBoostGradientBoosting.loc[:,'prediction'] = \
bst.predict(dtest)
logLossTestSetXGBoostGradientBoosting = \
log_loss(y_test, predictionsTestSetXGBoostGradientBoosting)
predictionsTestSetLightGBMGradientBoosting = \
pd.DataFrame(data=[],index=y_test.index,columns=['prediction'])
predictionsTestSetLightGBMGradientBoosting.loc[:,'prediction'] = \
gbm.predict(X_test, num_iteration=gbm.best_iteration)
logLossTestSetLightGBMGradientBoosting = \
log_loss(y_test, predictionsTestSetLightGBMGradientBoosting)
在下面的对数损失块中没有什么意外。LightGBM 梯度提升在测试集上有最低的对数损失,其次是其他模型。
Log Loss of Logistic Regression on Test Set: 0.123732961313
Log Loss of Random Forests on Test Set: 0.00918192757674
Log Loss of XGBoost Gradient Boosting on Test Set: 0.00249116807943
Log Loss of LightGBM Gradient Boosting on Test Set: 0.002376320092424
图 2-12 到 2-19 是四个模型的精确率-召回率曲线、平均精度和 auROC 曲线,验证了我们以上的发现。
逻辑回归
图 2-12. 逻辑回归的测试集精确率-召回率曲线
图 2-13. 逻辑回归的测试集 auROC 曲线
随机森林
图 2-14. 随机森林的测试集精确率-召回率曲线
图 2-15. 逻辑回归的测试集 auROC 曲线
XGBoost 梯度提升
图 2-16. XGBoost 梯度提升的测试集精确率-召回率曲线
图 2-17. XGBoost 梯度提升的测试集 auROC 曲线
LightGBM 梯度提升
图 2-18. LightGBM 梯度提升的测试集精确率-召回率曲线
图 2-19. LightGBM 梯度提升的测试集 auROC 曲线
LightGBM 梯度提升的结果令人印象深刻——我们可以捕捉超过 80%的欺诈交易,并且准确率接近 90%(换句话说,捕捉到 80%的总欺诈交易中,LightGBM 模型仅有 10%的错误)。
考虑到我们的数据集中欺诈案例很少,这是一项很大的成就。
集成模型
我们可以评估是否将这些开发的机器学习解决方案集成到生产中,以提高欺诈检测率¹⁰,而不是仅选择一个。
通常,如果我们包含来自不同机器学习家族的同样强大的解决方案(例如来自随机森林和神经网络的解决方案),这些解决方案的集成将比任何一个独立的解决方案产生更好的结果。这是因为每个独立的解决方案都有不同的优势和劣势。通过在集成中包含这些独立解决方案,一些模型的优势弥补了其他模型的劣势,反之亦然。
不过有重要的注意事项。如果独立的解决方案同样强大,集成模型的性能将优于任何一个独立的解决方案。但如果其中一个解决方案远远优于其他解决方案,集成模型的性能将等于最佳独立解决方案的性能;而次优解决方案将对集成模型的性能毫无贡献。
另外,独立的解决方案需要相对不相关。如果它们高度相关,一个解决方案的优点会反映在其余解决方案上,同样的情况也会出现在缺点上。通过集成来实现多样化将不会有太多好处。
堆叠
在我们的问题中,两个模型(LightGBM 梯度提升和 XGBoost 梯度提升)比其他两个模型(随机森林和逻辑回归)强大得多。但是最强大的两个模型来自同一个家族,这意味着它们的优势和劣势高度相关。
我们可以使用堆叠(一种集成形式)来确定是否能够相比之前的独立模型获得性能改进。在堆叠中,我们从每个四个独立模型的k-折交叉验证预测(称为第一层预测)中获取预测,并将它们附加到原始训练数据集上。然后,我们使用该原始特征加上第一层预测数据集进行k-折交叉验证训练。
这将产生一个新的k-折交叉验证预测集,称为第二层预测,我们将评估是否在性能上比任何单独的模型有所改进。
将第一层预测与原始训练数据集结合
首先,让我们将每个构建的四个机器学习模型的预测与原始训练数据集相结合:
predictionsBasedOnKFoldsFourModels = pd.DataFrame(data=[],index=y_train.index)
predictionsBasedOnKFoldsFourModels = predictionsBasedOnKFoldsFourModels.join(
predictionsBasedOnKFoldsLogisticRegression['prediction'].astype(float), \
how='left').join(predictionsBasedOnKFoldsRandomForests['prediction'] \
.astype(float),how='left',rsuffix="2").join( \
predictionsBasedOnKFoldsXGBoostGradientBoosting['prediction'] \
.astype(float), how='left',rsuffix="3").join( \
predictionsBasedOnKFoldsLightGBMGradientBoosting['prediction'] \
.astype(float), how='left',rsuffix="4")
predictionsBasedOnKFoldsFourModels.columns = \
['predsLR','predsRF','predsXGB','predsLightGBM']
X_trainWithPredictions = \
X_train.merge(predictionsBasedOnKFoldsFourModels,
left_index=True,right_index=True)
设置超参数
现在我们将使用 LightGBM 梯度提升——之前练习中的最佳机器学习算法——在该原始特征加上第一层预测数据集上进行训练。超参数将保持与之前相同:
params_lightGB = {
'task': 'train',
'application':'binary',
'num_class':1,
'boosting': 'gbdt',
'objective': 'binary',
'metric': 'binary_logloss',
'metric_freq':50,
'is_training_metric':False,
'max_depth':4,
'num_leaves': 31,
'learning_rate': 0.01,
'feature_fraction': 1.0,
'bagging_fraction': 1.0,
'bagging_freq': 0,
'bagging_seed': 2018,
'verbose': 0,
'num_threads':16
}
训练模型
和之前一样,我们将使用k-折交叉验证,并为五个不同的交叉验证集生成欺诈概率:
trainingScores = []
cvScores = []
predictionsBasedOnKFoldsEnsemble = \
pd.DataFrame(data=[],index=y_train.index,columns=['prediction'])
for train_index, cv_index in k_fold.split(np.zeros(len(X_train)), \
y_train.ravel()):
X_train_fold, X_cv_fold = \
X_trainWithPredictions.iloc[train_index,:], \
X_trainWithPredictions.iloc[cv_index,:]
y_train_fold, y_cv_fold = y_train.iloc[train_index], y_train.iloc[cv_index]
lgb_train = lgb.Dataset(X_train_fold, y_train_fold)
lgb_eval = lgb.Dataset(X_cv_fold, y_cv_fold, reference=lgb_train)
gbm = lgb.train(params_lightGB, lgb_train, num_boost_round=2000,
valid_sets=lgb_eval, early_stopping_rounds=200)
loglossTraining = log_loss(y_train_fold, \
gbm.predict(X_train_fold, num_iteration=gbm.best_iteration))
trainingScores.append(loglossTraining)
predictionsBasedOnKFoldsEnsemble.loc[X_cv_fold.index,'prediction'] = \
gbm.predict(X_cv_fold, num_iteration=gbm.best_iteration)
loglossCV = log_loss(y_cv_fold, \
predictionsBasedOnKFoldsEnsemble.loc[X_cv_fold.index,'prediction'])
cvScores.append(loglossCV)
print('Training Log Loss: ', loglossTraining)
print('CV Log Loss: ', loglossCV)
loglossEnsemble = log_loss(y_train, \
predictionsBasedOnKFoldsEnsemble.loc[:,'prediction'])
print('Ensemble Log Loss: ', loglossEnsemble)
评估结果
在以下结果中,我们没有看到任何改进。集成对数损失非常类似于独立梯度提升对数损失。由于最佳的独立解决方案来自相同的家族(梯度提升),我们没有看到结果的改进。它们在检测欺诈方面具有高度相关的优势和劣势。在模型之间进行多样化并没有好处:
Ensemble Log Loss: 0.002885415974220497
如图 2-20 和 2-21 所示,精确率-召回率曲线、平均精度和 auROC 也证实了改进的缺乏。
图 2-20. 集成的精确率-召回率曲线
图 2-21. 集成的 auROC 曲线
最终模型选择
由于集成并没有提高性能,我们更倾向于使用独立的 LightGBM 梯度提升模型的简洁性,并将其用于生产。
在我们为新进入的交易创建流水线之前,让我们可视化一下 LightGBM 模型在测试集中如何将欺诈交易与正常交易分开。
图 2-22 在 x 轴上显示了预测概率。基于这个图表,模型在将实际欺诈交易分配高欺诈概率方面表现相当不错。反之,该模型通常会给不欺诈的交易分配低概率。偶尔,模型会错误地给实际欺诈案例分配低概率,而给非欺诈案例分配高概率。
总体而言,结果相当令人印象深刻。
图 2-22. 预测概率和真实标签的绘图
生产流水线
现在我们已经选择了一个模型进行生产,让我们设计一个简单的流水线,对新进入的数据执行三个简单的步骤:加载数据,缩放特征,并使用我们已经训练并选择用于生产的 LightGBM 模型生成预测:
'''Pipeline for New Data'''
# first, import new data into a dataframe called 'newData'
# second, scale data
# newData.loc[:,featuresToScale] = sX.transform(newData[featuresToScale])
# third, predict using LightGBM
# gbm.predict(newData, num_iteration=gbm.best_iteration)
一旦生成了这些预测,分析师可以对预测为欺诈概率最高的交易采取行动(即进一步调查),并逐一处理列表。或者,如果自动化是目标,分析师可以使用一个自动拒绝预测为欺诈概率高于某个阈值的交易的系统。
例如,基于 图 2-13,如果我们自动拒绝预测概率高于 0.90 的交易,我们将拒绝几乎肯定是欺诈的案例,而不会意外地拒绝非欺诈案例。
结论
恭喜!您已经使用监督学习构建了一个信用卡欺诈检测系统。
我们一起建立了一个机器学习环境,获取并准备了数据,训练和评估了多个模型,选择了最终用于生产的模型,并设计了一个新的、流入交易的管道。你已经成功创建了一个应用的机器学习解决方案。
现在,我们将采用同样的实践方法,利用无监督学习开发应用的机器学习解决方案。
注意
随着欺诈模式的变化,上述解决方案需要随时间重新训练。此外,我们应该找到其他机器学习算法——来自不同机器学习家族的算法——它们能像梯度提升一样表现良好,并将它们组合起来以改善整体的欺诈检测性能。
最后,解释性对于机器学习在实际应用中非常重要。因为这个信用卡交易数据集的特征是 PCA 的输出(一种我们将在第三章探讨的降维形式),我们无法用简单的英语解释为什么某些交易被标记为潜在的欺诈行为。为了更好地解释结果,我们需要访问原始的 PCA 前特征,但对于这个示例数据集,我们没有这些特征。
¹ 想了解更多关于 fastcluster 的信息,请参阅文档。
² 这个数据集可以通过Kaggle获取,并且是由 Worldline 和 Universite Libre de Bruxelles 的机器学习小组在研究合作期间收集的。更多信息请参见 Andrea Dal Pozzolo、Olivier Caelen、Reid A. Johnson 和 Gianluca Bontempi 的论文《Calibrating Probability with Undersampling for Unbalanced Classification》,发表于 IEEE 的计算智能与数据挖掘研讨会(CIDM),2015 年。
³ 分类变量取可能的有限数量的可能质量值之一,通常需要进行编码以在机器学习算法中使用。
⁴ 想了解 stratify 参数如何保留正标签比例,请访问官方网站。为了在你的实验中复制相同的分割,请将随机状态设置为 2018。如果设置为其他数字或者不设置,结果将不同。
⁵ 想了解 L1 与 L2 的区别,请参考博文“L1 和 L2 作为损失函数和正则化的区别。”
⁶ 真正例是预测和实际标签都为真的实例。真负例是预测和实际标签都为假的实例。假正例是预测为真但实际标签为假的实例(也称为误报或类型 I 错误)。假负例是预测为假但实际标签为真的实例(也称为漏报或类型 II 错误)。
⁷ 召回率也称为敏感性或真正率。与敏感性相关的概念是特异性或真负率。特异性定义为数据集中真负例数除以总实际负例数。特异性 = 真负率 = 真负例 / (真负例 + 假正例)。
⁸ 关于 XGBoost 梯度提升的更多信息,请参阅GitHub 代码库。
⁹ 关于 Microsoft 的 LightGBM 梯度提升的更多信息,请参阅GitHub 代码库。
¹⁰ 关于集成学习的更多信息,请参考“Kaggle 集成指南,” “Python 中的集成/堆叠介绍,” 和 “实践中的模型堆叠指南”。