|
22 | 22 |
|
23 | 23 | PackedTensors = Union[tf.Tensor, Iterable[tf.Tensor], Dict[Text, tf.Tensor]] |
24 | 24 |
|
25 | | -GeneratorFunction = Optional[Callable[[Any, Tuple, Dict], Tuple[Any, Any]]] |
| 25 | +GeneratorFunction = Callable[[Any, Tuple, Dict], Tuple[Any, Any]] |
| 26 | + |
| 27 | +LayerFunction = Callable[[tf.keras.layers.Layer], None] |
26 | 28 |
|
27 | 29 |
|
28 | 30 | def has_internal_compute_graph(input_object: Any): |
@@ -52,7 +54,7 @@ def _get_internal_layers( |
52 | 54 | def model_forward_pass( |
53 | 55 | input_model: tf.keras.Model, |
54 | 56 | inputs: PackedTensors, |
55 | | - generator_fn: GeneratorFunction = None, |
| 57 | + generator_fn: Optional[GeneratorFunction] = None, |
56 | 58 | ) -> Tuple[PackedTensors, List[Any]]: |
57 | 59 | """Does a forward pass of a model and returns useful intermediates. |
58 | 60 |
|
@@ -211,6 +213,55 @@ def add_noise(g): |
211 | 213 | return tf.nest.map_structure(add_noise, clipped_grads) |
212 | 214 |
|
213 | 215 |
|
| 216 | +def depth_first_backward_pass( |
| 217 | + outputs: PackedTensors, layer_function: Optional[LayerFunction] = None |
| 218 | +): |
| 219 | + """Performs a depth-first traversal on a given set of model outputs. |
| 220 | +
|
| 221 | + This function is simplified version of |
| 222 | + `tf.keras.engine.functional._build_map()` that allows additional side-effects |
| 223 | + performed by an (optional) layer function. |
| 224 | +
|
| 225 | + NOTE: The behavior, name, and implementation details of this function may |
| 226 | + change in future versions. Users should avoid using it outside of this module. |
| 227 | +
|
| 228 | + Args: |
| 229 | + outputs: A `PackedTensor` that should be generated by calling a |
| 230 | + `tf.keras.Model` on a set of non-eager inputs. |
| 231 | + layer_function: A callable that consumes a `tf.keras.layers.Layer`. This |
| 232 | + callable is applied to every layer in the DAG that generates `outputs`. |
| 233 | + """ |
| 234 | + |
| 235 | + # Helper function that performs the traversal. |
| 236 | + finished_nodes = set() |
| 237 | + nodes_in_progress = set() |
| 238 | + |
| 239 | + def graph_crawler(tensor: tf.Tensor): |
| 240 | + layer, node_index, _ = tensor._keras_history # pylint: disable=protected-access |
| 241 | + node = layer._inbound_nodes[node_index] # pylint: disable=protected-access |
| 242 | + # Avoid duplicating work on shared subgraphs. |
| 243 | + if node in finished_nodes: |
| 244 | + return |
| 245 | + # Check if we encountered a cycle. |
| 246 | + if node in nodes_in_progress: |
| 247 | + raise ValueError( |
| 248 | + f'Tensor {tensor} from layer "{layer.name}" is part of a cycle.' |
| 249 | + ) |
| 250 | + # Apply side-effects and go to the next node (pre-order traversal). |
| 251 | + if layer_function is not None: |
| 252 | + layer_function(layer) |
| 253 | + nodes_in_progress.add(node) |
| 254 | + if not node.is_input: |
| 255 | + for tensor in node.keras_inputs: |
| 256 | + graph_crawler(tensor) |
| 257 | + finished_nodes.add(node) |
| 258 | + nodes_in_progress.remove(node) |
| 259 | + |
| 260 | + # Traverse over the outputs. |
| 261 | + for output in tf.nest.flatten(outputs): |
| 262 | + graph_crawler(output) |
| 263 | + |
| 264 | + |
214 | 265 | def generate_model_outputs_using_core_keras_layers( |
215 | 266 | input_model: tf.keras.Model, |
216 | 267 | ) -> PackedTensors: |
|
0 commit comments