基於BERT的多模型融合借鑒


本次介紹假新聞賽道一第一名的構建思路,大家一起學習下

任務描述

 

文本是新聞信息的主要載體,對新聞文本的研究有助於虛假新聞的有效識別。虛假新聞文本檢測,具體任務為:給定一個新聞事件的文本,判定該事件屬於真實新聞還是虛假新聞。該任務可抽象為NLP領域的文本分類任務,根據新聞文本內容,判定該新聞是真新聞還是假新聞。針對該任務,本文采用BERT-Finetune、BERT-CNN-Pooling、BERT-RCN-Pooling的多種結構進行融合,在輸入上引入字詞結合的形式,另外充分利用假新聞的關鍵詞特征進行優化。在智源\&計算所-互聯網虛假新聞檢測挑戰賽的假新聞文本識別這個評測任務上,該文提出的方法在最終的評測數據上達到F1為 0.92664的成績。

模型介紹

模型結構

本文采用了多種模型,下以BERT-CNN-Pooling模型為例介紹,見下圖。

該模型采用BERT模型提取出字向量(不Finetune),然后結合騰訊詞向量,作為最終的詞向量輸入到1維卷積網絡中。在池化過程中同時選擇最大池化和平均池化,最后將其結果相加,接入一個Dense層中得到結果。

除了此模型外,本文還是用了BERT-Finetune、BERT-RCN-Pooling模型。

模型參數和融合細節

BERT模型可采用roeberta_zh_L-24_H-1024_A-16,其優點為准確率高,缺點為顯存占用率較高。以BERT-Finetune為例,在訓練工程中,batch_size選擇為4,maxLen選擇為164,epoch數選擇為3,learning_rate為前兩個epoch為1e-5,后一個為1e-6。

本文選擇了10折交叉驗證,每折中選擇召回率較高的模型(一般為第二個epoch或第三個epoch訓練出的模型)。另外,由於數據假新聞識別正確率較高,其召回率較低,因此在這10個模型進行融合時,可以將10個模型的直接結果相加,當其大於3認為是假新聞,小於3即為真新聞。

同理,在BERT-CNN-Pooling、BERT-RCN-Pooling模型中也采取以上的融合策略,在BERT-Finetune、BERT-CNN-Pooling、BERT-RCN-Pooling這3個模型間采用該策略(值改為1)。

在模型融合時發現,假新聞喜歡對部分人、地、名詞、動詞進行造謠。這些詞的獲取可通過對所有的假新聞和test集合,利用textrank4zh進行關鍵詞獲取,最后經過人工篩選,加入到模型融合的評判中,具體為當新聞的關鍵詞含有這些詞時,就有假新聞的傾向,此時評判值可以降低,利用這個關鍵詞特征可以發現更多的假新聞,使得假新聞評判效果更好。

實驗結果與分析

實驗結果見下表,其中評判值即為判斷真假新聞的臨界值,BERT-RCN-Pooling、BERT-CNN-Pooling的實驗結果基本與BERT_Finetune類似。

由表一可知:單模型在真假新聞判定的結果並不是很好,而將單模型進行10折交叉驗證后准確率提升很大,說明10折交叉驗證還是很有必要的。另外,融合BERT_Finetune+BERT-RCN-Pooling+BERT-CNN-Pooling這三個模型並加上關鍵詞特征也會有不小的提升。

本文使用模型都較為基礎,基本是通過交叉驗證和模型融合提升測試集得分。在多模型融合上,測試了多種模型,最后處於效果和速度的考慮選擇了這三種。

結論

本文介紹了小組參加智源\&計算所-互聯網虛假新聞檢測挑戰賽假新聞文本識別評測的基本情況。本文采用BERT-Finetune、BERT-CNN-Pooling、BERT-RCN-Pooling的多種結構進行融合,在每一模型基礎上進行10折交叉驗證,然后利用假新聞的關鍵詞特征進行優化,最終達到了不錯的性能。

 

代碼精華

字詞向量結合

def remake(x,num):
    L = []
    for i,each in enumerate(num):
        L += [x[i]]*each
    return L
words = [t for t in jieba.cut(text)]
temp = [len(t) for t in words]
x3 = [word2id[t] if t in vocabulary else 1 for t in words]
x3 = remake(x3, temp)
if len(x3) < maxlen - 2:
    x3 = [1] + x3 + [1] + [0] * (maxlen - len(x3) - 2)
else:
    x3 = [1] + x3[:maxlen - 2] + [1]

主要思路是把詞向量映射到每個字上,如:中國,中國的詞向量為a,那么體現在字上即為[a , a],若中國的字向量為[b , c], 相加后即為[a+b, a+c]。此處x3即為對稱好的詞向量,直接輸入到Embedding層即可。

支持mask的最大池化

 
class MaskedGlobalMaxPool1D(keras.layers.Layer):
    def __init__(self, **kwargs):
        super(MaskedGlobalMaxPool1D, self).__init__(**kwargs)
        self.supports_masking = True

    def compute_mask(self, inputs, mask=None):
        return None

    def compute_output_shape(self, input_shape):
        return input_shape[:-2] + (input_shape[-1],)

    def call(self, inputs, mask=None):
        if mask is not None:
            mask = K.cast(mask, K.floatx())
            inputs -= K.expand_dims((1.0 - mask) * 1e6, axis=-1)
        return K.max(inputs, axis=-2)

 

支持mask的平均池化

class MaskedGlobalAveragePooling1D(keras.layers.Layer):

    def __init__(self, **kwargs):
        super(MaskedGlobalAveragePooling1D, self).__init__(**kwargs)
        self.supports_masking = True

    def compute_mask(self, inputs, mask=None):
        return None

    def compute_output_shape(self, input_shape):
        return input_shape[:-2] + (input_shape[-1],)
        
    def call(self, x, mask=None):
        if mask is not None:
            mask = K.repeat(mask, x.shape[-1])
            mask = tf.transpose(mask, [0, 2, 1])
            mask = K.cast(mask, K.floatx())
            x = x * mask
            return K.sum(x, axis=1) / K.sum(mask, axis=1)
        else:
            return K.mean(x, axis=1)

 

Bert Finetune

x1_in = Input(shape=(None,))
x2_in = Input(shape=(None,))
bert_model = load_trained_model_from_checkpoint(config_path, checkpoint_path)
for l in bert_model.layers:
    l.trainable = True
x = bert_model([x1_in, x2_in])
x = Lambda(lambda x: x[:, 0])(x)
x = Dropout(0.1)(x)
p = Dense(1, activation='sigmoid')(x)
model = Model([x1_in, x2_in], p)
model.compile(
        loss='binary_crossentropy',
        optimizer=Adam(1e-5), 
        metrics=['accuracy']
    )

 

BERT+TextCNN

x1_in = Input(shape=(None,))
x2_in = Input(shape=(None,))
x3_in = Input(shape=(None,))
x1, x2,x3 = x1_in, x2_in,x3_in
x_mask = Lambda(lambda x: K.cast(K.greater(K.expand_dims(x, 2), 0), 'float32'))(x1)
bert_model = load_trained_model_from_checkpoint(config_path, checkpoint_path)
embedding1= Embedding(len(vocabulary) + 2, 200,weights=[embedding_index],mask_zero= True)
x3 = embedding1(x3)
embed_layer = bert_model([x1_in, x2_in])
embed_layer  = Concatenate()([embed_layer,x3])
x = MaskedConv1D(filters=256, kernel_size=3, padding='same', activation='relu')(embed_layer )
pool = MaskedGlobalMaxPool1D()(x)
ave = MaskedGlobalAveragePooling1D()(x)
x = Add()([pool,ave])
x = Dropout(0.1)(x)
x = Dense(32, activation = 'relu')(x)
p = Dense(1, activation='sigmoid')(x)
model = Model([x1_in, x2_in,x3_in], p)
model.compile(
    loss='binary_crossentropy',
    optimizer=Adam(1e-3),
    metrics=['accuracy']
)

BERT + RNN + CNN

x1_in = Input(shape=(None,))
x2_in = Input(shape=(None,))
x3_in = Input(shape=(None,))
x1, x2,x3 = x1_in, x2_in,x3_in
x_mask = Lambda(lambda x: K.cast(K.greater(K.expand_dims(x, 2), 0), 'float32'))(x1)
bert_model = load_trained_model_from_checkpoint(config_path, checkpoint_path)
embedding1= Embedding(len(vocabulary) + 2, 200,weights=[embedding_index],mask_zero= True)
x3 = embedding1(x3)
embed_layer = bert_model([x1_in, x2_in])
embed_layer  = Concatenate()([embed_layer,x3])
embed_layer = Bidirectional(LSTM(units=128,return_sequences=True))(embed_layer)
embed_layer = Bidirectional(LSTM(units=128,return_sequences=True))(embed_layer)
x = MaskedConv1D(filters=256, kernel_size=3, padding='same', activation='relu')(embed_layer )
pool = MaskedGlobalMaxPool1D()(x)
ave = MaskedGlobalAveragePooling1D()(x)
x = Add()([pool,ave])
x = Dropout(0.1)(x)
x = Dense(32, activation = 'relu')(x)
p = Dense(1, activation='sigmoid')(x)
model = Model([x1_in, x2_in,x3_in], p)
model.compile(
    loss='binary_crossentropy',
    optimizer=Adam(1e-3),
    metrics=['accuracy']
)

10折交叉訓練

for train,test in kfold.split(train_data_X,train_data_Y):
    model = getModel()
    t1,t2,t3,t4 = np.array(train_data_X)[train], np.array(train_data_X)[test],np.array(train_data_Y)[train],np.array(train_data_Y)[test]
    train_D = data_generator(t1.tolist(), t3.tolist())
    dev_D = data_generator(t2.tolist(), t4.tolist())
    evaluator = Evaluate()
    model.fit_generator(train_D.__iter__(),
                        steps_per_epoch=len(train_D),
                        epochs=3,
                        callbacks=[evaluator,lrate]
                        )
    del model
    K.clear_session()

關鍵詞特征

def extract(L):
    return  [r.word for r in L]
    
tr4w = TextRank4Keyword()
result = []
for sentence in train:
    tr4w.analyze(text=text, lower=True, window=2)
    s =  extract(tr4w.get_keywords(10, word_min_len=1))
    result = result + s

c = Counter(result)
print(c.most_common(100))

找到詞后從其中人工遴選,選出每類的詞,另外,在test集合中也運行該代碼,同時用jieba輔助分割詞的類。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM