【问题标题】:Cosine similarity vs Hamming distance [closed]余弦相似度与汉明距离
【发布时间】:2010-10-31 01:13:20
【问题描述】:

为了计算两个文档之间的相似度,我创建了一个包含词频的特征向量。但是,对于下一步,我无法在“Cosine similarity”和“Hamming distance”之间做出决定。

我的问题:你有使用这些算法的经验吗?哪一个能给你带来更好的结果?

除此之外:您能告诉我如何在 PHP 中编写余弦相似度代码吗?对于汉明距离,我已经得到了代码:

function check ($terms1, $terms2) {
    $counts1 = array_count_values($terms1);
    $totalScore = 0;
    foreach ($terms2 as $term) {
        if (isset($counts1[$term])) $totalScore += $counts1[$term];
    }
    return $totalScore * 500 / (count($terms1) * count($terms2));
}

我不想使用任何其他算法。我只想在两者之间做出决定。

也许有人可以谈谈如何改进算法。过滤掉停用词或常用词会得到更好的结果吗?

我希望你能帮助我。提前致谢!

【问题讨论】:

    标签: php relationship similarity


    【解决方案1】:

    应在两个长度相等的字符串之间进行汉明距离,并考虑顺序。

    由于您的文档的长度肯定不同,并且如果单词位置不计算在内,余弦相似度会更好(请注意,根据您的需要,存在更好的解决方案)。 :)

    这是2个单词数组的余弦相似度函数:

    function cosineSimilarity($tokensA, $tokensB)
    {
        $a = $b = $c = 0;
        $uniqueTokensA = $uniqueTokensB = array();
    
        $uniqueMergedTokens = array_unique(array_merge($tokensA, $tokensB));
    
        foreach ($tokensA as $token) $uniqueTokensA[$token] = 0;
        foreach ($tokensB as $token) $uniqueTokensB[$token] = 0;
    
        foreach ($uniqueMergedTokens as $token) {
            $x = isset($uniqueTokensA[$token]) ? 1 : 0;
            $y = isset($uniqueTokensB[$token]) ? 1 : 0;
            $a += $x * $y;
            $b += $x;
            $c += $y;
        }
        return $b * $c != 0 ? $a / sqrt($b * $c) : 0;
    }
    

    它很快(isset() 而不是in_array() 是大型阵列的杀手)。

    如您所见,结果没有考虑每个单词的“大小”。

    我用它来检测“几乎”复制粘贴文本的多次发布消息。它运作良好。 :)

    关于字符串相似度指标的最佳链接http://www.dcs.shef.ac.uk/~sam/stringmetrics.html

    更多有趣的阅读:

    http://www.miislita.com/information-retrieval-tutorial/cosine-similarity-tutorial.html http://bioinformatics.oxfordjournals.org/cgi/content/full/22/18/2298

    【讨论】:

    • 非常感谢。 :) 但是迈克的解决方案(选择的答案)不是更好吗?代码更短,似乎和你的一样快。有什么区别?
    • Mike 的功能并不完全准确。试试echo check(array('a', 'b', 'c'), array('a', 'b', 'c')); 它应该返回 1 (cos(0)) 但他的函数返回 0.33。 :(
    • 你的函数真的正确吗?它为 [1, 1, 1] 和 [1, 1, 0] 给出 0.71。但是miislita.com/searchito/binary-similarity-calculator.html 给出 0.82?!是否仍然需要将相似度值除以文档长度?
    • 此工具用于二进制字符串比较。 “我的”功能适用于“文字文档”。结果不会一样。 :)
    • 好的,谢谢。我正在寻找一些工具进行比较,因为我想确定这次我有正确的功能;)而且我不需要将值除以文档的长度,因为长度在余弦中不起作用相似,对吧?
    【解决方案2】:

    除非我弄错了,否则我认为您的算法介于两种算法之间。对于汉明距离,使用:

    function check ($terms1, $terms2) {
        $counts1 = array_count_values($terms1);
        $totalScore = 0;
        foreach ($terms2 as $term) {
            if (isset($counts1[$term])) $totalScore += 1;
        }
        return $totalScore * 500 / (count($terms1) * count($terms2));
    }
    

    (请注意,您只为标记向量中的每个匹配元素添加 1。)

    对于余弦相似度,使用:

    function check ($terms1, $terms2) {
        $counts1 = array_count_values($terms1);
        $counts2 = array_count_values($terms2);
        $totalScore = 0;
        foreach ($terms2 as $term) {
            if (isset($counts1[$term])) $totalScore += $counts1[$term] * $counts2[$term];
        }
        return $totalScore / (count($terms1) * count($terms2));
    }
    

    (请注意,您要在两个文档之间添加令牌计数的乘积。)

    两者的主要区别在于当两个文档在文档中多次出现同一个词时,余弦相似度会产生更强的指示符,而汉明距离不关心多长时间个别令牌出现

    编辑:刚刚注意到您关于删除虚词等的查询。如果您要使用余弦相似度,我建议您这样做 - 因为虚词非常频繁(至少在英语中),您可能会通过不过滤掉结果来扭曲结果。如果使用汉明距离,效果不会那么好,但在某些情况下仍然可以察觉。此外,如果您有权访问lemmatizer,例如,当一个文档包含“galaxies”而另一个文档包含“galaxy”时,它将减少丢失。

    无论你走哪条路,祝你好运!

    【讨论】:

    • 非常感谢!那么,如果我使用两种算法的组合,它是否也结合了它们的优点?比这些算法更好,对吧? :) 还是我应该更好地使用您的代码示例之一?你的最后一句话很有趣。所以余弦相似度对我的目的会更好,对吧?如果一个词经常出现,它表示两个文本之间的关系更强,不是吗?
    • @marco92w:我认为余弦相似度在这种情况下是最好的——另请参阅我最近关于虚词的编辑。你的直觉已经死在那里了。
    • 谢谢,编辑内容也很丰富。最后一个问题::)余弦相似度和我的算法(有问题的代码)有什么区别?哪个更好?
    • 这个余弦相似度函数有些奇怪。在这种情况下,结果不应该是 1: echo check(array('a', 'b', 'c'), array('a', 'b', 'c'));相反,我得到 0.333,其结果与以下结果相同: echo check(array('a', 'b', 'c'), array('a', 'b'));
    • 托托是正确的。两个距离函数的向量范数计算都不正确。
    【解决方案3】:

    我很抱歉忽略了您说您不想使用任何其他算法的事实,但说真的,Levenshtein distanceDamerau-Levenshtein distance 比汉明距离更有用。这是一个D-L distance implementation in PHP,如果你不喜欢 PHP 的原生 levenshtein() 函数,我想你不会因为它有长度限制,这里是一个无长度限制的版本:

    function levenshtein_distance($text1, $text2) {
        $len1 = strlen($text1);
        $len2 = strlen($text2);
        for($i = 0; $i <= $len1; $i++)
            $distance[$i][0] = $i;
        for($j = 0; $j <= $len2; $j++)
            $distance[0][$j] = $j;
        for($i = 1; $i <= $len1; $i++)
            for($j = 1; $j <= $len2; $j++)
                $distance[$i][$j] = min($distance[$i - 1][$j] + 1, $distance[$i][$j - 1] + 1, $distance[$i - 1][$j - 1] + ($text1[$i - 1] != $text2[$j - 1]));
        return $distance[$len1][$len2];
    }
    

    【讨论】:

    • 谢谢。我想你误会了什么。我不想只使用汉明距离。我想将它应用于文本的特征向量,而不是文本本身。所以我会说它比 levenshtein 更有用,不是吗? ;) 但是感谢您提供的代码,我相信它对许多用户的其他用途很有用。
    • 糟糕。我确实没有吸收特征向量部分。没关系。 :) 既然您喜欢该代码,我将不删除答案。我希望投反对票的人会宽容。 :)
    • 是的,他们有。赞成者多于反对者。 ;)
    • levenshtein 应该用于计算编辑距离。所以这取决于需要。 “安娜弗雷德”和“弗雷德安娜”。 Lenvenshtein 会给出一个很高的数字,但对于余弦相似度(对于单词)来说,它将是 100% 相似的。相似与否?这取决于您的需求。
    【解决方案4】:

    这是 Toto 发布的余弦距离函数的更正代码

    function cosineSimilarity($tokensA, $tokensB)
    {
        $a = $b = $c = 0;
        $uniqueTokensA = $uniqueTokensB = array();
    
        $uniqueMergedTokens = array_unique(array_merge($tokensA, $tokensB));
    
        foreach ($tokensA as $token) $uniqueTokensA[$token] = 0;
        foreach ($tokensB as $token) $uniqueTokensB[$token] = 0;
    
        foreach ($uniqueMergedTokens as $token) {
            $x = isset($uniqueTokensA[$token]) ? 1 : 0;
            $y = isset($uniqueTokensB[$token]) ? 1 : 0;
            $a += $x * $y;
            $b += pow($x,2);
            $c += pow($y,2);
        }
        return $b * $c != 0 ? $a / sqrt($b * $c) : 0;
    }
    

    【讨论】:

    • $x(和 $y)将始终为 1(令牌存在)或 0(令牌不存在)。在这种情况下,POW($x, 2) 将始终返回 $x。因此我将其删除以节省 CPU。 :)
    • 所以你的版本都是正确的,洛伦佐和托托?他们都工作?
    猜你喜欢
    • 2017-12-12
    • 2020-04-22
    • 2015-05-31
    • 2014-02-25
    • 2020-02-11
    • 2020-08-12
    • 2013-02-27
    • 2016-01-01
    • 2013-10-24
    相关资源
    最近更新 更多