返回文章列表

GPT模型文字生成實作與訓練

本文探討如何從零開始實作 GPT 模型,並訓練其進行文字生成。文章涵蓋了模型架構、程式碼解析、技術原理、訓練流程、效能評估以及損失函式的應用。同時,也提供了一些程式碼範例和圖表,幫助讀者更好地理解 GPT 模型的運作機制和訓練細節。

深度學習 自然語言處理

現今自然語言處理領域中,GPT 模型已成為文字生成任務的重要工具。本文將引導讀者逐步實作一個簡易的 GPT 模型,並探討其訓練過程。從程式碼層面解析模型的輸入、輸出以及核心函式,並輔以圖表說明文字生成的流程。此外,文章也將介紹如何使用損失函式評估模型效能,為後續的模型訓練奠定基礎。讀者將能透過本文掌握 GPT 模型的核心概念和實作技巧,並瞭解如何評估和提升模型的生成能力。

實作GPT模型進行文字生成

本章節將探討如何從零開始實作一個GPT模型,以實作文字生成的功能。我們將詳細介紹相關程式碼,並逐步解析其背後的技術原理。

簡易文字生成函式

def generate_text_simple(model, idx, max_new_tokens, context_size):
    for _ in range(max_new_tokens):
        idx_cond = idx[:, -context_size:]
        with torch.no_grad():
            logits = model(idx_cond)
        logits = logits[:, -1, :]
        probas = torch.softmax(logits, dim=-1)
        idx_next = torch.argmax(probas, dim=-1, keepdim=True)
        idx = torch.cat((idx, idx_next), dim=1)
    return idx

內容解密:

  1. generate_text_simple函式:此函式負責根據給定的模型和初始輸入索引(idx),生成指定數量(max_new_tokens)的新文字標記。
  2. 輸入索引裁剪idx_cond = idx[:, -context_size:],確保輸入索引不會超過模型的上下文大小限制。
  3. 模型預測:使用模型對裁剪後的輸入索引進行預測,獲得輸出邏輯值(logits)。
  4. 轉換為機率分佈:透過torch.softmax函式,將邏輯值轉換為機率分佈(probas)。
  5. 選擇下一個標記:使用torch.argmax函式,選取機率最高的標記索引(idx_next)。
  6. 更新輸入索引:將新標記索引追加到原始輸入索引(idx)後面。

文字生成流程

圖4.17展示了GPT模型進行文字生成的單一迭代過程。首先,將輸入文字編碼為標記ID;然後,將這些ID輸入GPT模型;接著,將模型的輸出轉換迴文字,並追加到原始輸入文字中。

步驟解析:

  1. 文字編碼:將輸入文字轉換為標記ID。
  2. 模型預測:GPT模型根據輸入的標記ID,輸出一個矩陣,其中每個向量代表詞彙表中每個詞的邏輯值。
  3. 提取最後一個向量:選擇最後一個向量,因為它對應於下一個要生成的標記。
  4. 轉換為機率分佈:使用softmax函式,將邏輯值轉換為機率分佈。
  5. 選擇下一個標記:找出機率分佈中最大的元素,其索引即為下一個標記的ID。
  6. 追加標記:將新標記追加到原始輸入中,供下一輪迭代使用。

例項演示

start_context = "Hello, I am"
encoded = tokenizer.encode(start_context)
encoded_tensor = torch.tensor(encoded).unsqueeze(0)

model.eval()
out = generate_text_simple(
    model=model,
    idx=encoded_tensor,
    max_new_tokens=6,
    context_size=GPT_CONFIG_124M["context_length"]
)

內容解密:

  1. 編碼初始上下文:將輸入文字"Hello, I am"編碼為標記ID。
  2. 轉換為張量:將編碼結果轉換為PyTorch張量,並新增批次維度。
  3. 設定模型為評估模式:停用dropout等訓練相關的隨機成分。
  4. 呼叫文字生成函式:使用generate_text_simple函式生成新文字。

結果分析

圖4.18展示了六次迭代的文字生成過程。每次迭代,模型根據當前輸入的標記ID,預測下一個標記ID,並將其追加到輸入序列中。

迭代過程:

  1. 初始輸入:提供初始標記ID序列。
  2. 預測下一個標記:模型預測下一個標記ID。
  3. 追加新標記:將預測的標記ID追加到輸入序列中。
  4. 重複迭代:直到達到指定的最大新標記數量。

本章節詳細介紹瞭如何實作GPT模型進行文字生成,包括相關程式碼的解析和技術原理的闡述。透過這些內容,讀者可以更深入地理解GPT模型的運作機制和實作細節。

訓練大語言模型:實作與評估

在前一章中,我們完成了GPT架構的實作與初始化,但尚未進行模型訓練。本章將著重於實作訓練函式、進行模型預訓練,以及評估生成的文字品質。

5.1 評估生成式文字模型

首先,我們將簡要回顧上一章的文字生成內容,接著設定大語言模型(LLM)進行文字生成,並討論基本的文字評估方法。

權重引數

在LLM和其他深度學習模型中,權重是指透過學習過程調整的可訓練引數。這些權重也被稱為權重引數或簡稱為引數。在PyTorch等框架中,這些權重儲存線上性層中。我們在第3章實作了多頭注意力模組,並在第4章實作了GPT模型。在初始化層後,可以透過.weight屬性存取其權重。此外,PyTorch允許透過model.parameters()方法直接存取模型的所有可訓練引數,包括權重和偏差。

實作訓練函式與預訓練LLM

本章的主要目標包括:

  1. 計算訓練和驗證集損失:評估LLM在訓練過程中生成的文字品質。
  2. 實作訓練函式並預訓練LLM:編寫訓練迴圈並進行模型預訓練。
  3. 儲存和載入模型權重:實作模型的儲存和載入功能,以便繼續訓練。
  4. 載入預訓練權重:從OpenAI載入預訓練權重,為模型的微調提供良好的起點。

5.1.1 文字生成與評估

decoded_text = tokenizer.decode(out.squeeze(0).tolist())
print(decoded_text)

內容解密:

這段程式碼使用tokenizer的.decode方法,將模型的輸出ID轉換迴文字。這裡的out是模型的輸出張量,.squeeze(0)用於去除批次維度,.tolist()將張量轉換為列表,以便傳遞給.decode方法。輸出的decoded_text即為模型生成的文字。

在未經訓練的情況下,模型生成的文字通常是無意義的亂碼,如下所示:

Hello, I am Featureiman Byeswickattribute argue

這表明模型尚未學會生成連貫的文字。

為何需要訓練模型?

由於我們尚未對模型進行訓練,因此無法生成連貫的文字。模型的訓練是一個龐大的主題,將在後續章節中詳細討論。

練習5.1:修改dropout引數

在前一章中,我們在GPT_CONFIG_124M字典中定義了一個全域的drop_rate設定,用於設定GPT模型架構中的dropout率。請修改程式碼,為模型架構中的不同dropout層指定不同的dropout值。

修改建議:

  1. GPT_CONFIG_124M字典中新增多個dropout相關的設定,例如embedding_dropout_rateattention_dropout_rateresidual_dropout_rate
  2. 在GPT模型的實作中,將不同的dropout層使用不同的dropout率。
GPT_CONFIG_124M = {
    # ... 其他設定 ...
    "embedding_dropout_rate": 0.1,
    "attention_dropout_rate": 0.2,
    "residual_dropout_rate": 0.1,
}

# 在GPTModel中使用不同的dropout率
class GPTModel(nn.Module):
    def __init__(self, config):
        super().__init__()
        # ... 其他初始化 ...
        self.embedding_dropout = nn.Dropout(config["embedding_dropout_rate"])
        # ... 其他層的初始化 ...
        self.attention_dropout = nn.Dropout(config["attention_dropout_rate"])
        # ... 其他層的初始化 ...
        self.residual_dropout = nn.Dropout(config["residual_dropout_rate"])

內容解密:

這段程式碼展示瞭如何在組態字典中新增不同的dropout率,並在GPT模型的初始化中使用這些設定。透過這種方式,可以對不同層使用不同的dropout策略,從而更好地控制模型的正則化行為。

本章接下來將探討模型的預訓練過程,包括實作訓練迴圈、評估模型效能以及載入預訓練權重等重要步驟。透過這些內容,讀者將能夠全面理解如何有效地訓練一個大語言模型。

預訓練於未標註資料

5.1.1 使用 GPT 生成文字

首先,我們需要設定大語言模型(LLM)並簡要回顧在第4章中實作的文字生成過程。我們首先使用 GPTModel 類別和 GPT_CONFIG_124M 字典初始化 GPT 模型,稍後將對其進行評估和訓練:

import torch
from chapter04 import GPTModel

GPT_CONFIG_124M = {
    "vocab_size": 50257,
    "context_length": 256,
    "emb_dim": 768,
    "n_heads": 12,
    "n_layers": 12,
    "drop_rate": 0.1,
    "qkv_bias": False
}

torch.manual_seed(123)
model = GPTModel(GPT_CONFIG_124M)
model.eval()

相較於前一章,我們對 GPT_CONFIG_124M 字典進行了唯一調整,即將上下文長度(context_length)減少到 256 個標記。這項修改減少了訓練模型的計算需求,使得在標準筆記型電腦上進行訓練成為可能。

內容解密:

  1. 初始化 GPT 模型:使用 GPTModel 類別和 GPT_CONFIG_124M 組態字典來初始化模型。
  2. 設定隨機種子:使用 torch.manual_seed(123) 設定隨機種子,以確保結果的可重現性。
  3. 模型評估模式:呼叫 model.eval() 將模型設為評估模式。

原始的 GPT-2 模型擁有 1.24 億引數,組態為可處理最多 1,024 個標記。在訓練過程後,我們將更新上下文大小設定以配合 1,024 個標記的組態。

使用 GPTModel 例項,我們採用了來自第4章的 generate_text_simple 函式,並引入了兩個實用的函式:text_to_token_idstoken_ids_to_text。這些函式促進了文字和標記表示之間的轉換,這是本章將會用到的技術。

圖5.3:文字生成過程

此圖展示了使用 GPT 模型進行文字生成的三步驟過程。首先,分詞器將輸入文字轉換為一系列標記 ID(見第2章)。其次,模型接收這些標記 ID 並生成相應的 logits,這些是代表詞彙表中每個標記的機率分佈的向量(見第4章)。第三,將這些 logits 轉換回標記 ID,分詞器將其解碼為人類可讀的文字,從而完成從文字輸入到文字輸出的迴圈。

程式碼實作:

import tiktoken
from chapter04 import generate_text_simple

def text_to_token_ids(text, tokenizer):
    encoded = tokenizer.encode(text, allowed_special={'<|endoftext|>'})
    encoded_tensor = torch.tensor(encoded).unsqueeze(0)
    return encoded_tensor

def token_ids_to_text(token_ids, tokenizer):
    flat = token_ids.squeeze(0)
    return tokenizer.decode(flat.tolist())

start_context = "Every effort moves you"
tokenizer = tiktoken.get_encoding("gpt2")
token_ids = generate_text_simple(
    model=model,
    idx=text_to_token_ids(start_context, tokenizer),
    max_new_tokens=10,
    context_size=GPT_CONFIG_124M["context_length"]
)
print("Output text:\n", token_ids_to_text(token_ids, tokenizer))

輸出文字:

Every effort moves you rentingetic wasn? refres RexMeCHicular stren

顯然,該模型尚未生成連貫的文字,因為它尚未經過訓練。為了定義什麼是“連貫”或“高品質”的文字,我們必須實作一種數值方法來評估生成的內容。這種方法將使我們能夠在整個訓練過程中監控和提高模型的效能。

5.1.2 計算文字生成損失

接下來,讓我們探討在訓練過程中透過計算文字生成損失來數值評估文字品質的技術。我們將逐步介紹這個主題,並透過一個實際例子使概念變得清晰和適用。首先,我們簡要回顧如何載入資料以及如何透過 generate_text_simple 函式生成文字。

圖5.4:文字生成流程

此圖展示了從輸入文字到 LLM 生成文字的整體流程,使用了一個五步驟的程式。這個文字生成過程顯示了 generate_text_simple 函式內部的工作原理。在計算衡量生成文字品質的損失之前,我們需要執行這些相同的初始步驟。

內容解密:

  1. 文字生成流程:圖5.4展示了文字生成的五步驟流程,包括輸入文字、分詞、模型處理、logits 生成和最終的文字輸出。
  2. 小詞彙表範例:為了使圖表適合單頁,圖5.4使用了包含七個標記的小詞彙表。然而,我們的 GPTModel 使用的是更大的詞彙表。

程式碼與損失計算:

# 後續程式碼將展示如何計算文字生成損失

評估生成式文字模型

在前面的章節中,我們已經瞭解瞭如何將輸入文字對映到標記ID,並使用GPT模型生成文字。在本文中,我們將探討如何評估生成式文字模型的效能。

從標記ID到機率分數

首先,讓我們回顧一下圖5.4所示的過程。給定兩個輸入範例(“every effort moves"和"I really like”),我們將其對映到標記ID:

inputs = torch.tensor([[16833, 3626, 6100],  # ["every effort moves"]
                       [40, 1107, 588]])     # "I really like"

對應的目標包含我們希望模型生成的標記ID:

targets = torch.tensor([[3626, 6100, 345],   # ["effort moves you"]
                        [1107, 588, 11311]]) # "really like chocolate"

接下來,我們將輸入饋送到模型中,以計算兩個輸入範例的logits向量,每個範例包含三個標記。然後,我們應用softmax函式將這些logits轉換為機率分數(probas):

with torch.no_grad():
    logits = model(inputs)
    probas = torch.softmax(logits, dim=-1)
    print(probas.shape)

輸出的機率分數張量的維度為torch.Size([2, 3, 50257]),其中2對應於輸入範例的數量(批次大小),3對應於每個輸入中的標記數量,50257對應於詞彙表的大小。

內容解密:

  1. 輸入和目標的準備:首先,我們準備輸入和目標張量,分別代表輸入文字的標記ID和期望輸出的標記ID。
  2. 模型推斷:使用GPT模型對輸入進行推斷,得到logits向量。
  3. softmax函式應用:將logits向量透過softmax函式轉換為機率分數,這些分數代表了模型對每個標記的預測機率。
  4. 機率分數的解析:輸出的機率分數張量的形狀揭示了批次大小、每個輸入的標記數量以及詞彙表的大小。

從機率分數到標記ID

然後,我們透過應用argmax函式來獲得對應的標記ID:

token_ids = torch.argmax(probas, dim=-1, keepdim=True)
print("Token IDs:\n", token_ids)

這給出了兩個批次的輸出,每個包含三個預測的標記ID。

內容解密:

  1. argmax函式應用:對機率分數應用argmax函式,以獲得每個標記位置上機率最高的標記ID。
  2. 標記ID的解析:輸出的標記ID代表了模型預測的下一個標記。

評估模型的效能

最後,我們將預測的標記ID轉換迴文字,以評估模型的效能:

print(f"Targets batch 1: {token_ids_to_text(targets[0], tokenizer)}")
print(f"Outputs batch 1: {token_ids_to_text(token_ids[0].flatten(), tokenizer)}")

由於模型尚未訓練,生成的文字與目標文字大不相同。

內容解密:

  1. 文字解碼:使用tokenizer將標記ID解碼迴文字,以便於理解模型的輸出。
  2. 效能評估:比較目標文字和模型生成的文字,可以評估模型的當前效能。

使用損失函式進行數值評估

為了數值化評估模型的效能,我們需要實作一個損失函式。損失函式可以衡量生成的標記與正確預測之間的差異。

內容解密:

  1. 損失函式的目的:損失函式用於評估模型的預測與實際目標之間的差異,是訓練模型的重要組成部分。
  2. 訓練過程中的作用:透過最小化損失函式,可以調整模型的權重,以生成更接近目標文字的輸出。

在下一節中,我們將實作損失函式,以評估模型的效能並為訓練過程做準備。

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title GPT模型文字生成實作與訓練

package "機器學習流程" {
    package "資料處理" {
        component [資料收集] as collect
        component [資料清洗] as clean
        component [特徵工程] as feature
    }

    package "模型訓練" {
        component [模型選擇] as select
        component [超參數調優] as tune
        component [交叉驗證] as cv
    }

    package "評估部署" {
        component [模型評估] as eval
        component [模型部署] as deploy
        component [監控維護] as monitor
    }
}

collect --> clean : 原始資料
clean --> feature : 乾淨資料
feature --> select : 特徵向量
select --> tune : 基礎模型
tune --> cv : 最佳參數
cv --> eval : 訓練模型
eval --> deploy : 驗證模型
deploy --> monitor : 生產模型

note right of feature
  特徵工程包含:
  - 特徵選擇
  - 特徵轉換
  - 降維處理
end note

note right of eval
  評估指標:
  - 準確率/召回率
  - F1 Score
  - AUC-ROC
end note

@enduml

此圖示說明瞭從輸入文字到生成文字並評估模型效能的整個過程。