【问题标题】:Calculate cosine similarity given 2 sentence strings计算给定2个句子字符串的余弦相似度
【发布时间】:2013-02-16 20:50:55
【问题描述】:

Python: tf-idf-cosine: to find document similarity 开始,可以使用 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

【问题讨论】:

  • 我没有答案,但如果你想要有意义的结果,像 word2vec (code.google.com/p/word2vec) 这样的东西可能是一个好的开始。
  • @static_rtti word2vec 与余弦相似度无关。那是关于嵌入的。在这里,他给出了他想要计算余弦相似度的两个字符串。
  • 如果有人在寻找语义相似度gensim 会很有帮助。

标签: python string nlp similarity cosine-similarity


【解决方案1】:

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

【讨论】:

    【解决方案2】:

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

    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)
    

    【讨论】:

      【解决方案3】:

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

      这里使用的余弦公式描述为here

      这不包括 tf-idf 对单词的加权,但是为了使用 tf-idf,您需要有一个相当大的语料库来估计 tfidf 权重。

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

      【讨论】:

      • “猫以老鼠为食”和“啮齿动物经常被猫吃掉”怎么样?您的代码错误地返回 0。
      • 当然,SO 问题不是最终解决句子语义相似性建模问题的地方。问题是关于测量两段文本之间的(表面)相似性,这就是代码所做的。
      • 代码返回0,正确,因为它测量两个文本的表面相似性,它不测量含义。
      • 我同意你的第一点,不同意第二点。 SO 不适合长时间的理论科学讨论,这就是为什么我避免谈论技术方面的原因。虽然您的回答中肯且正确,但问题显然不是关于表面相似性的。请再看问题中的例句。
      • 您专门询问了余弦。我专门回答了这个问题。余弦被实现为“现实地”,你可能会得到。如果您的意思是“如何比余弦做得更好来衡量相似性”,那么这是一个不同的问题。
      【解决方案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】:

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

        【讨论】:

        • 假设第一句中的 6 个单词和 100 是嵌入大小,第二个中的 4 个单词,得到句子 1 (6 * 100) 和句子 2 (4*100) 的每个单词嵌入后要做什么100D 的句子是嵌入的。如果你有的话,我可以做词向量的总和或指导我吗?
        【解决方案6】:

        感谢@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 之间的夹角的余弦。

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

        【讨论】:

          【解决方案7】:

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

          词义

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

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

          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 的 "Vector space models of word meaning and phrase meaning: a survey"。这是一个很好的高级调查,可以帮助您入门。不幸的是,出版商的网站上没有免费提供,请直接给作者发送电子邮件以获取副本。在那篇论文中,您将找到对许多更具体方法的参考。比较容易理解的是Mitchel and Lapata (2008)Baroni and Zamparelli (2010)


          @vpekar 发表评论后编辑: 这个答案的底线是要强调一个事实,虽然朴素的方法确实存在(例如加法、乘法、表面相似性等),但这些方法存在根本性缺陷,而且总的来说人们不应该期望他们有出色的表现。

          【讨论】:

          • 出于好奇,您使用什么方法构建词库?
          • 这是一个分布式词库,使用Byblo 构建。在这个特定的实例中,每个标记都具有在所有 Wikipedia 中围绕它的 5 个单词的窗口中出现的其他标记作为特征,并且基于这些特征计算相似度。我们已经建立了其他词库,其中特征是与目标词有语法关系的其他词。这通常效果更好,但至少需要对语料库进行部分解析,这需要很长时间。
          • 为什么不能使用RNN来处理合成?这样我们就可以考虑词序以及它们各自的含义。
          • 当然可以。这个答案现在已经超过 5 年了。当时 RNN 刚刚开始(再次)变大,并没有真正引起我的注意
          • @OlegAfanasyev - 你是说带有 CNN 或 RNN 的 AutoEncoder?我要实施,如果像往常一样需要什么,您能提供任何指导吗?
          猜你喜欢
          • 2015-05-24
          • 2017-07-07
          • 2016-03-06
          • 2018-04-11
          • 1970-01-01
          • 2020-03-16
          • 2014-03-25
          • 2017-02-03
          相关资源
          最近更新 更多