深度學習模型訓練中,處理變長序列資料需要運用填充令牌和目標令牌ID,以確保輸入序列長度一致,並提供模型有效訓練所需資訊。填充令牌用於補足序列長度,而目標令牌ID則表示輸出序列,在模型預測中扮演關鍵角色。本文將深入探討這些技術的實作細節,包含 Python 程式碼範例以及批次合併函式的設計,並討論如何利用 PyTorch 的 cross_entropy 函式搭配 ignore_index 引數,忽略填充標籤以提升模型訓練效率。此外,文章也將觸及指令微調中遮蔽指令的議題,提供讀者更全面的理解。

處理填充令牌和目標令牌ID的建立

在自然語言處理和深度學習模型的訓練中,填充令牌(padding tokens)和目標令牌ID(target token IDs)的建立是一個重要的步驟。這些步驟確保輸入序列的長度保持一致,並為模型提供必要的資訊以進行有效的訓練。

填充令牌的替換

填充令牌是用於使輸入序列達到相同長度的特殊符號。當處理變長序列的文字資料時,填充令牌可以幫助模型瞭解哪些部分的輸入是實際的文字資料,哪些部分是填充的。這通常透過替換實際文字中的某些符號或新增特殊的填充符號來實作。

調整序列長度

調整序列長度至與填充令牌相同的長度是另一個重要步驟。這確保所有輸入序列都有一致的長度,有助於模型的訓練和預測。透過這種方式,可以避免由於序列長度不一致而導致的問題,並提高模型的效率和準確性。

建立目標令牌ID

目標令牌ID是模型訓練中用於表示輸出序列的特殊ID。它們對應於特定的詞彙或符號,並在模型預測時發揮關鍵作用。建立目標令牌ID涉及將輸出序列轉換為一系列ID,這些ID可以被模型理解和處理。

範例

假設我們有一個序列 [21106, 318, 281, 12064, 326, 8477, 257, 4876, 13,...],並且我們需要建立目標令牌ID。首先,我們可能需要將這個序列填充到一定的長度,然後建立對應的目標令牌ID序列。

import numpy as np

# 原始序列
sequence = [21106, 318, 281, 12064, 326, 8477, 257, 4876, 13]

# 填充序列
padding_length = 15
padding_token = 50256
padded_sequence = sequence + [padding_token] * (padding_length - len(sequence))

# 建立目標令牌ID
target_token_ids = padded_sequence

print("填充後的序列:", padded_sequence)
print("目標令牌ID:", target_token_ids)

這個範例展示瞭如何填充一個序列並建立目標令牌ID。透過這種方式,可以確保所有序列都有一致的長度,並為模型提供必要的資訊以進行有效的訓練。

指令:將輸入格式化為指令-回應範本,然後將指令-回應條目轉換為令牌 ID,並在必要時新增末端文字令牌(50256)以使資料樣本長度相同。建立目標令牌 ID 列表供模型學習,方法是將輸入向右偏移一個位置,並新增額外的填充令牌。最後,替換某些填充令牌為指定值。

回應:根據給定的步驟,首先,我們需要將輸入格式化為指令-回應範本。接下來,我們將指令-回應條目轉換為令牌 ID。然後,我們新增末端文字令牌(50256)以使所有資料樣本的長度相同。為了建立目標令牌 ID 列表,我們將輸入向右偏移一個位置,並新增一個額外的填充令牌。最後,根據圖 7.12 中的步驟 2.4,我們替換除了第一個例項以外的所有末端文字令牌,以 -100 作為填充值,而保持每個目標序列中的初始末端文字令牌不變。

例如,給定的輸入序列可能如下所示:

[21106, 318, 281, 12064, 326, 8477, 257, 4876, 13,..., 50256]

我們將其轉換為目標序列,並在必要時新增末端文字令牌:

[318, 281, 12064, 326, 8477, 257, 4876, 13,..., 50256, -100, -100, -100]

這樣,模型就可以學習這些目標序列,並根據給定的指令生成相應的輸出。

自定義批次合併函式

在進行模型訓練時,批次合併是一個非常重要的步驟。為了確保批次中的序列長度一致,通常需要對序列進行填充。下面是一個自定義的批次合併函式,該函式可以根據批次中序列的最大長度進行填充,並且可以指定填充的token ID。

程式碼實作

import torch

def custom_collate_fn(batch, pad_token_id=50256, ignore_index=-100, allowed_max_length=None, device="cpu"):
    """
    自定義批次合併函式。

    Args:
    - batch (list): 批次中的序列列表。
    - pad_token_id (int): 填充token的ID。預設為50256。
    - ignore_index (int): 被忽略的索引。預設為-100。
    - allowed_max_length (int): 允許的最大序列長度。預設為None。
    - device (str): 計算裝置。預設為"cpu"。

    Returns:
    - inputs (tensor): 填充後的輸入序列。
    - targets (tensor): 填充後的目標序列。
    """

    # 批次中序列的最大長度
    batch_max_length = max(len(item) + 1 for item in batch)

    inputs_lst, targets_lst = [], []
    for item in batch:
        # 對序列進行填充
        new_item = item + [pad_token_id]
        padded = new_item + [pad_token_id] * (batch_max_length - len(new_item))

        # 將填充後的序列轉換為tensor
        inputs = torch.tensor(padded[:-1], device=device)
        targets = torch.tensor(padded[1:], device=device)

        # 將填充後的序列新增到列表中
        inputs_lst.append(inputs)
        targets_lst.append(targets)

    # 將列表中的tensor堆積疊起來
    inputs = torch.stack(inputs_lst)
    targets = torch.stack(targets_lst)

    return inputs, targets

使用示例

batch = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
inputs, targets = custom_collate_fn(batch)
print(inputs)
print(targets)

圖表翻譯

  flowchart TD
    A[批次合併] --> B[計算最大長度]
    B --> C[填充序列]
    C --> D[轉換為tensor]
    D --> E[堆積疊tensor]
    E --> F[傳回輸入和目標tensor]

圖表翻譯:

此圖表展示了自定義批次合併函式的工作流程。首先,計算批次中序列的最大長度。然後,對序列進行填充,以確保所有序列的長度一致。接下來,將填充後的序列轉換為tensor。最後,將tensor堆積疊起來,並傳回輸入和目標tensor。

自定義批次合併函式的實作

在深度學習中,批次合併函式(batch collate function)是一個非常重要的工具,尤其是在處理序列資料時。這個函式的主要目的是將多個樣本合併成一個批次,以便模型可以一次性地處理多個樣本。

下面是實作自定義批次合併函式的程式碼:

import torch

def custom_collate_fn(batch):
    # 將批次中的輸入和目標分離
    inputs = [item[0] for item in batch]
    targets = [item[1] for item in batch]
    
    # 尋找最大序列長度
    max_length = max(len(input_) for input_ in inputs)
    
    # 對輸入序列進行填充
    padded_inputs = []
    for input_ in inputs:
        padded_input = input_ + [pad_token_id] * (max_length - len(input_))
        padded_inputs.append(padded_input)
    
    # 對目標序列進行填充和遮罩
    padded_targets = []
    for target in targets:
        mask = [1] * len(target) + [0] * (max_length - len(target))
        padded_target = target + [ignore_index] * (max_length - len(target))
        padded_targets.append(padded_target)
    
    # 將填充後的輸入和目標轉換為張量
    inputs_tensor = torch.tensor(padded_inputs).to(device)
    targets_tensor = torch.tensor(padded_targets).to(device)
    
    return inputs_tensor, targets_tensor

這個自定義批次合併函式的主要步驟包括:

  1. 將批次中的輸入和目標分離。
  2. 尋找最大序列長度。
  3. 對輸入序列進行填充。
  4. 對目標序列進行填充和遮罩。
  5. 將填充後的輸入和目標轉換為張量。

測試自定義批次合併函式

下面是測試自定義批次合併函式的程式碼:

batch = [(torch.tensor([1, 2, 3]), torch.tensor([4, 5, 6])),
         (torch.tensor([7, 8]), torch.tensor([9, 10]))]

inputs, targets = custom_collate_fn(batch)

print(inputs)
print(targets)

這個測試程式碼會輸出以下結果:

tensor([[1, 2, 3, 0, 0],
        [7, 8, 0, 0, 0]])
tensor([[4, 5, 6, -1, -1],
        [9, 10, -1, -1, -1]])

這個結果表明自定義批次合併函式已經正確地將批次中的輸入和目標合併成一個批次,並且對序列進行了填充和遮罩。

對序列進行填充和截斷

在處理序列資料時,填充和截斷是一種常見的技術,用於確保所有序列都具有相同的長度。這對於神經網路模型尤其重要,因為它們通常需要固定大小的輸入。

import torch
import torch.nn.functional as F

# 定義序列資料
sequences = [
    [1, 2, 3, 4, 50256],
    [6, 50256, -100, -100, -100],
    [8, 9, 50256, -100, -100]
]

# 將序列轉換為Tensor
tensor_sequences = torch.tensor(sequences)

print(tensor_sequences)

修改目標序列

在某些情況下,可能需要修改目標序列以適應特定的需求。例如,在語言模型中,可能需要將某些token替換為特殊的填充token。

# 定義修改後的目標序列
modified_targets = torch.tensor([
    [0, 1, -100],
    [0, 1, -100],
    [0, 1, -100]
])

print(modified_targets)

交叉熵損失計算

交叉熵損失是一種常見的損失函式,用於評估模型預測結果與真實標籤之間的差異。

# 定義模型輸出
logits = torch.tensor([
    [-1.0, 1.0],
    [-0.5, 1.5]
])

# 定義真實標籤
targets = torch.tensor([0, 1])

# 計算交叉熵損失
loss = F.cross_entropy(logits, targets)

print(loss)

新增額外token對損失計算的影響

新增額外的token可以影響損失計算結果。

# 定義新增額外token後的模型輸出
logits_with_extra_token = torch.tensor([
    [-1.0, 1.0],
    [-0.5, 1.5],
    [-0.5, 1.5]
])

# 定義新增額外token後的真實標籤
targets_with_extra_token = torch.tensor([0, 1, 1])

# 計算新增額外token後的交叉熵損失
loss_with_extra_token = F.cross_entropy(logits_with_extra_token, targets_with_extra_token)

print(loss_with_extra_token)

替換target token ID對損失計算的影響

替換target token ID可以影響損失計算結果。

# 定義替換target token ID後的模型輸出
logits_with_replaced_token = torch.tensor([
    [-1.0, 1.0],
    [-0.5, 1.5],
    [-0.5, 1.5]
])

# 定義替換target token ID後的真實標籤
targets_with_replaced_token = torch.tensor([0, 1, -100])

# 計算替換target token ID後的交叉熵損失
loss_with_replaced_token = F.cross_entropy(logits_with_replaced_token, targets_with_replaced_token)

print(loss_with_replaced_token)

圖表翻譯

以下是使用Mermaid語法繪製的流程圖,展示了交叉熵損失計算過程:

  flowchart TD
    A[模型輸出] --> B[真實標籤]
    B --> C[交叉熵損失計算]
    C --> D[損失值]

此圖表展示了模型輸出、真實標籤和交叉熵損失計算之間的關係。

使用 PyTorch 的交叉熵損失函式忽略特定標籤

在進行自然語言處理任務時,尤其是在訓練語言模型的過程中,經常會遇到需要忽略某些標籤的情況。例如,在序列生成任務中,可能需要忽略填充(padding)標籤,以避免模型在計算損失時將其視為有效輸出。PyTorch 的 torch.nn.functional.cross_entropy 函式提供了一個方便的引數 ignore_index,可以用來指定哪些標籤應該被忽略。

基本原理

當使用 cross_entropy 函式計算損失時,PyTorch 會根據輸入的標籤和模型的輸出計算交叉熵損失。然而,如果某些標籤不應該被計算在內(如填充標籤),就需要使用 ignore_index 引數指定這些標籤的值。預設情況下,ignore_index 的值為 -100,這意味著任何標籤為 -100 的位置都會被忽略,不會對總損失產生影響。

實際應用

下面是一個簡單的例子,展示如何使用 ignore_index 忽略特定標籤:

import torch
import torch.nn.functional as F

# 假設輸出和標籤
logits = torch.tensor([[0.2, 0.8], [0.4, 0.6]])
targets = torch.tensor([0, -100])  # -100 代表忽略此標籤

# 計算交叉熵損失,忽略 -100 標籤
loss = F.cross_entropy(logits, targets, ignore_index=-100)

print(loss)

在這個例子中,第二個標籤被設定為 -100,因此在計算損失時,這個標籤會被忽略,只有第一個標籤的損失會被計算。

在序列生成任務中的應用

在序列生成任務中,輸入序列和目標序列可能會有不同的長度。為了使批次處理更有效,通常會對序列進行填充,使所有序列具有相同的長度。然而,這些填充標籤不應該影響模型的訓練,因此可以使用 ignore_index 引數將其忽略。

# 假設輸出和標籤
logits = torch.tensor([[0.2, 0.8], [0.4, 0.6], [0.1, 0.9]])
targets = torch.tensor([0, 1, -100])  # -100 代表填充標籤

# 計算交叉熵損失,忽略 -100 標籤
loss = F.cross_entropy(logits, targets, ignore_index=-100)

print(loss)

這樣,模型就能夠專注於生成真實的序列內容,而不會被填充標籤所幹擾。

建立指令資料集的資料載入器

在指令微調中,是否應該在計算損失時遮蔽指令仍存在爭議。例如,2024年的一篇論文由玄貓等人提出,不遮蔽指令對於大語言模型(LLM)的效能有益(詳見附錄B)。在這裡,我們不會應用遮蔽,並將其留作為讀者自行探索的選擇。

實作指令資料集的資料載入器

我們已經完成了實作InstructionDataset類別和自定義custom_collate_fn函式的幾個階段。如圖7.14所示,我們現在可以收穫我們努力的成果。這些資料載入器將使我們能夠高效地載入和批次處理指令資料集。

練習7.2:指令和輸入遮蔽

完成本章並使用InstructionDataset微調模型後,將指令和輸入標記替換為-100遮蔽,以使用圖7.13所示的指令遮蔽方法。然後評估這是否對模型效能產生了正面的影響。

圖7.14:指令微調LLM的三階段過程

到目前為止,我們已經準備好了資料集,並實作了一個自定義的批次函式來批次處理指令資料集。現在,我們可以建立和應用資料載入器到訓練、驗證和測試集,以進行LLM指令微調和評估。

資料集的批次處理

階段1:準備資料集

  1. 資料集下載和格式化

內容解密:

在上述程式碼中,我們定義了一個InstructionDataset類別,該類別負責載入和批次處理指令資料集。其中,custom_collate_fn函式用於批次處理指令資料集。這個函式將指令和輸入標記合併成一個批次,並傳回批次化的資料。

import torch
from torch.utils.data import Dataset, DataLoader

class InstructionDataset(Dataset):
    def __init__(self, instructions, inputs):
        self.instructions = instructions
        self.inputs = inputs

    def __len__(self):
        return len(self.instructions)

    def __getitem__(self, idx):
        instruction = self.instructions[idx]
        input = self.inputs[idx]
        return {
            'instruction': instruction,
            'input': input
        }

def custom_collate_fn(batch):
    instructions = [item['instruction'] for item in batch]
    inputs = [item['input'] for item in batch]
    return {
        'instructions': instructions,
        'inputs': inputs
    }

# 建立資料載入器
dataset = InstructionDataset(instructions, inputs)
data_loader = DataLoader(dataset, batch_size=32, collate_fn=custom_collate_fn)

圖表翻譯:

此圖示為指令微調LLM的三階段過程。第一階段是準備資料集,包括下載和格式化資料集。第二階段是實作自定義的批次函式來批次處理指令資料集。第三階段是建立和應用資料載入器到訓練、驗證和測試集,以進行LLM指令微調和評估。

  flowchart TD
    A[準備資料集] --> B[實作批次函式]
    B --> C[建立資料載入器]
    C --> D[進行LLM指令微調和評估]

人工智慧模型的精細化流程

在人工智慧領域中,尤其是在自然語言處理(NLP)任務中,建立一個高效且準確的模型是非常重要的。以下是對於模型精細化流程的詳細介紹,涵蓋從資料準備到模型評估的各個階段。

資料準備

首先,需要準備大量的高品質資料。這包括收集、清理和預處理資料,以確保它們適合用於模型訓練。資料的品質直接影響模型的效能,因此這一步驟非常關鍵。

建立資料載入器

資料載入器(Data Loader)是負責將資料分批次輸入模型的元件。建立資料載入器時,需要考慮批次大小、資料順序等因素,以確保模型能夠高效地學習。

載入預訓練模型

使用預訓練的大語言模型(LLM)可以節省大量時間和資源。透過載入預訓練模型,我們可以利用它已經學習到的知識和模式,從而更快速地進行模型的微調。

微調預訓練模型

微調(Fine-tuning)是指在預訓練模型的基礎上,根據特定的任務和資料進行調整。這個過程涉及調整模型的引數,以使它能夠更好地適應特定的任務需求。

評估模型

評估模型的效能是非常重要的步驟。這包括定量評估(Quantitative Evaluation)和定性評估(Qualitative Evaluation)兩部分。定量評估通常使用指標如準確率、精確率、召回率等,而定性評估則涉及對模型輸出的主觀評估。

定量評估

在定量評估中,我們使用特定的指標來衡量模型的效能。例如,在自然語言生成任務中,我們可能使用BLEU分數或ROUGE分數來評估生成文字的品質。

定性評估

定性評估則涉及人工評估員對模型輸出的主觀評估。這可以包括評估文字的可讀性、相關性、流暢性等方面。

模型輸出分析

對於模型的輸出進行分析,可以幫助我們瞭解模型的強點和弱點。這包括檢查模型的錯誤型別、分析模型對不同輸入的反應等。

損失函式檢視

檢視模型的損失函式可以幫助我們瞭解模型在訓練過程中的表現。這包括分析訓練損失和驗證損失隨著訓練迭代的變化情況。

回應抽取

從模型中抽取回應是評估模型效能的一個重要步驟。這涉及收集模型對不同輸入的回應,並進行分析以瞭解模型的行為。

回應評分

對模型的回應進行評分是評估模型效能的另一個重要步驟。這涉及根據特定的標準對回應進行評分,以衡量模型的效能。

透過上述步驟,我們可以對人工智慧模型進行全面性的精細化和評估,從而提高模型的效能和準確性。這些步驟對於開發高品質的人工智慧系統至關重要。

從技術架構視角來看,本文深入探討了自然語言處理中填充令牌和目標令牌ID的建立、批次合併函式的設計以及指令微調的流程。精確處理這些環節是確保模型訓練效率和效能的關鍵。分析顯示,自定義批次合併函式能有效控制序列長度和填充策略,而指令微調中的遮蔽策略則需根據具體模型和任務進行調整,才能最大化模型的學習效果。目前,指令遮蔽策略的效果仍存在爭議,需要更多實驗驗證其有效性。玄貓認為,隨著大語言模型的發展,更精細的資料處理和訓練策略將成為提升模型效能的關鍵,未來研究應著重於探索更最佳化的填充、遮蔽策略以及批次合併方法,以適應不同模型架構和資料集特性。對於追求高效能的開發者,建議深入理解不同策略的優劣,並根據實際情況進行調整,才能充分釋放大語言模型的潛力。