【问题标题】:Fuzzy matching deduplication in less than exponential time?在不到指数的时间内进行模糊匹配重复数据删除?
【发布时间】:2011-08-25 19:25:38
【问题描述】:

我有一个大型数据库(可能有数百万条记录),其中包含相对较短的文本字符串(按街道地址、名称等顺序)。

我正在寻找一种去除不精确重复的策略,模糊匹配似乎是首选方法。我的问题:许多文章和 SO 问题都涉及将单个字符串与数据库中的所有记录进行匹配。我希望立即对整个数据库进行重复数据删除。

前者将是一个线性时间问题(将一个值与一百万个其他值进行比较,每次都计算一些相似性度量)。后者是指数时间问题(将每条记录的值与其他每条记录的值进行比较;对于一百万条记录,与前一个选项的 1,000,000 次计算相比,这大约是 5 x 10^11 计算)。

我想知道除了我提到的“蛮力”方法之外,是否还有其他方法。我正在考虑可能生成一个字符串来比较每个记录的值,然后将具有大致相等相似性度量的字符串分组,然后通过这些组运行蛮力方法。我不会达到线性时间,但它可能会有所帮助。此外,如果我正确地考虑这一点,这可能会错过字符串 A 和 B 之间潜在的模糊匹配,因为它们与字符串 C(生成的检查字符串)的相似性非常不同,尽管彼此非常相似。

有什么想法吗?

附注我意识到我可能使用了错误的时间复杂度术语——这是一个我基本掌握的概念,但还不够好,所以我可以当场将算法归入正确的类别。如果我用错了术语,我欢迎更正,但希望我至少能理解我的意思。

编辑

一些评论者问,鉴于记录之间的模糊匹配,我的策略是选择删除哪些记录(即给定“foo”、“boo”和“coo”,它们将被标记为重复并删除)。我应该注意,我不是在这里寻找自动删除。这个想法是在一个 60 多万条记录数据库中标记潜在的重复项,以供人工审查和评估。如果有一些误报是可以的,只要它是一个大致可预测/一致的数量。我只需要了解重复项的普遍性。但是,如果模糊匹配传递需要一个月的时间来运行,那么这甚至不是一个选项。

【问题讨论】:

  • 第二种方法会比第一种方法提供更高的准确性...但是您不需要那么多计算。假设您在开始时为每条记录提供了一个唯一标识符,并在匹配某些内容时更改了这些标识符,那么记录的数量会随着时间的推移而减少。
  • 但是每条记录可以有多个重复项,我关心的是单次通过的运行时间。因此,我认为更改行 ID 不会节省任何时间。显然,防止未来重复更容易,因为它只是将新插入的记录与所有其他记录进行比较 - O(1) 时间。但第一遍是 O(n^2) 时间,在 6300 万条记录和严重受限的硬件条件下,我们正在延长数月的计算时间来完成单遍。所以我需要一个更快的选择...

标签: algorithm duplicates time-complexity fuzzy record-linkage


【解决方案1】:

看看http://en.wikipedia.org/wiki/Locality-sensitive_hashing。一种非常简单的方法是将每个地址(或其他)分成一组重叠的 n-gram。此 STACKOVERFLOW 变为集合 {STACKO, TACKO, ACKOV, CKOVE... , RFLOW}。然后使用大型哈希表或排序合并来查找冲突的 n-gram 并使用模糊匹配器检查冲突。因此,STACKOVERFLOW 和 SXACKOVRVLOX 将发生冲突,因为两者都与冲突的 n-gram ACKOV 相关联。

更复杂的下一个级别是选择一个随机散列函数 - 例如具有任意键的 HMAC,在您找到的 n-gram 中,只保留散列值最小的那个。然后您必须跟踪更少的 n-gram,但只有在两种情况下的最小散列值都是 ACKOV 时才会看到匹配。在 n-gram 的长度和错误命中的概率之间显然需要权衡取舍。事实上,人们似乎做的是通过将同一记录中多个哈希函数的结果连接起来,使 n 变得非常小并获得更高的精度,因此您需要同时在多个不同的哈希函数中获得匹配 -我认为这样的概率会更好。尝试谷歌搜索“重复检测 minhash”

【讨论】:

  • 是的!我认为这可能是我正在寻找的。我在 LSH 上做了一些阅读,在实施它方面我有点迷茫。这个想法似乎是以这样一种方式计算值的哈希值,即相似的值很有可能发生碰撞哈希值。然后,您可以对具有冲突哈希的值运行更精确的算法。基本上,降低维度以降低计算强度。我不明白要使用什么散列函数——它们不是为了尽量减少冲突而设计的吗?我们是在谈论像 SHA 或 MD5 这样的哈希函数吗?
  • 良好的散列函数(例如 SHA)被定义为看起来像随机函数,与一些非常糟糕的散列函数相比,它确实产生更少的冲突。但是证明 LSH 合理的计算假设完全随机散列函数作为构建块,所以这很好。错误的散列函数通过将相似的输入散列到相同的输出来产生冲突,但 LSH 根本不依赖于此。它只依赖于哈希函数的一致性,因此如果 h(ACKOV) = 13 在一个地方,h(ACKOV) = 13 在另一个地方。如果你找到了一篇很好的 LSH 实用文章,请使用文章中的哈希函数。
  • 我认为这是有道理的 :) ... 你有没有机会知道这样的文章,或者至少是一个看的方向?到目前为止,我的尝试基本上没有结果。我找到了几个技术性很强的资源,但考虑到我目前的背景,它们比我能处理的要复杂。
  • 我在实践中从未这样做过——只是在它引起我注意时试图跟上文献的步伐。然而,快速的网络搜索在nlp.stanford.edu/IR-book/html/htmledition/… 找到了一个文档,看起来并不太难读。与往常一样,请记住,您可以剪切不想用作诱饵的文档——也就是说,对您而言无用的文档仍可能为更多的网络搜索提供有用的关键字。
【解决方案2】:

我认为您可能错误地计算了所有组合的复杂性。如果将一个字符串与所有其他字符串进行比较是线性的,这意味着由于长度较小,每次比较都是 O(1)。将每个字符串与其他字符串进行比较的过程不是指数的,而是二次的,这并不全是坏事。简单来说,您是在比较 nC2 或 n(n-1)/2 对字符串,所以它只是 O(n^2)

我想不出一种方法可以按顺序对它们进行排序,因为你不能编写一个客观的比较器,但即使你这样做,排序也需要 O(nlogn) 进行合并排序,因为你有这么多记录,可能会更喜欢不使用额外的内存,你会使用快速排序,在最坏的情况下需要 O(n^2),在最坏的情况下没有任何改进。

【讨论】:

  • 我想我的“检查字符串”是我对客观比较器的希望。你觉得这不可能吗?我不确定我自己在问题中提到的疑问 - 这种比较器方法是否真的会错过类似的一对?另外,感谢复杂的时间,我不确定我是否使用了正确的术语。但是即使复杂性没有改善,实际运行时间不会改善吗?因为蛮力必须在每个循环中进行密集的字符串相似度计算,但排序只是将相似度评级与目标比较器进行比较,计算速度要快得多。
  • 好吧,我想不出一个比较器,考虑 3 个字符串 abcd、efcd 和 abef。任何两对都恰好有 2 个不同的字符,那么它们的排列顺序是什么?它们的所有 6 种排列都是有意义的。是的,如果你想出了一个非常聪明的比较器,那么在平均情况下你可以做 O(nlgn),我想不出你可能需要在小数据集上尝试一些比较器并观察,一个比较器是编辑距离, en.wikipedia.org/wiki/Edit_distance
  • 好吧,鉴于“abcd”、“efcd”和“abef”,我正在考虑将它们与某种“中性”、任意检查字符串进行比较,该字符串充当第三方的比较。例如,将每个值与“abcdef”进行比较,使用对距离作为比较函数 (catalysoft.com/articles/StrikeAMatch.html)。 “abcd”有三对,而“efcd”和“abef”有两对。因此,您可以使用两对获取所有值并在它们之间进行精确比较。也许冲洗并使用不同的检查字符串重复以提高准确性。
  • 是的,先生,这绝对是一种做事方式,只不过是多次运行您在问题中提到的线性时间算法
  • 那么您对如何最有效地制定这个“检查字符串”有什么建议吗?我正在考虑在字母表中包含每个字母组合,并使用配对距离比较功能检查相似性。我认为这种方法是最保守的(它不会错过任何可能的重复,但会出现很多误报)。但是那个检查字符串的长度会超过一千个字符——我认为效率很低。
【解决方案3】:

您可以使用Levenshtein transducer,它“接受[s] 一个查询词并返回[s] 字典中与它相差n 个拼写错误的所有词”。 Here's a demo.

【讨论】:

    【解决方案4】:

    所有记录的成对比较是 O(N^2) 不是指数的。基本上有两种方法可以减少这种复杂性。

    第一个是阻塞,您只比较已经具有共同点且易于计算的记录,例如前三个字母或常见的 n-gram。这与本地敏感散列基本相同。 dedupe python library 实现了许多阻塞技术,documentation gives a good overview of the general approach

    在最坏的情况下,与阻塞的成对比较仍然是 O(N^2)。在最好的情况下,它是 O(N)。在实践中,最好或最坏的情况都没有真正得到满足。通常,阻塞可将要比较的对数减少 99.9% 以上。

    记录链接有一些有趣的替代范例,它们不基于成对比较。这些具有更好的更坏情况复杂性保证。查看 Beka Steorts 和 Michael Wick 的作品。

    【讨论】:

      【解决方案5】:

      我认为这是一次性清理。我认为问题将不必进行如此多的比较,而必须决定哪些比较值得进行。您提到了姓名和地址,请参阅this link 了解您可能遇到的一些比较问题。

      确实,您必须进行近 5000 亿次蛮力比较才能将一百万条记录与自己进行比较,但这是假设您从未跳过任何先前宣布匹配的记录(即,从未在 j-在下面的伪代码中循环)。

      我的 pokey E-machines T6532 2.2GHz 每秒可对 100 字节的文本文件记录进行 140 万次搜索和读取,因此 5000 亿次比较大约需要 4 天时间。而不是花 4 天时间研究和编写一些奇特的解决方案(只是发现我还需要另外 x 天来实际运行),并假设我的比较例程无法计算和保存我要比较的键,我' d 只是让它蛮力所有这些比较,而我找到其他事情要做:

      for i = 1 to LASTREC-1
        seektorec(i)
        getrec(i) into a
        for j = i+1 to LASTREC
          getrec(j) into b
          if similarrecs(a, b) then [gotahit(); break]
      

      即使给定的运行只找到易于定义的匹配项,也希望它将剩余的不匹配记录减少到一个更合理的更小的集合,这样进一步的蛮力运行就不会那么耗时。

      但似乎similarrecs() 无法独立计算和保存正在比较的 a + b 部分,在这种情况下更有效的方法是:

      for i = 1 to LASTREC
        getrec(i) in a
        write fuzzykey(a) into scratchfile
      sort scratchfile
      for i = 1 to LASTREC-1
        if scratchfile(i) = scratchfile(i+1) then gothit()
      

      如果允许您调用自己的自定义代码来计算每条记录的模糊键(),大多数数据库都可以在一个命令行中执行上述操作。

      无论如何,根据上面的链接,困难的部分将是弄清楚是什么导致两条记录重复。

      【讨论】:

      • 您使用什么语言/环境来实现 1.4m 搜索和读取/秒?您发布的第一个代码示例是我目前的方法。在我的第二台笔记本电脑上运行(一个公认的动力不足的设置),我估计需要一个月的运行时间来计算 5000 亿次比较。另外,我不理解您的第二个代码示例-fuzzykey() 函数调用将什么写入暂存文件?你为什么对暂存文件进行排序?对按字母顺序排序的相邻字符串(您的暂存文件)的比较将仅通过转置字符(即“foobar”与“ofobar”)错过重复项
      • 此外,实际重复计数不太可能(根据我对我正在使用的数据的了解)在数据库中重复数据删除的所有记录中占足够大的百分比粗略的第一次通过会显着减少记录的数量,从而使未来的蛮力通过明显更快。
      • 我正在使用 Delphi,但速度来自使用文本文件 + 避免使用数据库驱动程序。 Fuzzykey() 使用什么是 $64 的问题。这是您确定两条记录重复的标准,也是您最终花费大部分时间的问题。根据链接文档中的合并/清除策略/策略,fuzzykey() 可能是名称的 SortedSoundex、地址的 CASS 清理输出、电话号码的纯数字过滤器等。排序将键组合在一起。比如“foobar”和“ofobar”的SortedSoundex都一样,PO BOX 1和BOX 1的CASS一样,等等
      • 如果你的名单确实是姓名和地址,我不会碰这份工作,除非我被允许对地址进行 CASS 认证。我会 (1) 将 RECORD ID、LASTNAME、ADDRESS、ZIP 导出到一个固定字段的文本文件,以及 GROUP 和 MEMBER 字段的空间,供 DUPDTECT 在semaphorecorp.com/mpdd/dupdtect.html 使用,(2) CASS 所有地址,(3 ) 让 DUPDTECT 根据相同的名称/地址/ZIP 为所有受骗者分配组和成员编号,然后 (4) 导入 RECORD ID/GROUP/MEMBERS 以供查看或删除。如果电话号码可用,我会提供这些以帮助提高欺骗检测质量。
      • CASS 不适用于此作业。 Soundex 和其他基于发音的模糊匹配算法已失效(即“Joe's Plumbing Inc.”和“Joe's Plumbing Incorporated”重复的公司名称字段,以及任何单词乱序的情况 - 发音会错过这个,不会是吗?)。选择模糊匹配算法现在不是困难的部分——可能是配对距离或 Levenshtein。我只需要一种快速的方法来进行比较。此外,“我不会碰这份工作”不是一个选项。
      【解决方案6】:

      等价关系是一种特别好的匹配;它们满足三个属性:

      • 自反性:对于任意值 A,A ~ A
      • 对称性:如果 A ~ B,那么必然是 B ~ A
      • 及物性:如果 A ~ B 和 B ~ C,那么必然是 A ~ C

      让这些很好的是它们允许您将数据划分为不相交的集合,以便任何给定集合中的每对元素都通过 ~ 关联。所以,你可以做的是应用 union-find 算法首先对所有数据进行分区,然后从分区中的每个集合中挑选出单个 representative 元素;这完全消除了数据的重复数据(其中“重复”表示“与〜相关”)。此外,这个解决方案是规范的,因为无论您碰巧从每个分区中选择哪个代表,您都会获得相同数量的最终值,并且每个最终值都是成对不重复的。

      不幸的是,模糊匹配不是等价关系,因为它可能不是传递的(尽管它可能是自反和对称的)。这样做的结果是没有规范的数据分区方法。您可能会发现,无论以何种方式对数据进行分区,一组中的某些值与另一组中的值相等,或者单个组中的某些值不相等。

      那么,在这些情况下,您究竟想要什么行为?

      【讨论】:

      • 我不确定我是否理解您的回答,如果这不是您要找的内容,请见谅!所以你的意思是:当我根据数据之间的某种关系的结果划分我的数据时,不能保证,因为模糊匹配无法传递性,所以不能保证创建的对将包含在事实相似(满足这种关系);或者,所有满足的对都会出现,但也会出现一些误报。在这种情况下,我更喜欢准确性,所以如果有选择,我会选择后者。但我可能误解了你的答案:)
      • 这是一个最简单的例子来说明这个困境。假设 ~ 是“编辑距离为 1”。我们有单词“goo”、“foo”和“food”。应该如何对它们进行重复数据删除?结果 {"goo", "food"} 中没有重复项(其余的 "foo" 至少是其中一个的重复项);结果 {"foo"} 中没有重复项(其余的“goo”和“good”都是其中至少一个的重复项);但这两个结果明显不同。你更倾向哪个?你能详细说说你为什么喜欢那个吗?
      • 给定单词“goo”、“foo”和“food”,以及 1 个编辑距离的“重复”阈值,在我看来,“goo”和“foo”应该匹配( aka 被标记为可能的重复项)并且“foo”和“food”也应该被标记,但是“goo”和“food”不应该被标记为重复项,因为它们相距 2 个编辑距离。但这似乎相对简单,所以我想我可能不理解您的评论(再一次!)。感谢您的耐心解释,我真的很感激!
      • 我同意您声称的匹配项。在重复数据删除过程结束时,哪些会根据这些匹配项被删除?
      • 我应该更清楚地说明这部分(我将编辑问题以包括这一点):我不打算自动删除任何记录。我只想将高于某个相似性阈值(即 Levenshtein 编辑距离
      猜你喜欢
      • 2019-02-12
      • 1970-01-01
      • 1970-01-01
      • 2021-05-16
      • 1970-01-01
      • 2014-10-19
      • 1970-01-01
      • 1970-01-01
      • 2019-02-14
      相关资源
      最近更新 更多