【機器學習】 python 多種方法實驗比較 文本情感二分類

實驗目的及要求

實驗源於“2019大數據挑戰賽-預選賽”。

  本預選賽要求選手建立文本情感分類模型,選手用訓練好的模型對測試集中的文本情感進行預測,判斷其情感爲「Negative」或者「Positive」。所提交的結果按照指定的評價指標使用在線評測數據進行評測,達到或超過規定的分數線即通過預選賽。
  數據樣本格式:
在這裏插入圖片描述
  其中,訓練集的樣本規模爲6328,測試集的樣本規模爲2712。爲保證比賽結果的真實性。每週一中午12點,將替換爲新的測試集數據,供隊伍答題使用。選手提交結果的評估指標是AUC(Area Under Curve)

實驗儀器設備

  硬件配置:CORE i5 7thGen處理器,8G內存,1050顯卡
  軟件配置:Anaconda python3 Spyder
  數據集:train.csv、20190506_test.csv

實驗內容

  本實驗是一個典型的二分類問題,所有用於解決分類問題的模型如羅吉斯特迴歸、支持向量機、神經網絡、樸素貝葉斯等都能適用。
  其次,本實驗還屬於NLP中情感分析問題,處理的數據是印度尼西亞語的評論。情感分析一般有兩種方法,一種是帶詞典的,一種是不帶詞典的。由於印尼語是小語種,詞典難以尋找,所以採用不帶詞典的方法。

(一) 特徵表示

我們需要分類的目標是句子,換句話說,句子是模型的輸入。如何表示句子是解決該問題的關鍵。

1.One-Hot編碼

  one-hot向量將類別變量轉換爲機器學習算法易於利用的一種形式的過程,這個向量的表示爲一項屬性的特徵向量,也就是同一時間只有一個激活點(不爲0),這個向量只有一個特徵是不爲0的,其他都是0,特別稀疏。
句子可由每個詞的one-hot向量相加得到。

2.word2vec

  Word2vec 可以根據給定的語料庫,通過優化後的訓練模型快速有效地將一個詞語表達成向量形式,爲自然語言處理領域的應用研究提供了新的工具。Word2vec依賴skip-grams或連續詞袋(CBOW)來建立神經詞嵌入。
句子可由每個詞的詞向量直接相加或拼接成大的矩陣來表示。

3.doc2vec

  Doc2Vec 或者叫做 paragraph2vec, sentence embeddings,是一種非監督式算法,可以獲得 sentences/paragraphs/documents 的向量表達,是 word2vec 的拓展。
該方法可以直接得到句子的表示,表示形式爲向量。

4.tf-idf

  TF-IDF(term frequency–inverse document frequency,詞頻-逆向文件頻率)是一種用於信息檢索(information retrieval)與文本挖掘(text mining)的常用加權技術。
  TF-IDF是一種統計方法,用以評估一字詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。
   TF-IDF的主要思想是:如果某個單詞在一篇文章中出現的頻率TF高,並且在其他文章中很少出現,則認爲此詞或者短語具有很好的類別區分能力,適合用來分類。
句子可由One-Hot思想,用每個單詞的tf-idf值代替“1”來表示。

(二) 模型訓練

1. 樸素貝葉斯

  樸素貝葉斯分類是一種十分簡單的分類算法,叫它樸素貝葉斯分類是因爲這種方法的思想真的很樸素,樸素貝葉斯的思想基礎是這樣的:對於給出的待分類項,求解在此項出現的條件下各個類別出現的概率,哪個最大,就認爲此待分類項屬於哪個類別。

2. 羅吉斯特迴歸

  Logistic Regression是目前應用比較廣泛的一種優化算法,利用logistic regression進行分類的只要思想是:根據現有數據對分類邊界線建立迴歸公式,以此進行分類。“迴歸”一詞源於最佳擬合,表示要找到最佳擬合參數集。
Logistic迴歸的因變量可以是二分類的,也可以是多分類的,但是二分類的更爲常用,也更加容易解釋。所以實際中最常用的就是二分類的Logistic迴歸。

3. K近鄰

  K最近鄰(k-Nearest Neighbor,KNN)分類算法,是一個理論上比較成熟的方法,也是最簡單的機器學習算法之一。該方法的思路是:如果一個樣本在特徵空間中的k個最相似(即特徵空間中最鄰近)的樣本中的大多數屬於某一個類別,則該樣本也屬於這個類別。

4. 支持向量機

支持向量機(support vector machines)是一種二分類模型,它的目的是尋找一個超平面來對樣本進行分割,分割的原則是間隔最大化,最終轉化爲一個凸二次規劃問題來求解。

5. 隨機森林

  隨機森林,指的是利用多棵樹對樣本進行訓練並預測的一種分類器。該分類器最早由Leo Breiman和Adele Cutler提出,並被註冊成了商標。簡單來說,隨機森林就是由多棵CART(Classification And Regression Tree)構成的。對於每棵樹,它們使用的訓練集是從總的訓練集中有放回採樣出來的,這意味着,總的訓練集中的有些樣本可能多次出現在一棵樹的訓練集中,也可能從未出現在一棵樹的訓練集中。

6. 前饋神經網絡

  前饋神經網絡(Feedforward Neural Network),簡稱前饋網絡,是人工神經網絡的一種。在此種神經網絡中,各神經元從輸入層開始,接收前一級輸入,並輸出到下一級,直至輸出層。整個網絡中無反饋,可用一個有向無環圖表示。

(三) 模型自評

  官網給的標準是計算AUC,由於我們只有訓練集的Label數據,而官網提交次數有限,因此可計算訓練集的AUC作爲參考再進行提交。
  AUC(Area Under Curve)被定義爲ROC曲線下與座標軸圍成的面積,顯然這個面積的數值不會大於1。又由於ROC曲線一般都處於y=x這條直線的上方,所以AUC的取值範圍在0.5和1之間。使用AUC值作爲評價標準是因爲很多時候ROC曲線並不能清晰的說明哪個分類器的效果更好,而作爲一個數值,對應AUC更大的分類器效果更好。

實驗實施步驟

(一)特徵提取

1.分詞

  句子由詞組成,不管是爲了得到one-hot、詞向量、還是其他,處理的基本單元都是單詞。而因爲是外文單詞,採用NLTK的分詞可以幫助我們有效分詞。
  NLTK是一個高效的Python構建的平臺,用來處理人類自然語言數據。它提供了易於使用的接口,通過這些接口可以訪問超過50個語料庫和詞彙資源(如WordNet),還有一套用於分類、標記化、詞幹標記、解析和語義推理的文本處理庫,以及工業級NLP庫的封裝器和一個活躍的討論論壇。
  rev_cut():對評論進行分詞,分好的詞放在“rev_cut.txt”中。

def rev_cut(review):
    cut_file = './rev_cut.txt'
    with open(cut_file, 'w', encoding='utf-8') as f:
        for rev in review:
            rev_cut = " ".join(nltk.word_tokenize(rev))#對句子進行分詞
            f.writelines(rev_cut +'\n')
    return cut_file

2.word2vec得到句向量

  Gensim是一款開源的第三方Python工具包,用於從原始的非結構化的文本中,無監督地學習到文本隱層的主題向量表達。 它支持包括TF-IDF,LSA,LDA,和word2vec在內的多種主題模型算法。
  word_vec(cut_file,model_name,dimension):由分好的詞訓練得到每個單詞的詞向量,並保存。
  get_sents_word_vec (review,dimension,model_name):載入訓練好得模型,由詞向量的平均值得到句向量。

def word_vec(cut_file,model_name,dimension):
    logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
    sentences = gensim.models.word2vec.LineSentence(cut_file) #單詞隔開,換行符換行
    model = gensim.models.word2vec.Word2Vec(sentences, hs=1,min_count=1,window=3,size=dimension) 
    model.save(model_name)
    
def get_sents_word_vec(review,dimension,model_name):
    model = gensim.models.word2vec.Word2Vec.load(model_name)
    sent_vec = np.array([0]*dimension)
    sents_vec = []
    for rev in review:
        i = 0
        for word in nltk.word_tokenize(rev):
            sent_vec = sent_vec + model[word]
            i = i + 1
        sent_vec = sent_vec / float(i) 
        sents_vec.append(sent_vec)
    sents_vec = np.array(sents_vec)
    return sents_vec

3.doc2vec得到句向量

  同樣用Gensim庫,可得到句向量,具體如下。
  sent_vec(cut_file,model_name,dimension):由分好的詞訓練得到句子向量並將模型保存。
  get_sents_sent_vec (review,dimension,model_name):載入模型,獲取每條評論對應得句向量。

4.tf-idf及one-hot

  Sklearn庫提供了比較方便和一體的機器學習方法和tf-idf值計算,NLTK得到one-hot編碼,這兩部分相較比較簡單,和具體的方法寫在了一起,詳情可見第(二)點模型訓練。

(二)模型訓練

1.機器學習方法

  Sklearn庫封裝了大量機器學習的算法,對於應用只需調用,無需瞭解深度的細節。由於很多方法代碼基本一致,因此在這裏以結果最好的tf-idf和樸素貝葉斯的組合。

def train_NB_tfidf_skl(train_data,test_rev,all_rev):   
    labels = train_data['label']
    train_rev = train_data['review']
    ID = test_data['ID']
    train_lab = get_lab(labels)
    labs = train_lab
    corpus = all_rev
    vectorizer=CountVectorizer()
    transformer = TfidfTransformer()
    tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))
   
    train_X = tfidf[0:len(train_rev)]
    test_X = tfidf[len(train_rev):len(train_rev)+len(test_rev)]
    #使用樸素貝葉斯進行訓練
    mnb = MultinomialNB(alpha=0.5, class_prior=None, fit_prior=True)  
    mnb.fit(train_X,labs)    # 利用訓練數據對模型參數進行估計
    train_score = [pred[1]for pred in mnb.predict_proba(train_X)]
    test_score = [pred[1]for pred in mnb.predict_proba(test_X)]
    print ('The Accuracy of Naive Bayes Classifier is:', mnb.score(train_X,labs))
    train_score = np.array(train_score,dtype="float32")
    test_score = np.array(test_score,dtype="float32")
    print("AUC: ",cal_auc(train_score,train_lab))
    result = pd.DataFrame({'ID':ID.T,'Pred':test_score})
    result.to_csv("./result.csv",index = None)

2. 神經網絡方法

  使用tensorflow底層代碼自行搭建前饋神經網絡,分爲輸入層、隱層、輸出層。其中隱層共有兩層,每層有25個結點。輸入層結點數與評論數對應,輸出層與分類數對應,由於二分類,因此輸出層可以只有一個結點。用預測感情得分與真實分值的均方誤差作爲損失函數,用自適應梯度下降算法求解,用Relu做激活函數。

def train_simplenn(train_data,sents_vec,test_sentsvec):
    labels = train_data['label']
    lab = get_lab(labels)
    dataset_size = sents_vec.shape[0]#樣本數
    dimension = sents_vec.shape[1]#輸入特徵維度(特徵個數)
    emb_unit_counts_1 = 25 #隱層結點數
    emb_unit_counts_2 = 25 #隱層結點數
    batch_size = 16 #定義訓練數據batch的大小
    tf.reset_default_graph()
    w1 = tf.Variable(tf.random_normal([dimension,emb_unit_counts_1],stddev=1,seed=1))
    w2 = tf.Variable(tf.random_normal([emb_unit_counts_1,emb_unit_counts_2],stddev=1,seed=2))#初試化權重矩陣(生成相應大小的兩個隨機正態分佈,均值爲0,方差爲1)
    w3 = tf.Variable(tf.random_normal([emb_unit_counts_2,1],stddev=1,seed=3))
    x = tf.placeholder(tf.float32,shape=(None,dimension),name='x-input') #在第一個維度上利用None,可以方便使用不大的batch大小
    y_ = tf.placeholder(tf.float32,shape=(None,1),name='y-input') #在第一個維度上利用None,可以方便使用不大的batch大小
    b1 = tf.Variable(tf.zeros([emb_unit_counts_1]))# 隱藏層的偏向bias 
    b2 = tf.Variable(tf.zeros([emb_unit_counts_2]))# 隱藏層的偏向bias 
    b3 = tf.Variable(tf.zeros([1]))# 輸出層的偏向bias
    learning_rate = 0.0015
    #learning_rate = 0.01
    #定義前向傳播過程
    a1 = tf.nn.softplus(tf.add(tf.matmul(x,w1),b1)) #計算隱層的值
    a2 = tf.nn.softplus(tf.add(tf.matmul(a1,w2),b2)) #計算隱層的值
    y = tf.nn.sigmoid(tf.add(tf.matmul(a2,w3),b3)) #計算輸出值
    
    #定義損失函數和反向傳播算法
    loss = tf.reduce_mean(tf.square(y_-y))
    #loss = -tf.reduce_mean(y_*tf.log(tf.clip_by_value(y,1e-10,1.0)))#cross_entropy
    #learning_rate = 0.001 #tf.train.exponential_dacay(0.1 ,STEPS,dataset_size/batch_size , 0.96 , staircase = True)
    #optimizer = tf.train.MomentumOptimizer(learning_rate,momentum).minimize(loss)  #在此神經網絡中只有基礎學習率的設置,沒有指數衰減率,也沒有正則項和滑動平均模型。
    optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)  #在此神經網絡中只有基礎學習率的設置,沒有指數衰減率,也沒有正則項和滑動平均模型。
    X = sents_vec
    Y = lab
    #以下創建一個session會話來運行TensorFlow程序
    with tf.Session() as sess:
        init_op = tf.initialize_all_variables()
        sess.run(init_op) #在此利用以上兩行對其中涉及的參數進行統一初始化     
        STEPS = 100000#設定訓練的輪數
        for i in range(STEPS):
            start = (i*batch_size) %dataset_size
            end = min(start+batch_size,dataset_size)#每次選取batch_size個樣本進行訓練
            sess.run(optimizer,feed_dict = {x:X[start:end],y_:Y[start:end]})#通過選取的樣本訓練神經網絡並更新其中的參數
            if i%1000==0:
                score,loss_value = sess.run([y,loss],feed_dict={x:X,y_:Y})
                print("After%dtraining step(s),loss on all data is%g"%(i,loss_value))
        saver=tf.train.Saver(max_to_keep=1)
        saver.save(sess,'ckpt/emtion')#,global_step=i+1
        print("AUC: ",cal_auc(score,lab))
        X2 = test_sentsvec
        test_pred = sess.run(y,feed_dict = {x:X2})
        ID = []
        for s in range(1,len(X2)+1):
            ID.append(s)
        ID = np.array(ID)
        result = pd.DataFrame({'ID':ID.T,'Pred':test_pred.T[0]})
        result.to_csv("./result.csv",index = None)

(三) 模型自評AUC的計算

  AUC計算需先構造混淆矩陣,再畫出ROC曲線,最後利用定積分方法計算面積得到。

def cal_auc(score,lab):
    threhold = 0
    dx = 0.005
    #dx = 0.1
    auc = 0
    pos_x = [] 
    pos_y = []
    while(threhold<=1):
        TP = 0
        FN = 0
        FP = 0
        TN = 0
        pred = []
        for i in range(0,len(score)):
            if(score[i] >= threhold):pred.append(1)
            else: pred.append(0)
        pred = np.array(pred)
        for i in range(0,len(lab)):
            if(lab[i] == 1): #正例
                if(pred[i] == 1): #真正例 
                    TP = TP + 1
                elif(pred[i] == 0):#假負例
                    FN = FN + 1
            elif(lab[i] == 0): #負例
                if(pred[i] == 1): #假正例
                    FP = FP + 1
                elif(pred[i] == 0): #真負例
                    TN = TN + 1
        FPR = FP/float(FP+TN)
        TPR = TP/float(TP+FN)
        pos_x.append(FPR)
        pos_y.append(TPR)
        threhold = threhold + dx
    x = np.array(pos_x)
    y = np.array(pos_y)
    plt.xlim(0,1)
    plt.ylim(0,1)
    plt.scatter(x,y)
    auc = abs(np.trapz(y,x,dx=dx))
    return auc

  同樣以樸素貝葉斯+tf-idf的方法爲例,其效果圖如下圖所示,AUC值爲AUC: 0.9872392308651139。該圖只表明在訓練集上我們模型的效果,由於過擬合或面積計算不精確等原因,在測試集上會有較大偏差,但一般來說,訓練集AUC越大,曲線越連續平滑,在測試集上越好,對於我們提交有指導意義。
在這裏插入圖片描述

(四) 實驗評估數據記錄

  由於提交次數有限,還借了幾個賬號進行測試。總共做的實驗及其結果如下表所示:

實驗編號 實驗方法 提交評分(AUC)
1 tf-idf+樸素貝葉斯 0.85319362
2 tf-idf+羅吉斯特迴歸 0.8514
3 one-hot+樸素貝葉斯 0.81020273
4 tf-idf+隨機森林 0.79925816
5 tf-idf+K近鄰 0.78078132
6 tf-idf+支持向量機 0.77722
7 word2vec(平均)+前饋神經網絡 0.73
8 tf-idf+高斯貝葉斯 0.70844343
9 doc2vec+前饋神經網絡 0.62
10 word2vec(平均)+高斯貝葉斯 0.61831131
11 word2vec(拼接)+前饋神經網絡 0.61
12 doc2vec+高斯貝葉斯 0.52883599

在這裏插入圖片描述

實驗總結

  從實驗結果和編程調試的情況上來看,對於本次實驗,特徵表示最好的方法是tf-idf,其次是one-hot編碼,再是word2vec、doc2vec。這與理論上分析不太一致,因爲詞向量、句向量理論上更能表示特徵。造成這種情況的原因可能在於預選賽數據集較少,作爲data driven的方法代表,詞向量表現的不是很好。
  這同樣也體現在模型方法上,作爲一開始比較期待的神經網絡方法,儘管未採用CNN、RNN、LSTM等高端網絡結構,也未用deep learning經典網絡結構如Resnet、VGG等,前饋神經網絡表現得不是很盡如人意,劣於大多數傳統機器學習方法。
  這個實驗結果和代碼調試的經驗告訴我,data driven方法並不是萬金油,其容易過擬合,且在小數據集上還是表現不佳,傳統和經典方法,有些儘管簡單,但卻直接、高效。因此,往後對於特定問題還是要具體分析,不能盲目選擇方法。
  完整的數據集和實驗代碼見這裏

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