TensorFlow
deep learning
machine learning
data visualization
model training

Show training and validation accuracy in TensorFlow using same graph

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

The standard way to plot training and validation accuracy on the same graph in TensorFlow is to use the History object returned by model.fit(). As long as training includes validation data, Keras records both accuracy and val_accuracy, and you can plot them on the same axes with Matplotlib.

Train the model with validation enabled

First, make sure model.fit() is actually collecting validation metrics:

python
1history = model.fit(
2    x_train,
3    y_train,
4    epochs=10,
5    batch_size=32,
6    validation_data=(x_val, y_val),
7)

If you skip validation_data= or validation_split=, there is no val_accuracy series to plot later.

Plot both accuracy curves on one graph

The History object stores metrics in a dictionary called history.history:

python
1import matplotlib.pyplot as plt
2
3train_acc = history.history["accuracy"]
4val_acc = history.history["val_accuracy"]
5epochs = range(1, len(train_acc) + 1)
6
7plt.plot(epochs, train_acc, label="Training accuracy")
8plt.plot(epochs, val_acc, label="Validation accuracy")
9plt.xlabel("Epoch")
10plt.ylabel("Accuracy")
11plt.title("Training vs Validation Accuracy")
12plt.legend()
13plt.grid(True)
14plt.show()

Because both series share the same epoch axis, the result is a single chart that makes divergence easy to see.

A complete minimal example

Here is a compact end-to-end example with Keras:

python
1import tensorflow as tf
2import matplotlib.pyplot as plt
3
4model = tf.keras.Sequential([
5    tf.keras.layers.Dense(64, activation="relu"),
6    tf.keras.layers.Dense(10, activation="softmax"),
7])
8
9model.compile(
10    optimizer="adam",
11    loss="sparse_categorical_crossentropy",
12    metrics=["accuracy"],
13)
14
15history = model.fit(
16    x_train,
17    y_train,
18    epochs=10,
19    validation_data=(x_val, y_val),
20    verbose=1,
21)
22
23plt.plot(history.history["accuracy"], label="train")
24plt.plot(history.history["val_accuracy"], label="validation")
25plt.xlabel("Epoch")
26plt.ylabel("Accuracy")
27plt.legend()
28plt.show()

That is enough for most training notebooks and scripts.

Why the same graph is useful

Training and validation accuracy are most useful when read together. If training accuracy rises steadily while validation accuracy stalls or drops, the model may be overfitting. If both stay low, the model may be underfitting or the data pipeline may need work.

Putting them on one graph also makes training changes easier to compare across runs. You can quickly see whether a new optimizer, regularization setting, or augmentation strategy narrows the gap between the two curves.

If you want loss on the same figure as well, use a second subplot rather than mixing loss and accuracy on one axis. Accuracy and loss move on different scales, so separate panels are easier to read:

python
1fig, axes = plt.subplots(1, 2, figsize=(12, 4))
2
3axes[0].plot(history.history["accuracy"], label="train")
4axes[0].plot(history.history["val_accuracy"], label="validation")
5axes[0].set_title("Accuracy")
6axes[0].legend()
7
8axes[1].plot(history.history["loss"], label="train")
9axes[1].plot(history.history["val_loss"], label="validation")
10axes[1].set_title("Loss")
11axes[1].legend()
12
13plt.tight_layout()
14plt.show()

That gives you a fuller view of training dynamics without turning the chart into a cluttered mix of unrelated scales.

For notebook workflows, it is also helpful to save the figure after training so later experiments can be compared side by side without rerunning the entire model.

Common Pitfalls

The most common issue is using the wrong metric names. In modern TensorFlow and Keras, the keys are usually accuracy and val_accuracy. Older examples may use acc and val_acc, so do not copy those names blindly.

Another problem is forgetting to enable validation during training. In that case, the training graph works, but history.history["val_accuracy"] raises a key error because the metric was never recorded.

Be careful when comparing runs with different epoch counts or different validation sets. A clean graph does not help if the underlying experiment setup changed in ways that make the curves incomparable.

Finally, accuracy is not always the best metric. For imbalanced classification tasks, precision, recall, F1 score, or AUC may tell a more useful story than raw accuracy alone.

Summary

  • Use the History object from model.fit() to access recorded metrics.
  • Train with validation_data or validation_split so val_accuracy exists.
  • Plot history.history["accuracy"] and history.history["val_accuracy"] on the same axes.
  • Use the combined graph to spot overfitting and compare training behavior across runs.
  • Double-check metric names and experiment setup before interpreting the curves.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track 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.

Practice ML system design

All Rights Reserved.