CRF代码选自[https://spaces.ac.cn/archives/5542],大神编写,由于对代码解释没有特别清晰,对初学者可能有一定难度,本文对此代码做适当解析,帮助读者阅读。
如果阅读到此文,还望静下心来,认真思考,代码是经过测试,且认为相当简洁高效,才为大家做一个分享,目的在于减少各位看官查找无用博客而浪费时间。
以下为代码
# -*- coding:utf-8 -*-
from keras.layers import Layer
import keras.backend as K
class CRF(Layer):
"""纯Keras实现CRF层
CRF层本质上是一个带训练参数的loss计算层,因此CRF层只用来训练模型,
而预测则需要另外建立模型。
"""
def __init__(self, ignore_last_label=False, **kwargs):
"""ignore_last_label:定义要不要忽略最后一个标签,起到mask的效果
"""
self.ignore_last_label = 1 if ignore_last_label else 0
super(CRF, self).__init__(**kwargs)
def build(self, input_shape):
self.num_labels = input_shape[-1] - self.ignore_last_label
self.trans = self.add_weight(name='crf_trans',
shape=(self.num_labels, self.num_labels),
initializer='glorot_uniform',
trainable=True)
def log_norm_step(self, inputs, states):
"""递归计算归一化因子
要点:1、递归计算;2、用logsumexp避免溢出。
技巧:通过expand_dims来对齐张量。
"""
inputs, mask = inputs[:, :-1], inputs[:, -1:]
states = K.expand_dims(states[0], 2) # (batch_size, output_dim, 1)
trans = K.expand_dims(self.trans, 0) # (1, output_dim, output_dim)
outputs = K.logsumexp(states + trans, 1) # (batch_size, output_dim)
outputs = outputs + inputs
outputs = mask * outputs + (1 - mask) * states[:, :, 0]
return outputs, [outputs]
def path_score(self, inputs, labels):
"""计算目标路径的相对概率(还没有归一化)
要点:逐标签得分,加上转移概率得分。
技巧:用“预测”点乘“目标”的方法抽取出目标路径的得分。
"""
point_score = K.sum(K.sum(inputs * labels, 2), 1, keepdims=True) # 逐标签得分
labels1 = K.expand_dims(labels[:, :-1], 3)
labels2 = K.expand_dims(labels[:, 1:], 2)
labels = labels1 * labels2 # 两个错位labels,负责从转移矩阵中抽取目标转移得分
trans = K.expand_dims(K.expand_dims(self.trans, 0), 0)
trans_score = K.sum(K.sum(trans * labels, [2, 3]), 1, keepdims=True)
return point_score + trans_score # 两部分得分之和
def call(self, inputs): # CRF本身不改变输出,它只是一个loss
return inputs
def loss(self, y_true, y_pred): # 目标y_pred需要是one hot形式
if self.ignore_last_label:
mask = 1 - y_true[:, :, -1:]
else:
mask = K.ones_like(y_pred[:, :, :1])
y_true, y_pred = y_true[:, :, :self.num_labels], y_pred[:, :, :self.num_labels]
path_score = self.path_score(y_pred, y_true) # 计算分子(对数)
init_states = [y_pred[:, 0]] # 初始状态
y_pred = K.concatenate([y_pred, mask])
log_norm, _, _ = K.rnn(self.log_norm_step, y_pred[:, 1:], init_states) # 计算Z向量(对数)
log_norm = K.logsumexp(log_norm, 1, keepdims=True) # 计算Z(对数)
return log_norm - path_score # 即log(分子/分母)
def accuracy(self, y_true, y_pred): # 训练过程中显示逐帧准确率的函数,排除了mask的影响
mask = 1 - y_true[:, :, -1] if self.ignore_last_label else None
y_true, y_pred = y_true[:, :, :self.num_labels], y_pred[:, :, :self.num_labels]
isequal = K.equal(K.argmax(y_true, 2), K.argmax(y_pred, 2))
isequal = K.cast(isequal, 'float32')
if mask == None:
return K.mean(isequal)
else:
return K.sum(isequal * mask) / K.sum(mask)
class CRF(Layer) 继承keras.layers.Layer,继承后,重写build与call,通常对于call输入,第一次运行时会先加载build函数来指定输出维度,输入维度根据call(self, inputs)自动识别。
log_norm_step(self, inputs, states) 此函数,原作者是作为rnn函数的step_function输入,所以此时states是rnn模型的状态变量,inputs的shape为 (samples, …)的张量(没有时间维度),states是一个张量列表,最初输入值init_states,
**log_norm, _, _ = K.rnn(self.log_norm_step, y_pred[:, 1:], init_states)**
对于k.rnn函数
tf.keras.backend.rnn(
step_function,
inputs,
initial_states,
go_backwards=False,
mask=None,
constants=None,
unroll=False,
input_length=None,
time_major=False,
zero_output_for_mask=False
)
step_function:RNN步骤函数。参数:input:具有shape (samples, …)的张量(没有时间维度),表示在特定时间步骤的一批样品的输入。states:张量列表。返回:output:具有shape (samples, output_dim)的张量 (没有时间维度)。new_states:张量列表,长度和shape与“states”相同。列表中的第一个状态必须是前一个时间步的输出张量。
inputs:shape为(samples, time, …) (至少3D)的时间数据的张量,或嵌套张量,并且每个都具有shape (samples, time, …)。
initial_states:shape为(samples, state_size) 的张量(无时间维度),包含step函数中使用的状态的初始值。在state_size是嵌套形状的情况下,initial_states的形状也将遵循嵌套结构。
(以上解释为https://www.w3cschool.cn/tensorflow_python/tf_keras_backend_rnn.html复制)
运算为对于NLP领域,假设输入的shape为(samples, time,embedding),则rnn输出为每一个samples的embedding运行time次step_function所得出的last_output、outputs、new_states三个值。
last_output:shape为(samples, …) 输出的rnn的最新输出。
outputs:shape为(samples, time, …)的张量,其中每个条目 outputs[s, t] 是样本 s 在时间 t 的步骤函数输出值。
new_states:张量列表,步长函数返回的最新状态,shape为(samples, …)。
具体运算见代码:
batch_size = 2
time_step = 3
dim = 2
x = np.random.rand(batch_size, time_step, dim) # [2,3,2]生成输入
init_state = np.zeros(2).reshape(2, 1) # [2,1] 初始值设置为0
def step_func(inputs, states):
o = K.sum(inputs, axis=1, keepdims=True) + states[0]
return o, [o]
a,b,_ = K.rnn(step_func, inputs=x, initial_states=[init_state])
a:
<tf.Tensor: id=796, shape=(2, 1), dtype=float64, numpy=
array([[3.60335374],
[2.27873122]])>
b:
<tf.Tensor: id=798, shape=(2, 3, 1), dtype=float64, numpy=
array([[[0.97500236],
[2.7395605 ],
[3.60335374]],
[[0.41585083],
[1.04170515],
[2.27873122]]])>
x:
Out[206]:
array([[[0.14307762, 0.83192474],
[0.86293904, 0.90161909],
[0.04984334, 0.81394991]],
[[0.09448456, 0.32136627],
[0.54405903, 0.08179529],
[0.92263206, 0.314394 ]]])
以上为个人感觉难懂的点,对于函数内部计算,原文作者已经解释非常清晰,就不再赘述,建议先大致阅读本文,之后详细阅读原文,会有更清晰的认识。
如有错误,还请各位看官指出,会第一时间修改。