Python实现基于高斯过程(Gaussian Process)的贝叶斯优化与不确定性估计
大家好,今天我们来深入探讨一个在优化问题中非常强大的工具:基于高斯过程的贝叶斯优化,以及如何利用高斯过程进行不确定性估计。我们将重点关注使用Python实现这些概念。
1. 引言:优化的挑战与贝叶斯优化的优势
在机器学习、工程设计等领域,我们经常需要优化一个目标函数,找到使其达到最大值或最小值的参数组合。然而,很多实际问题中的目标函数往往具有以下挑战:
- 非凸性: 存在多个局部最优解,传统的梯度下降方法容易陷入局部最优。
- 黑盒性: 我们无法获得目标函数的梯度信息,只能通过评估不同参数组合的结果来了解其性质。
- 评估代价高昂: 每次评估目标函数都需要耗费大量的计算资源或时间,例如运行一次复杂的模拟。
贝叶斯优化(Bayesian Optimization)是一种解决这些挑战的有效方法。它通过建立目标函数的概率模型(通常是高斯过程),并利用采集函数(Acquisition Function)来指导搜索过程,从而在尽可能少的评估次数下找到全局最优解。
2. 高斯过程 (Gaussian Process) 回顾
高斯过程是一种强大的非参数概率模型,它定义了一个函数上的概率分布。简单来说,高斯过程认为函数值服从多元高斯分布。给定一些观测数据,我们可以使用高斯过程来预测未知点的函数值,并估计预测结果的不确定性。
一个高斯过程由它的均值函数 m(x) 和协方差函数 k(x, x’) 完全定义,其中 x 和 x’ 是输入空间中的两个点。
- 均值函数 m(x): 表示函数在点 x 的期望值,通常设置为0。
-
协方差函数 k(x, x’): 描述了函数在点 x 和 x’ 之间的相关性。常用的协方差函数包括:
-
径向基函数 (RBF) / 平方指数核 (Squared Exponential Kernel):
k(x, x') = σ^2 * exp(-||x - x'||^2 / (2 * l^2))其中,σ^2 是信号方差,l 是长度尺度。RBF核假设附近的点具有更高的相关性,并且可以捕捉平滑的函数。
-
Matérn核:
Matérn核是RBF核的推广,它引入了一个参数 ν 来控制函数的平滑度。Matérn 5/2核是一个常用的选择:
k(x, x') = σ^2 * (1 + sqrt(5) * ||x - x'|| / l + 5 * ||x - x'||^2 / (3 * l^2)) * exp(-sqrt(5) * ||x - x'|| / l)Matérn核比RBF核更灵活,可以适应不同平滑度的函数。
3. 高斯过程的Python实现
-
我们可以使用 scikit-learn 库来实现高斯过程。
import numpy as np
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, ConstantKernel as C
# 定义核函数
kernel = C(1.0, (1e-3, 1e3)) * RBF(1.0, (1e-2, 1e2))
# 创建高斯过程回归模型
gp = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=9)
# 训练数据
X = np.atleast_2d([1., 3., 5., 6., 7., 8.]).T
y = np.array([0.5, 1.2, 3.1, 4.2, 5.1, 5.4])
# 训练模型
gp.fit(X, y)
# 预测新的数据点
x_pred = np.atleast_2d(np.linspace(0, 10, 100)).T
y_pred, sigma = gp.predict(x_pred, return_std=True) # sigma 是标准差,用于衡量不确定性
# 可视化结果
import matplotlib.pyplot as plt
plt.figure()
plt.plot(X, y, 'r.', markersize=10, label='Observations')
plt.plot(x_pred, y_pred, 'b-', label='Prediction')
plt.fill(np.concatenate([x_pred, x_pred[::-1]]),
np.concatenate([y_pred - 1.9600 * sigma,
(y_pred + 1.9600 * sigma)[::-1]]),
alpha=.5, fc='b', ec='None', label='95% confidence interval')
plt.xlabel('$x$')
plt.ylabel('$f(x)$')
plt.legend(loc='upper left')
plt.title('Gaussian Process Regression')
plt.show()
这段代码首先定义了一个RBF核函数,并创建了一个高斯过程回归模型。然后,我们使用一些训练数据来训练模型,并预测新的数据点。gp.predict(x_pred, return_std=True) 函数返回预测值和标准差,标准差用于衡量预测的不确定性。最后,我们使用matplotlib库将结果可视化,包括观测数据、预测曲线和95%置信区间。
4. 贝叶斯优化的工作原理
贝叶斯优化通过迭代的方式,不断地更新目标函数的概率模型,并利用采集函数来选择下一个需要评估的参数组合。其基本步骤如下:
- 初始化: 选择一些初始的参数组合,并评估目标函数。
- 建立概率模型: 使用观测数据(参数组合及其对应的目标函数值)来建立目标函数的概率模型。通常使用高斯过程作为概率模型。
- 定义采集函数: 采集函数用于衡量不同参数组合的潜在价值。它平衡了探索(探索未知的区域)和利用(利用已知的最优区域)。
- 选择下一个评估点: 找到使采集函数最大化的参数组合,作为下一个需要评估的点。
- 评估目标函数: 评估目标函数在选定的参数组合上的值。
- 更新概率模型: 将新的观测数据添加到已有的数据集中,并更新概率模型。
- 重复步骤3-6,直到达到停止条件(例如,评估次数达到上限)。
5. 采集函数 (Acquisition Function)
采集函数是贝叶斯优化的核心,它决定了下一个需要评估的参数组合。常用的采集函数包括:
-
期望改善 (Expected Improvement, EI): EI计算了超越当前最优值的期望。
EI(x) = E[max(0, f(x) - f(x^+))]其中,f(x) 是目标函数在高斯过程下的预测值,f(x^+) 是当前最优值。EI鼓励选择那些既有可能超越当前最优值,又具有较高不确定性的区域。
-
概率改善 (Probability of Improvement, PI): PI计算了超越当前最优值的概率。
PI(x) = P(f(x) > f(x^+))PI只关注超越当前最优值的概率,忽略了改善的幅度。
-
置信上限 (Upper Confidence Bound, UCB): UCB将预测均值和预测标准差结合起来,鼓励探索未知的区域。
UCB(x) = μ(x) + κ * σ(x)其中,μ(x) 是预测均值,σ(x) 是预测标准差,κ 是一个控制探索程度的参数。
6. 贝叶斯优化的Python实现
这里我们使用scipy.optimize库的minimize函数来优化采集函数,并用scikit-learn的高斯过程回归模型。
import numpy as np
from scipy.stats import norm
from scipy.optimize import minimize
from sklearn.gaussian_process import GaussianProcessRegressor
from sklearn.gaussian_process.kernels import RBF, ConstantKernel as C
# 定义目标函数 (这里使用一个简单的示例函数)
def objective_function(x):
return (x-2)**2 + np.sin(x)
# 定义期望改善 (Expected Improvement) 采集函数
def expected_improvement(x, gp, y_max, xi=0.01):
mu, sigma = gp.predict(x.reshape(-1, 1), return_std=True)
sigma = sigma.reshape(-1, 1)
a = (mu - y_max - xi)
Z = a / sigma
EI = a * norm.cdf(Z) + sigma * norm.pdf(Z)
return EI
# 定义采集函数的优化器
def acquisition_optimizer(gp, y_max, bounds, n_restarts=5):
best_x = None
best_ei = -np.inf
for i in range(n_restarts):
x0 = np.random.uniform(bounds[0][0], bounds[0][1], size=1) # 随机初始化
res = minimize(lambda x: -expected_improvement(x, gp, y_max), # 最小化负的 EI
x0,
bounds=bounds,
method='L-BFGS-B')
if -res.fun > best_ei:
best_ei = -res.fun
best_x = res.x
return best_x
# 贝叶斯优化主循环
def bayesian_optimization(objective_function, bounds, n_iterations=10, n_initial_points=5):
# 初始化数据
X_sample = np.random.uniform(bounds[0][0], bounds[0][1], size=(n_initial_points, 1))
y_sample = np.array([objective_function(x) for x in X_sample]).reshape(-1, 1)
# 定义高斯过程模型
kernel = C(1.0, (1e-3, 1e3)) * RBF(1.0, (1e-2, 1e2))
gp = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=9)
# 优化循环
for i in range(n_iterations):
# 拟合高斯过程
gp.fit(X_sample, y_sample)
# 找到当前最优值
y_max = y_sample.max()
# 选择下一个评估点
next_x = acquisition_optimizer(gp, y_max, bounds)
# 评估目标函数
next_y = objective_function(next_x)
# 更新数据
X_sample = np.vstack((X_sample, next_x))
y_sample = np.vstack((y_sample, next_y))
print(f"Iteration {i+1}: x = {next_x[0]:.4f}, y = {next_y:.4f}")
# 返回最优解
best_index = np.argmin(y_sample)
return X_sample[best_index], y_sample[best_index]
# 定义搜索空间
bounds = [(-5, 5)] # x 的范围是 -5 到 5
# 运行贝叶斯优化
best_x, best_y = bayesian_optimization(objective_function, bounds)
print(f"Best x: {best_x[0]:.4f}, Best y: {best_y:.4f}")
# 可视化贝叶斯优化的过程
import matplotlib.pyplot as plt
# 重新运行贝叶斯优化,并保存每次迭代的结果
def bayesian_optimization_with_history(objective_function, bounds, n_iterations=10, n_initial_points=5):
X_sample = np.random.uniform(bounds[0][0], bounds[0][1], size=(n_initial_points, 1))
y_sample = np.array([objective_function(x) for x in X_sample]).reshape(-1, 1)
kernel = C(1.0, (1e-3, 1e3)) * RBF(1.0, (1e-2, 1e2))
gp = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=9)
X_history = [X_sample.copy()]
y_history = [y_sample.copy()]
for i in range(n_iterations):
gp.fit(X_sample, y_sample)
y_max = y_sample.max()
next_x = acquisition_optimizer(gp, y_max, bounds)
next_y = objective_function(next_x)
X_sample = np.vstack((X_sample, next_x))
y_sample = np.vstack((y_sample, next_y))
X_history.append(X_sample.copy())
y_history.append(y_sample.copy())
best_index = np.argmin(y_sample)
return X_sample[best_index], y_sample[best_index], X_history, y_history
best_x, best_y, X_history, y_history = bayesian_optimization_with_history(objective_function, bounds)
# 创建一个网格用于绘制目标函数和高斯过程
x_grid = np.linspace(bounds[0][0], bounds[0][1], 100).reshape(-1, 1)
y_true = np.array([objective_function(x) for x in x_grid]).reshape(-1, 1)
# 绘制每次迭代的结果
n_iterations = len(X_history) - 1
fig, axes = plt.subplots(n_iterations, 1, figsize=(8, 2 * n_iterations))
for i in range(n_iterations):
# 绘制目标函数
axes[i].plot(x_grid, y_true, label='Objective Function', color='black')
# 绘制观测数据
axes[i].plot(X_history[i], y_history[i], 'r.', markersize=10, label='Observations')
# 绘制高斯过程的预测
gp.fit(X_history[i], y_history[i])
y_pred, sigma = gp.predict(x_grid, return_std=True)
axes[i].plot(x_grid, y_pred, 'b-', label='GP Mean')
axes[i].fill_between(x_grid.flatten(),
y_pred - 1.96 * sigma,
y_pred + 1.96 * sigma,
alpha=0.2, color='blue', label='GP Uncertainty (95% CI)')
# 绘制下一个评估点
next_x = acquisition_optimizer(gp, y_history[i].max(), bounds)
axes[i].axvline(x=next_x[0], color='green', linestyle='--', label='Next Evaluation Point')
axes[i].set_title(f'Iteration {i+1}')
axes[i].legend(loc='upper left')
plt.tight_layout()
plt.show()
这段代码实现了一个简单的贝叶斯优化算法。它首先定义了目标函数和期望改善采集函数,然后使用scipy.optimize.minimize函数来优化采集函数。在主循环中,它不断地更新高斯过程模型,选择下一个评估点,并评估目标函数。最后,它返回找到的最优解。
7. 不确定性估计的应用
高斯过程不仅可以用于预测函数值,还可以估计预测结果的不确定性。不确定性估计在很多应用中都非常重要,例如:
- 主动学习 (Active Learning): 选择那些具有较高不确定性的样本进行标注,可以提高模型的学习效率。
- 风险管理: 在决策过程中,考虑不确定性可以帮助我们更好地评估风险。
- 探索-利用平衡: 在贝叶斯优化中,利用不确定性信息来平衡探索和利用,可以更快地找到全局最优解。
8. 总结:贝叶斯优化与高斯过程,解决复杂优化问题的利器
我们详细介绍了基于高斯过程的贝叶斯优化,以及如何使用Python实现它。 贝叶斯优化在高代价黑盒优化问题中表现出色,并通过高斯过程进行不确定性估计。 理解并掌握这些技术,能有效解决实际应用中的复杂优化问题。
更多IT精英技术系列讲座,到智猿学院