BERT模型使用及一個問題


  關於BERT模型的調用,這幾天基本上是摸得比較清楚了。

  模型源碼在github,該項目的Readme.md文件中提供了9個模型的下載鏈接。前兩個是區分大小寫的英文模型,第三個是中文模型,4589沒有用過具體不太清楚,六七是不區分大小寫的英文模型(根據Readme.md中的描述,如果對大小寫不是很敏感的話用uncased已經完全足夠了,但是我覺得像GEC這種任務應該對大小寫還是相當敏感的)。文件夾名稱中的"bert_"前綴是我自己添加以區分其他模型的。

  下載好所有模型分別解壓即為上圖所得,每個文件夾中的文件命名與數量都是相同的,

  一共五個文件,bert_config.json是配置文件,vocab.txt是對應模型使用的token集合,其他三個ckpt文件即為模型。

  調用分兩步,第一步先把文本轉化為BERT模型輸入的形式(一共三個輸入參數:input_ids,input_mask,token_type_ids),然后調用模型使用即可得到文本的向量表示,如下所示👇

  # -*- coding: UTF-8 -*-

  # Author: 囚生

  # 調用BERT模型的工具函數

  import os

  import tensorflow as tf

  from bert import modeling,tokenization

  def text2input(text,tokenizer, # 接收三個參數: 超參數, 文本, 分詞器

  maxlen=100, # 文本token最大數

  return_tensor=True, # 是否返回tensor類型的結果: 否則返回list類型

  ): # 將文本轉化為BERT輸入

  tokens = tokenizer.tokenize(text) # 分詞器分詞

  if len(tokens)>maxlen-2: tokens = tokens[:maxlen-2] # 注意tokens的數量不能超過maxlen-2, 因為頭尾還需要加句首與分句標志

  tokens_bert = ["[CLS]"] # 存放token的列表: 置入句首標志

  token_type_ids = [0] # 標識token屬句類別的列表: 置入句首標志的標識

  for token in tokens: # 添加token與對應標識

  tokens_bert.append(token) # 添加token

  token_type_ids.append(0) # 這個表示一般用0,1,2,...表示是第幾句話, 該函數一般只接收一個句子, 因此都是0

  tokens_bert.append("[SEP]") # 置入分句標志

  token_type_ids.append(0) # 置入分句標志的標識

  input_ids = tokenizer.convert_tokens_to_ids(tokens_bert) # 將tokens轉化為input_ids

  input_mask = [1]*len(input_ids) # 設置蒙布

  while len(input_ids)

  input_ids.append(0) # 零填充

  input_mask.append(0) # 零填充

  token_type_ids.append(0) # 零填充

  if return_tensor: # 若返回tensor類型

  input_ids = tf.convert_to_tensor([input_ids],dtype=tf.int32,name="input_ids")

  input_mask = tf.convert_to_tensor([input_mask],dtype=tf.int32,name="input_mask")

  token_type_ids = tf.convert_to_tensor([token_type_ids],dtype=tf.int32,name="token_type_ids")

  return input_ids,input_mask,token_type_ids # 返回BERT輸入的三個參數

  def load_model(input_ids,input_mask,token_type_ids,cpath,mpath,

  ): # 模型載入

  config = modeling.BertConfig.from_json_file(cpath) # 載入配置文件

  config_session = tf.ConfigProto() # 創建對象配置session運行參數

  config_session.gpu_options.allow_growth = True # 動態申請顯存

  with tf.Session(config=config_session).as_default() as session:

  model = modeling.BertModel( # 載入模型

  config=config, # BERT配置信息

  is_training=True, # 訓練模式

  input_ids=input_ids, # 輸入參數: 輸入token的索引

  input_mask=input_mask, # 輸入參數: 蒙布

  token_type_ids=token_type_ids, # 輸入參數:

  use_one_hot_embeddings=False, # 不使用one-hot編碼

  )

  saver = tf.train.Saver() # 訓練保存器

  session.run(tf.global_variables_initializer()) # 先初始化, 再加載參數,否則會把BERT的參數重新初始化

  saver.restore(session,mpath) # 保存模型到ckpt文件

  sequence_output = model.get_sequence_output() # 獲取每個token的輸出: shape(batch_size,sequence_length,embedding_size)

  pooled_output = model.get_pooled_output() # 獲取每個分句的輸出: shape(batch_size,embedding_size)

  layers = model.all_encoder_layers # 獲取所有層的輸出: shape(batch_size,sequence_length,embedding_size)

  embedding_output = model.get_embedding_output()

  embedding_table = model.get_embedding_table()

  '''

  with tf.Session() as session:

  session.run(tf.global_variables_initializer())

  sequence_output = session.run(sequence_output)

  pooled_output = session.run(pooled_output)

  embedding_output = session.run(embedding_output)

  embedding_table = session.run(embedding_table)

  print("sequence_output: {}".format(sequence_output.shape)) # (1,32,768)

  print("pooled_output: {}".format(pooled_output.shape)) # (1,768)

  print("embedding_output: {}".format(embedding_output.shape)) # (1,32,768)

  print("embedding_table: {}".format(embedding_table.shape)) # (28996,768)

  for layer in layers:

  print(layer.shape) # (1,32,768)

  '''

  return layers,embedding_output,pooled_output,embedding_table

  if __name__ == "__main__":

  # 以下4個路徑變量請根據自己的實際情況修改

  root = "otherdata/model/bert_cased_L-12_H-768_A-12"

  vpath = os.path.join(root,"vocab.txt") # 詞匯表文件

  cpath = os.path.join(root,"bert_config.json")

  mpath = os.path.join(root,"bert_model.ckpt") # 這個文件其實不存在, 但是就得這么寫, 我也不知道為什么

  tokenizer = tokenization.FullTokenizer(vpath) # 這個老版本中可能是CharTokenizer類, 目前源碼中不存在該類了

  text = "This will , if not already , cause problems as there is very limited space for us ."

  input_ids,input_mask,token_type_ids = text2input(hp,text,tokenizer,32)

  load_model(input_ids,input_mask,token_type_ids,cpath,mpath)

  注意代碼中調用到的bert庫,既可以直接從https://github.com/google-research/bert下載后放到同一目錄下,因為該項目本身就是bert庫的源碼,也可以直接pip install bert,其也是從github上下載源碼。

  上述代碼中的text2input是針對單句文字的,多句文字可以參考其他人的博客,因為我自己查下來關於BERT模型調用還是有不少人在詳細寫過的。

  我特別說一下模型輸出的問題,通過查看modeling.py中的源碼,可以看到BertModel類中定義了5個實例函數:

  sequence_output = model.get_sequence_output() # 獲取每個token的輸出: shape(batch_size,sequence_length,embedding_size)

  pooled_output = model.get_pooled_output() # 獲取每個分句的輸出: shape()

  layers = model.all_encoder_layers # 獲取所有層的輸出: shape()

  embedding_output = model.get_embedding_output()

  embedding_table = model.get_embedding_table()

  第三個輸出是12層encoders輸出的列表,該列表中最后一個元素即為sequence_output,其余不多做解釋。

  我主要說一下在實際使用中的問題,當我在需要多次調用模型對多個句子進行模型輸出時,出現了如下的報錯:

  NotFoundError (see above for traceback): Key bert_1/embeddings/LayerNorm/beta not found in checkpoint

  [[Node: save_1/RestoreV2 = RestoreV2[dtypes=[DT_FLOAT, DT_FLOAT, DT_FLOAT, DT_FLOAT, DT_FLOAT, ..., DT_FLOAT, DT_FLOAT, DT_FLOAT, DT_FLOAT, DT_FLOAT], _device="/job:localhost/replica:0/task:0/device:CPU:0"](_arg_save_1/Const_0_0, save_1/RestoreV2/tensor_names, save_1/RestoreV2/shape_and_slices)]]

  [[Node: save_1/RestoreV2/_307 = _Recv[client_terminated=false, recv_device="/job:localhost/replica:0/task:0/device:GPU:0", send_device="/job:localhost/replica:0/task:0/device:CPU:0", send_device_incarnation=1, tensor_name="edge_312_save_1/RestoreV2", tensor_type=DT_FLOAT, _device="/job:localhost/replica:0/task:0/device:GPU:0"]()]]

  事實上把上述模型調用的代碼僅需把最后一行復制一遍(即調用兩次模型),就會如上報錯

  if __name__ == "__main__":

  # 以下4個路徑變量請根據自己的實際情況修改

  root = "otherdata/model/bert_cased_L-12_H-768_A-12"

  vpath = os.path.join(root,"vocab.txt") # 詞匯表文件

  cpath = os.path.join(root,"bert_config.json")

  mpath = os.path.join(root,"bert_model.ckpt") # 這個文件其實不存在, 但是就得這么寫, 我也不知道為什么

  tokenizer = tokenization.FullTokenizer(vpath) # 這個老版本中可能是CharTokenizer類, 目前源碼中不存在該類了

  text = "This will , if not already , cause problems as there is very limited space for us ."

  input_ids,input_mask,token_type_ids = text2input(hp,text,tokenizer,32)

  load_model(input_ids,input_mask,token_type_ids,cpath,mpath)

  load_model(input_ids,input_mask,token_type_ids,cpath,mpath) # 調用兩次

  這個問題確實很讓人困擾,只能調用一次的模型未免太雞肋了。

  通過檢查排錯,最后找到了問題所在:

  在30行處添加tf.reset_default_graph(),作用是清除默認圖的堆棧,並設置全局圖為默認圖,

  # -*- coding: UTF-8 -*-

  # Author: 囚生

  # 調用BERT模型的工具函數

  import os

  import tensorflow as tf

  from bert import modeling,tokenization

  def text2input(text,tokenizer, # 接收三個參數: 超參數, 文本, 分詞器

  maxlen=100, # 文本token最大數

  return_tensor=True, # 是否返回tensor類型的結果: 否則返回list類型

  ): # 將文本轉化為BERT輸入

  tokens = tokenizer.tokenize(text) # 分詞器分詞

  if len(tokens)>maxlen-2: tokens = tokens[:maxlen-2] # 注意tokens的數量不能超過maxlen-2, 因為頭尾還需要加句首與分句標志

  tokens_bert = ["[CLS]"] # 存放token的列表: 置入句首標志

  token_type_ids = [0] # 標識token屬句類別的列表: 置入句首標志的標識

  for token in tokens: # 添加token與對應標識

  tokens_bert.append(token) # 添加token

  token_type_ids.append(0) # 這個表示一般用0,1,2,...表示是第幾句話, 該函數一般只接收一個句子, 因此都是0

  tokens_bert.append("[SEP]") # 置入分句標志

  token_type_ids.append(0) # 置入分句標志的標識

  input_ids = tokenizer.convert_tokens_to_ids(tokens_bert) # 將tokens轉化為input_ids

  input_mask = [1]*len(input_ids) # 設置蒙布

  while len(input_ids)

  input_ids.append(0) # 零填充

  input_mask.append(0) # 零填充

  token_type_ids.append(0) # 零填充

  if return_tensor: # 若返回tensor類型

  tf.reset_default_graph()

  input_ids = tf.convert_to_tensor([input_ids],dtype=tf.int32,name="input_ids")

  input_mask = tf.convert_to_tensor([input_mask],dtype=tf.int32,name="input_mask")

  token_type_ids = tf.convert_to_tensor([token_type_ids],dtype=tf.int32,name="token_type_ids")

  return input_ids,input_mask,token_type_ids # 返回BERT輸入的三個參數

  def load_model(input_ids,input_mask,token_type_ids,cpath,mpath,

  ): # 模型載入鄭州婦科醫院哪家好 http://www.sptdfk.com/

  config = modeling.BertConfig.from_json_file(cpath) # 載入配置文件

  config_session = tf.ConfigProto() # 創建對象配置session運行參數

  config_session.gpu_options.allow_growth = True # 動態申請顯存

  with tf.Session(config=config_session).as_default() as session:

  model = modeling.BertModel( # 載入模型

  config=config, # BERT配置信息

  is_training=True, # 訓練模式

  input_ids=input_ids, # 輸入參數: 輸入token的索引

  input_mask=input_mask, # 輸入參數: 蒙布

  token_type_ids=token_type_ids, # 輸入參數:

  use_one_hot_embeddings=False, # 不使用one-hot編碼

  )

  saver = tf.train.Saver() # 訓練保存器

  session.run(tf.global_variables_initializer()) # 先初始化, 再加載參數,否則會把BERT的參數重新初始化

  saver.restore(session,mpath) # 保存模型到ckpt文件

  sequence_output = model.get_sequence_output() # 獲取每個token的輸出: shape(batch_size,sequence_length,embedding_size)

  pooled_output = model.get_pooled_output() # 獲取每個分句的輸出: shape(batch_size,embedding_size)

  layers = model.all_encoder_layers # 獲取所有層的輸出: shape(batch_size,sequence_length,embedding_size)

  embedding_output = model.get_embedding_output()

  embedding_table = model.get_embedding_table()

  '''

  with tf.Session() as session:

  session.run(tf.global_variables_initializer())

  sequence_output = session.run(sequence_output)

  pooled_output = session.run(pooled_output)

  embedding_output = session.run(embedding_output)

  embedding_table = session.run(embedding_table)

  print("sequence_output: {}".format(sequence_output.shape)) # (1,32,768)

  print("pooled_output: {}".format(pooled_output.shape)) # (1,768)

  print("embedding_output: {}".format(embedding_output.shape)) # (1,32,768)

  print("embedding_table: {}".format(embedding_table.shape)) # (28996,768)

  for layer in layers:

  print(layer.shape) # (1,32,768)

  '''

  return layers,embedding_output,pooled_output,embedding_table

  if __name__ == "__main__":

  # 以下4個路徑變量請根據自己的實際情況修改

  root = "otherdata/model/bert_cased_L-12_H-768_A-12"

  vpath = os.path.join(root,"vocab.txt") # 詞匯表文件

  cpath = os.path.join(root,"bert_config.json")

  mpath = os.path.join(root,"bert_model.ckpt") # 這個文件其實不存在, 但是就得這么寫, 我也不知道為什么

  tokenizer = tokenization.FullTokenizer(vpath) # 這個老版本中可能是CharTokenizer類, 目前源碼中不存在該類了

  text = "This will , if not already , cause problems as there is very limited space for us ."

  input_ids,input_mask,token_type_ids = text2input(hp,text,tokenizer,32)

  load_model(input_ids,input_mask,token_type_ids,cpath,mpath)

  load_model(input_ids,input_mask,token_type_ids,cpath,mpath) # 調用兩次

  總之tensorflow用起來確實挺麻煩,我使用的版本是tensorflow-gpu v1.9.0,因為gpu版本再高一點的我的顯卡不支持,而我又不想卸了用cpu版本的了,建議如果使用tensorflow的話要么就用2.0版本以后的,要么就用v1.12.0這個長期更新的版本。總之tensorflow不同版本間的差異確實很大,別人的代碼很多時候不改都是跑不了的。

  可能pytorch還是好一點吧,可惜現在主流的幾個模型transformer,bert源碼都是用的tensorflow架構的,也沒什么辦法了。


免責聲明!

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



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