Нечеткое согласование дедупликации в менее чем экспоненциальном времени?

У меня есть большая база данных (возможно, в миллионах записей) с относительно короткими строками текста (по порядку адреса, имен и т.д.).

Я ищу стратегию удаления неточных копий, и выбор нечеткого соответствия - это метод выбора. Моя проблема: многие статьи и вопросы SO касаются сопоставления одной строки со всеми записями в базе данных. Я ищу, чтобы дедуплицировать всю базу данных сразу.

Первая была бы линейной проблемой времени (сравнивая значение с миллионом других значений, каждый раз вычисляя некоторое сходство). Последнее представляет собой проблему экспоненциального времени (сравнивайте все значения записи со всеми другими значениями записи, для миллиона записей - это около 5 x 10 ^ 11 вычислений и 1000 000 вычислений для первого варианта).

Мне интересно, есть ли другой подход, чем упомянутый мною метод "грубой силы". Я думал о возможности создания строки для сравнения каждого значения записи против, а затем группировать строки, которые имели примерно равные меры сходства, а затем запускать метод грубой силы через эти группы. Я бы не добился линейного времени, но это могло бы помочь. Кроме того, если я правильно это рассмотрю, это может пропустить потенциальное нечеткое совпадение между строками A и B, потому что их сходство с строкой C (сгенерированная контрольная строка) очень отличается, несмотря на то, что они очень похожи друг на друга.

Любые идеи?

P.S. Я понимаю, что, возможно, использовал неправильные термины для временной сложности - это концепция, в которой я имею базовое понимание, но недостаточно хорошо, поэтому я мог отбросить алгоритм в соответствующую категорию на месте. Если я неправильно использовал термины, я приветствую исправления, но, надеюсь, у меня есть точка зрения по крайней мере.

Edit

Некоторые комментаторы задавали вопрос с учетом нечетких совпадений между записями, что моя стратегия заключалась в том, чтобы выбрать, какие из них нужно удалить (т.е. данные "foo", "boo" и "coo", которые будут отмечены дублированием и удалены). Я должен отметить, что я не ищу автоматического удаления здесь. Идея состоит в том, чтобы пометить потенциальные дубликаты в 60-миллионной базе данных записей для целей оценки и оценки человека. Это нормально, если есть ложные срабатывания, если это примерно предсказуемое/согласованное количество. Мне просто нужно понять, насколько распространены дубликаты. Но если пропуски с нечетким сопоставлением проходят месяц, это даже не вариант.

Ответы

Ответ 1

Посмотрите http://en.wikipedia.org/wiki/Locality-sensitive_hashing. Одним очень простым подходом было бы разделить каждый адрес (или что-то еще) на набор перекрывающихся n-граммов. Этот STACKOVERFLOW становится набором {STACKO, TACKO, ACKOV, CKOVE..., RFLOW}. Затем используйте большую хэш-таблицу или сортировку-слияние, чтобы найти встречные n-граммы и проверить коллизии с нечетким совпадением. Таким образом, STACKOVERFLOW и SXACKOVRVLOX будут сталкиваться, потому что оба связаны с сталкивающимся n-грамм ACKOV.

Следующим уровнем сложности является выбор случайной хеш-функции - например. HMAC с произвольным ключом и n-граммами, которые вы находите, сохраняйте только тот, у которого наименьшее хешированное значение. Затем вы должны отслеживать меньше n-граммов, но увидите только совпадение, если наименьшее хешированное значение в обоих случаях - ACKOV. Очевидно, здесь существует компромисс между длиной n-грамма и вероятностью ложных ударов. Фактически, то, что люди, похоже, делают, это сделать n довольно маленьким и получить более высокую точность, объединив результаты более чем одной хеш-функции в одной записи, поэтому вам нужно получить совпадение в нескольких разных хеш-функциях одновременно - Я полагаю, что эта вероятность лучше всего работает. Попробуйте "поиск в Google для двойного обнаружения minhash"

Ответ 2

Я думаю, что вы, возможно, неправильно вычислили сложность для всех комбинаций. Если сравнение одной строки со всеми другими строками является линейной, это означает, что из-за малой длины каждое сравнение - O (1). Процесс сравнения каждой строки с любой другой строкой не является экспоненциальным, а квадратичным, что не так уж плохо. Проще говоря, вы сравниваете nC2 или n (n-1)/2 пары строк, поэтому его просто O (n ^ 2)

Я не мог думать о том, как вы можете сортировать их по порядку, поскольку вы не можете написать объективный компаратор, но даже если вы это сделаете, сортировка займет O (nlogn) для сортировки слияния, и поскольку у вас столько записей и, вероятно, будет предпочитайте использовать лишнюю память, вы бы использовали быструю сортировку, которая в худшем случае принимает O (n ^ 2), не улучшает время худшего случая в грубой силе.

Ответ 3

Вы можете использовать преобразователь Levenshtein, который "принимает [s] термин запроса и возвращает [s] все термины в словаре, которые в пределах n ошибок правописания от него". Здесь демо.

Ответ 4

Эквивалентные отношения - особенно приятные виды соответствия; они удовлетворяют трем свойствам:

  • рефлексивность: для любого значения A, A ~ A
  • : если A ~ B, то обязательно B ~ A
  • транзитивность: если A ~ B и B ~ C, то обязательно A ~ C

Что приятно, так это то, что они позволяют разбивать ваши данные на непересекающиеся множества, так что каждая пара элементов в любом заданном наборе связана ~. Итак, что вы можете сделать, это применить алгоритм объединения-поиска, чтобы сначала разбить все ваши данные, а затем выбрать один представительный элемент из каждого набора в разделе; это полностью дедуплицирует данные (где "дубликат" означает "связанный с ~" ). Более того, это решение является каноническим в том смысле, что независимо от того, какие представители вы выбираете из каждого раздела, вы получаете одинаковое количество конечных значений, и каждое из конечных значений попарно не дублируется.

К сожалению, нечеткое согласование не является отношением эквивалентности, поскольку оно предположительно не является транзитивным (хотя оно, вероятно, рефлексивно и симметрично). Результатом этого является отсутствие канонического способа разделения данных; вы можете обнаружить, что любой способ, которым вы пытаетесь разделить данные, некоторые значения в одном наборе эквивалентны значениям из другого набора или что некоторые значения из одного набора не эквивалентны.

Итак, какое поведение вы хотите точно в этих ситуациях?

Ответ 5

Я предполагаю, что это разовая очистка. Я думаю, что проблема не будет в том, чтобы делать так много сравнений, им придется решать, какие сравнения стоит делать. Вы указываете имена и адреса, поэтому см. эту ссылку для некоторых проблем сравнения, которые у вас будут.

Истинно, вам нужно сделать почти 500 миллиардов сравнений грубой силы для сравнения миллиона записей с самим собой, но при условии, что вы никогда не пропустите какие-либо ранее объявленные записи (т.е. никогда не делаете "разрыв" из j- петля в псевдокоде ниже).

My pokey E-machines T6532 2.2gHz позволяет делать 1,4 м и считывает в секунду 100-байтные записи текстовых файлов, поэтому 500 миллиардов сравнений занимают около 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]

Даже если заданный пробег обнаруживает только легкоразрешаемые совпадения, мы надеемся, что он уменьшит оставшиеся несогласованные записи до более разумного меньшего набора, для которого дальнейшие беговые действия не так трудоемки.

Но кажется маловероятным, что подобныеrecs() не могут самостоятельно вычислять и сохранять сравниваемые части 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()

Большинство баз данных могут сделать это в одной командной строке, если вам разрешено вызывать свой собственный код для вычисления каждой записи fuzzykey().

В любом случае, сложная часть будет выяснять, что делает две записи дубликатами по ссылке выше.

Ответ 6

Пары сравнения всех записей O (N ^ 2) не экспоненциальны. Там в основном два способа пойти, чтобы сократить эту сложность.

Первое - это блокирование, в котором вы сравниваете только записи, которые уже имеют что-то общее, что легко вычислить, например, первые три буквы или общий n-грамм. Это в основном та же идея, что и локально чувствительная хеширование. библиотека дедуплирования python реализует ряд методов блокировки, а документация дает хороший обзор общего подхода.

В худшем случае попарные сравнения с блокировкой по-прежнему O (N ^ 2). В лучшем случае это O (N). Ни один из лучших или худших случаев на самом деле не встречается на практике. Как правило, блокировка уменьшает количество пар для сравнения более чем на 99,9%.

Есть несколько интересных альтернативных парадигм для записи связей, которые не основаны на попарных сравнениях. Они имеют более худшие гарантии сложности случая. См. Работу Бека Стеортс и Майкла Вика.