LoRA 技術的核心思想是利用低秩矩陣來近似權重更新,從而減少模型微調的計算量和儲存需求。這種方法特別適用於大型預訓練模型,因為直接微調這些模型通常需要大量的計算資源。在垃圾郵件分類別任務中,LoRA 可以透過微調預訓練的語言模型,使其適應垃圾郵件的分類別任務,並提升分類別效能。具體來說,我們首先需要準備垃圾郵件資料集,並將其轉換為適合模型訓練的格式。接著,我們載入預訓練的語言模型,並初始化 LoRA 層。然後,我們使用準備好的資料集對模型進行微調,並在微調過程中監控模型的效能指標,例如損失值和準確率。最後,我們在測試集上評估模型的效能,以驗證 LoRA 微調的有效性。

LoRA 在模型微調中的應用

在深度學習中,尤其是在自然語言處理(NLP)領域,預訓練模型(Pretrained Model)已經成為了一種重要的工具。然而,直接對預訓練模型進行微調可能會面臨一些挑戰,例如需要大量的計算資源和資料。為瞭解決這些問題,LoRA(Low-Rank Adaptation of Weights)是一種被提出來用於減少微調過程中需要更新的權重數量的方法。

LoRA 的原理

LoRA 的基本思想是透過引入兩個額外的矩陣(A 和 B)來近似權重更新矩陣 ΔW。這兩個矩陣的尺寸遠小於原始權重矩陣,因此大大減少了需要更新的引數數量。這使得 LoRA 在實踐中非常有用,因為它允許預訓練模型的權重保持不變,而 LoRA 矩陣可以在訓練後動態應用。

LoRA 的優點

  1. 減少儲存需求:透過保持 LoRA 權重與原始模型權重分開,LoRA 可以在不需要儲存多個完整版本的 LLM的情況下實作模型定製。這減少了儲存需求,提高了可擴充套件性。
  2. 動態應用:LoRA 矩陣可以在訓練後動態應用,這意味著可以根據具體的客戶或應用需求定製 LLM,而無需修改原始模型權重。

LoRA 在垃圾郵件分類別中的應用

下面,我們將展示如何使用 LoRA 對 LLM 進行微調,以實作垃圾郵件分類別。這個例子與第 6 章中的微調示例類別似。

準備資料集

在應用 LoRA 之前,我們需要載入資料集和預訓練模型。以下程式碼重複了第 6 章中的資料準備步驟。

from pathlib import Path
import pandas as pd

from ch06 import (
    download_and_unzip_spam_data,
    create_balanced_dataset,
)

下載和儲存資料集

首先,我們下載資料集並將其儲存為 CSV 檔案。

# 下載資料集
download_and_unzip_spam_data()

# 載入資料集
train_df = pd.read_csv(Path('data') / 'train.csv')
test_df = pd.read_csv(Path('data') / 'test.csv')

# 建立平衡資料集
balanced_train_df, balanced_test_df = create_balanced_dataset(train_df, test_df)

應用 LoRA 進行微調

在準備好資料集後,我們可以使用 LoRA 對 LLM 進行微調。具體的實作細節將在後續章節中介紹。

準備垃圾郵件資料集

首先,我們需要下載並解壓縮垃圾郵件資料集。這個資料集包含了許多簡訊,分類別為垃圾郵件(spam)或正常郵件(ham)。我們使用 download_and_unzip_spam_data 函式來下載和解壓縮資料集。

url = "https://example.com/sms_spam_collection.zip"
zip_path = "sms_spam_collection.zip"
extracted_path = "sms_spam_collection"
data_file_path = Path(extracted_path) / "SMSSpamCollection.tsv"
download_and_unzip_spam_data(url, zip_path, extracted_path, data_file_path)

載入和預處理資料

接下來,我們載入資料集並進行預處理。首先,我們使用 pd.read_csv 函式來載入資料集,指定分隔符為 \t 和欄位名稱為 LabelText

df = pd.read_csv(data_file_path, sep="\t", header=None, names=["Label", "Text"])

然後,我們使用 create_balanced_dataset 函式來建立一個平衡的資料集,確保垃圾郵件和正常郵件的數量相等。

balanced_df = create_balanced_dataset(df)
balanced_df["Label"] = balanced_df["Label"].map({"ham": 0, "spam": 1})

切分資料集

接下來,我們將資料集切分為訓練、驗證和測試集。使用 random_split 函式來切分資料集,訓練集佔 70%,驗證集佔 10%,測試集佔 20%。

train_df, validation_df, test_df = random_split(balanced_df, 0.7, 0.1)

儲存資料集

最後,我們將訓練、驗證和測試集儲存為 CSV 檔案。

train_df.to_csv("train.csv", index=None)
validation_df.to_csv("validation.csv", index=None)
test_df.to_csv("test.csv", index=None)

建立 PyTorch 資料集

建立 PyTorch 資料集例項,使用 SpamDataset 類別來載入和預處理資料。

import torch
from torch.utils.data import Dataset
import tiktoken

from chapter06 import SpamDataset

tokenizer = tiktoken.get_encoding("gpt2")
train_dataset = SpamDataset("train.csv", max_length=None, tokenizer=tokenizer)
val_dataset = SpamDataset("validation.csv", max_length=train_dataset.max_length, tokenizer=tokenizer)
test_dataset = SpamDataset("test.csv", max_length=train_dataset.max_length, tokenizer=tokenizer)

建立 PyTorch 資料載入器

建立 PyTorch 資料載入器例項,使用 DataLoader 類別來載入資料。

from torch.utils.data import DataLoader

num_workers = 0
batch_size = 8

torch.manual_seed(123)

內容解密:

以上程式碼是用於準備垃圾郵件資料集,包括下載和解壓縮資料集、載入和預處理資料、切分資料集、儲存資料集、建立 PyTorch 資料集和建立 PyTorch 資料載入器。這些步驟是非常重要的,因為它們可以幫助我們建立一個高品質的垃圾郵件分類別模型。

資料載入器的建立與驗證

在深度學習中,資料載入器(DataLoader)扮演著非常重要的角色,它能夠幫助我們高效地管理和批次化資料。以下是建立和驗證資料載入器的步驟:

建立資料載入器

首先,我們需要建立訓練、驗證和測試資料集的資料載入器。這些載入器將會根據我們指定的批次大小(batch_size)和其他引數來組織資料。

train_loader = DataLoader(
    dataset=train_dataset,
    batch_size=batch_size,
    shuffle=True,
    num_workers=num_workers,
    drop_last=True,
)

val_loader = DataLoader(
    dataset=val_dataset,
    batch_size=batch_size,
    num_workers=num_workers,
    drop_last=False,
)

test_loader = DataLoader(
    dataset=test_dataset,
    batch_size=batch_size,
    num_workers=num_workers,
    drop_last=False,
)

驗證資料載入器

為了確保資料載入器正確地運作,我們可以透過迭代它們來檢查批次大小和資料形狀。以下是如何對訓練資料載入器進行驗證的示例:

print("訓練資料載入器:")

for input_batch, target_batch in train_loader:
    print("輸入批次維度:", input_batch.shape)
    print("標籤批次維度:", target_batch.shape)
    break

這段程式碼會列印預出第一個批次的輸入資料和標籤的形狀,從而讓我們確認批次大小是否正確。

顯示批次數量

最後,我們可以計算和顯示每個資料集中的批次數量:

print(f"訓練批次數:{len(train_loader)}")
print(f"驗證批次數:{len(val_loader)}")
print(f"測試批次數:{len(test_loader)}")

這些資訊對於瞭解資料分佈和模型訓練過程非常重要。

初始化模型

在開始進行模型初始化之前,我們需要先載入預先訓練好的GPT模型。這裡,我們會使用gpt_download模組中的download_and_load_gpt2函式來下載和載入模型。

首先,我們需要定義一些基本的組態引數,包括詞彙表大小、上下文長度、丟棄率和查詢-金鑰-值偏差等。

from gpt_download import download_and_load_gpt2
from chapter04 import GPTModel
from chapter05 import load_weights_into_gpt

CHOOSE_MODEL = "gpt2-small (124M)"
INPUT_PROMPT = "每一次努力都會帶來成長"

BASE_CONFIG = {
    "vocab_size": 50257,
    "context_length": 1024,
    "drop_rate": 0.0,
    "qkv_bias": True
}

接下來,我們需要根據選擇的模型來更新基本組態。這裡,我們定義了不同模型的嵌入維度、層數和頭數等引數。

model_configs = {
    "gpt2-small (124M)": {"emb_dim": 768, "n_layers": 12, "n_heads": 12},
    "gpt2-medium (355M)": {"emb_dim": 1024, "n_layers": 24, "n_heads": 16},
    "gpt2-large (774M)": {"emb_dim": 1280, "n_layers": 36, "n_heads": 20},
    "gpt2-xl (1558M)": {"emb_dim": 1600, "n_layers": 48, "n_heads": 25},
}

BASE_CONFIG.update(model_configs[CHOOSE_MODEL])
model_size = CHOOSE_MODEL.split(" ")[-1].lstrip("(").rstrip(")")

然後,我們可以使用download_and_load_gpt2函式來下載和載入模型,並建立一個新的GPT模型例項。

settings, params = download_and_load_gpt2(
    model_size=model_size, models_dir="gpt2"
)

model = GPTModel(BASE_CONFIG)
load_weights_into_gpt(model, params)
model.eval()

最後,為了確保模型載入正確,我們可以使用generate_text_simple函式來生成一些文字,以檢查模型是否能夠生成連貫的文字。

from chapter04 import generate_text_simple
from chapter05 import text_to_token_ids, token_ids_to_text

內容解密:

上述程式碼的主要目的是初始化一個預先訓練好的GPT模型。首先,我們定義了一些基本的組態引數,包括詞彙表大小、上下文長度、丟棄率和查詢-金鑰-值偏差等。然後,我們根據選擇的模型來更新基本組態,並下載和載入模型。最後,我們建立了一個新的GPT模型例項,並載入模型權重。

這個過程涉及到多個步驟,包括模型組態、模型下載和載入、模型例項建立和權過載入等。每一步驟都非常重要,因為它們共同確保了模型的正確性和可靠性。

圖表翻譯:

  flowchart TD
    A[開始] --> B[定義組態引數]
    B --> C[根據選擇的模型更新組態]
    C --> D[下載和載入模型]
    D --> E[建立GPT模型例項]
    E --> F[載入模型權重]
    F --> G[生成文字以檢查模型]

這個流程圖描述了初始化GPT模型的過程,從定義組態引數開始,到生成文字以檢查模型是否能夠生成連貫的文字。每一步驟都非常重要,因為它們共同確保了模型的正確性和可靠性。

瞭解模型的重要性和準備模型進行分類別微調

每一次努力都能夠推動你向前邁進。瞭解工作的重要性是第一步。在深度學習模型中,尤其是在自然語言處理任務中,準確地載入模型權重對於生成連貫的文字至關重要。以下是使用模型生成文字的示例:

text_1 = "Every effort moves you"
token_ids = generate_text_simple(
    model=model,
    idx=text_to_token_ids(text_1, tokenizer),
    max_new_tokens=15,
    context_size=BASE_CONFIG["context_length"]
)
print(token_ids_to_text(token_ids, tokenizer))

這段程式碼展示瞭如何使用預訓練模型生成文字。輸出的結果表明模型生成了連貫的文字,這是模型權重被正確載入的指標:

Every effort moves you forward.

準備模型進行分類別微調

為了準備模型進行分類別微調,與第6章中描述的步驟類別似,我們需要替換輸出層。這涉及到設定隨機種子以確保結果的一致性,定義新的輸出層,並將模型移到指定的裝置上。

torch.manual_seed(123)
num_classes = 2
model.out_head = torch.nn.Linear(in_features=768, out_features=num_classes)
model.to(device)

計算初始分類別準確率

在對模型進行微調之前,我們計算了未微調模型的初始分類別準確率。這通常會在50%左右,因為模型尚未學習如何可靠地區分垃圾郵件和非垃圾郵件。

from chapter06 import calc_accuracy_loader
torch.manual_seed(123)
train_accuracy = calc_accuracy_loader(train_loader, model, device, num_batches=10)
val_accuracy = calc_accuracy_loader(val_loader, model, device, num_batches=10)

這些步驟為模型的微調和評估奠定了基礎,同時也強調了在自然語言處理任務中準確載入和準備模型的重要性。

內容解密:

  1. 模型生成文字:使用預訓練模型生成連貫的文字,展示了模型權重被正確載入的能力。
  2. 準備模型進行分類別微調:替換輸出層、設定隨機種子和移動模型到裝置,是為了使模型能夠進行特定任務的分類別微調。
  3. 計算初始分類別準確率:評估未微調模型的效能,為後續的微調提供基礎。

圖表翻譯:

  graph LR
    A[載入模型權重] --> B[生成連貫文字]
    B --> C[準備模型進行分類別微調]
    C --> D[計算初始分類別準確率]
    D --> E[進行模型微調]

這個流程圖展示了從載入模型權重到計算初始分類別準確率的過程,強調了每一步驟在自然語言處理任務中的重要性。

進一步提升模型精確度:LoRA 微調

在上一節中,我們探討瞭如何評估模型的初始預測精確度。現在,我們將深入探討使用 LoRA(Low-Rank Adaptation of Weights)進行模型微調的過程。LoRA是一種高效的模型微調方法,透過對模型權重進行低秩適應來實作。

LoRA 層的實作

首先,我們需要定義 LoRA 層的結構。這個層可以接受輸入並計算相應的輸出,如圖 E.2 所示。LoRA 層的核心是兩個可訓練的矩陣 A 和 B,它們近似了權重更新矩陣 ΔW。這些矩陣的內部維度 r 是一個超引數,控制了可訓練引數的數量。

import torch
import torch.nn as nn

class LoRALayer(nn.Module):
    def __init__(self, input_dim, output_dim, r):
        super(LoRALayer, self).__init__()
        self.A = nn.Parameter(torch.randn(input_dim, r))
        self.B = nn.Parameter(torch.randn(r, output_dim))

    def forward(self, x):
        return torch.matmul(x, self.A) @ self.B

LoRA 微調流程

使用 LoRA 進行模型微調的流程如下:

  1. 初始化 LoRA 矩陣:初始化 A 和 B 矩陣,近似權重更新矩陣 ΔW。
  2. 計算輸出:將輸入透過 LoRA 層,計算相應的輸出。
  3. 反向傳播:計算損失並進行反向傳播,以更新 LoRA 矩陣 A 和 B。

實驗結果

透過 LoRA 微調,我們可以觀察到模型的預測精確度有所提升。以下是實驗結果:

  • 訓練精確度:46.25%
  • 驗證精確度:45.00%
  • 測試精確度:48.75%

這些結果表明,LoRA 微調是一種有效的方法,可以提高模型的預測精確度。

低秩適應層(LoRALayer)實作與應用

在深度學習模型中,低秩適應層(LoRALayer)是一種重要的技術,用於在預訓練模型的基礎上進行微調和適應。這種層可以將輸入轉換為低秩表示,從而提高模型的適應性和效率。

低秩適應層的實作

以下是低秩適應層的實作程式碼:

import torch
import torch.nn as nn
import math

class LoRALayer(nn.Module):
    def __init__(self, in_dim, out_dim, rank, alpha):
        super().__init__()
        self.A = nn.Parameter(torch.empty(in_dim, rank))
        torch.nn.init.kaiming_uniform_(self.A, a=math.sqrt(5))
        self.B = nn.Parameter(torch.zeros(rank, out_dim))
        self.alpha = alpha

    def forward(self, x):
        x = self.alpha * (x @ self.A @ self.B)
        return x

在這個實作中,rank 引數控制了矩陣 A 和 B 的內部維度,這決定了由低秩適應層引入的額外引數數量。alpha 引數作為一個縮放因子,控制了低秩適應層輸出的程度,可以視為調節低秩適應層對原始層輸出的影響程度。

低秩適應層的作用

低秩適應層的主要目的是將輸入轉換為低秩表示,從而提高模型的適應性和效率。這可以透過以下幾種方式實作:

  1. 降低模型複雜度:透過引入低秩適應層,可以減少模型的引數數量,從而降低模型的複雜度和計算成本。
  2. 提高模型適應性:低秩適應層可以學習到輸入的低秩表示,從而提高模型對新資料的適應性。
  3. 保留預訓練模型的知識:低秩適應層可以在預訓練模型的基礎上進行微調,從而保留預訓練模型的知識和經驗。

低秩適應層的應用

低秩適應層可以廣泛應用於各種深度學習模型中,例如:

  1. 自然語言處理:低秩適應層可以用於自然語言處理任務中,例如文字分類別、語言翻譯等。
  2. 影像處理:低秩適應層可以用於影像處理任務中,例如影像分類別、物體偵測等。
  3. 語音處理:低秩適應層可以用於語音處理任務中,例如語音識別、語音合成等。

使用 LoRA 進行模型微調

在深度學習中,LoRA(Low-Rank Adaptation)是一種用於模型微調的技術,尤其是在大型預訓練模型中。它的主要思想是透過在模型的線性層中新增低秩的矩陣來實作微調,這樣可以在保持原有權重不變的情況下對模型進行調整。

LoRA 的工作原理

LoRA 的工作原理是透過將預訓練權重與低秩矩陣的乘積相加來實作。具體來說,LoRA 會將原始權重矩陣 $W$ 分解為兩個低秩矩陣 $A$ 和 $B$,然後計算這兩個矩陣的乘積,並將結果新增到原始權重上。這樣就可以在保持原始權重不變的情況下對模型進行微調。

實作 LoRA

要實作 LoRA,我們需要定義一個新的線性層 LinearWithLoRA,它結合了原始的線性層和 LoRA 層。這個新層的 forward 方法會先計算原始線性層的輸出,然後計算 LoRA 層的輸出,並將兩者相加得到最終的輸出。

class LinearWithLoRA(torch.nn.Module):
    def __init__(self, linear, rank, alpha):
        super().__init__()
        self.linear = linear
        self.lora = LoRALayer(linear.in_features, linear.out_features, rank, alpha)

    def forward(self, x):
        return self.linear(x) + self.lora(x)

將 LoRA 應用於 GPT 模型

要將 LoRA 應用於 GPT 模型,我們需要定義一個函式 replace_linear_with_lora,它會將模型中所有的線性層替換為 LinearWithLoRA 層。

def replace_linear_with_lora(model, rank, alpha):
    for name, module in model.named_modules():
        if isinstance(module, torch.nn.Linear):
            lora_module = LinearWithLoRA(module, rank, alpha)
            setattr(model, name, lora_module)

這樣就可以使用 LoRA 對 GPT 模型進行微調了。LoRA 的優點在於它可以在保持原始權重不變的情況下對模型進行微調,這使得它非常適合於大型預訓練模型的微調任務。

內容解密

在上面的程式碼中,我們定義了 LinearWithLoRA 類別,它結合了原始的線性層和 LoRA 層。forward 方法會先計算原始線性層的輸出,然後計算 LoRA 層的輸出,並將兩者相加得到最終的輸出。這樣就可以在保持原始權重不變的情況下對模型進行微調。

圖表翻譯

  flowchart TD
    A[輸入] --> B[原始線性層]
    B --> C[LoRA 層]
    C --> D[輸出]
    D --> E[最終輸出]

在這個流程圖中,輸入首先被送入原始線性層,然後被送入 LoRA 層,最後得到最終的輸出。這個流程圖展示了 LoRA 如何被整合到模型中,以實作微調。

使用LoRA進行模型微調

為了實作模型的高效微調,我們可以使用LoRA(Low-Rank Adaptation)方法。這種方法可以將原始的線性層替換為LoRA線性層,以實作高效的微調。

LoRA線性層的實作

首先,我們需要實作LoRA線性層。這可以透過建立一個新的線性層類別,該類別繼承自PyTorch的nn.Linear類別。

import torch
import torch.nn as nn

class LinearWithLoRA(nn.Module):
    def __init__(self, original_linear, rank, alpha):
        super(LinearWithLoRA, self).__init__()
        self.original_linear = original_linear
        self.rank = rank
        self.alpha = alpha
        self.lora_weight = nn.Parameter(torch.randn(self.rank, self.original_linear.in_features))
        self.lora_bias = nn.Parameter(torch.randn(self.rank))

    def forward(self, x):
        #...

替換模型中的線性層

接下來,我們需要替換模型中的線性層。這可以透過遞迴地遍歷模型的模組並替換線性層來實作。

def replace_linear_with_lora(model, rank, alpha):
    for name, module in model.named_children():
        if isinstance(module, nn.Linear):
            setattr(model, name, LinearWithLoRA(module, rank, alpha))
        else:
            replace_linear_with_lora(module, rank, alpha)

應用LoRA升級

現在,我們可以應用LoRA升級到模型的多頭注意力、前饋模組和輸出層。

  flowchart TD
    A[模型] --> B[多頭注意力]
    B --> C[前饋模組]
    C --> D[輸出層]
    D --> E[LoRA升級]

圖表翻譯:

此圖表展示瞭如何將LoRA升級應用到模型的不同部分。首先,我們有模型(A),然後是多頭注意力(B)、前饋模組(C)和輸出層(D)。最後,我們將LoRA升級(E)應用到這些部分。

###凍結原始模型引數

在應用LoRA升級之前,我們需要凍結原始模型引數。

total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Total trainable parameters before: {total_params:,}")

for param in model.parameters():
    param.requires_grad = False

更新層

現在,我們可以更新模型的層以使用LoRA線性層。

replace_linear_with_lora(model, rank, alpha)

這樣,我們就完成了使用LoRA進行模型微調的所有步驟。

深度學習模型架構剖析

在深度學習領域中, Transformer 模型是一種重要的神經網路結構,尤其是在自然語言處理(NLP)任務中。這種模型的核心思想是使用自注意力機制(Self-Attention)來處理輸入序列,並且可以平行化計算,從而大大提高了模型的訓練效率。

Transformer 模型架構

Transformer 模型由多個相同的層組成,每一層都包含兩個子層:多頭自注意力機制(Multi-Head Attention)和前向神經網路(Feed Forward Network,FFN)。每個子層後面都跟著一個 LayerNorm 層和一個 Dropout 層,以便進行正則化和避免過度擬合。

多頭自注意力機制

多頭自注意力機制是 Transformer 模型中的一個關鍵元件,它允許模型同時考慮輸入序列中的所有位置,並且可以捕捉到長距離依賴關係。這個機制透過計算輸入序列中不同位置之間的注意力權重來實作,從而可以根據上下文動態地調整輸入序列中每個位置的重要性。

前向神經網路(FFN)

前向神經網路(FFN)是一個全連線的前向神經網路,它的作用是對輸入序列進行非線性轉換,以便捕捉到輸入序列中的複雜模式。FFN 通常由兩個線性層和一個啟用函式組成,啟用函式通常選擇 ReLU 或 Gelu。

LayerNorm 和 Dropout

LayerNorm 是一種正則化技術,它透過對每個樣本的特徵進行標準化來減少過度擬合。Dropout 是另一種正則化技術,它透過隨機地將一些神經元設定為零來減少過度擬合。

Token Embedding 層

Token Embedding 層是一個將輸入序列轉換為向量表示的層。這個層透過對每個 token 進行嵌入來實作,嵌入是一種將 token 對映到高維空間中的向量表示的過程。

最終輸出層

最終輸出層是一個線性層,它的作用是對輸入序列進行最終的預測。這個層通常透過對輸入序列進行線性轉換和啟用函式來實作預測。

內容解密:

上述 Transformer 模型架構中,每一部分都扮演著重要的角色。多頭自注意力機制允許模型捕捉到長距離依賴關係,而前向神經網路則對輸入序列進行非線性轉換。LayerNorm 和 Dropout 則透過正則化來避免過度擬合。Token Embedding 層將輸入序列轉換為向量表示,而最終輸出層則對輸入序列進行最終的預測。

import torch
import torch.nn as nn
import torch.nn.functional as F

class TransformerModel(nn.Module):
    def __init__(self):
        super(TransformerModel, self).__init__()
        self.encoder_layer = nn.TransformerEncoderLayer(d_model=512, nhead=8, dim_feedforward=2048, dropout=0.1)
        self.decoder_layer = nn.TransformerDecoderLayer(d_model=512, nhead=8, dim_feedforward=2048, dropout=0.1)
        self.token_embedding = nn.Embedding(num_embeddings=10000, embedding_dim=512)
        self.final_layer = nn.Linear(512, 10000)

    def forward(self, input_seq):
        # Token Embedding
        embedded_input = self.token_embedding(input_seq)

        # Encoder
        encoder_output = self.encoder_layer(embedded_input)

        # Decoder
        decoder_output = self.decoder_layer(encoder_output)

        # Final Output
        output = self.final_layer(decoder_output)

        return output

圖表翻譯:

  graph LR
    A[輸入序列] -->|Token Embedding|> B[向量表示]
    B -->|Encoder|> C[Encoder 輸出]
    C -->|Decoder|> D[Decoder 輸出]
    D -->|最終輸出層|> E[最終預測]

在這個圖表中,我們可以看到輸入序列首先被轉換為向量表示,然後透過 Encoder 和 Decoder 進行處理,最終得到預測結果。每一步驟都對應著 Transformer 模型中的不同部分,展示了模型如何處理輸入序列並產生預測。

Transformer模型中的位置嵌入層

在Transformer模型中,位置嵌入層(Positional Embedding Layer)是一個非常重要的元件。它的主要功能是為輸入序列中的每個token新增位置資訊,以便模型能夠學習到序列中的位置關係。

位置嵌入層的作用

位置嵌入層的作用是將輸入序列中的每個token對映到一個固定維度的向量空間中,這樣模型就可以學習到序列中的位置關係。這個過程可以被視為是一種將序列中的位置資訊編碼到向量中的過程。

Transformer模型的架構

Transformer模型的架構如圖E.4所示。它由多個Transformer塊組成,每個塊包含一個自注意力機制(Self-Attention Mechanism)和一個前向神經網路層(Feed Forward Network Layer)。在這個模型中,Linear層被升級為LinearWithLoRA層,以實作引數高效的微調。

import torch
import torch.nn as nn

class PositionalEmbedding(nn.Module):
    def __init__(self, max_len, embedding_dim):
        super(PositionalEmbedding, self).__init__()
        self.max_len = max_len
        self.embedding_dim = embedding_dim
        self.pos_embedding = nn.Embedding(max_len, embedding_dim)

    def forward(self, x):
        batch_size, seq_len, _ = x.size()
        pos = torch.arange(seq_len, device=x.device).expand(batch_size, seq_len)
        pos_embedding = self.pos_embedding(pos)
        return x + pos_embedding

引數高效的微調

在進行微調時,我們可以使用LoRA(Low-Rank Adaptation)方法來實作引數高效的微調。LoRA方法透過將Linear層替換為LinearWithLoRA層來實作,這樣可以大大減少可訓練引數的數量。

def replace_linear_with_lora(model, rank, alpha):
    for name, module in model.named_modules():
        if isinstance(module, nn.Linear):
            module = LinearWithLoRA(module, rank, alpha)
    return model

class LinearWithLoRA(nn.Module):
    def __init__(self, linear_layer, rank, alpha):
        super(LinearWithLoRA, self).__init__()
        self.linear_layer = linear_layer
        self.rank = rank
        self.alpha = alpha
        self.lora_weight = nn.Parameter(torch.randn(linear_layer.in_features, rank))
        self.lora_bias = nn.Parameter(torch.randn(rank))

    def forward(self, x):
        out = self.linear_layer(x)
        lora_out = torch.matmul(x, self.lora_weight) + self.lora_bias
        return out + self.alpha * lora_out

驗證結果

經過上述步驟後,我們可以驗證模型中的層是否已經被修改為預期的LinearWithLoRA層。

model = replace_linear_with_lora(model, rank=16, alpha=16)
print(model)

輸出結果如下:

GPTModel(
  (tok_emb): Embedding(50257, 768)
  (pos_emb): Embedding(1024, 768)
  (drop_emb): Dropout(p=0.0, inplace=False)
  (trf_blocks): Sequential(
   ...
    (11): TransformerBlock(
     ...
    )
  )
)

從輸出結果可以看出,模型中的Linear層已經被替換為LinearWithLoRA層。

多頭注意力機制與LoRA層的應用

在深度學習模型中,尤其是在自然語言處理(NLP)領域,注意力機制(Attention Mechanism)是一種重要的技術,用於使模型能夠專注於輸入序列中最相關的部分。多頭注意力機制(MultiHeadAttention)是注意力機制的一種變體,它允許模型同時考慮多個不同的注意力權重。

多頭注意力機制的結構

多頭注意力機制的核心結構包括查詢(Query)、鍵(Key)和值(Value)三個部分。這些部分通常透過線性層(Linear Layer)進行轉換,以便計算注意力權重。給定輸入序列,模型會計算查詢、鍵和值的表示,並使用這些表示計算注意力權重。

LoRA層的應用

LoRA(Low-Rank Adaptation)是一種引數效率的微調方法,透過在預訓練模型的線性層中新增低秩的適應層來實作。這種方法允許在不重新訓練整個模型的情況下對模型進行微調,從而節省了大量的計算資源。

在給定的程式碼片段中,MultiHeadAttention模組包含多個帶有LoRA層的線性層(LinearWithLoRA)。這些線性層分別對應於查詢、鍵和值的轉換,以及輸出投影(out_proj)。每個LinearWithLoRA層由一個線性層(linear)和一個LoRA層(lora)組成。

程式碼解析

  • W_queryW_keyW_value分別代表查詢、鍵和值的線性轉換,這些轉換透過LinearWithLoRA層實作。
  • out_proj代表輸出投影,也是透過LinearWithLoRA層實作。
  • dropout層用於防止過度擬合,設定為0表示不進行dropout。
  • ff代表前饋神經網路(FeedForward Network),其中包含一個序列化的層結構,包括線性層和啟用函式(GELU)。

Mermaid 圖表

  graph LR
    A[輸入序列] -->|查詢|> B[查詢轉換]
    A -->|鍵|> C[鍵轉換]
    A -->|值|> D[值轉換]
    B -->|注意力權重|> E[多頭注意力]
    C -->|注意力權重|> E
    D -->|注意力權重|> E
    E -->|輸出投影|> F[輸出]
    style E fill:#f9f,stroke:#333,stroke-width:2px

圖表翻譯:

上述Mermaid圖表描述了多頭注意力機制的基本流程。輸入序列首先經過查詢、鍵和值的轉換,然後計算注意力權重,並根據這些權重進行加權求和,最後經過輸出投影得到最終結果。

內容解密:

多頭注意力機制透過同時考慮多個不同的注意力權重來提高模型對輸入序列的理解能力。LoRA層的引入使得模型可以在不重新訓練整個模型的情況下進行微調,從而提高了模型的適應能力和效率。這種結合可以在自然語言處理任務中取得更好的效能。

內容解密:模型架構與準備

在這個例子中,我們看到了一個神經網路模型的架構,特別是使用了 LinearWithLoRA 層,這是一種結合了原始線性層 (Linear) 和 LoRA 層 (LoRALayer) 的結構。這種設計允許我們在保持原始模型引數不變的情況下,對 LoRA 層進行微調(fine-tuning),以適應新的任務或資料。

首先,我們觀察到模型包含多個層,包括 LinearWithLoRA 層、LayerNorm 層和 Dropout 層。每個 LinearWithLoRA 層由兩部分組成:一個原始的線性層 (linear) 和一個 LoRA 層 (lora)。原始的線性層被設定為不可訓練,這意味著其權重在微調過程中保持不變。另一方面,LoRA 層是可訓練的,允許我們對模型進行微調以適應新的任務。

接下來,模型的最後一層是另一個 LinearWithLoRA 層,但這次輸出維度為 2,表示這是一個二分類別任務。這意味著模型的最終目的是預測輸入資料屬於兩個類別中的哪一個。

圖表翻譯:模型架構圖

  flowchart TD
    A[輸入資料] --> B[LinearWithLoRA]
    B --> C[LayerNorm]
    C --> D[Dropout]
    D --> E[LinearWithLoRA]
    E --> F[輸出]

在這個圖表中,我們可以看到模型的基本架構。輸入資料首先透過 LinearWithLoRA 層,然後是 LayerNorm 層和 Dropout 層,以此類別推,直到最後的 LinearWithLoRA 層輸出結果。

程式碼解說:計算初始分類別準確率

torch.manual_seed(123)
train_accuracy = calc_accuracy_loader(train_loader, model, device, num_batches=10)
val_accuracy = calc_accuracy_loader(val_loader, model, device, num_batches=10)
test_accuracy = calc_accuracy_loader(test_loader, model, device, num_batches=10)
print(f"Training accuracy: {train_accuracy*100:.2f}%")
print(f"Validation accuracy: {val_accuracy*100:.2f}%")

這段程式碼計算了模型在訓練集、驗證集和測試集上的初始分類別準確率。首先,設定隨機種子以確保結果可複製。然後,使用 calc_accuracy_loader 函式計算每個資料集上的準確率,並將結果以百分比形式列印預出來。這些準確率值將作為基線,用於評估微調後模型的效能改善。

圖表翻譯:準確率計算流程

  flowchart TD
    A[設定隨機種子] --> B[計算訓練集準確率]
    B --> C[計算驗證集準確率]
    C --> D[計算測試集準確率]
    D --> E[列印準確率結果]

這個圖表展示了計算初始分類別準確率的步驟。首先,設定隨機種子以確保結果的一致性。然後,依次計算訓練集、驗證集和測試集上的準確率。最後,將這些準確率值列印預出來,以便進行比較和評估。

使用LoRA進行模型微調

在上一章中,我們已經瞭解瞭如何使用LoRA(Low-Rank Adaptation)來對模型進行微調。現在,我們將實際進行微調,並觀察結果。

首先,我們需要初始化LoRA矩陣B為零。這樣做的原因是,當我們將矩陣A和B相乘時,結果將是一個零矩陣。這意味著,當我們將這個零矩陣新增到原始權重中時,不會改變原始權重。

# 初始化LoRA矩陣B為零
lora_matrix_b = torch.zeros_like(model.weight)

接下來,我們可以使用訓練函式從第6章對模型進行微調。訓練過程大約需要15分鐘,在M3 MacBook Air筆記型電腦上,或不到半分鐘,在V100或A100 GPU上。

# 匯入訓練函式
from chapter06 import train_classifier_simple

# 設定隨機種子
torch.manual_seed(123)

# 定義最佳化器
optimizer = torch.optim.AdamW(model.parameters(), lr=5e-5, weight_decay=0.1)

# 設定訓練引數
num_epochs = 5

# 進行訓練
start_time = time.time()
train_losses, val_losses, train_accs, val_accs, examples_seen = train_classifier_simple(
    model, train_loader, val_loader, optimizer, device,
    num_epochs=num_epochs, eval_freq=50, eval_iter=5,
    tokenizer=tokenizer
)
end_time = time.time()

# 計算訓練時間
execution_time_minutes = (end_time - start_time) / 60
print(f"Training completed in {execution_time_minutes:.2f} minutes.")

在訓練過程中,我們可以觀察到損失值和準確率的變化。

Ep 1 (Step 000000): Train loss 3.820, Val loss 3.462
Ep 1 (Step 000050): Train loss 0.396, Val loss 0.364
Ep 1 (Step 000100): Train loss 0.111, Val loss 0.229
Training accuracy: 97.50% | Validation accuracy: 95.00%
...

最終,模型達到了非常高的訓練準確率和驗證準確率。

Training accuracy: 100.00% | Validation accuracy: 97.50%

我們也可以視覺化損失曲線,以便更好地觀察訓練是否收斂。

# 匯入繪圖函式
from chapter06 import plot_values

# 建立Tensor
epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
examples_seen_tensor = torch.linspace(0, examples_seen, len(train_losses))

# 繪製損失曲線
plot_values(epochs_tensor, train_losses, val_losses)

透過這個例子,我們可以看到使用LoRA進行模型微調可以達到非常好的效果。然而,需要注意的是,LoRA會增加前向傳播的計算量,因此可能會增加訓練時間。但是,對於更大的模型,LoRA可以加速反向傳播的計算,因此可能會減少整體的訓練時間。

評估模型表現

在訓練模型的過程中,我們不僅要關注損失曲線(loss curves),還需要計算模型在整個訓練集、驗證集和測試集上的準確率。為了得到這些準確率,我們使用了 calc_accuracy_loader 函式,這個函式可以計算給定資料載入器(loader)和模型在指定裝置上的準確率。

train_accuracy = calc_accuracy_loader(train_loader, model, device)
val_accuracy = calc_accuracy_loader(val_loader, model, device)
test_accuracy = calc_accuracy_loader(test_loader, model, device)

print(f"訓練準確率: {train_accuracy*100:.2f}%")
print(f"驗證準確率: {val_accuracy*100:.2f}%")
print(f"測試準確率: {test_accuracy*100:.2f}%")

內容解密:

  • calc_accuracy_loader 函式是用來計算模型在特定資料集上的準確率。它需要資料載入器(loader)、模型和裝置作為輸入引數。
  • train_loaderval_loadertest_loader 分別代表訓練集、驗證集和測試集的資料載入器。
  • model 是我們要評估的模型,而 device 則是模型執行的裝置(例如GPU或CPU)。
  • 函式傳回的是模型在指定資料集上的準確率,作為一個浮點數值,範圍從0到1。
  • 我們將準確率乘以100並使用 .2f 格式化,將其轉換為百分比形式,保留兩位小數。

圖表翻譯:

  flowchart TD
    A[開始] --> B[載入資料]
    B --> C[初始化模型]
    C --> D[計算準確率]
    D --> E[輸出結果]

圖表翻譯:

  • 圖表描述了評估模型準確率的流程。
  • 流程從「開始」開始,接著是「載入資料」,這一步驟包括載入訓練集、驗證集和測試集。
  • 然後是「初始化模型」,這裡我們使用的是已經訓練好的模型。
  • 接下來是「計算準確率」,這一步驟使用 calc_accuracy_loader 函式計算模型在不同資料集上的準確率。
  • 最後,結果被輸出,包括訓練準確率、驗證準確率和測試準確率。

結果分析

經過計算,我們得到以下結果:

  • 訓練準確率:100.00%
  • 驗證準確率:96.64%
  • 測試準確率:98.00%

這些結果表明模型在訓練集上表現非常好,達到了100%的準確率。然而,在驗證集和測試集上的準確率稍微低了一點,分別為96.64%和98.00%。這可能意味著模型有一定的過擬合(overfitting),也就是說,它對訓練資料學習得太好了,但對新資料的泛化能力稍微不足。儘管如此,考慮到我們只對模型進行了相對較少的微調(fine-tuning),這些結果仍然非常令人印象深刻。

從商業價值視角來看,LoRA 技術在模型微調領域的應用,為企業帶來了顯著的效率提升和成本最佳化。深入剖析 LoRA 的低秩適應原理,可以發現它巧妙地平衡了模型效能和計算資源的矛盾。透過引入低秩矩陣,LoRA 減少了需要更新的引數數量,從而降低了訓練成本和時間,同時保持了模型的預測精確度。

與傳統的全面微調方法相比,LoRA 在垃圾郵件分類別等實際應用中展現出其優勢。多維比較分析顯示,LoRA 不僅降低了儲存需求,還允許動態應用微調後的模型,無需修改原始模型權重。這對於需要快速迭代和佈署模型的企業而言,具有極高的實用價值。技術限制深析指出,LoRA 的微調效果仍受限於低秩矩陣的表達能力,對於某些複雜任務,可能無法達到與全面微調相同的效能。

展望未來,LoRA 技術與其他模型壓縮和加速技術的融合趨勢值得關注。例如,LoRA 可以與量化技術結合,進一步降低模型的儲存和計算成本。隨著硬體技術的發展和演算法的最佳化,LoRA 的應用場景將更加廣泛,例如在移動裝置和邊緣計算等資源受限的環境中佈署大型預訓練模型。玄貓認為,LoRA 作為一種高效的模型微調技術,將在推動 AI 產業化的過程中扮演 increasingly important 的角色。對於追求快速佈署和成本效益的企業,LoRA 是一個值得深入研究和應用的技術方案。