Long-running Keras training can spend extra epochs after the validation metric has stopped improving. EarlyStopping watches a metric from model.fit() and ends training early, which helps when a run has a fixed epoch budget but the best validation state appears sooner.
The callback reads epoch-end values from the training logs. Monitoring val_loss requires validation data, and any custom metric named in monitor must be available from the compiled model so the callback can see it in the log dictionary.
The smoke test uses mode=“min” for validation loss, patience=2 for two stale epochs, and restore_best_weights=True so the in-memory model returns to the best monitored epoch. Add a checkpoint callback as well when the best model must survive the Python process ending.
early_stopping_demo.py
.
import keras import numpy as np keras.utils.set_random_seed(42) x_train = np.linspace(-1.0, 1.0, 120, dtype="float32").reshape(-1, 1) y_train = (2.0 * x_train) + 0.5 x_val = np.linspace(-0.8, 0.8, 40, dtype="float32").reshape(-1, 1) y_val = (2.0 * x_val) + 0.5 model = keras.Sequential( [ keras.layers.Input(shape=(1,)), keras.layers.Dense(8, activation="relu"), keras.layers.Dense(1), ] ) model.compile( optimizer=keras.optimizers.SGD(learning_rate=0.35), loss="mse", metrics=["mae"], ) early_stop = keras.callbacks.EarlyStopping( monitor="val_loss", mode="min", min_delta=0.0001, patience=2, restore_best_weights=True, verbose=1, ) history = model.fit( x_train, y_train, validation_data=(x_val, y_val), epochs=20, batch_size=16, callbacks=[early_stop], verbose=0, ) best_epoch = int(np.argmin(history.history["val_loss"])) + 1 best_val_loss = min(history.history["val_loss"]) restored_val_loss, restored_val_mae = model.evaluate(x_val, y_val, verbose=0) print("epochs requested: 20") print(f"epochs run: {len(history.history['loss'])}") print(f"best epoch: {best_epoch}") print(f"best val_loss: {best_val_loss:.4f}") print(f"restored val_loss: {restored_val_loss:.4f}") print(f"history keys: {sorted(history.history.keys())}")
Set the Keras backend before Python imports keras when the project uses JAX or PyTorch instead of the default backend.
Related: How to set the Keras backend
$ python3 early_stopping_demo.py Epoch 3: early stopping Restoring model weights from the end of the best epoch: 1. epochs requested: 20 epochs run: 3 best epoch: 1 best val_loss: 0.0474 restored val_loss: 0.0474 history keys: ['loss', 'mae', 'val_loss', 'val_mae']
The exact metric values can vary by backend and hardware. A run that stops before epoch 20 and prints the same best and restored validation loss confirms the callback stopped training and restored the best monitored weights.
early_stop = keras.callbacks.EarlyStopping( monitor="val_loss", mode="min", min_delta=0.0001, patience=2, restore_best_weights=True, ) history = model.fit( train_features, train_labels, validation_data=(val_features, val_labels), epochs=100, callbacks=[early_stop], )
Use mode=“max” for metrics that should increase, such as val_accuracy. Add start_from_epoch when the model needs warm-up epochs before monitoring begins.
print(f"epochs run: {len(history.history['loss'])}") print(f"best val_loss: {min(history.history['val_loss']):.4f}")
history.history must include the monitored key. If val_loss is missing, pass validation data to model.fit() or choose a metric that is present in the training logs.
$ rm early_stopping_demo.py