由于keras中许多网络并不支持mask,但最近本菜需要在自己的网络中实现mask,如果想让keras中的自定义函数支持mask,就必须要实现comput_mask方法,因为在官方的文档中我并没有找到想要的东西,因此只能通过看源码的方式来学习,下面这一段就是keras中masking层的源码:
class Masking(Layer):
def __init__(self, mask_value=0., **kwargs):
super(Masking, self).__init__(**kwargs)
self.supports_masking = True
self.mask_value = mask_value
def compute_mask(self, inputs, mask=None):
output_mask = K.any(K.not_equal(inputs, self.mask_value), axis=-1)
return output_mask
def call(self, inputs):
boolean_mask = K.any(K.not_equal(inputs, self.mask_value),
axis=-1, keepdims=True)
return inputs * K.cast(boolean_mask, K.dtype(inputs))
def get_config(self):
config = {'mask_value': self.mask_value}
base_config = super(Masking, self).get_config()
return dict(list(base_config.items()) + list(config.items()))
def compute_output_shape(self, input_shape):
return input_shape
在compute_mask方法中,any函数实现的是按位规约,not_equal函数的功能正如其名,是逐个元素对比两个张量的不相等情况。
# x: 张量或变量 axis: 执行归约操作的轴 keepdims: 是否放弃或广播归约的轴。
keras.backend.any(x, axis=None, keepdims=False)
# x: 张量或变量 y: 张量或变量
keras.backend.not_equal(x, y)
这里面有几个疑惑,第一个是mask_value是一个常数,而inputs是一个张量,为什么这两者可以进行比较。我猜想是mask_value进行了扩展,简单测试如下:
from keras import backend as K
import tensorflow as tf
# inputs = K.constant([1,2,3])
inputs = K.constant([[1, 2, 3], [1, 2, 0], [0, 0, 0]])
mask_value = 0
output_mask = K.not_equal(inputs, mask_value)
print(output_mask)
with tf.Session() as sess:
print(output_mask.eval())
# 输出结果如下:
Tensor("NotEqual:0", shape=(3, 3), dtype=bool)
[[ True True True]
[ True True False]
[False False False]]
# 由此可以看出的确是进行了扩展
第二个疑惑是compute_mask()函数是向下传递mask,那么这个函数到底向下传递的是什么呢,从代码来看是对第一个维度进行了规约,我们也跑一下程序验证一下
from keras import backend as K
import tensorflow as tf
# inputs = K.constant([1,2,3])
inputs = K.constant([[1, 2, 3], [1, 0, 0]])
mask_value = 0
output_mask = K.any(K.not_equal(inputs, mask_value), axis=-1)
print(output_mask)
with tf.Session() as sess:
print(output_mask.eval())
# 输出结果如下:
Tensor("Any:0", shape=(3,), dtype=bool)
[ True True False]
这样一个(3,3)的矩阵被规约成了(1,3)的矩阵,第三个维度位False,也就表明了这个维度对应的特征将会被mask,这个矩阵也会作为mask的返回值传递下去。再来看一下官方给出的例子
model = Sequential()
model.add(Masking(mask_value=0., input_shape=(timesteps, features)))
model.add(LSTM(32))
这里masking层的输入是格式是input_shape=(timesteps, features),由刚才的测试结果可以得出,向下传递的mask维度应该是(1,timesteps),这样我们就可以根据传递下去的mask值来对LSTM层中的一些timesteps进行屏蔽。
综上,不那么严谨的得出结论,对于支持mask的网络层,在各层之间传递数据的同时,也传递着一个bool型的mask矩阵,矩阵中的True与False决定着对应位置的特征是否被屏蔽,并且这个矩阵的维度是(1,timesteps)。因此,在自定义函数中,我们要根据需求自己改变mask矩阵并继续传递下去,来作为下一层的mask,这里要注意的是,mask是不考虑batch_size的,也就是说我们只需要针对单个样本写好mask的传递函数就可以了。