從零實(shí)現(xiàn)大模型-BERT微調(diào) 原創(chuàng) 精華
按照順序,輪也該輪到BERT指令微調(diào)了吧!
是微調(diào),但不是指令微調(diào)!
我們在之前的文章介紹過大模型的多種微調(diào)方法,指令微調(diào)只是其中一種,就像訓(xùn)犬一樣,讓它坐就坐,讓它臥就臥,同理,你讓LLM翻譯,它不是去總結(jié),你讓它總結(jié),它不是去情感分析。
指令微調(diào)在像GPT這種自回歸的模型中應(yīng)用多一些。我們在前一篇文章中基于GPT-2預(yù)訓(xùn)練模型進(jìn)行了指令微調(diào)。
除了指令微調(diào),還有一種比較常用的是任務(wù)微調(diào),預(yù)訓(xùn)練模型雖然具備一定的知識,但尚不能直接用于某些具體任務(wù)。
例如,雖然在BERT的預(yù)訓(xùn)練過程中,通過Masked Language Model (MLM)和Next Sentence Prediction (NSP)使其學(xué)習(xí)了語言的基本特征。
Masked Language Model (MLM)
Next Sentence Prediction (NSP)
但它仍不能直接用于自然語言推理(NLI)和問答(QA)等具體任務(wù)。因此,今天我們將對之前的BERT預(yù)訓(xùn)練模型進(jìn)行進(jìn)一步微調(diào),使其能夠更好地適應(yīng)這些具體任務(wù)。
但完整代碼如下,請結(jié)合代碼閱讀本文。
https://github.com/AIDajiangtang/LLM-from-scratch/blob/main/Bert_fine_tune_from_scratch.ipynb
在正式開始之前,有幾點(diǎn)需要注意:
1.在微調(diào)階段,模型架構(gòu)與預(yù)訓(xùn)練要一致,2.使用預(yù)訓(xùn)練模型的權(quán)重進(jìn)行初始化而非隨機(jī)初始化,3.使用預(yù)訓(xùn)練相同的分詞方法和詞表,4.輸入數(shù)據(jù)的格式與預(yù)訓(xùn)練階段一致。例如,BERT模型通常要求輸入序列包含[CLS]和[SEP]標(biāo)記。
所以在下載預(yù)訓(xùn)練模型時,除了下載模型參數(shù),通常還要下載配套的詞表和模型超參數(shù)。
['bert_config.json',
'bert_model.ckpt.data-00000-of-00001',
'bert_model.ckpt.index',
'vocab.txt']
如果要擴(kuò)充詞表來支持多語言,那模型結(jié)構(gòu)中的嵌入層和輸出層也需要更改,所以往往需要重新預(yù)訓(xùn)練。
有了前面四篇文章的烘托,本篇文章會忽略重復(fù)內(nèi)容。
01、微調(diào)任務(wù)1:自然語言推理
自然語言推理任務(wù)通常是判斷兩個句子之間的邏輯關(guān)系(如蘊(yùn)涵、矛盾或中立)。
Next Sentence Prediction (NSP)可以看作是一種特殊的自然語言推理任務(wù)。
1.訓(xùn)練數(shù)據(jù)
本次微調(diào)用的數(shù)據(jù)來自GLUE MRPC,數(shù)據(jù)由成對的句子構(gòu)成,并且還有一個人工標(biāo)注的標(biāo)簽,表示兩個句子是否語義相似。
FeaturesDict({
'idx': int32,
'label': ClassLabel(shape=(), dtype=int64, num_classes=2),
'sentence1': Text(shape=(), dtype=string),
'sentence2': Text(shape=(), dtype=string),
})
下面打印一條數(shù)據(jù)。
idx : 1680
label : 0
sentence1: b'The identical rovers will act as robotic geologists , searching for evidence of past water .'
sentence2: b'The rovers act as robotic geologists , moving on six wheels .'
- 對于每個樣本中的句子對,拼接成一個輸入序列,格式為:[CLS] 句子A [SEP] 句子B [SEP]。
- 使用BERT的分詞器將輸入序列分詞,并將其轉(zhuǎn)換為輸入ID、注意力掩碼和類型ID。
詞表參數(shù):
{'vocab_size': 30522,
'start_of_sequence_id': 101,
'end_of_segment_id': 102,
'padding_id': 0,
'mask_id': 103}
設(shè)置batch_size=32,max_seq_length = 128。
則輸入ID:
模型的輸入X。
'input_word_ids': <tf.Tensor: shape=(32, 128), dtype=int32, numpy=
array([[ 101, 1996, 7235, ..., 0, 0, 0],
[ 101, 2625, 2084, ..., 0, 0, 0],
[ 101, 6804, 1011, ..., 0, 0, 0],
...,
[ 101, 2021, 2049, ..., 0, 0, 0],
[ 101, 2274, 2062, ..., 0, 0, 0],
[ 101, 2043, 1037, ..., 0, 0, 0]], dtype=int32)>
注意力掩碼:
注意力掩碼用于區(qū)分實(shí)際的 token 和填充的 token,1表示實(shí)際的 token,0表示填充的 token。
在多頭注意力計(jì)算時,注意力掩碼會將填充位置對應(yīng)的注意力權(quán)重設(shè)置為負(fù)無窮(通常是一個非常大的負(fù)數(shù),如 -10^9),這樣在通過 softmax 計(jì)算時,這些位置的權(quán)重就會接近于零,從而使這些填充位置不會對注意力分?jǐn)?shù)產(chǎn)生影響。
在計(jì)算損失時,通常會忽略填充位置對應(yīng)的 token。
'input_mask': <tf.Tensor: shape=(32, 128), dtype=int32, numpy=
array([[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
...,
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0],
[1, 1, 1, ..., 0, 0, 0]], dtype=int32)>,
類型ID:
表示token屬于哪個句子,0表示屬于句子A,1表示數(shù)據(jù)句子B。
'input_type_ids': <tf.Tensor: shape=(32, 128), dtype=int32, numpy=
array([[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]], dtype=int32)>
在將token id轉(zhuǎn)換成詞嵌入向量時,會將類型id視為segment Embedding。
標(biāo)簽:
['not_equivalent', 'equivalent']->[0,1]
0:表示兩個句子語義不相似。
1:表示兩個句子語義相似。
<tf.Tensor: shape=(32,), dtype=int64, numpy=
array([0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 1, 1, 1, 0, 0, 1, 0, 1])>
到此,我們就構(gòu)造了模型輸入和標(biāo)簽。
input_word_ids shape: (32, 128)
input_mask shape: (32, 128)
input_type_ids shape: (32, 128)
labels shape: (32,)
2.模型
在模型架構(gòu)上,相對于BERT預(yù)訓(xùn)練,在微調(diào)過程中,會在模型的輸出層添加一個分類層。這個分類層的輸入是[CLS]標(biāo)記對應(yīng)的隱藏狀態(tài),其輸出是表示類別概率的logits。
因?yàn)镋MB_SIZE = 768,所以分類層的輸入(32, 768),輸出(32, 768,2)。
3.微調(diào)
超參數(shù)
EMB_SIZE = 768//詞嵌入維度
HIDDEN_SIZE = 768
BATCH_SIZE = 32 #batch size
NUM_HEADS = 4 //頭的個數(shù)
3.1.詞嵌入
接下來將token ids轉(zhuǎn)換成embedding,在Bert中,每個token都涉及到三種嵌入,第一種是Token embedding,token id轉(zhuǎn)換成詞嵌入向量,第二種是位置編碼。還有一種是Segment embedding。用于表示哪個句子,0表示第一個句子,1表示第二個句子。
根據(jù)超參數(shù)EMB_SIZE = 768,所以詞嵌入維度768,Token embedding通過一個嵌入層[30522,768]將輸入[32,128]映射成[32,128,768]。
30522是詞表的大小,[30522,768]的嵌入層可以看作是有30522個位置索引的查找表,每個位置存儲768維向量。
位置編碼可以通過學(xué)習(xí)的方式獲得,也可以通過固定計(jì)算方式獲得,本次采用固定計(jì)算方式。
Segment embedding和輸入X大小一致,第一個句子對應(yīng)為0,第二個位置為1。
最后將三個embedding相加,然后將輸出的embedding[32,128,768]輸入到編碼器中。
3.2.多頭注意力
編碼器的第一個操作是多頭注意力,與Transformer和GPT中不同的是,不計(jì)算[PAD]的注意力,會將[PAD]對應(yīng)位置的注意力分?jǐn)?shù)設(shè)置為一個非常小的值,使之經(jīng)過softmax后為0。
多頭注意力的輸出維度[32,128,768]。
3.3.MLP
與Transformer和GPT中的一致,MLP的輸出維度[32,128,768]。
3.4.輸出
編碼器的輸出[32,128,768],但我們只需要[CLS]對應(yīng)的輸出[32,768]。
二分類損失
通過另一個線性層[768,2]將開頭的[CLS]的輸出[32,768]映射成[32,2],表示屬于正負(fù)類的概率,然后與標(biāo)簽[32,]計(jì)算交叉熵?fù)p失。
02、微調(diào)任務(wù)2:問答
問答任務(wù)通常是給定一個段落和一個問題,模型需要從段落中找出答案的起始位置和結(jié)束位置。
示例
假設(shè)我們有一個段落和一個問題:
段落:"BERT is a model developed by Google for natural language processing tasks. It stands for Bidirectional Encoder Representations from Transformers."
問題:"Who developed BERT?"
我們需要從段落中找出答案的起始位置和結(jié)束位置。在這個例子中,答案是 "Google",它在段落中的位置如下:
- 起始位置:6 (第7個詞,"Google")
- 結(jié)束位置:6 (第7個詞,"Google")
超參數(shù)
max_seq_length = 128
EMB_SIZE = 768//詞嵌入維度
HIDDEN_SIZE = 768
BATCH_SIZE = 32 #batch size
NUM_HEADS = 4 //頭的個數(shù)
1.訓(xùn)練數(shù)據(jù)
- 輸入預(yù)處理:
- 將段落和問題轉(zhuǎn)換為BERT的輸入格式:[CLS] 問題 [SEP] 段落 [SEP]。
- 例如:[CLS] Who developed BERT? [SEP] BERT is a model developed by Google for natural language processing tasks. It stands for Bidirectional Encoder Representations from Transformers. [SEP]
- 分詞和ID轉(zhuǎn)換:
- 使用BERT的分詞器將輸入序列分詞,并將其轉(zhuǎn)換為輸入ID、注意力掩碼和類型ID。
本文轉(zhuǎn)載自公眾號人工智能大講堂
