Contrastive Methods

Contrastive methods train against a pool of cached non-optimal solutions, treated as negative examples. solve_ratio controls how often new instances are solved exactly during training. dataset seeds the pool and is required by these methods. See Solution Pool for details on the solution-pool mechanism.

The training loops on this page build on the Common Setup from Training Methods.

Noise Contrastive Estimation (NCE)

NCE [1] is a surrogate loss based on negative examples. It uses a small set of non-optimal solutions as negative samples to maximize the predicted-cost margin between the optimal solution and the rest.

Let \(\Gamma\) be the cached pool of feasible solutions. For a minimization problem, NCE averages the predicted-cost margin between \(\mathbf{w}^*(\mathbf{c})\) and every member of the pool,

\[\mathcal{L}_{\mathrm{NCE}}(\hat{\mathbf{c}}, \mathbf{w}^*(\mathbf{c})) = \frac{1}{|\Gamma|} \sum_{\mathbf{w} \in \Gamma} \big( \hat{\mathbf{c}}^\top \mathbf{w}^*(\mathbf{c}) - \hat{\mathbf{c}}^\top \mathbf{w} \big).\]

(If \(\mathbf{w}^*(\mathbf{c}) \in \Gamma\), its term contributes zero and the sum effectively averages over the negatives.) The gradient has a closed form that requires no solver call in the backward pass,

\[\frac{\partial \mathcal{L}_{\mathrm{NCE}}(\hat{\mathbf{c}}, \mathbf{w}^*(\mathbf{c}))}{\partial \hat{\mathbf{c}}} = \mathbf{w}^*(\mathbf{c}) - \frac{1}{|\Gamma|} \sum_{\mathbf{w} \in \Gamma} \mathbf{w}.\]

For a fixed \(\Gamma\), this update direction stays constant per instance. solve_ratio controls how often the pool is refreshed.

pyepo.func.NCE

alias of noiseContrastiveEstimation

Training loop:

nce = pyepo.func.NCE(optmodel, processes=1, solve_ratio=0.05, dataset=dataset)

for epoch in range(20):
    for x, c, w, z in dataloader:
        cp = predmodel(x)
        loss = nce(cp, w)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

Contrastive MAP (CMAP)

CMAP [1] is a special case of NCE that uses only the lowest predicted-cost negative sample.

CMAP keeps only the most-violating member of the pool, the one with the smallest predicted-cost objective, yielding a max-margin contrast against the true optimum. For a minimization problem,

\[\mathcal{L}_{\mathrm{CMAP}}(\hat{\mathbf{c}}, \mathbf{w}^*(\mathbf{c})) = \max_{\mathbf{w} \in \Gamma} \big( \hat{\mathbf{c}}^\top \mathbf{w}^*(\mathbf{c}) - \hat{\mathbf{c}}^\top \mathbf{w} \big) = \hat{\mathbf{c}}^\top \mathbf{w}^*(\mathbf{c}) - \min_{\mathbf{w} \in \Gamma} \hat{\mathbf{c}}^\top \mathbf{w}.\]

If \(\mathbf{w}^*(\mathbf{c}) \in \Gamma\), that entry contributes a zero margin, so the loss is non-negative and vanishes precisely when the predicted costs already make \(\mathbf{w}^*(\mathbf{c})\) the minimizer over \(\Gamma\).

pyepo.func.CMAP

alias of contrastiveMAP

Training loop:

cmap = pyepo.func.CMAP(optmodel, processes=1, solve_ratio=0.05, dataset=dataset)

for epoch in range(20):
    for x, c, w, z in dataloader:
        cp = predmodel(x)
        loss = cmap(cp, w)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

Footnotes