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中看到的，此管道將三個步驟組合在一起：預處理、通過模型傳遞輸入和後處理：

讓我們快速瀏覽一下這些內容。

## 使用分詞器進行預處理

與其他神經網絡一樣，Transformer 模型無法直接處理原始文本， 因此我們管道的第一步是將文本輸入轉換為模型能夠理解的數字。 為此，我們使用tokenizer(標記器)，負責：

- 將輸入拆分為單詞、子單詞或符號（如標點符號），稱為標記(token)
- 將每個標記(token)映射到一個整數
- 添加可能對模型有用的其他輸入

所有這些預處理都需要以與模型預訓練時完全相同的方式完成，因此我們首先需要從Model Hub中下載這些信息。為此，我們使用`AutoTokenizer`類及其`from_pretrained()`方法。使用我們模型的檢查點名稱，它將自動獲取與模型的標記器相關聯的數據，並對其進行緩存（因此只有在您第一次運行下面的代碼時才會下載）。

因為`sentiment-analysis`（情緒分析）管道的默認檢查點是`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)

一旦我們有了標記器，我們就可以直接將我們的句子傳遞給它，然後我們就會得到一本字典，它可以提供給我們的模型！剩下要做的唯一一件事就是將輸入ID列表轉換為張量。

您可以使用🤗 Transformers，而不必擔心哪個 ML 框架被用作後端；它可能是 PyTorch 或 TensorFlow，或 Flax。但是，Transformers型號只接受張量作為輸入。如果這是你第一次聽說張量，你可以把它們想象成NumPy數組。NumPy數組可以是標量（0D）、向量（1D）、矩陣（2D）或具有更多維度。它實際上是張量；其他 ML 框架的張量行為類似，通常與 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, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        ])
    }

輸出本身是一個包含兩個鍵的字典，`input_ids`和`attention_mask`。`input_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)

在這個代碼片段中，我們下載了之前在管道中使用的相同檢查點（它實際上應該已經被緩存），並用它實例化了一個模型。

這個架構只包含基本轉換器模塊：給定一些輸入，它輸出我們將調用的內容隱藏狀態（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`或詞典相似。您可以通過屬性（就像我們所做的那樣）或鍵（`輸出["last_hidden_state"]`）訪問元素，甚至可以通過索引訪問元素，前提是您確切知道要查找的內容在哪裡（`outputs[0]`）。

### 模型頭：數字的意義

模型頭將隱藏狀態的高維向量作為輸入，並將其投影到不同的維度。它們通常由一個或幾個線性層組成：

Transformers 模型的輸出直接發送到模型頭進行處理。

在此圖中，模型由其嵌入層和後續層表示。嵌入層將標記化輸入中的每個輸入ID轉換為表示關聯標記(token)的向量。後續層使用注意機制操縱這些向量，以生成句子的最終表示。

🤗 Transformers中有許多不同的體系結構，每種體系結構都是圍繞處理特定任務而設計的。以下是一個非詳盡的列表：

- `*Model` (retrieve the hidden states)
- `*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

我們已經成功地複製了管道的三個步驟：使用標記化器進行預處理、通過模型傳遞輸入以及後處理！現在，讓我們花一些時間深入瞭解這些步驟中的每一步。

✏️ 試試看！ 選擇兩個（或更多）你自己的文本並在管道中運行它們。然後自己複製在這裡看到的步驟，並檢查是否獲得相同的結果！