Python 机器学习模型调试指南(一)
原文:
annas-archive.org/md5/842997dabd1ace27744af80826d549f0译者:飞龙
前言
欢迎阅读 使用 Python 调试机器学习模型 – 您掌握机器学习的全面指南。本书旨在帮助您从机器学习的基本概念进步到专家级模型开发的复杂性,确保您的旅程既具有教育性又具有实用性。在本书中,我们超越了简单的代码片段,深入到构建可靠、工业级模型的整体过程。从模块化数据准备的细微差别到模型无缝集成到更广泛的技术生态系统中,每一章都是精心策划的,以弥合基本理解和高级专业知识之间的差距。
我们的旅程不仅仅停留在模型创建上。我们将深入探讨评估模型性能、定位挑战,并为您提供有效的解决方案。强调在生产环境中引入和维护可靠模型的重要性,本书将为您提供应对数据处理和建模问题的技术。您将了解可重复性的重要性,并获得实现它的技能,确保您的模型既一致又可靠。此外,我们将强调公平性、消除偏见以及模型可解释性的艺术,确保您的机器学习解决方案是道德的、透明的和可理解的。随着我们的进展,我们还将探索深度学习和生成建模的前沿,通过使用 PyTorch 和 scikit-learn 等知名 Python 库的实践练习来丰富内容。
在机器学习不断演变的领域中,持续学习和适应是必不可少的。本书不仅是一个知识库,也是一个激励者,激发您进行实验和创新。随着我们深入探讨每个主题,我邀请您以好奇心和探索的意愿来对待它,确保您获得的知识是深入且可操作的。让我们共同塑造机器学习的未来,一次构建一个模型。
本书面向对象
本书面向数据科学家、分析师、机器学习工程师、Python 开发人员和希望为各种工业应用构建可靠、高性能、可重复、可信和可解释的机器学习模型的学生。您只需要基本的 Python 技能就可以深入理解本书涵盖的概念和实践示例。无论您是机器学习的新手还是有经验的从业者,本书都提供了广泛的知识和实践见解,以提升您的建模技能。
本书涵盖内容
第一章,超越代码调试,概述了代码调试的简要回顾以及为什么调试机器学习模型超出了这一点。
第二章,机器学习生命周期,教您如何为您的项目设计一个模块化的机器学习生命周期。
第三章,向负责任的 AI 调试,解释了负责任机器学习建模中的担忧、挑战和一些技术。
第四章,检测机器学习模型中的性能和效率问题,教你如何正确评估你的机器学习模型的性能。
第五章,提高机器学习模型性能,教你不同的技术来提高你的机器学习模型性能和泛化能力。
第六章,机器学习建模中的可解释性和可解释性,涵盖了机器学习可解释性技术。
第七章,减少偏差和实现公平性,解释了一些技术细节和工具,你可以使用它们来评估模型的公平性并减少偏差。
第八章,使用测试驱动开发控制风险,展示了如何使用测试驱动开发工具和技术来降低不可靠建模的风险。
第九章,生产环境下的测试和调试,解释了测试和模型监控技术,以确保在生产中有可靠的模型。
第十章,版本控制和可重复的机器学习建模,教你如何使用数据和模型版本控制来实现机器学习项目中的可重复性。
第十一章,避免和检测数据与概念漂移,教你如何检测你的机器学习模型中的漂移,以确保在生产中有可靠的模型。
第十二章,超越 ML 调试的深度学习,涵盖了深度学习建模的介绍。
第十三章,高级深度学习技术,涵盖了卷积神经网络、转换器和图神经网络,用于不同数据类型的深度学习建模。
第十四章,机器学习最新进展介绍,解释了生成建模、强化学习和自监督学习的最新进展的介绍。
第十五章,相关性对因果性,解释了因果建模的益处和一些实际的技术。
第十六章,机器学习中的安全和隐私,展示了在机器学习环境中保护隐私和确保安全的一些挑战,并教你一些应对这些挑战的技术。
第十七章,人机协同机器学习,阐述了人机协同建模的益处和挑战。
为了充分利用这本书
为了遵循本书中给出的说明,您需要了解以下基本知识:
-
通过集成开发环境(IDE)、Jupyter 笔记本或 Colab 笔记本访问 Python。
-
Python 编程基础。
-
对机器学习建模和术语的基本理解,例如监督学习、无监督学习以及模型训练和测试。
拥有包含所有所需库的虚拟环境将帮助您在本书的每个章节中运行代码,这些代码以 Jupyter 笔记本的形式提供在本书的关联 GitHub 仓库中。
本书所需的 Python 库包括:sklearn >= 1.2.2, numpy >= 1.22.4, pandas >= 1.4.4, matplotlib >= 3.5.3, collections >= 3.8.16, xgboost >= 1.7.5, sklearn >= 1.2.2, ray >= 2.3.1, tune_sklearn >= 0.4.5, bayesian_optimization >= 1.4.2, imblearn, pytest >= 7.2.2, shap >= 0.41.0, aif360 >= 0.5.0, fairlearn >= 0.8.0, pytest >= 3.6.4, ipytest >= 0.13.0, mlflow >= 2.1.1, libi_detect >= 0.11.1, lightgbm >= 3.3.5, evidently >= 0.2.8, torch >= 2.0.0, torchvision >= 0.15.1, transformers >= 4.28.0, datasets >= 2.12.0, torch_geometric == 2.3.1, dowhy == 0.5.1, bnlearn == 0.7.16, tenseal >= 0.3.14, pycryptodome = 3.18.0, pycryptodomex = 3.18.0
或者,您可以使用在线服务,如 Colab,并作为 Colab 笔记本运行笔记本。
| 本书涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| Python >=3.6 | Windows, macOS, 或 Linux |
| DVC >= 1.10.0 |
为了避免重复并使本书尽可能简短,每个代码单元中省略了导入所需库的步骤。拥有本书的 GitHub 仓库将帮助您确定每段代码所需的库以及如何安装它们。由于本书不是单一命令教程书,大多数示例都包括多行过程。因此,在大多数章节中,您不能在不注意所需库、它们的安装以及之前的代码行的情况下复制粘贴单个行。
如果您使用的是本书的数字版,我们建议您亲自输入代码或从本书的 GitHub 仓库(下一节中提供链接)获取代码。这样做将帮助您避免与代码复制粘贴相关的任何潜在错误。
下载示例代码文件
您可以从 GitHub 在github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python上下载本书的示例代码文件。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有来自我们丰富的图书和视频目录的其他代码包,可在github.com/PacktPublishing/找到。查看它们!
使用的约定
本书使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“将下载的WebStorm-10*.dmg磁盘映像文件作为系统中的另一个磁盘挂载。”
代码块设置如下:
import pandas as pdorig_df = pd.DataFrame({
'age': [45, 43, 54, 56, 54, 52, 41],
'gender': ['M', 'F', 'F', 'M', 'M', 'F', 'M'],
'group': ['H1', 'H1', 'H2', 'H3', 'H2', 'H1', 'H3'],
'target': [0, 0, 1, 0, 1, 1, 0]})
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
in_encrypt = open("molecule_enc.bin", "rb")nonce, tag, ciphertext = [in_encrypt.read(x) for x in (16, 16, -1) ]
in_encrypt.close()
任何命令行输入或输出都如下所示:
python -m pytest
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“您可能能够找到其他裂纹产品的图像,或者您可以使用称为数据增强的过程生成新的图像。”
小贴士或重要注意事项
看起来是这样的。
联系我们
读者的反馈总是受欢迎的。
总体反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送给我们 customercare@packtpub.com,并在邮件主题中提及书名。
勘误表:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将不胜感激,如果您能向我们报告这一点。请访问www.packtpub.com/support/err…并填写表格。
盗版:如果您在互联网上以任何形式遇到我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过电子邮件联系版权@packt.com,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了《使用 Python 调试机器学习模型》,我们很乐意听到您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区非常重要,并将帮助我们确保我们提供高质量的内容。
下载本书的免费 PDF 副本
感谢您购买本书!
您喜欢在路上阅读,但无法携带您的印刷书籍到处走吗?
您的电子书购买是否与您选择的设备不兼容?
不要担心,现在,随着每本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠不会就此结束,您还可以获得独家折扣、时事通讯和每日免费内容的每日电子邮件。
按照以下简单步骤获取好处:
- 扫描下面的二维码或访问以下链接
packt.link/free-ebook/978-1-80020-858-2
-
提交您的购买证明
-
就这些了!我们将直接将您的免费 PDF 和其他福利发送到您的邮箱
第一部分:机器学习建模调试
在本书的这一部分,我们将深入探讨机器学习发展的不同方面,这些方面超越了传统的范式。第一章阐明了传统代码调试与机器学习调试专门领域的细微差别,强调机器学习中的挑战超越了单纯的代码错误。下一章提供了一个关于机器学习生命周期的全面概述,突出了模块化在简化并增强模型开发中的作用。最后,我们将强调在追求负责任的人工智能过程中模型调试的重要性,强调其在确保道德、透明和有效的机器学习解决方案中的作用。
本部分包含以下章节:
-
第一章*,超越代码调试*
-
第二章*,机器学习生命周期*
-
第三章*,向负责任的人工智能调试*
第一章:代码调试之外
人工智能(AI),像人类智能一样,是一种可用于决策和任务完成的特性和工具。作为人类,我们在做出日常决策、思考我们面临的挑战和问题时使用我们的智能。我们使用我们的大脑和神经系统从周围环境中接收信息,并处理它们以进行决策和反应。
机器学习模型是当今用于解决医疗保健和金融等领域问题的 AI 技术。机器学习模型已在制造设施中的机器人系统中用于包装产品或识别可能损坏的产品。它们被用于我们的智能手机中,用于安全目的识别我们的面部,电子商务公司为我们推荐最合适的产品或电影,甚至用于改善医疗保健和药物开发,将新的更有效的药物推向市场以治疗严重疾病。
在本章中,我们将快速回顾不同类型的机器学习建模。您将了解调试机器学习代码的不同技术和挑战。我们还将讨论为什么调试机器学习建模远不止代码调试。
在本章中,我们将涵盖以下主题:
-
机器学习速览
-
机器学习建模的类型
-
软件开发中的调试
-
用于建模的数据缺陷
-
以模型和预测为中心的调试
本章是本书的介绍,旨在为您准备后续将介绍的更高级概念。这将帮助您提高模型,并朝着成为机器学习时代的专家迈进。
技术要求
您可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter01。
机器学习速览
构建机器学习模型需要三个基本要素:算法、数据和计算能力(图 1.1)。机器学习算法需要用正确的数据喂养,并使用必要的计算能力进行训练。然后,它可以用于预测它所训练的未见数据:
图 1.1 – 机器学习三角形的三个要素
机器学习应用可以大致分为自动化和发现。在自动化类别中,机器学习模型及其围绕其构建的软件和硬件系统的目标是执行人类可能且通常容易但繁琐、重复、无聊或危险的任务。这方面的例子包括在制造线中识别损坏的产品或在高度安全设施入口处识别员工的面孔。有时,尽管这些任务可能很容易,但无法使用人类来完成。例如,对于手机上的面部识别,如果你的手机被盗,你就无法在那里识别试图登录你手机的人不是你,而你的手机应该能够自动完成这项任务。但是,我们无法为这些任务提出一个通用的数学公式,告诉机器在每个情况下应该做什么。因此,机器学习模型学习如何根据数据中识别出的模式来提出其预测,例如,在识别面部方面。
另一方面,在机器学习建模的发现类别中,我们希望模型能够提供关于未知信息的信息和洞察,这些信息对于人类专家或非专家来说可能不容易或完全未被发现,甚至是不可能提取的。例如,为癌症患者发现新药并不是一个可以通过参加几门课程和阅读几本书就能全面了解其所有方面的任务。在这种情况下,机器学习可以帮助我们提出新的见解,以帮助发现新药。
对于发现和自动化,不同类型的机器学习建模可以帮助我们实现目标。我们将在下一节中探讨这一点。
机器学习建模类型
机器学习包含多种建模类型,这些类型可能依赖于输出数据、不同类型的模型输出,以及从预先录制的数据或经验中进行学习。尽管本书中的例子主要关注监督学习,但我们将回顾其他类型的建模,包括无监督学习、自监督学习、半监督学习、强化学习(RL)和生成式机器学习,以涵盖机器学习建模的六大主要类别(图 1*.2*)。我们还将讨论机器学习建模的技术,并提供与这些类别不平行但相关的代码示例,例如主动学习、迁移学习、集成学习和深度学习:
图 1.2 – 机器学习建模类型
自监督和半监督学习有时被认为是监督学习的子类别。然而,我们将在这里将它们分开,以便我们可以区分你熟悉的通常的监督学习模型和这两种建模类型。
监督学习
监督学习是关于识别每个数据点的输入/特征与输出之间的关系。但输入和输出是什么意思呢?
假设我们想要构建一个机器学习模型来预测一个人是否可能患上乳腺癌。模型的输出可以是 1 表示患上乳腺癌,0 表示未患上乳腺癌,输入可以是人的特征,如年龄、体重以及他们是否吸烟。甚至可能有使用先进技术测量的输入,例如每个人的遗传信息。在这种情况下,我们想要使用我们的机器学习模型来预测哪位病人在未来会患上癌症。
你也可以设计一个机器学习模型来估算一个城市的房价。在这里,你的模型可以使用房屋的特征,如卧室数量和房屋大小、社区以及学校可访问性,来估算房价。
在这两个例子中,我们都有模型试图在输入特征中识别模式,例如拥有很多卧室但只有一个浴室,并将这些与输出关联起来。根据输出变量的类型,你的模型可以被归类为分类模型,其中输出是分类的,例如得到或未得到癌症,或者回归模型,其中输出是连续的,例如房价。
无监督学习
我们的大部分生活,至少在童年时期,都是通过使用我们的五种感官(视力、听力、味觉、触觉和嗅觉)来收集关于我们周围环境、食物等信息,而无需我们试图找到基于颜色和形状判断香蕉是否成熟的监督学习风格的关系。同样,在无监督学习中,我们不是寻求识别特征(输入)和输出之间的关系。相反,目标是识别数据点之间的关系,如在聚类中,提取新的特征(即嵌入或表示),如果需要,在不使用任何输出数据点的情况下减少数据的维度(即特征的数量)。
自监督学习
机器学习建模的第三类被称为自监督学习。在这个类别中,目标是识别输入和输出之间的关系,但与监督学习的不同之处在于输出的来源。例如,如果监督机器学习模型的目的是将英语翻译成法语,那么输入来自英语单词和句子,输出来自法语单词和句子。然而,我们可以在英语句子内部有一个自监督学习模型,试图预测句子中的下一个单词或缺失的单词。例如,假设我们的目标是识别“talking”是填补“Jack is ____ with Julie.”空缺的好候选词。近年来,自监督学习模型被广泛应用于不同领域以识别新特征。这通常被称为表示学习。我们将在第十四章,“机器学习最新进展导论”中讨论一些自监督学习的例子。
半监督学习
半监督学习可以帮助我们利用监督学习的好处,而不必丢弃那些没有输出值的数据点。有时,我们有一些数据点,我们没有输出值,只有它们的特征值可用。在这种情况下,半监督学习帮助我们使用带有或没有输出的数据点。一个简单的做法是将相似的数据点分组,并使用每个组中数据点的已知输出为同一组中其他没有输出值的数据点分配输出。
强化学习
在强化学习(RL)中,模型根据其在环境(真实或虚拟)中的经验获得奖励。换句话说,强化学习是关于通过分段示例添加识别关系。在强化学习中,数据不被视为模型的一部分,并且与模型本身独立。我们将在第十四章,“机器学习最新进展导论”中详细介绍强化学习的某些细节。
生成式机器学习
生成式机器学习建模帮助我们开发出能够生成图像、文本或任何接近训练过程中提供的数据概率分布的数据点的模型。ChatGPT 是基于生成模型构建的、用于生成对用户提问和回答的逼真且有意义的文本的最著名工具之一(openai.com/blog/ChatGPT)。我们将在第十四章,“机器学习最新进展导论”中详细介绍生成式建模及其之上的可用工具。
在本节中,我们简要回顾了构建机器学习模型的基本组件和不同类型的建模。但如果你想要开发用于自动化或发现、医疗保健或任何其他应用的机器学习模型,无论数据点的数量是低还是高,无论是在你的笔记本电脑上还是在云端,使用 中央处理器(CPU)或 图形处理器(GPU),你需要开发出按预期工作的优质代码。尽管这本书不是一本软件调试的书,但软件调试挑战和技术概述可能有助于你在开发机器学习模型时。
软件开发中的调试
如果你想使用 Python 及其库来构建机器学习与深度学习模型,你需要确保你的代码按预期工作。让我们考虑以下相同函数的例子,用于返回两个变量的乘积:
-
正确的代码:
def multiply(x, y): z = x * y return z -
存在拼写错误的代码:
def multiply(x, y): z = x * y retunr z -
存在缩进问题的代码:
def multiply(x, y):** for multiplication:def multiply(x, y): z = x ** y return z
如你所见,代码中可能有拼写错误和缩进问题,这会阻止代码运行。你也可能因为使用了不正确的运算符而遇到问题,例如使用 ** 而不是 * 进行乘法。在这种情况下,你的代码将运行,但预期的结果将与函数本应执行的操作不同,即乘以输入变量。
Python 中的错误信息
有时,我们的代码中存在一些问题,导致代码无法继续运行。这些问题可能导致 Python 中出现不同的错误信息。以下是一些你运行 Python 代码时可能遇到的错误信息的例子:
-
SyntaxError: 当你在代码中使用的语法不是正确的 Python 语法时,你会得到此类错误。这可能是由于一个错误,例如之前显示的retunr而不是return,或者使用了一个不存在的命令,例如使用giveme而不是return。 -
TypeError: 当你的代码尝试在 Python 中对一个对象或变量执行无法进行的操作时,会引发此类错误。例如,如果你的代码尝试将两个数字相乘,而变量是以字符串格式而不是浮点数或整数格式存在。 -
AttributeError: 当一个属性被用于一个未定义其属性的物体时,会引发此类错误。例如,isnull对于列表未定义。因此,my_list.isnull()会引发AttributeError。 -
NameError: 当你尝试调用未在代码中定义的函数、类或其他名称和模块时,会引发此类错误。例如,如果你没有在代码中定义neural_network类,但在代码中调用它为neural_network(),你将收到NameError信息。 -
IndentationError:Python 是一种依赖于正确缩进的编程语言——也就是说,每行代码开头的必要空格——以理解行之间的关系。它还有助于代码的可读性。IndentationError是由于代码中使用了错误的缩进类型而产生的。但并非所有错误的缩进都会导致IndentationError。例如,以下代码示例在没有错误的情况下运行,但只有第一个示例达到了统计列表中奇数个数的目标。底部函数返回输入列表的长度。因此,如果你运行代码的上半部分,你会得到 3 作为输出,这是输入列表中奇数的总数,而代码的下半部分返回 5,这是列表的长度。这些类型的错误不会阻止代码运行,但会产生不正确的输出,被称为逻辑错误。
下面是一些示例代码,其中错误的缩进导致错误的结果,但没有错误信息:
def odd_counter(num_list: list): """
:param num_list: list of integers to be checked for identifying odd numbers
:return: return an integer as the number of odd numbers in the input list
"""
odd_count = 0
for num in num_list:
if (num % 2) == 0:
print("{} is even".format(num))
else:
print("{} is even".format(num))
odd_count += 1
return odd_count
num_list = [1, 2, 5, 8, 9]
print(f'Total number of odd numbers in the list:
{odd_counter(num_list)}')
以下代码可以运行,但会产生意外的结果:
def odd_counter(num_list: list): """
:param num_list: list of integers to be checked for identifying odd numbers
:return: return an integer as the number of odd numbers in the input list
"""
odd_count = 0
for num in num_list:
if (num % 2) == 0:
print("{} is even".format(num))
else:
print("{} is even".format(num))
odd_count += 1
return odd_count
num_list = [1, 2, 5, 8, 9]
print(f'Total number of odd numbers in the list:
{odd_counter(num_list)}')
有些错误根据其名称就能清楚地了解其含义,例如,当你的代码尝试执行除以零的操作时,会引发ZeroDivisionError;如果代码尝试根据一个大于列表长度的索引获取值,则会引发IndexError;当你尝试导入一个找不到的函数或类时,会引发ImportError。
在之前的代码示例中,我们使用了docstring来指定输入参数的类型(即列表)和预期的输出。拥有这些信息有助于你和新用户更好地理解代码,并快速解决与之相关的问题。
这些是在你的软件和管道中可能发生的一些简单问题示例。在机器学习建模中,你需要进行调试来处理数百或数千行代码以及数十或数百个函数和类。然而,与这些示例相比,调试可能更具挑战性。例如,当你加入一个新的行业或学术团队,需要开始处理你未曾编写过的代码时,调试可能会更加困难。你需要使用技术和工具来帮助你以最少的努力和时间调试代码。尽管这本书不是为代码调试而设计的,但回顾一些调试技术可能有助于你开发出按计划运行的优质代码。
调试技术
有一些技术可以帮助你在调试代码或软件的过程中。你可能已经使用过其中的一种或多种技术,即使你忘记了或不知道它们的名称。在这里,我们将回顾其中的四种。
Traceback
当您在 Python 中收到错误消息时,它通常会提供您查找问题的必要信息。这些信息创建了一个类似报告的消息,关于错误发生的代码行,以及导致这些错误的错误类型和函数或类调用。这种类似报告的消息在 Python 中称为回溯。
考虑以下代码,其中reverse_multiply函数本应返回一个列表,该列表包含输入列表及其反转的逐元素乘积。在这里,reverse_multiply使用multiply命令来乘以两个列表。由于multiply是为乘以两个浮点数而设计的,而不是两个列表,因此代码返回了包含必要信息的回溯消息,从底部操作开始。它指定在multiply的第 8 行发生了TypeError,这是底部操作,然后让我们知道这个问题导致在reverse_multiply的第 21 行发生错误,并最终在代码模块的第 27 行。PyCharm IDE 和 Jupyter 都返回了这些信息。以下代码示例展示了如何使用回溯来查找必要信息,以便您可以在 PyCharm 和 Jupyter Notebook 中调试一小段简单的 Python 代码:
def multiply(x: float, y: float): """
:param x: input variable of type float
:param y: input variable of type float
return: returning multiplications of the input variables
"""
z = x * y
return z
def reverse_multiply(num_list: list):
"""
:param num_list: list of integers to be checked for identifying odd numbers
:return: return a list containing element-wise multiplication of the input list and its reverse
"""
rev_list = num_list.copy()
rev_list.reverse()
mult_list = multiply(num_list, rev_list)
return mult_list
num_list = [1, 2, 5, 8, 9]
print(reverse_multiply(num_list))
以下行显示了在 Jupyter Notebook 中运行先前代码时的回溯错误消息:
TypeError Traceback (most recent call last)<ipython-input-1-4ceb9b77c7b5> in <module>()
25
26 num_list = [1, 2, 5, 8, 9]
---> 27 print(reverse_multiply(num_list))
<ipython-input-1-4ceb9b77c7b5> in reverse_multiply(num_list)
19 rev_list.reverse()
20
---> 21 mult_list = multiply(num_list, rev_list)
22
23 return mult_list
<ipython-input-1-4ceb9b77c7b5> in multiply(x, y)
6 return: returning multiplications of the input variables
7 """
----> 8 z = x * y
9 return z
10
TypeError: can't multiply sequence by non-int of type 'list'
Traceback error message in Pycharm
Traceback (most recent call last):
File "<input>", line 27, in <module>
File "<input>", line 21, in reverse_multiply
File "<input>", line 8, in multiply
TypeError: can't multiply sequence by non-int of type 'list'
Python 的回溯消息似乎对调试我们的代码非常有用。然而,对于包含许多函数和类的庞大代码库,它们并不足够。您需要使用辅助技术来帮助您在调试过程中。
归纳与演绎
当您在代码中找到错误时,您可以从收集尽可能多的信息开始,并尝试使用这些信息查找潜在的问题,或者您可以跳入检查您的怀疑。这两种方法在代码调试方面区分了归纳和演绎过程:
-
归纳:在归纳过程中,您开始收集有关代码中问题的信息和数据,这有助于您列出由错误引起的潜在问题列表。然后,您可以缩小列表,并在必要时从过程中收集更多信息和数据,直到修复错误。
-
演绎:在演绎过程中,您会列出有关代码中问题的怀疑点,并尝试找出它们中是否有任何一个是问题的实际来源。您继续这个过程,收集更多信息,并提出新的潜在问题来源。您继续这个过程,直到解决问题。
在这两种方法中,你都会经历一个迭代过程,即提出潜在的问题来源,建立假设,然后收集必要的信息,直到你修复代码中的错误。如果一段代码或软件对你来说是新的,这个过程可能会花费一些时间。在这种情况下,尝试从对代码有更多经验的队友那里寻求帮助,收集更多数据并提出更相关的假设。
错误聚类
正如帕累托原则所述,该原则以著名的意大利社会学家和经济学家维弗雷多·帕累托命名,80%的结果源于 20%的原因。具体的数字在这里并不重要。这个原则帮助我们更好地理解,我们代码中的大多数问题和错误都是由少数模块引起的。通过分组错误,我们可以一石多鸟,因为解决一组错误中的问题可能会潜在地解决同一组中的大多数其他问题。
问题简化
这里的想法是简化代码,以便你可以识别错误的来源并修复它。你可以用更小甚至合成的数据对象替换大数据对象,或者限制在大模块中的函数调用。这个过程可以帮助你快速排除识别代码中问题原因的选项,甚至在你代码中用作函数或类输入的数据格式中。特别是在机器学习环境中,你可能需要处理复杂的数据处理、大数据文件或数据流,这种调试过程中的简化过程可能非常有用。
调试器
你可能会使用每个 IDE,例如 PyCharm,或者如果你使用 Jupyter Notebook 用 Python 来实验你的想法,它们都有内置的调试功能。还有免费或付费的工具可以帮助你简化调试过程。例如,在 PyCharm 和大多数其他 IDE 中,你可以在运行一大段代码时使用断点作为暂停点,以便你可以跟踪代码中的操作(图 1.3)并最终找到问题的原因:
图 1.3 – 在 PyCharm 中使用断点进行代码调试
不同 IDE 中的断点功能并不相同。例如,你可以使用 PyCharm 的条件断点来加速你的调试过程,这有助于你避免在循环中执行代码行或手动重复函数调用。了解更多关于你使用的 IDE 的调试功能,并将它们视为你工具箱中另一个更好的、更简单的 Python 编程和机器学习建模工具。
我们在这里简要解释的调试技术和工具,或者你已经知道的那些,可以帮助你开发出能够运行并提供预期结果的代码。你也可以遵循一些高质量 Python 编程和构建你的机器学习模型的最佳实践。
高质量 Python 编程的最佳实践
预防胜于治疗。你可以遵循一些实践来预防或减少代码中发生错误的机会。在本节中,我们将讨论其中三种实践:增量编程、日志记录和防御性编程。让我们逐一详细探讨。
增量编程
在实践中,无论是学术界还是工业界,机器学习建模都不仅仅是写几行代码来训练一个简单的模型,比如使用scikit-learn中已存在的数据集训练逻辑回归模型。它需要许多模块来处理数据、训练和测试模型以及后处理推断或预测以评估模型的可靠性。为每个小组件编写代码,然后使用 PyTest 等工具进行测试和编写测试代码,可以帮助你避免你编写的每个函数或类的问题。它还帮助你确保作为另一个模块输入的模块的输出是兼容的。这个过程被称为增量编程。当你编写软件或管道时,尽量分步骤编写和测试。
日志记录
每辆车都有一系列仪表盘灯,当车辆出现问题时,这些灯会亮起。这些问题如果不采取行动,可能会停止车辆运行或造成严重损坏,例如燃油低或发动机油更换灯。现在,想象一下如果没有灯光或警告,你驾驶的汽车突然停止或发出可怕的声音,而你不知道该怎么办。当你用 Python 开发函数和类时,你可以从basicConfig()中受益,它为日志系统进行基本配置:
import loggingdef multiply(x: float, y: float):
"""
:param x: input variable of type float
:param y: input variable of type float
return: returning multiplications of
the input variables
"""
if not isinstance(x, (int, float)) or not isinstance(y,
(int, float)):
logging.error('Input variables are not of type float or integer!')
z = x * y
return z
def reverse_multiply(num_list: list):
"""
:param num_list: list of integers to be checked
for identifying odd numbers
:return: return a list containing element-wise multiplication
of the input list and its reverse
"""
logging.info("Length of {num_list} is {
list_len}".format(num_list=num_list,
list_len = len(num_list)))
rev_list = num_list.copy()
rev_list.reverse()
mult_list = [multiply(num_list[iter], rev_list[iter])
for iter in range(0, len(num_list))]
return mult_list
num_list = [1, 'no', 5, 8, 9]
print(reverse_multiply(num_list))
当你运行前面的代码时,你会得到以下信息和输出:
ERROR:root:Input variables are not of type float or integer!ERROR:root:Input variables are not of type float or integer!
[9, 'nononononononono', 25, 'nononononononono', 9]
记录的错误信息是尝试将字符串'no'与另一个数字相乘的结果。
防御性编程
防御性编程是关于为可能由你、你的队友和你的合作伙伴犯下的错误做好准备。有一些工具、技术和 Python 类可以用来防御代码中的这些错误,例如AssertionError: Variable should be of type float:
assert isinstance(num, float), 'Variable should be of type float'
版本控制
我们在这里讨论的工具和实践只是如何提高你的编程质量以及减少消除代码中问题和错误所需时间的例子。在提高你的机器学习建模能力方面,另一个重要的工具是版本控制。我们将在第十章“版本控制和可重复的机器学习建模”中讨论数据和模型版本控制,但让我们简要地谈谈代码版本控制。
版本控制系统允许你管理代码库中代码和文件的变化,并帮助你跟踪这些变化,访问变化的历史记录,并在开发机器学习管道的不同组件时进行协作。你可以使用如Git及其关联的托管服务GitHub、GitLab和BitBucket等版本控制系统来管理你的项目。这些工具让你和你的团队成员以及合作者可以在不同的代码分支上工作,而不会相互干扰。它还允许你轻松地回到变化的历史记录中,并找出代码中的变化发生在何时。
如果你还没有使用版本控制系统,不要把它们当作一个你需要开始学习的新复杂工具或编程语言。在使用 Git 时,你需要首先了解一些核心概念和术语,例如commit、push、pull和merge。如果你不想或不知道如何使用命令行界面(CLI),使用这些功能可能就像在 PyCharm 这样的 IDE 中点击几下那么简单。
我们回顾了一些常用的技术和工具,以帮助你调试代码和高质量的 Python 编程。然而,还有一些更高级的工具建立在模型之上,如 GPT,例如 ChatGPT(openai.com/blog/ChatGPT)和 GitHub Copilot(github.com/features/copilot),你可以使用这些工具来更快地开发代码,提高代码和代码调试工作的质量。我们将在第十四章“*机器学习最新进展的介绍”中讨论一些这些工具。
虽然使用前面的调试技术或最佳实践来避免 Python 代码中的问题可以帮助你拥有低 bug 的代码库,但它并不能防止所有与机器学习模型相关的问题。这本书是关于超越 Python 编程进行机器学习,帮助你识别机器学习模型中的问题并开发高质量模型。
超越 Python 的调试
消除代码问题并不能解决机器学习模型或数据准备和建模管道中可能存在的所有问题。可能存在一些不会产生任何错误消息的问题,例如来自用于建模的数据的问题,以及测试数据和生产数据(即模型最终需要使用的数据)之间的差异。
生产环境与开发环境
开发环境是我们开发模型的地方,比如我们用于开发的计算机或云环境。在这里,我们编写代码、调试代码、处理数据、训练模型并验证它们。但我们在这个阶段所做的工作不会直接影响用户。
生产环境是模型准备供最终用户使用或可能影响他们的地方。例如,一个模型可以在亚马逊平台上用于推荐产品,被发送到银行系统的其他团队进行欺诈检测,甚至被用于医院以帮助临床医生更好地诊断患者的病情。
用于建模的数据缺陷
数据是机器学习建模的核心组件(图 1.1)。通过获取训练和测试机器学习模型所需的数据,使得机器学习在不同行业如医疗保健、金融、汽车、零售和营销等领域的应用成为可能。当数据被输入到机器学习模型中进行训练(即识别最佳模型参数)和测试时,数据中的缺陷可能导致模型出现问题,例如训练性能低(例如,高偏差)、低泛化能力(例如高方差)或社会经济偏差。在这里,我们将讨论在设计机器学习模型时需要考虑的数据缺陷和属性示例。
数据格式和结构
数据在你的代码或管道中的存储、读取和移动方式可能存在问题。你可能需要处理结构化或表格数据,或者非结构化数据,如视频和文本文档。这些数据可以存储在关系型数据库中,如MySQL或NoSQL(即非关系型)数据库、数据仓库和数据湖中,甚至可以以不同的文件格式存储在本地,如CSV。无论如何,预期的和现有的文件数据结构和格式需要匹配。例如,如果你的代码期望一个制表符分隔的文件格式,但相应的函数的输入文件却是逗号分隔的,那么所有列可能会被合并在一起。幸运的是,大多数情况下,这类问题会导致代码中的错误。
提供的数据和预期数据之间可能存在不匹配,如果代码没有针对这些不匹配进行防御并且没有足够的信息记录,则不会引起任何错误。例如,想象一个 scikit-learn 的fit函数期望有 100 个特征的训练数据,同时你有 100 个数据点。在这种情况下,如果你的代码将特征放在输入 DataFrame 的行或列中,代码将不会返回任何错误。然后,你的代码需要检查输入 DataFrame 的每一行是否包含所有数据点的单个特征值或单个数据点的特征值。以下图示展示了如何通过交换特征和数据点,例如通过转置 DataFrame 来交换行和列,可能会提供错误的输入文件但不会产生错误。在这个图中,我们为了简化考虑了四列和四行。在这里,F 和 D 分别用作特征和数据点的缩写:
图 1.4 – 简化示例,展示如何在期望四个特征的 scikit-learn fit 函数中错误地使用 DataFrame 的转置
数据缺陷不仅限于结构和格式问题。当你试图构建和改进机器学习模型时,需要考虑一些数据特征。
数据数量和质量
尽管机器学习是一个超过半个世纪的概念,但围绕机器学习的兴奋情绪始于 2012 年。尽管在 2010 年至 2015 年之间图像分类算法有所进步,但 1.2 百万张高分辨率图像在 ImageNet LSVRC-2010 竞赛中的可用性以及必要的计算能力在开发第一个高性能图像分类模型(如 AlexNet(Krizhevsky 等人,2012)和 VGG(Simonyan 和 Zisserman,2014))中发挥了关键作用。
除了数据量,数据的质量也起着非常重要的作用。在某些应用中,例如临床癌症设置,高质量的大量数据是不可获取的。从数量和质量中受益也可能成为一种权衡,因为我们可能能够获取更多数据,但质量较低。我们可以选择坚持高质量数据或低质量数据,或者如果可能的话,尝试从高质量和低质量数据中受益。选择正确的方法是特定于领域的,并取决于用于建模的数据和算法。
数据偏见
机器学习模型可能具有不同的偏见,这取决于我们提供给它们的资料。纠正性罪犯管理配置文件用于替代制裁(COMPAS)是机器学习模型中具有报告偏见的著名例子。COMPAS 旨在根据被告对超过 100 个调查问题的回答来估计其再犯的可能性。对问题的回答的总结产生一个风险评分,其中包括是否有一位囚犯的父母曾入狱等问题。尽管这个工具在许多例子中都取得了成功,但当它在预测方面出错时,白人和黑人罪犯的结果并不相同。COMPAS 的开发公司提供了支持其算法发现的数据。你可以找到文章和博客文章了解更多关于其当前状态以及它是否仍在使用或是否仍有偏见的信息。
这些是数据问题和它们在结果机器学习模型中的后果的一些例子。但模型中还有其他问题并非源于数据。
模型和预测中心调试
训练、测试和生产阶段模型的预测可以帮助我们检测模型的问题并找到改进它们的机会。在这里,我们将简要回顾一些以模型和预测为中心的模型调试方面。你可以在本书的未来章节中了解更多关于这些问题和其他考虑因素,如何识别问题的根源,以及如何在未来的章节中解决它们。
欠拟合和过拟合
当我们训练一个模型,例如监督学习模型时,目标是不仅在训练阶段,而且在测试阶段都拥有高性能。当一个模型即使在训练集上表现不佳时,我们需要处理欠拟合的问题。我们可以开发更复杂的模型,例如随机森林或深度学习模型,而不是线性回归和逻辑回归模型。更复杂的模型可能会降低欠拟合,但它们可能会引起过拟合,导致预测对测试或生产数据的泛化能力降低(图 1.5):
图 1.5 – 欠拟合和过拟合的示意图
算法和超参数的选择决定了在训练和测试机器学习模型时的复杂程度以及欠拟合或过拟合的可能性。例如,通过选择可以学习非线性模式的模型而不是线性模型,你的模型在训练数据中能够识别更复杂的模式,因此有更高的可能性降低欠拟合。但与此同时,你可能会增加过拟合的可能性,因为训练数据中的一些复杂模式可能无法推广到测试数据(图 1.5)。有一些方法可以评估欠拟合和过拟合,这有助于你开发高性能且可泛化的模型。我们将在未来的章节中讨论这些问题。
模型超参数
一些参数可能会影响机器学习模型的性能,这些参数在训练过程中通常不会自动优化。这些被称为超参数。我们将在未来的章节中通过一些这样的超参数的例子进行说明,例如随机森林模型中的树的数量或神经网络模型中隐藏层的大小。
模型测试和生产中的推理
机器学习建模的最终目标是拥有在生产环境中高度有效的模型。当我们测试模型时,我们正在评估其泛化能力,但我们不能确定它在未见过的数据上的表现。用于训练机器学习模型的数据可能会过时。例如,服装市场趋势的变化可能会使服装推荐模型的预测不可靠。
在这个主题中存在不同的概念,如数据方差、数据漂移和模型漂移,我们将在接下来的几章中涵盖这些内容。
用于改变景观的数据或超参数
当我们使用特定的训练数据和一组超参数训练机器学习模型时,模型参数的值会发生变化,以便它们尽可能地接近定义的目标或损失函数的最优点。实现更好模型的另外两个工具是提供更好的训练数据和选择更好的超参数。每个算法都有提高性能的潜力。仅通过调整模型超参数,你无法开发出最佳模型。同样,仅通过增加数据和保持模型超参数不变,你也无法实现最佳性能。因此,数据和超参数是相辅相成的。在你阅读下一章之前,请记住,仅通过在超参数优化上投入更多的时间和金钱,你并不一定能得到更好的模型。我们将在本书的后面更详细地探讨这一点。
摘要
在本章中,我们回顾了软件开发中调试的重要概念和方法,以及它们与机器学习模型调试的区别。你了解到,在机器学习建模中的调试超出了软件调试的范畴,以及数据和算法,除了代码之外,也可能导致模型存在缺陷或性能低下,以及预测不可靠。你可以从这些理解以及本书中将要学习的工具和技术中受益,以开发可靠的机器学习模型。
在下一章中,你将了解机器学习生命周期的不同组成部分。你还将学习如何通过这些组件模块化机器学习建模,这有助于我们在训练和测试前后识别改进模型的机会。
问题
-
你的代码是否有意外的缩进,但不会返回任何错误信息?
-
在 Python 中,
AttributeError和NameError之间的区别是什么? -
数据维度如何影响模型性能?
-
Python 中的traceback消息提供了关于你代码中错误的信息是什么?
-
你能解释两个高质量的 Python 编程的最佳实践吗?
-
你能解释为什么你可能有不同置信水平的特征或数据点吗?
-
你能提供有关如何减少在为给定数据集构建模型时欠拟合或过拟合的建议吗?
-
我们是否可能有一个在生产环境中性能显著低于测试环境的模型?
-
当我们也可以提高训练数据的质量或数量时,专注于超参数优化是否是一个好主意?
参考文献
-
Widyasari, Ratnadira, 等人. BugsInPy: 一个用于 Python 程序现有错误的数据库,以实现受控的测试和调试研究. 第 28 届 ACM 欧洲软件工程会议和软件工程基础研讨会论文集。2020。
-
《软件测试的艺术,第二版》,作者:Glenford J. Myers, Corey Sandler, Tom Badgett, Todd M. Thomas.
-
Krizhevsky, Alex, Ilya Sutskever, 和 Geoffrey E. Hinton. 使用深度卷积神经网络的 ImageNet 分类. 神经信息处理系统进展 25 (2012).
-
Simonyan, Karen, 和 Andrew Zisserman. “用于大规模图像识别的超深层卷积神经网络.” arXiv 预印本 arXiv:1409.1556 (2014).
arxiv.org/abs/1409.1556.
第二章:机器学习生命周期
在实践中,机器学习建模,无论是在工业级别还是在学术研究中,都不仅仅是写几行 Python 代码来在公共数据集上训练和评估一个模型。学习编写 Python 程序来使用 Python 和scikit-learn或使用PyTorch的深度学习模型训练机器学习模型,是成为机器学习开发者和专家的起点。在本章中,你将了解机器学习生命周期的组件以及如何在规划机器学习建模时考虑这个生命周期,这有助于你设计一个有价值且可扩展的模型。
本章将涵盖以下主题,包括机器学习生命周期的核心组件:
-
在我们开始建模之前
-
数据收集
-
数据选择
-
数据探索
-
数据清洗
-
数据建模准备
-
模型训练和评估
-
测试代码和模型
-
模型部署和监控
到本章结束时,你将学会如何为你的项目设计机器学习生命周期,以及为什么将你的项目模块化到生命周期的组件中有助于你在协作模型开发中。你还将了解机器学习生命周期不同组件的一些技术和它们的 Python 实现,例如数据清洗和模型训练与评估。
技术要求
以下要求应考虑在本章中,因为它们将帮助你更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:
-
Python 库要求:
-
sklearn>= 1.2.2 -
numpy>= 1.22.4 -
pandas>= 1.4.4 -
matplotlib>= 3.5.3
-
你可以在 GitHub 上找到本章的代码文件,地址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter02。
在我们开始建模之前
在收集数据作为机器学习生命周期的起点之前,你需要了解你的目标。你需要知道你想要解决什么问题,然后定义一些较小的子问题,这些子问题可以通过机器学习来解决。例如,在像“我们如何减少返回制造工厂的易碎产品数量?”这样的问题中,子问题可能如下:
-
我们如何在包装前检测裂缝?
-
我们如何设计更好的包装来保护产品并减少运输造成的裂缝?
-
我们能否使用更好的材料来降低开裂的风险?
-
我们能否对产品进行一些小的设计改动,这些改动不会改变其功能,但可以降低开裂的风险?
一旦您已经确定了您的子问题,您就可以找出如何使用机器学习来解决每个问题,并针对定义的子问题进行机器学习生命周期。每个子问题可能需要特定的数据处理和机器学习建模,其中一些可能比其他问题更容易解决。
图 2.1 展示了机器学习生命周期中的主要步骤。其中一些名称并非普遍定义。例如,数据探索有时会被包含在数据处理中。但所有这些步骤都是必需的,即使在不同资源中它们的名称可能不同:
图 2.1 – 机器学习生命周期
当您依赖已经在 Python 中可用的数据集,例如通过 scikit-learn 或 PyTorch,或者一个在公共存储库中准备好的用于建模的数据集时,您不需要担心早期步骤,如数据收集、选择和处理。这些步骤已经为您处理好了。或者如果您只是进行建模练习,不想在生产系统中提供您的模型,您也不需要担心模型部署和监控。但理解所有这些步骤的意义、重要性和好处,有助于您开发或设计一个具有持续改进功能的技术,为用户提供服务。这也有助于您更好地理解作为机器学习开发者的角色,或者找到这个领域的第一份工作或更好的工作。
数据收集
机器学习生命周期的第一步是数据收集。这可能涉及从不同的公共或商业数据库收集数据,将用户数据存储回您的数据库或任何您拥有的数据存储系统,或者甚至使用那些为您处理数据收集和标注的商业实体。如果您依赖免费资源,您可能需要考虑数据在您本地或云存储系统中所占的空间以及您在后续步骤中收集和分析数据所需的时间。但对于付费数据,无论是商业资源中提供的还是数据收集、生成和标注公司生成的,在决定付费之前,您需要评估数据对建模的价值。
数据选择
根据相应项目的目标,我们需要选择用于模型训练和测试所需的数据。例如,你可能可以访问一个或多个医院中癌症患者的相关信息,例如他们的年龄、性别、是否吸烟,如果有的话,他们的遗传信息,如果有的话,他们的 MRI 或 CT 扫描,他们用药的历史,他们对癌症药物的反应,他们是否进行了手术,他们的处方,无论是手写还是 PDF 格式,以及更多。当你想要构建一个使用他们的 CT 扫描来预测患者对治疗反应的机器学习模型时,你需要为每位患者选择与你想使用他们的信息(如年龄、性别和吸烟状况)构建模型时不同的数据。如果你正在构建一个监督学习模型,你还需要选择那些你有输入和输出数据的患者。
注意
在半监督学习模型中,可以将带有和没有输出的数据点结合起来。
为你的模型选择相关数据并不是一项容易的任务,因为将数据区分成对模型目标来说是相关和不相关的信息并不一定以这种二进制方式可用。想象一下,你需要从一个化学、生物或物理数据库中提取数据,这可能是一组来自不同较小数据集的数据,论文的补充材料,甚至是从科学文章中来的数据。或者,你可能想要从患者的病历或甚至从经济或社会学调查的书面答案中提取信息。在所有这些例子中,为你的模型分离数据,或从相关数据库中查询相关数据,并不像搜索一个关键词那么简单。每个关键词可能有同义词,无论是普通英语还是技术术语,可能以不同的方式书写,有时相关信息可能存在于数据文件或关系数据库的不同列中。适当的数据选择和查询系统为你提供了提高模型的机会。
你可以受益于文献综述,并在需要时向专家咨询,以扩展你使用的关键词。你可以从已知的数据选择方法中受益,对于你拥有的特定任务,甚至可以许可工具或付费服务来帮助你提取更多与你的目标相关的数据。还有高级的自然语言处理技术可以帮助你在查询系统中从文本中提取信息。我们将在第十三章,高级深度学习技术,和第十四章,机器学习最新进展介绍中讨论这些内容。
数据探索
在这个阶段,您可以选择数据并探索数据的数量、质量、稀疏性和格式。如果您在监督学习中具有分类输出,您可以找到每个类别的数据点数量,特征分布,输出变量的置信度(如果有的话),以及从数据选择阶段获取的数据的其他特征。这个过程有助于您识别需要修复的数据问题,这些问题将在生命周期中的下一个步骤数据整理中解决,或者通过修改您的数据选择过程来提高数据的机会。
数据整理
您的数据需要经过结构化和丰富过程,并在必要时进行转换和清理。所有这些方面都是数据整理的一部分。
结构化
原始数据可能以不同的格式和大小出现。您可能可以访问手写笔记、Excel 表格,甚至包含需要提取并放入正确格式以供进一步分析和用于建模的信息的表格图像。这个过程并不是将所有数据转换成类似表格的格式。在数据结构化的过程中,您需要小心信息丢失。例如,您可能有一些按特定顺序排列的特征,如基于时间、日期或通过设备传入的信息序列。
丰富
在对数据进行结构和格式化之后,您需要评估您是否拥有构建该周期机器学习模型所需的数据。在继续整理过程之前,您可能发现添加或生成新数据的机会。例如,您可能会发现,在用于识别制造管道中产品图像裂缝的数据中,只有 50 张标签为裂缝产品图像的图像,而总共有 10,000 张图像。您可能能够找到其他裂缝产品的图像,或者您可以使用称为数据增强的过程生成新的图像。
数据增强
数据增强是一系列通过使用我们手头上的原始数据集,计算性地生成新数据点的技术。例如,如果您旋转您的肖像,或者通过向图像添加高斯噪声来改变图像的质量,新的图像仍然会显示您的脸。但这可能有助于使您的模型更具泛化能力。我们将在第五章 提高机器学习模型性能中讨论不同的数据增强技术。
数据转换
数据集的特征和输出可能是不同类型的变量,包括以下内容:
-
定量或数值:
-
离散:例如,一个社区中的房屋数量
-
连续:例如,患者的年龄或体重
-
-
定性或分类:
-
名义(无顺序):例如,不同颜色的汽车
-
有序(有序的定性变量):例如,学生的成绩,如 A、B、C 或 D
-
当我们训练一个机器学习模型时,模型需要在优化过程的每次迭代中使用数值来计算损失函数。因此,我们需要将分类变量转换为数值替代品。有多种特征编码技术,其中三种是独热编码、目标编码(Micci-Barreca,2001)和标签编码。一个包含年龄、性别、组和目标等四个列和七个行(七个示例数据点)的示例矩阵的独热、标签和目标编码计算如图 2.2 所示。2*:
图 2.2 – 使用具有四个特征和七个数据点的简单示例数据集进行独热、目标和标签编码的手动计算
这是一个用于预测患者对药物反应的假设数据集,目标列作为输出。变量类别缩写为 F:女性,M:男性,H1:医院 1,H2:医院 2,和 H3:医院 3。在现实中,需要考虑更多的变量,并且需要更多的数据点来有一个可靠的药物反应预测模型,并评估男性组和女性组之间或不同医院之间患者对药物反应是否存在偏差。
这些技术各有其优点和缺点。例如,独热编码增加了特征的数量(即数据集的维度)并增加了过拟合的机会。标签编码将整数值分配给每个类别,这些值不一定有意义。例如,将男性视为 1,女性视为 0 是任意的,并且没有任何实际意义。目标编码是一种考虑每个类别相对于目标的概率的替代方法。您可以在 Micci-Barreca,2001 中阅读此过程的数学细节。以下代码片段提供了这些方法的 Python 实现。
让我们定义一个用于特征编码的合成 DataFrame:
import pandas as pdorig_df = pd.DataFrame({
'age': [45, 43, 54, 56, 54, 52, 41],
'gender': ['M', 'F', 'F', 'M', 'M', 'F', 'M'],
'group': ['H1', 'H1', 'H2', 'H3', 'H2', 'H1', 'H3'],
'target': [0, 0, 1, 0, 1, 1, 0]})
首先,我们将使用标签编码对定义的 DataFrame 中的分类特征进行编码:
# encoding using label encodingfrom sklearn.preprocessing import LabelEncoder
# initializing LabelEncoder
le = LabelEncoder()
# encoding gender and group columns
label_encoded_df = orig_df.copy()
label_encoded_df['gender'] = le.fit_transform(
label_encoded_df.gender)
label_encoded_df['group'] = le.fit_transform(
label_encoded_df.group)
然后,我们将尝试对分类特征进行独热编码:
# encoding using one hot encodingfrom sklearn.preprocessing import OneHotEncoder
# initializing OneHotEncoder
ohe = OneHotEncoder(categories = 'auto')
# encoding gender column
gender_ohe = ohe.fit_transform(
orig_df['gender'].values.reshape(-1,1)).toarray()
gender_ohe_df = pd.DataFrame(gender_ohe)
# encoding group column
group_ohe = ohe.fit_transform(
orig_df['group'].values.reshape(-1,1)).toarray()
group_ohe_df = pd.DataFrame(group_ohe)
# generating the new dataframe with one hot encoded features
onehot_encoded_df = pd.concat(
[orig_df, gender_ohe_df, group_ohe_df], axis =1)
onehot_encoded_df = onehot_encoded_df.drop(
['gender', 'group'], axis=1)
onehot_encoded_df.columns = [
'age','target','M', 'F','H1','H2', 'H3']
现在,我们将安装category_encoders库后,在 Python 中实现目标编码,作为第三种编码方法,如下所示:
# encoding using target encodingfrom category_encoders import TargetEncoder
# initializing LabelEncoder
te = TargetEncoder()
# encoding gender and group columns
target_encoded_df = orig_df.copy()
target_encoded_df['gender'] = te.fit_transform(
orig_df['gender'], orig_df['target'])
target_encoded_df['group'] = te.fit_transform(
orig_df['group'], orig_df['target'])
有序变量也可以通过OrdinalEncoder类作为sklearn.preprocessing的一部分进行转换。有序变量和名义变量转换之间的区别在于有序变量中类别顺序背后的含义。例如,如果我们正在编码学生的成绩,A、B、C 和 D 可以转换为 1、2、3 和 4,或者 4、3、2 和 1,但将它们转换为 1、3、4 和 2 将不可接受,因为这改变了成绩顺序背后的含义。
输出变量也可以是分类变量。您可以使用标签编码将名义输出转换为数值变量,以便用于分类模型。
清洗
数据结构化后,需要对其进行清洗。清洗数据有助于提高数据质量,使其更接近建模准备状态。清洗过程的一个例子是在数据中填充缺失值。例如,如果您想使用患者的居住习惯来预测他们患糖尿病的风险,并使用他们对调查的回应,您可能会发现一些参与者没有回答有关他们吸烟习惯的问题。
特征插补以填充缺失值
我们手头的数据集的特征可能包含缺失值。大多数机器学习模型及其相应的 Python 实现都无法处理缺失值。在这些情况下,我们需要删除具有缺失特征值的数据点,或者以某种方式填充这些缺失值。我们可以使用特征插补技术来计算数据集中缺失的特征值。这些方法的示例如图 2.3 所示:
图 2.3 – 计算缺失特征值的特征插补技术
如您所见,我们既可以使用相同特征的其它值,并用可用的值的均值或中位数来替换缺失值,也可以使用与缺失值特征高度相关的、低缺失值或无缺失值的其它特征。在后一种情况下,我们可以使用与目标缺失值特征相关性最高的特征来构建线性模型。线性模型将相关特征视为输入,将缺失值特征视为输出,然后使用线性模型的预测来计算缺失值。
当我们使用相同特征的值的统计摘要,如均值或中位数时,我们正在减少特征值的方差,因为这些摘要值将被用于相同特征的所有缺失值 (图 2.3)。另一方面,当我们使用具有缺失值特征和低缺失值或无缺失值的高相关特征之间的线性模型时,我们假设它们之间存在线性关系。或者,我们可以在特征之间构建更复杂的模型来进行缺失值计算。所有这些方法都有其优点和局限性,您需要根据特征值的分布、具有缺失特征值的数据点的比例、特征之间的相关范围、低缺失值或无缺失值特征的存在以及其他相关因素,选择最适合您数据集的方法。
我们在图 2.3中使用了四个特征和五个数据点的非常简单的案例来展示所讨论的特征插补技术。但在现实中,我们需要构建具有超过四个特征的模型。我们可以使用 Python 库,如scikit-learn,通过使用相同特征值的平均值来进行特征插补,如下所示。首先,我们将导入所需的库:
import numpy as npfrom sklearn.impute import SimpleImputer
然后,我们必须定义一个二维输入列表,其中每个内部列表显示数据点的特征值:
X = [[5, 1, 2, 8], [2, 3, np.nan, 4],
[5, 4, 4, 6],
[8, 5, np.nan, 7],
[7, 8, 8, 3]]
现在,我们已经准备好通过指定需要考虑哪些值作为缺失值以及使用哪种插补策略来拟合SimpleImputer函数:
# strategy options: mean, median, most_frequent, constantimp = SimpleImputer(missing_values=np.nan, strategy='mean')
imp.fit(X)
# calculate missing values of the input
X_no_missing = imp.transform(X)
我们还可以使用scikit-learn来创建一个线性回归模型,该模型计算缺失的特征值:
import numpy as npfrom sklearn.linear_model import LinearRegression as LR
# defining input variables for feature 2 and 3
f2 = np.array([1, 4, 8]).reshape((-1, 1))
f3 = np.array([2, 4, 8])
# initializing a linear regression model with sklearn LinearRegression
model = LR()
# fitting the linear regression model using f2 and f3 as input and output variables, respectively
model.fit(f2, f3)
# predicting missing values of feature 3
model.predict(np.array([3, 5]).reshape((-1, 1)))
异常值移除
我们数据集中的数值变量可能具有远离其他数据的值。它们可能是与数据点中的其他值不相似的真实值,或者是由数据生成过程中的错误引起的,例如在实验测量过程中。您可以使用箱线图(图 2.4)直观地看到并检测到它们。图中的圆圈是 Python 中绘图函数(如matplotlib.pyplot.boxplot)自动检测到的异常值(图 2.4)。尽管可视化是探索我们的数据和理解数值变量分布的好方法,但我们仍需要一个无需绘制数据集中所有变量值的定量方法来检测异常值。
检测异常值的最简单方法是使用变量值分布的分位数。超出上下界限的数据点被认为是异常值(图 2.4)。下限和上限可以计算为 Q1 - a.IQR 和 Q3 - a.IQR,其中 a 是一个介于 1.5 和 3 之间的实数值。a 的常用值,也是绘制箱线图时的默认值,是 1.5,但使用更高的值会使异常识别过程不那么严格,并让更少的数据点被检测为异常。例如,将异常检测的严格性从默认值(即 a = 1.5)更改为 a = 3,图 2.4中的所有数据点都不会被检测为异常。这种异常识别方法是非参数的,这意味着它对数据点的分布没有任何假设。因此,它可以应用于非正态分布,例如图 2.4中显示的数据:
图 2.4 – 直方图和箱线图中的异常值
在前面的图中,图表是使用scikit-learn包中糖尿病数据集的特征值生成的,该数据集是通过sklearn.datasets.load_diabetes()加载的。
数据缩放
特征的值,无论是原始数值还是经过转换后的,可能具有不同的范围。如果机器学习模型的特征值得到适当的缩放和归一化,许多模型的表现会更好,或者至少它们的优化过程会更快地收敛。例如,如果您有一个范围从 0.001 到 0.05 的特征,另一个范围从 1,000 到 5,000 的特征,将它们都调整到合理的范围,如[0, 1]或[-1, 1],可以帮助提高收敛速度或模型性能。您需要确保您实施的缩放和归一化不会导致特征值中的数据点失去差异,这意味着基于经过转换的特征的数据点不会失去它们之间的差异。
缩放的目标是改变变量的值域。在归一化中,值的分布形状也可能发生变化。您可以在项目中使用scikit-learn中提供的这些方法的示例和相应的类来改进您特征的缩放和分布(表 2.1)。使用这些类中的每一个进行缩放后得到的缩放变量具有特定的特征。例如,使用scikit-learn的StandardScaler类后,变量的值将围绕零中心,标准差为 1。
其中一些技术,例如鲁棒缩放,可以使用scikit-learn的RobustScaler类实现,不太可能受到异常值的影响(表 2.1)。在鲁棒缩放中,根据我们提供的定义,异常值不会影响中位数和IQR的计算,因此不会影响缩放过程。异常值本身可以使用计算出的中位数和IQR进行缩放。在缩放之前或之后,根据所使用的机器学习方法和任务,可以选择保留或删除异常值。但重要的是在尝试为建模准备数据时检测它们,并意识到它们,如果需要,可以对其进行缩放或删除:
| Python 类 | 数学定义 | 值限制 |
|---|---|---|
sklearn.preprocessing.StandardScaler() | Z = (X - u) / su: 均值 | 无限制>99%的数据在-3 和 3 之间 |
sklearn.preprocessing.MinMaxScaler() | X_scaled = (X-Xmin)/(Xmax-Xmin) | [0,1] |
sklearn.preprocessing.MaxAbsScaler() | X_scaled = X/|X|max | [-1,1] |
sklearn.preprocessing.RobustScaler() | Zrobust = (X - Q2) / IQRQ2: 中位数 IQR: 四分位距 | 无限制大多数数据在-3 和 3 之间 |
表 2.1 – 缩放和归一化特征值的 Python 类示例
在开始机器学习建模之前,会先进行数据整理,然后进行其他形式的数据探索性分析。领域专业知识也有助于识别需要更好地理解其主题领域的解释模式的模式。为了提高机器学习建模的成功率,你可能需要进行特征工程来构建新的特征或通过表示学习学习新的特征。这些新特征可能像体质指数(BMI)那样简单,体质指数定义为某人以千克为单位体重的平方与以米为单位身高平方的比率。或者,它们可能是通过复杂过程或额外机器学习建模学习到的新特征和表示。我们将在第十四章“*机器学习最新进展介绍”中稍后讨论这一点。
数据建模准备
在机器学习生命周期的这个阶段,我们需要最终确定用于建模的特征和数据点,以及我们的模型评估和测试策略。
特征选择和提取
在之前的步骤中进行了归一化和缩放的原始特征现在可以进一步处理,以提高拥有高性能模型的概率。一般来说,特征可以通过特征选择方法进行子选择,这意味着一些特征被丢弃,或者可以用来生成新的特征,这传统上被称为特征提取。
特征选择
特征选择的目的是减少特征的数量或数据的维度,并保留信息丰富的特征。例如,如果我们有 20,000 个特征和 500 个数据点,那么当用于构建监督学习模型时,大多数原始的 20,000 个特征可能不是信息性的。以下列表解释了一些简单的特征选择技术:
-
保留数据点间具有高方差或 MAD 的特征
-
保留数据点间具有最高唯一值数量的特征
-
保留高度相关特征组中的代表性特征
这些过程可以使用所有数据点或仅使用训练数据来进行,以避免训练数据和测试数据之间潜在的信息泄露。
特征提取
线性或非线性地结合原始特征可能会为构建预测模型提供更有信息量的特征。这个过程被称为特征提取,可以根据领域知识或通过不同的统计或机器学习模型进行。例如,你可以使用主成分分析或等距映射以线性或非线性方式分别降低你的数据的维度。然后,你可以在你的训练和测试过程中使用这些新特征。以下代码片段提供了这两种方法的 Python 实现。
首先,让我们导入所需的库并加载scikit-learn数字数据集:
import numpy as npimport matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.manifold import Isomap
from sklearn.datasets import load_digits
# loading digit dataset from sklearn
X, _ = load_digits(return_X_y=True)
print('Number of features: {}'.format(X.shape[1]))
现在,让我们使用isomap和pca,它们都可在scikit-learn中找到:
# fitting isomap and build new dataframe of feature with 5 componentsembedding = Isomap(n_components=5)
X_transformed_isomap = embedding.fit_transform(X)
print('Number of features: {}'.format(
X_transformed_isomap.shape[1]))
# fitting pca and build new dataframe of feature with 5 components
pca = PCA(n_components=5)
X_transformed_pca = pca.fit_transform(X)
print('Number of features: {}'.format(
X_transformed_pca.shape[1]))
# plotting ratio of variance explained by the first n, being between 1 and 5, components
plt.bar(x = np.arange(0, len(
pca.explained_variance_ratio_)),
height = np.cumsum(pca.explained_variance_ratio_))
plt.ylabel('Explained variance ratio')
plt.xlabel('Number of components')
plt.show()
你可以从每种方法中选择多少个组件可以通过不同的技术来确定。例如,解释方差比是选择主成分数量的常用方法。这些是通过主成分分析确定的,并且共同解释了超过特定百分比的总方差,例如在数据集中解释了 70%的总方差。
还有更多高级技术,它们是自监督预训练和表示学习的一部分,用于识别新特征。在这些技术中,使用大量数据来计算新特征、表示或嵌入。例如,可以使用英文维基百科来提出更好的英文单词表示,而不是为每个单词执行独热编码。我们将在第十四章中讨论自监督学习模型,机器学习最新进展介绍。
设计评估和测试策略
在训练模型以识别其参数或最佳超参数之前,我们需要指定我们的测试策略。如果你在一个大型组织中工作,模型测试可以由另一个团队在单独的数据集上完成。或者,你可以指定一个或多个数据集,这些数据集与你的训练集分开,或者将你的数据的一部分分开,以便你可以单独测试它。你还需要列出你希望在测试阶段评估模型性能的方法。例如,你可能需要指定你想要使用的性能图表或度量,如接收者操作特征曲线(ROC)和精确率-召回率(PR)曲线,或其他标准,以选择新的分类模型。
一旦定义了测试策略,你就可以使用剩余的数据来指定训练集和验证集。验证集和训练集不需要是一系列固定的数据点。我们可以使用k-折交叉验证(CV)将数据集分成k个块,每次使用一个块作为验证集,其余的作为训练集。然后,可以使用所有k个块的平均性能作为验证集来计算验证性能。训练性能对于根据模型的目标找到模型参数的最佳值非常重要。你还可以使用验证性能来识别最佳超参数值。如果你指定了一个验证集或使用k-折 CV,你可以使用不同超参数组合的验证性能来识别最佳组合。然后,可以使用最佳超参数集在所有数据上训练模型,排除测试数据,以便在测试阶段提出最终要测试的模型。
对于每个应用程序,关于折叠数(即 k)或要分离为验证集和测试集的数据点分数的一些常见做法。对于小型数据集,通常使用 60%、30% 和 10% 分别指定数据点的训练、验证和测试分数。但是,数据点的数量及其多样性都是决定验证集和测试集中数据点数量或在 CV 中指定 k 的重要因素。您还可以使用可用的 Python 类,这些类使用您选择的 k 进行训练和验证,如下所示:
from sklearn.model_selection import cross_val_score,KFoldfrom sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
# loading breast cancer dataset
X, y = load_breast_cancer(return_X_y=True)
# defining the k-fold CV
k_CV = KFold(n_splits=5)
# initializing a k nearest neighbor model
knn = KNeighborsClassifier()
# outputting validation performances using average precision across different folds of the designed CV
scores = cross_val_score(
estimator = knn, X = X, y = y, cv = k_CV,
scoring = 'average_precision')
print("Average cross validation score: {}".format(
round(scores.mean(),4)))
这将返回以下输出:
Average Cross Validation score: 0.9496
注意
最好,在每个阶段准备的数据不应该只是简单地存放在云端或硬盘上,或者在每个生命周期的前一步之后添加到数据库中。将报告附加到数据上以跟踪每个步骤的历史努力,并为团队或组织内的其他个人或团队提供这些信息是有益的。适当的报告,如关于数据清洗的,可以提供寻求反馈的机会,以帮助您改进为机器学习建模提供的数据。
模型训练和评估
如果您使用 scikit-learn 或 PyTorch 和 TensorFlow 进行神经网络建模,训练和验证或测试模型的过程包括以下三个主要步骤:
-
初始化模型:初始化模型是关于指定方法、其超参数以及用于建模的随机状态。
-
训练模型:在模型训练中,步骤 1 中初始化的模型用于训练数据以训练机器学习模型。
-
推理、分配和性能评估:在这个步骤中,训练好的模型可以用于监督学习中的推理(例如,预测输出)或,例如,将新数据点分配给无监督学习中已识别的聚类。在监督学习中,您可以使用这些预测来评估模型性能。
这些步骤对于监督学习和无监督学习模型都是相似的。在 步骤 1 和 步骤 2 中,两种类型的模型都可以进行训练。以下代码片段提供了使用 scikit-learn 实现这三个步骤的 Python 实现,用于随机森林分类器和 k-均值聚类。
首先,让我们导入所需的库并加载 scikit-learn 乳腺癌数据集:
from sklearn.datasets import load_breast_cancerfrom sklearn.model_selection import train_test_split
from sklearn import metrics
# loading breast cancer dataset
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.30, random_state=5)
现在,我们可以使用随机森林来训练和测试一个监督学习模型:
from sklearn.ensemble import RandomForestClassifier# initializing a random forest model
rf_model = RandomForestClassifier(n_estimators=10,
max_features=10, max_depth=4)
# training the random forest model using training set
rf_model.fit(X_train, y_train)
# predicting values of test set using the trained random forest model
y_pred_rf = rf_model.predict(X_test)
# assessing performance of the model on test setprint("Balanced accuracy of the predictions:",
metrics.balanced_accuracy_score(y_test, y_pred_rf))
此代码在测试集上打印出以下性能:
Balanced accuracy of the predictions: 0.9572
我们还可以构建一个 k-均值聚类模型,如下所示:
from sklearn import cluster# initializing a random forest model
kmeans_model = cluster.KMeans(n_clusters=2, n_init = 10)
# training the kmeans clustering model using training set
kmeans_model.fit(X_train)
# assigning new observations, that are test set datapoints here, to the identified clusters
y_pred_kmeans = kmeans_model.predict(X_test)
如果您在机器学习建模方面没有足够的经验,表 2.2 中提供的方法和相应的 Python 类可能是一个好的起点:
| 类型 | 方法 | Python 类 |
|---|---|---|
| 分类 | 逻辑回归 | sklearn.linear_model.LogisticRegression() |
| K-最近邻 | sklearn.neighbors.KNeighborsClassifier() | |
| 支持向量机分类器 | sklearn.svm.SVC() | |
| 随机森林分类器 | sklearn.ensemble.RandomForestClassifier() | |
| XGBoost 分类器 | xgboost.XGBClassifier() | |
| LightGBM 分类器 | Lightgbm.LGBMClassifier() | |
| 回归 | 线性回归 | sklearn.linear_model.LinearRegression() |
| 支持向量机回归器 | sklearn.svm.SVR() | |
| 随机森林回归器 | sklearn.ensemble.RandomForestRegressor() | |
| XGBoost 回归器 | xgboost.XGBRegressor() | |
| LightGBM 回归器 | Lightgbm.LGBMRegressor() | |
| 聚类 | K-均值聚类 | sklearn.cluster.KMeans() |
| 聚类层次 | sklearn.cluster.AgglomerativeClustering() | |
| DBSCAN 聚类 | sklearn.cluster.DBSCAN() | |
| UMAP | umap.UMAP() |
表 2.2 – 对于你的监督学习或聚类问题的表格数据,开始方法和它们的 Python 类
注意
UMAP 是一种降维方法,它提供了低维可视化,例如一系列数据点的 2D 图。在低维空间中形成的数据点组也可以用作可靠的聚类。
测试代码和模型
尽管选定的机器学习模型在生命周期这一阶段可以使用一个或多个数据集进行进一步测试,但在这个阶段还需要进行一系列测试以确保这一点:
-
确保部署过程和将模型投入生产的流程顺利进行
-
确保模型在性能和计算成本方面按预期工作
-
确保在生产中使用模型不会产生法律和财务影响
在这个阶段可以使用的此类测试包括:
-
单元测试:这些是快速测试,确保我们的代码运行正确。这些测试不仅针对机器学习建模,甚至不是针对这个阶段。在整个生命周期中,你需要设计单元测试以确保你的数据处理和建模代码按预期运行。
-
A/B 测试:这种测试可以帮助你、你的团队以及你的组织决定是否选择或拒绝一个模型。这种测试的想法是评估两种可能的场景,例如两个模型,或者前端设计的两种不同版本,并检查哪一个更有利。但是,你需要通过决定需要测量什么和你的选择标准来定量评估结果。
-
scikit-learn、PyTorch或 TensorFlow 的更改,这个测试确保你的代码运行,并检查这些更改对模型性能和预测的影响。 -
安全测试:安全测试是工业级编程和建模的重要部分。你需要确保你的代码和依赖项没有漏洞。然而,你需要设计一个测试来应对高级对抗性攻击。我们将在第三章中讨论,向负责任 AI 调试。
-
负责任的 AI 测试:我们需要设计测试来评估负责任 AI 的重要因素,例如透明度、隐私和公平性。我们将在下一章中讨论负责任 AI 的一些重要方面。
虽然这些测试需要在这个阶段设计,但类似的测试可以作为生命周期之前步骤的一部分进行整合。例如,你可以在生命周期的所有步骤中进行安全测试,特别是如果你正在使用不同的工具或代码库。可能还有其他测试,例如检查模型的内存大小和预测运行时间,或者生产中的数据格式和结构以及部署的模型中预期的数据是否相同。
模型部署和监控
如果你刚开始接触部署,你可能认为它是如何为你的模型最终用户开发前端、移动应用程序或 API。但在这本书中,我们不想讨论这个话题。在这里和未来的章节中,我们想要涵盖部署的两个重要方面:提供生产环境中模型所需的操作以及将模型集成到应该为用户带来利益的过程中。
当你部署你的模型时,你的代码应该在指定的环境中正常运行,并且能够访问所需的硬件,例如 GPU,并且用户的数据需要以正确的格式可用,以便你的模型能够工作。我们在生命周期测试阶段讨论的一些测试确保你的模型在生产环境中按预期运行。
当我们谈论在生产环境中提供模型时,它要么在幕后被用于用户的利益,例如 Netflix 和 Amazon Prime 使用他们的机器学习模型为你推荐电影,要么被用户直接作为独立进程或作为更大系统的一部分使用,例如当机器学习模型在医院中用于帮助临床医生进行疾病诊断。这两种不同用例的考虑因素并不相同。如果你想将模型部署在医院中供临床医生直接使用,你需要考虑设置适当的生产环境所需的全部困难和规划,以及所有软件依赖项。你还需要确保他们的本地系统满足必要的硬件要求。或者,你可以通过 Web 应用程序提供你的模型。在这种情况下,你需要确保上传到你的数据库中的数据的保密性和安全性。
当涉及到收集必要的信息和反馈时,模型辅导是机器学习生命周期中的一个关键部分。这些反馈可以用来改进或纠正用于建模的数据,或者改进模型的训练和测试。监控机器学习模型有助于我们确保生产中的模型能够根据预期提供预测。可能导致机器学习模型预测不可靠的三个问题是数据方差、数据漂移和概念漂移。数据漂移和概念漂移被认为是两种不同类型的模型漂移。模型漂移涉及数据中不同类型的改变,无论是特征还是输出变量,这些改变使得模型对新用户数据的预测变得无关或无效。
我们将在本书的后续章节中更详细地讨论模型部署和监控,以及机器学习生命周期中的工程方面,例如第十章,版本控制和可重复的机器学习建模。
摘要
在本章中,我们讨论了机器学习生命周期的不同组成部分,从数据收集和选择到模型训练和评估,最后到模型部署和监控。我们还展示了如何模块化机器学习生命周期的数据处理、建模和部署方面,有助于识别改进机器学习模型的机会。
在下一章中,你将了解关于改进机器学习模型性能之外的概念,例如无偏建模和公平性,以及为了实现负责任的 AI 系统而进行的问责制和透明度。
问题
-
你能提供两个数据清洗过程的例子吗?
-
你能解释一下 one-hot 编码和标签编码方法之间的区别吗?
-
你如何使用分布的分位数来检测其异常值?
-
当考虑到在医生本地部署模型与在银行系统中部署聊天机器人背后的模型之间的差异时,你脑海中浮现的是什么?
参考文献
-
Micci-Barreca, Daniele. 《用于分类和预测问题中高基数分类属性的前处理方案》。ACM SIGKDD Explorations Newsletter 3.1 (2001): 27-32。
-
Basu, Anirban. 《软件质量保证、测试和度量》,PRENTICE HALL,2015 年 1 月 1 日。
第三章:调试以实现负责任人工智能
开发成功的机器学习模型并不仅仅是关于实现高性能。当我们提高我们模型的表现时,我们都感到兴奋。我们觉得自己有责任开发一个高性能的模型。但我们也有责任构建公平和安全的模型。这些超越性能改进的目标,是负责任机器学习或更广泛地,负责任人工智能的目标之一。作为负责任机器学习建模的一部分,我们在训练和预测模型时应该考虑透明度和问责制,并考虑我们数据和建模过程的治理系统。
在本章中,我们将涵盖以下主题:
-
机器学习中的无偏建模公平性
-
机器学习中的安全和隐私
-
机器学习建模中的透明度
-
可问责和可检查的建模
-
数据和模型治理
到本章结束时,您将了解负责任机器学习建模中的需求和不同关注点以及挑战。您还将了解不同的技术,这些技术可以帮助我们在负责任建模的同时确保隐私和安全,在开发机器学习模型时。
技术要求
在阅读本章之前,您需要了解机器学习生命周期的组成部分,因为这将帮助您更好地理解这些概念,并能够在您的项目中使用它们。
机器学习中的无偏建模公平性
机器学习模型会犯错误。但是当错误发生时,它们可能存在偏见,例如在第一章中提供的 COMPAS 示例,超越代码调试。我们需要调查我们的模型是否存在这种偏见,并修改它们以消除这些偏见。让我们通过更多示例来阐明调查我们的数据和模型是否存在这种偏见的重要性。
招聘对每家公司来说都是一个具有挑战性的过程,因为他们必须从提交简历和求职信的数百名申请人中确定最合适的候选人进行面试。2014 年,亚马逊开始开发一个招聘工具,使用机器学习来筛选求职者,并根据他们在简历中提供的信息选择最佳候选人。这是一个文本处理模型,它使用简历中的文本来识别关键信息并选择顶级候选人。但最终,亚马逊决定放弃该系统,因为该模型在招聘过程中对男性比对女性有偏见。这种偏见背后的主要原因是数据,这些数据主要是男性简历,被输入到机器学习模型中。模型学会了如何识别男性简历中的语言和关键信息,但面对女性简历时并不有效。因此,该模型在保持性别无偏见的同时,无法对工作申请的候选人进行排名。
一些机器学习模型被设计用来预测住院的可能性。这些模型可以帮助降低个人和人群的医疗保健成本。然而,这样的有益模型也可能存在自己的偏差。例如,住院需要获得和使用医疗服务,这受到社会经济条件差异的影响。这意味着用于构建预测住院可能性的模型的数据集,与贫困家庭相比,将包含更多社会经济条件较高人群的正面数据。这种不平等可能导致机器学习模型在住院决策中的偏差,从而进一步限制社会经济条件较低人群获得住院的机会。
医疗保健环境中机器学习应用中偏差的另一个例子出现在遗传研究中。这些研究因未能妥善考虑人群的多样性而受到批评,这可能导致所研究疾病的误诊。
偏差的两个主要来源包括数据,这些数据可能源自数据源,也可能在模型训练之前的数据处理中引入,以及算法偏差。让我们来回顾一下这两者。
数据偏差
你可能已经听说过计算机科学中的“垃圾输入,垃圾输出”概念。这个概念是关于这样一个事实:如果无意义的数据进入计算机工具,例如机器学习模型,输出也将是无意义的。用于帮助训练机器学习算法的数据可能存在各种问题,最终导致偏差,正如之前提到的。例如,数据可能未能充分代表某个群体,类似于亚马逊模型中输入的招聘数据中的女性。回想一下,拥有这种偏差数据不应该阻止我们构建模型,但我们必须在设计生命周期组件时考虑到这些偏差,例如数据选择和整理或模型训练,并在将模型投入生产之前测试我们的模型以检测偏差。以下是一些数据偏差的来源。
数据收集偏差
收集的数据可能包含偏差,例如,在亚马逊申请人分类示例中存在的性别偏差,在 COMPAS 中存在的种族偏差,在医院化示例中存在的社会经济偏差,或其他类型的偏差。作为另一个例子,想象一个用于自动驾驶的机器学习模型仅训练于白天拍摄的街道、汽车、人和交通标志的图像。该模型在夜间将具有偏差且不可靠。这种偏差可以通过在机器学习生命周期中的数据探索或数据整理步骤提供反馈后从数据收集和选择中移除。但如果在模型训练、测试和部署之前没有修订,那么在检测到预测偏差时,需要立即从模型监控中提供反馈,并在生命周期中使用以提供更少偏差的数据进行建模。
样本偏差
数据偏差的另一个来源可能是生命周期中数据收集阶段的样本数据点或人群样本。例如,当抽样学生填写调查问卷时,我们的抽样过程可能会偏向于女孩或男孩、富裕或贫困的学生家庭,或者高年级或低年级学生。这类偏差仅通过添加其他群体的样本难以轻易纠正。填写调查问卷或为新药测试设计临床试验的抽样过程就是数据收集过程中添加数据并不一定被允许的例子。其中一些数据收集过程需要在过程中预先定义人群,且人群定义在过程中不能改变。在这种情况下,在设计数据抽样过程时需要确定和考虑不同类型的可能偏差。
排除偏差
在数据清洗和整理过程中,在开始训练和测试机器学习模型之前,可能由于统计推理(如数据点的低信息含量或方差,或没有期望的特征)而删除特征。这些特征删除有时会导致我们的建模偏差。尽管不是排除在外,一些特征也可能导致最终机器学习模型预测的偏差。
测量或标注偏差
测量和标注偏差可能由技术、专家或非专家数据标注员在生成或标注用于模型训练、测试和生产预测的数据时遇到的问题或差异引起。例如,如果使用一种类型的相机收集数据来训练用于图像分类的机器学习模型,那么在生产中如果使用另一种生成不同质量的图像的相机进行图像捕捉,生产中的预测可能可靠性较低。
算法偏差
与机器学习模型的算法和训练过程相关的系统错误可能是存在的。例如,在人脸识别工具中,数据可能被偏向于特定的种族或肤色,而算法可能会对具有特定肤色或种族的群体产生有偏见的预测。考虑到机器学习生命周期,在第二章中展示的模块化方式,机器学习生命周期将帮助你在模型监控等阶段识别问题。然后,可以为相关步骤提供反馈,例如数据收集或数据处理,以消除已识别的偏见。我们将在未来的章节中介绍检测偏见和解决偏见的方法。例如,我们可以使用机器学习可解释性技术来识别可能导致预测偏见的特征或其组合的贡献。
除了消除模型中的偏见外,在经历机器学习生命周期时,我们还需要考虑安全和隐私问题,这是我们接下来要讨论的主题。
机器学习中的安全和隐私
对于所有拥有物理或虚拟产品和服务的企业来说,安全都是一个关注点。60 年前,每家银行都必须确保其分支机构中现金和重要文件等实物资产的安全。但是,在进入数字世界后,他们必须建立新的安全系统,以确保其客户的数据、金钱和资产的安全,这些资产现在可以以数字方式传输和更改。机器学习产品和技术也不例外,需要拥有适当的安全系统。机器学习环境中的安全担忧可能与数据的安全、模型本身或模型预测有关。在本节中,我们将介绍与机器学习建模中的安全和隐私相关的三个重要主题:数据隐私、数据中毒和对抗攻击。
数据隐私
在生产中用户数据的隐私性,或者您用于模型训练和测试存储和使用的数据库是机器学习技术安全系统设计的重要方面。出于许多原因,数据需要保持安全:
-
如果数据包含从用户、个人或组织接收的机密信息
-
如果数据是根据法律合同从商业数据提供商那里许可的,并且不应通过您的服务或技术对他人可访问
-
如果数据是为您生成的,并被认为是您团队和组织的资产之一
在所有这些情况下,您需要确保数据的安全。您可以使用数据库和数据集的安全系统。如果部分数据需要在两个服务器之间以数字方式传输,您还可以在此之上设计加密过程。
数据隐私攻击
一些攻击旨在访问您数据集和数据库中的私有和机密数据,例如医院中的患者信息、银行系统中的客户数据或政府机构员工的个人信息。其中三种攻击是数据重建攻击、身份识别攻击和个人追踪攻击,所有这些都可以通过互联网协议(IP)跟踪来实现,例如。
数据中毒
数据的意义和质量的变化是数据安全中的另一个担忧。数据可能会被中毒,预测结果的变化可能对个人、团队和组织在财务、法律和道德方面产生严重影响。想象一下,你和你的朋友们设计了一个用于股票市场预测的机器学习模型,并且你的模型使用过去几天的新闻和股票价格作为输入特征。这些数据是从雅虎财经和不同的新闻来源等不同资源中提取的。如果你的数据库被中毒,通过改变某些特征值或收集的数据的变化,例如某只股票的价格历史,你可能会遭受严重的经济损失,因为你的模型可能会建议你购买一周内价值下降超过 50%的股票,而不是上涨。这是一个有财务后果的例子。然而,如果发生在医疗或军事系统中,数据中毒可能会产生致命的后果。
对抗性攻击
有时,通过在特征值上做出非常简单的改变,例如添加少量的噪声或扰动,你可以欺骗机器学习模型。这是生成对抗样本和对抗攻击背后的概念。
例如,在一个医疗人工智能系统中,一个良性(即,无害)的痣的图像可能会被诊断为恶性(即,在一般意义上有害和危险的)通过在图像中添加人眼无法识别的对立噪声或简单地旋转图像。将“患者有背部疼痛和慢性酒精滥用史,最近还出现过几次...”替换为“患者有腰痛和慢性酒精依赖史,最近还出现过几次...”这样的同义文本替换可能会将诊断从良性变为恶性(Finlayson 等人,2019 年)。在其他图像分类的应用中,例如在自动驾驶汽车中,简单的黑白贴纸有时会欺骗模型将停车标志的图像或停车标志视频帧分类(Eykholt 等人,2018 年)。
对抗性样本可能会在推理或训练过程中误导你的系统,并验证它们是否被注入到你的建模数据中并使其中毒。了解你的对手有三个重要方面可以帮助你保护你的系统——即攻击者的目标、知识和能力(表 3.1):
| 关于对手的知识类型 | 不同类型知识的方面 | 定义 |
|---|
| 攻击者的目标 | 安全违规 | 攻击者试图做以下事情:
-
避免检测
-
破坏系统功能
-
获取访问私有信息
|
| 攻击特异性 | 针对特定或随机数据点以生成错误结果 | |
|---|---|---|
| 攻击者的知识 | 完美知识白盒攻击 | 攻击者了解系统的一切 |
| 零知识黑盒攻击 | 攻击者对系统本身没有任何了解,但通过生产中模型的预测收集信息 | |
| 有限知识灰盒攻击 | 攻击者有有限的知识 | |
| 攻击者的能力 | 攻击影响 | 因果性:攻击者可以毒害训练数据并操纵测试数据探索性:攻击者只能操纵测试数据 |
| 数据操纵约束 | 对数据操纵的约束,以消除数据操纵或使其具有挑战性 |
表 3.1 – 关于对手的知识类型(Biggio 等人,2018)
输出完整性攻击
这种攻击通常不会影响数据处理、模型训练和测试,甚至不会影响生产中的预测。它出现在你的模型输出和展示给用户的内容之间。根据这个定义,这种攻击并不特定于机器学习环境。但在我们的机器学习系统中,仅基于展示给用户的输出理解这种攻击可能具有挑战性。例如,如果你的模型在分类设置中的预测概率或标签偶尔发生变化,展示给用户的结果将是错误的,但如果用户相信我们的系统,他们可能会接受这些结果。确保这类攻击不会挑战我们模型在生产中的结果完整性是我们的责任。
系统操纵
你的机器学习系统可能被故意设计的合成数据操纵,这些数据可能不存在,或者可能从未存在于模型训练和测试集中。这种在预测层面的操纵不仅可能导致调查模型错误预测的时间浪费,还可能毒害你的模型,并改变你的模型在测试和生产中的性能,如果数据进入你的训练、评估或测试数据。
安全和隐私机器学习技术
一些技术帮助我们开发用于数据存储、传输以及在机器学习建模中使用的数据的安全和隐私保护流程和工具:
-
匿名化:这项技术侧重于删除有助于识别个人数据点(如医疗数据集中的单个患者)的信息。这些信息可能非常具体,例如健康卡号码,在不同国家可能有不同的名称,或者更一般的信息,如性别和年龄。
-
脱敏:与匿名化不同,脱敏不是删除信息,而是将可识别的个人数据替换为合成替代品作为脱敏的一部分。
-
数据和算法加密:加密过程将信息(无论是数据还是算法)转换成新的(加密)形式。如果个人有权访问加密密钥(即用于解密过程的密码式密钥),则加密数据可以被解密(使其成为人类可读或机器可理解)。这样,在没有加密密钥的情况下获取数据和算法将几乎不可能或非常困难。我们将在第十六章“机器学习中的安全和隐私”中回顾加密技术,如高级加密标准(AES)第十六章。
-
同态加密:这是一种加密技术,通过机器学习模型进行预测时无需对数据进行解密。模型使用加密数据进行预测,因此数据可以在机器学习管道中的整个数据传输和使用过程中保持加密状态。
-
联邦机器学习:联邦机器学习依赖于将学习、数据分析、推理去中心化的想法,从而允许用户数据保持在单个设备或本地数据库中。
-
差分隐私:差分隐私试图确保删除或添加单个数据点不会影响建模的结果。它试图从数据点的组内模式中学习。例如,通过添加来自正态分布的随机噪声,它试图使单个数据点的特征变得模糊。如果可以访问大量数据点,基于大数定律(
www.britannica.com/science/law-of-large-numbers),可以消除学习中的噪声影响。
这些技术并不适用于所有环境,也不一定有用。例如,当你有一个内部数据库并且只需要确保其安全性时,联邦机器学习将不会有所帮助。对于小型数据源,差分隐私也可能不可靠。
加密和解密过程
加密是将可读数据转换为人类不可读形式的过程。另一方面,解密是将加密数据转换回其原始可读格式的过程。您可以在docs.oracle.com/和learn.microsoft.com/en-ca/找到更多关于这个主题的信息。
在本节中,我们讨论了机器学习建模中的隐私和安全问题。即使我们构建了一个具有最小隐私担忧的安全系统,我们也需要考虑其他因素来建立对我们模型的信任。透明度就是这些因素之一。我们将在下一节介绍这一点。
机器学习建模中的透明度
透明度通过帮助用户理解模型的工作原理和构建方式,使用户信任您的模型。它还帮助您、您的团队、您的合作者和您的组织收集关于机器学习生命周期不同组件的反馈。了解生命周期不同阶段的透明度要求以及实现它们的挑战是值得的:
-
数据收集:数据收集的透明度需要回答两个主要问题:
-
你在收集哪些数据?
-
你想用这些数据做什么?
-
例如,当用户在注册手机应用时点击数据使用协议按钮,他们是在同意将他们在应用中提供的信息用于。但协议需要明确说明将要使用哪些用户数据以及用于什么目的。
-
数据选择和探索:在这些生命周期阶段,您数据选择的过程以及您如何实现探索性结果需要清晰。这有助于您从其他项目合作者和同事那里收集反馈。
-
数据整理和建模数据准备:在此步骤之前,数据几乎就像所谓的原始数据,没有对特征定义进行任何更改,也没有将数据分成训练集和测试集。如果您将这些生命周期组件设计成一个黑盒且不透明,您可能会失去其他专家在将来访问您的数据和结果时的信任和反馈机会。例如,想象一下,您本不应该使用医院的患者的遗传信息,而在生命周期这些步骤之后,您提供了称为 Feature1、Feature2 等特征。如果没有解释这些特征是如何生成的以及使用了哪些原始特征,人们就不能确定您是否使用了患者的遗传信息。您还需要对您如何设计测试策略以及如何将训练数据与验证和测试数据分开进行透明度说明。
-
模型训练和评估:模型训练的透明度有助于在从数据中学习时理解模型的决策和模式识别方面。训练和评估的透明度有助于直接用户、开发人员和审计员更好地评估这些过程。例如,确实有超过 99%的亚马逊 Prime 用户不想了解背后的机器学习建模。然而,我们的模型有时会被用户直接使用,例如医院中的医生进行诊断或制造设施中的员工。我们将讨论的第六章,“机器学习建模中的可解释性和可理解性”,是机器学习建模中的一个重要概念,它与透明度密切相关,并有助于用户更好地理解模型的工作原理。
-
测试:在生命周期测试阶段的决策透明度有助于更好地理解选择或拒绝模型的过程,或规划改进和调试相关代码、数据处理、或模型训练和评估。
-
模型部署和监控:除了从组织或团队中的其他开发人员和专家,或公众专家那里获取反馈外,您还需要让用户和审计员(无论在组织内部还是外部)清楚模型在生产中的效果以及收集用于监控的数据。
尽管需要透明度,但透明度的增加可能会从安全和隐私的角度使您的系统变得脆弱。因此,您不能在不考虑隐私和安全的情况下追求最大程度的透明度。
公平性、偏差消除、隐私和透明度都是负责任机器学习建模的因素。但我们需要记住,实现公平和安全模型及系统的责任在我们身上。
负责任且开放接受检查的建模
我们作为不同企业或公司、研究机构或大学,或自由职业者的员工开发的模型,最终可能会应用于不同的应用,如医疗保健、金融、制造、营销、零售、交通、媒体和娱乐。我们的模型可以使用患者数据来预测他们是否会患上癌症或糖尿病,或者他们是否会响应治疗。或者,它们可以使用银行客户的财务历史和其他信息来评估他们获得贷款的资格。另一个例子是,我们的模型可以使用人们购买的历史记录向他们推荐新产品。
正如我们在本章中讨论的,我们必须注意数据的隐私和模型的隐私,提供一个公平无私的模型,并尽可能使我们的模型透明。但我们必须记住,我们在开发可靠和负责任的机器学习模型时,必须对这些所有问题负责。我向著名的机器学习聊天机器人 ChatGPT 提问,“你对你的回应负责吗?”它的回答如下:
“作为一个机器学习模型,我没有能力像人类一样对我的回应承担责任。我被设计成根据接收到的输入生成回应,但我没有个人信念、意图和动机。我无法独立做出决策或选择。我的回应是我被创建时使用的训练数据和算法的反映,我的创造者负责确保我按预期运行。”
注意
本章接下来的几段引用了法律来源的引文,这些引文未作改动,以确保法律内容不会被不恰当地改写。然而,这不是一本法律书籍,本书的任何内容都不应被视为法律建议。
但问责制不是可选择的。在过去的几年中,已经出台了立法和法规,以使机器学习模型和产品的开发者和所有者对我们本章中讨论的担忧负责。例如,欧盟的(EU 的)通用数据保护条例(GDPR)列出了正在处理个人数据的个人的权利,以赋予他们对其数据的控制权。它是通过以下方面实现的:
-
需要个人明确同意处理其数据
-
数据主体更容易访问其数据
-
纠正权、删除权和被遗忘权
-
包括对个人数据进行画像在内的反对权
-
从一个服务提供商到另一个服务提供商的数据可移植权
欧盟还建立了一个司法救济和赔偿系统(来源:www.consilium.europa.eu/en/policies/data-protection/)。
欧盟后来制定了人工智能(AI)法案,这是主要监管机构制定的第一部人工智能法律(来源:artificialintelligenceact.eu/)。
但这些法规不仅限于欧盟。例如,白宫科学和技术政策办公室发布了以下人工智能权利法案蓝图,以保护人工智能时代的美国公众(来源:www.whitehouse.gov/ostp/ai-bill-of-rights/)。
加拿大后来还提出了 C-27 人工智能法案,该法案“通过一系列主要罪行建立其基本义务,保护公民免受错误的人工智能侵害,并要求对数据使用进行普遍记录的义务”(来源:www.lexology.com/library/detail.aspx?g=4b960447-6a94-47d1-94e0-db35c72b4624)。
本章我们要讨论的最后一个主题是机器学习建模中的治理。在下一节中,你将了解治理如何帮助你和你所在的组织开发机器学习模型。
数据和模型治理
机器学习建模中的治理是关于使用工具和程序来帮助你、你的团队和你的组织开发可靠和负责任的机器学习模型。你不应该将其视为对如何进行项目的任何限制,而应将其视为减少未检测错误风险的机会。机器学习中的治理应该设计成帮助你和你所在的组织实现帮助人类和商业的目标,避免可能产生道德、法律或财务后果的过程和模型。以下是一些在团队和组织中建立治理体系的方法示例:
-
定义指南和协议:鉴于我们希望检测模型中的问题,并在性能和责任方面改进模型,我们需要设计用于简化和一致性的指南和协议。我们需要定义被认为是模型问题的标准和方法,例如从安全角度考虑,以及被认为是值得花费时间和精力进行模型改进的机会。我们需要记住,考虑到本章讨论的主题和生命周期中的不同步骤,机器学习建模并非易事,你不应该期望与你合作的每个开发者都像专家一样了解所有这些。
-
培训和指导:如果你是管理者,你需要寻找导师和培训项目,阅读书籍和文章,然后为你的团队提供这些机会。但你也需要将你或你的团队学到的知识应用到实践中。机器学习建模中的每个概念都有其挑战。例如,如果你决定使用防御机制来对抗对抗性攻击,这并不像加载一个 Python 库并希望永远都不会发生任何事情那样简单。所以,实践你所学的,并为你的团队提供将所学知识应用到实践中的机会。
-
定义责任和问责制:照顾机器学习生命周期的所有方面以构建技术和处理本章中讨论的所有责任主题,并不是一个人的工作。话虽如此,团队和组织中个人的责任和问责制需要明确界定,以减少工作冗余并确保没有遗漏任何事项。
-
使用反馈收集系统:我们需要设计简单易用且最好是自动化的系统来收集反馈并在机器学习生命周期中采取行动。这种反馈将帮助负责生命周期每个步骤的开发者,并最终导致在生产中推出更好的模型。
-
使用质量控制流程:我们需要定量和预定义的方法和协议来评估训练后或生产中的机器学习模型的质量,或者评估机器学习生命周期每个阶段输出的处理数据。拥有定义和记录的质量控制流程有助于我们实现可扩展的系统,以便更快、更一致地进行质量评估。然而,这些流程可以根据新的标准和与数据和相应的机器学习模型相关的风险进行修订和调整。
现在我们已经了解了负责任机器学习建模的重要性,并回顾了实现它的关键因素和技术,我们准备进入本书的下一部分,并深入了解开发可靠、高性能和公平的机器学习模型和技术的技术细节。
摘要
在本章中,我们讨论了负责任人工智能的不同要素,例如数据隐私、机器学习系统的安全性、不同类型的攻击以及设计针对它们的防御系统、机器学习时代的透明度和问责制,以及如何在实际中利用数据和管理模型来开发可靠和负责任的模式。
本章和前两章,构成了本书的第一部分,介绍了机器学习建模和模型调试的重要概念。第二部分包括如何改进机器学习模型的主题。
在下一章中,你将了解检测机器学习模型问题以及提高此类模型性能和泛化性的方法。我们将涵盖使用真实生活示例进行模型调试的统计、数学和可视化技术,以帮助您快速实施这些方法,以便您可以调查和改进您的模型。
问题
-
你能解释两种类型的数据偏差吗?
-
白盒攻击和黑盒攻击有什么区别?
-
你能解释一下数据与算法加密如何帮助保护你系统的隐私和安全吗?
-
你能解释差分隐私和联邦机器学习之间的区别吗?
-
透明度如何帮助你在增加你的机器学习模型用户数量方面?
参考文献
-
Zou, James, 和 Londa Schiebinger. AI 可能是性别歧视和种族歧视的 – 是时候让它变得公平了。(2018):324-326.
-
Nushi, Besmira, Ece Kamar, 和 Eric Horvitz. 迈向可问责的 AI:用于表征系统失败的混合人机分析. AAAI 人机计算与众包会议论文集。第 6 卷。2018.
-
Busuioc, Madalina. 可问责的人工智能:对算法负责. 公共行政评论 81.5 (2021): 825-836.
-
Unceta, Irene, Jordi Nin, 和 Oriol Pujol. 算法问责制中的风险缓解:机器学习副本在其中的作用. Plos one 15.11 (2020): e0241286.
-
Leonelli, Sabina. 数据治理是关键:重新概念化数据在数据科学中的角色. 哈佛数据科学评论 1.1 (2019): 10-1162.
-
Sridhar, Vinay, 等人. 模型治理:减少生产 {ML} 的无政府状态. 2018 USENIX 年度技术会议 (USENIX ATC 18)。2018.
-
Stilgoe, Jack. 机器学习、社会学习和自动驾驶汽车的治理. 科学研究 48.1 (2018): 25-56.
-
Reddy, Sandeep, 等人. AI 在医疗保健中的应用治理模型. 美国医学信息学协会杂志 27.3 (2020): 491-497.
-
Gervasi, Stephanie S., 等人. 机器学习中的潜在偏见及其对健康保险公司的应对机会:文章探讨了机器学习中的潜在偏见以及健康保险公司应对它的机会. 健康事务 41.2 (2022): 212-218.
-
Gianfrancesco, M. A., Tamang, S., Yazdany, J., & Schmajuk, G. (2018). 使用电子健康记录数据的机器学习算法中的潜在偏见. JAMA 内科学杂志,178(11),1544.
-
Finlayson, Samuel G., 等人. 对抗医疗机器学习攻击. 科学 363.6433 (2019): 1287-1289.
-
Eykholt, Kevin, 等人. 对深度学习视觉分类的鲁棒物理世界攻击. IEEE 计算机视觉与模式识别会议论文集。2018.
-
Biggio, Battista, 和 Fabio Roli. 野模式:对抗机器学习兴起十年后. 模式识别 84 (2018): 317-331.
-
Kaissis, Georgios A., 等人. 医学成像中的安全、隐私保护和联邦机器学习. 自然机器智能 2.6 (2020): 305-311.
-
Acar, Abbas, 等人. 同态加密方案综述:理论与实现. ACM 计算机调查 (Csur) 51.4 (2018): 1-35.
-
Dwork, Cynthia. 差分隐私:结果综述. 国际计算模型理论与应用会议。Springer,柏林,海德堡,2008.
-
Abadi, Martin, 等人. 具有差分隐私的深度学习. 2016 ACM SIGSAC 计算机与通信安全会议论文集。2016.
-
杨强,等。联邦机器学习:概念与应用。ACM 智能系统与技术交易(TIST)10.2(2019):1-19。
第二部分:提高机器学习模型
这一部分将帮助我们过渡到精炼和理解机器学习模型的关键方面。我们将从深入探讨检测模型中的性能和效率瓶颈开始,接着提出可操作的战略来提升它们的性能。随后,叙述转向可解释性和可说明性的主题,阐明不仅构建出能工作的模型,而且我们能够理解和信任的模型的重要性。我们将通过介绍减少偏差的方法来结束这一部分,强调机器学习中公平性的必要性。
本部分包含以下章节:
-
第四章, 检测机器学习模型中的性能和效率问题
-
第五章, 提高机器学习模型的性能
-
第六章, 机器学习建模中的可解释性和可说明性
-
第七章, 减少偏差和实现公平性
第四章:检测机器学习模型中的性能和效率问题
我们必须牢记的主要目标之一是,如何在新的数据上构建一个高性能的机器学习模型,这些数据是我们希望使用模型的情况。在本章中,你将学习如何正确评估你的模型性能,并识别减少它们错误的机会。
本章包含许多图表和代码示例,以帮助你更好地理解这些概念,并在你的项目中开始从中受益。
我们将涵盖以下主题:
-
性能和错误评估措施
-
可视化
-
偏差和方差诊断
-
模型验证策略
-
错误分析
-
除此之外
到本章结束时,你将了解如何评估机器学习模型的性能,以及可视化在不同机器学习问题中的好处、局限性和错误使用。你还将了解偏差和方差诊断以及错误分析,以帮助你识别机会,以便你可以改进你的模型。
技术要求
在本章中,以下要求应予以考虑,因为它们将帮助你更好地理解概念,在你的项目中使用它们,并使用提供的代码进行实践:
-
Python 库要求:
-
sklearn>= 1.2.2 -
numpy>= 1.22.4 -
pandas>= 1.4.4 -
matplotlib>= 3.5.3 -
collections>= 3.8.16 -
xgboost>= 1.7.5
-
-
你应该具备模型验证和测试的基本知识,以及机器学习中的分类、回归和聚类
你可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter04。
性能和错误评估措施
我们用来评估模型性能和计算错误的指标,以及我们如何解释它们的值,决定了我们选择的模型,我们做出的改进机器学习生命周期组件的决定,以及我们是否有一个可靠的生产模型。尽管许多性能指标可以用一行 Python 代码来计算错误和性能,但我们不应该盲目使用它们,或者试图通过实现许多指标来提高我们的性能报告,而不了解它们的限制和如何正确解释它们。在本节中,我们将讨论用于评估分类、回归和聚类模型性能的指标。
分类
每个分类模型,无论是二分类还是多分类,都会返回预测的概率,一个介于 0 和 1 之间的数字,然后将其转换为类别标签。性能指标主要有两大类:基于标签的性能指标,它依赖于预测标签,以及基于概率的性能指标,它使用预测的概率进行性能或错误计算。
基于标签的性能指标
分类模型的预测概率通过我们用于建模的 Python 类转换为类别标签。然后,我们可以使用如图 图 4.1 所示的混淆矩阵来识别四组数据点,包括真正例(TPs)、假正例(FPs)、假反例(FNs)和真反例(TNs)对于二分类问题:
图 4.1 – 二分类的混淆矩阵
我们可以使用 sklearn.metrics.confusion_matrix() 提取这四组数据点,然后根据以下数学定义计算性能指标,如特异性:
下面是使用混淆矩阵提取特异性、精确率和召回率的 Python 实现:
from sklearn.metrics import confusion_matrix as cmdef performance_from_cm(y_true, y_pred):
# Calculating values of confusion matrix
cm_values = cm(y_true, y_pred)
# Extracting tn, fp, fn, and tp from calculated confusion matrix
tn, fp, fn, tp = cm_values.ravel()
# Calculating specificity
specificity = tn/(tn+fp)
# Calculating precision
precision = tp/(tp+fp)
# Calculating recall
recall = tp/(tp+fn)
return specificity, precision, recall
我们可以使用从混淆矩阵中提取的 TP、TN、FP 和 FN 计算其他性能指标,如精确率和召回率,或者直接使用 Python 中可用的函数(表 4.1)。除了用于计算分类模型的一些常见性能指标的 Python 函数外,您还可以在 表 4.1 中找到指标的数学定义及其解释。这些额外信息将帮助您理解如何解释这些指标以及何时使用它们:
| 指标 | Python 函数 | 公式 | 描述 |
|---|---|---|---|
| 准确率 | metrics.accuracy_score() | TP + TN _ n n: 数据点数量 | 在总数据点中正确预测的数量范围:[0, 1]值越高表示性能越好 |
| 精确率或阳性预测值(PPV) | metrics.precision_score() | TP _ TP + FP | 预测为阳性的预测中实际为阳性的比例范围:[0, 1]值越高表示性能越好 |
| 召回率、灵敏度或真正例率(TPR) | metrics.recall_score() | TP _ TP + FN | 被预测为阳性的正例的比例范围:[0, 1]值越高表示性能越好 |
| F1 分数及其衍生指标 | metrics.f1_score() | 精确率 * 召回率 ____________ 精确率 + 召回率 _ 2 | 精确率和召回率的调和平均值范围:[0, 1]值越高表示性能越好 |
| 平衡准确率 | metrics.balanced_accuracy_score() | 召回率 + 特异性 _____________ 2 | 真正预测的正负比例的平均值范围:[0, 1]值越高表示性能越好 |
| 马修斯相关系数 系数(MCC) | sklearn.metrics.matthews_corrcoef() | TP * TN − FP * FN ______________________________ √ __________________________________ (TP + FP)(FP + TN)(TN + FN)(FN + TP) | 分子旨在最大化混淆矩阵的对角线元素并最小化非对角线元素范围:[ − 1, 1]值越高表示性能越好 |
表 4.1 – 评估分类模型性能的常用指标
选择性能指标用于模型选择和报告的一个方面是它们与目标问题的相关性。例如,如果你正在构建一个用于癌症检测的模型,你可以通过最大化识别所有正类成员(即癌症患者)来最大化召回率,同时控制精确度。这种策略可以帮助你确保患有癌症的患者不会因致命疾病而未得到诊断,尽管同时拥有高精确度和召回率的模型会更理想。
选择性能指标取决于我们是否关心所有类别的真实预测具有相同的重要性水平,或者是否有一个或多个类别更为重要。有一些算法方法可以强制模型更加关注一个或多个类别。此外,在报告性能和模型选择时,我们需要考虑类别之间的这种不平衡,而不仅仅依赖于总结所有类别预测性能的等权重性能指标。
我们还必须注意,在二元分类的情况下,我们需要定义正类和负类。我们生成或收集的数据通常没有这样的标签。例如,你的数据集可能有“欺诈”与“非欺诈”、“癌症”与“健康”,或者字符串中的数字名称,如“一”、“二”和“三”。因此,如果有我们更关心或更少关心的一个或多个类别,我们需要根据我们对类别的定义来选择性能指标。
选择性能指标的其他方面是它们的可靠性,如果它们有依赖于数据的偏差,我们就会在训练、验证或测试中使用它们。例如,准确率,作为分类模型广泛使用的性能指标之一,不应在不平衡的数据集上使用。准确率定义为正确预测的总数除以数据点的总数(表 4.1)。因此,如果一个模型将所有数据点预测为多数类,即使它可能不是一个好的模型,它也会返回一个高值。图 4.2显示了不同性能指标,包括准确率,对于一个将所有数据点预测为负数的模型的值。如果数据集中有 80%的数据点是负数,那么这个糟糕模型的准确率是 0.8(图 4.2)。然而,平衡准确率或马修斯相关系数(MCC)等替代性能指标在具有不同正数据点分数的数据集上对这样一个糟糕的模型来说保持不变。数据平衡只是选择分类模型性能指标时考虑的参数之一,尽管它很重要。
一些性能指标具有更好的行为,适用于不平衡数据分类等情境。例如,F1 是一个广泛使用的指标,但在处理不平衡数据分类时并不是最佳选择(图 4.2):
图 4.2 – 对于一个将所有预测返回为负数的模型,在不同真实正分数下的常见分类指标值
然而,它有一个通用的形式 Fβ,其中参数β用作根据其数学定义增加精度的效果的权重。你可以使用sklearn.metrics.fbeta_score()函数来计算这个指标,使用数据点的真实标签和预测标签:
基于概率的性能指标
分类模型的概率输出可以直接用来评估模型性能,无需将预测标签进行转换。这种性能度量的一种例子是逻辑损失,也称为对数损失或交叉熵损失,它使用每个数据点的预测概率及其真实标签来计算数据集上的总损失,如下所示。对数损失也是一个用于训练分类模型的损失函数:
L log(y, p) = − (ylog(p) + (1 − y)log(1 − p))
还有其他基于概率的性能评估方法,如接收者操作特征(ROC)曲线和精确率召回率(PR)曲线,它们考虑了将概率转换为标签的不同截止点,以预测真正例率、假正例率、精确率和召回率。然后,这些值在不同截止点被用来生成 ROC 和 PR 曲线(图 4.3):
图 4.3 – ROC 和 PR 曲线的示意图
使用这些曲线下的面积,称为 ROC-AUC 和 PR-AUC,来评估分类模型的性能是很常见的。ROC-AUC 和 PR-AUC 的范围从 0 到 1,其中 1 表示完美模型的性能。
在图 4.2中,你看到了一些性能指标如何为预测所有数据点为负的坏模型返回高性能值,这是由于数据不平衡。我们可以在图 4.4中看到这种分析的扩展,它展示了真正例标签和预测标签中不同正数据点的比例。这里没有训练,数据点是随机生成的,以在图 4.4的每个面板中产生指定的正数据点比例。然后,随机生成的概率被转换为标签,以便可以使用不同的性能指标与真正例进行比较。
图 4.4和图 4.5显示了分类模型性能指标中的不同偏差。例如,随机预测的中位精确率等于真正例数据点的比例,而随机预测的中位召回率等于预测标签中正标签的比例。你还可以检查图 4.4和图 4.5中其他性能指标在不同真正例或预测正例比例下的行为:
图 4.4 – 1,000 个随机二元预测在 1,000 个数据点上的性能分布(第一部分)
图 4.5 – 1,000 个随机二元预测在 1,000 个数据点上的性能分布(第二部分)
ROC-AUC 和 PR-AUC 的组合,或使用 MCC 或平衡准确率,是降低分类模型性能评估偏差的常见方法。但如果你知道你的目标,例如如果你更关心精确率而不是召回率,那么你可以选择添加决策所需必要信息的性能指标。但避免仅仅为了计数模型中哪些性能指标更好而报告 10 个性能指标。
回归
您可以使用评估模型连续预测值与真实值之间差异的度量,例如均方根误差(RMSE),或者评估预测值与真实值之间一致性的度量,如决定系数 R²(表 4.2)。每个回归模型性能评估的度量都有其假设、解释和局限性。例如,R² 不考虑数据维度(即特征、输入或独立变量的数量)。因此,如果您有一个具有多个特征的回归模型,您应该使用调整后的 R² 而不是 R²。通过添加新特征,R² 可能会增加,但并不一定代表更好的模型。然而,当新输入通过偶然机会比预期更好地提高模型性能时,调整后的 R² 会增加。这是一个重要的考虑因素,尤其是如果您想比较具有不同输入数量的样本问题的模型:
| 度量 | Python 函数 | 公式 | 描述 | ||
|---|---|---|---|---|---|
| 均方根误差(RMSE)均方误差(MSE) | sklearn.metrics.mean_squared_error() | MSE = 1/n ∑(i=1 to n) (y_i - ˆy_i)², RMSE = √(MSE/n) n: 数据点数量 y_i: 数据点的真实值ˆy_i: 数据点的预测值 | 范围:[0, ∞),数值越低表示性能越高 | ||
| 平均绝对误差(MAE) | sklearn.metrics.mean_absolute_error() | MAE = 1/n ∑(i=1 to n) | y_i - ˆy_i | 范围:[0, ∞),数值越低表示性能越高 | |
| 决定系数(R²) | sklearn.metrics.r2_score() | R² = 1 - ∑(i=1 to n) (y_i - ˆy_i)² / ∑(i=1 to n) (y_i - y_)² ; y_ = 1/n ∑(i=1 to n) y_i y_: 真实值的平均值 n: 数据点数量 y_i: 数据点的真实值ˆy_i: 数据点的预测值 | 范围:[0, 1],数值越高表示性能越高,表示独立变量可以解释的因变量的比例 | ||
| 调整后的 R² | 使用 sklearn.metrics.r2_score() 计算原始 R²,然后使用其公式计算调整后的版本。 | Adj R² = 1 - (1 - R²)(n - 1) / (n - m - 1) n: 数据点数量 m: 特征数量 | 调整以适应特征数量,如果 m 接近 n,则可能大于 1 或小于 0。数值越高表示性能越高 |
表 4.2 – 评估回归模型性能的常见度量
相关系数也用于报告回归模型的性能。相关系数使用预测值和真实连续值,或这些值的变换,并报告介于 -1 和 1 之间的值,其中 1 表示理想的预测,即 100% 的一致性,-1 表示完全的不一致性(表 4.3)。相关系数也有其自身的假设,不能随机选择用于报告回归模型的性能。例如,Pearson 相关系数是一种参数化测试,假设预测值和真实连续值之间存在线性关系,这并不总是成立。另一方面,Spearman 和 Kendall 排序相关系数是非参数化的,没有变量关系或每个变量的分布背后的假设。Spearman 和 Kendall 排序相关系数都依赖于预测值和真实输出的排名,而不是它们的实际值:
| 相关系数 | Python 函数 | 公式 | 描述 |
|---|---|---|---|
| Pearson 相关系数或 Pearson 的 r | scipy.stats.pearsonr() | r = ∑ i=1 n (ˆy_i − ˆy_) (y_i − y_) __________________ √ ___________________ ∑ i=1 n (ˆy_i − ˆy_)² (y_i − y_)² n: 数据点数量 y_i: 数据点的真实值 iy_: 真实值的平均值 ˆy_i: 数据点的预测值 i ˆy_: 预测值的平均值 | 参数化 寻找预测值和真实值之间的线性关系 范围:[ − 1, 1] |
| Spearman 排序相关系数或 Spearman 相关系数 | scipy.stats.spearmanr() | ρ = 1 − 6∑ i=1 n d_i² ___________________ n(n² − 1) n: 数据点数量 d_i: 真实值和预测值中数据点 i 排名的差异 | 非参数化 寻找预测值和真实值之间的单调关系 范围:[ − 1, 1] |
| Kendall 排序相关系数或 Kendall 的 τ 系数 | scipy.stats.kendalltau() | τ = C − D ____________________ √ _____________________ (C + D + T)(C + D + c) C: 一致对数(例如,y_i > y_j 且 ˆy_i > ˆy_j;或 y_i < y_j 且 ˆy_i < ˆy_j)D: 不一致对数(例如,y_i > y_j 且 ˆy_i < ˆy_j;或 y_i < y_j 且 ˆy_i > ˆy_j)T: 仅在预测值中存在相同排名的情况 U: 仅在真实值中存在相同排名的情况 | 非参数化 寻找预测值和真实值之间的单调关系 范围:[ − 1, 1] |
表 4.3 – 评估回归模型性能的常用相关系数
聚类
聚类是一种无监督学习方法,用于通过数据点的特征值来识别数据点的分组。然而,为了评估聚类模型的性能,我们需要有一个数据集或具有可用真实标签的示例数据点。在监督学习中,我们不使用这些标签来训练聚类模型;相反,我们使用它们来评估相似数据点被分组以及与不相似数据点分离的程度。你可以在表 4.4中找到一些用于评估聚类模型性能的常见指标。这些指标不会告诉你聚类的质量。例如,同质性告诉你聚在一起的数据点是否彼此相似,而完整性告诉你数据集中相似的数据点是否被聚在一起。还有一些指标,如 V 度量、调整后的互信息,试图同时评估这两个质量:
| 指标 | Python 函数 | 公式 | 描述 | |||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 同质性 | sklearn.metrics.homogeneity_score() | 来自 Rosenberg 等人,EMNLP-CoNLL 2007 提供的公式(1) | 衡量同一聚类内的数据点之间有多少是彼此相似的范围:[0, 1]值越高表示性能越好 | |||||||||||||
| 完整性 | sklearn.metrics.completeness_score() | 来自 Rosenberg 等人,EMNLP-CoNLL 2007 提供的公式(2) | 衡量聚在一起的数据点之间的相似程度范围:[0, 1]值越高表示性能越好 | |||||||||||||
| V 度量或归一化互信息得分 | sklearn.metrics.v_measure_score() | v = (1 + β) × h × c / [(β × h + c) h: Homogeneity c: Completeness β: 同质性与完整性所赋予的权重比率] | 同时衡量同质性和完整性范围:[0, 1]值越高表示性能越好 | |||||||||||||
| 互信息 | sklearn.metrics.mutual_info_score() | MI(U, V) = ∑ i=1 | U | ∑ j=1 | V | U_i ∩ V_j | _ N log | U_i ∩ V_j | _ | U_i | V_j | 范围:[0, 1]值越高表示性能越好 | ||||
| 调整后的互信息 | sklearn.metrics.adjusted_mutual_info_score() | AMI(U, V)= [MI(U, V) − E(MI(U, V))] / [avg(H(U), H(V)) − E(MI(U, V))] | 范围:[0, 1]值越高表示性能越好 |
表 4.4 – 评估聚类模型性能的常见指标
在本节中,我们讨论了用于评估机器学习模型性能的不同性能度量。但还有其他重要的性能评估方面需要考虑,例如数据可视化,我们将在下一节讨论。
用于性能评估的可视化
可视化是一个重要的工具,它不仅帮助我们理解建模数据的特点,还能更好地评估我们模型的性能。可视化可以为上述模型性能指标提供补充信息。
汇总指标是不够的
有一些汇总统计量,如 ROC-AUC 和 PR-AUC,提供了对应曲线的一个数值总结,用于评估分类模型的性能。尽管这些汇总比许多其他指标(如准确率)更可靠,但它们并不能完全捕捉其对应曲线的特征。例如,具有不同 ROC 曲线的两个不同模型可以具有相同的或非常接近的 ROC-AUC 值(图 4*.6*):
图 4.6 – 比较具有相同 ROC-AUC 值和不同 ROC 曲线的两个任意模型
仅比较 ROC-AUC 值可能会导致判断这些模型的等效性。然而,它们的 ROC 曲线不同,在大多数应用中,红色曲线比蓝色曲线更受欢迎,因为它在低假阳性率(如FPR1)的情况下会产生更高的真正阳性率。
可视化可能会产生误导
使用适合您结果的正确可视化技术是分析模型结果和报告其性能的关键。没有考虑模型目标就绘制数据可能会导致误导。例如,你可能会看到时间序列图,如图 4**.7所示,在许多博客文章中,预测值和真实值随时间叠加。对于此类时间序列模型,我们希望每个时间点的预测值和真实值尽可能接近。尽管图 4**.7中的线条似乎彼此一致,但与蓝色显示的真实值相比,橙色显示的预测值存在两个时间单位的延迟。这种预测延迟在许多应用(如股票价格预测)中可能产生严重后果:
图 4.7 – 将两个时间序列图叠加在一起是误导性的 – 橙色和蓝色曲线代表任意时间序列数据的预测值和真实值
不要随意解释你的图表
每个可视化都有其假设和正确的解释方式。例如,如果你想比较二维图中数据点的数值,你需要注意x轴和y轴的单位。或者当我们使用t 分布随机邻域嵌入(t-SNE),这是一种旨在帮助在低维空间中可视化高维数据的降维方法时,我们必须提醒自己,数据点之间的大距离和每个组的密度并不代表原始高维空间中的距离和密度(图 4*.8*):
图 4.8 – 示意 t-SNE 折线图显示(A)具有不同距离的三组数据点以及(B)在二维空间中具有不同密度的两组数据点
你可以使用不同的性能指标来评估你的模型是否训练良好并且可以推广到新的数据点,这是本章的下一个主题。
偏差和方差诊断
我们的目标是在训练集(即低偏差模型)中实现高性能或低误差,同时保持对新数据点的性能或误差保持在高水平(即低方差模型)。由于我们没有访问未见过的新的数据点,我们必须使用验证集和测试集来评估我们模型的方差或泛化能力。模型复杂性是确定机器学习模型偏差和方差的重要因素之一。通过增加复杂性,我们让模型在训练数据中学习更复杂的模式,这可能会减少训练误差或模型偏差(图 4*.9*):
图 4.9 – 对于(A)高偏差、(B)高方差以及(C, D)两种低偏差和低方差模型的情况,误差与模型复杂度的关系
这种误差的降低有助于构建更好的模型,即使是对于新的数据点。然而,这种趋势在某个点之后会发生变化,更高的复杂性可能导致过拟合或验证集和测试集相对于训练集有更高的方差和更低的性能(图 4*.9*)。评估与模型复杂性或数据集大小等参数相关的偏差和方差可以帮助我们识别在训练、验证和测试集中提高模型性能的机会。
图 4*.9* 展示了训练和验证集中模型误差与模型复杂度之间可能的四种依赖关系。尽管验证误差通常高于训练误差,但你可能会因为训练和验证集中存在的数据点而经历较低的验证误差。例如,一个多类分类器可能因为更擅长预测验证集中数据点中占多数的类别而具有较低的验证误差。在这种情况下,在报告训练和验证数据集的性能评估并决定选择哪个模型用于生产之前,你需要调查训练和验证集中数据点的分布。
让我们练习一下偏差和方差分析。你可以在 scikit-learn 的乳腺癌数据集上找到使用不同最大深度的随机森林模型训练的结果(图 4.10)。scikit-learn 的乳腺癌数据用于训练和验证模型性能,其中 30% 的数据随机分离作为验证集,其余的保留为训练集。通过增加随机森林模型的最大深度,训练集的对数损失错误减少,而作为模型性能指标的平衡准确率增加。验证错误也减少到最大深度为三,之后开始增加,这是过拟合的迹象。尽管在最大深度为三之后错误减少,但通过将最大深度增加到四和五,平衡准确率仍然可以增加。原因是基于预测概率的对数损失定义与基于预测标签的平衡准确率定义之间的差异:
图 4.10 – 从 scikit-learn 的乳腺癌数据集中分离的训练集和验证集的平衡准确率(顶部)和对数损失(底部)
这是 图 4.10 中显示结果的代码。首先,我们必须导入必要的 Python 库并加载乳腺癌数据集:
from sklearn.datasets import load_breast_cancerfrom sklearn.model_selection import train_test_split
from sklearn.metrics import balanced_accuracy_score as bacc
from sklearn.ensemble import RandomForestClassifier as RF
from sklearn.metrics import log_loss
from sklearn.metrics import roc_auc_score
import matplotlib.pyplot as plt
X, y = load_breast_cancer(return_X_y=True)
然后,我们必须将数据分成训练集和测试集,并使用不同最大深度的随机森林模型进行训练:
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state=10)
maximum_depth = 15
depth_range = range(1, maximum_depth)
bacc_train = []
bacc_test = []
log_loss_train = []
log_loss_test = []
for depth_iter in depth_range:
# initializing an fitting a decision tree model
model_fit = RF(n_estimators = 5, max_depth = depth_iter,
random_state=10).fit(X_train, y_train)
# generating label outputs of train and test set using the trained model
train_y_labels = model_fit.predict(X_train)
test_y_labels = model_fit.predict(X_test)
# generating probability outputs of train and test set using the trained model
train_y_probs = model_fit.predict_proba(X_train)
test_y_probs = model_fit.predict_proba(X_test)
# calculating balanced accuracy
bacc_train.append(bacc(y_train, train_y_labels))
bacc_test.append(bacc(y_test, test_y_labels))
# calculating log-loss
log_loss_train.append(log_loss(y_train, train_y_probs))
log_loss_test.append(log_loss(y_test, test_y_probs))
现在你已经了解了偏差和方差的概念,我们将介绍不同的技术,你可以使用这些技术来验证你的模型。
模型验证策略
为了验证我们的模型,我们可以使用单独的数据集,或者使用不同的技术将我们拥有的数据集分成训练集和验证集,如 表 4.5 中所述,并在 图 4.11 中展示。在交叉验证策略中,我们将数据分成不同的子集,然后计算每个子集的性能分数或错误,因为验证集是使用其余数据训练的模型的预测来计算的。然后,我们可以使用子集间的性能平均值作为交叉验证性能:
图 4.11 – 在一个数据集中分离验证集和训练集的技术
这些验证技术各有其优点和局限性。使用交叉验证技术而不是保留法验证的好处是,至少在一个验证子集中涵盖了所有或大部分数据。与 k 折交叉验证或留一法交叉验证相比,分层 k 折交叉验证(CV)也是一个更好的选择,因为它保持了与整个数据集相同的平衡。
分类或回归的保留法或交叉验证方法不适用于时间序列数据。由于时间序列数据中数据点的顺序很重要,因此在训练和验证子集选择过程中对数据进行洗牌或随机选择是不合适的。随机选择数据点用于验证和训练集会导致在未来的某些数据点上训练模型来预测过去的结果,这与时间序列模型的目的不符。滚动或时间序列交叉验证是时间序列模型的一个合适的验证技术,因为它随着时间的推移滚动验证集而不是随机选择数据点(表 4.5):
| 验证方法 | Python 函数 | 描述 |
|---|---|---|
| 保留法验证 | sklearn.model_selection.train_test_split() | 这会将所有数据分为一个训练集和一个验证集。通常选择 20-40%的数据作为验证集,但对于大型数据集,这个百分比可能更低。 |
| k 折交叉验证 | sklearn.model_selection.KFold() | 此方法将数据分为k个不同的子集,并使用每个子集作为验证集,剩余的数据点作为训练集。 |
| 分层 k 折交叉验证 | sklearn.model_selection.StratifiedKFold() | 这与 k 折交叉验证类似,但保留了每个类别在k个子集中的样本百分比,正如在整个数据集中一样。 |
| 留出-p 个数据点的交叉验证(LOCV) | sklearn.model_selection.LeavePOut() | 这与 k 折交叉验证类似,每个子集有p个数据点,而不是将数据集分为k个子集。 |
| 留一法交叉验证(LOOCV) | sklearn.model_selection.LeaveOneOut() | 这与 k 折交叉验证完全相同,其中k等于数据点的总数。每个验证子集有一个数据点使用 LOOCV。 |
| 随机蒙特卡洛或随机排列交叉验证 | sklearn.model_selection.ShuffleSplit() | 这会将数据随机分为训练集和验证集,类似于保留法验证,并重复此过程多次。更多的迭代次数会导致对性能的更好评估,尽管它增加了验证的计算成本。 |
| 滚动或基于时间的交叉验证 | sklearn.model_selection.TimeSeriesSplit() | 选择一小部分数据作为训练集,更小的一部分数据作为验证集。验证集在时间上移动,之前用于验证的数据点被添加到训练集中。 |
表 4.5 – 使用一个数据集的常见验证技术
这里是 Python 实现保留法、k 折交叉验证和分层 k 折交叉验证的示例,以帮助你在项目中开始使用这些方法。
首先,我们必须导入必要的库,加载乳腺癌数据集,并初始化一个随机森林模型:
from sklearn.datasets import load_breast_cancerfrom sklearn.ensemble import RandomForestClassifier as RF
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import cross_val_score
# importing different cross-validation functions
from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
from sklearn.model_selection import StratifiedKFold
modle_random_state = 42
X, y = load_breast_cancer(return_X_y=True)
rf_init = RF(random_state=modle_random_state)
然后,我们必须使用每种验证技术训练和验证不同的随机森林模型:
# validating using hold-out validationX_train, X_test, y_train, y_test = train_test_split(X, y,
test_size = 0.3, random_state=10)
rf_fit = rf_init.fit(X_train, y_train)
# validating using k-fold (k=5) cross-validation
kfold_cv = KFold(n_splits = 5, shuffle=True,
random_state=10)
scores_kfold_cv = cross_val_score(rf_init, X, y,
cv = kfold_cv, scoring = "roc_auc")
# validating using stratified k-fold (k=5) cross-validation
stratified_kfold_cv = StratifiedKFold(n_splits = 5,
shuffle=True, random_state=10)
scores_strat_kfold_cv = cross_val_score(rf_init, X, y, cv = stratified_kfold_cv, scoring = "roc_auc")
错误分析是你在寻求开发可靠的机器学习模型时可以受益的另一种技术,我们将在下面介绍。
错误分析
你可以使用错误分析来找出具有错误预测输出的数据点之间的共同特征。例如,在图像分类模型中被错误分类的大多数图像可能背景较暗,或者疾病诊断模型可能男性比女性的性能低。虽然手动调查错误预测的数据点可能会有所启发,但这个过程可能会花费你大量时间。相反,你可以尝试以编程方式减少成本。
在这里,我们想通过一个简单的错误分析案例进行练习,即计算使用 5 折交叉验证训练和验证的随机森林模型中每个类别的错误分类数据点的数量。对于错误分析,仅使用验证子集的预测。
首先,我们必须导入必要的 Python 库并加载葡萄酒数据集:
from sklearn.datasets import load_winefrom sklearn.ensemble import RandomForestClassifier as RF
from sklearn.model_selection import KFold
from collections import Counter
# loading wine dataset and generating k-fold CV subsets
X, y = load_wine(return_X_y=True)
然后,我们必须初始化一个随机森林模型和 5 折交叉验证对象:
kfold_cv = KFold(n_splits = 5, shuffle=True, random_state=10)
# initializing the random forest model
rf_init = RF(n_estimators=3, max_depth=5, random_state=42)
然后,对于每个折,我们必须使用除该折之外的所有数据训练一个随机森林模型,并在该折考虑的数据块上验证模型:
misclass_ind_list = []for fold_n, (train_idx, validation_idx) in enumerate(
kfold_cv.split(X, y)):
#get train and validation subsets for current fold
X_train, y_train = X[train_idx], y[train_idx]
X_validation, y_validation = X[validation_idx],
y[validation_idx]
rf_fit = rf_init.fit(X_train, y_train)
# write results
match_list = rf_fit.predict(
X_validation) != y_validation
wrong_pred_subset = [i for i, x in enumerate(
match_list) if x]
misclass_ind_list.extend([validation_idx[
iter] for iter in wrong_pred_subset])
这项分析表明,类别 1 有九个被错误分类的数据点,而类别 2 和 0 分别只有三个和两个错误分类的例子。这个简单的例子可以帮助你开始练习错误分析。但错误分析不仅仅是识别每个类别的错误分类数量。你还可以通过比较错误分类数据点和整个数据集的特征值来识别错误分类示例的特征值中的模式。
在开发机器学习模型时,还需要考虑其他重要因素,例如计算成本和时间。在这里,我们将简要讨论这个重要话题,但详细内容超出了本书的范围。
不仅仅是性能
在工业级更大管道中建模以提高机器学习模型的性能并不是目的。通过提高模型性能的十分之一可能有助于你在机器学习竞赛中获胜或通过击败最先进的模型来发表论文。但并非所有改进都能导致值得部署到生产中的模型。在机器学习竞赛中常见的此类努力的例子是模型堆叠。模型堆叠是关于使用多个模型的输出来训练一个次级模型,这可能会将推理成本提高数个数量级。这里展示了 Python 对scikit-learn中的乳腺癌数据集上逻辑回归、k-最近邻、随机森林、支持向量机和 XGBoost 分类模型进行堆叠的实现。一个次级逻辑回归模型使用每个这些主要模型的预测作为输入,以得出堆叠模型的最终预测:
from sklearn.datasets import load_breast_cancerfrom sklearn.preprocessing import StandardScaler
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import StackingClassifier
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression as LR
from sklearn.neighbors import KNeighborsClassifier as KNN
from sklearn.svm import LinearSVC
from sklearn.ensemble import RandomForestClassifier as RF
from xgboost import XGBClassifier
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y,
stratify=y, random_state=123)
estimators = [
('lr', make_pipeline(StandardScaler(),
LR(random_state=123))),
('knn', make_pipeline(StandardScaler(), KNN())),
('svr', make_pipeline(StandardScaler(),
LinearSVC(random_state=123))),
('rf', RF(random_state=123)),
('xgb', XGBClassifier(random_state=123))
]
stacked_model = StackingClassifier(estimators=estimators,
final_estimator=LR())
stacked_model.fit(X_train, y_train).score(X_test, y_test)
individual_models = [estimators[iter][1].fit(X_train,
y_train).score(X_test, y_test) for iter in range(
0, len(estimators))]
在这个例子中,堆叠模型的性能比最佳单个模型低不到 1%,而推理时间可能会比你的硬件和软件配置高 20 倍。尽管推理时间可能不太重要,例如在疾病诊断或科学发现的情况下,但如果你的模型需要实时提供输出,例如在向消费者推荐产品时,它可能至关重要。因此,当你决定将模型投入生产或计划新的昂贵计算实验或数据收集时,你需要考虑其他因素,例如推理或预测时间。
尽管在构建和选择模型时需要考虑推理时间或其他因素,但这并不意味着你不能使用复杂模型进行实时输出生成。根据应用和你的预算,你可以使用更好的配置,例如在你的基于云的系统上,以消除由于性能更高但速度较慢的模型而产生的问题。
摘要
在本章中,我们学习了监督学习和无监督学习模型的性能和误差指标。我们讨论了每个指标的限制以及正确解释它们的方法。我们还回顾了偏差和方差分析以及用于评估模型泛化能力的不同验证和交叉验证技术。我们还介绍了错误分析作为检测模型中导致模型过拟合的组件的方法。我们通过这些主题的 Python 代码示例来帮助你练习,并能够快速在你的项目中使用它们。
在下一章中,我们将回顾提高机器学习模型泛化性的技术,例如向训练数据添加合成数据、去除数据不一致性和正则化方法。
问题
-
一个分类器被设计用来确定诊所的患者在第一轮测试后是否需要继续进行诊断步骤。哪种分类度量会更合适或不那么合适?为什么?
-
一个分类器被设计来评估不同投资选项的投资风险,针对特定金额,并将被用来向您的客户提供投资机会。哪种分类度量会更合适或不那么合适?为什么?
-
如果两个二元分类模型在相同的验证集上的计算 ROC-AUC 值相同,这意味着模型是相同的吗?
-
如果模型 A 在相同的测试集上比模型 B 具有更低的 log-loss,这总是意味着模型 A 的 MCC 也高于模型 B 吗?
-
如果模型 A 在相同数量的数据点上比模型 B 具有更高的 R²值,我们能否声称模型 A 比模型 B 更好?特征数量是如何影响我们对两个模型之间比较的?
-
如果模型 A 的性能优于模型 B,这意味着选择模型 A 是将其投入生产的正确选择吗?
参考文献
-
Rosenberg, Andrew,和 Julia Hirschberg. V-measure: 一种基于条件熵的外部聚类评估度量. 2007 年实证自然语言处理和计算自然语言学习联合会议(EMNLP-CoNLL)论文集。
-
Vinh, Nguyen Xuan,Julien Epps,和 James Bailey. 聚类比较的信息论度量:是否需要校正偶然性? 第 26 届国际机器学习年度会议论文集。2009 年。
-
Andrew Ng, 斯坦福 CS229:机器学习课程,2018 年秋季。
-
Van der Maaten, Laurens,和 Geoffrey Hinton. 使用 t-SNE 可视化数据. 机器学习研究杂志第 9 卷第 11 期(2008 年)。
-
McInnes, Leland,John Healy,和 James Melville. Umap:统一流形近似和投影用于降维. arXiv 预印本 arXiv:1802.03426(2018 年)。
第五章:提高机器学习模型的性能
在上一章中,你学习了关于正确验证和评估你的机器学习模型性能的不同技术。下一步是扩展你对这些技术的了解,以改善你模型的性能。
在本章中,你将通过处理你为机器学习建模选择的数据或算法来学习提高模型性能和泛化性的技术。
在本章中,我们将涵盖以下主题:
-
提高模型性能的选项
-
生成合成数据
-
改进预训练数据预处理
-
通过正则化提高模型泛化性
到本章结束时,你将熟悉不同的技术来提高你模型的性能和泛化能力,你将了解如何从 Python 中受益,将这些技术应用于你的项目。
技术要求
以下要求对于本章是必需的,因为它们有助于你更好地理解概念,并使你能够在你的项目和实践中使用它们,以及使用提供的代码:
-
Python 库要求:
-
sklearn>= 1.2.2 -
ray>= 2.3.1 -
tune_sklearn>= 0.4.5 -
bayesian_optimization>= 1.4.2 -
numpy>= 1.22.4 -
imblearn -
matplotlib>= 3.7.1
-
-
了解机器学习验证技术,如k-折交叉验证
你可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter05。
提高模型性能的选项
我们可以做出的改变以提高我们模型的性能可能与我们所使用的算法或我们提供给它们训练模型的数据有关(见表 5.1)。添加更多的数据点可以通过添加接近分类模型决策边界的数据来减少模型的方差,例如,增加对识别边界有信心并减少过拟合。移除异常值可以通过消除远离数据点的效果来减少偏差和方差。添加更多特征可以帮助模型在训练阶段表现得更好(即降低模型偏差),但它可能导致更高的方差。也可能存在导致过拟合的特征,它们的移除可以帮助提高模型的可泛化性。
| 改变 | 潜在影响 | 描述 |
|---|---|---|
| 增加更多训练数据点 | 降低方差 | 我们可以随机添加新的数据点,或者尝试添加具有特定特征值、输出值或标签的数据点。 |
| 以较低的标准移除异常值 | 降低偏差和方差 | 移除异常值可以减少训练集中的错误,但它也有助于训练一个更具泛化性的模型(即具有更低方差的模型)。 |
| 添加更多特征 | 降低偏差 | 我们可以添加提供未知信息的特征给模型。例如,为房价预测添加一个社区的犯罪率,如果该信息尚未被现有特征捕获,可能会提高模型性能。 |
| 移除特征 | 降低方差 | 每个特征都可能对训练性能有积极影响,但它可能添加了无法推广到新数据点的信息,从而导致更高的方差。 |
| 运行更多迭代的优化过程 | 降低偏差 | 增加迭代次数以优化可以减少训练误差,但可能导致过拟合。 |
| 使用更复杂的模型 | 降低偏差 | 增加决策树的深度是增加模型复杂性的一个例子,这可能导致降低模型偏差,但可能增加过拟合的风险。 |
表 5.1 – 减少机器学习模型偏差和/或方差的一些选项
增加模型复杂度可以帮助降低偏差,正如我们在上一章所讨论的,但模型可以有多个影响其复杂度或导致提高或降低模型偏差和泛化能力的超参数。在优化过程中,对于广泛使用的监督学习和无监督学习方法,您可以从中开始的一些超参数在表 5.2中提供。这些超参数可以帮助您提高模型性能,但您不需要编写新的函数或类来进行超参数优化。
| 方法 | 超参数 |
|---|---|
逻辑回归sklearn.linear_model.LogisticRegression() |
-
penalty: 在l1、l2、elasticnet和None之间选择正则化 -
class_weight: 将权重与类别关联 -
l1_ratio: 弹性网络混合参数
|
K-最近邻sklearn.neighbors.KNeighborsClassifier() |
|---|
-
n_neighbors: 邻居的数量 -
weights: 在uniform或distance之间选择,以平等使用邻居或根据它们的距离分配权重
|
支持向量机(SVM)分类器或回归器sklearn.svm.SVC()``sklearn.svm.SVR() |
|---|
-
C: 使用l2惩罚的正则化强度的倒数 -
kernel: 包含预构建核的 SVM 核,包括linear、poly、rbf、sigmoid和precomputed -
degree(多项式度):多项式核函数(poly)的度 -
class_weight(仅用于分类):将权重与类别关联
|
随机森林分类器或回归器sklearn.ensemble.RandomForestClassifier()``sklearn.ensemble.RandomForestRegressor() |
|---|
-
n_estimators: 森林中的树的数量 -
max_depth: 树的最大深度 -
class_weight: 将权重与类别关联 -
min_samples_split: 成为叶节点所需的最小样本数
|
XGBoost 分类器或回归器xgboost.XGBClassifier()``xgboost.XGBRegressor() |
|---|
-
booster(gbtree、gblinear或dart) -
对于树增强器:
-
eta: 步长缩减以防止过拟合。 -
max_depth: 树的最大深度 -
min_child_weight: 继续分区所需的最小数据点权重总和 -
lambda: L2 正则化因子 -
alpha: L1 正则化因子
-
|
LightGBM 分类器或回归器Lightgbm.LGBMClassifier()``Lightgbm.LGBMRegressor() |
|---|
-
boosting_type(gbdt、dart或rf) -
num_leaves: 树的最大叶子数 -
max_depth: 树的最大深度 -
n_estimators: 增强树的数目 -
reg_alpha: 权重的 L1 正则化项 -
reg_lambda: 权重的 L2 正则化项
|
K-Means 聚类sklearn.cluster.KMeans() |
|---|
n_clusters: 聚类数
|
聚类层次聚类sklearn.cluster.AgglomerativeClustering() |
|---|
-
n_clusters: 聚类数 -
metric: 包含euclidean、l1、l2、manhattan、cosine或precomputed等预建度量的距离度量 -
linkage: 包含ward、complete、average和single等预建方法的链接标准
|
DBSCAN 聚类sklearn.cluster.DBSCAN() |
|---|
-
eps: 数据点之间允许的最大距离,以被视为邻居 -
min_samples: 数据点需要作为核心点考虑的最小邻居数
|
UMAPumap.UMAP() |
|---|
-
n_neighbors: 限制学习数据结构的局部邻域大小 -
min_dist: 控制低维空间中组的紧凑性
|
表 5.2 – 一些广泛使用的监督和未监督机器学习方法的最重要的超参数,以开始超参数优化
列在表 5.3中的 Python 库具有针对不同超参数优化技术的模块,例如网格搜索、随机搜索、贝叶斯搜索和逐次减半。
| 库 | URL |
|---|---|
scikit-optimize | pypi.org/project/scikit-optimize/ |
Optuna | pypi.org/project/optuna/ |
GpyOpt | pypi.org/project/GPyOpt/ |
Hyperopt | hyperopt.github.io/hyperopt/ |
ray.tune | docs.ray.io/en/latest/tune/index.html |
表 5.3 – 常用的 Python 库用于超参数优化
让我们详细讨论每种方法。
网格搜索
此方法涉及确定一系列要逐一测试的超参数提名集,以找到最佳组合。网格搜索以找到最佳组合的成本很高。此外,考虑到对于每个问题都存在一组特定的超参数,为所有问题预定义一组超参数组合的网格搜索并不是一个有效的方法。
下面是一个使用sklearn.model_selection.GridSearchCV()对随机森林分类器模型进行网格搜索超参数优化的示例。80%的数据用于超参数优化,模型的性能使用分层 5 折交叉验证进行评估:
# determining random state for data split and model initializationrandom_state = 42
# loading and splitting digit data to train and test sets
digits = datasets.load_digits()
x = digits.data
y = digits.target
x_train, x_test, y_train, y_test = train_test_split(
x, y, random_state= random_state, test_size=0.2)
# list of hyperparameters to use for tuning
parameter_grid = {"max_depth": [2, 5, 10, 15, 20],
"min_samples_split": [2, 5, 7]}
# validating using stratified k-fold (k=5) cross-validation
stratified_kfold_cv = StratifiedKFold(
n_splits = 5, shuffle=True, random_state=random_state)
# generating the grid search
start_time = time.time()
sklearn_gridsearch = GridSearchCV(
estimator = RFC(n_estimators = 10,
random_state = random_state),
param_grid = parameter_grid, cv = stratified_kfold_cv,
n_jobs=-1)
# fitting the grid search cross-validation
sklearn_gridsearch.fit(x_train, y_train)
在此代码中,使用了 10 个估计器,并在超参数优化过程中考虑了不同的min_samples_split和max_depth值。您可以使用评分参数指定不同的性能指标,评分参数是sklearn.model_selection.GridSearchCV()的一个参数,根据您在上一章中学到的知识。在这种情况下,max_depth为10和min_samples_split为7的组合被确定为最佳超参数集,使用分层 5 折交叉验证得到了 0.948 的准确率。我们可以使用sklearn_gridsearch.best_params_和sklearn_gridsearch.best_score_提取最佳超参数和相应的分数。
随机搜索
此方法是对网格搜索的一种替代。它随机尝试不同的超参数值组合。对于相同的足够高的计算预算,研究表明,与网格搜索相比,随机搜索可以实现性能更高的模型,因为它可以搜索更大的空间(Bergstra 和 Bengio,2012)。
下面是一个使用sklearn.model_selection.RandomizedSearchCV()对相同模型和数据进行的随机搜索超参数优化的示例:
# generating the grid searchstart_time = time.time()
sklearn_randomsearch = RandomizedSearchCV(
estimator = RFC(n_estimators = 10,
random_state = random_state),
param_distributions = parameter_grid,
cv = stratified_kfold_cv, random_state = random_state,
n_iter = 5, n_jobs=-1)
# fitting the grid search cross-validation
sklearn_randomsearch.fit(x_train, y_train)
只需五次迭代,这个随机搜索就得到了 0.942 的 CV 准确率,运行时间不到三分之一,这可能会取决于您的本地或云系统配置。在这种情况下,max_depth为15和min_samples_split为7的组合被确定为最佳超参数集。比较网格搜索和随机搜索的结果,我们可以得出结论,具有不同max_depth值的模型可能在这个特定情况下对使用scikit-learn的数字数据集进行随机森林建模的 CV 准确率产生相似的结果。
贝叶斯搜索
在贝叶斯优化中,与随机选择超参数组合而不检查先前组合集的值不同,每个超参数组合集的每个组合都是基于先前测试的超参数集的历史记录在迭代中选择的。这个过程有助于降低与网格搜索相比的计算成本,但它并不总是优于随机搜索。我们在这里想使用 Ray Tune (ray.tune)来实现这种方法。您可以在教程页面上了解更多关于 Ray Tune 中可用的不同功能,例如记录 tune 运行、如何停止和恢复、分析 tune 实验结果和在云中部署 tune:docs.ray.io/en/latest/tune/tutorials/overview.html。
如前所述,使用ray.tune.sklearn.TuneSearchCV()对相同的随机森林模型进行贝叶斯超参数优化,实现了 0.942 的 CV 准确率:
start_time = time.time()tune_bayessearch = TuneSearchCV(
RFC(n_estimators = 10, random_state = random_state),
parameter_grid,
search_optimization="bayesian",
cv = stratified_kfold_cv,
n_trials=3, # number of sampled parameter settings
early_stopping=True,
max_iters=10,
random_state = random_state)
tune_bayessearch.fit(x_train, y_train)
连续减半法
连续减半法的理念在于不是对所有的超参数进行同等投资。候选超参数集使用有限的资源进行评估,例如,使用训练数据的一部分或在随机森林模型的一个迭代中使用有限数量的树,其中一些会进入下一个迭代。在后续的迭代中,使用更多的资源,直到最后一个迭代,在最后一个迭代中,使用所有资源,例如,所有训练数据,来评估剩余的超参数集。您可以使用HalvingGridSearchCV()和HalvingRandomSearchCV()作为sklearn.model_selection的一部分来尝试连续减半。您可以在scikit-learn.org/stable/modules/grid_search.html#id3了解更多关于这两个 Python 模块的信息。
还有其他超参数优化技术,例如Hyperband(Li 等人,2017)和BOHB(Falkner 等人,2018),您可以尝试这些技术,但大多数超参数优化进步背后的通用理念是尽量减少达到最佳超参数集所需的计算资源。还有用于深度学习超参数优化的技术和库,我们将在第十二章,使用深度学习超越 ML 调试和第十三章,高级深度学习技术中介绍。尽管超参数优化有助于我们获得更好的模型,但使用提供的数据进行模型训练和选定的机器学习方法,我们可以通过其他方法来提高模型性能,例如为模型训练生成合成数据,这是我们接下来要讨论的主题。
生成合成数据
我们可用于训练和评估机器学习模型的数据可能有限。例如,在分类模型的情况下,我们可能有一些数据点数量有限的类别,导致我们的模型在相同类别的未见数据点上的性能降低。在这里,我们将介绍一些方法来帮助您在这些情况下提高模型的性能。
对不平衡数据进行过采样
由于在训练过程中以及模型性能报告中的多数类的支配效应,不平衡数据分类具有挑战性。在模型性能报告方面,我们在上一章讨论了不同的性能指标以及如何在不平衡数据分类的情况下选择一个可靠的指标。在这里,我们想要讨论过采样的概念,以帮助您通过合成改善训练数据来提高您模型的性能。过采样的概念是使用您数据集中已有的真实数据点来增加少数类数据点的数量。最简单的方式来思考它就是复制少数类中的某些数据点,但这不是一个好的方法,因为它们在训练过程中不会提供与真实数据互补的信息。有一些技术是为过采样过程设计的,例如合成少数类过采样技术(SMOTE)及其用于表格数据的变体,我们将在下面介绍。
降采样
在对不平衡数据进行分类时,除了过采样之外,还可以通过采样多数类来减少不平衡。这个过程会降低多数类与少数类数据点的比例。由于并非所有数据点都会包含在一个采样集中,因此可以通过采样多数类数据点的不同子集来构建多个模型,并将这些模型的输出结合起来,例如通过模型间的多数投票。这个过程被称为降采样。与降采样相比,过采样通常会导致更高的性能提升。
SMOTE
SMOTE 是一种古老但广泛使用的方法,用于通过使用邻近数据点的分布(Chawla 等人,2022;参见 图 5*.1*)对连续特征集进行少数类过采样。
图 5.1 – 使用 SMOTE、Borderline-SMOTE 和 ADASYN 生成合成数据的示意图
使用 SMOTE 生成任何合成数据点的步骤可以总结如下:
-
从少数类中随机选择一个数据点。
-
确定该数据点的 K-最近邻。
-
随机选择一个邻居。
-
在特征空间中两个数据点之间随机选择一个点生成合成数据点。
SMOTE 及其两种变体Borderline-SMOTE和自适应合成(ADASYN)在图 5.1中展示。SMOTE、Borderline-SMOTE 和 ADASYN 的步骤 2 到步骤 4 是相似的。然而,Borderline-SMOTE 专注于分割类别的真实数据点,而 ADASYN 专注于特征空间中由多数类主导区域的数据点。通过这种方式,Borderline-SMOTE 增加了决策边界识别的置信度,以避免过拟合,而 ADASYN 提高了在多数类主导的空间部分对少数类预测的泛化能力。
您可以使用imblearn Python 库通过 SMOTE、Borderline-SMOTE 和 ADASYN 生成合成数据。然而,在进入使用这些功能之前,我们需要编写一个绘图函数供以后使用,以显示过采样过程之前和之后的数据:
def plot_fun(x_plot: list, y_plot: list, title: str): """
Plotting a binary classification dataset
:param x_plot: list of x coordinates (i.e. dimension 1)
:param y_plot: list of y coordinates (i.e. dimension 2)
:param title: title of plot
"""
cmap, norm = mcolors.from_levels_and_colors([0, 1, 2],
['black', 'red'])
plt.scatter([x_plot[iter][0] for iter in range(
0, len(x_plot))],
[x_plot[iter][1] for iter in range(
0, len(x_plot))],
c=y_plot, cmap=cmap, norm=norm)
plt.xticks(fontsize = 12)
plt.yticks(fontsize = 12)
plt.xlabel('1st dimension', fontsize = 12)
plt.ylabel('2nd dimension', fontsize = 12)
plt.title(title)
plt.show()
然后,我们生成一个包含两个类别和仅两个特征(即二维数据)的合成数据集,并将其视为我们的真实数据集。我们将一个类别的 100 个数据点视为多数类,另一个类别的 10 个数据点视为少数类:
np.random.seed(12)minority_sample_size = 10
majority_sample_size = 100
# generating random set of x coordinates
group_1_X1 = np.repeat(2,majority_sample_size)+\
np.random.normal(loc=0, scale=1,size=majority_sample_size)
group_1_X2 = np.repeat(2,majority_sample_size)+\
np.random.normal(loc=0, scale=1,size=majority_sample_size)
# generating random set of x coordinates
group_2_X1 = np.repeat(4,minority_sample_size)+\
np.random.normal(loc=0, scale=1,size=minority_sample_size)
group_2_X2 = np.repeat(4,minority_sample_size)+\
np.random.normal(loc=0, scale=1,size=minority_sample_size)
X_all = [[group_1_X1[iter], group_1_X2[iter]] for\
iter in range(0, len(group_1_X1))]+\
[[group_2_X1[iter], group_2_X2[iter]]\
for iter in range(0, len(group_2_X1))]
y_all = [0]*majority_sample_size+[1]*minority_sample_size
# plotting the randomly generated data
plot_fun(x_plot = X_all, y_plot = y_all,
title = 'Original')
结果数据点在以下散点图中显示,红色和黑色数据点分别代表少数类和多数类。我们使用这个合成数据而不是真实数据集,以直观地展示不同的合成数据生成方法是如何工作的。
图 5.2 – 用于练习 SMOTE 及其替代方法的示例数据集,包含两个特征(即维度),通过合成生成
我们现在使用 SMOTE 通过imblearn.over_sampling.SMOTE(),如以下代码片段所示,只为少数类生成合成数据点:
k_neighbors = 5# initializing smote
# using 'auto', equivalent to 'not majority',
# sampling_strategy that enforces resampling all classes but the majority class
smote = SMOTE(sampling_strategy='auto',
k_neighbors=k_neighbors)
# fitting smote to oversample the minority class
x_smote, y_smote = smote.fit_resample(X_all, y_all)
# plotting the resulted oversampled data
plot_fun(x_plot = x_smote, y_plot = y_smote,
title = 'SMOTE')
如您在以下图中可以看到,新的过采样数据点将位于少数类原始数据点(即红色数据点)之间的间隙中。然而,许多这些新的数据点并没有帮助识别一个可靠的决策边界,因为它们被分组在非常右上角,远离黑色数据点和潜在的决策边界。
图 5.3 – 实施 SMOTE 后图 5.2 所示数据集的可视化
我们通过imblearn.over_sampling.BorderlineSMOTE()使用 Borderline-SMOTE 来生成合成数据,如下所示:
k_neighbors = 5# using 5 neighbors to determine if a minority sample is in "danger"
m_neighbors = 10
# initializing borderline smote
# using 'auto', equivalent to 'not majority', sampling_strategy that enforces resampling all classes but the majority class
borderline_smote = BorderlineSMOTE(
sampling_strategy='auto',
k_neighbors=k_neighbors,
m_neighbors=m_neighbors)
# fitting borderline smote to oversample the minority class
x_bordersmote,y_bordersmote =borderline_smote.fit_resample(
X_all, y_all)
# plotting the resulted oversampled data
plot_fun(x_plot = x_bordersmote, y_plot = y_bordersmote,
title = 'Borderline-SMOTE')
我们可以看到,新的合成数据点更接近黑色的大多数类数据点,这有助于识别一个可泛化的决策边界:
图 5.4 – 实施 Borderline-SMOTE 后,图 5.2 所示数据集的可视化
我们也可以通过imblearn.over_sampling.ADASYN()使用 ADASYN,因为它专注于具有更多多数类样本的区域,因此生成更多接近黑色数据点的新合成数据:
# using 5 neighbors for each datapoint in the oversampling process by SMOTEn_neighbors = 5
# initializing ADASYN
# using 'auto', equivalent to 'not majority', sampling_strategy that enforces resampling all classes but the majority class
adasyn_smote = ADASYN(sampling_strategy = 'auto',n_neighbors = n_neighbors)
# fitting ADASYN to oversample the minority class
x_adasyn_smote, y_adasyn_smote = adasyn_smote.fit_resample(X_all, y_all)
# plotting the resulted oversampled data
plot_fun(x_plot = x_adasyn_smote, y_plot = y_adasyn_smote,
title = "ADASYN")
包括原始和合成数据点在内的数据在图 5.5中显示。
图 5.5 – 实施 ADASYN 后,图 5.2 所示数据集的可视化
基于 SMOTE 的合成数据生成方法近年来有了更多的发展,例如基于密度的合成少数类过采样技术(DSMOTE)(Xiaolong 等人,2019 年)和k-means SMOTE(Felix 等人,2017 年)。这两种方法都试图捕捉数据点在目标少数类或整个数据集中的分组。在 DSMOTE 中,使用基于密度的空间聚类应用噪声(DBSCAN)将少数类的数据点划分为三个组:核心样本、边界样本和噪声(即异常)样本,然后仅使用核心和边界样本进行过采样。这种方法被证明比 SMOTE 和 Borderline-SMOTE(Xiaolong 等人,2019 年)更有效。K-means SMOTE 是 SMOTE 的另一种近期替代方案(Last 等人,2017 年),它依赖于在过采样之前使用 k-means 聚类算法对整个数据集进行聚类(见图 5.6)。
图 5.6 – k-means SMOTE 的四个主要步骤的示意图(Last 等人,2017 年)
这里是数据生成 k-means SMOTE 方法中的步骤,您可以通过kmeans-smote Python 库使用它们:
-
根据原始数据确定决策边界。
-
使用 k-means 聚类将数据点聚类到 k 个簇中。
-
对于不平衡率(IR)大于不平衡率阈值(IRT)的簇使用 SMOTE 进行过采样。
-
重复决策边界识别过程。(注意:IRT 可以由用户选择或像超参数一样优化。)
您可以使用 SMOTE 的不同变体进行练习,找出最适合您数据集的版本,但 Borderline-SMOTE 和 K-means SMOTE 可以作为良好的起点。
接下来,您将学习一些技术,这些技术有助于在模型训练之前提高您数据的质量。
改进预训练数据处理
在机器学习生命周期的早期阶段,在模型训练和评估之前进行的数据处理决定了我们输入训练、验证和测试过程的数据质量,从而决定了我们实现高性能和可靠模型的成功。
异常检测和异常值移除
您数据中的异常值和离群点可能会降低您模型在生产中的性能和可靠性。训练数据、用于模型评估的数据以及生产中的未见数据中的离群点可能产生不同的影响:
-
模型训练中的离群点:监督学习模型的训练数据中存在离群点可能会导致模型泛化能力降低。它可能导致分类中的决策边界过于复杂,或者在回归模型中产生不必要的非线性。
-
模型评估中的离群点:验证和测试数据中的离群点可能会降低模型性能。由于模型不一定是为离群数据点设计的,它们会影响模型性能评估的可靠性,导致模型无法正确预测其标签或连续值。这个问题可能会使模型选择过程变得不可靠。
-
生产中的离群点:生产中的未见数据点可能远离训练或甚至测试数据的分布。我们的模型可能被设计来识别这些异常值,例如在欺诈检测的情况下,但如果这不是目标,那么我们应该将这些数据点标记为样本,这些样本不是我们的模型有信心处理或为它们设计的。例如,如果我们设计了一个基于肿瘤遗传信息的模型来建议癌症患者的药物,那么我们的模型应该对需要被视为离群样本的患者报告低置信度,因为错误的药物可能具有致命的后果。
表 5.4 提供了一些您可以用来识别数据中的异常值并在必要时移除离群点的异常检测方法的总结:
| 方法 | 文章和网址 |
|---|---|
| 隔离森林(iForest) | 刘等人,2008ieeexplore.ieee.org/abstract/document/4781136 |
| 轻量级在线异常检测器(Loda) | 彭维,2016link.springer.com/article/10.1007/s10994-015-5521-0 |
| 局部离群因子(LOF) | 布伦尼格等人,2000dl.acm.org/doi/abs/10.1145/342009.335388 |
| 基于角度的离群点检测(ABOD) | 克里格尔等人,2008dl.acm.org/doi/abs/10.1145/1401890.1401946 |
| 鲁棒核密度估计(RKDE) | 金和斯科特,2008ieeexplore.ieee.org/document/4518376 |
| 用于新颖性检测的支持向量方法 | 施洛克夫等人,1999proceedings.neurips.cc/paper/1999/hash/8725fb777f25776ffa9076e44fcfd776-Abstract.html |
表 5.4 – 广泛使用的异常检测技术(Emmott 等人,2013 和 2015)
异常检测的有效方法之一是 scikit-learn。为了尝试它,我们首先生成以下合成训练数据集:
n_samples, n_outliers = 100, 20rng = np.random.RandomState(12)
# Generating two synthetic clusters of datapoints sampled from a univariate "normal" (Gaussian) distribution of mean 0 and variance 1
cluster_1 = 0.2 * rng.randn(n_samples, 2) + np.array(
[1, 1])
cluster_2 = 0.3 * rng.randn(n_samples, 2) + np.array(
[5, 5])
# Generating synthetic outliers
outliers = rng.uniform(low=2, high=4, size=(n_outliers, 2))
X = np.concatenate([cluster_1, cluster_2, outliers])
y = np.concatenate(
[np.ones((2 * n_samples), dtype=int),
-np.ones((n_outliers), dtype=int)])
然后,我们使用 scikit-learn 中的 IsolationForest():
# initializing iForestclf = IsolationForest(n_estimators = 10, random_state=10)
# fitting iForest using training data
clf.fit(X)
# plotting the results
scatter = plt.scatter(X[:, 0], X[:, 1])
handles, labels = scatter.legend_elements()
disp = DecisionBoundaryDisplay.from_estimator(
clf,
X,
plot_method = "contour",
response_method="predict",
alpha=1
)
disp.ax_.scatter(X[:, 0], X[:, 1], s = 10)
disp.ax_.set_title("Binary decision boundary of iForest (
n_estimators = 10)")
plt.xlabel('Dimension 1', fontsize = 12)
plt.ylabel('Dimension 2', fontsize = 12)
plt.show()
在之前的代码中,我们使用了 10 个决策树,当初始化 IsolationForest() 时,n_estimator = 10。这是 iForest 的一个超参数,我们可以通过调整它来获得更好的结果。你可以看到 n_estimator = 10 和 n_estimator = 100 的结果边界。
图 5.7 – 使用不同估计器数量的 iForest 的决策边界
如果你未经进一步调查就接受异常检测方法(如 iForest)的结果,你可能会决定只使用显示边界内的数据。然而,这些技术可能存在问题,就像任何其他机器方法一样。尽管 iForest 不是一个监督学习方法,但用于识别异常的边界可能容易过拟合,并且不适用于进一步的评估或在生产环境中对未见数据的使用。此外,超参数的选择可能会导致错误地将大量数据点视为异常值。
利用低质量或相关性较低的数据
在进行监督机器学习时,我们理想情况下希望能够访问大量高质量的数据。然而,我们访问到的数据点中的特征或输出值并不具有相同程度的确定性。例如,在分类的情况下,标签可能并不都具有相同的有效性。换句话说,我们对不同数据点标签的信心可能不同。一些常用的数据点标签过程是通过平均实验测量值(例如,在生物或化学环境中)或使用多个专家(或非专家)的注释来进行的。
你也可能遇到这样的问题,比如预测乳腺癌患者对特定药物的反应,而你又有数据可以访问患者对其他癌症类型中相同或类似药物的反应。那么,你的一部分数据与乳腺癌患者对药物反应的目标相关性较低。
我们更希望在这些情况下依赖高质量的数据,或者高度可靠的注释和标签。然而,我们可能能够访问大量质量较低或与我们目标相关性较低的数据点。尽管这些方法并不总是成功,但我们有几种方法可以利用这些低质量或相关性较低的数据点:
-
在
scikit-learn中,初始化一个随机森林模型,例如rf_model = RandomForestClassifier(random_state = 42)后,你可以在拟合步骤中指定每个数据点的权重,作为rf_model.fit(X_train,y_train, sample_weight = weights_array),其中weights_array是训练集中每个数据点的权重数组。这些权重可以是根据数据点与目标的相关性或其质量而对你拥有的置信度分数。例如,如果你使用 10 位不同的专家注释员为一系列数据点分配标签,你可以使用其中的一部分来达成对类别标签的共识,作为每个数据点的权重。如果一个数据点的类别为 1,但只有 7 位注释员同意这个类别,那么它将比所有 10 位注释员都同意其标签的类别-1 数据点获得更低的权重。 -
集成学习:如果你考虑每个数据点的质量或置信度分数的分布,那么你可以使用分布的每个部分的数据点构建不同的模型,然后结合这些模型的预测,例如,使用它们的加权平均值(见图 5.8)。分配给每个模型的权重可以是一个数字,代表用于其训练的数据点的质量。
-
迁移学习:在迁移学习中,我们可以在一个参考任务上训练一个模型,通常有更多的数据点,然后在小任务上进行微调,以得出特定任务的预测(Weiss 等人,2016 年,Madani Tonekaboni 等人,2020 年)。这种方法可以用于具有不同置信度水平的数据(Madani Tonekaboni 等人,2020 年)。你可以在具有不同标签置信度水平的大型数据集上训练一个模型(见图 5.8),排除非常低置信度的数据,然后在其数据集的高置信度部分上进行微调。
图 5.8 – 在训练机器学习模型时使用不同质量或相关性的目标问题数据点的技术
这些方法可以帮助你减少生成更多高质量数据的需求。然而,如果可能的话,拥有更多高质量且高度相关的数据是首选的。
作为本章的最后一种方法,我们将讨论正则化作为一种控制过拟合并帮助生成具有更高泛化可能性的模型的技术。
正则化以改善模型泛化能力
在上一章中,我们了解到模型的高复杂度可能导致过拟合。控制模型复杂度和减少影响模型泛化能力的特征的影响的一种方法是通过正则化。在正则化过程中,我们在训练过程中优化的损失函数中考虑一个正则化或惩罚项。在简单的线性建模情况下,正则化可以如下添加到优化过程中的损失函数中:
其中第一个项是损失,Ω(W)是模型权重或参数 W 的正则化项作为函数。然而,正则化可以与不同的机器学习方法一起使用,如 SVM 或LightGBM(参见表 5.2)。以下表格显示了三种常见的正则化项,包括L1 正则化、L2 正则化及其组合。
| 方法 | 正则化项 | 参数 |
|---|---|---|
| L2 正则化 | Ω(W) = λ∑ j=1 p w j 2 | λ: 决定正则化强度的一个正则化参数 |
| L1 正则化 | Ω(W) = λ∑ j=1 p |w p| | λ: 与 L2 正则化中相同 |
| L2 和 L1 | Ω(W) = λ( 1 − α _ 2 ∑ j=1 p w j 2 + α∑ j=1 p |w j|) | λ: 与 L1 和 L2 正则化中相同α: 决定正则化过程中 L1 与 L2 影响的一个缺失参数 |
表 5.5 – 机器学习建模中常用的正则化方法
我们可以将带有正则化的优化过程视为尽可能接近最优参数集 ˆβ 的过程,同时将参数约束在约束区域内,如图图 5.9所示:
图 5.9 – L1 和 L2 范数正则化在二维特征空间中控制过拟合的示意图
在 L1 正则化的参数约束区域中,角落会导致一些参数被消除,或者使它们的相关权重变为零。然而,L2 正则化约束参数区域的凸性只会通过降低权重来降低参数的影响。这种差异通常会导致 L1 正则化对异常值的鲁棒性更高。
带有 L1 正则化和 L2 正则化的线性分类模型分别称为Lasso和Ridge回归(Tibshirani,1996)。Elastic-Net 后来被提出,它结合了 L1 正则化和 L2 正则化项(Zou 等人,2005)。在这里,我们想练习使用这三种方法,但你也可以使用其他方法的正则化超参数,例如 SVM 或 XGBoost 分类器(见表 5.2)。
我们首先导入必要的库并设计一个绘图函数来直观地显示正则化参数值的影响。我们还从scikit-learn加载数字数据集用于模型训练和评估:
random_state = 42# loading and splitting digit data to train and test sets
digits = datasets.load_digits()
x = digits.data
y = digits.target
# using stratified k-fold (k=5) cross-validation
stratified_kfold_cv = StratifiedKFold(n_splits = 5,
shuffle=True, random_state=random_state)
# function for plotting the CV score across different hyperparameter values
def reg_search_plot(search_fit, parameter: str):
"""
:param search_fit: hyperparameter search object after model fitting
:param parameter: hyperparameter name
"""
parameters = [search_fit.cv_results_[
'params'][iter][parameter] for iter in range(
0,len(search_fit.cv_results_['params']))]
mean_test_score = search_fit.cv_results_[
'mean_test_score']
plt.scatter(parameters, mean_test_score)
plt.xticks(fontsize = 12)
plt.yticks(fontsize = 12)
plt.xlabel(parameter, fontsize = 12)
plt.ylabel('accuracy', fontsize = 12)
plt.show()
我们可以使用GridSearchCV()函数评估以下模型中不同正则化参数值的效果。在scikit-learn中,正则化参数通常命名为alpha而不是λ,混合参数称为l1_ratio而不是α。在这里,我们首先评估不同alpha值对 Lasso 模型的影响,使用 L1 正则化,用数字数据集进行训练和评估:
# Defining hyperparameter gridparameter_grid = {"alpha": [0, 0.1, 0.2, 0.3, 0.4, 0.5]}
# generating the grid search
lasso_search = GridSearchCV(Lasso(
random_state = random_state),
parameter_grid,cv = stratified_kfold_cv,n_jobs=-1)
# fitting the grid search cross-validation
lasso_search.fit(x, y)
reg_search_plot(search_fit = lasso_search,
parameter = 'alpha')
最优的alpha值被确定为 0.1,如下面的图表所示,这导致了在考虑的值中达到最高的准确率。这意味着在alpha值为0.1之后增加正则化的效果会增加模型偏差,导致模型在训练中的性能降低。
图 5.10 – Lasso 模型中准确率与正则化参数 alpha 的关系
如果我们在岭回归模型中评估不同alpha值的效果,使用 L2 正则化,我们可以看到随着正则化强度的增加,性能会提高(参见图 5.11)。
图 5.11 – 岭回归模型中准确率与正则化参数 alpha 的关系
这两种方法的替代方案是 Elastic-Net,它结合了 L1 和 L2 正则化的效果。在这种情况下,alpha对模型性能的影响趋势与 Lasso 更相似;然而,与仅依赖 L1 正则化的 Lasso 相比,准确率值的范围更窄(参见图 5.12)。
图 5.12 – Elastic-Net 模型中准确率与正则化参数 alpha 的关系
如果你的数据集不是非常小,更复杂的模型可以帮助你实现更高的性能。只有在极少数情况下,你才会考虑线性模型作为你的最终模型。为了评估正则化对更复杂模型的影响,我们选择了 SVM 分类器,并检查了sklearn.svm.SVC()中作为正则化参数的不同C值的效果:
# Defining hyperparameter gridparameter_grid = {"C": [0.01, 0.2, 0.4, 0.6, 0.8, 1]}
# generating the grid search
svc_search = GridSearchCV(SVC(kernel = 'poly',
random_state = random_state),parameter_grid,
cv = stratified_kfold_cv,n_jobs=-1)
# fitting the grid search cross-validation
svc_search.fit(x, y)
reg_search_plot(search_fit = svc_search, parameter = 'C')
如下所示,模型的准确率范围更高,在 0.92 到 0.99 之间,与准确率低于 0.6 的线性模型相比,但更高的正则化控制过拟合并实现了更好的性能。
图 5.13 – SVM 分类模型中准确率与正则化参数 C 的关系
在第十二章《超越机器学习调试的深度学习》,你还将了解深度神经网络模型中的正则化技术。
摘要
在本章中,你学习了提高模型性能和减少偏差与方差的技术。你学习了除了深度学习之外广泛使用的机器学习方法的超参数,这些将在本书的后续章节中介绍,以及帮助你在识别最佳超参数集的 Python 库。你还学习了正则化作为另一种帮助你训练泛化机器学习模型的技术。你还学习了如何通过合成数据生成和异常值检测等方法提高输入训练过程的数据质量。
在下一章中,你将了解机器学习建模中的可解释性和可说明性,以及如何使用相关技术和 Python 工具来识别改进模型的机会。
问题
-
增加更多特征和训练数据点是否会减少模型方差?
-
你能提供一些方法示例,用于结合具有不同置信度类别标签的数据吗?
-
如何通过过采样提高你的监督机器学习模型的泛化能力?
-
DSMOTE 和 Borderline-SMOTE 之间的区别是什么?
-
在超参数优化过程中,你是否需要检查每个模型的每个超参数的每个值的效果?
-
L1 正则化能否消除某些特征对监督模型预测的贡献?
-
如果使用相同的训练数据训练,Lasso 回归和 Ridge 回归模型在相同的测试数据上会产生相同的性能吗?
参考文献
-
Bergstra, James 和 Yoshua Bengio. “随机搜索用于超参数优化.” 机器学习研究杂志 13.2 (2012).
-
Bergstra, James 等. “超参数优化的算法.” 神经信息处理系统进展 24 (2011).
-
Nguyen, Vu. “贝叶斯优化加速超参数调整.” 2019 IEEE 第二届人工智能与知识工程国际会议 (AIKE). IEEE (2019).
-
Li, Lisha 等. “Hyperband:一种基于 bandit 的超参数优化新方法.” 机器学习研究杂志 18.1 (2017): pp. 6765-6816.
-
Falkner, Stefan, Aaron Klein, 和 Frank Hutter. “BOHB:大规模鲁棒且高效的超参数优化.” 机器学习国际会议. PMLR (2018).
-
Ng, Andrew, 斯坦福 CS229:机器学习课程,2018 年秋季。
-
Wong, Sebastien C. 等. “理解数据增强在分类中的应用:何时进行扭曲?.” 2016 国际数字图像计算:技术与应用 (DICTA). IEEE (2016).
-
Mikołajczyk, Agnieszka 和 Michał Grochowski. “数据增强用于改进图像分类问题中的深度学习.” 2018 年国际跨学科博士研讨会(IIPhDW)。IEEE (2018).
-
Shorten, Connor 和 Taghi M. Khoshgoftaar. “关于深度学习图像数据增强的综述.” 大数据杂志 6.1 (2019): pp. 1-48.
-
Taylor, Luke 和 Geoff Nitschke. “通过通用数据增强改进深度学习.” 2018 年 IEEE 计算智能系列研讨会 (2018).
-
Shorten, Connor, Taghi M. Khoshgoftaar 和 Borko Furht. “文本数据增强用于深度学习.” 大数据杂志 8.1 (2021): pp. 1-34.
-
Perez, Luis 和 Jason Wang. “在深度学习图像分类中使用数据增强的有效性.” arXiv 预印本 arXiv:1712.04621 (2017).
-
Ashrapov, Insaf. “用于不均匀分布的表格生成对抗网络.” arXiv 预印本 arXiv:2010.00638 (2020).
-
Xu, Lei 等人. “使用条件生成对抗网络建模表格数据.” 神经信息处理系统进展 32 (2019).
-
Chawla, Nitesh V. 等人. “SMOTE: 生成少数类过采样技术.” 人工智能研究杂志 16 (2002): pp. 321-357.
-
Han, H., Wang, WY., Mao, BH. (2005). “Borderline-SMOTE: 不平衡数据集学习中的新过采样方法.” 载于: 黄德顺,张晓平,黄国宾 (编). 智能计算进展. ICIC 2005. 计算机科学讲座笔记,第 3644 卷. Springer, Berlin, Heidelberg.
-
He, Haibo, Yang Bai, E. A. Garcia 和 Shutao Li. “ADASYN: 用于不平衡学习的自适应合成采样方法.” 2008 年 IEEE 国际神经网络联合会议(IEEE 世界计算智能大会) (2008): pp. 1322-1328,doi: 10.1109/IJCNN.2008.4633969.
-
X. Xiaolong, C. Wen 和 S. Yanfei. “不平衡数据分类的过采样算法,”载于系统工程与电子学杂志,第 30 卷,第 6 期,pp. 1182-1191,2019 年 12 月,doi: 10.21629/JSEE.2019.06.12.
-
Last, Felix, Georgios Douzas 和 Fernando Bacao. “基于 k-means 和 SMOTE 的不平衡学习过采样.” arXiv 预印本 arXiv:1711.00837 (2017).
-
Emmott, Andrew F. 等人. “从真实数据中系统地构建异常检测基准.” ACM SIGKDD 异常检测与描述研讨会论文集. 2013.
-
Emmott, Andrew, 等人. “异常检测问题的元分析.” arXiv 预印本 arXiv:1503.01158 (2015).
-
Liu, Fei Tony, Kai Ming Ting 和 Zhi-Hua Zhou. “隔离森林.” 2008 年第八届 IEEE 国际数据挖掘会议。IEEE (2008).
-
Pevný, Tomáš. “Loda: 轻量级在线异常检测器.” 机器学习 102 (2016): pp. 275-304.
-
Breunig, Markus M. 等人. “LOF: 基于密度的局部异常识别.” 2000 年 ACM SIGMOD 国际数据管理会议论文集 (2000).
-
Kriegel, Hans-Peter,Matthias Schubert,和 Arthur Zimek。 “高维数据中的基于角度的异常值检测。” 《第 14 届 ACM SIGKDD 国际知识发现和数据挖掘会议论文集》(2008 年)。
-
Joo Seuk Kim 和 C. Scott,“鲁棒核密度估计。” 2008 年 IEEE 国际声学、语音和信号处理会议,拉斯维加斯,内华达州,美国(2008 年):第 3381-3384 页,doi: 10.1109/ICASSP.2008.4518376。
-
Schölkopf, Bernhard, et al. “支持向量方法用于新颖性检测。” 《神经信息处理系统进展》12(1999 年)。
-
Weiss, Karl,Taghi M. Khoshgoftaar,和 DingDing Wang。 “迁移学习综述。” 《大数据杂志》3.1(2016 年):第 1-40 页。
-
Tonekaboni, Seyed Ali Madani, et al. “使用过滤迁移学习跨越标签置信度分布进行学习。” 2020 年第 19 届 IEEE 国际机器学习与应用会议(ICMLA)。IEEE(2020 年)。
-
Tibshirani, Robert. “通过 lasso 进行回归收缩和选择。” 《皇家统计学会系列 B(方法论)》58.1(1996 年):第 267-288 页。
-
Hastie, Trevor, et al. 《统计学习的要素:数据挖掘、推理和预测》。第 2 卷。纽约:Springer,2009 年。
-
Zou, Hui,和 Trevor Hastie。 “通过弹性网进行正则化和变量选择。” 《皇家统计学会系列 B(统计方法论)》67.2(2005 年):第 301-320 页。