碎碎念
使用BERT预训练模型中讲了如何使用预训练模型,这次我们看一下如何进行微调。
微调模型
首先,我们学习如何为文本分类任务微调预训练的BERT模型。
举个简单的情感分析的例子。
情感分析是自然语言处理(NLP)中的一个重要任务,旨在通过分析文本中的情感信息,识别出文本中的情感情感极性,如积极、消极或中性。
情感分析通常有以下两种类型:
-
情感极性分类:这种类型的情感分析任务旨在将文本划分为积极、消极或中性三种情感极性。通常采用监督学习方法,通过训练一个分类器来自动分类文本中的情感。训练数据集通常包含带有情感标签的文本样本,例如社交媒体帖子、评论、新闻文章等。一些常用的分类算法包括朴素贝叶斯、支持向量机(SVM)、深度学习模型(如卷积神经网络、循环神经网络等)等。
-
情感强度分析:这种类型的情感分析任务旨在分析文本中的情感强度,即情感表达的程度。通常使用无监督学习方法,通过构建情感词典或使用基于规则的方法来评估文本中的情感强度。情感词典是一个包含情感词汇和对应情感强度的数据库,可以用来计算文本中的情感得分。一些常用的情感词典包括情感词汇本体(SentiWordNet)、情感词汇扩展版(SenticNet)等。
情感分析在许多应用领域都有广泛的应用,包括社交媒体舆情分析、产品评论分析、市场调研、情感监控等。它可以帮助企业了解用户对产品或服务的反馈,从而优化产品或服务的设计和营销策略,提升用户满意度和品牌形象。同时,情感分析还在社会科学、心理学等领域有着重要的研究价值。
比如我们要进行情感分析,目标是对一个句子是积极(正面情绪)还是消极(负面情绪)进行分类。假设我们有一个包含句子及其标签的数据集。
以句子I love BERT为例,我们首先对句子进行标记,在句首添加[CLS],在句尾添加[SEP]。然后,将这些标记输入预训练的BERT模型,并得到所有标记的嵌入。
我们只取[CLS]的嵌入,也就是,忽略所有其他标记的嵌入,因为[CLS]标记的嵌入包含整个句子的总特征。我们将送入一个分类器(使用softmax激活函数的前馈网络层),并训练分类器进行情感分析。
对预训练的BERT模型进行微调时,模型的权重与分类器一同更新。使用预训练的BERT模型作为特征提取器时,我们只更新分类器的权重,而不更新模型的权重。
在微调期间,可以通过以下两种方式调整权重:
-
与分类器层一起更新预训练的BERT模型的权重。
-
仅更新分类器层的权重,不更新预训练的BERT模型的权重。这类似于使用预训练的BERT模型作为特征提取器的情况。
我们以使用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,就是一个简单的二分类。
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类有很多优点。我们将在后面了解这方面的内容。
-
更快速的处理速度:BertTokenizerFast使用Rust编写的底层代码,因此在处理大量文本时比BertTokenizer更快。
-
更小的内存占用:BertTokenizerFast的内存占用较小,因为它使用了更高效的数据结构。
-
更多的功能:BertTokenizerFast支持更多的文本处理功能,例如添加特殊标记、截断文本、处理特殊字符等。
-
更好的适应性:BertTokenizerFast可以处理各种文本数据,包括特殊字符、emoji、URL等,而BertTokenizer可能会遇到处理不当的情况。
-
更好的兼容性:BertTokenizerFast兼容多种编程语言,包括Python、Java、C++等,而BertTokenizer主要用于Python。这使得BertTokenizerFast可以在不同的应用场景中使用。
总的来说,BertTokenizerFast是一个更加高效和功能丰富的文本处理工具,特别适合处理大规模文本数据。而BertTokenizer则适合简单的文本处理任务
model = BertForSequenceClassification.from_pretrained('bert-base-uncased')
tokenizer = BertTokenizerFast.from_pretrained('bert-base-uncased')
预处理数据集
上一篇文章使用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模型进行微调。
本文正在参加「金石计划」