[Примечания к источнику] Контейнер для анализа исходного кода keras | Сяо Шуанлоу

искусственный интеллект TensorFlow глубокое обучение Keras компьютерное зрение

В этой статье продолжается обсуждение структуры исходного кода 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 License@ddlee
Эта статья соответствуетCreative Commons Attribution-ShareAlike 4.0 International License.
Это означает, что вы можете воспроизводить эту статью с указанием авторства, сопровождаемой этим соглашением.
Если вы хотите получать регулярные обновления о моих сообщениях в блоге, пожалуйста, подпишитесьДонгдон ежемесячно.
Ссылка на эту статью: блог Дошел ох Можно /posts/ поставить 611…


Статьи по Теме