TPOT-自动机器学习-全-

TPOT 自动机器学习(全)

原文:annas-archive.org/md5/19eaa1d0c8545e21eea1290b4c7ec6c1

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

机器学习任务的自动化使开发者有更多时间专注于由机器学习模型驱动的软件的可用性和响应性。TPOT 是一个用于使用遗传编程优化机器学习管道的 Python 自动化机器学习工具。使用 TPOT 自动化机器学习使个人和公司能够比传统方法更快、更便宜地开发生产就绪的机器学习模型。

通过这本 AutoML 实用指南,使用 Python 进行机器学习任务的开发者将能够将他们的知识付诸实践,并迅速变得高效。你将采用动手的方式来学习 AutoML 的实现及其相关方法。本书包含对基本概念的逐步解释、实际示例和自我评估问题,将向你展示如何构建自动化分类和回归模型,并将它们的性能与自定义构建的模型进行比较。随着你的进步,你还将使用仅几行代码开发最先进的模型,并看到这些模型如何在同一数据集上优于你之前的所有模型。

到这本书的结尾,你将获得信心,在生产级别上在你的组织中实施 AutoML 技术。

本书面向对象

对于新接触机器学习并希望在他们的应用中使用它的数据科学家、数据分析师和软件开发者,这本书将非常有用。这本书也适合希望使用机器学习自动化业务任务的企业用户。要开始,需要具备 Python 编程语言的工作知识以及机器学习的入门级理解。

本书涵盖内容

第一章机器学习和自动化理念,简要介绍了机器学习,分类和回归任务之间的区别,自动化概述及其必要性,以及 Python 生态系统中的机器学习选项。

第二章深入 TPOT,提供了 TPOT 是什么以及不是的深入概述,如何使用它来处理机器学习中的自动化,以及它可以自动化的任务类型。本章还将指导你设置编程环境。

第三章使用 TPOT 探索回归,介绍了 TPOT 在回归任务中的应用。你将学习如何将自动化算法应用于数据,以及如何探索你的数据集。

第四章使用 TPOT 探索分类,介绍了 TPOT 在分类任务中的应用。你将学习如何进行基本的数据探索性分析、准备数据、训练自动化模型,并将这些自动化模型与 scikit-learn 的默认模型进行比较。

第五章, 使用 TPOT 和 Dask 进行并行训练,介绍了使用 Python 和 Dask 库进行并行编程的基础知识。您将学习如何使用 Dask 以并行方式训练自动化模型。

第六章, 深度学习入门:神经网络速成课程,涵盖了深度学习背后的基本概念,例如神经元、层、激活函数和人工神经网络。

第七章, 使用 TPOT 的神经网络分类器,提供了一个逐步指南,用于实现一个完全自动化的神经网络分类器、数据集探索、模型训练和评估。

第八章, TPOT 模型部署,带您通过模型部署的逐步指南。您将学习如何使用 Flask 和 Flask-RESTful 构建一个 REST API,然后在本地和 AWS 上部署。

第九章, 在生产中使用部署的 TPOT 模型,涵盖了在笔记本环境和简单 Web 应用程序中使用部署的模型的方法。

要充分利用本书

您需要在您的计算机上安装 Python 3.6 或更高版本。本书的代码在 Python 3.8.x 上进行了测试,但任何高于 3.6 的版本都应该工作良好。本书不是针对特定操作系统的,因为所有代码都将独立于操作系统工作。然而,请记住,大部分代码最初是在 macOS 上运行的。

您不需要任何高级或授权软件来跟随本书。每个库都是完全开源的。

如果您正在使用本书的数字版,我们建议您亲自输入代码或通过 GitHub 仓库(下一节中提供链接)访问代码。这样做将帮助您避免与代码复制和粘贴相关的任何潜在错误。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件:github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT。如果代码有更新,它将在现有的 GitHub 仓库中更新。

我们还有其他来自我们丰富的图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!

下载彩色图像

我们还提供了一个包含本书中使用的截图/图表的彩色图像的 PDF 文件。您可以从这里下载:static.packt-cdn.com/downloads/9781800567887_ColorImages.pdf

使用的约定

本书使用了多种文本约定。

文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“将下载的WebStorm-10*.dmg磁盘映像文件作为您系统中的另一个磁盘挂载。”

代码块设置如下:

output = (inputs[0] * weights[0] + 
          inputs[1] * weights[1] + 
          inputs[2] * weights[2] +
          inputs[3] * weights[3] +
          inputs[4] * weights[4] + 
          bias)
output

当我们希望您注意代码块中的特定部分时,相关的行或项目会被设置为粗体:

CPU times: user 26.5 s, sys: 9.7 s, total: 36.2 s
Wall time: 42 s

任何命令行输入或输出都应如下编写:

pipenv install "dask[complete]"

小贴士或重要注意事项

看起来是这样的。

联系我们

我们欢迎读者的反馈。

一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并通过 customercare@packtpub.com 将邮件发送给我们。

勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告,请访问www.packtpub.com/support/errata,选择您的书籍,点击勘误表提交表单链接,并输入详细信息。

盗版:如果您在互联网上以任何形式发现我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过版权@packt.com 与我们联系,并提供材料的链接。

如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com

评论

请留下评论。一旦您阅读并使用过这本书,为何不在购买它的网站上留下评论呢?潜在读者可以查看并使用您的客观意见来做出购买决定,我们 Packt 公司可以了解您对我们产品的看法,我们的作者也可以看到他们对书籍的反馈。谢谢!

想了解更多关于 Packt 的信息,请访问packt.com

第一部分:介绍机器学习与自动化理念

本节提供了机器学习的快速复习——分类和回归任务,自动化概述及其必要性,以及 Python 生态系统中的可用选项。

本节包含以下章节:

  • 第一章机器学习与自动化理念

第一章:机器学习和自动化的理念

在本章中,我们将快速复习机器学习的核心主题。包括监督式机器学习,以及回归和分类的基本概念。

我们将从学生、专业人士和商业用户等多个角度理解为什么机器学习对于 21 世纪的成就是至关重要的,并讨论机器学习可以解决的不同类型的问题。

此外,我们将介绍自动化的概念,并了解它如何应用于机器学习任务。我们将探讨 Python 生态系统中的自动化选项,并比较它们的优缺点。我们将简要介绍TPOT库,并讨论它在现代机器学习自动化中的作用。

本章将涵盖以下主题:

  • 回顾机器学习的历史

  • 回顾自动化

  • 将自动化应用于机器学习

  • Python 的自动化选项

技术要求

要完成本章,你只需要安装 Python,以及基本的数据处理和机器学习库,如numpypandasmatplotlibscikit-learn。你将在第二章**深入 TPOT中学习如何安装和配置这些库,但让我们保持简单。这些库在 Anaconda 的任何发行版中都是预安装的,所以你不必担心。如果你使用的是原始 Python 而不是 Anaconda,从终端执行此行将安装所需的一切:

> pip install numpy pandas matplotlib scikit-learn

请记住,始终在虚拟环境中安装库是一个好的实践,你很快就会学会如何做。

本章的代码可以在此处下载:

github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT/tree/main/Chapter01

回顾机器学习的历史

25 年多前(1994 年),在《今日秀》的一期节目中提出了一个问题——“互联网究竟是什么?”很难想象,在几十年前,普通大众很难定义互联网是什么以及它是如何工作的。他们不知道,仅仅四分之一世纪后,我们就会有智能系统自我管理,并服务于大众。

机器学习的概念早在 1949 年由唐纳德·赫布提出。他提出了关于神经元兴奋和神经元之间通信的理论(*《机器学习简史》- DATAVERSITY,福特,K.,2019 年 3 月 26 日)。他是第一个提出人工神经元、它们的激活以及通过权重建立的关系的概念的人。

在 20 世纪 50 年代,亚瑟·萨缪尔开发了一个用于玩跳棋的计算机程序。当时的内存相当有限,所以他设计了一个评分函数,试图根据棋盘上的位置来衡量每位玩家的获胜概率。程序使用 MinMax 策略选择其下一步行动,这最终演变成了 MinMax 算法(*《机器学习简史 – DATAVERSITY,》Foote, K.;2019 年 3 月 26 日)。萨缪尔也是第一个提出机器学习这个术语的人。

弗兰克·罗森布拉特决定将赫布的人工脑细胞模型与亚瑟·萨缪尔的成果结合起来,创建了一个感知器。1957 年,感知器被计划为一个机器,这导致了Mark 1 感知器机器的建造,该机器设计用于图像分类。

这个想法至少看起来很有希望,但机器无法识别有用的视觉模式,这导致了进一步研究的中断——这个时期被称为第一次人工智能冬天。直到 20 世纪 90 年代,感知器和神经网络模型并没有什么进展。

前几段告诉我们关于 20 世纪末机器学习和深度学习状态的足够信息。一些个人在神经网络方面取得了巨大的进步,而普通大众甚至难以理解互联网是什么。

要使机器学习在现实世界中变得有用,科学家和研究人员需要两样东西:

  • 数据

  • 计算能力

第一个定义由于互联网的兴起而迅速变得更容易获得。第二个定义则缓慢地进入了一个指数增长阶段——无论是在 CPU 性能还是在存储容量方面。

然而,1990 年代末和 2000 年代初的机器学习状态与今天的情况相去甚远。今天的硬件导致了机器学习在生产和应用中的显著增加。很难想象一个没有 Netflix 推荐电影或 Google 不自动从常规电子邮件中过滤垃圾邮件的世界。

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

什么是机器学习?

现在有很多关于机器学习的定义,有的更加正式,有的则不那么正式。以下是一些值得提到的定义:

  • 机器学习是人工智能AI)的一个应用,它为系统提供了自动从经验中学习和改进的能力,而不需要明确编程(*《什么是机器学习?一个定义 – Expert System,》Expert System 团队;2020 年 5 月 6 日)。

  • 机器学习是一个概念,即计算机程序可以在没有人类干预的情况下学习和适应新的数据(*《机器学习 – Investopedia,》Frankenfield, J.;2020 年 8 月 31 日)。

  • 机器学习是计算机科学的一个领域,旨在教会计算机如何在没有明确编程的情况下学习和行动(*《机器学习 – DeepAI,》Deep AI 团队;2020 年 5 月 17 日)。

尽管这些定义的表达方式不同,但它们传达了相同的信息。机器学习的目标是开发一个系统或算法,能够在没有人类干预的情况下从数据中学习。

数据科学家的目标不是指导算法如何学习,而是向算法提供足够大且准备好的数据集,并简要说明数据集中变量之间的关系。例如,假设目标是创建一个能够预测房价的模型。在这种情况下,数据集应提供大量历史价格观察结果,通过诸如位置、大小、房间数量、年龄、是否有阳台或车库等变量来衡量。

机器学习算法将决定哪些特征是重要的,哪些不是,因此,哪些特征具有显著的预测能力。前一段的例子解释了使用监督机器学习方法解决的回归问题。我们很快就会深入探讨这两个概念,所以如果你不太理解,不要担心。

此外,我们可能还想构建一个模型,能够以相当高的置信度预测客户是否可能流失(终止合同)。有用的特征可能包括客户正在使用的服务列表、他们使用服务的时间长短、之前的付款是否按时支付等。这是另一个监督机器学习问题的例子,但目标变量(流失)是分类的(是或否),而不是连续的,就像上一个例子中那样。我们称这类问题为分类机器学习问题

机器学习不仅限于回归和分类。它还应用于许多其他领域,如聚类和降维。这些属于无监督机器学习技术的范畴。这些主题将不会在本章中讨论。

但首先,让我们回答关于机器学习模型可用性的问题,并讨论谁使用这些模型以及在什么情况下使用。

在哪些行业中,公司正在使用机器学习?

用一句话来说——无处不在。但你必须继续阅读,才能获得完整的画面。在过去十年或二十年中,机器学习几乎被应用到每个行业中。主要原因在于硬件的进步。此外,机器学习对更广泛的公众来说变得更容易使用和理解。

列举所有使用机器学习的行业及其解决的问题是不可能的。更简单的是列出那些不能从机器学习中受益的行业,因为这样的行业要少得多。

在本节中,我们将仅关注更为人熟知的行业。

下面是一个列表和解释,介绍了机器学习最常用的十个用例,从行业角度和一般概述来看:

  • 金融行业:机器学习在金融领域越来越受欢迎。银行和金融机构可以利用它做出更明智的决策。借助机器学习,银行可以检测出最有可能无法偿还贷款的客户。此外,银行可以使用机器学习方法来跟踪和理解其客户的消费模式。这可以导致为双方都满意地创造更多个性化的服务。机器学习还可以通过某些客户账户上的意外行为来检测异常和欺诈。

  • 医疗行业:医学的最近进步至少部分得益于机器学习的进步。可以根据这些预测方法在疾病的早期阶段检测疾病,基于此,医学专家可以制定个性化的治疗和康复计划。例如,计算机视觉技术如图像分类和目标检测可以用于对肺图像进行分类。这些技术也可以用于根据单一图像或一系列图像检测肿瘤的存在。

  • 图像识别:这可能是机器学习应用最广泛的应用,因为它可以应用于任何行业。你可以从简单的猫狗图像分类到对非洲濒危动物皮肤状况的分类。图像识别还可以用于检测图像中是否存在感兴趣的对象。例如,在“哪里有 Waldo?”游戏中自动检测 Waldo 的逻辑与自动驾驶汽车中检测行人的算法大致相同。

  • 语音识别:这是一个既令人兴奋又充满希望的新兴领域。基本思想是,一个算法可以自动识别音频剪辑中的 spoken words,并将其转换为文本文件。一些更知名的应用包括设备控制(通过语音命令控制空调)、语音拨号(仅通过你的声音自动识别要拨打的联系人)和互联网搜索(用你的声音浏览网页)。这些只是立即想到的几个例子。自动语音识别软件的开发具有挑战性。并非所有语言都受到支持,许多非母语者在说外语时都有口音,这可能会让机器学习算法难以识别。

  • 自然语言处理(NLP):私营部门的公司可以从 NLP 中获得巨大的好处。例如,如果客户留下的在线评论太多而无法手动分类,公司可以使用 NLP 来分析这些评论的情感。此外,公司可以在网页上创建聊天机器人,它们可以立即与用户开始对话,从而带来更多潜在的销售。更高级的例子是,NLP 可以用来编写长文档的摘要,甚至可以用来分割和分析蛋白质序列。

  • 推荐系统:截至 2020 年底,很难想象一个谷歌不根据你的过去行为定制搜索结果的世界,亚马逊不自动推荐类似产品,Netflix 不根据你过去的观看推荐电影和电视剧,以及 Spotify 不推荐那些可能逃过你注意力的音乐的世界。这只是几个例子,但并不难认识到推荐系统的重要性。

  • 垃圾邮件检测:就像很难想象一个搜索结果不符合你喜好的世界一样,也很难想象一个不自动过滤掉关于现在或永不折扣的吸尘器信息的电子邮件服务。我们每天都被信息轰炸,自动垃圾邮件检测算法可以帮助我们关注重要的事情。

  • 自动化交易:即使股市移动得太快,没有自动化手段也无法完全捕捉到正在发生的事情。开发交易机器人并不容易,但机器学习可以帮助你根据大量的历史数据选择最佳的买卖时机。如果完全自动化,你可以在海滩上啜饮玛格丽塔酒的同时观察你的钱如何创造更多的钱。这可能对一些人来说听起来有些牵强,但有了稳健的模型和大量的领域知识,我看不到为什么不行。

  • 异常检测:让我们回到我们的银行业例。银行可以使用异常检测算法来处理各种用例,例如标记可疑交易和活动。最近,我一直在使用异常检测算法来检测网络流量中的可疑行为,目的是自动检测网络攻击和恶意软件。如果数据格式正确,这是一种适用于任何行业的另一种技术。

  • 社交网络:Facebook 有多少次推荐你可能认识的人?或者 YouTube 推荐了你刚刚思考的主题的视频?不,他们不是在读取你的思想,但他们知道你的过去行为和决定,并且可以相当自信地预测你的下一步。

这些只是机器学习能做什么的几个例子——当然不是详尽的列表。你现在已经熟悉了机器学习简史,并知道机器学习可以应用于广泛的任务。

下一个部分将简要回顾监督机器学习技术,例如回归和分类。

监督学习

大多数实际机器学习问题都是通过监督学习算法解决的。监督学习指的是你有一个输入变量(一个预测因子),通常用 X 表示,以及一个输出变量(你试图预测的内容),通常用 y 表示。

特征(X)用大写字母表示,而目标变量(y)没有,这是有原因的。从数学的角度来看,X 表示特征矩阵,而矩阵通常用大写字母表示。另一方面,y 是一个向量,通常用小写字母表示向量。

监督机器学习算法的目标是学习一个可以将任何输入转换为输出的函数。监督学习算法最一般的数学表示是通过以下公式表示的:

![图 1.1 – 一般监督学习公式图片

图 1.1 – 一般监督学习公式

我们必须应用两种修正之一来使这个公式可接受。第一种修正是将 y 替换为 y-hat,因为 y 通常表示真实值,而 y-hat 表示预测。第二种我们可以做的修正是添加误差项,因为只有这样我们才能在另一侧得到正确的 y 值。误差项表示不可减少的误差——那种无法通过进一步训练减少的误差。

这是第一个修正后的公式看起来是这样的:

![图 1.2 – 修正后的监督学习公式(v1)图片

图 1.2 – 修正后的监督学习公式(v1)

下面是第二个公式:

![图 1.3 – 修正后的监督学习公式(v2)图片

图 1.3 – 修正后的监督学习公式(v2)

更常见的是看到第二个公式,但不要被任何格式所迷惑——这些公式通常表示相同的东西。

监督机器学习被称为“监督学习”,因为我们已经拥有了可用的标记数据。你可能已经因为特征和目标讨论而选择了它。这意味着我们已经有正确的答案了,因此,我们知道哪些 X 的组合会产生 y 的相应值。

最终目标是根据可用的数据做出最佳泛化。我们希望产生最无偏的模型,能够泛化到新的、未见过的数据。过拟合、欠拟合和偏差-方差权衡的概念对于产生这样的模型很重要,但它们不在这个书的范围之内。

正如我们之前提到的,监督学习问题分为两大类:

  • 回归:目标变量在本质上连续,例如美元房价、华氏温度、磅重、英寸高度等。

  • 分类:目标变量是一个类别,可以是二元的(真/假、阳性/阴性、疾病/无疾病),也可以是多类的(无症状/轻度症状/重度症状、学校成绩等)。

回归和分类将在以下章节中探讨。

回归

如前几节简要讨论的,回归指的是目标变量连续的现象。目标变量可能代表价格、重量或高度,仅举几例。

最常见的回归类型是线性回归,这是一个假设变量之间存在线性关系的模型。线性回归进一步分为简单线性回归(只有一个特征)和多元线性回归(多个特征)。

重要提示

请记住,线性回归不是唯一类型的回归。你可以使用决策树、随机森林、支持向量机、梯度提升和人工神经网络等算法执行回归任务,但相同的概念仍然适用。

为了快速回顾回归概念,我们将声明一个简单的 pandas.DataFrame 对象,包含两列 – Living areaPrice。目标是仅根据居住空间预测价格。我们在这里使用简单的线性回归模型,因为它使数据可视化过程更简单,最终结果是使回归概念更容易理解:

  1. 以下是一个数据集 – 两个列都包含任意和虚构的值:

    import pandas as pd 
    df = pd.DataFrame({
        'LivingArea': [300, 356, 501, 407, 950, 782, 
                       664, 456, 673, 821, 1024, 900, 
                       512, 551, 510, 625, 718, 850],
        'Price': [100, 120, 180, 152, 320, 260, 
                  210, 150, 245, 300, 390, 305, 
                  175, 185, 160, 224, 280, 299]
    })
    
  2. 为了可视化这些数据点,我们将使用 matplotlib 库。默认情况下,这个库看起来并不吸引人,所以通过 matplotlib.rcParams 包进行了一些调整:

    import matplotlib.pyplot as plt 
    from matplotlib import rcParams
    rcParams['figure.figsize'] = 14, 8
    rcParams['axes.spines.top'] = False
    rcParams['axes.spines.right'] = False
    
  3. 以下选项使图表默认更大,并移除了顶部和右侧的边框(脊)。以下代码片段将我们的数据集可视化为二维散点图:

    plt.scatter(df['LivingArea'], df['Price'], color='#7e7e7e', s=200)
    plt.title('Living area vs. Price (000 USD)', size=20)
    plt.xlabel('Living area', size=14)
    plt.ylabel('Price (000 USD)', size=14)
    plt.show()
    

    上一段代码生成了以下图表:

    ![图 1.4 – 回归 – 居住面积与价格(000 美元)的散点图 居住面积与价格(000 美元)的散点图

    图 1.4 – 回归 – 居住面积与价格(000 美元)的散点图

  4. 使用 scikit-learn 库训练线性回归模型是最容易的。该库包含大量不同的算法和技术,我们可以将其应用于我们的数据。sklearn-learn.linear_model 模块包含 LinearRegression 类。我们将使用它来在整个数据集上训练模型,并对其进行预测。在生产环境中通常不会这样做,但在这里对于进一步理解模型的工作原理是必要的:

    from sklearn.linear_model import LinearRegression
    model = LinearRegression()
    model.fit(df[['LivingArea']], df[['Price']])
    preds = model.predict(df[['LivingArea']])
    df['Predicted'] = preds
    
  5. 我们将预测值分配为另一个数据集列,只是为了使数据可视化更简单。再次,我们可以创建一个包含整个数据集的散点图。这次,我们将添加一条代表最佳拟合线的线 – 这条线上的误差最小:

    plt.scatter(df['LivingArea'], df['Price'], color='#7e7e7e', s=200, label='Data points')
    plt.plot(df['LivingArea'], df['Predicted'], color='#040404', label='Best fit line')
    plt.title('Living area vs. Price (000 USD)', size=20)
    plt.xlabel('Living area', size=14)
    plt.ylabel('Price (000 USD)', size=14)
    plt.legend()
    plt.show()
    

    上一段代码生成了以下图表:

    ![图 1.5 – 回归 – 居住面积与价格(000 美元)的散点图及最佳拟合线 居住面积与价格(000 美元)的散点图

    图 1.5 – 回归 – 居住面积与价格(000 美元)的散点图及最佳拟合线

  6. 如我们所见,简单的线性回归模型几乎完美地捕捉了我们的数据集。这并不令人惊讶,因为数据集是为了这个目的而创建的。新的预测将沿着最佳拟合线进行。例如,如果我们对预测拥有 1,000 平方米居住空间的房屋价格感兴趣,模型将做出略高于$350K 的预测。在代码中的实现很简单:

    model.predict([[1000]])
    >>> array([[356.18038708]])
    
  7. 此外,如果您想评估这个简单的线性回归模型,R2RMSE等指标是一个不错的选择。R2 衡量拟合优度,因此它告诉我们我们的模型捕捉了多少方差(范围从 0 到 1)。它更正式地被称为确定系数。RMSE 衡量模型在平均意义上的错误程度,以感兴趣的单元为单位。例如,RMSE 值为 10 表示我们的模型平均错误为$10K,无论是正方向还是负方向。

    R2 分数和 RMSE 的计算方法如下:

    import numpy as np
    from sklearn.metrics import r2_score, mean_squared_error
    rmse = lambda y, ypred: np.sqrt(mean_squared_error(y, ypred))
    model_r2 = r2_score(df['Price'], df['Predicted'])
    model_rmse = rmse(df['Price'], df['Predicted'])
    print(f'R2 score: {model_r2:.2f}')
    print(f'RMSE: {model_rmse:.2f}')
    >>> R2 score: 0.97
    >>> RMSE: 13.88
    

总结来说,我们构建了一个简单但准确的模型。不要期望现实世界中的数据表现得如此完美,也不要期望大多数时候都能构建如此准确的模型。模型选择和调整的过程既繁琐又容易出错,这就是自动化库如TPOT发挥作用的地方。

我们将在下一节中再次介绍分类复习,同样是在相当简单的例子中。

分类

在机器学习中,分类指的是目标变量是分类类型的问题。我们可以通过将目标变量转换为类别,如已售/未售,将回归部分的例子转换为分类问题。

简而言之,分类算法帮助我们处理各种场景,例如预测客户流失、肿瘤是否恶性、某人是否患有特定疾病等。您应该明白了。

分类任务可以进一步分为二元分类任务和多类分类任务。在本节中,我们将简要探讨二元分类任务。最基本的分类算法是逻辑回归,我们将在本节中使用它来构建一个简单的分类器。

注意

请记住,您在进行分类任务时不仅限于逻辑回归。相反——将逻辑回归模型作为基线使用是一种良好的实践,并在生产中使用更复杂的算法。更复杂的算法包括决策树、随机森林、梯度提升和人工神经网络。

在这个例子中,数据是完全虚构和任意的:

  1. 我们有两个列——第一列表示某种测量(称为Radius),第二列表示分类(要么是 0 要么是 1)。数据集是用以下 Python 代码构建的:

    import numpy as np
    import pandas as pd
    df = pd.DataFrame({
        'Radius': [0.3, 0.1, 1.7, 0.4, 1.9, 2.1, 0.25, 
                   0.4, 2.0, 1.5, 0.6, 0.5, 1.8, 0.25],
        'Class': [0, 0, 1, 0, 1, 1, 0, 
                  0, 1, 1, 0, 0, 1, 0]
    })
    
  2. 我们将再次使用matplotlib库来进行可视化。以下是导入它并使其更具视觉吸引力的方法:

    import matplotlib.pyplot as plt 
    from matplotlib import rcParams
    rcParams['figure.figsize'] = 14, 8
    rcParams['axes.spines.top'] = False
    rcParams['axes.spines.right'] = False
    
  3. 我们可以重用之前回归示例中的相同逻辑来制作可视化。然而,这一次,我们不会看到类似于线的紧密数据。相反,我们会看到数据点被分成两组。在左下角是Class属性为 0 的数据点,而在右边的属性为 1:

    plt.scatter(df['Radius'], df['Class'], color='#7e7e7e', s=200)
    plt.title('Radius classification', size=20)
    plt.xlabel('Radius (cm)', size=14)
    plt.ylabel('Class', size=14)
    plt.show()
    

    下面的图表是前面代码的输出:

    ![图 1.6 – 分类 – 测量值与类别之间的散点图 图片

    ![图 1.6 – 分类 – 测量值与类别之间的散点图

    分类模型的目标不是产生最佳拟合线,而是要在类别之间画出最佳可能的分离。

  4. 逻辑回归模型可在sklearn.linear_model包中找到。我们将使用它在整个数据集上训练模型,然后在整个数据集上进行预测。再次强调,这不是我们将在本书后面继续做的事情,但了解模型内部工作原理在这个阶段是至关重要的:

    from sklearn.linear_model import LogisticRegression
    model = LogisticRegression()
    model.fit(df[['Radius']], df['Class'])
    preds = model.predict(df[['Radius']])
    df['Predicted'] = preds
    
  5. 我们现在可以使用这个模型对任意数量的 X 值进行预测,范围从整个数据集中的最小值到最大值。通过np.linspace方法获得均匀分布的数字范围。它需要三个参数 – startstop和元素数量。我们将元素数量设置为1000

  6. 然后,我们可以画一条线来表示 X 值每个值的概率。通过这样做,我们可以可视化模型的决策边界:

    xs = np.linspace(0, df['Radius'].max() + 0.1, 1000)
    ys = [model.predict([[x]]) for x in xs]
    plt.scatter(df['Radius'], df['Class'], color='#7e7e7e', s=200, label='Data points')
    plt.plot(xs, ys, color='#040404', label='Decision boundary')
    plt.title('Radius classification', size=20)
    plt.xlabel('Radius (cm)', size=14)
    plt.ylabel('Class', size=14)
    plt.legend()
    plt.show()
    

    上述代码产生了以下可视化:

    ![图 1.7 – 分类 – 测量值与类别之间的散点图和决策边界 图片

    图 1.7 – 分类 – 测量值与类别之间的散点图和决策边界

    我们的分类模型基本上是一个阶梯函数,对于这个简单问题来说是可以理解的。不需要更复杂的东西来正确分类我们数据集中的每个实例。但这并不总是如此,稍后我们会详细讨论。

  7. 混淆矩阵是评估分类模型的最佳方法之一。我们的负类是 0,正类是 1。混淆矩阵只是一个显示以下内容的方阵:

    • 真阴性:左上角的数字。这些是具有类别 0 的实例,并且模型预测为 0。

    • 假阴性:左下角的数字。这些是具有类别 0 的实例,但模型预测为 1。

    • 假阳性:右上角的数字。这些是具有类别 1 的实例,但模型预测为 0。

    • 真阳性:右下角的数字。这些是具有类别 1 的实例,并且模型预测为 1。

      需要多次阅读前面的列表,直到完全理解这个概念。混淆矩阵是分类评估中的一个基本概念,本书后面的章节假设你知道如何解释它。

  8. 混淆矩阵可以在sklearn.metrics包中找到。以下是导入它并获得结果的方法:

    from sklearn.metrics import confusion_matrix
    confusion_matrix(df['Class'], df['Predicted'])
    

    这里是结果:

![图 1.8 – 分类 – 使用混淆矩阵进行评估

![img/B16954_01_008.jpg]

图 1.8 – 分类 – 使用混淆矩阵进行评估

前面的图显示,我们的模型能够正确地分类每一个实例。一般来说,如果从左下角到右上角的对角线元素都是零,这意味着模型是 100%准确的。

混淆矩阵的解释结束了我们对监督机器学习方法的简要复习。接下来,我们将深入探讨自动化的概念,并讨论为什么我们需要它在机器学习中。

回顾自动化

本节简要讨论了自动化的概念,为什么我们需要它,以及它是如何应用于机器学习的。我们还将回答一个古老的疑问:机器学习是否会取代人类的工作,以及自动化在这方面所起的作用。

自动化在现代世界中扮演着巨大的角色,在过去几个世纪中,它使我们能够完全从危险和重复性工作中消除人的因素。这为就业市场开辟了一片新的天地,那里的工作通常基于一些无法自动化的东西,至少在这个时间点上是这样。

但首先,我们必须理解什么是自动化。

什么是自动化?

现在有许许多多语法上不同的定义,但它们都共享同一个基本思想。以下一个以最简单的术语表达了这一思想:

自动化是一个广泛的概念,可以涵盖许多技术领域,在这些领域中,人的输入被最小化(什么是自动化? – IBM, IBM 团队;2020 年 2 月 28 日)。

定义的核心部分是最小化人的输入。一个自动化过程完全或几乎完全由机器管理。直到几年前,机器是自动化枯燥、常规任务的一个很好的方式,而将创造性的事情留给人类。正如你可能猜到的,机器在创造性任务上并不那么出色。也就是说,直到最近它们是这样的。

机器学习为我们提供了一种机制,不仅能够自动化计算、电子表格管理和费用跟踪,还能进行更多认知任务,如决策。这个领域每天都在发展,很难说我们何时可以期待机器接管一些更具创造性的工作。

机器学习中的自动化概念将在后面讨论,但重要的是要记住,机器学习可以将自动化提升到一个全新的水平。并非所有的自动化形式都是平等的,通常接受的自动化划分是基于复杂性的四个级别:

  • 基本自动化:对最简单任务的自动化。机器人流程自动化(RPA)是完美的例子,因为它的目标是使用软件机器人来自动化重复性任务。这个自动化类别的最终目标是完全从等式中消除人为因素,从而实现快速执行重复性任务且无错误。

  • 流程自动化:这将在整个业务流程中应用和实施基本自动化技术。最终目标是完全自动化一项业务活动,只让人类进行最终批准。

  • 集成自动化:这使用人类定义的规则来模拟在任务完成中的人类行为。最终目标是最大限度地减少在更复杂的业务任务中的人类干预。

  • 人工智能自动化:自动化最复杂的形式。目标是拥有能够根据先前情况和在这些情况下做出的决策进行学习和做出决定的机器。

你现在知道了什么是自动化,接下来我们将讨论为什么它是 21 世纪必须的。

为什么需要自动化?

公司和客户都可以从自动化中受益。自动化可以提高资源分配和管理,并使业务扩展过程更容易。由于自动化,公司可以提供更可靠和一致的服务,这导致用户体验更加一致。最终结果是,如果服务质量不一致,客户更有可能购买并花费更多。

从长远来看,自动化简化并减少了人类活动,降低了成本。此外,任何自动化流程都可能比由人类执行的同种流程表现得更好。机器不会感到疲倦,不会有过糟糕的日子,也不需要薪水。

以下列表显示了自动化的一些最重要的原因:

  • 节省时间:自动化通过让机器代替人类完成日常例行任务来简化日常流程。结果,人类可以从一开始就专注于更具创造性的任务。

  • 降低成本:自动化应被视为一项长期投资。当然,它确实有一些启动成本,但如果正确实施,这些成本会很快得到弥补。

  • 准确性和一致性:如前所述,人类容易出错、有糟糕的日子和缺乏一致性。机器则不是这样。

  • 工作流程改进:由于自动化,可以花更多的时间在重要任务上,例如为客户提供个别帮助。如果员工的班次不是由重复性和例行任务组成,他们往往会更快乐,并取得更好的成果。

困难的问题不是“你是否要自动化”,而是“你何时要自动化”。关于这个话题有很多不同的观点,并没有一个绝对的对或错。决定何时自动化取决于你拥有的预算和机会成本(如果时间不是问题,你将能够做出的决定/投资)。

自动化你所擅长的任何事情,专注于需要改进的领域,这是大多数公司的一般规则。即使作为一个个人,你每天或每周都会做一些可以用普通语言描述的事情。如果某件事情可以一步一步地描述,那么它就可以被自动化。

但自动化这个概念是如何应用到机器学习中的呢?机器学习和自动化是同义词吗?这就是我们接下来要讨论的内容。

机器学习和自动化是同一件事吗?

嗯,不是的。但机器学习可以将自动化提升到一个全新的水平。让我们回顾一下之前讨论过的四个自动化级别。只有最后一个使用了机器学习,这是自动化最先进的形式。

让我们以我们日常生活中的一个活动作为一个过程来考虑。如果你确切地知道这个过程将如何开始和结束,以及中间会发生什么,以及发生的顺序,那么这个流程可以在没有机器学习的情况下被自动化。

这里有一个例子。在过去的几个月里,你一直在监控你想要搬去的地方的房地产价格。每天早上你给自己冲一杯咖啡,坐在电脑前,然后访问一个房地产网站。你筛选结果,只查看过去 24 小时内发布的广告,然后将数据,如位置、单价、房间数量等,输入到电子表格中。

这个过程需要你一天大约一个小时的时间,一个月下来就是 30 个小时。这相当多。在 30 个小时里,你很容易就能读一本书或者参加一个在线课程,以进一步发展你在其他领域的技能。本段描述的过程可以很容易地自动化,而不需要机器学习。

让我们再看另一个例子。你每天花几个小时在股市上,决定买什么卖什么。这个过程与上一个不同,因为它涉及到某种决策。问题是,有了所有可用的在线数据集,一个有技能的个人可以使用机器学习的方法来自动化买卖决策过程。

这是一种包含机器学习的自动化形式,但不是的,机器学习和自动化不是同义词。两者都可以在没有对方的情况下工作。

以下几节将详细讨论自动化在机器学习中的作用(而不是相反),并回答我们试图自动化什么,以及如何在当今时代实现它。

将自动化应用于机器学习

我们已经介绍了自动化的概念和多种自动化类型,但自动化和机器学习之间有什么联系呢?我们究竟在机器学习中试图自动化什么?

这正是本节旨在阐明的内容。在本节结束时,你将了解术语机器学习自动化自动化机器学习之间的区别。这两个术语乍听起来可能很相似,但在现实中却大相径庭。

我们试图自动化什么?

让我们明确一点——机器学习过程自动化机器学习业务流程自动化无关。在前者中,我们试图自动化机器学习本身,即自动化选择最佳模型和最佳超参数的过程。后者指的是利用机器学习自动化业务流程;例如,根据历史数据决定何时买入或卖出股票的决策系统。

记住这个区别至关重要。本书的主要重点是展示如何使用自动化库来自动化机器学习的过程。通过这样做,无论数据集如何,你都将遵循完全相同的方法,并且总是得到最佳可能的模型。

选择合适的机器学习算法并不是一件容易的事情。只需看看下面的图表:

![图 1.9 – scikit-learn 中的算法(来源:Scikit-learn:Python 中的机器学习,Pedregosa 等人,JMLR 12,第 2825-2830 页,2011)]

图片

图 1.9 – scikit-learn 中的算法(来源:Scikit-learn:Python 中的机器学习,Pedregosa 等人,JMLR 12,第 2825-2830 页,2011)

正如你所见,需要做出多个决策来选择合适的算法。此外,每个算法都有自己的超参数集(由工程师指定的参数)。更糟糕的是,其中一些超参数是连续的,所以当你把所有这些都加起来时,你作为工程师应该测试的潜在超参数组合可能有数百万种。

每个超参数组合都需要训练和评估一个全新的模型。例如,网格搜索这样的概念可以帮助你避免编写数十个嵌套循环,但这远非最佳解决方案。

现代的机器学习工程师并不将时间和精力花在模型训练和优化上——相反,他们专注于提高数据质量和可用性。超参数调整可以带来额外的 2%的准确率提升,但能够决定你的项目成功与否的是数据的质量

我们将在下一节中更深入地探讨超参数,并展示为什么手动寻找最佳超参数并不是一个好主意。

参数过多的问题

让我们来看看最流行的机器学习算法之一——XGBoost的可用的某些超参数。以下列表显示了通用的一些:

  • booster

  • verbosity

  • validate_parameters

  • nthread

  • disable_default_eval_metric

  • num_pbuffer

  • num_feature

这并不多,其中一些超参数是由算法自动设置的。问题在于进一步的筛选。例如,如果你将gbtree作为booster参数的值,你可以立即调整以下值:

  • eta

  • gamma

  • max_depth

  • min_child_weight

  • max_delta_step

  • subsample

  • sampling_method

  • colsample_bytree

  • colsample_bylevel

  • colsample_bynode

  • lambda

  • alpha

  • tree_method

  • sketch_eps

  • scale_pos_weight

  • updater

  • refresher_leaf

  • process_type

  • grow_policy

  • max_leaves

  • max_bin

  • predictor

  • num_parallel_tree

  • monotone_constraints

  • interaction_constraints

这已经很多了!如前所述,一些超参数接受连续值,这极大地增加了组合的总数。这里是最后的甜点——这些只是单个模型的超参数。不同的模型有不同的超参数,这使得调整过程更加耗时。

简而言之,模型选择和超参数调整不是你应该手动做的事情。有更多重要的任务需要你投入精力。即使没有其他事情要做,我宁愿去吃午饭也不愿意手动调整任何一天。

AutoML 使我们能够做到这一点,因此我们将在下一节简要探讨它。

什么是 AutoML?

AutoML代表自动化机器学习,其主要目标是减少或完全消除数据科学家在构建机器学习模型中的作用。一开始听到这句话可能有些刺耳。我知道你在想什么。但不是——AutoML 不能取代数据科学家和其他数据专业人士。

在最佳情况下,AutoML 技术使其他软件工程师能够在他们的应用程序中利用机器学习的力量,而不需要具备坚实的 ML 背景。这种最佳情况只有在数据得到充分收集和准备的情况下才可能实现——这不是后端开发人员的专长。

对于非数据科学家来说,事情变得更加困难,机器学习过程通常需要大量的特征工程。这一步可以跳过,但往往会导致模型表现不佳。

总之,AutoML 不会取代数据科学家,恰恰相反——它在这里是为了让数据科学家的生活更轻松。AutoML 只是完全自动化模型选择和调整。

有些 AutoML 服务宣称自己可以完全自动化数据准备和特征工程工作,但这只是通过组合各种特征并创建出大多数情况下不可解释的东西。机器不知道变量之间的真实关系。这是数据科学家的工作去发现。

自动化选项

AutoML 并不是一个全新的概念。这个想法和一些实现已经存在多年,并且总体上获得了积极的反馈。然而,由于缺乏理解,一些组织未能实施和充分利用 AutoML 解决方案。

AutoML 并不能做所有事情——仍然需要有人收集数据、存储和准备数据。这不是一个小任务,通常需要大量的领域知识。只有在那时,自动化的解决方案才能充分发挥其潜力。

本节探讨了实现 AutoML 解决方案的一些选项。我们将比较一个用 Python 编写的基于代码的工具,以及一个作为浏览器应用程序提供的工具,这意味着不需要编写代码。我们将首先从基于代码的工具开始。

PyCaret

PyCaret已被广泛用于尽可能少地编写代码来制作生产就绪的机器学习模型。这是一个完全免费的解决方案,可以轻松地训练、可视化和解释机器学习模型。

它内置了对回归和分类模型的支持,并以交互式方式显示用于任务的模型以及哪个模型产生了最佳结果。数据科学家需要决定用于任务的模型。训练和优化都简单得就像一个函数调用一样。

该库还提供了一个选项,使用博弈论算法(如SHAPShapely Additive Explanations))来解释机器学习模型,只需一个函数调用即可。

PyCaret 仍然需要一些人工交互。然而,通常情况下,模型的初始化和训练过程必须由用户明确选择,这打破了完全自动化的解决方案的想法。

此外,PyCaret 在处理大型数据集时运行和优化可能会比较慢。接下来,让我们看看一个无需编写代码的 AutoML 解决方案。

ObviouslyAI

我们并非所有人都知道如何开发机器学习模型,甚至不知道如何编写代码。这就是拖放解决方案发挥作用的地方。ObviouslyAI无疑是其中最好的之一,而且使用起来也很简单。

这种服务允许在浏览器中进行模型训练和评估,甚至可以解释模型做出决策背后的原因。对于机器学习不是核心业务的公司来说,这是一个不费吹灰之力的选择,因为它很容易开始,而且几乎不需要像整个数据科学团队那样花费大量费用。

对于这类服务的一个大问题是定价。总是包含一个免费计划,但在这个特定案例中,它限制在少于 50,000 行的数据集。这完全适用于偶尔的测试,但对于大多数生产用例来说是一个决定性因素。

第二个决定性因素是实际的自动化。你不能轻易地自动化鼠标点击和数据集加载。这项服务完全自动化了机器学习过程,但你仍然需要做一些手动工作。

TPOT

TPOT这个缩写代表基于树的管道优化工具。它是一个 Python 库,旨在以自动化的方式处理机器学习任务。

这里是官方文档中的一句话:

将 TPOT 视为你的数据科学助手。TPOT 是一个 Python 自动机器学习工具,它使用遗传编程优化机器学习流程(TPOT 文档页面,TPOT 团队;2019 年 11 月 5 日)。

遗传编程这个术语将在后面的章节中进一步讨论。现在,只需知道它基于进化算法——一种用于发现人类不知道如何解决的问题的特殊类型的算法。

在某种程度上,TPOT 确实是你的数据科学助手。你可以用它来自动化数据科学项目中所有无聊的任务。术语“无聊”是主观的,但在这本书中,我们用它来指代手动选择和调整机器学习模型的任务(即:花费数天等待模型调整)。

TPOT 无法自动化数据收集和清洗的过程,原因很明显——机器无法读取你的想法。然而,它可以在准备良好的数据集上执行机器学习任务,比大多数数据科学家做得更好。

以下章节将详细讨论该库。

摘要

你在本节中学到了很多——或者至少有一个简短的回顾。你现在对机器学习、回归、分类和自动化的概念有了新的认识。所有这些对于接下来的、更具挑战性的章节都是至关重要的。

下一个章节之后,我们将深入探讨代码,以便你能够全面掌握该库。从最基本的回归和分类自动化,到并行训练、神经网络和模型部署,都将被讨论。

在下一章中,我们将深入探讨 TPOT 库、其用例和其底层架构。我们将讨论 TPOT 背后的核心原则——遗传编程——以及它是如何用于解决回归和分类任务的。我们还将为 Windows、macOS 和 Linux 操作系统完全配置环境。

问答

  1. 用你自己的话定义“机器学习”这个术语。

  2. 用几句话解释监督学习。

  3. 回归和分类机器学习任务有什么区别?

  4. 列出三个机器学习的应用领域,并提供具体的例子。

  5. 你会如何描述自动化?

  6. 为什么在当今时代我们需要自动化?

  7. “具有机器学习的自动化”和“机器学习自动化”这两个术语有什么区别?

  8. “机器学习”和“自动化”这两个术语是同义词吗?解释你的答案。

  9. 解释手动机器学习中参数过多的问题。

  10. 定义并简要解释 AutoML。

进一步阅读

本章中引用的资料来源如下:

第二部分:TPOT – 实践分类和回归

本节深入探讨了 TPOT 是什么,它不是什么,以及它是如何用于处理机器学习中的自动化的,以及 TPOT 可以自动化的任务类型。本节还展示了如何安装 TPOT 以及一般的环境设置。

本节包含以下章节:

  • 第二章深入 TPOT

  • 第三章回归前的探索

  • 第四章分类前的探索

  • 第五章使用 TPOT 和 Dask 进行并行训练

第二章: 深入探索 TPOT

在本章中,你将了解 TPOT 库的理论方面及其底层架构的所有内容。例如架构和遗传编程GP)等主题对于全面掌握库的内部工作原理至关重要。

我们将探讨 TPOT 的使用案例,深入探讨解决各种机器学习问题的不同方法。你可以期待学习回归和分类任务自动化的基础知识。

我们还将介绍为独立 Python 安装和 Anaconda 发行版设置完整环境,并展示如何设置虚拟环境。

本章将涵盖以下主题:

  • 介绍 TPOT

  • TPOT 可以解决的问题类型

  • 安装 TPOT 和设置环境

技术要求

要完成本章,你只需要一台安装了 Python 的计算机。无论是独立版本还是 Anaconda 都行。我们将在本章末尾通过虚拟环境介绍两者的安装过程。

本章没有代码。

介绍 TPOT

TPOT,或基于树的流程优化工具,是一个开源库,使用 Python 编程语言以自动化的方式执行机器学习。在表面之下,它使用广为人知的scikit-learn机器学习库来执行数据准备、转换和机器学习。它还使用 GP 过程来发现给定数据集的最佳性能流程。GP 的概念将在后面的章节中介绍。

按照惯例,每次你需要一个自动化的机器学习流程时都应该使用 TPOT。数据科学是一个广泛的领域,像 TPOT 这样的库使你能够花更多的时间在数据收集和清洗上,因为其他所有事情都是自动完成的。

下图显示了典型的机器学习流程的外观:

![图 2.1 – 示例机器学习流程

![img/B16954_02_001.jpg]

图 2.1 – 示例机器学习流程

前面的图显示了机器学习过程中哪些部分可以自动化,哪些不能自动化。数据收集阶段(原始数据)对于任何机器学习项目都是至关重要的。在这个阶段,你收集的数据将作为机器学习模型的输入。如果输入数据不够好,或者数据量不足,机器学习算法就不能产生高质量的模型。

假设有足够的数据并且你可以访问它,下一个最显著的问题就是数据清洗。这一步不能完全自动化,至少不是完全自动化,这是显而易见的原因。每个数据集都是不同的,因此没有单一的数据清洗方法。缺失和格式错误的数据值是最常见且最耗时的类型,通常需要大量的领域知识才能成功解决。

一旦你有了相当数量的准备好的数据,TPOT 就可以发挥作用了。TPOT 使用遗传规划(GP)来寻找特定任务的最佳算法,因此无需手动选择和优化单个算法。自然选择中的达尔文过程启发了遗传算法,但关于这一点将在接下来的几节中详细说明。

TPOT 管道有许多参数,具体取决于你试图解决的问题类型(回归或分类)。所有参数将在本章后面讨论,但无论问题类型如何,以下是你应该知道的内容:

  • generations: 代表管道优化过程运行的迭代次数

  • population_size: 代表每一代 GP 种群中保留的个体数量

  • offspring_size: 代表每一代产生的后代数量

  • mutation_rate: 告诉 GP 算法每一代要对多少个管道应用随机变化

  • crossover_rate: 告诉 GP 算法每一代要繁殖多少个管道

  • cv: 用于评估管道的交叉验证技术

  • scoring: 用于评估给定管道质量的一个函数

一旦 TPOT 完成优化,它将返回最佳管道的 Python 代码,以便你可以自行进行模型评估和验证。以下图显示了 TPOT 管道的一个简化示例:

图 2.2 – 示例 TPOT 管道

img/B16954_02_002.jpg

图 2.2 – 示例 TPOT 管道

TPOT 库建立在 Python 知名的机器学习包scikit-learn之上。因此,TPOT 可以访问其所有类和方法。前面的图显示了主成分分析(PCA)多项式特征作为两种可能的特征预处理操作。TPOT 不仅限于这两种,还可以使用以下任何一种:

  • PCA

  • RandomizedPCA

  • PolynomialFeatures

  • Binarizer

  • StandardScaler

  • MinMaxScaler

  • MaxAbsScaler

  • RobustScaler

这些都是内置在scikit-learn中的类,用于以某种方式修改数据集并返回修改后的数据集。下一步涉及某种特征选择。这一步旨在仅选择具有良好预测能力的特征并丢弃其他特征。通过这样做,TPOT 正在降低机器学习问题的维度,这最终使得问题更容易解决。

前面的图在选择最佳特征步骤后面隐藏了这个抽象。为了执行这一步骤,TPOT 可以使用以下算法之一:

  • VarianceThreshold

  • SelectKBest

  • SelectPercentile

  • SelectFwe

  • RecursiveFeatureElimination

如你所见,TPOT 在模型训练方法上非常灵活。为了进一步了解表面之下发生的事情,我们需要简要介绍一些遗传规划(GP)。接下来的部分将做到这一点。

遗传规划的简要概述

GP 是一种进化算法,是机器学习的一个子集(遗传编程页面,GP 团队;2019 年 6 月 1 日)。进化算法用于寻找我们人类不知道如何直接解决的问题的解决方案。这些算法生成的解决方案在最坏的情况下与最佳人类解决方案相当,通常更好。

在机器学习中,GP 可以用于发现数据集中特征之间的关系(回归),以及将数据分组到类别中(分类)。在常规软件工程中,GP 通过代码合成、遗传改进、自动错误修复以及在开发游戏策略中(遗传编程页面,GP 团队;2019 年 6 月 1 日)得到应用。

GP 受到生物进化和其机制的启发。它使用基于随机变异、交叉、适应度函数和代的算法来解决之前描述的机器学习回归和分类任务。这些特性应该听起来很熟悉,因为我们已经在上一节中讨论过它们。

GP 的理念对于机器学习的发展至关重要,因为它基于 达尔文自然选择过程。在机器学习的术语中,这些过程用于生成最优解——模型和超参数。

遗传算法有三个特性:

  • 选择:由可能的解决方案的种群和适应度函数组成。每个适应度在每次迭代中都会被评估。

  • 交叉:选择最佳(最适应)的解决方案并执行交叉以创建新种群的过程。

  • 变异:从前一点中获取后代,并通过一些随机修改对其进行变异,直到获得最佳解决方案。

了解你正在处理的语言/库的基本知识和底层架构总是一个好主意。TPOT 用户友好且易于使用,因此它不需要我们了解 GP 和遗传算法的所有内容。因此,本章不会深入探讨这个主题。如果你对学习更多关于 GP 感兴趣,你可以在章节末尾找到有用的链接。

我们已经讨论了很多关于机器学习自动化、TPOT 和 GP 的优点。但是,有没有缺点?下一节将讨论其中的一些。

TPOT 限制

到目前为止,我们只讨论了 TPOT 库和机器学习过程自动化的优点。在这种情况下,优点大于缺点,但我们仍然应该讨论潜在的缺点。第一个是执行时间。它将根据数据集的大小和硬件规格而变化,但一般来说,需要花费很多时间才能完成——对于大型数据集可能是几个小时或几天,对于较小的数据集可能是几分钟。

理解这一点至关重要。使用默认的 TPOT 设置——100 代,100 个种群大小——TPOT 在完成之前将评估 10,000 个流程。这相当于进行了 10,000 次特征工程和机器学习模型的训练。正因为如此,TPOT 预期会运行得较慢。

如果你决定将 交叉验证 引入其中,事情会变得更加复杂。这个术语代表了一种过程,其中机器学习模型在 kk – 1 个子集上训练 k 次,并在一个单独的子集上进行评估。交叉验证的目的是获得模型性能的更准确表示。k 的选择是任意的,但在实践中,最常用的值是 10。

在实践中,交叉验证会使 TPOT 运行得更快。默认情况下,当使用交叉验证时,TPOT 将评估 100 代,100 个种群大小和 10 个交叉验证折。这导致在完成之前需要评估 100,000 个不同的流程。

为了解决这个问题,TPOT 引入了 max_time_mins 参数。默认情况下,它被设置为 None,但你可以明确地将它的值设置为任何整数。例如,指定 max_time_mins=10 将只给 TPOT 10 分钟的时间来优化流程。如果你想要最佳结果,这不是一个理想的选择,但在时间紧迫的情况下,它还是很有用的。

第二个缺点是,TPOT 有时可能会为同一数据集推荐不同的解决方案(流程)。当 TPOT 优化器运行时间较短时,这通常会成为问题。例如,如果你已经使用了 max_time_mins 参数来限制优化器的运行时间,每次得到一个略微不同的“最优”流程并不奇怪。

这并不是一个需要担心的理由,因为所有流程都应该在相同的时间框架内优于你手动完成的工作,但了解为什么会发生这种情况是至关重要的。有两个可能的原因:

  • TPOT 优化器没有收敛:这是最可能的情况。由于时间不足或数据集过于复杂,在给定的时间段内无法优化,TPOT 无法找到最优流程(或两者兼而有之)。

  • 存在多个“最优”流程:对于某些机器学习问题,看到多种方法以相同的方式工作并不罕见。如果数据集相对较小,这种情况更可能出现。

本节简要介绍了 TPOT 库,并解释了它的优点和缺点。下一节将讨论 TPOT 解决的问题类型,并详细讨论回归和分类任务的自动化。

TPOT 可以解决的问题类型

TPOT 库被设计为自动化机器学习任务的首选工具;因此,它应该能够轻松处理你抛给它的任何东西。我们很快就会从实际的角度开始使用 TPOT。第三章在回归之前探索,展示了如何使用该库通过许多示例来处理实际任务,接下来的章节将专注于其他类型的任务。

通常,TPOT 可以用来处理以下类型的任务:

  • 回归:目标变量是连续的,例如年龄、身高、体重、分数或价格。请参阅第一章机器学习和自动化思想,以了解回归的简要概述。

  • 分类:目标变量是分类的,例如已售/未售、流失/未流失或是/否。请参阅第一章机器学习和自动化思想,以了解分类的简要概述。

  • 并行训练:TPOT 可以通过Dask库以并行方式处理机器学习模型的训练。请阅读第五章使用 TPOT 和 Dask 进行并行训练,以获得全面了解。

  • 神经网络:TPOT 甚至可以以完全自动化的方式基于最先进的神经网络算法构建模型。请阅读第六章深度学习入门 - 神经网络快速入门,以获得关于神经网络的快速入门课程,以及第七章TPOT 神经网络分类器,以了解 TPOT 的实际实现。

本节其余部分简要讨论了 TPOT 如何处理回归和分类任务,并花费大量时间探索和解释它们的参数、属性和函数。你将在第五章使用 TPOT 和 Dask 进行并行训练中学习 TPOT 如何处理并行训练,以及如何在第六章深度学习入门 - 神经网络快速入门中处理神经网络,因为这些主题需要首先覆盖先决条件。

TPOT 如何处理回归任务

TPOT 库通过tpot.TPOTRegressor类处理回归任务。这个类在包含监督回归模型、预处理程序、特征选择技术以及遵循scikit-learn API 的任何其他估计器或转换器的机器学习管道中进行智能搜索(TPOT 文档页面,TPOT 团队;2019 年 11 月 5 日)。

同一个类也将在管道中的所有对象的超参数上执行搜索。tpot.TPOTRegressor 类允许我们通过 config_dict 参数完全自定义搜索的模型、转换器和参数。

现在我们将介绍 tpot.TPOTRegressor 类实例化时期望的参数:

  • generations: 整数或 None(默认 = 100)。一个可选参数,用于指定运行管道优化过程的迭代次数。它必须是正数。如果没有定义,则必须指定 max_time_mins 参数。

  • population_size: 整数(默认 = 100)。一个可选参数,用于指定每代中保留在 GP 种群中的个体数量。必须是一个正数。

  • offspring_size: 整数(默认 = population_size)。一个可选参数,用于指定每个遗传算法(GP)代中要产生的子代数量。必须是一个正数。

  • mutation_rate: 浮点数(默认 = 0.9)。一个可选参数,用于指定 GP 算法的突变率。必须在 [0.0, 1.0] 范围内。此参数用于指导算法在每代中对多少个管道应用随机变化。

  • crossover_rate: 浮点数(默认 = 0.1)。一个可选参数,指导 GP 算法每代要“繁殖”多少个管道。必须在 [0.0, 1.0] 范围内。

  • scoring: 字符串或可调用函数(默认 = neg_mean_squared_error)。一个可选参数,用于指定回归管道评估的函数名称。可以是 neg_median_abs_valueneg_mean_abs_errorneg_mean_squared_errorr2

  • cv: 整数、交叉验证生成器或可迭代对象(默认 = 5)。一个可选参数,用于指定评估回归管道的交叉验证策略。如果传递的值是整数,它期望折叠数。在其他情况下,它期望一个用作交叉验证生成器的对象,或一个产生训练/测试分割的可迭代对象。

  • subsample: 浮点数(默认 = 1.0)。一个可选参数,用于指定优化过程中使用的训练样本分数。必须在 [0.0, 1.0] 范围内。

  • n_jobs: 整数(默认 = 1)。一个可选参数,用于指定在优化过程中并行评估管道时使用的进程数。将其设置为 -1 以使用所有 CPU 核心。将其设置为 -2 以使用除了一个以外的所有 CPU 核心。

  • max_time_mins: 整数或 None(默认 = None)。一个可选参数,用于指定 TPOT 可以执行优化的分钟数。只有当所有代在指定的最大时间分钟内评估完毕时,TPOT 才会优化更少的时间。

  • max_eval_time_mins: 浮点数(默认 = 5)。一个可选参数,用于指定 TPOT 评估单个管道所需的时间(分钟)。如果参数设置得足够高,TPOT 将评估更复杂的管道。同时,这也使得 TPOT 运行时间更长。

  • random_state: 整数或 None(默认 = None)。一个可选参数,用于指定伪随机数生成器的种子。使用它以获得可重复的结果。

  • config_dict: 字典、字符串或 None(默认 = None)。一个可选参数,用于指定在优化过程中 TPOT 用于自定义操作符和参数的配置字典。可能的输入如下:

    a) None: TPOT 使用默认配置。

    b) Python 字典: TPOT 使用您的配置。

    c) 'TPOT light': 字符串;TPOT 将使用仅包含快速模型和预处理器的内置配置。

    d) 'TPOT MDR': 字符串;TPOT 将使用专门针对基因组研究的内置配置。

    e) 'TPOT sparse': 字符串;TPOT 将使用包含 one-hot 编码器和支持稀疏矩阵的操作符的配置字典。

  • template: 字符串(默认 = None)。一个可选参数,用于指定预定义管道的模板。用于指定 TPOT 评估的机器学习管道的期望结构。

  • warm_start: 布尔值(默认 = False)。一个可选参数,用于指示当前实例是否应重用fit()函数之前调用的种群。此函数将在本章后面讨论。

  • memory: 一个内存对象或字符串(默认 = None)。一个可选参数,用于在调用fit()函数后缓存每个转换器。此函数将在本章后面讨论。

  • use_dask: 布尔值(默认 = False)。一个可选参数,用于指定是否应使用Dask-ML的管道优化。

  • periodic_checkpoint_folder: 路径字符串(默认 = None)。一个可选参数,用于指定在优化过程中 TPOT 将保存管道的文件夹。

  • early_stop: 整数(默认 = None)。一个可选参数,用于指定在多少代之后如果没有改进,TPOT 将停止优化。

  • verbosity: 整数(默认 = 0)。一个可选参数,用于指定 TPOT 在运行时向控制台输出多少信息。可能的选项如下:

    a) 0: TPOT 不打印任何内容。

    b) 1: TPOT 打印最小信息。

    c) 2: TPOT 打印更多信息并提供进度条。

    d) 3: TPOT 打印所有内容并提供进度条。

  • disable_update_check: 布尔值(默认 = False)。一个可选参数,表示是否应禁用 TPOT 版本检查器。您可以忽略此参数,因为它只告诉您是否有库的新版本可用,这与实际的训练无关。

如果你的目标是真正掌握这个库——至少是处理回归任务的部分——那么你应该了解很多参数。我们只涵盖了tpot.TPOTRegressor类的参数,接下来我们将讨论属性和函数。不用担心;可用的属性和函数并不多。

让我们从属性开始。总共有三个属性。一旦管道拟合完成,这些属性就会变得可用:

  • fitted_pipeline_: 来自scikit-learn的管道对象。显示 TPOT 在给定训练集的优化过程中发现的最佳管道。

  • pareto_front_fitted_pipelines_:Python 字典。它包含 TPOT 帕累托前沿上的所有管道。字典的键是表示管道的字符串,值是对应的管道。此参数仅在将verbosity参数设置为3时可用。

  • evaluated_individuals_:Python 字典。它包含所有评估过的管道。字典的键是表示管道的字符串,值是一个元组,包含每个管道的步骤数和相应的准确度指标。

在接下来的章节中,我们将看到所提到的属性在实际中的应用。本节剩下要讨论的只有属于tpot.TPOTRegressor类的函数。总共有四个:

  • fit(features, target, sample_weight=None, groups=None): 此函数用于运行 TPOT 优化过程。features参数是用于预测目标变量的特征/预测器/属性的数组。target参数也是一个数组,指定了预测的目标标签列表。其他两个参数是可选的。sample_weights参数是一个表示每个样本权重的数组。较高的权重表示更高的重要性。最后一个参数groups是一个数组,指定了在执行交叉验证时用于样本的组标签。它应仅与组交叉验证函数一起使用。fit()函数返回拟合的 TPOT 对象的副本。

  • predict(features): 此函数用于根据features参数生成新的预测。此参数是一个包含用于预测目标变量的特征/预测器/属性的数组的数组。该函数返回预测的数组。

  • score(testing_features, testing_target): 此函数返回给定测试数据上优化管道的分数。该函数接受两个参数。第一个参数是testing_features。它是测试集的数组/特征矩阵。第二个参数是testing_target。它也是一个数组,但它是训练集中预测的目标标签。该函数返回测试集上的准确度分数。

  • export(output_file_name): 此函数用于将优化后的管道导出为 Python 代码。该函数接受一个参数,output_file_name。它用于指定 Python 代码应存储的路径和文件名。如果未指定所提及的参数值,则整个管道将作为文本返回。

通过对参数、属性和函数的概述,您现在可以使用 TPOT 的回归功能进行实践。第三章回归前的探索,充满了回归示例,所以如果您想自动化回归任务,请不要犹豫,直接跳转到那里。

本章的下一段将讨论 TPOT 如何处理分类任务。

TPOT 如何处理分类任务

TPOT 库通过tpot.TPOTClassifier类处理分类任务。此类在包含监督回归模型、预处理程序、特征选择技术以及遵循scikit-learn API 的任何其他估计器或转换器的机器学习管道中进行搜索(TPOT 文档页面,TPOT 团队;2019 年 11 月 5 日)。该类还搜索管道中所有对象的超参数。

tpot.TPOTClassifier类允许我们通过config_dict参数完全自定义将被搜索的模型、转换器和参数。

tpot.TPOTClassifier类包含的大部分参数、属性和函数与之前讨论的tpot.TPOTRegressor相同,因此再次详细讨论它们将是冗余的。相反,我们只需提及相同的参数、属性和函数,并将介绍和解释那些用于分类或工作方式不同的函数。

首先,让我们回顾一下参数:

  • generations

  • population_size

  • offspring_size

  • mutation_rate

  • crossover_rate

  • scoring: 字符串或可调用函数(默认 = accuracy)。这是一个可选参数,用于评估给定管道在分类问题中的质量。以下评分函数可以使用:accuracyadjusted_rand_scoreaverage_precisionbalanced_accuracyf1f1_macrof1_microf1_samplesf1_weightedneg_log_lossprecisionrecallrecall_macrorecall_microrecall_samplesrecall_weightedjaccardjaccard_macrojaccard_microjaccard_samplesjaccard_weightedroc_aucroc_auc_ovrroc_auc_ovoroc_auc_ovr_weighted、或 roc_auc_ovo_weighted。如果您想使用自定义评分函数,您可以传递一个具有以下签名的函数:scorer(estimator, X, y)

  • cv

  • subsample

  • n_jobs

  • max_time_mins

  • max_eval_time_mins

  • random_state

  • config_dict

  • template

  • warm_start

  • memory

  • use_dask

  • periodic_checkpoint_folder

  • early_stop

  • verbosity

  • disable_update_check

  • log_file: 类似于文件的对象或字符串(默认 = None)。这是一个可选参数,用于将进度内容保存到文件。如果提供一个字符串值,它应该是所需输出文件的路径和文件名。

正如我们所看到的,一个参数已经改变,一个参数是完全新的。为了重复——请参考前面的子节,以获取每个参数的详细说明。

接下来,我们必须讨论tpot.TPOTClassifier类的属性。一旦管道优化过程完成,它们就会变得可用。总共有三个,它们的行为与tpot.TPOTRegressor类完全相同:

  • fitted_pipeline_

  • pareto_front_fitted_pipelines_

  • evaluated_individuals_

最后,我们将讨论函数。与参数一样,所有函数都被列出,但只有新和更改的函数会详细讨论。总共有五个函数:

  • fit(features, classes, sample_weight=None, groups=None): 此函数的行为与tpot.TPOTRegressor中的函数相同,但第二个参数被称作classes而不是target。此参数期望一个用于预测的类别标签数组。

  • predict(features)

  • predict_proba(features): 此函数与predict()函数执行相同的任务,但返回类别概率而不是类别。你可以通过检查概率来了解模型在预测上完全确定和不确定的地方。你还可以使用类别概率来调整决策阈值。你将在第四章在分类之前探索中学习如何做到这一点。

  • score(testing_features, testing_target)

  • export(output_file_name)

现在,你准备好了解 TPOT 在实际中的工作了。大多数时候,没有必要与列出的某些参数纠缠,但你需要知道它们存在,以便于更高级的使用场景。第四章在分类之前探索,充满了分类示例,所以如果你想学习如何自动化分类任务,不要犹豫,直接跳转到那里。

本章的下一节讨论了如何通过虚拟环境设置 TPOT 环境,无论是对于独立的 Python 安装还是通过 Anaconda 安装。

安装 TPOT 和设置环境

本节讨论了在深入实际内容之前所需的最后一步——安装和环境设置。假设你已经通过独立安装或 Anaconda 安装了 Python 3。

你将学习如何在以下场景下为 TPOT 设置虚拟环境:

  • 独立 Python

  • Anaconda

没有必要阅读两个安装部分,只需选择更适合您的一个即可。在操作系统之间,安装方面不应该有任何差异。如果您已将 Python 作为独立安装安装,您可以通过终端访问pip。如果您通过 Anaconda 安装,您可以通过 Anaconda Navigator 访问。

在独立 Python 安装中安装和配置 TPOT

在继续之前,请确保已安装 Python 和pip(Python 的包管理器)。您可以通过在终端中输入以下代码行来检查pip是否已安装:

> pip

如果您看到以下图中的输出,那么您就可以开始了:

![图 2.3 – 检查 pip 是否已安装

![图片 B16954_02_003.jpg]

![图 2.3 – 检查 pip 是否已安装

我们现在可以继续设置虚拟环境:

  1. 首件事是安装virtualenv包。为此,从终端执行此行:

    > pip install virtualenv
    
  2. 几秒钟后,您应该会看到成功消息,如图所示:![图 2.4 – virtualenv 安装

    ![图片 B16954_02_004.jpg]

    图 2.4 – virtualenv 安装

  3. 下一步是创建一个文件夹,其中将存储 TPOT 环境。我们的位于Documents文件夹中,但您可以将它存储在任何地方。以下是您需要执行的精确 shell 行,以创建文件夹并安装 Python 虚拟环境:

    > cd Documents
    > mkdir python_venvs
    > virtualenv python_venvs/tpot_env
    
  4. 执行和结果如图所示:![图 2.5 – 创建虚拟环境

    ![图片 B16954_02_005.jpg]

    图 2.5 – 创建虚拟环境

    环境现在已成功安装。

  5. 要激活环境,您需要从终端执行以下行:

    > source python_venvs/tpot_env/bin/activate
    
  6. 括号中的文本确认环境已激活。请看以下图中从base环境到tpot_env的变化:![图 2.6 – 激活虚拟环境

    ![图片 B16954_02_006.jpg]

    图 2.6 – 激活虚拟环境

  7. 要停用环境,请在终端中输入以下行:

    jupyterlab: A notebook environment required for analyzing and exploring data and building machine learning models in an interactive way.
    
  8. numpy:Python 进行数值计算的首选库。

  9. pandas:一个知名的数据加载、处理、准备、转换、聚合甚至可视化的库。

  10. matplotlib:Python 的标准数据可视化库。我们有时会使用它进行基本绘图。

  11. seaborn:一个比matplotlib更美观的数据可视化库。

  12. scikit-learn:Python 进行机器学习和与之相关的所有内容的首选库。

  13. TPOT:用于以自动化方式找到最优机器学习管道。

  14. 要安装所有提到的库,您可以从打开的终端窗口执行以下行:

    > pip install jupyterlab numpy pandas matplotlib seaborn scikit-learn TPOT
    

    Python 将立即开始下载和安装库,如图所示:

    ![图 2.8 – 使用 pip 安装库

    ![图片 B16954_02_008.jpg]

    图 2.8 – 使用 pip 安装库

  15. 要测试环境是否已成功配置,我们可以从终端打开JupyterLab。在安装完库后,执行以下 shell 命令:

    > jupyter lab
    

    如果您看到以下类似内容,那么一切按计划进行。Jupyter 的浏览器窗口应立即打开:

    图 2.9 – 为独立安装启动 JupyterLab

    图 2.9 – 为独立安装启动 JupyterLab

  16. 最后的检查,我们将查看环境中附带的是哪个 Python 版本。这可以直接从笔记本中完成,如图所示:图 2.10 – 检查独立安装的 Python 版本

    图 2.10 – 检查独立安装的 Python 版本

  17. 最后,我们将通过导入并打印版本来查看 TPOT 库是否已安装。此检查也可以从笔记本中完成。按照以下图示中的说明进行操作:

图 2.11 – 检查独立安装的 TPOT 版本

图 2.11 – 检查独立安装的 TPOT 版本

TPOT 现在已成功安装在虚拟环境中。下一节将介绍如何使用 Anaconda 安装和配置环境。

通过 Anaconda 安装和配置 TPOT

在继续之前,请确保您的机器上已安装 Anaconda。我们将使用 Anaconda 创建和管理我们的环境,并从那里进行配置:

  1. 要开始,打开 Anaconda Navigator:图 2.12 – Anaconda Navigator

    图 2.12 – Anaconda Navigator

  2. 要创建一个新的虚拟环境,请点击屏幕左下角的创建按钮:图 2.13 – 在 Anaconda 中创建新环境

    图 2.13 – 在 Anaconda 中创建新环境

  3. 在点击base (root)环境后。以下是它应有的样子:图 2.15 – 所有虚拟环境的列表

    图 2.15 – 所有虚拟环境的列表

  4. 您现在可以开始在虚拟环境中安装库了。Anaconda 通过点击播放按钮并选择jupyterlab,使您能够轻松地从终端打开环境:这是一个用于以交互方式分析和探索数据以及构建机器学习模型的笔记本环境。

  5. numpy:Python 进行数值计算的常用库。

  6. pandas:一个用于数据加载、处理、准备、转换、聚合甚至可视化的知名库。

  7. matplotlib:Python 的标准数据可视化库。我们有时会使用它进行基本绘图。

  8. seaborn:一个比matplotlib更具视觉吸引力的数据可视化库。

  9. scikit-learn:Python 进行机器学习和相关内容的常用库。

  10. TPOT:用于以自动化的方式寻找最优机器学习流程。

  11. 要安装所有提到的库,你可以从打开的终端窗口中执行以下行:

    > pip install jupyterlab numpy pandas matplotlib seaborn scikit-learn TPOT
    

    Python 应该会立即开始下载和安装库,如下图所示:

    图 2.17 – 通过终端安装库

    图 2.17 – 通过终端安装库

  12. 为了测试环境是否成功配置,我们可以从终端打开 JupyterLab。在库安装完成后,执行以下 shell 命令:

    > jupyter lab
    

    如果你看到以下类似的内容,那么一切按计划进行。Jupyter 的浏览器窗口应该会立即打开:

    图 2.18 – 从终端启动 JupyterLab

    图 2.18 – 从终端启动 JupyterLab

  13. 为了最后的检查,我们将查看环境附带的是哪个 Python 版本。这可以直接从笔记本中完成,如下图所示:图 2.19 – 检查 Python 版本

    图 2.19 – 检查 Python 版本

  14. 最后,我们将通过导入并打印版本来查看 TPOT 库是否已安装。这个检查也可以从笔记本中完成。按照以下图中的说明查看如何操作:

图 2.20 – 检查 TPOT 版本

图 2.20 – 检查 TPOT 版本

我们现在可以开始使用 TPOT 的实际应用了。

摘要

你在本章中学到了很多——从 TPOT 的工作原理和遗传规划到使用 pip 和 Anaconda 设置环境。你现在可以以自动化的方式处理实际任务了。

以下章节将深入探讨使用 TPOT 处理回归任务,并附上几个示例。在本章讨论的所有内容,在我们动手实践后将会变得非常清晰。然后,在第四章 在分类之前探索,你将通过解决分类任务来进一步巩固你的知识。

Q&A

  1. 用你自己的话定义 TPOT 库。

  2. 列出并解释 TPOT 的几个局限性。

  3. 你会如何限制 TPOT 中的优化时间?

  4. 简要定义“遗传编程”这个术语。

  5. 列出并解释 tpot.TPOTRegressor 类的五个参数。

  6. 列出并解释 tpot.TPOTClassifier 类中引入的不同和新参数。

  7. 虚拟环境是什么,为什么它们有用?

进一步阅读

这里是本章中引用的资料来源:

第三章:使用 TPOT 探索回归

在本章中,您将通过三个数据集获得自动回归建模的实践经验。您将学习如何以自动化的方式使用 TPOT 处理回归任务,并通过大量的实际示例、技巧和建议来掌握这一点。

我们将首先介绍一些基本主题,如数据集加载、探索性数据分析和基本数据准备。然后,我们将使用 TPOT 进行实践。您将学习如何以自动化的方式训练模型以及如何评估这些模型。

在自动训练模型之前,我们将看看如何通过基本模型(如线性回归)获得良好的性能。这些模型将作为 TPOT 需要超越的基准。

本章将涵盖以下主题:

  • 将自动回归建模应用于鱼市场数据集

  • 将自动回归建模应用于保险数据集

  • 将自动回归建模应用于车辆数据集

技术要求

为了完成本章,您需要一个安装了 Python 和 TPOT 的计算机。上一章演示了如何从头开始设置环境,无论是独立的 Python 安装还是通过 Anaconda 安装。请参阅第二章深入 TPOT,以获取环境设置的详细说明。

您可以在此处下载本章的源代码和数据集:github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT/tree/main/Chapter03

将自动回归建模应用于鱼市场数据集

本节演示了如何使用 TPOT 将机器学习自动化应用于回归数据集。本节使用鱼市场数据集(www.kaggle.com/aungpyaeap/fish-market)进行探索和回归建模。目标是预测鱼的重量。您将学习如何加载数据集、可视化它、充分准备它,以及如何使用 TPOT 找到最佳的机器学习流程:

  1. 首先要做的事情是加载所需的库和数据集。关于库,您需要numpypandasmatplotlibseaborn。此外,使用matplotlib导入rcParams模块以稍微调整绘图样式。您可以在以下代码块中找到此步骤的代码:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    from matplotlib import rcParams
    rcParams['axes.spines.top'] = False
    rcParams['axes.spines.right'] = False
    df = pd.read_csv('data/Fish.csv')
    df.head()
    

    下面是如何查看前几行(调用head()方法的结果):

    图 3.1 – 鱼市场数据集的前五行

    图 3.1 – 鱼市场数据集的前五行

  2. 探索性数据分析将在下一部分介绍。这不是使用 TPOT 的硬性要求,但您应该始终了解您的数据看起来如何。最感兴趣的第一件事是缺失值。以下是检查它们的方法:

    df.isnull().sum()
    

    下面是相应的输出:

    ![图 3.2 – 每列缺失值的计数 图片

    图 3.2 – 每列缺失值的计数

    如您所见,没有缺失值。这使得数据准备过程更加容易和快捷。

  3. 下一步是检查目标变量的分布情况。对于这个数据集,我们试图预测Weight。以下是绘制简单直方图的代码:

    plt.figure(figsize=(12, 7))
    plt.title('Target variable (Weight) distribution', size=20)
    plt.xlabel('Weight', size=14)
    plt.ylabel('Count', size=14)
    plt.hist(df['Weight'], bins=15, color='#4f4f4f', ec='#040404');
    

    下面是直方图的样子:

    ![图 3.3 – 目标变量(重量)的直方图 图片

    图 3.3 – 目标变量(重量)的直方图

    大多数鱼都很轻,但也有一些重的。让我们进一步探索物种,以获得更好的了解。

  4. 以下代码打印了特定物种的实例数量(总数和百分比),并且还打印了每个属性的均值和标准差。为了更精确,我们保留了原始数据集中物种等于指定物种的子集。之后,为子集中的每一列打印了记录数、总百分比、均值和标准差。

    然后这个函数被调用以针对每个独特的物种:

    def describe_species(species):
        subset = df[df['Species'] == species]
        print(f'============ {species.upper()} ============')
        print(f'Count: {len(subset)}')
        print(f'Pct. total: {(len(subset) / len(df) * 100):.2f}%')
        for column in df.columns[1:]:
            avg = np.round(subset[column].mean(), 2)
            sd = np.round(subset[column].std(), 2)
            print(f'Avg. {column:>7}: {avg:6} +/- {sd:6}')
    for species in df['Species'].unique():
        describe_species(species)
        print()
    

    下面是相应的输出:

    ![图 3.4 – 每种鱼的特征探索 图片

    图 3.4 – 每种鱼的特征探索

  5. 最后,让我们检查属性之间的相关性。相关性只能计算数值属性。以下代码片段展示了如何使用seaborn库可视化相关矩阵:

    plt.figure(figsize=(12, 9))
    plt.title('Correlation matrix', size=20)
    sns.heatmap(df.corr(), annot=True, cmap='Blues');
    

    这是相关矩阵:

    ![图 3.5 – 特征的相关矩阵 图片

    图 3.5 – 特征的相关矩阵

    在探索性数据分析过程中,你可以做更多的事情,但我们将在这里停止。这本书展示了如何使用 TPOT 构建自动化模型,因此我们应该在那里花大部分时间。

  6. 在建模之前,我们还有一步要做,那就是数据准备。我们不能将非数值属性传递给管道优化器。我们将它们转换为虚拟变量以简化问题,并在之后将它们与原始数据合并。以下是执行此操作的代码:

    species_dummies = pd.get_dummies(df['Species'], drop_first=True, prefix='Is')
    df = pd.concat([species_dummies, df], axis=1)
    df.drop('Species', axis=1, inplace=True)
    df.head()
    

    下面是数据集现在的样子:

    ![图 3.6 – 数据准备后的鱼市场数据集的前五行 图片

    图 3.6 – 数据准备后的鱼市场数据集的前五行

    如您所见,我们删除了Species列,因为它不再需要。让我们接下来开始建模。

  7. 首先,我们需要做一些导入并决定评分策略。TPOT 自带一些回归评分指标。默认的是neg_mean_squared_error。我们无法避免负指标,但至少可以使其与目标变量的单位相同。预测重量并跟踪重量平方的错误是没有意义的。这就是均方根误差RMSE)发挥作用的地方。这是一个简单的指标,它计算先前讨论的均方误差的平方根。由于平方根运算,我们跟踪的是原始单位(重量)中的错误,而不是平方单位(重量平方)。我们将使用 lambda 函数来定义它:

    from tpot import TPOTRegressor
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import mean_squared_error, make_scorer
    rmse = lambda y, y_hat: np.sqrt(mean_squared_error(y, y_hat))
    
  8. 接下来在需求列表上是训练测试集分割。我们将保留 75%的数据用于训练,并在剩余的数据上进行评估:

    X = df.drop('Weight', axis=1)
    y = df['Weight']
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.25, random_state=42
    )
    

    这是训练集和测试集中实例的数量:

    图 3.7 – 训练集和测试集中的实例数量

    img/B16954_03_007.jpg

    图 3.7 – 训练集和测试集中的实例数量

  9. 接下来,让我们使用线性回归算法创建一个模型。这个模型只是 TPOT 需要超越的基线:

    from sklearn.linear_model import LinearRegression
    lm = LinearRegression()
    lm.fit(X_train, y_train)
    lm_preds = lm.predict(X_test)
    rmse(y_test, lm_preds)
    

    这是测试集上线性回归的相应 RMSE 值:

    图 3.8 – 线性回归模型的 RMSE 评分(基线)

    img/B16954_03_008.jpg

    图 3.8 – 线性回归模型的 RMSE 评分(基线)

    基线模型平均错误为 82 个权重单位。考虑到我们的权重高达 1,500,这还不错。

  10. 接下来,让我们拟合一个 TPOT 管道优化模型。我们将使用我们的 RMSE 评分器,并进行 10 分钟的优化。你可以优化更长的时间,但 10 分钟应该优于基线模型:

    rmse_scorer = make_scorer(rmse, greater_is_better=False)
    pipeline_optimizer = TPOTRegressor(
        scoring=rmse_scorer,
        max_time_mins=10,
        random_state=42
    )
    pipeline_optimizer.fit(X_train, y_train)
    

    优化完成后,控制台显示的输出如下:

    图 3.9 – TPOT 回归器输出

    img/B16954_03_009.jpg

    图 3.9 – TPOT 回归器输出

  11. 这是获取 RMSE 评分的方法:

    pipeline_optimizer.score(X_test, y_test)
    

    下面是相应的输出:

    图 3.10 – TPOT 优化管道模型的 RMSE 评分

    img/B16954_03_010.jpg

    图 3.10 – TPOT 优化管道模型的 RMSE 评分

    不要担心数字前面的负号。实际的 RMSE 是 73.35 个重量单位。TPOT 模型优于基线模型。这就是你需要知道的一切。TPOT 通过fitted_pipeline_属性给我们提供了访问最佳管道的途径。以下是它的样子:

    图 3.11 – 完整的 TPOT 管道

    img/B16954_03_011.jpg

    图 3.11 – 完整的 TPOT 管道

  12. 作为最后一步,我们可以将管道导出到 Python 文件中。以下是方法:

    pipeline_optimizer.export('fish_pipeline.py')
    

    文件看起来是这样的:

图 3.12 – TPOT 管道的源代码

img/B16954_03_012.jpg

图 3.12 – TPOT 管道的源代码

你现在可以使用这个文件对新数据做出预测。

在本节中,您已经在简单数据集上使用 TPOT 构建了您的第一个自动机器学习流程。在实践中,大多数情况下,您所采取的步骤看起来会很相似。数据清洗和准备是其中有所不同之处。始终确保在将数据集传递给 TPOT 之前充分准备您的数据集。当然,TPOT 为您做了很多事情,但它不能将垃圾数据转化为可用的模型。

在下一节中,您将看到如何将 TPOT 应用于医疗保险数据集。

将自动回归建模应用于保险数据集

本节演示了如何将自动机器学习解决方案应用于一个稍微复杂一些的数据集。您将使用医疗保险成本数据集(www.kaggle.com/mirichoi0218/insurance)来预测基于几个预测变量保险费用将花费多少。您将学习如何加载数据集,进行数据探索性分析,如何准备数据,以及如何使用 TPOT 找到最佳的机器学习流程:

  1. 与前面的例子一样,第一步是加载库和数据集。我们需要numpypandasmatplotlibseaborn来开始分析。以下是导入库和加载数据集的方法:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    from matplotlib import rcParams
    rcParams['axes.spines.top'] = False
    rcParams['axes.spines.right'] = False
    df = pd.read_csv('data/insurance.csv')
    df.head()
    

    下面的图中显示了前五行:

    ![Figure 3.13 – First five rows of the insurance dataset img/B16954_03_013.jpg

    图 3.13 – 医疗保险数据集的前五行

  2. 我们将继续进行数据探索性分析。与前面的例子一样,我们首先将检查缺失值的数量。以下是执行此操作的代码:

    df.isnull().sum()
    

    下面的图显示了每列的缺失值计数:

    ![Figure 3.14 – Missing value counts per column for the insurance dataset img/B16954_03_014.jpg

    图 3.14 – 医疗保险数据集每列的缺失值计数

    如您所见,没有缺失值。

  3. 我们试图使用这个数据集预测charges列,所以让我们快速检查我们可以在那里期望什么类型的值。直方图似乎是一个足够简单的选项。以下是绘制直方图所需的代码:

    plt.figure(figsize=(12, 7))
    plt.title('Target variable (charges) distribution', size=20)
    plt.xlabel('Charge', size=14)
    plt.ylabel('Count', size=14)
    plt.hist(df['charges'], bins=15, color='#4f4f4f', ec='#040404');
    

    下面是相应的直方图:

    ![Figure 3.15 – Distribution of the target variable img/B16954_03_015.jpg

    图 3.15 – 目标变量的分布

    因此,值甚至超过了$60,000.00。大多数值都较低,所以将很有趣地看到模型将如何处理它。

  4. 让我们深入分析并探索其他变量。目标是查看每个分类变量段的平均保险费用。我们将使用中位数作为平均值,因为它对异常值不太敏感。

    接近这种分析的最简单方法是创建一个函数,该函数为指定的列生成条形图。以下函数对于这个例子以及未来的许多其他例子都很有用。它从一个分组数据集中计算中位数,并使用标题、标签、图例和条形图顶部的文本来可视化条形图。您可以在一般情况下使用此函数来可视化分组操作后某些变量的中位数。它最适合分类变量:

    def make_bar_chart(column, title, ylabel, xlabel, y_offset=0.12, x_offset=700):
        ax = df.groupby(column).median()[['charges']].plot(
            kind='bar', figsize=(10, 6), fontsize=13, color='#4f4f4f'
        )
        ax.set_title(title, size=20, pad=30)
        ax.set_ylabel(ylabel, fontsize=14)
        ax.set_xlabel(xlabel, fontsize=14)
        ax.get_legend().remove()
    
        for i in ax.patches:
            ax.text(i.get_x() + x_offset, i.get_height() + y_offset, f'${str(round(i.get_height(), 2))}', fontsize=15)
        return ax
    
  5. 现在我们使用这个函数来可视化吸烟者和非吸烟者的平均保险费用。以下是代码:

    make_bar_chart(
        column='smoker',
        title='Median insurance charges for smokers and non-smokers',
        ylabel='Insurance charge ($)',
        xlabel='Do they smoke?',
        y_offset=700,
        x_offset=0.12
    )
    

    下面是相应的可视化:

    ![图 3.16 – 吸烟者和非吸烟者的平均保险费用 图片

    图 3.16 – 吸烟者和非吸烟者的平均保险费用

    如您所见,吸烟者支付的保险费是非吸烟者的几倍。

  6. 让我们制作一个类似的可视化来比较男性和女性的平均保险费用:

    make_bar_chart(
        column='sex',
        title='Median insurance charges between genders',
        ylabel='Insurance charge ($)',
        xlabel='Gender',
        y_offset=200,
        x_offset=0.15
    )
    

    您可以在这里看到可视化:

    ![图 3.17 – 性别之间的平均保险费用 图片

    图 3.17 – 性别之间的平均保险费用

    这里没有太大的差异。

  7. 但如果我们按孩子的数量比较平均保险费用会发生什么?以下代码片段正是这样做的:

    make_bar_chart(
        column='children',
        title='Median insurance charges by number of children',
        ylabel='Insurance charge ($)',
        xlabel='Number of children',
        y_offset=200,
        x_offset=-0.15
    )
    

    成本分布如下:

    ![图 3.18 – 按孩子数量划分的平均保险费用 图片

    图 3.18 – 按孩子数量划分的平均保险费用

    保险费用似乎会随着第五个孩子的出生而增加。可能没有那么多有五个孩子的家庭。你能自己确认一下吗?

  8. 那么,地区呢?以下是按地区可视化平均保险费用的代码:

    make_bar_chart(
        column='region',
        title='Median insurance charges by region',
        ylabel='Insurance charge ($)',
        xlabel='Region',
        y_offset=200,
        x_offset=0
    )
    

    下面这张图显示了每个地区的成本分布:

    ![图 3.19 – 按地区划分的平均保险费用 图片

    图 3.19 – 按地区划分的平均保险费用

    值之间没有太大的差异。

    我们已经制作了大量可视化并探索了数据集。现在是时候准备它并应用机器学习模型了。

  9. 为了使这个数据集准备好进行机器学习,我们需要做一些事情。首先,我们必须将sexsmoker列中的字符串值重映射为整数。然后,我们需要为region列创建虚拟变量。这一步是必要的,因为 TPOT 无法理解原始文本数据。

    下面是进行必要准备工作的代码片段:

    df['sex'] = [1 if x == 'female' else 0 for x in df['sex']]
    df.rename(columns={'sex': 'is_female'}, inplace=True)
    df['smoker'] = [1 if x == 'yes' else 0 for x in df['smoker']]
    region_dummies = pd.get_dummies(df['region'], drop_first=True, prefix='region')
    df = pd.concat([region_dummies, df], axis=1)
    df.drop('region', axis=1, inplace=True)
    df.head()
    

    调用head()函数会得到以下图所示的数据集:

    ![图 3.20 – 准备后的保险数据集 图片

    图 3.20 – 准备后的保险数据集

  10. 数据集现在已准备好进行预测建模。在我们这样做之前,让我们检查变量与目标变量之间的相关性。以下代码片段绘制了带有注释的相关矩阵:

    plt.figure(figsize=(12, 9))
    plt.title('Correlation matrix', size=20)
    sns.heatmap(df.corr(), annot=True, cmap='Blues');
    

    下面这张图显示了相应的相关矩阵:

    图 3.21 – 保险数据集的相关矩阵

    图 3.21 – 保险数据集的相关矩阵

    下一个目标 – 预测建模。

  11. 如前所述,第一步是进行训练/测试分割。以下代码片段展示了如何进行:

    from sklearn.model_selection import train_test_split
    X = df.drop('charges', axis=1)
    y = df['charges']
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.25, random_state=42
    )
    y_train.shape, y_test.shape
    

    训练集和测试集中的实例数量显示在以下图中:

    图 3.22 – 训练集和测试集中的实例数量

    图 3.22 – 训练集和测试集中的实例数量

  12. 我们首先使用线性回归算法创建一个基线模型。这将作为 TPOT 必须超越的目标。你可以在这里找到训练基线模型的代码片段:

    from sklearn.linear_model import LinearRegression
    from sklearn.metrics import r2_score, mean_squared_error
    rmse = lambda y, y_hat: np.sqrt(mean_squared_error(y, y_hat))
    lm = LinearRegression()
    lm.fit(X_train, y_train)
    lm_preds = lm.predict(X_test)
    print(f'R2   = {r2_score(y_test, lm_preds):.2f}')
    print(f'RMSE = {rmse(y_test, lm_preds):.2f}')
    

    决定系数(R2)和均方根误差(RMSE)值显示在以下图中:

    图 3.23 – 线性回归模型的 R2 和 RMSE

    图 3.23 – 线性回归模型的 R2 和 RMSE

    平均而言,一个简单的线性回归模型错误为\(5,926.02\)。这个简单的模型捕捉了数据集中 77%的方差。

  13. 我们可以通过检查分配的权重(系数)来进一步探索线性回归模型的特征重要性。

    以下代码片段打印变量名称及其对应的系数:

    for i, column in enumerate(df.columns[:-1]):
        coef = np.round(lm.coef_[i], 2)
        print(f'{column:17}: {coef:8}')
    

    输出显示在以下图中:

    图 3.24 – 线性回归模型的系数

    图 3.24 – 线性回归模型的系数

    如你所见,具有最大系数的列是smoker。这很合理,因为它证实了我们探索性数据分析阶段所做的可视化。

  14. 现在是时候动用重武器了。我们将使用 TPOT 库来生成一个自动化的机器学习流程。这次我们将优化流程以 R2 分数为目标,但如果你愿意,也可以坚持使用 RMSE 或其他任何指标。

    以下代码片段导入 TPOT 库,实例化它,并拟合流程:

    from tpot import TPOTRegressor
    pipeline_optimizer = TPOTRegressor(
        scoring='r2',
        max_time_mins=10,
        random_state=42,
        verbosity=2
    )
    pipeline_optimizer.fit(X_train, y_train)
    

    10 分钟后,你应该在你的笔记本中看到以下输出:

    图 3.25 – 每一代的 TPOT 分数

    图 3.25 – 每一代的 TPOT 分数

    在最近几代中,训练集上的分数开始上升。如果你给 TPOT 更多时间来训练,你可能会得到一个稍微更好的模型。

  15. 测试集上的 R2 分数可以通过以下代码获取:

    pipeline_optimizer.score(X_test, y_test)
    

    分数显示在以下图中:

    图 3.26 – 测试集上的 TPOT R2 分数

    图 3.26 – 测试集上的 TPOT R2 分数

  16. 你可以手动获取测试集的 R2 和 RMSE 值。以下代码片段展示了如何操作:

    tpot_preds = pipeline_optimizer.predict(X_test)
    print(f'R2   = {r2_score(y_test, tpot_preds):.2f}')
    print(f'RMSE = {rmse(y_test, tpot_preds):.2f}')
    

    相应的分数如下所示:

    图 3.27 – 测试集上的 TPOT R2 和 RMSE 分数

    图 3.27 – 测试集上的 TPOT R2 和 RMSE 分数

  17. 作为最后一步,我们将优化流程导出到一个 Python 文件中。以下代码片段展示了如何操作:

    pipeline_optimizer.export('insurance_pipeline.py')
    

    优化流程的 Python 代码如下所示:

![图 3.28 – 保险数据集的 TPOT 优化管道图片

图 3.28 – 保险数据集的 TPOT 优化管道

您现在可以使用此文件对新数据做出预测。最好让管道根据需要执行优化,但即使 10 分钟也足以产生高质量的模型。

本节向您展示了如何构建针对不同指标优化的自动化管道,并在控制台上打印出更多详细输出。如您所见,优化代码大致相同。数据准备从项目到项目变化很大,这也是您将花费大部分时间的地方。

在下一节中,您将看到如何将 TPOT 应用于车辆数据集。

将自动回归建模应用于车辆数据集

本节展示了如何开发一个针对迄今为止最复杂的数据集的自动化机器学习模型。您将使用车辆数据集(www.kaggle.com/nehalbirla/vehicle-dataset-from-cardekho),如果您还没有下载,请下载。目标是根据各种预测因子(如制造年份和行驶公里数)预测销售价格。

这次,我们不会专注于探索性数据分析。如果您已经跟随了最后两个示例,您可以自己完成这项工作。相反,我们将专注于数据集准备和模型训练。将此数据集转换为机器学习准备需要做大量的工作,所以让我们立即开始:

  1. 再次强调,第一步是加载库和数据集。要求与前面的示例相同。您需要numpypandasmatplotlibseaborn。以下是导入库和加载数据集的方法:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    from matplotlib import rcParams
    rcParams['axes.spines.top'] = False
    rcParams['axes.spines.right'] = False
    df = pd.read_csv('data/Car.csv')
    df.head()
    

    调用head()函数显示前五行。您可以在以下图中看到它们的样式:

    ![图 3.29 – 车辆数据集的前五行 图片

    图 3.29 – 车辆数据集的前五行

  2. 数据集有很多列,图 3.29 中并没有显示所有列。数据准备阶段的下一步是检查缺失值。以下代码片段就是做这件事的:

    df.isnull().sum()
    

    结果如下所示:

    ![图 3.30 – 车辆数据集中缺失值的计数 图片

    图 3.30 – 车辆数据集中缺失值的计数

    一些值缺失,我们将通过最简单的方法解决这个问题——通过删除它们。

  3. 删除缺失值可能并不总是最佳选择。您应该始终调查为什么值会缺失,以及它们是否可以(或应该)以某种方式填充。本书侧重于机器学习自动化,所以我们在这里不会这么做。

    这是删除缺失值的方法:

    df.dropna(inplace=True)
    df.isnull().sum()
    

    执行前面的代码会产生以下计数:

    图 3.31 – 从车辆数据集中移除缺失值

    图 3.31 – 从车辆数据集中移除缺失值

  4. 现在没有任何缺失值了,但这并不意味着我们已经完成了数据准备。以下是使此数据集适合机器学习所需的步骤列表:

    • transmission列转换为整数 - 如果是手动,则为 1,否则为 0。同时,将列重命名为is_manual

    • owner列重映射为整数。查看remap_owner()函数以获取更多说明。

    • 从相应的属性中提取汽车品牌、里程、引擎和最大马力。所有提到的属性中感兴趣的价值是第一个空格之前的内容。

    • 从属性namefuelseller_type创建虚拟变量。

    • 将原始数据集与虚拟变量连接,并删除不必要的属性。

      下面是remap_owner()函数的代码:

      def remap_owner(owner):
          if owner == 'First Owner': return 1
          elif owner == 'Second Owner': return 2
          elif owner == 'Third Owner': return 3
          elif owner == 'Fourth & Above Owner': return 4
          else: return 0
      

      下面是执行所有提到的转换的代码:

      df['transmission'] = [1 if x == 'Manual' else 0 for x in df['transmission']]
      df.rename(columns={'transmission': 'is_manual'}, inplace=True)
      df['owner'] = df['owner'].apply(remap_owner)
      df['name'] = df['name'].apply(lambda x: x.split()[0])
      df['mileage'] = df['mileage'].apply(lambda x: x.split()[0]).astype(float)
      df['engine'] = df['engine'].apply(lambda x: x.split()[0]).astype(int)
      df['max_power'] = df['max_power'].apply(lambda x: x.split()[0]).astype(float)
      brand_dummies = pd.get_dummies(df['name'], drop_first=True, prefix='brand')
      fuel_dummies = pd.get_dummies(df['fuel'], drop_first=True, prefix='fuel')
      seller_dummies = pd.get_dummies(df['seller_type'], drop_first=True, prefix='seller')
      df.drop(['name', 'fuel', 'seller_type', 'torque'], axis=1, inplace=True)
      df = pd.concat([df, brand_dummies, fuel_dummies, seller_dummies], axis=1)
      

      应用转换后,数据集看起来是这样的:

图 3.32 – 准备好的车辆数据集

图 3.32 – 准备好的车辆数据集

此格式的数据可以传递给机器学习算法。我们接下来就做这件事。

  1. 和往常一样,我们将从训练/测试集分割开始。以下代码片段显示了如何在数据集上执行此操作:

    from sklearn.model_selection import train_test_split
    X = df.drop('selling_price', axis=1)
    y = df['selling_price']
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.25, random_state=42
    )
    y_train.shape, y_test.shape
    

    你可以在图 3.33中看到两个集合中的实例数量:

    图 3.33 – 训练集和测试集中的实例数量

    图 3.33 – 训练集和测试集中的实例数量

    如您所见,这是一个比我们之前拥有的更大的数据集。

  2. 这次我们不会使用评估回归模型的常规指标(R2 和 RMSE)。我们将使用scikit-learn库,因此我们需要手动实现它。以下是实现方法:

    def mape(y, y_hat): 
        y, y_hat = np.array(y), np.array(y_hat)
        return np.mean(np.abs((y - y_hat) / y)) * 100
    
  3. 现在是时候建立一个基线模型了。再次强调,它将是一个线性回归模型,使用测试集的 R2 和 MAPE 指标进行评估。以下是实现基线模型的代码:

    from sklearn.linear_model import LinearRegression
    from sklearn.metrics import r2_score
    lm = LinearRegression()
    lm.fit(X_train, y_train)
    lm_preds = lm.predict(X_test)
    print(f'R2   = {r2_score(y_test, lm_preds):.2f}')
    print(f'MAPE = {mape(y_test, lm_preds):.2f}')
    

    相应的结果显示在下图中:

    图 3.34 – 基线模型的 R2 和 MAPE

    图 3.34 – 基线模型的 R2 和 MAPE

    平均而言,基线模型错误率为 43%。这很多,但我们不得不从某处开始。

  4. 让我们看一下线性回归模型的系数,以确定哪些特征是重要的。以下是获取系数的代码:

    for i, column in enumerate(df.columns[:-1]):
        coef = np.round(lm.coef_[i], 2)
        print(f'{column:20}: {coef:12}')
    

    下面是系数:

    图 3.35 – 基线模型系数

    图 3.35 – 基线模型系数

    请花点时间来欣赏一下这种可解释性。年份越高,汽车越新,这导致价格越高。车辆行驶的公里数越多,价格就越低。看起来自动挡汽车的售价也更高。你明白了。可解释性是线性回归提供的东西。但它缺乏准确性。这正是 TPOT 要改进的地方。

  5. 接下来,我们将拟合一个 TPOT 模型,并针对 MAPE 分数进行优化。我们将在每个可用的 CPU 核心上(由n_jobs=-1指示)训练模型 10 分钟:

    from tpot import TPOTRegressor
    from sklearn.metrics import make_scorer
    mape_scorer = make_scorer(mape, greater_is_better=False)
    pipeline_optimizer = TPOTRegressor(
        scoring=mape_scorer,
        max_time_mins=10,
        random_state=42,
        verbosity=2,
        n_jobs=-1
    )
    pipeline_optimizer.fit(X_train, y_train)
    

    10 分钟后的输出显示在以下图中:

    图 3.36 – TPOT 优化过程的输出

    图 3.36 – TPOT 优化过程的输出

    看起来 10 分钟的时间对 TPOT 来说远远不够以发挥其最佳性能。

    结果的管道显示在以下图中:

    图 3.37 – 10 分钟后的最佳拟合管道

    图 3.37 – 10 分钟后的最佳拟合管道

  6. 现在是真相大白的时候了——MAPE 是否有所下降?以下是查找代码:

    tpot_preds = pipeline_optimizer.predict(X_test)
    print(f'R2   = {r2_score(y_test, tpot_preds):.2f}')
    print(f'MAPE = {mape(y_test, tpot_preds):.2f}')
    

    输出显示在以下图中:

图 3.38 – TPOT 优化模型的 R2 和 MAPE

图 3.38 – TPOT 优化模型的 R2 和 MAPE

如您所见,TPOT 显著降低了错误并同时提高了拟合优度(R2)。正如预期的那样。

最后的代码示例部分向您展示了如何在更复杂的数据集上训练自动化模型是多么容易。根据你优化的指标,程序大致相同,但数据准备阶段才是所有差异的来源。

如果你花更多的时间准备和分析数据,也许还会移除一些噪声数据,你将获得更好的结果,这是有保证的!尤其是在有很多列包含文本数据的情况下。可以从那里提取出很多特征。

摘要

这是本书的第一个完全实践性的章节。您已经将前几章的理论与实践相结合。您不仅构建了一个,而是构建了三个完全自动化的机器学习模型。毫无疑问,您现在应该能够使用 TPOT 来解决任何类型的回归问题。

就像数据科学和机器学习中的大多数事情一样,90%的工作归结为数据准备。TPOT 可以使这个比例更高,因为花在设计和调整模型上的时间更少。明智地利用这额外的时间,并让自己完全熟悉数据集。这是不可避免的。

在下一章中,您将看到如何为分类数据集构建自动化机器学习模型。那一章也将完全是实践性的。稍后,在第五章并行训练 TPOT 和 Dask中,我们将结合理论与实践。

Q&A

  1. 哪种类型的数据可视化可以让你探索连续变量的分布?

  2. 解释 R2、RMSE 和 MAPE 指标。

  3. 你可以使用 TPOT 的自定义评分函数吗?如果可以,如何使用?

  4. 为什么首先构建基线模型是必要的?哪种算法被认为是回归任务的“基线”?

  5. 线性回归模型的系数能告诉你什么?

  6. 在训练 TPOT 模型时,如何使用所有 CPU 核心?

  7. 你可以使用 TPOT 获取最佳管道的 Python 代码吗?

第四章:使用 TPOT 探索分类

在本章中,你将继续学习自动化机器学习的实际示例。你将通过三个完整的数据集,学习如何以自动化的方式使用 TPOT 处理分类任务。

我们将涵盖如数据集加载、清理、必要的数据准备和探索性数据分析等基本主题。然后,我们将深入探讨使用 TPOT 的分类。你将学习如何训练和评估自动化分类模型。

在自动训练模型之前,你将看到如何通过基本分类算法,如逻辑回归,获得良好的模型。这个模型将作为 TPOT 需要超越的基准。

本章将涵盖以下主题:

  • 将自动化分类建模应用于 Iris 数据集

  • 将自动化分类建模应用于 Titanic 数据集

技术要求

为了完成本章,你需要在计算机上安装 Python 和 TPOT。有关环境设置的详细说明,请参阅第二章深入 TPOT。如果你对分类的概念完全陌生,请参阅第一章机器学习和自动化思想

你可以在此处下载本章的源代码和数据集:github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT/tree/main/Chapter04

将自动化分类模型应用于 Iris 数据集

让我们从最基础的、最基本的数据集之一——Iris 数据集(en.wikipedia.org/wiki/Iris_flower_data_set)开始。这里的挑战不是构建一个自动化模型,而是构建一个能够超越基准模型的模型。Iris 数据集如此简单,以至于即使是最基本的分类算法也能达到高准确率。

由于这个原因,你应该在这个部分专注于掌握分类的基础知识。你将有足够的时间担心性能问题:

  1. 与回归部分一样,你应该做的第一件事是导入所需的库并加载数据集。一开始你需要numpypandasmatplotlibseaborn。导入matplotlib.rcParams`模块以调整默认样式。

    下面是库导入和数据集加载的代码片段:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    from matplotlib import rcParams
    rcParams['axes.spines.top'] = False
    rcParams['axes.spines.right'] = False
    df = pd.read_csv('data/iris.csv')
    df.head()
    

    下面是head()函数返回的输出:

    图 4.1 – Iris 数据集的头部

    图 4.1 – Iris 数据集的头部

    太好了——这正是我们开始所需的。

  2. 下一步是检查数据质量是否足够好,可以传递给机器学习算法。这里的第一个步骤是检查缺失值。以下代码片段正是这样做的:

    df.isnull().sum()
    

    输出显示在以下图中:

    ![图 4.2 – 爱丽丝数据集每列缺失值计数 图片

    图 4.2 – 爱丽丝数据集每列缺失值计数

    看起来没有缺失值,因此我们可以继续。

  3. 现在我们来检查目标变量的类别分布。这指的是属于每个类别的实例数量——在这个例子中是setosavirginicaversicolor。已知如果存在严重的类别不平衡,机器学习模型的表现会较差。

    以下代码片段可视化类别分布:

    ax = df.groupby('species').count().plot(kind='bar', figsize=(10, 6), fontsize=13, color='#4f4f4f')
    ax.set_title('Iris Dataset target variable distribution', size=20, pad=30)
    ax.set_ylabel('Count', fontsize=14)
    ax.set_xlabel('Species', fontsize=14)
    ax.get_legend().remove()
    

    可视化显示在以下图中:

    ![图 4.3 – 爱丽丝数据集目标变量分布 图片

    图 4.3 – 爱丽丝数据集目标变量分布

    爱丽丝数据集非常完美——所以,在准备方面,我们再次没有什么可做的。

  4. 数据探索分析和准备的最后一步是检查相关性。特征之间的高度相关性通常意味着数据集中存在一些冗余,至少在某种程度上。

    以下代码片段绘制了一个带有注释的相关矩阵:

    plt.figure(figsize=(12, 9))
    plt.title('Correlation matrix', size=20)
    sns.heatmap(df.corr(), annot=True, cmap='Blues');
    

    相关矩阵显示在以下图中:

    ![图 4.4 – 爱丽丝数据集的相关矩阵 图片

    图 4.4 – 爱丽丝数据集的相关矩阵

    如预期的那样,大多数特征之间存在强烈的关联。

    现在,你已经熟悉了爱丽丝数据集,这意味着我们可以继续进行建模。

  5. 首先,让我们使用逻辑回归算法构建一个基线模型。它将作为一个起始模型,TPOT 需要超越它。

    该过程的第一步是训练/测试集划分。下面的代码片段正是这样做的,并且还打印了两个集合中的实例数量:

    from sklearn.model_selection import train_test_split
    X = df.drop('species', axis=1)
    y = df['species']
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.25, random_state=3
    )
    y_train.shape, y_test.shape
    

    实例数量显示在以下图中:

    ![图 4.5 – 训练集和测试集中的实例数量 图片

    图 4.5 – 训练集和测试集中的实例数量

    接下来,让我们构建基线模型。

  6. 如前所述,我们将使用逻辑回归来完成这项工作。下面的代码片段拟合了一个逻辑回归模型,在测试集上进行了预测,并打印了实际值和预测值的混淆矩阵:

    from sklearn.linear_model import LogisticRegression
    from sklearn.metrics import confusion_matrix
    lm = LogisticRegression(random_state=42)
    lm.fit(X_train, y_train)
    lm_preds = lm.predict(X_test)
    print(confusion_matrix(y_test, lm_preds))
    

    相应的混淆矩阵显示在以下图中:

    ![图 4.6 – 爱丽丝数据集的逻辑回归混淆矩阵 图片

    from sklearn.metrics import accuracy_score
    print(accuracy_score(y_test, lm_preds))
    

    准确率得分显示在以下图像中:

    ![图 4.7 – 使用逻辑回归在爱丽丝数据集测试集上的准确率 图片

    图 4.7 – 使用逻辑回归在爱丽丝数据集测试集上的准确率

    如此一来——97%的准确率,并且只有一个错误分类,这是使用最简单的分类算法。让我们看看 TPOT 是否能在下一步超越它。

  7. 让我们接下来构建一个自动分类模型。我们将优化准确率并训练 10 分钟——类似于我们在第三章使用 TPOT 探索回归中所做的。下面的代码片段导入 TPOT,实例化一个管道优化器,并在训练数据集上训练模型:

    from tpot import TPOTClassifier
    pipeline_optimizer = TPOTClassifier(
        scoring='accuracy',
        max_time_mins=10,
        random_state=42,
        verbosity=2
    )
    pipeline_optimizer.fit(X_train, y_train)
    

    TPOT 在我的机器上成功拟合了 18 代,如下所示:

    图 4.8 – 对 Iris 数据集进行 TPOT 管道优化的输出

  8. 让我们看看训练一个自动模型是否能够提高准确性。您可以使用以下代码片段来获取准确率分数:

    tpot_preds = pipeline_optimizer.predict(X_test)
    accuracy_score(y_test, tpot_preds)
    

    准确率分数如下所示:

    图 4.9 – 使用自动模型对 Iris 数据集测试集的准确性

    图 4.9 – 使用自动模型对 Iris 数据集测试集的准确性

    如您所见,测试集上的准确率没有提高。如果您绘制目标变量和特征之间的散点图,您会看到virginicaversicolor类之间有一些重叠。这很可能是这种情况,而且无论训练多久都无法正确分类这个单独的实例。

  9. 这里只剩下两件事要做,而且都是可选的。第一件事是查看 TPOT 在 10 分钟训练后宣布的最佳管道。以下代码片段将输出该管道到控制台:

    pipeline_optimizer.fitted_pipeline_
    

    相应的管道如下所示:

    图 4.10 – Iris 数据集的优化 TPOT 管道

    图 4.10 – Iris 数据集的优化 TPOT 管道

  10. 如同往常,您也可以使用export()函数导出管道:

    pipeline_optimizer.export('iris_pipeline.py')
    

    整个 Python 代码如下所示:

图 4.11 – Iris 数据集优化 TPOT 管道的 Python 代码

图 4.11 – Iris 数据集优化 TPOT 管道的 Python 代码

由此,您就拥有了一个使用 TPOT 的完全自动化的分类模型。是的,数据集非常基础,但原则始终如一。我们将在更复杂的数据集上构建自动模型,这样您就有机会深入研究了。

将自动分类建模应用于 Titanic 数据集

我们现在将应用自动 TPOT 分类建模到一个稍微复杂一些的数据集上。您将有机会使用 Titanic 数据集(gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv)——一个包含幸存和未幸存的乘客的各种属性和描述的数据集。

目标是构建一个自动化的模型,能够根据各种输入特征预测乘客是否会在事故中幸存,例如乘客等级、性别、年龄、船舱、兄弟姐妹、配偶、父母和孩子的数量,以及其他特征。

我们接下来将加载库和数据集:

  1. 和往常一样,第一步是加载库和数据集。您需要numpypandasmatplotlibseaborn来开始。Matplotlib.rcParams模块也被导入,只是为了使可视化更加吸引人。

    以下代码片段导入库,加载数据集,并显示前五行:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    from matplotlib import rcParams
    rcParams['axes.spines.top'] = False
    rcParams['axes.spines.right'] = False
    df = pd.read_csv('data/titanic.csv')
    df.head()
    

    调用head()函数返回数据集的前五行。它们在以下图中显示:

    ![图 4.12 – 泰坦尼克号数据集的前五行 图片

    图 4.12 – 泰坦尼克号数据集的前五行

    您现在可以继续进行探索性数据分析和准备。

  2. 探索性数据分析与准备的第一步是检查缺失值。以下代码行正是如此:

    df.isnull().sum()
    

    上一行代码报告了数据集中每列的缺失值数量,如下所示:

    ![图 4.13 – 泰坦尼克号数据集每列缺失值计数 图片

    图 4.13 – 泰坦尼克号数据集每列缺失值计数

    如您所见,数据集中存在许多缺失值。大多数缺失值出现在AgeCabin属性中。对于Cabin来说很容易理解——如果乘客没有自己的船舱,则值会缺失。

    我们稍后会处理这些缺失值,但现在,让我们将重点转向数据可视化,这样您可以更好地理解数据集。

  3. 为了避免代码重复,让我们定义一个单独的函数来显示条形图。该函数显示条形图,条形上方有列计数。它还允许您指定要为哪个数据集列绘制条形图,标题的值,x轴标签,以及y轴标签,还有计数的偏移量。

    您可以在此处找到该函数的代码:

    def make_bar_chart(column, title, ylabel, xlabel, y_offset=10, x_offset=0.2):
        ax = df.groupby(column).count()[['PassengerId']].plot(
            kind='bar', figsize=(10, 6), fontsize=13, color='#4f4f4f'
        )
        ax.set_title(title, size=20, pad=30)
        ax.set_ylabel(ylabel, fontsize=14)
        ax.set_xlabel(xlabel, fontsize=14)
        ax.get_legend().remove()
    
        for i in ax.patches:
            ax.text(i.get_x() + x_offset, i.get_height() + y_offset, i.get_height(), fontsize=20)
        return ax
    

    您将在接下来的几页中广泛使用此函数。目标是可视化分类变量的分布,以便您更好地理解数据集。

  4. 首先,让我们可视化有多少乘客幸存,有多少没有幸存。之前声明的make_bar_chart()函数对这项工作很有帮助。

    以下代码片段进行可视化:

    make_bar_chart(
        column='Survived',
        title='Distribution of the Survived variable',
        ylabel='Count',
        xlabel='Has the passenger survived? (0 = No, 1 = Yes)'
    );
    

    可视化显示在以下图中:

    ![图 4.14 – 泰坦尼克号数据集的目标类别分布 图片

    图 4.14 – 泰坦尼克号数据集的目标类别分布

    如您所见,大多数乘客在泰坦尼克号事故中没有幸存。仅凭这一信息并不能告诉你太多,因为你不知道每个性别、乘客舱位和其他属性的乘客中有多少人幸存。

    你可以使用 make_bar_chart() 函数来创建此类可视化。

  5. 让我们继续我们的数据可视化之旅,通过可视化每个乘客舱位的乘客数量。你可以使用相同的 make_bar_chart() 函数进行此可视化。只需确保相应地更改参数。

    以下代码片段可视化了每个乘客舱位的乘客数量。舱位号码越低,越好——票价更高,服务更好,也许生存的机会也更高:

    make_bar_chart(
        column='Pclass',
        title='Distirbution of the Passenger Class variable',
        ylabel='Count',
        xlabel='Passenger Class (smaller is better)',
        x_offset=0.15
    );
    

    可视化显示在下图中:

    图 4.15 – 按乘客舱位划分的乘客数量

    图 4.15 – 按乘客舱位划分的乘客数量

    如您所见,大多数乘客属于三等舱。这是预期的,因为船上的工人比富人多。

  6. 在数据可视化阶段的下一步,让我们看看 Sex 属性是如何分布的。这将让我们了解船上有更多的女性还是男性,以及差异有多大。

    以下代码片段创建了此可视化:

    make_bar_chart(
        column='Sex',
        title='Distirbution of the Sex variable',
        ylabel='Count',
        xlabel='Gender'
    );
    

    可视化显示在下图中:

    图 4.16 – 按性别划分的乘客数量

    图 4.16 – 按性别划分的乘客数量

    如您所见,船上的男性肯定更多。这与前一个可视化中得出的结论有关,我们得出结论,船上有许多工人。

    大多数工人是男性,所以这种可视化是有意义的。

  7. 让我们从条形图休息一下,可视化一个连续变量以展示变化。目标是创建 Fare 属性的直方图,这将显示支付船票金额的分布。

    以下代码片段为所提到的属性绘制直方图:

    plt.figure(figsize=(12, 7))
    plt.title('Fare cost distribution', size=20)
    plt.xlabel('Cost', size=14)
    plt.ylabel('Count', size=14)
    plt.hist(df['Fare'], bins=15, color='#4f4f4f', ec='#040404');
    

    直方图显示在下图中:

    图 4.17 – 船票变量分布

    图 4.17 – 船票变量分布

    看起来大多数乘客支付了 30 美元或更少的船票。像往常一样,总有一些极端情况。似乎有一位乘客支付了大约 500 美元的旅行费用。考虑到事情的结果,这并不是一个明智的决定。

  8. 现在我们来做点不同的事情。Name 属性在这个格式中或多或少是无用的。但如果你仔细观察,你可以看到提到的属性中的每个值都是格式化的。

    这意味着我们可以保留第一个逗号之后的单字并将其存储在一个新变量中。我们将这个变量称为 Title,因为它代表乘客头衔(例如,先生、小姐等)。

    下面的代码片段提取了头衔值到一个新的属性,并使用make_bar_chart()函数来直观地表示泰坦尼克号乘客中的不同头衔:

    df['Title'] = df['Name'].apply(lambda x: x.split(',')[1].strip().split(' ')[0])
    make_bar_chart(
        column='Title',
        title='Distirbution of the Passenger Title variable',
        ylabel='Count',
        xlabel='Title',
        x_offset=-0.2
    );
    

    结果显示在下述图中:

    图 4.18 – 乘客头衔分布

    图 4.18 – 乘客头衔分布

    这些是预期的结果。大多数乘客都有常见的头衔,例如先生和小姐。只有少数人拥有独特的头衔。您可以保留这个列不变,或者将其转换为二进制列——如果头衔是常见的,则值为零,否则为之一。您将在下一部分看到如何做到这一点。

  9. 关于探索性数据分析,就到这里吧。我们已经做了很多可视化,但您总是可以自己再做更多。

    现在是时候为机器学习准备数据集了。步骤在这里描述:

    a) 删除无用的列——TicketPassengerId。第一个只是一个假字母和数字的集合,对预测建模没有用。第二个是一个任意 ID,很可能是用数据库序列生成的。可以通过调用drop()函数删除这两个。

    b) 将Sex属性中的值重新映射为整数。文本值malefemale不能直接传递给机器学习算法。某种形式的转换是必须的——因此用 0 替换男性,用 1 替换女性。replace()函数是这项工作的完美候选人。

    c) 使用之前生成的Title列并将其转换为二进制列——如果头衔是常见的(例如,先生、小姐和夫人),则值为零,否则为之一。然后可以将该列重命名为更合适的东西,例如Title_UnusalName列不再需要,因此可以删除它。

    d) 通过将此属性转换为二进制列来处理Cabin列中的缺失值——如果船舱的值为缺失,则值为零,否则为之一。将这个新列命名为Cabin_Known。之后,可以删除Cabin列,因为它不再需要,并且不能传递给机器学习模型。

    e) 使用Embarked属性创建虚拟变量。该属性表示乘客进入船的港口。您将是判断这个属性是否必要的法官,但我们将保留它供 TPOT 决定。在声明虚拟变量后,将它们连接到原始数据集,并删除Embarked列。

    f) 以某种方式处理Age属性中的缺失值。有许多复杂的方法,例如KNN 插补MissForest 插补,但为了简单起见,只需用简单平均值来插补缺失值。

    下面的代码片段显示了如何应用所有提到的转换:

    df.drop(['Ticket', 'PassengerId'], axis=1, inplace=True)
    gender_mapper = {'male': 0, 'female': 1}
    df['Sex'].replace(gender_mapper, inplace=True)
    df['Title'] = [0 if x in ['Mr.', 'Miss.', 'Mrs.'] else 1 for x in df['Title']]
    df = df.rename(columns={'Title': 'Title_Unusual'})
    df.drop('Name', axis=1, inplace=True)
    df['Cabin_Known'] = [0 if str(x) == 'nan' else 1 for x in df['Cabin']]
    df.drop('Cabin', axis=1, inplace=True)
    emb_dummies = pd.get_dummies(df['Embarked'], drop_first=True, prefix='Embarked')
    df = pd.concat([df, emb_dummies], axis=1)
    df.drop('Embarked', axis=1, inplace=True)
    df['Age'] = df['Age'].fillna(int(df['Age'].mean()))
    df.head()
    

    您可以通过查看下述图来预览准备好的数据集:

    图 4.19 – 准备好的泰坦尼克号数据集

    图 4.19 – 准备好的泰坦尼克号数据集

    关于数据准备,这就是你需要做的。不需要缩放/标准化,因为 TPOT 将决定这一步是否必要。

    我们很快就会开始预测建模——只剩下一步了。

  10. 在你能够训练一个分类模型之前,你必须将数据集分成训练和测试子集。记住 random_state 参数——如果你想得到相同的数据分割,请使用相同的值:

    from sklearn.model_selection import train_test_split
    X = df.drop('Survived', axis=1)
    y = df['Survived']
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.25, random_state=42
    )
    y_train.shape, y_test.shape
    

    最后一条代码行打印了训练和测试子集中的实例数量。你可以在以下图中看到这些数字:

    图 4.20 – 训练集和测试集中实例的数量(泰坦尼克号)

    图 4.20 – 训练和测试集中实例的数量(泰坦尼克号)

    现在,你已经准备好训练预测模型了。

  11. 让我们从基线模型开始——逻辑回归。我们将在训练集上训练它,并在测试集上评估它。以下代码片段训练模型并打印混淆矩阵:

    from sklearn.linear_model import LogisticRegression
    from sklearn.metrics import confusion_matrix
    lm = LogisticRegression(random_state=42)
    lm.fit(X_train, y_train)
    lm_preds = lm.predict(X_test)
    print(confusion_matrix(y_test, lm_preds))
    

    你可以在以下图中看到混淆矩阵:

    图 4.21 – 逻辑回归混淆矩阵(泰坦尼克号)

    图 4.21 – 逻辑回归混淆矩阵(泰坦尼克号)

    看起来错误肯定和错误否定(23)的数量相同。如果我们考虑比率,错误否定更多。在翻译中,基线模型更有可能说乘客幸存了,即使他们没有。

  12. 解读混淆矩阵很好,但如果你想看一个具体的数字呢?由于这是一个分类问题,你可以使用准确率。但有一个“更好”的指标——F1 分数。这个指标的值在 0 到 1 之间(越高越好),它代表了精确率和召回率之间的调和平均值。

    下面是如何用 Python 计算它的示例:

    from sklearn.metrics import f1_score
    print(f1_score(y_test, lm_preds))
    

    测试集上 F1 分数的值显示在以下图中:

    图 4.22 – 测试集中逻辑回归 F1 分数(泰坦尼克号)

    图 4.22 – 测试集中逻辑回归 F1 分数(泰坦尼克号)

    对于基线模型来说,0.74 的分数并不差。TPOT 能否超越它?让我们训练一个自动模型看看会发生什么。

  13. 与之前类似,我们将训练一个自动分类模型 10 分钟。我们将优化 F1 分数而不是准确率。通过这样做,我们可以比较自动模型和基线模型的 F1 分数。

    以下代码片段在训练集上训练模型:

    from tpot import TPOTClassifier
    pipeline_optimizer = TPOTClassifier(
        scoring='f1',
        max_time_mins=10,
        random_state=42,
        verbosity=2
    )
    pipeline_optimizer.fit(X_train, y_train)
    

    在以下图中,你可以看到训练期间在笔记本中打印的输出。TPOT 在 10 分钟内成功训练了 7 代,随着模型的训练,分数在增加:

    图 4.23 – TPOT 管道优化输出(泰坦尼克号)

    图 4.23 – TPOT 管道优化输出(泰坦尼克号)

    您可以自由地将模型训练时间延长至 10 分钟以上。尽管如此,这个时间框架应该足以优于基线模型。

  14. 让我们看看测试集中 F1 分数的值。记住,任何高于 0.7415 的值都意味着 TPOT 的表现优于基线模型。

    以下代码片段打印出 F1 分数:

    pipeline_optimizer.score(X_test, y_test)
    

    对应的 F1 分数如下所示:

    图 4.24 – TPOT 优化模型在测试集上的 F1 分数(泰坦尼克号)

    图 4.24 – TPOT 优化模型在测试集上的 F1 分数(泰坦尼克号)

    看起来 TPOT 的表现优于基线模型——正如预期的那样。

  15. 如果您更信任基本指标,例如准确率,以下是您如何在基线模型和自动化模型之间进行比较的方法:

    tpot_preds = pipeline_optimizer.predict(X_test)
    from sklearn.metrics import accuracy_score
    print(f'Baseline model accuracy: {accuracy_score(y_test, lm_preds)}')
    print(f'TPOT model accuracy: {accuracy_score(y_test, tpot_preds)}')
    

    对应的准确率分数如下所示:

    图 3.25 – 基线模型和 TPOT 优化模型在测试集上的准确率(泰坦尼克号)

    图 3.25 – 基线模型和 TPOT 优化模型在测试集上的准确率(泰坦尼克号)

    如您所见,简单的准确率指标讲述了一个相似的故事——由 TPOT 构建的模型仍然优于基线模型。

  16. 我们接近这个实战例子的尾声。还有两个可选的事情要做。第一个是查看最佳管道。您可以使用以下代码行获取它:

    pipeline_optimizer.fitted_pipeline_
    

    最佳管道如下所示:

    图 4.26 – TPOT 优化后的管道(泰坦尼克号)

    图 4.26 – TPOT 优化后的管道(泰坦尼克号)

    如您所见,TPOT 使用极端梯度提升来解决这个分类问题。

  17. 最后,您可以将最佳管道转换为 Python 代码。这样做使得代码共享的过程变得更加容易。您可以在以下位置找到相应的代码:

    pipeline_optimizer.export('titanic_pipeline.py')
    

    自动化管道的完整源代码如下所示:

图 4.27 – 优化后的 TPOT 管道源代码(泰坦尼克号)

图 4.27 – 优化后的 TPOT 管道源代码(泰坦尼克号)

这样,我们就完成了在泰坦尼克号数据集上以自动化方式解决分类问题的任务。您现在已经构建了两个完全自动化的分类机器学习解决方案。让我们接下来总结这一章。

摘要

这是本书的第二个实战章节。您已经学会了如何使用两个深入实例在知名数据集上以自动化方式解决分类机器学习任务。毫无疑问,您现在可以使用 TPOT 解决任何类型的分类问题。

到现在为止,你已经知道如何解决回归和分类任务。但关于并行训练和神经网络呢?接下来的第五章,第五章**,使用 TPOT 和 Dask 的并行训练,将教你什么是并行训练以及如何使用 TPOT 来实现它。稍后,在第六章深度学习入门 – 神经网络快速课程中,你将巩固你对基本深度学习和神经网络的知识。作为甜点,你将在第七章使用 TPOT 的神经网络分类器中学习如何使用 TPOT 进行深度学习。

请鼓励自己使用本章介绍的工具和技术自动解决分类问题。

问答

  1. 你能探索分类变量的分布情况吗?请解释。

  2. 解释混淆矩阵以及真阳性、真阴性、假阳性和假阴性的术语。

  3. 什么是精确率?请通过一个实际例子来解释。

  4. 什么是召回率?请通过一个实际例子来解释。

  5. 准确率和 F1 分数有什么区别?在什么情况下你会使用 F1 而不是准确率?

  6. F1 分数中的“1”代表什么?这个数字可以被改变吗?在那个情况下会发生什么?

  7. 在训练过程中,TPOT 输出的是训练集还是测试集的评分指标值?请解释。

第五章:使用 TPOT 和 Dask 进行并行训练

在本章中,你将深入探讨一个更高级的话题;那就是自动化机器学习。你将学习如何通过在 Dask 集群上分配工作来以并行方式处理机器学习任务。本章将比前两章更具理论性,但你仍然会学到许多有用的东西。

我们将涵盖 Python 并行背后的基本主题和思想,你将学习以几种不同的方式实现并行。然后,我们将深入探讨 Dask 库,探索其基本功能,并了解如何将其与 TPOT 结合使用。

本章将涵盖以下主题:

  • Python 并行编程简介

  • Dask 库简介

  • 使用 TPOT 和 Dask 训练机器学习模型

让我们开始吧!

技术要求

在阅读和理解这一章之前,你不需要有任何关于 Dask 或甚至并行编程的经验。虽然将如此大的概念压缩到几页纸上几乎是不可能的,但你应该仍然能够跟上并完全理解这里所写的一切,因为所有概念都将得到解释。

你可以在此处下载本章的源代码和数据集:github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT/tree/main/Chapter05

Python 并行编程简介

在某些情况下,需要按顺序执行任务(即第二个任务在第一个任务完成后开始)。例如,第二个函数的输入可能依赖于第一个函数的输出。如果是这样,这两个函数(进程)不能同时执行。

但大多数情况下并非如此。想象一下,在仪表盘显示之前,你的程序正在连接到三个不同的 API 端点。第一个 API 返回当前的天气状况,第二个返回股票价格,最后一个返回今天的汇率。一个接一个地调用 API 没有意义。它们之间没有依赖关系,所以按顺序运行它们会浪费大量时间。

不仅如此,这还会浪费 CPU 核心。大多数现代电脑至少有四个 CPU 核心。如果你是按顺序运行任务的,你只在使用一个核心。为什么不能使用所有这些核心呢?

在 Python 中实现并行的一种方式是使用多进程。这是一种基于进程的并行技术。正如你所想象的那样,Python 内置了一个 multiprocessing 库,本节将教你如何使用它。从 Python 3.2 及更高版本开始,这个库不再被推荐用于在应用程序中实现多进程。有一个新的库出现了,它的名字叫 concurrent.futures。这又是另一个你将在本节中学习如何使用的内置库。

解释和理解多进程的最简单方式是使用 Python 的内置 time 库。你可以用它来跟踪时间差异,以及故意暂停程序执行等。这正是我们所需要的,因为我们可以在它们之间插入一些时间间隔的打印语句,然后观察程序在顺序执行和并行执行时的行为。

你将通过几个动手实例了解 Python 中多进程的工作方式。

首先,请查看以下代码片段。在这个片段中,已经声明了 sleep_func() 函数。它的任务是打印一条消息,暂停程序执行 1 秒,然后在函数完成时打印另一条消息。我们可以监控这个函数运行任意次数(比如说五次)并打印出执行时间。代码片段如下:

import time 
def sleep_func():
    print('Sleeping for a 1 second')
    time.sleep(1)
    print('Done.')
if __name__ == '__main__':
    time_start = time.time()
    # Run the function 5 times
    sleep_func()
    sleep_func()
    sleep_func()
    sleep_func()
    sleep_func()
    time_stop = time.time()
    print(f'Took {round(time_stop - time_start, 2)} seconds to execute!')

相应的输出如下所示:

Sleeping for a 1 second
Done.
Sleeping for a 1 second
Done.
Sleeping for a 1 second
Done.
Sleeping for a 1 second
Done.
Sleeping for a 1 second
Done.
Took 5.02 seconds to execute!

那么,这里发生了什么?至少说,没有什么意外。sleep_func() 函数按顺序执行了五次。执行时间大约为 5 秒。你也可以以下面的方式简化前面的代码片段:

import time 
def sleep_func():
    print('Sleeping for a 1 second')
    time.sleep(1)
    print('Done.')
if __name__ == '__main__':
    time_start = time.time()
    # Run the function 5 times in loop
    for _ in range(5):
        sleep_func()
    time_stop = time.time()
    print(f'Took {round(time_stop - time_start, 2)} seconds to execute!')

结果与你预期的一样:

Sleeping for a 1 second
Done.
Sleeping for a 1 second
Done.
Sleeping for a 1 second
Done.
Sleeping for a 1 second
Done.
Sleeping for a 1 second
Done.
Took 5.01 seconds to execute!

这种方法有什么问题吗?嗯,是的。我们浪费了时间和 CPU 核心。这些函数在某种程度上并不依赖,那么我们为什么不在并行中运行它们呢?正如我们之前提到的,有两种方法可以做到这一点。让我们首先检查通过 multiprocessing 库的老方法。

这种方法有点繁琐,因为它需要声明一个进程、启动它并加入它。如果你只有几个进程,这并不那么麻烦,但如果你程序中有成百上千个进程呢?这很快就会变得繁琐。

以下代码片段演示了如何并行运行 sleep_func() 函数三次:

import time 
from multiprocessing import Process
def sleep_func():
    print('Sleeping for a 1 second')
    time.sleep(1)
    print('Done.')
if __name__ == '__main__':
    time_start = time.time()
    process_1 = Process(target=sleep_func)
    process_2 = Process(target=sleep_func)
    process_3 = Process(target=sleep_func)
    process_1.start()
    process_2.start()
    process_3.start()
    process_1.join()
    process_2.join()
    process_3.join()
    time_stop = time.time()
    print(f'Took {round(time_stop - time_start, 2)} seconds to  execute!')

输出如下所示:

Sleeping for a 1 second
Sleeping for a 1 second
Sleeping for a 1 second
Done.
Done.
Done.
Took 1.07 seconds to execute!

如你所见,三个进程都是独立且并行启动的,因此它们都成功地在同一秒内完成了任务。

Process()start() 都是显而易见的,但 join() 函数在做什么呢?简单来说,它告诉 Python 等待进程完成。如果你在所有进程中调用 join(),则最后两行代码不会执行,直到所有进程都完成。为了好玩,尝试移除 join() 调用;你会立即明白其含义。

你现在对多进程有了基本的直觉,但故事还没有结束。Python 3.2 引入了一种新的、改进的并行执行任务的方法。concurrent.futures 库是目前最好的库,你将学习如何使用它。

使用它,你不需要手动管理进程。每个执行过的函数都会返回一些东西,在我们的sleep_func()函数中是None。你可以通过返回最后一个语句而不是打印它来改变它。此外,这种新的方法使用ProcessPoolExecutor()来运行。你不需要了解任何关于它的东西;只需记住,它用于同时执行多个进程。从代码的角度来看,简单地将你想要并行运行的所有内容放在里面。这种方法解锁了两个新的函数:

  • submit(): 用于并行运行函数。返回的结果将被追加到列表中,这样我们就可以在下一个函数中打印它们(或做任何其他事情)。

  • result(): 用于从函数中获取返回值。我们只需简单地打印结果,但你也可以做任何其他事情。

总结一下,我们将结果追加到列表中,然后在函数执行完毕后打印它们。以下代码片段展示了如何使用最新的 Python 方法实现多进程:

import time 
import concurrent.futures
def sleep_func():
    print('Sleeping for a 1 second')
    time.sleep(1)
    return 'Done.'
if __name__ == '__main__':
    time_start = time.time()
    with concurrent.futures.ProcessPoolExecutor() as ppe:
        out = []
        for _ in range(5):
            out.append(ppe.submit(sleep_func))
        for curr in concurrent.futures.as_completed(out):
            print(curr.result())
    time_stop = time.time()
    print(f'Took {round(time_stop - time_start, 2)} seconds to execute!')

结果如下所示:

Sleeping for a 1 second
Sleeping for a 1 second
Sleeping for a 1 second
Sleeping for a 1 second
Sleeping for a 1 second
Done.
Done.
Done.
Done.
Done.
Took 1.17 seconds to execute!

如你所见,程序的行为与我们之前所做的一样,增加了一些好处——你不需要自己管理进程,而且语法更加简洁。

我们目前遇到的一个问题是缺少函数参数。目前,我们只是调用一个不接受任何参数的函数。这种情况在大多数时候都不会发生,因此尽早学习如何处理函数参数是很重要的。

我们将向sleep_func()函数引入一个参数,允许我们指定执行将被暂停多长时间。函数内的打印语句相应地更新。暂停时间在sleep_seconds列表中定义,并在每次迭代中将该值作为第二个参数传递给append()

整个代码片段如下所示:

import time 
import concurrent.futures
def sleep_func(how_long: int):
    print(f'Sleeping for a {how_long} seconds')
    time.sleep(how_long)
    return f'Finished sleeping for {how_long} seconds.'
if __name__ == '__main__':
    time_start = time.time()
    sleep_seconds = [1, 2, 3, 1, 2, 3]
    with concurrent.futures.ProcessPoolExecutor() as ppe:
        out = []
        for sleep_second in sleep_seconds:
            out.append(ppe.submit(sleep_func, sleep_second))
        for curr in concurrent.futures.as_completed(out):
            print(curr.result())
    time_stop = time.time()
    print(f'Took {round(time_stop - time_start, 2)} seconds to execute!')

结果如下所示:

Sleeping for 1 seconds
Sleeping for 2 seconds
Sleeping for 3 seconds
Sleeping for 1 seconds
Sleeping for 2 seconds
Sleeping for 3 seconds
Finished sleeping for 1 seconds.
Finished sleeping for 1 seconds.
Finished sleeping for 2 seconds.
Finished sleeping for 2 seconds.
Finished sleeping for 3 seconds.
Finished sleeping for 3 seconds.
Took 3.24 seconds to execute!

这样你就可以在并行处理中处理函数参数了。请记住,每个机器上的执行时间可能不会完全相同,因为运行时间将取决于你的硬件。一般来说,你应该看到与脚本的非并行版本相比的速度提升。你现在已经了解了并行处理的基础。在下一节中,你将了解 Python 的 Dask 库是如何进入画面的,在接下来的章节中,你将结合并行编程、Dask 和 TPOT 来构建机器学习模型。

Dask 库简介

你可以将 Dask 视为数据规模处理中最革命的 Python 库之一。如果你是常规的 pandas 和 NumPy 用户,你会喜欢 Dask。这个库允许你处理 NumPy 和 pandas 不允许的数据,因为它们不适合 RAM。

Dask 支持 NumPy 数组和 pandas DataFrame 数据结构,所以你将很快熟悉它。它可以在你的电脑或集群上运行,这使得扩展变得更加容易。你只需要编写一次代码,然后选择你将运行它的环境。就这么简单。

另有一点需要注意,Dask 允许你通过最小的改动并行运行代码。正如你之前看到的,并行处理事物意味着执行时间减少,这是我们通常希望的行为。稍后,你将学习 Dask 中的并行性是如何通过 dask.delayed 实现的。

要开始,你必须安装这个库。确保正确的环境被激活。然后,从终端执行以下命令:

pipenv install "dask[complete]"

有其他安装选项。例如,你可以只安装数组或 DataFrames 模块,但最好从一开始就安装所有内容。不要忘记在库名称周围加上引号,否则会导致错误。

如果你已经安装了所有东西,你将能够访问三个 Dask 集合——数组、DataFrames 和 bags。所有这些都可以存储比你的 RAM 大的数据集,并且它们都可以在 RAM 和硬盘之间分区数据。

让我们从 Dask 数组开始,并与 NumPy 的替代品进行比较。你可以在 Notebook 环境中执行以下代码单元,创建一个具有 1,000x1,000x1,000 维度的 NumPy 单位数组。%%time 魔法命令用于测量单元格执行完成所需的时间:

%%time
import numpy as np
np_ones = np.ones((1000, 1000, 1000))

构建比这个更大的数组会导致我的机器出现内存错误,但这对比较来说已经足够了。相应的输出如下所示:

CPU times: user 1.86 s, sys: 2.21 s, total: 4.07 s
Wall time: 4.35 s

如你所见,创建这个数组花费了 4.35 秒。现在,让我们用 Dask 来做同样的事情:

%%time
import dask.array as da
da_ones = da.ones((1000, 1000, 1000))

如你所见,唯一的变化在于库导入名称。如果你第一次遇到 Dask 库,执行时间的结果可能会让你感到惊讶。它们在这里展示:

CPU times: user 677 µs, sys: 12 µs, total: 689 µs
Wall time: 696 µs 

是的,你确实在读这段话。Dask 创建了一个具有相同维度的数组,耗时 696 微秒,这比之前快了 6,250 倍。当然,你不应该期望在现实世界中执行时间会有这么大的减少,但差异仍然应该相当显著。

接下来,让我们看看 Dask DataFrames。语法应该再次感觉非常相似,所以你不需要花太多时间来学习这个库。为了完全展示 Dask 的功能,我们将创建一些大型数据集,这些数据集将无法适应单个笔记本电脑的内存。更准确地说,我们将创建 10 个基于时间序列的 CSV 文件,每个文件代表一年的数据,按秒聚合并通过五个不同的特征进行测量。这有很多,创建它们肯定需要一些时间,但最终你应该会有 10 个数据集,每个数据集大约有 1 GB 的大小。如果你像我一样有一个 8 GB RAM 的笔记本电脑,你根本无法将其放入内存中。

以下代码片段创建了这些数据集:

import pandas as pd
from datetime import datetime
for year in np.arange(2010, 2020):
    dates = pd.date_range(
        start=datetime(year=year, month=1, day=1),
        end=datetime(year=year, month=12, day=31),
        freq='S'
    )
    df = pd.DataFrame()
    df['Date'] = dates
    for i in range(5):
        df[f'X{i}'] = np.random.randint(low=0, high=100, size=len(df))

    df.to_csv(f'data/{year}.csv', index=False)
!ls data/

只需确保你的笔记本位于这个/data文件夹中,你就可以顺利开始了。另外,如果你要跟进度,请确保你有 10 GB 的磁盘空间。最后一行,!ls data/,列出了data文件夹中所有文件。你应该看到以下内容:

2010.csv 2012.csv 2014.csv 2016.csv 2018.csv
2011.csv 2013.csv 2015.csv 2017.csv 2019.csv

现在,让我们看看 pandas 读取单个 CSV 文件并执行简单聚合操作需要多少时间。更精确地说,数据集按月份分组,并提取总和。以下代码片段演示了如何进行此操作:

%%time
df = pd.read_csv('data/2010.csv', parse_dates=['Date'])
avg = df.groupby(by=df['Date'].dt.month).sum()

结果在这里显示:

CPU times: user 26.5 s, sys: 9.7 s, total: 36.2 s
Wall time: 42 s

如你所见,pandas 执行这个计算需要 42 秒。这并不算太糟糕,但如果你绝对需要加载所有数据集并执行计算呢?让我们接下来探索这个问题。

你可以使用glob库来获取指定文件夹中所需文件的路径。然后你可以单独读取它们,并使用 pandas 的concat()函数将它们堆叠在一起。聚合操作以相同的方式进行:

%%time
import glob
all_files = glob.glob('data/*.csv')
dfs = []
for fname in all_files:
    dfs.append(pd.read_csv(fname, parse_dates=['Date']))

df = pd.concat(dfs, axis=0)
agg = df.groupby(by=df['Date'].dt.year).sum()

这里没有太多可说的——笔记本只是简单地崩溃了。将 10 GB+的数据存储到 8 GB RAM 的机器中是不切实际的。你可以通过分块加载数据来解决这个问题,但这又是一个头疼的问题。

Dask 能做些什么来帮助呢?让我们学习如何使用 Dask 加载这些 CSV 文件并执行相同的聚合操作。你可以使用以下代码片段来完成:

%%time
import dask.dataframe as dd
df = dd.read_csv('data/*.csv', parse_dates=['Date'])
agg = df.groupby(by=df['Date'].dt.year).sum().compute()

结果将再次让你感到惊讶:

CPU times: user 5min 3s, sys: 1min 11s, total: 6min 15s
Wall time: 3min 41s

这是正确的——在不到 4 分钟内,Dask 成功地将超过 10 GB 的数据读取到 8 GB RAM 的机器上。仅此一点就足以让你重新考虑 NumPy 和 pandas,尤其是如果你正在处理大量数据或者你预计在不久的将来会处理数据。

最后,还有 Dask 的 bags。它们用于存储和处理无法放入内存的通用 Python 数据类型——例如,日志数据。我们不会探索这个数据结构,但了解它的存在是很好的。

另一方面,我们将使用 Dask 来探索并行处理的概念。在上一节中,你已经了解到没有有效的理由要按顺序处理数据或执行任何其他操作,因为一个操作的输入并不依赖于另一个操作的输出。

Dask 延迟允许并行执行。当然,你仍然可以依赖我们之前学到的多进程概念,但为什么还要这样做呢?这可能是一个繁琐的方法,而 Dask 有更好的解决方案。使用 Dask,你不需要改变编程语法,就像纯 Python 那样。你只需要使用@dask.delayed装饰器来注释你想并行化的函数,然后就可以开始了!

你可以将多个函数并行化,然后将它们放入计算图中。这正是我们接下来要做的。

以下代码片段声明了两个函数:

  • cube(): 返回一个数字的立方

  • multiply(): 将列表中的所有数字相乘并返回乘积

这里是你需要的库导入:

import time
import dask
import math
from dask import delayed, compute

让我们在五个数字上运行第一个函数,并在结果上调用第二个函数,看看会发生什么。注意cube()函数内部的time.sleep()调用。这将使我们在并行化和非并行化函数之间发现差异变得容易得多:

%%time
def cube(number: int) -> int:
    print(f'cube({number}) called!')
    time.sleep(1)
    return number ** 3
def multiply(items: list) -> int:
    print(f'multiply([{items}]) called!')
    return math.prod(items)
numbers = [1, 2, 3, 4, 5]
graph = multiply([cube(num) for num in numbers])
print(f'Total = {graph}')

这就是您的常规(顺序)数据处理。这没有什么问题,尤其是在操作如此少且简单的情况下。相应的输出如下:

cube(1) called!
cube(2) called!
cube(3) called!
cube(4) called!
cube(5) called!
multiply([[1, 8, 27, 64, 125]]) called!
Total = 1728000
CPU times: user 8.04 ms, sys: 4 ms, total: 12 ms
Wall time: 5.02 s

如预期的那样,由于顺序执行,代码单元运行了大约 5 秒钟。现在,让我们看看您需要对这些函数进行哪些修改以实现并行化:

%%time
@delayed
def cube(number: int) -> int:
    print(f'cube({number}) called!')
    time.sleep(1)
    return number ** 3
@delayed
def multiply(items: list) -> int:
    print(f'multiply([{items}]) called!')
    return math.prod(items)
numbers = [1, 2, 3, 4, 5]
graph = multiply([cube(num) for num in numbers])
print(f'Total = {graph.compute()}')

因此,只需要@delayed装饰器和在图上调用compute()。结果如下所示:

cube(3) called!cube(2) called!cube(4) called!
cube(1) called!
cube(5) called!
multiply([[1, 8, 27, 64, 125]]) called!
Total = 1728000
CPU times: user 6.37 ms, sys: 5.4 ms, total: 11.8 ms
Wall time: 1.01 s

如预期的那样,由于并行执行,整个过程仅用了不到一秒钟。之前声明的计算图还有一个方便的特性——它很容易可视化。您需要在您的机器上安装GraphViz,并将其作为 Python 库。对于每个操作系统,安装过程都不同,所以我们在这里不详细说明。快速 Google 搜索会告诉您如何安装它。一旦完成安装,您可以执行以下代码行:

graph.visualize()

对应的可视化显示如下:

![图 5.1 – Dask 计算图的可视化img/B16954_05_001.jpg

图 5.1 – Dask 计算图的可视化

如您从图中所见,cube()函数被并行调用五次,其结果存储在上面的桶中。然后,使用这些值调用multiply()函数,并将乘积存储在顶部的桶中。

关于 Dask 的基本知识,您需要了解的就是这些。您已经学会了如何使用 Dask 数组和数据框,以及如何使用 Dask 并行处理操作。不仅如此,您还了解了 Dask 在现代数据科学和机器学习中的关键作用。数据集的大小通常超过可用内存,因此需要现代解决方案。

在下一节中,您将学习如何使用 Dask 训练 TPOT 自动化机器学习模型。

使用 TPOT 和 Dask 训练机器学习模型

优化机器学习管道是首要的任务,这是一个耗时的过程。我们可以通过并行运行来显著缩短它。当与 TPOT 结合使用时,Dask 和 TPOT 工作得很好,本节将教会您如何在 Dask 集群上训练 TPOT 模型。不要让“集群”这个词吓到您,因为您的笔记本电脑或 PC 就足够了。

您需要安装一个额外的库才能继续,它被称为dask-ml。正如其名所示,它用于使用 Dask 进行机器学习。从终端执行以下命令来安装它:

pipenv install dask-ml

完成这些后,您可以打开 Jupyter Lab 或您喜欢的 Python 代码编辑器并开始编码。让我们开始:

  1. 让我们从库导入开始。我们还会在这里做出数据集的决定。这次,我们不会在数据清洗、准备或检查上花费任何时间。目标是尽快准备好数据集。scikit-learn 中的 load_digits() 函数很有用,因为它旨在获取许多 8x8 像素的数字图像以进行分类。

    由于一些库经常用不必要的警告填满你的屏幕,我们将使用 warnings 库来忽略它们。有关所有库导入的参考,请参阅以下代码片段:

    import tpot
    from tpot import TPOTClassifier
    from sklearn.datasets import load_digits
    from sklearn.model_selection import train_test_split
    from dask.distributed import Client
    import warnings
    warnings.filterwarnings('ignore')
    

    这里唯一的新事物是来自 dask.distributedClient 类。它用于与 Dask 集群(在这种情况下是你的计算机)建立连接。

  2. 你现在将创建一个客户端实例。这将立即启动 Dask 集群并使用你所有可用的 CPU 核心。以下是创建实例和检查集群运行位置的代码:

    client = Client()
    client
    

    执行后,你应该看到以下输出:

    ![Figure 5.2 – Dask 集群信息]

    ![img/B16954_05_002.jpg]

    ![Figure 5.2 – Dask 集群信息]

    你可以点击仪表板链接,它将带你到 http://127.0.0.1:8787/status。以下截图显示了仪表板首次打开时的样子(没有运行任务):

    ![Figure 5.3 – Dask 集群仪表板(无运行任务)]

    ![img/B16954_05_003.jpg]

    图 5.3 – Dask 集群仪表板(无运行任务)

    一旦开始训练模型,仪表板将变得更加多彩。我们将在下一步进行必要的准备。

  3. 你可以调用 load_digits() 函数来获取图像数据,然后使用 train_test_split() 函数将图像分割成训练和测试子集。在这个例子中,训练/测试比例是 50:50,因为我们不想在训练上花费太多时间。在几乎任何情况下,训练集的比例都应该更高,所以请确保记住这一点。

    分割完成后,你可以对子集调用 .shape 来检查它们的维度。以下是整个代码片段:

    digits = load_digits()
    X_train, X_test, y_train, y_test = train_test_split(
        digits.data,
        digits.target,
        test_size=0.5,
    )
    X_train.shape, X_test.shape
    

    相应的输出在以下图中显示:

    ![Figure 5.4 – 训练和测试子集的维度]

    ![img/B16954_05_004.jpg]

    图 5.4 – 训练和测试子集的维度

    下一个目标 – 模型训练。

  4. 你现在拥有使用 TPOT 和 Dask 训练模型所需的一切。你可以以与之前非常相似的方式这样做。这里的关键参数是 use_dask。如果你想使用 Dask 进行训练,你应该将其设置为 True。其他参数都是众所周知的:

    estimator = TPOTClassifier(
        n_jobs=-1,
        random_state=42,
        use_dask=True,
        verbosity=2,
        max_time_mins=10
    )
    

    现在,你已准备好调用 fit() 函数并在训练子集上训练模型。以下是执行此操作的代码行:

    estimator.fit(X_train, y_train)
    

    一旦开始训练模型,Dask 仪表板的界面将立即改变。以下是过程进行几分钟后的样子:

![Figure 5.5 – 训练期间的 Dask 仪表板]

![img/B16954_05_005.jpg]

图 5.5 – 训练期间的 Dask 仪表板

10 分钟后,TPOT 将完成管道优化,你将在你的笔记本中看到以下输出:

图 5.6 – TPOT 优化输出

图 5.6 – TPOT 优化输出

要将 TPOT 和 Dask 结合起来,你需要做的是所有这些。

你现在知道如何在 Dask 集群上训练模型,这是处理更大数据集和更具挑战性问题的推荐方法。

摘要

本章不仅包含了关于 TPOT 和以并行方式训练模型的信息,还包含了关于并行性的通用信息。你已经学到了很多——从如何并行化只做了一段时间休眠的基本函数,到并行化带有参数的函数和 Dask 基础知识,再到在 Dask 集群上使用 TPOT 和 Dask 训练机器学习模型。

到现在为止,你已经知道了如何以自动化的方式解决回归和分类任务,以及如何并行化训练过程。接下来的章节,第六章**,深度学习入门 – 神经网络速成课,将为你提供关于神经网络所需的知识。这将构成第七章**,使用 TPOT 的神经网络分类器*的基础,我们将深入探讨使用最先进的神经网络算法训练自动化机器学习模型。

和往常一样,请随意练习使用 TPOT 解决回归和分类任务,但这次,尝试使用 Dask 并行化这个过程。

问答

  1. 定义“并行性”这个术语。

  2. 解释哪些类型的任务可以并行化,哪些不可以。

  3. 列出并解释在应用程序中实现并行化的三种选项(所有这些都在本章中列出)。

  4. Dask 是什么?它为什么比 NumPy 和 pandas 在处理更大数据集时更优越?

  5. 名称并解释在 Dask 中实现的三种基本数据结构。

  6. Dask 集群是什么?

  7. 你需要做什么来告诉 TPOT 它应该使用 Dask 进行训练?

第三部分:TPOT 中的高级示例和神经网络

本节面向更高级的用户。介绍了如神经网络等主题,并在 TPOT 中构建神经网络分类器。最后,将预测模型作为 REST API 部署到本地和云端,并用于实时预测。

本节包含以下章节:

  • 第六章, 深度学习入门——神经网络速成课程

  • 第七章, 使用 TPOT 的神经网络分类器

  • 第八章, TPOT 模型部署

  • 第九章, 在生产中使用部署的 TPOT 模型

第六章:深度学习入门:神经网络快速课程

在本章中,你将学习深度学习和人工神经网络的基础知识。你将了解这些主题背后的基本思想和理论,以及如何使用 Python 训练简单的神经网络模型。本章将为后续章节提供一个极好的入门,在这些章节中,管道优化和神经网络的理念将结合在一起。

我们将涵盖深度学习背后的基本主题和思想,为什么它在过去几年中变得流行,以及神经网络比传统机器学习算法表现更好的案例。你还将获得实际操作经验,从零开始编写自己的神经网络,以及通过预制的库来实现。

本章将涵盖以下主题:

  • 深度学习概述

  • 介绍人工神经网络

  • 使用神经网络对手写数字进行分类

  • 比较回归和分类中的神经网络

技术要求

在深度学习和神经网络方面没有先前的经验是必要的。你应该能够仅从本章中理解基础知识。先前的经验是有帮助的,因为深度学习不是一次就能学会的。

你可以在此处下载本章的源代码和数据集:github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT/tree/main/Chapter06

深度学习概述

深度学习是机器学习的一个子领域,它专注于神经网络。神经网络作为一个概念并不新鲜——它们在 20 世纪 40 年代就被引入了,但直到它们开始在数据科学竞赛中获胜(大约在 2010 年左右),才获得了更多的流行。

深度学习和人工智能可能最有成效的一年是 2016 年,这一切都归功于一个单一事件。AlphaGo,一个玩围棋的计算机程序,击败了世界排名第一的选手。在此事件之前,围棋被认为是一种计算机无法掌握的游戏,因为存在如此多的潜在棋盘配置。

如前所述,深度学习基于神经网络。你可以将神经网络想象成有向无环图 – 由顶点(节点)和边(连接)组成的图。输入层(最左侧的第一层)接收来自数据集的原始数据,通过一个或多个隐藏层传递,并构建输出。

你可以在以下图表中看到神经网络的一个示例架构:

图 6.1 – 一个示例神经网络架构

图 6.1 – 一个示例神经网络架构

最左侧的小黑点表示输入数据——直接来自你的数据集的数据。然后这些值通过各自的权重和偏差与隐藏层连接。这些权重和偏差的常见称呼是可调参数。我们将在下一节中讨论这个术语并展示如何计算它们。

神经网络中的每个节点都称为神经元。让我们看看单个神经元的架构:

图 6.2 – 单个神经元

图 6.2 – 单个神经元

X 值对应于输入层或前一隐藏层的值。这些值相乘(x1 * w1x2 * w2)然后相加(x1w1 + x2w2)。在求和之后,添加一个偏差项,最后,所有内容都通过一个激活函数。这个函数决定神经元是否会“激活”。用最简单的话说,就像一个开关。

这里展示了权重和偏差的简要说明:

  • 权重:

    a) 与前一层的值相乘

    b) 可以改变幅度或完全将值从正变为负

    c) 在函数术语中——调整权重会改变函数的斜率

  • 偏差:

    a) 解释为函数的偏移

    b) 偏差的增加会导致函数向上移动

    c) 偏差的减少会导致函数向下移动

除了人工神经网络之外,还有许多类型的神经网络架构,这里将讨论它们:

  • 卷积神经网络CNNs)——一种最常见的神经网络类型,用于分析图像。它们基于卷积操作——两个矩阵之间的操作,其中一个矩阵在另一个矩阵上滑动(卷积)并计算逐元素乘积。这个操作的目标是找到一个滑动的矩阵(核)可以从输入图像中提取正确的特征,从而简化图像分类任务。

  • 循环神经网络RNNs)——一种最常见的神经网络类型,用于处理序列数据。如今,这些网络被应用于许多任务,如手写识别、语音识别、机器翻译和时间序列预测。RNN 模型一次处理序列中的单个元素。处理完毕后,新的更新单元的状态传递到下一个时间步。想象一下根据前 n 个字符预测单个字符;这就是一般的概念。

  • 生成对抗网络GANs)——一种最常见的神经网络类型,用于在从真实数据学习后创建新的样本。GAN 架构由两个独立的模型组成——生成器和判别器。生成器模型的任务是生成假图像并将其发送到判别器。判别器的工作就像一个法官,试图判断图像是否为假。

  • 自编码器 – 无监督学习技术,旨在学习高维数据集的低维表示。从某种意义上说,它们的工作方式类似于主成分分析PCA)。

这四个深度学习概念不会在本书中介绍。我们将只关注人工神经网络,但了解它们的存在是有好处的,以防你想要自己深入研究。

下一节将探讨人工神经网络,并展示如何在 Python 中实现它们,无论是从头开始还是使用数据科学库。

介绍人工神经网络

人工神经网络的基本构建块是神经元。单独来看,一个神经元是无用的,但当它组合成一个更复杂的网络时,它可以具有强大的预测能力。

如果你不能理解其中的原因,想想你的大脑以及它是如何工作的。就像人工神经网络一样,它也是由数百万个神经元组成的,只有当它们之间有通信时才会工作。由于人工神经网络试图模仿人脑,它们需要以某种方式复制大脑中的神经元及其之间的连接(权重)。这一关联将在本节中逐步变得不那么抽象。

今天,人工神经网络可以用来解决常规机器学习算法可以解决的任何问题。简而言之,如果你可以用线性或逻辑回归解决问题,你也可以用神经网络解决。

在我们能够探索整个网络的复杂性和内部工作原理之前,我们必须从简单开始——从单个神经元的理论开始。

单个神经元的理论

使用 Python 模拟单个神经元很容易。例如,假设一个神经元从五个其他神经元(输入,或 X)接收值。在将其实现为代码之前,我们先从视觉上考察这种行为。以下图表显示了单个神经元在接收来自前一层五个神经元的值时的样子(我们正在模拟右侧的神经元):

图 6.3 – 模拟单个神经元

图 6.3 – 模拟单个神经元

X 代表输入特征,这些特征可能来自原始数据或前一个隐藏层。每个输入特征都分配有一个权重,用 W 表示。相应的输入值和权重相乘并求和,然后在结果上加上偏置项(b)。

计算我们神经元输出值的公式如下:

让我们用具体的数值来使这个概念更清晰。以下图表看起来与图 6.3 相同,但用实际的数字代替了变量:

图 6.4 – 神经元值计算

图 6.4 – 神经元值计算

我们可以直接将这些值代入前面的公式来计算值:

实际上,单个神经元可以从前一层可能成千上万的神经元中获得其值,因此手动计算值并直观表达是不切实际的。

即使你决定这样做,那也只是一个前向传递。神经网络在反向传递中学习,手动计算这个传递要复杂得多。

编码单个神经元

接下来,让我们看看如何使用 Python 半自动化地计算神经元值:

  1. 首先,让我们声明输入值、它们各自的权重以及偏置项的值。前两者是列表,而偏置项只是一个数字:

    inputs = [5, 4, 2, 1, 6]
    weights = [0.1, 0.3, 0.05, 0.4, 0.9]
    bias = 4
    

    这就是计算输出值所需的所有内容。接下来,让我们看看你的选择有哪些。

  2. 计算神经元输出值有三种简单的方法。第一种是最手动的方法,即明确地乘以相应的输入和权重,然后将它们与偏置项相加。

    下面是一个 Python 实现:

    output = (inputs[0] * weights[0] + 
              inputs[1] * weights[1] + 
              inputs[2] * weights[2] +
              inputs[3] * weights[3] +
              inputs[4] * weights[4] + 
              bias)
    output
    

    执行此代码后,你应该会看到一个11.6的值打印出来。更精确地说,这个值应该是11.600000000000001,但不用担心这个计算误差。

    下一种方法稍微更可扩展一些,它归结为同时迭代输入和权重,并递增之前声明的输出变量。循环结束后,添加偏置项。下面是如何实现这种计算方法的:

    output = 0
    for x, w in zip(inputs, weights):
        output += x * w
    output += bias
    output
    

    输出仍然是相同的,但你可以立即看到这个选项的可扩展性有多高。想象一下,如果前一个网络层有 1,000 个神经元,使用第一个选项——这甚至都不方便。

    第三种也是首选的方法是使用科学计算库,例如 NumPy。有了它,你可以计算向量点积并添加偏置项。下面是如何操作的:

    import numpy as np
    output = np.dot(inputs, weights) + bias
    output
    

    这个选项既易于编写也易于执行,所以是首选的。

你现在知道如何编码单个神经元——但神经网络使用的是神经元层。你将在下一节中了解更多关于层的内容。

单层的理论

为了简化问题,可以将层想象成向量或简单的组。层并不是一些复杂或抽象的数据结构。在代码术语中,你可以将它们视为列表。它们包含一定数量的神经元。

编码单个神经层与编码单个神经元相当相似。我们仍然有相同的输入,因为它们要么来自前一个隐藏层,要么来自输入层。变化的是权重和偏置。在代码术语中,权重不再被视为列表,而是列表的列表(或矩阵)。同样,偏置现在是一个列表而不是标量值。

简单来说,你的权重矩阵将会有与新层中神经元数量一样多的行,以及与前一层的神经元数量一样多的列。让我们通过一个示例图来使这个概念更加具体化:

图 6.5 – 神经层

图 6.5 – 神经层

权重值故意没有放在之前的图中,因为它看起来会很杂乱。要在代码中实现这一层,你需要以下结构:

  • 输入向量(1 行,5 列)

  • 权重矩阵(2 行,5 列)

  • 偏置向量(1 行,2 列)

线性代数中的一个矩阵乘法规则指出,两个矩阵需要具有形状(m, n)和(n, p),以便在乘法后产生一个(m, p)矩阵。考虑到这一点,你可以通过转置权重矩阵轻松地进行矩阵乘法。

从数学上讲,这是你可以用来计算输出层值的公式:

公式 06_006.jpg

在这里,以下适用:

  • 公式 06_007.png是输入向量。

  • 公式 06_008.png是权重矩阵。

  • 公式 06_009.png是偏置向量。

让我们为所有这些声明值,并看看如何计算输出层的值:

公式 06_010

之前提到的公式现在可以用来计算输出层的值:

公式 06_011公式 06_012公式 06_013公式 06_014公式 06_015公式 06_016

这基本上就是你可以计算整个层的输出的方法。对于实际的神经网络,计算量会增长,因为每一层有数千个神经元,但数学背后的逻辑是相同的。

你可以看到手动计算层输出是多么繁琐。你将在下一节中学习如何在 Python 中计算这些值。

编码单层

现在我们来探讨三种计算单层输出值的方法。与单个神经元一样,我们将从手动方法开始,并以 NumPy 的一行代码结束。

你必须首先声明输入、权重和偏置的值,所以下面是如何做的:

inputs = [5, 4, 2, 1, 6]
weights = [
    [0.1, 0.3, 0.05, 0.4, 0.9],
    [0.3, 0.15, 0.4, 0.7, 0.2]
]
biases = [4, 2]

让我们继续计算输出层的值:

  1. 让我们从手动方法开始。不,我们不会像处理神经元那样进行相同的程序。当然,你可以这样做,但看起来会太杂乱且不实用。相反,我们将立即使用zip()函数遍历weights矩阵和biases数组,并计算单个输出神经元的值。

    这个过程会重复进行,直到有那么多神经元,每个输出神经元都会被添加到一个表示输出层的列表中。

    这是整个代码片段:

    layer = []
    for n_w, n_b in zip(weights, biases):
        output = 0
        for x, w in zip(inputs, n_w):
            output += x * w
        output += n_b
        layer.append(output)
    
    layer
    

    结果是一个包含值[11.6, 6.8]的列表,这与我们之前手动计算得到的结果相同。

    虽然这种方法可行,但仍然不是最优的。让我们看看如何改进。

  2. 你现在将通过将输入值与weights矩阵的每一行进行向量点积来计算输出层的值。在完成此操作后,将添加偏置项。

    让我们看看它是如何实际工作的:

    import numpy as np
    layer = []
    for n_w, n_b in zip(weights, biases):
        layer.append(np.dot(inputs, n_w) + n_b)
    layer
    

    层的值仍然是相同的——[11.6, 6.8],这种方法比之前的方法稍微可扩展一些。它还可以进一步改进。让我们看看下一步如何操作。

  3. 你可以使用一行 Python 代码在输入和转置权重之间执行矩阵乘法,并添加相应的偏差。下面是如何操作的:

    layer = np.dot(inputs, np.transpose(weights)) + biases
    layer
    

    如果出于某种原因,你想手动计算输出,这是推荐的方法。NumPy 可以完全处理它,因此它也是最快的。

现在,你已经知道了如何计算单个神经元和神经网络单层的输出值。到目前为止,我们还没有涵盖神经网络中的一个关键概念,它决定了神经元是否会“触发”或“激活”。这些被称为激活函数,我们将在下一节中介绍。

激活函数

激活函数对于神经网络输出,以及深度学习模型的输出至关重要。它们不过是数学方程,而且相对简单。激活函数是决定神经元是否“激活”的函数。

另一种思考激活函数的方式是将其视为一种门控,它位于当前神经元接收到的输入和其输出(传递到下一层)之间。激活函数可以像阶跃函数(开启或关闭神经元)那样简单,或者稍微复杂一些且非线性。在学习和提供准确预测的复杂数据中,非线性函数非常有用。

接下来,我们将介绍一些最常用的激活函数。

阶跃函数

阶跃函数基于一个阈值。如果输入的值高于阈值,则神经元被激活。这就是为什么我们可以说阶跃函数充当开/关开关——中间没有值。

你可以使用 Python 和 NumPy 轻松地声明和可视化一个基本的阶跃函数。过程如下:

  1. 首先,你必须定义一个阶跃函数。典型的阈值是 0,因此只有当传递给函数的值大于 0(输入值是前一个输入的总和乘以权重并加上偏差)时,神经元才会激活。

    这种逻辑在 Python 中实现起来非常简单:

    def step_function(x):
        return 1 if x > 0 else 0
    
  2. 现在,你可以声明一个值列表,这些值将作为该函数的输入,然后对列表应用step_function()。以下是一个示例:

    xs = np.arange(-10, 10, step=0.1)
    ys = [step_function(x) for x in xs]
    
  3. 最后,你只需两行代码就可以使用 Matplotlib 库可视化该函数:

    plt.plot(xs, ys, color='#000000', lw=3)
    plt.title('Step activation function', fontsize=20)
    

    你可以在以下图表中直观地看到函数的工作方式:

图 6.6 – 阶跃激活函数

图 6.6 – 阶跃激活函数

阶跃函数的最大问题是它不允许有多个输出——只有两个。接下来,我们将深入研究一系列非线性函数,你将看到它们的不同之处。

Sigmoid 函数

Sigmoid 激活函数通常被称为逻辑函数。它在神经网络和深度学习的领域中非常受欢迎。它本质上将输入转换为一个介于 0 和 1 之间的值。

你将在后面看到函数的工作方式,你将立即注意到它相对于阶梯函数的优势 – 梯度是平滑的,所以输出值中没有跳跃。例如,如果值略有变化(例如,从-0.000001 到 0.0000001),你不会从 0 跳到 1。

Sigmoid 函数在深度学习中确实存在一个常见问题 – 梯度消失。这是一个在反向传播(神经网络学习过程中的一个过程,远超本章范围)中经常出现的问题。简单来说,梯度在反向传递过程中“消失”,使得网络无法学习(调整权重和偏差),因为建议的调整太接近于零。

你可以使用 Python 和 NumPy 轻松声明和可视化 Sigmoid 函数。过程如下:

  1. 首先,你需要定义 Sigmoid 函数。其公式已经相当成熟:(1 / (1 + exp(-x))),其中x是输入值。

    下面是如何在 Python 中实现此公式的示例:

    def sigmoid_function(x):
         return 1 / (1 + np.exp(-x))
    
  2. 现在,你可以声明一个值列表,该列表将作为此函数的输入,然后应用sigmoid_function()到这个列表上。以下是一个示例:

    xs = np.arange(-10, 10, step=0.1)
    ys = [step_function(x) for x in xs]
    
  3. 最后,你只需两行代码就可以使用 Matplotlib 库可视化该函数:

    plt.plot(xs, ys, color='#000000', lw=3)
    plt.title(Sigmoid activation function', fontsize=20)
    

    你可以在以下图表中看到函数的视觉工作方式:

![Figure 6.7 – Sigmoid 激活函数img/B16954_06_7.jpg

图 6.7 – Sigmoid 激活函数

一个很大的缺点是 sigmoid 函数返回的值不是围绕零中心分布的。这是一个问题,因为对高度负值或高度正值输入的建模变得更加困难。双曲正切函数解决了这个问题。

双曲正切函数

双曲正切函数(或 TanH)与 Sigmoid 函数密切相关。它也是一种激活函数,也受到梯度消失问题的影响,但其输出值围绕零中心分布 – 函数的范围从-1 到+1。

这使得对高度负值或高度正值输入的建模变得容易得多。你可以使用 Python 和 NumPy 轻松声明和可视化双曲正切函数。过程如下:

  1. 首先,你需要定义双曲正切函数。你可以使用 NumPy 的tanh()函数来实现。

    下面是如何在 Python 中实现它的示例:

    def tanh_function(x):
        return np.tanh(x)
    
  2. 现在,你可以声明一个值列表,该列表将作为此函数的输入,然后应用tanh_function()到这个列表上。以下是一个示例:

    xs = np.arange(-10, 10, step=0.1)
    ys = [step_function(x) for x in xs]
    
  3. 最后,你只需两行代码就可以使用 Matplotlib 库可视化该函数:

    plt.plot(xs, ys, color='#000000', lw=3)
    plt.title(Tanh activation function', fontsize=20)
    

    你可以在以下图表中看到函数的视觉工作方式:

![Figure 6.8 – 双曲正切激活函数图片 B16954_06_8.jpg

图 6.8 – 双曲正切激活函数

为了有效地训练和优化神经网络,您需要一个充当线性函数但本质上是非线性的激活函数,以便网络能够学习数据中的复杂关系。这就是本节最后一个激活函数的作用所在。

矩形线性单元函数

矩形线性单元(或 ReLU)函数是您在大多数现代深度学习架构中都能看到的激活函数。简单来说,它返回 0 和 x 之间的两个值中的较大值,其中 x 是输入值。

ReLU 是最具计算效率的函数之一,它允许相对快速地找到收敛点。您将在下一节中看到如何在 Python 中实现它:

  1. 首先,您必须定义 ReLU 函数。这可以从头开始完成,也可以使用 NumPy 完成,因为您只需要找到两个值(0 和 x)中的较大值。

    这是 Python 中实现 ReLU 的方法:

    def relu_function(x):
        return np.maximum(0, x)
    
  2. 您现在可以声明一个值列表,该列表将作为此函数的输入,然后应用relu_function()到此列表。以下是一个示例:

    xs = np.arange(-10, 10, step=0.1)
    ys = [step_function(x) for x in xs]
    
  3. 最后,您可以使用 Matplotlib 库仅用两行代码来可视化该函数:

    plt.plot(xs, ys, color='#000000', lw=3)
    plt.title(ReLU activation function', fontsize=20)
    

    您可以在以下图表中直观地看到该函数的工作原理:

![图 6.9 – ReLU 激活函数图片 B16954_06_9.jpg

图 6.9 – ReLU 激活函数

这就是 ReLU 的概述。您可以使用默认版本或任何变体(例如,泄漏 ReLU 或参数 ReLU),具体取决于用例。

您现在已经了解了足够的理论,可以使用 Python 编写一个基本的神经网络。我们还没有涵盖所有的理论主题,所以像损失、梯度下降、反向传播等术语可能仍然感觉抽象。我们将在接下来的动手示例中尝试解开这些谜团。

使用神经网络对手写数字进行分类

深度学习的“hello world”是训练一个能够对手写数字进行分类的模型。这正是本节要做的。使用 TensorFlow 库实现它只需要几行代码。

在您继续之前,您必须安装 TensorFlow。根据您是在 Windows、macOS 还是 Linux 上,以及您是否有 CUDA 兼容的 GPU,安装过程会有所不同。您可以参考官方安装说明:www.tensorflow.org/install。本节其余部分假设您已安装 TensorFlow 2.x。以下是需要遵循的步骤:

  1. 首先,您必须导入 TensorFlow 库以及一些额外的模块。datasets模块使您能够直接从笔记本中下载数据。layersmodels模块将在以后用于设计神经网络的架构。

    这是导入代码片段:

    import tensorflow as tf
    from tensorflow.keras import datasets, layers, models
    
  2. 现在,您可以继续进行数据收集和准备。调用datasets.mnist.load_data()将下载训练和测试图像以及相应的训练和测试标签。这些图像是灰度的,大小为 28x28 像素。这意味着您将有一系列 28x28 的矩阵,其值范围从 0(黑色)到 255(白色)。

    然后,您可以通过重新缩放图像来进一步准备数据集 – 将值除以 255,将所有内容转换为 0 到 1 的范围:

    (train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data()
    train_images, test_images = train_images / 255.0, test_images / 255.0
    

    在您的笔记本中应该看到以下内容:

    图 6.10 – 下载 MNIST 数据集

    图 6.10 – 下载 MNIST 数据集

  3. 此外,您还可以检查图像中的一个矩阵值,看看您是否能在其中找到模式。

    以下代码行使检查矩阵变得容易 – 它打印它们并将所有浮点数四舍五入到一个小数点:

    print('\n'.join([''.join(['{:4}'.format(round(item, 1)) for item in row]) for row in train_images[0]]))
    

    结果如下截图所示:

    图 6.11 – 检查单个图像矩阵

    图 6.11 – 检查单个图像矩阵

    您是否注意到在图像中很容易找到数字 5?您可以通过执行train_labels[0]来验证。

  4. 您可以继续布局神经网络架构。如前所述,输入图像的大小为 28x28 像素。人工神经网络不能直接处理矩阵,因此您必须将这个矩阵转换为向量。这个过程被称为layers.Dense()来构建一个层。

    这个隐藏层也需要一个激活函数,因此可以使用 ReLU。

    最后,您可以添加最终的(输出)层,该层需要与不同类别的数量一样多的神经元 – 在这个例子中是 10 个。

    这是网络架构的整个代码片段:

    model = models.Sequential([
      layers.Flatten(input_shape=(28, 28)),
      layers.Dense(128, activation='relu'),
      layers.Dense(10)
    ])
    

    models.Sequential函数允许您将层一个接一个地堆叠起来,并且,嗯,从单个层中构建一个网络。

    您可以通过在模型上调用summary()方法来查看模型的架构:

    model.summary()
    

    结果如下截图所示:

    图 6.12 – 神经网络架构

    图 6.12 – 神经网络架构

  5. 在模型训练之前,您还需要做一件事,那就是编译模型。在编译过程中,您需要指定优化器、损失函数和优化指标的具体值。

    这些内容在本章中尚未涉及,但下面将简要解释每个部分:

    • 优化器 – 用于改变神经网络属性以减少损失值的算法。这些属性包括权重、学习率等。

    • 损失 – 一种用于计算梯度的方法,然后使用这些梯度来更新神经网络中的权重。

    • 指标 – 您正在优化的指标(例如,准确率)。

      深入探讨这些主题超出了本书的范围。有很多资源可以帮助您了解深度学习背后的理论。本章仅旨在介绍基本概念。

      你可以通过执行以下代码来编译你的神经网络:

      model.compile(
          optimizer='adam',
          loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
          metrics=['accuracy']
      )
      
  6. 现在,你已经准备好训练模型了。训练子集将被用来训练网络,测试子集将被用来评估。网络将训练 10 个 epoch(即 10 次完整遍历整个训练数据)。

    你可以使用以下代码片段来训练模型:

    history = model.fit(
        train_images, 
        train_labels, 
        epochs=10, 
        validation_data=(test_images, test_labels)
    )
    

    执行前面的代码将启动训练过程。所需时间取决于你的硬件配置以及你是否使用了 GPU 或 CPU。你应该会看到以下类似的屏幕截图:

    图 6.13 – MNIST 模型训练

    图 6.13 – MNIST 模型训练

    经过 10 个 epoch 后,验证集上的准确率达到了 97.7%——如果我们考虑到常规神经网络在图像处理上表现不佳的话,这已经是非常出色的了。

  7. 要在新的实例上测试你的模型,你可以使用predict()方法。它返回一个数组,告诉你对于给定类别的预测是否正确。这个数组将有 10 个项,因为共有 10 个类别。

    然后,你可以调用np.argmax()来获取值最高的项:

    import numpy as np
    prediction = model.predict(test_images[0].reshape(-1, 784))
    print(f'True digit = {test_labels[0]}')
    print(f'Predicted digit = {np.argmax(prediction)}')
    

    结果将在下面的屏幕截图中展示:

图 6.14 – 测试 MNIST 模型

图 6.14 – 测试 MNIST 模型

如你所见,预测是正确的。

就是这样简单,使用 TensorFlow 等库训练神经网络。请记住,在现实世界中,我们不建议使用这种方式处理图像分类,因为我们已经将 28x28 的图像展平,立即丢失了所有的二维信息。对于图像分类,CNN(卷积神经网络)会是一个更好的方法,因为它们可以从二维数据中提取有用的特征。我们的人工神经网络在这里表现良好,因为 MNIST 是一个简单且干净的数据库集——这不是你在工作中会遇到的很多情况。

在下一节中,你将了解使用神经网络处理分类和回归任务的差异。

回归与分类中的神经网络

如果你使用过 scikit-learn 进行过任何机器学习,你就会知道,对于回归和分类数据集,都有专门的类和模型。例如,如果你想将决策树算法应用于分类数据集,你会使用DecisionTreeClassifier类。同样,对于回归任务,你会使用DecisionTreeRegressor类。

但你该如何使用神经网络呢?没有专门用于分类和回归任务的类或层。

相反,你可以通过调整输出层中的神经元数量来适应。简单来说,如果你处理的是回归任务,输出层中必须只有一个神经元。如果你处理的是分类任务,输出层中的神经元数量将与目标变量中的不同类别数量相同。

例如,你看到了上一节中的神经网络在输出层有 10 个神经元。原因是存在从零到九的 10 个不同的数字。如果你预测的是某物的价格(回归),输出层将只有一个神经元。

神经网络的任务是学习适当的参数值(权重和偏置),以产生最佳输出值,无论你正在解决什么类型的问题。

摘要

如果这是您第一次接触深度学习和神经网络,那么这一章可能难以理解。反复阅读材料可能会有所帮助,但这还不足以完全理解这个主题。关于深度学习,甚至关于深度学习的小子集,已经写出了整本书。因此,在一章中涵盖所有内容是不可能的。

尽管如此,您应该了解神经元、层和激活函数概念背后的基本理论,并且您可以随时自学更多。下一章,第七章**,TPOT 神经网络分类器,将向您展示如何连接神经网络和管道优化,这样您就可以完全自动地构建最先进的模型。

和往常一样,请随时探索深度学习和神经网络的理沦和实践。这绝对是一个值得进一步研究的学术领域。

Q&A

  1. 你将如何定义“深度学习”这个术语?

  2. 传统机器学习算法与深度学习中使用的算法有什么区别?

  3. 列出并简要描述五种类型的神经网络。

  4. 你能想出如何根据每层的神经元数量来计算网络中的可训练参数数量吗?例如,具有架构[10, 8, 8, 2]的神经网络总共有 178 个可训练参数(160 个权重和 18 个偏置)。

  5. 列出四种不同的激活函数,并简要解释它们。

  6. 用你自己的话描述神经网络中的损失

  7. 解释为什么用常规人工神经网络来建模图像分类模型不是一个好主意。

第七章:使用 TPOT 的神经网络分类器

在本章中,你将学习如何以自动化的方式构建你的深度学习分类器——通过使用 TPOT 库。假设你已了解人工神经网络的基本知识,因此诸如神经元激活函数学习率等术语应该听起来很熟悉。如果你不知道如何简单地解释这些术语,请回顾第六章深度学习入门:神经网络快速入门

在本章的整个过程中,你将了解到构建基于神经网络的简单分类器是多么容易,以及你如何调整神经网络以更好地满足你的需求和训练数据。

本章将涵盖以下主题:

  • 探索数据集

  • 探索训练神经网络分类器的选项

  • 训练神经网络分类器

技术要求

你不需要有深度学习和神经网络的实际操作经验。然而,了解一些基本概念和术语是必须的。如果你对这个主题完全陌生,请回顾第六章深度学习入门:神经网络快速入门

你可以在此处下载本章的源代码和数据集:github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT/tree/main/Chapter07

探索数据集

没有必要对数据集进行过度探索。仅仅因为我们可以用 TPOT 训练神经网络模型,并不意味着我们应该花费 50 多页来无谓地探索和转换复杂的数据集。

因此,在本章中,你将使用 scikit-learn 内置的数据集——乳腺癌数据集。这个数据集不需要从网络上下载,因为它已经内置在 scikit-learn 中。让我们先加载并探索它:

  1. 首先,你需要加载几个库。我们导入 NumPy、pandas、Matplotlib 和 Seaborn,以便于数据分析和可视化。此外,我们还从sklearn.datasets模块导入load_breast_cancer函数。这是将数据集加载进来的函数。最后,从 Matplotlib 导入rcParams模块,以便使默认样式更容易看清楚:

    import numpy as np
    import pandas as pd
    import matplotlib.pyplot as plt
    import seaborn as sns
    from sklearn.datasets import load_breast_cancer
    from matplotlib import rcParams
    rcParams['figure.figsize'] = (14, 7)
    rcParams['axes.spines.top'] = False
    rcParams['axes.spines.right'] = False
    
  2. 你现在可以使用load_breast_cancer函数来加载数据集。该函数返回一个字典,因此我们可以使用keys()方法来打印键:

    data = load_breast_cancer()
    data.keys()
    

    结果如下所示:

    图 7.1 – 乳腺癌数据集的字典键

    图 7.1 – 乳腺癌数据集的字典键

  3. 您现在可以使用这个字典来提取感兴趣的属性。目前最重要的是 datatarget 键。您可以将它们的值存储到单独的变量中,然后从它们中构建一个数据框对象。使用原始值是可能的,但 pandas 数据框数据结构将允许更轻松的数据操作、转换和探索。

    这里是如何将这些数据转换为 pandas 数据框的:

    features = data.data
    target = data.target
    df =\
    pd.DataFrame(data=features,columns=data.feature_names)
    df['target'] = target
    df.sample(8)
    

    结果如下表所示:

    图 7.2 – 乳腺癌数据集的八行样本

    图 7.2 – 乳腺癌数据集的八行样本

  4. 在分析方面,您首先想要做的是检查缺失数据。Pandas 内置了一个 isnull() 方法,它为数据集中的每个值返回布尔值。然后,您可以在这些结果上调用 sum() 方法来获取每列的缺失值计数:

    df.isnull().sum()
    

    结果如下所示:

    图 7.3 – 每列的缺失值计数

    图 7.3 – 每列的缺失值计数

    如您所见,没有缺失值。

  5. 在探索阶段,下一步是熟悉您的数据集。数据可视化技术可以提供一种极好的方式来完成这项工作。

    例如,您可以声明一个名为 make_count_chart() 的函数,它接受任何分类属性并可视化其分布。下面是这个函数的代码示例:

    def make_count_chart(column, title, ylabel, xlabel, y_offset=0.12, x_offset=700):
        ax = df[column].value_counts().plot(kind='bar', fontsize=13, color='#4f4f4f')
        ax.set_title(title, size=20, pad=30)
        ax.set_ylabel(ylabel, fontsize=14)
        ax.set_xlabel(xlabel, fontsize=14)
    
        for i in ax.patches:
            ax.text(i.get_x() + x_offset, i.get_height()\
     + y_offset, f'{str(round(i.get_height(), 2))}',\
     fontsize=15)
        return ax
    

    您现在可以使用以下代码片段来可视化目标变量,以找出有多少实例是良性的,有多少是恶性的:

    make_count_chart(
        column='target',
        title=\
    'Number of malignant (1) vs benign (0) cases',
        ylabel='Malignant? (0 = No, 1 = Yes)',
        xlabel='Count',
        y_offset=10,
        x_offset=0.22
    )
    

    结果如下所示:

    图 7.4 – 恶性和良性病例数量

    图 7.4 – 恶性和良性病例数量

    如您所见,恶性病例的数量要多得多,所以类别并不完全平衡。类别不平衡可能导致高度准确但不可用的模型。想象一下,您正在对罕见事件进行分类。在每 10,000 笔交易中,只有一笔被分类为异常。显然,机器学习模型没有多少机会学习使异常与其他事件不同的原因。

    此外,总是预测交易是正常的会导致一个 99.99% 准确的模型。这是一个最先进的准确率,但模型不可用。

    处理不平衡数据集有许多技术,但这些超出了本书的范围。

  6. 下一个步骤是相关性分析。这一步的目标是看看哪些特征对目标变量影响最大。换句话说,我们想要确定特征方向变化与目标类之间的相关性。在 30+ 列的数据集上可视化整个相关性矩阵并不是最好的主意,因为这需要一个太大而无法舒适地放在单页上的图。相反,我们可以计算特征与目标变量之间的相关性。

    这里是如何为平均面积特征进行此操作的示例——通过从 NumPy 调用corrcoeff()方法:

    np.corrcoef(df['mean area'], df['target'])[1][0]
    

    结果如下所示:

    图 7.5 – 单个特征与目标变量的相关系数

    corr_with_target = []
    for col in df.columns[:-1]:
        corr = np.corrcoef(df[col], df['target'])[1][0]
        corr_with_target.append({'Column': col, 'Correlation': corr})
    
    corr_df = pd.DataFrame(corr_with_target)
    corr_df = \
    corr_df.sort_values(by='Correlation', ascending=False)
    

    请注意循环开始处的[:-1]。由于目标变量是最后一列,我们可以使用上述切片技术排除目标变量,从而不将其包含在相关性计算中。目标变量与非目标变量之间的相关系数将为 1,这对我们来说并不特别有用。

    您现在可以使用以下代码来绘制与目标变量的相关性水平条形图:

    plt.figure(figsize=(10, 14))
    plt.barh(corr_df['Column'], corr_df['Correlation'], color='#4f4f4f')
    plt.title('Feature correlation with the target variable', fontsize=20)
    plt.xlabel('Feature', fontsize=14)
    plt.ylabel('Correlation', fontsize=14)
    plt.show()
    

    结果如下所示:

    图 7.6 – 特征与目标变量的相关性

    图 7.6 – 特征与目标变量的相关性

    如您所见,大多数特征与目标变量具有高度负相关性。负相关性意味着当一个变量增加时,另一个变量会减少。在我们的例子中,特征数量的减少会导致目标变量的增加。

  7. 您还可以根据目标变量的值可视化每个数值列的分布。更准确地说,这意味着在单个图表上绘制两个单独的直方图,每个直方图只显示相应目标值子集的分布。

    例如,这意味着一个直方图将显示每个变量的恶性实例的分布,另一个直方图将显示良性实例的分布。

    您即将看到的代码片段声明了一个draw_histogram()函数,该函数遍历数据集中的每一列,根据目标变量的不同类别绘制直方图,并将此直方图附加到图上。

    一旦所有直方图都被附加,该图将显示给用户。用户还必须指定他们想要的行数和列数,这为设计可视化提供了一些额外的自由度。

    这是绘制此直方图网格的代码片段:

    def draw_histogram(data, columns, n_rows, n_cols):
        fig = plt.figure(figsize=(12, 18))
        for i, var_name in enumerate(columns):
            ax = fig.add_subplot(n_rows, n_cols, i + 1)
            sns.histplot(data=data, x=var_name, hue='target')
            ax.set_title(f'Distribution of {var_name}')
        fig.tight_layout()
        plt.show()
    draw_histogram(df, df.columns[:-1], 9, 4)
    

    这将是一个非常大的数据可视化,包含 9 行和 4 列。最后一行将只有 2 个直方图,因为总共有 30 个连续变量。

    结果如下所示:

图 7.7 – 每个连续变量的直方图

图 7.7 – 每个连续变量的直方图

如您所见,大多数情况下存在明显的分离,因此我们的模型在类之间进行合理的分离时不应有太多困难。

那就是我们在探索性数据分析方面要做的所有事情。您可以,并且被鼓励去做更多,特别是对于自定义和更复杂的数据集。下一节将向您介绍您用于训练自动神经网络分类器的选项。

探索训练神经网络分类器的选项

当使用 TPOT 训练神经网络模型时,你有许多选项。整个神经网络的故事在 TPOT 中仍然是新的和实验性的,需要比常规的 scikit-learn 估计器更多的手动工作。

默认情况下,TPOT 不会使用神经网络模型,除非你明确指定它必须使用。这种指定是通过选择一个包含一个或多个神经网络估计器的适当配置字典来完成的(你也可以手动编写这些字典)。

更方便的选项是从tpot/config/classifier_nn.py文件中导入配置字典。该文件包含两个 PyTorch 分类器配置,如下面的图所示:

图 7.8 – TPOT PyTorch 分类器配置

图 7.8 – TPOT PyTorch 分类器配置

从前面的图中,你可以看到 TPOT 目前可以处理基于深度学习库的两种不同类型的分类器:

  • 逻辑回归:在tpot.builtins.PytorchLRClassifier中展示

  • 多层感知器:在tpot.builtins.PytorchMLPClassifier中展示

你可以选择导入此文件或手动编写配置。此外,你还可以指定自己的配置字典,这些字典以某种方式修改了现有的配置。例如,你可以使用以下代码来使用基于 PyTorch 的逻辑回归估计器:

tpot_config = {
    'tpot.nn.PytorchLRClassifier': {
        'learning_rate': [1e-3, 1e-2, 1e-1, 0.5, 1.]
    }
}

在本章后面,当我们开始实现神经网络分类器时,将讨论自定义配置。

你应该记住,使用 TPOT 训练神经网络分类器是一个耗时的任务,通常比 scikit-learn 估计器训练时间要长得多。一般来说,你应该预计神经网络训练时间要慢几个数量级。这是因为神经网络架构可以有数百万个可训练和可调整的参数,找到所有这些参数的正确值需要时间。

考虑到这一点,你应该首先考虑更简单的选项,因为 TPOT 很可能在默认的 scikit-learn 估计器上为你提供一个性能优异的管道。

下一节将继续从上一节停止的地方继续训练神经网络分类器,并展示如何使用不同的训练配置来训练你的模型。

训练神经网络分类器

到目前为止,我们已经加载了数据集并进行了基本的数据探索性分析。本章的这一部分将专注于通过不同的配置来训练模型:

  1. 在我们开始模型训练之前,我们需要将我们的数据集分成训练集和测试集。这样做将允许我们有一个模型从未见过的数据样本,并且可以稍后用于评估。

    以下代码片段将以 75:25 的比例分割数据:

    from sklearn.model_selection import train_test_split
    X = df.drop('target', axis=1)
    y = df['target']
    X_train, X_test, y_train, y_test =train_test_split(\
    X, y, test_size=0.25, random_state=42)
    

    我们可以开始训练了。

  2. 像往常一样,让我们从训练一个基线模型开始。这将作为神经网络分类器必须超越的最小可行性能。

    最简单的二元分类算法是逻辑回归。以下代码片段从 scikit-learn 中导入它,以及一些评估指标,如混淆矩阵和准确率。此外,该片段实例化了模型,进行了训练,在保留集上进行了预测,并打印了混淆矩阵和准确率。

    以下是一个代码片段:

    from sklearn.linear_model import LogisticRegression
    from sklearn.metrics import confusion_matrix, accuracy_score
    lr_model = LogisticRegression()
    lr_model.fit(X_train, y_train)
    lr_preds = lr_model.predict(X_test)
    print(confusion_matrix(y_test, lr_preds))
    print()
    print(accuracy_score(y_test, lr_preds))
    

    结果如下所示:

    ![图 7.9 – 基线模型的混淆矩阵和准确率]

    图片

    图 7.9 – 基线模型的混淆矩阵和准确率

    我们现在知道基线模型的准确率为 96.5%,产生了 4 个假阳性和 1 个假阴性。接下来,我们将使用 TPOT 训练一个自动化的神经网络分类器,并看看结果如何比较。

  3. 如前所述,使用 TPOT 训练神经网络分类器是一项艰巨的任务。因此,你可能最好切换到一个免费的 GPU 云环境,例如Google Colab

    这将确保更快的训练时间,同时你也不会让你的电脑过热。一旦进入该环境,你可以使用以下代码片段来训练基于 PyTorch 的逻辑回归模型:

    from tpot import TPOTClassifier
    classifier_lr = TPOTClassifier(
        config_dict='TPOT NN',
        template='PytorchLRClassifier',
        generations=2,
        random_state=42,
        verbosity=3
    )
    classifier_lr.fit(X_train, y_train)
    

    这将训练模型两代。你会在训练过程中看到各种输出,如下所示:

    ![图 7.10 – TPOT 神经网络训练过程]

    图片

    classifier_lr.fitted_pipeline_
    

    结果如下所示:

    ![图 7.12 – TPOT PyTorch 逻辑回归最佳流程]

    图片

    from sklearn.metrics import confusion_matrix,\
     accuracy_score
    tpot_lr_preds = classifier_lr.predict(X_test)
    print(confusion_matrix(y_test, tpot_lr_preds))
    print()
    print(accuracy_score(y_test, tpot_lr_preds))
    

    结果如下所示:

    ![图 7.13 – PyTorch 逻辑回归模型的混淆矩阵和准确率得分]

    图片

    图 7.13 – PyTorch 逻辑回归模型的混淆矩阵和准确率得分

    如你所见,两代并不足以产生一个优于基线模型的结果。让我们看看使用多层感知器模型是否有所帮助。

  4. 我们仍然处于 Google Colab 环境中,因为在你自己的电脑上训练要慢得多(取决于你的配置)。现在的想法是使用多层感知器模型而不是逻辑回归,看看模型的变化如何影响性能。

    首先,你必须修改TPOTClassifiertemplate参数,如下所示:

    classifier_mlp = TPOTClassifier(
        config_dict='TPOT NN',
        template='PytorchMLPClassifier',
        generations=2,
        random_state=42,
        verbosity=3
    )
    

    如你所见,我们现在使用PytorchMLPClassifier而不是PytorchLRClassifier。要开始优化过程,只需用训练数据调用fit()方法:

    classifier_mlp.fit(X_train, y_train)
    

    与逻辑回归算法一样,你也会在优化过程中看到进度条:

    ![图 7.14 – TPOT 多层感知器训练过程]

    图片

    classifier_mlp.fitted_pipeline_
    

    结果如下所示:

    ![图 7.16 – TPOT PyTorch 多层感知器最佳流程]

    图片

    from sklearn.metrics import confusion_matrix,\
     accuracy_score
    tpot_mlp_preds = classifier_mlp.predict(X_test)
    print(confusion_matrix(y_test, tpot_mlp_preds))
    print()
    print(accuracy_score(y_test, tpot_mlp_preds))
    

    结果如下所示:

    图 7.17 – PyTorch 多层感知器模型的混淆矩阵和准确率得分

    图 7.17 – PyTorch 多层感知器模型的混淆矩阵和准确率得分

    如你所见,两代仍然不足以产生优于基线模型的结果,但 MLP 模型的表现优于逻辑回归模型。现在让我们看看使用自定义训练配置是否可以将准确率进一步提高。

  5. 最后,让我们看看如何为逻辑回归或多层感知器模型指定可能的超参数值。你所要做的就是指定一个自定义配置字典,其中包含你想要测试的超参数(如学习率、批量大小和迭代次数),并以列表的形式为这些超参数分配值。

    这里有一个例子:

    custom_config = {
        'tpot.builtins.PytorchMLPClassifier': {
            'learning_rate': [1e-1, 0.5, 1.],
            'batch_size': [16, 32],
            'num_epochs': [10, 15],
        }
    }
    

    现在,你可以在训练模型时使用这个custom_config字典。以下是一个基于多层感知器模型的示例训练片段:

    classifier_custom = TPOTClassifier(
        config_dict=custom_config,
        template='PytorchMLPClassifier',
        generations=2,
        random_state=42,
        verbosity=3
    )
    classifier_custom.fit(X_train, y_train)
    

    如你所见,只有config_dict参数发生了变化。一旦训练过程开始,你将在笔记本中看到类似这样的进度条:

图 7.18 – 使用神经网络的 TPOT 自定义调整

图 7.18 – 使用神经网络的 TPOT 自定义调整

一旦训练过程完成,你应在笔记本中看到以下类似的内容:

图 7.19 – 带有自定义超参数的 TPOT 多层感知器分类器

图 7.19 – 带有自定义超参数的 TPOT 多层感知器分类器

就这么简单!为了验证,你可以通过执行以下命令来检查最佳拟合管道:

classifier_custom.fitted_pipeline_

结果如下所示:

图 7.20 – 带有自定义超参数的模型的 TPOT 最佳拟合管道

图 7.20 – 带有自定义超参数的模型的 TPOT 最佳拟合管道

如你所见,所有超参数值都在指定的范围内,这表明自定义模型已成功训练。

这就结束了本节以及本节整体中的模型训练部分。接下来是一个对我们迄今为止所学内容的简要总结,以及对即将在后续章节中介绍内容的简要介绍。

摘要

本章在动手示例和演示方面相当密集。你或许已经学会了如何使用 TPOT 训练自动化分类管道,以及在整个过程中你可以调整什么。

现在,你应该能够使用 TPOT 训练任何类型的自动化机器学习模型,无论是回归、分类、标准分类器还是神经网络分类器。这是一个好消息,因为这是 TPOT 示例的最后一章。

在下一章第八章TPOT 模型部署,你将学习如何将模型的预测功能封装在 REST API 中,然后将在本地和云中进行测试和部署。你还将学习部署后如何与 API 进行通信。

最后,在上一章第九章在生产中使用部署的 TPOT 模型,你将学习如何利用部署的 API 开发有用的东西。更准确地说,你将学习如何在笔记本环境中通过向部署的 API 发起 REST 调用来进行预测,以及如何开发一个简单的 GUI 应用程序,使你的模型对最终用户更具吸引力。

和往常一样,你可以更深入地研究 TPOT,但到目前为止,你已经领先于大多数人,并且你准备好让机器学习变得有用。在那里见!

问题

  1. TPOT 中关于神经网络有哪些可用的算法?

  2. 大约神经网络分类器训练速度比默认的 scikit-learn 分类器慢多少倍?

  3. 列出并简要解释使用 TPOT 和神经网络训练模型时可用的一些超参数。

  4. 在使用 TPOT 训练自定义神经网络模型时,能否指定自定义的超参数值范围?如果是的话,如何操作?

  5. 模型训练完成后,如何找到最佳拟合的流水线?

  6. 使用如 Google Colab 这样的 GPU 运行时在 TPOT 训练神经网络模型时有哪些优势?

  7. 描述为什么多层感知器模型中的单个神经元可以被视为逻辑回归。

第八章: TPOT 模型部署

在本章中,您将学习如何将任何自动化机器学习模型部署到本地主机和云端。您将了解到如果您旨在创建机器学习驱动的软件,部署步骤是必要的。假设您知道如何使用 TPOT 训练基本的回归和分类模型。不需要了解前几章(Dask 和神经网络)的主题,因为我们在这里不会涉及这些内容。

在本章中,您将了解到将模型封装在 API 中并向非数据科学家用户暴露其预测能力是多么容易。您还将了解到哪些云服务提供商最适合您免费开始。

本章将涵盖以下主题:

  • 我们为什么需要模型部署?

  • 介绍FlaskFlask-RESTful

  • 自动化模型部署的最佳实践

  • 将机器学习模型部署到本地主机

  • 将机器学习模型部署到云端

技术要求

如前所述,您需要知道如何使用 TPOT 构建基本的机器学习模型。如果您对库还不够熟悉,请不要担心,我们将从头开始开发模型。如果您是 TPOT 的完全新手,请参阅第二章深入 TPOT第三章使用 TPOT 探索回归,和第四章使用 TPOT 探索分类

本章将包含大量的代码,因此如果您遇到困难,可以参考官方 GitHub 仓库:github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT/tree/main/Chapter08

我们为什么需要模型部署?

如果您已经在费劲周折地训练和优化机器学习模型,为什么不更进一步,将其部署出来供每个人使用呢?

也许您希望将模型的预测能力集成到 Web 应用程序中。也许您是一位希望将机器学习带到 Android 和 iOS 的移动应用开发者。选项无穷无尽,但它们都有一个共同点——需要部署。

现在,机器学习模型部署与机器学习本身无关。目标是编写一个简单的 REST API(最好使用 Python,因为本书中使用的语言是 Python)并将调用predict()函数的任何形式的端点暴露给世界。您希望以 JSON 格式发送参数到您的应用程序,并将它们用作模型的输入。一旦做出预测,您只需将其返回给用户即可。

是的,这就是机器学习模型部署的全部内容。当然,事情可能会变得更加技术化,但保持简单将让我们达到 95% 的效果,而你总是可以进一步探索以获得额外的 5%。

当涉及到模型部署的技术方面时,Python 为你提供了一系列选项。你可以使用 FlaskFlask-RESTfulFastAPIDjangoPyramid。当然,还有其他选项,但它们的“市场份额”或多或少可以忽略不计。你将在本章的下一节开始使用第一个选项。

下一个部分旨在通过几个基本的动手示例介绍这些库。之后,我们将深入了解机器学习。

介绍 Flask 和 Flask-RESTful

Flask 是一个用于构建 Web 应用的轻量级框架。它使你能够简单开始,并在需要时进行扩展。Flask-RESTfulFlask 的一个扩展,它允许你快速构建 RESTful API。

要开始使用这两个库,你需要安装它们。你可以在终端中执行以下行:

> pip install flask flask-restful

这就是你需要开始的地方。让我们首先探索 Flask 的基础知识:

  1. 信不信由你,你只需要七行代码就可以使用 Flask 创建你的第一个 Web 应用程序。它可能不会做任何有用的事情,但这仍然是一个正确的方向。

    首先,你需要导入库并创建一个应用程序实例。然后,你需要创建一个函数,该函数返回你想要在网站上显示的内容,并用 @app.route(route_url) 装饰器装饰该函数。请记住,你应该用函数应该显示结果的 URL 字符串替换 route_url。如果你传递一个正斜杠(/),结果将在根页面上显示——但关于这一点,我们稍后再说。

    最后,你必须使用 if __name__ == '__main__' 检查使 Python 文件可执行。应用程序将在本地的 8000 端口上运行。

    请参考以下代码片段以了解你的第一个 Flask 应用程序:

    from flask import Flask 
    app = Flask(__name__)
    @app.route('/')
    def root():
        return 'Your first Flask app!'
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8000)
    

    要运行应用程序,你必须在终端中执行 Python 文件。在我的机器上,该文件被命名为 flask_basics.py,因此要运行它,请执行以下操作:

    Running on http://0.0.0.0:8000/ message, you can see where the application is running, ergo which URL you need to visit to see your application. Just so you know, the 0.0.0.0 part can be replaced with localhost.Once there, you'll see the following displayed, indicating that everything worked properly:![Figure 8.2 – Your first Flask application    ](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-auto-tpot/img/B16954_08_2.jpg)Figure 8.2 – Your first Flask applicationAnd that's how easy it is to build your first web application with `Flask`. You'll learn how to make something a bit more complex next.
    
  2. 到目前为止,你已经知道如何使用 Flask 构建最简单的应用程序——但这不是你来的原因。我们想要构建 API 而不是应用程序,这是一个不同的故事。它们之间的区别相当明显——API 不带用户界面(除了文档页面),而 Web 应用程序则带。API 只是一个服务。实际上,你可以使用 Flask 直接构建 API。我们将探讨如何做到这一点,并解释为什么这不是最佳选择。

    首先,创建一个新的 Python 文件。这个文件将被引用为 flask_basics2.py。在里面,我们将有一个用于两种可能的 API 调用类型的单个路由。它们都有添加两个数字并返回结果的任务,但它们以不同的方式完成。让我们列出这些差异:

    a) /adding(GET)依赖于之前实现的逻辑。更准确地说,当调用端点时,会发起一个 GET 请求。唯一的区别是这次,参数是通过 URL 传递的。例如,调用/adding?num1=3&num2=5应在屏幕上显示8。参数值直接从 URL 中提取。您将看到这一动作,所以一切都会立即变得清晰。

    b) /adding(POST)与第一个端点非常相似,但发起的是 POST 请求。这是一种更安全的通信方法,因为参数值不是直接通过 URL 传递,而是在请求体中传递。此端点以 JSON 格式返回求和结果,因此您需要将结果包装在flask.jsonify()函数中。

    这两个函数旨在完成一个相同的目标 – 求两个数的和并返回结果。以下是如何实现这种逻辑的一个示例:

    from flask import Flask, request, jsonify
    app = Flask(__name__)
    @app.route('/adding')
    def add_get():
        num1 = int(request.args.get('num1'))
        num2 = int(request.args.get('num2'))
        return f'<h3>{num1} + {num2} = {num1 + num2}</h3>'
    @app.route('/adding', methods=['POST'])
    def add_post():
        data = request.get_json()
        num1 = data['num1']
        num2 = data['num2']
        return jsonify({'result': num1 + num2})
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8000)
    

    如您所见,add_get()函数返回一个格式为 HTML 的字符串。如果您想返回整个 HTML 文档,也可以,但这不是我们现在感兴趣的事情,所以我们不会进一步探讨。

    要运行应用程序,您需要在终端中执行 Python 文件。在我的机器上,该文件名为flask_basics2.py,因此要运行它,请执行以下命令:

    /adding for GET first:![Figure 8.3 – The GET endpoint without parameter values    ](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-auto-tpot/img/B16954_08_3.jpg)
    
    

    import requests

    req = requests.post(

    url='http://localhost:8000/adding',

    json={'num1': 3, 'num2': 5}

    )

    res = req.content

    print(res)

    
    If you were to run this code now, here's what you would see as the output: ![Figure 8.6 – The POST endpoint with parameters (Python)    ](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-auto-tpot/img/B16954_08_6.jpg)Figure 8.6 – The POST endpoint with parameters (Python)This is essentially a string, so some conversion to JSON will be mandatory before you can work with the returned value. More on that later, in *Chapter 9*, *Using the Deployed TPOT Model in Production*.So far, you've seen how the `Flask` library can be used to develop both web applications and web services (APIs). It's a good first option, but there's a better approach if you're only interested in building APIs – `Flask-RESTful`. Let's explore it next.
    
  3. 您已经安装了Flask-RESTful。使用它的语法略有不同。它使用get()post()put(),这些代表当发起特定类型的请求时会发生什么。

    所有 API 类都继承自Flask-RESTful.Resource类,并且每个端点必须通过add_resource()方法手动绑定到特定的 URL 字符串。

    总结一下,我们将有一个Add类,它有两个方法:get()post()。这些方法内部的逻辑与之前相同,唯一的例外是我们不会在任何地方返回 HTML。

    这是整个代码片段:

    from flask import Flask, request, jsonify
    from flask_restful import Resource, Api 
    app = Flask(__name__)
    api = Api(app)
    class Adding(Resource):
        @staticmethod
        def get():
            num1 = int(request.args.get('num1'))
            num2 = int(request.args.get('num2'))
            return num1 + num2
        @staticmethod
        def post():
            data = request.get_json()
            num1 = data['num1']
            num2 = data['num2']
            return jsonify({'result': num1 + num2})
    api.add_resource(Adding, '/adding')
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8000)
    

    Adding类内部的所有内容都在/adding端点上可用。

    如您所见,该 API 将在不同的端口上运行,以便于区分这个 API 和之前的那个。

    如果您现在打开http://localhost:8000/adding,您会看到以下消息:

图 8.7 – Flask-RESTful 无参数 GET

图 8.7 – Flask-RESTful 无参数 GET

我们现在遇到了与默认FlaskAPI 相同的错误,原因是 URL 中没有给出参数值。如果您要更改它并调用http://localhost:8000/adding?num1=5&num2=10,您会在浏览器窗口中看到以下内容:

图 8.8 – Flask-RESTful 带参数 GET

图 8.8 – Flask-RESTful 带参数 GET

如前所述,直接从浏览器与 API 通信不被认为是最佳实践,但你可以使用 GET 请求类型来做这件事。你最好使用像 Postman 这样的工具,而你已经知道如何使用它。

对于 POST 方法,你可以调用之前相同的 URL,即http://localhost:8000/adding,并在请求体中以 JSON 格式传递参数。以下是使用 Postman 进行操作的步骤:

图 8.9 – 使用 Postman 的 Flask-RESTful POST

图 8.9 – 使用 Postman 的 Flask-RESTful POST

你也可以通过 Python 来做同样的事情,但你现在应该已经知道如何做了。

你现在已经了解了使用 Python、FlaskFlask-RESTful进行 REST API 开发的基础知识。这是一个相对快速、实用的部分,作为即将到来的内容的入门。在下一节中,我们将讨论部署机器学习模型的几个最佳实践,在最后两节中,我们将探讨如何分别在本地和云中训练和部署模型。

自动化模型部署的最佳实践

自动化模型的部署与你的常规机器学习模型的部署大致相同。这归结为首先训练模型,然后以某种格式保存模型。在常规机器学习模型的情况下,你可以轻松地将模型保存为.model.h5文件。没有理由不将 TPOT 模型也这样做。

如果你记得前面的章节,TPOT 可以将最佳管道导出为 Python 文件,这样就可以在模型未训练的情况下使用该管道进行训练,并在之后保存模型。如果模型已经训练,则只获取预测。

通过检查文件是否存在,可以确定模型是否已被训练。如果模型文件存在,我们可以假设模型已被训练,因此可以加载它并做出预测。否则,模型应该首先进行训练和保存,然后才能进行预测。

在连接到机器学习 API 时使用 POST 请求类型也是一个好主意。它比 GET 更好,因为参数值不会直接传递到 URL 中。正如你可能知道的,参数值可能是敏感的,所以尽可能隐藏它们是个好主意。

例如,你可能需要在做出预测之前通过 API 进行身份验证。很容易理解为什么直接在 URL 中发送用户名和密码凭证不是一个好主意。POST 方法可以解决这个问题,本章的其余部分将很好地利用这一点。

简而言之,在做出预测之前,你应该始终检查模型是否已训练,并在需要时进行训练。另一个要点是,在我们的情况下,POST 比 GET 更好。你现在已经了解了部署机器学习模型的一些基本最佳实践。在下一节中,我们将训练并部署模型到本地主机。

将机器学习模型部署到本地主机

在我们可以部署模型之前,我们需要先训练它。你已经知道如何使用 TPOT 进行训练的所有内容,所以我们不会在这里花费太多时间。目标是训练一个简单的 Iris 分类器,并以某种方式导出预测功能。让我们一步一步地完成这个过程:

  1. 像往常一样,第一步是加载库和数据集。你可以使用以下代码片段来完成此操作:

    import pandas as pd
    df = pd.read_csv('data/iris.csv')
    df.head()
    

    这是前几行看起来像什么:

    图 8.10 – Iris 数据集的前几行

    img/B16954_08_10.jpg

    图 8.10 – Iris 数据集的前几行

  2. 下一步是将特征与目标变量分开。这次,我们不会将数据集分为训练集和测试集,因为我们不打算评估模型的性能。换句话说,我们知道模型表现良好,现在我们想在整个数据集上重新训练它。此外,目标变量中的字符串值将按照以下方式重映射为其整数表示:

    a) Setosa – 0

    b) Virginica – 1

    c) Versicolor – 2

    以下代码行执行了之前描述的所有操作:

    X = df.drop('species', axis=1)
    y = df['species']
    y = y.replace({'setosa': 0, 'virginica': 1, 'versicolor': 2})
    y
    

    现在的目标变量看起来是这样的:

    图 8.11 – 值重映射后的目标变量

    img/B16954_08_11.jpg

    图 8.11 – 值重映射后的目标变量

  3. 下一个步骤是模型训练。我们将使用 TPOT 进行 15 分钟的模型训练。这部分你应该很熟悉,因为我们没有使用之前章节中没有使用或描述过的任何参数。

    以下代码片段将在整个数据集上训练模型:

    from tpot import TPOTClassifier
    clf = TPOTClassifier(
        scoring='accuracy',
        max_time_mins=15,
        random_state=42,
        verbosity=2
    )
    clf.fit(X, y)
    

    在训练过程中,你会看到许多输出,但达到 100%的准确率不应该花费太多时间,如下图所示:

    图 8.12 – TPOT 训练过程

    img/B16954_08_12.jpg

    图 8.12 – TPOT 训练过程

    在 15 分钟的时间框架内,将经过多少代取决于你的硬件,但一旦完成,你应该看到类似以下的内容:

    图 8.13 – 训练后的 TPOT 输出

    img/B16954_08_13.jpg

    图 8.13 – 训练后的 TPOT 输出

  4. 一旦训练过程完成,你将能够访问fitted_pipeline_属性:

    clf.fitted_pipeline_
    

    这是一个可以导出以供以后使用的管道对象。以下是它应该看起来像什么(请注意,你可能在你的机器上看到不同的结果):

    图 8.14 – TPOT 拟合管道

    img/B16954_08_14.jpg

    图 8.14 – TPOT 拟合管道

  5. 为了演示这个管道的工作原理,请查看以下代码片段。它使用一个表示单个花种的二维输入数据数组调用fitted_pipeline_属性的predict()函数:

    clf.fitted_pipeline_.predict([[5.1, 3.5, 0.2, 3.4]])
    

    结果显示在以下图中:

    图 8.15 – TPOT 预测

    img/B16954_08_15.jpg

    图 8.15 – TPOT 预测

    记得我们几页前的重映射策略吗?0表示这种物种被分类为setosa

  6. 我们必须完成的最后一步是将此模型的预测能力保存到文件中。joblib 库使得这一步变得简单,因为你只需要调用 dump() 函数来保存模型,并调用 load() 函数来加载模型。

    这里有一个快速演示。目标是把 fitted_pipeline_ 属性保存到名为 iris.model 的文件中。你可以使用以下代码来完成:

    import joblib
    joblib.dump(clf.fitted_pipeline_, 'iris.model')
    

    就这么简单!一旦模型保存到文件中,你将看到以下输出:

![图 8.16 – 保存 TPOT 模型

![图片 B16954_08_16.jpg]

图 8.16 – 保存 TPOT 模型

为了验证模型仍然可以正常工作,你可以使用 load() 函数在新的变量中加载模型:

loaded_model = joblib.load('iris.model')
loaded_model.predict([[5.1, 3.5, 0.2, 3.4]])

上述代码的输出显示在下图中:

![图 8.17 – 保存模型的预测

![图片 B16954_08_17.jpg]

图 8.17 – 保存模型的预测

就这样,保存机器学习模型以供以后使用变得非常简单。我们现在拥有了部署此模型所需的一切,所以让我们继续下一步。

模型部署过程将与之前使用 FlaskFlask-RESTful 所做的非常相似。在继续逐步指南之前,你应该为你的 API 创建一个目录,并具有以下目录/文件结构:

![图 8.18 – API 目录结构

![图片 B16954_08_18.jpg]

图 8.18 – API 目录结构

如你所见,根目录被命名为 api,其中包含两个 Python 文件 —— app.pyhelpers.py。该文件夹还有一个用于存储之前训练的模型的文件夹。

让我们一步一步地构建 API:

  1. 让我们从 helpers.py 文件开始。这个 Python 文件的目标是从 app.py 中移除所有计算和数据操作。后者仅用于声明和管理 API 本身,其他所有操作都在其他地方执行。

    helpers.py 文件将包含两个函数 —— int_to_species(in_species)predict_single(model, X)

    第一个函数的目标是反转我们之前声明的映射,并给出整数表示的实际花卉种类名称。以下是给出整数输入时返回的字符串的具体列表:

    a) 0 – setosa

    b) 1 – virginica

    c) 2 – versicolor

    如果传递了其他数字,则返回一个空字符串。你可以按照以下方式找到此函数的代码:

    def int_to_species(in_species):
        if in_species == 0:
            return 'setosa'
        if in_species == 1:
            return 'virginica'
        if in_species == 2:
            return 'versicolor'
    

    接下来是 predict_single(model, X) 函数。该函数旨在根据模型和输入值的列表返回预测及其概率。该函数还执行以下检查:

    a) X 是一个列表吗?如果不是,则抛出异常。

    b) X 是否有四个项目(花瓣长度、花瓣宽度、花萼长度和花萼宽度)?如果不是,则抛出异常。

    这些检查是必需的,因为我们不希望不良或格式错误的数据进入我们的模型并使 API 崩溃。

    如果所有检查都通过,预测和概率将作为字典返回给用户,并列出每个参数的输入数据。以下是实现此函数的方法:

    def predict_single(model, X):
        if type(X) is not list:
            raise Exception('X must be of list data type!')
        if len(X) != 4:
            raise Exception('X must contain 4 values - \
    sepal_length, sepal_width, petal_length, petal_width')
        prediction = model.predict([X])[0]
        prediction_probability =\
    model.predict_proba([X])[0][prediction]
        return {
            'In_SepalLength': X[0],
            'In_SepalWidth': X[1],
            'In_PetalLength': X[2],
            'In_PetalWidth': X[3],
            'Prediction': int_to_species(prediction),
            'Probability': prediction_probability
        }
    

    这里是调用 predict_single() 函数的一个示例:

    predict_single(
        model=joblib.load('api/model/iris.model'), 
        X=[5.1, 3.5, 0.2, 3.4]
    )
    

    结果如下所示:

    图 8.19 – 调用 predict_single() 函数的结果

    图 8.19 – 调用 predict_single() 函数的结果

  2. 现在,让我们转向 app.py。如果您从本章的开头就一直在跟进,编写这个文件将易如反掌。目标是始终加载模型,并在调用 /predict 端点时触发 PredictSpecies 类的 post() 方法。您将不得不自己实现这个类和方法。

    用户必须以 JSON 格式传递输入数据。更准确地说,每个花卉测量值都是单独传递的,因此用户总共需要指定四个参数的值。

    如果一切顺利,helpers.py 中的 predict_single() 函数将被调用,并将结果返回给用户。

    让我们看看 app.py 的实现:

    import joblib 
    import warnings
    from flask import Flask, request, jsonify
    from flask_restful import Resource, Api
    from helpers import predict_single
    warnings.filterwarnings('ignore')
    app = Flask(__name__)
    api = Api(app)
    model = joblib.load('model/iris.model')
    class PredictSpecies(Resource):
        @staticmethod
        def post():
            user_input = request.get_json()
            sl = user_input['SepalLength']
            sw = user_input['SepalWidth']
            pl = user_input['PetalLength']
            pw = user_input['PetalWidth']
            prediction =\
    predict_single(model=model, X=[sl, sw, pl, pw])
            return jsonify(prediction)
    api.add_resource(PredictSpecies, '/predict')
    if __name__ == '__main__':
        app.run(host='0.0.0.0', port=8000)
    
  3. 您现在拥有了运行 API 所需的一切。您可以像之前使用其他 API 一样运行它,即在终端中执行以下行:

    > python app.py
    

    如果一切顺利,您将看到以下消息:

    图 8.20 – 运行 API

    图 8.20 – 运行 API

  4. API 现在正在 http://localhost:8000 上运行。我们将使用 Postman 应用程序来测试 API。

    这里是第一个示例:

图 8.21 – API 测试示例 1

图 8.21 – API 测试示例 1

如您所见,模型 100% 确信这种物种属于 setosa 类。让我们再试一个:

图 8.22 – API 测试示例 2

图 8.22 – API 测试示例 2

这具有相同的置信水平,但预测类别不同。让我们来点不一样的,传递一些与训练集不同的值:

图 8.23 – API 测试示例 3

图 8.23 – API 测试示例 3

如您所见,这次模型并不完全自信,因为输入数据与训练时看到的数据有很大不同。

到此为止,TPOT 模型部署到本地主机!本章剩下的唯一事情是将模型带到云端,使其在任何地方都可以访问。让我们接下来这么做。

将机器学习模型部署到云

云部署机器学习模型意味着创建一个云虚拟机,将我们的 API 转移到它上面,并运行它。这是一个繁琐的过程,但由于涉及许多步骤,随着重复执行会变得更容易。如果您从这个部分精确地遵循每个步骤,一切都会顺利。只需确保不要遗漏任何细节:

  1. 要开始,请访问 portal.aws.amazon.com/billing/signup#/start 并创建一个账户(假设您还没有一个)。以下是网站目前的模样(截至 2021 年 2 月):![图 8.24 – AWS 注册网站

    ![图片 B16954_08_24.jpg]

    图 8.24 – AWS 注册网站

    注册过程将花费一些时间,您将需要输入您的信用卡信息。请放心;我们将创建完全免费的虚拟机实例,所以您不会收取任何费用。

  2. 一旦注册过程完成,点击搜索栏中的 ubuntu:![图 8.26 – Ubuntu Server 20.04

    ![图片 B16954_08_26.jpg]

    图 8.26 – Ubuntu Server 20.04

    一旦点击 选择,您将需要指定类型。如果您不想被收费,请确保选择免费版本:

    ![图 2.27 – Ubuntu 实例类型

    ![图片 B16954_08_27.jpg]

    图 2.27 – Ubuntu 实例类型

    然后,点击 审查和启动 按钮。您将被带到以下屏幕:

    ![图 2.28 – Ubuntu 实例确认

    ![图片 B16954_08_28.jpg]

    图 2.28 – Ubuntu 实例确认

    一旦点击 启动,将出现以下窗口。请确保选择相同的选项,但密钥对名称由您决定:

    ![图 8.29 – Ubuntu 密钥对

    ![图片 B16954_08_29.jpg]

    图 8.29 – Ubuntu 密钥对

    在输入详细信息后,点击 下载密钥对 按钮。下载完成后,您将能够点击 启动实例 按钮:

    ![图 8.30 – 启动 Ubuntu 实例

    ![图片 B16954_08_30.jpg]

    图 8.30 – 启动 Ubuntu 实例

    最后,完成所有操作后,您可以点击 查看实例 按钮:

    ![图 8.31 – 查看实例

    ![图片 B16954_08_31.jpg]

    图 8.31 – 查看实例

    您将立即看到您创建的实例。在看到实例正在运行之前可能需要一些时间,所以请耐心等待:

    ![图 8.32 – 运行实例

    ![图片 B16954_08_32.jpg]

    图 8.32 – 运行实例

  3. 要获取连接参数,请点击实例行,并在 密钥文件 选项中选择 .ppk 文件。按下 连接 按钮后,您将看到以下内容:![图 8.36 – FileZilla 主机密钥

    ![图片 B16954_08_36.jpg]

    图 8.36 – FileZilla 主机密钥

    只需按 确定,您就可以开始了。连接成功,如下图所示:

    ![图 8.37 – FileZilla 成功连接

    ![图片 B16954_08_37.jpg]

    图 8.37 – FileZilla 成功连接

    您现在可以将 api 文件夹拖动到远程虚拟机上的 ubuntu 文件夹中,如图所示:

    ![图 8.38 – 将 API 数据传输到远程虚拟机

    ![图片 B16954_08_38.jpg]

    图 8.38 – 将 API 数据传输到远程虚拟机

    在进一步配置和启动 API 之前,让我们看看您如何通过终端获取连接。

  4. 在存储 .pem 文件的新终端窗口中打开。一旦进入,执行以下命令以更改权限:

    TPOT_Book_KeyPair.pem with your filename and also make sure to write your instance name after ubuntu@. If you did everything correctly, you should see the following in your terminal:![Figure 8.39 – Accessing the Ubuntu virtual machine through the terminal    ](https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-auto-tpot/img/B16954_08_39.jpg)
    
    

    sudo apt-get update && sudo apt-get install python3-pip

    
    Finally, let's install every required library. Here's the command for doing so within a virtual environment:
    
    

    pip3 install virtualenv

    virtualenv tpotapi_env

    source tpotapi_env/bin/activate

    pip3 install joblib flask flask-restful sklearn tpot

    
    There are a few steps you still need to complete before starting the API, such as managing security.
    
  5. 如果现在运行 API,不会引发错误,但您将无法以任何方式访问 API。这是因为我们首先需要“修复”一些权限。简单来说,我们的 API 需要从任何地方可访问,但默认情况下并不是。

    首先,导航到 网络与安全 | 安全组 侧边栏:

    图 8.40 – 安全组

    图 8.40 – 安全组

    您应该在浏览器窗口的右上角看到 创建安全组 按钮:

    图 8.41 – 创建安全组按钮

    图 8.41 – 创建安全组按钮

    当新窗口弹出时,您必须指定一些内容。安全组名称描述 字段完全是任意的。另一方面,入站规则 组不是任意的。您必须添加一个具有以下选项的新规则:

    a) 类型: 所有流量

    b) : 任何地方

    参考以下图示获取更多信息:

    图 8.42 – 创建安全组

    图 8.42 – 创建安全组

    在指定正确的值后,您必须滚动到屏幕底部并点击 创建安全组 选项:

    图 8.43 – 验证安全组

    图 8.43 – 验证安全组

    我们还没有完成。下一步是转到侧边栏上的 网络与安全 | 网络接口 选项:

    图 8.44 – 网络接口选项

    图 8.44 – 网络接口选项

    一旦进入,右键单击唯一可用的网络接口(假设这是您第一次进入 AWS 控制台)并选择 更改安全组 选项:

    图 8.45 – 更改安全组

    图 8.45 – 更改安全组

    整个目的就是将“从任何地方访问”的规则分配给我们的虚拟机。一旦弹出窗口,从选项下拉列表中选择之前声明的安全组:

    图 8.46 – 选择安全组

    图 8.46 – 选择安全组

    添加完成后,点击 保存 按钮保存此关联:

    图 8.47 – 保存安全关联

    图 8.47 – 保存安全关联

  6. 配置我们的虚拟机是一个相当漫长的过程,但现在您终于可以启动 Flask 应用程序(REST API)了。为此,导航到 /api 文件夹并执行以下操作:

    > python3 app.py
    

    到现在为止,你应该看到一个熟悉的消息:

    图 8.48 – 通过终端启动 REST API

    图 8.48 – 通过终端启动 REST API

    就这样!API 现在正在运行,我们可以测试它是否正常工作。

  7. 在通过 Postman 发送请求之前,我们首先需要找到远程虚拟机的完整 URL。你可以通过在实例下右键单击实例并点击连接选项来找到它。在那里,你会看到SSH 客户端标签页:

图 8.49 – 虚拟机 URL

图 8.49 – 虚拟机 URL

现在我们知道了完整的 URL:http://ec2-18-220-113-224.us-east-2.compute.amazonaws.com:8000/predict。从现在开始,流程与在本地主机上相同,如下图所示:

图 8.50 – 测试我们部署的 API

图 8.50 – 测试我们部署的 API

如你所见,连接已经建立,API 返回了与本地部署版本相同的响应。

就这样,你就有了一个将机器学习模型部署到 AWS 虚拟机的完整流程。这个过程可能相当繁琐,如果是第一次尝试,甚至可能有些棘手。随着你部署越来越多的机器学习模型,这个过程会变得越来越容易,因为流程是相同的。

如果你不想让任何人从任何地方访问你的 API,你可以玩一下权限,但这超出了本书的范围。这一章大致结束了——做得很好!接下来是所学内容的总结,然后是另一个有趣且实用的章节。

摘要

这一章是目前最长的,而且非常注重实践任务。你希望已经能够跟上并学习了如何部署使用 TPOT 构建的机器学习模型——无论是在本地还是在云端。

现在你已经能够部署任何使用 Python 构建的机器学习模型。此外,你还知道如何部署基本的 Python 网络应用程序,前提是你具备前端技术(如 HTML、CSS 和 JavaScript)的相关知识。我们没有深入这个领域,因为这不属于本书的范围。

在以下章节中第九章在生产中使用部署的 TPOT 模型,你将学习如何围绕这个 REST API 构建一个基本的应用程序。更准确地说,你将学习如何制作一个简单且外观不错的网页界面,该界面可以根据输入数据预测花卉种类。但在那之前,你将练习使用 Python 向我们的 API 发送请求。

和往常一样,你可以自由地更详细地研究模型部署,因为 AWS 不是唯一的选择。有许多云服务提供商提供某种免费层,AWS 只是池塘中的一条鱼。

问题

  1. 我们为什么需要(并希望)模型部署?

  2. REST API 在模型部署中扮演什么角色?

  3. 列举一些可以用来部署机器学习模型的 Python 库。

  4. GET 和 POST 请求类型之间有什么区别?

  5. Python 中 joblib 库背后的基本思想是什么?

  6. 用你自己的话解释什么是虚拟机。

  7. 哪个免费工具可以用来测试 REST API?

第九章:在生产中使用部署的 TPOT 模型

您已经到达了最后一章——恭喜!到目前为止,您通过解决分类和回归任务学习了 TPOT 的基础知识,了解了 TPOT 如何与 Dask 和神经网络协同工作,以及如何在本地和云端部署机器学习模型。

本章将作为甜点,因为您将学习如何与您的部署模型进行通信,以构建即使是 5 岁的孩子也能使用的东西。更具体地说,您将学习如何通过笔记本环境和简单的 GUI 网页应用程序与您的 API 进行通信。

本章将涵盖以下主题:

  • 在笔记本环境中进行预测

  • 开发一个简单的 GUI 网页应用程序

  • 在 GUI 环境中进行预测

技术要求

这是本书的最后一章,因此假设您有一些先验知识。您需要知道如何使用 TPOT 构建基本的机器学习模型以便部署。假设您的模型已部署到在 第八章**,TPOT 模型部署中创建的 AWS 虚拟机上。如果不是这种情况,请回顾该章节。

本章将包含大量的代码,如果您遇到困难,可以参考官方 GitHub 仓库:github.com/PacktPublishing/Machine-Learning-Automation-with-TPOT/tree/main/Chapter09

在笔记本环境中进行预测

如果您在上一章之后休息了一天(或几天),那么您与远程虚拟机的连接可能已经结束。因此,您需要重新连接并再次启动 API。有方法可以使您的 API 总是运行,但这超出了本书的范围。此外,如果您已将 TPOT_Book_KeyPair.pem 文件移动到其他文件夹,您将需要重置权限:

  1. 考虑到这一点,如果您需要重置权限,请仅执行以下片段中的第一个命令行:

    > chmod 400 TPOT_Book_KeyPair.pem
    > ssh -i "TPOT_Book_KeyPair.pem" ubuntu@ec2-18-220-113-224.us-east-2.compute.amazonaws.com
    > cd api
    > python3 app.py
    
  2. 您的 API 正在运行。下一步是打开 JupyterLab 或 Jupyter Notebook 环境,并发出请求。您需要 requests 库来完成此操作,以下是导入它的方法:

    import requests
    

    接下来,让我们声明几个变量。这些变量将保存主机名、端口和端点的值:

    HOST ='http://ec2-18-220-113-224.us-east-2.compute.amazonaws.com'
    PORT = '8000'
    ENDPOINT = '/predict'
    

    从那里,我们可以轻松地将这三个变量连接成一个,形成一个 URL:

    URL = f'{HOST}:{PORT}{ENDPOINT}'
    URL
    

    这就是它应该看起来像的样子:

    ![图 9.1 – URL 连接字符串

    ![img/B16954_09_1.jpg]

    图 9.1 – URL 连接字符串

    由于主机名的差异,您的操作可能略有不同。

  3. 接下来,我们将声明一个字典,它将作为输入数据。它将与通过 Postman 发送的上一章中的数据相同。以下是代码片段:

    in_data = {
        'SepalLength': 0.4,
        'SepalWidth': 3.1,
        'PetalLength': 0.1,
        'PetalWidth': 14
    }
    

    这就是我们需要进行请求的所有内容。让我们接下来这么做。

  4. 你可以使用 requests 包中的 post() 函数来发送 POST 请求。需要两个参数——URL 和 JSON 格式的数据:

    req = requests.post(url=URL, json=in_data)
    req
    

    结果将在以下图中显示:

    response = req.content
    response
    

    这就是响应的外观:

    图 9.3 – API 响应作为字符串

    ![图片 B16954_09_3.jpg]

    图 9.3 – API 响应作为字符串

    如你所见,预测成功返回了,但默认情况下不是所需的格式。

  5. 要改变这一点,你需要将响应字符串转换为 JSON 对象。你可以使用 json 包中的 loads() 函数来完成此操作:

    import json
    response_json = json.loads(response)
    response_json
    

    这里是结果:

    ![图 9.4 – API 响应作为 JSON 对象]

    ![图片 B16954_09_4.jpg]

    图 9.4 – API 响应作为 JSON 对象

  6. 你可以像访问普通字典对象一样访问预测的类别(或任何其他属性)。以下是一个示例:

    response_json['Prediction']
    

    这是返回的内容:

![图 9.5 – API 预测类别]

![图片 B16954_09_5.jpg]

图 9.5 – API 预测类别

这基本上就是你可以使用 Python 从部署的 REST API 获取预测的方法!在下一节中,你将围绕这个 API 构建一个基本的交互式网络应用程序,使其对任何人来说都极其简单易用。

开发一个简单的 GUI 网络应用程序

本节旨在展示如何使用 Flask 框架开发一个简单的网络应用程序。重点转向构建一个捕获表单数据的应用程序,然后将其传递到我们部署的机器学习 API:

  1. 首先,创建以下目录结构:![图 9.6 – 网络应用程序目录结构]

    ![图片 B16954_09_6.jpg]

    图 9.6 – 网络应用程序目录结构

    大部分逻辑都在 app.py 中处理,而 templates 文件夹用于存储应用中的 HTML 文件——稍后我会详细介绍这一点。

  2. 这次我们将更好地组织代码,因此你需要创建一个额外的文件来存储环境变量。在根目录 (webapp) 中创建一个名为 .env 的文件——并填充以下内容:

    SECRET_KEY=SecretKey
    HOST=0.0.0.0
    PORT=9000
    API_ENDPOINT=http://ec2-18-220-113-224.us-east-2.compute.amazonaws.com:8000/predict
    

    创建这样的单独文件被认为是开发任何类型的网络应用程序的最佳实践。

    要使用这些环境变量,你需要在虚拟环境中安装一个额外的包:

    > pip install python-dotenv
    
  3. 现在我们来构建应用程序的基本结构。打开 app.py 文件并编写以下代码:

    import os
    from flask import Flask, render_template
    from dotenv import load_dotenv
    load_dotenv('.env')
    app = Flask(__name__)
    @app.route('/')
    def index():
        return render_template('index.html')
    if __name__ == '__main__':
        app.run(host=os.getenv('HOST'), port=os.getenv('PORT'))
    

    如果你现在运行应用程序,你不会得到错误,但屏幕上不会显示任何内容。原因是简单的——我们还没有处理 index.html 文件。在我们这样做之前,让我们讨论代码中唯一可能不熟悉的部分:render_template() 函数。简单地说,这个函数将显示一个 HTML 文件,而不是显示函数返回的字符串或值。有一种方法可以传递参数,但稍后再说。

  4. 接下来是 index.html——以下是你可以粘贴到文件中的代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Iris Predictioner</title>
    </head>
    <body>
        <h1>Welcome to Iris Predictioner</h1>
    </body>
    </html>
    

    如果你之前没有写过一行 HTML,不要担心——它是一种简单的标记语言。把这里看到的一切都当作样板。我们感兴趣的是<body></body>标签内的内容。

    如果你现在运行你的应用程序,它看起来会是这样:

    图 9.7 – 爱尔兰预测应用程序(v1)

    图 9.7 – 爱尔兰预测应用程序(v1)

    这是一个简单且极其无聊的 Web 应用程序,但至少它工作了。

  5. 如前所述,我们的 Web 应用程序必须以某种方式处理表单数据,因此让我们开始工作。有一个专门的包用于处理Flask中的表单数据,称为Flask-WTF。以下是安装它的方法:

    forms.py file in the root directory – /webapp/forms.py. Let's take a look at the code this file contains and explain it:
    
    

    from flask_wtf import FlaskForm

    from wtforms import FloatField, SubmitField

    from wtforms.validators import DataRequired

    class IrisForm(FlaskForm):

    sepal_length = FloatField(

    label='花瓣长度', 验证器=[DataRequired()]

    )

    sepal_width = FloatField(

    label='花瓣宽度', 验证器=[DataRequired()]

    )

    petal_length = FloatField(

    label='花瓣长度', 验证器=[DataRequired()]

    )

    petal_width = FloatField(

    label='花瓣宽度', 验证器=[DataRequired()]

    )

    submit = SubmitField(label='预测')

    
    Okay, so what's going on in this file? Put simply, `Flask-WTF` allows us to declare forms for `Flask` applications easily, in a class format. We can use any of the built-in field types and validators. For this simple example, we'll only need float and submit fields (for flower measurements and the submit button). Validation-wise, we only want that no fields are left blank.That's all you need to do, and `Flask` will take care of the rest.
    
  6. 接下来是app.py。需要做一些更改:

    • Flask-WTF表单需要一个配置好的密钥才能工作。你可以通过访问.env文件来添加它。你声明的值完全是任意的。

    • 我们现在的索引路由需要允许 POST 和 GET 方法,因为它将处理表单。在index()函数内部,你将需要实例化之前编写的IrisForm类,并在点击提交按钮且没有验证错误时返回相关结果。

      你可以使用validate_on_submit()函数进行检查。如果检查通过,输入数据将以标题格式返回(我们稍后会看到如何显示预测)。如果没有通过,则返回index.html模板。

    • 现在调用render_template()时,会传递一个参数到我们的 HTML 文件——iris_form。这给了 HTML 文件访问表单数据的能力。你将在下一分钟看到如何处理它。

      这就是更改后你的文件应有的样子:

      import os
      from flask import Flask, render_template
      from forms import IrisForm
      from dotenv import load_dotenv
      load_dotenv('.env')
      app = Flask(__name__)
      app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
      @app.route('/', methods=['GET', 'POST'])
      def index():
          iris_form = IrisForm()
          if iris_form.validate_on_submit():
              return f'''
                      <h3>
                          Sepal Length: {iris_form.sepal_length.data}<br>
                          Sepal Width: {iris_form.sepal_width.data}<br>
                          Petal Length: {iris_form.petal_length.data}<br>
                          Petal Width: {iris_form.petal_width.data}
                      </h3>
                  '''
          return render_template('index.html', iris_form=iris_form)
      if __name__ == '__main__':
          app.run(host=os.getenv('HOST'), port=os.getenv('PORT'))
      

      我们几乎完成了。让我们接下来调整index.html文件。

  7. index.html是你需要调整以使应用程序工作的最后一个文件。我们需要的只是显示之前声明的字段的表单。同时,保护你的应用程序免受跨站请求伪造CSRF)攻击也是强制性的。为此,你必须在表单字段之前放置一个令牌。

    这就是 HTML 文件应有的样子:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Iris Predictioner</title>
    </head>
    <body>
        <h1>Welcome to Iris Predictioner</h1>
        <form method="POST" action="{{ url_for('index') }}">
            {{ iris_form.csrf_token }}
            {{ iris_form.sepal_length.label }} {{ iris_form.sepal_length(size=18) }}
            <br>
            {{ iris_form.sepal_width.label }} {{ iris_form.sepal_width(size=18) }}
            <br>
            {{ iris_form.petal_length.label }} {{ iris_form.petal_length(size=18) }}
            <br>
            {{ iris_form.petal_width.label }} {{ iris_form.petal_width(size=18) }}
            <br>
            <input type="submit" value="Predict">
        </form>
    </body>
    </html>
    

    如你所见,要访问从 Python 文件发送的参数,你必须用双大括号包围代码。

  8. 如果你现在启动应用程序,屏幕上会显示以下内容:

图 9.8 – 爱尔兰预测应用程序

图 9.8 – 爱尔兰预测应用程序

这就是你的机器学习应用的前端!它有点丑陋,但我们稍后会对其进行美化。让我们先测试一下功能。

我们不希望表单在任何一个输入值都为空的情况下被提交。如果立即按下按钮,会发生以下情况:

![图 9.9 – 爱尔兰预测应用表单验证(1)图片 B16954_09_9.jpg

图 9.9 – 爱尔兰预测应用表单验证(1)

验证测试 1 – 完成。让我们看看如果只有一个输入字段为空会发生什么:

![图 9.10 – 爱尔兰预测应用表单验证(2)图片 B16954_09_10.jpg

图 9.10 – 爱尔兰预测应用表单验证(2)

发生了同样的消息,正如你所期望的那样。为了总结,如果任何一个输入字段为空,表单将无法提交。

要继续,填写所有字段,如图所示:

![图 9.11 – 爱尔兰预测应用表单值图片 B16954_09_11.jpg

图 9.11 – 爱尔兰预测应用表单值

如果你现在点击按钮,你会看到以下结果:

![图 9.12 – 爱尔兰预测应用结果图片 B16954_09_12.jpg

图 9.12 – 爱尔兰预测应用结果

到目前为止,一切正常,但在将应用连接到我们的爱尔兰预测 API 之前,我们还需要做一步——美化。这一步是可选的,因为如果你决定立即跳到 API 连接部分,应用仍然可以工作。

为你的 Flask 应用设置适当的样式需要一点工作和重构。你可以在这里找到整个步骤列表。请注意,这本书假设没有 HTML 和 CSS 知识。你可以自由地复制粘贴这些文件的内容,但鼓励你自己进一步探索:

  1. 让我们从 app.py 开始。我们不会返回一个包含打印为单个长字符串的输入值的 H2 标签,而是返回一个将显示表格的 HTML 模板。现在,我们只填写输入数据,并为预测和预测概率设置假值。

    这里是更改后的文件应该看起来是什么样子:

    import os
    from flask import Flask, render_template
    from forms import IrisForm
    from dotenv import load_dotenv
    load_dotenv('.env')
    app = Flask(__name__)
    app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
    @app.route('/', methods=['GET', 'POST'])
    def index():
        iris_form = IrisForm()
        if iris_form.validate_on_submit():
            return render_template(
                'predicted.html',
                sepal_length=iris_form.sepal_length.data,
                sepal_width=iris_form.sepal_width.data,
                petal_length=iris_form.petal_length.data,
                petal_width=iris_form.petal_width.data,
                prediction='Prediction',
                probability=100000
            )
        return render_template('index.html', iris_form=iris_form)
    if __name__ == '__main__':
        app.run(host=os.getenv('HOST'), port=os.getenv('PORT'))
    
  2. 让我们在做这件事的同时创建一个模板文件。在 /templates 目录下创建一个 predicted.html 文件。如前所述,这个文件将包含一个显示 API 响应的表格(一旦我们实现它)。

    这里是文件应该看起来是什么样子:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
        <title>Iris Predictioner</title>
    </head>
    <body>
        <div class="container">
            <h1>Predictions:</h1>
            <table>
                <thead>
                  <tr><th>Attribute</th><th>Value</th></tr>
                </thead>
                <tbody>
                  <tr><td>Sepal Length</td><td>{{ sepal_length }}</td></tr>
                  <tr><td>Sepal Width</td><td>{{ sepal_width }}</td></tr>
                  <tr><td>Petal Length</td>td>{{ petal_length }}</td></tr>
                  <tr><td>Petal Width</td><td>{{ petal_width }}</td></tr>
                  <tr><td>Prediction</td><td>{{ prediction }}</td></tr>
                  <tr><td>Probability</td><td>{{ probability }}</td></tr>
                </tbody>
            </table>
        </div>
    </body>
    </html>
    

    如你所见,我们已经利用了参数传递的强大功能来显示数据在预测模型中的进出。如果你对文档头中 CSS 文件链接的问题感到好奇——现在不用担心。在处理 CSS 之前,我们还需要处理一件事。

  3. 最后,让我们重新格式化 index.html。这个文件只需要进行一些小的更改——几个 CSS 类和几个 div 元素。以下是重新格式化版本的整个代码片段:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
        <title>Iris Predictioner</title>
    </head>
    <body>
        <div class="container">
            <h1>Welcome to Iris Predictioner</h1>
            <form method="POST" action="{{ url_for('index') }}">
                {{ iris_form.csrf_token }}
                <div class="single-input">
                    {{ iris_form.sepal_length.label }} {{ iris_form.sepal_length(size=18) }}
                </div>
                <div class="single-input">
                    {{ iris_form.sepal_width.label }} {{ iris_form.sepal_width(size=18) }}
                </div>
                <div class="single-input">
                    {{ iris_form.petal_length.label }} {{ iris_form.petal_length(size=18) }}
                </div>
                <div class="single-input">
                    {{ iris_form.petal_width.label }} {{ iris_form.petal_width(size=18) }}
                </div>
                <input class="btn-submit" type="submit" value="Predict">
            </form>
        </div>
    </body>
    </html>
    
  4. 我们几乎完成了。到目前为止,你已经重构了所有需要重构的文件,现在你将创建一个额外的文件夹和文件。在根目录下,创建一个名为static的文件夹。创建后,在它里面创建一个名为css的额外文件夹。这个文件夹将包含我们应用程序的所有样式。在css文件夹中,创建一个名为main.css的文件。

    总结一下,创建这些文件夹和文件后,你的目录结构应该如下所示:

    图 9.13 – 新的目录结构

    @import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600&display=swap');
    * { margin: 0; padding: 0; box-sizing: border-box;
        font-family: 'Open Sans', sans-serif; }
    body { background-color: #f2f2f2; }
    .container { width: 800px; height: 100vh; margin: 0 auto;
        background-color: #ffffff; padding: 0 35px; }
    .container > h1 { padding: 35px 0; font-size: 36px;
        font-weight: 600; }
    .single-input { display: flex; flex-direction: column;
        margin-bottom: 20px; }
    .single-input label { font-weight: 600; }
    .single-input label::after { content: ":" }
    .single-input input { height: 35px; line-height: 35px;
        padding-left: 10px; }
    .btn-submit { width: 100%; height: 35px;
        background-color: #f2f2f2; font-weight: 600;
        cursor: pointer; border: 2px solid #dddddd;
        border-radius: 8px; }
    table { font-size: 18px; width: 100%; text-align: left; }
    

    完成了。让我们运行应用程序,看看现在的样子。

  5. 如果你现在重新运行应用程序,你会看到样式开始生效。以下图示显示了输入表单的外观:

图 9.14 – 样式化的 Iris 预测应用程序

图 9.14 – 样式化的 Iris 预测应用程序

应用程序现在远非完美,但至少已经是一个可展示的形式。让我们按照以下图示来完善它:

图 9.15 – 样式化的 Iris 预测应用程序(2)

图 9.15 – 样式化的 Iris 预测应用程序(2)

最后,让我们点击预测按钮,看看另一页的样子:

图 9.16 – Iris 预测应用程序预测

图 9.16 – Iris 预测应用程序预测

在样式方面,我们可以到此为止。应用程序现在看起来相当不错,但你也可以自由地进一步调整它。

由此,你就可以看到如何构建和样式化一个围绕机器学习模型构建的Flask应用程序。下一节将连接我们的 API,使应用程序完全可用。在那里见。

在 GUI 环境中进行预测

欢迎来到本书的最后一部分。本节将把我们的简单 Web 应用程序与已部署的机器学习 API 联系起来。这非常类似于生产环境,其中你部署了一个或多个机器学习模型,而应用程序开发团队希望将其用于他们的应用程序。唯一的区别是,你既是数据科学团队也是应用程序开发团队。

再次强调,我们还需要对应用程序结构进行一些修改:

  1. 让我们从简单的部分开始。在根目录下,创建一个名为predictor.py的 Python 文件。这个文件将包含一个函数,该函数实现了本章开头在笔记本环境中进行预测时讨论的逻辑。

    简而言之,这个函数必须向 API 发送 POST 请求,并以 JSON 格式返回响应。

    这是文件中的整个代码片段:

    import os
    import json
    import requests
    from dotenv import load_dotenv
    load_dotenv('.env')
    def predict(sepal_length, sepal_width, petal_length, petal_width):
        URL = os.getenv('API_ENDPOINT')
        req = requests.post(
            url=URL,
            json={
                'SepalLength': sepal_length,
                'SepalWidth': sepal_width,
                'PetalLength': petal_length,
                'PetalWidth': petal_width
            }
        )
        response = json.loads(req.content)
        return response
    

    请记住,URL 参数的值在你的机器上可能会有所不同,所以请相应地更改它。

    没有必要进一步解释这个代码片段,因为它几乎与你之前看到和编写的代码相同。

  2. 现在我们对 app.py 进行一些修改。我们将在输入字段验证后立即导入此文件并调用 predict() 函数。一旦返回响应,其值将作为参数传递给 return 语句的相应字段。

    这是 app.py 文件的整个代码片段:

    import os
    import numpy as np
    from flask import Flask, render_template
    from forms import IrisForm
    from predictor import predict
    from dotenv import load_dotenv
    load_dotenv('.env')
    app = Flask(__name__)
    app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
    @app.route('/', methods=['GET', 'POST'])
    def index():
        iris_form = IrisForm()
        if iris_form.validate_on_submit():
            pred_response = predict(
                sepal_length=iris_form.sepal_length.data,
                sepal_width=iris_form.sepal_width.data,
                petal_length=iris_form.petal_length.data,
                petal_width=iris_form.petal_width.data
            )
            return render_template(
                'predicted.html',
                sepal_length=pred_response['In_PetalLength'],
                sepal_width=pred_response['In_PetalWidth'],
                petal_length=pred_response['In_SepalLength'],
                petal_width=pred_response['In_SepalWidth'],
                prediction=pred_response['Prediction'],
                probability=f"{np.round((pred_response['Probability'] * 100), 2)}%"
            )
        return render_template('index.html', iris_form=iris_form)
    if __name__ == '__main__':
        app.run(host=os.getenv('HOST'), port=os.getenv('PORT'))
    

    如您所见,预测概率被转换为百分比并四舍五入到小数点后两位。这样做的原因只是为了在应用程序中输出更美观的格式。

  3. 现在是时候进行有趣的测试了。打开应用程序,在表单中输入一些数据。以下是一个示例:

图 9.17 – 爱尔兰预测应用程序最终测试

图 9.17 – 爱尔兰预测应用程序最终测试

一旦你点击了预测按钮,你将在屏幕上看到以下结果:

图 9.18 – 爱尔兰预测应用程序最终结果

图 9.18 – 爱尔兰预测应用程序最终结果

到此为止,你就有了一个基于部署的机器学习模型的完整且完全工作的 GUI Web 应用程序。

不包括随后的摘要,这是本章的最后一部分,也是整本书的结尾。你现在知道如何部署机器学习模型并在部署的模型周围构建简单的 Web 应用程序。恭喜!

摘要

本章属于实践性很强的章节,但我希望你已经设法跟上了。如果你做到了,你已经学到了很多——从如何在笔记本环境中进行预测到在简单和自定义构建的 Web 应用程序中进行预测。

不仅如此,你还完成了整本书。恭喜!在这九个章节中,你学到了很多。我们从机器学习的基础,通过基本的回归和分类示例开始,然后逐渐构建我们对 TPOT 的知识。你还学习了 TPOT 如何与并行训练和神经网络一起工作。但可能你获得的最重要的新技能是模型部署。没有它,你的模型就毫无用处,因为没有人可以使用它们来创造价值。

和往常一样,你可以自由探索 TPOT 以及它提供的所有惊人的功能。这本书应该为你提供一个很好的起点,因为它只用了几百页就带你从零开始构建围绕部署的自动化机器学习模型的 Web 应用程序。这确实值得骄傲!

问答

  1. 你可以使用哪个 Python 库向部署的 REST API 发送请求?

  2. 在发送 POST 请求时,数据以什么格式提供?

  3. 命名用于构建和操作表单的 Flask 扩展。

  4. 如果我们谈论的是进入机器学习模型的数据,为什么验证 Web 应用程序表单很重要?

  5. 你可以通过 Flask 将参数传递给 HTML 模板文件吗?如果是这样,你如何在 HTML 中显示它们的值?

  6. 解释将 CSS 文件链接到 Flask 应用程序的过程。

  7. 解释为什么将机器学习模型闲置在您的电脑上是没有意义的。

posted @ 2025-09-04 14:10  绝不原创的飞龙  阅读(7)  评论(0)    收藏  举报