什么是确定 2 个字符串是不是“足够相似”的好指标
Posted
技术标签:
【中文标题】什么是确定 2 个字符串是不是“足够相似”的好指标【英文标题】:what is a good metric for deciding if 2 Strings are "similar enough"什么是确定 2 个字符串是否“足够相似”的好指标 【发布时间】:2012-01-17 02:30:56 【问题描述】:我正在研究一个非常粗略的初稿算法,以确定 2 个字符串的相似程度。我还使用Levenshtein Distance 来计算字符串之间的编辑距离。
我目前所做的基本上是将编辑总数除以较大字符串的大小。如果该值低于某个阈值,目前随机设置为 25%,那么它们“足够相似”。
但是,这完全是任意的,我认为这不是计算相似度的好方法。是否有某种数学方程式或概率/统计方法来获取 Levenshtein 距离数据并使用它来表示“是的,这些字符串根据所做的编辑次数和字符串的大小足够相似”?
另外,这里的关键是我使用的是任意阈值,我不想这样做。如何计算这个阈值而不是分配它,以便我可以安全地说 2 个字符串“足够相似”?
更新
我正在比较代表 Java 堆栈跟踪的字符串。我想这样做的原因是通过相似性对一堆给定的堆栈跟踪进行分组,并将其用作过滤器来对“东西”进行排序:) 这种分组对于更高层次的原因很重要,我不能完全公开分享。
到目前为止,我的算法(伪代码)大致如下:
/*
* The input lists represent the Strings I want to test for similarity. The
* Strings are split apart based on new lines / carriage returns because Java
* stack traces are not a giant one-line String, rather a multi-line String.
* So each element in the input lists is a "line" from its stack trace.
*/
calculate similarity (List<String> list1, List<String> list2)
length1 = 0;
length2 = 0;
levenshteinDistance = 0;
iterator1 = list1.iterator();
iterator2 = list2.iterator();
while ( iterator1.hasNext() && iterator2.hasNext() )
// skip blank/empty lines because they are not interesting
str1 = iterator1.next(); length1 += str1.length();
str2 = iterator2.next(); length2 += str2.length();
levensteinDistance += getLevenshteinDistance(str1, str2);
// handle the rest of the lines from the iterator that has not terminated
difference = levenshteinDistance / Math.max(length1, length2);
return (difference < 0.25) ? true : false; // <- arbitrary threshold, yuck!
【问题讨论】:
你知道mutual information吗? 最佳指标取决于您所说的“相似性”。你在比较什么,为什么? 【参考方案1】:如何使用余弦相似度?这是评估两个文本之间相似性的通用技术。它的工作原理如下:
从两个字符串中取出所有字母,然后构建一个像这样的表:
Letter | String1 | String2
这可以是一个简单的哈希表或其他任何东西。
在字母列中放入每个字母,在字符串列中将它们的频率放入该字符串中(如果字母未出现在字符串中,则值为 0)。
之所以称为余弦相似度,是因为您将两个字符串列中的每一个都解释为向量,其中每个分量都是与字母关联的数字。接下来,计算向量之间“角度”的余弦为:
C = (V1 * V2) / (|V1| * |V2|)
分子是点积,即对应分量的乘积之和,分母是向量大小的乘积。
C 与 1 的接近程度表明字符串有多相似。
它可能看起来很复杂,但是一旦你理解了这个想法,它只是几行代码。
让我们看一个例子:考虑字符串
s1 = aabccdd
s2 = ababcd
表格如下:
Letter a b c d
s1 2 1 2 2
s2 2 2 1 1
因此:
C = (V1 * V2) / (|V1| * |V2|) =
(2 * 2 + 1 * 2 + 2 * 1 + 2 * 1) / (sqrt(13) * sqrt(10)) = 0.877
所以它们“非常”相似。
【讨论】:
@Tudor... 我喜欢你的例子,但它缺少一个关键步骤,这是我最感兴趣的。0.877 似乎是一个很好的价值,可以说“非常相似”,但是我们如何以可计算的方式从 0.877 输出true
或 false
?
+1 @Tudor 对于这种很酷的方法。我想知道这作为语义相似性的度量有多可靠?我的意思是两个字符串可以有完全相同的字母,但含义完全不同(例如“army”-“mary”)。
@Hristo 这取决于您的应用程序设计。您需要设置一个介于 0.0 和 1.0 之间的阈值,这表明您认为哪些是“可接受的相似”,哪些不是。
@Tudor... 是的,我还没有数据知道什么是“可接受的相似度”:) 我希望能够计算出来:D
@G.Bach... 可以,但我不希望这个阈值是任意的。我希望找到一种可计算的方式来表达 true
或 false
而没有阈值。【参考方案2】:
由于 Levenshtein 距离永远不会大于较长字符串的长度,我当然会将分母从 (length1 + length2)
更改为 Math.max(length1, length2)
。这会将指标标准化为介于 0 和 1 之间。
现在,根据所提供的信息,不可能回答什么是“足够相似”以满足您的需求。我个人尽量避免使用 0.25 截止值的阶跃函数,更喜欢已知区间的连续值。也许将连续的“相似性”(或“距离”)值输入更高级别的算法而不是将这些值转换为二进制值会更好?
【讨论】:
我也不想做 0.25 截止。但是我不明白您所说的“将连续的“相似性”(或“距离”)值输入更高级别的算法而不是将这些值转换为二进制值是什么意思。你能澄清一下吗?【参考方案3】:这是我对此的看法 - 只是一个需要考虑的长篇故事,不一定是您问题的答案:
我过去做过类似的事情,我会尝试通过简单地重新排列句子同时保持相同的信息来确定是否有人抄袭。
1“我们吃晚饭时孩子们应该玩” 2“我们吃晚饭的时候,孩子们应该玩” 3“我们应该边玩边吃孩子”
所以 levenshtein 在这里用处不大,因为它是线性的,而且每一个都会有很大的不同。标准差将通过测试,学生将逃脱犯罪。
所以我将句子中的每个单词分解,并将句子重组为数组,然后相互比较以确定单词是否存在于每个数组中,以及它与上一个数组的关系。然后每个单词都会检查数组中的下一个单词,以确定是否有连续的单词,就像我在第 1 行和第 2 行上方的例句中一样。 因此,如果有连续的单词,我将组成每个数组共有的每个序列的字符串,然后尝试找出剩余单词的差异。剩余的单词越少,它们就越有可能只是为了让它看起来不那么抄袭。
“我们吃晚饭的时候,我认为孩子们应该玩”
然后“我认为”会根据关键字词典进行评估和考虑填充 - 这部分很难在这里描述。
这是一个复杂的项目,它所做的不仅仅是我所描述的内容,也不是我可以轻松共享的简单代码块,但上面的想法并不难复制。
祝你好运。我对其他 SO 成员对您的问题的看法很感兴趣。
【讨论】:
这太棒了,这样做是值得称赞的 :) 就我的问题而言,我正在处理堆栈跟踪,所以我不需要真正担心语义。【参考方案4】:堆栈跟踪采用可解析的格式。我只是使用解析库解析堆栈跟踪,然后您可以提取您想要比较的任何语义内容。
当字符串未按预期进行比较时,相似性算法会变慢且难以调试。
【讨论】:
+1 感谢您的回答。这绝对是一种方法。完成当前方法后,我将考虑一些解析库。然后我将能够确定哪种方法更准确,并产生更理想和预期的结果。 如果不复杂,也可以使用正则表达式手动滚动解析器。 我有一个正则表达式用于解析像at java.util.blah.blah.method(File.java:100)
这样的行,但这不是我唯一考虑的事情。以上是关于什么是确定 2 个字符串是不是“足够相似”的好指标的主要内容,如果未能解决你的问题,请参考以下文章