parent
b81815d35b
commit
8805a83c1c
@ -0,0 +1,105 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020-2022, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Any, Hashable, Union
|
||||
|
||||
import numpy as np
|
||||
from sklearn.preprocessing import MultiLabelBinarizer
|
||||
|
||||
from miplearn.extractors.abstract import FeaturesExtractor
|
||||
from miplearn.h5 import H5File
|
||||
from miplearn.solvers.abstract import AbstractModel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _BaseMemorizingConstrComponent:
|
||||
def __init__(self, clf: Any, extractor: FeaturesExtractor, field: str) -> None:
|
||||
self.clf = clf
|
||||
self.extractor = extractor
|
||||
self.constrs_: List[Hashable] = []
|
||||
self.n_features_: int = 0
|
||||
self.n_targets_: int = 0
|
||||
self.field = field
|
||||
|
||||
def fit(
|
||||
self,
|
||||
train_h5: List[str],
|
||||
) -> None:
|
||||
logger.info("Reading training data...")
|
||||
n_samples = len(train_h5)
|
||||
x, y, constrs, n_features = [], [], [], None
|
||||
constr_to_idx: Dict[Hashable, int] = {}
|
||||
for h5_filename in train_h5:
|
||||
with H5File(h5_filename, "r") as h5:
|
||||
# Store constraints
|
||||
sample_constrs_str = h5.get_scalar(self.field)
|
||||
assert sample_constrs_str is not None
|
||||
assert isinstance(sample_constrs_str, str)
|
||||
sample_constrs = eval(sample_constrs_str)
|
||||
assert isinstance(sample_constrs, list)
|
||||
y_sample = []
|
||||
for c in sample_constrs:
|
||||
if c not in constr_to_idx:
|
||||
constr_to_idx[c] = len(constr_to_idx)
|
||||
constrs.append(c)
|
||||
y_sample.append(constr_to_idx[c])
|
||||
y.append(y_sample)
|
||||
|
||||
# Extract features
|
||||
x_sample = self.extractor.get_instance_features(h5)
|
||||
assert len(x_sample.shape) == 1
|
||||
if n_features is None:
|
||||
n_features = len(x_sample)
|
||||
else:
|
||||
assert len(x_sample) == n_features
|
||||
x.append(x_sample)
|
||||
logger.info("Constructing matrices...")
|
||||
assert n_features is not None
|
||||
self.n_features_ = n_features
|
||||
self.constrs_ = constrs
|
||||
self.n_targets_ = len(constr_to_idx)
|
||||
x_np = np.vstack(x)
|
||||
assert x_np.shape == (n_samples, n_features)
|
||||
y_np = MultiLabelBinarizer().fit_transform(y)
|
||||
assert y_np.shape == (n_samples, self.n_targets_)
|
||||
logger.info(
|
||||
f"Dataset has {n_samples:,d} samples, "
|
||||
f"{n_features:,d} features and {self.n_targets_:,d} targets"
|
||||
)
|
||||
logger.info("Training classifier...")
|
||||
self.clf.fit(x_np, y_np)
|
||||
|
||||
def predict(
|
||||
self,
|
||||
msg: str,
|
||||
test_h5: str,
|
||||
) -> List[Hashable]:
|
||||
with H5File(test_h5, "r") as h5:
|
||||
x_sample = self.extractor.get_instance_features(h5)
|
||||
assert x_sample.shape == (self.n_features_,)
|
||||
x_sample = x_sample.reshape(1, -1)
|
||||
logger.info(msg)
|
||||
y = self.clf.predict(x_sample)
|
||||
assert y.shape == (1, self.n_targets_)
|
||||
y = y.reshape(-1)
|
||||
return [self.constrs_[i] for (i, yi) in enumerate(y) if yi > 0.5]
|
||||
|
||||
|
||||
class MemorizingCutsComponent(_BaseMemorizingConstrComponent):
|
||||
def __init__(self, clf: Any, extractor: FeaturesExtractor) -> None:
|
||||
super().__init__(clf, extractor, "mip_cuts")
|
||||
|
||||
def before_mip(
|
||||
self,
|
||||
test_h5: str,
|
||||
model: AbstractModel,
|
||||
stats: Dict[str, Any],
|
||||
) -> None:
|
||||
if model.cuts_enforce is None:
|
||||
return
|
||||
assert self.constrs_ is not None
|
||||
model.cuts_aot_ = self.predict("Predicting cutting planes...", test_h5)
|
||||
stats["Cuts: AOT"] = len(model.cuts_aot_)
|
@ -0,0 +1,80 @@
|
||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
||||
# Copyright (C) 2020-2023, UChicago Argonne, LLC. All rights reserved.
|
||||
# Released under the modified BSD license. See COPYING.md for more details.
|
||||
|
||||
from typing import Any, List, Hashable, Dict
|
||||
from unittest.mock import Mock
|
||||
|
||||
import gurobipy as gp
|
||||
import networkx as nx
|
||||
from gurobipy import GRB, quicksum
|
||||
from sklearn.dummy import DummyClassifier
|
||||
from sklearn.neighbors import KNeighborsClassifier
|
||||
|
||||
from miplearn.components.cuts.mem import MemorizingCutsComponent
|
||||
from miplearn.extractors.abstract import FeaturesExtractor
|
||||
from miplearn.problems.stab import build_stab_model
|
||||
from miplearn.solvers.gurobi import GurobiModel
|
||||
from miplearn.solvers.learning import LearningSolver
|
||||
import numpy as np
|
||||
|
||||
|
||||
# def test_usage() -> None:
|
||||
# model = _build_cut_model()
|
||||
# solver = LearningSolver(components=[])
|
||||
# solver.optimize(model)
|
||||
# assert model.cuts_ is not None
|
||||
# assert len(model.cuts_) > 0
|
||||
# assert False
|
||||
|
||||
|
||||
def test_mem_component(
|
||||
stab_h5: List[str],
|
||||
default_extractor: FeaturesExtractor,
|
||||
) -> None:
|
||||
clf = Mock(wraps=DummyClassifier())
|
||||
comp = MemorizingCutsComponent(clf=clf, extractor=default_extractor)
|
||||
comp.fit(stab_h5)
|
||||
|
||||
# Should call fit method with correct arguments
|
||||
clf.fit.assert_called()
|
||||
x, y = clf.fit.call_args.args
|
||||
assert x.shape == (3, 50)
|
||||
assert y.shape == (3, 388)
|
||||
y = y.tolist()
|
||||
assert y[0][:20] == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||
assert y[1][:20] == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1]
|
||||
assert y[2][:20] == [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1]
|
||||
|
||||
# Should store violations
|
||||
assert comp.constrs_ is not None
|
||||
assert comp.n_features_ == 50
|
||||
assert comp.n_targets_ == 388
|
||||
assert len(comp.constrs_) == 388
|
||||
|
||||
# Call before-mip
|
||||
stats: Dict[str, Any] = {}
|
||||
model = Mock()
|
||||
comp.before_mip(stab_h5[0], model, stats)
|
||||
|
||||
# Should call predict with correct args
|
||||
clf.predict.assert_called()
|
||||
(x_test,) = clf.predict.call_args.args
|
||||
assert x_test.shape == (1, 50)
|
||||
|
||||
# Should set cuts_aot_
|
||||
assert model.cuts_aot_ is not None
|
||||
assert len(model.cuts_aot_) == 243
|
||||
|
||||
|
||||
def test_usage_stab(
|
||||
stab_h5: List[str],
|
||||
default_extractor: FeaturesExtractor,
|
||||
) -> None:
|
||||
data_filenames = [f.replace(".h5", ".pkl.gz") for f in stab_h5]
|
||||
clf = KNeighborsClassifier(n_neighbors=1)
|
||||
comp = MemorizingCutsComponent(clf=clf, extractor=default_extractor)
|
||||
solver = LearningSolver(components=[comp])
|
||||
solver.fit(data_filenames)
|
||||
stats = solver.optimize(data_filenames[0], build_stab_model)
|
||||
assert stats["Cuts: AOT"] > 0
|
@ -0,0 +1,23 @@
|
||||
from os.path import dirname
|
||||
|
||||
import numpy as np
|
||||
from scipy.stats import uniform, randint
|
||||
|
||||
from miplearn.collectors.basic import BasicCollector
|
||||
from miplearn.io import write_pkl_gz
|
||||
from miplearn.problems.stab import (
|
||||
MaxWeightStableSetGenerator,
|
||||
build_stab_model,
|
||||
)
|
||||
|
||||
np.random.seed(42)
|
||||
gen = MaxWeightStableSetGenerator(
|
||||
w=uniform(10.0, scale=1.0),
|
||||
n=randint(low=50, high=51),
|
||||
p=uniform(loc=0.5, scale=0.0),
|
||||
fix_graph=True,
|
||||
)
|
||||
data = gen.generate(3)
|
||||
data_filenames = write_pkl_gz(data, dirname(__file__), prefix="stab-n50-")
|
||||
collector = BasicCollector()
|
||||
collector.collect(data_filenames, build_stab_model)
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue