Surrogate Losses¶
Surrogate losses replace the non-differentiable regret with a differentiable training objective.
The training loops on this page build on the Common Setup from Training Methods.
Smart Predict-then-Optimize+ Loss (SPO+)¶
SPO+ [1] is a convex surrogate for the SPO loss, also called regret: the decision error of the downstream optimization.
The derivation starts from the observation that for any \(\alpha \geq 0\),
Set \(\alpha = 2\). Because \(z^*\) is a minimum and \(\mathbf{w}^*(\mathbf{c})\) is feasible, \(z^*(2 \hat{\mathbf{c}}) \leq 2 \hat{\mathbf{c}}^\top \mathbf{w}^*(\mathbf{c})\). This gives the SPO+ loss,
which is convex in \(\hat{\mathbf{c}}\) and upper-bounds the regret. By Danskin’s theorem, a subgradient is
- class pyepo.func.SPOPlus(optmodel: optModel, processes: int = 1, solve_ratio: float = 1.0, reduction: Reduction = 'mean', dataset: optDataset | None = None)
SPO+ loss: a convex surrogate for the SPO regret of a linear-objective LP.
SPO+ upper-bounds the SPO regret with a convex function of the predicted cost vector and provides an informative subgradient (via Danskin’s theorem) for end-to-end training. It is the strong default for predict-then-optimize when true optimal solutions \(\mathbf{w}^*(\mathbf{c})\) are available as supervision.
The forward pass solves the perturbed problem with cost \(2\hat{\mathbf{c}} - \mathbf{c}\) once per training instance and returns a scalar loss; the backward pass uses the cached solution to form the subgradient \(2(\mathbf{w}^*(\mathbf{c}) - \mathbf{w}^*(2\hat{\mathbf{c}} - \mathbf{c}))\) without any extra solver call.
Reference: Elmachtoub & Grigas (2022) https://doi.org/10.1287/mnsc.2020.3922
processes sets the number of solver processes (0 uses all available cores).
Training loop:
spo = pyepo.func.SPOPlus(optmodel, processes=1)
for epoch in range(20):
for x, c, w, z in dataloader:
cp = predmodel(x)
loss = spo(cp, c, w, z)
optimizer.zero_grad()
loss.backward()
optimizer.step()
Perturbation Gradient (PG)¶
PG [2] is a surrogate loss based on zeroth-order gradient approximation.
By Danskin’s theorem, regret can be written as the directional derivative of \(z^*(\hat{\mathbf{c}})\) along \(\mathbf{c}\) (up to a constant independent of \(\hat{\mathbf{c}}\)). PG approximates this directional derivative with finite differences. Two variants are available, backward (two_sides=False) and central (two_sides=True):
where \(\sigma > 0\) is the finite-difference width (the sigma parameter). The corresponding gradient estimate is
- pyepo.func.PG
alias of
perturbationGradient
sigma controls the finite-difference width. two_sides enables central differencing.
Training loop:
pg = pyepo.func.PG(optmodel, sigma=0.1, two_sides=False, processes=1)
for epoch in range(20):
for x, c, w, z in dataloader:
cp = predmodel(x)
loss = pg(cp, c)
optimizer.zero_grad()
loss.backward()
optimizer.step()
Footnotes