stop training when a monitored metric has stopped improving assuming the goal of a training is to minimize the loss with this the metric to be monitored would be loss and mode would be min a model fit training loop will check at end of every epoch whether the loss is no longer decreasing considering the mindelta and patience if applicable once it s found no longer decreasing model stoptraining is marked true and the training terminates the quantity to be monitored needs to be available in logs dict to make it so pass the loss or metrics at model compile args monitor quantity to be monitored defaults to valloss mindelta minimum change in the monitored quantity to qualify as an improvement i e an absolute change of less than mindelta will count as no improvement defaults to 0 patience number of epochs with no improvement after which training will be stopped defaults to 0 verbose verbosity mode 0 or 1 mode 0 is silent and mode 1 displays messages when the callback takes an action defaults to 0 mode one of auto min max in min mode training will stop when the quantity monitored has stopped decreasing in max mode it will stop when the quantity monitored has stopped increasing in auto mode the direction is automatically inferred from the name of the monitored quantity defaults to auto baseline baseline value for the monitored quantity if not none training will stop if the model doesn t show improvement over the baseline defaults to none restorebestweights whether to restore model weights from the epoch with the best value of the monitored quantity if false the model weights obtained at the last step of training are used an epoch will be restored regardless of the performance relative to the baseline if no epoch improves on baseline training will run for patience epochs and restore weights from the best epoch in that set defaults to false startfromepoch number of epochs to wait before starting to monitor improvement this allows for a warmup period in which no improvement is expected and thus training will not be stopped defaults to 0 example callback keras callbacks earlystoppingmonitor loss patience3 this callback will stop the training when there is no improvement in the loss for three consecutive epochs model keras models sequentialkeras layers dense10 model compilekeras optimizers sgd loss mse history model fitnp arange100 reshape5 20 np zeros5 epochs10 batchsize1 callbackscallback verbose0 lenhistory history loss only 4 epochs are run 4 allow instances to be reused delay setup until the model s metrics are all built if no monitor value exists or still in initial warmup stage if best weights were never set then the current weights are the best only restart wait if we beat both the baseline and our previous best patience has been exceeded stop training stop training when a monitored metric has stopped improving assuming the goal of a training is to minimize the loss with this the metric to be monitored would be loss and mode would be min a model fit training loop will check at end of every epoch whether the loss is no longer decreasing considering the min_delta and patience if applicable once it s found no longer decreasing model stop_training is marked true and the training terminates the quantity to be monitored needs to be available in logs dict to make it so pass the loss or metrics at model compile args monitor quantity to be monitored defaults to val_loss min_delta minimum change in the monitored quantity to qualify as an improvement i e an absolute change of less than min_delta will count as no improvement defaults to 0 patience number of epochs with no improvement after which training will be stopped defaults to 0 verbose verbosity mode 0 or 1 mode 0 is silent and mode 1 displays messages when the callback takes an action defaults to 0 mode one of auto min max in min mode training will stop when the quantity monitored has stopped decreasing in max mode it will stop when the quantity monitored has stopped increasing in auto mode the direction is automatically inferred from the name of the monitored quantity defaults to auto baseline baseline value for the monitored quantity if not none training will stop if the model doesn t show improvement over the baseline defaults to none restore_best_weights whether to restore model weights from the epoch with the best value of the monitored quantity if false the model weights obtained at the last step of training are used an epoch will be restored regardless of the performance relative to the baseline if no epoch improves on baseline training will run for patience epochs and restore weights from the best epoch in that set defaults to false start_from_epoch number of epochs to wait before starting to monitor improvement this allows for a warm up period in which no improvement is expected and thus training will not be stopped defaults to 0 example callback keras callbacks earlystopping monitor loss patience 3 this callback will stop the training when there is no improvement in the loss for three consecutive epochs model keras models sequential keras layers dense 10 model compile keras optimizers sgd loss mse history model fit np arange 100 reshape 5 20 np zeros 5 epochs 10 batch_size 1 callbacks callback verbose 0 len history history loss only 4 epochs are run 4 allow instances to be re used delay setup until the model s metrics are all built if no monitor value exists or still in initial warm up stage if best weights were never set then the current weights are the best only restart wait if we beat both the baseline and our previous best patience has been exceeded stop training
import warnings

from keras import ops
from keras.api_export import keras_export
from keras.callbacks.callback import Callback
from keras.trainers import compile_utils
from keras.utils import io_utils


@keras_export("keras.callbacks.EarlyStopping")
class EarlyStopping(Callback):
    

    def __init__(
        self,
        monitor="val_loss",
        min_delta=0,
        patience=0,
        verbose=0,
        mode="auto",
        baseline=None,
        restore_best_weights=False,
        start_from_epoch=0,
    ):
        super().__init__()

        self.monitor = monitor
        self.patience = patience
        self.verbose = verbose
        self.baseline = baseline
        self.min_delta = abs(min_delta)
        self.wait = 0
        self.stopped_epoch = 0
        self.restore_best_weights = restore_best_weights
        self.best_weights = None
        self.start_from_epoch = start_from_epoch

        if mode not in ["auto", "min", "max"]:
            warnings.warn(
                f"EarlyStopping mode {mode} is unknown, fallback to auto mode.",
                stacklevel=2,
            )
            mode = "auto"
        self.mode = mode
        self.monitor_op = None

    def _set_monitor_op(self):
        if self.mode == "min":
            self.monitor_op = ops.less
        elif self.mode == "max":
            self.monitor_op = ops.greater
        else:
            metric_name = self.monitor.removeprefix("val_")
            if metric_name == "loss":
                self.monitor_op = ops.less
            if hasattr(self.model, "metrics"):
                all_metrics = []
                for m in self.model.metrics:
                    if isinstance(
                        m,
                        (
                            compile_utils.CompileMetrics,
                            compile_utils.MetricsList,
                        ),
                    ):
                        all_metrics.extend(m.metrics)
                for m in all_metrics:
                    if m.name == metric_name:
                        if hasattr(m, "_direction"):
                            if m._direction == "up":
                                self.monitor_op = ops.greater
                            else:
                                self.monitor_op = ops.less
        if self.monitor_op is None:
            raise ValueError(
                f"EarlyStopping callback received monitor={self.monitor} "
                "but Keras isn't able to automatically determine whether "
                "that metric should be maximized or minimized. "
                "Pass `mode='max'` in order to do early stopping based "
                "on the highest metric value, or pass `mode='min'` "
                "in order to use the lowest value."
            )
        if self.monitor_op == ops.less:
            self.min_delta *= -1
        self.best = (
            float("inf") if self.monitor_op == ops.less else -float("inf")
        )

    def on_train_begin(self, logs=None):
        
        self.wait = 0
        self.stopped_epoch = 0
        self.best_weights = None
        self.best_epoch = 0

    def on_epoch_end(self, epoch, logs=None):
        if self.monitor_op is None:
            
            self._set_monitor_op()

        current = self.get_monitor_value(logs)
        if current is None or epoch < self.start_from_epoch:
            
            return
        if self.restore_best_weights and self.best_weights is None:
            
            
            self.best_weights = self.model.get_weights()
            self.best_epoch = epoch

        self.wait += 1
        if self._is_improvement(current, self.best):
            self.best = current
            self.best_epoch = epoch
            if self.restore_best_weights:
                self.best_weights = self.model.get_weights()
            
            
            if self.baseline is None or self._is_improvement(
                current, self.baseline
            ):
                self.wait = 0
            return

        if self.wait >= self.patience and epoch > 0:
            
            self.stopped_epoch = epoch
            self.model.stop_training = True

    def on_train_end(self, logs=None):
        if self.stopped_epoch > 0 and self.verbose > 0:
            io_utils.print_msg(
                f"Epoch {self.stopped_epoch + 1}: early stopping"
            )
        if self.restore_best_weights and self.best_weights is not None:
            if self.verbose > 0:
                io_utils.print_msg(
                    "Restoring model weights from "
                    "the end of the best epoch: "
                    f"{self.best_epoch + 1}."
                )
            self.model.set_weights(self.best_weights)

    def get_monitor_value(self, logs):
        logs = logs or {}
        monitor_value = logs.get(self.monitor)
        if monitor_value is None:
            warnings.warn(
                (
                    f"Early stopping conditioned on metric `{self.monitor}` "
                    "which is not available. "
                    f"Available metrics are: {','.join(list(logs.keys()))}"
                ),
                stacklevel=2,
            )
        return monitor_value

    def _is_improvement(self, monitor_value, reference_value):
        return self.monitor_op(monitor_value - self.min_delta, reference_value)