基於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
Google出品的大多都是精品 ,w2v也不例外。Word2Vec實際上使用了兩種方法,Continuous Bag of Words (CBOW) 和Skip-gram,如下圖所示。在CBOW方法中,目的是將文章中某個詞的上下文經過模型預測該詞。而Skip-gram方法則是用給定的詞來預測其周邊的詞。而詞向量是在訓練模型中所得到的一個副產品,此模型在原始碼中是為一個淺層的神經網路(3層)。在訓練前,每一個詞都會首先初始化為一個N維的向量,訓練過程中,會對輸入的向量進行反饋更新,在進行大量語料訓練之後,便可得到每一個詞相應的訓練向量。而每一種模型方法都可以使用兩種對應的訓練方法Hierarchical Softmax演算法和Negative Sampling演算法,有興趣的盆友可以自行查閱相關內容。
訓練出的向量有一定的特性,即相近意義的詞在向量空間上其距離也是相近。
有一個經典例子就是 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》
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 = 400,10
#獲取訓練與測試資料及其類別標註
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)
訓練結果的,test分類精度為86%,AUC面積為0.94
————————————————————————————————————————
————————————————————————————————————————
相關連結: