<FrameworkSwitchCourse {fw} />

# 管道的内部 [[管道的内部]]

{#if fw === 'pt'}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section2_pt.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section2_pt.ipynb"},
]} />

{:else}

<CourseFloatingBanner chapter={2}
  classNames="absolute z-10 right-0 top-0"
  notebooks={[
    {label: "Google Colab", value: "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section2_tf.ipynb"},
    {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter2/section2_tf.ipynb"},
]} />

{/if}

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

{#if fw === 'pt'}
<Youtube id="1pedAIvTWXk"/>
{:else}
<Youtube id="wVN12smEvqg"/>
{/if}

让我们从一个完整的示例开始，看看在[Chapter 1](/course/chapter1)中执行以下代码时在幕后发生了什么

```python
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!",
    ]
)
```

获得:

```python out
[{'label': 'POSITIVE', 'score': 0.9598047137260437},
 {'label': 'NEGATIVE', 'score': 0.9994558095932007}]
```

正如我们在[Chapter 1](/course/chapter1)中看到的，此管道将三个步骤组合在一起：预处理、通过模型传递输入和后处理：

<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline.svg" alt="The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/full_nlp_pipeline-dark.svg" alt="The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head."/>
</div>

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

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

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

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

所有这些预处理都需要以与模型预训练时完全相同的方式完成，因此我们首先需要从[Model Hub](https://huggingface.co/models)中下载这些信息。为此，我们使用`AutoTokenizer`类及其`from_pretrained()`方法。使用我们模型的检查点名称，它将自动获取与模型的标记器相关联的数据，并对其进行缓存（因此只有在您第一次运行下面的代码时才会下载）。

因为`sentiment-analysis`（情绪分析）管道的默认检查点是`distilbert-base-uncased-finetuned-sst-2-english`（你可以看到它的模型卡[here](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english))，我们运行以下程序：

```python
from transformers import AutoTokenizer

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

一旦我们有了标记器，我们就可以直接将我们的句子传递给它，然后我们就会得到一本字典，它可以提供给我们的模型！剩下要做的唯一一件事就是将输入ID列表转换为张量。

您可以使用🤗 Transformers，而不必担心哪个ML框架被用作后端；它可能是PyTorch或TensorFlow，或Flax。但是，Transformers型号只接受*张量*作为输入。如果这是你第一次听说张量，你可以把它们想象成NumPy数组。NumPy数组可以是标量（0D）、向量（1D）、矩阵（2D）或具有更多维度。它实际上是张量；其他ML框架的张量行为类似，通常与NumPy数组一样易于实例化。

要指定要返回的张量类型（PyTorch、TensorFlow或plain NumPy），我们使用`return_tensors`参数：

{#if fw === 'pt'}
```python
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)
```
{:else}
```python
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="tf")
print(inputs)
```
{/if}

现在不要担心填充和截断；我们稍后会解释这些。这里要记住的主要事情是，您可以传递一个句子或一组句子，还可以指定要返回的张量类型（如果没有传递类型，您将得到一组列表）。

{#if fw === 'pt'}

以下是PyTorch张量的结果：

```python out
{
    '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]
    ])
}
```
{:else}

以下是TensorFlow张量的结果：

```python out
{
    'input_ids': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [  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]
        ], dtype=int32)>, 
    'attention_mask': <tf.Tensor: shape=(2, 16), dtype=int32, numpy=
        array([
            [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]
        ], dtype=int32)>
}
```
{/if}

输出本身是一个包含两个键的字典，`input_ids`和`attention_mask`。`input_ids`包含两行整数（每个句子一行），它们是每个句子中标记的唯一标记（token）。我们将在本章后面解释什么是`attention_mask`。

## 浏览模型 [[浏览模型]]

{#if fw === 'pt'}
我们可以像使用标记器一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类，该类还具有`from_pretrained()`方法：

```python
from transformers import AutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModel.from_pretrained(checkpoint)
```
{:else}
我们可以像使用标记器一样下载预训练模型。🤗 Transformers提供了一个`TFAutoModel`类，该类还具有`from_pretrained()`方法：

```python
from transformers import TFAutoModel

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModel.from_pretrained(checkpoint)
```
{/if}

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

这个架构只包含基本转换器模块：给定一些输入，它输出我们将调用的内容*隐藏状态（hidden states）*，亦称*特征（features）*。对于每个模型输入，我们将检索一个高维向量，表示**Transformer模型对该输入的上下文理解**。 

如果这不合理，不要担心。我们以后再解释。

虽然这些隐藏状态本身可能很有用，但它们通常是模型另一部分（称为*头部(head)*）的输入。 在[Chapter 1](/course/chapter1)中，可以使用相同的体系结构执行不同的任务，但这些任务中的每个任务都有一个与之关联的不同头。

### 高维向量？ [[高维向量？]]

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

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

由于最后一个值，它被称为“高维”。隐藏的大小可能非常大（768通常用于较小的型号，而在较大的型号中，这可能达到3072或更大）。

如果我们将预处理的输入输入到模型中，我们可以看到这一点：

{#if fw === 'pt'}
```python
outputs = model(**inputs)
print(outputs.last_hidden_state.shape)
```

```python out
torch.Size([2, 16, 768])
```
{:else}
```py
outputs = model(inputs)
print(outputs.last_hidden_state.shape)
```

```python out
(2, 16, 768)
```
{/if}

注意🤗 Transformers模型的输出与`namedtuple`或词典相似。您可以通过属性（就像我们所做的那样）或键（`输出["last_hidden_state"]`）访问元素，甚至可以通过索引访问元素，前提是您确切知道要查找的内容在哪里（`outputs[0]`）。

### 模型头：数字的意义 [[模型头：数字的意义]]

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


<div class="flex justify-center">
<img class="block dark:hidden" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head.svg" alt="A Transformer network alongside its head."/>
<img class="hidden dark:block" src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter2/transformer_and_head-dark.svg" alt="A Transformer network alongside its head."/>
</div>

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

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


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

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

{#if fw === 'pt'}
对于我们的示例，我们需要一个带有序列分类头的模型（能够将句子分类为肯定或否定）。因此，我们实际上不会使用`AutoModel`类，而是使用`AutoModelForSequenceClassification`：

```python
from transformers import AutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = AutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(**inputs)
```
{:else}
For our example, we will need a model with a sequence classification head (to be able to classify the sentences as positive or negative). So, we won't actually use the `TFAutoModel` class, but `TFAutoModelForSequenceClassification`:

```python
from transformers import TFAutoModelForSequenceClassification

checkpoint = "distilbert-base-uncased-finetuned-sst-2-english"
model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint)
outputs = model(inputs)
```
{/if}

现在，如果我们观察输出的形状，维度将低得多：模型头将我们之前看到的高维向量作为输入，并输出包含两个值的向量（每个标签一个）：

```python
print(outputs.logits.shape)
```

{#if fw === 'pt'}

```python out
torch.Size([2, 2])
```

{:else}

```python out
(2, 2)
```

{/if}

因为我们只有两个句子和两个标签，所以我们从模型中得到的结果是2 x 2的形状。

## 对输出进行后处理 [[对输出进行后处理]]

我们从模型中得到的输出值本身并不一定有意义。我们来看看,

```python
print(outputs.logits)
```

{#if fw === 'pt'}
```python out
tensor([[-1.5607,  1.6123],
        [ 4.1692, -3.3464]], grad_fn=<AddmmBackward>)
```
{:else}
```python out
<tf.Tensor: shape=(2, 2), dtype=float32, numpy=
    array([[-1.5606991,  1.6122842],
           [ 4.169231 , -3.3464472]], dtype=float32)>
```
{/if}

我们的模型预测第一句为`[-1.5607, 1.6123]`，第二句为`[ 4.1692, -3.3464]`。这些不是概率，而是*logits*，即模型最后一层输出的原始非标准化分数。要转换为概率，它们需要经过[SoftMax](https://en.wikipedia.org/wiki/Softmax_function)层（所有🤗Transformers模型输出logits，因为用于训练的损耗函数通常会将最后的激活函数（如SoftMax）与实际损耗函数（如交叉熵）融合）：

{#if fw === 'pt'}
```py
import torch

predictions = torch.nn.functional.softmax(outputs.logits, dim=-1)
print(predictions)
```
{:else}
```py
import tensorflow as tf

predictions = tf.math.softmax(outputs.logits, axis=-1)
print(predictions)
```
{/if}

{#if fw === 'pt'}
```python out
tensor([[4.0195e-02, 9.5980e-01],
        [9.9946e-01, 5.4418e-04]], grad_fn=<SoftmaxBackward>)
```
{:else}
```python out
tf.Tensor(
[[4.01951671e-02 9.59804833e-01]
 [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32)
```
{/if}

现在我们可以看到，模型预测第一句为`[0.0402, 0.9598]`，第二句为`[0.9995,  0.0005]`。这些是可识别的概率分数。

为了获得每个位置对应的标签，我们可以检查模型配置的`id2label`属性（下一节将对此进行详细介绍）：

```python
model.config.id2label
```

```python out
{0: 'NEGATIVE', 1: 'POSITIVE'}
```

现在我们可以得出结论，该模型预测了以下几点：
 
- 第一句：否定：0.0402，肯定：0.9598
- 第二句：否定：0.9995，肯定：0.0005

我们已经成功地复制了管道的三个步骤：使用标记化器进行预处理、通过模型传递输入以及后处理！现在，让我们花一些时间深入了解这些步骤中的每一步。

<Tip>

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

</Tip>
