给出2个句子字符串计算余弦相似度

时间:2013-03-02 10:06:30

标签: python string nlp similarity cosine-similarity

Python: tf-idf-cosine: to find document similarity开始,可以使用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

6 个答案:

答案 0 :(得分:145)

一个简单的纯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

此处使用的余弦公式描述为here

这不包括tf-idf对单词的加权,但是为了使用tf-idf,你需要有一个相当大的语料库来估计tfidf权重。

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

答案 1 :(得分:50)

简短的回答是“不,不可能以有原则的方式做到这一点,甚至可以做得很好”。这是自然语言处理研究中尚未解决的问题,也恰好是我博士工作的主题。我将简要总结一下我们所处的位置并指出一些出版物:

词语含义

这里最重要的假设是,可以获得一个代表句子中每个单词的向量。通常选择此向量来捕获单词可以出现的上下文。例如,如果我们只考虑三个上下文“吃”,“红色”和“蓬松”,单词“cat”可能表示为[98,1 ,87,因为如果你要阅读一段非常长的文本(按照今天的标准,几十亿字并不少见),“cat”这个词经常出现在“蓬松”和“吃”的语境中,但不是经常在“红色”的背景下。以同样的方式,“狗”可能表示为[87,2,34],“伞”可能表示为[1,13,0]。将这些矢量成像为3D空间中的点,“猫”显然更接近“狗”而不是“伞”,因此“猫”也意味着更类似于“狗”而不是“伞”。

自90年代初以来,这项工作已经进行了调查(例如Greffenstette的this工作),并取得了一些令人惊讶的好结果。例如,这是我最近通过让我的计算机阅读维基百科而建立的词库中的一些随机条目:

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

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

短语的问题

你可能会问为什么我们不会为更长的短语做同样的事情,比如“生姜狐狸爱水果”。这是因为我们没有足够的文字。为了让我们可靠地确定X类似的东西,我们需要看到许多X在上下文中使用的例子。当X是像“语音”这样的单个词时,这并不太难。然而,随着X越来越长,找到X的自然出现的几率会以指数方式变慢。相比之下,谷歌有大约1B页包含“狐狸”这个词,而不是包含“生姜爱水果”的单页,尽管它是一个完全有效的英语句子,我们都明白这意味着什么。

<强>组合物

为了解决数据稀疏性的问题,我们希望执行组合,即采用易于从真实文本中获取的单词的向量,并以捕获其含义的方式将它们组合在一起。坏消息是到目前为止还没有人能做到这一点。

最简单和最明显的方法是将单个单词向量相加或相乘。这导致了不良的副作用,“猫追狗”和“狗追猫”对你的系统意味着相同。此外,如果你正在成倍增加,你必须要格外小心,否则每个句子最终都会被[0,0,0,...,0]表示,这会使得这一点失败。

进一步阅读

我不会讨论到目前为止提出的更复杂的构图方法。我建议你阅读Katrin Erk的"Vector space models of word meaning and phrase meaning: a survey"。这是一个非常好的高级调查,可以帮助您入门。不幸的是,发布者的网站上没有免费提供,请直接通过电子邮件发送给作者以获取副本。在该论文中,您将找到更多具体方法的参考。更容易理解的是Mitchel and Lapata (2008)Baroni and Zamparelli (2010)


@vpekar评论后编辑: 这个答案的底线是强调这样一个事实:虽然天真的方法确实存在(例如加法,乘法,表面相似性等),但这些从根本上是有缺陷的,并且一般来说不应期待他们的出色表现。

答案 2 :(得分:3)

我有类似的解决方案,但可能对熊猫有用

import math
import re
from collections import Counter
import pandas as pd

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 list(vec1.keys())])
    sum2 = sum([vec2[x] ** 2 for x in list(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)

df=pd.read_csv('/content/drive/article.csv')
df['vector1']=df['headline'].apply(lambda x: text_to_vector(x)) 
df['vector2']=df['snippet'].apply(lambda x: text_to_vector(x)) 
df['simscore']=df.apply(lambda x: get_cosine(x['vector1'],x['vector2']),axis=1)

答案 3 :(得分:1)

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

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之间角度的余弦。

请随时查看我的代码here。但首先你必须下载anaconda包。它会自动在Windows中设置python路径。在Eclipse中添加这个python解释器。

答案 4 :(得分:1)

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

答案 5 :(得分:1)

尝试一下。从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