Keras Model Accuracy differs after loading the same saved model
ML System Design practice on Codemia
Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.
A common and frustrating experience when working with Keras is saving a trained model, loading it back, and finding that the evaluation accuracy no longer matches what you saw during training. This discrepancy does not mean the model weights were corrupted. It is almost always caused by subtle differences in the evaluation environment. Understanding the root causes will help you deploy models with confidence.
Non-Deterministic Operations
Keras and TensorFlow use operations that can produce slightly different results between runs, especially on GPUs. Parallel floating-point reductions (such as summing across threads) can accumulate rounding errors in different orders depending on thread scheduling.
To reduce this variability, set random seeds at the start of your script:
The environment variable TF_DETERMINISTIC_OPS forces TensorFlow to use deterministic implementations of GPU kernels, which eliminates one source of variation at the cost of some speed.
Missing custom_objects During Loading
If your model uses custom layers, custom loss functions, or custom metrics, you must pass them in the custom_objects dictionary when loading. Without this, Keras either raises an error or silently substitutes a default implementation that behaves differently.
If you are using the newer .keras format (Keras 3), custom objects registered with @tf.keras.utils.register_keras_serializable are resolved automatically. However, explicitly passing custom_objects is still the safest approach.
The compile=False Trap
When you call load_model with compile=False, Keras loads only the architecture and weights without restoring the optimizer state, loss function, or metrics. If you then call model.evaluate() without recompiling, Keras raises an error. If you recompile with different settings, the accuracy metric may be computed differently.
Make sure the loss, metrics, and any class weights match exactly what you used during training. Even switching from "accuracy" to tf.keras.metrics.CategoricalAccuracy() can produce different numeric results due to floating-point accumulation order.
Batch Normalization Inference Mode
Batch normalization layers behave differently during training and inference. During training, they normalize using the current batch statistics. During inference, they use the running mean and variance accumulated over all training batches.
If you accidentally evaluate in training mode, the batch statistics from your test data will be used instead, producing different accuracy numbers. This can happen if you call the model with training=True by mistake:
Always use model.predict() or explicitly pass training=False when evaluating.
Evaluation Data Shuffle
If your evaluation data is shuffled differently between the original evaluation and the post-load evaluation, accuracy computed on individual batches will differ (though the overall accuracy across the full dataset should converge). More importantly, if you only evaluate on a subset of the data, the shuffle order determines which samples are included.
Never shuffle your test dataset. If you loaded it with image_dataset_from_directory, pass shuffle=False explicitly.
Common Pitfalls
- Evaluating on different data subsets. If you use a generator or a shuffled dataset, the samples seen during evaluation may differ between runs. Always disable shuffling for test data.
- Forgetting to set TF_DETERMINISTIC_OPS. Without this environment variable, GPU reductions can produce slightly different results on each run, even with all random seeds fixed.
- Using compile=False and forgetting to recompile. The model loads without metrics, so
evaluate()either fails or returns meaningless numbers. Always recompile with the identical configuration. - Saving with model.save_weights() instead of model.save(). The weights-only format does not save the optimizer state or architecture. You must reconstruct the model in code and call
load_weights(), which is error-prone. - Not pinning library versions. A TensorFlow minor version upgrade can change the default behavior of layers like BatchNormalization or the implementation of certain loss functions. Pin your dependency versions for reproducible results.
Summary
- Non-deterministic GPU operations cause small accuracy variations. Set
TF_DETERMINISTIC_OPS=1and fix all random seeds to minimize them. - Always pass
custom_objectswhen loading models that use custom layers, losses, or metrics. - If you use
compile=False, recompile with the exact same optimizer, loss, and metrics before evaluating. - Batch normalization layers must run in inference mode during evaluation. Use
model.predict()or passtraining=False. - Disable shuffling on your test dataset so that evaluation results are consistent across runs.
Related reading
- Keras model gets constant loss and accuracy
- Keras model LSTM predict 2 features
- keras model subclassing examples
- keras model.fit_generator several times slower than model.fit
- Keras model accuracy drops after reaching 99 percent accuracy and loss 0.01
- Keras Model predicts NaN
- Keras Model saving erroring TypeError get_config missing 1 required positional argument 'self
- Keras model working fine locally but won't work on Flask API
.png&w=3840&q=75)
Tackling System Design Interview Problems
A short course that equips you with the skills to approach system design interviews methodically.
Start the free courseTrack what you have practised
A free account saves your progress, solutions and study plan across every problem on Codemia.
ML System Design practice on Codemia
Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.