语义匹配(一)【NLP论文复现】Sentence-BERT 句子语义匹配模型的tensorflow实现以及训练Trick
Sentence-BERT 句子語義匹配模型的tensorflow實(shí)現(xiàn)以及訓(xùn)練trick
- 論文模型回顧
- 建模與訓(xùn)練
- 模型代碼部分
- 數(shù)據(jù)處理
- 訓(xùn)練
- 模型訓(xùn)練Trick
- trick1 warm up
- 代碼實(shí)現(xiàn):
- trick2 focal loss
- 代碼實(shí)現(xiàn):
- 總結(jié)與思考
論文模型回顧
論文鏈接:https://arxiv.org/abs/1908.10084
文章在已有的語義匹配模型的基礎(chǔ)上提出了基于Bert的句義匹配孿生網(wǎng)絡(luò)
模型介紹:將兩個(gè)句子通過Bert(注意:在對(duì)句子相似度建模時(shí),兩個(gè)句子經(jīng)過的Bert層應(yīng)該是共享權(quán)重的,及同一個(gè)Bert)進(jìn)行特征提取后,取最后一層的hidde_layers進(jìn)行pooling,文章試驗(yàn)了直接取CLS向量、max_pooling、mean_pooling,結(jié)果顯示mean_pooling效果最好。將pooling后得到的兩個(gè)句子向量進(jìn)行特征交叉,文章嘗試了多種交叉方式,|u-v|的效果最好,當(dāng)然使用者可以根據(jù)具體任務(wù)和場景自行嘗試多種交叉方法;最后通過softmax層。
訓(xùn)練好模型之后,我們可以將語料庫中的句子通過單塔轉(zhuǎn)化為對(duì)應(yīng)的句子向量,當(dāng)待匹配句子進(jìn)入時(shí),通過向量相似度檢索來直接搜索相似句子,節(jié)省了大量的模型推理時(shí)間。
建模與訓(xùn)練
tensorflow 2.0.0
transformers 3.1.0
模型代碼部分
class BertNerModel(tf.keras.Model):dense_layer = 512class_num = 2drop_out_rate = 0.5def __init__(self,pretrained_path,config,*inputs,**kwargs):super(BertNerModel,self).__init__()config.output_hidden_states = Trueself.bert = TFBertModel.from_pretrained(pretrained_path,config=config,from_pt=True)self.liner_layer = tf.keras.layers.Dense(self.dense_layer,activation='relu')self.softmax = tf.keras.layers.Dense(self.class_num,activation='softmax')self.drop_out = tf.keras.layers.Dropout(self.drop_out_rate) def call(self,input_1):hidden_states_1,_,_ = self.bert((input_1['input_ids'],input_1['token_type_ids'],input_1['attention_mask']))hidden_states_2,_,_ = self.bert((input_1['input_ids_2'],input_1['token_type_ids_2'],input_1['attention_mask_2']))hidden_states_1 = tf.math.reduce_mean(hidden_states_1,1)hidden_states_2 = tf.math.reduce_mean(hidden_states_2,1)concat_layer = tf.concat((hidden_states_1,hidden_states_2,tf.abs(tf.math.subtract(hidden_states_1, hidden_states_2))),1,)drop_out_l = self.drop_out(concat_layer)Dense_l = self.liner_layer(drop_out_l)outputs = self.softmax(Dense_l)print(outputs.shape)return outputs這里比較難受的是,在自定義模型的時(shí)候本來想直接繼承transformers的TFBertPreTrainedModel類,但是發(fā)現(xiàn)這傳入訓(xùn)練數(shù)據(jù)的時(shí)候需要以元組的形式傳入,但是在tf model.fit的時(shí)候會(huì)報(bào)錯(cuò)無法識(shí)別元組+datasets的數(shù)據(jù),因此這里改為繼承tf.keras.Model,在類中直接加入TFBertModel.from_pretrained加載之后的TFBertModel,再在后面接自定義的層。
數(shù)據(jù)處理
def data_proceed(path,batch_size,tokenizer):data = pd.read_csv(path)data = data.sample(frac=1)inputs_1 = tokenizer(list(data['sentence1']), padding=True, truncation=True, return_tensors="tf",max_length=30)inputs_2 = tokenizer(list(data['sentence2']), padding=True, truncation=True, return_tensors="tf",max_length=30)inputs_1 = dict(inputs_1)inputs_1['input_ids_2'] = inputs_2['input_ids']inputs_1['token_type_ids_2'] = inputs_2['token_type_ids']inputs_1['attention_mask_2'] = inputs_2['attention_mask']label = list(data['label'])steps = len(label)//batch_sizex = tf.data.Dataset.from_tensor_slices((dict(inputs_1),label))return x,steps訓(xùn)練
optimizer = tf.keras.optimizers.Adam(learning_rate=5e-5)bert_ner_model.compile(optimizer=optimizer,loss='sparse_categorical_crossentropy',metrics=['acc'])bert_ner_model.fit(train_data,epochs=5,verbose=1,steps_per_epoch=steps_per_epoch,validation_data=test_data,validation_steps=validation_steps)模型訓(xùn)練Trick
trick1 warm up
原文中提到了在訓(xùn)練時(shí)warm up learning rate的訓(xùn)練技巧
由于剛開始訓(xùn)練時(shí),模型的權(quán)重(weights)是隨機(jī)初始化的,此時(shí)若選擇一個(gè)較大的學(xué)習(xí)率,可能帶來模型的不穩(wěn)定(振蕩),選擇Warmup預(yù)熱學(xué)習(xí)率的方式,可以使得開始訓(xùn)練的幾個(gè)epoches或者一些steps內(nèi)學(xué)習(xí)率較小,在預(yù)熱的小學(xué)習(xí)率下,模型可以慢慢趨于穩(wěn)定,等模型相對(duì)穩(wěn)定后再選擇預(yù)先設(shè)置的學(xué)習(xí)率進(jìn)行訓(xùn)練,使得模型收斂速度變得更快,模型效果更佳。
該段摘自深度學(xué)習(xí)訓(xùn)練策略-學(xué)習(xí)率預(yù)熱Warmup
代碼實(shí)現(xiàn):
class WarmupExponentialDecay(Callback):def __init__(self,lr_base=0.0002,decay=0,warmup_epochs=0,steps_per_epoch=0):self.num_passed_batchs = 0 #一個(gè)計(jì)數(shù)器self.warmup_epochs=warmup_epochs self.lr=lr_base #learning_rate_baseself.decay=decay #指數(shù)衰減率self.steps_per_epoch=steps_per_epoch #也是一個(gè)計(jì)數(shù)器def on_batch_begin(self, batch, logs=None):# params是模型自動(dòng)傳遞給Callback的一些參數(shù)if self.steps_per_epoch==0:#防止跑驗(yàn)證集的時(shí)候唄更改了if self.params['steps'] == None:self.steps_per_epoch = np.ceil(1. * self.params['samples'] / self.params['batch_size'])else:self.steps_per_epoch = self.params['steps']if self.num_passed_batchs < self.steps_per_epoch * self.warmup_epochs:K.set_value(self.model.optimizer.lr,self.lr*(self.num_passed_batchs + 1) / self.steps_per_epoch / self.warmup_epochs)else:K.set_value(self.model.optimizer.lr,self.lr*((1-self.decay)**(self.num_passed_batchs-self.steps_per_epoch*self.warmup_epochs)))self.num_passed_batchs += 1def on_epoch_begin(self,epoch,logs=None):#用來輸出學(xué)習(xí)率的,可以刪除print("learning_rate:",K.get_value(self.model.optimizer.lr))trick2 focal loss
在實(shí)際應(yīng)用中,負(fù)樣本往往來自于負(fù)采樣,大量的負(fù)采樣會(huì)時(shí)訓(xùn)練時(shí)負(fù)樣本數(shù)量遠(yuǎn)多余正樣本數(shù)量導(dǎo)致訓(xùn)練樣本不平衡,且軟負(fù)采樣的負(fù)樣本往往非常弱,在模型推理時(shí)置信度一般較高,加入focal loss可以讓模型專注于那些置信度低的比較難區(qū)分的樣本,提高模型的訓(xùn)練效果。
詳細(xì)可以查看我的之前的博客Tensorlfow2.0 二分類和多分類focal loss實(shí)現(xiàn)和在文本分類任務(wù)效果評(píng)估
代碼實(shí)現(xiàn):
def sparse_categorical_crossentropy(y_true, y_pred):y_true = tf.reshape(y_true, tf.shape(y_pred)[:-1])y_true = tf.cast(y_true, tf.int32)y_true = tf.one_hot(y_true, K.shape(y_pred)[-1])return tf.keras.losses.categorical_crossentropy(y_true, y_pred)def loss_with_gradient_penalty(model,epsilon=1):def loss_with_gradient_penalty_2(y_true, y_pred):loss = tf.math.reduce_mean(sparse_categorical_crossentropy(y_true, y_pred))embeddings = model.variables[0]gp = tf.math.reduce_sum(tf.gradients(loss, [embeddings])[0].values**2)return loss + 0.5 * epsilon * gpreturn loss_with_gradient_penalty_2#使用方法: model.compile(optimizer=optimizer, loss=softmax_focal_loss,metrics=[tf.keras.metrics.SparseCategoricalAccuracy()])效果:
在公開數(shù)據(jù)集上:該focal_loss可以很好的抑制模型過擬合且模型效果也有1個(gè)多點(diǎn)的提升。
總結(jié)與思考
本次訓(xùn)練使用的數(shù)據(jù)集:LCQMC 是哈爾濱工業(yè)大學(xué)在自然語言處理國際頂會(huì) COLING2018 構(gòu)建的問題語義匹配數(shù)據(jù)集,其目標(biāo)是判斷兩個(gè)問題的語義是否相同。準(zhǔn)確率能達(dá)到90%+
但在實(shí)際測(cè)試時(shí)發(fā)現(xiàn),模型推理相似的問句條件比較嚴(yán)格,無法做到真的根據(jù)語義進(jìn)行匹配,(對(duì)于同義詞、別名等無法識(shí)別區(qū)分)需要應(yīng)用到實(shí)際生產(chǎn)工作則對(duì)訓(xùn)練樣本的要求比較嚴(yán)格。
拓展思考:由于該孿生模型的兩個(gè)句子共享一個(gè)bert參數(shù),因此要求兩個(gè)句子的分布或者說兩個(gè)句子必須來自統(tǒng)一場景,需要在格式、長度、風(fēng)格、句式上比較相近。因此在問句匹配、句子相似度判斷等工作上能有不錯(cuò)的表現(xiàn)。但可能不適用于類似評(píng)論于商品相關(guān)度等任務(wù)的分析(因?yàn)樵u(píng)論文本于商品介紹文本不統(tǒng)一,經(jīng)過同一個(gè)Bert會(huì)產(chǎn)生偏差)因此思考對(duì)于此類問題,借鑒雙塔模型,使用兩個(gè)不同的Bert來提取兩種分布的句子特征,或許仍能有不錯(cuò)的標(biāo)簽,之后有機(jī)會(huì)會(huì)試驗(yàn)一下~
總結(jié)
以上是生活随笔為你收集整理的语义匹配(一)【NLP论文复现】Sentence-BERT 句子语义匹配模型的tensorflow实现以及训练Trick的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文本分类(一)EWECT微博情绪分类大赛
- 下一篇: 信息抽取(一)机器阅读理解——样本数据处