维特比(Viterbi)算法

算法思想

维特比(Viterbi)算法属于一种动态规划算法,目标在于寻找最优路径。我个人用得最多就是与BiLSTM+CRF模型的结合,比如命名实体识别、分词等,计算了每个token的归一化概率矩阵和转移概率矩阵之后,最后根据维特比算法得到整个文本句子的最优概率输出。

它的思想是这样:如果最优路径在时刻t经过结点 i t i_t it,那么这一路径从结点 i t i_t it到终点 i T i_T iT的部分路径,对于从 i t i_t it i T i_T iT的所有可能的部分路径来说,必须是最优的。

依据这个原理,我们只需从时刻t=1开始,递推的计算在时刻t状态为 s i s_i si的各条部分路径的最大分数,然后按着这条最大路径往下走,走到时刻t = T的最大分数即为最优路径的分数。

实际例子

举个具体的例子来说明Viterbi完成的编码过程:

score:预测概率矩阵。比如第一行代表的含义:时刻t=1(第一个token)状态 s = s 1 s=s_1 s=s1(属于第一种标签)的概率为0.8,状态 s + s 2 s+s2 s+s2(属于第二种标签)的概率为0.1…

transition:转移矩阵。比如第一行:从状态 s 1 s_1 s1(第一种标签)向 s 1 s_1 s1(第一种标签)转移的概率为0.5,向 s 2 s_2 s2(第二种标签)转移的概率为0.2…

如果你应用在命名实体识别或者分词模型中,那这就是它的预测输出结果。

维特比(Viterbi)算法_第1张图片

维特比(Viterbi)算法_第2张图片
在寻找最优路径的时候,需要有一个用于存储累计得分的变量

  1. 因为时刻t=1没有发生状态转移,所以此时的累计得分为{0.8, 0.1, 0.1}。第一个数字0.8代表时刻t=1选择状态1的累计分数,其他同理。

  2. 来到时刻t=2,上一个时刻即t=1,状态1-3都可以转移到状态1,那么就要寻找转移到状态1的最优选择,比如状态1转移状态1的分数为:0.8(时刻t=1选择状态1的分数) + 0.5(状态1转移到状态1的转移分数) + 0.1(时刻t=2即当前选择状态1的分数)= 1.4。

    状态2和3的计算方法同理,然后得到更新后的累计得分为(1.4, 1.5, 1.5)

  3. 时刻t=3也是一样的。最后的累计分数为{2.1, 2.6, 2.2}。分别对应的路径就是上图中加粗的部分,那全局最优的路径当然就是累计分数最高的2.6对应的路径了** ( s 1 − − − > s 2 − − − > s 2 ) (s_1--->s_2--->s_2) (s1>s2>s2)**。

  4. 至此,Viterbi算法执行完毕。

代码实现

import numpy as np


def viterbi_decode(score, transition_params):
    """Decode the highest scoring sequence of tags outside of TensorFlow.

    This should only be used at test time.

    Args:
        score: A [seq_len, num_tags] matrix of unary potentials.
        transition_params: A [num_tags, num_tags] matrix of binary potentials.

    Returns:
        viterbi: A [seq_len] list of integers containing the highest scoring tag
                indices.
        viterbi_score: A float containing the score for the Viterbi sequence.
    """
    # 用于存储累计分数的数组
    trellis = np.zeros_like(score)
    # 用于存储最优路径索引的数组
    backpointers = np.zeros_like(score, dtype=np.int32)
    # 第一个时刻的累计分数
    trellis[0] = score[0]

    for t in range(1, score.shape[0]):
        # 各个状态截止到上个时刻的累计分数 + 转移分数
        v = np.expand_dims(trellis[t - 1], 1) + transition_params
        # max(各个状态截止到上个时刻的累计分数 + 转移分数)+ 选择当前状态的分数
        trellis[t] = score[t] + np.max(v, 0)
        # 记录累计分数最大的索引
        backpointers[t] = np.argmax(v, 0)
	
    # 最优路径的结果
    viterbi = [np.argmax(trellis[-1])]
    # 反向遍历每个时刻,得到最优路径
    for bp in reversed(backpointers[1:]):
        viterbi.append(bp[viterbi[-1]])
    viterbi.reverse()

    viterbi_score = np.max(trellis[-1])
    return viterbi, viterbi_score

欢迎关注同名公众号:“我就算饿死也不做程序员”。
交个朋友,一起交流,一起学习,一起进步。维特比(Viterbi)算法_第3张图片

你可能感兴趣的:(自然语言处理,viterbi,动态规划,维特比算法,最优路径,自然语言处理)