HuggingFace-transformers系列的介紹以及在下游任務中的使用
內容介紹
這篇部落格主要面向對Bert系列在Pytorch上應用感興趣的同學,將涵蓋的主要內容是:Bert系列有關的論文,Huggingface的實現,以及如何在不同下游任務中使用預訓練模型。
看過這篇部落格,你將瞭解:
- Transformers實現的介紹,不同的Tokenizer和Model如何使用。
- 如何利用HuggingFace的實現自定義你的模型,如果你想利用這個庫實現自己的下游任務,而不想過多關注其實現細節的話,那麼這篇文章將會成為很好的參考。
所需的知識
安裝Huggface庫(需要預先安裝pytorch)
在閱讀這篇文章之前,如果你能將以下資料讀一遍,或者看一遍的話,在後續的閱讀過程中將極大地減少你陷入疑惑的概率。
- 視訊類內容:根據排序觀看更佳
- 李巨集毅關於Elmo, Bert, GPT的講解
- Goebels關於transformerXL的講解
- Kilcher關於XLnet的講解
- McCormick關於ALBERT的講解
或者,你更願意去看論文的話:
- 相關論文:根據排序閱讀更佳
- arXiv:1810.04805, BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding, Authors: Jacob Devlin, Ming-Wei Chang, Kenton Lee, Kristina Toutanova
- arXiv:1901.02860, Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context, Authors: Zihang Dai, Zhilin Yang, Yiming Yang, William W. Cohen, Jaime Carbonell, Quoc V. Le and Ruslan Salakhutdinov.
- XLNet論文
- ALBERT論文
HuggingFace模型載入+下游任務使用
專案元件
一個完整的transformer模型主要包含三部分:
-
Config,控制模型的名稱、最終輸出的樣式、隱藏層寬度和深度、啟用函式的類別等。將Config類匯出時檔案格式為 json格式,就像下面這樣:
{ "attention_probs_dropout_prob": 0.1, "hidden_act": "gelu", "hidden_dropout_prob": 0.1, "hidden_size": 768, "initializer_range": 0.02, "intermediate_size": 3072, "max_position_embeddings": 512, "num_attention_heads": 12, "num_hidden_layers": 12, "type_vocab_size": 2, "vocab_size": 30522 }
當然,也可以通過config.json來例項化Config類,這是一個互逆的過程。
-
Tokenizer,這是一個將純文字轉換為編碼的過程。注意,Tokenizer並不涉及將詞轉化為詞向量的過程,僅僅是將純文字分詞,新增[MASK]標記、[SEP]、[CLS]標記,並轉換為字典索引。Tokenizer類匯出時將分為三個檔案,也就是:
-
vocab.txt
詞典檔案,每一行為一個詞或詞的一部分
-
special_tokens_map.json 特殊標記的定義方式
{"unk_token": "[UNK]", "sep_token": "[SEP]", "pad_token": "[PAD]", "cls_token": "[CLS]", "mask_token": "[MASK]"}
-
tokenizer_config.json 配置檔案,主要儲存特殊的配置。
-
-
Model,也就是各種各樣的模型。除了初始的Bert、GPT等基本模型,針對下游任務,還定義了諸如
BertForQuestionAnswering
等下游任務模型。模型匯出時將生成config.json
和pytorch_model.bin
引數檔案。前者就是1中的配置檔案,這和我們的直覺相同,即config和model應該是緊密聯絡在一起的兩個類。後者其實和torch.save()儲存得到的檔案是相同的,這是因為Model都直接或者間接繼承了Pytorch的Module類。從這裡可以看出,HuggingFace在實現時很好地尊重了Pytorch的原生API。
匯入Bert系列基本模型的方法
通過官網自動匯入
官方文件中初始教程提供的方法為:
# Load pre-trained model (weights)
# model = BertModel.from_pretrained('bert-base-uncased')
這個方法需要從官方的s3資料庫下載模型配置、引數等資訊(程式碼中已配置好位置)。這個方法雖然簡單,但是在國內並不可用。當然你可以先嚐試一下,不過會有很大的概率無法下載模型。
手動下載模型資訊並匯入
-
在HuggingFace官方模型庫上找到需要下載的模型,點選模型連結, 這個例子使用的是bert-base-uncased模型
-
點選List all files in model,將其中的檔案一一下載到同一目錄中。例如,對於XLNet:
# List of model files config.json 782.0B pytorch_model.bin 445.4MB special_tokens_map.json 202.0B spiece.model 779.3KB tokenizer_config.json 2.0B
但是這種方法有時也會不可用。如果您可以將Transformers預訓練模型上傳到迅雷等網盤的話,請在評論區告知,我會新增在此部落格中,併為您新增部落格友鏈。
-
通過下載好的路徑匯入模型:
import transformers MODEL_PATH = r"D:\transformr_files\bert-base-uncased/" # a.通過詞典匯入分詞器 tokenizer = transformers.BertTokenizer.from_pretrained(r"D:\transformr_files\bert-base-uncased\bert-base-uncased-vocab.txt") # b. 匯入配置檔案 model_config = transformers.BertConfig.from_pretrained(MODEL_PATH) # 修改配置 model_config.output_hidden_states = True model_config.output_attentions = True # 通過配置和路徑匯入模型 model = transformers.BertModel.from_pretrained(MODEL_PATH,config = model_config)
利用分詞器分詞
利用分詞器進行編碼
-
對於單句:
# encode僅返回input_ids tokenizer.encode("i like you") Out : [101, 1045, 2066, 2017, 102]
-
對於多句:
# encode_plus返回所有編碼資訊 tokenizer.encode_plus("i like you", "but not him") Out : {'input_ids': [101, 1045, 2066, 2017, 102, 2021, 2025, 2032, 102], 'token_type_ids': [0, 0, 0, 0, 0, 1, 1, 1, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
模型的所有分詞器都是在PreTrainedTokenizer中實現的,分詞的結果主要有以下內容:
{
input_ids: list[int],
token_type_ids: list[int] if return_token_type_ids is True (default)
attention_mask: list[int] if return_attention_mask is True (default)
overflowing_tokens: list[int] if a max_length is specified and return_overflowing_tokens is True
num_truncated_tokens: int if a max_length is specified and return_overflowing_tokens is True
special_tokens_mask: list[int] if add_special_tokens if set to True and return_special_tokens_mask is True
}
編碼解釋:
- 'input_ids':顧名思義,是單詞在詞典中的編碼
- 'token_type_ids', 區分兩個句子的編碼
- 'attention_mask', 指定對哪些詞進行self-Attention操作
- 'overflowing_tokens', 當指定最大長度時,溢位的單詞
- 'num_truncated_tokens', 溢位的token數量
- 'return_special_tokens_mask',如果新增特殊標記,則這是[0,1]的列表,其中0指定特殊新增的標記,而1指定序列標記
將分詞結果輸入模型,得到編碼
# 新增batch維度並轉化為tensor
input_ids = torch.tensor([input_ids])
token_type_ids = torch.tensor([token_type_ids])
# 將模型轉化為eval模式
model.eval()
# 將模型和資料轉移到cuda, 若無cuda,可更換為cpu
device = 'cuda'
tokens_tensor = input_ids.to(device)
segments_tensors = token_type_ids.to(device)
model.to(device)
# 進行編碼
with torch.no_grad():
# See the models docstrings for the detail of the inputs
outputs = model(tokens_tensor, token_type_ids=segments_tensors)
# Transformers models always output tuples.
# See the models docstrings for the detail of all the outputs
# In our case, the first element is the hidden state of the last layer of the Bert model
encoded_layers = outputs
# 得到最終的編碼結果encoded_layers
Bert最終輸出的結果為:
sequence_output, pooled_output, (hidden_states), (attentions)
以輸入序列長度為14為例
index | 名稱 | 維度 | 描述 |
---|---|---|---|
0 | sequence_output | torch.Size([1, 14, 768]) | 輸出序列 |
1 | pooled_output | torch.Size([1, 768]) | 對輸出序列進行pool操作的結果 |
2 | (hidden_states) | tuple,13*torch.Size([1, 14, 768]) | 隱藏層狀態(包括Embedding層),取決於modelconfig中output_hidden_states |
3 | (attentions) | tuple,12*torch.Size([1, 12, 14, 14]) | 注意力層,取決於引數中output_attentions |
Bert總結
這一節我們以Bert為例對模型整體的流程進行了瞭解。之後的很多模型都基於Bert,並基於Bert進行了少量的調整。其中的輸出和輸出引數也有很多重複的地方。
利用預訓練模型在下游任務上微調
如開頭所說,這篇文章重點在於"如何進行模型的調整以及輸入輸出的設定", 以及"Transformer的實現進行簡要的提及", 所以,我們不會去介紹、涉及如何寫train迴圈等話題,而僅僅專注於模型。也就是說,我們將止步於跑通一個模型,而不計批量資料預處理、訓練、驗證等過程。
同時,這裡更看重如何基於Bert等初始模型在實際任務上進行微調,所以我們不會僅僅地匯入已經在下游任務上訓練好的模型引數,因為在這些模型上使用的方法和上一章的幾乎完全相同。
這裡的輸入和輸入以模型的預測過程為例。
問答任務 via Bert
模型的構建:
from transformers import BertTokenizer, BertForQuestionAnswering
import torch
MODEL_PATH = r"D:\transformr_files\bert-base-uncased/"
# 例項化tokenizer
tokenizer = BertTokenizer.from_pretrained(r"D:\transformr_files\bert-base-uncased\bert-base-uncased-vocab.txt")
# 匯入bert的model_config
model_config = transformers.BertConfig.from_pretrained(MODEL_PATH)
# 首先新建bert_model
bert_model = transformers.BertModel.from_pretrained(MODEL_PATH,config = model_config)
# 最終有兩個輸出,初始位置和結束位置(下面有解釋)
model_config.num_labels = 2
# 同樣根據bert的model_config新建BertForQuestionAnswering
model = BertForQuestionAnswering(model_config)
model.bert = bert_model
一般情況下,一個基本模型對應一個Tokenizer, 所以並不存在對應於具體下游任務的Tokenizer。這裡通過bert_model初始化BertForQuestionAnswering。
任務輸入:問題句,答案所在的文章 "Who was Jim Henson?", "Jim Henson was a nice puppet"
任務輸出:答案 "a nice puppet"
# 設定模式
model.eval()
question, text = "Who was Jim Henson?", "Jim Henson was a nice puppet"
# 獲取input_ids編碼
input_ids = tokenizer.encode(question, text)
# 手動進行token_type_ids編碼,可用encode_plus代替
token_type_ids = [0 if i <= input_ids.index(102) else 1 for i in range(len(input_ids))]
# 得到評分,
start_scores, end_scores = model(torch.tensor([input_ids]), token_type_ids=torch.tensor([token_type_ids]))
# 進行逆編碼,得到原始的token
all_tokens = tokenizer.convert_ids_to_tokens(input_ids)
#['[CLS]', 'who', 'was', 'jim', 'henson', '?', '[SEP]', 'jim', 'henson', 'was', 'a', 'nice', 'puppet', '[SEP]']
模型輸入:inputids, token_type_ids
模型輸出:start_scores, end_scores 形狀都為torch.Size([1, 14])
,其中14
為序列長度,代表每個位置是開始/結束位置的概率。
將模型輸出轉化為任務輸出:
# 對輸出的答案進行解碼的過程
answer = ' '.join(all_tokens[torch.argmax(start_scores) : torch.argmax(end_scores)+1])
# assert answer == "a nice puppet"
# 這裡因為沒有經過微調,所以效果不是很好,輸出結果不佳。
print(answer)
# 'was jim henson ? [SEP] jim henson was a nice puppet [SEP]'
文字分類任務(情感分析等) via XLNet
模型的構建:
from transformers import XLNetConfig, XLNetModel, XLNetTokenizer, XLNetForSequenceClassification
import torch
# 定義路徑,初始化tokenizer
XLN_PATH = r"D:\transformr_files\XLNetLMHeadModel"
tokenizer = XLNetTokenizer.from_pretrained(XLN_PATH)
# 載入配置
model_config = XLNetConfig.from_pretrained(XLN_PATH)
# 設定類別數為3
model_config.num_labels = 3
# 直接從xlnet的config新建XLNetForSequenceClassification(和上一節方法等效)
cls_model = XLNetForSequenceClassification.from_pretrained(XLN_PATH, config=model_config)
任務輸入:句子 "i like you, what about you"
任務輸出:句子所屬的類別 class1
# 設定模式
model.eval()
token_codes = tokenizer.encode_plus("i like you, what about you")
模型輸入:inputids, token_type_ids
模型輸出:logits, hidden states, 其中logits形狀為torch.Size([1, 3])
, 其中的3對應的是類別的數量。當訓練時,第一項為loss。
其他的任務,將繼續更新
其他的模型和之前的兩個大致是相同的,你可以自己發揮。我會繼續在相關的庫上進行實驗,如果發現用法不一樣的情況,將會新增在這裡。
參考
本文章主要對HuggingFace庫進行了簡要介紹。具體安裝等過程請參見官方github倉庫。
本文主要參考於官方文件
同時,在模型的理解過程中參考了一些kaggle上的notebooks, 主要是這一篇,作者是Abhishek Tha