Эффективный способ вычисления оценок сходства строк при большом размере выборки?

Предположим, у вас есть список из 10 000 адресов электронной почты, и вы хотите узнать, какие из ближайших «соседей» в этом списке определены как адреса электронной почты, которые подозрительно близки к другим адресам электронной почты в вашем списке.

Я знаю, как рассчитать расстояние Левенштейна между двумя строками (благодаря этот вопрос), что даст мне оценку того, как требуется много операций для преобразования одной строки в другую.

Допустим, я определяю «подозрительно близко к другому адресу электронной почты» как две строки, у которых оценка Левенштейна меньше N.

Есть ли более эффективный способ найти пары строк, оценка которых ниже этого порога, помимо сравнения каждой возможной строки с каждой другой возможной строкой в ​​списке? Другими словами, можно ли решить эту проблему быстрее, чем O(n^2)?

Является ли оценка Левенштейна плохим выбором алгоритмов для этой задачи?


person matt b    schedule 22.10.2009    source источник
comment
Взгляните на мой ответ ниже, проблема хорошо известна, так как в основном это исправление орфографии, о котором вы заботитесь.   -  person Matthieu M.    schedule 23.10.2009
comment
Пожалуйста, подумайте об удалении статуса принятого ответа из ответа Ника Джонсона, поскольку его центральное утверждение о времени O (log n) для приблизительного поиска неверно.   -  person j_random_hacker    schedule 23.10.2012


Ответы (8)


Ага - вы можете найти все строки на заданном расстоянии от строки за время O (log n), используя BK-Tree. Альтернативные решения, включающие создание каждой строки с расстоянием n, могут быть быстрее для расстояния Левенштейна, равного 1, но объем работы быстро выходит из-под контроля на больших расстояниях.

person Nick Johnson    schedule 23.10.2009
comment
Запрос дерева перед построением - это O (log n), но как насчет создания И запроса дерева при просмотре списка писем? Я не думаю, что это хорошо для проблемы кластеризации. - person zvolkov; 24.10.2009
comment
Что ж, он не указывает явно, каковы его требования к среде выполнения, но я предполагаю, что он неоднократно получает новые адреса кандидатов и должен их проверять, а затем вставлять их в корпус, если они проходят. В любом случае построение дерева с нуля занимает O (n log n), что значительно лучше, чем O (n ^ 2). :) - person Nick Johnson; 24.10.2009
comment
Это выглядит полезным, но я не уверен, понимаю ли я, как применить это к моей ситуации. Я ищу, чтобы найти все пары адресов электронной почты с расстоянием ‹N (проблема не в том, чтобы найти все строки на расстоянии N от определенного адреса электронной почты). Алгоритм построения дерева основан на моем словаре (10 000 адресов электронной почты), а затем запрашивает дерево для каждого адреса? - person matt b; 26.10.2009
comment
Начните с пустого дерева. Для каждого адреса электронной почты сначала выполните поиск в дереве нового электронного письма с заданным расстоянием и выведите все значения в пределах расстояния n. Затем вставьте адрес в дерево. Это O (n log n) при условии, что количество пар также примерно O (n log n). - person Nick Johnson; 28.10.2009
comment
Вот отличное объяснение использования bk-деревьев для приблизительного сопоставления строк - blog.notdot.net/2007/4/Damn-Cool-Algorithms-Part-1-BK-Trees - person Pranav; 15.11.2009
comment
Спасибо, Пранав, но это мой собственный пост в блоге, и я дал ссылку на него в своем ответе. - person Nick Johnson; 16.11.2009
comment
Похоже на хороший алгоритм, который может работать очень хорошо большую часть времени, но из связанной страницы совсем не ясно, что он может найти все строки в пределах заданного L-расстояния за время O (log n) (т.е. независимо от расстояния г) - для начала, а если весь набор струн достаточно близок? Простой вывод их всех означает, что он должен быть не менее O (n), а затем :-P Однако более подробно границы O (log n) обычно применяются к поиску по дереву, где на каждом уровне посещается один дочерний элемент, но похоже, что поиск по деревьям BK может потребоваться обследование многих детей. -1 пока не объясни лучше! - person j_random_hacker; 04.10.2012
comment
@j_random_hacker -1, потому что вы не понимаете, что алгоритм немного резок. Да, время выполнения наихудшего случая - O (n), когда все узлы совпадают - точно так же, как время наихудшего случая для быстрой сортировки, например, составляет O (n ^ 2). И в статье определенно рассматривается взаимосвязь между расстоянием редактирования и долей дерева, в котором производится поиск - следующий пост об автоматах Левенштейна предоставляет эмпирические примеры. Сообщение в блоге также содержит ссылки на оригинальные статьи, на которых оно основано. - person Nick Johnson; 04.10.2012
comment
Когда вы говорите, что в статье определенно рассматривается взаимосвязь между расстоянием редактирования и пропорцией дерева, в котором проводился поиск, вы имеете в виду исходную статью B&K, на которую вы ссылаетесь из своего сообщения в блоге? Потому что эта статья недоступна для меня, как и статья Fast Approximate String Matching in a Dictionary, на которую вы также ссылаетесь, и нет никаких претензий, не говоря уже об оправдании, поведения O (log n) в вашем связанном сообщении в блоге. Вы признаете, что моя оценка O (n) для BK-деревьев верна, но затем обвиняете меня в непонимании? (И я не уверен, при чем тут быстрая сортировка.) - person j_random_hacker; 04.10.2012
comment
Если вы имеете в виду, что алгоритм равен O (log n) в среднем случае, скажите так. Это более слабое, но все же очень полезное свойство (хотя я до сих пор не понимаю, как оно может быть независимым от d, если только среднее значение не берется по некоторому распределению строк, которое IME обычно далеко от распределения, встречающегося на практике). В идеале вы могли бы набросать доказательство утверждения O (log n) здесь или в связанной публикации, но, по крайней мере, сказать в сообщении, где это утверждение O (log n) доказано. - person j_random_hacker; 04.10.2012
comment
Да, в худшем случае, когда вы возвращаете каждый элемент, будет O (n). Я надеюсь, что это было очевидно для всех, поскольку вы не можете обработать n элементов меньше, чем за O (n). Но когда совпадает только часть элементов в дереве, как это обычно бывает, доля исследуемого дерева равна O (log n), что довольно очевидно, если исследуется меньше, чем все ветви любого дерева. Мой пост не является исчерпывающим теоретическим упражнением в компьютерной науке, это его доступное описание. Если вам нужны доказательства, я предлагаю найти способ прочитать оригинальные статьи. - person Nick Johnson; 04.10.2012
comment
Кроме того, с каких это пор вы считаете что-то упущением в обосновании правильного ответа для голосования? На мой взгляд, отрицательный голос равносилен тому, чтобы сказать, что этот ответ является чистым отрицательным. Вы верите, что это так? - person Nick Johnson; 04.10.2012
comment
Во-первых, сама запись в блоге великолепна. Но да, я считаю ответ, который привлекает внимание, но неверно, без его подкрепления, чистым отрицанием. (Когда вы говорите, что алгоритм XYZ равен O (f (x)) без дальнейшего уточнения, универсально предполагается, что вы говорите о наихудшем случае, детерминированном временном ограничении.) Пожалуйста, ослабьте утверждение O (log n) до чего-то вроде обычно быстро на практике, набросайте доказательство или сделайте ссылку на что-то, что делает. Тогда это будет отличный ответ :) - person j_random_hacker; 04.10.2012
comment
Было бы даже достаточно очистить и прояснить описание сложности (O (log n) в среднем случае, независимо от расстояния) и сказать, что это доказано в статье B&K, если на самом деле это так. - person j_random_hacker; 04.10.2012
comment
Я прочитал обе статьи. В документе BK73 доказывается время поиска O (log n) только для запросов класса 0 (точное соответствие). Они не пытаются анализировать запросы с приблизительным соответствием, а дают лишь некоторые экспериментальные результаты. В приложении к статье B-YN98 дается анализ, показывающий, что для неточных запросов BK-деревья имеют значение O (n ^ a) для некоторого 0 ‹a‹ 1, которое является функцией ожидаемого расстояния между любой парой объектов. Короче говоря, нет никаких доказательств, теоретических или эмпирических, для вашего утверждения, что BK-деревья находят ближайших соседей на произвольном расстоянии за время O (log n), поэтому, пожалуйста, удалите его. - person j_random_hacker; 04.10.2012

Вы можете сделать это с Левенштейном в O(kl), где k - ваше максимальное расстояние, а l - максимальная строка.

В принципе, когда вы знаете, как вычислить базовый Левенштейн, тогда легко понять, что каждый результат, который находится дальше чем k от главной диагонали, должен быть больше k. Так что, если вы рассчитываете главную диагональ, достаточно будет 2k + 1.

Если у вас 10000 адресов электронной почты, вам не понадобится более быстрый алгоритм. Компьютер может вычислить с O(N^2) достаточно быстро.

Левенштейн неплохо подходит для такого рода задач.

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

person Egon    schedule 22.10.2009
comment
Можете ли вы определить, что вы подразумеваете под главной диагональю? - person matt b; 23.10.2009
comment
У вас есть матрица для расчета расстояния Левенштейна. его размеры - это что-то m*n, поэтому главная диагональ будет там, (i, i), где 0 ‹= i‹ min (m, n) - person Egon; 23.10.2009
comment
Подумайте об этом так. При задании матрицы для расчета расстояния Левенштейна. Затем, двигаясь боком или вверх-вниз, выполняет удаление или вставку, что означает, что если наилучшее выравнивание происходит от этого пути, то оно добавляет 1 к расстоянию. Так что если вы отойдете от диагонали более чем k раз, то расстояние будет не лучше k. - person Egon; 23.10.2009
comment
Вы также можете ускорить сравнение с автоматом. Построение автомата для распознавания слова с максимальным расстоянием k займет O (nk) времени, но проверка совпадения слов займет O (n). Так что, если у вас есть N писем, на выполнение всех автоматов уйдет O (N n k). А затем для проверки каждой электронной почты потребуется O (N N n). - person Egon; 23.10.2009
comment
Спасибо, это полезно. Если я вас правильно понял, это означает, что для порога = 3 мне не следует беспокоиться о вычислении каких-либо оценок для строк, которые имеют длину +/- 3 от проверяемой строки - это должно быть хорошей оптимизацией. - person matt b; 23.10.2009
comment
img101.imageshack.us/img101/7960/screenshotj.png См. это изображение . С порогом 3 вам не нужно рассчитывать красные части. - person Egon; 23.10.2009

Эта проблема известна как кластеризация и является частью более крупной проблемы дедупликации (где вы можете решить, какой член кластера является правильным), также известной как < сильный> слияние-чистка.

Однажды я прочитал несколько исследовательских работ именно по этой теме (имена приведены ниже), и в основном авторы использовали скользящее окно ограниченного размера над отсортированным списком строк. Они будут только сравнивать (используя алгоритм изменить расстояние) N * N строк внутри < / strong> окно, тем самым уменьшая вычислительную сложность. Если какие-либо две строки выглядели одинаково, они были объединены в кластер (путем вставки записи в отдельную таблицу кластера).

За первым проходом по списку последовал второй проход, где строки были перевернуты перед сортировкой. Таким образом, строки с разными головками получили еще один шанс приблизиться достаточно близко, чтобы их можно было оценить как часть одного и того же окна. На этом втором проходе, если строка выглядела достаточно близко к двум (или более) строкам в окне, и эти строки уже были частями своих собственных кластеров (найденных на первом проходе), тогда два кластера будут объединены (путем обновления таблицы кластеров), и текущая строка будет добавлена ​​во вновь объединенный кластер. Такой подход к кластеризации известен как алгоритм поиска объединения.

Затем они улучшили алгоритм, заменив окно списком X существенно уникальных прототипов. Каждая новая строка будет сравниваться с каждым из X лучших прототипов. Если строка будет выглядеть достаточно близко к одному из прототипов, она будет добавлена ​​в кластер прототипа. Если бы ни один из прототипов не выглядел достаточно похожим, строка стала бы новым прототипом, выталкивая самый старый прототип из верхнего списка X. (Применялась эвристическая логика, чтобы решить, какая из строк в кластере прототипа должна использоваться в качестве нового прототипа, представляющего весь кластер). Опять же, если бы строка выглядела похожей на несколько прототипов, все их кластеры были бы объединены.

Однажды я реализовал этот алгоритм для дедупликации записей имен / адресов с размерами списков порядка 10-50 миллионов записей, и он работал чертовски быстро (и хорошо находил дубликаты).

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

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

Библиография:

Эрнандес М. 1995, Проблема слияния / очистки больших баз данных.

Монж А. 1997, Эффективный домен-независимый алгоритм для обнаружения приблизительно повторяющихся записей в базе данных.

person Community    schedule 23.10.2009

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

  • Вы можете сначала отсортировать все адреса электронной почты по части th после @ и сравнивать только те адреса, где это то же самое.
  • Вы можете прекратить вычисление расстояния между двумя адресами, когда оно станет больше n

РЕДАКТИРОВАТЬ: На самом деле вы можете сделать лучше, чем O (n ^ 2), просто посмотрите на ответ Ника Джонсона ниже.

person Gabb0    schedule 22.10.2009
comment
Вы можете сделать намного лучше, чем O (n ^ 2) - см. Мой ответ. :) - person Nick Johnson; 24.10.2009

Допустим, у вас есть 3 строки:

1 - «abc» 2 - «bcd» 3 - «cde»

Расстояние L между 1 и 2 равно 2 (вычесть «a», добавить «d»). Расстояние L между 2 и 3 равно 2 (вычтите «b», добавьте «e»).

Ваш вопрос заключается в том, можем ли мы вывести расстояние L между 1 и 3, используя 2 сравнения выше. Ответ - нет.

L Расстояние между 1 и 3 равно 3 (замените каждый символ), это невозможно сделать из результатов первых двух вычислений. Оценки не показывают, выполнялись ли операции удаления, вставки или замены.

Итак, я бы сказал, что Левенштейн - плохой выбор для большого списка.

person Neil Kimber    schedule 22.10.2009
comment
Да, но по характеру метрики вы можете хотя бы предположить, что расстояние составляет 2 + 2 или меньше. Это может быть ценной информацией, если у вас есть список строк, где интересующие расстояния намного меньше, чем длины строк. - person jprete; 23.10.2009

10 000 адресов электронной почты - это немного. Для поиска сходства в большом пространстве вы можете использовать черепицу и min-hashing. Этот алгоритм немного сложнее реализовать, но он намного эффективнее на большом пространстве.

person Yuval F    schedule 22.10.2009
comment
Я как бы выбрал 10 000 из воздуха, надеясь, что это покажется большим, реальное проблемное пространство в несколько раз больше (но не в миллионах) - person matt b; 23.10.2009
comment
@Yuval, оба упомянутых вами подхода используются для сравнения двух больших документов, в то время как этот вопрос касается кластеризации большого количества небольших строк. Совсем разные проблемы. - person zvolkov; 24.10.2009
comment
@zvolkov - Я слышал лекцию об этих подходах. Подходят ли они к небольшим документам - спорный. Они определенно предназначены для поиска похожих документов среди очень большого набора. - person Yuval F; 27.10.2009
comment
@zvolkov: Шинглинг (я знаю его как n-граммы / k-кортежи) - отличный способ найти группы строк, которые, вероятно, будут иметь низкое L-расстояние. 1) Разбейте каждый адрес на n-граммы. 2) Создайте массив, проиндексированный n-граммой (так что он будет иметь, например, 26 ^ 4 записей, если n = 4 и разрешенные символы - AZ), где каждая запись содержит список всех адресов, содержащих эту n-грамму (адреса идентифицируются целыми числами. ). 3) Для каждого такого списка, скажем длины k, выпишите k * (k-1) / 2 пар целых чисел - 1 для каждой пары целых чисел, разделяющих эту n-грамм. 4) Отсортируйте этот список пар. 5) # одинаковых пар в серии дублей - сходство. - person j_random_hacker; 04.10.2012
comment
@j_random_hacker Понятно. Так что черепицу можно использовать для кластеризации, да. Но не масштабируется. - person zvolkov; 04.10.2012
comment
@zvolkov: Ты уверен в этом? :-P Это то, сколько программ биоинформатики находят совпадающие пары последовательностей ДНК. Он даже масштабируется до наборов данных, которые не помещаются в памяти, потому что каждый шаг (даже построение массива) можно выполнить с помощью сортировки, и существуют довольно эффективные внешние алгоритмы сортировки. Уловка состоит в том, чтобы сделать n как можно большим, чтобы список в каждом элементе массива был маленьким. (Но слишком большое n означает, что некоторые сходства будут упущены.) - person j_random_hacker; 04.10.2012

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

person spig    schedule 22.10.2009

Можно добиться большего при условии, что проблема решена.

Я полагаю, что ваши 10.000 адресов довольно «исправлены», иначе вам придется добавить механизм обновления.

Идея состоит в том, чтобы использовать расстояние Левенштейна, но в «обратном» режиме в Python:

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

person Matthieu M.    schedule 23.10.2009
comment
Это звучит как довольно разумный подход, за исключением того, что я ожидаю, что мой набор данных будет меняться каждый день; так что мне любопытно, стоит ли мне каждый раз регенерировать все вариации некоторой эффективности. Спасибо за идею, и ссылка на Norvig всегда приветствуется! - person matt b; 23.10.2009
comment
Генерация может быть произведена автономным процессом, затем сериализована и загружена по запросу. - person Matthieu M.; 23.10.2009
comment
Это не сработает для огромных списков (десятки миллионов записей). По этому поводу см. Мой ответ. - person zvolkov; 24.10.2009
comment
Собственно, так и будет. Норвиг скармливал ему файл слов объемом 6,2 млн, чтобы «обучить» его алгоритм. Но я думаю, что мы говорим о разных проблемах ›Я предполагаю, что набор« правильных »ответов известен заранее, и вы просто пытаетесь« сгруппировать »различные адреса вместе. - person Matthieu M.; 24.10.2009