Break down RelaxationComponent into multiple steps

pull/3/head
Alinson S. Xavier 5 years ago
parent 6540c88cc5
commit 8bb9996384

@ -7,9 +7,10 @@ from miplearn import Component
class CompositeComponent(Component): class CompositeComponent(Component):
""" """
A Component which redirects each method call to one or more subcomponents. Useful A Component which redirects each method call to one or more subcomponents.
for breaking down complex components into smaller classes. See RelaxationComponent
for a concrete example. Useful for breaking down complex components into smaller classes. See
RelaxationComponent for a concrete example.
Parameters Parameters
---------- ----------

@ -13,6 +13,7 @@ from tqdm import tqdm
from miplearn import Component from miplearn import Component
from miplearn.classifiers.counting import CountingClassifier from miplearn.classifiers.counting import CountingClassifier
from miplearn.components import classifier_evaluation_dict from miplearn.components import classifier_evaluation_dict
from miplearn.components.composite import CompositeComponent
from miplearn.components.lazy_static import LazyConstraint from miplearn.components.lazy_static import LazyConstraint
from miplearn.extractors import InstanceIterator from miplearn.extractors import InstanceIterator
@ -21,36 +22,83 @@ logger = logging.getLogger(__name__)
class RelaxationComponent(Component): class RelaxationComponent(Component):
""" """
A Component that tries to build a relaxation that is simultaneously strong and easy to solve. A Component that tries to build a relaxation that is simultaneously strong and easy
to solve.
Currently, this component performs the following operations: Currently, this component performs the following operations:
- Drops all integrality constraints - Drops all integrality constraints
- Drops all inequality constraints that are not likely to be binding. - Drops all inequality constraints that are not likely to be binding.
In future versions of MIPLearn, this component may keep some integrality constraints and perform other operations. In future versions of MIPLearn, this component may keep some integrality constraints
and perform other operations.
Parameters Parameters
---------- ----------
classifier : Classifier, optional classifier : Classifier, optional
Classifier used to predict whether each constraint is binding or not. One deep copy of this classifier Classifier used to predict whether each constraint is binding or not. One deep
is made for each constraint category. copy of this classifier is made for each constraint category.
threshold : float, optional threshold : float, optional
If the probability that a constraint is binding exceeds this threshold, the constraint is dropped from the If the probability that a constraint is binding exceeds this threshold, the
linear relaxation. constraint is dropped from the linear relaxation.
slack_tolerance : float, optional slack_tolerance : float, optional
If a constraint has slack greater than this threshold, then the constraint is considered loose. By default, If a constraint has slack greater than this threshold, then the constraint is
this threshold equals a small positive number to compensate for numerical issues. considered loose. By default, this threshold equals a small positive number to
compensate for numerical issues.
check_dropped : bool, optional check_dropped : bool, optional
If `check_dropped` is true, then, after the problem is solved, the component verifies that all dropped If `check_dropped` is true, then, after the problem is solved, the component
constraints are still satisfied, re-adds the violated ones and resolves the problem. This loop continues until verifies that all dropped constraints are still satisfied, re-adds the violated
either no violations are found, or a maximum number of iterations is reached. ones and resolves the problem. This loop continues until either no violations
are found, or a maximum number of iterations is reached.
violation_tolerance : float, optional violation_tolerance : float, optional
If `check_dropped` is true, a constraint is considered satisfied during the check if its violation is smaller If `check_dropped` is true, a constraint is considered satisfied during the
than this tolerance. check if its violation is smaller than this tolerance.
max_iterations : int max_iterations : int
If `check_dropped` is true, set the maximum number of iterations in the lazy constraint loop. If `check_dropped` is true, set the maximum number of iterations in the lazy
constraint loop.
""" """
def __init__(
self,
classifier=CountingClassifier(),
threshold=0.95,
slack_tolerance=1e-5,
check_dropped=False,
violation_tolerance=1e-5,
max_iterations=3,
):
self.steps = [
RelaxIntegralityStep(),
DropRedundantInequalitiesStep(
classifier=classifier,
threshold=threshold,
slack_tolerance=slack_tolerance,
violation_tolerance=violation_tolerance,
max_iterations=max_iterations,
check_dropped=check_dropped,
),
]
self.composite = CompositeComponent(self.steps)
def before_solve(self, solver, instance, model):
self.composite.before_solve(solver, instance, model)
def after_solve(self, solver, instance, model, results):
self.composite.after_solve(solver, instance, model, results)
def fit(self, training_instances):
self.composite.fit(training_instances)
def iteration_cb(self, solver, instance, model):
return self.composite.iteration_cb(solver, instance, model)
class RelaxIntegralityStep(Component):
def before_solve(self, solver, instance, _):
logger.info("Relaxing integrality...")
solver.internal_solver.relax()
class DropRedundantInequalitiesStep(Component):
def __init__( def __init__(
self, self,
classifier=CountingClassifier(), classifier=CountingClassifier(),
@ -73,9 +121,6 @@ class RelaxationComponent(Component):
def before_solve(self, solver, instance, _): def before_solve(self, solver, instance, _):
self.current_iteration = 0 self.current_iteration = 0
logger.info("Relaxing integrality...")
solver.internal_solver.relax()
logger.info("Predicting redundant LP constraints...") logger.info("Predicting redundant LP constraints...")
cids = solver.internal_solver.get_constraint_ids() cids = solver.internal_solver.get_constraint_ids()
x, constraints = self.x( x, constraints = self.x(
@ -103,7 +148,7 @@ class RelaxationComponent(Component):
x = self.x(training_instances) x = self.x(training_instances)
y = self.y(training_instances) y = self.y(training_instances)
logger.debug("Fitting...") logger.debug("Fitting...")
for category in tqdm(x.keys(), desc="Fit (relaxation)"): for category in tqdm(x.keys(), desc="Fit (rlx:drop_ineq)"):
if category not in self.classifiers: if category not in self.classifiers:
self.classifiers[category] = deepcopy(self.classifier_prototype) self.classifiers[category] = deepcopy(self.classifier_prototype)
self.classifiers[category].fit(x[category], y[category]) self.classifiers[category].fit(x[category], y[category])
@ -113,7 +158,7 @@ class RelaxationComponent(Component):
constraints = {} constraints = {}
for instance in tqdm( for instance in tqdm(
InstanceIterator(instances), InstanceIterator(instances),
desc="Extract (relaxation:x)", desc="Extract (rlx:drop_ineq:x)",
disable=len(instances) < 5, disable=len(instances) < 5,
): ):
if constraint_ids is not None: if constraint_ids is not None:
@ -138,7 +183,7 @@ class RelaxationComponent(Component):
y = {} y = {}
for instance in tqdm( for instance in tqdm(
InstanceIterator(instances), InstanceIterator(instances),
desc="Extract (relaxation:y)", desc="Extract (rlx:drop_ineq:y)",
disable=len(instances) < 5, disable=len(instances) < 5,
): ):
for (cid, slack) in instance.slacks.items(): for (cid, slack) in instance.slacks.items():

@ -6,6 +6,7 @@ from unittest.mock import Mock, call
from miplearn import RelaxationComponent, LearningSolver, Instance, InternalSolver from miplearn import RelaxationComponent, LearningSolver, Instance, InternalSolver
from miplearn.classifiers import Classifier from miplearn.classifiers import Classifier
from miplearn.components.relaxation import DropRedundantInequalitiesStep
def _setup(): def _setup():
@ -64,7 +65,8 @@ def test_usage():
solver, internal, instance, classifiers = _setup() solver, internal, instance, classifiers = _setup()
component = RelaxationComponent() component = RelaxationComponent()
component.classifiers = classifiers drop_ineqs_step = component.steps[1]
drop_ineqs_step.classifiers = classifiers
# LearningSolver calls before_solve # LearningSolver calls before_solve
component.before_solve(solver, instance, None) component.before_solve(solver, instance, None)
@ -97,10 +99,10 @@ def test_usage():
) )
# Should ask ML to predict whether constraint should be removed # Should ask ML to predict whether constraint should be removed
component.classifiers["type-a"].predict_proba.assert_called_once_with( drop_ineqs_step.classifiers["type-a"].predict_proba.assert_called_once_with(
[[1.0, 0.0], [0.5, 0.5]] [[1.0, 0.0], [0.5, 0.5]]
) )
component.classifiers["type-b"].predict_proba.assert_called_once_with([[1.0]]) drop_ineqs_step.classifiers["type-b"].predict_proba.assert_called_once_with([[1.0]])
# Should ask internal solver to remove constraints predicted as redundant # Should ask internal solver to remove constraints predicted as redundant
assert internal.extract_constraint.call_count == 2 assert internal.extract_constraint.call_count == 2
@ -131,7 +133,8 @@ def test_usage_with_check_dropped():
solver, internal, instance, classifiers = _setup() solver, internal, instance, classifiers = _setup()
component = RelaxationComponent(check_dropped=True, violation_tolerance=1e-3) component = RelaxationComponent(check_dropped=True, violation_tolerance=1e-3)
component.classifiers = classifiers drop_ineqs_step = component.steps[1]
drop_ineqs_step.classifiers = classifiers
# LearningSolver call before_solve # LearningSolver call before_solve
component.before_solve(solver, instance, None) component.before_solve(solver, instance, None)
@ -169,7 +172,7 @@ def test_usage_with_check_dropped():
def test_x_y_fit_predict_evaluate(): def test_x_y_fit_predict_evaluate():
instances = [Mock(spec=Instance), Mock(spec=Instance)] instances = [Mock(spec=Instance), Mock(spec=Instance)]
component = RelaxationComponent(slack_tolerance=0.05, threshold=0.80) component = DropRedundantInequalitiesStep(slack_tolerance=0.05, threshold=0.80)
component.classifiers = { component.classifiers = {
"type-a": Mock(spec=Classifier), "type-a": Mock(spec=Classifier),
"type-b": Mock(spec=Classifier), "type-b": Mock(spec=Classifier),

Loading…
Cancel
Save