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].valueoutput_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 dropoutoutput_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. 生成實體—關系三元組結果
模型過程總結:
?
總結
以上是生活随笔為你收集整理的BERT:代码解读、实体关系抽取实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人工智能:各种知识收集----不断追加内
- 下一篇: Keras:保存模型并载入模型继续训练