不平衡数据的机器学习-二-

128 阅读1小时+

不平衡数据的机器学习(二)

原文:annas-archive.org/md5/d4ae49feed2111c802924e036a1b91f2

译者:飞龙

协议:CC BY-NC-SA 4.0

第六章:深度学习中的数据不平衡

类不平衡数据是深度学习模型的一个常见问题。当一个或多个类别样本显著较少时,深度学习模型的性能可能会受到影响,因为它们倾向于优先从多数类别中学习,从而导致少数类别(或类别组)的泛化能力较差。

许多现实世界的数据是不平衡的,这对深度学习分类任务提出了挑战。图 6.1展示了在各种深度学习应用中常见的不平衡数据问题的一些类别:

图 6.1 – 一些常见的不平衡数据问题类别

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

  • 深度学习简介

  • 深度学习中的数据不平衡

  • 处理数据不平衡的深度学习技术概述

  • 多标签分类

到本章结束时,我们将对深度学习和神经网络有一个基础的理解。我们还将掌握数据不平衡对这些模型的影响,并了解解决不平衡数据挑战的各种策略的高级概述。

技术要求

在本章中,我们将利用常见的库,如numpyscikit-learnPyTorchPyTorch是一个开源的机器学习库,用于深度学习任务,因其灵活性和易用性而近年来越来越受欢迎。

您可以使用pipconda安装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!

autogradPyTorch 中一个强大的功能,它允许对张量操作进行自动微分。这在神经网络中的反向传播(稍后讨论)中特别有用:

# 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,执行一些操作来计算 yz,然后使用 backward() 方法计算 z 关于 x 的梯度。梯度存储在 x.grad 中。操作如下:

  • y = x + 2x 中的每个元素增加 2

  • z = y * y * 3y 中的每个元素平方然后乘以 3

在这种情况下,梯度计算涉及到对这些操作应用链式法则来计算 dz/dx:

dz/dx = dz/dy ⋅ dy/dx

好的,让我们计算这个方程中的每一部分:

  1. 首先,让我们计算 dz/dy:

    dz/dy = 2 * y * 3 = 6 * y = 6 *(x + 2)

  2. dy/dx = 1 因为 yx 的线性函数:

  3. 最后,让我们计算 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.]])

输出张量对应于在特定值 xz 关于 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.BatchNorm1dtorch.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、深度学习的基础知识、激活函数和数据不平衡挑战。我们还对接下来几章将要讨论的各种技术进行了鸟瞰。

理解这些基础知识将使你具备探索更高级主题和应用的知识,并最终为不断发展的深度学习世界做出贡献。

在下一章中,我们将探讨数据层面的深度学习方法。

问题

  1. 将数据不平衡处理方法从经典机器学习模型迁移到深度学习模型中面临哪些挑战?

  2. 如何创建一个不平衡版本的 MNIST 数据集?

  3. 使用 MNIST 数据集训练一个具有不同数据不平衡程度的 CNN 模型。记录模型在固定测试集上的整体准确率。绘制整体准确率随训练数据不平衡程度增加的变化情况。观察整体准确率是否随着训练数据的不平衡而下降。

  4. 使用深度学习模型进行随机过采样有什么目的?

  5. 在处理有限或不平衡数据时,有哪些数据增强技术可以应用?

  6. 在处理数据不平衡时,欠采样是如何工作的,以及它的局限性是什么?

  7. 为什么确保数据增强技术保留数据集的原始标签很重要?

参考文献

  1. A. W. Trask, Grokking Deep Learning (Manning, Shelter Island, NY, 2019)。

  2. F. Chollet, 使用 Python 进行深度学习。Manning Publications,2021 年。

  3. Y. Cui, M. Jia, T.-Y. Lin, Y. Song, 和 S. Belongie, “基于有效样本数的类平衡损失”,第 10 页。

  4. K. Cao, C. Wei, A. Gaidon, N. Arechiga, 和 T. Ma, 具有标签分布感知边缘损失的平衡学习数据集 [在线]。可在proceedings.neurips.cc/paper/2019/file/621461af90cadfdaf0e8d4cc25129f91-Paper.pdf获取。

  5. R. Jantanasukon 和 A. Thammano, 在分类问题中处理不平衡数据的自适应学习率。在 2021 年数字艺术、媒体和技术联合国际会议以及 ECTI 北方分会电气、电子、计算机和电信工程会议,泰国差安,IEEE,2021 年 3 月,第 229–232 页,doi: 10.1109/ECTIDAMTNCON51128.2021.9425715。

  6. H.-J. Ye, H.-Y. Chen, D.-C. Zhan, 和 W.-L. Chao, 在不平衡深度学习中识别和补偿特征偏差。arXiv,2022 年 7 月 10 日。访问日期:2022 年 12 月 14 日。[在线]。可在arxiv.org/abs/2001.01385获取。

  7. 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。

  8. M. Buda, A. Maki, 和 M. A. Mazurowski, 关于卷积神经网络中类不平衡问题的系统研究。Neural Networks,第 106 卷,第 249–259 页,2018 年 10 月,doi: 10.1016/j.neunet.2018.07.011。

  9. K. Ghosh, C. Bellinger, R. Corizzo, B. Krawczyk, 和 N. Japkowicz, 在深度学习中类不平衡和概念复杂性的综合影响。arXiv,2021 年 7 月 29 日。访问日期:2023 年 3 月 28 日。[在线]。可在arxiv.org/abs/2107.14194获取。

  10. K. Ghosh, C. Bellinger, R. Corizzo, B. Krawczyk, 和 N. Japkowicz, 在深度学习中类不平衡和概念复杂性的综合影响. arXiv,2021 年 7 月 29 日。访问时间:2023 年 3 月 28 日。[在线]。可在 arxiv.org/abs/2107.14194 获取。

  11. J. M. Johnson 和 T. M. Khoshgoftaar, 使用神经网络进行医疗保险欺诈检测. 大数据杂志,第 6 卷,第 1 期,第 63 页,2019 年 12 月,doi: 10.1186/s40537-019-0225-0.

  12. 医疗保险欺诈与滥用:预防、检测和报告. 医疗保险与医疗补助服务中心。2017。www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/MLN-Publications-Items/MLN4649244.

  13. R. Shwartz-Ziv 和 A. Armon, 表格数据:深度学习并非一切所需. arXiv,2021 年 11 月 23 日。访问时间:2023 年 4 月 10 日。[在线]。可在 arxiv.org/abs/2106.03253 获取。

  14. V. Borisov, T. Leemann, K. Sessler, J. Haug, M. Pawelczyk, 和 G. Kasneci, 深度神经网络和表格数据:综述. IEEE 交易神经网络与学习系统,第 1-21 页,2022 年,doi: 10.1109/TNNLS.2022.3229161.

  15. A. N. Tarekegn, M. Giacobini, 和 K. Michalak, 不平衡多标签分类方法综述. 模式识别,第 118 卷,第 107965 页,2021 年 10 月,doi: 10.1016/j.patcog.2021.107965.

第七章:数据级别深度学习方法

在前面的章节中,你已经了解了各种采样方法。在这本书中,我们将这些方法统称为数据级别方法。这些方法包括随机欠采样、随机过采样、NearMiss 和 SMOTE。我们还探讨了这些方法如何与经典机器学习算法协同工作。

在本章中,我们将探讨如何将熟悉的采样方法应用于深度学习模型。深度学习为这些方法提供了进一步改进的独特机会。我们将深入研究将深度学习与过采样和欠采样相结合的优雅技术。此外,我们还将学习如何使用基本神经网络实现各种采样方法。我们还将介绍动态采样,这涉及到在多个训练迭代中调整数据样本,并为每个迭代使用不同的平衡比率。然后,我们将学习使用一些数据增强技术来处理图像和文本。我们将通过强调来自各种其他数据级别技术的关键要点来结束本章。

本章将涵盖以下主题:

  • 准备数据

  • 深度学习模型的采样技术

  • 文本分类的数据级别技术

  • 关于其他数据级别深度学习方法和其关键思想的讨论

将处理数据不平衡的方法从在经典机器学习模型上表现良好的方法迁移到深度学习领域并不总是直接的。深度学习模型与经典机器学习模型的主要挑战和机遇不同,主要是因为这些模型必须处理的数据类型不同。虽然经典机器学习模型主要处理表格和结构化数据,但深度学习模型通常处理非结构化数据,如图像、文本、音频和视频,这与表格数据有根本性的不同。

我们将讨论各种处理计算机视觉中不平衡问题的技术。在本章的第一部分,我们将重点关注各种技术,例如采样和数据增强,以处理在图像和文本数据上训练卷积神经网络时的类别不平衡问题。

在本章的后半部分,我们将讨论可以应用于文本问题的常见数据级别技术。许多计算机视觉技术也可以成功地应用于自然语言处理问题。

技术要求

与前几章类似,我们将继续使用常见的库,如torchtorchvisionnumpyscikit-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 数据)、torchrandomcopy库。

接下来,我们可以从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下载了数据,我们就可以将其加载到PyTorchDataloader实用工具中,它创建数据批次并提供对批次的 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.nntorch.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)

在本章的所有与图像相关的方法中,我们将使用下面详细说明的模型代码。我们将创建一个名为NetPyTorch神经网络,它具有两个卷积层、一个 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类中,有两个这样的层——conv1conv2。数字(1, 10)(10, 20)仅仅是输入和输出通道。术语kernel_size=5意味着使用一个 5 x 5 的网格(或过滤器)来扫描输入。

  • conv2_drop是一个类型为Dropout2d的 dropout 层,专门设计用于 2D 数据(如图像)。

  • fc1fc2,它们进一步处理卷积层识别出的模式,以进行预测。

  • 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 方法中找到,例如RandomForestClassifierLogisticRegression),具有为训练数据集中的每个样本分配权重的类似目的。我们在第五章,“成本敏感学习”中对class_weightsample_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 = 所有类别中样本数量的平均值

对于特定的训练周期,假设我们在验证数据集上对三个类别,ABC,得到了以下 F1 分数:

类别F1 分数
A0.1
B0.2
C0.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显示了使用各种采样技术(包括基线,其中不进行采样)的每类模型准确率。

类别基线加权随机采样器动态采样器随机过采样随机欠采样
099.999.092.499.197.2
199.799.296.899.290.7
298.598.393.598.570.8
397.397.496.898.374.4
498.398.091.998.679.6
596.296.097.398.152.8
694.597.698.797.377.6
789.794.796.594.181.1
863.391.596.993.060.2
950.792.697.791.856.5

表 7.2 – 使用各种采样技术的每类模型准确率比较(类别的最高值用粗体表示)

下面是一些从结果中得到的见解:

  • 在整体性能方面,随机过采样ROS)表现最佳,而随机欠采样RUS)表现最差。

  • 虽然 ROS 表现最佳,但由于数据克隆,它可能计算成本非常高,这使得它不太适合大型数据集和工业环境。

  • 动态采样比 ROS 略差;它在少数类别 6-9 上表现最佳,将是我们的首选选择。然而,由于其复杂性增加,我们的第二选择将是加权随机采样器。

  • 基线和加权随机采样器技术在类别间表现稳定;RUS 在大多数类别上表现明显不稳定且表现不佳。

同样,我们可以像使用RandomOverSampler一样应用 SMOTE。请注意,虽然 SMOTE 可以应用于图像,但其对原始数据线性子空间的利用通常受到限制。

这结束了我们对各种采样技术的讨论。在下一节中,我们将专注于专门为图像设计的用于实现更有效过采样的数据增强技术。

视觉数据增强技术

现在,各种定制的增强技术被用于各种类型的数据,如图像、音频、视频,甚至文本数据。在视觉领域,这包括旋转、缩放、裁剪、模糊、向图像添加噪声以及其他许多技术,包括将这些技术一次性以适当的顺序组合在一起。图像数据增强并不是一项最近才出现的创新。例如,一些图像增强技术也可以在 1998 年的 LeNet-5 模型论文[2]中找到。同样,2012 年的 AlexNet 模型[3]使用随机裁剪、翻转、改变 RGB 通道的颜色强度等方法来减少模型训练过程中的错误。

让我们讨论一下为什么数据增强通常很有帮助:

  • 在我们数据有限或不平衡的问题中,并不总是能够收集到更多的数据。这可能是因为收集更多数据本身就很困难(例如,在处理信用卡欺诈时等待更多欺诈发生,或者收集卫星图像,我们必须支付卫星运营商的费用,这可能相当昂贵)或者标记数据很困难或昂贵(例如,为了标记医学图像数据集,我们需要领域专家)。

  • 数据增强可以帮助减少过拟合并提高模型的总体性能。这种做法的一个动机是,训练集中的属性如光照、噪声、颜色、比例和焦点可能与我们运行推理的真实世界图像中的属性不一致。此外,增强多样化数据集可以帮助模型更好地泛化。例如,如果模型仅在面向右的猫的图像上训练,它可能在面向左的猫的图像上表现不佳。因此,始终应用有效的变换来增强图像数据集是明智的,因为大多数模型在更多样化的数据上都能获得性能提升。

数据增强在计算机视觉任务中如目标检测、分类和分割等方面被广泛使用。它对于自然语言处理任务也非常有用。在计算机视觉领域,有许多开源库帮助标准化各种图像增强技术,而数据增强在自然语言处理工具方面尚未成熟。

虽然有几个流行的开源库用于图像增强,如 imgaug、Facebook 的 AugLyAlbumentations,但在这本书中我们将使用 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 数据集)

根据给定数据,以下是一些关于各种技术的见解,特别是在不平衡数据的情况下:

  • 整体性能CutmixRemix在大多数类别中通常提供最高的性能,其次是MixupCutmix+Mixup基线似乎在总体上效果最差。

  • 少数类别的性能:对于标记为“6”的少数类别,所有技术与其他类别相比都表现出相对较低的性能。然而,MixupCutmix+Mixup在基线之上提供了一点点改进。

  • 类别间的一致性CutmixMixup在不同类别间更为一致,除了类别“6”,在那里它们仅略有改进。另一方面,基线显示出显著的变异性,在某些类别(如“0”和“5”)上表现极好,但在其他类别(如“6”)上表现较差。

  • 适用于特定类别的技术Cutmix在标记为“1”和“8”的类别中表现出色,在这些类别中它优于所有其他技术。

    Remix在标记为“2”的类别中特别强大,它超越了所有其他技术。

  • 复杂性与收益Cutmix+MixupCutmixMixup单独使用相比,并没有带来显著的改进,这引发了额外的计算复杂性是否合理的疑问。

  • 泛化性CutmixMixup似乎是最稳健的技术,在大多数类别中表现出高性能。这些技术在未见过的数据上可能表现良好,并且对于不平衡数据集可能是好的选择。

  • 权衡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,这表明模型现在在处理两个类别(hamspam)方面更加均衡。加权平均指标仍然强劲,进一步证实了模型的整体性能得到了提升,而没有牺牲其正确分类大多数类别(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.970.800.880.97
随机过采样0.930.910.920.98
反向翻译0.960.860.910.98
EDA0.960.880.910.98

表 7.4 – 比较各种 NLP 数据级技术对垃圾邮件类别的结果(每个指标的最大值以粗体显示)

总体而言,如表 7.4所示,对于我们的数据集,随机过采样在垃圾邮件的召回率方面表现优异,但略微降低了精确度。反向翻译在略微牺牲召回率的同时提高了精确度。EDA 在两者方面都提供了平衡的改进。需要注意的是,这些结果是经验性的,并且特定于用于此分析的数据集。数据增强技术可能会产生不同的结果,这取决于数据的性质、其分布以及所解决的问题。因此,虽然这些技术在当前背景下显示出希望,但它们在不同数据集或 NLP 任务中的应用效果可能会有所不同。

由于篇幅限制,本书将不会涵盖短语级增强技术,但我们建议您自行探索。

接下来,我们将从高层次上查看一些其他的数据级深度学习技术。

其他数据级深度学习方法和其关键思想的讨论

除了之前讨论的方法之外,还有一系列专门设计来处理不平衡数据挑战的其他技术。本节提供了这些替代方法的概述,每个方法都提供了独特的见解和潜在优势。虽然我们只会触及它们的关键思想,但我们鼓励您深入研究文献,并在发现这些技术有趣时进一步探索。

两阶段学习

两阶段学习[16][17]是一种旨在在不损害多数类性能的情况下,提高多类分类问题中少数类性能的技术。该过程涉及两个训练阶段:

  1. 在第一阶段,首先在平衡每个类别的数据集上训练一个深度学习模型。平衡可以通过使用随机过采样或欠采样等技术来实现。

  2. 在第二阶段,我们冻结所有层除了最后一层,然后使用整个数据集对模型进行微调。

第一阶段确保所有层都在平衡数据集上训练。第二阶段通过使用整个数据集重新训练最后一层来校准输出概率,反映了原始不平衡的类别分布。

两个阶段的顺序可以颠倒——也就是说,第一个模型在完整的不平衡数据上训练,然后在第二阶段在平衡数据集上进行微调。这被称为延迟采样,因为采样是在之后进行的。

扩展过采样

由 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,这些技术是在垃圾邮件/非垃圾邮件数据集上运行的。

在下一章中,我们将探讨一些基于算法的方法来处理不平衡数据集。

问题

  1. 将 Mixup 插值应用于本章中使用的 Kaggle 垃圾邮件检测 NLP 数据集。看看 Mixup 是否有助于提高模型性能。您可以参考 Guo 等人撰写的论文《使用 Mixup 增强数据以进行句子分类:一项实证研究》以获取更多信息。《使用 Mixup 增强数据以进行句子分类:一项实证研究》论文链接

  2. 参考 FMix 论文[21]并实现 FMix 增强技术。将其应用于 Caltech101 数据集。看看使用 FMix 是否比基线模型性能有所提高。

  3. 将本章中描述的 EOS 技术应用于 CIFAR-10-LT(CIFAR-10 的长尾版本)数据集,并查看模型性能是否对最不平衡的类别有所提高。

  4. 将本章中学习的 MDSA 技术应用于 CIFAR-10-LT 数据集,并查看模型性能是否对最不平衡的类别有所提高。

参考文献

  1. 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。

  2. LeNet-5 论文,基于梯度的学习应用于文档分类vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf

  3. AlexNet 论文,使用深度卷积神经网络进行 ImageNet 分类papers.nips.cc/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html

  4. *利用实时用户行为来个性化 Etsy 广告(2023): www.etsy.com/codeascraft/leveraging-real-time-user-actions-to-personalize-etsy-ads

  5. Booking.com上的自动图像标记(2017): booking.ai/automated-image-tagging-at-booking-com-7704f27dcc8b

  6. *通过使用从 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

  7. 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。

  8. H. Zhang, M. Cisse, Y. N. Dauphin, 和 D. Lopez-Paz, “mixup: 超越经验风险最小化。” arXiv,2018 年 4 月 27 日。访问日期:2023 年 2 月 11 日。[在线]。可获取:arxiv.org/abs/1710.09412

  9. R. Geirhos, C. R. M. Temme, J. Rauber, H. H. Schütt, M. Bethge, 和 F. A. Wichmann, “人类和深度神经网络的泛化。”

  10. 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

  11. 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.

  12. Grab 的图像中保护个人数据 (2021):engineering.grab.com/protecting-personal-data-in-grabs-imagery.

  13. M. Bayer, M.-A. Kaufhold, 和 C. Reuter,"文本分类的数据增强综述",ACM Comput. Surv.,第 55 卷,第 7 期,第 1-39 页,2023 年 7 月,doi: 10.1145/3544558.

  14. 使用数据增强和采样提高我们机器学习 WAF 的准确性 (2022), Vikram Grover: blog.cloudflare.com/data-generation-and-sampling-strategies/.

  15. NLP 的数据增强: github.com/makcedward/nlpaug.

  16. B. Kang et al.,"解耦表示和分类器以实现长尾识别",arXiv,2020 年 2 月 19 日。访问日期:2022 年 12 月 15 日。[在线]。可获取:arxiv.org/abs/1910.09217.

  17. K. Cao, C. Wei, A. Gaidon, N. Arechiga, 和 T. Ma,"使用标签分布感知边缘损失的平衡数据集学习",[在线]。可获取:proceedings.neurips.cc/paper/2019/file/621461af90cadfdaf0e8d4cc25129f91-Paper.pdf.

  18. D. Dablain, C. Bellinger, B. Krawczyk, 和 N. Chawla,"不平衡深度学习的有效增强",arXiv,2022 年 10 月 17 日。访问日期:2023 年 7 月 23 日。[在线]。可获取:arxiv.org/abs/2207.06080.

  19. R. He et al.,"生成模型合成的合成数据是否适用于图像识别?" arXiv,2023 年 2 月 15 日。访问日期:2023 年 8 月 6 日。[在线]。可获取:arxiv.org/abs/2210.07574.

  20. 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.

  21. 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_weightsample_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_losstorch.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 中的许多其他损失函数,包括NLLLossMultiLabelSoftMarginLossMultiMarginLossBCELoss,也接受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 的datasetstransformers库。让我们导入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 – 使用无类别加权(左)和有类别加权(右)的混淆矩阵

如我们所见,少数类别ABBRDESC在类别加权后性能有所提升,但ENTY类别的性能有所下降。此外,观察一些非对角线项,我们可以看到ABBRDESC类别之间的混淆(图 8*.6*(左)中的 0.33)以及DESCENTY类别之间的混淆(图 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')

对于所使用的模型和数据集,alphagamma 值可能难以调整。在 CIFAR10-LT(CIFAR10 数据集的长尾版本)上使用alpha值为0.25gamma值为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 的准确率有所提高:

类别交叉熵损失类平衡损失
099.999.0
199.699.0
298.197.3
396.894.7
497.797.5
594.297.4
692.898.3
781.294.3
863.693.8
949.191.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 损失和类别难度平衡损失的讨论提供了,每个都引入了独特的策略来动态调整权重或调节模型在简单样本和挑战性样本之间的关注点,旨在提高不平衡数据集上的性能。

总结来说,算法级技术通常以某种方式修改模型使用的损失函数,以适应数据集中的不平衡。它们通常不会增加训练时间和成本,与数据级技术不同。它们非常适合数据量大的问题或领域,或者收集更多数据困难或昂贵的情况。

即使这些技术提高了少数类的性能,但有时多数类可能会因此受到影响。在下一章中,我们将探讨一些混合技术,这些技术可以结合数据级和算法级技术,以便我们可以兼得两者之长。

问题

  1. 平均假错误和平均平方假错误:

    王等人[16]提出,在高数据不平衡的情况下,由于大量占主导地位的负样本,常规损失函数无法很好地捕捉少数类别的错误。因此,他们提出了一种新的损失函数,其主要思想是将训练错误分为四种不同的错误:

    • 假阳性错误(FPE)= (1/负样本数量) * (负样本中的错误)

    • 假阴性错误(FNE)= (正样本数量/1) * (正样本错误)

    • 平均错误(MFE)= FPE + FNE

    • 均方误差(MSFE)= FPE² + FNE²

    此处的错误可以使用常用的交叉熵损失或任何其他用于分类的损失来计算。为不平衡的 MNIST 和 CIFAR10-LT 数据集实现 MFE 和 MSFE 损失函数,并查看模型性能是否优于交叉熵损失的基线。

  2. 在本章中,在实现 CDT 损失函数时,将不平衡的 MNIST 数据集替换为 CIFAR10-LT(CIFAR-10 的长尾版本)。检查您是否仍然能够超过基线实现改进性能。您可能需要调整 gamma 值或执行原始论文[12]中提到的其他技巧之一,以在基线之上获得改进。

  3. Tversky 损失函数在 Salehi 等人发表的论文[17]中提出。请阅读这篇论文以了解 Tversky 损失函数及其实现细节。最后,在一个不平衡的 MNIST 数据集上实现 Tversky 损失,并比较其与基线模型的表现。

  4. 在本章中,我们使用了类权重技术和交叉熵损失与 trec 数据集。将交叉熵损失替换为焦点损失,并查看模型性能是否有所提高。

参考文献

  1. DALL·E 2 预训练缓解措施,2022,openai.com/research/dall-e-2-pre-training-mitigations.

  2. BERT Does Business:在 Wayfair 实施 BERT 模型进行自然语言处理,2019,www.aboutwayfair.com/tech-innovation/bert-does-business-implementing-the-bert-model-for-natural-language-processing-at-wayfair.

  3. K. Cao, C. Wei, A. Gaidon, N. Arechiga, 和 T. Ma,通过标签分布感知边缘损失学习不平衡数据集,[在线]。可在proceedings.neurips.cc/paper/2019/file/621461af90cadfdaf0e8d4cc25129f91-Paper.pdf获取。

  4. T.-Y. Lin, P. Goyal, R. Girshick, K. He, 和 P. Dollár,密集目标检测的焦点损失。arXiv,2018 年 2 月 7 日,arxiv.org/abs/1708.02002

  5. Wang 等人,Imbalance-XGBoost:利用加权损失和焦点损失进行 XGBoost 的二进制标签不平衡分类arxiv.org/pdf/1908.01672.pdf

  6. LightGBM 的焦点损失实现maxhalford.github.io/blog/lightgbm-focal-loss

  7. PASCAL VOC 项目host.robots.ox.ac.uk/pascal/VOC/.

  8. fastai 库,2018 年,github.com/fastai/fastai1/blob/master/courses/dl2/pascal-multi.ipynb

  9. 社区标准报告,2019 年,ai.meta.com/blog/community-standards-report/

  10. Y. Cui, M. Jia, T.-Y. Lin, Y. Song, 和 S. Belongie,基于有效样本数量的类别平衡损失,第 10 页。

  11. X. Zhang 等人,屏幕识别:从像素创建移动应用程序的可访问元数据,载于 2021 年 CHI 会议关于人机交互系统中的因素论文集,日本横滨:ACM,2021 年 5 月,第 1–15 页。doi: 10.1145/3411764.3445186。博客:machinelearning.apple.com/research/mobile-applications-accessible

  12. H.-J. Ye, H.-Y. Chen, D.-C. Zhan, 和 W.-L. Chao, 在不平衡深度学习中识别和补偿特征偏差。arXiv,2022 年 7 月 10 日。访问时间:2022 年 12 月 14 日。[在线]。可在arxiv.org/abs/2001.01385获取。

  13. S. Sinha, H. Ohashi, 和 K. Nakamura,用于解决类别不平衡的类别难度平衡损失。arXiv,2020 年 10 月 5 日。访问时间:2022 年 12 月 17 日。[在线]。可在arxiv.org/abs/2010.01824获取。

  14. S. Alshammari, Y.-X. Wang, D. Ramanan, 和 S. Kong,通过权重平衡进行长尾识别,载于 2022 年 IEEE/CVF 计算机视觉和模式识别会议(CVPR),美国路易斯安那州新奥尔良,2022 年 6 月,第 6887–6897 页。Doi: 10.1109/CVPR52688.2022.00677。

  15. W. Ding, D.-Y. Huang, Z. Chen, X. Yu, 和 W. Lin,使用非常深的网络进行高度不平衡类别分布的人脸动作识别,载于 2017 年亚太信号与信息处理协会年会和会议(APSIPA ASC),吉隆坡,2017 年 12 月,第 1368–1372 页。doi: 10.1109/APSIPA.2017.8282246。

  16. 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。

  17. 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 的更真实的数据集,为处理现实场景中的不平衡问题提供新的视角。

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

  • 不平衡数据的图机器学习

  • 硬示例挖掘

  • 少数类增量校正

到本章结束时,我们将熟悉一些混合方法,使我们能够理解更复杂技术背后的核心原理。

技术要求

与前几章类似,我们将继续使用常见的库,如numpypandassklearntorch。对于图机器学习,我们将使用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 时,会发生以下情况:

  1. 我们将所有相邻图像的特征向量进行聚合。相邻图像是具有匹配 IP 地址或用户 ID 的图像。

  2. 使用平均函数来组合向量。让我们称组合向量为邻域平均向量。

  3. 使用权重矩阵(例如,5x1 大小,如图 图 9.4 所示)乘以邻域平均向量。

  4. 然后,将激活函数应用于结果以获得一个单一值,这表明图像是垃圾邮件的可能性。

图 9.4 – 单个 GCN 层的工作原理

第 4 步 – 双层 GCN

多层 GCN,就像传统的深度神经网络一样,可以被堆叠成多层:

  • 原始节点特征输入到第一层

  • 后续层的输入是前一层的结果

随着每层增加,GCN 能够掌握更广泛的邻域信息。例如,在两层 GCN 中,信息可以在图中传播两个跳数。

在对图 ML 和 GCN 的基础理解到位后,我们将探索一个案例研究。我们将比较图模型与其他模型(包括经典 ML 模型)在不平衡图数据集上的性能。我们的目标是确定图模型是否可以通过利用图结构之间的关系超越其他模型。

案例研究 – XGBoost、MLP 和 GCN 在不平衡数据集上的性能

我们将使用来自 PyTorch GeometricPyG) 库的 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 中的特征和标签:

12127标签
0-0.262576-0.276483-0.2238360
1-0.262576-0.276483-0.1286342
2-0.262576-0.265053-0.2238361
..................
22468-0.262576-0.276483-0.2181481
22469-0.232275-0.276483-0.2212750

表 9.1 – 每行显示特征值的数据集;最后一列显示每个数据点的标签

打印的整体结果如下:

22470 rows × 129 columns

这 127 个特征是通过从页面描述文本中使用 Doc2Vec 技术生成的。这些特征就像每个 Facebook 页面的嵌入向量。

图 9.5中,我们使用 Gephi(一种图形可视化软件)可视化数据集:

图 9.5 – Facebook 页面-页面数据集 Gephi 可视化

该图包含跨类别和类别内的连接,但后者更为突出,突显了同一类别内的相互类似亲和力。这导致了不同的集群,提供了对 Facebook 上强大的类别内关联的鸟瞰图。如果我们分析原始数据集中各种类别的数据,它们的比例并不那么不平衡。因此,让我们通过随机删除一些节点来添加一些不平衡(如图9.6所示):

类别原始数据集中节点的数量删除一些节点后的节点数量
03,3273,327
16,4951,410
26,880460
35,768256

表 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中,我们比较了各种模型的整体准确率以及按类别划分的准确率:

准确率百分比MLPXGBoostGCN
总体76.583.990.9
类别 084.995.296.6
类别 172.978.088.1
类别 233.357.171.4
类别 368.837.575.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]。以下是该技术的关键步骤:

  1. 每个批次中的类别识别

    • 二元分类:如果一个类别占批次不到 50%,我们将其视为少数类。其余的是多数类。

    • 多类分类:我们定义所有少数类为那些总共不超过批次 50%的类别。其余类别被视为多数类。

  2. 计算类别 校正损失

    • 定位 具有挑战性的样本

      • 寻找 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 的距离,并增加一个边界。
  3. 制定最终 损失函数

    • 类别不平衡校正:我们通过引入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)9392799399
胡须(1:24)8890608893
灰白头发(1:23)9090889096
白皙皮肤(1:22)8182788092
双下巴(1:20)8384808489

表 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 的正则化目标函数,因此它被认为是一种结合了数据级和算法级深度学习技术的混合方法。

我们希望这一章能让你对从新技术中提取关键见解并理解其核心思想充满信心,这些思想直接来自研究论文。

在下一章中,我们将讨论模型校准的重要性以及一些流行的校准机器学习模型的技术。

问题

  1. 将三元组损失应用于不平衡的 MNIST 数据集,并查看模型的性能是否优于使用交叉熵损失函数。

  2. 将少数类增量校正技术应用于不平衡数据集——CIFAR10-LT 和 CIFAR100-LT。对于 MNIST-LT 数据集上此技术的参考实现,您可以参考附带的 GitHub 笔记本。

参考文献

  1. 欺诈检测:利用关系图学习检测共谋(2021www.uber.com/blog/fraud-detection/.

  2. 欺诈检测图(2022engineering.grab.com/graph-for-fraud-detection.

  3. A. Shrivastava, A. Gupta, 和 R. Girshick, “基于在线硬样本挖掘的区域检测器训练,” 发表于 2016 年 IEEE 计算机视觉与模式识别会议(CVPR),拉斯维加斯,内华达州,美国,2016 年 6 月,第 761–769 页: doi: 10.1109/CVPR.2016.89.

  4. 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.

  5. Q. Dong, S. Gong, 和 X. Zhu, “不平衡深度学习的类矩形校正硬挖掘,” 发表于 2017 年 IEEE 国际计算机视觉会议(ICCV),威尼斯,2017 年 10 月,第 1869–1878 页. doi: 10.1109/ICCV.2017.205.

  6. Q. Dong, S. Gong, 和 X. Zhu, “通过少数类增量校正的不平衡深度学习。” arXiv,2018 年 4 月 28 日. 访问时间:2022 年 7 月 26 日. [在线]. 可用:arxiv.org/abs/1804.10851.