计算给定两个句子字符串的余弦距离

Python: tf-idf-cosine: 查找文档相似性可以使用 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
130627 次浏览

简短的回答是“不,这是不可能的,以一种有原则的方式来做,甚至远远不够好”。这是自然语言处理研究中一个尚未解决的问题,也是我的博士论文的主题。我将非常简要地总结一下我们现在的情况,并向你们介绍一些出版物:

字面意思

这里最重要的假设是,有可能获得一个向量,代表有关句子中的每个 。通常选择这个向量来捕捉单词可能出现的上下文。例如,如果我们只考虑“吃”、“红色”和“毛茸茸”这三种语境,那么“猫”这个词可能会被表示为[98,1,87] ,因为如果你要阅读一篇非常非常长的文本(按照今天的标准,几十亿个单词并不罕见) ,“猫”这个词会经常出现在“毛茸茸”和“吃”的语境中,但不经常出现在“红色”的语境中。同样,“ dog”可以表示为[87,2,34] ,“ rella”可以表示为[1,13,0]。把这些向量想象成三维空间中的点,“猫”显然比“伞”更接近于“狗”,因此“猫”的意思也更接近于“狗”而不是“伞”。

这项工作从90年代早期就开始了(例如 Greffenstette 的 这个工作) ,并且取得了一些惊人的好结果。例如,下面是我最近用电脑阅读维基百科建立的同义词表中的一些随机条目:

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 个页面包含单词“ fox”,而没有一个页面包含“ ginger fox love water”,尽管这是一个完全正确的英语句子,而且我们都明白它的意思。

作曲

为了解决数据稀疏的问题,我们需要进行合成,即从实际文本中获取单词的向量,并以一种捕捉其意义的方式将它们组合在一起。坏消息是,迄今为止还没有人能做到这一点。

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

进一步阅读

我不会讨论到目前为止已经提出的更复杂的作曲方法。我建议你读一下 Katrin Erk 的 词义和短语意义的向量空间模型: 概述。这是一个非常好的高级别调查,可以让你开始。遗憾的是,在出版商的网站上并不能免费获得,作者可以通过电子邮件直接获得一份副本。在那篇文章中,你会找到更多具体方法的参考资料。比较容易理解的是 Mitchell and Lapata (2008)Baroni 和 Zamparelli (2010)


评论后编辑@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 |) = (sum (qi,di)/(sqrt (sum (qi2))) * (sqrt (sum (vi2))) ,其中 i = 1 to v)

  • Qi 是查询中项 i 的 tf-idf 权重。
  • Di 是 tf-idf
  • 文档中 i 项的权重。 | q | 和 | d | 是 q 的长度 还有 D。
  • 这是 q 和 d 的余弦距离 等价的是 q 和 d 之间夹角的余弦。

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

好吧,如果你知道 词汇嵌入像手套/Word2Vec/Numberbatch,你的工作已经完成了一半。如果不让我解释如何处理这个问题。 Convert each sentence into word tokens, and represent each of these tokens as vectors of high dimension (using the pre-trained word embeddings, or you could 火车 them yourself even!). So, now you just don't capture their surface similarity but rather extract the meaning of each word which comprise the sentence as a whole. After this calculate their cosine similarity and you are set.

试试这个。从 https://conceptnet.s3.amazonaws.com/downloads/2017/numberbatch/numberbatch-en-17.06.txt.gz下载并解压缩文件“ numberbatch-en-17.06.txt”。函数“ get _ amp _ 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

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

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)

不使用外部库,您可以尝试 BLEU 或其替代品。您可以参考其标准实现: 萨克雷布鲁

我能想到的最简单的答案包括 CounterVectorizer。

假设我们有3段文字。

text_1 = """ """


text_2 = """ """


text_3 = """ """


documents = [text_1, text_2, text_3]
  1. 为了计算余弦距离,我们需要每个文档的单词计数矩阵
import pandas as pd


# Create the Document Term Matrix
count_vectorizer = CountVectorizer(stop_words='english')
count_vectorizer = CountVectorizer()
sparse_matrix = count_vectorizer.fit_transform(documents)


# OPTIONAL: Convert Sparse Matrix to Pandas Dataframe if you want to see the word frequencies.
doc_term_matrix = sparse_matrix.todense()
df = pd.DataFrame(doc_term_matrix,
columns=count_vectorizer.get_feature_names(),
index=['text_1', 'text_2', 'text_3'])
df
  1. 简单的余弦距离功能就可以完成 sklearn 的工作。
from sklearn.metrics.pairwise import cosine_similarity
print(cosine_similarity(df, df))