让我们从一个完整示例开始,回顾我们在第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!",
]
)
并得到了:
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
{'label': 'NEGATIVE', 'score': 0.9994558095932007}]
如我们在第1章中所见,这个管道包含了三个步骤:预处理、将输入通过模型,以及后处理。

Preprocessing with a tokenizer
像其他神经网络一样,Transformer模型不能直接处理原始文本,所以我们管道的第一步是将文本输入转换为模型可以理解的数字。为此,我们使用一个tokenizer(分词器),它负责:
- 将输入分割成单词、子词或符号(如标点符号),称为标记
- 将每个标记映射为整数
- 添加可能对模型有用的其他输入
所有预处理工作都需要与模型预训练时的方式完全一致,因此我们首先需要从模型库下载这些信息。使用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)
有了分词器后,我们可以直接将句子传递给它,然后我们会得到一个准备好输入模型的字典!剩下的就是将输入ID列表转换为张量。
在使用🤗 Transformers时,你无需担心底层的ML框架(如PyTorch或TensorFlow),因为它们可能支持。然而,Transformer模型只接受张量作为输入。如果你是第一次听说张量,你可以将其想象为NumPy数组。NumPy数组可以是标量(0D)、向量(1D)、矩阵(2D)或具有更多维度。它本质上就是一个张量,其他ML框架的张量行为类似,通常创建起来就像NumPy数组一样简单。
要指定我们想要返回的张量类型(PyTorch、TensorFlow或纯NumPy),我们使用return_tensors参数:
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")
print(inputs)
关于填充和截断,稍后我们会解释。这里要记住的是,你可以传递一个句子或一个句子列表,也可以指定你想要返回的张量类型(如果不指定类型,结果将是一个嵌套的列表)。
以下是作为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, 1, 0, 0, 0, 0, 0, 0, 0, 0]
])
}
输出是一个字典,包含两个键:input_ids和attention_mask。input_ids是一个二维整数数组,每个句子由一组整数标识,这些是每个句子中每个令牌的唯一标识。关于attention_mask的详细说明稍后会介绍。
通过模型处理
我们可以像处理分词器一样下载预训练模型。🤗 Transformers提供了一个AutoModel类,它也有一个from_pretrained()方法:
from transformers import AutoModel
checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
在这段代码中,我们下载了之前在管道中使用的相同检查点(实际上它应该已经被缓存)并用它创建了一个模型。
这个架构只包含基础的Transformer模块:给定一些输入,它会输出我们称之为隐藏状态或特征的东西。对于每个模型输入,我们将得到一个高维向量,表示Transformer模型对这个输入的上下文理解。
如果这听起来不清晰,不用担心,稍后我们会详细解释。
虽然这些隐藏状态本身可能很有用,但它们通常作为模型的另一个部分,称为头的输入。在第1章中,不同的任务可能使用相同的架构,但每个任务都会与之关联不同的头部。
一个高维向量?
Transformer模块输出的向量通常很大。它通常有三个维度:
- 批次大小:一次处理的序列数量(在我们的例子中为2)。
- 序列长度:数值表示序列的长度(在我们的例子中为16)。
- 隐藏大小:每个模型输入的向量维度。
它被称为“高维”是因为最后一个值。隐藏大小可能非常大(对于较小的模型,768是常见的,而在较大的模型中,这可能达到3072或更多)。
我们可以将预处理的输入馈送到我们的模型中查看:
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
torch.Size([2, 16, 768])
注意,🤗 Transformers模型的输出行为就像namedtuple或字典。你可以通过属性(如我们所做的那样)访问元素,通过键(outputs["last_hidden_state"]),或者如果你确切知道要找的是什么,可以通过索引(outputs[0])。
模型头:理解数字的意义
模型头接受高维的隐藏状态向量作为输入,并将其投影到不同的维度上。它们通常由一个或几个线性层组成:

Transformer模型的输出直接发送到模型头进行处理。
在这个图中,模型由嵌入层和随后的层表示。嵌入层将分词输入中的每个输入ID转换为表示关联令牌的向量。随后的层使用注意力机制处理这些向量,以产生句子的最终表示。
🤗 Transformers提供了许多不同的架构,每个架构都是围绕特定任务设计的。以下是一个非详尽的列表:
# (继续列出不同的模型架构)
请根据需要添加模型架构的列表。
*模型(获取隐藏状态)*因果语言模型*遮罩语言模型*多项选择*问答*序列分类*分词标注- 等等 🤗
以我们的示例来说,我们需要一个带有序列分类头的模型(以便能够对句子进行正面或负面分类)。因此,我们不会使用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])
由于我们有两句话和两个标签,从模型得到的结果形状为2 x 2。
输出后处理
我们从模型得到的值本身可能没有意义。让我们来看看:
print(outputs.logits)
tensor([[-1.5607, 1.6123],
[ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
模型预测第一句为[-1.5607, 1.6123],第二句为[ 4.1692, -3.3464]。这些不是概率,而是模型最后一层输出的未经归一化的分数。要转换为概率,它们需要通过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'}
现在我们可以得出结论,模型预测如下:
- 第一句:NEGATIVE: 0.0402, POSITIVE: 0.9598
- 第二句:NEGATIVE: 0.9995, POSITIVE: 0.0005
我们成功地完成了管道的三个步骤:使用分词器进行预处理,将输入传递给模型,以及后处理!现在让我们花点时间深入研究每个步骤。
📝 动手试试! 选择两段(或更多)自己的文本,通过sentiment-analysis管道运行它们。然后自己复制这里看到的步骤,检查是否得到相同的结果!

743

被折叠的 条评论
为什么被折叠?



