Расстояние Левенштейна:как лучше обращаться со словами, меняющими позиции?
-
06-07-2019 - |
Вопрос
Я добился некоторого успеха, сравнивая строки с помощью PHP левенштейн функция.
Однако для двух строк, содержащих подстроки, поменявшие местами позиции, алгоритм считает их совершенно новыми подстроками.
Например:
levenshtein("The quick brown fox", "brown quick The fox"); // 10 differences
рассматриваются как имеющие меньше общего чем:
levenshtein("The quick brown fox", "The quiet swine flu"); // 9 differences
Я бы предпочел алгоритм, который видел бы, что первые два были более похожи.
Как я мог бы создать функцию сравнения, которая может идентифицировать подстроки с измененной позицией как отличные от правок?
Один из возможных подходов, о котором я подумал, состоит в том, чтобы расположить все слова в строке в алфавитном порядке перед сравнением.Это полностью исключает из сравнения первоначальный порядок слов.Недостатком этого, однако, является то, что изменение только первой буквы слова может привести к гораздо большим сбоям, чем изменение одной буквы.
Чего я пытаюсь добиться, так это сравнить два факта о людях, которые являются свободными текстовыми строками, и решить, насколько вероятно, что эти факты указывают на один и тот же факт.Фактами могут быть, например, школа, в которой кто-то учился, имя его работодателя или издателя.В двух записях одна и та же школа может быть написана по-разному, слова расположены в разном порядке, дополнительные слова и т.д., Поэтому сопоставление должно быть несколько нечетким, если мы хотим сделать верное предположение, что они относятся к одной и той же школе.Пока что он очень хорошо работает с орфографическими ошибками (вдобавок ко всему я использую алгоритм phoenetic, похожий на metaphone), но очень плохо, если вы меняете порядок слов, которые кажутся распространенными в школе:"колледж ХХХ" против "колледжа ХХХ".
Решение
N-граммы
Используйте N-граммы , которые поддерживают несколько транспонирование символов по всему тексту .
Общая идея состоит в том, что вы разбиваете две рассматриваемые строки на все возможные 2-3-символьные подстроки (n-граммы) и рассматриваете количество общих n-граммов между двумя строками как их метрику сходства. Затем это можно нормализовать, разделив общее число на общее количество n-грамм в более длинной строке. Это тривиально для вычисления, но довольно мощный.
Для примеров предложений:
A. The quick brown fox
B. brown quick The fox
C. The quiet swine flu
A и B делят 18 2 грамма
А и С делят только 8 2 грамма
из 20 всего возможного.
Более подробно это обсуждалось в Gravano et al. бумага .
tf-idf и косинусное сходство
Не так тривиальная альтернатива, но основанная на теории информации, будет использовать термин термин частота & # 8211; частота обратных документов (tf-idf) , чтобы взвесить токены, построить векторы предложений и затем использовать косинусное сходство как показатель сходства.
Алгоритм:
<Ол>Модификации Левенштейна и Метафон
По поводу других ответов. Damerau & # 8211; Левенштейн поддерживает только транспонирование двух двух соседние символы. Метафон был разработан для соответствия словам, которые звучат одинаково и не для сопоставления сходства.
Другие советы
Это легко. Просто используйте Дамерау-Левенштейн в словах вместо слов. р>
Взрыв на пространствах, сортировка массива, взрыв, затем Левенштейн.
Вы также можете попробовать это. (просто дополнительное предложение)
$one = metaphone("The quick brown fox"); // 0KKBRNFKS
$two = metaphone("brown quick The fox"); // BRNKK0FKS
$three = metaphone("The quiet swine flu"); // 0KTSWNFL
similar_text($one, $two, $percent1); // 66.666666666667
similar_text($one, $three, $percent2); // 47.058823529412
similar_text($two, $three, $percent3); // 23.529411764706
Это покажет, что 1-й и 2-й больше похожи, чем один и три, а два и три.
Я внедрял Левенштейна в проверку орфографии. Р>
Вы просите считать транспозиции как 1 правку.
Это легко, если вы хотите считать только транспозиции одного слова. Однако для транспонирования слов 2 или более, добавление к алгоритму является наихудшим сценарием ! (Max (wordorder1.length (), wordorder2.length ()))
. Добавление нелинейного подалгоритма в уже квадратичный алгоритм не очень хорошая идея.
Вот как это будет работать.
if (wordorder1[n] == wordorder2[n-1])
{
min(workarray[x-1, y] + 1, workarray[x, y-1] + 1, workarray[x-2, y-2]);
}
else
{
min(workarray[x-1, y] + 1, workarray[x, y-1] + 1);
}
ТОЛЬКО для трогательных транспозиций. Если вы хотите, чтобы все транспонирования, вам пришлось бы для каждой позиции работать в обратном направлении от этой точки сравнения
1[n] == 2[n-2].... 1[n] == 2[0]....
Итак, вы понимаете, почему они не включают это в стандартный метод.
Возьмите этот ответ и внесите следующие изменения:
void match(trie t, char* w, string s, int budget){
if (budget < 0) return;
if (*w=='\0') print s;
foreach (char c, subtrie t1 in t){
/* try matching or replacing c */
match(t1, w+1, s+c, (*w==c ? budget : budget-1));
/* try deleting c */
match(t1, w, s, budget-1);
}
/* try inserting *w */
match(t, w+1, s + *w, budget-1);
/* TRY SWAPPING FIRST TWO CHARACTERS */
if (w[1]){
swap(w[0], w[1]);
match(t, w, s, budget-1);
swap(w[0], w[1]);
}
}
Это для поиска по словарю в дереве, но для сопоставления с одним словом, это та же идея. Вы делаете ветвление и привязку, и в любой момент вы можете вносить любые изменения, которые вам нравятся, если вы платите за это.
Удалите повторяющиеся слова между двумя строками, а затем используйте Левенштейна.
Я полагаю, что это яркий пример использования поисковой системы векторного пространства . р>
в этом методе каждый документ по существу становится вектором с таким количеством измерений, сколько есть разных слов во всем корпусе; затем аналогичные документы занимают соседние области в этом векторном пространстве. Одно приятное свойство этой модели состоит в том, что запросы также являются просто документами: чтобы ответить на запрос, вы просто вычисляете их положение в векторном пространстве, а ваши результаты - самые близкие документы, которые вы можете найти. я уверен, что есть готовые решения для PHP.
чтобы нечеткие результаты из векторного пространства, вы могли бы подумать о том, чтобы использовать основную / похожую технику обработки естественного языка и использовать levenshtein для создания вторичных запросов для похожих слов, которые встречаются в вашем общем словаре.
Если первая строка - A, а вторая - B:
- Разделите A и B на слова
- Для каждого слова в A найдите наиболее подходящее слово в B (используя levenshtein).
- Удалите это слово из B и поместите его в B * с тем же индексом, что и соответствующее слово в A.
- Теперь сравните A и B*
Пример:
A: The quick brown fox
B: Quick blue fox the
B*: the Quick blue fox
Вы могли бы улучшить шаг 2, выполнив его в несколько проходов, сначала найдя только точные совпадения, затем найдя близкие совпадения для слов в A, у которых еще нет компаньона в B *, затем менее близкие совпадения и т.д.