Python 数据中心的机器学习(一)
原文:
annas-archive.org/md5/db8f2beb902c7755f2a881df92b038cc译者:飞龙
前言
如果你正在阅读这本书,你已经迈出了探索之旅的第一步,旨在构建和实施更稳健、准确、公平、偏见更小且更容易解释的机器学习模型。
我们知道这是一个很大的声明。然而,我们基于在数据为中心的机器学习开发方法中看到的大量且相对未被开发的潜力,我们感到很自在地做出这个声明。
为什么我们认为以数据为中心的机器学习是开创性的?
改善数据质量将导致更多预测模型似乎很明显。然而,迄今为止的机器学习研究主要集中在不断改进各种算法和工具来构建和调整模型。
因此,我们手头上有大量的机器学习算法、工具和技术,可以在输入数据的质量和数量合适的情况下,以低成本为我们提供优秀的模型。
模型架构在大多数情况下都是一个已解决的问题。数据科学家及其所在的组织通常缺乏的是提高数据质量的最佳实践框架、工具和技术。
以数据为中心的机器学习建立在以模型为中心的方法基础上,通过利用更好的输入数据带来的巨大机会。
对数据收集和工程给予更大的重视要求我们优化收集高质量数据的过程,并发明新的技术来工程数据集,以更少的数量提供更多信号。
本书将介绍的大多数技术和例子都基于前沿研究和将现代实践应用于收集、工程和合成生成优秀数据集的应用。
以数据为中心的机器学习还需要数据科学家、领域专家和数据标记者之间更强的合作。正如你将在本书中了解到的那样,数据中心性通常始于人类以服务于运营和数据科学需求的方式收集和标记数据。
在许多组织中,专门为机器学习目的收集数据并不常见。一种更系统的方法来收集和标记数据用于数据科学,不仅会导致更好的数据,而且会将领域专家和数据科学家的思维和创造力结合起来。这种不同领域专家之间的积极反馈循环为思想的繁荣创造了新的机会,远远超出了个人机器学习项目的范围。
为什么我们声称以数据为中心的模型在几乎所有方面都将优于以模型为中心的对应模型?
想想你经常使用的任何高质量消费品。这可能是一台电脑,你驾驶的汽车,你坐的椅子,或者需要一定设计水平和技术水平的其他东西。
高质量的标准是什么?
设计和功能与之有很大关系,但除非产品由优质材料制成,否则它不会按预期工作,或者可能会完全损坏。只有当某物按预期工作并且持续工作时,它才算是高质量的。
对于机器学习模型也是如此。通过系统地提高数据质量——我们的建筑材料——我们能够构建出更具预测性、鲁棒性和可解释性的模型。
我们编写这本书是为了向您,我们的读者,提供实施数据中心化机器学习并参与人工智能革命下一阶段所需的最重要背景知识、工具、技术和应用示例。
在本书的技术章节中,我们将向您展示如何使用 Python 将数据中心化机器学习的原则应用于实际数据集,我们将探讨的技术和实际应用示例将为您提供一套工具箱,以系统化和编程方式收集、清理、增强和标记数据,以及识别和消除不希望的偏差。
在本书的结尾,您将对数据中心化机器学习的构建块和最佳实践方法有深刻的认识。
不要只听我们的一面之词。让我们深入探讨数据中心化机器学习。
本书面向的对象
这本书是为想要了解数据中心化是什么、其相对于模型中心化方法的优点以及如何将最佳实践的数据中心化方法应用于其工作的数据科学专业人士和机器学习爱好者而编写的。
这本书也是为其他数据专业人士和高级管理人员编写的,他们想要探索提高数据质量的工具和技术,以及如何在他们的组织中创造“小数据”机器学习/人工智能的机会。
本书涵盖的内容
第一章,探索数据中心化机器学习,对数据中心化机器学习进行了全面的定义,并与它的对立面——模型中心化——进行了对比。我们使用实际例子来比较经验性能,并说明这两种方法之间的关键差异。
第二章,从模型中心化到数据中心化——机器学习的演变,带您穿越人工智能和机器学习的演变历程,强调在模型调整之外提高数据质量的未开发潜力。我们还揭穿了“大数据”神话,展示了转向“优质数据”如何使机器学习解决方案民主化。准备好从数据在机器学习中的力量中获得新的视角。
第三章,数据中心化机器学习原理,为您深入数据中心化机器学习的旅程奠定了基础,概述了数据中心化机器学习的四个关键原则。这些原则提供了至关重要的背景知识——即为什么——在我们深入探讨与每个原则相关的具体方法和途径——即是什么——在随后的章节中。
第四章, 数据标注是一个协作过程,探讨了在机器学习开发中主题领域专业知识、训练有素的标注员和明确指令的关键作用。在本章中,你将了解数据标注的人本性,并获得提高其质量以减少偏差、增加一致性和构建更丰富数据集的策略。
第五章, 数据清洗技术,探讨了数据质量的六个关键方面,并展示了各种数据清洗技术,这是通过纠正错误来提高数据质量的重要过程。我们阐述为什么质疑和系统地提高数据质量对于可靠的机器学习系统至关重要,同时教授你必要的数据清洗技能。
第六章, 机器学习中程序化标注技术,关注于提高数据质量和信号强度的程序化标注技术。我们探讨了程序化标注的优缺点,并提供了如何执行和验证这些技术的实际示例。
第七章, 在数据为中心的机器学习中使用合成数据,介绍了合成数据作为一种高效且成本效益高的方法,以克服传统数据收集和标注的局限性。在本章中,你将了解什么是合成数据,它是如何用于改进模型的,生成它的技术,以及其风险和挑战。
第八章, 识别和消除偏差的技术,关注我们在收集数据、将数据和模型应用于问题以及许多数据集中固有的人类偏差中的偏差问题。我们将通过数据为中心的技术来识别和以道德方式纠正偏差。
第九章, 处理机器学习中的边缘案例和罕见事件,解释了在机器学习中检测罕见事件的过程。我们探讨了各种方法和技巧,讨论了评估指标的重要性,并说明了识别罕见事件产生的广泛影响。
第十章, 开启数据为中心的机器学习之旅,揭示了在模型开发和部署过程中可能遇到的技术和非技术挑战。本章展示了数据为中心的方法如何帮助你克服这些挑战,为你在组织内扩大机器学习应用和增长机会开辟了广阔的前景。
为了充分利用这本书
要从本书中获取最大价值,对机器学习概念有先前的了解、对统计方法的基础知识以及熟悉 Python 编程将非常有帮助。本书专为熟悉机器学习过程并希望深入了解以数据为中心的机器学习和人工智能世界的人士编写。
| 本书涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| Python 3 | Windows、macOS 或 Linux |
如果您正在使用这本书的数字版,我们建议您亲自输入代码或从书的 GitHub 仓库(下一节中有一个链接)获取代码。这样做将帮助您避免与代码的复制和粘贴相关的任何潜在错误。
下载示例代码文件
您可以从 GitHub 下载本书的示例代码文件:github.com/PacktPublishing/Data-Centric-Machine-Learning-with-Python。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他来自我们丰富图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
使用的约定
本书使用了许多文本约定。
文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个示例:“我们将调用loan_dataset.csv文件,并将其保存在同一目录中,然后我们将从这个目录运行此示例。”
代码块设置如下:
import pandas as pd
import os
FILENAME = "./loan_dataset.csv"
DATA_URL = "http://archive.ics.uci.edu/ml/machine-learning-databases/00350/default%20of%20credit%20card%20clients.xls"
粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个示例:“机器学习中的偏差可以采取多种形式,因此我们将这些偏差分为两种主要类型,易于识别的偏差和难以识别的偏差。”
小贴士或重要注意事项
看起来像这样。
联系我们
欢迎读者反馈。
一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件发送给我们至 customercare@packtpub.com,并在邮件主题中提及书名。
勘误表:尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在这本书中发现了错误,我们将非常感激您能向我们报告。请访问www.packtpub.com/support/err…并填写表格。
盗版:如果您在互联网上以任何形式遇到我们作品的非法副本,如果您能提供位置地址或网站名称,我们将不胜感激。请通过 mailto:copyright@packt.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了使用 Python 进行以数据为中心的机器学习,我们非常乐意听到您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区非常重要,并将帮助我们确保我们提供高质量的内容。
下载此书的免费 PDF 副本
感谢您购买此书!
您喜欢随时随地阅读,但无法携带您的印刷书籍到处走?
您的电子书购买是否与您选择的设备不兼容?
别担心,现在每本 Packt 书籍都免费提供该书的 DRM 免费 PDF 版本,无需额外费用。
在任何地方、任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。
优惠远不止于此,您还可以获得独家折扣、时事通讯和每日免费内容的每日电子邮件。
按照以下简单步骤获取优惠:
- 扫描下面的二维码或访问以下链接!img/B19297_QR_Free_PDF.jpg
packt.link/free-ebook/9781804618127
-
提交您的购买证明
-
就这些!我们将直接将您的免费 PDF 和其他优惠发送到您的电子邮件
第一部分:什么是以数据为中心的机器学习以及为什么我们需要它
在这部分,我们深入探讨以数据为中心的机器学习,将其与以模型为中心的方法进行对比。我们使用真实生活中的例子来说明它们的差异,并探讨人工智能和机器学习向以数据为中心的视角的演变。我们还消除了“大数据”的神话,强调了质量胜于数量的重要性,以及民主化机器学习解决方案的潜力。准备好以全新的视角来了解数据在机器学习中的变革力量。
本部分包含以下章节:
-
第一章*,探索以数据为中心的机器学习*
-
第二章*,从以模型为中心到以数据为中心——机器学习的演变*
第一章:探索数据为中心的机器学习
本章提供了对数据为中心的机器学习(ML)的基础理解。我们还将对比数据中心性与模型中心性,并比较两种方法的性能,使用实际例子来说明关键点。通过这些实际例子,你将深刻理解数据中心性的潜力。
在本章中,我们将涵盖以下主要主题:
-
理解数据为中心的机器学习
-
以数据为中心与以模型为中心的机器学习
-
质量数据在机器学习中的重要性
理解数据为中心的机器学习
以数据为中心的机器学习是系统地构建机器学习和人工智能(AI)系统所使用数据的学科。
数据为中心的 AI 和 ML 运动基于这样的哲学:在构建高度信息模型时,数据质量比数据量更重要。换句话说,使用一个小但高质量的数据集比使用一个大但嘈杂的数据集能取得更多成果。对于大多数机器学习用例,基于非常大的数据集(比如数百万个观察值)构建模型是不切实际的,因为数据量根本不存在。换句话说,基于可用数据集太小而忽略机器学习作为解决某些问题的工具的潜在用途是很常见的。
但如果我们能够使用机器学习来解决基于更小数据集的问题,甚至小于 100 个观察值呢?这是数据中心运动试图通过系统性的数据收集和工程来解决的一个挑战。
对于大多数机器学习用例,你需要的算法已经存在。你输入数据(x)和依赖变量标签(y)的质量才是决定因素。传统上处理数据集中噪声的响应是尽可能获取更多数据以平均异常值。以数据为中心试图提高数据中的信号,这样就不需要更多的数据。
需要注意的是,数据中心性也为更大的数据解决方案标记了下一个前沿。无论你的数据集有多大或多小,它都是你机器学习解决方案的基础成分。让我们更深入地看看数据为中心的机器学习的不同方面。
数据中心性的起源
推动向更以数据为中心的机器学习开发方法迈进的是著名的数据科学先驱,安德鲁·吴博士。
吴博士是大型开放在线课程平台 Coursera 的联合创始人,同时也是斯坦福大学计算机科学系的兼职教授。他还是教育公司 DeepLearning.AI 和制造 AI 驱动的视觉检测平台 Landing AI 的创始人兼首席执行官。他之前在百度担任首席科学家,并曾是谷歌大脑团队的创始负责人。他在 Coursera 上关于各种机器学习主题的课程已被全球数百万学生完成。
尼古拉斯·吴博士和他的 Landing AI 团队构建了复杂的机器学习解决方案,例如用于检查制造质量的计算机视觉系统。通过这项工作,他们观察到以下特征是大多数机器学习机会的典型特征 3:
-
大多数潜在的机器学习用例依赖于小于 10,000 个观察值的数据集。通常很难或不可能添加更多数据以减少噪声的影响,因此提高数据质量对于这些用例至关重要。
-
即使在非常大的数据集中,数据子集也会表现出小型数据集的行为。例如,谷歌的搜索引擎每天生成数十亿次的搜索,但其中 95%的搜索是基于每月出现次数少于 10 次的关键词组合(在美国)。15%的每日关键词组合之前从未被搜索过 4。
-
当数据集较小时,通常比收集更多数据更快、更容易识别和去除数据中的噪声。例如,如果一个包含 500 个观察值的数据集有 10%的误标记观察值,通常比收集一组新的观察值更容易提高现有数据的数据质量。
-
机器学习解决方案通常建立在预训练模型和包的基础上,需要的调整或修改很少。通过提高数据质量来提高模型性能,通常比更改模型参数或添加更多数据产生更好的结果。
尼古拉斯·吴博士发布了一篇关于 Landing AI 成果的比较,阐述了我们刚才讨论的最后一点。
如图 1**.1所示,Landing AI 为他们的客户提供了三个缺陷检测解决方案。在所有三个案例中,团队创建了一个基线模型,然后分别尝试使用以模型为中心和以数据为中心的方法来改进这个模型:
图 1.1 – 应用以数据为中心的机器学习 – Landing AI 的结果(来源:与 Andrew 关于 MLOps 的对话:从以模型为中心到以数据为中心的 AI)
在所有三个例子中,Landing AI 团队通过采用以数据为中心的方法而不是以模型为中心的方法,能够实现最佳结果。在三个例子中的一个中,以模型为中心的技术在基线模型性能上实现了微小的 0.04%的提升,而在另外两个例子中,没有实现任何改进。
相反,提高数据质量始终会导致基线模型得到改善,在三个案例中的两个案例中,改善相当显著。Landing AI 团队花费大约两周时间迭代改进训练数据集,以实现这些结果。
尼古拉斯·吴博士的建议很明确:无论你的数据集大小如何,如果你想构建相关且具有影响力的机器学习模型,你必须投入大量精力系统地设计你的输入数据。
从逻辑上讲,更好的数据导致更好的模型是有道理的,Landing AI 的结果也为这一点提供了实证证据。现在,让我们来看看为什么数据中心性是机器学习发展的未来。
机器学习系统的组件
机器学习系统由三个主要部分组成:
以数据为中心的方法认为,系统性的数据工程是下一个机器学习突破的关键,原因有以下两点:
-
首先,一个模型的训练数据通常具有最大的改进潜力,因为它是任何模型的基础成分。
-
其次,机器学习系统的代码和基础设施组件比我们持续捕获高质量数据的方法和过程更先进。
在过去的几十年里,我们在机器学习算法、数据科学工具、计算和存储能力方面经历了巨大的演变,我们的数据科学解决方案的实施方法也通过诸如 机器学习 操作(MLOps)等实践而成熟。
开源工具如 Python 和 R 使得几乎任何有电脑的人都能相对便宜且容易地学习如何生成、调整和验证机器学习模型。这些工具的流行得益于大量可免费从公共库中安装的预构建包。这些包允许用户仅用几行代码就使用常见的机器学习算法。
在工具谱的另一端,低代码和无代码的 自动化机器学习(AutoML)工具允许那些具有有限或没有编码经验的非专家通过几次鼠标点击来使用机器学习技术。
云计算的发展为我们提供了弹性计算和存储能力,当需求增加时可以相对容易地进行扩展或缩减(注意可变成本!)。
换句话说,我们已经解决了围绕机器学习模型的技术约束中的许多问题。现在最大的机会在于提高输入数据的可用性、准确性、一致性、完整性、有效性和唯一性。
让我们更深入地看看原因。
数据是基础成分
想象一下,一个厨师想要创建一家世界知名的米其林星级餐厅。这位厨师花费了很长时间学习如何将风味和质地结合成令人愉悦的食谱,让顾客满意。经过多年的实践和磨练技艺,他们准备开设自己的餐厅。他们知道如何让餐厅成功。
在餐厅的前端,他们必须有一个布置得体的餐厅,舒适的家具,以让客人享受彼此的陪伴。为了服务客人,他们需要优秀的服务员,他们会关注顾客的每一个需求,确保订单被接收,酒杯被填满,餐桌保持干净整洁。
但不仅如此。一家成功的餐厅还必须拥有一个配备齐全的商业厨房,能够快速且一致地制作出许多菜肴,无论同时处理多少订单。当然,还有食物。厨师创造了一份充满精心制作的食谱的菜单,将为他们的客人提供独特而令人愉悦的风味体验。他们一切准备就绪,即将开设即将获奖的餐厅。
然而,在首演之夜,出现了一个问题。一些蔬菜在储藏室里长出了霉斑,它们必须被扔掉。一些香草和香料已经缺货,难以轻易获得。最后,菜单上最受欢迎的菜肴含有红卷心菜,但供应商只送来了绿卷心菜。因此,这些菜肴并不是令人愉悦的风味感觉,而是平淡无奇。厨师已经建立了一个完美的运营和一份精彩的菜单,但过于忽略了最重要的、最难控制的元素:食材。
食材是在餐厅外生产的,由几个不同的供应商运送。如果供应链的某个或某些部分未能交付,那么最终输出将受到影响,无论厨师多么有才华。
这家餐厅的故事说明了为什么采用更系统的方法来构建高质量数据集是构建更好模型的关键。
就像超级明星厨师需要最好的食材来制作出卓越的菜肴一样,数据科学家往往因为输入数据不够好或不够容易获取而无法构建高度有影响力的模型。我们不是有腐烂的蔬菜,而是有误标记的观察结果。我们不是缺货的食材,而是缺失的值。我们不是错误的卷心菜种类,而是具有有限预测能力的通用或高级标签。我们不是食品供应商的网络,而是大量数据源和技术平台,这些平台很少专门为机器学习而构建。
这种数据收集不成熟的部分原因与机器学习作为计算机科学领域其他学科相对能力的成熟度有关。对于只有表面了解机器学习的人来说,他们通常会将机器学习系统视为与传统软件应用相同的方式。
然而,与传统的软件不同,机器学习系统产生的是可变输出,这些输出取决于一组不断变化的数据输入。在机器学习中,数据是代码的一部分。这很重要,因为数据是影响最终模型输出的最大潜在因素。输入特征和观察的广度、深度和准确性是构建有影响力和可靠模型的基础。如果数据集不能代表你试图预测的现实世界人口或场景,那么该模型可能没有用。
同时,数据集将决定模型的大部分潜在偏差;也就是说,模型更有可能产生结果,错误地偏向某一群体而不是另一群体。简而言之,输入数据是 ML 模型中变化最大的来源,我们希望利用这种变化来发挥优势,而不是让它成为风险或障碍。
随着我们从数据转向算法,再到系统基础设施,我们希望 ML 系统越来越标准化和统一。采用以数据为中心的方法,我们希望在数据中保持大量的正确类型的可变性(而非噪声!)同时保持我们的 ML 算法和整体运营基础设施的稳健和稳定。这样,我们可以通过提高数据质量来迭代提高模型精度,同时保持其他一切稳定。
图 1.2 提供了与 ML 系统三个组成部分(数据、代码和基础设施)相关的各个方面的概述:
图 1.2 – ML 系统的组成部分
在以数据为中心的方法下,高质量的数据是稳健 ML 系统的基石。提高 ML 模型的最大机会通常在于输入数据,而不是代码。
虽然在模型参数的更改上关注数据质量很有道理,但数据科学家往往更关注后者,因为这更容易在短期内实施。在采用传统的以模型为中心的方法后,通常可以在非常短的时间内测试多个模型和超参数,但增加建模数据集中的信号并减少噪声似乎是一项复杂且耗时的练习。
在一定程度上,这是因为系统性地改进数据收集通常涉及上游流程的改变以及组织中各种利益相关者的参与。这通常是数据科学家无法单独完成的,需要整个组织认识到数据科学的价值和潜力,投入适当的时间和资源以改善数据收集。不幸的是,大多数组织在基于不良数据构建和实施次优模型上浪费的资源比收集更好数据的资源要多。
在接下来的章节中,我们将了解到,精心设计的数据中心方法可以克服这一挑战,并且通常在组织内部解锁许多新的 ML 机会。这是因为数据中心 ML 要求所有参与数据管道的人都更全面地思考组织数据的结构和目的。
为了进一步理解和欣赏以数据为中心的方法在模型开发中的潜力,让我们将数据中心性与更占主导地位的模式中心方法进行比较。
数据中心与模式中心 ML
到目前为止,我们已经确定数据中心化是关于系统地构建机器学习模型所使用的数据。传统的、更普遍的以模型为中心的机器学习方法认为,优化模型本身是提高性能的关键。
如 图 1*.3* 所示,以模型为中心的方法的核心目标是改进模型背后的代码。在以数据为中心的方法下,目标是找到在改进数据质量方面的更大提升空间:
图 1.3 – 通过以模型为中心和以数据为中心的工作流程构建机器学习解决方案
机器学习模型开发传统上主要关注通过优化代码来提高模型性能。在以数据为中心的方法下,重点转向通过迭代改进数据质量来实现更大的性能提升。需要注意的是,以数据为中心的方法建立在支撑以模型为中心的机器学习原则和技术之上,而不是取代它们。两种方法都将模型和数据视为机器学习解决方案的关键组成部分。如果其中任何一个配置不当、存在错误、有偏见或应用不当,解决方案就会失败。
在以数据为中心的方法下,模型配置是一个重要步骤,而在短期内,通过优化代码来寻求模型性能的增量提升无疑是更快的。然而,正如我们讨论的那样,如果你没有合适的原料,改变配方所能带来的提升是有限的。换句话说,两种方法之间的区别在于我们在迭代改进模型性能时,将重点放在哪里和投入多少努力。
如 图 1*.4* 所示,以模型为中心的方法将数据视为固定的输入,并专注于模型选择、参数调整、特征工程以及添加更多数据作为提高模型性能的主要方式。以数据为中心的方法认为模型相对静态,并主要关注通过提高数据质量来改善性能。
采用以模型为中心的方法,我们试图收集尽可能多的数据,以消除数据中的任何异常值并减少偏差——数据集越大越好。然后,我们设计我们的模型(们)尽可能具有预测性,同时避免过拟合。
这与以数据为中心的方法形成对比,后者在模型选择和调整的基础上,在数据收集和标注方面做得更好。通过异常值检测、程序化标注、更系统的特征工程和合成数据创建(这些技术将在后续章节中深入解释),数据质量得到进一步改善:
图 1.4 – 比较以模型为中心和以数据为中心的机器学习方法
机器学习模型改进来自两个方面:改进代码和改进数据。虽然数据收集和工程过程听起来像是数据工程师的工作,但它们实际上应该是数据科学家工具箱的关键部分。
让我们来看看在数据中心化方法下,数据科学家、数据工程师和其他利益相关者需要做什么。
数据中心化是一项团队运动
虽然关注数据质量而不是模型参数的变化很有意义,但数据科学家往往更关注后者,因为这在短期内更容易实施。在传统的以模型为中心的方法之后,通常可以在非常短的时间内测试多个模型和超参数,但增加建模数据集中的信号并减少噪声似乎是一项复杂且耗时的工作,难以由小团队轻松处理。数据中心化的机器学习需要整个组织投入更多的努力,而以模型为中心的方法在很大程度上依赖于数据科学家的技能和工具来提高模型性能。
数据中心化是一项团队运动。数据中心化要求数据科学家和参与机器学习开发的其他人员掌握一套新的数据质量特定技能。这些新数据中心化技能和技术中最重要的,就是我们将在本书中向您传授的。
数据捕获和标注过程必须以数据科学为出发点,并由至少对机器学习开发有基础理解的专业人士执行。数据工程过程和 ETL 层必须结构化,以识别数据质量问题,并允许迭代改进机器学习输入数据。所有这些都需要数据科学家、数据收集者、领域专家、数据工程师、商业领袖以及将数据转化为洞察力的人员之间的持续协作。
为了说明这一点,图 1*.5*比较了两种方法的数据到模型过程。根据您组织的规模和目的,可能涉及多种角色来交付机器学习解决方案,例如数据架构师、机器学习工程师、数据标注员、分析师、模型验证员、决策者、项目经理和产品所有者。
然而,在我们的简化图图 1**.5中,涉及三种类型的角色——数据科学家、数据工程师和领域专家:
图 1.5 – 数据中心化与模型中心化的角色和责任
数据管道顶部的利益相关者必须积极参与过程,以便组织在机器学习目的的数据收集和工程方面做得好。简而言之,数据中心化需要大量的团队合作。
在传统的以模型为中心的方法下,数据创建通常从数据收集过程开始,这可能包括自动化、手动或两者的混合。例如,客户在网页上输入详细信息,放射科医生进行 CT 扫描,或呼叫中心操作员接听录音电话。在这个阶段,数据已经为了其主要运营目的而被捕获,但通过数据工程师的工作,这些信息也可以被转换成分析数据集。典型的过程需要数据工程师从数据库、数据湖、数据仓库或等效系统中提取、转换和标准化数据。
一旦数据科学家掌握了数据,它通常会经过几个步骤以确保准确性、一致性、有效性和完整性得到保持。换句话说,数据应该准备好使用;然而,任何数据科学家都知道这很少是情况。
数据科学中的一个常见经验法则是,构建一个新的机器学习模型所需的时间的 80%用于寻找、清理和准备用于建模的数据,而只有 20%用于分析和模型构建。传统上,这被视为一个问题,因为数据科学家被支付工资来处理数据以构建模型和执行分析,而不是花大部分时间准备数据。
采用以数据为中心的方法,数据准备成为模型构建过程中的最重要部分。我们不是问“我们如何最小化数据准备所花费的时间?”,而是问“我们如何系统地优化数据收集和准备?”问题不在于数据科学家在学习和增强他们的数据集上花费了大量的时间。问题在于机器学习开发与其他上游数据活动之间的连接不足,这允许数据科学家、工程师和领域专家更快、更准确地共同创造结果。
从本质上讲,数据中心性是关于建立系统化执行这些工作的流程、工具和技术。领域专家积极参与机器学习开发的关键部分,包括识别异常值、验证数据标签和模型预测,以及开发应在数据中捕获的新特征和属性。
在以数据为中心的方法下,数据工程师和数据科学家也承担了额外的责任。数据工程师的责任必须从构建和维护数据管道扩展到更直接地参与开发和维护特定机器学习解决方案的高质量特征和标签。反过来,这要求数据工程师和数据科学家理解彼此的角色,并朝着共同的目标进行合作。
在下一节中,我们将通过应用实例来说明,以数据为中心的方法可以对机器学习机会产生的影响。
质量数据在机器学习中的重要性
到目前为止,我们已经定义了数据中心的机器学习是什么,以及它与传统的模型中心方法相比如何。在本节中,我们将探讨在实践中良好的数据是什么样的。
从数据中心的视角来看,良好的数据如下 5:
-
一致捕捉:独立变量(x)和依赖变量(y)被明确标记
-
充满信号且无噪声:输入数据覆盖了尽可能少的观察范围内的重要观察和事件
-
针对商业问题设计:数据是专门设计和收集的,用于解决使用机器学习(ML)的商业问题,而不是用现有数据解决任何问题
-
及时且相关:独立和依赖变量提供了对当前趋势的准确表示(无数据或概念漂移)
初看起来,这种系统性的数据收集似乎既昂贵又耗时。然而,根据我们的经验,高度审慎的数据收集通常是获得所需结果的机器学习的基础要求。
为了理解数据中心的必要性和潜力,让我们看看一些应用实例,了解数据质量和特征系统工程的系统性如何产生重大差异。
使用自然语言处理识别高价值法律案件
我们第一个关于数据质量关键重要性的例子来自乔纳斯和曼莫汉在一家大型澳大利亚法律服务公司构建的机器学习解决方案。
相比于银行、保险、公用事业和电信等类似服务行业,机器学习在法律服务中是一个新兴学科。这是由于法律服务中数据的性质和复杂性,以及在使用机器学习时的风险和伦理问题。
尽管法律服务行业数据极其丰富,但数据通常是通过人工收集的,以文本格式存储,并且高度依赖于具体法律案件的情况。这种文本数据可能以各种格式出现,例如医疗专业人士的信件、法律合同、交易对手通讯、律师与客户之间的电子邮件、案件笔记和音频录音。
此外,法律服务行业是一个高风险环境,其中任何一方犯的错误或遗漏都可能完全赢得或输掉案件。因此,法律专业人士往往花费大量时间和精力审查详细文件,并跟踪法律过程中的关键日期和步骤。魔鬼藏在细节中!
法律服务公司是一家无胜无费原告律师事务所,代表那些在身体或财务上受到伤害或被不公正对待的人。公司代表个人或团体对抗更强大的交易对手,如保险公司、疏忽的医疗或医生以及行为不端的公司。只有在客户获胜的情况下,客户才支付费用——否则,公司承担损失。
到 2022 年,业务部门发现了一个机会,可以利用数据科学来寻找罕见但高价值的案例,然后可以通过专业律师快速处理。越早识别这些高价值案例,效果越好。因此,目标是让它们在潜在客户的第一次面试中就被识别出来。
初始项目设计遵循了传统的以模型为中心的方法。数据科学团队收集了来自潜在客户访谈的两年案例笔记,并为后来证明是高价值案例的案例创建了标志(因变量,y)。团队还使用了主题建模来创建要包含在最终输入数据集中的新特征。主题建模是一种无监督机器学习技术,用于检测可以分组为主题的跨各种文档或文本片段的模式。然后,这些主题被直接输入到初始模型中,并作为解释模型预测的工具。
初始模型证明具有一定的预测能力,但团队面临了一些只能通过以数据为中心的方法才能解决的问题:
-
每年只开放不到一千个高价值案例,因此即使在过采样之后,这也是一个小数据问题。
-
主要预测因素来自案例笔记,这些笔记是半结构化或非结构化的格式,通常是自由文本。尽管案例笔记遵循某些标准,但每个记录者都使用了他们独特的词汇、缩写和格式,这使得创建标准化的建模数据集变得困难。
-
由于输入数据主要是自由文本格式,一些非常重要的信息对模型来说过于模糊,无法捕捉。例如,法律案件是否涉及多个人受伤这一点很重要,因为这可能会完全改变案件策略。有时,每个受伤方都会被明确指出,有时则只是被称作他们。
-
由于某些细节要么是法律专业人士假设的知识,要么是阅读整个文档的人显而易见的内容,因此这些细节被省略在案例笔记中。不幸的是,这对学习算法并没有帮助。
团队决定采取以数据为中心的方法,并组建了一个跨职能项目团队,包括一名技术高超的律师、一名数据科学家、一名数据工程师、一名运营经理和一名呼叫中心专家。团队中的每个人都精通整个流程的一部分,他们共同为客户体验、法律、数据和运营流程提供了广泛的深度和广度。
与通过特征工程提高模型精度不同,团队通过设计一套高度预测案件是否具有高价值的客户问题,完全改变了数据捕获方式。新问题的标准如下:
-
它必须提供关于案件是否具有高价值的非常具体的细节
-
格式必须易于人类和算法理解
-
必须让潜在客户容易回答新问题,并且呼叫中心操作员能够捕捉到信息
-
必须容易创建一个围绕捕获数据的分级流程,以便呼叫中心操作员可以立即采取正确的行动
之前提到的标准突出了在开发机器学习解决方案时涉及广泛领域专家的重要性。跨职能团队中的每个人都具有特定的知识,这些知识有助于整体解决方案的细节。
团队确定了一些关键问题,这些问题将高度预测一个案例是否为高价值案例。这些问题需要非常具体,以至于只能用是、否或数量来回答。例如,与其在自由文本字段中寻找单词他们,呼叫中心操作员可以简单地问*有多少人参与了事件?*并仅记录一个数字答案:
图 1.6 – 数据中心改进前后的假设案例笔记
回答了这些问题后,每个潜在案例都可以根据其成为高价值案例的高、中、低概率进行分组。然后,团队建立了一个简单的流程,允许呼叫中心操作员将高概率案例直接引导到由专业律师处理的高速通道流程。其他案例将继续使用机器学习模型进行监控,以检测可能将它们推入高价值领域的新的事实。
最终解决方案之所以成功,是因为它帮助更快、更准确地识别高价值病例,但采取以数据为中心的方法的好处远不止于此。对改进数据收集的关注不仅为机器学习创造了更好的数据,还促进了来自整个业务不同部门之间的一种不同类型的合作,最终导致了更明确的过程定义和对客户旅程关键时刻优化的更强关注。
预测紧急呼叫中的心脏骤停
另一个例子来自在丹麦哥本哈根的紧急医疗调度中心(EMDC)进行的实验研究 6。
由医学研究员斯蒂格·布洛姆伯格领导的一个团队致力于研究是否可以使用机器学习解决方案通过监听 EMDC 的电话来识别院外心脏骤停。
该团队使用 2014 年生成的紧急呼叫录音训练和测试了一个机器学习模型,主要目标是协助医疗调度员在早期检测心脏骤停呼叫。
研究发现,根据模型灵敏度测量的结果,机器学习解决方案在识别心脏骤停病例方面更快、更准确。然而,研究人员也发现,在采用以模型为中心的方法时存在以下局限性:
-
由于救护车医护人员和调度员之间没有结构化的反馈能力,系统中缺乏学习。例如,通过向呼叫者提出定制和更结构化的问题,如“他看起来苍白吗?”或“他能 动吗?”,很可能会提高人类和机器对心脏骤停的预测。
-
非母语说话者的语言障碍影响了模型性能。机器学习解决方案在丹麦语说话者中表现最佳,在识别外国口音的电话中的心脏骤停方面不如可能说多种语言的人类调度员。
-
尽管该解决方案的敏感性(检测真实阳性)高于人类调度员,但不到五分之一的通知是真实阳性。这导致调度员中出现了高程度的警报疲劳,他们最终承担着根据机器学习建议行动或不行动的风险。
这个案例研究是机器学习用例的一个典型例子,它需要以数据为中心的方法来实现最佳结果,同时适当管理风险和伦理问题。
首先,由于底层问题的性质和复杂性,一个用于分类心脏骤停电话的机器学习解决方案将始终基于少量数据。在这种情况下,仅仅增加数据并不一定能提高模型性能。
在哥本哈根大区,每年大约有 1,000 起心脏骤停报告,而该地区的人口约为 180 万,即使是几年的通话录音也无法构成一个大数据集。一旦考虑到数据中的许多子集,如外语说话者和非母语口音的人,数据变得更加碎片化。
与生产错误预测(尤其是假阴性)相关的风险和伦理问题,尤其是在生死攸关的情况下,意味着数据标签必须经过仔细的整理,以确保任何偏差都降低到可接受的最低限度。这需要通过审查数据质量和增强模型功能来进行的迭代过程。
基于简短的电话对话对心脏骤停案例进行分类是一项复杂的任务。它需要专业知识,以及调度员和医护人员 alike 的培训和经验。为机器学习目的构建高质量的自然语言数据集主要关于减少对所寻找信号的解读的模糊性。这反过来又要求组织在设计中涉及专业知识专家,以确定建模过程中什么是重要的。您将在第四章,“数据标注是一个协作过程”,了解如何做到这一点。
在提问和回答的方式上具体明确,可以为人类代理(在这种情况下,是调度员)以及 ML 模型提供清晰性。这个例子突出了以数据为中心不仅关乎为 ML 模型收集更好的数据。这是一个黄金机会,可以更谨慎地定义和改进组织内部人们的工作和协作方式。
你刚刚阅读的两个案例研究突出了精心收集和整理数据集以保持高准确度、有效性和上下文相关性的重要性。在某些情况下,数据质量可能关乎生死!
正如你将在第二章**从模型中心到数据中心 – ML 的演变中了解到的那样,只要我们能管理好与数据质量相关的风险,ML 在法律服务和医疗保健等高风险领域有很大的潜力成为一款出色的工具。
现在我们已经讨论了数据为中心的 ML 的不同方面,让我们总结一下本章我们学到了什么。
摘要
在本章中,我们讨论了以数据为中心的机器学习(ML)的基本原理及其起源。我们还学习了以数据为中心与以模型为中心的区别,包括在典型组织中使用 ML 的关键利益相关者的角色和责任。到这一点,你应该对以数据为中心的 ML 及其与传统模型中心方法相比的额外潜力有一个扎实的理解。希望这能鼓励你在下一个项目中使用以数据为中心的 ML。
在下一章中,我们将探讨为什么 ML 开发至今一直以模型为中心,并进一步探讨为什么数据中心性是 AI 演变下一阶段的关键。
参考文献
-
datacentricai.org/,观看日期:2022 年 7 月 10 日 -
www.andrewng.org/和www.coursera.org/instructor/andrewng,观看日期:2022 年 7 月 6 日 -
www.youtube.com/watch?v=06-AZXmwHjo,观看日期:2022 年 8 月 2 日 -
ahrefs.com/blog/long-tail-keywords/,观看日期:2022 年 8 月 2 日 -
来自 与 Andrew 关于 MLOps 的对话 – 从模型中心到数据中心 AI:
www.youtube.com/watch?v=06-AZXmwHjo,观看日期:2022 年 8 月 2 日 -
Zicari 等人:在医疗保健中评估可信 AI:作为识别紧急呼叫中心心脏骤停的支持工具的机器学习的最佳实践。Frontiers in Human Dynamics (2021)
第二章:从模型为中心到数据为中心——机器学习的演变
到现在为止,你可能已经在想:如果数据中心性对于人工智能和机器学习的进一步发展至关重要,那么为什么模型中心性是主导方法?
这是一个非常相关的问题,我们将在本章中回答。为了了解转向数据中心方法需要什么,我们必须了解导致模型中心性成为主导方法的力量,以及如何克服它们。
我们将首先探讨为什么人工智能和机器学习的演变主要遵循以模型为中心的方法,然后深入探讨通过数据中心性可以解锁的巨大机会。
在本章中,我们将挑战机器学习需要大数据集以及更多数据总是更好的观念。当我们从“更大数据”转向“更好数据”时,会出现一系列小数据机器学习用例的长尾。
到本章结束时,你将清楚地了解机器学习至今的发展历程,并知道如何在此基础上构建并使用机器学习取得更好的成果。
在本章中,我们将涵盖以下主要主题:
-
探讨为什么机器学习的发展最终变成了以模型为中心
-
小数据机器学习的机会
-
为什么我们比以往任何时候都需要以数据为中心的机器学习
探讨为什么机器学习的发展最终变成了以模型为中心
为了真正理解为什么以数据为中心的方法是释放机器学习(ML)全部潜力的关键,我们需要上一堂简短的历史课。
数据科学和机器学习领域自从最早尝试让电子计算机表现出“智能”行为以来,已经取得了显著的进步。今天大多数智能手机所执行的“智能”任务,在 21 世纪初几乎无法想象。此外,我们每天产生的数据量比从人类文明开始到 21 世纪所创造的数据量还要多——而且我们以每年约 23%的增长率在这样做。
尽管在技术和数据量方面取得了这些令人难以置信的进步,但数据科学的一些元素非常古老。统计学和数据分析已经使用了几个世纪,而今天机器学习模型的数学组成部分大多是在数字计算机出现之前开发的。
就我们的目的而言,机器学习和人工智能的历史始于第二次世界大战期间首次电子计算机的引入。
1940 年代至 1970 年代——早期阶段
历史学家和前美国陆军军官 Adrian R. Lewis 在他的书《美国的战争文化》中写道:“战争创造了技术巨大进步的条件……如果没有战争,人们不会在几小时内穿越海洋,在太空中旅行,或者用微波炉爆米花 2。”
这确实是在第二次世界大战期间,以及随后的几十年里。计算机科学、密码学和硬件技术取得了巨大的飞跃,因为世界各地的战斗国家在全球各个战线上相互竞争以获得主导地位。
在 20 世纪 40 年代和 50 年代,编译器、半导体晶体管、集成电路和计算机芯片等创新使得数字电子计算机能够执行更复杂的过程(在此之前,计算机主要是指那些被雇佣来执行复杂计算的计算天赋异禀的人类的工作职位 3)。这反过来又导致了今天机器学习模型的一些早期创新。
在 1943 年,美国科学家沃尔特·皮茨和沃伦·麦克库洛赫创造了世界上第一个神经网络计算模型。这为人工智能的其他创新奠定了基础,包括 1952 年亚瑟·塞缪尔的自改进国际象棋程序和由美国海军和 IBM 资助的 1958 年的感知器,这是一个用于图像分类的神经网络。
在 1950 年,英国数学家和计算机科学家艾伦·图灵提出了用于评估计算机执行与人类相当智能操作的图灵测试。这个测试常被用作衡量计算机智能的基准,并对人工智能的一般哲学产生了深远的影响。
机器学习研究在 20 世纪 60 年代继续扩展,其中最近邻算法的发展是最显著的进步之一。斯坦福研究人员托马斯·科弗和彼得·哈特的工作为 k-最近邻算法作为一种强大的统计分类方法的出现奠定了基础 4。
在 1965 年,Fairchild 半导体和 Intel 的联合创始人戈登·摩尔提出了一个观点,即计算机的处理能力和硬盘存储容量每两年会翻一番,这也被称为摩尔定律5。尽管摩尔定律被证明是相当准确的,但要达到一个可以以合理速度和成本处理大量数据的地步,还需要许多十年。
为了更清楚地说明问题,IBM 在 1970 年的主要产品是 System/370 Model 145,它有 500 KB 的 RAM 和 233 MB 的硬盘空间 6。这台计算机占据了整整一个房间,成本为 705,775 美元到 1,783,000 美元 7,约合今天的 5 到 13 百万美元。在撰写本文时,最新的 iPhone 14 的 RAM 是 System/370 Model 145 的 12,000 倍,硬盘空间高达 2,200 倍,具体取决于 iPhone 的配置 8。
图 2.1 – IBM System/370 Model 145. 这张图片中的所有东西都是计算机操作的一部分(除了墙上的时钟)。来源:Jean Weber/INRA, DIST
20 世纪 70 年代的大部分时间被广泛认为是“人工智能寒冬”的时期——在这个时期,人工智能领域几乎没有突破性的研究或发展。商业界对人工智能的短期潜力看得很低,主要是因为计算机处理能力和数据存储容量尚未充分发展,且成本高昂。
1980 年代到 1990 年代——个人电脑和互联网的兴起
1982 年,IBM 推出了第一台个人电脑(IBM PC),这引发了工作场所和人们家庭中计算机技术的革命。它还导致了苹果、微软、惠普、英特尔和其他许多硬件和软件企业如流星般崛起,这些企业乘上了技术创新的浪潮。
处理和信息的数字化能力的提高也加剧了企业界对使用存储数据进行分析目的的兴趣。关系型数据库成为主流,以牺牲网络和层次数据库模型为代价。
SQL 查询语言在 20 世纪 70 年代开发;在整个 80 年代,它成为主要的数据库语言,并在 1986 年获得了 ISO 和 ANSI 认证。
数字信息的爆炸性增长需要新的技术来从统计角度理解数据。斯坦福大学的研究人员在 1984 年开发了第一个生成分类和回归树的软件,而像 WordNet 这样的词汇数据库创新为文本分析和自然语言处理奠定了早期基础。
个人电脑继续在 20 世纪 90 年代取代打字机和大型机,这促使万维网在 1991 年形成。网站、博客、互联网论坛、电子邮件、即时消息和 VoIP 电话又引发了数据量、种类和速度的另一次爆炸性增长。
因此,新的方法逐渐发展起来,用于组织更复杂和不同类型的数据。例如,AdaBoost 和梯度提升机等梯度提升算法在 90 年代末由斯坦福研究人员开发,为搜索引擎对各种信息进行排序铺平了道路。
互联网的兴起也为那些能够组织其上信息的人创造了巨大的商业机会。在此期间,亚马逊、阿里巴巴、雅虎和谷歌等公司成立,争夺电子商务和网页搜索的主导地位。这些公司看到了计算机科学、人工智能和机器学习的巨大潜力,并大量投资于开发算法来管理他们庞大的信息库。
2000 年代——科技巨头的崛起
机器学习研究在 2000 年代全速前进,无论是在大学还是在企业的研发部门(R&D)。计算机处理能力终于达到了大多数公司和研究人员可以进行大规模数据处理的水平。
当互联网搜索引擎提供商忙于开发算法来排序和分类在线发布的不断增长的信息时,大学研究人员正在创造新的工具和技术,这些工具和技术将推动机器学习的演变。
2003 年,R 基金会成立,旨在开发和支持开源机器学习工具和编程语言 R。作为统计计算和图形的免费开源编程语言,R 显著降低了研究人员在工作中使用统计编程的门槛,以及数据爱好者练习和学习机器学习技术的门槛。
随机森林算法于 2001 年推出,并在 2006 年由加州大学伯克利分校的统计学家和机器学习先驱 Leo Breiman 以及犹他州立大学的 Adele Cutler 获得专利。
斯坦福大学教授李飞飞在 2008 年推出了 ImageNet 项目,作为一个免费和开放的图像数据库,用于训练对象识别模型。该数据库的创建是为了提供一个高质量、标准化的数据集,用于训练和基准测试对象分类模型。截至撰写本文时,ImageNet 包含超过 1400 万张标记的图像,按照 WordNet 层次结构组织。
这个时期也见证了基于网络的商业模式如雨后春笋般崛起,成为创造互联网霸权的一种方式。LinkedIn、Facebook、Twitter 和 YouTube 等社交媒体平台在这个时期推出,并利用机器学习算法组织用户创建的信息和内容,成为了跨国的科技巨头。
随着数据量的激增,对廉价且灵活的数据存储的需求也随之增加。AWS、Dropbox 和 Google Drive 等云计算和存储服务应运而生,而大学与谷歌和 IBM 合作建立了服务器农场,用于数据密集型研究。日益增多的是,处理能力的可用性现在基于用户的成本效益而非技术限制。
2010 年至今 – 大数据推动人工智能创新
基于网络的商业模式继续为互联网和机器学习的发展指明方向。搜索引擎、社交媒体平台以及软件和硬件提供商在围绕人工智能的 R&D 活动上投入了大量资金。例如,谷歌大脑研究团队成立于 2011 年,旨在提供关于大数据的前沿人工智能研究。
新的基于网络的公司正在颠覆出租车、酒店、旅游服务、支付、餐饮和食品服务、媒体、音乐、银行、消费零售和教育等行业,利用数字平台、机器学习和大量消费者数据作为其强大的竞争优势。
传统研究机构与大型科技公司紧密合作,在音频和图像识别、自然语言理解、异常检测、合成数据生成等领域,深度学习技术取得了重大突破。
到 2017 年,在年度 ImageNet 挑战赛中,四分之三的参赛团队达到了 95%以上的准确率,证明了图像识别算法现在已经非常先进。
在人工智能的黄金十年中,也开发了用于生成新数据的有力算法。2014 年,谷歌大脑团队的一名研究人员伊恩·古德费洛发明了生成对抗网络(GAN),这是一种通过配对两个模型相互竞争来工作的神经网络 15。另一种生成模型框架,生成预训练转换器(GPT),在 2018 年由 OpenAI 研究实验室推出。
在生成模型运行的情况下,现在可以产生类似人类的输出,如文本片段、图像、艺术品、音乐和深度伪造——对某人声音和举止的音频和视频模仿。
随着“大数据”、“机器学习”和“人工智能”成为日常用语的一部分,对分析师、数据科学家、数据工程师和其他数据专业人士的需求大幅增加。2011 年,数据科学家的职位空缺年增长率为 1500%16。对数据和机器学习潜力的巨大热情导致分析先驱汤姆·达文波特和 DJ 帕蒂尔将数据科学称为 21 世纪的最具魅力的工作17。
全世界成千上万的数据爱好者都在寻找学习最新机器学习和数据挖掘技术的地方。Kaggle 和 Coursera 等平台允许数百万用户通过公开在线课程学习,参加机器学习竞赛,访问高质量数据集,并分享知识。
在工具方面,R、Python 或 SQL 上运行的免费可下载软件程序和包的激增,使得以低成本访问高级数据科学技术变得相对容易:
图 2.2 – 从 1940 年到现在的机器学习历史
在 2010 年至 2020 年人工智能的黄金十年中,随着信息技术、数据和人工智能的进步,机器学习模型架构已经显著成熟。此时,大多数创造更好模型的机会在于提高数据质量。
以模型为中心是逻辑上的进化结果
数据科学历史的最后八十年遵循了一条逻辑的进化路径,导致以模型为中心成为机器学习的主要方法。
机器学习背后的思想和数学概念在技术成熟到足以与之匹配之前就已经被构想出来了。在 20 世纪 90 年代之前,计算机的运算能力不足以让大学研究人员显著地发展机器学习领域。这些技术限制还意味着,在这一时期,私营企业为了商业利益进行的有限研究。
在 20 世纪 90 年代初互联网时代到来之际,硬件和软件解决方案开始变得足够先进,足以消除这些古老的限制。互联网还引发了一场信息革命,极大地增加了可用数据的数量和种类。突然之间,机器学习不仅在经济上可行,而且成为亚马逊、雅虎和谷歌等科技公司背后的驱动力。随着比以往任何时候都更多的数字信息可用,我们需要推进我们解释和建模各种数据的方式。换句话说,机器学习研究首先需要以模型为中心的关注点。
在 2000 年代,一种新型的商业模式开始主导我们的生活。基于网络的数字业务,如社交媒体平台、搜索引擎、软件创造者和在线市场,为用户提供了创建和互动内容与产品的平台。通过将机器学习应用于大量用户生成数据,这些业务在过程中观察并优化了每一次互动。
这些“以 AI 为先”的大型科技公司受数据质量或数量的限制较小。它们的限制主要在于快速且经济的计算和存储能力,以及机器学习技术的复杂性。通过内部研究、与大学的合作以及战略投资于有希望的 AI 技术,大型科技公司在过去二十年里能够推动机器学习发展的议程。这些公司最需要的是以模型为中心的方法。
自 1995 年中叶以来,由于以模型为中心的研究,我们现在拥有了能够组织全世界信息的算法,识别人群中的个体,在开放交通中驾驶车辆,识别和生成声音、语音和图像,以及更多。由于这一创新时期,我们根据输入数据构建准确模型的能力非常先进。
随着数据成为更普遍的资产,突然出现了对更多数据科学家和其他数据专业人士的强烈需求。如今,通过在线学习平台、大学课程和机器学习竞赛,学习机会并不缺乏,但它们通常有一个共同点:初始输入数据集是预定义的。
在固定数据集上教授机器学习是有意义的。没有可复制的输出,很难验证学习者是否掌握了特定的技术,或者将不同的模型相互基准测试。然而,自然的结果是,学习以通过模型为中心的任务为中心,例如模型选择、超参数调整、特征工程和对现有数据集的其他增强。
经验丰富的数据科学家必须掌握以模型为中心的技能,但它们只是以数据为中心范式的基础。这是因为机器学习的进步分为四个部分:
-
提高计算机能力
-
改进算法
-
提高数据
-
提高测量
到目前为止,我们在第 1 点和第 2 点上取得了巨大的进步,到了它们在很大程度上已成为大多数机器学习用例的解决方案。现在,大部分机会在于我们改进数据和质量的方法。当我们改进我们的数据时,我们可以构建更好的模型,但我们还解锁了那些通常因为只有几千行(或更少)数据而无法触及的机器学习用例的长尾。
解锁小数据机器学习的机会
被作者艾米·韦伯 18 称为“九大巨头”的科技企业群体是利用大数据和人工智能建立全球主导地位的消费互联网公司的典型例子。亚马逊、苹果、阿里巴巴、百度、Meta、谷歌、IBM、微软和腾讯在数字时代占据主导地位,因为他们利用了大量的用户数据来驱动他们的 AI 系统。
作为基于网络的“AI 优先”企业,他们以前所未有的规模积累了客户,因为用户乐于共同创造并分享他们的数据,只要这对他们有净收益。对于九大巨头来说,获取足够的建模数据很少是问题,投资于最先进的机器学习能力是一个良性循环,这有助于增强市场主导地位。
对于大多数其他组织和机器学习用例来说,这种规模是无法实现的。正如我们在第一章中探讨的,探索数据中心的机器学习,机器学习机会的长尾并不提供在大量训练数据上构建模型的选择,因为以下挑战:
-
缺乏训练数据观察:长尾中的数据集较小——通常只有几千行或更少。此外,大多数组织在非数字化的物理世界中捕获数据,这使得捕捉和微调某些数据点变得更加困难。
-
脏数据:与基于网络的“AI 优先”企业不同,大多数组织通过大量不同的来源生成数据,如内部(但外部开发)IT 系统、第三方平台以及员工或客户的手动收集。这创造了一个复杂的数据来源拼凑,伴随着各种数据质量挑战。
-
高风险领域的偏差和不公平风险:在医疗保健、法律服务、教育、公共安全和犯罪预防等高风险领域,数据质量差可能导致对个人或弱势群体产生灾难性的影响。例如,根据医学图像预测一个人是否患有癌症是一项高风险活动——根据你的 YouTube 观看历史推荐下一个视频则不是。
-
模型复杂性和缺乏规模经济:尽管在长尾中可以找到大量价值,但单个机器学习项目通常需要大量定制来处理不同的场景。定制成本高昂,因为它会创建许多模型、数据集和流程的积累,这些必须在模型实施前后维护。
-
在数据和模型开发中需要领域专业知识:小数据集、高风险和更复杂场景的结合使得在数据收集、标注和验证、模型开发和测试过程中,没有领域专家的参与很难构建机器学习模型。
重要的是要注意,许多公司有机会通过小数据机器学习解锁重大价值。例如,只有少数组织会有价值 5000 万美元或以上的单个机器学习项目,但许多组织会有 50 个潜在的价值 100 万美元的机器学习机会。在实践中,这意味着如果我们想让小型项目变得可行和具有财务可行性,我们必须从我们的原材料中获得最大价值。
安德鲁·吴博士,Landing AI 的 CEO 和创始人,将这些挑战总结如下 19:
“在消费者软件互联网中,我们可以训练几个机器学习模型来服务十亿用户。在制造业,可能有 1 万家制造商正在构建 1 万家定制的 AI 模型。”
“在许多行业中,由于大型数据集根本不存在,我认为重点必须从大数据转向优质数据。拥有 50 个精心设计的示例就足以向神经网络解释你希望它学习的内容。”
图 2.3 展示了小数据机器学习的挑战和机遇。虽然大数据/高价值机器学习用例的低垂之果已被“以 AI 为先”的企业摘取,但小数据/中等价值的长尾尚未得到充分利用。实际上,大多数机器学习用例存在于小数据集和低规模经济的长尾中。当数据集较小时,需要高度重视数据质量,以使机器学习变得有用:
图 2.3 – 机器学习机会的长尾
在下一节中,我们将探讨处理更小、更复杂的数据集的挑战,以及如何克服这些挑战。
为什么我们比以往任何时候都需要以数据为中心的人工智能
自世纪初以来,人工智能的领先组织,如“大九”,在机器学习方面取得了惊人的成果,但人工智能在长尾中的应用情况如何?
2020 年由麻省理工学院斯隆管理评论和波士顿咨询集团发布的一项调查得出结论,大多数公司都难以将他们对人工智能的愿景变为现实。在对来自 112 个国家的 29 个行业的 3000 多名商业领袖的调查中,70%的受访者理解人工智能如何创造商业价值,57%的人已经试点或生产化了人工智能解决方案。然而,只有十分之一的人能够通过人工智能产生显著的财务收益 20。
调查作者发现,那些通过人工智能实现显著财务收益的公司,他们的成功建立在两个支柱之上:
-
他们拥有正确的数据、技术和人才坚实的基础。
-
他们定义了人类和人工智能共同工作和学习的一些有效方式。换句话说,他们创造了一个人类与人工智能之间的迭代反馈循环,从数据收集和整理到解决方案部署。
为什么这两个支柱对机器学习和人工智能的成功至关重要?因为机器学习模型只是机器学习系统的一小部分。
在 2015 年,谷歌研究人员 Sculley 等人 21 发表了一篇开创性的论文,名为《机器学习系统中的隐藏技术债务》,在其中他们描述了“只有一小部分现实世界的机器学习系统由机器学习代码组成……所需的环境基础设施庞大且复杂”。
在传统的信息技术术语中,技术债务指的是在软件开发生命周期中走捷径所造成的长期成本。它包括硬编码的逻辑、缺失的文档、与其他平台的集成不足、代码效率低下,以及其他任何阻碍系统性能提升和未来改进的因素。技术债务可以通过消除这些问题来“偿还”。
机器学习系统不同之处在于它们可以在代码中承载技术债务,但它们还增加了技术债务可能存在于系统数据组件中的复杂性。输入数据是系统的基础成分,数据是可变的。由于机器学习模型是由数据和代码中许多特征加权影响驱动的,一个变量的变化可能会改变模型其余部分的逻辑结构。这也被称为 CACE 原则:改变任何东西都会改变一切。
如图 2.44 所示,一个商业化的机器学习系统远不止模型代码。在一个典型的机器学习项目中,估计只有 5-10%的系统是模型代码 22。其余的 90-95%的解决方案与数据和基础设施相关:
图 2.4 – 机器学习系统远不止代码。来源:改编自 Sculley 等人,2015
如 Sculley 等人所描述,机器学习解决方案中的数据收集和整理活动通常比直接模型开发活动资源密集得多。鉴于这一点,数据工程应该是数据科学家的最佳伙伴。然而,数据质量的重要性与大多数机器学习解决方案在实际开发中的发展之间存在脱节。
数据质量级联效应
在 2021 年,谷歌研究人员 Sambasivan 等人对来自美国、印度、东非和西非的 53 名机器学习从业者的实践进行了研究。研究参与者来自高风险领域,如医疗保健、农业、金融、公共安全、环境对话和教育。
研究的目的是确定和描述数据质量对机器学习系统的影响,并展示他们所说的数据级联的实证证据——由数据质量问题引起的累积负面效应。
数据级联是由传统的以模型为中心的机器学习实践引起的,这些实践低估了数据质量,通常会导致对模型性能的不可见和延迟影响——换句话说,这是机器学习特有的技术债务。根据研究人员的说法,数据级联非常普遍,研究中 92%的机器学习从业者在一个特定项目中经历过一个或多个数据级联。
数据级联的原因可以分为以下小节中解释的四个类别。
数据工作的感知价值低和缺乏奖励系统。
在机器学习机会的长尾中,数据不可用通常有两个潜在原因:
-
首先,正在建模的事件是定制化和罕见的,因此对于特定用例可以收集的数据量有一个物理限制。
-
其次,数据收集和整理活动被认为是相对昂贵和困难的,尤其是在涉及手动收集时。
事实上,大多数与数据相关的工作并不是由数据科学家完成的。直接负责创建、收集和整理数据的角色通常将这些任务作为他们工作中的次要职责。由于优先级竞争、时间限制、收集系统的技术限制或简单地缺乏如何进行良好数据收集的理解,收集高质量数据的责任经常与其他职责相冲突。
以医院护士为例,他们负责与患者护理相关的各种任务,其中一些是数据收集。如果可以通过机器学习进行聚合和推广,高质量的健康数据有可能为全球的患者和医疗保健提供者创造巨大的利益。然而,对于个别护士来说,完成记录患者状况和医疗干预措施所需的最少工作有更大的激励,这样就可以有更多的时间用于初级患者护理。这种场景的典型结果是数据收集在细节深度和标签一致性方面表现不佳。
机器学习实践者在下游也面临着类似的挑战。Sambasivan 等人描述了商业和项目目标,如成本、收入、上市时间和竞争压力如何导致数据科学家匆忙进行模型开发,从而为数据质量和伦理问题留下不足的空间。正如一位实践者所说,每个人都想做模型工作,而不是 数据工作。
缺乏跨职能协作
当涉及到高风险或定制化的机器学习项目时,领域专家通常在数据收集的上游阶段是关键参与者,同时也是模型输出的最终消费者。
表面上看,领域专家应该非常愿意积极参与机器学习项目,因为他们能够获得有用模型的益处。然而,情况往往相反。
为了机器学习目的而收集额外信息的要求通常意味着数据收集者和编纂者必须更加努力地完成工作。对于数据素养有限的前线工作者来说,可能很难理解数据收集的重要性,而且不幸的是,这种行为的级联效应往往在项目生命周期后期显现出来——通常是在部署之后。
数据科学家在数据收集中也应该扮演关键角色,因为他们将在模型开发过程中做出许多关于如何解释和操作数据集的决定。因此,机器学习实践者对特定领域的技术和社会背景的好奇心和愿意理解是任何项目成功的关键部分。它是使机器学习解决方案相关和准确的无形粘合剂。
不幸的是,数据科学家通常缺乏特定领域的专业知识,并依赖领域专家来验证他们对数据集的解释。如果机器学习实践者不不断质疑他们的假设,过度依赖他们的技术专长,并且理所当然地认为输入数据的准确性,他们就会错过他们试图建模的上下文的细微之处。当这种情况发生时,机器学习项目将遭受数据级联的影响。
跨职能协作不足会导致成本高昂的项目挑战,例如额外的数据收集、结果误解释以及对机器学习作为特定问题相关解决方案的信任缺失。
机器学习实践者的教育和知识差距
即使是最技术娴熟的机器学习实践者,如果他们缺乏机器学习管道的端到端知识,也可能无法为现实场景构建有用的模型。不幸的是,大多数数据科学家的学习路径都缺乏对数据工程实践的适当关注。
研究生课程和在线培训课程建立在干净的数据集之上,但现实生活中充满了脏数据。数据科学家并没有接受过从头开始构建机器学习解决方案的训练,包括数据收集设计、数据管理和数据治理流程、培训数据收集者、清理脏数据和构建领域知识。
因此,数据工程和 MLOps 实践被那些直接负责将原始数据转化为有用洞察的人理解不足且评价不高。
缺乏对数据质量的测量和问责
传统的 ML 实践依赖于统计准确性测试,如精确度和召回率,作为模型和数据质量的代理。这些措施并不提供关于数据集质量直接信息的任何直接信息,这些信息与表示特定事件和相关的情境背景相关。在过程早期缺乏标准化的方法来识别和纠正数据质量问题,使得数据改进工作变得反应性,而不是有计划地与项目目标保持一致。
广泛使用的管理短语“衡量什么,管理什么”在数据质量环境中也是正确的。如果没有适当的流程来识别数据质量问题,就难以激励和分配个人对良好数据收集的责任。
在高风险领域对数据质量进行问责的重要性得到了这样一个事实的支持:模型准确性通常必须非常高,基于小数据集。例如,在低风险且数据丰富的行业,如在线零售或数字广告,由于数据收集的自动化和持续性,一个表现不佳的模型可以相对快速地修改。
在长尾部署的 ML 模型通常更难以验证,因为事件发生的频率要低得多。同时,高风险领域通常要求更高的模型准确性阈值。在线广告商可能可以接受 75%的准确性分数,但用于癌症诊断的模型通常必须具有低于 1%的错误率才能具有可行性。
避免数据级联和技术债务
数据级联的普遍性突显了一个更大的潜在问题:ML 开发中的主导惯例是从大数据公司的实践中借鉴的。这些实践是在数据丰富且可消耗的环境中开发的,每个用户都有一个账户 24。结合“快速行动,打破事物”25 的文化,将数据工作视为不受欢迎的苦差事,这种做法在大多数高风险领域都会失败。
数据质量不佳的级联效应难以透明化和以标准化的方式追踪,尽管它们频繁发生且持续存在。幸运的是,数据级联也是可以修复的。Sambasivan 等人将“数据卓越”定义为解决方案:一种文化转变,将数据管理视为核心业务学科,并为 ML 管道中的相关人员建立正确的流程和激励机制。
作为数据专业人士,我们决定 ML 是否应该继续成为少数人的工具,或者是否是时候允许具有较小财务价值或更高风险的项目变得可行。为此,我们必须努力追求数据卓越。
现在,让我们总结本章的关键要点。
摘要
在本章中,我们回顾了机器学习的历史,以帮助我们清楚地理解为什么以模型为中心的机器学习是当今占主导地位的方法。我们还学习了以模型为中心的方法如何限制我们从机器学习机会的长河中释放潜在价值。
到目前为止,你应该已经深刻理解为什么数据中心化对于机器学习学科实现其全部潜力是必要的,同时也认识到这将需要巨大的努力来实现这一转变。要成为一名有效的数据中心化机器学习实践者,必须打破旧习惯并形成新习惯。
现在,是时候开始探索实现这一转变的工具和技术了。在下一章中,我们将讨论数据中心化机器学习的原则以及与每个原则相关的技术和方法。
参考文献
-
www.idc.com/getdoc.jsp?containerId=prUS47560321,于 2022 年 9 月 23 日查阅 -
Lewis, A. R., 2006, 《美国的战争文化》,Routledge,纽约,美国
-
www.nasa.gov/feature/when-the-computer-wore-a-skirt-langley-s-computers-1935-1970,于 2022 年 9 月 23 日查阅 -
www.historyofdatascience.com/k-nearest-neighbors-algorithm-classification-and-regression-star/,于 2022 年 9 月 23 日查阅 -
large.stanford.edu/courses/2012/ph250/lee1/docs/Excepts_A_Conversation_with_Gordon_Moore.pdf,于 2022 年 9 月 23 日查阅 -
www.businessinsider.com/ibm-1970-mainframe-specs-are-ridiculous-today-2014-5,于 2022 年 9 月 22 日查阅 -
www.ibm.com/ibm/history/exhibits/mainframe/mainframe_PP3145.html,于 2022 年 9 月 22 日查阅 -
www.apple.com/au/iphone-14/specs/,于 2022 年 9 月 23 日查阅 -
www.quickbase.com/articles/timeline-of-database-history,于 2022 年 9 月 24 日查阅 -
www.dataversity.net/brief-history-database-management/,于 2022 年 9 月 24 日查阅 -
www.r-project.org/about.html,于 2022 年 9 月 24 日查阅 -
www.historyofdatascience.com/leo-breiman-statistics-at-the-service-of-others/,于 2022 年 9 月 24 日查阅 -
www.image-net.org/about.php, 查阅于 2022 年 9 月 24 日 -
www.dataversity.net/brief-history-cloud-computing/, 查阅于 2022 年 9 月 25 日 -
thenextweb.com/news/2010-2019-the-rise-of-deep-learning, 查阅于 2022 年 9 月 25 日 -
www.dataversity.net/brief-history-data-science/, 查阅于 2022 年 9 月 25 日 -
hbr.org/2012/10/data-scientist-the-sexiest-job-of-the-21st-century, 查阅于 2022 年 9 月 25 日 -
Webb, A., 2019, The Big Nine: How Tech Titans and Their Thinking Machines Could Warp Humanity, Hachette Book Group, 纽约,美国
-
spectrum.ieee.org/andrew-ng-data-centric-ai, 查阅于 2022 年 9 月 25 日 -
Ransbotham, S., Khodabandeh, S., Kiron, D., Candelon, F., Chu, M., and LaFountain, B., Expanding AI’s Impact With Organizational Learning, MIT Sloan Management Review and Boston Consulting Group, 2020 年 10 月
-
papers.nips.cc/paper/2015/file/86df7dcfd896fcaf2674f757a2463eba-Paper.pdf, Sculley et al., 2015, 查阅于 2022 年 7 月 23 日, -
Yang, K., 2022, Landing AI – Moving Beyond the Software Industry,
community.ai-infrastructure.org/public/videos/landing-ai-ai-moving-beyond-the-software-industry-2022-09-30 -
Sambasivan, N., Kapania, S., Highfill, H., Akrong, D., Paritosh, P., Aroyo, L., 2021, Everyone wants to do the model work, not the data work: Data Cascades in High-Stakes AI
-
hbr.org/2019/01/the-era-of-move-fast-and-break-things-is-over, 查阅于 2022 年 10 月 8 日
第二部分:数据驱动机器学习的构建模块
在本部分中,我们通过四个关键原则为数据驱动机器学习奠定基础,这些原则支撑了这种方法,在探索具体技术之前,为您提供必要的背景知识。然后我们探讨了以人为中心和非技术性的数据质量方法,考察了专家知识、训练有素的标注员和清晰的指示如何增强您的机器学习输出。
本部分包含以下章节:
-
第三章*, 数据驱动机器学习的原则*
-
第四章*, 数据标注是一个协作过程*
第三章:数据中心化机器学习的原则
在本章中,你将学习以数据为中心的机器学习的关键原则。我们将在本章中介绍数据中心化的基础原则,以提供一个高级结构和框架,供你在本书的其余部分进行工作并参考。这些原则将在我们深入探讨下一章中与每个原则相关的具体技术和方法之前——即“为什么”——为你提供重要的背景信息。
阅读原则时,请记住,以数据为中心的机器学习是模型中心方法的扩展——而不是替代品。本质上,模型中心化和数据中心化技术协同工作,以从你的努力中获得最大价值。
到本章结束时,你将对每个原则及其如何共同形成一个以数据为中心的框架有很好的理解。
在本章中,我们将涵盖以下主题:
-
原则 1 – 数据应该是机器学习发展的中心
-
原则 2 – 有效利用标注者和主题专家(SMEs)
-
原则 3 – 使用机器学习来改进你的数据
-
原则 4 – 遵循道德、负责任和良好治理的机器学习实践
有时候,你所需要的只是正确的数据
几年前,我(乔纳斯)领导着一个数据科学家团队,他们面临着一个有趣但具有挑战性的问题。我们工作的金融服务业务吸引了大量新的在线访客,他们希望通过公司的网站向我们开设新账户。然而,由于未知原因,相当数量的潜在客户无法完成开户流程,这就是为什么公司转向数据科学家寻求帮助的原因。
这个未激活账户和失去客户的问题是多方面的,但我们决心在草堆中找到每一根针。开户流程相当直接,旨在使某人能够在 10 分钟内(无需支持)轻松开设新账户。对于客户来说,步骤如下:
-
输入个人详细信息。
-
验证身份。
-
验证联系信息。
-
接受条款和条件并开设账户。
这个过程在大多数情况下都有效,但对于相当一部分申请人来说,在步骤 2和步骤 3中出现了问题。如果某人的身份无法在线验证(步骤 2),那么个人必须亲自验证,这对许多人来说是一个明显的减分项,并导致了显著的用户流失。
在步骤 3中出现的这些问题不太明显。大约 10%的用户会在这一点上放弃他们的旅程,尽管大部分艰苦的工作已经完成。为什么有人会经历整个过程,然后最终决定不继续进行?
我们收集了我们能得到的所有相关数据点,但遗憾的是,我们没有一个非常深入的数据集来工作,因为账户开设过程非常简单,这些是新客户。我们对数据集进行了分析,并使用了各种监督和无监督的机器学习技术来找出与账户未开设相关的任何行为,但我们的分析中没有突出的事物。
我们决定深入挖掘。由于这些客户分享了他们的联系信息,我们可以将他们的电话号码与我们的通话记录相匹配,并获取与匹配电话号码的录音对话。我们提取了数百个通话录音并开始收听。
很快,一个清晰的模式出现了:“我点击了验证联系信息按钮,但从未收到验证码,”一位录音中的呼叫者说。“我已经等了 10 分钟,但验证码还没有到来,”另一位说。用户无法通过验证,因为他们没有收到作为短信的最终验证码——即使呼叫中心的工作人员重新发送了。但并非所有新用户都遇到这种情况,那么这个特定群体出了什么问题?
随着我们继续收听通话录音,另一个微弱的信号出现了:“我不应该回来,”一位用户说。“自从我上次来这里,你们的系统并没有变得更好,”另一位说。
我们查看了一些关闭的客户账户,果然,这些人以前是我们的客户。问题很简单,企业系统将这些用户视为现有客户,因此没有发送所需的短信,无论用户或工作人员提示多少次。这个问题每周大约发生 200 次,这意味着企业每年失去了 10,000 名新客户。为什么没有人早点发现这个问题?
只有 200 次中的少数几次会生成呼叫,而且由于整个星期都有数百名呼叫中心工作人员值班,这似乎是一个罕见的故障,只偶尔发生。没有人能看出问题,因为它太不频繁,我们的模型也无法标记。毕竟,初始数据集噪音太多,信号不足。
我们之所以能够找到这个问题的根源,并从混乱中找到我们的“针”,是因为我们遵循了本章讨论的数据中心化的四个原则。让我们更详细地探讨这些原则中的每一个。
原则 1——数据应该是机器学习发展的中心
正如我们在第二章**从以模型为中心到以数据为中心——机器学习的演变中讨论的那样,占主导地位的模式中心方法在几个方面存在不足:计算和存储已经商品化,算法已经实际上自动化并且高度依赖数据,模型是可访问的但不太灵活,深度学习和 AutoML 工具无处不在。但数据呢?嗯,那还是未知数。
而不是依赖于强大的计算和存储环境以及需要大量数据的复杂算法来给我们带来模型准确性的增量提升,一个更好的方法是由数据驱动——具体来说,是由手头问题可用且相关的数据驱动。
数据对每个公司、问题和情况都是独特的,数据驱动的范式通过在模型之前将焦点和开发努力放在数据上,来认可这一点。数据不再是可以在一开始收集后就被遗忘的静态资产;现在它是一种独特的商品,需要充分利用其全部潜力以做出更好的预测。我们将论证,在许多情况下,公司的专有数据是其唯一的真正独特的竞争优势——只要得到充分利用。
通过关注数据,数据驱动的范式帮助公司区别于其竞争对手。大多数公司都能访问相同的算法和大量的计算和存储,但他们使用的数据以及从这些数据中获得的见解可以给他们带来决定性的竞争优势。
在我们的观察中,关注数据质量除了获得更好的数据外,还为组织带来了实质性的好处。关注数据质量意味着超越简单的数据精炼,因为高质量数据是业务运营的关键组成部分。
为了提高数据质量,通常需要数字化和自动化流程,并创建对流程遵守的强大问责制。强大的数据治理流程将分配数据质量的拥有权、监护权和问责制,这反过来又依赖于遵循、衡量和管理的数据收集标准和流程。
数据质量通常是底层过程质量和对这些过程遵守情况的症状。如果一个组织擅长收集高质量数据,那么它也更有可能拥有良好的通用流程。随着公司改进数据收集,它们推动更好的问责制、准确性、可靠性和整体一致性。因此,关注数据质量可以产生深远的影响,而不仅仅是改善数据完整性和可靠性。它是运营卓越的关键驱动因素。
如果我们回顾本章开头讨论的缺失验证码的例子,对现有数据集进行任何模型选择、参数调整或特征工程都不会揭示问题的根本原因。
这个问题只能通过收集手头问题的正确数据来发现和解决。在这种情况下,缺失的数据点如下:
-
验证码没有被接收
-
这仅适用于之前客户返回并开设新账户的情况
验证码问题的发现导致业务运营方式发生了两项关键变化。首先,IT 部门修复了负责触发发送验证码的代码,在其他条件相同的情况下,这导致了新账户开设数量的显著增加。其次,客服中心团队建立了一个中央流程来记录客户的技术问题,无论大小,这样我们就能发现任何新系统问题的“冰山一角”。换句话说,现在公认的文化是收集高质量数据是改进运营流程的核心。
这对一线员工在两个方面产生了心态转变。首先,他们对数据作为一项强大的资产有了新的认识,这项资产可以汇总和分析,以了解他们工作的整体情况。其次,一线团队现在感到更有权力:如果我做好我的部分工作,捕捉和指出重要问题,就有机会修复它们。
我们的数据科学家也对数据收集和整理作为他们角色关键部分有了不同的认识。通过站在客户和一线员工的角度看问题,他们看到了他们能够产生的影响,这使团队解决问题的方法发生了深刻的变化。而不是接受数据(质量)作为既定事实,数据工程现在渗透到模型开发和部署过程的每个步骤。
数据中心的清单
为了忠实于数据中心的机器学习第一原则,在处理机器学习项目时拥有一个数据聚焦的任务清单非常有价值。以下是我们的清单,分布在模型开发生命周期中的五个步骤中。
第 1 步 – 确定业务问题,界定项目范围,并定义数据需求
任何机器学习项目的第一部分始终应该是清楚地定义你试图解决的问题;这应该与关键利益相关者,如最终用户和行业专家合作完成。当你对你要解决的问题有一个清晰的定义,以及问题解决后的成功样子时,识别数据差距会容易得多。
一个强大的建模数据集包含内容和上下文。内容是你正在测量的特定对象、事件或状态,上下文描述了对象、事件或状态发生的情境。在我们之前的缺失验证码示例中,内容是*(缺失的)验证码*,上下文是对于以前, 回归客户。
定义项目范围的一个关键要素是概述你试图模拟的过程。我们通过将相关最终用户和行业专家与数据科学家一起放在一个房间里,直到他们能够绘制出业务问题背后的过程或情况,来做到这一点。这使所有参与者能够深入了解问题的内容和背景,同时确定构建模型所需的重要数据点。
在这一步骤中,以下是一些需要考虑的问题清单:
-
我们是否清楚地定义了我们试图解决的问题?
-
这是不是我们首先应该解决的问题?
-
通过解决这个问题的我们期望实现什么结果?
-
我们是否已经与专家一起确定了问题的关键部分?
-
根据专家的意见,过程中的关键步骤或时刻是什么,我们的数据是否适当地捕捉了这些?
-
我们的数据点是否包含内容和上下文?
-
从我们的解决方案中可能会产生哪些偏差,我们需要在以后留意这些偏差?
-
这些偏差会导致任何群体或部分受到不公平对待吗?我们如何在验证阶段(步骤 4)识别这些偏差?
-
在我们的数据集中使用所有特征是否合法和道德?
-
输出将进行内部或外部审计吗?
步骤 2 – 准备和标记数据
对于许多数据科学家来说,数据准备是一项令人畏惧的任务。我们确实认为数据准备既可能是重复的,也可能是耗时的,但作为数据为中心的机器学习的倡导者,我们鼓励您将其视为工作中最重要的部分。
数据准备涉及收集、清理、结构化、增强和增强您的输入数据,以增加数据集中的信号并减少噪声。这些任务可能既具有技术挑战性,又具有回报性——特别是当您开始看到 AUC 分数上升时。到目前为止,您已经意识到,如果您投入正确的努力,这个过程可能会给您带来非常强大的建模结果。
以下清单对于指导您完成数据准备过程非常有用。我们将在本书的其余部分详细教授您如何执行这些任务。
这里是清单问题:
-
我们是否对数据质量进行了技术验证?(见第五章**,数据清理技术。)
-
我们能否通过清理数据来增强数据集的强度?(见第五章**,数据清理技术。)
-
我们需要收集额外数据或使用人工标记员提高现有数据集的质量吗?(见第四章**,数据标记是一个协作过程*。)
-
我们需要定义特定的标记规则并培训专家吗?(见第四章**,数据标记是一个协作过程*。)
-
我们能否通过程序化标记提高数据质量或填补缺失值?(见第六章**,机器学习中程序化标记的技术。)
-
我们是否应该使用合成数据来增强或提高数据中某些类别的质量?(见第七章**,在数据为中心的机器学习中使用合成数据。)
-
我们是否需要保护数据集中个人的隐私?(见第七章**,在数据为中心的机器学习中使用合成数据。)
-
我们的数据集是否包含有偏见的类别,我们需要调整这些类别吗?(参见第八章**,识别和 消除偏见 *的技术。)
-
我们的数据集是否包含足够数量的正确类型的罕见事件?我们需要添加更多或删除异常值吗?(参见第九章**,在 机器学习 *中处理边缘情况和罕见事件。)
-
我们能否从现有数据集中工程化新的特征?
第 3 步 – 训练模型
模型训练阶段是数据中心化和模型中心化机器学习原则结合在一起以产生协同效应的地方。再次强调,重要的是不要丢弃你基于模型中心方法已经知道的所有关于如何构建和增强机器模型的知识。数据中心化只是给你工具箱中额外的一套工具,并允许你放大模型的影响。
特征选择是这一协同过程的重要组成部分,因为它筛选出对模型无用(在最坏的情况下,可能是有问题的)特征。一般来说,希望有更少的属性对模型做出贡献是可取的,因为这减少了不必要的噪声,并使模型更容易解释。
考虑特征选择作为模型选择过程的一部分是很重要的,因为模型及其输入数据是手牵手产生预测的。它们本质上是相互联系的。实际上,这意味着你应该与模型一起选择特征,而不是使用静态的预选特征数据集来选择模型。
这里是清单问题:
-
你怀疑你的数据是否脏(例如,错误的标签、缺失值、无意义的模式或不相关的输出)?
-
我们能否通过提高数据集的质量来提高模型的准确性?本书中概述的长期数据中心化技术列表旨在帮助你完成这项任务!
-
我们工程化的特征是否表明数据中存在需要我们收集额外数据(特征或观察)的关系?
-
我们(工程化)的特征是否表明数据中存在任何我们应该与行业专家(SMEs)验证的关系?与其假设我们的新特征是正确的,不如将任何有影响力的相关性与行业专家交叉检查,以确保它们在解决方案的上下文中相关性和有效性。
-
我们能否在不损失预测能力的情况下减少模型中的特征数量?通过应用降维技术或特征选择方法,我们可能能够减少模型中的特征数量,而不会显著降低其预测准确性。
第 4 步 – 评估性能、公平性和偏见
以数据为中心的机器学习方法在模型评估期间对检测偏见和公平性问题给予了高度重视。为了验证机器学习模型的准确性,您仍然应该从传统的验证任务开始,例如将数据分为训练集和测试集,执行交叉验证,并生成混淆矩阵。以下清单假设您将已经执行这些任务,使用标准性能评估,使用如准确率、精确率、召回率、F1 分数等指标。
偏见检测是揭示机器学习模型中潜在的不公平性和歧视的重要工具。这可以通过为每个子组单独创建混淆矩阵来实现,以比较各组之间的假阳性率和假阴性率,并评估各组之间的群体平衡(平等代表)和机会平等(平等的真阳性率)或等概率(平等的假阳性率)。与性别或种族相关的子组间的差异是偏见或不公平的常见来源。
这里是清单问题:
-
我们能否通过提高数据质量或收集新特征来提升模型的表现?
-
我们能否检测到对特定群体或部分的任何偏见或不公平?
-
敏感属性与模型做出的预测之间是否存在任何大的相关性?
-
模型在处理未见过的数据时,在公平性和偏见方面的表现如何?
我们将在*第八章**,识别和消除偏见的技术中介绍识别和消除偏见的技术。
第 5 步 – 部署和监控
机器学习模型的有效监控也依赖于以数据为中心的原则。在监控模型性能时,包括数据质量、数据覆盖范围、数据相关性和标注一致性指标是很重要的。
数据质量指的是数据的准确性、完整性和一致性,而数据覆盖范围指的是拥有足够的数据点来首先做出自信的预测。数据相关性确保用于训练模型的数据适合该任务。最后,数据标注一致性确保用于训练模型的数据点具有正确的标签。
几种技术和工具可以帮助数据科学家有效地监控机器学习模型。例如,数据漂移检测有助于检测数据特征的变化,如均值、方差和分布。同样,异常值检测有助于识别与常见分布显著不同的数据点。此外,偏见检测技术有助于识别和纠正机器学习模型中的偏见实例。
除了依赖报告和指标来监控 ML 模型之外,理解监控是一个持续的过程,需要数据科学团队以外的利益相关者的参与至关重要。利益相关者可能包括领域专家、业务所有者和最终用户。这些利益相关者应协作评估模型的表现,解释结果,并确定需要解决的问题。
这里是清单问题:
-
模型中使用的数据源是否自动化、一致且可靠?
-
我们是否设计了一个监控和报告计划,以捕捉失败、偏差和漂移?
-
我们的监控是否量化了数据质量、数据覆盖范围、数据相关性和标签一致性?
-
我们是否为最终用户提供了对模型性能进行持续反馈的机制?
我们的设计清单问题旨在让您在模型开发过程的每一步都思考数据质量及其影响。换句话说,它们是对更多以模型为中心的开发任务的补充,而不是替代。
数据中心化需要从“我将用这些数据构建最好的模型”的心态转变为“我们如何构建最佳数据集来解决这个特定问题?”为了做到这一点,我们需要整个组织参与协调一致的努力。这使我们来到了数据中心化机器学习的第二个原则。
原则 2 – 有效利用标注者和领域专家
无论您在阅读此内容时 AI 炒作周期处于哪个阶段,AI 和 ML 开发不太可能已经发展到不再需要人类输入和标注的阶段。
近年来,我们在 AI 技术的复杂性方面经历了大幅增长,尤其是在生成 AI 领域。尽管如此,一个事实仍然存在,即即使是功能最强大、最具革命性的 AI 技术,如 ChatGPT,也依赖于由人类标注者组成的小型军队来完善和提升其能力。
这些个人会审查和标注数据样本,然后这些样本会被反馈到模型中,以提升其对自然语言和上下文的理解。人类标注者采用的一些关键方法和技巧包括以下内容:
-
领域专业知识:具有主题领域专业知识的标注者可以提供有价值的见解和标注,帮助模型更好地理解特定主题和领域。
-
主动学习:这种方法涉及优先处理模型认为模糊或具有挑战性的数据样本,使标注者能够专注于他们输入可以产生最大影响的领域。
-
视角多样性:通过涉及来自不同背景和具有不同经验水平的标注者,模型可以接触到更广泛的语言细微差别、文化背景和观点,从而提高其整体性能。
-
质量控制:定期审计和评估标注器的输出可以帮助确保标注质量的一致性和遵循指南,这对于有效的模型训练至关重要。
简而言之,人工标注员对于机器学习至关重要,我们模型的质量取决于我们训练、组织和与这些标注员合作的能力。从广义上讲,在机器学习开发过程中利用行业专家的方式有三种:
-
作为数据点的直接标注员
-
作为输出质量验证者和不良输出(如有害内容)的检测器
-
作为知识专家,他们可以帮助我们制定标注规则
有效利用行业专家需要从仅为标注员创建标注规则(尽管这仍然很重要)的心态转变到利用行业专家和数据科学家结合的强项,通过明确的标注规则覆盖问题空间。
我们的经验表明,采用这种方法不仅提供了强大的标注功能,还有助于我们追踪模糊的例子并提高模型性能,但同样重要的是,它允许数据科学家和行业专家进行合作。随着数据科学家了解主题内容,行业专家学习数据科学的工作方式,这会产生一个飞轮效应,导致新想法、洞察和知识的产生。
让我们更详细地探讨三种人工标注方法。
直接由人工标注员进行标注
人工标注数据的主要优势是其准确性。人类能够以计算机无法做到的方式识别模式和主观性。这意味着分配给数据的标签可能比自动化过程生成的标签更准确。此外,人类可以提供自动化过程中可能丢失的数据背景。
人工标注数据也比自动化流程提供了更大的灵活性。标注员可以根据具体需求或要求定制他们的标注过程,使他们能够调整标注以适应项目目标。这使得机器能够更准确、更快速地解释数据。
最后,与其它标注方法相比,人工标注数据可能更具成本效益。这一点在数据集规模从小到中等时尤为正确。
小型数据集可能包含几百到几千个观察值,通常可以由一个小型标注团队管理。中型数据集可能有数万个观察值。虽然手动标注仍然是可能的,但随着复杂性和所需时间的增加,它开始变得在经济上不太可行。
面对更大的数据集时,由于数据量巨大和潜在的复杂性,手动标注可能会变得重复且容易出错。在这个规模上,数据中的复杂性也可能增加,需要更细腻的理解,这可能对标注员保持一致性构成挑战。
对于更大或更复杂的数据集,我们建议走程序化标注的道路,我们将在下一部分进行讨论。有趣的是,混合方法也可以有效,其中大型数据集的一个子集被手动标注,作为程序化标注算法的训练数据。这样,你可以利用人工标注的准确性和机器学习的可扩展性,确保即使是大型数据集也能获得高质量的标签。
回想一下我们在本章开头概述的丢失验证码的故事。一旦我们确定了与尚未发现的议题相关的电话,我们选择手动监听数百个电话,而不是使用机器学习技术来捕捉主题。为什么?
因为我们想确保我们理解了这些交互的内容和上下文,而人类更有可能做好这项工作。同时,我们只听了数百个电话,而不是数百万个,所以人工标注是找到噪声中的信号和定位我们“大海捞针”问题的最经济有效的方式。
虽然人工标注是数据驱动方法的重要组成部分,但在使用人工标注员时,有一些陷阱和错误需要避免。在第四章《数据标注是一个协作过程》中,我们将教你如何最大限度地发挥专家和人工标注员的作用,同时管理潜在的负面影响。
使用人工标注员验证输出质量
如前所述,即使是像 ChatGPT 这样非常复杂的 AI 解决方案,也严重依赖人工标注员来引导算法达到最佳结果。ChatGPT 是基于监督学习和一种称为人类反馈强化学习(RLHF)的技术构建的。
强化学习是机器学习的一个领域,其中代理通过与环境的互动来学习做出决策。代理的目标是选择能够最大化累积奖励的行动。然而,为复杂任务定义合适的奖励函数可能具有挑战性。
这就是人类反馈发挥作用的地方。在 RLHF 中,AI 代理从人类提供的奖励和惩罚中学习,而不是从预定义的奖励函数中学习。这种方法结合了机器学习算法的力量与人类专家的直觉、经验和知识。
该过程包括以下步骤:
-
代理与环境互动并采取行动。
-
人类观察者评估代理的行为,并以奖励或惩罚的形式提供反馈。
-
代理使用这些反馈来更新其学习并随着时间的推移改进其决策。
通过这些交互,AI 代理学会通过结合人类指导来执行复杂任务。
使用 RLHF(强化学习与人类反馈)的好处有几个。首先,人类反馈使得 AI 代理能够从人类所拥有的丰富知识和经验中学习。这种方法使得代理能够学习复杂的行为,这些行为可能用传统的算法难以实现。同时,通过调整人类专家提供的反馈,学习过程可以针对特定的需求或目标进行定制。
在循环中有人类存在的不利之处在于,人类可能会犯错误或提供不一致的反馈,这可能会影响代理的学习。此外,使用人类反馈训练 AI 代理可能是一个缓慢的过程,因为它需要人类专家的持续输入。换句话说,它往往劳动密集且可能成本高昂。因此,重要的是要提前估计此类项目所需的人类和财务资源,以确保其可行性。
重要的是要注意,行业专家几乎可以成为任何机器学习练习的极其宝贵的贡献者。例如,我们经常使用行业专家来帮助我们审查模型的输出,因为这使我们能够发现问题空间中的新上下文,这些上下文应该成为训练数据中的特征。
在缺失验证码的例子中,我们通过首先通过采访呼叫中心工作人员(一种行业专家)和听取通话录音(我们自己成为行业专家)来深入了解具体的失败点,从而发现了问题的根源。一旦我们缩小了可能的问题范围,我们就与 IT 部门的同事(另一种行业专家)一起深入核心系统的内部运作,以验证故障。
这种方法与强化学习不同,但它强调了在整个开发过程中涉及行业专家的价值,即使这需要人工输入。
使用程序化标注来规范标注规则
使用人工标注的传统方法有时是过程中的瓶颈,可能会阻止我们以既高效又经济的方式创建高质量的训练集。这通常是在处理大量数据集时出现的问题。随着机器学习模型变得更加复杂,数据集变得更大,时间和成本效益高的训练变得越来越重要。
进入程序化标注。从本质上讲,程序化标注是一个基于预定义的规则或算法自动为数据点分配标签的过程。程序化标注相对于人工标注的主要优势是,它可以比人工标注更快、更准确地完成——一旦建立了稳健的标注功能。这使得它非常适合大型数据集,在这些数据集中,人工标注可能耗时过长或成本过高。
程序化标注的过程始于定义需要分配给每个数据点的标签。这可以通过 SMEs 手动完成,或者通过自动化方法,如自然语言处理(NLP)算法或基于规则的系统来完成。一旦定义了标签,就可以使用监督学习或无监督学习算法将它们应用于数据点。
程序化标注的主要好处如下:
-
可扩展性:程序化标注可以比人工标注更高效地处理大量数据,从而实现更快的模型训练和迭代。
-
一致性:自动化的标注方法确保在整个数据集中一致地应用规则和标准,减少变异性以及可能由人为主观性引起的潜在错误。
-
成本效益:通过自动化标注过程,组织可以节省在培训、管理和补偿人工标注员所需的时间和资源。
-
速度:程序化标注可以比人工标注更快地处理和标注数据,从而加速整个机器学习流程。
-
减少人为错误:自动化最小化了在人工标注过程中可能引入的人为错误和不一致性风险。
-
可重复性:自动化的标注过程易于复制,确保结果可以在不同的数据集和项目中重复和验证。
-
适应性:程序化标注算法可以根据需要微调和更新,以适应不断变化的需求、新的数据源或不断发展的项目目标。
-
24/7 可用性:与人工标注员不同,程序化标注可以持续运行,无需休息或停机时间,从而确保机器学习项目进展不间断。
我们将在第六章“机器学习中的程序化标注技术”中向您展示如何使用特定的程序化标注技术。
程序化标注技术通常足以提高数据质量。然而,在某些情况下,特征之间的关系过于复杂,基于规则的算法无法完成这项工作。这使我们来到了数据为中心的机器学习的第三原则。
原则 3 – 使用机器学习改进你的数据
正如我们可以使用程序化或算法方法来标注我们的数据一样,我们也可以使用机器学习来识别可能错误或不明确的数据点。通过利用可解释性、错误分析和半监督方法的发展,我们可以创建新的标签并找到改进或丢弃的数据点。
这里有一些使用机器学习生成更好输入数据的实际步骤:
-
丢弃噪声示例:有时,更多的数据并不总是更好的。噪声数据可能导致预测不准确。通过移除噪声示例,我们可以提高输入数据的质量。例如,如果你正在分析客户评论,其中一些评论充满了随机字符或不相关信息,这些可以被认为是“噪声”并予以移除。
-
使用技术来关注数据子集以提高质量:并非所有数据都具有相同的价值。我们可以关注数据子集来提高输入数据的质量。例如,如果你正在分析销售数据,你可能只会关注你最有利可图的地区的子集数据,以获得最大的回报,其他条件相同。
-
通过利用专家输入的 ML 泛化来扩展可用的标签数据:ML 可以通过使用专家输入来实现类似精度和更大覆盖范围来扩展可用的标签数据。例如,鸟类物种的专家可以提供关于有限图像集的输入,ML 可以使用这些输入来准确地为更大的图像集打标签。
-
使用半监督方法:包括弱学习和主动学习在内的半监督方法可以用来识别需要专家审查的数据点。例如,你可能会使用主动学习来识别需要由人工进行情感分析的客户电子邮件。
-
使用可解释性:可解释性在识别数据中的模式和确保模型有意义方面至关重要。复杂的模型需要特定的或模型无关的可解释性方法,包括局部和全局方法以及 SHAP 值。例如,使用 SHAP 值可以帮助你理解为什么你的模型在贷款审批过程中预测了某个特定的结果,确保决策过程透明且可解释。
-
使用错误分析:错误分析可以帮助识别模型在数据中犯错的模式,从而帮助我们提高输入数据的质量。例如,如果你的模型在图像识别中将猫错误地识别为其他东西,错误分析可以帮助你找出它在哪里以及为什么犯这些错误。
执行这些步骤所需的技术将在本书随后的章节中概述。
通过在生产中应用这些步骤,我们可以识别标签函数或模型中的性能漂移。此外,我们可以识别需要人工审查的数据点,从而提高输入数据的质量并改善预测准确性。
使用 ML 来提高输入数据质量是传统 ML 方法的一个根本性转变。它需要从使用 ML 模型进行最佳预测的心态转变为使用 ML 来识别那些不利于模型性能的数据点。毕竟,以数据为中心的 ML 的目标是增加输入数据中的信号并减少噪声。
采用数据中心的做法也为我们提供了一个独特的机会,以符合伦理、负责任和良好治理的机器学习实践的方式收集和精炼数据。这种关注点的转变使我们能够设计我们的数据策略,不仅围绕性能提升,还围绕公平、透明和问责制原则。
在我们继续前进的过程中,我们将探讨这种方法如何帮助我们将伦理嵌入到我们的数据收集和精炼过程的内核。这样,我们可以确保提高数据质量与保持我们机器学习应用的完整性和可靠性相辅相成。
原则 4 - 遵循道德、负责任和良好治理的机器学习实践
随着数据中心的兴起,我们能够应对更多高风险挑战,道德和负责任的机器学习实践变得越来越重要。这要求你在设计算法时考虑透明度、公平性和问责制等因素,以确保它们不会歧视某些群体或个人。此外,负责实施这些系统的人必须了解它们的工作原理,并理解它们的局限性,以便他们可以就其使用做出明智的决定。
很不幸,道德和负责任的机器学习实践通常不如应有的那样发达。在 2021 年,IBM 商业价值研究所和牛津经济研究所进行了一项研究 1,其中 75%的高管将 AI 伦理视为重要;然而,不到 20%的高管强烈同意他们的组织实践与声明的原则和价值观相一致。
作为数据中心的机器学习从业者,我们需要考虑的是,“数据质量”这个术语比单个数据点的客观准确性要广泛得多。高质量的数据还应使我们能够在整个机器学习开发过程及其之外识别和监控潜在的伦理问题。
人工智能伦理和责任不仅仅是打勾作业,它可能是一个潜在的差异化来源。关注 AI 伦理的组织更有可能获得客户的信任,而忽视它的组织可能会遭受客户的反感和声誉损害 2。
英国 2020 年学校评级丑闻的故事突显了在高风险环境中使用机器学习时忽视伦理考虑可能发生的事情。在 COVID-19 大流行期间,由于封锁,英国各地的学生无法参加考试。相反,使用了一个算法来评定学生的考试成绩,导致大量学生获得的分数低于他们应得的分数。这引起了学生、教师和学术界的强烈不满,因为它被视为不公平和不公正。
英国监管机构 Ofqual 使用的算法旨在标准化不同学校的成绩,以便进行比较。它考虑了诸如先前成就和学校表现等因素。然而,它没有考虑个别学生的表现或教师评估,这导致许多学生获得的分数低于他们应有的分数。
相反,该模型偏袒来自私立机构和富裕地区的学生,这对公立、公立资助学校的高绩效个体产生了重大影响。因此,许多学生因考试成绩降低而失去了大学录取资格。这给那些努力学习考试却因未能准确反映其能力的算法而失望的学生带来了极大的痛苦。最终,算法给出的分数被取消,并由更公平但更手动评分的方法所取代。
为了避免未来发生类似英国评分灾难等类似事件,AI 系统必须从一开始就考虑到道德因素进行设计。总的来说,这一事件突显了与 AI 系统相关的某些伦理问题,并说明了为什么在我们设计和实施这些系统时考虑这些问题非常重要。它还提醒我们,如果我们希望这些系统能够成为我们社会中决策的有效工具,我们必须确保这些系统是透明的、公平的和可问责的。
我们将在第四章**,数据标注是一个协作过程中讨论处理标注模糊性的具体方法,并在第八章**,识别和消除偏差的技术中向您展示一系列识别和消除偏差的技术。
摘要
在本章中,我们概述了以数据为中心的机器学习的四个原则。通过遵循这些原则,您将能够创建基于高质量数据的机器学习模型,这些数据已经通过人类、标签函数和机器学习技术进行了增强、交叉检查和验证。
这使我们能够从数据中获得更多信号,从而提高我们在小型或大型数据集上构建强大模型的能力。最后,我们可以在整个开发生命周期中捕捉到伦理考虑,这最终确保我们使用我们的力量行善。
在下一章中,我们将探讨您可以如何结构化、优化和治理使用人工标注员进行您的机器学习项目的流程。
参考文献
-
www.ibm.com/thought-leadership/institute-business-value/en-us/report/ai-ethics-in-action,2023 年 6 月 1 日访问 -
解码 AI 在商业成果中的信任与伦理, 访问于 2023 年 6 月 1 日
第四章:数据标注是一个协作过程
随着人工智能领域的发展,ChatGPT、大型语言模型 Meta AI(LLaMA)、Bard、Midjourney 等公开可用的工具为使用结构化和非结构化数据所能实现的可能性设定了新的基准。
这些模型显然依赖于先进的算法和大量数据,但许多人并不知道人类标注仍然是他们持续改进和发展的关键组成部分。例如,ChatGPT 的模型基础设施依赖于个人审查和标注数据样本,然后反馈给模型以改善其对自然语言和上下文的理解。
在本章中,我们将探讨如何充分利用涉及人类标注员的数据收集和标注任务。我们将涵盖以下一般主题:
-
我们为什么需要人类标注员
-
理解人类标注任务中出现的常见挑战
-
设计一个框架以实现高质量的标签
-
优化人类标注员激励措施、避免偏见以及处理标注模糊性的最佳实践方法
首先,让我们了解为什么人类输入是数据驱动机器学习(ML)的基石。
理解多样化人类标注的好处
在人类标注过程中纳入多样化的个人和观点提供了几个优势。人类在数据标注方面带来的精确度和准确性是机器难以匹敌的。尽管自动化系统可能难以处理模糊性或复杂性,但人类标注员可以利用他们的理解和推理能力做出明智的决策。
数据会随时间变化,新的场景可能会出现,而这些场景在原始训练数据中并不存在。人类标注员可以适应这些变化,提供反映新现实的更新标注。这确保了随着数据的发展,机器学习模型保持相关性和有效性。
人类标注员相对于程序化标注的一些关键优势包括以下内容:
-
领域专业知识:具有专业知识领域的标注员可以提供有价值的见解和标注,帮助模型更好地理解特定主题和领域。
-
主动学习:这种方法涉及优先处理模型认为模糊或具有挑战性的数据样本,使标注员能够专注于他们输入可以产生最大影响的领域。
-
观点多样性:多样化的标注员群体可以帮助减轻训练数据中的潜在偏见,从而实现更公平、更具包容性的 AI 模型。通过涉及来自不同背景和具有不同经验的人,模型可以接触到更广泛的语言细微差别、文化背景和观点,从而提高其整体性能。
-
增强的上下文理解:通过借鉴来自不同背景的标注员的经验和知识,模型可以更深入地理解语言细微差别、习语和文化参考。接触广泛的观点和输入可以使模型更具弹性和适应性,使其能够有效地处理更广泛的任务和场景。
-
质量控制(QC):定期审计和评估标签器输出可以帮助确保标注质量的一致性和遵守指南,这对于有效的模型训练至关重要。
-
遵守伦理规范:基于伦理考虑,可能存在不应成为模型输入的数据或场景。在这些情况下,人类标注员在帮助模型满足伦理标准方面发挥着至关重要的作用。例如,ChatGPT 背后的公司 OpenAI 使用人类标注员进行审查、标注和过滤掉有害的“不适宜工作环境”数据。
虽然人类标注是数据为中心的模型开发的关键部分,但人类也会给任何机器学习项目带来新的行为、偏见和风险,这些必须得到管理。在我们介绍管理这些挑战的框架之前,我们将讨论这些典型的挑战。
理解由人类标注员产生的一般挑战
在我们深入探讨标注准确性和一致性的最佳实践之前,我们将定义我们必须通过我们的标注框架解决的常见挑战。标注不准确性和歧义通常是由以下七个原因之一或多个原因引起的:
-
不明确的指示:数据标注任务的不明确或不充分的指示会导致标注不一致。如果标注员没有收到明确的指南,他们可能会做出假设或猜测,导致不一致或不准确的标注。
-
人类偏见:当数据偏向于特定结果或结果时,偏见可能会引入歧义,导致不准确的理解。一个常见的解决方案是为同一数据分配多个标注员进行标注,选择最频繁出现的标签作为正确的标签。然而,这种聚合或投票方法有时可能会加剧偏见而不是纠正它。例如,如果大多数标注员存在特定的偏见,他们的共识可能反映了这种偏见而不是真实的数据。
-
人类错误:标注员是人,人都会犯错误。即使是最受培训、最投入和最专注的标注员,也可能因为一个打字错误或鼠标点击错误而应用错误的标签,导致数据集中出现随机噪声。这些错误确实会发生,但不太可能以系统化的方式发生。尽管如此,我们需要有一种方法来识别和纠正这些错误,以确保它们不会引入不必要的随机噪声。
-
客观任务与主观任务:每个任务都位于一个光谱上,从完全客观(只有一个正确答案)到高度主观(有多个可能的正确解释)。任务越主观,它倾向于在数据标注中引入的歧义就越多。正如你将在本章学到的那样,即使看似相对简单的任务也可能包含隐藏的主观性层次。
-
难度:本质复杂或难以理解的任务可能导致数据标注中的歧义。如果一个任务太难,标注者可能难以正确理解或完成它,从而导致不一致或不准确的标注。
-
歧义:某些任务或数据集本身具有歧义性,这意味着存在多个有效解释的空间。这种歧义可能导致数据标注的不一致性,因为不同的标注者可能会以不同的方式解释相同的数据。
-
静态标注与可变标注:在静态标注中,每个数据点被分配一个单一且不变的标签。相比之下,可变标注允许标签根据上下文或附加信息而变化。可变标注可能会引入歧义,因为同一数据点在不同的上下文中可能被标注为不同的标签。这种标注不一致性也可能随着标注者对任务越来越熟悉而出现,这导致他们改变对标签定义的认识。
现在我们将介绍我们实现准确和一致标签的框架。该框架专门设计用于识别或防止由这七个常见的标注挑战产生的问题。
设计高质量标签的框架
由人类进行的标注和审查可能劳动密集且容易受到人为错误和不一致性的影响。因此,目标是构建既准确又一致的语料库,要求标签达到准确度标准,同时确保不同标注者的结果处于同一范围内。
这些目标乍一看可能很明显,但现实中,让人类标注者达成一致意见可能非常棘手。除此之外,我们还需要验证共识意见没有受到某种形式的偏见。
我们实现高质量人工标注的框架由六个维度组成。在深入解释如何实现它们之前,我们将简要总结这些维度:
-
清晰的指示:为确保标签质量,标注任务的指示必须明确且无歧义。标注者应清楚地了解对他们期望的内容,包括关于任务、标注标准以及正确标注数据的示例的详细信息。
-
动机一致:标注者的动机应与获得高质量标签的目标一致。这可能包括奖励准确性、提供反馈以及创造鼓励细致工作的环境。当标注者感到他们的工作是宝贵的并且得到认可时,他们更有可能产生高质量的标签。
-
主题专家(SMEs):利用对主题领域有专长的标注者可以显著提高标签的质量。这些人拥有对数据背后背景的深入知识和理解,使他们能够识别其他人可能错过的细微差别和细微之处。
-
迭代协作:通过迭代协作的过程可以实现高质量的标签。应鼓励标注者进行沟通和协作,根据集体反馈和讨论重新审视和改进他们的标签。
-
思维多样性:一个多元化的标注者群体会为任务带来不同的观点和解释,这可能导致更全面和稳健的标签。思维多样性有助于发现盲点并减少标注过程中的偏见。
-
处理歧义:由于歧义是任何数据标注任务固有的,因此对标注者进行如何处理模糊情况培训对于实现高质量标签至关重要。这可能包括寻求额外信息、咨询同行或主管或遵循预定义的模糊情况规则等策略。
让我们详细探讨这六个维度,以了解如何建立卓越的标注流程。这一切都始于确保我们对标注者的说明清晰明了。
设计清晰的说明
虽然看起来很明显,标注任务应该附带清晰的说明,但正如你将在本章中了解到的那样,这并不一定是一项容易的任务。分配不仅应该对创建它们的人清晰,而且更重要的是,对将执行它们的人也要清晰。
这个挑战有三个组成部分:首先,说明应包含执行任务所需的具体细节,无论谁执行这些任务。其次,指令设计应包括在分配过程中早期和持续地发现指令问题的方法。第三,我们必须确保我们的标注者具备足够的资格和动力来完成这项任务。
McInnis 等人(2016)1 研究了如果我们不管理一个或多个这些组成部分可能会出现的问题。研究人员检查了请求者和标注者之间指令不明确和动机不一致对亚马逊机械 Turk(AMT)的影响。
他们发现,经验丰富的标注者会使用各种工具和技术来评估作业的质量以及发布作业的请求者的可靠性,在承担新项目之前这样做。他们这样做是为了选择明确定义的作业,可以无障碍地完成,同时避免那些支付很少或影响他们声誉的任务。
基本上,好的标注者会绕过不明确的作业,以避免迭代任务或不公平拒绝已完成的工作(标注者的工作被拒绝会导致未支付工作费用)。作者发现,标注者在决定是否接受作业时,通常会在任务说明中寻找以下风险因素:
-
任务或界面设计中的缺陷
-
评价标准不明确
-
对拒绝的不响应、任意的解决方案
-
请求者信息不足
-
缺乏经验和不熟悉的请求者
-
返利差的任务
-
优先考虑效率而非质量
作为数据专业人员,我们的工作是提供减轻这些七个因素的作业说明,无论我们是在使用众包标注者还是在内部 SMEs。然而,有时你只有在实际使用中才会知道你的说明是否清晰。
因此,问题是:我们如何最好地在请求者和标注者之间对任务的理解进行对齐,同时确保我们有合适的人在工作?
Liu 等人(2016)2 为此目的开发了一种最佳实践方法,称为 Gated Instruction。这项技术用于培训标注者,在请求者和标注者之间对任务的理解进行对齐,并识别表现不佳的工人。
Gated Instruction 基于这样一个观点:当人们得到关于他们表现的反馈时,他们学得更好。这是通过提供一个交互式教学环境来实现的,用户可以在其中接收关于他们标注的反馈并相应地调整他们的方法。目标是创建一个系统,可以以最小的用户努力提供准确的标注。
Gated Instruction Crowdsourcing Protocol 是一个简单且可推广的三阶段流程,旨在确保数据标注的质量。它包括一个交互式教程、筛选问题和持续筛选的问题批次。作者如下描述了该协议:
第一阶段 – 交互式教程
这一阶段涉及一个全面的教程,解释手头的任务:
-
工人被赋予了每个关系的明确定义和标记标准。
-
工人通过标注说明每个关系的句子来练习。
-
每个练习句子之后都提供即时反馈,以指导工人。
第二阶段 – 筛选问题
这一阶段旨在评估工人对任务的理解:
-
工人被要求标注一组五个代表性的黄金标准问题。
-
对每个问题提供反馈,以帮助工人了解他们的错误。
-
在这些问题的多数失败者将被排除在剩余流程之外。
第三阶段 – 问题批次(持续筛选)
这一阶段专注于在任务执行过程中保持高质量的工作:
-
金标准问题被包含在任务中,但没有提供反馈。
-
每个包含 20 个问题的批次中包含 5 个金标准问题,其频率以指数级下降。
-
在最后 10 个金标准问题中得分低于 80%的工人将被淘汰。
一般原则
这些原则确保了过程的完整性和效率:
-
只有那些 AMT 声誉超过一定阈值的工人才能被接受。
-
在整个任务中提供了关系的定义链接,以便快速参考。
-
工人在继续之前必须纠正反馈中指出的任何错误。
-
在每个批次之后,都会提供到目前为止的收入和金标准问题的表现反馈。
-
工人在完成所有 10 个批次后都会得到奖金提醒,这鼓励他们持续保持高质量的工作。
Gated Instruction 比传统的指导和筛选方法提供更准确的结果,因为用户能够收到对其标注的反馈并根据此调整方法。使用这种方法,作者在同一数据集上提高了精确度从 0.50 到 0.77,召回率从 0.70 到 0.78。这是与传统指导方法相比的结果。
这两个研究使用了众包标注员来进行他们的研究,但当你的标注员不是众包工人,而是与你有更紧密工作关系的专家知识(SMEs)时,相同的框架仍然高度相关。
例如,如果你从内部同事的池中挑选标注员,你可能基于某人的特定专业知识、经验年限和对贡献项目的兴趣来做出选择。正如你将在下一节中了解到的那样,标注员完成任务的动力可以极大地影响数据的收集,以及最终你的模型在该数据上的表现。
当我们讨论如何激励标注员以及如何使用专家知识(SMEs)进行更复杂的标注任务时,让我们基于这些原则来展开。
对齐动机和使用专家知识(SMEs)
尽管技术在自动化数据收集方面取得了巨大进步,但人类数据收集者在各个领域仍然占据着至关重要的地位。他们带来了机器无法复制的理解力、同理心和判断力。例如,人类数据收集者和标注员可以解释语言、语境和情感中的细微差别。同样,他们可以与受访者互动,建立联系,并鼓励更开放和诚实的回答。
人类数据收集者面临的一个重大挑战是保持他们的动力和参与度与数据的次要用途保持一致。让我们讨论四个通常导致这一问题的因素。
#1 – 缺乏目的
如果数据收集者不理解他们工作的意义,他们可能会感到与工作脱节且缺乏动力。数据收集者可能会问自己,“如果没有人会使用这些信息,我为什么要收集这些信息?”
解决方案:传达整体情况,并展示每个数据点如何成为机器学习解决方案的有价值构建块。
我们开始任何数据收集和标注练习时,都假设工作人员是聪明的,能够理解收集丰富、无偏见信息的重要性。他们明白,任何数据质量上的妥协都可能对其未来使用产生负面影响。我们的角色随后变为阐明良好数据收集的重要性,并解释其预期用途。这种阐述将显著减轻与数据质量相关的问题。
几年前,Manmohan 和 Jonas 进行了一系列需要一线员工通过客户访谈收集大量信息的机器学习项目。这些访谈已经进行了多年,所以我们已经收集了大量的数据,但这些信息是以对话格式以文本形式捕获的,因此难以进行统计分析。
我们希望使这些数据更容易用于机器学习目的,因此我们召集所有一线员工进行了一次关于数据质量重要性的演示和研讨会。在向这些数据收集者展示我们如何使用他们收集的数据来构建特定的机器学习解决方案后,他们惊讶地了解到他们的工作可以产生多么大的影响。
如一位团队成员所说,“如果我知道我收集到的信息可以帮助成千上万的客户,我会投入更多的精力去关注细节。”
在研讨会上,我们通过创建一系列模板化问题,让数据收集者承担起责任,这些问题将提高收集数据的准确性和信号。我们的一线同事为改善他们为公司及其客户收集数据的方式而感到自豪。他们对数据质量和“他们的数据”产生的结果有了转变后的所有权感。
我们也很高兴,因为数据质量的提升最终使我们的模型在某些情况下准确率提高了高达 40%。
#2 – 数据任务的繁琐性质
数据收集和标注可能是重复和单调的,导致无聊和缺乏参与感。这创造了一种内在的激励,即以最小的努力完成任务。
解决方案:我们采用以下四种策略来减少数据收集和标注的枯燥部分:完善我们的问题、简化流程、消除不必要的数据,并在可能的情况下进行自动化。
有时候,通过教授数据收集者提出更好的问题,你可以产生巨大的影响。更具体和有针对性的问题通常会得到更有意义和吸引人的回答。这提高了数据质量,同时使数据收集过程更有趣且不那么重复。
简化流程可能意味着优化工作流程,使用更用户友好的软件,或为参与数据收集的人员提供清晰的指示和培训。通过使过程更加直接,我们可以减少个人的认知负荷,使任务不那么令人疲惫。
如果你像大多数数据专业人士一样,丢弃或限制数据收集的想法可能会引起一定程度的焦虑。然而,我们必须认识到,数据收集并不意味着囤积你遇到的每一份数据,尤其是如果它妨碍了保持良好的用户体验。实际上,收集不必要或不相关的数据可能会增加任务的单调性,并产生阻碍数据分析的杂乱。因此,识别并仅关注真正与你的研究问题或商业目标相关的数据至关重要。
我们最后的简化策略,即自动化,通常是解决数据收集繁琐问题的绝佳解决方案,但我们更倾向于在用尽其他三种策略之后再引入自动化。没有必要自动化那些应该以不同方式完成或根本不应该做的事情。
例如,抓取工具可以从网站或文档中自动提取大量数据,基于规则的逻辑或机器学习算法可以对数据进行标记和组织。你将在本书的编码章节中学习一些这些自动化技术。
不久前,我们与一家大型企业合作,构建了关于呼叫者购买投资产品倾向性的实时预测模型。该企业希望他们的呼叫中心工作人员在通话期间就能获得这一预测结果,以便他们能为呼叫者匹配到合适的投资专家。
这些筛选通话通常需要 15 到 20 分钟才能完成,呼叫中心工作人员会艰难地逐页浏览与呼叫者个人详情、人口统计、投资经验和风险偏好相关的问题。
虽然收集了大量的数据,但这些数据并不是构建预测所需的那种信息。我们确定了一些补充数据点,这些数据点对于可靠地确定某人的产品需求至关重要。然而,将这些数据点作为额外问题添加到数据收集过程中是不切实际的,因为筛选通话已经很长且很繁琐。为了获取所需的信息,我们必须为呼叫者、呼叫中心工作人员和数据科学家创造一个双赢的局面。
当我们与呼叫中心团队一起讨论这个挑战时,我们发现大约 20%的现有问题可以通过增强来使对话更加简洁,而另外 15%的问题可以完全删除,因为它们捕捉到了无关或重复的信息。最后,10%收集到的数据可以根据呼叫者的其他问题的答案预先填写或推导出来。
在实施这些变化后,即使我们在流程中增加了五个新问题,平均通话处理时间也下降了大约 30%。通话时间的缩短使得每天可以处理更多的通话,从而增加了转化为付费客户的机会——这对所有相关人员都是双赢。
#3 – 缺乏激励
没有适当的奖励或认可,数据收集者可能缺乏最佳表现的动机。例如,如果一个人被收集到的数据点的数量而不是单个响应的质量所激励,可能会导致任务完成得更加肤浅。
解决方案:当我们专注于以下四个不同领域的激励数据收集者时,我们看到了最佳的结果:
-
展示什么是好的样子:首先,设定清晰的高质量工作标准至关重要。大多数人都会为自己的工作感到自豪,并在理解他们的努力的价值和影响时努力表现良好。通过定义数据收集或标注任务的宗旨和重要性,并展示优秀工作的例子,你为团队提供了一个具体的目标。这种清晰度有助于确保收集到的数据是稳健的、相关的,并且富含有价值的见解,同时最大限度地减少噪音。
-
创造互利共赢:其次,培养数据收集者和数据使用者之间的共享成功感至关重要。当数据收集者了解他们的努力如何对大局做出贡献——可能是推动关键业务决策或推动创新项目——他们更有可能感到对工作有所投入。这种所有权和贡献感可以显著提高数据收集的质量。
-
提供非货币奖励:关注的第三个领域是非货币奖励。认可和感激是强大的激励因素。游戏化可以在这方面成为一个非常有效的工具,将数据收集过程转变为引人入胜的竞赛。实施如公开显示的排行榜、徽章或积分等特性,可以培养成就感,并在团队成员之间鼓励健康的竞争。长期存在的“月度员工”概念不仅奖励了卓越的表现,也为其他人树立了一个值得追求的标准。
-
提供经济奖励:最后,经济奖励可以是一个强有力的激励因素。将薪酬与工作质量挂钩可以促使个人达到或超过设定的标准。然而,这种方法需要一个明确的框架来概述绩效期望和绩效的客观衡量标准。同时,你还需要有能力影响某人的薪酬,如果你的注释员和数据收集员是内部员工,这可能很困难。虽然并非总是可行,但在可用时,经济激励可以成为一个强大的动机因素。如果它们不是一种选择,那么在其他三个动机领域加倍努力仍然可以产生令人印象深刻的结果。
总之,激励数据收集员和注释员做好工作是多方面的。它需要清晰沟通、互利、认可和适当的补偿的混合。
#4 – 工作环境
压力大或缺乏支持的工作环境也可能影响动机水平。如果一个数据收集员或注释员有许多相互竞争的优先事项,他们可能会对数据收集采取“差不多就可以了”的态度。
解决方案:为注释员和数据收集员创造一个适合专注于手头主要任务的合适工作环境基本上要求你移除任何不必要的任务或阻碍某人完成工作的摩擦。
我们通常拒绝参与数据科学项目,除非项目所需的所有人员都有动力参与,并在整个项目期间保持可用。这通常需要我们与小专家、数据收集员及其经理协商,在日历上预留专门的时间。
为数据收集员提供反馈机制也很重要。这样,他们可以参与到过程中,并指出不工作或可以做得更好的地方,例如可以以不同方式表述的问题,改变问题的顺序或任务,混淆的标签要求等。
为注释员和数据收集员提供必要的工具和资源,以便他们迅速有效地完成工作。例如,数字化数据收集,尽可能自动化数据录入,条件格式化下拉选项等。
项目参与者之间的持续和及时反馈也是至关重要的。在项目后期对工人进行负面反馈可能会令人沮丧,并产生反效果。相反,在整个过程中进行合作并提供反馈,以确保从一开始就保持一致。假设过程中可能会有一些小问题,并准备好积极引导团队通过这些问题。相反,如果你假设每个人都已经按照预期理解了每项指令,那么你就是在为自己设置失败。
有些人不认为这种工作属于数据科学领域。在我们看来,这正是许多数据专业人士的不足之处。重要的是要认识到人类行为和偏见可能是数据科学项目的严重限制因素,你希望尽可能减少这种影响。如果你不面对这些非技术障碍,你最终会得到更差的数据。
这将我们引向框架的下一个维度:迭代协作。
迭代协作
迭代协作应该是人类标注任务的核心策略。基本上,它包括创建一个持续的反馈和微调数据标注过程的流程。以下是实施数据标注协作方法的三个指导原则,这些原则得到了数据标注平台 SUPA 最佳实践方法的验证。
从小规模开始,尽早解决任何问题
使用较小的数据集启动数据标注过程是一种实用方法。与其从数千个观察值开始,不如从 50 个观察值的校准批次开始。这个可管理的数据集允许你审查标签,发现潜在问题,改进说明,并向标注员提供反馈。
重复此过程,直到你确信你已经遍历了整个数据集的代表性样本,已经识别出大多数边缘情况,并且你的标注员能够一致地完成任务。
相同观察中的标注不一致表明需要修订规则。如果标注员对指南有不同的理解,那么了解原因是否是不清晰的标注规则、个别标注员对理解和经验的差异,或者完全是其他原因,是值得的。换句话说,你的标注说明永远不会是一劳永逸的。它们应该始终处于监督之下,并随着项目需求的发展而演变。
可视化好的样子
标注规则有时可能含糊不清,导致主观解释和不一致。例如,在图像标注练习中,一条规则如“只有当大部分内容可见时才进行标注”可能被不同的标注员以不同的方式解释。因此,重要的是要直观地展示什么是好的。
可视化在数据标注过程中非常有价值,因为它们为标注员提供了明确的指导,说明标注对象应该如何呈现。我们建议展示如何正确执行标注任务的示例,同时也展示错误的示例。通过提供良好和不良标注规范的视觉说明,标注员能够更深入地理解任务,从而提高他们的生产力和精确度。
对边缘情况要非常具体
边界情况是指偏离常规的情况,由于主观意见可能导致标签不一致。例如,玩具车应该被标记为汽车吗?双人自行车是一辆还是两辆自行车,或者它属于一个单独的分类?
为了有效地管理边界情况,你需要有一个机制让标注者标记这些项目以供进一步考虑。如果你没有建立反馈机制,标注者很可能会现场自行做出判断以完成任务。
Pradhan 等人(2022)4 提出了一种所谓的 FIND-RESOLVE-LABEL 工作流程,用于众包标注,旨在解决这三个迭代步骤。FIND-RESOLVE-LABEL 工作流程是一种指导性标注过程,旨在揭示特定标注任务及其相关说明中的模糊性。
例如,一个标注任务可能是识别包含女性的图像。表面上,这个任务看起来相当简单,但现实中,标注者很快就会面临模糊性。例如,什么时候某人算作女性,什么时候算作女孩?这甚至重要吗?一个女性的雕像或《蒙娜丽莎》画作——这算数吗?如果女性只部分可见呢?
实际上,为特定的标注任务提供高质量的说明可能非常困难,因为可能有很多维度需要考虑。
FIND-RESOLVE-LABEL 工作流程旨在在标注练习的开始阶段发现并消除这些模糊性。它由三个关键组件组成,这些组件协同工作以简化数据标注过程:
-
发现:在这个初始阶段,标注者会收到标注说明,并要求根据这些说明识别模糊的示例。对于每个识别出的示例,标注者还被要求提供一个概念标签,解释为什么选择了特定的标签。这允许收集标注决策背后的推理和概念思考,然后可以将其反馈到改进的标注说明中。
-
解决:一旦确定了数据点,下一步就是解决数据中的任何模糊性或冲突。这可能需要领域专业知识来做出明智的决定,如何解决不一致或缺失信息。
-
标注:最后,在解决任何问题后,数据点被适当地标注,确保高质量的标注,这些标注可以用于训练机器学习模型。
普拉德汉和他的团队发现,在标注过程中关注最不明确的数据点并澄清它们,可以大大提高数据质量。他们注意到,在某些模糊的场景中,许多标注者同意的答案与请求者认为正确的答案不同。这意味着即使使用智能答案聚合方法,也存在为这些任务获得错误标签的风险。
有趣的是,该研究还发现,工人可以正确地标注与主要概念密切相关的研究结果。这表明我们可能不需要在任务中解释每一个可能的歧义,因为精心挑选的一组例子可以帮助团队正确地标注其他不明确的例子。
考虑到这些发现,让我们以数据为中心的方式探讨如何处理标注员之间的歧义。
处理歧义和反映多样性
在人类标注员的帮助下,我们可以生成信息极其丰富的数据集,但有时这需要我们以创新的方式解决歧义。同时,歧义可能很难被发现。一个人认为明显的事情,另一个人可能觉得完全令人困惑。
公司和研究人员使用内部员工、志愿者或众包平台(如 AMT)以可承受的价格获取人类标注员。这些标签员来自不同的背景,携带不同的偏见,所有这些都可能影响标注的质量——尤其是在涉及判断因素时。
随着人工智能和机器学习被用于从可以根据上下文和解释者不同而具有不同解释的数据集中分类和生成新内容,这一挑战只会日益加剧。这一点在 Sap 等人(2019)的研究论文《仇恨言论检测中的种族偏见风险》中得到了体现,该论文调查了标注员对方言差异的敏感性不足如何导致自动化仇恨言论检测中的种族偏见。
论文指出,即使是专门为检测仇恨言论而设计的数据集,也包含对特定群体或少数族裔语言的固有偏见。这是因为底层参数是基于标注员的偏好创建的,而这些标注员可能没有意识到不同民族或语言之间细微差别的微妙之处。
例如,一些民族或社会群体可能使用一些口语,这些口语对其他群体的人来说可能显得粗鲁或冒犯。例如,研究人员发现,标签员倾向于将非裔美国英语中的短语标记为比使用通用美国英语的短语更具毒性。
这些固有的偏见难以避免,因为标注员很少是完全的领域专家,而是遵循一般指令的人。同时,标签员不太可能成为普通公众的代表样本。例如,AMT 参与者的大多数历史上是未婚且无子女的年轻人。绝大多数 Turkers 来自仅两个国家,即美国和印度,而来自全球南部的不到 2%。
这个问题不仅仅与仇恨言论的主题有关。因此,在确定任何需要主观解释的标签时,某些方言、生活方式、文化背景和世界观可能被过度代表,而其他则可能被代表性不足。由此导致的数据收集不佳可能导致标签与它们旨在代表的现实世界场景之间的差距增大。
数据标注中存在的失衡在一些全球最广泛使用的公共训练数据集中表现得非常明显。研究发现,其中两个最常用的数据库,ImageNet 和 Open Images,倾向于美国和欧洲,这一点从这些数据集创建的模型在来自全球南部的图像上的性能较差可以明显看出。
例如,当新郎的图片来自埃塞俄比亚或巴基斯坦时,与来自美国的相似图片相比,其准确性评级较低。这种特定的差异是由于“婚礼”和“香料”等物体根据其文化背景被解释的方式不同,公开可用的识别系统在来自美国或欧洲以外的国家时,往往难以正确分类它们。
理解处理标注中模糊性的方法
标签往往是绝对的,但观点不是。同时,人类在更抽象的标签应该是什么的问题上往往会意见不一。作为以数据为中心的从业者,我们应该预见标注者之间的模糊性和分歧,并制定管理它的计划。
值得注意的是,我们实际上希望出现模糊性,这样我们才能以正确的方式处理它。模糊场景可能源于标注指令不明确,但它们也可能揭示出必须包含在我们数据集中的新标签。因此,我们应该努力设计我们的标注团队,以最大化我们能够揭示出任何存在的分歧的可能性。
做这件事的一个好方法就是涉及一个更加多元化的标注者群体,他们更能适应语言和观点等元素之间的差异,但在这样做的同时,我们也希望提升少数群体的意见。斯坦福大学的研究人员 Gordon 等人(2022)8 提出了一种旨在实现此目的的方法,称为 陪审团学习。
在 Gordon 等人(2021)9 的先前研究中,发现当在评论毒性标注任务中考虑非多数群体的标签时,分类器的性能从 0.95 ROC AUC 下降到 0.73 ROC AUC。这意味着当应用于非多数群体的人的评论时,分类器并不那么有效。换句话说,不可能让每个人都达成一致,因此我们应该考虑我们听取的是谁——不一定只是简单的多数。
陪审团学习与更直接的汇总或多数投票方法形成对比。陪审团学习不是基于多数规则或概率来建立标签,而是积极使用不同的意见来挑选出潜在的偏见并压制少数意见。它被提出作为一种将不同意见的声音整合到机器学习系统中的方法,以防止它们过度依赖单一意见或观点。以下是它是如何工作的。
陪审团学习是一种监督机器学习(SML)方法,通过陪审团的隐喻明确解决分歧。这种方法允许实践者指定他们的分类器反映了哪些声音,以及以何种比例。陪审团学习的目标是定义哪些人或群体决定了系统的预测以及以何种比例,从而允许开发者分析——并可能减轻——模型中可能存在的任何潜在偏见。
为了有效地使用陪审团学习,实践者必须首先确定他们希望将其包括在模型中的陪审员。这可以通过选择代表手头问题不同视角的个人来完成。
例如,假设任务是标记电影是否好。实践者可以从不同的群体中选择陪审员(标记者),例如年龄、性别、种族、地点、政治倾向或社会经济地位。一旦选定了陪审员,他们就必须通过回答关于电影是否好以及为什么好的问题,向模型的预测提供输入。
一旦所有陪审员都向模型的预测提供了他们的意见,实践者就可以使用这些数据为每个个案创建一个汇总预测。通过考虑每个个案的多个视角,实践者可以确保他们的模型比仅依赖单一视角进行预测时更加准确和较少偏见。
最后,实践者还可以通过比较由不同背景或视角的个人组成的陪审团所做的汇总预测,使用陪审团学习来分析他们模型中存在的任何潜在偏见。这种比较有效地为我们提供了一个预测范围,而不是二元标签。分析可以帮助识别任何可能存在偏见的领域,并允许实践者相应地调整他们的模型,以减少任何潜在的偏见并提高整体准确性。这个过程在以下图中得到了说明:
图 4.1 – 来自 Gordon 等人(2022 年)的陪审团学习过程概述
到现在为止,你可能已经注意到陪审团学习并不简单。与许多以数据为中心的方法一样,陪审团学习需要广泛的规划和协调,以确保所有参与者都清楚地了解过程的期望和指南。这可能是一个耗时且资源密集的过程。
陪审团学习也需要一定规模的标注者池,以确保意见和观点的必要多样性。这可能会是一个挑战,尤其是对于资源有限的项目来说。
实施陪审团学习带来的另一个挑战是需要关于标注者的元信息。为了确保结果既准确又可靠,陪审团学习需要具有不同技能和背景的标注者。收集这些信息并开发符合要求标准的标注者池可能是一项困难的任务,这又需要提前规划。
尽管存在这些挑战,陪审团学习为从业者提供了一种创新且有效的方法,将不同意见的声音纳入机器学习模型,同时帮助他们识别和减轻模型中可能存在的任何潜在偏见。通过在做出预测和分析模型中存在的潜在偏见时考虑多个观点,从业者可以确保他们的机器学习模型在处理容易受到主观性影响的重要任务时既准确又无偏见。
为了结束这一章,让我们探讨如何从统计上衡量标注者之间的歧义或不一致。
衡量标注一致性
到目前为止,我们已经讨论了一系列创建一致性和高质量标注的工具和技术。虽然这些元素为良好的数据集奠定了基础,但我们还希望能够衡量我们的标注者是否表现出一贯性。
为了衡量标注者的一致性,我们建议使用两种标注一致性的度量,分别称为观察者内部和观察者之间的可变性。这些是临床研究中的标准术语,指的是同一观察者(内部)或不同观察者(外部)在不同测量或评估中的同意程度。为了简化解释,可以将“观察者”视为与“标注者”、“注释者”、“评分者”、“数据收集者”以及我们在本章中使用的任何其他类似术语可以互换。
虽然观察者内部和观察者之间的可变性都与测量一致性相关,但它们解决的是不同的方面。观察者内部的可变性指的是单个观察者在一段时间内的稳定性,而观察者之间的可变性指的是不同观察者之间的稳定性。诸如培训、经验和协议标准化等因素可以显著影响这两者。
跟踪观察者差异至关重要,因为它直接影响您输入数据集的质量和可靠性,从而影响您的模型。如果同一对象被不同的观察者(观察者间差异)或同一观察者在不同时间(观察者内差异)解释不同,可能会导致标签不一致,从而影响机器学习输出的整体质量。
几个因素导致了观察者之间的差异,包括测量技术缺乏标准化、观察者疲劳以及主观解释。例如,某人的判断可能受到其经验水平、个人偏见或甚至观察时的心理状态的影响。
重要的是要注意,两个或更多观察者之间的标签差异并不一定意味着其中一个观察者是正确的,而其他人是错误的。标签上的分歧可能仅仅意味着被标记的对象或情况是模糊的或短暂的,因此难以给出明确的标签。
例如,两位医生可能会根据医学影像或症状描述对疾病的相对进展意见不一致。这可能是因为诊断不确定,而不是一位医生比另一位医生更正确。
测量差异的常见技术是类内相关系数(ICC)。ICC 是一种统计工具,用于评估一个或多个观察者对同一实体进行标注的一致性或一致性。与常用的皮尔逊相关系数不同,后者测量变量之间的线性关系,ICC 评估同一组数据中评分的可靠性。当我们想知道同一组别中的单位之间相似性有多强时,它特别有用。
一个接近 1 的高 ICC 值表明同一组别中的值之间具有高度相似性。相反,一个低的 ICC 值表明评分之间的一致性较低。
ICC 有不同的形式,每种形式适用于特定的情况。例如,某些形式更适合当我们从每个受试者那里得到单一测量时,而其他形式更适合于几个测量的平均值。形式的选择取决于您研究的性质和您拥有的数据类型。
以下截图显示了 Shrout 和 Fleiss(1979)10 提出的六种常见定义:
图 4.2 – 六种常见的 ICC
为了让我们对 ICC 分数的使用有更直观的理解,让我们通过使用Pingouin Python 包的实例来实际操作。Pingouin 是一个开源包,具有大量有用的统计功能。它主要利用 pandas 和 NumPy,所以请确保您已经安装了这些。
对于我们的示例场景,假设我们有四位品酒评委通过给八种不同的葡萄酒评分 0 到 9 来评价其质量。我们想知道这些评委是否对葡萄酒的评分是一致的。评委的评分显示在以下屏幕截图:
图 4.3 – 来自四位不同评委的品酒评分
-
一般而言,可能发生三种类型的变异性:
-
由于评估对象之间的差异引起的变异性(假设两种不同的同一种葡萄酒样本有略微不同的口感)
-
由观察者的评估引起的变异性,例如,评委 B 和 C 对葡萄酒 #3 评分的差异
-
标签使用的变化;例如,每个人都认为葡萄酒 #1 是最差的,但已经使用了三种不同的评分来对其进行评价
ICC 计算将考虑所有这些因素,因为它基于 方差分析(ANOVA)分析。在我们计算 ICC 之前,我们必须首先确定我们追求的是哪种类型的衡量标准。我们可以使用以下屏幕截图作为选择我们情况中正确 ICC 形式的指南:
图 4.4 – ICC 模型选择指南
在我们的场景中,以下适用:
-
每位评委都只对每款葡萄酒进行过一次评分,因此我们可以确定所有受试者都由同一组观察者进行了评估
-
在这种情况下,我们将假设四位评委是从更大的潜在评委池中随机选择的
-
我们对个别观察者的可靠性感兴趣,而不是平均可靠性
因此,我们正在寻找使用 ICC2 计算来确定我们的 可靠性分数。
我们随后使用以下脚本在我们的葡萄酒评分上运行 ICC 函数。Pingouin ICC 操作符 intraclass_corr 将计算并展示所有六种常见的 ICC 衡量指标,但我们只对 ICC2 感兴趣:
import pingouin as pg
data = pg.read_dataset('icc')
icc = pg.intraclass_corr(data=data, targets='Wine', raters='Judge',
ratings='Scores').round(3)
icc.set_index("Type")
这会产生以下输出。我们的 ICC 分数为 0.728,这意味着我们的评委在评分上达成中等程度的共识:
图 4.5 – 输出表
重要的是要理解,没有严格的阈值来界定“可接受”的 ICC 分数。虽然没有固定的基准,但一些一般性指南可以帮助解释 ICC 分数。这些范围不是绝对的,应该根据你具体的研究或分析来解释:
-
ICC 小于 0.5:这个范围通常被认为是表明可靠性差。例如,如果你有一组评分的 ICC 为 0.3,这表明评分者之间达成一致的程度较低。
-
ICC 在 0.5 到 0.75 之间:这个范围内的分数通常被认为是表现出中等可靠性。
-
ICC 在 0.75 到 0.9 之间:这些分数表明良好的可靠性。例如,如果你达到 ICC 为 0.8,这表明你的评分者之间达成高度的一致。
-
ICC 大于 0.90:这个范围表示出色的可靠性。例如,ICC 为 0.95 表示评分者之间几乎完全一致。
在解释 ICC 分数时,考虑几个可能影响其可靠性的因素也同样重要。这些因素包括以下内容:
-
样本大小:与许多统计指标一样,ICC 对样本大小也很敏感。较大的样本大小往往能提供更可靠的 ICC 估计。
-
数据结果范围:ICC 分数也可能受到被评估结果的可能范围以及确定注释的难度的影响。例如,如果我们的葡萄酒评委只能将葡萄酒标注为“好”或“坏”(1,0),而不是一个范围(0–9),那么这可能会改变最终的 ICC 分数。
-
受试者变异性:ICC 也受到受试者之间变异性的影响。即使评分者在评分上保持一致,高变异性也可能导致 ICC 值降低。
在实践中,解释 ICC 分数需要理解你的研究背景、数据性质以及所使用的特定 ICC 形式。在解释和传达你的结果时,始终考虑这些因素。
记住——ICC 分数只是拼图中的一块。它们应该与其他统计指标和见解结合使用,以提供对数据的全面理解。让我们总结一下本章到目前为止所涵盖的内容。
摘要
在本章中,我们考察了人类在确保数据质量方面发挥的关键作用,尤其是在数据标注的初始阶段。我们认识到,虽然人类标注者是不可或缺的,但他们也带来了一些挑战,包括偏见和不一致性。
为了解决这些问题,我们探索了各种策略来有效地训练标注者以开发高质量的数据库。关键要点是,经过良好训练且拥有明确指示的标注者可以显著提高数据的整体质量。
改进任务说明成为了一个反复出现的主题,强调了其在促进标注过程中的重要性。迭代协作也被强调为一种基本实践,通过反馈和改进促进持续改进。
到本章结束时,你应该已经全面理解了为什么在以数据为中心的模型构建中,人类参与至关重要,人类标注者所面临的挑战,以及克服这些挑战的实际方法。更重要的是,你将学会如何使用特定的框架来实现高质量的标注,为成功的 ML 项目打下坚实的基础。
在下一章中,我们将在这些技能的基础上进一步深入探讨数据清洗和增强的技术方面,然后再探讨第六章中程序化标注技术,即机器学习中的程序化标注技术。现在是深入代码的时候了!
参考文献
-
McInnis B., Cosley D., Nam C., Leshed G., 围绕拒绝、不信任、风险和亚马逊机械师图灵平台工人体验进行设计,信息科学法学院,康奈尔大学。
dl.acm.org/doi/epdf/10.1145/2858036.2858539 -
Liu A., Soderland S., Bragg J., Lin C. H., Ling X., Weld D. S., 有效的大规模标注用于关系抽取,图灵中心,华盛顿大学计算机科学与工程学院。
aclanthology.org/N16-1104.pdf -
www.supa.so/post/iteration-a-key-data-labeling-process-often-overlooked,2023 年 7 月 30 日查阅。 -
Pradhan V. K., Schaekerman M., Lease M., 2022,寻找歧义:为众包工人澄清标注指南的三阶段工作流程设计,前沿人工智能,2022 年 5 月 18 日,机器学习与人工智能卷 5。
doi.org/10.3389/frai.2022.828187 -
Sap, M., Card, D., Gabriel, S., Choi, Y., Smith, N. A., 仇恨言论检测中的种族偏见风险,保罗·G·艾伦计算机科学与工程学院,华盛顿大学,西雅图,美国,机器学习系,卡内基梅隆大学,匹兹堡,美国,艾伦人工智能研究所,西雅图,美国
-
venturebeat.com/business/the-ai-industry-is-built-on-geographic-and-social-inequality-research-shows/,2023 年 4 月 22 日查阅。 -
venturebeat.com/ai/mit-researchers-find-systematic-shortcomings-in-imagenet-data-set/,2023 年 4 月 22 日查阅。 -
Gordon M. L., Lam M. S., Park J. S., Patel K., Hancock J., Hashimoto T., Bernstein M. S., 2022. 陪审团学习:将不同意见融入机器学习模型. 在 CHI 计算机系统人因工程会议 (CHI ‘22),2022 年 4 月 29 日至 5 月 5 日,新奥尔良,LA,美国. ACM,纽约,NY,美国
-
Gordon M. L., Zhou K., Patel K., Hashimoto T., Bernstein M.S., 2021,不一致解卷积:将机器学习性能指标与现实对齐,在 CHI 计算机系统人因工程会议 (CHI ‘21),2021 年 5 月 8 日至 13 日,横滨,日本. ACM,纽约,NY,美国,14 页。
doi.org/10.1145/3411764.3445423 -
Shrout, P. E. & Fleiss, J. L. (1979), Intraclass correlations: uses in assessing rater reliability, Psychological Bulletin, 86(2), 420.
psycnet.apa.org/doi/10.1037/0033-2909.86.2.420,2023 年 7 月 30 日查阅。
第三部分:提高数据质量的技术方法
在本部分,我们探讨了提高机器学习中数据质量和管理的技术方法。我们涵盖了从数据清洗、程序化标注、合成数据使用,到解决偏差和处理罕见事件等主题。每一章都为你提供了在机器学习中高效工作所需的基本技能和知识,强调了高质量数据在构建稳健的机器学习系统中的重要性。
本部分包含以下章节:
-
第五章*,数据清洗的技术*
-
第六章*,机器学习中程序化标注的技术*
-
第七章*,在数据为中心的机器学习中使用合成数据*
-
第八章*,识别和消除偏差的技术*
-
第九章*,处理机器学习中的边缘情况和罕见事件*
第五章:数据清洗技术
在本章中,我们将介绍数据质量的六个关键维度及其提高数据质量的相关技术,这些技术通常被称为机器学习中的数据清洗技术。简单来说,数据清洗是通过实施技术来修复数据中的错误或删除错误数据,以提高数据质量的过程。正如第一章和第二章所涵盖的,减少数据中的错误是提高模型质量的一种非常高效和有效的方法,比使用以模型为中心的技术,如添加更多数据或实施复杂算法,更为有效。
在高层次上,数据清洗技术包括修复或删除不正确、不完整、无效、有偏见、不一致、过时或损坏的数据。由于数据是从多个来源捕获的,由于不同的注释者根据他们的判断或由于系统设计不良,将这些来源结合起来通常会导致数据被错误标记、不一致、重复或不完整。正如前几章所发现的,错误的数据会使算法和结果不可靠。因此,为了在机器学习系统中实现可靠性,重要的是帮助数据科学家和数据工程师质疑数据,并使用数据清洗技术系统地提高数据质量。
本章将涵盖以下主题:
-
数据质量的六个关键维度
-
测量数据质量
-
提高数据质量所需的六个维度的数据清洗技术
数据质量的六个关键维度
我们可以使用六个关键维度来检查数据的整体健康状况。确保数据整体健康可以确保我们能够构建可靠的系统并做出更好的决策。例如,如果 20%的调查数据是重复的,并且大多数重复数据是由男性候选人填写的,我们可以想象,如果数据重复未被检测到,决策者的行动将有利于男性候选人。因此,了解数据的整体健康状况对于做出可靠且无偏见的决策非常重要。为了衡量数据质量或查看数据的整体健康状况,我们可以将数据质量分解为以下维度:
-
一致性:这指的是对于给定的列或特征,同一数据是否在行之间保持一致。一个例子可能是男性性别标签是否一致。标签可以取“1”、“男性”、“M”或“male”等值,但如果数据有多个值来表示男性,那么算法将单独处理每个标签,这可能导致随机性和错误。因此,确保一致性的目标是确保标签在整个数据集中定义一致。
-
唯一性:这指的是每条记录是否可以唯一识别。如果系统中有重复值,模型将因为那些记录而变得有偏见。例如,如果一个地区有高贷款批准率,由于系统故障,该地区的记录被重复,算法将偏向于批准更多该地区的贷款。
-
完整性:这指的是给定列或特征的数据在行之间是否完整,以及数据是否由于系统错误或未捕获而缺失,尤其是在该信息将被用于机器学习系统时。
-
如果一个或几个标注者认为某些郊区既不是城市也不是乡村,并违反了数据规则输入了
semi_urban,那么semi_urban可能无效。这可能会在数据中引入噪声,因此确保数据符合业务规则非常重要。 -
准确性:这指的是数据最初是否被正确输入,并且可以从内部或外部来源进行验证。在医疗保健环境中,一个例子可能是如果入院日期和时间输入了不同的时区,那么分析和洞察将是不准确的。如果入院时间是护理质量的预测因子,那么时区的不匹配可能会导致错误的结论。
-
新鲜度:这指的是可用的数据是否是最近的并且是最新的,以满足数据需求。某些应用程序需要数据是实时的——也就是说,每秒更新一次——而其他应用程序需要数据每月或每几个月可用一次。昨天的事实可能因为法规、天气条件、趋势、竞争、业务变化等因素的变化而改变。
接下来,我们将安装各种 Python 包,然后深入到数据修复和质量测量的工作。在每个部分,我们将深入探讨不同的数据清洗技术以及如何提高数据质量。
安装所需的包
对于本章,我们需要以下 Python 包或库:
-
pandas版本 1.5.3 -
numpy版本 1.22.4 -
scikit-learn版本 1.2.1 -
jupyter版本 1.0.0 -
alibi版本 0.9.0 -
alibi-detect版本 0.10.4 -
seaborn版本 0.12.2 -
matplotlib版本 3.6.3 -
missingno版本 0.5.1 -
feature-engine版本 1.5.2
接下来,我们将简要介绍数据集并开始探索数据。
介绍数据集
首先,让我们介绍我们的问题陈述。对于贷款提供者来说,确保获得贷款的人能够还款并且不会违约是很重要的。然而,同样重要的是,人们不应因为基于低质量数据训练的模型而被拒绝贷款。这就是以数据为中心的方法帮助使世界变得更美好的地方——它为数据科学家和数据工程师提供了一个质疑数据质量的框架。
对于本章,我们将使用 Analytics Vidhya 的贷款预测数据集。您可以从 datahack.analyticsvidhya.com/contest/practice-problem-loan-prediction-iii/#ProblemStatement 下载数据集。有两个文件——一个用于训练,一个用于测试。测试文件不包含任何标签。对于本章,我们将利用训练文件,该文件已下载并保存为 train_loan_prediction.csv。
首先,我们将查看数据集并检查前五行。为此,我们必须导入以下必要的包:
import pandas as pd
import numpy as np
import missingno as msno
import matplotlib.pyplot as plt
接下来,我们将读取数据:
df = pd.read_csv('train_loan_prediction.csv')
df.head().T
我们将使用 pandas 的 read_csv 方法读取数据。然后,我们将使用 .head() 方法可视化数据集的前五行。如果我们有一个大的特征集,我们可以在 .head() 方法的末尾应用 .T 方法。这将表示列作为行,行作为列,其中列名不会超过 5 个,因为我们想可视化前五行。
我们得到以下输出:
图 5.1 – 我们 df 数据集的前五行
如我们所见,列名之间存在一些不一致。所有列都遵循驼峰命名法,除了 Loan_ID,而长列名由 _ 分隔,除了 LoanAmount、CoapplicantIncome 和 ApplicantIncome。这表明列名存在不一致的命名约定。在数据中,我们还可以看到一些列具有驼峰命名法的数据,但 Loan_ID 的所有值都是大写。在 Education 列中,Not Graduate 值由空格分隔。在机器学习中,确保数据的一致性非常重要;否则,模型可能会产生不一致的结果。例如,如果 Gender 列有两个不同的男性客户值——Male 和 male,会发生什么?如果我们不处理这个问题,那么我们的机器学习模型将把 male 数据点视为与 Male 分离的,模型将不会得到准确的信号。
接下来,我们将从数据中提取列名列表,将它们全部转换为小写,并确保单词之间由一个独特的字符 _ 分隔。我们还将遍历分类列的数据值,在将所有特殊字符替换为 _ 并使我们的数据一致之前,将它们全部转换为小写。
确保数据的一致性
为了确保数据的一致性,我们必须检查 DataFrame 中列的名称:
column_names = [cols for cols in df]
print(column_names)
['Loan_ID', 'Gender', 'Married', 'Dependents', 'Education', 'Self_Employed', 'ApplicantIncome', 'CoapplicantIncome', 'LoanAmount', 'Loan_Amount_Term', 'Credit_History', 'Property_Area', 'Loan_Status']
接下来,我们必须获取所有不包含下划线的列名:
num_underscore_present_columns = [cols for cols in column_names if '_' not in cols]
num_underscore_present_columns
['Gender',
'Married',
'Dependents',
'Education',
'ApplicantIncome',
'CoapplicantIncome',
'LoanAmount']
由于一些列的名称中有两个大写字母,我们必须在第二个大写字母之前添加下划线。接下来,我们创建一个布尔映射,针对每个列字母的索引,将大写字母的位置映射为True,这样我们就可以定位第二个大写字母的位置,并在其前面加上下划线:
cols_mappings = {}
for cols in num_underscore_present_columns:
uppercase_in_cols = [val.isupper() for val in cols]
num_uppercase_letters = sum(uppercase_in_cols)
cols_mappings[cols] = {
"is_uppercase_letter": uppercase_in_cols,
"num_uppercase_letters": num_uppercase_letters,
"needs_underscore": (num_uppercase_letters > 1)
}
然后,我们遍历映射并打印需要下划线的列名,以及打印第二个大写字母的位置:
for key in cols_mappings.keys():
if cols_mappings[key]['needs_underscore']:
print()
print(f'{key} need the underscore at location ', cols_mappings[key]['is_uppercase_letter'].index(True, 1))
ApplicantIncome need the underscore at location 9
CoapplicantIncome need the underscore at location 11
LoanAmount need the underscore at location 4
使用这些信息,我们为ApplicantIncome列构建了一些逻辑:
'ApplicantIncome'[:9] + '_' + 'ApplicantIncome'[9:]
'Applicant_Income'
接下来,我们将前面的步骤结合起来,遍历需要下划线的列,构建映射。然后,我们打印需要下划线的列名。最后,我们创建旧列名和新列名的映射:
cols_mappings = {}
for cols in num_underscore_present_columns:
uppercase_in_cols = [val.isupper() for val in cols]
num_uppercase_letters = sum(uppercase_in_cols)
if num_uppercase_letters > 1:
underscore_index = uppercase_in_cols.index(True, 1)
updated_column_name = cols[:underscore_index] + "_" + cols[underscore_index:]
else:
updated_column_name = cols
cols_mappings[cols] = {
"is_uppercase_letter": uppercase_in_cols,
"num_uppercase_letters": num_uppercase_letters,
"needs_underscore": (num_uppercase_letters > 1),
"updated_column_name": updated_column_name
}
if cols_mappings[cols]['needs_underscore']:
print(f"{cols} will be renamed to {cols_mappings[cols]['updated_column_name']}")
column_mappings = {key: cols_mappings[key]["updated_column_name"] for key in cols_mappings.keys()}
column_mappings
ApplicantIncome will be renamed to Applicant_Income
CoapplicantIncome will be renamed to Coapplicant_Income
LoanAmount will be renamed to Loan_Amount
{'Gender': 'Gender',
'Married': 'Married',
'Dependents': 'Dependents',
'Education': 'Education',
'ApplicantIncome': 'Applicant_Income',
'CoapplicantIncome': 'Coapplicant_Income',
'LoanAmount': 'Loan_Amount'}
最后,我们将列映射应用于更新列名并打印新的列名:
df = df.rename(columns=column_mappings)
column_names = [cols for cols in df]
print(column_names)
['Loan_ID', 'Gender', 'Married', 'Dependents', 'Education', 'Self_Employed', 'Applicant_Income', 'Coapplicant_Income', 'Loan_Amount', 'Loan_Amount_Term', 'Credit_History', 'Property_Area', 'Loan_Status']
尽管前面的代码可以通过手动选择列来简单地更新,但通过这种方式编程化,我们可以确保数据遵循编程规则。为了提高一致性,我们将所有列名转换为小写。首先,我们创建一些简单的单行逻辑来将列名转换为小写:
print([cols.lower() for cols in df])
['loan_id', 'gender', 'married', 'dependents', 'education', 'self_employed', 'applicant_income', 'coapplicant_income', 'loan_amount', 'loan_amount_term', 'credit_history', 'property_area', 'loan_status']
然后,我们通过传递前面的逻辑来更新列名:
df.columns = [cols.lower() for cols in df]
print(df.columns)
Index(['loan_id', 'gender', 'married', 'dependents', 'education',
'self_employed', 'applicant_income', 'coapplicant_income',
'loan_amount', 'loan_amount_term', 'credit_history', 'property_area',
'loan_status'],
dtype='object')
通过这样,我们已经确保了列名的一致性。但更重要的是,我们必须确保分类值的一致性,以便我们可以使机器学习系统免受不一致数据的影响。首先,我们提取id和target列,然后识别分类列。这些列包含非数值数据:
id_col = 'loan_id'
target = 'loan_status'
cat_cols = [cols for cols in df if df[cols].dtype == 'object' and cols not in [id_col, target]]
cat_cols
['gender',
'married',
'dependents',
'education',
'self_employed',
'property_area']
我们遍历每个列,检查唯一值以确保值是独特的且没有拼写错误。我们还检查相同的值是否以不同的方式表示,例如以不同的形式或缩写:
for cols in cat_cols:
print(cols)
print(df[cols].unique())
print()
gender
['Male' 'Female' nan]
married
['No' 'Yes' nan]
dependents
['0' '1' '2' '3+' nan]
education
['Graduate' 'Not Graduate']
self_employed
['No' 'Yes' nan]
property_area
['Urban' 'Rural' 'Semiurban']
观察数据,似乎值是独特的,没有缩写或拼写错误。但机器学习系统可以被设计成面向未来。例如,如果我们将所有值都转换为小写,那么在未来,如果相同的值以不同的形式出现,在进入系统之前,它将被转换为小写。我们还可以看到,一些字符串,如Not Graduate,占用空间。就像我们确保列名的一致性一样,我们必须将所有空白替换为下划线。首先,我们创建一个新的 DataFrame 名为df_consistent;然后,我们将所有分类值转换为小写,并将所有空格替换为下划线:
df_consistent = df.copy()
for col in cat_cols:
df_consistent[col] = df_consistent[col].apply(lambda val: val.lower() if isinstance(val, str) else val)
df_consistent[col] = df_consistent[col].apply(lambda val: val.replace(' ','_') if isinstance(val, str) else val)
for cols in cat_cols:
print(cols)
print(df_consistent[cols].unique())
print()
gender
['male' 'female' nan]
married
['no' 'yes' nan]
dependents
['0' '1' '2' '3+' nan]
education
['graduate' 'not_graduate']
self_employed
['no' 'yes' nan]
property_area
['urban' 'rural' 'semiurban']
通过这样,我们确保了数据的一致性,并且所有值都被转换为小写。
如我们所见,dependents列包含数值信息。然而,由于存在3+这样的值,列值被编码为字符串。我们必须移除特殊字符,然后将此值编码回数值,因为该列是序数的:
df_consistent.dependents = df_consistent.dependents.apply(lambda val: float(val.replace('+','')) if isinstance(val, str) else float(val))
接下来,我们查看已婚和自雇列,因为这些是二进制列,必须编码为1和0。性别列有两个值,也可以进行二进制编码——例如,我们可以将男性编码为1,将女性编码为0。教育列也有两个值,我们可以将毕业生编码为1,将非毕业生编码为0:
for cols in ['married', 'self_employed']:
df_consistent[cols] = df_consistent[cols].map({"yes": 1, "no": 0})
df_consistent.education = df_consistent.education.map({
'graduate': 1,
'not_graduate': 0
})
df_consistent.gender = df_consistent.gender.map({
'male': 1,
'female': 0
})
for cols in cat_cols:
print(cols)
print(df_consistent[cols].unique())
print()
gender
[ 1. 0\. nan]
married
[ 0. 1\. nan]
dependents
[ 0. 1. 2. 3\. nan]
education
[1 0]
self_employed
[ 0. 1\. nan]
property_area
['urban' 'rural' 'semiurban']
现在数据已经一致并且正确编码,我们必须创建一个预处理数据的函数,以便我们可以一致地处理任何未来的分类标签变化。然后,我们将该函数应用于 DataFrame,并打印值以确保函数被正确应用:
def make_data_consistent(df, cat_cols) -> pd.DataFrame:
"""Function to make data consistent and meaningful"""
df = df.copy()
for col in cat_cols:
df[col] = df[col].apply(lambda val: val.lower() if isinstance(val, str) else val)
df[col] = df[col].apply(lambda val: val.replace(' ','_') if isinstance(val, str) else val)
df['dependents'] = df['dependents'].apply(lambda val: float(val.replace('+','')) if isinstance(val, str) else float(val))
for cols in ['married', 'self_employed']:
df[cols] = df[cols].map({"yes": 1, "no": 0})
df['education'] = df['education'].map({
'graduate': 1,
'not_graduate': 0
})
df['gender'] = df['gender'].map({
'male': 1,
'female': 0
})
return df
df_consistent = df.copy()
df_consistent = make_data_consistent(df=df_consistent, cat_cols=cat_cols)
for cols in cat_cols:
print(cols)
print(df_consistent[cols].unique())
print()
gender
[ 1. 0\. nan]
married
[ 0. 1\. nan]
dependents
[ 0. 1. 2. 3\. nan]
education
[1 0]
self_employed
[ 0. 1\. nan]
property_area
['urban' 'rural' 'semiurban']
现在我们已经确保了数据的一致性,因此如果分类值在未来用空格代替_或以不同的案例输入,我们可以使用我们在这里创建的功能来清理数据并使其一致,在它进入我们的模型之前:
接下来,我们将探索数据唯一性,以确保没有提供重复记录以在数据中造成偏差。
检查数据是否唯一
现在我们已经确保了数据的一致性,我们必须在它进入机器学习系统之前确保它的唯一性。
在本节中,我们将调查数据并检查loan_id列中的值是否唯一,以及某些列的组合是否可以确保数据的唯一性。
在 pandas 中,我们可以利用.nunique()方法来检查列的唯一记录数,并将其与行数进行比较。首先,我们将检查loan_id是否唯一,以及是否没有输入重复的申请:
df.loan_id.nunique(), df.shape[0]
(614, 614)
通过这种方式,我们已经确保了贷款 ID 的唯一性。然而,我们可以更进一步,确保不会将错误数据添加到另一个贷款申请中。我们相信贷款申请不太可能需要超过一种收入和贷款金额的组合。我们必须检查我们是否可以使用列值的组合来确保这些列之间的唯一性:
df[['applicant_income', 'coapplicant_income', 'loan_amount']].value_counts().reset_index(name='count')
applicant_income coapplicant_income loan_amount count
0 4333 2451.0 110.0 2
1 150 1800.0 135.0 1
2 4887 0.0 133.0 1
3 4758 0.0 158.0 1
4 4817 923.0 120.0 1
.. ... ... ... ...
586 3166 2985.0 132.0 1
587 3167 0.0 74.0 1
588 3167 2283.0 154.0 1
589 3167 4000.0 180.0 1
590 81000 0.0 360.0 1
[591 rows x 4 columns]
如我们所见,在第一行中,有两个具有相同收入变量和贷款金额的申请。让我们通过使用第一行的值来过滤数据集,以找到这些被认为是重复的记录:
df[(df.applicant_income == 4333) & (df.coapplicant_income == 2451) & (df.loan_amount == 110)]
loan_id gender married dependents education self_employed \
328 LP002086 Female Yes 0 Graduate No
469 LP002505 Male Yes 0 Graduate No
applicant_income coapplicant_income loan_amount loan_amount_term \
328 4333 2451.0 110.0 360.0
469 4333 2451.0 110.0 360.0
credit_history property_area loan_status
328 1.0 Urban N
469 1.0 Urban N
观察这个子集,很明显数据包含重复项或有两个不同的申请——一个是丈夫做的,另一个是妻子做的。这些数据除了表明一个男性候选人提交了一个申请,另一个女性候选人提交了一个申请之外,没有提供更多信息。我们可以删除其中一个数据点,但男性和女性申请的比例不平衡。此外,如果第二个申请是真实的,那么我们应该保留这个数据点:
df.gender.value_counts(normalize=True)
Male 0.813644
Female 0.186356
Name: gender, dtype: float64
基于此,我们已经理解了什么使数据点独特——那就是性别、申请人收入、共同申请人收入和贷款金额的组合。作为数据科学家和数据工程师,我们的目标是确保一旦定义了唯一性规则,进入机器学习系统的数据都符合这些唯一性检查。
在下一节中,我们将讨论数据完整性或数据不完整的问题,以及如何处理不完整的数据。
确保数据完整且无缺失
现在我们已经实现了数据的一致性和唯一性,是时候识别和解决其他质量问题了。其中一个问题是数据中的缺失信息或不完整数据。缺失数据是真实数据集中常见的问题。随着数据集大小的增加,数据点在数据中缺失的可能性也会增加。缺失记录可以以多种方式发生,其中包括:
-
源数据集的合并:例如,当我们尝试根据出生日期或邮编匹配记录以丰富数据时,如果这些信息在一个数据集中缺失或不准确,那么这种情况将产生 NA 值。
-
随机事件:这在调查中很常见,其中受访者可能不知道所需信息是否为必填项,或者他们可能不知道答案。
-
测量失败:例如,一些特征,如血压,在传统方式(即使用血压计)测量时,已知具有很大的随机误差成分。如果两个人几乎同时测量一个受试者的血压,或者一个人在短时间内两次快速测量一个受试者的血压,测量的值可以很容易地相差 10 毫米汞柱(dept.stat.lsa.umich.edu/~kshedden/i… NA 值。在金融领域,一个重要的测量比率是确定个人或公司的信用价值,即债务收入比。在某些情况下,收入未申报,在这种情况下,将债务除以 0 或缺失数据会导致比率缺失信息。
-
数据收集过程中的设计不当:例如,在健康调查中,人们经常被问及他们的 BMI,并不是每个人都了解自己的 BMI 或理解测量方法。如果我们要求提供身高和体重会简单得多,因为人们更有可能知道这些信息。当有人被问及他们的体重测量时,有些人可能会省略或谎报这些信息。如果在收集数据时无法理解或测量 BMI,数据将产生 NA 值。
当训练数据集包含缺失值时,机器学习模型可能会产生不准确的预测或由于信息不完整而无法正确训练。在本节中,我们将讨论以下处理缺失数据的技术:
-
删除数据
-
缺失值编码
-
填充方法
一种处理缺失数据的方法是通过删除缺失的记录。这也被称为完整案例分析(CCA)方法。如果少于 5%的行包含缺失值,这通常是可行的,但删除更多记录可能会降低模型的效力,因为样本量会变小。由于这种技术假设数据是完整随机缺失的,因此可能存在系统偏差,但违反了其他假设,例如当数据是随机缺失(MAR)或非随机缺失(MNAR)时。因此,盲目删除数据可能会使模型更加有偏差。例如,如果一个少数群体在过去没有申报收入或没有持有信用,他们可能没有信用评分。如果我们不了解缺失的原因而盲目删除这些数据,算法可能会更有利于向有信用信息的多数群体提供贷款,而少数群体将失去机会,尽管其中一些成员有稳定的收入和信用。
让我们使用 CCA 技术来探索数据集,删除所有缺失信息的行,并找出丢失了多少数据量:
remaining_rows = df_consistent.dropna(axis=0).shape[0]
total_records = df_consistent.shape[0]
perc_dropped = ((total_records - remaining_rows)/total_records)*100
print("By dropping all missing data, only {:,} records will be left out of {:,}, a reduction by {:,.3f}%".format(remaining_rows, total_records, perc_dropped))
By dropping all missing data, only 480 records will be left out of 614, a reduction by 21.824%
由于 21%几乎占数据集的四分之一,这不是一个可行的方法。因此,在本节中,我们将探讨如何识别缺失数据,揭示数据缺失的模式或原因,并发现处理缺失数据的技术,以便数据集可以用于机器学习。
首先,我们将提取分类特征、二元特征和数值特征。为此,我们必须分离标识符和目标标签:
id_col = 'loan_id'
target = 'loan_status'
feature_cols = [cols for cols in df_consistent if cols not in [id_col, target]]
binary_cols = [cols for cols in feature_cols if df_consistent[cols].nunique() == 2]
cat_cols = [cols for cols in feature_cols if (df_consistent[cols].dtype == 'object' or df_consistent[cols].nunique() <= 15)]
num_cols = [cols for cols in feature_cols if cols not in cat_cols]
cat_cols
['gender',
'married',
'dependents',
'education',
'self_employed',
'loan_amount_term',
'credit_history',
'property_area']
binary_cols
['gender', 'married', 'education', 'self_employed', 'credit_history']
num_cols
['applicant_income', 'coapplicant_income', 'loan_amount']
要检查数据集中是否存在缺失数据,pandas 提供了一个方便的方法叫做.info()。此方法显示总记录中完整行数有多少。该方法还显示每列的数据类型:
df_consistent.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 614 entries, 0 to 613
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 loan_id 614 non-null object
1 gender 601 non-null float64
2 married 611 non-null float64
3 dependents 599 non-null float64
4 education 614 non-null int64
5 self_employed 582 non-null float64
6 applicant_income 614 non-null int64
7 coapplicant_income 614 non-null float64
8 loan_amount 592 non-null float64
9 loan_amount_term 600 non-null float64
10 credit_history 564 non-null float64
11 property_area 614 non-null object
12 loan_status 614 non-null object
dtypes: float64(8), int64(2), object(3)
memory usage: 62.5+ KB
pandas 库还有一个方便的方法叫做.isnull(),用来检查某一列的哪些行缺失数据,哪些行是完整的。通过将.sum()与.isnull()结合使用,我们可以得到每个列的缺失记录总数:
df_consistent.isnull().sum()
loan_id 0
gender 13
married 3
dependents 15
education 0
self_employed 32
applicant_income 0
coapplicant_income 0
loan_amount 22
loan_amount_term 14
credit_history 50
property_area 0
loan_status 0
dtype: int64
如我们所见,credit_history、self_employed和loan_amount列有最多的缺失数据。原始值有时难以理解,知道每个列缺失数据的百分比更有用。在下一步中,我们将创建一个函数,该函数将接受 DataFrame 并打印出每个列的缺失数据百分比。然后,我们将按缺失度降序排序数据:
def missing_data_percentage(df: pd.DataFrame):
"""Function to print percentage of missing values"""
df = df.copy()
missing_data = df.isnull().sum()
total_records = df.shape[0]
perc_missing = round((missing_data/total_records)*100, 3)
missing_df = pd.DataFrame(data={'columm_name':perc_missing.index, 'perc_missing':perc_missing.values})
return missing_df
missing_data_percentage(df_consistent[feature_cols]).sort_values(by='perc_missing', ascending=False)
columm_name perc_missing
9 credit_history 8.143
4 self_employed 5.212
7 loan_amount 3.583
2 dependents 2.443
8 loan_amount_term 2.280
0 gender 2.117
1 married 0.489
3 education 0.000
5 applicant_income 0.000
6 coapplicant_income 0.000
10 property_area 0.000
现在,我们可以提取缺失数据的量级。然而,在我们深入处理缺失数据之前,了解缺失数据的模式非常重要。通过理解这些关系,我们将能够采取适当的步骤。这是因为填补缺失数据可能会改变数据的分布,这可能会进一步影响变量交互。
我们将利用missingno库和其他可视化工具来了解数据缺失的位置,在没有主题专家的情况下,我们将对缺失数据的原因做出一些假设。
为了查看值缺失和数据中存在差距的位置,我们将利用矩阵图。当数据集具有深度或数据包含与时间相关的信息时,矩阵图非常有用。数据的存在用灰色表示,而缺失数据用白色显示:
msno.matrix(df_consistent[feature_cols], figsize=(35, 15))
<AxesSubplot: >
这里是输出:
图 5.2 – 矩阵图
仔细观察,我们可以看到credit_history列有很多缺失点,缺失的发生在整个数据中分布,而不是在某个特定的时间点。
正如我们之前提到的,了解数据缺失的原因可以帮助我们选择正确的技术来处理缺失数据。从高层次上讲,我们可以将这些缺失数据的机制称为,并将它们分为三类:
-
完全随机缺失(MCAR)
-
非随机缺失(MNAR)
-
随机缺失(MAR)
数据在缺失数据对所有观测值的可能性相同,并且缺失数据与数据集中任何其他特征之间没有关系时,被认为是完全随机缺失(MCAR)。例如,一封邮件问卷可能在邮寄过程中丢失,或者如果一个人匆忙,他们可能忘记回答一个问题。在这种情况下,数据缺失与问题的类型、年龄组或性别(与其他变量的关系)无关,我们可以将这些特征或数据点归类为 MCAR。删除这些数据点或将这些实例的值更改为 0 不会对预测造成偏差。
另一方面,当数据点的缺失可能性取决于其他现有数据点时,数据被认为是随机缺失(MAR)。例如,如果男性平均有 5%的时间不披露他们的体重,而女性有 15%的时间不披露他们的体重,我们可以假设数据缺失是由性别偏见引起的。这将导致女性比男性有更高比例的数据缺失。对于这种机制,我们可以使用统计技术或机器学习来预测缺失值,利用数据集中的其他特征。
第三个机制,MNAR,通常容易与 MCAR 混淆,但略有不同。在这种情况下,可以明确地假设为什么数据不是随机缺失的。例如,如果我们试图了解导致抑郁(结果)的因素,那么抑郁的人可能更不愿意回答问题或更不可能被联系。由于缺失与结果相关,这些缺失记录可以标记为“缺失”,对于数值特征,我们可以使用机器学习结合其他特征来估计缺失数据,并通过创建另一个变量来标记数据缺失的点。
现在我们已经了解了不同类型的缺失数据,我们将利用 missingno 中的 heatmap 函数,它将创建一个关联热图。可视化显示了数据集列之间的空值相关系数。它显示了某个特征的存在或缺失如何强烈地影响其他特征。
空值相关系数范围从 -1 到 1:
-
-1 表示如果一个列(属性)存在,另一个几乎肯定不存在
-
0 表示列(属性)之间没有依赖关系
-
1 表示如果一个列(属性)存在,另一个也肯定存在
与标准的关联热图不同,以下可视化是关于缺失数据特征之间的关系,因为其中许多特征几乎没有缺失数据。那些始终完整或始终为空的列没有有意义的关联,并且被从可视化中移除。
这个热图有助于识别属性对之间的数据完整性相关系数,但它对更广泛关系的解释能力有限:
msno.heatmap(df_consistent[feature_cols], labels=True)
这导致了以下热图:
图 5.3 – 热图
从这个图中,我们可以解释几个变量之间的缺失关系。dependents 和 married 之间存在 0.4 的相关性,这是有道理的,因为大多数情况下,人们先结婚再成为有依赖的人。
接下来,我们将提取包含缺失数据的列,并使用这些列进行下一个可视化。dendrogram 方法使用层次聚类,将属性分组在一起,其中缺失与另一个变量的缺失相关联,或完整性与其他变量的完整性相关联:
missing_cols = [cols for cols in feature_cols if df_consistent[cols].isnull().sum() > 0]
msno.dendrogram(df_consistent[missing_cols])
输出如下:
图 5.4 – 系谱图
我们根据自上而下的方法来解释系谱图 – 即,我们关注任何两个列在空值问题上的连接高度。高度越大,关系越小,反之亦然。例如,credit_history 中的数据缺失与任何其他变量的缺失或完整性没有关系。
这样,我们已经了解了缺失数据的模式,以及缺失数据列之间是否存在关系。接下来,我们将探索缺失数据与结果之间的关系。在我们决定删除缺失数据或进行插补之前,我们还应该看看变量的缺失是否与结果相关联——也就是说,数据可能存在 MNAR 的机会吗?
首先,我们将可视化缺失分类数据中的这种关系:
cat_missing = [cols for cols in cat_cols if df_consistent[cols].isnull().sum() > 0]
def cat_missing_association_with_outcome(data, missing_data_column, outcome):
"""Function to plot missing association of categorical varibles with outcome"""
df = data.copy()
df[f"{missing_data_column}_is_missing"] = df[missing_data_column].isnull().astype(int)
df.groupby([outcome]).agg({f"{missing_data_column}_is_missing": 'mean'}).plot.bar()
for cols in cat_missing:
cat_missing_association_with_outcome(df_consistent, cols, target)
这将创建一些图表,展示分类特征与目标变量之间的关系:
图 5.5 - 显示分类特征与目标变量关联的输出图表
在高层次上,我们可以假设对于married、dependents、loan_amount_term、gender和credit_history等变量,数据的缺失与贷款批准状态相关联。因此,我们可以认为这些变量的数据是 MNAR(Missing Not At Random,非随机缺失)。对于这三个变量,我们可以用“missing”这个词来编码缺失数据,因为这个信号将有助于预测结果。credit_history的缺失或完整性略与self_employed状态相关,如热图所示,这表明数据可能随机缺失。同样,married状态的缺失与dependents和loan_amount的缺失相关。
对于所有数据缺失的二进制变量,我们可以假设数据不是 MCAR(Missing Completely At Random,完全随机缺失),而是假设数据是 MNAR,因为缺失信息与结果之间存在某种关系,或者 MAR(Missing At Random,随机缺失),因为缺失与其他变量的存在或不存在相关,如树状图所示。
编码缺失值的一种方法是将这些值编码为最频繁的值,或者去除缺失值,或者创建一个额外的列,用 1 或 0 表示缺失。然而,对于 MAR 场景,这并不是最佳技术。如前所述,数据中心方法的目标是提高数据质量和减少偏差。因此,我们不应使用频率插补方法或仅删除记录,而应考虑要求注释者提供缺失数据的信息,或者进行系统修复以恢复缺失信息。如果这不可能,我们应该考虑使用机器学习技术或概率技术来确定可能的值,而不是简单的众数、均值和中位数插补方法。然而,当缺失超过一定阈值时,即使是高级技术也不可靠,最好是删除该特征。对于剩余的变量,我们将使用机器学习技术来确定缺失值,因为我们无法获得注释者的帮助来提供完整信息。
既然我们已经确定了分类值缺失与结果之间的关联,接下来,我们将研究缺失数值数据与结果之间的关系:
num_missing = [cols for cols in num_cols if df_consistent[cols].isnull().sum() > 0]
def num_missing_association_with_outcome(data, missing_data_column, outcome):
"""Function to plot missing association of categorical varibles with outcome"""
df = data.copy()
df[f"{missing_data_column}_is_missing"] = df[missing_data_column].isnull().astype(int)
df.groupby([outcome]).agg({f"{missing_data_column}_is_missing": 'mean'}).plot.bar()
for cols in num_missing:
num_missing_association_with_outcome(df, cols, target)
这将显示以下图表:
图 5.6 – 贷款金额缺失与目标的相关性
对于loan_amount,可以假设数据是 MNAR 以及 MAR,因为married和dependents变量中的数据缺失或完成与loan_amount的数据缺失和完整性略有关联,如热图中所示。因此,我们选择使用机器学习来插补缺失值,并创建一个额外的列来指示缺失,这将为我们模型提供更好的信号。
接下来,我们将深入研究各种数据插补方法,并进行比较,同时讨论每种方法的不足。我们还将讨论机器学习在以数据为中心的机器学习中插补缺失数据的影响。
采用以模型为中心的方法,插补数值变量的标准规则是,当 5%的数据缺失时,使用均值、中位数或众数进行插补。这种方法假设数据是随机缺失的。如果这个假设不成立,这些简单的插补方法可能会掩盖数据中的分布和关系。
首先,我们将探索未插补和用中位数插补的loan_amount的分布。当我们用中位数插补 6%的值时,分布会发生变化:
df_consistent.loan_amount.plot.kde(color='orange', label='loan_amount', legend=True)
df_consistent.loan_amount.fillna(value=df.loan_amount.median()).plot.kde(color='b', label='loan_amount_imputed', alpha=0.5, figsize=(9,7), legend=True)
以下图表是输出显示:
图 5.7 – 使用中位数的简单密度图插补
接下来,我们比较插补前后贷款金额的标准差:
round(df_consistent.loan_amount.std(),2), round(df_consistent.loan_amount.fillna(value=df_consistent.loan_amount.median()).std(),2)
(85.59, 84.11)
上一段代码展示了简单的插补方法如何掩盖数据的分布。为了抵消这些影响并保持分布,我们将使用随机样本插补方法。
首先,我们提取所有loan_amount缺失的行。然后,我们计算与loan_amount相关的变量,并使用这些值来设置种子。这是因为,如果我们对所有值使用相同的种子,那么将生成相同的随机数,该方法的行为将与任意值插补类似,这将与均值和中位数等简单插补方法一样无效。
随机样本分布的缺点是协方差将受到影响,我们需要一种同时保持协方差的方法。
首先,我们检查哪个特征与loan_amount高度相关:
df_consistent[num_cols].corr()
applicant_income coapplicant_income loan_amount
applicant_income 1.000000 -0.116605 0.570909
coapplicant_income -0.116605 1.000000 0.188619
loan_amount 0.570909 0.188619 1.000000
在这里,我们可以看到loan_amount与applicant_income高度相关,因此在这个例子中,我们使用这个变量来设置种子。首先,我们提取loan_amount缺失的索引。然后,我们使用缺失位置的applicant_income值,并使用这个值来设置种子。接下来,我们使用这个种子从loan_amount生成一个随机值来插补缺失的行。我们使用这种方法来插补loan_amount的所有缺失数据:
observation = df_consistent[df_consistent.loan_amount.isnull()]
imputed_values = []
for idx in observation.index:
seed = int(observation.loc[idx,['applicant_income']])
imputed_value = df_consistent['loan_amount'].dropna().sample(1, random_state=seed)
imputed_values.append(imputed_value)
df_consistent.loc[df_consistent['loan_amount'].isnull(),'loan_amount_random_imputed']=imputed_values
df_consistent.loc[df['loan_amount'].isnull()==False,'loan_amount_random_imputed']=df_consistent[df_consistent['loan_amount'].isnull()==False]['loan_amount'].values
接下来,我们比较loan_amount的分布与随机样本插补的loan_amount和中位数插补的loan_amount:
df_consistent.loan_amount.plot.kde(color='orange', label='loan_amount', legend=True, linewidth=2)
df_consistent.loan_amount_random_imputed.plot.kde(color='g', label='loan_amount_random_imputed', legend=True, linewidth=2)
df_consistent.loan_amount.fillna(value=df_consistent.loan_amount.median()).plot.kde(color='b', label='loan_amount_median_imputed', linewidth=1, alpha=0.5, figsize=(9,7), legend=True)
<AxesSubplot: ylabel='Density'>
这将输出以下图表:
图 5.8 – 显示随机和中位数插补的密度图
现在,我们比较预先插补的贷款的标准差与随机样本插补方法和中位数插补方法:
round(df_consistent.loan_amount.std(),2), round(df_consistent.loan_amount_random_imputed.std(),2), round(df_consistent.loan_amount.fillna(value=df_consistent.loan_amount.median()).std(),2)
(85.59, 85.57, 84.11)
随机样本插补方法在分布和标准差上与预先插补的loan_amount方法比中位数插补的loan_amount方法更接近。接下来,我们检查随机样本插补方法是否与其他方法相比保留了与其他变量的相关性:
df_consistent['loan_amount_median_imputed'] = df_consistent['loan_amount'].fillna(value=df_consistent['loan_amount'].median())
df_consistent[['loan_amount', 'loan_amount_median_imputed','loan_amount_random_imputed', 'applicant_income']].corr()
得到的 DataFrame 如下:
图 5.9 – 相关性 DataFrame
从这一点来看,很明显,随机插补方法可以保留分布,但可能会掩盖与其他变量的相互关系。我们需要一种可以保留分布并保持与其他变量相互关系的方法。我们将使用机器学习来帮助我们实现这一点。在我们转向机器学习之前,我们首先将讨论简单插补对分类/二进制变量的影响。我们使用最频繁的值插补credit_history二进制列,并比较插补前后的分布:
df_consistent.credit_history.value_counts(normalize=True)
1.0 0.842199
0.0 0.157801
Name: credit_history, dtype: float64
df_consistent.credit_history.fillna(value=df_consistent.credit_history.mode()[0]).value_counts(normalize=True)
1.0 0.855049
0.0 0.144951
Name: credit_history, dtype: float64
通过用最频繁的值插补credit_history,我们使数据偏向于credit_history状态。正如我们之前发现的,credit_history的缺失与任何其他变量都不相关,但它可能与结果相关。
前面的例子表明,如果我们使用简单的插补方法,那么我们可能会对数据进行偏差,分布也会相应改变,而如果我们使用随机方法,分布将得以保留,但数据关系可能会改变,数据方差可能会增加。因此,当数据是 MAR(完全随机应答)或 MNAR(非随机应答)时,为了在数据偏差和数据方差之间取得平衡,我们可以使用机器学习模型。
为了利用机器学习进行数值插补,我们将利用scikit-learn库中可用的最近邻插补方法KNNImputer。这个插补器的一个问题是,我们只能传递一个 DataFrame 给它,而不能传递列的列表。因此,我们将使用SklearnTransformerWrapper模块,它是feature-engine库的一部分,来传递列的列表。由于 KNN 是一个基于距离的算法,为了确保模型收敛并且一个变量不会压倒另一个变量,我们必须在使用此算法之前对数据进行缩放。
另一种用于插补数据的技术被称为链式方程多重插补(MICE)。MICE 通过使用均值、中位数或众数来插补所有数据。然后,对于将要插补的变量,初始插补的值被转换回缺失值。接着,使用其他变量作为预测变量,利用机器学习模型预测缺失值。之后,以类似的方式插补下一个变量,其中初始插补的值被转换回缺失值,并使用包括最近插补的变量在内的其他变量作为预测变量来插补缺失值。一旦所有带有缺失值的变量都被建模,并且使用预测值插补了值,第一次插补轮次就完成了。这个过程重复n次(理想情况下为 10 次),从第二轮开始,使用第一轮的预测来预测最初缺失的记录。
使用多轮的原因是,最初我们使用其他也包含 NA 值的变量来模拟缺失数据,而初始的插补策略使用的是次优方法,如均值、中位数或众数,这些方法可能会对预测产生偏差。随着我们继续进行多轮回归,预测将趋于稳定并减少偏差。
MICE 的一个问题是,我们必须选择用于任务的机器学习模型。我们将使用随机森林算法实现 MICE,在 R 语言中这被称为[missForest]。
在我们实现的 MICE 中,我们将称之为missForest,因为它将复制 R 语言中实现的方式(R 语言中的 MissForest)。为了对抗选择算法的影响,我们鼓励实践者利用自动化机器学习,对于每一次插补和迭代,都会选择一个新的算法。这种方法的一个缺点是,当用于大数据集时,它计算量大且耗时。
首先,我们导入必要的包:
from sklearn.impute import KNNImputer
from feature_engine.wrappers import SklearnTransformerWrapper
from sklearn.preprocessing import StandardScaler
接下来,我们通过过滤掉可能包含超过 15 个类别的任何列,同时过滤id列和结果列,以及使用插补方法过滤新创建的变量来提取数值列:
num_cols = [cols for cols in df_consistent if df_consistent[cols].nunique() > 15 and cols not in [id_col, target] and not cols.endswith('imputed')]
接下来,我们创建包含数值变量的 DataFrame 并可视化它:
df_num = df_consistent[num_cols].copy()
df_num.head()
applicant_income coapplicant_income loan_amount
0 5849 0.0 NaN
1 4583 1508.0 128.0
2 3000 0.0 66.0
3 2583 2358.0 120.0
4 6000 0.0 141.0
接下来,我们构建一个函数,该函数接受缩放器(标准缩放器或任何其他缩放器)和 DataFrame,并返回缩放后的数据和经过处理的缩放器。在应用 KNN 估计器之前,我们必须缩放数据集,因为基于距离的方法需要数据处于相同的尺度。一旦我们缩放了数据,我们就应用 KNN 估计器来估计数据,然后使用函数返回的经过处理的缩放器来反缩放数据。完成这些后,我们可以比较机器学习估计的数据与中位数和随机估计方法:
def scale_data(df, scaler, columns):
"""Function to scale the data"""
df_scaled = df.copy()
if columns:
df_scaled[columns] = scaler.fit_transform(df_scaled[columns])
else:
columns = [cols for cols in df_scaled]
df_scaled[columns] = scaler.fit_transform(df_scaled[columns])
return df_scaled, scaler
接下来,我们定义缩放器并调用scale_data函数:
scaler = StandardScaler()
df_scaled, scaler = scale_data(df_num, scaler=scaler, columns=num_cols)
然后,我们使用 10 个邻居的参数应用 KNN 估计器来估计数据。我们利用weights='distance'参数,以便在预测结果时,更重视靠近邻居的投票,而不是远离邻居的投票。
首先,我们初始化估计器:
knn_imputer = SklearnTransformerWrapper(
transformer = KNNImputer(n_neighbors=10, weights='distance'),
variables = num_cols
)
然后,我们应用估计:
df_imputed = knn_imputer.fit_transform(df_scaled)
接下来,我们通过调用缩放器对象的inverse_transform方法来反缩放数据,并用未缩放值覆盖df_imputed DataFrame:
df_imputed = pd.DataFrame(columns=num_cols, data=scaler.inverse_transform(df_imputed))
df_imputed.head()
applicant_income coapplicant_income loan_amount
0 5849.0 0.0 149.666345
1 4583.0 1508.0 128.000000
2 3000.0 0.0 66.000000
3 2583.0 2358.0 120.000000
4 6000.0 0.0 141.000000
接下来,我们比较预先估计的loan_amount的分布,并将其与机器学习估计的方法进行比较。然后,我们检查机器学习估计方法与申请者收入的关联性,并将其与其他估计方法进行比较:
df_imputed['loan_amount'].plot.kde(color='orange', label='loan_amount_knn_imputed',linewidth=2, legend=True)
df_consistent['loan_amount'].plot.kde(color='b', label='loan_amount', legend=True, linewidth=2, figsize=(9,7), alpha=0.5)
结果图如下:
图 5.10 – 贷款金额 KNN 估计
接下来,我们比较预先估计的贷款金额的标准差与所有估计方法:
round(df_consistent.loan_amount.std(),2), round(df_consistent.loan_amount_random_imputed.std(),2), round(df_consistent.loan_amount_median_imputed.std(),2), round(df_imputed.loan_amount.std(),2)
(85.59, 85.57, 84.11, 85.59)
然后,我们将检查当使用机器学习来估计loan_amount时,相关性是否保持不变:
df_consistent['loan_amount_knn_imputed'] = df_imputed.loan_amount
df_consistent[['loan_amount', 'loan_amount_median_imputed','loan_amount_random_imputed', 'loan_amount_knn_imputed', 'applicant_income']].corr()
图 5.11 – 贷款金额估计后的相关性
机器学习估计的方法几乎与原始数据具有相同的分布。然而,与预先估计的loan_amount相比,与applicant_income的相关性略高。我们现在已经看到了如何使用现成的技术来估计缺失数据。这种方法的一个优点是易于实现。然而,缺点是我们不能选择另一个算法。
因此,在下一步中,我们更进一步,使用随机森林构建一个 MICE 实现。首先,我们使用独热编码将分类数据转换为数值数据。然后,我们使用RandomForestClassifier的 MICE 实现来估计缺失的分类数据。
一旦分类数据被估计,我们使用分类和数值数据,通过利用 MICE 与RandomForestRegressor来估计数值缺失值。
为了构建 MICE 实现,我们使用 scikit-learn 中的IterativeImputer,它可以帮助进行 10 轮 MICE。为了利用IterativeImputer,我们必须从 scikit-learn 的实验包中导入enable_iterative_imputer,如文档所述:scikit-learn.org/stable/modules/generated/sklearn.impute.IterativeImputer.html。
首先,我们导入必要的包:
from sklearn.ensemble import ExtraTreesRegressor, ExtraTreesClassifier
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from feature_engine.encoding import OneHotEncoder
接下来,我们提取字符串编码的分类列,以便我们可以对这些列进行独热编码:
ohe_cols = [cols for cols in cat_cols if df_consistent[cols].dtype == 'object']
ohe_cols
['property_area']
然后,我们对分类列进行独热编码:
df_ohe_encoded = df_consistent.copy()
ohe = OneHotEncoder(variables=ohe_cols)
df_ohe_encoded = ohe.fit_transform(df_ohe_encoded)
之后,我们可视化独热编码数据的前五个结果:
df_ohe_encoded[[cols for cols in df_ohe_encoded if 'property_area' in cols]].head()
property_area_urban property_area_rural property_area_semiurban
0 1 0 0
1 0 1 0
2 1 0 0
3 1 0 0
4 1 0 0
接下来,我们提取二进制编码的分类变量,包括已经独热编码的数据:
cat_cols = [cols for cols in df_ohe_encoded if df_ohe_encoded[cols].nunique() <= 15 and cols not in [id_col, target]]
cat_cols
['gender',
'married',
'dependents',
'education',
'self_employed',
'loan_amount_term',
'credit_history',
'property_area_urban',
'property_area_rural',
'property_area_semiurban']
然后,我们使用随机森林构建 MICE 实现以填充分类数据:
miss_forest_classifier = IterativeImputer(
estimator=ExtraTreesClassifier(n_estimators=100,
random_state=1,
bootstrap=True,
n_jobs=-1),
max_iter=10,
random_state=1,
add_indicator=True,
initial_strategy='median')
df_cat_imputed = miss_forest_classifier.fit_transform(df_ohe_encoded[cat_cols])
接下来,我们将填充的数值通过将 NumPy 数组转换为名为df_cat_imputed的 DataFrame 来提取特征:
df_cat_imputed = pd.DataFrame(
columns=miss_forest_classifier.get_feature_names_out(),
data=df_cat_imputed,
index=df_ohe_encoded.index)
让我们确保分类器没有创建任何新的意外值。为此,我们遍历所有列并打印每列的唯一值:
for cols in cat_cols:
print(cols)
print(df_cat_imputed[cols].unique())
print()
gender
[1\. 0.]
married
[0\. 1.]
dependents
[0\. 1\. 2\. 3.]
education
[1\. 0.]
self_employed
[0\. 1.]
loan_amount_term
[360\. 120\. 240\. 180. 60\. 300\. 480. 36. 84. 12.]
credit_history
[1\. 0.]
property_area_urban
[1\. 0.]
property_area_rural
[0\. 1.]
property_area_semiurban
[0\. 1.]
现在,我们将分类填充数据与数值数据合并。然后,我们使用所有数据来填充数值数据:
num_cols = [cols for cols in df_consistent if cols not in df_cat_imputed and cols not in [id_col, target] + ohe_cols
and not cols.endswith("imputed")]
df_combined = pd.concat([df_consistent[num_cols], df_cat_imputed], axis=1)
feature_cols = [cols for cols in df_combined]
feature_cols
['applicant_income',
'coapplicant_income',
'loan_amount',
'gender',
'married',
'dependents',
'education',
'self_employed',
'loan_amount_term',
'credit_history',
'property_area_urban',
'property_area_rural',
'property_area_semiurban',
'missingindicator_gender',
'missingindicator_married',
'missingindicator_dependents',
'missingindicator_self_employed',
'missingindicator_loan_amount_term',
'missingindicator_credit_history']
接下来,我们使用随机森林实现 MICE 填充以填充数值数据:
miss_forest_regressor = IterativeImputer(
estimator=ExtraTreesRegressor(n_estimators=100,
random_state=1,
bootstrap=True,
n_jobs=-1),
max_iter=10,
random_state=1,
add_indicator=True,
initial_strategy='median')
df_imputed = miss_forest_regressor.fit_transform(df_combined[feature_cols])
现在,我们将填充的数值通过将 NumPy 数组转换为 DataFrame 来提取特征:
df_imputed
df_imputed = pd.DataFrame(data=df_imputed,
columns=miss_forest_regressor.get_feature_names_out(),
index=df_combined.index)
然后,我们检查是否所有列都已填充并且没有缺失值:
df_imputed.isnull().sum()
applicant_income 0
coapplicant_income 0
loan_amount 0
gender 0
married 0
dependents 0
education 0
self_employed 0
loan_amount_term 0
credit_history 0
property_area_urban 0
property_area_rural 0
property_area_semiurban 0
missingindicator_gender 0
missingindicator_married 0
missingindicator_dependents 0
missingindicator_self_employed 0
missingindicator_loan_amount_term 0
missingindicator_credit_history 0
missingindicator_loan_amount 0
dtype: int64
接下来,我们比较预填充的loan_amount的分布,并将其与 MICE 填充方法进行比较。然后,我们检查 MICE 填充方法与申请者收入的关联性,并将其与其他填充方法进行比较:
df_imputed['loan_amount'].plot.kde(color='orange', label='loan_amount_miss_forest_imputed',linewidth=2, legend=True)
df_consistent['loan_amount'].plot.kde(color='b', label='loan_amount', legend=True, linewidth=2, figsize=(9,7), alpha=0.5)
<AxesSubplot: ylabel='Density'>
输出结果如下:
图 5.12 – loan_amount_miss_forest_imputed
接下来,我们比较预填充贷款金额的标准差与所有填充方法,包括 MICE 填充方法:
round(df_consistent.loan_amount.std(),2), round(df_consistent.loan_amount_random_imputed.std(),2), round(df_consistent.loan_amount_median_imputed.std(),2), round(df_imputed.loan_amount.std(),2)
(85.59, 85.57, 84.11, 85.41)
然后,我们检查当使用 MICE 填充方法填充loan_amount时,与其他方法相比,相关性是否保持不变:
df_consistent['loan_amount_miss_forest_imputed'] = df_imputed.loan_amount
df_consistent[['loan_amount', 'loan_amount_median_imputed','loan_amount_random_imputed', 'loan_amount_miss_forest_imputed', 'applicant_income']].corr()
输出的 DataFrame 如下:
图 5.13 – 使用 MICE 填充方法填充 loan_amount 后的相关性
标准差略低于随机填充,但高于中位数填充方法。正如我们所见,与随机填充方法或中位数填充方法相比,与applicant_income的相关性没有改善。因此,为了测试 MICE 与随机森林是否是此用例的更好实现,我们可以比较当使用 MICE 时和当使用中位数填充时机器学习模型的评估指标。
但在我们这样做之前,我们希望机器学习从业者使用 MICE 插补框架探索自动化机器学习(AutoML)。机器学习可能是一个繁琐的过程,它包括试错,这就是为什么 AutoML 框架在减少人工时间方面越来越受欢迎。这些框架自动化特征工程、交叉验证、模型选择和模型调优。MICE 当前实现的一个问题是,我们必须选择用于任务的机器学习模型。如果我们想尝试多个算法,看看哪个提供了最佳的插补任务预测,并且在尝试过程中确保预测具有可推广性,并且模型没有过拟合或欠拟合,会怎样呢?我们可以想象其复杂性。为了解决这个问题,我们将结合 AutoML 和 MICE。
这种方法的优点是,在每次迭代中,AutoML 将选择一个新的模型,从而让机器学习从业者从繁琐的任务中解放出来。然而,这种方法的缺点是,当数据量增加时,需要更多的资源,这可能不可行。另外,一些开源的 AutoML 框架的另一个缺点是,在某些操作系统上,完整功能可能会出现错误。例如,在 Mac 电脑上,TPOT 和 AutoSklearn 框架在并行处理时都会出现错误。因此,我们将让您探索使用 MICE 的 AutoML 的个性化版本。
接下来,我们将实现一个包含 MICE 实现和随机森林的 scikit-learn 流水线。然后,我们使用交叉验证训练决策树模型,并使用准确率和 ROC 评估模型。完成这些后,我们创建另一个流水线,它将使用简单的插补方法,并比较评估结果。最后,我们探索进一步改进数据以提高模型性能的技术。
我们将把这些步骤转换成 scikit-learn 流水线,因为通过使用流水线,我们可以定义步骤的顺序,并将这些步骤保存为 pickle 对象。通过利用这种做法,我们保持机器学习系统的最佳实践,并可以确保可靠性及可重复性,而无需在推理环境中重复编写代码。
首先,让我们删除df_consistent DataFrame 中所有以_imputed结尾的新创建的列:
df_consistent.drop([cols for cols in df_consistent if cols.endswith('imputed')], axis=1, inplace=True)
接下来,我们将导入所有必要的包和模块,以帮助将数据分为训练集和测试集,评估模型的性能,并创建机器学习流水线:
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import roc_auc_score, accuracy_score, confusion_matrix, ConfusionMatrixDisplay
from typing import List
from sklearn.tree import DecisionTreeClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
现在,我们提取模型所需的特征,并将数据分为训练集和测试集,其中 10%的数据保留用于测试:
feature_cols = [cols for cols in df_consistent if cols not in [target, id_col]]
X_train, X_test, y_train, y_test = train_test_split(df_consistent[feature_cols],
df_consistent[target].map({'Y':1, 'N':0}),
test_size=0.1,
random_state=1,
stratify=df_consistent[target].map({'Y':1, 'N':0}))
feature_cols
['gender',
'married',
'dependents',
'education',
'self_employed',
'applicant_income',
'coapplicant_income',
'loan_amount',
'loan_amount_term',
'credit_history',
'property_area']
接下来,我们将提取分类数据和数值数据到单独的列表中,以便我们可以使用这些数据为每种类型的数据设置流水线:
cat_cols = [cols for cols in X_train if X_train[cols].nunique() <= 15]
num_cols = [cols for cols in X_train if cols not in cat_cols]
现在,我们创建一个函数,该函数将返回用于分类数据的管道。首先,该管道将ohe_cols变量中的列列表进行独热编码,其中包括property_area。然后,该管道使用随机森林的 MICE 实现来填充缺失数据。该函数将返回转换器,以便当我们传递分类数据时,转换器将独热编码数据并填充缺失数据。转换器将首先在训练数据上运行,以便了解数据并保存所有元数据,以便用新数据运行相同的步骤。然后,转换器可以用来转换测试数据:
def miss_forest_categorical_transformer():
"""Function to define categorical pipeline"""
cat_transformer = Pipeline(
steps=[
("one_hot_encoding",
OneHotEncoder(variables=ohe_cols)
),
("miss_forest_classifier",
IterativeImputer(
estimator=ExtraTreesClassifier(
n_estimators=100,
random_state=1,
bootstrap=True,
n_jobs=-1),
max_iter=10,
random_state=1,
initial_strategy='median',
add_indicator=True)
)
]
)
return cat_transformer
接下来,我们创建一个函数,该函数返回用于用 MICE 实现填充数值缺失数据的管道转换器。与分类转换器类似,数值转换器将针对训练数据进行训练,然后应用于测试数据以填充训练和测试数据中的缺失值:
def miss_forest_numerical_transformer():
"""Function to define numerical pipeline"""
num_transformer = Pipeline(
steps=[
("miss_forest",
IterativeImputer(
estimator=ExtraTreesRegressor(n_estimators=100,
random_state=1,
bootstrap=True,
n_jobs=-1),
max_iter=10,
random_state=1,
initial_strategy='median',
add_indicator=True)
)
]
)
return num_transformer
然后,我们初始化分类和数值转换器,然后转换训练和测试数据。在转换数值数据之前,将转换后的分类数据与数值数据合并。这个输出的结果是填充后的训练和测试数据框:
cat_transformer = miss_forest_categorical_transformer()
num_transformer = miss_forest_numerical_transformer()
X_train_cat_imputed = cat_transformer.fit_transform(X_train[cat_cols])
X_test_cat_imputed = cat_transformer.transform(X_test[cat_cols])
X_train_cat_imputed_df = pd.DataFrame(data=X_train_cat_imputed,
columns=cat_transformer.get_feature_names_out(),
index=X_train.index)
X_test_cat_imputed_df = pd.DataFrame(data=X_test_cat_imputed,
columns=cat_transformer.get_feature_names_out(),
index=X_test.index)
X_train_cat_imputed_df = pd.concat([X_train_cat_imputed_df, X_train[num_cols]], axis=1)
X_test_cat_imputed_df = pd.concat([X_test_cat_imputed_df, X_test[num_cols]], axis=1)
X_train_imputed = num_transformer.fit_transform(X_train_cat_imputed_df)
X_test_imputed = num_transformer.transform(X_test_cat_imputed_df)
X_train_transformed = pd.DataFrame(data=X_train_imputed,
columns=num_transformer.get_feature_names_out(),
index=X_train.index)
X_test_transformed = pd.DataFrame(data=X_test_imputed,
columns=num_transformer.get_feature_names_out(),
index=X_test.index)
在将完整数据集传递给机器学习模型之前,我们检查训练和测试标签是否具有相似的贷款批准率:
y_train.mean(), y_test.mean()
(0.6865942028985508, 0.6935483870967742)
由于类别略微不平衡,我们可以使用class_weight='balanced'选项,因为这个选项使用y的值在训练算法时自动调整与输入数据中类别频率成反比的权重。问题的目标是识别出优于可能获得贷款的人类。由于大多数类别是在收到贷款的人身上训练的,因此模型将偏向于给某人发放贷款。通过使用class_weight='balanced',算法将更多地强调类别标签 0,因为它是一个少数类别。
我们定义了决策树分类器的网格搜索以执行交叉验证,以确保模型具有泛化能力:
d_param_grid = {
'max_features': [None, 'sqrt', 'log2'],
'max_depth' : [4,5,6,7,8,10,20],
'min_samples_leaf' : [1,3,5,8,10,12,15],
'min_samples_split': [2,6,10,16,20,24,30],
'criterion' : ['gini', 'entropy'],
'random_state' : [1],
'class_weight' : ['balanced']
}
d_clf = DecisionTreeClassifier()
接下来,我们创建一个自定义函数,该函数将接受训练数据、测试数据、分类器和网格搜索参数。该函数执行 10K 交叉验证以找到最佳超参数,并在最佳参数上训练模型。然后,该函数返回模型、预测、训练和测试准确率以及 ROC-AUC 分数:
def train_custom_classifier(X_train, y_train, X_test, y_test, clf, params):
"""Function to train the decision tree classifier and return some metrics"""
d_clf_cv = GridSearchCV(estimator=d_clf, param_grid=d_param_grid, cv=10, scoring='roc_auc')
d_clf_cv.fit(X_train_transformed, y_train)
print("Decision tree optimised")
d_best_params = d_clf_cv.best_params_
print(f"Getting the best params which are {d_best_params}")
model = DecisionTreeClassifier(**d_best_params)
model.fit(X_train_transformed, y_train)
training_predictions_prob = model.predict_proba(X_train_transformed)
testing_predictions_prob = model.predict_proba(X_test_transformed)
training_predictions = model.predict(X_train_transformed)
testing_predictions = model.predict(X_test_transformed)
training_roc_auc = roc_auc_score(y_train, training_predictions_prob[:,1])
testing_roc_auc = roc_auc_score(y_test, testing_predictions_prob[:,1])
training_acc = accuracy_score(y_train, training_predictions)
testing_acc = accuracy_score(y_test, testing_predictions)
print(f"Training roc is {training_roc_auc}, and testing roc is {testing_roc_auc} \n \
training accuracy is {training_acc}, testing_acc as {testing_acc}")
return model, testing_predictions, training_roc_auc, testing_roc_auc, training_acc, testing_acc
接下来,我们运行自定义分类器并计算模型性能:
model, test_predictions, train_roc, test_roc, train_acc, test_acc = train_custom_classifier(
X_train=X_train_transformed,
y_train=y_train,
X_test=X_test_transformed,
y_test=y_test,
clf=d_clf,
params=d_param_grid
)
Decision tree optimised
Getting the best params which are {'class_weight': 'balanced', 'criterion': 'entropy', 'max_depth': 8, 'max_features': None, 'min_samples_leaf': 1, 'min_samples_split': 30, 'random_state': 1}
Training roc is 0.8763326063416048, and testing roc is 0.7858017135862914
training accuracy is 0.8152173913043478, testing_acc as 0.7903225806451613
测试准确率略低于 80%。让我们通过观察混淆矩阵来查看模型在哪些方面表现不佳:
cm = confusion_matrix(y_test, test_predictions, labels=model.classes_, normalize='true')
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=model.classes_)
disp.plot()
这将输出以下混淆矩阵:
图 5.14 – 混淆矩阵
我们现在已应用了带有 MICE 插补的机器学习管道来创建机器学习模型。为了证明 MICE 插补技术比简单插补技术更好,我们将使用简单插补方法重新创建机器学习管道并评估模型性能。
一旦我们创建了管道步骤,我们将在将其传递给决策树分类器和自定义分类器函数以衡量模型性能之前,对训练数据和测试数据进行转换:
cat_transformer = Pipeline(
steps=[
("one_hot_encoding",
OneHotEncoder(variables=ohe_cols)
)
]
)
impute_transformer = Pipeline(
steps=[
("simple_imputer",
SimpleImputer(strategy='median',
add_indicator=True)
)
]
)
X_train_ohe = cat_transformer.fit_transform(X_train)
X_test_ohe = cat_transformer.transform(X_test)
X_train_imputed = impute_transformer.fit_transform(X_train_ohe)
X_test_imputed = impute_transformer.transform(X_test_ohe)
X_train_transformed = pd.DataFrame(data=X_train_imputed,
columns=impute_transformer.get_feature_names_out(),
index=X_train.index)
X_test_transformed = pd.DataFrame(data=X_test_imputed,
columns=impute_transformer.get_feature_names_out(),
index=X_test.index)
接下来,我们运行自定义分类器并提取模型性能:
model, test_predictions, train_roc, test_roc, train_acc, test_acc = train_custom_classifier(
X_train=X_train_transformed,
y_train=y_train,
X_test=X_test_transformed,
y_test=y_test,
clf=d_clf,
params=d_param_grid
)
测试准确率下降到低于 67%,下降了 12%,ROC-AUC 下降了 6%。接下来,我们回顾混淆矩阵:
cm = confusion_matrix(y_test, test_predictions, labels=model.classes_, normalize='true')
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=model.classes_)
disp.plot()
这是输出:
图 5.15 – 混淆矩阵
真阳性类的准确率从 88% 下降到 67%,而阴性类的准确率从 58% 上升到 63%。通过使用基本的插补技术,我们可以得出结论,该模型更有可能存在偏差,模型性能可能不够准确。
在以数据为中心的机器学习中,目标是改进数据并调整它,而不是改进算法和调整模型。但如何确定一个数据集是否包含标签错误的数据、缺失特征或其他数据相关的问题?我们将在第六章中介绍如何识别数据标签错误并应用技术来改进错误标记的数据,机器学习中的程序化标签技术。
为了找出是否需要更多特征或更多数据,我们利用一种称为错误分析的技术。在机器学习中,错误分析用于通过关注模型表现良好和表现不佳的数据区域来识别和诊断错误的预测。尽管模型的总体性能可能为 79%,但这种性能可能不会在整个数据区域中均匀分布,这些高低起伏可能是由某些区域存在的输入和在其他区域缺失的输入造成的。
为了识别数据问题,我们将使用 10% 的数据进行模型训练,并在每次迭代中增加 10%。然后,我们绘制训练 ROC 曲线和测试 ROC 曲线,以测试数据规模增加的情况。如果图表似乎收敛并表明数据规模增加,这将导致测试 ROC 的提高,此时我们将生成合成数据以增加数据规模。这项技术将在第七章中介绍,以数据为中心的机器学习中的合成数据使用。
如果图表似乎没有收敛,并表明数据增加,它将对提高测试 ROC 产生最小的影响。在这种情况下,我们可以观察到模型表现不佳的数据点,并可能利用特征工程生成新的列。尽管特征工程可能是一种迭代方法,但就本章的范围而言,我们只涵盖添加一个或两个特征。
要运行错误分析,首先,我们创建从 0.1 到 1.0 的数据截止点,其中 0.1 表示 10% 的训练数据,1.0 表示 100% 的训练数据:
data_cutoff_points = np.linspace(start=0.1, stop=1, num=10)
data_cutoff_points
array([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1\. ])
接下来,我们创建一个名为 scores 的空列表,并对每个数据截止点进行数据预处理、模型训练和评估。如果截止点小于 1.0,我们将子集训练数据;否则,我们将所有数据传递用于训练。在每个迭代结束时,我们将截止点、训练和测试评估指标保存到 scores 中,通过将指标追加到 scores 列表:
scores = []
for cutoff in data_cutoff_points:
if cutoff < 1.0:
X_train_subset, X_train_rem, y_train_subset, y_train_rem = train_test_split(X_train,
y_train,
random_state=1,
train_size=cutoff,
stratify=y_train)
else:
X_train_subset = X_train.copy()
y_train_subset = y_train.copy()
print(f"Model will be trained on {X_train_subset.shape[0]} rows out of {X_train.shape[0]}")
cat_transformer = miss_forest_categorical_transformer()
num_transformer = miss_forest_numerical_transformer()
X_train_cat_imputed = cat_transformer.fit_transform(X_train_subset[cat_cols])
X_test_cat_imputed = cat_transformer.transform(X_test[cat_cols])
X_train_cat_imputed_df = pd.DataFrame(data=X_train_cat_imputed,
columns=cat_transformer.get_feature_names_out(),
index=X_train_subset.index)
X_test_cat_imputed_df = pd.DataFrame(data=X_test_cat_imputed,
columns=cat_transformer.get_feature_names_out(),
index=X_test.index)
X_train_cat_imputed_df = pd.concat([X_train_cat_imputed_df, X_train_subset[num_cols]], axis=1)
X_test_cat_imputed_df = pd.concat([X_test_cat_imputed_df, X_test[num_cols]], axis=1)
X_train_imputed = num_transformer.fit_transform(X_train_cat_imputed_df)
X_test_imputed = num_transformer.transform(X_test_cat_imputed_df)
X_train_transformed = pd.DataFrame(data=X_train_imputed,
columns=num_transformer.get_feature_names_out(),
index=X_train_subset.index)
X_test_transformed = pd.DataFrame(data=X_test_imputed,
columns=num_transformer.get_feature_names_out(),
index=X_test.index)
model, test_predictions, train_roc, test_roc, train_acc, test_acc = train_custom_classifier(
X_train=X_train_transformed,
y_train=y_train_subset,
X_test=X_test_transformed,
y_test=y_test,
clf=d_clf,
params=d_param_grid)
scores.append((cutoff, train_roc, test_roc, train_acc, test_acc))
Model will be trained on 55 rows out of 552
Training roc is 0.9094427244582044, and testing roc is 0.5917992656058751
training accuracy is 0.7454545454545455, testing_acc as 0.5806451612903226
Model will be trained on 110 rows out of 552
Training roc is 0.901702786377709, and testing roc is 0.7552019583843328
training accuracy is 0.7272727272727273, testing_acc as 0.6290322580645161
Model will be trained on 165 rows out of 552
Training roc is 0.8986555479918311, and testing roc is 0.7099143206854346
training accuracy is 0.7696969696969697, testing_acc as 0.5967741935483871
Model will be trained on 220 rows out of 552
Training roc is 0.8207601497264613, and testing roc is 0.8084455324357405
training accuracy is 0.8318181818181818, testing_acc as 0.8064516129032258
Model will be trained on 276 rows out of 552
Training roc is 0.8728942407103326, and testing roc is 0.7906976744186047
training accuracy is 0.822463768115942, testing_acc as 0.7419354838709677
Model will be trained on 331 rows out of 552
Training roc is 0.9344501863774991, and testing roc is 0.7753977968176254
training accuracy is 0.8368580060422961, testing_acc as 0.7419354838709677
Model will be trained on 386 rows out of 552
Training roc is 0.8977545610478715, and testing roc is 0.7184822521419829
training accuracy is 0.7849740932642487, testing_acc as 0.6612903225806451
Model will be trained on 441 rows out of 552
Training roc is 0.8954656335198737, and testing roc is 0.7429620563035496
training accuracy is 0.81859410430839, testing_acc as 0.7258064516129032
Model will be trained on 496 rows out of 552
Training roc is 0.9102355500898685, and testing roc is 0.7441860465116278
training accuracy is 0.8266129032258065, testing_acc as 0.7258064516129032
Model will be trained on 552 rows out of 552
Training roc is 0.8763326063416048, and testing roc is 0.7858017135862914
training accuracy is 0.8152173913043478, testing_acc as 0.7903225806451613
接下来,我们从 scores 列表创建一个 DataFrame,并传递相关的列名:
df = pd.DataFrame(data=scores, columns=['data_size', 'training_roc', 'testing_roc', "training_acc", "testing_acc"])
然后,我们绘制训练和测试 ROC 与每个截止点的对比图:
plt.plot(df.data_size, df.training_roc, label='training_roc')
plt.plot(df.data_size, df.testing_roc, label='testing_roc')
plt.xlabel("Data Size")
plt.ylabel("ROC")
plt.title("Error Analysis")
plt.legend()
这将输出以下图表:
图 5.15 – 错误分析训练和测试 ROC
接下来,绘制训练和测试准确率与每个截止点的对比图:
plt.plot(df.data_size, df.training_acc, label='training_acc')
plt.plot(df.data_size, df.testing_acc, label='testing_acc')
plt.xlabel("Data Size")
plt.ylabel("Accuracy")
plt.title("Error Analysis")
plt.legend()
这将输出以下图表:
图 5.17 – 错误分析训练和测试准确率
测试 ROC 和测试准确率似乎显示出与训练 ROC 和训练准确率收敛的迹象,这表明如果提供更多的数据点,模型性能可能会得到提升。这就是为什么我们将在下一章生成模拟数据(模仿真实数据的数据)并使用添加的数据重新训练模型以获得更好的模型性能。
正如我们在前面的章节中学到的,数据为中心的机器学习的原则之一是让人类参与其中。让我们想象我们与领域专家进行了交谈,他们提到,一个人能否获得贷款的关键决定因素之一是收入与债务比率——即总收入除以贷款金额。这决定了一个人是否能够偿还贷款。收入与贷款比率较低的申请更有可能被拒绝。在数据集中,有两个收入变量——申请人收入和共同申请人收入。此外,贷款金额以千位表示——即数据中的贷款金额 66 代表 66,000。为了创建这个比率,我们将贷款金额乘以 1,000,然后结合申请人和共同申请人的收入。完成这些后,我们将合并的收入除以贷款金额以获得收入与贷款比率。领域专家还提到,等额本息还款(EMIs)也可以决定候选人的还款能力。EMI 越低,贷款被接受的可能性越大,而 EMI 越高,贷款被拒绝的可能性越大。为了在没有利率的情况下计算这个值,我们可以使用贷款期限和贷款金额来得到每月的近似 EMI 金额。
对于收入与贷款比率,我们将创建一个自定义转换器,将贷款金额乘以 1,000,以便我们可以在管道中使用它。
这个转换器是一个 Python 类,我们可以用它来覆盖管道所需的 fit 和 transform 函数。这个类将继承自BaseEstimator和TransformerMixin类,这两个类都可以在sklearn.base模块中找到。这个类将用于实现 fit 和 transform 方法。这些方法应该包含X和y参数,transform 方法应该返回一个 pandas DataFrame 以确保与 scikit-learn 管道的兼容性。
为了创建完整的收入列,我们利用feature_engine库,因为它已经与 scikit-learn 管道兼容,并且有应用于其他变量的数学运算方法。首先,我们求和收入变量。这个转换的输出将除以loan_amount变量以创建收入与贷款比率。
为了创建 EMI,我们利用feature_engine库,将loan_amount除以loan_amount_term。一旦我们创建了这些特征,我们就移除了两个收入变量,因为我们已经创建了这两个变量的组合。对于这一步,我们使用feature_engine库中的DropFeatures类。所有这些特征工程步骤将组合在一个新的名为feature-transformer的管道中,并在数据插补后应用。
我们相信,通过添加这些额外特征,决策树算法的模型性能将得到提高。让我们在特征工程后运行算法并评估结果。
首先,我们创建用于特征工程步骤的自定义变量,它将接受一个变量列表:
income_variables = ['applicant_income', 'coapplicant_income']
loan_variable = ['loan_amount']
loan_term_variable = ['loan_amount_term']
接下来,我们从 feature_engine 导入相关包以执行特征工程步骤,并导入 BaseEstimator 和 TransformerMixin 类:
from feature_engine.creation.math_features import MathFeatures
from feature_engine.creation.relative_features import RelativeFeatures
from sklearn.base import BaseEstimator, TransformerMixin
from feature_engine.selection import DropFeatures
然后,我们创建一个自定义转换器,它将接受变量名和一个将被每个变量乘以的值。默认情况下,每个变量将被乘以 1:
class MultiplyColumns(BaseEstimator, TransformerMixin):
"""Custom pipeline class to multiply columns passed in a DataFrame with a value"""
def __init__(self, multiply_by=1, variables=None):
self.multiply_by = multiply_by
self.variables = variables
def fit(self, X, y=None):
return self
def transform(self, X, y=None):
if self.variables:
X[self.variables] = X[self.variables] * self.multiply_by
return X
接下来,我们调用之前创建的 missForest 类别和数值转换器。完成此操作后,我们创建一个特征转换器管道,利用之前创建的自定义转换器将 loan_amount 乘以 1,000。新的管道然后将收入变量添加到一个收入变量中,即收入与贷款比率,以及 EMI 特征。最后,管道删除了两个收入变量,因为将创建新的收入变量。通过使用转换器管道,训练和测试数据将被转换,并创建新特征。这一步骤的输出将完全转换为训练和测试数据,以便可以传递给自定义分类器:
cat_transformer = miss_forest_categorical_transformer()
num_transformer = miss_forest_numerical_transformer()
feature_transformer = Pipeline(
steps=[
("multiply_by_thousand",
MultiplyColumns(
multiply_by=1000,
variables=loan_variable
)
),
("add_columns",
MathFeatures(
variables=income_variables,
func='sum'
)
),
("income_to_loan_ratio",
RelativeFeatures(variables=[f"sum_{income_variables[0]}_{income_variables[1]}"],
reference=loan_variable,
func=["div"]
)
),
("emi",
RelativeFeatures(variables=loan_variable,
reference=loan_term_variable,
func=["div"])
),
("drop_features",
DropFeatures(features_to_drop=income_variables
))
]
)
接下来,我们创建用于插补的类别转换器:
X_train_cat_imputed = cat_transformer.fit_transform(X_train[cat_cols])
X_test_cat_imputed = cat_transformer.transform(X_test[cat_cols])
X_train_cat_imputed_df = pd.DataFrame(data=X_train_cat_imputed,
columns=cat_transformer.get_feature_names_out(),
index=X_train.index)
X_test_cat_imputed_df = pd.DataFrame(data=X_test_cat_imputed,
columns=cat_transformer.get_feature_names_out(),
index=X_test.index)
X_train_cat_imputed_df = pd.concat([X_train_cat_imputed_df, X_train[num_cols]], axis=1)
X_test_cat_imputed_df = pd.concat([X_test_cat_imputed_df, X_test[num_cols]], axis=1)
然后,我们添加数值插补并完成插补步骤:
X_train_imputed = num_transformer.fit_transform(X_train_cat_imputed_df)
X_test_imputed = num_transformer.transform(X_test_cat_imputed_df)
X_train_imputed_df = pd.DataFrame(data=X_train_imputed,
columns=num_transformer.get_feature_names_out(),
index=X_train.index)
X_test_imputed_df = pd.DataFrame(data=X_test_imputed,
columns=num_transformer.get_feature_names_out(),
index=X_test.index)
接下来,我们使用之前创建的特征插补管道对缺失数据进行转换:
X_train_transformed = feature_transformer.fit_transform(X_train_imputed_df)
X_test_transformed = feature_transformer.transform(X_test_imputed_df)
在这一点上,我们调用自定义分类器函数,通过添加特征工程步骤来评估模型性能:
model, test_predictions, train_roc, test_roc, train_acc, test_acc = train_custom_classifier(
X_train=X_train_transformed,
y_train=y_train,
X_test=X_test_transformed,
y_test=y_test,
clf=d_clf,
params=d_param_grid)
Training roc is 0.8465996614150411, and testing roc is 0.8188494492044063
training accuracy is 0.8206521739130435, testing_acc as 0.8225806451612904
接下来,我们调用混淆矩阵:
cm = confusion_matrix(y_test, test_predictions, labels=model.classes_, normalize='true')
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=model.classes_)
disp.plot()
生成的混淆矩阵如下:
图 5.18 – 使用自定义特征工程时的混淆矩阵
我们的测试准确率从 79% 提高到 82%,ROC 从 78.5% 提高到 81.8%。前面的混淆矩阵显示,正类准确率从 88% 提高到 91%,而负类准确率从 58% 提高到 63%。
通过这种方式,我们已经证明了通过使用以数据为中心的方法,我们可以遍历数据而不是遍历多个算法,并设法提高模型性能。我们将在下一章探讨如何添加一些合成数据,并进一步提高模型性能。
确保数据有效
到目前为止,我们已经确保我们的数据是一致的、唯一的和完整的。但我们是否知道我们拥有的数据是有效的?数据标签是否符合规则?例如,如果数据集中的属性区域不符合规则,semi_urban是无效的怎么办?如果一位或几位标注者认为一些郊区既不是城市也不是乡村,并且违反了规则,输入了semi_urban怎么办?为了衡量有效性,我们可能需要查看业务规则并检查符合这些业务规则的数据百分比。让我们假设semi_urban是一个无效值。在 Python 中,我们可以检查无效标签的百分比,然后联系标注者纠正数据。我们也可以通过使用用于生成标签的数据来实现这一点。如果我们有suburb_name到property_area的数据映射,并且suburb_name在数据集中可用,那么我们可以利用这个映射来捕获无效值,以及通过编程方式编码标签。在系统中构建业务规则,以便自动编码即将到来的数据,这被称为编程标签。我们将在接下来的章节中深入探讨编程标签,我们将探讨在数据捕获时使数据一致和有效的技术,这样当数据到来时,它已经是纯净的,并且一些数据清理过程将是多余的。
首先,我们创建一个包含 10 行数据的假数据集,并编写一个业务规则。它将包含一个id列,其值为 1 到 10,一个population列,包含 1,000 到 100,000 之间的 10 个随机值,以及一个property_area列,其中四个值设置为urban,五个值设置为semi_urban,一个值设置为rural:
np.random.seed(1)
data = {
"id": np.linspace(start=1, stop=10, num=10, dtype=int),
"population" : np.random.randint(low=1000, high=100000, size=10),
"property_area": ["urban"]*4 + ["semi_urban"]*5 + ["rural"]*1
}
df = pd.DataFrame(data=data)
接下来,我们打印前五行:
df.head()
id population property_area
0 1 99539 urban
1 2 78708 urban
2 3 6192 urban
3 4 99047 urban
4 5 51057 semi_urban
假设业务规则说,当人口超过 20,000 时,郊区或property_area被归类为城市;否则,被归类为乡村。在这种情况下,验证规则应该检查property_area是否只包含urban或rural值。
在 Python 中检查这一点的简单方法是在value_counts()方法旁边使用normalize=True参数。这个输出的结果将显示 50%的数据是无效的:
df.property_area.value_counts(normalize=True)
semi_urban 0.5
urban 0.4
rural 0.1
Name: property_area, dtype: float64
接下来,我们可以对每一行运行检查,并在值位于预期值列表中时标记,以及当值不在预期值集中时标记:
df.property_area.isin(['rural', 'urban']) == False
0 False
1 False
2 False
3 False
4 True
5 True
6 True
7 True
8 True
9 False
Name: property_area, dtype: bool
现在,我们将违反数据验证规则的数据行求和,并将无效行数除以总行数,以提供一个指标——即无效标签的百分比:
sum(df.property_area.isin(['rural', 'urban']) == False) / df.shape[0]
0.5
无效数据必须通知数据源提供者,并且必须进行清理;否则,这些数据可能会悄悄进入,机器学习模型将学习这些无效标签。当使用有效数据进行训练时,模型的学习能力显著提高,因为它提供了更强的标签信号,与无效数据相比,无效数据由于对有效标签点的接触减少而削弱了这些信号。
确保数据准确
即使数据是有效的,它可能也不准确。数据准确性衡量的是与真实世界数据或可验证来源匹配的数据百分比。考虑到前面的property_area示例,为了衡量数据准确性,我们可能需要查找一个可靠的已发布数据集,并检查该地区的人口和地区类型。假设人口与可验证的数据源匹配,但地区类型来源不可用。使用定义农村地区和城市地区的规则,我们可以衡量数据准确性。
使用这个业务规则,我们将创建一个新的标签true_property_area,当人口在 20,000 人或以下时,其值为rural;否则,其值为urban:
df['true_property_area'] = df.population.apply(lambda value: 'rural' if value <= 20000 else 'urban')
接下来,我们将打印数据集的行以查看property_area和true_property_area之间是否存在任何不匹配:
df[['true_property_area', 'property_area', 'population']]
true_property_area property_area population
0 urban urban 99539
1 urban urban 78708
2 rural urban 6192
3 urban urban 99047
4 urban semi_urban 51057
5 urban semi_urban 74349
6 urban semi_urban 22440
7 urban semi_urban 99448
8 urban semi_urban 21609
9 urban rural 50100
然后,我们将匹配property_area值与真实值的行求和,然后除以总行数以计算数据准确性:
sum(df.property_area == df.true_property_area) / df.shape[0]
0.3
我们可以不创建一个函数来计算准确性,而是利用 scikit-learn 中的accuracy_score:
accuracy_score(y_pred=df.property_area, y_true=df.true_property_area)
0.3
如我们所见,两种方法都返回了相同的分数。如果错误的数据进入系统,模型可能会对半城市和农村地区学习不准确,并在推理时产生不理想的结果。
确保数据新鲜
数据新鲜度是衡量数据质量的一个重要方面,它对机器学习应用的质量和鲁棒性有影响。让我们想象一下,我们有一个在 2019 年和 2020 年客户行为上训练的机器学习应用,并用于预测到 2021 年 4 月的酒店房间预订。也许一月份和二月份的数字相当准确,但当三月份和四月份到来时,准确性下降。这可能是由于 COVID-19,这是数据未看到的情况,其影响没有被捕捉到。在机器学习中,这被称为数据漂移。这种情况正在发生;三月份和四月份的数据分布与 2019 年和 2020 年的数据分布有很大不同。通过确保数据新鲜且更新,我们可以更频繁地训练模型,或者在检测到数据漂移时立即训练。
为了测量数据漂移,我们将使用alibi Python 包。然而,还有更多广泛的 Python 包可以帮助完成这项工作。我们推荐 Evidently AI (www.evidentlyai.com/),这是一个数据与机器学习模型监控工具包,或者 WhyLogs (whylabs.ai/whylogs),这是 WhyLabs 的一个开源倡议,用于监控模型降级和数据漂移。
让我们假设,当模型在超过 5 天的旧数据上训练时,模型准确性开始下降,而当数据超过 10 天时,模型表现不佳并开始给企业带来成本。我们希望能够在这种情况发生时发出警报并捕获它。为了演示这个场景,我们将创建一个包含日期列的样本数据集,并定义错误和警告阈值——也就是说,如果数据是 5 天前的,我们打印警告;如果数据超过 10 天,我们阻止应用程序。在实践中,建议使用最新的可用数据进行模型训练。遵循以数据为中心的方法,我们必须鼓励从业者与数据提供者定义阈值和服务级别协议(SLAs),以便他们有机制来请求最新的数据,当 SLA 被违反时进行处罚,当 SLA 得到满足时进行奖励(鼓励保持高质量数据的重要性)。
现在,我们将生成 100 个样本数据点,并演示如何使用日期变量来识别数据是否过时。
我们使用alibi包来检测loan_prediction数据集中的漂移。我们将通过比较漂移前后的准确率来展示不检测和采取数据漂移措施的危害。
首先,我们导入datetime和warning包:
from datetime import datetime, timedelta
import warnings
接下来,我们生成一个基准日期——比如说我们运行代码的日期——然后从基准日期开始,通过每天减去一天来生成 100 个过去的日期:
numdays = 100
base = datetime.today()
date_list = [base - timedelta(days=day) for day in range(numdays)] # Subracting values from 1 to 100 from todays date
然后,我们按生成日期的顺序打印前 10 个日期,最近的日期是第一个日期:
[date.date().strftime('%Y-%m-%d') for date in date_list[0:10]]
['2023-02-04',
'2023-02-03',
'2023-02-02',
'2023-02-01',
'2023-01-31',
'2023-01-30',
'2023-01-29',
'2023-01-28',
'2023-01-27',
'2023-01-26']
接下来,我们创建一个包含 100 行的 DataFrame,通过创建四个列来实现。它将包含一个id列,其值为 1 到 100,一个date_loaded列,包含我们之前创建的 100 个日期,一个population列,包含 100 个介于 1,000 到 100,000 之间的随机值,以及一个property_area列,其中 40 个值设置为urban,50 个值设置为semi_urban,10 个值设置为rural:
np.random.seed(1)
data = {
"id": np.linspace(start=1, stop=100, num=100, dtype=int),
"population" : np.random.randint(low=1000, high=100000, size=100),
"property_area": ["urban"]*40 + ["semi_urban"]*50 + ["rural"]*10,
"date_loaded": date_list
}
df = pd.DataFrame(data=data)
现在,我们可视化前五个数据点:
df.head()
id population property_area date_loaded
0 1 99539 urban 2023-02-04 11:18:46.771142
1 2 78708 urban 2023-02-03 11:18:46.771142
2 3 6192 urban 2023-02-02 11:18:46.771142
3 4 99047 urban 2023-02-01 11:18:46.771142
4 5 51057 urban 2023-01-31 11:18:46.771142
接下来,我们编写一行代码来演示从今天日期减去任何日期并提取两个日期之间天数的方法:
(datetime.now() - df.date_loaded.max()).days
0
然后,我们创建一个函数,该函数将接受一个 DataFrame 及其日期列,默认情况下,如果数据超过 5 天,将发出警告;如果数据超过 10 天,将阻止应用程序:
def check_data_recency_days(df: pd.DataFrame, loaded_at_column: str, warning_at: int=5, error_at: int=10):
"""Function to detect data freshness"""
df = df.copy()
days_since_data_refreshed = (datetime.now() - df[loaded_at_column].max()).days
if days_since_data_refreshed < warning_at:
print(f"Data is fresh and is {days_since_data_refreshed} days old")
elif error_at > days_since_data_refreshed >= warning_at:
warnings.warn(f"Warning: Data is not fresh, and is {days_since_data_refreshed} days old")
else:
raise ValueError(f"Date provided is too old and stale, please contact source provider: {days_since_data_refreshed} days old")
接下来,我们使用之前创建的样本 DataFrame 运行该函数。该函数将声明数据是新鲜的,只有 0 天:
check_data_recency_days(df, "date_loaded")
Data is fresh and is 0 days old
为了展示函数在数据过时时的警告或错误输出能力,我们通过删除 6 天和 12 天内的数据来对数据进行子集划分。我们创建了两个 DataFrame——一个删除了 6 天内的数据,另一个删除了 12 天内的数据。然后,我们在这些 DataFrame 上运行check_data_recency_day函数。我们看到,当我们用 6 天前的数据运行函数时,函数将发出警告,但当我们用 12 天前的数据运行函数时,函数将发出一个Value错误。
让我们创建两个 DataFrame:
df_filter_6_days = df[df.date_loaded <= (datetime.today() - timedelta(days=6))]
df_filter_12_days = df[df.date_loaded <= (datetime.today() - timedelta(days=12))]
接下来,我们对 6 天前的数据进行函数运行:
check_data_recency_days(df_filter_6_days, "date_loaded")
/var/folders/6f/p7312_7n4nq5hp35rfymms1h0000gn/T/ipykernel_5374/1750573000.py:11: UserWarning: Warning: Data is not fresh, and is 6 days old
warnings.warn(f"Warning: Data is not fresh, and is {days_since_data_refreshed} days old")
您也可以对 12 天前的数据进行函数运行;它将生成类似的输出。
有了这些,我们已经展示了如何测量数据的新鲜度,捕捉警告,并在数据极度过时时阻止应用程序。接下来,我们将展示数据新鲜度对实际数据集的影响。
在现实生活中,我们不会期望一家公司不改变其产品,或者消费者行为不会随着市场上新产品的出现而改变。公司必须不断研究消费者行为的变化;否则,他们的业绩会下降。机器学习系统面临着市场力量变化、数据变化和数据分布变化相同的问题。如果新数据与训练数据大相径庭,这将对模型性能产生影响。
这在机器学习中被称为漂移,如果未检测到且未得到处理,它会导致模型退化。
让我们探索如何检测漂移。
首先,我们从alibi-detect包中导入TabularDrift:
import alibi
from alibi_detect.cd import TabularDrift
接下来,我们展示TabularDrift参考数据,这是机器学习系统训练的数据——在我们的案例中,是在我们将数据传递给决策树分类器之前转换的贷款预测数据。我们还为 p 值测试传递了一个值为0.05的值。如果测试数据分布违反了此值,该包将通知我们测试数据已从训练数据中漂移:
cd = TabularDrift(x_ref=X_train_transformed.to_numpy(), p_val=.05 )
现在,我们运行predict方法来检查测试数据是否发生了漂移。alibi包使用Kolmogorov-Smirnov测试来确定两个分布是否不同。如果 p 值超过 0.05,则拒绝零假设,可以推断出test数据分布与train数据分布不同。这一步骤的输出将是No:
preds = cd.predict(X_test_transformed.to_numpy())
labels = ['No', 'Yes']
print('Drift: {}'.format(labels[preds['data']['is_drift']]))
Drift: No
现在,让我们假设房价开始飙升,而收入并没有以相同的速度增长。为了模拟这种情况,我们将贷款金额增加到原始测试集的 1.5 倍,但将总收入增加到测试集的 1.2 倍。然后,我们更新依赖于loan_amount和income变量的新特征值:
X_test_transformed['loan_amount'] = X_test_transformed['loan_amount']*1.5
X_test_transformed['sum_applicant_income_coapplicant_income'] = X_test_transformed['sum_applicant_income_coapplicant_income']*1.2
X_test_transformed.sum_applicant_income_coapplicant_income_div_loan_amount = X_test_transformed.sum_applicant_income_coapplicant_income/X_test_transformed.loan_amount
X_test_transformed.loan_amount_div_loan_amount_term = X_test_transformed.loan_amount/X_test_transformed.loan_amount_term
接下来,我们再次运行 TabularDrift 的predict方法来检查是否检测到漂移。这一步骤的输出是Yes:
preds = cd.predict(X_test_transformed.to_numpy())
labels = ['No', 'Yes']
print('Drift: {}'.format(labels[preds['data']['is_drift']]))
Drift: Yes
然后,我们对由漂移引起的测试数据进行重新预测,并检查准确率和 ROC 是否受到影响:
testing_predictions_prob = model.predict_proba(X_test_transformed)
testing_predictions = model.predict(X_test_transformed)
testing_roc_auc = roc_auc_score(y_test, testing_predictions_prob[:,1])
testing_acc = accuracy_score(y_test, testing_predictions)
print(f"Testing roc is {testing_roc_auc} and testing_acc as {testing_acc}")
Testing roc is 0.747858017135863 and testing_acc as 0.6935483870967742
如我们所见,模型在训练过程中所看到的分布与真实数据不同,其影响是模型性能显著下降。ROC 值从 0.82 降至 0.74,准确率从 82%降至 70%。因此,确保数据新鲜非常重要,一旦检测到数据漂移,就需要用新数据重新训练模型,以确保模型性能不会下降。
摘要
在本章中,我们深入了解了数据质量的六个关键维度以及为什么提高数据质量对于提高模型性能至关重要。我们进一步探讨了通过迭代数据来提高模型性能的数据中心方法,而不是迭代各种算法(模型中心方法),通过提高数据的整体健康状况来实现。
接下来,我们学习了如何确保数据的一致性、唯一性、准确性、有效性、新鲜性和完整性。我们深入探讨了各种填充缺失值的技巧以及何时应用哪种方法。我们得出结论,使用机器学习填充缺失值可能比使用简单的填充方法更好,尤其是在数据是 MAR 或 MNAR 的情况下。我们还展示了如何进行错误分析,以及如何利用这些结果通过执行特征工程(涉及构建新特征)或通过创建合成数据来增加数据量,从而进一步提高模型性能,这些内容将在下一章中介绍。
我们还讨论了为什么确保数据新鲜且未从原始训练集中漂移很重要,并得出结论,漂移数据可能会损害模型性能。
现在我们已经理解了确保数据质量在数据质量的六个关键维度中的重要性,在下一章中,我们将深入探讨使用合成数据来进一步提高模型性能,特别是在边缘情况中。我们还将深入探讨数据增强技术,这是一种用于为图像创建合成数据的技术,以便算法可以从更多更好的数据中学习,尤其是在这些新示例可以以各种形式出现时。