前言

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

  • 我们如何处理多个序列?
  • 我们如何处理不同长度的多个序列?
  • 词汇索引是允许模型良好工作的唯一输入吗?
  • 有没有所谓的过长序列?

让我们看看这些问题提出了哪些类型的问题,以及我们如何使用🤗 Transformers API来解决它们。

src link: https://huggingface.co/learn/nlp-course/chapter2/5

Operating System: Ubuntu 22.04.4 LTS

参考文档

  1. NLP Course - Handling multiple sequences

模型期望接收一批输入数据。

在之前的练习中,您看到了如何将序列转换成数字列表。现在,让我们将这个数字列表转换成张量,并将其发送给模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
1
IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)

哦不!为什么失败了?“我们按照第2节中的管道步骤进行了操作。

问题在于我们向模型发送了一个单独的序列,而🤗 Transformers模型默认期望多个句子。在这里,我们尝试对序列应用分词器时,模仿分词器幕后所做的所有操作。但如果你仔细观察,你会发现分词器不仅仅是将输入ID列表转换成张量,它还在其上增加了一个维度:

1
2
tokenized_inputs = tokenizer(sequence, return_tensors="pt")
print(tokenized_inputs["input_ids"])
1
2
tensor([[  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,
2607, 2026, 2878, 2166, 1012, 102]])

让我们再试一次,并增加一个新的维度:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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——这是输出结果:

1
2
Input IDs: [[ 1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607, 2026,  2878,  2166,  1012]]
Logits: [[-2.7276, 2.8789]]

批处理是将多个句子同时通过模型发送的行为。如果你只有一个句子,你可以构建一个只包含单个序列的批次:

1
batched_ids = [ids, ids]

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

批处理允许模型在您输入多个句子时工作。使用多个序列与构建单个序列的批次一样简单。然而,还有一个问题。当您尝试将两个(或更多)句子组合成一个批次时,它们的长度可能不同。如果您以前处理过张量,您知道它们需要是矩形形状的,所以您不能直接将输入ID的列表转换成张量。为了解决这个问题,我们通常会对输入进行填充(padding)。

填充输入

以下列表的列表不能转换为张量:

1
2
3
4
batched_ids = [
[200, 200, 200],
[200, 200]
]

为了解决这个问题,我们将使用填充来使我们的张量具有矩形形状。填充通过向句子中添加一个特殊单词,即填充标记,确保所有句子的长度相同。例如,如果您有10个包含10个单词的句子和1个包含20个单词的句子,填充将确保所有句子都有20个单词。在我们的示例中,结果张量看起来像这样:

1
2
3
4
5
6
padding_id = 100

batched_ids = [
[200, 200, 200],
[200, 200, padding_id],
]

填充标记的ID可以在tokenizer.pad_token_id中找到。让我们使用它,单独发送我们的两个句子并通过模型进行批处理:

1
2
3
4
5
6
7
8
9
10
11
12
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)
1
2
3
4
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表示相应的标记不应该被关注(即,它们应该被模型的注意力层忽略)。

让我们用注意力掩码来完成前面的示例:

1
2
3
4
5
6
7
8
9
10
11
12
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)
1
2
tensor([[ 1.5694, -1.3895],
[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)

现在我们在批次中的第二个句子得到了相同的logits。

注意第二个序列的最后一个值是一个填充ID,在注意力掩码中对应的是0值。

更长的序列

对于Transformer模型,我们能传递给模型的序列长度是有限制的。大多数模型能处理最多512或1024个标记的序列,当要求处理更长的序列时会崩溃。解决这个问题有两个方法:

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

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

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

1
sequence = sequence[:max_sequence_length]

结语

第二百一十二篇博文写完,开心!!!!

今天,也是充满希望的一天。