机器学习算法---条件随机场(Conditional Random Field,CRF)

为什么要引入条件随机场(CRF)?

假设你有许多小明同学一天内不同时段的照片,从小明提裤子起床到脱裤子睡觉各个时间段都有(小明是照片控!)。现在的任务是对这些照片进行分类。比如有的照片是吃饭,那就给它打上吃饭的标签;有的照片是跑步时拍的,那就打上跑步的标签;有的照片是开会时拍的,那就打上开会的标签。问题来了,你准备怎么干?
一个简单直观的办法就是,不管这些照片之间的时间顺序,想办法训练出一个多元分类器。就是用一些打好标签的照片作为训练数据,训练出一个模型,直接根据照片的特征来分类。例如,如果照片是早上6:00拍的,且画面是黑暗的,那就给它打上睡觉的标签;如果照片上有车,那就给它打上开车的标签。
这样可行吗?
乍一看可以!但实际上,由于我们忽略了这些照片之间的时间顺序这一重要信息,我们的分类器会有缺陷的。举个例子,假如有一张小明闭着嘴的照片,怎么分类?显然难以直接判断,需要参考闭嘴之前的照片,如果之前的照片显示小明在吃饭,那这个闭嘴的照片很可能是小明在咀嚼食物准备下咽,可以给它打上吃饭的标签;如果之前的照片显示小明在唱歌,那这个闭嘴的照片很可能是小明唱歌瞬间的抓拍,可以给它打上唱歌的标签。
所以,为了让我们的分类器能够有更好的表现,在为一张照片分类时,我们必须将与它相邻的照片的标签信息考虑进来。这——就是条件随机场(CRF)大显身手的地方!

马尔科夫过程定义

假设一个随机过程中, t n t_n tn 时刻的状态 x n x_n xn的条件发布,只与其前一状态 x n − 1 x_{n-1} xn1 相关,即:

P ( x n ∣ x 1 , x 2 , . . . , x n − 1 ) = P ( x n ∣ x n − 1 ) P(x_n|x_1,x_2,...,x_{n-1}) = P(x_n|x_{n-1}) P(xnx1,x2,...,xn1)=P(xnxn1)

则将其称为 马尔可夫过程。

在这里插入图片描述

隐马尔科夫算法定义

隐马尔科夫算法是对含有未知参数(隐状态)的马尔可夫链进行建模的生成模型,如下图所示:
在这里插入图片描述

在隐马尔科夫模型中,包含隐状态 和 观察状态,隐状态 x i x_i xi 对于观察者而言是不可见的,而观察状态 y i y_i yi 对于观察者而言是可见的。隐状态间存在转移概率,隐状态 x i x_i xi到对应的观察状态 y i y_i yi 间存在输出概率。

假设

  1. 假设隐状态 x i x_i xi 的状态满足马尔可夫过程,i时刻的状态 x i x_i xi 的条件分布,仅与其前一个状态 x i − 1 x_{i-1} xi1相关,即:

P ( x i ∣ x 1 , x 2 , . . . , x i − 1 ) = P ( x i ∣ x i − 1 ) P(x_i|x_1,x_2,...,x_{i-1}) = P(x_i|x_{i-1}) P(xix1,x2,...,xi1)=P(xixi1)

  1. 假设观测序列中各个状态仅取决于它所对应的隐状态,即:

P ( y i ∣ x 1 , x 2 , . . . , x i − 1 , y 1 , y 2 , . . . , y i − 1 , y i + 1 , . . . ) = P ( y i ∣ x i ) P(y_i|x_1,x_2,...,x_{i-1},y_1,y_2,...,y_{i-1},y_{i+1},...) = P(y_i|x_{i}) P(yix1,x2,...,xi1,y1,y2,...,yi1,yi+1,...)=P(yixi)

存在问题
在序列标注问题中,隐状态(标注)不仅和单个观测状态相关,还和观察序列的长度、上下文等信息相关。例如词性标注问题中,一个词被标注为动词还是名词,不仅与它本身以及它前一个词的标注有关,还依赖于上下文中的其他词。因而我们需要更好的模型来解决我们的问题,也就是接下来要讲的条件随机场(CRF)。

CRF的定义(以线性链条件随机场为例)

给定 X = ( x 1 , x 2 , . . . , x n ) X=(x_1,x_2,...,x_n) X=(x1,x2,...,xn) Y = ( y 1 , y 2 , . . . , y n ) Y=(y_1,y_2,...,y_n) Y=(y1,y2,...,yn) 均为线性链表示的随机变量序列,若在给随机变量序列 X 的条件下,随机变量序列 Y 的条件概率分布 P ( Y ∣ X ) P(Y|X) P(YX) 构成条件随机场,即满足马尔可夫性:

P ( y i ∣ x 1 , x 2 , . . . , x i − 1 , y 1 , y 2 , . . . , y i − 1 , y i + 1 ) = P ( y i ∣ x , y i − 1 , y i + 1 ) P(y_i|x_1,x_2,...,x_{i-1},y_1,y_2,...,y_{i-1},y_{i+1}) = P(y_i|x,y_{i-1},y_{i+1}) P(yix1,x2,...,xi1,y1,y2,...,yi1,yi+1)=P(yix,yi1,yi+1)

则称为 P(Y|X) 为线性链条件随机场。

通过去除了隐马尔科夫算法中的观测状态相互独立假设,使算法在计算当前隐状态 x i x_i xi时,会考虑整个观测序列,从而获得更高的表达能力,并进行全局归一化解决标注偏置问题。

在这里插入图片描述

参数化形式

p ( y ∣ x ) = 1 Z ( x ) ∏ i = 1 n exp ⁡ ( ∑ i , k λ k t k ( y i − 1 , y i , x , i ) + ∑ i , l μ l s l ( y i , x , i ) ) p\left(y | x\right)=\frac{1}{Z\left(x\right)} \prod_{i=1}^{n} \exp \left(\sum_{i, k} \lambda_{k} t_{k}\left(y_{i-1}, y_{i}, x, i\right)+\sum_{i, l} \mu_{l} s_{l}\left(y_{i}, x, i\right)\right) p(yx)=Z(x)1i=1nexpi,kλktk(yi1,yi,x,i)+i,lμlsl(yi,x,i)

其中:

Z ( x ) Z(x) Z(x) 为归一化因子,是在全局范围进行归一化,枚举了整个隐状态序列 x 1 … n x_{1…n} x1n的全部可能,从而解决了局部归一化带来的标注偏置问题。

Z ( x ) = ∑ y exp ⁡ ( ∑ i , k λ x t k ( y i − 1 , y i , x , i ) + ∑ i , l μ l s l ( y i , x , i ) ) Z(x)=\sum_{y} \exp \left(\sum_{i, k} \lambda_{x} t_{k}\left(y_{i-1}, y_{i}, x, i\right)+\sum_{i, l} \mu_{l} s_{l}\left(y_{i}, x, i\right)\right) Z(x)=yexpi,kλxtk(yi1,yi,x,i)+i,lμlsl(yi,x,i)

t k t_k tk 为定义在边上的特征函数,转移特征,依赖于前一个和当前位置,k=1,2,…K,K代表结点i转移特征函数的个数(可能不止一个)
s 1 s_1 s1 为定义在节点上的特征函数,状态特征,依赖于当前位置。,l=1,2,…L,L是当前该节点i的结点特征函数的个数(可能不止一个)

以上两种特征函数,都只能取值为0或者1,因此其特点就在于他们的权重了,权重越大,说明这个函数的重要性越大。

实现条件随机场预测问题的维特比算法

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()

在这里插入图片描述

参考

【1】https://github.com/datawhalechina/team-learning/blob/master/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80/Task4%20%E6%9D%A1%E4%BB%B6%E9%9A%8F%E6%9C%BA%E5%9C%BA.ipynb

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值