Keras
deep learning
model training
validation loss
accuracy issues

Keras - Validation `Loss` and Accuracy stuck at 0

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

Validation loss and validation accuracy staying at 0 almost never means the model is performing perfectly. It usually means the validation loop is not receiving usable data, the labels do not match the loss function, or the evaluation pipeline is misconfigured.

Make Sure Validation Is Really Happening

The first question is simple: does Keras actually have validation examples to evaluate? If validation_data is empty, if a generator yields no batches, or if validation_steps is wrong, the logged metrics can be meaningless.

A basic fit call should look like this:

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

Before you tune the model, print the shapes and verify that the validation arrays contain real samples:

python
print("train:", x_train.shape, y_train.shape)
print("val:", x_val.shape, y_val.shape)

If you are using tf.data, inspect the dataset cardinality as well:

python
1import tensorflow as tf
2
3print(tf.data.experimental.cardinality(train_ds).numpy())
4print(tf.data.experimental.cardinality(val_ds).numpy())

If the validation dataset is empty, repeated indefinitely in the wrong way, or built from the wrong files, no amount of architecture tuning will help.

Match Labels, Output Layer, and Loss Function

A very common source of nonsense metrics is a mismatch between label encoding and loss selection. Keras will often run even when your configuration is conceptually wrong.

Binary classification usually looks like this:

python
1import tensorflow as tf
2
3model = tf.keras.Sequential([
4    tf.keras.layers.Input(shape=(10,)),
5    tf.keras.layers.Dense(16, activation="relu"),
6    tf.keras.layers.Dense(1, activation="sigmoid"),
7])
8
9model.compile(
10    optimizer="adam",
11    loss="binary_crossentropy",
12    metrics=["accuracy"],
13)

Multiclass classification with one-hot labels should instead use softmax with categorical_crossentropy:

python
1model = tf.keras.Sequential([
2    tf.keras.layers.Input(shape=(10,)),
3    tf.keras.layers.Dense(32, activation="relu"),
4    tf.keras.layers.Dense(3, activation="softmax"),
5])
6
7model.compile(
8    optimizer="adam",
9    loss="categorical_crossentropy",
10    metrics=["accuracy"],
11)

If your labels are integer class IDs such as 0, 1, and 2, then sparse_categorical_crossentropy is the correct loss instead. Mixing these setups can produce training logs that look broken or flat.

Inspect the Validation Labels Directly

Do not assume the validation labels are correct just because the dataset loaded successfully. A bug in splitting, shuffling, or preprocessing can leave you with a validation set containing only one class or misaligned labels.

python
1import numpy as np
2
3print("train labels:", np.unique(y_train, return_counts=True))
4print("val labels:", np.unique(y_val, return_counts=True))

If y_val contains only zeros because of a faulty export step, the validation metrics may appear stuck. The same thing happens when image generators read the right files but assign the wrong class order.

When using ImageDataGenerator or a custom generator, verify:

  • the validation directory has files
  • 'class_mode matches the loss'
  • the generator is not accidentally using training labels
  • the batches contain both features and targets in the expected format

Be Careful With validation_steps and Custom Generators

Manually setting steps_per_epoch or validation_steps is useful only when you know exactly how many batches the iterator should produce. Wrong values can skip part of the data or evaluate nothing useful.

python
1history = model.fit(
2    train_ds,
3    validation_data=val_ds,
4    epochs=10,
5)

In many cases it is safer to let Keras infer the steps automatically. If you do set them yourself, confirm the numbers match the dataset size and batch size.

A quick debugging trick is to pull one batch from the validation input and inspect it:

python
for features, labels in val_ds.take(1):
    print(features.shape)
    print(labels[:5])

This exposes shape mismatches immediately. If labels are missing, if features are all zeros, or if the batch layout is not what the model expects, you will see it before another wasted training run.

Run a Tiny Overfitting Test

If the full training job is hard to reason about, shrink the problem. Use a tiny dataset and try to overfit it.

python
1small_x = x_train[:64]
2small_y = y_train[:64]
3
4history = model.fit(
5    small_x,
6    small_y,
7    epochs=50,
8    verbose=0,
9)
10
11print(history.history["loss"][-1])

If the model cannot overfit a tiny clean batch, the issue is usually in data formatting, label encoding, or model configuration rather than generalization. This test removes most of the noise from the debugging process.

Common Pitfalls

The most frequent mistake is pairing the wrong loss with the label encoding, such as one-hot labels with sparse_categorical_crossentropy or integer labels with categorical_crossentropy.

Another common issue is an empty or malformed validation dataset caused by bad splits, wrong generator paths, or incorrect validation_steps. Developers also sometimes chase optimizer settings too early when the real problem is that validation labels are misaligned or the metric is evaluating the wrong target shape.

Finally, inspect predictions before guessing. If model.predict on a small validation batch returns obviously invalid output, your bug is almost always in the pipeline, not in the fact that the network is "too simple."

Summary

  • Validation metrics stuck at 0 usually indicate a broken evaluation pipeline, not a perfect model.
  • Verify that validation data exists and that Keras is actually consuming it.
  • Make sure the output layer, label encoding, and loss function all match.
  • Inspect labels and sample batches directly when using generators or tf.data.
  • Use a tiny overfitting test to separate pipeline bugs from real modeling issues.

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.