信息抽取(三)三元关系抽取——改良后的层叠式指针网络,让我的模型F1提升近4%(接上篇)
信息抽取(三)三元關系抽取——改良后的層疊式指針網絡
- 前言
- 優化在驗證集上的模型推理結果的SPO抽取方法
- 不隨機選擇S(subject),?是遍歷所有不同主語的標注樣本構建訓練集。
- 模型優化
- 加入對抗訓練FGM
- 總結
前言
基于我上一篇的博客:信息抽取(二)花了一個星期走了無數條彎路終于用TF復現了蘇神的《Bert三元關系抽取模型》,我到底悟到了什么?
復現后的模型在百度2019年語言競賽三元關系抽取的數據集上F1值僅達到77%,我在博文總結了幾點可以優化的方向,并實現一系列層疊式指針網絡的改良。在此貼出代碼和提升結果。
優化在驗證集上的模型推理結果的SPO抽取方法
原方案是將token decode后組成結果,改良后的方案通過在token上的index返回到原文本中切割出答案,這避免了token無法識別一些特殊文字和符號亦或是空格。
def rematch_text_word(tokenizer,text,enc_context,enc_start,enc_end):span = [a.span()[0] for a in re.finditer(' ', text)]decode_list = [tokenizer.decode([i]) for i in enc_context][1:]start = 0end = 0len_start = 0for i in range(len(decode_list)):if i == enc_start - 1:start = len_startj = decode_list[i]if '#' in j and len(j)>1:j = j.replace('#','')if j == '[UNK]':j = '。'len_start += len(j)if i == enc_end - 1:end = len_startbreakfor span_index in span:if start >= span_index:start += 1end += 1if end > span_index and span_index>start:end += 1return text[start:end]不隨機選擇S(subject),?是遍歷所有不同主語的標注樣本構建訓練集。
原方案是對于每組文本數據,僅隨機抽取一個S以及其相關的PO構建成一組數據。
改良后,對于每組文本數據,分別抽取其所有不同的S以及其相關的PO組成多組數據。
盡管對不不同樣本來說S是相同的,但在實驗中發現,模型對于S的推理往往比PO關系優秀太多,因此S的可能過擬合來提升模型在PO上的表現是值得的。
以上兩個優化方案的提升效果:F1:0.7719 —> 0.7979
def proceed_data(text_list,spo_list,p2id,id2p,tokenizer,MAX_LEN,sop_count):id_label = {}ct = len(text_list)MAX_LEN = MAX_LENprint(sop_count)input_ids = np.zeros((sop_count,MAX_LEN),dtype='int32')attention_mask = np.zeros((sop_count,MAX_LEN),dtype='int32')start_tokens = np.zeros((sop_count,MAX_LEN),dtype='int32')end_tokens = np.zeros((sop_count,MAX_LEN),dtype='int32')send_s_po = np.zeros((sop_count,2),dtype='int32')object_start_tokens = np.zeros((sop_count,MAX_LEN,len(p2id)),dtype='int32')object_end_tokens = np.zeros((sop_count,MAX_LEN,len(p2id)),dtype='int32')index_vaild = -1for k in range(ct):context_k = text_list[k].lower().replace(' ','')enc_context = tokenizer.encode(context_k,max_length=MAX_LEN,truncation=True) if len(spo_list[k])==0:continue start = []S_index = []for j in range(len(spo_list[k])):answers_text_k = spo_list[k][j]['subject'].lower().replace(' ','')chars = np.zeros((len(context_k)))index = context_k.find(answers_text_k)chars[index:index+len(answers_text_k)]=1offsets = []idx=0for t in enc_context[1:]:w = tokenizer.decode([t])if '#' in w and len(w)>1:w = w.replace('#','')if w == '[UNK]':w = '。'offsets.append((idx,idx+len(w)))idx += len(w)toks = []for i,(a,b) in enumerate(offsets):sm = np.sum(chars[a:b])if sm>0: toks.append(i) if len(toks)>0:S_start = toks[0]+1S_end = toks[-1]+1if (S_start,S_end) not in start:index_vaild += 1start.append((S_start,S_end))input_ids[index_vaild,:len(enc_context)] = enc_contextattention_mask[index_vaild,:len(enc_context)] = 1start_tokens[index_vaild,S_start] = 1end_tokens[index_vaild,S_end] = 1send_s_po[index_vaild,0] = S_startsend_s_po[index_vaild,1] = S_endS_index.append([j,index_vaild])else:S_index.append([j,index_vaild])if len(S_index) > 0:for index_ in range(len(S_index)):#隨機選取object的首位,如果選取錯誤,則作為負樣本object_text_k = spo_list[k][S_index[index_][0]]['object'].lower().replace(' ','')predicate = spo_list[k][S_index[index_][0]]['predicate']p_id = p2id[predicate]chars = np.zeros((len(context_k)))index = context_k.find(object_text_k)chars[index:index+len(object_text_k)]=1offsets = [] idx = 0for t in enc_context[1:]:w = tokenizer.decode([t])if '#' in w and len(w)>1:w = w.replace('#','')if w == '[UNK]':w = '。'offsets.append((idx,idx+len(w)))idx += len(w)toks = []for i,(a,b) in enumerate(offsets):sm = np.sum(chars[a:b])if sm>0: toks.append(i) if len(toks)>0:id_label[p_id] = predicateP_start = toks[0]+1P_end = toks[-1]+1object_start_tokens[S_index[index_][1]][P_start,p_id] = 1object_end_tokens[S_index[index_][1]][P_end,p_id] = 1return input_ids[:index_vaild],attention_mask[:index_vaild],start_tokens[:index_vaild],\ end_tokens[:index_vaild],send_s_po[:index_vaild],object_start_tokens[:index_vaild],\ object_end_tokens[:index_vaild],id_label模型優化
圖中并沒有表示如何加入實體的position embedding,這部分也是本人自己摸索出來的,通過抽取Bert的position embedding加入到hidden state中,然后作self_attention,這樣能給模型的f1帶來0.06的提升。
也嘗試過重新構建一個position embedding讓模型自己學習,但并沒有提升。
模型提升效果:F1:0.7979 —> 0.8095
加入對抗訓練FGM
@tf.function def train_step(model,x,y,loss_func,optimizer,train_loss):with tf.GradientTape() as tape:y_pred = model(x,training=True)loss1 = loss_func(y['lambda'],tf.squeeze(y_pred[0]))loss2 = loss_func(y['lambda_1'],tf.squeeze(y_pred[1]))loss3 = loss_func(y['lambda_2'],tf.squeeze(y_pred[2]))loss4 = loss_func(y['lambda_3'],tf.squeeze(y_pred[3]))loss = loss1+loss2+loss3+loss4embedding = model.trainable_variables[0]embedding_gradients = tape.gradient(loss,[model.trainable_variables[0]])[0]embedding_gradients = tf.zeros_like(embedding) + embedding_gradientsdelta = 0.1 * embedding_gradients / (tf.math.sqrt(tf.reduce_sum(embedding_gradients**2)) + 1e-8) # 計算擾動model.trainable_variables[0].assign_add(delta)with tf.GradientTape() as tape2:y_pred = model(x,training=True)loss1 = loss_func(y['lambda'],tf.squeeze(y_pred[0]))loss2 = loss_func(y['lambda_1'],tf.squeeze(y_pred[1]))loss3 = loss_func(y['lambda_2'],tf.squeeze(y_pred[2]))loss4 = loss_func(y['lambda_3'],tf.squeeze(y_pred[3]))new_loss = loss1+loss2+loss3+loss4gradients = tape2.gradient(new_loss,model.trainable_variables)model.trainable_variables[0].assign_sub(delta)optimizer.apply_gradients(zip(gradients,model.trainable_variables))train_loss.update_state(new_loss)但由于本人設備配置有限,并沒有得到相應的對抗訓練結果,大家可以自己嘗試~
總結
完整代碼地址: https://github.com/zhengyanzhao1997/TF-NLP-model/blob/main/model/train/Three_relation_extract.py
參考文章:
蘇劍林. (2020, Jan 03). 《用bert4keras做三元組抽取 》[Blog post]. Retrieved from https://kexue.fm/archives/7161
一人之力,刷爆三路榜單!信息抽取競賽奪冠經驗分享
總結
以上是生活随笔為你收集整理的信息抽取(三)三元关系抽取——改良后的层叠式指针网络,让我的模型F1提升近4%(接上篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【云音乐】从手游APP到云音乐视频标签分
- 下一篇: 信息抽取(四)【NLP论文复现】Mult