句子相似度预测

数据挖掘 Python nlp scikit-学习 相似 文本
2021-09-19 21:34:35

我正在寻找解决以下问题:我有一组句子作为我的数据集,我希望能够输入一个新句子,并在数据集中找到与新句子最相似的句子。一个示例如下所示:

新句子:“ I opened a new mailbox

基于数据集的预测:

Sentence                       | Similarity
A dog ate poop                   0%
A mailbox is good                50%
A mailbox was opened by me       80%

我读过余弦相似度可用于解决与 tf-idf 配对的这类问题(并且 RNN 不应对基本方法带来重大改进),或者word2vec也可用于类似问题。在这种特定情况下,这些实际上是否也可以使用?是否有任何其他技术/算法可以解决这个问题(最好使用 Python 和 SKLearn,但我也愿意学习 TensorFlow)?

4个回答

你的问题可以用 Word2vec 和 Doc2vec 来解决。Doc2vec 会给出更好的结果,因为它在训练模型时会考虑句子。

Doc2vec 解决方案您可以按照此链接
训练您的 doc2vec 模型您可能需要执行一些预处理步骤,例如删除所有停用词(诸如“the”、“an”等不会给句子增加太多意义的词)。训练模型后,您可以使用以下代码找到相似的句子。

import gensim  

model = gensim.models.Doc2Vec.load('saved_doc2vec_model')  

new_sentence = "I opened a new mailbox".split(" ")  
model.docvecs.most_similar(positive=[model.infer_vector(new_sentence)],topn=5)

结果:

[('TRAIN_29670', 0.6352514028549194),
 ('TRAIN_678', 0.6344441771507263),
 ('TRAIN_12792', 0.6202734708786011),
 ('TRAIN_12062', 0.6163255572319031),
 ('TRAIN_9710', 0.6056315898895264)]

以上结果是 的元组列表(label,cosine_similarity_score)您可以通过执行将输出映射到句子train[29670]

请注意,只有当您的 doc2vec 模型包含新句子中的单词嵌入时,上述方法才会产生良好的结果。如果您尝试获取一些诸如 之类的乱码句子的相似性sdsf sdf f sdf sdfsdffg,它会给您很少的结果,但这些可能不是实际相似的句子,因为您的训练模型在训练模型时可能没有看到这些乱码词。因此,请尝试在尽可能多的句子上训练您的模型,以包含尽可能多的单词以获得更好的结果。

Word2vec 解决方案
如果使用 word2vec,则需要计算每个句子中所有单词的平均向量,并使用向量之间的余弦相似度。

def avg_sentence_vector(words, model, num_features, index2word_set):
    #function to average all words vectors in a given paragraph
    featureVec = np.zeros((num_features,), dtype="float32")
    nwords = 0

    for word in words:
        if word in index2word_set:
            nwords = nwords+1
            featureVec = np.add(featureVec, model[word])

    if nwords>0:
        featureVec = np.divide(featureVec, nwords)
    return featureVec

计算相似度

from sklearn.metrics.pairwise import cosine_similarity

#get average vector for sentence 1
sentence_1 = "this is sentence number one"
sentence_1_avg_vector = avg_sentence_vector(sentence_1.split(), model=word2vec_model, num_features=100)

#get average vector for sentence 2
sentence_2 = "this is sentence number two"
sentence_2_avg_vector = avg_sentence_vector(sentence_2.split(), model=word2vec_model, num_features=100)

sen1_sen2_similarity =  cosine_similarity(sentence_1_avg_vector,sentence_2_avg_vector)

Word Mover's Distance (WMD)是一种用于查找句子之间距离的算法。WMD 基于词嵌入(例如 word2vec),将词的语义编码为密集向量。

WMD 距离衡量两个文本文档之间的差异,作为一个文档的嵌入词需要“旅行”以到达另一个文档的嵌入词的最小距离量。

例如:

在此处输入图像描述 资料来源:“从词嵌入到文档距离”论文

gensim有一个WMD 实现

对于您的问题,您会将输入的句子与所有其他句子进行比较,并返回具有最低 WMD 的句子。

您可以尝试使用sklearn的简单解决方案,它会正常工作。

  • 使用tfidfvectorizer获取每个文本的向量表示

  • 用您的数据拟合矢量化器,删除停用词。

  • 使用先前训练的向量器转换新条目

  • 计算此表示与数据集中元素的每个表示之间的余弦相似度。

如果你有一个非常大的数据集,你可以在获得表示之后和预测新数据之前对它进行聚类(例如使用 scikit learn 中的 KMeans)。

此代码执行所有这些步骤。你可以在我的 github repo上查看。

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.cluster import KMeans
from sklearn.metrics import adjusted_rand_score
import numpy

texts = ["This first text talks about houses and dogs",
        "This is about airplanes and airlines",
        "This is about dogs and houses too, but also about trees",
        "Trees and dogs are main characters in this story",
        "This story is about batman and superman fighting each other", 
        "Nothing better than another story talking about airplanes, airlines and birds",
        "Superman defeats batman in the last round"]

# vectorization of the texts
vectorizer = TfidfVectorizer(stop_words="english")
X = vectorizer.fit_transform(texts)
# used words (axis in our multi-dimensional space)
words = vectorizer.get_feature_names()
print("words", words)


n_clusters=3
number_of_seeds_to_try=10
max_iter = 300
number_of_process=2 # seads are distributed
model = KMeans(n_clusters=n_clusters, max_iter=max_iter, n_init=number_of_seeds_to_try, n_jobs=number_of_process).fit(X)

labels = model.labels_
# indices of preferible words in each cluster
ordered_words = model.cluster_centers_.argsort()[:, ::-1]

print("centers:", model.cluster_centers_)
print("labels", labels)
print("intertia:", model.inertia_)

texts_per_cluster = numpy.zeros(n_clusters)
for i_cluster in range(n_clusters):
    for label in labels:
        if label==i_cluster:
            texts_per_cluster[i_cluster] +=1 

print("Top words per cluster:")
for i_cluster in range(n_clusters):
    print("Cluster:", i_cluster, "texts:", int(texts_per_cluster[i_cluster])),
    for term in ordered_words[i_cluster, :10]:
        print("\t"+words[term])

print("\n")
print("Prediction")

text_to_predict = "Why batman was defeated  by superman so easy?"
Y = vectorizer.transform([text_to_predict])
predicted_cluster = model.predict(Y)[0]
texts_per_cluster[predicted_cluster]+=1

print(text_to_predict)
print("Cluster:", predicted_cluster, "texts:", int(texts_per_cluster[predicted_cluster])),
for term in ordered_words[predicted_cluster, :10]:
print("\t"+words[term])

最近有一些基于 RNN 模型中的变分自动编码器的工作。从连续空间生成句子,使用 pytorch 实现:github 代码
他们设法将句子的语义、句法全局特征压缩到可能用一些有限的 10 到 30 个独立随机变量(分解分布)表示的潜在空间中。
这项工作中的新颖想法,它们插入两个句子之间。结果非常惊人。