Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tensorflow/python/keras/engine/node.py: 25%
147 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-03 07:57 +0000
1# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15# pylint: disable=protected-access
16# pylint: disable=g-classes-have-attributes
17"""Contains the `Node` class."""
19import collections
20import copy
21import json
22import numpy as np
24from tensorflow.python.framework import ops
25from tensorflow.python.framework import tensor_util
26from tensorflow.python.keras import backend
27from tensorflow.python.keras.engine import base_layer_utils
28from tensorflow.python.keras.saving.saved_model import json_utils
29from tensorflow.python.keras.utils import tf_utils
30from tensorflow.python.util import nest
32_CONSTANT_VALUE = '_CONSTANT_VALUE'
35class Node:
36 """A `Node` describes the connectivity between two layers.
38 Each time a layer is connected to some new input,
39 a node is added to `layer._inbound_nodes`.
40 Each time the output of a layer is used by another layer,
41 a node is added to `layer._outbound_nodes`.
43 Args:
44 layer: The Layer for the Layer.__call__ this node represents.
45 call_args: The positional arguments the Layer was called with.
46 call_kwargs: The keyword arguments the Layer was called with.
47 outputs: The outputs of the Layer.__call__
48 """
50 def __init__(self,
51 layer,
52 call_args=None,
53 call_kwargs=None,
54 outputs=None):
55 call_args = [] if call_args is None else call_args
56 call_kwargs = {} if call_kwargs is None else call_kwargs
57 outputs = [] if outputs is None else outputs
59 self.layer = layer
60 self.is_input = not call_args and not call_kwargs
62 # These arguments are user-provided. Copy the structures here so that
63 # future user modifications do not affect the node's metadata.
64 # We copy using map_structure rather than python's shallow or deep copy,
65 # because the args can be data structures (so shallow copy is
66 # insufficient), but individual values might not support copy.copy
67 # or be too expensive to deep copy.
68 call_args = nest.map_structure(lambda t: t, call_args)
69 call_kwargs = nest.map_structure(lambda t: t, call_kwargs)
70 self.outputs = nest.map_structure(lambda t: t, outputs)
71 self.call_args = call_args
72 self.call_kwargs = call_kwargs
74 # Cached for performance.
75 self._flat_arguments = nest.flatten((self.call_args, self.call_kwargs))
76 # Used to avoid expensive `nest` operations in the most common case.
77 self._single_positional_tensor_passed = (not self.call_kwargs and len(
78 self.call_args) == 1 and tensor_util.is_tf_type(self.call_args[0]))
80 if not ops.executing_eagerly_outside_functions():
81 # Create TensorFlowOpLayers if needed (in TF1)
82 for obj in self._flat_arguments:
83 if (isinstance(obj, ops.Tensor) and
84 base_layer_utils.needs_keras_history(
85 obj, ignore_call_context=True)):
86 base_layer_utils.create_keras_history(obj)
88 self._keras_inputs = []
89 self._keras_inputs_ids_and_indices = []
90 for i, ele in enumerate(self._flat_arguments):
91 if is_keras_tensor(ele):
92 self._keras_inputs.append(ele)
93 kt_id = str(id(ele))
94 kt_index = i
95 self._keras_inputs_ids_and_indices.append((kt_id, kt_index))
97 # Wire up Node to Layers.
98 self.layer._inbound_nodes.append(self)
99 for kt in self.keras_inputs:
100 inbound_layer = kt._keras_history.layer
101 if inbound_layer is not None: # `None` for `Input` tensors.
102 inbound_layer._outbound_nodes.append(self)
104 # Set metadata on outputs.
105 node_index = len(self.layer._inbound_nodes) - 1
106 for i, tensor in enumerate(nest.flatten(outputs)):
107 tensor._keras_history = KerasHistory(
108 layer=layer, node_index=node_index, tensor_index=i)
110 # Cached for performance.
111 self.flat_input_ids = [str(id(t)) for t in self._keras_inputs]
112 self.flat_output_ids = [str(id(t)) for t in nest.flatten(self.outputs)]
114 @property
115 def keras_inputs(self):
116 """Tensors input to this node that can be traced back to a `keras.Input`."""
117 return self._keras_inputs
119 @property
120 def parent_nodes(self):
121 """Returns all the `Node`s whose output this node immediately depends on."""
122 node_deps = []
123 for kt in self.keras_inputs:
124 layer = kt._keras_history.layer
125 node_index = kt._keras_history.node_index
126 if layer is not None: # `None` for `Input` tensors.
127 node_deps.append(layer._inbound_nodes[node_index])
128 return node_deps
130 def iterate_inbound(self):
131 """Yields tuples representing the data inbound from other nodes.
133 Yields:
134 tuples like: (inbound_layer, node_index, tensor_index, tensor).
135 """
136 for kt in self.keras_inputs:
137 keras_history = kt._keras_history
138 layer = keras_history.layer
139 node_index = keras_history.node_index
140 tensor_index = keras_history.tensor_index
141 yield layer, node_index, tensor_index, kt
143 def map_arguments(self, tensor_dict):
144 """Maps Keras Tensors to computed Tensors using `tensor_dict`."""
145 if self._single_positional_tensor_passed:
146 # Performance optimization for most common case.
147 kt_id, _ = self._keras_inputs_ids_and_indices[0]
148 return (tensor_dict[kt_id].pop(),), {}
149 else:
150 flat_arguments = copy.copy(self._flat_arguments)
151 for kt_id, kt_index in self._keras_inputs_ids_and_indices:
152 flat_arguments[kt_index] = tensor_dict[kt_id].pop()
154 args, kwargs = nest.pack_sequence_as((self.call_args, self.call_kwargs),
155 flat_arguments)
156 return args, kwargs
158 def serialize(self, make_node_key, node_conversion_map):
159 """Serializes `Node` for Functional API's `get_config`."""
160 # Serialization still special-cases first argument.
161 args, kwargs = self.call_args, self.call_kwargs
162 inputs, args, kwargs = self.layer._split_out_first_arg(args, kwargs)
164 # Treat everything other than first argument as a kwarg.
165 arguments = dict(zip(self.layer._call_fn_args[1:], args))
166 arguments.update(kwargs)
167 kwargs = arguments
169 def _serialize_keras_tensor(t):
170 """Serializes a single Tensor passed to `call`."""
171 if hasattr(t, '_keras_history'):
172 kh = t._keras_history
173 node_index = kh.node_index
174 node_key = make_node_key(kh.layer.name, node_index)
175 new_node_index = node_conversion_map.get(node_key, 0)
176 return [kh.layer.name, new_node_index, kh.tensor_index]
178 if isinstance(t, np.ndarray):
179 return t.tolist()
181 if isinstance(t, ops.Tensor):
182 return backend.get_value(t).tolist()
184 return t
186 kwargs = nest.map_structure(_serialize_keras_tensor, kwargs)
187 try:
188 json.dumps(kwargs, default=json_utils.get_json_type)
189 except TypeError:
190 kwarg_types = nest.map_structure(type, kwargs)
191 raise TypeError('Layer ' + self.layer.name +
192 ' was passed non-JSON-serializable arguments. ' +
193 'Arguments had types: ' +
194 str(kwarg_types) + '. They cannot be serialized out '
195 'when saving the model.')
197 # `kwargs` is added to each Tensor in the first arg. This should be
198 # changed in a future version of the serialization format.
199 def serialize_first_arg_tensor(t):
200 if is_keras_tensor(t):
201 kh = t._keras_history
202 node_index = kh.node_index
203 node_key = make_node_key(kh.layer.name, node_index)
204 new_node_index = node_conversion_map.get(node_key, 0)
205 data = [kh.layer.name, new_node_index, kh.tensor_index, kwargs]
206 else:
207 # If an element in the first call argument did not originate as a
208 # keras tensor and is a constant value, we save it using the format
209 # ['_CONSTANT_VALUE', -1, serializaed_tensor_or_python_constant]
210 # (potentially including serialized kwargs in an optional 4th argument
211 data = [_CONSTANT_VALUE, -1, _serialize_keras_tensor(t), kwargs]
212 return tf_utils.ListWrapper(data)
214 data = nest.map_structure(serialize_first_arg_tensor, inputs)
215 if (not nest.is_nested(data) and
216 not self.layer._preserve_input_structure_in_config):
217 data = [data]
218 data = tf_utils.convert_inner_node_data(data)
219 return data
221 #############################################################
222 # Properties for Backwards compatibility.
223 # These only check the first input argument
224 # As nodes are internal, they may be removed in the future.
225 #############################################################
227 @property
228 def input_tensors(self):
229 if self.is_input:
230 return [self.outputs] # Used in `Layer.input`.
231 return self.call_args[0]
233 @property
234 def output_tensors(self):
235 if self.is_input:
236 return [self.outputs] # Used in `Layer.input`.
237 return self.outputs
239 @property
240 def input_shapes(self):
241 input_shapes = nest.map_structure(backend.int_shape, self.input_tensors)
242 if len(input_shapes) == 1 and not self.is_input:
243 return input_shapes[0]
244 return input_shapes
246 @property
247 def output_shapes(self):
248 return nest.map_structure(backend.int_shape, self.output_tensors)
250 @property
251 def outbound_layer(self):
252 return self.layer
254 @property
255 def inbound_layers(self):
256 if self.is_input:
257 return []
258 inbound_layers = nest.map_structure(lambda t: t._keras_history.layer,
259 self.call_args[0])
260 return inbound_layers
263class KerasHistory(
264 collections.namedtuple('KerasHistory',
265 ['layer', 'node_index', 'tensor_index'])):
266 """Tracks the Layer call that created a Tensor, for Keras Graph Networks.
268 During construction of Keras Graph Networks, this metadata is added to
269 each Tensor produced as the output of a Layer, starting with an
270 `InputLayer`. This allows Keras to track how each Tensor was produced, and
271 this information is later retraced by the `keras.engine.Network` class to
272 reconstruct the Keras Graph Network.
274 Attributes:
275 layer: The Layer that produced the Tensor.
276 node_index: The specific call to the Layer that produced this Tensor. Layers
277 can be called multiple times in order to share weights. A new node is
278 created every time a Layer is called.
279 tensor_index: The output index for this Tensor. Always zero if the Layer
280 that produced this Tensor only has one output. Nested structures of
281 Tensors are deterministically assigned an index via `nest.flatten`.
282 """
283 # Added to maintain memory and performance characteristics of `namedtuple`
284 # while subclassing.
285 __slots__ = ()
288def is_keras_tensor(obj):
289 return hasattr(obj, '_keras_history')