Last Updated on 2021-10-27 by Clay
2018 年年底,以 BERT 為首等 Transformer 模型席捲了各大 NLP 競賽的排行榜,並幾乎都有著相當優異的表現。一直以來我都對 BERT 等 Transformer 模型充滿了興趣,故今天便開始動手紀錄該如何使用 Hugging Face 所開發的 Transformers 套件。
本篇文章較少著墨於 BERT 等 Transformer 模型的原理,更多聚焦在如何使用 Transformers 這個套件。雖然這個套件是存在著 Tensorflow 版本,不過由於我本人目前使用都是透過 PyTorch,故這篇心得筆記也會以 PyTorch 作為示範。
Transformer 模型概述
首先,BERT 是 Transformer 模型的一種,但究竟什麼是 Transformer 模型呢?基本上,我們可以理解 Transformer 模型架構分成 Encoder (編碼器) 以及 Decoder (解碼器) 的部份。
然而,由於 BERT 為將單詞 (Character) 轉為詞嵌入 (Word Embedding) 的語言模型,故通常我們使用到的,實際上只是 Transformer 模型中的 Encoder。
如何使用 BERT
剛剛簡單說明了 BERT 是 Transformer 模型的一種,現在來簡單介紹一下什麼是 BERT。
BERT (Bidirectional Encoder Representations from Transformers) 是由 Google 研究人員發表的論文,並證明了雙向訓練的語言模型比單向更好。
那麼,我們該如何使用 BERT 在我們自己的下游任務中呢?
首先,我們先使用以下指令安裝 Hugging Face 的 Transformers 套件:
pip3 install transformers
如果 Python 環境中沒有 PyTorch 以及 Tensorflow,那麼很有可能會在後頭使用 transformers 套件時發生 Core dump 的問題,最好先確認系統中裝有 PyTorch 以及 Tensorflow。
而要使用 BERT 轉換文字成向量,首先我們需要把我們的文字轉換成 BERT 模型當中單個 Token 的編號,並把我們的輸入都 Padding 成一樣的長度,然後提出一個句子的 Mask (遮罩,後面程式碼會解釋),然後就能使用 Hugging Face 事先訓練好的 Pre-trained 模型了。
以下來看個簡單的示範:
# coding: utf-8
import torch
from transformers import AutoTokenizer, AutoModel
from keras.preprocessing.sequence import pad_sequences
# Tokenizer and Bert Model
tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese')
embedding = AutoModel.from_pretrained('bert-base-chinese')
# Preprocess
sent = '今天天氣真 Good。'
sent_token = tokenizer.encode(sent)
sent_token_padding = pad_sequences([sent_token], maxlen=10, padding='post', dtype='int')
masks = [[float(value>0) for value in values] for values in sent_token_padding]
print('sent:', sent)
print('sent_token:', sent_token)
print('sent_token_padding:', sent_token_padding)
print('masks:', masks)
print('\n')
# Convert
inputs = torch.tensor(sent_token_padding)
masks = torch.tensor(masks)
embedded, _ = embedding(inputs, attention_mask=masks)
print('embedded shape:', embedded.shape)
Output:
sent: 今天天氣真 Good。
sent_token: [101, 791, 1921, 1921, 3706, 4696, 100, 511, 102]
sent_token_padding: [[ 101 791 1921 1921 3706 4696 100 511 102 0]]
masks: [[1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0]]
embedded shape: torch.Size([1, 10, 768])
首先,我們必須先初始化我們的 Tokenizer 以及 Model,在這裡由於我要處理中文,故我的模型選擇為 "bert-base-chinese"。
接著,我使用 tokenizer.encode() 將我的句子編碼成 BERT 中所需要的編號,每個編號對應著一個『字』(Character),最前方為 [CLS],最後方為 [SEP],這是 BERT 所需要的輸入格式。
都轉為 torch.tensor 的型態後,輸入建立好的 "embedding" (在這裡即為 BERT 模型),就可以得到最後的輸出。
除了直接使用 encode() 之外,也可以使用 convert_token_to_ids() 來轉換 —— convert_token_to_ids() 可以讓我們一口氣放入前後文,並使用 [SEP] 符號隔開。
看個簡單的例子:
# coding: utf-8
import torch
from transformers import AutoTokenizer, AutoModel
from keras.preprocessing.sequence import pad_sequences
# Tokenizer and Bert Model
tokenizer = AutoTokenizer.from_pretrained('bert-base-chinese')
embedding = AutoModel.from_pretrained('bert-base-chinese')
# Preprocess
sent = '今天天氣真 Good。'
sent_token = ['[CLS]'] + tokenizer.tokenize(sent) + ['[SEP]']
sent_token_encode = tokenizer.convert_tokens_to_ids(sent_token)
sent_token_decode = tokenizer.convert_ids_to_tokens(sent_token_encode)
print('sent:', sent)
print('sent_token:', sent_token)
print('encode:', sent_token_encode)
print('decode:', sent_token_decode)
Output:
sent: 今天天氣真 Good。
sent_token: ['[CLS]', '今', '天', '天', '氣', '真', '[UNK]', '。', '[SEP]']
encode: [101, 791, 1921, 1921, 3706, 4696, 100, 511, 102]
decode: ['[CLS]', '今', '天', '天', '氣', '真', '[UNK]', '。', '[SEP]']
除了編碼之外,也可以還原回我們本來輸入的字串。順帶一提,由於我使用的是中文預訓練的模型,故 "Good" 沒有相對應的編號,只能使用 [UNK] 符號遮蔽起來。
Transformers 的基本使用方法就是這樣了,如果想要嘗試更多不同的模型,可以參考以下這個網址:https://huggingface.co/models
不過若是要真實使用 BERT 模型,可能還是需要經過 Fine-tune 來讓 BERT 轉換的 Embedding 更適合你的任務,可能可以將其放在 PyTorch 你建立的模型架構中的第一層一同訓練,大致上架構可以參考:
# coding: utf-8
import torch.nn as nn
from transformers import BertModel
# Bert-BiGRU-Classifier
class BiGRU(nn.Module):
def __init__(self):
super(BiGRU, self).__init__()
self.embedding = BertModel.from_pretrained('bert-base-chinese')
self.gru = nn.GRU(
input_size=768,
hidden_size=768,
dropout=0.3,
num_layers=5,
bidirectional=True,
batch_first=True,
)
self.fc_1 = nn.Linear(768, 1)
self.sigmoid = nn.Sigmoid()
def forward(self, tokens, masks=None):
# BERT
embedded, _ = self.embedding(tokens, attention_mask=masks)
cls_vector = embedded[:, 0, :]
cls_vector = cls_vector.view(-1, 1, 768)
# GRU
_, hidden = self.gru(cls_vector)
hidden = hidden[-1]
# Fully-connected layer
outputs = self.fc_1(hidden.squeeze(0))
outputs = self.sigmoid(outputs).view(-1)
return outputs
References
- https://huggingface.co/transformers/model_doc/bert.html
- https://github.com/huggingface/transformers
- https://huggingface.co/transformers/quickstart.html
- https://towardsdatascience.com/bert-for-dummies-step-by-step-tutorial-fb90890ffe03
您好,非常感谢您的文章,有一个问题想请教一下
假如我定义了模型 model = BiGRU()
请问如何保存和加载模型呢?因为这个模型里面还含有huggingface的预训练模型,所以我觉得保存和加载可能会和传统使用pytorch定义模型的时候不太一样?期待您的回复
您好,很榮幸能回答這個問題。
其實是可以直接使用 torch.save() 將模型儲存起來的,huggingface 所提供的 PyTorch 預訓練模型本身就是 PyTorch 架構的模型;而 Tensorflow 的預訓練模型則是 Tensorflow 架構的模型。
另外,如果有需要儲存特定的模型層(比方說只需要取 BERT 的 Embedding 層),或許可以參考我的另一篇文章: https://clay-atlas.com/blog/2020/06/13/pytorch-cn-note-how-to-extract-model-layer-or-weights/
谢谢您的回复!我仔细看了下您的文章,然后做了一下实验
我在训练的过程中,穿插进行验证集的验证操作,并且使用torch.save(model_copy.state_dict(), output_model_file)保存验证集上score最高的模型。按道理来说,如果我现在先加载这个模型model.load_state_dict(torch.load(output_model_file)),然后再去跑一边训练集,训练集的loss(或者说验证集的分数应该很高才对)应该很低才对,可是却和没加载模型是一样的效果(初始时的训练集loss很高,验证集的score很低)
期待您的回复
我在huggingface transformers github页面中提了个issue,您可以看一下,里面有我具体运行的效果图
https://github.com/huggingface/transformers/issues/7849
您好。
有點不太確定問題發生的原因,因為程式碼看起來沒有問題。
是否考慮將模型完整儲存下來測試一下呢?就是只使用 save() 和 load() 來完整儲存、讀取模型,而非只儲存權重。
哦哦,我再去试试,感谢!
不會,很遺憾沒能解決您的問題。