在訓練大語言模型(LLM)時,資料處理是至關重要的一環。為了讓模型有效地學習語言模式,我們需要將原始文字轉換成適合模型輸入的格式。這篇文章將會介紹如何使用滑動視窗技術建立訓練資料集,並使用 PyTorch 建立資料載入器,以實作高效的批次處理。滑動視窗技術可以幫助我們從文字中提取連續的片段,作為模型的輸入和目標。這些片段通常包含一定數量的 token,例如 4 個或 256 個,取決於模型的設定和訓練需求。透過滑動視窗,我們可以有效地利用文字資料,並建立大量的訓練樣本。在 PyTorch 中,我們可以使用 DataLoader 來實作批次處理,將資料分成小批次輸入模型,以提高訓練效率並減少記憶體佔用。

資料取樣與滑動視窗

在建立大語言模型(LLM)的嵌入之前,需要生成輸入-目標對,以便訓練LLM。這些輸入-目標對的結構是什麼?如前所述,LLM是透過預訓練獲得的,具體過程如圖2.12所示。

讓我們實作一個資料載入器,使用滑動視窗方法從訓練資料集中提取圖2.12中的輸入-目標對。首先,我們使用BPE編碼器對整個「判決」短篇故事進行標記化:

with open("the-verdict.txt", "r", encoding="utf-8") as f:
    raw_text = f.read()
    enc_text = tokenizer.encode(raw_text)
    print(len(enc_text))

執行此程式碼將傳回5145,即在應用BPE編碼器後訓練集中的令牌總數。

接下來,我們為了演示目的而從資料集中移除前50個令牌,從而得到稍微有趣的文欄位落:

enc_sample = enc_text[50:]

大語言模型透過預測一個詞彙一次來學習。這個過程涉及到使用滑動視窗從資料集中生成輸入-目標對。下一步就是實作這個滑動視窗機制,以便從資料集中提取有用的資訊。

內容解密:

上述程式碼使用with陳述式開啟檔案,確保檔案在使用後被正確關閉。open函式的encoding引數設定為"utf-8",以便正確讀取包含特殊字元的文字。tokenizer.encode方法將原始文字轉換為令牌序列,然後儲存在enc_text變數中。最後,print陳述式輸出令牌序列的長度。

圖表翻譯:

此圖示展示了使用滑動視窗從資料集中生成輸入-目標對的過程。滑動視窗是一種常見的資料取樣技術,透過在資料集中移動一個固定大小的視窗來提取子序列。在這個例子中,滑動視窗用於從「判決」短篇故事中提取連續的令牌序列,以便訓練大語言模型。

  flowchart TD
    A[載入資料] --> B[標記化]
    B --> C[移除前50個令牌]
    C --> D[生成輸入-目標對]
    D --> E[訓練LLM]

此流程圖描述了從載入資料到訓練大語言模型的整個過程。每個步驟都對應到程式碼中的特定操作,例如載入資料、標記化、移除前50個令牌、生成輸入-目標對和訓練LLM。

自然語言處理中的預測任務

在自然語言處理(NLP)中,預測下一個詞彙是語言模型(LLM)的一項基本任務。這項任務涉及根據給定的文字序列預測下一個詞彙的出現。為了完成這項任務,LLM需要學習文字序列中詞彙之間的模式和關係。

預測任務的實作

實作預測任務的一種方法是建立輸入-目標對。輸入是文字序列的一部分,目標是輸入序列後面的下一個詞彙。例如,給定文字序列「LLMs learn to predict one word at a time」,輸入可以是「LLMs learn to predict one word」,目標可以是「at」。

資料預處理

在訓練LLM之前,需要對文字資料進行預處理。這包括將文字分割成單個詞彙或子詞(tokenization),並將每個詞彙轉換為數字向量(embedding)。這樣,LLM就可以處理文字資料並學習詞彙之間的模式。

訓練過程

在訓練過程中,LLM會接收到輸入序列和目標詞彙。LLM的任務是預測下一個詞彙的出現。為了完成這項任務,LLM需要學習輸入序列和目標詞彙之間的關係。訓練過程涉及遮蔽輸入序列後面的詞彙,只保留輸入序列和目標詞彙。

圖表示意

以下圖表示意了預測任務的過程:

  flowchart TD
    A[文字序列] --> B[輸入序列]
    B --> C[目標詞彙]
    C --> D[預測]
    D --> E[輸出]

在這個圖表中,文字序列被分割成輸入序列和目標詞彙。LLM接收到輸入序列和目標詞彙,並預測下一個詞彙的出現。

內容解密:

在上述流程中,LLM需要學習輸入序列和目標詞彙之間的關係。這涉及到對文字資料的預處理,包括分割文字成單個詞彙或子詞,並將每個詞彙轉換為數字向量。然後,LLM會接收到輸入序列和目標詞彙,並預測下一個詞彙的出現。

圖表翻譯:

上述圖表示意了預測任務的過程。文字序列被分割成輸入序列和目標詞彙。LLM接收到輸入序列和目標詞彙,並預測下一個詞彙的出現。這個圖表展示了LLM如何學習文字序列中詞彙之間的模式和關係,以完成預測任務。

玄貓的語言模型實踐

在實踐語言模型時,瞭解如何將輸入序列轉換為目標序列是非常重要的。下面,我們將探討如何使用Python實作這一過程。

序列轉換

首先,我們需要定義一個序列,並從中提取子序列作為輸入和目標。假設我們有一個編碼樣本enc_sample,我們可以從中提取兩個子序列xy,其中x是原始序列的前context_size個元素,y是從第二個元素開始的前context_size+1個元素。

context_size = 4
enc_sample = [290, 4920, 2241, 287, 257]

x = enc_sample[:context_size]
y = enc_sample[1:context_size+1]

print(f"x: {x}")
print(f"y: {y}")

這將輸出:

x: [290, 4920, 2241, 287]
y: [4920, 2241, 287, 257]

建立下一個詞預測任務

接下來,我們可以建立下一個詞預測任務。這涉及到從輸入序列中提取子序列,並將其餘的元素作為目標。

for i in range(1, context_size+1):
    context = enc_sample[:i]
    desired = enc_sample[i]
    print(context, "---->", desired)

這將輸出:

[290] ----> 4920
[290, 4920] ----> 2241
[290, 4920, 2241] ----> 287
[290, 4920, 2241, 287] ----> 257

在這裡,左邊的序列代表輸入,右邊的元素代表目標token ID。

將Token ID轉換為文字

最後,我們可以將token ID轉換為對應的文字。這需要一個字典或對映,將token ID對映到其對應的文字。

假設我們有一個字典token_to_text,它將token ID對映到其對應的文字,我們可以修改上面的程式碼來輸出文字而不是token ID。

token_to_text = {290: "這", 4920: "是", 2241: "一個", 287: "測試", 257: "句子"}

for i in range(1, context_size+1):
    context = [token_to_text[j] for j in enc_sample[:i]]
    desired = token_to_text[enc_sample[i]]
    print(context, "---->", desired)

這將輸出:

['這'] ----> 是
['這', '是'] ----> 一個
['這', '是', '一個'] ----> 測試
['這', '是', '一個', '測試'] ----> 句子

這樣,我們就成功地將token ID轉換為對應的文字,並建立了下一個詞預測任務。

文字預處理與資料載入

在進行大語言模型(LLM)的訓練之前,我們需要將原始文字資料轉換為模型可以理解的格式。這涉及將文字分割為token,並建立輸入和目標對,以便模型可以預測下一個token。

資料預處理

首先,我們需要將原始文字資料分割為較小的區塊,以便模型可以處理。這可以透過設定一個context_size引數來實作,該引數控制了輸入序列中包含的token數量。

for i in range(1, context_size+1):
    context = enc_sample[:i]
    desired = enc_sample[i]
    print(tokenizer.decode(context), "---->", tokenizer.decode([desired]))

這段程式碼展示瞭如何根據context_size建立輸入和目標對。輸出結果如下:

and ----> established
and established ----> himself
and established himself ----> in
and established himself in ----> a

資料載入

接下來,我們需要建立一個資料載入器,以便模型可以高效地存取和處理資料。這涉及將輸入和目標對轉換為PyTorch張量。

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

class GPTDatasetV1(Dataset):
    def __init__(self, txt, tokenizer, max_length, stride):
        self.input_ids = []
        self.target_ids = []

這段程式碼定義了一個自定義的資料集類別GPTDatasetV1%,該類別繼承自PyTorch的Dataset類別。它包含了兩個列表:input_idstarget_ids%,分別用於儲存輸入和目標張量。

資料載入器實作

使用PyTorch的DataLoader類別,可以實作一個高效的資料載入器。這個載入器可以根據設定的batch_sizestride引數,自動地將資料分割為批次,並提供一個迭代器以便存取批次資料。

data_loader = DataLoader(dataset=GPTDatasetV1(txt, tokenizer, max_length, stride), 
                         batch_size=batch_size, 
                         shuffle=True)

這段程式碼建立了一個資料載入器例項,該例項負責載入和提供批次資料給模型進行訓練。

文字資料的批次處理

在自然語言處理中,文字資料的批次處理是一個非常重要的步驟。這涉及將原始文字資料轉換為模型可以理解的格式,並將其分割成批次,以便於模型的訓練和預測。

文字資料的轉換

首先,我們需要將原始文字資料轉換為模型可以理解的格式。這通常涉及將文字資料分割成單個詞彙或子詞彙,並將其轉換為數字表示。這個過程稱為「tokenization」。

import torch

# 定義原始文字資料
txt = "In the heart of the city stood the old library, a relic from a bygone era. Its stone walls bore the marks of time, and ivy clung tightly to its facade..."

# 對文字資料進行tokenization
token_ids = tokenizer.encode(txt)

批次輸入和目標的建立

接下來,我們需要建立批次輸入和目標。批次輸入是指模型在訓練或預測時所需的輸入資料,而批次目標是指模型預測的結果。

# 定義批次輸入和目標
x = torch.tensor([[ "In", "the", "heart", "of" ],
                 [ "the", "city", "stood", "the" ],
                 [ "old", "library", ",", "a" ]])

y = torch.tensor([[ "the", "heart", "of", "the" ],
                  [ "city", "stood", "the", "old" ],
                  [ "library", ,", “a", relic]])

效率的資料載入器

為了實作效率的資料載入器,我們需要將輸入和目標資料收集到張量中。這樣可以讓模型更快速地處理資料。

# 定義資料載入器
class DataLoader:
    def __init__(self, token_ids, max_length, stride):
        self.token_ids = token_ids
        self.max_length = max_length
        self.stride = stride
        self.input_ids = []
        self.target_ids = []

    def __iter__(self):
        for i in range(0, len(self.token_ids) - self.max_length, self.stride):
            input_chunk = self.token_ids[i:i + self.max_length]
            target_chunk = self.token_ids[i + 1: i + self.max_length + 1]

            self.input_ids.append(torch.tensor(input_chunk))
            self.target_ids.append(torch.tensor(target_chunk))

        return zip(self.input_ids, self.target_ids)

圖示化資料結構

以下圖示化了批次輸入和目標的資料結構:

Figure 2.13 批次輸入和目標的資料結構

在這個圖中,x 和 y 分別代表批次輸入和目標。每一行代表一個輸入或目標例項,而每一列代表一個詞彙或子詞彙。

使用 PyTorch DataLoader 載入資料

在前面的章節中,我們已經介紹瞭如何使用 GPTDatasetV1 類別來定義資料集的結構。現在,我們將使用 PyTorch 的 DataLoader 類別來載入資料。

GPTDatasetV1 類別

GPTDatasetV1 類別繼承自 PyTorch 的 Dataset 類別,定義瞭如何從資料集中取得個別的資料列。每個資料列由一系列的 token ID 組成,根據 max_length 引數進行切割。目標資料則存放在 target_ids 屬性中。

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

def __getitem__(self, idx):
    return self.input_ids[idx], self.target_ids[idx]

create_dataloader_v1 函式

create_dataloader_v1 函式使用 GPTDatasetV1 類別來載入資料,並傳回一個 PyTorch 的 DataLoader 物件。這個函式接受多個引數,包括:

  • txt: 資料集的文字內容
  • batch_size: 批次大小
  • max_length: 每個資料列的最大長度
  • stride: 資料列之間的步長
  • shuffle: 是否打亂資料順序
  • drop_last: 是否丟棄最後一個批次
  • num_workers: 載入資料的工作執行緒數
def create_dataloader_v1(txt, batch_size=4, max_length=256,
                         stride=128, shuffle=True, drop_last=True,
                         num_workers=0):
    tokenizer = tiktoken.get_encoding("gpt2")
    dataset = GPTDatasetV1(txt, tokenizer, max_length, stride)
    dataloader = DataLoader(
        dataset,
        batch_size=batch_size,
        shuffle=shuffle,
        drop_last=drop_last,
        num_workers=num_workers
    )
    return dataloader

使用 DataLoader 載入資料

現在,我們可以使用 create_dataloader_v1 函式來載入資料。這個函式傳回一個 DataLoader 物件,我們可以使用它來迭代載入資料。

dataloader = create_dataloader_v1(txt, batch_size=4, max_length=256)
for batch in dataloader:
    input_ids, target_ids = batch
    # 處理批次資料
    print(input_ids.shape, target_ids.shape)

在這個範例中,我們使用 create_dataloader_v1 函式來載入資料,並設定批次大小為 4,最大長度為 256。然後,我們迭代載入資料,並列印每個批次的輸入和目標資料形狀。

圖表翻譯:

  graph LR
    A[create_dataloader_v1] --> B[GPTDatasetV1]
    B --> C[DataLoader]
    C --> D[批次資料]
    D --> E[處理批次資料]

這個圖表展示瞭如何使用 create_dataloader_v1 函式來載入資料,並傳回一個 DataLoader 物件。然後,我們可以使用這個物件來迭代載入資料,並處理每個批次的資料。

資料集建立與批次處理

在建立語言模型的資料集時,批次處理是一個重要的步驟。批次處理可以幫助我們更有效地利用GPU資源,從而加速訓練過程。在這裡,我們將探討如何建立一個資料集,並使用批次處理來加速訓練。

資料集建立

建立資料集的第一步是讀取原始文字資料。假設我們有一個名為the-verdict.txt的檔案,內容如下:

with open("the-verdict.txt", "r", encoding="utf-8") as f:
    raw_text = f.read()

接下來,我們需要將原始文字資料轉換為一個適合訓練的格式。這裡,我們使用create_dataloader_v1函式來建立一個資料集:

dataloader = create_dataloader_v1(
    raw_text, batch_size=1, max_length=4, stride=1, shuffle=False)

在這裡,batch_size引數設定為1,表示每個批次只包含一個樣本。max_length引數設定為4,表示每個樣本的最大長度為4。stride引數設定為1,表示每個樣本之間的間隔為1。shuffle引數設定為False,表示不隨機排列樣本。

批次處理

現在,我們可以使用iter函式來建立一個迭代器,以便遍歷資料集:

data_iter = iter(dataloader)

接下來,我們可以使用next函式來取得第一個批次:

first_batch = next(data_iter)

印出first_batch變數的內容,可以看到它包含兩個張量:第一個張量儲存輸入token ID,第二個張量儲存目標token ID:

print(first_batch)
# [tensor([[ 40, 367, 2885, 1464]]), tensor([[ 367, 2885, 1464, 1807]])]

在這裡,每個張量包含四個token ID,因為max_length引數設定為4。注意,輸入大小設定為4相當小,只是為了簡單示範。實際上,訓練語言模型時,通常會使用至少256的輸入大小。

內容解密:

在這個例子中,我們使用了create_dataloader_v1函式來建立一個資料集。這個函式需要五個引數:原始文字資料、批次大小、最大長度、間隔和隨機排列。透過調整這些引數,我們可以控制資料集的大小和結構。

圖表翻譯:

  flowchart TD
    A[讀取原始文字資料] --> B[建立資料集]
    B --> C[設定批次大小和最大長度]
    C --> D[遍歷資料集]
    D --> E[取得每個批次的資料]

在這個流程圖中,我們可以看到讀取原始文字資料、建立資料集、設定批次大小和最大長度、遍歷資料集和取得每個批次的資料之間的關係。這有助於我們理解如何建立一個資料集,並使用批次處理來加速訓練。

深入瞭解資料載入器

在深度學習中,資料載入器(Data Loader)是一個重要的工具,負責將資料分批次地輸入模型中進行訓練。瞭解資料載入器的工作原理對於有效地訓練模型至關重要。在本文中,我們將探討資料載入器的設定,包括批次大小(Batch Size)、最大長度(Max Length)和步幅(Stride)。

批次大小(Batch Size)

批次大小是指在一次模型更新中使用的資料數量。批次大小越小,模型更新越頻繁,但也可能導致模型更新過於頻繁,從而增加了訓練時間。另一方面,批次大小越大,模型更新越少,但也可能導致模型更新過於少,從而降低了模型的表現。

在下面的例子中,我們設定批次大小為 8,並使用 create_dataloader_v1 函式建立一個資料載入器:

dataloader = create_dataloader_v1(
    raw_text, batch_size=8, max_length=4, stride=4, shuffle=False
)

最大長度(Max Length)和步幅(Stride)

最大長度是指每個批次中資料的最大長度。步幅是指每個批次中資料的移動步幅。這兩個引數共同決定了資料載入器的行為。

在上面的例子中,我們設定最大長度為 4,步幅為 4。這意味著每個批次中資料的長度為 4,且每個批次中資料的移動步幅為 4。

資料載入器的工作原理

資料載入器的工作原理如下:

  1. 將原始資料分割成批次。
  2. 對每個批次中的資料進行處理,包括填充、截斷等。
  3. 將每個批次中的資料輸入模型中進行訓練。

在下面的例子中,我們使用 next 函式從資料載入器中取得下一個批次:

data_iter = iter(dataloader)
inputs, targets = next(data_iter)
print("Inputs:\n", inputs)
print("\nTargets:\n", targets)

這將輸出:

Inputs:
 tensor([[ 40, 367, 2885, 1464],
        [1807, 3619, 402, 271],
        [10899, 2138, 257, 7026],
        [15632, 438, 2016, 257],
        [ 922, 5891, 1576, 438]])
 
Targets:
 tensor([[2885, 1464, 1807, 3619],
        [402, 271, 10899, 2138],
        [257, 7026, 15632, 438],
        [2016, 257, 922, 5891],
        [1576, 438, 367, 2885]])
內容解密:

在上面的例子中,我們使用 create_dataloader_v1 函式建立了一個資料載入器。這個函式接受原始資料、批次大小、最大長度、步幅和是否打亂資料等引數。然後,我們使用 next 函式從資料載入器中取得下一個批次。這個批次包含了輸入資料和目標資料。

圖表翻譯:

此圖示了資料載入器的工作原理。資料載入器將原始資料分割成批次,然後對每個批次中的資料進行處理。最後,資料載入器將每個批次中的資料輸入模型中進行訓練。

  flowchart TD
    A[原始資料] --> B[分割成批次]
    B --> C[對每個批次中的資料進行處理]
    C --> D[輸入模型中進行訓練]

文字預處理與Token Embeddings

在自然語言處理(NLP)任務中,文字預處理是一個至關重要的步驟。這個步驟涉及將原始文字轉換為機器學習模型可以理解的格式。在本文中,我們將探討如何建立token embeddings,這是將token IDs轉換為向量表示的過程。

文字分詞與Token IDs

首先,我們需要將原始文字分詞為單個token。這個過程稱為tokenization。例如,對於句子"This is an example.", 我們可以將其分詞為[“This”, “is”, “an”, “example”, “."]。

接下來,我們需要將每個token對映為一個唯一的ID。這個過程稱為token ID化。例如,對於上述的token列表,我們可以將其對映為[40134, 2052, 133, 389, 12]。

建立Token Embeddings

建立token embeddings的過程涉及將token IDs轉換為向量表示。這個過程稱為embedding化。embedding是一種連續向量表示,能夠捕捉token之間的語義關係。

在GPT-like模型中,embedding大小通常設定為12,288維。但是,為了簡化示例,我們假設embedding大小為3維。假設我們有四個input tokens,分別為2, 3, 5, 和1,我們可以使用以下程式碼建立token embeddings:

import torch

input_ids = torch.tensor([2, 3, 5, 1])
embedding_size = 3
vocabulary_size = 6

# 初始化embedding權重
embedding_weights = torch.randn(vocabulary_size, embedding_size)

# 建立token embeddings
token_embeddings = embedding_weights[input_ids]

在這個示例中,我們首先初始化embedding權重為一個隨機值。然後,我們使用input IDs從embedding權重中查詢對應的token embeddings。

初始化Embedding權重

在實際應用中,embedding權重需要初始化為隨機值。這個初始化過程稱為隨機初始化。隨機初始化能夠提供一個好的起點,使得模型能夠學習到有效的embedding表示。

最佳化Embedding權重

在模型訓練過程中,embedding權重需要最佳化以適應模型的需求。這個最佳化過程稱為反向傳播演算法。在第5章中,我們將探討如何最佳化embedding權重以提高模型的效能。

圖表翻譯:

  graph LR
    A[文字分詞] --> B[Token ID化]
    B --> C[建立Token Embeddings]
    C --> D[初始化Embedding權重]
    D --> E[最佳化Embedding權重]

在這個圖表中,我們展示了文字預處理和token embeddings建立的過程。從左到右,分別為文字分詞、Token ID化、建立Token Embeddings、初始化Embedding權重和最佳化Embedding權重。

從資料取樣、滑動視窗、批次處理到token embeddings的建立,本文深入淺出地探討了訓練大語言模型(LLM)的關鍵技術細節。透過滑動視窗技術,我們得以有效地從原始文字中提取輸入-目標對,為模型訓練提供必要的資料基礎。文章清晰地闡述瞭如何利用tokenizer進行分詞、編碼,以及如何建構DataLoader來進行批次資料的載入與處理,尤其強調了batch_sizemax_lengthstride等引數對於效能的影響。更進一步,本文還解析了token embeddings的建立過程,以及embedding權重的初始化與最佳化策略,為理解LLM的底層機制提供了重要線索。玄貓認為,掌握這些資料處理和模型訓練的基礎技術,對於構建和應用高效能的LLM至關重要。對於想要深入研究LLM的開發者而言,理解這些核心概念將有助於更好地掌控模型訓練過程,並針對特定應用場景進行客製化調整,從而最大化模型的效能和價值。未來,隨著硬體效能的提升和演算法的持續最佳化,我們預見更大規模、更精細的LLM將在更多領域展現其 transformative 的力量。