1. 程式人生 > >中文短文字相似度:WMD

中文短文字相似度:WMD

開篇

句子相似是目前我做問句匹配的基礎。
這是我嘗試使用詞向量,以一種無監督方法去計算兩個句子相似度的第二種方法。第一種方法,我嘗試使用詞向量的加權平均生成句向量來計算句子間的相似度,效果很一般,之後我會嘗試使用不同的加權方法再次計算。有機會我會連著程式碼一起放出來。
當然我還使用了三種不同的深度學習方法來計算相似度,之後都會以程式碼講解的方式呈現。本部落格沒有使用任何公司的資料,也未整合到公司的任何系統中,屬於學術型文章開源。

WMD

word mover’s distance 是一種計算句子之間距離的方法,距離越小,相似度越高。看了網上幾篇部落格,基本是這邊抄一下,那邊抄一下,內容非常得凌亂。這邊也放

一篇部落格,等我讀過原始論文,再來這篇部落格來補充相關的理論知識,現在這篇部落格希望通過程式碼讓你更快的構建自己的wmd計算函式。迅速實戰。

詞向量

首先我們還是需要藉助詞向量,你可以自己下載別人訓練好的詞向量,或者自己使用gensim訓練詞向量

關於詞向量的訓練,我這邊不再重複,使用gensim只要幾行程式碼,如果沒有相關的經驗,可以參考我之前的部落格

WMDdistance

先放出相關的程式碼,部落格的最後我會放出完整的程式碼,如果有什麼疑問歡迎留言。資料是公司的,不會公開,但是有其他資料可以替換。不影響程式碼的執行。

def wmd(sent1, sent2):
    sent1 = word_cut(sent1)
    sent2 = word_cut(sent2)
    model = KeyedVectors.load_word2vec_format(os.path.join(data_dir, "Word2VecModel_small.vector"))
    model.init_sims(replace=True)
    #這邊是歸一化詞向量,不加這行的話,計算的距離資料可能會非常大
    distance = model.wmdistance(sent1,sent2)
    return distance

詞向量是一定要載入的。

中文當然是要分詞的,這裡我使用的是哈工大的分詞工具,下面我放出我的程式碼,這是需要載入的一下模型和詞典。

data_dir = "C:\\Users\\d84105613\\PycharmProjects\\Ai_qa\\data"
LTP_DATA_DIR = 'C:\\Users\\d84105613\\ltp_data'
cws_model_path = os.path.join(LTP_DATA_DIR, 'cws.model')
segmentor = Segmentor()  # 初始化例項
segmentor.load_with_lexicon(cws_model_path, os.path.join(data_dir,'wordDictupper.txt'))  # 載入模型

#停用詞載入
stopwords = []
stopword = open(os.path.join(data_dir,'stopwords_zh.txt'),'rt',encoding='utf-8')
for line in stopword:
    stopwords.append(line.strip())

分詞的時候記得把停用詞也給去了

def word_cut(sentence):
    words = segmentor.segment(sentence)
    # segmentor.release()
    words = list(words)
    # print(len(set(words)))
    key = [",", "?", "[", "]"]
    words = [c for c in words if c not in punctuation]
    words = [c for c in words if c not in key]
    words = [c for c in words if c not in stopwords]
    return words

搜尋相似句子

這邊就是問答系統一個簡單的功能元件了,給出一個你要查詢的句子,在語料庫裡搜尋出最相似的幾個句子。感謝gensim提供的庫函式。

def get_top_n(document,query,num_best):
    corpus = [word_cut(i) for i in document]
    model = KeyedVectors.load_word2vec_format(os.path.join(data_dir, "Word2VecModel_small.vector"))
    instance = WmdSimilarity(corpus, model, num_best)
    query = word_cut(query)
    sims = instance[query]
    for i in range(num_best):
        print(sims[i][1])
        print(document[sims[i][0]])

完整程式碼

import os
from pyltp import Segmentor
from zhon.hanzi import punctuation
from gensim.models import KeyedVectors
from gensim.similarities import WmdSimilarity

data_dir = "C:\\Users\\d84105613\\PycharmProjects\\Ai_qa\\data"
LTP_DATA_DIR = 'C:\\Users\\d84105613\\ltp_data'
cws_model_path = os.path.join(LTP_DATA_DIR, 'cws.model')
segmentor = Segmentor()  # 初始化例項
segmentor.load_with_lexicon(cws_model_path, os.path.join(data_dir,'wordDictupper.txt'))  # 載入模型

#停用詞載入
stopwords = []
stopword = open(os.path.join(data_dir,'stopwords_zh.txt'),'rt',encoding='utf-8')
for line in stopword:
    stopwords.append(line.strip())

def read_data_sets(train_dir):
    s1 = []
    s2 = []
    for line in open(train_dir, encoding='utf-8'):
        l = line.strip().split("¥")
        if len(l) < 2:
            continue
        s1.append(l[0])
        s2.append(l[1])
    return s1, s2

def word_cut(sentence):
    words = segmentor.segment(sentence)
    # segmentor.release()
    words = list(words)
    # print(len(set(words)))
    key = [",", "?", "[", "]"]
    words = [c for c in words if c not in punctuation]
    words = [c for c in words if c not in key]
    words = [c for c in words if c not in stopwords]
    return words


def wmd(sent1, sent2):
    sent1 = word_cut(sent1)
    sent2 = word_cut(sent2)
    model = KeyedVectors.load_word2vec_format(os.path.join(data_dir, "Word2VecModel_small.vector"))
    model.init_sims(replace=True)
    distance = model.wmdistance(sent1,sent2)
    return distance

def get_top_n(document,query,num_best):
    corpus = [word_cut(i) for i in document]
    model = KeyedVectors.load_word2vec_format(os.path.join(data_dir, "Word2VecModel_small.vector"))
    instance = WmdSimilarity(corpus, model, num_best)
    query = word_cut(query)
    sims = instance[query]
    for i in range(num_best):
        print(sims[i][1])
        print(document[sims[i][0]])

輸出

0.8867670409833355
修改採購計劃當前處理人
0.856070758049195
修改BPA當前處理人
0.8339566600760685
缺陷修改當前處理人
0.8040727679959436
修改變更單當前處理人
0.7797357960999256
修改單據當前處理人

Process finished with exit code 0

相關部落格程式碼

補充

之前的程式碼跑一個批次的資料,大概是6萬條要40秒,後續做了一些優化,大概跑6萬11秒左右

def overlap(list1,list2):
    temp = [i for i in list1 if i in list2]
    return len(temp)/len(list1)


def fast_top_n(document, query, num_best):
    doc = [word_cut(i) for i in document]
    query = word_cut(query)
    corpus=[]
    document1 = []
    for i,j in enumerate(doc):
        if overlap(query,j) > 0.25:
            corpus.append(doc[i])
            document1.append(document[i])
    model = KeyedVectors.load_word2vec_format(os.path.join(data_dir, "Word2VecModel_small.vector"))
    instance = WmdSimilarity(corpus, model, num_best)
    sims = instance[query]
    for i in range(num_best):
        print(sims[i][1])
        print(document1[sims[i][0]])