赞
踩
Task07 文本分类
本次学习参照Datawhale开源学习:https://github.com/datawhalechina/learn-nlp-with-transformers
内容大体源自原文,结合自己学习思路有所调整。
本章节主要内容包含三部分内容:
本章节将使用 Hugging Face 生态系统中的库——Transformers来进行自然语言处理工作(NLP)。
Transformer 架构于 2017 年 6 月推出。原始研究的重点是翻译任务。随后推出了几个有影响力的模型,包括:
这个列表并不全,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类:
对Transformer模型的研究中的一些术语:
Architecture(架构):定义了模型的基本结构和基本运算
checkpoints(检查点):模型的某个训练状态,加载此checkpoint会加载此时的权重。
Model(模型):这是一个总称,不像“架构”或“检查点”那样精确,它可以同时表示两者。 当需要减少歧义时,本课程将指定架构或检查点。例如,BERT 是一种 Architectures,而 bert-base-cased(谷歌团队为 BERT 的第一个版本训练的一组权重)是一个checkpoints。 但是,可以说“the BERT model”和“the bert-base-cased model”。
checkpoint概念在大数据里面说的比较多。模型在训练时可以设置自动保存于某个时间点(比如模型训练了一轮epoch,更新了参数,将这个状态的模型保存下来,为一个checkpoint。)
所以每个checkpoint对应模型的一个状态,一组权重。大数据中检查点是一个数据库事件,存在的根本意义是减少崩溃时间。即减少因为意外情况数据库崩溃后重新恢复的时间。
Model Hub(模型中心)包含多语言模型的checkpoints。您可以通过单击语言标签来优化对模型的搜索,然后选择生成另一种语言文本的模型。
通过单击选择模型后,您会看到有一个小部件——Inference API(支持在线试用)。即您可以直接在此页面上使用各种模型,通过输入自定义文本就可以看到模型处理输入数据后的结果。 通过这种方式,您可以在下载模型之前快速测试模型的功能。
在本节中,我们将看看 Transformer 模型可以做什么,并使用Transformers 库中的第一个工具:管道pipeline。
Transformers 库提供了创建和使用共享模型的功能.。Model Hub包含数千个所有人都可以下载和使用的预训练模型。 您也可以将自己的模型上传到 Hub!
Transformers 库中最基本的对象是pipeline。 它将模型与其必要的预处理和后处理步骤连接起来,使我们能够直接输入任何文本并获得可理解的答案。例如对句子情感正负面判断:
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
classifier("I've been waiting for a HuggingFace course my whole life.")
[{'label': 'POSITIVE', 'score': 0.9598047137260437}]
可以看到该句情感是正面。我们也可以传入多个句子:
classifier([
"I've been waiting for a HuggingFace course my whole life.",
"I hate this so much!"
])
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
{'label': 'NEGATIVE', 'score': 0.9994558095932007}]
默认情况下,此管道选择一个特定的预训练模型"sentiment-analysis",该模型已针对英语情感分析进行了微调。 创建分类器对象时,将下载并缓存模型。 如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。
将一些文本传递到管道时涉及三个主要步骤:
目前可用的一些管道是:
您也可以从 Hub 中针对特定任务来选择特定模型的管道。转到 Model Hub并单击左侧的相应标签,页面将会仅显示文本生成任务支持的模型。
Transformers pipeline API 可以处理不同的 NLP 任务。您可以使用完整架构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。 下表总结了这一点:
模型 | 例子 | 任务 |
---|---|---|
Encoder | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | 句子分类、命名实体识别、抽取式问答 |
Decoder | CTRL, GPT, GPT-2, Transformer XL | 文本生成 |
Encoder-decoder | BART, T5, Marian, mBART | 摘要生成、翻译、生成式问答 |
以上显示的pipeline主要用于演示目的。 它们是为特定任务编程的,不能执行它们的变体。 在下一节中,您将了解管道内部的内容以及如何自定义其行为。
上面这几种管道的简单示例可以查看——Hugging Face主页课程第一篇《Transformer models》。
或单击Open in Colab以打开包含其它管道应用代码示例的 Google Colab 笔记本。
如果您想在本地运行示例,我们建议您查看设置。
本节代码:Open in Colab (PyTorch)
让我们从一个完整的例子开始,看看当我们在第1节中执行以下代码时,幕后发生了什么:
from transformers import pipeline
classifier = pipeline("sentiment-analysis")
classifier([
"I've been waiting for a HuggingFace course my whole life.",
"I hate this so much!",
])
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
{'label': 'NEGATIVE', 'score': 0.9994558095932007}]
这个管道将三个步骤组合在一起:预处理、通过模型传递输入和后处理:
让我们快速浏览一下这些内容。
与其他神经网络一样,Transformer 模型不能直接处理原始文本,因此我们管道的第一步是将文本输入转换为模型可以理解的数字。为此,我们使用了一个分词器tokenizer,它将负责:
使用 AutoTokenizer 类及其 from_pretrained 方法,以保证所有这些预处理都以与模型预训练时完全相同的方式完成。设定模型的 checkpoint名称,它会自动获取与模型的Tokenizer关联的数据并缓存它(所以它只在你第一次运行下面的代码时下载)。
由于情感分析管道的默认检查点是 distilbert-base-uncased-finetuned-sst-2-english,我们可以运行以下命令得到我们需要的tokenizer:
from transformers import AutoTokenizer
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
raw_inputs = [
"I've been waiting for a HuggingFace course my whole life.",
"I hate this so much!",
]
inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt")
#return_tensors="pt"表示返回Pytorch张量。文本转换为数字之后必须再转换成张量tensors才能输入模型。
#padding=True表示填充输入序列到最大长度,truncation=True表示过长序列被截断
print(inputs)
以下是 PyTorch 张量的结果:
{
'input_ids': tensor([
[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102],
[ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0]
]),
'attention_mask': tensor([
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
])
}
我们可以像使用分词器一样下载我们的预训练模型。 Transformers 提供了一个 AutoModel 类,它也有一个 from_pretrained 方法:
from transformers import AutoModel
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
AutoModel 类及其所有相关类实际上是库中各种可用模型的简单包装器。 它可以自动为您的checkpoint猜测合适的模型架构,然后使用该架构实例化模型。
在此代码片段中,我们下载了之前在管道中使用的相同checkpoint,并用它实例化了一个模型。但是这个架构只包含基本的 Transformer 模块:给定一些输入,它输出我们称之为隐藏状态hidden states的东西。虽然这些隐藏状态本身就很有用,但它们通常是模型另一部分(model head)的输入。
我们可以使用相同的模型体系结构执行不同的任务,但是每个任务都有与之关联的不同的Model heads。
Model heads:将隐藏状态的高维向量(也就是logits向量)作为输入,并将它们投影到不同的维度上。 它们通常由一个或几个线性层组成:
在此图中,模型由其embeddings layer和后续层表示。输入数据经过embeddings layer输出logits向量以产生句子的最终表示。
Transformers 中有许多不同的架构可用,每一种架构都围绕着处理特定任务而设计。 下面列举了部分Model heads:
以情感分类为例,我们需要一个带有序列分类的Model head(能够将句子分类为正面或负面)。 因此,我们实际上不会使用 AutoModel 类,而是使用 AutoModelForSequenceClassification:
(也就是说前面写的model = AutoModel.from_pretrained(checkpoint)并不能得到情感分类任务的结果,因为没有加载Model head)
from transformers import AutoModelForSequenceClassification
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
model head将我们之前看到的高维向量作为输入,并输出包含两个值(每个标签一个)的向量:
print(outputs.logits.shape)
torch.Size([2, 2])
由于我们只有两个句子和两个标签,因此我们从模型中得到的结果是 2 x 2 的形状。
我们从模型中获得的作为输出的值本身并不一定有意义。 让我们来看看:
print(outputs.logits)
tensor([[-1.5607, 1.6123],
[ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
我们的模型预测了第一个句子结果 [-1.5607, 1.6123] 和第二个句子的结果 [4.1692, -3.3464]。 这些不是概率,而是 logits,即模型最后一层输出的原始非标准化分数。 要转换为概率,它们需要经过一个 SoftMax 层。所有Transformers 模型都输出 logits,这是因为训练的损失函数一般会将最后一个激活函数(比如SoftMax)和实际的交叉熵损失函数相融合。
(补充:在Pytorch里面,交叉熵损失CEloss不是数学上的交叉熵损失(NLLLoss)。Pytorch的CrossEntropyLoss就是把Softmax–Log–NLLLoss合并成一步。详细内容可以参考知乎文章《如何理解NLLLoss?》)
import torch
predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
tensor([[4.0195e-02, 9.5980e-01],
[9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
这次输出是可识别的概率分数。
要获得每个位置对应的标签,我们可以检查模型配置的 id2label 属性:
model.config.id2label
{0: 'NEGATIVE', 1: 'POSITIVE'}
现在我们可以得出结论,该模型预测了以下内容:
第一句:NEGATIVE:0.0402,POSITIVE:0.9598
第二句:NEGATIVE:0.9995,POSITIVE:0.0005
本节代码:Open in Colab(PyTorch)
在第3节中,我们探讨了如何使用分词器和预训练模型进行预测。 但是,如果您想为自己的数据集微调预训练模型怎么办? 这就是本章的主题! 你将学习:
Hub 不仅包含模型;还含有多个datasets,这些datasets有很多不同的语言。我们建议您在完成本节后尝试加载和处理新数据集(参考文档)。
MRPC 数据集是构成 GLUE 基准的 10 个数据集之一。而GLUE 基准是一种学术基准,用于衡量 ML 模型在 10 个不同文本分类任务中的性能。
Datasets库提供了一个非常简单的命令来下载和缓存Hub上的dataset。 我们可以像这样下载 MRPC 数据集:
from datasets import load_dataset
raw_datasets = load_dataset("glue", "mrpc")
raw_datasets
DatasetDict({
train: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 3668
})
validation: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 408
})
test: Dataset({
features: ['sentence1', 'sentence2', 'label', 'idx'],
num_rows: 1725
})
})
这样就得到一个DatasetDict对象,包含训练集、验证集和测试集,训练集中有3,668 个句子对,验证集中有408对,测试集中有1,725 对。每个句子对包含四列数据:‘sentence1’, ‘sentence2’, 'label’和 ‘idx’。
load_dataset 方法, 可以从不同的地方构建数据集
例如: datasets = load_dataset(“text”, data_files={“train”: path_to_train.txt, “validation”: path_to_validation.txt} 具体可以参考文档
load_dataset命令下载并缓存数据集,默认在 ~/.cache/huggingface/dataset 中。您可以通过设置 HF_HOME 环境变量来自定义缓存文件夹。
和字典一样,raw_datasets 可以通过索引访问其中的句子对:
raw_train_dataset = raw_datasets["train"]
raw_train_dataset[0]
{'idx': 0,
'label': 1,
'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .',
'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'}
import pandas as pd
validation=pd.DataFrame(raw_datasets['validation'])
validation
可见标签已经是整数,不需要再做任何预处理。通过raw_train_dataset的features属性可以知道每一列的类型:
raw_train_dataset.features
{'sentence1': Value(dtype='string', id=None),
'sentence2': Value(dtype='string', id=None),
'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None),
'idx': Value(dtype='int32', id=None)}
label是 ClassLabel 类型,label=1表示这对句子互为paraphrases,label=0表示句子对意思不一致。
通过tokenizer可以将文本转换为模型能理解的数字。
from transformers import AutoTokenizer
checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
让我们看一个示例:
inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs
{ 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
所以将句子对列表传给tokenizer,就可以对整个数据集进行分词处理。因此,预处理训练数据集的一种方法是:
tokenized_dataset = tokenizer(
raw_datasets["train"]["sentence1"],
raw_datasets["train"]["sentence2"],
padding=True,
truncation=True,
)
这种处理方法是ok的,但缺点是处理之后tokenized_dataset不再是一个dataset格式,而是返回字典(带有我们的键:input_ids、attention_mask 和 token_type_ids,对应的键值对的值)。 而且一旦我们的dataset过大,无法放在内存中,那么这样子的做法会导致 Out of Memory 的异常。( Datasets 库中的数据集是存储在磁盘上的 Apache Arrow 文件,因此请求加载的样本都保存在内存中)。
为了使我们的数据保持dataset的格式,我们将使用更灵活的Dataset.map 方法。此方法可以完成更多的预处理而不仅仅是 tokenization。 map 方法是对数据集中的每个元素应用同一个函数,所以让我们定义一个函数来对输入进行tokenize预处理:
def tokenize_function(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
这个函数接受的是一个字典(就像我们dataset的items),返回的也是一个字典(有三个键:input_ids、attention_mask 和 token_type_ids )。
在tokenization函数中省略了padding 参数,这是因为padding到该批次中的最大长度时的效率,会高于所有序列都padding到整个数据集的最大序列长度。 当输入序列长度很不一致时,这可以节省大量时间和处理能力!
以下是对整个数据集应用tokenization方法。 我们在 map 调用中使用了 batched=True,因此该函数一次应用于数据集的整个batch元素,而不是分别应用于每个元素。 这样预处理速度会更快(因为
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。