计算两个句子字符串的余弦相似度
来自于 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
8 个回答
我有一个类似的解决方案,但可能对pandas有用。
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)
简单来说,答案是“不,这样做在原则上是不可能的,而且效果也不会好”。这是自然语言处理研究中的一个未解决的问题,同时也是我博士研究的主题。我会简单总结一下我们目前的进展,并推荐一些相关的出版物:
单词的意义
这里最重要的假设是,我们可以得到一个向量来表示句子中的每个单词。这个向量通常是为了捕捉单词可能出现的上下文。例如,如果我们只考虑“吃”、“红色”和“毛茸茸”这三个上下文,单词“猫”可能会被表示为[98, 1, 87]。因为如果你阅读一段非常非常长的文本(按今天的标准,几亿个单词并不罕见),单词“猫”通常会出现在“毛茸茸”和“吃”的上下文中,而在“红色”的上下文中出现得就少得多。类似地,“狗”可能被表示为[87, 2, 34],而“雨伞”可能是[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自然出现的机会就会急剧减少。举个例子,谷歌有大约10亿个页面包含“狐狸”,但没有一个页面包含“姜色狐狸爱水果”,尽管这是一句完全有效的英语句子,我们都明白它的意思。
组合
为了应对数据稀疏的问题,我们想要进行组合,也就是将单词的向量(这些向量很容易从真实文本中获得)以某种方式结合在一起,以捕捉它们的意义。坏消息是,到目前为止,没有人能够很好地做到这一点。
最简单和最明显的方法是将单个单词的向量相加或相乘。这会导致一个不好的副作用,即“猫追狗”和“狗追猫”在你的系统中会被认为是相同的。此外,如果你在进行乘法运算时不小心,所有句子最终可能会被表示为[0, 0, 0,..., 0],这就失去了意义。
进一步阅读
我不会讨论到目前为止提出的更复杂的组合方法。我建议你阅读Katrin Erk的“单词意义和短语意义的向量空间模型:综述”。这是一个非常好的高水平综述,可以帮助你入门。不幸的是,这篇文章在出版商的网站上并不免费,你可以直接给作者发邮件索要一份。在那篇论文中,你会找到许多更具体方法的参考。比较易懂的有Mitchel和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,你需要有一个相对较大的文本库来估算tf-idf的权重。
你还可以进一步改进,比如用更复杂的方法从文本中提取单词,对单词进行词干提取或词形还原等等。