Bayesian optimization
Optimize an expensive black-box function with a Gaussian-process surrogate and an acquisition function.
Some objectives have no gradient and cost a fortune to evaluate: a hyperparameter setting that takes hours to train, a physical experiment, a chemical assay. Bayesian optimization spends computation to be sample-efficient. It fits a probabilistic model of the objective, then uses that model's uncertainty to choose the single most informative point to try next.
The setting
Minimize (or maximize) where each evaluation is expensive and no derivatives are available. The budget is a few dozen evaluations, not millions, so the goal is to find a good in as few queries as possible. Two ingredients make this work: a surrogate model that predicts with calibrated uncertainty, and an acquisition function that turns those predictions into the next query.
The Gaussian-process surrogate
A Gaussian process (GP) models the unknown function as a distribution over functions, specified by a covariance kernel that says how correlated the values at two inputs are. The common choice is the RBF kernel
which makes nearby inputs strongly correlated and distant ones independent, with length scale . Given observations , the GP posterior at a new point is Gaussian with closed-form mean and variance:
where , , and is observation noise[1]. The mean is the prediction; the variance grows away from observed points, encoding "I have not looked here yet". This is the conjugate Gaussian update of the MAP lesson, lifted to functions.
The acquisition function
The acquisition function scores each candidate by how worthwhile it is to evaluate, balancing exploitation (low predicted mean) against exploration (high variance). Expected improvement (EI), for minimizing with current best , is the expected amount by which a query at would beat the best so far:
with the standard normal CDF and PDF. EI is large where the mean is low (promising) or the variance is high (uncertain), and it is zero where the model is confident the value is worse than . The next query is [2].
The two terms encode exploitation and exploration, and a few computed values make their tug-of-war concrete. Take the current best :
- Exploration, isolated. A point predicted above the best, , looks unpromising on its mean alone. With small uncertainty its EI is : the model is sure it is worse, nothing to gain. Widen the uncertainty to at the same mean and EI rises to , because now the upside tail might beat . Same predicted mean, opposite verdicts; the variance term is the whole difference.
- Exploitation, isolated. A point predicted below the best, with , has EI , essentially its mean improvement: the gain is locked in, not speculative.
- Ruled out. A point with and tiny has EI (under ): far above the best and almost certain, so no query is worth spending there.
This is the named confusion behind "explore vs exploit": EI does not optimize the predicted mean, and it does not chase raw uncertainty either. It scores the expected improvement over the current best, which is why a high-variance point that looks mediocre on its mean can outrank a confident, slightly-better one.
The loop
- Fit the GP to all evaluations so far.
- Maximize the acquisition function to pick the next .
- Evaluate the true (expensive) there, add the point, repeat.
Each step uses cheap GP and acquisition computation to decide where to spend one expensive evaluation, which is why it dominates random or grid search for hyperparameter tuning.
Check it on arrays
import numpy as np
import math
rng = np.random.default_rng(0)
# Expensive black box (here cheap, for demonstration): minimize on [0, 10].
f = lambda x: np.sin(x) + 0.3 * (x - 4)**2 / 4
def rbf(a, b, ell=1.0, sf=1.0):
d2 = (a[:, None] - b[None, :])**2
return sf**2 * np.exp(-d2 / (2 * ell**2))
_erf = np.vectorize(math.erf)
normal_cdf = lambda z: 0.5 * (1 + _erf(np.asarray(z, float) / np.sqrt(2)))
normal_pdf = lambda z: np.exp(-z**2 / 2) / np.sqrt(2 * np.pi)
cand = np.linspace(0, 10, 400) # candidate grid for the acquisition max
X = np.array([1.0, 9.0]) # two initial evaluations
y = f(X)
noise = 1e-2 # nugget keeps the kernel matrix well-conditioned
for step in range(12):
K = rbf(X, X) + noise * np.eye(len(X))
alpha = np.linalg.solve(K, y) # solve, never form the inverse
ks = rbf(X, cand)
mu = ks.T @ alpha
var = np.maximum(rbf(cand, cand).diagonal() - np.sum(ks * np.linalg.solve(K, ks), axis=0), 1e-12)
s = np.sqrt(var)
f_best = y.min()
z = (f_best - mu) / s
ei = (f_best - mu) * normal_cdf(z) + s * normal_pdf(z)
idx = np.argmax(ei)
X = np.append(X, cand[idx])
y = np.append(y, f(cand[idx]))
cand = np.delete(cand, idx) # do not re-query the same point
best = X[np.argmin(y)]
# True minimum by dense search for reference
dense = np.linspace(0, 10, 100_000)
print("BO best x:", round(best, 3), " f:", round(f(best), 4))
print("true best x:", round(dense[np.argmin(f(dense))], 3),
" f:", round(f(dense).min(), 4))
print("evaluations used:", len(X))Bayesian optimization locates the minimum to within grid resolution using only about a dozen evaluations, guided by expected improvement.
Exercises
Exercise 1. With current best , compute the expected improvement at a candidate with posterior mean and standard deviation . Use and . Confirm the lesson's claim that EI here is essentially the mean improvement.
Solution
Step 1. Form the standardized gap .
Step 2. Substitute into :
Step 3. The first term is ; the second is about , negligible. So , the mean improvement . When the model is confident the point beats the best ( large and positive), the exploration term vanishes and EI reduces to the locked-in gain.
Exercise 2. Take two candidates with the same posterior mean (above the best , so unpromising on the mean) but different uncertainties and . Argue from the EI formula why the high-uncertainty point is preferred.
Solution
Step 1. Here , so is negative for both. The first term is negative, and the second term is positive.
Step 2. For : , so and . Both terms are essentially zero, so . The model is sure the point is worse, nothing to gain.
Step 3. For : , so and . The positive exploration term outweighs the negative , giving a clearly positive EI of about .
Step 4. Same mean, opposite verdicts. The wide posterior puts enough mass below that the upside tail is worth a query; the narrow one does not. EI prefers the uncertain point because it scores expected improvement, not the mean.
Exercise 3 (code). Implement the EI formula and verify both hand results: at , and that the point at has strictly larger EI than the point at the same mean.
Solution
import numpy as np
import math
_erf = np.vectorize(math.erf)
Phi = lambda z: 0.5 * (1 + _erf(np.asarray(z, float) / np.sqrt(2)))
phi = lambda z: np.exp(-np.asarray(z, float)**2 / 2) / np.sqrt(2 * np.pi)
def ei(mu, s, f_best=0.0):
z = (f_best - mu) / s
return (f_best - mu) * Phi(z) + s * phi(z)
ei_exploit = ei(-0.5, 0.1) # confident improvement
ei_narrow = ei(0.5, 0.1) # unpromising, certain
ei_wide = ei(0.5, 1.0) # unpromising mean, uncertain
assert np.isclose(ei_exploit, 0.5, atol=1e-3)
assert ei_wide > ei_narrow
print("EI(-0.5, 0.1):", round(float(ei_exploit), 4))
print("EI(0.5, 0.1):", round(float(ei_narrow), 6),
" EI(0.5, 1.0):", round(float(ei_wide), 4))The confident point scores its full mean improvement of , and the wide-posterior point outscores the narrow one at the same mean, the exploration term doing the work.
This closes the A3 optimization track; the next subsection, A4, examines the finite-precision arithmetic these algorithms actually run in.
References
- [1]Rasmussen, C. E., & Williams, C. K. I. (2006). Gaussian Processes for Machine Learning, §2.2. MIT Press. The GP posterior mean and variance for regression.↩
- [2]Frazier, P. I. (2018). "A tutorial on Bayesian optimization". Acquisition functions and expected improvement (arXiv:1807.02811).↩