Процентный рейтинг совпадений с использованием сопоставления расстояний Левенштейна

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

Поскольку строка поиска может быть длиннее или короче, чем отдельные строки словаря, какая логика могла бы быть подходящей для выражения расстояния в процентах, которая качественно отражала бы, насколько "в процентах" каждый результат близок к строке запроса, со 100 %, указывающий на точное совпадение.

Рассматривал следующие варианты:

Q = query string
M = matched string
PM = Percentage Match
Option 1. PMi = (1 - Lev_distance(Q, Mi)/Strlen(Q)) * 100
Option 2. PMi = (1 - Lev_distance(Q, Mi)/max(Strlen(Q), strlen(Mi))) * 100

Вариант 1 может иметь отрицательные проценты в случае, если расстояние больше, чем длина строки поиска, когда строка соответствия длинная. Например, запрос "ABC" соответствует запросу "ABC Corp." приведет к отрицательному проценту совпадения.

Вариант 2, по-видимому, не дает согласованного процентного соотношения по набору Mi, поскольку каждый расчет, возможно, будет использовать другой знаменатель, и, следовательно, результирующие процентные значения не будут нормализованы.

Единственный другой способ, о котором я могу думать, - это отказаться от сравнения lev_distance с любой длиной строки, а вместо этого представить сравнительные расстояния между верхними совпадениями "N" как обратный процентильный ранг (100-процентильный ранг).

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


person user1368587    schedule 01.05.2012    source источник
comment
Как насчет вашего 1-го варианта, но когда результат отрицательный, просто верните 0? PS: Я также разместил здесь проблему math. stackexchange.com/questions/1776860/   -  person Wakan Tanka    schedule 08.05.2016
comment
Я не понял, в чем проблема с Option2, поскольку я реализовал ту же логику, которую вы описали, и, похоже, работает правильно. Не могли бы вы объяснить это лучше?   -  person Roberto14    schedule 20.04.2017


Ответы (8)


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

int levDis = Lev_distance(Q, Mi)
int bigger = max(strlen(Q), strlen(Mi))
double pct = (bigger - levDis) / bigger

Он должен возвращать 100%, если обе строки абсолютно одинаковы, и 0%, если они совершенно разные.

(извините, если мой английский не так хорош)

person Celio Camargo Junior    schedule 11.02.2014
comment
Это неверно, потому что дает разные результаты для ("ABC Corp", "ABC") и ("ABC Corp", "ABC Corporati") - person Wakan Tanka; 08.05.2016

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

percent = 0.75; // at least 75% of string must match
maxOperationsFirst = s1.length() - s1.length() * percent;
maxOperationsSecond = s2.length() - s2.length() * percent;
maxOperations = round(min(maxOperationsFirst, maxOperationsSecond));

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

Когда у вас есть максимальное количество операций, вы можете сравнить его с результатом levenshtein и определить, подходит ли строка. Таким образом, вы можете использовать любые расширенные методы Левенштейна, например, Расстояние Дамерау – Левенштейна, которые учитывают орфографические ошибки, например test -> tset, только как 1 операция, что весьма полезно при проверке ввода пользователя, когда такие орфографические ошибки встречаются очень часто.

Надеюсь, это поможет вам понять, как решить эту проблему.

person Marko Gresak    schedule 14.08.2013

(1 - (levNum / Math.max(s.length,t.length) ) ) *100

должно быть правильно

person cocoa coder    schedule 09.01.2013
comment
В исходном вопросе это решение уже есть как Вариант 2. Он ищет альтернативное решение проблемы. - person Jim Clouse; 31.08.2013

По сути, это вариант 2, упомянутый в моем вопросе. Однако позвольте мне продемонстрировать проблему с таким подходом.

Q = "ABC Corp" (len = 8)
M1 = "ABC"
M2 = "ABC Corporati"
M3 = "ABC Corp"

Я выбрал M1 и M2 так, чтобы их расстояния Lev были одинаковыми (по 5). Используя вариант 2, процент совпадений будет

M1 = (1 - 5/8)*100  = 37.5%
M2 = (1 - 5/13)*100 = 61.5%
M3 = 100%

Как вы можете видеть, если я представлю совпадения в таком порядке, существует огромная разница в рангах между M1 и M2, даже несмотря на то, что у них одинаковое расстояние левов. Вы видите проблему?

person NG Algo    schedule 11.01.2013
comment
Спустя какое-то время я думаю, что это правильный подход. Предположим, у вас есть очень короткие строки, у которых LevDisstance равно 5. Предположим, у вас есть очень длинные строки, у которых LevDist также 5. Тогда будет правильным сказать, что самые короткие строки менее похожи, чем более длинные. - person Wakan Tanka; 12.05.2016
comment
Tbh, я не вижу здесь проблем, потому что, как сказал @Wakan Tanka, одинаковое расстояние до более длинной строки означает, что между ними совпало больше символов. Следовательно, проблем нет, и вариант 2 - допустимый вариант. - person Roberto14; 20.04.2017

Что насчет этого:

100 - ( ((2*Lev_distance(Q, Mi)) / (Q.length + Mi.length)) * 100 )

Это дает одинаковое расстояние на (Q, M1) и (Q,M2)

person Wakan Tanka    schedule 08.05.2016

Максимальное количество дистанций Левенштейна [l1, l2].max. Я думаю, это правда. Но делить по нему не стоит.

gem install levenshtein diff-lcs

Diff::LCS.lcs "abc", "qwer"
=> []
Levenshtein.distance("abc", "qwer").to_f / [3, 4].max
=> 1.0

Diff::LCS.lcs "abc", "cdef"
=> ["c"]
Levenshtein.distance("abc", "cdef").to_f / [3, 4].max
=> 1.0

Diff::LCS.lcs "1234", "34567890"
=> ["3", "4"]
Levenshtein.distance("1234", "34567890").to_f / [4, 8].max
=> 1.0

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

Я могу порекомендовать просто проанализировать разницу между каждой последовательностью и LCS.

def get_similarity(sequence_1, sequence_2)
  lcs_length = Diff::LCS::Internals.lcs(sequence_1, sequence_2).compact.length
  lcs_length.to_f * 2 / (sequence_1.length + sequence_2.length)
end
person puchu    schedule 20.06.2018

Max = Lev_distance(Q,''); //max operations to transform query string to empty string
PM = (Max - Lev_distance(Q, Mi)) / Max * 100%;

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

person Артем Паламарчу&    schedule 29.05.2021

Я думаю, что более простой подход может быть:

from nltk import edit_distance

str1 = 'abc'
str2 = 'abd'
edit_dist  = edit_distance(str1,str2)
len_total = len(str1)+len(str2)
pct_edit_dist = ((len_total-edit_dist)/len_total)*100
print(pct_edit_dist)

pct_edit_dist будет 100 при полном соответствии и 0 при отсутствии совпадения.

person Hisham Sajid    schedule 26.06.2021