简介:面向代理模型优化算法研究的MATLAB工具箱,整合了Kriging及其变形、RBF、多项式代理模型,并配套试验设计、采样技术与大量测试函数,适合研究生和科研人员快速搭建实验框架、避免重复昂贵的仿真计算。压缩包内共160个文件,主体为138个.m源码,涉及代理模型拟合、混合整数优化、多模型融合等模块,另有17个.mat数据文件作为验证算例,2个.pdf文档说明使用与扩展,1个.txt补充基础信息,整体仅1.88MB,结构分区明确,下载后即可按需调用。整个流程覆盖实验设计、模型训练到优化求解,并给出Schoen等多组测试函数输入脚本,可灵活替换参数以比较不同代理模型的精度与效率。已有2734人浏览学习,对从事代理模型算法研究的读者来说,是一份可直接用于复现和扩展实验的实用代码集。
1. 代理模型工具箱:让昂贵仿真退到后台,用毫秒级预测接替优化搜索
一个典型的参数优化任务,卡点往往不在算法,而在目标函数:一次流场仿真少则几分钟,多则几小时,优化器要评估上千次,时间根本耗不起。代理模型用少量样本点训练一个近似映射,替代原目标函数供优化搜索调用;代理模型工具箱则把Kriging、径向基函数、多项式回归这类方法收敛到统一接口,免去自己维护零散脚本的麻烦。这篇文章写给两类人:手里有仿真或实验数据、想加速迭代的工程师,以及刚接触代理模型与贝叶斯优化、想弄懂落地路径的开发者。目标很直接——搞清楚它值不值得引入,并照着一套最小闭环复现出来。
2. 代理模型工具箱内部长什么样:四个接口决定它能接多大的活
代理模型工具箱不是把几个算法列在一起就算完事。它真正值钱的地方,是定义了四类接口:样本集接口、模型注册接口、验证接口、优化建议接口。接口设计决定了这套工具是只能跑通演示,还是能接住真实工程里的脏数据、多模型切换和反复迭代。下面逐个拆开看,每个接口都配有最小实现,方便你在自己的项目里复刻相同结构。
2.1 样本集接口:代理模型的唯一事实来源
工具箱里所有模型都吃同一份样本数据,但绝不能容忍每个模型各存一份、各归一化一遍。样本集接口统一承担三件事:维度校验、边界校验、数据归一化。维度错了,后面所有模型训练都会在矩阵乘法上翻车;边界越界,等于是拿着设计空间外的输入去评价一个没见过的区域;归一化不做,Kriging这类对数值尺度敏感的模型会在某个变量上失去调节能力。
import numpy as np class SampleSet: def __init__(self, lower_bound, upper_bound): self.X = [] self.y = [] self.lower = np.asarray(lower_bound, dtype=float) self.upper = np.asarray(upper_bound, dtype=float) self._normalized_X = None def add_point(self, x, y): x = np.asarray(x, dtype=float).flatten() if x.shape[0] != self.lower.shape[0]: raise ValueError("样本维度与设计空间维度不一致") if np.any(x < self.lower) or np.any(x > self.upper): raise ValueError("样本点超出设计空间边界") self.X.append(x) self.y.append(float(y)) self._normalized_X = None def normalized(self): if self._normalized_X is None: span = self.upper - self.lower X = np.vstack(self.X) self._normalized_X = (X - self.lower) / span return self._normalized_X, np.array(self.y)这个接口的逻辑是:样本进来先做格式和范围检查,通过后才进入内部列表;normalized()方法在第一次调用时统一把原始坐标映射到 [0,1] 区间,并缓存结果,避免每次训练都重复计算归一化。参数里 lower_bound 和 upper_bound 是设计空间边界,形状为 (n_dim,),add_point 的 x 必须与它同维度。我一般建议把样本数校验也放进来,比如样本量少于 2n+1 时给出警告,因为低于这个量,多数代理模型只能勉强拟合线性趋势。
接口这样设计的直接好处是,后续做交叉验证、模型切换、加点优化时,所有模块拿到的都是同一份清洗后的数据。实际项目里最常见的坑是仿真程序返回的样本顺序不一致,有的按行存 x,有的按列存 y。SampleSet 在 add_point 里用 flatten() 强制拉平,就是为了把这类顺序问题挡在入口处。
2.2 模型注册接口:Kriging、RBF 与多项式回归的统一包装
不同代理模型的数学形式差得很远,但工具箱使用者不关心推导过程,关心的是三件事:能不能拟合、能不能给预测、能不能给不确定度。所以模型接口只需要暴露 fit、predict、predict_with_std 三个方法。Kriging 本身带方差估计,天然支持 predict_with_std;RBF 和多项式回归没有严格的不确定度,工具箱通常用局部残差去近似一个校准后的标准差。
class SurrogateModel: def fit(self, X, y): ... def predict(self, X): ... def predict_with_std(self, X): std = np.ones(len(X)) * self._calibrated_noise return self.predict(X), std基类把 predict_with_std 做成默认实现,用训练时留下的残差噪声来填充,这样优化接口调用时永远拿得到 (均值, 标准差) 两个输出。参数上要留意的是 _calibrated_noise,这个值不能直接用训练残差的整体方差,否则在样本密集区域会高估不确定度。常见做法是把设计空间分桶,在每个桶内分别统计残差标准差,拟合一条随局部样本密度变化的噪声曲线。我在自己的工具箱里会额外加一个 model.clone() 方法,交叉验证每个 fold 都要一个新实例,不能复用同一个对象反复 fit,否则训练状态会串。
模型注册机制让使用方不需要关心“当前跑的是哪种模型”。加载样本、设置模型名、训练、给建议,四步走完,中途切换模型只需要改一个字符串参数。代码里出现 Kriging 的核函数选择、RBF 的基函数宽度这类配置,全部下沉到模型注册表的构造参数里,对外暴露尽量少的选项。
2.3 验证接口:交叉验证是核心功能,不是附加功能
代理模型最大的风险是过拟合,尤其样本量小的时候,训练集误差可以做到很小,但换一个新点立刻偏离真实仿真结果。验证接口存在的意义,就是让你在看优化结果之前先回答一个问题:这个模型在没见过的点上平均犯多大错?
def cross_validate(model_factory, X, y, k=5): rng = np.random.default_rng(0) indices = rng.permutation(len(X)) scores = [] for i in range(k): test_idx = indices[i::k] train_idx = np.setdiff1d(indices, test_idx) model = model_factory() model.fit(X[train_idx], y[train_idx]) pred = model.predict(X[test_idx]) scores.append(float(np.sqrt(np.mean((pred - y[test_idx]) ** 2)))) return np.mean(scores), np.std(scores)这里用交叉验证生成 K 个 fold、K 个 RMSE,最终返回均值与标准差。k=5 适合样本量在 50 到 200 之间的场景;小于 30 条样本时,我一般会直接用留一法,即 k=n,保证每个样本都有机会当一次测试点。参数 model_factory 传入的是一个零参数构造方法,这种写法比直接传 model 实例更安全,能避免多个 fold 共用同一个模型的参数状态。
验证接口在工具箱里的地位是承上启下。训练前用它判断模型能不能用,训练后再用它输出报告,报告里的两个数字:RMSE 均值决定模型整体准度,RMSE 标准差决定模型稳定性。稳定性的重要性经常被低估,如果五个 fold 的标准差比均值还大,说明模型在某个区域完全失控,这种情况下继续跑优化等于拿一个半盲模型去导航。
2.4 优化建议接口:代理模型怎么把“下一步计算哪里”交出来
代理模型的终点不是预测,而是驱动优化。优化建议接口拿候选点集合,调用模型的 predict_with_std,按既定的加点策略挑出最值得做真实仿真的那个点。这个接口把“模型”和“优化算法”解耦,换加点策略不需要改模型代码。
import math def norm_cdf(z): return 0.5 * (1.0 + math.erf(z / math.sqrt(2.0))) def norm_pdf(z): return math.exp(-0.5 * z * z) / math.sqrt(2.0 * math.pi) def propose_next(candidates, model, y_best, mode="EI"): pred, std = model.predict_with_std(candidates) if mode == "EI": gap = y_best - pred z = gap / std ei = gap * norm_cdf(z) + std * norm_pdf(z) return candidates[np.argmax(ei)] if mode == "LCB": lcb = pred - 1.96 * std return candidates[np.argmin(lcb)] return candidates[np.argmin(pred)]EI(期望提升)在寻找最小化问题时,理解方式是:gap 代表这个点可能比当前最优值好多少,标准差代表不确定性有多大,两者加权后得到一个综合得分,取得分最高的点。LCB(置信下界)则是另一套逻辑:直接取“预测值减两倍标准差”的最小点,更偏向探索。我在实际项目中用 EI 多一些,它在小样本下不会过早扎堆到已探索区域,而 LCB 在噪声较大时容易找到意外惊喜。
注意 norm_cdf 和 norm_pdf 是用误差函数手写的,避免引用重型统计库;如果你手头环境方便,也可以换成现成的统计函数。candidates 集合通常由拉丁超立方抽样或随机采样生成,规模在 1000 到 5000 个之间,每个候选点都过一遍模型预测,计算量很小,所以这一步可以反复执行加速。
3. 用代理模型工具箱搭一个最小优化闭环:从加载样本到输出推荐点
上一章把接口拆开讲,这一章把它们拼起来。闭环的完整链路是:准备样本集 → 设定模型 → 训练 → 交叉验证 → 生成候选点 → 按加点策略选推荐点 → 把推荐点交给真实仿真 → 仿真结果追加回样本集 → 进入下一轮。整个循环里,你只需要在第四步之后把推荐点送去仿真,其他全部由工具箱代跑。
3.1 从 CSV 样本到第一个推荐点的最小例程
假设你已经有一份仿真采样结果,两列分别是输入 x 和目标值 y。这个例程演示一个完整的最小流程,可以直接拷到本地跑通,再替换成自己的数据。
from model_toolbox import SurrogateManager manager = SurrogateManager() manager.load_samples( "samples.csv", input_cols=["x1", "x2"], target_col="y", lower_bound=[-5.0, -5.0], upper_bound=[5.0, 5.0], ) manager.set_model("kriging", kernel="matern52", noise=1e-6) manager.train() report = manager.cross_validate(k=5) print("validation_rmse:", report["rmse_mean"], "+/-", report["rmse_std"]) candidates = manager.sample_candidates(n=2000, method="lhs") x_next = manager.suggest(candidates, strategy="EI", y_best=report["observed_best"]) print("next_point:", x_next)load_samples 内部调用了样本集接口,自动完成维度检查和归一化;input_cols 指定特征列,target_col 指定目标列,工具箱会把缺失值和重复行过滤掉。set_model 里 kernel 参数是 Kriging 的协方差核,matern52 适合多数光滑但有不规则波动的工程响应面;如果数据非常平滑,换成平方指数核收敛更快。noise=1e-6 表示假定仿真确定性,不给观测噪声留余量;如果你的数据来自实验测量,把 noise 调到 1e-2 甚至更大。
train 之后必须做交叉验证,这一步最容易跳过,却是判断模型可信度的关键。suggest 的 y_best 参数按你的优化方向传入当前最优观测值,EI 策略依赖这个基准值来算提升量。返回的 x_next 就是本轮建议做真实仿真评估的输入点。整个流程跑完耗时可忽略不计,真正的耗时在下一行——你要把这个点送进仿真程序。
3.2 三种加点策略的取舍:探索、利用和两者之间的平衡
加点策略决定了每一轮迭代推荐点的性格,对优化效率的影响比模型类型更大。工具箱里最常见的三种策略,实际使用场景差别很明显。
| 策略 | 核心逻辑 | 适合场景 | 风险 |
|---|---|---|---|
| min_pred | 直接取预测最优值点 | 对模型非常有信心 | 容易陷入局部最优 |
| EI | 优化预测提升与不确定性的加权 | 大多数中小样本问题 | 前期探索略保守 |
| LCB | 预测值减若干倍标准差 | 噪声大、需要开拓新区域 | 探索过度,浪费仿真次数 |
EI 是我最常用也最推荐的默认项。它的公式里有一个隐含行为:当所有候选点的预测值都远差于当前最优时,标准差大的点会获得更高的 EI,模型会自动转向探索;当某个点预测明显优于当前最优时,它又会迅速收敛到利用。这种自适应是 min_pred 和 LCB 都不具备的。LCB 里的 1.96 这个系数对应 95% 置信区间,调大它可以增加搜索范围,调小则偏向局部精修,适合在迭代后期使用。
加点的另一个细节是批次。很多仿真程序支持并行评估,一次可以提交多个点。工具箱通常提供 sobol 或 k-means 聚类的批加点模式,把候选点按 EI 得分排序后选前 N 个彼此分散的点。我建议批大小不要超过 5,批次太大等于把本轮信息量摊薄,后续每轮修正模型的收益会明显下降。
3.3 训练报告里的三个关键数字:先看它们再信推荐点
训练报告不是摆设。拿到一份模型报告,我只看三个数字:交叉验证 RMSE、RMSE 标准差、预测标准差中位数。第一个数字回答“平均错多少”,第二个回答“误差波动多大”,第三个回答“模型对整体空间的自信程度”。
fold 1 rmse = 0.182 fold 2 rmse = 0.195 fold 3 rmse = 0.170 fold 4 rmse = 0.226 fold 5 rmse = 0.201 mean = 0.195, std = 0.021 median predictive std = 0.154如果 RMSE 均值明显大于预测标准差中位数,说明模型的方差估计偏乐观,真实误差比它自己认为的大。这时要检查两点:一是样本是否有异常值,二是 Kriging 的 noise 参数是否设得太小。反过来,如果 RMSE 均值很小但预测标准差很大,模型属于过度保守,优化时 EI 会倾向探索,但收敛速度慢,可以把 noise 适当调低。一份健康报告的标志是这两个数字在同一量级,比如上面示例里 0.195 对 0.154,说明方差估计基本靠谱。
4. 代理模型选型实践:样本量、非线性与噪声的三角权衡
很多人在这个问题上犹豫不决:到底用 Kriging 还是 RBF?工具箱把模型切换成本降到很低之后,纠结就变成了实际问题。代理模型没有绝对优劣,只有相对适用。决定选型的三个变量是样本量、响应面的非线性程度、以及数据里的真实噪声水平。这三者互相拉扯,选型本质上是在找当前条件下的平衡点。
4.1 一张选型决策表:大多数项目都能在这里找到方向
| 数据特征 | 首选模型 | 备选 | 理由 |
|---|---|---|---|
| 样本量 < 30,响应光滑 | Kriging(matern52) | RBF | 小样本下高斯过程的不确定度最可靠 |
| 样本量 30~200,非线性中等 | RBF | Kriging | RBF 训练快,拟合灵活,不加速比慢 |
| 样本量 > 200,响应复杂 | 集成多项式回归 + RBF | 神经网络代理 | 单模型容易欠拟合,集成更稳 |
| 实测数据,噪声明显 | Kriging(noise>1e-2) | 带正则化的 RBF | Kriging 的噪声参数可显式建模观测误差 |
| 高维设计空间(>20维) | 降维后再选 RBF | 多项式回归 | Kriging 在高维下协方差矩阵濒临奇异 |
这张表不是教条,但能省掉大量试错。我见过不止一个项目,样本量不到 20 个就上神经网络代理,结果交叉验证 RMSE 惨不忍睹,回头换 RBF 立刻可用。小样本下模型复杂度必须压住,Kriging 的优势不在于拟合精度,而在于它能给出有校准意义的不确定度,这是加点优化最需要的输入。
50 到 200 个样本的区间是最舒服的,Kriging 和 RBF 都能跑,但我偏 RBF 多一点,理由是训练稳定。Kriging 要估计协方差超参数,优化过程偶尔不收敛,而 RBF 只要解一个线性方程组,基本没有调参失败一说。RBF 的问题是宽度参数需要根据样本密度设置,工具箱里的自动搜索默认按最近邻距离的中位数来定,一般效果还行。
4.2 用工具箱自带测试函数起步,别拿真实工程数据开荒
刚上手代理模型工具箱时,我强烈建议不要直接拿仿真数据开跑。真实数据的真实分布你不知道,出错时也分不清是模型问题还是数据问题。工具箱一般会带一组测试函数,有二维的,也有六维的,特征明确,最优值已知,非常适合用来验证整个闭环是否正常。
测试流程分三步:第一步在测试函数上随机抽取 20 个点作为初始样本,训练模型并做交叉验证;第二步跑十轮加点优化,每轮把推荐点送进测试函数算出真实目标值并追加回样本集;第三步对比十轮内找到的最优值和全局最优值,差距越小说明整套循环越有效。这个过程跑通一次,你才知道 EI 策略在你的工具箱里行为是否符合预期,也能观察到模型报告数字的变化规律。
我在测试函数上习惯加一组对照实验:分别用 min_pred 和 EI 各跑一轮,观察收敛曲线。EI 前期波动但后期追上去,min_pred 可能早早停在局部值。这个对照不是为了证明某个策略厉害,而是帮你建立参数手感——后面换到自己的数据时,你会更清楚加点策略的调整方向。
4.3 多保真度代理模型:别再对高低精度数据一视同仁
工程场景里经常存在两种仿真精度:高保真模型昂贵但准确,低保真模型便宜但有偏差。代理模型工具箱的多保真度模式,核心思路是用大量低保真样本构建趋势面,再用少量高保真样本校正偏差。这种组合常比只用高保真数据训练出的代理模型更准,成本还低很多。
多保真度接口通常让用户传入两组样本并标注 fidelity 等级。底层实现常见的是协同Kriging,低保真模型作为主趋势,高保真样本只用来训练一个偏差模型。使用时要记住一个关键前提:两种保真度之间必须存在可学习的相关性,如果低保真结果与高保真结果毫无规律地乱跳,偏差校正等于白做。判断方法是把两组数据的响应差画出来,看是否存在平滑趋势。
我一般只在高保真仿真特别贵时启用多保真度。比如单次高保真评估要三小时,低保真只要十分钟,那么用四十个低保真点加十个高保真点往往能逼近三十个高保真点的精度,代价却小得多。反之,如果高保真评估本来就快,多保真度带来的接口复杂度和调参成本就不划算了。
5. 代理模型工具箱避坑与常见问题排查:五类翻车现场及修复
代理模型工具跑起来容易,跑得好难。这几类问题几乎每个用代理模型的团队都会遇到,按“现象 → 原因 → 解决”记下来,遇到时直接对照排查。
5.1 交叉验证分数难看,源头是样本集里有重复点
现象:训练模型一切正常,但交叉验证 RMSE 异常高,而且每次随机分组结果波动巨大。排查后发现样本集里存在完全相同的 x 对应不同 y 的重复记录。代理模型是确定性映射,同一个输入不该有两个输出,重复点的存在让模型被迫在一个局部分裂。
原因:仿真程序有时会对同一个输入重复计算,尤其是批量提交任务时参数被默认值覆盖;数据合并时也容易把新旧两批样本里的相同点叠进去。模型拟合时把重复点的均值当趋势,验证时又撞上偏差更大的原始值。
解决:在样本集入口做精确去重,按 x 坐标完全一致或误差小于 1e-8 判定重复,取多次评估的均值作为该点目标值。如果重复点是由于分批次采样产生的,保留均值还能顺便降低噪声。我一般在 load_samples 之后输出一行提示,说明去除了多少重复点,这个数字本身就能预警数据清洗问题。
5.2 优化循环在局部最优处反复打转,EI 得分几乎不动
现象:跑了几十轮加点,推荐点始终徘徊在同一个区域,EI 最大值得分从 0.1 降到 0.001 后不再变化,真实目标值也没有改善。
原因:这是典型的“探索耗尽”。EI 的探索动力来自预测标准差,经过多轮加点后,已探索区域的标准差被压得很低,而未探索区域虽然标准差大,但预测均值极差,两者加权后得分仍然拼不过已探索区域的局部提升。模型就此锁死在局部。
解决:把加点策略临时切换到 LCB 并把系数调到 2.5 或 3.0,强制模型去预测均值较差但不确定性大的区域跑几轮。更系统的做法是给 EI 增加一个随机扰动项,比如对得分乘以一个带噪声的系数,让每一轮有小概率跳出局部。工具箱里如果有“随机探索比例”参数,设到 0.1 到 0.2,通常能显著改善长期收敛结果。
5.3 交叉验证分数很高,但推荐点送进仿真后效果差
现象:模型报告很漂亮,RMSE 均值 0.02,但推荐点真实评估的误差超过 30%,优化完全跑偏。
原因:交叉验证分数高不代表模型对全局空间都可信。交叉验证是随机分组的,它衡量的是“整体平均误差”,而优化推荐的点恰好在某个样本稀疏的角落,那个区域没有任何训练样本,模型的预测纯粹靠协方差函数外推。平摊到全局的小误差掩盖了局部区域的严重失控。
解决:训练报告里加一个“最小最近邻距离”指标,或者把样本空间按网格切块,统计每块内的样本数。推荐点生成后,先计算它与已有样本点的最小距离,如果小于训练样本间距的十分之一,说明该点落在已探索区域,优化价值低;如果大于平均间距的三倍,说明处于严重外推区,要谨慎接受。距离警告是后悔药,能提前拦住大半次优化失败。
5.4 样本量超过几百后 Kriging 训练时间暴涨
现象:样本量从 200 涨到 500,训练耗时从不到一秒变成几十秒,而且 predict_with_std 每次调用也明显变慢,整个加点循环卡到无法接受。
原因:Kriging 的协方差矩阵需要做一次 Cholesky 分解,复杂度是 O(n^3),n 是样本量。每轮加点后都要重新训练,等于每轮都做一次立方级运算。200 样本时计算量是 800 万量级,500 样本时到了 1.25 亿,差距不是线性而是指数级。
解决:这是典型的换模型信号。样本量超过 300 后,把模型切到 RBF 或集成多项式回归,训练复杂度降到 O(nk) 或更低。另一个折中方案是用 Kriging 的稀疏近似,只保留每块区域的核心样本,但实现复杂度高。我的习惯是以 300 为界,超过就换模型,工具箱里的模型热切换在这时派上用场。
5.5 把噪声数据当作真实信号,模型给出了虚高置信度
现象:样本来自实验测量,目标值有明显的随机波动。模型训练后预测标准差极小,交叉验证 RMSE 也小,但推荐点在真实评估后依然偏差很大。
原因:Kriging 的 noise 参数被设成了 1e-6,相当于模型认为所有观测值都是精确值。它在拟合时会把噪声当作真实响应的一部分,导致预测方差被严重低估。模型表现得“过分自信”,加点时便不再探索,优化提前收敛。
解决:把 noise 参数设为可估计。工具箱里常见做法是把 noise 也纳入超参数优化,或者用样本重复试验的标准差作为先验噪声值。如果没有重复试验数据,保守设到目标值量级的 1% 再交叉验证几轮,看预测标准差是否恢复到合理水平。记住一个原则:实测数据永远有噪声,noise 参数等于零是一种危险设定。
6. 快速上手代理模型工具箱:我的调试顺序和三个验证技巧
上手这个工具箱,我建议保持固定调试顺序,避免变量太多。第一步永远是用自带测试函数跑通最小闭环,确认加载、训练、建议、追加四个环节没有断裂;第二步再换上自己的数据,但先别开优化循环,只做一次训练加交叉验证,观察 RMSE 是否在可接受范围;第三步才进入加点循环,并且每五轮输出一次完整报告,对比模型误差和真实目标值的变化趋势。这个顺序能保证出错时问题定位范围最小。
三个验证技巧里,最有用的是留一法逐点误差图。画出每个样本点的预测误差与其空间位置的关系,如果误差明显集中在某个角落,说明那个区域需要补样本。第二个技巧是主动做一次“假推荐”:拿一批已知真实值的点当作候选点,让模型建议下一步算哪里,再看被推荐点的真实误差。如果推荐点正好落在误差最大的区域,说明加点策略和模型都正常;如果落在误差很小的区域,说明模型已经不够自信,该降温了。第三个技巧是记录每轮推荐点的 x 与已有样本的最近距离,距离持续变小说明优化进入局部精修,此时可以把 LCB 系数下调,加速收敛。
我自己最开始用代理模型时,也犯过拿着真实工程数据一把梭的错,连跑两天优化,结果是模型把噪声全学进去了,推荐点一个比一个偏。后来严格按测试函数开荒、交叉验证看双指标、推荐点算距离这三步走,翻车率大幅下降。这个工具的价值不在于算法多新鲜,而在于把试错成本摊得很薄——换模型、换策略都是改一个字符串的事。希望帮到你。
本文还有配套的精品资源,点击获取