layout: blog
title: HuggingFace-transformers系列的介紹以及在下游任務中的使用
date: 2020-04-23 09:02:46
tags: 5
categories: nlp
top: true
為了更好的閱讀體驗,可以訪問原博客https://dxzmpk.github.io/
內容介紹
這篇博客主要面向對Bert系列在Pytorch上應用感興趣的同學,將涵蓋的主要內容是:Bert系列有關的論文,Huggingface的實現,以及如何在不同下游任務中使用預訓練模型。
看過這篇博客,你將了解:
- Transformers實現的介紹,不同的Tokenizer和Model如何使用。
- 如何利用HuggingFace的實現自定義你的模型,如果你想利用這個庫實現自己的下游任務,而不想過多關注其實現細節的話,那么這篇文章將會成為很好的參考。
所需的知識
安裝Huggface庫(需要預先安裝pytorch)
在閱讀這篇文章之前,如果你能將以下資料讀一遍,或者看一遍的話,在后續的閱讀過程中將極大地減少你陷入疑惑的概率。
- 視頻類內容:根據排序觀看更佳
或者,你更願意去看論文的話:
- 相關論文:根據排序閱讀更佳
- 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
任務輸入:問題句,答案所在的文章 "Who was Jim Henson?", "Jim Henson was a nice puppet"
任務輸出:答案 "a nice puppet"
現存的模型輸入輸出和任務的輸入輸出有一定差別,這也是在使用上需要區別的地方:
模型輸入:inputids, token_type_ids
模型輸出:start_scores, end_scores 形狀都為torch.Size([1, 14])
,其中14
為序列長度,代表每個位置是開始/結束位置的概率。
模型的構建:
一般情況下,一個基本模型對應一個Tokenizer, 所以並不存在對應於具體下游任務的Tokenizer。這里通過bert_model初始化BertForQuestionAnswering。
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
利用模型進行運算:
# 設定模式
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]']
將模型輸出轉化為任務輸出:
# 對輸出的答案進行解碼的過程
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
任務輸入:句子 "i like you, what about you"
任務輸出:句子所屬的類別 class1
模型輸入:inputids, token_type_ids
模型輸出:logits, hidden states, 其中logits形狀為torch.Size([1, 3])
, 其中的3對應的是類別的數量。當訓練時,第一項為loss。
模型的構建:
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)
利用模型進行運算:
# 設定模式
model.eval()
token_codes = tokenizer.encode_plus("i like you, what about you")
# encode_plus結果為字典形式
with torch.no_grad():
outputs = cls_model(input_ids=torch.tensor([token_codes['input_ids']]),token_type_ids = torch.tensor([token_codes['token_type_ids']]))
# outputs[0]為logits,outputs[1]為hidden
輸出的轉化可直接通過numpy的argmax函數實現。
其他的任務,將繼續更新
其他的模型和之前的兩個大致是相同的,你可以自己發揮。我會繼續在相關的庫上進行實驗,如果發現用法不一樣的情況,將會添加在這里。
參考
本文章主要對HuggingFace庫進行了簡要介紹。具體安裝等過程請參見官方github倉庫。
本文主要參考於官方文檔
同時,在模型的理解過程中參考了一些kaggle上的notebooks, 主要是這一篇,作者是Abhishek Thakur