В этой статье продолжается обсуждение структуры исходного кода keras.
Первое примечание к исходному кодумы наблюдалиLayer
, Tensor
иNode
Как они связаны друг с другом, и в этой статье основное внимание уделяется ориентированному ациклическому графу (DAG), состоящему из многослойных сетей. К основным документам относятсяkeras/engine/topology.py,
Класс для наблюденияContainer
.
Container
Объект: Топологический прототип DAG.
В первой статье мы упомянули, что улучшенный Keras Tensor\_keras_history
Атрибуты позволяют построить весь вычислительный граф только через входной и выходной тензоры. иContainer
Объекты реализуют именно этот процесс.
Построение вычислительного графика
Граф вычислений DAG строится вContainer
Завершается при создании экземпляра объекта и в основном включает следующие операции:
1) ЗаписьContainer
информация о соединении верхнего и нижнего колонтитула
def __init__(self, inputs, outputs, name=None):
for x in self.outputs:
layer, node_index, tensor_index = x._keras_history
self.output_layers.append(layer)
self.output_layers_node_indices.append(node_index)
self.output_layers_tensor_indices.append(tensor_index)
for x in self.inputs:
layer, node_index, tensor_index = x._keras_history
self.input_layers.append(layer)
self.input_layers_node_indices.append(node_index)
self.input_layers_tensor_indices.append(tensor_index)
2) Отoutput_tensors
Начните строить вычислительный граф в обратной рекурсии, используя принцип ширины. Ключом к этому шагу является построениеnodes_in_decreasing_depth
эта очередь, этиNode
Включенная информация о соединении и информация о глубине будут основой для порядка выполнения последующих расчетов прямого распространения и обратного обучения.
def build_map_of_graph(tensor, finished_nodes, nodes_in_progress):
layer, node_index, tensor_index = tensor._keras_history
node = layer.inbound_nodes[node_index]
nodes_in_progress.add(node)
# 广度优先搜索
for i in range(len(node.inbound_layers)):
x = node.input_tensors[i]
layer = node.inbound_layers[i]
node_index = node.node_indices[i]
tensor_index = node.tensor_indices[i]
# 递归调用
build_map_of_graph(x, finished_nodes, nodes_in_progress,
layer, node_index, tensor_index)
# 维护两个队列
finished_nodes.add(node)
nodes_in_progress.remove(node)
nodes_in_decreasing_depth.append(node)
# 反向构建DAG
for x in self.outputs:
build_map_of_graph(x, finished_nodes, nodes_in_progress)
3) Рассчитайте глубину каждого узла и откалибруйте положение узла в DAG в соответствии с глубиной
# 根据队列标定各节点的深度
for node in reversed(nodes_in_decreasing_depth):
depth = nodes_depths.setdefault(node, 0)
previous_depth = layers_depths.get(node.outbound_layer, 0)
depth = max(depth, previous_depth)
layers_depths[node.outbound_layer] = depth
nodes_depths[node] = depth
for i in range(len(node.inbound_layers)):
inbound_layer = node.inbound_layers[i]
node_index = node.node_indices[i]
inbound_node = inbound_layer.inbound_nodes[node_index]
previous_depth = nodes_depths.get(inbound_node, 0)
nodes_depths[inbound_node] = max(depth + 1, previous_depth)
# 按深度标定各节点的位置
nodes_by_depth = {}
for node, depth in nodes_depths.items():
if depth not in nodes_by_depth:
nodes_by_depth[depth] = []
nodes_by_depth[depth].append(node)
# 按深度标定各层的位置
layers_by_depth = {}
for layer, depth in layers_depths.items():
if depth not in layers_by_depth:
layers_by_depth[depth] = []
layers_by_depth[depth].append(layer)
self.layers_by_depth = layers_by_depth
self.nodes_by_depth = nodes_by_depth
4) Ставим весьContainer
ВключитьNode
поддерживать совместимость
self.outbound_nodes = []
self.inbound_nodes = []
Node(outbound_layer=self,
inbound_layers=[],
node_indices=[],
tensor_indices=[],
input_tensors=self.inputs,
output_tensors=self.outputs,
...)
Вычисления в вычислительных графах
Рассчитано наContainer
объектcall()
Метод завершен, и его реализация зависит от внутреннего методаrun_internal_graph()
.
def run_internal_graph(self, inputs, masks=None):
depth_keys = list(self.nodes_by_depth.keys())
depth_keys.sort(reverse=True)
# 依据深度
for depth in depth_keys:
nodes = self.nodes_by_depth[depth]
# 对同一深度上的Node进行计算
for node in nodes:
layer = node.outbound_layer # Node对应的layer
reference_input_tensors = node.input_tensors
reference_output_tensors = node.output_tensors
computed_data = []
if len(computed_data) == len(reference_input_tensors):
# 在Layer中进行计算
with K.name_scope(layer.name):
if len(computed_data) == 1:
computed_tensor, computed_mask = computed_data[0]
output_tensors = _to_list(layer.call(computed_tensor, **kwargs))
computed_tensors = [computed_tensor]
else:
computed_tensors = [x[0] for x in computed_data]
output_tensors = _to_list(layer.call(computed_tensors, **kwargs))
output_tensors = []
output_masks = []
for x in self.outputs:
tensor, mask = tensor_map[str(id(x))]
output_tensors.append(tensor)
output_masks.append(mask)
return output_tensors, output_masks
Из приведенного выше кода видно, что расчет основан на глубине и обновляется путем обновленияcomputed_data
иoutput_tensor
Подождите, пока переменные завершат вычисление обхода всего графа.
Читайте третью часть серии:[Примечания к источнику] Модель анализа исходного кода keras
@ddlee
Эта статья соответствуетCreative Commons Attribution-ShareAlike 4.0 International License.
Это означает, что вы можете воспроизводить эту статью с указанием авторства, сопровождаемой этим соглашением.
Если вы хотите получать регулярные обновления о моих сообщениях в блоге, пожалуйста, подпишитесьДонгдон ежемесячно.
Ссылка на эту статью: блог Дошел ох Можно /posts/ поставить 611…