Pytorch
TensorFlow

#
管道的内部

这是第一部分，根据您使用PyTorch或者TensorFlow，内容略有不同。点击标题上方的平台，选择您喜欢的平台！

让我们从一个完整的示例开始，看看在Chapter 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}]

正如我们在Chapter 1中看到的，这个 pipeline 集成了三个步骤：预处理、模型推理和后处理:

让我们快速浏览一下这些内容。

##
使用分词器进行预处理

与其他神经网络一样，Transformer模型无法直接处理原始文本， 因此pipeline的第一步是将文本输入转换为模型能够理解的数字。 为此，我们使用tokenizer(分词器)，负责：

- 将输入拆分为单词、子单词或符号（如标点符号），称为标记(token)
- 将每个标记(token)映射到一个整数
- 添加其他可能对模型有用的输入：例如，位置编码等信息。
- - 位置编码：指示每个词元在句子中的位置。
- - 段落标记：区分不同段落的文本。
- - 特殊标记：例如 [CLS] 和 [SEP] 标记，用于标识句子的开头和结尾。

所有预处理都需要以与模型预训练时完全相同的方式完成(保持分词方式的一致性非常重要，可以确保模型的性能和准确性)，因此我们首先需要从Model Hub中下载这些信息。为此，我们使用`AutoTokenizer`类及其`from_pretrained()`方法。使用我们模型的检查点名称，它将自动获取与模型的标记器相关联的数据，并对其进行缓存（因此只有在您第一次运行下面的代码时才会下载）。

因为`sentiment-analysis`（情绪分析）Pipeline的默认检查点是`distilbert-base-uncased-finetuned-sst-2-english`（你可以看到它的模型介绍页here)，我们运行以下程序：

    from transformers import AutoTokenizer

    checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
    tokenizer = AutoTokenizer.from_pretrained(checkpoint)

一旦我们有了tokenizer，就可以直接将句子传递给它，然后我们会得到一个字典，它包含了可以输入到模型的数据。接下来，我们只需要将输入 ID 列表转换为张量即可。

使用 🤗 Transformers 时，您无需担心它背后的机器学习框架，它可以是 PyTorch、TensorFlow或 Flax。不过，Transformer 模型只接受张量作为输入。如果您不熟悉张量，可以把它看作 NumPy 数组。NumPy 数组可以是标量 (0D)、向量 (1D)、矩阵 (2D) 或更高维度的数组，本质上就是一个张量。其他机器学习框架中的张量也类似，通常与 NumPy 数组一样容易创建。

要指定要返回的张量类型（PyTorch、TensorFlow或plain 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` 包含两行整数（每个句子一行），它们是每个句子中各个 token 的唯一标识符。我们将在本章后面解释 `attention_mask` 的作用。

##
模型推理

我们可以像使用tokenizer一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类，该类也有`from_pretrained()`方法：

    from transformers import AutoModel

    checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
    model = AutoModel.from_pretrained(checkpoint)

在这段代码中，我们加载了之前在 pipeline 中使用的相同检查点（实际上它应该已经被缓存）并用它实例化了一个模型。

这个架构只包含基本的 Transformer 模块：给定一些输入，它会输出我们称之为隐藏状态 (hidden states) 的内容，也称为特征 (features)。对于每个模型输入，我们将检索一个高维向量，该向量表示 Transformer 模型对该输入的上下文理解。 如果你现在对隐藏状态和上下文理解的概念还不理解，也不用担心，我们会在后续的内容中详细解释这些概念，并举例说明它们是如何工作的。

虽然这些隐藏状态本身可能很有用，但它们通常作为下游任务的输入，例如模型头部(head)的输入。在Chapter 1中，我们介绍了可以使用相同的架构执行不同的任务，但每个任务都会有不同的头部与之关联。

###
高维向量？

Transformers模块的向量输出通常较大。它通常有三个维度：

- 批次大小(Batch size): 一次处理的序列数（在我们的示例中为2）。
- 序列长度 (Sequence length): 序列的数值表示的长度（在我们的示例中为16）。
- 隐藏层维度(Hidden size): 每个模型输入的向量维度。

之所以说它是“高维度”的，是因为最后一个维度值。隐藏层维度可以非常大（对于较小的模型768 是常见的，而在较大的模型中，它可以达到 3072 或更多）。

我们可以通过将预处理后的输入数据传递给模型来验证这一点：

    outputs = model(**inputs)
    print(outputs.last_hidden_state.shape)

    torch.Size([2, 16, 768])

🤗 Transformers 模型的输出类似于 `namedtuple` 或字典。您可以通过属性 (就像我们之前所做的那样) 或键 (例如`outputs["last_hidden_state"]`) 来访问元素，甚至可以通过索引访问元素 (例如 `outputs[0]`)，前提是您知道要查找的内容的位置。

###
模型头：数值的意义

模型头接收隐藏状态的高维向量作为输入，并将其映射到另一个维度。它们通常由一个或几个线性层构成：

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

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

🤗 Transformers中有许多不同的体系结构，每种体系结构都是围绕处理特定任务而设计的。以下是一个非详尽的列表：

- `*Model` (retrieve the hidden states)
- `*ForCausalLM`
- `*ForMaskedLM`
- `*ForMultipleChoice`
- `*ForQuestionAnswering`
- `*ForSequenceClassification`
- `*ForTokenClassification`
- 以及其他 🤗

对于我们的示例，我们需要一个带有序列分类头部的模型（能够将句子分类为肯定或否定）。 因此，我们实际上不会使用 `AutoModel` 类，而是使用 `AutoModelForSequenceClassification`(`AutoModel` 类只包含基本的 Transformer 模块，它可以提取文本的特征，但不能进行分类。 `AutoModelForSequenceClassification` 类在 `AutoModel` 的基础上添加了一个序列分类头部，可以将文本分类为不同的类别。 ):

    from transformers import AutoModelForSequenceClassification

    checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
    model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
    outputs = model(**inputs)

现在，如果我们观察输出的shape，维度将低得多：模型头将我们之前看到的高维向量作为输入，并输出包含两个值的向量（每个标签一个）： 正如您所见，输出向量的尺寸与输入向量相比要小得多。这是因为模型头将输入向量中的信息压缩成两个值，每个标签一个

    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`属性（下一节将对此进行详细介绍）： 为了将这些概率分值转换为可识别的标签（“否定”或“肯定”），我们需要参考模型配置中的 id2label 属性。该属性将每个模型输出的 ID 映射到相应的标签。

    model.config.id2label

    {0: 'NEGATIVE', 1: 'POSITIVE'}

根据 id2label 属性，我们可以得出以下结论：

- 第一句：否定：0.0402，肯定：0.9598
- 第二句：否定：0.9995，肯定：0.0005

至此，我们已经成功完成了文本分类任务的三个步骤：使用tokenizer对文本进行预处理;将预处理后的文本输入到模型中;对模型的输出结果进行后处理,并将预测结果转换为可识别的标签。接下来，我们将对这三个步骤进行更详细的解释。

✏️ 试试看！ 选择两个（或更多）你自己的文本并在管道中运行它们。然后自己复制在这里看到的步骤，并检查是否获得相同的结果！

< > Update on GitHub