Loading a SavedModel in Keras 3 loses any mask information on the model output. Loading a SavedModel in tf-keras preserves the mask information, as expected. Here is an example:
USE_KERAS_3 = True
import tensorflow as tf
if USE_KERAS_3:
import keras
else:
import tf_keras as keras
inp = keras.Input((1,))
x = keras.layers.Masking(mask_value=0.0)(inp)
model = keras.Model(inp, x)
if USE_KERAS_3:
model.export("saved_model")
loaded = keras.layers.TFSMLayer("saved_model")
else:
model.save("saved_model", save_format="tf")
loaded = keras.models.load_model("saved_model")
print(loaded(tf.zeros((1, 1)))._keras_mask)
With USE_KERAS_3=True
this gives:
AttributeError: 'tensorflow.python.framework.ops.EagerTensor' object has no attribute '_keras_mask'
With USE_KERAS_3=False
this gives:
tf.Tensor([False], shape=(1,), dtype=bool)
Comment From: sonali-kumari1
Hi @drasmuss -
I tried to reproduce the issue and I got AttributeError: 'tensorflow.python.framework.ops.EagerTensor' object has no attribute '_keras_mask'
with latest version of keras(3.9.2). It seems using model.export
to save and loading model with loaded = keras.layers.TFSMLayer("saved_model")
results in the mask information being lost.
However, I tried to save the model using model.save("saved_model.keras")
and loaded the model using loaded = keras.models.load_model("saved_model.keras")
and I was able to get the following output tensor, without any error:
tf.Tensor([False], shape=(1,), dtype=bool)
Attaching gist for your reference. Thanks!
Comment From: drasmuss
Unfortunately the model I am working with uses custom layers, and I need to be able to load the model in an environment where the original source code isn't available, so I have to use the SavedModel format, not the Keras format.
Comment From: sonali-kumari1
@drasmuss, If you are loading model with loaded = keras.layers.TFSMLayer("saved_model")
, please note that the reloaded object retains none of the internal structure or custom methods of the original object. Since you are using custom layers, I would suggest implementing masking logic directly in your call()
method to ensure it becomes a part of the saved computation graph. Also, make sure to implement get_config()
and from_config()
methods for your custom layers.
Please refer to this documentation for more details on TFSMLayer. Thanks!
Comment From: drasmuss
The SavedModel saving and loading works in tf-keras, so I don't think it's a problem with the custom layer code. Also note that the example above doesn't use any custom layers. The issue is specifically with Keras 3, and how it implements the saving/loading.
Comment From: divyashreepathihalli
@drasmuss, having your build() method and get_config() and from_config(), should be accurately implemented for saving and loading to be accurate. Yes TF backend is more forgiving for saving and loading and other backends are not.
Comment From: drasmuss
As mentioned, in the example above I'm using a standard, built-in Keras layer (keras.layers.Masking
), so it has nothing to do with my implementation.
Comment From: hertschuh
@drasmuss ,
Unfortunately the model I am working with uses custom layers, and I need to be able to load the model in an environment where the original source code isn't available, so I have to use the SavedModel format, not the Keras format.
Can you elaborate on your use case? Is this for serving/inference?
You say the original source code isn't available. Is the Keras source code available?
This:
loaded = keras.models.load_model("saved_model")
Requires the source code to be available. It does some magic to reconnect the saved model to the Python code.
However, if you do:
loaded = tf.saved_model.load("saved_model_keras2").signatures["serving_default"]
This does not require the source code, but the masks won't work either.
The pure graph representation of the exported function in a "saved model" does not support masks. Doing model.save("saved_model", save_format="tf")
adds some information in addition to the "saved model" graph, which is then used by keras.models.load_model("saved_model")
to reconnect the graph to the code. But that requires the code.
The workaround is to return masks explicitly. With this, your saved model will return a dict with "output" and "mask".
@tf.function
def fn(x):
output = model(x)
return {"output": output, "mask": output._keras_mask}
export_archive = keras.export.ExportArchive()
export_archive.track(model)
export_archive.add_endpoint(
"serve",
fn,
input_signature=[tf.TensorSpec(shape=(1, 1), dtype=tf.float32)],
)
export_archive.write_out("saved_model")
loaded = keras.layers.TFSMLayer("saved_model")
print(loaded(tf.zeros((1, 1))))
{'mask': <tf.Tensor: shape=(1,), dtype=bool, numpy=array([False])>, 'output': <tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[0.]], dtype=float32)>}
If you want to get fancy, you can then create a wrapper to recombine them:
def loaded_wrapper(input):
output_dict = loaded(input)
output = output_dict["output"]
output._keras_mask = output_dict["mask"]
return output
print(loaded_wrapper(tf.zeros((1, 1)))._keras_mask)
tf.Tensor([False], shape=(1,), dtype=bool)
Comment From: github-actions[bot]
This issue is stale because it has been open for 14 days with no activity. It will be closed if no further activity occurs. Thank you.
Comment From: github-actions[bot]
This issue was closed because it has been inactive for 28 days. Please reopen if you'd like to work on this further.