Skip to content

Commit 398395a

Browse files
committed
Merge branch 'feature/sawei' of github.com:automl/SMAC3 into feature/sawei
2 parents 4b25599 + 0f83cb5 commit 398395a

File tree

6 files changed

+140
-10
lines changed

6 files changed

+140
-10
lines changed

examples/6_advanced_features/2_SAWEI.py

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818

1919
import smac
2020
import numpy as np
21-
from smac.callback.sawei_callback import get_sawei_kwargs
21+
import pandas as pd
22+
import seaborn as sns
23+
from smac.callback.sawei_callback import get_sawei_kwargs, WEITracker, UpperBoundRegretCallback
2224

2325

2426
__copyright__ = "Copyright 2021, AutoML.org Freiburg-Hannover"
@@ -28,6 +30,11 @@
2830
class QuadraticFunction:
2931
@property
3032
def configspace(self) -> ConfigurationSpace:
33+
"""Configuration/search space of the problem
34+
35+
Returns:
36+
ConfigurationSpace: Configuration space.
37+
"""
3138
cs = ConfigurationSpace(seed=0)
3239
x = Float("x", (-5, 5), default=-5)
3340
cs.add_hyperparameters([x])
@@ -41,6 +48,14 @@ def train(self, config: Configuration, seed: int = 0) -> float:
4148

4249

4350
def plot(runhistory: RunHistory, incumbent: Configuration) -> None:
51+
"""Plot the objective function, all trials and the incumbent
52+
53+
Args:
54+
runhistory (RunHistory): Runhistory object after optimization
55+
incumbent (Configuration): Best configuration found by SMAC
56+
"""
57+
sns.set_style("whitegrid")
58+
sns.set_palette("colorblind")
4459
plt.figure()
4560

4661
# Plot ground truth
@@ -60,34 +75,72 @@ def plot(runhistory: RunHistory, incumbent: Configuration) -> None:
6075

6176
plt.show()
6277

78+
def plot_sawei(facade: smac.facade.abstract_facade.AbstractFacade) -> None:
79+
"""Plot the interpolation weight alpha of SAWEI and the UBR (Upper Bound Regret)
80+
81+
Args:
82+
facade (smac.facade.abstract_facade.AbstractFacade): SMAC facade object after optimization
83+
"""
84+
85+
sns.set_style("whitegrid")
86+
sns.set_palette("colorblind")
87+
88+
weitracker = None
89+
ubrtracker = None
90+
for callback in facade._callbacks:
91+
if isinstance(callback, WEITracker):
92+
weitracker = callback
93+
if isinstance(callback, UpperBoundRegretCallback):
94+
ubrtracker = callback
95+
96+
# The data also lies in the output folder as a csv file
97+
df_alpha = pd.DataFrame(weitracker.history)
98+
df_ubr = pd.DataFrame(ubrtracker.history)
99+
df = pd.merge(df_alpha, df_ubr, on="n_evaluated")
100+
101+
fig, ax = plt.subplots()
102+
ax2 = ax.twinx()
103+
ax = sns.lineplot(data=df, x="n_evaluated", y="alpha", ax=ax, label="alpha", color=sns.color_palette()[0])
104+
ax2 = sns.lineplot(data=df, x="n_evaluated", y="ubr", ax=ax2, label="ubr", color=sns.color_palette()[1])
105+
ax.legend(loc="upper left")
106+
ax2.legend(loc="upper right")
107+
ax.set_ylabel("alpha")
108+
ax2.set_ylabel("ubr")
109+
ax.set_xlabel("n_evaluated")
110+
plt.show()
111+
112+
63113

64114
if __name__ == "__main__":
65115
model = QuadraticFunction()
66116

67117
# Scenario object specifying the optimization "environment"
68-
scenario = Scenario(model.configspace, deterministic=True, n_trials=100, seed=np.random.randint(low=0,high=10000))
118+
scenario = Scenario(model.configspace, deterministic=True, n_trials=50, seed=np.random.randint(low=0,high=10000))
69119

70120
# Get the kwargs necessary to use SAWEI
71121
# SAWEI is implemented as a chain of callbacks
72122
sawei_kwargs = get_sawei_kwargs()
73123

74124
# Now we use SMAC to find the best hyperparameters
75-
smac = BlackBoxFacade(
125+
optimizer = BlackBoxFacade(
76126
scenario,
77127
model.train, # We pass the target function here
78128
overwrite=True, # Overrides any previous results that are found that are inconsistent with the meta-data
79129
**sawei_kwargs # Add SAWEI
80130
)
81131

82-
incumbent = smac.optimize()
132+
incumbent = optimizer.optimize()
83133

84134
# Get cost of default configuration
85-
default_cost = smac.validate(model.configspace.get_default_configuration())
135+
default_cost = optimizer.validate(model.configspace.get_default_configuration())
86136
print(f"Default cost: {default_cost}")
87137

88138
# Let's calculate the cost of the incumbent
89-
incumbent_cost = smac.validate(incumbent)
139+
incumbent_cost = optimizer.validate(incumbent)
90140
print(f"Incumbent cost: {incumbent_cost}")
91141

92142
# Let's plot it too
93-
plot(smac.runhistory, incumbent)
143+
plot(optimizer.runhistory, incumbent)
144+
145+
# Let's plot the interpolation weight alpha of SAWEI and the UBR (Upper Bound Regret)
146+
plot_sawei(optimizer)

setup.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ def read_file(filepath: str) -> str:
5959
"pytest-coverage",
6060
"pytest-cases",
6161
],
62+
"wandb": [
63+
"wandb",
64+
]
6265
}
6366

6467
setuptools.setup(
@@ -88,6 +91,7 @@ def read_file(filepath: str) -> str:
8891
"emcee>=3.0.0",
8992
"regex",
9093
"pyyaml",
94+
"seaborn",
9195
],
9296
extras_require=extras_require,
9397
test_suite="pytest",

smac/callback/utils.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22
import numpy as np
33

44

5-
def query_callback(solver: smac.main.smbo.SMBO, callback_type: str, key: str) -> float:
5+
def query_callback(solver: smac.main.smbo.SMBO, callback_type: str, key: str, raise_error: bool = True) -> float:
66
obs = None
77
for callback in solver._callbacks:
88
if type(callback).__name__ == callback_type:
99
if callback.history:
1010
obs = callback.history[-1][key]
11+
elif hasattr(callback, key):
12+
obs = getattr(callback, key)
1113
else:
1214
obs = -np.inf # FIXME: alpha can only take 0-1 so -np.inf is not too correct
1315
break
14-
if obs is None:
16+
if obs is None and raise_error:
1517
raise ValueError(f"Couldn't find the {callback_type} callback.")
1618

1719
return obs

smac/callback/wandb_callback.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
from __future__ import annotations
2+
3+
import smac
4+
from smac.callback import Callback
5+
6+
7+
class WandBCallback(Callback):
8+
def __init__(
9+
self,
10+
project: str,
11+
entity: str,
12+
id: str | None = None,
13+
outdir: str | None = None,
14+
mode: str | None = None,
15+
resume: str = "allow",
16+
job_type: str | None = None,
17+
group: str | None = None,
18+
config: dict | str | None = None,
19+
save_code: bool = True,
20+
**kwargs
21+
) -> None:
22+
import wandb
23+
self.run = wandb.init(
24+
id=id,
25+
resume=resume,
26+
mode=mode,
27+
project=project,
28+
job_type=job_type,
29+
entity=entity,
30+
group=group,
31+
dir=outdir,
32+
config=config,
33+
save_code=save_code,
34+
**kwargs
35+
)
36+
super().__init__()
37+
38+
39+
def on_end(self, smbo: smac.main.smbo.SMBO) -> None:
40+
intensifier_data = smbo.intensifier.get_data()
41+
trajectory = intensifier_data["trajectory"]
42+
import pandas as pd
43+
df = pd.DataFrame(data=trajectory)
44+
print(df)
45+
# trajectory = Table(dataframe=df, allow_mixed_types=True)
46+
df["costs"] = df["costs"].apply(lambda x: x[0]) # TODO properly log multi costs
47+
for index, row in df.iterrows():
48+
print(dict(row))
49+
self.run.log(dict(row))
50+
self.run.finish()
51+
return super().on_end(smbo)
52+
53+
54+

smac/intensifier/intensifier.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from typing import Any, Iterator
4+
import dataclasses
45

56
from ConfigSpace import Configuration
67

@@ -90,6 +91,16 @@ def get_state(self) -> dict[str, Any]: # noqa: D102
9091

9192
def set_state(self, state: dict[str, Any]) -> None: # noqa: D102
9293
self._queue = [(self.runhistory.get_config(id), n) for id, n in state["queue"]]
94+
95+
def get_data(self):
96+
data = {
97+
"incumbent_ids": [self.runhistory.get_config_id(config) for config in self._incumbents],
98+
"rejected_config_ids": self._rejected_config_ids,
99+
"incumbents_changed": self._incumbents_changed,
100+
"trajectory": [dataclasses.asdict(item) for item in self._trajectory],
101+
"state": self.get_state(),
102+
}
103+
return data
93104

94105
def __iter__(self) -> Iterator[TrialInfo]:
95106
"""This iter method holds the logic for the intensification loop.

smac/main/config_selector.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,13 @@ def __iter__(self) -> Iterator[Configuration]:
217217
# we don't need to train the next time
218218
self._previous_entries = Y.shape[0]
219219

220-
# Now we maximize the acquisition function
220+
# Now we maximize the acquisition function
221221
challengers = self._acquisition_maximizer.maximize(
222222
previous_configs,
223223
random_design=self._random_design,
224224
)
225+
# DO NOT FIDDLE WITH THE CHALLENGERS LIST because it is an iterator
226+
# e.g. if we convert it to a list, the following for loop does not work!
225227

226228
counter = 0
227229
failed_counter = 0
@@ -241,6 +243,10 @@ def __iter__(self) -> Iterator[Configuration]:
241243
)
242244
break
243245
else:
246+
logger.debug(
247+
f"Config {config} was already processed. Increase fail counter. "\
248+
"Re-maximize acquisition function to find a new config."
249+
)
244250
failed_counter += 1
245251

246252
# We exit the loop if we have tried to add the same configuration too often

0 commit comments

Comments
 (0)