keras/layers/core.py
中。call()
函数如下,call函数中写具体逻辑,可以看到逻辑非常简单,就是做一个点乘,有bias加bias,有activation做activation。self.kernel
的初始化在函数build()
里。 def call(self, inputs):
output = K.dot(inputs, self.kernel)
if self.use_bias:
output = K.bias_add(output, self.bias, data_format='channels_last')
if self.activation is not None:
output = self.activation(output)
return output
build()
函数如下。其中对self.kernel
做了初始化,其shape为input_shape[-1], self_units
, self_units
就是创建Dense对象时传进来的参数。自己从numpy写神经网络,或者使用框架时都非常需要ndarray要有一个维度是表示batch,这是我容易忽略的。shape = (input_dim, self.units)
。 def build(self, input_shape):
assert len(input_shape) >= 2
input_dim = input_shape[-1]
self.kernel = self.add_weight(shape=(input_dim, self.units),
initializer=self.kernel_initializer,
name='kernel',
regularizer=self.kernel_regularizer,
constraint=self.kernel_constraint)
if self.use_bias:
self.bias = self.add_weight(shape=(self.units,),
initializer=self.bias_initializer,
name='bias',
regularizer=self.bias_regularizer,
constraint=self.bias_constraint)
else:
self.bias = None
self.input_spec = InputSpec(min_ndim=2, axes={-1: input_dim})
self.built = True
可以注意到除了最后两行,其他代码都很清楚,self.built = True
是必须要写的,大概是表示分配空间,调用super(MyLayer, self).build(input_shape)
也是可以的,Layer类中的build()
函数里就一句self.built = True
。
InputSpec()
类可以看一下其源码,在engine/base_layer.py
中,它有一个__repr__
函数,是python类自带的一个方法,是用来显示的,print该类的对象时就会调用__repr__
函数,也就是我们可以这样,print(model.layer[2].input_spec)
查看当前层的dtype,shape等信息。但是print(model.summary)
更好用,能看到model结构更详细的信息,以及每层之间怎么连接的。
compute_output_shape()
如下。可以看到这里是计算输出的shape, keras中间结果为向量时,shape为(None, dim)
,类的说明写了,shape[0]
表示batch维度(即batch_size),这里写None
表示与任何数值兼容。 # now: model.output_shape == (None, 32)
# note: `None` is the batch dimension
显然全连接层的输出shape为(None, self.units)
def compute_output_shape(self, input_shape):
assert input_shape and len(input_shape) >= 2
assert input_shape[-1]
output_shape = list(input_shape)
output_shape[-1] = self.units
return tuple(output_shape)
attention机制的权重怎么来的用红框标出来了,可以看到 e i j e_{ij} eij是通过 s i − 1 , h j s_{i-1}, h_j si−1,hj得出的,论文里也说了这种机制就是基于encoder-decoder的,论文里 h j h_j hj成为annotation, s i s_i si称为hidden state,其实 s i s_i si就是decoder的第i个隐状态, h j h_j hj就是encoder的第j个隐状态。注意这里 s i s_i si也和 y i − 1 y_{i-1} yi−1有关,但是图里没画出来,论文的附录里有讲具体推导。
s i − 1 s_{i-1} si−1和 h j h_j hj都是向量,向量 s i − 1 s_{i-1} si−1显然等于decoder的每个timestep的输出 y i y_i yi维度, h j h_j hj的维度则是手动设置的RNN的hidden units的维度。
这篇文章里分层,也就是先用词向量训练句向量,然后再对句向量用GRU的意义在哪啊? 我感觉直接用词向量训练,然后attention应该也没差,文章里也没说这样做的目的,也可能是我看的还不够仔细。我觉得这篇文章的结果好,可能是因为直接用词向量进行分类,输入序列较长;但是用sentence representation进行分类,输入序列长度较短,此时document representation能够保留更多的信息,同时sentence representation是用词向量训练得到的,每个句子的长度也有限,也帮助保留了信息。可能这才是Hierarchical的意义所在吧。
(等等,我为什么想到了InfoGAN能够查看随机变量的每个维度对生成的复杂样本的影响,是否和attention有什么共通的地方)
# Output shape
- if `return_state`: a list of tensors. The first tensor is
the output. The remaining tensors are the last states,
each with shape `(batch_size, units)`.
- if `return_sequences`: 3D tensor with shape
`(batch_size, timesteps, units)`.
- else, 2D tensor with shape `(batch_size, units)`.
return_state
这个参数不用在意,对单层RNN没影响(看了源码,但是没测试),因为其hidden state等于输出(这里单指RNN作为模型中一个组件的输出,不加softmax之类的函数)。
显然用attention时需要return_sequences = True
。
参考代码:
class Attention(Layer):
'''
返回值:
返回的不是attention权重,而是每个timestep乘以权重后相加得到的向量。
输入:
输入是rnn的timesteps,也是最长输入序列的长度。keras
'''
def __init__(self, step_dim,
W_regularizer=None, b_regularizer=None,
W_constraint=None, b_constraint=None,
bias=True, **kwargs):
self.supports_masking = True
self.init = initializers.get('glorot_uniform')
self.W_regularizer = regularizers.get(W_regularizer)
self.b_regularizer = regularizers.get(b_regularizer)
self.W_constraint = constraints.get(W_constraint)
self.b_constraint = constraints.get(b_constraint)
self.bias = bias
self.step_dim = step_dim
self.features_dim = 0
super(Attention, self).__init__(**kwargs)
def build(self, input_shape):
assert len(input_shape) == 3
self.W = self.add_weight((input_shape[-1],),
initializer=self.init,
name='{}_W'.format(self.name),
regularizer=self.W_regularizer,
constraint=self.W_constraint)
self.features_dim = input_shape[-1]
if self.bias:
self.b = self.add_weight((input_shape[1],),
initializer='zero',
name='{}_b'.format(self.name),
regularizer=self.b_regularizer,
constraint=self.b_constraint)
else:
self.b = None
self.built = True
def compute_mask(self, input, input_mask=None):
# 后面的层不需要mask了,所以这里可以直接返回none
return None
def call(self, x, mask=None):
features_dim = self.features_dim
# 这里应该是 step_dim是我们指定的参数,它等于input_shape[1],也就是rnn的timesteps
step_dim = self.step_dim
# 输入和参数分别reshape再点乘后,tensor.shape变成了(batch_size*timesteps, 1),之后每个batch要分开进行归一化
# 所以应该有 eij = K.reshape(..., (-1, timesteps))
eij = K.reshape(K.dot(K.reshape(x, (-1, features_dim)),
K.reshape(self.W, (features_dim, 1))), (-1, step_dim))
if self.bias:
eij += self.b
# RNN一般默认激活函数为tanh, 对attention来说激活函数差别不打,因为要做softmax
eij = K.tanh(eij)
a = K.exp(eij)
if mask is not None:
# 如果前面的层有mask,那么后面这些被mask掉的timestep肯定是不能参与计算输出的,也就是将他们的attention权重设为0
a *= K.cast(mask, K.floatx())
# cast是做类型转换,keras计算时会检查类型,可能是因为用gpu的原因
a /= K.cast(K.sum(a, axis=1, keepdims=True) + K.epsilon(), K.floatx())
# a = K.expand_dims(a, axis=-1) , axis默认为-1, 表示在最后扩充一个维度。
# 比如shape = (3,)变成 (3, 1)
a = K.expand_dims(a)
# 此时a.shape = (batch_size, timesteps, 1), x.shape = (batch_size, timesteps, units)
weighted_input = x * a
# weighted_input的shape为 (batch_size, timesteps, units), 每个timestep的输出向量已经乘上了该timestep的权重
# weighted_input在axis=1上取和,返回值的shape为 (batch_size, 1, units)
return K.sum(weighted_input, axis=1)
def compute_output_shape(self, input_shape):
# 返回的结果是c,其shape为 (batch_size, units)
return input_shape[0], self.features_dim