信息抽取(一)机器阅读理解——样本数据处理与Baseline模型搭建训练(2020语言与智能技术竞赛)
機器閱讀理解——樣本數(shù)據(jù)處理與Baseline模型搭建訓(xùn)練
- 前言
- 樣本數(shù)據(jù)處理
- 數(shù)據(jù)測試
- 模型部分
- 模型構(gòu)建
- 模型訓(xùn)練
- 部分推理結(jié)果
- 總結(jié)
前言
最近看到今年早些時候百度的“2020語言與智能技術(shù)競賽”比賽,里面有五個賽道,三個賽道與信息抽取有關(guān),分別是機器閱讀理解、關(guān)系抽取、事件抽取。最近正好對信息抽取任務(wù)比較感興趣,所以拿來復(fù)現(xiàn)一下baseline模型,同時參考參考大佬們的想法,學(xué)習(xí)下思想和技巧。
參考比賽:Tweet Sentiment Extraction、2020語言與智能技術(shù)競賽-機器閱讀理解,這個兩個賽題都涉及到了信息抽取
Tweet Sentiment Extraction:通過給定的tweet,以及其情感傾向,從該條tweet中抽取可以代表其情感傾向的詞句。
2020語言與智能技術(shù)競賽-機器閱讀理解:給定背景內(nèi)容context,根據(jù)所給問題,從context中抽取對應(yīng)的答案。
其實Tweet Sentiment Extraction也可以看作閱讀理解問題,只是把tweet和emotion作為context,而問題固定(抽取情感語句)。
這篇是信息抽取系列的第一篇:即機器閱讀理解,比起其他兩個信息抽取任務(wù)來說,可以算是小打小鬧了,baseline模型也比較簡單,通過兩個softmax,找到答案的start和end即可。
但在數(shù)據(jù)集構(gòu)建時真的不知道出了多少bug,最后自己總結(jié)一個模版,可以用在中文和英文并且對標(biāo)注的數(shù)據(jù)有很大的噪聲容忍度的SQuAD任務(wù)的數(shù)據(jù)集生成方法。
樣本數(shù)據(jù)處理
數(shù)據(jù)樣例:數(shù)據(jù)為json格式,每條樣本包含context、question、id、answers:text、answer_start(答案的起始位置)。
注意這個answer_start很重要,后面有妙用,如果當(dāng)文中出現(xiàn)重復(fù)的答案片段時,要保證你的y值和answer_start保持一致,而且answer_start可以用來處理過長(>512)的context,把每一個樣本數(shù)據(jù)都利用起來。
數(shù)據(jù)提取,這里就不多介紹怎么把樣本以結(jié)構(gòu)的形式取出,我的方法比較笨。
def data_load(path):with open(path) as json_file:data = json.load(json_file)context = [x['context'] for x in data['data'][0]['paragraphs']]question = [x['qas'][0]['question'] for x in data['data'][0]['paragraphs']]answers_text = [x['qas'][0]['answers'][0]['text'] for x in data['data'][0]['paragraphs']]answer_start = [x['qas'][0]['answers'][0]['answer_start'] for x in data['data'][0]['paragraphs']]return context,question,answers_text,answer_start訓(xùn)練樣本處理:前方高能
主要思路:找到answer與context重合的區(qū)域,對context進行編碼,再反解碼后,如果反解碼出來的文字在原生context中的位置處于答案重合區(qū)域,則該文字的token視為答案的一部分,找到連續(xù)答案token取第一個為start_index,最后一個為end_index。
測試集數(shù)據(jù)處理同理,但不需要給Y值
def test_data_proceed(tokenizer,context_t,question_t,answers_text_t,answer_start_t,MAX_LEN):ct = len(context_t)input_ids_t = np.zeros((ct,MAX_LEN),dtype='int32')attention_mask_t = np.zeros((ct,MAX_LEN),dtype='int32')for k in range(len(context_t)):enc_context_t = tokenizer.encode(context_t[k]) enc_question_t = tokenizer.encode(question_t[k])if len(enc_question_t)+len(enc_context_t)-1 > MAX_LEN:x = enc_question_t + enc_context_t[1:]input_ids_t[k] = x[:MAX_LEN]attention_mask_t[k,:MAX_LEN] = 1else:input_ids_t[k,:len(enc_question_t)+len(enc_context_t)-1] = enc_question_t + enc_context_t[1:]attention_mask_t[k,:len(enc_question_t)+len(enc_context_t)-1] = 1return input_ids_t,attention_mask_t數(shù)據(jù)測試
def random_int_list(start, stop, length):start, stop = (int(start), int(stop)) if start <= stop else (int(stop), int(start))length = int(abs(length)) if length else 0random_list = []for i in range(length):random_list.append(random.randint(start, stop))return random_listprint('#'*25) print('### check train_data') print('#'*25)for i in random_int_list(0,len(context),10):x_ = [tokenizer.decode([t]) for t in input_ids[i]]token_ans = ''.join(x_[np.argmax(start_tokens[i]):np.argmax(end_tokens[i])+1])print(token_ans+' '+answers_text[i])'''1200字左右 1200字左右一天兩次 一天兩次龍巖市 龍巖市荊州地區(qū) 荊州地區(qū)深圳紐仕達電商之家 深圳紐仕達電商之家25度左右 25度左右第六期 第六期狼爪 狼爪298.0元 298.0元所有的肌膚類型 所有的肌膚類型'''print('#'*25) print('### check test_data') print('#'*25)for i in random_int_list(0,len(context_t),5):x_ = [tokenizer.decode([t]) for t in input_ids_t[i]]token_ans = ''.join(x_)print(token_ans)模型部分
模型構(gòu)建
Tensorflow:2.0.0
Transformers:3.1.0
模型1:
def build_model(pretrained_path,config,MAX_LEN):ids = tf.keras.layers.Input((MAX_LEN,), dtype=tf.int32)att = tf.keras.layers.Input((MAX_LEN,), dtype=tf.int32) bert_model = TFBertModel.from_pretrained(pretrained_path,config=config,from_pt=True)x = bert_model(ids,attention_mask=att)'''取最后一層的hidden_layers,分別接兩個liner+softmax,得到start和end'''x1 = tf.keras.layers.Dropout(0.1)(x[0]) x1 = tf.keras.layers.Conv1D(1,1)(x1)'''(None, 96, 768)(None, 96, 1)769個參數(shù)相當(dāng)于做了一次加權(quán)+b如果不指定該函數(shù),將不會使用任何激活函數(shù)(即使用線性激活函數(shù):a(x)=x)flatten后得到展開的768接一個softmax'''x1 = tf.keras.layers.Flatten()(x1)x1 = tf.keras.layers.Activation('softmax')(x1)x2 = tf.keras.layers.Dropout(0.1)(x[0]) x2 = tf.keras.layers.Conv1D(1,1)(x2)x2 = tf.keras.layers.Flatten()(x2)x2 = tf.keras.layers.Activation('softmax')(x2)model = tf.keras.models.Model(inputs=[ids, att], outputs=[x1,x2])optimizer = tf.keras.optimizers.Adam(learning_rate=3e-5)model.compile(loss='categorical_crossentropy', optimizer=optimizer)return model模型2:
def build_model_2(pretrained_path,config,MAX_LEN):ids = tf.keras.layers.Input((MAX_LEN,), dtype=tf.int32)att = tf.keras.layers.Input((MAX_LEN,), dtype=tf.int32)config.output_hidden_states = Truebert_model = TFBertModel.from_pretrained(pretrained_path,config=config,from_pt=True)x, _, hidden_states = bert_model(ids,attention_mask=att)layer_1 = hidden_states[-1]layer_2 = hidden_states[-2]'''不同于模型1,模型2取了最后兩層hidden_layers分別接了兩個一維卷積后作softmax,因為start和end來源于不同的hidden_layer,模型學(xué)習(xí)的目的性更強,特征也更加豐富'''x1 = tf.keras.layers.Dropout(0.1)(layer_1)#(512,768)x1 = tf.keras.layers.Conv1D(128, 2, padding='same')(x1)#(512,128)x1 = tf.keras.layers.LeakyReLU()(x1)'''ReLU是將所有的負值都設(shè)為零,相反,Leaky ReLU是給所有負值賦予一個非零斜率。'''#x1 = tf.keras.layers.Conv1D(64, 2, padding='same')(x1)#(512,64)x1 = tf.keras.layers.Dense(1, dtype='float32')(x1)#(512,1)start_logits = tf.keras.layers.Flatten()(x1)#(512,)start_logits = tf.keras.layers.Activation('softmax')(start_logits)x2 = tf.keras.layers.Dropout(0.1)(layer_2)x2 = tf.keras.layers.Conv1D(128, 2, padding='same')(x2)x2 = tf.keras.layers.LeakyReLU()(x2)x2 = tf.keras.layers.Conv1D(64, 2, padding='same')(x2)x2 = tf.keras.layers.Dense(1, dtype='float32')(x2)end_logits = tf.keras.layers.Flatten()(x2)end_logits = tf.keras.layers.Activation('softmax')(end_logits)model = tf.keras.models.Model(inputs=[ids, att], outputs=[start_logits,end_logits])optimizer = tf.keras.optimizers.Adam(learning_rate=2e-5)model.compile(loss='categorical_crossentropy', optimizer=optimizer)return model模型訓(xùn)練
這里采用的是K折訓(xùn)練,單模型stacking,將數(shù)據(jù)集隨機劃分成5份,取其中4份作為訓(xùn)練集,1份作為測試集,分別訓(xùn)練5個val_loss最低的模型,分別對驗證集進行推理,將5個模型的softmax結(jié)果加權(quán)平均后,argmax得到預(yù)測的答案start和end位置。
def main():VER='v0'DISPLAY=2 # USE display=1 FOR INTERACTIVEoof_start = np.zeros((input_ids.shape[0],MAX_LEN))oof_end = np.zeros((input_ids.shape[0],MAX_LEN))preds_start = np.zeros((input_ids_t.shape[0],MAX_LEN))preds_end = np.zeros((input_ids_t.shape[0],MAX_LEN))skf = StratifiedKFold(n_splits=5,shuffle=True,random_state=777)for fold,(idxT,idxV) in enumerate(skf.split(input_ids,answer_start)):'''fold:當(dāng)前K折折次idxT:當(dāng)前訓(xùn)練集indexidxV:當(dāng)前測試集indexlen(idxV)*4=len(idxT)'''print('#'*25)print('### FOLD %i'%(fold+1))print('#'*25)K.clear_session()model = build_model_2(pretrained_path,config,MAX_LEN)sv = tf.keras.callbacks.ModelCheckpoint('./model_/%s-roberta-%i.h5'%(VER,fold), monitor='val_loss', verbose=2, save_best_only=True,save_weights_only=True, mode='auto', save_freq='epoch')model.fit([input_ids[idxT,], attention_mask[idxT,]],[start_tokens[idxT,],end_tokens[idxT,]], epochs=1, batch_size=8, verbose=DISPLAY, callbacks=[sv],validation_data=([input_ids[idxV,],attention_mask[idxV,]], [start_tokens[idxV,], end_tokens[idxV,]]))print('Loading model...')model.load_weights('./model_/%s-roberta-%i.h5'%(VER,fold))print('Predicting Test...')preds = model.predict([input_ids_t,attention_mask_t],verbose=DISPLAY)preds_start += preds[0]/skf.n_splitspreds_end += preds[1]/skf.n_splits'''5折驗證結(jié)果進行投票,選出得分最高的點作為開始和結(jié)束點'''all_ = []for k in range(len(input_ids_t)):a = np.argmax(preds_start[k,])b = np.argmax(preds_end[k,])if a>b: st = context_t[k]else:x_ = [tokenizer.decode([t]) for t in input_ids_t[k]]st = ''.join(x_[a:b+1])all_.append(st)ans_data = pd.DataFrame(context_t,columns=['context'])ans_data['question'] = question_tans_data['answers_text'] = answers_text_tans_data['pred_answers'] = all_ans_data.to_csv('result.csv')部分推理結(jié)果
抽取了一些驗證集上的推理結(jié)果,可以看到模型基本都能準確給出正確答案。
總結(jié)
有關(guān)于其他的數(shù)據(jù)增強、訓(xùn)練技巧這里就不做過多介紹了,想要了解的朋友可以去看下大牛們的技術(shù)報告。
總的來說,閱讀理解baseline模型還是非常簡單的,大家可以采取不同的方法取標(biāo)注指針,不一定是模型的最后兩層隱藏層。
在實際訓(xùn)練中,所有子模型都只訓(xùn)練了一輪就達到了過擬合,有幾個可能:1.數(shù)據(jù)太簡單,2.模型太復(fù)雜了,3.學(xué)習(xí)速率太高,可以考慮使用warm_up。
接下來我會找時間去復(fù)現(xiàn)關(guān)系抽取、事件抽取這兩個任務(wù)類型,涉及到NER,應(yīng)該會更加有意思。
完整訓(xùn)練代碼,可以參考我的github: https://github.com/zhengyanzhao1997/TF-NLP-model/blob/main/model/train/SQuAD_baseline.py
部分參考資料:
樣本數(shù)據(jù)構(gòu)造與模型訓(xùn)練參考思路:https://www.kaggle.com/cdeotte/tensorflow-roberta-0-705/comments
模型參考思路:https://zhuanlan.zhihu.com/p/139779541
總結(jié)
以上是生活随笔為你收集整理的信息抽取(一)机器阅读理解——样本数据处理与Baseline模型搭建训练(2020语言与智能技术竞赛)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 语义匹配(一)【NLP论文复现】Sent
- 下一篇: 信息抽取(二)花了一个星期走了无数条弯路