经典模型的处理能力:
RNN 10-40
LSTM 50-100
Transformer BERT 512 GPT 1024
Transformer-XL,LXNet 3000-5000
Classifying whole sentences:评论情感分析,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或判断两个句子在逻辑上是否相关。
Classifying each word in a sentence:识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织)。
Generating text content::用自动生成的文本完成提示,用屏蔽词填充文本中的空白。
Extracting an answer from a text: 给定问题和上下文,根据上下文中提供的信息提取问题的答案。
Generating a new sentence from an input text: 将文本翻译成另一种语言,文本摘要。
🤗 Transformers library中最基本的对象是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}]
默认情况下,pipeline会选择一个特定的预训练模型,该模型已针对英语情感分析进行了微调。创建classifier对象时,将下载并缓存模型。如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。
将文本输入给pipeline,pipeline的处理涉及到以下三个主要步骤:
feature-extraction (get the vector representation of a text)fill-mask
ner (named entity recognition)
question-answering
sentiment-analysis
summarization
text-generation
translation
zero-shot-classification
Transformer architecture是在2017年六月推出,当初的研究用于翻译任务。随后推出了几个有影响力的模型,如下图:
预训练模型的详细介绍,它们可以分为三类:
类 GPT(也称为auto-regressive Transformer 模型)
类 BERT(也称为 auto-encoding Transformer 模型)
BART/T5 类(也称为equence-to-sequence Transformer 模型)
encoder models auto-encoding models使用 Transformer 模型的编码器。在每个阶段,注意力层都可以访问初始句子中的所有单词。这些模型通常被描述为具有“双向”注意力。 | 这些模型的预训练通常围绕着以某种方式破坏给定的句子(例如,通过随机屏蔽其中的单词)并让模型找到或重建初始句子。 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | 句子分类,命名实体识别(以及更一般的单词分类)和提取式问答。 |
---|---|---|---|
decoder modlesauto-regressive models使用 Transformer 模型的解码器。在每个阶段,对于给定的单词,注意力层只能访问句子中位于它之前的单词。 | 解码器模型的预训练通常围绕预测句子中的下一个单词。 | CTRL, GPT, GPT-2, Transformer XL | 文本生成。 |
encoder-decoder modelssequence-to-sequence models使用 Transformer 架构的两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中给定单词之前的单词。 | 这些模型的预训练可以使用编码器或解码器模型的目标来完成,但通常涉及一些更复杂的东西。例如,T5是通过用一个掩码特殊单词替换随机范围的文本(可以包含几个单词)进行预训练的,然后目标是预测这个掩码单词替换的文本。。 | BART, T5, Marian, mBART | 摘要、翻译或生成式问答。 |
MLM:masked language modeling 预测句子中的掩码词。
CLM:causal language modeling 根据当前的文本预测下一个词。
Architecture: 这是模型的骨架—模型中每一层和每个操作的定义。
Checkpoints: 这些是将在给定架构中加载的权重。
Model: 这是一个总称,不像“架构”或“检查点”那样精确:它可以同时表示两者。
pipeline将三个步骤组合在一起:预处理、通过模型传递输入和后处理:流程见下图
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,它将负责:
将输入拆分为称为标记的单词、子词或符号(如标点符号)。
将每个标记映射到一个整数。
添加可能对模型有用的其他输入。
所有这些预处理都需要以与模型预训练时完全相同的方式完成,因此我们首先需要从Model Hub下载该信息。为此,我们使用AutoTokenizer类及其from_pretrained方法。使用我们模型的检查点名称,它将自动获取与模型的标记器关联的数据并将其缓存。
由于sentiment-analysis管道的默认检查点是distilbert-base-uncased-finetuned-sst-2-english(您可以在此处查看其模型卡),我们运行以下命令:
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!", ] # 要指定我们想要返回的张量类型(PyTorch、TensorFlow 或普通 NumPy),我们使用return_tensors参数: inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") print(inputs) # 输出结果为: { '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] ]) } # 如果没有使用return_tensors参数,则输出如下(列表嵌套列表的格式),该格式无法直接输入给model # Transformer 模型只接受tensors作为输入。 {'input_ids': [[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': [[1, 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]]}
输出本身是一个包含两个键的字典,input_ids和attention_mask。
input_ids包含两行整数(每个句子一个),它们是每个句子中标记的唯一标识符。attention_mask是具有与input_ids张量完全相同形状的张量,填充0和1:1表示应注意的相应位置的标记,0表示不应注意的相应位置的标记(即,模型的 attention layers 应忽略它们)。
注意:🤗 Transformers 模型的输出表现得像namedtuples 或字典。您可以通过属性(就像我们所做的那样)或键 ( outputs[“last_hidden_state”])访问元素,如果您确切地知道要查找的内容在哪里,甚至可以通过索引访问元素outputs[0]。
AutoModel这个架构只包含基本的 Transformer 模块:给定一些输入,它输出我们称之为隐藏层的东西,也称为features。对于每个模型输入,我们将得到一个高维向量,表示Transformer 模型对该输入的上下文理解,可以理解成是做了word embedding。
补充:对于不同架构输出会不一样:
model = AutoModel.from_pretrained("bert-base-chinese")
,输出为BaseModelOutputWithPoolingAndCrossAttentions,包含’last_hidden_state’和’pooler_output’两个元素。其中’last_hidden_state’的形状是(batch size,sequence length,768),'pooler_output’的形状是(batch size,768)。pooler output是取[CLS]标记处对应的向量后面接个全连接再接tanh激活后的输出。
虽然这些隐藏状态本身就很有用,但它们通常是模型另一部分(称为head )的输入。在pipeline那一节中,可以使用相同的体系结构执行不同的任务,是因为这些任务中的每一个都有与之关联的不同头。
model = AutoModelForMaskedLM.from_pretrained("bert-base-chinese")
,输出为MaskedLMOutput,包含’logits’元素,形状为[batch size,sequence length,21128],21128是’vocab_size’。model = AutoModelForTokenClassification.from_pretrained( "bert-base-chinese")
,输出为TokenClassifierOutput,包含’logits’元素,形状为[batch size,sequence length,2]。from transformers import AutoModel checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" model = AutoModel.from_pretrained(checkpoint) # inputs见上一个代码示例,model的输入可以是多个参数,但也可以只有input_ids,形状必须是2维张量。 outputs = model(**inputs) print(outputs) # 输出结果为: BaseModelOutput(last_hidden_state=tensor([[[-0.1798, 0.2333, 0.6321, ..., -0.3017, 0.5008, 0.1481], [ 0.2758, 0.6497, 0.3200, ..., -0.0760, 0.5136, 0.1329], [ 0.9046, 0.0985, 0.2950, ..., 0.3352, -0.1407, -0.6464], ..., [ 0.1466, 0.5661, 0.3235, ..., -0.3376, 0.5100, -0.0561], [ 0.7500, 0.0487, 0.1738, ..., 0.4684, 0.0030, -0.6084], [ 0.0519, 0.3729, 0.5223, ..., 0.3584, 0.6500, -0.3883]], [[-0.2937, 0.7283, -0.1497, ..., -0.1187, -1.0227, -0.0422], [-0.2206, 0.9384, -0.0951, ..., -0.3643, -0.6605, 0.2407], [-0.1536, 0.8987, -0.0728, ..., -0.2189, -0.8528, 0.0710], ..., [-0.3017, 0.9002, -0.0200, ..., -0.1082, -0.8412, -0.0861], [-0.3338, 0.9674, -0.0729, ..., -0.1952, -0.8181, -0.0634], [-0.3454, 0.8824, -0.0426, ..., -0.0993, -0.8329, -0.1065]]], grad_fn=<NativeLayerNormBackward>), hidden_states=None, attentions=None) print(outputs.last_hidden_state.shape) # 输出结果为: torch.Size([2, 16, 768])
注意:🤗 Transformers 模型的输出表现得像namedtuples 或字典。您可以通过属性(就像我们所做的那样)或键 ( outputs[“last_hidden_state”])访问元素,如果您确切地知道要查找的内容在哪里,甚至可以通过索引访问元素outputs[0]。
模型头将隐藏状态的高维向量作为输入,并将它们投影到不同的维度上。它们通常由一个或几个线性层组成:
Transformer 模型的输出直接送到模型头部进行处理。
在此图中,transformer network由其嵌入层和后续层表示。嵌入层将标记化输入中的每个输入 ID 转换为表示关联标记的向量。随后的层使用注意力机制操纵这些向量以产生句子的最终表示。
🤗 Transformers 中有许多不同的架构可用,每一种架构都围绕着处理特定任务而设计。这是一个非详尽列表:
*Model (retrieve the hidden states)
*ForCausalLM
*ForMaskedLM
*ForMultipleChoice
*ForQuestionAnswering
*ForSequenceClassification
*ForTokenClassification
and others 🤗
对于我们上文的示例,我们将需要一个带有序列分类头的模型(能够将句子分类为正面或负面)。所以,我们不会使用这个AutoModel类,而是使用AutoModelForSequenceClassification:
from transformers import AutoModelForSequenceClassification checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" model = AutoModelForSequenceClassification.from_pretrained(checkpoint) outputs = model(**inputs) print(outputs.logits.shape) # 输出结果为: # 现在,我们查看输入的形状,维度会低得多: # 模型头将我们之前看到的高维向量作为输入,并输出包含两个值的向量(每个标签一个) torch.Size([2, 2]) print(outputs) # 输出结果为: SequenceClassifierOutput(loss=None, logits=tensor([[-1.5607, 1.6123], [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>), hidden_states=None, attentions=None)
我们的模型预测第一句为[-1.5607,1.6123],第二句为[4.1692,-3.3464]。这些不是概率,而是logits,即模型最后一层输出的原始的、非标准化的分数。要转换为概率,它们需要经过softmax(所有🤗transformers模型都会输出logits,因为用于训练的损失函数通常会将最后一个激活函数(如SoftMax)与实际损失函数(如交叉熵)融合在一起):
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>)
现在我们可以看到模型预测[0.0402, 0.9598]第一个句子和[0.9995, 0.0005]第二个句子。这些是可识别的概率分数。
要获得每个位置对应的标签,我们可以检查id2label模型配置的属性
model.config.id2label
# 输出为:
{0: 'NEGATIVE', 1: 'POSITIVE'}
我们可以使用from_pretrained方法,来加载一个已经训练好的 Transformer 模型。
from transformers import BertModel
model = BertModel.from_pretrained("bert-base-cased")
保存模型就像加载模型一样简单——我们使用save_pretrained方法,它类似于from_pretrained方法:
model.save_pretrained("directory_on_my_computer")
该命令会将config.json pytorch_model.bin
两个文件保存在硬盘。
config.json
文件,构建model architecture所需的属性。此文件还包含一些元数据,例如上次保存 checkpoint时使用的🤗Transformers版本。文件内容如下:{ "_name_or_path": "bert-base-cased", "architectures": [ "BertModel" ], "attention_probs_dropout_prob": 0.1, "gradient_checkpointing": false, "hidden_act": "gelu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "intermediate_size": 3072, "layer_norm_eps": 1e-12, "max_position_embeddings": 512, "model_type": "bert", "num_attention_heads": 12, "num_hidden_layers": 12, "pad_token_id": 0, "position_embedding_type": "absolute", "transformers_version": "4.8.1", "type_vocab_size": 2, "use_cache": true, "vocab_size": 28996 }
pytorch_model.bin
文件称为状态字典;它包含模型的所有权重。configuration是了解model architecture所必需的,而模型权重是模型的参数。Tokenizers:将文本转换为模型可以处理的数据。模型只能处理数字,因此Tokenizers需要将我们的文本输入转换为数字数据。tokenizer 主要分为如图所示三类:
采用word-based vocabulary,英语有17W的单词。中文有2W个字,超过37.5W个词。
word-based和character-based tokenization的缺点
原则:
其他技术
Byte-level BPE, as used in GPT-2
WordPiece, as used in BERT
SentencePiece or Unigram, as used in several multilingual models
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
tokenizer("Using a Transformer network is simple")
# 输出为:
{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
tokenizer将文本转换为数字称为编码。编码分两步完成:标记化,然后转换为输入 ID。
第一步是将文本拆分为单词(或单词的一部分、标点符号等),通常称为标记。有多个规则可以管理该过程,这就是为什么我们需要使用模型名称来实例化分词器,以确保我们使用模型预训练时使用的相同规则。
第二步是将这些标记转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,分词器有一个词汇表,这是我们在使用from_pretrained方法实例化它时下载的部分。同样,我们需要使用模型预训练时使用的相同词汇。
tokenizer方法实现了tokenization,处理special tokens和转化为input ids三个过程。
我们可以使用tokenizer.tokenize(sequence)
来实现tokenization这个过程,输出是 list of strings, or tokens。使用tokenizer.convert_tokens_to_ids(tokens)
来实现将tokens转换为ids。
注意:tokenizer(sequence)方法,可能会在开头添加[CLS]结尾添加[SEP]等特殊词。这是因为模型是用这些进行预训练的,所以为了获得相同的推理结果,我们还需要添加它们。注意有些ckeckpoint不加特殊词,或者加不同的词;模型也可以仅在开头或仅在结尾添加这些特殊词。在任何情况下,tokenizer()都会自动处理这些。。。而使用tokenizer.tokenize(sequence)则不会处理这些。
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") sequence = "Using a Transformer network is simple" tokens = tokenizer.tokenize(sequence) print(tokens) # 输出为: ['Using', 'a', 'Trans', '##former', 'network', 'is', 'simple'] # 这个分词器是一个子词分词器:它对词进行拆分,直到获得可以用其词汇表表示的标记。 # transformer,它被分成两个标记:trans和##former。 ids = tokenizer.convert_tokens_to_ids(tokens) print(ids) # 输出为: [7993, 170, 13809, 23763, 2443, 1110, 3014] # 将其转换为张量后,可以用作模型的输入。
解码:从词汇索引中,我们想要得到一个字符串。这可以通过以下decode方法完成:
decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])
print(decoded_string)
# 输出为:
Using a transformer network is simple
请注意,该decode方法不仅将索引转换回标记,还将属于相同单词的标记组合在一起以生成可读的句子。当我们使用预测新文本的模型(从提示生成的文本,或序列到序列问题(如翻译或摘要))时,这种行为将非常有用。
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequence = "I've been waiting for a HuggingFace course my whole life."
tokens = tokenizer.tokenize(sequence)
ids = tokenizer.convert_tokens_to_ids(tokens)
input_ids = torch.tensor(ids)
# 这里无法执行,将会报错
model(input_ids)
报错原因是我们向model发送了单个序列,而 🤗 Transformers model默认需要多个句子即2维张量。
修改方法:
添加一个维度input_ids = torch.tensor([ids])
。
对于不同长度的序列,需要进行截断或者填充。可以使用tokenizer.pad_token_id找到padding token ID 。
tokenizer能将输出处理为特定框架的tensor— “pt” returns PyTorch tensors, “tf” returns TensorFlow tensors, and “np” returns NumPy arrays。
import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = AutoModelForSequenceClassification.from_pretrained(checkpoint) sequences = [ "I've been waiting for a HuggingFace course my whole life.", "So have I!" ] # 一定要有return_tensors参数,后续才能直接输入给model。 # padding=True/"longest"是将每个sequence填充到该批输入的最大长度 # padding="max_length" 填充每个sequence到model的最大长度(512 for BERT or DistilBERT) # padding="max_length", max_length=8 Will pad the sequences up to the specified max_length # truncation=True 截断每个sequence到modle的最大长度(512 for BERT or DistilBERT) # max_length=8, truncation=True 截断每个sequence到指定的max_length长度 # padding=True, truncation=True, max_length=8 3个参数一起使用,将所有sequence处理为长度8. # padding=True, truncation=True 2个参数一起使用,将所有sequence处理为该批最长sequence的长度或者模型允许的最大长度. tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") output = model(**tokens)
下面是我们如何在 PyTorch 中训练一个批次的序列分类器:
import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification # Same as before checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = AutoModelForSequenceClassification.from_pretrained(checkpoint) sequences = [ "I've been waiting for a HuggingFace course my whole life.", "This course is amazing!", ] batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") # This is new batch["labels"] = torch.tensor([1, 1]) optimizer = AdamW(model.parameters()) loss = model(**batch).loss loss.backward() optimizer.step()
当然,仅仅在两个句子上训练模型不会产生很好的结果。为了获得更好的结果,您需要准备更大的数据集。
在下文中在本节中,我们将使用MRPC(Microsoft Research Paraphase Corpus)数据集作为示例,该数据集在William B.Dolan和Chris Brockett的一篇论文中介绍。该数据集由5801对句子组成,并有一个标签表明它们是否是意译(即,如果两个句子的意思是相同的)。我们选择了它,因为它是一个很小的数据集,因此很容易在上面进行训练。通过以下代码获取该数据集:
from datasets import load_dataset raw_datasets = load_dataset("glue", "mrpc") raw_datasets # 输出结果为: DatasetDict({ train: Dataset({ features: ['idx', 'label', 'sentence1', 'sentence2'], num_rows: 3668 }) validation: Dataset({ features: ['idx', 'label', 'sentence1', 'sentence2'], num_rows: 408 }) test: Dataset({ features: ['idx', 'label', 'sentence1', 'sentence2'], num_rows: 1725 }) }) # 我们得到一个DatasetDict包含训练集、验证集和测试集的对象。 # 每一个都包含几列(sentence1、sentence2、label和idx)和可变数量的行 # 查看训练集中的一条数据 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 .'} # 查看训练集的features raw_train_dataset.features # 输出为: {'idx': Value(dtype='int32', id=None), 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), 'sentence1': Value(dtype='string', id=None), 'sentence2': Value(dtype='string', id=None)} # label是ClassLabel类型,整数到标签名称的映射存储在names中。0对应not_equivalent,1对应equivalent。
我们得到一个DatasetDict包含训练集、验证集和测试集的对象。每一个都包含几列(sentence1、sentence2、label和idx)和可变数量的行,它们是每个集合中元素的数量(因此,训练集中有 3668 对句子,验证集中有 408 对,还有 1725在测试集中)。
token_type_ids是告诉模型输出的哪一部分是第一句,哪一部分是第二句。
请注意,如果选择不同的checkpoint,则在标记化的输入中不一定有token_type_ids(例如,如果您使用DistilBERT模型,则不会返回它们)。只有当模型知道如何处理它们时,它们才会被return,因为它在预训期间看到了它们。在这里,Bert预先接受了token type IDs的训练,在前文谈到的 masked language modeling objective目标之上,Bert还有一个额外的目标,称为下一句预测。这项任务的目标是对句子对之间的关系进行建模。对于下一句预测,模型被提供句子对(带有随机掩蔽的标记),并被要求预测第二句是否在第一句之后。
通常,我们不需要担心标记化输入中是否有 token_type_ids:只要您对标记器和模型使用相同的检查点,一切都会自动处理。
注意:送入tokenizer的两个list的对应关系。如上图的代码,'my name is sylvain’和’i work at hugging face’是一对。输出解释看下面两个图。
from datasets import load_dataset
raw_datasets = load_dataset("glue", "mrpc")
tokenized_dataset = tokenizer(
raw_datasets["train"]["sentence1"],
raw_datasets["train"]["sentence2"],
padding=True,
truncation=True,
return_tensors = 'pt'
)
上面的示例代码在内存使用上有缺点,为此我们使用Dataset.map()方法,该方法提供了更大的灵活性。如果我们需要完成更多的预处理而不仅仅是tokenization。该map方法通过在数据集的每个元素上应用一个函数来工作,所以让我们定义一个函数tokenizes our inputs:
from datasets import load_dataset
raw_datasets = load_dataset("glue", "mrpc")
# 注意:这里不需要使用padding,返回的也不是tensors。
def tokenize_function(example):
return tokenizer(example["sentence1"], example["sentence2"], truncation=True)
# batched=True在调用中使用,map因此该函数一次应用于数据集的多个元素(默认是1000),而不是分别应用于每个元素。这允许更快的预处理。
tokenized_datasets = raw_datasets.map(tokenize_function, batched=True)
🤗 Datasets library 应用这种处理的方式是向数据集添加新字段,预处理函数返回的字典中的每个键对应一个字段。处理后的raw_datasets内容如下:
DatasetDict({
train: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 3668
})
validation: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 408
})
test: Dataset({
features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'],
num_rows: 1725
})
})
Dataset.map通过传递num_proc参数来应用预处理函数时,您甚至可以使用多线程。我们在这里没有这样做,因为 🤗 Tokenizers 库已经使用多个线程来更快地处理我们的样本,但是如果您没有使用由该库支持的tokenizer,可以通过num_proc参数加快预处理。
from datasets import load_dataset raw_datasets = load_dataset("glue", "mrpc") # 注意:这里不需要使用padding,返回的也不是tensors。 def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) # batched=True在调用中使用,map因此该函数一次应用于数据集的多个元素(默认是1000),而不是分别应用于每个元素。这允许更快的预处理。 tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) # 通过remove_columns删除与模型不期望的值对应的列 tokenized_datasets = tokenized_datasets.remove_columns( ["sentence1", "sentence2", "idx"] ) # 通过rename_column重命名一些列 tokenized_datasets = tokenized_datasets.rename_column("label", "labels") #设置数据集的格式,使其返回 PyTorch 张量而不是列表。 tokenized_datasets.set_format("torch") # 检查是否只有我们的模型可以接受的列: tokenized_datasets["train"].column_names # 输出结果为: [ 'attention_mask','input_ids','labels','token_type_ids' ]
在 PyTorch 中,负责将一批样本放在一起的函数称为collate_fn函数。这是您在构建DataLoader时可以传递的参数,默认值是一个函数,它只会将您的样本转换为 PyTorch 张量并连接它们(如果您的元素是列表、元组或字典,则递归)。在我们的情况下这是不可能的,因为我们拥有的输入不会全部具有相同的大小。我们特意推迟了填充,只在每批次必要时应用它,避免过长的输入和大量的填充。这将大大加快训练速度,但请注意,如果您在 TPU 上进行训练,它可能会导致问题——TPU 更喜欢固定形状。
为了能动态填充,我们在tokenize_function函数中没有做padding处理,在之后的data_collator函数做了padding。
from transformers import DataCollatorWithPadding
data_collator = DataCollatorWithPadding(tokenizer=tokenizer)
from datasets import load_dataset from transformers import AutoTokenizer, DataCollatorWithPadding from transformers import TrainingArguments from transformers import AutoModelForSequenceClassification from transformers import Trainer raw_datasets = load_dataset("glue", "mrpc") checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) data_collator = DataCollatorWithPadding(tokenizer=tokenizer) # 在定义训练器之前,第一步是定义一个TrainingArguments类 # 它将包含训练器用于培训和评估的所有超参数。 # 必须提供的唯一参数是保存训练模型的目录。 # TrainingArguments参数指定了训练的设置:输出目录、总的epochs、 # 训练的batch_size、预测的batch_size、warmup的step数、weight_decay和log目录。 training_args = TrainingArguments("test-trainer") model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) trainer = Trainer( model, training_args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"], data_collator=data_collator, tokenizer=tokenizer, ) trainer.train()
上述代码每500步报告一次训练损失,但是不会报告性能如何。这是因为:
代码进行如下修改:
def compute_metrics(eval_preds): metric = load_metric("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels) training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) trainer = Trainer( model, training_args, train_dataset=tokenized_datasets["train"], eval_dataset=tokenized_datasets["validation"], data_collator=data_collator, tokenizer=tokenizer, compute_metrics=compute_metrics )
使用训练的模型预测:
predictions = trainer.predict(tokenized_datasets["validation"])
print(predictions.predictions.shape, predictions.label_ids.shape)
from datasets import load_dataset from transformers import AutoTokenizer, DataCollatorWithPadding from torch.utils.data import DataLoader from transformers import AutoModelForSequenceClassification from transformers import AdamW from transformers import get_scheduler import torch from tqdm.auto import tqdm from datasets import load_metric device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") raw_datasets = load_dataset("glue", "mrpc") checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) data_collator = DataCollatorWithPadding(tokenizer=tokenizer) tokenized_datasets = tokenized_datasets.remove_columns( ["sentence1", "sentence2", "idx"] ) tokenized_datasets = tokenized_datasets.rename_column("label", "labels") tokenized_datasets.set_format("torch") tokenized_datasets["train"].column_names train_dataloader = DataLoader( tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator ) eval_dataloader = DataLoader( tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator ) model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) optimizer = AdamW(model.parameters(), lr=5e-5) num_epochs = 3 num_training_steps = num_epochs * len(train_dataloader) lr_scheduler = get_scheduler( "linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps ) model.to(device) progress_bar = tqdm(range(num_training_steps)) model.train() for epoch in range(num_epochs): for batch in train_dataloader: batch = {k: v.to(device) for k, v in batch.items()} outputs = model(**batch) loss = outputs.loss loss.backward() optimizer.step() lr_scheduler.step() optimizer.zero_grad() progress_bar.update(1) metric= load_metric("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} with torch.no_grad(): outputs = model(**batch) logits = outputs.logits predictions = torch.argmax(logits, dim=-1) metric.add_batch(predictions=predictions, references=batch["labels"]) metric.compute() model.save_pretrained('./my_model/')
from datasets import load_dataset from transformers import AutoTokenizer, DataCollatorWithPadding from torch.utils.data import DataLoader from transformers import AutoModelForSequenceClassification from transformers import AdamW from transformers import get_scheduler import torch from tqdm.auto import tqdm from datasets import load_metric from accelerate import Accelerator import os from multiprocessing import cpu_count cpu_num = cpu_count() # 自动获取最大核心数目 os.environ ['OMP_NUM_THREADS'] = str(cpu_num) os.environ ['OPENBLAS_NUM_THREADS'] = str(cpu_num) os.environ ['MKL_NUM_THREADS'] = str(cpu_num) os.environ ['VECLIB_MAXIMUM_THREADS'] = str(cpu_num) os.environ ['NUMEXPR_NUM_THREADS'] = str(cpu_num) torch.set_num_threads(cpu_num) raw_datasets = load_dataset("glue", "mrpc") checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) data_collator = DataCollatorWithPadding(tokenizer=tokenizer) tokenized_datasets = tokenized_datasets.remove_columns( ["sentence1", "sentence2", "idx"] ) tokenized_datasets = tokenized_datasets.rename_column("label", "labels") tokenized_datasets.set_format("torch") tokenized_datasets["train"].column_names train_dataloader = DataLoader( tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator ) eval_dataloader = DataLoader( tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator ) accelerator = Accelerator() model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) optimizer = AdamW(model.parameters(), lr=5e-5) num_epochs = 3 num_training_steps = num_epochs * len(train_dataloader) lr_scheduler = get_scheduler( "linear", optimizer=optimizer, num_warmup_steps=0, num_training_steps=num_training_steps ) progress_bar = tqdm(range(num_training_steps)) model.train() for epoch in range(num_epochs): for batch in train_dataloader: outputs = model(**batch) loss = outputs.loss accelerator.backward(loss) optimizer.step() lr_scheduler.step() optimizer.zero_grad() progress_bar.update(1) model.save_pretrained('./my_model/')
启动方式:
# 假设代码为train.py文件
# 运行以下命令,它将提示您回答几个问题,并将答案转储到此命令使用的配置文件中
accelerate config
# 启动train.py文件
accelerate launch train.py
略
联系客服