给定2个句子字符串,计算余弦相似度
nlp
python
8
0

Python:tf-idf-cosine:查找文档相似度 ,可以使用tf-idf余弦计算文档相似度。如果不导入外部库,是否有任何方法可以计算2个字符串之间的余弦相似度?

s1 = "This is a foo bar sentence ."
s2 = "This sentence is similar to a foo bar sentence ."
s3 = "What is this string ? Totally not related to the other two lines ."

cosine_sim(s1, s2) # Should give high cosine similarity
cosine_sim(s1, s3) # Shouldn't give high cosine similarity value
cosine_sim(s2, s3) # Shouldn't give high cosine similarity value
参考资料:
Stack Overflow
收藏
评论
共 5 个回答
高赞 时间 活跃

一个简单的纯Python实现是:

import re, math
from collections import Counter

WORD = re.compile(r'\w+')

def get_cosine(vec1, vec2):
     intersection = set(vec1.keys()) & set(vec2.keys())
     numerator = sum([vec1[x] * vec2[x] for x in intersection])

     sum1 = sum([vec1[x]**2 for x in vec1.keys()])
     sum2 = sum([vec2[x]**2 for x in vec2.keys()])
     denominator = math.sqrt(sum1) * math.sqrt(sum2)

     if not denominator:
        return 0.0
     else:
        return float(numerator) / denominator

def text_to_vector(text):
     words = WORD.findall(text)
     return Counter(words)

text1 = 'This is a foo bar sentence .'
text2 = 'This sentence is similar to a foo bar sentence .'

vector1 = text_to_vector(text1)
vector2 = text_to_vector(text2)

cosine = get_cosine(vector1, vector2)

print 'Cosine:', cosine

印刷品:

Cosine: 0.861640436855

这里所用的余弦公式描述这里

这不包括通过tf-idf对单词进行加权,但是为了使用tf-idf,您需要拥有一个相当大的语料库,据此可以估算tfidf的权重。

您还可以通过使用更复杂的方法从一段文本中提取单词,对其进行词干或词义化等来进一步开发它。

收藏
评论

尝试这个。从https://conceptnet.s3.amazonaws.com/downloads/2017/numberbatch/numberbatch-en-17.06.txt.gz下载文件'numberbatch-en-17.06.txt'并解压缩。函数“ get_sentence_vector”使用单词向量的简单总和。但是,可以通过使用权重与每个单词的Tf-Idf成正比的加权和来改进它。

import math
import numpy as np

std_embeddings_index = {}
with open('path/to/numberbatch-en-17.06.txt') as f:
    for line in f:
        values = line.split(' ')
        word = values[0]
        embedding = np.asarray(values[1:], dtype='float32')
        std_embeddings_index[word] = embedding

def cosineValue(v1,v2):
    "compute cosine similarity of v1 to v2: (v1 dot v2)/{||v1||*||v2||)"
    sumxx, sumxy, sumyy = 0, 0, 0
    for i in range(len(v1)):
        x = v1[i]; y = v2[i]
        sumxx += x*x
        sumyy += y*y
        sumxy += x*y
    return sumxy/math.sqrt(sumxx*sumyy)


def get_sentence_vector(sentence, std_embeddings_index = std_embeddings_index ):
    sent_vector = 0
    for word in sentence.lower().split():
        if word not in std_embeddings_index :
            word_vector = np.array(np.random.uniform(-1.0, 1.0, 300))
            std_embeddings_index[word] = word_vector
        else:
            word_vector = std_embeddings_index[word]
        sent_vector = sent_vector + word_vector

    return sent_vector

def cosine_sim(sent1, sent2):
    return cosineValue(get_sentence_vector(sent1), get_sentence_vector(sent2))

我确实跑了给定的句子,发现以下结果

s1 = "This is a foo bar sentence ."
s2 = "This sentence is similar to a foo bar sentence ."
s3 = "What is this string ? Totally not related to the other two lines ."

print cosine_sim(s1, s2) # Should give high cosine similarity
print cosine_sim(s1, s3) # Shouldn't give high cosine similarity value
print cosine_sim(s2, s3) # Shouldn't give high cosine similarity value

0.9851735249068168
0.6570885718962608
0.6589335425458225
收藏
评论

简短的答案是“不,不可能以原则上可行的方式做到这一点”。在自然语言处理研究中,这是一个尚未解决的问题,并且恰好也是我的博士论文的主题。我将简要概述一下我们的位置,并为您提供一些出版物:

词义

这里最重要的假设是,有可能获得一个向量,该向量表示疑问句中的每个单词 。通常选择此向量来捕获单词可能出现的上下文。例如,如果仅考虑“吃”,“红色”和“蓬松”这三个上下文,则单词“ cat”可能表示为[98,1 (87),因为如果您要阅读很长的一段文字(按照今天的标准,几十亿个单词并不少见),那么“猫”一词经常出现在“蓬松”和“吃”的上下文中,但在“红色”的背景下并不常见。同样,“狗”可以表示为[87,2,34],“伞”可以表示为[1,13,0]。将这些向量成像为3D空间中的点,“猫”显然比“雨伞”更接近“狗”,因此,“猫”还比“雨伞”更类似于“狗”。

工作的这条线,因为90年代初(例如已被调查这个由Greffenstette工作),并已取得了一些效果出奇的好。例如,这是我最近通过计算机阅读维基百科建立的同义词库中的一些随机条目:

theory -> analysis, concept, approach, idea, method
voice -> vocal, tone, sound, melody, singing
james -> william, john, thomas, robert, george, charles

这些类似单词的列表完全是在没有人工干预的情况下获得的-您输入文本并在几个小时后返回。

短语问题

您可能会问,为什么我们对更长的短语不做同样的事情,例如“狐狸爱吃水果”。这是因为我们没有足够的文字。为了使我们能够可靠地确定X类似于什么,我们需要查看在上下文中使用X的许多示例。当X是单个单词(例如“ voice”)时,这不太难。但是,随着X越长,发现X的自然出现的机会就越快。为了进行比较,尽管Google句子是完全有效的英语句子,但Google大约有1B页面包含“ fox”一词,而不是包含“ ginger foxes love fruit”的单个页面,我们都知道这是什么意思。

组成

为了解决数据稀疏性的问题,我们希望执行合成,即为单词提取矢量,这些单词很容易从真实文本中获取,并以一种可捕捉其含义的方式将它们放在一起。坏消息是,到目前为止,没有人能够做到这一点。

最简单,最明显的方法是将各个单词向量相加或相乘。这会导致不良后果,即“猫追狗”和“猫追狗”对您的系统意味着相同。另外,如果要相乘,则必须格外小心,否则每个句子最终都会以[0,0,0,...,0]表示,这不符合要求。

进一步阅读

到目前为止,我不会讨论更复杂的合成方法。我建议您阅读Katrin Erk的“单词含义和短语含义的向量空间模型:调查” 。这是一个非常好的高级调查,可以帮助您入门。不幸的是,它不能在发行者的网站上免费获得,请直接向作者发送电子邮件以获取副本。在那篇论文中,您将找到许多更具体方法的参考。 Mitchel和Lapata(2008)以及Baroni和Zamparelli(2010)更容易理解。


@vpekar发表评论后编辑:此答案的底线是强调以下事实:尽管存在幼稚的方法 (例如加法,乘法,表面相似性等),但这些方法从根本上来说是有缺陷的,并且通常不应期望从中获得出色的性能他们。

收藏
评论

感谢@vpekar的实施。这很有帮助。我只是发现在计算余弦相似度时,它错过了tf-idf权重。计数器(单词)返回一个字典,其中包含单词列表及其出现。

cos(q,d)= sim(q,d)=(q·d)/(| q || d |)=(sum(qi,di)/(sqrt(sum(qi2)))*(sqrt( sum(vi2))),其中i = 1至v)

  • qi是查询中第i项的tf-idf权重。
  • di是tf-idf
  • 文档中术语i的权重。 | q |和| d |是q和d的长度。
  • 这是q和d的余弦相似度。 。 。 。 。 。或等效地,q和d之间的角度的余弦值。

请随时在这里查看我的代码。但是首先,您必须下载anaconda软件包。它将在Windows中自动为您设置python路径。在Eclipse中添加此python解释器。

收藏
评论

好吧,如果您知道像Glove / Word2Vec / Numberbatch这样的词嵌入 ,您的工作就完成了一半。如果不能,请允许我解释如何解决。将每个句子转换为单词标记,并将每个标记表示为高维向量(使用预先训练的单词嵌入,或者甚至可以自己训练它们!)。因此,现在您只是不捕获它们的表面相似性,而是提取构成整个句子的每个单词的含义。在此之后,计算它们的余弦相似度并设置好。

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题