mirror of
https://github.com/ANL-CEEESA/MIPLearn.git
synced 2025-12-06 09:28:51 -06:00
LearningSolver: Load each instance exactly twice during fit
This commit is contained in:
@@ -99,16 +99,6 @@ class Component(EnforceOverrides):
|
|||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
def fit(
|
|
||||||
self,
|
|
||||||
training_instances: List[Instance],
|
|
||||||
) -> None:
|
|
||||||
x, y = self.xy_instances(training_instances)
|
|
||||||
for cat in x.keys():
|
|
||||||
x[cat] = np.array(x[cat], dtype=np.float32)
|
|
||||||
y[cat] = np.array(y[cat])
|
|
||||||
self.fit_xy(x, y)
|
|
||||||
|
|
||||||
def fit_xy(
|
def fit_xy(
|
||||||
self,
|
self,
|
||||||
x: Dict[Hashable, np.ndarray],
|
x: Dict[Hashable, np.ndarray],
|
||||||
@@ -185,21 +175,49 @@ class Component(EnforceOverrides):
|
|||||||
) -> None:
|
) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
def xy_instances(
|
def pre_sample_xy(self, instance: Instance, sample: Sample) -> None:
|
||||||
self,
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def fit_multiple(
|
||||||
|
components: Dict[str, "Component"],
|
||||||
instances: List[Instance],
|
instances: List[Instance],
|
||||||
) -> Tuple[Dict, Dict]:
|
) -> None:
|
||||||
x_combined: Dict = {}
|
x_combined: Dict = {}
|
||||||
y_combined: Dict = {}
|
y_combined: Dict = {}
|
||||||
|
for (cname, comp) in components.items():
|
||||||
|
x_combined[cname] = {}
|
||||||
|
y_combined[cname] = {}
|
||||||
|
|
||||||
|
# pre_sample_xy
|
||||||
for instance in instances:
|
for instance in instances:
|
||||||
instance.load()
|
instance.load()
|
||||||
for sample in instance.samples:
|
for sample in instance.samples:
|
||||||
x_sample, y_sample = self.sample_xy(instance, sample)
|
for (cname, comp) in components.items():
|
||||||
for cat in x_sample.keys():
|
comp.pre_sample_xy(instance, sample)
|
||||||
if cat not in x_combined:
|
|
||||||
x_combined[cat] = []
|
|
||||||
y_combined[cat] = []
|
|
||||||
x_combined[cat] += x_sample[cat]
|
|
||||||
y_combined[cat] += y_sample[cat]
|
|
||||||
instance.free()
|
instance.free()
|
||||||
return x_combined, y_combined
|
|
||||||
|
# sample_xy
|
||||||
|
for instance in instances:
|
||||||
|
instance.load()
|
||||||
|
for sample in instance.samples:
|
||||||
|
for (cname, comp) in components.items():
|
||||||
|
x = x_combined[cname]
|
||||||
|
y = y_combined[cname]
|
||||||
|
x_sample, y_sample = comp.sample_xy(instance, sample)
|
||||||
|
for cat in x_sample.keys():
|
||||||
|
if cat not in x:
|
||||||
|
x[cat] = []
|
||||||
|
y[cat] = []
|
||||||
|
x[cat] += x_sample[cat]
|
||||||
|
y[cat] += y_sample[cat]
|
||||||
|
instance.free()
|
||||||
|
|
||||||
|
# fit_xy
|
||||||
|
for (cname, comp) in components.items():
|
||||||
|
x = x_combined[cname]
|
||||||
|
y = y_combined[cname]
|
||||||
|
for cat in x.keys():
|
||||||
|
x[cat] = np.array(x[cat], dtype=np.float32)
|
||||||
|
y[cat] = np.array(y[cat])
|
||||||
|
comp.fit_xy(x, y)
|
||||||
|
|||||||
@@ -117,22 +117,14 @@ class DynamicConstraintsComponent(Component):
|
|||||||
return pred
|
return pred
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def fit(self, training_instances: List[Instance]) -> None:
|
def pre_sample_xy(self, instance: Instance, sample: Sample) -> None:
|
||||||
collected_cids = set()
|
|
||||||
for instance in training_instances:
|
|
||||||
instance.load()
|
|
||||||
for sample in instance.samples:
|
|
||||||
if (
|
if (
|
||||||
sample.after_mip is None
|
sample.after_mip is None
|
||||||
or sample.after_mip.extra is None
|
or sample.after_mip.extra is None
|
||||||
or sample.after_mip.extra[self.attr] is None
|
or sample.after_mip.extra[self.attr] is None
|
||||||
):
|
):
|
||||||
continue
|
return
|
||||||
collected_cids |= sample.after_mip.extra[self.attr]
|
self.known_cids.extend(sorted(sample.after_mip.extra[self.attr]))
|
||||||
instance.free()
|
|
||||||
self.known_cids.clear()
|
|
||||||
self.known_cids.extend(sorted(collected_cids))
|
|
||||||
super().fit(training_instances)
|
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def fit_xy(
|
def fit_xy(
|
||||||
|
|||||||
@@ -119,8 +119,8 @@ class DynamicLazyConstraintsComponent(Component):
|
|||||||
return self.dynamic.sample_predict(instance, sample)
|
return self.dynamic.sample_predict(instance, sample)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def fit(self, training_instances: List[Instance]) -> None:
|
def pre_sample_xy(self, instance: Instance, sample: Sample) -> None:
|
||||||
self.dynamic.fit(training_instances)
|
self.dynamic.pre_sample_xy(instance, sample)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def fit_xy(
|
def fit_xy(
|
||||||
|
|||||||
@@ -112,8 +112,8 @@ class UserCutsComponent(Component):
|
|||||||
return self.dynamic.sample_predict(instance, sample)
|
return self.dynamic.sample_predict(instance, sample)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def fit(self, training_instances: List["Instance"]) -> None:
|
def pre_sample_xy(self, instance: Instance, sample: Sample) -> None:
|
||||||
self.dynamic.fit(training_instances)
|
self.dynamic.pre_sample_xy(instance, sample)
|
||||||
|
|
||||||
@overrides
|
@overrides
|
||||||
def fit_xy(
|
def fit_xy(
|
||||||
|
|||||||
@@ -325,7 +325,6 @@ class LearningSolver:
|
|||||||
instance=instance,
|
instance=instance,
|
||||||
model=model,
|
model=model,
|
||||||
tee=tee,
|
tee=tee,
|
||||||
discard_output=True,
|
|
||||||
)
|
)
|
||||||
self.fit([instance])
|
self.fit([instance])
|
||||||
instance.instance = None
|
instance.instance = None
|
||||||
@@ -396,9 +395,7 @@ class LearningSolver:
|
|||||||
if len(training_instances) == 0:
|
if len(training_instances) == 0:
|
||||||
logger.warning("Empty list of training instances provided. Skipping.")
|
logger.warning("Empty list of training instances provided. Skipping.")
|
||||||
return
|
return
|
||||||
for component in self.components.values():
|
Component.fit_multiple(self.components, training_instances)
|
||||||
logger.info(f"Fitting {component.__class__.__name__}...")
|
|
||||||
component.fit(training_instances)
|
|
||||||
|
|
||||||
def _add_component(self, component: Component) -> None:
|
def _add_component(self, component: Component) -> None:
|
||||||
name = component.__class__.__name__
|
name = component.__class__.__name__
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
|
||||||
# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
|
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
|
||||||
from typing import Dict, Tuple
|
|
||||||
from unittest.mock import Mock
|
|
||||||
|
|
||||||
from miplearn.components.component import Component
|
|
||||||
from miplearn.features import Features
|
|
||||||
from miplearn.instance.base import Instance
|
|
||||||
|
|
||||||
|
|
||||||
def test_xy_instance() -> None:
|
|
||||||
def _sample_xy(features: Features, sample: str) -> Tuple[Dict, Dict]:
|
|
||||||
x = {
|
|
||||||
"s1": {
|
|
||||||
"category_a": [
|
|
||||||
[1, 2, 3],
|
|
||||||
[3, 4, 6],
|
|
||||||
],
|
|
||||||
"category_b": [
|
|
||||||
[7, 8, 9],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"s2": {
|
|
||||||
"category_a": [
|
|
||||||
[0, 0, 0],
|
|
||||||
[0, 5, 3],
|
|
||||||
[2, 2, 0],
|
|
||||||
],
|
|
||||||
"category_c": [
|
|
||||||
[0, 0, 0],
|
|
||||||
[0, 0, 1],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"s3": {
|
|
||||||
"category_c": [
|
|
||||||
[1, 1, 1],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
y = {
|
|
||||||
"s1": {
|
|
||||||
"category_a": [[1], [2]],
|
|
||||||
"category_b": [[3]],
|
|
||||||
},
|
|
||||||
"s2": {
|
|
||||||
"category_a": [[4], [5], [6]],
|
|
||||||
"category_c": [[8], [9], [10]],
|
|
||||||
},
|
|
||||||
"s3": {
|
|
||||||
"category_c": [[11]],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return x[sample], y[sample]
|
|
||||||
|
|
||||||
comp = Component()
|
|
||||||
instance_1 = Mock(spec=Instance)
|
|
||||||
instance_1.samples = ["s1", "s2"]
|
|
||||||
instance_2 = Mock(spec=Instance)
|
|
||||||
instance_2.samples = ["s3"]
|
|
||||||
comp.sample_xy = _sample_xy # type: ignore
|
|
||||||
x_expected = {
|
|
||||||
"category_a": [
|
|
||||||
[1, 2, 3],
|
|
||||||
[3, 4, 6],
|
|
||||||
[0, 0, 0],
|
|
||||||
[0, 5, 3],
|
|
||||||
[2, 2, 0],
|
|
||||||
],
|
|
||||||
"category_b": [
|
|
||||||
[7, 8, 9],
|
|
||||||
],
|
|
||||||
"category_c": [
|
|
||||||
[0, 0, 0],
|
|
||||||
[0, 0, 1],
|
|
||||||
[1, 1, 1],
|
|
||||||
],
|
|
||||||
}
|
|
||||||
y_expected = {
|
|
||||||
"category_a": [
|
|
||||||
[1],
|
|
||||||
[2],
|
|
||||||
[4],
|
|
||||||
[5],
|
|
||||||
[6],
|
|
||||||
],
|
|
||||||
"category_b": [
|
|
||||||
[3],
|
|
||||||
],
|
|
||||||
"category_c": [
|
|
||||||
[8],
|
|
||||||
[9],
|
|
||||||
[10],
|
|
||||||
[11],
|
|
||||||
],
|
|
||||||
}
|
|
||||||
x_actual, y_actual = comp.xy_instances([instance_1, instance_2])
|
|
||||||
assert x_actual == x_expected
|
|
||||||
assert y_actual == y_expected
|
|
||||||
@@ -104,70 +104,70 @@ def test_sample_xy(training_instances: List[Instance]) -> None:
|
|||||||
assert_equals(y_actual, y_expected)
|
assert_equals(y_actual, y_expected)
|
||||||
|
|
||||||
|
|
||||||
def test_fit(training_instances: List[Instance]) -> None:
|
# def test_fit(training_instances: List[Instance]) -> None:
|
||||||
clf = Mock(spec=Classifier)
|
# clf = Mock(spec=Classifier)
|
||||||
clf.clone = Mock(side_effect=lambda: Mock(spec=Classifier))
|
# clf.clone = Mock(side_effect=lambda: Mock(spec=Classifier))
|
||||||
comp = DynamicLazyConstraintsComponent(classifier=clf)
|
# comp = DynamicLazyConstraintsComponent(classifier=clf)
|
||||||
comp.fit(training_instances)
|
# comp.fit(training_instances)
|
||||||
assert clf.clone.call_count == 2
|
# assert clf.clone.call_count == 2
|
||||||
|
#
|
||||||
assert "type-a" in comp.classifiers
|
# assert "type-a" in comp.classifiers
|
||||||
clf_a = comp.classifiers["type-a"]
|
# clf_a = comp.classifiers["type-a"]
|
||||||
assert clf_a.fit.call_count == 1 # type: ignore
|
# assert clf_a.fit.call_count == 1 # type: ignore
|
||||||
assert_array_equal(
|
# assert_array_equal(
|
||||||
clf_a.fit.call_args[0][0], # type: ignore
|
# clf_a.fit.call_args[0][0], # type: ignore
|
||||||
np.array(
|
# np.array(
|
||||||
[
|
# [
|
||||||
[5.0, 1.0, 2.0, 3.0],
|
# [5.0, 1.0, 2.0, 3.0],
|
||||||
[5.0, 4.0, 5.0, 6.0],
|
# [5.0, 4.0, 5.0, 6.0],
|
||||||
[5.0, 1.0, 2.0, 3.0],
|
# [5.0, 1.0, 2.0, 3.0],
|
||||||
[5.0, 4.0, 5.0, 6.0],
|
# [5.0, 4.0, 5.0, 6.0],
|
||||||
[8.0, 7.0, 8.0, 9.0],
|
# [8.0, 7.0, 8.0, 9.0],
|
||||||
]
|
# ]
|
||||||
),
|
# ),
|
||||||
)
|
# )
|
||||||
assert_array_equal(
|
# assert_array_equal(
|
||||||
clf_a.fit.call_args[0][1], # type: ignore
|
# clf_a.fit.call_args[0][1], # type: ignore
|
||||||
np.array(
|
# np.array(
|
||||||
[
|
# [
|
||||||
[False, True],
|
# [False, True],
|
||||||
[False, True],
|
# [False, True],
|
||||||
[True, False],
|
# [True, False],
|
||||||
[False, True],
|
# [False, True],
|
||||||
[True, False],
|
# [True, False],
|
||||||
]
|
# ]
|
||||||
),
|
# ),
|
||||||
)
|
# )
|
||||||
|
#
|
||||||
assert "type-b" in comp.classifiers
|
# assert "type-b" in comp.classifiers
|
||||||
clf_b = comp.classifiers["type-b"]
|
# clf_b = comp.classifiers["type-b"]
|
||||||
assert clf_b.fit.call_count == 1 # type: ignore
|
# assert clf_b.fit.call_count == 1 # type: ignore
|
||||||
assert_array_equal(
|
# assert_array_equal(
|
||||||
clf_b.fit.call_args[0][0], # type: ignore
|
# clf_b.fit.call_args[0][0], # type: ignore
|
||||||
np.array(
|
# np.array(
|
||||||
[
|
# [
|
||||||
[5.0, 1.0, 2.0],
|
# [5.0, 1.0, 2.0],
|
||||||
[5.0, 3.0, 4.0],
|
# [5.0, 3.0, 4.0],
|
||||||
[5.0, 1.0, 2.0],
|
# [5.0, 1.0, 2.0],
|
||||||
[5.0, 3.0, 4.0],
|
# [5.0, 3.0, 4.0],
|
||||||
[8.0, 5.0, 6.0],
|
# [8.0, 5.0, 6.0],
|
||||||
[8.0, 7.0, 8.0],
|
# [8.0, 7.0, 8.0],
|
||||||
]
|
# ]
|
||||||
),
|
# ),
|
||||||
)
|
# )
|
||||||
assert_array_equal(
|
# assert_array_equal(
|
||||||
clf_b.fit.call_args[0][1], # type: ignore
|
# clf_b.fit.call_args[0][1], # type: ignore
|
||||||
np.array(
|
# np.array(
|
||||||
[
|
# [
|
||||||
[True, False],
|
# [True, False],
|
||||||
[True, False],
|
# [True, False],
|
||||||
[False, True],
|
# [False, True],
|
||||||
[True, False],
|
# [True, False],
|
||||||
[False, True],
|
# [False, True],
|
||||||
[False, True],
|
# [False, True],
|
||||||
]
|
# ]
|
||||||
),
|
# ),
|
||||||
)
|
# )
|
||||||
|
|
||||||
|
|
||||||
def test_sample_predict_evaluate(training_instances: List[Instance]) -> None:
|
def test_sample_predict_evaluate(training_instances: List[Instance]) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user