LLM 课程文档

管道背后

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始入门

管道背后

Ask a Question Open In Colab Open In Studio Lab
这是第一个部分,其内容会根据您使用 PyTorch 还是 TensorFlow 而略有不同。切换标题顶部的开关以选择您偏好的平台!

让我们从一个完整的示例开始,看看当我们在第一章中执行以下代码时幕后发生了什么

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}]

正如我们在第一章中看到的,此管道将三个步骤组合在一起:预处理、将输入传递到模型中以及后处理

The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head.

让我们快速浏览一下每个步骤。

使用分词器进行预处理

与其他神经网络一样,Transformer 模型无法直接处理原始文本,因此我们管道的第一步是将文本输入转换为模型可以理解的数字。为此,我们使用分词器,它将负责

  • 将输入拆分为称为tokens的单词、子词或符号(如标点符号)
  • 将每个 token 映射到一个整数
  • 添加可能对模型有用的其他输入

所有这些预处理都需要以与模型预训练时完全相同的方式完成,因此我们首先需要从模型中心下载该信息。为此,我们使用 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,或者某些模型的 Flax。但是,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_idsattention_maskinput_ids 包含两行整数(每句一行),它们是每个句子中 token 的唯一标识符。我们将在本章稍后解释 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 模型对该输入的上下文理解

如果这听起来不合理,请不要担心。我们稍后会解释所有这些。

虽然这些隐藏状态本身可能很有用,但它们通常是模型另一部分(称为头部)的输入。在第一章中,不同的任务可以使用相同的架构来执行,但是每个任务都将具有与之关联的不同头部。

高维向量?

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])。

模型头部:理解数字

模型头部将隐藏状态的高维向量作为输入,并将它们投影到不同的维度。它们通常由一个或几个线性层组成

A Transformer network alongside its head.

Transformer 模型的输出直接发送到模型头部进行处理。

在此图中,模型由其嵌入层和后续层表示。嵌入层将标记化输入中的每个输入 ID 转换为表示关联 token 的向量。后续层使用注意力机制来操作这些向量,以生成句子的最终表示。

🤗 Transformers 中有许多不同的架构可用,每种架构都围绕解决特定任务而设计。这是一个非详尽的列表

  • *Model(检索隐藏状态)
  • *ForCausalLM
  • *ForMaskedLM
  • *ForMultipleChoice
  • *ForQuestionAnswering
  • *ForSequenceClassification
  • *ForTokenClassification
  • 以及其他的 🤗

对于我们的示例,我们将需要一个带有序列分类头部的模型(以便能够将句子分类为正面或负面)。因此,我们实际上不会使用 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]。这些不是概率,而是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'}

现在我们可以得出结论,模型预测如下

  • 第一个句子:负面: 0.0402,正面: 0.9598
  • 第二个句子:负面: 0.9995,正面: 0.0005

我们已成功重现了管道的三个步骤:使用分词器进行预处理、将输入传递到模型中以及后处理!现在让我们花一些时间更深入地研究这些步骤中的每一个。

✏️ 尝试一下! 选择您自己的两个(或更多)文本,并通过 sentiment-analysis 管道运行它们。然后复制您在此处看到的步骤,并检查您是否获得相同的结果!

< > 在 GitHub 上更新