38

根据python-Levenshtein.ratio消息来源:

https://github.com/miohtama/python-Levenshtein/blob/master/Levenshtein.c#L722

它被计算为(lensum - ldist) / lensum。这适用于

# pip install python-Levenshtein
import Levenshtein
Levenshtein.distance('ab', 'a') # returns 1
Levenshtein.ratio('ab', 'a')    # returns 0.666666

但是,它似乎与

Levenshtein.distance('ab', 'ac')  # returns 1
Levenshtein.ratio('ab', 'ac')     # returns 0.5

我觉得我一定错过了一些非常简单的东西..但为什么不0.75呢?

4

4 回答 4

23

Levenshtein 距离'ab'和如下'ac'

图片

所以对齐是:

  a c
  a b 

对齐长度 = 2
错配数 = 1

Levenshtein Distance1因为只需要一个替换即可转移acab(或反向)

距离比 = (Levenshtein 距离)/(对齐长度) = 0.5

编辑

你在写

(lensum - ldist) / lensum= (1 - ldist/lensum)= 1 - 0.5 = 0.5。

但这是匹配(不是距离)
REFFRENCE,你可能会注意到它的写法

Matching %

p = (1 - l/m) × 100

l是单词 在哪里:levenshtein distancemlength of the longest of the two

注意:有些作者使用了两者中最长的,我使用了对齐长度)

(1 - 3/7) × 100 = 57.14...  

  (Word 1    Word 2    RATIO   Mis-Match   Match%
   AB         AB         0       0        (1 - 0/2 )*100  = 100%  
   CD         AB         1       2        (1 - 2/2 )*100  = 0%   
   AB         AC        .5       1        (1 - 1/2 )*100  = 50%      

为什么有些作者除以对齐长度,而另一些则除以两者之一的最大长度?..,因为 Levenshtein 不考虑间隙。距离 = 编辑次数(插入 + 删除 + 替换),而作为标准全局对齐的 Needleman–Wunsch 算法考虑间隙。这是 Needleman–Wunsch 和 Levenshtein 之间的(差距)差异,很多论文使用两个序列之间的最大距离但这是我自己的理解,我不确定 100%

这是 IEEE TRANSACTIONS ON PAITERN ANALYSIS : Computation of Normalized Edit Distance and Applications在本文中Normalized Edit Distance如下:

给定有限字母表上的两个字符串 X 和 Y,X 和 Y 之间的归一化编辑距离 d( X , Y ) 定义为 W( P ) / L ( P )w 的最小值,这里 P 是之间的编辑路径X 和 Y ,W ( P ) 是 P 的基本编辑操作的权重之和,L(P) 是这些操作的数量(P 的长度)。

于 2013-01-10T15:00:04.730 回答
21

通过更仔细地查看 C 代码,我发现这种明显的矛盾是由于对ratio“替换”编辑操作的处理与其他操作不同(即成本为 2),而distance对它们都一样对待成本 1。

这可以从对函数内部levenshtein_common函数 的调用中看出ratio_py


https://github.com/miohtama/python-Levenshtein/blob/master/Levenshtein.c#L727

static PyObject*
ratio_py(PyObject *self, PyObject *args)
{
  size_t lensum;
  long int ldist;

  if ((ldist = levenshtein_common(args, "ratio", 1, &lensum)) < 0) //Call
    return NULL;

  if (lensum == 0)
    return PyFloat_FromDouble(1.0);

  return PyFloat_FromDouble((double)(lensum - ldist)/(lensum));
}

并按distance_py功能:

https://github.com/miohtama/python-Levenshtein/blob/master/Levenshtein.c#L715

static PyObject*
distance_py(PyObject *self, PyObject *args)
{
  size_t lensum;
  long int ldist;

  if ((ldist = levenshtein_common(args, "distance", 0, &lensum)) < 0)
    return NULL;

  return PyInt_FromLong((long)ldist);
}

这最终导致不同的成本参数被发送到另一个内部函数lev_edit_distance,它具有以下文档片段:

@xcost: If nonzero, the replace operation has weight 2, otherwise all
        edit operations have equal weights of 1.

lev_edit_distance() 的代码:

/**
 * lev_edit_distance:
 * @len1: The length of @string1.
 * @string1: A sequence of bytes of length @len1, may contain NUL characters.
 * @len2: The length of @string2.
 * @string2: A sequence of bytes of length @len2, may contain NUL characters.
 * @xcost: If nonzero, the replace operation has weight 2, otherwise all
 *         edit operations have equal weights of 1.
 *
 * Computes Levenshtein edit distance of two strings.
 *
 * Returns: The edit distance.
 **/
_LEV_STATIC_PY size_t
lev_edit_distance(size_t len1, const lev_byte *string1,
                  size_t len2, const lev_byte *string2,
                  int xcost)
{
  size_t i;

[回答]

所以在我的例子中,

ratio('ab', 'ac')意味着在字符串 (4) 的总长度上进行替换操作(成本为 2),因此2/4 = 0.5.

这解释了“如何”,我想唯一剩下的方面就是“为什么”,但目前我对这种理解感到满意。

于 2013-01-12T18:46:02.560 回答
9

(lensum - ldist) / lensum

ldist 不是距离,是成本的总和

在此处输入图像描述

不匹配的数组的每个数字来自上方、左侧或对角线

如果数字来自左边他是一个插入,它来自上面它是一个删除,它来自对角线它是一个替换

插入和删除的成本为 1,替换的成本为 2。替换成本为 2,因为它是删除和插入

ab ac 成本为 2,因为它是替代品

>>> import Levenshtein as lev
>>> lev.distance("ab","ac")
1
>>> lev.ratio("ab","ac")
0.5
>>> (4.0-1.0)/4.0    #Erro, the distance is 1 but the cost is 2 to be a replacement
0.75
>>> lev.ratio("ab","a")
0.6666666666666666
>>> lev.distance("ab","a")
1
>>> (3.0-1.0)/3.0    #Coincidence, the distance equal to the cost of insertion that is 1
0.6666666666666666
>>> x="ab"
>>> y="ac"
>>> lev.editops(x,y)
[('replace', 1, 1)]
>>> ldist = sum([2 for item in lev.editops(x,y) if item[0] == 'replace'])+ sum([1 for item in lev.editops(x,y) if item[0] != 'replace'])
>>> ldist
2
>>> ln=len(x)+len(y)
>>> ln
4
>>> (4.0-2.0)/4.0
0.5

在此处输入图像描述

更多信息:python-Levenshtein 比率计算

另一个例子:

在此处输入图像描述

成本为 9(4 替换 => 4*2=8 和 1 删除 1*1=1, 8+1=9)

str1=len("google") #6
str2=len("look-at") #7
str1 + str2 #13

distance = 5 (根据矩阵的向量 (7, 6) = 5)

比率为 (13-9)/13 = 0.3076923076923077

>>> c="look-at"
>>> d="google"
>>> lev.editops(c,d)
[('replace', 0, 0), ('delete', 3, 3), ('replace', 4, 3), ('replace', 5, 4), ('replace', 6, 5)]
>>> lev.ratio(c,d)
0.3076923076923077
>>> lev.distance(c,d)
5
于 2015-09-10T14:25:55.883 回答
4

虽然没有绝对的标准,但归一化的列文斯泰因距离是最常见的定义ldist / max(len(a), len(b))。对于这两个示例,这将产生 0.5。

max是有道理的,因为它是 Levenshtein 距离的最低上限:a要从bwhere获得len(a) > len(b),您始终可以将 的第一个len(b)元素替换为bfrom 的相应元素a,然后插入缺失的部分a[len(b):],总共进行len(a)编辑操作。

这个论点以明显的方式扩展到 的情况len(a) <= len(b)。要将归一化距离转换为相似性度量,请将其从 1 中减去:1 - ldist / max(len(a), len(b))

于 2013-01-10T15:30:21.183 回答