def __init__(self, layer, call_args=None, call_kwargs=None, outputs=None): call_args = [] if call_args is None else call_args call_kwargs = {} if call_kwargs is None else call_kwargs outputs = [] if outputs is None else outputs self.layer = layer self.is_input = not call_args and not call_kwargs # These arguments are user-provided. Copy the structures here so that # future user modifications do not affect the node's metadata. # We copy using map_structure rather than python's shallow or deep copy, # because the args can be data structures (so shallow copy is # insufficient), but individual values might not support copy.copy # or be too expensive to deep copy. call_args = nest.map_structure(lambda t: t, call_args) call_kwargs = nest.map_structure(lambda t: t, call_kwargs) self.outputs = nest.map_structure(lambda t: t, outputs) self.call_args = call_args self.call_kwargs = call_kwargs # Cached for performance. self._flat_arguments = nest.flatten((self.call_args, self.call_kwargs)) # Used to avoid expensive `nest` operations in the most common case. self._single_positional_tensor_passed = (not self.call_kwargs and len( self.call_args) == 1 and tensor_util.is_tensor(self.call_args[0])) if not keras_tensor.keras_tensors_enabled(): # Create TensorFlowOpLayers if needed. for obj in self._flat_arguments: if (isinstance(obj, ops.Tensor) and base_layer_utils.needs_keras_history( obj, ignore_call_context=True)): base_layer_utils.create_keras_history(obj) self._keras_inputs = [] self._keras_inputs_ids_and_indices = [] for i, ele in enumerate(self._flat_arguments): if is_keras_tensor(ele): self._keras_inputs.append(ele) kt_id = str(id(ele)) kt_index = i self._keras_inputs_ids_and_indices.append((kt_id, kt_index)) # Wire up Node to Layers. self.layer._inbound_nodes.append(self) for kt in self.keras_inputs: inbound_layer = kt._keras_history.layer if inbound_layer is not None: # `None` for `Input` tensors. inbound_layer._outbound_nodes.append(self) # Set metadata on outputs. node_index = len(self.layer._inbound_nodes) - 1 for i, tensor in enumerate(nest.flatten(outputs)): tensor._keras_history = KerasHistory(layer=layer, node_index=node_index, tensor_index=i) # Cached for performance. self.flat_input_ids = [str(id(t)) for t in self._keras_inputs] self.flat_output_ids = [str(id(t)) for t in nest.flatten(self.outputs)]
def __init__(self, outbound_layer, inbound_layers, node_indices, tensor_indices, input_tensors, output_tensors, arguments=None): # Layer instance (NOT a sequence) if isinstance(outbound_layer, (list, tuple, dict)): raise ValueError('`outbound_layer` should be a layer instance, ' 'not a list, tuple, or, dict.') # this is the layer that takes a nested structure of input tensors # and turns them into a nested structure of output tensors. # the current node will be added to # the inbound_nodes of outbound_layer. self.outbound_layer = outbound_layer # The following 3 properties describe where # the input tensors come from: which layers, # and for each layer, which node and which # tensor output of each node. # Nested structure of layer instances. self.inbound_layers = inbound_layers # Nested structure of integers, 1:1 mapping with inbound_layers. self.node_indices = node_indices # Nested of integers, 1:1 mapping with inbound_layers. self.tensor_indices = tensor_indices # Following 2 properties: # tensor inputs and outputs of outbound_layer. # Nested structure of tensors. 1:1 mapping with inbound_layers. self.input_tensors = input_tensors # Nested structure of tensors, created by outbound_layer.call(). self.output_tensors = output_tensors # Following 2 properties: input and output shapes. # Nested structure of shape tuples, shapes of input_tensors. self.input_shapes = nest.map_structure(backend.int_shape, input_tensors) # Nested structure of shape tuples, shapes of output_tensors. self.output_shapes = nest.map_structure(backend.int_shape, output_tensors) # Optional keyword arguments to layer's `call`. self.arguments = arguments # Create Keras History for any Keras Tensors in `arguments`. tensor_arguments = [ t for t in nest.flatten(self.arguments) if isinstance(t, ops.Tensor) ] for tensor_argument in tensor_arguments: if base_layer_utils.needs_keras_history(tensor_argument, ignore_call_context=True): base_layer_utils.create_keras_history(tensor_argument) # Add nodes to all layers involved. for layer in nest.flatten(inbound_layers): if layer is not None: # For compatibility with external Keras, we use the deprecated # accessor here. layer.outbound_nodes.append(self) # For compatibility with external Keras, we use the deprecated # accessor here. outbound_layer.inbound_nodes.append(self)
def inference(self, inputs, *args, **kwargs): call_context = base_layer_utils.call_context() input_list = nest.flatten(inputs) # We will attempt to build a TF graph if & only if all inputs are symbolic. # This is always the case in graph mode. It can also be the case in eager # mode when all inputs can be traced back to `keras.Input()` (when building # models using the functional API). build_graph = tf_utils.are_all_symbolic_tensors(input_list) # Accept NumPy and scalar inputs by converting to Tensors. if any(isinstance(x, (np.ndarray, float, int)) for x in input_list): def _convert_non_tensor(x): # Don't call `ops.convert_to_tensor` on all `inputs` because # `SparseTensors` can't be converted to `Tensor`. if isinstance(x, (np.ndarray, float, int)): return ops.convert_to_tensor(x) return x inputs = nest.map_structure(_convert_non_tensor, inputs) input_list = nest.flatten(inputs) # Handle `mask` propagation from previous layer to current layer. Masks can # be propagated explicitly via the `mask` argument, or implicitly via # setting the `_keras_mask` attribute on the inputs to a Layer. Masks passed # explicitly take priority. mask_arg_passed_by_framework = False input_masks = self._collect_input_masks(inputs, args, kwargs) if (self._expects_mask_arg and input_masks is not None and not self._call_arg_was_passed('mask', args, kwargs)): mask_arg_passed_by_framework = True kwargs['mask'] = input_masks # If `training` argument was not explicitly passed, propagate `training` # value from this layer's calling layer. training_arg_passed_by_framework = False # Priority 1: `training` was explicitly passed. if self._call_arg_was_passed('training', args, kwargs): training_value = self._get_call_arg_value('training', args, kwargs) if not self._expects_training_arg: kwargs.pop('training') else: training_value = None # Priority 2: `training` was passed to a parent layer. if call_context.training is not None: training_value = call_context.training # Priority 3a: `learning_phase()` has been set. elif backend.global_learning_phase_is_set(): training_value = backend.learning_phase() # Priority 3b: Pass the `learning_phase()` if in the Keras FuncGraph. elif build_graph: with backend.get_graph().as_default(): if base_layer_utils.is_in_keras_graph(): training_value = backend.learning_phase() if self._expects_training_arg and training_value is not None: # Force the training_value to be bool type which matches to the contract # for layer/model call args. if tensor_util.is_tensor(training_value): training_value = math_ops.cast(training_value, dtypes.bool) else: training_value = bool(training_value) kwargs['training'] = training_value training_arg_passed_by_framework = True # Only create Keras history if at least one tensor originates from a # `keras.Input`. Otherwise this Layer may be being used outside the Keras # framework. if build_graph and base_layer_utils.needs_keras_history(inputs): base_layer_utils.create_keras_history(inputs) # Clear eager losses on top level model call. # We are clearing the losses only on the top level model call and not on # every layer/model call because layer/model may be reused. if (base_layer_utils.is_in_eager_or_tf_function() and not call_context.in_call): self._clear_losses() with call_context.enter(self, inputs, build_graph, training_value): # Check input assumptions set after layer building, e.g. input shape. if build_graph: # Symbolic execution on symbolic tensors. We will attempt to build # the corresponding TF subgraph inside `backend.get_graph()` # TODO(reedwm): We should assert input compatibility after the inputs # are casted, not before. input_spec.assert_input_compatibility(self.input_spec, inputs, self.name) if (any(isinstance(x, ragged_tensor.RaggedTensor) for x in input_list) and self._supports_ragged_inputs is False): # pylint: disable=g-bool-id-comparison raise ValueError('Layer %s does not support RaggedTensors as input. ' 'Inputs received: %s. You can try converting your ' 'input to an uniform tensor.' % (self.name, inputs)) graph = backend.get_graph() with graph.as_default(), backend.name_scope(self._name_scope()): # Build layer if applicable (if the `build` method has been # overridden). self._maybe_build(inputs) cast_inputs = self._maybe_cast_inputs(inputs) # Wrapping `call` function in autograph to allow for dynamic control # flow and control dependencies in call. We are limiting this to # subclassed layers as autograph is strictly needed only for # subclassed layers and models. # tf_convert will respect the value of autograph setting in the # enclosing tf.function, if any. if (base_layer_utils.is_subclassed(self) and not base_layer_utils.from_saved_model(self)): call_fn = autograph.tf_convert( self._inference, ag_ctx.control_status_ctx()) else: call_fn = self._inference if not self.dynamic: try: with base_layer_utils.autocast_context_manager( self._compute_dtype): # Add auto_control_deps in V2 when they are not already added by # a `tf.function`. if (ops.executing_eagerly_outside_functions() and not base_layer_utils.is_in_eager_or_tf_function()): with auto_control_deps.AutomaticControlDependencies() as acd: outputs = call_fn(cast_inputs, *args, **kwargs) # Wrap Tensors in `outputs` in `tf.identity` to avoid # circular dependencies. outputs = base_layer_utils.mark_as_return(outputs, acd) else: outputs = call_fn(cast_inputs, *args, **kwargs) except errors.OperatorNotAllowedInGraphError as e: raise TypeError('You are attempting to use Python control ' 'flow in a layer that was not declared to be ' 'dynamic. Pass `dynamic=True` to the class ' 'constructor.\nEncountered error:\n"""\n' + str(e) + '\n"""') else: # We will use static shape inference to return symbolic tensors # matching the specifications of the layer outputs. # Since `self.dynamic` is True, we will never attempt to # run the underlying TF graph (which is disconnected). # TODO(fchollet): consider py_func as an alternative, which # would enable us to run the underlying graph if needed. outputs = self._symbolic_call(inputs) if outputs is None: raise ValueError('A layer\'s `call` method should return a ' 'Tensor or a list of Tensors, not None ' '(layer: ' + self.name + ').') if base_layer_utils.have_all_keras_metadata(inputs): if training_arg_passed_by_framework: kwargs.pop('training') if mask_arg_passed_by_framework: kwargs.pop('mask') inputs, outputs = self._set_connectivity_metadata_( inputs, outputs, args, kwargs) self._handle_activity_regularization(inputs, outputs) self._set_mask_metadata(inputs, outputs, input_masks) if hasattr(self, '_set_inputs') and not self.inputs: # Subclassed network: explicitly set metadata normally set by # a call to self._set_inputs(). # TODO(b/120997007): This should be done in Eager as well, but # causes garbage collection issues because of the placeholders # created on the default Keras graph. self._set_inputs(inputs, outputs) else: # Eager execution on data tensors. with backend.name_scope(self._name_scope()): self._maybe_build(inputs) cast_inputs = self._maybe_cast_inputs(inputs) with base_layer_utils.autocast_context_manager( self._compute_dtype): outputs = self._inference(cast_inputs, *args, **kwargs) self._handle_activity_regularization(inputs, outputs) self._set_mask_metadata(inputs, outputs, input_masks) return outputs