BERT:代码解读、实体关系抽取实战
目錄
前言? ??
一、BERT的主要亮點(diǎn)
1. 雙向Transformers
2.句子級別的應(yīng)用
3.能夠解決的任務(wù)
二、BERT代碼解讀
1. 數(shù)據(jù)預(yù)處理
1.1 InputExample類
1.2 InputFeatures類
1.3 DataProcessor? ?重點(diǎn)
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? ?重點(diǎn)
3. main主函數(shù)
4. 總結(jié)
三、Entity-Relation-Extraction-master實(shí)戰(zhàn)
BERT代碼:參加另一篇文章《命名實(shí)體識別NER & 如何使用BERT實(shí)現(xiàn)》
前言? ??
? ? ? ? BERT模型是谷歌2018年10月底公布的,它的提出主要是針對word2vec等模型的不足,在之前的預(yù)訓(xùn)練模型(包括word2vec,ELMo等)都會生成詞向量,這種類別的預(yù)訓(xùn)練模型屬于domain transfer。而近一兩年提出的ULMFiT,GPT,BERT等都屬于模型遷移,說白了BERT 模型是將預(yù)訓(xùn)練模型和下游任務(wù)模型結(jié)合在一起的,核心目的就是:是把下游具體NLP任務(wù)的活逐漸移到預(yù)訓(xùn)練產(chǎn)生詞向量上。
基于google公布的一個(gè)源代碼: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的主要亮點(diǎn)
1. 雙向Transformers
? ? ? BERT真真意義上同時(shí)考慮了上下文:
? ? ? ?正如論文中所講,目前的主要限制是當(dāng)前模型不能同時(shí)考慮上下文,像上圖的GPT只是一個(gè)從左到右,ELMo雖然有考慮從左到右和從右到左,但是是兩個(gè)分開的網(wǎng)絡(luò),只有BERT是真真意義上的同時(shí)考慮了上下文。
2.句子級別的應(yīng)用
? ? ?通過使用segment同時(shí)考慮了句子級別的預(yù)測。
3.能夠解決的任務(wù)
? ? ? ?google已經(jīng)預(yù)預(yù)訓(xùn)練好了模型,我們要做的就是根據(jù)不同的任務(wù),按照bert的輸入要求(后面會看到)輸入我們的數(shù)據(jù),然后獲取輸出,在輸出層加一層(通常情況下)全連接層就OK啦,整個(gè)訓(xùn)練過程就是基于預(yù)訓(xùn)練模型的微調(diào),下述圖片是其可以完成的幾大類任務(wù):
? ? ? ?a、b都是sentence級別的:文本分類,關(guān)系抽取等;
? ? ? ?c、d是tokens級別的:如命名實(shí)體識別,知識問答等。
二、BERT代碼解讀
BERT的代碼主要分為兩個(gè)部分:
1. 預(yù)訓(xùn)練部分:其入口是在run_pretraining.py。
2. Fine-tune部分:Fine-tune的入口針對不同的任務(wù)分別在run_classifier.py和run_squad.py。其中
- run_classifier.py:適用的任務(wù)為分類任務(wù),如CoLA、MRPC、MultiNLI等。而
- run_squad.py:適用的是閱讀理解任務(wù),如squad2.0和squad1.1。
? ? ?在使用的時(shí)候,一般是需要下面三個(gè)腳本的,我們也不必修改,直接拿過來使用就ok
- ? modeling.py:模型定義
- ? optimization.py:優(yōu)化器
- ? tokenization.py
? ? ? ? 其中tokenization是對原始句子內(nèi)容的解析,分為BasicTokenizer和WordpieceTokenizer兩個(gè),一般來說BasicTokenizer主要是進(jìn)行unicode轉(zhuǎn)換、標(biāo)點(diǎn)符號分割、中文字符分割、去除重音符號等操作,最后返回的是關(guān)于詞的數(shù)組(中文是字的數(shù)組),WordpieceTokenizer的目的是將合成詞分解成類似詞根一樣的詞片。例如將"unwanted"分解成["un", "##want", "##ed"],這么做的目的是防止因?yàn)樵~的過于生僻沒有被收錄進(jìn)詞典最后只能以[UNK]代替的局面,因?yàn)橛⒄Z當(dāng)中這樣的合成詞非常多,詞典不可能全部收錄。FullTokenizer的作用就很顯而易見了,對一個(gè)文本段進(jìn)行以上兩種解析,最后返回詞(字)的數(shù)組,同時(shí)還提供token到id的索引以及id到token的索引。這里的token可以理解為文本段處理過后的最小單元。上述來源https://www.jianshu.com/p/22e462f01d8c,更多該腳本的內(nèi)容可以看該鏈接,下面主要用到FullTokenizer這個(gè)類。
真正需要修改是:
? ? ? ? ? ? run_classifier.py
? ? ? ? ? ? run_squad.py
分別是解決分類、閱讀理解任務(wù),其實(shí)套路差不多,我們具體來看一下run_classifier.py
? ? ? 首先BERT主要分為兩個(gè)部分。一個(gè)是訓(xùn)練語言模型(language model)的預(yù)訓(xùn)練(run_pretraining.py)部分。另一個(gè)是訓(xùn)練具體任務(wù)(task)的fine-tune部分,預(yù)訓(xùn)練部分巨大的運(yùn)算資源,但是其已經(jīng)公布了BERT的預(yù)訓(xùn)練模型。
這里需要中文,直接下載就行,總得來說,我們要做的就是自己的數(shù)據(jù)集上進(jìn)行fine-tune。
1. 數(shù)據(jù)預(yù)處理
? ? run_classifier.py中的類如下:? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ??
1.1 InputExample類
? ? ?主要定義了一些數(shù)據(jù)預(yù)處理后要生成的字段名,如下:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
- guid就是一個(gè)id號,一般將數(shù)據(jù)處理成train、dev、test數(shù)據(jù)集,那么這里定義方式就可以是相應(yīng)的數(shù)據(jù)集+行號(句子)
- text_a 就是當(dāng)前的句子,text_b是另一個(gè)句子,因?yàn)橛械娜蝿?wù)需要兩個(gè)兩個(gè)句子,如果任務(wù)中沒有的話,可以將text_b設(shè)為None
- label就是標(biāo)簽
1.2 InputFeatures類
? ? ? ?主要是定義了bert的輸入格式,形象化點(diǎn)就是特征,即上面的格式使我們需要將原始數(shù)據(jù)處理成的格式,但并不是bert使用的最終格式,且還會通過一些代碼將InputExample轉(zhuǎn)化為InputFeatures,這才是bert最終使用的數(shù)據(jù)格式,當(dāng)然啦這里根據(jù)自己的需要還可以自定義一些字段作為中間輔助字段,但bert最基本的輸入字段就需要input_ids,input_mask和segment_ids這三個(gè)字段,label_id是計(jì)算loss時(shí)候用到的:
- input_ids,segment_ids:分別對應(yīng)單詞id和句子(上下句標(biāo)示),input_ids、segment_ids分別代表token、segment。
- Input_mask:記錄的是填充信息,具體看下面
? ? ? ? ? ? ? ?
1.3 DataProcessor? ?重點(diǎn)
? ? ? 這是一個(gè)數(shù)據(jù)預(yù)處理的基類,里面定義了一些基本方法。
? ? ? XnliProcessor、MnliProcessor、MrpcProcessor、ColaProcessor四個(gè)類是對DataProcessor的具體實(shí)現(xiàn),這里之所以列舉了四個(gè)是盡可能多的給用戶呈現(xiàn)出各種demo,具體到實(shí)際使用的時(shí)候我們只需要參考其寫法,定義一個(gè)自己的數(shù)據(jù)預(yù)處理類即可,其中一般包括如下幾個(gè)方法:
? ? ? ?get_train_examples,get_dev_examples,get_test_examples,get_labels,_create_examples
其中前三個(gè)都通過調(diào)用_create_examples返回一個(gè)InputExample類數(shù)據(jù)結(jié)構(gòu),get_labels就是返回類別,所以重點(diǎn)就是以下兩個(gè)函數(shù):
? ? ? 這里的tokenization的convert_to_unicode就是將文本轉(zhuǎn)化為utf-8編碼。
? ? ?上述就是數(shù)據(jù)預(yù)處理過程,也是需要我們自己根據(jù)自己的數(shù)據(jù)定義的,其實(shí)呢,這并不是Bert使用的最終樣子,其還得經(jīng)過一系列過程才能變成其能處理的數(shù)據(jù)格式,該過程是通過接下來的四個(gè)方法完成的:
? ? ? ? ? ? convert_single_example:返回一個(gè)InputFeatures類
? ? ? ? ? ? file_based_convert_examples_to_features
? ? ? ? ? ? file_based_input_fn_builder
? ? ? ? ? ? truncate_seq_pair
只不過一般情況下我們不需要修改,它都是一個(gè)固定的流程。
1.4 convert_single_example
? ? ?bert的輸入:
? 代碼中的input_ids、segment_ids分別代表token、segment,同時(shí)其還在句子的開頭結(jié)尾加上了[CLS]和SEP]標(biāo)示?:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
- input_ids:記錄的是使用FullTokenizer類convert_tokens_to_ids方法將tokens轉(zhuǎn)化成單個(gè)字的id;
- segment_ids:就是句子級別(上下句)的標(biāo)簽,大概形式:
(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
當(dāng)沒有text_b的時(shí)候,就都是0啦
- input_mask:其就是和最大長度有關(guān),假設(shè)我們定義句子的最大長度是120,當(dāng)前句子長度是100,那么input_mask前100個(gè)元素都是1,其余20個(gè)就是0。
最后返回的就是一個(gè)InputFeatures類:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
1.5 file_based_convert_examples_to_features
? ? ? 很簡單啦,因?yàn)樵谟?xùn)練的時(shí)候?yàn)榱俗x寫快速方便,便將數(shù)據(jù)制作成TFrecords 數(shù)據(jù)格式,該函數(shù)主要就是將上述返回的InputFeatures類數(shù)據(jù),保存成一個(gè)TFrecords數(shù)據(jù)格式,關(guān)于TFrecords數(shù)據(jù)格式的制作可以參考:https://blog.csdn.net/weixin_42001089/article/details/90236241
1.6 file_based_input_fn_builder
? ? ? ?對應(yīng)的就是從TFrecords 解析讀取數(shù)據(jù)
1.7 _truncate_seq_pair
? ? ? 就是來限制text_a和text_b總長度的,當(dāng)超過的話,會輪番pop掉tokens
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
至此整個(gè)數(shù)據(jù)的預(yù)處理才算處理好,其實(shí)最后最關(guān)鍵的就是得到了那個(gè)TFrecords文件。
2. 模型部分
- ?create_model
- ?model_fn_builder
? ?整個(gè)模型過程采用了tf.contrib.tpu.TPUEstimator這一高級封裝的API
? ? model_fn_builder是殼,create_model是核心,其內(nèi)部定義了loss,預(yù)測概率以及預(yù)測結(jié)果等等。
2.1 model_fn_builder
? ? ? ?其首先調(diào)用create_model得到total_loss、 per_example_loss、logits、 probabilities等:
? ? ? ? ??
? ? ?然后針對不同的狀態(tài)返回不同的結(jié)果(output_spec):
- 如果是train,則返回loss、train_op等;
- 如果是dev,則返回一些評價(jià)指標(biāo)如accuracy;
- 如果是test,則返回預(yù)測結(jié)果
? ? ? 所以我們如果想看一下別的指標(biāo)什么的,可以在這里改 ,需要注意的是指標(biāo)的定義這里因?yàn)槭褂昧薳stimator API使得其必須返回一個(gè)operation,至于怎么定義f1可以看:
? ? ? ? ? ? ?https://www.cnblogs.com/jiangxinyang/p/10341392.html
2.2 create_model? ?重點(diǎn)
? ? ? ? 這里可以說整個(gè)Bert使用的最關(guān)鍵的地方,我們使用Bert大多數(shù)情況無非進(jìn)行在定義自己的下游工作進(jìn)行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)首先調(diào)用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:是根據(jù)是不是用GPU而定的,其他字段上述都說過啦
(2)bert模型的輸出:其有兩種情況? ?
? ? ? ? ?model.get_sequence_output():第一種輸出結(jié)果是[batch_size, seq_length, embedding_size]
? ? ? ? ?model.get_pooled_output():第二種輸出結(jié)果是[batch_size,?embedding_size]
? ? ? ? 第二種結(jié)果是第一種結(jié)果在第二個(gè)維度上面進(jìn)行了池化,要是形象點(diǎn)比喻的話,第一種結(jié)果得到是tokens級別的結(jié)果,第二種是句子級別的,其實(shí)就是一個(gè)池化。
(3)我們定義部分
? ? ? ? 這部分就是需要我們根據(jù)自己的任務(wù)自己具體定義啦,假設(shè)是一個(gè)簡單的分類,那么就是定義一個(gè)全連接層將其轉(zhuǎn)化為[batch_size, num_classes]。
? ? ? ?output_weights和output_bias就是對應(yīng)全連接成的權(quán)值,后面就是loss,使用了tf.nn.log_softmax應(yīng)該是一個(gè)多分類,多標(biāo)簽的話可以使用tf.nn.sigmoid。
? ? ? 總的來說,使用bert進(jìn)行自己任務(wù)的時(shí)候,可以千變?nèi)f化,變的就是這里這個(gè)下游。
3. main主函數(shù)
? ? ?最后就是主函數(shù),主要就是通過人為定義的一些配置值(FLAGS)將上面的流程整個(gè)組合起來
這里大體說一下流程:
processors = {"cola": ColaProcessor,"mnli": MnliProcessor,"mrpc": MrpcProcessor,"xnli": XnliProcessor,}? ? ? ?這里就是定義數(shù)據(jù)預(yù)處理器的,記得把自己定義的預(yù)處理包含進(jìn)來,名字嘛,隨便起起啦,到時(shí)候通過外部參數(shù)字段task_name來指定用哪個(gè)(說白了就是處理哪個(gè)數(shù)據(jù))。
? ? ? 數(shù)據(jù)預(yù)處理完了,就使用tf.contrib.tpu.TPUEstimator定義模型
? ? ? 最后就是根據(jù)不同模式(train/dev/test,這也是運(yùn)行時(shí)可以指定的)運(yùn)行estimator.train,estimator.evaluate,estimator.predict。
4. 總結(jié)
(1)總體來說,在進(jìn)行具體工作時(shí),復(fù)制 BERT 的 run_classifier.py,修改核心內(nèi)容作為自己的run函數(shù),需要改的核心就是:
? ? ? ? ? 1) 繼承DataProcessor,定義一個(gè)自己的數(shù)據(jù)預(yù)處理類
? ? ? ? ? 2) 在create_model中,定義自己的具體下游工作
? ? ? ? 剩下的就是一些零零碎碎的小地方啦,也很簡單
(2)關(guān)于bert上游的具體模型定義這里沒有,實(shí)在感興趣可以看modeling.py腳本,優(yōu)化器部分是optimization.py
(3)這里沒有從頭訓(xùn)練bert模型,因?yàn)楹臅r(shí)耗力,沒有資源一般來說很難,關(guān)于預(yù)訓(xùn)練的部分是run_pretraining.py
三、Entity-Relation-Extraction-master實(shí)戰(zhàn)
https://github.com/yuanxiaosc/Entity-Relation-Extraction
基于 TensorFlow 的實(shí)體及關(guān)系抽取,2019語言與智能技術(shù)競賽信息抽取(實(shí)體與關(guān)系抽取)任務(wù)解決方案。
實(shí)體關(guān)系抽取本模型過程:
該代碼以管道式的方式處理實(shí)體及關(guān)系抽取任務(wù),首先使用一個(gè)多標(biāo)簽分類模型判斷句子的關(guān)系種類, 然后把句子和可能的關(guān)系種類輸入序列標(biāo)注模型中,序列標(biāo)注模型標(biāo)注出句子中的實(shí)體,最終結(jié)合預(yù) 測的關(guān)系和實(shí)體輸出實(shí)體-關(guān)系列表:(實(shí)體1,關(guān)系,實(shí)體2)
- 1. 先進(jìn)行關(guān)系抽取:得到一句話中有幾種關(guān)系
- 2. 根據(jù)預(yù)測出來的關(guān)系類,如當(dāng)前句子預(yù)測出3個(gè)關(guān)系,那么就重復(fù)該句話分成3個(gè)樣本
- 3. 再進(jìn)行實(shí)體識別:根據(jù)序列標(biāo)注找出實(shí)體
- 4. 生成實(shí)體—關(guān)系三元組結(jié)果
模型過程總結(jié):
?
總結(jié)
以上是生活随笔為你收集整理的BERT:代码解读、实体关系抽取实战的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人工智能:各种知识收集----不断追加内
- 下一篇: Keras:保存模型并载入模型继续训练