• 《统计学习方法》啃书手册|字符串核函数动态规划的实现


    位置:《统计学习方法》啃书手册 > 第7章 支持向量机 > 字符串核函数动态规划的实现

    已知两个字符串 s s s t t t 上的字符串核函数是基于映射 ϕ n \phi_n ϕn 的特征空间中的内积,即字符串 s s s t t t 中长度等于 n n n 的所有子串组成的特征向量的余弦相似度。其计算公式如下:

    k n ( s , t ) = ∑ u ∈ Σ n [ ϕ n ( s ) ] u [ ϕ n ( t ) ] u = ∑ u ∈ Σ n ∑ ( i , j ) : s ( i ) = t ( j ) = u λ l ( i ) λ l ( j )

    kn(s,t)=uΣn[ϕn(s)]u[ϕn(t)]u=uΣn(i,j):s(i)=t(j)=uλl(i)λl(j)" role="presentation" style="position: relative;">kn(s,t)=uΣn[ϕn(s)]u[ϕn(t)]u=uΣn(i,j):s(i)=t(j)=uλl(i)λl(j)
    kn(s,t)=uΣn[ϕn(s)]u[ϕn(t)]u=uΣn(i,j):s(i)=t(j)=uλl(i)λl(j)

    其中 λ \lambda λ 是衰减参数, u u u 是相同子序列, l ( i ) l(i) l(i) 为子序列在 s s s 中的长度(最后 1 个字符的下标-第 1 个字符的下标+1), l ( j ) l(j) l(j) 为子序列在 t t t 中的长度。

    观察上式,我们可以发现,每一个相同子串所提供的相似度,都是 λ l ( i ) + l ( j ) \lambda^{l(i)+l(j)} λl(i)+l(j),即衰减参数的两个子串的长度之和次方;于是有长度每增加 1 1 1,所提供的相似度都只需要乘以 λ \lambda λ 即可。

    于是,我们很自然地想到,可以用类似计数 DP 的方法,当前状态( s 1 s1 s1 的前 i i i 个字符和 s 2 s2 s2 的前 j j j 个字符),只需要记录所有长度为 l l l 为相同子序列提供的相似度之和即可。当考虑下一个状态(例如 s 1 s1 s1 的前 i + 1 i+1 i+1 个字符和 s 2 s2 s2 的前 j j j 个字符)时,只需要简单地将上一个状态乘以衰减因子 λ \lambda λ 即可。需要注意的是,我们需要考虑可能存在的被重复计算的问题。

    具体地,状态矩阵如下:

    dp[l][i]][j] s 1 s1 s1 的前 i i i 个字符和 s 2 s2 s2 的前 j j j 个字符中,所有长度为 l l l 的相同子序列所提供的相似度的和。因为考虑到状态转移中,只会用到 l l l l − 1 l-1 l1,所以可以省略 l l l 以节约空间。

    状态转移方程如下(其中att为衰减因子):

    dp[l][i][j] = dp[l][i-1][j] * att + dp[l][i][j-1] * att + dp[l][i-1][j-1] * att * att(第 3 项是在处理被重复计算的问题)

    在当前字符相同时,额外增加使用当前相同字符的情况:dp[l][i][j] += dp[l-1][i-1][j-1] * att * att;同时,当前子序列长度的核函数的结果由这部分累加即可。

    源码地址

    def count_kernel_function_for_string(s1, s2, length: int, att: float):
        """计算字串长度等于n的字符串核函数的值
    
        :param s1: 第1个字符串
        :param s2: 第2个字符串
        :param length: 需要查找的英文字符集长度
        :param att: 衰减参数
        :return: 字符串核函数的值
        """
    
        # 计算字符串长度
        n1, n2 = len(s1), len(s2)
    
        # 定义状态矩阵
        dp1 = [[1] * (n2 + 1) for _ in range(n1 + 1)]
    
        # 字符串的核函数的值的列表:ans[i] = 子串长度为(i+1)的字符串核函数的值
        ans = []
    
        # 依据子串长度进行状态转移:[1,n]
        for l in range(length):
            dp2 = [[0] * (n2 + 1) for _ in range(n1 + 1)]
    
            # 定义当前子串长度的核函数的值
            res = 0
    
            # 进行状态转移
            for i in range(1, n1 + 1):
                for j in range(1, n2 + 1):
                    dp2[i][j] += dp2[i - 1][j] * att + dp2[i][j - 1] * att - dp2[i - 1][j - 1] * att * att
                    if s1[i - 1] == s2[j - 1]:
                        dp2[i][j] += dp1[i - 1][j - 1] * att * att
                        res += dp1[i - 1][j - 1] * att * att  # 累加当前长度核函数的值
    
            dp1 = dp2
            ans.append(res)
    
        return ans[-1]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    时间复杂度、空间复杂度分析:

    • 时间复杂度: O ( N 1 × N 2 × L e n g t h ) O(N1×N2×Length) O(N1×N2×Length);其中 N 1 N1 N1 为第 1 个字符串的长度, N 2 N2 N2 为第 2 个字符串的长度, L e n g t h Length Length 为子串长度
    • 空间复杂度: O ( N 1 × N 2 ) O(N1×N2) O(N1×N2)
  • 相关阅读:
    【Oracle训练营】属于你的9天Oracle实战训练营!
    第十三更---大家都在那里查找资料??
    ROS学习之C++读取txt点云并在rviz中显示
    使用docker部署mysql8.0+zabbix5.0
    SSM出租车查询系统 毕业设计-附源码220915
    MySQL和Navicat的安装与配置
    Ubuntu22.04更新后 点击深度微信无反应
    自动驾驶---Control之LQR控制
    使用naive-ui做一个标签页展示列表
    LeetCode 每日一题——1413. 逐步求和得到正数的最小值
  • 原文地址:https://blog.csdn.net/Changxing_J/article/details/126924608