条件随机场CRF总结和实现

https://applenob.github.io/crf.html

https://github.com/heshenghuan/linear_chain_crf 实例

条件随机场 CRF总结和实现

目录

概率无向图模型

回顾一下之前讲解的概率无向图模型:https://applenob.github.io/graph_model.html

总结一下:

  • 最大团:无向图GG中任何两个结点都有边连接的结点子集称为团(clique)。若团CC不能在加入任何一个结点使其称为一个更大的团,则称CC为图GG的一个最大团。
  • 概率无向图模型的联合概率分布可以表示成其最大团上的随机变量的函数的乘积形式。这也被称为概率无向图模型的因子分解
  • P(Y)=1Z∏CψC(YC)P(Y)=1Z∏CψC(YC),其中,ZZ是规范化因子,Z=∑Y∏CψC(YC)Z=∑Y∏CψC(YC),ψC(YC)ψC(YC)称为势函数,要求势函数是严格正的(因为涉及到累乘)。

条件随机场

条件随机场(Conditional Random Field, CRF)也是一种无向图模型。它是在给定随机变量XX的条件下,随机变量YY的马尔科夫随机场。

我们常用的是线性链条件随机场,多用于序列标注等问题。形式化定义:设X=(X1,X2,...,Xn)X=(X1,X2,...,Xn),Y=(Y1,Y2,...,Yn)Y=(Y1,Y2,...,Yn)均为线性链表示的随机变量序列,若给在定随机变量序列XX的条件下,随机变量序列YY的条件概率分布P(Y|X)P(Y|X)构成条件随机场,即满足马尔科夫性:P(Yi|X,Y1,...,Yi−1,Yi+1,...,Yn)=P(Yi|X,Yi−1,Yi+1)i=1,2,...,nP(Yi|X,Y1,...,Yi−1,Yi+1,...,Yn)=P(Yi|X,Yi−1,Yi+1)i=1,2,...,n(这个式子是核心,充分理解这个式子和下面的图片),则称P(Y|X)P(Y|X)是线性链条件随机场。

参数化形式

P(y|x)=1Z(x)exp(∑i,kλktk(yi−1,yi,x,i)+∑i,lμlsl(yi,x,i))P(y|x)=1Z(x)exp(∑i,kλktk(yi−1,yi,x,i)+∑i,lμlsl(yi,x,i))

其中,Z(x)=∑yexp(∑i,kλktk(yi−1,yi,x,i)+∑i,lμlsl(yi,x,i))Z(x)=∑yexp(∑i,kλktk(yi−1,yi,x,i)+∑i,lμlsl(yi,x,i)),tktk是转移(transform)特征函数,依赖于当前和前一个位置,slsl是状态(state)特征函数,依赖于当前位置,λkλk和μlμl是对应的权值。

从模型的参数化形式可以看出,线性链条件随机场也是对数线性模型。

简化形式

所谓简化形式即,将局部特征特征统一成一个全局特征函数。

设有K1K1个转移特征,有K2K2个状态特征,K=K1+K2K=K1+K2。

fk(yi−1,yi,x,i)={tk(yi−1,yi,x,i),k=1,2,...,K1sl(yi,x,i),k=K1+l,l=1,2,..,K2fk(yi−1,yi,x,i)={tk(yi−1,yi,x,i),k=1,2,...,K1sl(yi,x,i),k=K1+l,l=1,2,..,K2

对所有在位置ii的特征求和:

fk(y,x)=∑i=1nfk(yi−1,yi,x,i),k=1,2,...,Kfk(y,x)=∑i=1nfk(yi−1,yi,x,i),k=1,2,...,K

用wkwk表示特征fk(y,x)fk(y,x)的权值,即:wk={λk,k=1,2,...,K1μl,k=K1+l;l=1,2,...,K2wk={λk,k=1,2,...,K1μl,k=K1+l;l=1,2,...,K2

于是条件随机场又可以表示成:

P(y|x)=1Z(x)exp∑k=1Kwkfk(y,x)Z(x)=∑yexp∑k=1Kwkfk(y,x)P(y|x)=1Z(x)exp∑k=1Kwkfk(y,x)Z(x)=∑yexp∑k=1Kwkfk(y,x)

如果用ww表示权值向量,即w=(w1,...,wK)Tw=(w1,...,wK)T,用F(y,x)F(y,x)表示全局特征向量,即F(y,x)=(f1(y,x),f2(y,x),...,fK(y,x))TF(y,x)=(f1(y,x),f2(y,x),...,fK(y,x))T,则条件随机场可以携程向量ww和F(y,x)F(y,x)的内积的形式:

Pw(y|x)=exp(w⋅F(y,x))Zw(x)Z(x)=∑yexp(w⋅F(y,x))Pw(y|x)=exp(w⋅F(y,x))Zw(x)Z(x)=∑yexp(w⋅F(y,x))

矩阵形式

引入特殊的起点和终点状态标记y0=starty0=start,yn+1=stopyn+1=stop。

对于观测序列xx的每一个位置i=1,2,...,n+1i=1,2,...,n+1定义n+1n+1个mm阶方阵(mm是标记yiyi取值的个数)。

  • Mi(x)=[Mi(yi−1,yi|x)]Mi(x)=[Mi(yi−1,yi|x)]
  • Mi(yi−1,yi|x)=exp(Wi(yi−1,yi|x))Mi(yi−1,yi|x)=exp(Wi(yi−1,yi|x))
  • Wi(yi−1,yi|x)=∑Kk=1wkfk(yi,yi,x,i)Wi(yi−1,yi|x)=∑k=1Kwkfk(yi,yi,x,i)

条件随机场的矩阵形式:

Pw(y|x)=1Zw(x)∏i=1n+1wkfk(yi−1,yi,x,i)Zw(x)=(M1(x)M2(x)...Mn+1(x))start,stopPw(y|x)=1Zw(x)∏i=1n+1wkfk(yi−1,yi,x,i)Zw(x)=(M1(x)M2(x)...Mn+1(x))start,stop

即,简化了配分函数Zw(x)Zw(x)的计算方式。

三个问题

类似于隐马尔科夫模型(HMM),CRF也有典型的三个问题。对比二者在这三个问题的解决方法的不同,可以更深入理解这两个模型。

  • 1.概率计算问题:给定条件随机场P(Y|X)P(Y|X),输入序列xx和输出序列yy,计算条件概率P(Yi=yi|x)P(Yi=yi|x)和P(Yi−1=yi−1,Yi=yi|x)P(Yi−1=yi−1,Yi=yi|x)和相应的数学期望。
  • 2.学习问题:给定训练数据集,估计条件随机场模型参数,即用极大似然法的方法估计参数。
  • 3.预测问题:给定条件随机场P(Y|X)P(Y|X)和输入序列(观测序列)xx,求条件概率最大的输出序列(标记序列)y∗y∗。

概率计算问题

给定条件随机场P(Y|X)P(Y|X),输入序列xx和输出序列yy,计算条件概率P(Yi=yi|x)P(Yi=yi|x)和P(Yi−1=yi−1,Yi=yi|x)P(Yi−1=yi−1,Yi=yi|x)。

在这里我们可以明显看出,条件随机场直接计算条件概率,因此是判别模型;而HMM先由上一个状态生成下一个状态,再由下一个状态生成下一个输出,因此HMM是生成模型。

类似于HMM,引入前向-后向向量

对每个下标i=0,1,...,n+1i=0,1,...,n+1,定义前向向量αi(x)αi(x):

α0(y|x)={1,y=start0,否则α0(y|x)={1,y=start0,否则

递推公式:

αTi(yi|x)=αTi−1(yi−1|x)Mi(yi−1,yi|x),i=1,2,...,n+1αiT(yi|x)=αi−1T(yi−1|x)Mi(yi−1,yi|x),i=1,2,...,n+1

简单地表示:

αTi(x)=αTi−1(x)Mi(x)αiT(x)=αi−1T(x)Mi(x)

第ii个前向向量表示在位置ii的标记是yiyi,并且到位置ii的前部分标记序列的非规范化概率。yiyi的取值有mm个,所以αi(x)αi(x)是mm维列向量。

类似地,对于每个下标i=0,1,...,n+1i=0,1,...,n+1,定义前后向向量βi(x)βi(x):

βn+1(yn+1|x)={1,yn+1=stop0,否则βn+1(yn+1|x)={1,yn+1=stop0,否则

递推公式:

βi(yi|x)=Mi+1(yi,yi+1|x)βi+1(yi+1|x),i=1,2,...,n+1βi(yi|x)=Mi+1(yi,yi+1|x)βi+1(yi+1|x),i=1,2,...,n+1

简单地表示:

βi(x)=Mi+1(x)βi+1(x)βi(x)=Mi+1(x)βi+1(x)

第ii个后向向量表示在位置ii的标记是yiyi,并且从位置i+1i+1到nn的后部分标记序列的非规范化概率。

用前向-后向向量表示配分函数:Z(x)=αTn(x)⋅1=1T⋅β1(x)Z(x)=αnT(x)⋅1=1T⋅β1(x)

概率计算

不同于HMM的概率计算,使用前向概率或者后向概率即可,这里计算需要同时使用前向向量和后向向量。

P(Yi=yi|x)=αTi(yi|x)βi(yi|x)Z(x)P(Yi=yi|x)=αiT(yi|x)βi(yi|x)Z(x)

P(Yi−1=yi−1,Yi=yi|x)=αTi−1(yi−1|x)Mi(yi−1,yi|x)βi(yi|x)Z(x)P(Yi−1=yi−1,Yi=yi|x)=αi−1T(yi−1|x)Mi(yi−1,yi|x)βi(yi|x)Z(x)

期望值计算

特征函数fkfk关于条件分布P(Y|X)P(Y|X)的期望:

EP(Y|X)[fk]=∑yP(y|x)fk(y,x)=∑i=1n+1∑yi−1,yifk(yi−1,yi,x,i)αTi−1(yi−1|x)Mi(yi−1,yi|x)βi(yi|x)Z(x)k=1,2,..,KEP(Y|X)[fk]=∑yP(y|x)fk(y,x)=∑i=1n+1∑yi−1,yifk(yi−1,yi,x,i)αi−1T(yi−1|x)Mi(yi−1,yi|x)βi(yi|x)Z(x)k=1,2,..,K

特征函数fkfk关于联合分布P(X,Y)P(X,Y)的期望:

EP(X,Y)[fk]=∑x,yP(x,y)∑i=1n+1fk(yi−1,yi,x,i)=∑xP~(x)∑yP(y|x)∑i=1n+1fk(yi−1,y,x,i)=∑xP~(x)∑i=1n+1∑yi−1yiαTi−1(yi−1|x)Mi(yi−1,yi|x)βi(yi|x)Z(x)k=1,2,..,KEP(X,Y)[fk]=∑x,yP(x,y)∑i=1n+1fk(yi−1,yi,x,i)=∑xP~(x)∑yP(y|x)∑i=1n+1fk(yi−1,y,x,i)=∑xP~(x)∑i=1n+1∑yi−1yiαi−1T(yi−1|x)Mi(yi−1,yi|x)βi(yi|x)Z(x)k=1,2,..,K

核心代码:

前向后向和M矩阵都用保存其log值(因为它们本身的值可能很小,计算乘法可能下溢)。

"""
关键变量的尺寸,Y是标注空间的个数,K是特征函数的个数。
all_features:   len(x_vec) + 1, Y, Y, K
log_M_s:        len(x_vec) + 1, Y, Y
log_alphas:     len(x_vec) + 1, Y
log_betas:      len(x_vec) + 1, Y
log_probs:      len(x_vec) + 1, Y, Y
"""

MM:

log_M_s = np.dot(all_features, w)

前向向量初始化:

alpha = alphas[0]
alpha[start] = 0  # log1 = 0

前向向量的递推公式:

alphas[t] = log_dot_vm(alpha, log_M_s[t - 1])

后向向量的初始化:

beta = betas[-1]
beta[end] = 0  # log1 = 0

后向向量的递推公式:

betas[t] = log_dot_mv(log_M_s[t], beta)

其中:

def log_dot_vm(loga, logM):
    """通过log向量和log矩阵,计算log(向量^T 点乘 矩阵)"""
    return special.logsumexp(np.expand_dims(loga, axis=1) + logM, axis=0)


def log_dot_mv(logM, logb):
    """通过log向量和log矩阵,计算log(矩阵 点乘 向量)"""
    return special.logsumexp(logM + np.expand_dims(logb, axis=0), axis=1)

ZZ:

log_Z = special.logsumexp(last)

注:special.logsumexp函数等价于np.log(np.sum(np.exp(a), axis))

计算P(Yi−1=yi−1,Yi=yi|x)=αTi−1(yi−1|x)Mi(yi−1,yi|x)βi(yi|x)Z(x)P(Yi−1=yi−1,Yi=yi|x)=αi−1T(yi−1|x)Mi(yi−1,yi|x)βi(yi|x)Z(x):

log_alphas1 = np.expand_dims(log_alphas, axis=2)
log_betas1 = np.expand_dims(log_betas, axis=1)
log_probs = log_alphas1 + log_M + log_betas1 - log_Z

计算特征函数fkfk关于条件分布P(Y|X)P(Y|X)的期望:

exp_features = np.sum(np.exp(log_probs) * all_features, axis=(0, 1, 2))

特征函数fkfk关于联合分布P(X,Y)P(X,Y)的期望:

# y_vec = [START] + y_vec + [END]
yp_vec_ids = y_vec[:-1]
y_vec_ids = y_vec[1:]
emp_features = np.sum(all_features[range(length), yp_vec_ids, y_vec_ids], axis=0)

学习方法

给定训练数据集,估计条件随机场模型参数,即用极大似然法的方法估计参数。

这里学习的参数是ww,应该对比最大熵的学习算法,HMM的有监督学习的参数估计很简单,参数估计的是三元组概率矩阵。

改进的迭代尺度法

L(w)=LP~(Pw)=log∏x,yPw(y|x)P~(x,y)=∑x,yP~(x,y)logPw(y|x)=∑x,y[P~(x,y)∑k=1Kwkfk(x,y)−P~(x,y)logZw(x)]=∑j=1N∑k=1Kwkfk(yj,xj)−∑j=1NlogZw(xj)L(w)=LP~(Pw)=log∏x,yPw(y|x)P~(x,y)=∑x,yP~(x,y)logPw(y|x)=∑x,y[P~(x,y)∑k=1Kwkfk(x,y)−P~(x,y)logZw(x)]=∑j=1N∑k=1Kwkfk(yj,xj)−∑j=1NlogZw(xj)

改进的迭代尺度法引入参数向量的增量向量:δ=(δ1,...,δK)Tδ=(δ1,...,δK)T。

类似于最大熵的迭代尺度法,引入两个方程:

  • 关于转移特征的方程:∑x,yP~(x,y)∑n+1i=1tk(yi−1,yi,x,i)=∑x,yP~(x)P(y|x)∑n+1i=1tk(yi−1,yi,x,i)exp(δkT(x,y))k=1,2,...,K1∑x,yP~(x,y)∑i=1n+1tk(yi−1,yi,x,i)=∑x,yP~(x)P(y|x)∑i=1n+1tk(yi−1,yi,x,i)exp(δkT(x,y))k=1,2,...,K1
  • 关于状态特征的方程:∑x,yP~(x,y)∑n+1i=1sl(yi−1,yi,x,i)=∑x,yP~(x)P(y|x)∑n+1i=1sl(yi−1,yi,x,i)exp(δK1+lT(x,y))l=1,2,...,K2∑x,yP~(x,y)∑i=1n+1sl(yi−1,yi,x,i)=∑x,yP~(x)P(y|x)∑i=1n+1sl(yi−1,yi,x,i)exp(δK1+lT(x,y))l=1,2,...,K2
  • 其中:T(x,y)=∑kfk(y,x)=∑Kk=1∑n+1i=1fk(yi−1,yi,x,i)T(x,y)=∑kfk(y,x)=∑k=1K∑i=1n+1fk(yi−1,yi,x,i)是某数据(x,y)(x,y)出现的所有特征数的总和。

具体算法流程:

  • 输入:特征函数:t1,...,tK1t1,...,tK1,s1,...,sK2s1,...,sK2;经验分布P~(x,y)P~(x,y)。
  • 输出:参数估计值w^w^;模型Pw^Pw^。
  • 1.对于所有的k∈{1,2,...,K}k∈{1,2,...,K},取初始值wk=0wk=0
  • 2.对于每一k∈{1,2,...,K}k∈{1,2,...,K}:
    • a.当k=1,2,...,K1k=1,2,...,K1时,令δkδk是关于转移特征的方程的解;当k=K1+ll=1,...,K2k=K1+ll=1,...,K2时,令δkδk是关于状态特征的方程的解。
    • b.更新wkwk:wk←wk+δkwk←wk+δk

BFGS算法

梯度函数:g(w)=∑x,yP~(x)Pw(y|x)f(x,y)−EP~(f)g(w)=∑x,yP~(x)Pw(y|x)f(x,y)−EP~(f)

具体算法流程:

  • 输入:特征函数f1,...,fnf1,...,fn;经验分布P~(x,y)P~(x,y)。
  • 输出:参数估计值w^w^;模型Pw^Pw^。
  • 1.选定初始点w(0)w(0),取B0B0是正定对称矩阵,置k=0k=0。
  • 2.计算gk=g(w(k))gk=g(w(k)),若gk=0gk=0,则停止计算,否则转步骤3。
  • 3.由Bkpk=−gkBkpk=−gk,求出pkpk
  • 4.一维搜索:求λkλk使得:f(w(k)+λkpk)=minλ≥0f(w(k)+λpk)f(w(k)+λkpk)=minλ≥0f(w(k)+λpk)
  • 5.置gk+1=g(w(k+1))gk+1=g(w(k+1)),若gk=0gk=0,则停止计算;否则,求Bk+1Bk+1:Bk+1=Bk+yktTkyTkδk−BkδkδTkBkδtkBkδkBk+1=Bk+yktkTykTδk−BkδkδkTBkδktBkδk,其中,yk=gk+1−gkyk=gk+1−gk,δk=w(k+1)−w(k)δk=w(k+1)−w(k)
  • 7.置k=k+1k=k+1,转到步骤3。

关键代码:

似然函数:

likelihood += np.sum(log_M_s[range(length), yp_vec_ids, y_vec_ids]) - log_Z

训练,直接使用scipy中的optimize.fmin_l_bfgs_b去优化似然函数:

def train(self, x_vecs, y_vecs, debug=False):
    vectorised_x_vecs, vectorised_y_vecs = self.create_vector_list(x_vecs, y_vecs)
    l = lambda w: self.neg_likelihood_and_deriv(vectorised_x_vecs, vectorised_y_vecs, w)
    val = optimize.fmin_l_bfgs_b(l, self.w)
    if debug:
        print(val)
    self.w, _, _ = val
    return self.w

optimize.fmin_l_bfgs_b的第一个参数是被优化的目标函数,这个函数需要返回函数值和梯度值,梯度值的计算:

derivative += emp_features - exp_features

即特征关于模型的训练数据的期望和关于模型的期望的差。

预测算法

给定条件随机场P(Y|X)P(Y|X)和输入序列(观测序列)xx,求条件概率最大的输出序列(标记序列)y∗y∗,即,对观测序列进行标注。

类似于HMM,CRF也是采用维特比算法进行预测。

y∗=maxy(w⋅F(y,x))w=(w1,...,wK)TF(y,x)=(f1(y,x),...,fK(y,x))Tfk(y,x)=∑i=1nfk(yi−1,yi,x,i),k=1,2,...,Ky∗=maxy(w⋅F(y,x))w=(w1,...,wK)TF(y,x)=(f1(y,x),...,fK(y,x))Tfk(y,x)=∑i=1nfk(yi−1,yi,x,i),k=1,2,...,K

注意,这里只用计算非规范化概率,即不用计算配分函数ZZ,可以大大提高效率。

具体算法流程:

  • 输入:模型特征向量F(y,x)F(y,x)和权值向量ww,观测序列x=(x1,...,xn)x=(x1,...,xn);
  • 输出:最优路径y∗=(y∗1,y∗2,...,y∗n)y∗=(y1∗,y2∗,...,yn∗)
  • 1.初始化非规范化概率:δ1(j)=w⋅F1(y0=start,y1=j,x),j=1,...,mδ1(j)=w⋅F1(y0=start,y1=j,x),j=1,...,m
  • 2.递推:对i=1,2,...,ni=1,2,...,n:
    • δi(l)=max1≤j≤m{δi−1(j)+w⋅Fi(yi−1=j,yi=l,x)l=1,2,...,m}δi(l)=max1≤j≤m{δi−1(j)+w⋅Fi(yi−1=j,yi=l,x)l=1,2,...,m}
    • 对应的路径:Ψi(l)=argmax1≤j≤m{δi−1(j)+w⋅Fi(yi−1=j,yi=l,x)l=1,2,...,m}Ψi(l)=argmax1≤j≤m{δi−1(j)+w⋅Fi(yi−1=j,yi=l,x)l=1,2,...,m}
  • 3.终止:
    • maxy(w⋅F(y,x))=max1≤j≤mδn(j)maxy(w⋅F(y,x))=max1≤j≤mδn(j)
    • y∗n=argmax1≤j≤mδn(j)yn∗=argmax1≤j≤mδn(j)
  • 4.返回路径:y∗i=Ψi+1(y∗i+1),i=n−1,n−2,...,1yi∗=Ψi+1(yi+1∗),i=n−1,n−2,...,1

核心代码:

def predict(self, x_vec, debug=False):
    """给定x,预测y。使用Viterbi算法"""
    # all_features, len(x_vec) + 1, Y, Y, K
    all_features = self.get_all_features(x_vec)
    # log_potential: len(x_vec) + 1, Y, Y  保存各个下标的非规范化概率
    log_potential = np.dot(all_features, self.w)
    T = len(x_vec)
    Y = len(self.labels)
    # Psi保存每个时刻最优情况的下标
    Psi = np.ones((T, Y), dtype=np.int32) * -1
    # 初始化
    delta = log_potential[0, 0]
    # 递推
    for t in range(1, T):
        next_delta = np.zeros(Y)
        for y in range(Y):
            w = delta + log_potential[t, :, y]
            Psi[t, y] = psi = w.argmax()
            next_delta[y] = w[psi]
        delta = next_delta
    # 回溯找到最优路径
    y = delta.argmax()
    trace = []
    for t in reversed(range(T)):
        trace.append(y)
        y = Psi[t, y]
    trace.reverse()
    return [self.labels[i] for i in trace]

完整代码地址:https://github.com/applenob/simple_crf

猜你喜欢

转载自blog.csdn.net/QFire/article/details/86156664
今日推荐