低代码人工智能-全-
低代码人工智能(全)
原文:
zh.annas-archive.org/md5/9e2f86413b22bbbaac43a178023cd8ea译者:飞龙
前言
人工智能(AI)可以定义为计算机展示智能的广泛研究领域。短语“展示智能”是模糊的;它可以被解释为计算机做出我们期望从生物体中得到的决策。AI 的概念至少在神话中自古就存在。一个著名的例子是希腊神话中的塔洛斯,一个铜制机器人,用来保护欧罗巴免受希望绑架她的入侵者的侵害。随着世纪的流逝,基本形式的 AI 从神话世界进入了现实生活。
在现代,人工智能(AI)已经在增强人类能力、自动化决策以及其他对人们来说耗时的过程中找到了它的家园。专家系统,首次开发于 1970 年代,是现代人工智能的一个例子。专家系统利用知识库,这是一组事实和规则,以及推理系统来综合生成新知识。专家系统的主要缺点在于它需要领域专家花费时间和精力来创建知识库的事实和规则。
在最近几十年中,另一种形式的人工智能变得更加普遍。机器学习(ML)是一门使计算机从提供的数据中学习算法的学科,而不是程序员必须提供算法的学科。与专家系统相对,另一种表达方式是,ML 是使用数据来发现规则,而不是让专家为您编写规则。
ML 几乎触及到现今的每个行业。在零售业,ML 用于需求预测,提前几个月预测产品或服务的预期销售量。旅游业使用 ML 根据客户的过往旅行历史和其他信息推荐兴趣点和目的地给顾客。在医疗保健领域,ML 不仅可以用于确定 X 射线图像是否包含健康或患病的肺部,还可以精确定位导致医疗专家进一步探索的 X 射线图像区域。ML 的活跃应用清单可以填满一本完整的书。在本书中,我们专注于一些具体的 ML 用例,包括广告媒体渠道销售、能源生产和客户流失等,只是举几个例子。
在工业中应用 ML 的应用非常广泛,探索不同的可能性非常令人兴奋。许多人的一个假设是,ML 只是专家研究的领域。也就是说,除非你在许多不同领域(计算机科学、数学、统计学等)有大量的背景知识,否则你没有希望在实践中使用 ML。事实并非如此。
近年来,“公民数据科学家”的概念变得越来越普遍。公民数据科学家指的是那些并非必须拥有数据科学或相关领域正规教育和/或角色的人,但可以在其他领域特定专业知识的支持下进行一些数据科学工作的人。为公民数据科学家开发了许多易于使用的 ML 工具,本书的目标是使更多的人成为公民数据科学家并鼓励他们。
谁应该阅读这本书?
本书的目标是教读者如何为结构化(表格)数据框架化机器学习问题,准备他们的数据用于机器学习工作流程,并使用不同的无代码、低代码和一些基本自定义代码解决方案构建和使用机器学习模型。你将逐步进行这些目标的过程,以一个具体的业务问题框架来理解。本书的主要读者包括业务分析师、数据分析师、学生和有志于成为公民数据科学家的人,他们希望快速学习如何使用自动化机器学习(AutoML)、BigQuery ML(使用 SQL)和 Python 中的定制培训将机器学习应用于他们的工作。我们假设读者对数据分析有一些基本的了解,但不需要是专家也能从本书中受益。
任何考虑转向数据科学和/或机器学习工程的职业的人,可能会发现这本书是实现目标的一个很好的第一步。机器学习从业者可能会觉得这本书对他们的需求太基础,但如果他们对一些正在使用的工具不熟悉,可能会发现书中关于这些工具的讨论有所帮助。
读者不需要有机器学习或特定编程语言的先验知识,但如果对编程概念、Python 和 SQL 有一些基本的了解,会觉得这本书更容易阅读。我们在整本书中都会提到附加的基础材料参考。除了机器学习的概念和基于用例的示例,你还会探索不同的工具,如 Jupyter Notebooks 和 Linux 终端的基本使用。
本书的内容包括什么和不包括什么?
本书旨在成为希望成为机器学习从业者的人的第一步,而不是使你成为机器学习专家的书。我们不详细介绍机器学习的理论,也不涵盖数据科学成功所需的统计学和数学的所有主题。我们涵盖本书讨论项目所需的理论,以帮助你逐步进入机器学习项目的工作,但进一步深入的内容超出了本书的范围。但是,如果你有兴趣,我们会提供许多资源的参考,可以深入学习。
第二章和第三章讨论了可以在 ML 问题中使用的许多不同类型的数据,以及可以在实践中使用的不同工具。然而,没有一本书能覆盖每一种情况和每一个可用的工具。我们专注于结构化数据的用例,并仅在第二章和第九章轻度讨论非结构化数据的 ML。一些最激动人心的应用(如 AI 驱动的聊天机器人和图像生成)使用非结构化数据,但实际上,业界和行业中大多数 ML 应用集中于涉及结构化数据的问题。
在工具方面,我们专注于狭窄的工具范围,以便您可以专注于业务用例。Python 中的包,如 NumPy、Seaborn、Pandas、scikit-learn 和 TensorFlow,在所有行业中都很受欢迎,我们在本书中涵盖这些包以及许多用例。Jupyter Notebooks 也是一种行业标准,用于在笔记本环境中交互地运行 Python 代码。
我们使用 Google Colab,一个免费的 Jupyter Notebook 服务来运行我们的笔记本。此外,我们还将使用 Google Cloud 的其他工具,如 Vertex AI AutoML 进行无代码 ML 模型训练和 BigQuery 进行 SQL 数据分析和训练 ML 模型。其他主要的云提供商,如 Microsoft Azure 和 Amazon Web Services(AWS),也提供类似的服务来运行 Jupyter Notebooks,AutoML,使用 SQL 分析数据和训练 ML 模型。我们强烈建议您探索我们提到但未在此处使用的其他工具。本书中包含了更多信息和文档的链接。
本书中使用的约定
本书中使用以下排版约定:
Italic
表示新术语、网址、电子邮件地址、文件名和文件扩展名。
Constant width
用于程序列表,以及段落内用来指代程序元素如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。
Constant width bold
显示用户应直接输入的命令或其他文本。
Constant width italic
显示应由用户提供值或由上下文确定值的文本。
提示
此元素表示提示或建议。
注意
此元素表示一般注释。
警告
这个图标表示警告或注意事项。
使用代码示例
可下载补充材料(代码示例、练习等),请访问https://oreil.ly/supp-lcai。
本书旨在帮助您完成工作。一般情况下,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分内容,否则无需征得我们的许可。例如,编写一个程序使用了本书中的几个代码片段并不需要许可。销售或分发包含 O'Reilly 书籍示例的 CD-ROM 需要许可。引用本书并引用示例代码来回答问题不需要许可。将本书中大量示例代码整合到产品文档中需要许可。
我们感谢您的支持,但不要求署名。署名通常包括标题、作者、出版商和 ISBN。例如:“Low-Code AI by Gwendolyn Stripling and Michael Abel (O’Reilly). Copyright 2023 Gwendolyn Stripling and Michael Abel, 978-1-098-14682-5.”
如果您认为您使用的代码示例超出了合理使用范围或上述许可,请随时通过 permissions@oreilly.com 与我们联系。
致谢
这本书的存在完全是因为许多人的慷慨支持和时间。我们要感谢我们的谷歌同事——恩里卡·菲利皮、莫娜·莫娜、本瓦·德林和迈克尔·蒙——他们对这本书的详细审查极大地增加了表达和技术细节的质量。我们还要感谢我们的同事们——里奇·罗斯、帕特里克·本特利、约安娜·朗、埃斯拉·杜尤根和康塞普西昂·迪亚兹——对我们的支持和反馈。最后但并非最不重要的,我们要感谢我们的经理们——凯利·汤普森、埃里克·皮洛特和斯里·乌帕迪乌拉——对这项努力的坚定支持。
特别感谢 O’Reilly 团队在将这个梦想变为现实的过程中的所有合作,并对两位初次尝试这一过程的作家的耐心。我们要感谢尼科尔·巴特菲尔德从一开始就对我们的项目和愿景的信任,以及克莱尔·莱洛克在准备这本书印刷方面的工作。我们还要感谢科尔宾·柯林斯在整个过程中的辛勤工作和编辑专业知识。
迈克尔要感谢他的妻子杰基,在整个写书过程中给予他不可思议的支持和耐心;他的女儿米娅,总能在合适的时机打断他的工作来玩键盘,让他开心不已;还有他的父母本和丽塔,他们从他记事起就培养了他对数学和计算机的热爱。
格温多琳要感谢她的生活伴侣纳达·维利米罗维奇,在这段惊人旅程中给予她坚定不移的支持;还有她的女儿萨斯基亚和宠物姜姜与馄饨,它们总是在适当的时候给予拥抱、寻求陪伴、或是要求短暂的散步。她还要感谢她的家人:她的父母(感谢你们的 DNA);她的姐姐贝琳达(感谢你看见了我);她的姐姐格洛丽亚·泰勒·威廉姆斯和她的丈夫查尔斯·威廉姆斯;她的兄弟莫里斯·科布和卡洛斯·S·阿姆斯特朗;以及她的叔叔托尼·霍尔。她非常感激她的侄女卡特琳娜·玛丽·布拉奇和侄子卡洛斯·S·阿姆斯特朗在这段旅程中给予的所有支持!她还要感谢她的亲密朋友克里斯蒂娜·拉米雷斯,一直以来对她不拘一格的思维方式的信任和支持!
第一章:数据如何在机器学习中驱动决策制定
本章探讨了数据在企业中的角色及其对业务决策制定的影响。您还将了解机器学习(ML)工作流程的组成部分。您可能已经看过许多书籍、文章、视频和博客,它们在讨论 ML 工作流程时通常从数据收集开始。但是,在收集数据之前,您需要了解要收集什么类型的数据。这种 数据理解 只能通过了解您需要解决的问题或需要做出的决定来实现。
业务案例/问题定义和数据理解可以用来制定无代码或低代码的机器学习策略。无代码或低代码的机器学习项目战略方法具有几个优点/好处。正如介绍中所提到的,无代码自动 ML 方法使具有其专业领域知识但没有编码经验的任何人能够快速开发 ML 模型,而无需编写一行代码。这是开发 ML 应用程序的一种快速高效的方式。低代码方法使那些具有一些编码或深度编码经验的人能够快速开发 ML 应用程序,因为基本代码是自动生成的——并且可以添加任何额外的自定义代码。但是,任何 ML 项目都必须从定义目标、使用案例或问题开始。
目标或使用情况是什么?
企业、教育机构、政府机构和从业者面临许多反映 ML 实际应用的决策问题。例如:
-
如何增加我们的糖尿病网络应用程序中患者的参与度?
-
如何增加我们课程调查中学生反馈数量?
-
如何提高我们在发现针对公司网络的网络攻击速度上的效率?
-
我们是否可以减少进入我们电子邮件服务器的垃圾邮件数量?
-
如何减少我们制造生产线停机时间?
-
如何增加我们的客户保留率?
-
如何减少我们的客户流失(客户流失)率?
在这些示例中,必须检查许多数据源,以确定什么样的机器学习解决方案最适合解决问题或帮助决策制定。让我们以减少客户流失或损失率的使用案例为例——使用一个非常简单的例子。 流失预测 是识别最有可能离开您的服务或产品的客户。这个问题属于监督学习桶中的一个分类问题,有两个类别: “流失-是” 类和 “流失-否” 类。
从数据源的角度来看,您可能需要检查客户档案信息(姓名、地址、年龄、职位、就业声明)、购买信息(购买和账单历史记录)、互动信息(客户与您的产品进行互动的体验[数字化和实体])、您的客户服务团队或您的数字支持服务。客户信息的流行数据来源包括客户关系管理系统、系统电子商务分析服务和客户反馈。实质上,客户“接触”的一切作为数据点应该被跟踪和捕捉为数据源。
您必须做出的决策的性质直接与您需要收集的数据紧密相关,这些数据需要制定为问题陈述。假设您负责一家生产雨伞的公司的市场营销工作,业务目标是增加销量。如果您降低现有雨伞的销售价格,您能预测将销售多少把雨伞吗?图 1-1 展示了考虑此选项的数据元素。

图 1-1. 影响价格降低策略以增加销量的数据元素。
如您在这个数据驱动的业务插图中所见,您的业务目标(增加销量)呈现出新的维度。您现在意识到,要理解产品价格降低,除了销售价格外,还需要包括其他数据维度。您需要了解特定地区的雨季情况、人口密度,以及您的库存是否足以满足通过降价增加销量的需求。您还需要查看历史数据与实时捕获的数据之间的差异。历史数据通常称为批处理,而实时数据捕获通常称为流式处理。随着这些额外的维度,业务目标突然变成了一个非常复杂的问题,因为可能需要这些附加列。对于任何组织来说,可能会存在数十个离散的数据源,每个数据源都需要特定的技能来理解它们之间的关系。图 1-2 展示了这一挑战的插图。

图 1-2. 今天的典型业务数据和机器学习经验。
那么这里的用例是什么?这取决于情况。您需要进行业务决策过程,这是通过提出问题、收集数据和评估备选方案来进行选择的过程。一旦确定用例或业务目标,您可以使用相同的数据训练机器以了解客户模式、发现趋势,并使用 AutoML 或低代码 AI 预测结果。图 1-3 显示我们的雨伞示例作为业务用例,然后导致数据源确定、ML 框架,最终进行预测。

图 1-3. 使用 ML 框架进行预测的业务案例。
企业 ML 工作流程
决策过程帮助您识别问题或用例,而 ML 工作流帮助您实施解决方案。本节介绍了一个典型的 ML 工作流程。在我们持续讨论的雨伞示例中,您可以使用数据训练一个 ML 模型,使用提供无代码解决方案的 AutoML 服务来运行无监督 ML 聚类。从那里,您可以检查数据点的聚类以查看推导出的模式。或者,您可以决定仅仅专注于历史数据,以便基于一定数量的数据输入特征预测特定目标。您的企业 ML 工作流程将是什么样子呢?毫不奇怪,它是数据驱动的,并且在过程中需要决策。
ML 工作流程可以显示为一系列步骤,并且这些步骤可以组合成阶段。图 1-4 显示了 10 个步骤,我们稍后会简要讨论每个步骤。后续章节将提供每个步骤的更详细的示例。

图 1-4. 十步 ML 工作流程。
确定业务目标或问题陈述
ML 工作流程始于定义具有明确边界的特定问题或问题。在此阶段,您试图定义范围和可行性。正确的问题将引导您确定需要的数据以及必须准备数据的潜在方法。值得注意的是,在分析数据时可能出现的任何问题都可以归类为表 1-1 中显示的五种 ML 类别之一。让我们继续使用我们的雨伞示例。
表 1-1. 分析数据的类别
| 算法/模型 | 问题或疑问 |
|---|---|
| 回归问题 | 本月/本季度预计销售多少把雨伞? |
| 分类问题 | 他们购买了直杆雨伞(A)还是可折叠雨伞(B)? |
| 聚类问题 | 每月或每个地区销售了多少直杆雨伞? |
| 异常检测问题 | Mojave 沙漠地区的雨伞销量是否高于俄勒冈州波特兰地区? |
| 强化学习 | 公司政策是仅向欠款不超过$500 的客户发货。可以训练制造机器人根据此政策提取、包装、装载和发运直杆伞吗? |
数据收集
在 21 世纪初期,公司、大学和研究人员通常依赖本地服务器/硬盘或数据中心来托管其数据库应用程序并存储其数据。依赖本地数据中心或甚至在数据中心租用服务器空间是昂贵的:需要维护服务器基础设施,更新软件,安装安全补丁,更换物理硬件等。在某些情况下,大量数据存储在一组机器中。
如今,为了节省成本,企业和教育机构已经转向云端来托管其数据库应用程序和存储其数据。云存储是云供应商提供的一种服务,用于存储文件,允许您上传不同的文件格式,或配置为自动接收来自不同数据源的文件。由于大多数机器学习模型是使用来自文件的数据进行训练的,将数据存储在云存储存储桶中可以实现轻松的数据收集。云存储桶可用于存储结构化和非结构化数据。
另一个存储数据文件以进行数据收集的选择是GitHub,这是一个设计用于协作编程项目的服务。您可以将数据存储在云中以备将来使用(免费),跟踪更改,并使数据公开可用以进行复制。这个选项有严格的文件大小限制为 100 MB,但有使用 Git Large File Storage (LFS) 的选项,这是一个用于对大文件进行版本控制的 GitHub 开源扩展。Git LFS 会在 Git 中用文本指针替换大文件(如数据集、音频样本、图形和视频),同时将文件内容存储在像GitHub.com或GitHub Enterprise这样的远程服务器上。
数据收集的挑战在大型组织中更加复杂,因为存在许多不同类型的运营管理软件,例如企业资源规划、客户关系管理和生产系统,并且可能在不同的数据库上运行。数据也可能需要实时从外部来源获取,例如交货卡车的物联网传感器设备。因此,组织面临的挑战不仅是收集结构化数据,还包括批处理或实时(流式处理)中的非结构化和半结构化数据格式。图 1-5 显示了为结构化、非结构化和半结构化数据提供数据收集的各种数据元素。

图 1-5. 目标/问题流向数据收集。
注
可能存在流式结构化数据。结构化与非结构化是数据格式的属性。流式与批处理是延迟的属性。第二章提供了关于数据格式和属性的更多信息。
数据预处理
要进行数据清理,您需要处理缺失的数据值、重复项、异常数据、格式问题或由于人为错误导致不一致的数据。这是因为现实世界的数据通常是原始和混乱的,充满了假设。一个假设可能是你的数据具有正态分布,这意味着数据对称分布且没有偏斜,大多数值聚集在中心区域,从中心(均值或平均数)向外值的频率逐渐减少。
假设你的数据首次显示,八月份在加利福尼亚沙漠小镇帕姆斯普林斯售出的雨伞数量增加了。你的数据是否服从正态分布,还是被视为异常值?这是否会偏离对八月份雨伞销售预测结果的影响?当数据不服从正态分布时,需要进行归一化,即将所有记录分组到[0,1]或[-1,1]范围内,例如。你通过归一化数据集来更轻松、更快速地训练机器学习模型。归一化在第七章中有详细介绍。
注意
如果存在异常值,这种最小-最大归一化的示例可能会产生不利影响。例如,当缩放到[0,1]时,它基本上将异常值映射为 1,并将所有其他值压缩为 0。处理异常值和异常情况超出了我们书本的范围。
因此,数据预处理可以意味着对数据进行归一化(使数据集中的数值列使用共同的尺度)和缩放数据,即转换数据以适应特定范围。幸运的是,Python 中只需几行简单的代码就可以进行归一化和标准化。图 1-6 显示了归一化和标准化前后的实际数据。

图 1-6. 实际数据、归一化和标准化数据的三张图片。
注意
从单一来源收集数据可能是一个相对简单的过程。但是,如果你要将几个数据源聚合到一个文件中,请确保数据格式匹配,并验证关于时间序列数据的任何假设(或者你的机器学习模型所需的时间戳和日期范围)。一个常见的假设是数据是静止的——即统计属性(均值、方差等)随时间不变。
数据分析
探索性数据分析(EDA)是用于探索和分析数据结构的过程。在这一步骤中,您正在寻找趋势、模式、特征相关性以及诸如一个变量(特征)如何与另一个变量相关的信息。根据您尝试解决的问题类型,您必须选择相关的特征数据用于您的 ML 模型。这一步骤的结果是一个输入变量的特征列表,可以潜在地用于 ML。我们在第六章中介绍的实际操作的 EDA 可以找到。
图 1-7 和 1-8 是使用 Python 数据可视化库 Seaborn 绘制的 EDA 过程的结果(详见第六章中的数据集详情)。图 1-7 展示了 x 和 y 之间的反向关系。图 1-8 展示了 热图(或相关矩阵),并说明在温度较低时产生更多能量。

图 1-7. Seaborn regplot 显示在温度较低时产生更多能量。

图 1-8. Seaborn 相关矩阵(热图)显示 Temp 和 Energy_Production 之间的强烈反向关系,-0.75。
数据转换和特征选择
在数据清理和分析之后,您会得到一个特征列表,用于解决 ML 问题。但是,其他特征可能也是相关的吗?这就是 特征工程 发挥作用的地方,您可以在原始数据集中创建新的特征。例如,如果您的数据集分别包含月份、日期和年份的字段/列,您可以将所有三个组合为“月-日-年”时间特征。特征工程是特征选择之前的最后一步。
实际上,特征选择分两个阶段进行:在 EDA 之后和数据转换之后。例如,在完成 EDA 后,您应该有一个潜在的特征列表,可以用来创建新的特征,例如,结合时间和星期几以获取一天中的小时。在进行特征工程之后,您将从中选择最终的特征列表。图 1-9 显示了在工作流程中进行数据转换和特征选择的位置。

图 1-9. 在 ML 工作流程中数据转换和特征选择的位置。
研究模型选择或使用 AutoML(无代码解决方案)
在这一步中,您可以研究最适合您问题类型的模型,或者您可以使用 AutoML,这是一种基于您上传的数据集选择适当模型、训练、测试和生成评估指标的无代码解决方案。实质上,如果您使用 AutoML,模型选择、模型训练、模型调优和生成评估指标的繁重工作都为您完成。第三章介绍了 AutoML,而第四章开始实际使用 AutoML。请注意,使用低代码解决方案,您需要知道选择哪种模型。
虽然 AutoML 可能覆盖了您 80%的 ML 问题,但您可能希望构建一个更定制化的解决方案。在这种情况下,了解 ML 算法可以解决的问题类型是有帮助的。选择算法完全取决于问题本身(如前所述)。在表 1-2 中,“描述”列被添加以进一步描述 ML 模型的问题类型。
表 1-2. 描述模型类型
| 问题或疑问 | 问题 | 描述 |
|---|---|---|
| 多少或多少把雨伞? | 回归问题 | 回归算法用于处理连续和数值输出的问题。通常用于处理像“多少”或“多少”的问题。 |
| 他们购买了直杆雨伞(A)还是折叠雨伞(B)? | 分类问题 | 输出只能是一组固定的输出类别之一,如是/否或真/假,称为分类问题。根据输出类别的数量,问题可以是二元分类问题或多元分类问题。 |
| 公司政策是仅向欠款$500 或更少的客户发货。我们的制造机器人可以根据这一政策被训练来提取、包装、装载和发运直杆雨伞到我们的客户吗? | 强化学习 | 当根据学习经验做出决策时,使用强化算法。机器代理通过与不断变化的环境进行交互的试错学习行为。这提供了一种使用奖励和惩罚的概念来编程代理的方法,而无需指定任务如何完成。游戏程序和温度控制程序是使用强化学习的一些流行示例。 |
模型训练、评估和调优
在将 ML 模型部署到生产环境之前,必须对其进行训练、评估和测试。训练 ML 模型是一个过程,其中存储的数据实例被输入到 ML 模型(算法)中。由于每个存储的数据实例都有特定的特征(回想我们对不同类型、价格、销售地区等的雨伞示例),因此可以使用数百个变量检测这些数据实例的模式,算法因此能够从训练数据中学习如何基于数据进行一般化预测。
每个 ML 模型不仅需要进行训练,还需要进行评估。因此,需要保留一部分数据样本,称为验证数据集。验证集用于衡量模型对未见或新数据的一般化程度。训练错误用于确定模型对数据的拟合程度,因为这是模型训练的内容。
应选择或定义模型评估指标,以使其与问题或业务目标保持一致。模型调优应该通过评估指标来改善模型性能。例如,在 12 月销售的雨伞预测中,准确性如何?这些预测是否可以用于未来的预测工作?请注意,令人满意的性能应该由业务需求确定,并应在开始任何 ML 参与之前达成共识。
注意
验证集也用于确定模型是否过拟合。第八章讨论了过拟合问题。
模型测试
在没有测试模型之前,无法知道你的雨伞预测应用程序是否可以推广到未来的预测工作。一旦训练数据集用于将模型拟合到数据上,验证数据集用于提高模型准确性,你就需要在模型从未见过的数据上测试模型。测试数据用于评估模型性能。
例如,假设你想构建一个可以根据雨伞图像识别颜色或图案的应用程序。你通过向模型提供每个都标记有特定颜色或图案的雨伞图像来训练模型。你在移动应用程序中使用该模型来识别任何雨伞的颜色或图案。测试就是要看模型在区分雨伞颜色和图案方面表现如何。
图 1-10 显示了训练、验证和测试数据集之间的关系。

图 1-10. 模型部署和模型评估中训练、验证和测试数据集之间的关系。
图 1-11 展示了训练、验证和测试数据集在五个步骤中的关系。为简单起见,没有显示回到第 5 步数据集的箭头,因为一旦将模型部署为应用程序并开始收集数据,新数据进入管道可能会偏离原始模型的结果。(在这一点上,您进入了机器学习运营的迷人领域,即 MLOps,这超出了本书的范围。)

图 1-11. 机器学习工作流程的五个步骤。
模型部署(服务)
一旦机器学习模型经过训练、评估和测试,就会部署到实时生产环境中以供使用。请注意,当模型达到生产环境时,它很可能具有 Web 应用程序前端(使用浏览器),通过应用程序编程接口(API)与生产系统通信。数据可以实时捕获并流式传输(摄取)到 MLOps 管道中。或者数据可以批量捕获并存储以供后续摄入管道使用。或者两者兼而有之。
维护模型
当模型的预测与原始业务目标或用例指标不一致时,模型可能会变得陈旧。当世界变化或业务需求变化时,可能会发生陈旧现象。这些变化会影响模型。在部署后,您需要监控您的模型,以确保它继续按预期表现。模型和数据漂移是您应该预期并通过定期使用 MLOps 进行重新训练来减轻的现象。让我们来看一个数据漂移的例子,这意味着您训练的数据和现在从 Web 应用程序接收到的数据发生了变化。
在我们的综合示例中,曾经经历过大雨的地区现在正经历干旱条件。同样地,曾经经历干旱条件的地区现在正经历大雨。与天气和气候以及需要雨伞和伞类型相关的任何预测都将受到影响。在这种情况下,您需要使用新数据重新训练和测试新模型。
总结
企业、教育机构、政府机构和从业者面临许多决策,这些决策反映了机器学习的实际示例,从增加客户参与到减少客户流失。数据——其收集、分析和使用——驱动了用于机器学习的决策制定,以确定提供解决实际问题的最佳机器学习战略方法。
虽然决策过程帮助您确定问题或用例,但是机器学习工作流程帮助您实施解决方案。企业机器学习工作流程是数据驱动的,需要在流程中做出决策。机器学习工作流程可以显示为一系列 10 步,并且这些步骤可以组合成四个阶段:
-
决策制定
-
数据处理
-
建模
-
部署
每个机器学习工作流的阶段都可以使用 AutoML 或低代码 AI 来实现。AutoML 为您完成所有繁重的工作。AutoML 将训练模型、调整模型、测试模型,并向您展示评估指标。您的角色仅仅是评估这些指标,确定它们是否符合您的业务目标或解决您的问题。AutoML 推荐用于快速实验和原型开发。它也被用于生产环境中。低代码方法使得那些具有一定编程或深度编程经验的人可以使用自动生成的代码,在机器学习工作流的任何阶段进行进一步定制。
在本章中,您学习了数据收集和分析作为机器学习工作流的一部分。第二章 概述了本书使用的数据集,数据来源、数据文件类型,以及批处理、流处理、结构化、半结构化和非结构化数据之间的区别。您还可以通过基本的 Python 代码进行实践,帮助您进行探索性数据分析(EDA),解决数据质量问题。
第二章:数据是第一步
本章概述了本书中使用的用例和数据集,并提供了关于在哪里找到数据源进行进一步研究和实践的信息。您还将了解数据类型以及批处理和流处理数据之间的区别。您将通过使用谷歌免费基于浏览器的开源 Jupyter Notebook 进行数据预处理的实际操作。本章最后部分讲述了使用 GitHub 为本书中使用的选定项目创建数据存储库的方法。
本书用例和数据集概述
希望您能从我们的书中学习机器学习,而不是从数学或算法优先的角度,而是从基于项目的方法。我们选择的用例旨在使用不同部门的实际、真实世界的数据来教授您机器学习。有医疗保健、零售、能源、电信和金融等各种部门的用例。客户流失的用例可以应用于任何部门。如果您具有一些数据预处理经验,每个用例项目都可以独立存在,因此请随时跳到您需要学习以提升技能的内容。表 2-1 显示了每个部分、其用例、部门以及是否为无代码或低代码。
表 2-1. 按行业部门和编码类型列出的用例列表
| 部分 | 用例 | 部门 | 类型 |
|---|---|---|---|
| 1 | 产品定价 | 零售 | 不适用 |
| 2 | 心脏病 | 医疗保健 | 低代码数据预处理 |
| 3 | 市场营销活动 | 能源 | 无代码(AutoML) |
| 4 | 广告媒体渠道销售 | 保险 | 无代码(AutoML) |
| 5 | 欺诈检测 | 金融 | 无代码(AutoML) |
| 6 | 发电厂生产预测 | 能源 | 低代码(BigQuery ML) |
| 7 | 客户流失预测 | 电信 | 低代码(scikit-learn 和 Keras) |
| 8 | 提升自定义模型性能 | 汽车 | 自定义代码(scikit-learn、Keras、BigQuery ML) |
1. 零售:产品定价
本节以一个旨在说明数据在决策中的角色的用例开始。在这个用例中,您负责一家生产雨伞的公司的市场营销工作,业务目标是增加销量。如果降低现有雨伞的销售价格,您能预测将会销售多少把雨伞吗?图 2-1 展示了可能影响价格降低策略以增加销售的数据元素。

图 2-1. 影响价格降低策略以增加销售的数据元素。
2. 医疗保健:心脏病宣传
在这里,你是一名医疗保健顾问,提供了美国 35 岁以上人群心脏病死亡率的数据。目标是分析心脏病死亡率数据,并建议在心脏病预防活动中可能的用例。例如,一个可能的用例是跟踪心脏病死亡率随时间的趋势,或者开发和验证预测心脏病死亡率的模型。这些数据集有脏数据。一些字段有缺失值。一个字段缺失。在解决这些问题时,你学会了将数据导入 Python Jupyter Notebook,分析数据,并修复脏数据元素。图 2-2 展示了对你的分析有贡献的数据要素。

图 2-2. 心脏病死亡率用例的数据要素。
3. 能源:公用事业活动
在这里,你是一名为公用事业公司工作的业务分析师。你的任务是开发一个针对高电能消费社区的营销和宣传计划。数据已经预处理过。你没有 ML 背景或任何编程知识。你决定使用 AutoML 作为你的 ML 框架。图 2-3 展示了对你的模型有贡献的数据要素。

图 2-3. 对公用事业能源活动有贡献的数据要素。
4. 保险:广告媒体渠道销售预测
在这一部分中,你将与一个负责为保险公司制定媒体战略的团队合作。团队希望开发一个基于各种媒体渠道广告支出预测销售的 ML 模型。你的任务是进行探索性数据分析,并建立和训练模型。你没有 ML 背景或任何编程知识。你决定使用 AutoML 作为你的 ML 框架。图 2-4 展示了对你的模型有贡献的数据要素。

图 2-4. 影响媒体渠道销售预测的数据要素。
5. 金融:欺诈检测
在这个项目中,您的目标是构建一个模型,预测金融交易是否是欺诈性或合法的。您的新公司是一家移动支付服务,为数十万用户提供服务。欺诈交易相对较少,并且通常会被其他保护措施捕捉到。然而,不幸的事实是,一些欺诈交易会逃过检查,对您的用户造成负面影响。本节中的数据集包含已模拟以复制用户行为和欺诈交易的交易数据。您没有机器学习背景或任何编程知识。您选择使用自动机器学习(AutoML)作为您的机器学习框架。图 2-5 显示了贡献到您模型的数据元素。

图 2-5. 贡献到欺诈检测模型的数据元素。
6. 能源:电力生产预测
在这个项目中,您的目标将是预测一个联合循环发电厂(CCPP)在接近厂区的天气条件下的净每小时电能输出。本节中的数据集包含从 2006 年到 2011 年的六年期间(当发电厂设定为全负荷运行时)收集的数据点。数据按小时聚合,尽管数据集中没有提供记录天气条件和能量生产的确切小时。从实际角度来看,这意味着您将无法将数据视为序列或时间序列数据,其中您使用先前记录的信息来预测未来记录。您从与数据库一起使用结构化查询语言(SQL)中获得了一些知识。您选择使用 Google 的 BigQuery 机器学习作为您的机器学习框架。图 2-6 显示了贡献到您模型的数据元素。

图 2-6. 贡献到电力能量输出模型的数据元素。
7. 电信业务:客户流失预测
您在这个项目中的目标是预测电信公司的客户流失。客户流失 定义为客户的流失率,或者换句话说,选择停止使用服务的客户比率。电信公司通常以月租费或年度合同销售其产品,因此在此处的流失 将表示客户在接下来的一个月内取消其订阅或合同。数据集包含数值变量和分类变量,其中变量取自离散可能性集。您具备一些 Python 知识,并认为 AutoML 非常强大,但希望学习允许您对模型有更多控制的低代码解决方案。您选择使用 scikit-learn 和 Keras 作为 ML 框架。图 2-7 显示了贡献到您模型的数据元素。

图 2-7. 贡献到客户流失模型的数据元素。
8. 汽车:改进定制模型性能
在这个项目中(作为机器学习团队的新成员),您的目标是改进训练用于预测二手车拍卖价格的机器学习模型的性能。初始模型是一个使用 scikit-learn 进行线性回归的模型,并且并不完全符合您的业务目标。您最终将探索使用 scikit-learn、Keras 和 BigQuery ML 中的工具来提高模型性能。用于训练线性回归模型的训练、验证和测试数据集已作为 CSV 文件提供给您。这些数据集已经经过清洗(缺失和不正确的值已经得到适当修复),并且提供了用于构建 scikit-learn 线性回归模型的代码。图 2-8 显示了贡献到您模型的数据元素。

图 2-8. 贡献到汽车定价模型的数据元素。
数据和文件类型
数据确实是第一步,所以让我们来了解一些围绕数据的基本术语和概念。如果您已经熟悉定量数据和定性数据之间的区别;结构化、半结构化和非结构化数据之间的区别;以及批处理和流处理数据之间的区别,则跳到“GitHub 和 Google Colab 概述”,在那里您可以开始在 GitHub 中创建 Jupyter 笔记本。
定量和定性数据
在数据分析中,你将处理两种类型的数据:定量数据和定性数据。如果可以计数或测量,并给出一个数值,那么它就是定量数据。定量数据可以告诉你多少,多少钱,或者多频繁——例如,有多少人访问网站查看产品目录?公司本财年收入多少?制造伞把的机器多频繁损坏?
与定量数据不同,定性数据无法测量或计数,几乎可以包括任何非数字数据。它是描述性的,用语言而不是数字表达。在机器学习中,为什么这种区别很重要?如果你有定性数据,那么你需要预处理它,使其变成定量数据——因为你不能将定性数据输入机器学习模型。你将在后续章节学习如何处理一些定性数据。
结构化、非结构化和半结构化数据
数据可以分为三大类:结构化数据、非结构化数据和半结构化数据。
结构化数据是已格式化并转换为明确定义的数据模型的信息。数据模型是一种组织和结构化数据的方式,使其易于理解和操作。数据模型应用广泛,包括数据库、软件应用程序和数据仓库。结构化数据组织有序。表 2-2 显示了在第四章的广告媒体频道销售预测用例中使用的模式和数据类型。请注意,这里有列名和列类型。有四列数字(定量)数据,供 AutoML 模型使用。
表 2-2。广告数据集的模式和字段值信息,来自第四章
| 列名 | 列类型 | 关于字段值的注释 |
|---|---|---|
| 数字 | 数字 | 数字 |
| 报纸 | 数字 | 广告预算 |
| 无线电 | 数字 | 广告预算 |
| 电视 | 数字 | 广告预算 |
这里有一些结构化数据的例子:
-
客户记录
-
产品库存
-
财务数据
-
交易日志
-
网站分析数据
-
日志文件
非结构化数据是没有结构化或表格化或特定格式的数据。以下是一些非结构化数据的例子:
-
社交媒体帖子
-
聊天记录(文本)
-
视频
-
照片
-
网页
-
音频文件
半结构化数据是介于结构化和非结构化数据之间的一种类型的结构化数据。它没有表格化的数据模型,但可以包含数据集中记录和字段的标签和语义标记。半结构化数据实质上是结构化和非结构化数据的组合。视频可能包含与日期或位置相关的元标签,但内部信息没有结构。
这里是一些半结构化数据的例子:
-
CSV,XML,JSON 文件
-
HTML
-
电子邮件(电子邮件被视为半结构化数据,因为它们有一定的结构,但不如结构化数据那么多。电子邮件通常包含标题,正文和附件。标题包含有关发件人,收件人和消息日期的信息。消息正文包含消息的文本。)
图 2-9 比较了非结构化、半结构化和结构化数据。

图 2-9. 非结构化、半结构化和结构化数据示例。
数据文件类型
刚刚您了解了不同类型的数据,提到了几种文件类型。有许多不同类型的数据文件格式,每种都有其特定的用途。表 2-3 展示了一些最常见的数据文件类型。
表 2-3. 常见数据文件类型
| 常见数据文件类型 | 常见文件扩展名 |
|---|---|
| 文本文件是包含纯文本的文件。它们通常用于存储文档,如信件,报告和代码。 | 一些常见的文本文件扩展名包括 .txt,.csv,.tsv,.log 和 .json。 |
| 电子表格文件是以表格格式存储数据的文件。它们通常用于存储财务数据,销售数据和其他表格数据。 | 一些常见的电子表格文件扩展名包括 .xls,.xlsx 和 .csv。 |
| 图像文件是包含图像的文件。它们通常用于存储照片,图形和其他视觉内容。 | 一些常见的图像文件扩展名包括 .jpg,.png 和 .gif。 |
| 音频文件是包含音频录音的文件。它们通常用于存储音乐,播客和其他音频内容。 | 一些常见的音频文件扩展名包括 .mp3,.wav 和 .ogg。 |
| 视频文件是包含视频录制的文件。它们通常用于存储电影,电视节目和其他视频内容。 | 一些常见的视频文件扩展名包括 .mp4,.avi 和 .mov。 |
| 网页文件是包含网页的文件。它们通常用于存储 HTML 代码,CSS 代码和 JavaScript 代码。 | 一些常见的网页文件扩展名包括 .html,.htm 和 .php。 |
数据的处理方式
数据处理有两种主要模式:批处理和实时处理。批处理是一种数据处理模式,其中数据在一段时间内收集,然后在稍后处理。这是处理大型数据集的常见模式,因为批量处理数据比实时处理效率更高。实时处理是一种数据处理模式,数据在收集后立即处理。这是一种常见的数据处理模式,适用于需要快速处理数据的应用,如欺诈检测或股票交易。
数据处理的频率也可能不同。连续处理是一种数据处理模式,数据在收集时连续处理。这是处理数据需要实时处理的应用程序常见模式。周期性处理是一种数据处理模式,数据在规则间隔内处理。这是处理数据不需要实时性的应用程序常见模式,如财务报告。
数据处理的模式和频率取决于应用程序的具体需求。例如,需要处理大数据集的应用程序可能会使用批处理,而需要实时处理数据的应用程序可能会使用实时处理。表 2-4 总结了数据处理的不同模式和频率。
表 2-4. 不同数据处理模式和频率的总结
| 模式 | 频率 | 描述 |
|---|---|---|
| 批处理 | 间歇性 | 数据在一段时间内收集,然后在稍后处理。 |
| 实时处理 | 持续 | 数据在收集后立即处理。 |
| 周期性处理 | 间歇性 | 数据定期处理。 |
批处理数据和流数据是两种不同类型的数据,它们被不同方式处理。
-
批处理 数据是一种在一段时间内收集,然后在稍后处理的数据。
-
流数据 是一种在接收时立即处理的数据。
批处理数据要求在处理、存储、分析并喂入机器学习模型之前收集数据成批处理。
数据流是持续不断地流入,可以立即处理、存储、分析和实时操作。流数据可以来自多种不同格式的分布式源头。简而言之,流数据是连续生成并且实时的数据。这种类型的数据可以用来训练能够实时进行预测的机器学习模型。例如,流数据模型可以用于检测欺诈或预测客户流失。
GitHub 和 Google 的 Colab 概述
本节讲述如何设置 Jupyter Notebook 和 GitHub 项目仓库。GitHub 仓库可以存放你的数据集和低代码项目笔记本,比如本书提到的 Jupyter 笔记本。
使用 GitHub 创建数据仓库来管理你的项目
GitHub 是一个代码仓库,你可以免费存储你的 Jupyter 笔记本和实验原始数据。让我们开始吧!
1. 注册一个新的 GitHub 账户
GitHub 提供个人账户和组织账户。创建个人账户时,它作为你在 GitHub.com 上的身份。创建个人账户时,必须为账户选择一个计费计划。
2. 设置你的项目 GitHub 仓库
要设置您的第一个 GitHub 存储库,请查看书中第二章“使用 GitHub 创建项目数据存储库”页面的完整步骤,位于GitHub 存储库。您还可以参考GitHub 文档了解如何创建存储库。
为您的存储库键入一个简短且易记的名称;例如,低代码书籍项目。描述是可选的,但在此练习中,请输入低代码 AI 书籍项目。选择存储库可见性——在这种情况下,默认为 Public,这意味着任何人都可以在互联网上看到此存储库。图 2-10 展示了您的设置应该是什么样子。

图 2-10. 创建新存储库页面。
让 GitHub 创建一个 README.md 文件。这是您可以为项目撰写长描述的地方。保留其他默认设置:.gitignore 允许您选择不要跟踪的文件,许可证告诉其他人可以做什么以及不能做什么。最后,GitHub 提醒您正在创建一个公共存储库,该存储库位于您的个人帐户中。完成后,单击“创建存储库”。图 2-11 展示了页面的样子。

图 2-11. 初始化存储库设置。
单击“创建存储库”后,将显示存储库页面,如图 2-12 所示。

图 2-12. 您的 GitHub 存储库页面。
注意
在下一节中,您将在 Google 的 Colaboratory 中创建一个 Jupyter Notebook。您将从 Colab 中保存笔记本文件到 GitHub,这将在 Main 分支下创建一个文件。在 GitHub 中创建文件是改进项目协作的好方法。它提供了许多功能,可以帮助团队更有效地协同工作:
版本控制
GitHub 跟踪文件的更改。这意味着所有有权访问文件的人都可以看到已经进行的更改,并且可以在必要时返回到以前的版本。
拉取请求
拉取请求允许协作者提出对文件的更改。这使每个人都有机会审查更改,然后将其合并到主分支中。
问题
可以使用问题来跟踪错误或功能请求。这样每个人都可以合作解决问题并添加新功能。
评论
可以向文件添加评论以提供反馈或提问。这种方式可以更加协作地处理代码。
使用 Google 的 Colaboratory 进行低代码 AI 项目
多年前,如果你想学习 Python,你必须下载 Python 解释器并在计算机上安装。对于初学者来说,这可能是一项艰巨的任务,因为它需要了解如何安装软件和配置计算机。如今,有许多学习 Python 的方法,而无需在计算机上安装任何东西。你可以使用在线集成开发环境(IDE),它允许你在网页浏览器中编写和运行 Python 代码。你还可以使用基于云的 Python 环境,提供 Python 解释器和你开始所需的所有库的访问权限。
这些在线和基于云的资源使得无论你的经验或技术水平如何,学习 Python 都比以往更容易。以下是使用在线和基于云的资源学习 Python 的一些好处:
无需安装
你可以立即开始学习 Python,无需下载或安装任何软件。
可从任何地方访问
只要有互联网连接,你就可以在任何地方使用在线和基于云的资源学习 Python。
费用低廉
在线和基于云的资源通常是免费的或价格非常实惠的。
易于使用
即使对于初学者,在线和基于云的资源也设计得易于使用。
使用 Google 的 Colaboratory 构建你的低代码 Python Jupyter Notebook,或者简称 Colab。Colab 是一个托管的 Jupyter Notebook 服务,无需设置即可使用,同时提供访问计算资源,包括图形处理单元(GPU)。Colab 在你的 Web 浏览器中运行,允许你编写和执行 Python 代码。Colab 笔记本存储在 Google Drive 中,并可以像分享 Google 文档或表格一样分享。
Google Colaboratory 免费使用,无需注册任何帐户或支付订阅费用。你可以与他人分享你的笔记本并共同完成项目。
1. 创建 Colaboratory Python Jupyter 笔记本
前往Colab创建一个新的 Python Jupyter 笔记本。图 2-13 显示了主屏幕。

图 2-13. Google Colab 主页。
在标题栏中为笔记本命名,如图 2-14(A)所示,并展开显示目录(B)。然后点击+ Code 按钮(C)添加一个用于存放代码的单元格。+ Text 按钮允许你添加文本,如文档。

图 2-14. 标题笔记本和添加新单元格代码。
2. 使用 Pandas 导入库和数据集
添加了代码单元格后,你需要导入所需的任何库。在这个简单的例子中,你只需在单元格中输入import pandas as pd并点击箭头运行,如图 2-15 所示。

图 2-15. 导入 Pandas 的代码。
Pandas 库用于数据分析。通常,当你导入一个库时,你希望能够在不每次都写出 Pandas 的情况下使用它。因此,pd 是 Pandas 的一个缩写名称(或别名)。这种别名通常按照惯例用于缩短模块和子模块的名称。
数据集来自 data.gov 网站。它名为 “美国成年人心脏病死亡数据”(参见 图 2-16)。

图 2-16. 美国成年人心脏病死亡数据按地区。
滚动页面直到到达 图 2-17 所示的部分。现在,有两种方法可以将文件导入到你的 Jupyter Notebook 中。你可以将文件下载到你的桌面,然后导入它,或者你可以使用 URL。让我们使用 URL 方法。点击 图 2-17 中显示的逗号分隔值文件,该文件将带你到 URL 下载链接,如 图 2-18 所示。

图 2-17. 下载和资源页面。

图 2-18. 逗号分隔值文件 URL 链接。
从网站上复制 图 2-18 中显示的 URL。然后,转到你的 Google Colab 笔记本,并在新的单元格(A)中输入 图 2-19 中显示的代码。通过点击箭头(B)运行单元格。

图 2-19. 读取 URL 到 Pandas DataFrame 的代码。
你已经编写了导入数据集到 Pandas DataFrame 的代码。Pandas DataFrame 是一个用于以表格格式存储数据的二维数据结构。它类似于电子表格。
现在,添加代码显示 DataFrame 的前五行(或 head)。添加一个新的单元格,输入 heart_df.head() 并运行单元格。代码和输出如 图 2-20 所示。

图 2-20. DataFrame 的前五行。为了便于阅读,某些列已被移除。
添加一个新的代码单元格。输入 heart_df.info() 并运行单元格以查看 DataFrame 的信息。.info() 方法为你提供了数据集的信息。信息包括列数、列标签、列数据类型、内存使用情况、范围索引以及每列(非空值)的单元格数。 图 2-21 显示了输出。具体数值可能因数据下载时期而异。

图 2-21. DataFrame 信息输出。
根据 .info() 输出显示,你有 15 列字符串对象(即定性数据)和 4 列数值列(即定量数据)。将 int64 视为没有小数的数字(例如,25),将 float64 视为有小数的数字(例如,25.5)。
3. 数据验证
作为最佳实践,验证从 URL 导入的任何数据——特别是如果你有一个 CSV 文件格式可以进行比较。如果数据集页面列出了有关数据的更多元数据,例如列数和列名,你可能可以避免后续步骤。但遗憾的是,这是与数据工作的本质!
现在,返回 data.gov 页面并下载 CSV 文件到你的计算机。你将验证所下载的文件与从 URL 导入的文件是否匹配。
你可以通过将下载的文件上传到你的 Colab 笔记本,然后将该文件读入 Pandas DataFrame 中来完成此操作。通过选择 章节 2 笔记本中显示的文件夹来展开目录,你的屏幕应该显示如 图 2-22(A)所示。然后,要上传文件,请选择向上箭头文件夹(B)。

图 2-22. 将文件上传到你的 Colab 笔记本。
当你上传文件时,你会看到如 图 2-23 所示的警告消息。该警告基本上指出,如果运行时终止(例如关闭 Colab),则不会保存你上传的任何文件。请注意,运行时为程序提供其运行所需的环境。

图 2-23. 警告消息指出任何上传的文件都不会被永久保存。
上传后刷新你的笔记本浏览器选项卡并展开以查看目录。你的屏幕应该显示如 图 2-24 所示。

图 2-24. 显示已上传文件的目录。
注意文件名有多长——右键单击文件并将其重命名为 heart.csv。你的屏幕应该显示如 图 2-25 所示。

图 2-25. 选择“重命名文件”选项。
在重命名文件后,你的屏幕应该显示如 图 2-26 所示。

图 2-26. 文件重命名为 heart.csv。
所以,你已经将文件重命名为 heart.csv。现在你需要复制文件路径,如 图 2-27 所示。为什么?因为你将需要将该路径作为 Pandas read.csv 方法的参数输入。右键单击 heart.csv 文件以获取路径。

图 2-27. 复制文件路径。
添加一个代码单元,并输入以下代码。确保在/content/heart.csv两个单引号之间粘贴文件路径。运行单元并查看输出:
heart_df = pd.read_csv('/content/heart.csv', error_bad_lines=False,
engine="python")
heart_df.head()
在一个新的单元格中输入heart_df.info()。运行单元以查看 DataFrame 信息。将 Figures 2-21 和 2-28 的总列数进行比较。Figure 2-21 有 19 列,而 Figure 2-28 有 20 列。这意味着通过上传文件,添加了一个年份列。Year 数据类型不是数值类型——它包含数字,但这些数字是用来排序和排列的,并非用于计算。Figure 2-28 表明,这是我们机器学习用例的第一个脏数据案例。

Figure 2-28. 年份列显示为 int64。
在新的单元格中输入heart_df.isnull().sum(),如 Figure 2-29 所示。运行该单元格。是否有任何空值?空值 是指表示值不存在的值。这是你的第二个脏数据案例!你的三列存在空值。在后续章节中,你将学习如何处理缺失值。由于目前你还没有使用案例,你可能会想知道你是否真的需要所有这些特征。请注意,特征是用于训练模型的输入数据。然后模型使用这些特征进行预测。在后续章节中,你将学习特征选择。

Figure 2-29. IsNull 输出显示了三列中空值的数量。
你的数据质量问题是什么?
缺失值
大多数算法不接受缺失值。因此,当我们在数据集中看到缺失值时,可能倾向于只是“删除所有具有缺失值的行”。然而,你可以用不同的方式处理缺失值,如下文所述。
注意
在 ML 数据集中处理缺失值有两种主要方法:删除或插补。
删除 涉及从数据集中删除具有缺失值的行或列。可以通过删除所有具有缺失值的行、删除所有具有缺失值的列或删除具有一定缺失值阈值的行或列来完成此操作。
插补 涉及用估计值填充缺失值。有许多不同的插补技术,包括 (1) 均值插补,用该变量观察值的均值替换缺失值;(2) 中位数插补,用该变量观察值的中位数替换缺失值;(3) 众数插补,用该变量的最频繁值替换缺失值;以及 (4) 回归插补,使用回归模型根据其他变量的观察值预测缺失值。
虽然 Pandas 会用 NaN (不是一个数字) 填补空白处,我们应该以某种方式处理它们。书中稍后将详细介绍。
数据类型不正确
Year 显示为 int64 数据类型,应为字符串对象——您需要将其处理为定性分类特征。
分类列
有相当多的字符串对象特征——这些特征不是数值型的。您不能将此类值馈送到 ML 模型中。这些特征需要进行 one-hot 编码。您将在后续章节中看到这一点。
4. 一些探索性数据分析
在结束本节之前,让我们看看探索数据的一些简单方法。想看到特征 Stratification2 中的所有唯一值吗?请在新的单元格中输入 heart_df.Stratification2.unique(),如 图 2-30 所示。运行该单元格。

图 2-30. 显示列 Stratification2 的唯一值的代码。
让我们使用 Seaborn 的小提琴图来可视化这个特征。Seaborn 是一个用于制作统计图形的 Python 库。您可以在同一个单元格中编写所有内容,如 图 2-31 所示。此代码使用 Data_Value 特征作为 x 轴,Stratification2 作为 y 轴。请注意,Data_Value 特征是每个地区和组中心脏病的计数,如 图 2-32 所示。

图 2-31. Stratification2 列的小提琴图。
“长尾”表明该特定特征的数据中存在更多的异常值。小提琴图主体的大形状显示了按种族分布的心脏病病例数量。

图 2-32. 心脏病计数分布图。
注意
Seaborn 的小提琴图是可视化单变量数据分布的一种好方法。
用于可视化数值数据分布。与只能显示摘要统计信息的箱线图不同,小提琴图描绘了每个变量的摘要统计信息和密度。
在后续章节中,您将进行更多的数据分析,并学习数据预处理。现在,将您的笔记本保存到 GitHub。在 Colab 菜单中,点击“文件” > “在 GitHub 中保存副本”,如图 2-33 所示。

图 2-33. 如何在 GitHub 中保存笔记本文件副本。
图 2-34 显示了设置。从“存储库”下拉菜单中选择要将笔记本保存到的存储库。在“分支”下选择“笔记本”(您之前创建了这个)。保持默认的“包括指向 Colaboratory 的链接”。这将在 GitHub 上显示一个链接,允许您直接从 GitHub 打开您的笔记本。

图 2-34. Colab 的 GitHub 导出窗口。
在 Colab 将笔记本复制到 GitHub 后,它会直接带您到 GitHub,如图 2-35 所示。

图 2-35. 笔记本已复制到 GitHub。
刷新存储库中的屏幕。您应该会看到您的 Colab Jupyter 笔记本,如图 2-36 所示。

图 2-36. Colab Jupyter 笔记本的副本在 GitHub 中。
概要
本章概述了书中使用的用例和数据集。您了解了数据类型以及结构化、半结构化和非结构化数据以及批处理和流处理数据之间的区别。您通过一个免费的基于浏览器的 Python Jupyter 笔记本和 GitHub 进行了实际操作。您发现脏数据很棘手,可能会影响 ML 模型中的数据类型和数据摄取。
在下一章中,您将了解 ML 框架,并开始使用 AutoML。您将获得一个预处理过的数据集,您只需将数据集上传到 AutoML 框架中,就能够构建和训练预测模型,而无需编写一行代码。
第三章:机器学习库和框架
本章介绍了机器学习(ML)框架,简化了 ML 模型的开发。通常情况下,您需要理解数学、统计学和 ML 的基本工作原理,才能构建和训练 ML 管道。这些框架通过自动化许多耗时的 ML 工作流任务(如特征选择、算法选择、代码编写、管道开发、性能调整和模型部署),帮助您。
无代码 AutoML
假设您是一家公用事业公司的业务分析师。您有一个项目,需要帮助公司开发针对高电能消耗社区的市场营销和推广计划。数据以逗号分隔值(CSV)文件格式提供。
尽管您没有编程背景或任何编程知识,但团队负责人要求您承担此项目,因为您对 ML 及其在组织中应用表现出兴趣。虽然您没有编程经验,但您做了一些研究,并得出了一些观察结果:
-
对于像您这样的非编程人员,有自动化的无代码ML 框架,具有图形用户界面(GUI),您可以使用它们来构建和训练 ML 模型,而无需编写一行代码。
-
对于轻度编程者来说,有低代码的 ML 框架,可以通过编写少量代码来构建和训练 ML 模型。
-
对于经验丰富的编程人员,有 ML 库,允许灵活控制编写 ML 工作流的每个阶段。
基于您的公用事业市场推广项目和用例的数据,您确定您的目标是基于多个变量预测总千瓦时(kWh):邮政编码、月份、年份和客户类别(住宅、商业、工业和农业)。
假设您需要快速获得基准预测。这是 AutoML 的一个很好的用例。基于 GUI 的 AutoML 框架是最易于使用的。图 3-1 显示了您可以用于业务用例的典型 AutoML 无代码工作流的高级概述。此示例使用的是 Google 的 Vertex AI,这是一个帮助您构建、部署和扩展 ML 模型的 ML 平台。总体而言,Google AutoML、Microsoft Azure AutoML 和 AWS SageMaker AutoML 都是强大的 AutoML 解决方案,可以帮助您在不编写任何代码的情况下构建和部署 ML 模型。对于您来说,最佳解决方案将取决于您的具体需求和要求。

图 3-1. 典型的 Vertex AI AutoML 无代码工作流。
由于文件格式是 CSV,您选择 Tabular 选项卡。考虑到总千瓦时是输出并且是您想要预测的数值,您注意到这是一个回归任务,并且由于具有多个变量的列名(或标签),这是一个监督 ML 问题。没有标签的数据需要无监督 ML 任务,例如聚类。图 3-2 显示选择了回归/分类作为目标。
注意
Vertex AI 允许您为表格数据创建以下模型类型:
二元分类模型
这些模型预测二元结果(两个类别中的一个)。用于是或否问题。
多类别分类模型
这些模型预测来自三个或更多离散类的一个类。用于分类。
回归模型
这些模型预测连续值。用于预测销售额。
预测模型
这些模型预测一系列值。用于预测每日需求。

图 3-2. 回归/分类选择。
一些框架在数据加载后会生成统计信息。其他框架通过自动检测和清理缺失值、异常值和重复行列来帮助减少手动清理数据的需求。请注意,您可以采取一些额外步骤,例如在加载后检查缺失值并查看数据统计。图 3-3 显示数据集上传选项。

图 3-3. 数据源选项。
图 3-4 显示使用谷歌 Vertex AI 框架生成的能源公用数据集的统计信息。没有缺失值,并显示每列的不同值数量。例如,有 145 个邮政编码。由于邮政编码是一个数字,转换列显示为“数字”。然而,邮政编码是一个分类特征,因为每个邮政编码都不同,可以被归为自己的“类别”。将 ZipCode 从数字转换为分类很容易,只需选择下拉菜单自定义转换。

图 3-4. 谷歌 Vertex AI 生成的统计数据。
图 3-5 显示 ZipCode 现在作为分类特征。还请注意最右侧列,您可以选择或取消选择要包含在训练中的特征。

图 3-5. ZipCode 显示为分类特征。
AutoML 显示每个特征的数据概况。图 3-6 显示邮政编码 92694 作为最常见的特征,表明更多客户居住在该邮政编码区域。您可以利用这些信息进行营销活动。

图 3-6. 显示邮政编码作为最常见特征。
在第 3 步中,您通过选择几个训练参数来训练新模型。Vertex AI 的“训练新模型”窗口允许您选择训练方法、模型详细信息、训练选项以及计算和定价。请注意,“训练方法”参数中显示了数据集和目标(回归)作为输入。AutoML 默认选中。图 3-7 显示了“训练新模型”窗口。

图 3-7. “训练新模型”窗口,选择了计算和定价。
输入完所有参数后,您可以开始训练作业。图 3-8 显示准备提交训练作业。

图 3-8. 提交训练作业进行训练。
注意
训练时间可能长达数小时,具体取决于数据大小和所选择的模型目标类型。图像和视频数据类型的处理时间可能比 CSV 文件等结构化数据类型长得多。训练样本数量也会影响训练时间。由于 AutoML 的性质及其工作方式,AutoML 训练也非常耗时。
训练模型后呈现模型训练结果。现在,您可以在进行下一步(可能包括更多实验或将模型部署为网页)之前向团队展示您的初步发现,用户可以选择客户类别和邮政编码,然后显示预测的总 kWh。
图 3-9 显示训练结果。在接下来的章节中,将详细讨论图 3-9 中呈现的指标的完整 AutoML 项目示例。

图 3-9. 模型训练结果。
模型特征归因告诉您每个特征对模型训练的影响程度。图 3-10 显示归因值以百分比表示;百分比越高,相关性越强,即该特征对模型训练的影响越大。模型特征归因使用采样的 Shapley 方法(请参阅GitHub 上的术语表)。

图 3-10. 归因值以百分比表示。
图 3-11 显示模型元数据。您可以查看有关模型的各种信息,包括其 ID、创建日期、训练时间、使用的数据集、目标列、数据拆分百分比分配以及在此案例中使用的模型评估指标——均方根误差(RMSE)。单击“模型”或“试验”可获取有关 AutoML 使用的模型信息。

图 3-11. 模型元数据。
AutoML 的工作原理
实施 ML 工作流程是耗时的。正如您从前面的市场营销案例中看到的那样,AutoML 简化了为您构建 ML 模型的过程——您不需要为任何任务编写一行代码。图 3-12 显示了公用事业营销推广项目的工作流程。阶段 2、3 和 4 无需编码。

图 3-12. 公用事业营销推广项目的工作流程。
要更好地理解 AutoML 的工作原理,请注意您无需做的事情!
首先,您无需设置环境来运行 Python Jupyter Notebook。无需安装任何软件库。事实上,无需安装任何东西。
一旦您将 CSV 数据文件上传到 Vertex AI,该文件将存储在云中。如果您正在使用 Python 和 Pandas,则无需编写任何代码来加载数据集,甚至不需要将数据集拆分为训练、验证和测试文件。
尽管数据干净,但有两个分类特征:邮政编码和客户类别。如果您对这两个特征进行了编码,您将需要对每个特征进行“独热编码”。独热编码是将分类数据变量转换为数值的过程。以下是用于邮政编码特征的独热编码示例代码:
from sklearn.preprocessing import OneHotEncoder
one_hot = OneHotEncoder()
encoded = one_hot.fit_transform(df[[`zipcode`]])
df[one_hot.categories[0]] = encoded.toarray()
独热编码是特征转换或工程化的一个示例。您还可以轻松选择目标(输出)并/或取消选择特征,或删除它们。您无需编写类似以下显示的代码,其中列“id”正在从 Pandas DataFrame 中删除:
import pandas as pd
df = pd.read_csv(`/path/file.tsv`, header=0, delimiter=`\t`)
print df.drop(`id`, 1)
数据集中的更多特征会导致更复杂的非线性关系。神经网络非常适合处理非线性关系。您可能对这些都一无所知,因此让我们进一步分解一下。
正如前面所述,这是一个预测问题,因为您希望根据平均千瓦时数、客户类别、月份、年份和邮政编码预测未来总千瓦时数。再深入理论,这种类型的用例因其多个输入特征而被认为是复杂的——它具有多个变量且为多变量。这些复杂关系被认为是非线性的,因为您不能简单地画一条“直线”,使其成为已知的总千瓦时数与其他多个变量之间的“最佳拟合”。
这个数据集非常适合神经网络。神经网络在没有先前的机器学习知识的情况下难以构建。尽管神经网络是下一章的话题,让我们快速看一下一幅图像,以确定您不需要考虑的内容。图 3-13 展示了具有输入层、隐藏层和输出层的典型神经网络。

图 3-13。四层神经网络。
在 Python 中编写神经网络的代码看起来像这样:
# Create the 'Network' class and define its arguments:
# Set the number of neurons/nodes for each layer
# and initialize the weight matrices:
class Network:
def __init__(self,
no_of_in_nodes,
no_of_out_nodes,
no_of_hidden_nodes,
learning_rate):
self.no_of_in_nodes = no_of_in_nodes
self.no_of_out_nodes = no_of_out_nodes
self.no_of_hidden_nodes = no_of_hidden_nodes
self.learning_rate = learning_rate
self.create_weight_matrices()
def create_weight_matrices(self):
""" A method to initialize the weight matrices of the neural network"""
rad = 1 / np.sqrt(self.no_of_in_nodes)
X = truncated_normal(mean=0, sd=1, low=-rad, upp=rad)
self.weights_in_hidden = X.rvs((self.no_of_hidden_nodes,
self.no_of_in_nodes))
rad = 1 / np.sqrt(self.no_of_hidden_nodes)
X = truncated_normal(mean=0, sd=1, low=-rad, upp=rad)
self.weights_hidden_out = X.rvs((self.no_of_out_nodes,
self.no_of_hidden_nodes))
def train(self, input_vector, target_vector):
pass # More work is needed to train the network
def run(self, input_vector):
"""
running the network with an input vector 'input_vector'.
'input_vector' can be tuple, list or ndarray
"""
# Turn the input vector into a column vector:
input_vector = np.array(input_vector, ndmin=2).T
# activation_function() implements the expit function,
# which is an implementation of the sigmoid function:
input_hidden = activation_function(
self.weights_in_hidden @ input_vector)
output_vector = activation_function(
self.weights_hidden_out @ input_hidden)
return output_vector
使用 Keras,编码变得更加简单。编写神经网络的代码看起来像这样:
# Import python libraries required in this example:
from keras.layers import Dense, Activation
from keras.models import Sequential
import numpy as np
# Use numpy arrays to store inputs (x) and outputs (y):
x = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])
# Define the network model and its arguments.
# Set the number of neurons/nodes for each layer:
model = Sequential()
model.add(Dense(2, input_shape=(2,)))
model.add(Activation('relu'))
model.add(Dense(1))
model.add(Activation('relu'))
# Compile the model and calculate its accuracy:
model.compile(
loss='mean_squared_error', optimizer='rmse', metrics=['accuracy']
)
# Print a summary of the Keras model:
model.summary()
当您建立训练作业时,只需选择一个数据集,然后选择几个训练参数。不需要以下任何内容:
不需要了解使用什么样的回归算法
有许多种回归分析技术,每种方法的使用取决于多种因素。这些因素包括目标变量的类型、回归线的形状和自变量的数量。
不需要了解“经典机器学习”与神经网络的区别
不需要理解常用的神经网络构建模块,如层、神经元(节点)、目标、激活函数或优化器(参见GitHub 上的术语表)。
不需要了解训练过程或任何模型优化策略
在训练过程中,AutoML 专注于优化模型权重和架构。选择适当的架构由 AutoML 完成。
不需要了解或指定计算资源
当您选择“一个节点”时,AutoML 选择了合适的机器类型。
注意
参见GitHub 上的术语表了解回归算法类型的定义,如线性回归、逻辑回归、岭回归、拉索回归、多项式回归和贝叶斯线性回归。
机器学习即服务
AutoML 是云提供商提供的机器学习即服务(MLaaS)平台的一部分。排名前三的云提供商是 Google、Amazon 和 Microsoft。如果您对云架构和服务不熟悉,图 3-14 展示了典型的云“平台金字塔”。
占据金字塔底部的是 IaaS(基础设施即服务)。将此层视为硬件和存储层,客户使用云提供商的服务器处理实际的计算和存储服务,用于存储数据集文件、模型、容器等。中间层是 PaaS(平台即服务)。将此层视为提供平台(如 Linux 或 Windows 操作系统),客户使用该平台运行其自己的软件。顶层是 SaaS(软件即服务)。AutoML 是最佳示例 — 您不需要配置服务器或编写代码,只需打开浏览器即可使用它。

图 3-14. 典型云“平台金字塔”。
Google、Amazon 和 Microsoft 提供支持整个 ML 工作流程的服务,包括 ML 学习算法的训练和调整、数据可视化、数据预处理,以及深度学习。它们还为使用 scikit-learn、Keras、TensorFlow 和 PyTorch 等框架提供托管的 Jupyter Notebooks。表 3-1 展示了 MLaaS 的益处。
表 3-1. MLaaS 益处
| 数据提取和探索 |
|---|
-
使用 Jupyter Notebook 或在某些情况下使用云提供商自己的数据可视化工具提取和清理数据集。
-
对模型应用数据转换和特征工程,并将数据分割为训练、验证和测试集。
-
使用 GUI 工具或 Jupyter Notebook 探索和可视化数据。
|
| 模型训练 |
|---|
-
用于表格、图像、文本和视频数据的 AutoML。自动特征化和算法选择。
-
共享笔记本、计算资源、数据和环境。
-
使用 PyTorch、TensorFlow 或 scikit-learn 等开源平台进行定制训练。
-
优化超参数。
-
通过运行不同的模型版本和技术进行实验,并比较结果。
|
| 分布式训练 | 提供多节点分布式训练。 |
|---|---|
| 模型评估和迭代 | 提供评估指标,使您可以对数据进行调整并迭代您的模型。 |
| 模型可解释性 | 理解每个特征如何影响模型预测(特征归因)。 |
| 模型服务 | 部署您的模型到生产环境并进行预测。 |
| 模型监控 | 监控部署模型的性能,检测训练与服务之间的偏差和预测漂移,并在预测数据与训练基线偏离过大时发送警报。 |
AutoML 是企业和组织希望利用 ML 提升运营效率的有价值工具。通过自动化构建 ML 模型中许多耗时复杂的任务,AutoML 能帮助企业和组织更快地启动和运行模型。以下是一些当前业务中如何使用 AutoML 的具体示例:
电信
电信公司正在使用 AutoML 来改善客户流失预测、欺诈检测和网络优化。
制造业
制造业公司正在使用 AutoML 来提高产品质量、优化生产流程并预测设备故障。
零售
零售商正在使用 AutoML 来个性化客户体验,推荐产品并优化库存水平。
医疗保健
医疗保健公司正在使用 AutoML 来诊断疾病、预测患者结果并个性化治疗计划。
这些只是今天业务中如何使用 AutoML 的几个例子。随着 AutoML 技术的不断成熟,未来预计会看到更多创新的 AutoML 应用。表 3-2 展示了 AutoML 的益处。
表 3-2. AutoML 的益处
| 提高准确性 | AutoML 可以帮助企业建立更准确的 ML 模型。这是因为 AutoML 可以尝试比人类数据科学家更广泛的算法和超参数。 |
|---|---|
| 普及 ML | AutoML 使非专家更容易接触 ML。这是因为 AutoML 提供了一个简单、用户友好的界面来构建和部署 ML 模型。 |
| 缩短上市时间 | AutoML 可以帮助企业更快地启动和运行其 ML 模型。这是因为 AutoML 自动化了构建 ML 模型中许多耗时的任务,如数据清洗、特征工程和模型训练。 |
| 降低风险 | AutoML 可以帮助企业降低构建偏见或不准确的 ML 模型的风险。这是因为 AutoML 自动化了模型验证和测试中的许多步骤。 |
图 3-15 展示了谷歌的 Vertex AI 解决方案,图 3-16 展示了微软的 Azure ML Studio 解决方案,以及 图 3-17 展示了 Amazon SageMaker 的 AutoML 解决方案。

图 3-15. 谷歌的 Vertex AI 界面。

图 3-16. 微软的 Azure ML Studio 界面。

图 3-17. Amazon SageMaker 的 AutoML 界面。
低代码 ML 框架
低代码 AutoML 需要安装和配置库,以及一些 Python 或结构化查询语言(SQL)的知识。低代码 在此处的定义如下:
-
提供在现有 ML 框架之上的“抽象层”的 ML 框架。
-
允许您使用 SQL 运行 ML 模型的数据库,或者允许您运行包含 ML 代码的 Python 代码的数据库。
表 3-3 展示了一些例子。
表 3-3. ML 框架示例
| 云提供商(SQL) | 开源 |
|---|---|
| 谷歌 | BigQuery |
| Amazon | Aurora, Redshift |
| Microsoft | Azure SQL Server |
SQL ML 框架
数据分析师和数据科学家通常使用 SQL 进行数据分析。他们可以利用现有的 SQL 技能和专业知识,并将其应用于 ML 项目,而无需具备任何 ML 编程背景。如果他们懂 SQL 但无法编写 Python、Java 或 R 代码,他们可以在 SQL-ML 框架内工作。这就是为什么 SQL-ML 框架被认为是低代码的原因。几乎不需要大量的 SQL 编码。
使用数据库/数据仓库进行 SQL-ML 的好处:
大数据集的模型构建
当您可以在数据“居住”的地方构建 ML 模型时,ML SQL 代码会“接近数据”,从而减少延迟(数据传输时间)。这对于使用深度学习的大型数据集尤为重要,其中训练需要通过数据的一部分进行迭代,以用于训练、验证和测试。
与现有 ML 系统的后端集成
与云服务提供商的后端 ML 框架集成(例如 Google 的 Vertex AI、Amazon 的 SageMaker 和 Microsoft 的 Azure)。
常见的模型构建语句
所有使用 CREATE MODEL SQL 命令,并指定训练数据作为表格或 SELECT 语句。然后在数据仓库中编译和导入训练模型,并准备一个 SQL 推理函数,可以立即在 SQL 查询中使用。
使用案例
典型用例包括欺诈检测、产品推荐和广告定位,由于低延迟和实时需求。
注意
数据库 是数据或信息的集合。术语 数据库 通常用于引用数据库本身以及数据库管理系统(DBMS)。数据仓库 存储来自各种来源的大量当前和历史数据。
Google 的 BigQuery ML
Google 的 BigQuery 是一个数据仓库。它可以通过其 ML 工具提供决策指导和预测分析。您可以在 BigQuery 中创建和训练模型,无需导出数据。与 Vertex AI 一样,BigQuery ML 不需要环境和依赖设置。BigQuery ML 是基于浏览器的和无服务器的,意味着您无需服务器即可运行它。如果您的数据已驻留在 BigQuery 的数据仓库中,则可以将该数据用于您的 ML 项目。
Amazon Aurora ML 和 Redshift ML
Amazon Aurora 是为云构建的关系数据库管理系统(RDBMS),完全兼容 MySQL 和 PostgreSQL。Amazon Aurora ML 可以通过 SQL 为应用程序添加基于 ML 的预测。当运行 ML 查询时,Aurora 调用 Amazon SageMaker 使用各种 ML 算法。
Redshift ML 是一个数据仓库。您可以使用 SQL 语句在 Redshift 数据上创建和训练 Amazon SageMaker ML 模型,然后使用这些模型进行预测。Redshift ML 将模型作为 SQL 函数提供在 Redshift 数据仓库中使用。
开源 ML 库
开源 AutoML 指的是诸如 AutoKeras, Auto-sklearn 和 Auto-PyTorch 等开源框架,这些框架在现有开源库之上增加了额外的抽象层。通常,您需要使用 Jupyter 笔记本来编码以下内容:
-
安装 AutoML 包。
-
导入包。
-
加载数据集。
-
拆分数据。
-
拟合模型。
-
预测。
-
评估。
-
导出模型。
在第四步之后,每个开源框架都有自己的方法来执行模型拟合、预测和评估。图 3-18 展示了前四个步骤。

图 3-18. 使用开源库的前四个步骤。
AutoKeras
AutoKeras 是基于 Keras 的开源 AutoML 框架,旨在允许非专家快速构建神经网络,并且代码量最小。使用 AutoKeras 时,您只需指定训练数据,AutoKeras 将独立进行数据预处理。例如,如果数据包含分类变量,它将根据是分类任务还是回归任务将其转换为独热编码;如果输入数据包含文本,AutoKeras 将其转换为嵌入。
Auto-Sklearn
Auto-sklearn 是基于 scikit-learn ML 库的开源 Python 包。Auto-sklearn 自动搜索新 ML 数据集的正确学习算法,并优化其超参数。该框架仅支持基于 sklearn 的模型。Auto-sklearn 由弗莱堡大学和汉诺威大学的实验室开发。
Auto-PyTorch
除了 Auto-sklearn,Freiburg-Hannover AutoML 小组还开发了基于 PyTorch 的 AutoML,专注于深度学习。Auto-PyTorch 被认为在快速原型设计方面表现出色。它还支持分布式训练。
总结
业务分析师、数据分析师、公民数据科学家、数据科学家、软件开发人员和 ML 工程师都可以使用 AutoML 框架来简化开发过程。
首先,您加载包含目标变量和用于预测的输入特征数据的数据集。加载数据后,会为每个数据列生成数据概要。要提交训练作业,只需选择几个参数。AutoML 然后尝试多个模型并执行模型优化。结果被呈现出来,同时还有特征归因。
云供应商提供 MLaaS,加速和自动化日常 ML 工作流,提供将模型集成到应用程序或服务中的工具,以及将模型部署到生产环境的工具。
低代码 AutoML 需要安装和配置库,并且需要一些 SQL 或 Python 的知识。开源 AutoML 指的是诸如 AutoKeras、Auto-sklearn 和 Auto-PyTorch 等开源框架,它们在现有的开源库之上提供了额外的抽象层。
在 第四章 中,您将建立一个 AutoML 模型来预测广告媒体渠道的销售情况。首先,您会使用 Pandas 探索数据。然后,您将学习如何使用 AutoML 构建、训练和部署机器学习模型以预测销售额。
第四章:使用 AutoML 预测广告媒体渠道销售
在本章中,您将建立一个 AutoML 模型来预测广告媒体渠道的销售情况。首先,您将使用 Pandas 探索您的数据。然后,您将学习如何使用 AutoML 构建、训练和部署一个 ML 模型来预测销售。您将通过性能指标全面了解您模型的表现,并回答常见的业务问题。在此过程中,您将学习到用于预测用例的常见技术——回归分析。
业务用例:媒体渠道销售预测
企业使用广告媒体渠道来推广其产品、服务或品牌。营销人员和媒体策划人员创建可能在数字、电视、广播或报纸上运行的营销活动。在这种情况下,您作为一个中型太阳能公司市场部门的媒体策划人员工作。您的公司有一个适度的媒体预算,需要评估哪些渠道提供了最大的利益,成本最低。这是一个支出优化问题。
您被要求制定一个营销计划,以增加明年的产品销售量。为了实现这一目标,您需要了解媒体渠道产品广告预算对整体销售额的影响。广告数据集捕捉了在数字、电视、广播和报纸媒体渠道上的广告费用与销售收入之间的关系。
通常情况下,这种要求会交给数据科学家或数据分析师。但是,尽管您没有任何编程经验,市场团队负责人却要求您使用 AutoML 构建销售预测模型,这是团队首次尝试的事情。目标是建立一个机器学习模型,根据在每个媒体渠道上的花费来预测销售量。
业务问题包括:
-
模型能否根据每个媒体渠道的投入预测未来的销售量?
-
广告预算和销售之间是否存在关系?
-
哪个媒体渠道对销售贡献最大?
-
该模型能否用于预测基于媒体渠道拟议预算的未来销售?
-
模型能够准确预测未来销售吗?
该用例是一个简单的回归问题,仅包含五个变量,您可以用来回答前面的五个问题。
项目工作流程
图 4-1 显示了典型 AutoML 无代码工作流程的高级概述,来自第 3 章。该工作流程适用于您的用例。

图 4-1. 业务案例的 AutoML 工作流程。
现在您了解了业务用例和目标,可以继续进行数据提取和分析。请注意,此工作流程不包括数据预处理步骤。在后续章节中,您将获得大量数据预处理的实践经验。数据提取和分析后,您将上传数据集到 AutoML 平台。数字、电视、报纸和广播数据的广告预算将输入模型。然后,您将评估 AutoML 的结果,并部署模型进行预测。完成本章的实践后,您将能够为您的团队创建战略营销计划。
项目数据集
数据集由历史营销渠道数据组成,可用于洞察支出分配并预测销售。本章使用的数据集,广告 _2023 数据集,基于An Introduction to Statistical Learning with Applications in R一书(Springer, 2021)中的数据,作者为 Daniela Witten、Gareth M. James、Trevor Hastie 和 Robert Tibshirani。该广告数据集记录了特定产品广告预算(以千美元计)在电视、广播和报纸媒体上产生的销售收入(以千单位计)。
对于本书,数据集已更新以包括数字变量,并修改以显示数字预算对销售的影响。市场数量已从 200 增加到 1,200 个。因此,数据包括 1,200 个不同市场的广告预算(数字、电视、广播和报纸)以及总销售额。完成本章练习后,鼓励您查看如何处理此数据集的其他示例以增进您的知识。
数据最初以 CSV 文件的形式提供,因此您需要花些时间将数据加载到 Pandas 中才能进行探索。数据集仅包含数值变量。
数据集有五列。表 4-1 提供了这些列的列名、数据类型以及关于这些列可能值的一些信息。
表 4-1. 广告数据集的模式和字段值信息
| 列名 | 列类型 | 字段值的注释 |
|---|---|---|
| 数字 | 数字 | 在数字广告上的支出金额 |
| 报纸 | 数字 | 在报纸广告上的支出金额 |
| 广播 | 数字 | 在广播广告上的支出金额 |
| 电视 | 数字 | 在电视广告上的支出金额 |
| 销售额 | 数字 | 总媒体渠道销售额 |
使用 Pandas、Matplotlib 和 Seaborn 探索数据集
在开始使用 AutoML 之前,您需要遵循前几章讨论的工作流程,以了解和准备 ML 所需的数据。本节向您展示如何使用 Pandas 将数据加载到 Google Colab 笔记本中,Pandas 是一种广泛用于数据科学和数据分析的开源 Python 包。一旦数据加载到 DataFrame 中,您将探索数据。幸运的是,数据已经过清理—数据集中没有缺失值或奇怪的字符。您的探索性数据分析是为了验证数据的清洁性,并探索变量之间的关系,以帮助您回答团队提出的问题。正如前几章所述,大部分 ML 工作是用于理解和准备训练数据—而不是训练模型—因为您依赖于 AutoML 来构建模型。
本节中的所有代码,包括一些额外的示例,都包含在名为low-code-ai repository on GitHub的 Jupyter 笔记本 Chapter4_Media_Channel_Sales_Notebook 中。
在 Google Colab Notebook 中将数据加载到 Pandas DataFrame 中
首先,转到https://colab.research.google.com,打开一个新笔记本,按照第二章中讨论的过程操作。您可以通过单击图 4-2 中显示的名称并用新名称替换当前名称,例如Advertising_Model.ipynb,对笔记本进行重命名。

图 4-2. 将 Google Colab 笔记本重命名为更有意义的名称。
现在将以下代码输入到第一个代码块中,以导入分析和可视化广告数据集所需的包:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import seaborn as sns
%matplotlib inline
当您首次探索 Colab 笔记本的使用时,在第二章中就看到了这些包的一些示例。
现在执行包含导入语句的单元格以导入这些包。要执行此操作,请单击单元格左侧的“运行单元格”按钮。您也可以按 Shift + Enter 运行单元格。
现在您可以导入您的数据了。使用 Pandas,您可以直接从互联网上的位置将 CSV 文件导入 DataFrame,而无需先下载文件。要做到这一点,请从解决方案笔记本中复制代码或在新单元格中输入以下代码并执行该单元格:
url="https://github.com/maabel0712/low-code-ai/blob/main/advertising_2023.csv?raw=true"
advertising_df=pd.read_csv(url, index_col=0)
一般来说,查看 DataFrame 的前几行是个好主意。使用advertising_df.head()来探索 DataFrame 的前几行。head Pandas 方法让我们看到我们数据的前五行。通过这样做,您可以快速查看特征、一些可能的值以及它们是否是数值。
一个示例是在图 4-3 中显示的几列。

图 4-3. 使用head()方法打印出来的 DataFrame advertising_df 的前五行的几列。
探索广告数据集
现在数据已经加载到 DataFrame advertising_df 中,你可以开始探索和理解它。当前的目标是了解数据中可能存在的问题,以便在继续之前解决这些问题。
描述性分析:检查数据
首先使用标准的 Python 方法检查数据。要检查 DataFrame 的数据类型,请在一个新的单元格中键入 advertising_df.info() 并执行该单元格。信息包括列数、列标签、列数据类型、内存使用情况、索引范围以及每列的单元格数(非空值)。
图 4-4 展示了info()方法的输出示例。

图 4-4. 使用info()方法获取数据集信息。
注意,有了数据类型的信息,你可以检查确保 Pandas 推断的类型与预期的表 4-1 相匹配。
图 4-5 显示了describe()方法的输出,该方法计算并显示数据集的汇总统计信息。describe函数显示了数据集的数值变量的信息。你可以看到每个变量的均值、最大值、最小值以及它们的标准差。在一个新的单元格中键入 advertising_df.describe() 并执行该单元格。

图 4-5. 广告数据集所有列的汇总统计信息。
图 4-6 展示了.isnull()方法的输出。在一个新的单元格中键入 advertising_df.isnull().sum() 并执行该单元格。输出显示了 DataFrame 中所有列的关联零值。如果存在空值,则会显示每列的空值数量。

图 4-6. 使用isnull()方法确定空值。
探索数据
探索性数据分析(EDA)是任何 ML 项目的第一步。在构建任何 ML 模型之前,你需要先探索数据。目标是查看原始数据、探索数据并从中获取相关的见解。通过这样做,你还可以改进模型,因为你将能够发现任何可能影响性能的“脏数据”问题,如缺失值、列中的奇怪字符等。
热力图(相关性)
热图是一种以视觉方式表示数据的方法。数据值在图中表示为颜色。热图的目标是提供信息的彩色视觉摘要。热图显示了变量(特征)之间的关系(相关性)。相关性是一个统计量,显示两个或多个变量如何一起移动的程度。在图 4-7](#correlation_matrix_for_advertising_medi)中显示了一个相关性方法的输出,该方法在网格上绘制相关性值。在新单元格中键入以下代码并执行该单元格:
plt.figure(figsize=(10,5))
sns.heatmap(advertising_df.corr(),annot=True,vmin=0,vmax=1,cmap='viridis')

图 4-7. 广告媒体渠道的相关矩阵。
相关矩阵的结果可以在多种方式中使用,包括:
识别变量之间的关系
两个变量之间的相关系数可以告诉您它们之间的关系强度。相关系数为 0 意味着没有关系,而相关系数为 1 意味着存在完美的正关系。相关系数为–1 意味着存在完美的负关系。请注意,销售和电视之间的关系最为显著(0.78),其次是销售和广播(0.58)。这些信息可用于制定有针对性的营销活动,以提高销售。
选择模型中包含的变量
在构建预测模型时,您需要选择最有可能预测结果的变量。例如,是否应将报纸(与销售的相关性为 0.23)作为模型中的特征包含以预测销售?
检测多重共线性
当回归模型中两个或更多预测变量高度相关时,就会发生多重共线性。例如,如果电视和广播的相关性很高(意味着两者的值>0.7,而不是 0.056,如图 4-7](#correlation_matrix_for_advertising_medi)所示),则表明存在多重共线性。由于很难从数值上区分具有强共线关系的预测变量,因此回归算法更难确定其中一个对销售的影响程度或权重。
警告
作为警告,由于样本大小或数据集中的异常值,相关矩阵的结果可能会发生变化。
散点图
散点图用于确定两个数值变量之间的关系。它们可以帮助您看出两个变量之间是否存在直接关系(例如正线性关系或负线性关系)。此外,它们还可以帮助您检测数据是否存在异常值。图 4-8 显示了数字特征和销售目标的散点图。在新单元格中键入以下代码并执行该单元格:
advertising_df.plot(kind='scatter', x=['digital'], y='sales')

图 4-8. 数字和销售的散点图。
你想探索每个变量与预测变量 sales 的散点图。你可以单独绘制每个特征的散点图(如之前所做的那样),或者将它们绘制在同一张图中以展示所有关系。将以下所有代码键入新的单元格并执行单元格(图 4-9 显示输出):
plt.figure(figsize=(18, 18))
for i, col in enumerate(advertising_df.columns[0:13]):
plt.subplot(5, 3, i+1) # each row three figure
x = advertising_df[col] #x-axis
y = advertising_df['sales'] #y-axis
plt.plot(x, y, 'o')
# Create regression line
plt.plot(np.unique(x), np.poly1d(np.polyfit(x, y, 1)) (np.unique(x)),
color='red')
plt.xlabel(col) # x-label
plt.ylabel('sales') # y-label

图 4-9. 所有特征和销售目标的散点图。
请注意,电视和销售之间存在强烈的线性关系——似乎显示出电视预算增加时,对销售量有正面影响。报纸和销售之间似乎没有强烈的关系。回顾该关系的相关系数为 0.23。这与电视和销售之间的关系(0.78)非常不同。
直方图分布图
可视化分布的常见方法是直方图。直方图是一种柱形图,其中代表目标变量的轴被划分为一组离散的箱子,每个箱子内观察到的数量用相应柱的高度表示。
将此代码键入新的单元格并运行单元格:
sns.displot(advertising_df, x="sales")
图 4-10 显示了销售列的数据值。该图看起来略呈左偏态钟形曲线。最常见的销售额为$11,000 美元。

图 4-10. 稍微向左偏斜的销售直方图。
其他特征呢?它们是向左偏斜还是向右偏斜,还是像钟形曲线一样“正态分布”?你可以为每个单独的特征键入前面的代码,或者使用以下代码查看所有特征的汇总。将以下代码键入新的单元格并执行单元格(图 4-11 显示输出):
lis = ['digital', 'newspaper', 'radio','TV']
plt.subplots(figsize=(15, 8))
index = 1
for i in lis:
plt.subplot(2, 2, index)
sns.distplot(advertising_df[i])
index += 1
正如你在图 4-10 中看到的那样,销售基本上呈正态分布。然而,在图 4-11 中,数字似乎向左偏斜,而电视、广播和报纸则不是正态分布的。在将这些特征标准化为正态分布之前,将它们输入到你的 ML 模型中会产生更好的结果。
注意
然而,你的角色并不是数据科学家。不必担心理解这些概念。讨论每个特征所需的每个转换超出了本章节的范围。请参阅第 7 章,在数据集上执行转换操作。

图 4-11. 数字、电视、广播和报纸的分布图。
导出广告数据集
在检查和探索数据集之后,现在是时候导出文件,以便将其上传到您的 AutoML 框架中。将以下代码输入到一个新的单元格中并执行该单元格。第一行导入操作系统,使您可以创建一个名为 data 的目录(第二和第三行):
import os
if not os.path.isdir("/content/data"):
os.makedirs("/content/data")
在创建完目录后,将以下代码输入到一个新的单元格中并执行该单元格。第一行代码创建了广告数据框架的 CSV 文件格式,并将其放置在您在上一步中创建的 content/data 目录中:
advertising_df.to_csv('/content/data/advertising.csv',
encoding='utf-8', index=False)
图 4-12 显示了名为 data 的新创建目录及其文件 advertising.csv。

图 4-12. 新创建的 data 目录中的 advertising.csv 文件。
作为最佳实践,请验证您能够看到新导出文件的内容位于新创建的目录中。在一个新的单元格中键入 !head /content/data/advertising.csv 并执行该单元格。检查输出是否与 图 4-13 中显示的内容相同。

图 4-13. 广告.csv 文件的输出。
现在您已经验证文件已正确导出,可以将其下载到您的计算机上。
在新创建的 data 目录中右键单击 advertising.csv 文件,并选择如 图 4-14 所示的下载。该文件将下载到您的桌面。您现在可以将该文件上传以供 AutoML 使用。

图 4-14. 在数据目录中验证 advertising.csv 文件。
在接下来的部分中,您将基于刚刚导出的训练数据文件构建一个无代码模型。
使用 AutoML 训练线性回归模型
本书的 AutoML 项目将使用 Google 的 Vertex AI 实施,这是作者们最熟悉的基于 GUI 的 AutoML 和自定义训练框架。请注意,三大主要云供应商(Google、Microsoft 和 AWS)都提供 AutoML 教程。可以在它们的文档中找到这三家主要云供应商的指南。许多云供应商提供试用期,免费探索其产品。
鉴于 Google 提供了有关 AutoML 的逐步教程,某些入门步骤被排除在外。
图 4-15 展示了用于您业务用例的 AutoML 无代码工作流的高级概述。

图 4-15. 自动化机器学习(AutoML)无代码工作流适用于您的用例。
无代码使用 Vertex AI
图 4-16 显示了 Vertex AI 仪表板。要创建 AutoML 模型,请点击“启用所有推荐的 API”按钮开启 Vertex AI API。从左侧导航菜单中,从“仪表板”中滚动到“数据集”并选择。

图 4-16. Vertex AI 仪表板显示“启用所有推荐的 API”按钮。
在 Vertex AI 中创建托管数据集
Vertex AI 根据数据类型和您希望通过模型实现的目标提供不同的 AutoML 模型。创建数据集时,您选择一个初始目标,但创建数据集后,您可以使用它来训练具有不同目标的模型。保持默认区域(us-central1),如 图 4-17 所示。
在页面顶部选择“创建”按钮,然后为数据集输入名称。例如,您可以将数据集命名为 advertising_automl。

图 4-17. Vertex AI 允许您创建数据集的数据集导航。
选择模型目标
图 4-18 显示选中了回归/分类作为表格选项卡下的模型目标。如果您想要预测目标列(销售)的值,这是合适的选择。

图 4-18. 用于模型目标的回归/分类选择。
您选择了回归/分类作为您的目标。让我们介绍一些基本概念,以帮助您处理未来的使用案例。回归 是一种监督式机器学习过程。它与分类相似,但与预测分类标签(例如从您的电子邮件收件箱分类垃圾邮件)不同,您尝试预测一个连续值。线性 回归定义了目标变量(y)与一组预测特征(x)之间的关系。如果您需要预测一个数值,那么请使用回归。在您的使用案例中,线性回归使用数据集中提供的一些独立变量(数字、电视、广播和报纸)来预测一个实际值(销售)。
本质上,线性回归假设与每个特征的线性关系。预测值是线上的数据点,真实值则在散点图中。目标是找到最佳拟合线,以便在输入新数据时,模型可以预测新数据点相对于线的位置。“评估”拟合效果如何好,包括评估标准——这在 “评估模型性能” 中有所涵盖。
图 4-19 显示了基于您的数据集的“最佳拟合”线,其中模型尝试将线拟合到您的数据点(即深色散点)。

图 4-19. 真实值和预测值及最佳拟合线。
在进行回归/分类选择后,向下滚动并点击“创建”按钮。现在您可以向数据集添加数据了。Vertex AI 管理的数据集适用于各种数据类型,包括表格、图像、文本和视频数据。
图 4-20 展示了数据源上传选项——从计算机上传 CSV,从云存储选择 CSV 文件,或从 BigQuery(Google 的数据仓库)选择表或视图。

图 4-20. 数据集文件的数据源上传选项。
要上传您的广告数据集,请选择“从计算机上传 CSV 文件”。找到本地计算机上的文件并加载它。
向下滚动页面并查看“选择云存储路径”部分,要求您将文件存储在云存储桶中。为什么需要将文件存储在云存储桶中?有两个原因:(1)当训练大规模 ML 模型时,您可能需要存储数千兆字节甚至数百个千兆字节的数据;(2)云存储桶可扩展、可靠且安全。
图 4-21 显示 Advertising_automl.csv 文件已上传,并创建了一个云存储桶以存储上传的文件。要查看创建存储桶及整个操作的逐步过程,请参阅名为 Chapter 4 AutoML Sales Prediction 的 PDF 文件,位于 repository 中。

图 4-21. 数据源选项,用于加载 CSV 文件并将其存储在云存储桶中。
一些框架在数据加载后会生成统计信息。其他框架则通过自动检测和清理缺失值、异常值和重复行列来帮助减少手动数据清理的需求。请注意,您可以采用一些额外步骤,例如在加载后检查缺失值和查看数据统计。
图 4-22 展示了生成统计窗口的输出。请注意,这里没有缺失值,并显示了每列的不同值数量。

图 4-22. 生成统计窗口的输出。
AutoML 会为每个特征展示数据概要。要分析特征,请点击特征的名称。页面将显示该特征的特征分布直方图。
图 4-23 显示了销售的数据概况。请注意,均值为 14.014,这与您在探索数据集时键入advertising_df.describe()代码获得的数字值非常接近。

Figure 4-23. 销售特征概况。
构建训练模型
图 4-24 显示模型现已准备好训练。在“培训作业和模型”部分选择“训练新模型”。选择其他而非管道上的 AutoML。AutoML on Pipelines 是一种功能,允许您指定要构建的 ML 模型类型和其他参数。这超出了本书的范围。

Figure 4-24. 现在模型已准备好训练。
“训练新模型”窗口显示。有四个步骤:
-
选择培训方法
-
配置模型详细信息
-
确定训练选项
-
选择计算和定价
在第一步中,在目标下拉菜单中选择回归。在“模型培训方法”下选择 AutoML(如图 4-25 所示)。点击继续。

Figure 4-25. 在第一步中配置培训方法。
在第二步中,在“模型详细信息”下,为您的模型命名并给出描述。在“目标列”下拉菜单中选择销售(如图 4-26 所示)。点击继续。

Figure 4-26. 在第二步中添加模型详细信息。
在第三步中,审查培训选项。请注意,任何数据转换(或数据处理)如标准化都将自动处理(如图 4-27 所示)。点击继续。

Figure 4-27. 在第三步中添加培训选项。
在第四步中,您将看到“计算和定价”(如图 4-28 所示)。训练模型所需的时间取决于训练数据的大小和复杂性。一个节点小时是云中一个节点(如虚拟机)的使用时间,分布在所有节点上。在预算字段中输入3,以获取最大节点小时数—这仅是一个估计值。您只需支付使用的计算小时数;如果训练由于用户未启动的取消以外的任何原因而失败,则不会计费。如果取消操作,您将支付培训时间费用。

Figure 4-28. 在第四步中选择计算和定价。
在“计算和定价”下还有“提前停止”。启用此选项表示,当 AutoML 确定无法再改进模型时,训练将结束。如果禁用提前停止,则 AutoML 将训练模型直到耗尽预算小时数。
输入所有参数后,您可以启动训练作业。单击开始训练。
在模型训练完成后,模型将注册到模型注册表中(如图 4-29 所示)。

图 4-29. Advertising_automl 模型显示在模型注册中。
注意
如前所述,培训可能需要数小时,具体取决于您的数据大小和选择的模型目标类型。图像和视频数据类型的处理时间可能比 CSV 文件等结构化数据类型长得多。培训样本的数量也会影响培训时间。
此外,AutoML 非常耗时。AutoML 算法需要训练各种模型,而这一训练过程可能计算量很大。这是因为 AutoML 算法通常尝试大量不同的模型和超参数,并且每个模型都需要在整个数据集上进行训练。然后,AutoML 算法需要从训练好的模型集中选择最佳模型,而这一选择过程也可能非常耗时。这是因为 AutoML 算法通常需要评估每个模型在保留数据集上的性能,而这一评估过程可能计算量很大。
评估模型性能
图 4-30 显示了模型训练结果。

图 4-30. 使用五个评估指标的模型训练结果。
当从业者权衡不同线性回归评估指标的重要性时,有几个因素需要考虑:
模型的目的
模型的目的将决定哪些评估指标最重要。例如,如果模型用于进行预测,那么从业者可能希望关注诸如均方误差(MSE)或根均方误差(RMSE)等指标。然而,如果模型用于理解变量之间的关系,那么从业者可能希望关注 R 平方或调整后的 R 平方等指标。
数据的特征
数据的特征也会影响不同评估指标的重要性。例如,如果数据存在噪声(例如包含不需要的信息或错误),那么从业者可能希望关注对噪声鲁棒的指标,如平均绝对误差(MAE)。然而,如果数据没有噪声,那么从业者可能能够关注更加敏感于模型变化的指标,如 MSE。
从业者的偏好
最终,实践者的偏好也会影响确定不同评估指标的重要性。一些实践者可能更喜欢易于理解的指标,而其他人可能更喜欢更准确的指标。没有对与错的答案,实践者应选择对他们最重要的指标。
这里是常见的线性回归评估指标:
R 平方
R 平方是衡量模型与数据拟合程度的指标。它是观察值和预测值之间的皮尔逊相关系数的平方。它通过将平方残差(预测值与实际值之间的差异)除以总平方和来计算。较高的 R 平方值表示拟合效果更好。R 平方范围从 0 到 1,较高的值表示模型质量更高。你的 R² 应该在约 0.997 左右。
调整后的 R 平方
调整后的 R 平方是 R 平方的修改版本,考虑了模型中独立变量的数量。它通过将平方残差之和除以总平方和减去自由度来计算。较高的调整后 R 平方值表示拟合效果更好,但对独立变量的数量不太敏感,与 R 平方相比。
均方误差 (MSE)
MSE 是预测值与实际值之间平均平方误差的度量。较低的 MSE 值表示拟合效果更好。图 4-31 显示了表格和图表中可视化的损失。

图 4-31. RMSE 真实值和预测值的损失公式。
均方根误差 (RMSE)
RMSE 是 MSE 的平方根。它是 MSE 的更易解释版本。较低的 RMSE 值表示拟合效果更好和模型质量更高,其中 0 表示模型没有误差。解释 RMSE 取决于系列值的范围。你的 RMSE 应该在约 0.345 左右。
均方根对数误差 (RMSLE)
解释 RMSLE 取决于系列值的范围。RMSLE 对异常值的响应较小,比 RMSE 更加严厉地惩罚低估。你的 RMSLE 应该在约 0.026 左右。
平均绝对误差 (MAE)
MAE 是预测值与实际值之间平均绝对误差的度量。较低的 MAE 值表示拟合效果更好。你的 MAE 应该在约 0.304 左右。
平均绝对百分比误差 (MAPE)
MAPE 范围从 0% 到 100%,较低的值表示模型质量更高。MAPE 是绝对百分比误差的平均值。你的 MAPE 应该在约 2.28 左右。
模型特征重要性(归因)
模型特征重要性告诉您每个特征对模型训练的影响程度。图 4-32 显示的归因值以百分比表示;百分比越高,相关性越强—即该特征对模型训练的影响越大。特征归因允许您看到哪些特征对于图 4-32 中显示的模型训练结果贡献最大。

图 4-32. 广告数据集特征重要性结果。
如果您将鼠标悬停在图 4-32 中显示的报纸特征上,您会发现其对模型训练的贡献率为 0.2%。这支持您在之前完成的探索数据分析阶段中发现的结论—即销售额与报纸广告支出之间的关系最弱。这些结果意味着广播、数字和电视广告对销售额的贡献最大,而报纸广告对销售额影响较小。
从您的模型获取预测。
部署模型时,您需要对其进行测试。您可以部署到您的环境中测试您的模型,而无需构建一个需要部署到云中的应用程序。训练完机器学习模型后,您需要部署该模型,以便其他人可以用它进行推理。在机器学习中,推理是使用训练好的模型对新数据进行预测的过程。
有四个步骤,但对于本章节,您只需要前两步。在这个练习中,不需要配置模型监控或模型目标。模型监控会额外收费以记录日志,而模型目标要求您根据训练的模型类型和使用的应用程序选择各种模型目标。以下是四个步骤:
-
定义您的端点。
-
配置模型设置。
-
配置模型监控。
-
配置模型目标。
注意
什么是端点和部署?在机器学习中,端点 是为在线预测公开模型的服务。部署 是使模型作为端点可用的过程。端点是提供接口供客户端发送请求(输入数据)并接收训练后模型的推理(评分)输出的 HTTPS 路径。端点通常用于实时预测。例如,您可以使用端点预测客户点击广告的可能性或贷款违约风险。
部署通常用于将模型提供给更广泛的受众。例如,您可以将模型部署到生产环境中,以便客户或员工使用。
图 4-33 显示了部署和测试页面。要部署您的模型,请转到模型注册表,选择部署和测试,然后选择您的模型。

图 4-33. 将您的模型部署到端点页面。
在第 1 步中,您定义您的端点。您选择一个区域,并确定如何访问您的端点。
在第 2 步中,您添加模型并添加交通分流。在 Vertex AI 中,traffic split是一种将流量分配给部署到同一端点的多个模型的方法。这可以用于各种目的,例如:
A/B 测试
交通分流可用于 A/B 测试不同的模型,以查看哪个性能更好。
金丝雀部署
交通分流可用于先向少数用户部署新模型,然后再向更大的受众部署。这可以帮助在新模型影响太多用户之前捕获任何问题。
推出
交通分流可用于逐步向用户推出新模型。这可以帮助减轻新模型可能带来的任何问题的影响。
在第 3 步中,您选择如何将计算资源提供给您的模型的预测(如图 4-34 所示)。对于此练习,使用最少数量的计算节点(虚拟机服务器)。在“机器类型”下,选择标准。

图 4-34. 选择计算资源来训练模型。
注意:“机器类型”在几个方面有所不同:(1)每个节点的虚拟中央处理单元(vCPU)数量,(2)每个节点的内存量,以及(3)定价。
在选择预测模型的计算资源时需要考虑几个因素:
模型的大小和复杂性
模型越大越复杂,就需要更多的计算资源。(这更适用于定制编码的神经网络。)
将要进行的预测数量
如果您预计要进行大量预测,您需要选择一个能够处理负载的计算资源。
延迟要求
如果您需要实时或非常低延迟地进行预测,您需要选择一个可以提供必要性能的计算资源。注意:机器学习中的低延迟指的是 ML 模型在接收到新数据点后进行预测所需的时间。
成本
计算资源的价格可能有所不同,因此您需要选择一个符合预算的资源。
一旦您考虑了这些因素,您可以开始缩小选择范围。以下是可以用来提供预测模型的几个计算资源示例:
CPU
中央处理单元(CPU)是最常见的计算资源类型,适合那些不太大或复杂的模型。
GPU
图形处理单元(GPU)比 CPU 更强大,可用于加速大型复杂模型的训练和推理。
TPU
张量处理单元(TPUs)是专门为机器学习工作负载设计的硬件加速器。它们是最强大的选项,可以用于训练和服务最苛刻的模型。
作为“模型设置”下的第二步,有一个日志设置。如果启用端点日志记录,将会产生费用。因此,请在此练习中不要启用它。
接下来是可解释性选项,不会产生费用。选中“为此模型启用特征归因”。
步骤 3 是“模型监控”。请不要为这个项目启用它(如图 图 4-35 所示)。

图 4-35. “模型监控”配置窗口。
现在所有配置都已完成,部署按钮应该已突出显示。点击部署将您的模型部署到端点(如图 图 4-36 所示)。

图 4-36. 部署到端点。
在端点创建并将模型部署到端点后,您应该会收到有关端点部署状态的电子邮件。如果部署成功,您就可以开始进行预测。有四个步骤:
-
转到模型注册表。
-
选择您的模型。
-
选择模型的版本。
-
向下滚动直到看到“测试您的模型”页面。
图 4-37 显示了“测试您的模型”页面。请注意,此页面可能是一个应用程序或网页,看起来像这样——您和您的团队输入媒体渠道值并预测销售量。
点击预测按钮。

图 4-37. 在线预测的测试页面。
点击预测按钮后,您将得到一个关于您的标签(销售)的预测,如 图 4-38 所示。

图 4-38. 基于初始值的销售量预测。
回归模型返回一个预测值。图 4-38 显示了一个销售预测结果值为 14.63,这非常接近销售直方图中的均值(在图 4-10 和 4-23 中显示)。预测区间提供了一个值范围,模型有 95% 的置信度包含实际结果。因此,由于销售预测结果为 14.63,而预测区间是从 13.58 到 15.53 的范围,您可以有 95% 的把握认为任何预测结果都会落在这个范围内。
现在,让我们回答这些业务问题。
目标是构建一个机器学习模型,根据在各种媒体渠道上的支出来预测销售量。
模型能预测基于每个媒体渠道的支出会产生多少销售量吗?
是的。由于 Vertex AI 允许您为每个媒体渠道输入值,因此您现在可以根据预测结果做出未来预算分配的决策。例如,您公司的战略媒体计划现在可以根据预测结果增加数字渠道的预算。
广告支出与销售之间是否存在关系?
是的。数字、电视和广播的广告支出与销售之间存在正线性关系。报纸支出与销售之间关系较弱。
媒体渠道对销售贡献最大的是哪个?
电视对销售的贡献大于其他媒体渠道。如何?您在 EDA 部分构建的散点图以及模型训练后查看的 Vertex AI 特征归因条形图显示了电视对销售的贡献。
模型能够准确预测未来销售吗?
当将媒体渠道值输入“预测”窗口以预测销售量时,回归模型将返回一个预测值。预测结果显示了销售预测值和预测区间。预测区间可以用来对未来观察做出决策,因此您可以确信,未来任何销售预测结果都将在该范围内。
警告
完成本章后,请不要忘记取消部署模型。即使未使用,部署的模型也会产生成本,以便随时提供快速预测。要取消部署模型,请转到 Vertex AI Endpoints,点击端点名称,然后点击“更多操作”三个点的菜单,最后点击“从端点取消部署模型”。
总结
在本章中,您构建了一个 AutoML 模型来预测广告媒体渠道的销售情况。您使用 Pandas 探索了数据,创建了热力图、散点图和直方图。在导出数据文件后,您将其上传到 Google 的 Vertex AI 框架中。然后,您学习了如何使用 Google Cloud 的 AutoML 构建、训练和部署 ML 模型来预测销售。您通过性能指标全面了解了模型的性能,并回答了常见的业务问题。您使用模型进行了在线预测和一些预算预测。现在您已经准备好向您的团队展示了!
第五章:使用 AutoML 检测欺诈交易
在本章中,您将构建一个 Vertex AI AutoML 模型来预测金融交易是否存在欺诈行为。在创建 Vertex AI 上的托管数据集之前,您将在 Google Colab 笔记本环境中清理和探索数据集,就像您在第三章中所做的那样。一旦创建了托管数据集,您将使用 AutoML 创建一个分类模型来预测交易是否存在欺诈行为。在本章中,还将讨论一般的分类模型以及通常用于评估它们的相应指标。
本章的整体工作流程与您在第四章中解决预测广告媒体渠道销售问题时非常相似。因此,在本章的许多地方,您将看到更简明的细节,对话内容可能会非常相似。如果您在这些部分遇到困难,请参考第四章获取更多详细信息。
业务用例:金融交易欺诈检测
如前所述,本章中您的任务是构建一个模型,以预测金融交易是否存在欺诈或合法。您的新公司是一家为数十万用户提供移动支付服务的公司。欺诈交易相对较少,并且通常会被其他保护机制捕捉到。然而,不幸的是,一些此类交易却会溜过漏网,对您的用户造成负面影响。您的公司可以事后纠正这些问题,但存在客户因不得不报告这些交易而流失的风险。目标是通过利用机器学习(ML)构建定制模型来改进您公司正在使用的欺诈检测软件。
一个复杂的因素是相应的数据集将极度不平衡。绝大多数交易将是合法交易,因此简单地预测所有交易为合法交易将同样准确且无用。您需要利用其他指标来更好地理解模型的性能。
这项任务通常可能会被交给数据科学家来创建某种高级模型(例如自编码器),但您已被委托快速组建一个用于原型化欺诈检测系统其他部分的基准模型。这似乎是一个无望的任务,但请记住,在以前的项目中,您已能够快速使用 AutoML 创建这样一个用于预测媒体渠道销售的原型。因此,您应该对自己能够应对这一挑战感到自信!
项目工作流程
本章中的项目工作流程,如图 5-1,与上一章类似。因此,为避免重复,流程的某些细节将被省略,但如有需要,请随时参考前一章。

图 5-1. 欺诈检测项目的整体工作流程。
现在您了解了业务用例和目标,可以像以前的项目一样进行数据提取和分析。完成数据提取和分析后,您将上传数据集到 AutoML 平台。各种特征(即将介绍)将被输入模型中。您将评估 AutoML 的结果,然后部署模型进行预测。完成这些活动后,您将为工程团队准备好基准模型,以便开始开发更好的欺诈检测管道。也许这个模型实际上会投入生产。
项目数据集
该项目数据集包含模拟的交易数据,以复制用户行为和欺诈交易。这是使用PaySim完成的。PaySim 是一组研究人员开发的开源工具,当时他们正在研究大数据分析的可伸缩资源有效系统。¹
由于金融交易数据可能会暴露用户信息,所以您的公司决定使用这些模拟数据。贵公司的数据分析师已确认,分享的数据集在分布上与贵公司应用程序中看到的实际数据非常相似,因此您可以继续假设该数据代表了贵公司在预测时要利用的真实世界数据。
数据集已作为 CSV 文件存储在 Google Cloud Storage 中(可在https://oreil.ly/n1y1X下载)。在您的数据集版本中,共有 10 列。表 5-1 提供了这些列的列名、数据类型以及有关这些列可能值的一些信息。
表 5-1. 客户流失数据集的模式和字段值信息
| 列名 | 列类型 | 关于字段值的备注 |
|---|---|---|
step |
Integer | 自生成模拟数据以来的小时数 |
type |
String | 交易类型 |
amount |
Float | 交易金额 |
nameOrig |
String | 发起交易的客户的匿名化姓名 |
oldbalanceOrg |
Float | 发起方交易前的初始余额 |
newbalanceOrig |
Float | 发起方交易后的新余额 |
nameDest |
String | 目标账户所有者的匿名化姓名 |
oldbalanceDest |
Float | 目标账户交易前的初始余额 |
newbalanceDest |
浮点数 | 交易后目标账户的新余额 |
isFraud |
整数 | 如果交易是欺诈,则为 1,否则为 0 |
使用 Pandas、Matplotlib 和 Seaborn 探索数据集
此节中的所有代码,包括一些额外的示例,都包含在 GitHub 上的 low-code-ai 仓库 的 Jupyter 笔记本中。你将从头开始创建这个笔记本,但 GitHub 上的笔记本非常适合在独立工作或遇到困难时使用。
就像在 第四章 中那样,你将在 Jupyter Notebook 环境中加载 CSV 文件并使用 Pandas、Matplotlib 和 Seaborn 分析和探索数据,以更好地理解你的特征。你将利用这个过程中获得的信息来选择最佳的特征集用于你的 AutoML 模型。
在 Google Colab 笔记本中将数据加载到 Pandas DataFrame
首先,前往 https://colab.research.google.com,打开一个新的笔记本,按照第二章和第四章中讨论的过程进行操作创建 Google Colab 笔记本。你可以通过点击名称并替换当前名称为一个更有意义的名称,比如 Fraud_Detection_Model.ipynb,对此笔记本进行重命名。
接下来,在第一个代码块中输入以下代码以导入分析和可视化金融交易数据集所需的包,并执行该单元格:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
%matplotlib inline
现在导入了所需的包,下一步是将数据加载到 Pandas DataFrame 中:
url = ('https://storage.googleapis.com/' +
'low-code-ai-book/financial_transactions.csv')
transaction_df = pd.read_csv(url)
现在你的数据已经加载到 DataFrame 中了。在继续之前,查看几行数据总是一个好主意。要查看前几行数据,请将以下代码添加到新单元格中并执行该单元格:
transaction_df.head()
现在看看 DataFrame 中的前五行数据。这将帮助你了解不同列的样子。你注意到数据有什么特点?表格 5-2 和 5-3 显示了此行代码的输出。
表格 5-2. transaction_df.head() 语句输出的前六列
step |
type |
amount |
nameOrig |
oldbalanceOrg |
newbalanceOrig |
|---|---|---|---|---|---|
1 |
PAYMENT |
9839.64 |
C1231006815 |
170136.0 |
160296.36 |
1 |
PAYMENT |
1864.28 |
C1666544295 |
21249.0 |
19384.72 |
1 |
TRANSFER |
181.00 |
C1305486145 |
181.0 |
0.00 |
1 |
CASH_OUT |
181.00 |
C84003671 |
181.0 |
0.00 |
1 |
PAYMENT |
11668.14 |
C2048537720 |
41554.0 |
29855.86 |
表格 5-3. transaction_df.head() 语句输出的后五列
nameDest |
oldbalanceDest |
newbalanceDest |
isFraud |
isFlaggedFraud |
|---|---|---|---|---|
M1979787155 |
0.0 |
0.0 |
0 |
0 |
M204428225 |
0.0 |
0.0 |
0 |
0 |
C553264065 |
0.0 |
0.0 |
1 |
0 |
C38997010 |
21182.0 |
0.0 |
1 |
0 |
M1230701703 |
0.0 |
0.0 |
0 |
0 |
到目前为止,注意step列在您正在查看的所有行中的值为1。这可能有很多原因。例如,所有行可能具有相同的值,数据可能按step分组,或者仅仅是一个巧合,因为我们只看了五行。在这种情况下,实际上数据是按step按升序排序的,尽管从仅五行数据中完全不清楚。
您可能还注意到type列与oldbalanceDest和newbalanceDest列的模式。在这些行中,每当出现PAYMENT或TRANSFER交易类型时,oldbalanceDest和newbalanceDest列都为零。这可能是一个巧合,但您应该在稍后的数据集中探索这一点。
在 DataFrame 的最后一列中,您应该注意到一些奇怪的事情。有一列isFlaggedFraud,这在原始模式中是看不到的。这可能有很多原因,但这也是基本数据探索可以派上用场的另一个原因。如果在实践中遇到这种情况,您可能需要重新联系共享数据的人员或团队,以验证该列是否应该存在,以及它代表什么。
在这种情况下,您了解到这是一个列,它显示了上一个模型预测的输出,指示交易是否合法或欺诈。为什么这会成为您的模型不应使用的不良特征?因为在预测时可能没有isFlaggedFraud。理想情况下,这是来自一个您将废弃的模型,一旦部署了一个新的更好的模型。然而,这一发现仍然很有用。您可以使用以前的模型作为基准进行比较,确保您的新模型显示出改进。
探索数据集
正如您在第四章中所看到的,AutoML 将在训练模型之前做大量工作来描述您的特征并执行特征工程,但重要的是您仍然要花时间了解用于训练模型的数据。机器学习对数据质量非常敏感。如果您的数据存在问题,它们在使用 AutoML 过程中不会神奇地消失。
描述性分析
首先,在您的 DataFrame 上使用.info()方法,快速了解您正在处理的数据量和列的数据类型。将以下代码添加到一个单元格中并执行以执行此操作:
transaction_df.info()
您应该看到有 6,362,620 行交易数据和您预期的 11 列。数据类型都符合预期的类型(object 类型对应于 Pandas DataFrame 中的字符串数据类型)。
正如以往一样,在 Pandas 中快速查看数据集的描述统计信息的最简单方法是使用.describe()方法。在您的笔记本中创建一个新单元格,添加以下代码,并执行该单元格以查看描述统计信息(如图 5-2 所示):
transaction_df.describe()

图 5-2. transaction_df.describe()的渲染输出。
看起来每行的计数显示没有空值。step字段在 1 到 743 之间变化相对均匀。amount、oldbalanceOrg、newbalanceOrig、oldbalanceDest 和 newbalanceDest 在一个较大范围内变化,但这在金融交易中是可以预期的。
isFraud列展示了数据集的一个有趣特征。一个字段只有值 0 和 1 的均值对应于值等于 1 的比例。在这种情况下,大约有 0.129% 的交易确实是欺诈交易,或者大约每 800 笔交易中就有一笔是欺诈交易。这代表了一个相当不平衡的数据集,在尝试构建分类模型时可能会导致问题。您将在后续部分探索这些问题和可能的解决方案,但现在继续探索数据。
isFlaggedFraud列中被标记为欺诈交易的行数大约是实际欺诈交易数量的两倍。这是一件坏事吗?未必。您希望能够捕捉到欺诈交易,如果在进一步考虑后将一些额外标记的交易识别为合法交易,那么这就是为了捕捉欺诈交易而付出的小代价。然而,如果标记了太多额外的交易,那么您将浪费大量时间和资源来探索合法交易。这些都是您在近期评估模型时需要考虑的因素。
尽管如此,您的描述性分析中还缺少一些列。请注意,您仅对数值特征(整数和浮点数类型的列)进行了统计,但缺少了type、nameOrig和nameDest列的数据。如果数值和分类特征混合,describe()方法只会返回数值特征的统计信息。要查看分类特征的描述统计信息,请将范围限制为这些列。要执行此操作,请将以下代码添加到新单元格,并执行该单元格:
cols = ['type', 'nameOrig', 'nameDest']
transaction_df[cols].describe()
你正在定义一个列的列表,你希望查看描述性统计信息,并使用符号transaction_df[cols],仅考虑.describe()方法中的这些列(图 5-3)。

图 5-3. 分类列的describe()方法的代码和输出。
再次看到,通过将count与 DataFrame 中的行数进行比较,可以看到每行这些列都有一个值。预期地,type列有五个唯一值,但请注意nameOrig列几乎与交易的一对一关系。特别地,nameOrig列有 6,353,307 个值,而总交易数为 6,362,620。这不是一个好的迹象,用于构建良好模型的特征。为什么呢?因为如果有一个特征可以识别一行或几乎识别一行,那么你的模型可能只看这个值而不会学习更复杂的特征关系。对于nameDest列来说,这似乎不是那么大的问题,但考虑到唯一值的数量很大,仍然存在风险。
提示
AutoML 将为您执行特征工程和选择,但如果您可以提供一个更好的特征集开始,您仍然会看到更好的性能。例如,您可以重新创建nameOrig和nameDest特征作为布尔值列,其中如果该值重复超过一次,则该值为True,否则为False。这是正确的方法吗?也许是。要验证这一点,您应该与领域专家联系,并尝试不同的特征转换,看看哪种方法最有效。在处理接下来的章节时,您将更多地了解整体特征工程。
探索性分析
在可视化数据的某些特征之前,值得探索前一个模型的表现如何。一个简单的方法是创建一个新列,称为isCorrect,如果isFraud和isFlaggedFraud相同,则为 1,否则为 0。要计算这个新列并计算旧模型正确预测欺诈交易的次数,请在新单元格中执行以下代码:
transaction_df['isCorrect'] = (
transaction_df['isFraud'] == transaction_df['isFlaggedFraud']
)
transaction_df['isCorrect'].sum()
你应该看到6354423的结果,但这实际上意味着什么呢?在 Python 中,布尔数据类型有两个可能的值:True和False。当你使用sum函数时,它将True值视为 1,False值视为 0。因此,你看到的结果是从语句transaction_df['isFraud'] == transaction_df['isFlaggedFraud']中True值的数量。当这些列具有相同值时返回True,否则返回False。回想一下总共有6362620行数据,这意味着 99.87%的交易被正确标记。
所以,旧模型是一个真正好的模型,对吧?大多数交易都不是欺诈交易,而这些是您最担心的交易。但请考虑另一个相关的问题:它正确预测了多少笔欺诈交易?您可以通过询问isFraud列的值为1并且isCorrect列也为1来找出答案。在新单元格中执行以下代码以查看结果:
(transaction_df['isFraud']*transaction_df['isCorrect']).sum()
为什么这行代码告诉您成功标记的欺诈交易数量?如果交易是欺诈的,isFraud列的值只有 1,而且如果预测是正确的,则isCorrect列为True(在算术中视为1)。因此,将这些列的值相乘只有在两列的值都为 1 时才会得到 1,否则乘积将为 0。将 1 的数量相加即可得到正确标记的欺诈交易总数。
16 个正确标记的欺诈交易。通过使用代码transaction_df['isFraud'].sum(),您可以看到总共有 8,213 笔欺诈交易。这意味着只有 0.19%的欺诈交易被成功标记。当我们询问一个略有不同、可能更相关的问题时,这个模型表现非常糟糕。这是一个被称为召回率的指标的例子。在选择模型目标并在本章后面考虑评估指标时,您将探讨如何解释 AutoML 中可用的不同指标。
您可以通过创建条形图来可视化数据的另一种方式,以了解每个值的欺诈交易百分比。例如,对于type特征,有五个值。要创建这样的条形图,请在新单元格中使用以下代码来创建可视化效果:
transaction_df.groupby('type')['isFraud'].mean().plot.bar()
这行代码根据type的值对行进行分组,并取isFraud列的平均值。请记住,由于isFraud的值为 1 或 0,这相当于值为 1 的百分比。然后,使用plot.bar()方法将结果绘制成条形图,如图 5-4 所示。

图 5-4. 欺诈交易百分比的条形图与交易类型。
您应该立即注意到一些有趣的事情。您的数据集中只有两种交易类型有欺诈交易:CASH_OUT和TRANSFER。您可以使用以下代码确认这一点:
transaction_df.groupby('type')['isFraud'].value_counts()
输出每个值组合出现的次数。您应该看到的输出将类似于表 5-4。
表 5-4. value_counts()函数的输出
type |
isFraud |
|
|---|---|---|
CASH_IN |
0 |
1399284 |
CASH_OUT |
0 |
2233384 |
1 |
4116 |
|
DEBIT |
0 |
41432 |
PAYMENT |
0 |
2151495 |
TRANSFER |
0 |
528812 |
1 |
4097 |
在这种情况下,您可以确认欺诈交易仅出现在提到的交易类型中。这意味着——只要这个数据集真正代表了贵公司看到的交易——这可能是一个非常有用的特征。然而,如果新类型的欺诈交易开始出现,您的模型几乎肯定会错过它们,因为它从未见过这样的例子。
注
随着时间的推移监控欺诈交易的交易类型,并根据需要重新训练模型将是非常重要的。关于模型监控和持续训练的讨论超出了本书的范围,但这绝对是一个值得关注的概念。
另一种可视化具有分类标签数据的有用方法是将数值特征分桶,并查看每个桶中欺诈行为与合法行为的百分比。桶化,或称为离散化,是将数值变量分割成值范围或“桶”的过程,并将每个元素分配到一个桶中。您在第四章中创建直方图时也做过这样的操作。您可以用这种方法来探索交易金额以及原始账户和目标账户的新旧余额特征。
例如,假设您想可视化amount特征不同范围的百分比:
transaction_df['amountBkts'] = pd.qcut(transaction_df['amount'], 10)
transaction_df.groupby('amountBkts')['isFraud'].mean().plot.bar()
在查看输出之前,先花点时间解析代码的第一行。您正在为 DataFrame 创建一个名为amountBkts的新列,该列将包含桶信息。pd.qcut()函数基于分位数分配桶。您正在将amount列分成 10 个桶。第一个桶包含从 0 到 10 分位数的数据,第二个包含从 10 到 20 分位数的数据,依此类推(参见图 5-5)。

图 5-5. 交易金额列的欺诈交易百分比与十分位数范围。
您可以在这里看到一个普遍的趋势,这暗示了您可能直觉上考虑过的一些内容。交易金额越大,交易很可能是欺诈的可能性就越大。特别是,您会看到在金额超过 90 分位数的交易中,欺诈交易率增加了七倍。
要查看oldbalanceOrg列的同样可视化效果,请将以下代码添加到一个新的单元格并执行该单元格:
transaction_df['oldbalanceOrgBkts'] = pd.qcut(transaction_df['oldbalanceOrg'],
10, duplicates='drop')
transaction_df.groupby('oldbalanceOrgBkts')['isFraud'].mean().plot.bar()
pd.qcut()函数中有一个新的参数duplicates='drop'。该参数用于将重复的桶合并为一个单独的桶。在这种情况下,oldbalanceOrg列的第 30 百分位数为 0,因此前三个桶将相同。这些桶被合并到第 30 到 40 百分位的桶中。
如你在图 5-6 中所见,oldbalanceOrg和isFraud之间显然存在某种关系。总体趋势是,发起账户的旧余额越高,欺诈的可能性也越高,但仅限于某个点。这种欺诈可能性在数值处于第 90 到 100 百分位区间时会降低。

图 5-6. 旧发起余额列的欺诈交易百分比与十分位范围。
作为一个练习,以类似的方式探索其他数值特征与标签之间的关系。这些示例的代码可在 GitHub 笔记本中找到,并且对应的可视化可以在图 5-7、5-8 和 5-9 中找到。

图 5-7. 新目标余额列的欺诈交易百分比与十分位范围。

图 5-8. 新起源余额列的欺诈交易百分比与十分位范围。

图 5-9. 旧目标余额列的欺诈交易百分比与十分位范围。
探索交易的其他方法有很多。例如,你知道历史上只有两种类型的交易是欺诈性的。你可以为其中一种类型,比如CASH_OUT,创建一个散点图,其中你将newbalanceDest与oldbalanceDest进行绘制,然后根据它们是否欺诈性着色。使用以下代码创建此可视化:
cashout_df = transaction_df.query("type == 'CASH_OUT' & newbalanceDest < 1e8")
cashout_df.plot.scatter(x='oldbalanceDest',
y='newbalanceDest',
c='isFraud',
colormap='YlOrRd',
alpha=0.1)
第一行应用了两个过滤器。第一个过滤器提取了 type 为 CASH_OUT 且 newbalanceDest 小于 1e8 或 100,000,000 的所有交易。第二行是关于构建散点图的。我们设置了 x 和 y 轴列,用于着色点的列 (isFraud),以及一个颜色映射 YlOrRd,使点更易于分辨。这种特定的颜色映射将 isFraud=1 分配为较深的颜色(红色),将 isFraud=0 分配为较浅的颜色(黄色)。有关其他颜色映射,请参阅Matplotlib 文档。最后,由于要绘制许多点,我们设置 alpha=0.1 以在点上添加少量透明度。这使得可视化更易于解析(见图 5-10)。

图 5-10. 以 CASH_OUT 交易类型为基础,绘制 newbalanceDest 相对于 oldbalanceDest 的图,根据 isFraud 的值进行着色。
看起来有一些区域的点比黄色更接近红色,或者存在更多的欺诈交易而非合法交易。这表明这两列之间存在某种交叉相关性。这种关系可以通过一些不同的方式捕捉,如特征交叉,AutoML 将在特征选择和工程过程中使用它。特征交叉是通过连接两个或更多预先存在的分类特征来创建合成特征的过程。在处理自定义代码模型时,您将在第 8 章手动执行此过程。
作为练习,继续使用本章和前一章介绍的工具直观地探索数据集。看看是否能发现一些意想不到的新见解。
导出数据集
在探索数据集之后,您可以将其导出为 Vertex AI AutoML 最终可用的形式。您将在这里看到如何执行此操作的提醒,但请注意,数据已经存储在 Google Cloud Storage 中的 CSV 文件和存储在 BigQuery 表中,因此在下一节中,您无需加载导出的数据集。如果您有兴趣了解如何将数据加载到 BigQuery 中,这将是您在项目中完成的步骤之一,详见第 6 章。
要将 Pandas DataFrame 中的数据导出到 CSV 文件,您可以使用以下代码:
transaction_df.to_csv('transactions.csv', encoding='utf-8', index=False)
每次在将 CSV 文件写入后,检查其内容都是一个好习惯,可以使用 !head 命令执行此操作:
!head transactions.csv
要下载文件,请按照第四章中的过程进行操作。此外,Google Colab 在 google.colab 包中还有一个功能可以为您下载文件。要使用此功能,请从 google.colab 包中导入该函数,并将文件下载到您的本地机器:
from google.colab import files
files.download('transactions.csv')
警告
虽然通过编程方式下载文件是 Google Colab 的一项非常方便的功能,但这也是为什么非常重要要仔细查看您运行的任何笔记本,并确保您理解在笔记本中运行的代码。您不希望将可能具有恶意性质的意外文件下载到您的计算机上,避免这种情况的最简单方法是仔细阅读执行的代码。
分类模型和度量
在开始训练模型之前,让我们先了解一下分类模型是什么,以及如何正确评估它们。更多数学细节将在第七章中找到,但是只要对分类模型有基本的理解,您就可以理解结果并检测模型的问题行为。
分类模型 是一种返回分类输出的模型,比如 猫、狗、鱼 或 欺诈 。在分类模型中,您事先知道要区分的类别。这些类别也可以是数字;例如,评级或使用数值标签替代分类标签的问题。在本章中,您的问题是 0 和 1,分别代表 合法 和 欺诈 交易。
在实践中,分类模型并不直接返回预测的类别,而是返回所有可能类别的概率。通常情况下,预测的类别只是最可能的类别。在二元分类的情况下,可能不是这样。例如,假设只有当模型输出显示交易是 欺诈 的概率至少为 50%时,才将其分类为 欺诈 。这样可能会漏掉一些欺诈案例。另一方面,如果将 阈值 设置得太低,例如 5%,则可能会将太多的交易标记为欺诈,从而浪费大量资源进一步探索这些交易。正如您所想象的,确定阈值的确切位置既是建模问题也是业务问题。
为了确定阈值的设置位置,您可以利用不同的评估指标根据业务需求做出决策。准确率是一种简单易懂的分类模型常用指标。然而,正如您已经看到的,单独使用准确率作为主要评估指标存在危险。毕竟,原始模型的准确率很高,超过 99%,但几乎没有捕捉到任何欺诈交易。准确率仍然是一个重要的指标,但它并不能总是提供完整的画面。
当存在类别不平衡时,即一个类别比其他类别常见得多时,往往会出现这类问题。在这里,只有 0.129%的交易是欺诈的。欺诈与合法的比例接近 1:800。这种情况意味着某些问题很容易被像准确率这样的指标掩盖。在诸如欺诈检测之类的场景中,阳性 类别非常罕见,单靠准确率很快就变得不可靠。
您可以结合准确率、召回率和精确率来更清楚地了解模型的性能。召回率 可以视为真阳性率。在这个欺诈交易的例子中,召回率表示模型成功预测的欺诈交易的百分比。精确率 可以视为模型在预测阳性案例时正确的概率。在欺诈交易问题中,精确率表示被预测为欺诈交易的交易中确实是欺诈的百分比。如果您的目标是主动标记欺诈交易进行审核,那么召回率将是一个非常重要的指标。您可能愿意在提高召回率的同时牺牲一些准确率或精确率。当然,这仍然是您需要权衡的事情,因为审核大量交易会有资源成本。
通常,召回率和精确率是基于混淆矩阵计算的。混淆矩阵 将预测结果按预测类别和实际类别分解为表格。如 表 5-5 所示。
表 5-5. 一般问题的混淆矩阵
| 预测阳性 | 预测阴性 | |
|---|---|---|
| 实际阳性 | 真阳性 (TP) | 假阴性 (FN) |
| 实际阴性 | 假阳性 (FP) | 真阴性 (TN) |
召回率被定义为 ,即被预测为正的实际正例的百分比。精确度被定义为 ,即被预测为正的实际正例的百分比。正如你所预期的那样,在实践中,精确度和召回率之间存在一种平衡。对于正类别的阈值较低(例如欺诈交易),会导致更多的假阳性和更少的假阴性,因此精确度较低而召回率较高。另一方面,对于正类别的阈值较高会导致更多的假阴性和更少的假阳性,因此精确度较高而召回率较低。
提示
哪个更重要:精确度还是召回率?很多时候,这个问题的答案取决于业务决策。你的优先事项是什么?例如,如果你的公司希望尽可能捕获欺诈交易,但也愿意检查一些合法交易,那么你可能会倾向于将召回率作为你的评估指标。
如何确保你在精确度和召回率之间找到了正确的平衡?一个度量标准被称为F1 分数,它是精确度和召回率的调和平均数。一般来说,调和平均数通常被认为是比率的“最佳平均数”。对于两个数字,可以很容易地写下来。假设 是精确度, 是召回率。那么 F1 分数可以用以下公式计算:
F1 分数倾向于更偏向于精确度和召回率中较小的那个。因此,这可以是一个很好的度量标准,以确保你不会为了优化一个而牺牲另一个。
精确度和召回率在很大程度上取决于设定的阈值,但也取决于底层模型。在你的度量标准(包括准确度)如此依赖于阈值时,如何决定底层模型?有些度量标准仅依赖于底层模型,例如接收器操作特性曲线下面积(ROC AUC)和精确率-召回率曲线下面积(PR AUC)。这两个度量标准都在 0 到 1 之间变化。你的度量标准越接近 1,模型就越好。对这些度量标准的深入讨论超出了本章的范围,但直观理解对实践很有帮助。
ROC 曲线绘制了召回率(或真正例率)与假正例率之间的关系,假正例率定义为被错误分类为正例的负例百分比。该曲线上的不同点对应于不同的阈值,因此整体曲线涵盖了所有可能的阈值。曲线下面积,即 ROC AUC,可以看作是模型(独立于阈值)在给出一个正例确实是正例的更高概率时的机会,与一个负例相比。这对于分类问题是一个非常有用的度量标准,但在数据集不平衡或误判一个负例的成本比误判一个正例的成本更高的情况下,通常不那么实用。在这里,可以合理地认为您所处的情况正是如此,即错过一个欺诈交易的成本可能比查看一个合法交易更高。
另一个度量标准是 PR 曲线。其概念与 ROC 曲线相同,但这里是将精确率与召回率相对比。在不平衡数据集的情况下,PR 曲线往往是一个更强的度量标准。为什么呢?好吧,精确率和召回率都集中在正类上,如果将正类设置为较稀少的类别,那么你的度量标准就是基于成功预测该类别而不是整个数据集。
在接下来的部分中,当评估您在 AutoML 中训练的模型时,您将看到 ROC 曲线和 PR 曲线的示例。
使用 AutoML 训练分类模型
现在您已经探索了数据,可以使用 AutoML 训练模型了。类似于第四章,您将在 Vertex AI 上创建一个受管数据集,然后探索数据集统计信息。然后您将训练模型并检查模型性能。由于分类度量标准可能对您来说较新,您将花一些时间研究这些内容,然后再深入了解特征重要性并使用模型进行预测。
这些步骤中的许多细节在第四章中详细介绍了回归问题。因此,本章节中的某些主题将以较简略的方式处理,并建议您回顾第四章以获取更多详细信息。
创建受管数据集并选择模型目标
前往console.cloud.google.com,然后进入 Vertex AI(可以使用侧边菜单或搜索栏)。从那里,选择数据集,然后在数据集页面点击创建按钮。用fraud_detection替换自动生成的名称。类似于第四章的项目,首先需要选择数据类型和目标。由于您处理的是表格数据并希望解决分类问题,您应该选择 Tabular,然后选择“回归/分类”,如图 5-11 所示。

图 5-11. 您的新fraud_detection数据集所需的选项。
点击创建按钮,您将进入下一页,在此页面中,您可以指定源数据。这里您有两个选择:Google Cloud Storage 中的 CSV 文件,这是您在笔记本探索中使用的文件,或者事先为您准备好的 BigQuery 表中的表格。存储在 BigQuery 中的数据将附带额外的架构信息,例如每个列的数据类型,这是 CSV 文件中不包含的,因此您将使用这种方法。
在“选择数据源”下,点击“选择 BigQuery 中的表或视图”的单选按钮,然后输入以下 BigQuery 路径,点击继续按钮:
ma-low-code-ai.low_code_ai.financial_transactions
数据加载和预处理大约需要 15-20 分钟。AutoML 会执行大量额外的预处理工作,以分析和准备数据以进行即将进行的训练作业。之后,您将可以在 Vertex AI 数据集 UI 中探索数据。
探索数据集统计信息
一旦数据加载并准备就绪,您可以开始分析数据集。点击生成统计信息按钮。生成统计信息可能需要几分钟时间。生成完成后,您可以点击各个列并查看统计信息。根据之前的分析,不应该有任何意外,但在继续之前值得再看一眼。例如,如果点击type列,您将看到图 5-12 中显示的统计信息。您将看到没有缺失值;有五个不同的值,这些值的出现次数的详细分解。新的部分应该是漂亮的可视化图表,可以快速地视觉解析每个值在总数中的占比。

图 5-12. 由 Vertex AI 生成的type列的列统计。
在进入下一节之前,请逐列检查确保所有内容与您之前分析的预期一致。特别关注数据类型和统计信息,并确保没有任何意外情况。这次您不会发现任何意外,但养成这种习惯是很好的,确保在使用 AutoML 进行任何训练之前进行充分的检查是最佳实践。
训练模型
要训练您的模型,请点击“训练新模型”按钮,并选择“其他”选项(如图 5-13 所示)。确保选择分类作为目标,并选择 AutoML 作为模型训练方法,然后点击“继续”按钮。

图 5-13. 在 Vertex AI AutoML 中训练新模型的选项。
接下来,在“模型详细信息”页面上,选择“训练新模型”,将模型名称设为fraud_detection。同时,选择isFraud作为目标列。您的输入应该与图 5-14 中显示的相同。点击“继续”按钮进入下一页。

图 5-14. 模型名称和目标列选定后的模型详细信息页面。
在“训练选项”页面上,选择要用于训练的特征,并确保这些特征被正确地视为分类或数值。在屏幕右侧,点击isFlaggedFraud、nameDest和nameOrig特征旁边的减号。这将从训练过程中删除这些特征。确保step特征也被视为分类特征。这一点非常重要,因为它代表一个时间段而不是数值。特别是,step为 10 不应被认为比step为 5 对模型具有两倍影响力。参见图 5-15 查看在点击“继续”按钮之前您的页面应该是什么样子。

图 5-15. 选定选项后的训练选项页面。
在最终的“计算和定价”页面上,您将设置训练预算。请注意,定价是按每小时训练时间计算的。有关最新定价,请参阅Vertex AI 文档。考虑到这一点,将预算设定为一小时。确保选择了早停功能,因为它会在不再看到改进时终止模型训练过程。如果您设置了较高的预算但最终并不需要那么多时间,这将非常有用。完成后,点击“开始训练”。
模型训练完成后,您将收到一封电子邮件通知,并且该模型将注册到模型注册表中。
评估模型性能
一旦你的模型完成训练过程,你就可以查看评估指标了。通过侧边菜单或搜索栏导航到 Vertex AI 模型注册表。点击你的模型 fraud_detection,然后点击你想查看的相应版本。如果你在跟随本章,那将是版本 1。
由于你最关心尝试预测欺诈交易,点击图中突出显示的 1 类(在图 5-16 中)来查看你的模型在由 AutoML 分离的测试数据集上的表现。请注意,由于模型训练过程中的随机性,你的模型评估指标可能与本章中显示的有所不同。

图 5-16. fraud_detection 模型的模型评估指标。
注意置信阈值的滑块。你可以移动它来查看它对精确度和召回率的影响。例如,假设你的公司愿意有多达 50% 的标记交易是合法的,以便尽可能多地捕捉欺诈,同时仍然有资源检查标记的交易。对于图 5-16 所代表的模型,对应于 50% 精确度的阈值为 0.06。也就是说,如果模型预测欺诈的概率超过 6%,你会希望将其标记为欺诈并进行检查。请注意,这种情况下的召回率为 95.2% — 换句话说,在这种情况下,预期有 20 笔欺诈交易中只会漏掉 1 笔。
最佳置信阈值将取决于你的业务需求和目标,因此在这里没有单一正确答案。
模型特征重要性
如果你在 UI 侧边栏点击“所有标签”,然后滚动到页面底部,你可以看到模型的特征重要性。从第四章回顾,特征重要性衡量了特征对模型预测的影响。请注意,与评估指标一样,你在模型中看到的确切重要性可能与本章显示的重要性有所不同。
这个模型的特征重要性显示在图 5-17 中。oldbalanceOrg 和 newbalanceOrig 特征是最重要的特征,而 amount 和 type 分别排在第三和第四位。回想一下,在笔记本环境中探索数据时,这些特征似乎在特征值与欺诈交易百分比之间有最清晰的模式。总的来说,检查这些特征是很重要的,以确保你的模型学到的内容没有什么异常之处。模型可能会注意到你可能没有注意到的模式,这并不罕见,但如果你的模型似乎学到了一些荒谬的东西,这应该是一个警告信号。例如,如果一个预测出租车费用的模型学到了乘车距离是最不重要的特征,在某些情况下,这可能是一个需要更仔细探索数据的原因。特征重要性对于利益相关者来说非常有用,以了解模型如何进行预测;对于数据科学家和机器学习工程师来说,它们在调试模型时理解意外或荒谬预测的方式也非常有帮助。

图 5-17. fraud_detection 模型的特征重要性。
从您的模型获取预测结果
现在,您已经看到了模型的评估指标,可以准备部署模型进行预测了。如果在部署模型的过程中遇到困难,请参考 第 4 章,因为本章的过程与该章相同。
完成以下步骤来部署您的模型,将所有未提及的选项保留为默认值:
-
在“定义您的端点”页面上,将端点命名为
fraud_endpoint。 -
在“模型设置”页面上,选择
n1-standard-2, 2vCPUs, 7.5GiB memory作为机器类型。使用较小的机器类型将降低部署模型的成本,并满足本示例中的需求。 -
在“模型设置”页面上,在可解释性选项下,选中“为该模型启用特征归因”复选框。
-
在“模型监控”页面上,禁用模型监控,因为在这里你不需要它。
完成这些步骤后,点击部署。模型将需要几分钟来部署。一旦模型完成部署,您可以在页面底部测试模型。
使用以下值来测试您的模型,注意,再次强调,您的确切结果将与 图 5-18 中显示的模型有所不同:
step |
14 |
|---|---|
type |
CASH_OUT |
amount |
1000000 |
oldbalanceOrg |
1000000 |
newbalanceOrig |
0 |
oldbalanceDest |
0 |
newbalanceDest |
0 |

图 5-18. 使用预测和本地特征重要性进行模型测试。
一旦完成,点击“预测”按钮。要查看您模型预测的欺诈交易的概率,将“选择的标签”改为 1。
对于图 5-18 中所示的模型,“置信度分数”是预测概率。在这种情况下,它给出了交易欺诈的可能性为 91%。基于之前关于评估指标和阈值的讨论,该交易将被标记为欺诈。您还可以看到局部特征重要性。最高的特征重要性值对应于oldbalanceOrg特征。在这种情况下,存在一个精确余额为 100 万美元的账户的CASH_OUT交易。根据您的模型,这笔交易有很高的欺诈可能性。
作为练习,探索不同特征值的组合,并查看预测的概率和相应的特征归因。
警告
完成本章后,不要忘记取消部署您的模型。已部署的模型即使未被使用也会产生费用,以便随时返回快速预测。取消部署模型时,请转到 Vertex AI 端点,点击端点名称为fraud_endpoint,点击“更多操作”三点菜单(如图 5-19 所示),最后点击“从端点中取消部署模型”。

图 5-19. “取消模型部署到端点”的选项位置。
总结
在过去的两章中,您已经使用 AutoML 训练了回归模型和分类模型!在这一章中,您在 Google Colab 环境中探索了您的数据,然后将数据上传到 Vertex AI 的受管理数据集,然后使用 AutoML 训练分类模型。原始模型的召回率明显低于新模型的召回率,这意味着这个新模型最终将更好地标记交易并保护您的客户账户。
然而,这只是您的机器学习之旅的开始。在许多情况下,您将希望能够更多地控制模型训练过程。实际上,这意味着向更低代码和自定义代码解决方案迈进。下一章将介绍如何使用 BigQuery 中的 SQL 探索数据,并使用 BigQuery ML 在 SQL 中训练 ML 模型。
¹更多详情请见“PaySim: A Financial Mobile Money Simulator for Fraud Detection” by E. A. Lopez-Rojas, A. Elmir, and S. Axelsson (in The 28th European Modeling and Simulation Symposium-EMSS, Larnaca, Cyprus, 2016)。
第六章:使用 BigQuery ML 训练线性回归模型
在本章中,您将学习如何从头开始构建线性回归模型和神经网络模型,以预测电厂的生产。您将使用 SQL 进行数据分析,使用 Jupyter Notebook 进行数据探索,并使用 BigQuery Machine Learning(BigQuery ML)来训练 ML 模型。在此过程中,您将学习准备 ML 所需的新技术,以及如何将这些知识应用于改善模型性能。
业务用例:电厂生产
在这个项目中,您的目标将是根据电厂附近的天气条件预测联合循环发电厂(CCPP)的净每小时电能输出。
CCPP 由燃气轮机、汽轮机和热回收蒸汽发生器组成。电力是由燃气轮机和汽轮机生成的,它们在一个循环中结合,并且从一个涡轮机传递到另一个涡轮机。虽然真空是从汽轮机收集的,但其他三个环境变量(温度、环境压力和相对湿度)影响燃气轮机的性能。
本节中的数据集包含从 CCPP 收集的数据点,时间跨度为六年(2006 年至 2011 年),当时发电厂设置为全负荷运行。尽管数据按小时聚合,但未提供记录天气条件和能源生产的确切时间。从实际的角度来看,这意味着您将无法将数据视为序列或时间序列数据,即您无法使用先前记录的信息来预测未来的记录。
该数据最初以 CSV 文件的形式提供,¹因此,在您能够探索它并最终用它创建 ML 模型之前,您需要花一些时间将数据加载到 BigQuery 中。正如您马上会看到的,我们的数据集中有五列,如表 6-1 所示,以及 9,590 行。
表 6-1。数据集中的五列:目标变量“能量生产”以粗体显示
| 列名 | 最小值 | 最大值 |
|---|---|---|
| 温度 | 1.81°C | 37.11°C |
| 环境压力 | 992.89 毫巴 | 1,033.30 毫巴 |
| 相对湿度 | 25.56% | 100.16% |
| 排气真空 | 25.36 厘米汞柱 | 81.56 厘米汞柱 |
| 能量生产 | 420.26 MW | 495.76 MW |
这些期望的数值范围已经被电厂工程师充分记录,并且通过技术报告与您分享过(比如通过技术报告)。在探索数据时,这将非常有帮助,以确保没有空值或魔数等问题,正如第四章中所讨论的那样。
使用 SQL 在 BigQuery 中清洗数据集
正如之前讨论的那样,在开始构建机器学习模型的处理过程之前,了解数据集的重要性至关重要。请记住,您训练的任何机器学习模型的质量将严重依赖于用于训练模型的数据集质量。如果数据集中充满错误数据或缺失值,那么机器学习模型将无法学习到正确的见解。
在本节中,您将使用 SQL 作为工具,并选择 BigQuery 作为平台。本章中的所有 SQL 代码也可以在 low-code-ai 代码库 中找到。BigQuery 是 Google Cloud 的无服务器数据仓库解决方案。在这里,“无服务器”意味着您可以快速将数据加载到 BigQuery,并在不需要配置任何服务器的情况下开始 SQL 数据分析。如果您对 SQL 不熟悉,那么 Google 在 Coursera 上提供的 “准备探索数据”课程 是一个很好的免费起点。Alan Beaulieu 的 学习 SQL(第三版,O’Reilly,2020)是希望深入使用 SQL 的人的良好资源。
如果您还没有使用 BigQuery,它有一个免费层,可以覆盖本章中线性回归的活动。每个月通过 SQL 查询处理的前 1 TB 数据和每月的 10 GB 存储是免费的。此外,用于创建某些类型的机器学习模型(例如线性回归)的前 10 GB 数据处理也是免费的。如果您有兴趣在存储在 BigQuery 中的数据上进行机器学习,则可以使用 BigQuery ML。BigQuery ML 使用 Vertex AI 中的资源来训练神经网络模型。如果您希望按照本节的指导在 BigQuery ML 中训练神经网络模型,则会针对免费试用或计费账户产生费用。
加载数据集到 BigQuery
CCPP 数据集尚未在 BigQuery 中可用。您需要做的第一件事是将数据加载到 BigQuery 中。为了您的方便,我们已将数据放入了公共 Google Cloud Storage 存储桶中(在 https://oreil.ly/zY85- 下载)。
要将数据加载到 BigQuery 中,首先打开 Google Cloud 控制台,并返回 BigQuery SQL 工作空间。在 UI 的左侧,选择您的项目名称,然后单击项目名称右侧的“查看操作”按钮(三个垂直点)。选择如 图 6-1 所示的“创建数据集”选项。

图 6-1. BigQuery 控制台中的“创建数据集”按钮。
确保在“项目 ID”下选择你的项目。对于“数据集 ID”,请在框中键入data_driven_ml。选择数据位置为美国。我们选择这个选项是因为要加载到 BigQuery 的数据存储在一个美国云存储桶中。现在点击“创建数据集”。按照 图 6-2 中显示的字段输入数据。

图 6-2. 在 BigQuery 控制台中创建新数据集。
数据集创建后,你可以使用“查看操作”按钮(如 图 6-3 所示)创建 BigQuery 表。选择数据集,点击“查看操作”,然后选择“创建表”。

图 6-3. “查看操作”和“创建表”按钮。
你需要指定数据加载的位置、文件格式以及要创建的表的名称。这些选择在 表 6-2 中有总结。
表 6-2. 创建表选项总结
| 字段 | 值 |
|---|---|
| 从哪里创建表 | Google Cloud Storage |
| 从 GCS 存储桶选择文件或使用 URI 模式 | low-code-ai-book/ccpp.csv |
| 文件格式 | CSV |
| 表 | ccpp_raw |
| 模式 | 自动检测 |
图 6-4 显示已填写所有必填值的“创建表”窗口。在你的情况下,CSV 文件具有标题,并且所有值都是浮点数,因此你可以让 BigQuery 根据此信息检测模式。

图 6-4. 填写了指定值的“创建表”窗口。
保留“表类型”、“表分区”和“聚类”的默认值。点击“创建表”按钮开始加载作业并创建原始数据的表。
注意
表分区是一种将较大的表拆分为“较小的表”或分区的方法,可以通过筛选器分别访问。未在查询中引用的任何分区都不会被读取,从而降低查询成本并提高性能。同样,BigQuery 中的聚集表是使用聚集列定义的用户定义列排序顺序的表。聚集表通过将数据按排序顺序存储在同一物理位置中来提高查询性能和降低查询成本。
表创建后,你可以通过选择表并选择“模式”选项卡来查看表的模式。你也可以通过选择“预览”选项卡预览表中的数据。 图 6-5 和 图 6-6 显示了这些步骤在 BigQuery 控制台中的展示效果。

图 6-5. 新创建的 ccpp_raw 表的模式。

图 6-6. ccpp_raw表的预览。
使用 SQL 在 BigQuery 中探索数据
现在数据已加载到 BigQuery 中,是时候开始探索数据了。首先检查是否有空值的最简单方法是使用 BigQuery 中的IF函数。IF语句,IF(expr, true_result, else_result),接受三个参数。expr语句返回一个布尔值,确定是返回true_result还是else_result。正如您可能期望的那样,如果expr返回TRUE,则返回true_result,否则返回else_result。
使用空值函数检查空值
如果您想要查看Temp列是否有空值,您可以使用以下语句:IF(Temp IS NULL, 1, 0)。如果Temp为NULL,则返回 1,如果Temp不为NULL,则返回 0。运行以下查询,将其中的your-project-id替换为您的 Google Cloud 项目 ID,并查看结果:
SELECT
IF(Temp IS NULL, 1, 0) AS is_temp_null
FROM
`your-project-id.data_driven_ml.ccpp_raw`
如果您浏览结果,您会在超过 9,000 个值的列中找到两个 1。这种方法可行,但效率不高,对吧(图 6-7)?相反,让我们利用true_result和else_result选择分别为 1 和 0 的事实。

图 6-7. 在超过 9,000 个值的列中查找两个 1 的结果效率低下。
使用SUM()函数而不是浏览列表,可以轻松计算空值的数量。运行以下查询,在其中将your-project-id替换为您的 Google Cloud 项目 ID,以计算每列的空值数量:
SELECT
SUM(IF(Temp IS NULL, 1, 0)) AS no_temp_nulls,
SUM(IF(Exhaust_Vacuum IS NULL, 1, 0)) AS no_ev_nulls,
SUM(IF(Ambient_Pressure IS NULL, 1, 0)) AS no_ap_nulls,
SUM(IF(Relative_Humidity IS NULL, 1, 0)) AS no_rh_nulls,
SUM(IF(Energy_Production IS NULL, 1, 0)) AS no_ep_nulls
FROM
`your-project-id.data_driven_ml.ccpp_raw`
运行此查询后,您应该会看到除了Ambient_Pressure列外,所有列都有空值。将您的结果与图 6-8 中的结果进行比较。

图 6-8. 查询计算空值的结果。除了Ambient_Pressure列外,所有列都有空值。
那些包含空值的行应该如何处理?最简单的方法是简单地省略这些行。另一个选项,在第七章中探讨,是遵循插补策略。插补是将缺失数据替换为替代值的过程,通常以这样的方式进行,使得替代值在特定的上下文中是现实的。在这种情况下,您可能不是 CCPP 的专家。最坏的情况下,包含空值的行将占数据的大约 0.1%。因此,简单地省略这些行是一个非常合理的策略。
在什么情况下您希望填补而不是丢弃数据?如果您的数据集很小,或者缺失值的行在数据集中占据了重要的百分比,那么丢弃这些行可能会严重影响您的模型性能。另一个删除数据的更微妙问题涉及到偏差。统计偏差是指您的数据分布与实际数据分布之间的系统性差异。如果空值出现在特定示例的子集中(例如,市场数据集中的某些人口统计学信息),那么删除具有缺失值的行将导致模型无法学习重要信息。
使用 Min 和 Max 函数确定可接受的数据范围
接下来,请确保所有值都在预期范围内。最快速地完成此操作的方法是使用MIN和MAX函数。与SUM函数一样,MIN和MAX是聚合函数的示例。聚合函数是指接受列或列的子集,并返回单个值的函数。MIN和MAX函数分别返回其应用于的列的最小值和最大值。请继续通过以下 SQL 查询将这些函数应用于Temp列,再次用您的 Google Cloud 项目 ID 替换your-project-id:
SELECT
MIN(Temp) as min_temp,
MAX(Temp) as max_temp
FROM
`your-project-id.data_driven_ml.ccpp_raw`
你应该看到最低温度为 1.81°C,最高温度为 37.11°C(参见图 6-9)。好消息是,这一数值范围与先前指定的温度数值范围相符。继续使用相同的逻辑检查其他列的范围。如果这次你遇到困难,可以尝试自己编写查询,但是查询已包含在图像下方。

图 6-9. 计算数据集中温度的最小值和最大值的上一个查询结果。
SELECT
MIN(Temp) as min_temp,
MAX(Temp) as max_temp,
MIN(Exhaust_Vacuum) as min_ev,
MAX(Exhaust_Vacuum) as max_ev,
MIN(Ambient_Pressure) as min_ap,
MAX(Ambient_Pressure) as max_ap,
MIN(Relative_Humidity) as min_rh,
MAX(Relative_Humidity) as max_rh,
MIN(Energy_Production) as min_ep,
MAX(Energy_Production) as max_ep
FROM
`your-project-id.data_driven_ml.ccpp_raw`
注意
您可以在 BigQuery 中以 JavaScript 对象表示法或 JSON 格式显示结果。JSON 是一种与编程语言无关的数据格式,用于许多不同的 Web 应用程序和产品,包括 BigQuery,用于交换信息。JSON 格式的优点是它易于阅读,并以文本格式存储,因此易于处理。例如在图 6-10 中展示了一个示例。

图 6-10. 在 JSON 格式中显示的ccpp_raw表中所有列的最小和最大值。请注意异常值。
如果仔细查看,您会发现几个可疑的值。最小环境压力是 0.0,最小能量产生是-1.0。基于传达的数值范围,以及可能的常识,我们知道这两个值都是不合理的。很可能,-1.0 值是一个魔数的例子。魔数是用来表示与标准含义不同的独特值。由于-1.0 不作为能量产生值而存在,这很可能是一个魔数,用于表示缺失的数据。同样,最小环境压力为 0.0 很可能是一个默认值的例子。默认值通常在应用程序中存在,用于在未报告值时记录一个值。这用于避免一些与NULL值相关的问题。
了解技术报告中预期值的范围后,确保避免这些不切实际的值的最简单方法是基于预期范围进行过滤。请注意,这也将消除您之前检测到的NULL值,因为这些值也不在这些范围内。
使用 BigQuery 中的 DDL 语句保存查询结果
在编写用于过滤NULL和其他不需要的值的查询之前,重要的是要考虑如何存储该查询结果以供训练 ML 模型使用。这可以通过在 BigQuery 中执行 DDL 语句来完成。DDL 代表数据定义语言,是用于在数据集中创建和修改对象(如表)的语法。您将使用CREATE TABLE语句来创建一个新表。在 BigQuery 中,CREATE TABLE语句的基本语法如下:
CREATE TABLE
table_name
AS
query_statement
此查询将创建一个名为table_name的新表,并将query_statement的结果保存为此表。请注意,使用此语句时,如果表已经存在,将不会被覆盖。如果您希望这样做,可以将CREATE TABLE替换为CREATE OR REPLACE TABLE。
现在,您已经知道如何使用CREATE TABLE语句保存查询结果,可以编写查询来清理原始发电厂数据,并将数据保存到一个新表中,比如ccpp_cleaned,以便训练 ML 模型。
查询很简单,但如果用不等式来写,可能会相当冗长。然而,操作符BETWEEN可以简化查询。要使用BETWEEN,您需要按以下方式指定最小和最大值:
Field_name BETWEEN min_value AND max_value
如果您正在检查的值在min_value和max_value之间的范围内,则语句将返回TRUE;否则,语句将返回FALSE。例如,在这里,您正在查找Energy_Production值在 420.26 到 495.76 之间的范围。前面讨论的-1.0 值不在此范围内,因此将被过滤掉。特别是,我们希望仅保留与技术报告中共享的范围匹配的值。
如前所述,尝试自己编写查询并在 BigQuery 中运行它,但如果需要帮助,这里是:
CREATE TABLE
`data_driven_ml.ccpp_cleaned`
AS
SELECT
*
FROM
`your-project-id.data_driven_ml.ccpp_raw`
WHERE
Temp BETWEEN 1.81 AND 37.11 AND
Ambient_Pressure BETWEEN 992.89 AND 1033.30 AND
Relative_Humidity BETWEEN 25.56 AND 100.16 AND
Exhaust_Vacuum BETWEEN 25.36 AND 81.56 AND
Energy_Production BETWEEN 420.26 AND 495.76
执行查询后,可以通过在 BigQuery UI 左侧面板中选择项目名称,然后点击数据集名称 (data_driven_ml),最后选择表 ccpp_cleaned 来查看我们创建的新表的元数据。在打开与表对应的选项卡后,点击“详细信息”选项卡查看表的元数据(见 图 6-11)。

图 6-11. 新创建的 ccpp_cleaned 表的元数据。比较此表中的行数与 ccpp_raw 表中的行数,以查看删除了多少行。
新创建的 ccpp_cleaned 表有 9,576 行。如果按照相同过程处理原始 ccpp_raw 表,可以看到它有 9,590 行。这意味着我们从数据集中过滤掉了 14 行,约占所有数据的 0.15%。由于清理导致的数据损失很少!然而,一些不正确的值,特别是如果它们导致极端离群值,可能会严重影响模型性能。因此,经过这个过程是件好事。
注:
在上述示例中,你事先知道想要保存查询结果,并使用 DDL 语句立即创建表。如果在运行查询后才决定保存结果怎么办?难道需要重新运行查询只是为了将结果保存到表中吗?
幸运的是,答案是否定的。在执行查询后,你可以转到 Web 控制台上的“保存结果”,选择 BigQuery 表,然后填写数据集和表名称,以创建这些结果所需的表。
当你事后意识到应该保存结果时,你会陷入困境吗?在 BigQuery 中执行查询时,结果会存储在临时表中。此表将在查询完成后保留 24 小时。要访问临时表,转到控制台底部的个人历史选项卡,点击与要检索结果的查询对应的作业,然后点击“临时表”。此表可以像其他表一样查询,并且可以保存结果如前所述。
线性回归模型
现在数据已经清理好了,你准备好开始训练模型了吗?还不完全。在早期章节中,你依赖像 AutoML 这样的工具在幕后处理了许多特征选择过程。现在在 BigQuery ML 中由你来选择更深入的特征选择和工程化过程,但现在你将专注于为这个问题选择模型类型以及选择特征的标准。
在进一步进行之前,请退后一步,更深入地思考手头的问题。目标是根据温度、环境压力、相对湿度和排气真空压力预测 CCPP 的能源产量,如图 6-12 所示。

图 6-12. 目标是基于温度、环境压力、相对湿度和排气真空压力预测能源产量。
这是一个回归问题的示例,因为目标是预测一个实数:以兆瓦(MW)为单位的发电厂能源产量。尽管 BigQuery ML 支持许多不同的模型类型,通常最好的起点是最简单的一个,即线性回归模型,您寻求找到最佳拟合线。
在第四章中,您看到了线性回归的简化示例。在本章中,讨论将更深入,以便更好地理解模型的工作原理(见图 6-13)。这样,当您在本章后面的训练 ML 模型时,您将能够更明智地选择要使用的特征。

图 6-13. 线性回归的简单示例。点对应于具有 x 值作为特征和 y 值作为标签的示例。虚线表示最佳拟合线。
假设您有一些数值特征,并且您想基于特征值预测一些实数。通常,被用作缩写符号来表示特征列表。线性回归模型是以下形式的函数:
-
其中也是实数,称为权重;通常称为模型的偏置。当然,你可以选择任意随机的权重并得到一个函数,但这样的模型有多好呢?
-
请回忆你在第四章中使用了均方根误差(RMSE)来评估你的回归模型,你可以在这里做同样的事情。在继续之前,请回忆 RMSE 的定义。假设你的数据集有个示例,即对于特征值,相应的标签是。上标表示我们正在查看你的数据集中的第个示例。
-
给定一个模型,模型的 RMSE 表达式是:
-
这里求和是在数据集中的所有示例上进行的。包含参数在此作为提醒,RMSE 取决于用于计算它的数据集,就像我们评估的模型一样。
-
如果你只是选择一些权重,那么你如何知道你拥有给出最佳模型的权重?换句话说,你希望使损失函数,RMSE尽可能小。
- 注
记住,损失函数的目标是衡量你的算法在数据集上表现如何。换句话说,损失函数是评估你的算法模型数据集的一种方法。如果你的预测完全不准确,损失函数会输出一个较高的数字。如果预测相当不错,它会输出一个较低的数字。
查看第四章以了解损失函数的可视化解释。
有两种常用方法来确定线性回归模型的适当权重。第一种被称为正规方程。使用数据集和相应的标签,解正规方程可以给出最佳模型权重的精确分析解。许多分析包和产品(包括 BigQuery)在其工具包中包含此方法。
如果总有一种好方法可以找到最佳权重,为什么不总是使用呢?好吧,原因有几个。首先是计算复杂度,即计算所需的工作量。从技术上讲,我们说解决正规方程的计算复杂度略低于。这究竟意味着什么?假设数据集从 1,000 个例子增加到 10,000 个例子,即增加十倍。解决正规方程所需的工作量将大约增加一个因子!可以看出,随着数据集变得越来越大,这个问题会迅速失控。
另一个更加数学上微妙的原因是计算可能涉及非常大的数值。由于计算机处理算术的方式(浮点运算),这些大数值可能在解决正常方程时造成问题。这种情况的技术术语是病态条件。
无论情况如何,如果你有大量示例或在解决正规方程时遇到问题,你可以采用称为梯度下降的第二种方法。我们这里不详细介绍,但需要知道大多数(如果不是所有)ML 框架都提供梯度下降及其变体,用于训练模型。要了解更多关于梯度下降基础知识,请参阅 Google 的“机器学习入门课程”中的相应章节。
注意
如果你有一些矩阵工作背景,那么一旦符号设置好,正规方程并不难描述。这里没有涵盖推导过程,但这是微积分和线性代数教材中的常见主题。有关使用微积分技术推导的例子,请参阅此博客文章。
特征选择和相关性
现在确定了使用的模型类型(线性回归),是时候选择要使用的特征了。请记住,在您准备的 BigQuery 数据集中有四列可能用于预测能源生产:Temp、Ambient_Pressure、Relative_Humidity和Exhaust_Vacuum。那么,如何决定使用哪些特征呢?
通常情况下,在选择特征时有三个基本准则可以遵循:
-
特征应与问题目标相关。
-
特征应在预测时已知。
-
特征应是数值的,或者可以转换为数值。
这些绝不是唯一需要考虑的因素,您将在本章和后续章节中看到更多考虑因素,但它们是一个很好的起点。第三个条件由于线性回归模型的性质以及实践中使用的大多数其他模型类型而显得重要。当一切都说完了,机器学习模型是一个数学函数,它以数值特征作为输入并输出一些实数,然后根据模型目标进行解释。因此,从计算的角度来看,具有数值特征或能够将所选特征转换为数值特征是至关重要的。
警告
数值值特征如果没有有意义的数量级,需要与具有有意义数量级的特征进行区别对待。考虑一个记录汽车颜色的特征作为数字的例子,比如 0 代表红色,1 代表蓝色,2 代表银色等。说银色是蓝色的两倍是没有意义的。在后面的章节中,您将看到一些技术,如独热编码,来正确地将这类特征编码为具有有意义数量级的数值特征。
您的特征与问题目标相关吗?这在一般情况下可能是一个棘手的问题。您必须具备一定的领域知识才能适当地回答这个问题,即使如此,答案在某些情况下也可能相当微妙。有几种不同的方法可以解决这个问题:
-
您可以利用自己或其他专家的领域专业知识。
-
您可以使用统计方法如相关性来理解特征与目标之间的关系。
至于第一种方法,为了简单起见,假设在报告中,领域专家确认这些特征确实与目标相关。这可能不是令人满意的答案,但作为机器学习从业者,你必须依靠真正了解领域的人来指导你找出可能的特征。然而,这并不应阻止你尝试研究你的模型所解决的问题领域!随着你在一个领域中处理更多问题,你会更多地了解该领域,并且能够真正直观地理解该领域的关键概念。这种直觉是将人类洞察力带入你的模型的另一种方式。这不仅是一个有用的工具,而且通常是构建解决问题的强大模型所必需的组成部分。
现在来看第二种方法。是的,你知道所有特征都应与模型目标相关,但如何理解这种关系呢?在构建模型时,理解这个“如何”非常重要,因为它能帮助你如何利用和转换你的特征。这个过程通常被称为特征工程,它往往是提升模型性能的最强大工具之一,超越了数据的质量和数量。
对于线性模型,你可以使用的一个简单工具称为Pearson 相关系数。给定两个(数值)变量X和Y,Pearson 相关系数Corr(X, Y)是一个介于–1 和 1 之间的数字,用于衡量两个变量之间线性关系的紧密程度。特别地,如果系数恰好为 1,则X和Y具有完全线性关系。也就是说,Y = m × X + b,其中m和b是正数。如果系数恰好为–1,则m为负数。那么系数在–1 到 1 之间的情况又如何呢?系数的绝对值越接近于 0,变量之间的线性关系就越远。有多种方法可用于确定特征之间的相关性。一些示例散点图及其对应的相关系数如图 6-14 所示。

图 6-14. 示例散点图及其对应的 Pearson 相关系数;图片来源于Wikipedia(CC0 许可)。
第一种方法是计算每对变量之间的相关系数。例如,要在 BigQuery 中计算Temp列和Exhaust_Vacuum列之间的相关系数,可以使用如下查询中的CORR函数:
SELECT
CORR(Temp, Exhaust_Vacuum)
FROM
`your-project-id.data_driven_ml.ccpp_cleaned`
你会发现Temp和Exhaust_Vacuum之间的相关系数约为 0.844。你应该如何解释这个结果?这意味着这两个特征之间存在中等到强(正)的线性关系。从物理角度来看,这是有道理的,因为在温度增加时,压力也会增加(假设其他变量保持不变)。
你可以更高效一些,同时使一列与多列相关,但编写查询仍然需要时间。例如:
SELECT
CORR(Temp, Ambient_Pressure) AS corr_t_ap,
CORR(Temp, Relative_Humidity) AS corr_t_rh,
CORR(Temp, Exhaust_Vacuum) AS corr_t_ev
FROM
`your-project-id.data_driven_ml.ccpp_cleaned`
对于列数较少的情况,这不是一个不合理的方法。在这里,我们有 5 列,包括我们的标签,因此总共需要计算 10 个相关系数。然而,随着列数的增加,这个数字增长得非常快。当然,你可以进一步创建更高级的自动化查询,计算所有相关系数,但这超出了本书的范围。
Google Colaboratory
另一种方法是利用 Google Colaboratory,或简称 Google Colab,创建、绘制和可视化相关矩阵。在后续章节中,当介绍 Python 中的 ML 包时,你将使用 Colaboratory 作为运行 Python 代码的简便方法,而无需事先设置环境。现在,你将看到如何将 BigQuery 的查询结果带到 Google Colab 中,使用一些基本的 Python 进行探索性数据分析(EDA)。
从 BigQuery 加载你想要的数据的最简单方法是使用 Google Colab 中的内置连接器。BigQuery 具有创建模板笔记本的功能,以将查询结果加载到笔记本环境中。现在,你将逐步了解设置这种环境的步骤。
首先,你需要运行查询,以便将其结果加载到笔记本环境中。在这种情况下,编写并运行一个将返回整个清理数据集的查询。与之前一样,这是查询的内容,如果需要一点帮助:
SELECT
*
FROM
`your-project-id.data_driven_ml.ccpp_cleaned`
接下来,在刚刚运行的查询的同一个窗口中,选择在控制台中探索数据(在查询编辑器下方),然后选择使用 Python 笔记本进行探索,如图 6-15 所示。

图 6-15. 用于探索查询结果的 Explore with Python 笔记本选项。
当你选择使用 Python 笔记本进行探索时,在 Google Colab 中创建一个模板笔记本,可以让你通过样板 Python 进行可视化探索或创建描述性统计。要创建可视化图形,你只需将两个import语句添加到第一个单元格(设置):
import matplotlib.pyplot as plt
import seaborn as sns
在这两行之后,第一个单元格应如下所示:
# @title Setup
from google.colab import auth
from google.cloud import bigquery
from google.colab import data_table
import matplotlib.pyplot as plt
import seaborn as sns
project = 'your-project-id'
location = 'US'
client = bigquery.Client(project=project, location=location)
data_table.enable_dataframe_formatter()
auth.authenticate_user()
现在您已准备好运行笔记本中当前存在的所有单元格。要执行此操作,请点击单元格,然后点击单元格左侧的“运行单元格”按钮。您还可以按 Ctrl+Enter(或者如果您使用的是 macOS X,则按 Cmd+Enter)来执行单元格。逐个浏览单元格并按顺序运行单元格非常重要,不要跳过单元格以确保一切正常运行。
最后一个单元格预先编码以显示描述性统计数据,例如results.describe()。请注意,results是图 6-16 中显示的数据框的名称。

图 6-16. 一个生成笔记本单元格的示例,用于检索结果和前五行结果。出于可读性考虑,仅显示了前三列。
现在您可以轻松创建相关矩阵——即每对特征的不同相关系数的数组。
要创建此矩阵,请点击笔记本上方的+ Code 按钮创建一个新单元格,并输入代码 results.corr().round(2)。round方法用于将相关性四舍五入到小数点后两位,以提高可读性。如前所述运行单元格,并将结果与图 6-17 中的结果进行比较。

图 6-17. 新代码单元格计算相关矩阵及相应结果。
相关矩阵易于阅读。要查找两个特征之间的相关性——比如Temp和Ambient_Pressure——请转到Temp列和Ambient_Pressure行(或者反之),以找到该值。在本例中,该值为–0.508(四舍五入到千分之一)。这意味着这两个特征之间存在适度的负相关性,即降低温度将增加环境压力,反之亦然。
您还可以使用热图来可视化这个矩阵。为此,请在一个新单元格中输入以下代码,并像以前一样运行该单元格。请注意,您前面在笔记本的第一个单元格中添加的两个import语句是运行这些代码行所必需的。结果显示在图 6-18 中:
plt.figure(figsize=(10,6))
sns.heatmap(results.corr());

图 6-18. 特征的相关性热图。较深的颜色对应较大的负相关性,较浅的颜色对应较大的正相关性。
在继续之前,请对您所拥有的数据进行以下知识检查:
-
哪些特征与
Energy_Production有强相关性?为什么? -
哪些特征与
Energy_Production之间的相关性较弱?为什么? -
哪些特征与
Energy_Production之间有中度相关性?为什么?
警告
共线性或多重共线性是指回归模型中两个或多个预测变量之间存在中度或高度相关的情况,例如,意味着预测变量彼此相关,这使得更难以确定每个相关变量所起的作用。这意味着,在数学上,标准误差会增加。当预测变量之间存在高度相关时,就会出现多重共线性,这会导致回归系数的估计不可靠和不稳定。多重共线性可能会限制可以得出的研究结论,特别是在使用线性回归等线性模型时。
绘制特征与标签的关系图
通过执行探索性数据分析(EDA)来可视化特征与标签之间的关系,也是理解哪些特征对模型最有用的一种方法。您可以继续在之前使用的同一个 Google Colab 笔记本中可视化您的数据。
首先,通过将以下代码添加到新单元格并运行该单元格来可视化Temp特征和标签Energy_Production之间的关系。将结果与图 6-19 中的可视化进行比较:
ax = sns.regplot(
x='Temp', y='Energy_Production',
fit_reg=False, ci=None, truncate=True, data=results)
ax.figure.set_size_inches(10,8)

图 6-19. 一个散点图,显示Temp和Energy_Production之间的关系。该关系看起来是一个负线性关系。
现在可视化Ambient_Pressure特征和标签Energy_Production之间的关系。首先尝试自己编写代码,但如果需要帮助,以下是解决方案。将结果与图 6-20 中的可视化进行比较:
ax = sns.regplot(
x='Ambient_Pressure', y='Energy_Production',
fit_reg=False, ci=None, truncate=True, data=results)
ax.figure.set_size_inches(10,8)

图 6-20. 一个散点图,显示Ambient_Pressure和Energy_Production之间的关系。该关系似乎是模糊正相关,但对于Temp特征来说不太明显。
最后,对Relative_Humidity和Exhaust_Vacuum特征重复此过程。与之前一样,解决方案代码随后可视化,您应将结果与图 6-21 和图 6-22 中的可视化进行比较:
ax = sns.regplot(
x='Relative_Humidity', y='Energy_Production',
fit_reg=False, ci=None, truncate=True, data=results)
ax.figure.set_size_inches(10,8)

图 6-21. 显示Relative_Humidity和Energy_Production之间关系的散点图。从这个可视化图中看,它们之间似乎呈现出微弱的正相关,但并不十分清晰。
ax = sns.regplot(
x='Vacuum_Pressure', y='Energy_Production',
fit_reg=False, ci=None, truncate=True, data=results)
ax.figure.set_size_inches(10,8)

图 6-22. 显示Vacuum_Pressure和Energy_Production之间关系的散点图。从这个可视化图中看,它们之间似乎呈现出负线性关系。
总结一下,看起来Temp和Energy_Production之间存在强烈的“反向”关系——温度越低,能量输出越高。如果你参考图 6-17 中的先前相关矩阵,Temp和Energy_Production的相关性为 –0.948. 这与图 6-19 中观察到的负线性关系相符。
请记住,你的目标是根据温度、环境压力、相对湿度和排气真空压力来预测能量产出。你是否应该舍弃那些相关性较弱的特征?这将使你从多变量模型转变为单变量模型。实质上,你将只有一个特征 (Temp) 来预测标签 (Energy_Production)。这样的模型是否具有泛化能力?是否有额外的数据可以收集,以确定特征对能量生产的重要性?这些是在面对这种情况时需要自问的问题。请利用第 1 章的业务决策模型来帮助你。
BigQuery ML 中的 CREATE MODEL 语句
在本节中,你将使用 BigQuery ML 创建一个线性回归模型,该模型使用电厂数据集中的所有特征。如你所见,现在你已经准备好数据,这个过程非常简单。
使用 CREATE MODEL 语句
返回 BigQuery 控制台。在 BigQuery Editor 窗口中输入以下 SQL 语句以创建线性回归模型:
CREATE OR REPLACE MODEL data_driven_ml.energy_production OPTIONS
(model_type='linear_reg',
input_label_cols=['Energy_Production']) AS
SELECT
Temp,
Ambient_Pressure,
Relative_Humidity,
Exhaust_Vacuum,
Energy_Production
FROM
`your-project-id.data_driven_ml.ccpp_cleaned`
在执行查询之前,请注意一些符号的表示法。CREATE OR REPLACE MODEL 语句将在 data_driven_ml 数据集中创建一个新的 ML 模型,名为 energy_production。BigQuery 中的 ML 模型是数据集中的对象,类似于表。CREATE OR REPLACE MODEL 语句指定了两个选项。第一个选项 model_type 指定模型类型(这里使用 linear_reg 进行线性回归)。第二个选项 input_label_cols 是你指定作为标签的列。在这个案例中,即 Energy_Production 列。
现在运行查询以训练模型。这只需要几分钟的时间。在进行下一步之前,请等待模型训练完成。
查看训练模型的评估指标
您可以在控制台中看到模型评估指标。创建模型表后,选择“评估”选项卡以查看评估指标。这些评估指标的示例在图 6-23 中展示。请注意,您看到的指标可能与图中稍有不同。

图 6-23. 预测能源产量的线性回归模型的评估指标。
ML.EVALUATE 函数还提供评估指标。运行以下 SQL 查询以返回模型的评估指标:
SELECT
*
FROM
ML.EVALUATE(MODEL data_driven_ml.energy_production)
以 JSON 格式显示的输出在图 6-24 中。

图 6-24. ML.EVALUATE 命令的输出。explained_variance 输出是控制台中未包含的唯一输出。
从控制台和 ML.EVALUATE 函数输出可以看出,除了一个额外的输出之外,它们是相同的。ML.EVALUATE 函数还通过 explained_variance 列提供了一种被称为“解释方差” 的度量。解释方差可以被看作是对这个问题的回答:“我们的模型在其输出中捕捉了标签中多少方差?” 我们这里不会深入讨论确切细节,但如果平均标签值和平均预测值相同,那么我们期望解释方差和 R² 得分相同。这被称为无偏估计器,线性回归就是这样一个例子。
为什么这些分数在这里不同?因为您没有在我们的训练数据集上评估模型!在评估数据集上,只要评估数据集和训练数据集在统计上相似,您就只期望这些指标彼此接近。BigQuery ML 在训练模型时会自动将数据集分成训练和评估数据集,但有一些选项可以更好地控制数据如何分割,详见第七章。
使用 ML.PREDICT 函数提供预测
现在您已经训练了您的模型并探索了评估指标,接下来是什么?ML 的终极目标是为您的用例提供预测,而不仅仅是训练最好的模型。一旦您在 BigQuery ML 中拥有性能满意的模型,使用 ML.PREDICT 函数非常简单地为该模型提供预测。请注意,ML.PREDICT 仅适用于 BigQuery 可用于处理的数据的预测。
假设您想要了解在平均气温为 27.45°C、环境压力为 1,001.23 毫巴、相对湿度为 84%、排气真空压力为 65.12 厘米汞柱的一个小时内的发电量。您可以运行以下查询来计算此预测:
SELECT
*
FROM
ML.PREDICT(MODEL `your-project-id.data_driven_ml.energy_production`,
(
SELECT
27.45 AS Temp,
1001.23 AS Ambient_Pressure,
84 AS Relative_Humidity,
65.12 AS Exhaust_Vacuum) )
注意,第二个SELECT语句包括了预测能量产量的特征值。使用AS关键字为列命名是一种最佳实践,以确保将值适当地传递给模型。请注意,如果包含了不对应特征的额外列,则它们将简单地传递到结果中。这在您想将预测标签作为结果表中的一列时非常有用,但又希望包含未用于模型的列时尤为如此。
与图 6-25 中的结果进行比较,但请注意,您模型预测的标签可能会略有不同。列predicted_label包含了预测的能量产量。

图 6-25. ML.PREDICT函数的结果。给定特征值的预测能量产量为 433.35 兆瓦。
您在此处所做的方法非常适合单个预测,但如果您想要预测一个特征值表呢?您可以使用ML.PREDICT函数来在表格上进行预测,效果同样出色。您可以用一个表格替换前面示例中的第二个SELECT语句,以指定表格作为结果而不是单个行。例如:
SELECT
*
FROM
ML.PREDICT(MODEL `your-project-id.data_driven_ml.energy_production`,
(
SELECT
Temp,
Ambient_Pressure,
Relative_Humidity,
Exhaust_Vacuum
FROM
`your-project-id.some_dataset.some_table`) )
这种形式的查询将 BigQuery 转变为一个批量预测的绝佳工具,这正是您需要一次性对大量实例进行预测的地方。
引入可解释人工智能
在过去的十年中,随着深度学习和更复杂的模型的增长,可解释人工智能(或简称为 XAI)已成为一个快速发展的研究领域。XAI 的目标是用人类可理解的术语描述模型的行为。这种理解可以用在许多不同的方式上:提高模型的性能,理解模型存在的问题,确保模型避免特定偏见以符合或保持伦理原因,以及许多其他用例。本节内容在 BigQuery ML 的工作环境中进行了快速介绍。
当讨论 XAI 时,通常会讨论局部或全局解释。局部解释聚焦于单个实例或可能是一小组实例。您可以将此目标看作是“为什么我的模型对于这个具体示例给出了这个预测?”全局解释则查看模型在某个数据集上的整体行为。例如,要回答“哪些特征往往对该模型的预测贡献最大?”这个问题,您可以使用全局解释。
如何计算这些解释?大多数方法都是 post hoc 使用的,即在模型训练后。这些方法可以针对特定的模型类型,也可以对使用的模型不可知。一般来说,后续方法使用一个特定的数据集(例如,评估数据集),并利用模型在此数据集上的行为和扰动来提供解释。
一些模型本质上是可解释的,比如线性回归模型。在这种情况下,可以直接从模型本身推导出解释,而无需使用单独的数据集。然而,存在一种权衡。一般来说,复杂模型的本质解释性较低。例如,大多数图像和语言模型中使用的深度神经网络等更复杂的模型类型,无法从模型定义中解释,必须依赖后续方法。
本章及其后续章节中将看到一些这些方法的应用,但是要深入了解这些概念,Explainable AI for Practitioners 由 Michael Munn 和 David Pitman(O’Reilly,2022)是一个很好的资源。
BigQuery ML 中的可解释 AI
虽然 ML.EVALUATE 函数提供评估指标,但 BigQuery 还提供了一个功能,用于解释模型及其产生的预测。BigQuery 中的全局和局部解释使用 Google Cloud 的可解释 AI 服务。BigQuery 中的可解释 AI 提供“特征归因”,显示对整体模型和特定预测最重要的输入特征。要计算全局解释,您需要修改 CREATE MODEL 查询并添加一个额外选项以启用全局可解释性:
enable_global_explanation = TRUE
全局解释返回特征对模型的整体影响,通常通过对整个数据集的特征归因进行聚合获得。较高的绝对值表明特征对模型预测的影响更大。
可以在不启用全局解释的情况下计算局部解释。接下来的内容中将展示两者的例子。
修改 CREATE MODEL 语句
将原始 CREATE OR REPLACE MODEL 查询复制到新的查询窗口中。按照以下方式修改查询,添加语句 enable_global_explain=TRUE:
CREATE OR REPLACE MODEL data_driven_ml.energy_production: OPTIONS
(model_type='linear_reg',
input_label_cols=['Energy_Production'],
enable_global_explain=TRUE) AS
SELECT
Temp,
Ambient_Pressure,
Relative_Humidity,
Exhaust_Vacuum,
Energy_Production
FROM
`your-project-id.data_driven_ml.ccpp_cleaned`
运行修改后的查询以训练启用全局解释的新版本模型。这只需几分钟时间。在模型训练完成之前,请等待再进行下一步操作。
使用 ML.GLOBAL_EXPLAIN 函数
要查看解释,您可以创建一个基本的“SELECT * FROM”SQL 语句。 这里的不同之处在于FROM语句。 与FROM语句引用项目 ID、数据集和表不同,FROM语句被修改如下代码所示。 ML.GLOBAL_EXPLAIN函数调用模型本身(在本例中为“energy_production”)以检索结果。 在 BigQuery 控制台中运行以下查询来探索这个问题:
SELECT
*
FROM
ML.GLOBAL_EXPLAIN(MODEL `data_driven_ml.energy_production`)
图 6-26 包含查询结果,并显示了您模型整体中重要性评分最高的特征。 基于先前使用相关性分析功能的特征分析,您预计Temp特征将具有最大的归因分数,这在这里得到了确认。

图 6-26。 ML.GLOBAL_EXPLAIN返回通过对评估数据集中每行收到的平均绝对归因进行取得的全局特征归因。
使用ML.EXPLAIN_PREDICT函数计算局部解释
回想一下我们之前的例子:您想知道温度平均为 27.45°C 时的功率产生情况,环境压力为 1,001.23 毫巴,相对湿度为 84%,排气真空压力为 65.12 厘米汞。 您使用ML_PREDICT函数预测在这一小时内能量产生将为 433.35 兆瓦。 您预期温度将基于模型的全局特征归因对您的模型产生最大影响。 然而,一般的全局解释会对整个数据集进行聚合——那么特别的例子呢?
您可以使用ML.EXPLAIN_PREDICT方法替换ML.PREDICT方法,以返回具有局部特征归因的预测。 运行以下查询以获取前三个特征的特征归因。 请注意,您的确切输出可能与图 6-27 中显示的输出不同:
SELECT
*
FROM
ML.EXPLAIN_PREDICT(
MODEL `your-project-id.data_driven_ml.energy_production`,
(
SELECT
Temp,
Ambient_Pressure,
Relative_Humidity,
Exhaust_Vacuum
FROM
`your-project-id.data_driven_ml.ccpp_cleaned`),
STRUCT(3 AS top_k_features) )

图 6-27。 ML.EXPLAIN_PREDICT函数的输出。 在这种情况下,温度对于这个例子的输出有最大的(按大小)贡献。
输出中有一些新的语法和新列需要解释。ML.EXPLAIN_PREDICT 的附加参数 STRUCT(3 AS top_k_features) 限制了特征归因的输出为前三个特征。top_k_features 选项是进行此操作的选项,ML.EXPLAIN_PREDICT 期望将此信息作为 STRUCT 传递。在 SQL 中,可以将 STRUCT 理解为具有特定名称和可能不同类型的值(或列)列表。
现在来讲讲新的列。top_feature_attributions 本身就是一个 STRUCT,包含两个字段,feature 和 attribution。字段 feature 给出了相应的特征名称,而 attribution 给出了此实例的归因值。另外还有一个新列:baseline_prediction_value。这个值为了获取局部特征归因,提供了一个用于将您的实例与之进行比较的基线。在线性回归模型中,这个基线是数据集中标签值(能量产量)的平均值。
那么如何解释归因值呢?请注意,预测标签小于基线预测,并且所有归因值均为负值。温度值平均从基线值减少约 18.52 MW,相对湿度约为平均减少 2.16 MW,环境压力约为平均减少 0.31 MW。本例中未包括排气真空压力,因为它不在前三位,但其对该示例的贡献甚至比环境压力更小。因此,您可以看到,对于此示例,与基线能量生产的大部分偏差是由温度引起的,其他特征的贡献较小。
另一种选择是利用 Jupyter Notebooks 中的可解释性库,如 Google Colab notebooks。LIME 和 SHAP 是两个流行的 Python 库,广泛用于许多不同的用例。本书不涉及详细讨论;我们推荐这篇博客文章和其他已经提到的可解释 AI 参考资料,以进行更深入的讨论和明确的示例。
注意
在上一段中,“平均”这个词组在初次阅读时可能显得有些奇怪。BigQuery ML 使用 Shapley 值来计算线性模型的归因。Shapley 值是从联合博弈理论中借来的工具,它计算不同联合体(您感兴趣的特征值及某些基线特征值的不同组合)中特征值的平均贡献。尽管有更技术性的定义(例如,参见 Christoph Molnar 的《可解释机器学习:使黑盒模型可解释的指南》[自出版,2022]),对于线性模型,这些可以简单地计算为对应于该特征的权重和该特征值本身的局部解释。
练习
在 “特征选择和相关性” 中,你开始更好地理解特征选择过程及其一些技术用于特征选择。然而,在前一节中,你使用了所有可能的特征来训练你的模型。读者的一些练习:
-
使用一部分特征来训练新模型。利用你对相关性和共线性的了解来选择你的特征。
-
评估这些新模型。哪些特征集表现最好?
-
使用讨论过的可解释性函数来探索哪些特征在全局和局部对模型性能贡献最大。有什么意外之处吗?
BigQuery ML 中的神经网络
现在你已经使用 BigQuery ML 训练了线性回归模型,是时候看看另一种流行的 ML 模型类型:神经网络了。由于额外的计算资源、新的模型架构以及它们在转移学习中应用知识的灵活性,神经网络在过去十年变得非常流行。本节提供了神经网络的快速介绍,然后展示如何在 BigQuery ML 中构建这样一个模型。
神经网络简要概述
和线性回归模型一样,神经网络也是数学函数,接受数值特征值作为输入,并输出标签的预测值。神经网络可以用于回归和分类问题,但本节重点是回归模型。
为了描述神经网络,让我们重新以视觉术语描述线性回归。回想一下,为了预测能源产量的问题,有四个特征:温度、环境压力、相对湿度和排气真空压力。为了简单起见,将它们标记为 和 。使用这四个特征的线性回归模型将具有以下形式:
要将其可视化为网络,请绘制如图 6-28 的图。为每个特征和输出绘制一个顶点。从特征顶点到输出顶点画箭头,并用权重和标记这些边。通常在此表示中不绘制偏置。

图 6-28. 线性回归模型的可视化表示。
现在,您已经将线性回归模型可视化为神经网络了!在撰写任何一般神经网络公式之前,我们将从一个不是线性回归模型的视觉表示开始。假设现在您想要将原始特征组合成新的隐藏特征和,其中每个特征都是原始特征的线性组合,或者您可以将其视为特征的加权和。从技术上讲,它是以下形式的表达式
其中是一些实数。隐藏特征将具有类似的定义,但常数不同(例如,使用而不是)。严格来说,项使其略微不同于特征本身的线性组合,但如果将 1 包括为恒定特征,则技术定义与此处的使用是一致的。
我们将这些隐藏特征一起称为隐藏层。它们是隐藏的,因为您在模型的输入或输出中看不到它们,但它们在计算从输入到输出的过程中起到作用。您可以以与之前相同的方式绘制视觉表示,但现在特征顶点连接到新的隐藏特征和,而隐藏特征连接到输出,如图 6-29 中所示。

图 6-29. 一个具有一个隐藏层的神经网络的视觉表示。
这是具有一个隐藏层的神经网络的示例。然而,你如何确定这些隐藏特征应该是什么呢?也就是说,你如何找到各个 和 的最佳值?神经网络没有正常方程,但你可以像以前一样使用梯度下降!不同于尝试不同的组合,你可以将 和 看作是你线性回归模型中的其他权重,通过训练模型来找到最佳权重。
激活函数与非线性
然而,神经网络的拼图还缺少一部分。要看清楚这一点,考虑一个简单的数学问题。假设 和 。那么 是多少?要计算一个复合函数,将内部函数的输出(这里是 )代入第二个函数:
这项练习的目的是什么?请注意, 和 是形式为 的线性函数,其中 和 是一些常数。最终答案 也是同样形式的线性函数(只是 和 不同)。这并非巧合!一般来说,线性函数的复合仍然是线性的。
这与神经网络有何关联?隐藏特征和是和的线性函数;是和的线性函数。您可以将神经网络视为两个线性函数的组合。第一个函数将映射到,第二个函数将映射到。由于两个函数都是线性的,所以组合也是线性的。我们刚刚找到了一个更复杂的方法,将写成和的线性函数!
那么 所以,神经网络中带有隐藏层的网络毫无意义吗?完全不是,但你需要再添加一些东西才能使它们变得有趣:非线性激活函数。激活函数是应用于隐藏层特征的函数,在将这些值传递到下一层之前使用。这样做是为了使从一层到下一层的函数不再是线性的,从而使模型能够学习更有趣的行为。用于神经网络(也是最简单的)的最常见激活函数是修正线性单元函数,简称 ReLU。ReLU 是一个非常简单的函数,如果输入值为正数则返回该值,否则返回 0(参见图 6-30)。

激活函数通常应用于隐藏层,但不适用于回归问题的输出本身。现在你对神经网络有了一个定义,不仅仅是线性回归模型。具有 ReLU 激活的结果函数仍然是分段线性的,但它们不是线性的事实意味着你可以建模更复杂的函数。你可以看到这些激活函数如何被添加的视觉表示在图 6-31 中。
激活函数通常应用于隐藏层,但不应用于 th> 的线性函数!

图 6-31. 具有一个隐藏层和激活函数 ReLU 的神经网络的视觉表示。盒子代表 ReLU 被应用于隐藏特征值之前,用于计算最终输出。
总体而言,您可以构建任意数量的隐藏层神经网络。例如,如果您有两个隐藏层,您可以概念化地认为神经网络执行以下操作。训练后,模型将学习两个不同隐藏层的隐藏特征。您可以将第一个隐藏层的特征看作是从原始输入特征学习到的,将第二个隐藏层的隐藏特征看作是从第一个隐藏层的隐藏特征学习到的。当然,通过查看模型的输入和输出无法直接看到这一点,但这确实对应于神经网络如何从一层到另一层逐步构建概念,并最终将其应用于计算最终输出。请参见图 6-32 以查看这样一个神经网络的视觉示例。

图 6-32. 一个具有两个隐藏层的神经网络的视觉表示。在这个例子中,激活函数 ReLU 没有被可视化。
小贴士
更多的隐藏层会导致更强大的模型,但需要优化更多的权重,因此需要更多的数据和时间来训练。较小的神经网络(较少的隐藏层)更容易训练,但可能无法像较大的神经网络那样学习复杂的关系。
在 BigQuery ML 中训练深度神经网络
现在您对神经网络有了一些了解,是时候在 BigQuery ML 中训练一个神经网络了。训练深度神经网络将使您能够学习输入变量和标签(功率产生)之间的关键非线性关系。幸运的是,尽管概念有些复杂,但用于此操作的 SQL 语法与以前一样简单,只是有些细微差别。在 BigQuery 控制台中编写并运行以下查询:
CREATE OR REPLACE MODEL data_driven_ml.energy_production_nn
OPTIONS
(model_type='dnn_regressor',
hidden_units=[32,16,8],
input_label_cols=['Energy_Production']) AS
SELECT
Temp,
Ambient_Pressure,
Relative_Humidity,
Exhaust_Vacuum,
Energy_Production
FROM
`your-project-id.data_driven_ml.ccpp_cleaned`
由于模型更加复杂,并且 BigQuery 正在将数据导出到 Vertex AI 以使用 Vertex AI 训练神经网络,因此这个模型的训练时间会更长。
在模型训练过程中,有一些 SQL 语句需要进行一些小的更改。首先是模型类型。dnn_regressor 是用于深度神经网络回归模型的模型类型。这里的 deep 指的是神经网络可能具有任意数量的隐藏层,也就是说,它可以有很多层深度。另一个新参数是 hidden_units。隐藏单元 或 神经元 是以前称为 隐藏特征 的更技术化和常用的术语。hidden_units 选项期望一个整数数组,在这种情况下,数组 [32,16,8] 被给定。这意味着神经网络有三个隐藏层:第一个隐藏层有 32 个神经元,第二个有 16 个神经元,第三个有 8 个神经元。要绘制其可视化表示会有点繁琐,但希望您可以通过类比以前的图表来理解其可能的样子。
一旦您的模型训练完成,就像您为线性模型所做的那样,转到模型和评估选项卡,并查看您模型的评估指标。您的指标可能与您在 图 6-33 中看到的不同。

图 6-33. 经过训练的神经网络评估指标。
新的神经网络模型在均方误差(以及 RMSE,因为 RMSE 是均方误差的平方根)方面略逊于线性回归模型。这是一个过拟合的例子(如 图 6-34 所示)。过拟合的模型可以更准确地预测训练集中数据的标签(如所示),但这是以错过数据整体二次趋势为代价的。模型也可能出现欠拟合。也就是说,模型过于简单,无法学习数据的趋势。图 6-34 展示了一个使用线性模型的例子,因此模型无法学习数据中的二次趋势。

图 6-34. 数据集的欠拟合和过拟合模型。
提示
在训练机器学习模型时,您应该比较训练数据集和评估数据集的性能,以确保模型没有过拟合。您可以通过改变模型架构、使用早停或各种其他正则化技术来解决这个问题。正则化 是指用来抑制模型过拟合的一系列技术。第八章 更详细地讨论了一些这些技术,适用于不同的框架,包括 BigQuery ML。
自然而然地,您可能在路上遇到了一个重要问题。您如何知道您有最佳的隐藏层和神经元数?您可以尝试一些不同的列表来为 hidden_units 选项,并查看哪个表现最佳,但理论上有无限多种可能性。隐藏层的数量,每层的神经元数以及使用的激活函数都是所谓的超参数。超参数与参数(如权重)不同,它们在模型训练之前设置。超参数定义了模型架构、训练过程等,而参数则在训练过程中被优化,以尝试找到由这些超参数定义的最佳模型。第八章 探讨了在 BigQuery ML、Vertex AI 和其他流行的 ML 框架中寻找最佳超参数的不同方法。
练习
在 “特征选择和相关性” 中,您看到了特征选择过程,并学习了一些用于特征选择的技术。但是,在前一节中,您使用了所有可能的特征来训练您的模型。以下是一些可以尝试的练习:
-
使用更大的神经网络训练另一个模型。你可以为每一层添加额外的神经元,或者添加额外的层。这对模型性能有何影响?
-
使用较小的神经网络训练另一个模型。您可以为每一层删除神经元或删除层。这对模型性能有何影响?
-
使用讨论过的可解释性函数来探索哪些特征在全局和局部上对模型性能贡献最大。是否有什么意外之处?
深入探讨:使用云 Shell 查看您的云存储文件
云 Shell 是 Google Cloud 上的免费服务,提供对预安装有 Google Cloud CLI(命令行界面)的小型虚拟机的终端访问。这允许您使用命令行与 Google Cloud 资源进行交互。我们无法在本书中覆盖其所有功能,但您将看到一个简单的示例,演示如何在不下载文件的情况下打印存储在 Google Cloud Storage 中文本文件的前几行。
要访问云 Shell,请在 Google Cloud 控制台 UI 的右上角单击激活云 Shell 按钮(在 图 6-35 中显示)。虚拟机的配置可能需要一些时间。

图 6-35. 激活云 Shell 按钮。
每次打开 Cloud Shell(参见图 6-36),底层的虚拟机都是不同的,但持久磁盘是不变的。这是什么意思?这意味着你在 Cloud Shell 中的数据将始终可用,但你安装的任何东西在重新打开 Cloud Shell 时都需要重新安装。由于你将在这里使用 Google Cloud CLI,这不会成为问题。

图 6-36. Cloud Shell 终端。
首先,请确保你的项目在 Cloud Shell 中是活动状态。如果是活动状态,你应该看到类似于 “your-login@cloudshell:~ (your-project-id)$” 的终端提示符。如果你在提示符的第二部分看不到项目 ID,你需要设置一下。幸运的是,使用 Google Cloud CLI 非常容易做到这一点。键入以下命令(用你的项目 ID 替换 your-project-id)并按 Enter/Return 键:
gcloud config set project your-project-id
现在,你应该在终端提示符的第二部分看到你的项目 ID。你也可以通过运行以下命令来检查项目 ID 是否设置成功:
gcloud config list project
现在,你已经使用你的项目 ID 激活了 CLI,可以一起查看 CSV 文件的前几行。通过运行以下命令来实现:
gcloud storage cat -r 0-250 gs://low-code-ai-book/ccpp.csv
不同于之前的命令,这个命令的目的可能不太明显。gcloud storage 是与 Google Cloud Storage 交互的命令系列,你正在执行 cat 命令。这个命令是 concatenate 的缩写,用于从 Google Cloud Storage 中读取文件并将其写入标准终端输出。命令的下一部分 -r 0-250 指定你不想读取整个文件,而只是前 250 个字节。对于这个 CSV 文件,这将让你快速查看前几行,以了解你看到的内容。最后,你有 Google Cloud Storage 中文件的统一资源标识符(URI):gs://low-code-ai-book/ccpp.csv
这个命令的输出如下:
Temp,Exhaust_Vacuum,Ambient_Pressure,Relative_Humidity,Energy_Production
14.96,41.76,1024.07,73.17,463.26
25.18,62.96,1020.04,59.08,444.37
5.11,39.4,1012.16,92.14,488.56
20.86,57.32,1010.24,76.64,446.48
10.82,37.5,1009.23,96.62,473.9
26.27,59.44
最后一行没有完成,但没关系!这里的目标只是简单了解你看到的东西的概念。请注意,这个 CSV 文件有五列:Temp, Exhaust_Vacuum, Ambient_Pressure, Relative_Humidity, 和 Energy_Production。这些与预期的列相对应。至少在前几行,这些数据看起来都没问题,但当然,这个文件里可能会有比现在展示的更多的数据。你怎么能弄清楚呢?有一个很好的终端命令,wc,可以计算文件中的行数:
gcloud storage cat gs://low-code-ai-book/ccpp.csv | wc -l
命令的第一部分很熟悉:你正在连接来自 Google Cloud Storage 的 CSV 文件。你没有指定要读取多少字节,所以将会读取整个文件。但命令的最后一部分是新内容。这个命令实际上是使用管道运算符|将两个不同的命令链在一起。管道运算符将第一个命令的输出“管道”到第二个命令,作为其输入使用。在本例中,第二个命令是wc -l命令。这个命令使用wc(“word count”)来计算 CSV 文件中的行数、单词数和字符数。选项-l用于仅打印出行数。在本例中,你会看到 CSV 文件有 9,590 行。
概要
在本章中,您使用 SQL 和 Python 分析了发电厂生产数据。利用您在分析中学到的内容,您构建了线性回归和深度神经网络回归模型,以使用 BigQuery ML 预测发电厂生产。在此过程中,您探索了关于可解释性以及神经网络背后某些数学概念的新概念。
到目前为止,本书的重点一直放在无代码和低代码解决方案上,但可能会有需要更灵活解决方案的情况。在下一章中,您将介绍如何使用 Python 中的 scikit-learn 和 Keras 进行自定义代码解决方案。这两个库非常易于上手,是开始探索使用 Python 进行机器学习的绝佳选择。
¹ 这个数据集是使用来自加州大学尔湾分校机器学习库的联合循环发电厂数据集创建的。为了更好地展示本章的概念,做了一些小的修改。
第七章:在 Python 中训练自定义 ML 模型
在本章中,你将学习如何使用两个流行的 Python 机器学习库,scikit-learn 和 Keras,构建分类模型来预测客户流失。首先,你将使用 Pandas 探索和清理你的数据。然后,你将学习如何使用 scikit-learn 对分类特征进行独热编码以进行训练,训练逻辑回归模型,使用评估指标了解模型性能,并改善模型性能。你将学习如何使用 Keras 执行相同的步骤,使用已准备好的数据构建神经网络分类模型。在此过程中,你将进一步了解分类模型的性能指标以及如何更好地理解混淆矩阵来评估你的分类模型。
此章节使用的数据集,IBM 电信客户流失数据集,是一个学习如何建模客户流失的流行数据集。在完成本章练习后,你应该有兴趣查看其他使用该数据集的示例,以增加你的知识。
业务用例:客户流失预测
你在此项目中的目标是预测电信公司的客户流失。客户流失被定义为客户的离职率,或者说选择停止使用服务的客户比率。电信公司通常以每月费率或年度合同销售其产品,因此这里的“流失”表示客户在下个月取消其订阅或合同。
初始数据以 CSV 文件的形式提供,因此你需要花一些时间将数据加载到 Pandas 中,然后才能探索它,并最终使用它来创建不同框架下的 ML 模型。数据集包含数值变量和分类变量,其中变量从一组离散可能值中取值。
数据集中有 21 列。表 7-1 提供了这些列的列名、数据类型以及关于这些列可能值的一些信息。
表 7-1. 客户流失数据集的模式和字段值信息
| 列名 | 列类型 | 关于字段值的注释 |
|---|---|---|
customerID |
String | 每个客户的唯一值 |
gender |
String | “男性”或“女性” |
SeniorCitizen |
Integer | 如果客户是老年人则为 1,否则为 0 |
Partner |
String | 记录客户是否在家庭中有伴侣(配偶或同居伴侣) |
Dependents |
String | 记录客户家庭中是否有受抚养人 |
tenure |
Integer | 客户使用电信服务的月数 |
PhoneService |
String | 记录客户是否支付电话服务费用 |
MultipleLines |
String | 如果客户支付电话服务,他们是否支付多条电话线? |
InternetService |
String | 客户是否付费获得何种类型的互联网服务? |
OnlineSecurity |
String | 客户是否付费获得在线安全服务? |
OnlineBackup |
String | 客户是否付费获得在线备份服务? |
DeviceProtection |
String | 客户是否购买设备保护? |
TechSupport |
String | 客户是否付费获得在线技术支持? |
StreamingTV |
String | 客户是否付费观看流媒体电视? |
StreamingMovies |
String | 客户是否付费观看流媒体电影? |
Contract |
String | 客户是否签有合同或者按月支付? |
PaperlessBilling |
String | 客户是否使用无纸化账单? |
PaymentMethod |
String | 客户使用何种付款方式? |
MonthlyCharges |
Float | 客户服务的月费用 |
TotalCharges |
Float | 客户在其生命周期内支付的总金额 |
Churn |
String | 客户是否在接下来的一个月内离开电信服务? |
你会发现许多功能可以合并或省略以训练你的 ML 模型。然而,许多功能需要清洗和进一步转换,以准备训练过程。
在无代码、低代码或定制代码 ML 解决方案之间进行选择
在探索如何使用诸如 scikit-learn、Keras 或本书中讨论的其他选项之前,讨论何时应该使用定制培训工具是值得的,这在 第 3 章中已经讨论过:
无代码解决方案
在两种特定情况下尤其出色。首先是当您需要构建一个 ML 模型,但没有任何 ML 的专业知识时。本书的目标是为您提供更多关于如何在 ML 数据周围做出正确决策的见解,但无代码解决方案通常存在以简化决策并减少与更复杂解决方案的工作需要。另一个无代码解决方案突出的地方是模型的快速原型设计。因为无代码解决方案(如 AutoML 解决方案)管理诸如特征工程和超参数调整等步骤,这可以是训练快速基准模型的简便方法。不仅如此,正如第四章和第五章所示,通过 Vertex AI AutoML 部署这些模型也非常简单。在许多情况下,这些无代码解决方案可以立即用于生产环境。在实践中,定制解决方案在足够时间和精力的情况下可能会优于无代码解决方案,但模型性能的增量收益通常会被将无代码解决方案投入生产所节省的时间所抵消。
低代码解决方案
当你确实需要一些定制化,并且正在处理符合所使用工具约束条件的数据时,这些方案非常适合。例如,如果你正在处理结构化数据,并且希望解决的问题类型得到了 BigQuery ML 的支持,那么 BigQuery ML 可能是一个很好的选择。在这些情况下,低代码解决方案的优势在于,构建模型所需的时间更少,可以花更多时间对数据进行实验和调整模型。在许多低代码解决方案中,模型可以直接在产品内部进行生产化,或通过模型导出和使用 Vertex AI 等其他工具进行。
自定义代码解决方案
这些无疑是最灵活的,经常被数据科学家和其他人工智能从业者所利用,他们喜欢构建自己定制的模型。使用像 TensorFlow、XGBoost、PyTorch 和 scikit-learn 这样的 ML 框架,你可以使用任何类型的数据构建模型,并选择你想要的目标。从某种意义上说,灵活性和部署选项几乎没有限制。如果需要自定义转换,可以进行构建。如果需要将模型作为 Web 应用的一部分进行部署,也可以实现。在拥有正确的数据、专业知识和足够时间的情况下,可以通过定制代码解决方案实现最佳结果。然而,其中一个权衡是需要花时间学习这些不同工具和技术。
你应该偏向哪一个?对于每种可能的用例,没有单一正确的答案。考虑你有多少时间来训练、调整和部署模型。还要考虑数据集和问题目标。无代码或低代码解决方案是否支持你的用例?如果不支持,那么自定义代码解决方案可能是唯一的选择。最后,考虑你自己的专业知识。如果你非常擅长 SQL 但对 Python 还不熟悉,那么像 BigQuery ML 这样如果支持你试图解决的问题,可能是最好的选择。
本书不旨在让你成为使用各种不同自定义代码 ML 框架的专家。然而,本书确实采用了这样一种方法,认为接触这些工具和一些基础知识可以为解决问题和与数据科学家以及 ML 工程师合作走得更远。如果你对 Python 不熟悉,那么 Bill Lubanovic 的Introducing Python(O'Reilly,2019)是一个很好的入门资源。此外,如果你想深入学习本章介绍的 ML 框架,Aurélien Géron 的Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow(第二版,O'Reilly,2022)是一个被实际数据科学家和 ML 工程师引用的精彩资源。
使用 Pandas、Matplotlib 和 Seaborn 探索数据集
在学习 scikit-learn 和 Keras 之前,您应该遵循之前章节讨论过的围绕理解和准备 ML 数据的工作流程。尽管您在之前章节中简要使用过 Google Colab 从 BigQuery 加载数据到 DataFrame 并进行了一些基本的可视化,但在 Jupyter Notebook 环境中还没有完全通过数据准备和模型训练过程。
本节重新介绍如何使用 Pandas 将数据加载到 Google Colab 笔记本中。将数据加载到 DataFrame 后,您将探索、清理和转换数据,然后创建用于训练 ML 模型的数据集。正如您在之前章节中看到的那样,大部分工作不是用来训练模型,而是用来理解和准备训练数据。
本节中的所有代码,包括一些额外的示例,都包含在 GitHub 上的低代码 AI 仓库的 Jupyter 笔记本中。
在 Google Colab 笔记本中加载数据到 Pandas DataFrame
首先,访问https://colab.research.google.com,并打开一个新的笔记本,按照第二章中讨论的流程进行操作。您可以通过点击名称如图 7-1 所示,并替换当前名称为新名称,例如Customer_Churn_Model.ipynb来将此笔记本重命名为更有意义的名称。

图 7-1. 将 Google Colab 笔记本重命名为更有意义的名称。
现在,在第一个代码块中键入以下代码以导入分析和可视化客户流失数据集所需的包:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import sklearn
import tensorflow as tf
在第二章中首次探索使用 Colab 笔记本时,您已经见过其中一些包,但在这里有些包对您可能是新的。import sklearn 导入了 scikit-learn,这是一个流行的 ML 框架。Scikit-learn 首次发布于 2007 年,构建在 NumPy 和 SciPy 等其他 Python 库之上。它旨在成为一个易于使用的框架,用于构建包括线性模型、基于树的模型和支持向量机在内的 ML 模型。接下来的一行,import tensorflow as tf,导入了 TensorFlow。TensorFlow 是一个高性能的数值计算库,设计初衷是用于训练和部署深度神经网络。TensorFlow 包括 Keras,一个旨在简化深度神经网络开发和相应数据转换的库。您将在本章后面使用 Keras 来训练神经网络模型。
现在执行包含import语句的单元格以导入这些包。要做到这一点,点击单元格左侧显示的运行单元格按钮,如图 7-2 所示,或者按 Shift + Enter 来运行单元格。

图 7-2. 在此处顶部左侧看到“运行单元”按钮。
您可以通过检查包的版本来快速验证 import 语句是否成功执行。每个包都包含一个特殊的属性 __version__,它返回包的版本。在一个新的单元格中输入以下代码,并执行该单元格以检查 scikit-learn 和 TensorFlow 包的版本:
print("scikit-learn version:", sklearn.__version__)
print("TensorFlow version:", tf.__version__)
您应该看到如 图 7-3 所示的版本已打印出来。请注意,您的确切版本号将取决于您执行此操作的时间。

图 7-3. 打印 scikit-learn 和 TensorFlow 的版本,确保它们已正确导入。
现在您已准备好导入数据了。回想一下,数据集以 CSV 格式存储,因此您需要下载该数据,将其上传到笔记本中,然后导入到 Pandas DataFrame 中,对吗?实际上,情况并非如此。Pandas 的一个非常好的功能是,您可以直接从互联网上的位置将 CSV 文件导入到 DataFrame 中,而无需先下载文件。要做到这一点,输入以下代码到一个新的单元格,并执行该单元格:
file_loc = 'https://storage.googleapis.com/low-code-ai-book/churn_dataset.csv'
df_raw = pd.read_csv(file_loc)
一般来说,查看 DataFrame 的前几行是一个好主意。使用 df_raw.head() 来探索 DataFrame 的前几行。您可以快速滚动数据的列,并一目了然地看到它们似乎与预期相符。表 7-2 显示了前五行的部分列示例。查看前几行是一个很好的快速第一步,但当然,这个数据集不仅仅是几行,可能会隐藏一些看不见的问题。
表 7-2. 使用 head() 方法打印的 DataFrame df_raw 的前五行的部分列
流媒体电视 |
流媒体电影 |
合同 |
无纸化账单 |
支付方式 |
月费 |
总费用 |
流失 |
|---|---|---|---|---|---|---|---|
No |
No |
按月付费 |
Yes |
电子支票 |
29.85 |
29.85 |
No |
No |
No |
一年 |
No |
邮寄支票 |
56.95 |
1889.5 |
No |
No |
No |
按月 |
Yes |
邮寄支票 |
53.85 |
108.15 |
Yes |
No |
No |
一年 |
No |
银行转账(自动) |
42.30 |
1840.75 |
No |
No |
No |
按月付费 |
Yes |
电子支票 |
70.70 |
151.65 |
Yes |
理解和清理客户流失数据集
现在,数据已加载到 DataFrame df_raw中,您可以开始探索和理解数据。即时目标是了解数据可能存在的问题,以便在继续之前解决这些问题。但是,在转换数据时,您还应该关注 DataFrame 列的整体分布和其他属性,这在后续操作中将非常重要。
检查和转换数据类型
首先,您将检查 Pandas 推断的数据类型是否与 Table 7-1 中预期的相符。为什么这很有用?这可以是检查误输入数据的简便方法,这种问题通常来自数据本身。例如,如果整数列中存在字符串值,Pandas 会将该列导入为字符串列,因为整数可以转换为字符串,但反之通常不行。要检查 DataFrame 的数据类型,请在新单元格中键入df_raw.dtypes并执行该单元格。
注意,在dtypes后面没有括号。这是因为dtypes不是函数,而是 Pandas DataFrame df_raw的属性。除了TotalCharges列之外,Pandas DataFrame 中导入的任何不是浮点数或整数的内容都被视为object类型是正常行为。如果您仔细查看输出,几乎每一列都与预期类型匹配,但TotalCharges列除外。您可以在 Table 7-3 中查看最后几列的输出,并确认在您的笔记本环境中是否看到相同的情况。
Table 7-3. df_raw DataFrame 的最后六列的数据类型(请注意,TotalCharges列不是预期的float64类型)
Contract |
object |
|---|---|
PaperlessBilling |
object |
PaymentMethod |
object |
MonthlyCharges |
float64 |
TotalCharges |
object |
Churn |
object |
TotalCharges列与预期有所不同,这是一个不错的迹象。在继续之前,您应该探索该列并理解发生了什么。请记住,您可以使用语法df['ColumnName']来处理 Pandas DataFrame 的单个列,其中df是 DataFrame 名称,ColumnName是列名。
首先,使用describe()方法获取TotalCharges列的一些高级统计信息。尝试在查看提供的代码之前完成这一步,但如果需要,这里是代码:
df_raw['TotalCharges'].describe()
您的输出应与 Figure 7-4 中的输出相同。由于TotalCharges被视为分类变量(在这种情况下为字符串),因此您只会看到元素的计数、唯一值的数量、频率最高的顶级值以及该值出现的次数。

图 7-4. TotalCharges列的汇总统计信息。注意,最频繁的值是一个只包含一个空格的字符串。
在这种情况下,您几乎可以立即看到问题所在。顶部的值要么为空,要么是空字符串,并且出现了 11 次。这很可能是为什么 Pandas 将TotalCharges视为意外数据类型,并导致您发现数据的问题。
当数据缺失时,您可以问:“应该有什么?”为了尝试理解这一点,查看 DataFrame 中对应的数据行,看看缺失的行是否有某种模式。为此,您将创建一个掩码并将其应用于 DataFrame。该掩码将是一个简单的语句,根据输入值返回true或false。在本例中,您的掩码将采用mask=(df.raw['TotalCharges']==' ')的形式。==运算符用于检查TotalCharges列的值是否等于只包含一个空格的字符串。如果值是只包含一个空格的字符串,则运算符返回true;否则返回false。将以下代码输入到新的单元格中并执行该单元格:
mask = (df_raw['TotalCharges']==' ')
df_raw[mask].head()
该单元格的输出显示在表 7-4 中。现在探索此单元格的结果。您是否注意到任何可能解释为何这些客户的TotalCharges列为空的情况?查看tenure列,注意这些客户的值都为 0。
表 7-4. 数据框df_raw的前几列和行,其中TotalCharges列的值为' '
customerID |
gender |
SeniorCitizen |
Partner |
Dependents |
tenure |
PhoneService |
|---|---|---|---|---|---|---|
4472-LVYGI |
女 |
0 |
是 |
是 |
0 |
否 |
3115-CZMZD |
男 |
0 |
否 |
是 |
0 |
是 |
5709-LVOEQ |
女 |
0 |
是 |
是 |
0 |
是 |
4367-NUYAO |
男 |
0 |
是 |
是 |
0 |
是 |
1371-DWPAZ |
女 |
0 |
是 |
是 |
0 |
否 |
如果tenure为 0,则这对电信公司的新客户来说是他们的第一个月,他们还没有被收费。这就解释了为什么这些客户的TotalCharges没有值。现在通过使用另一个掩码来验证这个假设,检查tenure等于 0 的行。尝试自己编写此单元格的代码,但如果需要帮助,可以查看下面的解决方案:
mask = (df_raw['tenure']==0)
df_raw[mask][['tenure','TotalCharges']]
请注意,在上述代码中,你指定了一个列名列表 ['tenure','TotalCharges']。由于你仅仅关注 tenure 和 TotalCharges 之间的关系,这将使结果更易于解析。所有 TotalCharges 等于 ' ' 的 11 行数据,在 tenure 列中的值均为 0。因此,实际上,这个关系是符合预期的。现在你知道这些奇怪的字符串值对应于零 TotalCharges,并且可以将这些字符串值替换为浮点数 0.0。最简单的方法是使用 df.replace() 方法。这个函数的语法可能需要一点时间来解析,所以首先在一个新的单元格中输入以下代码,并执行该单元格来查看结果:
df_1 = df_raw.replace({'TotalCharges': {' ': 0.0}})
mask = (df_raw['tenure']==0)
df_1[mask][['tenure','TotalCharges']]
你的结果应该与 Table 7-5 中的结果相同。现在你可以看到,以前 TotalCharges 的字符串值现在已经被替换为浮点数值 0.0。
Table 7-5. TotalCharges 列已被替换为值 0.0,对应所有 tenure 值为 0 的行
tenure |
TotalCharges |
|
|---|---|---|
488 |
0 |
0.0 |
753 |
0 |
0.0 |
936 |
0 |
0.0 |
1082 |
0 |
0.0 |
1340 |
0 |
0.0 |
3331 |
0 |
0.0 |
3826 |
0 |
0.0 |
4380 |
0 |
0.0 |
5218 |
0 |
0.0 |
6670 |
0 |
0.0 |
6754 |
0 |
0.0 |
将这些结果牢记心中,更容易理解第一行代码中使用的语法,即 df_raw.replace({'TotalCharges': {' ': 0.0}})。该方法采用了一种称为字典的 Python 数据结构。字典 是无序的键值对列表,其中每对的第一个元素是值的名称,第二个元素是值本身。在这种情况下,第一个元素是 TotalCharges,即你想要替换值的列名。第二个元素本身也是一个字典,{' ':0.0}。这对中的第一个元素是你想要替换的值,第二个元素是你想要插入的新值。
在你探索 TotalCharges 列和其他数值列的汇总统计数据之前,请确保 Pandas 知道 TotalCharges 是一个 float 值的列。要做到这一点,请在一个新的单元格中输入以下代码,并执行该单元格:
df_2 = df_1.astype({'TotalCharges':'float64'})
df_2.dtypes
请注意,astype() 方法使用了与 replace() 方法类似的参数。输入是一个字典,其中每对的第一个元素是要更改其数据类型的列,第二个参数(这里是 float64)是该列的新数据类型。你从单元格的输出应该类似于 Table 7-6 中显示的结果。
Table 7-6. 新 DataFrame df_2 中最后四列的数据类型,以竖排展示(本图中省略了其余列)
PaymentMethod |
object |
|---|---|
MonthlyCharges |
float64 |
TotalCharges |
float64 |
Churn |
object |
探索汇总统计信息
现在您已经解决了遇到的数据类型问题,请查看数值列的汇总统计信息。您之前在 第二章 中看到如何执行此操作,因此请尝试在不查看代码的情况下完成,尽管代码如下以备您需要帮助,并且结果在 表 7-7 中:
df_2.describe()
表 7-7。客户流失数据集中数值列的汇总统计信息
老年人 |
任期 |
每月费用 |
总费用 |
|
|---|---|---|---|---|
count |
7043 |
7043 |
7043 |
7043 |
mean |
0.162147 |
32.371149 |
64.761692 |
2279.734304 |
std |
0.368612 |
24.559481 |
30.090047 |
2266.79447 |
min |
0 |
0 |
18.25 |
0 |
25% |
0 |
9 |
35.5 |
398.55 |
50% |
0 |
29 |
70.35 |
1394.55 |
75% |
0 |
55 |
89.85 |
3786.6 |
max |
1 |
72 |
118.75 |
8684.8 |
一眼看去,在 表 7-7 的结果中,除了可能是 老年人 外,没有异常值或其他不正常情况。回想一下,老年人 的值要么是 0 要么是 1。SeniorCitizen 列的平均值(均值)为 0.162…,则代表了老年顾客的百分比。尽管该特征可能更适合被视为分类变量,但它是二进制的 0 或 1,这意味着像均值这样的汇总统计信息仍然可以提供有用的信息。
谈到分类特征,你如何探索这些特征的汇总统计信息?describe() 方法默认只显示数值特征。您可以通过使用可选关键字参数 include='object' 来包含分类特征的统计信息。这指定您希望仅包括 object 类型的列,这是 Pandas 中所有非数值列的默认数据类型。在 describe() 方法中包括此可选参数,并执行单元格。如果您需要帮助,这里包含了代码:
df_2.describe(include='object')
您现在将看到分类特征的统计信息。这些汇总统计信息更简单,因为您正在处理离散值而不是数值。您可以看到具有非空值的行数或计数,唯一值的数量,最常见的值(或在并列情况下的其中之一),以及该值的频率。
例如,考虑 customerID 列。该列的唯一值数量与行数相同。解释这些信息的另一种方式是,该列中的每个值都是唯一的。您还可以通过查看顶部值出现的频率来进一步验证这一点。
探索摘要统计信息并查看其他注意事项。以下是一些观察结果的集合,这些将对未来的工作有所帮助,但并不完全涵盖这些结果中可用信息的列表:
-
gender和Partner列在两个不同值之间相当平衡。 -
绝大多数客户拥有电话服务,但几乎一半的客户没有多条线路。
-
许多列有三种不同的可能值。尽管您已了解到顶级类别的信息,但目前还不清楚不同值的分布情况。
-
我们数据集的标签
Churn的平衡性有些不足,约为No和Yes值的 5:2 比率。 -
所有列,包括数字列,都有 7,043 个元素。可能存在其他类似于您发现的
TotalCharges的缺失值,但没有任何空值。
探索分类列的组合
正如您在第六章中所见,查看不同特征之间的交互通常有助于了解哪些特征最重要以及它们之间的相互作用。然而,在该项目中,您的大多数特征是分类的而不是数值的。在这种情况下,通过查看跨多列的不同特征值组合的分布来探索理解特征交互的方法。
首先查看 PhoneService 和 MultipleLines 列。常识告诉我们,如果客户没有电话服务,他们就不能拥有多条电话线。通过使用 value_counts() 方法,您可以在数据集中确认这一点。value_counts() 方法接受一个数据框中的列列表作为参数,并返回唯一值组合的计数。在新单元格中输入以下代码并执行以返回跨 PhoneService 和 MultipleLines 列的唯一值组合:
df_2.value_counts(['PhoneService','MultipleLines'])
您的结果应与以下结果相同。请注意,MultipleLines 有三种不同的值,No、Yes 和 No phone service。毫不奇怪,当 PhoneService 特征的值为 No 时,No phone service 只出现一次。这意味着 MultipleLines 特征包含了 PhoneService 特征的所有信息。PhoneService 是多余的,您将稍后从训练数据集中删除此特征。
PhoneService MultipleLines
Yes No 3390
Yes 2971
No No phone service 682
数据集中的其他特征是否以类似的方式“相关”?毫不奇怪,确实如此。作为练习,在新单元格中编写代码来探索 InternetService、OnlineSecurity、OnlineBackup、StreamingTV 和 StreamingMovies 之间的关系。
再次出现一些特征值之间的冗余,但在这种情况下并不那么明显。当InternetService的值为No时,所有其他列的值都为No internet service。然而,存在两种不同的互联网类型,即Fiber optic和DSL,在这些情况下图片不那么清晰,不清楚是否存在冗余。虽然您在这里没有包括这些列,但DeviceProtection和TechSupport列与InternetService具有相同的关系。您应该自己探索这一点。在转换特征时,您将考虑如何考虑这些信息。
注意
除了查看特定值组合的计数之外,还存在用于理解分类特征不同值之间关联的技术。其中两个例子是卡方检验和Cramer's V 系数。卡方检验检查因变量和独立分类变量之间的独立性,而Cramer's V 系数确定了该关系的强度,类似于数值变量的 Pearson 相关系数。有关更多细节,您可以参考几乎任何统计书籍,比如 Sarah Boslaugh 的Statistics in a Nutshell(O’Reilly, 2012)。
您还应探索分类特征与标签Churn之间的关系。例如,考虑Contract特征。此特征有三个可能的值:Month-to-month、One year和Two year。您对此特征与Churn的关系有何直觉?您可以合理地期望较长的合同期限会降低流失率,至少如果客户不是处于合同期末的话。您可以像之前一样使用value_counts()方法来查看这种关系,但通常更容易通过视觉理解关系而不是查看数值表格。要可视化这一点,请将以下代码写入一个新的单元格并执行该单元格:
(df_2.groupby('Contract')['Churn'].value_counts(normalize=True)
.unstack('Churn')
.plot.bar(stacked=True))
这实际上是一行非常长的代码要进行解析。括号在开头和结尾告诉 Python 将其视为一行代码而不是三行独立的代码。首先使用 groupby() 函数按不同的 Contract 值分组。你想要查看 Churn 的关系,所以选择 Churn 列,然后应用 value_counts 函数。注意附加的 normalize=True 参数,它将每对值的计数替换为百分比而不是数字。这样做的好处是,你可以看到在每个 Contract 值中,多少客户流失相比于没有流失的客户,而不是比较不均匀组中的计数。在使用内置的 Pandas 绘图功能之前,使用 unstack() 函数将表格格式化为更易读的格式。在这种情况下,图 7-5 使用堆叠条形图快速比较不同 Contract 值。
你可以看到按月付合同的客户流失率比按年付或两年付合同的要高。从可视化中可以看出,超过 40% 的按月付合同客户取消了他们的服务,而按年付约为 15%,按两年付则不到 5%。这意味着合同类型几乎肯定会成为未来的一个有用特征。

图 7-5. 基于合同类型,显示离开电信公司客户比例的可视化。
作为练习,对你的其他分类特征进行类似的分析。注意哪些特征在不同值之间有不同的流失百分比,哪些则相对更为相似。这将有助于稍后选择特征。
当在 Python 中多次执行类似的代码块时,创建一个函数来执行可能更有效。例如,你可以通过以下代码创建一个函数来生成上面的分布图:
def plot_cat_feature_dist(feature_name):
(df_2.groupby(feature_name)['Churn'].value_counts(normalize=True)
.unstack('Churn')
.plot.bar(stacked=True))
def 是 Python 中定义函数的关键字,函数名为 plot_cat_feature_dist,而 feature_name 则是输入变量。这样,plot_cat_feature_dist('Contract') 将生成与 图 7-5 相同的图表。你随后可以对所有的分类变量使用此函数。
在探索分类特征时,你应该注意到以下一些观察结果:
-
针对老年人与非老年人,流失率大约是两倍。
-
gender、StreamingTV和StreamingMovies特征的值似乎对流失率没有影响。 -
家庭规模越大,流失率越低。换句话说,家庭中有伴侣或者依赖者会降低流失率。
-
对于拥有电话线的用户来说,拥有多条电话线会增加流失率。
-
InternetService特征会影响流失率。光纤互联网服务的流失率远高于 DSL。没有互联网服务的用户流失率最低。 -
互联网附加服务(如
OnlineSecurity和DeviceProtection)会降低流失率。 -
PaperlessBilling会增加流失率。PaymentMethod的大多数值相同,除了Electronic Check,其流失率远高于其他方式。
你有没有注意到其他什么?记得做好这些观察以备后用。
探索数值和分类列之间的交互作用。
在最终思考如何转换特征之前,你还应该探索数值特征与标签之间的关系。记住SeniorCitizen实际上是一个分类列,因为两个值表示两个离散类别。剩下的数值列是tenure、MonthlyCharges和TotalCharges。如果客户每个月支付相同金额,这些列将有一个简单的关系。也就是说,tenure × MonthlyCharges = TotalCharges。在tenure为 0 的情况下,你显式地看到了这一点。
这是多么频繁的情况呢?直觉上,也许是从经验中得知,月费用通常随时间变化。这可能是由于促销价格的结束,也可能是由于例如改变你支付的服务等原因。你可以使用 Pandas 函数来验证这种直觉。将以下代码写入新的单元格并执行该单元格以查看比较tenure × MonthlyCharges和TotalCharges的新列的摘要统计信息:
df_2['AvgMonthlyCharge'] = df_2['TotalCharges']/df_2['tenure']
df_2['DiffCharges'] = df_2['MonthlyCharges']-df_2['AvgMonthlyCharge']
df_2['DiffCharges'].describe()
请注意,在你的 DataFrame df_2中创建了两个新列。AvgMonthlyCharge列记录客户在使用期间的平均月费用,DiffCharges列记录平均月费用与当前月费用之间的差异。结果如下所示:
count 7032.000000
mean -0.001215
std 2.616165
min -18.900000
25% -1.160179
50% 0.000000
75% 1.147775
max 19.125000
Name: DiffCharges, dtype: float64
从这些摘要统计中,你应该注意几点:首先,注意到计数比总行数少了 11 行。为什么呢?回想一下,你有 11 行的tenure值为零。在 Pandas 中,如果除以零,该值记录为NaN而不是抛出错误。另外,注意到分布似乎相当对称。平均值几乎为零,中位数为 0,最小值和最大值几乎互为相反数。
删除NaN值的一种方法是使用replace()方法替换未定义的值。在新的单元格中使用以下代码执行此任务:
df_2['AvgMonthlyCharge'] = (df_2['TotalCharges'].div(df_2['tenure'])
.replace(np.nan,0))
df_2['DiffCharges'] = df_2['MonthlyCharges']-df_2['AvgMonthlyCharge']
df_2['DiffCharges'].describe()
将空值替换为零值的选择是填充策略的一个示例。填充 的过程是用在问题处理上合理的替代值来替换未知值。因为你希望查看月费和平均月费之间的差异,说“没有差异”是一个合理的方法,以避免必须丢弃可能有用的数据。如果不进行填充,你将丢失所有tenure为零的行,因此你的模型将无法准确预测这些情况。对于足够大的数据集,如果缺失数据并不集中在一个单一组中,则通常会采取删除数据的策略。这是第 6 章中采取的方法。
DiffCharges的值如何与Churn列相关联?你用于理解分类列之间关系的方法在这里不太适用,因为DiffCharges是数值型的。但是你可以将DiffCharges的值分桶化,并使用之前使用的方法。分桶 的思想是将数值列分成称为桶的值范围。在 Pandas 中,你可以使用cut()函数为数值列定义桶。你可以提供要使用的桶的数量,或指定一个切分点列表。要对DiffCharges列进行分桶并探索其对Churn的影响,请将以下代码键入到新的单元格中并执行该单元格:
df_2['DiffBuckets'] = pd.cut(df_2['DiffCharges'], bins=5)
plot_cat_feature_dist('DiffBuckets')
结果图(见图 7-6)显示,MonthlyCharges与AvgMonthlyCharge之间的差异(无论是正还是负)越大,相应数值范围内的流失率就越高。作为练习,尝试不同数量的箱体,并观察你能注意到的模式。

图 7-6. DiffCharges列每个桶的流失率。
每个桶的流失率并不会呈现出一个漂亮的线性趋势。也就是说,流失率在桶离中心越远时先下降,然后再上升。在这种情况下,将桶成员视为一个分类变量对机器学习而言可能比保持其作为数值特征更有优势。
你还可以探索数值特征而不进行任何操作。例如,我们可以使用以下代码探索MonthlyCharges和Churn之间的关系。关系可在图 7-7 中可视化:
df_2['MonthlyBuckets'] = pd.cut(df_2['MonthlyCharges'], bins=3)
plot_cat_feature_dist('MonthlyBuckets')

图 7-7. 对于 MonthlyCharges 每个桶的客户流失率。随着每月费用的增加,流失率也在增加,85.25 到 118.75 之间的费用具有最高的流失率。
在 图 7-7 中,您可以看到随着 MonthlyCharges 值的增加,流失率倾向于增加。这意味着 MonthlyCharges 列对预测流失将是有用的。
警告
找到要用于数值列的正确桶数量可能有些棘手。桶太少可能会错过重要的模式,但桶太多可能会导致模式变得非常嘈杂甚至误导性。图 7-8 展示了一个例子,即当桶数过多时,会导致噪声模式,难以获取见解。同时注意,每个桶的值范围相当小,因此每个桶中捕获的客户数量较少。

图 7-8. 对于 MonthlyCharges 每个桶的客户流失率,桶太多。模式在噪声中丢失,难以从这种可视化中理解关系。
作为一个练习,自行分析 tenure 和 TotalCharges 列。您应该会看到随着 tenure 和 TotalCharges 的增加,流失率减少。由于较长的任期应该会导致在任期内支付的费用增加,所以这两列与流失之间有类似的关系是合理的。使用之前章节的代码,检查这两个特征之间的相关性,确保它们确实高度相关,相关系数约为 0.82。
使用 Pandas 和 Scikit-Learn 转换特征
到此为止,您已经探索了数据集中不同的列,它们之间的相互作用,特别是它们与标签的交互作用。现在,您将准备好这些数据以供自定义模型使用。首先,您将选择用于训练机器学习模型的列。之后,您将转换这些特征,使其更适合训练。请记住,您的特征必须是具有有意义的大小的数值。在选择此项目的特征时,您将考虑到这一点。
特征选择
前一节探索了客户流失数据集中不同特征与客户流失列Churn之间的交互作用。您发现一些特征要么不具有预测性——即不同的值不影响流失率——要么与其他特征冗余。您应该复制您的 DataFrame df_2,然后移除您不打算用于训练模型的列。为什么要复制?如果从df_2中移除列,那么您可能需要重新查看创建该 DataFrame 的代码,以便再次访问该数据。虽然没有明确说明,但这就是为什么 DataFrame df_2被创建而不是修改原始 DataFrame df_raw的原因。通过在 DataFrame 的副本中删除列,您可以保留原始数据以便在需要时再次访问。
在前一节中,您发现gender、StreamingTV和StreamingMovies列对Churn标签没有预测性。此外,您还发现PhoneLine特征是冗余的,并包含在MultipleLines特征中,因此您也希望将其移除,以避免与共线性相关的问题。在第六章中,您了解到当预测变量之间存在高相关性时会发生共线性,导致回归系数的估计不可靠和不稳定。在使用复杂模型类型以上时,这些问题会被放大。对抗这些问题的一种方法是只使用一组共线性列中的一列。
在 Pandas DataFrame 中删除列的最简单方法是使用drop()函数。将以下代码输入到新单元格中并执行,以创建 Pandas DataFrame 的副本并丢弃您不再需要的列:
df_3 = df_2.copy()
df_3 = df_3.drop(columns=['gender','StreamingTV',
'StreamingMovies','PhoneService'])
df_3.columns
要检查哪些列保留,包含了df_3.columns这一行。具体输出会因您之前的探索而异,但例如,您可能看到如下输出:
Index(['customerID', 'SeniorCitizen', 'Partner', 'Dependents',
'tenure', 'MultipleLines', 'InternetService', 'OnlineSecurity',
'OnlineBackup','DeviceProtection', 'TechSupport', 'Contract',
'PaperlessBilling', 'PaymentMethod', 'MonthlyCharges',
'TotalCharges', 'Churn', 'AvgMonthlyCharge', 'DiffCharges',
'DiffBuckets', 'MonthlyBuckets', 'TenureBuckets,
'TotalBuckets'], dtype='object')
对于这里显示的 DataFrame 列,已添加了AvgMonthlyCharge、DiffCharges、DiffBuckets、MonthlyBuckets、TotalBuckets和TenureBuckets。您发现DiffBuckets特征将是一个有用的特征,并且tenure特征与TotalCharges特征高度相关。为了避免共线性问题,删除TotalCharges特征以及除了DiffBuckets之外的所有其他添加的特征。执行此操作所需的代码可能与下面的代码不同,具体取决于您执行的探索:
df_3 =df_3.drop(columns=['TotalCharges','AvgMonthlyCharge',
'DiffCharges','MonthlyBuckets',
'TenureBuckets', 'TotalBuckets'])
最后,customerID 列怎么样?此列过于精细化,对预测模型没有任何用处。为什么呢?请记住,customerID 列唯一标识每一行。您会冒险使模型学习将此特征的值与 Churn 的值直接关联,特别是在接下来的转换之后。这对于您的训练数据集很好,但一旦您的模型第一次看到 customerID 的新值,它将无法以有意义的方式使用该值。因此,在训练模型时最好删除此列。作为练习,编写代码将 customerID 列删除到一个新的单元格,并执行该单元格以删除该列。这里是解决方案代码,但请尽量完成此任务而不查看它:
df_3 = df_3.drop(columns=['customerID'])
df_3.dtypes
最后,您将得到 15 个特征列和 1 个标签列 Churn。最后一行 df_3.dtypes 的输出在此提供作为参考:
SeniorCitizen int64
Partner object
Dependents object
tenure int64
MultipleLines object
InternetService object
OnlineSecurity object
OnlineBackup object
DeviceProtection object
TechSupport object
Contract object
PaperlessBilling object
PaymentMethod object
MonthlyCharges float64
Churn object
DiffBuckets category
DiffBuckets 是一个 category 列,而不是一个 object 列。这是因为桶化过程包括附加信息,即表示桶的间隔。
使用 scikit-learn 对分类特征进行编码
在开始训练过程之前,您需要将分类特征编码为数值特征。SeniorCitizen 就是一个很好的例子。不再使用 Yes 和 No 值,而是分别编码为 1 或 0。实质上,这就是您将来将要为您的特征执行的操作,使用 scikit-learn。
首先,请注意,许多分类特征都是二进制特征。Partner、Dependents、OnlineSecurity、OnlineBackup、DeviceProtection、TechSupport 和 PaperlessBilling 都是二进制特征。请注意,对于 OnlineSecurity、OnlineBackup、DeviceProtection 和 TechSupport,这并不严格适用,但 No internet service 的值已经由 InternetService 捕获。在编码您的特征之前,请使用以下代码将不同列中所有 No internet service 值替换为 No:
df_prep = df_3.replace('No internet service', 'No')
df_prep[['OnlineSecurity', 'OnlineBackup',
'DeviceProtection', 'TechSupport']].nunique()
nunique() 方法计算每列的唯一值数量。您应该看到此单元格的输出中,OnlineSecurity、OnlineBackup、DeviceProtection 和 TechSupport 列有两个唯一值,分别对应 No 和 Yes。您将保留此 DataFrame df_prep,以便稍后进行任何额外的特征工程。
现在,您已经准备好执行独热编码了。独热编码是将具有独立值的分类特征转换为数字表示的过程。这个表示是一个整数列表,每个可能的特征值对应一个整数。例如,InternetService 特征有三个可能的值:No、DSL 和 Fiber Optic。这些值的独热编码分别是 [1,0,0]、[0,1,0] 和 [0,0,1]。另一种思考方式是,我们为每个特征值创建了一个新的特征列。也就是说,第一列询问,“InternetService 的值是否等于 No?”如果是,值为 1,否则为 0。其他两列分别对应相同的问题,但针对 DSL 和 Fiber Optic 的值。通过这种独热编码的思路,通常像 Partner 这样只有两个值 No 和 Yes 的特征将被编码为 0 和 1,而不是 [1,0] 和 [0,1]。
Scikit-learn 包含一个预处理库,专门为您的特征和标签提供转换器。要将分类特征转换为独热编码特征,您将使用 scikit-learn 中的 OneHotEncoder 类。以下代码展示了如何在这个示例中使用 OneHotEncoder 对分类列进行独热编码:
from sklearn.preprocessing import OneHotEncoder
numeric_columns = ['SeniorCitizen', 'tenure', 'MonthlyCharges']
categorical_columns = ['Partner', 'Dependents', 'MultipleLines',
'InternetService','OnlineSecurity',
'OnlineBackup','DeviceProtection',
'TechSupport','Contract','PaperlessBilling',
'PaymentMethod','DiffBuckets']
X_num = df_prep[numeric_columns]
X_cat = df_prep[categorical_columns]
ohe = OneHotEncoder(drop='if_binary')
X_cat_trans = ohe.fit_transform(X_cat)
在继续之前理解这段代码值得一提。首先,通过 from sklearn.preprocessing import OneHotEncoder 从 scikit-learn 中导入 OneHotEncoder 类。接下来,将列分为数值和分类列。由于 SeniorCitizen 已经被编码,您可以简单地将其包含在数值列中。在此之后,代码的下两行将 DataFrame 拆分为两个单独的 DataFrame:X_num 用于数值特征,X_cat 用于分类特征。
最后,您可以开始使用 scikit-learn 的OneHotEncoder。首先,通过以下代码创建一个独热编码器:ohe = OneHotEncoder(drop='if_binary')。参数drop='if_binary'将会把二元特征值替换为 0 或 1,而不是返回完整的独热编码。
最后一行是实际进行转换的地方。fit_transform 函数执行两个不同的操作。fit_transform 中的 fit 部分指的是 OneHotEncoder 学习不同特征的不同值及其独热编码值的分配。这很重要,因为有时您可能需要反向过程,回到原始值。例如,在进行预测后,您想知道客户使用的支付方式。您可以使用 OneHotEncoder 的 inverse_transform() 方法将编码后的数字输入转换回原始输入。例如,考虑在不同单元格中运行以下两行代码:
X_cat_trans.toarray()[0]
ohe.inverse_transform(X_cat_trans.toarray())[0]
第一行返回以下输出:
[1., 0., 0., 1., 0., 1., 0., 0., 0., 1., 0., 0., 1., 0., 0., 1., 0., 0., 1.,
0., 0., 0., 1., 0., 0., 0.]
第二行返回以下输出:
Partner Yes
Dependents No
MultipleLines No phone service
InternetService DSL
OnlineSecurity No
OnlineBackup Yes
DeviceProtection No
TechSupport No
Contract Month-to-month
PaperlessBilling Yes
PaymentMethod Electronic check
DiffBuckets (-3.69, 3.915]
一旦OneHotEncoder已经适合数据,您可以使用transform()和inverse_transform()在原始值和编码值之间来回切换。
最后,您需要将数值特征和编码的分类特征组合回一个单一对象中。一次热编码的分类特征返回为 NumPy 数组,因此您需要将 Pandas DataFrame 转换为 NumPy 数组,并将数组连接成一个单一数组。此外,您还需要为标签Churn创建一个 NumPy 数组。为此,请在新的单元格中执行以下代码:
X = np.concatenate((X_num.values,X_cat_trans.toarray()), axis=1)
y = df_prep['Churn'].values
NumPy 中的concatenate()函数接受两个数组并返回一个单一的数组。X_num是一个 Pandas DataFrame,但是 Pandas DataFrame 的实际值存储为 NumPy 数组。您可以通过查看 DataFrame 的values属性来访问这个数组。X_cat_trans是一种特殊类型的 NumPy 数组,称为稀疏数组。稀疏数组,即大多数条目为 0,可能很好,因为有许多聪明的优化可以用来更有效地存储它们。但是,您需要实际的相应数组。您可以使用toarray()方法来访问它。最后,您希望“水平”连接 DataFrame,即将列并排组合在一起,因此您需要指定额外的参数axis=1。类似地,axis=0对应于“垂直”堆叠数组,即将一行列表附加到另一个上。
泛化和数据分割
在准备好数据集之后,您终于可以开始训练模型了,对吗?不完全是。您需要进行训练-测试数据分割,以确保能够正确评估您的模型。
Scikit-learn 提供了一个很好的辅助函数来完成这个任务,称为train_test_split,在model_selection模块中。尽管在 scikit-learn 和其他自定义训练框架中,分割数据集用于训练和评估是你自己需要管理的事情,但大多数(如果不是全部)框架都提供了工具来简化这一过程。
要将数据集分割为训练集和测试集,请在新的单元格中执行以下代码:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X,y,test_size=0.20,
random_state=113)
X_train.shape
第一行从 scikit-learn 的model_selection库导入train_test_split函数。第二行是分割发生的地方。train_test_split接受您要分割的数组列表(这里是您的X和y)和test_size以指定训练数据集的大小作为百分比。您还可以提供一个random_state,以便执行此代码的任何其他人得到相同的训练和测试数据集行。最后,您可以通过X_train.shape看到训练数据集的最终大小。这是一个形状为(5634, 29)的数组。也就是说,经过一次热编码后,训练数据集中有 5,634 个示例和 29 个特征。这意味着测试数据集有 1,409 个示例。
使用 Scikit-Learn 构建逻辑回归模型
准备好训练和测试数据集后,你就可以开始训练你的机器学习模型了。本节介绍了你将要训练的模型类型——逻辑回归,以及如何在 scikit-learn 中开始训练模型。接下来,你将学习不同的方法来评估和改进你的分类模型。最后,你将了解 scikit-learn 中的pipelines,这是一种将你在数据集上执行的不同转换和你想要使用的训练算法整合在一起的方式。
逻辑回归
逻辑回归模型的目标是预测属于两个离散类别中的一个的成员资格。为了简单起见,将这两个类别分别表示为和。你经常会把其中一个类别视为“正”类,另一个类别视为“负”类。像线性回归一样,输入是一组数值,但输出是给定特征列表时正类的预测概率。。在的情况下,你希望这个概率尽可能接近 1,而在的情况下,你希望这个概率尽可能接近 0。
如何计算这个概率?回想一下,对于线性回归,你使用了模型,其中是一些权重。对于逻辑回归,方程式类似:
乍一看,这个公式可能看起来很可怕,但值得解析。是两个函数的组合:所谓的logit和sigmoid(或 logistic)函数:
Sigmoid 函数(及其变体)出现在许多不同的领域中,包括生态学、化学、经济学、统计学,当然还有机器学习。Sigmoid 函数具有一些很好的特性,使其在分类模型中非常有吸引力。首先,其值范围在 0 到 1 之间,可以将输出解释为概率。在技术术语中,这是累积分布函数的一个例子,因为值随着独立变量的增加而始终增加。其次,函数的导数(在任意给定时刻的变化率)易于计算。这对于梯度下降至关重要,使得训练这样的模型成为一个非常合理的过程。逻辑函数的图形可以在图 7-9 中看到。

图 7-9. 逻辑函数的图形。注意其值范围在 0 到 1 之间。
在训练线性回归模型时,请记住,您使用的目标是最小化均方误差。对于逻辑回归,您使用的是一种称为交叉熵的不同损失函数。交叉熵损失函数的定义如下:
这里,总和是在数据集 中的所有示例上。参数 在这里被包含,提醒交叉熵取决于用于计算的数据集,就像评估的模型一样。无论是 还是 都将为零,因此每个示例的总和中只有一个项是非零的。相应的术语 或 将会对损失函数作出贡献。如果模型对最终的正确答案有 100%的信心,那么这个项将为零。如果不是,则随着信心的减少,对损失函数的贡献值会呈指数增长。
这里有一个具体的例子:假设 = 0.6,而 。换句话说,模型给出标签为 1 的概率为 60%。这如何影响损失函数?对于这个单项来说,唯一的贡献来自于 。log(0.6) 大约等于 –0.222。在损失函数的计算中,由于前面的负号,符号最终被反转。然而,如果 = 0.4,则 log(0.4) 大约等于 –0.398。在这种情况下,预测概率 离 1 越远,对损失函数的贡献就越大。
注意
交叉熵最初是信息论领域的一个概念。粗略地说,交叉熵测量了在假设一个概率分布时表示事件所需的信息量,但实际的概率分布是不同的。在逻辑回归的情况下,假设的概率分布来自模型的输出,而实际的分布由我们的标签给出。
在 Scikit-Learn 中训练和评估模型
你已经准备好了数据,并确定了一种模型类型——逻辑回归,你希望用它来预测客户流失。现在你已经准备好使用 scikit-learn 来训练你的第一个模型了。在 scikit-learn 中,创建和训练模型的过程非常简单。首先,你创建你想要训练的模型类型的模型,然后你训练这个模型。要构建和训练一个逻辑回归模型,将以下代码键入一个新单元格并执行该单元格:
from sklearn.linear_model import LogisticRegression
cls = LogisticRegression()
cls.fit(X_train, y_train)
执行完这个单元格后,你可能会看到以下或类似的消息:
/usr/local/lib/python3.10/dist-packages/sklearn/linear_model/_logistic.py:814:
ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.
Increase the number of iterations (max_iter) or scale the data as shown in:
https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
n_iter_i = _check_optimize_result(
LogisticRegression()
发生了什么问题?默认情况下,scikit-learn 将执行梯度下降来尝试找到模型的最佳权重,用于一定数量的迭代。在这个上下文中,迭代意味着在整个数据集上计算梯度下降。一旦我们的损失函数——交叉熵——在迭代之间的改善变得非常小(默认情况下,),训练过程就会终止。在你的情况下,你的模型从未达到这个阈值。在实践中,这意味着模型仍有改进的空间,可以通过更长时间的训练来实现。
然而,造成这种问题的原因是什么呢?有几个不同的原因,但其中一个最基本的问题之一与特征范围有关。如果特征值的范围差异很大,梯度下降往往需要更多的迭代才能收敛到最佳解决方案。
一种思考这个问题的方式是考虑一个滚球的类比。首先,假设你有一个完全圆形的碗,意味着顶部是一个完美的圆圈。如果你把一个球放在碗的边缘并给它一个小推动,它会立即滚到碗底的最低点。那么如果碗更像是椭圆或椭圆形呢?如果你给球一个推动,它可能不会直接朝向中心滚动,而是沿途来回振荡。本质上,这正是梯度下降的工作原理。曲率,也就是“碗”距离完全平坦的程度,影响了梯度下降的行为。当曲率是常数时——例如,当碗是完全圆形时——我们就有了之前讨论过的良好行为。然而,当曲率不是常数时,例如特征值具有不同的比例时,我们就会面临之前讨论过的摇摆行为。
如何解决这个问题?一个方法是简单地增加模型的迭代次数。在 scikit-learn 中,你可以通过提供可选的 max_iter 参数来轻松实现这一点。对于较小的数据集,这是一个不错的方法,但一旦开始处理更大的数据集,这种方法就不再可行。更好的方法是将特征重新缩放到标准范围内,例如 0 到 1 之间。在 scikit-learn 中,你可以使用 MinMaxScaler() 转换器来完成这一点。在新的代码单元格中输入以下代码并执行,以重新缩放你的训练数据集,然后训练一个新的逻辑回归模型:
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler()
X_train_scaled = scaler.fit_transform(X_train)
cls = LogisticRegression()
cls.fit(X_train_scaled, y_train)
这次你的模型应该成功收敛了。与其让模型花费更多计算时间来收敛,你可以简单地重新缩放数据,从而使训练过程更高效。这是训练机器学习模型的一般最佳实践,并不仅限于使用 scikit-learn 或训练逻辑回归模型时。
模型训练完成后,下一步是评估模型。在 scikit-learn 中,你可以使用训练好的模型的 score 方法来评估测试数据集的表现。score 方法的输出是模型在测试数据集上的平均准确率,表示为小数。执行新的代码单元格并使用 cls.score(X_test, y_test) 来计算模型在测试数据集上的准确率。
你的模型准确率可能大约在 48%左右,尽管具体数值可能会因随机状态而略有不同。这似乎不是一个好模型,因为你可能通过随机抛硬币得到略微更好的结果。但你可能已经注意到这里的问题。在训练模型之前,你将训练特征缩放到 0 到 1 之间。但是你没有对测试数据集执行相同的转换。由于模型在评估时预期的值范围与实际呈现的值范围不同,模型表现不佳。这是 训练-服务偏差 的典型例子。
你希望对测试数据集执行与训练数据集相同的缩放。你之前使用了 fit_transform() 方法,以便 MinMaxScaler() 学习每个特征的最小值和最大值,并根据此范围将值缩放到 0 到 1 之间。你不希望重新拟合转换器,但可以使用相同的转换器来对测试数据集进行转换,以便进行评估。通过以下代码完成这一步,然后比较性能差异:
X_test_scaled = scaler.transform(X_test)
cls.score(X_test_scaled, y_test)
您的模型的准确率现在应该在 80%左右。这比您在未对评估数据集进行缩放时获得的准确率要高得多。从这个例子中要得出的重要结论是,您必须在训练和评估时执行相同的转换,以确保获得准确的结果。在预测时也是如此。重要的是要记录您对数据进行的转换,并利用像 scikit-learn 中的管道这样的工具,以确保这些转换被一致地应用。
分类评估指标
准确率是用于分类模型的一种简单易懂的指标。然而,仅使用准确率作为指标可能存在一些问题。您的模型准确率约为 80%,但只预测没有客户流失的模型准确率约为 73.5%。80%比 73.5%要好,但预测没有流失的模型将比预测可能有更高商业价值的其他模型具有显著较低的商业价值。如果您的目标是预测客户流失并尝试防止客户离开,那么“无流失”模型永远不会提供任何有价值的见解,实际上没有价值。
正如您在第五章中学到的,您可以结合准确率、召回率和精确率等指标,更清晰地了解您模型的性能。简而言之,召回率可以被视为真正例率。在这个客户流失的例子中,召回率表示模型正确预测了取消账户的客户所占的百分比。精确率可以被视为模型的正预测能力。在客户流失问题中,精确率表示模型预测的取消账户中实际上确实取消账户的百分比。如果您的目标是积极联系可能取消账户的客户,那么召回率将是一个非常重要的指标。您可能愿意牺牲一点准确性或精确率来提高召回率。当然,这仍然是需要平衡的,因为联系未计划取消账户的客户也是需要资源成本的。
以预测没有客户流失的模型为例。该模型的召回率为 0,因为未能正确识别取消账户的任何客户。然而,您预期训练过的模型的召回率会更高。
记住,混淆矩阵根据预测类别和实际类别将预测分解成表格。对此的提醒显示在表格 7-8 中。
表格 7-8. 一般问题的混淆矩阵
| 预测正例 | 预测负例 | |
|---|---|---|
| 实际正例 | 真正例(TP) | 假负例(FN) |
| 实际负样本 | 假阳性(FP) | 真负样本(TN) |
您训练的模型的混淆矩阵很容易使用 Scikit-Learn 进行计算。为此,您可以使用sklearn.metrics库中的confusion_matrix函数。在新的单元格中键入以下代码并执行,以计算您模型的混淆矩阵:
from sklearn.metrics import confusion_matrix
y_pred = cls.predict(X_test_scaled)
confusion_matrix(y_test, y_pred, labels=['Yes','No'])
confusion_matrix在此处使用了三个参数。第一个参数是来自您的测试数据集y_test的实际标签。第二个是您模型的预测标签。要计算这些预测,您可以使用您的模型的predict方法,传入转换后的测试输入X_test_scaled。最后一个参数是可选的,但很有用。labels参数期望一个标签值列表,这里是'Yes'和'No'。这确定了混淆矩阵中标签的顺序。您的混淆矩阵应该类似于以下内容:
array([[187, 185],
[ 98, 939]])
这是什么意思?在您的测试数据集中,有 187 + 185 = 372 名顾客取消了他们的服务,而 98 + 939 = 1,037 名顾客保留了他们的服务。您的模型正确预测了 187 名顾客会取消(真正例),但错过了 185 名取消的顾客(假阴性)。您的模型还正确预测了 939 名顾客会保留他们的服务(真负例),但预测 98 名实际保留服务的顾客会取消(假阳性)。
由此,您可以通过两种不同的方式计算精确度和召回率。您可以通过定义使用混淆矩阵中的信息来计算它们,或者您可以利用 Scikit-Learn 中的precision_score和recall_score函数。使用以下代码来按照第二种方法计算您模型的精确度和召回率:
from sklearn.metrics import precision_score, recall_score
print("Precision:", precision_score(y_test, y_pred,
labels=['Yes','No'], pos_label='Yes'))
print("Recall:", recall_score(y_test, y_pred,
labels=['Yes','No'], pos_label='Yes'))
在探索结果之前,请注意该代码的几点。前三个参数与confusion_matrix函数中的相同。请注意,如果您不使用索引标签(如 0 和 1),则precision_score和recall_score需要labels参数。您还包括了一个额外的参数pos_label,它定义了“正类”。由于召回率和精确度是与“正类”相关的度量,因此您需要定义哪个类应被视为正类。您的结果应该类似于以下内容:
Precision:0.656140350877193
Recall:0.5026881720430108
换句话说,你的模型预测将要取消账户的顾客中,实际取消的顾客有 65.6%。另一方面,你的模型只捕获了实际取消的顾客中的 50.3%。你的模型仍有改进的空间,但显然比不能检测到任何客户流失的模型带来更多的业务价值。
在 Scikit-Learn 中使用训练模型提供预测
在前一节中,你看到了如何使用predict方法提供预测,以便使用不同的度量评估模型。在这个过程中你自然会遇到一个问题,即训练与服务之间的偏差,即在你的情况下,预测时的数据尚未转换,导致结果不准确。训练与服务之间的偏差可能以不同的方式出现。
另一个常见问题是用于预测的传入数据格式可能与训练使用的数据不同。在这种情况下,你的训练数据是 CSV 格式,但是预测的传入数据可能是 JSON 格式,这是 Web 应用中常用的数据交换格式。
当考虑如何在模型中提供预测时,重要的是回顾你所做的所有转换。将这些转换整合到一个单独的函数中,并在预测时与你的模型一起使用,会非常方便。
这里是你转换数据所采取的步骤:
-
清洗数据,确保
TotalCharges为浮点数。 -
创建一个新的
DiffBuckets特征。 -
删除
CustomerID、gender、StreamingTV、StreamingMovies、PhoneService和其他中间列。 -
对分类特征进行独热编码。
-
将数值特征缩放到 0 到 1 的范围内。
在提供预测时,你需要执行相同的步骤。现在,你将所有预处理代码汇总到一个单独的函数中,以便轻松地将其应用于新进入的数据。假设你想预测某个特定客户在月底是否会取消他们的账户。数据以 JSON 格式提供给你:
{"customerID": "7520-HQWJU", "gender": "Female", "SeniorCitizen": 0,
"Partner": "Yes", "Dependents": "Yes", "tenure": 66, "PhoneService": "Yes",
"MultipleLines": "Yes", "InternetService": "DSL", "OnlineSecurity": "Yes",
"OnlineBackup": "Yes", "DeviceProtection": "Yes", "TechSupport": "No",
"StreamingTV": "No", "StreamingMovies": "No", "Contract": "Month-to-month",
"PaperlessBilling": "Yes", "PaymentMethod": "Bank transfer (automatic)",
"MonthlyCharges": 67.45, "TotalCharges": "4508.65"}
你需要解析这些数据,执行之前列出的转换,并使用你训练好的模型提供预测。为了解析数据,你可以使用 Python 中内置的json包。该包有一个有用的函数,json.loads(),可以将 JSON 数据加载到 Python 字典中。从那里,你可以轻松地转换数据。输入以下代码,或者从solution notebook中复制粘贴代码,并执行该单元格:
import json
example = json.loads("""{"customerID": "7090-HPOJU", "gender": "Female",
"SeniorCitizen": 0, "Partner": "Yes", "Dependents": "Yes", "tenure": 66,
"PhoneService": "Yes", "MultipleLines": "Yes", "InternetService": "DSL",
"OnlineSecurity": "Yes", "OnlineBackup": "Yes", "DeviceProtection": "Yes",
"TechSupport": "No", "StreamingTV": "No", "StreamingMovies": "No",
"Contract": "Month-to-month", "PaperlessBilling": "Yes",
"PaymentMethod": "Bank transfer (automatic)", "MonthlyCharges": 67.45,
"TotalCharges": "4508.65"}""")
ex_df = pd.DataFrame([example])
ex_df['TotalCharges'] = ex_df['TotalCharges'].astype('float64')
ex_df = ex_df.drop(columns=['customerID','gender',
'StreamingTV','StreamingMovies',
'PhoneService'])
ex_df['AvgMonthlyCharge'] = ex_df['TotalCharges']/ex_df['tenure']
ex_df['DiffCharges'] = ex_df['MonthlyCharges']-ex_df['AvgMonthlyCharge']
ex_df['DiffBuckets'] = pd.cut(ex_df['DiffCharges'],
bins=[-18.938,-11.295,-3.69,3.915,11.52,19.125])
ex_df.pop('DiffCharges')
numeric_columns = ['SeniorCitizen', 'tenure', 'MonthlyCharges']
categorical_columns = ['Partner', 'Dependents', 'MultipleLines',
'InternetService','OnlineSecurity','OnlineBackup',
'DeviceProtection','TechSupport','Contract',
'PaperlessBilling','PaymentMethod','DiffBuckets']
X_num = df_prep[numeric_columns]
X_cat = df_prep[categorical_columns]
X_cat_trans = ohe.transform(X_cat)
X = np.concatenate((X_num.values,X_cat_trans.toarray()), axis=1)
X_scaled = scaler.transform(X)
cls.predict(X)
乍一看,这可能看起来是很多代码,但几乎所有这些内容你之前都已经处理过。第一部分可能是与以往最不同的部分。在那里,你使用json.loads()将 JSON 数据加载为字典。由于许多转换是在 Pandas DataFrame 中执行的,因此将传入数据加载到 Pandas DataFrame 中是方便的。之后,确保TotalCharges的类型为float64,并且删除你的模型不需要的列。接下来,你创建DiffBuckets特征。注意,对于pd.cut()函数,你指定的是分箱的端点而不是箱子的数量。切割点是依赖于数据的,你希望确保使用与训练时相同的桶。最后,在提供预测之前,你将分离出分类列以执行独热编码和最小-最大缩放。
在这种情况下,你会看到这位客户预计不会取消她的账户。如果你想让这段代码更容易运行,你可以创建一个函数来执行这段代码。以下是一个示例:
def custom_predict_routine(example):
# Insert the code from above, indented once
return cls.predict(X)
警告
在训练时,应仅使用诸如OneHotEncoder和MinMaxScaler的转换器的fit_transform方法。在预测时,应改用transform方法,以确保以与训练时相同的方式转换特征。
当你需要在与模型训练不同的环境中提供预测时,你需要不仅传输模型文件,还有任何预处理代码和转换器。你可以使用像joblib这样的包来存储对象,比如你训练好的模型cls。joblib中的dump方法可以序列化 Python 对象并将其保存到磁盘,之后可以使用load方法重新加载对象。例如:
import joblib
joblib.dump(cls, 'filename.joblib')
cls = joblib.load('filename.joblib')
这不仅可以用于模型,还可以用于正在使用的转换器和其他对象。
Scikit-Learn 中的管道:简介
本节深入讨论了 scikit-learn 中管道的更高级主题。在第一次阅读时,可以将本节视为可选内容,并在需要时返回以学习如何管理 scikit-learn 中的转换器。
当将各种不同的转换合并成一个函数时,你可能觉得这个过程有点乏味。然而,这是一个非常重要的步骤,以确保你能够避免训练-服务偏差。Scikit-learn 包含一种称为Pipeline的结构来简化这个过程。在 scikit-learn 中,Pipeline包含一个对象序列,其中所有对象都是转换器(如OneHotEncoder),除了最后一个对象,它是一个模型(如LinearRegression)。
然而,您的一些处理代码涉及并非直接涉及 scikit-learn 变压器的 Pandas 操作。如何将这些包含到 scikit-learn 管道中?您可以在这些情况下使用 scikit-learn 的 FunctionTransformer()。FunctionTransformer() 接受一个 Python 函数作为参数来定义变压器。当您在该变压器上调用 fit_transform() 或 transform() 时,它简单地将包含的函数应用于输入并返回该函数的输出。这是将 NumPy 或 Pandas 处理逻辑包含到 scikit-learn 管道中的好方法。
在您的情况下,管道会是什么样子?实质上,您执行了以下步骤:
-
将数据加载到 Pandas DataFrame 中
-
使用 Pandas 函数清理和准备数据
-
将要分别转换的分类和数值列进行拆分
-
对分类特征进行了一次独热编码并重新组合了特征
-
在训练模型之前对数据集进行了最小-最大缩放。
这里的步骤稍微重新组织,以使过渡到 scikit-learn Pipeline 更加无缝。第一步(加载 DataFrame)在这里不会改变。对于第二和第三步,您将使用 FunctionTransformer()。对于第四步,您将需要两个变压器:您之前熟悉的 OneHotEncoder() 和一个新的变压器,ColumnTransformer()。ColumnTransformer() 允许您在不同的列上应用不同的转换。这正是您的使用案例所需要的。OneHotEncoder() 将应用于分类列,而 MinMaxScaler() 将应用于数值列。
首先,将 Pandas 预处理逻辑合并为一个单独的函数:
def transform_fn(df):
df = df.replace({'TotalCharges': {' ': 0.0}})
df = df.astype({'TotalCharges':'float64'})
df['AvgMonthlyCharge']= df['TotalCharges'].div(df['tenure'],
fill_values=0.0)
df['DiffCharges'] = df['MonthlyCharges']-df['AvgMonthlyCharge']
df['DiffBuckets'] = pd.cut(df['DiffCharges'], bins=5)
df = df.drop(columns=['AvgMonthlyCharge', 'gender','StreamingTV',
'StreamingMovies','PhoneService',
'customerID', 'DiffCharges'])
return df
接下来,包括指定数值和分类列的代码:
numeric_columns = ['SeniorCitizen', 'tenure', 'MonthlyCharges']
categorical_columns = ['Partner', 'Dependents', 'MultipleLines',
'InternetService','OnlineSecurity',
'OnlineBackup', 'DeviceProtection',
'TechSupport','Contract',
'PaperlessBilling','PaymentMethod',
'DiffBuckets']
现在定义在管道中要使用的变压器和模型:
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import FunctionTransformer
fn_transformer = FunctionTransformer(transform_fn)
col_transformer = ColumnTransformer(
[('ohe', OneHotEncoder(drop='if_binary'), categorical_columns),
('sca', MinMaxScaler(), numeric_columns)])
model = LogisticRegression()
关于上述代码的一些说明。FunctionTransformer 和 ColumnTransformer 的 import 语句显示在前两行。FunctionTransformer 接受一个参数:包含转换逻辑的 Python 函数。由于您想要包含函数而不是调用函数,因此传入 transform_fn 而不是 transform_fn(df)。在 Python 中,函数是对象,因此我们可以将它们用作其他函数的输入,就像这里所看到的那样。
现在,要定义 Pipeline,请使用以下代码:
from sklearn.pipeline import Pipeline
pipe = Pipeline([('preproc', fn_transformer),
('col_trans', col_transformer),
('model', model)])
Pipeline 接受一个有序对的列表。每对的第一个元素是对象的名称(转换器或模型),第二个元素是转换器或模型本身。最终的 Pipeline 是打包所有代码的好方法,但除了更干净的代码之外,还有其他优势吗?
Pipeline 的 fit 方法会对所有的转换器调用 fit_transform,然后对模型调用 fit 方法。predict 方法会在调用模型的 predict 之前按顺序应用每个转换器的 transform 方法。本质上,你可以将 Pipeline 视为一个内置了所有转换的模型。
使用 Pipeline 的最终优势是可移植性。在运行 fit 方法后,可以使用 pickle 或 joblib 库导出 Pipeline。这个导出的 Pipeline 不仅包含训练模型的信息,还包含适合的转换器。这是将转换和模型一起传输到其他位置以保持预测一致性的好方法。
作为练习,完成重写模型代码以使用 Pipeline,使用 fit 方法训练模型,然后评估模型以计算其准确率、精确度和召回率。
使用 Keras 构建神经网络
你能够使用 scikit-learn 构建逻辑回归模型,并使用自定义代码训练你的第一个 ML 模型。在本节中,你将使用 Keras 构建另一种类型的模型,Keras 是一个易于构建自定义神经网络的框架,作为更大的 TensorFlow 软件开发工具包(SDK)的一部分。
请记住,神经网络由多个层组成,每一层有若干个神经元,并且每个神经元有对应的激活函数。在回归模型中,通常 ReLU 函数用作隐藏层的激活函数,最终输出层则不使用激活函数。对于分类模型,思想非常相似,但你需要将最终输出转换为正类的概率。在逻辑回归中,你使用 sigmoid 函数来实现这一点,在神经网络中的分类中它也将发挥同样的作用。
Keras 简介
TensorFlow 于 2015 年底推出,作为一个免费开源的 SDK 用于开发机器学习模型。TensorFlow 这个名字指的是张量和计算图的概念。张量 简单地说就是一个带有一定数量维度的数组,其中维度的数量称为张量的秩。例如,你可以将一行文本看作是单词或字符串的秩为 1 的张量。一整页文本则是一个秩为 2 的张量,因为它是一组行的数组。一本书可以是一个秩为 3 的张量,类推。张量在科学计算中是常见的数据结构,在许多不同的上下文中被使用。计算图 是 TensorFlow 为 CPU(或 GPU/TPU)构建的执行所需计算的一组指令。基于图的计算方法的优势包括可以在幕后应用的优化技术以及轻松地将计算分配到多个设备上的能力。
尽管 TensorFlow 具有所有这些优势,由于其采用的方法,最初版本的 TensorFlow 被认为难以学习。随着时间的推移,向 TensorFlow 添加了新的库和功能,使其更易于使用,对于那些新手来说更加友好。2019 年,TensorFlow 2.0 发布,引入了 Keras 作为构建、训练和提供人工神经网络预测的高级接口选择。Keras 最初开发为创建神经网络的 Python 接口,用于 Theano,一个定义、优化和高效评估涉及多维数组的数学表达式的 Python 库。Keras 扩展以支持 TensorFlow,并且在 TensorFlow 2.0 中,Keras 现在正式成为 TensorFlow 框架的一部分。Keras 易于使用,你可以仅用几行代码创建一个神经网络。
使用 Keras 训练神经网络分类器
由于数据已经使用 scikit-learn 和 Pandas 准备好了,你可以快速开始训练一个新的机器学习模型。你将使用 Dataset API 创建 TensorFlow 数据集用于训练和测试数据集,然后定义你的神经网络模型,最后训练和评估你的神经网络。你将能够重新调整你之前为 scikit-learn 模型编写的自定义函数,以为你的 Keras 模型提供预测服务。
在开始这个过程之前,需要进行一些额外的预处理。使用 scikit-learn 中的 LogisticRegression 模型,你可以构建一个二分类模型来预测 Yes 和 No 两个类别。在 Keras 中,你必须使用数值标签 1 和 0 而不是之前使用的字符串标签。幸运的是,scikit-learn 包含一个 LabelEncoder 转换器来执行这个任务。使用以下代码将你的标签 Yes 和 No 编码为 1 和 0:
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y_train_enc = le.fit_transform(y_train)
y_test_enc = le.transform(y_test)
le.inverse_transform([1])
在输出中,你会看到Yes被视为正类,或者1。请注意,你也可以通过在集合[“No”,”Yes”]上拟合转换器,然后简单地转换y_train和y_test来确保标签的顺序。
现在,标签已经正确编码,接下来创建 TensorFlow 数据集。tf.data.Dataset API 允许你创建数据摄入流水线,以便在训练模型时流入数据。由于数据是以一批次一次流入的,可以将数据分布在多台机器上。这使得你可以在多台不同的机器上对可能是大规模数据集的大型模型进行训练。在你的情况下,数据集确实适合内存,但是使用此 API 可以更轻松地改变规模,而无需改变你的训练代码。Dataset API 使用起来很简单,并且在使用 TensorFlow 和 Keras 时被认为是最佳实践。
使用tf.data.Dataset API 的常见模式是首先从输入数据创建源数据集。在这里,源将是你从 Pandas DataFrames 创建的 NumPy 数组。之后,你可以执行任何想要的转换,使用map()或filter()等功能。在本例中,这些转换已经完成。tf.data.Dataset API 将处理将数据批次发送到训练循环中,并保持训练过程无缝运行。
由于你已经在使用 NumPy 数组,你将使用from_tensor_slices方法创建你的Dataset。此方法接受一个 NumPy 数组,并将该“切片”视为训练数据集中的一个示例。例如,X_train是一个秩为 2 的数组或矩阵。from_tensor_slices将该矩阵的每一行视为一个单独的示例。此外,你还可以传入一对数组,Keras 将第一个数组视为示例,第二个数组视为标签,这正是你在这种情况下想要的。要创建你的Dataset对象,请使用以下代码:
import tensorflow as tf
import tensorflow.keras as keras
train_dataset=(tf.data.Dataset.from_tensor_slices((X_train_scaled, y_train_enc))
.batch(128))
test_dataset=(tf.data.Dataset.from_tensor_slices((X_test_scaled, y_test_enc))
.batch(1))
代码中唯一需要解释的部分可能是batch()方法。回想一下,Dataset对象会将数据批次从你的数据集发送到训练循环中。batch()方法定义了这些批次的大小。确切的批次大小取决于你的数据集和模型类型,并且通常需要进行一些调整以达到最佳效果。作为一个经验法则,你的模型或示例大小越大,批次大小就应该越小。然而,批次大小越小,训练过程可能会更加嘈杂。换句话说,梯度下降将不会直接朝着最优权重集合的路径前进,因此可能需要更多的计算时间来收敛到最优模型。批次大小是定义模型和模型训练过程的一个典型超参数。
现在数据已准备好进行训练,您应该创建您的模型。回想一下,在 BigQuery ML 中,您指定了一个整数列表。列表中的元素数量是隐藏单元的数量,每个整数的值是该隐藏层中的神经元数。您将在 Keras 中使用相同的信息,但格式稍有不同。keras.Sequential API 允许您提供您模型中要包含的层的列表,然后根据该信息构建模型。前一章中讨论的层是 Keras 称之为 Dense 层的层。Dense 层是指所有前一层的神经元连接到下一层的每个神经元,形成了您在 第六章 中看到的加权总和。以下是 Dense 层的一个示例:
keras.layers.Dense(
units=64, input_shape=(28,), activation="relu",
name="input_layer"
)
这个示例中显示了四个参数。第一个是 units 的数量。这只是该层中的神经元数目。第二个参数是 input_shape。如果您正在定义模型的第一层,则需要指定传入样本的形状。如果这不是模型的第一层,则可以省略此参数,因为 Keras 将从前一层接收到这些信息。请记住,在进行独热编码之后,有 28 个不同的特征。您可以通过查看 X_train.shape 的输出来再次确认。X_train 的形状是 (5634, 28)。共有 5,634 个样本,每个样本有 28 个特征值。(28,) 这种表示法可能看起来有些奇怪,但这是 Python 表示单一元素列表的方式。如果传入的样本具有更高的维度(例如,图像数据),则 input_shape 将是包含多个元素的列表。第三个参数是 activation,用于定义激活函数。对于二元分类模型,最终层将需要 1 个输出和 sigmoid 作为激活函数。对于隐藏层,最常用的激活函数是 ReLU(或修正线性单元)函数,正如在 第六章 中讨论的那样。最后,您可以使用 name 参数为层分配自定义名称。
使用以下代码在 Keras 中定义您的神经网络。此代码将创建一个包含分别为 64、32 和 16 个神经元的三个隐藏层的神经网络:
model = keras.Sequential(
[
keras.layers.Dense(
units=64, input_shape=(28,), activation="relu",
name="input_layer"
),
keras.layers.Dense(units=32, activation="relu",
name="hidden_1"),
keras.layers.Dense(units=16, activation="relu",
name="hidden_2"),
keras.layers.Dense(units=1, activation="sigmoid",
name="output"),
]
)
现在模型已经定义,需要进行编译。这个过程将模型转换为 TensorFlow 操作,并配置模型进行训练。幸运的是,Keras 只需您提供少量输入即可处理所有这些。
使用以下代码编译您的模型:
loss_fn = keras.losses.BinaryCrossentropy()
metrics = [tf.keras.metrics.BinaryAccuracy(),
tf.keras.metrics.Precision(),
tf.keras.metrics.Recall()]
model.compile(optimizer="adam", loss=loss_fn, metrics=metrics)
keras.losses.BinaryCrossentropy() 是 Keras 实现的本章早期讨论过的交叉熵损失函数。您还包括了三个指标来评估模型的性能。BinaryAccuracy()、Precision() 和 Recall() 分别是 Keras 实现的用于您在 scikit-learn 中创建的模型的准确率、精确度和召回率指标。在使用 compile() 方法编译模型时,您需要包括损失函数、想要使用的任何指标以及优化器。对优化器的深入讨论超出了本书的范围,但 Adam 优化器被认为是训练神经网络模型的良好默认选择。简而言之,Adam 对原始梯度下降算法进行了一些改进,以解决使用梯度下降时可能遇到的一些问题。关于优化器的更深入讨论,请参阅《Scikit-Learn、Keras 和 TensorFlow 实战》(O'Reilly, 2022)作者 Aurélien Géron 的书籍 Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow。
现在模型已经定义并编译完成,下一步是训练模型。在 Keras 中,您使用 fit() 方法来训练一个已编译的模型。您需要指定用于训练和评估的数据集,并且需要指定希望训练模型的时长。以下代码展示了一个示例:
history = model.fit(
x=train_dataset,
epochs=20,
validation_data=test_dataset
)
参数 x 对应于训练数据集。如果您不使用 tf.data.Dataset,则必须在参数 y 中单独指定标签,但是您的 train_dataset 包含特征和标签。validation_data 类似于第一个参数 x,只是专门用于评估数据集。这个参数是可选的,但是监视训练和评估指标并行演变往往是个好主意。最后,中间参数 epochs 是训练过程将持续的时间测量。在机器学习术语中,一个 epoch 是整个数据集的一次完整遍历,用于训练。请记住,您的数据集正在以 128 的批次发送到训练循环,并且您的训练数据集有 5,634 个示例。经过 44 个批次(或通常称为 步骤),您将完成整个数据集的一次遍历,然后下一个 epoch 将再次开始,因为 Keras 会再次遍历数据集。
模型应该训练多长时间呢?在实践中没有一个明确的经验法则。然而,有几个迹象可以依据。首先,如果模型从一个时期到另一个时期不再改善,那就表明训练过程已经收敛。第二个迹象是,模型开始在评估数据集上表现更差。这表明模型已经开始过拟合,您希望停止训练它,即使在训练数据集上的性能继续改善。
fit()方法还有一个可选的回调参数。回调是在训练的各个阶段执行操作的函数,例如每个周期保存模型的检查点,或者在评估数据集上模型性能停滞或变差时停止训练。后一种例子称为早期停止。使用以下代码来训练您的模型,使用keras.callbacks.EarlyStopping()回调函数在需要时实施早期停止:
early_stopping = keras.callbacks.EarlyStopping(patience=5,
restore_best_weights=True)
history = model.fit(
x=train_dataset,
epochs=100,
validation_data=test_dataset,
callbacks = [early_stopping]
)
keras.callbacks.EarlyStopping()方法的参数patience表示在停止训练过程之前,您希望等待五个周期的停滞或性能恶化。由于训练过程中存在随机性的多个地方,可能会产生噪声。您不希望由于噪声而过早终止训练,因此patience参数在防止这种情况下非常有用。然而,以防在等待期间模型性能变差,将restore_best_weights参数设置为True将使模型恢复到终止训练之前的最佳性能。如果您还没有运行上述代码来训练您的模型,请执行此操作。在 Colab 中完成训练可能需要几分钟。
在训练模型时,您将看到每个训练周期的指标。前几个指标特定于训练数据集,后半部分与测试数据集相关。您还可以使用model.evaluate(x=test_dataset)方法来评估模型。这可以用于测试数据集或其他数据集,具体取决于您如何拆分数据集进行训练和评估。从model.evaluate()中输出的结果应该类似于以下内容:
12/12 [==============================] - 0s 6ms/step - loss: 0.4390
- binary_accuracy: 0.7928 - precision: 0.6418 - recall: 0.4866
好消息,您已经使用 Keras 训练了一个成功的 ML 模型。不幸的是,在这种情况下,神经网络模型的准确性、精确度和召回率略低。这意味着什么?这并不意味着神经网络模型自动更差,但这意味着可能需要进一步调整和特征工程来提高该模型的性能。
在 Vertex AI 上构建自定义 ML 模型
您已经成功构建、训练和评估了多个 ML 模型。您使用 scikit-learn 训练了一个逻辑回归模型,并使用 Keras 训练了一个神经网络分类器。在这种情况下,您使用的数据集相对较小,但实际上您可能希望在大型数据集上训练自定义代码模型。在第五章中,您看到了如何使用 Vertex AI AutoML 训练分类模型。在本节中,您将简要介绍如何使用 Vertex AI 训练自定义代码模型。本节向您介绍 Python 中更复杂的主题,例如创建包。在这里,您不会深入了解细节,但足以让您开始。
Vertex AI 允许你在选择的机器集群中的容器化环境中训练你的模型。粗略地说,你可以把容器看作是一个计算机,其中硬件和操作系统被抽象化,开发人员可以专注于他们想要运行的软件。当使用标准 ML 框架时,可以使用预构建的容器。scikit-learn、TensorFlow、XGBoost 和 PyTorch 都提供预构建的容器。要在 Cloud Console 中使用 Vertex AI 训练服务,执行以下步骤:
-
确保你的数据集已经在 Google Cloud Storage、BigQuery 或 Vertex AI 托管的数据集中可用。
-
将 Python 代码收集到一个单独的脚本中。
-
更新你的代码以将结果保存在 Google Cloud Storage 存储桶中。
-
创建一个源代码分发。
幸运的是,第一步已经完成。数据集已经在公共 Google Cloud Storage 存储桶中可用。接下来的步骤是将 Python 代码收集到一个单独的脚本中。这段代码包括用于加载数据到 Pandas DataFrame、准备训练数据、构建和训练 ML 模型的所有代码,并且你还需要添加代码来确保模型被保存到 Vertex AI 资源之外的地方。
警告
Vertex AI 为训练作业分配资源,仅在作业运行期间使用这些资源,然后将其销毁。这意味着你需要确保将不想丢失的任何内容保存在本地,但也意味着你只使用你需要的资源。
在继续之前,你需要创建一个目录来保存你将为 Python 包创建的文件。在 Colab 中,可以在新单元格中运行以下代码来执行此操作:
!mkdir trainer
代码前面的感叹号告诉 Colab 将其作为 Linux 终端命令运行。mkdir命令创建一个新目录,你将这个新目录命名为trainer。
本章的 Python 代码已经合并到一个单文件中,并且可以在low-code-ai GitHub 仓库中找到。你也应该尝试自己构建这个文件。你可以在笔记本中使用特殊的单元格魔法%%writefile来完成这个操作。%%writefile单元格魔法告诉 Colab 将单元格的内容写入指定的文件,而不是执行单元格中的代码。单元格的格式如下:
%%writefile trainer/trainer.py
*`<``Your` `Python` `code` `to` `be` `written` `to` `trainer``.``py``>`*
在继续阅读之前,你应该将笔记本中的代码合并到一个单元格中,然后使用%%writefile单元格魔法将其写入trainer/trainer.py,或者你可以复制上面链接中的解决方案。请注意,你不需要包括用于可视化或检查输出的代码。如果你将 Python 代码自己合并到单个文件中,可以使用这个解决方案来检查你的工作。
查看解决方案时,您将首先注意到所有的import语句都已移到脚本顶部。尽管如果导入在使用之前就已经完成,这被认为是 Python 的最佳实践,它也不会成为问题。在文件末尾,添加了print语句以打印出模型的各种不同指标。如果不打印结果,它们将会丢失。使用print语句将允许您在训练日志中稍后找到这些指标。
最后,在 Python 脚本的最后一行,您将看到使用joblib.dump()将模型写入 Google Cloud 存储的用法。请注意,此处 Cloud Storage 的引用不同:'gcs/<YOUR-BUCKET-NAME>/sklearn_model/'。Google Cloud 存储桶通过 Cloud Storage FUSE 挂载,并被有效地视为文件系统。您需要包括您在第四章中创建的或创建新的云存储桶的名称。
现在脚本已经写入到trainer\trainer.py,下一步是创建包中的其他文件。一个简单的方法是使用%%writefile单元格魔法。为了让 Python 将一个文件夹识别为一个包,它需要在trainer目录中有一个init.py文件。该文件包含包的任何初始化代码,如果不需要这样的代码,它也可以是一个空文件。您还需要的另一个文件是setup.py文件。setup.py文件的目标是确保在执行训练作业的机器上正确安装该包,并且它是 Python 包的标准部分。尽管如此,当使用 Vertex AI 上的 scikit-learn 训练的标准预构建容器时,大部分流程都是直截了当的,并且主要涉及样板代码。要在trainer目录中创建init.py文件,请在新单元格中运行以下代码:
%%writefile trainer/__init__.py
#No initialization needed
行#No initialization needed是 Python 中注释的一个示例。#符号表示 Python 不应解析该行,但是这仅供人类可读性。由于您的包不需要初始化,Python 将简单地将此init.py文件视为一个空文件。要创建您的setup.py文件,请在新单元格中运行以下代码:
%%writefile setup.py
"""Using `setuptools` to create a source distribution."""
from setuptools import find_packages, setup
setup(
name="churn_sklearn",
version="0.1",
packages=find_packages(),
include_package_data=True,
install_requires=['gcsfs'],
description="Training package for customer churn.",
)
这里的代码大部分是样板代码。在setup函数中,您定义了包名称、版本号、作为分发的一部分应安装的包以及包的描述。find_packages()函数会自动检测您目录中的包。install_requires=['gcsfs']参数确保安装gcsfs包以便使用 Cloud Storage FUSE。
所有文件都就位了,现在您可以通过在新单元格中执行以下代码来创建您的包:
!python ./setup.py sdist --formats=gztar
执行 Python 脚本./setup.py的命令,并使用sdist选项。这将创建一个带有压缩格式tar.gz的 Python 源代码分发。您的文件,以及相关文件夹扩展后,应如 Figure 7-10 所示。

Figure 7-10 创建您的训练包的源代码分布后的文件结构。
现在过程中的困难部分已经完成。现在,您应该将软件包移动到 Google Cloud 存储的位置,以供在 Vertex AI 上使用。这样做的最简单方法是授权 Colab 访问您的 Google Cloud 帐户,然后使用gcloud storage工具。要授权 Colab 访问您的 Google Cloud 资源,请在新单元格中运行以下代码,并按照提示操作:
import sys
if "google.colab" in sys.modules:
from google.colab import auth
auth.authenticate_user()
在按照提示操作后,您可以将文件移动到您选择的云存储桶中。在新单元格中运行以下代码,将your-project-id替换为您的项目 ID,将your-bucket-name替换为您的存储桶名称:
!gcloud config set project your-project-name
!gcloud storage cp ./dist/churn_sklearn-0.1.tar.gz gs://your-bucket-name/
现在一切就绪,您可以开始训练您的模型。您将通过 Cloud 控制台提交您的培训作业。打开一个新的浏览器窗口或选项卡,然后转到console.cloud.google.com。然后,在左侧菜单中选择 Vertex AI,然后选择培训。如果您找不到培训选项,请参见图 7-11。

Figure 7-11 中 Vertex AI 中培训选项的位置。
选择培训选项后,单击创建以开始创建新的培训作业(参见图 7-12)。在开始培训过程之前,您需要设置几个选项。不同页面上的输入如图 7-13 至图 7-16 所示,并且这些输入如表 7-9 所示。未在此处提到的任何选项都应保留为默认值。一旦在每个页面上输入了所有选项,请继续,直到“开始培训”按钮可用为止。

Figure 7-12 中 Vertex AI 训练控制台中“创建”按钮的位置。

Figure 7-13 中您的培训作业的“培训详情”页面上的输入。

Figure 7-14 中您的培训作业的“模型详情”页面上的输入。

图 7-15. “训练容器”页面上的输入。确保用您正在使用的存储桶替换这个存储桶。

图 7-16. 计算和定价页面上的输入。确保选择与您的存储桶接近的位置。
表 7-9. Vertex AI 训练作业的输入
| 训练方法页面 |
|---|
| 数据集 |
| 模型训练方法 |
| 模型详细信息页面 |
| 单选按钮 |
| 模型名称 |
| “训练容器”页面 |
| 单选按钮 |
| 模型框架 |
| 模型框架版本 |
| 包位置 |
| Python 模块 |
| 计算和定价页面 |
| 区域 |
| 机器类型 |
Vertex AI 创建的训练流水线运行大约需要三分钟。完成后,您可以通过返回 Vertex AI 链接并选择“训练”来查看日志。在该页面上,点击“自定义作业”,然后点击“churn-custom-job”(见 图 7-17)。进入自定义作业页面后,您将看到一个信息表格。在表格底部,点击“查看日志”链接。如果向下滚动,您将看到训练作业日志中的指标打印输出。日志中显示的指标示例如 图 7-18 所示。

图 7-17. 完成的自定义作业churn-custom-job在“自定义作业”页面上。

图 7-18. 自定义训练作业的指标。
您已成功在 Vertex AI 上使用 scikit-learn 训练了一个模型。当然,考虑到设置所需的时间和处理的数据量,这个过程可能会感觉有些繁琐。对于处理较小数据集,本地工作或在 Colab 笔记本中工作是一个合理的方法。但随着数据集变得越来越大,利用更大资源池的优势变得更有利。在 Vertex AI 中,当处理不断增长的数据集时,同样的基本流程也适用。现在,您的模型存储为一个 .joblib 文件,并准备好在需要时加载以进行服务预测。
作为练习,使用 Keras 模型重复这个过程。几个提示:
-
确保使用 TensorFlow 的预构建容器。您可以通过在 Colab 笔记本中运行命令
tf.__version__来检查使用的版本。 -
TensorFlow 预构建镜像包含 sklearn 包,因此您可以轻松重用您的预处理代码。
-
TensorFlow 模型不再使用
joblib.dump()来保存模型,而是包含一个内置方法save()。使用model.save()将您的模型存储在 Google Cloud Storage 中。
最后,对于那些完成了 scikit-learn 中管道(pipelines)可选部分的人,请将模型代码的 Pipeline 版本打包并提交到 Vertex AI 训练中进行训练。
如需更多资源,请参阅官方 Vertex AI Custom Training 文档。
总结
在本章中,您学习了如何构建自定义代码模型,以预测电信公司的客户流失。您探索了 scikit-learn 和 TensorFlow 中两种最流行的 ML 框架,并在每种框架中构建了简单的分类模型。然后,您学习了如何使用 Google Cloud 上的 Vertex AI 训练服务来训练您的模型。本章涵盖的所有主题仅仅是冰山一角,希望作为进一步深入了解机器学习的基础。在下一章中,您将看到如何通过诸如 BigQuery ML 中的超参数调整和在 Vertex AI 中使用自定义代码等技术来改进您的模型。
第八章:改进自定义模型性能
在第六章和第七章中,您学习了如何准备数据,并使用 SQL、BigQuery ML 和 Python 构建自定义模型。在本章中,您将重温这些工具,关注额外的特征工程和超参数调整。与之前的章节不同,您将从准备好的数据和已训练的模型开始,并努力进一步改进。如果您在探索先前构建的模型的代码或 BigQuery 的用户界面时感到困惑,请重新查看第六章和第七章中的讨论内容。
业务使用案例:二手车拍卖价格
本项目的目标是改进一个用于预测二手车拍卖价格的机器学习模型的性能。初始模型是一个在 scikit-learn 中构建的线性回归模型,但并没有完全达到您的业务目标。最终,您将探索使用 scikit-learn、Keras 和 BigQuery ML 中的工具,通过特征工程和超参数调整来提高模型的性能。
用于训练线性回归模型的数据集已经作为 CSV 文件提供给您。这些数据集已经进行了清理(已适当修复了缺失和不正确的值),并且提供了用于构建 scikit-learn 线性回归模型的代码。您的队友训练了线性回归模型,并与您分享了一些关于模型性能以及他们初步尝试使用 Keras 训练 ML 模型的笔记。您的同事还分享了用于训练和评估模型的数据分割。他们创建了一个独立的测试数据集,尚未使用,您将可以用它来验证最终模型的性能。您的任务将是探索使用特征工程来改进模型的特征集,并利用超参数调整来确保使用最佳模型架构。您将学习如何在 scikit-learn、Keras 和 BigQuery ML 中执行这些任务。
在批发汽车销售行业中,批发价格的一个主要指标是Manheim 市场报告(MMR)。 MMR 定价计算基于过去 13 个月的 1000 万多次销售交易。 在您的数据集中,您可以访问数据最初提取时共享的汽车销售定价计算。 但是,您不确定将来是否还能访问此信息。 因此,您被要求在探索中避免使用此功能。 与您共享的业务目标是在不使用 MMR 功能的情况下,使销售价格的 RMSE 达到 2000 美元或更低。 您将首先使用同事提供的笔记本加载数据并复制他们执行的模型训练。
数据集中有 13 列。 表 8-1 给出了列名、数据类型以及这些列可能值的一些信息。
表 8-1. 汽车销售数据集的模式和字段值信息
| 列名 | 列类型 | 关于字段值的注释 |
|---|---|---|
year |
Integer | 车辆制造年份 |
make |
String | 车辆品牌 |
model |
String | 车辆品牌的具体版本或变种 |
trim |
String | 车辆型号的具体版本或变种 |
body |
String | 车辆的车身风格(例如轿车) |
transmission |
String | 自动或手动变速器 |
state |
String | 车辆将要出售的州 |
condition |
Float | 车辆评级的条件,从 0 到 5 |
odometer |
Integer | 销售时的里程表读数 |
color |
String | 车辆颜色 |
interior |
String | 内饰颜色 |
mmr |
Float | Manheim 市场报告的定价 |
sellingprice |
Float | 车辆的实际销售价格(标签) |
在 Scikit-Learn 中改进模型
在本节中,您将努力改进您的同事与您分享的 scikit-learn 中的线性回归模型。 您将首先快速探索数据、预处理管道和 scikit-learn 中的模型本身。 然后,您将仔细探索特征,看看如何使用新的和熟悉的特征工程技术来提高模型性能。 最后,您将利用超参数调整来确保您在为您的特定问题优化地创建新特征。
加载带有现有模型的笔记本
首先,前往https://colab.research.google.com。您将直接从low-code-ai repository加载笔记本,而不是创建一个新的笔记本。点击 GitHub 按钮,并在提示“输入 GitHub URL 或按组织或用户搜索”下输入 low-code-ai GitHub repo 的 URL,https://github.com/maabel0712/low-code-ai,如图 8-1 所示。

图 8-1. 在 Google Colab 中连接到 GitHub 直接打开笔记本。
按 Enter(或点击放大镜图标)在 repo 中搜索笔记本。向下滚动直到看到chapter_8/sklearn_model.ipynb,然后点击最右侧的“在新标签页中打开笔记本”按钮。这将在新的浏览器标签页中打开sklearn_model.ipynb笔记本。
加载车辆拍卖销售数据的代码,准备训练数据,训练 ML 模型以及评估 ML 模型的步骤已经包含在笔记本中。在本章中,您不会像在第七章中那样详细地讨论这些代码,但在开始模型改进过程之前,您将花一些时间审查这些代码。
加载数据集和训练-验证-测试数据拆分
首先执行单元格以加载训练、验证和测试数据集到相应的 DataFrames 中:
!wget -q https://storage.googleapis.com/low-code-ai-book/car_prices_train.csv
!wget -q https://storage.googleapis.com/low-code-ai-book/car_prices_valid.csv
!wget -q https://storage.googleapis.com/low-code-ai-book/car_prices_test.csv
import pandas as pd
train_df = pd.read_csv('./car_prices_train.csv')
valid_df = pd.read_csv('./car_prices_valid.csv')
test_df = pd.read_csv('./car_prices_test.csv')
这些代码在之前的章节中应该大部分都很熟悉,但wget bash 命令可能对您来说是新的。wget简单地将给定 URL 处的文件下载到本地文件目录中。-q标志抑制了wget命令的日志输出。在将数据加载到 DataFrames 时,请注意文件位置以./开头。.是“当前工作目录”的简写,这也是wget命令将下载您指定的三个文件的位置。
在将数据集加载到各自的 DataFrames 中之后,通过对每个 DataFrame 使用head()方法快速确认数据是否符合预期。train_df.head()方法输出的前几列如下所示:
Unnamed: 0 year make model trim body
0 0 2012 Infiniti G Sedan G37 Journey g sedan
1 1 2012 Chevrolet Cruze LS Sedan
2 2 2005 Jeep Wrangler X SUV
3 3 2011 Kia Sorento SX SUV
4 4 2002 Volkswagen New Beetle GLS Hatchback
在您的 DataFrame train_df 中,第一列是索引,但第二列Unnamed:0是从哪里来的?这列是 CSV 文件中未命名的列,您加载以创建 DataFrames 时没有提到它,最有可能是前一个 DataFrame 中因误操作而保留的索引,并不是重要特征。在下一节中,您将看到您的同事在数据预处理中删除了这一列。
注意
请注意,您也可以像在第七章中所做的那样,使用 Pandas 直接从 Google Cloud Storage 中的文件位置读取。使用wget命令的优点是您现在拥有数据的本地副本。哪种方法更有利取决于您的工作流程以及如何操作数据。
在继续之前,请回忆一下在前几章中,您使用了两个数据集,一个训练数据集和一个测试数据集,用于训练后评估模型。现在有三个数据集:一个训练数据集,一个验证(或评估)数据集和一个测试数据集。为什么会有三个数据集呢?训练数据集当然用于训练模型,验证数据集用于评估模型。在这个项目中,您将比较许多不同的模型。您将使用训练数据集来训练每个模型,然后使用验证数据集来评估模型。"最终模型"将是在验证数据集上表现最佳的模型。然而,您选择的最终模型可能会偏向于验证数据集,因为您明确选择了在验证数据集上表现最佳的模型。
测试数据集的作用是有一个最终独立的数据集来验证最终模型的性能。如果最终模型在测试数据集上的表现与验证数据相似,那么该模型就可以使用。如果模型在测试数据集上的性能显著较差,那么在将模型用于工作负载之前,您就知道存在问题。
帮助避免这种情况的一种方法是确保您的训练、验证和测试数据集具有类似的数据分布。作为一个练习,使用describe(include='all')方法来探索数据集,看看这三个数据集是否具有相似的分布,直到一些离群值为止。
探索 Scikit-Learn 线性回归模型
现在转到笔记本中的下一个单元格。此单元格包含准备数据、训练 ML 模型和评估模型的代码,所有这些都使用 scikit-learn 完成。在本节中不会仔细讲解所有代码,因为在第七章中已经介绍了 scikit-learn 流水线的概念。然而,在路上会讨论一个快速概述和一些额外的注释。首先考虑导入语句之后的数据处理部分代码:
y_train = train_df['sellingprice']
X_train = train_df.drop('sellingprice', axis=1)
def drop_columns(df, columns):
return df.drop(columns, axis=1)
preproc_cols = FunctionTransformer(drop_columns,
kw_args={"columns":['Unnamed: 0', 'mmr']})
numeric_columns = ['year', 'condition', 'odometer']
categorical_columns = ['make', 'model', 'trim', 'body',
'transmission', 'state', 'color', 'interior']
col_transformer = ColumnTransformer(
[
('ohe', OneHotEncoder(drop='if_binary',
handle_unknown='infrequent_if_exist'),
categorical_columns),
('minmax', MinMaxScaler(), numeric_columns)
]
)
首先,您将 DataFrame 分割为标签列(sellingprice)和其余特征列作为单独的 DataFrame。然后,您在训练、验证和测试数据集中删除Unnamed: 0和mmr列。这是通过定义一个drop_columns函数并使用FunctionTransformer应用该函数来完成的。请注意,在定义FunctionTransformer时有一个新的参数。kw_args参数接受超出第一个参数的选择函数参数的值。对于preproc_cols,第一个参数是我们希望从中删除列的 DataFrame,这将在管道中提供。第二个参数是我们希望删除的列的列表,这将作为字典键columns的相应值传递。
Unnamed: 0列可能看起来有些奇怪,但正如之前讨论的那样,这很可能是使用 Pandas DataFrame 方法sample()对数据进行洗牌时保留原始索引作为新列的结果。在您问题的背景下,这列与目标没有关系,因此被丢弃。mmr列与目标sellingprice高度相关,但由于您已被指示避免使用该特征,因此也被丢弃。
否则,前述代码的其余部分将会很熟悉“Scikit-Learn 中的管道:介绍”在第七章。您将列分为数值列和分类列(分别为numeric_columns和categorical_columns),然后使用ColumnTransformer()对不同的列集应用不同的转换。对于分类列,将使用OneHotEncoder(),对于数值列将使用MinMaxScaler()。
现在考虑在定义模型和管道的单元格中的其余代码:
model = LinearRegression()
pipeline = Pipeline(steps=[('preproc_cols' , preproc_cols),
('col_transformer', col_transformer),
('model', model)])
pipeline.fit(X_train, y_train)
这里使用的模型是线性回归模型。同时,您还使用了一个Pipeline对象来定义预处理和模型作为一系列步骤。首先,将会对 DataFrame 应用preproc_cols FunctionTransformer(),然后使用col_transformer ColumnTransformer()根据列的类型应用适当的转换。最后,在调用pipeline.fit时,线性回归模型将作为管道的最后一部分进行训练。最后一行同时拟合转换器并训练模型。在预测时,拟合的转换器将作为管道的一部分存储。训练模型后,您将看到管道的图形表示,如图 8-2 所示。如果希望,您可以展开查看更多细节,并确认这些细节与代码的预期一致。

图 8-2. scikit-learn Pipeline用于训练模型的图形表示。
现在模型已经在您的笔记本环境中训练好了,您可以评估模型。回想一下,当使用 scikit-learn 管道时,您可以像对待任何模型对象一样使用score()方法。您还可以导入其他指标,如 RMSE 和 MAE。在下一个单元格中运行以下代码查看模型的评估指标:
import math
from sklearn.metrics import mean_squared_error, mean_absolute_error
y_valid = valid_df['sellingprice']
X_valid = valid_df.drop('sellingprice', axis=1)
print('R2:', pipeline.score(X_valid, y_valid))
print('RMSE:',math.sqrt(mean_squared_error(y_valid, pipeline.predict(X_valid))))
print('MAE:', mean_absolute_error(y_valid, pipeline.predict(X_valid)))
您将看到 R²分数约为 0.876。这意味着您的特征大致描述了标签变量的 87.6%的变异性。您还将看到 RMSE 约为 3,384.60,MAE 约为 2,044.26。回想一下,您的业务目标是使 RMSE 低于$2,000。根据同事的传达,模型未能达到这些需求,但现在您已经准备好改进模型了。
注意
通常情况下,在团队合作时,您希望在比较结果时避免任何从分割和训练过程中产生的随机性。否则,您可能会被在不同环境中训练的不同模型结果误导。在这种情况下,实际的数据拆分已与您共享,而不是代码。这通常是可取的,因为通常会使用随机洗牌和拆分。您可以设置随机种子以使拆分确定性,或保存相应的训练、验证和测试数据集,就像这里做的那样。此外,在初始化和训练模型时也要考虑这一点。
特征工程和改进预处理流水线
通常情况下,选择得当并精心创建的特征,即使使用简单的模型架构,也可能导致非常强大的结果。仅仅是让模型变得更复杂并不总是正确的方法。更复杂的模型将需要更多的数据来成功训练模型,并且需要更多的计算资源来训练和最终调整超参数。甚至寻找简单的修复方法,例如查找异常值并删除无关的特征,也可能导致显著的模型改进。
寻找简单的改进
您的同事对原始数据集进行了仔细分析,并向您传达了他们已删除所有空值和与标签呈一对一关系的列(例如 VIN)。现在是探索数据集以查看是否还有其他可以改进的地方的好时机。如果您尚未这样做,请在笔记本环境中的新单元格中运行命令train_df.describe()。预期输出的示例在表 8-2 中显示。
表 8-2. train_df.describe()的部分输出
year |
condition |
odometer |
mmr |
sellingprice |
|
|---|---|---|---|---|---|
count |
385000.000000 |
385000.000000 |
385000.000000 |
385000.000000 |
385000.000000 |
mean |
2010.120177 |
3.339402 |
67732.957974 |
13695.356558 |
13544.324018 |
std |
3.879672 |
1.117698 |
52521.619238 |
9525.243974 |
9599.953488 |
min |
1990.000000 |
-1.000000 |
1.000000 |
25.000000 |
1.000000 |
25% |
2008.000000 |
2.700000 |
28494.000000 |
7200.000000 |
7000.000000 |
50% |
2012.000000 |
3.600000 |
52122.000000 |
12200.000000 |
12100.000000 |
75% |
2013.000000 |
4.200000 |
98188.000000 |
18150.000000 |
18000.000000 |
max |
2015.000000 |
5.000000 |
999999.000000 |
182000.000000 |
183000.000000 |
请记住,在预处理管道中删除 Unnamed: 0 和 mmr 列,所以在分析中无需担心这些列。乍一看 year 列中似乎没有什么异常;汽车的年份在 1990 年到 2015 年之间,数据分布偏向更新的车辆。但是 condition 列似乎有些奇怪。在 condition 列中有一个最小值为 -1.0。这很可能意味着你的同事在处理时遗漏了一个魔数。在分析数据集中的多列时,有时会错过像这样简单的问题。这正是为什么额外的一双眼睛总是有价值的原因。
由于 condition 是一个浮点数,我们不能简单地将 -1.0 视为一个单独的值进行处理。你有几个选择。如果你认为销售价格与 condition 值有线性关系,那么你可以创建一个新特征 condition_recorded,作为二进制 0 或 1 值,并将 -1.0 的实例替换为 0.0,这样这些值就会与普通的 condition 值不同。然而,正如你可能在其他评分系统中经历过的那样,评分的效果通常并非线性的。解决这个问题的简单方法是对值进行分桶,然后对应的桶进行独热编码。这样,没有评分的情况将与其他评分的情况(例如 2 到 3 之间)完全不同,你可以调整桶的数量以找到最适合你的模型性能的设置。
要采用第二种方法,在笔记本中创建一个新的单元格,并添加以下代码,但暂时不要运行代码:
import pandas as pd
from sklearn.preprocessing import (OneHotEncoder, MinMaxScaler,
FunctionTransformer,
KBinsDiscretizer)
from sklearn.compose import ColumnTransformer
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
y_train = train_df['sellingprice']
X_train = train_df.drop('sellingprice', axis=1)
def preproc_cols(df, drop_cols):
return df.drop(drop_cols, axis=1)
drop_cols = FunctionTransformer(preproc_cols,
kw_args={"drop_cols":['Unnamed: 0', 'mmr']})
ohe = OneHotEncoder(drop='if_binary', handle_unknown='infrequent_if_exist')
minmax = MinMaxScaler()
bucket_cond = KBinsDiscretizer(n_bins=10, encode='onehot', strategy='uniform')
这段代码与之前分享的大部分相同,但请注意几处更改。KBinsDiscretizer 转换器已经添加;这个转换器是 scikit-learn 中用于数据分桶的工具。请注意,现在转换器是在单独的行上定义而不是像以前在 ColumnTransformer 中。这样做增加了可读性,同时也增强了模块化,即在继续改进模型时更容易分割代码。
在这些行的最后定义了KBinsDiscretizer。n_bins参数设置为10,表示 10 个不同的桶,encode参数告诉转换器执行独热编码,而strategy参数uniform告诉转换器均匀地分割这些桶。这样,-1.0将会独立于其他范围在其自己的桶中。完成使用以下代码定义管道,并运行单元格以训练您的新模型:
numeric_columns = ['year', 'odometer']
categorical_columns = ['make', 'model', 'trim', 'body',
'transmission', 'state', 'color', 'interior']
col_transformer = ColumnTransformer(
[('ohe', ohe, categorical_columns),
('minmax', minmax, numeric_columns),
('bucket_cond', bucket_cond, ['condition'])])
pipeline = Pipeline(steps=[('drop_cols' , drop_cols),
('col_transformer', col_transformer),
('model', model)])
pipeline.fit(X_train, y_train)
您可以通过在新的单元格中执行之前使用的代码来评估新模型:
print('R2:', pipeline.score(X_valid, y_valid))
print('RMSE:',math.sqrt(mean_squared_error(y_valid, pipeline.predict(X_valid))))
print('MAE:', mean_absolute_error(y_valid, pipeline.predict(X_valid)))
这一变更确实导致了模型性能的轻微提升。RMSE 从约 3,384.60 降至 3,313.63。尽管在这种情况下提升不大,但在许多情况下,捕捉到这样的问题可以大幅提升模型性能。
在评估模型时,您可能注意到了结果中的警告消息:
UserWarning: Found unknown categories in columns [2, 3] during transform.
These unknown categories will be encoded as all zeros
这个警告实际上是什么意思?这里的第 2 列和第 3 列对应于trim和body。这个警告意味着在验证数据集中,这些列对应的值在训练数据集中不存在。检查数据集之间的偏差,例如训练和验证数据集中出现不同值的情况,是理解数据准备训练的重要步骤。然而,在训练和评估模型时可能会出现意外问题,因此了解需要注意的事项是非常有用的。
使用以下代码,您可以快速检查trim列中出现的仅一次的值有多少个:
(train_df.trim.value_counts()==1).sum()
您将看到在训练数据集中有 124 个在trim列中是唯一的值。同样,在验证数据集中,您可以看到有 273 个唯一值。看起来您的同事可能已经意识到了这一点,并在他们的OneHotEncoder定义中加以解决。他们包括了handle_unknown='infrequent_if_exist'参数和值。handle_unknown定义了在预测时出现未知值时遵循的行为,而'infrequent_if_exist'值将未知特征分配给一个少见的类别(如果存在)。要创建一个“少见”类别,您可以设置min_frequency参数。这也是可以调整的内容。
将min_frequency设置得太高将导致许多类别对模型输出的贡献相同,降低特征的有用性。另一方面,如果min_frequency设置得太低,则可能会出现许多仅出现一次的特征或者您已经看到的在数据集之间难以获得正确分布的特征值的问题。
将min_frequency设置为 1,然后重新运行训练代码,看看性能是否有所不同。你会发现,这一次性能只有微小的变化。实质上,你说你将所有在训练集中出现少于 1 次(或 0 次)的类别视为相同。也许增加min_frequency是有道理的,这样你就可以将所有不频繁的变量视为同一类别,即“不频繁”类别。在进行超参数调优时,你将稍后探索这一点。
特征交叉
仔细考虑一下model和trim特征。通常情况下,你会将这些特征一起考虑,而不是分开考虑,对吧?当你说“我买了一辆本田 CR-V”时,这并不能完全描述这辆车。车上可能有多种不同的trim或包装。例如,对于 2023 年的本田 CR-V,有三种 trim 型号:“LX”,“EX”和“EX-L”。同样的名字也可能会用于不同的车型。例如,2023 年的本田 Pilot 也有“LX”和“EX-L”这两种 trim。因此,trim变量的值也不能完全描述整个情况。你需要这两个特征的值才能识别出车辆。
然而,在你的模型中,你将model和trim视为完全独立的变量。回想一下,使用一位有效编码时,你为每个特征值创建一个二进制特征,并且线性回归模型将为每个这些二进制特征分配一个权重。因为你使用了一位有效编码,所以trim值为 LX 将有其自己的权重,与model变量的值无关。也就是说,“LX”这个trim特征值在model是“Pilot”还是“CR-V”时都会被同等对待。考虑到某些制造商往往比其他制造商更昂贵,因此仍然有必要单独考虑make特征。
如何捕捉两个特征值作为一对?一种方法是使用所谓的特征交叉。特征交叉是通过连接两个或更多特征形成的合成特征。直观地说,你考虑的是同时考虑两个变量的值,而不是分开考虑。
这对分类特征如何工作?回想一下,与一位有效编码对应的特征值是二进制的 0 或 1。在这种情况下,特征交叉的想法是,交叉特征值将在对应值的特征对存在时为 1,否则为 0。例如,以“CR-V LX”作为model和trim。在一位有效编码下,“CR-V”特征的值将为 1,而“LX”特征的值也将为 1。因此,model和trim的特征交叉“CR-V LX”的值将为 1。然而,“Pilot LX”的特征交叉值将为 0,因为在这个例子中,“Pilot”特征的值为 0。
这似乎是一个创建和使用的简单特征,当您在第四章和第五章中使用 AutoML 时,它会在找到最适合您数据集的模型的过程中为您创建这些特征(以及更多)。然而,即使在简单的线性回归模型中,特征交叉也可以是极其强大的特征。您能想到其他可以受益于特征交叉的特征对吗?
要看到其效果,请首先将preproc_cols函数的代码替换为以下内容:
def preproc_cols(df, drop_cols):
df['model_trim'] = df['model'] + df['trim']
df['model_trim'] = df['model_trim'].str.lower()
df['color_interior'] = df['color'] + df['interior']
df['color_interior'] = df['color_interior'].str.lower()
return df.drop(drop_cols, axis=1)
考虑此函数的前两行。您正在 DataFrame 中创建一个新列model_trim。这个新列是通过连接model列和trim列的值形成的。因此,model_trim列的值将取决于车型和车辆修剪。第二行将相应的字符串转换为全部小写。这是一个很好的做法,可以确保大小写的随机差异不会导致不同的特征值。color和interior是另一个很好的例子,它们之间的关系可以通过特征交叉很好地表示,因此第三行和第四行实现了相同的思想。
最后,您需要确保新的特征交叉列正如其他分类变量一样进行了独热编码;为此,请更新列表categorical_columns以包含新的特征名称。您的最终列表应如下所示:
categorical_columns = ['make', 'model', 'trim', 'model_trim', 'body',
'transmission', 'state', 'color', 'interior',
'color_interior']
现在执行具有上述更改的模型代码,并重新评估模型的性能。如果您遇到困难,完整的代码在解决方案笔记本中可供查看。您应该能够看到 RMSE 现在约为 3,122.14。通过添加特征交叉,您能够将 RMSE 降低约 2%,并更接近您的最终目标。
作为练习,在进入下一节之前,探索其他可以进行桶装并与其他特征进行交叉的特征。作为目标,在进入下一节之前,看看能否将您模型的 RMSE 降到 3,000 以下。
超参数调优
在前一节中,您为模型添加了新的有用功能,以降低 RMSE。也许您还没有完全达到$2,000 RMSE 的目标,但您已经取得了良好的进展。接下来您将探索的下一个过程被称为超参数调优。请记住,超参数是在训练过程中不更新的变量,但定义了模型架构(如神经网络中隐藏层的数量或每个隐藏层的神经元数)、特征工程的方式(如多少个桶)以及训练过程的执行方式(如学习率或批量大小)。当您对condition特征进行桶装时,您选择了一些桶的数量。但是您如何知道最佳的桶数量是多少呢?超参数调优的过程旨在帮助您回答这些问题。
超参数调优策略
通常用于超参数调优的三种主要策略是:网格搜索、随机搜索和贝叶斯搜索。对于这三种方法,第一步是相同的。首先,您选择要调整的超参数的候选值范围。您可以根据要调整的超参数选择一个值范围或一个离散的值集合。例如,如果要调整优化器的学习率,您可以设置一个如的范围作为候选范围。在您的情况下,您对condition特征进行了分桶处理,并将桶数设置为 6。这实际上是一个任意选择,并且可能存在更好的选择。例如,您可以将候选范围设置在 5 到 15 之间。
如果您选择的桶数太少,则在模型中处理大范围的条件值时将其视为相同。例如,使用两个桶,所有在3.0和5.0之间的条件值都可能位于同一个桶中。另一方面,如果您的桶数过多,则会有过拟合的风险,因为每个桶中的样本数可能会被模型记住。综上所述,5 到 15 似乎是一个合理的候选范围。
一旦您为希望调整的超参数设置了候选范围,下一步就是选择调整方法。网格搜索方法非常简单,即“尝试一切,找出最有效的方法”。例如,假设您要调整两个超参数。第一个有 4 个候选值,第二个有 3 个候选值,因此有 12 种超参数组合要检查。这在图 8-3 中有可视化表示。

图 8-3. 网格搜索方法的可视化表示,其中调整了两个超参数。
要选择最佳的超参数集,您需要使用训练数据集为每组超参数训练一个模型,并使用验证数据集评估模型。在这种情况下,您确信已经找到了最佳的超参数(在候选范围内),因为您尝试了每一个可能的值。
现在网格搜索方法的缺点应该显而易见了。如果您想调整几个超参数,每个超参数的候选值范围都很小,那么可以接受的模型数量就不多。然而,如果您想调整大量超参数,并且每个超参数都有大量候选值,那么这很快就会变得不可行。例如,如果您有四个超参数要调整,每个超参数有 10 个候选值,那么就有 10,000 个候选模型需要训练。
经常使用两种备选方法。随机搜索方法是一种部分搜索策略,随机选择预设数量的候选模型。这些模型将被训练和比较。这种方法的优点是您可以控制在搜索候选模型集合时花费多少时间和精力,但缺点是您可能因为在随机选择过程中不幸而错过了搜索空间中的最佳模型。
第三种方法是贝叶斯搜索或优化,这是一种更智能的部分搜索方法。该方法的详细细节超出了本书的范围,但核心思想相当简单。首先随机训练少量候选模型作为起始点。根据这些初始模型的评估指标,贝叶斯优化算法选择搜索空间中的下一组候选模型。这些候选模型是基于之前模型的评估指标,预计具有最佳的评估指标。这个过程将在预先设定的步骤中继续进行。下一组候选模型将基于先前候选模型的性能选择。虽然在搜索空间耗尽方面与随机搜索相同的劣势,但好处是这种搜索比随机搜索更“智能”。
在 scikit-learn 中的超参数调优
在 scikit-learn 中,网格搜索和随机搜索策略都很容易实现。在本节的其余部分中,您将实现网格搜索策略的一种变体,以找到模型的更好超参数集。首先,添加一对新的转换器来分桶odometer和year列,并移除数值列的列表,因为这些现在将被分桶为分类列。还要在ColumnTransformer中包含新的KBinsDiscretizer转换器。为方便起见,这里包括相应的代码:
bucket_cond = KBinsDiscretizer(n_bins=10, encode='onehot',
strategy='uniform')
bucket_odo = KBinsDiscretizer(n_bins=10, encode='onehot',
strategy='quantile')
bucket_year = KBinsDiscretizer(n_bins=10, encode='onehot',
strategy='uniform')
categorical_columns = ['make', 'model', 'trim', 'model_trim', 'body',
'transmission', 'state', 'color', 'interior',
'color_interior']
col_transformer = ColumnTransformer(
[('ohe', ohe, categorical_columns),
('minmax', minmax, numeric_columns),
('bucket_cond', bucket_cond, ['condition']),
('bucket_odo', bucket_odo, ['odometer']),
('bucket_year', bucket_year, ['year'])]
)
您将调整以下四个超参数:odometer、condition 和 year 列的桶数,以及OneHotEncoder转换器的最小频率,以便不将某些特征编码为infrequent。在 scikit-learn 中,您需要将候选范围定义为值的字典。因为您正在使用pipeline进行模型和转换,所以语法可能乍看起来有点奇怪。此案例的代码如下:
grid_params = {'col_transformer__bucket_cond__n_bins': range(8,13),
'col_transformer__bucket_odo__n_bins': range(8,13),
'col_transformer__bucket_year__n_bins': range(8,13),
'col_transformer__ohe__min_frequency': range(1,6)
}
字典中有形如 'hyperparameter_name' : candidate_range 的成对数据。超参数范围乍看起来可能有些奇怪,但解析起来并不困难。例如,第一个超参数的名称是 col_transformer__bucket_cond__n_bins。这对应于 col_transformer 的一部分 bucket_cond 转换器的 n_bins 值。相应的候选范围是 bucket_cond 的 n_bins 参数的可能取值列表。range(8,13) 是列表 [8,9,10,11,12] 的一种便捷写法。注意,列表中不包括第二个端点 13。对于 min_frequency 超参数,候选范围是 range(1,6)。
现在候选范围已经定义好,你需要定义策略,然后训练相应的模型——在本例中,有 625 个候选模型,使用你定义的不同超参数选择。这不是一个过多的模型训练数量,但可能需要至少一个小时或更长时间来完全训练它们。Scikit-learn 提供了一种名为减半网格搜索的网格搜索策略的变体。
要执行减半网格搜索,首先对所有候选模型进行训练,但仅使用少量的训练数据。根据这些模型的评估结果,保留候选模型池中的一部分。名称暗示你保留一半,但如果愿意,你可以更积极地减少模型数量。在从候选池中移除模型之后,然后使用更多的训练数据来训练剩余的候选模型。重复整个过程,直到选择出最佳候选模型。
完全有可能,表现良好的模型在部分数据上表现良好,但在整个数据集上表现不佳,并且可能在后续的过程中被淘汰。此外,一个在小数据子集上表现不佳的模型可能在整个训练数据集上表现非常好。在你看到模型利用更多数据改善之前,该候选模型可能已被丢弃。一般来说,除了网格搜索之外的任何方法在这些方面都有一定风险,但是减半网格搜索倾向于比随机搜索更有效地找到最佳候选模型。
在 scikit-learn 中实现减半网格搜索只需几行代码:
from sklearn.experimental import enable_halving_search_cv
from sklearn.model_selection import HalvingGridSearchCV
grid_search = HalvingGridSearchCV(pipeline, grid_params,
cv=3, verbose=1,
scoring='neg_root_mean_squared_error')
在撰写本文时,halving grid search 策略在 scikit-learn 中是实验性的,因此必须通过第一行启用它。第二行导入HalvingGridSearchCV类以执行 halving grid search。第三行是我们创建将执行 halving grid search 的对象的地方。第一个参数是您希望使用的模型或流水线,第二个参数是您之前定义的grid_params字典。关键字参数cv指的是一种称为交叉验证的重采样方法。简而言之,cv对应于每个候选模型使用训练数据集的不同拆分来进行试验的数量。较高的cv值将导致更精确的评估指标,但处理时间更长。verbose参数接受从0到3的整数。数字越高,在调整过程中输出的信息越多。最后,您必须设置您在调整过程中尝试优化的度量标准。由于我们试图优化 RMSE,因此我们使用neg_root_mean_squared_error得分。
注意
您可能会想知道为什么我们在scoring参数中使用负的均方根误差。在统计建模中,得分函数应在模型改进时增加。另一方面,损失函数应在模型改进时减少。scikit-learn 中的超参数调整方法设置为使用得分函数。幸运的是,我们可以通过取负的 RMSE 来得到一个得分函数。
现在您已经准备好进行超参数调整。在定义grid_search对象之后添加以下代码,并执行代码单元以执行超参数调整以找到最佳候选模型:
grid_search.fit(X_train, y_train)
print(grid_search.best_params_)
添加的第二行将打印最佳候选模型的超参数。在 Google Colab 中,超参数调整过程将需要 35-40 分钟。您应该看到类似以下的结果,尽管确切的输出可能会根据采样过程中的随机性而有所不同:
{'col_transformer__bucket_cond__n_bins': 11,
'col_transformer__bucket_odo__n_bins': 12,
'col_transformer__bucket_year__n_bins': 11,
'col_transformer__ohe__min_frequency': 1}
您可以在我们的验证数据集上检查最佳模型的性能,以便与早期模型的性能进行比较。在新单元格中执行以下代码以输出来自网格搜索最佳模型的评估指标(RMSE):
# Load validation dataset in case it is not currently loaded
y_valid = valid_df['sellingprice']
X_valid = valid_df.drop('sellingprice', axis=1)
print('RMSE:', math.sqrt(mean_squared_error(y_valid,
grid_search.predict(X_valid))))
请注意,当您在grid_search方法上调用predict()方法时,它会调用网格搜索中最佳模型的predict()方法。最佳模型的 RMSE 为 2,915.02。与起始值超过 3,300 的 RMSE 相比,这是通过特征工程和超参数调整显著的改进。作为练习,继续尝试实验,看看是否可以找到新的特征并调整任何新出现的超参数,以查看是否可以进一步改进模型。
最后,一旦您认为已经得到了最佳模型,您应该在测试数据集上评估模型。然而,在本章中,您仍将探索使用 Keras 的新模型架构,因此暂时不要执行这一步骤。
Keras 中的模型改进
本节探讨了在 Keras 中为您的汽车拍卖销售价格问题使用不同的神经网络模型架构。您不会回顾之前的特征选择和工程对话,但将介绍 Keras 预处理层作为您在 scikit-learn 中使用的转换器的类比。重新创建 scikit-learn 管道的特征工程部分后,您将学习如何使用 Keras Tuner 包和前一节讨论的贝叶斯优化方法进行超参数调整。
Keras 中的预处理层介绍
Keras 预处理层允许您将数据预处理功能轻松地构建到模型函数中,就像您在 scikit-learn 中创建管道一样。在第七章和前一节中,您看到将预处理逻辑包含到模型本身中是多么方便。尽管在前一节中您没有导出模型,但您可以像在第七章中使用joblib库一样轻松导出整个训练过程中的管道。
请回顾您在前一节中对数据集执行的转换。您进行了分类特征的独热编码,对数值特征进行了分桶,并创建了特征交叉。在开始在 Keras 中构建模型之前,了解您将使用的预处理层是很重要的。
Discretization 层在 Keras 中用于像 scikit-learn 中的 KBinsDiscretizer 转换器一样对数值特征进行分桶。您可以提供桶的端点或使用 adapt() 方法,让 Keras 基于数据和指定的桶数量选择端点。使用 adapt() 方法时,您必须指定希望使用的数据集。此数据集中的值范围将用于选择桶的边界。通常情况下,您应该使用训练数据集或其代表性样本进行 adapt() 方法。
注意
adapt() 方法的结果是一个边界点列表,对应于您选择的桶的数量。最左边的边界点实际上是一个桶的右端点,而最右边的边界点实际上是另一个桶的左端点。
例如,如果将桶的数量设置为四个并收到边界点 [0.0, 1.0, 2.0],那么实际的桶是 (-inf, 0.0)、[0.0, 1.0)、[1.0, 2.0) 和 [2.0, +inf)。换句话说,所有小于 0 的值将属于第一个桶,所有大于 2.0 的值将属于最后一个桶。
另一个与 scikit-learn 中正在进行的转换对应的预处理层是StringLookup层。StringLookup层用于对具有字符串值的分类列进行编码。您可以以不同的方式编码值,但是您将在模型中使用独热编码。另一个选项是将列编码为整数,然后在后续层中执行独热编码或其他可能的转换。
最后,在预处理 scikit-learn 中特征时,还执行了特征交叉。在 scikit-learn 中,这是一个相对手动的过程:您将每个特征的值对应的字符串串联起来,然后对串联的值进行独热编码。在 Keras 中,有一个处理特征交叉的预处理层,称为HashedCrossing层。HashedCrossing层接受两个分类特征,并为您创建特征交叉。
还有许多有用的预处理层可以探索。有关更多层的详细信息,请参阅 TensorFlow 文档中的“使用预处理层”指南。
创建模型的数据集和预处理层
现在,您将重新创建在 scikit-learn 中创建的预处理管道,以便可以在 Keras 中探索新的模型架构。
返回至https://colab.research.google.com。打开一个新笔记本并命名笔记本为keras_model.ipynb。您将在接下来的几个部分中向此笔记本添加代码,但如果遇到困难,chapter8目录中也有一个名为keras_model.ipynb的解决方案笔记本。
首先,像以前在 scikit-learn 中一样,将训练和验证数据集导入到 DataFrames 中。还将数据集拆分为特征的 DataFrame 和标签的系列。如果需要帮助,这里是解决方案代码:
import pandas as pd
!wget -q https://storage.googleapis.com/low-code-ai-book/car_prices_train.csv
!wget -q https://storage.googleapis.com/low-code-ai-book/car_prices_valid.csv
!wget -q https://storage.googleapis.com/low-code-ai-book/car_prices_test.csv
train_df = pd.read_csv('./car_prices_train.csv')
y_train = train_df['sellingprice']
X_train = train_df.drop('sellingprice', axis=1)
valid_df = pd.read_csv('./car_prices_valid.csv')
y_valid = valid_df['sellingprice']
X_valid = valid_df.drop('sellingprice', axis=1)
您需要在 Keras 中准备输入特征。当在不同特征上使用不同的预处理层时,无法像您在第七章中使用的 Sequential API 一样在 Keras 中构建神经网络。替代 API 是 Functional API,使用起来非常类似,只是语法略有不同。要使用 Functional API,您需要首先为每个输入特征创建一个Input。首先,将以下代码复制到笔记本中的新单元格中,并执行该单元格:
import tensorflow as tf
from tensorflow.keras.layers import (StringLookup, HashedCrossing,
Discretization, Concatenate)
cat_cols = ['make', 'model', 'trim', 'body', 'transmission', 'state',
'color', 'interior']
num_cols = ['odometer', 'year', 'condition']
inputs = {}
for col in cat_cols:
inputs[col] = tf.keras.Input(shape=(1,), name=col,
dtype = tf.string)
for col in num_cols:
inputs[col] = tf.keras.Input(shape=(1,), name=col, dtype = tf.int64)
在继续之前,请花点时间解析这段代码。首先,导入你稍后将利用的预处理层。然后,像在 scikit-learn 中那样,将列列表分为数值列和分类列(分别为 num_cols 和 cat_cols)。然后,创建一个空字典用于 Input 层。接着,为每个特征创建一个 Input。for col in cat_cols 语句意味着以下代码将针对 cat_cols 列表中的每一列执行。tf.keras.Input 是 Input 层的完整名称。第一个参数 shape 表明,对于每个示例,每个特征将只是一个单一值。你将每个 Input 的名称设置为 cat_cols 中对应的列名。最后,设置数据类型 (dtype) 为 tf.string,这是 TensorFlow 中字符串数据类型的实现。对于 num_cols 列,概念相同,只是数据类型设置为 tf.int64,即 TensorFlow 中 64 位整数的实现。
现在 Input 层已创建完成,你可以开始创建预处理层。首先,使用以下代码对每个分类列进行 one-hot 编码:
preproc_layers = {}
for col in cat_cols:
layer = StringLookup(output_mode='one_hot')
layer.adapt(X_train[col])
preproc_layers[col] = layer(inputs[col])
首先,创建一个空字典来保存预处理中将使用的层。然后,为每个分类列创建一个 StringLookup 层,并将 output_mode 设置为 'one_hot',以便进行 one-hot 编码输出。然后,使用训练数据集中相应列上的 adapt() 方法来学习 one-hot 编码的词汇表。请注意,如果在模型中转换数据时出现未知值,将分配一个未知值 '[UNK]'。可以设置未知值处理的行为。最后,在训练和预测时为列指定输入,并将其存储在字典 preproc_layers 中。有关更多详细信息,请参考 StringLookup 文档。
接下来是用于分桶数值列的 Discretization 层。使用以下代码创建 Discretization 预处理层:
for col in num_cols:
layer = Discretization(num_bins=10,
output_mode='one_hot')
layer.adapt(X_train[col])
preproc_layers[col] = layer(inputs[col])
这里的想法与之前类似,对于每个数值列,你创建一个 Discretization 层。每个层(暂时)将数据分为 10 个桶,然后进行桶成员的 one-hot 编码。最后,使用 adapt() 方法将桶化适应于各个列。
你的 scikit-learn 模型执行的最后一种特征工程类型是特征交叉。现在,使用以下代码重新创建这些特征,使用 HashedCrossing 层:
model_trim=tf.keras.layers.HashedCrossing(num_bins=1000, output_mode='one_hot')(
(inputs['model'], inputs['trim']))
color_int=tf.keras.layers.HashedCrossing(num_bins=400, output_mode='one_hot')(
(inputs['color'], inputs['interior']))
preproc_layers['model_trim'] = model_trim
preproc_layers['color_int'] = color_int
请注意,HashedCrossing列与我们在 scikit-learn 中执行特征交叉略有不同。哈希函数是一种特殊类型的函数,它接受字符串输入并返回一个整数。输出是确定性的,即当输入相同的字符串时,始终会得到相同的输出整数,但输出以一种几乎不可能预测的方式分布。HashedCrossing列接收哈希函数的输出,并使用它选择一个桶来放置相应的元素。对于model_trim层,有 1,000 个桶,对于color_interior层,有 400 个桶。
这些数字从何而来?嗯,我们的数据集中可能有超过一百万种不同的模型和修剪组合。同样,颜色和内饰值可能有大约 300 种组合。由于值的分布是有效随机的,可能会有多个值最终落入同一个桶中。过度估计桶的数量有助于降低这种情况发生的可能性。不过,存在一个权衡:每个桶对应于模型中的一个特征,并且这取决于第一个隐藏层中有多少神经元的多个权重。这种权衡是我们选择在model_trim特征中使用 1,000 个桶而不是包括一百万个桶的原因。这种权衡也使得桶的数量成为超参数调优的一个很好的选择。
建立神经网络模型
现在您已经创建了预处理层,是时候将所有内容组合在一起了。首先要做的是将所有预处理层连接成一个单独的层,以输入到神经网络中。使用以下代码执行此任务:
prepared_layer = Concatenate()(preproc_layers.values())
prepared_layer = tf.reshape(prepared_layer, [-1,3903])
这段代码非常直观:您创建一个Concatenate层,然后给它一个输入层列表。由于您已经在字典中创建了所有预处理层,因此只需提取字典的值即可。prepared_layer是一个长度为3903的张量,考虑了所有可能的特征值,用于独热编码和桶化特征。第二行将prepared_layer重塑为二阶张量,这是 Functional API 中下一层所期望的。
将所有输入作为一个单独的层后,构建模型的其余过程与第七章中基本相同。在 Keras 的 Functional API 中有一点点不同,但在看过代码之后很容易解释:
hid_1 = tf.keras.layers.Dense(16, activation='relu')(prepared_layer)
hid_2 = tf.keras.layers.Dense(16, activation='relu')(hid_1)
output = tf.keras.layers.Dense(1)(hid_2)
model = tf.keras.Model(inputs=inputs, outputs=output)
第一行创建了一个新的层hid_1,它是一个具有 16 个神经元和 ReLU 激活的密集层。在 Functional API 中,您必须为每个层指定一个输入,就像为函数一样。在本例中,这将是之前的prepared_layer。接下来,您定义第二层hid_2,具有与第一隐藏层相同的参数,但以hid_1作为输入层。最后,您将输出层定义为一个具有单个输出神经元且没有激活函数的密集层。请记住,对于回归模型,您的输出应该是一个单一的预测值。
现在您需要创建Model对象。您可以使用tf.keras.Model来做到这一点,并指定模型的输入(之前定义的inputs)和模型的输出(output层)。从这里开始,过程与第七章中的过程基本相同,有一些细微的差别。使用以下代码来编译和训练模型:
model.compile(optimizer='adam', loss='mse')
train_ds = tf.data.Dataset.from_tensor_slices(
(dict(X_train), y_train)).batch(100)
valid_ds = tf.data.Dataset.from_tensor_slices(
(dict(X_valid), y_valid)).batch(1000)
history = model.fit(
x=train_ds,
epochs=25,
verbose=1,
validation_data=valid_ds
)
首先,您需要编译模型,将优化器设置为 Adam 优化器,损失函数设置为均方误差(MSE)。接下来,您从相应的 DataFrames 创建用于训练和验证的tf.Datasets。将批量大小设置为100用于训练和1000用于验证。要训练模型,您像以前一样使用fit()方法。
您的模型性能可能会因初始化和训练神经网络时涉及的随机性而有所不同,但在训练完成后,您应该看到约$10,719,103 的 MSE,这相当于$3,274 的 RMSE。性能与在 scikit-learn 中进行超参数调整之前的模型性能类似。请注意,由于神经网络初始化的随机性可能会导致您的 MSE 有所不同。神经网络架构的选择是任意的,因此可能仍有改进的空间。
在 Keras 中进行超参数调整
现在您在 Keras 中有一个可工作的模型,是时候开始改进它了。在构建 Keras 模型时,您可以使用 Keras Tuner 包轻松进行超参数调整。
Google Colab 默认不包含 Keras Tuner 包,但安装起来很容易。pip(递归缩写,意为 Pip Installs Packages)是 Python 的包管理工具,用于安装和管理包。pip install命令允许您从 Python Package Index 或 PyPI 下载和安装包。在新的单元格中运行以下命令以安装 Keras Tuner 包:
!pip install -q keras-tuner
pip是一个命令行工具,因此您像以前一样使用!行魔术将该行作为 bash 命令运行。-q标志抑制了安装过程中大部分输出,以避免在笔记本环境中产生混乱。现在 Keras Tuner 已安装完成,您可以开始修改模型代码以准备进行超参数调整。
使用 Keras Tuner 时,需要创建一个函数(称为build_model),该函数以超参数作为输入,并返回已编译的模型。对于每个候选模型,此函数将使用不同的超参数执行,以创建用于训练的模型。正如您之前注意到的,对所有预处理层执行adapt()方法需要几分钟的时间,因此理想情况下,您将将此代码放在build_model函数之外。使用以下代码为 Keras Tuner 创建build_model函数:
import keras_tuner as kt
from functools import partial
def _build_model_fn(hp, prepared_layer):
units_1 = hp.Int('units_1', min_value=8, max_value=64, step=4)
units_2 = hp.Int('units_2', min_value=4, max_value=64, step=4)
units_3 = hp.Int('units_3', min_value=4, max_value=32, step=2)
hid_1 = tf.keras.layers.Dense(units_1,
activation='relu')(prepared_layer)
hid_2 = tf.keras.layers.Dense(units_2, activation='relu')(hid_1)
hid_3 = tf.keras.layers.Dense(units_3, activation='relu')(hid_2)
output = tf.keras.layers.Dense(1, activation='linear')(hid_3)
model = tf.keras.Model(inputs=inputs, outputs=output)
model.compile(optimizer='adam', loss='mse')
return model
build_model = partial(_build_model_fn, prepared_layer=prepared_layer)
首先导入keras_tuner包和partial函数,两者稍后将被使用。接下来,您定义了一个“中间”函数:_build_model_fn。函数名称开头的下划线是 Python 的约定,表示这是一个不应直接使用的函数。请注意,此函数有两个参数,hp和prepared_layer。hp参数将由 Keras Tuner 提供,prepared_layer参数将对应您之前创建的同名层。
units_1 = hp.Int('units_1', min_value=8, max_value=64, step=4)这一行展示了如何使用 Keras Tuner 定义超参数的示例。hp.Int定义了一个整数值超参数。您还可以定义浮点数超参数(hp.Float)、布尔型超参数(hp.Boolean)或从可能值列表中选择(hp.Choice)。有关更多详细信息,请参阅Keras Tuner 文档。
对于整数超参数,您设置最小值、最大值和步长。因此,在本例中,可能的值为 8、12、16、20、...、64。在前面的代码中,您创建了三个超参数:units_1、units_2和units_3。接下来,为模型定义三个隐藏层。请注意,对于每个隐藏层,神经元的数量被替换为之前定义的hp.Int对象。否则,该过程与您用于构建和编译模型的代码类似。_build_model_fn函数将编译后的模型作为输出返回。
build_model函数只需接受hp作为参数,以供 Keras Tuner 使用。这就是partial函数的作用所在。partial函数允许您从旧函数创建新函数,但某些固定参数已经插入到原始函数中。partial(_build_model_fn, prepared_layer=prepared_layer)接受_build_model_fn函数,并创建一个新函数,其中您的prepared_layer层始终插入到相应的参数中。
现在已经创建了build_model函数,接下来创建调整器,用于管理超参数调优过程。使用以下代码创建tuner对象,并执行超参数搜索:
tuner = kt.BayesianOptimization(
build_model,
objective=kt.Objective("val_loss", direction="min"),
max_trials=20)
tuner.search(
x=train_ds,
epochs=5,
verbose=1,
validation_data=valid_ds)
tuner是 Keras Tuner 中使用贝叶斯优化来优化超参数的一个示例Tuner。您可以创建一个Objective来定义调整过程的目标。在本例中,您希望最小化验证数据集的损失(MSE),因此将val_loss设置为目标,并将direction设置为min以指定您希望最小化val_loss。您还设置了最大试验或候选模型的数量。
要执行调整过程,您可以在tuner上使用search()方法。您需要指定训练数据集、训练候选模型的周期数、详细程度(从 0 到 3 的程度)和验证数据集。请注意,这里的周期数相对较少,因为您正在训练许多模型。通常情况下,您可以在仅进行几个周期的训练后就了解哪些模型将表现更好,而不必将它们训练至收敛。您的输出和结果应与图 8-4 中的类似。

图 8-4. 使用 Keras Tuner 进行超参数调整过程的输出示例。
由于过程中的某些随机性,你的确切结果会有所不同,但可能最佳模型的val_loss约为$6,000,000,对应 RMSE 为$2,470。这比之前的模型结果有所改进,甚至仅经过五次周期后也是如此。现在,你应该继续训练这个最佳候选模型,看看是否能获得更好的结果。为此,你需要能够检索最佳超参数。在新单元格中执行以下代码以找到最佳候选模型的超参数:
best_hps=tuner.get_best_hyperparameters(num_trials=1)[0]
print('units_1:', best_hps.get('units_1'))
print('units_2:', best_hps.get('units_2'))
print('units_3:', best_hps.get('units_3'))
由于过程中的随机性,每次找到的最佳超参数可能会有所不同。在本章讨论的运行中,units_1、units_2和units_3的最佳值分别为 52、64 和 32。
为了简化操作,Keras Tuner 包括了tuner.hypermodel.build()方法,我们可以在其中提供最佳超参数,它将这些值传递给build_model方法以重新创建我们的最佳候选模型。使用以下代码来实现这一点,创建一个早期停止的回调,并训练最佳模型直到val_loss不再改善:
best_model = tuner.hypermodel.build(best_hps)
early_stopping = tf.keras.callbacks.EarlyStopping(monitor='val_loss',
patience=5)
history = best_model.fit(
x=train_ds,
epochs=1000,
verbose=1,
callbacks = [early_stopping],
validation_data=valid_ds
)
训练模型后,验证 RMSE 进一步降低,降至 2000 以下。最终你得到了一个符合初衷的模型!作为练习,实现其他超参数的超参数调整,例如HashedCrossing层的箱数。
然而,我们选择的模型基于验证数据集的性能,所以我们可能只是简单地选择了一个偏向于该数据集的模型。这就是测试数据集的用武之地。测试数据集在模型训练过程中从未被使用过,因此它是我们最接近“野外”数据或您的模型在生产中可能看到的数据。
由于我们已经选择了最终的模型,我们可以使用测试数据集作为性能的最终验证。为此,请使用以下代码:
test_df = pd.read_csv('./car_prices_test.csv')
y_test = test_df['sellingprice']
X_test = test_df.drop('sellingprice', axis=1)
test_ds = tf.data.Dataset.from_tensor_slices(
(dict(X_test), y_test)).batch(1000)
best_model.evaluate(test_ds)
您的模型在测试数据集上表现如何?如果表现相似,那么您的模型表现良好,准备好进行部署。否则,您可能需要重新组合数据集,进行新的训练-验证-测试数据集拆分,并从头开始整个过程。在这个过程中,请确保您的训练、验证和测试数据集具有相似的示例分布。实际上,不同数据集之间的不同分布是在测试数据集评估时看到性能大幅下降的非常常见原因。
如果您需要继续改进模型的性能,重新开始可能会令人沮丧,但一旦测试数据集的独立性被使用来做出决策所破坏,这是最佳的方法。
BigQuery ML 中的超参数调优
在这一节中,您将重新访问在 BigQuery ML 中创建的 scikit-learn 和 Keras 模型。您将把之前使用的汽车拍卖价格数据集加载到 BigQuery 中,探索 BigQuery ML 中的特征工程,并训练一个神经网络模型。最后,您将学习如何在 BigQuery ML 中进行超参数调优。
在本章中,我们不会再对 BigQuery 和 BigQuery ML 的概念进行全面审查,请参考第六章了解本章中执行的某些任务的详细信息。
加载和转换汽车拍卖数据
首先,转到Google Cloud 控制台,然后导航到 BigQuery(可以使用侧边菜单或搜索栏)。在项目 ID 的右侧的“资源管理器”中,单击“查看操作”按钮,该按钮由项目 ID 右侧的三个垂直点表示。然后单击“创建数据集”。UI 中这些项目的位置提示如图 8-5 所示。

图 8-5. BigQuery UI 中查看操作按钮和“创建数据集”操作的位置。
在美国地区创建一个名为 car_sales_prices 的新数据集。创建数据集后,您可以使用数据集旁边的“查看操作”按钮创建一个 BigQuery 表。选择数据集,单击“查看操作”,然后选择“创建表格”。根据 表 8-3 中的信息创建三个表,每个表对应一个数据集。请注意,您需要将“从 GCS 存储桶中选择文件或使用 URI 模式”和“表格”字段中的 *<dataset>* 部分替换为 train、valid 和 test,以适应三个不同的数据集。
表 8-3. 要创建的三个表的选项
| 字段 | 值 |
|---|---|
| 从中创建表格 | Google Cloud Storage |
| 从 GCS 存储桶中选择文件或使用 URI 模式 | low-code-ai-book/car_prices_ |
| 文件格式 | CSV |
| 表格 | car_prices_ |
| 模式 | 自动检测 |
在开始构建模型之前,您需要复制在 scikit-learn 和 Keras 中执行的转换操作。首先,回想一下,您对分类列执行了独热编码。请记住,在 BigQuery ML 中,所有字符串值列都会自动进行独热编码,因此对于这些列,您无需进行任何操作。
那么您如何处理桶化的数值列呢?BigQuery ML 提供了两个用于桶化数值特征的函数。首先是 ML.BUCKETIZE 函数,它接受两个参数。第一个参数是您希望进行桶化的列,第二个参数是您提供的桶端点的列表。请注意,您需要事先知道希望使用的桶。
还有 ML.QUANTILE_BUCKETIZE 函数。该函数同样需要两个参数。第一个参数再次是您希望进行分桶的列,但第二个列现在是您希望将数据分成的桶的数量。 ML.QUANTILE_BUCKETIZE 将根据您指定的桶数将数据分成基于分位数的桶。例如,如果您指定四个桶,那么第一个四分位数(数据的 25%)将被放入第一个桶中,第二四分位数将被放入第二个桶中,依此类推。这些函数的实际输出将采用 bin_n 的形式,用于放入第 n 个桶的数据,然后 BigQuery ML 将像对待任何其他字符串列一样对此列进行独热编码。
您执行的最终转换是特征交叉。在 BigQuery ML 中实现特征交叉的函数是 ML.FEATURE_CROSS。该函数接受一个特征列的 STRUCT 并返回这些列的特征交叉。如果您只提供一对列,那么它将返回这两列的特征交叉。如果您提供三列,则将收到三个特征交叉,分别对应每对可能的列。
ML.FEATURE_CROSS 的语法起初可能看起来有些奇怪:
ML.FEATURE_CROSS(STRUCT(column1,column2))
创建STRUCT关键字用于创建STRUCT,这是可能包含不同类型列的有序列。如果没有这个关键字,您将从这行代码中收到错误信息。
现在,您可以预处理您的数据。在 BigQuery 控制台中编写并执行以下 SQL 查询,执行所需的转换:
SELECT
* EXCEPT (int64_field_0, mmr, odometer, year, condition),
ML.QUANTILE_BUCKETIZE(odometer, 10) OVER() AS odo_bucket,
ML.QUANTILE_BUCKETIZE(year, 10) OVER() AS year_bucket,
ML.QUANTILE_BUCKETIZE(condition, 10) OVER() AS cond_bucket,
ML.FEATURE_CROSS(STRUCT(make,model)) AS make_model,
ML.FEATURE_CROSS(STRUCT(color,interior)) AS color_interior
FROM
`car_sales_prices.car_prices_train`
LIMIT 10
SELECT * EXCEPT(...)语句返回表中除了列出的列之外的所有列。这里的int64_field_0是之前的Unnamed: 0列的名称。您还想删除mmr列,因为您将不会将其用于训练。最后,您之前没有使用odometer、year和condition的数值,因为您已经对这些特征进行了桶化,所以在结果中不会返回这些特征。
接下来,您使用ML.QUANTILE_BUCKETIZE和 10 个桶对odometer、year和condition列进行桶化。最后的OVER()子句允许您将数据分割成不同的集合(基于OVER语句内部),然后进行分位数桶化。在这里,您只是进行分位数桶化,没有额外的分割。
最后,您可以使用ML.FEATURE_CROSS实现特征交叉。对于本例,您有LIMIT 10语句,因此可以查看数据的前几行。显示的结果示例可参见表 8-4。
表 8-4. ML.QUANTILE_BUCKETIZE 和 ML.FEATURE_CROSS 转换的预处理查询输出
odo_bucket |
year_bucket |
cond_bucket |
make_model |
color_interior |
|---|---|---|---|---|
bin_10 |
bin_1 |
bin_1 |
Nissan_300ZX |
red_red |
bin_7 |
bin_1 |
bin_1 |
Chevrolet_Corvette |
red_— |
bin_10 |
bin_1 |
bin_2 |
Lexus_LS 400 |
silver_silver |
bin_10 |
bin_1 |
bin_4 |
Jeep_Cherokee |
white_gray |
bin_9 |
bin_1 |
bin_2 |
Mazda_MX-5 Miata |
red_blue |
bin_10 |
bin_1 |
bin_2 |
Honda_Accord |
blue_— |
注意,对于桶化的列,输出是预期的bin_n形式。此外,特征交叉列的输出形式为value1_value2。这些串联值将由 BigQuery ML 进行独热编码,与您在本章早些时候在 scikit-learn 中所做的非常相似。
训练线性回归模型并使用 TRANSFORM 子句
现在,您可以使用前面编写的查询来训练线性回归模型,以预处理数据。请注意,如果您使用该查询转换数据,保存结果,然后使用新表训练模型,一切都能正常工作。然而,在预测时,您必须执行相同的转换。当您不知道独热编码的确切方式或者桶化的分桶端点时,这变得非常棘手。
BigQuery ML 提供了TRANSFORM子句,使您能够将这些转换集成到模型中。CREATE MODEL语句的整体结构如下:
CREATE OR REPLACE MODEL `dataset.model_name`
TRANSFORM (<transformation_sql>)
OPTIONS (<model_options>)
AS SELECT …
<transformation_sql>是前面查询的SELECT部分,其中指定了要使用的列及其上的转换。编写并执行以下 SQL 语句,使用TRANSFORM子句训练线性回归模型:
CREATE OR REPLACE MODEL
`car_sales_prices.linear_car_model`
TRANSFORM (
* EXCEPT (int64_field_0, mmr, odometer, year, condition),
ML.QUANTILE_BUCKETIZE(odometer, 10) OVER() AS odo_bucket,
ML.QUANTILE_BUCKETIZE(year, 10) OVER() AS year_bucket,
ML.QUANTILE_BUCKETIZE(condition, 10) OVER() AS cond_bucket,
ML.FEATURE_CROSS(STRUCT(make,model)) AS make_model,
ML.FEATURE_CROSS(STRUCT(color,interior)) AS color_interior)
OPTIONS (
model_type='linear_reg',
input_label_cols=['sellingprice'],
data_split_method='NO_SPLIT') AS
SELECT
*
FROM
`car_sales_prices.car_prices_train`;
这个查询应该在大部分情况下都与您之前在第六章中所做的类似,只有一些变化。首先,包含了TRANSFORM子句以将转换逻辑构建到模型中,以便在推理时引用。当调用ML.PREDICT来提供预测时,TRANSFORM子句将在输入表上执行,然后传递给模型进行预测。这意味着像桶端点这样的事物现在将被包含在模型本身中。在 scikit-learn 和 Keras 中,您使用管道和预处理层来管理此过程。
你可能注意到的另一件事是,出现了一个新选项。data_split_method选项决定了数据在训练和验证中的拆分方式。由于你已经有了一个单独的验证数据集,所以采用NO_SPLIT选项来使用整个训练数据集进行训练。你可以使用以下 SQL 语句对已训练的模型进行验证:
SELECT SQRT(mean_squared_error)
FROM ML.EVALUATE(MODEL `car_sales_prices.linear_car_model`,
(SELECT * FROM `car_sales_prices.car_prices_valid`))
由于您之前使用过 RMSE 进行评估,因此在这里为保持一致性,您将再次使用它。这里的 RMSE 可能会相当高,可能超过 $8,000. 您可以通过运行以下查询来检查训练集的 RMSE:
SELECT SQRT(mean_squared_error)
FROM ML.EVALUATE(MODEL `car_sales_prices.linear_car_model`,
(SELECT * FROM `car_sales_prices.car_prices_train`))
训练数据集上的 RMSE 将接近于 $3,000,并且与您之前的 scikit-learn 模型预期一致。这是过拟合的一个典型例子,但是它是从哪里来的呢?特征交叉涉及大量可能的值,因此导致模型的特征数量非常多。您可以通过运行以下查询计算来自特征交叉的特征数量:
SELECT
COUNT(ML.FEATURE_CROSS(STRUCT(color,interior))) +
COUNT(ML.FEATURE_CROSS(STRUCT(make,model)))
FROM
`car_sales_prices.car_prices_train`
您将看到特征交叉中存在 770,000 个不同的特征值。与示例数相比,这么多的特征数量很容易导致过拟合。在接下来的部分,您将了解到正则化技术如何处理大量特征的过拟合问题。
最后,您可以像在第六章中之前一样使用模型进行预测:
SELECT *
FROM ML.PREDICT(MODEL `car_sales_prices.linear_car_model`,
(SELECT * FROM `car_sales_prices.car_prices_valid`))
您可以通过简单更改选项来训练深度神经网络回归模型,如下所示的 SQL 语句所示:
CREATE OR REPLACE MODEL
`car_sales_prices.dnn_car_model`
TRANSFORM (
* EXCEPT (int64_field_0, mmr, odometer, year, condition),
ML.QUANTILE_BUCKETIZE(odometer, 10) OVER() AS odo_bucket,
ML.QUANTILE_BUCKETIZE(year, 10) OVER() AS year_bucket,
ML.QUANTILE_BUCKETIZE(condition, 10) OVER() AS cond_bucket,
ML.FEATURE_CROSS(STRUCT(make,model)) AS make_model,
ML.FEATURE_CROSS(STRUCT(color,interior)) AS color_interior)
OPTIONS (
model_type='dnn_regressor',
hidden_units=[64, 32, 16],
input_label_cols=['sellingprice'],
data_split_method='NO_SPLIT') AS
SELECT
*
FROM
`car_sales_prices.car_prices_train`;
在 BigQuery ML 中配置超参数调优作业
编写训练模型的代码后,只需进行少量修改即可开始超参数调整。首先,您需要包含一个新的选项 num_trials。此选项设置在超参数调整过程中将训练的不同模型数量。您还可以选择为 num_parallel_trials 选项设置一个值。这将允许您同时并行运行多个试验。用于训练所有模型的总资源数量将保持不变,但能够同时运行多个模型将总体花费的时间缩短。然而,使用 BigQuery ML 和 Vertex AI 中实现的贝叶斯优化时存在权衡。您运行的并行试验越多,直到达到最大试验次数为止,贝叶斯优化学习的迭代次数就越少。
在设置了 num_trials 选项之后,下一步是设置超参数。在 BigQuery ML 中,只能调整某些超参数。对于深度神经网络(DNN)模型,您可以调整 batch_size、dropout、hidden_units、learn_rate、optimizer、l1_reg、l2_reg 和 activation_fn。在这里,您将专注于 dropout、l1_reg 和 hidden_units,但您可以作为练习探索其他超参数。
正则化
您在之前的示例中熟悉了 hidden_units。但 dropout 和 l1_reg 呢?Dropout 是一种正则化技术。通常,正则化技术用于减少模型过拟合的风险。过拟合 是指模型在训练数据集上的表现远远优于评估数据集。这通常发生因为模型“记住”了数据集,开始错过在其他数据集上表现良好所需的一般模式。减少过拟合风险的主要方式之一是降低模型的复杂性。
L1 和 L2 正则化通常是机器学习实践者首先学习的正则化技术。假设您有损失函数 。回想一下,训练过程的目标是最小化这个损失函数。L1/L2 正则化的想法是在损失函数中添加一个额外的项,以强制学习算法在最小化原始损失函数和新的“惩罚项”之间取得平衡。让 表示模型中所有权重的平方和。对于 L2 正则化,新的损失函数如下所示:
大致的思路是,为了使模型变得更复杂,权重值需要变得更大,以对结果产生更大的影响。这个新的损失函数平衡了原始损失函数和模型复杂性(由度量)之间的关系。被称为正则化率,它控制原始损失函数与模型复杂性之间的权衡程度。的值越高,在训练过程中惩罚模型的复杂性就越严重。类似于 L1 正则化,L2 正则化中的被所有权重的绝对值之和所取代。这些正则化类型可以结合在一起,称为弹性网络正则化。弹性网络正则化的相应损失函数如下:
注意,和是分别控制 L1 和 L2 正则化影响的不同常数。
你现在知道了 L1 和 L2 正则化的定义,但它们对模型的实际影响是什么?这方面的数学超出了本书的范围,虽然并不太复杂,但最终的效果很容易描述。L2 正则化倾向于将权重推向较小的值。L1 正则化倾向于将对模型性能不重要的权重推向零。当你有大量稀疏特征时,这可能非常有价值。例如,当你创建具有大量值的两个特征的特征交叉时。这正是你在 BigQuery ML 中训练的线性回归模型中遇到的情况。通常,在使用特征交叉时,包括 L1 正则化通常是个好主意。正则化参数控制训练过程中权重值推动的侵略性。
Dropout 是一种不同类型的正则化,因为它是在模型本身在训练过程中应用的,而不是在损失函数上。在神经网络中使用 dropout 的想法是,每批数据中有一定百分比的神经元被“关闭”。这里所说的“关闭”是指在特定数据批次中,隐藏层中某些神经元的加权总和被设置为零。使用 dropout 这样的技术的目标是在训练时阻碍模型的复杂性。这样可以避免模型变得过于复杂,同时仍然让模型更多地学习数据。然而,在预测时,不会使用 dropout,这样可以充分利用模型的能力。
注意
在过去的十年中,研究人员发现,在预测时使用dropout同样是有益的¹。这可以用作表示模型不确定性的一种方式,适用于分类和回归任务,并使模型的预测变得非确定性。
在CREATE MODEL语句中使用超参数调优。
现在你对正则化有了一些了解,是时候在 BigQuery ML 中设置超参数调优了。首先考虑以下 SQL 语句:
CREATE OR REPLACE MODEL
`car_sales_prices.dnn_hp_car_model`
TRANSFORM (
* EXCEPT (int64_field_0, mmr, odometer, year, condition),
ML.QUANTILE_BUCKETIZE(odometer, 10) OVER() AS odo_bucket,
ML.QUANTILE_BUCKETIZE(year, 10) OVER() AS year_bucket,
ML.QUANTILE_BUCKETIZE(condition, 10) OVER() AS cond_bucket,
ML.FEATURE_CROSS(STRUCT(make,model)) AS make_model,
ML.FEATURE_CROSS(STRUCT(color,interior)) AS color_interior)
OPTIONS (
model_type='dnn_regressor',
optimizer='adagrad',
hidden_units=hparam_candidates([STRUCT([64,32,16]),
STRUCT([32,16]),
STRUCT([32])]),
l1_reg=hparam_range(0,1),
dropout=hparam_range(0,0.8),
input_label_cols=['sellingprice'],
num_trials = 10,
hparam_tuning_objectives=['mean_squared_error'])
AS SELECT
*
FROM
`car_sales_prices.car_prices_train`;
创建超参数调优作业的语句与之前使用的非常相似,但为了进行超参数调优,有一些关键区别。首先,请注意hidden_units选项。不再仅仅是一个隐藏层单元的列表,而是有了hparam_candidates函数。此函数接受一个包含相应超参数调优值的结构体列表,并在调优过程中将它们传递给模型。在这里,你让模型在三种可能性之间决定最佳架构。第一种是具有 64 个神经元的神经网络,第二层有 32 个神经元,第三层有 16 个神经元。第二个选项有两个隐藏层,分别有 32 个和 16 个神经元。最后一个选项只有一个隐藏层,有 32 个神经元。此外,你正在使用hparam_range来搜索最佳的l1_reg和dropout值。hparam_range用于在浮点值范围内查找最佳值。例如,在这里,dropout的范围在 0 到 0.8 之间,表示在训练时影响隐藏层神经元的百分比。
最后,在开始训练之前,有几个需要设置的新选项。首先是num_trials,前面提到过,以及hparam_tuning_objectives。你希望优化 RMSE,所以将hparam_tuning_objectives设置为mean_squared_error。如果还没有开始,现在可以开始调优过程。这个调优过程大约需要一个小时来完成。
注意
在超参数调优作业的查询中,您必须使用 optimizer='adagrad' 选项指定正在使用的优化器。默认优化器 adam 不支持 L1 正则化。有关更多详细信息,请参阅 BigQuery ML 文档中关于 创建 DNN 模型 的内容。
一旦训练过程完成,您可以通过执行以下查询来探索试验结果和选择的超参数:
SELECT
*
FROM
ML.TRIAL_INFO(MODEL `car_sales_prices.dnn_hp_car_model`)
ORDER BY
hparam_tuning_evaluation_metrics.mean_squared_error ASC
你的输出示例如 表 8-5 所示。
表 8-5. 五个最佳试验的试验信息查询结果—注意所选的超参数和试验指标(您的输出中的确切值将与此处显示的不同;一些列名为了可读性而被压缩)
trial_id |
l1_reg |
hidden_units |
dropout |
mean_squared_error |
|---|---|---|---|---|
| 10 | 1.0 | 64 | 0.0 | 194784591.6 |
| 32 | ||||
| 16 | ||||
| 8 | 0.00031591034078693391 | 64 | 0.0 | 213445602.34905455 |
| 32 | ||||
| 16 | ||||
| 9 | 1.0 | 64 | 0.25599690406708309 | 218611976.60226983 |
| 32 | ||||
| 16 |
如果您选择 car_sales_prices.dnn_hp_car_model 作为模型,使用 ML.PREDICT,BigQuery 将默认使用最佳试验:
SELECT *
FROM ML.PREDICT(MODEL `car_sales_prices.dnn_hp_car_model`,
(SELECT * FROM `car_sales_prices.car_prices_valid`))
大型模型超参数调整选项
本章讨论的框架和技术非常适合数据集和模型不太大的情况。然而,在本地机器或 Colab 笔记本上使用 scikit-learn 和 Keras 处理非常大的数据集和模型可能需要很长时间,甚至可能由于内存和处理约束而无法完成。训练和调整大型模型是一门艺术,并且有公共云提供商提供的工具可以使这一过程变得更加容易。本书不会对这些产品进行深入介绍,因为从自定义代码开发的角度来看,这些产品通常更为复杂,但只是列出了一些选项和参考资料,供有兴趣的人参考。
Vertex AI 训练和调优
在 第 7 章 中,您看到了如何将一个 Python 模块打包为训练 scikit-learn 模型,并提交给 Vertex AI 训练。在本章中,用于超参数调优的 scikit-learn 或 Keras 代码也可以做同样的处理。
Vertex AI 还提供作为 Vertex AI 训练一部分的超参数调整服务。这使用 cloudml-hypertune Python 从不同的试验向 Vertex AI 报告指标,可以在不同的集群中使用 Vertex AI 训练执行。与 Keras 调优器类似,Vertex AI 使用贝叶斯优化来找到您模型的最佳超参数。
有关如何使用此服务的更多详细信息,请参阅Vertex AI 文档。
使用 Amazon SageMaker 自动模型调整
Amazon SageMaker 包括一个自动模型调整服务(SageMaker AMT),用于进行超参数调整。你可以使用 SageMaker AMT 配合内置算法、自定义算法或 SageMaker 预构建的 ML 框架容器,如 scikit-learn、TensorFlow 和 PyTorch。
欲了解更多详情,请参阅 SageMaker AMT 文档。
Azure 机器学习
Azure 机器学习包括超参数调整作为 Python 客户端库和命令行界面的一部分。与前述选项类似,你可以提供自己选择框架中编写的自定义模型,将模型的超参数作为函数创建模型的参数,指定超参数搜索空间,并指定一个作业配置来提交在 Azure 机器学习上运行超参数扫描作业。更多信息,请参阅 Azure 机器学习文档。
摘要
在本章中,你接手了同事构建的自定义代码模型,并通过特征工程和超参数调整进行了改进。你利用了 scikit-learn 中的新转换器,并进行了网格搜索以调整原始的线性回归模型。你学会了如何在 Keras 中使用预处理层进行相同的特征工程,并使用 Keras Tuner 对 Keras 中的神经网络模型进行超参数调整。最后,你还学会了如何在 BigQuery ML 中使用 SQL 执行这些相同的任务。
本章和前一章关于自定义代码模型的内容,希望能为你展示构建 ML 模型的可能性。无代码和低代码解决方案至少是一个很好的起点,而且很可能可以帮助你达到目标,而无需编写自定义代码。但是,你并不需要成为数据科学家来探索自定义代码,也不必编写成百上千行的代码。
在接下来的最后一章中,你将了解如果想要更深入地学习 ML,可以采取的一些下一步措施。在本书中,你已经建立了一个非常强大的工具包,但是这个领域正在不断发展,很多新工具和发展已经不仅仅是研究人员的专利。
¹ 例如,参见 Y. Gal 和 Z. Ghahramani, “Dropout as a Bayesian Approximation: Representing Model Uncertainty in Deep Learning”(第 33 届国际机器学习会议论文集,2016 年)。
第九章:AI 旅程中的下一步
在本书的学习过程中,您已经了解了如何通过企业 ML 工作流程驱动业务决策的数据,如何通过理解数据来构建 ML 模型,并了解了用于构建 ML 模型的工具。您已经学会了如何使用 AutoML 训练回归和分类模型,如何在 BigQuery ML 中使用 SQL 创建自定义低代码模型,如何使用 scikit-learn 和 TensorFlow 框架创建自定义代码模型,以及如何通过进一步的特征工程和超参数调整提高自定义模型的性能。希望您觉得这段旅程同样启发和愉快。对于许多人来说,这应该足以让您能够将 ML 融入到解决问题的流程中去。
对于其他人来说,这只是进入机器学习和人工智能更长旅程的开始。本章探讨了接下来的方向。您将了解数据科学和 ML 运维(或 MLOps)中的其他重要主题。还会指引您去很多超越本书的知识资源。
深入探讨数据科学
数据科学或数据科学家并没有普遍认可的定义。这样的定义的一个合理近似可能是,数据科学是利用来自其他学科的各种工具从数据集中提取洞见的学科。这些各种工具来自于数学、统计学、计算机科学,以及根据手头问题的不同情况,可能还涉及其他领域。
本书中您所使用的所有数据集都是结构化数据集——具有明确定义模式的数据集。大多数业务问题涉及结构化数据,并且您已经掌握了探索结构化数据的精湛技能。然而,随着 ML 学科的成熟,非结构化数据集变得越来越重要。回顾第二章,非结构化数据包括图像、视频、声音文件和文本。在过去十年中,大量研究已经涉及 ML 在非结构化数据方面的各个方面。
最近变得越来越重要的一种 AI 类型是生成式 AI,指的是能够生成各种类型数据,如图像、视频等的模型。最近,生成式 AI 已成为一个非常流行和快速增长的领域,例如图像生成模型如Midjourney和Craiyon,以及聊天机器人(上下文文本生成)如ChatGPT和Bard。此外,生成式 AI 的能力已经被纳入许多商业产品中,如Bing(ChatGPT)、Google Search(Search Generative Experience)和Amazon CodeWhisperer。
模型变得越复杂,理解起来就越困难。例如,在您学习第六章中的线性回归时,您会发现模型的权重清楚地显示了各个特征的重要性。即使是只有一个隐藏层的神经网络,模型的权重与使用的特征的重要性之间也不再有简单的描述连接。在处理非结构化数据和生成模型问题时,使用非常大的模型会变得更加困难。
本节将更深入地探讨各种资源,并提供额外的资源,以便您选择探索这些主题。
处理非结构化数据
非结构化数据被定义为没有模式的数据。一些经典的例子如图像和文本。请记住,ML 模型最终是将数值输入并具有数值输出的数学函数,然后您进行解释。如何将图像或句子解释为数值输入?
处理图像数据
对于图像,情况比您预期的要简单。每个图像都表示为像素值数组。例如,考虑图 9-1 中手写数字的像素化图像。左侧的图像是手写数字 2 的低分辨率版本。该图像由一个 12×12 的像素块网格组成。这种灰度图像的像素值范围在 0 到 255 之间。0 代表黑色,255 代表白色,中间的值代表不同的灰度值。在第二幅图像中,您可以看到图像的实际像素值作为数组。

图 9-1. 手写数字 2 的低分辨率灰度图像及其相应的像素值。
对于彩色图像,思路非常相似。彩色图像由三个通道组成:红色、绿色和蓝色。对于每个像素,每个通道的值介于 0 和 255 之间。这些通道的值通常被称为RGB 值。例如,白色由[255,255,255]表示,黄色由[255,255,0]表示。有很多简单的工具,比如在RapidTables上的工具,可以让您探索不同的颜色并查看它们的 RGB 值。
现在您理解了图像可以作为数值值的二维(黑白图像)或三维数组(彩色图像),您现在可能对如何在 ML 中使用这些值有所了解。这些是您图像模型的数值输入。
图像分类的“hello world”示例是使用所谓的 MNIST(修改过的国家标准与技术研究所)数据集进行手写数字识别的问题。这是一个包含 60,000 个训练图像和 10,000 个测试图像的数据集,这些图像在 10 个手写数字(0 到 9)之间很好地平衡。这些图像是 28×28 的灰度图像。你可以在图 9-2 中看到其中一个示例图像。

图 9-2. MNIST 数据集中手写数字 7 的示例。
将每个图像分类为相应的数字是多类别分类问题的一个示例。我们在本书的示例中没有探讨超过两个类别,但大致思路是相似的。模型将预测每个数字的概率,最可能的数字将被视为预测标签。你可以像在第七章中使用线性分类和神经网络分类器一样使用它们,但也有其他附加工具,如卷积层,在处理图像数据时非常有用。这些额外的工具超出了本书的范围,但以下是一些想要进一步学习的有用资源:
处理文本数据
在你的 ML 模型中可能会遇到的另一种常见的非结构化数据类型是文本数据。例如,如果你想要使用评论中的文本数据来理解为什么你的客户给出了某些积极或消极的评分,你需要有一种方法将文本数据转化为数值数据。
执行此任务的最简单方法是像在前面的问题中对分类数据使用的一样使用独热编码。例如,你可以有词汇表['red', 'blue', 'green']。如果有单词'blue',则相应的值将是[0,1,0]。
然而,这很快就会变得棘手。如果每个不同的单词都有一个不同的对应值,那么你可能会得到一个非常高维的特征。某些单词在训练集中可能很少出现,甚至根本不出现,因此模型可能会难以学习这些单词的含义。
一个策略是使用n-grams而不是单个单词。 n-grams 是n个连续单词的序列。例如,在句子The cow jumped over the moon中,2-grams(或bigrams)是['The cow', 'cow jumped', 'jumped over', 'over the', 'the moon']。对于垃圾邮件检测,3-grams 和 4-grams 往往比单个单词更有用。直观地,我们可以在一个明确的例子中看到这一点。垃圾邮件可能包含像“你赢得了彩票,现在变得富有!”这样的句子作为正文的一部分。1-grams 和 2-grams 将查看句子的片段,这些片段对于捕获上下文来说太细粒度了,除了"now rich!"之外,这可能会提示人或模型该邮件是垃圾邮件。例如,"won","the"和"lottery"缺少单独需要确认该邮件是垃圾邮件的上下文。另一方面,3-gram "won the lottery"对于大多数警惕寻找垃圾邮件的人来说会立即引起警觉。
另一个策略是使用词嵌入。词嵌入是单词在某些维度上的表示,旨在捕捉其含义和与其他单词的关系。例如,单词king可以表示为[0.5, 0.7]。理想情况下,词嵌入将会将相似的单词放置在彼此附近。例如,如 Figue 9-3 所示,dog和puppy的嵌入将彼此靠近,cat和kitten也将如此。

图 9-3. “dog”、“puppy”、“cat”和“kitten”的示例二维词嵌入。
还要注意,“dog”和“puppy”以及“cat”和“kitten”之间的距离和方向非常相似。使用词嵌入,您会期望在任何动物及其幼崽之间(比如“sheep”和“lamb”)之间有类似的关系。
词嵌入本身就是模型,并且通常在特定问题的上下文中进行训练。更一般地,存在不同的预处理模型和工具,用于将单词和句子片段转换为数值输入。BERT(双向编码器转换器表示)预处理是将文本输入转换为模型输入的一种流行方式。
要了解更多关于在 ML 模型中处理文本的信息,请参考以下几个有用的资源:
-
Yelp 评论数据集:一个用于处理文本数据以预测评论分数的优秀数据集。
生成式 AI
我们在本书中讨论的分类模型是判别模型。判别模型的目标是预测实例属于哪个类别。例如,预测交易是欺诈还是合法。生成模型在某种程度上是相反的问题。该模型从标签生成实例。例如,给定标签“一只弹奏班卓琴的猫”,将生成一张弹奏班卓琴的猫的图像。
有免费工具,比如craiyon.com,可以用来玩弄这些模型。其中一个示例显示在图 9-4 中。

图 9-4. 使用提示“一只弹奏班卓琴的猫”生成的图像。该图像使用craiyon.com生成。
生成人工智能近来因其在聊天机器人中的应用成为了讨论的主要话题。ChatGPT 和 Bard 就是生成人工智能应用于聊天机器人的两个例子。这些产品的核心是大型语言模型,或称为 LLMs。术语LLM有点模糊,但指的是在大规模数据集上训练的参数极为庞大的语言模型。例如,ChatGPT 的原始模型 GPT-3.5 就有超过 1750 亿个参数,并拥有超过五百万亿个标记。
关于生成人工智能在社会中的角色以及我们作为社会应如何与其互动,有很多有趣的讨论。关于负责任使用生成人工智能的伦理深入讨论超出了本书的范围和目的,但这些是未来重要的讨论。
如果您对深入了解生成人工智能感兴趣,Coursera 上的 ChatGPT 101是一个探索这种新技术使用和影响的好资源。
可解释人工智能(Explainable AI)
第四章和第五章介绍了用于您的模型的特征归因,而第六章介绍了可解释人工智能或 XAI,通常用于结构化数据。许多 XAI 技术也适用于非结构化数据,如图像和文本数据。
XAI 技术可以分为内在和事后技术。内在技术利用模型的结构来解释预测。某些类型的模型,如线性模型和决策树,本质上是可解释的。对于线性模型,本书的第六章和第七章详细探讨了这一点,权重给出了特征的相对重要性。
事后技术,另一方面,使用模型的预测来理解其行为。这些技术是在模型训练完成后应用的,通常它们是在评估数据集的一部分上进行训练的。事后技术可以分为两大类:局部和全局技术。局部技术专注于单个实例,并试图解释为什么对于该特定实例做出了特定的预测。全局技术专注于模型在整个数据集上的行为。一般来说,局部技术可以通过对数据集中的局部技术的结果进行聚合来转化为全局技术。
局部和全局技术都可以进一步分为与模型无关和与模型相关的技术。与模型无关的技术倾向于改变数据并理解它如何改变正在进行的预测。一个很好的例子是您在第 4 和第五章节中看到的特征归因,当使用 AutoML 时。这些特征归因是如何计算的?它们是使用称为排列特征重要性或 PFI 的技术计算的。PFI 的计算方式如下:首先计算您想要使用的数据集的模型损失。接下来,在原地排列数据的第一列时再次计算损失。也就是说,您在保持其他列不变的情况下排列第一列(参见表 9-1)。原始数据集的损失与第一列数据集排列后的损失之间的差异给出了第一个特征重要性的“分数”。
表格 9-1. 在原地对一列进行排列;在此示例中,列 A 是被排列的列
| 列 A | 列 B | 列 C |
|---|---|---|
| 1 | 4 | 7 |
| 2 | 5 | 8 |
| 3 | 6 | 9 |
| 列 A | 列 B | 列 C |
| --- | --- | --- |
| 3 | 4 | 7 |
| 1 | 5 | 8 |
| 2 | 6 | 9 |
重复此过程以处理每一列。一列的 PFI 是从此过程中得到的归一化分数。归一化在这里意味着重新缩放分数,使所有归一化分数相加为一。PFI 在实践中易于解释和计算,但它确实依赖于一些由于排列而引起的随机性。通常情况下,在使用 PFI 时,您将对一列进行多次不同排列并对其进行平均以尝试减少随机性的影响。
另一种技术是模型特定的技术。例如,方向特征重要性/贡献专门针对基于树的模型,如决策树、随机森林和梯度提升树。另一方面,神经网络的一种流行技术是综合梯度技术。综合梯度利用神经网络是可微模型的事实。可微性是一种重要的数学性质,在使用梯度下降算法最小化损失函数时也被利用。确切的数学内容超出了本书的范围,但其思想非常直观。
以图像分类模型为例。假设你已经训练了一个模型来基于其主题对图像进行分类。你的模型接收到一张火船的图片,并正确预测这张图片是火船。但是为什么它会做出这个预测呢?粗略地说,综合梯度方法观察当你改变特征并累积每个特征的变化时预测如何改变。对于图像模型来说,这些特征就是单个像素值。对于综合梯度来说,你定义一个起始点或基线。在图像模型中,基线通常是完全黑色的图像,尽管在特定情况下可以使用其他基线图像(如完全白色的图像或随机噪声)。你从纯黑图像的火船标签的预测概率开始,然后按比例增加像素直到得到原始图像。对于每一步,你计算像素值变化对火船预测概率的影响。这可以通过计算梯度来完成,梯度是理解输出随多个输入变化率的数学工具。最后,你累积(或积分)每个像素的这些变化率。具有最高累积变化率的像素对应于预测中最重要的像素。在图 9-5 和图 9-6 中,你可以看到这些插值图像及使用综合梯度确定的像素重要性。

图 9-5. 左侧是基线图像,右侧是火船的原始图像。中间图像是两者之间的插值。

图 9-6. 消防船图像;由综合梯度标识的最重要像素更亮。请注意,综合梯度突出了从船上的水流作为解释,说明为什么这是一幅消防船的图片。
要了解更多关于 XAI 的信息,可解释机器学习:使黑匣子模型可解释的指南 由 Christoph Molnar 提供了一个很好的资源。
ML 运营
在第七章中,您训练了一个 ML 模型来预测客户流失,并能够在笔记本环境中提供预测。然而,这只是将 ML 模型投入生产过程中的一步。正如您所看到的,需要做一些工作来确保数据准备就绪。但是这并未考虑到需要进行培训和服务模型的基础计算基础设施,管理和优化正在使用的资源消耗,考虑如何使您的模型对消费者可用,以及如何随时间监控该模型。图 9-7 显示了与生产化 ML 模型相关的代码相比的相对努力程度。

图 9-7. 使 ML 模型可用的隐藏复杂性。
ML 运营,或 MLOps,是管理模型本身之外的所有不同任务的学科。这包括管理基础设施,决定模型的部署和访问方式,以及在适当时监控和更新模型。
MLOps 通常是 ML 工程师、数据工程师和数据科学家的责任来管理。但是,即使你不在这些角色之一,了解在生产中使用 ML 模型的各种关注点也非常有价值。作为构建模型的人,你可以分享关于所使用的工具和数据源、需要进行的数据预处理以及模型在预测时期望的特征的良好笔记。像在模型本身中包含预处理逻辑这样的做法,使用像 scikit-learn 中的 transformers 或 Keras 中的预处理层,可以使工程师在模型投入生产时更容易地部署训练和预测流水线。在大多数公司,不会有一个人做所有事情,因此良好的沟通至关重要。
要了解更多关于 MLOps 相关主题的详细信息,请参阅以下资源:
-
设计机器学习系统 由 Chip Huyen(O’Reilly,2022)撰写
持续培训和评估
在第五章中,您使用 AutoML 训练了一个模型,用于将交易分类为欺诈或合法。您的模型在提供的数据集上表现良好,但在使用几个月后的生产过程中,您公司的支持团队表示他们收到了更多关于事后客户报告的欺诈交易。尽管如此,您的模型并没有标记出许多这类欺诈交易。发生了什么?
随着时间的推移,不同类型的漂移可能会发生,并影响您模型的性能。例如,可能会出现新类型的欺诈交易,这些交易在您原始的训练数据集中并不存在。您的模型可能不会发现这一点。这将是数据漂移的一个例子,即基础数据分布的变化。
另一种漂移类型被称为概念漂移。概念漂移是指特征与标签之间的关系随时间变化的情况。零售需求预测中有一个很好的例子。购物趋势随时间改变,同一产品在不同时间销售情况不同,这取决于当前的趋势。产品本身没有变化,但产品与销售之间的关系发生了变化。
持续训练是根据某些标准自动重新训练模型的过程。最常见的标准通常是时间或模型性能。例如,您可能希望每周重新训练模型,或者当某个特定比例的欺诈交易被错过并由客户报告时重新训练模型。对于大多数在生产中使用的模型来说,这是一个非常常见和重要的实践。
持续评估通常是决定何时重新训练模型的重要组成部分,也是监控模型性能的重要部分。当模型在新数据上预测结果时,会对新数据进行标记。通常这些标签由领域专家确定。然后模型根据这些数据与新标签进行评估。如果模型的性能随时间减弱,那么可能表明发生了某种漂移,可能是时候重新训练模型了。
如果您是本书预期读者的一部分,那么您在思考这些问题时可能会与其他人合作。然而,即使这不是您的责任,了解其他人正在使用的工具和问题也是很重要的。这可以让您在自己的方法上做出更明智的决策,使他人的工作更轻松,并让您成为更好的合作者。
总结
你在阅读这本书的过程中已经经历了一段很不错的机器学习之旅!希望现在你能够自如地思考如何将自己的问题转化为机器学习项目,并且已经准备好开始使用本书中介绍的工具构建模型了。从这里起,你可以利用不同的资源,探索多种方向,并且应该感到自信去追寻你感兴趣的领域。机器学习和人工智能是一个快速增长的领域,在过去十年里需求呈指数级增长,今后几年很可能会继续增长。


浙公网安备 33010602011771号