def __init__(self, input_shape=None, batch_size=None, dtype=dtypes.float32, input_tensor=None, sparse=False, name=None): super(InputLayer, self).__init__(dtype=dtype, name=name) self.built = True self.sparse = sparse self.batch_size = batch_size if isinstance(input_shape, tensor_shape.TensorShape): input_shape = tuple(input_shape.as_list()) if input_tensor is None: if input_shape is not None: batch_input_shape = (batch_size,) + tuple(input_shape) else: batch_input_shape = None if context.in_eager_mode(): # In eager mode, create a temporary placeholder to call the layer on. input_tensor = base._DeferredTensor( # pylint: disable=protected-access shape=batch_input_shape, dtype=dtype, name=self.name) else: # In graph mode, create a graph placeholder to call the layer on. if sparse: input_tensor = array_ops.sparse_placeholder( shape=batch_input_shape, dtype=dtype, name=self.name) else: input_tensor = array_ops.placeholder( shape=batch_input_shape, dtype=dtype, name=self.name) # For compatibility with Keras API. self.is_placeholder = True self._batch_input_shape = batch_input_shape else: # For compatibility with Keras API. self.is_placeholder = False self._batch_input_shape = tuple(input_tensor.get_shape().as_list()) # Create an input node to add to self.outbound_node # and set output_tensors' _keras_history. input_tensor._keras_history = (self, 0, 0) # pylint: disable=protected-access base.Node( self, inbound_layers=[], node_indices=[], tensor_indices=[], input_tensors=[input_tensor], output_tensors=[input_tensor])
def __init__(self, inputs, outputs, name=None): # pylint: disable=super-init-not-called if context.in_eager_mode(): # TODO(fchollet): check that all inputs and outputs are DeferredTensors. pass self._init_set_name(name) self._activity_regularizer = None with vs.variable_scope(None, default_name=self._base_name) as captured_scope: self._scope = captured_scope call_fn_args = estimator_util.fn_args(self.call) self._compute_previous_mask = ('mask' in call_fn_args or hasattr(self, 'compute_mask')) self._call_has_scope_arg = 'scope' in call_fn_args # This acts just like the `trainable` attribute of any layer instance. # It does not affect users of the underlying layers, only users of the # GraphNetwork instance. self.trainable = True # A GraphNetwork does not create weights of its own, thus it is already # built. self.built = True # A GraphNetwork does not create weights of its own, thus has no dtype. self._dtype = None # The following are implemented as property functions: # self.trainable_weights # self.non_trainable_weights # self.input_spec # Private attributes to implement compatibility with Layer. self._per_input_losses = {} self._per_input_updates = {} self._updates = [] self._losses = [] self._scope = None self._reuse = None self._graph = ops.get_default_graph() # GraphNetwork-specific properties. if isinstance(inputs, (list, tuple)): self.inputs = list(inputs) # Tensor or list of tensors. else: self.inputs = [inputs] if isinstance(outputs, (list, tuple)): self.outputs = list(outputs) else: self.outputs = [outputs] # All layers in order of horizontal graph traversal. # Entries are unique. Includes input and output layers. self.layers = [] # Check for redundancy in inputs. if len(set(self.inputs)) != len(self.inputs): raise ValueError('The list of inputs passed to the model ' 'is redundant. ' 'All inputs should only appear once.' ' Found: ' + str(self.inputs)) # # List of initial layers (1 to 1 mapping with self.inputs, # # hence the same layer might appear twice) # self._input_layers = [] # self._input_layers_node_indices = [] # self._input_layers_tensor_indices = [] # # list of layers (1 to 1 mapping with self.inputs, # # hence the same layer might appear twice) # self._output_layers = [] # self._output_layers_node_indices = [] # self._output_layers_tensor_indices = [] self._input_layers = [] self._output_layers = [] self._input_coordinates = [] self._output_coordinates = [] # This is for performance optimization when calling the GraphNetwork on new # inputs. Every time the GraphNetwork is called on a set on input tensors, # we compute the output tensors, output masks and output shapes in one pass, # then cache them here. When any of these outputs is queried later, we # retrieve it from there instead of recomputing it. self._output_mask_cache = {} self._output_tensor_cache = {} self._output_shape_cache = {} # User-provided arguments validation. for x in self.inputs: # Check that x has appropriate `_keras_history` metadata. if not hasattr(x, '_keras_history'): cls_name = self.__class__.__name__ raise ValueError('Input tensors to a ' + cls_name + ' ' + 'must come from `tf.layers.Input`. ' 'Received: ' + str(x) + ' (missing previous layer metadata).') # Check that x is an input tensor. # pylint: disable=protected-access layer, node_index, tensor_index = x._keras_history if len(layer._inbound_nodes) > 1 or ( layer._inbound_nodes and layer._inbound_nodes[0].inbound_layers): cls_name = self.__class__.__name__ logging.warning( cls_name + ' inputs must come from ' '`tf.layers.Input` (thus holding past layer metadata), ' 'they cannot be the output of ' 'a previous non-Input layer. ' 'Here, a tensor specified as ' 'input to "' + self.name + '" was not an Input tensor, ' 'it was generated by layer ' + layer.name + '.\n' 'Note that input tensors are ' 'instantiated via `tensor = tf.layers.Input(shape)`.\n' 'The tensor that caused the issue was: ' + str(x.name)) # pylint: enable=protected-access for x in self.outputs: if not hasattr(x, '_keras_history'): cls_name = self.__class__.__name__ raise ValueError( 'Output tensors to a ' + cls_name + ' must be ' 'the output of a TensorFlow `Layer` ' '(thus holding past layer metadata). Found: ' + str(x)) # Build self._output_layers: for x in self.outputs: layer, node_index, tensor_index = x._keras_history # pylint: disable=protected-access self._output_layers.append(layer) self._output_coordinates.append((layer, node_index, tensor_index)) # Build self._input_layers: for x in self.inputs: layer, node_index, tensor_index = x._keras_history # pylint: disable=protected-access # It's supposed to be an input layer, so only one node # and one tensor output. assert node_index == 0 assert tensor_index == 0 self._input_layers.append(layer) self._input_coordinates.append((layer, node_index, tensor_index)) # Network_nodes: set of nodes included in the graph # (not all nodes included in the layers # are relevant to the current graph). network_nodes = set() # ids of all nodes relevant to the GraphNetwork nodes_depths = {} # dict {node: depth value} layers_depths = {} # dict {layer: depth value} layer_indices = {} # dict {layer: index in traversal} nodes_in_decreasing_depth = [] def build_map_of_graph(tensor, finished_nodes, nodes_in_progress, layer, node_index, tensor_index): """Builds a map of the graph of layers. This recursively updates the map `layer_indices`, the list `nodes_in_decreasing_depth` and the set `network_nodes`. Arguments: tensor: Some tensor in a graph. finished_nodes: Set of nodes whose subgraphs have been traversed completely. Useful to prevent duplicated work. nodes_in_progress: Set of nodes that are currently active on the recursion stack. Useful to detect cycles. layer: Layer from which `tensor` comes from. If not provided, will be obtained from `tensor._keras_history`. node_index: Node index from which `tensor` comes from. tensor_index: Tensor_index from which `tensor` comes from. Raises: ValueError: if a cycle is detected. """ node = layer._inbound_nodes[node_index] # pylint: disable=protected-access # Prevent cycles. if node in nodes_in_progress: raise ValueError('The tensor ' + str(tensor) + ' at layer "' + layer.name + '" is part of a cycle.') # Don't repeat work for shared subgraphs if node in finished_nodes: return node_key = _make_node_key(layer.name, node_index) # Update network_nodes. network_nodes.add(node_key) # Store the traversal order for layer sorting. if layer not in layer_indices: layer_indices[layer] = len(layer_indices) nodes_in_progress.add(node) # Propagate to all previous tensors connected to this 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) finished_nodes = set() nodes_in_progress = set() for x in self.outputs: layer, node_index, tensor_index = x._keras_history # pylint: disable=protected-access build_map_of_graph(x, finished_nodes, nodes_in_progress, layer=layer, node_index=node_index, tensor_index=tensor_index) for node in reversed(nodes_in_decreasing_depth): # If the depth is not set, the node has no outbound nodes (depth 0). depth = nodes_depths.setdefault(node, 0) # Update the depth of the corresponding layer previous_depth = layers_depths.get(node.outbound_layer, 0) # If we've seen this layer before at a higher depth, # we should use that depth instead of the node depth. # This is necessary for shared layers that have inputs at different # depth levels in the graph. depth = max(depth, previous_depth) layers_depths[node.outbound_layer] = depth nodes_depths[node] = depth # Update the depth of inbound nodes. # The "depth" of a node is the max of the depths # of all layers it is connected to. 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] # pylint: disable=protected-access previous_depth = nodes_depths.get(inbound_node, 0) nodes_depths[inbound_node] = max(depth + 1, previous_depth) # Build a dict {depth: list of nodes with this 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) # Build a dict {depth: list of layers with this depth} 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) # Get sorted list of layer depths. depth_keys = list(layers_by_depth.keys()) depth_keys.sort(reverse=True) # Set self.layers and self._layers_by_depth. layers = [] for depth in depth_keys: layers_for_depth = layers_by_depth[depth] # GraphNetwork.layers needs to have a deterministic order: # here we order them by traversal order. layers_for_depth.sort(key=lambda x: layer_indices[x]) layers.extend(layers_for_depth) self.layers = layers self._layers_by_depth = layers_by_depth # Get sorted list of node depths. depth_keys = list(nodes_by_depth.keys()) depth_keys.sort(reverse=True) # Check that all tensors required are computable. # computable_tensors: all tensors in the graph # that can be computed from the inputs provided. computable_tensors = [] for x in self.inputs: computable_tensors.append(x) layers_with_complete_input = [] # To provide a better error msg. for depth in depth_keys: for node in nodes_by_depth[depth]: layer = node.outbound_layer if layer: for x in node.input_tensors: if x not in computable_tensors: raise ValueError( 'Graph disconnected: ' 'cannot obtain value for tensor ' + str(x) + ' at layer "' + layer.name + '". ' 'The following previous layers ' 'were accessed without issue: ' + str(layers_with_complete_input)) for x in node.output_tensors: computable_tensors.append(x) layers_with_complete_input.append(layer.name) # Keep track of the network's nodes. self._network_nodes = network_nodes self._nodes_by_depth = nodes_by_depth # Ensure name unicity, which will be crucial for serialization # (since serialized nodes refer to layers by their name). all_names = [layer.name for layer in self.layers] for name in all_names: if all_names.count(name) != 1: raise ValueError('The name "' + name + '" is used ' + str(all_names.count(name)) + ' times in the model. ' 'All layer names should be unique.') # Layer parameters. # The new network starts with a single inbound node # for its inputs, and no outbound nodes. self._outbound_nodes = [ ] # Will be appended to by future calls to __call__ self._inbound_nodes = [ ] # Will be appended to below, and by future calls to __call__ # Create the node linking internal inputs to internal outputs. base.Node(outbound_layer=self, inbound_layers=[], node_indices=[], tensor_indices=[], input_tensors=self.inputs, output_tensors=self.outputs)
def __init__(self, input_shape=None, batch_size=None, dtype=None, input_tensor=None, sparse=False, name=None, **kwargs): if 'batch_input_shape' in kwargs: batch_input_shape = kwargs.pop('batch_input_shape') if input_shape and batch_input_shape: raise ValueError('Only provide the input_shape OR ' 'batch_input_shape argument to ' 'InputLayer, not both at the same time.') batch_size = batch_input_shape[0] input_shape = batch_input_shape[1:] if kwargs: raise ValueError('Unrecognized keyword arguments:', kwargs.keys()) if not name: prefix = 'input' name = prefix + '_' + str(K.get_uid(prefix)) if not dtype: if input_tensor is None: dtype = K.floatx() else: dtype = K.dtype(input_tensor) super(InputLayer, self).__init__(dtype=dtype, name=name) self.built = True self.sparse = sparse self.batch_size = batch_size if isinstance(input_shape, tensor_shape.TensorShape): input_shape = tuple(input_shape.as_list()) if input_tensor is None: if input_shape is not None: batch_input_shape = (batch_size,) + tuple(input_shape) else: batch_input_shape = None if context.in_eager_mode(): # In eager mode, create a temporary placeholder to call the layer on. input_tensor = tf_base_layers._DeferredTensor( # pylint: disable=protected-access shape=batch_input_shape, dtype=dtype, name=self.name) else: # In graph mode, create a graph placeholder to call the layer on. if sparse: input_tensor = array_ops.sparse_placeholder( shape=batch_input_shape, dtype=dtype, name=self.name) else: input_tensor = array_ops.placeholder( shape=batch_input_shape, dtype=dtype, name=self.name) # For compatibility with Keras API. self.is_placeholder = True self._batch_input_shape = batch_input_shape else: # For compatibility with Keras API. self.is_placeholder = False self._batch_input_shape = tuple(input_tensor.get_shape().as_list()) # Create an input node to add to self.outbound_node # and set output_tensors' _keras_history. input_tensor._keras_history = (self, 0, 0) # pylint: disable=protected-access tf_base_layers.Node( self, inbound_layers=[], node_indices=[], tensor_indices=[], input_tensors=[input_tensor], output_tensors=[input_tensor])