Move collected data to instance.training_data

master
Alinson S. Xavier 5 years ago
parent 23dd311d75
commit 06402516e6

@ -78,7 +78,15 @@ class ObjectiveValueComponent(Component):
def evaluate(self, instances): def evaluate(self, instances):
y_pred = self.predict(instances) y_pred = self.predict(instances)
y_true = np.array([[inst.lower_bound, inst.upper_bound] for inst in instances]) y_true = np.array(
[
[
inst.training_data[0]["Lower bound"],
inst.training_data[0]["Upper bound"],
]
for inst in instances
]
)
y_true_lb, y_true_ub = y_true[:, 0], y_true[:, 1] y_true_lb, y_true_ub = y_true[:, 0], y_true[:, 1]
y_pred_lb, y_pred_ub = y_pred[:, 1], y_pred[:, 1] y_pred_lb, y_pred_ub = y_pred[:, 1], y_pred[:, 1]
ev = { ev = {

@ -68,7 +68,8 @@ class PrimalSolutionComponent(Component):
for label in [0, 1]: for label in [0, 1]:
y_train = solutions[category][:, label].astype(int) y_train = solutions[category][:, label].astype(int)
# If all samples are either positive or negative, make constant predictions # If all samples are either positive or negative, make constant
# predictions
y_avg = np.average(y_train) y_avg = np.average(y_train)
if y_avg < 0.001 or y_avg >= 0.999: if y_avg < 0.001 or y_avg >= 0.999:
self.classifiers[category, label] = round(y_avg) self.classifiers[category, label] = round(y_avg)
@ -130,7 +131,7 @@ class PrimalSolutionComponent(Component):
desc="Evaluate (primal)", desc="Evaluate (primal)",
): ):
instance = instances[instance_idx] instance = instances[instance_idx]
solution_actual = instance.solution solution_actual = instance.training_data[0]["Solution"]
solution_pred = self.predict(instance) solution_pred = self.predict(instance)
vars_all, vars_one, vars_zero = set(), set(), set() vars_all, vars_one, vars_zero = set(), set(), set()

@ -24,8 +24,8 @@ def test_convert_tight_usage():
) )
# Solve original problem # Solve original problem
solver.solve(instance) stats = solver.solve(instance)
original_upper_bound = instance.upper_bound original_upper_bound = stats["Upper bound"]
# Should collect training data # Should collect training data
assert instance.training_data[0]["slacks"]["eq_capacity"] == 0.0 assert instance.training_data[0]["slacks"]["eq_capacity"] == 0.0
@ -35,12 +35,12 @@ def test_convert_tight_usage():
stats = solver.solve(instance) stats = solver.solve(instance)
# Objective value should be the same # Objective value should be the same
assert instance.upper_bound == original_upper_bound assert stats["Upper bound"] == original_upper_bound
assert stats["ConvertTight: Inf iterations"] == 0 assert stats["ConvertTight: Inf iterations"] == 0
assert stats["ConvertTight: Subopt iterations"] == 0 assert stats["ConvertTight: Subopt iterations"] == 0
class TestInstance(Instance): class SampleInstance(Instance):
def to_model(self): def to_model(self):
import gurobipy as grb import gurobipy as grb
@ -70,9 +70,9 @@ def test_convert_tight_infeasibility():
components=[comp], components=[comp],
solve_lp_first=False, solve_lp_first=False,
) )
instance = TestInstance() instance = SampleInstance()
stats = solver.solve(instance) stats = solver.solve(instance)
assert instance.lower_bound == 5.0 assert stats["Upper bound"] == 5.0
assert stats["ConvertTight: Inf iterations"] == 1 assert stats["ConvertTight: Inf iterations"] == 1
assert stats["ConvertTight: Subopt iterations"] == 0 assert stats["ConvertTight: Subopt iterations"] == 0
@ -93,9 +93,9 @@ def test_convert_tight_suboptimality():
components=[comp], components=[comp],
solve_lp_first=False, solve_lp_first=False,
) )
instance = TestInstance() instance = SampleInstance()
stats = solver.solve(instance) stats = solver.solve(instance)
assert instance.lower_bound == 5.0 assert stats["Upper bound"] == 5.0
assert stats["ConvertTight: Inf iterations"] == 0 assert stats["ConvertTight: Inf iterations"] == 0
assert stats["ConvertTight: Subopt iterations"] == 1 assert stats["ConvertTight: Subopt iterations"] == 1
@ -116,8 +116,8 @@ def test_convert_tight_optimal():
components=[comp], components=[comp],
solve_lp_first=False, solve_lp_first=False,
) )
instance = TestInstance() instance = SampleInstance()
stats = solver.solve(instance) stats = solver.solve(instance)
assert instance.lower_bound == 5.0 assert stats["Upper bound"] == 5.0
assert stats["ConvertTight: Inf iterations"] == 0 assert stats["ConvertTight: Inf iterations"] == 0
assert stats["ConvertTight: Subopt iterations"] == 0 assert stats["ConvertTight: Subopt iterations"] == 0

@ -15,8 +15,8 @@ def test_usage():
instances, models = get_test_pyomo_instances() instances, models = get_test_pyomo_instances()
comp = ObjectiveValueComponent() comp = ObjectiveValueComponent()
comp.fit(instances) comp.fit(instances)
assert instances[0].lower_bound == 1183.0 assert instances[0].training_data[0]["Lower bound"] == 1183.0
assert instances[0].upper_bound == 1183.0 assert instances[0].training_data[0]["Upper bound"] == 1183.0
assert np.round(comp.predict(instances), 2).tolist() == [ assert np.round(comp.predict(instances), 2).tolist() == [
[1183.0, 1183.0], [1183.0, 1183.0],
[1070.0, 1070.0], [1070.0, 1070.0],

@ -50,7 +50,7 @@ def test_evaluate():
comp = PrimalSolutionComponent(classifier=[clf_zero, clf_one], threshold=0.50) comp = PrimalSolutionComponent(classifier=[clf_zero, clf_one], threshold=0.50)
comp.fit(instances[:1]) comp.fit(instances[:1])
assert comp.predict(instances[0]) == {"x": {0: 0, 1: 0, 2: 1, 3: None}} assert comp.predict(instances[0]) == {"x": {0: 0, 1: 0, 2: 1, 3: None}}
assert instances[0].solution == {"x": {0: 1, 1: 0, 2: 1, 3: 1}} assert instances[0].training_data[0]["Solution"] == {"x": {0: 1, 1: 0, 2: 1, 3: 1}}
ev = comp.evaluate(instances[:1]) ev = comp.evaluate(instances[:1])
assert ev == { assert ev == {
"Fix one": { "Fix one": {

@ -2,14 +2,13 @@
# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved. # Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details. # Released under the modified BSD license. See COPYING.md for more details.
import gzip
import logging import logging
import pickle import pickle
import gzip from abc import ABC, abstractmethod
import numpy as np import numpy as np
from tqdm.auto import tqdm from tqdm.auto import tqdm
from abc import ABC, abstractmethod
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -48,10 +47,10 @@ class Extractor(ABC):
@staticmethod @staticmethod
def split_variables(instance): def split_variables(instance):
assert hasattr(instance, "lp_solution")
result = {} result = {}
for var_name in instance.lp_solution: lp_solution = instance.training_data[0]["LP solution"]
for index in instance.lp_solution[var_name]: for var_name in lp_solution:
for index in lp_solution[var_name]:
category = instance.get_variable_category(var_name, index) category = instance.get_variable_category(var_name, index)
if category is None: if category is None:
continue continue
@ -71,6 +70,7 @@ class VariableFeaturesExtractor(Extractor):
): ):
instance_features = instance.get_instance_features() instance_features = instance.get_instance_features()
var_split = self.split_variables(instance) var_split = self.split_variables(instance)
lp_solution = instance.training_data[0]["LP solution"]
for (category, var_index_pairs) in var_split.items(): for (category, var_index_pairs) in var_split.items():
if category not in result: if category not in result:
result[category] = [] result[category] = []
@ -78,7 +78,7 @@ class VariableFeaturesExtractor(Extractor):
result[category] += [ result[category] += [
instance_features.tolist() instance_features.tolist()
+ instance.get_variable_features(var_name, index).tolist() + instance.get_variable_features(var_name, index).tolist()
+ [instance.lp_solution[var_name][index]] + [lp_solution[var_name][index]]
] ]
for category in result: for category in result:
result[category] = np.array(result[category]) result[category] = np.array(result[category])
@ -97,14 +97,15 @@ class SolutionExtractor(Extractor):
disable=len(instances) < 5, disable=len(instances) < 5,
): ):
var_split = self.split_variables(instance) var_split = self.split_variables(instance)
if self.relaxation:
solution = instance.training_data[0]["LP solution"]
else:
solution = instance.training_data[0]["Solution"]
for (category, var_index_pairs) in var_split.items(): for (category, var_index_pairs) in var_split.items():
if category not in result: if category not in result:
result[category] = [] result[category] = []
for (var_name, index) in var_index_pairs: for (var_name, index) in var_index_pairs:
if self.relaxation: v = solution[var_name][index]
v = instance.lp_solution[var_name][index]
else:
v = instance.solution[var_name][index]
if v is None: if v is None:
result[category] += [[0, 0]] result[category] += [[0, 0]]
else: else:
@ -121,7 +122,7 @@ class InstanceFeaturesExtractor(Extractor):
np.hstack( np.hstack(
[ [
instance.get_instance_features(), instance.get_instance_features(),
instance.lp_value, instance.training_data[0]["LP value"],
] ]
) )
for instance in InstanceIterator(instances) for instance in InstanceIterator(instances)
@ -137,13 +138,22 @@ class ObjectiveValueExtractor(Extractor):
def extract(self, instances): def extract(self, instances):
if self.kind == "lower bound": if self.kind == "lower bound":
return np.array( return np.array(
[[instance.lower_bound] for instance in InstanceIterator(instances)] [
[instance.training_data[0]["Lower bound"]]
for instance in InstanceIterator(instances)
]
) )
if self.kind == "upper bound": if self.kind == "upper bound":
return np.array( return np.array(
[[instance.upper_bound] for instance in InstanceIterator(instances)] [
[instance.training_data[0]["Upper bound"]]
for instance in InstanceIterator(instances)
]
) )
if self.kind == "lp": if self.kind == "lp":
return np.array( return np.array(
[[instance.lp_value] for instance in InstanceIterator(instances)] [
[instance.training_data[0]["LP value"]]
for instance in InstanceIterator(instances)
]
) )

@ -15,8 +15,8 @@ def test_stab():
weights = [1.0, 1.0, 1.0, 1.0, 1.0] weights = [1.0, 1.0, 1.0, 1.0, 1.0]
instance = MaxWeightStableSetInstance(graph, weights) instance = MaxWeightStableSetInstance(graph, weights)
solver = LearningSolver() solver = LearningSolver()
solver.solve(instance) stats = solver.solve(instance)
assert instance.lower_bound == 2.0 assert stats["Lower bound"] == 2.0
def test_stab_generator_fixed_graph(): def test_stab_generator_fixed_graph():

@ -38,16 +38,16 @@ def test_instance():
) )
instance = TravelingSalesmanInstance(n_cities, distances) instance = TravelingSalesmanInstance(n_cities, distances)
solver = LearningSolver() solver = LearningSolver()
solver.solve(instance) stats = solver.solve(instance)
x = instance.solution["x"] x = instance.training_data[0]["Solution"]["x"]
assert x[0, 1] == 1.0 assert x[0, 1] == 1.0
assert x[0, 2] == 0.0 assert x[0, 2] == 0.0
assert x[0, 3] == 1.0 assert x[0, 3] == 1.0
assert x[1, 2] == 1.0 assert x[1, 2] == 1.0
assert x[1, 3] == 0.0 assert x[1, 3] == 0.0
assert x[2, 3] == 1.0 assert x[2, 3] == 1.0
assert instance.lower_bound == 4.0 assert stats["Lower bound"] == 4.0
assert instance.upper_bound == 4.0 assert stats["Upper bound"] == 4.0
def test_subtour(): def test_subtour():
@ -68,7 +68,7 @@ def test_subtour():
solver.solve(instance) solver.solve(instance)
assert hasattr(instance, "found_violated_lazy_constraints") assert hasattr(instance, "found_violated_lazy_constraints")
assert hasattr(instance, "found_violated_user_cuts") assert hasattr(instance, "found_violated_user_cuts")
x = instance.solution["x"] x = instance.training_data[0]["Solution"]["x"]
assert x[0, 1] == 1.0 assert x[0, 1] == 1.0
assert x[0, 4] == 1.0 assert x[0, 4] == 1.0
assert x[1, 2] == 1.0 assert x[1, 2] == 1.0

@ -115,11 +115,11 @@ class LearningSolver:
def solve( def solve(
self, self,
instance, instance: Union[Instance, str],
model=None, model: Any = None,
output="", output: str = "",
tee=False, tee: bool = False,
): ) -> MIPSolveStats:
""" """
Solves the given instance. If trained machine-learning models are Solves the given instance. If trained machine-learning models are
available, they will be used to accelerate the solution process. available, they will be used to accelerate the solution process.
@ -127,20 +127,9 @@ class LearningSolver:
The argument `instance` may be either an Instance object or a The argument `instance` may be either an Instance object or a
filename pointing to a pickled Instance object. filename pointing to a pickled Instance object.
This method modifies the instance object. Specifically, the following This method adds a new training sample to `instance.training_sample`.
properties are set: If a filename is provided, then the file is modified in-place. That is,
the original file is overwritten.
- instance.lp_solution
- instance.lp_value
- instance.lower_bound
- instance.upper_bound
- instance.solution
- instance.solver_log
Additional solver components may set additional properties. Please
see their documentation for more details. If a filename is provided,
then the file is modified in-place. That is, the original file is
overwritten.
If `solver.solve_lp_first` is False, the properties lp_solution and If `solver.solve_lp_first` is False, the properties lp_solution and
lp_value will be set to dummy values. lp_value will be set to dummy values.
@ -190,7 +179,7 @@ class LearningSolver:
def _solve( def _solve(
self, self,
instance: Instance, instance: Union[Instance, str],
model: Any = None, model: Any = None,
output: str = "", output: str = "",
tee: bool = False, tee: bool = False,
@ -211,14 +200,18 @@ class LearningSolver:
fileformat = "pickle" fileformat = "pickle"
with open(filename, "rb") as file: with open(filename, "rb") as file:
instance = pickle.load(cast(IO[bytes], file)) instance = pickle.load(cast(IO[bytes], file))
assert isinstance(instance, Instance)
# Generate model # Generate model
if model is None: if model is None:
with RedirectOutput([]): with RedirectOutput([]):
model = instance.to_model() model = instance.to_model()
# Initialize training data # Initialize training sample
training_sample: TrainingSample = {} training_sample: TrainingSample = {}
if not hasattr(instance, "training_data"):
instance.training_data = []
instance.training_data += [training_sample]
# Initialize internal solver # Initialize internal solver
self.tee = tee self.tee = tee
@ -275,11 +268,6 @@ class LearningSolver:
for component in self.components.values(): for component in self.components.values():
component.after_solve(self, instance, model, stats, training_sample) component.after_solve(self, instance, model, stats, training_sample)
# Append training data
if not hasattr(instance, "training_data"):
instance.training_data = []
instance.training_data += [training_sample]
# Write to file, if necessary # Write to file, if necessary
if filename is not None and output is not None: if filename is not None and output is not None:
output_filename = output output_filename = output
@ -350,7 +338,7 @@ class LearningSolver:
self._restore_miplearn_logger() self._restore_miplearn_logger()
return stats return stats
def fit(self, training_instances): def fit(self, training_instances: Union[List[str], List[Instance]]) -> None:
if len(training_instances) == 0: if len(training_instances) == 0:
return return
for component in self.components.values(): for component in self.components.values():

@ -25,20 +25,19 @@ def test_learning_solver():
) )
solver.solve(instance) solver.solve(instance)
assert instance.solution["x"][0] == 1.0 data = instance.training_data[0]
assert instance.solution["x"][1] == 0.0 assert data["Solution"]["x"][0] == 1.0
assert instance.solution["x"][2] == 1.0 assert data["Solution"]["x"][1] == 0.0
assert instance.solution["x"][3] == 1.0 assert data["Solution"]["x"][2] == 1.0
assert instance.lower_bound == 1183.0 assert data["Solution"]["x"][3] == 1.0
assert instance.upper_bound == 1183.0 assert data["Lower bound"] == 1183.0
assert round(instance.lp_solution["x"][0], 3) == 1.000 assert data["Upper bound"] == 1183.0
assert round(instance.lp_solution["x"][1], 3) == 0.923 assert round(data["LP solution"]["x"][0], 3) == 1.000
assert round(instance.lp_solution["x"][2], 3) == 1.000 assert round(data["LP solution"]["x"][1], 3) == 0.923
assert round(instance.lp_solution["x"][3], 3) == 0.000 assert round(data["LP solution"]["x"][2], 3) == 1.000
assert round(instance.lp_value, 3) == 1287.923 assert round(data["LP solution"]["x"][3], 3) == 0.000
assert instance.found_violated_lazy_constraints == [] assert round(data["LP value"], 3) == 1287.923
assert instance.found_violated_user_cuts == [] assert len(data["MIP log"]) > 100
assert len(instance.solver_log) > 100
solver.fit([instance]) solver.fit([instance])
solver.solve(instance) solver.solve(instance)
@ -55,7 +54,8 @@ def test_parallel_solve():
results = solver.parallel_solve(instances, n_jobs=3) results = solver.parallel_solve(instances, n_jobs=3)
assert len(results) == 10 assert len(results) == 10
for instance in instances: for instance in instances:
assert len(instance.solution["x"].keys()) == 4 data = instance.training_data[0]
assert len(data["Solution"]["x"].keys()) == 4
def test_solve_fit_from_disk(): def test_solve_fit_from_disk():
@ -73,14 +73,14 @@ def test_solve_fit_from_disk():
solver.solve(filenames[0]) solver.solve(filenames[0])
with open(filenames[0], "rb") as file: with open(filenames[0], "rb") as file:
instance = pickle.load(file) instance = pickle.load(file)
assert hasattr(instance, "solution") assert len(instance.training_data) > 0
# Test: parallel_solve # Test: parallel_solve
solver.parallel_solve(filenames) solver.parallel_solve(filenames)
for filename in filenames: for filename in filenames:
with open(filename, "rb") as file: with open(filename, "rb") as file:
instance = pickle.load(file) instance = pickle.load(file)
assert hasattr(instance, "solution") assert len(instance.training_data) > 0
# Test: solve (with specified output) # Test: solve (with specified output)
output = [f + ".out" for f in filenames] output = [f + ".out" for f in filenames]

@ -7,13 +7,14 @@ from typing import TypedDict, Optional, Dict, Callable, Any
TrainingSample = TypedDict( TrainingSample = TypedDict(
"TrainingSample", "TrainingSample",
{ {
"LP log": Optional[str], "LP log": str,
"LP solution": Optional[Dict], "LP solution": Dict,
"LP value": Optional[float], "LP value": float,
"Lower bound": Optional[float], "Lower bound": float,
"MIP log": Optional[str], "MIP log": str,
"Solution": Optional[Dict], "Solution": Dict,
"Upper bound": Optional[float], "Upper bound": float,
"slacks": Dict,
}, },
total=False, total=False,
) )

Loading…
Cancel
Save