Why am I having KeyError 'val_acc'?
ML System Design practice on Codemia
Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.
Introduction
KeyError: 'val_acc' usually appears when code expects the training history dictionary to contain val_acc, but newer TensorFlow/Keras versions store validation accuracy under val_accuracy. This is a version and metric-name mismatch, not a model-training failure by itself.
The fix is to inspect the keys actually present in history.history and reference metrics dynamically. Hard-coded legacy names from old tutorials often break in modern environments. A small compatibility helper can make plotting and monitoring code robust across versions.
Core Sections
1. Understand where the key comes from
After training, Keras returns a History object:
In current TensorFlow/Keras, common keys include:
lossaccuracyval_lossval_accuracy
Older examples may reference acc and val_acc, causing KeyError when those keys do not exist.
2. Write version-tolerant metric access
Use fallback logic instead of hard-coded names.
This avoids brittle plotting code across environments.
3. Ensure metric is actually configured
If you did not compile with accuracy metrics, validation accuracy keys will never exist.
Then plotting is straightforward:
Also verify callback code, checkpoints, and custom loggers use the same metric names.
Common Pitfalls
- Copying old tutorial code that references
acc/val_accin modern TensorFlow environments. - Forgetting to include
metrics=["accuracy"]duringcompile, so accuracy keys are never logged. - Assuming callback monitor names (
monitor="val_acc") are version-invariant. - Not printing
history.history.keys()before plotting/debugging metric access. - Mixing standalone
kerasandtf.kerasversions, which can produce inconsistent naming behavior.
Summary
KeyError: 'val_acc' is typically a metric key naming mismatch. Inspect actual history keys, use compatibility fallbacks (val_accuracy vs val_acc), and ensure metrics are configured during compile. Once metric names are aligned across training, callbacks, and plotting, the error disappears and logs remain stable across library upgrades.
In team environments, metric naming drift is often introduced by mixed examples, old notebooks, or partial framework upgrades. A good defensive step is to centralize metric key resolution in one utility module and import that everywhere plots, callbacks, and alerts are configured. Then when names change again in future framework versions, you update one place instead of many scripts. This reduces breakage during upgrades and keeps dashboards consistent.
You can also harden training pipelines by validating metric availability immediately after the first epoch. If required keys are missing, fail fast with an explicit message instead of waiting for a later plotting or checkpoint step to throw an opaque KeyError. For example, after fit, compare expected keys against history.history.keys() and raise a custom exception with remediation guidance. This turns a confusing runtime failure into a clear contract check.
For monitoring systems, prefer explicit callback monitor names tied to your resolved metric key. If your compatibility helper decides on val_accuracy, your early stopping and model checkpoint monitors should use the same resolved value. Keeping training metrics, visualization, and callbacks aligned avoids situations where one component silently tracks a different metric than another. Consistency across these layers is what actually eliminates recurring val_acc errors.
A brief post-upgrade checklist can prevent recurrence: print history keys, verify callback monitor names, and run one plotting smoke test in CI. These checks are cheap and catch metric-name drift before training jobs reach production pipelines.
Make this check part of every dependency upgrade branch.
Related reading
- Why are deep learning libraries so huge?
- Why batch normalization over channels only in CNN
- Why Bert transformer uses CLS token for classification instead of average over all tokens?
- Why Bert transformer uses CLS token for classification instead of average over all tokens?
- Why am I seeing TypeError string indices must be integers?
- Why are Python's arrays slow?
- Why am I not getting a java.util.ConcurrentModificationException in this example?
- Why am I seeing Promise pending from this code on repl.it?
.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.