在訓練大語言模型(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
,我們可以從中提取兩個子序列x
和y
,其中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_ids和
target_ids%,分別用於儲存輸入和目標張量。
資料載入器實作
使用PyTorch的DataLoader
類別,可以實作一個高效的資料載入器。這個載入器可以根據設定的batch_size
和stride
引數,自動地將資料分割為批次,並提供一個迭代器以便存取批次資料。
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。
資料載入器的工作原理
資料載入器的工作原理如下:
- 將原始資料分割成批次。
- 對每個批次中的資料進行處理,包括填充、截斷等。
- 將每個批次中的資料輸入模型中進行訓練。
在下面的例子中,我們使用 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_size
、max_length
和stride
等引數對於效能的影響。更進一步,本文還解析了token embeddings的建立過程,以及embedding權重的初始化與最佳化策略,為理解LLM的底層機制提供了重要線索。玄貓認為,掌握這些資料處理和模型訓練的基礎技術,對於構建和應用高效能的LLM至關重要。對於想要深入研究LLM的開發者而言,理解這些核心概念將有助於更好地掌控模型訓練過程,並針對特定應用場景進行客製化調整,從而最大化模型的效能和價值。未來,隨著硬體效能的提升和演算法的持續最佳化,我們預見更大規模、更精細的LLM將在更多領域展現其 transformative 的力量。