目录

  • 一、什么是LCS
  • 子序列
  • 最长公共子序列
  • 二、LCS的应用场景
  • 三、LCS的查找方法
  • 1. 动态规划法计算LCS的长度和两字符串的相似度
  • 2. 回溯算法查找LCS
  • 四、代码实现


一、什么是LCS

子序列

子序列:一个序列S任意删除若干个字符得到的新序列T,则T叫做S的子序列

最长公共子序列

最长公共子序列(Longest Common Subsequence):两个序列X和Y的公共子序列中,长度最长的那个,定义为X和Y的最长公共子序列

例如:
字符串12455245576的最长公共子序列为2455
字符串acdfg与adfc的最长公共子序列为adf

二、LCS的应用场景

nlp相似度代码 nlp相似度匹配算法_算法

文本匹配:即字面匹配,是自然语言处理中一个重要的基础问题,可以应用于大量的NLP任务中,如信息检索、问答系统、复述问题、对话系统、机器翻译等,这些NLP任务在很大程度上可以抽象为文本匹配问题。

局限性

  • 词义局限:“的士”和“出租车”虽然字面上不相似,但实际为同一种交通工具;“苹果”在不同的语境下表示不同的东西,或为水果或为公司
  • 结构局限:“机器学习”和“学习机器”虽然词汇完全重合,但表达的意思不同。
  • 知识局限:“秦始皇打Dota”,这句话虽从词法和句法上看均没问题,但结合知识看这句话是不对的。

语义匹配:结合语义、语境和知识进行的文本匹配
比如 ‘苹果’ 这个词语,要结合上下文的语境才能判断指的是水果还是苹果品牌

多模态

  • 模态:模态指交流的渠道和媒介,包括语言、技术、图像、颜色、音乐等符号系统。
  • 多模态:指的便是除了文本之外,还带有图像、图表等符合话语,或者说任何由一种以上符合编码实现意义的文本
  • 多模态话语分析 :就是分析单一感官的多符号语篇或多感官的多符号语篇。

三、LCS的查找方法

1. 动态规划法计算LCS的长度和两字符串的相似度

已知两个字符串X=‘ABCBDAB’,Y=‘BDCABA’,长度分别为i=7,j=6

构建二维数组C[i,j],以0填充

nlp相似度代码 nlp相似度匹配算法_自然语言处理_02

将X的每个字符分别和Y的每个字符比较,
1.如果X和Y其中有任意一个字符串长度为0,则C[i,j]=0
2.如果X[i]=Y[j],则C[i,j]取矩阵左上角数值+1,即C[i-1,j-1]+1
3.如果X[i]≠Y[j],则C[i,j]取矩阵上和左数值中较大的一个,即max(C[i-1,j],C[i,j-1])

公式:

nlp相似度代码 nlp相似度匹配算法_nlp相似度代码_03


矩阵按照公式填满后如下图所示:

nlp相似度代码 nlp相似度匹配算法_自然语言处理_04

序列X 和Y 的最长公共子序列的长度为 C[i,j]=4

X和Y相似度=最长公共子序列的长度*2 / X和Y的长度之和,即
sim=len(lcs)*2 / len(X)+len(Y)

本例相似度 sim=4*2 / 13 = 0.615

2. 回溯算法查找LCS

在1 中的算法查到了LCS的长度,下边我们讲把LCS查找出来
从上文的中我们发现,真正使得LCS计算长度增加的有效数字,只有下边这种情况

2.如果X[i]=Y[j],则C[i,j]取矩阵左上角数值+1,即C[i-1,j-1]+1

在矩阵中标识出这些数字

nlp相似度代码 nlp相似度匹配算法_nlp相似度代码_05


注:C[i-1,j-1]+1=max(C[i-1,j],C[i,j-1]),即 左上角数字+1=上、左数字较大值的情况,不计入有效从C[i,j] 右下角出发,向左上角 i,j 递减的方向,每个梯度数字选择一个,找出所有满足条件的路径

nlp相似度代码 nlp相似度匹配算法_机器学习_06


找到每条路径路过的有效数字正上方Y中对应的字符,则找到所有的LCS

本例的结果为:BCBA,BCAB,BDAB

四、代码实现

# coding=utf-8
class LCS():
    def input(self,x, y):
    #读入待匹配的两个字符串
        if type(x) != str or type(y) != str:
            print ('input error')
            return None
        self.x = x
        self.y = y

    def Compute_LCS(self):
        xlength = len(self.x)
        ylength = len(self.y)
        self.direction_list = [None] * xlength #这个二维列表存着回溯方向

        for i in range(xlength):
            self.direction_list[i] = [None] * ylength
        self.lcslength_list = [None] * (xlength + 1)

        #这个二维列表存着当前最长公共子序列长度
        for j in range(xlength + 1):
            self.lcslength_list[j] = [None] * (ylength + 1)
        for i in range(0, xlength + 1):
            self.lcslength_list[i][0] = 0
        for j in range(0, ylength + 1):
            self.lcslength_list[0][j] = 0
            
        #下面是进行回溯方向和长度表的赋值
        for i in range(1, xlength + 1):
            for j in range(1, ylength + 1):
                if self.x[i - 1] == self.y[j - 1]:
                    self.lcslength_list[i][j] = self.lcslength_list[i - 1][j - 1] + 1
                    self.direction_list[i - 1][j - 1] = 0  # 左上
                elif self.lcslength_list[i - 1][j] > self.lcslength_list[i][j - 1]:
                    self.lcslength_list[i][j] = self.lcslength_list[i - 1][j]
                    self.direction_list[i - 1][j - 1] = 1  # 上
                elif self.lcslength_list[i - 1][j] < self.lcslength_list[i][j - 1]:
                    self.lcslength_list[i][j] = self.lcslength_list[i][j - 1]
                    self.direction_list[i - 1][j - 1] = -1  # 左
                else:
                    self.lcslength_list[i][j] = self.lcslength_list[i - 1][j]
                    self.direction_list[i - 1][j - 1] = 2  # 左或上
        self.lcslength=self.lcslength_list[-1][-1]
        
        #遍历矩阵
        for i in range(len(self.lcslength_list)):     
            for j in range(len(self.lcslength_list[i])):
                print(self.lcslength_list[i][j], end='\t')
            print('')
        
        sum_len=xlength + ylength
        similarity=float(float(self.lcslength * 2) / float(sum_len))
        print('max_len:',self.lcslength,',sum_len:',sum_len,'similarity:',similarity)
        return self.direction_list, self.lcslength_list

    def printLCS(self, curlen, i, j, s):
        if i == 0 or j == 0:
            return None

        if self.direction_list[i - 1][j - 1] == 0:
            if curlen == self.lcslength:
                s += self.x[i - 1]
                for i in range(len(s)-1,-1,-1):
                    print (s[i], end=''),
                print('')
            elif curlen < self.lcslength:
                s += self.x[i-1]
                self.printLCS(curlen + 1, i - 1, j - 1, s)
        elif self.direction_list[i - 1][j - 1] == 1:
            self.printLCS(curlen,i - 1, j,s)
        elif self.direction_list[i - 1][j - 1] == -1:
            self.printLCS(curlen,i, j - 1,s)
        else:
            self.printLCS(curlen,i - 1, j,s)
            self.printLCS(curlen,i, j - 1,s)


    def returnLCS(self):
        #回溯的入口
        self.printLCS(1,len(self.x), len(self.y),'')


if __name__ == '__main__':
    p = LCS()
    p.input('ABCBDAB', 'BDCABA')
    p.Compute_LCS()
    p.returnLCS()

效果:

0	0	0	0	0	0	0	
0	0	0	0	1	1	1	
0	1	1	1	1	2	2	
0	1	1	2	2	2	2	
0	1	1	2	2	3	3	
0	1	2	2	2	3	3	
0	1	2	2	3	3	4	
0	1	2	2	3	4	4	
max_len: 4 ,sum_len: 13 similarity: 0.6153846153846154
BCBA
BCAB
BDAB