时序模型:线性条件随机场模型 ( Linear - CRF )

1. 线性条件随机场(Linear-CRF)

线性条件随机场(linear chain conditional random field,Linear-CRF)是对隐马尔科夫模型(hidden Markov model,HMM)在更普遍的实际问题上的推广1

隐马尔科夫(HMM)模型假设序列数据具有齐次马尔可夫性和观测独立性,这是对实际问题的两种近似简化。线性条件随机场(Linear-CRF)模型取消了观测独立性假设,并削弱了齐次马尔可夫性假设;Linear-CRF考虑一个模型每时刻隐藏状态,都是受其相邻时刻隐藏状态和全部时刻观测值共同影响的情况假设。即Linear-CRF模型中:
P ( y i ∣ X , y 1 , ⋯   , y i − 1 , y i + 1 , ⋯   , y n ) = P ( y i ∣ X , y i − 1 , y i + 1 ) (1.1) P(y_i|X, y_1, \cdots, y_{i-1}, y_{i+1}, \cdots, y_n) = P(y_i|X, y_{i-1}, y_{i+1}) \tag{1.1} P(yiX,y1,,yi1,yi+1,,yn)=P(yiX,yi1,yi+1)(1.1)

线性条件随机场(Linear-CRF)是一种概率无向图模型,在实际中一般假设输入序列X和标签序列Y具有相同的序列长度,其模型结构如下所示:
请添加图片描述

图1 X和Y具有相同图结构的Linear-CRF模型

图中每个节点都表示一个随机变量,每条边表示所连接的随机变量间的概率依赖关系。且无向边表示相连变量间有概率依赖关系,但并不一定是因果关系。

此无向图中,最大团(maximal clique)由每时刻隐藏状态、其前一时刻隐藏状态和同时刻观测值组成。所以根据 Hammersley-Clifford 定理,Linear-CRF模型的条件概率分布可表示为

P ( y ∣ X ) = e x p [ ∑ i ∈ N ∑ l ∈ K μ l ( i ) s l ( y i , x i ) + ∑ i ∈ N ∑ k ∈ K 2 λ k t k ( y i − 1 , y i , x i ) ] ∑ y ∈ Y e x p [ ∑ i ∈ N ∑ l ∈ K μ l ( i ) s l ( y i , x i ) + ∑ i ∈ N ∑ k ∈ K 2 λ k t k ( y i − 1 , y i , x i ) ] (1.2) P(y|X) = \frac{ exp[\sum_{i \in N}\sum_{l \in K} \mu_l^{(i)} s_l(y_i, x_i) + \sum_{i \in N}\sum_{k \in K^2} \lambda_k t_k(y_{i-1}, y_i, x_i)] }{ \sum_{y\in Y} exp[\sum_{i \in N}\sum_{l \in K} \mu_l^{(i)} s_l(y_i, x_i) + \sum_{i \in N}\sum_{k \in K^2} \lambda_k t_k(y_{i-1}, y_i, x_i)] } \tag{1.2} P(yX)=yYexp[iNlKμl(i)sl(yi,xi)+iNkK2λktk(yi1,yi,xi)]exp[iNlKμl(i)sl(yi,xi)+iNkK2λktk(yi1,yi,xi)](1.2)
式中,分子是状态序列 y y y 出现的可能性分数(下文简称:状态序列分数),分母是状态序列出现可能性分数的归一化项(下文简称:归一化项),二者相除等于状态序列出现的条件概率。 N N N序列长度 K K K 是所有可能的状态数量

s l ( y i , x i ) s_l(y_i, x_i) sl(yi,xi)状态特征,是定义在节点上的特征函数,它依赖于当前时刻隐藏状态和观测值,表示是否由当前时刻观察值发射(emit)到第 l l l 种隐藏状态(是:状态特征为1,否:状态特征为0); t k ( y i − 1 , y i , x i ) t_k(y_{i-1}, y_i, x_i) tk(yi1,yi,xi)转移特征,是定义在边上的特征函数,它依赖于前一时刻及当前时刻的隐藏状态,表示当前时刻是否由前一时刻隐藏状态转到第 k k k 种隐藏状态(是:转移特征为1,否:转移特征为0)。

μ l ( i ) \mu_l^{(i)} μl(i)发射分数(emission score),它表示由当前时刻观测值发射(emit)到第 l l l 种隐藏状态的可能性权重; λ k \lambda_k λk转移分数(transfer score),它表示由上一时刻隐藏状态转移到第 k k k 种隐藏状态的可能性权重;发射分数与转移分数之和,决定隐藏状态 y i y_i yi 出现的条件概率

2. 线性条件随机场(Linear-CRF)的矩阵形式

2.1 模型定义

为便于并行计算,根据上述定义可将 Linear-CRF 改写为矩阵形式

P ( y ∣ X ) = e x p [ E ( X ) ⋅ M E ( y ) + T ⋅ M T ( y ) ] ∑ y ∈ Y e x p [ E ( X ) ⋅ M E ( y ) + T ⋅ M T ( y ) ] (2.1.1) P(y|X) = \frac{ exp[E(X) \cdot M_{E}(y) + T \cdot M_{T}(y)] }{ \sum_{y\in Y} exp[E(X) \cdot M_{E}(y) + T \cdot M_{T}(y)] } \tag{2.1.1} P(yX)=yYexp[E(X)ME(y)+TMT(y)]exp[E(X)ME(y)+TMT(y)](2.1.1)

式中, X X X 是输入特征序列, y y y 是标签序列, Y Y Y 是所有可能的标签序列的集合; E ( X ) E(X) E(X) 是发射分数矩阵, M E ( y ) M_{E}(y) ME(y) 是发射分数掩码矩阵; T T T 是转移分数矩阵, M T ( y ) M_{T}(y) MT(y) 是转移分数掩码矩阵。

发射分数矩阵(emission matrix) E ( X ) E(X) E(X) 由输入特征的线性变换得到,其形状为 [ N , K ] [N, K] [N,K],它表示输入序列每时刻观测值发射到每种标签状态的可能性权重,表达式如下所示:

E ( X ) = X W E + b E (2.1.2) E(X) = XW_E + b_E \tag{2.1.2} E(X)=XWE+bE(2.1.2)

式中 W E W_E WE b E b_E bE 是可学习的模型参数。转移分数矩阵(transfer matrix) T T T 由可学习的模型参数得到,其形状为 [ K , K ] [K, K] [K,K],它表示每种标签类别转移到任意另一种类别的可能性权重。

模型训练时:发射分数掩码矩阵(emission mask matrix) M E ( y ) M_{E}(y) ME(y) 由标签序列的 one-hot 编码得到,其形状为 [ N , K ] [N, K] [N,K],它表示每时刻生效的发射分数类别。转移分数掩码矩阵(transfer mask matrix) M T ( y ) M_{T}(y) MT(y) 由标签序列相邻时刻状态的二维 one-hot 编码得到,其形状为 [ N , K , K ] [N, K, K] [N,K,K],它表示每时刻生效的转移分数类别。

模型预测时:根据发射分数矩阵和转移分数矩阵,直接由维特比算法解码(decoder)出预测标签序列,不需要计算发射分数掩码矩阵和转移分数掩码矩阵。

2.2 模型参数优化

Linear-CRF是一种判别式概率模型,其参数优化方法是针对目标函数的极大似然估计。判别模型的目的是最大化对所有样本标签预测正确的概率,所以Linear-CRF模型的目标函数是 m a x   ∏ i = 1 m P ( y i ∣ X i ) max\ \prod_{i=1}^{m} P(y_i|X_i) max i=1mP(yiXi),其中 m m m 是小批量样本的批量大小。由此我们可简单地设定:Linear-CRF模型的损失函数等于模型预测样本标签出现的条件概率的负数

那么根据极大似然估计法,模型参数的估计为

θ ^ = a r g m i n   − ∏ i = 1 m P ( y i ∣ X i ) θ ^ = a r g m i n   − l o g ( ∏ i = 1 m P ( y i ∣ X i ) ) θ ^ = a r g m i n   − ∑ i = 1 m l o g ( P ( y i ∣ X i ) ) θ ^ = a r g m i n   − ∑ i = 1 m l o g ( e x p [ E ( X i ) ⋅ M E ( y i ) + T ⋅ M T ( y i ) ] ∑ y i ∈ Y e x p [ E ( X i ) ⋅ M E ( y i ) + T ⋅ M T ( y i ) ] ) (2.2.1) \begin{matrix} \hat{\theta} = argmin \ -\prod_{i=1}^{m} P(y_i|X_i) \\\\ \hat{\theta} = argmin \ -log(\prod_{i=1}^{m} P(y_i|X_i)) \\\\ \hat{\theta} = argmin \ -\sum_{i=1}^{m} log(P(y_i|X_i)) \\\\ \hat{\theta} = argmin \ -\sum_{i=1}^{m} log(\frac{ exp[E(X_i) \cdot M_{E}(y_i) + T \cdot M_{T}(y_i)] }{ \sum_{y_i \in Y} exp[E(X_i) \cdot M_{E}(y_i) + T \cdot M_{T}(y_i)] }) \end{matrix} \tag{2.2.1} θ^=argmin i=1mP(yiXi)θ^=argmin log(i=1mP(yiXi))θ^=argmin i=1mlog(P(yiXi))θ^=argmin i=1mlog(yiYexp[E(Xi)ME(yi)+TMT(yi)]exp[E(Xi)ME(yi)+TMT(yi)])(2.2.1)

设:一种隐藏状态序列出现的可能性权重分数(Score)为 S i ( y i ) = E ( X i ) ⋅ M E ( y i ) + T ⋅ M T ( y i ) S_i^{(y_i)} = E(X_i) \cdot M_{E}(y_i) + T \cdot M_{T}(y_i) Si(yi)=E(Xi)ME(yi)+TMT(yi)。将其带入上式后,Linear-CRF模型参数估计的表达式可进一步简化为:
θ ^ = a r g m i n   ∑ i = 1 m [ l o g ( ∑ y i ∈ Y e S i ( y i ) ) − S i ( y i ) ] (2.2.2) \hat{\theta} = argmin\ \sum_{i=1}^{m} [log(\sum_{y_i\in Y} e^{S_i^{(y_i)}}) - S_i^{(y_i)}] \tag{2.2.2} θ^=argmin i=1m[log(yiYeSi(yi))Si(yi)](2.2.2)

进而模型的损失函数为:
L o s s = 1 m ∑ i = 1 m [ l o g ( ∑ y i ∈ Y e S i ( y i ) ) − S i ( y i ) ] (2.2.3) Loss = \frac{1}{m}\sum_{i=1}^{m} [log(\sum_{y_i\in Y} e^{S_i^{(y_i)}}) - S_i^{(y_i)}] \tag{2.2.3} Loss=m1i=1m[log(yiYeSi(yi))Si(yi)](2.2.3)

式中,归一化项 l o g ( ∑ y i ∈ Y e S i ( y i ) ) log(\sum_{y_i\in Y} e^{S_i^{(y_i)}}) log(yiYeSi(yi)) 不能直接穷举计算,因为 S i ( y i ) S_i^{(y_i)} Si(yi) 共有 K N K^N KN 种可能的情况,N为序列长度。本文设计了一种基于动态规划的高效计算方法,借助矩阵运算,可将归一化项计算的时间复杂度从 O ( K n ) O(K^n) O(Kn) 降低到 O ( n ) O(n) O(n)

本方法利用指数函数性质,将累加所有可能的状态序列分数(即: ∑ y i ∈ Y e S i ( y i ) \sum_{y_i\in Y} e^{S_i^{(y_i)}} yiYeSi(yi))转化为一个动态规划问题。

首先,我们设:

  • i i i 个样本序列 t t t 时刻,发射到状态 k k k 的发射分数记作 e m i t ( i , t , k ) emit_{(i, t, k)} emit(i,t,k)
  • i i i 个样本序列 t t t 时刻,从状态 j j j 转移到状态 k k k 的转移路径分数记作 R ( i , t , j , k ) R_{(i, t, j, k)} R(i,t,j,k)
  • i i i 个样本序列 t t t 时刻,通向状态 k k k 的所有状态路径的分数的指数之和记作 S ( i , t , k ) S_{(i, t, k)} S(i,t,k)
  • 任意样本序列的任意时刻,从状态 l l l 转移到状态 k k k 的转移分数记作 t r a n s ( l , k ) trans_{(l,k)} trans(l,k)

下图中的每一个指向箭头(黑色或灰色),都是一条可能的状态路径route)。

在这里插入图片描述

图2 Linear-CRF的状态路径

所以动态规划算法的初始状态和状态转移方程为:
{ 初 始 状 态 : S 1 = E t = 1 = [ [ e m i t ( 1 , 1 , 1 ) ⋯ e m i t ( 1 , 1 , K ) ] ⋮ [ e m i t ( M , 1 , 1 ) ⋯ e m i t ( M , 1 , K ) ] ] = [ [ S ( 1 , 1 , 1 ) ⋯ S ( 1 , 1 , K ) ] ⋮ [ S ( M , 1 , 1 ) ⋯ S ( M , 1 , K ) ] ] , S 1 ∈ R M × 1 × K 状 态 路 径 集 合 : R t = T + E t = [ [ t r a n s ( 1 , 1 ) + e m i t ( 1 , t , 1 ) ⋯ t r a n s ( 1 , K ) + e m i t ( 1 , t , K ) ⋮ ⋱ ⋮ t r a n s ( K , 1 ) + e m i t ( 1 , t , 1 ) ⋯ t r a n s ( K , K ) + e m i t ( 1 , t , K ) ] ⋮ [ t r a n s ( 1 , 1 ) + e m i t ( M , t , 1 ) ⋯ t r a n s ( 1 , K ) + e m i t ( M , t , K ) ⋮ ⋱ ⋮ t r a n s ( K , 1 ) + e m i t ( M , t , 1 ) ⋯ t r a n s ( K , K ) + e m i t ( M , t , K ) ] ] = [ [ R ( 1 , t , 1 , 1 ) ⋯ R ( 1 , t , 1 , K ) ⋮ ⋱ ⋮ R ( 1 , t , K , 1 ) ⋯ R ( 1 , t , K , K ) ] ⋮ [ R ( M , t , 1 , 1 ) ⋯ R ( M , t , 1 , K ) ⋮ ⋱ ⋮ R ( M , t , K , 1 ) ⋯ R ( M , t , K , K ) ] ] , R t ∈ R M × K × K ,    t = 2 , ⋯   , N 状 态 转 移 方 程 : S t = l o g [ ∑ k ∈ K e ( S t − 1 + R t T ) ] = l o g ( ∑ [ [ e ( S ( 1 , t − 1 , 1 ) + R ( 1 , t , 1 , 1 ) ) ⋯ e ( S ( 1 , t − 1 , K ) + R ( 1 , t , K , 1 ) ) ⋮ ⋱ ⋮ e ( S ( 1 , t − 1 , 1 ) + R ( 1 , t , 1 , K ) ) ⋯ e ( S ( 1 , t − 1 , K ) + R ( 1 , t , K , K ) ) ] ⋮ [ e ( S ( M , t − 1 , 1 ) + R ( M , t , 1 , 1 ) ) ⋯ e ( S ( M , t − 1 , K ) + R ( M , t , K , 1 ) ) ⋮ ⋱ ⋮ e ( S ( M , t − 1 , 1 ) + R ( M , t , 1 , K ) ) ⋯ e ( S ( M , t − 1 , K ) + R ( M , t , K , K ) ) ] ] ) ,    S t ∈ R M × 1 × K ,    t = 2 , ⋯   , N \begin{cases} 初始状态: & S_{1} = E_{t=1} = \begin{bmatrix} \begin{bmatrix} emit_{(1, 1, 1)} & \cdots & emit_{(1, 1, K)} \end{bmatrix} \\ \vdots \\ \begin{bmatrix} emit_{(M, 1, 1)} & \cdots & emit_{(M, 1, K)} \end{bmatrix} \end{bmatrix}= \begin{bmatrix} \begin{bmatrix} S_{(1, 1, 1)} & \cdots & S_{(1, 1, K)} \end{bmatrix} \\ \vdots \\ \begin{bmatrix} S_{(M, 1, 1)} & \cdots & S_{(M, 1, K)} \end{bmatrix} \end{bmatrix} , & S_1 \in R^{M \times 1 \times K} \\\\\\ 状态路径集合: & R_t = T + E_t = \begin{bmatrix} \begin{bmatrix} trans_{(1,1)}+emit_{(1, t, 1)} & \cdots & trans_{(1,K)}+emit_{(1, t, K)} \\ \vdots & \ddots & \vdots \\ trans_{(K,1)}+emit_{(1, t, 1)} & \cdots & trans_{(K,K)}+emit_{(1, t, K)} \end{bmatrix} \\ \vdots \\ \begin{bmatrix} trans_{(1,1)}+emit_{(M, t, 1)} & \cdots & trans_{(1,K)}+emit_{(M, t, K)} \\ \vdots & \ddots & \vdots \\ trans_{(K,1)}+emit_{(M, t, 1)} & \cdots & trans_{(K,K)}+emit_{(M, t, K)} \end{bmatrix} \end{bmatrix}= \begin{bmatrix} \begin{bmatrix} R_{(1,t,1,1)} & \cdots & R_{(1,t,1,K)} \\ \vdots & \ddots & \vdots \\ R_{(1,t,K,1)} & \cdots & R_{(1,t,K,K)} \end{bmatrix} \\ \vdots \\ \begin{bmatrix} R_{(M,t,1,1)} & \cdots & R_{(M,t,1,K)} \\ \vdots & \ddots & \vdots \\ R_{(M,t,K,1)} & \cdots & R_{(M,t,K,K)} \end{bmatrix} \end{bmatrix} , & R_t \in R^{M \times K \times K},\ \ t = 2, \cdots, N \\\\\\ 状态转移方程: & S_t = log[\sum_{k \in K} e^{(S_{t-1} + R_t^T)}]= log(\sum \begin{bmatrix} \begin{bmatrix} e^{(S_{(1, t-1, 1)}+R_{(1,t,1,1)})} & \cdots & e^{(S_{(1, t-1, K)}+R_{(1,t,K,1)})} \\ \vdots & \ddots & \vdots \\ e^{(S_{(1, t-1, 1)}+R_{(1,t,1,K)})} & \cdots & e^{(S_{(1, t-1, K)}+R_{(1,t,K,K)})} \end{bmatrix} \\ \vdots \\ \begin{bmatrix} e^{(S_{(M, t-1, 1)}+R_{(M,t,1,1)})} & \cdots & e^{(S_{(M, t-1, K)}+R_{(M,t,K,1)})} \\ \vdots & \ddots & \vdots \\ e^{(S_{(M, t-1, 1)}+R_{(M,t,1,K)})} & \cdots & e^{(S_{(M, t-1, K)}+R_{(M,t,K,K)})} \end{bmatrix} \end{bmatrix}) , & \ \ S_t \in R^{M \times 1 \times K},\ \ t = 2, \cdots, N \end{cases} S1=Et=1=[emit(1,1,1)emit(1,1,K)][emit(M,1,1)emit(M,1,K)]=[S(1,1,1)S(1,1,K)][S(M,1,1)S(M,1,K)],Rt=T+Et=trans(1,1)+emit(1,t,1)trans(K,1)+emit(1,t,1)trans(1,K)+emit(1,t,K)trans(K,K)+emit(1,t,K)trans(1,1)+emit(M,t,1)trans(K,1)+emit(M,t,1)trans(1,K)+emit(M,t,K)trans(K,K)+emit(M,t,K)=R(1,t,1,1)R(1,t,K,1)R(1,t,1,K)R(1,t,K,K)R(M,t,1,1)R(M,t,K,1)R(M,t,1,K)R(M,t,K,K),St=log[kKe(St1+RtT)]=log(e(S(1,t1,1)+R(1,t,1,1))e(S(1,t1,1)+R(1,t,1,K))e(S(1,t1,K)+R(1,t,K,1))e(S(1,t1,K)+R(1,t,K,K))e(S(M,t1,1)+R(M,t,1,1))e(S(M,t1,1)+R(M,t,1,K))e(S(M,t1,K)+R(M,t,K,1))e(S(M,t1,K)+R(M,t,K,K))),S1RM×1×KRtRM×K×K,  t=2,,N  StRM×1×K,  t=2,,N
归一化项等于:
l o g ( ∑ y i ∈ Y e S i ( y i ) ) = l o g ( ∑ k ∈ K S ( i , N , k ) ) (2.2.5) log(\sum_{y_i\in Y} e^{S_i^{(y_i)}}) = log(\sum_{k \in K} S_{(i,N, k)}) \tag{2.2.5} log(yiYeSi(yi))=log(kKS(i,N,k))(2.2.5)

综上所述,损失函数表达式为:
L o s s = 1 m ∑ i = 1 m [ l o g ( ∑ k ∈ K S ( i , N , k ) ) − S i ( y i ) ] (2.2.3) Loss = \frac{1}{m}\sum_{i=1}^{m} [log(\sum_{k \in K} S_{(i,N, k)}) - S_i^{(y_i)}] \tag{2.2.3} Loss=m1i=1m[log(kKS(i,N,k))Si(yi)](2.2.3)

2.3 模型预测

每时刻发射分数为节点权重,以转移分数为节点连接边权重,使用维特比算法找出分数最高的状态路径(route)。如下图所示:

在这里插入图片描述

图3 Linear-CRF解码过程

设:第 i i i 个样本序列截至 t t t 时刻的最佳状态路径(出现可能性最大的状态路径)分数记作 s ( i , t ) s_{(i, t)} s(i,t)。则,Linear-CRF解码过程的初始状态和状态转移方程为:
{ 初 始 状 态 : y ^ 1 ( i ) = a r g m a x k ∈ K    e m i t ( i , 1 , k ) ,     s ( i , 1 ) = m a x k ∈ K    e m i t ( i , 1 , k ) 状 态 转 移 方 程 : y ^ t ( i ) = a r g m a x k ∈ K    s ( i , t − 1 ) + t r a n s ( y ^ t − 1 ( i ) , k ) + e m i t ( i , t − 1 , k ) ,   t = 2 , … , N (2.2.6) \begin{cases} 初始状态:& \hat{y}_1^{(i)} = \underset{k \in K}{argmax} \ \ emit_{(i, 1, k)},\ \ \ s_{(i, 1)} = \underset{k \in K}{max} \ \ emit_{(i, 1, k)} \\\\ 状态转移方程:& \hat{y}_t^{(i)} = \underset{k \in K}{argmax} \ \ s_{(i, t-1)} + trans_{(\hat{y}_{t-1}^{(i)}, k)}+emit_{(i,t-1, k)}, & \ t=2,\dots,N \end{cases} \tag{2.2.6} y^1(i)=kKargmax  emit(i,1,k),   s(i,1)=kKmax  emit(i,1,k)y^t(i)=kKargmax  s(i,t1)+trans(y^t1(i),k)+emit(i,t1,k), t=2,,N(2.2.6)

3. 线性条件随机场(Linear-CRF)的工程实现

Linear-CRF模型的结构如下图所示:

在这里插入图片描述

图4 Linear-CRF模型结构图

3.1 TensorFlow框架的代码实现

"""
V2.0 此版本在CRF的发射分数计算和损失函数计算中加入了mask机制,可有效去除padding值引起的噪声干扰。 2022.4.10
V1.0 Baseline版本 2022.3.28
"""
# %%
import numpy as np
from tqdm import tqdm
import tensorflow as tf
from tensorflow.keras import layers, losses
from tensorflow.keras.optimizers import SGD, Adam
import tensorflow_addons as tfa

gpus = tf.config.experimental.list_physical_devices(device_type="GPU")
tf.config.experimental.set_visible_devices(devices=gpus[0], device_type="GPU")


class LinerCRFlayer(layers.Layer):
    def __init__(self, num_labels, **kwargs):
        super().__init__(**kwargs)
        self.num_labels = num_labels
        self.Emit = layers.Dense(num_labels)

    def build(self, batch_input_shape):
        self.transform = self.add_weight(
            name='transform',
            shape=[self.num_labels, self.num_labels],
            initializer='glorot_normal'
        )
        super().build(batch_input_shape)

    def call(self, inputs, mask, training):
        """
        :param:
        inputs is the feature sequence, its' shape is [batch_size, time_steps, hidden_num].
        mask is the mask matrix of inputs, its' shape is [batch_size, time_steps].
        emit is the emit score matrix, its' shape is [batch_size, time_steps, num_labels].
        transform is the transform score matrix, its' shape is [num_labels, num_labels].
        transform_i is the transform score vector of label state at time i-1, its' shape is [batch_size, 1, num_labels].

        :return:
        if training is True, return the emit score matrix of inputs and transfer score matrix;
        if training is False, return the label sequence decoded by Viterbi algorithm.
        """
        # 发射矩阵的padding噪声不用在输入端滤除,
        # 而是在训练阶段的损失函数计算和预测阶段的维特比结果选择中滤除。
        emit = self.Emit(inputs)
        if training:
            return [emit, self.transform]
        else:
            """维特比寻找最佳标注路径"""
            y = []
            # 初始状态赋值
            emit_i = emit[:, :1, :]
            y.append(tf.argmax(emit_i, axis=-1))
            Score = tf.reduce_max(emit_i, axis=-1)
            # 状态转移方程
            for i in range(1, inputs.shape[1], 1):
                y_onehot = tf.one_hot(y[i - 1], self.num_labels)
                transform_i = tf.matmul(y_onehot, self.transform)
                Score = tf.expand_dims(Score, axis=-1) + transform_i + emit[:, i:i + 1, :]
                # 记录本轮状态
                y.append(tf.argmax(Score, axis=-1))
                Score = tf.reduce_max(Score, axis=-1)
            return tf.concat(y, axis=-1)


#
class LinerCRF(tf.keras.Model):
    def __init__(self, input_block, hidden_block, num_labels):
        super().__init__()
        self.num_labels = num_labels
        # model define
        self.Input = input_block
        self.Hiddens = hidden_block
        self.Output = LinerCRFlayer(self.num_labels)
        # compile
        self.optimizer = None
        self.loss_fun = None
        self.metric = None

    def call(self, X, mask, num_mask, training):
        """
        mask 是布尔型掩码矩阵,元素值为 True 或 False
        num_mask 是浮点型掩码矩阵,元素值为 1. 或 0.
        """
        hiddens = [self.Input(X, num_mask)['last_hidden_state']]
        for Hidden in self.Hiddens:
            hiddens = Hidden(hiddens[0], mask=mask, training=training)
        outputs = self.Output(hiddens[0], num_mask, training=training)
        return outputs

    def compile(self, optimizer=Adam(), loss=None, metrics=None, **kwargs):
        if loss is None:
            raise ValueError("Please give the loss function!")
        self.optimizer = optimizer
        self.loss_fun = loss

    def train(self, train_generator, epochs=10, verbose=100,  gard_theta=1, pad_id=0):
        """
        tensorflow原生LSTM/GRU层的mask接口要求是布尔矩阵(True/False),而其他层的mask接口要求是01矩阵(0./1.);
        所以有 num_mask 转换。
        """
        loss_pre_epoch = []
        for epoch in range(epochs):
            loss_pre_batch = []
            for i, train_data in enumerate(tqdm(list(train_generator), desc='step')):
                X, y, mask, num_mask = train_data
                with tf.GradientTape(persistent=True) as tape:
                    emit, transform = self(X, mask, num_mask, training=True)
                    loss = self.loss_fun(emit, transform, y, num_mask)
                # 参数更新
                train_varb = self.trainable_variables
                del train_varb[197:199]    # 删除不使用的BERT模型pool层参数
                grads = tape.gradient(loss, train_varb)
                grads = self.grad_clipping(grads, gard_theta)  # 梯度裁剪
                self.optimizer.apply_gradients(zip(grads, train_varb))
                # record
                loss_pre_batch.append(loss.numpy())
                if i % verbose == 0:
                    print('step ', i, ' ', 'loss:', np.mean(loss_pre_batch))
            loss_pre_epoch.append(np.mean(loss_pre_batch))
            print('epoch ', epoch, ' ', 'loss:', np.mean(loss_pre_batch), '\n')
        return loss_pre_epoch

    #
    def predict(self, X):
        y_hat = self(X, training=False)
        return y_hat

    # 梯度裁剪
    def grad_clipping(self, grads, theta):
        """
        梯度裁剪
        """
        theta = tf.constant(theta, dtype=tf.float32)
        new_grads = [tf.convert_to_tensor(grad) if isinstance(grad, tf.IndexedSlices) else grad for grad in grads]
        norm = tf.math.sqrt(sum(tf.reduce_sum(grad ** 2).numpy() for grad in new_grads))
        norm = tf.cast(norm, tf.float32)
        if tf.greater(norm, theta):
            new_grads = [(grad * theta) / norm for grad in new_grads]
        return new_grads

    def CRFLossFun(self, emit, transform, y, num_mask):
        """
        **Param
        :emit:          emit is input sequences, and its' shape is [batch_size, time_steps, num_labels]
        :transform:     transform is transform score, and its' shape is [num_labels, num_labels]
        :y:             y is label sequences, and its' shape is [batch_size, time_steps]
        :y_len:         y_len is one-dim tensor and its' shape is [batch_size]
        **备注
        输入序列padding噪声包含在emit矩阵的对映时间步位置中;
        标签序列padding噪声包含在num_mask矩阵( MEy & MTy )的对映时间步位置中;
        经过矩阵数乘,两类padding噪声积累在 Ey 和 Ty 矩阵的对映时间步位置中,通过 ME 和 MT 掩码矩阵的过滤,可全完去除对映时间步位置中的padding噪声(两类噪声都被除去)。
        y 矩阵 time_steps 的尾端,包含标签序列的 padding 噪声。
        **Output
        :return:
        """
        ## 计算标签序列的分数
        # 计算标签序列的发射分数
        MEy = tf.one_hot(y, self.num_labels)   # 引入标签序列padding噪声,包含在num_mask矩阵(MEy)对映时间步位置的元素中
        Ey = tf.reduce_sum(emit * MEy, axis=[-1])
        # 计算标签序列的转移分数
        yts1 = tf.expand_dims(tf.one_hot(y[:, :y.shape[-1] - 1], self.num_labels), axis=-2)
        yt = tf.expand_dims(tf.one_hot(y[:, 1:], self.num_labels), axis=-2)
        MTy = tf.matmul(tf.transpose(yts1, [0, 1, 3, 2]), yt)
        Ty = tf.reduce_sum(MTy * transform, axis=[-1, -2])  # 引入标签序列padding噪声,包含在num_mask矩阵(MTy)对映时间步位置的元素中
        # 汇总标签序列分数,并去除标签序列中的padding噪声;
        # ME = self.generate_num_mask(Ey, y_len);
        # MT = self.generate_num_mask(Ty, y_len - 1).
        yScore = tf.reduce_sum(Ey * num_mask, axis=-1) + tf.reduce_sum(Ty * num_mask[:, 1:], axis=-1)

        ## 输入序列所有可能的状态序列的分数之和
        allScore = tfa.text.crf_log_norm(emit, tf.reduce_sum(num_mask, axis=-1), transform)

        ## 计算损失函数值
        return tf.reduce_mean(allScore - yScore)


  1. 详细演进推导过程可参考文章Linear-Chain-CRF 的前世今生 ↩︎

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值