1. 程式人生 > >HuggingFace-transformers系列的介紹以及在下游任務中的使用

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模型主要包含三部分:

  1. 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類,這是一個互逆的過程。

  2. 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 配置檔案,主要儲存特殊的配置。

  3. Model,也就是各種各樣的模型。除了初始的Bert、GPT等基本模型,針對下游任務,還定義了諸如BertForQuestionAnswering等下游任務模型。模型匯出時將生成config.jsonpytorch_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資料庫下載模型配置、引數等資訊(程式碼中已配置好位置)。這個方法雖然簡單,但是在國內並不可用。當然你可以先嚐試一下,不過會有很大的概率無法下載模型。

手動下載模型資訊並匯入

  1. 在HuggingFace官方模型庫上找到需要下載的模型,點選模型連結, 這個例子使用的是bert-base-uncased模型

  2. 點選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預訓練模型上傳到迅雷等網盤的話,請在評論區告知,我會新增在此部落格中,併為您新增部落格友鏈。

  3. 通過下載好的路徑匯入模型:

    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