【问题标题】:Efficient way of calculating likeness scores of strings when sample size is large?样本量大时计算字符串相似度分数的有效方法?
【发布时间】:2010-12-09 05:54:52
【问题描述】:

假设您有一个包含 10,000 个电子邮件地址的列表,并且您想查找此列表中一些最近的“邻居”是什么 - 定义为与您列表中的其他电子邮件地址可疑地接近的电子邮件地址.

我知道如何计算两个字符串之间的Levenshtein distance(感谢this question),这将使我知道将一个字符串转换为另一个字符串需要多少操作。

假设我将“可疑地靠近另一个电子邮件地址”定义为 Levenshtein 分数小于 N 的两个字符串。

除了将每个可能的字符串与列表中的每个其他可能的字符串进行比较之外,是否有更有效的方法来查找分数低于此阈值的字符串对?也就是说,这类问题能比O(n^2)更快解决吗?

Levenshtein 对这个问题的算法选择是否糟糕?

【问题讨论】:

  • 看看我在下面的回复,这个问题是众所周知的,因为它基本上是您正在处理的拼写更正。
  • 请考虑从 Nick Johnson 的答案中删除“已接受答案”状态,因为它对近似搜索的 O(log n) 时间的核心主张不正确。

标签: algorithm string cluster-analysis complexity-theory edit-distance


【解决方案1】:

是的 - 您可以使用 BK-Tree 在 O(log n) 时间内找到字符串给定距离内的所有字符串。对于 1 的 levenshtein 距离,生成每个距离为 n 的字符串的替代解决方案可能会更快,但是对于更长的距离,工作量会迅速膨胀,失去控制。

【讨论】:

  • 查询预构建树是 O(log n) 但是在浏览电子邮件列表时构建和查询树呢?我认为这对聚类问题没有任何好处。
  • 好吧,他没有明确说明他的运行时要求是什么 - 但我假设他反复获得新的候选地址,并且必须检查它们,然后如果它们通过则将它们插入语料库。无论如何,从头开始构建树是 O(n log n),仍然比 O(n^2) 好很多。 :)
  • 看起来是一个很好的算法,在大多数情况下可能工作得很好,但是从链接的页面中并不清楚它可以在 O(log n 中找到给定 L 距离内的所有字符串) 时间(即与距离 d 无关)——对于初学者来说,如果整个字符串集足够近怎么办?仅仅输出它们意味着它必须至少是 O(n) 然后 :-P 虽然更深入,O(log n) 边界通常适用于在每个级别访问单个子节点的树搜索,但似乎搜索 BK 树可能需要检查许多孩子。 -1,直到你解释得更好!
  • 当您说“文章肯定涵盖了编辑距离和搜索树的比例之间的关系”时,您是指您在博客文章中链接到的 B&K 的原始论文吗?因为我无法访问该论文,您还链接到“字典中的快速近似字符串匹配”文章,并且在您链接的博客文章中没有声称,更不用说证明 O(log n) 行为的理由。你承认我对 BK-trees 的 O(n) 评估是正确的,但又责怪 me 不理解? (而且我不确定快速排序与任何事情有什么关系。)
  • 我现在已经阅读了这两篇论文。 BK73 论文证明了 O(log n) 查找时间仅适用于“Class 0”(精确匹配)查询。他们不尝试分析近似匹配查询,只给出一些实验结果。 B-YN98 论文在其附录中给出了分析,表明对于非精确查询,对于一些 0
【解决方案2】:

这个问题被称为 clustering 并且是更大的 deduplication 问题的一部分(您可以决定集群的哪个成员是“正确的”) ,也称为合并清除

我曾经读过几篇关于这个主题的研究论文(名字在下面),基本上,作者在排序的字符串列表上使用了大小有限的滑动窗口。他们只会比较(使用edit distance 算法)N*N 个字符串inside 窗口,从而降低计算复杂度。如果任何两个字符串看起来相似,则将它们组合成一个集群(通过将一条记录插入到单独的集群表中)。

第一次遍历列表之后是​​第二次遍历,其中字符串在排序之前反转。这样,具有不同磁头的字符串有另一个机会足够接近以作为同一窗口的一部分进行评估。在第二遍中,如果一个字符串看起来与窗口中的两个(或更多)字符串足够接近,并且这些字符串已经是它们自己的簇的一部分(由第一遍找到),那么这两个簇将被 合并 (通过更新集群表),当前字符串将被添加到新合并的集群中。这种聚类方法称为 union-find 算法。

然后他们改进了算法,将窗口替换为前 X 个非常独特的原型列表。每个新字符串都会与前 X 个原型中的每一个进行比较。如果字符串看起来与其中一个原型足够接近,那么它将被添加到 原型的集群。如果没有一个原型看起来足够相似,则该字符串将成为一个新原型,将最旧的原型推出顶部 X 列表。 (采用启发式逻辑来决定原型集群中的哪些字符串应该用作代表整个集群的新原型)。同样,如果字符串看起来与几个原型相似,则它们的所有集群都将被合并。

我曾经实现过这种算法,用于对名称/地址记录进行重复数据删除,列表大小约为 10-5000 万条记录,它的运行速度非常快(并且也能很好地发现重复项)。

总体而言,对于此类问题,最棘手的部分当然是找到相似度阈值的正确值。这个想法是捕获所有不产生太多误报的重复数据。具有不同特征的数据往往需要不同的阈值。编辑距离算法的选择也很重要,因为一些算法更适合 OCR 错误,而另一些更适合拼写错误,而另一些则更适合语音错误(例如通过电话获取姓名时)。

实施聚类算法后,测试它的一个好方法是获取唯一样本列表并人为地对每个样本进行变异以产生其变体,同时保留所有变体都具有来自同一个父母。然后将该列表打乱并馈送到算法。将原始聚类与重复数据删除算法生成的聚类进行比较将为您提供效率得分

参考书目:

Hernandez M. 1995,大型数据库的合并/清除问题。

Monge A. 1997,一种用于检测近似重复数据库记录的有效领域无关算法。

【讨论】:

    【解决方案3】:

    我认为你不能比 O(n^2) 做得更好,但你可以做一些较小的优化,这可能足以让你的应用程序可用:

    • 您可以先按 @ 后面的部分对所有电子邮件地址进行排序,然后仅比较相同的地址
    • 当两个地址之间的距离大于 n 时,您可以停止计算它

    编辑:实际上你可以做得比 O(n^2) 更好,看看下面尼克约翰逊的回答。

    【讨论】:

    • 你可以做得比 O(n^2) 好很多 - 请参阅我的回答。 :)
    【解决方案4】:

    在扭转问题的情况下,有可能做得更好。

    我在这里假设您的 10.000 个地址非常“固定”,否则您将不得不添加更新机制。

    这个想法是在 Python 中使用 Levenshtein 距离,但在“反向”模式下:

    class Addresses:
      def __init__(self,addresses):
        self.rep = dict()
        self.rep[0] = self.generate_base(addresses)
          # simple dictionary which associate an address to itself
    
        self.rep[1] = self.generate_level(1)
        self.rep[2] = self.generate_level(2)
        # Until N
    

    generate_level 方法从上一个集合中生成所有可能的变体,减去上一个级别已经存在的变体。它将“原点”保留为与键关联的值。

    然后,您只需在各种集合中查找您的单词:

      def getAddress(self, address):
        list = self.rep.keys()
        list.sort()
        for index in list:
          if address in self.rep[index]:
            return (index, self.rep[index][address]) # Tuple (distance, origin)
        return None
    

    这样做,您只需计算一次不同的集合(这需要一些时间……但您可以将其序列化并永久保存)。

    然后查找比 O(n^2) 更有效,尽管准确给出它有点困难,因为它取决于生成的集合的大小。

    供参考,请看:http://norvig.com/spell-correct.html

    【讨论】:

    • 这听起来是个不错的方法,除了我希望我的数据集每天都在变化;所以我很好奇是否每次都必须重新生成所有变化会降低我的效率。不过,感谢您的想法,我们始终感谢您提供 Norvig 链接!
    • 可以通过离线进程生成,然后序列化并按需加载。
    • 这不适用于大型列表(数千万条记录)。为此,请参阅我的回答。
    • 其实会的。 Norvig 给它提供了一个 6.2Mo 的单词文件来“训练”它的算法。但我认为我们讨论的是不同的问题 > 我假设“正确”答案的集合是事先知道的,您只需尝试将各种地址“分组”在一起。
    【解决方案5】:

    如果您真的要比较电子邮件地址,那么一种明显的方法是将 levenshtein 算法与域映射相结合。我可以想到有时我使用同一个域多次注册某项内容,但电子邮件地址的用户名部分有所不同。

    【讨论】:

      【解决方案6】:

      10,000 个电子邮件地址听起来不算多。对于更大空间中的相似性搜索,您可以使用shinglingmin-hashing。这种算法实现起来有点复杂,但在大空间上效率更高。

      【讨论】:

      • 我凭空挑选了 10,000 个,希望听起来“很大”,真正的问题空间要大几倍(但不是数百万)
      • @Yuval,您提到的两种方法都用于比较两个大文档,而这个问题是关于聚集大量小字符串。完全不同的问题。
      • @zvolkov - 我听过关于这些方法的讲座。它们是否适合小文件是有争议的。它们绝对是用来在非常大的集合中查找相似文档的。
      • @zvolkov:“Shingling”(我知道它是 n-grams/k-tuples)是查找可能具有低 L 距离的字符串组的好方法。 1) 将每个地址拆分为 n-gram。 2) 构建一个由 n-gram 索引的数组(因此,如果 n=4 并且允许的字符为 AZ,它将具有例如 26^4 个条目),其中每个条目包含包含该 n-gram 的所有地址的列表(地址由整数标识)。 3) 对于每个这样的列表,比如长度为 k,写出 k*(k-1)/2 个整数对——对于共享该 n-gram 的每对整数,写出 1。 4) 对这个对列表进行排序。 5) 在一系列重复中的​​同一对数≈相似度。
      • @j_random_hacker 我明白了。所以 shingling 可以 用于聚类,呵呵。但不会扩展。
      【解决方案7】:

      您可以在 O(kl) 中使用 Levenshtein,其中 k 是您的最大距离,l 是最大字符串。

      基本上,当您知道如何计算基本的 Levenshtein 时,很容易计算出从主对角线到比 k 更远的每个结果都必须大于 k。因此,如果您计算宽度为2k + 1 的主对角线就足够了。

      如果您有 10000 个电子邮件地址,则不需要更快的算法。计算机可以用O(N^2) 计算足够快。

      Levenshtein 非常适合这类问题。

      您还可以考虑在比较之前使用 soundex 转换电子邮件。你可能会得到更好的结果。

      【讨论】:

      • 你能定义“主对角线”是什么意思吗?
      • 您有一个用于计算 Levenshtein 距离的矩阵。它的尺寸是 m*n 所以主对角线会在那里, (i,i) where 0
      • 这样想。当给定一个用于计算 Levenshtein 距离的矩阵时。然后横向或上下移动,进行删除或插入,这意味着如果最佳对齐来自该路径,则它将1 添加到距离。因此,如果您与对角线的距离超过k 次,那么距离不会比k 好。
      • 您还可以加快与自动机的比较。识别具有最大距离 k 的单词的自动机构造将花费 O(nk) 时间,但检查单词是否匹配将花费 O(n)。因此,如果您有 N 封电子邮件,则需要 O(Nnk) 才能完成所有自动机。然后检查每封电子邮件需要 O(NNn)。
      • img101.imageshack.us/img101/7960/screenshotj.png 看那张照片。使用阈值 3,您无需计算红色部分。
      【解决方案8】:

      假设你有 3 个字符串:

      1 - “ABC” 2 - “bcd” 3 - “cde”

      1 和 2 之间的 L 距离为 2(减去“a”,加上“d”)。 2 和 3 之间的 L 距离为 2(减去 'b',加上 'e')。

      您的问题是我们是否可以通过使用上面的 2 个比较来推断 1 和 3 之间的 L 距离。答案是否定的。

      1 和 3 之间的 L 距离为 3(替换每个字符),由于前 2 次计算的分数,无法推断出这一点。分数不显示是否执行了删除、插入或替换操作。

      所以,我会说 Levenshtein 对于大型列表来说是一个糟糕的选择。

      【讨论】:

      • 是的,但是根据度量的性质,您至少可以假设距离为 2+2 或更小。如果您有一个字符串列表,其中有趣的距离远小于字符串的长度,这可能是有价值的信息。
      猜你喜欢
      • 2017-02-28
      • 2018-03-08
      • 2013-02-24
      • 2011-04-04
      • 2015-01-30
      • 2011-10-03
      • 2019-12-27
      • 2011-12-10
      • 2018-06-11
      相关资源
      最近更新 更多