用 Bert 做克漏字練習

為什麼要用 Bert?

Jenny Ching
7 min readJun 1, 2020

Bert 的訓練過程,是透過學會填克漏字和預測下一句兩個任務,因此讓 BERT 知道每個字在不同語境下有不同的 representation,而 NSP 任務則能幫助 BERT model 兩個句子之間的關係,這在問答系統 QA自然語言推論 NLI 或是新聞分類任務都很有幫助。

這樣的 word representation 就是 contextual word representation 的概念。跟以往沒有蘊含上下文資訊的 Word2Vec、GloVe 等無語境的 word vector 有很大的差異。Word2vec 的 word vector 無論上下文,都會讓同個字的 representation 相同。但 Bert 最大的優勢就是可以讓同個字的 word token 在不同語境下有不同的 word vector。

用 Hugging Face 現成 Framework

我們要用的是 BertTokenizer 和 BertForMaskedLM (doc),來自 PyTorchHub 的 pretained model,用 from_pretrained() 就可以從 PyTorchHub 上把 model 下載下來到指定的 model_path。因為我們這邊只是用來預測克漏字,所以並不需要實際訓練模型(下一篇有機會再講怎麼用 transfer learning finetune Bert),所以直接用 model.eval() 切換到 evaluate 的模式即可:

def init_model(model_path,to_lower):
tokenizer = BertTokenizer.from_pretrained(model_path)
model = BertForMaskedLM.from_pretrained(model_path)
model.eval()
return model,tokenizer

取得 Bert model 和 Bert tokenizer 之後,我們就可以用它們來預測克漏字了。首先給 Bert 一個完整的句子 text ,和你想挖空的是哪個字 masked_index。用 Bert tokenizer 先 tokenize 再把 tokens 轉成 id(也就是每個字在 Bert vocab 中的 index),而要挖空的字則是用 [MASK] 取代掉,這樣 Bert 就知道你想要預測這個字了:

import torch
from transformers import *
import operator
from collections import OrderedDict
def perdict_masked(model, tokenizer,top_k,accrue_threshold,text, masked_index):
tokenized_text = tokenizer.tokenize(text)
indexed_tokens = tokenizer.convert_tokens_to_ids(tokenized_text)
# Create the segments tensors.
segments_ids = [0] * len(tokenized_text)
tokenized_text[masked_index] = “[MASK]”
indexed_tokens[masked_index] = 103
print(tokenized_text)
print(masked_index)
results_dict = {}
# Convert inputs to PyTorch tensors
tokens_tensor = torch.tensor([indexed_tokens])
segments_tensors = torch.tensor([segments_ids])
with torch.no_grad():
predictions = model(tokens_tensor, segments_tensors)
for i in range(len(predictions[0][0,masked_index])):
if (float(predictions[0][0,masked_index][i].tolist()) > accrue_threshold):
tok = tokenizer.convert_ids_to_tokens([i])[0]
results_dict[tok] = float(predictions[0][0,masked_index][i].tolist())
k = 0
sorted_d = OrderedDict(sorted(results_dict.items(), key=lambda kv: kv[1], reverse=True))
for i in sorted_d:
print(i,sorted_d[i])
k += 1
if (k > top_k):
break

Demo 時間

舉例來說,given 一個短句“The capital of Italy is Rome”,我想把 Rome 挖空讓 Bert 來填,首先用 init_model 來下載 pretrained Bert model 和 tokenizer,在用 perdict_masked 來列出 Bert 認為最符合這個填空的字:

DEFAULT_MODEL_PATH='bert-base-cased'
DEFAULT_TO_LOWER=False
DEFAULT_TOP_K = 20
ACCRUE_THRESHOLD = 1
if __name__ == ‘__main__’:
model, tokenizer = init_model(DEFAULT_MODEL_PATH,DEFAULT_TO_LOWER)
text = “The capital of Italy is Rome”
masked_index = 5
perdict_masked(model,tokenizer,DEFAULT_TOP_K,ACCRUE_THRESHOLD,text, masked_index)

我們得到的結果是:

[‘The’, ‘capital’, ‘of’, ‘Italy’, ‘is’, ‘[MASK]’]
Rome 10.017385482788086
Turin 8.92904281616211
Milan 8.327985763549805
Italy 8.265679359436035
located 7.860157489776611
Bologna 7.668505668640137
Florence 7.59014892578125
Naples 7.535651683807373
: 7.410454750061035
the 7.194808006286621
Palermo 6.947678565979004
Venice 6.920002460479736
in 6.355855941772461
, 6.338903427124023
Sicily 6.310441017150879
Genoa 6.264047145843506
city 6.168617248535156
Athens 6.162437915802002
Verona 6.003897666931152
Torino 5.917325019836426
Roma 5.6902666091918945

除了克漏字之外(用 Huggingface 的 BertForMaskedLM model),Bert 還可以做很多其他有趣的 task 像是 Q&A(BertForQuestionAnswering)、分類(BertForTokenClassification、BertForSequenceClassification)、填下一句(BertForNextSentencePrediction)、多選題(BertForMultipleChoice)。除了直接用現成的模型,你也可以用 transfer learning 來 finetune Bert,讓 Bert 的預測符合你自己的資料需求。

--

--

No responses yet