当前位置:   article > 正文

Learn NLP with Transformer (Chapter 7)_the following columns in the training set don't ha

the following columns in the training set don't have a corresponding argumen

7. 文本分类

Task07 文本分类
本次学习参照Datawhale开源学习:https://github.com/datawhalechina/learn-nlp-with-transformers
内容大体源自原文,结合自己学习思路有所调整。

本章节主要内容包含三部分内容:

  • pipeline工具演示NLP任务处理
  • 构建Trainer微调模型
  • 文本分类、超参数搜索任务

7.1. 简介

本章节将使用 Hugging Face 生态系统中的库——Transformers来进行自然语言处理工作(NLP)。

7.1.1 Transformers的历史

transformers_chrono
Transformer 架构于 2017 年 6 月推出。原始研究的重点是翻译任务。随后推出了几个有影响力的模型,包括:

  • 2018 年 6 月:GPT,第一个预训练的 Transformer 模型,用于各种 NLP 任务的微调并获得最先进的结果
  • 2018 年 10 月:BERT,另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!)
  • 2019 年 2 月:GPT-2,GPT 的改进(和更大)版本,由于道德问题未立即公开发布
  • 2019 年 10 月:DistilBERT,BERT 的蒸馏版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能
  • 2019 年 10 月:BARTT5,两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做)
  • 2020 年 5 月,GPT-3,GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习zero-shot learning)

这个列表并不全,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类:

  • GPT类(只使用transformer-decoder部分,自回归 Transformer 模型)
  • BERT类(只使用transformer-encoder部分,自编码 Transformer 模型)
  • BART/T5 类(Transformer-encoder-decoder模型)

7.1.2 Architectures和checkpoints

对Transformer模型的研究中的一些术语:

Architecture(架构):定义了模型的基本结构和基本运算

checkpoints(检查点):模型的某个训练状态,加载此checkpoint会加载此时的权重。

Model(模型):这是一个总称,不像“架构”或“检查点”那样精确,它可以同时表示两者。 当需要减少歧义时,本课程将指定架构或检查点。例如,BERT 是一种 Architectures,而 bert-base-cased(谷歌团队为 BERT 的第一个版本训练的一组权重)是一个checkpoints。 但是,可以说“the BERT model”和“the bert-base-cased model”。

checkpoint概念在大数据里面说的比较多。模型在训练时可以设置自动保存于某个时间点(比如模型训练了一轮epoch,更新了参数,将这个状态的模型保存下来,为一个checkpoint。)
所以每个checkpoint对应模型的一个状态,一组权重。大数据中检查点是一个数据库事件,存在的根本意义是减少崩溃时间。即减少因为意外情况数据库崩溃后重新恢复的时间。

7.1.3 The Inference API

Model Hub(模型中心)包含多语言模型的checkpoints。您可以通过单击语言标签来优化对模型的搜索,然后选择生成另一种语言文本的模型。

通过单击选择模型后,您会看到有一个小部件——Inference API(支持在线试用)。即您可以直接在此页面上使用各种模型,通过输入自定义文本就可以看到模型处理输入数据后的结果。 通过这种方式,您可以在下载模型之前快速测试模型的功能。
DistilBERT base model (uncased)

7.2. 用pipeline处理NLP问题

在本节中,我们将看看 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.")
  • 1
  • 2
  • 3
  • 4
[{'label': 'POSITIVE', 'score': 0.9598047137260437}]
  • 1

可以看到该句情感是正面。我们也可以传入多个句子:

classifier([
    "I've been waiting for a HuggingFace course my whole life.", 
    "I hate this so much!"
])
  • 1
  • 2
  • 3
  • 4
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
  • 1
  • 2

默认情况下,此管道选择一个特定的预训练模型"sentiment-analysis",该模型已针对英语情感分析进行了微调。 创建分类器对象时,将下载并缓存模型。 如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。

将一些文本传递到管道时涉及三个主要步骤:

  1. 预处理:文本被预处理为模型可以理解的格式。
  2. 输入模型:构建模型,并将预处理的输入传递给模型。
  3. 后处理:模型的预测是经过后处理的,因此您可以理解它们。

目前可用的一些管道是:

  • feature-extraction (获取文本的向量表示)
  • fill-mask填充给定文本中的空白(完形填空)
  • ner (named entity recognition)词性标注
  • question-answering问答
  • sentiment-analysis情感分析
  • summarization摘要生成
  • text-generation文本生成
  • translation翻译
  • zero-shot-classification零样本分类

您也可以从 Hub 中针对特定任务来选择特定模型的管道。转到 Model Hub并单击左侧的相应标签,页面将会仅显示文本生成任务支持的模型。

Transformers pipeline API 可以处理不同的 NLP 任务。您可以使用完整架构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。 下表总结了这一点:

模型例子任务
EncoderALBERT, BERT, DistilBERT, ELECTRA, RoBERTa句子分类、命名实体识别、抽取式问答
DecoderCTRL, GPT, GPT-2, Transformer XL文本生成
Encoder-decoderBART, T5, Marian, mBART摘要生成、翻译、生成式问答

以上显示的pipeline主要用于演示目的。 它们是为特定任务编程的,不能执行它们的变体。 在下一节中,您将了解管道内部的内容以及如何自定义其行为。

上面这几种管道的简单示例可以查看——Hugging Face主页课程第一篇《Transformer models》
或单击Open in Colab以打开包含其它管道应用代码示例的 Google Colab 笔记本。
如果您想在本地运行示例,我们建议您查看设置

7.3. Behind the pipeline

本节代码: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!",
])
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
  • 1
  • 2

这个管道将三个步骤组合在一起:预处理、通过模型传递输入和后处理:
full_nlp_pipeline

让我们快速浏览一下这些内容。

7.3.1 tokenizer预处理

与其他神经网络一样,Transformer 模型不能直接处理原始文本,因此我们管道的第一步是将文本输入转换为模型可以理解的数字。为此,我们使用了一个分词器tokenizer,它将负责:

  • 将输入拆分为称为标记的单词、子词subword或符号symbols(如标点符号)
  • 将每个标记映射到一个整数
  • 添加可能对模型有用的其他输入

使用 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)
  • 1
  • 2
  • 3
  • 4
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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

以下是 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]
    ])
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

7.3.2 选择模型

我们可以像使用分词器一样下载我们的预训练模型。 Transformers 提供了一个 AutoModel 类,它也有一个 from_pretrained 方法:

from transformers import AutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
  • 1
  • 2
  • 3
  • 4

AutoModel 类及其所有相关类实际上是库中各种可用模型的简单包装器。 它可以自动为您的checkpoint猜测合适的模型架构,然后使用该架构实例化模型

在此代码片段中,我们下载了之前在管道中使用的相同checkpoint,并用它实例化了一个模型。但是这个架构只包含基本的 Transformer 模块:给定一些输入,它输出我们称之为隐藏状态hidden states的东西。虽然这些隐藏状态本身就很有用,但它们通常是模型另一部分(model head)的输入。

7.3.3 Model heads

我们可以使用相同的模型体系结构执行不同的任务,但是每个任务都有与之关联的不同的Model heads。

Model heads:将隐藏状态的高维向量(也就是logits向量)作为输入,并将它们投影到不同的维度上。 它们通常由一个或几个线性层组成:
transformer_and_head
在此图中,模型由其embeddings layer和后续层表示。输入数据经过embeddings layer输出logits向量以产生句子的最终表示。

Transformers 中有许多不同的架构可用,每一种架构都围绕着处理特定任务而设计。 下面列举了部分Model heads:

  • Model (retrieve the hidden states)
  • ForCausalLM
  • ForMaskedLM
  • ForMultipleChoice
  • ForQuestionAnswering
  • ForSequenceClassification
  • ForTokenClassification
  • and others

以情感分类为例,我们需要一个带有序列分类的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)
  • 1
  • 2
  • 3
  • 4
  • 5

model head将我们之前看到的高维向量作为输入,并输出包含两个值(每个标签一个)的向量:

print(outputs.logits.shape)
  • 1
torch.Size([2, 2])
  • 1

由于我们只有两个句子和两个标签,因此我们从模型中得到的结果是 2 x 2 的形状。

7.3.4 Post-processing后处理

我们从模型中获得的作为输出的值本身并不一定有意义。 让我们来看看:

print(outputs.logits)
  • 1
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
  • 1
  • 2

我们的模型预测了第一个句子结果 [-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)
  • 1
  • 2
  • 3
  • 4
tensor([[4.0195e-02, 9.5980e-01],
        [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
  • 1
  • 2

这次输出是可识别的概率分数。

要获得每个位置对应的标签,我们可以检查模型配置的 id2label 属性:

model.config.id2label
  • 1
{0: 'NEGATIVE', 1: 'POSITIVE'}
  • 1

现在我们可以得出结论,该模型预测了以下内容:

第一句:NEGATIVE:0.0402,POSITIVE:0.9598

第二句:NEGATIVE:0.9995,POSITIVE:0.0005

7.4. 构建Trainer API微调预训练模型

本节代码:Open in Colab(PyTorch)

在第3节中,我们探讨了如何使用分词器和预训练模型进行预测。 但是,如果您想为自己的数据集微调预训练模型怎么办? 这就是本章的主题! 你将学习:

  • 如何从Model Hub 准备大型数据集
  • 如何使用high-level Trainer API来微调模型
  • 如何使用自定义训练循环a custom training loop
  • 如何利用Accelerate 库在任何分布式设置上轻松运行该custom training loop

7.4.1 从Hub上下载dataset

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
  • 1
  • 2
  • 3
  • 4
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
    })
})
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这样就得到一个DatasetDict对象,包含训练集、验证集和测试集,训练集中有3,668 个句子对,验证集中有408对,测试集中有1,725 对。每个句子对包含四列数据:‘sentence1’, ‘sentence2’, 'label’和 ‘idx’。

load_dataset 方法, 可以从不同的地方构建数据集

  • from the HuggingFace Hub,
  • from local files, 如CSV/JSON/text/pandas files
  • from in-memory data like python dict or a pandas dataframe.

例如: 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]
  • 1
  • 2
{'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 .'}
  • 1
  • 2
  • 3
  • 4
import pandas as pd
validation=pd.DataFrame(raw_datasets['validation'])
validation
  • 1
  • 2
  • 3

validation
可见标签已经是整数,不需要再做任何预处理。通过raw_train_dataset的features属性可以知道每一列的类型:

raw_train_dataset.features
  • 1
{'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)}
  • 1
  • 2
  • 3
  • 4

label是 ClassLabel 类型,label=1表示这对句子互为paraphrases,label=0表示句子对意思不一致。

7.4.2 数据集预处理

通过tokenizer可以将文本转换为模型能理解的数字。

from transformers import AutoTokenizer

checkpoint = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
  • 1
  • 2
  • 3
  • 4

让我们看一个示例:

inputs = tokenizer("This is the first sentence.", "This is the second one.")
inputs
  • 1
  • 2
{ '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]}
  • 1
  • 2
  • 3

所以将句子对列表传给tokenizer,就可以对整个数据集进行分词处理。因此,预处理训练数据集的一种方法是:

tokenized_dataset = tokenizer(
    raw_datasets["train"]["sentence1"],
    raw_datasets["train"]["sentence2"],
    padding=True,
    truncation=True,
)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这种处理方法是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)
  • 1
  • 2

这个函数接受的是一个字典(就像我们dataset的items),返回的也是一个字典(有三个键:input_ids、attention_mask 和 token_type_ids )。

在tokenization函数中省略了padding 参数,这是因为padding到该批次中的最大长度时的效率,会高于所有序列都padding到整个数据集的最大序列长度。 当输入序列长度很不一致时,这可以节省大量时间和处理能力!

以下是对整个数据集应用tokenization方法。 我们在 map 调用中使用了 batched=True,因此该函数一次应用于数据集的整个batch元素,而不是分别应用于每个元素。 这样预处理速度会更快(因为

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/356591
推荐阅读
相关标签