螞蟻金融NLP競賽——文本語義相似度賽題總結

1前言

許久沒有更新博客了,主要是忙於考試周和最近參加的一個螞蟻金融的NLP比賽——文本語義相似度賽題。話不多說,直奔主題。本篇博客主要記錄的是自己入門NLP以來第一次參加NLP性質的賽題的詳細解題過程。接下來將分成三個部分進行敘述:賽題描述、解題思路及相關代碼實現、賽題總結.
項目代碼地址及說明

2賽題描述

說明:問題相似度計算,即給定客服裏用戶描述的兩句話,用算法來判斷是否表示了相同的語義。若語義相同則判斷爲1,不相同則爲0.

1 “花唄如何還款” –“花唄怎麼還款”:同義問句
2 “花唄如何還款” – “我怎麼還我的花被呢”:同義問句
3 “花唄分期後逾期瞭如何還款”– “花唄分期後逾期了哪裏還款”:非同義問句

需要思考和解決的問題: 1.句子對中存在錯別字、同義詞、詞序變換等問題。2.兩句話很類似,僅僅有一處細微的差別,最後的語義卻不同。
賽題的評價指標:
賽題評分以F1-score爲準,得分相同時,參照accuracy排序。對於F1的計算,需要明白以下幾個概念,True Positive(TP)意思表示做出同義的判定,而且判定是正確的,TP的數值表示正確的同義判定的個數; False Positive(FP)意思表示做出同義的判定,但是判定是錯誤的,數值表示錯誤的同義判定的個數;True Negative(TN)意思表示做出不是同義的判定,而且判定是正確的。數值表示正確的不同義判定個數;False Negative(FN)意思表示做出不是同義的判定,而且判定是錯誤的。數值表示錯誤的不同義判定個數。
計算公式:

精準率(做出爲同義的判定中,事實上爲同義的比例):precision rate = TP / (TP + FP)
召回率(事實上爲同義的語句對中,有多少比例的同義對被預測出來了):
recall rate = TP / (TP + FN)
準確率:accuracy = (TP + TN) / (TP + FP + TN + FN)
F1-score = 2 * precision rate * recall rate / (precision rate + recall rate)

3解題思路及相關代碼實現

3.1賽題整體分析

分析:本賽題中需要設計一個算法計算兩個語句的語義相似度,最後表示是否爲同義和非同義。對於要設計出一個算法,首先,需要先明確算法的輸入和輸出。很容易的判斷出算法最終的輸出是0或1,而算法的輸入則是需要思考的地方,如果考慮以神經網絡進行建模的話,需要通過表示學習方法將輸入的語句用向量表示(詞向量),如果以機器學習的分類方法進行建模的話,則需要對輸入的語句進行特徵的抽取,特徵的選擇,和特徵的表示。輸入語句的詞向量表示需要思考的問題——訓練詞向量的語料庫和訓練詞向量的模型方法的選擇。

3.2訓練數據的分析

整個賽題相關的數據下載地址,密碼爲:5ig5。

1.數據label包含兩類1和0,屬於經典的二分類問題,數據集中類別1和類別0的比例大致爲4.5:1,屬於分類不平衡的現象(這點也是後面需要解決的問題也是賽題成績提高的重要點)。
2.需要判斷的每隊語句隊存在着一定的錯別字,如:“花唄”寫成“花被”,“登錄”寫成“登陸等,語句中也包含相應的同義詞如:”爲啥”:”爲什麼”,雖然這些人可以立馬判斷出,但是算法卻是需要解決的問題點。
3.樣本數據不僅類別分配不平衡,其中正例(同義)的語句隊數據量偏少,模型比較難學習出同義語句隊的特徵。
4.中文句子中存有未登錄詞的現象,經過測試像螞蟻花唄,螞蟻借唄…等新詞經jieba分詞是區分不出來的。

3.3整體思路

本次賽題最終的評價指標F1爲0.6238,賽題的評價指標F1提升過程0.29——>0.45->0.53->0.5925->0.6159->0.6238。本次賽題純屬是第一次參加NLP的比賽,一步一步慢慢積累解題技能的過程。最終的解題思路爲:

最終採用機器學習的方法對本賽題的分類問題進行建模,對機器學習輸入主要是兩個方面:抽取的NLP統計特徵和經過深度模型訓練輸出的語句的相似度。最終的分類模型採用Stacking的集成學習方式進行訓練。

3.4具體實現過程和步驟

Step1 前期外部數據整理工作
根據前期訓練數據的分析工作,需要使用到的外部數據有,主要部分是人工尋找的:

1.用於語句隊的分詞的停用詞表——stop_words.txt
2.用於jieba分詞的新詞的自定義字典——dict_all.txt
3.用於糾錯及部分同義詞替換規則的文件——spelling_corrections.json
4.用於語義相似度的深度學習模型的預訓練的詞向量(知乎問答語料庫訓練的300維度)——sgns.zhihu.bigram
5.用於計算兩個語句中的疑問詞的相似度的疑問詞相似的規則文件——doubt_words.txt

Step2 文本預處理工作
該部分主要的目的是:爲最終算法的輸入提供一個相對較正確的表示,主要的工作是:將尋找的規則文件的詞語對在語句中進行替換,去除語句中的停用詞,將語句進行分詞處理,並根據預訓練的詞向量的矩陣,構建出詞彙表的詞向量矩陣embedding_matrix.其中構建預訓練的詞向量使用的語料庫爲整個訓練集10447對個語句組成,訓練方法是Word2Vec中的CBOW方式,negative sampling技巧。另外一種預訓練的詞向量是外部網上資源知乎問答語料庫經過Word2vec訓練的。詞向量(詞的分佈表示)主要是爲了作爲神經網絡的Embeding層的輸入。
詳細的過程爲:

    # step 1 加載原數據
    jieba.load_userdict(project.aux_dir + dict_path)
    data_local_df = pd.read_csv(project.data_dir + train_all, sep='\t', header=None,names=["index", "s1", "s2", "label"])
    data_test_df = pd.read_csv(project.data_dir + test_all, sep='\t', header=None, names=["index", "s1", "s2", "label"])
    data_all_df = pd.read_csv(project.data_dir + train_data_all, sep='\t', header=None, names=["index", "s1", "s2", "label"])
    # 預訓練中文字符向量
    pre_train_char_w2v()
    # 訓練集中的語句的文本處理,去除停用詞,根據規則表替換相應的詞,使用jieba對語句進行分詞處理
    preprocessing(data_local_df,'train_0.6_seg')
    preprocessing(data_test_df,'test_0.4_seg')
    preprocessing(data_all_df,'data_all_seg')

    # 保存label
    project.save(project.features_dir + 'y_0.4_test.pickle', data_test_df['label'].tolist())
    project.save(project.features_dir + 'y_0.6_train.pickle', data_local_df['label'].tolist())
    project.save(project.features_dir + 'y_train.pickle', data_all_df['label'].tolist())

    # step 2預訓練中文詞組向量
    pre_train_w2v()

    # step 3記錄訓練集中的詞彙表中的詞對應的詞向量表示
    process_save_embedding_wv('train_all_w2v_embedding_matrix.pickle',type=2,isStore_ids=True)
    # process_save_embedding_wv('zhihu_w2v_embedding_matrix.pickle',type=2,isStore_ids=False)
    # process_save_embedding_wv('zhihu_w2v_embedding_matrix.pickle',type=3,isStore_ids=False)

    # step 4 char wordembedding
    process_save_char_embedding_wv(isStore_ids=True)

Step3 文本特徵抽取工作
描述:賽題的重點部分,賽題成績的好壞主要取決於特徵的選取工作。由於不像很多大佬一樣一開始便能設計出針對語句對進行語義相似度判斷的神經網絡模型,此部分是一點一點調研和摸索出來的。因爲最終的分類模型是通過機器學習的方法進行建立,所以需要以多維度的特徵作爲計算語句對語義相似度算法的輸入。最終我們選取的特徵包括兩個方面,一方面是計算語句對語義相似度的神經網絡的輸出作爲特徵之一,另一方面是統計語句對的NLP數據的特徵。
a.兩個語句的長度上的差距.
b.兩個語句的編輯距離.
c.兩個語句的n-gram相似性的特徵.
d.兩個語句的詞的統計特徵.包括相同詞的個數,不同詞的個數,Jaccard相似度。
e.兩個語句中的疑問詞的相似度.主要是根據疑問詞相似度規則文件進行計算。
f.兩個語句是否同時包括螞蟻花唄和螞蟻借唄相關的主題.,觀測到訓練數據中的語句對基本全都是關於螞蟻花唄和螞蟻借唄相關的問題。
g.兩個語句的詞向量組合的相似度.主要是根據全部訓練集做語料庫使用word2vec訓練的詞向量計算的兩個語句的詞向量的組合相似度。
h.兩個語句神經網絡編碼的曼哈頓距離相似度和餘弦相似度主要是根據兩個語句的預訓練詞向量輸入經過LSTM進行編碼計算出兩個語句的語義向量的曼哈頓距離和餘弦相似度作爲最後的機器學習的分類模型特徵之一。
i.兩個語句的神經網絡編碼的match vector形式計算的相似度.
j.兩個語句的神經網絡編碼的改進的Compare-Aggregate模型的相似度
神經網絡模型說明
此處相似度特徵表現最好的神經網絡模型是改進的Compare-Aggregate模型,其次是兩個語句神經網絡編碼的曼哈頓距離相似度和餘弦相似度的模型。所以接下來對這兩個神經網絡模型做詳細的介紹。接下來將詳細介紹這三種神經網絡模型,這也是自己在賽題中的使用模型的嘗試改進和理解性的構建模型的過程。
模型一:基於LSTM進行語義編碼的曼哈頓距離相似度和餘弦相似度的模型
圖1
提交結果後的平臺測試的F1結果爲0.456,該模型主要是簡單對輸入的語句序列進行LSTM的編碼獲取得到句子的語義表示向量,再經過語義向量的曼哈頓計算和餘弦距離計算,最後依據這兩項訓練出模型參數。
具體實現爲:

class ManDist(Layer):
    def call(self, inputs, **kwargs):
        """This is where the layer's logic lives."""
        self.res  = K.exp(- K.sum(K.abs(inputs[0]-inputs[1]),axis = 1,keepdims = True))
        return self.res
class ConsDist(Layer):
    def call(self, inputs, **kwargs):
        self.res = K.sum(inputs[0] * inputs[1],axis=1,keepdims=True)/(K.sum(inputs[0]**2,axis=1,keepdims=True) * K.sum(inputs[1]**2,axis=1,keepdims=True))
        return self.res

模型二:基於LSTM進行語義編碼的match vector形式計算的相似度的模型
該模型是在模型一的基礎上進行了改進,提交結果後的平臺測試的F1結果爲0.53.改進的地方是在使用LSTM進行對語句詞向量編碼後將兩個語句的語義編碼向量進行點乘和減法計算後再進行接下來的相似度計算。提升的原因是:語句的語義向量除了LSTM編碼形成的語義向量還附加了兩個語句的語義向量的點乘和減法形成的語義交互。
具體實現爲:

    def call(self, inputs, **kwargs):
        encode_s1 = inputs[0]
        encode_s2 = inputs[1]
        sentence_differerce = encode_s1 - encode_s2
        sentece_product = encode_s1 * encode_s2
        self.match_vector = K.concatenate([encode_s1,sentence_differerce,sentece_product,encode_s2],1)
        return self.match_vector

模型三:改進的Compare-Aggregate的模型
模型三做出的改進:第一部分,該模型是基於論文《A Decomposable Attention Model for Natural Language Inference》中的Attention機制,在語句中的詞級別上添加了注意力機制,讓經過LSTM編碼後的語義向量在語句中的詞上更有了重心,主要體現在語句中各個詞的詞向量經過attention機制後的權重分配不一樣了。此外,還藉助了該論文中的Compare部分,將attention機制表示的語句詞向量與雙向LSTM語義編碼的向量進行Concatenate連接。第二部分,模型三除了以兩個語句的詞組級別的向量做爲輸入外,還增加了兩個語句的字級別的向量作爲輸入。增加字符級的輸入,主要是爲了解決out-of-vocabulary詞組級別的問題。第三部分,在語句的詞向量的語義向量上添加了基於CNN的交互式處理思想,這種方式考慮了句子之間的所有交互屬性。
根據模型三提交結果後的平臺測試的F1結果提升到0.59.
圖2
最終的代碼實現

    # 提取深度學習特徵
    extract_feature_siamese_lstm_manDist()
    extract_feature_siamese_lstm_attention()
    extract_feature_siamese_lstm_dssm()
    # 提取NLP特徵
    extract_sentece_length_diff()
    extract_edit_distance()
    extract_ngram()
    extract_sentence_diff_same()
    extract_doubt_sim()
    extract_sentence_exist_topic()
    extract_word_embedding_sim()

每種抽取方法對應一個函數,抽取特徵的步驟:
step1 定義抽取特徵的方法名
step2 載入需要抽取特徵的數據
step3 定義抽取特徵的方法(如果是深度學習需要調用./final_demo/train_model.py文件中構建模型的方法)
step4 逐行讀取數據,並進行特徵的抽取,保存到相應的np.array數組中
step5 特徵的保存
調用方法:
project.save_features(feature_train, feature_test, col_names, feature_name)
參數說明:訓練集的抽取出的特徵feature_train,測試集的抽取特徵feature_test,抽取特徵的方法的多列特徵的列名col_names,抽取特徵的方式名feature_name

Step4 Stacking分類模型的建立工作
通過上述的特徵提取方法將會提取出19個特徵,通過對提取出的特徵使用機器學習建立分類模型。一開始選擇使用了基本的sklearn中的LogisticRegression分類方法。後來提出使用集成學習中的Stacking模式將Sklearn中的多個分類學習方法進行融合,最終提交結果後的平臺測試的F1結果提升到0.61.
圖3
本次賽題使用的兩層Stacking方式,選用了GussianNBClassifier、RandomForestClassifier、LogisticRegression、DecisionTreeClassifier四個基分類器作爲第一層Stacking基模型。第二層Stacking選用的是RandomForestClassifier分類器進行訓練的。具體實現的方式爲:

    # stacking 第一層模型訓練,分別使用基分類器對訓練集X_train進行5折交叉驗證,在使用訓練的模型預測X_test取均值。作爲第二層Stacking模型的輸入。
    gnb_cls = GussianNBClassifier()
    gnb_oop_train,gnb_oofp_val = gnb_cls.get_model_out(,y_train,X_test)

    rf_cls = RFClassifer()
    rf_oop_train, rf_oofp_val = rf_cls.get_model_out(X_train, y_train, X_test)

    lg_cls = LogisicClassifier()
    lg_oop_train, lg_oofp_val = lg_cls.get_model_out(X_train, y_train, X_test)

    dt_cls = DecisionClassifier()
    dt_oop_train, dt_oofp_val = dt_cls.get_model_out(X_train, y_train, X_test)
     # 構造第二層Stacking模型的輸入
    input_train = [gnb_oop_train,rf_oop_train,lg_oop_train,dt_oop_train]
    input_test = [gnb_oofp_val,rf_oofp_val,lg_oofp_val,dt_oofp_val]

    stacked_train = np.concatenate([data.reshape(-1,1) for data in input_train],axis=1)
    stacked_test = np.concatenate([data.reshape(-1,1) for data in input_test],axis=1)

    # stacking 第二層模型訓練
    second_model = DecisionTreeClassifier(max_depth=3,class_weight={0: 1, 1: 4})
    second_model.fit(stacked_train,y_train)

Step5 整個項目的運行

1.選出抽取特徵的方法名組合成需要特徵輸入list
2.加載多個抽取方法抽取出的特徵數據和訓練集的label數據
調用方法:project.load_feature_lists(feature_names_list)
參數說明:feature_names_list爲你的抽取特徵的方法名組合成list
返回參數說明:你的訓練集經過多種抽取方法組合成的多個列,你的測試集經過多種抽取方法組合成的多個列,每種抽取方法對應的列的index。
注意:一種抽取方法可能對應着多種特徵列
3.構建最終的分類模型,交叉驗證的方式

Step6 前期訓練數據的分析出現的問題的處理
1.對於樣本分類不平衡的問題,通過最終的樣本類別權重調整實現樣本不平衡問題的解決,在Sklearn中的分類模型用設置class_weight參數即可,查看Sklearn的原理設置class_weight就是改變sample_weight。
2.對於正例的語句對樣本數量少的問題,通過將正例的樣本語句對進行順序調換,形成新的正例樣本對。
通過解決上述兩個問題,最終提交結果後的平臺測試的F1結果提升到0.6238.

4賽後總結

1.首先需要自己定義算法的賽題,首先需要考慮好算法的輸入的形式和輸出的展示。對於算法的輸入,需要考慮好對輸入要做什麼預處理,以什麼形式輸入算法中進行計算,也就是怎麼表示輸入。對於算法的輸出,這是重點,它決定算法的結構選型。
2.其次對於數據的分析,設計算法的結構之前,一定需要對給出的賽題數據進行統計方面,算法方面的分析,對潛在的可能需要解決的問題,或者存在的統計特徵進行記錄下來,這很可能是後期賽題成績提高的關鍵。
3.設計算法的結構的時候,如果完全沒有想法,可以查詢相關的論文,從論文中提出的方法模型進行改進這是一種很好的方法。針對前期提出的潛在問題有針對性的查詢解決該些問題的論文。所以說平時有空多閱讀自己領域相關論文還是很有好處的。
4.訓練模型時,要儘量打印出每次算法迭代過程中的val_loss,val_f1,val_acc,val_precision,val_recall.學會觀察這些數據,根據經驗來處理和分析出現打印的結果的原因。這很可能是查看模型是否擬合訓練數據或者過擬合數據的基礎。
5.就是細節問題,最後成績的提高除了比拼算法架構外,還比拼處理問題的細節,比如說詞向量的訓練的過程,中文預處理的過程,Embeding 矩陣是否當做參數進行微調的過程。

5參考文獻及鏈接

1.深度學習中漢語字向量和詞向量結合方式探究
2.A Compare-Aggregate Model for Matching Text Sequences
3.A Decomposable Attention Model for Natural Language Inference
4.Text Matching as Image Recognition
5.sentence pair model 總結
6.gensim中的Word2Vec的使用
7.集成學習總結 & Stacking方法詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章