Python 计算两个句子串的余弦相似度

Python 计算两个句子串的余弦相似度,python,string,nlp,similarity,cosine-similarity,Python,String,Nlp,Similarity,Cosine Similarity,从中,可以使用tf idf余弦计算文档相似性。在不导入外部库的情况下,有没有办法计算两个字符串之间的余弦相似性 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

从中,可以使用tf idf余弦计算文档相似性。在不导入外部库的情况下,有没有办法计算两个字符串之间的余弦相似性

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
简言之,答案是“不,这是不可能做到的,在一个原则性的方式,甚至远程工作良好”。这是自然语言处理研究中尚未解决的问题,也是我博士研究的主题。我将非常简要地总结一下我们的情况,并向您介绍一些出版物:

词语的含义

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

这项工作从90年代初就开始了研究(例如Greffensette的工作),并取得了一些令人惊讶的好结果。例如,以下是我最近通过让电脑阅读维基百科构建的一个同义词库中的一些随机条目:

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的自然出现的机会呈指数级降低。相比之下,谷歌有大约10亿个页面包含“狐狸”一词,而没有一个页面包含“姜狐狸爱水果”,尽管这是一个非常有效的英语句子,我们都理解它的意思

作文

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

最简单和最明显的方法是将单个单词向量相加或相乘。这会导致不良的副作用,即“猫追狗”和“狗追猫”对您的系统的意义相同。此外,如果你在乘法,你必须格外小心,否则每一个句子都会以[0,0,0,…,0]来表示,这就不符合要点

进一步阅读

我不会讨论到目前为止提出的更复杂的构图方法。我建议你读一下卡特琳·埃尔克的。这是一个非常好的高级调查,可以帮助您开始。不幸的是,这本书不能在出版商的网站上免费获得,请直接通过电子邮件向作者索取。在那篇论文中,你会找到更多具体方法的参考资料。更容易理解的是


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

一个简单的纯Python实现是:

import math
import re
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 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)


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权重


您还可以进一步开发它,使用更复杂的方法从文本中提取单词、词干或柠檬酸盐等。

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

cos(q,d)=sim(q,d)=(q·d)/(q | | | | d |)=(求和(qi,di)/(求和(qi2))*(求和(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这样的代码,那么您的工作就完成了一半。如果没有,让我解释一下如何解决这个问题。
将每个句子转换为单词标记,并将每个标记表示为高维向量(使用预先训练好的单词嵌入,或者您自己也可以!)。所以,现在你不需要捕捉它们表面上的相似性,而是提取组成整个句子的每个单词的意思。在此之后,计算它们的余弦相似性,然后设置

试试这个。从下载文件“numberbatch-en-17.06.txt”并将其解压缩。函数“get_Session_vector”使用简单的词向量和。但是,它可以通过使用加权和来改进,其中
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
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)