Implement lazy callbacks & two-phase gap

This commit is contained in:
2020-09-25 06:02:07 -05:00
parent 86e7b1981f
commit a221740ac5
10 changed files with 288 additions and 75 deletions

View File

@@ -24,3 +24,6 @@ class Component(ABC):
def after_iteration(self, solver, instance, model):
return False
def on_lazy_callback(self, solver, instance, model):
return

View File

@@ -21,14 +21,29 @@ class LazyConstraint:
class StaticLazyConstraintsComponent(Component):
def __init__(self,
classifier=CountingClassifier(),
threshold=0.05):
threshold=0.05,
use_two_phase_gap=True,
large_gap=1e-2,
violation_tolerance=-0.5,
):
self.threshold = threshold
self.classifier_prototype = classifier
self.classifiers = {}
self.pool = []
self.original_gap = None
self.large_gap = large_gap
self.is_gap_large = False
self.use_two_phase_gap = use_two_phase_gap
self.violation_tolerance = violation_tolerance
def before_solve(self, solver, instance, model):
self.pool = []
if not solver.use_lazy_cb and self.use_two_phase_gap:
logger.info("Increasing gap tolerance to %f", self.large_gap)
self.original_gap = solver.gap_tolerance
self.is_gap_large = True
solver.internal_solver.set_gap_tolerance(self.large_gap)
instance.found_violated_lazy_constraints = []
if instance.has_static_lazy_constraints():
self._extract_and_predict_static(solver, instance)
@@ -37,23 +52,41 @@ class StaticLazyConstraintsComponent(Component):
pass
def after_iteration(self, solver, instance, model):
logger.info("Finding violated lazy constraints...")
if solver.use_lazy_cb:
return False
else:
should_repeat = self._check_and_add(instance, solver)
if should_repeat:
return True
else:
if self.is_gap_large:
logger.info("Restoring gap tolerance to %f", self.original_gap)
solver.internal_solver.set_gap_tolerance(self.original_gap)
self.is_gap_large = False
return True
else:
return False
def on_lazy_callback(self, solver, instance, model):
self._check_and_add(instance, solver)
def _check_and_add(self, instance, solver):
logger.debug("Finding violated lazy constraints...")
constraints_to_add = []
for c in self.pool:
if not solver.internal_solver.is_constraint_satisfied(c.obj):
if not solver.internal_solver.is_constraint_satisfied(c.obj,
tol=self.violation_tolerance):
constraints_to_add.append(c)
for c in constraints_to_add:
self.pool.remove(c)
solver.internal_solver.add_constraint(c.obj)
instance.found_violated_lazy_constraints += [c.cid]
if len(constraints_to_add) > 0:
logger.info("Added %d lazy constraints back into the model" % len(constraints_to_add))
logger.info("Lazy constraint pool has %d constraints" % len(self.pool))
logger.info("%8d lazy constraints added %8d in the pool" % (len(constraints_to_add), len(self.pool)))
return True
else:
logger.info("Found no violated lazy constraints")
return False
def fit(self, training_instances):
training_instances = [t
for t in training_instances
@@ -92,7 +125,7 @@ class StaticLazyConstraintsComponent(Component):
obj=solver.internal_solver.extract_constraint(cid))
constraints[category] += [c]
self.pool.append(c)
logger.info("Extracted %d lazy constraints" % len(self.pool))
logger.info("%8d lazy constraints extracted" % len(self.pool))
logger.info("Predicting required lazy constraints...")
n_added = 0
for (category, x_values) in x.items():
@@ -108,8 +141,7 @@ class StaticLazyConstraintsComponent(Component):
self.pool.remove(c)
solver.internal_solver.add_constraint(c.obj)
instance.found_violated_lazy_constraints += [c.cid]
logger.info("Added %d lazy constraints back into the model" % n_added)
logger.info("Lazy constraint pool has %d constraints" % len(self.pool))
logger.info("%8d lazy constraints added %8d in the pool" % (n_added, len(self.pool)))
def _collect_constraints(self, train_instances):
constraints = {}

View File

@@ -13,6 +13,9 @@ from miplearn.classifiers import Classifier
def test_usage_with_solver():
solver = Mock(spec=LearningSolver)
solver.use_lazy_cb = False
solver.gap_tolerance = 1e-4
internal = solver.internal_solver = Mock(spec=InternalSolver)
internal.get_constraint_ids = Mock(return_value=["c1", "c2", "c3", "c4"])
internal.extract_constraint = Mock(side_effect=lambda cid: "<%s>" % cid)
@@ -37,7 +40,8 @@ def test_usage_with_solver():
"c4": "type-b",
}[cid])
component = StaticLazyConstraintsComponent(threshold=0.90)
component = StaticLazyConstraintsComponent(threshold=0.90,
use_two_phase_gap=False)
component.classifiers = {
"type-a": Mock(spec=Classifier),
"type-b": Mock(spec=Classifier),