给定 2 个句子字符串计算余弦相似度
- 2025-01-10 08:47:00
- admin 原创
- 112
问题描述:
从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
解决方案 1:
一个简单的纯 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 权重。
您还可以进一步开发它,通过使用更复杂的方法从一段文本中提取单词、对其进行词干或词形还原等。
解决方案 2:
简短的回答是“不,不可能以有原则的方式做到这一点,甚至效果也不太好”。这是自然语言处理研究中尚未解决的问题,也是我博士论文的主题。我将非常简要地总结一下我们目前的情况,并向您介绍一些出版物:
词语含义
这里最重要的假设是,有可能获得一个表示问题句子中每个单词的向量。通常选择这个向量来捕获单词可能出现的上下文。例如,如果我们只考虑三个上下文“吃”、“红色”和“毛茸茸的”,单词“猫”可能表示为 [98, 1, 87],因为如果你要阅读一段非常长的文本(按今天的标准,几十亿个单词并不罕见),单词“猫”会经常出现在“毛茸茸的”和“吃”的上下文中,但在“红色”的上下文中出现的频率并不高。同样,“狗”可能表示为 [87,2,34],“雨伞”可能表示为 [1,13,0]。将这些向量想象成 3D 空间中的点,“猫”显然更接近“狗”而不是“雨伞”,因此“猫”的意思也更类似于“狗”而不是“雨伞”。
这项工作自 20 世纪 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 自然出现的机会会呈指数级下降。相比之下,Google 有大约 10 亿个页面包含单词“fox”,而没有一个页面包含“姜狐狸爱水果”,尽管这是一个完全有效的英语句子,我们都明白它的意思。
作品
为了解决数据稀疏性问题,我们希望进行组合,即获取单词向量(这些向量很容易从真实文本中获得),然后以能够捕捉其含义的方式将它们组合在一起。坏消息是,到目前为止,还没有人能够很好地做到这一点。
最简单、最明显的方法是将各个词向量相加或相乘。这会导致不良的副作用,即“猫追狗”和“狗追猫”对您的系统来说意味着相同。此外,如果您要进行乘法运算,则必须格外小心,否则每个句子最终都会由 [0,0,0,...,0] 表示,这违背了要点。
进一步阅读
我不会讨论迄今为止提出的更复杂的组合方法。我建议你阅读 Katrin Erk 的“词义和短语义的向量空间模型:调查”。这是一篇非常好的高级调查,可以帮助你入门。不幸的是,在出版商的网站上没有免费提供,请直接给作者发送电子邮件以获取副本。在那篇论文中,你会找到许多更具体方法的参考。更易理解的是Mitchel 和 Lapata (2008)和Baroni 和 Zamparelli (2010)的论文。
在@vpekar 评论后进行编辑:这个答案的底线是强调这样一个事实:虽然确实存在简单的方法(例如加法、乘法、表面相似性等),但这些方法从根本上是有缺陷的,一般来说,不要指望它们有很好的性能。
解决方案 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)
解决方案 4:
试试这个。从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
解决方案 5:
我能想到的最简单的答案包括 CounterVectorizer。
假设我们有 3 段文本。
text_1 = """ """
text_2 = """ """
text_3 = """ """
documents = [text_1, text_2, text_3]
为了计算余弦相似度,我们需要每个文档的单词计数矩阵
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
只需使用 sklearn 中的余弦相似度函数即可完成这项工作。
from sklearn.metrics.pairwise import cosine_similarity
print(cosine_similarity(df, df))
解决方案 6:
好吧,如果您知道Glove/Word2Vec/Numberbatch 之类的词嵌入,那么您的工作就完成了一半。如果不了解,让我来解释如何解决这个问题。将每个句子转换为单词标记,并将每个标记表示为高维向量(使用预先训练的词嵌入,或者您甚至可以自己训练它们!)。所以,现在您不仅要捕捉它们的表面相似性,还要提取构成整个句子的每个单词的含义。在此之后,计算它们的余弦相似性,您就大功告成了。
解决方案 7:
感谢@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 之间角度的余弦。
请随意在此处查看我的代码。但首先您必须下载 anaconda 包。它将自动在 Windows 中设置您的 python 路径。在 Eclipse 中添加此 python 解释器。
解决方案 8:
如果不依赖外部库,你可以尝试 BLEU 或者它的替代品。你可以参考它的标准实现:SACREBLEU。