NLP深度學習不能不用的套件 - Transformers

Posted by MingLun Allen Wu on Thursday, June 18, 2020

前言

本篇筆記所紀錄的 Transformers 並不是 Attention 論文中所提到的 “Transformer” 模型,而是由 Higginface 團隊所開發的 Transformers 套件。

這則筆記的重點在於:

  1. 為什麼要使用 Transformers
  2. Transformers 的兩大元件:
    • Tokenizer
    • Model
  3. 使用 Transformers 實作 Feature Extraction.

近年來 NLP 在 Attention 概念提出以後,各種模型如同雨後春筍般噴發:

(圖片來源: https://medium.com/@hamdan.hussam/from-bert-to-albert-pre-trained-langaug-models-5865aa5c3762)

然而在使用不同的模型時,每個模型的架構、參數的載入方式都不相同,大大的提高了模型間的轉換成本。舉例來說: 進行假新聞分類任務時,可能會想要嘗試從「BERT」轉換到「XLNet」,在其餘架構不變的前提下,光是進行 Pre-train Language Model 的更換可能就會花費不少時間。

透過 Transformers 這個套件,能夠將轉換成本降到最低! 使用這套框架能夠直接對多種模型進行操作,各種模型的架構及參數都已經被封裝在套件中,只需要了解此框架的機制,即可快速套用到數十種不同的模型!

直白的說:學習這個套件,就能同時學會使用多種主流的語言模型。

附上 Transfomers 的官方網站:

Hugging Face - Transformers

以及 Transformers 當前所支援的模型清單:

Hugging Face - On a mission to solve NLP, one commit at a time.


如何使用Transformers

我將 Transformers 套件分成兩個部分:

  1. Tokenizer : 將文字斷詞後轉換為 Index.
  2. Pre-trained Model : 接受轉換的 Index, 輸出 Word Representation.

下面這張流程圖,是從文字轉換成 Word Representation的過程,我們藉此來了解這兩個元件的作用(反黃)到底是什麼 :

Tokenizer

1. 初始化

Tokenizer 的功用是將文字進行斷詞及轉換為 Index,不同的 Pre-trained Language Model 所使用的斷詞方式以及 Index 也不盡相同,所以在使用前需要先進行「初始化」。

from transformers import AutoTokenizer, BertTokenizer
    
# 常見模型具有自己的Tokenizer
tokenizer_bert = BertTokenizer.from_pretrained("bert-base-cased")

# AutoTokenizer 則是通用型
tokenizer_other = AutoTokenizer.from_pretrained("emilyalsentzer/Bio_ClinicalBERT")

針對較常見的語言模型 (BERT, GPT2, XLM, XLNet…), Transformers 有提供專屬的 Tokenizer 可以使用。

除此之外,還有額外定義一個 AutoTokenizer,可以讓使用者互相分享、使用訓練好的模型。 透過 tokenizer.from_pretrained(<model_name>) 即可下載並且載入該模型的相關設定。

在初次使用模型時, transformers 會自動下載該模型的權重及相關設定,需要花費一點時間 (時間長度端看模型的大小),下載完成後,往後的執行將會從本地端的快取資料夾載入,就不需要再進行下載了。

2. 基本使用

tokenizer_bert = BertTokenizer.from_pretrained("bert-base-cased")
test_input = "I don't want to work."

encode_result = tokenizer.encode(test_input)  # 使用 encode() 來對文字進行斷詞、編碼。
print(encode_result)
# [101, 146, 1274, 112, 189, 1328, 1106, 1250, 119, 102]

decode_result = tokenizer.decode(encode_result) # 使用 decode() 來將 id 轉換回文字。
print(decode_result)
# "[CLS] I don't want to work. [SEP]"

從中我們觀察到兩件事情:

  1. encode_result 的長度好像跟 test_input 的長度不相同!:

    這是因為有些模型會使用 Word Piece Tokenizer,也就是在斷詞過程中將詞彙進行拆解,所以會導致 Encode 過的結果長度與原始 Input 不同!

  2. Decode後的結果好像跟原本的不太一樣…:

    因為在 tokenizer.encode() 過程中會自動加入 [CLS]、[SEP]等特殊標記,這是由於 BERT 模型的機制而自動產生,其餘模型的 Tokenizer 不一定會有,也可以在encode過程中加入參數來移除:

    encode_result = tokenizer.encode(test_input, add_special_tokens=False) # 編碼過程中,移除特殊符號
    print(encode_result) 
    # [146, 1274, 112, 189, 1328, 1106, 1250, 119]
    
    decode_result = tokenizer.decode(encode_result) # 使用 decode() 來將 id 轉換回文字。
    print(decode_result)
    # "I don't want to work."
    

除此之外,在進行資料前處理時,另外一個麻煩的問題是在產生 “batch” 時,必須要讓每一筆資料的長度相同(也就是要進行 “Padding” ),假設最大長度設定為 64:

  • 長度不足的句子需要補齊 “[PAD]“符號,使長度達到 64。
  • 長度過長的句子需要只保留前 64 個字,超過的部分捨棄不用。

這件事情如果自己處理,會有點小麻煩,幸好 Tokenizer 都幫忙做好了.

test_input = "I don't want to work."

encode_result = tokenizer.encode(test_input, max_length=64, pad_to_max_length=True)
print(encode_result)
# [101, 146, 1274, 112, 189, 1328, 1106, 1250, 119, 102, 0, 0, 0,.... 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

decode_result = tokenizer.decode(encode_result)
print(decode_result)
# "[CLS] I don't want to work. [SEP] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD] [PAD]...[PAD]"

透過 Tokenizer 進行斷詞及編碼後,我們就能將其送入 Pre-trained Model 中取得 Representation 了!


Pre-trained Language Model

1. 初始化

from transformers import AutoModel, BertModel

model_bert = BertModel.from_pretrained("bert-base-cased")
model_other = AutoModel.from_pretrained("emilyalsentzer/Bio_ClinicalBERT")

Tokenizer 的機制相同, Transformers 中也包含了各種預先定義好的 Model (例如 BertModel),以及方便使用其他開源模型的泛用Model (AutoModel)。

在使用前也需要透過 model.from_pretrained(<model_name>) 來指定要載入的模型。

Tokenizer 相同,初次使用模型時會需要進行下載,所以需要等待一段時間,以 xlnet-large-cased 為例,大約有 3.3G 需要進行下載。

值得注意的是:TokenizerModel 是有先後關係的:

  1. 文字進入 Tokenizer 轉換。
  2. 轉換後的Index送入 Model 中取得 Word Representation。

所以 TokenizerModel<model_name> 必需要一致,以免發生錯誤。

2. 基本使用

tokenizer_bert = BertTokenizer.from_pretrained("bert-base-cased")
test_input = "I don't want to work."

encode_result = tokenizer.encode(test_input, max_length=64, pad_to_max_length=True)  # 使用 encode() 來對文字進行斷詞、編碼。
print(encode_result)
# [101, 146, 1274, 112, 189, 1328, 1106, 1250, 119, 102, 0, 0, ..., 0]

model_bert = BertModel.from_pretrained("bert-base-cased")

output = model_bert(encode_result) # 等同於將 encode_result 送入 model 中進行 forward propagation.
last_hidden_layer = output[0] # 這就是 “test_input" 的 word representation.

print(last_hidden_layer.shape)
# (1, 64, 768)   => (batch, words, word vector dimension)

transformers 中的所有 model 都是繼承 torch.nn.Module 而來,在使用上其實跟 Pytorch 的 Model 相當類似! 所以在上述的語法中將 encode_result 送入 model_bert 中,就等同於將 encode_result送入 model 中進行 Forward Propagation,最後即可得到 Word Representation.

至於為什麼 last_hidden_layer = output[0],可以參考 BertModel 的 Return 欄位,回傳的結果有許多內容,而第一個就是「最後一層的 Hidden States」也就是我們要的 Word Representation.

附上 BertModel的官方文件連結: BERT - transformers 2.11.0 documentation


Transformers 的優勢

了解 Transformers 的基本運作原理後,再回頭強調一次它的優勢!:

  1. 可視為封裝好的 Language Model :

    Transformers 的 Model 繼承了 torch.nn.Module,也就是說在設計整個任務的過程中,不需要自己去定義 “Pre-trained Language Model” 的結構、也不需要自行載入權重,**只需要將其視為一個Pytorch 預先定義好的 Model :**實作一個 Transformers的Model、載入相關權重,接下來只需要組裝就好了!

    model_bert = BertModel.from_pretrained("bert-base-cased")
    model_ln1 = nn.Linear(768, 3) 
    
    output = model_bert(encode_result) # 等同於將 encode_result 送入 model 中進行 forward propagation.
    last_hidden_layer = output[0]
    output = model_ln1(last_hidden_layer) # 可以直接再將 BertModel 的 Output送入其他自定義的結構。
    

    了解到如何組裝、如何使用後,除了將其拿來做 Feature Extraction 外,也可以用相同的概念組裝成 Transfer Learning 的模型架構!不過這超出這則筆記想要紀錄的範圍了~

    另外,習慣使用 Tensorflow 的朋友也別傷心, transformers也有提供 tensorflow 版本的 Model (例如 tfBertModel)!

  2. 同樣的架構能使用多種模型 → 方便抽換

    由於 Transformers 中包含許多模型:有部分是官方持續新增,有些則是使用者互相釋出,導致使用這套架構就能快速切換多種模型。

    MODEL = "bert-base-cased"
    model_language = AutoModel.from_pretrained(MODEL)
    model_ln1 = nn.Linear(768, 3) 
    
    output = model_language(encode_result) # 等同於將 encode_result 送入 model 中進行 forward propagation.
    last_hidden_layer = output[0]
    output = model_ln1(last_hidden_layer) # 可以直接再將 model_language 的 Output送入其他自定義的結構。
    

    相同的程式碼,只需要更換模型變數即可達到「抽換底層 Pre-trained Langauge Model的效果」

    MODEL = "xlnet-large-cased"
    model_language = AutoModel.from_pretrained(MODEL)
    model_ln1 = nn.Linear(768, 3) 
    
    output = model_language(encode_result) # 等同於將 encode_result 送入 model 中進行 forward propagation.
    last_hidden_layer = output[0]
    output = model_ln1(last_hidden_layer) # 可以直接再將 model_language 的 Output送入其他自定義的結構。
    

後記

了解 Transformers 這個套件後,在進行 NLP 的相關任務上節省了許多時間,可以快速地切換Pre-trained Language Model 來進行各種嘗試。

此外,在Survey新的Language Model時,如果在Github上面看到它支援 transformers,會備感欣慰QQ,頓時感到世界的美好~

感謝你的閱讀,希望你也能跟我一起感受世界的美好!XD

有任何問題歡迎一起交流! 下次見!



See Also