Stopper#
- class liesel.optim.Stopper(epochs, patience, atol=0.0, rtol=0.0, min_epochs=0)[source]#
Bases:
objectHandles maximum-epoch and early stopping decisions.
Stopperuses a rolling patience window over the recorded monitoring loss. In the experimental optimizer engine, the argumentipassed to the stopper is the number of completed epochs, which is also the next epoch index to be written. Therefore, the recent patience window isloss_history[i - patience : i]once at leastpatienceepochs have been completed.- Parameters:
epochs (
int) – The maximum number of optimization epochs.patience (
int) – Length of the rolling early-stopping window. Early stopping can only happen after more thanpatienceepochs have been completed.atol (
float, default:0.0) – Absolute tolerance for early stopping.rtol (
float, default:0.0) – Relative tolerance for early stopping. The default of0.0disables additional relative tolerance beyond exact non-improvement.min_epochs (
int, default:0) – Minimum number of completed epochs before early stopping can happen. The default of0preserves the existing patience-based behavior. The effective earliest stop ismax(min_epochs, patience + 1).
Notes
Early stopping happens when the oldest loss value within the patience window is the best loss value within the patience window. With experimental optimizer indexing, a simplified pseudo-implementation is:
def stop(patience, i, loss_history): recent_history = loss_history[i - patience : i] oldest_within_patience = recent_history[0] best_within_patience = np.min(recent_history) return oldest_within_patience <= best_within_patience
Absolute and relative tolerance make it possible to stop even in cases when the oldest loss within patience is not the best. Instead, the algorithm stops, when the absolute or relative difference between the oldest loss within patience and the best loss within patience is so small that it can be neglected. To be clear: If either of the two conditions is met, then early stopping happens. The relative magnitude of the difference is calculated with respect to the best loss within patience. A simplified pseudo-implementation is:
def stop(patience, i, loss_history, atol, rtol): recent_history = loss_history[i - patience : i] oldest_within_patience = recent_history[0] best_within_patience = np.min(recent_history) diff = oldest_within_patience - best_within_patience rel_diff = diff / np.abs(best_within_patience) abs_improvement_is_neglectable = diff <= atol rel_improvement_is_neglectable = rel_diff <= rtol return abs_improvement_is_neglectable | rel_improvement_is_neglectable
Examples
>>> import jax.numpy as jnp >>> from liesel.optim import Stopper >>> stopper = Stopper(epochs=10, patience=3) >>> loss_history = jnp.array([3.0, 2.0, 1.0, 1.5, 1.6, 1.7]) >>> bool(stopper.stop_early(i=5, loss_history=loss_history)) True >>> int(stopper.which_best_in_recent_history(i=5, loss_history=loss_history)) 2
max_iteris available as a read-only alias forepochs:>>> stopper.max_iter 10
Methods
continue_(i, loss_history)Whether optimization should continue (inverse of
stop_now()).stop_early(i, loss_history)Returns whether the recent patience window should trigger early stopping.
stop_now(i, loss_history)Whether optimization should stop now, including at the maximum epoch.
which_best_in_recent_history(i, loss_history)Identifies the index of the best observation in the recent loss window.
Attributes
Absolute tolerance for early stopping.
Read-only alias for
epochs.Minimum number of completed epochs before early stopping can happen.
Relative tolerance for early stopping.
The maximum number of optimization epochs.
Length of the rolling early-stopping window.