BERT:代碼解讀、實體關係抽取實戰

目錄

前言    

一、BERT的主要亮點

1. 雙向Transformers

2.句子級別的應用

3.能夠解決的任務

二、BERT代碼解讀

1. 數據預處理

1.1 InputExample類

1.2 InputFeatures類

1.3 DataProcessor   重點

1.4 convert_single_example

1.5 file_based_convert_examples_to_features

1.6 file_based_input_fn_builder

1.7 _truncate_seq_pair

2. 模型部分

2.1 model_fn_builder

2.2 create_model   重點

3. main主函數

4. 總結

三、Entity-Relation-Extraction-master實戰


BERT代碼:參加另一篇文章《命名實體識別NER & 如何使用BERT實現》

前言    

        BERT模型是谷歌2018年10月底公佈的,它的提出主要是針對word2vec等模型的不足,在之前的預訓練模型(包括word2vec,ELMo等)都會生成詞向量,這種類別的預訓練模型屬於domain transfer。而近一兩年提出的ULMFiT,GPT,BERT等都屬於模型遷移,說白了BERT 模型是將預訓練模型和下游任務模型結合在一起的,核心目的就是:是把下游具體NLP任務的活逐漸移到預訓練產生詞向量上。

基於google公佈的一個源代碼:https://github.com/google-research/bert

將bert寫成了service 的方式:https://github.com/hanxiao/bert-as-service

論文:https://arxiv.org/abs/1810.04805

一篇中文博客:https://www.cnblogs.com/rucwxb/p/10277217.html

一、BERT的主要亮點

1. 雙向Transformers

      BERT真真意義上同時考慮了上下文:

       正如論文中所講,目前的主要限制是當前模型不能同時考慮上下文,像上圖的GPT只是一個從左到右,ELMo雖然有考慮從左到右和從右到左,但是是兩個分開的網絡,只有BERT是真真意義上的同時考慮了上下文。

2.句子級別的應用

     通過使用segment同時考慮了句子級別的預測。

3.能夠解決的任務

       google已經預預訓練好了模型,我們要做的就是根據不同的任務,按照bert的輸入要求(後面會看到)輸入我們的數據,然後獲取輸出,在輸出層加一層(通常情況下)全連接層就OK啦,整個訓練過程就是基於預訓練模型的微調,下述圖片是其可以完成的幾大類任務:

       a、b都是sentence級別的:文本分類,關係抽取等;

       c、d是tokens級別的:如命名實體識別,知識問答等。

二、BERT代碼解讀

BERT的代碼主要分爲兩個部分:

1. 預訓練部分:其入口是在run_pretraining.py。

2. Fine-tune部分:Fine-tune的入口針對不同的任務分別在run_classifier.py和run_squad.py。其中

  • run_classifier.py:適用的任務爲分類任務,如CoLA、MRPC、MultiNLI等。而
  • run_squad.py:適用的是閱讀理解任務,如squad2.0和squad1.1。

     在使用的時候,一般是需要下面三個腳本的,我們也不必修改,直接拿過來使用就ok

  •   modeling.py:模型定義
  •   optimization.py:優化器
  •   tokenization.py

        其中tokenization是對原始句子內容的解析,分爲BasicTokenizer和WordpieceTokenizer兩個,一般來說BasicTokenizer主要是進行unicode轉換、標點符號分割、中文字符分割、去除重音符號等操作,最後返回的是關於詞的數組(中文是字的數組),WordpieceTokenizer的目的是將合成詞分解成類似詞根一樣的詞片。例如將"unwanted"分解成["un", "##want", "##ed"],這麼做的目的是防止因爲詞的過於生僻沒有被收錄進詞典最後只能以[UNK]代替的局面,因爲英語當中這樣的合成詞非常多,詞典不可能全部收錄。FullTokenizer的作用就很顯而易見了,對一個文本段進行以上兩種解析,最後返回詞(字)的數組,同時還提供token到id的索引以及id到token的索引。這裏的token可以理解爲文本段處理過後的最小單元。上述來源https://www.jianshu.com/p/22e462f01d8c,更多該腳本的內容可以看該鏈接,下面主要用到FullTokenizer這個類。

真正需要修改是:

            run_classifier.py
            run_squad.py
分別是解決分類、閱讀理解任務,其實套路差不多,我們具體來看一下run_classifier.py

      首先BERT主要分爲兩個部分。一個是訓練語言模型(language model)的預訓練(run_pretraining.py)部分。另一個是訓練具體任務(task)的fine-tune部分,預訓練部分巨大的運算資源,但是其已經公佈了BERT的預訓練模型

這裏需要中文,直接下載就行,總得來說,我們要做的就是自己的數據集上進行fine-tune。

1. 數據預處理

    run_classifier.py中的類如下:             

                    

1.1 InputExample類

     主要定義了一些數據預處理後要生成的字段名,如下:

                                               

  • guid就是一個id號,一般將數據處理成train、dev、test數據集,那麼這裏定義方式就可以是相應的數據集+行號(句子)
  • text_a 就是當前的句子,text_b是另一個句子,因爲有的任務需要兩個兩個句子,如果任務中沒有的話,可以將text_b設爲None
  • label就是標籤

1.2 InputFeatures類

       主要是定義了bert的輸入格式,形象化點就是特徵,即上面的格式使我們需要將原始數據處理成的格式,但並不是bert使用的最終格式,且還會通過一些代碼將InputExample轉化爲InputFeatures,這纔是bert最終使用的數據格式,當然啦這裏根據自己的需要還可以自定義一些字段作爲中間輔助字段,但bert最基本的輸入字段就需要input_ids,input_mask和segment_ids這三個字段,label_id是計算loss時候用到的:

  • input_ids,segment_ids:分別對應單詞id和句子(上下句標示),input_ids、segment_ids分別代表token、segment。
  • Input_mask:記錄的是填充信息,具體看下面

               

1.3 DataProcessor   重點

      這是一個數據預處理的基類,裏面定義了一些基本方法。

      XnliProcessor、MnliProcessor、MrpcProcessor、ColaProcessor四個類是對DataProcessor的具體實現,這裏之所以列舉了四個是儘可能多的給用戶呈現出各種demo,具體到實際使用的時候我們只需要參考其寫法,定義一個自己的數據預處理類即可,其中一般包括如下幾個方法:

       get_train_examples,get_dev_examples,get_test_examples,get_labels,_create_examples

其中前三個都通過調用_create_examples返回一個InputExample類數據結構,get_labels就是返回類別,所以重點就是以下兩個函數:

      這裏的tokenization的convert_to_unicode就是將文本轉化爲utf-8編碼。

     上述就是數據預處理過程,也是需要我們自己根據自己的數據定義的,其實呢,這並不是Bert使用的最終樣子,其還得經過一系列過程才能變成其能處理的數據格式該過程是通過接下來的四個方法完成的

            convert_single_example:返回一個InputFeatures類
            file_based_convert_examples_to_features
            file_based_input_fn_builder
            truncate_seq_pair
只不過一般情況下我們不需要修改,它都是一個固定的流程。

1.4 convert_single_example

     bert的輸入:

  代碼中的input_ids、segment_ids分別代表token、segment,同時其還在句子的開頭結尾加上了[CLS]和SEP]標示 :

                                    

  • input_ids:記錄的是使用FullTokenizer類convert_tokens_to_ids方法將tokens轉化成單個字的id;
  • segment_ids:就是句子級別(上下句)的標籤,大概形式:

(a) For sequence pairs:
      tokens:   [CLS] is this jack ##son ##ville ? [SEP]  no it is not . [SEP]
      type_ids:    0     0  0     0       0         0      0    0      1  1  1  1   1    1
(b) For single sequences:
     tokens:     [CLS] the dog is hairy . [SEP]
     type_ids:      0     0     0   0    0    0    0

當沒有text_b的時候,就都是0啦

  • input_mask:其就是和最大長度有關,假設我們定義句子的最大長度是120,當前句子長度是100,那麼input_mask前100個元素都是1,其餘20個就是0。

最後返回的就是一個InputFeatures類:

                               

1.5 file_based_convert_examples_to_features

      很簡單啦,因爲在訓練的時候爲了讀寫快速方便,便將數據製作成TFrecords 數據格式,該函數主要就是將上述返回的InputFeatures類數據,保存成一個TFrecords數據格式,關於TFrecords數據格式的製作可以參考:https://blog.csdn.net/weixin_42001089/article/details/90236241

1.6 file_based_input_fn_builder

       對應的就是從TFrecords 解析讀取數據

1.7 _truncate_seq_pair

      就是來限制text_a和text_b總長度的,當超過的話,會輪番pop掉tokens

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

至此整個數據的預處理纔算處理好,其實最後最關鍵的就是得到了那個TFrecords文件。

2. 模型部分

  •  create_model
  •  model_fn_builder

   整個模型過程採用了tf.contrib.tpu.TPUEstimator這一高級封裝的API

    model_fn_builder是殼,create_model是核心,其內部定義了loss,預測概率以及預測結果等等。

2.1 model_fn_builder

       其首先調用create_model得到total_loss、 per_example_loss、logits、 probabilities等:

          

     然後針對不同的狀態返回不同的結果(output_spec):

  • 如果是train,則返回loss、train_op等;
  • 如果是dev,則返回一些評價指標如accuracy;
  • 如果是test,則返回預測結果

      所以我們如果想看一下別的指標什麼的,可以在這裏改 ,需要注意的是指標的定義這裏因爲使用了estimator API使得其必須返回一個operation,至於怎麼定義f1可以看:

             https://www.cnblogs.com/jiangxinyang/p/10341392.html

2.2 create_model   重點

        這裏可以說整個Bert使用的最關鍵的地方,我們使用Bert大多數情況無非進行在定義自己的下游工作進行fine-tune,就是在這裏定義的。

把這段代碼貼出來吧

def create_model(bert_config, is_training, input_ids, input_mask, segment_ids,
                 labels, num_labels, use_one_hot_embeddings):
  """Creates a classification model."""
  model = modeling.BertModel(
      config=bert_config,
      is_training=is_training,
      input_ids=input_ids,
      input_mask=input_mask,
      token_type_ids=segment_ids,
      use_one_hot_embeddings=use_one_hot_embeddings)
 
  # In the demo, we are doing a simple classification task on the entire
  # segment.
  #
  # If you want to use the token-level output, use model.get_sequence_output()
  # instead.
  output_layer = model.get_pooled_output()
 
  hidden_size = output_layer.shape[-1].value
 
  output_weights = tf.get_variable(
      "output_weights", [num_labels, hidden_size],
      initializer=tf.truncated_normal_initializer(stddev=0.02))
 
  output_bias = tf.get_variable(
      "output_bias", [num_labels], initializer=tf.zeros_initializer())
 
  with tf.variable_scope("loss"):
    if is_training:
      # I.e., 0.1 dropout
      output_layer = tf.nn.dropout(output_layer, keep_prob=0.9)
 
    logits = tf.matmul(output_layer, output_weights, transpose_b=True)
    logits = tf.nn.bias_add(logits, output_bias)
    probabilities = tf.nn.softmax(logits, axis=-1)
    log_probs = tf.nn.log_softmax(logits, axis=-1)
 
    one_hot_labels = tf.one_hot(labels, depth=num_labels, dtype=tf.float32)
 
    per_example_loss = -tf.reduce_sum(one_hot_labels * log_probs, axis=-1)
    loss = tf.reduce_mean(per_example_loss)
 
    return (loss, per_example_loss, logits, probabilities)

首先調用modeling.BertModel得到bert模型:

(1)bert模型的輸入:input_ids,input_mask,segment_ids

model = modeling.BertModel(
      config=bert_config,
      is_training=is_training,
      input_ids=input_ids,
      input_mask=input_mask,
      token_type_ids=segment_ids,
      use_one_hot_embeddings=use_one_hot_embeddings
  • config:是bert的配置文件,在開頭下載的中文模型中裏面有,直接加載即可

                                               

  • use_one_hot_embeddings:是根據是不是用GPU而定的,其他字段上述都說過啦

(2)bert模型的輸出:其有兩種情況   

         model.get_sequence_output():第一種輸出結果是[batch_size, seq_length, embedding_size]

         model.get_pooled_output():第二種輸出結果是[batch_size, embedding_size]

        第二種結果是第一種結果在第二個維度上面進行了池化,要是形象點比喻的話,第一種結果得到是tokens級別的結果,第二種是句子級別的,其實就是一個池化。

(3)我們定義部分

        這部分就是需要我們根據自己的任務自己具體定義啦,假設是一個簡單的分類,那麼就是定義一個全連接層將其轉化爲[batch_size, num_classes]。

       output_weights和output_bias就是對應全連接成的權值,後面就是loss,使用了tf.nn.log_softmax應該是一個多分類,多標籤的話可以使用tf.nn.sigmoid。

      總的來說,使用bert進行自己任務的時候,可以千變萬化,變的就是這裏這個下游。

3. main主函數

     最後就是主函數,主要就是通過人爲定義的一些配置值(FLAGS)將上面的流程整個組合起來

這裏大體說一下流程:

processors = {
      "cola": ColaProcessor,
      "mnli": MnliProcessor,
      "mrpc": MrpcProcessor,
      "xnli": XnliProcessor,
  }

       這裏就是定義數據預處理器的,記得把自己定義的預處理包含進來,名字嘛,隨便起起啦,到時候通過外部參數字段task_name來指定用哪個(說白了就是處理哪個數據)。

      數據預處理完了,就使用tf.contrib.tpu.TPUEstimator定義模型

      最後就是根據不同模式(train/dev/test,這也是運行時可以指定的)運行estimator.train,estimator.evaluate,estimator.predict。

4. 總結

(1)總體來說,在進行具體工作時,複製 BERT 的 run_classifier.py,修改核心內容作爲自己的run函數,需要改的核心就是:

          1) 繼承DataProcessor,定義一個自己的數據預處理類

          2) 在create_model中,定義自己的具體下游工作

        剩下的就是一些零零碎碎的小地方啦,也很簡單

(2)關於bert上游的具體模型定義這裏沒有,實在感興趣可以看modeling.py腳本,優化器部分是optimization.py

(3)這裏沒有從頭訓練bert模型,因爲耗時耗力,沒有資源一般來說很難,關於預訓練的部分是run_pretraining.py

三、Entity-Relation-Extraction-master實戰

https://github.com/yuanxiaosc/Entity-Relation-Extraction

基於 TensorFlow 的實體及關係抽取,2019語言與智能技術競賽信息抽取(實體與關係抽取)任務解決方案。

實體關係抽取本模型過程:

該代碼以管道式的方式處理實體及關係抽取任務,首先使用一個多標籤分類模型判斷句子的關係種類, 然後把句子和可能的關係種類輸入序列標註模型中,序列標註模型標註出句子中的實體,最終結合預 測的關係和實體輸出實體-關係列表:(實體1,關係,實體2)

  • 1. 先進行關係抽取:得到一句話中有幾種關係
  • 2. 根據預測出來的關係類,如當前句子預測出3個關係,那麼就重複該句話分成3個樣本
  • 3. 再進行實體識別:根據序列標註找出實體
  • 4. 生成實體—關係三元組結果

模型過程總結:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章