BERT:文本偏见检测

社区文章 发布于2024年8月17日

image/png

本文将逐步介绍如何构建一个2024年的二元偏见分类BERT模型。它也是我们在即将于9月发布的《伦理奇观研究(Ethical Spectacle Research)GUS-Net论文》中构建的部分架构的预览。

模型🔬 | 试用🧪 | 训练ipynb 💻


从理解BERT开始:

Transformer架构的引入,在编码-解码过程的两端,带来了两个特别具有革命性的模型。其中一个你可能很熟悉……OpenAI的GPT是通过堆叠解码器诞生的,而谷歌的BERT则堆叠编码器。

编码

BERT架构输出的“编码”捕获了标记(标记/单词两侧)在编码中的上下文信息。在继续阅读之前,请先理解这一点;实际上,每个BERT编码都包含每个标记的含义及其使用上下文的信息。这些编码可以通过为您的任务添加正确的输出层,用于情感分析或实体识别等NLP任务。这些编码(即表示句子含义的数字)正是我们进行分类所需要的。以下是BERT的自注意力机制如何捕获序列中标记之间关系的图示:

image/jpeg

BERT用于二元分类

由于我们正在对偏见进行分类,我们的输出层只需要一个神经元(公平为0,偏见为1)。从高层次来看,每个分类都将遵循以下过程:

  1. 使用BERT分词器对输入字符串进行分词。输出:与BERT词汇表相关的标记ID(包括CLS、SEP、UNK、PAD)和注意力掩码(主要用于忽略PAD标记)。
  2. 一个经过微调的BERT将创建编码(768个固定大小的向量)。
  3. 一个额外的线性层将把编码转换为“公平”或“偏见”的分类(768 -> 1个神经元)。
  4. 我们将使用Sigmoid函数激活输出,以将输出归一化(0到1)。

BERT模型众多……

许多人都使用不同的训练语料和架构差异(ColBERTRoBERTaDistilBERT等)重新实现了BERT。在本文中,我将权衡现代偏见检测数据科学的实际挑战,并探讨各种(普通)BERT架构在二元分类任务上的结果,但相同的实验也可以通过对输出层的编码管理(池化)进行简单更改来使用变体模型完成。


理解评估指标:

本文深入探讨了多种架构和训练参数的具体指标及解释结果,因此我们首先回顾一下在此过程中哪些指标是重要的。

键:TP - 真阳性,TN - 真阴性,FP - 假阳性,FN - 假阴性

  • 准确率 = (TP + TN) / 总记录数 - 正确预测占总分类的比例。
  • 精确率 = TP / (TP + FP) - 正向预测中实际正确的比例。
  • 召回率:TP / (TP + FN) - 模型正确预测的真阳性比例。
  • F1分数:2 x ((精确率 x 召回率) / (精确率 + 召回率)) - 调和平均值,如果数据集偏向正/负样本,可以将其视为平衡的准确率。
  • 混淆矩阵:这些有助于可视化模型的准确性并指出潜在问题。如图所示,我们希望最大化测试集中落入TP和TN单元格的预测部分。

image/png


设计神经网络:

回想一下我之前讨论的分类过程;这个管道将由我们的PyTorch前向方法执行。我们的神经网络的核心将是这样的。请记住,我们将使用分词器将我们的初始文本序列转换为BERT层所需输入ID和注意力掩码。

image/jpeg

我们模型的前向传播,由BERT层、我们的输出层和激活函数组成,将把来自分词器的输入ID和注意力掩码转换为0到1的偏见分类预测(作为概率)。

需要了解的:BERT-base有12个Transformer层,每层有12个注意力头。这些注意力头本质上是12个自注意力机制,用于衡量序列中不同标记之间相对重要性。此外,BERT(base)在所有层中都保持768个维度。

训练我们的模型

为了更新我们模型在分类任务中的权重,在训练中我们可以衡量我们的预测与数据集真实标签的接近程度(即损失),用于反向传播计算梯度(即应如何更改权重),最后,通过优化器应用这些更改。

image/jpeg

损失函数(二元交叉熵)

我们可以使用损失函数通过比较预测标签和真实标签来计算模型预测的正确程度。PyTorch.nn模块中有很多损失函数,你可能想了解一下,你也可以自己创建。但对于本文中的实验,我们将使用久经考验的二元交叉熵损失

反向传播

这是机器学习的基础部分,我们在此过程中反向遍历管道并计算梯度。这些梯度捕获了权重变化将对输出产生何种影响的信息,因此可以用于后续优化层。

优化器(AdamW)

优化器将学习率应用于梯度,从而得到新的权重。学习率定义了在每个优化器步骤中更新权重的幅度。

我们将使用一种常见的优化器,称为AdamW,它在底层处理了一些有用的逻辑,例如动量平滑和权重衰减

调度器(带热身期的线性调度器)

为了从我们的训练过程中榨取一点额外的“果汁”,我们将使用学习率调度器。这会更新学习率,使其更适合训练的不同部分。

我们将使用带有热身期的线性调度器。这意味着在最初的x步中,学习率将从0增长到我们定义的学习率,然后缓慢下降。这有助于避免在模型尚未看到数据之前进行大的更改,并通过在训练期间进行更精确的更改来改善损失。我们的学习率调度器看起来像这样:

image/png

评估

我们的训练数据集BABEv3附带了一个测试集。我们可以使用它来检查我们编译后的模型在一个精心策划的1000条记录数据集上的准确性,而不是从我们的训练数据集中分割出一部分。

它也很有帮助,因为我很确定Dbias没有在这个测试集上进行训练。如果我们从训练数据集中分割出一些数据,Dbias会有一点优势,因为它在一些完全相同的例子上进行了训练(MBAD是大约1.7k条BABAE记录)。

在训练期间,您会希望绘制训练损失和验证损失图。该笔记本正在使用TensorBoard,而我正在使用PyTorch Lightning来记录一些您应该查看的有用指标。


训练模型:

当我们开始寻找最合适的架构和训练参数时,有几点需要考虑:

训练集大小:3.12k条记录

我正在使用BABEv3数据集进行这些实验和训练(训练集)。

由于标注过程耗时且需要专业知识,偏见/社会偏见数据集在质量和大小上都受到限制。我们使用的数据集大约是Dbias中使用的MBAD/MBIC数据集的两倍,但对于BERT这种复杂的模型来说,它仍然偏小。因此,我们需要注意过拟合(训练的模型架构对数据来说过于复杂,实际上是记住了训练集的答案,但对其他输入泛化能力差)。

训练参数

如果您是模型或超参数调优的新手,请遵循Andrej Karpathy的这份指南。我一直都在用它。以下是我们重点关注的参数:

  • 学习率 - 每个优化器步骤中权重变化的程度。
  • 批量大小 - 每个训练步骤(epoch)中使用的数据点数量。通常目标是4到64之间(为了GPU核心效率,使用2的幂次方)。
  • 周期(Epoch) - 模型在完整数据集上训练的“轮次”数量。我们使用提前停止来防止过拟合,并使用检查点回调来选择最佳模型版本,但这是一个需要密切关注的重要事项。
  • 最大Token长度 - BERT将处理固定大小的向量,这意味着我们需要将输入ID列表和注意力掩码截断/填充到固定长度。

值得了解——Dropout率激活函数梯度裁剪

基准

我们将首先使用我们的数据集重新实现Dbias模型。Raza等人使用distilbert-base-uncased训练了30个epoch,批量大小为16,学习率为5e-5,最大token长度为512。下方左侧是Dbias在评估集上的运行结果,右侧是我们使用bert-base-uncased和128最大长度的重新实现结果。这些将作为我们实验的基准结果。

image/png

选择一个BERT模型

我们应该首先测试几个不同版本的BERT,以大致了解我们需要多大复杂度的模型。Dbias使用了DistilBERT,这是一个6层的BERT学生模型,但我们将坚持使用更容易互换的普通BERT模型;)。

BERT有四种架构:基础版(12层,768个特征)和大型版(24层,1024个特征),区分大小写和不区分大小写。由于大小写可能是与偏见相关的一个特征,我预感区分大小写的模型会带来一些改进。

image/png

结果解读:并非我所期望的,BERT大型版计算成本更高。但我们可以看到,使用区分大小写的模型可以减少假阳性,并增加假阴性(在我们的数据中)。这很有用,但我们也可以看到这并没有带来准确性的提高,因为我们看到了整体准确性从bert-base-uncased到base-cased的下降。

Large-cased达到了最佳准确率,或许通过其更多的特征细化了增加的负偏见。然而,由于我们过拟合得很快(仅在3个epoch之后),显然bert-base已经足够复杂。我相信我们可以通过优化训练而不是选择更大的模型来捕捉这些细微差别,所以我们将继续使用bert-base-uncased。

学习率

接下来,我们将找到最适合我们的基础学习率。学习率调优通常是数据集特定的,但以下是我寻找有效学习率的非常精妙的过程:

  1. 找到一个相似的模型,在一个大小相似的数据集上训练。尝试他们使用的学习率。
  2. 对于迁移学习,检查原始创建者是否建议了学习率。
  3. 尝试3e-4 😉
  4. 根据步骤1-3的结果进行猜测。

image/png

结果解读:我们仍然进展迅速。我们提前停止了训练,仅用了几个epoch,因为验证损失开始上升而不是像我们希望的那样下降。我尝试了一个超低学习率(3e-6),它显示验证损失在更多的epoch后达到了相同的点(约0.4),但准确率更低。我理解这意味着较低的学习率只是更仔细地学习了训练集特有的细节。我们可以快速运行它。

在接下来的几步中,我们将使用3e-4的学习率,并进行7个epoch的热身。它在图表上看起来有点奇怪,但它确实有效,尽管您在测试时应该放慢速度。

批量大小

对于每个批次,我们将计算一次梯度和新权重。

我喜欢把批量大小比作图像中的像素:如果你看4个像素,可能比看32个像素更难辨认出模式。但在使用中,我们的模型将以批量大小为1运行,所以我们希望在不牺牲准确性的前提下,保持批量大小尽可能小。为了找到最佳批量大小,请密切关注训练损失。我喜欢使用平均训练损失最低的批量大小(在验证损失开始上升之前)。

更大的批量大小会平滑梯度,而更小的批量大小会保留更多噪音

image/png

过拟合:对于以上示例,我让模型过拟合(没有提前停止)。这是一项重要的测试,可以查看模型可能达到的最低训练损失。在两种批量大小下,模型都达到了相对较低的训练损失(小于0.1),这意味着我们的模型理论上能够达到这么低的验证损失。实际上,验证损失会高于训练损失;这种差异是由模型泛化到新数据能力不足引起的。我们将把最小化训练损失和验证损失之间的差异作为我们未来的目标。

选择批量大小:我们将尝试几种不同的批量大小,并选择在过拟合之前准确率最佳的那个。这通常是训练和验证损失之间差距最小的批量大小,我们将从这里开始优化。

image/png

正则化

现在我们对要使用的超参数有了更好的理解,我们可以探索训练过程和架构。我们可以应用正则化技术,例如冻结BERT层或在线性层之前添加一个dropout层。

冻结BERT层将阻止其权重更新,这可能会减少可能导致训练集过拟合的精确特征提取。

添加一个**Dropout层**将随机用0替换BERT输出张量中给定百分比的值。这会产生“噪声”,从而防止模型记住细节。

不要浪费你的时间:在我(多次)实验中,我描述的任何正则化技术实际上都没有提高测试集的准确性。如果你想玩玩它们,它们仍然在笔记本中。

关键在于:偏见分类中,我们正在用一个小数据集处理一个微妙的定义。真正解决我们泛化问题的方法是获取更多信息,无论是广度还是深度。我们的ESR研究论文(GUS-Net)将实施改进这两种方法,但在这篇文章中,我们将坚持使用已有的东西……

均值池化与池化器输出

我们最简单的训练管道和调度器结果证明是最准确的,情况往往如此。好吧,并非所有事情都需要过度设计。

我还有一件事值得探讨。

  • 池化器输出:BERT输出一个固定大小为768的向量,称为pooler_output。它通过使用序列开头的[CLS]编码来完成此操作,该编码(经过各层后)应包含整个序列的信息。池化器对[CLS]标记使用激活函数,并输出固定大小的向量。这就是我们目前传递到输出层进行分类的内容。

  • 均值池化:我们不使用内置的池化器,而是可以对编码进行平均。这可能以不同的方式表示标记序列。我们可以确保它仍然是768个固定大小的向量,并以相同的过程训练我们的模型。实现后,结果令人失望,但也显示出一些优点。在其他数据集上看看会很有趣。

image/png


结论

所以……事实证明我们最初对训练参数的猜测相当幸运,但现在我们已经测试了许多训练参数,我们的理智可以安息了。

要点:数据集大小是瓶颈。

但是,如果数据集与BABEv3数据集相当,您可能会发现使用这些参数训练BERT进行序列分类效果不错:

  • 批量大小:16
  • 学习率:3e-4(7个epoch预热,13个epoch衰减)
  • 周期数:2

为了测试,你应该放慢速度。使用1.5e-6的学习率,这将使你的验证和测试损失曲线更平滑。然后像我们在实验部分那样调整参数,以获得较低的验证损失,然后再将学习率调高。

我用这些参数训练的模型在测试集上达到了81.7%的准确率,比Dbias论文中提出的偏见检测模型提高了13.6%。


接下来:

这只是我们在开源研究小组Ethical Spectacle Research所做工作的一小部分。我们的偏见检测研究论文将命名实体识别模型与本文所述的架构相结合,以实现更高级的编码和更准确的预测。我也会就此发表一篇博客。

论文/黑客松:我们将在九月发布我们的ArXiv论文,届时我们还将举办一场黑客松,看看开发者能用新的最先进模型构建出什么。我们将在明年一月发布会议论文(希望顺利)。

研讨会:感谢PHX Ventures,我们刚刚获得了一个很棒的新研讨会场地,所以我对即将举行的偏见检测、合成数据、代理网络等研讨会感到兴奋。请查看我们的聚会,了解活动详情。

社区

注册登录发表评论