《Python機(jī)器學(xué)習(xí)》作者科普長(zhǎng)文:從頭構(gòu)建類GPT文本分類器,代碼開(kāi)源
近日,機(jī)器學(xué)習(xí)研究員、暢銷書《Python 機(jī)器學(xué)習(xí)》作者 Sebastian Raschka 又分享了一篇長(zhǎng)文,主題為《從頭開(kāi)始構(gòu)建一個(gè) GPT 風(fēng)格的 LLM 分類器》。
文章展示了如何將預(yù)訓(xùn)練的大型語(yǔ)言模型(LLM)轉(zhuǎn)化為強(qiáng)大的文本分類器。機(jī)器之心對(duì)文章內(nèi)容進(jìn)行了不改變?cè)獾木幾g、整理:
為什么要關(guān)注分類呢?首先,針對(duì)分類任務(wù),對(duì)預(yù)訓(xùn)練模型進(jìn)行微調(diào)是一個(gè)簡(jiǎn)單有效的 LLM 知識(shí)入門方式。其次,文本分類有許多商業(yè)應(yīng)用場(chǎng)景,比如:垃圾郵件檢測(cè)、情感分析、客戶反饋分類、主題分類等等。
閱讀完本文,你將找到以下 7 個(gè)問(wèn)題的答案:
1. 需要訓(xùn)練所有層嗎?
2. 為什么微調(diào)最后一個(gè) token,而不是第一個(gè) token?
3. BERT 與 GPT 在性能上有何比較?
4. 應(yīng)該禁用因果掩碼嗎?
5. 擴(kuò)大模型規(guī)模會(huì)有什么影響?
6. LoRA 可以帶來(lái)什么改進(jìn)?
7. Padding 還是不 Padding?
完整代碼可以從 GitHub 找到:https://github.com/rasbt/LLMs-from-scratch/blob/main/ch06/01_main-chapter-code/ch06.ipynb
Different categories of finetuning
微調(diào)的不同種類
指令微調(diào)和分類微調(diào)是最常見(jiàn)的語(yǔ)言模型微調(diào)方法。指令微調(diào)是用特定任務(wù)訓(xùn)練模型,提高它理解和執(zhí)行自然語(yǔ)言提示中所描述任務(wù)的能力,如下圖 1 所示。
圖 1:指令微調(diào)的兩種場(chǎng)景。上方:模型的任務(wù)是判斷文本是否為垃圾郵件;下方:模型的任務(wù)是將英文句子翻譯成德語(yǔ)。
在分類微調(diào)中,模型被訓(xùn)練用于識(shí)別特定的類別標(biāo)簽,比如「垃圾郵件」和「非垃圾郵件」。分類任務(wù)還包括從圖像中識(shí)別不同的植物、給新聞按體育、政治或科技等主題分類,從醫(yī)學(xué)影像中區(qū)分良性和惡性腫瘤等等。
不過(guò)經(jīng)過(guò)分類微調(diào)的模型只能判斷類別,不能對(duì)輸入的文本作出其他判斷。
圖 2:一個(gè)使用 LLM 進(jìn)行垃圾郵件分類的示例。針對(duì)垃圾郵件分類微調(diào)的模型在輸入時(shí)不需要額外的指令,然而,與指令微調(diào)模型相比,它的回答只能是「垃圾郵件」和「非垃圾郵件」。
指令微調(diào)的模型通常能夠執(zhí)行更廣泛的任務(wù)。我們可以將分類微調(diào)的模型視為是高度專業(yè)化的模型,一般來(lái)說(shuō),開(kāi)發(fā)一個(gè)專用模型比開(kāi)發(fā)一個(gè)在各種任務(wù)上表現(xiàn)良好的通用模型更容易。
使用預(yù)訓(xùn)練權(quán)重初始化模型
下圖中展示了將通用預(yù)訓(xùn)練 LLM 轉(zhuǎn)變?yōu)閷iT用于分類任務(wù)的 LLM 需要做的修改:
圖 3:在此跳過(guò)步驟 1-5,直接進(jìn)入步驟 6(將在下一節(jié)開(kāi)始)。
在做修改之前,讓我們先簡(jiǎn)單了解一下正在使用的預(yù)訓(xùn)練 LLM。為簡(jiǎn)便起見(jiàn),假設(shè)我們?cè)O(shè)置了如下代碼來(lái)加載該模型:
model = GPTModel (BASE_CONFIG)
load_weights_into_gpt (model, params)
model.eval ()
在將模型權(quán)重加載到 GPT 后,使用下列文本生成的函數(shù)庫(kù),確保模型生成連貫的文本:
from chapter04 import generate_text_simple
from chapter05 import text_to_token_ids, token_ids_to_text
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))
根據(jù)以下輸出,我們可以看到模型生成了連貫的文本,這表明模型權(quán)重已正確加載:
Every effort moves you forward.
The first step is to understand the importance of your work
讓我們先看看模型是否可以通過(guò)指令微調(diào)完成垃圾郵件的分類:
text_2 = (
"Is the following text'spam'? Answer with 'yes' or 'no':"
"'You are a winner you have been specially"
"selected to receive $1000 cash or a $2000 award.'"
)
token_ids = generate_text_simple (
model=model,
idx=text_to_token_ids (text_2, tokenizer),
max_new_tokens=23,
context_size=BASE_CONFIG ["context_length"]
)
print (token_ids_to_text (token_ids, tokenizer))
模型的輸出如下所示:
Is the following text'spam'? Answer with 'yes' or 'no': 'You are a winner you have been specially selected to receive $1000 cash or a $2000 award.'
The following text'spam'? Answer with 'yes' or 'no': 'You are a winner
可以明顯看出模型在準(zhǔn)確遵循指令方面遇到了一些挑戰(zhàn)。這是可以預(yù)見(jiàn)的,因?yàn)樗鼉H經(jīng)過(guò)了預(yù)訓(xùn)練,缺乏指令微調(diào)。
加入分類頭
我們將原始輸出層(這層的功能是將模型內(nèi)部生成的隱藏表示轉(zhuǎn)換為一個(gè)包含 50,257 個(gè) tokens 的詞表)替換為一個(gè)較小的輸出層,該層映射到兩個(gè)類別:0(非垃圾郵件)和 1(垃圾郵件),如下圖 4 所示。
圖 4:此圖展示了如何通過(guò)改變架構(gòu)將 GPT 模型適配為垃圾郵件分類。最初,模型的線性輸出層將 768 個(gè)隱藏單元映射到一個(gè)包含 50,257 個(gè) tokens 的詞匯表。為了進(jìn)行垃圾郵件檢測(cè),這一層被替換為一個(gè)新的輸出層,該層將相同的 768 個(gè)隱藏單元映射到兩個(gè)類別,分別表示「垃圾郵件」和「非垃圾郵件」。
輸出層節(jié)點(diǎn)
從技術(shù)上講,因?yàn)檫@是一個(gè)二元分類任務(wù),可以只用一個(gè)輸出節(jié)點(diǎn)。然而,這將需要修改損失函數(shù)。因此,我們選擇一種更通用的方法,匹配輸出節(jié)點(diǎn)與分類的數(shù)量。例如,對(duì)于一個(gè)分三類的問(wèn)題,如將新聞文章分類為「科技」、「體育」或「政治」,使用三個(gè)輸出節(jié)點(diǎn),依此類推。
在嘗試進(jìn)行圖 4 中所示的修改之前,先通過(guò) print (model) 輸出模型架構(gòu):
GPTModel (
(tok_emb): Embedding (50257, 768)
(pos_emb): Embedding (1024, 768)
(drop_emb): Dropout (p=0.0, inplace=False)
(trf_blocks): Sequential (
...
(11): TransformerBlock (
(att): MultiHeadAttention (
(W_query): Linear (in_features=768, out_features=768, bias=True)
(W_key): Linear (in_features=768, out_features=768, bias=True)
(W_value): Linear (in_features=768, out_features=768, bias=True)
(out_proj): Linear (in_features=768, out_features=768, bias=True)
(dropout): Dropout (p=0.0, inplace=False)
)
(ff): FeedForward (
(layers): Sequential (
(0): Linear (in_features=768, out_features=3072, bias=True)
(1): GELU ()
(2): Linear (in_features=3072, out_features=768, bias=True)
)
)
(norm1): LayerNorm ()
(norm2): LayerNorm ()
(drop_resid): Dropout (p=0.0, inplace=False)
)
)
(final_norm): LayerNorm ()
(out_head): Linear (in_features=768, out_features=50257, bias=False)
)
如上所示,GPTModel 由嵌入層和 12 個(gè)相同的 transformer 塊組成,為簡(jiǎn)潔起見(jiàn),僅顯示最后一個(gè)塊,然后是最終的 LayerNorm 和輸出層 out_head。
接下來(lái),我們將 out_head 替換為一個(gè)新的輸出層,如圖 4 所示,我們將對(duì)這一層進(jìn)行微調(diào)。
選擇微調(diào)特定層與微調(diào)所有層
我們不必對(duì)模型每一層進(jìn)行微調(diào),因?yàn)樯窠?jīng)網(wǎng)絡(luò)的較低層捕捉到的基本的語(yǔ)言結(jié)構(gòu)和語(yǔ)義是通用的,可以在許多不同的任務(wù)和數(shù)據(jù)集中發(fā)揮作用。
因此,我們僅微調(diào)最后幾層(靠近輸出的層)就夠了,這些層更具體于細(xì)微的語(yǔ)言模式和任務(wù)特征。這種方法在計(jì)算上也將更加高效。
為了準(zhǔn)備進(jìn)行分類微調(diào),首先我們凍結(jié)模型,即將所有層設(shè)置為不可訓(xùn)練:
for param in model.parameters ():
param.requires_grad = False
然后,如圖 4 所示,我們修改輸出層 model.out_head :
torch.manual_seed (123)
num_classes = 2
model.out_head = torch.nn.Linear (
in_features=BASE_CONFIG ["emb_dim"],
out_features=num_classes
)
注意,在上述代碼中,我們使用了 BASE_CONFIG ["emb_dim"],它的值在 “gpt2-small(124M)” 模型中為 768。這樣做的目的是為了讓后續(xù)的代碼更加通用,相同的代碼也能處理其他型號(hào)的 GPT-2 模型。
新的 model.out_head 輸出層的 requires_grad 屬性默認(rèn)設(shè)置為 True,這意味著這是模型中唯一會(huì)在訓(xùn)練期間更新的層。
從技術(shù)上講,只訓(xùn)練剛剛添加的輸出層就足夠了。然而,我在實(shí)驗(yàn)中發(fā)現(xiàn),微調(diào)額外的層,可以顯著提高微調(diào)模型的預(yù)測(cè)性能。
此外,我們將最后一個(gè) transformer 塊以及連接該塊與輸出層的 LayerNorm 模塊設(shè)置為可訓(xùn)練,如圖 5 所示。
圖 5:用我的步驟開(kāi)發(fā)的 GPT 模型包含 12 個(gè)重復(fù)的 transformer 塊。除了輸出層,我們將最后的 LayerNorm 和最后一個(gè) transformer 塊設(shè)置為可訓(xùn)練,而其余 11 個(gè) transformer 塊和嵌入層保持為不可訓(xùn)練。
為了做到這點(diǎn),我們將它們各自的 requires_grad 設(shè)置為 True:
for param in model.trf_blocks [-1].parameters ():
param.requires_grad = True
for param in model.final_norm.parameters ():
param.requires_grad = True
盡管我們添加了一個(gè)新的輸出層,并將某些層設(shè)置為不可訓(xùn)練,我們?nèi)匀豢梢允褂眠@個(gè)模型。例如,我們可以像之前那樣輸入一段示例文本:
inputs = tokenizer.encode ("Do you have time")
inputs = torch.tensor (inputs).unsqueeze (0)
print ("Inputs:", inputs)
print ("Inputs dimensions:", inputs.shape)
如輸出所示,上述代碼將輸入編碼為一個(gè)包含 4 個(gè)輸入 tokens 的張量:
Inputs: tensor ([[5211, 345, 423, 640]])
Inputs dimensions: torch.Size ([1, 4])
然后,我們將編碼后的 token IDs 輸入模型:
with torch.no_grad ():
outputs = model (inputs)
print ("Outputs:\n", outputs)
print ("Outputs dimensions:", outputs.shape)
輸出張量如下所示:
Outputs:
tensor ([[[-1.5854, 0.9904],
[-3.7235, 7.4548],
[-2.2661, 6.6049],
[-3.5983, 3.9902]]])
Outputs dimensions: torch.Size ([1, 4, 2])
模型將輸出一個(gè) [1, 4, 50257] 的輸出張量,其中 50,257 代表詞匯表的大小。輸出行數(shù)對(duì)應(yīng)于輸入標(biāo)記的數(shù)量(在本例中是 4)。每個(gè)輸出的嵌入維度(列數(shù))現(xiàn)在減少到 2,而不是 50,257,因?yàn)槲覀兲鎿Q了模型的輸出層。
由于我們的主要目標(biāo)是微調(diào)出更擅長(zhǎng)對(duì)垃圾郵件進(jìn)行分類的模型。為了實(shí)現(xiàn)這一點(diǎn),我們不需要對(duì)所有行進(jìn)行微調(diào),可以專注于一個(gè)單一的輸出 token。具體來(lái)說(shuō),我們將專注于最后一行,對(duì)應(yīng)的最后一個(gè)輸出 token,如圖 6 所示。
圖 6: 本圖展示了 GPT 模型處理一個(gè)包含 4 個(gè) token 的輸入示例,并生成相應(yīng)輸出的詳細(xì)過(guò)程。模型的輸出層經(jīng)過(guò)調(diào)整,輸出張量?jī)H包含 2 列,為了完成分類微調(diào),我們專注于輸出的最后一行,對(duì)應(yīng)的最后一個(gè) token。
可以使用以下代碼從輸出張量中提取最后一個(gè)輸出 token:
print ("Last output token:", outputs [:, -1, :])
Print 出來(lái)結(jié)果如下:
Last output token: tensor([[-3.5983, 3.9902]])
那么,我們?yōu)槭裁匆x擇最后一個(gè) token,而不是其他位置上的 token 呢?
注意力機(jī)制建立了每個(gè)輸入 token 與其他 token 之間的關(guān)系,為了讓「注意力」集中,需要用到因果注意力掩碼。它的原理是限制每個(gè) token 只關(guān)注自己和前面的 token,如下圖 7 所示:
圖 7:因果注意力機(jī)制,矩陣顯示了每個(gè)輸入 token 之間的注意力得分??瞻讍卧癖硎颈谎诖a屏蔽的位置,防止 token 關(guān)注后來(lái)的 token。最后一個(gè) token「time」是唯一需要為所有之前的 token 計(jì)算注意力得分的 token。
如圖所示,序列中的最后一個(gè) token 積累了最多的信息,因此,在微調(diào)過(guò)程中,我們重點(diǎn)關(guān)注這個(gè)最后的 token。
如何將最后一個(gè) token 轉(zhuǎn)換為分類標(biāo)簽預(yù)測(cè),并計(jì)算模型的初始預(yù)測(cè)準(zhǔn)確率。接下來(lái),我們將在后續(xù)部分微調(diào)模型以完成垃圾郵件分類任務(wù)。
評(píng)估模型性能
由于這部分內(nèi)容已經(jīng)很長(zhǎng),我就不詳細(xì)討論模型評(píng)估的細(xì)節(jié)了。不過(guò),我想至少分享一張圖,展示訓(xùn)練過(guò)程中,模型訓(xùn)練集和驗(yàn)證集的分類準(zhǔn)確率,以展示模型確實(shí)學(xué)得很好。
圖 8:訓(xùn)練準(zhǔn)確率(實(shí)線)和驗(yàn)證準(zhǔn)確率(虛線)在早期的訓(xùn)練周期中大幅上升,然后趨于平穩(wěn),達(dá)到了幾乎完美的準(zhǔn)確率 1.0,對(duì)應(yīng) 100%。兩條線在整個(gè)訓(xùn)練過(guò)程中相距較近,表明模型對(duì)訓(xùn)練數(shù)據(jù)并沒(méi)有過(guò)度擬合。
模型的驗(yàn)證準(zhǔn)確率約為 97%。測(cè)試準(zhǔn)確率約為 96%。此外,我們可以看到模型略微有一點(diǎn)點(diǎn)過(guò)擬合,因?yàn)橛?xùn)練集的準(zhǔn)確率稍高。
從補(bǔ)充實(shí)驗(yàn)得出的洞見(jiàn)
到這里,你可能對(duì)某些設(shè)計(jì)選擇有很多疑問(wèn),所以我進(jìn)行了一些補(bǔ)充實(shí)驗(yàn)并把結(jié)果分享了出來(lái)。重新運(yùn)行這些實(shí)驗(yàn)的代碼已經(jīng)放在了以下 GitHub 項(xiàng)目中。
GitHub 地址:https://github.com/rasbt/LLMs-from-scratch/tree/main/ch06/02_bonus_additional-experiments
需要訓(xùn)練所有層嗎?
出于效率原因,我們僅訓(xùn)練輸出層和最后一個(gè) transformer 塊。如前所述,對(duì)于分類微調(diào),無(wú)需更新 LLM 中的所有層。我們更新的權(quán)重越少,訓(xùn)練速度就越快,因?yàn)槲覀儾恍枰诜聪騻鞑テ陂g計(jì)算權(quán)重的梯度。
但是,你可能想知道如果不更新所有層,我們會(huì)留下多少預(yù)測(cè)性能。因此,在下表中,我對(duì)所有層、僅最后一個(gè) transformer 塊(包括最后一層)、僅最后一層進(jìn)行了微調(diào)。
表 1:訓(xùn)練所有層 vs 僅訓(xùn)練最后一個(gè) Transformer 塊(包括最后一層)vs 僅訓(xùn)練最后一層
如上表 1 所示,訓(xùn)練所有層的性能稍好一些:96.67% vs 95.00%。不過(guò),這使運(yùn)行時(shí)間增加了約 2.5 倍。
為什么要微調(diào)最后一個(gè) token,而不是第一個(gè) token?
如果你熟悉 BERT(Devlin et al. 2018)等編碼器式語(yǔ)言模型,你可能知道這些模型有一個(gè)指定的分類 token 作為其第一個(gè) token,如下圖所示:
圖來(lái)自 BERT 原始論文:https://arxiv.org/abs/1810.04805
與 BERT 相比,GPT 是一種具有因果注意力掩碼的解碼器式模型(如圖 7 所示)。這意味著第一個(gè) token 沒(méi)有輸入中任何其他 token 的上下文信息。只有最后一個(gè) token 具有有關(guān)所有其他 token 的信息。
因此,如果我們想使用像 GPT 這樣的模型進(jìn)行分類微調(diào),我們應(yīng)該關(guān)注最后一個(gè) token 標(biāo)記以捕獲所有其他輸入 token 的上下文信息。
如下表所示,我們可以看到使用第一個(gè) token 來(lái)微調(diào) GPT 模型進(jìn)行分類會(huì)導(dǎo)致性能更差。
表 2:微調(diào) GPT 模型中的最后一個(gè) token 與第一個(gè) token。
BERT 與 GPT 的性能比較如何?
說(shuō)到 BERT,你可能想知道它在分類任務(wù)上與類 GPT 模型的性能比較如何?簡(jiǎn)單來(lái)說(shuō),在垃圾郵件分類任務(wù)上,更小的 GPT-2(124M)與更大 BERT(340M)的性能類似,具體如下表 3 所示。
表 3:GPT-2 與 BERT 的結(jié)果比較。
可以看到,BERT 模型的表現(xiàn)比 GPT-2 稍微好一點(diǎn)(測(cè)試準(zhǔn)確率高 1%),但 BERT 的參數(shù)規(guī)模幾乎是 GPT-2 的 3 倍。此外,數(shù)據(jù)集可能太小且太簡(jiǎn)單了,因此我又在 IMDB Movie Review 數(shù)據(jù)集上嘗試比較了情感分類表現(xiàn)(即預(yù)測(cè)觀看者是否喜歡一部電影)。
表 4:GPT-2 與 BERT 在影評(píng)分類任務(wù)上的比較。
可以看到,在這個(gè)更大的數(shù)據(jù)集上(包含 25k 訓(xùn)練和 25k 測(cè)試集記錄),GPT-2 與 BERT 兩個(gè)模型的預(yù)測(cè)性能同樣類似。
總的來(lái)說(shuō),在分類任務(wù)上,BERT 和其他編碼器風(fēng)格的模型被認(rèn)為優(yōu)于解碼器風(fēng)格的模型。但是,實(shí)驗(yàn)結(jié)果也表明,編碼器風(fēng)格的 BERT 和解碼器風(fēng)格的 GPT 模型之間沒(méi)有太大的差異。
此外,如果你對(duì)更多基準(zhǔn)比較以及如何進(jìn)一步提升解碼器風(fēng)格模型的分類性能感興趣,可以參閱以下兩篇最近的論文:
- Label Supervised LLaMA Finetuning:https://arxiv.org/abs/2310.01208
- LLM2Vec: Large Language Models Are Secretly Powerful Text Encoders:https://arxiv.org/abs/2404.05961
其中第一篇論文討論了:在分類微調(diào)期間移除因果掩碼可以提升解碼器風(fēng)格模型的分類性能。
我們應(yīng)該禁用因果掩碼嗎?
當(dāng)我們?cè)谙乱粋€(gè)詞(next-word)預(yù)測(cè)任務(wù)上訓(xùn)練類 GPT 模型時(shí),GPT 架構(gòu)的核心特征是因果注意力掩碼,這與 BERT 模型或原始 transformer 架構(gòu)不同。
但實(shí)際上,我們可以在分類微調(diào)階段移除因果掩碼, 從而允許我們微調(diào)第一個(gè)而不是最后一個(gè) token。這是因?yàn)槲磥?lái)的 tokens 將不再被掩碼,并且第一個(gè) token 可以看到所有其他的 tokens.
有 / 無(wú)因果掩碼的注意力權(quán)重矩陣。
幸運(yùn)的是,在類 GPT 大語(yǔ)言模型中禁用因果注意力掩碼只需要改變 2 行代碼。
class MultiheadAttention (nn.Module):
def __init__(self, d_in, d_out, context_length, dropout, num_heads):
super ().__init__()
# ...
def forward (self, x):
b, num_tokens, d_in = x.shape
keys = self.W_key (x) # Shape: (b, num_tokens, d_out)
queries = self.W_query (x)
values = self.W_value (x)
# ...
attn_scores = queries @ keys.transpose (2, 3)
# Comment out the causal attention mask part
# mask_bool = self.mask.bool ()[:num_tokens, :num_tokens]
# attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax (
attn_scores /keys.shape [-1]**0.5, dim=-1
)
context_vec = (attn_weights @ values).transpose (1, 2)
context_vec = context_vec.contiguous ().view (
b, num_tokens, self.d_out
)
context_vec = self.out_proj (context_vec)
return context_vec
下表 5 展示了改變代碼后對(duì)垃圾郵件分類任務(wù)帶來(lái)的影響。
表 5:有無(wú)使用因果注意力掩碼來(lái)微調(diào) GPT-2 分類器的結(jié)果。
可以看到,在微調(diào)階段禁用因果掩碼可以帶來(lái)略微的提升。
增加模型大小會(huì)帶來(lái)哪些影響?
目前為止,我們只看到了最小的 GPT-2(124M)模型的性能,那么與規(guī)模更大的 GPT-2 變體相比如何呢?比如 GPT-2 medium(355M)、GPT-2 large(774M)和 GPT-2 XL(1558M)。結(jié)果如下表 6 所示。
表 6:不同參數(shù)規(guī)模的 GPT-2 變體的分類微調(diào)結(jié)果。
可以看到,隨著模型參數(shù)增加,預(yù)測(cè)準(zhǔn)確率顯著提升。不過(guò) GPT-2 medium 是個(gè)例外,它在其他數(shù)據(jù)集上的性能同樣很差。我懷疑該模型可能沒(méi)有經(jīng)過(guò)很好的預(yù)訓(xùn)練。
此外,最大的 GPT-2 XL 獲得了比最小的 GPT-2 small(124M)好得多的分類準(zhǔn)確率,但微調(diào)時(shí)間也長(zhǎng)了 7 倍。
LoRA 預(yù)計(jì)能帶來(lái)哪些改進(jìn)?
回到本文第一個(gè)問(wèn)題:我們需要訓(xùn)練所有層嗎?結(jié)果發(fā)現(xiàn),當(dāng)僅僅微調(diào)最后一個(gè) transformer 塊而不是整個(gè)模型時(shí), 我們可以(或幾乎可以)匹配分配性能。所以僅僅微調(diào)最后一個(gè)塊的優(yōu)勢(shì)在于訓(xùn)練速度更快,畢竟不是所有的權(quán)重參數(shù)都要更新。
接下來(lái)的問(wèn)題是與低秩適應(yīng)(LoRA)的比較結(jié)果如何,LoRA 是一種參數(shù)高效的微調(diào)技術(shù)。
表 7:覆蓋所有層的完整微調(diào) vs 利用 LoRA 的參數(shù)高效微調(diào)。
可以看到,完整微調(diào)(所有層)和 LoRA 在數(shù)據(jù)集上獲得了相似的測(cè)試集性能。
在小模型上,LoRA 會(huì)稍微慢一點(diǎn),添加 LoRA 層帶來(lái)的額外開(kāi)銷可能會(huì)超過(guò)獲得的收益。但當(dāng)訓(xùn)練更大的 15 億參數(shù)模型時(shí),LoRA 的訓(xùn)練速度會(huì)快 1.53 倍。
填充(Padding)還是不填充?
如果我們想要在訓(xùn)練或推理階段分批次地處理數(shù)據(jù)(包括一次處理多個(gè)輸入序列),則需要插入 padding token,以確保訓(xùn)練樣本的長(zhǎng)度相等。
圖中描述了給定批次中的輸入文本如何在 padding 過(guò)程中保持長(zhǎng)度相等。
在常規(guī)文本生成任務(wù)中,由于 padding tokens 通常要添加到右側(cè),因而 padding 不影響模型的響應(yīng)結(jié)果。并且由于前面討論過(guò)的因果掩碼,這些 padding tokens 也不影響其他 token。
但是,我們對(duì)最后一個(gè) token 進(jìn)行了微調(diào)。同時(shí)由于 padding tokens 在最后一個(gè) token 的左側(cè),因此可能影響結(jié)果。
如果我們使用的批大小為 1,實(shí)際上不需要 pad 輸入。當(dāng)然,這樣做從計(jì)算的角度來(lái)看更加高效(一次只處理一個(gè)輸入樣本)。并且批大小為 1 可以用作一個(gè)變通方法,來(lái)測(cè)試使用 padding 是否影響結(jié)果。
表 8:有無(wú) padding 時(shí),GPT-2(124M)的訓(xùn)練準(zhǔn)確率、驗(yàn)證準(zhǔn)確率和測(cè)試準(zhǔn)確率變化。
可以看到,避免 padding tokens 的確可以為模型帶來(lái)效果的顯著提升。這里使用了梯度累計(jì)來(lái)模擬批大小 8,以匹配默認(rèn)實(shí)驗(yàn)的批大小,并進(jìn)行公平比較。
作者介紹
個(gè)人主頁(yè):https://sebastianraschka.com/
Sebastian Raschka 是一名機(jī)器學(xué)習(xí)和人工智能研究員,曾在威斯康星大學(xué)麥迪遜分校擔(dān)任統(tǒng)計(jì)學(xué)助理教授,專門研究深度學(xué)習(xí)和機(jī)器學(xué)習(xí)。他致力于關(guān)于 AI 和深度學(xué)習(xí)相關(guān)的內(nèi)容更簡(jiǎn)單易懂。
Sebastian 還熱衷于開(kāi)源軟件,十多年來(lái),他一直是一個(gè)充滿熱情的開(kāi)源貢獻(xiàn)者。他提出的方法現(xiàn)已成功在 Kaggle 等機(jī)器學(xué)習(xí)競(jìng)賽中得到應(yīng)用。
除了編寫代碼,Sebastian 還喜歡寫作,他撰寫了暢銷書《Python Machine Learning》(《Python 機(jī)器學(xué)習(xí)》)和《Machine Learning with PyTorch and ScikitLearn》。
這篇博客的內(nèi)容是他的新書《Build a Large Language Model (From Scratch)》的第六章。
更多研究細(xì)節(jié),可參考原博客。