Python-机器学习模型调试指南-全-

Python 机器学习模型调试指南(全)

原文:annas-archive.org/md5/842997dabd1ace27744af80826d549f0

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

欢迎阅读 使用 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/errata并填写表格。

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

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

分享您的想法

一旦您阅读了《使用 Python 调试机器学习模型》,我们很乐意听到您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。

您的评论对我们和科技社区非常重要,并将帮助我们确保我们提供高质量的内容。

下载本书的免费 PDF 副本

感谢您购买本书!

您喜欢在路上阅读,但无法携带您的印刷书籍到处走吗?

您的电子书购买是否与您选择的设备不兼容?

不要担心,现在,随着每本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。

在任何地方、任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。

优惠不会就此结束,您还可以获得独家折扣、时事通讯和每日免费内容的每日电子邮件。

按照以下简单步骤获取好处:

  1. 扫描下面的二维码或访问以下链接

二维码

packt.link/free-ebook/978-1-80020-858-2

  1. 提交您的购买证明

  2. 就这些了!我们将直接将您的免费 PDF 和其他福利发送到您的邮箱

第一部分:机器学习建模调试

在本书的这一部分,我们将深入探讨机器学习发展的不同方面,这些方面超越了传统的范式。第一章阐明了传统代码调试与机器学习调试专门领域的细微差别,强调机器学习中的挑战超越了单纯的代码错误。下一章提供了一个关于机器学习生命周期的全面概述,突出了模块化在简化并增强模型开发中的作用。最后,我们将强调在追求负责任的人工智能过程中模型调试的重要性,强调其在确保道德、透明和有效的机器学习解决方案中的作用。

本部分包含以下章节:

  • 第一章**,超越代码调试

  • 第二章**,机器学习生命周期

  • 第三章**,向负责任的人工智能调试

第一章:代码调试之外

人工智能AI),像人类智能一样,是一种可用于决策和任务完成的特性和工具。作为人类,我们在做出日常决策、思考我们面临的挑战和问题时使用我们的智能。我们使用我们的大脑和神经系统从周围环境中接收信息,并处理它们以进行决策和反应。

机器学习模型是当今用于解决医疗保健和金融等领域问题的 AI 技术。机器学习模型已在制造设施中的机器人系统中用于包装产品或识别可能损坏的产品。它们被用于我们的智能手机中,用于安全目的识别我们的面部,电子商务公司为我们推荐最合适的产品或电影,甚至用于改善医疗保健和药物开发,将新的更有效的药物推向市场以治疗严重疾病。

在本章中,我们将快速回顾不同类型的机器学习建模。您将了解调试机器学习代码的不同技术和挑战。我们还将讨论为什么调试机器学习建模远不止代码调试。

在本章中,我们将涵盖以下主题:

  • 机器学习速览

  • 机器学习建模的类型

  • 软件开发中的调试

  • 用于建模的数据缺陷

  • 以模型和预测为中心的调试

本章是本书的介绍,旨在为您准备后续将介绍的更高级概念。这将帮助您提高模型,并朝着成为机器学习时代的专家迈进。

技术要求

您可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter01

机器学习速览

构建机器学习模型需要三个基本要素:算法、数据和计算能力(图 1.1)。机器学习算法需要用正确的数据喂养,并使用必要的计算能力进行训练。然后,它可以用于预测它所训练的未见数据:

图 1.1 – 机器学习三角形的三个要素

图 1.1 – 机器学习三角形的三个要素

机器学习应用可以大致分为自动化发现。在自动化类别中,机器学习模型及其围绕其构建的软件和硬件系统的目标是执行人类可能且通常容易但繁琐、重复、无聊或危险的任务。这方面的例子包括在制造线中识别损坏的产品或在高度安全设施入口处识别员工的面孔。有时,尽管这些任务可能很容易,但无法使用人类来完成。例如,对于手机上的面部识别,如果你的手机被盗,你就无法在那里识别试图登录你手机的人不是你,而你的手机应该能够自动完成这项任务。但是,我们无法为这些任务提出一个通用的数学公式,告诉机器在每个情况下应该做什么。因此,机器学习模型学习如何根据数据中识别出的模式来提出其预测,例如,在识别面部方面。

另一方面,在机器学习建模的发现类别中,我们希望模型能够提供关于未知信息的信息和洞察,这些信息对于人类专家或非专家来说可能不容易或完全未被发现,甚至是不可能提取的。例如,为癌症患者发现新药并不是一个可以通过参加几门课程和阅读几本书就能全面了解其所有方面的任务。在这种情况下,机器学习可以帮助我们提出新的见解,以帮助发现新药。

对于发现和自动化,不同类型的机器学习建模可以帮助我们实现目标。我们将在下一节中探讨这一点。

机器学习建模类型

机器学习包含多种建模类型,这些类型可能依赖于输出数据、不同类型的模型输出,以及从预先录制的数据或经验中进行学习。尽管本书中的例子主要关注监督学习,但我们将回顾其他类型的建模,包括无监督学习、自监督学习、半监督学习、强化学习RL)和生成式机器学习,以涵盖机器学习建模的六大主要类别(图 1**.2)。我们还将讨论机器学习建模的技术,并提供与这些类别不平行但相关的代码示例,例如主动学习、迁移学习、集成学习和深度学习:

图 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 中使用断点进行代码调试

图 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及其关联的托管服务GitHubGitLabBitBucket等版本控制系统来管理你的项目。这些工具让你和你的团队成员以及合作者可以在不同的代码分支上工作,而不会相互干扰。它还允许你轻松地回到变化的历史记录中,并找出代码中的变化发生在何时。

如果你还没有使用版本控制系统,不要把它们当作一个你需要开始学习的新复杂工具或编程语言。在使用 Git 时,你需要首先了解一些核心概念和术语,例如commitpushpullmerge。如果你不想或不知道如何使用命令行界面CLI),使用这些功能可能就像在 PyCharm 这样的 IDE 中点击几下那么简单。

我们回顾了一些常用的技术和工具,以帮助你调试代码和高质量的 Python 编程。然而,还有一些更高级的工具建立在模型之上,如 GPT,例如 ChatGPT(openai.com/blog/ChatGPT)和 GitHub Copilot(github.com/features/copilot),你可以使用这些工具来更快地开发代码,提高代码和代码调试工作的质量。我们将在第十四章“*机器学习最新进展的介绍”中讨论一些这些工具。

虽然使用前面的调试技术或最佳实践来避免 Python 代码中的问题可以帮助你拥有低 bug 的代码库,但它并不能防止所有与机器学习模型相关的问题。这本书是关于超越 Python 编程进行机器学习,帮助你识别机器学习模型中的问题并开发高质量模型。

超越 Python 的调试

消除代码问题并不能解决机器学习模型或数据准备和建模管道中可能存在的所有问题。可能存在一些不会产生任何错误消息的问题,例如来自用于建模的数据的问题,以及测试数据和生产数据(即模型最终需要使用的数据)之间的差异。

生产环境与开发环境

开发环境是我们开发模型的地方,比如我们用于开发的计算机或云环境。在这里,我们编写代码、调试代码、处理数据、训练模型并验证它们。但我们在这个阶段所做的工作不会直接影响用户。

生产环境是模型准备供最终用户使用或可能影响他们的地方。例如,一个模型可以在亚马逊平台上用于推荐产品,被发送到银行系统的其他团队进行欺诈检测,甚至被用于医院以帮助临床医生更好地诊断患者的病情。

用于建模的数据缺陷

数据是机器学习建模的核心组件(图 1.1)。通过获取训练和测试机器学习模型所需的数据,使得机器学习在不同行业如医疗保健、金融、汽车、零售和营销等领域的应用成为可能。当数据被输入到机器学习模型中进行训练(即识别最佳模型参数)和测试时,数据中的缺陷可能导致模型出现问题,例如训练性能低(例如,高偏差)、低泛化能力(例如高方差)或社会经济偏差。在这里,我们将讨论在设计机器学习模型时需要考虑的数据缺陷和属性示例。

数据格式和结构

数据在你的代码或管道中的存储、读取和移动方式可能存在问题。你可能需要处理结构化或表格数据,或者非结构化数据,如视频和文本文档。这些数据可以存储在关系型数据库中,如MySQLNoSQL(即非关系型)数据库、数据仓库和数据湖中,甚至可以以不同的文件格式存储在本地,如CSV。无论如何,预期的和现有的文件数据结构和格式需要匹配。例如,如果你的代码期望一个制表符分隔的文件格式,但相应的函数的输入文件却是逗号分隔的,那么所有列可能会被合并在一起。幸运的是,大多数情况下,这类问题会导致代码中的错误。

提供的数据和预期数据之间可能存在不匹配,如果代码没有针对这些不匹配进行防御并且没有足够的信息记录,则不会引起任何错误。例如,想象一个 scikit-learn 的fit函数期望有 100 个特征的训练数据,同时你有 100 个数据点。在这种情况下,如果你的代码将特征放在输入 DataFrame 的行或列中,代码将不会返回任何错误。然后,你的代码需要检查输入 DataFrame 的每一行是否包含所有数据点的单个特征值或单个数据点的特征值。以下图示展示了如何通过交换特征和数据点,例如通过转置 DataFrame 来交换行和列,可能会提供错误的输入文件但不会产生错误。在这个图中,我们为了简化考虑了四列和四行。在这里,F 和 D 分别用作特征和数据点的缩写:

图 1.4 – 简化示例,展示如何在期望四个特征的 scikit-learn fit 函数中错误地使用 DataFrame 的转置

图 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 – 欠拟合和过拟合的示意图

算法和超参数的选择决定了在训练和测试机器学习模型时的复杂程度以及欠拟合或过拟合的可能性。例如,通过选择可以学习非线性模式的模型而不是线性模型,你的模型在训练数据中能够识别更复杂的模式,因此有更高的可能性降低欠拟合。但与此同时,你可能会增加过拟合的可能性,因为训练数据中的一些复杂模式可能无法推广到测试数据(图 1.5)。有一些方法可以评估欠拟合和过拟合,这有助于你开发高性能且可泛化的模型。我们将在未来的章节中讨论这些问题。

模型超参数

一些参数可能会影响机器学习模型的性能,这些参数在训练过程中通常不会自动优化。这些被称为超参数。我们将在未来的章节中通过一些这样的超参数的例子进行说明,例如随机森林模型中的树的数量或神经网络模型中隐藏层的大小。

模型测试和生产中的推理

机器学习建模的最终目标是拥有在生产环境中高度有效的模型。当我们测试模型时,我们正在评估其泛化能力,但我们不能确定它在未见过的数据上的表现。用于训练机器学习模型的数据可能会过时。例如,服装市场趋势的变化可能会使服装推荐模型的预测不可靠。

在这个主题中存在不同的概念,如数据方差、数据漂移和模型漂移,我们将在接下来的几章中涵盖这些内容。

用于改变景观的数据或超参数

当我们使用特定的训练数据和一组超参数训练机器学习模型时,模型参数的值会发生变化,以便它们尽可能地接近定义的目标或损失函数的最优点。实现更好模型的另外两个工具是提供更好的训练数据和选择更好的超参数。每个算法都有提高性能的潜力。仅通过调整模型超参数,你无法开发出最佳模型。同样,仅通过增加数据和保持模型超参数不变,你也无法实现最佳性能。因此,数据和超参数是相辅相成的。在你阅读下一章之前,请记住,仅通过在超参数优化上投入更多的时间和金钱,你并不一定能得到更好的模型。我们将在本书的后面更详细地探讨这一点。

摘要

在本章中,我们回顾了软件开发中调试的重要概念和方法,以及它们与机器学习模型调试的区别。你了解到,在机器学习建模中的调试超出了软件调试的范畴,以及数据和算法,除了代码之外,也可能导致模型存在缺陷或性能低下,以及预测不可靠。你可以从这些理解以及本书中将要学习的工具和技术中受益,以开发可靠的机器学习模型。

在下一章中,你将了解机器学习生命周期的不同组成部分。你还将学习如何通过这些组件模块化机器学习建模,这有助于我们在训练和测试前后识别改进模型的机会。

问题

  1. 你的代码是否有意外的缩进,但不会返回任何错误信息?

  2. 在 Python 中,AttributeErrorNameError之间的区别是什么?

  3. 数据维度如何影响模型性能?

  4. Python 中的traceback消息提供了关于你代码中错误的信息是什么?

  5. 你能解释两个高质量的 Python 编程的最佳实践吗?

  6. 你能解释为什么你可能有不同置信水平的特征或数据点吗?

  7. 你能提供有关如何减少在为给定数据集构建模型时欠拟合或过拟合的建议吗?

  8. 我们是否可能有一个在生产环境中性能显著低于测试环境的模型?

  9. 当我们也可以提高训练数据的质量或数量时,专注于超参数优化是否是一个好主意?

参考文献

  • 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 – 机器学习生命周期

图 2.1 – 机器学习生命周期

当您依赖已经在 Python 中可用的数据集,例如通过 scikit-learnPyTorch,或者一个在公共存储库中准备好的用于建模的数据集时,您不需要担心早期步骤,如数据收集、选择和处理。这些步骤已经为您处理好了。或者如果您只是进行建模练习,不想在生产系统中提供您的模型,您也不需要担心模型部署和监控。但理解所有这些步骤的意义、重要性和好处,有助于您开发或设计一个具有持续改进功能的技术,为用户提供服务。这也有助于您更好地理解作为机器学习开发者的角色,或者找到这个领域的第一份工作或更好的工作。

数据收集

机器学习生命周期的第一步是数据收集。这可能涉及从不同的公共或商业数据库收集数据,将用户数据存储回您的数据库或任何您拥有的数据存储系统,或者甚至使用那些为您处理数据收集和标注的商业实体。如果您依赖免费资源,您可能需要考虑数据在您本地或云存储系统中所占的空间以及您在后续步骤中收集和分析数据所需的时间。但对于付费数据,无论是商业资源中提供的还是数据收集、生成和标注公司生成的,在决定付费之前,您需要评估数据对建模的价值。

数据选择

根据相应项目的目标,我们需要选择用于模型训练和测试所需的数据。例如,你可能可以访问一个或多个医院中癌症患者的相关信息,例如他们的年龄、性别、是否吸烟,如果有的话,他们的遗传信息,如果有的话,他们的 MRI 或 CT 扫描,他们用药的历史,他们对癌症药物的反应,他们是否进行了手术,他们的处方,无论是手写还是 PDF 格式,以及更多。当你想要构建一个使用他们的 CT 扫描来预测患者对治疗反应的机器学习模型时,你需要为每位患者选择与你想使用他们的信息(如年龄、性别和吸烟状况)构建模型时不同的数据。如果你正在构建一个监督学习模型,你还需要选择那些你有输入和输出数据的患者。

注意

在半监督学习模型中,可以将带有和没有输出的数据点结合起来。

为你的模型选择相关数据并不是一项容易的任务,因为将数据区分成对模型目标来说是相关不相关的信息并不一定以这种二进制方式可用。想象一下,你需要从一个化学、生物或物理数据库中提取数据,这可能是一组来自不同较小数据集的数据,论文的补充材料,甚至是从科学文章中来的数据。或者,你可能想要从患者的病历或甚至从经济或社会学调查的书面答案中提取信息。在所有这些例子中,为你的模型分离数据,或从相关数据库中查询相关数据,并不像搜索一个关键词那么简单。每个关键词可能有同义词,无论是普通英语还是技术术语,可能以不同的方式书写,有时相关信息可能存在于数据文件或关系数据库的不同列中。适当的数据选择和查询系统为你提供了提高模型的机会。

你可以受益于文献综述,并在需要时向专家咨询,以扩展你使用的关键词。你可以从已知的数据选择方法中受益,对于你拥有的特定任务,甚至可以许可工具或付费服务来帮助你提取更多与你的目标相关的数据。还有高级的自然语言处理技术可以帮助你在查询系统中从文本中提取信息。我们将在第十三章高级深度学习技术,和第十四章机器学习最新进展介绍中讨论这些内容。

数据探索

在这个阶段,您可以选择数据并探索数据的数量、质量、稀疏性和格式。如果您在监督学习中具有分类输出,您可以找到每个类别的数据点数量,特征分布,输出变量的置信度(如果有的话),以及从数据选择阶段获取的数据的其他特征。这个过程有助于您识别需要修复的数据问题,这些问题将在生命周期中的下一个步骤数据整理中解决,或者通过修改您的数据选择过程来提高数据的机会。

数据整理

您的数据需要经过结构化和丰富过程,并在必要时进行转换和清理。所有这些方面都是数据整理的一部分。

结构化

原始数据可能以不同的格式和大小出现。您可能可以访问手写笔记、Excel 表格,甚至包含需要提取并放入正确格式以供进一步分析和用于建模的信息的表格图像。这个过程并不是将所有数据转换成类似表格的格式。在数据结构化的过程中,您需要小心信息丢失。例如,您可能有一些按特定顺序排列的特征,如基于时间、日期或通过设备传入的信息序列。

丰富

在对数据进行结构和格式化之后,您需要评估您是否拥有构建该周期机器学习模型所需的数据。在继续整理过程之前,您可能发现添加或生成新数据的机会。例如,您可能会发现,在用于识别制造管道中产品图像裂缝的数据中,只有 50 张标签为裂缝产品图像的图像,而总共有 10,000 张图像。您可能能够找到其他裂缝产品的图像,或者您可以使用称为数据增强的过程生成新的图像。

数据增强

数据增强是一系列通过使用我们手头上的原始数据集,计算性地生成新数据点的技术。例如,如果您旋转您的肖像,或者通过向图像添加高斯噪声来改变图像的质量,新的图像仍然会显示您的脸。但这可能有助于使您的模型更具泛化能力。我们将在第五章 提高机器学习模型性能中讨论不同的数据增强技术。

数据转换

数据集的特征和输出可能是不同类型的变量,包括以下内容:

  • 定量数值

    • 离散:例如,一个社区中的房屋数量

    • 连续:例如,患者的年龄或体重

  • 定性分类

    • 名义(无顺序):例如,不同颜色的汽车

    • 有序(有序的定性变量):例如,学生的成绩,如 A、B、C 或 D

当我们训练一个机器学习模型时,模型需要在优化过程的每次迭代中使用数值来计算损失函数。因此,我们需要将分类变量转换为数值替代品。有多种特征编码技术,其中三种是独热编码、目标编码(Micci-Barreca,2001)和标签编码。一个包含年龄、性别、组和目标等四个列和七个行(七个示例数据点)的示例矩阵的独热、标签和目标编码计算如图 2.2 所示。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)。另一方面,当我们使用具有缺失值特征和低缺失值或无缺失值的高相关特征之间的线性模型时,我们假设它们之间存在线性关系。或者,我们可以在特征之间构建更复杂的模型来进行缺失值计算。所有这些方法都有其优点和局限性,您需要根据特征值的分布、具有缺失特征值的数据点的比例、特征之间的相关范围、低缺失值或无缺失值特征的存在以及其他相关因素,选择最适合您数据集的方法。

我们在图 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 – 直方图和箱线图中的异常值

图 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-learnStandardScaler类后,变量的值将围绕零中心,标准差为 1。

其中一些技术,例如鲁棒缩放,可以使用scikit-learnRobustScaler类实现,不太可能受到异常值的影响(表 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]))

现在,让我们使用isomappca,它们都可在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-learnPyTorch 和 TensorFlow 进行神经网络建模,训练和验证或测试模型的过程包括以下三个主要步骤:

  1. 初始化模型:初始化模型是关于指定方法、其超参数以及用于建模的随机状态。

  2. 训练模型:在模型训练中,步骤 1 中初始化的模型用于训练数据以训练机器学习模型。

  3. 推理、分配和性能评估:在这个步骤中,训练好的模型可以用于监督学习中的推理(例如,预测输出)或,例如,将新数据点分配给无监督学习中已识别的聚类。在监督学习中,您可以使用这些预测来评估模型性能。

这些步骤对于监督学习和无监督学习模型都是相似的。在 步骤 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-learnPyTorch或 TensorFlow 的更改,这个测试确保你的代码运行,并检查这些更改对模型性能和预测的影响。

  • 安全测试:安全测试是工业级编程和建模的重要部分。你需要确保你的代码和依赖项没有漏洞。然而,你需要设计一个测试来应对高级对抗性攻击。我们将在第三章中讨论,向负责任 AI 调试

  • 负责任的 AI 测试:我们需要设计测试来评估负责任 AI 的重要因素,例如透明度、隐私和公平性。我们将在下一章中讨论负责任 AI 的一些重要方面。

虽然这些测试需要在这个阶段设计,但类似的测试可以作为生命周期之前步骤的一部分进行整合。例如,你可以在生命周期的所有步骤中进行安全测试,特别是如果你正在使用不同的工具或代码库。可能还有其他测试,例如检查模型的内存大小和预测运行时间,或者生产中的数据格式和结构以及部署的模型中预期的数据是否相同。

模型部署和监控

如果你刚开始接触部署,你可能认为它是如何为你的模型最终用户开发前端、移动应用程序或 API。但在这本书中,我们不想讨论这个话题。在这里和未来的章节中,我们想要涵盖部署的两个重要方面:提供生产环境中模型所需的操作以及将模型集成到应该为用户带来利益的过程中。

当你部署你的模型时,你的代码应该在指定的环境中正常运行,并且能够访问所需的硬件,例如 GPU,并且用户的数据需要以正确的格式可用,以便你的模型能够工作。我们在生命周期测试阶段讨论的一些测试确保你的模型在生产环境中按预期运行。

当我们谈论在生产环境中提供模型时,它要么在幕后被用于用户的利益,例如 Netflix 和 Amazon Prime 使用他们的机器学习模型为你推荐电影,要么被用户直接作为独立进程或作为更大系统的一部分使用,例如当机器学习模型在医院中用于帮助临床医生进行疾病诊断。这两种不同用例的考虑因素并不相同。如果你想将模型部署在医院中供临床医生直接使用,你需要考虑设置适当的生产环境所需的全部困难和规划,以及所有软件依赖项。你还需要确保他们的本地系统满足必要的硬件要求。或者,你可以通过 Web 应用程序提供你的模型。在这种情况下,你需要确保上传到你的数据库中的数据的保密性和安全性。

当涉及到收集必要的信息和反馈时,模型辅导是机器学习生命周期中的一个关键部分。这些反馈可以用来改进或纠正用于建模的数据,或者改进模型的训练和测试。监控机器学习模型有助于我们确保生产中的模型能够根据预期提供预测。可能导致机器学习模型预测不可靠的三个问题是数据方差、数据漂移和概念漂移。数据漂移和概念漂移被认为是两种不同类型的模型漂移。模型漂移涉及数据中不同类型的改变,无论是特征还是输出变量,这些改变使得模型对新用户数据的预测变得无关或无效。

我们将在本书的后续章节中更详细地讨论模型部署和监控,以及机器学习生命周期中的工程方面,例如第十章版本控制和可重复的机器学习建模

摘要

在本章中,我们讨论了机器学习生命周期的不同组成部分,从数据收集和选择到模型训练和评估,最后到模型部署和监控。我们还展示了如何模块化机器学习生命周期的数据处理、建模和部署方面,有助于识别改进机器学习模型的机会。

在下一章中,你将了解关于改进机器学习模型性能之外的概念,例如无偏建模和公平性,以及为了实现负责任的 AI 系统而进行的问责制和透明度。

问题

  1. 你能提供两个数据清洗过程的例子吗?

  2. 你能解释一下 one-hot 编码和标签编码方法之间的区别吗?

  3. 你如何使用分布的分位数来检测其异常值?

  4. 当考虑到在医生本地部署模型与在银行系统中部署聊天机器人背后的模型之间的差异时,你脑海中浮现的是什么?

参考文献

  • 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 库并希望永远都不会发生任何事情那样简单。所以,实践你所学的,并为你的团队提供将所学知识应用到实践中的机会。

  • 定义责任和问责制:照顾机器学习生命周期的所有方面以构建技术和处理本章中讨论的所有责任主题,并不是一个人的工作。话虽如此,团队和组织中个人的责任和问责制需要明确界定,以减少工作冗余并确保没有遗漏任何事项。

  • 使用反馈收集系统:我们需要设计简单易用且最好是自动化的系统来收集反馈并在机器学习生命周期中采取行动。这种反馈将帮助负责生命周期每个步骤的开发者,并最终导致在生产中推出更好的模型。

  • 使用质量控制流程:我们需要定量和预定义的方法和协议来评估训练后或生产中的机器学习模型的质量,或者评估机器学习生命周期每个阶段输出的处理数据。拥有定义和记录的质量控制流程有助于我们实现可扩展的系统,以便更快、更一致地进行质量评估。然而,这些流程可以根据新的标准和与数据和相应的机器学习模型相关的风险进行修订和调整。

现在我们已经了解了负责任机器学习建模的重要性,并回顾了实现它的关键因素和技术,我们准备进入本书的下一部分,并深入了解开发可靠、高性能和公平的机器学习模型和技术的技术细节。

摘要

在本章中,我们讨论了负责任人工智能的不同要素,例如数据隐私、机器学习系统的安全性、不同类型的攻击以及设计针对它们的防御系统、机器学习时代的透明度和问责制,以及如何在实际中利用数据和管理模型来开发可靠和负责任的模式。

本章和前两章,构成了本书的第一部分,介绍了机器学习建模和模型调试的重要概念。第二部分包括如何改进机器学习模型的主题。

在下一章中,你将了解检测机器学习模型问题以及提高此类模型性能和泛化性的方法。我们将涵盖使用真实生活示例进行模型调试的统计、数学和可视化技术,以帮助您快速实施这些方法,以便您可以调查和改进您的模型。

问题

  1. 你能解释两种类型的数据偏差吗?

  2. 白盒攻击和黑盒攻击有什么区别?

  3. 你能解释一下数据与算法加密如何帮助保护你系统的隐私和安全吗?

  4. 你能解释差分隐私和联邦机器学习之间的区别吗?

  5. 透明度如何帮助你在增加你的机器学习模型用户数量方面?

参考文献

  • 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 – 二分类的混淆矩阵

图 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 – 对于一个将所有预测返回为负数的模型,在不同真实正分数下的常见分类指标值

图 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 曲线的示意图

图 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.4 – 1,000 个随机二元预测在 1,000 个数据点上的性能分布(第一部分)

图 4.5 – 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
决定系数(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
调整后的互信息 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 曲线的两个任意模型

图 4.6 – 比较具有相同 ROC-AUC 值和不同 ROC 曲线的两个任意模型

仅比较 ROC-AUC 值可能会导致判断这些模型的等效性。然而,它们的 ROC 曲线不同,在大多数应用中,红色曲线比蓝色曲线更受欢迎,因为它在低假阳性率(如FPR1)的情况下会产生更高的真正阳性率。

可视化可能会产生误导

使用适合您结果的正确可视化技术是分析模型结果和报告其性能的关键。没有考虑模型目标就绘制数据可能会导致误导。例如,你可能会看到时间序列图,如图 4**.7所示,在许多博客文章中,预测值和真实值随时间叠加。对于此类时间序列模型,我们希望每个时间点的预测值和真实值尽可能接近。尽管图 4**.7中的线条似乎彼此一致,但与蓝色显示的真实值相比,橙色显示的预测值存在两个时间单位的延迟。这种预测延迟在许多应用(如股票价格预测)中可能产生严重后果:

图 4.7 – 将两个时间序列图叠加在一起是误导性的 – 橙色和蓝色曲线代表任意时间序列数据的预测值和真实值

图 4.7 – 将两个时间序列图叠加在一起是误导性的 – 橙色和蓝色曲线代表任意时间序列数据的预测值和真实值

不要随意解释你的图表

每个可视化都有其假设和正确的解释方式。例如,如果你想比较二维图中数据点的数值,你需要注意x轴和y轴的单位。或者当我们使用t 分布随机邻域嵌入t-SNE),这是一种旨在帮助在低维空间中可视化高维数据的降维方法时,我们必须提醒自己,数据点之间的大距离和每个组的密度并不代表原始高维空间中的距离和密度(图 4**.8):

图 4.8 – 示意 t-SNE 折线图显示(A)具有不同距离的三组数据点以及(B)在二维空间中具有不同密度的两组数据点

图 4.8 – 示意 t-SNE 折线图显示(A)具有不同距离的三组数据点以及(B)在二维空间中具有不同密度的两组数据点

你可以使用不同的性能指标来评估你的模型是否训练良好并且可以推广到新的数据点,这是本章的下一个主题。

偏差和方差诊断

我们的目标是在训练集(即低偏差模型)中实现高性能或低误差,同时保持对新数据点的性能或误差保持在高水平(即低方差模型)。由于我们没有访问未见过的新的数据点,我们必须使用验证集和测试集来评估我们模型的方差或泛化能力。模型复杂性是确定机器学习模型偏差和方差的重要因素之一。通过增加复杂性,我们让模型在训练数据中学习更复杂的模式,这可能会减少训练误差或模型偏差(图 4**.9):

图 4.9 – 对于(A)高偏差、(B)高方差以及(C, D)两种低偏差和低方差模型的情况,误差与模型复杂度的关系

图 4.9 – 对于(A)高偏差、(B)高方差以及(C, D)两种低偏差和低方差模型的情况,误差与模型复杂度的关系

这种误差的降低有助于构建更好的模型,即使是对于新的数据点。然而,这种趋势在某个点之后会发生变化,更高的复杂性可能导致过拟合或验证集和测试集相对于训练集有更高的方差和更低的性能(图 4**.9)。评估与模型复杂性或数据集大小等参数相关的偏差和方差可以帮助我们识别在训练、验证和测试集中提高模型性能的机会。

图 4**.9 展示了训练和验证集中模型误差与模型复杂度之间可能的四种依赖关系。尽管验证误差通常高于训练误差,但你可能会因为训练和验证集中存在的数据点而经历较低的验证误差。例如,一个多类分类器可能因为更擅长预测验证集中数据点中占多数的类别而具有较低的验证误差。在这种情况下,在报告训练和验证数据集的性能评估并决定选择哪个模型用于生产之前,你需要调查训练和验证集中数据点的分布。

让我们练习一下偏差和方差分析。你可以在 scikit-learn 的乳腺癌数据集上找到使用不同最大深度的随机森林模型训练的结果(图 4.10)。scikit-learn 的乳腺癌数据用于训练和验证模型性能,其中 30% 的数据随机分离作为验证集,其余的保留为训练集。通过增加随机森林模型的最大深度,训练集的对数损失错误减少,而作为模型性能指标的平衡准确率增加。验证错误也减少到最大深度为三,之后开始增加,这是过拟合的迹象。尽管在最大深度为三之后错误减少,但通过将最大深度增加到四和五,平衡准确率仍然可以增加。原因是基于预测概率的对数损失定义与基于预测标签的平衡准确率定义之间的差异:

图 4.10 – 从 scikit-learn 的乳腺癌数据集中分离的训练集和验证集的平衡准确率(顶部)和对数损失(底部)

图 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 – 在一个数据集中分离验证集和训练集的技术

图 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 代码示例来帮助你练习,并能够快速在你的项目中使用它们。

在下一章中,我们将回顾提高机器学习模型泛化性的技术,例如向训练数据添加合成数据、去除数据不一致性和正则化方法。

问题

  1. 一个分类器被设计用来确定诊所的患者在第一轮测试后是否需要继续进行诊断步骤。哪种分类度量会更合适或不那么合适?为什么?

  2. 一个分类器被设计来评估不同投资选项的投资风险,针对特定金额,并将被用来向您的客户提供投资机会。哪种分类度量会更合适或不那么合适?为什么?

  3. 如果两个二元分类模型在相同的验证集上的计算 ROC-AUC 值相同,这意味着模型是相同的吗?

  4. 如果模型 A 在相同的测试集上比模型 B 具有更低的 log-loss,这总是意味着模型 A 的 MCC 也高于模型 B 吗?

  5. 如果模型 A 在相同数量的数据点上比模型 B 具有更高的 R²值,我们能否声称模型 A 比模型 B 更好?特征数量是如何影响我们对两个模型之间比较的?

  6. 如果模型 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: 在l1l2elasticnetNone之间选择正则化

  • class_weight: 将权重与类别关联

  • l1_ratio: 弹性网络混合参数

|

K-最近邻sklearn.neighbors.KNeighborsClassifier()
  • n_neighbors: 邻居的数量

  • weights: 在uniformdistance之间选择,以平等使用邻居或根据它们的距离分配权重

|

支持向量机SVM)分类器或回归器sklearn.svm.SVC()``sklearn.svm.SVR()
  • C: 使用l2惩罚的正则化强度的倒数

  • kernel: 包含预构建核的 SVM 核,包括linearpolyrbfsigmoidprecomputed

  • degree(多项式度):多项式核函数(poly)的度

  • class_weight(仅用于分类):将权重与类别关联

|

随机森林分类器或回归器sklearn.ensemble.RandomForestClassifier()``sklearn.ensemble.RandomForestRegressor()
  • n_estimators: 森林中的树的数量

  • max_depth: 树的最大深度

  • class_weight: 将权重与类别关联

  • min_samples_split: 成为叶节点所需的最小样本数

|

XGBoost 分类器或回归器xgboost.XGBClassifier()``xgboost.XGBRegressor()
  • boostergbtreegblineardart

  • 对于树增强器:

    • eta: 步长缩减以防止过拟合。

    • max_depth: 树的最大深度

    • min_child_weight: 继续分区所需的最小数据点权重总和

    • lambda: L2 正则化因子

    • alpha: L1 正则化因子

|

LightGBM 分类器或回归器Lightgbm.LGBMClassifier()``Lightgbm.LGBMRegressor()
  • boosting_type (gbdtdartrf)

  • 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: 包含euclideanl1l2manhattancosineprecomputed等预建度量的距离度量

  • linkage: 包含wardcompleteaveragesingle等预建方法的链接标准

|

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_splitmax_depth值。您可以使用评分参数指定不同的性能指标,评分参数是sklearn.model_selection.GridSearchCV()的一个参数,根据您在上一章中学到的知识。在这种情况下,max_depth10min_samples_split7的组合被确定为最佳超参数集,使用分层 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_depth15min_samples_split7的组合被确定为最佳超参数集。比较网格搜索和随机搜索的结果,我们可以得出结论,具有不同max_depth值的模型可能在这个特定情况下对使用scikit-learn的数字数据集进行随机森林建模的 CV 准确率产生相似的结果。

贝叶斯搜索

在贝叶斯优化中,与随机选择超参数组合而不检查先前组合集的值不同,每个超参数组合集的每个组合都是基于先前测试的超参数集的历史记录在迭代中选择的。这个过程有助于降低与网格搜索相比的计算成本,但它并不总是优于随机搜索。我们在这里想使用 Ray Tune (ray.tune)来实现这种方法。您可以在教程页面上了解更多关于 Ray Tune 中可用的不同功能,例如记录 tune 运行如何停止和恢复分析 tune 实验结果在云中部署 tunedocs.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 生成合成数据的示意图

图 5.1 – 使用 SMOTE、Borderline-SMOTE 和 ADASYN 生成合成数据的示意图

使用 SMOTE 生成任何合成数据点的步骤可以总结如下:

  1. 从少数类中随机选择一个数据点。

  2. 确定该数据点的 K-最近邻。

  3. 随机选择一个邻居。

  4. 在特征空间中两个数据点之间随机选择一个点生成合成数据点。

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 及其替代方法的示例数据集,包含两个特征(即维度),通过合成生成

图 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 所示数据集的可视化

图 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 所示数据集的可视化

图 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 所示数据集的可视化

图 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 年)

图 5.6 – k-means SMOTE 的四个主要步骤的示意图(Last 等人,2017 年)

这里是数据生成 k-means SMOTE 方法中的步骤,您可以通过kmeans-smote Python 库使用它们:

  1. 根据原始数据确定决策边界。

  2. 使用 k-means 聚类将数据点聚类到 k 个簇中。

  3. 对于不平衡率IR)大于不平衡率阈值IRT)的簇使用 SMOTE 进行过采样。

  4. 重复决策边界识别过程。(注意: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 = 10n_estimator = 100 的结果边界。

图 5.7 – 使用不同估计器数量的 iForest 的决策边界

图 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 – 在训练机器学习模型时使用不同质量或相关性的目标问题数据点的技术

图 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 范数正则化在二维特征空间中控制过拟合的示意图

图 5.9 – L1 和 L2 范数正则化在二维特征空间中控制过拟合的示意图

在 L1 正则化的参数约束区域中,角落会导致一些参数被消除,或者使它们的相关权重变为零。然而,L2 正则化约束参数区域的凸性只会通过降低权重来降低参数的影响。这种差异通常会导致 L1 正则化对异常值的鲁棒性更高。

带有 L1 正则化和 L2 正则化的线性分类模型分别称为LassoRidge回归(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 的关系

图 5.10 – Lasso 模型中准确率与正则化参数 alpha 的关系

如果我们在岭回归模型中评估不同alpha值的效果,使用 L2 正则化,我们可以看到随着正则化强度的增加,性能会提高(参见图 5.11)。

图 5.11 – 岭回归模型中准确率与正则化参数 alpha 的关系

图 5.11 – 岭回归模型中准确率与正则化参数 alpha 的关系

这两种方法的替代方案是 Elastic-Net,它结合了 L1 和 L2 正则化的效果。在这种情况下,alpha对模型性能的影响趋势与 Lasso 更相似;然而,与仅依赖 L1 正则化的 Lasso 相比,准确率值的范围更窄(参见图 5.12)。

图 5.12 – Elastic-Net 模型中准确率与正则化参数 alpha 的关系

图 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 的关系

图 5.13 – SVM 分类模型中准确率与正则化参数 C 的关系

第十二章《超越机器学习调试的深度学习》,你还将了解深度神经网络模型中的正则化技术。

摘要

在本章中,你学习了提高模型性能和减少偏差与方差的技术。你学习了除了深度学习之外广泛使用的机器学习方法的超参数,这些将在本书的后续章节中介绍,以及帮助你在识别最佳超参数集的 Python 库。你还学习了正则化作为另一种帮助你训练泛化机器学习模型的技术。你还学习了如何通过合成数据生成和异常值检测等方法提高输入训练过程的数据质量。

在下一章中,你将了解机器学习建模中的可解释性和可说明性,以及如何使用相关技术和 Python 工具来识别改进模型的机会。

问题

  1. 增加更多特征和训练数据点是否会减少模型方差?

  2. 你能提供一些方法示例,用于结合具有不同置信度类别标签的数据吗?

  3. 如何通过过采样提高你的监督机器学习模型的泛化能力?

  4. DSMOTE 和 Borderline-SMOTE 之间的区别是什么?

  5. 在超参数优化过程中,你是否需要检查每个模型的每个超参数的每个值的效果?

  6. L1 正则化能否消除某些特征对监督模型预测的贡献?

  7. 如果使用相同的训练数据训练,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 页。

第六章:机器学习建模中的可解释性和可解释性

我们使用或开发的绝大多数机器学习模型都是复杂的,需要使用可解释性技术来识别改进它们性能、减少偏差和增加可靠性的机会。

在本章中,我们将探讨以下主题:

  • 可解释性机器学习与黑盒机器学习

  • 机器学习中的可解释性方法

  • 在 Python 中练习机器学习可解释性

  • 审视为什么可解释性并不足够

到本章结束时,您将了解机器学习建模中可解释性的重要性,并练习使用 Python 中的某些可解释性技术。

技术要求

在本章中,以下要求应予以考虑,因为它们有助于您更好地理解所提到的概念,在您的项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • matplotlib >= 3.7.1

您可以在 GitHub 上找到本章的代码文件:github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter06

可解释性机器学习与黑盒机器学习

如线性回归这样的可解释和简单模型使得评估改进它们的可能性、发现它们的问题(如需要检测和消除的偏差)以及建立对使用此类模型的信任变得容易。然而,为了实现更高的性能,我们通常不会止步于这些简单模型,而是依赖于复杂或所谓的黑盒模型。在本节中,我们将回顾一些可解释模型,然后介绍您可以用来解释您的黑盒模型的技术。

可解释的机器学习模型

线性模型,如线性回归和逻辑回归、浅层决策树和朴素贝叶斯分类器,是简单且可解释的方法的例子(图 6.1)。我们可以轻松地提取这些模型在预测输出中对特征贡献的部分,并识别改进它们性能的机会,例如通过添加或删除特征或改变特征归一化。我们还可以轻松地识别模型中是否存在偏差——例如,对于特定的种族或性别群体。然而,这些模型非常简单,能够访问成千上万或数百万样本的大型数据集使我们能够训练高性能但复杂的模型:

图 6.1 – 可解释分类方法的示例

图 6.1 – 可解释分类方法的示例

复杂模型,例如具有许多深度决策树或深度神经网络的随机森林模型,虽然它们几乎像黑盒系统一样工作,但有助于我们实现更高的性能。为了能够理解这些模型并解释它们是如何得出预测的,以及建立对其有用性的信任,我们可以使用机器学习可解释性技术。

复杂模型的可解释性

可解释性技术就像复杂机器学习模型和用户之间的桥梁。它们应该提供忠实于模型工作方式的解释。另一方面,它们应该提供对用户有用且易于理解的解释。这些解释可用于识别改进模型性能的机会、减少模型对特征值变化敏感性的影响、提高模型训练中的数据效率、帮助在模型中进行适当的推理、避免虚假相关性,并帮助实现公平性(Weber 等人,2022 年;图 6.2):

图 6.2 – 使用可解释性对机器学习模型的影响

图 6.2 – 使用可解释性对机器学习模型的影响

现在你已经更好地理解了可解释性在机器学习建模中的重要性,我们准备深入了解可解释性技术的细节。

机器学习中的可解释性方法

在使用或开发机器学习建模的可解释性技术时,我们需要牢记以下考虑因素(Ribeiro 等人,2016 年):

  • 可解释性:解释需要用户能够理解。机器学习解释的主要目标之一是使复杂模型对用户可理解,并在可能的情况下提供可操作的信息。

  • 局部忠实度(忠实性):捕捉模型的复杂性,以便它们完全忠实并满足全局忠实度标准,并非所有技术都能实现。然而,解释至少应该对模型局部忠实。换句话说,解释需要恰当地解释模型在研究数据点的邻近区域是如何表现的。

  • 模型无关性:尽管有一些技术是为特定的机器学习方法设计的,例如随机森林,但它们应该对使用不同超参数或针对不同数据集构建的模型保持无关。可解释性技术需要将模型视为黑盒,并为模型提供全局或局部的解释。

可解释性技术可以分为局部可解释性全局可解释性方法。局部可解释性方法旨在满足之前列出的标准,而全局可解释性技术试图超越局部可解释性,并为模型提供全局解释。

局部可解释性技术

局部可解释性帮助我们理解模型在特征空间中接近数据点的行为。尽管这些模型满足局部保真度标准,但被识别为局部重要的特征可能不是全局重要的,反之亦然(Ribeiro 等人,2016)。这意味着我们无法轻易地从全局解释推断出局部解释,反之亦然。在本节中,我们将讨论五种局部解释技术:

  • 特征重要性

  • 反事实

  • 基于样本的可解释性

  • 基于规则的解释性

  • 显著性图

之后,我们还将介绍一些全局可解释性技术。

特征重要性

局部可解释性的主要方法之一是解释每个特征在预测目标数据点在邻域中的结果时的局部贡献。这类方法的广泛使用例子包括 SHapley Additive exPlanations(SHAP)(Lundberg 等人,2017)和 Local Interpretable Model-agnostic Explanations(LIME)(Ribeiro 等人,2016)。让我们简要讨论这两种方法背后的理论,并在 Python 中实践它们。

使用 SHAP 的局部解释

SHAP 是由 Scott Lundberg 和 Su-In Lee(Lundberg 和 Lee,2017)引入的 Python 框架。这个框架的想法是基于使用 Shapely 值,这是一个以美国博弈论家、诺贝尔奖获得者 Lloyd Shapley 命名的已知概念(Winter,2022)。SHAP 可以确定每个特征对模型预测的贡献。由于特征在确定分类模型的决策边界并最终影响模型预测方面是协同工作的,SHAP 尝试首先识别每个特征的边际贡献,然后提供 Shapely 值作为每个特征在整个特征集合作下对模型预测贡献的估计。从理论角度来看,这些边际贡献可以通过单独移除特征以及在不同的组合中移除特征来计算,计算每个特征集移除的效果,然后对贡献进行归一化。由于可能的组合数量可能呈指数增长到数十亿,即使对于具有 40 个特征的模型,这个过程也不能对所有可能的特征组合重复进行。相反,这个过程只使用有限次数来得到 Shapely 值的近似。此外,由于在大多数机器学习模型中无法移除特征,特征值要么由随机分布中的替代值替换,要么由每个特征的背景集合中的有意义且可能的值替换。我们不想深入探讨这个过程的细节,但将在下一节中练习使用这种方法。

使用 LIME 的局部解释

LIME 是 SHAP 的替代品,用于局部可解释性,它以模型无关的方式通过局部近似可解释模型来解释任何分类器或回归器的预测(图 6**.3;Ribeiro 等人,2016):

图 6.3 – LIME 中局部可解释建模的示意图

图 6.3 – LIME 中局部可解释建模的示意图

该技术的某些优点,如 Ribeiro 等人(2016)的原论文中提到的,包括以下内容:

  • 理论和提供的解释直观且易于理解

  • 提供稀疏解释以增加可解释性

  • 与不同类型的结构化和非结构化数据一起工作,例如文本和图像

反事实

反事实示例或解释有助于我们识别在实例中需要改变什么才能改变分类模型的输出。这些反事实可以帮助在许多应用中识别可操作路径,例如金融、零售、营销、招聘和医疗保健。一个例子是向银行客户建议他们如何改变其贷款申请被拒绝的情况(Guidotti, 2022)。反事实还可以帮助识别模型中的偏差,这有助于我们提高模型性能或消除模型中的公平性问题。在生成和使用反事实解释时,我们需要考虑以下因素(Guidotti, 2022):

  • 有效性:只有当反事实示例的分类结果与原始样本不同时,反事实示例才是有效的。

  • 相似性:反事实示例应尽可能与原始数据点相似。

  • 多样性:尽管反事实示例应与它们所派生的原始样本相似,但它们之间需要具有多样性,以提供不同的选项(即不同的可能特征变化)。

  • 可操作性:并非所有特征值的变化都具有可操作性。由反事实方法建议的反事实的可操作性是实际受益的重要因素。

  • 合理性:反事实示例的特征值应该是合理的。反事实的合理性增加了从它们中推导出解释的信任度。

我们还必须注意,反事实解释器需要高效且足够快地生成反事实,并且在生成与相似数据点相关的反事实时保持稳定(Guidotti, 2022)。

基于样本的可解释性

另一种解释性的方法是依靠真实或合成数据点的特征值和结果来帮助局部模型解释性。在这个解释性技术类别中,我们的目标是找出哪些样本被错误分类,以及哪些特征集导致错误分类的概率增加,以帮助我们解释我们的模型。我们还可以评估哪些训练数据点导致决策边界的改变,以便我们可以预测测试或生产数据点的输出。有一些统计方法,如影响函数(Koh 和 Liang 2017),这是一种评估样本对模型参数影响的经典方法,我们可以用它来识别样本对模型决策过程的贡献。

基于规则的解释性

基于规则的解释方法,如锚点解释,旨在找出导致高概率得到相同输出的特征值条件(Ribeiro 等,2018)。例如,在预测数据集中个人的薪水低于或等于 50k 或高于 50k 的情况下,“教育程度≤高中导致薪水≤50k”可以被视为基于规则的解释中的一个规则。这些解释需要局部忠实。

显著性图

显著性图的目标是解释哪些特征对数据点的预测输出贡献更多或更少。这些方法通常用于在图像数据上训练的机器学习或深度学习模型(Simonyan 等,2013)。例如,我们可以使用显著性图来确定分类模型是否使用背景森林来识别它是一张熊的图片而不是泰迪熊,或者是否使用熊的身体部件来识别它。

全局解释

尽管为机器学习模型实现可靠的全球解释很困难,但它可以增加对它们的信任(Ribeiro 等,2016)。在开发和部署机器学习模型时,性能并不是建立信任的唯一方面。虽然局部解释在调查单个样本和提供可操作信息方面非常有帮助,但可能不足以建立这种信任。在这里,我们将讨论三种超越局部解释的方法,包括收集局部解释、知识蒸馏和反事实的摘要。

收集局部解释

子模可加选择 LIMESP-LIME)是一种全局解释技术,它使用 LIME 的局部解释来提供一个模型行为的全局视角(Riberio 等,2016)。由于可能无法使用所有数据点的局部解释,SP-LIME 选择了一组代表性的多样化样本,这些样本能够代表模型的全局行为。

知识蒸馏

知识蒸馏的思想是使用简单的可解释模型(如决策树)来近似复杂模型的行为,这一概念最初是为神经网络模型提出的(Hinton 等人,2015 年;Frosst 和 Hinton,2017 年)。换句话说,我们的目标是构建简单的模型,如决策树,以近似给定样本集的复杂模型的预测。

反事实的摘要

我们可以使用为多个数据点生成的反事实摘要(包括正确和错误的预测结果)来了解特征在输出预测中的贡献以及预测对特征扰动的敏感性。我们将在本章后面练习使用反事实,你将看到并非所有反事实都是可接受的,并且它们需要根据特征及其值的含义来选择。

在 Python 中实践机器学习可解释性

有几个 Python 库你可以用来提取你的机器学习模型的局部和全局解释(表 6.1)。在这里,我们想练习一些专注于局部模型可解释性的库:

导入和安装库名称 URL
SHAP Shap pypi.org/project/shap/
LIME Lime pypi.org/project/lime/
Shapash shapash pypi.org/project/shapash/
ELI5 eli5 pypi.org/project/eli5/
解释仪表板 explainer dashboard pypi.org/project/explainerdashboard/
Dalex dalex pypi.org/project/dalex/
OmniXAI omnixai pypi.org/project/omnixai/
CARLA carla carla-counterfactual-and-recourse-library.readthedocs.io/en/latest/
Diverse Counterfactual Explanations (DiCE) dice-ml pypi.org/project/dice-ml/
机器学习库扩展 mlxtend pypi.org/project/mlxtend/
锚点 anchor github.com/marcotcr/anchor

表 6.1 – 具有机器学习模型可解释性功能的 Python 库或存储库

首先,我们将使用 SHAP 进行练习,这是一种广泛用于机器学习可解释性的技术。

SHAP 中的解释

我们将首先探讨使用 SHAP 进行局部解释,随后再讨论全局解释。

局部解释

在本节中,我们将使用 SHAP 从我们的机器学习模型中提取特征重要性。我们将使用加州大学欧文分校UCI)的成年人数据集来预测 90 年代人们是否年收入超过 50k;这也可以作为 SHAP 库的一部分的成年人收入数据集。您可以在archive.ics.uci.edu/ml/datasets/adult上阅读有关特征定义和其他有关此数据集的信息。

首先,在使用任何可解释性方法之前,我们需要使用此数据集构建一个监督机器学习模型。我们将使用XGBoost作为表格数据的高性能机器学习方法来练习 SHAP:

# loading UCI adult income dataset# classification task to predict if people made over $50k in the 90s or not
X,y = shap.datasets.adult()
# split the data to train and test sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size = 0.3, random_state=10)
# initializing a XGboost model
xgb_model = xgboost.XGBClassifier(random_state=42)
# fitting the XGboost model with training data
xgb_model.fit(X_train, y_train)
# generating predictions for the test set
y_pred = xgb_model.predict(X_test)
# identifying misclassified datapoints in the test set
misclassified_index = np.where(y_test != y_pred)[0]
# calculating roc-auc of predictions
print("ROC-AUC of predictions: {}".format(
    roc_auc_score(y_test, xgb_model.predict_proba(
        X_test)[:, 1])))
print("First 5 misclassified test set datapoints:
    {}".format(misclassified_index[0:5]))

SHAP 库中提供了不同的方法来近似特征重要性,例如shap.LinearExplainer()shap.KernelExplainer()shap.TreeExplainer()shap.DeepExplainer()。在基于树的方法(如随机森林和 XGBoost)的情况下,您可以使用shap.TreeExplainer()。让我们使用训练好的模型构建一个解释器对象,然后提取 Shapely 值:

# generate the Tree explainerexplainer = shap.TreeExplainer(xgb_model)
# extract SHAP values from the explainer object
shap_values = explainer.shap_values(X_test)

SHAP 库中有多个绘图函数,可以提供使用 Shapely 值的特征重要性的视觉说明。例如,我们可以使用shap.dependence_plot()来识别教育程度数特征的 Shapely 值:

# If interaction_index of "auto" is chosen then# the strongest interaction is used to color the dots.
shap.dependence_plot("Education-Num", shap_values, X_test)

下面的依赖性图清楚地表明,更高的教育程度数值会导致更高的 Shapely 值或更大的预测积极结果(即,>50k 薪水)的贡献:

图 6.4 – 成年人收入数据集测试集中教育程度数特征的 SHAP 值

图 6.4 – 成年人收入数据集测试集中教育程度数特征的 SHAP 值

我们可以用其他特征重复此过程,例如年龄,这将产生与教育程度数相似的解释。使用shap.dependence_plot()教育程度数年龄的唯一区别在于interaction_index,对于年龄被指定为None

# generate dependence plot for "Age" featureshap.dependence_plot("Age", shap_values, X_test,
    interaction_index=None)

图 6.5 – 成年人收入数据集测试集中年龄特征的 SHAP 值

图 6.5 – 成年人收入数据集测试集中年龄特征的 SHAP 值

如果我们需要从数据集的特定子集中提取模型解释,我们可以使用相同的函数,但使用我们想要调查的数据子集而不是整个数据集。我们还可以使用训练集和测试集来识别用于模型训练的数据中的解释以及我们想要用于评估模型性能的未见数据。为了展示这一点,我们将使用以下代码调查测试集中被错误分类的子集中年龄的重要性:

# generate dependence plot for "Age" featureshap.dependence_plot("Age",
    shap_values[misclassified_index],
    X_test.iloc[misclassified_index,:],
    interaction_index=None)

如您所见,SHAP 值对于错误分类的数据点(图 6.6)和整个数据集(图 6.5)具有相似的趋势:

图 6.6 – 成年人收入数据集测试集中误分类数据点的年龄特征 SHAP 值

图 6.6 – 成年人收入数据集测试集中误分类数据点的年龄特征 SHAP 值

除了从一系列数据点中提取 Shapely 值之外,我们还需要调查特征是如何对一个数据点的正确或错误预测做出贡献的。在这里,我们选择了两个样本:样本 12,实际标签为 False 或 0(即低收入),预测标签为 True 或 1(即高收入),以及 样本 24,实际和预测标签分别为 True 和 False。在这里,我们可以使用 shap.plots._waterfall.waterfall_legacy() 并提取输入特征的预期值,如图 图 6.7 所示。在这种 SHAP 绘图中,对于每个特征 Xf(X) 是给定 X 的预测值,而 E[f(X)] 是目标变量的预期值(即所有预测的平均值,mean(model.predict(X)))。此图显示了单个特征对预测的影响程度:

# extracting expected valuesexpected_value = explainer.expected_value
# generate waterfall plot for observation 12
shap.plots._waterfall.waterfall_legacy(expected_value,
    shap_values[12], features=X_test.iloc[12,:],
    feature_names=X.columns, max_display=15, show=True)
# generate waterfall plot for observation 24
shap.plots._waterfall.waterfall_legacy(expected_value,
    shap_values[24],features=X_test.iloc[24,:],
    feature_names=X.columns,max_display=15, show=True)

图 6.7,针对 样本 12,显示 关系教育程度 是影响最大的特征,而 种族国家 对该样本结果的影响最小:

图 6.7 – 成年人收入数据集中样本 12 的 SHAP 瀑布图

图 6.7 – 成年人收入数据集中样本 12 的 SHAP 瀑布图

关系教育程度 也是对 样本 24 影响最大的特征(图 6.8)。然而,在 样本 12 中,第三大贡献来自 每周小时数,它对 样本 24 的结果影响较小。这种分析类型可以帮助我们比较一些错误的预测,并识别出可能有助于提高模型性能的建议。或者,我们可以提取出改善该数据集中个人未来收入的可操作建议:

图 6.8 – 成年人收入数据集中样本 24 的 SHAP 瀑布图

图 6.8 – 成年人收入数据集中样本 24 的 SHAP 瀑布图

尽管 SHAP 提供了易于理解的见解,但我们仍需确保模型中的特征依赖性在解释 Shapely 值时不会导致混淆。

全局解释

虽然 shap.dependence_plot() 可能看起来提供了全局解释,因为它显示了特征对所有或数据点的子集的影响,但我们仍需要模型特征和数据点的解释来建立对模型的信任。shap.summary_plot() 是此类全局解释的一个例子,它总结了指定数据点集中特征的全局 Shapely 值。这类摘要图和结果对于识别最有效的特征以及了解模型中是否存在诸如种族或性别之类的偏见非常重要。通过以下摘要图(图 6.9),我们可以轻松地看到 性别种族 并不是影响最大的特征之一,尽管它们的影响可能并不一定可以忽略,可能需要进一步调查。我们将在下一章讨论模型偏差和公平性:

图 6.9 – 成人收入数据集的 SHAP 摘要图

图 6.9 – 成人收入数据集的 SHAP 摘要图

以下是生成之前摘要图的代码:

# create a SHAP beeswarm plot (i.e. SHAP summary plot)shap.summary_plot(shap_values, X_test,plot_type="bar")

使用 LIME 的解释

在学习了如何使用 SHAP 进行解释后,我们现在将注意力转向 LIME。我们将首先从局部解释开始。

局部解释

LIME 是获取单个数据点易于理解的局部解释的另一种方法。我们可以使用 lime Python 库来构建解释器对象,然后使用它来识别感兴趣样本的局部解释。在这里,我们再次将使用为 SHAP 训练的 XGBoost 模型,并为 sample 12sample 24 生成解释,以表明它们的预测结果是不正确的。

默认情况下,lime 使用 岭回归 作为生成局部解释的可解释模型。我们可以在 lime.lime_tabular.LimeTabularExplainer() 类中通过将 feature_selection 改为 none 来更改此方法,以进行无特征选择的线性建模,或者使用 lasso_path,它使用 scikit-learnlasso_path(),作为另一种带有正则化的监督线性建模形式。

注意

xgb_model.fit(np.array(X_train), y_train) makes the model usable for the lime library:
# create explainerexplainer = lime.lime_tabular.LimeTabularExplainer(
    np.array(X_train), feature_names=X_train.columns,
    #X_train.to_numpy()
    class_names=['Lower income','Higher income'],
    verbose=True)
# visualizing explanation by LIME
print('actual label of sample 12: {}'.format(y_test[12]))
print('prediction for sample 12: {}'.format(y_pred[12]))
exp = explainer.explain_instance(
    data_row = X_test.iloc[12],
    predict_fn = xgb_model.predict_proba)
exp.show_in_notebook(show_table=True)

您可以将图 6.10 中中间的图解释为 sample 12 预测结果为 高收入低收入 的局部特征贡献。与 SHAP 类似,教育程度关系 特征对样本被错误预测为 高收入 的贡献最大。另一方面,资本收益资本损失 对推动样本输出预测为另一类别的贡献最大。但我们也必须注意特征值,因为对于这个样本,资本收益资本损失 都是零:

图 6.10 – 成人收入数据集中样本 12 的 LIME 局部解释

图 6.10 – 成人收入数据集中样本 12 的 LIME 局部解释

同样,我们可以调查 sample 24 的 LIME 结果,如图 6.11 所示:

图 6.11 – 成年人收入数据集中样本 24 的 LIME 局部解释

图 6.11 – 成年人收入数据集中样本 24 的 LIME 局部解释

资本收益教育年限每周工作时间对预测输出在正负方向上的贡献最大。然而,资本收益不影响这个特定的数据点,因为其值为零。

全局解释

使用 lime.submodular_pick.SubmodularPick() 来选择这些样本。以下是此类的参数,这些参数可以帮助您解释全局回归或分类模型:

  • predict_fn(预测函数):对于 ScikitClassifiers,这是 classifier.predict_proba(),而对于 ScikitRegressors,这是 regressor.predict()

  • sample_size:如果选择 method == 'sample',则要解释的数据点的数量

  • num_exps_desired:返回的解释对象的数量

  • num_features:解释中存在的最大特征数:

sp_obj = submodular_pick.SubmodularPick(explainer,    np.array(X_train), xgb_model.predict_proba,
    method='sample', sample_size=3, num_features=8,
    num_exps_desired=5)
# showing explanation for the picked instances for explanation if you are using Jupyter or Colab notebook
[exp.show_in_notebook() for exp in sp_obj.explanations]

图 6**.12 展示了 SP-LIME 选出的三个数据点:

图 6.12 – SPI-LIME 为全局可解释性选择的数据点

图 6.12 – SPI-LIME 为全局可解释性选择的数据点

但您可以选择不可视化选定的实例,而是为每个解释对象使用 as_map() 参数而不是 show_in_notebook(),作为 sp_obj.explanations 中的解释对象的一部分,然后为更大的数据点集总结信息,而不是调查少量样本。对于此类分析,您可以使用少量数据点,例如在具有数万个数据点的非常大的数据集中,使用 1%或更低的百分比。

使用多样化的反事实解释(DiCE)生成反事实

您可以使用 dice_ml Python 库(Mothilal 等人,2020 年)来生成反事实,并了解模型如何从一个预测切换到另一个预测,正如本章前面所解释的。首先,我们必须训练一个模型,然后使用 dice_ml.Dice() Python 类创建一个解释对象,在安装并导入 dice_ml 库之后,如下所示:

### This example is taken from https://github.com/interpretml/DiCE ###dataset = helpers.load_adult_income_dataset()
target = dataset["income"] # outcome variable
train_dataset, test_dataset, _, _ = train_test_split(
    dataset,target,test_size=0.2,random_state=0,
    stratify=target)
# Dataset for training an ML model
d = dice_ml.Data(dataframe=train_dataset,
    continuous_features=['age','hours_per_week'],
    outcome_name='income')
# Pre-trained ML model
m = dice_ml.Model(
    model_path=dice_ml.utils.helpers.get_adult_income_modelpath(),
    backend='TF2', func="ohe-min-max")
# DiCE explanation instance
exp = dice_ml.Dice(d,m)

然后,我们可以使用生成的解释对象为单个或多个样本生成反事实。在这里,我们为 sample 1 生成 10 个反事实:

query_instance = test_dataset.drop(columns="income")[0:1]dice_exp = exp.generate_counterfactuals(query_instance,
    total_CFs=10, desired_class="opposite",
    random_seed = 42)
# Visualize counterfactual explanation
dice_exp.visualize_as_dataframe()

图 6**.13 展示了目标样本的特征值以及 10 个相应的反事实:

图 6.13 – 成年人收入数据集中选定的数据点和生成的反事实

图 6.13 – 成年人收入数据集中选定的数据点和生成的反事实

尽管所有反事实都符合切换目标样本结果的目标(即,样本 1),但并非所有反事实都符合每个特征的定义和意义,是可行的。例如,如果我们想建议一个 29 岁的人将他们的结果从低薪改为高薪,建议他们在 80 岁时会赚高薪并不是一个有效和可行的建议。同样,建议将每周工作小时数从 38 小时增加到>90 小时也是不可行的。你需要使用这样的考虑来拒绝反事实,以便你可以识别模型性能的机会,并为用户提供可操作的建议。此外,你可以切换到不同的技术来为你的模型和应用生成更有意义反事实。

有更多最近的 Python 库,如Dalex(Baniecki 等人,2021 年)和OmniXA(杨等人,2022 年),你可以用于模型可解释性。我们还将讨论如何使用这些方法和 Python 库来减少偏差,并帮助我们朝着在新开发或修改我们已训练的机器学习模型时实现公平性迈进。

回顾为什么仅仅拥有可解释性是不够的

可解释性帮助我们为模型用户建立信任。正如你在本章所学,你可以使用可解释性技术来理解你的模型是如何生成数据集中一个或多个实例的输出的。这些解释有助于从性能和公平性的角度改进我们的模型。然而,我们并不能仅仅通过盲目地使用这些技术并在 Python 中生成一些结果来实现这样的改进。例如,正如我们在使用多样化的反事实解释(DiCE)生成反事实部分所讨论的,一些生成的反事实可能并不合理和有意义,我们不能依赖它们。或者,当使用 SHAP 或 LIME 为单个或多个数据点生成局部解释时,我们需要注意特征的意义、每个特征值的范围及其背后的意义,以及我们调查的每个数据点的特征。使用可解释性进行决策的一个方面是区分模型和我们在训练、测试或生产中调查的特定数据点的问题。一个数据点可能是一个异常值,它使我们的模型对它来说不那么可靠,但并不一定使我们的模型整体上不那么可靠。在下一章,第七章减少偏差和实现公平性中,我们将讨论偏差检测并不仅仅是识别我们的模型依赖于诸如年龄种族肤色等特征。

总的来说,这些考虑告诉我们,仅仅运行几个 Python 类来为我们的模型使用可解释性是不够的,以达到信任并生成有意义的解释。这还远不止于此。

摘要

在本章中,你学习了可解释的机器学习模型以及可解释性技术如何帮助你提高模型的表现力和可靠性。你学习了不同的局部和全局可解释性技术,如 SHAP 和 LIME,并在 Python 中进行了实践。你还有机会使用提供的 Python 代码进行实践,学习如何在项目中使用机器学习可解释性技术。

在下一章中,你将学习如何检测和减少模型中的偏差,以及如何使用 Python 中可用的功能在开发机器学习模型时满足必要的公平性标准。

问题

  1. 可解释性如何帮助你提高模型的表现?

  2. 局部可解释性和全局可解释性之间有什么区别?

  3. 由于其可解释性,使用线性模型是否更好?

  4. 可解释性分析是否会使机器学习模型更可靠?

  5. 你能解释一下 SHAP 和 LIME 在机器学习可解释性方面的区别吗?

  6. 你如何在开发机器学习模型时从反事实中受益?

  7. 假设一个机器学习模型被用于银行的贷款审批。所有建议的反事实对建议一个人如何提高获得批准的机会都很有用吗?

参考文献

  • Weber, Leander,等人。超越解释:基于 XAI 的模型改进的机会和挑战. 信息融合 (2022)。

  • Linardatos, Pantelis,Vasilis Papastefanopoulos,和 Sotiris Kotsiantis. 可解释人工智能:机器学习可解释性方法综述. 混沌 23.1 (2020): 18.

  • Gilpin, Leilani H.,等人。解释解释:机器学习可解释性的概述. 2018 IEEE 第 5 届数据科学和高级分析国际会议(DSAA)。IEEE,2018。

  • Carvalho, Diogo V.,Eduardo M. Pereira,和 Jaime S. Cardoso. 机器学习可解释性:方法与度量综述. 电子学 8.8 (2019): 832.

  • Winter, Eyal. Shapley 值. 游戏理论及其在经济应用中的手册 3 (2002): 2025-2054.

  • *使用 Python 的可解释人工智能指南www.thepythoncode.com/article/explainable-ai-model-python

  • Burkart, Nadia,和 Marco F. Huber. 监督机器学习可解释性综述. 人工智能研究杂志 70 (2021): 245-317.

  • Guidotti, Riccardo. 反事实解释及其发现:文献综述和基准测试. 数据挖掘与知识发现 (2022): 1-55.

  • Ribeiro, Marco Tulio,Sameer Singh,和 Carlos Guestrin. 锚:高精度无模型可解释性. AAAI 人工智能会议论文集。第 32 卷。第 1 期。2018 年。

  • Hinton, Geoffrey,Oriol Vinyals,和 Jeff Dean. 从神经网络中提取知识. arXiv 预印本 arXiv:1503.02531 (2015).

  • Simonyan, Karen, Andrea Vedaldi, and Andrew Zisserman. 卷积网络内部深探:可视化图像分类模型和显著性图. arXiv 预印本 arXiv:1312.6034 (2013)。

  • Frosst, Nicholas, and Geoffrey Hinton. 将神经网络蒸馏成软决策树. arXiv 预印本 arXiv:1711.09784 (2017)。

  • Lundberg, Scott M., and Su-In Lee. 解释模型预测的统一方法. 神经信息处理系统进展第 30 卷 (2017)。

  • Ribeiro, Marco Tulio, Sameer Singh, and Carlos Guestrin. “为什么我应该相信你?”解释任何分类器的预测. 第 22 届 ACM SIGKDD 国际知识发现和数据挖掘会议论文集,2016 年。

  • Baniecki, Hubert, et al. Dalex: 在 Python 中实现具有交互式可解释性和公平性的负责任机器学习. 机器学习研究杂志第 22 卷第 1 期 (2021): 9759-9765。

  • Yang, Wenzhuo, et al. OmniXAI: 一个可解释人工智能库. arXiv 预印本 arXiv:2206.01612 (2022)。

  • Hima Lakkaraju, Julius Adebayo, Sameer Singh, AAAI 2021 教程:解释机器学习预测

  • Mothilal, Ramaravind K., Amit Sharma, and Chenhao Tan. 通过多样化的反事实解释解释机器学习分类器. 2020 年公平、问责和透明度会议论文集。

第七章:降低偏见和实现公平性

当使用机器学习跨越不同行业时,公平性是一个重要的话题,正如我们在第三章,“向负责任的人工智能调试”中讨论的那样。在本章中,我们将向您提供一些在机器学习环境中广泛使用的公平性概念和定义,以及如何使用旨在不仅帮助您评估模型中的公平性,而且在此方面改进它们的公平性和可解释性 Python 库。

本章包含许多图表和代码示例,以帮助您更好地理解这些概念,并在项目中开始从中受益。请注意,一个章节远远不足以使您成为公平性主题的专家,但本章将为您提供开始在实际项目中实践这一主题所需的知识和工具。您可以通过使用更多致力于机器学习公平性的高级资源来了解更多关于这个主题的信息。

在本章中,我们将涵盖以下主题:

  • 机器学习建模中的公平性

  • 偏见的来源

  • 使用可解释性技术

  • Python 中的公平性评估和改进

到本章结束时,您将了解一些技术细节和 Python 工具,您可以使用它们来评估模型的公平性并减少偏见。您还将了解如何从您在第六章,“机器学习建模中的可解释性和可解释性”中学习的机器学习可解释性技术中受益。

技术要求

以下要求应在本章中考虑,因为它们将帮助您更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • pytest >= 7.2.2

    • shap >= 0.41.0

    • aif360 >= 0.5.0

    • fairlearn >= 0.8.0

  • 对上一章中讨论的机器学习可解释性概念的基本了解

您可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter07

机器学习建模中的公平性

要评估公平性,我们需要在心中考虑特定的因素,然后使用适当的指标来量化模型中的公平性。表 7.1为您提供了评估或实现机器学习建模中公平性的考虑因素、定义和方法的示例。我们将讨论人口统计学平等机会均等均衡机会机会平等的数学定义,作为不同的群体公平性定义。群体公平性定义确保具有共同属性和特征的人群群体的公平性,而不是个人:

机器学习公平主题 描述
人口统计学平等 确保预测不依赖于给定的敏感属性,例如种族、性别或种族
概率平等 确保预测对给定敏感属性的独立性,例如在给定真实输出时对种族、性别或种族的独立性
机会平等 确保为个人或人群提供的平等机会
个人公平 确保对个人而不是具有共同属性的群体公平
一致性 不仅在相似的数据点或用户之间,而且在时间上提供决策的一致性
通过无意识实现公平 如果你在决策过程中不知道敏感属性,则可以实现公平
通过透明度实现公平 通过透明度和通过可解释性建立信任来提高公平性

表 7.1 – 机器学习和人工智能中关于公平性的重要主题和考虑因素

人口统计学平等是一个群体公平定义,确保模型预测不依赖于给定的敏感属性,例如种族或性别。从数学上讲,我们可以将其定义为预测类别的概率相等,例如 C i,对于给定属性的各个群体,如下所示:

P(C = C i|G = g 1) = P(C = C i|G = g 2)

为了更好地理解人口统计学平等的含义,我们可以考虑以下例子,这些例子符合人口统计学平等的公平性:

  • 在 COMPAS 中每个种族群体中拒绝保释的百分比相同。我们已在第三章中介绍了 COMPAS,向负责任的 AI 调试

  • 男女贷款申请的接受率相同。

  • 贫富社区之间住院的可能性相同。我们已在第三章中详细介绍了这个问题,向负责任的 AI 调试

差异影响比率DIR)是一个衡量基于人口统计学平等差异的指标:

DIR =  P(C = 1|G = g 1)  _____________  P(C = 1|G = g 2)

DIR 值范围是[0, ∞),其中 1 的值满足人口统计学平等,而向更高或更低值的偏差则表示根据此定义的公平性偏差。大于和小于 1 的 DIR 值分别被称为正偏差和负偏差,考虑到我们在分子中使用的群体。

尽管人口统计学上的平等在公平性中很重要,但它有其局限性。例如,在数据本身中的 DIR(即不同群体之间类别普遍性的差异)的情况下,一个完美的模型将不会满足人口统计学上的平等标准。此外,它也不反映每个群体的预测质量。其他定义有助于我们改进公平性评估。机会平等或均衡机会是一个这样的定义。当给定预测与给定敏感属性所属的群体以及真实输出无关时,均衡机会得到满足:

P( ˆ y |y, G = g 1) = P( ˆ y |y, G = g 2) = P( ˆ y |y)

机会平等的定义与均衡机会非常相似,它评估预测与给定真实输出相关的群体的独立性。但机会平等专注于特定的真实值标签。通常,正类被认为是目标类,代表为个人提供机会,例如入学或高薪。以下是机会平等的公式:

P( ˆ y |y = 1, G = g 1) = P( ˆ y |y = 1, G = g 2) = P( ˆ y |y = 1)

根据这些公平性的概念,每个概念都可能给出不同的结果。你需要考虑不同概念之间的差异,以免基于一个或另一个定义泛化公平性。

敏感变量的代理

在评估机器学习模型中的公平性时,一个挑战是存在敏感属性(如性别和种族)的代理。这些代理可能是生成模型输出的主要贡献者,并可能导致我们的模型对特定群体产生偏差。然而,我们不能简单地移除它们,因为这可能会对性能产生重大影响。表 7.2提供了这些代理的示例,针对不同的敏感属性:

敏感变量 示例代理
性别 教育水平、工资和收入(在某些国家),职业,犯罪指控历史,用户生成内容中的关键词(例如,在简历或社交媒体中),作为大学教职员工
种族 犯罪指控历史、用户生成内容中的关键词(例如,在简历或社交媒体中)、ZIP 或邮政编码
残疾 行走速度、眼动、身体姿势
婚姻状况 教育水平、工资和收入(在某些国家),以及房屋大小和卧室数量
年龄 姿势和用户生成内容中的关键词(例如,在简历或社交媒体中)

表 7.2 – 在公平性的背景下,一些重要敏感变量的代理示例(Caton 和 Haas,2020)

现在你已经了解了公平性的重要性以及这个主题下的一些重要定义,让我们回顾一下可能产生偏差的来源,这些偏差可能会阻碍你在模型中实现公平性的目标。

偏差的来源

机器学习生命周期中存在不同的偏差来源。偏差可能存在于收集的数据中,在数据子采样、清理和过滤中引入,或者在模型训练和选择中。在这里,我们将回顾这些来源的例子,以帮助您更好地理解如何在机器学习项目的整个生命周期中避免或检测此类偏差。

数据生成和收集中引入的偏差

我们输入到模型中的数据可能默认就有偏差,甚至在建模开始之前。我们在这里想要回顾的第一个这种偏差的来源是数据集大小的问题。将数据集视为更大人群的一个样本——例如,100 名学生的调查或 200 名银行客户的贷款申请信息。这些数据集的小规模可能会增加偏差的机会。让我们用一个简单的随机数据生成来模拟这一点。我们将编写一个函数,使用np.random.randint()生成两个随机二进制值的向量,然后计算两个 0 和 1 组之间的DIR

np.random.seed(42)def disparate_impact_randomsample(sample_size,
    sampling_num = 100): disparate_impact = []
    for sam_iter in range(0, sampling_num):
        # generating random array of 0 and 1 as two groups with different priviledges (e.g. male versus female)
        group_category = np.random.randint(2,
            size=sample_size)
    # generating random array of 0 and 1 as the output labels (e.g. accepted for loan or not)
    output_labels = np.random.randint(2, size=sample_size)
    group0_label1 = [iter for iter in range(0, len(
        group_category)) if group_category[iter] == 0 
        and output_labels[iter] == 1]
    group1_label1 = [iter for iter in range(0, len(
        group_category)) if group_category[iter] == 1 and 
        output_labels[iter] == 1]
    # calculating disparate impact 
    disparate_impact.append(len
        (group1_label1)/len(group0_label1))
    return disparate_impact

现在,让我们使用这个函数来计算 1,000 个不同规模的不同组别的 DIR,包括501001000100001000000个数据点:

sample_size_list = [50, 100, 1000, 10000, 1000000]disparate_impact_list = []
for sample_size_iter in sample_size_list:
    disparate_impact_list.append(
        disparate_impact_randomsample(
            sample_size = sample_size_iter,
            sampling_num = 1000))

以下箱线图显示了不同样本规模下DIR的分布。你可以看到,较小的样本规模具有更宽的分布,覆盖了非常低或高的DIR值,远离理想的 1 值:

图 7.1 – 不同采样规模下的 DIR 分布

图 7.1 – 不同采样规模下的 DIR 分布

我们还可以计算不同规模样本组的百分比,这些组没有通过特定的阈值,例如>=0.8 和<=1.2。图 7.2显示,较高的数据集规模导致具有正或负偏差的数据集的机会降低:

图 7.2 – 未通过 DIR 阈值的样本集百分比

图 7.2 – 未通过 DIR 阈值的样本集百分比

数据集中现有偏差的来源可能不仅仅是小样本大小的产物。例如,如果你要训练一个模型来预测个人是否会进入 STEM 领域,STEM 是科学、技术、工程和数学领域的缩写,那么你必须考虑这样一个现实:在工程等领域的相应数据中,男性相对于女性的存在是不平衡的,甚至直到最近(图 7.3):

图 7.3 – 1970 年至 2019 年间 STEM 职业中女性的百分比

图 7.3 – 1970 年至 2019 年间 STEM 职业中女性的百分比

多年来,工程师中女性比例不到 20%,这可能是由于她们对这一领域的兴趣较低、招聘过程中的偏见,或是社会中的刻板印象,这导致了该领域工作者数据中的偏见。如果数据处理的建模任务中不公平,可能会导致预测男性进入 STEM 领域的可能性高于女性,尽管她们拥有才能、知识和经验。

数据中还存在另一类内在偏见,尽管在开发机器学习模型时需要考虑它。例如,不到 1%的乳腺癌病例发生在男性身上(www.breastcancer.org)。男性和女性之间这种患病率的差异并非由数据生成或收集中的任何偏见,或社会存在的偏见所导致。这是男性和女性乳腺癌发生率的自然差异。但如果负责开发用于诊断乳腺癌的机器学习模型,男性可能会出现较高的假阴性率(即未诊断出乳腺癌)。如果你的模型没有考虑到女性比男性高得多的患病率,那么它将不是一个对男性公平的乳腺癌诊断模型。这是一个高级示例,用于阐明这类偏见。在构建用于癌症诊断的机器学习工具时,还有许多其他需要考虑的因素。

模型训练和测试中的偏见

如果数据集在男性或女性、不同种族或其他敏感属性方面存在高度不平衡,我们的模型可能会因为相应的机器学习算法在预测数据点结果时使用特征的方式而产生偏见。例如,我们的模型可能会高度依赖敏感属性或它们的代理(表 7.2)。这是模型选择过程中的一个重要考虑因素。在模型选择过程中,我们需要从训练的模型中选择一个,使用不同的方法或同一方法的超参数,以进行进一步的测试或生产。如果我们仅基于性能做出决定,那么我们可能会选择一个不公平的模型。如果我们有敏感属性,并且这些模型将直接或间接影响不同群体的人,那么在模型选择过程中,我们需要同时考虑公平性和性能。

生产中的偏见

由于训练、测试和生产阶段数据分布的不同,生产过程中可能会出现偏见和不公平现象。例如,在生产和测试数据中不存在的性别差异可能会在生产的某个阶段出现。这种情况可能导致在生命周期早期阶段无法检测到的生产偏见。我们将在第十一章中更详细地讨论这类差异,避免和检测数据与概念漂移

本章的下一步是开始练习使用帮助你在检测和消除模型偏差方面的技术和 Python 库。首先,我们将练习使用在第六章中介绍的机器学习建模中的可解释性和可解释性技术

使用可解释性技术

我们可以使用可解释性技术来识别我们模型中的潜在偏差,然后计划改进它们以实现公平性。在这里,我们想通过 SHAP 练习这个概念,并识别我们在上一章练习的成人收入数据集中男性和女性群体之间的公平性问题。使用我们在上一章为成人收入数据训练的 XGBoost 模型构建的相同的 SHAP 解释器对象,在下面的条形图中,我们可以看到,关于整个数据集或仅错误预测的数据点,对性别的依赖性很低,但并非微不足道:

图 7.4 – 整个成人收入数据集和错误预测数据点的 SHAP 摘要图

图 7.4 – 整个成人收入数据集和错误预测数据点的 SHAP 摘要图

现在,我们可以提取每个性别群体中误分类数据点的比例,如下所示:

X_WithPred.groupby(['Sex', 'Correct Prediction']).size().unstack(fill_value=0)

这将产生以下结果:

图 7.5 – 正确和错误预测中男性和女性的数量

图 7.5 – 正确和错误预测中男性和女性的数量

在这里,我们分别有女性和男性群体的 6.83%和 20.08%的误分类百分比。测试集中仅针对男性和女性群体的模型预测的 ROC-AUC 分别为 0.90 和 0.94。

你可以考虑将识别特征之间的相关性作为一种识别代理和潜在去除模型中偏差的方法。以下代码和热图(图 7.6)显示了该数据集特征之间的相关性:

corr_features = X.corr()corr_features.style.background_gradient(cmap='coolwarm')

输出将如下所示:

图 7.6 – 成人收入数据集特征之间的相关性 DataFrame

图 7.6 – 成人收入数据集特征之间的相关性 DataFrame

然而,使用这种相关性分析作为处理代理识别问题或甚至用于过滤特征以提高性能的方法存在缺点。以下是其中两个缺点:

  • 你需要考虑每对特征适当的关联度量。例如,皮尔逊相关不能用于所有特征对,因为每对数据的分布必须满足该方法的使用假设。两个变量都需要遵循正态分布,数据不应有任何异常值,这是皮尔逊相关适当使用时的两个假设之一。这意味着为了正确使用特征相关性分析方法,你需要使用适当的关联度量来比较特征。非参数统计度量,如斯皮尔曼等级相关,可能更适合,因为在使用不同变量对时,其背后的假设较少。

  • 并非所有数值都有相同的意义。一些特征是分类的,并且通过不同的方法被转换成数值特征。性别就是这些特征之一。0 和 1 的值可以用来表示女性和男性群体,但它们在数值特征(如年龄或薪水)中没有任何数值意义。

可解释性技术如 SHAP 会告诉你关于敏感属性及其对数据点结果贡献的依赖关系。然而,默认情况下,它们并不提供改进模型公平性的方法。在这个例子中,我们可以尝试将数据分割成男性和女性群体进行训练和测试。以下代码展示了针对女性群体的这种方法。同样,你可以通过使用“性别”特征的1来分离训练和测试输入输出数据,为男性群体重复此操作。为男性和女性群体分别构建的模型分别得到了 0.90 和 0.93 的 ROC-AUC 值,这几乎与分组分离的性能相同:

X_train = X_train.reset_index(drop=True)X_test = X_test.reset_index(drop=True)
# training a model only for female category (Sex category of 0 in this dataset)
X_train_only0 = X_train[X_train['Sex'] == 0]
X_test_only0 = X_test[X_test['Sex'] == 0]
X_only0 = X[X['Sex'] == 0]
y_train_only0 = [y_train[iter] for iter in X_train.index[
    X_train['Sex'] == 0].tolist()]
y_test_only0 = [y_test[iter] for iter in X_test.index[
    X_test['Sex'] == 0].tolist()]
# initializing an XGboost model
xgb_model = xgboost.XGBClassifier(random_state=42)
# fitting the XGboost model with training data
xgb_model.fit(X_train_only0, y_train_only0)
# calculating roc-auc of predictions
print("ROC-AUC of predictions:
    {}".format(roc_auc_score(y_test_only0,
        xgb_model.predict_proba(X_test_only0)[:, 1])))
# generate the Tree explainer
explainer_xgb = shap.TreeExplainer(xgb_model)
# extract SHAP values from the explainer object
shap_values_xgb = explainer_xgb.shap_values(X_only0)
# create a SHAP beeswarm plot (i.e. SHAP summary plot)
shap.summary_plot(shap_values_xgb, X_only0,
    plot_type="bar")

我们没有从模型中移除“性别”特征。这个特征不能对模型的性能做出贡献,因为每个模型的数据点中这个特征的值没有差异。这也在条形图中通过零 Shapely 值得到了体现:

图 7.7 – 分别在女性和男性群体上训练和测试的模型的 SHAP 摘要图

图 7.7 – 分别在女性和男性群体上训练和测试的模型的 SHAP 摘要图

根据敏感属性分离群体的这种做法,尽管有时被视为一种选择,但并不是处理公平性问题的一个理想方式。它可能不是一个有效的办法,因为模型可能高度依赖于其他敏感特征。此外,我们无法根据数据集中所有敏感属性的组合将数据分割成小块。有一些公平性工具可以帮助你不仅评估公平性和检测偏差,还可以选择一个更好地满足公平性概念的模型。

除了用于可解释性的库之外,还有一些专门为机器学习建模中的公平性检测和改进设计的 Python 库,我们将在下一部分介绍。

Python 中的公平性评估和改进

在评估模型公平性的 Python 库中,广泛使用的并不多(表 7.3)。您可以使用这些库来识别模型是否满足根据数据集中不同的敏感属性所定义的公平性:

导入和安装库名称 URL
IBM AI Fairness 360 aif360 pypi.org/project/aif360/
Fairlearn fairlearn pypi.org/project/fairlearn/
黑盒审计 BlackBoxAuditing pypi.org/project/BlackBoxAuditing/
Aequitas aequitas pypi.org/project/aequitas/
负责任 AI 工具箱 responsibleai pypi.org/project/responsibleai/
负责任地 responsibly pypi.org/project/responsibly/
Amazon Sagemaker Clarify smclarify pypi.org/project/smclarify/
公平感知机器学习 fairness pypi.org/project/fairness/
偏差校正 biascorrection pypi.org/project/biascorrection/

表 7.3 – 具有机器学习公平性可用功能的 Python 库或存储库

首先,让我们加载成人收入数据集,在导入所需的库之后,并准备训练和测试集,如下所示:

# loading UCI adult income dataset# classification task to predict if people made over $50k in the 90s or not
X,y = shap.datasets.adult()
# split the data to train and test sets
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size = 0.3, random_state=10)
# making a dataframe out of y values with "Sex" being their indices
y_train = pd.DataFrame({'label': y_train},
    index = X_train['Sex'])
y_test = pd.DataFrame({'label': y_test},
    index = X_test['Sex'])

现在,我们可以训练和测试一个 XGBoost 模型:

xgb_model = xgboost.XGBClassifier(random_state=42)# fitting the XGboost model with training data
xgb_model.fit(X_train, y_train)
# calculating roc-auc of predictions
print("ROC-AUC of predictions:
    {}".format(roc_auc_score(y_test,
        xgb_model.predict_proba(X_test)[:, 1])))
# generating predictions for the test set
y_pred_train = xgb_model.predict(X_train)
y_pred_test = xgb_model.predict(X_test)

在这里,我们希望使用aif360根据Sex属性计算训练和测试数据中真实和预测结果的方向性指标(DIR):

# calculating disparate impact ratiodi_train_orig = disparate_impact_ratio(y_train,
    prot_attr='Sex', priv_group=1, pos_label=True)
di_test_orig = disparate_impact_ratio(y_test,
    prot_attr='Sex', priv_group=1, pos_label=True)
di_train = disparate_impact_ratio(y_train, y_pred_train,
    prot_attr='Sex', priv_group=1, pos_label=True)
di_test = disparate_impact_ratio(y_test, y_pred_test,
    prot_attr='Sex', priv_group=1, pos_label=True)

下面的分组条形图显示,预测在训练和测试集中使 DIR 变得更糟:

图 7.8 – 原始数据和预测输出的 DIR 比较

图 7.8 – 原始数据和预测输出的 DIR 比较

我们可以使用aif360来提高我们的模型向公平性发展。拒绝选项分类是一种后处理技术,在决策边界最高不确定性的置信区间内,对无特权群体给予有利结果,对特权群体给予不利结果(aif360.readthedocs.io/, Kamira 等,2012)。首先,让我们导入在 Python 中执行此操作所需的全部必要库和功能:

# importing Reject option classification, a post processing technique that gives favorable outcomes to unprivileged groups and unfavourable outcomes to# privileged groups in a confidence band around the decision boundary
# with the highest uncertainty
from aif360.sklearn.postprocessing import RejectOptionClassifierCV
# importing PostProcessingMeta,  a meta-estimator which wraps a given
# estimator with a post-processing step.
# fetching adult dataset from aif360 library
X, y, sample_weight = fetch_adult()
X.index = pd.MultiIndex.from_arrays(X.index.codes,
    names=X.index.names)
y.index = pd.MultiIndex.from_arrays(y.index.codes,
    names=y.index.names)
y = pd.Series(y.factorize(sort=True)[0], index=y.index)
X = pd.get_dummies(X)

然后,我们可以使用 RejectOptionClassifierCV()aif360 中可用的成人数据集上训练和验证一个随机森林分类器。我们之所以从 XGBoost 切换到随机森林,只是为了练习不同的模型。我们需要将一个初始的随机森林模型和 RejectOptionClassifierCV()PostProcessingMeta() 对象拟合。在过程中,'sex' 被认为是敏感特征:

metric = 'disparate_impact'ppm = PostProcessingMeta(RF(n_estimators = 10,
    random_state = 42),
    RejectOptionClassifierCV('sex', scoring=metric,
        step=0.02, n_jobs=-1))
ppm.fit(X, y)

然后,我们可以绘制平衡准确率和 DIR 在网格搜索中不同尝试的图表,以显示最佳选择的参数,这是 图 7**.9 中散点图中的星号点。蓝色点向您展示了平衡准确率和 DIR 之间的权衡的帕累托前沿:

图 7.9 – 网格搜索中平衡准确率与 DIR 的比较

图 7.9 – 网格搜索中平衡准确率与 DIR 的比较

如您所见,在这种情况下,性能和公平性之间存在权衡。但在这个例子中,性能降低不到 4% 就能将 DIR 从低于 0.4 提高到 0.8。

正如您在示例中看到的那样,我们可以使用 aif360 来评估公平性,并在性能损失很小的情况下提高我们模型的公平性。您可以使用 Python 中的其他库以类似的方式做到这一点。每个库都有其功能,用于机器学习建模中的公平性评估和改进的两个目标。

我们在本章中提供的内容只是机器学习公平性冰山的一角。但到目前为止,您已经准备好尝试不同的库和技术,并通过我们经历过的实践来了解它们。

摘要

在本章中,您学习了更多关于机器学习时代公平性的概念,以及评估公平性的指标、定义和挑战。我们讨论了诸如 sexrace 这样的敏感属性的示例代理。我们还讨论了可能的偏差来源,例如数据收集或模型训练。您还学习了如何使用 Python 库来评估模型的可解释性和公平性,以评估或改进您的模型,以及避免不仅不道德,而且可能对您的组织产生法律和财务后果的偏差。

在下一章中,您将学习测试驱动开发以及单元测试和差分测试等概念。我们还将讨论机器学习实验跟踪以及它在模型训练、测试和选择过程中如何帮助我们避免模型问题。

问题

  1. 公平性是否只取决于可观察的特征?

  2. 'sex' 的代理特征有哪些例子?

  3. 如果一个模型根据人口统计学平等是公平的,那么根据其他公平性概念,如均衡机会,它是否也是公平的?

  4. 作为两种公平性指标,人口统计学平等和均衡机会有何区别?

  5. 如果您的模型中有一个 'sex' 特征,并且您的模型对它的依赖性较低,这意味着您的模型在不同性别群体中是公平的吗?

  6. 你如何使用可解释性技术来评估你模型中的公平性?

参考文献

  • Barocas, Solon, Moritz Hardt 和 Arvind Narayanan. 机器学习中的公平性. Nips 教程 1 (2017): 2017.

  • Mehrabi, Ninareh, 等人. 机器学习中的偏差与公平性调查. ACM 计算机调查 (CSUR) 54.6 (2021): 1-35.

  • Caton, Simon 和 Christian Haas. 机器学习中的公平性:调查. arXiv 预印本 arXiv:2010.04053 (2020).

  • Pessach, Dana 和 Erez Shmueli. 机器学习中的公平性综述. ACM 计算机调查 (CSUR) 55.3 (2022): 1-44.

  • Lechner, Tosca, 等人. 公平表示的不可能性结果. arXiv 预印本 arXiv:2107.03483 (2021).

  • McCalman, Lachlan, 等人. 评估金融中的 AI 公平性. 计算机 55.1 (2022): 94-97.

  • F. Kamiran, A. Karim 和 X. Zhang,歧视感知分类的决策理论. 国际数据挖掘会议,2012.

第三部分:低错误机器学习开发和部署

在本书的这一部分,我们将提供确保机器学习模型稳健性和可靠性的基本实践,尤其是在生产环境中。我们将从采用测试驱动开发开始,说明它在模型开发过程中降低风险的关键作用。随后,我们将深入研究测试技术和模型监控的重要性,确保我们的模型在部署后仍保持可靠性。然后,我们将解释通过代码、数据和模型版本化实现机器学习可重复性的技术和挑战。最后,我们将讨论数据漂移和概念漂移的挑战,以确保在生产中有可靠的模型。

本部分包含以下章节:

  • 第八章, 使用测试驱动开发控制风险

  • 第九章, 生产环境下的测试和调试

  • 第十章, 版本控制和可重复的机器学习建模

  • 第十一章, 避免和检测数据及概念漂移

第八章:使用测试驱动开发控制风险

与创建基于我们模型的模型和技术相关的风险,例如选择不可靠的模型,是存在的。问题是,我们能否避免这些风险并更好地管理与机器学习建模相关的风险?在本章中,我们将讨论编程策略,如单元测试,这些策略不仅可以帮助我们开发和选择更好的模型,还可以降低建模相关的风险。

在本章中,我们将涵盖以下主题:

  • 驱动式开发

  • 机器学习差异测试

  • 跟踪机器学习实验

到本章结束时,您将学会如何通过单元测试和差异测试来降低不可靠建模和软件开发的风险,以及如何使用机器学习实验跟踪可靠地构建模型训练和评估的先前尝试。

技术要求

在本章中,以下要求应予以考虑,因为它们将帮助您更好地理解概念,在您的项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • pytest >= 7.2.2

    • ipytest >= 0.13.0

    • mlflow >= 2.1.1

    • aif360 >= 0.5.0

    • shap >= 0.41.0

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • pandas >= 1.4.4

  • 您还需要了解模型偏差和偏差度量定义的基本知识,例如差异影响 比率DIR

您可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter08

驱动式开发用于机器学习建模

减少开发不可靠模型并将其推送到生产的风险的一种方法是通过测试驱动开发。我们的目标是设计单元测试(即设计用于测试软件单个组件的测试),以降低代码修订在同一或不同生命周期中的风险。为了更好地理解这个概念,我们需要了解单元测试是什么,以及我们如何在 Python 中设计和使用它们。

单元测试

单元测试旨在测试我们设计和编写的代码和软件中最小的组件或单元。在机器学习建模中,我们可能有多个模块负责机器学习生命周期的不同步骤,例如数据整理和清洗或模型评估。单元测试帮助我们避免错误和失误,并在编写代码时无需担心是否犯了不会被早期发现的错误。早期发现代码中的问题成本较低,并有助于我们避免错误累积,从而使调试过程更加容易。Pytest 是一个 Python 测试框架,它帮助我们设计不同的测试,包括单元测试,在机器学习编程中使用。

使用 Pytest

Pytest 是一个易于使用的 Python 库,我们可以通过以下步骤来设计单元测试:

  1. 确定我们想要为哪个组件设计单元测试。

  2. 定义一个小操作来用于测试该组件。例如,如果该模块是数据处理的一部分,测试可以设计使用一个非常小的玩具数据集,无论是真实的还是合成的。

  3. 为相应的组件设计一个以 "test_" 开头的函数。

  4. 对于我们想要设计单元测试的所有代码组件,重复 步骤 13。最好尽可能覆盖更多组件。

设计的测试可以用来测试代码中的更改。我们将在这里使用 Pytest 实践单元测试设计,针对一个计算 DIR 并使用 DIR 的输入阈值进行偏差检测以返回“无偏数据”或“有偏数据”的函数:

import pandas as pdfrom aif360.sklearn.metrics import disparate_impact_ratio
def dir_grouping(data_df: pd.DataFrame,
    sensitive_attr: str, priviledge_group,
    dir_threshold = {'high': 1.2, 'low': 0.8}):
        """
        Categorizing data as fair or unfair according to DIR
        :param data_df: Dataframe of dataset
        :param sensitive_attr: Sensitive attribute under investigation
        :priviledge_group: The category in the sensitive attribute that needs to be considered as priviledged
        :param dir_threshold:
        """
    dir = disparate_impact_ratio(data_df,
        prot_attr=sensitive_attr,
        priv_group=priviledge_group, pos_label=True)
    if dir < dir_threshold['high'] and dir > dir_threshold[
        'low']:
        assessment = "unbiased data"
    else:
        assessment = "biased data"
    return assessment

现在我们已经定义了这个函数的示例用法,用于我们的单元测试设计,我们可以选择数据集的前 100 行并计算 DIR:

# calculating DIR for a subset of adult income data in shap libraryimport shap
X,y = shap.datasets.adult()
X = X.set_index('Sex')
X_subset = X.iloc[0:100,]

根据计算出的 DIR,这个数据子集在 'Sex' 属性方面是有偏的。为了设计单元测试,我们需要导入 pytest 库。但如果你使用 Jupyter 或 Colab 笔记本进行原型设计,你可以使用 ipytest 来测试你的代码:

import pytest# you can use ipytest if you are using Jupyter or Colab notebook
import ipytest
ipytest.autoconfig()

如果我们在 Jupyter 或 Colab Notebook 中使用 pytest 并想使用 ipytest 运行测试,我们必须添加 %%ipytest -qq。然后,我们可以定义我们的单元测试函数,test_dir_grouping(),如下所示:

%%ipytest -qqdef test_dir_grouping():
    bias_assessment = dir_grouping(data_df = X_subset,
        sensitive_attr = 'Sex',priviledge_group = 1,
        dir_threshold = {'high':1.2, 'low': 0.8})
    assert bias_assessment == "biased data"

assert 命令检查 dir_grouping() 函数的结果是否为“有偏数据”,正如我们之前的分析所预期的那样,对于数据集的前 100 行。如果结果不同,则测试失败。

当你为软件组件准备好所有单元测试时,你可以在 test_dir_grouping 中运行 pytest,如前面的代码所示,在名为 test_script.py 的 Python 脚本中,你只能测试该脚本,如下所示:

pytest test_script.py

或者,你可以在特定目录中运行 pytest。如果你有一个包含许多不同模块的代码库,你可以根据你的主要函数和类的分组来组织你的测试,然后测试每个目录,如下所示:

# "testdir" could be a directory containing test scriptspytest testdir/

相反,如果你简单地运行 pytest,它将在当前目录及其子目录中执行所有名为 test_*.py\*_test.py 的文件中的所有测试:

pytest

你也可以使用 Python 解释器通过 pytest 执行测试:

python -m pytest

如果你正在使用 Jupyter 或 Colab Notebook 并且使用了 ipytest,你可以按照以下方式运行 Pytest:

ipytest.run()

现在,假设我们以这种方式执行设计的 test_dir_grouping() 函数。当测试通过时,我们将看到如下消息,它告诉我们 100% 的测试通过了。这是因为我们只测试了一个测试,并且测试通过了(图 8**.1):

图 8.1 – 当设计的测试通过时 Pytest 的输出

图 8.1 – 当设计的测试通过时 Pytest 的输出

如果我们在dir_grouping()函数中错误地将assessment = "biased data"改为assessment = "unbiased data",我们会得到以下结果,这告诉我们 100%的测试失败了。这是因为我们只有一个测试,在这种情况下失败了(图 8**.2):

图 8.2 – 运行 Pytest 后的失败信息

图 8.2 – 运行 Pytest 后的失败信息

pytest中的失败信息包含一些补充信息,我们可以使用这些信息来调试我们的代码。在这种情况下,它告诉我们,在test_dir_grouping()中,它试图断言test_dir_grouping()的输出,即“unbiased data”,与“biased data”。

Pytest 夹具

当我们为数据分析和学习建模编程时,我们需要使用来自不同变量或数据对象的数据,这些数据可能来自我们本地机器或云中的文件,是从数据库中查询的,或者来自测试中的 URL。夹具通过消除在测试中重复相同代码的需要来帮助我们完成这些过程。将夹具函数附加到测试上将在每个测试运行之前运行它,并将数据返回给测试。在这里,我们使用了 Pytest 文档页面上提供的夹具示例(来源:docs.pytest.org/en/7.1.x/how-to/fixtures.html)。首先,让我们定义两个非常简单的类,称为FruitFruitSalad

# Example of using Pytest fixtures available in https://docs.pytest.org/en/7.1.x/how-to/fixtures.html
class Fruit:
    def __init__(self, name):
        self.name = name
        self.cubed = False
    def cube(self):
        self.cubed = True
class FruitSalad:
    def __init__(self, *fruit_bowl):
        self.fruit = fruit_bowl
        self._cube_fruit()
    def _cube_fruit(self):
        for fruit in self.fruit:
            fruit.cube()

当我们使用pytest时,它会查看测试函数签名中的参数,并寻找与这些参数具有相同名称的夹具。Pytest 然后运行这些夹具,捕获它们返回的内容,并将这些对象作为参数传递给测试函数。我们通过使用@pytest.fixture装饰器来通知 Pytest 一个函数是夹具。在以下示例中,当我们运行测试时,test_fruit_salad请求fruit_bowl,Pytest 执行fruit_bowl并将返回的对象传递给test_fruit_salad

# Arrange@pytest.fixture
def fruit_bowl():
    return [Fruit("apple"), Fruit("banana")]
def test_fruit_salad(fruit_bowl):
    # Act
    fruit_salad = FruitSalad(*fruit_bowl)
    # Assert
    assert all(fruit.cubed for fruit in fruit_salad.fruit)

这里有一些夹具的特性,可以帮助我们设计测试:

  • 夹具可以请求其他夹具。这有助于我们设计更小的夹具,甚至可以将它们作为其他夹具的一部分来构建更复杂的测试。

  • 夹具可以在不同的测试中重复使用。它们就像函数一样,在不同的测试中使用,并返回它们自己的结果。

  • 一个测试或夹具可以同时请求多个夹具。

  • 夹具可以在每个测试中请求多次。

在测试驱动开发中,我们的目标是编写生产就绪的代码,这些代码能够通过设计的单元测试。通过设计的单元测试对代码中的模块和组件进行更高覆盖,可以帮助你在安心的情况下修订与机器学习生命周期任何组件相关的代码。

在本节中,你学习了单元测试,但其他技术可以帮助我们在可靠的编程和机器学习模型开发中,例如差异测试。我们将在下一节介绍这一内容。

机器学习差异测试

差异测试试图检查同一软件的两个版本,视为基准版本和测试版本,在相同的输入上进行比较,然后比较输出。这个过程帮助我们确定输出是否相同,并识别意外的差异(Gulzar 等人,2019 年;图 8**.3):

图 8.3 – 差异测试的简化流程图,作为测试同一数据上同一过程两个实现输出的过程

图 8.3 – 差异测试的简化流程图,作为测试同一数据上同一过程两个实现输出的过程

在差异测试中,基准版本已经过验证,并被认为是批准版本,而测试版本需要与基准版本进行比较,以产生正确的输出。在差异测试中,我们还可以旨在评估基准版本和测试版本输出之间的观察到的差异是否是预期的或可以解释的。

在机器学习建模中,我们还可以在比较同一数据上同一算法的两个不同实现时从差异测试中受益。例如,我们可以用它来比较使用scikit-learn和 Spark MLlib构建的模型,作为机器学习建模的两个不同库。如果我们需要使用scikit-learn重新创建一个模型并将其添加到我们的管道中,而原始模型是在 Spark MLlib中构建的,我们可以使用差异测试来评估输出并确保没有差异或差异是预期的(Herbold 和 Tunkel,2023 年)。表 8.1提供了scikit-learn和 Spark MLlib中可用类的一些算法示例。这种方法已被更广泛地用于比较不同深度学习框架之间的模型,例如 TensorFlow 和 PyTorch:

方法 scikit-learn Spark MLlib
逻辑回归 LogisticRegression LogisticRegression
朴素贝叶斯 GaussianNB, MultinomialNB NaiveBayes
决策树 DecisionTree Classifier DecisionTreeClassifier
随机森林 RandomForest Classifier RandomForestClassifier
支持向量机 LinearSVC LinearSVC
多层感知器 MLPClassifier MultilayerPerceptron Classifier
梯度提升 GradientBoosting Classifier GBTClassifier

表 8.1 – scikit-learn 和 Spark MLlib 中一些重叠的算法及其类名

除了单元测试和差异测试之外,实验跟踪是我们可以在机器学习项目中受益的另一种技术。

跟踪机器学习实验

跟踪我们的机器学习实验将帮助我们减少得出无效结论和选择不可靠模型的风险。机器学习中的实验跟踪是关于保存实验信息——例如,使用的数据——测试性能和用于性能评估的指标,以及用于建模的算法和超参数。以下是使用机器学习实验跟踪工具的一些重要考虑因素:

  • 你能否将工具集成到你的 持续集成/持续部署CI/CD)管道和机器学习建模框架中?

  • 你能重现你的实验吗?

  • 你能否轻松地搜索实验以找到最佳模型或具有不良或意外行为的模型?

  • 它是否会引起任何安全或隐私问题?

  • 工具是否帮助你更好地在机器学习项目中协作?

  • 工具是否允许你跟踪硬件(例如,内存)消耗?

表 8.2 中提供了常用的机器学习实验跟踪工具及其 URL:

工具 URL
MLflow Tracking mlflow.org/docs/latest/tracking.html
DVC dvc.org/doc/use-cases/experiment-tracking
Weights & Biases wandb.ai/site/experiment-tracking
Comet ML www.comet.com/site/products/ml-experiment-tracking/
ClearML clear.ml/clearml-experiment/
Polyaxon polyaxon.com/product/#tracking
TensorBoard www.tensorflow.org/tensorboard
Neptune AI neptune.ai/product/experiment-tracking
SageMaker aws.amazon.com/sagemaker/experiments/

表 8.2 – 教学机器学习实验的工具示例

在这里,我们想要在 Python 中练习 MLflow Tracking。首先,我们需要导入所需的库:

import pandas as pdimport numpy as np
from sklearn.metrics import mean_squared_error, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier as RF
from sklearn.datasets import load_breast_cancer
import mlflow
import mlflow.sklearn
np.random.seed(42)

然后,我们必须定义一个函数来评估我们想要测试的模型的结果:

def eval_metrics(actual, pred, pred_proba):    rmse = np.sqrt(mean_squared_error(actual, pred))
    roc_auc = roc_auc_score(actual, pred_proba)
    return rmse, roc_auc

接下来,我们必须从 scikit-learn 加载乳腺癌数据集以进行建模:

X, y = load_breast_cancer(return_X_y=True)# split the data into training and test sets. (0.7, 0.3) split
X_train, X_test, y_train, y_test = train_test_split(X, y,
    test_size = 0.3, random_state=42)

现在,我们准备使用 mlflow 定义一个实验:

experiment_name = "mlflow-randomforest-cancer"existing_exp = mlflow.get_experiment_by_name(
    experiment_name)
if not existing_exp:
    experiment_id = mlflow.create_experiment(
        experiment_name, artifact_location="...")
else:
    experiment_id = dict(existing_exp)['experiment_id']
mlflow.set_experiment(experiment_name)

现在,我们必须检查三个不同的决策树数量,或者三个不同的估计器数量,在加载的乳腺癌数据集上构建、训练和测试三个不同的随机森林模型。这三个运行的所有信息都将存储在指定的实验中,但作为不同的运行。正如你可以在下面的代码中看到的那样,我们使用了 mlflow 的不同功能:

  • mlflow.start_run:作为实验的一部分启动一个运行

  • mlflow.log_param:为了记录模型作为超参数的估计器数量

  • mlflow.log_metric:为了记录在定义的测试集上模型性能的计算指标

  • mlflow.sklearn.log_model:为了记录模型:

for idx, n_estimators in enumerate([5, 10, 20]):    rf = RF(n_estimators = n_estimators, random_state = 42)
    rf.fit(X_train, y_train)
    pred_probs = rf.predict_proba(X_test)
    pred_labels = rf.predict(X_test)
    # calculating rmse and roc-auc for the randorm forest
    # model predictions on the test set
    rmse, roc_auc = eval_metrics(actual = y_test,
        pred = pred_labels,pred_proba = [
            iter[1]for iter in pred_probs])
    # start mlflow
    RUN_NAME = f"run_{idx}"
    with mlflow.start_run(experiment_id=experiment_id,
        run_name=RUN_NAME) as run:
            # retrieve run id
            RUN_ID = run.info.run_id
        # track parameters
        mlflow.log_param("n_estimators", n_estimators)
        # track metrics
        mlflow.log_metric("rmse", rmse)
        # track metrics
        mlflow.log_metric("roc_auc", roc_auc)
        # track model
        mlflow.sklearn.log_model(rf, "model")

我们还可以检索已存储的实验,如下所示:

from mlflow.tracking import MlflowClientesperiment_name = "mlflow-randomforest-cancer"
client = MlflowClient()
# retrieve experiment information
experiment_id = client.get_experiment_by_name(
    esperiment_name).experiment_id

然后,我们可以获取该实验中不同运行的详细信息:

# retrieve runs information (parameter: 'n_estimators',    metric: 'roc_auc')
experiment_info = mlflow.search_runs([experiment_id])
# extracting run ids for the specified experiment
runs_id = experiment_info.run_id.values
# extracting parameters of different runs
runs_param = [client.get_run(run_id).data.params[
    "n_estimators"] for run_id in runs_id]
# extracting roc-auc across different runs
runs_metric = [client.get_run(run_id).data.metrics[
    "roc_auc"] for run_id in runs_id]

我们可以根据用于模型测试的指标来识别最佳运行,例如本例中的 ROC-AUC:

df = mlflow.search_runs([experiment_id],    order_by=["metrics.roc_auc"])
best_run_id = df.loc[0,'run_id']
best_model_path = client.download_artifacts(best_run_id,
    "model")
best_model = mlflow.sklearn.load_model(best_model_path)
print("Best model: {}".format(best_model))

这将产生以下输出:

Best mode: RandomForestClassifier(n_estimators=5,    random_state=42)

如果需要,我们还可以删除运行或实验的运行,如下所示。但你需要确保你确实希望删除此类信息:

# delete runs (make sure you are certain about deleting the runs)for run_id in runs_id:
    client.delete_run(run_id)
# delete experiment (make sure you are certain about deleting the experiment)
client.delete_experiment(experiment_id)

在本节中,你学习了在机器学习环境中进行实验跟踪。你将在下一两章中学习更多关于你在机器学习项目中用于风险控制的技术。

摘要

在本章中,你学习了使用单元测试进行驱动开发以控制你的机器学习开发项目中的风险。你学习了使用 pytest 库在 Python 中的单元测试。我们还简要回顾了差分测试的概念,这有助于你比较你的机器学习模块和软件的不同版本。稍后,你学习了模型实验跟踪作为一项重要工具,它不仅有助于你的模型实验和选择,还有助于你在机器学习项目中进行风险控制。你练习了使用 mlflow 作为广泛使用的机器学习实验跟踪工具之一。现在,你知道如何通过测试驱动开发和实验跟踪来开发可靠的模型和编程模块。

在下一章中,你将学习关于测试模型、评估其质量以及监控其在生产中的性能的策略。你将了解模型监控、集成测试和模型管道及基础设施测试的实用方法。

问题

  1. pytest 如何帮助你开发机器学习项目中的代码模块?

  2. pytest fixtures 如何帮助你使用 pytest

  3. 什么是差分测试,何时需要它?

  4. 什么是 mlflow 以及它如何帮助你在机器学习建模项目中?

参考文献

  • Herbold, Steffen, 和 Steffen Tunkel. 机器学习中的差分测试:超越深度学习的分类算法分析. 实证软件工程 28.2 (2023): 34。

  • Lichman, M. (2013). UCI 机器学习仓库 [archive.ics.uci.edu/ml]。Irvine, CA:加州大学信息与计算机科学学院。

  • Gulzar, Muhammad Ali, Yongkang Zhu, 和 Xiaofeng Han. 差分测试的感知与实践. 2019 IEEE/ACM 第 41 届国际软件工程会议:软件工程实践(ICSE-SEIP)。IEEE,2019。

第九章:生产测试和调试

您可能对训练和测试机器学习模型感到兴奋,而没有考虑到模型在生产中的意外行为以及您的模型如何融入更大的技术。大多数学术课程不会详细介绍测试模型、评估其质量以及在生产前和生产中监控其性能的策略。在本章中,我们将回顾测试和调试生产中模型的重要概念和技术。

在本章中,我们将涵盖以下主题:

  • 基础设施测试

  • 机器学习管道的集成测试

  • 监控和验证实时性能

  • 模型断言

到本章结束时,您将了解基础设施和集成测试的重要性,以及模型监控和断言。您还将了解如何使用 Python 库,以便在项目中从中受益。

技术要求

在本章中,以下要求应予以考虑,因为它们将帮助您更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • pytest >= 7.2.2

  • 您还必须具备机器学习生命周期的基础知识。

您可以在 GitHub 上找到本章的代码文件,地址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter09

基础设施测试

基础设施测试是指验证和验证部署、管理和扩展机器学习模型所涉及的各个组件和系统的过程。这包括测试软件、硬件和其他构成支持机器学习工作流程的基础设施的资源。机器学习中的基础设施测试有助于您确保模型得到有效训练、部署和维护。它为您在生产环境中提供可靠的模型。定期的基础设施测试可以帮助您及早发现和修复问题,并降低部署和生产阶段失败的风险。

这里是机器学习基础设施测试的一些重要方面:

  • 数据管道测试:这确保了负责数据收集、选择和整理的数据管道正在正确且高效地工作。这有助于保持训练、测试和部署机器学习模型的数据质量和一致性。

  • 模型训练和评估:这验证了模型训练过程的功能,例如超参数调整和模型评估。这个过程消除了训练和评估中的意外问题,以实现可靠和负责任的模式。

  • 模型部署和托管:这项测试用于检查在生产环境中部署训练好的模型的过程,确保服务基础设施,如 API 端点,能够正确工作并能处理预期的请求负载。

  • 监控和可观察性:这项测试用于检查提供对机器学习基础设施性能和行为洞察的监控和日志系统。

  • 集成测试:这项测试验证机器学习基础设施的所有组件,如数据管道、模型训练系统和部署平台,是否能够无缝且无冲突地协同工作。

  • 可伸缩性测试:这项测试评估基础设施根据不断变化的需求(如增加的数据量、更高的用户流量或更复杂的模型)进行扩展或缩减的能力。

  • 安全和合规性测试:这项测试确保机器学习基础设施满足必要的网络安全要求、数据保护法规和隐私标准。

现在你已经了解了基础设施测试的重要性和好处,你准备好学习相关的工具,这些工具可以帮助你在模型部署和基础设施管理中。

基础设施即代码工具

基础设施即代码IaC)和配置管理工具,如ChefPuppetAnsible,可以用于自动化软件和硬件基础设施的部署、配置和管理。这些工具可以帮助我们确保在不同环境中的一致性和可靠性。让我们了解 Chef、Puppet 和 Ansible 是如何工作的,以及它们如何能帮助你在项目中:

  • Chef (www.chef.io/products/chef-infrastructure-management):Chef 是一个开源配置管理工具,它依赖于客户端-服务器模型,其中 Chef 服务器存储所需的配置,Chef 客户端将其应用于节点。

  • Puppet (www.puppet.com/):Puppet 是另一个开源配置管理工具,它以客户端-服务器模式或作为独立应用程序工作。Puppet 通过定期从 Puppet 主服务器拉取配置来在节点上强制执行所需的配置。

  • Ansible (www.ansible.com/):Ansible 是一个开源且易于使用的配置管理、编排和自动化工具,它与节点通信并应用配置。

这些工具主要关注基础设施管理和自动化,但它们也具有模块或插件,可以执行基础设施的基本测试和验证。

基础设施测试工具

Test Kitchen、ServerSpec 和 InSpec 是我们可以使用的基础设施测试工具,以验证和验证我们基础设施所需配置和行为:

  • Test Kitchen (github.com/test-kitchen/test-kitchen):Test Kitchen 是一个主要用于与 Chef 一起使用的集成测试框架,但也可以与其他 IaC 工具(如 Ansible 和 Puppet)一起工作。它允许你在不同的平台和配置上测试你的基础设施代码。Test Kitchen 在各种平台上创建临时实例(使用 Docker 或云提供商等驱动程序),合并你的基础设施代码,并对配置的实例运行测试。你可以使用 Test Kitchen 与不同的测试框架(如 ServerSpec 或 InSpec)一起定义你的测试。

  • ServerSpec (serverspec.org/):ServerSpec 是一个用于基础设施的 行为驱动开发BDD)测试框架。它允许你用人类可读的语言编写测试。ServerSpec 通过在目标系统上执行命令并检查输出与预期结果是否一致来测试你基础设施的期望状态。你可以使用 ServerSpec 与 Test Kitchen 或其他 IaC 工具一起确保你的基础设施配置正确。

  • InSpec (github.com/inspec/inspec):InSpec 是由 Chef 开发的开源基础设施测试框架。它以人类可读的语言定义测试和合规性规则。你可以独立运行 InSpec 测试,或者与 Test Kitchen、Chef 或其他 IaC 平台等工具一起运行。

这些工具确保我们的 IaC 和配置管理设置在部署前按预期工作,以实现跨不同环境的一致性和可靠性。

使用 Pytest 进行基础设施测试

我们还可以使用 Pytest,这是我们上一章中用于单元测试的工具,也可以用于基础设施测试。假设我们编写了应该在名为 test_infrastructure.py 的 Python 文件中以 test_ 前缀开始的测试函数。我们可以使用 Python 库(如 paramikorequestssocket)与我们的基础设施交互(例如,进行 API 调用、连接到服务器等)。例如,我们可以测试 Web 服务器是否以状态码 200 响应:

import requestsdef test_web_server_response():
    url = "http://your-web-server-url.com"
    response = requests.get(url)
    assert response.status_code == 200,
        f"Expected status code 200,
        but got {response.status_code}"

然后,我们可以运行上一章中解释的测试。

除了基础设施测试之外,其他技术可以帮助你为成功部署模型做准备,例如集成测试,我们将在下一章中介绍。

机器学习管道的集成测试

当我们训练一个机器学习模型时,我们需要评估它与其所属的更大系统其他组件的交互效果。集成测试帮助我们验证模型在整体应用程序或基础设施中是否正确工作,并满足预期的性能标准。在我们的机器学习项目中,以下是一些重要的集成测试组件:

  • 测试数据管道:我们需要评估在模型训练之前的数据预处理组件(如数据整理)在训练和部署阶段之间的一致性。

  • 测试 API:如果我们的机器学习模型通过 API 公开,我们可以测试 API 端点以确保它正确处理请求和响应。

  • 测试模型部署:我们可以使用集成测试来评估模型的部署过程,无论它是作为独立服务、容器内还是嵌入在应用程序中部署。这个过程有助于我们确保部署环境提供必要的资源,例如 CPU、内存和存储,并且模型在需要时可以更新。

  • 测试与其他组件的交互:我们需要验证我们的机器学习模型与数据库、用户界面或第三方服务无缝工作。这可能包括测试模型预测在应用程序中的存储、显示或使用方式。

  • 测试端到端功能:我们可以使用模拟真实场景和用户交互的端到端测试来验证模型的预测在整体应用程序的上下文中是准确的、可靠的和有用的。

我们可以从集成测试中受益,以确保在实际应用程序中的平稳部署和可靠运行。我们可以使用几个工具和库来为我们的 Python 机器学习模型创建健壮的集成测试。表 9.1显示了集成测试的一些流行工具:

工具 简要描述 URL
Pytest 一个在 Python 中广泛用于单元和集成测试的框架 docs.pytest.org/en/7.2.x/
Postman 一个 API 测试工具,用于测试机器学习模型与 RESTful API 之间的交互 www.postman.com/
Requests 一个 Python 库,通过发送 HTTP 请求来测试 API 和服务 requests.readthedocs.io/en/latest/
Locust 一个允许你模拟用户行为并测试机器学习模型在各种负载条件下的性能和可扩展性的负载测试工具 locust.io/
Selenium 一个浏览器自动化工具,你可以用它来测试利用机器学习模型的 Web 应用程序的端到端功能 www.selenium.dev/

表 9.1 – 集成测试的流行工具

使用 pytest 进行集成测试

在这里,我们想要练习使用pytest对一个具有两个组件的简单 Python 应用程序进行集成测试:一个数据库和一个服务,它们都从数据库中检索数据。让我们假设我们有database.pyservice.py脚本文件:

database.py:

class Database:    def __init__(self):
        self.data = {"users": [{"id": 1,
            "name": "John Doe"},
            {"id": 2, "name": "Jane Doe"}]}
    def get_user(self, user_id):
        for user in self.data["users"]:
            if user["id"] == user_id:
                return user
            return None

service.py:

from database import Databaseclass UserService:
    def __init__(self, db):
        self.db = db
    def get_user_name(self, user_id):
        user = self.db.get_user(user_id)
        if user:
            return user["name"]
        return None

现在,我们将使用 pytest 编写一个集成测试,以确保 UserService 组件与 Database 组件正确工作。首先,我们需要在名为 test_integration.py 的测试脚本文件中编写我们的测试,如下所示:

import pytestfrom database import Database
from service import UserService
@pytest.fixture
def db():
    return Database()
@pytest.fixture
def user_service(db):
    return UserService(db)
def test_get_user_name(user_service):
    assert user_service.get_user_name(1) == "John Doe"
    assert user_service.get_user_name(2) == "Jane Doe"
    assert user_service.get_user_name(3) is None

定义好的 test_get_user_name 函数通过检查 get_user_name 方法是否为不同的用户 ID 返回正确的用户名来测试 UserServiceDatabase 组件之间的交互。

要运行测试,我们可以在终端中执行以下命令:

pytest test_integration.py

使用 pytest 和 requests 进行集成测试

我们可以将 requestspytest Python 库结合起来,对我们的机器学习 API 进行集成测试。我们可以使用 requests 库发送 HTTP 请求,并使用 pytest 库编写测试用例。假设我们有一个机器学习 API,其端点如下:

POST http://mldebugging.com/api/v1/predict

在这里,API 接受一个包含输入数据的 JSON 有效负载:

{    "rooms": 3,
    "square_footage": 1500,
    "location": "suburban"
}

这将返回一个包含预测价格的 JSON 响应:

{    "predicted_price": 700000
}

现在,我们需要创建一个名为 test_integration.py 的测试脚本文件:

import requestsimport pytest
API_URL = "http://mldebugging.com/api/v1/predict"
def test_predict_house_price():
    payload = {
        "rooms": 3,
        "square_footage": 1500,
        "location": "suburban"
    }
    response = requests.post(API_URL, json=payload)
    assert response.status_code == 200
    assert response.headers["Content-Type"] == "application/json"
    json_data = response.json()
    assert "predicted_price" in json_data
    assert isinstance(json_data["predicted_price"],
        (int, float))

要运行测试,我们可以在终端中执行以下命令:

pytest test_integration.py

在这个例子中,我们定义了一个名为 test_predict_house_price 的测试函数,该函数向 API 发送 POST 请求(即用于将数据提交到服务器以创建或更新资源的 HTTP 方法),并将输入数据作为 JSON 有效负载。然后,测试函数检查 API 响应的状态码、内容类型和预测价格值。如果您想尝试使用您拥有的真实 API,请将示例 URL 替换为实际的 API 端点。

除了本章中提到的测试策略外,您还可以通过模型监控和断言来确保在生产环境中成功部署和可靠的模型。

监控和验证实时性能

在部署期间,我们可以使用监控和日志记录机制来跟踪模型的性能并检测潜在问题。我们可以定期评估已部署的模型,以确保它继续满足性能标准或其他标准,例如无偏见,这是我们为其定义的。我们还可以利用模型监控的信息,根据需要更新或重新训练模型。以下是关于部署前和在生产中建模之间差异的三个重要概念:

  • 数据方差:用于模型训练和测试的数据会经过数据整理和所有必要的清理和重新格式化步骤。然而,提供给已部署模型的(即从用户到模型的数据)可能不会经过相同的数据处理过程,这会导致生产中模型结果出现差异。

  • 数据漂移:如果生产中特征或独立变量的特征和意义与建模阶段的不同,就会发生数据漂移。想象一下,你使用第三方工具为人们的健康或财务状况生成分数。该工具背后的算法可能会随时间改变,当你的模型在生产中使用时,其范围和意义将不会相同。如果你没有相应地更新你的模型,那么你的模型将不会按预期工作,因为特征值的含义在用于训练的数据和部署后的用户数据之间将不同。

  • 概念漂移:概念漂移是指输出变量定义的任何变化。例如,由于概念漂移,训练数据和生产之间的实际决策边界可能不同,这意味着在训练中付出的努力可能导致在生产中远离现实的决策边界。

除了上一章中介绍的 MLflow 之外,还有 Python 和库工具(如 表 9.2 中所示),你可以使用这些工具来监控机器学习模型的表现、I/O 数据和基础设施,帮助你维护生产环境中的模型质量和可靠性:

工具 简要描述 URL
Alibi Detect 一个专注于异常值、对抗性和漂移检测的开源 Python 库 github.com/SeldonIO/alibi-detect
Evidently 一个开源 Python 库,用于分析和监控机器学习模型,提供各种模型评估技术,如数据漂移检测和模型性能监控 github.com/evidentlyai/evidently
ELK Stack Elasticsearch, Logstash, and Kibana (ELK) 是一个流行的用于收集、处理和可视化来自各种来源(包括机器学习模型)的日志和指标的工具栈 www.elastic.co/elk-stack
WhyLabs 一个为机器学习模型提供可观察性和监控的平台 whylabs.ai/

表 9.2 – 机器学习模型监控和漂移检测的流行工具

我们还可以从一些统计和可视化技术中受益,用于检测和解决数据和概念漂移。以下是一些用于数据漂移评估的方法示例:

  • 统计测试:我们可以使用假设检验,如 Kolmogorov-Smirnov 测试卡方测试Mann-Whitney U 测试,来确定输入数据的分布是否在时间上发生了显著变化。

  • 分布指标:我们可以使用分布指标,如均值、标准差、分位数和其他汇总统计量,来比较训练数据和生产中的新数据。这些指标中的显著差异可能表明数据漂移。

  • 可视化:我们可以使用直方图、箱线图或散点图等可视化技术来展示训练数据和生产中新数据的输入特征,以帮助识别数据分布的变化。

  • 特征重要性:我们可以监控特征重要性值的变化。如果新数据中的特征重要性与训练数据中的特征重要性有显著差异,这可能表明数据漂移。

  • 距离度量:我们可以使用诸如Kullback-Leibler 散度Jensen-Shannon 散度等距离度量来衡量训练数据与新数据分布之间的差异。

模型断言是另一种技术,正如你接下来将要学习的,它可以帮助你构建和部署可靠的机器学习模型。

模型断言

我们可以在机器学习建模中使用传统的编程断言来确保模型按预期行为。模型断言可以帮助我们早期发现问题,例如输入数据漂移或其他可能影响模型性能的意外行为。我们可以将模型断言视为一组在模型训练、验证甚至部署期间进行检查的规则,以确保模型的预测满足预定义的条件。模型断言可以从许多方面帮助我们,例如检测模型或输入数据的问题,使我们能够在它们影响模型性能之前解决它们。它们还可以帮助我们保持模型性能。以下是一些模型断言的示例:

  • 输入数据断言:这些可以检查输入特征是否在预期的范围内或具有正确的数据类型。例如,如果一个模型根据房间数量预测房价,你可能会断言房间数量始终是正整数。

  • 输出数据断言:这些可以检查模型的预测是否满足某些条件或约束。例如,在二元分类问题中,你可能会断言预测的概率在 0 到 1 之间。

让我们通过一个简单的 Python 中模型断言的例子来了解。在这个例子中,我们将使用scikit-learn中的简单线性回归模型,使用玩具数据集根据房间数量预测房价。首先,让我们创建一个玩具数据集并训练线性回归模型:

import numpy as npfrom sklearn.linear_model import LinearRegression
# Toy dataset with number of rooms and corresponding house prices
X = np.array([1, 2, 3, 4, 5]).reshape(-1, 1)
y = np.array([100000, 150000, 200000, 250000, 300000])
# Train the linear regression model
model = LinearRegression()
model.fit(X, y)

现在,让我们定义我们的模型断言,以便它们执行以下操作:

  1. 检查输入(房间数量)是否为正整数。

  2. 检查预测的房价是否在预期范围内。

下面是执行这些操作的代码:

def assert_input(input_data):    assert isinstance(input_data, int),
        "Input data must be an integer"
    assert input_data > 0, "Number of rooms must be positive"
def assert_output(predicted_price, min_price, max_price):
    assert min_price <= predicted_price <= max_price,
        f"Predicted price should be between {min_price} and 
        {max_price}"

现在,我们可以使用定义好的模型断言函数,如下所示:

# Test the assertions with example input and output datainput_data = 3
assert_input(input_data)
predicted_price = model.predict([[input_data]])[0]
assert_output(predicted_price, 50000, 350000)

assert_input函数检查输入数据(即房间数量)是否为整数且为正数。assert_output函数检查预测的房价是否在指定的范围内(例如,在本例中为 50,000 至 350,000)。前面的代码没有给出任何AssertionError断言,因为它符合模型断言函数中定义的标准。假设我们不是使用整数3,而是使用一个字符串,如下所示:

input_data = '3'assert_input(input_data)

这里,我们得到以下AssertionError

AssertionError: Input data must be an integer

假设我们定义了assert_output的输出范围,使其在50000150000之间,并使用具有3个卧室的房屋模型预测,如下所示:

input_data = 3predicted_price = model.predict([[input_data]])[0]
assert_output(predicted_price, 50000, 150000)

我们将得到以下AssertionError

AssertionError: Predicted price should be between 50000 and 150000

模型断言是另一种技术,与模型监控并列,有助于确保我们模型的可靠性。

有了这些,我们就结束了这一章。

摘要

在本章中,你学习了测试驱动开发的重要概念,包括基础设施和集成测试。你学习了实现这两种类型测试的可用工具和库。我们还通过示例学习了如何使用pytest库进行基础设施和集成测试。你还学习了模型监控和模型断言作为评估我们模型在生产和生产环境中的行为之前和之后的两个其他重要主题。这些技术和工具帮助你设计策略,以便你在生产环境中成功部署并拥有可靠的模型。

在下一章中,你将了解可重复性,这是正确机器学习建模中的一个重要概念,以及你如何可以使用数据和模型版本控制来实现可重复性。

问题

  1. 你能解释数据漂移和概念漂移之间的区别吗?

  2. 模型断言如何帮助你开发可靠的机器学习模型?

  3. 集成测试的组件有哪些例子?

  4. 我们如何使用 Chef、Puppet 和 Ansible?

参考文献

  • Kang, Daniel, et al. 模型监控与改进的模型断言. 机器学习与系统会议论文集 2 (2020): 481-496.

第十章:版本控制和可重复性机器学习建模

可重复性是一个重要的主题,有助于机器学习开发者回到机器学习生命周期的不同阶段,并识别模型改进的机会。通过访问通过机器学习生命周期生成的不同版本的数据和模型,可以帮助我们提高项目的可重复性。

在本章中,你将了解机器学习建模中可重复性的意义和重要性。你将学习如何在机器学习管道中集成数据版本控制,以帮助你在项目中实现更有效的协作,并在模型中实现可重复性。你还将了解模型版本化的不同方面以及将其集成到管道中的工具。

我们将涵盖以下主题:

  • 机器学习中的可重复性

  • 数据版本控制

  • 模型版本控制

到本章结束时,你将学会如何在 Python 中利用数据和模型版本控制来为你的建模项目实现可重复性。

技术要求

以下为本章的要求,将帮助你更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • pandas >= 1.4.4

    • sklearn >= 1.2.2

  • DVC >= 1.10.0

  • 你还应该对机器学习生命周期有基本了解

你可以在 GitHub 上找到本章的代码文件,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter10

机器学习中的可重复性

机器学习项目中缺乏可重复性可能会导致资源浪费,并降低你在研究项目中模型和发现的可信度。可重复性在这个上下文中并不是唯一的术语;还有两个其他的关键术语:可重复性可复制性。我们不想深入探讨这些差异的细节。相反,我们想要在这个书中使用可重复性的定义。我们将机器学习中的可重复性定义为不同个人或科学家和开发者团队使用与原始报告或研究中报告的相同数据集、方法和开发环境获得相同结果的能力。我们可以通过适当共享代码、数据、模型参数和超参数以及其他相关信息来确保可重复性,这允许其他人验证并基于我们的发现进行构建。让我们通过两个例子来更好地理解可重复性的重要性。

一家生物技术公司的科学家们试图重现 53 项癌症研究的发现(Begley et al., 2012)。但他们只能重现其中 6 项研究的成果。这些研究并不一定是在机器学习可重复性的背景下进行的,但它突出了科学研究中可重复性的重要性以及基于不可重复发现做出决策或进一步研究和开发的潜在后果。

另一个在数据分析和数据驱动发现背景下强调可重复性重要性的例子是所谓的Reinhart-Rogoff Excel 错误(Reinhart, C.,and Rogoff, K.,2010)。2010 年,经济学家 Carmen Reinhart 和 Kenneth Rogoff 发表了一篇论文,提出高公共债务与经济增长之间存在负相关关系。这篇论文影响了全球的经济政策。然而,2013 年,其他研究人员发现他们在 Excel 计算中存在错误,这显著影响了结果。但后来,有人认为错误不是结论背后的驱动因素(Maziarz, 2017)。在这里,我们不想关注他们的发现,而是想强调分析的可重复性可以消除任何进一步的争论,无论原始分析中是否存在错误。

以下三个概念可以帮助您在机器学习建模项目中实现可重复性:

  • 代码版本控制:能够访问机器学习生命周期任何阶段的代码版本对于重复分析或训练和评估过程至关重要

  • 数据版本控制:为了实现可重复性,我们需要访问机器学习生命周期任何阶段使用的版本数据,例如训练和测试

  • 模型版本控制:拥有一个具有冻结参数且在初始化、评估或其他建模过程中的随机性为零的模型版本,有助于您消除不可重复性的风险

我们在第一章“超越代码调试”中简要介绍了代码版本控制。在这里,我们将专注于数据和模型版本控制,以帮助您设计可重复的机器学习模型。

数据版本控制

机器学习生命周期中,我们有不同的阶段,从数据收集和选择到数据整理和转换,数据在这些过程中逐步准备以供模型训练和评估。数据版本控制有助于我们在这些过程中保持数据完整性和可重复性。数据版本控制是跟踪和管理数据集变化的过程。它涉及记录数据的不同版本或迭代,使我们能够在需要时访问和比较先前状态或恢复早期版本。通过确保更改得到适当记录和版本控制,我们可以降低数据丢失或不一致的风险。

有一些数据版本控制工具可以帮助我们管理和跟踪我们想要用于机器学习建模或评估模型可靠性和公平性的数据变化。以下是一些流行的数据版本控制工具:

这些工具中的每一个都为你提供了不同的数据版本控制能力。你可以根据数据的大小、项目的性质以及与其他工具集成的期望水平来选择满足你需求的工具。

这里是一个使用 DVC 进行数据版本控制的 Python 示例。在安装 DVC 后,你可以在终端中写入以下命令来初始化它:

dvc init

这将创建一个.dvc目录并设置必要的配置。现在,让我们创建一个小的 DataFrame 并将其保存为 Python 中的 CSV 文件:

import pandas as pd# create a sample dataset
data_df = pd.DataFrame({'feature 1': [0.5, 1.2, 0.4, 0.8],
    'feature 2': [1.1, 1.3, 0.6, 0.1]})
# save the dataset to a CSV file
data_df.to_csv('dataset.csv', index=False)

现在,我们可以将dataset.csv文件添加到 DVC 中并提交更改,类似于使用 Git 提交代码更改:

dvc add dataset.csvgit add dataset.csv.dvc .gitignore
git commit -m "add initial dataset"

这将创建一个data.csv.dvc文件来跟踪数据集的版本,并将data.csv添加到.gitignore中,以便 Git 不跟踪实际的数据文件。现在,我们可以按如下方式修改数据集并使用相同的名称保存它:

# Add a new column to the datasetdata_df['feature 3'] = [0.05, 0.6, 0.4, 0.9]
# Save the modified dataset to the same CSV file
data_df.to_csv('dataset.csv', index=False)

我们也可以提交更改并将其保存为不同的版本:

dvc add dataset.csvgit add dataset.csv.dvc
git commit -m "update dataset with new feature column"

现在我们有了dataset.csv文件的两个版本,我们可以在需要时使用以下命令在终端切换到之前的版本或最新版本的数据集:

# go back to the previous version of the datasetgit checkout HEAD^
dvc checkout
# return to the latest version of the dataset
git checkout master
dvc checkout

但如果你有很多相同文件或数据的版本,你可以使用 DVC(Data Version Control)作为其一部分的其他简单命令。

除了对数据进行版本控制外,我们还需要在整个开发周期中跟踪和管理模型的不同版本。我们将在下一章中介绍这一点。

模型版本控制

生产的模型是经过一系列实验和模型修改的最终结果,包括不同版本的训练和测试数据,以及不同的机器学习方法和相应的超参数。模型版本化帮助我们确保对模型所做的更改是可追溯的,有助于在机器学习项目中建立可重复性。它确保了在特定时间点可以轻松地重现每个模型的版本,通过提供模型参数、超参数和训练数据的完整快照。它允许我们在新部署的模型出现问题时轻松回滚到先前的版本,或者恢复可能被无意中修改或删除的旧版本。

让我们通过一个非常简单的例子来更好地理解模型版本化的必要性。图 10.1展示了具有五个估计器或决策树的随机森林模型的不同最大深度。如果我们简单地改变用于将数据分割成训练集和测试集的随机状态,使用scikit-learn中的train_test_split(),并对RandomForestClassifier()模型进行模型初始化,我们得到不同的对数损失值和随机森林模型中树的最大深度的依赖性:

图 10.1 – 使用不同的随机状态进行建模和数据分割,从乳腺癌数据集中分离出的训练集和验证集的对数损失

图 10.1 – 使用不同的随机状态进行建模和数据分割,从乳腺癌数据集中分离出的训练集和验证集的对数损失

这是一个小例子,用以展示如果我们的模型没有进行版本控制,这样的简单变化可能会对我们的机器学习建模产生极大的影响。当我们使用实验跟踪工具,如 MLflow 时,我们可以访问所选模型的全部跟踪信息。

为了对模型进行版本控制,我们需要确保以下几点:

  • 我们可以访问相应模型的参数的保存版本

  • 其他必要信息,如模型超参数,已记录或保存以供模型重新训练

  • 需要与模型参数一起用于推理或甚至重新训练和测试的代码已进行版本控制

  • 具有随机性的过程,如模型初始化和训练及测试数据分割,有指定的随机状态或种子

存储模型及其相关文档的方式有很多种。例如,您可以使用序列化库如pickle单独存储模型,或者与 DVC (dvc.org/doc/api-reference/open) 结合使用,如下所示:

with dvc.api.open(model_path, mode='w', remote=remote_url) as f:     pickle.dump(model, f)

为了此目的,您需要指定一个用于保存模型的本地路径,使用pickle.dump,以及使用 DVC 进行模型版本化的远程路径。

摘要

在本章中,你学习了机器学习建模中可重现性的意义和重要性。你还学习了数据版本化和模型版本化,这些有助于我们开发更可靠和可重现的模型和数据分析结果。接下来,你学习了可用于版本化数据和模型的不同的工具和 Python 库。通过本章介绍的概念和实践,你已准备好确保你的机器学习项目中的可重现性。

在下一章中,你将学习如何使用技术来避免和消除数据漂移和概念漂移,这两者构成了模型在部署前后行为差异的两个方面。

问题

  1. 请列举三个可用于数据版本化的工具的例子?

  2. 当你使用 DVC 等工具生成相同数据的不同版本时,你是否需要用不同的名称保存它们?

  3. 你能提供一个例子,说明你使用相同的方法、训练和评估数据,但得到不同的训练和评估性能吗?

参考文献

  • Reinhart, C.,& Rogoff, K. (2010b). 债务与增长的回顾. VOX. CEPRs 政策门户。检索日期:2015 年 9 月 18 日。

  • Reinhart, C.,& Rogoff, K. (2010a). 债务时期的增长.美国经济评论,100,573–578.10.1257/aer.100.2.573。

  • Maziarz, Mariusz. Reinhart-Rogoff 争议作为“新兴相反结果”现象的一个实例.经济方法论杂志,24.3 (2017):213-225。

  • Begley, C. G.,& Ellis, L. M. (2012). 药物开发:提高临床前癌症研究标准.自然,483(7391),531-533。

  • 计算机协会(2016 年).工件审查和徽章.可在www.acm.org/publications/policies/artifact-review-badging在线获取(访问日期:2017 年 11 月 24 日)。

  • Plesser, Hans E. 可重现性 vs. 可复制性:一个混乱术语的简要历史.神经信息学前沿,11 (2018):76。

  • Pineau, J.,Vincent, M.,Larochelle, H.,& Bengio, Y. (2020). 提高机器学习研究可重现性(来自 NeurIPS 2019 可重现性计划的一份报告). arXiv 预印本 arXiv:2003.12206。

  • Raff, E.,Lemire, D.,& Nicholas, C. (2019). 机器学习中算法稳定性的新度量.机器学习研究杂志,20(168),1-32。

  • Gundersen, O. E.,& Kjensmo, S. (2018). 人工智能中的最新技术:可重现性.在第三十二届 AAAI 人工智能会议。

  • Jo, T.,& Bengio, Y. (2017). 测量 CNN 学习表面统计规律的趋势. arXiv 预印本 arXiv:1711.11561。

  • Haibe-Kains, B.,Adam, G. A.,Hosny, A.,Khodakarami, F.,& Waldron, L. (2020). 人工智能中的透明度和可重现性.自然,586(7829),E14-E16。

第十一章:避免和检测数据和相关漂移

我们在第九章 生产测试和调试中讨论了数据和相关漂移在机器学习建模中的影响。在本章中,我们想要更深入地探讨这些概念,并在 Python 中练习检测漂移。

在这里,你将了解我们之前介绍的概念的重要性,例如模型版本控制和模型监控,以避免漂移,并练习使用一些用于漂移检测的 Python 库。

在本章中,我们将涵盖以下主题:

  • 避免模型中的漂移

  • 漂移检测

到本章结束时,你将能够在 Python 中检测你的机器学习模型中的漂移,并在生产中拥有可靠的模型。

技术要求

以下要求适用于本章,因为它们有助于你更好地理解这些概念,让你能够在项目中使用它们,并使用提供的代码进行练习:

  • Python 库需求如下:

    • sklearn >= 1.2.2

    • numpy >= 1.22.4

    • pandas >= 1.4.4

    • alibi_detect >= 0.11.1

    • lightgbm >= 3.3.5

    • evidently >= 0.2.8

  • 需要理解以下内容:

    • 数据和相关漂移

    • 数据和模型版本控制

你可以在 GitHub 上找到本章的代码文件:github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter11

避免模型中的漂移

数据和相关漂移挑战了机器学习模型在生产中的可靠性。我们机器学习项目中的漂移可能具有不同的特征。以下是一些可以帮助你在项目中检测漂移并计划解决它们的特征:

  • 幅度:我们可能会面对数据分布中的差异幅度,导致我们的机器学习模型发生漂移。数据分布中的小变化可能难以检测,而大变化可能更明显。

  • 频率:漂移可能以不同的频率发生。

  • 渐进与突然:数据漂移可能逐渐发生,数据分布的变化在时间上缓慢发生,或者可能突然发生,变化快速且出乎意料。

  • 可预测性:某些类型的漂移可能是可预测的,例如季节性变化或由于外部事件引起的变化。其他类型的漂移可能是不可预测的,例如消费者行为或市场趋势的突然变化。

  • 意图性:漂移可能是故意的,例如对数据生成过程所做的更改,或者无意的,例如随着时间的推移自然发生的变化。

我们需要使用帮助我们在机器学习建模项目中避免漂移发生和累积的技术和实践。

避免数据漂移

在机器学习生命周期的不同阶段访问我们模型的不同数据版本可以帮助我们通过比较训练和生产的数据、评估预处理数据处理或识别可能导致漂移的数据选择标准来更好地检测漂移。模型监控还有助于我们尽早识别漂移并避免累积。

让我们通过简单地检查用于模型训练的数据版本之间的特征分布的均值,以及生产中的新数据来练习漂移监控。我们首先定义一个用于监控数据漂移的类。在这里,我们假设如果两个数据版本之间的分布均值之差大于 0.1,则认为特征发生了漂移:

class DataDriftMonitor:    def __init__(self, baseline_data: np.array,
        threshold_mean: float = 0.1):
            self.baseline = self.calculate_statistics(
                baseline_data)
            self.threshold_mean = threshold_mean
    def calculate_statistics(self, data: np.array):
        return np.mean(data, axis=0)
    def assess_drift(self, current_data: np.array):
        current_stats = self.calculate_statistics(
            current_data)
        drift_detected = False
        for feature in range(0, len(current_stats)):
            baseline_stat = self.baseline[feature]
            current_stat = current_stats[feature]
            if np.abs(current_stat - baseline_stat) > self.threshold_mean:
                drift_detected = True
                print('Feature id with drift:
                    {}'.format(feature))
                print('Mean of original distribution:
                    {}'.format(baseline_stat))
                print('Mean of new distribution:
                    {}'.format(current_stat))
                break
        return drift_detected

然后,我们使用它来识别两个合成数据集之间的漂移:

np.random.seed(23)# Generating a synthetic dataset, as the original data, with 100 datapoints and 5 features
# from a normal distribution centered around 0 with std of 1
baseline_data = np.random.normal(loc=0, scale=1,
    size=(100, 5))
# Create a DataDriftMonitor instance
monitor = DataDriftMonitor(baseline_data,
    threshold_mean=0.1)
# Generating a synthetic dataset, as the original data, with 100 datapoints and 5 features from a normal distribution #centered around 0.2 with std of 1
current_data = np.random.normal(loc=0.15, scale=1,
    size=(100, 5))
# Assess data drift
drift_detected = monitor.assess_drift(current_data)
if drift_detected:
    print("Data drift detected.")
else:
    print("No data drift detected.")

这会产生以下结果:

Feature id with drift: 1Mean of original distribution: -0.09990597519469419
Mean of new distribution: 0.09662442557421645
Data drift detected.

应对概念漂移

我们可以同样定义具有检测概念漂移标准的类和函数,就像我们练习数据漂移检测那样。但我们还可以检查,无论是通过编程还是作为将我们的机器学习模型投入生产时的质量保证的一部分,外部因素可能导致的漂移,例如环境因素、机构或政府政策的变更等。除了监控数据外,我们还可以从特征工程中受益,选择对概念漂移更鲁棒的特征,或者使用动态适应概念漂移的集成模型。

虽然在我们的模型中避免漂移是理想的,但在实践中我们需要准备好检测和消除它。接下来,你将学习检测模型中漂移的技术。从实际的角度来看,避免和检测模型中的漂移非常相似。但比简单地检查特征分布的均值(如我们在本节中用于避免数据漂移)更好的技术,我们将在下一节中练习。

检测漂移

在所有模型中完全避免漂移是不可能的,但我们可以努力尽早检测并消除它们。在这里,我们将通过在 Python 中使用alibi_detectevidently来练习漂移检测。

使用 alibi_detect 进行漂移检测的练习

我们想要练习的广泛使用的 Python 库之一是alibi_detect。我们首先导入必要的 Python 函数和类,并使用scikit-learn中的make_classification生成一个具有 10 个特征和 10,000 个样本的合成数据集:

import numpy as npimport pandas as pd
import lightgbm as lgb
from alibi_detect.cd import KSDrift
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.metrics import balanced_accuracy_score as bacc
# Generate synthetic data
X, y = make_classification(n_samples=10000, n_features=10,
    n_classes=2, random_state=42)

然后,我们将数据分为训练集和测试集:

# Split into train and test setsX_train, X_test, y_train, y_test = train_test_split(X, y,
    test_size=0.2, random_state=42)

然后,我们在训练数据上训练一个LightGBM分类器:

train_data = lgb.Dataset(X_train, label=y_train)params = {
    "objective": "binary",
    "metric": "binary_logloss",
    "boosting_type": "gbdt"
}
clf = lgb.train(train_set = train_data, params = params,
    num_boost_round=100)

现在,我们评估模型在测试集上的性能,并定义一个用于漂移检测的测试标签 DataFrame:

# Predict on the test sety_pred = clf.predict(X_test)
y_pred = [1 if iter > 0.5 else 0 for iter in y_pred]
# Calculate the balanced accuracy of the predictions
balanced_accuracy = bacc(y_test, y_pred)
print('Balanced accuracy on the synthetic test set:
    {}'.format(balanced_accuracy))
# Create a DataFrame from the test data and predictions
df = pd.DataFrame(X_test,
    columns=[f"feature_{i}" for i in range(10)])
df["actual"] = y_test
df["predicted"] = y_pred

现在,我们使用测试数据点的预测和实际标签定义的 DataFrame 来检测漂移。我们从 alibi_detect 包初始化 KSDrift 检测器并将其拟合到训练数据上。我们使用检测器的 predict 方法在测试数据上计算漂移分数和 p 值。漂移分数表示每个特征的漂移水平,而 p 值表示漂移的统计显著性。如果任何漂移分数或 p 值超过某个阈值,我们可能认为模型正在经历漂移,并采取适当的行动,例如使用更新后的数据重新训练模型:

# Initialize the KSDrift detectordrift_detector = KSDrift(X_train)
# Calculate the drift scores and p-values
drift_scores = drift_detector.predict(X_test)
p_values = drift_detector.predict(X_test,
    return_p_val=True)
# Print the drift scores and p-values
print("Drift scores:")
print(drift_scores)
print("P-values:")
print(p_values)

这里是生成的漂移分数和 p 值。由于所有 p 值都大于 0.1,并且考虑到阈值是 0.005,我们可以说在这种情况下没有检测到漂移:

Drift scores:{'data': {'is_drift': 0, 'distance': array([0.02825 , 0.024625, 0.0225  , 0.01275 , 0.014   , 0.017125,0.01775 , 0.015125, 0.021375, 0.014625], dtype=float32), 'p_val': array([0.15258548, 0.28180763, 0.38703775, 0.95421314, 0.907967 ,0.72927415, 0.68762517, 0.8520056 , 0.45154762, 0.87837887],dtype=float32), 'threshold': 0.005}, 'meta': {'name': 'KSDrift', 'online': False, 'data_type': None, 'version': '0.11.1', 'detector_type': 'drift'}}
P-values:
{'data': {'is_drift': 0, 'distance': array([0.02825 , 0.024625, 0.0225  , 0.01275 , 0.014   , 0.017125,0.01775 , 0.015125, 0.021375, 0.014625], dtype=float32), 'p_val': array([0.15258548, 0.28180763, 0.38703775, 0.95421314, 0.907967 ,0.72927415, 0.68762517, 0.8520056 , 0.45154762, 0.87837887],dtype=float32), 'threshold': 0.005}, 'meta': {'name': 'KSDrift', 'online': False, 'data_type': None, 'version': '0.11.1', 'detector_type': 'drift'}}

使用 evidently 进行漂移检测实践

另一个广泛使用的用于漂移检测的 Python 库,我们将在下面进行实践的是 evidently。在导入必要的库之后,我们从 scikit-learn 加载糖尿病数据集:

import pandas as pdimport numpy as np
from sklearn import datasets
from evidently.report import Report
from evidently.metrics import DataDriftTable
from evidently.metrics import DatasetDriftMetric
diabetes_data = datasets.fetch_openml(name='diabetes',
    version=1, as_frame='auto')
diabetes = diabetes_data.frame
diabetes = diabetes.drop(['class', 'pres'], axis = 1)

以下表格显示了我们要从糖尿病数据集中用于漂移检测的特征及其含义:

特征 描述
preg 怀孕次数
plas 口服葡萄糖耐量测试 2 小时后的血浆葡萄糖浓度
skin 三角肌皮肤褶皱厚度(毫米)
insu 2 小时血清胰岛素(微 U/ml)
mass 体重指数(千克/(米)²)
pedi 糖尿病家系函数
Age 年龄(年)

表 11.1 – 用于漂移检测的糖尿病数据集中特征名称及其描述(Efron 等,2004)

我们将数据点分为两组,称为参考集和当前集,然后使用 evidently.report.Reference 集中的 Report() 生成漂移报告,包括所有年龄小于或等于 40 岁的个体,以及当前集包括数据集中年龄大于 40 岁的其他个体:

diabetes_reference = diabetes[diabetes.age <= 40]diabetes_current = diabetes[diabetes.age > 40]
data_drift_dataset_report = Report(metrics=[
    DatasetDriftMetric(),
    DataDriftTable(),
])
data_drift_dataset_report.run(
    reference_data=diabetes_reference,
    current_data=diabetes_current)
Data_drift_dataset_report

以下插图是我们为糖尿病数据集生成的报告,考虑了所选特征和分离的参考和当前数据集:

图 11.1 – 糖尿病数据集中分离的参考和当前数据的漂移报告

图 11.1 – 糖尿病数据集中分离的参考和当前数据的漂移报告

我们可以看到,agepregplasinsuskin是参考集和当前集中分布差异显著的特性,这些特性在图 11.1所示的报告中指定为检测到漂移的特征。尽管分布之间的差异很重要,但拥有如均值差异这样的补充统计数据可能有助于开发更可靠的漂移检测策略。我们还可以从报告中获取特征的分布,例如图 11.211.3中分别显示的agepreg在参考集和当前集中的分布:

图 11.2 – 当前和参考数据中 age 特征的分布

图 11.2 – 当前和参考数据中 age 特征的分布

图 11.3 – 当前和参考数据中 preg 特征的分布

图 11.3 – 当前和参考数据中 preg 特征的分布

当我们在模型中检测到漂移时,我们可能需要通过摄取新数据或过滤可能引起漂移的部分数据来重新训练它们。如果检测到概念漂移,我们还可能需要更改模型训练。

摘要

在本章中,你了解了避免机器学习模型中漂移的重要性,以及如何通过使用你在前几章中学到的概念(如模型版本化和监控)来从中受益。你还练习了两个用于 Python 中漂移检测的库:alibi_detectevidently。使用这些或类似的库将有助于你在模型中消除漂移,并在生产中拥有可靠的模型。

在下一章中,你将了解不同类型的深度神经网络模型以及如何使用 PyTorch 开发可靠的深度学习模型。

问题

  1. 你能解释一下在机器学习建模中,漂移的两个特征——幅度和频率之间的区别吗?

  2. 我们可以使用哪种统计测试来检测数据漂移?

参考文献

  • Ackerman, Samuel, 等人。“检测影响机器学习模型性能随时间变化的数据漂移和异常值。”arXiv 预印本 arXiv:2012.09258(2020)。

  • Ackerman, Samuel, 等人。“自动检测机器学习分类器中的数据漂移。”arXiv 预印本 arXiv:2111.05672(2021)。

  • Efron, Bradley, Trevor Hastie, Iain Johnstone, 和 Robert Tibshirani(2004)“最小角度回归,”统计年鉴(附带讨论),407-499

  • Gama, João, 等人。“概念漂移适应的调查。”ACM 计算调查(CSUR)46.4(2014):1-37。

  • Lu, Jie, 等人。“在概念漂移下的学习:综述。”IEEE 知识数据工程杂志 31.12(2018):2346-2363。

  • Mallick, Ankur, 等人。“Matchmaker:在机器学习中为大规模系统减轻数据漂移。”机器学习和系统会议论文集 4(2022):77-94。

  • Zenisek, Jan, Florian Holzinger, 和 Michael Affenzeller. “基于机器学习的概念漂移检测用于预测性维护。” 计算机与工业工程 137 (2019): 106031。

第四部分:深度学习建模

在本书的这一部分,我们将通过介绍深度学习的底层理论来奠定基础,然后过渡到对全连接神经网络的动手探索。接着,我们将学习更高级的技术,包括卷积神经网络、转换器和图神经网络。在本部分的结尾,我们将聚焦于机器学习的尖端进展,特别关注生成建模,并介绍强化学习和自监督学习。在这些章节中,我们将通过 Python 和 PyTorch 提供实际示例,确保我们既获得理论知识,也获得实践经验。

本部分包含以下章节:

  • 第十二章, 超越机器学习调试的深度学习

  • 第十三章, 高级深度学习技术

  • 第十四章, 机器学习最新进展简介

第十二章:深度学习超越机器学习调试

机器学习最新的进展是通过深度学习建模实现的。在本章中,我们将介绍深度学习以及 PyTorch 作为深度学习建模的框架。由于本书的重点不是详细介绍不同的机器学习和深度学习算法,我们将关注深度学习为你提供的机会,以开发高性能模型,或使用可用的模型,这些模型可以建立在本章和下一章回顾的技术之上。

本章将涵盖以下主题:

  • 人工神经网络简介

  • 神经网络建模框架

到本章结束时,你将了解深度学习的某些理论方面,重点关注全连接神经网络。你还将使用广泛使用的深度学习框架 PyTorch 进行实践。

技术要求

以下要求应考虑本章,因为它们将帮助你更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • torch >= 2.0.0

    • torchvision >= 0.15.1

  • 你还需要了解不同类型机器学习模型之间的基本区别,例如分类、回归和聚类

你可以在 GitHub 上找到本章的代码文件,链接为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter12

人工神经网络简介

我们的大脑神经网络作为决策系统工作,其中信息处理单元称为神经元,帮助我们识别朋友的面孔等。人工神经网络ANNs)的工作原理类似。与我们的身体中存在一个庞大的神经元网络,负责所有决策(主动或被动)不同,ANNs 被设计为针对特定问题。例如,我们有用于图像分类、信用风险估计、目标检测等任务的 ANNs。为了简化,本书中将使用神经网络而不是 ANNs。

首先,我们想专注于全连接神经网络FCNNs),它们在表格数据上工作(图 12)。在许多资源中,FCNNs 和多层感知器MLPs)被互换使用。为了更好地比较不同类型的神经网络,本书中将使用 FCNNs 而不是 MLPs:

图 12.1 – FCNN 和单个神经元的示意图

图 12.1 – FCNN 和单个神经元的示意图

用于监督学习的 FCNN 有一个输入、一个输出和一个或多个隐藏(中间)层。包含输入和输出层的监督模型中超过三层的神经网络称为深度神经网络,深度学习指的是使用这种网络进行建模(Hinton 和 Salakhutdinov,2006)。

输入层不过是用于建模的数据点的特征。输出层神经元的数量也是根据实际问题确定的。例如,在二元分类的情况下,输出层中的两个神经元代表两个类别。隐藏层的数量和大小是 FCNN 的超参数之一,可以通过优化来提高 FCNN 的性能。

FCNN 中的每个神经元接收来自前一层的神经元的加权输出值的总和,对接收到的值的总和应用线性或非线性变换,然后将结果值输出到下一层的其他神经元。每个神经元输入值计算中使用的权重是训练过程中的学习权重(参数)。非线性变换是通过预定的激活函数实现的(图 12**.2)。FCNN 以其在输入特征值和输出之间产生复杂非线性关系而闻名,这使得它们在确定(可能)输入和输出之间不同类型的关系时非常灵活。在 FCNN 中,应用于接收到的神经元信息的激活函数负责这种复杂性或灵活性:

图 12.2 – 神经网络建模中广泛使用的激活函数

图 12.2 – 神经网络建模中广泛使用的激活函数

这些激活函数,例如sigmoidsoftmax函数,通常用于输出层,将输出神经元的分数转换为介于零和一之间的值,用于分类模型;这些被称为预测的概率。还有其他激活函数,如高斯误差线性单元GELU)(Hendrycks 和 Gimpel,2016),这些在更近期的模型中如生成预训练转换器GPT)中已被使用,这将在下一章中解释。以下是 GELU 的公式:

GELU(z) = 0.5z(1 + tanh(√ _  2 _ π  (z + 0.044715 z 3)))

监督学习有两个主要过程:预测输出和从预测的不正确性或正确性中学习。在全连接神经网络(FCNNs)中,预测发生在前向传播中。输入和第一个隐藏层之间的 FCNNs 的权重用于计算第一个隐藏层中神经元的输入值,以及其他 FCNN 中的层也是如此(图 12.3)。从输入到输出的过程称为前向传播或前向传递,它为每个数据点生成输出值(预测)。然后,在反向传播(反向传递)中,FCNN 使用预测输出及其与实际输出的差异来调整其权重,从而实现更好的预测:

图 12.3 – 分别用于输出预测和参数更新的前向传播和反向传播的示意图

图 12.3 – 分别用于输出预测和参数更新的前向传播和反向传播的示意图

神经网络的参数在训练过程中通过优化算法来确定。现在,我们将回顾一些在神经网络设置中广泛使用的优化算法。

优化算法

优化算法在幕后工作,试图最小化损失函数以识别训练机器学习模型时的最佳参数。在训练过程的每一步,优化算法决定如何更新神经网络中的每个权重或参数,或其他机器学习模型。大多数优化算法依赖于成本函数的梯度向量来更新权重。主要区别在于如何使用梯度向量以及使用哪些数据点来计算它。

在梯度下降中,所有数据点都用于计算成本函数的梯度;然后,模型的权重在成本最大减少的方向上更新。尽管这种方法对于小数据集是有效的,但它可能变得计算成本高昂,不适用于大数据集,因为对于学习的每一次迭代,都需要同时计算所有数据点的成本。另一种方法是随机梯度下降SGD);在每次迭代中,不是所有数据点,而是选择一个数据点来计算成本和更新权重。但每次只使用一个数据点会导致权重更新时出现高度振荡的行为。相反,我们可以使用迷你批梯度下降,这在教程和工具中通常被称为 SGD,其中不是每个迭代中所有数据点或仅一个,而是使用一批数据点来更新权重。这些三种方法背后的数学原理在图 12.4 中展示:

图 12.4 – 梯度下降、随机梯度下降和迷你批梯度下降优化算法

图 12.4 – 梯度下降、随机梯度下降和小批量梯度下降优化算法

近年来,已经提出了其他优化算法来提高神经网络模型在各种应用中的性能,例如 Adam 优化器(Kingma 和 Ba,2014)。这种方法的背后直觉是避免优化过程中的梯度消失。深入探讨不同优化算法的细节超出了本书的范围。

在神经网络建模中,有两个重要的术语你需要了解其定义:epochbatch size。当使用不同的框架训练神经网络模型时,我们将在下一节中回顾,你需要指定 batch sizeepoch 的数量。在优化的每一次迭代中,数据点的一个子集,或者说是小批量梯度下降中的小批量(如图 12.4*),被用来计算损失;然后,使用反向传播更新模型的参数。这个过程会重复进行,以覆盖训练数据中的所有数据点。Epoch 是我们在优化过程中用来指定所有训练数据被使用多少次的术语。例如,指定 5 个 epoch 意味着模型在优化过程中会使用训练过程中的所有数据点五次。

现在你已经了解了神经网络建模的基础知识,我们将介绍用于神经网络建模的框架。

神经网络建模框架

已有多个框架被用于神经网络建模:

在本书中,我们将专注于 PyTorch 来实践深度学习,但我们介绍的概念与你在项目中使用的框架无关。

用于深度学习建模的 PyTorch

PyTorch 是一个开源的深度学习框架,基于 Meta AI 开发的 Torch 库。你可以在深度学习项目中轻松地将 PyTorch 与 Python 的科学计算库集成。在这里,我们将通过查看使用 MNIST 数字数据集构建 FCNN 模型的一个简单示例来练习使用 PyTorch。这是一个常用的示例,其目标仅仅是理解如果你没有相关经验,如何使用 PyTorch 训练和测试深度学习模型。

首先,我们将导入所需的库并加载用于训练和测试的数据集:

import torchimport torchvision
import torchvision.transforms as transforms
torch.manual_seed(10)
# Device configuration
device = torch.device(
    'cuda' if torch.cuda.is_available() else 'cpu')
# MNIST dataset
batch_size = 100
train_dataset = torchvision.datasets.MNIST(
    root='../../data',train=True,
    transform=transforms.ToTensor(),download=True)
test_dataset = torchvision.datasets.MNIST(
    root='../../data', train=False,
    transform=transforms.ToTensor())
# Data loader
train_loader = torch.utils.data.DataLoader(
    dataset=train_dataset,batch_size=batch_size,
    shuffle=True)
test_loader = torch.utils.data.DataLoader(
    dataset=test_dataset,  batch_size=batch_size,
    shuffle=False)

接下来,我们将确定模型的超参数及其input_size,这是输入层中的神经元数量;这等同于我们数据中的特征数量。在这个例子中,它等于每张图像中的像素数,因为我们把每个像素视为一个特征来构建一个全连接神经网络(FCNN)模型:

input_size = 784# size of hidden layer
hidden_size = 256
# number of classes
num_classes = 10
# number of epochs
num_epochs = 10
# learning rate for the optimization process
learning_rate = 0.001

然后,我们将导入torch.nn,从中我们可以为我们的 FCNN 模型添加线性神经网络层,并编写一个类来确定我们网络的架构,这是一个包含一个大小为 256(256 个神经元)的隐藏层的网络:

import torch.nn as nnclass NeuralNet(nn.Module):
    def __init__(self, input_size, hidden_size,
        num_classes):
        super(NeuralNet, self).__init__()
        Self.fc_layer_1 = nn.Linear(input_size, hidden_size)
        self.fc_layer_2 = nn.Linear(hidden_size, num_classes)
    def forward(self, x):
        out = self.fc_layer_1(x)
        out = nn.ReLU()(out)
        out = self.fc_layer_2(out)
        return out
model = NeuralNet(input_size, hidden_size,
    num_classes).to(device)

torch.nn.Linear()类添加一个线性层,并有两个输入参数:当前层和下一层的神经元数量。对于第一个nn.Linear(),第一个参数必须等于特征数量,而网络初始化类中最后一个nn.Linear()输入参数的第二个参数需要等于数据中的类别数量。

现在,我们必须使用torch.optim()中的 Adam 优化器定义我们的交叉熵损失函数和优化器对象:

criterion = nn.CrossEntropyLoss()optimizer = torch.optim.Adam(model.parameters(),
    lr=learning_rate)

我们现在准备好训练我们的模型了。正如你在下面的代码块中可以看到的,我们有一个关于 epochs 的循环,以及另一个关于每个批次的内部循环。在内部循环中,我们有三个在大多数使用 PyTorch 的监督模型中常见的步骤:

  1. 获取批次内数据点的模型输出。

  2. 使用该批次的真实标签和预测输出计算损失。

  3. 反向传播并更新模型的参数。

接下来,我们必须在 MNIST 训练集上训练模型:

total_step = len(train_loader)for epoch in range(num_epochs):
    for i, (images, labels) in enumerate(train_loader):
        images = images.reshape(-1, 28*28).to(device)
        labels = labels.to(device)
        # Forward pass to calculate output and loss
        outputs = model(images)
        loss = criterion(outputs, labels)
        # Backpropagation and optimization
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

在第 10 个 epoch 结束时,我们在训练集中有一个损失为 0.0214 的模型。现在,我们可以使用以下代码来计算测试集中模型的准确率:

with torch.no_grad():    correct = 0
    total = 0
    for images, labels in test_loader:
        images = images.reshape(-1, 28*28).to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
    print('Accuracy of the network on the test images:
         {} %'.format(100 * correct / total))

这使得 MNIST 测试集中的模型达到了 98.4%的准确率。

PyTorch(pytorch.org/docs/stable/optim.html)中提供了超过 10 种不同的优化算法,包括 Adam 优化算法,这些算法可以帮助你在训练深度学习模型时。

接下来,我们将讨论深度学习设置中的超参数调整、模型可解释性和公平性。我们还将介绍 PyTorch Lightning,这将有助于你在深度学习项目中。

深度学习超参数调整

在深度学习建模中,超参数是决定其性能的关键因素。以下是一些你可以用来提高你的深度学习模型性能的 FCNN 超参数:

  • 架构: FCNN 的架构指的是隐藏层的数量和它们的大小,或者神经元的数量。更多的层会导致深度学习模型具有更高的深度,并可能导致更复杂的模型。尽管神经网络模型的深度在许多情况下已被证明可以提高大型数据集上的性能(Krizhevsky 等人,2012 年;Simonyan 和 Zisserman,2014 年;Szegedy 等人,2015 年;He 等人,2016 年),但大多数关于更高深度对性能产生积极影响的成功故事都发生在 FCNN 之外。但架构仍然是一个需要优化的重要超参数,以找到高性能模型。

  • 激活函数: 尽管每个领域和问题中都有常用的激活函数,但你仍然可以为你自己的问题找到最佳的一个。记住,你不需要在所有层中使用相同的函数,尽管我们通常坚持使用一个。

  • 批量大小: 改变批量大小会改变你模型的性能和收敛速度。但通常,它对性能没有显著影响,除了在第一个几个训练轮数学习曲线的陡峭部分。

  • 学习率: 学习率决定了收敛的速度。较高的学习率会导致收敛速度加快,但也可能导致在局部最优点的振荡,甚至发散。例如,Adam 优化器等算法在优化过程中接近局部最优时,会控制收敛率的衰减,但我们仍然可以将学习率作为深度学习建模的超参数进行调整。

  • 训练轮数: 深度学习模型在前几轮的训练中学习曲线陡峭,这取决于学习率和批量大小,然后性能开始趋于平稳。使用足够的训练轮数对于确保你从训练中得到最佳模型至关重要。

  • 正则化: 我们在第五章《提高机器学习模型性能》中讨论了正则化在控制过拟合和提升泛化能力方面的重要性,通过防止模型过度依赖单个神经元,从而可能提高泛化能力。例如,如果设置 dropout 为 0.2,每个神经元在训练过程中有 20%的概率被置零。

  • 权重衰减: 这是一种 L2 正则化形式,它对神经网络中的权重添加惩罚。我们在第五章《提高机器学习模型性能》中介绍了 L2 正则化。

您可以使用不同的超参数优化工具,如 Ray Tune,与 PyTorch 一起训练您的深度学习模型并优化其超参数。您可以在 PyTorch 网站上的这个教程中了解更多信息:pytorch.org/tutorials/beginner/hyperparameter_tuning_tutorial.html

除了超参数调整之外,PyTorch 还具有不同的功能和相关库,用于模型可解释性和公平性等任务。

PyTorch 中的模型可解释性

第六章《机器学习建模中的可解释性和可解释性》中,我们介绍了多种可解释性技术和库,可以帮助您解释复杂的机器学习和深度学习模型。Captum AI (captum.ai/) 是由 Meta AI 开发的一个开源模型可解释性库,用于使用 PyTorch 的深度学习项目。您可以将 Captum 容易地集成到现有的或未来的基于 PyTorch 的机器学习管道中。您可以通过 Captum 利用不同的可解释性和可解释性技术,如集成梯度、GradientSHAP、DeepLIFT 和显著性图。

PyTorch 开发的深度学习模型中的公平性

第七章《减少偏差和实现公平性》中,我们讨论了公平性的重要性,并介绍了不同的概念、统计指标和技术,以帮助您评估和消除模型中的偏差。FairTorch (github.com/wbawakate/fairtorch) 和 inFairness (github.com/IBM/inFairness) 是另外两个库,您可以使用它们来评估使用 PyTorch 的深度学习建模中的公平性和偏差。您可以从 inFairness 中受益,用于审计、训练和后处理模型以实现个体公平性。Fairtorch 还为您提供了减轻分类和回归中偏差的工具,尽管目前这仅限于二元分类。

PyTorch Lightning

PyTorch Lightning 是一个开源的高级框架,简化了使用 PyTorch 开发和训练深度学习模型的过程。以下是 PyTorch Lightning 的一些特性:

  • 结构化代码:PyTorch Lightning 将代码组织成 Lightning Module,这有助于您分离模型架构、数据处理和训练逻辑,使代码更加模块化且易于维护。

  • 训练循环抽象:您可以使用 PyTorch Lightning 避免训练、验证和测试循环中的重复代码

  • 分布式训练:PyTorch Lightning 简化了在多个 GPU 或节点上扩展深度学习模型的过程

  • 实验跟踪和日志记录:PyTorch Lightning 与实验跟踪和日志记录工具(如 MLflow 和 Weights & Biases)集成,这使得您更容易监控深度学习模型训练。

  • 自动优化:PyTorch Lightning 自动处理优化过程,管理优化器和学习率调度器,并使得在不同优化算法之间切换更加容易。

尽管有这些因素,深度学习建模的内容远不止 FCNNs,正如我们在下一章中将要看到的。

摘要

在本章中,你学习了使用 FCNNs 进行深度学习建模。我们通过使用 PyTorch 和一个简单的深度学习模型来练习,帮助你开始使用 PyTorch 进行深度学习建模(如果你还没有这样的经验)。你还学习了 FCNNs 的重要超参数、可用于深度学习环境中的模型可解释性和公平性工具,以及 PyTorch Lightning 作为简化深度学习建模的开源高级框架。你现在可以学习更多关于 PyTorch、PyTorch Lightning 和深度学习的内容,并开始从它们中受益于你的问题。

在下一章中,你将学习到其他更高级的深度学习模型,包括卷积神经网络、转换器和图卷积神经网络模型。

问题

  1. 神经网络模型的参数在反向传播中会更新吗?

  2. 随机梯度下降和批量梯度下降有什么区别?

  3. 你能解释一下批量(batch)和周期(epoch)之间的区别吗?

  4. 你能提供一个例子说明在您的神经网络模型中需要使用 sigmoid 和 softmax 函数的地方吗?

参考文献

  • LeCun, Yann, Yoshua Bengio, 和 Geoffrey Hinton. 深度学习. 自然 521.7553 (2015): 436-444。

  • Hinton, G. E., & Salakhutdinov, R. R. (2006). 使用神经网络降低数据维度. 科学,313(5786),504-507。

  • Abiodun, Oludare Isaac, 等人. 人工神经网络应用中的最新进展:一项调查. Heliyon 4.11 (2018): e00938。

  • Hendrycks, D., & Gimpel, K. (2016). 高斯误差线性单元 (GELUs). arXiv 预印本 arXiv:1606.08415。

  • Kingma, D. P., & Ba, J. (2014). Adam: 一种用于随机优化的方法. arXiv 预印本 arXiv:1412.6980。

  • Kadra, Arlind, 等人. 精心调优的简单网络在表格数据集上表现出色. 神经信息处理系统进展 34 (2021): 23928-23941。

  • Krizhevsky, A., Sutskever, I., & Hinton, G. E. (2012). 使用深度卷积神经网络进行 ImageNet 分类. 在神经信息处理系统进展(第 1097-1105 页)。

  • Simonyan, K., & Zisserman, A. (2014). 非常深的卷积网络在大规模图像识别中的应用. arXiv 预印本 arXiv:1409.1556。

  • He, K., Zhang, X., Ren, S., & Sun, J. (2016). 深度残差学习在图像识别中的应用. 在 IEEE 计算机视觉与模式识别会议论文集(第 770-778 页)。

  • Szegedy, C., Liu, W., Jia, Y., Sermanet, P., Reed, S., Anguelov, D., ... & Rabinovich, A. (2015). 使用卷积进行更深入的学习。载于 IEEE 计算机视觉与模式识别会议论文集(第 1-9 页)。

第十三章:高级深度学习技术

在上一章中,我们回顾了神经网络建模和深度学习的概念,同时关注全连接神经网络。在本章中,我们将讨论更多高级技术,这些技术让您能够使用深度学习模型跨越不同的数据类型和结构,例如图像、文本和图。这些技术是人工智能在各个行业取得进步的主要推动力,例如在聊天机器人、医疗诊断、药物发现、股票交易和欺诈检测等领域。尽管我们将介绍不同数据类型中最著名的深度学习模型,但本章旨在帮助您理解概念,并使用 PyTorch 和 Python 进行实践,而不是为每个数据类型或主题领域提供最先进的模型。

在本章中,我们将涵盖以下主题:

  • 神经网络类型

  • 用于图像形状数据的卷积神经网络

  • 用于语言建模的转换器

  • 使用深度神经网络建模图

到本章结束时,您将了解卷积神经网络CNNs)、转换器和图神经网络作为深度学习建模的三个重要类别,以在您感兴趣的问题中开发高性能模型。您还将了解如何使用 PyTorch 和 Python 开发此类模型。

技术要求

对于本章,以下要求应予以考虑,因为它们将帮助您更好地理解概念,在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • torch >= 2.0.0

    • torchvision >= 0.15.1

    • transformers >= 4.28.0

    • datasets >= 2.12.0

    • torch_geometric == 2.3.1

  • 您需要具备以下基本知识:

    • 深度学习建模和全连接神经网络

    • 如何使用 PyTorch 进行深度学习建模

您可以在 GitHub 上找到本章的代码文件,链接为 github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter13

神经网络类型

本书迄今为止提供的示例主要集中在表格数据上,无论是机器学习还是深度学习建模,作为机器学习建模的一个类别。然而,机器学习和特别是深度学习在处理非表格数据、非结构化文本、图像和图的问题上已经取得了成功。首先,在本节中,我们将介绍涉及此类数据类型的不同问题;然后,我们将回顾可以帮助您为这些问题构建可靠模型的深度学习技术。

基于数据类型的分类

结构化数据,也称为表格数据,是指可以组织成电子表格和结构化数据库的数据。正如我们在本书中使用这种数据类型一样,我们通常在表格、矩阵或 DataFrame 的列中具有不同的特征,甚至输出。DataFrame 的行代表数据集中的不同数据点。然而,我们还有其他类型的数据,它们不是结构化的,将它们重新格式化为 DataFrame 或矩阵会导致信息丢失。“图 13.1”显示了三种最重要的非结构化数据类型——即文本等序列数据、家庭照片等图像形状数据以及社交网络等图数据:

图 13.1 – 使用深度学习可以建模的不同数据类型

图 13.1 – 使用深度学习可以建模的不同数据类型

表 13.1 提供了一些问题和它们对应的数据如何适合图 13.1 中提到的每个类别的一些示例:

数据类型 示例
序列数据 文本、股票价格等时间序列数据、声音波序列的音频数据、物体运动序列的地理位置数据、大脑电活动序列的 EEG 数据、心脏电活动序列的 ECG 数据
图像形状数据 照片、安全监控图像、X 射线或 CT 扫描等医学图像、视觉艺术和绘画图像、卫星捕获的图像,如天气模式、显微镜捕获的图像,如细胞图像
道路网络、网页之间的连接(网络图)、概念之间的关系(知识图)、个人和群体之间的连接(社交网络)、基因或其他生物实体之间的连接(生物网络)

表 13.1 – 每种数据类型的示例问题

将不同数据类型重新格式化为表格数据时的一些挑战和问题如下:

  • 将序列数据重新格式化为表格形状的数据对象会导致关于数据顺序(如单词)的信息丢失

  • 将图像重新格式化为表格格式会导致局部模式(如二维图像像素之间的关系)的丢失

  • 将图重新格式化为表格数据将消除数据点或特征之间的依赖关系

现在我们已经理解了不将所有数据集和数据类型重新格式化为表格数据的重要性,我们可以开始使用不同的深度学习技术来了解我们如何为非表格数据构建成功的模型。我们将从查看图像形状数据开始。

用于图像形状数据的卷积神经网络

CNNs 允许我们在图像数据上构建深度学习模型,而无需将图像重新格式化为表格格式。这类深度学习技术的名称来源于卷积的概念,在深度学习中指的是将滤波器应用于图像形状数据以产生一个次级图像形状特征图(如图 13**.2所示):

图 13.2 – 将预定义的卷积滤波器应用于 3x3 图像形状数据点的简单示例

图 13.2 – 将预定义的卷积滤波器应用于 3x3 图像形状数据点的简单示例

当训练一个深度学习模型时,例如使用 PyTorch,卷积滤波器或其他我们将在本章后面介绍的滤波器并不是预定义的,而是通过学习过程来学习的。卷积和其他 CNN 建模中的滤波器及过程使我们能够使用这类深度学习技术处理不同图像形状的数据(正如我们在图 13**.1中看到的)。

CNNs 的应用不仅限于图像分类的监督学习,这是它最著名的应用之一。CNNs 已被用于不同的问题,包括图像分割分辨率增强目标检测等(图 13**.3):

图 13.3 – 卷积神经网络的一些成功应用

图 13.3 – 卷积神经网络的一些成功应用

表 13.2列出了 CNN 在不同应用中的高性能模型,您可以在项目中使用或在构建更好的模型时参考:

问题 一些广泛使用的模型和相关技术
图像分类 ResNet(He et al., 2016);EfficientNets(Tan and Le, 2019);MobileNets(Howard et al., 2017;Sandler et al., 2018);Xception(Chollet, 2017)
图像分割 U-Net(Ronneberger et al., 2015);Mask R-CNN(He et al., 2017);DeepLab(Chen et al., 2017);PSPNet(Chao et al., 2017)
目标检测 Mask R-CNN(He et al., 2017);Faster R-CNN(Ren et al., 2015);YOLO(Redmon et al., 2016)
图像超分辨率 SRCNN(Dong et el., 2015);FSRCNN(Dong et al., 2016);EDSR(Lim et al., 2017)
图像到图像翻译 Pix2Pix(Isola et al., 2017);CycleGAN(Zhu et al., 2017)
风格迁移 艺术风格神经算法(Gatys et al., 2016);AdaIN-Style(Huang et al., 2017)
异常检测 AnoGAN(Schlegl et al., 2017);RDA(Zhou et al., 2017);Deep SVDD(Ruff et al., 2018)
光学字符识别 EAST(Zhou et al., 2017);CRAFT(Bake et al., 2019)

表 13.2 – 不同问题下的高性能 CNN 模型

你可以在二维或三维图像形状数据上训练 CNN 模型。你也可以构建处理此类数据点序列的模型,例如视频,作为图像序列。你可以玩的一些在视频上使用 CNN 的最著名模型或方法包括 C3D(Tran 等,2015 年)、I3D(Carreira 和 Zisserman,2017 年)和 SlowFast(Feichtenhofer 等,2019 年)。

接下来,我们将了解一些评估 CNN 模型性能的方法。

性能评估

你可以使用在第第四章中介绍的机器学习模型性能和效率问题检测的性能指标,例如 ROC-AUC、PR-AUC、精确率和召回率,用于 CNN 分类模型。然而,对于图 13**.3中提出的一些问题,还有其他更具体的指标,如下所示:

  • 像素精度:这个指标定义为正确分类的像素数与总像素数的比率。这个指标与准确度类似,当像素存在类别不平衡时可能会产生误导。

  • Jaccard 指数:Jaccard 指数定义为交集与并集的比率,可以用来计算预测分割与真实分割的交集,并按它们的并集进行归一化。

使用 PyTorch 进行 CNN 建模

PyTorch 中的 CNN 建模过程与我们之前章节中介绍的构建全连接神经网络非常相似。它从指定网络架构开始,然后初始化优化器,最后通过不同的周期和批次从训练数据点中学习。在这里,我们想使用torchvision库在 PyTorch 中练习 CNN 建模。此数据集中图像的示例在图 13**.4中显示:

图 13.4 – torchvision 数据集中德国交通标志识别基准(GTSRB)数据集的图像示例

图 13.4 – torchvision 数据集中德国交通标志识别基准(GTSRB)数据集的图像示例

除了torch.nn.Conv2d卷积滤波器外,torch.nn模块中还有其他滤波器和层可供使用,你可以使用它们来训练高性能的 CNN 模型。除了torch.nn.Conv2d之外,广泛使用的另一个滤波器是torch.nn.MaxPool2d,它可以用作 CNN 建模中的池化层(LeCun 等,1989 年)。你可以在 PyTorch 网站上阅读这两个滤波器所需的参数(pytorch.org/docs/stable/nn.html)。

让我们开始使用 GTSRB 数据集练习 CNN 建模。首先,我们必须加载用于模型训练和测试的数据,然后指定分类模型中的类别数:

transform = transforms.Compose([    transforms.Resize((32, 32)),
    transforms.ToTensor(),
    transforms.Normalize((0.3337, 0.3064, 0.3171),
        ( 0.2672, 0.2564, 0.2629))
])
batch_size = 6
n_class = 43
# Loading train and test sets of
# German Traffic Sign Recognition Benchmark (GTSRB) Dataset.
trainset = torchvision.datasets.GTSRB(
    root='../../data',split = 'train',
    download=True,transform=transform)
trainloader = torch.utils.data.DataLoader(trainset,
    batch_size=batch_size,shuffle=True, num_workers=2)
testset = torchvision.datasets.GTSRB(
    root='../../data',split = 'test',
    download=True,transform=transform)
testloader = torch.utils.data.DataLoader(testset,
    batch_size=batch_size,shuffle=False,num_workers=2)

然后,我们必须定义一个名为Net的神经网络类,它决定了网络的架构,包括两层卷积加上池化滤波器,然后是 ReLU 激活函数,接着是三层具有 ReLU 激活函数的全连接神经网络:

import torch.nn as nnimport torch.nn.functional as F
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 128)
        self.fc2 = nn.Linear(128, 64)
        self.fc3 = nn.Linear(64, n_class)
    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = torch.flatten(x, 1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

然后,我们必须初始化网络和优化器,如下所示:

import torch.optim as optimnet = Net()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001,
    momentum=0.9)

现在,我们已经准备好使用初始化的架构和优化器来训练网络。在这里,我们将使用三个 epoch 来训练网络。批大小不需要在这里指定,因为它们在从torchvision加载数据时就已经确定,在这个例子中指定为6(这可以在本书的 GitHub 仓库中找到):

n_epoch = 3for epoch in range(n_epoch):
    # running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the input data
        inputs, labels = data
        # zero the parameter gradients
        optimizer.zero_grad()
        # output identification
        outputs = net(inputs)
        # loss calculation and backward propagation for parameter update
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

经过 3 个 epoch 后的最终计算损失为 0.00008。

这只是一个使用 PyTorch 进行 CNN 建模的简单示例。在构建 CNN 模型时,PyTorch 还有其他你可以从中受益的功能,例如数据增强。我们将在下一节讨论这个问题。

CNN 的图像数据转换和增强

作为机器学习生命周期预训练阶段的一部分,你可能需要转换你的图像,例如通过裁剪它们,或者实现数据增强作为一系列用于合成数据生成的技术,以提高你模型的性能,如第五章中所述,提高机器学习模型的性能图 13.5展示了数据增强的一些简单示例,包括旋转和缩放,这些可以帮助你生成合成但高度相关的数据点,以帮助你的模型:

图 13.5 – 基于规则的图像数据增强示例 – (A) 原始图像,(B) 旋转图像,和(C) 缩放图像

图 13.5 – 基于规则的图像数据增强示例 – (A) 原始图像,(B) 旋转图像,和(C) 缩放图像

虽然你可以实现数据增强的简单规则示例,但在 PyTorch 中有许多类可以用于数据转换和增强,如pytorch.org/vision/stable/transforms.html中所述。

使用预训练模型

在深度学习环境中,我们通常依赖于预训练模型来进行推理或进一步微调以解决我们手头的特定问题。卷积神经网络(CNNs)也不例外,你可以在 PyTorch 中找到许多用于图像分类或其他 CNN 应用的预训练模型(pytorch.org/vision/stable/models.html)。你还可以在相同的 URL 上找到如何使用这些模型的代码示例。你可以在pytorch.org/tutorials/beginner/finetuning_torchvision_models_tutorial.html找到必要的代码,教你如何使用新数据微调这些模型。

虽然我们迄今为止一直专注于将 CNN 应用于图像数据,但它们可以用来建模任何图像形状数据。例如,音频数据可以从时域转换到频域,从而产生可以结合序列建模算法使用 CNN 来建模的图像形状数据,正如本章后面所介绍的 (pytorch.org/audio/main/models.html)。

除了图像和图像形状数据之外,深度学习模型和算法已经被开发出来,以在多种应用中正确地建模序列数据,例如在自然语言处理NLP)中,为了简便起见,我们在这里将其称为语言建模。在下一节中,我们将回顾用于语言建模的 Transformer,以帮助您在手中有一个相关想法或项目时开始从这些模型中受益。

用于语言建模的 Transformer

Transformer 在一篇名为 Attention is All You Need 的著名论文(Vaswani et al., 2017)中被引入,作为一种新的序列到序列数据建模任务的方法,例如将一种语言的陈述翻译成另一种语言(即机器翻译)。这些模型建立在自注意力概念之上,该概念有助于模型在训练过程中关注句子或信息序列中的其他重要部分。这种注意力机制有助于模型更好地理解输入序列元素之间的关系——例如,在语言建模中输入序列中的单词之间的关系。使用 Transformer 构建的模型通常比使用如 长短期记忆LSTM)和 循环神经网络RNNs)(Vaswani et al., 2017; Devlin et al., 2018)等前辈技术构建的模型表现更好。

图 13.6 展示了通过 Transformer 模型成功解决的四个传统语言建模问题:

图 13.6 – 深度学习技术在语言建模中成功应用的四个传统问题

图 13.6 – 深度学习技术在语言建模中成功应用的四个传统问题

一些著名的模型已经在这些或其他语言建模任务中直接使用或经过一些修改。以下是一些例子:

变压器模型也被用于其他领域和序列数据,例如电子健康记录(Li et al., 2020)、蛋白质结构预测(Jumpter et al., 2021)和时间序列异常检测(Xu et al., 2021)。

生成模型是机器学习建模中另一个重要的概念,变压器和 CNNs 已经成功地被用于此类模型。此类模型的例子包括 GPT 的不同版本,如 GPT-4 (openai.com/product/gpt-4)。你将在第十四章,“机器学习最新进展导论”中了解生成模型。有一个公开的大型语言模型LLM)排行榜,提供了最新的开源 LLM 模型列表(huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard)。你还可以查看 LLMs 的实用指南资源列表github.com/Mooler0410/LLMsPracticalGuide

我们不想深入探讨变压器背后的理论细节,但你在用 PyTorch 构建一个变压器架构的过程中,将会了解其组成部分。然而,其他广泛使用的性能指标也被用于序列数据和语言模型,例如以下内容:

这些指标可以帮助你评估你的序列模型。

分词

在训练和测试 transformer 模型之前,我们需要通过一个称为分词的过程将数据转换成正确的格式。分词是将数据分成更小的片段,如单词,如在单词分词中,或者字符,如在字符分词中。例如,句子“我喜欢读书”可以被转换成其包含的单词——即[“我”,“喜欢”,“读书”,“书籍”]。在构建分词器时,需要指定允许的最大标记数。例如,对于一个有 1,000 个标记的分词器,最频繁的 1,000 个单词将从提供给构建分词器的文本中用作标记。然后,每个标记将是这 1,000 个最频繁标记中的一个。之后,这些标记每个都会得到一个 ID;这些数字将在之后的神经网络模型训练和测试中使用。分词器标记之外的字词和字符会得到一个共同的值,例如,0 或 1。在文本分词中,另一个挑战是语句和单词序列的不同长度。为了应对这一挑战,在称为填充的过程中,通常会在每个单词序列或句子中的标记 ID 之前或之后使用一个共同的 ID,例如 0。

近期的大型语言模型(LLM)在分词过程中的标记数不同。例如,OpenAI 的 gpt-4-32k 模型提供 32,000 个标记 (help.openai.com/en/articles/7127966-what-is-the-difference-between-the-gpt-4-models),而 Claude 的 LLM 提供 100k 个标记 (www.anthropic.com/index/100k-context-windows)。标记数的差异可能会影响模型在相应文本相关任务中的性能。

常用的分词库包括 Hugging Face 的 transformer (huggingface.co/transformers/v3.5.1/main_classes/tokenizer.html)、SpaCy (spacy.io/) 和 NLTK (www.nltk.org/api/nltk.tokenize.html)。让我们通过练习 Hugging Face 的 transformer 库来更好地理解分词是如何工作的。

首先,让我们导入 transformers.AutoTokenizer() 并加载 bert-base-casedgpt2 预训练的分词器:

from transformers import AutoTokenizertokenizer_bertcased = AutoTokenizer.from_pretrained(
    'bert-base-cased')
tokenizer_gpt2 = AutoTokenizer.from_pretrained('gpt2')

为了练习这两个分词器,我们必须制作一个包含两个语句的列表,用于分词过程:

batch_sentences = ["I know how to use machine learning in my projects","I like reading books."]

然后,我们必须使用每个加载的分词器对这两个语句进行分词和编码,以得到相应的 ID 列表。首先,让我们使用 gpt2,如下所示:

encoded_input_gpt2 = tokenizer_gpt2(batch_sentences)

上述代码将这些两个语句转换成以下二维列表,其中包含每个语句中每个标记的 ID。例如,由于这两个语句都以“I”开头,因此它们的第一 ID 都是 40,这是gpt2分词器中“I”的标记:

[[40, 760, 703, 284, 779, 4572, 4673, 287, 616, 4493], [40, 588, 3555, 3835, 13]]

现在,我们将使用bert-base-cased,但这次,我们将要求分词器也使用填充来生成相同长度的 ID 列表,并以张量格式返回生成的 ID,这对于后续在神经网络建模中使用,例如使用 PyTorch,是合适的:

encoded_input_bertcased = tokenizer_bertcased(    batch_sentences, padding=True, return_tensors="pt")

以下张量显示了生成的两个句子 ID 的长度相同:

tensor([[ 101,  146, 1221, 1293, 1106, 1329, 3395, 3776,    1107, 1139, 3203,  102],
    [ 101,146, 1176, 3455, 2146, 119, 102, 0, 0, 0, 0, 0]])

我们还可以使用这些分词器的解码功能将 ID 转换回原始语句。首先,我们必须使用gpt2解码生成的 ID:

[tokenizer_gpt2.decode(input_id_iter) for input_id_iter in encoded_input_gpt2["input_ids"]]

这生成了以下语句,这些语句与原始输入语句相匹配:

['I know how to use machine learning in my projects', 'I like reading books.']

然而,假设我们使用bert-base-cased分词器进行 ID 解码,如下所示:

[tokenizer_bertcased.decode(input_id_iter) for input_id_iter in encoded_input_bertcased["input_ids"]]

生成的语句不仅包含原始语句,还显示了填充标记的解码方式。这显示为[PAD][CLS],这相当于句子的开始,以及[SEP],它显示了另一个句子开始的位置:

['[CLS] I know how to use machine learning in my projects [SEP]', '[CLS] I like reading books. [SEP] [PAD] [PAD] [PAD] [PAD] [PAD]']

语言嵌入

我们可以将每个单词的识别 ID 转换成更信息丰富的嵌入。这些 ID 本身可以用作 one-hot 编码,如在第第四章中讨论的,检测机器学习模型的性能和效率问题,其中每个单词都得到一个所有元素为零、对应单词的标记为 1 的长向量。但这些 one-hot 编码并没有提供任何关于单词之间关系的信息,这些关系在语言建模的单词级别上类似于数据点。

我们可以将词汇表中的单词转换为嵌入,这些嵌入可以用来捕捉它们之间的语义关系,并帮助我们的机器学习和深度学习模型从不同语言建模任务中的新信息丰富的特征中受益。尽管 BERT 和 GPT-2 等模型并非专为文本嵌入提取而设计,但它们可以用来为文本语料库中的每个单词生成嵌入。但还有其他一些较老的方法,如 Word2Vec(Mikolov 等人,2013 年)、GloVe(Pennington 等人,2014 年)和 fast-text(Bojanowski 等人,2017 年),它们是为嵌入生成而设计的。还有更多最近且更全面的词嵌入模型,如 Cohere(txt.cohere.com/embedding-archives-wikipedia/),您可以使用它来生成文本嵌入,用于嵌入和建模的不同语言。

使用预训练模型进行语言建模

我们可以将预训练模型导入到不同的深度学习框架中,例如 PyTorch,仅用于推理或使用新数据进行进一步的微调。在这里,我们想用 DistilBERT(Sanh et al., 2019)来练习这个过程,它是 BERT(Devlin et al., 2018)的一个更快、更轻量级的版本。具体来说,我们想使用基于 DistilBERT 架构的DistilBertForSequenceClassification()模型,该模型已被调整为用于序列分类任务。在这些过程中,模型会被训练并可用于对给定句子或陈述分配标签的任务的推理。此类标签分配的例子包括垃圾邮件检测或语义标记,如正面、负面和中立。

首先,我们将从torchtransformers库中导入必要的库和类:

import torchfrom torch.utils.data import DataLoader
from transformers import DistilBertTokenizerFast, DistilBertForSequenceClassification, Trainer, TrainingArguments

然后,我们将加载imdb数据集,以便我们可以使用它来训练一个模型,作为一个微调版本的DistilBertForSequenceClassification()

from datasets import load_datasetdataset = load_dataset("imdb")

现在,我们可以在DistilBertTokenizerFast()分词器的基础上定义一个分词器函数,其中使用distilbert-base-uncased作为预训练的分词器:

tokenizer = DistilBertTokenizerFast.from_pretrained(    "distilbert-base-uncased")
def tokenize(batch):
    return tokenizer(batch["text"], padding=True,
        truncation=True, max_length=512)

然后,我们可以将imdb数据集的一小部分(1%)分离出来用于训练和测试,因为我们只想练习这个过程,而使用整个数据集在训练和测试方面需要花费很长时间:

train_dataset = dataset["train"].train_test_split(    test_size=0.01)["test"].map(tokenize, batched=True)
test_dataset = dataset["test"].train_test_split(
    test_size=0.01)["test"].map(tokenize, batched=True)

现在,我们可以在分类过程中指定标签的数量来初始化DistilBertForSequenceClassification()模型。这里,这是2

model = DistilBertForSequenceClassification.from_pretrained(    "distilbert-base-uncased", num_labels=2)

现在,我们可以使用imdb数据集的独立训练数据来训练模型,进行3个 epoch 的训练:

training_args = TrainingArguments(output_dir="./results",    num_train_epochs=3,per_device_train_batch_size=8,
    per_device_eval_batch_size=8, logging_dir="./logs")
trainer = Trainer(model=model, args=training_args,
    train_dataset=train_dataset,eval_dataset=test_dataset)
trainer.train()

这样,模型就已经训练好了,我们可以使用imdb数据集的独立测试集来评估它:

eval_results = trainer.evaluate()

这导致了 0.35 的评估损失。

在你的语言建模或推理任务中,有许多其他可用的模型可以使用(例如,PyTorch Transformers 库:pytorch.org/hub/huggingface_pytorch-transformers/)。还有其他序列模型,除了语言建模之外,适用于以下领域:

你可以在pytorch.org/tutorials/beginner/transformer_tutorial.html了解更多关于 transformer 建模以及如何在 PyTorch 中从头开始构建新架构,而不是使用预训练模型。

在本节中,你学习了将文本建模为一种序列数据类型。接下来,我们将介绍建模图,这是一种更复杂的数据结构。

使用深度神经网络建模图

我们可以将图视为我们用于机器学习和深度学习建模的几乎所有非表格数据的更通用结构。序列可以被认为是一维的(1D),而图像或图像形状数据可以被认为是二维的(2D)(参见图 13.7)。在本章的早期,你学习了如何在 Python 和 PyTorch 中开始从 CNN 和变压器中受益,用于序列和图像形状数据。但更通用的图不适用于这些两个图,它们具有预定义的结构(参见图 13.7),我们不能简单地使用 CNN 或序列模型来建模它们:

图 13.7 – 不同非结构化数据的图表示

图 13.7 – 不同非结构化数据的图表示

图有两个重要的元素,称为节点和边。边连接节点。图中的节点和边可以具有不同的特征,这些特征将它们彼此区分开来(参见图 13.8):

图 13.8 – 根据节点和边特征分类的图类型

图 13.8 – 根据节点和边特征分类的图类型

我们可以有节点具有特征、边具有权重或特征,或者边具有方向的图。无向图(具有无向边的图)在许多应用中很有用,例如社交媒体网络。假设社交媒体图中的每个节点都是一个节点,那么边可以确定哪些人相连。此类图中的节点特征可以是社交媒体网络中人们的不同特征,例如他们的年龄、研究领域或职称、居住城市等。有向图可用于不同的应用,例如因果建模,我们将在第十五章 相关性 与因果关系 中讨论。

如本节开头所述,CNN 和变压器等技术不能直接应用于图。因此,我们将回顾其他神经网络技术,这些技术可以帮助你在项目中建模图。

图神经网络

与 2D 图像和 1D 序列数据相比,图可能具有更复杂的结构。然而,我们可以使用与 CNN 和变压器模型相同的理念,通过依赖数据中的局部模式和关系来建模它们。我们可以在图中依赖局部模式,让神经网络从相邻节点中学习,而不是试图学习关于整个图的信息,这个图可能包含成千上万的节点和数百万的边。这就是图神经网络(GNNs)背后的理念。

我们可以使用 GNNs(图神经网络)来完成不同的任务,例如以下内容:

  • 节点分类:我们可以通过使用图神经网络(GNNs)来预测图中每个节点的类别。例如,如果你考虑一个城市中酒店的图,其中边是它们之间的最短路径,你可以预测在假期期间哪个酒店会被预订。或者如果你有化学背景,你可以使用节点分类来注释蛋白质中的氨基酸,使用蛋白质的 3D 结构(Abdollahi 等人,2023 年)。

  • 节点选择:对于 GNNs 的节点选择与 CNNs 的对象检测任务类似。我们可以设计 GNNs 来识别和选择具有特定特征的节点,例如在产品与消费者图中选择推荐产品的人。

  • 链接预测:我们可以旨在预测已存在节点或图中新节点之间的未知边。例如,在一个代表社交媒体网络的图中,链接预测可能是预测人与人之间的联系。然后,这些个人可以被推荐给对方,以便他们可以将彼此添加到他们的联系网络中。

  • 图分类:我们不是旨在预测或选择节点或边,而是可以设计 GNNs 来预测整个图的特征(chrsmrrs.github.io/datasets/)。在这种情况下,可能会有代表数据点的图,例如用于图分类 GNN 模型的药物分子。

存在着不同 GNNs 的一般分类法,例如 Wu 等人(2020 年)提出的分类法。但在这里,我们想专注于广泛使用的方法的例子,而不是过于技术性地涉及 GNNs 的不同类别。成功用于建模图的方 法的例子包括图卷积网络GCNs)(Kipf 和 Welling 在 2016 年),图样本和聚合GraphSAGE)(Hamilton 等人,2017 年),以及图注意力网络GATs)(Veličković等人,2018 年)。虽然大多数 GNN 技术考虑节点的特征,但并非所有都考虑边特征。消息传递神经网络MPNNs)是一种考虑节点和边特征的技术示例,最初是为生成药物分子的图而设计的(Gilmer 等人,2017 年)。

你可以从手头的数据构建图,或者使用公开可用的数据集,如斯坦福大型网络数据集收集SNAP)来练习不同的 GNN 技术。SNAP 拥有你可以下载并开始练习的最大的图数据集之一(snap.stanford.edu/data/)。

接下来,我们将使用 PyTorch 练习 GNN 建模,以帮助你更好地理解如何在 Python 中构建此类模型。

PyTorch Geometric 中的 GNNs

PyTorch Geometric 是一个基于 PyTorch 的 Python 库,它帮助您训练和测试 GNN。有一系列教程您可以从中受益,了解如何使用 PyTorch Geometric 进行 GNN 建模(pytorch-geometric.readthedocs.io/en/latest/notes/colabs.html)。在这里,我们将通过改编自这些教程之一的代码来练习节点分类问题(colab.research.google.com/drive/14OvFnAXggxB8vM4e8vSURUp1TaKnovzX?usp=sharing#scrollTo=0YgHcLXMLk4o)。

首先,让我们从 PyTorch Geometric 中的 Planetoid 导入 CiteSeer 引用网络数据集(Yang et al., 2016):

from torch_geometric.datasets import Planetoidfrom torch_geometric.transforms import NormalizeFeatures
dataset = Planetoid(root='data/Planetoid', name='CiteSeer',
    transform=NormalizeFeatures())
data = dataset[0]

现在,类似于初始化 FCNN 和 CNN 的神经网络,我们必须为 GNN 建模初始化一个 GCNet 类,但我们将使用 GCNConv 图卷积层而不是线性层和卷积层:

import torchfrom torch_geometric.nn import GCNConv
import torch.nn.functional as F
torch.manual_seed(123)
class GCNet(torch.nn.Module):
    def __init__(self, hidden_channels):
        super().__init__()
        self.gcn_layer1 = GCNConv(dataset.num_features,
            hidden_channels[0])
        self.gcn_layer2 = GCNConv(hidden_channels[0],
            hidden_channels[1])
        self.gcn_layer3 = GCNConv(hidden_channels[1],
            dataset.num_classes)
    def forward(self, x, edge_index):
        x = self.gcn_layer1(x, edge_index)
        x = x.relu()
        x = F.dropout(x, p=0.3, training=self.training)
        x = self.gcn_layer2(x, edge_index)
        x = x.relu()
        x = self.gcn_layer3(x, edge_index)
        return x

在上一节课中,我们使用了三个 GCNConv 层,结合 ReLU 激活函数和 dropout 进行正则化。

现在,我们可以使用定义的 GCNet 类来初始化我们的模型,其隐藏层的大小为 128 和 16,在这段实践代码中都是任意的。我们还需要在指定算法的同时初始化一个优化器,在这个例子中是 Adam,学习率为 0.01,权重衰减为 1e-4 以进行正则化:

model = GCNet(hidden_channels=[128, 16])optimizer = torch.optim.Adam(model.parameters(), lr=0.01,
    weight_decay=1e-4)
criterion = torch.nn.CrossEntropyLoss()

现在,我们可以定义我们的训练函数,它将被用于单 epoch 训练:

def train():        model.train()
        optimizer.zero_grad()
        out = model(data.x, data.edge_index)
        loss = criterion(out[data.train_mask],
            data.y[data.train_mask])
        loss.backward()
        optimizer.step()
        return loss

这样,我们就准备好了一系列的 epoch 并开始训练模型。请注意,以下用于训练模型 400 个 epoch 的循环可能需要很长时间:

import numpy as npepoch_list = []
loss_list = []
for epoch in np.arange(1, 401):
    loss = train()
    if epoch%20 == 0:
        print(f'Epoch: {epoch:03d}, Loss: {loss:.4f}')
        epoch_list.append(epoch)
        loss_list.append(loss.detach().numpy())

下面的图显示了训练过程中的学习曲线(损失与 epoch 的关系):

图 13.9 – 在 CiteSeer 数据集上示例 GCN 模型的学习曲线

图 13.9 – 在 CiteSeer 数据集上示例 GCN 模型的学习曲线

我们还可以在数据集的测试部分测试模型,如下所示:

model.eval()pred = model(data.x, data.edge_index).argmax(dim=1)
test_correct = pred[data.test_mask] ==
    data.y[data.test_mask]
test_acc = int(test_correct.sum()) / int(
    data.test_mask.sum())

这导致准确率为 0.655。我们还可以生成测试集上预测的混淆矩阵:

from sklearn.metrics import confusion_matrixcf = confusion_matrix(y_true = data.y, y_pred = model(
    data.x, data.edge_index).argmax(dim=1))
import seaborn as sns
sns.set()
sns.heatmap(cf, annot=True, fmt="d")

这导致以下矩阵,以热图的形式展示。尽管大多数数据点的预测和真实类别匹配,但其中许多被错误分类,并且总结在混淆矩阵的对角线元素之外:

图 13.10 – 在 CiteSeer 数据集上示例 GCN 模型的测试集预测混淆矩阵

图 13.10 – 在 CiteSeer 数据集上示例 GCN 模型的测试集预测混淆矩阵

在本节中,我们讨论了使用深度学习建模不同数据类型和问题的技术。现在,你准备好学习更多关于这些高级技术并在你的项目中使用它们了。

摘要

在本章中,你学习了高级深度学习技术,包括卷积神经网络(CNNs)、转换器(transformers)和图神经网络(GNNs)。你了解了一些使用这些技术开发的广泛使用或著名的模型。你还练习了使用 Python 和 PyTorch 从头开始构建这些高级模型或微调它们。这些知识帮助你更深入地了解这些技术,并在你的项目中开始使用它们,以便你可以对图像和图像形状数据进行建模,对文本和序列数据进行建模,以及对图进行建模。

在下一章中,你将学习到最近在生成建模和提示工程以及自监督学习方面的进展,这些进展将帮助你开发项目或为你提供开发有趣和有用的工具和应用程序的机会。

问题

  1. 你可以使用 CNNs 和 GNNs 解决哪些问题的示例?

  2. 应用卷积是否可以保留图像中的局部模式?

  3. 减少标记数是否会导致语言模型中的错误更多?

  4. 文本标记过程中的填充(padding)是什么?

  5. 我们在 PyTorch 中为 CNNs 和 GNNs 构建的网络架构类是否相似?

  6. 在构建 GNNs 时,何时需要边缘特征?

参考文献

  • He, Kaiming 等人。用于图像识别的深度残差学习。IEEE 计算机视觉和模式识别会议论文集。2016 年。

  • Tan, Mingxing 和 Quoc Le. Efficientnet:重新思考卷积神经网络的模型缩放。机器学习国际会议。PMLR,2019 年。

  • Howard, Andrew G.等人。Mobilenets:用于移动视觉应用的效率卷积神经网络。arXiv 预印本 arXiv:1704.04861(2017 年)。

  • Sandler, Mark 等人。Mobilenetv2:倒置残差和线性瓶颈。IEEE 计算机视觉和模式识别会议论文集。2018 年。

  • Chollet, François。Xception:使用深度可分离卷积的深度学习。IEEE 计算机视觉和模式识别会议论文集。2017 年。

  • Ronneberger, Olaf,Philipp Fischer 和 Thomas Brox. U-net:用于生物医学图像分割的卷积网络。医学图像计算和计算机辅助干预(MICCAI 2015):第 18 届国际会议,德国慕尼黑,2015 年 10 月 5-9 日,第 III 部分 18。Springer 国际出版社,2015 年。

  • He, Kaiming 等人。Mask r-cnn。IEEE 国际计算机视觉会议。2017 年。

  • Chen, Liang-Chieh 等人。Deeplab:使用深度卷积网络、扩张卷积和全连接 crfs 进行语义图像分割。IEEE 模式分析杂志第 40 卷第 4 期(2017 年):834-848。

  • Zhao, Hengshuang 等人。金字塔场景解析网络。IEEE 计算机视觉和模式识别会议论文集。2017 年。

  • Ren, Shaoqing 等人。Faster r-cnn:使用区域提议网络的实时目标检测。神经信息处理系统进展第 28 卷(2015 年)。

  • Redmon, Joseph, et al. 一次检测:统一、实时目标检测. IEEE 计算机视觉与模式识别会议论文集。2016.

  • Dong, Chao, et al. 使用深度卷积网络进行图像超分辨率. IEEE 模式分析杂志 38.2 (2015): 295-307.

  • Dong, Chao, Chen Change Loy, 和 Xiaoou Tang. 加速超分辨率卷积神经网络. 计算机视觉–ECCV 2016:第 14 届欧洲会议,荷兰阿姆斯特丹,2016 年 10 月 11-14 日,会议论文集第 II 部分 14. Springer International Publishing, 2016.

  • Lim, Bee, et al. 用于单图像超分辨率的增强深度残差网络. IEEE 计算机视觉与模式识别会议论文集 workshops。2017.

  • Isola, Phillip, et al. 使用条件对抗网络进行图像到图像翻译. IEEE 计算机视觉与模式识别会议论文集。2017.

  • Zhu, Jun-Yan, et al. 使用循环一致对抗网络的无配对图像到图像翻译. IEEE 国际计算机视觉会议论文集。2017.

  • Gatys, Leon A., Alexander S. Ecker, 和 Matthias Bethge. 使用卷积神经网络进行图像风格迁移. IEEE 计算机视觉与模式识别会议论文集。2016.

  • Huang, Xun, 和 Serge Belongie. 实时自适应实例归一化在任意风格转换中的应用. IEEE 国际计算机视觉会议论文集。2017.

  • Schlegl, Thomas, et al. 无监督异常检测与生成对抗网络引导标记发现. 医学图像处理:第 25 届国际会议,IPMI 2017,美国北卡罗来纳州博恩,2017 年 6 月 25-30 日,会议论文集。Cham: Springer International Publishing, 2017.

  • Ruff, Lukas, et al. 深度单类分类. 国际机器学习会议。PMLR,2018.

  • Zhou, Chong, 和 Randy C. Paffenroth. 使用鲁棒深度自编码器的异常检测. 第 23 届 ACM SIGKDD 国际知识发现和数据挖掘会议论文集。2017.

  • Baek, Youngmin, et al. 文本检测中的字符区域感知. IEEE/CVF 计算机视觉与模式识别会议论文集。2019.

  • Zhou, Xinyu, et al. East:一种高效准确的场景文本检测器. IEEE 计算机视觉与模式识别会议论文集。2017.

  • Tran, Du, et al. 使用 3D 卷积网络学习时空特征. IEEE 国际计算机视觉会议论文集。2015.

  • Carreira, Joao, 和 Andrew Zisserman. 动作识别何去何从?一个新的模型和 Kinetics 数据集. IEEE 计算机视觉与模式识别会议论文集。2017.

  • Feichtenhofer, Christoph, et al. 用于视频识别的 Slowfast 网络. IEEE/CVF 国际计算机视觉会议论文集。2019.

  • LeCun, Yann, 等人. 使用反向传播网络进行手写数字识别. 神经信息处理系统进展 2 (1989).

  • Vaswani, Ashish, 等人. 注意力即一切. 神经信息处理系统进展 30 (2017).

  • Devlin, Jacob, 等人. BERT: 用于语言理解的深度双向变换器预训练. arXiv 预印本 arXiv:1810.04805 (2018).

  • Touvron, Hugo, 等人. Llama: 开放且高效的通用语言模型. arXiv 预印本 arXiv:2302.13971 (2023).

  • Li, Yikuan, 等人. BEHRT: 用于电子健康记录的变换器. 科学报告 10.1 (2020): 1-12.

  • Jumper, John, 等人. 使用 AlphaFold 进行高度精确的蛋白质结构预测. 自然 596.7873 (2021): 583-589.

  • Xu, Jiehui, 等人. 异常变换器:基于关联差异的时间序列异常检测. arXiv 预印本 arXiv:2110.02642 (2021).

  • Yuan, Li, 等人. 从零开始训练图像 Net 上的视觉变换器:tokens-to-token vit. 2021 年 IEEE/CVF 国际计算机视觉会议论文集. 2021.

  • Liu, Yinhan, 等人. Roberta: 一种鲁棒优化的 BERT 预训练方法. arXiv 预印本 arXiv:1907.11692 (2019).

  • Lewis, Mike, 等人. Bart: 用于自然语言生成、翻译和理解的去噪序列到序列预训练. arXiv 预印本 arXiv:1910.13461 (2019).

  • Radford, Alec, 等人. 通过生成预训练改进语言理解. (2018).

  • Raffel, Colin, 等人. 通过统一的文本到文本变换器探索迁移学习的极限. 机器学习研究杂志 21.1 (2020): 5485-5551.

  • Sanh, Victor, 等人. DistilBERT,BERT 的精炼版本:更小、更快、更便宜、更轻. arXiv 预印本 arXiv:1910.01108 (2019).

  • Yang, Zhilin, 等人. Xlnet: 用于语言理解的广义自回归预训练. 神经信息处理系统进展 32 (2019).

  • Mikolov, Tomas, 等人. 在向量空间中高效估计词表示. arXiv 预印本 arXiv:1301.3781 (2013).

  • Pennington, Jeffrey, Richard Socher, 和 Christopher D. Manning. Glove: 用于词表示的全局向量. 2014 年自然语言处理实证方法会议论文集 (EMNLP). 2014.

  • Bojanowski, Piotr, 等人. 通过子词信息丰富词向量. 计算语言学协会会刊 5 (2017): 135-146.

  • Wu, Zonghan, 等人. 图神经网络综述. 电气和电子工程师协会神经网络和机器学习系统交易 32.1 (2020): 4-24.

  • Abdollahi, Nasim, 等人. NodeCoder: 一种基于图的机器学习平台,用于预测建模蛋白质结构的活性位点. arXiv 预印本 arXiv:2302.03590 (2023).

  • Kipf, Thomas N., 和 Max Welling. 使用图卷积网络进行半监督分类. arXiv 预印本 arXiv:1609.02907 (2016).

  • Hamilton, Will, Zhitao Ying, and Jure Leskovec. 在大图上进行归纳表示学习. 神经信息处理系统进展 30 (2017).

  • Velickovic, Petar, et al. 图注意力网络. stat 1050.20 (2017): 10-48550.

  • Gilmer, Justin, et al. 神经消息传递在量子化学中的应用. 机器学习国际会议. PMLR, 2017.

  • Yang, Zhilin, William Cohen, and Ruslan Salakhudinov. 重新审视使用图嵌入的半监督学习. 机器学习国际会议. PMLR, 2016.

第十四章:机器学习最新进展简介

监督学习一直是机器学习在不同行业和应用领域成功应用的重点,直到 2020 年。然而,其他技术,如生成模型,后来引起了机器学习开发者和用户的关注。因此,了解这些技术将有助于你拓宽对机器学习能力的理解,超越监督学习。

本章将涵盖以下主题:

  • 生成模型

  • 强化学习

  • 自监督学习

到本章结束时,你将了解生成模型、强化学习RL)和自监督学习SSL)的含义、广泛使用的技术和好处。你还将使用 Python 和 PyTorch 练习其中的一些技术。

技术要求

以下要求适用于本章,因为它们将帮助你更好地理解概念,能够在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • torch >= 2.0.0

    • torchvision >= 0.15.1

    • matplotlib >= 3.7.1

你可以在 GitHub 上找到本章的代码文件,地址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter14

生成模型

生成模型,或更普遍的生成 AI,为你提供了生成接近预期或参考数据点集或分布的数据的机会,通常称为真实数据。生成模型最成功的应用之一是语言建模。生成预训练 TransformerGPT)-4 和 ChatGPT(openai.com/blog/ChatGPT),这是一个建立在 GPT-4 和 GPT-3.5 之上的聊天机器人,以及类似的工具如 Perplexity(www.perplexity.ai/),引起了工程师、科学家、金融和医疗保健等不同行业的人士以及许多其他生成模型相关岗位的人士的兴趣。当使用 Chat-GPT 或 GPT-4 时,你可以提出一个问题或提供询问的描述,称为提示,然后这些工具会生成一系列陈述或数据来为你提供你请求的答案、信息或文本。

除了在文本生成中成功应用生成模型之外,许多其他生成模型的应用可以帮助你在工作或学习中。例如,GPT-4 及其之前的版本或其他类似模型,如 LLaMA(Touvron 等,2023 年),可用于代码生成和补全(github.com/features/copilot/github.com/sahil280114/codealpaca)。你可以编写你感兴趣生成的代码,它会为你生成相应的代码。尽管生成的代码可能并不总是按预期工作,但通常在经过几次尝试后,它至少接近预期。

生成模型还有许多其他成功的应用,例如在图像生成(openai.com/product/dall-e-2)、药物发现(Cheng 等,2021 年)、时尚设计(Davis 等,2023 年)、制造业(Zhao 等,2023 年)等领域。

从 2023 年开始,许多传统商业工具和服务开始整合生成 AI 功能。例如,你现在可以使用生成 AI 在 Adobe Photoshop 中编辑照片,只需用简单的英语说明你需要什么(www.adobe.com/ca/products/photoshop/generative-fill.html)。WolframAlpha 也将它的符号计算能力与生成 AI 结合,你可以用简单的英语请求特定的符号过程(www.wolframalpha.com/input?i=Generative+Adversarial+Networks)。可汗学院([www.khanacademy.org/](https://www.khanacademy.org/))制定了一种策略,帮助教师和学生从生成 AI 中受益,特别是 ChatGPT,而不是对学生的教育造成伤害。

这些成功故事是通过依赖为生成模型设计的不同深度学习技术实现的,我们将在下面简要回顾。

生成深度学习技术

在 PyTorch 或其他深度学习框架(如 TensorFlow)中,有多种生成模型方法可供使用。在这里,我们将回顾其中的一些,以帮助你开始了解它们是如何工作的,以及你如何在 Python 中使用它们。

基于 Transformer 的文本生成

你已经了解到,2017 年引入的转换器(Vaswani et al., 2017)被用于生成最近最成功的语言模型,这在第十三章“高级深度学习技术”中有详细描述。然而,这些模型并不仅限于像翻译这样的传统自然语言处理任务,它们还可以用于生成建模,帮助我们生成有意义的文本,例如,回答我们提出的问题。这正是 GPT 模型、Chat-GPT 以及许多其他生成语言模型背后的方法。提供简短文本作为提问或问题的过程也被称为提示(prompting),在这个过程中,我们需要提供一个好的提示以获得好的答案。我们将在“基于文本的生成模型的提示工程”部分讨论最优提示。

变分自编码器(VAEs)

自编码器是一种技术,你可以将特征数量减少到一个信息丰富的嵌入集,你可以将其视为主成分分析(PCA)的更复杂版本,以更好地理解它。它是通过首先尝试将原始空间编码到新的嵌入(称为编码),然后解码嵌入,并为每个数据点(称为解码)重新生成原始特征来实现的。在 VAE(Kingma and Welling, 2013)中,它不是生成一组特征(嵌入),而是为每个新特征生成一个分布。例如,不是将原始的 1,000 个特征减少到 100 个特征,每个特征有一个浮点值,而是得到 100 个新的变量,每个变量都是一个正态分布(或高斯分布)。这个过程的美妙之处在于,然后你可以从这些分布中选择不同的值来为每个变量生成一组新的 100 个嵌入。在解码它们的过程中,这些嵌入被解码,并生成一组具有原始大小(1,000)的新特征。这个过程可以用于不同类型的数据,如图像(Vahdat et al., 2020)和图(Simonovsky et al., 2018; Wengong et al., 2018)。你可以在github.com/AntixK/PyTorch-VAE找到实现 PyTorch 的 VAE 的集合。

生成对抗网络(GANs)

在 2014 年引入的这项技术(Goodfellow et al., 2020)中,一个类似于监督分类模型的判别器和生成器协同工作。生成器,可能是一个用于生成所需数据类型(如图像)的神经网络架构,旨在生成图像以欺骗判别器,使其将生成的数据识别为真实数据。判别器学习如何保持区分生成数据和真实数据的能力。在某些情况下,生成的数据被称为假数据,例如在深度伪造(www.businessinsider.com/guides/tech/what-is-deepfake)等技术模型中。然而,生成的数据可以作为新数据点在不同应用中使用的机会,例如药物发现(Prykhodko et al., 2019)。你可以使用torchgan来实现 GANs(torchgan.readthedocs.io/en/latest/)。

由于基于生成模型之上涌现出一批基于提示的技术,我们将提供如何最优设计提示的更好理解。

基于文本生成模型的提示工程

提示工程不仅是在机器学习中的一个新兴话题,而且已经成为一个高薪的职位名称。在提示工程中,我们的目标是提供最优的提示以生成最佳可能的结果(例如,文本、代码和图像),并将生成模型的问题识别为改进它们的机会。对大型语言和生成模型的基本理解、你的语言熟练度和特定领域的数据生成领域的专业知识可以帮助你更好地进行提示。有一些免费资源可以帮助你学习提示工程,例如 Andrew Ng 和 OpenAI 提供的一门课程(www.deeplearning.ai/short-courses/ChatGPT-prompt-engineering-for-developers/)以及微软发布的一些关于提示工程的入门内容(learn.microsoft.com/en-us/azure/cognitive-services/openai/concepts/prompt-engineering)。然而,我们不会让你自己从头开始学习这个话题。在这里,我们将提供一些关于最优提示的指导,这将帮助你提高你的提示技能。

目标提示

在我们的日常对话中,无论是在工作、大学还是家里,我们都有方法确保对方更好地理解我们的意思,从而得到更好的回应。例如,如果你对朋友说“给我那个”,而不是“给我桌子上那瓶水”,你的朋友可能不会给你那瓶水,或者对你具体指的是什么感到困惑。在提示中,如果你清楚地解释了针对一个非常具体的任务你想要什么,你可以得到更好的回应和生成数据,比如图像。以下是一些用于更好提示的技术:

  • 明确要求:你可以提供具体信息,例如你希望生成的数据的格式,如项目符号或代码,以及你所指的任务,如撰写电子邮件与编写商业计划。

  • 指定数据生成对象:你甚至可以指定为谁生成数据的专业技能或职位,例如为机器学习工程师、业务经理或软件开发人员生成一段文本。

  • 指定时间:你可以指定你想要的信息,比如技术发布日期、某事首次宣布的时间、事件的年代顺序,以及像埃隆·马斯克这样的名人随时间变化的净资产变化等等。

  • 简化概念:你可以提供一个简化的版本,确保模型不会被你提示的复杂性所困惑。

虽然这些技巧可以帮助你更好地进行提示,但如果要求文本响应或生成无关数据,仍然有可能得到高度自信的虚假答案。这通常被称为幻觉。减少无关或不正确响应或数据生成机会的一种方法是为模型提供测试。当我们用 Python 编写函数和类时,我们可以设计单元测试来确保它们的输出符合预期,正如在第8 章中讨论的,使用测试驱动开发控制风险

使用 PyTorch 进行生成建模

你可以使用本章前面讨论的不同技术,基于 PyTorch 开发生成模型。我们在这里想练习使用 VAEs。VAE 的目标是为数据的低维表示找到一个概率分布。例如,模型学习关于输入参数表示的均值和方差(或对数方差),假设潜在空间(即潜在变量或表示的空间)为正态或高斯分布。

我们首先导入所需的库和模块,并从 PyTorch 加载Flowers102数据集:

transform = transforms.Compose([    transforms.Resize((32, 32)),
    transforms.ToTensor()
])
train_dataset = datasets.Flowers102(root='./data',
    download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32,
    shuffle=True)

然后,我们定义了一个用于 VAE 的类,如下所示,其中定义了两个线性层来编码图像的输入像素。然后,通过两个线性层定义了潜在空间概率分布的均值和方差,以便将潜在变量解码回原始输入数量以生成与输入数据相似的图像。在潜在空间中学习的分布的均值和方差将被用来生成新的潜在变量,并可能生成新的数据:

class VAE(nn.Module):    def __init__(self):
        super(VAE, self).__init__()
        self.encoder = nn.Sequential(
            nn.Linear(32 * 32 * 3, 512),
            nn.ReLU(),
            nn.Linear(512, 128),
            nn.ReLU(),
        )self.fc_mean = nn.Linear(128, 32)
         self.fc_var = nn.Linear(128, 32)
         self.decoder = nn.Sequential(
             nn.Linear(32, 128),
             nn.ReLU(),
             nn.Linear(128, 512),
             nn.ReLU(),
             nn.Linear(512, 32 * 32 * 3),
             nn.Sigmoid(),
         )
    def forward(self, x):
        h = self.encoder(x.view(-1, 32 * 32 * 3))
        mean, logvar = self.fc_mean(h), self.fc_var(h)
        std = torch.exp(0.5*logvar)
        q = torch.distributions.Normal(mean, std)
        z = q.rsample()
        return self.decoder(z), mean, logvar

现在,我们初始化定义的VAE类,并将Adam优化器作为优化算法,学习率为0.002

model = VAE()optimizer = optim.Adam(model.parameters(), lr=2e-3)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

然后,我们定义了一个使用binary_cross_entropy的损失函数,如下所示,以比较重新生成的像素与输入像素:

def loss_function(recon_x, x, mu, logvar):    BCE = nn.functional.binary_cross_entropy(recon_x,
        x.view(-1, 32 * 32 * 3), reduction='sum')
    KLD = -0.5 * torch.sum(
        1 + logvar - mu.pow(2) - logvar.exp())
    return BCE + KLD

现在,我们准备使用之前加载的Flowers102数据集来训练模型:

n_epoch = 400for epoch in range(n_epoch):
    model.train()
    train_loss = 0
    for batch_idx, (data, _) in enumerate(train_loader):
        data = data.to(device)
        optimizer.zero_grad()
        recon_batch, mean, logvar = model(data)
        loss = loss_function(recon_batch, data, mean,
            logvar)
        loss.backward()
        train_loss += loss.item()
        optimizer.step()
    print(f'Epoch: {epoch} Average loss: {
        train_loss / len(train_loader.dataset):.4f}')

然后,我们可以使用这个训练好的模型来生成几乎像花朵一样的图像(见图 14**.1)。通过超参数优化,例如改变模型的架构,你可以获得更好的结果。你可以在第十二章中回顾深度学习中的超参数优化,超越机器学习调试的深度学习

图 14.1 – 我们之前开发的简单 VAE 生成的示例图像

图 14.1 – 我们之前开发的简单 VAE 生成的示例图像

这只是一个使用 PyTorch 进行生成建模的简单示例。尽管生成建模取得了成功,但最近使用生成模型开发的工具(如 Chat-GPT)的部分成功归功于强化学习的智能使用,我们将在下一节中讨论。

强化学习

强化学习RL)不是一个新想法或技术。其最初的想法可以追溯到 20 世纪 50 年代,当时由理查德·贝尔曼提出,并引入了贝尔曼方程(Sutton 和 Barto,2018)。然而,它最近与人类反馈的结合,我们将在下一节中解释,为它在开发机器学习技术中的效用提供了新的机会。强化学习的一般思想是通过经验学习,或与指定环境的交互,而不是像监督学习那样使用收集到的数据点集进行训练。在强化学习中,考虑了一个代理,它学习如何改进动作以获得更大的奖励(Kaelbling 等人,1996)。代理在接收到前一步采取的动作的奖励后,会迭代地改进其采取行动的方法,或更技术性地说是策略。

在 RL 的历史上,两个重要的发展和用途导致了其流行度的增加,包括 Q-learning(Watkins,1989)的发展以及使用 Q-learning 将 RL 和深度学习(Mnih 等人,2013)相结合。尽管 RL 背后的成功故事和它模仿人类经验学习的直觉,但已经证明深度强化学习不是数据高效的,需要大量的数据或迭代经验,这使得它与人类学习在本质上不同(Botvinick 等人,2019)。

最近,带有人类反馈的强化学习RLHF)被用作强化学习成功应用于改进生成模型结果的应用,我们将在下文中讨论。

带有人类反馈的强化学习(RLHF)

使用带有人类反馈的强化学习,奖励是根据人类反馈计算的,无论是专家还是非专家,这取决于问题。然而,奖励并不是一个预定义的数学公式,考虑到问题的复杂性,如语言模型。人类提供的反馈会导致模型逐步改进。例如,RLHF 语言模型的训练过程可以总结如下 (huggingface.co/blog/rlhf):

  1. 训练语言模型,这被称为预训练。

  2. 数据收集和训练奖励模型。

  3. 使用奖励模型通过强化学习微调语言模型。

然而,学习如何使用 PyTorch 设计基于 RLHF 的模型可能有助于更好地理解这一概念。

基于 PyTorch 的 RLHF

从 RLHF 中受益的一个主要挑战是设计用于人类反馈收集和整理的基础设施,然后提供它们来计算奖励,然后改进主要预训练模型。在这里,我们不想深入探讨 RLHF 的这一方面,而是通过一个简单的代码示例来了解如何将此类反馈纳入机器学习模型。有一些很好的资源,如 github.com/lucidrains/PaLM-rlhf-pytorch,可以帮助你更好地理解 RLHF 以及如何使用 Python 和 PyTorch 来实现它。

在这里,我们将使用 GPT-2 (huggingface.co/transformers/v1.2.0/_modules/pytorch_transformers/modeling_gpt2.html) 作为预训练模型。首先,我们导入必要的库和模块,并初始化模型、分词器和优化器,这里选择的是 Adam

import torchfrom transformers import GPT2LMHeadModel, GPT2Tokenizer
from torch import optim
from torch.utils.data import DataLoader
# Pretrain a GPT-2 language model
tokenizer = GPT2Tokenizer.from_pretrained('gpt2')
model = GPT2LMHeadModel.from_pretrained('gpt2')
optimizer = optim.Adam(model.parameters(), lr=1e-3)

现在,假设我们已经收集了人类反馈并正确格式化,我们可以使用它来创建一个来自 PyTorch 的 DataLoader:

dataloader = DataLoader(dataset, batch_size=1, shuffle=True)

下一步是设计一个奖励模型,我们使用一个两层全连接神经网络:

class Reward_Model(torch.nn.Module):    def __init__(self, input_size, hidden_size, output_size):
        super(RewardModel, self).__init__()
        self.fc_layer1 = torch.nn.Linear(input_size,
            hidden_size)
        self.fc_layer2 = torch.nn.Linear(hidden_size,
            output_size)
    def forward(self, x):
        x = torch.relu(self.fc_layer1(x))
        x = self.fc_layer2(x)
        return x

然后,我们使用先前定义的类初始化奖励模型:

reward_model = Reward_Model(input_size, hidden_size, output_size)

我们现在可以使用收集到的人类反馈和奖励模型来改进我们的预训练模型。如果你注意以下代码,与没有奖励模型的神经网络相比,这个简单的循环遍历 epochs 和 batches 进行模型训练的主要区别在于奖励计算,然后将其用于损失计算:

for epoch in range(n_epochs):    for batch in dataloader:
        input_ids = tokenizer.encode(batch['input'],
            return_tensors='pt')
        output_ids = tokenizer.encode(batch['output'],
            return_tensors='pt')
        reward = reward_model(batch['input'])
        loss = model(input_ids, labels=output_ids).loss * reward
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

这是一个设计基于 RLHF 的模型改进的非常简单的例子,用于帮助你更好地理解这个概念。例如,github.com/lucidrains/PaLM-rlhf-pytorch这样的资源将帮助你实现更复杂的方法,将此类人类反馈纳入模型改进。

接下来,让我们探讨机器学习中的另一个有趣话题,称为自监督学习。

自监督学习(SSL)

自监督学习SSL)不是一个新概念。它与强化学习类似,但由于其在学习数据表示方面的有效性,在深度学习结合之后引起了人们的关注。此类模型的例子包括用于语言建模的 Word2vec(Mikolov 等人,2013 年)和 Meta 的 RoBERTa 模型,这些模型使用 SSL 训练,在多个语言建模任务上取得了最先进的性能。SSL 的想法是为机器学习模型定义一个目标,该目标不依赖于预先标记或数据点的量化——例如,使用前一时间步预测视频中的对象或人的位置,遮盖图像或序列数据的一部分,并试图填充这些被遮盖的部分。此类模型的一个广泛应用的例子是在强化学习中学习图像和文本的表示,然后在其他上下文中使用这些表示,例如,在带有数据标签的小数据集上进行监督建模(Kolesnikov 等人,2019 年,王等人,2020 年)。

SSL 的范畴下有多个技术,以下列举三个:

  • 对比学习:对比学习的想法是学习表示,使得相似的数据点比不相似的数据点更接近(Jaiswal 等人,2020 年)。

  • 自回归模型:在自回归建模中,模型旨在根据之前的数据点预测下一个数据点,无论是基于时间还是特定的序列顺序。这在语言建模中是一个非常流行的技术,例如 GPT 模型预测句子中的下一个单词(Radford 等人,2019 年)。

  • 通过修复缺失部分进行自监督:在这种方法中,我们遮盖数据的一部分,并训练模型来填补缺失的部分。例如,图像的一部分可能被遮盖,模型被训练来预测被遮盖的部分。遮盖自动编码器是这种技术的一个例子,其中自动编码器的解码过程中填充了图像被遮盖的部分(张等人,2022 年)。

接下来,我们将通过一个简单的 Python 和 PyTorch 自监督建模示例进行练习。

使用 PyTorch 的自监督学习

从编程的角度来看,与监督学习相比,SSL 深度学习的主要区别在于定义训练和测试的目标和数据。在这里,我们想使用我们用来练习 RLHF 的Flowers102数据集进行练习。

我们首先使用两个编码和解码torch.nn.Conv2d()层定义神经网络类,如下所示:

class Conv_AE(nn.Module):    def __init__(self):
        super(Conv_AE, self).__init__()
        # Encoding data
        self.encoding_conv1 = nn.Conv2d(3, 8, 3, padding=1)
        self.encoding_conv2 = nn.Conv2d(8, 32, 3,padding=1)
        self.pool = nn.MaxPool2d(2, 2)
        # Decoding data
        self.decoding_conv1 = nn.ConvTranspose2d(32, 8, 2,
            stride=2)
        self.decoding_conv2 = nn.ConvTranspose2d(8, 3, 2,
            stride=2)
    def forward(self, x):
        # Encoding data
        x = torch.relu(self.encoding_conv1(x))
        x = self.pool(x)
        x = torch.relu(self.encoding_conv2(x))
        x = self.pool(x)
        # Decoding data
        x = torch.relu(self.decoding_conv1(x))
        x = self.decoding_conv2(x)
        x = torch.sigmoid(x)
        return x

然后,我们初始化模型,指定torch.nn.MSELoss()作为预测图像和真实图像比较的标准,以及torch.optim.Adam()作为优化器,学习率为0.001

model = Conv_AE().to(device)criterion = nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

以下函数帮助我们实现对每个图像随机 8x8 部分的掩码,然后自动编码器学习填充这些部分:

def create_mask(size=(32, 32), mask_size=8):    mask = np.ones((3, size[0], size[1]), dtype=np.float32)
    height, width = size
    m_height, m_width = mask_size, mask_size
    top = np.random.randint(0, height - m_height)
    left = np.random.randint(0, width - m_width)
    mask[:, top:top+m_height, left:left+m_width] = 0
    return torch.from_numpy(mask)

然后,我们按照以下方式训练模型 200 个 epoch。正如你在图 14.2中可以看到的,图像首先被掩码,然后在解码步骤中,自动编码器试图重建完整的图像,包括掩码部分:

n_epoch = 200for epoch in range(n_epoch):
    for data in train_loader:
        img, _ = data
        # Creating mask for small part in training images
        mask = create_mask().to(device)
        img_masked = img * mask
        img = img.to(device)
        img_masked = img_masked.to(device)
        optimizer.zero_grad()
        outputs = model(img_masked)
        loss = criterion(outputs, img)
        loss.backward()
        optimizer.step()

正如你在图 14.2中看到的填充图像的示例中所示,模型能够正确地找到模式。然而,通过适当的超参数优化和设计具有更好神经网络架构的模型,你可以实现更高的性能和更好的模型。

图 14.2 – 使用卷积自动编码器模型的示例图像(第一行)、其掩码版本(第二行)和再生版本(第三行)

图 14.2 – 使用卷积自动编码器模型的示例图像(第一行)、其掩码版本(第二行)和再生版本(第三行)

你可以通过提供的资源和参考资料了解更多关于 SSL 和本章中提供的技术,以更好地理解这些概念。

摘要

在本章中,你获得了对机器学习建模中监督学习之外的最新进展的高级理解,包括生成建模、强化学习和自监督学习。你还了解了最佳提示和提示工程,以便从基于生成模型并接受用户文本提示的工具和应用中受益。你提供了相关的代码仓库和 Python 和 PyTorch 中可用的功能,这将帮助你开始学习这些高级技术。这些知识不仅帮助你更好地理解它们是如何工作的,如果你遇到它们,而且开始使用这些高级技术构建自己的模型。

在下一章中,你将了解在机器学习建模和实践中识别因果关系的好处,以及使用 Python 库实现因果建模的示例。

问题

  1. 生成深度学习技术的例子有哪些?

  2. 使用 transformers 的生成文本模型的例子有哪些?

  3. GAN 中的生成器和判别器是什么?

  4. 你可以使用哪些技术来提高提示的效果?

  5. 你能解释一下强化学习如何有助于导入生成模型的结果吗?

  6. 简要解释对比学习。

参考文献

  • Cheng, Yu, 等人. “药物发现中的分子设计:深度生成模型的全面综述。” 生物信息学简报 22.6 (2021): bbab344.

  • Davis, Richard Lee, 等人. “塑造未来:解锁深度生成模型在设计空间探索中的创造潜能。” 2023 年 CHI 会议关于人机交互系统人类因素扩展摘要 (2023).

  • Zhao, Yaoyao Fiona, 等人,编。 “高级制造设计。” 机械设计杂志 145.1 (2023): 010301.

  • Touvron, Hugo, 等人. “Llama:开放且高效的基金会语言模型。” arXiv 预印本 arXiv:2302.13971 (2023).

  • Vaswani, Ashish, 等人. “注意力即所需。” 神经信息处理系统进展 30 (2017).

  • Kingma, Diederik P., 和 Max Welling. “自动编码变分贝叶斯。” arXiv 预印本 arXiv:1312.6114 (2013).

  • Vahdat, Arash, 和 Jan Kautz. “NVAE:一种深度分层变分自动编码器。” 神经信息处理系统进展 33 (2020): 19667-19679.

  • Simonovsky, Martin, 和 Nikos Komodakis. “Graphvae:使用变分自动编码器生成小图。” 人工神经网络与机器学习–ICANN 2018:第 27 届国际人工神经网络会议,希腊罗得岛,2018 年 10 月 4-7 日,会议论文集,第一部分 27. Springer 国际出版社 (2018).

  • Jin, Wengong, Regina Barzilay, 和 Tommi Jaakkola. “用于分子图生成的连接树变分自动编码器。” 机器学习国际会议 PMLR (2018).

  • Goodfellow, Ian, 等人. “生成对抗网络。” ACM 通讯 63.11 (2020): 139-144.

  • Karras, Tero, Samuli Laine, 和 Timo Aila. “基于风格的生成对抗网络生成器架构。” IEEE/CVF 计算机视觉与模式识别会议论文集 (2019).

  • Prykhodko, Oleksii, 等人. “基于潜在向量生成对抗网络的从头分子生成方法。” 化学信息学杂志 11.1 (2019): 1-13.

  • Sutton, Richard S., 和 Andrew G. Barto. 强化学习:入门。 MIT 出版社 (2018).

  • Kaelbling, Leslie Pack, Michael L. Littman, 和 Andrew W. Moore. “强化学习:综述。” 人工智能研究杂志 4 (1996): 237-285.

  • Watkins, Christopher John Cornish Hellaby. 从延迟奖励中学习。 (1989).

  • Mnih, Volodymyr, 等人. “使用深度强化学习玩 Atari。” arXiv 预印本 arXiv:1312.5602 (2013).

  • Botvinick, Matthew, 等人. “强化学习,快与慢。” 认知科学趋势 23.5 (2019): 408-422.

  • Kolesnikov, Alexander, Xiaohua Zhai 和 Lucas Beyer。“重新审视自监督视觉表示学习。” IEEE/CVF 计算机视觉与模式识别会议论文集 (2019).

  • Wang, Jiangliu, Jianbo Jiao 和 Yun-Hui Liu。“通过速度预测进行自监督视频表示学习。” 计算机视觉–ECCV 2020: 第 16 届欧洲计算机视觉会议,英国格拉斯哥,2020 年 8 月 23 日至 28 日,第 17 卷 16。Springer 国际出版社 (2020)。

  • Jaiswal, Ashish, 等人。“关于对比自监督学习的综述。” Technologies 9.1 (2020): 2.

  • Radford, Alec, 等人。“语言模型是无监督的多任务学习者。” OpenAI 博客 1.8 (2019): 9.

  • Zhang, Chaoning, 等人。“关于视觉和更多领域的掩码自动编码器在自监督学习中的应用综述。” arXiv 预印本 arXiv:2208.00173 (2022).

第五部分:模型调试的高级主题

在本书的结论部分,我们将探讨机器学习中最关键的一些主题。我们将首先解释相关性和因果性的区别,阐明它们在模型开发中的不同影响。过渡到安全和隐私的主题,我们将讨论确保我们的模型既强大又尊重用户数据的紧迫问题、挑战和技术。我们将以对人类在循环机器学习的解释来结束本书,强调人类专业知识与自动化系统之间的协同作用,以及这种合作如何为更有效的解决方案铺平道路。

本部分包含以下章节:

  • 第十五章, 相关性 versus 因果性

  • 第十六章机器学习中的安全和隐私

  • 第十七章人类在循环机器学习

第十五章:相关性与因果性

在本书的前几章中,你学习了如何训练、评估和构建高性能和低偏差的机器学习模型。然而,我们用来练习本书中引入的概念的算法和示例方法并不一定能在监督学习设置中为你提供特征和输出变量之间的因果关系。在本章中,我们将讨论因果推理和建模如何帮助你提高模型在生产中的可靠性。

在本章中,我们将涵盖以下主题:

  • 相关性作为机器学习模型的一部分

  • 因果建模以降低风险和提高性能

  • 在机器学习模型中评估因果关系

  • 使用 Python 进行因果建模

到本章结束时,你将了解因果建模和推理相对于相关性建模和练习可用 Python 功能来识别特征和输出变量之间因果关系的益处。

技术要求

为了更好地理解概念、在项目中使用它们以及使用提供的代码进行练习,你需要以下内容:

  • Python 库需求:

    • dowhy == 0.5.1

    • bnlearn == 0.7.16

    • sklearn >= 1.2.2

    • d3blocks == 1.3.0

  • 你还需要了解机器学习模型训练、验证和测试的基本知识

本章的代码文件可在 GitHub 上找到,地址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter15

相关性作为机器学习模型的一部分

大多数机器学习建模和数据分析项目在监督学习设置和统计建模中导致特征和输出变量之间的相关性关系。尽管这些关系不是因果的,但识别因果关系具有很高的价值,即使在我们试图解决的多数问题中它不是必需的。例如,我们可以将医学诊断定义为“根据患者的医疗历史,识别最有可能导致患者症状的疾病”(Richens 等人,2020 年)。

识别因果关系解决在识别变量之间误导性关系时的问题。仅仅依赖相关性而不是因果关系可能会导致虚假和奇怪的关联,如下所示(www.tylervigen.com/spurious-correlationswww.buzzfeednews.com/article/kjh2110/the-10-most-bizarre-correlations):

  • 美国在科学、太空和技术上的支出与吊死、勒死和窒息自杀相关

  • 美国电子游戏产生的总收入与在美国授予的计算机科学博士学位相关

  • 美国从挪威进口的原油与在铁路列车碰撞中丧生的驾驶员相关

  • 吃有机食品与自闭症相关

  • 肥胖与债务泡沫相关

你可以在这些例子的来源中找到更多这些虚假的相关性。

依赖于相关性而非因果关系可能会降低技术发展和改进过程中不同方面的可靠性,例如 A/B 测试。例如,理解“如果我们能让更多的访客进行搜索,我们将看到购买和收入的增加”(conversionsciences.com/correlation-causation-impact-ab-testing/)有助于正确的决策和对技术发展的投资。

现在你已经了解了仅仅依赖相关关系的问题,让我们来讨论在机器学习环境中因果建模的含义。

因果建模以降低风险和提高性能

因果建模有助于消除变量之间不可靠的相关关系。消除这种不可靠的关系可以降低机器学习在不同应用领域(如医疗保健)中错误决策的风险。医疗保健中的决策,如诊断疾病和为患者指定有效的治疗方案,对生活质量及生存有直接影响。因此,决策需要基于可靠的模型和关系,其中因果建模和推理可以帮助我们(Richens et al., 2020; Prosperi et al., 2020; Sanchez et al., 2022)。

因果建模技术有助于消除模型中的偏差,如混淆和碰撞偏差(Prosperi et al., 2020)(图 15.1)。这种偏差的一个例子是吸烟作为黄手指与肺癌之间关系的混淆因素(Prosperi et al., 2020)。如图图 15.1 所示,碰撞变量的存在导致某些输入变量与结果之间存在相关但偏差和虚假的关联。此外,在建模中缺少可能造成混淆的一些变量可能导致我们得出其他变量与结果相关的结论:

图 15.1 – 混淆和碰撞偏差的示意图

图 15.1 – 混淆和碰撞偏差的示意图

接下来,我们将介绍因果建模中的一些概念和技术,例如因果推理以及如何在机器学习模型中测试因果关系。

评估机器学习模型中的因果关系

在机器学习建模中计算特征与结果之间的相关性在许多领域和行业中已经成为一种常见的方法。例如,我们可以简单地计算皮尔逊相关系数来识别与目标变量相关的特征。在我们的许多机器学习模型中,也有一些特征对结果的预测不是作为因果因素,而是作为相关预测因素。我们可以使用 Python 中可用的功能来区分这些相关和因果特征。以下是一些示例:

  • 实验设计:建立因果关系的一种方式是通过进行实验,测量因果特征变化对目标变量的影响。然而,这样的实验研究可能并不总是可行或道德的。

  • 特征重要性:我们可以使用在第六章《机器学习建模中的可解释性和可解释性》中介绍的可解释性技术来识别特征重要性,并利用这些信息来区分相关性和因果关系。

  • 因果推断:因果推断方法旨在识别变量之间的因果关系。你可以使用因果推断来确定一个变量的变化是否会导致另一个变量的变化。

我们在第六章《机器学习建模中的可解释性和可解释性》中讨论了不同的可解释性技术,如 SHAP、LIME 和反事实解释。你可以使用这些技术来识别模型中非因果的特征。例如,SHAP 值低的特征很可能在所研究的模型中不是因果特征。如果根据 LIME,在局部近似中有低重要性的特征,那么它很可能与模型的输出无关。或者,如果改变一个特征对模型输出的影响很小或没有影响,通过反事实分析,那么它很可能不是一个因果特征。

我们还可以使用另一种技术,称为置换特征重要性,它也被认为是可解释性技术的一部分,用于识别低概率的因果特征。在这种方法中,我们改变一个特征的价值,并测量这种变化对模型性能的影响。然后我们可以识别出低影响的特征,这些特征很可能不是因果特征。

我们已经在第六章《机器学习建模中的可解释性和可解释性》中实践了可解释性技术。接下来我们将专注于本章剩余部分的因果推断。

因果推断

在因果推断中,我们的目标是识别和理解数据集或模型中变量之间的因果关系。在这个过程中,我们可能会依赖不同的统计和机器学习技术来分析数据并推断变量之间的因果关系。图 15.2展示了五种这样的方法:实验设计观察性研究倾向得分匹配工具变量基于机器学习的方法

图 15.2 – 五种因果推断技术

图 15.2 – 五种因果推断技术

实验设计中,你设计实验来比较具有不同治疗变量或基于特定特征或特征的条件的样本的结局变量。表 15.1中提供了治疗和结果变量的示例,以帮助您理解这两个术语之间的区别:

治疗变量 结果变量
教育水平 收入水平
吸烟 肺癌
体力活动 心血管健康
家庭收入 学术表现

表 15.1 – 因果建模中治疗和结果变量的示例

观察性研究中,我们使用观察数据,而不是受控实验,并试图通过控制混杂变量来识别因果关系。倾向得分匹配根据观察变量的概率来匹配治疗组和对照组。工具变量用于克服观察性研究中常见的难题,即治疗和结果变量由其他变量(或混杂变量)共同决定,而这些变量未包含在模型中。这种方法从识别一个与治疗变量相关但与结果变量不相关的工具变量开始,除了通过其对治疗变量的影响之外。基于机器学习的方法是其他技术类别,其中使用贝叶斯网络和决策树等机器学习方法来识别变量和结果之间的因果关系。

贝叶斯网络

您可以从贝叶斯网络中受益,在因果建模和识别变量之间的因果关系时。贝叶斯网络是图形模型,通过有向无环图DAGs)展示变量之间的关系,其中每个变量(包括输入特征和输出)都是一个节点,方向显示变量之间的关系(图 15.3):

图 15.3 – 示例贝叶斯网络的说明

图 15.3 – 示例贝叶斯网络的说明

这个网络告诉我们,特征 A特征 B的值越高,结果发生的可能性就越大。请注意,特征可以是数值的或分类的。尽管从特征 A 到结果的方向(图 15.3)不一定意味着因果关系,但贝叶斯网络可以用来估计变量对结果的因果效应,同时控制混杂变量。

从概率的角度来看,网络可以用来简化所有变量的联合概率,包括特征和结果,如下所示:

p(F A, F B, F C, Outcome) = p(Outcome| F A, F B)p(F B| F C)p(F C| F A)p(F A)

在这里,p(Outcome| F A, F B)是给定特征 A 和 B 值的条件概率分布CPD),p(F B| F C)是给定特征 C 的特征 B 的 CPD,p(F C| F A)是给定特征 A 的特征 C 的 CPD,p(F A)是特征 A 的概率,它不是其他特征的函数,因为在图中没有指向它的边。这些 CPD 可以帮助我们估计一个特征值的变化对另一个特征的影响。它告诉我们,在给定一个或多个变量发生的情况下,一个变量的发生可能性。你将在本章结束时学习如何以数据驱动的方式为给定的数据集创建贝叶斯网络,以及如何使用 Python 识别网络的 CPD。

Python 中有几种可用于因果推断的方法。我们将在下一节中介绍这些方法。

使用 Python 进行因果建模

几个 Python 库为你提供了使用因果方法和进行因果推断的易于使用的功能。以下是一些:

在接下来的几个小节中,我们将回顾dowhybnlearn

使用 dowhy 进行因果效应估计

首先,我们想要练习一种倾向得分匹配方法,这种方法在你有一个治疗变量在心中时很有用——例如,当你想要确定药物对患者的效果,并在模型中有其他变量,如他们的饮食、年龄、性别等时。在这里,我们将使用scikit-learn的乳腺癌数据集,其中目标变量是一个二元结果,告诉我们来自乳腺癌患者的细胞是恶性还是良性。在这里,我们将使用平均半径特征——即从中心到边缘上的点的平均距离——作为治疗变量。

首先,我们必须在 Python 中导入所需的库和模块:

import pandas as pdimport numpy as np
from sklearn.datasets import load_breast_cancer
import dowhy
from dowhy import CausalModel

然后,我们必须加载乳腺癌数据集并将其转换为 DataFrame:

breast_cancer = load_breast_cancer()data = pd.DataFrame(breast_cancer.data,
    columns=breast_cancer.feature_names)
data['target'] = breast_cancer.target

现在,我们需要将治疗变量的数值(平均半径)转换为二进制,因为倾向得分匹配仅接受二进制治疗变量:

data['mean radius'] = data['mean radius'].gt(data[    'mean radius'].values.mean()).astype(int)
data=data.astype({'mean radius':'bool'}, copy=False)

我们还需要列出共同原因列表,在这种情况下,我们将所有其他数据集中的属性视为共同原因:

common_causes_list = data.columns.values.tolist()common_causes_list.remove('mean radius')
common_causes_list.remove('target')

现在,我们可以通过指定数据、治疗、结果变量和共同原因来使用 dowhyCausalModel() 构建模型。CausalModel() 对象帮助我们估计 treatment 变量(平均半径)对结果变量(target)的因果效应:

model = CausalModel(    data=data,
    treatment='mean radius',
    outcome='target',
    common_causes=common_causes_list
)

现在,我们可以估计指定治疗变量(平均半径)对目标变量的因果效应。请注意,我们在这里使用的倾向得分匹配仅适用于离散治疗变量:

identified_est = model.identify_effect()estimate = model.estimate_effect(identified_est,
    method_name='backdoor.propensity_score_matching')

estimate 值为 -0.279,这意味着当平均半径作为治疗变量时,结果发生的概率降低了约 28%。这个倾向得分是在给定一组观察到的协变量条件下接受治疗的条件概率。后门调整控制了混杂变量,这些变量与治疗和结果变量都有关联。

我们还可以使用 refute_estimate() 函数来评估我们对因果变量及其对结果数据驱动估计效应的假设的有效性。例如,我们可以使用 'placebo_treatment_refuter' 方法,该方法将指定的治疗变量替换为一个独立的随机变量。如果我们对治疗和结果之间因果关系的假设是正确的,那么新的估计值将接近零。以下是使用 'placebo_treatment_refuter' 检查我们假设有效性的代码:

refute_results = model.refute_estimate(identified_estimand,    estimate, method_name='placebo_treatment_refuter',
    placebo_type='permute', num_simulations=40)

这导致新的效应为 0.0014,这保证了我们假设的有效性。然而,此命令的另一个输出 p-值估计为 0.48,这显示了统计置信水平。

refute_estimate() 函数返回的低 p-值并不意味着治疗变量不具有因果性。低 p-值表明估计的因果效应对特定假设的敏感性。反驳结果的显著性并不暗示治疗变量和结果变量之间不存在因果关系。

使用 bnlearn 通过贝叶斯网络进行因果推理

在 Python 和 R 编程语言中都存在的用于贝叶斯网络学习和推理的库之一是 bnlearn。我们可以使用这个库学习给定数据集的贝叶斯网络,然后使用学习到的图来推断因果关系。

为了使用 bnlearn 进行练习,我们必须安装并导入这个库,然后加载作为其一部分存在的 Sprinkler 数据集:

import bnlearn as bndf = bn.import_example('sprinkler')

接下来,我们必须拟合一个 structure_learning() 模型来生成贝叶斯网络或 DAG:

DAG = bn.structure_learning.fit(df)

然后,我们必须定义节点属性并可视化 DAG,如下所示:

# Set some colors to the edges and nodesnode_properties = bn.get_node_properties(DAG)
node_properties['Sprinkler']['node_color']='#00FFFF'
node_properties['Wet_Grass']['node_color']='#FF0000'
node_properties['Rain']['node_color']='#A9A9A9'
node_properties['Cloudy']['node_color']='#A9A9A9'
# Plotting the Bayesian Network
bn.plot(DAG,
    node_properties=node_properties,
    interactive=True,
    params_interactive={'notebook':True,
        'cdn_resources': 'remote'})

这导致了图 15.4中显示的网络。如图所示,'Sprinkler'可能是多云天气和湿草的因果变量。湿草可能由雨和水龙头引起。但存在量化这些依赖性的功能:

图 15.4 – 使用 bnlearn 为 Sprinkler 数据集学习的 DAG

图 15.4 – 使用 bnlearn 学习 Sprinkler 数据集的 DAG

你可以使用independence_test()如下测试变量的依赖性:

bn.independence_test(DAG, df, test = 'chi_square',    prune = True)

表 15.2包括了前一个命令的输出总结,清楚地显示了 DAG 中成对变量依赖性的显著性:

来源 目标 p-value(来自 chi_square test) chi-square
多云 1.080606e-87 394.061629
水龙头 湿草 1.196919e-23 100.478455
水龙头 多云 8.383708e-53 233.906474
湿草 3.886511e-64 285.901702

表 15.2 – 使用 bnlearn.independence_test()在 Sprinkler 数据集上的总结

你也可以使用bnlearn.parameter_learning.fit()如下学习 CPD:

model_mle = bn.parameter_learning.fit(DAG, df,    methodtype='maximumlikelihood')
# Printing the learned Conditional Probability Distribution (CPDs)
bn.print_CPD(model_mle)

图 15.5显示了CloudyRainSprinkler变量的 CPD。这些 CPD 与已识别的 DAG(图 15.4)相结合,不仅提供了识别变量之间潜在因果关系所需的信息,而且还可以对这些关系进行定量评估:

图 15.5 – bnlearn 为 Sprinkler 数据集识别的 CPD 示例

图 15.5 – bnlearn 为 Sprinkler 数据集识别的 CPD 示例

在本章中,你练习了使用因果建模,但这个主题还有更多内容。这是机器学习中最重要的话题之一,你将受益于对这个主题的更多了解。

摘要

在本章中,你学习了相关关系和因果关系之间的区别,因果建模的重要性,以及贝叶斯网络等因果推理技术。随后,我们通过 Python 实践帮助你开始在你的项目中使用因果建模和推理,以便你能识别出数据集中变量之间更可靠的关系,并设计出可靠的模型。

在下一章中,你将学习在构建可靠的机器学习模型时,如何保护隐私并确保安全,同时最大化使用私有和专有数据的益处。

问题

  1. 在监督学习模型中,你能否有一个与输出高度相关但不是因果的特征?

  2. 实验设计和观察研究在因果推理中有何区别?

  3. 使用工具变量进行因果推理有哪些要求?

  4. 贝叶斯网络中的关系是否必然可以被认为是因果的?

参考文献

  • Schölkopf, Bernhard. 机器学习的因果性. 概率与因果推理:朱迪亚·佩尔的工作. 2022. 765-804.

  • Kaddour, Jean, 等人. 因果机器学习:综述与开放问题. arXiv 预印本 arXiv:2206.15475 (2022).

  • Pearl, Judea. 贝叶斯 网络. (2011).

  • Richens, Jonathan G., Ciarán M. Lee, 和 Saurabh Johri. 利用因果机器学习提高医疗诊断的准确性. Nature communications 11.1 (2020): 3923.

  • Prosperi, Mattia, 等人. 在可操作的医疗保健中,机器学习的因果推理和反事实预测. Nature Machine Intelligence 2.7 (2020): 369-375.

  • Sanchez, Pedro, 等人. 因果机器学习在医疗和精准医学中的应用. Royal Society Open Science 9.8 (2022): 220638.

第十六章:机器学习中的安全和隐私

在我们生活的数字世界中,保护用户数据和个人隐私,以及确保他们的数字信息和资产的安全,在技术发展中具有重要意义。这对于建立在机器学习模型之上的技术也不例外。我们在第三章中简要讨论了这一主题,即调试 向负责任的人工智能迈进。在本章中,我们将为您提供更多详细信息,以帮助您开始学习更多关于在开发机器学习模型和技术中保护隐私和确保安全的知识。

在本章中,我们将涵盖以下主题:

  • 加密技术及其在机器学习中的应用

  • 同态加密

  • 差分隐私

  • 联邦学习

到本章结束时,您将了解在机器学习环境中保护隐私和确保安全所面临的挑战,并学习一些应对这些挑战的技术。

技术要求

以下要求适用于本章,因为它们将帮助您更好地理解概念,能够在项目中使用它们,并使用提供的代码进行实践:

  • Python 库要求:

    • numpy >= 1.22.4

    • matplotlib >= 3.7.1

    • tenseal >= 0.3.14

    • pycryptodome = 3.18.0

    • pycryptodomex = 3.18.0

如果您是 Mac 用户并且遇到tenseal安装问题,您可以按照github.com/OpenMined/TenSEAL/issues中解释的方法直接克隆其存储库进行安装。

本章的代码文件可在 GitHub 上找到,网址为github.com/PacktPublishing/Debugging-Machine-Learning-Models-with-Python/tree/main/Chapter16

加密技术及其在机器学习中的应用

我们可以使用不同的加密技术来加密原始数据、用于模型训练和推理的处理数据、模型参数或需要保护的其他敏感信息。有一个术语叫做密钥,通常是一串数字或字母,在大多数加密技术中都很重要。密钥通过加密算法进行编码和解码数据的处理。有几种加密技术,其中一些如下(Bhanot 等人,2015;Dibas 等人,2021):

  • 高级加密标准AES):AES 是保护数据的最强大的加密算法之一。AES 接受不同的密钥大小:128 位、192 位或 256 位。

  • Rivest-Shamir-Adleman (RSA) 安全性:RSA 是安全性最高的加密算法之一,是一种广泛用于安全数据传输的公钥加密算法。

  • 三重数据加密标准 (DES):三重 DES 是一种使用 56 位密钥加密数据块的加密方法。

  • Blowfish:Blowfish 是一种对称密钥加密技术,用作 DES 加密算法的替代方案。Blowfish 速度快,在数据加密方面非常有效。它将数据,例如字符串和消息,分割成 64 位的块,并分别加密它们。

  • Twofish:Twofish 是 Blowfish 的后继者,是一种对称加密算法,可以解密 128 位数据块。

接下来,我们将使用 Python 来练习使用 AES 进行数据加密,这是最常见的加密技术之一。

在 Python 中实现 AES 加密

在这里,我们希望通过 Python 练习 AES 在数据加密中的应用。这次练习的唯一目的是帮助您更好地了解如何从 Python 中受益于数据加密,以及如何在 Python 中加密和解密数据是多么容易,以及您如何从中受益以保护数据隐私并在机器学习环境中确保安全性。

我们首先导入Cryptodome.Cipher.AES()进行加密(加密)和解密,以及Cryptodome.Random.get_random_bytes()用于密钥生成:

from Cryptodome.Cipher import AESfrom Cryptodome.Random import get_random_bytes

我们可以使用 AES 加密文本,如我的名字是 Ali或其他类型的信息。在这里,我们希望用它来加密所谓的 SMILES,这是一种表示化学化合物的序列。例如,CC(=O)NC1=CC=C(C=C1)O代表一种著名的药物叫做对乙酰氨基酚(来源:pubchem.ncbi.nlm.nih.gov/compound/Acetaminophen):

data = b'CC(=O)NC1=CC=C(C=C1)O'key_random = get_random_bytes(16)
cipher_molecule = AES.new(key_random, AES.MODE_EAX)
ciphertext, tag = cipher_molecule.encrypt_and_digest(data)
out_encrypt = open("molecule_enc.bin", "wb")
[out_encrypt.write(x) for x in (cipher_molecule.nonce, tag,
    ciphertext) ]
out_encrypt.close()

如果我们有密钥,我们可以解密并安全地加载数据:

in_encrypt = open("molecule_enc.bin", "rb")nonce, tag, ciphertext = [in_encrypt.read(x) for x in (16,
    16, -1) ]
in_encrypt.close()
# let's assume that the key is somehow available again
decipher_molecule = AES.new(key_random, AES.MODE_EAX,nonce)
data = decipher_molecule.decrypt_and_verify(ciphertext,tag)
print('Decrypted data: {}'.format(data))

这将重新生成我们加密的序列CC(=O)NC1=CC=C(C=C1)O

在这个例子中,AES 帮助我们加密有关药物的信息,这对于制药和生物技术公司在开发新药的过程中可能很重要。然而,您可以通过 Python 使用 AES 加密其他类型的数据。

接下来,我们将讨论另一种称为同态加密的技术。

同态加密

另一种允许我们在加密数据上执行计算的技术称为同态加密。这种能力在机器学习环境中非常有用,例如,在不需要解密的情况下使用模型对加密数据进行推理。然而,实现全同态加密可能很复杂,计算成本高,内存效率低(Armknecht 等人,2015;Gentry 等人,2009;Yousuf 等人,2020)。

有几个 Python 库可以帮助我们练习同态加密方案,如下所示:

让我们看看使用 TenSEAL 进行同态加密的一个简单示例。

我们首先导入TenSEAL库,并使用tenseal.context()生成一个context对象。context对象生成并存储加密计算所需的必要密钥:

import tenseal as tscontext = ts.context(ts.SCHEME_TYPE.BFV,
    poly_modulus_degree=4096, plain_modulus=1032193)

poly_modulus_degree参数用于确定多项式模的次数,这是一个具有整数系数的多项式。plain_modulus参数用于指定编码明文消息到可以加密和同态处理的多项式的模数。如果plain_modulus参数太小,消息可能会溢出并导致错误的结果,而如果太大,密文可能会变得过大并减慢同态操作的速度。

在之前的代码中,我们使用了Brakerski-Fan-VercauterenBFV)方案。BFV 是一种支持整数算术的同态加密方案。它包括不同的多项式时间算法,用于生成公钥和私钥、加密明文消息、解密密文消息、对两个密文进行加法和减法以及乘法运算。密文是加密信息,在没有适当的密钥或执行加密或解密算法的情况下,我们或计算机无法读取,或者算法无法解密它。

现在我们定义一个包含三个数字的列表:

plain_list = [50, 60, 70]

然后我们使用之前定义的context对象实现解密:

encrypted_list = ts.bfv_vector(context, plain_list)

然后我们首先实现以下操作过程:

add_result = encrypted_vector + [1, 2, 3]

生成的add_result列表将是[51, 62, 73],这是原始值列表[50, 60, 70][1, 2, 3]逐元素求和的结果。

虽然同态加密或其他加密技术看起来非常安全,但它仍然需要访问密钥,例如在云服务器上,这可能导致安全担忧。有解决方案可以降低这种风险,例如使用密钥管理服务,如 AWS KMS(aws.amazon.com/kms/) 或多因素认证MFA)。

接下来,我们将简要回顾差分隐私DP)作为一种保护单个数据点隐私的技术。

差分隐私

差分隐私的目标是确保删除或添加单个数据点不会影响建模的结果。例如,通过向正态分布添加随机噪声,它试图使单个数据点的特征变得模糊。如果可以访问大量数据点,基于大数定律(Dekking et al., 2005),可以消除噪声在学习中的影响。为了更好地理解这个概念,我们想要生成一个随机数字列表,并从正态分布中向它们添加噪声,以帮助您更好地理解为什么这种技术有效。在这个过程中,我们还将定义一些广泛使用的技术术语。

我们首先定义一个名为gaussian_add_noise()的函数,用于向查询值列表添加高斯噪声:

def gaussian_add_noise(query_result: float,    sensitivity: float, epsilon: float):
        std_dev = sensitivity / epsilon
        noise = np.random.normal(loc=0.0, scale=std_dev)
        noisy_result = query_result + noise
    return noisy_result

在前面的函数中,我们使用了sensitivityepsilon作为函数的输入参数,其意义可以简化如下:

  • sensitivity: DP 机制中所需的噪声水平由灵敏度参数确定。灵敏度告诉我们查询结果对变化的影响。较大的sensitivity值将导致更好的隐私,但响应的准确性较低。

  • epsilon (隐私预算): 隐私预算是一个参数,它限制了噪声数据和查询数据之间偏差的程度。较小的epsilon值将导致更好的隐私,但响应的准确性较低。

然后,我们使用一个简单的for循环来生成符合正态分布的随机值作为查询值,然后使用定义的gaussian_mechanism()函数向它们添加噪声:

query_list = []noisy_list = []
for iter in range(1000):
    # Generating a random value between 0 and 100
    query_val = np.random.rand()*100
    noisy_val = gaussian_add_noise(query_val, sensitivity,
        epsilon_budget)
    query_list.append(query_val)
    noisy_list.append(noisy_val)
print('Mean of the original distribution:
    {}'.format(np.mean(query_list)))
print('Mean of the nosiy distribution:
    {}'.format(np.mean(noisy_list)))
print('Standard deviation of the original distribution:
    {}'.format(np.std(query_list)))
print('Standard deviation of the nosiy distribution:
    {}'.format(np.std(noisy_list)))

结果的噪声和查询分布非常相似,平均值为 0.78 和 0.82,标准差分别为 99.32 和 99.67。图 16**.1显示了这两组值的散点图。你可以通过调整sensitivityepsilon参数来改变查询值和噪声值之间的距离。

图 16.1 – 添加噪声前后变量值的比较

图 16.1 – 添加噪声前后变量值的比较

此外,还有一些 Python 库可以帮助你实现 DP,如下所示:

本章最后要介绍的主题被称为联邦学习,它帮助我们超越中央存储系统的隐私保护。

联邦学习

联邦学习FL)依赖于去中心化学习、数据分析和解推理的想法,因此允许用户数据保持在单个设备或本地数据库中(Kaissis 等人,2020 年;Yang 等人,2019 年)。通过 FL,我们可以从无法存储在集中式数据存储系统中的本地设备和用户数据中受益,以训练和改进我们的机器学习模型。如图图 16**.2所示,本地设备或用户可以提供本地数据以更新全局模型和我们正在训练的模型,并改善中心服务器。然后全局模型得到更新和改进,并向本地用户和设备提供更新的推理。

图 16.2 – 使用本地数据更新模型并反馈全局模型到本地设备和用户的示意图

图 16.2 – 使用本地数据更新模型并反馈全局模型到本地设备和用户的示意图

在实现联邦学习(FL)时,你可以从以下 Python 库中受益:

然而,在实践中使用联邦学习的挑战超出了编程或基础设施设计。尽管这是存储用户数据本地化的绝佳替代方案,但在不同应用中从联邦学习中受益仍存在伦理、法律和商业挑战。医疗保健是一个联邦学习可以带来最大利益的领域,但法律和伦理挑战仍然存在,这减缓了其实际应用的实施。许多机构、医院、制药公司和政府机构仍然要求用于建模的数据,即使通过联邦学习,也需要经过通常用于完全访问数据的伦理、法律和商业审批流程。然而,随着联邦学习算法和相关基础设施的改进,机构、医院和机构也将提出解决方案,以利用这项技术。

除了本章中关于数据隐私和安全的讨论,你还可以在第三章 向负责任的人工智能调试中回顾机器学习设置中的重要攻击主题。你还可以检查其他资源,如 Papernot 等人 2018 年发表的优秀文章《Sok:机器学习中的安全和隐私》以了解更多关于这些重要主题的信息。

摘要

在本章中,你了解了一些帮助你保护隐私和确保安全的重要概念和技术,包括数据加密技术、同态加密、差分隐私和联邦学习。你学习了同态加密如何提供与传统数据加密技术相比不同类型操作和机器学习推理的可能性。你还学习了如何通过向数据添加噪声、在差分隐私中,或者与去中心化数据合作并省略传输原始数据的需求,如在联邦学习中,来确保数据隐私。你还在 Python 中实践了一些这些技术。这些知识可以成为你进一步了解这些概念并从你的机器学习项目中受益的起点。

在下一章中,你将了解将人类反馈整合到机器学习建模中的重要性以及有助于这一主题的技术。

问题

  1. 解释三种可以帮助你在机器学习项目中使用的加密技术。

  2. 在机器学习设置中,同态加密有什么好处?

  3. 什么是差分隐私?

  4. 使用联邦学习或差分隐私时有哪些非技术挑战?

参考文献

  • Shafahi, Ali, 等人. “免费对抗训练!.” 神经信息处理系统进展 32 (2019).

  • Gaur, Shailendra Singh, Hemanpreet Singh Kalsi, 和 Shivani Gautam. “加密算法的比较研究和分析:RSA, DES, AES, BLOWFISH, 3-DES, 和 TWOFISH。”

  • Bhanot, Rajdeep, 和 Rahul Hans. “各种加密算法的综述和比较分析.” 国际安全与应用杂志 9.4 (2015): 289-306.

  • Dibas, Hasan, 和 Khair Eddin Sabri. “对称算法(AES, 3DES, Blowfish 和 Twofish)的全面性能实证研究.” 信息技术国际会议 (ICIT). IEEE (2021).

  • Armknecht, Frederik, 等人. “完全同态加密指南.” 密码学电子档案 (2015).

  • Gentry, Craig. 全同态加密方案. 斯坦福大学,2009.

  • Yousuf, Hana, 等人. “关于全同态加密方案及其应用的系统综述.” 智能系统和智能应用最新进展 (2020): 537-551.

  • Yang, Qiang, 等人. “联邦机器学习:概念和应用.” ACM 智能系统与技术交易 (TIST) 10.2 (2019): 1-19.

  • Abadi, Martin, 等人. “具有差分隐私的深度学习.” 2016 年 ACM SIGSAC 计算机和通信安全会议论文集 (2016).

  • Dekking, Frederik Michel, 等人. 现代概率与统计导论:理解为什么以及如何. 第 488 卷. 伦敦:Springer (2005).

  • Kaissis, Georgios A., 等人. “医学图像中的安全、隐私保护和联邦机器学习.” 自然机器智能 2.6 (2020): 305-311.

  • Yang, Qiang, 等人. “联邦机器学习:概念和应用.” ACM 智能系统与技术交易 (TIST) 10.2 (2019): 1-19.

  • Papernot, Nicolas, 等人. “机器学习中的安全和隐私(Sok).” IEEE 欧洲安全与隐私研讨会 (EuroS&P). IEEE (2018).

第十七章:人类在环机器学习

机器学习建模不仅仅是机器学习开发者和工程师坐在电脑后面构建和修改机器学习生命周期的组件。融入领域专家或非专家群体的反馈对于将更可靠和应用导向的模型投入生产至关重要。这个被称为人类在环机器学习的概念,是关于在不同生命周期阶段利用人类智能和专业知识来进一步提高我们模型的性能和可靠性。

本章将涵盖以下主题:

  • 机器学习生命周期中的人类

  • 人类在环建模

到本章结束时,你将了解在机器学习建模项目中融入人类智能的好处和挑战。

机器学习生命周期中的人类

开发和改进机器学习生命周期的不同组件,以将可靠且高性能的模型投入生产是一个需要专家和非专家人类反馈的协作努力(图 17.1):

图 17.1 – 机器学习生命周期中的人类

图 17.1 – 机器学习生命周期中的人类

例如,放射科医生可以在标注放射学图像时提供帮助,而大多数具有良好视觉能力的人可以轻松地标注猫和狗的图像。但融入人类反馈并不限于生命周期开始时的数据标注。

我们可以利用人类智能和专业知识来改进生命周期中的数据准备、特征工程和表示学习等方面,以及模型训练和测试,最终实现模型的部署和监控。在这些每个阶段,人类反馈都可以被动或主动地融入,这使我们能够将更好的模型投入生产。

被动的人类在环是关于收集专家和非专家的反馈和信息,并在下一次修改相应的机器学习建模系统的组件时从中受益。在这个过程中,反馈和额外信息有助于识别改进生命周期组件的机会,以及识别数据漂移和概念漂移,以将更好的模型投入生产。在主动的人类在环机器学习中,基础设施和生命周期的一个或所有组件需要以某种方式设计,以便额外的人类在环信息和数据可以主动和持续地融入,以改进数据分析建模。

首先,我们将回顾专家反馈收集以及如何有效地从中受益来改进我们的模型。

专家反馈收集

在一个或多个机器学习模型之上构建一项技术的最终目标是提供一个工具,供用户、专家或非专家用于特定目标,例如医疗图像分类、股价预测、信用风险评估以及亚马逊等平台上的产品推荐。例如,我们可以收集数据标注或生产阶段后期用于漂移检测的反馈。然后我们可以利用这些反馈来改进我们的模型。然而,这种反馈可能超出了仅仅数据标注或识别数据和概念漂移的目的。

我们可以将专家反馈纳入四个主要目的:数据生成和标注、数据过滤、模型选择和模型监控。专家反馈收集对于标注和监控通常与非专家数据收集相似,除了在某些应用中,专业知识是必需的,例如在分类放射学图像时。

对于模型选择,我们可以利用专家反馈,不仅依赖于我们用于模型性能评估的性能指标,而且根据错误预测检测红旗,或者根据模型的解释性信息进行选择,例如,如果对预测贡献最大的特征具有最低的相关性。

我们还可以从专家的反馈中受益,以监控我们的模型。正如在第十一章“避免和检测数据及概念漂移”中讨论的那样,漂移检测对于确保我们的模型在生产中的可靠性至关重要。在许多应用中,我们模型的使用者可能是特定领域的专家,例如医疗保健和药物发现。在这种情况下,我们需要确保我们持续收集他们的反馈,并利用这些反馈来检测和消除模型中的漂移。

从我们机器学习模型的使用者那里收集专家反馈不应仅限于获取他们“好”与“坏”的二进制响应。我们需要提供关于我们的模型及其预测的足够信息,并要求专家提供以下反馈:

  • 提供足够的信息:当我们向我们的模型专家用户请求反馈时,我们需要提供足够的信息以获得更好和更相关的反馈。例如,除了我们的模型在测试和生产中的性能,或针对一组特定数据点的错误和正确预测之外,我们还可以提供关于模型如何针对这些数据点做出决策的解释性信息。这类信息可以帮助用户提供更好的反馈,从而帮助我们改进模型。

  • 不要要求翻译:我们模型的许多用户可能对统计和机器学习建模知识有限。因此,要求他们将自己的意见和想法转换为技术术语将限制有效的反馈收集。你需要提供足够的信息,并要求他们的反馈,进行双向对话,将他们的见解转化为改进模型的行动项。

  • 设计自动化反馈收集:虽然最好不要求翻译,如前所述,你可以通过使用清晰详细的问题和适当的基础设施设计来收集反馈并将其纳入模型,从而朝着更自动化的反馈收集迈进。例如,你可以使用机器学习可解释性,并询问模型用于预测特定数据点集输出的最有信息量的特征是否与任务相关。

环境中的人类建模有其自身的挑战,例如,当需要第三方公司监控模型和管道时,或者在分享来自合作者和商业伙伴的数据时存在特定的法律障碍,我们团队和组织中的其他人。在设计时,我们需要牢记这些挑战,以便我们可以在机器学习生命周期中从人类反馈中受益。

尽管我们可以在机器学习生命周期的不同阶段收集反馈来改进我们的模型,但还有一些技术,如主动学习(我们将在下一节中介绍),可以帮助我们以更低的成本将更好的模型投入生产。

环境中的人类建模

尽管更多高质量的标注数据点更有价值,但标注数据的成本可能非常高,尤其是在需要领域专业知识的情况下。主动学习是一种帮助我们以较低成本生成和标注数据以改进模型性能的策略。在主动学习环境中,我们旨在利用有限的数据量,迭代选择新的数据点进行标注,或识别其连续值,以达到更高的性能(Wu et al., 2022; Ren et al., 2021; Burbidge et al., 2007)。模型会查询需要由专家或非专家标注的新实例,或者通过任何计算或实验技术识别其标签或连续值。然而,与随机选择实例不同,有新技术用于选择新实例,以帮助我们以更少的实例和迭代次数实现更好的模型(表 17.1)。每种技术都有其优缺点。例如,不确定性采样简单,但如果实例预测输出的不确定性没有高度相关于模型错误,其对性能的影响可能有限:

数据中心 模型中心
不确定性采样选择具有最大不确定性的实例(在推理中),这些实例可能是分类问题中离决策边界最近的实例 预期模型变化选择已知标签的实例,对当前模型的影响最大
密度加权不确定性采样选择不仅具有最高不确定性,而且代表了许多其他依赖特征空间数据密度的数据点的实例 误差减少估计选择已知标签的实例,将导致最大的未来误差减少
委员会查询多个模型(委员会)被训练,并选择预测中分歧最大的实例 方差减少选择已知标签的实例,将导致模型对其参数的不确定性减少最多

表 17.1 – 每个步骤中用于实例选择的主动学习技术

在本章中,我们专注于介绍人类在循环背后的概念和技术。然而,有一些 Python 库,如modAL (modal-python.readthedocs.io/en/latest/),可以帮助你在项目中实现一些这些技术,将人类反馈引入你的机器学习生命周期。

摘要

在本章中,你了解了一些人类在循环机器学习中的重要概念,这些概念可以帮助你在与专家或非专家的团队合作中更好地建立协作,以便你可以将他们的反馈纳入你的机器学习建模项目中。

这是本书的最后一章。我希望你学到了足够多的关于提高机器学习模型和构建更好模型的不同方法,以便你可以开始你的旅程,成为这个领域的专家。

问题

  1. 人类在循环机器学习是否仅限于数据标注和标签化?

  2. 在主动学习过程的每个步骤中,不确定性采样和密度加权不确定性采样在实例选择上的区别是什么?

参考文献

  • Amershi, Saleema, 等人。赋予人民力量:人类在交互式机器学习中的作用. 人工智能杂志 35.4(2014):105-120。

  • Wu, Xingjiao, 等人。人机交互机器学习综述. 未来一代计算机系统 135(2022):364-381。

  • Ren, Pengzhen, 等人。深度主动学习综述. ACM 计算调查(CSUR)54.9(2021):1-40。

  • Burbidge, Robert, Jem J. Rowland, 和 Ross D. King. 基于委员会查询的回归主动学习. 智能数据工程与自动学习-IDEAL 2007:第 8 届国际会议,英国伯明翰,2007 年 12 月 16-19 日。第 8 卷。Springer Berlin Heidelberg,2007。

  • Cai, Wenbin, Ya Zhang, 和 Jun Zhou. 最大化回归中主动学习的预期模型变化. 2013 年第 13 届国际数据挖掘会议。IEEE,2013。

  • Roy, Nicholas, 和 Andrew McCallum. 通过蒙特卡洛估计误差减少实现最优主动学习. ICML,威廉斯塔特 2(2001):441-448。

  • Donmez, Pinar, Jaime G. Carbonell, 和 Paul N. Bennett. 双重策略主动学习. 机器学习:ECML 2007:第 18 届欧洲机器学习会议,波兰华沙,2007 年 9 月 17-21 日。第 18 卷。Springer Berlin Heidelberg,2007。

评估

第一章 – 超越代码调试

  1. 是的——这里有一个在本章中提供的例子:

    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_countnum_list = [1, 2, 5, 8, 9]print(odd_counter(num_list))
    
  2. 这里是它们的定义:

    • AttributeError:当对一个未为其定义属性的对象使用属性时,会引发此类错误。例如,isnull未在列表中定义。因此,my_list. isnull()会导致AttributeError

    • NameError:当你尝试调用未在代码中定义的函数、类或其他名称和模块时,会引发此错误。例如,如果你没有在代码中定义neural_network类,但在代码中调用它为neural_network(),你将得到NameError消息。

  3. 高维性使得特征空间更稀疏,可能会降低模型在分类设置中识别可推广决策边界的信心。

  4. 当你在 Python 中遇到错误信息时,它通常会提供必要的信息来帮助你找到问题。这些信息创建了一个类似报告的消息,关于代码中发生错误的行,错误类型,以及导致这些错误的功能或类调用。这种类似报告的消息在 Python 中称为回溯

  5. 增量编程:为每个小组件编写代码,然后测试它,例如使用 PyTest 编写测试代码,这可以帮助你避免每个编写的功能或类的问题。它还帮助你确保作为另一个模块输入的模块的输出是兼容的。

日志记录:当你用 Python 开发函数和类时,你可以从日志记录中受益,将信息、错误和其他类型的消息记录下来,以帮助你识别在收到错误信息时的潜在问题来源。

  1. 例如,如果你使用专家,如放射科医生,为癌症诊断标注医学图像,那么对图像标签的信心可能不同。这些信心可以在建模阶段考虑,无论是在数据收集过程中,例如通过要求更多专家标注相同的图像,还是在建模过程中,例如通过根据标签的信心为每个图像分配权重。你的数据特征也可能具有不同的质量。例如,你可能具有高度稀疏的特征,这些特征在数据点中大部分为零值,或者具有不同置信水平的特征。例如,如果你使用卷尺来捕捉物体尺寸(如骰子)之间的毫米差异,而不是使用相同的卷尺来捕捉更大物体(如家具)之间的差异,那么测量特征将具有较低的置信度。

  2. 你可以通过控制模型复杂度来控制欠拟合和过拟合。

  3. 是的,这是可能的。用于训练和测试机器学习模型的数据可能会过时。例如,服装市场趋势的变化可能会使服装推荐模型的预测变得不可靠。

  4. 仅通过调整模型超参数,你无法开发出最佳可能的模型。同样,通过增加数据和超参数的质量和数量,同时保持模型超参数不变,你也不能达到最佳性能。因此,数据和超参数是相辅相成的。

第二章 – 机器学习生命周期

  1. 清洗过程的例子包括在数据中填充缺失值和移除异常值。

  2. One-hot 编码为每个分类特征类别生成一个新特征。标签编码保持相同的特征,只是将每个类别替换为分配给该类别的数字。

  3. 检测异常值的最简单方法是通过使用变量值分布的分位数。超出上下界限的数据点被认为是异常值。下限和上限可以计算为Q1-a.IQRQ3+a.IQR,其中a可以是一个介于 1.5 和 3 之间的实数值。a的常用值也是默认用于绘制箱线图的 1.5,但使用更高的值会使异常值识别过程不那么严格,并允许检测到更少的数据点作为异常值。

  4. 如果你想在医院医生的电脑上部署一个模型,以便临床医生直接使用,你需要考虑所有必要的困难和规划,以设置适当的生产环境以及所有软件依赖项。你还需要确保他们的本地系统满足必要的硬件要求。如果你想在银行系统中部署在聊天机器人背后的模型,这些就不是需要考虑的因素。

第三章 – 负责任的人工智能调试

  1. 数据收集偏差:收集的数据可能存在偏差,例如性别偏差,如在亚马逊的应聘者排序示例中,种族偏差,如在 COMPAS 中,社会经济偏差,如在住院示例中,或其他类型的偏差。

抽样偏差:数据偏差的另一个来源可能是数据收集阶段的生命周期中数据点的抽样或人群的抽样过程。例如,在抽样学生填写调查时,我们的抽样过程可能偏向于女孩或男孩,富裕或贫穷的学生家庭,或高年级与低年级学生。

  1. 完美知识白盒攻击:攻击者了解系统的所有信息。

零知识黑盒攻击:攻击者对系统本身没有任何了解,但通过在生产中对模型进行预测来收集信息。

  1. 加密过程将信息、数据或算法转换成新的(即,加密)形式。如果个人有权访问加密密钥(即,解密过程中必要的密码式密钥),则加密数据可以被解密(即,成为可读或机器可理解的)。这样,在没有加密密钥的情况下获取数据和算法将几乎不可能或非常困难。

  2. 差分隐私试图确保删除或添加单个数据点不会影响建模的结果。它试图从数据点的组中学习模式。例如,通过添加来自正态分布的随机噪声,它试图使单个数据点的特征变得模糊。如果可以访问大量数据点,则可以根据大数定律消除学习中的噪声效应。

联邦学习依赖于分散学习、数据分析和推理的想法,从而允许用户的数据保留在单个设备或本地数据库中。

  1. 透明度有助于在用户中建立信任,并可能增加信任并使用您模型的用户数量。

第四章 – 在机器学习模型中检测性能和效率问题

  1. 在初步诊断测试中,随着更精确的后续测试,我们希望确保我们不会失去任何我们正在测试的疾病患者。因此,我们需要旨在减少假阴性,同时尝试减少假阳性。因此,我们可以旨在最大化召回率,同时控制精确度特异性

  2. 在这种情况下,您需要确保您有 精确度 来控制风险并建议良好的投资机会。这可能会导致较低的 召回率,这是可以接受的,因为不良投资可能导致个人投资者资本的重大损失。在这里,我们不考虑投资风险管理的细节,而希望对如何选择良好的性能指标有一个高层次的理解。如果您是这个领域的专家,考虑您的知识,并选择一个满足您已知要求的良好性能指标。

  3. ROC-AUC 是一个汇总指标。具有相同 ROC-AUC 的两个模型可能对个别数据点的预测不同。

  4. MCC 关注预测标签,而 log-loss 关注测试数据点的预测概率。因此,较低的 log-loss 并不一定导致较低的 MCC

  5. 不一定。R2 并不考虑数据维度(即特征、输入或独立变量的数量)。具有更多特征的模型可能导致更高的 R2,但这不一定是一个更好的模型。

  6. 这取决于用于评估模型泛化能力的性能指标和测试数据。我们需要为生产中的目标使用正确的性能指标,并使用一组数据点进行模型测试,这组数据点将更能反映生产中未见数据。

第五章 – 提高机器学习模型的性能

  1. 增加更多的训练数据点可以帮助减少方差,而增加更多的特征可以帮助减少偏差。然而,通过添加新的数据点来减少方差,或者新特征是否有助于减少方差,都没有保证。

优化过程中的权重分配:在训练机器学习模型时,您可以根据类别标签的置信度,为每个数据点分配一个权重。

集成学习:如果您考虑每个数据点的质量或置信度分数的分布,那么您可以使用分布的每个部分的数据点构建不同的模型,然后结合这些模型的预测,例如使用它们的加权平均。

迁移学习:您可以在具有不同标签置信度级别的大数据集上训练一个模型(参见 图 5**.3),排除非常低置信度的数据,然后在数据集非常高置信度的部分进行微调。

  1. 通过提高识别决策边界的置信度,在分类设置中,少数类是稀疏的。

  2. 如果我们使用 Borderline-SMOTE,新合成的数据点将接近多数类数据点,这有助于识别一个可推广的决策边界。

在 DSMOTE 中,DBSCAN 用于将少数类的数据点划分为三个组:核心样本、边界样本和噪声(即异常)样本,然后仅使用核心和边界样本进行过采样。

  1. 如本章所述,搜索所有可能的超参数组合并不是必要的。

  2. 是的,L1 正则化可以消除特征对正则化过程的贡献。

  3. 是的,这是可能的。

第六章 – 机器学习建模中的可解释性和可解释性

  1. 可解释性可以帮助提高性能,例如通过减少模型对特征值小变化的敏感性,提高模型训练中的数据效率,试图帮助模型进行适当的推理,以及避免虚假相关性。

  2. 局部可解释性帮助我们理解模型在特征空间中接近数据点的行为。尽管这些模型满足局部保真度标准,但被识别为局部重要的特征可能不是全局重要的,反之亦然。

全局可解释性技术试图超越局部可解释性,并为模型提供全局解释。

  1. 线性模型虽然可解释,但通常性能较低。相反,我们可以从更复杂、性能更高的模型中受益,并使用可解释性技术来理解模型是如何得出其预测的。

  2. 是的,确实如此。可解释性技术可以帮助我们了解哪些模型是预测一组数据点的主要贡献者。

  3. SHAP 可以确定每个特征对模型预测的贡献。由于特征在确定分类模型的决策边界并最终影响模型预测方面是协同工作的,SHAP 尝试首先识别每个特征的边际贡献,然后提供 Shapely 值作为每个特征与整个特征集合作预测模型的估计。

LIME 是 SHAP 的替代品,用于 局部可解释性,它以模型无关的方式解释任何分类器或回归器的预测,通过在局部近似一个可解释的模型。

  1. 反事实示例或解释有助于我们确定在实例中需要改变什么才能改变分类模型的输出。这些反事实可以帮助在许多应用中识别可操作的路径,例如金融、零售、营销、招聘和医疗保健。例如,我们可以用它们来建议银行客户如何改变其贷款申请被拒绝的情况。

  2. 如在使用多样化的反事实解释(DiCE)生成反事实部分所述,并非所有反事实都符合每个特征的定义和意义。例如,如果我们想建议一个 30 岁的人改变他们的结果,建议他们等到 50 多岁才这样做并不是一个有效且可行的建议。同样,建议将hours_per_week的工作时间从 38 小时增加到>80 小时也是不可行的。

第七章 – 减少偏差和实现公平性

  1. 不一定。我们的模型中可能有敏感属性的代理,但不是我们的模型中。

  2. 薪酬和收入(在某些国家),职业,犯罪指控的历史。

  3. 不一定。根据人口比例来满足公平性并不一定会导致模型根据均衡机会来表现公平。

  4. 人口比例是一个群体公平性定义,旨在确保模型的预测不依赖于给定的敏感属性,如种族或性别。

均衡机会在给定预测与给定敏感属性组独立且与真实输出无关时得到满足。

  1. 不一定。例如,模型预测的主要贡献者中可能有 'sex' 的特征代理。

  2. 我们可以使用可解释性技术来识别模型中的潜在偏差,然后计划改进它们以实现公平性。例如,我们可以识别男性和女性群体之间的公平性问题。

第八章 – 使用测试驱动开发控制风险

  1. pytest是一个简单易用的 Python 库,你可以用它来设计单元测试。设计的测试可以简单地用来测试代码的变化,并在整个开发过程和未来代码的变化中控制潜在错误的危险。

  2. 在数据分析和机器学习建模的编程中,我们需要使用来自不同变量或数据对象的数据,这些数据可能来自本地机器或云端的文件,也可能来自数据库的查询,或者来自我们的测试的 URL。固定装置(Fixtures)通过消除在测试中重复相同代码的需要来帮助我们完成这些过程。将固定装置函数附加到测试上会在每个测试运行之前运行它,并将数据返回给测试。我们可以使用pytest文档页面上的示例(docs.pytest.org/en/7.1.x/how-to/fixtures.html)。

  3. 差分测试试图检查同一输入上的软件的两个版本,即基版和测试版,并比较输出。这个过程有助于确定输出是否相同,并识别意外的差异。在差分测试中,基版已经过验证,被认为是批准的版本,而测试版需要与基版进行比较,以产生正确的输出。

  4. mlflow是一个广泛使用的机器学习实验跟踪库,我们可以在 Python 中使用它。跟踪我们的机器学习实验将帮助我们减少无效结论的风险和选择不可靠模型的风险。机器学习中的实验跟踪是关于保存有关实验的信息,例如已使用的数据、测试性能和用于性能评估的指标,以及用于建模的算法和超参数。

第九章 – 生产测试和调试

  1. 数据漂移:如果生产中特征或独立变量的特征和意义与建模阶段不同,就会发生数据漂移。想象一下,你使用第三方工具为人们的健康或财务状况生成分数。该工具背后的算法可能会随时间变化,并且当你的模型在生产中使用时,其范围和意义将不会相同。如果你没有相应地更新你的模型,那么你的模型将不会按预期工作,因为特征值的含义在用于训练的数据和部署后的用户数据之间将不同。

概念漂移:概念漂移是指输出变量定义的任何变化。例如,由于概念漂移,训练数据和生产数据之间的实际决策边界可能不同,这意味着训练中的努力可能导致生产中的决策边界远离现实。

  1. 模型断言可以帮助你早期发现问题,例如输入数据漂移或其他可能影响模型性能的意外行为。我们可以将模型断言视为在模型训练、验证甚至部署期间进行检查的一组规则,以确保模型的预测满足预定义的条件。模型断言可以从许多方面帮助我们,例如检测和诊断模型或输入数据的问题,使我们能够在它们影响模型性能之前解决这些问题。

  2. 这里有一些集成测试组件的例子:

    • 测试数据管道:我们需要评估在模型训练之前的数据预处理组件,如数据整理,在训练和部署阶段之间是否一致。

    • 测试 API:如果我们的机器学习模型通过 API 公开,我们可以测试 API 端点以确保它们正确处理请求和响应。

    • 测试模型部署:我们可以使用集成测试来评估模型的部署过程,无论它是作为独立服务、容器内还是嵌入在应用程序中部署。这个过程帮助我们确保部署环境提供必要的资源,例如 CPU、内存和存储,并且如果需要,模型可以更新。

    • 测试与其他组件的交互:我们需要验证我们的机器学习模型能够与数据库、用户界面或第三方服务无缝工作。这可能包括测试模型预测在应用程序中如何存储、显示或使用。

    • 测试端到端功能:我们可以使用模拟真实场景和用户交互的端到端测试来验证模型的预测在整体应用程序的上下文中是准确、可靠和有用的。

  3. IaC 和配置管理工具,如 Chef、Puppet 和 Ansible,可以用于自动化软件和硬件基础设施的部署、配置和管理。这些工具可以帮助我们确保在不同环境中的一致性和可靠性。首先,在描述这些 IaC 工具对我们有什么用之前,我们需要定义两个重要的术语:客户端和服务器:

    • Chef (www.chef.io/products/chef-infrastructure-management): Chef 是一款开源配置管理工具,它依赖于客户端-服务器模型,其中 Chef 服务器存储期望的配置,Chef 客户端将其应用于节点。

    • Puppet (www.puppet.com/): Puppet 是另一款开源配置管理工具,它可以在客户端-服务器模式下工作,也可以作为独立应用程序运行。Puppet 通过定期从 Puppet 主服务器拉取配置来强制执行节点上的期望配置。

    • Ansible (www.ansible.com/): Ansible 是一款开源且易于使用的配置管理、编排和自动化工具,它采用无代理架构来与节点通信并应用配置。

第十章 – 版本控制和可重复的机器学习建模

  1. MLflow: 我们在之前的章节中介绍了 MLflow 用于实验跟踪和模型监控,但您也可以用它进行数据版本化 (mlflow.org/)。

DVC: 一个用于管理数据、代码和机器学习模型的开源版本控制系统。它旨在处理大型数据集,并与 Git 集成 (dvc.org/)。

Pachyderm: 一个提供机器学习工作流程可重复性、溯源和可扩展性的数据版本化平台 (www.pachyderm.com/)。

  1. No. 同一个数据文件的多个版本可以以相同的名称存储,并在需要时恢复和检索。

  2. 在将数据分割为训练集和测试集或模型初始化时简单更改随机状态可能会导致训练集和评估集的参数值和性能不同。

第十一章 – 避免和检测数据漂移和概念漂移

  1. 幅度:我们可能会遇到数据分布中不同的幅度差异,导致我们的机器学习模型发生漂移。数据分布中的小变化可能难以检测,而大变化可能更明显。

频率:漂移可能发生在不同的频率上。

  1. Kolmogorov-Smirnov 测试可用于数据漂移检测。

第十二章 – 深度学习超越 ML 调试

  1. 是的,在正向传递中,已经计算出的参数被用于输出生成;然后,实际输出和预测输出之间的差异被用于反向传播过程中更新权重。

  2. 在随机梯度下降中,每个迭代使用一个数据点来优化和更新模型权重,而在小批量梯度下降中,使用数据点的小批量(小子集)。

  3. 每个批次或小批量是训练集中用于计算损失和更新模型权重的数据点的小子集。在每个 epoch 中,多个批次被迭代以覆盖所有训练数据。

  4. Sigmoid 和 softmax 函数通常用于输出层,将输出神经元的分数转换为介于零和一之间的值,用于分类模型。这被称为预测的概率。

第十三章 – 高级深度学习技术

  1. CNNs 可用于图像分类或分割——例如,用于放射学图像以识别恶性肿瘤(肿瘤区域)。另一方面,GNNs 可用于社交和生物网络。

  2. 是的,确实如此。

  3. 这可能会导致更多的错误。

  4. 为了应对这一挑战,在称为填充的过程中,在单词序列或句子中的每个单词的标记 ID 之前或之后使用一个常见的 ID,例如 0。

  5. 我们为 CNNs 和 GNNs 构建的类别具有相似的代码结构。

  6. 边缘特征有助于你包含一些关键信息,具体取决于应用。例如,在化学中,你可以将化学键的类型作为边缘特征,而节点可以是图中的原子。

第十四章 – 机器学习最新进展简介

  1. 基于 Transformer 的文本生成,VAEs 和 GANs。

  2. LLaMA 和 GPT 的不同版本。

  3. 生成器,它可以是用于生成所需数据类型的神经网络架构,例如图像,生成图像的目的是欺骗判别器将其识别为真实数据。判别器学习在识别生成数据与真实数据相比保持良好。

  4. 你可以通过具体说明问题和指定数据生成对象来提高你的提示。

  5. 在 RLHF 中,奖励是根据人类反馈计算的,无论是专家还是非专家,这取决于问题。但奖励并不是像考虑语言模型等问题的复杂性那样预定义的数学公式。人类提供的反馈导致模型逐步改进。

  6. 对比学习的想法是学习表示,使得相似数据点彼此更接近,而不同数据点则更远。

第十五章 – 相关性 versus 因果性

  1. 是的。在监督学习中,你可以有与输出高度相关的特征,而这些特征并不是因果的。

  2. 建立因果关系的一种方法是通过实验,如在实验设计中,我们测量因果特征变化对目标变量的影响。然而,这种实验研究可能并不总是可行或道德的。在观察性研究中,我们使用观察数据,而不是控制实验,并试图通过控制混杂变量来识别因果关系。

  3. 工具变量用于因果分析,以克服观察性研究中常见的共同问题,即治疗变量和结果变量由其他变量(或混杂因素)共同决定,而这些变量未包含在模型中。这种方法从识别一个与治疗变量相关且与结果变量不相关(除了通过其对治疗变量的影响)的工具变量开始。

  4. 从特征到结果的方向并不一定意味着因果性。但贝叶斯网络可以用来估计变量对结果的影响,同时控制混杂变量。

第十六章 – 机器学习中的安全和隐私

  1. 高级加密标准AES):AES 是保护数据的最强大的加密算法之一。AES 接受不同的密钥大小:128 位、192 位或 256 位。

三重数据加密标准DES):三重 DES 是一种使用 56 位密钥加密数据块的加密方法。

Blowfish:Blowfish 是一种对称密钥加密技术,用作 DES 加密算法的替代方案。Blowfish 加密速度快,对数据加密非常有效。它将数据,例如字符串和消息,分成 64 位的块,并分别加密它们。

  1. 我们可以在不需要解密的情况下使用模型对加密数据进行推理。

  2. 差分隐私DP)的目标是确保删除或添加单个数据点不会影响建模的结果。例如,通过向正态分布添加随机噪声,它试图使单个数据点的特征变得模糊不清。

  3. 在实践中使用联邦学习(FL)或差分隐私(DP)的挑战不仅限于编程或基础设施设计。尽管存储用户数据本地有这样一个很好的替代方案,但在从不同应用中受益于 FL 时,仍然存在伦理、法律和商业挑战。

第十七章 – 循环中的人类机器学习

  1. 不,例如,您可以通过主动学习将人类专家引入循环。

  2. 不确定性采样中,数据点仅根据推理中的不确定性被选中。但在密度加权不确定性采样中,实例不仅基于它们最高的不确定性被选中,还要代表依赖于特征空间中数据密度的许多其他数据点。

posted @ 2025-09-03 10:19  绝不原创的飞龙  阅读(8)  评论(0)    收藏  举报