为下游任务微调BERT预训练模型 文本分类

1,866 阅读8分钟

碎碎念

使用BERT预训练模型中讲了如何使用预训练模型,这次我们看一下如何进行微调。

微调模型

首先,我们学习如何为文本分类任务微调预训练的BERT模型。

举个简单的情感分析的例子。

情感分析是自然语言处理(NLP)中的一个重要任务,旨在通过分析文本中的情感信息,识别出文本中的情感情感极性,如积极、消极或中性。

情感分析通常有以下两种类型:

  • 情感极性分类:这种类型的情感分析任务旨在将文本划分为积极、消极或中性三种情感极性。通常采用监督学习方法,通过训练一个分类器来自动分类文本中的情感。训练数据集通常包含带有情感标签的文本样本,例如社交媒体帖子、评论、新闻文章等。一些常用的分类算法包括朴素贝叶斯、支持向量机(SVM)、深度学习模型(如卷积神经网络、循环神经网络等)等。

  • 情感强度分析:这种类型的情感分析任务旨在分析文本中的情感强度,即情感表达的程度。通常使用无监督学习方法,通过构建情感词典或使用基于规则的方法来评估文本中的情感强度。情感词典是一个包含情感词汇和对应情感强度的数据库,可以用来计算文本中的情感得分。一些常用的情感词典包括情感词汇本体(SentiWordNet)、情感词汇扩展版(SenticNet)等。

情感分析在许多应用领域都有广泛的应用,包括社交媒体舆情分析、产品评论分析、市场调研、情感监控等。它可以帮助企业了解用户对产品或服务的反馈,从而优化产品或服务的设计和营销策略,提升用户满意度和品牌形象。同时,情感分析还在社会科学、心理学等领域有着重要的研究价值。

比如我们要进行情感分析,目标是对一个句子是积极(正面情绪)还是消极(负面情绪)进行分类。假设我们有一个包含句子及其标签的数据集。

以句子I love BERT为例,我们首先对句子进行标记,在句首添加[CLS],在句尾添加[SEP]。然后,将这些标记输入预训练的BERT模型,并得到所有标记的嵌入。

我们只取[CLS]的嵌入,也就是R[CLS]R_{[CLS]},忽略所有其他标记的嵌入,因为[CLS]标记的嵌入包含整个句子的总特征。我们将R[CLS]R_{[CLS]}送入一个分类器(使用softmax激活函数的前馈网络层),并训练分类器进行情感分析。

对预训练的BERT模型进行微调时,模型的权重与分类器一同更新。使用预训练的BERT模型作为特征提取器时,我们只更新分类器的权重,而不更新模型的权重。

在微调期间,可以通过以下两种方式调整权重

  • 与分类器层一起更新预训练的BERT模型的权重。

  • 仅更新分类器层的权重,不更新预训练的BERT模型的权重。这类似于使用预训练的BERT模型作为特征提取器的情况。

image.png

我们以使用ethos数据集的情感分析任务为例来微调预训练的BERT模型。

Ethos数据集是OpenAI发布的一个多模态自然语言处理数据集,用于训练人工智能模型识别和理解伦理和道德问题。该数据集包含了丰富的多模态信息,包括了图片、文本、音频和视频等多种类型的数据,用于模拟现实世界中的复杂情境。

Ethos数据集的主要任务包括了情感分类、道德判断和情境理解等。情感分类任务涉及对道德和伦理问题中的情感进行分类,如积极、消极、中性等。道德判断任务涉及对复杂情境中的伦理判断进行预测,如判断应该如何行为等。情境理解任务涉及对现实世界中的复杂情境进行理解和推理,如理解复杂道德决策的背后原因等。

Ethos数据集中的情感分类部分,主要包含了对道德和伦理问题的情感进行分类的任务。这个数据集包含了大量的文本数据,其中包括了人类对于不同伦理和道德问题的情感和态度。每个样本都包含了一段描述了一个伦理和道德问题的文本,以及相应的情感标签,例如积极、消极、中性等。

导入依赖库

首先,安装必要的库。

!pip install Transformers
!pip install Datasets

然后,导入必要的模块。

from transformers import BertForSequenceClassification, BertTokenizerFast, Trainer, TrainingArguments
from datasets import load_dataset
import torch
import numpy as np

加载模型和数据集

下载并加载数据集。 我们这用的是binary,就是一个简单的二分类。

image.png

Ethos_Dataset_Binary: contains 998 comments in the dataset alongside with a label about hate speech presence or absence. 565 of them do not contain hate speech, while the rest of them, 433, contain.

打印看一下数据集内容。

ethos = load_dataset("ethos", "binary", split='train')
print(ethos)

输出

Dataset({ features: ['text', 'label'], num_rows: 998 })

然后,检查数据类型。

type(ethos)

输出

datasets.arrow_dataset.Dataset

现在,创建训练集和测试集。

dataset = ethos.train_test_split(test_size=0.2)
train_set = dataset['train']
test_set = dataset['test']

接下来,下载并加载预训练的BERT模型。在这个例子中,我们使用预训练的bert-base-uncased模型。由于要进行序列分类,因此我们使用BertForSequence-Classification类。

然后,下载并加载用于预训练bert-base-uncased模型的词元分析器。可以看到,我们使用了BertTokenizerFast类创建词元分析器,而不是使用BertTokenizer。与BertTokenizer相比,BertTokenizerFast类有很多优点。我们将在后面了解这方面的内容。

  1. 更快速的处理速度:BertTokenizerFast使用Rust编写的底层代码,因此在处理大量文本时比BertTokenizer更快。

  2. 更小的内存占用:BertTokenizerFast的内存占用较小,因为它使用了更高效的数据结构。

  3. 更多的功能:BertTokenizerFast支持更多的文本处理功能,例如添加特殊标记、截断文本、处理特殊字符等。

  4. 更好的适应性:BertTokenizerFast可以处理各种文本数据,包括特殊字符、emoji、URL等,而BertTokenizer可能会遇到处理不当的情况。

  5. 更好的兼容性:BertTokenizerFast兼容多种编程语言,包括Python、Java、C++等,而BertTokenizer主要用于Python。这使得BertTokenizerFast可以在不同的应用场景中使用。

总的来说,BertTokenizerFast是一个更加高效和功能丰富的文本处理工具,特别适合处理大规模文本数据。而BertTokenizer则适合简单的文本处理任务

model = BertForSequenceClassification.from_pretrained('bert-base-uncased')
tokenizer = BertTokenizerFast.from_pretrained('bert-base-uncased')

预处理数据集

image.png

上一篇文章使用BERT预训练模型我们手动实现了 'input_ids'、'token_type_ids'、'attention_mask',当时说这些其实不用自己实现,tokenizer可以一步实现的,所以现在我们就不搞那么麻烦了。

通过词元分析器,还可以输入任意数量的句子,并动态地进行补长或填充。要实现动态补长或填充,需要将padding设置为True,同时设置最大序列长度。假设输入3个句子,并将最大序列长度max_length设置为5。

tokenizer(['I love Paris', 'birds fly', 'snow fall'], padding = True, max_length = 5)

输出

{

'input_ids': [[101, 1045, 2293, 3000, 102], [101, 5055, 4875, 102, 0], [101, 4586, 2991, 102, 0]],

'token_type_ids': [[0, 0, 0, 0, 0], [1, 1, 1, 1, 1], [0, 0, 0, 0, 0]],

'attention_mask': [[1, 1, 1, 1, 1], [1, 1, 1, 1, 0], [1, 1, 1, 1, 0]]

}

上面的代码将返回以下内容。可以看到,所有的句子都被映射到input_ids、token_type_ids和attention_mask。第2句和第3句只有两个标记,加上[CLS]和[SEP]后,有4个标记。由于将padding设置为True,并将max_length设置为5,因此在第2句和第3句中添加了一个额外的[PAD]标记。这就是在第2句和第3句的注意力掩码中出现0的原因。

有了词元分析器,我们可以轻松地预处理数据集。我们定义了一个名为preprocess的函数来处理数据集,如下所示。

def preprocess(data):
    return tokenizer(data['text'], padding = True, truncation = True)

使用preprocess函数对训练集和测试集进行预处理。

train_set = train_set.map(preprocess, batched = True, batch_size = len(train_set))
test_set = test_set.map(preprocess, batched = True, batch_size = len(test_set))

接下来,使用set_format函数,选择数据集中需要的列及其对应的格式,如下所示。

train_set.set_format('torch', columns = ['input_ids', 'attention_mask', 'label']) test_set.set_format('Torch', columns = ['input_ids', 'attention_mask', 'label'])

训练模型

首先,定义批量大小和迭代次数。 然后,确定预热步骤和权重衰减。

batch_size = 64
epochs = 5
warmup_steps = 500
weight_decay = 0.01

接着,设置训练参数。

training_args = TrainingArguments(
    output_dir = './results',
    num_train_epochs = epochs,
    per_device_train_batch_size = batch_size,
    per_device_eval_batch_size = batch_size,
    warmup_steps = warmup_steps,
    weight_decay = weight_decay,
    evaluate_during_training = True,
    logging_dir = './logs',
)

最后,定义训练函数。

trainer = Trainer(
    model = model,
    args = training_args,
    train_dataset = train_set,
    eval_dataset = test_set
)

开始训练

trainer.train()

训练完之后评估模型

trainer.evaluate()

以这种方式,我们就可以针对文本分类任务对预训练的BERT模型进行微调。


本文正在参加「金石计划」