LLM 课程文档

处理多个序列

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

处理多个序列

Ask a Question Open In Colab Open In Studio Lab

在上一节中,我们探讨了最简单的用例:对单个短序列进行推理。然而,一些问题已经浮现:

  • 我们如何处理多个序列?
  • 我们如何处理不同长度的多个序列?
  • 词汇表索引是唯一能让模型良好工作的输入吗?
  • 序列是否存在过长的情况?

让我们看看这些问题会带来哪些挑战,以及我们如何使用 🤗 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]]

批处理(Batching)是指一次性将多条句子通过模型。如果你只有一条句子,你也可以只用一条序列构建一个批次。

batched_ids = [ids, ids]

这是包含两个相同序列的批次!

✏️ 试一试! 将这个 `batched_ids` 列表转换为张量并传递给你的模型。检查你是否获得了和之前相同的 logits(但结果是两倍)!

批量处理使得模型在接收多条句子时能够正常工作。使用多个序列就像用单个序列构建一个批次一样简单。然而,还有第二个问题。当你尝试将两条(或更多)句子批量处理在一起时,它们的长度可能不同。如果你以前使用过张量,你会知道它们需要是矩形形状的,因此你无法直接将输入 ID 列表转换为张量。为了解决这个问题,我们通常会对输入进行填充(pad)

填充输入

以下列表嵌套列表无法转换为张量:

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 节中使用的两句话(“我一生都在等待 HuggingFace 课程。”和“我太讨厌这个了!”)进行分词。将它们传入模型,检查你是否获得了与第 2 节中相同的 logits。现在使用填充标记将它们批量处理,然后创建正确的注意力掩码。检查通过模型后你是否获得了相同的结果!

更长的序列

对于 Transformer 模型,我们可以传递给模型的序列长度是有限制的。大多数模型处理最长可达 512 或 1024 个标记的序列,如果要求处理更长的序列就会崩溃。这个问题有两种解决方案:

  • 使用支持更长序列长度的模型。
  • 截断你的序列。

模型支持的序列长度不同,有些模型专门处理非常长的序列。Longformer 是一个例子,另一个是 LED。如果你正在处理需要非常长序列的任务,我们建议你看看这些模型。

否则,我们建议你通过指定 `max_sequence_length` 参数来截断你的序列。

sequence = sequence[:max_sequence_length]
< > 在 GitHub 上更新