Skip to content

Commit 79a9647

Browse files
committed
Add scikit-learn tutorial as bonus
1 parent 26583b0 commit 79a9647

File tree

7 files changed

+1063
-0
lines changed

7 files changed

+1063
-0
lines changed

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [Download the repository](#download-the-repository)
66
- [Getting started](#getting-started)
77
- [Running the tutorial](#running-the-tutorial)
8+
- [BO with scikit-learn](#bo-with-scikit-learn)
89

910
## Material for this tutorial
1011

@@ -117,6 +118,10 @@ Alternatively, you can use supported Editor to run the jupyter notebooks, e.g. w
117118

118119
Use `cmd+Enter` to execute one cell block
119120

121+
## BO with scikit-learn
122+
123+
In the `sklearn-gp` folder, there's an additional notebook explaining the BO concepts using only the `scikit-learn` package.
124+
120125
## Citing the tutorial
121126

122127
This tutorial is registered [Zenodo](https://zenodo.org/), which means that there is a DOI for each code release.

sklearn-gp/bayesian_optimization.ipynb

+842
Large diffs are not rendered by default.

sklearn-gp/img/ackley.png

52.6 KB
Loading

sklearn-gp/img/benchmark_ackley.png

123 KB
Loading

sklearn-gp/preamble.py

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import h5py
2+
import numpy as np
3+
import matplotlib.pyplot as plt
4+
from IPython.display import set_matplotlib_formats, display
5+
6+
plt.rcParams['figure.figsize'] = 10, 7
7+
plt.rcParams['savefig.dpi'] = 300
8+
plt.rcParams['image.cmap'] = "viridis"
9+
plt.rcParams['image.interpolation'] = "none"
10+
plt.rcParams['savefig.bbox'] = "tight"

sklearn-gp/requirements.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
h5py
2+
jupyterlab
3+
matplotlib
4+
numpy
5+
scikit-learn
6+
scikit-optimize
7+
torch

sklearn-gp/utils/gp_helper.py

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import matplotlib.pyplot as plt
2+
import numpy as np
3+
from sklearn.gaussian_process import GaussianProcessRegressor
4+
from scipy.stats import norm
5+
from scipy.optimize import minimize
6+
7+
"""
8+
Helper functions for the GP and BO notebook.
9+
10+
author: Chenran Xu (chenran.xu@kit.edu)
11+
"""
12+
13+
# plot helper functions
14+
def plot_gpr_samples(gpr_model: GaussianProcessRegressor, ax, x=np.linspace(0,5,100), n_samples=5, random_state=0):
15+
"""Plot samples drawn from the Gaussian process model.
16+
modified from sklearn example: https://scikit-learn.org/stable/auto_examples/gaussian_process/plot_gpr_prior_posterior.html
17+
18+
If the Gaussian process model is not trained then the drawn samples are
19+
drawn from the prior distribution. Otherwise, the samples are drawn from
20+
the posterior distribution. Be aware that a sample here corresponds to a
21+
function.
22+
23+
Parameters
24+
----------
25+
gpr_model : `GaussianProcessRegressor`
26+
A :class:`~sklearn.gaussian_process.GaussianProcessRegressor` model.
27+
ax : matplotlib axis
28+
The matplotlib axis where to plot the samples.
29+
n_samples : int
30+
The number of samples to draw from the Gaussian process distribution.
31+
random_state: int, RandomState instance or None, defualt=0
32+
Determines random number generation to randomly draw samples.
33+
Pass an int for reproducible results across multiple function calls.
34+
"""
35+
X = x.reshape(-1, 1)
36+
37+
y_mean, y_std = gpr_model.predict(X, return_std=True)
38+
y_samples = gpr_model.sample_y(X, n_samples, random_state=random_state)
39+
40+
for idx, single_prior in enumerate(y_samples.T):
41+
ax.plot(
42+
x,
43+
single_prior,
44+
linestyle="--",
45+
alpha=0.7,
46+
label=f"Sample #{idx + 1}",
47+
)
48+
ax.plot(x, y_mean, color="black", label=r"GP mean $\mu(x)$")
49+
ax.fill_between(
50+
x,
51+
y_mean - y_std,
52+
y_mean + y_std,
53+
alpha=0.1,
54+
color="black",
55+
label=r"$\pm 1 \sigma$",
56+
)
57+
ax.set_xlabel("x")
58+
ax.set_ylabel("y")
59+
60+
def plot_gp(gpr, x, y, x_samples, y_samples, ax=None):
61+
"""Helper function to plot GP posterior
62+
63+
Input:
64+
gpr: GaussianProcessRegression
65+
x, y: fine array representing the target function
66+
x_samples, y_samples: noisy samples used for building GP
67+
ax: matplotlib.pyplot axes.axes
68+
"""
69+
if ax is None:
70+
ax = plt.gcf.add_subplot()
71+
ax.plot(x, y, label="True f")
72+
y_mean, y_std = gpr.predict(x.reshape(-1, 1), return_std=True)
73+
ax.plot(x, y_mean, label=r"GP mean $\mu(x)$", color='black')
74+
ax.fill_between(
75+
x,
76+
np.array(y_mean - y_std),
77+
np.array(y_mean + y_std),
78+
alpha=0.3,
79+
color="grey",
80+
label=r"$\pm 1 \sigma$",
81+
)
82+
ax.plot(x_samples, y_samples, "*", label="Noisy Samples")
83+
ax.set_xlabel("x")
84+
ax.set_ylabel("y")
85+
86+
def plot_gp_with_acq(gpr, x, y, x_samples, y_samples, y_acq, axes, fig, legend=True):
87+
88+
ax1, ax2 = axes
89+
x_acq_argmax = np.argmax(y_acq)
90+
91+
# plotting
92+
plot_gp(gpr, x, y, x_samples, y_samples, ax=ax1)
93+
ax1.set_xticks([])
94+
95+
ax2.set_xlabel('x')
96+
ax2.plot(x, y_acq, color='g', label=r'Acquisition $\alpha$')
97+
ax2.plot(x[x_acq_argmax], y_acq[x_acq_argmax], '*', color='r', label=r"argmax($\alpha$)")
98+
if legend:
99+
fig.subplots_adjust(0,0,0.8,0.85,hspace=0.1)
100+
fig.legend(bbox_to_anchor = (0.95,0.3,0.2,0.5))
101+
102+
def plot_bo_result(yhist, ax, n_tries = None, nsteps=None, label=None):
103+
if n_tries is None or nsteps is None:
104+
ybest = np.asarray(yhist)
105+
n_tries, nsteps = ybest.shape
106+
else:
107+
ybest = np.zeros((n_tries, nsteps))
108+
for i in range(n_tries):
109+
for n in range(nsteps):
110+
ybest[i,n] = np.max(yhist[i][:n+1])
111+
112+
ybest_mean = np.mean(ybest, axis=0)
113+
ybest_std = np.std(ybest, axis=0)
114+
115+
ax.plot(ybest_mean, label=label)
116+
ax.fill_between(np.arange(nsteps), ybest_mean-ybest_std, ybest_mean+ybest_std, alpha=0.3)
117+
118+
119+
120+
# Acquisition function classes
121+
class Acquisition:
122+
"""Acquisition function base class"""
123+
124+
def __init__(self):
125+
pass
126+
127+
def get_acq(self, x, gp: GaussianProcessRegressor):
128+
return NotImplementedError
129+
130+
def suggest_next_sample(self, gp: GaussianProcessRegressor, bounds):
131+
"""Return the next point to sample by maximizing the acquisition function
132+
133+
Input:
134+
gp: GaussianProcessRegressor object
135+
bounds: Optimization ranges with a shape of (n, 2), e.g. [[x1_min, x1_max],... [xi_min, xi_max]]
136+
"""
137+
# initial guesses
138+
xdim = bounds.shape[0]
139+
x_tries = np.random.uniform(low=bounds[:, 0], high=bounds[:, 1], size=(5000, xdim))
140+
ys = self.get_acq(x_tries,gp)
141+
max_acq = ys.max()
142+
x_max = x_tries[ys.argmax()]
143+
# simply use scipy.optimize.minimize for now
144+
res = minimize(
145+
lambda x: -1*self.get_acq(x.reshape(-1, xdim), gp),
146+
x_max.reshape(xdim,),
147+
bounds=bounds,
148+
)
149+
150+
if res.success and -res.fun >= max_acq:
151+
x_max = res.x
152+
153+
# ensure the returned point is within bounds
154+
return np.clip(x_max.reshape(-1,xdim), bounds[:, 0], bounds[:, 1])
155+
156+
class AcqEI(Acquisition):
157+
"""
158+
Expected Improvement (EI) acquisition
159+
a(x) = E[ f(x) - f(best)]
160+
161+
Parameter:
162+
xi : hyperparamter for exploitation-exploration tradeoff
163+
"""
164+
def __init__(self, xi=0.):
165+
166+
super().__init__()
167+
self.xi = xi
168+
169+
def get_acq(self, x, gp):
170+
"""Calculate EI at point x"""
171+
if len(np.shape(x)) == 1:
172+
x = np.array(x).reshape(-1,1)
173+
y_mean, y_std = gp.predict(x, return_std=True)
174+
y_best = np.max(gp.y_train_)
175+
imp = y_mean - y_best - self.xi
176+
z = imp / y_std
177+
return imp * norm.cdf(z) + y_std * norm.pdf(z)
178+
179+
180+
class AcqUCB(Acquisition):
181+
"""
182+
Upper confidence bound (UCB) acquisition
183+
a(x) = mu(x) + k * sigma(x)
184+
185+
Parameter:
186+
k : hyperparamter for exploitation-exploration tradeoff
187+
"""
188+
def __init__(self, k = 2.):
189+
190+
super().__init__()
191+
self.k = k
192+
193+
def get_acq(self, x, gp: GaussianProcessRegressor):
194+
"""Calculate UCB at point x"""
195+
if len(np.shape(x)) == 1:
196+
x = np.array(x).reshape(-1,1)
197+
mu, sigma = gp.predict(x, return_std=True)
198+
199+
return mu + sigma * self.k

0 commit comments

Comments
 (0)