keras
keras copied to clipboard
Extract Nested Layers from Encapsulated Model
System information.
- Have I written custom code (as opposed to using a stock example script provided in Keras): No
- OS Platform and Distribution (e.g., Linux Ubuntu 16.04): Windows 10
- TensorFlow installed from (source or binary): binary
- TensorFlow version (use command below):
- Python version: 3.7
- Bazel version (if compiling from source):
- GPU model and memory:
- Exact command to reproduce:
Describe the problem and current behavior
Encapsulated Model - It can be expressed as a whole model but act as a single layer. Please see the description below for a better understanding.
Case 1
Let's say, at first you initiate a pretrained model from keras. applications. And next, place it to Sequential API to create a final model. Later, you successfully train the model. And then, you want to get the intermediate feature maps from that keras. applications model.
IMG_SHAPE = (224, 224, 3)
pretrained_model = keras.applications.EfficientNetB0(
weights='imagenet',
include_top=False,
input_shape=IMG_SHAPE,
pooling='avg'
)
pretrained_model.trainable = False
model = keras.Sequential([
pretrained_model,
keras.layers.Dense(512, activation='relu'),
keras.layers.Dense(3, activation='softmax'),
])
for l in model.layers: print(l.name, l.output_shape)
efficientnetb0 (None, 1280)
dense_5 (None, 512)
dense_6 (None, 3)
nested_layers = tf.keras.models.Model(
[model.inputs],
[model.layers[0].get_layer('top_activation').output, model.output]
)
It gives
ValueError: Graph disconnected: cannot obtain value for tensor KerasTensor(type_spec=TensorSpec(shape=(None, 224, 224, 3), dtype=tf.float32, name='input_1'), name='input_1', description="created by layer 'input_1'") at layer "rescaling". The following previous layers were accessed without issue: []
[continue]
Case 2
It's not only limited to Sequential API but also some cases with Functional API. See another example below.
inputs = keras.Input(shape=IMG_SHAPE)
x = pretrained_model(inputs)
x = keras.layers.Dense(512, activation='relu')(x)
outputs = keras.layers.Dense(3, activation='softmax')(x)
model = keras.Model(inputs, outputs)
for l in model.layers:
print(l.name, l.output_shape)
input_7 [(None, 224, 224, 3)]
efficientnetb0 (None, 1280)
dense_7 (None, 512)
dense_8 (None, 3)
nested_layers = tf.keras.models.Model(
[model.inputs],
[model.layers[1].get_layer('top_activation').output, model.output]
)
It also gives
ValueError: Graph disconnected: cannot obtain value for tensor KerasTensor(type_spec=TensorSpec(shape=(None, 224, 224, 3), dtype=tf.float32, name='input_6'), name='input_6', description="created by layer 'input_6'") at layer "rescaling_3". The following previous layers were accessed without issue: []
[continue]
The Current Workaround
Currently, we need to flatten the base model (above pretrained_model). It means, disclosing the target base model in the following manner.
inputs = keras.Input(shape=IMG_SHAPE)
pretrained_model = keras.applications.EfficientNetB0(
weights='imagenet',
include_top=False,
input_tensor=inputs,
pooling='avg'
)
pretrained_model.trainable = False
x = keras.layers.Dense(512, activation='relu')(pretrained_model.output)
outputs = keras.layers.Dense(3, activation='softmax')(x)
model = keras.Model(inputs, outputs)
nested_layers = tf.keras.models.Model(
[model.inputs],
[model.get_layer('top_activation').output, model.output]
)
# OK
Describe the expected behavior.
The case 1 and case 2, described above should be done like the above-mentioned current workaround.
- Do you want to contribute a PR? (yes/no): Yes, but need some design guidance.
- If yes, please read this page for instructions
- Briefly describe your candidate solution(if contributing): It's mentioned in the current workaround section. Please see.
Standalone code to reproduce the issue.
It's already provided in the above description. Please see.
[End]
@gadagashwini I was able to replicate the issue on colab using TF v2.8.0 , please find the gist.Thanks!
I think the error message could be better, but the behavior here is logical.
When you create the EfficientNet model, you create a first graph of layers linking pretrained_model.inputs to pretrained_model.get_layer('top_activation').output. Then you create a second graph built on top of your own Input object.
Does that make sense so far?
Because the top layer has been called twice, pretrained_model.get_layer('top_activation').output is now undefined -- there are actually 2 outputs, one of the first call and one for the second call. The one for the second call is tracked at the level of the output nodes of pretrained_model. When you retrieve pretrained_model.get_layer('top_activation').output you are getting the first output, but you were expecting the second one. This is why your graph is disconnected.
To access the second output, the one you're looking for, you should be able to do:
nested_layers = keras.Model(
[model.inputs], # This is the Input() you've created yourself
[pretrained_model.inbound_nodes[0].output_tensors, # This is output created when you called `pretrained_model()` on your own `Input()`
model.output] # This is the output of the final `Dense()` layer you created
)
Let me know if this is clear.
@fchollet yes, that makes sense. Thanks for your details answer. I also understood that it's not a bug, I should mention that earlier. The thing is, I saw many practitioners face such issues after training their models and later trying to compute grad-cam. It's one of the common keras-tagged questions on the stack. The fact is the modeling approach (model above) is so typical that people usually go with it and later face such issues (with nested_layers above).
To access the second output, the one you're looking for, you should be able to do:
About this workaround, in case 1/2,
nested_layers = keras.Model([
model.inputs
],[
pretrained_model.inbound_nodes[0].output_tensors,
model.output
]
)
This inbound_nodes gives the last layer output of the pretrained model (None, 1280). Is there any way we can get an intermediate 2D layer (i.e. top_activation)? Also, when I did
pretrained_model.inbound_nodes?
Type: property
String form: <property object at 0x7ff9f6f22d70>
Docstring: Deprecated, do NOT use!
Only for compatibility with external Keras.
Is it safe to use for now and later?
cc @gadagashwini
@lucasdavid any thoughst on this? I saw some utility from here, looks promising.
As far as I am aware, in a previous version of Keras:
Using a model as a layer of another model would call Layer.__call__ for every sub-component of the inner model, creating nodes for each intermediate signal (see v2.0.0/engine/topology.py#L573).
The new nodes were stacked in the _inbound_nodes property of the inner model's layers, and we could extract them with this:
nested_layers = keras.Model(
[model.inputs],
[model.get_layer("inner_model").get_layer("post_relu").get_output_at(1),
model.output] # This is the output of the final `Dense()` layer you created
)
This solution is better discussed in #34977#issuecomment-571878943.
In the current version:
Calling Model.__call__ will not call Layer.__call__ for its composing layers, but treat the intermediate operations of the inner model as a single encapsulated function.
A new node will be stacked in the _inbound_nodes list of the inner model itself (see base_layer.py#L2589), but the intermediate signals will not be available.
In other words, calling inner_model.get_layer("post_relu").get_output_at(1) raises an error, as <layer post_relu>._inbound_nodes is a list of a single element. Calling inner_model.get_output_at(1) does not raise an error, but it is not the output you need...
About the utility you mentioned: I kept it in my own lib (keras-explainable), in case the user has an older version of keras, but I have skipped the testing of this functionality on master. As of today, I believe avoiding the nesting of the layers of interest (e.g. GAP) is the only viable solution.
@innat has this problem been fixed? How does the new API behave?
Hi @lucasdavid, I didn't test this with the new API yet.