不平衡数据的机器学习-全-
不平衡数据的机器学习(全)
原文:
annas-archive.org/md5/d4ae49feed2111c802924e036a1b91f2译者:飞龙
前言
欢迎大家!机器学习(ML)使计算机能够通过算法从数据中学习,以做出明智的决策、自动化任务并提取有价值的见解。一个经常引起注意的特定方面是不平衡数据,其中某些类别可能比其他类别有显著较少的样本。
本书提供了深入了解和导航倾斜数据复杂性的指南。你将获得在机器学习环境中管理不平衡数据集的最佳实践见解。
尽管不平衡数据可能带来挑战,但重要的是要理解,解决这种不平衡的技术并非普遍适用。它们的相关性和必要性取决于各种因素,如领域、数据分布、你正在优化的性能指标以及商业目标。在采用任何技术之前,建立基线是至关重要的。即使你目前没有面临不平衡数据的问题,了解本书中讨论的挑战和解决方案也可能是有益的。熟悉这些技术将为你提供一套全面的工具箱,帮助你为可能尚未遇到的情况做好准备。如果你发现模型性能不佳,尤其是对于代表性不足(少数)类别,书中涵盖的见解和策略可以在指导有效改进方面发挥关键作用。
随着机器学习和人工智能领域的不断扩展,对能够熟练处理各种数据挑战,包括不平衡数据的专业人士的需求将会增加。本书旨在为你提供知识和工具,让你成为那些备受追捧的专家之一。
本书面向的对象
这本全面的书精心设计,以满足各种专业人士的需求,包括以下人员:
-
机器学习研究人员、机器学习科学家、机器学习工程师和学生:在机器学习和深度学习领域寻求获得宝贵见解和实用知识,以应对数据不平衡带来的挑战的专业人士和学习者
-
数据科学家和分析师:渴望通过实际、现实世界的解决方案来扩展他们处理倾斜数据知识的数据专家
-
软件工程师:在处理不平衡数据时,希望有效地将机器学习和深度学习解决方案集成到他们的应用程序中的软件工程师
-
实用洞察寻求者:来自各个背景的专业人士和爱好者,他们希望通过实际、与行业相关的途径高效地处理机器学习和深度学习中的数据不平衡,从而在各自的岗位上脱颖而出
本书涵盖的内容
第一章, 机器学习中的数据不平衡介绍,探讨了机器学习环境中数据不平衡的问题。本章阐述了不平衡数据的本质,将其与其他数据集类型区分开来。它还全面介绍了机器学习的基本组成部分以及当存在数据不平衡时最相关的模型性能指标。本章探讨了处理不平衡数据所涉及的问题和关注点,解释了它何时可能发生以及为什么有时可能是一个挑战。更重要的是,我们将讨论何时不必担心数据不平衡,或者何时可能不值得担心。此外,它介绍了imbalanced-learn库,提供了处理不平衡数据集复杂性的宝贵见解和一般指南。
第二章, 过采样方法,介绍了过采样的概念,概述了何时使用以及何时不使用,以及增强不平衡数据集的各种技术。它通过使用imbalanced-learn库指导这些技术的实际应用,并比较它们在经典机器学习模型中的性能。本章以这些技术在现实场景中的有效性方面的实用建议结束。
第三章, 欠采样方法,介绍了欠采样作为在标准过采样不可行时平衡数据的有效方法的概念。本章涵盖了从不平衡数据中有效删除示例的策略,处理噪声观察的不同方法,以及处理易于分类实例的程序。我们还将讨论何时应避免对多数类进行欠采样。
第四章, 集成方法,探讨了包括袋装和提升在内的集成技术在增强机器学习模型性能中的应用。此外,它通过结合前几章中介绍的技术来解决不平衡数据集的挑战,其中传统的集成方法可能无效。
第五章, 代价敏感学习,探讨了包括过采样和欠采样在内的采样技术的替代方案。本章强调了代价敏感学习作为一种有效策略来克服不平衡数据集问题的意义。我们还讨论了阈值调整技术,这在数据不平衡的背景下可能非常相关。
第六章, 深度学习中的数据不平衡,介绍了深度学习的核心概念,并探讨了不平衡数据集提出的问题。您将研究各种深度学习应用中典型的不平衡数据挑战类型,并了解它们的影响。
第七章,数据级深度学习方法,标志着从传统机器学习到深度学习的转变,探讨了熟悉的数据级采样技术的调整,并在深度学习模型的背景下揭示了改进这些方法的机会。它深入探讨了结合深度学习与过采样和欠采样技术,包括图像和文本的动态采样和数据增强。它强调了深度学习和传统机器学习之间的基本区别,尤其是它们处理数据的性质,而深度学习处理的是如图像、文本、音频和视频这样的非结构化数据。本章还探讨了处理计算机视觉中类别不平衡的技术,以及它们在自然语言处理(NLP)问题中的应用。
第八章,算法级深度学习技术,在第五章,成本敏感学习的基础上扩展了概念,并将其应用于深度学习模型。我们通过使用 PyTorch 深度学习框架对损失函数进行修改来调整深度学习模型,从而提高模型性能并实现更有效的预测。
第九章,混合深度学习方法,探讨了连接前两章中数据级和算法级方法的创新技术。本章介绍了图机器学习的概念,并使用一个真实的 Facebook 社交网络数据集提供了关于解决深度学习中数据不平衡的有价值见解和实际应用。我们还将介绍硬挖掘损失的概念,并在此基础上探索一种称为少数类增量校正的专门技术,该技术将硬挖掘与交叉熵损失相结合。
第十章,模型校准,从不同的角度解决数据不平衡问题。本章不是关注数据预处理或模型构建,而是强调从训练模型获得的预测分数的后处理。这种后处理对于实时预测和离线模型评估都非常有价值。本章探讨了如何衡量模型的校准,并解释了为什么在处理不平衡数据时这一方面可能是不可或缺的。这尤其重要,因为数据平衡技术往往会导致模型校准不当。
附录,生产环境中的机器学习管道,提供了一个构建生产环境中遇到数据不平衡的 ML 管道的基础指南。本附录提供了一个简要的路线图,概述了在哪个顺序和阶段应该整合解决数据不平衡的技术。
📌 技术的使用 - 生产环境提示
在本书的整个过程中,你将遇到“在生产中”提示框,如下所示,突出显示所讨论技术的实际应用:
🚀 OpenAI 在生产中的类别重加权
OpenAI 试图解决图像生成模型 DALL-E 2 训练数据中的偏差问题[1]。DALL-E 2 在来自互联网的大量图像数据集上训练,这些数据可能包含偏差。例如,数据集中可能包含比女性更多的男性图像,或者比其他种族或民族群体更多的图像。
这些片段提供了关于知名公司如何应对数据不平衡以及他们采用了哪些策略来有效应对这些挑战的见解。例如,关于 OpenAI 使用 DALL-E 2 的方法的提示,揭示了在过滤训练数据和无意中放大偏差之间的复杂平衡。这样的例子强调了在处理不平衡数据时既要策略性强又要谨慎的重要性。为了深入了解具体细节并理解这些实现的细节,我们鼓励你关注公司博客或提供的论文链接。这些见解可以提供更清晰的理解,了解如何有效地适应和在不同现实场景中应用技术。
要充分利用这本书
本书假设读者对机器学习、深度学习和 Python 编程有一定的基础知识。对scikit-learn和PyTorch的基本了解可能会有所帮助,尽管这些可以在学习过程中掌握。
| 本书涵盖的软件/硬件 | 操作系统要求 |
|---|---|
| Google Colab | Windows、macOS 或 Linux |
对于软件要求,你有两种执行本书中提供代码的选项。你可以选择在colab.research.google.com/的 Google Colab 在线运行代码,或者将代码下载到你的本地计算机上并执行。Google Colab 提供了一个无烦恼的选项,因为它预装了所有必要的库,因此你不需要在本地机器上安装任何东西。你只需要一个网络浏览器来访问 Google Colab 和一个 Google 账户。如果你更喜欢本地工作,请确保你已经安装了 Python(3.6 或更高版本),以及指定的库,如 PyTorch、torchvision、NumPy 和 scikit-learn。所需库的列表可以在本书的 GitHub 仓库中找到。这些库与 Windows、macOS 和 Linux 操作系统兼容。现代 GPU 可以加速书中稍后出现的深度学习章节的代码执行;然而,这不是强制性的。
如果你使用的是本书的数字版,我们建议你亲自输入代码或从本书的 GitHub 仓库(下一节中提供链接)获取代码。这样做将有助于避免与代码复制和粘贴相关的任何潜在错误。
关于参考文献,我们使用编号参考文献,如“[6]”,你可以去该章节末尾的参考文献部分下载相应的参考文献(论文/博客/文章),无论是通过链接(如果提到)还是通过谷歌学术搜索(scholar.google.com/)。
每章结束时,你将找到一系列旨在测试你对所涵盖内容的理解程度的问题。我们强烈建议你参与这些问题,以巩固你的学习。所选问题的答案或解决方案可以在本书末尾的评估部分找到。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data。如果代码有更新,它将在 GitHub 仓库中更新。
我们还有其他来自我们丰富的图书和视频目录的代码包,可在github.com/PacktPublishing/找到。查看它们吧!
使用的约定
本书使用了多种文本约定。
文本中的代码: 表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称。以下是一个例子:“由于可以向BaggingClassifier提供一个基估计器,让我们使用最大树深为6的DecisionTreeClassifier。”
代码块设置如下:
from collections import Counter X, y = make_data(sep=2)print(y.value_counts()) sns.scatterplot(data=X, x="feature_1", y="feature_2")plt.title('Separation: {}'.format(separation))plt.show()
粗体: 表示新术语、重要单词或你在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是一个例子:“真负率(TNR):TNR 衡量的是实际负例被正确识别为负例的比例。”
小贴士或重要注意事项
看起来像这样。
联系我们
我们读者反馈总是受欢迎的。
一般反馈: 如果你对本书的任何方面有疑问,请通过客户关怀@packtpub.com 给我们发邮件,并在邮件主题中提及书名。
勘误: 尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果你在这本书中发现了错误,我们将不胜感激,如果你能向我们报告这个错误。请访问www.packtpub.com/support/errata并填写表格。
盗版: 如果你在互联网上以任何形式遇到我们作品的非法副本,如果你能提供位置地址或网站名称,我们将不胜感激。请通过版权@packt.com 与我们联系,并提供材料的链接。
如果您有兴趣成为作者:如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请访问authors.packtpub.com。
分享您的想法
一旦您阅读了《不平衡数据机器学习》,我们很乐意听听您的想法!请点击此处直接进入此书的亚马逊评论页面并分享您的反馈。
您的评论对我们和科技社区非常重要,并将帮助我们确保我们提供高质量的内容。
下载此书的免费 PDF 副本
感谢您购买此书!
您喜欢随时随地阅读,但又无法携带您的印刷书籍到处走?
您的电子书购买是否与您选择的设备不兼容?
不要担心,现在,随着每本 Packt 书籍,您都可以免费获得该书的 DRM 免费 PDF 版本。
在任何地方、任何地点、任何设备上阅读。从您最喜欢的技术书籍中搜索、复制和粘贴代码直接到您的应用程序中。
优惠远不止于此,您还可以获得独家折扣、时事通讯和每日免费内容的每日电子邮件访问权限
按照以下简单步骤获取好处:
- 扫描下面的二维码或访问以下链接

packt.link/free-ebook/9781801070836
-
提交您的购买证明
-
就这样!我们将直接将您的免费 PDF 和其他优惠发送到您的电子邮件中
第一章:机器学习中数据不平衡的介绍
机器学习算法已经帮助解决了各种现实世界问题,从疾病预测到在线购物。然而,我们希望用机器学习解决的问题中,许多都涉及不平衡数据集。在本章中,我们将讨论并定义不平衡数据集,解释它们与其他类型数据集的不同之处。我们将通过常见问题和场景的例子来展示不平衡数据的普遍性。我们还将学习机器学习的基础知识,包括损失函数、正则化和特征工程等基本概念。我们还将了解常见的评估指标,特别是那些对于不平衡数据集非常有帮助的指标。然后我们将介绍imbalanced-learn库。
尤其,我们将学习以下主题:
-
不平衡数据集的介绍
-
机器学习 101
-
数据集的类型和拆分
-
常见的评估指标
-
处理不平衡数据时的挑战和考虑因素
-
我们何时会在数据集中出现不平衡?
-
为什么不平衡数据会成为一个挑战?
-
何时不必担心数据不平衡
-
imbalanced-learn库的介绍 -
需要遵循的一般规则
技术要求
在本章中,我们将利用常见的库,如numpy和scikit-learn,并介绍imbalanced-learn库。本章的代码和笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/main/chapter01。您可以通过点击本章笔记本顶部的Open in Colab图标或通过使用笔记本的 GitHub URL 在colab.research.google.com启动来使用 Google Colab 打开 GitHub 笔记本。
不平衡数据集的介绍
机器学习算法从我们所称的数据集的集合中学习。这些数据集包含多个数据样本或点,我们可以在整本书中将这些样本、样本或实例互换使用。
当所有目标类别具有相似数量的示例时,可以说数据集具有平衡分布,如图图 1**.1所示:

图 1.1 – 类别间示例数量几乎相等的平衡分布
不平衡数据集或倾斜数据集是指某些目标类别(也称为标签)的数量超过其他类别的数据集(图 1**.2)。尽管这通常适用于机器学习中的分类问题(例如,欺诈检测),但在回归问题(例如,房价预测)中也会不可避免地发生:

图 1.2 – 具有五个类别和不同样本数量的不平衡数据集
我们将具有更多实例的类别标记为“多数”或“负”类,将具有较少实例的类别标记为“少数”或“正”类。大多数时候,我们的主要兴趣在于少数类,这就是为什么我们经常将少数类称为“正”类,将多数类称为“负”类:

图 1.3 – 不平衡分类中常用术语的视觉指南
这可以扩展到超过两个类别,这样的分类问题称为多类分类。在这本书的前半部分,我们将只关注二元分类,以使材料更容易理解。将概念扩展到多类分类相对容易。
让我们看看几个不平衡数据集的例子:
-
欺诈检测是指从多笔交易中检测出欺诈交易。这个问题在金融、医疗和电子商务行业中经常遇到并被广泛使用。
-
使用机器学习的网络入侵检测涉及分析大量网络流量数据,以检测和防止未经授权的访问和计算机系统的滥用。
-
癌症检测。癌症并不罕见,但我们仍然可能想使用机器学习来分析医疗数据,以更早地识别潜在的癌症病例并改善治疗效果。
在这本书中,我们希望专注于一般类别不平衡问题,并查看各种解决方案,在这些解决方案中,我们看到类别不平衡正在影响我们模型的性能。一个典型的问题是在模型训练期间模型看到的少数类示例非常少,模型在这些少数类上的表现相当差。
机器学习 101
让我们快速概述一下机器学习及其相关领域:
-
人工智能是所有与智能相关问题的超集。经典机器学习包括可以通过训练传统经典模型(如决策树或逻辑回归)并预测目标值来解决的问题。它们通常在表格数据上工作,需要大量的特征工程(手动开发特征),并且在文本和图像数据上的效果较差。深度学习在图像、文本、语音和视频数据上往往表现得更好,在这些情况下,通常不需要手动特征工程,神经网络中的各个层自动为我们进行特征工程。
-
在监督学习中,数据集既有输入也有输出(标签),模型在训练期间学习预测输出。每个输入可以表示为一个特征列表。输出或标签可以是有限类别的集合(分类)、实数(回归)或更复杂的东西。分类中的监督学习的一个经典例子是鸢尾花分类。在这种情况下,数据集包括花瓣长度、花瓣宽度、萼片长度和萼片宽度等特征,标签是鸢尾花的种类(setosa、versicolor 或 virginica)。可以在该数据集上训练一个模型,然后使用该模型将新的、未见过的鸢尾花分类为这些种类之一。
-
在无监督学习中,模型要么无法访问标签,要么不使用标签,然后尝试做出一些预测——例如,将数据集中的示例聚类到不同的组中。
-
在强化学习中,模型通过犯错误和优化目标或利润变量来尝试学习。一个例子是训练一个模型来下棋,并根据通过奖励和惩罚获得的反馈调整其策略。
在监督学习中(本书的重点),主要有两种类型的问题:分类和回归。分类问题涉及将数据分类到预定义的类别或标签中,例如“欺诈”或“非欺诈”以及“垃圾邮件”或“非垃圾邮件”。另一方面,回归问题旨在预测一个连续变量,例如房屋的价格。
虽然数据不平衡也可能影响回归问题,但本书将仅专注于分类问题。这种关注是由于几个因素,例如本书的范围有限以及分类中可用的技术已经建立。在某些情况下,甚至可以将回归问题重新构造成分类问题,使得本书中讨论的方法仍然相关。
当涉及到用于分类问题的各种流行模型时,我们有相当多的经典监督机器学习模型类别:
-
逻辑回归:这是一种用于二元分类问题的监督机器学习算法。它通过拟合逻辑函数到数据,根据一组预测变量(特征)预测二元目标变量的概率,该函数输出介于 0 和 1 之间的值。
-
支持向量机(SVMs):这些是主要用于分类的监督机器学习算法,也可以扩展到回归问题。SVMs 通过找到最优的超平面来最大化分离输入数据中的不同类别,从而成为二元和多类别分类任务的强大工具。
-
K-最近邻(KNN):这是一种用于分类和回归分析的监督机器学习算法。它根据训练数据集中的k个最近邻来预测目标变量。k的值决定了在做出预测时考虑的邻居数量,并且可以调整以优化模型性能。
-
树模型:这是一种用于分类和回归分析的监督机器学习算法。它们通过根据最重要的特征递归地将数据分割成更小的子集来创建决策树,该决策树根据输入特征预测目标变量。
-
集成模型:这些模型结合多个单个模型以提高预测准确性和减少过拟合(本章后面将解释)。集成技术包括袋装(例如,随机森林)、提升(例如,XGBoost)和堆叠。它们常用于分类以及回归分析。
-
神经网络:这些模型受人类大脑的启发,由多个层组成,每层有众多神经元,能够学习复杂函数。我们将在第六章中更详细地讨论这些内容,深度学习中的数据不平衡。
图 1.4显示了迄今为止我们已审查的各种分类器的决策边界。它显示逻辑回归具有线性决策边界,而基于树的模型,如决策树、随机森林和 XGBoost,通过将示例分割成轴平行的矩形来形成它们的决策边界。另一方面,SVM 将数据转换到不同的空间,以便它可以绘制其非线性决策边界。神经网络具有非线性决策边界:

图 1.4 – 在不平衡数据集上流行机器学习算法的决策边界
接下来,我们将深入探讨模型训练过程背后的原理。
模型训练过程中会发生什么?
在机器学习模型的训练阶段,我们向模型提供一个由示例组成的训练数据集,每个示例都有输入特征和相应的标签。让 X 代表用于训练的特征列表,y 代表训练数据集中的标签列表。模型的目标是学习一个函数 f,使得 f(X) ≈ y。
该模型具有可调整的参数,表示为θ,这些参数在训练过程中进行微调。误差函数,通常称为损失函数,定义为 L(f(X; θ), y)。该误差函数需要通过学习算法最小化,该算法找到这些参数θ的最佳设置。
在分类问题中,我们的典型损失函数是交叉熵损失(也称为对数损失):
CrossEntropyLoss(p) = {− log(p) if y = 1 − log(1 − p) otherwise
这里,p 是当 y = 1 时模型预测的概率。
当模型的预测与目标标签非常接近时,损失函数将趋近于零。然而,当预测与目标有显著偏差时,损失可以变得任意大,这表明模型拟合不良。
随着训练的进行,训练损失持续下降(图 1**.5):

图 1.5 – 随着训练的进行损失函数的变化率
这引出了模型拟合的概念:
-
如果一个模型过于简单,无法捕捉数据的复杂性,则称为欠拟合。它在训练数据和新的数据上表现都较差。
-
一个模型如果能够准确捕捉数据模式而不包含学习噪声,则被认为是合适的。它在训练数据和新的数据上表现良好。
-
一个过拟合的模型过于复杂,并学会了数据模式中的噪声。它在训练数据上表现良好,但在新的数据上表现较差(图 1**.6):

图 1.6 – 分类任务中的欠拟合、合适拟合和过拟合模型
接下来,让我们简要了解机器学习中的两个重要概念:
-
正则化是一组用于防止模型过度拟合训练数据的技巧。一种正则化类型(即 L1 或 L2)将惩罚项添加到损失函数中,这鼓励模型具有更小的权重并降低其复杂性。这有助于防止模型过于接近训练数据,并更好地泛化到未见数据。
-
特征工程是选择和转换模型输入特征以改进其性能的过程。特征工程包括选择与问题最相关的特征,将它们转换以使其更具信息性,并从现有特征中创建新特征。良好的特征工程可以在模型性能上产生巨大差异,并且通常比算法或超参数的选择更重要。
数据集类型和分割
通常,我们在训练集上训练模型,并在一个独立的未见数据集上测试模型,这个数据集被称为测试集。我们这样做是为了对模型进行公平的评估。如果我们不这样做,而是在整个数据集上训练模型并在同一数据集上评估模型,我们就不知道模型在未见数据上的表现如何,此外,模型很可能会过拟合。
在机器学习中,我们可能会遇到三种类型的数据集:
-
训练集:模型训练的数据集。
-
验证集:用于调整模型超参数的数据集。验证集通常被称为开发集。
-
评估集或测试集:用于评估模型性能的数据集。
当处理小型示例数据集时,通常将 80%的数据分配给训练集,10%分配给验证集,10%分配给测试集。然而,训练集和测试集之间的具体比例并不像确保测试集足够大以提供具有统计意义的评估结果那样重要。在大数据环境中,训练集、验证集和测试集分别以 98%、1%和 1%的比例分割可能是合适的。
通常,人们没有为超参数调整设置专门的验证集,而是将测试集作为评估集。这可能发生在超参数调整不是作为常规训练周期的一部分进行,而是一次性活动时。
交叉验证
交叉验证可能是一个令人困惑的术语,猜测其含义。分解它:交叉+验证,所以它是对扩展(交叉)某种东西的验证。在这里,“某种东西”对我们来说是测试集。
让我们看看什么是交叉验证:
-
交叉验证是一种技术,用于估计模型在实际应用中的准确性。
-
它通常用于检测过拟合——即未能泛化数据中的模式,尤其是在数据量可能有限的情况下。
让我们看看不同的交叉验证类型:
-
保留集(Holdout):在保留集方法中,我们将数据点随机分配到两个集合中,通常分别称为训练集和测试集。然后我们在训练集上训练(构建模型),在测试集上测试(评估其性能)。
-
k 折:它的工作方式如下:
-
我们随机打乱数据。
-
我们将所有数据分成k部分,也称为折。我们在k-1 折上训练模型,并在剩余的折上评估它。我们使用我们选择的模型评估指标记录这个模型的性能,然后丢弃这个模型。
-
我们重复这个过程k次,每次都保留不同的子集进行测试。我们从所有之前的模型中取评估指标值(例如,准确率)的平均值。这个平均值代表了模型的总体性能指标。
-
k 折交叉验证主要用于数据点有限的情况,比如 100 个点。在执行交叉验证时,使用 5 或 10 折是最常见的。
让我们看看机器学习中的常见评估指标,特别关注与不平衡数据问题相关的指标。
常见评估指标
几种机器学习和深度学习指标被用于评估分类模型的性能。
让我们来看看一些有助于评估我们在测试集上模型性能的有用指标。
混淆矩阵
给定一个试图将示例分类为正类或负类的模型,有四种可能性:
-
真阳性(TP):这发生在模型正确地将样本预测为正类的一部分时,这是其实际的分类。
-
假阴性 (FN): 这发生在模型错误地将正类样本分类为负类
-
真阴性 (TN): 这指的是模型正确地将样本识别为负类,这是其实际分类
-
假阳性 (FP): 这发生在模型错误地将负类样本预测为正类
表 1.1 展示了模型在做出预测时可能会“混淆”的方式,恰当地称为混淆矩阵。混淆矩阵是许多常见机器学习指标的基础:
| 预测正样本 | 预测负样本 | |
|---|---|---|
| 实际正样本 | 真正例 (TP) | 假阴性 (FN) |
| 实际负样本 | 假阳性 (FP) | 真阴性 (TN) |
表 1.1 – 混淆矩阵
让我们看看机器学习中一些最常见的指标:
-
真正例率 (TPR) 衡量模型正确分类的实际正例的比例:
TPR = 正确分类的正样本 ______________ 总正样本 = TP _ TP + FN
-
假阳性率 (FPR) 衡量模型错误地将实际负样本识别为正样本的比例:
FPR = 错误分类的负样本 _______________ 总负样本 = FP _ FP + TN
-
sklearn库中的sklearn.metrics.accuracy_score。 -
sklearn库下的sklearn.metrics.precision_score。精确率 = TP _ TP + FP。 -
sklearn库下的sklearn.metrics.recall_score。召回率 = TP _ TP + FN。表 1.2 总结了精确率和召回率之间的差异:
| 精确率 | 召回率 | |
|---|---|---|
| 定义 | 精确率是可信度的衡量标准 | 召回率是完整性的衡量标准 |
| 要问的问题 | 当模型说某件事是正样本时,它有多经常是正确的? | 在所有正样本中,模型正确识别了多少? |
| 示例(使用电子邮件过滤器) | 精确率衡量模型标记为垃圾邮件的电子邮件中有多少实际上是垃圾邮件,占所有标记电子邮件的百分比 | 召回率衡量模型捕获的实际垃圾邮件数量,占数据集中所有垃圾邮件的百分比 |
| 公式 | 精确率 = TP _ TP + FP | 召回率 = TP _ TP + FN |
表 1.2 – 精确率与召回率
为什么准确率对于不平衡数据集来说可能是一个糟糕的指标?
假设我们有一个不平衡的数据集,包含 1,000 个示例,其中 100 个标签属于类别 1(少数类)和 900 个属于类别 0(多数类)。
假设我们有一个模型,总是对所有示例预测 0。该模型对少数类的准确率为 900 + 0 _ (900 + 0+ 100 + 0 ) = 90%。

图 1.7 – 一幅漫画显示准确率可能并不总是正确的指标
这将我们带到机器学习中的 精度-召回率权衡。通常,精度和召回率是负相关的,也就是说,当召回率增加时,精度通常会降低。为什么?注意,召回率 = TP _ TP + FN,为了召回率增加,FN 应该减少。这意味着模型需要将更多项目分类为正类。然而,如果模型将更多项目分类为正类,其中一些可能是错误的分类,导致假阳性(FP)的数量增加。随着假阳性数量的增加,定义为 TP _ TP + FP 的精度将降低。用类似的逻辑,你可以论证当召回率降低时,精度通常会提高。
接下来,让我们尝试理解一些基于精度和召回率的指标,这些指标可以帮助衡量在不平衡数据上训练的模型的性能:
-
sklearn库中的sklearn.metrics.f1_score。 -
sklearn库中的sklearn.metrics.fbeta_score。 -
sklearn库中的sklearn.metrics.balanced_accuracy_score。 -
特异性 (SPE):特异性是衡量模型正确识别负样本能力的指标。在二分类中,它被计算为真阴性预测与负样本总数之比。高特异性表明模型擅长识别负类,而低特异性表明模型倾向于正类。
-
sklearn.metrics.precision_recall_fscore_support和imblearn.metrics.classification_report_imbalancedAPI。 -
imbalanced-learn库中的geometric_mean_score()定义为“正类示例上的准确率”(召回率或灵敏度或 TPR)和“负类示例上的准确率”(特异性或 TNR)的几何平均值。因此,即使一个类别被另一个类别大量超过,该指标仍然能代表模型的总体性能。 -
imblearn.metrics.classification_report_imbalanced。
表 1.3 展示了与混淆矩阵相关的指标及其公式作为扩展:
| 预测为正 | 预测为负 | ||
|---|---|---|---|
| 实际上 正 | 真阳性 (TP) | 假阴性 (FN) | 召回率 = 灵敏度 = 真阳性率 (TPR) = TP _ TP + FN |
| 实际上 负 | 假阳性 (FP) | 真阴性 (TN) | 特异性 = TN _ TN + FP |
| 精度 = TP/(TP+FP) 假阳性率 (FPR) = FP/(FP+TN) | 准确率 = TP + TN ________________________ TP + TN + FP + FN F1 分数 = 2 * 精度 * 召回率 ________________________ 精度 + 召回率 |
表 1.3 – 带有各种指标及其定义的混淆矩阵
ROC
接收者操作特征,通常称为 ROC 曲线,是显示各种阈值值下 y 轴上的 TPR 与 x 轴上的 FPR 的图表:
-
ROC 曲线本质上表示了y轴上正确预测的阳性实例的比例,与x轴上错误预测的阴性实例的比例形成对比。
-
在分类任务中,阈值是一个用于确定示例类别的截止值。例如,如果一个模型将一个示例分类为“阳性”,则可能设置 0.5 的阈值来决定该实例是否应被标记为属于“阳性”或“阴性”类别。ROC 曲线可以用来识别模型的最佳阈值。这个主题将在第五章,成本敏感学习中详细讨论。
-
要创建 ROC 曲线,我们计算模型预测概率的许多不同阈值值的 TPR 和 FPR。对于每个阈值,相应的 TPR 值被绘制在y轴上,FPR 值被绘制在x轴上,形成一个点。通过连接这些点,我们生成 ROC 曲线(图 1**.8):

图 1.8 – ROC 曲线作为 TPR 与 FPR 的图表(虚线表示没有技能的模型)
ROC 曲线的一些特性如下:
-
ROC 曲线下的面积(AUC)(也称为AUC-ROC)具有特定的用途:它提供了一个单一的数值,表示模型在所有可能的分类阈值上的性能:
-
AUC-ROC 表示类别的可分离程度。这意味着 AUC-ROC 越高,模型区分类别和预测阳性类别示例为阳性以及阴性类别示例为阴性的能力就越强。AUC 接近 0 的差劲模型实际上将阳性类别预测为阴性类别,反之亦然。
-
随机分类器的 AUC-ROC 为 0.5,并且是连接 ROC 曲线上的点(0,0)和(1,0)的对角线。
-
AUC-ROC 具有概率解释:AUC 为 0.9 表示模型将随机选择的阳性类别示例分配的分数高于阴性类别示例的概率为 90%。也就是说,AUC-ROC 可以表示如下:
-
P(score(𝑥+ ) > score(𝑥− ))
在这里,𝑥+ 表示阳性(少数)类别,而𝑥− 表示阴性(多数)类别。
- 在评估模型性能的背景下,使用一个反映模型将在现实世界场景中遇到的数据分布的测试集至关重要。这在考虑如 ROC 曲线等指标时尤其相关,因为 ROC 曲线在测试数据类别失衡变化时保持一致[2]。之所以如此,是因为这两个比率都是独立于测试数据类别分布的,因为它们仅基于每个类别的正确和错误分类实例计算,而不是每个类别的实例总数。这不同于训练数据中失衡的变化,这可能会对模型性能产生不利影响,并反映在 ROC 曲线上。
现在,让我们看看使用 ROC 曲线评估失衡数据集时可能遇到的一些问题:
-
ROC 曲线不区分各种类别——也就是说,它不会强调一个类别比另一个类别更重要。这对于失衡数据集来说可能是一个问题,因为在失衡数据集中,通常检测少数类别比检测多数类别更重要。正因为如此,它可能无法很好地反映少数类别。例如,我们可能希望召回率比精确率更好。
-
虽然 ROC 曲线可以用于比较模型在全范围内 FPR 的性能,但对于需要非常低 FPR 的特定应用,如金融交易或银行应用中的欺诈检测,它们可能不那么相关。FPR 需要非常低的原因是,此类应用通常需要有限的手动干预。可以手动检查的交易数量可能低至所有数据的 1%甚至 0.1%,这意味着 FPR 不能高于 0.001。在这些情况下,ROC 曲线上的 FPR 等于 0.001 右侧的所有内容都变得无关紧要[3]。为了进一步理解这一点,让我们考虑一个例子:
-
假设对于一个测试集,我们总共有 10,000 个示例,其中只有 100 个正类示例,占所有示例的 1%。因此,任何高于 1%的 FPR——即 0.01——都会引发过多的警报,无法由调查人员手动处理。
-
在 ROC 曲线的左侧远端的表现对于大多数现实世界问题至关重要,这些问题通常由大量负实例主导。因此,对于需要保持非常低 FPR 的应用,ROC 曲线的大部分内容都变得无关紧要。
-
精确率-召回率曲线
与 ROC 曲线类似,精确率-召回率(PR)曲线在不同阈值下绘制了一对指标。但与绘制 TPR 和 FPR 的 ROC 曲线不同,PR 曲线绘制的是精确率和召回率。为了展示这两条曲线之间的差异,让我们假设我们比较了两个模型——模型 1 和模型 2——在特定手工制作的失衡数据集上的性能:
-
在图 1.9(a)中,两个模型的 ROC 曲线似乎接近左上角(点(0, 1)),这可能会让你得出结论,两个模型都表现良好。然而,这在不平衡数据集的背景下可能会误导。
-
当我们将注意力转向图 1.9(b)中的 PR 曲线时,故事发生了变化。模型 2 接近图表的理想右上角(点(1, 1)),这表明在考虑精度和召回率时,其性能远优于模型 1。
-
PR 曲线显示模型 2 相对于模型 1 有优势。
ROC 曲线和 PR 曲线之间的这种差异也强调了使用多个指标进行模型评估的重要性,尤其是在处理不平衡数据时:

图 1.9 – 与 ROC 曲线相比,PR 曲线可以显示模型之间的明显差异
让我们尝试详细理解这些观察结果。虽然 ROC 曲线显示两种模型性能之间的差异很小,但 PR 曲线显示了更大的差距。原因在于 ROC 曲线使用 FPR,即 FP/(FP+TN)。通常,对于不平衡数据集,TN 非常高,因此即使 FP 有相当大的变化,FPR 的整体值也会被 TN 所掩盖。因此,ROC 变化不大。
哪个分类器更优越的结论可能会随着测试集中类别的分布而改变。在数据集倾斜的情况下,PR 曲线可以比 ROC 曲线更清楚地显示出模型表现不佳,如图所示。
sklearn中的average_precision_score。
ROC 曲线与 PR 曲线之间的关系
ROC 曲线和 PR 曲线之间的主要区别在于,ROC 评估模型在“计算”正负类别方面的表现,而 PR 仅关注正类别。因此,当处理平衡数据集的情况,并且你关心正负类别时,ROC AUC 表现得非常好。相比之下,当处理不平衡情况时,PR AUC 更为合适。然而,重要的是要记住,PR AUC 仅评估模型“计算”正类的能力。因为 PR 曲线对正类(少数类)更敏感,所以本书的前半部分我们将使用 PR 曲线。
我们可以在x轴上以精度重新想象 PR 曲线,在y轴上则是 TPR,也称为召回率。这两条曲线的关键区别在于,虽然 ROC 曲线使用 FPR,PR 曲线则使用精度。
如前所述,当处理不平衡数据集时,FPR(假正率)往往非常低。这种低 FPR 值的特性在诸如欺诈检测等某些应用中至关重要,因为这些应用中手动调查的能力本质上有限。因此,这种观点可能会改变对分类器性能的认识。如图 1.9 所示,当使用平均精度(0.69 比 0.90)而不是 AUC-ROC(0.97 和 0.95)进行比较时,两个模型的性能似乎也出现了反转。
让我们总结一下:
-
AUC-ROC 是在 y 轴上以 TPR 为基准,x 轴上以 FPR 为基准的曲线下的面积。
-
AUC-PR 是在 y 轴上以精确率为基准,x 轴上以召回率为基准的曲线下的面积。
由于 TPR 等于召回率,这两个图表仅在比较召回率时有所不同——要么是精确率,要么是 FPR。此外,这两个图表相对于彼此旋转了 90 度:
| AUC-ROC | AUC-PR | |
|---|---|---|
| 一般公式 | AUC(TPR, FPR) | AUC(精确率,召回率) |
| 扩展公式 | AUC( TP _ TP + FN , FP _ FP + TN ) | AUC( TP _ TP + FP , TP _ TP + FN ) |
| 对应关系 | AUC(召回率,FPR) | AUC(精确率,召回率) |
表 1.4 – 比较 ROC 和 PR 曲线
在接下来的几节中,我们将探讨导致数据集不平衡的情况,这些不平衡可能带来的挑战,以及数据不平衡可能不是问题的情况。
处理不平衡数据时的挑战和考虑因素
在某些情况下,直接使用数据用于机器学习而不担心数据不平衡,可能会得到适合特定业务场景的可用的结果。然而,在某些情况下,需要更多的努力来管理不平衡数据的影响。
广泛的说法,声称你必须始终或永远调整不平衡类别,往往具有误导性。事实是,解决类别不平衡的需要取决于数据的特定特征、手头的问题以及可接受解决方案的定义。因此,处理类别不平衡的方法应根据这些因素量身定制。
我们在什么情况下会遇到数据集的不平衡?
在本节中,我们将探讨导致数据集不平衡的各种情况和原因,例如罕见事件的发生或数据收集过程的偏差:
-
问题固有的:有时,我们需要解决的任务涉及检测数据集中的异常值——例如,患有某种疾病的患者或一组交易中的欺诈案例。在这种情况下,数据集固有不平衡,因为目标事件本身就很罕见。
-
在构建机器学习解决方案时数据收集的高成本:对于某些类别,收集数据可能成本过高。例如,由于需要专业的医疗测试、防护设备,以及在高度紧张的医疗环境中获取知情同意的伦理和物流挑战,收集 COVID-19 患者的数据成本很高。
-
某些类别的噪声标签:这可能在数据收集过程中,某些类别的标签中引入了大量噪声时发生。
-
标签错误:标签错误也可能导致数据不平衡。例如,如果一些样本被错误地标记为负例,而实际上它们是正例,这可能导致数据集中的不平衡。此外,如果一个类别本身就很罕见,人类标注者可能会存在偏见,并忽略那些存在的罕见类别的少数示例。
-
采样偏差:数据收集方法有时会在数据集中引入偏差。例如,如果一项调查在特定的地理区域或特定的人群中进行,那么得到的数据集可能无法代表整个群体。
-
数据清洗:在数据清洗或过滤过程中,由于数据不完整或缺失,某些类别或样本可能会被移除。这可能导致剩余数据集中的不平衡。
为什么不平衡数据会是一个挑战?
让我们深入探讨不平衡数据对模型预测的困难及其对模型性能的影响:
-
度量指标如准确率的失败:正如我们之前讨论的,在数据不平衡的背景下(一个 99%不平衡的数据集仍然可以达到 99%的准确率),传统的度量指标如准确率可能会产生误导。阈值不变的度量指标,如 PR 曲线或 ROC 曲线,试图揭示模型在广泛阈值范围内的性能。真正的挑战在于混淆矩阵中“真正负例”单元格的不成比例影响。那些较少关注“真正负例”的度量指标,如精确率、召回率或 F1 分数,更适合评估模型性能。需要注意的是,这些度量指标有一个隐藏的超参数——分类阈值——不应被忽视,而应在实际应用中进行优化(参考第五章,成本敏感学习,了解更多关于阈值调整的信息)。
-
不平衡数据对模型的损失函数可能构成挑战:这可能是因为损失函数通常设计为最小化预测输出与训练数据真实标签之间的错误。当数据不平衡时,一个类别的实例比另一个类别的实例多,模型可能会偏向多数类。我们将在第五章,成本敏感学习和第八章,算法级深度学习技术中更详细地讨论解决这个问题。
-
不同类别的误分类成本不同:通常,误分类正例的成本可能比误分类负例的成本更高。我们可能有的假阳性比假阴性更昂贵。例如,通常将患有癌症的患者误诊为健康(假阴性)的成本将远高于将健康患者误诊为患有癌症(假阳性)。为什么?因为第二种情况下,通过进行一些额外的测试来重新验证测试结果的成本要低得多,而不是在第一种情况下晚得多地检测到它。这被称为误分类成本,对于多数类和少数类可能不同,这使得不平衡数据集变得复杂。我们将在第五章,成本敏感学习中进一步讨论这个问题。
-
计算资源受限:在金融、医疗和零售等行业,处理大数据是一个常见的挑战。在这些大型数据集上进行训练不仅耗时,而且由于所需的计算能力,成本也很高。在这种情况下,对多数类进行下采样或欠采样变得至关重要,这将在第三章,欠采样方法中讨论。此外,为少数类获取更多样本还可以进一步增加数据集大小和计算成本。内存限制也可能限制可以处理的数据量。
-
少数类样本变化不足,无法充分代表其分布:通常,少数类样本的绝对数量问题并不像少数类样本的变化那样严重。数据集可能看起来很大,但样本中可能没有很多变化或种类,足以代表少数类的分布。这可能导致模型无法正确学习分类边界,从而导致模型性能不佳(图 1.10)。这种情况在计算机视觉问题中经常发生,例如目标检测,我们可能只有少数几个特定类别的样本。在这种情况下,数据增强技术(在第7 章,数据级深度学习方法中讨论)可以显著帮助:

图 1.10 – 不同分布的少数类示例对决策边界的影响 – 十字表示多数类,圆圈表示少数类
-
未校准模型的性能不佳:不平衡数据对未校准模型来说可能是一个挑战。未校准模型是指那些不输出良好校准概率的模型,这意味着预测概率可能不会反映预测类别的真实可能性:
-
当处理不平衡数据时,未校准的模型可能会特别容易产生偏向多数类的预测,因为它们可能无法有效地区分少数类和多数类。这可能导致模型在少数类上的性能不佳,模型可能会产生过于自信的预测或过于保守的预测。
-
例如,在不平衡数据上训练的未校准模型可能会错误地将属于少数类的实例分类为多数类示例,通常具有很高的置信度。这是因为模型可能没有学会根据数据的不平衡调整其预测,也可能对少数类示例没有很好的理解。
-
为了应对这一挑战,使用能够输出反映预测类别真实可能性的良好校准模型[4]非常重要。这可以通过如 Platt 缩放或等调回归等技术实现,这些技术可以将未校准模型的预测概率校准,以产生更准确和可靠的概率。模型校准将在第十章,模型校准中详细讨论。
-
-
由于未调整阈值导致的模型性能不佳:在使用不平衡数据集训练的模型进行预测时,使用智能阈值选择非常重要。当模型概率超过 0.5 时简单地预测 1 可能并不总是最佳方法。相反,我们应该考虑其他可能更有效的阈值。这可以通过检查模型的 PR 曲线来实现,而不是仅仅依赖于默认概率阈值 0.5 的成功率。阈值调整对于在自然或人工平衡数据集上训练的模型来说可能非常重要。我们将在第五章,成本敏感学习中详细讨论阈值调整。
接下来,让我们看看何时我们不应该对数据不平衡采取任何措施。
何时不必担心数据不平衡
类不平衡不一定总是对性能产生负面影响,有时使用特定于不平衡的方法可能会使结果变得更糟[5]。因此,在应用任何专门技术之前,准确评估任务是否真正受到类不平衡的影响至关重要。一种策略可以简单到设置一个不考虑类不平衡的基线模型,并观察模型在各种类别上使用各种性能指标的性能。
让我们探讨数据不平衡可能不是问题,且不需要纠正措施的场景:
-
当不平衡程度较小时:如果数据集中的不平衡相对较小,少数类到多数类的比例只有轻微倾斜(比如说 4:5 或 2:3),对模型性能的影响可能最小。在这种情况下,模型可能仍然表现良好,无需任何特殊技术来处理不平衡。
-
当目标是预测多数类:在某些情况下,重点可能在于准确预测多数类,而少数类可能不是特别感兴趣。例如,在线广告定位时,重点可以放在针对可能点击广告的用户(多数类)以最大化点击率和即时收入,而对可能觉得广告烦人的用户(少数类)的关注较少。
-
当两类误分类的成本几乎相等时:在某些应用中,将正类样本误分类的成本并不高(即假阴性)。例如,将电子邮件分类为垃圾邮件或非垃圾邮件。偶尔错过一封垃圾邮件并将其误分类为非垃圾邮件是完全正常的。在这种情况下,误分类对性能指标的影响可能可以忽略不计,不平衡可能不是问题。
-
当数据集足够大时:即使少数类到多数类样本的比例非常低,例如 1:100,并且如果数据集足够大,两个类别中都有大量样本,数据不平衡对模型性能的影响可能会降低。随着数据集的增大,模型可能能够更有效地学习少数类中的模式。然而,仍然建议将基线模型的性能与考虑数据不平衡的模型的性能进行比较。例如,将基线模型与具有阈值调整、过采样和欠采样的模型(第二章,过采样方法,和第三章,欠采样方法),以及基于算法的技术(第五章,成本敏感学习)进行比较。
在下一节中,我们将熟悉一个在处理不平衡数据时非常有用的库。我们将在一个不平衡的玩具数据集上训练一个模型,并查看一些指标来评估训练模型的性能。
不平衡-learn 库简介
imbalanced-learn(导入为 imblearn)是一个 Python 包,提供了处理数据不平衡的几种技术。在这本书的前半部分,我们将大量依赖这个库。让我们安装 imbalanced-learn 库:
pip3 install imbalanced-learn==0.11.0
我们可以使用 imbalanced-learn 为我们的分析创建一个合成数据集:
from sklearn.datasets import make_classification
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
def make_data(sep):
X, y = make_classification(n_samples=50000,
n_features=2, n_redundant=0,
n_clusters_per_class=1, weights=[0.995],
class_sep=sep, random_state=1)
X = pd.DataFrame(X, columns=['feature_1', 'feature_2'])
y = pd.Series(y)
return X, y
让我们分析生成的数据集:
from collections import Counter
X, y = make_data(sep=2)
print(y.value_counts())
sns.scatterplot(data=X, x="feature_1", y="feature_2", hue=y)
plt.title('Separation: {}'.format(separation))
plt.show()
这是输出:
0 49498
1 502

图 1.11 – 具有两个特征的 2 类数据集
让我们将这个数据集分成训练集和测试集:
From sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify = \
y, test_size=0.2, random_state=42)
print('train data: ', Counter(y_train))
print('test data: ', Counter(y_test))
这是输出:
train data: Counter({0: 39598, 1: 402})
test data: Counter({0: 9900, 1: 100})
注意 sklearn 的 train_test_split API 中 stratify 的使用。指定 stratify=y 确保我们在训练集和测试集中保持多数和少数类别的相同比例。让我们更详细地了解分层。
分层抽样是一种根据某些共享特征将数据集分成各种子组(称为“层”)的方法。在处理不平衡数据集时,它非常有价值,因为它确保训练集和测试集具有与原始数据集相同的类别标签比例。
在不平衡数据集中,少数类占总数据的很小一部分。如果我们不进行分层进行简单的随机分割,少数类可能无法在训练集中得到充分代表,甚至可能完全被排除在测试集之外,这可能导致性能不佳和不可靠的评估指标。
通过分层抽样,每个类别在整体数据集中的比例在训练集和测试集中都得到保留,确保了代表性的抽样,并为模型从少数类中学习提供了更好的机会。这导致了一个更稳健的模型和更可靠的模型性能评估。
scikit-learn 的分层 API
scikit-learn 的 API,例如 RepeatedStratifiedKFold 和 StratifiedKFold,使用分层概念通过交叉验证评估模型性能,尤其是在处理不平衡数据集时。
现在,让我们在训练数据上训练一个逻辑回归模型:
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(random_state=0, max_iter=2000)
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)
让我们从 sklearn 库中获取报告指标:
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
这会输出以下内容:
precision recall f1-score support
0 0.99 1.00 1.00 9900
1 0.94 0.17 0.29 100
accuracy 0.99 10000
macro avg 0.97 0.58 0.64 10000
weighted avg 0.99 0.99 0.99 10000
让我们从 imblearn 获取报告指标:
from imblearn.metrics import classification_report_imbalanced
print(classification_report_imbalanced(y_test, y_pred))
这会输出更多列:

图 1.12 – imbalanced-learn 的分类报告输出
你注意到这里与 sklearn API 相比有额外的指标吗?我们得到了三个额外的指标:spe 用于特异性,geo 用于几何平均值,iba 用于指数平衡准确率。
imblearn.metrics模块有几个这样的函数,可以帮助处理不平衡数据集。除了classification_report_imbalanced()之外,它还提供了sensitivity_specificity_support()、geometric_mean_score()、sensitivity_score()和specificity_score()等 API。
需要遵循的一般规则
通常,任何机器学习流程的第一步应该是将数据分为训练集、测试集和验证集。我们应该避免在数据分割之后应用任何处理不平衡的技术。我们应该首先将数据分为训练集、测试集和验证集,然后对训练数据进行任何必要的调整。在分割数据之前应用如过采样(见第二章,过采样方法)等技术可能会导致数据泄露、过拟合和过度乐观[6]。
我们应该确保验证数据与测试数据非常相似。验证数据和测试数据都应该代表模型将用于预测的真实世界场景。避免对验证集应用任何采样技术或修改。唯一的要求是包含来自所有类别的足够数量的样本。
让我们稍微讨论一下使用无监督学习算法。异常检测或离群值检测是一类可以用于处理不平衡数据问题的算法。异常或离群值是显著偏离其他数据的点。这些异常通常对应于不平衡数据集中的少数类,使得无监督方法可能很有用。
这些问题的常用术语是单类分类。当正例(少数类)稀疏或收集它们在训练之前不可行时,这种技术特别有益。模型仅针对被认为是“正常”或多数类的数据进行训练。然后它将新实例分类为“正常”或“异常”,有效地识别可能属于少数类的实例。这对于二元不平衡分类问题特别有用,其中多数类被认为是“正常”,而少数类被认为是异常。
然而,它确实有一个缺点:在训练期间丢弃了离群值或正例[7],这可能导致潜在的有价值信息的丢失。
总结来说,虽然像单类分类这样的无监督方法为处理类别不平衡提供了一个替代方案,但本书的讨论将始终集中在监督学习算法上。尽管如此,我们建议您在认为适当的时候探索和实验这些解决方案。
摘要
让我们总结一下到目前为止学到的内容。不平衡数据是机器学习中常见的问题,其中一个类别的实例数量显著多于另一个类别。不平衡数据集可能源于各种情况,包括罕见事件发生、高数据收集成本、标签噪声、标签错误、采样偏差和数据清洗。这可能会对机器学习模型构成挑战,因为它们可能会偏向多数类。
可以使用几种技术来处理不平衡数据,例如过采样、欠采样和成本敏感学习。最佳技术取决于具体问题和数据。
在某些情况下,数据不平衡可能不是问题。当数据集足够大时,数据不平衡对模型性能的影响可能会降低。然而,仍然建议比较基线模型性能与使用解决数据不平衡的技术(如阈值调整、基于数据的技术(过采样和欠采样)以及基于算法的技术)构建的模型性能。
传统的性能指标,如准确率,在不平衡数据集中可能会失效。一些更有用的不平衡数据集指标是 ROC 曲线、PR 曲线、精确率、召回率和 F1 分数。虽然 ROC 曲线适用于平衡数据集,但 PR 曲线在某一类比另一类更重要时,更适合不平衡数据集。
imbalanced-learn库是一个 Python 包,提供了处理数据不平衡的几种技术。
有一些一般规则需要遵循,例如在应用任何处理数据不平衡的技术之前将数据分为训练/测试/验证集,确保验证数据与测试数据非常相似,并且测试数据代表模型将最终进行预测的数据,以及避免对验证集和测试集应用任何采样技术或修改。
一类分类或异常检测是另一种可以用于处理无监督不平衡数据问题的技术。在这本书中,我们将只关注监督学习算法的讨论。
在下一章中,我们将探讨通过应用过采样技术来处理数据不平衡问题的一种常见方法。
问题
-
训练模型时选择损失函数如何影响不平衡数据集上模型的性能?
-
你能解释为什么 PR 曲线在处理高度倾斜的数据集时比 ROC 曲线更有信息量吗?
-
使用准确率作为不平衡数据集上模型性能的指标可能会出现哪些潜在问题?
-
“类别不平衡”的概念如何影响机器学习中的特征工程过程?
-
在不平衡数据集的背景下,k 折交叉验证中“k”的选择如何影响模型的性能?你将如何解决这个问题?
-
测试数据中类别的分布如何影响 PR 曲线,为什么?ROC 曲线又是如何?
-
在不平衡数据集的背景下,高 AUC-ROC 但低 AUC-PR 意味着什么?
-
“采样偏差”的概念如何有助于解决机器学习中不平衡数据集的挑战?
-
“标签错误”的概念如何有助于解决机器学习中不平衡数据集的挑战?
-
在哪些现实世界场景中,处理不平衡数据集是问题固有的部分?
-
使用
fetch_datasetAPI 然后计算 MCC、准确率、精确率、召回率和 F1 分数的值。看看 MCC 值是否可以成为该数据集的有用指标。
参考文献
-
V. García, R. A. Mollineda, 和 J. S. Sánchez, 平衡精度指数:偏斜类别分布的性能度量, 见《模式识别与图像分析》,第 5524 卷,H. Araujo, A. M. Mendonça, A. J. Pinho, 和 M. I. Torres 编著. 柏林,海德堡:Springer Berlin Heidelberg,2009,第 441–448 页. 访问时间:2023 年 3 月 18 日. [在线]. 可在
link.springer.com/10.1007/978-3-642-02172-5_57获取。 -
T. Fawcett, ROC 分析的介绍, 模式识别信函,第 27 卷,第 8 期,第 861–874 页,2006 年 6 月,doi: 10.1016/j.patrec.2005.10.010.
-
Y.-A. Le Borgne, W. Siblini, B. Lebichot, 和 G. Bontempi, 可复现的机器学习用于信用卡欺诈检测 - 实用手册. 自由布鲁塞尔大学,2022. [在线]. 可在
github.com/Fraud-Detection-Handbook/fraud-detection-handbook获取。 -
W. Siblini, J. Fréry, L. He-Guelton, F. Oblé, 和 Y.-Q. Wang, 通过校准掌握你的指标,第 12080 卷,2020,第 457–469 页. doi: 10.1007/978-3-030-44584-3_36.
-
Xu-Ying Liu, Jianxin Wu, 和 Zhi-Hua Zhou, 探索性下采样用于类别不平衡学习, IEEE Trans. Syst., Man, Cybern. B, 第 39 卷,第 2 期,第 539–550 页,2009 年 4 月,doi: 10.1109/TSMCB.2008.2007853.
-
M. S. Santos, J. P. Soares, P. H. Abreu, H. Araujo, 和 J. Santos, 不平衡数据集的交叉验证:避免过度乐观和过度拟合方法 [研究前沿],IEEE Comput. Intell. Mag., 第 13 卷,第 4 期,第 59–76 页,2018 年 11 月,doi: 10.1109/MCI.2018.2866730.
-
A. Fernández, S. García, M. Galar, R. Prati, B. Krawczyk, 和 F. Herrera, 从不平衡数据集中学习. Springer 国际出版社,2018
第二章:过采样方法
在机器学习中,我们往往没有足够的少数类样本。一个可能的解决方案可能是收集此类更多的样本。例如,在检测患者是否患有癌症的问题中,如果我们没有足够的癌症类样本,我们可以等待一段时间来收集更多样本。然而,这种策略并不总是可行或明智,并且可能耗时。在这种情况下,我们可以通过使用各种技术来增强我们的数据。其中一种技术就是过采样。
在本章中,我们将介绍过采样的概念,讨论何时使用它,以及执行它的各种技术。我们还将通过imbalanced-learn库的 API 演示如何利用这些技术,并使用一些经典的机器学习模型比较它们的性能。最后,我们将总结一些实际建议,说明哪些技术在特定现实世界条件下效果最佳。
在本章中,我们将涵盖以下主题:
-
随机过采样
-
SMOTE
-
SMOTE 变体
-
ADASYN
-
各种过采样方法的模型性能比较
-
使用各种过采样技术的指南
-
多类分类中的过采样
技术要求
在本章中,我们将使用如numpy、scikit-learn和imbalanced-learn等常用库。本章的代码和笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/master/chapter02。您只需点击本章笔记本顶部的在 Colab 中打开图标,或通过colab.research.google.com使用笔记本的 GitHub URL 启动,即可启动 GitHub 笔记本。
什么是过采样?
采样涉及从更大的观察集中选择观察子集。在本章中,我们最初将关注具有两个类别的二分类问题:正类和负类。少数类的实例数量显著少于多数类。在本章的后面部分,我们将探讨多类分类问题。在本章的结尾,我们将探讨多类分类问题的过采样。
过采样是一种数据平衡技术,它为少数类生成更多的样本。然而,这可以很容易地扩展到适用于任何有多个类别且存在不平衡的类别。图 2.1显示了在应用过采样技术之前,少数类和多数类的样本是不平衡的(a)以及之后平衡的情况(b):

图 2.1 – 过采样后少数类样本数量的增加
你可能会问:“为什么需要过采样?”这是必要的,以便我们给模型足够的少数类样本来从中学习。如果我们提供的少数类实例太少,模型可能会选择忽略这些少数类示例,而只关注多数类示例。这反过来又会导致模型无法很好地学习决策边界。
让我们使用sklearn库的make_classification API 生成一个 1:99 比例的两类不平衡数据集,该 API 为每个类别创建一个正态分布的点集。这将生成两个类的不平衡数据集:一个是有标签 1 的少数类,另一个是有标签 0 的多数类。在本章中,我们将应用各种过采样技术来平衡这个数据集:
from collections import Counter
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=10000, n_features=2,\
n_redundant=0, n_classes=2, flip_y=0, n_clusters_per_class=2,\
class_sep=0.79, weights=[0.99], random_state=81)
这段代码生成了 100 个类别 1 的例子和 9,900 个类别 0 的例子,不平衡比率为 1:99。通过绘制数据集,我们可以看到例子是如何分布的:

图 2.2 – 不平衡比率为 1:99 的数据集
在本节中,我们了解了过采样的必要性。我们还生成了一个合成的非平衡二分类数据集,以展示各种过采样技术的应用。
随机过采样
平衡数据集中不平衡的最简单策略是随机选择少数类的样本并重复或复制它们。这也被称为随机过采样****带替换。
为了增加少数类观察的数量,我们可以复制少数类数据观察结果足够多次以平衡两个类。这听起来太简单了吗?是的,但它是有效的。通过增加少数类样本的数量,随机过采样减少了向多数类的偏差。这有助于模型更有效地学习少数类的模式和特征。
我们将使用来自imbalanced-learn库的随机过采样。RandomOverSampler类的fit_resample API 重新采样原始数据集并使其平衡。sampling_strategy参数用于指定各种类的新比率。例如,我们可以将sampling_strategy=1.0指定为两个类具有相同数量的例子。
有多种方式可以指定sampling_strategy,例如浮点值、字符串值或dict – 例如,{0: 50, 1: 50}:
from imblearn.over_sampling import RandomOverSampler
ros = RandomOverSampler(sampling_strategy=1.0, random_state=42)
X_res, y_res = ros.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))
这里是输出:
Resampled dataset shape Counter({0: 9900, 1: 9900})
因此,我们从 1:99 的比例变为了 1:1,这正是我们期望的sampling_strategy=1.0的结果。
让我们绘制过采样后的数据集:

图 2.3 – 使用 RandomOverSampler(RandomOverSampler)过采样后的数据集(标签 1 的示例由于重叠而未改变)
应用随机过采样后,标签为 1 的示例会相互重叠,给人一种没有变化的感觉。反复重复相同的数据点可能导致模型记住特定的数据点,而无法推广到新的、未见过的示例。RandomOverSampler中的shrinkage参数让我们可以通过一个小量扰动或移动每个点。
shrinkage参数的值必须大于或等于 0,可以是float或dict类型。如果使用float数据类型,相同的收缩因子将用于所有类别。如果使用dict数据类型,收缩因子将针对每个类别具体指定。
在图 2.4 中,我们可以观察到shrinkage=0.2的随机过采样的影响:

图 2.4 – 应用随机过采样(shrinkage=0.2)的结果
在本章的结尾,我们将比较随机过采样与其他多种过采样技术在多个模型和数据集上的性能。这将为我们提供关于它们在实际应用中的有效性的见解。
🚀 Grab 在生产中使用随机过采样
Grab 是一家东南亚的打车和食品配送服务公司,开发了一个用于存储和检索图像和地图数据的图像收集平台[1]。该平台的一个关键特性是能够自动检测和模糊街景图像中的个人身份信息(PII),如人脸和车牌。这对于维护用户隐私至关重要。用于此目的的数据集存在显著的不平衡,负样本(没有 PII 的图像)远多于正样本(有 PII 的图像)。手动标注不可行,所以他们转向机器学习来解决此问题。
为了解决数据不平衡,Grab 采用了随机过采样技术来增加正样本的数量,从而提高了他们的机器学习模型的性能。
随机过采样的问题
随机过采样往往会导致模型过拟合,因为生成的合成观察值会重复,模型会一次又一次地看到相同的观察值。收缩试图在某种程度上处理这个问题,但可能很难找到一个合适的收缩值,而且收缩并不关心生成的合成样本是否与多数类样本重叠,这可能导致其他问题。
在上一节中,我们学习了应用过采样平衡数据集和减少对多数类偏差的最基本和实用的技术。很多时候,随机过采样本身可能会极大地提高我们模型的表现,以至于我们可能甚至不需要应用更高级的技术。在生产环境中,在准备引入更多复杂性之前,保持事情简单明了也是有利的。正如他们所说,“过早优化是万恶之源”,所以我们从简单的事情开始,只要它能提高我们模型的表现。
在随后的章节中,我们将探讨一些替代技术,例如 SMOTE 和 ADASYN,它们采用不同的过采样方法,并缓解了与随机过采样技术相关的一些问题。
SMOTE
随机过采样的主要问题是它会重复少数类的观察结果。这通常会导致过拟合。合成少数过采样技术(SMOTE)[2]通过使用称为插值的技术来解决这种重复问题。
插值涉及在已知数据点的范围内创建新的数据点。将插值想象成类似于生物学中的繁殖过程。在繁殖中,两个个体结合在一起产生一个具有两者特征的新个体。同样,在插值中,我们从数据集中选择两个观察结果,并通过选择两个选定点之间线上的随机点来创建一个新的观察结果。
我们通过插值合成示例来过采样少数类。这防止了少数样本的重复,同时生成与已知点相似的新的合成观察结果。图 2.5展示了 SMOTE 是如何工作的:

图 2.5 – SMOTE 的工作原理
在这里,我们可以看到以下内容:
-
大多数类和少数类样本被绘制出来(左侧)
-
通过在连接少数样本和两个最近邻多数类样本的线上的随机点生成合成样本(右侧)
SMOTE 最初是为连续输入设计的。为了保持解释的简单性,我们将从连续输入开始,稍后再讨论其他类型的输入。
首先,我们将检查 SMOTE 的功能,并探讨与此技术相关的任何潜在缺点。
SMOTE 是如何工作的
SMOTE 算法的工作原理如下:
-
它只考虑少数类的样本。
-
它在少数样本上训练 KNN。
k的典型值是 5。 -
对于每个少数样本,从该点到其 KNN 示例之间画一条线。
-
对于这样的线段,随机选择线段上的一个点来创建一个新的合成示例。
让我们使用imbalanced-learn库的 API 来应用 SMOTE:
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=0)
X_res, y_res = sm.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))
这里是输出:
Resampled dataset shape Counter({0: 9900, 1: 9900})
过采样后的数据集看起来像这样:

图 2.6 – 使用 SMOTE 进行过采样
🚀 微软生产中的过采样技术
在微软的一个实际应用[3]中,机器学习被用于预测现场事件(LSIs),以实现事件的早期检测和升级,这对于工程团队来说至关重要。每天都会产生大量的事件,其中大部分最初是低严重性问题。由于资源有限,工程团队调查所有事件是不切实际的,这可能导致在事件对客户产生重大影响之前,缓解关键问题的潜在延迟。
为了解决这个问题,微软采用了机器学习来预测哪些 LSIs 可能会升级为严重问题,目标是进行主动识别和早期解决。挑战在于训练集中的数据不平衡:在约 40,000 个事件中,不到 2%升级为高严重性。微软使用了两种不同的过采样技术——袋分类(在第第四章,集成方法)和 SMOTE,这些技术在提高模型性能方面最为有效。他们使用了两步流程来平衡类别:首先,使用SMOTE进行过采样,然后使用RandomUnderSampler(在第第三章,欠采样方法)进行欠采样。该流程自动选择了两步的最优采样比率,并且当与欠采样结合时,SMOTE 表现更佳。所得到的端到端自动化模型被设计为通用型,使其适用于微软内部或外部的不同团队,前提是可用历史事件进行学习。LSI 洞察工具使用了这个模型,并被各个工程团队采用。
接下来,我们将探讨使用 SMOTE 的局限性。
SMOTE 的问题
SMOTE 有其陷阱——例如,它可能会向已经嘈杂的数据集中添加噪声。它也可能导致以下类重叠问题:
- SMOTE 在生成少数类样本时没有考虑多数类的分布,这可能会增加类别之间的重叠。在图 2.7 中,我们绘制了应用 SMOTE 前后的二分类不平衡数据集。我们可以看到应用 SMOTE 后两个类别之间有很多重叠:

图 2.7 – 应用 SMOTE 前(左)和后(右)的二分类数据集(右图中两个类别的重叠部分)
- 另一种情况可能是你拥有大量数据,运行 SMOTE 可能会增加你管道的运行时间。
问题 1 可以通过使用 SMOTE 变体 Borderline-SMOTE(将在下一节中讨论)来解决。
在本节中,我们学习了 SMOTE,它使用最近邻技术来生成少数类的合成样本。有时,SMOTE 可能比随机过采样表现得更好,因为它利用了与其他少数类样本的邻近性来生成新的样本。
SMOTE 变体
现在,让我们看看一些 SMOTE 的变体,例如 Borderline-SMOTE、SMOTE-NC 和 SMOTEN。这些变体将 SMOTE 算法应用于特定类型的样本,并且不一定总是适用。
Borderline-SMOTE
Borderline-SMOTE [4]是 SMOTE 的一种变体,它从靠近分类边界的少数类样本中生成合成样本,该边界将多数类与少数类分开。
为什么要考虑分类边界的样本?
理念是靠近分类边界的例子比远离决策边界的例子更容易误分类。在边界附近产生更多的这种少数样本将有助于模型更好地学习少数类。直观上,远离分类边界的点可能不会使模型成为一个更好的分类器。
这里是 Borderline-SMOTE 的逐步算法:
-
我们在整个数据集上运行 KNN 算法。
-
然后,我们将少数类点分为三类:
-
噪声点是所有邻居都是多数类的少数类例子。这些点被埋在多数类邻居中。它们可能是异常值,可以安全地被忽略作为“噪声”。
-
安全点比多数类邻居有更多的少数类邻居。这样的观察结果包含的信息不多,可以安全地忽略。
-
危险点比少数类邻居有更多的多数类邻居。这表明这样的观察结果位于或接近两类之间的边界。
-
-
然后,我们仅在少数类例子上训练 KNN 模型。
-
最后,我们将 SMOTE 算法应用于
危险点。请注意,这些危险点的邻居可能被标记为危险,也可能不是。
如图 2.8所示,Borderline-SMOTE 在生成合成数据时专注于危险类点:

图 2.8 – Borderline-SMOTE 算法仅使用危险点来生成合成样本。危险点比少数类邻居有更多的多数类邻居
图 2.9展示了 Borderline-SMOTE 如何专注于靠近分类边界的少数类样本,该边界将多数类和少数类分开:

图 2.9 – Borderline-SMOTE 的说明
这里,我们可以看到以下情况:
a) 多数类和少数类样本的图
b) 使用靠近分类边界的邻居生成的合成样本
让我们看看如何使用imbalanced-learn库中的 Borderline-SMOTE 来执行数据过采样:
print("Before: ", sorted(Counter(y).items()))
from imblearn.over_sampling import BorderlineSMOTE
X_resampled, y_resampled = BorderlineSMOTE().fit_resample(X, y)
print("After: ", sorted(Counter(y_resampled).items()))
这里是输出:
Before: [(0, 9900), (1, 100)]
After: [(0, 9900), (1, 9900)]
你能猜出只关注两类决策边界上的数据点的问题吗?
由于这种技术如此侧重于边界上非常少数的点,少数类簇内的点根本就没有被采样:

图 2.10 – 利用危险点(多数类邻居多于少数类)的 Borderline-SMOTE 算法生成合成样本
在本节中,我们学习了边界 SMOTE,它通过关注接近多数和少数类别分类边界的样本来生成合成少数类别样本,这反过来又可能有助于提高模型的判别能力。
🚀 亚马逊生产中的过采样技术
在实际应用中,亚马逊使用机器学习优化产品的包装类型,旨在减少浪费同时确保产品安全[5]。在他们的训练数据集中,包含了数百万种产品和包装组合,亚马逊面临显著的类别不平衡,其中只有 1%的示例代表不适合的产品-包装配对(少数类别)。
为了解决这种不平衡,亚马逊使用了各种过采样技术:
-
边界 SMOTE 过采样,导致 PR-AUC 提高了 4%-7%,但训练时间增加了 25%-35%。
-
随机过采样和随机欠采样的混合,其中它们随机过采样少数类,并欠采样多数类。这导致了 PR-AUC 提高了 6%-10%,但训练时间增加了高达 25%。
表现最好的技术是两阶段学习与随机欠采样(在第第七章,数据级深度学习方法)中讨论),它将 PR-AUC 提高了 18%-24%,而没有增加训练时间。
他们提到,处理数据集不平衡的技术效果既与领域相关,也与数据集特定。这个现实世界的例子强调了过采样技术在解决类别不平衡问题上的有效性。
接下来,我们将学习另一种过采样技术,称为 ADASYN,它通过对边界附近和其他低密度区域的示例进行过采样,而不会完全忽略不在边界上的数据点。
ADASYN
虽然 SMOTE 不区分少数类别样本的密度分布,自适应合成采样(ADASYN)[6]则专注于难以分类的少数类别样本,因为它们位于低密度区域。ADASYN 根据分类观察的难度,使用少数类别的加权分布。这样,从更难样本中生成更多的合成数据:

图 2.11 – ADASYN 工作原理的说明
在这里,我们可以看到以下内容:
-
a) 绘制了多数类和少数类样本
-
b) 根据硬度因子(稍后解释)生成合成样本
虽然 SMOTE 使用少数类中的所有样本进行均匀过采样,但在 ADASYN 中,更难分类的观察结果被更频繁地使用。
这两种技术之间的另一个区别是,与 SMOTE 不同,ADASYN 在训练 KNN 时也使用多数类观测值。然后,它根据有多少多数观测值是其邻居来决定样本的硬度。
ADASYN 的工作原理
ADASYN 遵循一个简单的算法。以下是 ADASYN 的逐步工作原理:
-
首先,它在整个数据集上训练一个 KNN。
-
对于少数类的每个观测值,我们找到硬度因子。这个因子告诉我们分类该数据点有多困难。硬度因子,用 r 表示,是多数类邻居数与邻居总数的比率。在这里,r = M / K,其中 M 是多数类邻居的数量,K 是最近邻的总数。
-
对于每个少数类观测值,我们通过在少数类观测值及其邻居(邻居可以是多数类或少数类)之间画线来生成与硬度因子成比例的合成样本。数据点分类越困难,为其创建的合成样本就越多。
让我们看看如何使用来自imbalanced-learn库的 ADASYN API 进行数据的过采样:
from imblearn.over_sampling import ADASYN
X_resampled, y_resampled = ADASYN().fit_resample(X, y)
print(sorted(Counter(y_resampled).items()))
这里是输出:
[(0, 9900), (1, 9900)]

图 2.12 – ADASYN 优先考虑较难分类的样本,并在 KNN 中包含多数类示例以评估样本硬度

图 2.13 – 总结各种过采样技术的记忆辅助图
在本节中,我们学习了关于 ADASYN 的内容。接下来,让我们看看当我们的数据包含分类特征时,我们该如何处理这些情况。
分类特征和 SMOTE 变体(SMOTE-NC 和 SMOTEN)
如果你的数据包含分类特征呢?分类特征可以取有限或固定数量的可能值,这与计算机科学中的枚举(enums)类似。这些可能是无序的分类特征,例如头发颜色、种族等,或者是有序的分类特征,例如低、中、高:

图 2.14 – 带有示例的分类数据和其类型
-
对于有序特征,我们可以通过 sklearn 的
OrdinalEncoder对其进行编码,它将类别分配给值 0、1、2 等。 -
对于名义特征,我们之前学到的所有 SMOTE 变体都不会起作用。然而,
RandomOverSampler也可以处理名义特征:from imblearn.over_sampling import RandomOverSampler X_cat_mix = np.array([["abc", 1], ["def", 2],\ ["ghi", 3]], dtype=object) y_cat_mix = np.array([0, 0, 1]) print('X_cat_mix:', X_cat_mix, '\n y_cat_mix: ', y_cat_mix) X_resampled, y_resampled = RandomOverSampler().fit_resample(\ X_cat_mix, y_cat_mix) print('X_resampled:', X_resampled, '\n y_resampled: ',\ y_resampled)这里是输出:
X_cat_mix: [['abc' 1] ['def' 2] ['ghi' 3]] y_cat_mix: [0 0 1] X_resampled: [['abc' 1] ['def' 2] ['ghi' 3] ['ghi' 3]] y_resampled: [0 0 1 1]
然而,默认情况下,SMOTE 仅在连续数据上工作,不能直接用于分类数据。为什么? 这是因为 SMOTE 通过生成连接少数类两个不同数据点的线上的随机点(也称为插值)来工作。如果我们的数据是分类的,并且有“是”和“否”这样的值,我们首先需要将这些值转换为数字。即使我们这样做,比如“是”映射到 1,“否”映射到 0,SMOTE 的插值可能会产生一个 0.3 的新点,这并不映射到任何真实类别。
此外,由于RandomOverSampler中的shrinkage参数仅设计用于连续值,因此我们无法在分类数据中使用该参数。
然而,SMOTE 的两个变体可以处理分类特征:
-
使用
imbalanced-learn对数据进行过采样。数据集的第一项是分类的,第二项是连续的:from imblearn.over_sampling import SMOTENC X_cat_mix = np.array([["small", 1],\ ["medium", 2],\ ["large", 3],\ ["large", 4],\ ["large", 5]], dtype=object) y_cat_mix = np.array([0, 0, 1, 0, 1]) print('X_cat_mix:', X_cat_mix, '\n y_cat_mix: ', y_cat_mix) X_resampled, y_resampled = SMOTENC( categorical_features=[0], k_neighbors=1, random_state=1 ).fit_resample(X_cat_mix, y_cat_mix) print('X_resampled:', X_resampled, '\ny_resampled: ', \ y_resampled)这里是输出:
X_cat_mix: [['small' 1] ['medium' 2] ['large' 3] ['large' 4] ['large' 5]] y_cat_mix: [0 0 1 0 1] X_resampled: [['small' 1.0] ['medium' 2.0] ['large' 3.0] ['large' 4.0] ['large' 5.0] ['large' 3.005630378122263]] y_resampled: [0 0 1 0 1 1] -
用于名义数据的合成少数过采样技术(SMOTEN)用于名义分类数据。SMOTEN 对所有特征执行与 SMOTE-NC 类似的多数投票。它将所有特征视为名义分类,新样本的特征值通过选择最近邻中最频繁的类别来决定。用于计算最近邻的距离度量称为值距离度量(VDM)。VDM 通过考虑与每个值关联的类别标签分布来计算两个属性值之间的距离。基于这样的想法,如果两个属性值的类别标签分布相似,则这两个属性值更相似。这样,VDM 可以捕捉分类属性及其对应类别标签之间的潜在关系。
让我们看看使用 SMOTEN 的一些示例代码:
from imblearn.over_sampling import SMOTEN X_original = np.array([["abc"], \ ["def"], \ ["ghi"], \ ["ghi"], \ ["ghi"]], dtype=object) y_original = np.array([0, 0, 1, 1, 1]) print('X_original:', X_original, '\ny_original: ', y_original) X_resampled, y_resampled = \ SMOTEN(k_neighbors=1).fit_resample(X_original, y_original) print('X_resampled:', X_resampled, '\ny_resampled:', \ y_resampled)这里是输出:
X_original: [['abc'] ['def'] ['ghi'] ['ghi'] ['ghi']] y_original: [0 0 1 1 1] X_resampled: [['abc'] ['def'] ['ghi'] ['ghi'] ['ghi'] ['abc']] y_resampled: [0 0 1 1 1 0]
在表 2.1中,我们可以看到 SMOTE、SMOTEN 和 SMOTENC,以及每种技术的一些示例,以展示它们之间的差异:
| SMOTE 类型 | 支持的特性 | 示例数据 |
|---|---|---|
| SMOTE | 仅数值 | 特征:[2.3, 4.5, 1.2],标签:0 特征:[3.4, 2.2, 5.1],标签:1 |
| SMOTEN | 分类(名义或有序) | 特征:[‘green’, ‘square’],标签:0 特征:[‘red’, ‘circle’],标签:1 |
| SMOTENC | 数值或分类(名义或有序) | 特征:[2.3, ‘green’, ‘small’, ‘square’],标签:0 特征:[3.4, ‘red’, ‘large’, ‘circle’],标签:1 |
表 2.1 – SMOTE 及其一些常见变体与示例数据
总结来说,当我们有分类和连续数据类型的混合时,应使用 SMOTENC,而 SMOTEN 只能用于所有列都是分类的情况。你可能对各种过采样方法在模型性能方面的比较感到好奇。我们将在下一节探讨这个话题。
各种过采样方法的模型性能比较
让我们检查一些流行的模型在不同过采样技术下的表现。我们将使用两个数据集进行比较:一个是合成数据集,另一个是真实世界数据集。我们将使用逻辑回归和随机森林模型评估四种过采样技术以及无采样的性能。
你可以在本书的 GitHub 仓库中找到所有相关代码。在图 2.15和图 2.16中,我们可以看到两个数据集上两种模型的平均精确度得分值:

图 2.15 – 在合成数据集上各种过采样技术的性能比较

图 2.16 – 在 thyroid_sick 数据集上各种过采样技术的性能比较
根据这些图表,我们可以得出一些有用的结论:
-
过采样的有效性:总的来说,使用过采样技术似乎比不使用任何采样(NoSampling)提高了平均精确度得分。
-
算法敏感性:过采样技术的有效性取决于所使用的机器学习算法。例如,随机森林似乎比逻辑回归从过采样技术中受益更多,尤其是在合成数据上。
-
thyroid_sick数据集但在合成数据中显示了变化。 -
thyroid_sick数据 -
对于随机森林,Borderline-SMOTE 在合成数据上具有最高的平均精确度得分。
-
thyroid_sick数据.* 没有明显的胜者:没有一种过采样技术在所有条件下都优于其他技术。技术的选择可能取决于所使用的特定算法和数据集。
请注意,这里使用的模型没有使用最佳超参数进行调整。
调整随机森林和逻辑回归模型的超参数可以进一步提高模型性能。
通常情况下,没有一种技术总是比其他技术表现得更好。这里有几个变量在起作用,即“模型”和“数据”。大多数时候,唯一知道的方法是尝试这些技术中的一系列,并找到最适合我们模型和数据的那个。你可能会对如何从众多过采样选项中进行选择感到好奇。
使用各种过采样技术的指南
现在,让我们回顾一下如何导航我们讨论过的各种过采样技术以及这些技术如何彼此不同的一些指南:
-
不应用任何采样技术来训练模型。这将是我们具有基线性能的模型。我们应用的任何过采样技术都预期会提高这种性能。
-
从随机过采样开始,并添加一些收缩。我们可能需要调整一些收缩的值,看看模型性能是否有所改善。
-
当我们具有分类特征时,我们有几种选择:
-
首先将所有分类特征转换为数值特征,使用独热编码、标签编码、特征哈希或其他特征转换技术。
-
(仅适用于名义分类特征)直接在数据上使用 SMOTENC 和 SMOTEN。
-
-
应用各种过采样技术——随机过采样、SMOTE、Borderline-SMOTE 和 ADASYN——并在适用于您问题的指标上衡量模型的性能,例如平均精度得分、ROC-AUC、精确率、召回率、F1 分数等。
-
由于过采样改变了训练数据集的分布,而测试集或现实世界并非如此,使用过采样可能会产生潜在的偏差预测。使用过采样后,根据应用重新校准模型的概率得分可能至关重要。模型的重新校准纠正了由于改变类别分布而引入的任何偏差,确保在部署时做出更可靠的决策。同样,调整分类阈值对于准确解释模型至关重要,尤其是在不平衡数据集的情况下。有关重新校准和阈值调整的更多详细信息,请参阅第十章《模型校准》和第五章《成本敏感学习》。
避免过采样的时机
在第一章《机器学习中的数据不平衡介绍》中,我们讨论了数据不平衡可能不是问题的场景。在您选择过采样技术之前,应该重新审视这些考虑因素。尽管存在批评,但过采样的适用性应该根据具体情况评估。以下是一些在选择应用过采样技术时需要考虑的额外技术因素:
-
计算成本:过采样增加了数据集的大小,导致处理时间和硬件资源方面的计算需求更高。
-
数据质量:如果少数类数据有噪声或许多异常值,过采样可能会引入更多噪声,降低模型可靠性。
-
分类器限制:在系统约束场景中,例如极低延迟或处理遗留系统时,使用强大的分类器(复杂且更准确的模型)可能不可行。在这种情况下,我们可能只能使用弱分类器。弱分类器更简单、精度较低,但需要较少的计算资源,并且具有更低的运行时延迟。在这种情况下,过采样可能是有益的[7]。对于强大的分类器,过采样可能带来递减的回报,有时优化决策阈值可能是一个更简单、资源消耗更少的替代方案。
在决定是否使用过采样方法来解决不平衡数据集时,考虑以下因素。
表 2.2 总结了各种过采样技术的关键思想、优点和缺点。这可以帮助你更好地评估选择哪种过采样方法:
| SMOTE | Borderline-SMOTE | ADASYN | SMOTE-NC 和 SMOTEN | |
|---|---|---|---|---|
| 核心思想 | 在连接少数类样本最近邻的线上选择随机点。 | 在多数类和少数类之间的边界上选择少数样本。对这样的样本在边界上执行 SMOTE。 | 根据密度分布自动决定要生成的少数类样本数量。在密度分布低的地方生成更多点。 | 它对分类特征进行多数投票。 |
| 优点 | 通常减少假阴性。 | 创建的合成样本不是已知数据的简单复制。 | 它关注不同类别的密度分布。 | 它适用于分类数据。 |
| 缺点 | 可能会发生重叠的类别,并可能向数据中引入更多噪声。这可能不适合高维数据或多类别分类问题。 | 它不关心少数类样本的分布。 | 它关注类别之间重叠的区域。它可能过分关注异常值,导致模型性能不佳。 | 与 SMOTE 相同。 |
表 2.2 – 总结本章讨论的各种过采样技术
在本节中,我们讨论了如何应用本章中学习到的各种过采样技术以及使用它们的优缺点。接下来,我们将探讨如何将各种过采样方法扩展到多类别分类问题。
多类别分类中的过采样
在多类别分类问题中,我们有超过两个类别或标签需要预测,因此可能存在多个类别不平衡。这给问题增加了更多复杂性。然而,我们也可以将相同的技巧应用于多类别分类问题。imbalanced-learn 库提供了在几乎所有支持的方法中处理多类别分类的选项。我们可以通过使用 sampling_strategy 参数选择各种采样策略。对于多类别分类,我们可以在 SMOTE API 中的 sampling_strategy 参数传递一些固定的字符串值(称为内置策略)。我们还可以传递一个包含以下内容的字典:
-
以类别标签作为键
-
以该类别的样本数量作为值
在使用参数作为字符串时,以下是一些内置的 sampling_strategy 样本策略:
-
minority策略仅重新采样少数类。 -
not minority策略重新采样除了少数类以外的所有类别。在多类别不平衡的情况下,这可能是有帮助的,因为我们有超过两个类别,并且多个类别不平衡,但我们不想触及少数类。 -
not majority策略重新采样所有类别,除了多数类别。 -
all策略重新采样所有类别。 -
auto策略与not``majority策略相同。
以下代码展示了使用各种采样策略进行多类分类时 SMOTE 的使用方法。
首先,让我们创建一个包含 100 个样本的数据集,其中三个类别的权重分别为 0.1、0.4 和 0.5:
X, y = make_classification(n_classes=3, class_sep=2, \
weights=[0.1, 0.4, 0.5], n_clusters_per_class=1, \
n_samples=100, random_state=10)
print('Original dataset shape %s' % Counter(y))
这里是输出:
Original dataset shape Counter({2: 50, 1: 40, 0: 10})
如预期,我们的数据集包含三个类别,分别为类别 0、1 和 2,其比例为 10:40:50。
现在,让我们应用带有“少数”采样策略的 SMOTE。这将增加最少样本数的类别:
over_sampler = SMOTE(sampling_strategy='minority')
X_res, y_res = over_sampler.fit_resample(X, y)
print('Resampled dataset shape using minority strategy: %s'% \
Counter(y_res))
这里是输出:
Resampled dataset shape using minority strategy: Counter({0: 50, 2: 50, 1: 40})
由于类别 0 之前样本数最少,因此“少数”采样策略只对类别 0 进行了过采样,使得样本数等于多数类别的样本数。
在下面的代码中,我们使用字典进行过采样。在这里,对于sampling_strategy字典中的每个类别标签(0、1 或 2)作为key,我们都有每个目标类别的期望样本数作为value:
print('Original dataset shape %s' % Counter(y))
over_sampler = SMOTE(sampling_strategy={
0 : 40,
1 : 40,
2 : 50})
X_res, y_res = over_sampler.fit_resample(X, y)
print('Resampled dataset shape using dict strategy: %s\n'% \
Counter(y_res))
这里是输出:
Original dataset shape Counter({2: 50, 1: 40, 0: 10})
Resampled dataset shape using dict strategy:
Counter({2: 50, 0: 40, 1: 40})
小贴士
请注意,当在sampling_strategy中使用dict时,每个类别的期望样本数应大于或等于原始样本数。否则,fit_resampleAPI 将抛出异常。
在本节中,我们看到了如何将过采样策略扩展到处理具有两个以上类别的数据不平衡情况。大多数情况下,“auto”采样策略就足够好了,并且会平衡所有类别。
摘要
在本章中,我们介绍了处理不平衡数据集的各种过采样技术,并使用 Python 的imbalanced-learn库(也称为imblearn)进行了应用。我们还通过从头实现一些技术来了解这些技术的内部工作原理。虽然过采样可能会潜在地使模型对数据进行过拟合,但它通常比缺点多,具体取决于数据和模型。
我们将它们应用于一些合成和公开可用的数据集,并对其性能和有效性进行了基准测试。我们看到了不同的过采样技术如何可能导致模型性能在各个尺度上变化,因此尝试几种不同的过采样技术以决定最适合我们数据的方法变得至关重要。
如果你被发现与深度学习模型相关的过采样方法所吸引,我们邀请你查看第七章,数据级深度学习方法,我们将讨论深度学习领域内的数据级技术。
在下一章中,我们将介绍各种欠采样技术。
练习
-
探索
imbalanced-learn库中的 SMOTE 的两个变体,即 KMeans-SMOTE 和 SVM-SMOTE,本章未讨论。使用逻辑回归和随机森林模型比较它们与 vanilla SMOTE、Borderline-SMOTE 和 ADASYN 的性能。 -
对于一个有两个类别的分类问题,假设少数类与多数类的比例是 1:20。我们应该如何平衡这个数据集?我们应该在测试或评估时间应用平衡技术吗?请提供你的答案的理由。
-
假设我们正在尝试构建一个模型,用于估计一个人是否能获得银行贷款。在我们拥有的 5,000 个观测值中,只有 500 人的贷款获得了批准。为了平衡数据集,我们将批准的人的数据进行复制,然后将其分为训练集、测试集和验证集。使用这种方法是否存在任何问题?
-
数据归一化有助于处理数据不平衡。这是真的吗?为什么或为什么不?
-
在这里探索
imbalanced-learn库中可用的各种过采样 API:imbalanced-learn.org/stable/references/over_sampling.html。请注意每个 API 的各种参数。
参考文献
-
《Grab 的图像中保护个人数据》(2021 年),
engineering.grab.com/protecting-personal-data-in-grabs-imagery。 -
N. V. Chawla,K. W. Bowyer,L. O. Hall,和 W. P. Kegelmeyer,SMOTE:合成少数类过采样技术,jair,第 16 卷,第 321-357 页,2002 年 6 月,doi: 10.1613/jair.953。
-
《实时网站事件升级预测》(2023 年),
medium.com/data-science-at-microsoft/live-site-incident-escalation-forecast-566763a2178。 -
H. Han,W.-Y. Wang,和 B.-H. Mao,Borderline-SMOTE:不平衡数据集学习中的新过采样方法,在《智能计算进展》中,D.-S. Huang,X.-P. Zhang,和 G.-B. Huang,编,在《计算机科学讲座笔记》第 3644 卷。柏林,海德堡:Springer Berlin Heidelberg,2005 年,第 878-887 页。doi: 10.1007/11538059_91。
-
P. Meiyappan 和 M. Bales,立场文件:使用多模态深度学习减少亚马逊的包装浪费,(2021 年),文章:
www.amazon.science/latest-news/deep-learning-machine-learning-computer-vision-applications-reducing-amazon-package-waste,论文:www.amazon.science/publications/position-paper-reducing-amazons-packaging-wasteusing-multimodal-deep-learning。 -
Haibo He, Yang Bai, E. A. Garcia 和 Shutao Li,ADASYN:用于不平衡学习的自适应合成采样方法,载于 2008 年 IEEE 国际神经网络联合会议(IEEE 计算智能世界大会),中国香港:IEEE,2008 年 6 月,第 1322–1328 页。doi: 10.1109/IJCNN.2008.4633969。
-
Y. Elor 和 H. Averbuch-Elor, SMOTE 是否必要?,arXiv,2022 年 5 月 11 日。访问时间:2023 年 2 月 19 日。[在线]。可在
arxiv.org/abs/2201.08528获取。
第三章:欠采样方法
有时候,你拥有如此多的数据,通过过采样添加更多数据只会使事情变得更糟。别担心,我们也有针对这些情况的策略。这被称为欠采样或降采样。在本章中,你将了解欠采样的概念,包括何时使用它以及执行它的各种技术。你还将看到如何通过imbalanced-learn库的 API 使用这些技术,并将它们的性能与一些经典的机器学习模型进行比较。
本章将涵盖以下主题:
-
介绍欠采样
-
在多数类中何时避免欠采样
-
均匀地移除示例
-
移除噪声观察的策略
-
移除简单观察的策略
到本章结束时,你将掌握各种用于不平衡数据集的欠采样技术,并能够自信地使用imbalanced-learn库来构建更好的机器学习模型。
技术要求
本章将使用常见的库,如matplotlib、seaborn、pandas、numpy、scikit-learn和imbalanced-learn。本章的代码和笔记本可以在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/master/chapter03。要运行笔记本,有两种选择:你可以点击章节笔记本顶部的在 Colab 中打开图标,或者你可以直接从colab.research.google.com使用笔记本的 GitHub URL 启动它。
介绍欠采样
两个家庭,同样有尊严,
在公平的维罗纳,我们设景之处,
从古老的仇恨到新的叛乱,
在这里,市民的血使市民的手变得不洁。
——莎士比亚《罗密欧与朱丽叶》的开篇诗句
让我们看看一个受莎士比亚戏剧《罗密欧与朱丽叶》启发的场景。想象一个有两个敌对社区(即,蒙塔古家族和凯普莱特家族)的城镇。他们世世代代都是敌人。蒙塔古家族在城镇中属于少数派,而凯普莱特家族是多数派。蒙塔古家族非常富有和强大。凯普莱特家族并不那么富裕。这给城镇带来了复杂的情况。由于这种竞争,城镇里经常发生暴乱。有一天,蒙塔古家族赢得了国王的青睐,并密谋消灭一些凯普莱特家族成员以减少他们的数量。想法是,如果城镇中的凯普莱特家族成员减少,蒙塔古家族将不再是少数派。国王同意了这个计划,因为他希望在执行后实现和平。我们将在这个章节中使用这个故事来说明各种欠采样算法。
有时候,仅仅对少数类别进行过采样是不够的。过采样可能会导致过拟合和更长的训练时间。为了解决这些问题,并从不同的角度处理类别不平衡问题,人们想到了过采样的对立面——即欠采样。在文献中,这也常被称为降采样或负降采样,以表示负类别(即多数类别)正在被欠采样。
欠采样技术减少了多数类别中的样本数量。这种方法与过采样相比有两个明显的优势:
-
数据规模得到控制:即使数据不平衡不是问题,处理从太字节到拍字节不等的大量数据集通常需要数据降维以进行实际训练。数据量本身就可以使训练在时间和计算成本上变得不切实际。云服务提供商如亚马逊网络服务、微软 Azure 和谷歌云除了存储费用外,还会对计算单元收费,使得大规模训练变得昂贵。鉴于你很可能只使用可用训练数据的一小部分,因此关于保留哪些数据以及丢弃哪些数据要有战略性地考虑。欠采样不仅是一种平衡类别的手段,而且是一种有效的成本效益策略,可以将训练时间从几天减少到几小时。
-
过拟合的可能性更小:通过使用欠采样技术,可以减少多数类别的实例数量,使模型更多地关注少数类别的实例。这反过来又提高了模型在两个类别之间泛化的能力。因此,模型不太可能对多数类别过拟合,并且更好地准备处理新的、未见过的数据,从而降低过拟合的可能性。我们将在本章中讨论各种欠采样方法。
图 3.1以图形方式展示了欠采样的基本思想。

图 3.1 – 欠采样的基本思想展示:(a)具有两个类别的失衡数据,(b)欠采样后的数据
在图 3.1(a)中,我们展示了包含来自圆形类别的许多数据点的原始数据。在图 3.1(b)中,我们展示了从圆形类别中移除一些数据点后的重采样数据。
当何时避免对多数类别进行欠采样
欠采样并非万能良药,并不总是有效。它取决于所考虑的数据集和模型:
-
所有类别的训练数据都太少:如果数据集本身已经很小,对多数类别进行欠采样可能会导致信息损失很大。在这种情况下,建议尝试收集更多数据或探索其他技术,例如对少数类别进行过采样以平衡类别分布。
-
多数类同等重要或比少数类更重要:在特定场景下,例如在第一章“机器学习中的数据不平衡介绍”中提到的垃圾邮件过滤示例,保持识别多数类实例的高准确性至关重要。在这种情况下,对多数类进行下采样可能会降低模型准确分类多数类实例的能力,导致更高的误报率。相反,可以考虑其他方法,如成本敏感学习或调整决策阈值(这两种方法都在第五章“成本敏感学习”中讨论过)。
-
当下采样损害模型性能或导致模型过拟合时:对多数类进行下采样可能会降低整体模型性能,因为它丢弃了可能有价值的信息。一些下采样方法会丢弃决策边界附近的示例,这也会改变决策边界。此外,通过减少多数类的大小,下采样可能导致欠拟合,即模型变得过于简单,无法捕捉有限训练数据中的潜在趋势,在新未见过的数据上表现不佳。在使用下采样技术时,如果模型记住了减少后的数据集,也存在过拟合的风险。在这种情况下,探索其他技术,如集成方法(第四章“集成方法”中讨论过)、结合过采样和下采样的混合方法(在本章末尾讨论),或使用不太容易过拟合的不同算法可能更好。
🚀 Meta、Microsoft 和 Uber 在生产中的下采样技术
在广告点击预测等任务中,主要挑战是处理大规模且不平衡的数据集。例如,Facebook 单日广告可能包含数亿个实例,平均点击率(CTR)仅为 0.1%。为了解决这个问题,Meta 在论文《从预测 Facebook 广告点击中汲取的实用经验》[1]中详细介绍了两种专门的技巧。第一种是均匀下采样,它均匀地减少了训练数据量,并表明使用仅 10%的数据只会导致模型性能降低 1%。第二种是负样本下采样,它专门针对负样本(“无点击”)示例,并使用最优的下采样率为 0.025。
类似地,微软和优步在应对这些挑战方面有非常相似的方法。为了估计必应搜索中赞助广告的点击率[2],微软对非点击案例使用 50%的负样本下采样率,有效地将训练时间减半,同时保持相似的性能指标。优步外卖也采用负样本下采样来减少训练数据,以便训练预测是否向客户发送有关新餐厅推送通知的模型[3]。此外,他们在构建模型最终版本时移除了最不重要的特征。
让我们看看分类下采样方法的一种方式。
固定方法与清洗方法
根据从多数类中移除数据点的方式,下采样方法可以分为两类:固定方法和清洗方法。
在固定方法中,多数类的示例数量减少到固定的数量。通常,我们会将多数类的样本数量减少到少数类的规模。例如,如果多数类有 1 亿个样本,少数类有 1000 万个样本,应用固定方法后,你将只剩下 1000 万个两个类的样本。这类方法包括随机下采样和基于实例硬度的下采样。
在清洗方法中,基于某些预先确定的准则减少多数类的样本数量,与示例的绝对数量无关。一旦满足这个准则,算法就不关心多数类或少数类的规模。
表 3.1 以表格形式总结了两种方法之间的关键差异:
| 固定 下采样方法 | 清洗 下采样方法 | |
|---|---|---|
| 关键思想 | 选择特定数量的多数类实例进行移除 | 识别并移除噪声、冗余或误分类的多数类实例,旨在改善类之间的决策边界 |
| 实例之间的关系 | 不考虑实例之间的关系 | 评估实例之间的关系 |
| 性能和实现难度 | 实现更快且更容易 | 有时,可能比固定下采样方法有更好的模型性能和泛化能力 |
| 示例 | 随机下采样 | 基于实例硬度的下采样 |
表 3.1 – 固定方法与清洗方法下采样
让我们使用sklearn的make_classification API 创建一个不平衡的数据集。我们将在本章中应用各种下采样技术来平衡这个数据集:
from collections import Counter
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=10000, n_features=2,
n_redundant=0, n_classes=2, flip_y=0,
n_clusters_per_class=2, class_sep=0.79,
weights=[0.99], random_state=81)
图 3.2 展示了数据集在二维图上的样子。完整的笔记本代码请参考本章的 GitHub 仓库。

图 3.2 – 绘制不平衡率为 1:99 的数据集
模型校准和阈值调整
在应用欠采样技术后,你可能需要重新校准模型的概率分数。为什么?因为欠采样改变了原始类别的分布,模型的置信度估计存在偏差[4],可能不再准确反映现实场景中每个类别的真实可能性。未能重新校准可能导致模型部署时做出误导性或次优的决策。因此,重新校准模型的概率分数确保模型不仅能够正确分类实例,而且以与实际类别分布一致的方式估计概率,从而提高其可靠性。对于更深入理解此过程,特别是如何校准模型分数以考虑下采样的影响,请参阅第十章,模型校准。
在不平衡数据集的背景下,阈值调整技术可以是欠采样方法的临界补充。无论我们最终是否应用任何采样技术,调整阈值以确定正确的类别标签对于正确解释模型性能至关重要。对于更深入理解各种阈值调整技术,你可以参阅第五章,成本敏感学习。
欠采样方法
让我们看看第二种对欠采样算法进行分类的方法。国王可以通过几种方式消除一些凯普莱特家族成员:
-
他可以从整个城镇均匀地消除凯普莱特家族,从而从城镇的所有地区移除一些凯普莱特家族成员
-
或者,国王可以移除住在蒙太古家族房屋附近的凯普莱特家族
-
最后,他可以移除住在远离蒙太古家族房屋的凯普莱特家族成员
这些是在欠采样技术中使用的三种主要方法。我们要么均匀地移除多数样本,要么移除靠近少数样本的多数样本,要么移除远离少数样本的多数样本。我们也可以通过移除一些靠近的和一些远离的样本来结合后两种方法。以下图表给出了这些方法的分类:

图 3.3 – 欠采样技术分类
以下图表说明了两种标准之间的差异。在图 3.4(a)中,我们展示了原始数据集。在图 3.4(b)中,我们展示了移除靠近决策边界例子后的相同数据集。注意靠近类别边界的例子是如何被移除的。
大多数类别的例子远离少数类别可能无法有效地帮助模型建立决策边界。因此,这样的远离决策边界的多数类别例子可以被移除。在图 3.4(c)中,我们展示了移除远离边界例子后的数据集。远离决策边界的例子可以被认为是易于分类的例子。

图 3.4 – 两种下采样一般方法的差异
在讨论了各种分类下采样技术的方法之后,我们现在更详细地看看它们。
均匀移除示例
从数据中均匀移除多数类示例主要有两种方法。第一种是随机移除示例,另一种则涉及使用聚类技术。让我们详细讨论这两种方法。
随机下采样
国王可能首先想到的技术是随机挑选凯普莱特并从城镇中移除他们。这是一个简单的方法。它可能有效,国王可能能够带来城镇的和平。但是,国王可能会通过挑选一些有影响力的凯普莱特造成不可预见的损害。然而,这是一个很好的开始讨论的地方。这种技术可以被认为与随机过采样是近亲。在随机下采样(RUS)中,正如其名所示,我们随机从多数类中提取观察值,直到类别平衡。这种技术不可避免地导致数据损失,可能会损害数据的潜在结构,因此有时表现不佳。

图 3.5 – 解释 RUS 方法主要思想的漫画
下面的代码示例展示了如何使用 RUS:
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(sampling_strategy=1.0, random_state=42)
X_res, y_res = rus.fit_resample(X, y)
可以使用sampling_strategy值来指定少数类和多数类所需的比率,默认情况下,它们将被设置为数量相等。图 3.6显示了RandomUnderSampler技术的应用,其中右侧的图显示大多数负类样本被丢弃:

图 3.6 – 使用 RandomUnderSampler 在降采样前后绘制数据集
接下来,我们转向一种更智能的技术,它会在多数类示例之间形成小组。
ClusterCentroids
国王可能采用的第二种技术来实现均匀下采样,是将凯普莱特人口根据地理位置分成小组。然后,每组保留一个凯普莱特,并从该组中移除其他凯普莱特。这种下采样方法被称为ClusterCentroids方法。如果少数类中有N个项目,我们就从多数类的点中创建N个簇。例如,这可以使用 K-means 算法完成。K-means 是一种聚类算法,它将附近的点分组到不同的簇中,并为每个组分配质心。

图 3.7 – 描述 ClusterCentroids 方法主要思想的漫画
在 ClusterCentroids 技术中,我们首先将 K-means 算法应用于所有多数类数据。然后,对于每个簇,我们保留质心并移除该簇内的所有其他示例。值得注意的是,质心甚至可能不是原始数据的一部分,这是该方法的一个重要方面。
在图 3.8中,我们展示了 ClusterCentroids 的工作原理。在图 3.8(a)中,我们从一个不平衡的数据集开始。在图 3.8(b)中,我们计算了三个聚类的质心。这些质心在图中以星号表示。最后,我们在图 3.8(c)中从数据集中移除了所有大多数类别的样本,除了质心。

图 3.8 – 展示 ClusterCentroids 方法的工作原理
这里是使用 ClusterCentroids 的代码:
from imblearn.under_sampling import ClusterCentroids
cc = ClusterCentroids(random_state=42)
X_res, y_res = cc.fit_resample(X,y)
print('Resampled dataset shape %s' % Counter(y_res))
以下是输出:
Resampled dataset shape Counter({0: 100, 1: 100})
图 3.9展示了 ClusterCentroids 技术的应用,其中右侧的图表显示大多数负类样本被剔除。

图 3.9 – 使用 ClusterCentroids 在降采样前后绘制数据集
需要注意的一点是,ClusterCentroids 可能计算成本较高,因为它默认使用 K-means 算法,这可能会很慢。我们建议探索 ClusterCentroids 方法中的各种参数,例如 estimator,它指定了要使用的聚类方法。例如,K-means 可以被 MiniBatchKMeans 替换,这是 K-means 聚类算法的一个更快变体。
在下一节中,我们将尝试以更战略性的方式消除大多数类别的示例。
移除噪声观测值的策略
国王可能会在剔除任何人之前查看公民的友谊和位置。国王可能会决定剔除那些富有且住在蒙太古家族附近的凯普莱特家族。这可以通过分离争斗的家族来为城市带来和平。让我们看看一些使用我们的数据来实现这一点的策略。
ENN、RENN 和 AllKNN
国王可以根据邻居来剔除凯普莱特家族。例如,如果一个或多个凯普莱特家族最近三个邻居中的一个是蒙太古家族,国王就可以剔除凯普莱特家族。这种技术称为imbalanced-learn库为我们提供了选择我们想要重采样的类别以及样本邻居应该具有的类别排列方式。
我们可以遵循两种不同的标准来排除样本:
-
我们可以选择排除一个或多个邻居不属于自身相同类别的样本
-
我们可以决定排除大多数邻居不属于自身相同类别的样本
在图 3.10中,我们展示了 ENN 算法的工作原理。在这里,我们移除了具有一个或多个少数邻居的大多数样本。在图 3.10(a)中,我们展示了原始数据集。在图 3.10(b)中,我们突出显示了具有一个或多个少数类最近邻的大多数类样本。突出显示的大多数类样本以实心框表示,它们的邻居通过围绕它们创建曲线来表示。

图 3.10 – 展示 ENN 方法的工作原理
这里是使用 ENN 的代码:
from imblearn.under_sampling import EditedNearestNeighbours
enn = EditedNearestNeighbours(
sampling_strategy='auto', n_neighbors=200, kind_sel='all')
X_res, y_res = enn.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))
以下是输出:
Resampled dataset shape Counter({0: 7852, 1: 100})

图 3.11 – 使用 ENN 进行下采样前后的数据集绘图
在这里,n_neighbors是要考虑的邻域大小,用于计算最近邻。
有两种 ENN 变体我们不会深入探讨,但如果你感兴趣,可以探索它们:imblearn.under_sampling.RepeatedEditedNearestNeighbours)和imblearn.under_sampling.AllKNN)。在 RENN [6]中,我们重复在 ENN 中遵循的过程,直到没有更多可以删除的示例或达到最大循环计数。此算法也移除噪声数据。由于算法重复多次,它在移除边界示例方面更强(图 3**.12)。

图 3.12 – 使用 RENN 进行下采样前后的数据集绘图
在AllKNN方法[6]中,我们重复ENN,但邻居的数量从 1 增加到 K。
Tomek 链接
1976 年,伊万·托梅克提出了Tomek 链接的概念[7]。如果两个例子属于两个不同的类别,并且没有第三个点与它们的距离比两个点之间的距离短,则称这两个例子形成 Tomek 链接。Tomek 链接背后的直觉是“如果两个点来自不同的类别,它们不应该彼此最近。”这些点是噪声的一部分,我们可以消除多数成员或两个点以减少噪声。这就像国王决定移除那些最好的朋友是蒙太古家族的凯普莱特家族成员。
我们可以这样使用TomekLinks API:
from imblearn.under_sampling import TomekLinks
tklinks = TomekLinks(sampling_strategy='auto')
X_res, y_res = tklinks.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))
下面的输出是:
Resampled dataset shape Counter({0: 9875, 1: 100})

图 3.13 – 使用 TomekLinks 进行下采样前后的数据集绘图
图 3**.14展示了 Tomek 链接算法的工作原理。在图 3**.14(a)中,我们有原始数据集。在图 3**.14(b)中,我们找到并突出显示 Tomek 链接。注意这些链接中的点彼此很近。在图 3**.14(c)中,我们展示了移除属于 Tomek 链接的多数类样本(以圆圈表示)后的数据集。注意在部分(b)中出现的两个圆圈但在部分(c)中缺失。同样,我们在图例的部分(d)中展示了移除 Tomek 链接中所有点后的数据集。

图 3.14 – 展示 TomekLinks 算法的工作原理
由于其要求计算所有示例之间的成对距离,Tomek 链接是一种资源密集型方法。正如在《几种平衡机器学习训练数据行为的研究》[8]中所述,在处理大量数据时,在减少的数据集上执行此过程将更具有计算效率。
在下一个方法中,我们将尝试从少数类示例的角度尝试移除多数类示例。我们能否移除属于多数类的少数类示例的最近邻?
社区清洁规则
除了移除那些一个或多个最近邻是蒙太古家族的凯普莱特家族成员之外,国王还可能决定查看蒙太古家族成员的最近邻,并移除那些可能成为蒙太古家族成员最近邻的凯普莱特家族成员。在邻域清洗规则(NCR)[9]中,我们应用一个 ENN 算法,在剩余数据上训练一个 KNN,然后移除所有作为少数样本最近邻的大多数类样本。
下面是使用NeighourhoodCleaningRule的代码:
from imblearn.under_sampling import NeighbourhoodCleaningRule
ncr = NeighbourhoodCleaningRule(
sampling_strategy='auto', n_neighbors=200, threshold_cleaning=0.5)
X_res, y_res = ncr.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))
下面是输出结果:
Resampled dataset shape Counter({0: 6710, 1: 100})

图 3.15 – 使用 NCR 在欠采样前后绘制数据集
实例硬度阈值
国王可能会问一位大臣,“哪些凯普莱特家族成员与蒙太古家族相处得很好?”大臣根据他们对城镇的了解,会给出一个那些凯普莱特家族成员的名单。然后,国王会移除名单上的凯普莱特家族成员。这种使用另一个模型来识别噪声样本的方法被称为实例硬度阈值。在此方法中,我们在数据上训练一个分类模型,例如决策树、随机森林或线性 SVM。
除了预测实例的类别之外,这些分类器还可以返回它们的类别概率。类别概率显示了模型在分类实例时的置信度。使用实例硬度阈值方法[10],我们移除了那些获得低概率估计的大多数类样本(称为“难以分类的实例”)。这些实例由于类别重叠而被认为是“难以分类的”,而类别重叠是实例硬度的主要原因。
imbalanced-learn库提供了一个用于利用InstanceHardnessThreshold的 API,其中我们可以指定用于估计示例硬度的估计器。在这种情况下,我们使用LogisticRegression作为估计器:
from imblearn.under_sampling import InstanceHardnessThreshold
nm = InstanceHardnessThreshold(
sampling_strategy='auto', estimator=LogisticRegression())
X_res, y_res = nm.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))
下面是输出结果:
Resampled dataset shape Counter({0: 100, 1: 100})

图 3.16 – 使用 InstanceHardnessThreshold 在欠采样前后绘制数据集
由于分类模型需要在多数类和少数类之间绘制决策边界,因此那些离少数类样本太远的大多数类样本可能无法帮助模型决定这个决策边界。考虑到这一点,我们将在下一节中探讨移除此类简单多数类样本的方法。
移除简单观察的策略
移除富有的、著名的凯普莱特家族成员的策略的相反做法是移除贫穷的、弱小的凯普莱特家族成员。本节将讨论移除远离少数样本的多数样本的技术。我们不是从两个类别的边界移除样本,而是使用它们来训练模型。这样,我们可以训练一个模型以更好地区分类别。然而,一个缺点是这些算法可能会保留噪声数据点,这些数据点随后可能被用来训练模型,从而可能将噪声引入预测系统。
压缩最近邻
压缩最近邻(CNNeighbors)[11] 是一个按照以下方式工作的算法:
-
我们将所有少数样本添加到一个集合中,并添加一个随机选择的多数样本。让我们称这个集合为
C。 -
我们在集合
C上使用 k = 1 训练 KNN 模型。 -
现在,我们为每个剩余的多数样本重复以下四个步骤:
-
我们考虑一个多数样本;让我们称它为
e。 -
我们尝试使用 KNN 预测
e的类别。 -
如果预测的类别与原始类别匹配,我们则移除该样本。直觉上,从
e中学习的东西很少,因为即使是 1-NN 分类器也能学会它。 -
否则,我们将样本添加到我们的集合
C中,并在C上再次训练 1-NN。
-
此方法从多数类中移除了易于分类的样本。
使用 CondensedNearestNeighbour 的代码如下:
from imblearn.under_sampling import CondensedNearestNeighbour
cnn = CondensedNearestNeighbour(random_state=42)
X_res, y_res = cnn.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))
下面是输出:
Resampled dataset shape Counter({0: 198, 1: 100})

图 3.17 – 使用 CNNeighbors 在欠采样前后绘制数据集
然而,CNNeighbors 方法可能计算成本较高,因为它使用 KNN 算法评估每个多数类示例。这使得 CNNeighbors 方法不适合大数据应用。
单侧选择
国王可能会决定移除一些富有的许多贫穷的凯普莱特。这样,只有中产阶级的凯普莱特会留在城镇里。在单侧选择 [12] 中,我们就是这样做的。这种方法是 CNNeighbors 和 Tomek 链接的组合。我们首先使用 CNNeighbors 进行重采样。然后,我们从重采样数据中移除 Tomek 链接。这减少了噪声和易于识别的样本。
下面是 OneSidedSelection 的代码。当我们不提供 n_neighbors 参数时,默认值 1 被采用:
from imblearn.under_sampling import OneSidedSelection
oss = OneSidedSelection(random_state=0, n_seeds_S=10)
X_res, y_res = oss.fit_resample(X, y)
print('Resampled dataset shape %s' % Counter(y_res))
下面是输出:
Resampled dataset shape Counter({0: 4276, 1: 100})

图 3.18 – 使用 OneSidedSelection 在欠采样前后绘制数据集
在这里,n_seeds_S 是在方法中使用作为种子的少数类样本数量,它可以显著影响方法的表现。建议调整此参数。
结合欠采样和过采样
你可能会想知道我们是否可以将欠采样技术与过采样技术结合起来以产生更好的结果。答案是肯定的。过采样方法增加了少数类样本的数量,但通常也会增加数据中的噪声。一些欠采样技术可以帮助我们去除噪声,例如,ENN、Tomek 链接、NCR 和实例硬度。我们可以将这些方法与 SMOTE 结合起来以产生良好的结果。SMOTE 与 ENN [13] 和 Tomek 链接 [14] 的组合已经得到了充分的研究。此外,imbalanced-learn 库支持这两个方法:SMOTEENN 和 SMOTETomek。
模型性能比较
让我们探讨一些流行的模型在使用我们讨论的各种采样不足技术时的表现。我们为此比较使用了两个数据集:一个是合成数据集,另一个是从imbalanced-learn库中获取的名为thyroid_sick的现实世界数据集。我们将使用逻辑回归和随机森林模型评估 11 种不同的采样不足技术相对于无采样基线的性能。图 3.19到3.22显示了使用这些各种方法训练的模型的平均精度值。
你可以在本章的 GitHub 仓库中找到该笔记本。

图 3.19 – 使用随机森林在 thyroid_sick 数据集上使用各种方法时的平均精度

图 3.20 – 使用随机森林在合成数据上使用各种方法时的平均精度

图 3.21 – 使用逻辑回归在 thyroid_sick 数据集上使用各种方法时的平均精度

图 3.22 – 使用逻辑回归在合成数据上使用各种方法时的平均精度
这里有一些更多的观察:
-
采样不足技术的有效性可能会因数据集及其特征而显著不同
-
没有任何一种技术在所有数据集上都占主导地位,这强调了进行实证测试以选择最适合你特定问题的最佳方法的需求。
那么,哪种方法最适合你的数据?这个问题没有简单的答案。关键在于对这些方法的内部工作原理有一个直觉,并拥有一个可以帮助你测试不同技术的流程。
然而,某些技术可能会很耗时。在我们的测试中,在一个包含一百万个示例的数据集上,CondensedNearestNeighbor、ClusterCentroids和ALLKNN等方法比其他方法花费的时间更长。如果你处理大量数据,计划将来进行扩展,或者时间紧迫,你可能想要避免这些方法或调整它们的参数。RandomUnderSampler和InstanceHardnessThreshold等技术更适合快速迭代开发。
这就带我们结束了这一章。
摘要
在本章中,我们讨论了采样不足,这是一种通过减少多数类样本数量来解决数据集类别不平衡的方法。我们回顾了采样不足的优点,例如控制数据大小和减少过拟合的机会。采样不足方法可以分为固定方法,即将多数类样本数量减少到固定大小,和清洗方法,即根据预定的标准减少多数类样本。
我们讨论了各种欠采样技术,包括随机欠采样、基于实例硬度的欠采样、ClusterCentroids、ENN、Tomek links、NCR、实例硬度、CNNeighbors、单侧选择,以及欠采样和过采样技术的组合,如SMOTEENN和SMOTETomek。
我们通过在几个数据集上对逻辑回归和随机森林模型进行各种欠采样技术的性能比较,以及基准测试它们的性能和有效性,得出了结论。
一旦你确定你的数据集是不平衡的,并且可能从应用欠采样技术中受益,就可以尝试本章中讨论的各种方法。使用适当的指标,如 PR-AUC,来评估它们的有效性,以找到最适合提高模型性能的方法。
在下一章中,我们将讨论各种基于集成的方法。
练习
-
探索
imbalanced-learn库提供的各种欠采样 API,请参阅imbalanced-learn.org/stable/references/under_sampling.html。 -
探索通过
imblearn.under_sampling.NearMissAPI 提供的NearMiss欠采样技术。它属于哪一类方法?将NearMiss方法应用于本章中使用的那个数据集。 -
在 UCI 的
us_crime数据集上尝试本章中讨论的所有欠采样方法。你可以在imbalanced-learn库的fetch_datasetsAPI 中找到这个数据集。为LogisticRegression和XGBoost模型找到具有最高f1-score指标的欠采样方法。 -
你能识别出你自己的欠采样方法吗?(提示:考虑以新的方式结合各种欠采样方法。)
参考文献
-
X. He 等人,"从 Facebook 广告点击预测中获得的实际经验",载于第八届国际在线广告数据挖掘研讨会论文集,纽约纽约,美国:ACM,2014 年 8 月,第 1–9 页。doi: 10.1145/2648584.2648589。
-
X. Ling, W. Deng, C. Gu, H. Zhou, C. Li, 和 F. Sun,"在 Bing 搜索广告中的点击预测模型集成",载于第 26 届国际万维网大会(WWW '17)配套会议论文集,澳大利亚珀斯:ACM 出版社,2017 年,第 689–698 页。doi: 10.1145/3041021.3054192。
-
如何使用机器学习和线性规划优化 Uber 推送通知的时间:
www.uber.com/blog/how-uber-optimizes-push-notifications-using-ml/. -
A. D. Pozzolo, O. Caelen, R. A. Johnson, 和 G. Bontempi,"使用欠采样对不平衡分类进行概率校准",载于 2015 年 IEEE 计算智能系列研讨会,南非开普敦:IEEE,2015 年 12 月,第 159–166 页。doi: 10.1109/SSCI.2015.33。
-
(介绍 ENN 方法) D. L. Wilson, “使用编辑数据的最近邻规则渐近性质,” IEEE Trans. Syst., Man, Cybern., vol. SMC-2, no. 3, pp. 408–421, Jul. 1972, doi: 10.1109/TSMC.1972.4309137.
-
(介绍 RENN 和 AllKNN 方法) “编辑最近邻规则的实验,” IEEE Trans. Syst., Man, Cybern., vol. SMC-6, no. 6, pp. 448–452, Jun. 1976, doi: 10.1109/TSMC.1976.4309523.
-
I. Tomek, “CNN 的两种改进,” IEEE Trans. Syst., Man, Cybern., vol. SMC-6, no. 11, pp. 769–772, Nov. 1976, doi: 10.1109/TSMC.1976.4309452.
-
G. E. A. P. A. Batista, R. C. Prati, 和 M. C. Monard, “研究平衡机器学习训练数据几种方法的性能,” SIGKDD Explor. Newsl., vol. 6, no. 1, pp. 20–29, Jun. 2004, doi: 10.1145/1007730.1007735.
-
(介绍邻域清洗规则方法) J. Laurikkala, “通过平衡类分布改进困难小类的识别,” 在 Artificial Intelligence in Medicine, S. Quaglini, P. Barahona, 和 S. Andreassen, Eds.,in Lecture Notes in Computer Science, vol. 2101. Berlin, Heidelberg: Springer Berlin Heidelberg, 2001, pp. 63–66. doi: 10.1007/3-540-48229-6_9.
-
(介绍实例硬度阈值技术) M. R. Smith, T. Martinez, 和 C. Giraud-Carrier, “数据复杂性的实例级分析,” Mach Learn, vol. 95, no. 2, pp. 225–256, May 2014, doi: 10.1007/s10994-013-5422-z.
-
P. Hart, “压缩最近邻规则 (corresp.),” IEEE transactions on information theory, vol. 14, no. 3, pp. 515–516, 1968, https://citeseerx.ist.psu.edu/document? repid=rep1&type=pdf&doi=7c3771fd6829630cf450af853 df728ecd8da4ab2.
-
(介绍单侧选择方法) M. Kubat 和 S. Matwin, “解决不平衡训练集的诅咒:单侧选择”。
-
(应用 SMOTEENN 和 SMOTETomek 方法) Gustavo EAPA Batista, Ronaldo C Prati, 和 Maria Carolina Monard. 研究平衡机器学习训练数据几种方法的性能。ACM SIGKDD explorations newsletter, 6(1):20–29, 2004.
-
(应用 SMOTETomek 方法) Gustavo EAPA Batista, Ana LC Bazzan, 和 Maria Carolina Monard. 平衡训练数据以实现关键词自动标注:一个案例研究。在 WOB, 10–18. 2003.
第四章:集成方法
想象一下一家大型公司的顶级高管。他们不会独自做出决策。在一天中,他们需要做出许多关键决策。他们是如何做出这些选择的?不是独自一人,而是通过咨询他们的顾问。
假设一位高管咨询了来自不同部门的五位不同顾问,每位顾问根据他们的专业知识、技能和领域知识提出略微不同的解决方案。为了做出最有效的决策,高管结合了五位顾问的见解和意见,创建了一个混合解决方案,其中包含了每个提案的最佳部分。这个场景说明了集成方法的概念,其中多个弱分类器被组合起来创建一个更强、更准确的分类器。通过结合不同的方法,集成方法通常可以实现比依赖单个分类器更好的性能。
我们可以通过结合多个弱分类器的结果来创建一个强大的模型。这些弱分类器,如简化的决策树、神经网络或支持向量机,其表现略好于随机猜测。相比之下,通过集成这些弱分类器创建的强大模型,其表现显著优于随机猜测。弱分类器可以接受不同来源的信息。构建模型集成的两种通用方法:bagging 和 boosting。
传统集成方法的问题在于它们使用假设数据平衡的分类器。因此,它们可能不适合处理不平衡的数据集。因此,我们将流行的机器学习集成方法与我们之前章节中研究的不平衡数据处理技术相结合。我们将在本章讨论这些组合。
本章将涵盖以下主题:
-
处理不平衡数据的 Bagging 技术
-
处理不平衡数据的 Boosting 技术
-
集成集成
-
模型性能比较
在图 4.1 中,我们将本章将要涵盖的各种集成技术进行了分类:

图 4.1 – 集成技术概述
到本章结束时,你将了解如何将诸如 bagging 和 boosting 之类的集成模型应用于数据集中类不平衡的问题。
技术要求
本章的 Python 笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/master/chapter04。像往常一样,你可以通过点击本章笔记本顶部的在 Colab 中打开图标或通过使用笔记本的 GitHub URL 在colab.research.google.com启动它来打开 GitHub 笔记本。
在本章中,我们将继续使用使用make_classification API 生成的合成数据集,就像我们在前几章中所做的那样。在本章的末尾,我们将对我们在本章中学到的方法在几个真实数据集上进行测试。我们的完整数据集包含 90,000 个示例,不平衡比率为 1:99。以下是训练数据集的外观:

图 4.2 – 不平衡比率为 1:99 的数据集的绘图
我们的失衡数据集已经准备好使用,让我们看看第一种集成方法,称为 Bagging。
处理不平衡数据的 Bagging 技术
想象一个商业高管,他拥有数千份关于重要并购的机密文件。分配给这个案例的分析师没有足够的时间来审查所有文件。每个人都可以从集合中随机选择一些文件并开始审查。稍后,他们可以在会议上结合他们的见解来得出结论。
这种场景是机器学习中一个称为 Bagging 的过程的隐喻[1],它是自助聚合的缩写。在 Bagging 中,就像前一个场景中的分析师一样,我们创建原始数据集的几个子集,在每个子集上训练一个弱学习器,然后聚合它们的预测。
为什么使用弱学习器而不是强学习器?这个理由适用于 Bagging 和 Boosting 方法(本章后面将讨论)。有几个原因:
-
速度:弱学习器在计算上效率高且成本低。
-
多样性:弱学习器更有可能犯不同类型的错误,这在结合它们的预测时是有利的。使用强学习器可能导致它们都犯同一种类型的错误,从而导致集成效果较差。
-
过拟合:作为前一点的推论,错误的多样性有助于降低集成中过拟合的风险。
-
可解释性:虽然整个集成可能不容易解释,但其个别组件——通常是更简单的模型——更容易理解和解释。
现在,回到 Bagging。算法的第一步称为自助抽样。在这个步骤中,我们通过从主数据中随机选择项目来制作几个子集或较小的数据组。数据的选择有可能会选择同一个项目多次(这个过程称为“有放回的随机抽样”),所以这些较小的组可能有一些共同的项目。然后,我们在这些较小的组上训练我们的分类器。
第二步称为聚合。在预测时,将测试样本传递给每个分类器。之后,我们取平均或多数预测作为真实答案。
如图 4.3 所示,数据集首先通过有放回抽样分成三个子集。然后,在每个子集上分别训练单独的分类器。最后,在预测时将分类器的结果合并:

图 4.3 – 展示 bagging 的工作原理
图 4**.4 以伪代码格式总结了 bagging 算法:

图 4.4 – Bagging 的伪代码
我们将在之前创建的数据集上训练一个来自 sklearn 的 bagging 分类器模型。由于可以向 BaggingClassifier 提供一个基估计器,我们将使用最大树深为 6 的 DecisionTreeClassifier:
from sklearn.ensemble import BaggingClassifier
from sklearn.metrics import PrecisionRecallDisplay
clf = BaggingClassifier(\
estimator=DecisionTreeClassifier(max_depth=6), random_state=0
).fit(X_train, y_train)
让我们绘制决策边界:
plot_decision_boundary(X_train, y_train, clf, 'BaggingClassifier')
plt.show()
您可以参考 GitHub 上相应笔记本中 plot_decision_boundary() 的定义。我们使用 sklearn.inspection 模块中的 DecisionBoundaryDisplay API 来绘制决策边界。
图 4**.5 展示了在训练数据上的学习决策边界:

图 4.5 – BaggingClassifier 在训练数据上的决策边界
让我们也注意使用此模型在测试集上的基线指标平均精度:
PrecisionRecallDisplay.from_estimator(
clf, X_test, y_test, ax = plt.gca(),name = "BaggingClassifier")
图 4**.6 展示了得到的 PR 曲线:

图 4.6 – BaggingClassifier 在测试数据上的精确度-召回率曲线
这里有一些其他指标:
Average Precision Score: 0.969
AUC-ROC Score: 0.999
F2-score: 0.891
Precision: 0.967
Recall: 0.874
在本章中,我们还将考虑 F2 分数(Fbeta 分数,beta=2.0),它按比例结合精确度和召回率,给予召回率更高的权重,而给予精确度较低的权重。
因此,当在不平衡数据集上使用 BaggingClassifier 时,我们可能会遇到什么问题?一个明显的事情可能是,在引导过程中,一些用于训练基分类器的子集可能只有很少的少数类示例,甚至没有。这意味着每个基分类器在少数类上的表现都会很差,而它们的组合表现仍然会很差。
我们可以将下采样技术与 bagging(例如 UnderBagging)或过采样技术与 bagging(例如 OverBagging)结合起来,以获得更好的结果。我们将在下一节讨论这些技术。
UnderBagging
UnderBagging [2] 技术在引导(或子集选择)时使用随机下采样。我们为每个分类器选择少数类示例的整个集合,并用与少数类示例数量相同的多数类示例进行带替换的引导。聚合步骤与 bagging 相同。我们可以选择任何分类器,例如决策树,进行训练。
UnderBagging 有变体,其中可以对少数类进行带替换的重采样,以获得更多样化的集成。
图 4**.7 中的流程图代表了 UnderBagging 算法在三个数据子集中的主要步骤。它包括创建多个数据子集,对每个子集中的多数类进行随机下采样,在每个子集上训练分类器,最后结合分类器的预测:

图 4.7 – 展示 UnderBagging 算法的工作原理
imbalanced-learn 库为 BalancedBaggingClassifier 提供了实现。默认情况下,此分类器使用决策树作为基分类器,并通过 sampler 参数使用 RandomUnderSampler 作为采样器。图 4.8 展示了训练好的 UnderBagging 模型的决策边界:

图 4.8 – 在训练数据上 UnderBagging 分类器的决策边界
🚀 微软生产环境中的 Bagging 分类器
在微软的一个实际应用中 [3],团队在预测 Live Site Incident 升级(如第二章,过采样方法)时面临重大挑战。数据集高度不平衡,使得标准分类器难以表现良好。为了解决这个问题,微软采用了集成方法,特别是来自 imbalanced-learn 库的 BalancedBaggingClassifier。他们使用了 UnderBagging,其中每个引导样本都是随机欠采样的,以获得平衡的类别分布。正如我们刚才讨论的,UnderBagging 使用所有少数类样本和多数类样本的随机选择来训练模型。
在他们的评估中,Bagged 分类提供了最佳结果,并且在经过几个月的跟踪后也证明更加一致。他们能够显著提高对事件升级的预测准确性。
OverBagging
在引导过程中,不是对多数类样本进行随机欠采样,而是对少数类进行过采样(带替换)。这种方法称为 OverBagging [2]。作为一种变体,多数类和少数类的样本都可以进行带替换的重采样,以达到多数类和少数类样本数量相等。
图 4.9 中的流程图展示了 OverBagging 算法在三个数据子集上的主要步骤。它包括创建多个数据子集,对每个子集中的少数类进行随机过采样,在每个子集上训练分类器,并最终结合分类器的预测:

图 4.9 – 展示 OverBagging 算法的工作原理
对于 OverBagging,我们可以在 sampler 参数中使用与 BalancedBaggingClassifier 相同的 RandomOverSampler。
我们将看到以下决策边界:

图 4.10 – 在训练数据上 OverBagging 分类器的决策边界
在讨论了各种 Bagging 技术之后,我们将比较这些技术的性能指标。
SMOTEBagging
我们能否在引导过程中使用 SMOTE 代替对少数类样本的随机过采样?答案是肯定的。多数类将进行带替换的引导,而少数类将使用 SMOTE 进行采样,直到达到平衡比率。
SMOTEBagging [2] 的伪代码与 OverBagging 非常相似,关键区别在于使用 SMOTE 算法而不是随机过采样来增强少数类数据。
与过袋装类似,我们可以使用带有 SMOTE 作为 sampler 参数的 BalancedBagging Classifier API 实现 SMOTEBagging。
决策边界与过袋装没有很大区别:

图 4.11 – SMOTEBagging 分类器在训练数据上的决策边界
关于随机森林及其与袋装的关系的说明
随机森林 [4] 是另一个基于袋装概念的模型。sklearn 中的 RandomForestClassifier 和 BaggingClassifier 模型之间的区别在于,RandomForestClassifier 在尝试决定决策树中节点分裂的特征时,考虑了特征的一个随机子集,而 BaggingClassifier 则采用所有特征。
表 4.1 突出了随机森林和袋装分类器之间的差异:
| RandomForestClassifier | BaggingClassifier | |
|---|---|---|
| 基本分类器 | 决策树 | 任何分类器。 |
| 自举采样 | 是 | 是。 |
| 选取特征子集 | 是(在每个节点) | 否,默认情况下。我们可以使用 max_features 超参数来选取特征子集。 |
| 最适合的数据类型 | 任何表格数据,但在大型特征集上表现最佳 | 任何表格数据,但最好在仔细选择基本分类器时使用。 |
| 处理缺失值和异常值 | 是,固有 | 取决于基本分类器。 |
表 4.1 – RandomForestClassifier 与 BaggingClassifier 对比
imbalanced-learn 库提供了 BalancedRandomForestClassifier 类来处理不平衡数据集,其中在训练单个决策树之前,每个自举样本都是欠采样的。作为练习,我们鼓励你了解 BalancedRandomForestClassifier。看看它如何与我们刚才讨论的其他技术相关。还可以尝试各种采样策略,并探索此类提供的参数。
袋装方法的比较性能
让我们使用迄今为止使用的相同数据集比较各种袋装方法的性能。我们将使用决策树作为基线,并在多个性能指标上评估不同的技术。所有技术中每个指标的最高值都以粗体突出显示:
| 技术 | F2 | 精确度 | 召回率 | 平均精确度 | AUC-ROC |
|---|---|---|---|---|---|
| SMOTEBagging | 0.928 | 0.754 | 0.985 | 0.977 | 1.000 |
| 过袋装 | 0.888 | 0.612 | 1.000 | 0.976 | 1.000 |
| 下袋装 | 0.875 | 0.609 | 0.981 | 0.885 | 0.999 |
| 袋装 | 0.891 | 0.967 | 0.874 | 0.969 | 1.000 |
| 平衡随机森林 | 0.756 | 0.387 | 0.993 | 0.909 | 0.999 |
| 随机森林 | 0.889 | 0.975 | 0.870 | 0.979 | 1.000 |
| 决策树 | 0.893 | 0.960 | 0.878 | 0.930 | 0.981 |
表 4.2 – 各种袋装技术的性能比较
以下是我们可以从表 4.2中得出的结论:
-
为了最大化 F2 分数,SMOTEBagging表现最佳
-
对于高精确度,袋装法和随机森林表现异常出色
-
对于高召回率,OverBagging和平衡随机森林是强有力的选择
-
对于所有指标的综合性能,SMOTEBagging和袋装法被证明是稳固的选项
总之,尽管如袋装法和随机森林这样的集成方法建立了难以超越的稳健基准,但结合如 SMOTEBagging 这样的不平衡学习策略可以带来显著的收益。
这就结束了我们对袋装技术的讨论。如果袋装法是群众的智慧,那么提升法就是大师级的雕塑家,通过每一笔都精炼着先前的艺术。我们将在下一节尝试理解提升法是如何工作的。
处理不平衡数据的提升技术
想象两个朋友一起做小组学习来解决他们的数学作业。第一个学生在大多数主题上都很强,但在两个主题上较弱:复数和三角形。因此,第一个学生要求第二个学生在这两个主题上花更多的时间。然后,在解决作业的过程中,他们结合了他们的答案。由于第一个学生大多数主题都很熟悉,他们决定在作业问题的答案中给予他的答案更多的权重。这两个学生所做的是提升法的核心思想。
在袋装法中,我们注意到我们可以并行训练所有分类器。这些分类器是在数据的一个子集上训练的,并且在预测时它们都有平等的发言权。
在提升法中,分类器是一个接一个地训练的。虽然每个分类器都从整个数据中学习,但根据数据集中点的分类难度,这些点被分配了不同的权重。分类器也被分配了权重,这些权重告诉我们它们的预测能力。在预测新数据时,使用分类器的加权总和。
提升法首先在全部数据集上训练第一个分类器,每个数据点分配相同的权重。在第二次迭代中,第一次迭代中被误分类的数据点被赋予更多的权重,并使用这些新权重训练第二个分类器。同时,也会根据分类器的整体性能给分类器本身分配权重。这个过程通过多个迭代和不同的分类器继续进行。图 4.12展示了对于双分类数据集的此概念:

图 4.12 – 提升法思想:(左)第一个分类器的决策边界;(中)第二分类器对误分类数据点的权重提升;(右)第二个分类器的决策边界
我们刚才描述的这种提升方法称为AdaBoost。还有一种提升算法的类别称为梯度提升,其主要重点是最小化先前模型的残差(实际值与预测输出值之间的差异),试图纠正先前模型的错误。有几个流行的梯度提升实现,如XGBoost、LightGBM和CatBoost。
在本章中,我们将主要关注 AdaBoost 并将其修改为考虑数据不平衡。然而,将 AdaBoost 与 XGBoost 等替换不应太困难。
AdaBoost
AdaBoost,即自适应提升,是基于决策树的最早提升方法之一。决策树是易于组合在一起的分类器:

图 4.13 – AdaBoost 伪代码
以下代码显示了如何从sklearn库中导入分类器并在数据上训练它:
from sklearn.ensemble import AdaBoostClassifier
clf = AdaBoostClassifier(
random_state=0, estimator = DecisionTreeClassifier(max_depth=6)
).fit(X_train, y_train)
让我们绘制模型在数据上训练后的决策边界:
plot_decision_boundary(X_train, y_train, clf, 'AdaBoostClassifier')
plt.show()
图 4**.14显示了模型在训练数据上的决策边界:

图 4.14 – AdaBoostClassifier 在训练数据上的决策边界
我们可以将过采样和欠采样作为提升算法的组成部分,类似于我们对 bagging 算法所做的那样。我们将在下一节讨论这一点。
RUSBoost、SMOTEBoost 和 RAMOBoost
如您所猜测,我们可以将 AdaBoost 与重采样技术相结合。以下是主要思路:在每次提升迭代中,在训练分类器之前,对前一次迭代中的错误示例进行数据采样(通过某些欠采样或过采样变体)。以下是通用伪代码:
-
输入:训练数据和一些决策树分类器。
-
输出:一个聚合分类器。
-
初始化训练数据所有样本的相等权重。
-
对每个决策树分类器重复此操作:
-
使用数据采样方法重新采样数据:
-
如果使用的采样方法是随机欠采样(RUS),则该方法称为RUSBoost [5]。
-
如果使用的采样方法是 SMOTE,则该方法称为SMOTEBoost [6]。
-
在RAMOBoost(即Boosting 中的排序少数过采样 [7])中,少数类的过采样是基于少数类示例的权重进行的。如果一个示例的权重更高(因为模型在前一次迭代中对该示例的表现不佳),则对其进行更多的过采样,反之亦然。
-
-
在重采样数据上训练分类器,根据先前迭代中样本的权重给予更高的重视。
-
通过比较其预测与实际输出,计算给定数据上分类器的错误。
-
考虑下一次迭代中所有被错误分类的示例。增加这些错误分类示例的权重。
-
-
将所有决策树分类器组合成一个最终分类器,其中在训练数据上具有较小错误值的分类器在最终预测中具有更大的发言权。
在这个伪代码中,步骤 4(I)是我们相对于 AdaBoost 算法所添加的唯一额外步骤。让我们讨论这些技术的优缺点:
-
在 RUSBoost 中,随着数据的减少,我们倾向于有更快的训练时间。
-
SMOTEBoost 从少数类生成合成样本。因此,它增加了数据的多样性,并可能提高分类器的准确性。然而,它会增加训练时间,并且可能无法扩展到非常大的数据集。
-
RAMOBoost 优先考虑靠近类别边界的样本。这在某些情况下可以提高性能。然而,类似于 SMOTEBoost,这种方法可能会增加训练时间和成本,并可能导致最终模型的过拟合。
imbalanced-learn库提供了RUSBoostClassifier的实现:
from imblearn.ensemble import RUSBoostClassifier
rusboost_clf = RUSBoostClassifier(random_state=0, \
estimator=DecisionTreeClassifier\
(max_depth=6)).fit(X_train, y_train)
让我们检查训练模型的决策边界:
plot_decision_boundary(
X_train, y_train, rusboost_clf, 'RUSBoostClassifier')
plt.show()
结果图如下所示:

图 4.15 – RUSBoostClassifier 在训练数据上的决策边界
imbalanced-learn库尚未实现 RAMOBoost 和 SMOTEBoost(截至版本 0.11.0)。您可以在github.com/dialnd/imbalanced-algorithms的开放源代码存储库中查看参考实现。
我们能否为多数类创建多个子集,从每个子集中训练一个集成,并将这些集成中的所有弱分类器组合成一个最终输出?这种方法将在下一节中探讨,我们将利用集成集成技术。
集成集成
我们能否结合提升和 bagging?如我们之前所看到的,在 bagging 中,我们创建多个数据子集,然后在那些数据集上训练分类器。我们可以将 AdaBoost 视为在 bagging 过程中使用的分类器。过程很简单:首先,我们创建袋子,然后在每个袋子上训练不同的 AdaBoost 分类器。在这里,AdaBoost 本身就是一种集成。因此,这些模型被称为集成 的集成。
除了拥有集成集成之外,我们还可以在 bagging 时进行下采样(或过采样)。这使我们能够在单个模型中获得下采样(或过采样)的袋装、提升和随机下采样(或过采样)的好处。在本节中,我们将讨论一个名为EasyEnsemble的算法,它具有与具有相同数量弱分类器的任何其他算法相似的训练时间。由于随机下采样没有显著的开销,这两个算法的训练时间与其他算法相似。
EasyEnsemble
EasyEnsemble 算法[8]从原始数据集中生成平衡数据集,并在每个平衡数据集上训练不同的 AdaBoost 分类器。随后,它创建一个聚合分类器,该分类器基于 AdaBoost 分类器的多数投票进行预测:

图 4.16 – EasyEnsemble 伪代码
图 4**.17总结了使用训练数据三个子集的 EasyEnsemble 算法:

图 4.17 – EasyEnsemble 算法解释
我们不仅可以随机下采样多数类示例,还可以随机上采样少数类示例。
imbalanced-learn库提供了使用EasyEnsembleClassifier的 API。EasyEnsembleClassifier API 提供了一个base_estimator参数,可以用来设置任何分类器,默认为AdaBoostClassifier:
from imblearn.ensemble import EasyEnsembleClassifier
clf = EasyEnsembleClassifier(n_estimators=70,random_state=42).fit(X,y)
让我们绘制决策边界:
plot_decision_boundary(X, y, clf, 'EasyEnsembleClassifier')
plt.show()

图 4.18 – EasyEnsembleClassifier 在训练数据上的决策边界
默认情况下,EasyEnsemble 使用AdaBoostClassifier作为基线估计器。然而,我们也可以使用任何其他估计器,例如XGBoostClassifier,或者以其他方式调整它,例如通过传递另一个sampling_strategy。
这就结束了我们对 EasyEnsemble 的讨论。接下来,我们将比较我们研究过的各种提升方法。
提升方法的比较性能
让我们比较我们讨论过的各种提升方法的性能。我们以决策树作为基线,并使用 RUSBoost、AdaBoost、XGBoost 和 EasyEnsemble,以及两种变体。默认情况下,EasyEnsembleClassifier使用AdaBoostClassifier作为基线估计器。我们在EasyEnsembleClassifier的第二种变体中使用 XGBoost 作为估计器;在第三种变体中,我们使用not majority作为我们的sampling_strategy,并配合 XGBoost 估计器:
| 技术 | F2 分数 | 精确度 | 召回率 | 平均精确度 | AUC-ROC |
|---|---|---|---|---|---|
EasyEnsemble(估计器=XGBoost 和sampling_strategy = not_majority) |
0.885 | 0.933 | 0.874 | 0.978 | 1.000 |
| EasyEnsemble(估计器=XGBoost) | 0.844 | 0.520 | 1.000 | 0.978 | 0.999 |
| EasyEnsemble | 0.844 | 0.519 | 1.000 | 0.940 | 0.999 |
| RUSBoost | 0.836 | 0.517 | 0.989 | 0.948 | 1.000 |
| AdaBoost | 0.907 | 0.938 | 0.900 | 0.978 | 1.000 |
| XGBoost | 0.885 | 0.933 | 0.874 | 0.968 | 1.000 |
| 决策树 | 0.893 | 0.960 | 0.878 | 0.930 | 0.981 |
表 4.3 – 各种提升技术的性能比较
下面是从表 4.3中得出的结论:
-
对于最高的 F2 分数,AdaBoost 是最好的选择。
-
对于高精确度,纯决策树击败了所有其他技术。
-
对于完美的召回率,EasyEnsemble(估计器为
XGBoost)和 EasyEnsemble 表现完美。 -
对于整体平衡性能,AdaBoost 和 EasyEnsemble(估计器为
XGBoost和sampling_strategy=not_majority)是强有力的竞争者。 -
集成技术,如 RUSBoost 和 EasyEnsemble,专门设计用于处理数据不平衡并提高召回率,与基线模型(如决策树或甚至 AdaBoost)相比。
总体而言,结果表明,尽管像 AdaBoost 和 XGBoost 这样的集成方法提供了难以超越的稳健基准,但利用不平衡学习技术确实可以修改结果分类器的决策边界,这有可能有助于提高召回率。然而,这些技术的有效性在很大程度上取决于所考虑的数据集和性能指标。
通过结束我们通过集成集成之旅,我们为我们的机器学习工具箱添加了另一个强大且动态的工具。
模型性能比较
我们所讨论的技术有效性高度依赖于它们应用到的数据集。在本节中,我们将进行全面的比较分析,比较我们迄今为止所讨论的各种技术,同时以逻辑回归模型作为基准。有关完整实现的全面审查,请参阅 GitHub 上提供的配套笔记本。
分析涵盖了四个不同的数据集,每个数据集都有其独特的特性和挑战:
-
Sep: 0.5 的合成数据:一个类别之间具有适度分离的模拟数据集,作为在简化条件下理解算法性能的基准。
-
Sep: 0.9 的合成数据:另一个合成数据集,但具有更高的分离度,使我们能够检查随着类别区分性的提高,算法的表现如何。
-
与医疗保健相关的
imblearn),因其实际重要性以及医疗数据集中常见的自然类别不平衡而被选中。 -
imblearn也是如此。
我们的主要评估指标是平均精确度,这是一个结合了精确度和召回率的综合度量,从而提供了一个平衡的算法性能视图。
我们想强调,我们正在使用各种集成模型的原始版本进行比较。通过一些额外的努力来调整这些模型的超参数,我们当然可以增强这些实现的性能。我们将这留给你作为练习。
通过在多种数据集上比较这些不同的算法,这项分析旨在以下方面提供一些有价值的见解:
-
传统技术与专门技术的对比
-
算法有效性对数据集特征的依赖性
-
在不同场景下选择一种算法而非另一种算法的实际影响
图 4.19比较了使用平均精确度得分,以逻辑回归模型为基准,在两个合成数据集上各种 bagging 和 boosting 技术的性能:

图 4.19 – 合成数据集上的平均精确度得分
图 4.20显示了两个真实世界数据集上的相似图:

图 4.20 – thyroid_sick 和 abalone_19 数据集上的平均精确度得分
让我们分析每个数据集的结果:
-
Sep 0.5 的合成数据 (图 4.19,左侧):在平均精确度方面,XGBoost 和逻辑回归表现最佳,分别得分为 0.30 和 0.27。有趣的是,专门为不平衡数据设计的集成方法,如 SMOTEBagging 和 OverBagging,表现与常规方法如 Bagging 相当,甚至更差。这表明在更简单的合成设置中,专门的方法并不总是能保证优势。
-
Sep 0.9 的合成数据 (图 4.19,右侧):EasyEnsemble 在这个数据集上以平均精确度 0.64 领先,紧随其后的是逻辑回归和 XGBoost。这种更高的分离似乎使 EasyEnsemble 能够利用其对平衡的关注,从而获得更好的性能。其他集成方法如 UnderBagging 和 OverBagging 表现尚可,但未超越领先者。
-
甲状腺疾病数据集 (图 4.20,左侧):在一个关注甲状腺疾病的真实世界数据集中,XGBoost 的表现远超其他所有方法,平均精确度为 0.97。其他集成方法如 Bagging、OverBagging 和 SMOTEBagging 也得分很高,表明集成方法特别适用于这个数据集。有趣的是,Boosting 和 RUSBoost 没有跟上步伐,这表明并非所有 Boosting 变体都普遍有效。
-
Abalone 19 数据集 (图 4.20,右侧):对于 Abalone 19 数据集,所有方法的表现相对较差,XGBoost 以平均精确度 0.13 脱颖而出。EasyEnsemble 以 0.09 的得分位居第二,而传统方法如逻辑回归和 Bagging 则落后。这可能表明该数据集对大多数方法来说特别具有挑战性,而专门的不平衡技术只能带来微小的改进。
这里有一些总体见解:
-
常规方法如 XGBoost 和逻辑回归通常提供强大的基线,难以超越。
-
专门的不平衡学习技术的有效性可能因数据集及其固有的复杂性而显著不同。
-
集成方法通常在各种数据集上表现良好,但它们的有效性可能取决于具体情境。
-
性能指标的选择——在本例中为平均精确度——可以显著影响评估,因此考虑多个指标以获得全面理解至关重要。
我们希望这一章已经展示了您如何将采样技术与集成方法相结合以实现更好的结果,尤其是在处理不平衡数据时。
摘要
机器学习中的集成方法通过结合多个弱分类器的结果来创建强大的分类器,例如使用袋装和提升方法。然而,这些方法假设数据平衡,可能难以处理不平衡数据集。将集成方法与过采样和欠采样等采样方法相结合,导致 UnderBagging、OverBagging 和 SMOTEBagging 等技术,所有这些都可以帮助解决不平衡数据问题。
EasyEnsemble 等集成集成,结合提升和袋装技术,为不平衡数据集创建强大的分类器。
基于集成的不平衡学习技术可以成为您工具箱中的优秀补充。基于 KNN 的,如 SMOTEBoost 和 RAMOBoost 可能较慢。然而,基于随机欠采样和随机过采样的集成成本较低。此外,发现提升方法在处理不平衡数据时有时比袋装方法更有效。我们可以将随机采样技术与提升方法相结合,以获得更好的整体性能。正如我们之前强调的,这是经验性的,我们必须尝试了解什么最适合我们的数据。
在下一章中,我们将学习如何调整模型以考虑数据的不平衡以及模型由于对少数类样本的错误分类而引起的各种成本。
问题
-
尝试在
abalone_19数据集上使用RUSBoostClassifier,并将性能与其他章节中的技术进行比较。 -
imbalanced-learn库中的BalancedRandomForestClassifier和BalancedBaggingClassifier类之间的区别是什么?
参考文献
-
L. Breiman, Bagging predictors, Mach Learn, vol. 24, no. 2, pp. 123–140, Aug. 1996, doi: 10.1007/BF00058655,
link.springer.com/content/pdf/10.1007/BF00058655.pdf. -
(介绍了 OverBagging、UnderBagging 和 SMOTEBagging 的论文)S. Wang 和 X. Yao, Diversity analysis on imbalanced data sets by using ensemble models, in 2009 IEEE Symposium on Computational Intelligence and Data Mining, Nashville, TN, USA: IEEE, Mar. 2009, pp. 324–331. doi: 10.1109/CIDM.2009.4938667,
www.cs.bham.ac.uk/~wangsu/documents/papers/CIDMShuo.pdf. -
实时网站事件升级预测 (2023),
medium.com/data-science-at-microsoft/live-site-incident-escalation-forecast-566763a2178 -
L. Breiman, Random Forests, Machine Learning, vol. 45, no. 1, pp. 5–32, 2001, doi: 10.1023/A:1010933404324,
link.springer.com/content/pdf/10.1023/A:1010933404324.pdf. -
(介绍了 RUSBoost 算法的论文)C. Seiffert, T. M. Khoshgoftaar, J. Van Hulse, 和 A. Napolitano, RUSBoost: 一种缓解类别不平衡的混合方法,IEEE Trans. Syst., Man, Cybern. A, 第 40 卷,第 1 期,第 185–197 页,2010 年 1 月,doi: 10.1109/TSMCA.2009.2029559,
www.researchgate.net/profile/Jason-Van-Hulse/publication/224608502_RUSBoost_A_Hybrid_Approach_to_Alleviating_Class_Imbalance/links/0912f50f4bec299a8c000000/RUSBoost-A-Hybrid-Approach-to-Alleviating-Class-Imbalance.pdf. -
(介绍了 SMOTEBoost 算法的论文)N. V. Chawla, A. Lazarevic, L. O. Hall, 和 K. W. Bowyer, SMOTEBoost: 在提升中改进少数类的预测,在数据库中的知识发现:PKDD 2003,N. Lavrač, D. Gamberger, L. Todorovski, 和 H. Blockeel 编著,计算机科学讲座笔记,第 2838 卷。柏林,海德堡:Springer Berlin Heidelberg,2003 年,第 107–119 页。doi: 10.1007/978-3-540-39804-2_12,
www3.nd.edu/~dial/publications/chawla2003smoteboost.pdf. -
(介绍了 RAMOBoost 算法的论文)Sheng Chen, Haibo He, 和 E. A. Garcia, RAMOBoost: 在提升中的排序少数类过采样,IEEE Trans. Neural Netw.,第 21 卷,第 10 期,第 1624–1642 页,2010 年 10 月,doi: 10.1109/TNN.2010.2066988,
ieeexplore.ieee.org/abstract/document/5559472. -
(介绍了 EasyEnsemble 的论文)Xu-Ying Liu, Jianxin Wu, 和 Zhi-Hua Zhou, 用于类别不平衡学习的探索性欠采样,IEEE Trans. Syst., Man, Cybern. B,第 39 卷,第 2 期,第 539–550 页,2009 年 4 月,doi: 10.1109/TSMCB.2008.2007853,
129.211.169.156/publication/tsmcb09.pdf.
第五章:成本敏感学习
到目前为止,我们已经学习了各种采样技术以及如何对数据进行过采样或欠采样。然而,这两种技术都有其独特的问题。例如,过采样可能会因为重复看到精确或非常相似的例子而导致模型过拟合。同样,在欠采样中,我们失去了一些信息(这些信息可能对模型有用),因为我们丢弃了大多数类别的例子来平衡训练数据集。在本章中,我们将考虑之前所学数据级技术的替代方案。
成本敏感学习是一种有效的方法来处理不平衡数据。我们将介绍这项技术,并了解为什么它可能是有用的。这将帮助我们理解成本函数的一些细节以及机器学习模型默认不是设计来处理不平衡数据集的。虽然机器学习模型没有配备处理不平衡数据集的能力,但我们将看到现代库是如何实现这一点的。
我们将涵盖以下主题:
-
成本敏感 学习(CSL)的概念
-
实践中的成本理解
-
逻辑回归的成本敏感学习
-
决策树的成本敏感学习
-
使用
scikit-learn和 XGBoost 模型进行成本敏感学习 -
MetaCost – 使任何分类模型具有成本敏感性
-
阈值调整
到本章结束时,您将了解在分类问题中成本的含义,如何调整模型参数以考虑这些成本,以及如何优先考虑少数类别的预测以减轻误分类的成本。我们还将探讨一个通用的元算法,该算法可以使任何算法具有成本敏感性,以及一种后处理技术,用于调整预测阈值。
技术要求
与之前的章节类似,我们将继续使用常见的库,如numpy、scikit-learn、xgboost和imbalanced-learn。本章的代码和笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/main/chapter05。您可以通过点击本章笔记本顶部的在 Colab 中打开图标或通过使用笔记本的 GitHub URL 在colab.research.google.com启动它来打开这个 GitHub 笔记本。
成本敏感学习(CSL)的概念
成本敏感学习(CSL)是一种技术,其中机器学习模型的成本函数被改变以考虑数据的不平衡。CSL 背后的关键洞察是我们希望我们的模型成本函数反映不同类别的相对重要性。
让我们尝试理解机器学习中的成本函数和各种类型的成本敏感学习(CSL)。
成本和成本函数
成本函数估计了实际结果与模型预测结果之间的差异。例如,逻辑回归模型的成本函数由对数损失函数给出:
LogLoss = − 1 * N * ∑ i=1 N (y_i * log(ˆy_i) + (1 − y_i) * log(1 − ˆy_i))
在这里,N 是观察的总数,y_i 是真实标签(0 或 1),ˆy_i 是从模型预测出的概率值(介于 0 和 1 之间)。
一种类型的成本被称为误分类错误成本 [1] - 即,预测多数类而不是少数类或反之的成本。
实际上,我们可能会遇到其他类型的成本,例如以下这些:
-
标记数据集的成本
-
训练或评估模型的成本
-
训练数据收集的成本
让我们考虑混淆矩阵:
| 预测为负 | 预测为正 | |
|---|---|---|
| 实际为负 | 真阴性 | 假阳性 |
| 实际为正 | 假阴性 | 真阳性 |
表 5.1 – 理解分类错误成本的混淆矩阵
心理学研究表明,损失带来的痛苦是收益的两倍。同样,在机器学习中,“成本”捕捉了模型出错(假阳性与假阴性)的时刻,而不关心它正确的时候(真阳性与真阴性)。这种成本是误分类错误的成本。
并非所有误分类都是相同的。例如,假设我们正在尝试预测患者是否患有癌症。如果我们的模型错误地指示患者患有癌症(假阳性),这可能导致额外的测试。然而,如果我们的模型错误地建议患者无病(假阴性),后果可能更为严重,因为疾病可能会未诊断而进展。因此,假阴性比假阳性更具破坏性。我们的成本函数应该考虑这种差异。
不幸的是,大多数模型默认将多数类和少数类同等对待。然而,现代机器学习框架如 scikit-learn、Keras/TensorFlow 和 PyTorch 提供了一种方法,可以在各种学习算法中不同地权衡各种类。
成本敏感学习类型
成本敏感学习(CSL)有两种主要方法,即加权法和元学习。在加权方法中,我们更新机器学习模型的成本函数,以反映不同类的重要性。在元学习中,我们可以使模型对成本敏感,而无需更改其成本函数。
在元学习技术 MetaCost 中,例如,我们可以改变训练实例的标签,以最小化预期的误分类成本。同样,在阈值调整方法中,我们确定一个概率阈值,以最小化预测的总误分类成本。图 5.1 从高层次上对这些方法进行了分类 [2][3]:

图 5.1 – 成本敏感学习方法的分类
CSL 与重采样的区别
与之前讨论的数据级别技术相比,CSL 的关键区别在于数据级别技术调整不同错误类型的频率,但它们对待所有误分类错误都是一样的。在某些情况下,正如我们之前遇到的,不同类别的观测值被误分类的成本并不相同。例如,在癌症检测中,将患有癌症的患者误分类为健康(假阴性)的成本要高得多,因为如果未检测或未早期治疗,患者风险很高。同样,将欺诈预订误分类为非欺诈可能会比将合法交易误分类为欺诈的成本更高。为什么?因为在后一种情况下,我们只需联系用户并验证交易的合法性即可。通过应用重采样技术,如上采样或下采样,我们隐式地改变了不同类型错误的成本。因此,CSL 和重采样技术最终可以对模型产生等效的影响。
然而,在某些情况下,重采样技术可能存在问题,我们将在下一节讨论。在这种情况下,CSL 可能更实用:

图 5.2 – 漫画再次强调误分类错误的概念
重平衡技术的缺陷
在前面的章节中,我们简要地提到了为什么在某些情况下,我们可能更喜欢不应用任何数据采样技术。这可能是因为以下原因:
-
我们已经拥有太多的训练数据,处理更多的数据可能相当昂贵,或者由于有更多的训练数据,训练时间可能增加数倍。
-
有时,由于我们使用的数据集,我们可能无法通过采样或数据重平衡技术获得最佳结果。
-
另一个需要考虑的因素是,在重新平衡数据集后,我们的模型的预测分数可能变得不准确,需要重新校准。我们将在第十章“模型校准”中介绍这一主题,我们将学习各种模型校准技术。
-
重平衡技术可能导致模型过拟合或欠拟合问题。特别是当使用过采样时,它们会产生重复或相似的训练示例,这可能导致过拟合。同样,当使用欠采样时,模型可能欠拟合,因为模型没有在欠采样过程中丢弃的数据上进行训练。
接下来,让我们尝试理解成本究竟意味着什么。
实践中理解成本
在为不同类别创建权重时,我们需要了解涉及的各种成本类型。这些成本根据具体情况而变化。让我们讨论一个成本计算的例子,以了解在考虑成本计算时应考虑什么。
让我们以儿童肺炎为例。根据联合国儿童基金会,每 43 秒就有一个孩子死于肺炎[4]。想象一下,我们正在为儿童肺炎开发一个新的测试——我们将如何决定不同错误的成本?
让我们回顾一下表 5.1 中的混淆矩阵。通常,对于真正的负例和真正的正例,不会有额外的成本。但是,使用错误的负例——也就是说,当一个孩子患有肺炎,却预测该孩子健康时——将会有非常高的成本。另一方面,当一个健康的儿童被预测为患有肺炎时,将会有与孩子家庭可能遇到的麻烦相关的成本,但这个成本比前一种情况要低得多。此外,误分类的成本可能因孩子的年龄而异。例如,年幼的孩子比年长的孩子风险更高。因此,如果模型在年幼孩子的案例中出错,我们将对模型进行更多的惩罚。
成本可能因症状的持续时间而异。可以这样考虑:如果我们犯了一个错误,错误地诊断了一个只有一天流感症状的孩子,这并不理想,但也不是灾难性的。然而,如果那个孩子已经忍受了 2 周的流感症状,那将是一个不同的场景。这个错误将给我们带来更大的成本。
虽然我们迄今为止讨论了现实世界的问题,但本章将转向使用合成数据集。这种方法旨在在受控环境中强化概念和方法,从而增强学习过程:
X, y = make_classification(
n_samples=50000, n_features=2, n_redundant=0, class_sep=2,\
weights=[0.99], random_state=1, n_clusters_per_class=1)
X_train, X_test, y_train, y_test = train_test_split(X, y,\
test_size = 0.2, random_state = 0, stratify=y)
print('y_train: ', Counter(y_train))
print('y_test: ', Counter(y_test))
plot_dataset(X_train, y_train)
make_classification函数产生了一些重叠的点,我们清理了这些点。为了简化,我们在这里省略了清理代码。您可以在 GitHub 上的完整笔记本中查阅。
之前的代码产生了以下输出和散点图(图 5**.3):
y_train: Counter({0: 39404, 1: 596})
y_test: Counter({0: 9851, 1: 149})

图 5.3 – 显示训练数据集分布的散点图
我们将深入探讨如何将 CSL 应用于逻辑回归模型。
逻辑回归的成本敏感学习
逻辑回归是一种简单的分类算法。我们通过将特征进行线性组合来训练模型。然后,我们将线性组合的结果传递给 sigmoid 函数,以预测不同类别的类别概率。
sigmoid函数(也称为logit函数)是一种可以将任何实数转换为 0 到 1 之间值的数学工具。这个值可以解释为概率估计:
import numpy as np
def sigmoid(x):
s = 1/(1+np.exp(-x))
return s
Sigmoid 函数的图像呈 S 形曲线,看起来是这样的:

图 5.4 – Sigmoid 函数
具有最高预测概率的类别被用作给定样本的预测。
假设我们有一个要分类为垃圾邮件或非垃圾邮件的电子邮件,并且我们的逻辑回归模型输出非垃圾邮件的概率为 0.25,垃圾邮件的概率为 0.75。在这里,具有最高预测概率的类别是“垃圾邮件”(1),因为 0.75 大于 0.25。因此,模型会预测这封邮件是垃圾邮件(图 5**.5):

图 5.5 – 高类别概率决定二分类的类别
对于二分类,我们只预测一个类别的概率。另一个类别的概率是第一个类别的概率减去 1。
对数逻辑回归模型使用损失函数进行训练。来自具有两个类别的数据集的一个示例的损失函数看起来像这样:
cost = − y * log(classProbability) − (1 − y)* log(1 − classProbability)
对于真正的阳性和真正的阴性,这个损失将非常低。对于误报,y,实际值将是 0;因此,第一个项将是 0,但第二个项将非常高,因为类别概率接近 1,这个项将接近负无穷大(因为,log(0) → − ∞)。由于前面有一个负号,成本将接近正无穷大。对误检情况可以进行类似的分析。成本的一部分可以看作是误报部分,另一部分可以看作是误检部分:
cost = falsePositiveCost + falseNegativeCost
如前所述,我们不希望两种类型的成本同等重要。所以我们只是为各自的成本添加权重,W FP 和 W FN:
cost = W FP * falsePositiveCost + W FN * falseNegativeCost
这是对数逻辑回归中 CSL 的核心。为了得到模型的总体成本,我们取所有数据点的平均成本:
lr = LogisticRegression(random_state=0, max_iter=150).fit(
X_train, y_train)
plot_decision_boundary(X_train, y_train, lr, 'LogisticRegression')
plt.show()
PrecisionRecallDisplay.from_estimator(
lr, X_test, y_test, ax = plt.gca(),name = "LogisticRegression")
当所有错误成本相等时,模型的决策边界和模型的精度-召回率(PR)曲线将看起来像这样:

图 5.6 – 基线回归模型的决策边界(左)和 PR 曲线(右)
compute_scores(lr, X_test, y_test)
之前的代码输出了以下 F2 分数、精度和召回值:
f2-score: 0.921 precision: 0.926 recall: 0.919
在本章中,我们将使用 F2 分数作为我们的主要指标。F2 分数是什么?在第一章,机器学习中数据不平衡的介绍,我们学习了 F-beta 分数。F2 分数是 beta=2 的 F-beta 分数,而 F1 分数是 beta=1 的 F-beta 分数。当召回率比精度更重要时,它很有用——也就是说,误报比误检(重要)的成本更高:
F β = (1 + β 2) × (precision × recall) ____________________ (β 2 × precision) + recall = (5 × precision × recall) ________________ (4 × precision) + recall
LogisticRegression来自scikit-learn库提供了一个class_weight参数。当此参数的值设置为“balanced”时,每个类别的权重将自动通过以下公式计算:
weightOfClass = totalNumberOfSamples ________________________________ numberOfClasses * numberOfSamplesPerClass
例如,在数据集中我们有 100 个示例 – 80 个属于类别 0,20 个属于类别 1。每个类别的权重计算如下:
-
类别 0 的权重 = 100/(2*80) = 0.625
-
类别 1 的权重 = 100/(2*20) = 2.5
由于类别 0 的示例数量是类别 1 的四倍,类别 1 的权重是 2.5,是类别 0 权重的四倍,即 0.625。这很有道理,因为我们希望给类别 1 更多的权重,因为它的数量较少。
我们也可以将class_weight作为一个字典来提及:
LogisticRegression(class_weight={0: 0.5, 1:0.5})
让我们尝试在LogisticRegression函数中使用class_weight参数:
lr_weighted = LogisticRegression(class_weight='balanced', \
random_state=0, max_iter=150).fit(X_train, y_train)
plot_decision_boundary(X_train, y_train, lr_weighted, \
'LogisticRegression')
plt.show()
PrecisionRecallDisplay.from_estimator(lr_weighted, X_test,\
y_test, ax = plt.gca(),name = "LogisticRegressionWeighted")

图 5.7 – “平衡”类别加权逻辑回归模型的决策边界(左)和 PR 曲线(右)
让我们计算 F2 分数、精确率和召回率分数:
compute_scores(lr_weighted, X_test, y_test)
“平衡”类别加权逻辑回归模型的分数如下:
f2-score: 0.873 precision: 0.587 recall: 0.993
在分析结果后,我们可以看到一个决策边界,它正确地分类了大多数正类示例。精确率下降,而召回率上升。F2 分数的下降可以归因于召回率和精确率值的变化。模型在召回率方面有所提高,表明其识别所有正类示例的能力得到了增强。然而,这种进步导致精确率同时下降,这表明在负类示例(我们并不特别关心)上犯错的速率增加。
让我们尝试使用网格搜索调整class_weight参数,以优化我们的 F2 分数。我们始终可以尝试优化任何其他目标,例如平均精确率、精确率或召回率等。np.linspace(0.05, 0.95, 20)函数是一个numpy函数,它生成一个介于 0.05 和 0.95 之间的 20 个均匀分布的数字数组:
from sklearn.metrics import make_scorer, fbeta_score
def f2_func(y_true, y_pred):
f2_score = fbeta_score(y_true, y_pred, beta=2.)
return f2_score
def f2_scorer():
return make_scorer(f2_func)
# Define the parameter grid
param_grid = {
'class_weight': [
{0: x, 1: 1.0-x} for x in np.linspace(0.05, 0.95, 20)]
}
# Instantiate the grid search model
grid_search =GridSearchCV(
LogisticRegression(),param_grid,\
cv=3, scoring=f2_scorer(), n_jobs=-1
)
# Fit the grid search to the data
grid_search.fit(X_train, y_train)
# Get the best parameters
best_params = grid_search.best_params_
best_params
这会产生以下输出:
{'class_weight': {0: 0.14473684210526316, 1: 0.8552631578947368}}
我们的标准指标如下:
f2-score: 0.930 precision: 0.892 recall: 0.940
在引入这些类别权重后,我们的决策边界试图在错误分类正类和负类示例之间取得更好的平衡,如图图 5**.8所示。这导致 F2 分数达到 0.93,提高了精确率值,同时保持适度的召回率:

图 5.8 – 类别加权逻辑回归模型的决策边界(左)和 PR 曲线(右)
🚀 微软在生产中的成本敏感学习
在微软的一个实际应用中,主要目标是提高 Bing 广告的点击率(CTR)预测[5]。实现准确的 CTR 预测对于优化用户体验和收入流至关重要。预测准确率仅提高 0.1%,就有可能使利润增加数亿美元。通过严格的测试,一个结合神经网络(NNs)和梯度提升决策树(GBDTs)的集成模型成为最有效的解决方案。
对于训练数据集,从一个月的日志数据中随机选择了 5600 万个样本,每个样本包含数百个统计特征。为了减少训练成本,非点击案例被降采样了 50%,并分配了 2 的类别权重以保持原始分布。然后使用从随后一周日志中随机抽取的 4000 万个样本的测试数据集评估模型性能。而不是重新校准模型,使用了类别权重来在降采样后保持平均 CTR。
在下一节中,我们将讨论如何使用决策树进行代价敏感学习。
决策树的代价敏感学习
决策树是使用条件决策来预测样本类别的二叉树。每个树节点代表一组与基于特征的连续条件语句相对应的样本。我们根据特征和阈值值将节点分为两个子节点。想象一下有一组学生,他们的身高、体重、年龄、班级和位置。我们可以根据年龄特征和 8 的阈值将这个集合分为两部分。现在,所有年龄小于 8 岁的学生将进入左子节点,而所有年龄大于或等于 8 岁的学生将进入右子节点。
这样,我们可以通过连续选择特征和阈值值来创建树。树的每个叶节点将只包含来自一个类别的节点。
在构建决策树的过程中,经常会遇到一个问题:“应该选择哪个特征和阈值对来分割给定节点的样本集?”答案很简单:我们选择产生最均匀(或同质)数据子集的对。理想情况下,产生的两个结果子集——被称为左右子节点——应该各自主要包含来自单个类别的元素。
节点从不同类别中混合样本的程度被称为节点的不纯度,这可以被视为决策树的损失度量。不纯度越高,样本集的异质性就越大。以下是计算不纯度的两种最常见方法:
-
Gini 系数
-
Entropy
让我们看看 Gini 系数和熵的两个类 c1 和 c2 的公式:
Gini = 1− Proportion c1 2− Proportion c2 2
我们将得到以下结果:
Entropy = − Proportion c1 * log (Proportion c1)
− 比例 c2 * log (比例 c2)
要使用决策树进行 CSL,我们只需将类权重与 Gini 和熵计算中每个类的项相乘。如果两个类的权重是 W1 和 W2,Gini 和熵将如下所示:
Gini = 1 − W1 * 比例 c1² − W2 * 比例 c2²
Entropy = − W1 * 比例 c1 * log(比例 c1)
− W2 * 比例 c2 * log(比例 c2)
现在,模型优先考虑权重较高的类,而不是权重较低的类。如果我们给少数类更多的权重,模型将做出优先考虑具有同质少数类样本的节点的决策。
在本节中,我们了解了一些如何将类权重纳入决策树的损失函数中,以考虑误分类错误。在下一节中,我们将看到scikit-learn如何通过将其集成到模型创建 API 中来简化此过程,从而消除我们手动调整损失函数的需要。
使用 scikit-learn 和 XGBoost 模型进行成本敏感学习
scikit-learn提供了一个class_weight超参数来调整大多数模型中各种类的权重。这个参数可以根据scikit-learn中不同学习算法的不同方式指定。然而,主要思想是这个参数指定了损失计算公式中每个类的权重。例如,这个参数指定了之前提到的逻辑回归中的权重 FP 和权重 FN 的值。
与LogisticRegression函数类似,对于DecisionTreeClassifier,我们可以使用DecisionTreeClassifier(class_weight='balanced')或DecisionTreeClassifier(class_weight={0: 0.5, 1: 0.5})。
关于 SVM,它甚至可以通过为每个类标签指定一个权重值来扩展到多类分类:
svm.SVC(class_weight= {-1: 1.0, 0: 1.0, 1: 1.0})
关于确定class_weight值的通用指导原则是使用多数类与少数类比例的倒数。我们可以通过使用网格搜索算法(使用scikit-learn中的GridSearchCV函数)进行超参数调整来找到更优的class_weight值。
类似地,XGBoost 也有scale_pos_weight参数来控制正负权重的平衡:
XGBClassifier(scale_pos_weight)
scale_pos_weight的默认值是 1。一个推荐的scale_pos_weight值是sum(negative_instances)/sum(positive_instances),这可以计算为float(np.sum(label == 0)) / np.sum(label==1)。
XGBoost 有几个其他参数,例如 max_delta_step 和 min_child_weight,这些参数可以针对不平衡数据集进行调整。在优化过程中,max_delta_step 决定了更新步长的大小,影响学习速度和稳定性。min_child_weight 通过影响决策树中叶节点的大小来控制过拟合并增强泛化。在处理不平衡数据场景时,调整这些参数可以策略性地提高算法性能。
首先,让我们使用 DecisionTreeClassifier 解决我们的分类问题:
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import PrecisionRecallDisplay
dt_clf = DecisionTreeClassifier(random_state=0, max_depth=6).fit(
X_train, y_train)
plot_decision_boundary(X,y,dt_clf,'DecisionTreeClassifier')
plt.show()
PrecisionRecallDisplay.from_estimator(
dt_clf, X_test, y_test, ax = plt.gca(),\
name = "DecisionTreeClassifier")
print(classification_report_imbalanced(
y_test,\
dt_clf.predict(X_test), \
target_names=['class 0', 'class 1']
)
)
computescores(dt_clf, X_test, y_test)
输出的决策边界比逻辑回归的更复杂(图 5.9),更好地分离了两个类别,并给出了 F2 分数为 0.932:

图 5.9 – 决策树分类器模型的决策边界(左)和 PR 曲线(右)
我们在 图 5.10 中重现了逻辑回归模型的决策边界和 PR 曲线,以进行比较:

图 5.10 – 用于比较的逻辑回归(左)和 PR 曲线(右)
我们对决策树分类器的标准指标如下:
f2-score: 0.932 precision: 0.892 recall: 0.94
接下来,让我们使用 class_weight='balanced' 参数:
dt_clf_tuned = DecisionTreeClassifier(
class_weight = 'balanced', random_state=0, max_depth=6
).fit(X_train, y_train)
在使用之前的代码绘制决策边界、PR 曲线和计算分数后,输出如下:

图 5.11 – 决策树分类器模型的决策边界(左)和 PR 曲线(右)
f2-score: 0.934 precision: 0.770 recall: 0.987
调整后的权重提高了 F2 分数和召回值。
流行框架如 scikit-learn 也允许我们指定 sample_weight 作为数据集中每个观察值的权重列表。sample_weight 和 class_weight 参数可能相当令人困惑,并且它们的目的可能不会从它们的文档中非常清楚地了解何时使用什么。以下表格说明了两者之间的区别:
sample_weight |
class_weight |
|
|---|---|---|
| 目的 | 用于指定单个示例的权重。当某些示例比其他示例更重要时,无论它们的类别如何,都可能很有用。当某些数据更可信(例如使用内部人工标注器标注)时,它可以获得更高的权重。当您对批处理中的样本没有相同的信心时,这可能很有用。 | 用于纠正类别不平衡。当示例的重要性取决于它们的类别时应该使用。 |
| 用法 | 可以在训练和测试中使用。特别适用于在具有 AUC 等指标的不同测试集上比较多个模型时,通常希望平衡测试集:sklearn.metrics.confusion_matrix(…, sample_weight) sklearn.linear_model . LogisticRegression() . score(…,sample_weight) |
主要在训练期间使用,以指导训练。考虑到某些类别比其他类别更重要,因此会计算误分类错误:sklearn.linear_model . LogisticRegression(``class_weight) |
| 在模型训练期间设置值为 0 的影响 | 模型将不会考虑samples_weight=0的示例(无论示例的类别如何)。 |
模型将不会考虑属于class_weight = 0的类别的任何示例。此外,模型永远不会预测该类别。 |
| 应用案例 | 当预测客户流失时,如果失去某些客户会对业务产生更大的影响,因为他们倾向于更频繁地购买或花费更多,我们希望使用sample_weight给这些客户更高的权重。 |
如果我们有一个数据集,其中一个类别明显多于其他类别,使用class_weight可以帮助模型更多地关注代表性不足的类别。 |
表 5.2 – scikit-learn 库中的 sample_weight 与 class_weight
警告
如果我们使用sample_weight和class_weight,两者都会相乘,我们将看到这两个参数的效果。这两个参数仍然可以一起使用,以平衡类别重要性和个体实例重要性,并实现其预期目的。
使用numpy可以更容易地创建sample_weight所需的权重值列表:sample_weight = np.where(label==1, 80, 20)。然而,scikit-learn有一个名为sklearn.utils.class_weight.compute_sample_weight()的函数,可以用来自动从class_weight估计sample_weight的值。
class_weight也可以是一个字典,包含每个标签的值或平衡值。如果我们将其设置为平衡,类别权重将由n_samples/(n_classes * np.bincount(y))确定。
class_weight返回的值是一个字典:{class_label: weight},对于每个class_label值。
类似地,如果你需要进行多标签分类,你也可以使用sklearn.utils.class_weight.compute_sample_weight。
🚀 Airbnb 生产中的成本敏感学习
在 Airbnb 的一个实际应用中[6],要解决的主要问题是提高搜索和可发现性以及他们体验(手工活动)平台的个性化。随着体验数量的增加,有效地对这些体验进行排名以匹配用户偏好并提高预订变得至关重要。
Airbnb 旨在提高其搜索排名,为用户提供最相关和高质量的经历。为了提升其排名模型的品质,他们在目标函数中使用了样本权重(在上一节中讨论过)。
通过使用样本权重(在上一节中讨论过)解决训练数据中的数据不平衡问题。在目标函数中,高质量的经历被赋予更高的权重,而低质量的经历被赋予较低的权重。这样做是为了在搜索排名中推广高质量的经历,并且他们成功地提高了高质量经历的排名,减少了低质量经历,而没有影响整体预订,如 A/B 测试所证实。
Airbnb 迭代地开发和测试其机器学习模型,最终将其集成到其生产系统中,以实时对“体验”进行排名。他们经历了多个阶段,从建立强大的基线到个性化、在线评分以及处理各种业务规则。
在下一节中,我们将了解一种可以将任何模型转换为成本敏感版本的技术,而无需我们知道其损失函数或模型的内部工作原理。
MetaCost – 使任何分类模型成本敏感
MetaCost 首次在 1999 年由 Pedro Domingos [7]发表的一篇论文中提出。MetaCost 作为机器学习算法的包装器,将底层算法转换为自身的成本敏感版本。它将底层算法视为黑盒,与不稳定算法(如下定义)配合最佳。当 MetaCost 首次提出时,CSL 还处于早期阶段。只有少数算法,如决策树,已被转换为它们的成本敏感版本。对于某些模型,创建成本敏感版本变得很容易,而对于其他模型,则是一项非平凡的任务。对于定义模型成本敏感版本变得困难的算法,人们主要依赖于数据采样技术,如过采样或欠采样。这就是 Domingos 提出了一种将大量算法转换为它们的成本敏感版本的方法。MetaCost 可以用于多类分类和所有类型的成本矩阵。
不稳定算法
如果一个算法的初始条件(例如,训练数据或初始权重)的微小变化可以导致模型产生大的变化,则称该算法为不稳定[8]。假设你有一个包含 1,000 个项目的数据集。一个稳定的模型,如K-最近邻(KNN),如果你从数据集中移除一个项目,它不会改变太多。然而,如果你用 999 个项目而不是 1,000 个项目来训练一个决策树模型,它可能会完全重构。
让我们深入探讨 MetaCost 算法的机制,如图 5.12 所示:

图 5.12 – MetaCost 算法
MetaCost 通过结合 bagging 的概念与误分类成本矩阵来工作:
-
首先,我们创建原始数据的多个 bootstrap 样本。
-
对于每个 bootstrap 样本,我们训练给定模型的一个新副本。到目前为止,这个过程与 bagging 相同。你可以在图 5.12 左侧看到前两个步骤。首先,我们从原始数据中创建 bootstrap 样本 S1、S2 和 S3。然后,我们分别在样本(S1、S2 和 S3)上训练模型 L1、L2 和 L3。
-
接下来,我们将原始数据 S 送入由 L1、L2 和 L3 组成的集成中。
-
我们将来自成本矩阵的误分类成本与集成预测的类别概率相乘,以获得实际成本。这显示在图 5.12 的右侧。
-
然后,我们重新标记数据,使得新的类别标签最小化实际成本。
-
最后,我们在重新标记的数据上训练了一个新模型的副本。这个模型副本被用作最终模型。
我们可以在 图 5.13 中看到使用 MetaCost 重新标记数据的过程:

图 5.13 – 使用 MetaCost 重新标记数据的过程
在 图 5.13 的左侧,我们有原始数据。星星是少数类示例,正方形是多数类示例。在这里,所有在椭圆形内的样本都被预测为星星,所有在椭圆形外的样本都被预测为正方形。左侧的椭圆形是根据对所有错误具有相同误分类成本的假设绘制的。在中间,我们根据实际误分类成本绘制的一个拉长的椭圆形创建了一个新的类别边界。请注意,现在所有的星星都被正确分类了。同时,请注意,现在一些正方形被错误地分类为星星。这是预期的,因为星星的误分类成本远高于正方形。在此阶段,MetaCost 将这些被错误分类的正方形重新标记为星星。最后,MetaCost 在重新标记的数据上训练了一个模型。由于容易被误认为是少数类的多数类示例已被重新标记为属于少数类,因此最终的模型不太可能将少数类的实例标记错误。
为了节省空间,我们省略了 MetaCost 算法的实现。您可以在本章的 GitHub 仓库中找到它。
我们将应用该算法到逻辑回归模型中。MetaCost 使用一个代价矩阵,这是一个超参数。代价矩阵中的值对应于混淆矩阵(表 5.1中混淆矩阵的转置)中各项的权重或成本:
C = (TN FN FP TP )
假设我们使用一个对假阳性和假阴性具有相同成本的代价矩阵(即单位矩阵):
C = np.array([[0, 1], [1, 0]])
图 5.14 展示了决策边界和指标,它们与逻辑回归分类器的结果(图 5.10)非常接近:

图 5.14 – 具有单位代价矩阵的逻辑回归模型 MetaCost 变种的决策边界(左侧)和 PR 曲线(右侧)
我们可以根据训练数据的失衡比率估计代价矩阵:
C = np.array([[0, 66], [1, 0]])
图 5.15 展示了输出决策函数和 PR 曲线:

图 5.15 – 具有更优代价矩阵的逻辑回归模型 MetaCost 变种的决策边界(左侧)和 PR 曲线(右侧)
虽然与基线相比 F2 分数有所下降,但召回率确实大幅提高。
MetaCost 算法中的各种步骤,如重新标记整个训练集,可能相当昂贵,这可能会阻止我们在训练数据集很大时使用这种技术。
成本敏感的集成技术
AdaCost [9]、AdaUBoost [10]和 AsymBoost [11]是 AdaBoost 模型的成本敏感修改。AdaCost 在迭代训练过程中最小化误分类成本。AdaUBoost 通过强调少数类来处理不平衡数据集。AsymBoost 专注于减少最昂贵的误分类。它们都在考虑误分类成本的同时调整权重。
这些算法背后的基本原理是,除了对误分类成本大的实例分配高初始权重外,更新权重的规则也应考虑成本。这意味着应该增加昂贵误分类的权重,同时减少正确分类的权重。
在下一节中,我们将了解另一种成本敏感的元学习技术,称为阈值调整。
阈值调整
决策阈值是一个非常重要的概念,需要密切关注。默认情况下,我们有以下情况:
-
预测概率 >= 0.5 表示类别 1
-
预测概率 < 0.5 表示类别 0
然而,阈值是一个强大的元参数,我们可以自由调整。表 5.3显示了模型预测与真实标签的预测。
如果我们使用默认的阈值 0.5,准确率为 2/4 = 50%。另一方面,如果我们选择的阈值是 0.80,准确率为 100%。这表明所选阈值的重要性:
| 预测输出 | 真实输出 |
|---|---|
| 0.65 | 0 |
| 0.75 | 0 |
| 0.85 | 1 |
| 0.95 | 1 |
表 5.3 – 一个显示模型预测输出与真实输出(标签)的表格
大多数指标,如准确率、精确率、召回率和 F1 分数,都是阈值相关指标。
另一方面,如 ROC 曲线和 PR 曲线这样的指标是阈值无关的,这意味着这些图表评估的是模型在所有可能的阈值下的性能,而不是单个固定的阈值。
在处理机器学习指标,如 F1 或准确率时,了解阈值值的作用很重要。这些指标默认使用 0.5 的阈值。因此,尤其是对于新手和中级机器学习从业者,会产生一种误解,认为这些指标不可避免地与这个特定的阈值相关联。
然而,这可能导致对模型性能的不准确解释,尤其是在涉及不平衡数据集的场景中。指标的选择和决策阈值的确定是独立的选择,应该这样处理。确定适当的阈值是过程中的一个关键步骤,应该独立于所选的指标来考虑。
此外,仅仅依赖于默认的阈值 0.5 可能会产生误导。阈值应根据项目的具体要求和数据的性质来设置。因此,机器学习从业者理解阈值与所选指标之间的相互作用,以准确评估其模型性能至关重要。
在二元分类中,改变阈值将很容易改变阈值相关的指标,如准确度、F1 分数、TPR 或 FPR。许多研究[12][13]提到了阈值调整的价值,尤其是在训练数据不平衡的情况下。Provost [14]的一篇论文指出,使用未调整输出阈值的模型可能是一个严重的错误。在深度学习领域,Buda 等人[15]表明,使用随机过采样(ROS)并结合阈值处理,在由 CIFAR 和 MNIST 创建的不平衡数据集上优于简单的 ROS。无论数据是否不平衡,选择一个最优的阈值可以在模型的性能上产生很大的差异。
许多时候,我们希望找到优化我们的阈值相关指标(例如 F1 分数)的阈值。在这里,找到 F1 分数最大的阈值(图 5.16):

图 5.16 – 一个具有最佳阈值的 PR 曲线,该阈值找到最大的 F1 分数(请参阅本章 GitHub 仓库中的笔记本)
图 5.17 展示了一个图表,说明了修改决策阈值对不平衡数据集的各种分类指标的影响:真正例率(TPR或召回率)、真负例率(TNR)、假正例率(FPR)和精确度。所使用的模型是没有任何类别权重或对少数类敏感性的逻辑回归。有关完整笔记本,请参阅本章的 GitHub 仓库:

图 5.17 – 不同分类指标(TPR、TNR、FPR、精确度、F1 分数和准确度)作为决策阈值的函数的图表
关于这些图表,有以下几点观察:
-
精确度:如果阈值增加,精确度( TP _________________ 总预测为正的次数)通常会上升。为什么?因为随着阈值的增加,总预测为正的次数会减少,因此,随着分母的减少,精确度增加。同样,相反的情况也是正确的:如果阈值降低,精确度也会降低。
-
召回率:让我们看看阈值变化对召回率的影响。召回率定义为 TP ____________ 总正例数,分母是一个常数。随着阈值的降低,TP 可能会增加,通常会提高召回率。
-
真正例率(TNR):TNR 衡量的是实际负例中被正确识别为负例的比例。在不平衡的数据集中,如果负类是多数类,一个简单或表现不佳的分类器可能因为对所有或大多数实例预测多数类而具有很高的 TNR。在这种情况下,TNR 可能会误导性地很高。
-
误报率(FPR):这是将负实例错误地分类为正实例的比率。在不平衡的数据集中,一个简单的分类器,如果将所有实例都预测为多数类(负类),其 FPR 将接近 0。
通常,我们在选择最佳决策阈值时,需要在 TPR(真正例率)和 TNR(真负例率)之间进行权衡,如图 5.17所示。
🚀 Shopify 在生产中的成本敏感学习
在 Shopify 的实际应用中[16],平台面临着为成百万的商家对各种商品进行分类的挑战。准确的产品分类对于增强搜索和发现功能以及向商家提供个性化营销洞察至关重要。鉴于产品数量庞大且种类繁多,手动分类是不可行的。机器学习技术被用于自动化分类过程,以适应不断扩展和多样化的产品范围。所使用的数据集高度不平衡,这主要是由于 Shopify 采用的谷歌产品分类法(GPT)的层级结构。GPT 拥有超过 5,500 个类别,这给一个已经具有挑战性的问题增加了复杂性。
为了解决数据不平衡的问题,实施了类权重。通过分配类权重,模型可以对代表性不足的类别中的错误预测施加更高的惩罚,从而有效地缓解这些类别中数据不足的问题。模型经过微调,以在层级精度和召回率之间取得平衡。这种微调是基于特定的业务用例,旨在通过最小化负面交互和摩擦来提升商家体验。对置信阈值进行了手动调整(这表明阈值调整在现实世界中是多么相关!),以确保在“宗教和仪式”等敏感类别中实现最佳性能。平衡了各种指标,如层级准确率、精确率、召回率和 F1 分数,以使模型满足业务需求。该模型现在被多个内部团队和合作伙伴生态系统积极用于开发衍生数据产品。
接下来,我们将探讨调整这些阈值的各种方法。
阈值调整方法
大多数情况下,我们旨在优化特定的业务指标或标准机器学习指标,这要求我们选择一个最大化感兴趣指标的阈值。在文献中,讨论了各种阈值调整方法,例如将阈值设置为观察正例的优先概率,使用 ROC 曲线优化高 TPR 和低 FPR,或使用 PR 曲线最大化 F1 分数或 Fbeta 分数(见图 5**.18):

图 5.18 – 调整阈值的流行方法
我们将逐一讨论这些方法。
我们将继续使用我们之前创建的相同数据集。以下代码块拟合逻辑回归模型并获取测试集的预测概率:
lr = LogisticRegression(max_iter=1000)
lr.fit(X_train, y_train)
y_pred = lr.predict_proba(X_test)
y_pred = y_pred[:, 1]
使用先验阈值进行阈值调整
一个明显的阈值可以使用的是训练数据集中正类概率的值[10]。让我们实现这个想法:
num_pos, num_neg = Counter(y)[1], Counter(y)[0]
prior_threshold = num_pos /(num_pos + num_neg)
print('Prior threshold=%f'% prior_threshold)
# Find the closest threshold from thresholds from ROC curve
fpr, tpr, thresholds = roc_curve(y_test, y_pred)
min_threshold_index = np.absolute( \
thresholds-prior_threshold).argmin()
print('Best threshold using prior threshold from ROC \
function=%f'% thresholds[min_threshold_index])
print("TPR at threshold=%f" % tpr[min_threshold_index])
print("FPR at threshold=%f" % fpr[min_threshold_index])
print("TNR at threshold=%f" % (1-fpr[min_threshold_index]))
这将打印以下阈值:
Prior threshold=0.014900
Best threshold using prior threshold from ROC function=0.014232
TPR value at the threshold=0.675676
FPR value at the threshold=0.147990
TNR value at the threshold=0.852010
使用 ROC 曲线进行阈值调整
对于 ROC 曲线,可以使用尤登的 J 统计量[17]来找到最优阈值。尤登的 J 统计量在临床领域有根源,是一个捕获诊断测试性能的单个统计量。在二元分类的背景下,统计量 J 定义为以下:
J = 灵敏度 + 特异性 − 1 = TPR + TNR − 1 = TPR − FPR
其值可以从-1(TPR=0 和 TNR=0 – 即,总是错误的结果)到 1(TPR=1 和 FPR=0 – 即,完美结果)不等。这是在 ROC 分析中选择阈值的一个常见选择,因为它平衡了灵敏度(TPR)和特异性(TNR)。请注意,TNR = 1-FPR。
最大化尤登指数 J 的原因等同于选择最优阈值,因为它本质上是在 ROC 曲线上找到离无歧视线(对角线)最远的点。这意味着它选择一个在 TPR 和 FPR 之间取得平衡的阈值,这通常是我们在分类器中想要的。
“最优”阈值可能严重依赖于我们特定应用中假阳性与假阴性的成本。以下代码块使用尤登指数识别最优分类阈值,该指数是从 ROC 曲线计算得出的:
fpr, tpr, thresholds = roc_curve(y_test, y_pred)
youden_index = tpr - fpr
max_youden_index = np.argmax(youden_index)
best_thresh = thresholds[max_youden_index]
print('Best threshold using Youden index=%f'% best_thresh)
print('Max Youden index value=%f'% youden_index[max_youden_index])
print("TPR value at the threshold=%f" % tpr[max_youden_index])
print("FPR value at the threshold=%f" % fpr[max_youden_index])
print("TNR value at the threshold=%f" % (1-fpr[max_youden_index]))
这将输出以下值:
Best threshold using Youden index=0.098879
Max Youden index value=0.622143
TPR value at the threshold=0.635135
FPR value at the threshold=0.012992
TNR value at the threshold=0.987008
另一种常用于文献中的阈值调整方法,它使用 ROC 曲线,即最大化 TPR(也称为灵敏度)和 TNR(也称为特异性)的几何平均值:
G − mean = √ __________________ 灵敏度 * 特异性 = √ _TPR * TNR = √ ______________ TPR * (1 − FPR)
最大化几何平均值等同于在 TPR 和 TNR 之间找到一个良好的平衡。以下代码块使用 G-mean 指标计算分类的最佳阈值,以及其相应的 TPR、FPR 和 TNR 值。我们从sklearn.metrics导入roc_curve:
fpr, tpr, thresholds = roc_curve(y_test, y_pred)
gmean = np.sqrt(tpr*(1-fpr))
max_gmean_index = np.argmax(gmean)
best_thresh = thresholds[max_gmean_index]
print('Best threshold using G-mean=%f'% (best_thresh))
print('Max G-mean value=%f'% (gmean[max_gmean_index]))
print("TPR value at the threshold=%f" % tpr[max_gmean_index])
print("FPR value at the threshold=%f" % fpr[max_gmean_index])
print("TNR value at the threshold=%f" % (1 - fpr[max_youden_index]))
这将输出以下最优值:
Best threshold using G-mean=0.098879
Max G-mean value=0.791760
TPR value at the threshold=0.635135
FPR value at the threshold=0.012992
TNR value at the threshold=0.987008
使用 PR 曲线进行阈值调整
正如我们在第一章,“机器学习中的数据不平衡介绍”中讨论的那样,当正类比负类更重要时,PR 曲线通常比 ROC 曲线更适合不平衡数据集。作为提醒,简单的原因是 PR 曲线忽略了真正的负例,因此,它可以在使用不平衡数据集与平衡数据集相比时,表示模型性能的显著差异。虽然随着数据不平衡的增加,ROC 曲线不会变化太多,但如果两个类别都同样重要,它们可以是一个更好的选择。
我们希望精确率和召回率都达到最大值,理想情况下都是 1。因此,PR 曲线上的优化点是(1,1)。
当我们偏离这个优化点时,精确率和召回率的值都会降低,我们就不那么优化了。
一种捕捉精确率和召回率之间权衡的度量是 F1 分数。F1 分数被定义为精确率和召回率的调和平均值:
F1 = 2 * (Precision * Recall) / (Precision + Recall)
如果我们分析这个方程,我们会看到当精确率和召回率都是 1 时,F1 分数最高(在 1 处达到最大值),这正是我们在 PR 曲线上定义的优化点。
因此,优化最大 F1 分数将确保我们努力最大化精确率和召回率,有效地推动我们向 PR 曲线上的优化点靠近。
最大化 F1 分数是确定最佳阈值的一种常见且有效的方法。以下代码块使用从 PR 曲线派生的 F1 分数度量计算最佳阈值:
from numpy import argmax
from sklearn.metrics import precision_recall_curve
precision, recall, thresholds = precision_recall_curve(y_test, y_pred)
fscore = 2*precision*recall/(precision+recall)
max_fscore_idx = argmax(fscore)
print('max_fscore_idx==%d' % max_fscore_idx)
print('best threshold using PR curve=%f' % thresholds[max_fscore_idx])
这会输出以下优化值:
max_fscore_idx==4950
best threshold using PR curve=0.258727
max(fscore)= 0.661290
让我们绘制具有最佳阈值值的 PR 曲线:
pyplot.plot(recall, precision, marker='.', \
label='precision vs recall for various thresholds')
result = pyplot.scatter(recall[max_fscore_idx], \
precision[max_fscore_idx], \
marker='x', color='red', \
label='Best threshold with highest\
f1-score')
plt.legend(handles=result.legend_elements()[0], \
labels="legend", loc='upper center', \
bbox_to_anchor=(1, 1))
pyplot.xlabel('recall')
pyplot.ylabel('precision')
PR 曲线看起来是这样的:

图 5.19 – 最佳 F1 分数阈值值的 PR 曲线
一般阈值调整
作为一种一般方法,我们可能需要优化任何度量,如平均精确度、准确度等,或任何其他业务度量。在这种情况下,我们可以编写一个函数来直接优化该度量。让我们以 I. Unal 等人在其研究[18]中定义的联合指数(IU)度量为例,其中度量由使 IU(c)最小化的阈值值 c 定义:
IU(c) = (|Sensitivity(c) − ROC _ AUC| + |Specificity(c) − ROC _ AUC|)
在这里,Sensitivity(c)是 c 处的灵敏度,Specificity 是 c 处的特异性,ROC _ AUC 是 ROC 图的曲线下面积(AUC)。
让我们实现 IU 度量,如这里定义的,作为一个自定义度量来找到最小化它的最佳阈值:
from sklearn.metrics import f1_score, auc
def custom_metric(y_test, y_pred):
fpr, tpr, thresholds = roc_curve(y_test, y_pred)
sensitivity = tpr
specificity = 1-fpr #same as tnr
roc_auc = auc(fpr, tpr)
index_of_union = abs(sensitivity-roc_auc) + \
abs(specificity-roc_auc)
return index_of_union
scores = custom_metric(y_test, y_pred)
min_score_idx = np.argmin(scores)
print('max_score_idx=%d' % min_score_idx)
print('best threshold=%f'% thresholds[min_score_idx])
print('minimum IU-value at the best threshold=%f' % \
scores[min_score_idx])
这会产生以下优化值:
max_score_idx=34
best threshold=0.000042
minimum IU-value at the best threshold=0.112514
这标志着我们对经典建模技术的讨论结束。我们现在准备进入研究深度学习领域中的数据不平衡。我们将探讨从上一章学习的一般技术如何适应以增强我们处理不平衡数据时的深度学习模型。
摘要
在本章中,我们深入探讨了 CSL(组合过采样和欠采样),作为一种替代方案。与将所有误分类错误同等对待的数据级别技术不同,CSL 调整模型的成本函数,以考虑不同类别的显著性。它包括类别加权和元学习技术。
类似于scikit-learn、Keras/TensorFlow 和 PyTorch 这样的库支持成本敏感学习。例如,scikit-learn提供了一个class_weight超参数来调整损失计算中的类别权重。XGBoost 有一个scale_pos_weight参数用于平衡正负权重。MetaCost 通过使用袋装和误分类成本矩阵将任何算法转换为成本敏感版本。此外,阈值调整技术可以通过后处理模型预测来增强 F1 分数、精确度和召回率等指标。
使用各种数据采样和 CSL 技术的实验可以帮助确定最佳方法。我们将在第八章中扩展这些概念,即算法级深度学习技术。这标志着我们对经典机器学习模型的讨论结束,我们已经毕业到深入学习技术。在下一章中,我们将简要介绍深度学习概念,并看看不平衡数据集在深度学习世界中可能成为问题。
问题
-
在使用本章中使用的相同数据集的同时,将 CSL 技术应用于
scikit-learn中的 SVM 模型。使用class_weight和sample_weight参数,类似于我们在本章中用于其他模型的方式。比较此模型与本章中已经遇到的模型的表现。 -
LightGBM 是另一个类似于 XGBoost 的梯度提升框架。在本章使用的数据集上应用成本敏感学习技术到 LightGBM 模型。使用
class_weight和sample_weight参数,类似于我们在本章中用于其他模型的方式。比较此模型与本章中已经遇到的模型的表现。 -
AdaCost [10] 是一种结合了提升和 CSL 的 AdaBoost 变体。它通过利用误分类成本来更新连续提升轮次的训练分布。将
scikit-learn中的AdaBoostClassifier扩展以实现 AdaCost 算法。比较 AdaCost 与 MetaCost 在本章使用的数据集上的性能。 -
调整 XGBoost 模型的超参数,特别是
max_depth、max_delta_step和min_child_weight,使用本章中使用的数据集。调整后,评估加权 XGBoost 模型是否优于非加权版本。
参考文献
-
P. Turney, 归纳概念学习中的成本类型, 在第 17 届国际机器学习会议的成本敏感学习研讨会论文集,斯坦福大学,加利福尼亚州(2000 年),第 15–21 页。
-
C. X. Ling 和 V. S. Sheng, 成本敏感学习和类 不平衡问题.
-
Sheng, V. S.,& Ling, C. X. (2006). 为了使分类器成本敏感的阈值方法. AAAI’06: 第 21 届全国人工智能会议论文集,第 6 卷,第 476–481 页。
-
儿童肺炎统计数据 – 联合国儿童基金会数据:
data.unicef.org/topic/child-health/pneumonia/. -
X. Ling, W. Deng, C. Gu, H. Zhou, C. Li, 和 F. Sun, 在第 26 届国际万维网会议(WWW '17)的伴随会议论文集中,Bing 搜索广告的点击预测模型集成,珀斯,澳大利亚:ACM 出版社,2017 年,第 689–698 页。doi: 10.1145/3041021.3054192.
-
机器学习驱动的 Airbnb 体验搜索排名(2019 年),
medium.com/airbnb-engineering/machine-learning-powered-search-ranking-of-airbnb-experiences-110b4b1a0789. -
P. Domingos, MetaCost: 一种使分类器成本敏感的通用方法,在知识发现与数据挖掘国际会议论文集中,第 155–164 页,1999 年。
-
不稳定的学习者. 在:Sammut, C.,Webb, G.I. (eds) 机器学习与数据挖掘百科全书。Springer,波士顿,马萨诸塞州。doi:
doi.org/10.1007/978-1-4899-7687-1_866. -
W. Fan, S. J. Stolfo, J. Zhang, 和 P. K. Chan, AdaCost: 误分类 成本敏感的 Boosting.
-
G. I. Karakoulas 和 J. Shawe-Taylor, 优化不平衡 训练集 的分类器*.
-
P. Viola 和 M. Jones, 使用非对称 AdaBoost 和 检测级联 进行快速和鲁棒的分类.
-
J. M. Johnson 和 T. M. Khoshgoftaar, 集成学习者的输出阈值和类不平衡大数据, 在 2021 年 IEEE 第 33 届国际人工智能工具会议(ICTAI)中,华盛顿特区,美国:IEEE,2021 年 11 月,第 1449–1454 页。doi: 10.1109/ICTAI52525.2021.00230.
-
J. M. Johnson 和 T. M. Khoshgoftaar, 在 2019 年第 18 届 IEEE 国际机器学习与应用会议(ICMLA)中,关于类不平衡大数据的深度学习和阈值处理, 佛罗里达州博卡拉顿,美国,2019 年 12 月,第 755–762 页。doi: 10.1109/ICMLA.2019.00134.
-
F. Provost, 不平衡数据集的机器学习 入门.
-
M. Buda, A. Maki, 和 M. A. Mazurowski, 卷积神经网络中类别不平衡问题的系统研究, 神经网络,第 106 卷,第 249–259 页,2018 年 10 月,doi: 10.1016/j.neunet.2018.07.011.
-
使用丰富图像和文本数据大规模分类产品(2021 年),
shopify.engineering/using-rich-image-text-data-categorize-products. -
W. J. Youden, 诊断测试评分指数,癌症,第 3 卷,第 1 期,第 32–35 页,1950 年,doi: 10.1002/1097-0142(1950)3:1<32::AID-CNCR2820030106>3.0.CO;2-3.
-
I. Unal, 在 ROC 分析中定义最佳切割点值:一种替代方法,计算与数学在医学中的应用,第 2017 卷,第 1–14 页,2017 年,doi: 10.1155/2017/3762651.
-
X. Ling, W. Deng, C. Gu, H. Zhou, C. Li, 和 F. Sun, 用于 Bing 搜索广告点击预测的模型集成, 在第 26 届国际万维网大会伴随会议——WWW '17 伴随会议论文集中,珀斯,澳大利亚:ACM 出版社,2017 年,第 689–698 页。doi: 10.1145/3041021.3054192.
第六章:深度学习中的数据不平衡
类不平衡数据是深度学习模型的一个常见问题。当一个或多个类别样本显著较少时,深度学习模型的性能可能会受到影响,因为它们倾向于优先从多数类别中学习,从而导致少数类别(或类别组)的泛化能力较差。
许多现实世界的数据是不平衡的,这对深度学习分类任务提出了挑战。图 6.1展示了在各种深度学习应用中常见的不平衡数据问题的一些类别:

图 6.1 – 一些常见的不平衡数据问题类别
本章我们将涵盖以下主题:
-
深度学习简介
-
深度学习中的数据不平衡
-
处理数据不平衡的深度学习技术概述
-
多标签分类
到本章结束时,我们将对深度学习和神经网络有一个基础的理解。我们还将掌握数据不平衡对这些模型的影响,并了解解决不平衡数据挑战的各种策略的高级概述。
技术要求
在本章中,我们将利用常见的库,如numpy、scikit-learn和PyTorch。PyTorch是一个开源的机器学习库,用于深度学习任务,因其灵活性和易用性而近年来越来越受欢迎。
您可以使用pip或conda安装PyTorch。访问官方 PyTorch 网站(pytorch.org/get-started/locally/)以获取适合您系统配置的相应命令。
本章的代码和笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/main/chapter06。
深度学习简介
深度学习是机器学习的一个子领域,它专注于具有多层(深度模型通常有三层或更多,包括输入、输出和隐藏层)的人工神经网络。这些模型在各种应用中展示了非凡的能力,包括图像和语音识别、自然语言处理和自动驾驶。
“大数据”(大量结构化或非结构化数据,通常难以用传统的数据处理软件管理)问题的普遍存在,极大地得益于图形处理单元(GPU)的发展,这些 GPU 最初是为图形处理设计的。
在本节中,我们将简要介绍深度学习的基石,仅讨论与深度学习中数据不平衡问题相关的内容。对于更深入的介绍,我们建议参考更专业的深度学习书籍(请参阅参考文献部分列出的[1]和[2])。
神经网络
神经网络是深度学习的基础。受人类大脑的结构和功能启发,神经网络由层状组织的相互连接的节点或人工神经元组成。
PyTorch 的核心数据结构是张量。张量是多维数组,类似于 NumPy 数组,但具有 GPU 加速支持和自动微分的能力。可以使用 PyTorch 函数创建、操作和执行张量,如下所示:
import torch
# Create a tensor with specific data
t1 = torch.tensor([[1, 2], [3, 4]])
# Create a 2x3 tensor filled with zeros
t2 = torch.zeros(2, 3)
# Create a 2x3 tensor filled with random values
t3 = torch.randn(2, 3)
将 CUDA 张量与 CPU 密集型张量混合会导致错误:
# moving the tensor to GPU (assuming a GPU with CUDA support is available)
x = torch.rand(2,2).to("cuda")
y = torch.rand(2,2) # this tensor remains on the CPU
x+y # adding a GPU tensor & a CPU tensor will lead to an error
这是输出:
--------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
----> 1 x+y
RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu!
autograd 是 PyTorch 中一个强大的功能,它允许对张量操作进行自动微分。这在神经网络中的反向传播(稍后讨论)中特别有用:
# Create a tensor with autograd enabled
x = torch.tensor([[1., 2.], [3., 4.]], requires_grad=True)
# Perform operations on the tensor
y = x + 2
z = y * y * 3
# Compute gradients with respect to the input tensor x
z.backward(torch.ones_like(x))
# Display the gradients
print(x.grad)
我们将看到以下输出:
tensor([[18., 24.],
[30., 36.]])
让我们回顾一下这段代码是如何工作的。它创建了一个 PyTorch 张量 x,执行一些操作来计算 y 和 z,然后使用 backward() 方法计算 z 关于 x 的梯度。梯度存储在 x.grad 中。操作如下:
-
y = x + 2将x中的每个元素增加 2 -
z = y * y * 3将y中的每个元素平方然后乘以 3
在这种情况下,梯度计算涉及到对这些操作应用链式法则来计算 dz/dx:
dz/dx = dz/dy ⋅ dy/dx
好的,让我们计算这个方程中的每一部分:
-
首先,让我们计算 dz/dy:
dz/dy = 2 * y * 3 = 6 * y = 6 *(x + 2)
-
dy/dx = 1 因为 y 是 x 的线性函数:
-
最后,让我们计算 dz/dx:
dz/dx = dz/dy ⋅ dy/dx = 6 ⋅ (x + 2) ⋅ 1 = 6 ⋅ (x + 2)
给定 x = [[1., 2.], [3., 4.]],当我们打印 x.grad 时的输出应该是以下内容:
tensor([[18., 24.],
[30., 36.]])
输出张量对应于在特定值 x 处 z 关于 x 的评估梯度。
感知器
感知器是神经网络中最基本的单元。它是一个简单的线性分类器,它接受一组输入值,将它们乘以相应的权重,加上一个偏置项,将结果相加,并应用激活函数以产生输出:

图 6.2 – 一个简单的感知器
那么,激活函数是什么呢?
激活函数
激活函数将非线性引入神经网络,并帮助确定神经元的输出。常见的激活函数包括 sigmoid、tanh、线性整流单元(ReLU)和 softmax。这些函数使网络能够学习数据中的复杂非线性模式。
让我们深入了解人工神经网络的各种组成部分。
层
一个神经网络通常由输入层、一个或多个隐藏(中间)层和输出层组成。输入层接收原始数据,而隐藏层执行各种转换,输出层产生最终结果。
神经网络中的层数称为网络的深度,而每层的神经元数量称为网络的宽度。
与直觉相反,尽管深度和宽度更大的神经网络在训练数据中具有学习更复杂模式和表示的更多能力,但它们并不一定比浅层和窄网络对不平衡数据集更鲁棒。
深度和宽度更大的网络更容易过拟合,尤其是在不平衡数据集的背景下,因为大网络可以记住多数类别的模式,这可能会妨碍少数类别的性能。
前馈神经网络
前馈神经网络是一种具有单向信息流的神经网络,从输入层开始,通过任何隐藏层,最终到达输出层。
由于没有反馈回路或层与层之间的循环连接,因此得名前馈。这些网络广泛用于图像分类、回归和其他任务。
PyTorch提供了nn模块用于创建和训练神经网络——nn.Module类是所有神经网络模块的基类。以下代码片段定义了一个简单的前馈神经网络:
import torch
import torch.optim as optim
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = torch.nn.Linear(4, 10)
self.fc2 = torch.nn.Linear(10, 3)
def forward (self, x):
x = torch.relu(self.fc1(x))
x = self.fc2(x)
return x
# Create an instance of the network
net = Net()
# Define a loss function and an optimizer
criterion = torch.nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.01)
损失函数量化了预测输出和目标值之间的差异。常见的损失函数包括均方误差(MSE)、交叉熵和 Hinge 损失。
最常用的损失函数CrossEntropyLoss,我们在前面的代码片段中使用过,在类别不平衡的数据集中往往倾向于多数类别的示例。这是因为多数类别的示例数量显著多于少数类别的示例。因此,损失函数偏向多数类别,并且未能充分考虑到少数类别的错误。
我们将在第八章“算法级深度学习技术”中学习更多关于针对类别不平衡问题更适合的损失函数修改。
多层感知器
多层感知器(MLP)是一个前馈神经网络,由输入层、一个或多个隐藏层和输出层组成。每一层都是全连接的,这意味着一个层中的每个神经元都与下一层中的所有神经元相连。MLPs 可用于各种任务,包括分类、回归和特征提取。它们特别适合于输入数据可以表示为固定大小向量的问题,例如表格数据或展平的图像。图 6.3展示了具有两个输入节点、两个隐藏节点和一个输出节点的多层感知器,使用指定的权重 w1 到 w6:

图 6.3 – 多层感知器
训练神经网络
训练神经网络涉及找到最小化损失函数的最优权重和偏置。在此过程中使用了两个基本算法 – 梯度下降和反向传播:
-
梯度下降:梯度下降是一种优化算法,通过迭代更新权重和偏置来最小化损失函数。
-
反向传播:反向传播是训练神经网络的关键算法。它使用链式法则(一种寻找复合函数导数的方法)计算损失函数相对于每个权重的梯度,有效地从输出层计算梯度到输入层。
训练神经网络涉及遍历数据集,将数据输入网络,计算损失,并使用反向传播更新权重,如图所示:
# Example dataset
inputs = torch.randn(100, 4)
targets = torch.randint(0, 3, (100,))
# Train for 100 epochs
for epoch in range(100):
# Forward pass
outputs = net(inputs)
# Compute loss
loss = criterion(outputs, targets)
# Backward pass and optimization
# reset gradients of model parameters
optimizer.zero_grad()
# compute the gradient of loss w.r.t model parameters
loss.backward()
# update model parameters based on computed gradients
optimizer.step()
# Print the loss for this epoch
print(f"Epoch {epoch + 1}, Loss: {loss.item()}")
此代码生成以下输出,显示了每个 epoch 的损失(注意,随着训练的进行,损失会下降):
Epoch 1, Loss: 1.106634497642517
Epoch 2, Loss: 1.1064313650131226
…
Epoch 100, Loss: 1.026018738746643
在前面的代码片段中,包含loss.backward()的行对应于反向传播过程。optimizer.zero_grad()和optimizer.step()这两行都代表梯度下降过程的一步。optimizer.zero_grad()清除上一步的旧梯度(否则它们会累积),而optimizer.step()执行参数(权重和偏置)的实际更新,以减少损失。
以下流程图描述了 PyTorch 中的训练逻辑:

图 6.4 – PyTorch 训练逻辑流程图
过拟合是机器学习中常见的问题,模型在训练数据上表现良好,但无法泛化到未见数据。
欠拟合发生在模型过于简单时(想想当我们需要决策树回归模型时却使用了线性回归模型的情况)并且无法捕捉数据中的潜在模式。
过拟合和欠拟合都会导致在未见数据上的性能不佳。
正则化
正则化技术通过为模型必须满足的约束条件添加额外的限制来帮助减轻过拟合。如果 L 是损失函数,最常用的正则化类型是 L1 和 L2,它们将惩罚项添加到损失函数中,从而阻止过于复杂的模型:
L1 损失 = L + λ∑i=1n|w_i|
L2 损失 = L + λ∑i=1nw_i²
在这里,λ是正则化强度,w_i 是模型参数(权重)。
还有其他几种正则化技术:
-
PyTorch中的torch.nn.Dropout类实现了 dropout 功能,并接受一个参数,即 dropout 率(神经元被置零的概率)。 -
torch.nn.BatchNorm1d和torch.nn.BatchNorm2d是一种用于改进深度神经网络训练的技术。它通过调整每个层的均值和标准差来规范化每个层的输入,这有助于稳定和加速训练过程,允许使用更高的学习率并减少对权重初始化的敏感性。 -
早期停止:早期停止是一种在神经网络训练过程中防止过拟合的技术。它涉及监控模型在验证集上的性能,并在性能停止提高或开始下降时停止训练过程。这有助于找到模型在没有需要记住训练集的情况下,对新数据泛化良好的点:

图 6.5 – 当验证损失随着更多轮次增加时应用早期停止技术
学习率对数据不平衡的影响
我们已经看到,梯度下降在负梯度的方向上迈出一步——这一步的大小称为学习率。
选择合适的学习率对于在数据不平衡的数据集上运行的模型至关重要。
学习率的选择应确保模型能够有效地从多数类和少数类中学习模式。监控训练和验证损失以及其他评估指标,如精确度、召回率和 F1 分数,可以帮助微调学习率。
高学习率意味着模型在每次训练迭代中更新权重更为剧烈。当应用于少数类时,这些快速、大的更新可能导致模型跳过最小化该类损失的优化权重集。通常,使用自适应学习率[3][4]或甚至类特定学习率[5]来确保模型从多数类和少数类中有效地学习是有益的。
现在,让我们回顾一些对图像和文本领域非常有用的特定类型的神经网络。
使用卷积神经网络进行图像处理
卷积神经网络(CNNs)是一种为图像和视频处理任务设计的深度学习模型。它由卷积层、池化层和全连接层组成:
-
卷积层将滤波器应用于输入数据,学习检测局部特征,如边缘或纹理。这些滤波器在输入数据上滑动,执行逐元素乘法和求和结果,从而创建一个特征图,表示特定特征的存在。
-
池化层,如最大池化或平均池化,减少了数据的空间维度,聚合信息并降低计算复杂度。这些层有助于构建对输入数据中小的平移和扭曲的不变性。
-
完全连接层处理卷积层和池化层提取的高级特征,以进行预测。完全连接层是传统的神经网络层,其中每个神经元都与前一层的每个神经元相连。
卷积神经网络(CNNs)倾向于过度拟合少数类别,这与通常欠拟合这些少数类别的传统机器学习算法形成对比[6]。这里的直觉是,CNNs 具有多层和大量参数,旨在捕捉复杂模式。此外,由于对数据有很强的需求,CNNs 可以从数据中学习到复杂的细节。当面对不平衡数据时,CNNs 可能会过度关注少数类别,本质上“记忆”了少数类别的实例。
我们将在第八章(B17259_08.xhtml#_idTextAnchor235)的算法级深度学习技术部分,类依赖温度(CDT)损失部分中更详细地讨论这个问题。
计算机视觉领域中的不平衡问题可以按照图 6.6所示进行分类[7]:

图 6.6 – 计算机视觉中不平衡问题的分类(改编自[7])
使用自然语言处理进行文本分析
自然语言处理(NLP)是人工智能的另一个分支,它帮助计算机理解和分析人类语言,以提取见解和组织信息。图 6.7展示了基于数据复杂度级别的文本不平衡问题的分类,而图 6.8展示了基于一些流行的 NLP 应用领域的分类:

图 6.7 – 基于文本数据形式的 NLP 中不平衡问题的分类

图 6.8 – 基于应用的 NLP 中不平衡问题的分类
在基本概念介绍完毕后,让我们看看数据不平衡如何影响深度学习模型。
深度学习中的数据不平衡
虽然许多使用表格数据的经典机器学习问题仅限于二进制类别,并且关注于预测少数类别,但在深度学习经常应用的领域中,这并不是常态,尤其是在计算机视觉或 NLP 问题中。
即使是像 MNIST(包含 0 到 9 的灰度图像的手写数字集合)和 CIFAR10(包含 10 个不同类别的彩色图像)这样的基准数据集也有 10 个类别需要预测。因此,我们可以说在应用深度学习模型的领域中,多类别分类是典型的。
这种数据倾斜或不平衡可能会严重影响模型的表现。我们应该回顾一下在第一章,《机器学习中的数据不平衡介绍》中讨论的典型不平衡类型。为了模拟现实世界的数据不平衡场景,文献中通常研究两种类型的不平衡:
- 步长不平衡:所有少数类别和所有多数类别的示例数量相同或几乎相同:

图 6.9 – 步长不平衡
- 长尾不平衡:不同类别的示例数量遵循指数衰减。图表通常向左或向右有长长的尾巴:

图 6.10 – 长尾不平衡
通常情况下,当比较两个数据集时,不平衡比率较高的数据集可能得到较低的 ROC-AUC 分数[8]。在不平衡数据集中可能存在类别重叠、标签噪声和概念复杂性的问题[9][10]。这些问题可能会严重影响深度学习模型的表现:
-
类别重叠:当不同类别的实例在特征空间中彼此靠近时,就会发生类别重叠。深度学习模型通常依赖于复杂的决策边界来区分类别。当不同类别的实例在特征空间中靠近时,这在不平衡数据集中很常见,多数类别可能会主导这些决策边界。这使得深度学习模型准确分类少数类别实例变得特别具有挑战性。
-
噪声标签:噪声标签指的是数据集中具有错误或不明确类别标签的实例。在不平衡数据集中,噪声标签可能会不成比例地影响少数类别,因为模型有较少的实例可以学习。深度学习模型对数据非常渴求,并且对训练数据的质量高度敏感。这可能导致深度学习模型泛化能力差,影响它们在新、未见过的数据上的表现。
-
概念复杂性:概念复杂性是指根据给定的特征区分类别的固有难度。深度学习模型擅长捕捉数据中的复杂模式。然而,在不平衡数据集中特征与类别标签之间关系的复杂性可能会使得这些模型难以有效地学习少数类别。少数类别可用的实例数量有限,这通常会使问题更加严重。
数据不平衡对深度学习模型的影响
让我们再回顾一下数据不平衡如何影响深度学习模型与经典机器学习模型相比:
- 模型敏感性:随着数据集不平衡率的增加,深度学习模型的表现可能会受到显著影响(图 6**.11):

图 6.11–在具有固定少数类数量的不平衡版本的 CIFAR-10 上的模型性能(改编自[8])
-
特征工程:经典机器学习模型通常需要手动特征工程,这有助于通过创建新特征或转换现有特征来突出显示少数类,从而解决数据不平衡问题。在深度学习模型中,特征工程通常通过学习过程自动执行,这使得它对人类干预来解决不平衡问题不那么依赖。
-
处理不平衡的技术:在经典机器学习中,处理数据不平衡的标准技术包括重采样(对少数类进行过采样或对多数类进行欠采样)、生成合成样本(例如,使用合成少数类过采样技术(SMOTE))和使用代价敏感学习(为不同类别分配不同的误分类成本)。
一些来自经典机器学习的技术也可以应用于深度学习,但已经开发出专门针对深度学习模型的方法。这些包括迁移学习(利用预训练模型从不平衡数据中学习)、使用焦点损失(一个关注难以分类示例的损失函数)以及采用数据增强技术来生成更多样化和平衡的训练数据。这些数据增强技术将在第七章,数据级深度学习方法中详细讨论。
-
模型可解释性:经典机器学习模型通常更可解释,这有助于我们了解数据不平衡对模型决策过程的影响。另一方面,由于缺乏可解释性,深度学习模型通常被称为“黑盒”。这种缺乏可解释性使得理解模型如何处理不平衡数据以及它是否在学习有意义的模式或仅仅是记忆多数类变得更加困难。
-
训练数据大小:深度学习模型通常需要大量的训练数据以实现最佳性能。在数据严重不平衡的情况下,收集足够的数据以供少数类使用可能更具挑战性,这可能会阻碍深度学习模型的表现。此外,如果有一个大型数据集可用,那么在大量数据中找到少数类的实例的可能性更大。相比之下,在较小的数据集中,少数类可能根本不会出现!另一方面,经典机器学习算法通常可以在较小的数据集上实现相当不错的性能,这在处理不平衡数据时是一个优势。
-
深度(层数)对不平衡数据问题训练的深度学习模型的影响:更多层的优点是提高了学习数据中复杂模式和特征的能力,以及提高了模型的泛化能力。增加更多层的缺点可能是模型过拟合和梯度消失或爆炸问题恶化(随着深度的增加,反向传播中的梯度可以变得非常小(消失)或非常大(爆炸),这使得训练模型变得具有挑战性)。这总结在图 6.12中:

图 6.12 – 总结深度对深度学习模型的影响
总体而言,深度对深度学习模型的影响随着数据和模型架构的不同而变化,需要经验评估。
-
过采样和欠采样:Mateusz Buda[8]撰写的题为卷积神经网络中类别不平衡问题的系统研究的研究,详细考察了类别不平衡如何影响 CNN 的性能。该研究使用了三个著名的数据集——MNIST、CIFAR-10 和 ImageNet——并使用了 LeNet-5、All-CNN 和 ResNet-10 等模型进行实验。他们的主要发现如下:
-
在几乎所有分析案例中,过采样都被证明是减轻类别不平衡的最有效技术
-
当消除不平衡时,过采样更有效,而欠采样在仅部分减少不平衡时产生更好的结果
-
与一些传统的机器学习算法相反,当应用过采样时,CNN 没有过拟合
-
在某些情况下,欠采样与过采样表现相当,即使使用更少的数据
-
欠采样通常与基线相比表现较差,从未显示出比过采样明显的优势
-
如果重点是正确分类少数类别的示例,欠采样可能比过采样更可取
如果数据太多,欠采样可能是节省时间、资源和训练成本的首选或唯一选项
-
-
阈值调整:我们在第五章中详细讨论了阈值调整,成本敏感学习。作为复习,决策阈值是一个确定分类问题中不同类别或结果之间边界的值。Johnson 等人[11]的论文强调了最佳阈值与少数类大小呈线性相关(即少数类大小越低,阈值越低)。他们使用深度学习模型在 CMS 医疗保险数据[12]上进行了两个和四个隐藏层的欺诈检测训练和测试。默认阈值通常会导致分类效果不佳,特别是对于少数类,突出了基于验证集进行阈值调整的必要性。
关于本书后半部分使用的数据集的说明
在本书的剩余部分,我们将主要使用 MNIST 和 CIFAR10-LT(“LT”代表“长尾”)数据集的不平衡版本进行训练:
• MNIST:一个包含 0 到 9 数字的 10 个类别的灰度图像数据集。它由 60,000 个训练图像和 10,000 个测试图像组成,每个图像为 28 像素 x 28 像素。与 CIFAR-10 相比,训练/测试速度更快。
• CIFAR-10:用于物体识别,这个彩色图像数据集也有 10 个类别。它包括 50,000 个训练图像和 10,000 个测试图像,每个图像为 32 像素 x 32 像素。
尽管训练集是不平衡的,但相应的测试集中每个类别的示例数量是相等的。这种平衡测试集方法提供了几个好处:
• 可比性:平衡的测试集允许在不同类别和模型之间进行无偏比较
• 可重复性和可再现性:使用如 MNIST 和 CIFAR-10 等简单数据集确保了代码执行和理解上的便捷性
• 效率:较小的数据集能够加快迭代速度,使我们能够在合理的时间内尝试、测试和重试运行代码
• 与研究的契合度:这种方法与大多数关于长尾学习和不平衡数据集的研究是一致的,提供了一个通用的比较框架
下一节将为我们概述处理数据不平衡的深度学习策略。我们还将看到,最初为经典机器学习技术开发的处理不平衡数据集的各种技术如何轻松扩展到深度学习模型。
处理数据不平衡的深度学习技术概述
与本书的前半部分类似,我们当时专注于经典机器学习技术,主要类别通常包括采样技术、成本敏感型技术、阈值调整技术,或这些技术的组合:
-
样本技术包括对多数类进行欠采样或对少数类数据进行过采样。数据增强是计算机视觉问题中的一个基本技术,用于增加训练集的多样性。虽然它不是直接针对解决类别不平衡的过采样方法,但数据增强确实具有扩展训练数据的效果。我们将在第七章的数据级深度学习方法中更详细地讨论这些技术。
-
成本敏感型技术通常涉及以某种方式更改模型损失函数,以适应少数类样本分类错误的较高成本。一些标准损失函数,如 PyTorch 中的
CrossEntropyLoss,支持权重参数以适应这些成本。我们将在第八章的算法级深度学习技术中详细介绍其中许多,包括几个自定义损失函数。 -
混合深度学习技术整合了数据级和算法级方法。这种融合允许更细致和有效的解决方案来处理类别不平衡。我们将在第九章,混合深度学习方法中讨论这一点。
-
阈值调整技术应用于模型训练后产生的分数。这些技术可以帮助调整阈值,从而使模型指标,例如 F1 分数或几何平均数,得到优化。这在第五章**,成本敏感学习*中已有讨论:

图 6.13 – 深度学习技术的分类
从本章开始,我们将不会使用深度学习方法处理表格数据,因为像 XGBoost、LightGBM 和 CatBoost 这样的经典模型在处理这种结构化数据时往往表现良好。几项研究([13]和[14])表明,在涉及表格数据的监督学习任务中,传统机器学习模型通常优于深度学习模型。然而,XGBoost 模型和深度学习模型的集成可以优于单独的 XGBoost 模型[13]。因此,使用表格数据集的任务仍然可以从深度学习模型中受益。深度学习模型在表格数据上的性能赶上经典模型可能只是时间问题。尽管如此,当我们使用深度学习模型时,我们将重点关注视觉和 NLP 问题。
这就结束了我们对使用深度学习模型处理不平衡数据集的各种技术的高级讨论。
多标签分类
多标签分类是一种分类任务,其中每个实例可以同时分配给多个类别或标签。换句话说,一个实例可以属于多个类别或具有多个属性。例如,一部电影可以属于多个类型,如动作、喜剧和浪漫。同样,一张图片可以包含多个对象(图 6**.14):

图 6.14 – 显示预测概率的多标签图像分类
但它与多类分类有何不同?多类分类是一种分类任务,其中每个实例只能分配给一个类别或标签。在这种情况下,类别或类别是互斥的,这意味着一个实例只能属于一个类别。例如,手写数字识别任务将是多类的,因为每个数字只能属于一个类别(0-9)。
总结来说,多标签分类和多类分类的主要区别在于,在多标签分类中,实例可以有多个标签。相比之下,在多类分类中,实例只能有一个标签。这总结在图 6**.15中:

图 6.15 – 多标签分类和多类分类的区别
许多现实世界的问题本质上是多标签的,并且存在类别不平衡。深度学习模型在这里特别有用,尤其是当涉及的数据是无结构化的,如图像、视频或文本时。
在多标签数据集(MLDs)中,可能有成十或上百个标签,每个实例可以与这些标签的子集相关联。标签种类越多,某些标签出现频率非常低的可能性就越大,从而导致显著的失衡。
多标签分类中的数据不平衡可能发生在多个层面[15]:
-
标签内部的不平衡:每个标签中负实例和正实例之间可能存在很大的差异,这可能导致分类模型在少数类别上遇到困难
-
标签之间的不平衡:标签中正实例的分布不均,导致少数类别的性能较差
-
标签集之间的不平衡:由于标签稀疏性,标签集(各种标签的组合)的频率稀疏,这使得分类模型难以有效学习
图 6**.16总结了在分类多标签数据集时这些不平衡类型:

图 6.16 – 多标签数据集中的不平衡类型
数据不平衡处理方法和度量标准与用于不平衡多类分类方法的方法类似。典型的方法包括重采样方法、集成方法和成本敏感方法。
最直接的方法是将多标签数据集转换为多类数据集,然后应用各种重采样方法,如随机过采样、随机欠采样、编辑最近邻(ENN)等。文献中已经使用了类似的策略,将成本敏感方法适应到多标签分类问题。
摘要
深度学习在许多领域已成为必要的技术,从计算机视觉和自然语言处理到医疗保健和金融。本章简要介绍了深度学习的核心概念和技术。我们讨论了 PyTorch、深度学习的基础知识、激活函数和数据不平衡挑战。我们还对接下来几章将要讨论的各种技术进行了鸟瞰。
理解这些基础知识将使你具备探索更高级主题和应用的知识,并最终为不断发展的深度学习世界做出贡献。
在下一章中,我们将探讨数据层面的深度学习方法。
问题
-
将数据不平衡处理方法从经典机器学习模型迁移到深度学习模型中面临哪些挑战?
-
如何创建一个不平衡版本的 MNIST 数据集?
-
使用 MNIST 数据集训练一个具有不同数据不平衡程度的 CNN 模型。记录模型在固定测试集上的整体准确率。绘制整体准确率随训练数据不平衡程度增加的变化情况。观察整体准确率是否随着训练数据的不平衡而下降。
-
使用深度学习模型进行随机过采样有什么目的?
-
在处理有限或不平衡数据时,有哪些数据增强技术可以应用?
-
在处理数据不平衡时,欠采样是如何工作的,以及它的局限性是什么?
-
为什么确保数据增强技术保留数据集的原始标签很重要?
参考文献
-
A. W. Trask, Grokking Deep Learning (Manning, Shelter Island, NY, 2019)。
-
F. Chollet, 使用 Python 进行深度学习。Manning Publications,2021 年。
-
Y. Cui, M. Jia, T.-Y. Lin, Y. Song, 和 S. Belongie, “基于有效样本数的类平衡损失”,第 10 页。
-
K. Cao, C. Wei, A. Gaidon, N. Arechiga, 和 T. Ma, 具有标签分布感知边缘损失的平衡学习数据集 [在线]。可在
proceedings.neurips.cc/paper/2019/file/621461af90cadfdaf0e8d4cc25129f91-Paper.pdf获取。 -
R. Jantanasukon 和 A. Thammano, 在分类问题中处理不平衡数据的自适应学习率。在 2021 年数字艺术、媒体和技术联合国际会议以及 ECTI 北方分会电气、电子、计算机和电信工程会议,泰国差安,IEEE,2021 年 3 月,第 229–232 页,doi: 10.1109/ECTIDAMTNCON51128.2021.9425715。
-
H.-J. Ye, H.-Y. Chen, D.-C. Zhan, 和 W.-L. Chao, 在不平衡深度学习中识别和补偿特征偏差。arXiv,2022 年 7 月 10 日。访问日期:2022 年 12 月 14 日。[在线]。可在
arxiv.org/abs/2001.01385获取。 -
V. Sampath, I. Maurtua, J. J. Aguilar Martín, 和 A. Gutierrez, 关于计算机视觉任务中不平衡问题的生成对抗网络综述。J Big Data,第 8 卷,第 1 期,第 27 页,2021 年 12 月,doi: 10.1186/s40537-021-00414-0。
-
M. Buda, A. Maki, 和 M. A. Mazurowski, 关于卷积神经网络中类不平衡问题的系统研究。Neural Networks,第 106 卷,第 249–259 页,2018 年 10 月,doi: 10.1016/j.neunet.2018.07.011。
-
K. Ghosh, C. Bellinger, R. Corizzo, B. Krawczyk, 和 N. Japkowicz, 在深度学习中类不平衡和概念复杂性的综合影响。arXiv,2021 年 7 月 29 日。访问日期:2023 年 3 月 28 日。[在线]。可在
arxiv.org/abs/2107.14194获取。 -
K. Ghosh, C. Bellinger, R. Corizzo, B. Krawczyk, 和 N. Japkowicz, 在深度学习中类不平衡和概念复杂性的综合影响. arXiv,2021 年 7 月 29 日。访问时间:2023 年 3 月 28 日。[在线]。可在
arxiv.org/abs/2107.14194获取。 -
J. M. Johnson 和 T. M. Khoshgoftaar, 使用神经网络进行医疗保险欺诈检测. 大数据杂志,第 6 卷,第 1 期,第 63 页,2019 年 12 月,doi: 10.1186/s40537-019-0225-0.
-
医疗保险欺诈与滥用:预防、检测和报告. 医疗保险与医疗补助服务中心。2017。
www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/MLN-Publications-Items/MLN4649244. -
R. Shwartz-Ziv 和 A. Armon, 表格数据:深度学习并非一切所需. arXiv,2021 年 11 月 23 日。访问时间:2023 年 4 月 10 日。[在线]。可在
arxiv.org/abs/2106.03253获取。 -
V. Borisov, T. Leemann, K. Sessler, J. Haug, M. Pawelczyk, 和 G. Kasneci, 深度神经网络和表格数据:综述. IEEE 交易神经网络与学习系统,第 1-21 页,2022 年,doi: 10.1109/TNNLS.2022.3229161.
-
A. N. Tarekegn, M. Giacobini, 和 K. Michalak, 不平衡多标签分类方法综述. 模式识别,第 118 卷,第 107965 页,2021 年 10 月,doi: 10.1016/j.patcog.2021.107965.
第七章:数据级别深度学习方法
在前面的章节中,你已经了解了各种采样方法。在这本书中,我们将这些方法统称为数据级别方法。这些方法包括随机欠采样、随机过采样、NearMiss 和 SMOTE。我们还探讨了这些方法如何与经典机器学习算法协同工作。
在本章中,我们将探讨如何将熟悉的采样方法应用于深度学习模型。深度学习为这些方法提供了进一步改进的独特机会。我们将深入研究将深度学习与过采样和欠采样相结合的优雅技术。此外,我们还将学习如何使用基本神经网络实现各种采样方法。我们还将介绍动态采样,这涉及到在多个训练迭代中调整数据样本,并为每个迭代使用不同的平衡比率。然后,我们将学习使用一些数据增强技术来处理图像和文本。我们将通过强调来自各种其他数据级别技术的关键要点来结束本章。
本章将涵盖以下主题:
-
准备数据
-
深度学习模型的采样技术
-
文本分类的数据级别技术
-
关于其他数据级别深度学习方法和其关键思想的讨论
将处理数据不平衡的方法从在经典机器学习模型上表现良好的方法迁移到深度学习领域并不总是直接的。深度学习模型与经典机器学习模型的主要挑战和机遇不同,主要是因为这些模型必须处理的数据类型不同。虽然经典机器学习模型主要处理表格和结构化数据,但深度学习模型通常处理非结构化数据,如图像、文本、音频和视频,这与表格数据有根本性的不同。
我们将讨论各种处理计算机视觉中不平衡问题的技术。在本章的第一部分,我们将重点关注各种技术,例如采样和数据增强,以处理在图像和文本数据上训练卷积神经网络时的类别不平衡问题。
在本章的后半部分,我们将讨论可以应用于文本问题的常见数据级别技术。许多计算机视觉技术也可以成功地应用于自然语言处理问题。
技术要求
与前几章类似,我们将继续使用常见的库,如torch、torchvision、numpy和scikit-learn。我们还将使用nlpaug来实现 NLP 相关的功能。本章的代码和笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/main/chapter07。您可以通过点击章节笔记本顶部的在 Colab 中打开图标或通过从colab.research.google.com启动它,使用笔记本的 GitHub URL 来打开 GitHub 笔记本。
准备数据
在本章中,我们将使用经典的 MNIST 数据集。该数据集包含 28 像素 x 28 像素的手写数字图像。对于模型的任务是接收一个图像作为输入并识别图像中的数字。我们将使用流行的深度学习库PyTorch来展示算法。现在让我们准备数据。
该过程的第一步将是导入库。我们需要 NumPy(因为我们处理numpy数组)、torchvision(用于加载 MNIST 数据)、torch、random和copy库。
接下来,我们可以从torchvision.datasets下载 MNIST 数据。torchvision库是PyTorch框架的一部分,它包含用于计算机视觉任务的数据库、模型和常见图像转换器。以下代码将从该库下载 MNIST 数据集:
img_transform = torchvision.transforms.ToTensor()
trainset = torchvision.datasets.MNIST(\
root='/tmp/mnist', train=True,\
download=True, transform=img_transform)
testset = torchvision.datasets.MNIST(root='/tmp/mnist',\
train=False, transform=img_transform)
一旦从torchvision下载了数据,我们就可以将其加载到PyTorch的Dataloader实用工具中,它创建数据批次并提供对批次的 Python 风格迭代器。以下代码正是这样做的。在这里,我们为train_loader创建大小为 64 的批次。
train_loader = torch.utils.data.DataLoader(trainset,\
batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=testset,\
batch_size=500)
由于我们对不平衡数据集感兴趣,我们将把我们的 MNIST 数据集,一个平衡数据集,通过删除各个类别的示例转换为它的长尾版本。我们在这里省略了该实现以节省空间;您可以参考章节的 GitHub 仓库以获取详细信息。我们假设您已经创建了由不平衡训练集生成的imbalanced_train_loader类。
图 7.1显示了不平衡 MNIST 数据集中样本的分布:

图 7.1 – 每个数字类示例计数的条形图
接下来,我们将学习如何在PyTorch中创建训练循环。
创建训练循环
在创建训练循环之前,我们应该导入torch.nn和torch.optim包。torch.nn包提供了创建神经网络图的全部构建块,而torch.optim包为我们提供了大多数常见的优化算法。
import torch.nn as nn
import torch.optim as optim
由于我们需要一些超参数,让我们定义它们:
input_size = 28 * 28 # 784
num_classes = 10
num_epochs = 20
learning_rate = 0.01
在设置好超参数之后,我们可以定义train函数,该函数将接受 PyTorch 的数据加载器作为输入,并返回一个拟合数据的模型。为了创建一个训练好的模型,我们需要一个模型、一个损失准则和一个优化器。在这里,我们将使用单层线性神经网络作为模型。你可以根据你的需求设计自己的神经网络架构。对于损失准则,我们将使用CrossEntropyLoss,并且我们将使用随机梯度下降(SGD)作为优化器。
我们将训练模型num_epochs个 epochs。我们将在下一段讨论模型在单个 epoch 中的训练过程。现在,我们将在run_epoch函数中抽象出这部分:
def train(trainloader):
model = nn.Linear(input_size, num_classes)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), learning_rate)
for epoch in range(num_epochs):
run_epoch(trainloader, model, criterion, optimizer, \
total_step, epoch)
return model
在每个 epoch 期间,我们将对整个训练数据进行一次模型训练。如前所述,数据加载器将数据分成多个批次。首先,我们必须将批次中图像的形状与模型的输入维度相匹配。我们将对当前批次进行前向传递,一次计算预测和预测的损失。然后,我们将损失反向传播以更新模型权重:
def run_epoch(
trainloader, model, criterion, optimizer, total_step, epoch
):
for i, (images, labels) in enumerate(trainloader):
# Reshape images to (batch_size, input_size)
images = images.reshape(-1, input_size)
# Forward pass
outputs = model(images)
loss = criterion(outputs, torch.tensor(labels))
# Backward and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
要获得一个训练好的模型,我们现在可以将数据加载器发送到train函数:
model = train(imbalanced_train_loader)
在本章的所有与图像相关的方法中,我们将使用下面详细说明的模型代码。我们将创建一个名为Net的PyTorch神经网络,它具有两个卷积层、一个 dropout 机制和一对全连接层。通过forward函数,模型使用ReLU激活和最大池化无缝集成这些层,操作输入x。结果是计算输出的log_softmax:
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = torch.nn.Dropout2d()
self.fc1 = torch.nn.Linear(320, 50)
self.fc2 = torch.nn.Linear(50, 10)
def forward(self, x):
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(
self.conv2_drop(self.conv2(x)),2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
接下来,让我们分解一些这些术语:
-
在
Net类中,有两个这样的层——conv1和conv2。数字(1, 10)和(10, 20)仅仅是输入和输出通道。术语kernel_size=5意味着使用一个 5 x 5 的网格(或过滤器)来扫描输入。 -
conv2_drop是一个类型为Dropout2d的 dropout 层,专门设计用于 2D 数据(如图像)。 -
fc1和fc2,它们进一步处理卷积层识别出的模式,以进行预测。 -
F.relu是一个激活函数,它向模型引入非线性,使其能够学习复杂的模式 -
F.max_pool2d是一个池化函数,它在保留重要特征的同时减少了数据的空间维度 -
最后,
F.log_softmax是一个常用于分类任务的激活函数,用于为每个类别生成概率。
从本质上讲,Net类定义了一个神经网络,它首先使用卷积层在数据中检测模式,使用 dropout 减少过拟合,然后使用全连接层进行预测。前向方法是一系列操作,定义了数据如何通过这个网络流动。
在下一节中,我们将学习如何使用 oversampling 方法与train函数一起使用。
深度学习模型的采样技术
在本节中,我们将探讨一些采样方法,例如随机过采样和加权采样,用于深度学习模型。然后我们将过渡到数据增强技术,这些技术可以增强模型鲁棒性并减轻数据集的限制。虽然大型数据集对于深度学习来说是理想的,但现实世界的限制往往使得它们难以获得。我们还将探讨一些高级增强技术,如 CutMix 和 MixUp。我们将从标准方法开始,然后讨论这些高级技术。
随机过采样
在这里,我们将应用我们在第二章,“过采样方法”中学习的简单随机过采样,但使用图像数据作为神经网络的输入。基本思想是随机复制少数类的样本,直到每个类别的样本数量相等。这种技术通常比不采样表现得更好。
小贴士
确保训练模型足够多的轮次,以便它已经完全拟合到数据上。欠拟合可能会导致模型性能不佳。
让我们花些时间来处理代码。我们需要遵循几个简单的步骤。首先,我们需要将数据加载器中的数据转换为张量。我们的imbalanced-learn中的RandomOverSampler API 不能直接与数据加载器一起使用:
X=torch.stack(tuple(imbalanced_train_loader.dataset.data))
y=torch.tensor(imbalanced_train_loader.dataset.targets)
我们还需要重塑X张量,以便RandomOverSampler可以处理二维输入,因为我们的每个图像都是一个 28 x 28 的矩阵:
reshaped_X = X.reshape(X.shape[0],-1)
现在,我们可以从imbalanced-learn库中导入RandomOverSampler类,定义一个oversampler对象,并使用它重新采样我们的数据:
from imblearn.over_sampling import RandomOverSampler
oversampler = RandomOverSampler()
oversampled_X, oversampled_y = oversampler.fit_resample(reshaped_X, y)
在重新采样数据后,我们需要将其再次重塑回原始形式:
oversampled_X = oversampled_X.reshape(-1,28,28)
我们现在可以使用过采样数据创建一个新的数据加载器:
balanced_train_dataset = copy.deepcopy(imbalanced_train_dataset)
balanced_train_dataset.targets = torch.from_numpy(oversampled_y)
balanced_train_dataset.data = torch.from_numpy(oversampled_X)
balanced_train_loader = torch.utils.data.DataLoader( \
balanced_train_dataset, batch_size=100, shuffle=True)
最后,我们可以使用新的数据加载器来训练我们的模型。对于这一步,我们可以使用上一节中定义的train函数:
balanced_data_model = train(balanced_train_loader)
这就是我们使用深度学习模型中的随机过采样技术所需做的所有事情。
可以使用类似的策略从imbalanced-learn库中的RandomUnderSampling进行采样。
PyTorch提供了一个WeightedRandomSampler API,它与scikit-learn中的sample_weight参数类似(在scikit-learn估计器的许多 fit 方法中找到,例如RandomForestClassifier和LogisticRegression),具有为训练数据集中的每个样本分配权重的类似目的。我们在第五章,“成本敏感学习”中对class_weight和sample_weight之间的差异进行了详细讨论。
我们可以将weights参数指定给WeightedRamdomSampler,以便它可以根据每个样本的权重自动对批次的示例进行加权。weights参数的值通常是数据集中各种类别的频率的倒数:
class_counts = pd.Series(\
imbalanced_train_loader.dataset.targets.numpy()).value_counts()
class_weights = 1.0/class_counts
class_weights 对于少数类标签比多数类标签更重要。让我们计算weightedRamdomSampler的值:
weightedRandomSampler = \
WeightedRandomSampler(weights=class_weights, \
num_samples=len(imbalanced_train_dataset), \
replacement=True)
weightedRandomSampler_dataloader = \
torch.utils.data.DataLoader(imbalanced_train_dataset,\
sampler=weightedRandomSampler, batch_size=64)
在下一节中,我们将学习如何动态采样数据。
动态采样
动态采样 [1] 是一种高级技术,可以在训练过程中自我调整采样率。它承诺根据问题的复杂性和类别不平衡进行适应,几乎不需要调整超参数。它是你武器库中另一个可以尝试的工具,尤其是在你手头有不平衡的图像数据时,看看它是否比我们在本章中讨论的其他技术给出更好的性能。
动态采样的基本思想是根据它们在特定训练迭代中与先前迭代相比的表现好坏,动态调整各种类别的采样率。如果一个类别的表现相对较差,那么在下一个迭代中该类别将被过度采样,反之亦然。
算法的细节
这些是动态采样的核心组件:
-
实时数据增强:将各种图像变换应用于每个训练批次的图像。这些变换可以是旋转、翻转、调整亮度、平移、调整对比度/颜色、噪声注入等。如前所述,这一步骤有助于减少模型过拟合并提高泛化能力。
-
动态采样方法:在每次迭代中,选择一个样本大小(由某个公式给出),并使用该样本大小训练一个模型。在下一个迭代中,具有较低 F1 分数的类别将以更高的比率进行采样,迫使模型更多地关注先前错误分类的示例。下一个迭代中图像的数量,c j,根据以下公式更新:
UpdateSampleSize( F1 i, c j) = 1 − f1 i, j _ ∑ c k∈ C 1 − f1 i,k × N
这里:
-
f1 i, j 是第 i 次迭代中类 c j 的 F1 分数
-
N = 所有类别中样本数量的平均值
-
对于特定的训练周期,假设我们在验证数据集上对三个类别,A、B和C,得到了以下 F1 分数:
| 类别 | F1 分数 |
|---|---|
| A | 0.1 |
| B | 0.2 |
| C | 0.3 |
表 7.1 – 某个周期后每个类别的样本 F1 分数
这里是我们如何计算每个类别的权重以用于下一个训练周期的计算方法:
权重(类 A)= N * (1 − f1 a) ______________________ (1 − f1 a)+ (1 − f1 b)+ (1 − f1 c) = N * 0.9 _ 0.9 + 0.8 + 0.7
= N * 0.375
(类 B 的)权重 = N 0.8/2.4 =* N0.33*
(类 C 的)权重 = N 0.7/2.4 =* N 0.29*
这意味着我们将以比类 B 和类 C 更高的比率采样类 A,这是有意义的,因为类 A 的性能比类 B 和类 C 弱。
通过迁移学习训练第二个模型,不进行采样,以防止少数类过拟合。在推理时间,模型的输出是两个模型输出的函数。
由于篇幅限制,我们在此省略了关于动态采样算法的更多细节。您可以在本章对应的 GitHub 仓库中找到完整的实现代码。

图 7.2 – 使用各种采样技术的整体模型准确率比较
表 7.2显示了使用各种采样技术(包括基线,其中不进行采样)的每类模型准确率。
| 类别 | 基线 | 加权随机采样器 | 动态采样器 | 随机过采样 | 随机欠采样 |
|---|---|---|---|---|---|
| 0 | 99.9 | 99.0 | 92.4 | 99.1 | 97.2 |
| 1 | 99.7 | 99.2 | 96.8 | 99.2 | 90.7 |
| 2 | 98.5 | 98.3 | 93.5 | 98.5 | 70.8 |
| 3 | 97.3 | 97.4 | 96.8 | 98.3 | 74.4 |
| 4 | 98.3 | 98.0 | 91.9 | 98.6 | 79.6 |
| 5 | 96.2 | 96.0 | 97.3 | 98.1 | 52.8 |
| 6 | 94.5 | 97.6 | 98.7 | 97.3 | 77.6 |
| 7 | 89.7 | 94.7 | 96.5 | 94.1 | 81.1 |
| 8 | 63.3 | 91.5 | 96.9 | 93.0 | 60.2 |
| 9 | 50.7 | 92.6 | 97.7 | 91.8 | 56.5 |
表 7.2 – 使用各种采样技术的每类模型准确率比较(类别的最高值用粗体表示)
下面是一些从结果中得到的见解:
-
在整体性能方面,随机过采样(ROS)表现最佳,而随机欠采样(RUS)表现最差。
-
虽然 ROS 表现最佳,但由于数据克隆,它可能计算成本非常高,这使得它不太适合大型数据集和工业环境。
-
动态采样比 ROS 略差;它在少数类别 6-9 上表现最佳,将是我们的首选选择。然而,由于其复杂性增加,我们的第二选择将是加权随机采样器。
-
基线和加权随机采样器技术在类别间表现稳定;RUS 在大多数类别上表现明显不稳定且表现不佳。
同样,我们可以像使用RandomOverSampler一样应用 SMOTE。请注意,虽然 SMOTE 可以应用于图像,但其对原始数据线性子空间的利用通常受到限制。
这结束了我们对各种采样技术的讨论。在下一节中,我们将专注于专门为图像设计的用于实现更有效过采样的数据增强技术。
视觉数据增强技术
现在,各种定制的增强技术被用于各种类型的数据,如图像、音频、视频,甚至文本数据。在视觉领域,这包括旋转、缩放、裁剪、模糊、向图像添加噪声以及其他许多技术,包括将这些技术一次性以适当的顺序组合在一起。图像数据增强并不是一项最近才出现的创新。例如,一些图像增强技术也可以在 1998 年的 LeNet-5 模型论文[2]中找到。同样,2012 年的 AlexNet 模型[3]使用随机裁剪、翻转、改变 RGB 通道的颜色强度等方法来减少模型训练过程中的错误。
让我们讨论一下为什么数据增强通常很有帮助:
-
在我们数据有限或不平衡的问题中,并不总是能够收集到更多的数据。这可能是因为收集更多数据本身就很困难(例如,在处理信用卡欺诈时等待更多欺诈发生,或者收集卫星图像,我们必须支付卫星运营商的费用,这可能相当昂贵)或者标记数据很困难或昂贵(例如,为了标记医学图像数据集,我们需要领域专家)。
-
数据增强可以帮助减少过拟合并提高模型的总体性能。这种做法的一个动机是,训练集中的属性如光照、噪声、颜色、比例和焦点可能与我们运行推理的真实世界图像中的属性不一致。此外,增强多样化数据集可以帮助模型更好地泛化。例如,如果模型仅在面向右的猫的图像上训练,它可能在面向左的猫的图像上表现不佳。因此,始终应用有效的变换来增强图像数据集是明智的,因为大多数模型在更多样化的数据上都能获得性能提升。
数据增强在计算机视觉任务中如目标检测、分类和分割等方面被广泛使用。它对于自然语言处理任务也非常有用。在计算机视觉领域,有许多开源库帮助标准化各种图像增强技术,而数据增强在自然语言处理工具方面尚未成熟。
虽然有几个流行的开源库用于图像增强,如 imgaug、Facebook 的 AugLy 和 Albumentations,但在这本书中我们将使用 torchvision。作为 PyTorch 生态系统的一部分,它提供了与 PyTorch 工作流程的无缝集成、一系列常见的图像变换、以及预训练模型和数据集,使其成为计算机视觉任务的方便且全面的选项。如果您需要更高级的增强,或者如果速度是一个关注点,Albumentations 可能是一个更好的选择。
我们可以使用 torchvision.transforms.Pad 来向图像边界添加一些填充:
padded_imgs = [torchvision.transforms.Pad(padding=90)(orig_img)]
plot(padded_imgs)

图 7.3 – 应用 Pad 函数到图像的结果
torchvision.transforms.FiveCrop类将给定的图像转换并裁剪成四个角落和中央部分:
(top_left, top_right, bottom_left, bottom_right, center) =\
torchvision.transforms.FiveCrop(size=(100,100))(orig_img)
plot([top_left, top_right, bottom_left, bottom_right, center])

图 7.4 – 应用 FiveCrop 函数到图像的结果
torchvision.transforms.CenterCrop是一个类似的类,用于从中心裁剪图像。
torchvision.transforms.ColorJitter类会改变图像的亮度、饱和度和其他类似属性:
jitter = torchvision.transforms.ColorJitter(brightness=.7, hue=.5)
jitted_imgs = [jitter(orig_img) for _ in range(3)]
plot(jitted_imgs)

图 7.5 – 应用 ColorJitter 函数到图像的结果
GaussianBlur可以为图像添加一些模糊效果:
gaussian_blurrer = \
torchvision.transforms.GaussianBlur(kernel_size=(9,\
11), sigma=(0.1, 5))
blurred_imgs = [gaussian_blurrer(orig_img) for _ in \
range(4)]
plot(blurred_imgs)

图 7.6 – 应用高斯模糊函数到图像的结果
torchvision.transforms.RandomRotation类可以将图像随机旋转一个角度:
rotater = torchvision.transforms.RandomRotation(degrees=(0, 50))
rotated_imgs = [rotater(orig_img) for _ in range(4)]
plot(rotated_imgs)

图 7.7 – 对原始图像(最左侧)进行随机旋转的结果
考虑探索torchvision.transforms类支持的其它图像变换功能,这些功能我们在这里没有讨论。
在训练过程中,Cutout 会随机遮盖输入图像的随机正方形区域。虽然这种技术看起来像是移除了图像中不必要的部分,但重要的是要注意,要被遮盖的区域通常是随机选择的。主要目的是通过确保神经网络不过度依赖给定图像中的任何特定像素集,来强迫神经网络更好地泛化。

图 7.8 – 应用 cutout 函数到图像的结果
🚀 Etsy/Booking/Wayfair 在生产中应用深度学习数据级技术
🎯 问题解决:
Etsy、Booking.com和 Wayfair 利用用户行为来增强个性化。Etsy 专注于基于浏览历史的商品推荐[4],Booking.com定制搜索结果以增加预订[5],而 Wayfair 优化产品图像角度以提高点击率[6]。所有这些都是为了利用数据驱动策略,以改善用户体验和性能。
⚖️ 数据不平衡问题:
Etsy、Booking.com和 Wayfair 在他们的机器学习项目中都遇到了数据不平衡问题。Etsy 面临用户会话的幂律分布,其中大多数用户在一小时窗口内只与几个商品列表互动。Booking.com处理酒店图像中的不平衡类别,卧室和浴室的照片远远多于其他设施如桑拿或乒乓球的照片。Wayfair 遇到了家具真实世界图像的不平衡,大多数图像显示“正面”角度,导致其他角度的性能不佳。这三家公司都必须解决这些不平衡问题,以提高模型性能和公平性。
🎨 数据增强策略:
Etsy、Booking.com和 Wayfair 各自采用了独特的数据增强策略来解决他们各自的挑战。Etsy 使用了图像随机旋转、平移、缩放和颜色对比度变换来增强他们的数据集。Booking.com采用了包括镜像、随机裁剪、仿射变换、纵横比扭曲、颜色操作和对比度增强在内的各种技术。他们通过这些方法将标记数据增加了 10 倍,在训练过程中实时应用扭曲。Wayfair 通过创建 3D 模型生成的合成数据采取了不同的方法,为椅子和沙发的每个 3D 模型生成 100 个视图,从而为训练提供了细粒度的角度信息。
接下来,让我们看看一些更高级的技术,例如 CutMix、MixUp 和 AugMix,这些都是混合样本数据增强(MSDA)技术的类型。
MSDA 是一组涉及混合数据样本以生成增强数据集的技术,用于训练模型(图 7**.9)。

图 7.9 – 常见的 MSDA 技术
CutMix
CutMix [7]是一种图像数据增强技术,其中在训练图像之间剪切和粘贴补丁。具体来说,图像的一部分被另一图像的一部分所取代,如下所示:

图 7.10 – 将 CutMix 函数应用于图像的结果(图像 1 和图像 2)
它旨在鼓励模型做出更多局部化、细粒度的预测,从而提高整体泛化能力。CutMix 还强制在混合区域外进行一致的预测,进一步增强了模型的鲁棒性。
torchvision还提供了内置的 CutMix API,torchvision.transforms.v2.CutMix,因此我们不必从头实现它。
从零开始实现的 CutMix 完整笔记本可以在 GitHub 仓库中找到。
CutMix 在基准数据集(如 CIFAR-10、CIFAR-100 和 ImageNet [7])上通常比传统的增强技术有改进。
MixUp
MixUp [8]通过形成输入及其对应标签的成对组合来创建虚拟训练示例。
如果(x i, y i)和(x j, y j)是数据集 D 中的任意一对图像,其中 x 是图像,y 是其标签,则可以使用以下方程生成混合样本~x , ~y :
~ x = λ x i + (1 − λ) x j
~ y = λ y i + (1 − λ) y j
其中λ是从 Beta 分布中采样的混合因子。Beta 分布是一种灵活的、在区间[0, 1]上定义的连续概率分布。
MixUp 充当正则化器,防止过拟合并增强模型的泛化能力。以下实现通过从 Beta 分布中采样的值进行数据集和目标的洗牌,然后使用加权平均结合它们,从而创建用于增强的混合数据和目标:
def mixup(data, target, alpha):
indices = torch.randperm(data.size(0))
shuffled_data = data[indices]
shuffled_target = target[indices]
lamda = np.random.beta(alpha, alpha)
data = lamda * data + (1 - lamda) * shuffled_data
target = lamda * target + (1 - lamda) * shuffled_target
return data, target

图 7.11 – 应用 MixUp 于图像(图像 1 和图像 2)的结果
在 CIFAR-100 等数据集上,MixUp 被发现与未使用 MixUp 训练的模型相比,在测试精度上提供了显著的提升[8]。
与 CutMix 类似,torchvision提供了一个名为torchvision.transforms.v2.MixUp的内置 API,消除了手动实现的需求。
AugMix
我们迄今为止研究过的增强技术都是固定的增强,但深度学习模型可以记住它们[9],并且它们的性能可能会达到平台期。这就是 AugMix[10]可以发挥作用的地方,因为它通过执行一系列随机增强来生成一组多样化的增强图像。
AugMix 在不改变模型架构的情况下提高了模型的鲁棒性和不确定性。完整的 AugMix 算法还使用了一种特殊的损失函数,但为了简单起见,我们将跳过这一点。
以下代码展示了 AugMix 核心逻辑的简化版本:
from torchvision.transforms import transforms
def simple_augmix(image):
# Our box of magic tricks
magic_tricks = [
transforms.RandomHorizontalFlip(),
transforms.RandomAffine(degrees=30)
# other transforms here
]
# Pick a random number of tricks to use
num_tricks = np.random.randint(0, len(magic_tricks) + 1)
# Create a new picture by mixing transformed ones
new_picture = torch.zeros_like(image)
# Let's use 4 mixed images for our example
for _ in range(4):
transformed_picture = image.clone()
for _ in range(num_tricks):
trick = np.random.choice(magic_tricks)
transformed_picture = trick(transformed_picture)
# Add the transformed picture to our new picture
new_picture += (1/4) * transformed_picture
return new_picture
在函数的末尾,我们通过为四个转换图像中的每一个使用相等权重来组合图像。实际的 AugMix 实现使用 Dirichlet 分布函数来组合图像。Dirichlet 分布是我们在 MixUp 技术中看到的贝塔分布的推广。

图 7.12 – 将 Augmix 函数应用于四幅不同图像的结果
在*图 7**.12 中,顶部行显示原始图像,而底部行显示应用 AugMix 的结果。图像 1 和 3 似乎没有变化,但图像 2 和 4 有明显的改变。
根据 AugMix 论文[10]的描述,在 ImageNet 和 CIFAR 的实验中,AugMix 实现了减少测试误差的同时,提高了对损坏的鲁棒性。
我们不需要从头开始创建 AugMix,因为torchvision提供了一个名为torchvision.transforms.AugMix的内置 API 来完成这个目的。
Remix
标准数据增强技术,如 MixUp 和 CutMix,可能不足以处理类别不平衡,因为它们没有考虑类别标签的分布。Remix[11]解决了在类别不平衡数据集上训练深度学习模型的挑战。
MixUp 和 CutMix 使用相同的混合因子在特征空间和标签空间中结合样本。在处理不平衡数据的情况下,Remix 论文[11]的作者们认为这种方法可能不是最优的。因此,他们提出了将混合因子分开,以便在应用中提供更大的灵活性。通过这样做,可以给少数类分配更大的权重,从而创建出更有利于代表性不足的类的标签。
如果(xi, yi; xi, yj)是数据集 D 中的任意一对图像,可以使用以下方程生成混合样本 xRM,y RM:
x RM = λxxi + (1 − λx)xj
y RM = λyyi + (1 − λy)yj
λx 和λy 是从贝塔分布中采样的混合因子。
这里是一个简化的实现:
def remix_data(inputs, labels, class_counts, alpha=1.0):
lambda_x = np.random.beta(alpha, alpha) if alpha > 0 else 1
# Constants for controlling the remixing conditions.
K = 3
tau = 0.5
# Shuffle the indices randomly.
random_indices = torch.randperm(inputs.size()[0])
# Determine lambda_y values based on class counts and lambda_x.
lambda_y_values = []
for i, j in enumerate(random_indices):
class_count_ratio = (
class_counts[labels[i]] / class_counts[labels[j]]
)
if class_count_ratio >= K and lambda_x < tau:
lambda_y_values.append(0)
else:
lambda_y_values.append(lambda_x)
lambda_y = torch.tensor(lambda_y_values)
# Mix inputs, labels based on lambda_x, lambda_y, and shuffled indices.
mixed_inputs = (
lambda_x * inputs + (1 - lambda_x) * inputs[random_indices, :]
)
mixed_labels = (
lambda_y * labels + (1 - lambda_y) * labels[random_indices]
)
return mixed_inputs, mixed_labels
结合先前技术
可以将这些方法结合起来,为训练数据引入更多的多样性,例如以下内容:
-
CutMix 和 MixUp:这些可以交替使用或同时使用,在图像中创建被其他图像的部分替换的区域,同时像素级混合图像
-
顺序:您可以顺序应用这些技术(例如,首先使用 MixUp 然后使用 CutMix)以进一步多样化增强数据集
当结合这些方法时,重要的是要仔细管理每种方法的概率和强度,从而避免引入过多的噪声或使训练数据与原始分布差异过大。
此外,虽然结合这些方法可能在某些情况下提高模型的鲁棒性和泛化能力,但它也可能使训练更加计算密集和复杂。权衡利弊是至关重要的。
记住,始终在验证集上验证结合增强的有效性,以确保它们对当前任务有益。
让我们使用名为 Fashion-MNIST 的不同数据集的尾部版本来讨论我们刚才讨论的技术(图 7.13)。Fashion-MNIST 是 MNIST 的另一个变体,包含 60,000 个训练图像和 10,000 个测试图像,共有 10 种不同的服装项目,如鞋子、衬衫和连衣裙,每个项目都以 28x28 像素的灰度图像表示。

图 7.13 – 不平衡的 FashionMNIST
图 7.14显示了在 FashionMNIST 不平衡数据集上使用 CutMix、MixUp、两者组合以及 Remix 训练时的整体模型准确率。

图 7.14 – 整体模型准确率(FashionMNIST)
当查看按类别准确率数字时,这些技术的性能差异更为明显(图 7.15)。

图 7.15 – 按类别模型准确率(FashionMNIST 数据集)
根据给定数据,以下是一些关于各种技术的见解,特别是在不平衡数据的情况下:
-
整体性能:Cutmix和Remix在大多数类别中通常提供最高的性能,其次是Mixup和Cutmix+Mixup。基线似乎在总体上效果最差。
-
少数类别的性能:对于标记为“6”的少数类别,所有技术与其他类别相比都表现出相对较低的性能。然而,Mixup和Cutmix+Mixup在基线之上提供了一点点改进。
-
类别间的一致性:Cutmix和Mixup在不同类别间更为一致,除了类别“6”,在那里它们仅略有改进。另一方面,基线显示出显著的变异性,在某些类别(如“0”和“5”)上表现极好,但在其他类别(如“6”)上表现较差。
-
适用于特定类别的技术:Cutmix在标记为“1”和“8”的类别中表现出色,在这些类别中它优于所有其他技术。
Remix在标记为“2”的类别中特别强大,它超越了所有其他技术。
-
复杂性与收益:Cutmix+Mixup与Cutmix或Mixup单独使用相比,并没有带来显著的改进,这引发了额外的计算复杂性是否合理的疑问。
-
泛化性:Cutmix和Mixup似乎是最稳健的技术,在大多数类别中表现出高性能。这些技术在未见过的数据上可能表现良好,并且对于不平衡数据集可能是好的选择。
-
权衡:Cutmix提供了高性能,但可能不是对少数类别最好的选择。Mixup虽然整体上略逊一筹,但在各个类别,包括少数类别中提供了更平衡的性能。
在执行这些图像增强时需要注意以下一些点:
-
我们必须确保数据增强保留原始标签。例如,旋转数字如 6 和 9 在数字识别任务中可能会出现问题。同样,裁剪图像可能会使其标签无效,例如从“猫”图像中移除猫。这在复杂任务中尤为重要,例如在自动驾驶汽车中的图像分割任务,增强可能会改变输出标签或掩码。
-
虽然几何和颜色变换通常会增加内存使用和训练时间,但它们本身并不是问题。尽管颜色变化有时会去除重要细节并影响标签完整性,但智能操作也可能有益。例如,调整颜色空间以模拟不同的光照或相机镜头可以提高模型性能。
为了克服一些增加的内存、时间和成本问题,如前所述,PyTorch 中有一个名为AutoAugment的技术,可以在使用的数据集上自动搜索最佳的增强策略。
🚀 Grab 在生产中使用的深度学习数据级技术
🎯 解决的问题:
Grab 是一家位于东南亚的打车和食品配送公司,面临着匿名化用于其地理标记图像平台 KartaView [12]中收集的图像中的人脸和车牌的主要挑战。这是确保用户隐私所必需的。
⚖️ 数据不平衡问题:
Grab 使用的数据集不平衡,特别是在物体大小方面。较大的感兴趣区域,如面部特写或车牌,代表性不足。这种分布偏差导致模型在检测这些较大物体时性能不佳。
🎨 数据增强策略:
Grab 采用了多角度的数据增强方法来解决不平衡问题:
• 离线增强:他们使用的关键方法之一是“图像视图分割”,即将每个原始图像分割成多个具有预定义属性的“视图”。这对于适应不同类型的图像,如透视、宽视野和 360 度等距图像至关重要。每个“视图”都被视为一个单独的图像,具有其标签,这有助于模型更好地泛化。他们还实施了过采样,以解决具有较大标签的图像数据集中的不平衡。这对于他们的基于锚点的目标检测模型至关重要,因为不平衡正在影响模型识别较大对象的表现。
• 在线增强:他们使用了 YOLOv4 进行目标检测,这允许进行各种在线增强,如饱和度、曝光、色调、翻转和马赛克。
近期,现代技术如自动编码器和对抗网络,特别是生成对抗网络(GANs),在创建合成数据以增强图像数据集方面取得了显著进展。GAN 由两个神经网络组成——生成器,它生成合成数据,和判别器,它评估这些数据的真实性。它们共同工作以创建逼真且高质量的合成样本。GANs 也被应用于生成合成表格数据。例如,它们被用于创建合成医学图像,这显著提高了诊断模型。我们将在本章末尾更详细地探讨这些尖端技术。在下一节中,我们将学习如何将数据级别技术应用于 NLP 问题。
文本分类的数据级别技术
数据不平衡,即数据集中某些类别代表性不足,不仅限于图像或结构化数据领域的问题。在 NLP 中,不平衡的数据集可能导致模型在大多数类别上表现良好,但很可能错误分类代表性不足的类别。为了应对这一挑战,已经设计了多种策略。
在 NLP 中,数据增强可以提高模型性能,尤其是在训练数据有限的情况下。表 7.3对文本数据的不同数据增强技术进行了分类:
| 级别 | 方法 | 描述 | 示例技术 |
|---|---|---|---|
| 字符级别 | 噪声 | 在字符级别引入随机性 | 字符打乱 |
| 基于规则的 | 基于预定义规则的转换 | 大小写 | |
| 词级别 | 噪声 | 随机更改单词 | “cat”到“dog” |
| 同义词 | 用同义词替换单词 | “happy”到“joyful” | |
| 嵌入 | 使用词嵌入进行替换 | “king”到“monarch” | |
| 语言模型 | 利用高级语言模型进行单词替换 | BERT | |
| 短语级别 | 结构 | 改变短语的结构 | 改变单词顺序 |
| 插值 | 合并两个短语的特性 | “The cat sat” + “The dog barked” = “The cat barked” | |
| 文档级别 | 翻译 | 将文档翻译成另一种语言再翻译回原文 | 英文到法文再到英文 |
| 生成式 | 使用模型生成新内容 | GPT-3 |
表 7.3 – 不同数据增强方法的分类(改编自[13])
文本数据增强技术可以根据字符、单词、短语和文档级别进行分类。这些技术从字符打乱到使用 BERT 和 GPT-3 等模型,这一分类法引导我们了解 NLP 数据增强方法。
表 7.3 展示了在 NLP 中使用的各种数据增强方法。我们将根据数据操作的水平来分解这些方法——字符、单词、短语和文档。每个级别都有其独特的方法集,例如在字符级别引入“噪声”或在单词级别利用“语言模型”。这些方法不仅仅是随机变换;它们通常被精心设计,以在引入可变性同时保留文本的语义意义。
使这种分类与众不同的特点是它的多层次方法,这允许更精确地应用数据增强方法。例如,如果你处理的是短文本片段,字符或单词级别的方法可能更合适。另一方面,如果你处理的是较长的文档或需要生成全新的内容,那么文档级别的“生成”技术等方法就派上用场。
在接下来的章节中,我们将探讨一个不平衡的文本分类数据集,并使用它来展示各种数据增强技术。这些方法旨在合成额外的数据,从而增强模型从不平衡信息中学习和泛化的能力。
数据集和基线模型
让我们以 Kaggle 上可用的垃圾邮件文本消息分类数据集为例(www.kaggle.com/datasets/team-ai/spam-text-message-classification)。这个数据集主要用于区分垃圾邮件和合法消息,存在不平衡,大多数是“ham”(合法)消息,少数是“spam”(垃圾)消息。这里省略代码以节省空间。你可以在 GitHub 仓库中找到名为Data_level_techniques_NLP.ipynb的笔记本。
使用基线模型,我们得到了以下结果:
precision recall f1-score support
ham 0.97 1.00 0.98 1216
spam 0.97 0.80 0.88 177
accuracy 0.97 1393
macro avg 0.97 0.90 0.93 1393
weighted avg 0.97 0.97 0.97 1393
随机过采样
处理数据不平衡的一个基本技术是随机过采样,即复制少数类的实例以平衡类别分布。虽然这种方法易于实现并且通常显示出改进的性能,但必须警惕过拟合:
precision recall f1-score support
ham 0.99 0.99 0.99 1216
spam 0.93 0.91 0.92 177
accuracy 0.98 1393
macro avg 0.96 0.95 0.95 1393
weighted avg 0.98 0.98 0.98 1393
随机过采样在整体准确率上略有提升,从 0.97 上升到 0.98。最显著的提升是在spam类别的召回率上,从 0.80 上升到 0.91,表明对垃圾邮件消息的识别能力更好。然而,spam的精确度略有下降,从 0.97 下降到 0.93。
宏观平均 F1 分数也从 0.93 提高到 0.95,这表明模型现在在处理两个类别(ham和spam)方面更加均衡。加权平均指标仍然强劲,进一步证实了模型的整体性能得到了提升,而没有牺牲其正确分类大多数类别(ham)的能力。
类似地,欠采样可以用来减少大多数类别的规模,特别是通过消除完全重复的句子。例如,你可能不需要 500 个“非常感谢!”的副本。然而,具有相似语义但措辞不同的句子,如“非常感谢!”和“非常感谢!”,通常应该保留。可以使用字符串匹配等方法识别完全重复的句子,而具有相似意义的句子可以使用余弦相似度或句子嵌入的 Jaccard 相似度来检测。
🚀 Cloudflare 在生产环境中应用深度学习数据级技术
🎯 待解决的问题:
Cloudflare [14] 旨在增强其 Web 应用防火墙(WAF),以更好地识别恶意 HTTP 请求并防范常见的攻击,如 SQL 注入和跨站脚本(XSS)。
⚖️ 数据不平衡问题:
由于严格的隐私法规和缺乏恶意 HTTP 请求的标记数据,创建高质量的训练 WAF 模型的数据库很困难。样本的异质性也带来了挑战,因为请求以各种格式和编码方式到来。对于特定类型的攻击,样本严重不足,导致数据集不平衡,增加了假阳性或假阴性的风险。
🎨 数据增强策略:
为了解决这个问题,Cloudflare 采用了数据增强和生成技术的组合。这包括以各种方式变异良性内容,生成伪随机噪声样本,以及使用语言模型进行合成数据创建。重点是增加负样本的多样性,同时保持内容的完整性,从而迫使模型考虑更广泛的结构、语义和统计特性,以实现更好的分类。
🚀 模型部署:
他们使用的模型在采用这些数据增强技术后显著改进,增强后的 F1 分数达到了 0.99,而增强前的 F1 分数为 0.61。该模型已通过 Cloudflare 基于签名的 WAF 进行验证,发现其性能相当,因此可以用于生产。
文档级增强
在文档级增强中,整个文档被修改以创建新的示例,以保留文档的更广泛语义上下文或叙事流程。其中一种技术是反向翻译。
反向翻译
反向翻译涉及将一个句子翻译成另一种语言,然后再将其翻译回原文(图 7**.16)。这会产生与原文在句法上不同但语义上相似的句子,提供了一种增强形式。

图 7.16 – 反向翻译技术的演示
我们生成反向翻译的文本并将其附加到原始数据集上。然后,我们使用完整的数据集来训练逻辑回归模型。请注意,这可能是一个耗时的过程,因为翻译模型的二进制文件资源密集。它还可能引入错误,因为某些单词可能无法在语言之间精确翻译。在 GitHub 笔记本中,我们使用了nlpaug库中的BackTranslationAug API [15]。
以下结果展示了测试集上的分类指标。垃圾邮件类别的精确度相较于随机过采样技术有所提升,而召回率略有下降:
precision recall f1-score support
ham 0.98 1.00 0.99 1216
spam 0.96 0.86 0.91 177
accuracy 0.98 1393
macro avg 0.97 0.93 0.95 1393
weighted avg 0.98 0.98 0.98 1393
反向翻译保持了整体准确率 0.98,与随机过采样相似。它略微提高了垃圾邮件的精确度至 0.96,但将召回率降低至 0.86。两种方法都优于基线,反向翻译在垃圾邮件类别中更倾向于精确度而非召回率。
字符和词级增强
让我们简要回顾一下可以应用于 NLP 问题的几个字符和词级增强技术。
简易数据增强技术
简易数据增强(EDA)是一套针对文本数据特定的数据增强技术。它包括诸如同义词替换、随机插入、随机交换和随机删除等简单操作。这些操作简单,确保增强后的数据仍然具有意义。以下表格展示了在数据集上使用 EDA 时的各种指标:
precision recall f1-score support
ham 0.98 0.99 0.99 1216
spam 0.96 0.88 0.91 177
accuracy 0.98 1393
macro avg 0.97 0.93 0.95 1393
weighted avg 0.98 0.98 0.98 1393
应用 EDA 后,模型保持了整体准确率 0.98,与随机过采样和反向翻译一致。垃圾邮件的精确度很高,为 0.96,与反向翻译相似,而召回率略好于反向翻译的 0.86。宏平均和加权平均分别保持在 0.95 和 0.98。
EDA 在垃圾邮件类别的精确度和召回率上提供了平衡的改进,使其成为我们尝试过的数据增强技术中的有力竞争者。
| 精确度 | 召回率 | F1 分数 | 准确度 | |
|---|---|---|---|---|
| 基线模型 | 0.97 | 0.80 | 0.88 | 0.97 |
| 随机过采样 | 0.93 | 0.91 | 0.92 | 0.98 |
| 反向翻译 | 0.96 | 0.86 | 0.91 | 0.98 |
| EDA | 0.96 | 0.88 | 0.91 | 0.98 |
表 7.4 – 比较各种 NLP 数据级技术对垃圾邮件类别的结果(每个指标的最大值以粗体显示)
总体而言,如表 7.4所示,对于我们的数据集,随机过采样在垃圾邮件的召回率方面表现优异,但略微降低了精确度。反向翻译在略微牺牲召回率的同时提高了精确度。EDA 在两者方面都提供了平衡的改进。需要注意的是,这些结果是经验性的,并且特定于用于此分析的数据集。数据增强技术可能会产生不同的结果,这取决于数据的性质、其分布以及所解决的问题。因此,虽然这些技术在当前背景下显示出希望,但它们在不同数据集或 NLP 任务中的应用效果可能会有所不同。
由于篇幅限制,本书将不会涵盖短语级增强技术,但我们建议您自行探索。
接下来,我们将从高层次上查看一些其他的数据级深度学习技术。
其他数据级深度学习方法和其关键思想的讨论
除了之前讨论的方法之外,还有一系列专门设计来处理不平衡数据挑战的其他技术。本节提供了这些替代方法的概述,每个方法都提供了独特的见解和潜在优势。虽然我们只会触及它们的关键思想,但我们鼓励您深入研究文献,并在发现这些技术有趣时进一步探索。
两阶段学习
两阶段学习[16][17]是一种旨在在不损害多数类性能的情况下,提高多类分类问题中少数类性能的技术。该过程涉及两个训练阶段:
-
在第一阶段,首先在平衡每个类别的数据集上训练一个深度学习模型。平衡可以通过使用随机过采样或欠采样等技术来实现。
-
在第二阶段,我们冻结所有层除了最后一层,然后使用整个数据集对模型进行微调。
第一阶段确保所有层都在平衡数据集上训练。第二阶段通过使用整个数据集重新训练最后一层来校准输出概率,反映了原始不平衡的类别分布。
两个阶段的顺序可以颠倒——也就是说,第一个模型在完整的不平衡数据上训练,然后在第二阶段在平衡数据集上进行微调。这被称为延迟采样,因为采样是在之后进行的。
扩展过采样
由 Damien Dablain 等人[18]在论文中引入的扩展过采样(EOS)是另一种在三个阶段的 CNN 训练框架中使用的数据增强技术,旨在处理不平衡数据。它可以被认为是结合了两阶段学习和数据增强技术。
EOS 通过在嵌入空间中少数类样本及其最近的“敌人”之间创建合成训练实例来工作。术语“最近的敌人”指的是在特征空间中与给定实例最接近的其他类别的实例。通过这种方式创建合成实例,EOS 旨在减少泛化差距,对于少数类来说,这个差距更大。
论文的作者[18]声称,这种方法比常见的平衡学习技术提高了准确性和效率,需要更少的参数和更少的训练时间。
使用生成模型进行过采样
生成模型,包括 GANs、变分自编码器(VAEs)、扩散模型及其衍生品,如 StyleGAN、StyleGAN2 和基于 GPT 的模型,已成为生成类似于训练数据的数据点的突出工具。
VAEs(变分自编码器),一种特定的生成模型,由一个编码器和一个解码器组成,它们协同工作以创建新的数据实例,例如逼真的图像,并且可以用于平衡不平衡的数据集。在 MNIST 长尾版本上,我们通过使用增强的 VAE 模型与基线模型相比,在最不平衡的类别上获得了相当的性能提升。图 7.17显示了 50 个 epoch 后的性能比较。您可以在 GitHub 仓库的相应章节中找到笔记本。

图 7.17 – VAE 增强模型在长尾 MNIST 数据集上的性能
扩散模型通过逐步用噪声破坏图像并随后重建它来运行,在医学成像等领域有应用。例如包括 DALLE-2 和开源的稳定扩散模型。
近期研究[19]强调了合成数据在增强零样本和少样本图像分类任务中的实用性。具体来说,当与大型预训练模型如 DALL-E 和稳定扩散结合使用时,文本到图像生成模型在现实世界数据稀少或不可用的情况下显著提高了性能。这些生成模型因其能够根据自然语言提示创建高质量图像的能力而备受关注,为不平衡数据集提供了一种潜在的解决方案。例如,如果缺少猴子坐在车里的图像,这些模型可以生成数百甚至数千张这样的图像来扩充训练数据集。然而,值得注意的是,仅用合成数据进行训练的模型可能仍然比那些用真实数据进行训练的模型表现不佳。
这些模型通常需要大量的计算资源,这使得它们在扩展时既耗时又昂贵,尤其是在大规模数据集的情况下。特别是扩散模型,计算密集,潜在的过度拟合可能会损害模型的泛化能力。因此,在采用这些高级生成模型时,平衡数据增强的好处与计算成本和潜在挑战至关重要。
DeepSMOTE
深度合成少数类过采样(DeepSMOTE)技术[20]本质上是对 SMOTE 进行了适配,使其适用于深度学习模型,使用编码器-解码器架构,并对图像数据进行了细微调整。DeepSMOTE 由三个主要组件组成:
-
一个用于处理复杂和高维数据的编码器/解码器框架:使用编码器/解码器框架来学习图像数据的紧凑特征表示。它被训练从这种紧凑形式重建原始图像,确保捕获了基本特征。
-
基于 SMOTE 的过采样生成合成实例:一旦学习到特征表示,SMOTE 就应用于这个特征空间以生成少数类的合成实例。这在原始数据高维且复杂的情况下,如图像数据,尤其有用。SMOTE 通过在特征空间中找到最近的* k*个邻居,并生成介于待考虑实例及其邻居之间的插值新实例来创建这些合成实例。
-
一个专门的损失函数:DeepSMOTE 引入了一个专门的损失函数,它不仅关注重建误差(解码器从编码形式重建原始图像的能力),还包括一个惩罚项,确保合成实例对分类任务有用。
与基于 GAN 的过采样不同,DeepSMOTE 不需要判别器。它声称可以生成高质量、信息丰富的合成图像,这些图像可以进行视觉检查。

图 7.18 – 展示 DeepSMOTE 技术(改编自[20])
神经风格迁移
神经风格迁移是深度学习中的一个技术,它艺术性地将一张图像的内容与另一张图像的风格相结合(图 7**.19)。虽然其主要应用在艺术和图像处理中,但生成合成数据样本的概念可以适应解决机器学习中的数据不平衡问题。通过从风格迁移中汲取灵感,可以潜在地生成少数类的合成样本,混合不同类的特征,或适应特定领域的知识。然而,必须小心确保合成数据真实地代表现实世界场景,以避免过度拟合和真实数据的泛化能力差。

图 7.19 – 展示神经风格迁移技术
我们希望这能为您提供对解决不平衡数据的数据级别深度学习方法的全面理解,包括过采样、数据增强以及其他各种策略。
摘要
从经典机器学习模型到深度学习模型处理数据不平衡的方法的转变可能会带来独特的挑战,这主要是因为这些模型必须处理的数据类型不同。经典机器学习模型通常处理结构化、表格数据,而深度学习模型通常处理非结构化数据,如图像、文本、音频和视频。本章探讨了如何调整采样技术以与深度学习模型一起工作。为此,我们使用 MNIST 数据集的不平衡版本来训练一个模型,然后将其与各种过采样方法结合使用。
将随机过采样与深度学习模型结合使用涉及随机复制少数类的样本,直到每个类别的样本数量相等。这通常使用 imbalanced-learn、Keras、TensorFlow 或 PyTorch 等库的 API 来完成,这些库可以无缝地协同工作。一旦数据过采样,就可以将其发送到 PyTorch 或 TensorFlow 进行模型训练。
本章还深入探讨了不同的数据增强技术,这些技术在处理有限或不平衡数据时特别有益。增强技术包括旋转、缩放、裁剪、模糊和添加噪声,以及其他高级技术,如 AugMix、CutMix 和 MixUp。然而,必须小心确保这些增强不会改变原始标签,并且不会无意中改变数据中的关键信息。我们还讨论了其他方法,如两阶段学习和动态采样,作为提高不平衡数据模型性能的潜在策略。同时,我们还了解了一些适用于文本的数据级别技术,如回译和 EDA,这些技术是在垃圾邮件/非垃圾邮件数据集上运行的。
在下一章中,我们将探讨一些基于算法的方法来处理不平衡数据集。
问题
-
将 Mixup 插值应用于本章中使用的 Kaggle 垃圾邮件检测 NLP 数据集。看看 Mixup 是否有助于提高模型性能。您可以参考 Guo 等人撰写的论文《使用 Mixup 增强数据以进行句子分类:一项实证研究》以获取更多信息。《使用 Mixup 增强数据以进行句子分类:一项实证研究》论文链接
-
参考 FMix 论文[21]并实现 FMix 增强技术。将其应用于 Caltech101 数据集。看看使用 FMix 是否比基线模型性能有所提高。
-
将本章中描述的 EOS 技术应用于 CIFAR-10-LT(CIFAR-10 的长尾版本)数据集,并查看模型性能是否对最不平衡的类别有所提高。
-
将本章中学习的 MDSA 技术应用于 CIFAR-10-LT 数据集,并查看模型性能是否对最不平衡的类别有所提高。
参考文献
-
Samira Pouyanfar, Yudong Tao, Anup Mohan, Haiman Tian, Ahmed S. Kaseb, Kent Gauen, Ryan Dailey, Sarah Aghajanzadeh, Yung-Hsiang Lu, Shu-Ching Chen, 和 Mei-Ling Shyu. 2018. 卷积神经网络在不平衡数据分类中的动态采样. 载于 2018 年 IEEE 多媒体信息处理与检索会议(MIPR),第 112–117 页,佛罗里达州迈阿密,4 月。IEEE。
-
LeNet-5 论文,基于梯度的学习应用于文档分类:
vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf。 -
AlexNet 论文,使用深度卷积神经网络进行 ImageNet 分类:
papers.nips.cc/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html。 -
*利用实时用户行为来个性化 Etsy 广告(2023):
www.etsy.com/codeascraft/leveraging-real-time-user-actions-to-personalize-etsy-ads。 -
在Booking.com上的自动图像标记(2017):
booking.ai/automated-image-tagging-at-booking-com-7704f27dcc8b。 -
*通过使用从 3D 模型生成的图像进行深度学习来估计家具物品的拍摄角度(2020):
www.aboutwayfair.com/tech-innovation/shot-angle-prediction-estimating-pose-angle-with-deep-learning-for-furniture-items-using-images-generated-from-3d-models。 -
S. Yun, D. Han, S. Chun, S. J. Oh, Y. Yoo, and J. Choe, “CutMix: 用于训练具有可定位特征的强大分类器的正则化策略,”载于 2019 年 IEEE/CVF 国际计算机视觉会议(ICCV),韩国首尔:IEEE,2019 年 10 月,第 6022–6031 页。doi: 10.1109/ICCV.2019.00612。
-
H. Zhang, M. Cisse, Y. N. Dauphin, 和 D. Lopez-Paz, “mixup: 超越经验风险最小化。” arXiv,2018 年 4 月 27 日。访问日期:2023 年 2 月 11 日。[在线]。可获取:
arxiv.org/abs/1710.09412。 -
R. Geirhos, C. R. M. Temme, J. Rauber, H. H. Schütt, M. Bethge, 和 F. A. Wichmann, “人类和深度神经网络的泛化。”
-
D. Hendrycks, N. Mu, E. D. Cubuk, B. Zoph, J. Gilmer, 和 B. Lakshminarayanan, “AugMix: 一种简单的数据处理方法,以提高鲁棒性和不确定性。” arXiv,2020 年 2 月 17 日。访问日期:2023 年 8 月 1 日。[在线]。可获取:
arxiv.org/abs/1912.02781。 -
H.-P. Chou, S.-C. Chang, J.-Y. Pan, W. Wei, 和 D.-C. Juan,"Remix: Rebalanced Mixup",arXiv,2020 年 11 月 19 日。访问日期:2023 年 8 月 15 日。[在线]。可获取:
arxiv.org/abs/2007.03943. -
Grab 的图像中保护个人数据 (2021):
engineering.grab.com/protecting-personal-data-in-grabs-imagery. -
M. Bayer, M.-A. Kaufhold, 和 C. Reuter,"文本分类的数据增强综述",ACM Comput. Surv.,第 55 卷,第 7 期,第 1-39 页,2023 年 7 月,doi: 10.1145/3544558.
-
使用数据增强和采样提高我们机器学习 WAF 的准确性 (2022), Vikram Grover:
blog.cloudflare.com/data-generation-and-sampling-strategies/. -
NLP 的数据增强:
github.com/makcedward/nlpaug. -
B. Kang et al.,"解耦表示和分类器以实现长尾识别",arXiv,2020 年 2 月 19 日。访问日期:2022 年 12 月 15 日。[在线]。可获取:
arxiv.org/abs/1910.09217. -
K. Cao, C. Wei, A. Gaidon, N. Arechiga, 和 T. Ma,"使用标签分布感知边缘损失的平衡数据集学习",[在线]。可获取:
proceedings.neurips.cc/paper/2019/file/621461af90cadfdaf0e8d4cc25129f91-Paper.pdf. -
D. Dablain, C. Bellinger, B. Krawczyk, 和 N. Chawla,"不平衡深度学习的有效增强",arXiv,2022 年 10 月 17 日。访问日期:2023 年 7 月 23 日。[在线]。可获取:
arxiv.org/abs/2207.06080. -
R. He et al.,"生成模型合成的合成数据是否适用于图像识别?" arXiv,2023 年 2 月 15 日。访问日期:2023 年 8 月 6 日。[在线]。可获取:
arxiv.org/abs/2210.07574. -
D. Dablain, B. Krawczyk, 和 N. V. Chawla,"DeepSMOTE:融合深度学习和 SMOTE 的平衡数据",IEEE Transactions on Neural Networks and Learning Systems,第 33 卷,第 1-15 页,2022 年,doi: 10.1109/TNNLS.2021.3136503.
-
E. Harris, A. Marcu, M. Painter, M. Niranjan, A. Prügel-Bennett, 和 J. Hare,"FMix:增强混合样本数据增强",arXiv,2021 年 2 月 28 日。访问日期:2023 年 8 月 8 日。[在线]。可获取:
arxiv.org/abs/2002.12047.
第八章:算法级深度学习技术
数据级深度学习技术存在与经典机器学习技术非常相似的问题。由于深度学习算法与经典机器学习技术有很大不同,因此在本章中,我们将探讨一些算法级技术来解决数据不平衡问题。这些算法级技术不会改变数据,而是适应模型。这种探索可能会揭示新的见解或方法,以更好地处理不平衡数据。
本章将与第五章的成本敏感学习保持一致,将思想扩展到深度学习模型。我们将探讨算法级深度学习技术来处理数据不平衡问题。通常,这些技术不会修改训练数据,并且通常不需要预处理步骤,从而提供了无需增加训练时间或额外运行时硬件成本的优点。
在本章中,我们将涵盖以下主题:
-
算法级技术的动机
-
加权技术
-
明确损失函数修改
-
讨论其他基于算法的技术
到本章结束时,您将了解如何通过使用 PyTorch API 对模型权重进行调整和修改损失函数来管理不平衡数据。我们还将探索其他算法策略,使您能够在现实世界的应用中做出明智的决策。
技术要求
在本章中,我们将主要使用 PyTorch 和torchvision的标准函数。我们还将使用 Hugging Face Datasets 库来处理文本数据。
本章的代码和笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/master/chapter08。像往常一样,您可以通过点击本章笔记本顶部的在 Colab 中打开图标或通过使用笔记本的 GitHub URL 从colab.research.google.com启动它来打开 GitHub 笔记本。
算法级技术的动机
在本章中,我们将专注于在视觉和文本领域都受到欢迎的深度学习技术。我们将主要使用与第七章的数据级深度学习方法中使用的类似的 MNIST 数据集的长尾不平衡版本。我们还将考虑 CIFAR10-LT,这是 CIFAR10 的长尾版本,在处理长尾数据集的研究者中相当受欢迎。
在本章中,我们将讨论的想法将与我们在第五章中学习的相似,即成本敏感学习,其中高级思想是在模型的成本函数中增加正类(少数类)的权重并减少负类(多数类)的权重。为了便于调整损失函数,scikit-learn和 XGBoost 等框架提供了特定的参数。scikit-learn提供了如class_weight和sample_weight等选项,而 XGBoost 提供了scale_pos_weight作为参数。
在深度学习中,这个想法保持不变,PyTorch 在torch.nn.CrossEntropyLoss类中提供了一个weight参数来实现这个加权思想。
然而,我们将看到一些更相关且可能为深度学习模型带来更好结果的先进技术。
在不平衡的数据集中,多数类别的示例对整体损失的贡献远大于少数类别的示例。这是因为多数类别的示例数量远远超过少数类别的示例。这意味着所使用的损失函数自然地偏向多数类别,并且无法捕捉到少数类别的错误。考虑到这一点,我们能否改变损失函数来考虑这种不平衡数据集的差异?让我们试着找出答案。
二元分类的交叉熵损失定义为以下:
CrossEntropyLoss(p) = {− log(p) if y = 1 (minority class term) − log(1 − p) otherwise (majority class term)
假设 y = 1 代表少数类别,这是我们试图预测的类别。因此,我们可以通过乘以一个更高的权重值来增加少数类别的权重,从而增加其对整体损失的贡献。
加权技术
让我们继续使用上一章中的不平衡 MNIST 数据集,它具有长尾数据分布,如下面的条形图(图 8.1)所示:

图 8.1 – 不平衡 MNIST 数据集
这里,x轴是类别标签,y轴是各种类别的样本计数。在下一节中,我们将看到如何在 PyTorch 中使用权重参数。
我们将使用以下模型代码来完成本章中所有与视觉相关任务。我们定义了一个名为Net的 PyTorch 神经网络类,包含两个卷积层、一个 dropout 层和两个全连接层。forward方法按顺序应用这些层,包括 ReLU 激活和最大池化来处理输入x。最后,它返回输出的log_softmax激活:
class Net(torch.nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
self.conv2 = torch.nn.Conv2d(10, 20, kernel_size=5)
self.conv2_drop = torch.nn.Dropout2d()
self.fc1 = torch.nn.Linear(320, 50)
self.fc2 = torch.nn.Linear(50, 10)
def forward(self, x):
x = F.relu(F.max_pool2d(self.conv1(x), 2))
x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)),2))
x = x.view(-1, 320)
x = F.relu(self.fc1(x))
x = F.dropout(x, training=self.training)
x = self.fc2(x)
return F.log_softmax(x, dim=1)
由于我们的模型最终层使用log_softmax,我们将使用 PyTorch 的负对数似然损失(torch.nn.functional.nll_loss或torch.nn.NLLLoss)。
使用 PyTorch 的权重参数
在torch.nn.CrossEntropyLoss API 中,我们有一个weight参数:
torch.nn.CrossEntropyLoss(weight=None, …)
在这里,weight是一个一维张量,为每个类别分配权重。
我们可以使用scikit-learn中的compute_class_weight函数来获取各种类别的权重:
from sklearn.utils import class_weight
y = imbalanced_train_loader.dataset.targets
class_weights=class_weight.compute_class_weight( \
'balanced', np.unique(y), y.numpy())
print(class_weights)
这将输出以下内容:
array([0.25002533, 0.41181869, 0.68687384, 1.14620743, 1.91330749, 3.19159483, 5.32697842, 8.92108434, 14.809 , 24.68166667])
compute_class_weight 函数根据以下公式为每个类别计算权重,正如我们在第五章中看到的,成本敏感学习:
weight_class_a = 1 / (total_num_samples_for_class_a * total_number_of_samples / number_of_classes)
在图 8.2 中,这些权重已经用条形图绘制出来,以帮助我们了解它们如何与每个类别的频率(y轴)和类别(x轴)相关:

图 8.2 – 每个类别对应的权重条形图
如此图所示,一个类别的样本数量越少,其权重就越高。
提示
这里的关键点是,一个类别的权重与该类别的样本数量成反比,也称为逆类别频率加权。
另一点需要记住的是,类别权重应该始终从训练数据中计算。使用验证数据或测试数据来计算类别权重可能会导致机器学习中的著名的数据泄露或标签泄露问题。正式来说,数据泄露可能发生在训练数据之外的信息被输入到模型中。在这种情况下,如果我们使用测试数据来计算类别权重,那么我们对模型性能的评价将会是有偏的且无效的。
图 8.3 中的漫画展示了一位魔术师管理不同大小的权重,每个权重都标有不同的类别标签,象征着在模型训练过程中为处理类别不平衡而分配给不同类别的不同权重:

图 8.3 – 描述类别加权核心思想的漫画
提示
计算权重的另一种方法是经验性地调整权重。
让我们编写训练循环:
def train(train_loader):
model.train()
for batch_idx, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = torch.nn.functional.nll_loss(output, target weight)
loss.backward()
optimizer.step()
PyTorch 中的许多其他损失函数,包括NLLLoss、MultiLabelSoftMarginLoss、MultiMarginLoss和BCELoss,也接受weight作为参数。
图 8.4。4 比较了使用类别权重和不使用类别权重时各种类别的准确率:

图 8.4 – 使用无类别权重和有类别权重训练的模型性能比较
如我们所见,尽管 0-4 类别的准确率有所下降,但 5-9 类这种最不平衡的类别的准确率有了显著提高。模型的总体准确率也有所上升。
警告
请注意,一些损失函数,如BCEWithLogitsLoss,提供了两个加权参数(BCEWithLogitsLoss可用于二分类或多标签分类):
• weight参数是每个批次的示例的手动缩放权重参数。这更像是sklearn库中的sample_weight参数。
• pos_weight参数用于指定正类的权重。它与sklearn库中的class_weight参数类似。
🚀 OpenAI 在生产中实现类重新加权
OpenAI 试图通过图像生成模型 DALL-E 2 [1]来解决训练数据中的偏差问题。DALL-E 2 在来自互联网的大量图像数据集上训练,这些数据集可能包含偏差。例如,数据集中可能包含比女性更多的男性图像,或者比其他种族或民族群体更多的图像。
为了限制不希望有的模型能力(例如生成暴力图像),他们首先从训练数据集中过滤掉了这样的图像。然而,过滤训练数据可能会放大偏差。为什么?在他们的博客[1]中,他们用一个例子解释说,当为提示“一个 CEO”生成图像时,他们的过滤模型比未过滤的模型显示出更强的男性偏差。他们怀疑这种放大可能源于两个来源:数据集对女性性化的偏差以及潜在的分类器偏差,尽管他们努力减轻这些偏差。这可能导致过滤器移除更多女性的图像,从而扭曲训练数据。
为了解决这个问题偏差,OpenAI 通过对 DALL-E 2 训练数据进行重新加权来应用,通过训练一个分类器来预测图像是否来自未过滤的数据集。然后根据分类器的预测计算每个图像的权重。这种方案已被证明可以减少由过滤引起的频率变化,这意味着它在对抗训练数据中的偏差方面是有效的。
接下来,为了展示其广泛的应用性,我们将对文本数据应用类加权技术。
处理文本数据
让我们处理一些文本数据。我们将使用 Hugging Face 的datasets和transformers库。让我们导入trec数据集(文本检索会议(TREC),一个包含训练集 5,500 个标记问题和测试集 500 个问题的问答分类数据集):
from datasets import load_dataset
dataset = load_dataset("trec")
这个数据集是平衡的,因此我们随机从 ABBRA 和 DESC 类中移除示例,使这些类成为最不平衡的。以下是这个数据集中各种类的分布情况,证实了数据的不平衡性:

图 8.5 – Hugging Face Datasets 库中 trec 数据集中各种类的频率
让我们为预训练的 DistilBERT 语言模型词汇创建一个最大输入标记长度为 512 的分词器(将文本分割成单词或子词):
from transformers import AutoTokenizer
model_name = 'distilbert-base-uncased'
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.model_max_length = 512
接下来,我们将通过调用分词器从我们刚刚导入的数据集中创建标记化的训练集和测试集:
def tokenize_function(examples):
return tokenizer(examples["text"], padding=False, truncation=True)
tokenized_train_dataset = dataset["train"].shuffle(seed=42).\
map(tokenize_function, batched=True)
tokenized_test_dataset = dataset["test"].shuffle(seed=42).\
map(tokenize_function, batched=True)
接下来,让我们实例化模型:
from transformers import \
AutoModelForSequenceClassification, TrainingArguments
model = AutoModelForSequenceClassification.from_pretrained(\
model_name, num_labels=6)
现在,让我们定义和调用一个函数来获取训练参数:
def get_training_args(runname):
training_args = TrainingArguments(
run_name=runname, output_dir="./results", \
num_train_epochs=5, evaluation_strategy="epoch",\
save_strategy="epoch", warmup_ratio=0.1, \
lr_scheduler_type='cosine', \
auto_find_batch_size=True, \
gradient_accumulation_steps=4, fp16=True, \
log_level="error"
)
return training_args
training_args = get_training_args(model_name)
以下custom_compute_metrics()函数返回一个包含精确率、召回率和 F1 分数的字典:
from transformers import EvalPrediction
from typing import Dict
from sklearn.metrics import precision_score, recall_score, f1_score
def custom_compute_metrics(res: EvalPrediction) -> Dict:
pred = res.predictions.argmax(axis=1)
target = res.label_ids
precision = precision_score(target, pred, average='macro')
recall = recall_score(target, pred, average='macro')
f1 = f1_score(target, pred, average='macro')
return {'precision': precision, 'recall': recall, 'f1': f1}
现在,让我们实现包含使用类权重的损失函数的类:
from transformers import Trainer
class CustomTrainerWeighted(Trainer):
def compute_loss(self, model, inputs, return_outputs):
labels = inputs.get("labels")
outputs = model(**inputs)
logits = outputs.get('logits')
loss_fct = nn.CrossEntropyLoss( \
weight=torch.from_numpy(class_weights).cuda(0).float()
)
loss = loss_fct(logits.view(-1,self.model.config.num_labels),\
labels.view(-1))
return (loss, outputs) if return_outputs else loss
我们可以像之前一样使用sklearn中的compute_class_weight函数初始化权重,然后将其输入到我们的CustomTrainerWeighted类中的CrossEntropyLoss函数:
modelWeighted = AutoModelForSequenceClassification \
.from_pretrained(model_name, num_labels=4)
trainerWeighted = CustomTrainerWeighted(\
model=modelWeighted, \
args=training_args, \
train_dataset=tokenized_train_dataset, \
eval_dataset=tokenized_test_dataset, \
tokenizer=tokenizer, \
data_collator=data_collator, \
compute_metrics=custom_compute_metrics)
trainerWeighted.train()
如图 8.6 所示,我们可以看到在处理最不平衡的类别时性能有所提升。然而,对于多数类别(权衡!)观察到轻微的下降:

图 8.6 – 使用无类别加权(左)和有类别加权(右)的混淆矩阵
如我们所见,少数类别ABBR和DESC在类别加权后性能有所提升,但ENTY类别的性能有所下降。此外,观察一些非对角线项,我们可以看到ABBR和DESC类别之间的混淆(图 8**.6(左)中的 0.33)以及DESC和ENTY类别之间的混淆(图 8**.6(左)中的 0.08)在使用类别加权时显著下降(分别为 0.22 和 0.04)。
一些特定于 NLP 任务的变体建议将样本的权重设置为对应类别的平方根倒数,而不是使用之前使用的逆类别频率加权技术。
从本质上讲,类别加权通常可以帮助任何类型的深度学习模型,包括文本数据,在处理不平衡数据时。由于数据增强技术在文本上的应用不如图像直接,因此类别加权对于 NLP 问题来说可以是一个有用的技术。
🚀 Wayfair 生产中的类别重加权
Wayfair 使用 BERT 语言模型来提高其产品搜索和推荐系统的准确性[2]。这是一个具有挑战性的问题,因为 Wayfair 销售的产品数量非常大,而客户可能感兴趣的产品数量则相对较小。
数据存在不平衡,因为客户互动过的产品数量(例如,查看、加入购物车或购买)远小于客户未互动过的产品数量。这使得 BERT 难以学习准确预测客户可能感兴趣的产品。
Wayfair 使用类别加权来解决数据不平衡问题。他们给正例(即客户互动过的产品)分配比负例(即客户未互动过的产品)更高的权重。这有助于确保 BERT 能够准确分类正例和负例,即使数据不平衡。
模型已部署到生产环境中。Wayfair 正在使用该模型来提高其产品搜索和推荐系统的准确性,并为顾客提供更好的体验。
在下一节中,我们将讨论一个类加权的小变体,有时它比单纯的加权技术更有帮助。
延迟重新加权 – 类权重技术的一个小变体
有一种延迟重新加权技术(由 Cao 等人[3]提到),类似于我们在第七章“数据级深度学习方法”中讨论的两阶段采样方法。在这里,我们将重新加权推迟到后面,即在训练的第一阶段,我们在没有任何加权或采样的情况下在完整的不平衡数据集上训练模型。在第二阶段,我们使用已经应用于损失函数的类权重(与类频率成反比)重新训练第一阶段相同的模型,并且可以选择使用较小的学习率。训练的第一阶段为第二阶段使用重新加权损失的训练提供了良好的模型初始化形式。由于我们在训练的第二阶段使用较小的学习率,因此模型的权重不会从第一阶段训练的权重移动得太远:

图 8.7 – 延迟重新加权技术
图 8.8 中的漫画展示了一位魔术师从一个帽子里变出一只大兔子,然后又变出一只小兔子,这描绘了两个阶段的过程:最初在不平衡数据集上训练,然后在第二阶段应用重新加权以实现更平衡的训练:

图 8.8 – 一幅描绘延迟重新加权核心思想的漫画
请参阅本书 GitHub 仓库中标题为Deferred_reweighting_DRW.ipynb的笔记本以获取更多详细信息。在应用延迟重新加权技术的两阶段训练部分之后,我们可以看到与使用交叉熵损失训练相比,我们最不平衡的类的准确率有所提高:

图 8.9 – 延迟重新加权与交叉熵损失的性能比较
接下来,我们将探讨在 PyTorch 标准损失函数无法完成我们想要的所有事情时如何定义自定义损失函数。
显式损失函数修改
在 PyTorch 中,我们可以通过从nn.Module类派生一个子类并重写forward()方法来定义自定义损失函数。损失函数的forward()方法接受预测输出和实际输出作为输入,随后返回计算出的损失值。
尽管类权重确实为多数类和少数类示例分配了不同的权重以实现平衡,但这通常是不够的,尤其是在极端类别不平衡的情况下。我们希望的是减少易于分类示例的损失。原因是这样的易于分类示例通常属于多数类,由于数量较多,它们主导了我们的训练损失。这就是焦点损失的主要思想,它允许对示例进行更细致的处理,无论它们属于哪个类别。我们将在本节中探讨这一点。
理解 PyTorch 中的 forward()方法
在 PyTorch 中,你将在神经网络层和损失函数中遇到 forward() 方法。这是因为神经网络层和损失函数都是从 nn.Module 派生出来的。虽然一开始可能看起来有些混乱,但理解上下文可以帮助阐明其作用:
🟠 在神经网络层:
forward() 方法定义了输入数据在通过层时经历的转换。这可能涉及线性变换、激活函数等操作。
🟢 在损失函数中:
forward() 方法计算预测输出和实际目标值之间的损失。这个损失作为衡量模型性能好坏的指标。
🔑****关键要点:
在 PyTorch 中,神经网络层和损失函数都继承自 nn.Module,提供了一个统一的接口。forward() 方法对两者都是核心的,作为层中数据转换和损失函数中损失计算的计算引擎。将 forward() 视为这两个过程的“引擎”。
焦点损失
我们迄今为止研究的技术假定由于代表性较弱,少数类需要更高的权重。然而,一些少数类可能得到了充分的代表,过度加权它们的样本可能会降低整体模型性能。因此,Facebook(现 Meta)的 Tsung-Yi 等人 [4] 引入了焦点损失,这是一种基于样本的加权技术,其中每个示例的权重由其难度决定,并通过模型对其造成的损失来衡量。
焦点损失技术源于密集目标检测任务,在这些任务中,某一类别的观察结果比另一类多得多:

图 8.10 – 目标检测中的类别不平衡 – 多数类作为背景,少数类作为前景
焦点损失降低了易于分类的示例的权重,并专注于难以分类的示例。这意味着它会减少模型的过度自信;这种过度自信通常阻止模型很好地泛化。
焦点损失是交叉熵损失的扩展。它特别适用于多类分类,其中一些类别易于分类,而其他类别则难以分类。
让我们从我们熟知的二元分类的交叉熵损失开始。如果我们让 p 表示 y = 1 的预测概率,那么交叉熵损失可以定义为以下:
CrossEntropyLoss(p) = {− log(p) if y = 1 − log(1 − p) otherwise
这可以重写为 CrossEntropyLoss(p) = − log( p t),其中 p t,真实类的概率,可以定义为以下:
p t = {p if y = 1 1 − p otherwise
在这里,p 是模型预测 y = 1 的概率。
这个损失函数的问题在于,在数据不平衡的情况下,这个损失函数主要由多数类的损失贡献所主导,而少数类的损失贡献非常小。这可以通过焦点损失来修复。
那么,什么是焦点损失?
FocalLoss( p t) = − α (1 − p t) γ log( p t)
这个公式看起来与交叉熵损失略有不同。有两个额外的项 – α 和 (1 − p t) γ。让我们尝试理解这些项的每个含义:
-
α:这个值可以设置为与正(少数)类样本的数量成反比,并用于使少数类样本比多数类样本更重。它也可以被视为一个可以调整的超参数。
-
(1 − p t) γ:这个项被称为调制因子。如果一个样本对模型来说太容易分类,这意味着 p t 非常高,整个调制因子值将接近零(假设 γ > 1),模型不会太关注这个样本。另一方面,如果一个样本很难分类 – 即 p t 低 – 那么调制因子值将很高,模型将更关注这个样本。
实现
下面是从头实现焦点损失的方法:
class FocalLoss(torch.nn.Module):
def __init__(self, gamma: float = 2, alpha =.98) -> None:
super().__init__()
self.gamma = gamma
self.alpha = alpha
def forward(self, pred: torch.Tensor, target: torch.Tensor):
# pred is tensor with log probabilities
nll_loss = torch.nn.NLLLoss(pred, target)
p_t = torch.exp(-nll_loss)
loss = (1 – p_t)**self.gamma * self.alpha * nll_loss
return loss.mean()
尽管焦点损失技术在计算机视觉和目标检测领域有根源,但我们也可以在处理表格数据和文本数据时从中受益。一些最近的研究将焦点损失移植到经典的机器学习框架,如 XGBoost [5] 和 LightGBM [6],以及使用基于 transformer 的模型的文本数据。
图 8**.11 中的漫画展示了一位弓箭手瞄准一个远处的目标,却忽略了附近的大目标,象征着焦点损失对具有挑战性的少数类样本的重视:

图 8.11 – 焦点损失的示意图
PyTorch 的torchvision库已经为我们实现了这个损失函数:
torchvision.ops.sigmoid_focal_loss(\
inputs: Tensor, targets: Tensor, alpha: float = 0.25,\
gamma: float = 2, reduction: str = 'none')
对于所使用的模型和数据集,alpha 和 gamma 值可能难以调整。在 CIFAR10-LT(CIFAR10 数据集的长尾版本)上使用alpha值为0.25和gamma值为2,并且reduction= 'mean'似乎比常规的交叉熵损失表现更好,如以下图表所示。更多详情,请查看本书 GitHub 仓库中的CIFAR10_LT_Focal_Loss.ipynb笔记本:

图 8.12 – 随着训练的进行,使用交叉熵损失与焦点损失(alpha=0.25,gamma=2)在 CIFAR10-LT 数据集上的模型准确度
在目标检测的 Pascal VOC 数据集 [7] 中,焦点损失有助于检测图像中的摩托车,而交叉熵损失则无法检测到它 (图 8**.13):

图 8.13 – 在 Pascal VOC 数据集中,交叉熵损失无法检测到摩托车(左),而焦点损失则可以检测到(右)。来源:fastai 库 GitHub 仓库 [8]
尽管焦点损失最初是为密集目标检测而设计的,但由于其能够为在少数类中常见的具有挑战性的示例分配更高的权重,因此在类别不平衡的任务中获得了关注。虽然这种样本在少数类中的比例较高,但由于其规模较大,在多数类中的绝对数量更高。因此,对所有类别的挑战性样本分配高权重仍然可能导致神经网络性能的偏差。这促使我们探索其他可以减少这种偏差的损失函数。
🚀 Meta 生产环境中的焦点损失
在 Meta(之前称为 Facebook)[9]中,需要检测有害内容,如仇恨言论和暴力。机器学习模型在包含有害和非有害内容的庞大文本和图像数据集上进行了训练。然而,由于有害内容的例子比非有害内容的例子少得多,系统在从有害内容示例中学习方面遇到了困难。这导致系统过度拟合非有害示例,在现实世界中检测有害内容的性能不佳。
为了解决这个问题,Meta 使用了焦点损失。正如我们所见,焦点损失是一种降低易于分类的示例权重的技术,以便系统专注于从难以分类的示例中学习。Meta 在其训练流程中实现了焦点损失,并在检测有害内容方面提高了其 AI 系统的性能,最高可达 10%。这是一个重大的改进,表明焦点损失是训练 AI 系统检测罕见或难以分类事件的有前途的技术。新的系统已在 Meta 的生产环境中部署,并有助于显著提高平台的安全性。
类别平衡损失
Cui 等人[10]的论文通过在损失函数中添加乘性系数(1 − β)^(1− βn)对交叉熵损失方程进行微小修改——也就是说,我们使用α = (1 − β)^(1− βn)的值,其中β是一个介于 0 和 1 之间的超参数,n 是某一类别的样本数量:
ClassBalancedCrossEntropyLoss(p) = −(1 − β)^(1− βn) log(p_t)
β = 0 表示完全不进行加权,而β → 1 表示通过类频率的倒数进行重新加权。因此,我们可以认为这是一种使特定类别的类别权重在 0 和(1/类别频率)之间可调的方法,这取决于超参数β的值,β是一个可调参数。
这个相同的项可以用作 alpha 值的替代。它可以与焦点损失一起使用:
ClassBalancedFocalLoss(p_t) = −(1 − β)^(1− βn) (1 − p_t)^γ log(p_t)
根据 Cui 等人的研究,建议的 beta 值设置为(N-1)/N,其中 N 是训练示例的总数。
图 8.14 中的漫画展示了这种损失的核心思想。它展示了一个使用带有两端标有“beta”重量的杆来保持平衡的走钢丝者,这代表着调整类别权重以解决类别不平衡:

图 8.14 – 类平衡损失的示意图
实现
让我们看看实现类平衡损失的代码:
class ClassBalancedCrossEntropyLoss(torch.nn.Module):
def __init__(self, samples_per_cls, no_of_classes, beta=0.9999):
super().__init__()
self.beta = beta
self.samples_per_cls = samples_per_cls
self.no_of_classes = no_of_classes
def forward(self, model_log_probs: torch.Tensor, \
labels: torch.Tensor):
effective_num = 1.0-np.power(self.beta,self.samples_per_cls)
weights = (1.0 - beta)/np.array(effective_num)
weights = weights/np.sum(weights) * self.no_of_classes
weights = torch.tensor(weights).float()
loss = torch.nn.NLLLoss(weights)
cb_loss = loss(model_log_probs, labels)
return cb_loss
在forward()函数中,effective_num有效地计算了一个向量(1-βn),其中n是一个包含每个类样本数量的向量。因此,weights向量是(1 − β) _ (1− βn)。使用这些权重,我们通过使用NLLLoss在模型的输出和相应的标签之间计算损失。表 8.1显示了当模型使用类平衡交叉熵损失训练 20 个 epoch 时的类准确率。在这里,我们可以看到最不平衡的类别 9、8、7、6 和 5 的准确率有所提高:
| 类别 | 交叉熵损失 | 类平衡损失 |
|---|---|---|
| 0 | 99.9 | 99.0 |
| 1 | 99.6 | 99.0 |
| 2 | 98.1 | 97.3 |
| 3 | 96.8 | 94.7 |
| 4 | 97.7 | 97.5 |
| 5 | 94.2 | 97.4 |
| 6 | 92.8 | 98.3 |
| 7 | 81.2 | 94.3 |
| 8 | 63.6 | 93.8 |
| 9 | 49.1 | 91.4 |
表 8.1 – 使用交叉熵损失(左)和类平衡交叉熵损失(右)的类准确率
图 8.15 比较了类平衡损失和交叉熵损失的性能:

图 8.15 – 使用类平衡损失与基线模型相比的总体准确率与类准确率
🚀 苹果公司生产中的类平衡损失
苹果公司的可访问性团队旨在确保所有人都能使用,通过解决许多应用中缺乏适当的可访问性信息。他们通过屏幕识别等特性使这些应用对残疾人可用。研究人员旨在根据移动应用的视觉界面自动生成可访问性元数据[11],由于 UI 元素的多样性,这个问题存在显著的类别不平衡。从应用截图中识别出 UI 元素,如文本、图标和滑块。文本元素高度表示,有 741,285 个注释,而滑块表示最少,有 1,808 个注释。
数据集由来自 4,068 个 iPhone 应用的 77,637 个屏幕组成,包含各种 UI 元素,导致数据集高度不平衡,尤其是考虑到 UI 元素的层次性质。
为了有效地处理类别不平衡,采用了类平衡损失函数和数据增强。这使得模型能够更多地关注代表性不足的 UI 类别,从而提高了整体性能。该模型被设计成健壮且快速,能够实现设备上的部署。这确保了实时生成可访问性功能,提高了屏幕阅读器用户的用户体验。
现代卷积神经网络分类器倾向于在不平衡数据集中过拟合少数类。如果我们能防止这种情况发生会怎样?类依赖温度(CDT)损失函数旨在做到这一点。
类依赖温度损失
在处理不平衡数据集时,传统的解释认为,模型在少数类上的表现不如多数类,这源于其倾向于最小化平均实例损失。这使模型偏向于预测多数类。为了对抗这一点,已经提出了重采样和重新加权策略。
然而,Ye 等人[12]引入了类依赖温度(CDT)损失,提出了一个新颖的视角。他们的研究表明,卷积神经网络倾向于过拟合少数类示例,正如少数类与多数类相比,训练集和测试集之间的特征偏差更大所证明的那样。当模型过度学习特征值的训练数据分布时,就会发生特征偏差,随后无法推广到新数据。使用 CDT 损失,模型的训练示例决策值被除以一个“温度”因子,该因子取决于每个类的频率。这种除法使训练更适应特征偏差,并有助于在常见和稀缺类别之间进行有效学习。
图 8.16 展示了 CDT 损失如何根据类频率调整类权重,使用杂技演员骑独轮车处理标有不同类名的物品的视觉类比:

图 8.16 – 独轮车手抛接物品,根据频率调整类权重
以下类实现了这个损失函数:
class CDT(torch.nn.Module):
def __init__(self, num_class_list, gamma=0.36):
super(CDT, self).__init__()
self.gamma = gamma
self.num_class_list = num_class_list
self.cdt_weight = torch.FloatTensor(
[
(max(self.num_class_list)/i) ** self.gamma\
for i in self.num_class_list
]
).to(device)
def forward(self, inputs, targets):
inputs = inputs/self.cdt_weight
loss=torch.nn.functional.nll_loss(inputs,targets)
return loss
这里是对 CDT 类的解释:
-
self.num_class_list存储每个类中的示例数量。 -
self.cdt_weight = torch.FloatTensor([...]).to(device)计算每个类的类依赖温度权重。对于每个类,权重计算为(max(num_class_list) / num_class_list[i]) **gamma。类中示例的数量越多,其在
self.cdt_weight列表中的值就越小。多数类示例具有较低的值,而少数类示例具有较高的值。 -
inputs = inputs /self.cdt_weight通过类依赖温度权重对模型的输入(作为输入)进行缩放。这增加了少数类示例的负对数概率的绝对值,使它们在损失计算中比多数类示例更重要。这旨在使模型更多地关注少数类示例。在 图 8.17 中,我们绘制了 CDT 损失和交叉熵损失的整体准确率(左)以及各个类的准确率(右):

图 8.17 – 交叉熵损失和 CDT 损失的性能比较
如我们所见,一些类别(如 9、7、6、5 和 3)的准确率有所提高,但其他一些类别的性能有所下降。它似乎在我们使用的不平衡 MNIST 数据集上给出了温和的性能,但它可能对其他数据集有潜在的帮助。
如果我们能够在训练过程中根据模型对类别难度的估计动态调整类别的权重会怎样?我们可以通过预测示例类别的准确率来衡量类别难度,然后使用这个难度来计算该类别的权重。
类别难度平衡损失
Sinha 等人 [13] 的论文提出,类别 c 在训练时间 t 后的权重应直接与类别的难度成正比。类别的准确率越低,其难度越高。
从数学上讲,这可以表示如下:
w c, t = ( d c, t) τ
在这里,w c, t 是训练时间 t 后类别 c 的权重,d c, t 是类别难度,其定义如下:
d c, t = (1 − Accuracy c, t)
在这里,Accuracy c, t 是训练时间 t 后类别 c 在验证数据集上的准确率,τ 是一个超参数。
这里的问题是,我们希望在训练过程中动态增加模型准确率较低的类别的权重。我们可以每轮或每几轮训练后这样做,并将更新的权重输入到交叉熵损失中。请参阅本书 GitHub 仓库中标题为 Class_wise_difficulty_balanced_loss.ipynb 的相应笔记本,以获取完整的训练循环:
class ClassDifficultyBalancedLoss(torch.nn.Module):
def __init__(self, class_difficulty, tau=1.5):
super().__init__()
self.class_difficulty = class_difficulty
self.weights = self.class_difficulty ** float(tau)
self.weights = self.weights / (
self.weights.sum() * len(self.weights))
self.loss = torch.nn.NLLLoss(
weight= torch.FloatTensor(self.weights))
def forward(self, input: torch.Tensor, target: torch.Tensor):
return self.loss(input, target)
图 8**.18 使用一个飞人在蹦床上跳跃的漫画来阐述难度平衡损失的概念。每次跳跃都标有准确率分数,突出了低准确率类别如何随着飞人每次跳跃得更高而增加权重:

图 8.18 – 难度平衡损失的示意图 – 飞人跳跃显示了低准确率类别的权重增加
图 8**.19 展示了与交叉熵损失作为基线相比,类别难度平衡损失的性能:

图 8.19 – 使用类别难度平衡损失和交叉熵损失训练的模型性能比较
在这里,我们可以看到几个类别的性能有所提高,包括最不平衡的类别(9)从 40% 上升到 63.5% 的最大跳跃。
接下来,我们将探讨一些其他基于算法的技术,这些技术仍然可以帮助我们处理不平衡数据集。
讨论其他基于算法的技术
在本节中,我们将探讨一些我们之前尚未涉及的算法级技术。有趣的是,这些方法——从减轻过拟合的正则化技术到擅长单样本和少样本学习的 Siamese 网络,再到更深的神经网络架构和阈值调整——还具有有益的副作用:它们有时可以减轻类别不平衡的影响。
正则化技术
S. Alshammari 等人[14]的论文发现,如 L2-正则化和 MaxNorm 约束等知名的正则化技术在长尾识别中非常有帮助。该论文建议只在分类(例如 sigmoid 或 softmax)的最后一层进行这些操作。以下是他们的发现:
-
L2-正则化(也称为权重衰减)通常可以控制权重,并通过防止模型过拟合来帮助模型更好地泛化。
-
tf.keras.constraints.MaxNorm,而 PyTorch 有torch.clamp来帮助实现这一点。
Siamese 网络
在类似的研究中,先前的研究发现Siamese 网络对类别不平衡的负面影响非常稳健。Siamese 网络在单样本学习(在训练数据中每个类只有一个示例时对新的数据进行分类)和少样本学习(在训练数据中每个类只有少数示例时对新的数据进行分类)领域非常有用。Siamese 网络使用对比损失函数,该函数接受成对的输入图像,然后计算相似性度量(欧几里得距离、曼哈顿距离或余弦距离)以确定它们有多相似或不相似。这可以用来计算训练数据中每个独特图像类别的嵌入表示。这种技术的最好之处在于,它提供了一种学习每个类别特征表示的方法。Siamese 网络在视觉问题(例如,两个图像是否为同一个人)以及 NLP 问题(例如,在 Stack Overflow、Quora、Google 等平台上,确定两个问题/查询是否相似)等领域的实际应用中找到了广泛的应用。
图 8**.20 展示了一个 Siamese 网络,其中两个输入被输入到模型中以获取它们的嵌入表示,然后使用距离度量来比较它们的相似性:

图 8.20 – Siamese 网络模型的高级工作原理
深度神经网络
Ding 等人 2017 年的研究[15]发现,对于不平衡数据集来说,深度神经网络(超过 10 层)更有帮助,原因有两个:
-
收敛速度更快
-
更好的整体性能
这归因于深度网络在捕捉数据复杂性方面具有指数级的效率。尽管他们的实验是针对面部动作识别任务,但这可能有助于在其他类型的数据和领域尝试更深的网络。
然而,更长的训练时间、增加的硬件成本和增加的复杂性在工业环境中可能并不总是值得麻烦。
阈值调整
正如我们在第五章中讨论的,成本敏感学习,阈值调整是一种成本敏感的元学习技术。阈值调整同样适用于深度学习模型,并且确保分类的阈值得到适当的调整和微调至关重要,尤其是在训练数据分布发生变化(例如,过采样或欠采样)或者甚至当使用类权重或新的损失函数时。
摘要
在本章中,我们探讨了各种损失函数作为解决类别不平衡的补救措施。我们从类别加权技术和延迟重新加权开始,这两个技术都是为了惩罚对少数类样本的错误。随着我们的进展,我们遇到了焦点损失,我们从以类别为中心的加权转向以样本为中心的加权,关注样本的难度。尽管它有其优点,但我们了解到焦点损失在分配所有类别的挑战性样本权重时,仍然可能对多数类有偏见。随后对类别平衡损失、CDT 损失和类别难度平衡损失的讨论提供了,每个都引入了独特的策略来动态调整权重或调节模型在简单样本和挑战性样本之间的关注点,旨在提高不平衡数据集上的性能。
总结来说,算法级技术通常以某种方式修改模型使用的损失函数,以适应数据集中的不平衡。它们通常不会增加训练时间和成本,与数据级技术不同。它们非常适合数据量大的问题或领域,或者收集更多数据困难或昂贵的情况。
即使这些技术提高了少数类的性能,但有时多数类可能会因此受到影响。在下一章中,我们将探讨一些混合技术,这些技术可以结合数据级和算法级技术,以便我们可以兼得两者之长。
问题
-
平均假错误和平均平方假错误:
王等人[16]提出,在高数据不平衡的情况下,由于大量占主导地位的负样本,常规损失函数无法很好地捕捉少数类别的错误。因此,他们提出了一种新的损失函数,其主要思想是将训练错误分为四种不同的错误:
-
假阳性错误(FPE)= (1/负样本数量) * (负样本中的错误)
-
假阴性错误(FNE)= (正样本数量/1) * (正样本错误)
-
平均错误(MFE)= FPE + FNE
-
均方误差(MSFE)= FPE² + FNE²
此处的错误可以使用常用的交叉熵损失或任何其他用于分类的损失来计算。为不平衡的 MNIST 和 CIFAR10-LT 数据集实现 MFE 和 MSFE 损失函数,并查看模型性能是否优于交叉熵损失的基线。
-
-
在本章中,在实现 CDT 损失函数时,将不平衡的 MNIST 数据集替换为 CIFAR10-LT(CIFAR-10 的长尾版本)。检查您是否仍然能够超过基线实现改进性能。您可能需要调整 gamma 值或执行原始论文[12]中提到的其他技巧之一,以在基线之上获得改进。
-
Tversky 损失函数在 Salehi 等人发表的论文[17]中提出。请阅读这篇论文以了解 Tversky 损失函数及其实现细节。最后,在一个不平衡的 MNIST 数据集上实现 Tversky 损失,并比较其与基线模型的表现。
-
在本章中,我们使用了类权重技术和交叉熵损失与
trec数据集。将交叉熵损失替换为焦点损失,并查看模型性能是否有所提高。
参考文献
-
DALL·E 2 预训练缓解措施,2022,
openai.com/research/dall-e-2-pre-training-mitigations. -
BERT Does Business:在 Wayfair 实施 BERT 模型进行自然语言处理,2019,
www.aboutwayfair.com/tech-innovation/bert-does-business-implementing-the-bert-model-for-natural-language-processing-at-wayfair. -
K. Cao, C. Wei, A. Gaidon, N. Arechiga, 和 T. Ma,通过标签分布感知边缘损失学习不平衡数据集,[在线]。可在
proceedings.neurips.cc/paper/2019/file/621461af90cadfdaf0e8d4cc25129f91-Paper.pdf获取。 -
T.-Y. Lin, P. Goyal, R. Girshick, K. He, 和 P. Dollár,密集目标检测的焦点损失。arXiv,2018 年 2 月 7 日,
arxiv.org/abs/1708.02002。 -
Wang 等人,Imbalance-XGBoost:利用加权损失和焦点损失进行 XGBoost 的二进制标签不平衡分类,
arxiv.org/pdf/1908.01672.pdf。 -
LightGBM 的焦点损失实现,
maxhalford.github.io/blog/lightgbm-focal-loss。 -
PASCAL VOC 项目,
host.robots.ox.ac.uk/pascal/VOC/. -
fastai 库,2018 年,
github.com/fastai/fastai1/blob/master/courses/dl2/pascal-multi.ipynb。 -
社区标准报告,2019 年,
ai.meta.com/blog/community-standards-report/。 -
Y. Cui, M. Jia, T.-Y. Lin, Y. Song, 和 S. Belongie,基于有效样本数量的类别平衡损失,第 10 页。
-
X. Zhang 等人,屏幕识别:从像素创建移动应用程序的可访问元数据,载于 2021 年 CHI 会议关于人机交互系统中的因素论文集,日本横滨:ACM,2021 年 5 月,第 1–15 页。doi: 10.1145/3411764.3445186。博客:
machinelearning.apple.com/research/mobile-applications-accessible。 -
H.-J. Ye, H.-Y. Chen, D.-C. Zhan, 和 W.-L. Chao, 在不平衡深度学习中识别和补偿特征偏差。arXiv,2022 年 7 月 10 日。访问时间:2022 年 12 月 14 日。[在线]。可在
arxiv.org/abs/2001.01385获取。 -
S. Sinha, H. Ohashi, 和 K. Nakamura,用于解决类别不平衡的类别难度平衡损失。arXiv,2020 年 10 月 5 日。访问时间:2022 年 12 月 17 日。[在线]。可在
arxiv.org/abs/2010.01824获取。 -
S. Alshammari, Y.-X. Wang, D. Ramanan, 和 S. Kong,通过权重平衡进行长尾识别,载于 2022 年 IEEE/CVF 计算机视觉和模式识别会议(CVPR),美国路易斯安那州新奥尔良,2022 年 6 月,第 6887–6897 页。Doi: 10.1109/CVPR52688.2022.00677。
-
W. Ding, D.-Y. Huang, Z. Chen, X. Yu, 和 W. Lin,使用非常深的网络进行高度不平衡类别分布的人脸动作识别,载于 2017 年亚太信号与信息处理协会年会和会议(APSIPA ASC),吉隆坡,2017 年 12 月,第 1368–1372 页。doi: 10.1109/APSIPA.2017.8282246。
-
S. Wang, W. Liu, J. Wu, L. Cao, Q. Meng, 和 P. J. Kennedy,在不平衡数据集上训练深度神经网络,载于 2016 年国际神经网络联合会议(IJCNN),加拿大不列颠哥伦比亚省温哥华,2016 年 7 月,第 4368–4374 页。doi: 10.1109/IJCNN.2016.7727770。
-
S. S. M. Salehi, D. Erdogmus, 和 A. Gholipour,使用 3D 全卷积深度网络进行图像分割的 Tversky 损失函数。arXiv,2017 年 6 月 18 日。访问时间:2022 年 12 月 23 日。[在线]。可在
arxiv.org/abs/1706.05721获取。
第九章:混合深度学习方法
在本章中,我们将讨论一些混合深度学习技术,这些技术以某种方式结合了数据级(第七章,数据级深度学习方法)和算法级(第八章,算法级深度学习技术)方法。本章包含一些最近且更高级的技术,可能难以实现,因此建议您对前几章有良好的理解。
我们将首先介绍图机器学习的基础,阐明图模型如何利用数据中的关系来提升性能,尤其是在少数类别的应用中。通过将图卷积网络(GCN)、XGBoost 和 MLP 模型进行并排比较,使用不平衡的社会网络数据集,我们将突出 GCN 的优越性能。
我们将继续探索解决深度学习中类别不平衡的策略,检查操纵数据分布和优先处理挑战性示例的技术。我们还将介绍称为硬示例挖掘和少数类增量校正的技术,它们分别通过优先处理困难实例和迭代增强少数类表示来提高模型性能。
尽管我们的大部分讨论将围绕图像数据集展开,特别是不平衡的 MNIST 数据集,但理解这些技术的更广泛适用性至关重要。例如,我们对图机器学习的深入研究不会依赖于 MNIST。相反,我们将转向来自 Facebook 的更真实的数据集,为处理现实场景中的不平衡问题提供新的视角。
在本章中,我们将涵盖以下主题:
-
不平衡数据的图机器学习
-
硬示例挖掘
-
少数类增量校正
到本章结束时,我们将熟悉一些混合方法,使我们能够理解更复杂技术背后的核心原理。
技术要求
与前几章类似,我们将继续使用常见的库,如numpy、pandas、sklearn和torch。对于图机器学习,我们将使用torch_geometric库。本章的代码和笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/master/chapter09。您可以通过点击章节笔记本顶部的在 Colab 中打开图标或在colab.research.google.com使用笔记本的 GitHub URL 来打开 GitHub 笔记本。
使用图机器学习处理不平衡数据
在本节中,我们将探讨在机器学习中何时图形是有用的工具,一般何时使用图机器学习模型,以及它们在特定类型的失衡数据集上如何有所帮助。我们还将探讨图机器学习模型如何在某些失衡数据集上优于经典模型,如 XGBoost。
图形是极其灵活的数据结构,可以表示复杂的关系和结构,从社交网络和网页(将链接视为边)到化学中的分子(将原子视为节点,它们之间的键视为边)以及各种其他领域。图模型使我们能够表示数据中的关系,这对于预测和洞察力是有帮助的,即使对于关系没有明确定义的问题也是如此。
理解图形
图形是图机器学习的基础,因此首先理解它们是很重要的。在计算机科学领域,图是由节点(或顶点)和边组成的一个集合。节点代表实体,边代表这些实体之间的关系或交互。例如,在一个社交网络中,每个人可以是一个节点,两个人之间的友谊可以是一条边。
图形可以是定向的(边有方向)或非定向的(边没有方向)。它们也可以是有权重的(边有值)或无权重的(边没有值)。
图 9.1 展示了一个样本表格数据集在左侧及其对应的图形表示在右侧:

图 9.1 – 表格数据(左侧)与其视觉图形表示(右侧)进行对比
右侧的图形表示强调了各种实体之间的关系。在左侧的表格表示中,设备和它们的 IP 地址以及网络带宽的连接细节被列出。右侧的图形表示直观地表示了这些连接,使得网络拓扑结构更容易理解。设备是节点,连接是带有带宽的边,权重。图形比表格提供了更清晰的相互关系视图,强调了网络设计洞察。
在下一节中,我们将概述机器学习如何应用于图形。
图机器学习
图机器学习(GML)是一组使用图的架构来提取特征和进行预测的技术。GML 算法可以利用图结构中包含的丰富信息,例如节点之间的连接和这些连接的模式,以提高模型性能,尤其是在失衡数据上。
两种流行的神经网络 GML 算法是 GCNs 和图注意力网络(GATs)。这两种算法都使用图结构从节点的邻域中聚合信息。然而,它们在如何权衡节点邻居的重要性方面有所不同。GCN 对所有邻居给予相同的权重,而 GAT 使用注意力机制为不同的邻居分配不同的权重。在本章中,我们将仅讨论 GCNs。
处理不平衡数据
在机器学习中,当一个类别显著多于其他类别时,模型可能会偏向多数类别,导致对少数类别的性能不佳。这是问题所在,因为通常,少数类别才是我们感兴趣的。例如,在欺诈检测中,非欺诈案例的数量显著多于欺诈案例,但我们感兴趣的是检测欺诈案例。
在 GML 的背景下,图的结构可以提供额外的信息,有助于减轻数据不平衡的影响。例如,少数类别的节点可能比多数类别的节点彼此之间更紧密地连接。GML 算法可以利用这种结构来提高少数类别的性能。
GCNs
我们将简要讨论 GCNs 背后的关键思想和它们是如何工作的。
GCNs 提供了一种独特的处理结构化数据的方法。与假设独立同分布数据的标准神经网络不同,GCNs 可以在图数据上操作,捕捉节点之间的依赖和连接。
GCNs 的本质是消息传递,可以分解如下:
-
节点消息传递:图中的每个节点通过其边向其邻居发送和接收消息
-
聚合:节点将这些消息聚合起来,以获得对这些局部邻域更广泛的理解
在 GCNs 中,传递的是完整的特征向量,而不是仅仅节点的标签。
将 GCN 层视为一个转换步骤。主要操作可以看作如下:
-
聚合邻居:节点从其邻居那里提取特征,导致聚合
-
神经网络转换:前一步骤中聚合的特征集随后通过神经网络层进行转换
让我们通过 Facebook 上的照片示例来探索 GCNs。用户上传照片,我们的目标是将这些图像分类为垃圾邮件或非垃圾邮件。这种分类基于图像内容以及 IP 地址或用户 ID。
让我们想象我们有一个图,其中每个节点是一张 Facebook 照片,如果两张照片是使用相同的 IP 地址或相同的账户上传的,那么这两张照片就是相连的。
假设我们想要使用照片的实际内容(可能是一个从预训练的 CNN 或某些元数据中得到的特征向量)作为节点属性。假设我们有一个 5 维向量来表示每张照片的特征。

图 9.2 – 一个具有 5 维特征嵌入的图像
第 1 步 – 图创建
我们将创建一个图,其中每个顶点代表一个 Facebook 图像。如果两个图像是通过相同的 IP 地址或用户 ID 上传的,我们将在这两个图像之间建立链接。

图 9.3 – 如果图像共享 IP 或用户 ID,则它们之间相互连接的图表
第 2 步 – 图像表示
使用 5 维向量表示每个图像。这可以通过使用图像元数据、从训练好的神经网络中提取的特征或其他适合图像数据的适当技术来实现(如图 图 9.2 所示)。
第 3 步 – 单层 GCN 用于图像分析
当一个特定的图像通过单层 GCN 时,会发生以下情况:
-
我们将所有相邻图像的特征向量进行聚合。相邻图像是具有匹配 IP 地址或用户 ID 的图像。
-
使用平均函数来组合向量。让我们称组合向量为邻域平均向量。
-
使用权重矩阵(例如,5x1 大小,如图 图 9.4 所示)乘以邻域平均向量。
-
然后,将激活函数应用于结果以获得一个单一值,这表明图像是垃圾邮件的可能性。

图 9.4 – 单个 GCN 层的工作原理
第 4 步 – 双层 GCN
多层 GCN,就像传统的深度神经网络一样,可以被堆叠成多层:
-
原始节点特征输入到第一层
-
后续层的输入是前一层的结果
随着每层增加,GCN 能够掌握更广泛的邻域信息。例如,在两层 GCN 中,信息可以在图中传播两个跳数。
在对图 ML 和 GCN 的基础理解到位后,我们将探索一个案例研究。我们将比较图模型与其他模型(包括经典 ML 模型)在不平衡图数据集上的性能。我们的目标是确定图模型是否可以通过利用图结构之间的关系超越其他模型。
案例研究 – XGBoost、MLP 和 GCN 在不平衡数据集上的性能
我们将使用来自 PyTorch Geometric (PyG) 库的 Facebook Page-Page 数据集,该库旨在在图和其他不规则结构上创建和训练深度学习模型。此数据集包含来自 Facebook 的大量社交网络,其中节点代表官方 Facebook 页面,边表示它们之间的相互点赞。每个节点都标记为四个类别之一:政治家、政府机构、电视节目或公司。任务是根据节点特征预测这些类别,这些特征是从页面所有者提供的描述中提取的。
该数据集由于其规模和复杂性,成为图神经网络模型的一个具有挑战性的基准。它于 2017 年 11 月通过 Facebook Graph API 收集,并专注于上述四个类别中的多类节点分类。您可以在snap.stanford.edu/data/facebook-large-page-page-network.html了解更多关于该数据集的信息。
我们首先导入一些常用库和 Facebook 数据集:
import pandas as pd
from torch_geometric.datasets import FacebookPagePage
dataset = FacebookPagePage(root=".")
data = dataset[0]
这里数据对象的数据类型为torch_geometric.data。
这里有一些关于图数据的统计数据:
Dataset: FacebookPagePage()
-----------------------
Number of graphs: 1
Number of features: 128
Number of classes: 4
Number of graphs: 1
Number of nodes: 22,470
Number of edges: 342,004
Average node degree: 15.22
Contains isolated nodes: False
Contains self-loops: True
Is undirected: True
让我们以表格格式打印特征和标签:
dfx = pd.DataFrame(data.x.numpy())
dfx['label'] = pd.DataFrame(data.y)
dfx
这打印出包含在dfx DataFrame 中的特征和标签:
| 1 | 2 | … | 127 | 标签 | |
|---|---|---|---|---|---|
| 0 | -0.262576 | -0.276483 | … | -0.223836 | 0 |
| 1 | -0.262576 | -0.276483 | … | -0.128634 | 2 |
| 2 | -0.262576 | -0.265053 | … | -0.223836 | 1 |
| ... | ... | ... | ... | ... | ... |
| 22468 | -0.262576 | -0.276483 | … | -0.218148 | 1 |
| 22469 | -0.232275 | -0.276483 | … | -0.221275 | 0 |
表 9.1 – 每行显示特征值的数据集;最后一列显示每个数据点的标签
打印的整体结果如下:
22470 rows × 129 columns
这 127 个特征是通过从页面描述文本中使用 Doc2Vec 技术生成的。这些特征就像每个 Facebook 页面的嵌入向量。
在图 9.5中,我们使用 Gephi(一种图形可视化软件)可视化数据集:

图 9.5 – Facebook 页面-页面数据集 Gephi 可视化
该图包含跨类别和类别内的连接,但后者更为突出,突显了同一类别内的相互类似亲和力。这导致了不同的集群,提供了对 Facebook 上强大的类别内关联的鸟瞰图。如果我们分析原始数据集中各种类别的数据,它们的比例并不那么不平衡。因此,让我们通过随机删除一些节点来添加一些不平衡(如图9.6所示):
| 类别 | 原始数据集中节点的数量 | 删除一些节点后的节点数量 |
|---|---|---|
| 0 | 3,327 | 3,327 |
| 1 | 6,495 | 1,410 |
| 2 | 6,880 | 460 |
| 3 | 5,768 | 256 |
表 9.2 – 数据集中各种类别的分布
添加不平衡后数据的分布如下所示:

图 9.6 – 添加不平衡后各种类别的分布
让我们通过指定索引的范围将数据分为训练集和测试集。在Data对象中,我们可以使用掩码指定此范围以表示训练集和测试集:
# Create masks
data.train_mask = range(4368)
data.val_mask = range(4368, 4611)
data.test_mask = range(4611, 4853)
训练 XGBoost 模型
让我们在该数据集上使用 XGBoost 模型设置一个简单的基线。
首先,让我们使用我们之前创建的掩码来创建我们的训练/测试数据集:
X_train, X_test, y_train, y_test = \
data1.x[data1.train_mask].cpu().numpy(), \
data1.x[data1.test_mask].cpu().numpy(), \
data1.y[data1.train_mask].cpu().numpy(), \
data1.y[data1.test_mask].cpu().numpy()
然后,我们在数据上训练和评估:
xgb_clf = XGBClassifier(eval_metric='logloss')
xgb_clf.fit(X_train, y_train)
y_pred = xgb_clf.predict_proba(X_test)
test_acc = accuracy_score(y_test, np.argmax(y_pred,axis=1))
test_acc.round(3)
这在测试集上打印出以下准确度值:
0.793
让我们绘制 PR 曲线:
y1_test_one_hot = F.one_hot(data1.y[data1.test_mask], \
num_classes=4)
display_precision_recall_curve(y1_test_one_hot, y_pred)
这会打印出使用 XGBoost 模型的各个类别的 PR 曲线(图 9**.7)和面积。最不平衡的类别 3 的面积最低,正如预期的那样。

图 9.7 – 使用 XGBoost 的 PR 曲线
训练多层感知器模型
我们可以使用最简单的深度学习模型,即多层感知器(MLP)来设置另一个基线。图 9**.8显示了每个类的 PR 曲线。总体而言,MLP 的表现不如 XGBoost,但在最不平衡的类别 3 上的表现优于 XGBoost。

图 9.8 – 使用 MLP 模型的 PR 曲线
训练 GCN 模型
最后,我们切换到使用图卷积网络,这是 CNN 中卷积层的一种推广。正如我们之前讨论的,GCN 使用图的结构根据其邻居的特征更新每个节点的特征。换句话说,每个节点都可以从它的朋友那里学习!
第一步涉及导入所需的库。在这里,我们导入PyTorch、PyG 中的GCNConv模块和 PyTorch 的functional模块:
import torch
from torch_geometric.nn import GCNConv
import torch.nn.functional as F
GraphConvolutionalNetwork类是我们模型的表示。该类继承自 PyTorch 的nn.Module。它包含一个初始化器、一个用于前向传播的前向函数、一个训练模型的函数和一个评估模型的函数:
class GraphConvolutionalNetwork(torch.nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.convolution_layer1 = GCNConv(input_dim, hidden_dim)
self.convolution_layer2 = GCNConv(hidden_dim, output_dim)
在__init__()函数中,我们初始化模型的层。我们的模型包含两个图卷积网络层(GCNConv 层)。输入、隐藏和输出层的维度作为参数要求。
然后,我们定义一个forward()函数来通过网络执行前向传播。它接受节点特征和边索引作为输入,应用第一个 GCN 层,然后是 ReLU 激活函数,接着应用第二个 GCN 层。该函数随后应用log_softmax激活函数并返回结果:
def forward(self, node_features, edge_index):
hidden_representation = self.convolution_layer1( \
node_features,edge_index)
hidden_representation = torch.relu(hidden_representation)
output_representation = self.convolution_layer2 \
(hidden_representation, edge_index)
return F.log_softmax(output_representation, dim=1)
train_model()函数用于训练模型。它接受数据和 epoch 数作为输入。它将模型设置为训练模式,并将负对数似然损失(NLLLoss)作为损失函数,Adam 作为优化器。然后它运行指定数量的 epoch 来训练模型。在每个 epoch 中,它计算模型的输出,计算损失和准确率,执行反向传播并更新模型参数。它还会每 20 个 epoch 计算并打印训练和验证的损失和准确率:
def train_model(self, data, num_epochs):
loss_function = torch.nn.NLLLoss()
optimizer = torch.optim.Adam(self.parameters(),\
lr=0.01, weight_decay=5e-4)
self.train()
for epoch in range(num_epochs + 1):
optimizer.zero_grad()
network_output = self(data.x, data.edge_index)
true_train_labels = data.y[data.train_mask]
loss = loss_function(network_output[data.train_mask], \
true_train_labels)
accuracy = compute_accuracy(\
network_output[data.train_mask].argmax(\
dim=1), true_train_labels)
loss.backward()
optimizer.step()
if(epoch % 20 == 0):
true_val_labels = data.y[data.val_mask]
val_loss = loss_function(\
network_output[data.val_mask], true_val_labels)
val_accuracy = compute_accuracy(\
network_output[data.val_mask].argmax(\
dim=1), true_val_labels)
print(f'Epoch: {epoch}\n'\
f'Train Loss: {loss:.3f}, Accuracy:\
{accuracy*100:.0f}%\n'\
f'Validation Loss: {val_loss:.2f},\
Accuracy: {val_accuracy*100:.0f}%\n'\
'-------------------')
evaluate_model函数用于评估模型。它将模型设置为评估模式,并计算模型的输出和测试准确率。它返回测试准确率和测试数据的输出。
@torch.no_grad()
def evaluate_model(self, data):
self.eval()
network_output = self(data.x, data.edge_index)
test_accuracy = compute_accuracy(\
network_output.argmax(dim=1)[data.test_mask],\
data.y[data.test_mask])
return test_accuracy,network_output[data.test_mask,:]
我们开始训练过程,然后评估模型:
gcn = GraphConvolutionalNetwork(dataset.num_features, 16,\
dataset.num_classes)
gcn.train_model(data1, num_epochs=100)
acc,_ = gcn.evaluate_model(data1)
print(f'\nGCN test accuracy: {acc*100:.2f}%\n')
这会产生以下输出:
Epoch: 0
Train Loss: 1.414, Accuracy: 32%
Validation Loss: 1.38, Accuracy: 29%
-------------------
Epoch: 20
Train Loss: 0.432, Accuracy: 85%
Validation Loss: 0.48, Accuracy: 83%
-------------------
Epoch: 40
Train Loss: 0.304, Accuracy: 89%
Validation Loss: 0.43, Accuracy: 86%
-------------------
Epoch: 60
Train Loss: 0.247, Accuracy: 92%
Validation Loss: 0.43, Accuracy: 86%
-------------------
Epoch: 80
Train Loss: 0.211, Accuracy: 93%
Validation Loss: 0.43, Accuracy: 88%
-------------------
Epoch: 100
Train Loss: 0.184, Accuracy: 94%
Validation Loss: 0.44, Accuracy: 88%
-------------------
GCN test accuracy: 90.91%
让我们打印 PR 曲线:
_, y1_score = gcn.evaluate_model(data1)
y1_test_one_hot = F.one_hot(data1.y[data1.test_mask], num_classes=4)
display_precision_recall_curve(y1_test_one_hot, y1_score)

图 9.9 – 使用 GCN 模型的 PR 曲线
在表 9.3中,我们比较了各种模型的整体准确率以及按类别划分的准确率:
| 准确率百分比 | MLP | XGBoost | GCN |
|---|---|---|---|
| 总体 | 76.5 | 83.9 | 90.9 |
| 类别 0 | 84.9 | 95.2 | 96.6 |
| 类别 1 | 72.9 | 78.0 | 88.1 |
| 类别 2 | 33.3 | 57.1 | 71.4 |
| 类别 3 | 68.8 | 37.5 | 75.0 |
表 9.3 – 在 Facebook 页面-页面网络数据集上的类别准确率百分比
这里是一些来自表 9.3的见解:
-
总体性能:
- GCN 以 90.9%的整体准确率超越了 MLP 和 XGBoost。GCN 对于这种网络数据来说是最好的,在所有类别中都表现出色。
-
类别特定见解:
-
类别 0:GCN 和 XGBoost 在类别 0 上表现良好。
-
类别 1–3:GCN 领先,而 MLP 和 XGBoost 在类别 2 和 3 中表现不佳。特别是要注意,在训练数据中示例数量最少的类别 3 上,GCN 的表现显著优于其他模型。
-
在这里,我们比较了 XGBoost 这种传统机器学习算法、基本的 MLP 深度学习模型与 GCN(图机器学习模型)在不平衡数据集上的性能。结果显示,图机器学习算法可以超越传统算法,展示了图机器学习处理不平衡数据的潜力。
图机器学习算法优越性能的归因于它们能够利用图的结构。通过从节点的邻域聚合信息,图机器学习算法可以捕捉到传统算法可能错过的数据中的局部和全局模式。
🚀 优步和 Grab 的图机器学习
🎯 解决的问题:
优步和 Grab 都旨在解决其多样化的服务中复杂的欺诈问题,从打车到送餐和金融服务。优步专注于共谋欺诈[1],即用户群体共同实施欺诈。例如,用户可以合作使用被盗信用卡进行虚假行程,然后向银行申请退款以获得那些非法购买的退款。Grab 旨在建立一个通用的欺诈检测框架,能够适应新的模式[2]。
⚖️ 数据不平衡问题:
欺诈活动虽然罕见但种类繁多,造成了类别不平衡问题。两家公司都面临着适应新欺诈模式挑战。
🎨 图建模策略:
• 图模型:两家公司都采用了关系图卷积网络(RGCNs)来捕捉欺诈的复杂关系。为了确定优步用户是否欺诈,优步不仅想利用目标用户的特征,还想利用在定义的网络距离内与之相连的用户的特征。
• 半监督学习:Grab 的 RGCN 模型在一个包含数百万个节点和边的图上进行了训练,其中只有一小部分有标签。基于树的模型严重依赖于高质量的标签和特征工程,而基于图的模型需要的特征工程最少,在检测未知欺诈方面表现出色,利用图结构。
📊实际影响:
基于图的模型在检测已知和未知欺诈风险方面已被证明是有效的。它们需要的特征工程较少,且对标签的依赖性较低,这使得它们成为对抗各种类型欺诈风险的可持续基础。然而,由于延迟问题,Grab 没有使用 RGCN 进行实时模型预测[2]。
🛠 挑战 和技巧:
• 数据管道和可扩展性:大型图大小需要分布式训练和预测。在 Uber,未来工作需要增强实时能力。
• 批量实时预测:对于 Grab 来说,实时图更新计算密集,使得批量实时预测成为可行的解决方案。
总结来说,图机器学习为处理不平衡数据提供了一种有前景的方法,当数据本身具有图结构,或者我们认为可以利用数据中的相互关联性时。通过利用图结构中包含的丰富信息,图机器学习算法可以提高模型性能,并提供更准确、更可靠的预测。随着数据的增多和图变得更大、更复杂,其处理不平衡数据的能力将只会持续增长。
在下一节中,我们将把重点转向另一种称为硬例挖掘的不同策略,该策略基于优先处理数据集中最具挑战性的示例的原则。
硬例挖掘
硬例挖掘是深度学习中的一种技术,它迫使模型更加关注这些困难示例,并防止模型过度拟合那些容易预测的大多数样本。为此,硬例挖掘识别并选择数据集中最具挑战性的样本,然后仅对这些具有挑战性的样本进行反向传播损失。硬例挖掘常用于计算机视觉任务,如目标检测。硬例可以分为两种:
-
硬正例是指那些预测分数低但标签正确的示例
-
硬负例是指那些标签错误但预测分数高的示例,这是模型犯的明显错误
“挖掘”一词指的是寻找这些“困难”示例的过程。硬负例挖掘的想法实际上并不新颖,并且与提升(boosting)的想法非常相似,而提升是流行的提升决策树算法的基础。提升决策树本质上确定了模型出错的地方,然后在这些“困难”示例上训练一个新的模型(称为弱学习器)。
当处理大型数据集时,处理所有训练数据以识别困难例子可能很耗时。这促使我们探索硬样本挖掘的在线版本。
在线硬样本挖掘
在在线硬样本挖掘(OHEM)[3]中,每个训练周期的批次都会确定“硬”的例子,其中我们选取了最小的 k个例子,这些例子具有最低的损失值。然后我们只在训练中反向传播这些最小的 k个例子的损失。
这样,网络专注于比简单样本具有更多信息的最困难样本,并且模型在较少的训练数据下更快地提高。
Shrivastava 等人[3]在论文中介绍的开源硬例子挖掘(OHEM)技术相当受欢迎。这是一种主要用于目标检测的技术,通过关注具有挑战性的案例来提高模型性能。它的目标是高效地选择一组“硬”的负面例子,这些例子对训练模型最有信息量。例如,想象我们正在开发一个面部识别模型,我们的数据集由带有面部(正例)的图像和没有面部(负例)的图像组成。在实践中,我们经常遇到比正例数量少得多的负例。为了使我们的训练更有效率,选择一组最具挑战性的负面例子,这些例子对我们的模型最有信息量是明智的。
在我们的实验中,我们发现在线硬样本挖掘确实有助于不平衡的 MNIST 数据集,并提高了我们模型在最不平衡的类别上的性能。
这里是 OHEM 函数的核心实现:
class NLL_OHEM(torch.nn.NLLLoss):
def __init__(self):
super(NLL_OHEM, self).__init__()
def forward(self, cls_pred, cls_target, rate=0.95):
batch_size = cls_pred.size(0)
ohem_cls_loss = F.cross_entropy(cls_pred,\
cls_target, ignore_index=-1)
keep_num = int(batch_size*rate)
ohem_cls_loss = ohem_cls_loss.topk(keep_num)[0]
cls_loss = ohem_cls_loss.sum() / keep_num # mean
return cls_loss
在NLL_OHEM类中,我们首先计算了常规的交叉熵损失,然后确定了最小的 k个损失值。这些最小的 k个值表示模型难以处理的最小的 k个例子。然后我们只在反向传播中传播这些最小的 k个损失值。
正如我们在第八章中提到的,算法级深度学习技术,我们将继续使用 MNIST 数据集的长尾版本(图 9.10)。

图 9.10 – 一个不平衡的 MNIST 数据集,显示了每个类的计数
在图 9.11中,我们展示了 OHEM 损失与交叉熵损失在 20 个 epoch 后的性能。

图 9.11 – 与交叉熵损失相比的在线硬样本挖掘性能比较
显然,对于不平衡程度最高的类别,观察到的改进最为显著。尽管一些研究工作[4]试图将 OHEM 应用于一般问题而没有取得太大成功,但我们认为这是一个值得注意的好技术。
在下一节中,我们将介绍我们关于少数类增量校正的最后一个主题。
少数类增量校正
少数类增量校正是一种深度学习技术,通过使用类别校正损失(CRL)来增强不平衡数据集中少数类的表示。这种策略动态调整类别不平衡,通过结合硬示例挖掘和其他方法来提高模型性能。
这种技术基于 Dong 等人撰写的论文[5][6]。以下是该技术的关键步骤:
-
每个批次中的类别识别:
-
二元分类:如果一个类别占批次不到 50%,我们将其视为少数类。其余的是多数类。
-
多类分类:我们定义所有少数类为那些总共不超过批次 50%的类别。其余类别被视为多数类。
-
-
计算类别 校正损失:
-
定位 具有挑战性的样本:
-
寻找 hard positives:我们识别出模型错误评估为低预测分数的少数类样本。
-
寻找 hard negatives:我们定位到其他(多数)类别的样本,这些样本被我们的模型错误地分配了少数类的高预测分数。
-
-
构建三元组:
-
使用少数样本作为 anchor:我们将每个少数类样本用作三元组形成的 anchor。
-
创建三元组:我们使用 anchor 样本、hard positive 和 hard negative 来形成三元组。
-
-
计算三元组内的距离:我们定义匹配对(anchor 和 hard positive)与未匹配对(anchor 和 hard negative)之间的距离(d)如下:
d(anchor, hard positive) = ∣ 预测分数(anchor)- 预测分数(hard positive)∣
d(anchor, hard negative) = 预测分数(anchor)- 预测分数(hard negative)
- 施加边界排序:我们确保 anchor 到 hard negative 的距离大于 anchor 到 hard positive 的距离,并增加一个边界。
-
-
制定最终 损失函数:
-
类别不平衡校正:我们通过引入CRL项来修改标准的交叉熵损失,以解决类别不平衡。
-
自定义损失计算:我们使用形成的三元组来计算定义的距离的平均总和。
-
损失方程:
-
L final = α × L CRL + (1 − α) × L CE
这里,L CRL 是 CRL 损失,L CE 是交叉熵损失,α是依赖于数据集中类别不平衡程度的超参数。

图 9.12 – 一幅说明三元组损失在类别校正损失中应用的漫画
利用少数类增量校正中的硬样本挖掘技术
少数类增量校正技术使用硬负样本技术,但有两项定制:
-
它仅使用少数类进行硬挖掘
-
它使用硬正样本和硬负样本进行损失计算(三元组边界损失)
在处理高度不平衡数据集时,少数类增量校正技术的关键亮点在于它在其操作的批次中对少数类使用三元组损失。这确保了模型在每一批次中逐步优化少数类的三元组损失。
我们使用ClassRectificationLoss在不平衡 MNIST 数据上的结果与采用交叉熵损失的基线模型相比相对平庸。这种性能差异可能是由于该技术适合非常大的训练数据,而不是像我们这里使用的 MNIST 这样的小数据集。请参阅 GitHub 仓库中的完整笔记本。
值得注意的是,该论文的原始作者将此方法应用于 CelebA 面部属性数据集,该数据集广泛且具有多标签和多类别。表 9.4展示了论文中的结果,其中他们使用五层 CNN 作为基线,并将 CRL 与过采样、欠采样和成本敏感技术进行了比较。
| 属性(****不平衡比率) | 基线(****五层 CNN) | 过采样 | 欠采样 | 成本敏感 | CRL |
|---|---|---|---|---|---|
| 秃头(1:43) | 93 | 92 | 79 | 93 | 99 |
| 胡须(1:24) | 88 | 90 | 60 | 88 | 93 |
| 灰白头发(1:23) | 90 | 90 | 88 | 90 | 96 |
| 白皙皮肤(1:22) | 81 | 82 | 78 | 80 | 92 |
| 双下巴(1:20) | 83 | 84 | 80 | 84 | 89 |
表 9.4 – 在 CelebA 基准上使用类别平衡准确率(%)比较 CRL 在面部属性识别上的性能(改编自 Dong 等人[6])
从表中可以看出,CRL 技术在各种面部属性上始终优于其他方法,即使在高度不平衡的情况下也是如此。具体来说,对于秃头属性,在 1:43 的不平衡比率下,CRL 实现了令人瞩目的 99%准确率。它在胡须和灰白头发等属性上的有效性也很明显,分别超过了基线 5%和 6%。这证明了 CRL 在处理类别不平衡方面的优越能力。

图 9.13 – CRL 正则化在纠正由类别不平衡训练数据引起的模型偏差中的视觉表示
总体而言,ClassRectificationLoss类提供了一个自定义损失函数,该函数结合了三元组损失和负对数似然损失,同时考虑了数据集中的类别不平衡。这可以是一个有用的工具,用于在少数类样本特别感兴趣的类别不平衡数据集上训练模型。
本章探讨了处理不平衡数据的一些现代深度学习策略,包括图机器学习、困难示例挖掘和少数类增量校正。通过结合数据级和算法级技术,有时甚至将问题范式从表格数据转换为基于图的数据表示,我们可以有效地利用具有挑战性的示例,改善不常见类别的表示,并提高我们管理数据不平衡的能力。
摘要
在本章中,我们介绍了图机器学习,并看到了它如何对某些不平衡数据集有用。我们在 Facebook 页面-页面数据集上训练并比较了 GCN 模型与 XGBoost 和 MLP 基线的性能。对于某些数据集(包括表格数据集),我们能够利用图数据的丰富和互联结构,图机器学习模型甚至可以击败 XGBoost 模型。随着我们继续遇到越来越复杂和互联的数据,图机器学习模型的重要性和相关性将只会继续增长。理解和利用这些算法可以在你的工具库中非常有价值。
然后,我们介绍了一种困难挖掘技术,其中首先识别出具有最低损失值的“困难”示例。然后,仅对这样的k个示例进行反向传播,以迫使模型专注于模型最难以学习的少数类示例。最后,我们深入探讨了另一种混合深度学习技术,称为少数类增量校正。这种方法在在线困难示例挖掘技术挖掘的示例上使用三元组损失。由于少数类增量校正方法结合了来自少数组的困难样本挖掘和称为 CRL 的正则化目标函数,因此它被认为是一种结合了数据级和算法级深度学习技术的混合方法。
我们希望这一章能让你对从新技术中提取关键见解并理解其核心思想充满信心,这些思想直接来自研究论文。
在下一章中,我们将讨论模型校准的重要性以及一些流行的校准机器学习模型的技术。
问题
-
将三元组损失应用于不平衡的 MNIST 数据集,并查看模型的性能是否优于使用交叉熵损失函数。
-
将少数类增量校正技术应用于不平衡数据集——CIFAR10-LT 和 CIFAR100-LT。对于 MNIST-LT 数据集上此技术的参考实现,您可以参考附带的 GitHub 笔记本。
参考文献
-
欺诈检测:利用关系图学习检测共谋(2021):
www.uber.com/blog/fraud-detection/. -
欺诈检测图(2022):
engineering.grab.com/graph-for-fraud-detection. -
A. Shrivastava, A. Gupta, 和 R. Girshick, “基于在线硬样本挖掘的区域检测器训练,” 发表于 2016 年 IEEE 计算机视觉与模式识别会议(CVPR),拉斯维加斯,内华达州,美国,2016 年 6 月,第 761–769 页: doi: 10.1109/CVPR.2016.89.
-
Marius Schmidt-Mengin, Théodore Soulier, Mariem Hamzaoui, Arya Yazdan-Panah, Benedetta Bod-ini, 等人。“在线硬样本挖掘与固定过采样策略在纵向 FLAIR MRI 中分割新多发性硬化症病灶的比较”。Frontiers in Neuroscience,2022,16,pp.100405. 10.3389/fnins.2022.1004050. hal-03836922.
-
Q. Dong, S. Gong, 和 X. Zhu, “不平衡深度学习的类矩形校正硬挖掘,” 发表于 2017 年 IEEE 国际计算机视觉会议(ICCV),威尼斯,2017 年 10 月,第 1869–1878 页. doi: 10.1109/ICCV.2017.205.
-
Q. Dong, S. Gong, 和 X. Zhu, “通过少数类增量校正的不平衡深度学习。” arXiv,2018 年 4 月 28 日. 访问时间:2022 年 7 月 26 日. [在线]. 可用:
arxiv.org/abs/1804.10851.
第十章:模型校准
到目前为止,我们已经探讨了处理数据不平衡的各种方法。在本章中,我们将看到需要对从训练模型获得的预测分数进行一些后处理的需求。这可以在从模型进行实时预测或在对模型进行离线训练时间评估时有所帮助。我们还将了解一些衡量模型校准程度的方法以及不平衡数据集如何使模型校准变得不可避免。
本章将涵盖以下主题:
-
模型校准简介
-
数据平衡技术对模型校准的影响
-
为在真实世界数据集上训练的模型绘制校准曲线
-
模型校准技术
-
校准对模型性能的影响
到本章结束时,你将清楚地了解模型校准的含义、如何衡量它以及何时以及如何应用它。
技术要求
与前几章类似,我们将继续使用常见的库,如matplotlib、numpy、scikit-learn、xgboost和imbalanced-learn。本章的代码和笔记本可在 GitHub 上找到,网址为github.com/PacktPublishing/Machine-Learning-for-Imbalanced-Data/tree/master/chapter10。您可以通过点击章节笔记本顶部的在 Colab 中打开图标或通过使用笔记本的 GitHub URL 从colab.research.google.com启动它来打开 GitHub 笔记本。
模型校准简介
“模型预测交易为欺诈”和“模型估计交易欺诈的可能性为 60%”之间的区别是什么?何时一个陈述比另一个更有用?
两者之间的区别在于第二个陈述代表似然性。这种似然性在理解模型的置信度时可能很有用,这在许多应用中都是必需的,例如在医学诊断中。例如,预测一个患者有 80%的可能性或 80%的可能性患有癌症,对医生来说比仅仅预测患者是否患有癌症更有用。
如果正类别的数量与预测概率相匹配,则认为模型是校准的。让我们进一步理解这一点。假设我们有 10 个观察值,并且对于每一个,模型预测正类别的概率为 0.7。如果模型是校准的,那么我们预计这 10 个观察值中有 7 个属于正类别。
然而,令人惊讶的是,大多数机器学习模型都没有校准,它们的预测值往往过于自信或缺乏自信。这意味着什么?一个过于自信的模型可能会预测概率为 0.9(例如),而实际概率可能只有 0.6。同样,一个缺乏自信的模型可能会预测概率为 0.6(例如),而实际概率可能是 0.9。
我们是否总是需要校准 模型概率?
实际上,这取决于具体问题。如果问题本质上涉及某些项目的排序,比如在搜索排名中,那么我们只需要相对分数,实际的概率并不重要。
这里是一个过度自信模型的例子,我们可以看到,大多数时候,模型预测的概率远高于实际正例的分数:

图 10.1 – 对预测概率高估的过度自信模型的校准曲线
为什么要在乎模型校准
正如我们讨论的那样,如果主要目标是获得项目的相对排名,则模型校准可能不是必要的。然而,还有其他几种场景下,模型校准变得至关重要:
-
将模型预测解释为置信度:校准模型允许将分数解释为模型对其预测的置信度。例如,在垃圾邮件检测系统中,校准得分为 0.9 可能意味着模型有 90%的置信度认为一封电子邮件是垃圾邮件。
-
将模型预测解释为概率:这些分数也可以被视为概率,使它们直接可解释。在一个天气预报模型中,校准得分为 0.8 可以解释为有 80%的降雨可能性。
-
高风险应用:这种校准概率在医疗保健等高风险应用中特别有用,例如疾病预测或欺诈检测。例如,在预测患者患有某种疾病的可能性时,校准得分为 0.7 可能意味着患者有 70%的可能性患有该疾病,从而指导进一步的医学检查或治疗。
-
增强人类可解释性和信任:当模型校准时,人类对模型预测的可解释性和信任度得到增强。例如,在贷款审批系统中,校准得分可以帮助贷款官员了解贷款申请的风险,从而有助于决策过程。
当与深度学习模型一起工作时,尤其重要的是要意识到模型校准,因为几个常见的神经网络超参数会影响模型校准:
-
模型容量:更多的层(深度)和更多的神经元(宽度)通常可以减少分类错误,但发现这会降低模型的校准度 [1]。
-
批归一化:尽管批归一化通常可以提高训练时间,具有轻微的正则化效果,甚至可能提高模型的准确性,但它也可能使模型出现更多的校准错误[1]。
-
权重衰减:权重衰减是一种正则化技术,通常更多的权重衰减有助于校准模型。因此,如果我们有较少的权重衰减,那么我们预期模型会更多地出现校准错误[1]。
让我们看看通常需要校准的模型得分类型。
具有和没有良好校准概率的模型
逻辑回归模型通常假设输出校准概率,尤其是在它适合数据时[2]。这个假设基于模型对交叉熵损失或对数损失函数的优化。然而,值得注意的是,逻辑回归可能会产生过于自信的预测,而 L1/L2 等正则化技术可以帮助模型更加保守,从而提高校准。
由于对特征独立性的假设,朴素贝叶斯模型通常将概率推向零或一,这可能导致校准不良[2]。另一方面,袋装模型(如随机森林)和提升模型通常产生远离零和一的概率。这是由于它们使用的单个决策树或树桩的得分平均性质,这通常会导致更好的校准。
对于神经网络,一些研究显示,简单的网络往往给出校准得分[2]。然而,由于神经网络模型每天都在变得更加复杂,现代神经网络往往校准不足[1] [3]。如图10**.2所示,五层 LeNet 校准良好,因为其置信水平与预期的准确性紧密匹配,这从条形图大致沿对角线对齐中可以看出。相比之下,虽然 110 层 ResNet 具有更高的准确性(更低的错误率),但其置信分数与这种准确性并不紧密匹配[1]。

图 10.2 – 在 CIFAR-100 上对五层 LeNet(左)和 110 层 ResNet(右)的可靠性图(改编自 Guo 等人[1])
接下来,我们将学习如何衡量模型是否校准。
校准曲线或可靠性图
让我们看看我们如何理解模型的得分是否校准。假设我们有一个预测图像是否为猫的模型。
校准曲线基本上是通过将实际正值的比例(y轴)与预测概率得分(x轴)进行绘图来获得的。
让我们看看如何绘制校准曲线,也称为可靠性图:
-
创建一个包含两列的数据集:一列是实际标签,另一列是预测概率。
-
使用预测概率对数据进行升序排序。
-
将预测概率数据集划分为从 0 到 1 的固定大小区间。例如,如果我们创建 10 个区间,我们得到 0.1、0.2、0.3、……、0.9、1.0。如果数据集中有太多示例,我们可以使用更小的区间,反之亦然。
-
现在计算每个区间中实际正例的比例。这些比例值将是我们的 y 轴值。在 x 轴上,我们绘制固定区间值,即 0.1、0.2、0.3、等等。
我们可以得到如下图中类似的图表:

图 10.3 – 将 XGBoost 分类器的概率预测与正例比例绘制成图
我们需要小心选择区间的数量,以下是一些原因:
-
如果我们选择的区间太少,图表可能看起来是线性的并且拟合得很好,给人一种模型已经校准的印象。更重要的是,使用太少区间的真正危险是曲线将没有足够的细节;它本质上只是几个点连接在一起。
-
同样,如果我们选择的区间太多,那么图表可能看起来很嘈杂,我们可能会错误地得出模型未校准的结论。
-
如果我们处理的是不平衡数据集,那么确定模型是否校准可能特别困难。如果我们的数据集不平衡,并且正例的示例数量很少,那么校准图可能看起来很嘈杂或显示模型不够自信或过于自信。
然而,值得注意的是,许多模型并不是完全校准的,它们的校准曲线可能会偏离完美的校准线:

图 10.4 – 通过 XGBoost 模型拟合的校准曲线图;左侧是一个过于自信的模型,右侧是一个不够自信的模型
scikit-learn 提供了一个名为 calibration_curve 的函数,可以轻松绘制此曲线:
fraction_of_positives, mean_pred_bin = calibration_curve( \
y_true,probs, n_bins=8)
然而,视觉判断和比较各种校准图可能会出错,我们可能希望使用一个可以比较两个不同模型校准的某种数值比较指标。
Brier 分数
有一个常用的度量标准称为 Brier 分数,基本上是从模型获得的预测概率的均方误差,如下所示:
Brier 分数 = 1/N ∑ (预测概率 - 实际标签)²
其中 N 是示例数量。
这个分数介于 0(最佳可能分数)和 1(最差可能分数)之间。这个指标与 scikit-learn 非常相似,它使我们的工作变得容易一些:
import numpy as np
from sklearn.metrics import brier_score_loss
y_pred = np.array([0.1, 0.2,0.8,0.9])
y_actual = np.array([1,0,0,1])
brier_score_loss(y_actual, y_pred)
# order of parameters here is important!
这将输出以下 Brier 分数损失值:
0.37500000000000006
Wallace 和 Dahabreh 的论文《不平衡数据中的类别概率估计不可靠(以及如何修复它们)》([4])认为,对于不平衡数据集,较低的 Brier 分数可能仅仅意味着校准总体上是好的,但并不一定适用于少数或稀有类别。为了跟踪单个类别的校准,他们提出了分层 Brier 分数:

图 10.5 – 正负类的分层 Brier 分数 [4]
其中 N pos 表示正类示例的数量,N neg 表示负类示例的数量,y i 是标签,ˆ P 是模型预测分数。
让我们看看一个更受欢迎的替代指标来衡量校准,这在深度学习模型中更为常见。
预期校准误差
预期校准误差(ECE)[5]是衡量模型校准程度的另一个指标。模型的预测概率被分组到 M 个大小相等的区间中。假设 B m 是预测分数落在第m个区间的示例集合。
然后,对于每个区间(B m),我们计算平均预测概率(即 conf(B m))和准确率(即正确分类的示例比例)之间的差异。这个差异是| acc(B m) − conf(B m)|。
我们还根据每个区间的示例数量权衡这些差异,最后将它们加起来得到总的 ECE 值。这相当于将差异乘以 B m/n,其中 n 是示例总数。最后,我们将这个值对所有区间求和,得到最终的公式:
ECE = ∑ m=1 M |B m| /n *|acc(B m) − conf(B m)|
准确率和置信度可以定义为以下:
- 准确率 acc(B m)是模型正确分类到 B m 区间的示例比例:
acc(B m) = (1 / |B m|)* ∑ i=1 |B m| I(y i = ŷ i)
- 置信度是 B m 区间中示例的平均预测概率:
conf(B m) = (1 / |B m|)* ∑ i=1 |B m| p i
有一个之前指标的扩展,称为最大校准误差(MCE),它衡量了所有区间中准确率和置信度之间的最大差异:
MCE = ma x m=1 M |acc(B m) − conf(B m)|
这在模型需要在所有区间内都很好地校准的应用中非常有用,并且可以最小化 MCE。
图 10**.6显示了 MNIST 数据集上的可靠性图,其中包含 ECE 和 MCE 值:

图 10.6 – MNIST 数据集的可靠性图,包含 ECE 和 MCE 值
🚀 Netflix 在生产中的模型校准
🎯 解决的问题:
Netflix 旨在提供与用户多样化的兴趣紧密相关的推荐[6],而不是仅仅关注他们的主要偏好。
⚖️ 数据不平衡:
传统的推荐系统可能会放大用户的主要兴趣,从而掩盖他们的次要或第三级偏好。这可以被认为是一种兴趣不平衡。
🎨 模型校准策略:
Netflix 采用了贪婪重排序方法进行校准。初始模型根据用户观看电影的预测概率对电影进行排序。然后使用贪婪算法调整排序,以确保前 10 个推荐电影与用户观看历史中的类型分布相匹配。
示例:如果一个用户的观看历史包括 50%的动作片,30%的喜剧片和 20%的剧情片,重新排序算法会重新排列顶级推荐以反映这种分布。
📊 附加点:
贪婪重新排序算法易于实现。经验上证明,它在各种数据集上提高了推荐性能。
这种方法确保了 Netflix 的推荐能够满足用户兴趣的全谱系,防止任何单一兴趣主导建议。
在下一节中,让我们尝试了解数据平衡技术如何影响模型的校准。
数据平衡技术对模型校准的影响
应用数据级技术,如过采样和欠采样,的通常影响是它们改变了模型训练数据的分布。这意味着模型看到所有类别的数量几乎相等,这并不反映实际的数据分布。因此,模型对真实不平衡数据分布的校准程度降低。同样,使用class_weight来处理数据不平衡的算法级成本敏感技术对模型对真实数据分布的校准也有类似的负面影响。图 10.7(对数尺度)来自最近的研究[7],展示了基于 CNN 的肺炎检测任务的模型校准的退化,随着class_weight从 0.5 增加到 0.9 到 0.99。随着class_weight的增加,模型变得过于自信,因此与真实数据分布的校准程度降低。

图 10.7 – 随着 class_weight 从 0.5 变化到 0.9 到 0.99(对数尺度)的 CNN 模型校准退化(图像改编自 Caplin 等人[7])
类似地,在图 10.8中,我们展示了在thyroid_sick UCI 数据集上逻辑回归模型的校准曲线。相应的笔记本可以在本书的 GitHub 仓库中找到。

图 10.8 – 使用无采样的逻辑回归校准曲线
图 10.9和图 10.10展示了过采样技术如何恶化模型的校准:

图 10.9 – 使用随机过采样的逻辑回归校准曲线

图 10.10 – 使用 SMOTE 的逻辑回归校准曲线
类似地,图 10.11展示了欠采样技术产生的类似效果:

图 10.11 – 使用随机欠采样的逻辑回归校准曲线
图 10.12展示了类权重如何对模型校准产生负面影响:

图 10.12 – 无采样技术(左)和类权重(右)使用逻辑回归的校准曲线
在我们刚刚看到的图表中,无论是欠采样还是过采样都使模型过于自信。欠采样可能会使模型对其分类少数类的能力过于乐观,而过采样可能会导致模型高估遇到少数实例的可能性。这种过自信是由于模型假设修改后的训练数据代表了真实世界的分布。为了详细说明,当我们进行欠采样或过采样时,我们实际上是在告诉模型少数类的出现频率比实际情况更高(或更不常见)。然后,模型可以将这种扭曲的观点推广到新的、未见过的数据。因此,它可能会对其对少数类的预测过于自信,认为这些结果比实际情况更有可能。这种过自信并不适用于多数类,因为模型在训练过程中仍然看到了大量的这些例子。因此,模型最终会校准不当,并且倾向于对其对少数类的预测过于自信。
在下一节中,我们将使用一个真实世界的数据集,使用这个数据集训练一个模型,然后通过绘制校准曲线来确定模型的校准情况。
绘制基于真实世界数据集训练的模型的校准曲线
模型校准理想情况下应该在独立于训练集和测试集的数据集上进行。为什么?这是为了避免过拟合,因为模型可能会变得过于适应训练/测试集的独特特征。
我们可以保留一个专门用于模型校准的保留数据集。在某些情况下,我们可能数据太少,无法证明将其进一步分割为单独的保留数据集进行校准的合理性。在这种情况下,一个实用的折衷方案可能是使用测试集进行校准,假设测试集与模型将用于最终预测的数据集具有相同的分布。然而,我们应该记住,在测试集上进行校准后,我们不再有模型最终性能的无偏估计,我们需要谨慎地解释模型性能指标。
我们使用 Kaggle 上的 HR Data for Analytics 数据集(www.kaggle.com/datasets/jacksonchou/hr-data-for-analytics)。这个数据集包含了一家大型公司的员工档案,其中每条记录代表一名员工。
下载的数据集 HR_comma_sep.csv 已添加到本书的 GitHub 仓库中。让我们将数据集加载到一个 pandas DataFrame 中:
df = pd.read_csv('HR_comma_sep.csv')
df
这显示了数据集的一些样本行:
last_evaluation |
left |
... | sales |
salary |
|
|---|---|---|---|---|---|
0 |
0.53 | 1 | ... | 销售额 | 低 |
1 |
0.86 | 1 | ... | 销售额 | 中等 |
2 |
0.88 | 1 | ... | 销售额 | 中等 |
3 |
0.87 | 1 | ... | 销售额 | 低 |
4 |
0.52 | 1 | ... | 销售额 | 低 |
... |
... | ... | ... | ... | ... |
14994 |
0.57 | 1 | ... | 支持度 | 低 |
14995 |
0.48 | 1 | ... | 支持度 | 低 |
14996 |
0.53 | 1 | ... | 支持度 | 低 |
14997 |
0.96 | 1 | ... | 支持度 | 低 |
14998 |
0.52 | 1 | ... | 支持度 | 低 |
表 10.1 – Kaggle 的 HR Data for Analytics 数据集的样本行
很明显,一些列,如sales和salary,是分类的。left列是我们的标签列。让我们获取数据集的不平衡情况:
import seaborn as sns
print(df['left'].value_counts())
df['left'].value_counts().plot(kind='bar')
这给出了标签的数量:
0 11428
1 3571
我们需要使用LabelEncoder将分类列转换为 ID 标签,然后使用sklearn中的StandardScaler对这些列进行标准化。预处理完成后,我们将数据集分为三个子集:80%用于训练集,10%分别用于验证集和测试集。我们将跳过这些步骤的代码,直接进入模型训练。完整的代码请参阅附带的 GitHub 笔记本。
如往常一样,我们将在训练集上训练随机森林模型,并使用测试集来评估模型。我们将使用验证集来校准模型。
model = RandomForestClassifier(n_estimators=100, random_state=49)
model.fit(X_train, y_train)
让我们来了解一下模型的校准程度。我们打印 Brier 分数并绘制校准曲线:
# Get model probabilities on test set(uncalibrated model)
probs_uncalibrated = model.predict_proba(X_test)[:, 1]
# Calculate Brier score for the uncalibrated model
brier_uncalibrated = brier_score_loss(y_test, probs_uncalibrated)
print(f"Brier Score for Uncalibrated Model: \
{round(brier_uncalibrated, 4)}")
# Compute the calibration curve for the uncalibrated model
fraction_of_positives_uncalibrated,mean_predicted_value_uncalibrated=\
calibration_curve(y_test, probs_uncalibrated, n_bins=10)
这给出了以下输出:
Brier Score for Uncalibrated Model: 0.0447
让我们绘制校准曲线:
plt.figure(figsize=(6, 4))
plt.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
plt.plot(mean_predicted_value_uncalibrated, \
fraction_of_positives_uncalibrated, label="Uncalibrated")
plt.xlabel('Mean Predicted Value')
plt.ylabel('Fraction of Positives')
plt.title('Calibration Curve (Test Set)')
plt.legend()
plt.show()

图 10.13 – 未校准的随机森林模型的校准曲线
图 10.13 显示,模型在预测范围的初始阶段(0 到约 0.4)过于自信,随后则不够自信。
在下一节中,我们将探讨一些提高模型校准的技术。
模型校准技术
有几种方法可以校准模型。根据调整预测概率以更好地与真实概率对齐所使用的方法的性质,校准技术可以分为两大类:参数化和非参数化:
-
参数化方法:这些方法假设预测概率与真实概率之间的关系具有特定的函数形式。它们具有一组需要从数据中估计的参数。一旦这些参数被估计,校准函数就完全确定了。例如,Platt 缩放法假设对数函数,beta 校准假设 beta 分布。我们还将讨论温度缩放和标签平滑。
-
非参数化方法:这些方法不假设校准函数具有特定的函数形式。它们更灵活,可以适应预测概率与真实概率之间更复杂的关系。然而,它们通常需要更多的数据来产生可靠的校准。例如,等距回归法拟合分段常数函数,样条校准法使用样条(分段定义的多项式)函数来拟合预测概率。
首先,我们将探讨一种基于理论、公式的方法,用于校准在采样数据上训练的模型分数,特别是在数据不平衡的背景下。接下来,我们将检查流行的方法,如 Platt 的缩放和等调回归,这些方法通常与经典机器学习模型一起使用。最后,我们将介绍支持技术,如温度缩放和标签平滑,这些技术在深度学习模型中更为常见。
对模型分数进行校准以考虑采样
如果我们使用过采样或欠采样来平衡数据集,我们可以推导出一个理论校准公式。正如我们在第二章、“过采样方法”,第三章、“欠采样方法”(两者均基于采样),以及第七章,“数据级深度学习方法”中看到的,我们可以应用一些(过/欠)采样技术或数据增强技术,以提高少数类(s)样本的相对数量,以解决数据不平衡问题。因此,我们改变了训练数据的分布。尽管下采样增强了模型区分类别的能力,但它也导致了预测概率的高估。因此,在推理(现实世界预测)时间内的模型分数仍然处于下采样空间中,我们应该将这些分数带回真实分布。
通常,下采样的目标是平衡数据集中正类和负类实例的数量。
例如,如果经过下采样后,有 100 个正类实例和 200 个负类实例,那么比例 w = 100/200 = 0.5。
假设负类样本的数量多于正类样本的数量,我们定义 w 为该比例:
w = 下采样数据集中正类实例的数量 ________________________________________ 下采样数据集中负类实例的数量
现在,假设 p 是原始数据集中选择正类样本的概率(没有使用任何下采样)。
如果 p 是原始数据集中选择正类的概率,那么从下采样数据集中选择正类的概率 p d 可以使用以下公式[8]计算:
p d = p _ p+ w (1 − p)
注意,如果 w=1,即没有进行下采样时,p d = p。您可以参考 Moscatelli 等人[9]的论文,以证明原始和下采样数据集概率之间的关系。
解释分母
以下为前一个公式分母中的各种术语:
-
p 是原始数据集中选择正类实例的概率
-
(1 − p)是原始数据集中选择负类实例的概率
-
w(1 - p) 是使用下采样时选择负类实例的概率
-
求和 p + w(1 - p) 是在负类以 w 倍数下采样后,数据集中正类和下采样负类的概率总和
现在我们已经理解了之前的公式,我们可以将其反转来找出原始数据集中选择正类的概率 p [10]:
p = p_d / (p_d + 1 - p_d * w)
其中:
-
w 是正类与负类的比例(称为负类下采样率)
-
p_d 是从下采样数据集中选择正类的概率
让我们用一些数字来理解这一点:
-
我们总共有 100,000 个示例,其中 10,000 个来自正类,90,000 个来自负类。
-
假设我们使用下采样率 w = 0.5,这意味着下采样后,我们有 10,000 个正类和 20,000 个负类。这也意味着在下采样过程中,对于每个正类示例,我们只选择了两个负类示例。
-
假设我们在对下采样数据集进行训练时的模型预测分数为 p_d,为 0.9。
-
让我们计算原始数据集中的预测分数。根据之前定义的公式,p = p_d / (p_d + 1 - p_d * w) = 0.9 / (0.9 + 0.2) = 0.9 / 1.1 = 0.82。
-
因此,下采样示例中的模型预测分数 0.9 在原始数据集中变为 0.82。注意概率是如何降低的。
一个更通用且简单的公式 [11] 是采样前后概率之比的关系:
欠采样后的概率 = 欠采样前的概率 * 欠采样前 1 的比例 * (1 - 欠采样后 1 的比例) / (1 - 欠采样前 1 的比例) * 欠采样后 1 的比例
其中 odds 是以以下方式表达事件概率的一种方式:
概率 = 1 - 概率
图 10.14 和 图 10.15 分别显示了在应用之前的校准公式之前和之后,使用随机欠采样对来自 imblearn.datasets 包的 thyroid_sick UCI 数据集进行校准时模型校准图。您可以在 GitHub 上找到完整的笔记本。

图 10.14 – 使用欠采样进行校准前的模型校准图

图 10.15 – 使用欠采样进行校准后的模型校准图
🚀 Meta 在生产中的模型校准
🎯 解决的问题:
Meta 旨在准确预测广告的点击通过率(CTR)以优化 Meta 广告系统中的在线竞价和拍卖 [10]。准确的点击预测对于优化在线竞价和拍卖至关重要。
⚖️ 数据不平衡问题:
Meta 处理了大量的数据,这些数据本身存在不平衡。一天内 Facebook 广告曝光数据包含大量的实例。Meta 使用负下采样来加速训练并提高模型性能。
🎨 模型 校准策略:
由于 Meta 使用了下采样,他们使用了上一节中的公式,
p = p d _ p d + 1 − p d _ w ,以重新校准模型预测分数。
📊 额外 重要点:
他们研究了数据新鲜度和在线学习对预测准确性的影响。广告拍卖的效率取决于点击预测的准确性和校准。他们还使用了归一化交叉熵损失和校准作为他们主要的评估指标。
Platt 缩放
使用这种技术,我们试图将分类器的概率映射到完美的校准线上。更精确地说,我们只是拟合一个逻辑回归模型,输入是原始模型的概率分数,标签是实际标签。sklearn中的CalibratedClassifierCV API 已经简化了这种技术的实现:
# Calibrate the model on the validation data using Platt's scaling
platt_scaling = CalibratedClassifierCV(model, \
method='sigmoid', cv='prefit')
platt_scaling.fit(X_val, y_val)
# Get model probabilities on test set (calibrated model)
probs_ps = platt_scaling.predict_proba(X_test)[:, 1]
# Compute Brier score for Platt's scaling calibrated model
brier_platt = brier_score_loss(y_test, probs_ps)
print(f"Brier Score for Platt's Scaled Model: \
{round(brier_platt, 4)}")
# Compute the calibration curve for Platt's scaling
fraction_of_positives_ps, mean_predicted_value_ps = \
calibration_curve(y_test, probs_ps, n_bins=10)
这里是 Brier 分数的输出结果:
Brier Score for Platt's Scaled Model: 0.032
Platt 缩放模型的 Brier 分数小于未校准模型的 Brier 分数,后者为 0.0447,这意味着 Platt 缩放模型的校准更好。
等调回归
当我们期望输入变量和输出之间存在单调关系时,等调回归特别有用。在这种情况下,单调函数是指要么完全非递减,要么完全非递增的函数。这里的单调性指的是模型的原始输出和真实概率之间的关系,而不是数据点的排列。
如果模型的输出不遵循这种预期的单调行为,可以使用等调回归来强制执行。等调回归可用于信用评分或医疗诊断等场景,在这些场景中,更高的分数应始终表示更高的特定结果的可能性。
isotonic_regression = CalibratedClassifierCV(model, \
method='isotonic', cv='prefit')
isotonic_regression.fit(X_val, y_val)
probs_ir = isotonic_regression.predict_proba(X_test)[:, 1]
brier_isotonic = brier_score_loss(y_test, probs_ir)
print(f"Brier Score for Isotonic Regression Calibrated \
Model: {round(brier_isotonic, 4)}")
fraction_of_positives_ir, mean_predicted_value_ir = \
calibration_curve(y_test, probs_ir, n_bins=10)
这里是输出结果:
Brier Score for Isotonic Regression Calibrated Model: 0.0317
这个 Brier 分数值是在 Platt 缩放方法之上的进一步改进。
让我们绘制这两种技术的校准曲线:
plt.figure(figsize=(6, 4))
plt.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated")
plt.plot(mean_predicted_value_uncalibrated, \
fraction_of_positives_uncalibrated, label="Uncalibrated")
plt.plot(mean_predicted_value_ps, fraction_of_positives_ps, \
label="Platt's scaling", linestyle='-.')
plt.plot(mean_predicted_value_ir, fraction_of_positives_ir, \
label="Isotonic regression", linestyle='--')
plt.xlabel('Mean predicted value')
plt.ylabel('Fraction of positives')
plt.title('Calibration curves (Test Set)')
plt.legend()
plt.show()

图 10.16 – 随机森林模型的校准曲线
如图 10.16所示,等调回归最接近完美的校准曲线;因此,它在我们的模型和数据上表现最佳。Platt 缩放在校准模型方面也做得相当不错。
在 Platt 缩放和等调回归之间进行选择
Platt 缩放被认为更适合模型预测遵循 sigmoid 曲线的问题。这是有道理的,因为逻辑回归(Platt 缩放使用的)使用 sigmoid 来拟合数据点。等距回归可以覆盖更广泛的预测概率的扭曲。然而,一些研究[2]表明,等距回归更容易过度拟合预测概率。因此,当只有有限的数据集时,它的性能可能比 Platt 缩放更差,因为它与有限数据集的泛化能力不佳。
遵循的一般规则
当手头的数据集非常小或有限时,选择 Platt 缩放。然而,当数据足够多,不会导致过度拟合模型时,等距回归通常比 Platt 缩放表现更好。
对于多类分类器的校准,我们可以使用一对余方法,并为每个类别使用单独的校准图。我们可以应用像 Platt 缩放或等距回归这样的技术来提高可预测性,就像二元分类一样。
温度缩放
温度缩放是一种后处理技术,用于提高神经网络的校准。它通过使用温度参数缩放 logits(在应用 softmax 函数之前网络的最终层输出)来实现。这会根据温度值调整分配给每个类别的概率,使其变尖锐或变柔和。通过调整温度参数,可以实现对模型置信度估计的更好校准,这在分类或排名等应用中可能很有用。
温度缩放可以被视为 Platt 缩放的多类扩展,它只有一个温度超参数 T > 0,适用于所有类别。
标签平滑
标签平滑[12]是一种已知的可以改善模型校准的正则化技术。它修改用于训练模型的训练数据,通常作为模型训练的一部分来处理。它不像温度缩放和之前的技术那样是一种后处理技术。
当神经网络进行训练时,它们往往对自己的预测过于自信,这可能会阻碍它们泛化能力和在新、未见过的数据上的表现。因此,引入一种正则化形式以降低网络的不确定性水平并提高其在新数据上的性能是有益的。
假设我们有一个二元分类问题,其中真实标签可以是 0 或 1。在没有标签平滑的情况下,训练标签将被进行 one-hot 编码,这意味着对于正例,真实标签为 1,对于负例,真实标签为 0。
使用标签平滑,我们在真实标签上添加一小部分噪声。例如,我们可以设置一个平滑因子为 0.1。
下面是一个二元分类中正例原始标签和平滑标签的例子:
Original one-hot encoded label: [1, 0]
Smoothed one-hot encoded label: [0.9, 0.1]
通过向标签添加这种噪声,模型被鼓励对其预测不那么自信,并对输入数据的小变化更加鲁棒。这可能导致在未见过的数据上性能提升。
在大型数据集中,错误标记的数据可能是一个问题。神经网络应该被设计成谨慎地接近正确答案,以减轻错误标签的影响。标签平滑在这方面有所帮助,通过稍微调整目标标签,使模型对其预测不那么自信。这可以防止模型过度拟合噪声或错误标签。
根据 Müller 的论文[13],标签平滑可以通过自动调整网络的输出概率来提高模型校准。这消除了手动温度缩放的需要。
标签平滑可以帮助提高各个领域的准确性,例如图像分类、文本和语音识别问题。大多数现代机器学习框架,包括 TensorFlow、PyTorch 和来自 Hugging Face 的 Transformers,在其 API 中以某种形式提供了标签平滑的内置实现。
在 PyTorch 中,它在交叉熵损失函数中实现:
torch.nn.CrossEntropyLoss(label_smoothing=0.1, …)
在计算损失时,我们可以指定平滑量(介于 0 和 1 之间的浮点值),其中默认值为 0.0(0.0 表示无平滑)。
通常,如果你想在网络中添加一些正则化,那么将标签平滑添加到损失函数中可能会有所帮助。
标签平滑的反对意见
对于标签平滑有一些反对意见。它只是另一个需要调整的超参数,当有更好的正则化技术,如权重衰减和 L1 正则化时,可能过度复杂化你的网络并隐式修改训练数据的标签。另一个需要考虑的问题是,由于它向标签添加随机噪声,网络可能在某些情况下可能欠拟合。
标签平滑还有一些改进的变体,例如文中提到的标签感知平滑,见 Zhong 等人关于改进长尾识别校准的[14]。
下表显示了我们对模型校准所讨论的四种技术的比较:
| 主题 | 温度缩放 | 标签平滑 | Platt 的缩放 | 等调回归 |
|---|---|---|---|---|
| 标签值的变化 | 训练数据的标签值没有变化 | 训练数据的标签值发生变化 | 训练数据的标签值没有变化 | 训练数据的标签值没有变化 |
| 时间 | 训练完成后,在验证数据集上计算超参数 T 的值 | 在模型实际训练期间完成 | 训练后应用 | 训练后应用 |
| 预测值调整 | 模型的预测值被手动调整 | 通过应用标签平滑改变预测值 | 模型的预测值被手动调整 | 模型的预测值被手动调整 |
| 角色 | 作为正则化器 | 作为正则化器 | 作为模型校准器或预测分数转换器 | 作为模型校准器或预测分数转换器 |
表 10.2 – 比较温度缩放、标签平滑、Platt 缩放和等调回归
还有其他模型校准技术我们没有机会探索。例如,样条校准 [15][16] 是一种非参数方法,它使用样条函数,这是一种平滑且连续的分段多项式函数。这种技术在非参数性质上与等调回归有些相似。
另一方面,beta 校准 [17] 是一种参数方法,它将 beta 分布拟合到模型的预测中。这种技术与 Platt 缩放在概念上相似,因为两者都是参数方法。Beta 校准特别适用于建模概率,例如点击率或客户转化率。
焦点损失,在第第八章中讨论,算法级深度学习技术,是深度学习模型中常用的一种方法。正如 Mukhoti 等人 [3] 的论文所示,焦点损失产生校准良好的模型,并且通常与温度缩放结合以获得最佳结果。鉴于多层神经网络往往对其预测过于自信,焦点损失起到正则化作用。它迫使模型关注更难的问题示例,从而减少过度自信并提高校准 [3]。
🚀 在亚马逊使用焦点损失进行模型校准
🎯 问题解决:
当亚马逊部署会话机器人 [18] 来处理客户请求时,底层机器学习模型的校准证明非常重要。在一次实例中,亚马逊的聊天机器人被要求自动分类退货原因代码。这些退货原因代码表现出类别不平衡。当客户想要退货时,确定适当的理由对于高效的退货处理变得至关重要。例如,如果客户对商品的大小或颜色表示不满,它会被归类为“客户偏好”。在这种情况下,亚马逊理解提供替代品不是最佳解决方案;相反,退款更为合适。
🎨 模型校准策略:
通过严格的测试,他们揭示了在处理此类现实任务中的模型误校准问题时,焦点损失的鲁棒性。焦点损失被用作校准方法。此外,这不仅仅关于采用焦点损失;损失函数中γ的值在增强模型校准方面发挥了关键作用。
📊 其他要点:
Focal loss 在实现更好校准的模型方面优于传统的交叉熵损失。这项技术已在亚马逊内部进行的 A/B 测试中得到验证。结果显示,自动化率和客户体验得到了提升,这意味着机器人可以在没有人工干预的情况下解决更多查询,并从客户那里获得更多积极的反馈。
接下来,让我们看看校准可能会以何种方式影响模型的性能。
校准对模型性能的影响
准确率、对数损失和 Brier 分数通常会因为校准而提高。然而,由于模型校准仍然涉及将模型拟合到在保留的校准数据集上绘制的校准曲线,它有时可能会略微降低准确率或其他性能指标。尽管如此,拥有校准概率的好处——即给我们提供实际可解释的概率值,这些值代表可能性——远远超过了轻微的性能影响。
如第一章《机器学习中数据不平衡的介绍》中所述,ROC-AUC 是一个基于排名的指标,这意味着它评估模型区分不同类别的能力是基于预测分数的排名,而不是它们的绝对值。ROC-AUC 对准确的概率估计不做任何声明。严格单调的校准函数,即连续增加或减少而没有任何平坦区域,保留这种排名;它们调整概率的尺度,而不改变它们的相对顺序。例如,如果一个分数在校准之前比另一个分数高,那么它在之后仍然更高。因为 ROC-AUC 关注的是预测的排名而不是实际的概率值,所以它不受这种单调校准函数的影响。
然而,在罕见的情况下,由于校准,紧密排序的预测可能会因为校准而变得相同,特别是如果校准函数是松散单调的并且有平坦部分。这可能会略微影响 ROC-AUC。
摘要
在本章中,我们讨论了模型校准的基本概念、为什么我们应该关注它、如何衡量模型是否校准、数据不平衡如何影响模型校准,以及最后如何校准未校准的模型。我们讨论的一些校准技术包括 Platt 缩放、等调回归、温度缩放和标签平滑。
有了这些,我们这本书就结束了。感谢您抽出时间阅读这本书。我们相信,它已经拓宽了您对处理不平衡数据集及其在机器学习中的实际应用的知识。随着我们这本书的结束,我们想提供一些关于如何有效利用所讨论技术的结论性建议。
与其他机器学习技术一样,本书中讨论的方法在适当的条件下非常有用,但它们也带来了一组自己的挑战。认识到何时何地应用这些技术是至关重要的,因为过于复杂的解决方案可能导致性能不佳。
建立一个可靠的基线解决方案至关重要。实施各种方法,如成本敏感学习中的方法以及算法级别的深度学习技术,可以提供有效处理不平衡数据集的见解。每种方法都有其优缺点。
对于特定问题,本书提供了针对性的解决方案。对于小数据集,过采样方法可以帮助管理计算资源。对于大数据集,关于欠采样方法的章节提供了合适的技巧。
有时,可以应用更现代的方法,如图机器学习算法来解决手头的问题。模型校准和阈值调整技术对于基于模型预测的决策非常有用。
有时,数据不平衡可能根本不是问题,我们强烈建议你在不应用本书中讨论的任何技术的情况下,使用不平衡数据建立基线性能。许多现实世界的数据也倾向于是表格形式,其中基于树的模型如 XGBoost 可以对某些类型的数据不平衡具有鲁棒性。
我们鼓励你应用这些知识,尝试新的方法,并在你在这个领域进步的过程中继续扩展你的专业知识。机器学习的领域不断变化,随着你跟上其发展,你的技能将只会增加价值。我们希望你所获得的知识能够赋予你选择任何你感兴趣的研究论文并能够重现其结果的能力。我们感谢你阅读这本书的承诺,并祝愿你在未来的所有事业中取得成功。
问题
-
一个校准良好的模型是否可能具有较低的准确率?反之,一个具有高准确率的模型是否可能校准不良?
-
以一个有限的分类数据集为例,比如只有 100 个数据点。使用这个数据集训练一个决策树模型,然后评估其校准情况。
-
使用 Platt 的缩放方法校准模型。校准后测量 Brier 分数。
-
使用等调回归校准模型。校准后测量 Brier 分数
-
在(A)和(B)中,Brier 分数有何不同?
-
在校准前后测量模型的 AUC、准确率、精确率、召回率和 F1 分数。
-
-
使用一个平衡的数据集,比如有 10,000 个数据点。使用它来训练一个决策树模型。然后检查其校准情况。
-
使用 Platt 的缩放方法校准模型。校准后测量 Brier 分数。
-
使用等调回归校准模型。校准后测量 Brier 分数。
-
在(a)和(b)中,Brier 分数有何不同?
-
在校准前后测量模型的 AUC、准确率、精确率、召回率和 F1 分数,并比较它们的值。
-
-
对于一个分类数据集,通过比较它们的 Brier 分数来比较以下模型默认情况下未经校准技术校准的校准程度:
-
逻辑回归
-
决策树
-
XGBoost
-
随机森林
-
AdaBoost
-
神经网络
-
-
对一个不平衡的数据集进行训练,分别使用逻辑回归、随机森林模型和 XGBoost 模型。使用校准曲线和 Brier 分数来衡量这些模型的校准情况。最后,应用这些技术来处理数据不平衡并再次测量校准:
-
下采样
-
过采样
-
成本敏感学习:将
class_weight增加一倍。由于将class_weight加倍,模型是否变得校准度更低?
-
参考文献
-
C. Guo, G. Pleiss, Y. Sun, 和 K. Q. Weinberger,"关于现代神经网络的校准"。arXiv,2017 年 8 月 3 日。访问日期:2022 年 11 月 21 日,
arxiv.org/abs/1706.04599。 -
A. Niculescu-Mizil 和 R. Caruana,"使用监督学习预测良好的概率",载于第 22 届国际机器学习会议 - ICML ‘05,德国波恩,2005 年,第 625-632 页。doi: 10.1145/1102351.1102430。
-
J. Mukhoti, V. Kulharia, A. Sanyal, S. Golodetz, P. H. S. Torr, 和 P. K. Dokania,"使用焦点损失校准深度神经网络"。2020 年 2 月,
doi.org/10.48550/arXiv.2002.09437。 -
B. C. Wallace 和 I. J. Dahabreh,"对于不平衡数据,类别概率估计是不可靠的(以及如何修复它们)",载于 2012 年 IEEE 第 12 届数据挖掘国际会议,比利时布鲁塞尔,2012 年 12 月,第 695-704 页。doi: 10.1109/ICDM.2012.115。
-
M. Pakdaman Naeini, G. Cooper, 和 M. Hauskrecht,"使用贝叶斯分箱获得良好校准的概率",AAAИ,第 29 卷,第 1 期,2015 年 2 月,doi: 10.1609/aaai.v29i1.9602。
-
H. Steck,"校准推荐",载于第 12 届 ACM 推荐系统会议论文集,加拿大不列颠哥伦比亚省温哥华:ACM,2018 年 9 月,第 154-162 页。doi: 10.1145/3240323.3240372。
-
A. Caplin, D. Martin, 和 P. Marx,"通过建模机器学习校准类别权重"。arXiv,2022 年 7 月 31 日。访问日期:2022 年 12 月 9 日。[在线]。可在
arxiv.org/abs/2205.04613获取。 -
A. D. Pozzolo, O. Caelen, R. A. Johnson, 和 G. Bontempi,"使用下采样校准不平衡分类的概率",载于 2015 年 IEEE 计算智能系列研讨会,南非开普敦,2015 年 12 月,第 159-166 页。doi: 10.1109/SSCI.2015.33,
dalpozz.github.io/static/pdf/SSCI_calib_final_noCC.pdf -
M. Moscatelli, S. Narizzano, F. Parlapiano 和 G. Viggiano,使用机器学习进行公司违约预测。IT:意大利银行,2019 年。访问日期:2023 年 10 月 14 日。[在线]。可在
doi.org/10.32057/0.TD.2019.1256获取。 -
X. He 等人,"从预测 Facebook 广告点击中汲取的实际经验",载于第八届国际在线广告数据挖掘研讨会论文集,纽约 NY 美国,2014 年 8 月,第 1-9 页。doi: 10.1145/2648584.2648589。
-
G. King 和 L. Zeng,"稀事件数据中的逻辑回归",2001 年。
-
Szegedy, V. Vanhoucke, S. Ioffe, J. Shlens 和 Z. Wojna,"重新思考计算机视觉中的 Inception 架构。" arXiv,2015 年 12 月 11 日。访问日期:2022 年 12 月 17 日。[在线]。可在
arxiv.org/abs/1512.00567获取。 -
R. Müller, S. Kornblith 和 G. Hinton,"何时标签平滑有助于?" arXiv,2020 年 6 月 10 日。访问日期:2022 年 12 月 11 日。[在线]。可在
arxiv.org/abs/1906.02629获取。 -
Zhong 等人,改进长尾识别的校准。CVPR 2021。
arxiv.org/abs/2104.00466 -
B. Lucena,"基于样条的概率校准。" arXiv,2018 年 9 月 20 日。访问日期:2023 年 7 月 22 日。[在线]。可在
arxiv.org/abs/1809.07751获取。 -
K. Gupta, A. Rahimi, T. Ajanthan, T. Mensink, C. Sminchisescu 和 R. Hartley,"使用样条进行神经网络校准。" arXiv,2021 年 12 月 29 日。访问日期:2023 年 7 月 22 日。[在线]。可在
arxiv.org/abs/2006.12800获取。 -
M. Kull 和 P. Flach,"Beta 校准:对二元分类器的逻辑校准的合理且易于实现的改进",载于第二十届国际人工智能与统计会议论文集(第 623-631 页)。
-
C. Wang, J. Balazs, G. Szarvas, P. Ernst, L. Poddar 和 P. Danchenko,"使用焦点损失校准不平衡分类器:一项实证研究",载于 2022 年自然语言处理实证方法会议:工业轨迹论文集,阿布扎比,阿联酋:计算语言学协会,2022 年,第 145-153 页。doi: 10.18653/v1/2022.emnlp-industry.14。
附录
机器学习流水线在生产中的应用
在本附录中,我们将探讨在生产的机器学习流水线中何时以及在哪一步引入数据不平衡处理技术。这主要适用于监督分类问题。
机器学习训练流水线
机器学习流水线是指训练一个或多个机器学习模型并将其部署到实际环境中的端到端过程。它可能包括数据收集、模型训练、验证、部署、监控和迭代改进等阶段,重点关注可扩展性、效率和鲁棒性。
离线训练过程中的各种步骤在图 A.1中展示。请注意,根据具体问题,某些步骤可能不是必需的。

图 A.1 – 机器学习训练流程的高级步骤
构建能够处理数据不平衡的模型所涉及的步骤如下:
-
收集数据:第一步涉及收集训练机器学习模型所需的数据。这些数据可以来自数据库、文件、API 或通过网络爬虫。收集数据后,立即进行数据验证通常是有益的。在这个阶段,可以验证数据模式和数据范围,以及任何自定义的数据验证检查。随后,数据被划分为训练集、验证集和测试集。许多生产系统通常不优先考虑生成验证集。生成这些验证集的主要功能是帮助进行模型调优活动,如超参数调整、早期停止、模型校准、阈值调整等。这种调整通常在模型开发阶段进行,而不是在主生产流程之外。在执行任何数据转换或不平衡处理技术之前,对数据进行拆分至关重要。这种预防措施确保避免了数据泄露,否则可能会导致模型性能偏差。
-
数据转换:下一步是将数据转换成可以轻松输入机器学习模型的格式。这可能涉及数据清洗、特征选择、归一化和缩放等任务。这些转换步骤可能需要存储起来,以便在模型预测时应用。将转换存储在某个地方(例如,文件或数据库)可能会有所帮助,这样可以在后续的推理过程中检索它们。
-
处理数据不平衡(如果需要):由于对多数类的偏差,机器学习模型可能在少数类(或多个少数类)上表现不佳。在这本书中,我们深入探讨了数据级和算法级的技术。总结来说,数据级技术侧重于重采样数据集,以实现每个类别的样本平衡,而算法级技术则修改学习算法以处理不平衡数据。为了更深入的理解,请参考书中的相关章节。
-
训练模型:数据预处理完成后,就是训练机器学习模型的时候了。这一步骤包括以下内容:
-
选择合适的算法
-
设置其超参数
-
将预处理数据输入到算法中
训练过程可能需要多次迭代以微调模型,直到产生令人满意的结果。训练好的模型二进制文件应该进行版本控制并存储起来,以供将来使用,包括部署到生产环境进行在线推理。
如果应用了任何数据不平衡处理技术,可能会使模型失准。如果期望模型提供校准后的预测分数,那么重新校准预测分数至关重要。有关各种模型校准技术的更多信息,请参阅第十章,模型校准。
-
-
评估模型:这一步骤涉及评估训练好的模型在步骤 1中产生的测试集上的性能。对于分类问题,通常使用准确率、精确率和召回率等指标,而对于其他类型的问题,应选择适当的指标。如果模型的性能未达到预期的基准,您可能不仅需要回顾数据转换(如步骤 2中概述的),还需要考虑调整模型的架构、超参数,甚至问题表述。对于二元分类模型,您需要确定一个合适的阈值来对预测进行分类。有关阈值调整技术的更深入信息,请参阅第五章,成本敏感学习。
在成功评估模型后,评估其作为服务的适用性,使其能够处理实时流量或进行批量预测。我们将在下一节更深入地探讨推理。
推理(在线或批量)
推理是一个使用训练好的机器学习模型对新未见数据进行预测的过程。在线推理指的是在实时数据到来时进行预测。在线推理期间,延迟是最重要的,以防止对最终用户造成任何延迟。
另有一种类型称为批量推理,在这种推理中,对已收集的大量数据进行离线预测。

图 A.2 – 当实时数据到来模型进行评分(推理)时的流程图
推理是一个使用训练好的机器学习模型对新输入(未见)数据进行实时预测的过程。推理过程中涉及以下步骤:
-
输入数据:第一步是接收需要分类或预测的新输入数据。这些数据可以是文本、图像、音频或其他任何数据格式。
-
转换数据:在预测之前,输入数据需要经过转换(如归一化和缩放),以便与训练好的模型兼容。应用在训练期间使用的相同转换至关重要。
-
模型预测:一旦输入数据被转换,它就会被输入到训练好的模型中,以生成一个预测分数。预测分数表示输入属于特定类别或类别的可能性。
-
校准分数(如有必要):当模型预测不可靠时,模型校准可能至关重要。值得注意的是,当使用任何数据不平衡处理技术时,模型误校准的风险增加。为了全面了解这一主题,请参阅第十章,模型校准。
-
使用阈值进行最终预测:然后使用校准后的分数,通过适当的阈值进行最终预测,并采取任何行动——例如,通知客户、人工审查等。
🌟 监控生产中的数据和模型
监控数据和其分布至关重要,因为它们可能会随时间变化,可能影响最初应用的任何不平衡处理技术的有效性。这种变化可能会影响模型性能和评估指标,需要重新评估和可能重新校准策略。除了数据不平衡之外,模型漂移和数据漂移等现象——即模型性能或传入数据的性质发生变化——也引起重大关注。实施自动机制以跟踪这些变化对于确保最佳模型性能和一致的预测至关重要。
总结来说,推理涉及将新的输入数据转换为训练好的机器学习模型的预测分数,校准该分数,并使用阈值确定最终预测。此过程对每个需要预测的输入数据点都会重复进行。
评估
第一章 - 机器学习中的数据不平衡简介
-
训练模型时选择损失函数可以极大地影响模型在不平衡数据集上的性能。一些损失函数可能对类别不平衡更敏感。例如,使用交叉熵损失函数训练的模型可能会受到多数类的影响很大,在少数类上的表现可能不佳。
-
当处理高度倾斜的数据集时,PR 曲线比 ROC 曲线更有信息量,因为它关注分类器在正类(少数类)上的性能,这在不平衡数据集中通常是感兴趣的类别。另一方面,ROC 曲线同时考虑了 TPR 和 FPR,因此在负类主导数据集时,可能会对模型性能给出过于乐观的看法。
-
准确率可能是一个误导性的模型性能指标,因为它没有考虑到类别的分布。始终预测多数类的模型将具有高准确率,但如果我们目标是正确分类少数类实例,则这并不有用。
-
在不平衡数据集的背景下,特征工程由于少数类别的实例数量有限而提出独特的挑战。由于示例如此之少,即使是人类专家也很难识别出真正指示少数类别的特征。选择不当的特征可能会加剧问题:如果特征捕捉到的是噪声而不是潜在模式,模型很可能会过拟合。相反,如果特征过于通用且未能捕捉到少数类别的细微差别,模型可能会欠拟合,导致在新未见数据上的表现不佳。
-
在 k 折交叉验证中“k”的选择可能会影响模型在不平衡数据集上的性能。在不平衡数据集中,某些折可能包含非常少的甚至没有来自少数类别的示例,这可能导致对模型评估的误导。解决这个问题的一个方法是使用分层 k 折交叉验证,通过
sklearn.model_selection.StratifiedKFoldAPI 提供,这确保了每个折保持各种类别的相似分布。 -
通常,测试集中的不平衡程度越大,PR 曲线受到的负面影响就越大。相比之下,ROC 曲线不受测试集中类别分布的影响。
在图 1.13和图 1.14中,我们展示了三个不平衡比率为 1:9、1:3 和 1:1 的测试集。所有这些情况的 ROC-AUC 都是 0.96,如图图 1.13所示。另一方面,平均精度值与测试集中不平衡的程度成反比,如图图 1.14所示(即,更大的不平衡导致平均精度更低):

图 B.1 – 当测试集中的不平衡比率变化时,ROC 曲线保持不变

图 B.2 – 当测试集中的不平衡比率变化时,PR 曲线会显著变化
-
在不平衡数据集的背景下,具有高 AUC-ROC 但低 AUC-PR 可能表明模型在总体上区分类别表现良好(如高 AUC-ROC 所示),但在识别正类(少数类)方面做得不好(如低 AUC-PR 所示)。
-
样本偏差可能导致机器学习中不平衡数据集的挑战,因为它可能导致某一类别的过度代表和另一类别的不足代表。这可能会扭曲模型的训练并导致在代表性不足的类别上表现不佳。
-
标记错误可能导致机器学习中不平衡数据集的挑战,因为它们可能导致数据集中类别的错误表示。如果少数类别的实例被错误地标记为多数类,模型可能会学习到错误的模式并在少数类上表现不佳。
-
在许多现实世界场景中,处理不平衡数据集本质上就是问题的一部分。一些例子包括欺诈检测(欺诈交易与合法交易相比很少见)、医疗诊断(疾病与健康病例相比通常很少见)和垃圾邮件检测(垃圾邮件通常比非垃圾邮件少)。你能想到其他的例子吗?
-
这里是答案:
-
其值范围从 -1(最差值)到 +1(最佳值)。
-
空白模型总是预测类别 1,因此这里列出了我们各种混淆矩阵的值:
TP = 90,TN = 0,FP=10,FN = 0
MCC = TP · TN − FP · FN __________________________________ √ ______________________________________ (TP + FP) · (TP + FN) · (TN + FP) · (TN + FN)
混淆矩阵的值如下:
-
TP = 90
-
TN = 0
-
FP = 10
-
FN = 0
将这些值代入公式,我们得到以下结果:
MCC = (90 × 0) − (10 × 0) _____________________________ √ _________________________________ (90 + 10) × (90 + 0) × (0 + 10) × (0 + 0)
= 0 − 0 ______________ √ _______________ 100 × 90 × 10 × 0
由于分母变为零(因为 (TN + FN = 0 + 0) 这一项),MCC 是未定义的。
我们可以按照以下方式计算其他指标:
-
Accuracy = TP+TN/ (TP+TN+FP+FN) = 0.90
-
Precision = TP/(TP+FP) = 90/(90+10) = 0.90
-
Recall = TP/(TP+FN) = 90/(90+0) = 1
-
F1 分数 = 2PrecisionRecall/(Precision+Recall) = 20.901/(0.90+1) = 0.95
让我们计算前述值的 MCC。它是未定义的(0/0),这意味着模型可能存在问题,我们应该回去检查数据或模型中的任何问题。
-
总结来说,MCC 是一个指标,只有当模型在测试集的正负类示例上都表现良好时,才会产生高分。此外,MCC 还可以帮助用户了解持续的预测问题。
-
这留给你作为练习。
-
第二章 – 过采样方法
-
这留给你作为练习。
-
一种方法是通过将少数类过采样 20 倍来平衡两类。需要注意的是,在类之间实现完美的平衡并不总是必要的;轻微的不平衡可能是可接受的,具体取决于特定的需求和限制。这种技术不在测试时间应用,因为测试数据应该代表我们在现实世界中会遇到的情况。
-
在将数据分割成训练、测试和验证集之前,对数据进行过采样的主要问题是数据泄露。这发生在重复样本同时出现在训练和测试/验证集中,导致过度乐观的性能指标。模型在评估期间可能表现良好,因为它已经在训练期间看到了相同的示例,但这可能导致对新、未见数据的泛化能力差。为了减轻这种风险,首先将数据分割成训练、测试和验证集,然后仅将平衡技术(如过采样)应用于训练集。
-
数据归一化可以通过确保所有特征具有相同的尺度来间接帮助处理数据不平衡,这可能导致更好的模型性能。然而,归一化可能不会直接解决数据集中类之间的不平衡。为了解决数据不平衡,可以采用其他技术,例如各种采样技术、成本敏感方法或阈值调整技术。
-
这已被留作你的练习。
第三章 – 欠采样方法
-
这已被留作你的练习。
-
这已被留作你的练习。
-
这已被留作你的练习。
-
TomekLinksNCR是一种自定义的欠采样方法,它结合了 Tomek links 和 NCR。它首先移除 Tomek links,然后应用 NCR 从多数类中移除更多噪声和边缘样本。这旨在创建一个更平衡的数据集,同时保留数据的底层结构。
第四章 – 集合方法
-
这已被留作你的练习。
-
BalancedRandomForestClassifier和BalancedBaggingClassifier之间的主要区别在于它们使用的基分类器和集成学习方法。BalancedRandomForestClassifier使用决策树作为基分类器,并遵循随机森林作为估计器,而BalancedBaggingClassifier可以使用任何支持样本加权的基分类器,并遵循 bagging 方法。随机森林可以被视为 bagging 的扩展,它通过在决策树的每个分裂点随机选择特征子集来引入额外的随机性。这有助于创建更多样化的树,并且通常会导致随机森林模型性能更好。
第五章 – 成本敏感学习
本章的问题已被留作你的练习。
第六章 – 深度学习中的数据不平衡
-
主要挑战源于这些模型处理的数据类型不同。经典的机器学习模型通常处理结构化、表格数据,而深度学习模型处理非结构化数据,如图像、文本、音频和视频。
-
可以通过随机选择每个类的一定百分比的示例来创建 MNIST 数据集的不平衡版本。这个过程涉及选择要移除的样本的索引,然后实际上从训练集中移除这些样本。
-
这已被留作你的练习。
-
随机过采样用于解决数据集中的不平衡。它通过复制少数类的样本,直到每个类都有相同数量的样本来实现。这种技术通常被认为比不采样表现得更好。
-
数据增强技术可以包括旋转、缩放、裁剪、模糊、向图像添加噪声等。然而,确保这些增强保留原始标签,并避免无意中从数据中删除重要细节是至关重要的。请参阅第七章,数据级深度学习方法,以详细了解各种数据增强技术。
-
下采样减少了多数类的实例以平衡数据集。然而,这种方法有一个显著的局限性:如果随机删除多数类的实例,可能会丢失重要信息,特别是当多数类有很多变化时。
-
数据增强技术必须保留原始标签,因为模型学习将数据的特征与这些标签关联起来。如果标签由于增强而改变,模型可能会学习到错误的关联,这会降低其在进行预测时的性能。
第七章 – 数据级深度学习方法
本章的问题已被留作你的练习题。
第八章 – 算法级深度学习技术
-
这已被留作你的练习题。
-
这已被留作你的练习题。
-
Tversky 损失基于 Tversky 指数,其定义如下公式:
TverskyIndex = TruePositive _______________________________________ TruePositive+ α * FalsePositive + (1 − α) * FalseNegative
在分子和分母中添加了一个平滑因子以避免除以零。
alpha是一个可以调整的超参数:import torch import torch.nn.functional as F def Tversky(y_true, y_pred, smooth=1, alpha=0.8): y_true_pos = y_true.view(-1) y_pred_pos = y_pred.view(-1) true_pos = torch.sum(y_true_pos * y_pred_pos) false_neg = torch.sum(y_true_pos * (1 - y_pred_pos)) false_pos = torch.sum((1 - y_true_pos) * y_pred_pos) return (true_pos + smooth) / (true_pos + alpha * false_pos \ + (1 - alpha) * false_neg + smooth) -
这已被留作你的练习题。
第九章 – 混合深度学习方法
-
我们在这里不提供完整的答案,而只提供一些将帮助你完成主要任务的函数。
我们可以使用
torch.nn.functional.triplet_margin_loss(),或者我们可以从头实现它:import torch import torch.nn as nn from torch.nn import functional as F class TripletLoss(nn.Module): def __init__(self, margin=1.0): super(TripletLoss, self).__init__() self.margin = margin def forward(self, anchor, pos, neg): pos_dist = F.pairwise_distance(anchor, pos) neg_dist = F.pairwise_distance(anchor, neg) loss = torch.relu(pos_dist - neg_dist + self.margin) return loss.mean()你会想要为不平衡的 MNIST 数据集生成三元组。以下函数为一批图像生成三元组列表(锚点、正例和负例)。它为批次中存在的每个类别生成一个三元组。我们假设批次中每个类别至少有两个示例:
def generate_triplets(images, labels): triplets = [] classes_present = labels.unique() for c in classes_present: # Find indices of anchor and positive examples pos_indices = (labels == c).nonzero(as_tuple=True)[0] anchor_idx, positive_idx = \ torch.choice(pos_indices, 2, replace=False) anchor, positive = images[anchor_idx], \ images[positive_idx] # Find index of negative example neg_indices = (labels != c).nonzero(as_tuple=True)[0] negative_idx = torch.choice(neg_indices) negative = images[negative_idx] # Add the triplet to the list triplets.append((anchor, positive, negative)) return triplets -
这已被留作你的练习题。
第十章 – 模型校准
-
是的,一个校准良好的模型可以具有较低的准确率,反之亦然。让我们考虑一个总是为任何输入示例输出 0.1 概率的愚蠢模型。这个模型校准得非常完美,但它的准确率只有 90%,对于一个 1:9 不平衡比率的失衡数据集来说相当低。以下是此类模型的实现:
from sklearn.datasets import make_classification from sklearn.calibration import calibration_curve import matplotlib.pyplot as plt import numpy as np # Make an imbalanced binary classification dataset y = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, \ 1, 0, 0, 0, 0, 0, 0]) # Dummy model always predicts not-1 (i.e., 0) with full confidence y_pred = np.array([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,\ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,\ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]) y_pred_labels = np.array([0, 0, 0, 0, 0, 0, 0, 0, 0, \ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) # Calculate the calibration curve fraction_of_positives, mean_predicted_value = \ calibration_curve(y, y_pred) # Calculate accuracy accuracy = (y == y_pred_labels).mean() print('accuracy: ', accuracy) # Plot calibration curves plt.plot([0, 1], [0, 1], "k:", label="Perfectly calibrated") plt.plot(mean_predicted_value, fraction_of_positives,\ "s-", label="Model A") plt.legend() plt.show()这会产生准确率值和校准图:
accuracy: 0.9

图 B.3 – 一个校准完美但准确率得分低的虚拟模型
-
这已被留作你的练习题。
-
这已被留作你的练习题。
-
这已被留作你的练习题。
-
这已被留作你的练习题。


浙公网安备 33010602011771号