Keras架构的主要逻辑实现在/keras/engine/topology.py中,主要有两个基类Node()
和Layer()
,一个重要函数Input()
。具体地,
Layer()
是一个计算层的抽象,完成网络中对Tensor的计算过程;Node()
描述两个层之间连接关系的抽象,配合Layer()
构建DAG;Input()
实例化一个特殊的Layer
(InputLayer
),将backend
(TensorFlow或Theano)建立的Tensor对象转化为Keras Tensor对象。
Keras Tensor: 增强版Tensor
相比原始的TensorFlow或者Theano的张量对象,Keras Tensor加入了如下两个属性,以使Tensor中包含了自己的来源和规模信息:
- _Keras_history: 保存了最近一个应用于这个Tensor的Layer
- _keras_shape: 标准化的Keras shape接口
当使用Keras建立深度网络时,传入的数据首先要经过Input()
函数。在Input()
函数中,实例化一个InputLayer()
对象,并将此Layer()
对象作为第一个应用于传入张量的Layer,置于_keras_history
属性中。此外,InputLayer()
和Input()
还会对传入的数据进行规模检查和变换等,使之符合后续操作的要求。
代码上实现如下:
def Input():
input_layer = InputLayer()
outputs = InputLayer.inbound_nodes[0].output_tensor
return outputs
class InputLayer():
def __init__():
input_tensor._keras_history = (self, 0, 0)
Node(self, ...)
在下面我们将看到,加入的_keras_history
属性在计算图的构建上所起的作用是关键的。仅通过输入和输出的Tensor,我们可以构建出整张计算图。但这样的代价是Tensor对象太重了,包含了Layer的信息。
Node
对象:层与层之间链接的抽象
若考虑Layer
对象抽象的是完成计算的神经元胞体,则Node
对象是对神经元树突结构的抽象。其内聚的主要信息是:
class Node():
def __init__(self, outbound_layer,
inbound_layers, node_indices, tensor_indices,
input_tensors, output_tensors, ...)
其中outbound_layer
是施加计算(使input_tensors
变为output_tensors
)的层,inbound_layers
对应了input_tensors
来源的层,而node_indices
和tensor_indices
则记录了Node
和Layer
之间的标定信息。
Node
对象总在outbound_layer
被执行时创建,并加入outbound_layer
的inbound_nodes
属性中。在Node
对象的表述下,A和B两个层产生连接关系时,Node
对象被建立,并被加入A.outbound_nodes
和B.inbound_nodes
。
Layer
对象:计算层的抽象
Layer
对象是对网络中神经元计算层的抽象,实例化需要如下参数:
allowed_kwargs = {'input_shape',
'batch_input_shape',
'batch_size',
'dtype',
'name',
'trainable',
'weights',
'input_dtype', # legacy
}
大部分与传入数据的类型和规模相关,trainable
表征该层是否需要更新权重。此外,还有inbound_nodes
和outbound_nodes
属性来标定与Node
对象的链接。
Layer
对象最重要的方法是__call__()
,主要完成如下三件事情:
验证传入数据的合法性,通过调用内部方法实现:
self.assert_input_compatibility(inputs)
进行计算
outputs = self.call(inputs, ...)
,被其子类具体实现,如Linear
,Dropout
等更新Tensor中的
_keras_history
属性,记录该次计算操作,通过内部方法_add_inbound_nodes()
实现
方法_add_inbound_nodes()
对Tensor的更新是构建Layer
之间关系的关键操作,其主要代码如下:
for x in input_tensors:
if hasattr(x, '_keras_history'):
inbound_layer, node_index, tensor_index = x._keras_history
inbound_layers.append(inbound_layer)
node_indices.append(node_index)
tensor_indices.append(tensor_index)
# Node对象的建立过程中将更新self的inbound_nodes属性
Node(self,
inbound_layers=inbound_layers,
node_indices=node_indices,
tensor_indices=tensor_indices,
...)
for i in range(len(output_tensors)):
output_tensors[i]._keras_history = (self, len(self.inbound_nodes) - 1, i)
上段代码取出input_tensor
的_keras_history
属性,建立新的Node
,并将当前Layer
的信息更新到计算得到的output_tensor
中。
实例:Node
,Tensor
和Layer
间连接关系的表征
下面通过代码来说明三者之间的关系,来自于测试代码:
# 建立新的keras Tensor
a = Input(shape=(32,), name='input_a')
b = Input(shape=(32,), name='input_b')
a_layer, a_node_index, a_tensor_index = a._keras_history
assert len(a_layer.inbound_nodes) == 1
assert a_tensor_index is 0
# node和layer之间的关系
node = a_layer.inbound_nodes[a_node_index]
assert node.outbound_layer == a_layer
# 建立连接层,将Tensor传入
dense = Dense(16, name='dense_1')
a_2 = dense(a)
b_2 = dense(b)
assert len(dense.inbound_nodes) == 2
assert len(dense.outbound_nodes) == 0
# 与张量a关联的Node
assert dense.inbound_nodes[0].inbound_layers == [a_layer]
assert dense.inbound_nodes[0].outbound_layer == dense
assert dense.inbound_nodes[0].input_tensors == [a]
# 与张量b关联的Node
assert dense.inbound_nodes[1].inbound_layers == [b_layer]
assert dense.inbound_nodes[1].outbound_layer == dense
assert dense.inbound_nodes[1].input_tensors == [b]
总结
keras利用Node
对象描述Layer
之间的连接关系,并在Tensor
中记录其来源信息。在下篇中,我们将看到keras如何利用这些抽象和增强属性构建DAG,并实现前向传播和反向训练的。