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文件夾下,此例中該文件夾放在E:\transformer_file\下。
提前導包:
1 import numpy as np 2 import torch 3 from transformers import BertTokenizer, BertConfig, BertForMaskedLM, BertForNextSentencePrediction 4 from transformers import BertModel 5 6 model_name = 'bert-base-chinese' 7 MODEL_PATH = 'E:/transformer_file/bert-base-chinese/' 8 9 # a.通過詞典導入分詞器 10 tokenizer = BertTokenizer.from_pretrained(model_name) 11 # b. 導入配置文件 12 model_config = BertConfig.from_pretrained(model_name) 13 # 修改配置 14 model_config.output_hidden_states = True 15 model_config.output_attentions = True 16 # 通過配置和路徑導入模型 17 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操作
1 print(tokenizer.encode('我不喜歡你')) #[101, 2769, 679, 1599, 3614, 872, 102] 2 sen_code = tokenizer.encode_plus('我不喜歡這世界','我只喜歡你') 3 print(sen_code) 4 # {'input_ids': [101, 2769, 679, 1599, 3614, 6821, 686, 4518, 102, 2769, 1372, 1599, 3614, 872, 102], 5 # 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1], 6 # 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
將input_ids轉化回token:
1 print(tokenizer.convert_ids_to_tokens(sen_code['input_ids'])) 2 #['[CLS]', '我', '不', '喜', '歡', '這', '世', '界', '[SEP]', '我', '只', '喜', '歡', '你', '[SEP]']
將分詞輸入模型,得到編碼:
1 #對編碼進行轉換,以便輸入Tensor 2 tokens_tensor = torch.tensor([sen_code['input_ids']]) # 添加batch維度並轉化為tensor 3 segments_tensors = torch.tensor([sen_code['token_type_ids']]) 4 5 bert_model.eval() 6 7 #進行編碼 8 with torch.no_grad(): 9 10 outputs = bert_model(tokens_tensor, token_type_ids=segments_tensors) 11 encoded_layers = outputs #outputs類型為tuples
Bert最終輸出的結果維度為:sequence_output, pooled_output, (hidden_states), (attentions)
以輸入序列為14為例:
- sequence_output:torch.Size([1, 14, 768]) 輸出序列
- pooled_output:torch.Size([1, 768]) 對輸出序列進行pool操作的結果
- (hidden_states):tuple, 13*torch.Size([1, 14, 768]) 隱藏層狀態(包括Embedding層),取決於model_config中的output_hidden_states
- (attentions):tuple, 12*torch.Size([1, 12, 14, 14]) 注意力層,取決於model_config中的output_attentions
1.遮蔽語言模型
BERT以訓練遮蔽語言模型(Masked Language Model)作為預訓練目標,具體來說就是把輸入的語句中的字詞隨機用 [MASK] 標簽覆蓋,然后訓練模型結合被覆蓋的詞的左側和右側上下文進行預測。可以看出,BERT 的做法與從左向右語言模型只通過左側語句預測下一個詞的做法相比,遮蔽語言模型能夠生成同時融合了左、右上下文的語言表示。這種做法能夠使 BERT 學到字詞更完整的語義表示。
1 model_name = 'bert-base-chinese' #指定需下載的預訓練模型參數 2 3 #任務一:遮蔽語言模型 4 # BERT 在預訓練中引入了 [CLS] 和 [SEP] 標記句子的開頭和結尾 5 samples = ['[CLS] 中國的首都是哪里? [SEP] 北京是 [MASK] 國的首都。 [SEP]'] # 准備輸入模型的語句 6 7 tokenizer = BertTokenizer.from_pretrained(model_name) 8 tokenized_text = [tokenizer.tokenize(i) for i in samples] #將句子分割成一個個token,即一個個漢字和分隔符 9 input_ids = [tokenizer.convert_tokens_to_ids(i) for i in tokenized_text] #把每個token轉換成對應的索引 10 input_ids = torch.LongTensor(input_ids) 11 12 # 讀取預訓練模型 13 model = BertForMaskedLM.from_pretrained(model_name, cache_dir="E:/transformer_file/") 14 model.eval() 15 16 outputs = model(input_ids) 17 prediction_scores = outputs[0] #prediction_scores.shape=torch.Size([1, 21, 21128]) 18 sample = prediction_scores[0].detach().numpy() #sample.shape = (21, 21128) 19 20 pred = np.argmax(sample, axis=1) #21為序列長度,pred代表每個位置最大概率的字符索引 21 print(tokenizer.convert_ids_to_tokens(pred)[14]) #被標記的[MASK]是第14個位置
中
2.句子預測任務
預訓練 BERT 時除了 MLM 預訓練策略,還要進行預測下一個句子的任務。句子預測任務基於理解兩個句子間的關系,這種關系無法直接被 Masked Language Model 捕捉到。
1 sen_code1 = tokenizer.encode_plus('今天天氣怎么樣','今天天氣很好') 2 sen_code2 = tokenizer.encode_plus('小明今年幾歲了','我不喜歡學習') 3 4 tokens_tensor = torch.tensor([sen_code1['input_ids'], sen_code2['input_ids']]) 5 6 #讀取預訓練模型 7 model = BertForNextSentencePrediction.from_pretrained(model_name, cache_dir="E:/transformer_file/") 8 model.eval() 9 10 outputs = model(tokens_tensor) 11 seq_relationship_scores = outputs[0] #seq_relationship_scores.shape= torch.Size([2, 2]) 12 sample = seq_relationship_scores.detach().numpy() #sample.shape = (2, 2) 13 14 pred = np.argmax(sample, axis=1) 15 print(pred)
[0 0] 0表示是上下句,1表示不是上下句(第二句明明不是前后句關系,不知道為什么會輸出0。)
前面三行代碼等價於:
1 samples = ["[CLS]今天天氣怎么樣[SEP]今天天氣很好[SEP]", "[CLS]小明今年幾歲了[SEP]我不喜歡學習[SEP]"] 2 tokenizer = BertTokenizer.from_pretrained(model_name) 3 tokenized_text = [tokenizer.tokenize(i) for i in samples] 4 input_ids = [tokenizer.convert_tokens_to_ids(i) for i in tokenized_text] 5 tokens_tensor = torch.LongTensor(input_ids)
3.問答任務
任務輸入:問題句“里昂是誰”,答案所在文章“里昂是一個殺手”
任務輸出:一對整數,表示答案在文本中的開頭和結束位置
1 model_name = 'bert-base-chinese' 2 3 # 通過詞典導入分詞器 4 tokenizer = BertTokenizer.from_pretrained(model_name) 5 # 導入配置文件 6 model_config = BertConfig.from_pretrained(model_name) 7 # 最終有兩個輸出,初始位置和結束位置 8 model_config.num_labels = 2 9 10 # 根據bert的model_config新建BertForQuestionAnswering 11 model = BertForQuestionAnswering(model_config) 12 model.eval() 13 14 question, text = "里昂是誰", "里昂是一個殺手" 15 16 sen_code = tokenizer.encode_plus(question,text) 17 18 tokens_tensor = torch.tensor([sen_code['input_ids']]) 19 segments_tensor = torch.tensor([sen_code['token_type_ids']]) 20 21 start_pos, end_pos = model(tokens_tensor, segments_tensor) 22 # 進行逆編碼,得到原始的token 23 all_tokens = tokenizer.convert_ids_to_tokens(sen_code['input_ids']) 24 print(all_tokens) #['[CLS]', '里', '昂', '是', '誰', '[SEP]', '里', '昂', '是', '一', '個', '殺', '手', '[SEP]'] 25 26 # 對輸出的答案進行解碼的過程 27 answer = ' '.join(all_tokens[torch.argmax(start_pos) : torch.argmax(end_pos)+1]) 28 29 # 每次執行的結果不一致,這里因為沒有經過微調,所以效果不是很好,輸出結果不佳,下面的輸出是其中的一種。 30 print(answer) #一 個 殺 手 [SEP]