【机器学习】【条件随机场CRF-2】CRF的预测算法之维特比算法(viterbi alg) 详解 + 示例讲解 + Python实现

1.CRF的预测算法

条件随机场的预测算法是给定条件随机场P(Y|X)和输入序列(观测序列)x,求条件概率最大的输出序列(标记序列)y*,即对观测序列进行标注。条件随机场的预测算法是著名的维特比算法(Vitebi Algorthim)。

维特比算法在隐马尔科夫模型的预测算法中已经详细介绍和Python实现过,详见以前的博客:

【机器学习】【隐马尔可夫模型-4】维特比算法:算法详解+示例讲解+Python实现

2.CRF的预测算法之维特比算法

2.1维特比算法简介

维特比算法实际使用动态规划解CRF条件随机场的预测问题,即用动态规划(Dynamic Programming)求概率最大状态路径(最优路径)。这时一条路径对应一个状态序列。

根据动态规划原理,最优路径具有这样的特性:如果最优路径在时刻t通过节点i*t,那么这一路径从节点i*t到终点i*T的部分路径,对于从i*t到i*T的所有可能的部分来说,必须是最优的。

我们只需要从时刻t=1开始,递推地计算在时刻t状态为i的各条部分路径的最大概率,直至得到时刻t=T状态为i的各条路径的最大概率。时刻t=T的最大概率即为最优路径的概率P*,最优路径的终结点i*T也同时得到。

之后,为了找出最优路径的各个节点,从终结点i*T开始,由后向前逐步求得节点i*T-1,……,i*1,得到最优路径I*=(i*1, i*2, i*3,……,i*T),这就是维特比算法。

2.2CRF预测问题的维特比算法

在条件随机场预测问题中,CRF预测问题就成为了求非规范化概率最大的最优路径问题。

提高效率思想:

    在CRF预测问题的维特比算法中,最终结果求得是最优路径,只需要计算非规范化概率,不必计算概率,可以大大提高效率。

条件随机场预测的维特比算法步骤:

输入:模型特征向量F(y, x)和权值向量w, 观测序列x=(x1, x2, ……,xn)

输出:最优路径y*=(y*1, y*2, ……,y*n)

(1)初始化位置1的各个标记j=1,2,……,m的非规范化概率

 

(2)递推计算,对i=2,3,……,n

 

(3)终止

 

(4)返回路径

 

注意理解:ψ保存每个节点的最大非规范化概率的前导节点,即当前节点的最优前节点,最优路径先已知最优终结点,然后根据最优终结点从ψ里面求出他的最优前前节点,依次求出y*n, y*n-1, ……,y*2, y*1

则Y*=(y*1, y*2, ……,y*n-1, y*n)T就是条件随机场预测问题的最优路径。

3.数学示例展示:CRF的预测算法之维特比算法

设有一标注问题:输入观测序列为X=(X1,X2,X3),输出标记序列为Y=(Y1,Y2,Y3),Y1,Y2,Y3取值于{1,2}.

假设特征t<k>,s<l>的对应的权值为λ<k>,μ<l>,公式如下所示:

 

这里只注明特征取值为1的条件,取值为0的条件省略,如下:

 

下同

 

请用维特比算法求给定的输入序列(观测序列)x对应的最有输出序列(标记序列)y*=(y*1, y*2, y*3)。

说明:t<k>是定义在边上的特征函数,称为转移特征,依赖于当前和前一个位置,s<l>是定义在节点上的特征函数,称为状态特征,依赖于当前位置。这部分内容属于:条件随机场的参数形式的知识。关于条件随机场的参数化形式,详见前面博客:点我。

示例中给的是条件随机场的参数化形式,我给出了对应的状态路径图,画在了纸上,人肉出品,哈哈,详见下图:

 

上图中给出了对t和s函数的理解,如果仔细看,应该很容易清楚其含义,然后就很容易地画出条件随机场的参数化形式对应的状态路径图~

下面给出维特比算法的数学求解过程,如果不懂,可以板砖儿扔过来~:

 

最后求得此示例的最优标记序列,即最优状态路径为Y*=(1,2,1)。

4.python实现CRF的预测算法之维特比算法

下面是展示数学过程的CRF预测问题的维特比算法的python实现,不是最终精简的代码,精简的代码,请见5节。

完全人肉出品,请详见:

4.1代码

# -*- coding: utf-8 -*-
"""
@author: 蔚蓝的天空tom
Talk is cheap, show me code
Aim:实现条件随机场预测问题的维特比算法
"""

import numpy as np

class CRF(object):
'''实现条件随机场预测问题的维特比算法
'''
def __init__(self, V, VW, E, EW):
'''
:param V:是定义在节点上的特征函数,称为状态特征
:param VW:是V对应的权值
:param E:是定义在边上的特征函数,称为转移特征
:param EW:是E对应的权值
'''
self.V = V #点分布表
self.VW = VW #点权值表
self.E = E #边分布表
self.EW = EW #边权值表
self.D = [] #Delta表,最大非规范化概率的局部状态路径概率
self.P = [] #Psi表,当前状态和最优前导状态的索引表s
self.BP = [] #BestPath,最优路径
return

def Viterbi(self):
'''
条件随机场预测问题的维特比算法,此算法一定要结合CRF参数化形式对应的状态路径图来理解,更容易理解.
'''
self.D = np.full(shape=(np.shape(self.V)), fill_value=.0)
self.P = np.full(shape=(np.shape(self.V)), fill_value=.0)
for i in range(np.shape(self.V)[0]):
#初始化
if 0 == i:
self.D[i] = np.multiply(self.V[i], self.VW[i])
self.P[i] = np.array([0, 0])
print('self.V[%d]='%i, self.V[i], 'self.VW[%d]='%i, self.VW[i], 'self.D[%d]='%i, self.D[i])
print('self.P:', self.P)
pass
#递推求解布局最优状态路径
else:
for y in range(np.shape(self.V)[1]): #delta[i][y=1,2...]
for l in range(np.shape(self.V)[1]): #V[i-1][l=1,2...]
delta = 0.0
delta += self.D[i-1, l] #前导状态的最优状态路径的概率
delta += self.E[i-1][l,y]*self.EW[i-1][l,y] #前导状态到当前状体的转移概率
delta += self.V[i,y]*self.VW[i,y] #当前状态的概率
print('(x%d,y=%d)-->(x%d,y=%d):%.2f + %.2f + %.2f='%(i-1, l, i, y, \
self.D[i-1, l], \
self.E[i-1][l,y]*self.EW[i-1][l,y], \
self.V[i,y]*self.VW[i,y]), delta)
if 0 == l or delta > self.D[i, y]:
self.D[i, y] = delta
self.P[i, y] = l
print('self.D[x%d,y=%d]=%.2f\n'%(i, y, self.D[i,y]))
print('self.Delta:\n', self.D)
print('self.Psi:\n', self.P)

#返回,得到所有的最优前导状态
N = np.shape(self.V)[0]
self.BP = np.full(shape=(N,), fill_value=0.0)
t_range = -1 * np.array(sorted(-1*np.arange(N)))
for t in t_range:
if N-1 == t:#得到最优状态
self.BP[t] = np.argmax(self.D[-1])
else: #得到最优前导状态
self.BP[t] = self.P[t+1, int(self.BP[t+1])]

#最优状态路径表现在存储的是状态的下标,我们执行存储值+1转换成示例中的状态值
#也可以不用转换,只要你能理解,self.BP中存储的0是状态1就可以~~~~
self.BP += 1

print('最优状态路径为:', self.BP)
return self.BP

def CRF_manual():
S = np.array([[1,1], #X1:S(Y1=1), S(Y1=2)
[1,1], #X2:S(Y2=1), S(Y2=2)
[1,1]]) #X3:S(Y3=1), S(Y3=1)
SW = np.array([[1.0, 0.5], #X1:SW(Y1=1), SW(Y1=2)
[0.8, 0.5], #X2:SW(Y2=1), SW(Y2=2)
[0.8, 0.5]])#X3:SW(Y3=1), SW(Y3=1)
E = np.array([[[1, 1], #Edge:Y1=1--->(Y2=1, Y2=2)
[1, 0]], #Edge:Y1=2--->(Y2=1, Y2=2)
[[0, 1], #Edge:Y2=1--->(Y3=1, Y3=2)
[1, 1]]])#Edge:Y2=2--->(Y3=1, Y3=2)
EW= np.array([[[0.6, 1], #EdgeW:Y1=1--->(Y2=1, Y2=2)
[1, 0.0]], #EdgeW:Y1=2--->(Y2=1, Y2=2)
[[0.0, 1], #EdgeW:Y2=1--->(Y3=1, Y3=2)
[1, 0.2]]])#EdgeW:Y2=2--->(Y3=1, Y3=2)

crf = CRF(S, SW, E, EW)
ret = crf.Viterbi()
print('最优状态路径为:', ret)
return

if __name__=='__main__':
CRF_manual()
4.2运行结果

会发现,运行结果展示的数学计算中间值,和上面的数学求解步骤中间值一模一样~~~~~,可以说明代码is ok~~

runfile('C:/Users/tom/CRF_vitebi.py', wdir='C:/Users/tom')
self.V[0]= [1 1] self.VW[0]= [ 1. 0.5] self.D[0]= [ 1. 0.5]
self.P: [[ 0. 0.]
[ 0. 0.]
[ 0. 0.]]
(x0,y=0)-->(x1,y=0):1.00 + 0.60 + 0.80= 2.4
(x0,y=1)-->(x1,y=0):0.50 + 1.00 + 0.80= 2.3
self.D[x1,y=0]=2.40

(x0,y=0)-->(x1,y=1):1.00 + 1.00 + 0.50= 2.5
(x0,y=1)-->(x1,y=1):0.50 + 0.00 + 0.50= 1.0
self.D[x1,y=1]=2.50

(x1,y=0)-->(x2,y=0):2.40 + 0.00 + 0.80= 3.2
(x1,y=1)-->(x2,y=0):2.50 + 1.00 + 0.80= 4.3
self.D[x2,y=0]=4.30

(x1,y=0)-->(x2,y=1):2.40 + 1.00 + 0.50= 3.9
(x1,y=1)-->(x2,y=1):2.50 + 0.20 + 0.50= 3.2
self.D[x2,y=1]=3.90

self.Delta:
[[ 1. 0.5]
[ 2.4 2.5]
[ 4.3 3.9]]
self.Psi:
[[ 0. 0.]
[ 0. 0.]
[ 1. 0.]]
最优状态路径为: [ 1. 2. 1.]
最优状态路径为: [ 1. 2. 1.]
5.精简后的python代码

5.1代码

# -*- coding: utf-8 -*-
"""
@author: 蔚蓝的天空tom
Talk is cheap, show me code
Aim:实现条件随机场预测问题的维特比算法
"""

import numpy as np

class CRF(object):
'''实现条件随机场预测问题的维特比算法
'''
def __init__(self, V, VW, E, EW):
'''
:param V:是定义在节点上的特征函数,称为状态特征
:param VW:是V对应的权值
:param E:是定义在边上的特征函数,称为转移特征
:param EW:是E对应的权值
'''
self.V = V #点分布表
self.VW = VW #点权值表
self.E = E #边分布表
self.EW = EW #边权值表
self.D = [] #Delta表,最大非规范化概率的局部状态路径概率
self.P = [] #Psi表,当前状态和最优前导状态的索引表s
self.BP = [] #BestPath,最优路径
return

def Viterbi(self):
'''
条件随机场预测问题的维特比算法,此算法一定要结合CRF参数化形式对应的状态路径图来理解,更容易理解.
'''
self.D = np.full(shape=(np.shape(self.V)), fill_value=.0)
self.P = np.full(shape=(np.shape(self.V)), fill_value=.0)
for i in range(np.shape(self.V)[0]):
#初始化
if 0 == i:
self.D[i] = np.multiply(self.V[i], self.VW[i])
self.P[i] = np.array([0, 0])
#递推求解布局最优状态路径
else:
for y in range(np.shape(self.V)[1]): #delta[i][y=1,2...]
for l in range(np.shape(self.V)[1]): #V[i-1][l=1,2...]
#前导状态的最优状态路径的概率 + 前导状态到当前状体的转移概率 + 当前状态的概率
delta = self.D[i-1, l] + self.E[i-1][l,y]*self.EW[i-1][l,y] + self.V[i,y]*self.VW[i,y] #
if 0 == l or delta > self.D[i, y]:
self.D[i, y], self.P[i, y] = delta, l
#返回,得到所有的最优前导状态
N = np.shape(self.V)[0]
self.BP = np.full(shape=(N,), fill_value=0.0)
t_range = -1 * np.array(sorted(-1*np.arange(N)))
for t in t_range:
if N-1 == t:#得到最优状态
self.BP[t] = np.argmax(self.D[-1])
else: #得到最优前导状态
self.BP[t] = self.P[t+1, int(self.BP[t+1])]

#最优状态路径表现在存储的是状态的下标,我们执行存储值+1转换成示例中的状态值
#也可以不用转换,只要你能理解,self.BP中存储的0是状态1就可以~~~~
self.BP += 1
return self.BP

def CRF_manual():
S = np.array([[1,1], #X1:S(Y1=1), S(Y1=2)
[1,1], #X2:S(Y2=1), S(Y2=2)
[1,1]]) #X3:S(Y3=1), S(Y3=1)
SW = np.array([[1.0, 0.5], #X1:SW(Y1=1), SW(Y1=2)
[0.8, 0.5], #X2:SW(Y2=1), SW(Y2=2)
[0.8, 0.5]])#X3:SW(Y3=1), SW(Y3=1)
E = np.array([[[1, 1], #Edge:Y1=1--->(Y2=1, Y2=2)
[1, 0]], #Edge:Y1=2--->(Y2=1, Y2=2)
[[0, 1], #Edge:Y2=1--->(Y3=1, Y3=2)
[1, 1]]])#Edge:Y2=2--->(Y3=1, Y3=2)
EW= np.array([[[0.6, 1], #EdgeW:Y1=1--->(Y2=1, Y2=2)
[1, 0.0]], #EdgeW:Y1=2--->(Y2=1, Y2=2)
[[0.0, 1], #EdgeW:Y2=1--->(Y3=1, Y3=2)
[1, 0.2]]])#EdgeW:Y2=2--->(Y3=1, Y3=2)

crf = CRF(S, SW, E, EW)
ret = crf.Viterbi()
print('最优状态路径为:', ret)
return

if __name__=='__main__':
CRF_manual()
5.2运行结果

runfile('C:/Users/tom/CRF_vitebi_imp.py', wdir='C:/Users/tom')
最优状态路径为: [ 1. 2. 1.]
写完了,条件随机场先到此结束,后续再更新。
---------------------
作者:CV_ML_DP
来源:CSDN
原文:https://blog.csdn.net/u012421852/article/details/80287588
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2019-07-11 23:14  交流_QQ_2240410488  阅读(895)  评论(0编辑  收藏  举报