处理多个序列
在上一节中,我们探讨了最简单的用例:对短序列进行推理。但是,已经出现了一些问题
- 我们如何处理多个序列?
- 我们如何处理不同长度的多个序列?
- 词汇表索引是唯一允许模型良好工作的输入吗?
- 序列过长会有什么问题吗?
让我们看看这些问题会带来什么样的挑战,以及我们如何使用 🤗 Transformers API 来解决它们。
模型期望一批输入
在之前的练习中,您看到了序列如何被转换为数字列表。让我们将此数字列表转换为张量并将其发送到模型
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)
# This line will fail.
model(input_ids)
IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)
糟糕!为什么会失败?“我们按照第 2 节中管道中的步骤操作。
问题在于我们将单个序列发送到模型,而 🤗 Transformers 模型默认情况下期望多个句子。在这里,我们尝试执行分词器在将分词器应用于sequence
时在幕后执行的所有操作。但是,如果您仔细观察,您会发现分词器不仅将输入 ID 列表转换为张量,还在其顶部添加了一个维度
tokenized_inputs = tokenizer(sequence, return_tensors="pt")
print(tokenized_inputs["input_ids"])
tensor([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172,
2607, 2026, 2878, 2166, 1012, 102]])
让我们再试一次,并添加一个新维度
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])
print("Input IDs:", input_ids)
output = model(input_ids)
print("Logits:", output.logits)
我们打印输入 ID 以及生成的 logits - 以下是输出
Input IDs: [[ 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]]
Logits: [[-2.7276, 2.8789]]
批处理是同时将多个句子通过模型发送的操作。如果您只有一个句子,则只需使用单个序列构建一个批次即可
batched_ids = [ids, ids]
这是一个包含两个相同序列的批次!
✏️ 试试看!将此batched_ids
列表转换为张量,并将其通过您的模型。检查您是否获得了与之前相同的 logits(但出现两次)!
批处理允许模型在您向其馈送多个句子时工作。使用多个序列就像使用单个序列构建一个批次一样简单。不过,还有一个问题。当您尝试将两个(或更多)句子一起批处理时,它们可能具有不同的长度。如果您之前使用过张量,您就会知道它们需要具有矩形形状,因此您将无法直接将输入 ID 列表转换为张量。为了解决此问题,我们通常会对输入进行填充。
填充输入
以下列表列表无法转换为张量
batched_ids = [
[200, 200, 200],
[200, 200]
]
为了解决此问题,我们将使用填充使我们的张量具有矩形形状。填充通过向值较少的句子添加一个称为填充标记的特殊词来确保所有句子都具有相同的长度。例如,如果您有 10 个包含 10 个词的句子和 1 个包含 20 个词的句子,则填充将确保所有句子都包含 20 个词。在我们的示例中,生成的张量如下所示
padding_id = 100
batched_ids = [
[200, 200, 200],
[200, 200, padding_id],
]
填充标记 ID 可以在tokenizer.pad_token_id
中找到。让我们使用它并将我们的两个句子分别和一起通过模型发送
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
sequence1_ids = [[200, 200, 200]]
sequence2_ids = [[200, 200]]
batched_ids = [
[200, 200, 200],
[200, 200, tokenizer.pad_token_id],
]
print(model(torch.tensor(sequence1_ids)).logits)
print(model(torch.tensor(sequence2_ids)).logits)
print(model(torch.tensor(batched_ids)).logits)
tensor([[ 1.5694, -1.3895]], grad_fn=<AddmmBackward>)
tensor([[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
tensor([[ 1.5694, -1.3895],
[ 1.3373, -1.2163]], grad_fn=<AddmmBackward>)
我们的批处理预测中的 logits 有问题:第二行应该与第二个句子的 logits 相同,但我们得到了完全不同的值!
这是因为 Transformer 模型的关键特征是上下文化每个标记的注意力层。由于它们会关注序列的所有标记,因此它们会考虑填充标记。为了在通过模型传递不同长度的单个句子或传递具有相同句子和应用填充的批次时获得相同的结果,我们需要告诉这些注意力层忽略填充标记。这是通过使用注意力掩码来完成的。
注意力掩码
注意力掩码是与输入 ID 张量具有完全相同形状的张量,填充了 0 和 1:1 表示应关注相应的标记,0 表示不应关注相应的标记(即,应由模型的注意力层忽略它们)。
让我们用注意力掩码完成前面的示例
batched_ids = [
[200, 200, 200],
[200, 200, tokenizer.pad_token_id],
]
attention_mask = [
[1, 1, 1],
[1, 1, 0],
]
outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask))
print(outputs.logits)
tensor([[ 1.5694, -1.3895],
[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
现在我们获得了批次中第二个句子的相同 logits。
注意第二个序列的最后一个值是如何填充 ID 的,它在注意力掩码中是 0 值。
✏️ **试试看!** 在第 2 节中使用的两个句子上手动应用分词(“I’ve been waiting for a HuggingFace course my whole life.” 和 “I hate this so much!”)。将它们传递给模型,并检查是否得到了与第 2 节中相同的 logits。现在使用填充标记将它们批处理在一起,然后创建正确的注意力掩码。检查在通过模型时是否获得了相同的结果!
更长的序列
对于 Transformer 模型,我们可以传递给模型的序列长度是有限制的。大多数模型处理最多 512 或 1024 个标记的序列,当要求处理更长的序列时会崩溃。这个问题有两个解决方案
- 使用支持更长序列长度的模型。
- 截断你的序列。
模型支持不同的序列长度,有些模型专门处理非常长的序列。 Longformer 就是一个例子,另一个是 LED。如果您正在处理需要非常长序列的任务,我们建议您查看这些模型。
否则,我们建议您通过指定 max_sequence_length
参数来截断您的序列
sequence = sequence[:max_sequence_length]