1. 程式人生 > >基於gensim的Doc2Vec簡析

基於gensim的Doc2Vec簡析

摘要:本文主要描述了一種文章向量(doc2vec)表示及其訓練的相關內容,並列出相關例子。兩位大牛Quoc Le 和 Tomas Mikolov(搞出Word2vec的傢伙)在2014年的《Distributed Representations of Sentences and Documents》所提出文章向量(Documents vector),或者稱句向量(Sentences vector),當然在文章中,統一稱這種向量為Paragraph Vector,本文也將已doc2vec稱呼之。文章中講述瞭如何將文章轉換成向量表示的演算法。

1、Word2vec的基本原理

先簡述一下Word2vec

相關原理,因為本文要講述的doc2vec是基於Word2vec思想的演算法。w2v的數學知識還比較豐富,網路上相關資料也很多。如果要系統的講述,我可能會涉及包括詞向量的理解、sigmoid函式、邏輯迴歸、Bayes公式、Huffman編碼、n-gram模型、淺層神經網路、啟用函式、最大似然及其梯度推導、隨機梯度下降法、詞向量與模型引數的更新公式、CBOW模型和 Skip-gram模型、Hierarchical Softmax演算法和Negative Sampling演算法。當然還會結合google釋出的C原始碼(好像才700+行),講述相關部分的實現細節,比如Negative Sampling演算法如何隨機取樣、引數更新的細節、sigmod的快速近似計算、詞典的hash儲存、低頻與高頻詞的處理、視窗內的取樣方式、自適應學習、引數初始化、w2v實際上含有兩中方法等,用C程式碼僅僅700+行實現,並加入了諸多技巧,推薦初識w2v的愛好者得看一看。

Google出品的大多都是精品 ,w2v也不例外。Word2Vec實際上使用了兩種方法,Continuous Bag of Words (CBOW) 和Skip-gram,如下圖所示。在CBOW方法中,目的是將文章中某個詞的上下文經過模型預測該詞。而Skip-gram方法則是用給定的詞來預測其周邊的詞。而詞向量是在訓練模型中所得到的一個副產品,此模型在原始碼中是為一個淺層的神經網路(3層)。在訓練前,每一個詞都會首先初始化為一個N維的向量,訓練過程中,會對輸入的向量進行反饋更新,在進行大量語料訓練之後,便可得到每一個詞相應的訓練向量。而每一種模型方法都可以使用兩種對應的訓練方法Hierarchical Softmax演算法和Negative Sampling演算法,有興趣的盆友可以自行查閱相關內容。
Word2Vec的兩種不同方法

訓練出的向量有一定的特性,即相近意義的詞在向量空間上其距離也是相近。
有一個經典例子就是 V(‘king’) – V(‘man’) + V(‘woman’) ≈ V(‘queen’)

2、Doc2Vec的基本原理

基於上述的Word2Vec的方法,Quoc Le 和Tomas Mikolov又給出了Doc2Vec的訓練方法。如下圖所示,其原理與Word2Vec非常的相似。分為Distributed Memory (DM) 和Distributed Bag of Words (DBOW),可以看出 Distributed Memory version of Paragraph Vector
(PV-DM)方法與Word2Vec的CBOW方法類似,Bag of Words version of Paragraph Vector (PV-DBOW)與Word2Vec的Skip-gram方法類似。不同的是,給文章也配置了向量,並在訓練過程中更新。熟悉了w2v之後,Doc2Vec便非常好理解。具體細節可以看原文Distributed Representations of Sentences and Documents

Doc2Vec的兩種不同方法

3、gensim的實現

使用Doc2Vec進行分類任務,我們使用 IMDB電影評論資料集作為分類例子,測試gensim的Doc2Vec的有效性。資料集中包含25000條正向評價,25000條負面評價以及50000條未標註評價。

#!/usr/bin/python
import sys
import numpy as np
import gensim

from gensim.models.doc2vec import Doc2Vec,LabeledSentence
from sklearn.cross_validation import train_test_split

LabeledSentence = gensim.models.doc2vec.LabeledSentence
##讀取並預處理資料
def get_dataset():
    #讀取資料
    with open(pos_file,'r') as infile:
        pos_reviews = infile.readlines()
    with open(neg_file,'r') as infile:
        neg_reviews = infile.readlines()
    with open(unsup_file,'r') as infile:
        unsup_reviews = infile.readlines()

    #使用1表示正面情感,0為負面
    y = np.concatenate((np.ones(len(pos_reviews)), np.zeros(len(neg_reviews))))
    #將資料分割為訓練與測試集
    x_train, x_test, y_train, y_test = train_test_split(np.concatenate((pos_reviews, neg_reviews)), y, test_size=0.2)

    #對英文做簡單的資料清洗預處理,中文根據需要進行修改
    def cleanText(corpus):
        punctuation = """.,?!:;(){}[]"""
        corpus = [z.lower().replace('\n','') for z in corpus]
        corpus = [z.replace('<br />', ' ') for z in corpus]

        #treat punctuation as individual words
        for c in punctuation:
            corpus = [z.replace(c, ' %s '%c) for z in corpus]
        corpus = [z.split() for z in corpus]
        return corpus

    x_train = cleanText(x_train)
    x_test = cleanText(x_test)
    unsup_reviews = cleanText(unsup_reviews)

    #Gensim的Doc2Vec應用於訓練要求每一篇文章/句子有一個唯一標識的label.
    #我們使用Gensim自帶的LabeledSentence方法. 標識的格式為"TRAIN_i"和"TEST_i",其中i為序號
    def labelizeReviews(reviews, label_type):
        labelized = []
        for i,v in enumerate(reviews):
            label = '%s_%s'%(label_type,i)
            labelized.append(LabeledSentence(v, [label]))
        return labelized

    x_train = labelizeReviews(x_train, 'TRAIN')
    x_test = labelizeReviews(x_test, 'TEST')
    unsup_reviews = labelizeReviews(unsup_reviews, 'UNSUP')

    return x_train,x_test,unsup_reviews,y_train, y_test
##讀取向量
def getVecs(model, corpus, size):
    vecs = [np.array(model.docvecs[z.tags[0]]).reshape((1, size)) for z in corpus]
    return np.concatenate(vecs)
##對資料進行訓練
def train(x_train,x_test,unsup_reviews,size = 400,epoch_num=10):
    #例項DM和DBOW模型
    model_dm = gensim.models.Doc2Vec(min_count=1, window=10, size=size, sample=1e-3, negative=5, workers=3)
    model_dbow = gensim.models.Doc2Vec(min_count=1, window=10, size=size, sample=1e-3, negative=5, dm=0, workers=3)

    #使用所有的資料建立詞典
    model_dm.build_vocab(np.concatenate((x_train, x_test, unsup_reviews)))
    model_dbow.build_vocab(np.concatenate((x_train, x_test, unsup_reviews)))

    #進行多次重複訓練,每一次都需要對訓練資料重新打亂,以提高精度
    all_train_reviews = np.concatenate((x_train, unsup_reviews))
    for epoch in range(epoch_num):
        perm = np.random.permutation(all_train_reviews.shape[0])
        model_dm.train(all_train_reviews[perm])
        model_dbow.train(all_train_reviews[perm])

    #訓練測試資料集
    x_test = np.array(x_test)
    for epoch in range(epoch_num):
        perm = np.random.permutation(x_test.shape[0])
        model_dm.train(x_test[perm])
        model_dbow.train(x_test[perm])

    return model_dm,model_dbow
##將訓練完成的資料轉換為vectors
def get_vectors(model_dm,model_dbow):

    #獲取訓練資料集的文件向量
    train_vecs_dm = getVecs(model_dm, x_train, size)
    train_vecs_dbow = getVecs(model_dbow, x_train, size)
    train_vecs = np.hstack((train_vecs_dm, train_vecs_dbow))
    #獲取測試資料集的文件向量
    test_vecs_dm = getVecs(model_dm, x_test, size)
    test_vecs_dbow = getVecs(model_dbow, x_test, size)
    test_vecs = np.hstack((test_vecs_dm, test_vecs_dbow))

    return train_vecs,test_vecs
##使用分類器對文字向量進行分類訓練
def Classifier(train_vecs,y_train,test_vecs, y_test):
    #使用sklearn的SGD分類器
    from sklearn.linear_model import SGDClassifier

    lr = SGDClassifier(loss='log', penalty='l1')
    lr.fit(train_vecs, y_train)

    print 'Test Accuracy: %.2f'%lr.score(test_vecs, y_test)

    return lr
##繪出ROC曲線,並計算AUC
def ROC_curve(lr,y_test):
    from sklearn.metrics import roc_curve, auc
    import matplotlib.pyplot as plt

    pred_probas = lr.predict_proba(test_vecs)[:,1]

    fpr,tpr,_ = roc_curve(y_test, pred_probas)
    roc_auc = auc(fpr,tpr)
    plt.plot(fpr,tpr,label='area = %.2f' %roc_auc)
    plt.plot([0, 1], [0, 1], 'k--')
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])

    plt.show()
##執行模組
if __name__ == "__main__":
    #設定向量維度和訓練次數
    size,epoch_num = 40010
    #獲取訓練與測試資料及其類別標註
    x_train,x_test,unsup_reviews,y_train, y_test = get_dataset()
    #對資料進行訓練,獲得模型
    model_dm,model_dbow = train(x_train,x_test,unsup_reviews,size,epoch_num)
    #從模型中抽取文件相應的向量
    train_vecs,test_vecs = get_vectors(model_dm,model_dbow)
    #使用文章所轉換的向量進行情感正負分類訓練
    lr=Classifier(train_vecs,y_train,test_vecs, y_test)
    #畫出ROC曲線
    ROC_curve(lr,y_test)

IMDB影評資料使用Doc2vec分類的ROC曲線

訓練結果的,test分類精度為86%,AUC面積為0.94

————————————————————————————————————————
————————————————————————————————————————
相關連結: