79

来自Python:tf-idf-cosine:要找到文档相似度,可以使用 tf-idf cosine 计算文档相似度。在不导入外部库的情况下,是否有任何方法可以计算 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
4

7 回答 7

174

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

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

于 2013-03-02T12:40:22.793 回答
50

简短的回答是“不,不可能以一种原则性的方式来做到这一点,即使是远程也能很好地工作”。这是自然语言处理研究中一个未解决的问题,也恰好是我博士工作的主题。我将非常简要地总结一下我们所处的位置,并为您指出一些出版物:

词义

这里最重要的假设是有可能得到一个代表每个词的向量在有问题的句子中。通常选择这个向量来捕获单词可能出现的上下文。例如,如果我们只考虑三个上下文“eat”、“red”和“fluffy”,那么单词“cat”可能表示为 [98, 1 , 87],因为如果您要阅读一段非常长的文本(按照今天的标准,几十亿字并不少见),“猫”这个词会经常出现在“蓬松”和“吃”的上下文中,但在“红色”的上下文中并不常见。同样,“dog”可能表示为 [87,2,34],“umbrella”可能表示为 [1,13,0]。将这些向量成像为 3D 空间中的点,“cat”显然更接近“dog”而不是“umbrella”,因此“cat”

自 90 年代初以来一直对这一工作进行调查(例如Geffenstette 的这项工作),并取得了一些令人惊讶的好结果。例如,这是我最近通过让我的计算机阅读 wikipedia 构建的同义词库中的一些随机条目:

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 大约有 1B 页面包含“狐狸”一词,而没有一个页面包含“姜狐狸爱水果”,尽管它是一个完全有效的英文句子,我们都明白它的意思。

作品

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

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

进一步阅读

我不会讨论迄今为止提出的更复杂的组合方法。我建议你阅读 Katrin Erk 的“词义和短语意义的向量空间模型:调查”。这是一个很好的高级调查,可以帮助您入门。不幸的是,出版商的网站上没有免费提供,请直接给作者发送电子邮件以获取副本。在那篇论文中,您将找到对许多更具体方法的参考。更容易理解的是Mitchel 和 Lapata (2008)以及Baroni 和 Zamparelli (2010)


在@vpekar 发表评论后进行编辑:这个答案的底线是强调这样一个事实,即虽然确实存在幼稚的方法(例如加法、乘法、表面相似性等),但这些方法从根本上来说是有缺陷的,通常人们不应该期望从他们。

于 2013-03-02T11:15:56.773 回答
6

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

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)
于 2020-03-27T23:03:38.170 回答
3

试试这个。从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
于 2018-11-21T07:42:36.927 回答
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 之间的夹角的余弦。

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

于 2015-02-13T22:56:14.267 回答
1

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

于 2018-07-05T05:44:39.420 回答
0

在不使用外部库的情况下,您可以尝试 BLEU 或其替代品。你可以参考它的标准实现:SACREBLEU

于 2021-09-20T13:02:33.370 回答