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