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):
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_pred_lb, y_pred_ub = y_pred[:, 1], y_pred[:, 1]
ev = {

@ -68,7 +68,8 @@ class PrimalSolutionComponent(Component):
for label in [0, 1]:
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)
if y_avg < 0.001 or y_avg >= 0.999:
self.classifiers[category, label] = round(y_avg)
@ -130,7 +131,7 @@ class PrimalSolutionComponent(Component):
desc="Evaluate (primal)",
):
instance = instances[instance_idx]
solution_actual = instance.solution
solution_actual = instance.training_data[0]["Solution"]
solution_pred = self.predict(instance)
vars_all, vars_one, vars_zero = set(), set(), set()

@ -24,8 +24,8 @@ def test_convert_tight_usage():
)
# Solve original problem
solver.solve(instance)
original_upper_bound = instance.upper_bound
stats = solver.solve(instance)
original_upper_bound = stats["Upper bound"]
# Should collect training data
assert instance.training_data[0]["slacks"]["eq_capacity"] == 0.0
@ -35,12 +35,12 @@ def test_convert_tight_usage():
stats = solver.solve(instance)
# 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: Subopt iterations"] == 0
class TestInstance(Instance):
class SampleInstance(Instance):
def to_model(self):
import gurobipy as grb
@ -70,9 +70,9 @@ def test_convert_tight_infeasibility():
components=[comp],
solve_lp_first=False,
)
instance = TestInstance()
instance = SampleInstance()
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: Subopt iterations"] == 0
@ -93,9 +93,9 @@ def test_convert_tight_suboptimality():
components=[comp],
solve_lp_first=False,
)
instance = TestInstance()
instance = SampleInstance()
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: Subopt iterations"] == 1
@ -116,8 +116,8 @@ def test_convert_tight_optimal():
components=[comp],
solve_lp_first=False,
)
instance = TestInstance()
instance = SampleInstance()
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: Subopt iterations"] == 0

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

@ -50,7 +50,7 @@ def test_evaluate():
comp = PrimalSolutionComponent(classifier=[clf_zero, clf_one], threshold=0.50)
comp.fit(instances[:1])
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])
assert ev == {
"Fix one": {

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

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

@ -115,11 +115,11 @@ class LearningSolver:
def solve(
self,
instance,
model=None,
output="",
tee=False,
):
instance: Union[Instance, str],
model: Any = None,
output: str = "",
tee: bool = False,
) -> MIPSolveStats:
"""
Solves the given instance. If trained machine-learning models are
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
filename pointing to a pickled Instance object.
This method modifies the instance object. Specifically, the following
properties are set:
- 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.
This method adds a new training sample to `instance.training_sample`.
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
lp_value will be set to dummy values.
@ -190,7 +179,7 @@ class LearningSolver:
def _solve(
self,
instance: Instance,
instance: Union[Instance, str],
model: Any = None,
output: str = "",
tee: bool = False,
@ -211,14 +200,18 @@ class LearningSolver:
fileformat = "pickle"
with open(filename, "rb") as file:
instance = pickle.load(cast(IO[bytes], file))
assert isinstance(instance, Instance)
# Generate model
if model is None:
with RedirectOutput([]):
model = instance.to_model()
# Initialize training data
# Initialize training sample
training_sample: TrainingSample = {}
if not hasattr(instance, "training_data"):
instance.training_data = []
instance.training_data += [training_sample]
# Initialize internal solver
self.tee = tee
@ -275,11 +268,6 @@ class LearningSolver:
for component in self.components.values():
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
if filename is not None and output is not None:
output_filename = output
@ -350,7 +338,7 @@ class LearningSolver:
self._restore_miplearn_logger()
return stats
def fit(self, training_instances):
def fit(self, training_instances: Union[List[str], List[Instance]]) -> None:
if len(training_instances) == 0:
return
for component in self.components.values():

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

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

Loading…
Cancel
Save