Pytorch-Bert預訓練模型的使用(調用transformers)


筆記摘抄

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/下。

提前導包:

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)   # 一 個 殺 手

筆記摘抄


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM