原文:
annas-archive.org/md5/f3dcd9d910b2341a99f14dab450d4e9b译者:飞龙
前言
近年来,机器学习获得了极大的普及。GPT-3 和 4 等大型语言模型的引入加速了这一领域的发展。这些大型语言模型变得如此强大,以至于在本地计算机上训练它们几乎是不可能的。然而,这根本不是必要的。这些语言模型提供了创建新工具的能力,而无需训练它们,因为它们可以通过上下文窗口和提示来引导。
在这本书中,我的目标是展示如何训练、评估和测试机器学习模型——无论是在小型原型还是在完整软件产品中。这本书的主要目标是弥合机器学习在软件工程中的理论知识与实践应用之间的差距。它旨在使你具备理解、有效实施并在你的职业追求中创新 AI 和机器学习技术的技能。
将机器学习融入软件工程的旅程既令人兴奋又充满挑战。当我们深入研究机器学习基础设施的复杂性时,这本书充当了一本全面的指南,帮助软件工程师穿越复杂性和最佳实践,这些对于软件工程师至关重要。它旨在弥合机器学习的理论方面与在现实场景实施中面临的实际挑战之间的差距。
我们首先探讨机器学习的根本概念,为那些新进入该领域的人提供坚实的基础。随着我们的进展,焦点转向基础设施——任何成功机器学习项目的支柱。从数据收集和处理到模型训练和部署,每一步都至关重要,需要仔细考虑和规划。
书中很大一部分内容致力于最佳实践。这些实践不仅仅是理论上的指导方针,而是源自我的研究团队在这个领域工作中发现的真实经验和案例研究。这些最佳实践为处理常见陷阱和确保机器学习系统的可扩展性、可靠性和效率提供了无价的见解。
此外,我们深入探讨了数据和机器学习算法的伦理问题。我们研究了机器学习伦理背后的理论,更深入地探讨了数据和模型许可,最后,探讨了可以量化机器学习中数据和模型偏见的实际框架。
这本书不仅仅是一本技术指南;它是一次穿越软件工程中机器学习演变景观的旅程。无论你是渴望学习的初学者,还是寻求提升技能的资深专业人士,这本书旨在成为一项宝贵的资源,为机器学习这个充满活力且不断变化的领域提供清晰的方向。
本书面向的对象
本书精心制作,旨在为寻求在他们的领域中应用人工智能和机器学习实际应用的软件工程师、计算机科学家和程序员提供帮助。内容旨在传授关于与机器学习模型一起工作的基础知识,通过程序员的视角和系统架构师的视角来审视。
本书假设读者对编程原则有所了解,但并不要求在数学或统计学方面有专业知识。这种做法确保了软件开发领域的更广泛的专业人士和爱好者能够接触本书。对于那些没有 Python 先前经验的读者,本书需要掌握语言的基本理解。然而,材料结构旨在帮助快速全面地掌握 Python 的基本知识。相反,对于那些精通 Python 但尚未在专业编程方面有所经验的人,本书作为过渡到以 AI 和 ML 应用为重点的软件工程领域的宝贵资源。
本书涵盖的内容
第一章,机器学习与传统软件的比较,探讨了这两种类型的软件系统最适用的情况。我们了解了程序员用来创建这两种类型软件的软件开发过程,以及我们学习了经典的四种机器学习软件类型 – 基于规则的、监督的、无监督的和强化学习。最后,我们还了解了数据在传统和机器学习软件中的不同作用。
第二章,机器学习系统要素,回顾了专业机器学习系统的每个要素。我们首先了解哪些要素是重要的以及为什么重要。然后,我们探讨如何创建这些要素以及如何通过将它们组合成一个单一的机器学习系统(所谓的机器学习管道)来工作。
第三章,软件系统中的数据 – 文本、图像、代码和特征,介绍了三种数据类型 – 图像、文本和格式化文本(程序源代码)。我们探讨了每种类型的数据如何在机器学习中使用,它们应该如何标注以及用于什么目的。引入这三种类型的数据为我们提供了探索不同标注这些数据来源方式的可能性。
第四章,数据获取、数据质量和噪声,深入探讨了与数据质量相关的话题。我们介绍了一个评估数据质量的理论模型,并提供了实施它的方法和工具。我们还探讨了机器学习中的噪声概念以及如何通过不同的标记化方法来减少它。
第五章,量化并改进数据属性,深入探讨了数据的属性以及如何改进它们。与上一章相比,我们专注于特征向量而不是原始数据。特征向量已经是数据的一种转换;因此,我们可以改变诸如噪声之类的属性,甚至改变数据被感知的方式。我们专注于文本的处理,这是当今许多机器学习算法的一个重要部分。我们首先理解如何使用简单的算法,如词袋模型,将数据转换为特征向量,以便我们可以在特征向量上工作。
第六章,机器学习系统中数据处理,深入探讨了数据和算法相互交织的方式。我们经常用通用术语谈论数据,但在这章中,我们解释了机器学习系统中需要哪种类型的数据。我们解释了所有类型的数据都是以数值形式使用的——要么作为特征向量,要么作为更复杂的特征矩阵。然后,我们将解释将非结构化数据(例如文本)转换为结构化数据的必要性。本章将为深入探讨每种类型的数据奠定基础,这是下一章几章的内容。
第七章,数值和图像数据的特征工程,专注于数值和图像数据的特征工程过程。我们首先回顾了典型的方法,例如主成分分析(PCA),我们之前曾用它进行可视化。然后,我们转向更高级的方法,如t-Student 分布随机网络嵌入(t-SNE)和独立成分分析(ICA)。最终,我们使用自动编码器作为数值和图像数据的降维技术。
第八章,自然语言数据的特征工程,探讨了使变换器(GPT)技术如此强大的第一步——从自然语言数据中提取特征。自然语言是软件工程中的一种特殊数据源。随着 GitHub Copilot 和 ChatGPT 的引入,变得明显的是,用于软件工程任务的机器学习和人工智能工具不再是科幻。
第九章,机器学习系统类型 – 基于特征和原始数据(深度学习),探讨了不同类型的机器学习系统。我们从经典的机器学习模型,如随机森林开始,然后转向卷积和 GPT 模型,这些被称为深度学习模型。它们的名称来源于它们使用原始数据作为输入,并且模型的第一层包括特征提取层。它们也被设计为随着输入数据通过这些模型而逐步学习更抽象的特征。本章展示了这些模型类型,并从经典机器学习进展到生成式 AI 模型。
第十章,经典机器学习系统和神经网络的训练与评估,对训练和评估过程进行了更深入的探讨。我们首先介绍不同算法背后的基本理论,然后展示它们是如何被训练的。我们从经典的机器学习模型开始,以决策树为例,然后逐渐转向深度学习,探索密集神经网络和一些更高级的网络类型。
第十一章,高级机器学习算法的训练与评估 – GPT 和自编码器,探讨了基于 GPT 和双向编码器表示转换器(BERT)的生成式 AI 模型的工作原理。这些模型被设计为根据它们训练的模式生成新的数据。我们还探讨了自编码器的概念,其中我们训练一个自编码器根据之前训练的数据生成新的图像。
第十二章,设计机器学习管道及其测试,描述了 MLOps 的主要目标是弥合数据科学和运维团队之间的差距,促进协作,并确保机器学习项目能够有效地和可靠地在规模上部署。MLOps 有助于自动化和优化整个机器学习生命周期,从模型开发到部署和维护,从而提高生产中机器学习系统的效率和效果。在本章中,我们学习如何在实践中设计和操作机器学习系统。本章展示了如何将管道转化为一个软件系统,重点关注测试 ML 管道及其在 Hugging Face 的部署。
第十三章,设计和实现大规模、健壮的机器学习软件,解释了如何将机器学习模型与用 Gradio 编写的图形用户界面和数据库中的存储集成。我们使用两个机器学习管道的例子 – 来自我们之前章节的预测缺陷的模型示例和一个生成式 AI 模型,用于根据自然语言提示创建图片。
第十四章,数据获取与管理中的伦理问题,首先探讨了几个不道德的系统示例,这些系统表现出偏见,例如惩罚某些少数群体的信用评级系统。我们还解释了使用开源数据和揭露受试者身份的问题。然而,本章的核心是对数据管理和软件系统伦理框架的解释和讨论,包括 IEEE 和 ACM 的行为准则。
第十五章,机器学习系统中的伦理问题,重点关注机器学习系统中的偏见。我们首先探讨了偏见来源,并简要讨论了这些来源。然后,我们探讨了如何发现偏见,如何最小化它们,最后,如何向我们的系统用户传达潜在的偏见。
第十六章,在生态系统中集成机器学习系统,解释了如何将机器学习系统打包成 Web 服务,使我们能够以非常灵活的方式将它们集成到工作流程中。我们不必编译或使用动态链接库,而可以部署通过 HTTP 协议使用 JSON 协议进行通信的机器学习组件。实际上,我们已经在使用 OpenAI 托管的 GPT-3 模型时看到了如何使用该协议。在本章中,我们探讨了创建自己的带有预训练机器学习模型的 Docker 容器,部署它,并将其与其他组件集成的可能性。
第十七章,总结与下一步行动,回顾了所有最佳实践,并按章节进行了总结。此外,我们还探讨了机器学习和人工智能的未来可能给软件工程带来的影响。
为了充分利用本书
在这本书中,我们使用 Python 和 PyTorch,因此您需要在您的系统上安装这两个软件。我在 Windows 和 Linux 上使用过它们,但它们也可以在 Google Colab 或 GitHub Codespaces(两者都已测试过)这样的云环境中使用**。
| 本书涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| Python 3.11 | Windows,Ubuntu,Debian Linux 或 Windows Subsystem for Linux (WSL) |
| PyTorch 2.1 | Windows, Ubuntu 或 Debian Linux |
如果您正在使用这本书的数字版,我们建议您亲自输入代码或从书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码的复制和粘贴相关的任何潜在错误。
下载示例代码文件
您可以从 GitHub 下载本书的示例代码文件:github.com/PacktPublishing/Machine-Learning-Infrastructure-and-Best-Practices-for-Software-Engineers。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有来自我们丰富的书籍和视频目录的其他代码包可供选择,请访问github.com/PacktPublishing/。查看它们!
使用的约定
本书使用了多种文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“模型本身是在 model = LinearRegression()这一行上面创建的。”
代码块设置如下:
def fibRec(n):
if n < 2:
return n
else:
return fibRec(n-1) + fibRec(n-2)
任何命令行输入或输出都按以下方式编写:
>python app.py
最佳实践
看起来像这样。
联系我们
我们始终欢迎读者的反馈。
总体反馈:如果您对本书的任何方面有任何疑问,请通过电子邮件发送至 customercare@packtpub.com,并在邮件主题中提及书名。
勘误表:尽管我们已经尽最大努力确保内容的准确性,但错误仍然可能发生。如果您在本书中发现错误,我们将不胜感激,如果您能向我们报告,请访问www.packtpub.com/support/errata并填写表格。
盗版:如果您在互联网上以任何形式发现我们作品的非法副本,我们将不胜感激,如果您能提供位置地址或网站名称,请通过 copyright@packt.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为本书做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了《软件工程师的机器学习基础设施和最佳实践》,我们非常乐意听到您的想法!请点击此处直接进入本书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区都至关重要,并将帮助我们确保提供高质量的内容。
下载本书的免费 PDF 副本
感谢您购买本书!
您喜欢随时随地阅读,但又无法携带您的印刷书籍到处走吗?
您的电子书购买是否与您选择的设备不兼容?
别担心,现在,随着每本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何地点、任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠远不止于此,您还可以获得独家折扣、时事通讯以及每天收件箱中的精彩免费内容。
按照以下简单步骤获取优惠:
- 扫描二维码或访问以下链接
packt.link/free-ebook/978-1-83763-406-4
-
提交您的购买证明
-
就这样!我们将直接将您的免费 PDF 和其他福利发送到您的邮箱
第一部分:软件工程中的机器学习景观
传统上,机器学习(ML)被认为是软件工程中的一个细分领域。没有大型软件系统在生产中使用统计学习。这种情况在 2010 年代发生了变化,当时推荐系统开始利用大量数据 – 例如,推荐电影、书籍或音乐。随着转换技术的兴起,这种情况发生了变化。众所周知的产品,如 ChatGPT,普及了这些技术,并表明它们不再是细分产品,而是已经进入了主流软件产品和服务的领域。软件工程需要跟上步伐,我们需要知道如何基于这些现代机器学习模型创建软件。本书的第一部分,我们将探讨机器学习如何改变软件开发,以及我们需要如何适应这些变化。
本部分包含以下章节:
-
第一章, 机器学习与传统软件的比较
-
第二章, 机器学习系统的要素
-
第三章, 软件系统中的数据 – 文本、图像、代码和特性
-
第四章, 数据采集、数据质量和噪声
-
第五章, 量化并改进数据特性
第一章:与传统软件相比,机器学习
机器学习软件是一种特殊的软件,它能在数据中找到模式,从中学习,甚至在新数据上重新创建这些模式。因此,开发机器学习软件的重点是找到合适的数据,将其与适当的算法匹配,并评估其性能。相反,传统软件的开发是以算法为前提的。基于软件需求,程序员开发解决特定任务的算法,然后对其进行测试。数据是次要的,尽管并非完全不重要。这两种类型的软件可以在同一软件系统中共存,但程序员必须确保它们之间的兼容性。
在本章中,我们将探讨这两种类型软件系统最合适的领域。我们将了解程序员用来创建这两种类型软件的软件开发过程。我们还将了解四种经典的机器学习软件类型——基于规则的学习、监督学习、无监督学习和强化学习。最后,我们将了解数据在传统和机器学习软件中的不同作用——在传统软件中作为预编程算法的输入,在机器学习软件中作为训练模型的输入。
本章介绍的最佳实践提供了何时选择每种类型软件以及如何评估这些类型软件优缺点的实用指导。通过探讨几个现代实例,我们将了解如何以机器学习算法为中心创建整个软件系统。
本章将涵盖以下主要内容:
-
机器学习不是传统软件
-
概率和软件——它们搭配得怎么样?
-
测试和验证——相同但不同
机器学习不是传统软件
尽管机器学习和人工智能自 20 世纪 50 年代由艾伦·图灵引入以来就已经存在,但它们只有在第一个 MYCIN 系统出现以及我们对机器学习系统的理解随时间变化后才开始流行。直到 2010 年代,我们才开始以今天(2023 年)的方式感知、设计和开发机器学习。在我看来,有两个关键时刻塑造了我们今天所看到的机器学习格局。
第一个关键时刻是在 2000 年代末和 2010 年代初对大数据的关注。随着智能手机的引入,公司开始收集和处理越来越多的数据,这些数据大多关于我们在网上的行为。其中一家完美掌握这一技术的公司是谷歌,它收集了我们搜索、在线行为以及使用谷歌操作系统 Android 的数据。随着收集到的数据量(及其速度/速度)的增加,其价值和对真实性的需求——即五个“V”——也随之增加。这五个“V”——量、速度、价值、真实性和多样性——需要一种新的数据处理方法。传统的基于关系型数据库(SQL)的方法在处理高速数据流时变得过于缓慢,这导致了 map-reduce 算法、分布式数据库和内存数据库的出现。传统的基于关系模式的处理方法对于数据的多样性来说过于约束,这导致了非 SQL 数据库的出现,这些数据库存储文档。
第二个关键时刻是现代机器学习算法——深度学习的兴起。深度学习算法被设计来处理非结构化数据,如文本、图像或音乐(与表格和矩阵形式的结构化数据相比)。传统的机器学习算法,如回归、决策树或随机森林,需要表格形式的数据。每一行是一个数据点,每一列是它的一个特征——一个属性。传统的模型被设计来处理相对较小的数据集。另一方面,深度学习算法可以处理大型数据集,并利用大型神经网络和其复杂的架构找到数据中的更复杂模式。
机器学习有时被称为统计学习,因为它基于统计方法。统计方法计算数据的属性(如平均值、标准偏差和系数),从而在数据中找到模式。机器学习的核心特征是它使用数据来寻找模式,从中学习,然后在新的数据上重复这些模式。我们称这种学习模式的方式为训练,将这些模式重复为推理,或者用机器学习的语言来说,就是预测。使用机器学习软件的主要好处来自于我们不需要设计算法——我们专注于要解决的问题以及我们用来解决问题的数据。“图 1.1”展示了这样一个机器学习软件流程图的实现示例。
首先,我们从库中导入一个通用的机器学习模型。这个通用模型具有它特有的所有元素,但它并未训练用于解决任何任务。这类模型的例子是一个决策树模型,它被设计用来学习数据中的依赖关系,以决策(或数据拆分)的形式呈现,之后它将使用这些信息来处理新数据。为了使这个模型变得有些用处,我们需要对其进行训练。为此,我们需要数据,我们称之为训练数据。
第二步,我们在新的数据上评估训练好的模型,我们称之为测试数据。评估过程使用训练好的模型,并应用它来检查其推理是否正确。更精确地说,它检查推理正确的程度。训练数据与测试数据具有相同的格式,但这两个数据集的内容是不同的。不应该有任何数据点同时存在于两者中。
在第三步,我们将模型作为软件系统的一部分使用。我们开发其他非机器学习组件,并将它们连接到训练好的模型。整个软件系统通常包括数据采集组件、实时验证组件、数据清洗组件、用户界面和业务逻辑组件。所有这些组件,包括机器学习模型,都为最终用户提供特定的功能。一旦软件系统开发完成,就需要对其进行测试,这时输入数据就派上用场。输入数据是最终用户输入到系统中的数据,例如通过填写表格。输入数据被设计成既包含输入又包含预期的输出——以测试软件系统是否正确工作。
最后一步是部署整个系统。部署可能非常不同,但大多数现代机器学习系统被组织成两部分——用于非机器学习组件的机载/边缘算法和用户界面,以及用于机器学习推理的离岸/云算法。尽管有可能在目标设备上部署系统的所有部分(包括机器学习和非机器学习组件),但复杂的机器学习模型需要大量的计算能力以实现良好的性能和无缝的用户体验。原则很简单——更多的数据/更复杂的数据意味着更复杂的模型,这意味着需要更多的计算能力:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_01_1.jpg
图 1.1 – 机器学习软件开发典型流程
如图 1**.1所示,机器学习软件的一个关键元素是模型,这是经过特定数据训练的通用机器学习模型之一,例如神经网络。这类模型用于进行预测和推理。在大多数系统中,这种组件——模型——通常是用 Python 进行原型设计和开发的。
模型针对不同的数据集进行训练,因此,机器学习软件的核心特征是其对那个数据集的依赖性。这样的模型的一个例子是视觉系统,我们训练一个机器学习算法,如卷积神经网络(CNN),以对猫和狗的图像进行分类。
由于模型是在特定数据集上训练的,因此在进行推理时,它们在类似数据集上的表现最佳。例如,如果我们训练一个模型在 160 x 160 像素的灰度图像中识别猫和狗,该模型可以识别这样的图像中的猫和狗。然而,如果需要在该模型中识别彩色图像中的猫和狗而不是灰度图像,该模型的性能将非常差(如果有的话!)——分类的准确性将很低(接近 0)。
另一方面,当我们开发和设计传统软件系统时,我们并不那么依赖数据,如图1.2所示。这张图概述了传统、非机器学习软件的开发过程。尽管它被描绘为流程,但通常是一个迭代过程,其中步骤 1到步骤 3是循环进行的,每次循环都以向产品中添加新功能结束。
第一步是开发软件系统。这包括开发所有组件——用户界面、业务逻辑(处理)、数据处理和通信。除非软件工程师为了测试目的创建数据,否则这一步不涉及太多数据。
第二步是系统测试,我们使用输入数据来验证软件系统。本质上,这一步几乎与测试机器学习软件相同。输入数据与预期的结果数据相结合,这使得软件测试人员能够评估软件是否正确工作。
第三步是部署软件。部署可以通过多种方式进行。然而,如果我们考虑与机器学习软件在功能上相似的传统的软件,它通常更简单。它通常不需要在云上进行部署,就像机器学习模型一样:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_01_2.jpg
图 1.2 - 传统软件开发典型流程
传统软件与基于机器学习的软件之间的主要区别在于,我们需要设计、开发和测试传统软件的所有元素。在基于机器学习的软件中,我们使用一个包含所有必要元素的空模型,并使用数据对其进行训练。我们不需要从头开始开发机器学习模型的各个组件。
传统软件的主要部分之一是算法,这是软件工程师从头开始根据需求或用户故事开发的。算法通常被编写为一系列按编程语言实现的步骤。自然地,所有算法都使用数据来操作它,但它们与机器学习系统不同。它们基于软件工程师的设计——如果 x,则 y或类似的东西。
我们通常将这些传统算法视为确定性的、可解释的和可追溯的。这意味着软件工程师的设计决策在算法中得到了记录,并且算法可以在之后进行分析。它们是确定性的,因为它们是基于规则编写的;没有从数据中训练或从数据中识别模式。它们是可解释的,因为它们是由程序员设计的,程序中的每一行都有预定义的含义。最后,它们是可追溯的,因为我们能够调试这些程序的每一步。
然而,也存在一个缺点——软件工程师需要彻底考虑所有边界情况,并且非常了解问题。软件工程师使用的这些数据只是为了支持他们分析算法,而不是用于训练算法。
一个可以使用机器学习算法和传统算法实现的系统示例是读取护照信息的系统。而不是使用机器学习进行图像识别,软件使用护照中的特定标记(通常是<<<字符序列)来标记行首或表示姓氏的字符序列的开始。这些标记可以使用基于规则的光学字符识别(OCR)算法快速识别,无需深度学习或 CNN。
因此,我想介绍第一条最佳实践。
最佳实践 #1
当你的问题集中在数据而不是算法时,使用机器学习算法。
在选择合适的技术时,我们需要了解它是否基于经典方法,其中算法的设计是重点,或者我们是否需要专注于处理数据和在其中寻找模式。通常,以下指导方针是有益的。
如果问题需要处理大量原始格式的数据,则使用机器学习方法。这类系统的例子包括对话机器人、图像识别工具、文本处理工具,甚至是预测系统。
然而,如果问题需要可追溯性和控制,则使用传统方法。这类系统的例子包括汽车中的控制软件(如防抱死制动系统、发动机控制等)和嵌入式系统。
如果问题需要根据现有数据生成新数据,即所谓的数据处理过程,请使用机器学习方法。这类系统的例子包括图像处理程序(DALL-E)、文本生成程序、深度伪造程序和源代码生成程序(GitHub Copilot)。
如果问题需要随着时间的推移进行适应和优化,请使用机器学习软件。这类系统的例子包括电网优化软件、计算机游戏中的非玩家角色行为组件、播放列表推荐系统,甚至现代汽车的 GPS 导航系统。
然而,如果问题需要稳定性和可追溯性,请使用传统方法。这类系统的例子包括医学中的诊断和推荐系统、汽车、飞机和火车中的安全关键系统,以及基础设施控制和监控系统。
监督学习、无监督学习和强化学习——这只是开始
现在是提到机器学习领域非常庞大的时候了,它被组织成三个主要领域——监督学习、无监督学习和强化学习。每个领域都有数百种不同的算法。例如,监督学习领域有超过 1,000 种算法,所有这些都可以通过元启发式算法如 AutoML 自动选择:
-
监督学习:这是一组基于标注数据进行训练的算法。这些算法中使用的数据需要有一个目标或标签。标签用于告诉算法寻找哪种模式。例如,这样的标签可以是监督学习模型需要识别的每张图像的猫或狗。历史上,监督学习算法是最古老的,因为它们直接来自如线性回归和多项式回归等统计方法。现代算法更先进,包括深度学习神经网络等方法,可以识别 3D 图像中的对象并相应地进行分割。该领域最先进的算法是深度学习和多模态模型,它们可以同时处理文本和图像。
监督学习算法的一个子集是自监督模型,它们通常基于转换器架构。这些模型不需要数据中的标签,但它们使用数据本身作为标签。这些算法中最突出的例子是自然语言的翻译模型和图像或文本的生成模型。这些算法通过在原始文本中遮蔽单词并预测它们来训练。对于生成模型,这些算法通过遮蔽其输出的部分来预测它进行训练。
-
无监督学习:这是一组应用于在数据中寻找模式而不需要任何标签的模型。这些模型未经训练,但它们使用输入数据的统计特性来寻找模式。这类算法的例子包括聚类算法和语义映射算法。这些算法的输入数据没有标签,应用这些算法的目标是根据相似性在数据集中找到结构;然后,这些结构可以用来为这些数据添加标签。当我们得到购买产品、阅读书籍、听音乐或看电影的建议时,我们每天都会遇到这些算法。
-
强化学习:这是一组应用于数据以解决特定任务的模型。对于这些模型,我们除了需要提供数据外,还需要提供目标。这被称为奖励函数,它是一个定义我们何时达到目标的表达式。模型基于这个适应函数进行训练。这类模型的例子包括玩围棋、象棋或星际争霸的算法。这些算法也被用来解决困难的编程问题(AlphaCode)或优化能耗。
因此,让我介绍第二个最佳实践。
最佳实践 #2
在你开始开发机器学习系统之前,要进行尽职调查,并确定使用正确的算法组。
由于这些模型组各自具有不同的特性,解决不同的问题,并需要不同的数据,选择错误算法可能会造成高昂的代价。监督模型在解决与预测和分类相关的问题上非常出色。在这个领域中最强大的模型可以在选定的领域与人类竞争——例如,GitHub Copilot 可以创建可以以人类编写的程序。无监督模型在需要将实体分组并做出推荐时非常强大。最后,当我们需要连续优化并且每次数据或环境变化时都需要重新训练模型时,强化学习模型是最好的选择。
尽管所有这些模型都基于统计学习,但它们都是更大系统的一部分,以便使它们变得有用。因此,我们需要了解机器学习的这种概率和统计性质如何与传统、数字软件产品相结合。
传统机器学习和机器学习软件的例子
为了说明传统软件和机器学习软件之间的区别,让我们使用这两种范式实现相同的程序。我们将使用传统方法实现一个计算斐波那契序列的程序,这在计算机科学课程中我们已经见过无数次。然后,我们将使用机器学习模型——确切地说是一个模型——来实现相同的程序,即逻辑回归。
这里展示了传统的实现方式。它基于一个递归函数和一个测试它的循环:
# a recursive function to calculate the fibonacci number
# this is a standard solution that is used in almost all
# of computer science examples
def fibRec(n):
if n < 2:
return n
else:
return fibRec(n-1) + fibRec(n-2)
# a short loop that uses the above function
for i in range(23):
print(fibRec(i))
实现非常简单,基于算法——在我们的案例中,是fibRec函数。它很简单,但有其局限性。第一个是其递归实现,这会消耗资源。尽管它可以写成迭代形式,但它仍然存在第二个问题——它专注于计算而不是数据。
现在,让我们看看机器学习实现的步骤。我将通过将其分为两部分来解释——数据准备和模型训练/推理:
#predicting fibonacci with linear regression
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
# training data for the algorithm
# the first two columns are the numbers and the third column is the result
dfTrain = pd.DataFrame([[1, 1, 2],
[2, 1, 3],
[3, 2, 5],
[5, 3, 8],
[8, 5, 13]
])
# now, let's make some predictions
# we start the sequence as a list with the first two numbers
lstSequence = [0,1]
# we add the names of the columns to make it look better
dfTrain.columns = ['first number','second number','result']
在机器学习软件的情况下,我们准备数据来训练算法。在我们的案例中,这是dfTrain DataFrame。它是一个表格,包含机器学习算法需要找到模式的数字。
请注意,我们准备了两个数据集——dfTrain,其中包含训练算法所需的数字,以及lstSequence,这是我们稍后将找到的斐波那契数列。
现在,让我们开始训练算法:
# algorithm to train
# here, we use linear regression
model = LinearRegression()
# now, the actual process of training the model
model.fit(dfTrain[['first number', 'second number']],
dfTrain['result'])
# printing the score of the model, i.e. how good the model is when trained
print(model.score(dfTrain[['first number', 'second number']], dfTrain['result']))
整个代码片段的魔力在于加粗的代码——model.fit方法的调用。此方法基于我们为其准备的数据训练逻辑回归模型。模型本身是在上一行的model = LinearRegression()行中创建的。
现在,我们可以使用以下代码片段进行推理或创建新的斐波那契数:
# and loop through the newly predicted numbers
for k in range(23):
# the line below is where the magic happens
# it takes two numbers from the list
# formats them to an array
# and makes the prediction
# since the model returns a float,
# we need to convert it to it
intFibonacci = int(model.predict(np.array([[lstSequence[k],lstSequence[k+1]]])))
# add this new number to the list for the next iteration
lstSequence.append(intFibonacci)
# and print it
print(intFibonacci)
此代码片段包含与上一个类似的行——model.predict()。此行使用先前创建的模型进行推理。由于斐波那契数列是递归的,我们需要在lstSequence.append()行中将新创建的数字添加到列表中,然后才能进行新的推理。
现在非常重要的一点是要强调这两种解决相同问题的方法之间的区别。传统的实现暴露了用于创建数字的算法。在那里我们看不到斐波那契数列,但我们可以看到它是如何计算的。机器学习实现暴露了用于创建数字的数据。我们看到第一个序列作为训练数据,但我们从未看到模型是如何创建该序列的。我们不知道该模型是否总是正确的——我们需要将其与真实序列进行测试——仅仅因为我们不知道算法是如何工作的。这引出了下一个部分,也就是关于概率的部分。
概率与软件——它们如何相得益彰
使机器学习软件与传统软件不同的基本特征是,机器学习模型的核心是统计学。这种统计学习意味着机器学习模型的输出是一个概率,因此它不如传统软件系统那样清晰。
概率,即模型的结果,意味着我们收到的答案是某事的概率。例如,如果我们对一张图片进行分类以检查它是否包含狗或猫,这个分类的结果就是一个概率——例如,有 93% 的概率该图片包含狗,有 7% 的概率包含猫。这如图 图 1*.3* 所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_01_3.jpg
图 1.3 – 机器学习软件的概率性质
要在其他软件部分或其他系统中使用这些概率结果,机器学习软件通常使用阈值(例如,如果 x<0.5)来只提供单一结果。这些阈值指定了哪些概率是可以接受的,以便将结果考虑为属于特定类别。以我们的图像分类为例,这个概率将是 50%——如果识别图像中狗的概率大于 50%,则模型表示该图像包含狗(不包含概率)。
将这些概率结果转换为数字,就像我们在前面的例子中所做的那样,通常是正确的,但并不总是如此。特别是在边缘情况下,例如当概率接近阈值的下限时,分类可能导致错误,从而引起软件故障。这样的故障通常是可以忽略的,但并不总是如此。在安全关键系统中,不应有任何错误,因为它们可能导致不必要的危害,并可能产生灾难性的后果。
在机器学习软件的概率性质成为问题,但我们仍然需要机器学习以利用其其他好处的情况下,我们可以构建机制来减轻误判、误分类和次优化的后果。这些机制可以保护机器学习模型,防止它们提出错误的建议。例如,当我们使用机器学习图像分类在汽车的安全系统中时,我们在模型周围构建了一个所谓的 安全笼。这个安全笼是一个非机器学习组件,它使用规则来检查在特定环境中某个建议、分类或预测是否合理。例如,它可以防止汽车在高速公路上突然停车,因为这是由前摄像头摄像头的误分类引起的。
因此,让我们看看另一个最佳实践,它鼓励在安全关键系统中使用机器学习软件。
最佳实践 #3
如果你的软件是安全关键的,确保你可以设计机制来防止由机器学习软件的概率性质引起的安全隐患。
尽管这个最佳实践是针对关键安全系统制定的,但它比这更普遍。即使是任务关键或业务关键系统,我们也可以构建机制来门控机器学习模型,并防止整个软件系统的错误行为。如何构建这样一个笼子的一个例子在图 1**.4中显示,其中门控器组件提供了一个额外的信号,表明模型的预测不可信/不可用:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_01_4.jpg
图 1.4 – 机器学习模型的门控
在此图中,附加组件被放置在处理管道的最后,以确保结果始终是二元的(对于这种情况)。在其他情况下,这样的门控器可以与机器学习模型并行放置,并可以作为并行处理流程,检查数据质量而不是分类模型。
这样的门控器模型被相当频繁地使用,例如在感知系统检测对象时 – 模型检测单个图像中的对象,而门控器检查在一系列连续图像中是否一致地识别出相同的对象。它们可以形成冗余的处理通道和管道。它们可以形成可行性检查组件,或者可以将超出范围的输出结果更正为适当的值。最后,它们还可以将机器学习组件从管道中断开,并将这些管道适应软件的其他组件,通常是做出决策的算法 – 从而形成自适应或自修复的软件系统。
机器学习软件的这种概率性质意味着部署前的活动与传统软件不同。特别是,测试机器学习和传统软件的过程是不同的。
测试和评估 – 相同但不同
每个机器学习模型都需要经过验证,这意味着模型需要能够为模型之前未见过的一个数据集提供正确的推理。目标是评估模型是否在数据中学习了模式,数据本身,或者两者都不是。分类问题中正确性的典型度量是准确率(正确推断的实例数与所有分类实例数的商),曲线下面积/接收器操作特性(AUROC),以及真正例率(TPR)和假正例率(FPR)。
对于预测问题,模型的质量通过误预测来衡量,例如均方误差(MSE)。这些度量量化了预测中的错误 – 值越小,模型越好。图 1*.5*显示了最常见形式监督学习的评估过程:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_01_5.jpg
图 1.5 – 监督学习模型评估过程
在这个过程中,模型在每次训练迭代中都会接受不同的数据,之后它被用来对相同的测试数据进行推断(分类或回归)。测试数据在训练之前就被留出,并且仅在验证时作为模型的输入,永远不会在训练期间使用。
最后,一些模型是强化学习模型,其质量通过模型根据预定义函数(奖励函数)优化输出的能力来评估。这些措施允许算法优化其操作并找到最优解——例如,在遗传算法、自动驾驶汽车或能源网格操作中。这些模型的挑战在于没有单一的指标可以衡量性能——它取决于场景、函数以及模型接收到的训练量。一个著名的此类训练的例子是来自 1983 年电影《战争游戏》中的算法,其中主要超级计算机玩数百万个井字棋游戏,以理解没有获胜策略——游戏没有赢家。
图 1.6以图形方式展示了训练强化系统的过程:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_01_6.jpg
图 1.6 – 强化学习训练过程
我们可能会产生这样的印象,当开发机器学习软件时,我们只需要训练、测试和验证机器学习模型。这远远不是真的。这些模型是更大系统的一部分,这意味着它们需要与其他组件集成;这些组件在图 1.5和图 1.6中描述的验证过程中并未得到验证。
每个软件系统在发布之前都需要经过严格的测试。测试的目标是尽可能多地发现和消除缺陷,以便软件用户能够体验到最佳的质量。通常,测试软件的过程是一个包含多个阶段的流程。测试过程遵循软件开发过程,并与之一致。最初,软件工程师(或测试人员)使用单元测试来验证其组件的正确性。
图 1.7展示了这三种测试类型是如何相互关联的。在单元测试中,重点是算法。通常,这意味着软件工程师必须测试单个函数和模块。集成测试关注模块之间的连接以及它们如何共同执行任务。最后,系统测试和验收测试关注整个软件产品。测试人员模仿真实用户来检查软件是否满足用户的需求:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_01_7.jpg
图 1.7 – 三种软件测试类型 – 单元测试(左)、集成测试(中)和系统及验收测试(右)
软件测试过程与模型验证过程非常不同。尽管我们可以将单元测试误认为是模型验证,但这并不完全正确。模型验证过程的输出是其中一个指标(例如,准确率),而单元测试的输出是true/false——软件是否产生了预期的输出。对于软件公司来说,没有已知的缺陷(相当于错误的测试结果)是可以接受的。
在传统的软件测试中,软件工程师准备一系列测试用例来检查他们的软件是否按照规格工作。在机器学习软件中,测试过程基于留出一部分数据集(测试集)并检查训练好的模型(在训练集上)在该数据上的表现如何。
因此,这是我关于测试机器学习系统的第四个最佳实践。
最佳实践 #4
将机器学习软件测试作为机器学习模型开发典型训练-验证-评估流程的补充。
测试整个系统非常重要,因为整个软件系统包含处理机器学习组件概率性质的机制。其中一种机制是安全笼机制,我们可以监控机器学习组件的行为,防止它们向系统其他部分提供低质量信号(在边缘情况、接近决策边界、推理过程中)。
当我们测试软件时,我们也会了解机器学习组件的限制以及我们处理边缘情况的能力。这种知识对于在需要为软件指定操作环境时部署系统非常重要。我们需要了解与软件需求和规格相关的限制——我们软件的使用案例。更重要的是,我们需要了解软件使用的伦理和可靠性方面的含义。
我们将在第十五章和第十六章中讨论伦理问题,但重要的是要理解我们需要从一开始就考虑伦理问题。如果我们不这样做,我们的系统可能会犯出潜在的有害错误,例如大型人工智能招聘系统、人脸识别系统或自动驾驶汽车所犯的错误。这些有害错误涉及货币成本,但更重要的是,它们涉及对产品的信任损失,甚至错失机会。
摘要
机器学习与传统软件通常被视为两种替代品。然而,它们更像是兄弟姐妹——一个不能没有另一个。机器学习模型在解决约束性问题方面非常出色,但它们需要传统软件进行数据收集、准备和展示。
机器学习模型的概率性质需要额外的元素才能使它们在完整的软件产品环境中变得有用。因此,我们需要接受这种性质并利用它来发挥优势。即使对于安全关键系统,如果我们知道如何设计安全机制来防止危险后果,我们也可以(并且应该)使用机器学习。
在本章中,我们探讨了机器学习软件与传统软件之间的差异,同时关注如何设计能够包含这两部分的软件。我们还展示了机器学习软件不仅仅是训练、测试和评估模型,我们还展示了严格的测试对于部署可靠的软件是有意义且必要的。
现在,是时候进入下一章了,我们将揭开机器学习软件的黑箱,探讨我们需要开发一个完整的机器学习软件产品所需的内容——从数据采集开始,到用户交互结束。
参考文献
-
Shortliffe, E.H. 等,基于计算机的临床治疗咨询:MYCIN 系统的解释和规则获取能力。计算机与生物医学研究,1975 年,第 8 卷第 4 期: p. 303-320。
-
James, G. 等,统计学习引论。第 112 卷。Springer,2013 年。
-
Saleh, H.,机器学习基础:使用 Python 和 scikit-learn 开始使用机器学习的最新发展。Packt 出版公司,2018 年。
-
Raschka, S. 和 V. Mirjalili, Python 机器学习:使用 Python、scikit-learn 和 TensorFlow 2 的机器学习和深度学习,Packt 出版公司,2019 年。
-
Sommerville, I.,软件工程。第 10 版。软件工程书籍。第 10 版,软件工程系列,2015 年。
-
Houpis, C.H.,G.B. Lamont 和 B. Lamont,数字控制系统:理论、硬件、软件。McGraw-Hill,纽约,1985 年。
-
Sawhney, R.,人工智能能否使软件开发更高效?LSE 商业评论,2021 年。
-
He, X.,K. Zhao 和 X. Chu,AutoML:最新技术的调查。知识库系统。2021 年,第 212 卷: p. 106622。
-
*Reed, S. 等,通用代理。arXiv 预印本 arXiv:2205.06175,2022 年。
-
Floridi, L. 和 M. Chiriatti,GPT-3:其本质、范围、局限性和后果。心智与机器,2020 年,第 30 卷第 4 期: p. 681-694。
-
Creswell, A. 等,生成对抗网络:概述。IEEE 信号处理杂志,2018 年,第 35 卷第 1 期: p. 53-65。
-
Celebi, M.E. 和 K. Aydin,无监督学习算法。Springer,2016 年。
-
Chen, J.X., 计算机发展的演变:AlphaGo。科学工程计算,2016 年。18(4): p. 4-7.
-
Ko, J.-S., J.-H. Huh, and J.-C. Kim, 改进应用于工业 4.0 数据中心冷却系统风扇的能源效率和控制性能。电子学,2019 年。8(5): p. 582.
-
Dastin, J., 亚马逊废弃了显示对女性有偏见的秘密 AI 招聘工具。在《数据与分析伦理》中。2018 年,Auerbach 出版社。 p. 296-299.
-
Castelvecchi, D., 面部识别技术是否偏见过大以至于不能放任?自然,2020 年。587(7834): p. 347-350.
-
Siddiqui, F., R. Lerman, 和 J.B. Merrill,自去年以来报告的特斯拉 Autopilot 涉及 273 起事故。在《华盛顿邮报》中。2022 年。
第二章:机器学习系统的要素
数据和算法对于机器学习系统至关重要,但它们远远不够。算法是生产级机器学习系统中最小的一部分。机器学习系统还需要数据、基础设施、监控和存储才能高效运行。对于大规模机器学习系统,我们需要确保我们可以包含一个良好的用户界面或包装模型在微服务中。
在现代软件系统中,结合所有必要的元素需要不同的专业能力——包括机器学习/数据科学工程专业知识、数据库工程、软件工程,以及最终的用户交互设计。在这些专业系统中,提供可靠的结果,为用户提供价值,比包含大量不必要的功能更为重要。同时,也很重要将机器学习的所有元素(数据、算法、存储、配置和基础设施)协同起来,而不是单独优化每一个元素——所有这些都是为了提供一个最优化系统,用于满足一个或多个最终用户的使用案例。
在本章中,我们将回顾专业机器学习系统的每个要素。我们将首先了解哪些要素是重要的以及为什么重要。然后,我们将探讨如何创建这些要素以及如何将它们组合成一个单一的机器学习系统——所谓的机器学习管道。
在本章中,我们将涵盖以下主要主题:
-
机器学习不仅仅是算法和数据
-
数据和算法
-
配置和监控
-
基础设施和资源管理
-
机器学习管道
生产级机器学习系统的要素
现代机器学习算法非常强大,因为它们使用大量数据,并包含大量可训练的参数。目前可用的最大模型是来自 OpenAI 的生成预训练转换器 3(GPT-3)(拥有 1750 亿个参数)和来自 NVIDIA 的 Megatron-Turing(3560 亿个参数)。这些模型可以创建文本(小说)和进行对话,还可以编写程序代码、创建用户界面或编写需求。
现在,这样的大型模型无法在台式电脑、笔记本电脑甚至专用服务器上使用。它们需要先进的计算基础设施,能够承受长期训练和评估这样的大型模型。这样的基础设施还需要提供自动为这些模型提供数据、监控训练过程,以及最终为用户提供访问模型进行推理的可能性。提供这种基础设施的现代方式之一是机器学习即服务(MLaaS)。MLaaS 为数据分析师或软件集成商提供了一个简单的方式来使用机器学习,因为它将基础设施的管理、监控和配置委托给专业公司。
图 2*.1* 展示了基于现代机器学习软件系统的元素。从那时起,谷歌已经使用这些来描述生产机器学习系统。尽管这种设置存在变化,但原则仍然适用:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_01.jpg
图 2.1 – 生产机器学习系统的元素
在这里,机器学习模型(ML 代码)是这些元素中最小的(谷歌,根据创意共享 4.0 属性许可,developers.google.com/machine-learning/crash-course/production-ml-systems)。从实际源代码的角度来看,在 Python 中,模型创建、训练和验证只需要三行代码(至少对于某些模型):
model = RandomForestRegressor(n_estimators=10, max_depth=2)
model.fit(X_train, Y_train)
Y_pred = model.predict(X_test)
第一行从模板创建模型——在这种情况下,它是一个包含 10 棵树的随机森林模型,每棵树最多有两个分割。随机森林是一种集成学习方法,在训练过程中构建多个决策树,并输出给定输入的各个树的类(分类)的模态。通过聚合多个树的结果,它减少了过拟合,并比单个决策树提供了更高的准确性。第二行基于训练数据(X_train,其中只包含预测者/输入特征,以及 Y_train,其中包含预测的类/输出特征)训练模型。最后,最后一行对测试数据(X_test)进行预测,以便在后续步骤中将预测结果与先知(预期值)进行比较。尽管这一行 model.predict(X_test) 不是生产系统的一部分,但我们仍然需要进行推断,因此我们的软件中始终存在类似的行。
因此,我们可以介绍下一个最佳实践。
最佳实践 #5
在设计机器学习软件时,应优先考虑数据和要解决的问题,而不是算法。
在这个例子中,我们从软件工程师的角度看到,机器学习代码相当小。在应用算法之前,我们需要正确准备数据,因为算法(model.fit(X_train, Y_train))需要数据以特定格式存在——第一个参数是用于推断的数据(所谓的特征向量或输入数据样本),而第二个参数是目标值(所谓的决策类、参考值或目标值,具体取决于算法)。
数据和算法
现在,如果使用算法不是机器学习代码的主要部分,那么其他部分必须是——也就是说,数据处理。如图 图 2*.1* 所示,在机器学习软件中管理数据包括三个领域:
-
数据收集。
-
特征提取。
-
数据验证。
尽管我们将在整本书中回顾这些领域,但让我们来看看它们包含的内容。图 2*.2* 展示了这些领域的处理流程:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_02.jpg
图 2.2 – 数据收集和准备流程
注意,为算法准备数据的过程可能变得相当复杂。首先,我们需要从其来源中提取数据,这通常是一个数据库。它可以是测量、图像、文本或其他任何原始数据的数据库。一旦我们导出/提取了所需的数据,我们必须以原始数据格式存储它。这可以是表格的形式,如图中所示,也可以是一组原始文件,如图像。
数据收集
数据收集是将数据从其原始格式转换为机器学习算法可以接受的输入格式的过程。根据数据和算法的不同,这个过程可以采取不同的形式,如图 图 2*.3* 所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_03.jpg
图 2.3 – 不同形式的数据收集 – 示例
来自图像和测量(如时间序列)的数据通常被收集来进行分类和预测。这两类问题需要可用的事实数据,这在之前的代码示例中我们将其视为 Y_train。这些目标标签要么从原始数据中自动提取,要么通过标记过程手动添加。手动过程耗时较长,因此更倾向于自动化。
用于非监督学习和强化学习模型的数据通常被提取为无标签的表格数据。这些数据用于决策过程或优化过程,以找到给定问题的最佳解决方案。没有优化,我们的结果可能无法代表新数据。前面的图显示了此类问题的两个示例——工业 4.0 智能工厂的优化和自动驾驶汽车。在智能工厂中,强化学习模型用于优化生产过程或控制所谓的暗工厂,这些工厂完全不需要人工干预(暗工厂这个名字来源于这些工厂不需要灯光;机器人不需要灯光工作)。
用于现代自监督模型的常用数据通常来自文本或语音等来源。这些模型不需要数据的表格形式,但需要结构。例如,为了训练文本转换器模型,我们需要按句子(或段落)对文本进行分词,以便模型学习单词的上下文。
因此,接下来是我的下一个最佳实践。
最佳实践 #6
一旦你探索了你想要解决的问题,并了解了数据的可用性,决定你是否想使用监督学习、自监督学习、无监督学习或强化学习算法。
我们需要为不同的算法使用不同的数据是自然的。然而,我们还没有讨论如何决定算法。仅当我们想要静态地预测或分类数据时,选择监督学习才有意义——也就是说,我们训练模型,然后使用它进行推断。当我们训练并做出推断时,模型不会改变。我们没有进行调整,并且算法的重新训练是定期进行的——我把这称为“训练一次,预测多次”原则。
当我们在没有目标类的情况下使用/训练和应用算法时,我们可以选择无监督方法。其中一些算法也用于根据数据的属性对数据进行分组,例如,进行聚类。我把这称为“训练一次,预测一次”原则。
对于自监督模型,情况要有趣得多。在那里,我们可以使用一种称为“预训练”的方法。预训练意味着我们可以在没有任何特定上下文的大数据语料库上训练一个模型——例如,我们在维基百科的英文文本大型语料库上训练语言模型。然后,当我们想要将模型用于特定任务时,例如编写新文本,我们会在该任务上对其进行更多训练。我把这称为“训练多次,预测一次”原则,因为我们必须为每个任务进行预训练和训练模型。
最后,强化学习需要每次使用模型时都发生变化的数据。例如,当我们使用强化学习算法优化一个过程时,它每次使用时都会更新模型——我们可以说是从错误中学习。我把这称为“训练多次,预测多次”原则。
通常,原始数据不适合用于机器学习,因为它可能包含空数据点、噪声或损坏的文件。因此,我们需要清理这些错误数据点,例如通过删除空数据点(使用 Python 命令如dataFrame.dropna(…))或使用数据插补技术。
现在,删除数据点和它们的插补之间存在根本性的区别。数据插补过程是我们根据相似数据点添加数据缺失属性。这就像在数字序列中填补空白——1, 2, 3, …, 5,其中我们填补数字 4。尽管填补数据增加了可用的数据点数量(从而使得模型更好),但它可以加强数据的某些属性,这可能导致模型学习。当数据量较小时,插补也相关;在大数据集中,删除缺失数据点更好(更资源高效且公平)。有了这个,我们就来到了我的下一个最佳实践。
最佳实践 #7
只有当你知道你希望加强哪些数据属性时,才使用数据插补,并且只为小数据集这样做。
最后,一旦我们有了可以工作的干净数据,我们就可以提取特征。在那里,我们可以使用针对我们手头问题的特定算法。例如,当我们处理文本数据时,我们可以使用简单的词袋模型来计算单词的频率,尽管我们也可以使用 word2vec 算法来嵌入单词共现的频率(我们将在下一章讨论的算法)。一旦我们提取了特征,我们就可以开始验证数据。这些特征可以强调我们之前没有看到的数据的某些属性。
以下是一个这样的例子:噪声——当我们有特征格式的数据时,我们可以检查数据中是否存在属性或类别噪声。类别噪声是与标签错误相关的一种现象——一个或多个数据点被错误地标记。类别噪声可以是矛盾示例或错误标记的数据点。这是一个危险的现象,因为它可能导致在训练和使用机器学习模型时性能低下。
属性噪声是指一个(或多个)属性被错误值所损坏。例如,包括错误值、缺失属性(特征)值和不相关值。
一旦数据经过验证,就可以用于算法。所以,让我们深入探讨数据处理管道的每个步骤。
现在,由于不同的算法以不同的方式使用数据,数据具有不同的形式。让我们探索每种算法应该如何构建数据结构。
特征提取
将原始数据转换为算法可用的格式的过程称为特征提取。这是一个应用特征提取算法来寻找数据中感兴趣属性的过程。提取特征算法根据问题和数据类型的不同而有所不同。
当我们处理文本数据时,我们可以使用几个算法,但让我来展示最简单的一个——词袋模型的使用。该算法简单地计算句子中单词的出现次数——它要么计算一个预定义的单词集,要么使用统计方法来找到最频繁的单词。让我们考虑以下句子:
Mike is a tall boy.
当我们无约束地使用词袋算法时,它提供了以下表格:
| 句子 ID | Mike | Is | A | tall | Boy |
|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 1 | 1 |
图 2.4 – 使用词袋模型提取的特征
该表包含句子中的所有单词作为特征。对于单个句子来说,它并不很有用,但如果我们添加另一个(句子 1),事情就会变得更加明显。所以,让我们添加以下句子:
Mary is a smart girl.
这将导致以下特征表:
| 句子 ID | Mike | Is | A | Tall | boy | smart | girl |
|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 |
1 | 0 | 1 | 1 | 0 | 0 | 1 | 1 |
图 2.5 – 从两个句子中提取的特征
现在,我们已经准备好将标签列添加到数据中。假设我们想要将每个句子标记为正面或负面。然后表格将增加一列 – label – 其中 1 表示句子是正面的,否则为 0:
| 句子 ID | Mike | Is | A | Tall | boy | smart | girl | Label |
|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
1 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 |
图 2.6 – 添加到数据中的标签
现在,这些特征使我们能够看到两个句子之间的差异,然后我们可以使用这些差异来训练和测试机器学习算法。
然而,这种方法有两个重要的局限性。第一个局限性是,将所有句子中的所有单词作为列/特征是不切实际(如果不是不可能)的。对于任何非平凡文本,这会导致大型稀疏矩阵 – 浪费大量空间。第二个局限性是,我们通常会丢失重要的信息 – 例如,句子“Is Mike a boy?”会产生与第一个句子相同的特征向量。特征向量是描述机器学习中模式识别中某个对象的 n- 维数值特征向量。尽管这些句子并不相同,但它们变得无法区分,这可能导致如果它们被标记为不同类别时出现类别噪声。
如果我们使用统计方法来选择最频繁的单词作为特征,那么添加这种噪声的问题就会变得更加明显。在这里,我们可能会丢失一些重要的特征,这些特征以有用的方式区分数据点。因此,这种词袋方法在这里仅用于说明。在本书的后面部分,我们将更深入地探讨所谓的转换器,它们使用更先进的技术来提取特征。
数据验证
特征向量是机器学习算法的核心。它们最显著且直接地被监督机器学习算法使用。然而,数据验证的相同概念也适用于其他类型验证中使用的数据。
每种形式的数据验证都是一系列检查,确保数据包含所需的属性。以下是一个此类检查集的示例 图 2*.7*:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_04.jpg
图 2.7 – 数据质量检查示例
数据的完整性是描述我们的数据覆盖总分布多少的一个属性。这可以通过对象分布来衡量 – 例如,在我们的图像数据集中有多少种/型号/颜色的汽车 – 或者它可以通过属性来衡量 – 例如,我们的数据中包含的语言中有多少个单词。
准确性是描述我们的数据与经验(现实)世界相关性的一个属性。例如,我们可能想要检查我们数据集中的所有图像是否都与一个对象相关联,或者图像中的所有对象是否都被标注了。
一致性描述了数据内部结构的好坏以及相同的数据点是否以相同的方式进行标注。例如,在二元分类中,我们希望所有数据点都被标注为“0”和“1”或“True”和“False”,而不是两者都有。
完整性是检查数据是否可以与其他数据集成的属性。集成可以通过公共键或其他方式完成。例如,我们可以检查我们的图像是否包含允许我们知道图像拍摄地点的元数据。
最后,及时性是描述数据新鲜程度的属性。它检查数据是否包含最新的所需信息。例如,当我们设计推荐系统时,我们希望推荐新项目和旧项目,因此及时性很重要。
因此,这里是下一个最佳实践。
最佳实践 #8
选择对您的系统最相关的数据验证属性。
由于每个检查都需要在工作流程中增加额外的步骤,可能会减慢数据处理的速度,因此我们应该选择影响我们业务和架构的数据质量检查。如果我们开发一个希望提供最新推荐的系统,那么及时性是我们的首要任务,我们应该专注于这一点,而不是完整性。
虽然有很多用于数据验证和评估数据质量的框架,但我通常使用来自 AIMQ 框架的数据质量属性子集。AIMQ 框架已被设计为根据几个质量属性量化数据质量,类似于软件工程中的质量框架,如 ISO 25000 系列。我发现以下数据属性对于验证来说是最重要的:
-
数据无噪声
-
数据是新鲜的
-
数据适合用途
第一个属性是最重要的,因为噪声数据会导致机器学习算法性能低下。对于之前介绍的类别噪声,重要的是检查数据标签是否没有矛盾,并检查标签是否分配正确。矛盾标签可以自动找到,但错误标注的数据点需要人工评估。对于属性噪声,我们可以使用统计方法来识别具有低变异性(甚至完全恒定的)或对模型学习不贡献的完全随机的属性。让我们考虑一个句子的例子:
Mike is not a tall boy.
如果我们使用与之前句子相同的特征提取技术,我们的特征矩阵看起来就像图 2.8 中所示的那样。对于句子 2,我们使用与句子 0 和 1 相同的特征。由于最后一个句子仅在一词(not)上有所不同,这可能导致类别噪声。最后一列有一个标签,not。这可能会发生在我们在一个数据集上训练模型并将其应用于另一个数据集时。这意味着第一个句子和最后一个句子具有相同的特征向量,但标签不同:
| 句子 ID | Mike | Is | A | tall | boy | smart | girl | 标签 |
|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 |
1 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 |
2 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
图 2.8 – 噪声数据集
对于同一特征向量存在两种不同的标注是有问题的,因为机器学习算法无法学习这些噪声数据点的模式。因此,我们需要验证数据是否无噪声。
数据需要具备的另一属性是其时效性——也就是说,数据必须是新鲜的。我们必须使用当前数据,而不是旧数据。在自动驾驶领域,这一点尤为重要,我们需要确保模型与最新条件(例如,最新的交通标志)保持更新。
最后,验证最重要的部分是评估数据是否适合使用。请注意,这种评估不能自动完成,因为它需要专家进行。
配置和监控
机器学习软件旨在进行专业化的设计、部署和维护。现代公司称这个过程为MLOps,这意味着同一个团队需要负责机器学习系统的开发和运营。这种扩展责任背后的逻辑是,团队最了解系统,因此可以以最佳方式对其进行配置、监控和维护。团队知道在开发系统时必须做出的设计决策,对数据的假设,以及在部署后需要监控的潜在风险。
配置
配置是开发团队做出的设计决策之一。团队配置机器学习模型的参数、执行环境和监控基础设施。让我们探讨第一个;后两个将在接下来的几节中讨论。
为了说明这个挑战,让我们看看一个用于在特定手术期间分类事件的随机森林分类器。该分类器,至少在其 Python 实现中,有 16 个超参数(scikit-learn.org/stable/modules/generated/sklearn.ensemble.RandomForestClassifier.html)。这些超参数中的每一个都有几个值,这意味着找到最佳的超参数集可能是一项繁琐的任务。
然而,在实践中,我们不需要探索所有超参数的值,也不需要探索所有组合。我们只需探索与我们任务和,通过扩展,数据集最相关的那些。我通常只探索两个超参数,因为它们是最重要的:树木的数量和树的深度。第一个决定了森林有多宽,而第二个决定了它有多深。指定这些参数的代码可能看起来像这样:
rnd_clf = RandomForestClassifier(n_estimators=2,
max_leaf_nodes=10,
n_jobs=-1)
n_estimators超参数是树木的数量,而max_depth超参数是每棵树的深度。这些参数的值取决于数据集——我们有多少个特征以及有多少个数据点。如果我们有太多的树木和叶子,与特征和数据点的数量相比,分类器就会过拟合,无法从数据中泛化。这意味着分类器学会了识别每个实例,而不是识别数据中的模式——我们称这种情况为过拟合。
如果树木或叶子太少,那么泛化模式就会过于宽泛,因此我们在分类中观察到错误——至少比最佳分类器更多的错误。我们称这种情况为欠拟合,因为模型没有正确学习到模式。
因此,我们可以编写一段代码,根据预定义的值集搜索这两个参数的最佳组合。手动寻找最佳参数的代码可能看起来像这样:
numEstimators = [2, 4, 8, 16, 32, 64, 128, 256, 512]
numLeaves = [2, 4, 8, 16, 32, 64, 128]
for nEst in numEstimators:
for nLeaves in numLeaves:
rnd_clf = RandomForestClassifier(n_estimators=nEst,
max_leaf_nodes=nLeaves,
n_jobs=-1)
rnd_clf.fit(X_train, y_train)
y_pred_rf = rnd_clf.predict(X_test)
accuracy_rf = accuracy_score(y_test, y_pred_rf)
print(f'Trees: {nEst}, Leaves: {nLeaves}, Acc: {accuracy_rf:.2f}')
突出显示的两行橙色代码展示了探索这些参数的两个循环——内循环循环使用这些参数训练分类器并打印输出。
让我们将这个算法应用于经历过手术的患者的生理数据。当我们把输出绘制在图表上,如图*图 2**.9 所示,我们可以观察到准确率是如何演变的。如果我们把树木的数量设置为 2,分类器的最佳性能是在 8 个叶子时达到的,但即使如此,它也无法完美地分类事件。对于四棵树,分类器在 128 个叶子时达到最佳性能,准确率为 1.0。从下面的图表中,我们可以看到增加更多的树木并没有显著提高结果:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_05.jpg
图 2.9 – 每个估计器和叶子数目的准确率。x 轴的标签显示了树的数量(下划线之前)和叶子的数量(下划线之后)
对于这个例子,搜索最佳结果所需的时间相对较短——在标准笔记本电脑上最多需要 1-2 分钟。然而,如果我们想找到所有 16 个参数的最佳组合,我们将花费相当多的时间来做这件事。
有一种更自动化的方法来找到机器学习分类器的最佳参数——不同类型的搜索算法。其中最受欢迎的一种是 GridSearch 算法 (scikit-learn.org/stable/modules/generated/sklearn.model_selection.GridSearchCV.html),它的工作方式与我们的手动脚本类似,只不过它可以进行交叉验证,有多个分割,以及许多其他统计技巧来改进搜索。使用 GridSearch 算法搜索最佳解决方案可能看起来像这样:
# Create the parameter grid based on the results of random search
param_grid = {
'max_depth': [2, 4, 8, 16, 32, 64, 128],
'n_estimators': [2, 4, 8, 16, 32, 64, 128, 256, 512]
}
# Create a base model
rf = RandomForestClassifier()
# Instantiate the grid search model
grid_search = GridSearchCV(estimator = rf,
param_grid = param_grid,
cv = 3,
n_jobs = -1)
# Fit the grid search to the data
grid_search.fit(X_train, y_train)
# get the best parameters
best_grid = grid_search.best_estimator_
# print the best parameters
print(grid_search.best_params_)
之前的代码找到了最佳解决方案并将其保存为 GridSearch 模型的 best_estimator_ 参数。在这个数据集和模型的情况下,算法找到了最佳随机森林,它有 128 棵树(n_estimators)和 4 个层级(max_depth)。结果与手动找到的结果略有不同,但这并不意味着其中一种方法更优越。
然而,树的数量过多可能会导致过拟合,因此我宁愿选择有 4 棵树和 128 个叶子的模型,而不是有 128 棵树和 4 个层级的模型。或者,也许我还会使用一个介于两者之间的模型——也就是说,一个具有相同准确率但不太容易在深度或宽度上过拟合的模型。
这导致我的下一个最佳实践。
最佳实践 #9
在手动探索参数搜索空间之后,使用 GridSearch 和其他算法。
尽管自动参数搜索算法非常有用,但它们隐藏了数据的一些特性,并且不允许我们自行探索数据和参数。根据我的经验,理解数据、模型、其参数及其配置对于成功部署机器学习软件至关重要。我只有在尝试手动找到一些最优解之后才会使用 GridSearch(或其他优化算法),因为我希望理解数据。
监控
一旦机器学习系统配置完成,它就会被投入生产,通常作为更大软件系统的一部分。机器学习所做的推断是产品特性和背后商业模式的基础。因此,机器学习组件应尽可能减少错误。不幸的是,客户对失败和错误的关注程度高于正确运行的产品。
然而,机器学习系统的性能会随时间退化,但这并不是因为编程或设计质量低劣——这是概率计算的本质。因此,所有机器学习系统都需要监控和维护。
需要监控的一个方面被称为概念漂移。概念漂移是数据中的一个现象,这意味着数据中实体的分布因自然原因随时间变化。图 2.10展示了机器学习分类器(蓝色和红色线条)对黄色和橙色卡车图像的概念漂移:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_06.jpg
图 2.10 – 概念漂移的示意图。物体(左侧)的原始分布随时间(右侧)变化,因此分类器必须重新训练(蓝色与红色线条)
左侧显示了用于训练模型的原始数据分布。模型在概念上表示为蓝色虚线。模型能够识别两种图像类别之间的差异。然而,随着时间的推移,数据可能会发生变化。新的图像可能会出现在数据集中,分布也会发生变化。原始模型在推理中开始出错,因此需要调整。重新训练的模型——即实心红色线条——捕捉了数据的新分布。
我们将数据集中的这种变化称为概念漂移。它在复杂的数据集和监督学习模型中更为常见,但概念漂移对非监督模型和强化学习模型的影响同样成问题。
图 2.11展示了应用于相同分布(直接在训练后)和经过某些操作后的数据的同一随机森林模型的表现。概念漂移在准确率从 1.0 降至 0.44 的降低中可见。该模型与图 2.9中的示例使用相同的数据进行训练,但应用于来自另一位患者的数据:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_07.jpg
图 2.11 – 概念漂移前后性能降低的示例
因此,我想介绍我的下一个最佳实践。
最佳实践#10
在您的机器学习系统中始终包含监控机制。
即使是使用卡方统计测试来比较分布相似性的简单机制,包括监控概念漂移的机制,也能产生很大的影响。它使我们能够识别系统中的问题,解决问题,并防止它们扩散到软件的其他部分,甚至影响到软件的最终用户。
专业机器学习工程师在生产中设置了概念漂移的监控机制,这表明人工智能软件的退化。
基础设施和资源管理
机器学习软件所需的基础设施和资源被组织成两个区域——数据服务基础设施(例如,数据库)和计算基础设施(例如,GPU 计算平台)。还有服务基础设施,用于向最终用户提供服务。服务基础设施可以是桌面应用程序、嵌入式软件(例如自动驾驶汽车中的软件)、工具的插件(如 GitHub Co-pilot 的情况)或网站(如 ChatGPT)。然而,在这本书中,我们将重点关注数据服务基础设施和计算基础设施。
这两个区域都可以本地或远程部署。本地部署意味着我们使用公司自己的基础设施,而远程基础设施意味着我们使用云服务或其他供应商的服务。
从概念上讲,我们可以将这些区域视为相互依赖的,如图图 2.12所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_08.jpg
图 2.12 – 服务、计算和数据服务基础设施之间的相互依赖关系
数据服务基础设施提供用于计算基础设施的数据。它包括数据库和其他数据源(例如,原始文件)。计算基础设施包括用于训练和测试机器学习模型的计算资源。最后,用户服务基础设施使用模型进行推理,并向最终用户提供服务和功能。
数据服务基础设施
数据服务基础设施是机器学习软件的基本组成部分之一,因为没有数据就没有机器学习。对数据有需求的人工智能应用对基础设施在性能、可靠性和可追溯性方面提出了新的要求。最后一个要求非常重要,因为机器学习训练数据决定了训练好的机器学习模型如何进行推理。在终端用户功能出现缺陷的情况下,软件工程师需要仔细审查用于构建失败机器学习系统的算法、模型和数据。
与传统软件相比,数据服务基础设施通常由三个不同的部分组成,如图图 2.13所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_09.jpg
图 2.13 – 数据服务基础设施
数据存储在持久存储中,例如硬盘上的数据库。它可以是本地存储或云服务器上的存储。最重要的是,这些数据是安全的,并且可以用于进一步处理。持久存储的数据需要提取出来,以便在机器学习中使用。第一步是找到所需的数据快照——例如,通过选择用于训练的数据。快照需要准备并格式化为表格形式,以便机器学习算法可以使用这些数据进行推理。
今天,机器学习系统使用了几种不同类型的数据库。首先,有标准的数据库,数据以表格形式存储。这些是众所周知且在传统和机器学习软件中广泛使用的数据库。
新类型的数据库是非 SQL 数据库,如 Elasticsearch (www.elastic.co),它被设计用于存储文档,而不是表格。这些文档可以灵活索引,以便根据这些文档存储和检索数据。在机器学习软件的情况下,这些文档可以是图像、整个文本文档,甚至是声音数据。这对于以收集时的相同格式存储数据非常重要,这样我们就可以在需要时追踪数据。
不论数据库中数据的格式如何,它都是从数据库中检索出来并转换为表格形式的;我们将在第三章中讨论这一点。这种表格形式是数据处理所需的基础设施。
有了这些,我们就来到了我的下一个最佳实践。
最佳实践#11
为您的数据选择正确的数据库——从数据的角度而不是系统的角度来考虑。
虽然选择适合我们数据的正确数据库听起来很明显,但对于机器学习系统来说,选择最适合当前数据的数据库,而不是系统本身,这一点非常重要。例如,当我们使用自然语言处理模型时,我们应该将数据存储在我们可以轻松检索并按组织形式存储的文档中。
计算基础设施
计算基础设施可能会随时间变化。在机器学习系统开发的早期阶段,软件开发者通常会使用预配置的实验环境。这些可以是他们电脑上的 Jupyter Notebooks,也可以是预配置的服务,如 Google Colab 或 Microsoft Azure Notebooks。这种基础设施支持机器学习的快速原型设计,易于数据提供,无需设置高级功能。它们还允许我们轻松地根据需要调整计算资源,而无需添加或移除额外硬件。
这种方法的替代方案是使用我们自己的基础设施,在那里我们为您设置自己的服务器和运行时环境。这需要更多的努力,但它使我们能够完全控制计算资源,以及完全控制数据处理。对数据处理拥有完全控制权有时可能是选择基础设施最重要的因素。
因此,我的下一个最佳实践。
最佳实践 #12
如果可能,请使用云基础设施,因为它可以节省资源并减少对专业技术的需求。
专业 AI 工程师使用自有的基础设施进行原型设计和训练,并使用基于云的基础设施进行生产,因为它的扩展性更好,可以随着用户数量的增加而扩展。相反,即使用我们自己的基础设施,也只有在我们需要保留对数据或基础设施的完全控制时才是正确的。对于使用敏感客户数据、军事应用、安全应用和其他数据极其敏感的应用程序,可能需要完全控制。
幸运的是,我们有几个大型演员提供计算基础设施,以及一个庞大的小型演员生态系统。其中最大的三个是亚马逊网络服务(Amazon Web Services)、谷歌云(Google Cloud)和微软 Azure(Microsoft Azure),它们可以为小型和大型企业提供各种服务。亚马逊网络服务(aws.amazon.com)专注于提供数据存储和处理基础设施。它通常用于必须快速处理大量数据的应用程序。该基础设施由专业人员维护,并可用于实现基于该平台构建的产品的高近完美可靠性。为了高效使用它,你通常必须与执行机器学习应用程序代码的容器和虚拟机一起工作。
谷歌云(cloud.google.com)专注于为数据密集型应用程序和计算密集型解决方案提供平台。多亏了谷歌的处理器(张量处理单元(TPUs)),该平台为训练和使用机器学习解决方案提供了一个非常高效的环境。谷歌云还提供免费的学习机器学习解决方案,形式为 Google Colab,它是 Python 平台 Jupyter Notebook(jupyter.org)的扩展。
微软 Azure(azure.microsoft.com)专注于提供虚拟机形式的机器学习系统训练和部署平台。它还提供用于图像识别、分类和自然语言处理的现成可部署模型,甚至提供用于训练通用机器学习模型的平台。它是所有可用平台中最灵活的,也是最具可扩展性的。
除了这些平台,你还可以使用几个专门的平台,例如 Facebook 的机器学习平台,该平台专注于推荐系统。然而,由于专门的平台通常比较狭窄,如果我们想将我们的软件从一个平台迁移到另一个平台,我们需要记住可能存在的问题。
因此,我的下一个最佳实践。
最佳实践#13
早期确定你希望使用的生产环境,并将你的流程与该环境对齐。
我们需要决定是想使用亚马逊的、谷歌的,还是微软的云环境,或者我们是否想使用自己的基础设施以降低软件开发成本。虽然可以在这些环境之间移动我们的软件,但这并不简单,并且需要(最好)进行大量的测试和预部署验证,这通常伴随着显著的成本。
所有这些如何结合在一起——机器学习管道
在本章中,我们探讨了机器学习系统的主要特征,并将它们与传统软件系统进行了比较。让我们通过总结我们通常如何设计和描述机器学习系统——通过使用管道来完成这一比较。管道是一系列数据处理步骤,包括机器学习模型。典型的步骤集(也称为阶段)如图2.14所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_02_10.jpg
图 2.14 – 机器学习管道中的典型步骤序列
这种类型的管道,尽管是线性绘制的,但通常是在循环中处理的,例如,监控概念漂移可以触发重新训练、重新测试和重新部署。
机器学习管道,就像图2.14中展示的那样,通常被描绘为整个系统的一部分组件集。然而,使用管道类比来展示它有助于我们理解机器学习系统是按步骤处理数据的。
在下一章中,我们将探讨管道的第一部分——数据处理。我们将从探索不同类型的数据以及这些类型的数据在现代软件系统中的收集、处理和使用开始。
参考文献
-
Shortliffe, E.H.,等人,基于计算机的临床治疗咨询:MYCIN 系统的解释和规则获取能力。计算机与生物医学研究,1975 年。8(4): p. 303-320。
-
Vaswani, A.,等人,注意力就是一切。神经信息处理系统进展,2017 年。30。
-
Dale, R.,GPT-3:它有什么用?自然语言工程,2021 年。27(1): p. 113-118。
-
Smith, S.,等人,使用 deepspeed 和 megatron 训练 megatron-turing nlg 530b,一个大规模生成语言模型。arXiv 预印本 arXiv:2201.11990,2022 年。
-
Lee, Y.W.,等人,AIMQ:信息质量评估的方法。信息与管理,2002 年。40(2): p. 133-146。
-
Zenisek, J., F. Holzinger, and M. Affenzeller, 基于机器学习的概念漂移检测用于预测性维护。计算机与工业工程,2019. 137: p. 106031.
-
Amershi, S., 等人. 软件工程在机器学习中的应用:一个案例研究。在 2019 IEEE/ACM 第 41 届国际软件工程会议:软件工程实践(ICSE-SEIP)。 2019. IEEE.
第三章:软件系统中的数据——文本、图像、代码及其标注
机器学习(ML)系统是数据需求大的应用,它们喜欢数据在训练和推理前已经准备妥当。尽管这听起来可能很显然,但比选择一个处理数据的算法更重要的是仔细审查数据的属性。然而,数据可以以许多不同的格式出现,并来自不同的来源。我们可以考虑数据以原始格式——例如,文本文档或图像文件。我们还可以考虑数据以特定于当前任务的手动格式——例如,分词文本(其中单词被分成标记)或带有边界框的图像(其中对象被识别并包含在矩形内)。
当考虑最终用户系统时,我们可以用数据做什么以及我们如何处理数据变得至关重要。然而,识别数据中的重要元素并将其转换为对机器学习算法有用的格式取决于我们的任务和使用的算法。因此,在本章中,我们将同时处理数据和算法来处理它。
在本章中,我们将介绍三种数据类型——图像、文本和格式化文本(程序源代码)。我们将探讨每种类型的数据如何在机器学习中使用,它们应该如何标注,以及用于什么目的。
介绍这三种类型的数据为我们提供了探索这些数据源不同标注方式的可能性。因此,在本章中,我们将重点关注以下内容:
-
原始数据和特征——它们之间有什么区别?
-
每种数据都有其用途——标注和任务
-
不同类型的数据可以一起使用的地方——多模态数据模型展望
原始数据和特征——它们之间有什么区别?
机器学习(ML)系统对数据的需求很大。它们依赖于数据进行训练和推理。然而,并非所有数据都同等重要。在深度学习(DL)时代之前,数据需要经过处理才能用于机器学习。在深度学习之前,算法在可用于训练的数据量方面受到限制。存储和内存限制也有限,因此,机器学习工程师必须为深度学习准备更多的数据。例如,机器学习工程师需要花费更多精力来找到一个既小又具有代表性的数据样本用于训练。深度学习引入后,机器学习模型可以在更大的数据集中找到复杂的模式。因此,机器学习工程师的工作现在集中在寻找足够大且具有代表性的数据集。
经典机器学习系统——即非深度学习系统——需要表格形式的数据来进行推理,因此为这类系统设计正确的特征提取机制非常重要。
另一方面,深度学习系统需要最少的数据处理,并且可以从数据(几乎)原始格式中学习模式。由于深度学习系统需要为不同任务获取一些关于数据的不同信息,因此需要最少的数据处理;它们还可以自行从原始数据中提取信息。例如,它们可以捕捉文本的上下文,而无需手动处理。图 3.1展示了基于可以执行的任务的不同类型数据之间的这些差异。在这种情况下,数据以图像的形式存在:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_1.jpg
图 3.1 – 学习系统的类型及其对图像所需的数据
原始图像通常用于进一步处理,但它们也可以用于图像分类等任务。图像分类的任务与算法的输入是原始图像,输出是图像类别相关。当我们谈论包含“猫”、“狗”或“汽车”等内容的图像时,我们经常看到这类任务。
这个任务有许多实际应用。一个应用是在保险领域。几家保险公司已经改变了他们的商业模式并将业务数字化。在 2010 年代中期之前,保险公司需要首先到车间进行一次访问以对汽车损坏进行初步评估。今天,这种初步的损坏评估是通过图像分类算法自动完成的。我们用智能手机拍摄损坏的部分并发送给保险公司的软件,在那里使用训练好的机器学习算法进行评估。在罕见且困难的情况下,图像需要由人工操作员仔细检查。这种工作流程节省了金钱和时间,并为处理索赔提供了更好的体验。
另一个应用是医学图像分类,其中放射学图像被自动分类以提供初步诊断,从而减轻医学专家(在这种情况下,是放射科医生)的负担。
遮罩图像通过使用过滤器来强调感兴趣的部分进行处理。最常见的是黑白过滤器或灰度过滤器。它们强调图像中明暗部分之间的差异,以便更容易地识别形状,然后对这些形状进行分类并追踪它们(在视频流的情况下)。这类应用通常用于感知系统——例如,在汽车中。
使用掩码图像的感知系统的一个实际应用是识别水平道路标记,如车道标记。车辆的摄像头拍摄汽车前方的道路图像,然后其软件对图像进行掩码处理,并将其发送到机器学习算法进行检测和分类。OpenCV是用于此类任务的一个库。其他实际应用包括人脸识别或光学字符识别(OCR)。
语义地图图像包括描述图像中可见内容的叠加,覆盖包含特定信息(如天空、汽车、人或建筑物)的部分图像。语义地图可以覆盖包含汽车、汽车所在的路面、周围环境和天空的图像部分。语义地图提供了关于图像的丰富信息,这些信息用于高级视觉感知算法,反过来,这些算法为决策算法提供信息。在自动驾驶汽车系统中,视觉感知尤其重要。
语义地图的一个应用领域是车辆主动安全系统。前摄像头捕捉到的图像通过卷积神经网络(CNNs)进行处理,添加语义地图,然后用于决策算法。这些决策算法要么向驾驶员提供反馈,要么自主采取行动。我们可以看到,当一辆车对另一辆车行驶过近或在其路径上检测到障碍物时,通常会有反应。
语义地图的其他应用包括医学图像分析,其中机器学习算法为医学专家提供有关图像内容的输入。一个例子是使用深度****CNNs(DCNNs)进行脑肿瘤分割。
最后,边界框图像包含有关图像中物体边界的信息。对于感兴趣的每个形状,如汽车、行人或肿瘤,都有一个围绕该图像部分的边界框,并标注该形状的类别。这类图像用于检测物体并将该信息提供给其他算法。
我们使用这类图像的一个应用是机器人协调系统中的物体识别。机器人的摄像头捕捉到一个图像,CNN 识别出物体,然后机器人的决策软件追踪该物体以避免碰撞。追踪每个物体用于改变自主机器人的行为,以降低碰撞和损坏的风险,以及优化机器人和其环境操作。
因此,本章的第一条最佳实践。
最佳实践 #14
设计整个软件系统应基于您需要解决的问题,而不仅仅是机器学习模型。
由于我们使用的每种算法都需要对图像进行不同的处理并提供不同类型的信息,因此我们需要了解如何围绕它创建整个系统。在前一章中,我们讨论了管道,它只包括机器学习数据管道,但一个软件系统需要更多。对于关键功能,我们需要设计安全笼和信号来降低机器学习模型错误分类/检测的风险。因此,我们需要了解我们想要做什么——信息是否仅用于做出简单决策(例如,汽车上的损坏保险杠与未损坏的情况)或者分类是否是复杂行为决策的一部分(例如,机器人应该向右转以避开障碍物,还是应该减速以让另一个机器人通过?)。
图像是我们用于机器学习的一种数据类型;另一种是文本。近年来,随着循环神经网络(RNNs)和转换器的引入,文本的使用变得流行。这些神经网络架构是深度学习网络,能够捕捉到词语的上下文(以及由此扩展的基本语义)。这些模型在标记(因此,词语)之间发现统计联系,因此可以识别出经典机器学习模型无法识别的相似性。机器翻译是这些模型最初的一个流行应用,但现在,应用范围比这更广——例如,在理解编程语言方面。图 3.2展示了可以与不同类型的模型一起使用的文本数据类型:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_2.jpg
图 3.2 – 学习系统的类型及其对文本数据的需求
原始文本数据目前用于训练word2vec模型,该模型将文本标记转换为数字向量——嵌入——这些向量是该标记与词汇表中其他标记的距离。我们在第二章中看到了一个例子,其中我们计算了句子中的单词数量。通过使用这项技术,word2vec模型捕捉到标记的上下文,也称为它们的相似性。这种相似性可以扩展到整个句子或段落,这取决于模型的大小和深度。
原始文本的另一个应用,尽管是以结构化格式,是在情感分析(SA)中。我们使用文本数据的表格格式来分析文本的情感是积极、消极还是中性。该任务的扩展是理解文本的意图——它是否是解释、查询还是描述。
掩码文本数据指的是在一系列标记中掩码一个或多个标记,并训练模型来预测该标记。这是一个自监督训练的例子,因为模型是在未标注的数据上训练的,但通过以不同的方式(例如,随机、基于相似性、人工标注)掩码标记,模型可以理解哪些标记可以在特定上下文中使用。模型越大——即 transformer——需要的数据就越多,并且需要更复杂的训练过程。
最后,标注文本指的是当我们用特定的类别标记文本片段,就像图像一样。这种标注的一个例子是情感。然后,模型捕捉数据中的模式,因此可以重复这些模式。这个领域的一个任务示例是情感识别,其中模型被训练来识别文本的语气是积极还是消极。
文本数据的一个特殊情况是编程语言源代码。在过去的几年里,使用 ML 模型进行编程语言任务变得越来越流行,因为它提供了提高软件开发速度和质量的可能。图 3.3展示了编程语言数据类型和典型任务:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_3.jpg
图 3.3 – 编程语言数据类型和典型任务
原始源代码数据用于与编程语言理解相关的任务——例如,使用 TransCoder 模型在不同编程语言之间的翻译。这项任务类似于自然语言之间的翻译,尽管它增加了额外的步骤来使程序编译并通过测试用例。
掩码编程语言代码通常用于训练旨在修复缺陷的模型——模型是在一组纠正缺陷的程序上训练的,然后应用于有缺陷的程序。掩码程序用于训练能够识别问题并提供修复方案的模型。在撰写本书时,这些任务相当实验性,但结果非常令人鼓舞。
标注源代码用于各种任务。这些任务包括缺陷预测、代码审查以及识别设计模式或公司特定的设计规则。与静态代码分析工具等其他技术相比,ML 模型在这些任务上提供了更好的结果——例如,与静态代码分析工具相比。
源代码用于训练用于高级软件工程任务的模型,例如创建程序。GitHub Copilot 就是这样一种工具,它在研究和商业应用中都取得了巨大的成功。
现在,上述三种类型的数据仅展示了 ML 应用的一小部分。对于那些想要利用 ML 模型设计软件系统的人来说,天空才是极限。然而,在设计系统之前,我们需要更详细地了解我们如何与数据打交道。
图像
原始图像数据通常存储在包含在其他文件中的注释的文件中。原始图像数据呈现与所讨论系统相关的方面。用于训练主动安全算法的数据示例在图 3.4中展示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_4.jpg
图 3.4 – 车辆的前置摄像头图像
在这个例子中,使用带有汽车的图像来训练 CNN 以识别是否安全驾驶(例如,前方道路是否无障碍)。当在图像级别上标注数据时——即没有蒙版和边界框——机器学习模型可以分类整个图像或识别对象。在识别对象时,模型会将边界框信息添加到图像中。
为了训练一个用于包含许多显著大小对象的图像(例如 1920 x 1080 像素的高清分辨率)的 CNN,我们需要大量的数据集和计算资源。这有几个原因。
首先,颜色需要大量数据才能正确识别。尽管我们人类将红色视为几乎均匀,但实际上该颜色的像素强度变化很大,这意味着我们需要创建一个 CNN,使其能够理解不同深度的红色有时对于识别制动车辆很重要。
其次,图像的大尺寸包含了不相关的细节。图 3.5展示了 CNN 的设计方式。这是一个 LeNet 风格的 CNN:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_5.jpg
图 3.5 – CNN 的概念设计
图 3.5显示 NN 以 192 x 108 像素大小的图像(比高清图像小 10 倍)作为输入。然后它使用MaxPool层(例如)来减少元素数量,然后使用卷积来识别形状。最后,它使用两个密集层将图像分类为 64 个不同类别的向量。图像的大小决定了网络的复杂性。图像越大,所需的卷积就越多,第一层也越大。更大的网络需要更多的时间来训练(差异可能以天计)并且需要更多的数据(差异可能以成千上万张图像计,取决于类别的数量和图像的质量)。
因此,对于许多应用,我们使用灰度图像并将它们显著缩小。图 3.6显示了之前相同的图像,但以灰度显示,缩小到 192 x 108 像素。图像的大小已经显著减小,因此对第一卷积层的要求也减少了:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_6.jpg
图 3.6 – 黑白转换图像(故意展示的低质量有损转换)
然而,图像中的物体仍然非常清晰可见,可以用于进一步分析。因此,这里是下一个最佳实践。
最佳实践#15
缩小图像的大小并尽可能使用较少的颜色,以减少系统的计算复杂度。
在设计系统之前,我们需要了解我们有哪些类型的图像以及我们如何使用它们。然后,我们可以执行这些类型的转换,以便我们设计的系统能够处理其设计的目标任务。然而,需要注意的是,缩小图像也可能导致信息丢失,这可能会影响机器学习模型的准确性。在决定如何预处理图像以用于机器学习任务时,重要的是要仔细权衡计算复杂度和信息丢失之间的权衡。
在机器学习中,缩小图像并将其转换为灰度是一个常见的做法。实际上,存在几个广为人知且广泛使用的基准数据集,它们使用了这种技术。其中一个就是手写数字的MNIST数据集。该数据集可以作为最受欢迎的机器学习库(如 TensorFlow 和 Keras)的一部分下载。只需使用以下代码即可获取图像:
# import the Keras library that contains the MNIST dataset
from keras.datasets import mnist
# load the dataset directly from the Keras website
# and use the standard train/test splits
(X_train, Y_train), (X_test, Y_test) = mnist.load_data()
# import a Matplot library to plot the images
from matplotlib import pyplot
# plot first few images
for i in range(9):
# define subplot to be 330 pixels wide
pyplot.subplot(330 + 1 + i)
# plot raw pixel data
pyplot.imshow(X_train[i],
cmap=pyplot.get_cmap('gray'))
# show the figure
pyplot.show()
该代码说明了如何下载数据集,该数据集已经分为测试和训练数据,并带有注释。它还展示了如何可视化数据集,这导致了图 3.7中看到的图像:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_7.jpg
图 3.7 – MNIST 数据集中前几个图像的可视化;有意将图像转换为位图以展示其实际大小
MNIST 数据集中图像的大小是 28 x 28 像素,这对于训练和测试新的机器学习模型来说已经足够完美。尽管该数据集在机器学习中广为人知并被使用,但它相对较小且均匀——只有灰度数字。因此,对于更高级的任务,我们应该寻找更多样化的数据集。
手写数字的图像自然是有用的,但我们通常希望使用更复杂的图像,因此,标准库中包含的图像不仅仅只有 10 个类别(数字的数量)。其中一个这样的数据集是 Fashion-MNIST 数据集。
我们可以使用以下代码下载它:
from keras.datasets import fashion_mnist
(X_train, Y_train), (X_test, Y_test)=fashion_mnist.load_data()
该代码可以用于生成图 3.7中所示的可视化,它产生了图 3.8中看到的图像集合。图像大小和类别数量相同,但复杂度更大:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_8.jpg
图 3.8 – Fashion-MNIST 数据集;有意将图像转换为位图以展示其实际大小
最后,我们还可以使用包含彩色图像的库,例如 CIFAR-10 数据集。可以使用以下代码访问该数据集:
from keras.datasets import cifar10
# load dataset
(X_train, Y_train), (X_test, Y_test)== cifar10.load_data()
该数据集包含 10 个不同类别的图像,大小相似(32 x 32 像素),但带有颜色,如图 图 3.9 所示。
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_9.jpg
图 3.9 – CIFAR-10 数据集;图像有意进行光栅化以展示其实际大小
这并不是这些基准数据集的终点。一些数据集包含更多类别、更大的图像,或者两者都有。因此,在系统必须执行的任务之前和期间查看这些数据集是很重要的。
在大多数情况下,灰度图像对于分类任务来说已经足够好了。它们能够快速让我们在数据中找到方向,而且它们足够小,以至于分类质量良好。
基准数据集的通常大小约为 50,000–100,000 张图像。这表明,即使是如此小的类别数量和如此小的图像,数量也是相当大的。想象一下标注那 100,000 张图像。对于更复杂的图像,数据集的大小可以显著更大。例如,在汽车软件中使用的 BDD100K 数据集包含超过 100,000 张图像。
因此,这是我的下一个最佳实践。
最佳实践 #16
使用参考数据集(如 MNIST 或 STL)来基准测试系统是否工作。
为了理解整个系统是否工作,这样的基准数据集非常有用。它们为我们提供了一个预配置的训练/测试分割,并且有许多算法可以用来理解我们算法的质量。
我们还应该考虑下一个最佳实践。
最佳实践 #17
在可能的情况下,使用已经为特定任务预训练的模型(例如,用于图像分类或语义分割的神经网络模型)。
正如我们应该努力重复使用图像进行基准测试一样,我们也应该努力重复使用预训练的模型。这节省了之前的设计资源,并减少了花费太多时间寻找神经网络模型的最佳架构或最佳参数集(即使我们使用 GradientSearch 算法)的风险。
文本
在对文本进行的分析类型中,SA 是其中之一——对文本片段(例如句子)是否为正面或负面的分类。
图 3.10 展示了可用于 SA 的数据示例。这些数据是公开可用的,并且是从亚马逊产品评论中创建的。这类分析的数据通常以表格形式结构化,其中包含诸如 ProductId(为了简洁,我已截断 Id 列)或 UserId 这样的实体,以及用于参考的 Score 和用于分类的 Text。
这种数据结构为我们提供了快速总结文本和可视化的可能性。可视化可以通过多种方式进行——例如,通过绘制得分的直方图。然而,最有趣的视觉化是那些由文本中使用的单词/标记的统计信息提供的:
| Id | ProductId | UserId | Score | 摘要 | 文本 |
|---|---|---|---|---|---|
| 1 | B001KFG0 | UHU8GW | 5 | 良好的狗粮质量 | 我购买了几种 Vitality 罐装狗粮产品,并发现它们的质量都很好。产品看起来更像炖菜而不是加工肉类,而且气味更好。我的拉布拉多很挑食,她比大多数狗更喜欢这个产品。 |
| 2 | B008GRG4 | ZCVE5NK | 1 | 不如广告所述 | 产品到达时标明为巨型盐味花生…实际上花生是小型未加盐的。不确定这是否是一个错误,还是供应商有意将产品标为“巨型”。 |
| 3 | B000OCH0 | WJIXXAIN | 4 | “满意”就足够了 | 这是一种存在了几百年的糖果。它是一种轻盈、蓬松的柑橘果冻,里面有坚果——在这种情况下是榛子。然后切成小块,然后大量裹上糖粉。这是一口小小的天堂。不太嚼劲,非常美味。我强烈推荐这种美味的小吃。如果你熟悉 C.S.路易斯的《狮子、女巫和魔衣橱》的故事——这就是诱惑埃德蒙向女巫出卖他的兄弟姐妹的糖果。 |
| 4 | B000A0QIQ | C6FGVXV | 2 | 咳嗽药水 | 如果你正在寻找 Robitussin 的秘密成分,我相信我已经找到了。我除了订购的根啤酒提取物(很好)外,还做了一些樱桃汽水。味道非常药性。 |
| 5 | B0062ZZ7K | LF8GW1T | 5 | 优秀的太妃糖 | 价格合理的优秀太妃糖。有各种各样的美味太妃糖。送货非常快。如果你是太妃糖爱好者,这是一个划算的交易。 |
图 3.10 – 产品评论数据示例,为 SA 结构化;仅显示前五行
一种可视化数据的方法是使用词云可视化技术。以下是一个用于可视化此类数据的简单脚本的示例:
# Create stopwords list:
stopwords = set(STOPWORDS)
stopwords.update(["br", "href"])
# create text for the wordcloud
textt = " ".join(review for review in dfRaw.Text)
# generation of wordcloud
wordcloud = WordCloud(stopwords=stopwords,
max_words=100,
background_color="white").generate(textt)
# showing the image
# and saving it to the png file
plt.figure(figsize = [12,9])
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis("off")
plt.show()
运行此脚本的输出结果如图3.11所示。一个词云显示了单词使用频率的趋势——使用频率较高的单词比使用频率较低的单词大:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_10.jpg
图 3.11 – 文本列的词云可视化
因此,我的下一个最佳实践如下。
最佳实践 #18
将你的原始数据可视化,以了解数据中的模式。
数据的可视化对于理解潜在模式非常重要。这一点我无法强调得更多。我既使用 Python 的 Matplotlib 和 Seaborn,也使用 TIBCO Spotfire 等可视化分析工具来绘制图表并理解我的数据。没有这种可视化,以及没有对模式的这种理解,我们必然会得出错误的结论,甚至设计出需要完全重新设计才能移除缺陷的系统。
更高级文本处理输出的可视化
文本的可视化帮助我们理解文本包含的内容,但它并不捕捉其含义。在这本书中,我们将使用高级文本处理算法——特征提取器。因此,我们需要了解如何创建这些算法输出的可视化。
与特征提取一起工作的一种方法是用词嵌入——一种将单词或句子转换为数字向量的方法。word2vec是能够做到这一点的模型之一,但还有更强大的模型。OpenAI 的 GPT-3 模型是公开可用的最大模型之一。获取段落嵌入相当直接。首先,我们连接到 OpenAI API,然后查询它以获取嵌入。以下是查询 OpenAI API 的代码(加粗):
# first we combine the title and the content of the review
df['combined'] = "Title: " + df.Summary.str.strip() + "; Content: " + df.Text.str.strip()
# we define a function to get embeddings, to make the code more straightforward
def get_embedding(text, engine="text-similarity-davinci-001"):
text = text.replace("\n", " ")
return openai.Embedding.create(input = [text], engine=engine)['data'][0]['embedding']
# and then we get embeddings for the first 5 rows
df['babbage_similarity'] = df.head(5).combined.apply(lambda x: get_embedding(x, engine='text-similarity-babbage-001'))
通过运行这段代码,我们获得了5个向量(每个行一个)的2048个数字,我们称之为嵌入。整个向量太大,无法包含在页面上,但前几个元素看起来像这样:[-0.005302980076521635, 0.018141526728868484, -0.018141526728868484, 0.004692177753895521, …]。
这些数字对我们人类来说意义不大,但对语言模型来说却有着意义。意义在于它们之间的距离——相似度高的单词/标记/句子比不相似的单词/标记/句子更接近。为了理解这些相似性,我们使用降低维度的转换——其中之一是t 分布随机邻域嵌入(t-SNE)。图 3.12展示了我们获得的五个嵌入的这种可视化:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_11.jpg
图 3.12 – 五个评论嵌入向量的 t-SNE 可视化
每个点代表一条评论,每个十字代表簇的中心。簇由原始数据集中的Score列指定。截图显示,每条评论中的文本都不同(点不重叠),并且簇是分开的——十字在截图的不同部分。
因此,我接下来的最佳实践就是关于这个。
最佳实践#19
当数据被转换为特征以监控是否仍可观察到相同模式时,请可视化您的数据。
就像之前的最佳实践一样,我们需要可视化数据以检查我们在原始数据中观察到的模式是否仍然可观察。这一步很重要,因为我们需要知道机器学习模型确实可以学习这些模式。由于这些模型本质上是统计的,它们总是捕捉到模式,但当模式不存在时,它们捕捉到的可能是无用的东西 – 即使它看起来像是一个模式。
结构化文本 – 程序的源代码
程序的源代码是文本数据的一个特殊情况。它具有相同类型的模态 – 文本 – 但它包含程序语法/句法结构形式的附加信息。由于每种编程语言都基于语法,因此程序的结构化都有特定的规则。例如,在 C 语言中,应该有一个名为 main 的特定函数,它是程序的入口点。
这些特定的规则使得文本以特定的方式结构化。它们可能使人类理解文本变得更加困难,但这种结构确实非常有帮助。使用这种结构的一个模型是 code2vec (code2vec.org/)。code2vec 模型与 word2vec 类似,但它以输入它所分析程序的 抽象语法树 (AST) – 例如,以下程序:
void main()
{
Console.println("Hello World");
}
这可以通过 图 3*.13* 中的 AST 来表示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_12.jpg
图 3.13 – 简单“Hello World”程序的 AST
示例程序被可视化为一组指令及其上下文以及它们在程序中扮演的角色。例如,void 和 main 是方法声明的一部分,与块语句(BlockStmt)一起,构成了方法的主体。
code2vec 是一个使用编程语言信息(在这种情况下,是语法)作为模型输入的模型的例子。模型可以执行的任务包括查找单词之间的相似性(如 word2vec 模型)、查找组合以及识别类比。例如,模型可以识别 int 和 main 这两个单词的所有组合,并提供以下答案(带有概率):realMain(71%),isInt(71%),和 setIntField(69%)。通过扩展,这些任务可以用于程序修复,其中模型可以识别错误并修复它们。
然而,使用 AST 或类似信息也有缺点。主要缺点是分析程序必须编译。这意味着我们无法在想要分析不完整程序的环境中使用这些类型的模型 – 例如,在 持续集成 (CI) 或现代代码审查的环境中。当我们只分析代码的一小部分时,模型无法解析它,获取其 AST,并使用它。因此,这是我的下一个最佳实践。
最佳实践 #20
只将必要的信息作为输入提供给机器学习模型。过多的信息可能需要额外的处理,并使训练难以收敛(完成)。
在设计处理流程时,确保提供给模型的信息是必要的,因为每一条信息都对整个软件系统提出新的要求。例如,在抽象语法树(AST)的例子中,当它是必要的,它就是强大的信息,但如果不可用,它可能会成为数据分析流程工作的巨大障碍。
每种数据都有其目的 – 标注和任务
原始格式的数据很重要,但只是机器学习软件开发和运营的第一步。最重要的是数据标注,这也是成本最高的部分。为了训练机器学习模型并使用它进行推理,我们需要定义一个任务。定义任务既是概念性的也是操作性的。概念性定义是定义我们希望软件做什么,而操作性定义是我们希望如何实现这一目标。操作性定义归结为对我们在数据中看到的内容以及我们希望机器学习模型识别/复制的定义。
标注是我们指导机器学习算法的机制。我们使用的每一条数据都需要某种标签来表示其内容。在数据的原始格式中,这种标注可以是数据点包含的内容的标签。例如,这样的标签可以是图像包含数字 1(来自 MNIST 数据集)或汽车(来自 CIFAR-10 数据集)。然而,这些简单的标注在专用任务中很重要。对于更高级的任务,标注需要更丰富。
这类标注的一种类型与我们在数据中指定部分数据为有趣的部分相关。在图像的情况下,这是通过在感兴趣的对象周围绘制边界框来完成的。图 3.14 展示了这样的图像:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_13.jpg
图 3.14 – 带有边界框的图像
图像包含围绕我们希望模型识别的元素的框。在这种情况下,我们希望识别车辆(绿色框)、其他道路使用者(橙色框)和重要的背景对象(灰色框)。这类标注用于使机器学习模型学习形状,并在新对象中识别这些形状。在这个例子中,边界框识别了对于汽车主动安全系统重要的元素,但这不是唯一的用途。
这种边界框的其他应用包括医学图像分析,其任务是识别需要进一步分析的组织。这些也可以是面部识别和物体检测的系统。
虽然这个任务和边界框可以被视为标注原始数据的一个特殊情况,但它略有不同。每个框都可以被视为一个带有标签的独立图像,但挑战在于每个框的大小都不同。因此,使用这种不同形状的图像将需要预处理(例如,重新缩放)。它也仅适用于训练,因为在推理中,我们需要在分类之前识别对象——这正是我们需要神经网络为我们完成的任务。
我使用这类数据的最佳实践将在下面列出。
最佳实践 #21
当任务需要检测和跟踪对象时,在数据中使用边界框。
由于边界框使我们能够识别对象,因此这些数据的自然用途是在跟踪系统中。一个示例应用是使用摄像头监控停车位的系统。它检测停车位并跟踪是否有车辆停在该位置。
对象检测任务的扩展是感知任务,其中我们的机器学习软件需要根据数据的上下文或情况做出决策。
对于图像数据,这种上下文可以通过语义地图来描述。图 3.15 展示了这样一个地图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_14.jpg
图 3.15 – 带有语义地图的图像;建筑是语义地图的一个标签
截图显示了覆盖特定类型对象的多种颜色叠加。橙色叠加显示车辆,紫色叠加显示易受伤害的道路使用者,在本图中即为行人。最后,粉色表示建筑,红色覆盖背景/天空。
语义地图比边界框提供了更多的灵活性(因为某些对象比其他对象更有趣),并允许机器学习系统获取图像的上下文。通过识别图像中存在哪些类型的元素,机器学习模型可以为我们设计的软件系统的决策算法提供有关图像拍摄位置的信息。
因此,这是我的下一个最佳实践。
最佳实践 #22
当你需要获取图像的上下文或需要特定区域的详细信息时,使用语义地图。
语义地图需要大量计算才能有效使用;因此,我们应该很少使用它们。当我们有与上下文相关的任务时,例如感知算法或图像修改——例如,改变图像中天空的颜色——我们应该使用这些地图。关于信息的准确性,一般来说,语义地图需要大量计算,因此是选择性使用的。一个进行此类语义映射的工具示例是 Segments.ai。
语义地图在需要理解图像的上下文或特定区域的细节时非常有用。例如,在自动驾驶中,语义地图可以用来识别道路上的物体及其相互关系,从而使车辆能够就其移动做出明智的决定。然而,语义地图的具体应用案例可能因应用而异。
为意图识别标注文本
我们之前提到的 SA 只是文本数据标注的一种类型。它有助于评估文本是正面还是负面。然而,我们不是用情感标注文本,而是可以用——例如——意图来标注文本,并训练一个机器学习模型从其他文本段落中识别意图。图3.16中的表格提供了这样的标注,基于之前的相同评论数据:
| Id | Score | Summary | Text | Intent |
|---|---|---|---|---|
| 1 | 5 | 高质量狗粮 | 我购买了几个 Vitality 罐装狗粮产品,并发现它们的质量都很好。产品看起来更像炖菜而不是加工肉类,而且味道更好。我的拉布拉多很挑食,她比大多数狗更喜欢这个产品。 | 广告 |
| 2 | 1 | 不如广告所说 | 产品到达时标有“巨型盐味花生”……实际上花生是小型无盐的。不确定这是否是一个错误,还是卖家有意将产品标为“巨型”。 | 批评 |
| 3 | 4 | “快乐”一词已尽其意 | 这是一种存在了几百年的糖果。它是一种轻盈、蓬松的柑橘果冻,里面有坚果——在这种情况下是榛子。然后它被切成小块,并大量裹上糖粉。这是一口小小的天堂。不太嚼劲,非常美味。我强烈推荐这种美味的点心。如果你熟悉 C.S.路易斯的《狮子、女巫和魔衣橱》的故事——这就是诱惑爱德蒙背叛他的兄弟姐妹给女巫的点心。 | 描述 |
| 4 | 2 | 咳嗽 | ||
| 药品 | 如果你正在寻找 Robitussin 的秘诀成分,我相信我已经找到了。我除了订购的根啤酒提取物(味道不错)外,还制作了一些樱桃汽水。味道非常像药。 | 批评 | ||
| 5 | 5 | 优秀的太妃糖 | 价格合理的优秀太妃糖。有各种各样的美味太妃糖。送货非常快。如果你是太妃糖爱好者,这是一个划算的交易。 | 广告 |
图 3.16 – 用于意图识别的文本数据标注
表格的最后一列显示了文本的注释——意图。现在,我们可以使用意图作为标签来训练一个机器学习模型以识别新文本中的意图。通常,这项任务需要两步方法,如图3.17所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_15.jpg
图 3.17 – 基于文本训练模型的两个步骤方法
标注的文本被组织成两部分。第一部分是文本本身(例如,我们示例表中的Text列),第二部分是标注(例如,我们示例中的Intent列)。文本使用如 word2vec 模型或 transformer 等模型进行处理,将文本编码为向量或矩阵。标注使用如 one-hot 编码等技术编码为向量,以便它们可以作为分类算法的决策类别。分类算法接受编码的标注和向量化的文本。然后,它被训练以找到向量化文本(X)与标注(Y)的最佳匹配。
这里是我的最佳实践,如何执行这项操作。
最佳实践#23
使用预训练的嵌入模型,如 GPT-3 或现有的 BERT 模型来向量化您的文本。
根据我的经验,如果我们使用预定义的语言模型来向量化文本,那么处理文本通常会更简单。Hugging Face网站(www.huggingface.com)是这些模型的优秀来源。由于 LLMs 需要大量的资源来训练,现有的模型通常对于大多数任务来说已经足够好了。由于我们将在管道的下一步开发分类器模型,我们可以集中精力使该模型更好,并与我们的任务保持一致。
文本数据的另一种标注类型是关于词性(POS)的上下文。它可以被视为在图像数据中使用的语义图。每个词都被标注,无论它是名词、动词还是形容词,无论它属于句子的哪个部分。这种标注的一个示例可以通过使用艾伦研究所的 AllenNLP 语义角色标注(SRL)工具(demo.allennlp.org/semantic-role-labeling)进行视觉展示。图 3.18展示了简单句子的此类标注截图,而图 3.19展示了更复杂句子的标注:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_16.jpg
图 3.18 – 使用 AllenNLP 工具集进行 SRL
在这个句子中,每个词的作用都被强调,我们可以看到有三个具有不同关联的动词——最后一个动词是主要的,因为它将句子的其他部分联系起来:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_03_17.jpg
图 3.19 – 更复杂句子的 SRL
复杂句子具有更大的语义角色框架,因为它包含句子的两个不同部分。我们使用这种角色标注来提取文本段落的意义。这在设计基于所谓基于事实的模型的软件系统时尤其有用,这些模型会检查信息是否与事实相符。这些模型解析文本数据,找到正确的锚点(例如,问题是什么),并在它们的数据库中找到相关的答案。这些与非基于事实的模型相对,这些模型基于哪个词最适合完成句子来创建答案——例如,ChatGPT。
因此,我的最佳实践如下。
最佳实践#24
在设计需要提供基于事实的决策的软件时,使用角色标签。
基于事实的决策通常更难提供,因为模型需要理解句子的上下文,捕捉其意义,并提供相关的答案。然而,这并不总是必需的,甚至可能不是所希望的。非基于事实的模型对于可以由专家稍后修正的建议通常已经足够好。ChatGPT 这样的软件工具就是一个例子,它提供的答案有时是不正确的,需要人工干预。然而,它们是一个非常好的起点。
在可以使用不同类型的数据一起使用的地方——对多模态数据模型的展望
本章介绍了三种类型的数据——图像、文本和结构化文本。这三种类型的数据是数值形式的数据的例子,如数字矩阵,或时间序列的形式。然而,无论形式如何,与数据和机器学习系统一起工作是非常相似的。我们需要从源系统中提取数据,然后将其转换为我们可以注释的格式,然后将其用作机器学习模型的输入。
当我们考虑不同类型的数据时,我们可以开始思考是否可以在同一个系统中使用两种类型的数据。有几种方法可以实现这一点。第一种是在不同的管道中使用不同的机器学习系统,但我们连接了这些管道。GitHub Copilot 就是这样一种系统。它使用一个管道来处理自然语言,以找到类似的程序并将它们转换成适合当前正在开发的程序上下文。
另一个例子是生成图像文本描述的系统。它接受一个图像作为输入,识别其中的对象,然后基于这些对象生成文本。文本的生成是通过一个与图像分类完全不同的机器学习模型完成的。
然而,有一些新模型在同一个神经网络中使用两种不同的模态——图像和文本——例如,Gato 模型。通过使用来自两个来源的输入,并在中间使用一个非常窄(就神经元数量而言)的网络,该模型被训练以泛化由两种不同模态描述的概念。这样,该模型被训练以理解,如果不在完全相同的位置,猫的图像和单词“猫”应该被放置在非常接近的同一嵌入空间中。尽管仍然处于实验阶段,但这些类型的网络旨在模仿人类对概念的理解。
在下一章中,我们将更深入地探讨数据理解,通过深入研究特征工程的过程。
参考文献
-
Tao, J. et al., An object detection system based on YOLO in traffic scene. In 2017 6th International Conference on Computer Science and Network Technology (ICCSNT). 2017. IEEE.
-
Artan, C.T. and T. Kaya, Car Damage Analysis for Insurance Market Using Convolutional Neural Networks. In International Conference on Intelligent and Fuzzy Systems. 2019. Springer.
-
Nakaura, T. et al., A primer for understanding radiology articles about machine learning and deep learning. Diagnostic and Interventional Imaging, 2020. 101(12): p. 765-770.
-
Bradski, G., The OpenCV Library. Dr. Dobb’s Journal: Software Tools for the Professional Programmer, 2000. 25(11): p. 120-123.
-
Memon, J. et al., Handwritten optical character recognition (OCR): A comprehensive systematic literature review (SLR). IEEE Access, 2020. 8: p. 142642-142668.
-
Mosin, V. et al., Comparing autoencoder-based approaches for anomaly detection in highway driving scenario images. SN Applied Sciences, 2022. 4(12): p. 1-25.
-
Zeineldin, R.A. et al., DeepSeg: deep neural network framework for automatic brain tumor segmentation using magnetic resonance FLAIR images. International journal of computer assisted radiology and surgery, 2020. 15(6): p. 909-920.
-
Reid, R. et al., Cooperative multi-robot navigation, exploration, mapping and object detection with ROS. In 2013 IEEE Intelligent Vehicles Symposium (IV). 2013. IEEE.
-
Mikolov, T. et al., Recurrent neural network based language model. In Interspeech. 2010. Makuhari.
-
Vaswani, A. et al., Attention is all you need. Advances in neural information processing systems, 2017. 30.
-
Ma, L. and Y. Zhang, Using Word2Vec to process big text data. In 2015 IEEE International Conference on Big Data (Big Data). 2015. IEEE.
-
Ouyang, X. et al., Sentiment analysis using convolutional neural network. In 2015 IEEE International Conference on Computer and Information Technology; ubiquitous computing and communications; dependable, autonomic and secure computing; pervasive intelligence and computing. 2015. IEEE.
-
Roziere, B. et al., Unsupervised translation of programming languages. Advances in Neural Information Processing Systems, 2020. 33: p. 20601-20611.
-
Yasunaga, M. and P. Liang, Break-it-fix-it: Unsupervised learning for program repair. In International Conference on Machine Learning. 2021. PMLR.
-
Halali, S. et al., Improving defect localization by classifying the affected asset using machine learning. In International Conference on Software Quality. 2019. Springer.
-
Ochodek, M. et al., Recognizing lines of code violating company-specific coding guidelines using machine learning. In Accelerating Digital Transformation. 2019, Springer. p. 211-251.
-
Nguyen, N. and S. Nadi, An empirical evaluation of GitHub copilot’s code suggestions. In Proceedings of the 19th International Conference on Mining Software Repositories. 2022.
-
Zhang, C.W. et al., Pedestrian detection based on improved LeNet-5 convolutional neural network. Journal of Algorithms & Computational Technology, 2019. 13: p. 1748302619873601.
-
LeCun, Y. et al., Gradient-based learning applied to document recognition. Proceedings of the IEEE, 1998. 86(11): p. 2278-2324.
-
Xiao, H., K. Rasul, and R. Vollgraf, Fashion-MNIST: a novel image dataset for benchmarking machine learning algorithms. arXiv preprint arXiv:1708.07747, 2017.
-
Recht, B. et al., Do CIFAR-10 classifiers generalize to CIFAR-10? arXiv preprint arXiv:1806.00451, 2018.
-
*Robert, T., N. Thome, and M. Cord, HybridNet: Classification and reconstruction cooperation for semi-supervised learning. In Proceedings of the European Conference on Computer Vision (*ECCV). 2018.
-
Yu, F. et al., Bdd100k: A diverse driving video database with scalable annotation tooling. arXiv preprint arXiv:1805.04687, 2018. 2(5): p. 6.
-
McAuley, J.J. and J. Leskovec, From amateurs to connoisseurs: modeling the evolution of user expertise through online reviews. In Proceedings of the 22nd International Conference on World Wide Web. 2013.
-
Van der Maaten, L. and G. Hinton, Visualizing data using t-SNE. Journal of Machine Learning Research, 2008. 9(11).
-
Sengupta, S. et al., Automatic dense visual semantic mapping from street-level imagery. In 2012 IEEE/RSJ International Conference on Intelligent Robots and Systems. 2012. IEEE.
-
Palmer, M., D. Gildea, and N. Xue, Semantic role labeling. Synthesis Lectures on Human Language Technologies, 2010. 3(1): p. 1-103.
-
Reed, S. et al., A generalist agent. arXiv preprint arXiv:2205.06175, 2022.
第四章:数据采集、数据质量和噪声
机器学习系统的数据可以直接来自人类和软件系统——通常称为源系统。数据的来源对其外观、质量以及如何处理它都有影响。
来自人类的数据通常比来自软件系统的数据更嘈杂。我们人类以小的不一致性而闻名,我们也可以不一致地理解事物。例如,两个人报告的同一缺陷可能有非常不同的描述;对于需求、设计和源代码也是如此。
来自软件系统的数据通常更一致,包含的噪声更少,或者数据中的噪声比人类生成数据的噪声更规律。这些数据由源系统生成。因此,控制和监控自动生成数据的品质是不同的——例如,软件系统不会在数据中“撒谎”,因此检查自动生成数据的可信度是没有意义的。
在本章中,我们将涵盖以下主题:
-
数据的不同来源以及我们可以如何利用它们
-
如何评估用于机器学习的数据的品质
-
如何识别、测量和减少数据中的噪声
数据来源以及我们可以如何利用它们
机器学习软件在当今所有领域都变得越来越重要。从电信网络、自动驾驶汽车、计算机游戏、智能导航系统、面部识别到网站、新闻制作、电影制作和实验音乐创作,都可以使用机器学习来完成。一些应用在例如使用机器学习进行搜索字符串(BERT 模型)方面非常成功。一些应用则不太成功,例如在招聘过程中使用机器学习。通常,这取决于在这些应用中使用到的程序员、数据科学家或模型。然而,在大多数情况下,机器学习应用的成功往往取决于用于训练和使用的训练数据。它取决于数据的品质以及从中提取的特征。例如,亚马逊的机器学习推荐系统被停用,因为它对女性存在偏见。由于它是基于历史招聘数据训练的,而这些数据主要包含男性候选人,因此系统倾向于在未来的招聘中推荐男性候选人。
用于机器学习系统的数据可以来自各种来源。然而,我们可以将这些来源分为两大类——人工/人类和自动化软件/硬件。这两类具有不同的特征,决定了如何组织这些来源以及如何从这些来源的数据中提取特征。图 4.1展示了这些数据类型并提供了每种类型数据的示例。
手动生成数据是指来自人类输入或起源于人类的数据。这类数据通常比软件生成数据信息量更丰富,但变异性也更大。这种变异性可能来自我们人类的自然变异性。例如,在表格中提出的问题,两个人可能会有不同的理解和回答。这类数据通常比系统性错误更容易受到随机错误的影响。
自动生成数据起源于硬件或软件系统,通常由传感器或测量脚本收集来自其他系统、产品、流程或组织的数据。这类数据通常更一致、可重复,但也更容易受到系统性错误的影响:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_04_1.jpg
图 4.1 – 数据来源及其分类。此图中的绿色部分是本书的范围
人类起源的数据示例,常用于机器学习的是关于软件缺陷的数据。人类测试员通常会检查问题并使用表格报告。这个表格包含有关测试阶段、受影响的组件、对客户的影响等内容,但表格的一部分通常是问题描述的自然语言描述,这是由人类测试员对发生情况的一种解释。
另一种由人类生成数据是源代码。作为程序员,我们使用给定语法的编程语言编写软件源代码,并使用编程指南来保持一致的风格,以便我们的工作成果——源代码——可以被软件自动解释或编译。我们编写的代码中存在一些结构,但远非一致。即使是相同的算法,当由两位不同的程序员实现时,也会在变量命名、类型或解决问题的方法上有所不同。一个很好的例子是 Rosetta 代码网站,它提供了不同编程语言中的相同解决方案,有时甚至在同一编程语言中。
使用表格进行的需求规范或数据输入具有相同的属性和特征。
然而,有一种数据来源特别有趣——医疗数据。这是来自患者记录和图表的数据,由医疗专家作为医疗程序的一部分输入。这些数据可以是电子的,但它反映了专家对症状的理解以及他们对医疗测试和诊断的解释。
另一方面,我们还有由软件或硬件以某种方式生成的数据。自动生成的数据更一致,尽管不是没有问题,并且更重复。此类数据的例子是电信网络中生成以从一电信节点传输信息到另一电信节点。与其它类型的数据相比,无线电信号非常稳定,可能会受到外部因素(如降水)或障碍物(如建筑起重机)的干扰。数据是可重复的,所有变异性都源于外部因素。
另一个例子是来自车辆的数据,这些数据记录了它们周围的信息并存储以供进一步处理。这些数据可以包含车辆组件之间的信号以及与其他车辆或基础设施的通信。
医疗数据,例如脑电图(EEG——即脑电波)或心电图(ECG——即心率),是从源系统中收集的,我们可以将其视为测量仪器。因此,从技术上讲,这些数据是由计算机系统生成的,但它们来自人类患者。这种来自患者的起源意味着数据具有与其他从人类收集的数据相同的自然变异性。由于每个患者都有所不同,测量系统可以以略微不同的方式连接到每个患者,因此每个患者生成的数据与其他患者略有不同。例如,心电图心跳数据包含基本、一致的信息——每分钟的跳动次数(以及其他参数)。然而,原始数据在心电图信号的幅度(取决于测量电极的位置)或曲线尖峰之间的差异(R 和 T 尖峰)上有所不同。
因此,本章我的第一个最佳实践与我们所使用的软件数据的来源有关。
最佳实践 #25
识别你软件中使用的数据的来源,并据此创建你的数据处理流程。
由于所有类型的数据在清洁、格式化和特征提取方面都需要不同的处理方式,我们应该确保我们知道数据是如何产生的,以及我们可以预期(并处理)哪些问题。因此,首先,我们需要确定我们需要什么类型的数据,它来自哪里,以及它可能携带哪些问题。
从软件工程工具中提取数据——Gerrit 和 Jira
为了说明如何进行数据提取,让我们从一款流行的代码审查软件工具——Gerrit 中提取数据。这个工具用于审查和讨论个人程序员在代码集成到产品主代码库之前开发的代码片段。
以下程序代码展示了如何通过 JSON API 访问 Gerrit 的数据库——也就是说,通过 JSON API ——以及如何提取特定项目的所有变更列表。此程序使用 Python pygerrit2 包 (pypi.org/project/pygerrit2/)。此模块帮助我们使用 JSON API,因为它提供 Python 函数而不是仅仅 JSON 字符串:
# importing libraries
from pygerrit2 import GerritRestAPI
# A bit of config - repo
gerrit_url = "https://gerrit.onap.org/r"
# since we use a public OSS repository
auth = None
# this line gets sets the parameters for the HTML API
rest = GerritRestAPI(url=gerrit_url, auth = auth)
# the main query where we ask the endpoint to provide us the list and details of all changes
# each change is essentially a review that has been submitted to the repository
changes = rest.get("/changes/?q=status:merged&o=ALL_FILES&o=ALL_REVISIONS&o=DETAILED_LABELS&start=0",
headers={'Content-Type': 'application/json'})
此代码片段中的关键行是 rest.get("/changes/?q=status:merged&o=ALL_FILES&o=ALL_REVISIONS&o=DETAILED_LABELS&start=0", headers={'Content-Type': 'application/json'})。这一行指定了要检索变更的端点以及参数。它表示我们想要访问所有文件和所有修订,以及所有变更的详细信息。在这些详细信息中,我们可以找到有关所有修订(特定的补丁/提交)的信息,然后我们可以解析这些修订中的每一个。重要的是要知道 JSON API 在每个查询中返回的最大变更数是 500,因此最后一个参数——start=0——可以用来访问从 500 开始的变更。此程序的输出是一个非常长的变更列表,以 JSON 格式,因此我不会在本书中详细展示。相反,我鼓励您执行此脚本,并根据自己的节奏浏览此文件。脚本可以在本书的 GitHub 仓库中找到,网址为 github.com/miroslawstaron/machine_learning_best_practices,在 第四章 下。脚本的名称是 gerrit_exporter.ipynb。
现在,仅提取变更列表对于分析来说并不很有用,因为它只提供了自动收集的信息——例如,哪些修订存在,以及谁创建了这些修订。它不包含有关哪些文件和行被注释的信息,或者注释是什么——换句话说,特别有用的信息。因此,我们需要与 Gerrit 进行更多交互,如 图 4*.2* 所示。
在 图 4*.2* 中展示的程序流程说明了代码注释数据库(如 Gerrit)中关系的复杂性。因此,访问数据库并导出这些数据的程序对于本书来说有点太长了。它可以在与之前相同的仓库中找到,名称为 gerrit_exporter_loop.ipynb:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_04_2.jpg
图 4.2 – 与 Gerrit 交互以提取注释文件、注释行和注释内容
这类数据可以用来训练机器学习模型以审查代码,甚至可以识别哪些代码行需要被审查。
在使用 Gerrit 的时候,我发现以下最佳实践非常有用。
最佳实践 # 26
提取您所需的所有数据并将其存储在本地,以减少使用该工具进行工作的软件工程师的干扰。
虽然可以逐个提取更改,但最好一次性提取整个更改集并保留其本地副本。这样,我们减轻了其他人日常工作中使用的服务器压力。我们必须记住,数据提取是这些源系统的次要任务,而它们的主要任务是支持软件工程师的工作。
另一个为支持软件工程任务的软件系统提供数据的好来源是 JIRA,一个问题和任务管理系统。JIRA 用于记录史诗、用户故事、软件缺陷和任务,并已成为此类活动最受欢迎的工具之一。
因此,我们可以从 JIRA 作为源系统提取大量有关流程的有用信息。然后,我们可以使用这些信息来开发机器学习模型,评估和改进任务和需求(以用户故事的形式),并设计帮助我们识别重叠的用户故事或将它们分组为更连贯的史诗的工具。这种软件可以用来提高这些任务的质量或提供更好的估计。
以下代码片段说明了如何连接到 JIRA 实例,然后如何提取特定项目的所有问题:
# import the atlassian module to be able to connect to JIRA
from atlassian import Jira
jira_instance = Jira(
#Url of the JIRA server
url="https://miroslawstaron.atlassian.net/",
# user name
username='email@domain.com',
# token
password='your_token',
cloud=True
)
# get all the tasks/ issues for the project
jql_request = 'project = MLBPB'
issues = jira_instance.jql(jql_request)
在这个片段中,为了说明目的,我正在使用自己的 JIRA 数据库和自己的项目(MLBPB)。此代码需要导入atlassian-python-api模块。此模块提供了一个 API,用于使用 Python 连接到并交互 JIRA 数据库,类似于 Gerrit 的 API。因此,适用于 Gerrit 的最佳实践也适用于 JIRA。
从产品数据库中提取数据——GitHub 和 Git
JIRA 和 Gerrit 在一定程度上是主要产品开发工具的补充工具。然而,每个软件开发组织都使用源代码仓库来存储主要资产——公司软件产品的源代码。如今,使用最频繁的工具是 Git 版本控制和它的近亲 GitHub。源代码仓库可以成为机器学习系统非常有用的数据来源——我们可以提取产品的源代码并对其进行分析。
如果我们负责任地使用 GitHub,它对机器学习来说是一个很好的数据来源。请记住,由社区提供的开源源代码不是为了盈利。我们需要遵守许可证,并承认开源社区作者、贡献者和维护者所做的贡献。无论许可证如何,我们总是能够分析我们自己的代码或我们公司的代码。
一旦我们可以访问我们产品或我们想要分析的产品源代码,以下代码片段帮助我们连接到 GitHub 服务器并访问存储库:
# First create a Github instance:
# using an access token
g = Github(token, per_page=100)
# get the repo for this book
repo = g.get_repo("miroslawstaron/machine_learning_best_practices")
# get all commits
commits = repo.get_commits()
为了支持对代码的安全访问,GitHub 在连接到它时使用访问令牌而不是密码。我们还可以使用 SSL 和 CLI 接口,但为了简单起见,我们将使用带有令牌的 HTTPS 协议。g = Github(token, per_page=100)这一行使用令牌来实例化 PyGitHub 库的主类。令牌是唯一的,需要为每个仓库或每个用户单独生成。
下一行代码通过repo = g.get_repo("miroslawstaron/machine_learning_best_practices")建立与仓库的连接,在这个例子中,它连接到与这本书相关的仓库。最后,代码片段中的最后一行获取仓库中的提交数量。一旦获取,我们可以打印它并开始分析,如下面的代码片段所示:
# print the number of commits
print(f'Number of commits in this repo: {commits.totalCount}')
# print the last commit
print(f'The last commit message: {commits[0].commit.message}')
以下代码片段的最后一行打印出最新提交的提交信息。值得注意的是,最新提交总是位于提交列表的第一位。一旦我们知道提交,我们也可以访问包含在该提交中的文件列表。以下代码片段展示了这一点:
# print the names of all files in the commit
# 0 means that we are looking at the latest commit
print(commits[0].file)
打印提交中文件的列表是好的,但并不十分有用。更有用的事情是访问这些文件并分析它们。以下代码片段展示了如何从最近的两个提交中访问两个文件。首先,我们访问文件,然后下载它们的内容并将它们存储在两个不同的变量中——linesOne和linesTwo:
# get one of the files from the commit
fileOne = commits[0].files[0]
# get the file from the second commit
fileTwo = commits[1].files[0]
# to get the content of the file, we need to get the sha of the commit
# otherwise we only get the content from the last commit
fl = repo.get_contents(fileOne.filename, ref=commits[0].sha)
fr = repo.get_contents(fileTwo.filename, ref=commits[1].sha)
# read the file content, but decoded into strings
# otherwise we would get the content in bytes
linesOne = fl.decoded_content
linesTwo = fr.decoded_content
最后,我们可以分析这两个文件,执行最重要的任务之一——获取两个文件之间的差异。我们可以使用difflib Python 库来完成这项任务,如下所示:
# calculate the diff using difflib
# for which we use a library difflib
import difflib
# print diff lines by iterating the list of lines
# returned by the difflib library
for line in difflib.unified_diff(str(linesOne),
str(linesTwo),
fromfile=fileOne.filename,
tofile=fileTwo.filename):
print(line)
上述代码片段允许我们识别文件之间的差异,并以类似于 GitHub 展示差异的方式打印它们。
我接下来的最佳实践与公共仓库的使用有关。
最佳实践 # 27
当从公共仓库访问数据时,请检查许可证并确保你承认创建了分析代码的社区的贡献。
如我之前提到的,开源程序是为了让每个人使用,包括分析和从中学习。然而,这个源代码背后的社区已经投入了无数小时来创建和完善它。因此,我们应该负责任地使用这些仓库。如果我们使用仓库来创建自己的产品,包括机器学习软件产品,我们需要承认社区的贡献,如果我们使用的是 copyleft 许可证下的软件,我们需要将我们的工作回馈给社区。
数据质量
在设计和开发机器学习系统时,我们从相对较低的水平考虑数据质量。我们寻找缺失值、异常值或类似情况。它们很重要,因为它们可能在训练机器学习模型时引起问题。尽管如此,从软件工程的角度来看,它们几乎足够了。
在构建可靠的软件系统时,我们需要了解我们使用的不仅仅是数据是否包含(或不包含)缺失值。我们需要知道我们是否可以信任数据(是否可信),数据是否具有代表性,或者是否是最新的。因此,我们需要为我们的数据建立一个质量模型。
在软件工程中,数据有几种质量模型,我经常使用并推荐的模型是 AIMQ 模型——一种评估信息质量的方法。
AIMQ 模型的质量维度如下(摘自 Lee, Y.W.等,AIMQ:信息质量评估方法。信息与管理,2002 年,40(2):p. 133-146):
-
可访问性:信息易于检索,并且可以轻松访问我们的系统
-
适当数量:信息对于我们的需求和我们的应用来说是足够的
-
可信度:信息是可信的,可以信赖
-
完整性:信息包括我们系统所需的所有必要值
-
简洁表示:信息以紧凑和适当的方式格式化,适用于我们的应用
-
一致表示:信息以一致的方式呈现,包括其随时间的变化表示
-
易用性:信息易于操作以满足我们的需求
-
无错误:信息对于我们所创建的应用来说是正确的、准确的和可靠的
-
可解释性:很容易理解信息的含义
-
客观性:信息是客观收集的,并基于事实
-
相关性:信息对我们系统是有用的、相关的、适当的,并且适用于我们的系统
-
声誉:信息在质量上享有良好声誉,且来源于可靠来源
-
安全性:信息受到未经授权的访问保护,并且受到充分的限制
-
时效性:信息对于我们的工作来说是足够最新的
-
可理解性:信息易于理解和理解
其中一些维度对于所有类型的应用都是通用的。例如,“无错误”维度对所有系统和所有机器学习模型都相关。同时,“相关性”必须在我们所设计和设计的应用和软件的上下文中进行评估。接近原始数据的维度比与应用相关的维度更容易自动评估。对于与应用相关的维度,我们通常需要进行专家评估或进行手动分析或调查。
以可信度为例。为了评估我们的源数据是否可信以及是否可用于此应用,我们需要了解数据来自哪里,谁/什么创造了这些数据,以及基于哪些前提。这不能自动化,因为它需要人类、专家的判断。
因此,我们可以将这些维度组织在不同的抽象层次上 – 原始数据或源系统、用于训练和推理的数据、机器学习模型和算法,以及整个软件产品。显示这些维度中哪些更接近原始数据,哪些更接近算法,哪些更接近产品可能是有用的。图 4.3 展示了这种组织:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_04_3.jpg
图 4.3 – 根据逻辑相关性组织的数据质量属性
图 4.3 表明我们在检查信息质量方面存在一个抽象层次,这是完全正确的。最低的抽象层次,或者说第一次检查,旨在量化基本的质量维度。这些检查也不必非常复杂。整个质量测量和监控系统可以非常简单,但非常强大。图 4.4 展示了这样一个系统的概念设计。该系统由三个部分组成 – 机器学习管道、日志文件和信息质量测量与监控:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_04_4.jpg
图 4.4 – 信息质量测量系统
首先,机器学习管道包含探测器,或测量仪器,用于收集与信息质量相关的问题信息。例如,这些仪器可以收集有关访问数据是否存在问题的信息,这可以表明可访问性质量维度存在问题。
以下代码片段展示了如何在实践中实现这一点。它配置了一个基本的日志文件,用于收集机器学习管道的错误信息:
import logging
# create a logging file
# including the format of the log messages
logging.basicConfig(filename='./information_quality_gerrit.log',
filemode='w',
format='%(asctime)s;%(name)s;%(levelname)s;%(message)s',
level=logging.DEBUG)
# specifying the name of the logger,
# which will tell us that the message comes from this program
# and not from any other modules or components imported
logger = logging.getLogger('Gerrit data export pipeline')
# the first log message to indicate the start of the execution
# it is important to add this, since the same log-file can be re-used
# the re-use can be done by other components to provide one single point of logging
logger.info('Configuration started')
这段代码创建日志文件,为它们提供整个机器学习管道使用的唯一名称,并指定错误消息的格式。然后,在机器学习管道本身中,日志文件通过消息进行传播。以下代码片段展示了之前展示的数据导出工具是如何被配置为传播这些信息的:
# A bit of config – repo
gerrit_url = "https://gerrit.onap.org/r"
fileName = "./gerrit_reviews.csv"
# since we use a public oss repository, we don't need to authenticate
auth = None
# this line gets sets the parameters for the HTML API
rest = GerritRestAPI(url=gerrit_url, auth = auth)
logger.info('REST API set-up complete')
# a set of parameters for the JSON API to get changes in batches of 500
start = 0 # which batch we start from – usually 0
logger.info('Connecting to Gerrit server and accessing changes')
try:
# the main query where we ask the endpoing to provide us the list and details of all changes
# each change is essentially a review that has been submitted to the repository
changes = rest.get("/changes/?q=status:merged&o=ALL_FILES&o=ALL_REVISIONS&o=DETAILED_LABELS&start={}".format(start),
headers={'Content-Type': 'application/json'})
except Exception as e:
logger.error('ENTITY ACCESS – Error retrieving changes: {}'.format)
logger.info(…) statement as well as error messages with the logger.error(…) statement.
The content of this log file can be quite substantial, so we need to filter the messages based on their importance. That’s why we distinguish between errors and information.
The following is a fragment of such a log file. The first line contains the information message (boldface `INFO`) to show that the machine learning pipeline has been started:
2023-01-15 17:11:45,618;Gerrit 数据导出管道;INFO;配置开始
2023-01-15 17:11:45,951;Gerrit 数据导出管道;INFO;配置结束
2023-01-15 17:11:46,052;Gerrit 数据导出管道;INFO;将新鲜数据下载到./gerrit_reviews.csv
2023-01-15 17:11:46,055;pygerrit2;DEBUG;解析 netrc 错误:netrc 文件缺失或未在 netrc 中找到凭据
2023-01-15 17:11:46,057;Gerrit 数据导出管道;INFO;获取从 0 到 500 的更改数据
2023-01-15 17:11:46,060;urllib3.connectionpool;DEBUG;开始新的 HTTPS 连接(1):gerrit.onap.org:443
We filter these messages in the last part of our measurement system – the information quality measurement and monitoring system. This last part reads through the log files and collects the error messages, categorizes them, and then visualizes them:
try:
logFile = open(“./information_quality_gerrit.log”, “r”)
for logMessage in logFile:
再次分割日志信息 - 这与我们的方法有关
在测量系统中结构化日志消息
logItem = logMessage.split(‘;’)
logLevel = logItem[2]
logSource = logItem[1]
logTime = logItem[0]
logProblem = logItem[3]
这部分是关于提取相关信息
如果这确实是一个问题:
if (logLevel == ‘ERROR’):
如果这是库的问题
if (‘LIBRARIES’ in logProblem):
iq_problems_configuration_libraries += 1
if (‘ENTITY_ACCESS’ in logProblem):
iq_problems_entity_access += 1
except Exception as e:
iq_general_error = 1
The bold-faced lines categorize the error messages found – in other words, they quantify the quality dimensions. This quantification is important as we need to understand how many problems of each kind were found in the machine learning pipeline.
The next step is to visualize the information quality, and for that, we need a quality analysis model. Then, we can use this quality model to visualize the quality dimensions:
def getIndicatorColor(ind_value):
if ind_value > 0:
return ‘red’
else:
return ‘green’
列 = (‘信息质量检查’, ‘值’)
行 = [‘实体访问检查’, ‘库’]
cell_text = [[f’实体访问: {iq_problems_entity_access}'],
[f’库: {iq_problems_configuration_libraries}']]
颜色 = [[getIndicatorColor(iq_problems_entity_access)],
[getIndicatorColor(iq_problems_configuration_libraries)]]
The visualization can be done in several ways, but in the majority of cases, it is enough to visualize it in a tabular form, which is easy to overview and comprehend. The most important for this visualization is that it communicates whether there are (or not) any information quality problems:
fig, ax = plt.subplots()
ax.axis(‘tight’)
ax.axis(‘off’)
the_table = ax.table(cellText=cell_text,
cellColours=colors,
colLabels=columns,
loc=‘left’)
plt.show()
The result of this code fragment is the visual representation shown in *Figure 4**.5*. This example can be found in this book’s GitHub repository:
<https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_04_5.jpg>
Figure 4.5 – Results from the quality checks, visualized in a tabular form
This rudimentary way of checking the information’s quality illustrates my next best practice.
Best practice # 28
Use simple logging to trace any problems in your machine learning pipeline to monitor information quality.
It’s generally a good practice to design and develop robust software systems. Logging is one of the mechanisms that’s used to detect potential problems. Logging is also a very good software engineering practice for systems that are not interactive, such as machine learning-based ones. Therefore, extracting the information from logs can help us understand the quality of the information that is used in a machine learning-based system.
Noise
Data quality in machine learning systems has one additional and crucial attribute – noise. Noise can be defined as data points that contribute negatively to the ability of machine learning systems to identify patterns in the data. These data points can be outliers that make the datasets skew toward one or several classes in classification problems. The outliers can also cause prediction systems to over- or under-predict because they emphasize patterns that do not exist in the data.
Another type of noise is contradictory entries, where two (or more) identical data points are labeled with different labels. We can illustrate this with the example of product reviews on Amazon, which we saw in *Chapter 3*. Let’s import them into a new Python script with `dfData = pd.read_csv('./book_chapter_4_embedded_1k_reviews.csv')`. In this case, this dataset contains a summary of the reviews and the score. We focus on these two columns and we define noise as different scores for the same summary review. For example, if one person provides a score of 5 for the review with the tag “Awesome!” and another person provides a score of 4 for another review with the tag “Awesome!,” the same data point becomes noisy as it is annotated with two different labels – two different scores.
So, first, we must check whether there are any duplicate entries:
现在,让我们检查是否有任何重复条目
获取所有数据点的数量
allDataPoints = len(dfData.Summary)
获取唯一数据点的数量
uniqueDataPoints = len(dfData.Summary.unique())
检查唯一数据点和所有数据点的数量是否相同
if allDataPoints != uniqueDataPoints:
print(f’有 {allDataPoints - uniqueDataPoints} 个重复条目,这可能会产生噪声’)
This code checks whether the number of data points is the same as the number of unique data points; if not, then we risk having noisy entries. We can check whether there are duplicate entries by using the following code:
然后,我们找到重复数据点的索引
首先,我们分组数据点
dfGrouped = dfData.groupby(by=dfData.Summary).count()
然后,我们找到那些不唯一的索引
lstDuplicated = dfGrouped[dfGrouped.Time > 1].index.to_list()
Now, we can remove all duplicate entries using the following command, though a simple solution would be to remove them (`dfClean = dfData[~dfData.Summary.isin(lstDuplicated)]`). A better solution is to check whether they are noisy entries or just duplicates. We can do this using the following code fragment:
对于这些数据点中的每一个,我们检查这些数据点
被分类到不同的标签,并仅移除具有不同标签的标签
for onePoint in lstDuplicated:
找到这个数据点的所有实例
dfPoint = dfData[dfData.Summary == onePoint]
现在检查这些数据点是否有不同的分数
numLabels = len(dfPoint.Score.unique())
如果标签数量超过 1,那么
这意味着数据集中有噪声
我们应该移除这个点
if numLabels > 1:
dfData.drop(dfData[dfData.Summary == onePoint].index, inplace=True)
让我们也打印出我们移除的数据点
print(f’点: {onePoint}, 标签数量: {len(dfPoint.Score.unique())}')
After running this fragment of code, the dataset does not contain any contradictory entries and therefore no class noise. Although it is possible to adopt a different strategy – for example, instead of removing noisy data points, we could change them to one of the classes – such an approach changes the pattern in the data, and therefore is not fully representative of the data. We simply do not know which of the classes is more correct than the others, especially if there are duplicate data points with two different labels.
Best practice # 29
The best strategy to reduce the impact of noise on machine learning classifiers is to remove the noisy data points.
Although we can correct noisy data points by changing their label or reducing the impact of these attributes on the predictions, the best strategy is to remove these data points. Removing is better as it does not change the patterns in the data. Imagine that we relabel noisy entries – this creates a pattern in the data that does not exist, which causes the algorithms to mispredict future data points.
Removing noise from the data is the only one way to handle noise. Another method is to increase the number of features so that we can distinguish between data points. We can analyze data and identify whether there is a risk of noise, and then we can check whether it is possible to add one more feature to the dataset to distinguish between entries labeled differently. However, this is outside the scope of this chapter.
Summary
Data for machine learning systems is crucial – without data, there can be no machine learning systems. In most machine learning literature, the process of training models usually starts with the data in tabular form. In software engineering, however, this is an intermediate step. The data is collected from source systems and needs to be processed.
In this chapter, we learned how to access data from modern software engineering systems such as Gerrit, GitHub, JIRA, and Git. The code included in this chapter illustrates how to collect data that can be used for further steps in the machine learning pipeline – feature extraction. We’ll focus on this in the next chapter.
Collecting data is not the only preprocessing step that is required to design and develop a reliable software system. Quantifying and monitoring information (and data) quality is equally important. We need to check that the data is fresh (timely) and that there are no problems in preprocessing that data.
One of the aspects that is specific to machine learning systems is the presence of noise in the data. In this chapter, we learned how to treat class noise in the data and how to reduce the impact of the noise on the final machine learning algorithm.
In the next chapter, we dive deeper into concepts related to data - clearning it from noise and quantifying its properties.
References
* *Vaswani, A. et al., Attention is all you need. Advances in neural information processing systems,* *2017\. 30.*
* *Dastin, J., Amazon scraps secret AI recruiting tool that showed bias against women. In Ethics of Data and Analytics. 2018, Auerbach Publications.* *p. 296-299.*
* *Staron, M., D. Durisic, and R. Rana,* *Improving measurement certainty by using calibration to find systematic measurement error—a case of lines-of-code measure. In Software Engineering: Challenges and Solutions. 2017, Springer.* *p. 119-132.*
* *Staron, M. and W. Meding, Software Development Measurement Programs. Springer. https://doi. org/10.1007/978-3-319-91836-5, 2018\. 10:* *p. 3281333.*
* *Fenton, N. and J. Bieman, Software metrics: a rigorous and practical approach. 2014:* *CRC press.*
* *Li, N., M. Shepperd, and Y. Guo, A systematic review of unsupervised learning techniques for software defect prediction. Information and Software Technology, 2020\. 122:* *p. 106287.*
* *Staron, M. et al. Robust Machine Learning in Critical Care—Software Engineering and Medical Perspectives. In* *2021 IEEE/ACM 1st Workshop on AI Engineering-Software Engineering for AI (WAIN).* *2021\. IEEE.*
* *Zhang, J. et al., CoditT5: Pretraining for Source Code and Natural Language Editing. arXiv preprint* *arXiv:2208.05446, 2022.*
* *Staron, M. et al. Using machine learning to identify code fragments for manual review. In 2020 46th Euromicro Conference on Software Engineering and Advanced Applications (SEAA).* *2020\. IEEE.*
* *Ochodek, M., S. Kopczyńska, and M. Staron, Deep learning model for end-to-end approximation of COSMIC functional size based on use-case names. Information and Software Technology, 2020\. 123:* *p. 106310.*
* *Cichy, C. and S. Rass, An overview of data quality frameworks. IEEE Access, 2019\. 7:* *p. 24634-24648.*
* *Lee, Y.W. et al., AIMQ: a methodology for information quality assessment. Information & management, 2002\. 40(2):* *p. 133-146.*
* *Staron, M. and W. Meding. Ensuring reliability of information provided by measurement systems. In International Workshop on Software Measurement.* *2009\. Springer.*
* *Pandazo, K. et al. Presenting software metrics indicators: a case study. In Proceedings of the 20th international conference on Software Product and Process Measurement (**MENSURA). 2010.*
* *Staron, M. et al. Improving Quality of Code Review Datasets–Token-Based Feature Extraction Method. In International Conference on Software Quality.* *2021\. Springer.*
第五章:量化并改进数据属性
在机器学习系统中获取数据是一个漫长的过程。到目前为止,我们主要关注从源系统收集数据和从数据中清除噪声。然而,噪声并不是我们可能在数据中遇到的所有问题的唯一来源。缺失值或随机属性是可能导致机器学习系统出现问题的数据属性示例。即使输入数据的长度如果超出预期值,也可能成为问题。
在本章中,我们将更深入地探讨数据的属性以及如何改进它们。与上一章相比,我们将专注于特征向量而不是原始数据。特征向量已经是数据的一种转换,因此我们可以改变诸如噪声等属性,甚至改变数据被感知的方式。
我们将专注于文本的处理,这是许多机器学习算法中一个重要的部分。我们将从了解如何使用简单的算法,如词袋模型,将数据转换为特征向量开始。我们还将学习处理数据问题的技术。
本章将涵盖以下主要内容:
-
为机器学习系统量化数据属性
-
培育噪声——在干净数据集中的特征工程
-
处理噪声数据——机器学习算法和噪声消除
-
消除属性噪声——数据集精炼指南
特征工程——基础
特征工程是将原始数据转换为可用于机器学习算法的数字向量的过程。这个过程是有结构的,需要我们首先选择需要使用的特征提取机制——这取决于任务的类型——然后配置所选的特征提取机制。当所选算法配置完成后,我们可以使用它将原始输入数据转换为特征矩阵——我们称这个过程为特征提取。有时,在特征提取之前(或之后)需要处理数据,例如通过合并字段或去除噪声。这个过程称为数据整理。
特征提取机制的种类繁多,我们无法涵盖所有内容。我们也不需要这样做。然而,我们需要理解的是,特征提取机制的选择如何影响数据的属性。我们将在下一章深入探讨特征工程的过程,但在这章中,我们将介绍一个用于文本数据的基本算法。我们需要介绍它,以便了解它如何影响数据的属性以及如何应对特征提取过程中可能出现的最常见问题,包括处理需要清理的“脏”数据。
为了理解这个过程,让我们从使用称为“词袋”算法的特征提取的第一个文本示例开始。词袋是一种将文本转换为表示该文本中哪些单词的数字向量的方法。单词形成结果数据框中的特征集——或列。在下面的代码中,我们可以看到特征提取是如何工作的。我们使用了sklearn标准库来创建词袋特征向量。
在下面的代码片段中,我们取了两行 C 代码——printf("Hello world!");和return 1——然后将这些代码转换成特征矩阵:
# create the feature extractor, i.e., BOW vectorizer
# please note the argument - max_features
# this argument says that we only want three features
# this will illustrate that we can get problems - e.g. noise
# when using too few features
vectorizer = CountVectorizer(max_features = 3)
# simple input data - two sentences
sentence1 = 'printf("Hello world!");'
sentence2 = 'return 1'
# creating the feature vectors for the input data
X = vectorizer.fit_transform([sentence1, sentence2])
# creating the data frame based on the vectorized data
df_bow_sklearn = pd.DataFrame(X.toarray(),
columns=vectorizer.get_feature_names(),
index=[sentence1, sentence2])
# take a peek at the featurized data
df_bow_sklearn.head()
粗体的行是创建CodeVectorizer类实例的语句,该类将给定的文本转换为特征向量。这包括提取已识别的特征。这一行有一个参数——max_features = 3。此参数告诉算法我们只想获取三个特征。在这个算法中,特征是输入文本中使用的单词。当我们向算法输入文本时,它提取标记(单词),然后对于每一行,它计算是否包含这些单词。这是在语句X = vectorizer.fit_transform([sentence1, sentence2])中完成的。当特征被提取后,结果数据集看起来如下:
| Hello | printf | return | |
|---|---|---|---|
| printf(“Hello world!”); | 1 | 1 | 0 |
| return 1 | 0 | 0 | 1 |
图 5.1 – 提取的特征创建此数据集
表格的第一行包含索引——输入算法的行——然后是1或0以表示该行包含词汇表中的单词。由于我们只要求三个特征,因此表格有三列——Hello、printf和return。如果我们更改CountVectorizer()的参数,我们将获得这两行中的完整标记列表,即hello、printf、return和world。
对于这两行简单的 C 代码,我们得到了四个特征,这说明了这种特征提取可以快速增加数据的大小。这使我们转向我的下一个最佳实践。
最佳实践#30
平衡特征数量与数据点数量。特征数量并不总是越多越好。
在创建特征向量时,重要的是提取有意义的特征,这些特征可以有效地区分数据点。然而,我们应该记住,拥有更多特征将需要更多内存,并且可能会使训练过程变慢。它也容易受到缺失数据点的问题。
清洁数据
当涉及到机器学习时,数据集的一个最棘手的问题就是存在空数据点或数据点的特征值为空。让我们通过前一个章节中提取的特征的例子来说明这一点。在下面的表格中,我引入了一个空数据点——中间列的NaN值。这意味着该值不存在。
| Hello | printf | return | |
|---|---|---|---|
| printf(“Hello world!”); | 1 | NaN | 0 |
| return 1 | 0 | 0 | 1 |
图 5.2 – 表格中包含 NaN 值的提取特征
如果我们将这些数据作为机器学习算法的输入,我们会得到一个错误消息,指出数据包含空值,并且模型无法训练。这是对这个问题的非常准确的描述——如果存在缺失值,那么模型不知道如何处理它,因此无法进行训练。
应对数据集中的空值有两种策略——移除数据点或插补值。
让我们从第一个策略开始——移除空数据点。以下脚本读取我们用于进一步计算的数据,即我们的代码审查数据:
# read the file with gerrit code reviews
dfReviews = pd.read_csv('./gerrit_reviews.csv', sep=';')
# just checking that we have the right columns
# and the right data
dfReviews.head()
上述代码片段读取文件并显示其前 10 行,以便我们检查数据内容。
一旦我们将数据存储在内存中,我们可以检查包含实际代码行(命名为LOC)的列中包含 null 值的行数有多少。然后,我们还可以删除不包含任何数据的行/数据点。数据点的删除由以下行处理——dfReviews = dfReviews.dropna()。此语句删除了空行,并将结果保留在数据框本身中(inplace=True参数):
import numpy as np
# before we use the feature extractor, let's check if the data contains NANs
print(f'The data contains {dfReviews.LOC.isnull().sum()} empty rows')
# remove the empty rows
dfReviews = dfReviews.dropna()
# checking again, to make sure that it does not contain them
print(f'The data contains {dfReviews.LOC.isnull().sum()} empty rows')
在执行这些命令后,我们的数据集已准备好创建特征向量。我们可以使用CountVectorizer从数据集中提取特征,如下面的代码片段所示:
# now, let's convert the code (LOC) column to the vector of features
# using BOW from the example above
vectorizer = CountVectorizer(min_df=2,
max_df=10)
dfFeatures = vectorizer.fit_transform(dfReviews.LOC)
# creating the data frame based on the vectorized data
df_bow_sklearn = pd.DataFrame(dfFeatures.toarray(),
columns=vectorizer.get_feature_names(),index=dfReviews.LOC)
# take a peek at the featurized data
df_bow_sklearn.head()
此代码片段创建了一个包含两个参数的词袋模型(CountVectorizer)——标记的最小频率和最大频率。这意味着算法计算每个标记在数据集中出现的频率统计,然后选择满足条件的标记。在我们的情况下,算法选择至少出现两次(min_df=2)且最多 20 次(max_df=20)的标记。
此代码片段的结果是一个包含 661 个特征的大型数据框,每个特征对应于我们数据集中每行代码。我们可以通过在执行上述代码片段后编写len(df_bow_sklearn.columns)来检查这一点。
为了检查如何处理数据插补,让我们打开一个不同的数据集并检查每列有多少缺失数据点。让我们读取名为gerrit_reviews_nan.csv的数据集,并使用以下代码片段列出该数据集中的缺失值:
# read data with NaNs to a dataframe
dfNaNs = pd.read_csv('./gerrit_reviews_nan.csv', sep='$')
# before we use the feature extractor, let's check if the data contains NANs
print(f'The data contains {dfNaNs.isnull().sum()} NaN values')
由于这个代码片段,我们得到了一个包含列中缺失值数量的列表——列表的尾部如下:
yangresourcesnametocontentmap 213
yangtextschemasourceset 205
yangtextschemasourcesetbuilder 208
yangtextschemasourcesetcache 207
yangutils 185
有许多缺失值,因此,我们需要采用不同于删除它们的策略。如果我们删除所有这些值,我们将得到恰好 0 个数据点——这意味着每个(或更多)数据列中都有一个 NaN 值。所以,我们需要采用另一种策略——填充。
首先,我们需要为填充器准备数据,它只对特征有效。因此,我们需要从数据集中删除索引:
# in order to use the imputer, we need to remove the index from the data
# we remove the index by first re-setting it (so that it becomes a regular column)
# and then by removing this column.
dfNaNs_features = dfNaNs.reset_index()
dfNaNs_features.drop(['LOC', 'index'], axis=1, inplace=True)
dfNaNs_features.head()
然后,我们可以创建填充器。在这个例子中,我使用了一种基于在现有数据上训练分类器,然后使用它来填充原始数据集中数据的现代方法。训练填充器的代码片段如下所示:
# let's use iterative imputed to impute data to the dataframe
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
# create the instance of the imputer
imp = IterativeImputer(max_iter=3,
random_state=42,
verbose = 2)
# train the imputer on the features in the dataset
imp.fit(dfNaNs_features)
代码片段的最后一行是填充器的实际训练。在这之后,我们可以开始对数据集进行填充,如下面的代码片段所示:
# now, we fill in the NaNs in the original dataset
npNoNaNs = imp.transform(dfNaNs_features)
dfNoNaNs = pd.DataFrame(npNoNaNs)
在这个片段之后,我们得到了一个包含填充值的数据集。现在,我们需要记住这些值只是估计值,而不是真实值。这个特定的数据集很好地说明了这一点。当我们执行dfNoNaNs.head()命令时,我们可以看到一些填充值是负数。由于我们的数据集是CountVectorizer的结果,负值不太可能。因此,我们可以使用另一种类型的填充器——KNNImputer。这个填充器使用最近邻算法找到最相似的数据点,并根据相似数据点的值填充缺失数据。这样,我们得到一组具有相同属性(例如,没有负值)的填充值,与数据集的其余部分相同。然而,填充值的模式是不同的。
因此,这是我的下一个最佳实践。
最佳实践#30
在数据点之间相似性预期是局部的情况下使用 KNNImputer。
当数据中有明显的局部结构时,KNNImputer表现良好,尤其是在相邻数据点在缺失值的特征上相似时。它可能对最近邻数(k)的选择敏感。
IterativeImputer在数据集中特征之间存在复杂关系和依赖时往往表现良好。它可能更适合那些缺失值不容易由局部模式解释的数据集。
然而,检查填充方法是否为当前数据集提供逻辑结果,以降低偏差风险。
数据管理中的噪声
缺失数据和矛盾的注释只是数据问题的一种类型。在许多情况下,由特征提取算法生成的大型数据集可能包含过多的信息。特征可能是多余的,不会对算法的最终结果做出贡献。许多机器学习模型可以处理特征中的噪声,称为属性噪声,但特征过多可能会在训练时间、存储甚至数据收集本身方面造成成本。
因此,我们也应该注意属性噪声,识别它,然后将其移除。
属性噪声
在大型数据集中减少属性噪声有几种方法。其中一种方法是一个名为成对属性噪声检测算法(PANDA)的算法。PANDA 成对比较特征并识别出哪些特征给数据集带来了噪声。这是一个非常有效的算法,但不幸的是计算量非常大。如果我们的数据集有几百个特征(这是我们真正需要使用这个算法的时候),我们需要大量的计算能力来识别这些对分析贡献甚微的特征。
幸运的是,有一些机器学习算法提供了类似的功能,同时计算开销很小。其中之一是随机森林算法,它允许你检索特征重要性值的集合。这些值是一种识别哪些特征在这个森林中的任何决策树中都没有被使用的方法。
让我们看看如何使用该算法提取和可视化特征的重要性。在这个例子中,我们将使用前几章从 Gerrit 工具中提取的数据:
# importing the libraries to vectorize text
# and to manipulate dataframes
from sklearn.feature_extraction.text import CountVectorizer
import pandas as pd
# create the feature extractor, i.e., BOW vectorizer
# please note the argument - max_features
# this argument says that we only want three features
# this will illustrate that we can get problems - e.g. noise
# when using too few features
vectorizer = CountVectorizer()
# read the file with gerrit code reviews
dfReviews = pd.read_csv('./gerrit_reviews.csv', sep=';')
在这个数据集中,我们有两个列是从中提取特征的。第一列是LOC列,我们使用CountVectorizer来提取特征——就像在之前的例子中一样。这些特征将成为训练算法的X值。第二列是感兴趣的列是message列。message列用于提供decision类。为了转换消息文本,我们使用情感分析模型来识别消息是正面还是负面。
首先,让我们使用CountVectorizer提取 BOW 特征:
# now, let's convert the code (LOC) column to the vector of features
# using BOW from the example above
vectorizer = CountVectorizer(min_df=2,
max_df=10)
dfFeatures = vectorizer.fit_transform(dfReviews.LOC)
# creating the data frame based on the vectorized data
df_bow_sklearn = pd.DataFrame(dfFeatures.toarray(),
columns=vectorizer.get_feature_names(),index=dfReviews.LOC)
要将信息转换为情感,我们可以使用 Hugging Face Hub 上公开可用的模型。我们需要使用以下命令安装相关库:! pip install -q transformers。一旦我们有了这些库,我们就可以开始特征提取:
# using a classifier from the Hugging Face hub is quite straightforward
# we import the package and create the sentiment analysis pipeline
from transformers import pipeline
# when we create the pipeline, and do not provide the model
# then the huggingface hub will choose one for us
# and download it
sentiment_pipeline = pipeline("sentiment-analysis")
# now we are ready to get the sentiment from our reviews.
# let's supply it to the sentiment analysis pipeline
lstSentiments = sentiment_pipeline(list(dfReviewComments))
# transform the list to a dataframe
dfSentiments = pd.DataFrame(lstSentiments)
# and then we change the textual value of the sentiment to
# a numeric one – which we will use for the random forest
dfSentiment = dfSentiments.label.map({'NEGATIVE': 0, 'POSITIVE': 1})
上述代码片段使用了预训练的情感分析模型和一个来自标准管道的模型——sentiment-analysis。结果是包含正面或负面情感的 dataframe。
现在,我们有了X值——从代码行中提取的特征——以及预测的Y值——来自评论消息的情感。我们可以使用这些信息创建一个数据框,将其用作随机森林算法的输入,训练算法,并确定哪些特征对结果贡献最大:
# now, we train the RandomForest classifier to get the most important features
# Note! This training does not use any data split, as we only want to find
# which features are important.
X = df_bow_sklearn.drop(['sentiment'], axis=1)
Y = df_bow_sklearn['sentiment']
# import the classifier – Random Forest
from sklearn.ensemble import RandomForestClassifier
# create the classifier
clf = RandomForestClassifier(max_depth=10, random_state=42)
# train the classifier
# please note that we do not check how good the classifier is
# only train it to find the features that are important.
Clf.fit(X,Y)
当随机森林模型训练完成后,我们可以提取重要特征列表:
# now, let's check which of the features are the most important ones
# first we create a dataframe from this list
# then we sort it descending
# and then filter the ones that are not important
dfImportantFeatures = pd.DataFrame(clf.feature_importances_, index=X.columns, columns=['importance'])
# sorting values according to their importance
dfImportantFeatures.sort_values(by=['importance'],
ascending=False,
inplace=True)
# choosing only the ones that are important, skipping
# the features which have importance of 0
dfOnlyImportant = dfImportantFeatures[dfImportantFeatures['importance'] != 0]
# print the results
print(f'All features: {dfImportantFeatures.shape[0]}, but only {dfOnlyImportant.shape[0]} are used in predictions. ')
以下代码片段选择了重要性大于0的特征,并将它们列出。我们发现 662 个特征中有 363 个被用于预测。这意味着剩下的 270 个特征只是属性噪声。
我们还可以使用seaborn库可视化这些特征,如下面的代码片段所示:
# we use matplotlib and seaborn to make the plot
import matplotlib.pyplot as plt
import seaborn as sns
# Define size of bar plot
# We make the x axis quite much larger than the y-axis since
# there is a lot of features to visualize
plt.figure(figsize=(40,10))
# plot seaborn bar chart
# we just use the blue color
sns.barplot(y=dfOnlyImportant['importance'],
x=dfOnlyImportant.index,
color='steelblue')
# we make the x-labels rotated so that we can fit
# all the features
plt.xticks(rotation=90)
# add chart labels
plt.title('Importance of features, in descending order')
plt.xlabel('Feature importance')
plt.ylabel('Feature names')
以下代码片段为数据集生成了以下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_05_1.jpg
图 5.3 – 具有许多特征的特性重要性图表
由于特征太多,图表变得非常杂乱且难以阅读,所以我们只能可视化前 20 个特征,以了解哪些是最重要的。
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_05_2.jpg
图 5.4 – 数据集中最重要的前 20 个特征
上述代码示例表明,我们可以将特征数量减少 41%,这几乎是特征数量的一半。算法只需几秒钟就能找到最重要的特征,这使得它成为减少数据集中属性噪声的完美候选。
最佳实践#31
使用随机森林分类器来消除不必要的特征,因为它提供了非常好的性能。
虽然我们并没有真正得到关于被移除的特征包含多少噪声的信息,但得到它们对预测算法没有价值的信息就足够了。因此,我建议在机器学习管道中使用这种特征减少技术,以减少我们管道的计算和存储需求。
数据分割
在设计基于机器学习的软件的过程中,另一个重要的属性是理解数据的分布,并且随后确保用于训练和测试的数据具有相似的分布。
用于训练和验证的数据分布很重要,因为机器学习模型识别模式并重新创建它们。这意味着如果训练数据中的数据分布与测试集中的数据分布不同,我们的模型就会错误地分类数据点。错误分类(或错误预测)是由于模型在训练数据中学习到的模式与测试数据不同所导致的。
让我们了解分割算法在理论上的工作原理以及在实际中的应用。图 5.5展示了在理论和概念层面上分割是如何工作的:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_05_3.jpg
图 5.5 – 将数据分割为训练集和测试集
图标代表审查评论(和讨论)。每个图标代表一个自己的讨论线程,每种类型的图标反映不同的团队。分割数据集背后的想法是,这两个集合非常相似,但并不完全相同。因此,训练集和测试集中元素的分发需要尽可能相似。然而,这并不总是可能的,如图5**.5所示 – 训练集中有一种类型的四个图标中的三个,而测试集中只有一个。在设计机器学习软件时,我们需要考虑这个方面,尽管它只与机器学习模型相关。我们的数据处理管道应该包含检查,提供理解数据是否正确分布的能力,如果不正确,我们需要纠正它。如果我们不纠正它,我们的系统开始做出错误的预测。在基于机器学习的系统中,数据分布随时间的变化,这是自然的,被称为概念漂移。
让我们通过计算我们的 Gerrit 审查数据集中数据的分布来实际应用这个方法。首先,我们读取数据,然后使用sklearn的train_test_split方法创建一个随机分割:
# then we read the dataset
dfData = pd.read_csv('./bow_sentiment.csv', sep='$')
# now, let's split the data into train and test
# using the random split
from sklearn.model_selection import train_test_split
X = dfData.drop(['LOC', 'sentiment'], axis=1)
y = dfData.sentiment
# now we are ready to split the data
# test_size parameter says that we want 1/3rd of the data in the test set
# random state allows us to replicate the same split over and over again
X_train, X_test, y_train, y_test =
train_test_split(X, y,
test_size=0.33,
random_state=42)
在这个代码片段中,我们将预测值(y)与预测值(X)特征分开。然后我们使用train_test_split方法将数据集分割成两个部分 – 训练集中的三分之二数据和测试集中的一分之一数据。这个 2:1 的比例是最常见的,但根据应用和数据集的不同,我们也可能遇到 4:1 的比例。
现在我们有了两组数据,我们应该探索它们的分布是否相似。本质上,我们应该对每个特征和预测变量(y)都这样做,但在我们的数据集中,我们有 662 个特征,这意味着我们可能需要进行如此多的比较。所以,为了举例,我们只可视化其中一个 – 在我们之前的例子中被认为是最重要的一个 – dataresponse:
# import plotting libraries
import matplotlib.pyplot as plt
import seaborn as sns
# we make the figure a bit larger
# and the font a bit more visible
plt.figure(figsize=(10,7))
sns.set(font_scale=1.5)
# here we visualize the histogram using seaborn
# we take only one of the variables, please see the list of columns
# above, or use print(X_train.columns) to get the list
# I chose the one that was the most important one
# for the prediction algorithm
sns.histplot(data=X_train['dataresponse'],
binwidth=0.2)
我们也将对测试集进行同样的操作:
plt.figure(figsize=(10,7))
sns.set(font_scale=1.5)
sns.histplot(data=X_test['dataresponse'],
binwidth=0.2)
这两个片段产生了两个直方图,显示了该变量的分布。它们在图 5**.6中展示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_05_6.jpg
图 5.6 – 训练集和测试集中数据响应特征的分布
列车集的分布位于左侧,测试集的分布位于右侧。乍一看,分布显示只有一个值 - 0 值。因此,我们需要更深入地手动探索数据。我们可以通过计算每个值(0 和 1)的实体数量来检查分布:
# we can even check the count of each of these values
X_train_one_feature = X_train.groupby(by='dataresponse').count()
X_train_one_feature
# we can even check the count of each of these values
X_test_one_feature = X_test.groupby(by='dataresponse').count()
X_test_one_feature
从前面的计算中,我们发现训练集中有 624 个 0 值和 5 个 1 值,测试集中有 309 个 0 值和 1 个 1 值。这些比例并不完全相同,但考虑到规模——0 值显著多于 1 值——这不会对机器学习模型产生任何影响。
我们数据集中的特征应该具有相同的分布,Y值——预测变量也是如此。我们可以使用相同的技巧来可视化Y值之间的类别分布。下面的代码片段正是这样做的:
# we make the figure a bit larger
# and the font a bit more visible
plt.figure(figsize=(10,7))
sns.set(font_scale=1.5)
sns.histplot(data=y_train, binwidth=0.5)
sns.histplot(data=y_test, binwidth=0.5)
此代码片段生成两个图表,显示了两个类别的差异。它们在图 5.7中展示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_05_7.jpg
图 5.7 – 训练和测试数据中类(0 和 1)的分布
预测的Y变量 0 值是负面情绪值,而 1 值是正面情绪值。尽管两个图表中 y 轴的刻度不同,但分布非常相似——在负面(0)情绪和正面(1)情绪的数量上大约是 2:1。
类别不平衡——0 的数量远大于 1 的数量,但分布相同。类别不平衡的事实意味着在此数据上训练的模型略微偏向负面情绪而不是正面情绪。然而,这反映了我们的经验观察:在代码审查中,审查员更可能评论需要改进的代码,而不是写得很好的代码。
最佳实践#32
尽可能保留数据的原始分布,因为它反映了经验观察。
尽管我们可以使用欠采样、过采样或类似的技术来平衡类别,但我们应尽可能保持原始分布。改变分布使模型在预测/分类方面“更公平”,但它改变了观察到的现象的本质。
机器学习模型如何处理噪声
从数据集中减少噪声是一个耗时的工作,而且也是一个难以自动化的任务。我们需要了解数据中是否存在噪声,数据中存在什么类型的噪声,以及如何去除它。幸运的是,大多数机器学习算法在处理噪声方面相当出色。
例如,我们迄今为止使用得相当多的算法——随机森林——对数据集中的噪声相当鲁棒。随机森林是一个集成模型,这意味着它由几个独立的决策树组成,这些决策树内部“投票”选择最佳结果。因此,这个过程可以过滤掉噪声,并趋向于数据中包含的模式。
深度学习算法也有类似的特性——通过利用大量的小神经元,这些网络对大数据集中的噪声具有鲁棒性。它们可以强制数据中的模式。
最佳实践 #33
在大型软件系统中,如果可能的话,依赖机器学习模型来处理数据中的噪声。
这听起来可能像是在提出一条简单的出路,但事实并非如此。数据的手动清理至关重要,但它也很慢且成本高昂。因此,在大型系统操作期间,最好选择一个对数据噪声鲁棒且同时使用更干净数据的模型。由于手动噪声处理过程需要时间和精力,依赖它们将为我们的产品运营带来不必要的成本。
因此,使用为我们做这件事的算法并因此创建可靠且维护成本最低的产品会更好。与其进行昂贵的噪声清理过程,不如重新训练算法,让它为你做这项工作。
在下一章中,我们将探讨数据可视化技术。这些技术帮助我们理解数据中的依赖关系,以及它是否揭示了可以被机器学习模型学习到的特征。
参考文献
-
*斯科特,S. 和 S. 马特温. 文本分类的特征工程. 在 ICML. 1999.
-
库尔卡尼,A. 等,将文本转换为特征. 自然语言处理食谱:使用 Python 的机器学习和深度学习解锁文本数据,2021: p. 63-106.
-
范·胡尔斯,J.D.,T.M. 科什戈法塔和黄,成对属性噪声检测算法. 知识与信息系统,2007. 11: p. 171-190.
-
李,X. 等,利用 BERT 进行端到端基于方面的情感分析. arXiv 预印本 arXiv:1910.00883, 2019.
-
徐,Y. 和 R. 古德雷克,关于分割训练集和验证集:比较交叉验证、自助法和系统抽样在估计监督学习泛化性能方面的研究. 分析与测试杂志,2018. 2(3): p. 249-262.
-
莫辛,V. 等. 比较测试深度学习算法的输入优先级技术. 在 2022 年 48 届欧姆尼微软件工程和高级应用会议(SEAA). 2022. IEEE.
-
刘,X.-Y.,吴,J. 和 周志华,探索性欠采样用于类别不平衡学习. IEEE 系统,人,和网络,第 B 部分(网络学),2008. 39(2): p. 539-550.
-
阿特拉,A. 等,不同机器学习算法对噪声的敏感性. 计算机科学学院杂志,2011. 26(5): p. 96-103.
第二部分:数据获取与管理
机器学习软件比其他类型的软件更依赖于数据。为了利用统计学习,我们需要收集、处理和准备数据以用于机器学习模型的发展。数据需要代表软件解决的问题以及它提供的服务,不仅在开发期间,而且在运营期间都需要。在本部分书中,我们专注于数据——我们如何获取它以及我们如何使它对机器学习模型的训练、测试和部署有用。
本部分包含以下章节:
-
第六章, 机器学习系统中的数据处理
-
第七章, 数值和图像数据的特征工程
-
第八章, 自然语言数据的特征工程
第六章:在机器学习系统中处理数据
我们在第三章中讨论了数据,其中我们介绍了在机器学习系统中使用的数据类型。在本章中,我们将更深入地探讨数据和算法相互交织的方式。我们将以通用术语讨论数据,但在本章中,我们将解释机器学习系统中需要哪种类型的数据。我将解释所有类型的数据都是以数值形式使用的——要么作为特征向量,要么作为更复杂的特征矩阵。然后,我将解释将非结构化数据(例如,文本)转换为结构化数据的必要性。本章将为深入探讨每种类型的数据奠定基础,这是下一章的内容。
在本章中,我们将做以下工作:
-
讨论测量过程(获取数值数据)以及在该过程中使用的测量仪器
-
使用 Matplotlib 和 Seaborn 库可视化数值数据
-
使用主成分分析(PCA)降低维度
-
使用 Hugging Face 的 Dataset 模块下载和处理图像和文本数据
数值数据
数值数据通常以数字表的形式出现,类似于数据库表。这种形式中最常见的数据之一是指标数据——例如,自 1980 年代以来一直使用的标准面向对象指标。
数值数据通常是测量过程的成果。测量过程是一个使用测量仪器将实体的经验属性量化为数字的过程。这个过程必须保证重要的经验属性在数学领域中得以保留——也就是说,在数字中。图 6.1展示了这一过程的例子:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_1.jpg
图 6.1 – 使用缺陷进行质量测量的测量过程示例
这个过程的重要部分包括三个要素。首先是测量仪器,它需要将经验属性真实地映射到数字上。然后是测量标准,例如 ISO 计量学词汇(VIM),被称为测量的真值。最后,我们有测量过程的成果——将测量仪器应用于特定的测量实体——这会产生一个数字,即测量属性的量化。然而,一个单一的数字并不能表征整个软件产品或其任何部分,无论它对测量实体的真实性如何。因此,在实践中,我们使用多个测量仪器来创建对测量实体的整体视图。
这就是数值数据发挥作用的地方。每个表征测量实体的测量值都存储在数据库或表中——每个实体成为一行,每个度量成为一列。我们拥有的列越多,测量的实体特征就越好。然而,同时,我们收集的度量越多,它们相互关联、相关(正相关性或负相关性)以及重叠的风险就越高。因此,我们需要对数据进行一些处理,以便对其有一个大致的了解。所以,首先,我们必须可视化数据。
本章的这一部分所使用的数据来自 Alhustain 和 Sultan 的一篇论文(预测面向对象度量的相对阈值。"2021 IEEE/ACM 国际技术债务会议(TechDebt)。IEEE,2021)并可在 Zenodo 上获得(zenodo.org/records/4625975),这是软件工程研究中最常用的开放数据存储库之一。数据包含典型面向对象度量度量的值:
-
对象之间的耦合(CB):从测量实体(类)到其他类的引用数量
-
直接类耦合(DCC):从此类到其他类的连接数量(例如,关联)
-
导出耦合(ExportCoupling):从类的出去连接数量
-
导入耦合(ImportCoupling):到类的进入连接数量
-
方法数量(NOM):类中的方法数量
-
按类加权的度量方法(WMC):类中方法的数量,按其大小加权
-
缺陷计数(defect):为此类发现的缺陷数量
数据集描述了来自 Apache 基金会的几个软件项目——例如,Ant 工具。对于每个产品,测量的实体是项目中的类。
那么,让我们从下一个最佳实践开始,这将引导我们进入可视化阶段。
最佳实践 #34
在处理数值数据时,首先可视化它,从数据的概览视图开始。
当我处理数值数据时,我通常从可视化开始。我从数据的概述开始,然后逐步深入到细节。
总结数据
使用表格、交叉表以及图表可以总结数据。我通常开始工作的图表之一就是相关图——它是一个显示数据集中每个变量/度量之间相关性的图表。
因此,让我们将数据读入笔记本并开始可视化:
# read the file with data using openpyxl
import pandas as pd
# we read the data from the excel file,
# which is the defect data from the ant 1.3 system
dfDataAnt13 = pd.read_excel('./chapter_6_dataset_numerical.xlsx',
sheet_name='ant_1_3',
index_col=0)
一旦数据集存储在内存中,我们可以使用 Python 的 Seaborn 库通过相关图来可视化它。以下代码就是这样做:
# now, let's visualize the data using correlograms
# for that, we use the seaborn library
import seaborn as sns
import matplotlib.pyplot as plt
# in seaborn, the correlogram is called
# pairplot
sns.pairplot(dfDataAnt13)
此代码片段的结果是以下相关图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_2.jpg
图 6.2 – 来自 Alhusain 论文数据集的相关图
这里有趣的部分是展示在对角线单元格中的每个度量值的分布。在我们的数据中,这种分布对于某些变量来说很难解释,因此我们可以以不同的方式可视化它。当我们用sns.pairplot(dfDataAnt13, diag_kind="kde")替换代码片段的最后一行时,我们得到一个新的可视化,可以更好地查看分布。这显示在图 6.3:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_3.jpg
图 6.3 – 具有更好分布可视化的自相关图
这些自相关图为我们提供了快速了解哪些变量可以相互关联的导向。这些相关性是我们可以在以后的工作中使用的。
我们还可以通过使用热图来可视化数字来查看数据。热图是一种表格可视化,其中颜色的强度表示每个变量值的强度。我们可以使用以下代码创建热图:
# heatmap
p1 = sns.heatmap(dfDataAnt13, cmap="Reds")
结果图展示在图 6.4:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_4.jpg
图 6.4 – 度量值的总结热图
在深入进行相关性分析之前,我经常先进行一些成对比较的深入分析。我也建议我的学生这样做,因为处理成对数据可以让我们理解变量之间的联系。
因此,这是我的下一个最佳实践。
最佳实践#35
在总体层面上可视化数据时,关注值之间关系的强度和连接。
在总体层面上进行可视化可以为我们提供许多不同的视角,但我们应该寻找变量之间的联系。自相关图和热图为我们提供了这种数据可视化和理解。
深入了解相关性
一套好的图表用于工作的是散点图。然而,我经常使用被称为 KDE 图(核密度估计图)的图表,也称为密度图。它们提供了变量更好的概述。以下代码片段以这种方式可视化数据:
# now, let's make some density plots
# set seaborn style
sns.set_style("white")
# Basic 2D density plot
sns.kdeplot(x=dfDataAnt13.CBO, y=dfDataAnt13.DCC)
plt.show()
此代码片段的结果是图 6.5中展示的图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_5.jpg
图 6.5 – 两个度量值(DCC 和 CBO)的密度图
此图表表明,两个度量值 – CBO 和 DCC – 相互之间有很强的依赖性(或者它们量化了相似/相同的可度量概念)。
如果我们想在仪表板上使用此图,可以使用以下代码片段使其更美观:
# Custom the color, add shade and bandwidth
sns.kdeplot(x=dfDataAnt13.WMC,
y=dfDataAnt13.ImportCoupling,
cmap="Reds",
shade=True,
bw_adjust=.5)
plt.show()
此代码片段的结果是以下图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_6.jpg
图 6.6 – 带有颜色图的密度图
前面的图表显示了每个区域的相关性和点数——颜色越深,该区域的数据点就越多。对于 DCC 和 CBO 测量的相同图表显示在图 6.7中:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_7.jpg
图 6.7 – DCC 和 CBO 测量的密度图,带有颜色图
最后,我们可以使用气泡图来可视化相关性和每个组的数据点数。以下代码创建了气泡图:
# now a bubble diagram
# use the scatterplot function to build the bubble map
sns.scatterplot(data=dfDataAnt13,
x="NOM",
y="DCC",
size="Defect",
legend=False,
sizes=(20, 2000))
# show the graph
plt.show()
这段代码产生了图 6.8中展示的图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_8.jpg
图 6.8 – 散点图 – 一种称为气泡图的变体
这个图让我们可以看到散点图中每个区域的点数,这有助于我们直观地追踪相关性。
总结单个测量值
散点图和密度图适合追踪变量之间的依赖关系。然而,我们经常需要总结单个测量值。为此,我们可以使用箱线图。以下代码为我们示例中的数据创建了一个箱线图:
# boxplot
sns.boxplot( x=dfDataAnt13.Defect, y=dfDataAnt13.CBO )
结果是图 6.9中展示的箱线图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_9.jpg
图 6.9 – 描述有缺陷和无缺陷类别的 CBO 测量的箱线图
总结提供了快速视觉指示,表明有缺陷的类别通常比无缺陷的类别与其他类别更紧密地连接。这并不令人惊讶,因为通常类别是相互连接的,而那些不连接的类别通常非常简单,因此不太可能出错。
箱线图的一种变体是提琴图,如果我们把最后一个代码片段的最后一行改为sns.violinplot( x='Defect', y='CBO', data=dfDataAnt13),就会得到这样的提琴图:图 6.10展示了这样的提琴图:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_10.jpg
图 6.10 – 一种箱线图的变体,称为提琴图
可视化是理解我们拥有的数值数据的好方法。我们可以更进一步,通过使用降维等方法开始与之工作。
因此,这里是下一个最佳实践。
最佳实践#36
深入分析单个分析应该由当前的机器学习任务指导。
虽然我们没有明确讨论我们的数值数据的任务,但它始终存在。在涉及缺陷相关的数据的情况下,最常见的任务是预测每个模块或类别的缺陷数量。这意味着像提琴图这样的图表非常有用,它为我们提供了对是否存在某种差异的视觉理解——这种差异可以被机器学习模型捕捉。
减少测量数量 – 主成分分析(PCA)
本章关于数值数据的最终分析是关于减少变量数量。它来自统计学领域,并且已被用于减少实验中的变量数量:PCA(Wold,1987 #104)。简而言之,PCA 是一种找到最佳拟合预定义数量向量的技术,以适应手头的数据。它不会删除任何变量;相反,它会以这种方式重新计算它们,使得新变量集(称为主成分)之间的相关性最小化。
让我们使用以下代码片段将此应用于我们的数据集:
# before we use PCA, we need to remove the variable "defect"
# as this is the variable which we predict
dfAnt13NoDefects = dfDataAnt13.drop(['Defect'], axis=1)
# PCA for the data at hand
from sklearn.decomposition import PCA
# we instantiate the PCA class with two parameters
# the first one is the number of principal components
# and the second is the random state
pcaComp = PCA(n_components=2,
random_state=42)
# then we find the best fit for the principal components
# and fit them to the data
vis_dims = pcaComp.fit_transform(dfAnt13NoDefects)
现在,我们可以可视化数据:
# and of course, we could visualize it
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
colors = ["red", "darkgreen"]
x = [x for x,y in vis_dims]
y = [y for x,y in vis_dims]
# please note that we use the dataset with defects to
# assign colors to the data points in the diagram
color_indices = dfDataAnt13.Defect
colormap = matplotlib.colors.ListedColormap(colors)
plt.scatter(x, y, c=color_indices, cmap=colormap, alpha=0.3)
for score in [0,1]:
color = colors[score]
plt.rcParams['figure.figsize'] = (20,20)
这个代码片段产生了图 6.11中展示的图表:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_11.jpg
图 6.11 – 缩减缺陷数据集维度的 PCA 结果。红色数据点是具有缺陷的类别,绿色数据点是无缺陷的类别
PCA 变换的典型特征是其线性。我们可以看到这个图表包含了一些这样的痕迹 - 它看起来像一个三角形,有一个水平维度沿着x 轴,一个垂直维度沿着y 轴,以及左侧的 0 点。
对于这个数据集,图表显示红色标记的数据点聚集在左侧,而绿色标记的点稍微向右分散。这意味着有缺陷和无缺陷的类别之间存在一些差异。然而,这种差异并不明显。这表明机器学习模型找不到模式 - 至少,找不到一个稳健的模式。
其他类型的数据 - 图片
在第三章中,我们研究了图像数据,主要从存在什么类型的图像数据的视角。现在,我们将采取更实际的方法,介绍一种比仅使用文件更好的图像处理方式。
让我们看看图像数据在流行的存储库 Hugging Face 中是如何存储的。这个库有一个专门用于处理数据集的模块 - 便于称为Dataset。它可以通过pip install -q datasets命令安装。因此,让我们加载一个数据集,并使用以下代码片段可视化其中一张图片:
# importing the images library
from datasets import load_dataset, Image
# loading a dataset "food101", or more concretely it's split for training
dataset = load_dataset("food101", split="train")
现在,变量数据集包含了所有图片。嗯,不是全部 - 只包含数据集设计者指定的训练集部分(见代码片段的最后一行)。我们可以使用以下代码来可视化其中一张图片:
# visualizing the first image
dataset[0]["image"]
由于图像的版权未知,我们不会在这本书中可视化它们。然而,上一行将显示数据集中的第一张图像。我们还可以通过简单地输入 dataset 来查看该数据集中还有什么其他内容。我们将看到以下输出:
Dataset({ features: ['image', 'label'], num_rows: 75750 })
这意味着数据集包含两列——图像及其标签。它包含 75,750 个。让我们使用以下代码来看看这个数据集中标签的分布情况:
# we can also plot the histogram
# to check the distribution of labels in the dataset
import seaborn as sns
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (20,10)
sns.histplot(data=dataset['label'], x=dataset['label'])
这为我们提供了一个漂亮的直方图,如图 图 6.12 所示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_12.jpg
图 6.12 – 标签分布的直方图。每列是带有适当标签的图像数量——0 到 100
此图表显示了图像类别,其中包含的图像数量大于其他类别——包含超过 2,000 个图像的类别。然而,如果不理解数据集,很难检查这些标签的含义。我们可以通过手动可视化图像来做到这一点。所以,这是我的下一个最佳实践。
最佳实践 #37
在可视化图像的元数据时,确保可视化图像本身。
我们必须记住通过绘制图像来可视化图像数据。我们需要确保我们知道标签的含义以及我们如何使用它们。
文本数据
对于文本数据,我们将使用相同的 Hugging Face hub 获取两种类型的数据——非结构化文本,正如我们在 第三章 中所做的那样,以及结构化数据——编程语言代码:
# import Hugging Face Dataset
from datasets import load_dataset
# load the dataset with text classification labels
dataset = load_dataset('imdb')
上述代码片段从 互联网电影数据库(IMDb)加载电影评论数据集。我们可以通过使用与图像类似的接口来获取数据的示例:
# show the first example
dataset['train'][0]
我们可以使用类似的图表来可视化它:
# plot the distribution of the labels
sns.histplot(dataset['train']['label'], bins=2)
上述代码片段创建以下图表,显示正面和负面评论完全平衡:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_13.jpg
图 6.13 – IMDb 电影数据库评论中的平衡类别
我们可以在下一步对文本数据进行各种处理。然而,这种处理与特征提取相关,所以我们将在接下来的几章中讨论它。
在我们这样做之前,让我们先看看更接近软件工程领域的数据集——编程语言代码。我们在 第三章 中使用了类似的数据,所以让我们关注一下我们如何从 Hugging Face 获取更大的编程语言代码语料库。我们可以使用以下代码来获取数据并检查第一个程序:
# now, let us import the code to the text summarization dataset
dsCode = load_dataset('code_x_glue_ct_code_to_text', 'java', split='test')
# and see the first example of the code
dsCode[0]
此代码片段显示第一个程序,它已经分词并准备好进行进一步分析。所以,让我们看看这个数据集中标记的频率。我们可以使用以下代码来做这件事:
import pandas as pd
import matplotlib.pyplot as plt
# create a list of tokens
lstCodeLines = dsCode['code_tokens']
# flatten the list of lists to one list
lstCodeLines = [item for sublist in lstCodeLines for item in sublist]
#print the first elements of the list
print(lstCodeLines[:10])
dfCode = pd.DataFrame(lstCodeLines, columns=['token'])
# group the tokens and count the number of occurences
# which will help us to visualize the frequency of tokens in the next step
dfCodeCounts = dfCode.groupby('token').size().reset_index(name='counts')
# sort the counts by descending order
dfCodeCounts = dfCodeCounts.sort_values(by='counts', ascending=False)
fig, ax = plt.subplots(figsize=(12, 6))
# plot the frequency of tokens as a barplot
# for the simplicity, we only take the first 20 tokens
sns.barplot(x='token',
y='counts',
data=dfCodeCounts[:20],
palette=sns.color_palette("BuGn_r", n_colors=20),
ax=ax)
# rotate the x-axis labels to make sure that
# we see the full token names, i.e. lines of code
ax.set_xticklabels(ax.get_xticklabels(),
rotation=45,
horizontalalignment='right')
以下代码提取标记,计算它们,并创建前 20 个标记频率的图表。结果在图 6.14中展示:
https://github.com/OpenDocCN/freelearn-ml-zh/raw/master/docs/ml-infra-bst-prac/img/B19548_06_14.jpg
图 6.14 – 代码数据集中前 20 个最常见标记的频率
有趣的是,我们可以观察到括号、逗号、分号和大括号是数据集中最常用的标记。这并不令人惊讶,因为这些字符在 Java 中具有特殊含义。
在前 20 个标记列表中的其他标记,不出所料,是 Java 中的关键字或具有特殊含义(如==)。
因此,我在本章的最后一条最佳实践是关于理解文本数据。
最佳实践 #38
文本数据的汇总统计有助于我们对数据进行合理性检查。
尽管文本数据在本质上相当无结构,但我们仍然可以可视化数据的一些属性。例如,标记频率分析可以揭示我们对数据的经验理解是否合理,以及我们是否可以信任它。
向特征工程迈进
在本章中,我们探讨了可视化数据的方法。我们学习了如何创建图表并识别数据中的依赖关系。我们还学习了如何使用降维技术将多维数据绘制在二维图表上。
在接下来的几章中,我们将深入研究不同类型数据的特征工程。有时,将特征工程与数据提取混淆是很常见的。在实践中,区分这两者并不那么困难。
提取的数据是通过应用某种测量仪器收集的数据。原始文本或图像是这类数据的良好例子。提取的数据接近数据来源的领域——或者它是如何被测量的。
特征基于我们想要执行的分析来描述数据——它们更接近我们想要对数据进行什么操作。它们更接近我们想要实现的目标以及我们想要进行的机器学习分析形式。
参考文献
-
国际标准化组织,国际计量学基本和通用术语词汇(VIM)。在国际组织。2004 年第 09-14 页。
-
Alhusain, S. 预测面向对象度量指标的相对阈值。在 2021 IEEE/ACM 国际技术债务会议(TechDebt)。 2021 年 IEEE 出版。
-
Feldt, R. 等。支持软件决策会议:用于可视化测试和代码度量的热图。在 2013 年第 39 届 Euromicro 软件工程和高级应用会议。 2013 年 IEEE 出版。
-
Staron, M. 等。在三家公司的案例研究中测量和可视化代码稳定性。在 2013 年第 23 届国际软件度量研讨会和第 8 届国际软件过程和产品度量会议联合会议。 2013 年 IEEE 出版。
-
文,S.,C. 尼尔森,和 M. 斯塔隆。评估引擎控制软件的发布准备情况。载于《第 1 届国际软件质量及其 依赖性研讨会论文集》。2018 年。
3928

被折叠的 条评论
为什么被折叠?



