Pytorch
TensorFlow

#
處理多個序列

在上一節中，我們探討了最簡單的用例：對一個小長度的序列進行推理。然而，一些問題已經出現：

- 我們如何處理多個序列？

- 我們如何處理多個序列不同長度?

- 詞彙索引是讓模型正常工作的唯一輸入嗎？

- 是否存在序列太長的問題？

讓我們看看這些問題會帶來什麼樣的問題，以及如何使用🤗 Transformers API解決它們

##
模型需要一批輸入

在上一個練習中，您看到了序列如何轉換為數字列表。讓我們將此數字列表轉換為張量，並將其發送到模型：

    import torch
    from transformers import AutoTokenizer, AutoModelForSequenceClassification

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

    sequence = "I've been waiting for a HuggingFace course my whole life."

    tokens = tokenizer.tokenize(sequence)
    ids = tokenizer.convert_tokens_to_ids(tokens)
    input_ids = torch.tensor(ids)
    # This line will fail.
    model(input_ids)

    IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1)

哦不！為什麼失敗了？我們遵循了第2節中管道的步驟。

問題是我們向模型發送了一個序列，而 🤗Transformers 模型默認情況下需要多個句子。在這裡，當我們將分詞器應用於一個應用程序時，我們嘗試在幕後完成分詞器所做的一切，但如果仔細觀察，您會發現它不僅將輸入ID列表轉換為張量，還在其頂部添加了一個維度：

    tokenized_inputs = tokenizer(sequence, return_tensors="pt")
    print(tokenized_inputs["input_ids"])

    tensor([[  101,  1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,
              2607,  2026,  2878,  2166,  1012,   102]])

讓我們重試並添加一個新維度：

    import torch
    from transformers import AutoTokenizer, AutoModelForSequenceClassification

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

    sequence = "I've been waiting for a HuggingFace course my whole life."

    tokens = tokenizer.tokenize(sequence)
    ids = tokenizer.convert_tokens_to_ids(tokens)

    input_ids = torch.tensor([ids])
    print("Input IDs:", input_ids)

    output = model(input_ids)
    print("Logits:", output.logits)

我們打印輸入ID以及生成的logits-以下是輸出：

    Input IDs: [[ 1045,  1005,  2310,  2042,  3403,  2005,  1037, 17662, 12172,  2607, 2026,  2878,  2166,  1012]]
    Logits: [[-2.7276,  2.8789]]

Batching 是一次通過模型發送多個句子的行為。如果你只有一句話，你可以用一個序列構建一個批次：

    batched_ids = [ids, ids]

這是一批兩個相同的序列！

✏️ 試試看！ 將此列表轉換為張量並通過模型傳遞。檢查您是否獲得與之前相同的登錄（但是隻有兩次）

批處理允許模型在輸入多個句子時工作。使用多個序列就像使用單個序列構建批一樣簡單。不過，還有第二個問題。當你試圖將兩個（或更多）句子組合在一起時，它們的長度可能不同。如果您以前使用過張量，那麼您知道它們必須是矩形，因此無法將輸入ID列表直接轉換為張量。為了解決這個問題，我們通常填充輸入。

##
填充輸入

以下列表不能轉換為張量：

    batched_ids = [
        [200, 200, 200],
        [200, 200]
    ]

為了解決這個問題，我們將使用填充使張量具有矩形。Padding通過在值較少的句子中添加一個名為Padding token的特殊單詞來確保我們所有的句子長度相同。例如，如果你有10個包含10個單詞的句子和1個包含20個單詞的句子，填充將確保所有句子都包含20個單詞。在我們的示例中，生成的張量如下所示：

    padding_id = 100

    batched_ids = [
        [200, 200, 200],
        [200, 200, padding_id],
    ]

可以在tokenizer.pad_token_id中找到填充令牌ID. 讓我們使用它，將我們的兩句話分別發送到模型中，並分批發送到一起：

    model = AutoModelForSequenceClassification.from_pretrained(checkpoint)

    sequence1_ids = [[200, 200, 200]]
    sequence2_ids = [[200, 200]]
    batched_ids = [
        [200, 200, 200],
        [200, 200, tokenizer.pad_token_id],
    ]

    print(model(torch.tensor(sequence1_ids)).logits)
    print(model(torch.tensor(sequence2_ids)).logits)
    print(model(torch.tensor(batched_ids)).logits)

    tensor([[ 1.5694, -1.3895]], grad_fn=<AddmmBackward>)
    tensor([[ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)
    tensor([[ 1.5694, -1.3895],
            [ 1.3373, -1.2163]], grad_fn=<AddmmBackward>)

我們批處理預測中的logits有點問題：第二行應該與第二句的logits相同，但我們得到了完全不同的值！

這是因為Transformer模型的關鍵特性是關注層，它將每個標記上下文化。這些將考慮填充標記，因為它們涉及序列中的所有標記。為了在通過模型傳遞不同長度的單個句子時，或者在傳遞一批應用了相同句子和填充的句子時獲得相同的結果，我們需要告訴這些注意層忽略填充標記。這是通過使用 attention mask來實現的。

##
注意力遮罩（Attention masks）

Attention masks 是與輸入 ID 張量形狀完全相同的張量，用0和1填充：1s表示應注意相應的標記，0s表示不應注意相應的標記（即，模型的注意力層應忽略它們）。

讓我們用 attention mask 完成上一個示例：

    batched_ids = [
        [200, 200, 200],
        [200, 200, tokenizer.pad_token_id],
    ]

    attention_mask = [
        [1, 1, 1],
        [1, 1, 0],
    ]

    outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask))
    print(outputs.logits)

    tensor([[ 1.5694, -1.3895],
            [ 0.5803, -0.4125]], grad_fn=<AddmmBackward>)

現在我們得到了該批中第二個句子的相同登錄。

請注意，第二個序列的最後一個值是一個填充ID，它在attention mask中是一個0值。

✏️ 試試看！在第2節中使用的兩個句子上手動應用標記化（“我一生都在等待Hugging Face課程。”和“我非常討厭這個！”）。通過模型傳遞它們，並檢查您是否獲得與第2節中相同的登錄。現在使用填充標記將它們批處理在一起，然後創建適當的注意掩碼。檢查通過模型時是否獲得相同的結果！

##
長序列

對於Transformers模型，我們可以通過模型的序列長度是有限的。大多數模型處理多達512或1024個令牌的序列，當要求處理更長的序列時，會崩潰。此問題有兩種解決方案：

- 使用支持的序列長度較長的模型。

- 截斷序列。

模型有不同的支持序列長度，有些模型專門處理很長的序列。 Longformer 這是一個例子，另一個是 LED . 如果您正在處理一項需要很長序列的任務，我們建議您查看這些模型。

否則，我們建議您通過指定max_sequence_length參數：

    sequence = sequence[:max_sequence_length]

< > Update on GitHub