1. transformer資料
transformers(以前稱為pytorch-transformers和pytorch-pretrained-bert)
-
提供用於自然語言理解(NLU)和自然語言生成(NLG)的BERT家族通用結構(BERT,GPT-2,RoBERTa,XLM,DistilBert,XLNet等),包含超過32種、涵蓋100多種語言的預訓練模型。
-
首先下載transformers包,
pip install transformers
-
其次手動下載模型(直接
from transformers import BertModel
會從官方的s3數據庫下載模型配置、參數等信息,在國內並不可用)- 下載bert-base-chinese的config.josn,vocab.txt,pytorch_model.bin三個文件后,放在bert-base-chinese文件夾下,此例中該文件夾放在
F:/Transformer-Bert/
下。
- 下載bert-base-chinese的config.josn,vocab.txt,pytorch_model.bin三個文件后,放在bert-base-chinese文件夾下,此例中該文件夾放在
提前導包:
import numpy as np
import torch
from transformers import BertTokenizer, BertConfig, BertForMaskedLM, BertForNextSentencePrediction
from transformers import BertModel
model_name = 'bert-base-chinese'
MODEL_PATH = 'F:/Transformer-Bert/bert-base-chinese/'
# a. 通過詞典導入分詞器
tokenizer = BertTokenizer.from_pretrained(model_name)
# b. 導入配置文件
model_config = BertConfig.from_pretrained(model_name)
# 修改配置
model_config.output_hidden_states = True
model_config.output_attentions = True
# 通過配置和路徑導入模型
bert_model = BertModel.from_pretrained(MODEL_PATH, config = model_config)
利用分詞器進行編碼:
-
encode僅返回input_ids
-
encode_plus返回所有編碼信息
-
input_ids
:是單詞在詞典中的編碼 -
token_type_ids
:區分兩個句子的編碼(上句全為0,下句全為1) -
attention_mask
:指定 對哪些詞 進行self-Attention操作
-
print(tokenizer.encode('吾兒莫慌')) # [101, 1434, 1036, 5811, 2707, 102]
sen_code = tokenizer.encode_plus('這個故事沒有終點', "正如星空沒有彼岸")
# print(sen_code)
# [101, 1434, 1036, 5811, 2707, 102]
# {'input_ids': [101, 6821, 702, 3125, 752, 3766, 3300, 5303, 4157, 102, 3633, 1963, 3215, 4958, 3766, 3300, 2516, 2279, 102],
# 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1],
# 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
將input_ids轉化回token:
print(tokenizer.convert_ids_to_tokens(sen_code['input_ids']))
# ['[CLS]', '這', '個', '故', '事', '沒', '有', '終', '點', '[SEP]', '正', '如', '星', '空', '沒', '有', '彼', '岸', '[SEP]']
將分詞輸入模型,得到編碼:
# 對編碼進行轉換,以便輸入Tensor
tokens_tensor = torch.tensor([sen_code['input_ids']]) # 添加batch維度並,轉換為tensor,torch.Size([1, 19])
segments_tensors = torch.tensor(sen_code['token_type_ids']) # torch.Size([19])
bert_model.eval()
# 進行編碼
with torch.no_grad():
outputs = bert_model(tokens_tensor, token_type_ids = segments_tensors)
encoded_layers = outputs # outputs類型為tuple
print(encoded_layers[0].shape, encoded_layers[1].shape,
encoded_layers[2][0].shape, encoded_layers[3][0].shape)
# torch.Size([1, 19, 768]) torch.Size([1, 768])
# torch.Size([1, 19, 768]) torch.Size([1, 12, 19, 19])
Bert最終輸出的結果維度為:sequence_output, pooled_output, (hidden_states), (attentions)
以輸入序列為19為例:
-
sequence_output:torch.Size([1, 19, 768])
- 輸出序列
-
pooled_output:torch.Size([1, 768])
- 對輸出序列進行pool操作的結果
-
(hidden_states):tuple, 13 * torch.Size([1, 19, 768])
- 隱藏層狀態(包括Embedding層),取決於 model_config 中的 output_hidden_states
-
(attentions):tuple, 12 * torch.Size([1, 12, 19, 19])
- 注意力層,取決於
model_config
中的output_attentions
- 注意力層,取決於
2. 遮蔽語言模型 Masked Language Model
BERT以訓練遮蔽語言模型(Masked Language Model)作為預訓練目標。
-
具體來說就是把 輸入的語句中的字詞 隨機用 [MASK] 標簽覆蓋,然后 訓練模型 結合被覆蓋的詞的 左側 和 右側上下文進行預測。
-
可以看出,BERT 的做法 與 從左向右語言模型只通過左側語句預測下一個詞的做法相比,遮蔽語言模型 能夠生成同時融合了左、右上下文的語言表示。
-
這種做法能夠使 BERT 學到字詞更完整的語義表示。

model_name = 'bert-base-chinese' # 指定需下載的預訓練模型參數
# 任務一:遮蔽語言模型
# BERT 在預訓練中引入 [CLS] 和 [SEP] 標記句子的 開頭和結尾
samples = ['[CLS] 中國的首都是哪里? [SEP] 北京是 [MASK] 國的首都。 [SEP]'] # 准備輸入模型的語句
tokenizer = BertTokenizer.from_pretrained(model_name)
tokenizer_text = [tokenizer.tokenize(i) for i in samples] # 將句子分割成一個個token,即一個個漢字和分隔符
# [['[CLS]', '中', '國', '的', '首', '都', '是', '哪', '里', '?', '[SEP]', '北', '京', '是', '[MASK]', '國', '的', '首', '都', '。', '[SEP]']]
# print(tokenizer_text)
input_ids = [tokenizer.convert_tokens_to_ids(i) for i in tokenizer_text]
input_ids = torch.LongTensor(input_ids)
# print(input_ids)
# tensor([[ 101, 704, 1744, 4638, 7674, 6963, 3221, 1525, 7027, 8043, 102, 1266,
# 776, 3221, 103, 1744, 4638, 7674, 6963, 511, 102]])
# 讀取預訓練模型
model = BertForMaskedLM.from_pretrained(model_name, cache_dir = 'F:/Transformer-Bert/')
model.eval()
outputs = model(input_ids)
prediction_scores = outputs[0] # prediction_scores.shape=torch.Size([1, 21, 21128])
sample = prediction_scores[0].detach().numpy() # (21, 21128)
# 21為序列長度,pred代表每個位置最大概率的字符索引
pred = np.argmax(sample, axis=1) # (21,)
# [',', '中', '國', '的', '首', '都', '是', '哪', '里', '?', '。', '北', '京', '是', '中', '國', '的', '首', '都', '。', '。']
print(tokenizer.convert_ids_to_tokens(pred))
print(tokenizer.convert_ids_to_tokens(pred)[14]) # 被標記的[MASK]是第14個位置, 中
3. 句子預測任務
-
預訓練 BERT 時除了 MLM 預訓練策略,還要進行預測下一個句子的任務。
-
句子預測任務基於理解兩個句子間的關系,這種關系無法直接被 Masked Language Model 捕捉到。

# sen_code1 = tokenizer.encode_plus('今天天氣怎么樣?', '今天天氣很好!')
# sen_code2 = tokenizer.encode_plus('明明是我先來的!', '我喜歡吃西瓜!')
# tokens_tensor = torch.tensor([sen_code1['input_ids'], sen_code2['input_ids']])
# print(tokens_tensor)
# tensor([[ 101, 791, 1921, 1921, 3698, 2582, 720, 3416, 102, 791, 1921, 1921,
# 3698, 2523, 1962, 102],
# [ 101, 3209, 3209, 3221, 2769, 1044, 3341, 4638, 102, 7471, 3449, 679,
# 1963, 1921, 7360, 102]])
# 上面可以換成
samples = ["[CLS]天氣真的好啊![SEP]一起出去玩吧![SEP]", "[CLS]小明今年幾歲了[SEP]我不喜歡學習![SEP]"]
tokenizer = BertTokenizer.from_pretrained(model_name)
tokenized_text = [tokenizer.tokenize(i) for i in samples]
input_ids = [tokenizer.convert_tokens_to_ids(i) for i in tokenized_text]
tokens_tensor = torch.LongTensor(input_ids)
# 讀取預訓練模型
model = BertForNextSentencePrediction.from_pretrained(model_name, cache_dir='F:/Transformer-Bert/')
model.eval()
outputs = model(tokens_tensor)
# sequence_output:輸出序列
seq_relationship_scores = outputs[0] # seq_relationship_scores.shape: torch.Size([2, 2])
sample = seq_relationship_scores.detach().numpy() # sample.shape: [2, 2]
pred = np.argmax(sample, axis=1)
print(pred) # [0 0], 0表示是上下句關系,1表示不是上下句關系
4. 問答任務

任務輸入:問題句“里昂是誰”,答案所在文章“里昂是一個殺手”
任務輸出:一對整數,表示答案在文本中的開頭和結束位置
model_name = 'bert-base-chinese'
# 通過詞典導入分詞器
tokenizer = BertTokenizer.from_pretrained(model_name)
# 導入配置文件
model_config = BertConfig.from_pretrained(model_name)
# 最終有兩個輸出,初始位置和結束位置
model_config.num_labels = 2
# 根據bert的 model_config 新建 BertForQuestionAnsering
model = BertForQuestionAnswering(model_config)
model.eval()
question, text = '里昂是誰?', '里昂是一個殺手。'
sen_code = tokenizer.encode_plus(question, text)
tokens_tensor = torch.tensor([sen_code['input_ids']])
segments_tensors = torch.tensor([sen_code['token_type_ids']]) # 區分兩個句子的編碼(上句全為0,下句全為1)
start_pos, end_pos = model(tokens_tensor, token_type_ids = segments_tensors)
# 進行逆編碼,得到原始的token
all_tokens = tokenizer.convert_ids_to_tokens(sen_code['input_ids'])
print(all_tokens) # ['[CLS]', '里', '昂', '是', '誰', '[SEP]', '里', '昂', '是', '一', '個', '殺', '手', '[SEP]']
# 對輸出的答案進行解碼的過程
answer = ' '.join(all_tokens[torch.argmax(start_pos) : torch.argmax(end_pos) + 1])
# 每次執行的結果不一致,這里因為沒有經過微調,所以效果不是很好,輸出結果不佳,下面的輸出是其中的一種。
print(answer) # 一 個 殺 手