前言:
最近由於要開發一款項目,就想用doc2vec來實現其中的推薦功能,根據用戶輸入的問題利用doc2vec返回相似的問題。
以下是整個Demo的實現過程,具體的詳細代碼請參考我的Git:https://github.com/645187919/doc2vecDemo
對於doc2vec來說其實內部原理也是先拿到儘可能多的數據生成一個model然後根據輸入再在model中進行匹配,得到對應的輸入。
第一步:首先我們需要拿到對應的數據,相關的代碼如下:
def get_datasest():
fin = open("questions.txt",encoding='utf8').read().strip(' ') #strip()取出首位空格
# print(fin)
# print(type(fin))
# 添加自定義的詞庫用於分割或重組模塊不能處理的詞組。
jieba.load_userdict("userdict.txt")
# 添加自定義的停用詞庫,去除句子中的停用詞。
stopwords = set(open('stopwords.txt',encoding='utf8').read().strip('\n').split('\n')) #讀入停用詞
text = ' '.join([x for x in jieba.lcut(fin) if x not in stopwords]) #去掉停用詞中的詞
# print(text)
print (type(text),len(text))
x_train = []
word_list = text.split('\n')
print(word_list[0])
for i,sub_list in enumerate(word_list):
document = TaggededDocument(sub_list, tags=[i])
# document是一個Tupple,形式爲:TaggedDocument( 楊千嬅 現在 教育 變成 一種 生意 , [42732])
# print(document)
x_train.append(document)
return x_train
第二步:拿到對應的數據後,就開始訓練數據生成對應的model,對應的代碼如下:
def train(x_train, size=200, epoch_num=1):
# D2V參數解釋:
# min_count:忽略所有單詞中單詞頻率小於這個值的單詞。
# window:窗口的尺寸。(句子中當前和預測單詞之間的最大距離)
# size:特徵向量的維度
# sample:高頻詞彙的隨機降採樣的配置閾值,默認爲1e-3,範圍是(0,1e-5)。
# negative: 如果>0,則會採用negativesampling,用於設置多少個noise words(一般是5-20)。默認值是5。
# workers:用於控制訓練的並行數。
model_dm = Doc2Vec(x_train,min_count=1, window = 5, size = size, sample=1e-3, negative=5, workers=4,hs=1,iter=6)
# total_examples:統計句子數
# epochs:在語料庫上的迭代次數(epochs)。
model_dm.train(x_train, total_examples=model_dm.corpus_count, epochs=70)
model_dm.save('model_test')
return model_dm
第三步:得到生成的model後,我們就可以輸入相應的問題得到相似的問題。代碼如下:
def test():
model_dm = Doc2Vec.load("model_test")
test_ = '申請貸款需要什麼條件?'
#讀入停用詞
stopwords = set(open('stopwords.txt',encoding='utf8').read().strip('\n').split('\n'))
#去掉停用詞中的詞
test_text = ' '.join([x for x in jieba.lcut(test_) if x not in stopwords])
print(test_text)
#獲得對應的輸入句子的向量
inferred_vector_dm = model_dm.infer_vector(doc_words=test_text)
# print(inferred_vector_dm)
#返回相似的句子
sims = model_dm.docvecs.most_similar([inferred_vector_dm], topn=10)
return sims
第四步:將拿到的相似問題表示出來就OK了。
if __name__ == '__main__':
x_train = get_datasest()
# print(x_train)
# model_dm = train(x_train)
sims = test()
# sims:[(89, 0.730167031288147), (6919, 0.6993225812911987), (6856, 0.6860911250114441), (40892, 0.6508388519287109), (40977, 0.6465731859207153), (30707, 0.6388640403747559), (40160, 0.6366203427314758), (11672, 0.6353889107704163), (16752, 0.6346361637115479), (40937, 0.6337493062019348)]
# sim是一個Tuple,內部包含兩個元素,一個是對應的句子的索引號(之前自定義的tag)一個是對應的相似度
# print(type(sims))
# print('sims:'+str(sims))
for count, sim in sims:
sentence = str(x_train[count])
# sentence = x_train[count]
# print('sentence:'+sentence)
# print('sim:'+str(sim))
print(sentence, sim, len(sentence)
)
當然中間你也可以拿到對應的句子的向量如下:
def getVecs(model, corpus, size):
vecs = [np.array(model.docvecs[z.tags[0]].reshape(1, size)) for z in corpus]
return np.concatenate(vecs)
總結:
雖然在程序中自定義了停詞庫和詞庫但是整體的效果依舊不盡人意,甚至在剛開始未調參階段碰到對於同一個輸入運行多次得到不同結果的尷尬情況……雖然這個問題在後來通過調參解決了,但是發現發現這裏面仍有許多問題:如輸入問題A,model中也包含問題A,但是返回的相似問題中,A的相關度有些卻不是最高的等等。後來查了一些資料發現其他的一些網友做這個實驗的時候也是效果不理想(至於出現這些問題的原因目前不是特別清楚,按照Doc2Vec的理論來說效果應該不會很差的,可實踐後卻啪啪啪打臉。。。)。所以暫時得到的結論就是:doc2vec效果時好時壞,偶然性大,不穩定。目前有找到另一種方法來滿足我的需求,同樣採用的是句子向量,同樣是用餘弦定理來求相似句子,理論比doc2vec簡單,效果也比doc2vec好。等整理好了,會在下一篇文章中做介紹。