详细剖解Levenshtein距离算法(附python实现)

在实习期间,看到同事在做文字识别的相关项目,用Levenshtein距离作为评价模型好坏的标准之一。由于是行外人,当时对这个算法并没有任何了解,只听他介绍是用来判断两个字符是否相似的一种指标吧,直到后来自己也做NLP相关的项目,才好好钻研了一番,特此记录。

一、Levenshtein距离

一般的,我们在NLP中评价模型的时候,经常会使用计算得到的Levenshtein距离作为模型的评分(正确率或错误率)。

Levenshtein距离又称作编辑距离(Edit Distance),是指两个字符之间,有一个转变成另一个所需的最少编辑操作次数。被允许的操作有以下几种:
a. Replace替换,将一个字符替换成另一个字符
b. Insert插入,插入一个字符
c. Delete删除,删除一个字符

一般来说,编辑的距离越小,两个字符的相似度越大。不难分析出,两个字符串的编辑距离肯定不超过它们的最大长度(可以通过先把短串的每一位都修改成长串对应位置的字符,然后插入长串中的剩下字符)。

二、算法实现

通过编辑距离来判断两个字符串是否相等,属于动态规划算法的一种形式。

Levenshtein距离的数学定义:
在这里插入图片描述
其中当 a i = b j a_{i}=b_{j} ai=bj时, 1 ( a i ≠ b j ) 1_{(a_{i}\neq b_{j})} 1(ai=bj)为0,否则为1。 l e v a , b ( i , j ) lev_{a,b}(i,j) leva,b(i,j)就是a的前i个字符与b的前j个字符的编辑距离。

a、b的相似度 S i m a , b Sim_{a,b} Sima,b S i m a , b = 1 − ( lev ⁡ a , b ( ∣ a ∣ , ∣ b ∣ ) / m a x ( ∣ a ∣ , ∣ b ∣ ) ) Sim_{a,b}=1-(\operatorname{lev}_{a,b}(\left | a \right |,\left | b \right |)/max(\left | a \right |,\left | b \right |)) Sima,b=1(leva,b(a,b)/max(a,b))

1、简单举例

通过一个例子整理思路,如计算kitten和sitting之间的编辑距离:

kitten → sitten (替换 “k” -> “s”)
sitten → sittin (替换 “e” -> “i”)
sittin → sitting (插入"g")

上面的变化过程所需要的步数就是最小的步数,所以他们之间的编辑距离就是"3"

2、极端假设

给定字符串A和B,我们首先确定他们距离的上下界限:

  • 距离最小是两个字符串之间的长度的差值
  • 距离最大是两个字符串中较长字符串的长度
  • 当且仅当字符串相同时长度为0
  • 当字符串的长度相同时,距离的最大长度是 Hamming distance
  • 两个字符串之间的距离小于等于与另外一个字符串距离之和

根据以上规则,遍历每个字符利用递归可实现

//实现方法
private static int distance(String a, int len_a, String b, int len_b) {
    //递归回归点
    if (len_a == 0)
        return len_b;
    if (len_b == 0)
        return len_a;
    
    int cos;
    if (a.charAt(len_a-1) == b.charAt(len_b-1))
        cos = 0;
    else
        cos = 1;

    int re1 = distance(a, len_a - 1, b, len_b) + 1;
    int re2 = distance(a, len_a, b, len_b - 1) + 1;
    int re3 = distance(a, len_a - 1, b, len_b - 1) + cos;
    //返回在a中删除一个字符、在b中删除一个字符、ab中均删除一个字符获得结果中取最小值
    return re1 < re2 ? (re1 < re3 ? re1 : re3) : (re2 < re3 ? re2 : re3);
}
//测试
public static void main(String[] args) {
    String a = "kitten";
    String b = "sitting";
    int re = distnace(a, a.length(), b, b.length());
    System.out.println(re);
    //输出:3
}
3、动态规划

参考文章:

https://blog.csdn.net/jave_f/article/details/79859805
https://blog.csdn.net/asty9000/article/details/81384650
https://blog.csdn.net/CSDN___LYY/article/details/85009190

def normal_leven(str1, str2):
  len_str1 = len(str1) + 1
  len_str2 = len(str2) + 1
  # 创建矩阵
  matrix = [0 for n in range(len_str1 * len_str2)]
  #矩阵的第一行
  for i in range(len_str1):
    matrix[i] = i
  # 矩阵的第一列
  for j in range(0, len(matrix), len_str1):
    if j % len_str1 == 0:
      matrix[j] = j // len_str1
  # 根据状态转移方程逐步得到编辑距离
  for i in range(1, len_str1):
    for j in range(1, len_str2):
      if str1[i-1] == str2[j-1]:
        cost = 0
      else:
        cost = 1
      matrix[j*len_str1+i] = min(matrix[(j-1)*len_str1+i]+1,
                    matrix[j*len_str1+(i-1)]+1,
                    matrix[(j-1)*len_str1+(i-1)] + cost)
 
  return matrix[-1]  # 返回矩阵的最后一个值,也就是编辑距离

三、库函数调用

应用的时候我们直接安装python-Levenshtein包,然后调用即可。

#pip install python-Levenshtein

import Levenshtein


In [56]: Levenshtein.distance('kitten', 'sitting')
Out[56]: 3

  • 3
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
AOP(Aspect-Oriented Programming,面向方面编程)是对OOP(Object-Oriented Programming,面向对象编程)的补充和完善。它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,即方面。AOP的核心思想是将与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,以减少系统的重复代码,降低模块间的耦合度,并有利于未来的可操作性和可维护性。 AOP将软件系统分为两个部分:核心关注点和横切关注点。核心关注点是业务处理的主要流程,而横切关注点是与核心关注点关系不大的部分,如权限认证、日志、事务处理等。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。通过AOP,可以实现将应用程序中的商业逻辑与对其提供支持的通用服务进行分离的目标。 实现AOP的技术主要分为两大类:一是采用动态代理技术,利用截取消息的方式对消息进行装饰,以取代原有对象行为的执行;二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。 总结来说,AOP的核心思想是将系统中的通用功能和业务逻辑分离,使系统更加模块化、可维护和可扩展。通过AOP,我们可以将一些横切关注点(如日志、权限控制等)与核心业务逻辑相分离,从而提高代码的复用性和可读性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值