
简介这份CEC2013测试集面向进化计算、群体智能与连续优化方向的研究生及算法工程师用于在统一基准上复现、比较和验证优化算法的收敛精度、稳定性与鲁棒性。压缩包共32个文件以28个txt输入数据与说明文本为主体辅以2个m脚本、1个mexw64编译组件和1个cpp源文件整体约3.18MB同时覆盖MATLAB快速实验与C底层实现两条路径。每个问题实例附带维度、搜索范围、目标函数及评价标准等输入信息包内还提供主程序入口与粒子群算法示例读者可据此搭建单目标优化实验观察不同方法在多模态、非凸及约束场景下的表现并参照说明文档理解数据组织、参数配置与结果评价方式。已有390人学习适合需要标准测试平台、核对算法实现或补充对比实验的读者也可作为课程设计与论文实验的基础素材。1. cec2013测试集和那份 input 目录你复现的其实是同一份常数很多人第一次接触 cec2013测试集是从别人论文里的收敛曲线开始的自己写的优化器在自造的 sphere、rastrigin 上收敛得干干净净换到 cec2013 之后曲线整体抬高或者干脆原地不动。真正出问题的地方常常不在算法而在 input 目录里那几个文本文件——平移向量读错了一行旋转矩阵按列主序塞进去了或者只对齐了一半。这个测试集一共 28 个函数前 5 个单峰、中间 15 个多峰、最后 8 个组合全部做过平移大部分还叠加了旋转input 里装的就是这些让它“变难”的常数。下面按“这 28 个函数各自检验什么能力 → input 里的文件怎么读对 → 评估器怎么写才能和公开结果对上”的顺序讲适合正在做连续优化算法对比、又不想在数据搬运上出错的人。2. 28 个函数的分组逻辑单峰、多峰、组合各自检验什么能力先把“考卷”拆开看。cec2013测试集的编号不是随便排的f1 到 f5 是单峰组f6 到 f20 是多峰组f21 到 f28 是组合组。三组的设计意图完全不同单峰组看的是收敛速度和精度上限多峰组看的是跳出局部最优的能力组合组看的是算法在“地形性质突变”时会不会直接失效。你把一个只在单峰组表现好的算法拿去跑组合组曲线通常会出现明显的台阶——在某个子函数覆盖的区域内还能往下走一跨进另一个子函数的势力范围就卡住。这不是调参能救的是算法结构的问题。2.1 三组函数的具体分工与典型卡点下表是我自己做基线对比时贴在显示器旁边的版本右侧一列写的是“这个函数最常让哪类算法暴露问题”比单纯记名字有用得多。编号函数简名组别典型卡点f1Sphere平移单峰基准线收不动说明步长或种群初始化有问题f2高条件数椭圆单峰病态各维曲率差 1e6 量级各维等步长直接失效f3Bent Cigar单峰单一方向极陡其余方向极平方向信息丢失f4Discus单峰一维极扁容易在扁平方向上震荡f5Different Powers单峰幂次随维度递增靠前的维度主导适应度f6Rosenbrock旋转多峰窄弯曲谷旋转后谷道不再平行于坐标轴f7Schaffers F7旋转多峰大面积局部极值旋转放大方向误导f8Ackley旋转多峰外围近乎平坦中心一个深陷阱f9Weierstrass旋转多峰分形结构局部极值极密f10Griewank旋转多峰大范围缓坡叠加高频扰动f11Rastrigin多峰规则多峰未旋转是最“善良”的多峰函数f12Rastrigin旋转多峰同上但打乱方向逐维独立更新的策略立刻退化f13非连续 Rastrigin旋转多峰额外做非连续化相邻点适应度跳变f14Schwefel多峰全局最优远离次优且靠近搜索域边界f15Schwefel旋转多峰同上旋转后边界效应更明显f16Katsuura旋转多峰高频振荡精度难以下探f17Lunacek Bi-Rastrigin多峰双峰谷结构两个盆地宽度不同f18Lunacek Bi-Rastrigin旋转多峰同上旋转后盆地方向不可分f19GriewankRosenbrock 展开多峰两种地形拼接尺度不匹配f20Scaffer F6 展开多峰相邻维度强耦合可分性彻底丧失f21–f28组合函数 1–8组合子函数地形、尺度、最优值三者同时切换f21 到 f28 这八个是真正区分算法水平的地方。每个组合函数由若干个子函数按距离加权拼起来权重随当前解离各子函数中心的远近变化所以同一个算法在搜索前期和后期面对的等效地形是不一样的。很多算法在 f11 上能压到 1e-8 以下到 f25 就停在 1e2 附近不动差距全在这里。2.2 为什么非要加平移和旋转让算法没法靠“地形记忆”占便宜如果不做平移最优解固定在原点任何带“向原点收缩”倾向的更新公式都会白拿一份便宜——交叉算子、边界处理、甚至初始化分布都可能无意中把种群往原点推跑出来的曲线好看但和真实搜索能力没关系。cec2013测试集的做法是把每个函数的最优点挪到一个随机位置上这个偏移量就在 input 目录的 shift 文件里每个函数一行组合函数是每个子函数一行。你只要偏移量没读对哪怕算法完全正确曲线也会整体平移一个常数做对比时看起来“差一点点”实际是两个不同的函数。旋转的影响更隐蔽。以 Rastrigin 为例未旋转时各个维度是可分离的一个只会做逐维更新的局部搜索就能一个维度一个维度地慢慢啃下去乘以一个正交矩阵之后各维之间被线性耦合逐维更新每次都会破坏上一步的进展。这就是 f11 和 f12 名字几乎一样、难度差一大截的原因。旋转矩阵同样是 input 目录里给好的常数不用你自己生成——这一点很关键很多人图省事自己 np.random.randn 生成一个再正交化跑出来的数永远对不上公开结果。2.3 三条硬约定搜索范围、最优值、误差口径第一搜索范围。整套函数的定义域统一是每个维度 [-100, 100]初始化也常用这个区间。写成代码就是上下界各是一个长度为 D、元素全为 -100 和 100 的向量别写成 [-5.12, 5.12]——那是另一个测试集的习惯混用之后 f1 的收敛精度会突然变得“很好”其实是搜索空间小了四个数量级。第二最优值。官方给每个函数一个已知的全局最优值f1 是 -1400往后每项加 100到 f28 是 1300。这份常数在常见实现里以 f_bias 数组的形式出现长度正好 28import numpy as np # 官方约定的全局最优值序列f1 - -1400逐项 100f28 - 1300 FBias np.arange(-1400, 1400, 100, dtypenp.float64) assert FBias.size 28, 长度必须是 28对不上说明区间端点写错了 print(FBias[0], FBias[27]) # -1400.0 1300.0这段代码的意义不是“算出来”而是把它当成一条断言任何时候你怀疑自己读错了版本先打印首尾两项。首项不是 -1400说明你手上这份实现的偏差约定和常见版本不一样后面所有误差值都会差一个常数。第三误差口径。做对比时报告的不是 f(x) 的绝对值而是 e f(x_best) - f*。这个减法是区分“算法好”和“常数搬对了”的唯一手段。同时把 1e-8 当作“收敛到”的阈值低于它按 0 记否则在均值里会被一两个极小的数拉得毫无意义。注意f* 只在报告阶段减一次。评估器内部该加的 bias 加了外层再减一次误差会凭空偏移两倍而且偏移量随函数编号递增特别像“算法在后面的函数上性能下降”。3. 把 input 里的 shift 数据和旋转矩阵读对解析、形状与正交性校验拿到发布包之后先别急着跑算法花二十分钟把 input 目录弄清楚后面能省掉几天。这个目录里通常有三类文件一份平移向量shift_data.txt 之类、按维度分的旋转矩阵M_D10.txt / M_D30.txt / M_D50.txt 这种命名、以及组合函数专用的一组分量矩阵M1_D30.txt 到 M8_D30.txt。不同来源的包命名会有一点出入所以第一步不是抄代码是列出文件清单、把“文件名 → 用途 → 维度”这三列写到一张纸上再动手。3.1 文件清单与各自的用途对照文件内容用在哪读错的后果shift 文件每个函数组合函数是每个子函数一个 D 维偏移向量全部 28 个函数曲线整体平移f1 的最优值对不上M_D{D}.txt一个 D×D 正交矩阵带旋转的单峰、多峰函数各维耦合方式错误多峰函数难度失真M{i}_D{D}.txt组合函数第 i 个分量的正交矩阵f21–f28组合函数权重区域错位出现假台阶这张表里最容易忽略的是第三行。组合函数的每个子函数有自己独立的旋转矩阵一共 8 组它们的下标和函数编号不是一一对应的——f21 可能用到 M1 到 M5f22 用到 M1 到 M3具体对应关系写在实现的参数区里。凭直觉按“f21 用 M1”去猜跑出来的 f21 曲线会很平滑很好看因为它其实退化成了一个普通多峰函数。3.2 用 numpy 读平移向量并按函数号建索引import numpy as np from pathlib import Path def load_shift(path: Path, dim: int) - np.ndarray: 把 shift 文件读成 (行数, dim) 的二维数组。 raw np.loadtxt(path, dtypenp.float64) if raw.ndim 1: # 有些包把整份数据写成一长条按维度切开即可 raw raw.reshape(-1, dim) if raw.shape[1] ! dim: raise ValueError(f列数 {raw.shape[1]} 与 dim{dim} 不一致多半拿错了维度的文件) print(fshift 共 {raw.shape[0]} 行每行 {raw.shape[1]} 维) return raw SHIFT load_shift(Path(./input_data/shift_data.txt), dim30) o1 SHIFT[0] # f1 的平移向量函数本身很短真正要盯的是最后那行打印。行数和你实现里用到的最大行号必须能对上如果实现里 f28 的最后一个子函数读到第 52 行而文件只有 28 行那就说明你读到的不是同一份文件或者这份包把组合函数的子偏移量单独放在了别的地方。“行号 → 函数号”的映射在官方实现里是硬编码的不是 1 到 28 顺排就完事组合函数一个函数会占连续多行。我的做法是直接把实现里那段 case 分支抄成一张 Python 字典抄完立刻用行数校验一次绝不按顺序猜。这一步没有任何技巧就是老老实实对一遍对完之后所有函数共用同一份数据后面再也不用碰它。3.3 读旋转矩阵并做正交性自检def load_matrix(path: Path, dim: int) - np.ndarray: M np.loadtxt(path, dtypenp.float64) if M.ndim 1: M M.reshape(dim, dim) # 单行写完的情况 if M.shape ! (dim, dim): raise ValueError(f矩阵形状 {M.shape} 不是 ({dim},{dim})) return M def orthogonality_error(M: np.ndarray) - float: D M.shape[0] return float(np.abs(M.T M - np.eye(D)).max()) M load_matrix(Path(./input_data/M_D30.txt), dim30) print(正交性偏差:, orthogonality_error(M)) # 期望 1e-8正交性偏差这个数是最好用的“体检指标”。旋转矩阵的定义要求 MᵀM I正常情况下偏差在 1e-12 到 1e-15 量级如果你打印出来是 0.3、1.7 这种数只有两种可能文件读的时候形状判断错了或者行列顺序反了把本该按行读的内容按列拼进去。第二种最坑因为一个矩阵和它的转置都满足“元素个数正确”只是正交性偏差变大如果这个矩阵恰好是对称的你甚至查不出来——所以别只信正交性顺手打印前两行前两列和文本文件的开头肉眼对一下。另一个常见情况是正交性合格但每个函数的曲线依然偏。这时候问题不在矩阵本身而在“用了哪个矩阵”。带旋转的函数共用一个 M_D{D}.txt组合函数用各自的分量矩阵这两类千万别串。我一般会把每个函数用到的矩阵文件名记进一张表跑冒烟测试时顺手打印一次确认没有函数拿错。3.4 在 f1 上对齐最优值一行代码确认搬运没走样所有校验里最直接的一条f1 是带平移的 Sphere把平移向量本身喂进去适应度必须等于它的理论最优值。def check_f1(evaluator, shift_row, f_star): x shift_row.copy() # 最优点就在平移向量的位置 val evaluator(x, fid1) print(ff1(x*) {val:.6f}期望 {f_star:.6f}偏差 {val - f_star:.3e}) return abs(val - f_star) 1e-8 check_f1(my_cec13, SHIFT[0], FBias[0]) # 期望偏差接近 0如果这一行打印出来的偏差是 100 的整数倍说明 bias 加的次数不对如果是几万、几十万量级说明平移向量或者旋转矩阵没有真正生效如果是 NaN检查一下有没有把整型数组直接送进幂运算。这个检查做完再做下一步比跑完一整套实验再回头查要省事得多血泪经验。4. 写出能跑的评估器f1–f20 的骨架和 f21–f28 的权重归一化评估器不需要写得多漂亮但必须满足两个条件一是同一个函数只加载一次数据二是 28 个函数的调用签名完全一致这样跑批脚本不用写分支。我一般的做法是包一个类构造时把 shift 和各类矩阵全部读进内存__call__(x, fid)只做计算。4.1 统一的调用签名与数据缓存class CEC13: def __init__(self, dim, input_dir, fbiasNone): self.D dim self.dir Path(input_dir) self.shift load_shift(self.dir / shift_data.txt, dim) self.M load_matrix(self.dir / fM_D{dim}.txt, dim) self.fbias FBias if fbias is None else np.asarray(fbias, dtypenp.float64) self._cfg_cache {} def __call__(self, x, fid): x np.asarray(x, dtypenp.float64).ravel() if x.size ! self.D: raise ValueError(f输入维度 {x.size} 与评估器维度 {self.D} 不符) # 先裁剪到搜索域和常见实现保持一致 x np.clip(x, -100.0, 100.0) if fid 20: raw self._basic(x, fid) else: raw self._composition(x, fid) return raw self.fbias[fid - 1]参数说明dim决定读哪一份旋转矩阵10、30、50 之间不能混fbias允许外部覆盖方便你在验证别的实现版本时临时替换np.clip这一步很多人省掉但 cec2013 的基线结果基本都是在裁剪后的点上算的你如果不裁剪越界点的适应度会大得离谱最终均值被拉偏。裁剪放在__call__里做算法内部就不用重复处理边界了。4.2 f1–f20先移再转最后套基本函数基本函数这部分没有捷径就是老老实实按公式抄。下面给出前五个的写法其余多峰函数的公式在文献里都能查到结构完全一样。def _sphere(z): return float(z z) def _elliptic(z): D z.size a 1e6 idx np.arange(D) / (D - 1) return float(np.sum((a ** idx) * z ** 2)) def _bent_cigar(z): return float(z[0] ** 2 1e6 * (z[1:] z[1:])) def _discus(z): return float(1e6 * z[0] ** 2 z[1:] z[1:]) def _different_powers(z): D z.size idx np.arange(D) / (D - 1) return float(np.sum(np.abs(z) ** (2 10 * idx))) BASE {1: _sphere, 2: _elliptic, 3: _bent_cigar, 4: _discus, 5: _different_powers} ROTATED {2, 3, 4, 6, 7, 8, 9, 10, 12, 13, 15, 16, 17, 18, 19, 20} def _basic(self, x, fid): z x - self.shift[fid - 1] if fid in ROTATED: z self.M z # 先平移后旋转顺序不能反 return BASE[fid](z)三个细节值得单独说。第一平移和旋转的顺序是“先减偏移、再乘矩阵”写成 M (x - o)反过来写成 M x - o等价于把最优点挪到了别的地方曲线会整体偏一个常数而且这个常数与 x 相关看起来像收敛速度问题。第二ROTATED这张集合要和你手上实现里的宏定义逐个对一遍光靠函数名字猜容易漏掉几个——f11 和 f14 是不带旋转的组合函数另算。第三Elliptic 和 Different Powers 里那个idx一定要用浮点除整除会让幂次全变成 0 或 1函数退化成加权平方和。矩阵乘法的方向也值得确认一次。z 是长度 D 的列向量写成M z如果你把它当成行向量写z M实际算的是 Mᵀz。对于一个正交矩阵这不改变函数的取值范围但会改变最优解的位置最终结果和公开数据对不上而且因为数值量级看起来“正常”很难被发现。4.3 组合函数的三件套sigma、lambda 与权重的归一化f21 到 f28 的通用形式是若干个基本函数每个先减去自己的中心、除以自己的尺度 lambda、乘上自己的旋转矩阵然后按距离给权重加权求和最后加各自的偏置。def _composition(self, x, fid): cfg self._cfg_cache.get(fid) if cfg is None: cfg load_comp_config(self.dir, fid, self.D) # n, sigma, lambda, bias, 子函数表 self._cfg_cache[fid] cfg n cfg[n] d2 np.empty(n) w np.empty(n) for i in range(n): diff x - cfg[shift][i] d2[i] float(diff diff) w[i] np.exp(-d2[i] / (2.0 * self.D * cfg[sigma][i] ** 2)) / np.sqrt(d2[i] 1e-300) w w / w.sum() # 归一化别省 total 0.0 for i in range(n): zi (x - cfg[shift][i]) / cfg[lambda][i] zi cfg[M][i] zi total w[i] * (cfg[f][i](zi) cfg[bias][i]) return total逻辑说明第一层循环算距离平方和同时算权重权重公式里的1/sqrt(d²)是距离反比项指数项负责让远处的子函数快速失去影响力两者相乘再归一化保证权重和为 1。1e-300是为了防止 x 恰好落在某个中心上导致除零这个分支在实际搜索中出现的概率不低尤其是平移向量被当成初始解的时候。参数说明sigma控制每个子函数的影响半径一般在几十的量级lambda控制尺度取值跨度很大有的子函数是 1有的是 1e-6 量级抄错一个数量级那个子函数就会在求和里彻底消失bias是每个子函数的偏置用来把不同子函数的最小值拉到同一水平线上。这三组参数在 D10、30、50 下各有一套必须按维度分支取跨维度混用不会有任何报错只会让结果看起来“差一点”。4.4 冒烟测试28 个函数各打一次分写完之后先别优化算法拿一个固定点把 28 个函数全跑一遍看两件事有没有 NaN量级是否合理。ev CEC13(dim30, input_dir./input_data) rng np.random.default_rng(2024) x rng.uniform(-100, 100, size30) for fid in range(1, 29): v ev(x, fid) print(ff{fid:02d} value{v: .6e} error{v - ev.fbias[fid-1]: .6e})单峰组的误差量级通常是 1e2 到 1e6多峰组在 1e1 到 1e2组合组则在 1e2 上下浮动。如果某个函数的误差打印成 0 或者负数几乎可以肯定是 bias 加错了如果组合函数的误差比单峰组还小检查一下权重是不是忘了归一化——没归一化时权重和可能远小于 1函数值被整体压扁。5. cec2013 复现最容易出错的地方从曲线对不上到越界异常这一节记录的是我在不同机器、不同语言实现之间来回搬这套测试集时真正踩过的坑。每一条都按“现象 → 原因 → 解决”写看到类似现象可以直接对号入座。5.1 现象收敛曲线整体平移一个常数形状却完全一样原因几乎总是 f* 用错。常见的三种写法把 f* 当成 0也就是直接报告 f(x)评估器内部已经加过 bias外面又减了一次或者把 f1 的 -1400 套到了所有函数上。三种情况的共同点是曲线形状不变、只在纵轴上平移所以看起来像“算法稍差一点”很容易被忽略。解决方式是把 f* 变成一条断言而不是一个记忆。构造评估器时校验fbias.size 28冒烟测试时打印首尾两项跑之前用 3.4 那个 f1 检查确认命中理论最优值。我现在的习惯是每个实验脚本开头都跑一次这个检查两秒钟的事能挡掉后面几个小时的困惑。5.2 现象f1 结果正常f2 之后全部异常但正交性检查是通过的这是旋转矩阵的行列顺序反了。文本文件按行写、按列读或者 numpy 里整形时order参数没对上都会得到 Mᵀ。由于 Mᵀ 同样是正交矩阵orthogonality_error打印出来照样是 1e-15看不出任何问题。但函数实际算的是 Mᵀ(x-o)各维的耦合方向全变了。解决方式有两个。一是打印矩阵的前两行和文本文件开头肉眼对照这一步最笨也最可靠。二是用函数值反查取一个随机点 x分别用 M 和 Mᵀ 算一次 f2如果两个值差得很远说明这个矩阵不是对称的那么行列顺序就一定会影响结果此时以文件原始排版为准。对于对称矩阵两种读法等价也就不用纠结了。5.3 现象f21–f28 的收敛曲线呈台阶状某个区间怎么也下不去多半是组合函数的权重没归一化或者 sigma 用了别的维度那一套。没归一化时权重和随位置变化等效于给整个函数乘了一个变系数地形被扭曲成一块一块的sigma 用错维度时各子函数的影响半径整体放大或缩小某些子函数在自己的中心附近反而拿不到主导权重。解决方式是先在每个子函数的中心点上单独测一次。把 x 设成第 i 个子函数的中心此时权重应该几乎全部落在第 i 项上函数值应当接近bias[i] f_i(0)。如果测出来偏差很大先查 sigma再查 lambda最后查子函数的 shift 是不是也用错了行。这个逐项验证的过程比较枯燥但能把问题锁死在一个子函数上。5.4 现象换个维度重跑结果和文献里的数差一个数量级cec2013测试集在 D10、30、50 下各有一整套常数包括平移向量、旋转矩阵和组合函数参数。常见错误是 D 改了但只换了旋转矩阵shift 文件还是原来那份——有些包里的 shift 文件是按维度分开放的有些是同一份文件里按列切还有的干脆每个维度一个文件。文件名看着像内容不是。解决方式是把维度写进所有文件的加载路径并且在构造时做一次形状强校验列数必须等于 D行数必须能覆盖到最大行号组合函数的参数块必须能取到D对应的分支。这三个校验任何一条不过就直接抛异常别让它静默跑完。静默跑完是最贵的错误你要花两天才能发现两天的结果都是废的。5.5 现象越界之后最优值一路漂移最终结果比不裁剪还好算法不做边界处理或者越界点直接返回一个大常数。前者会让解跑到搜索域外而域外的函数值有可能反而更小尤其是平移后的函数最优点不一定在域中心后者会让种群在边界外“撞墙”实际有效的搜索次数被浪费。cec2013 的基线结果基本都是在裁剪到 [-100, 100] 之后算的你的实现不裁剪两边就不是同一个问题。解决方式是在评估器入口统一裁剪而不是在每个算法里各写一遍。这样所有算法共享同一套边界语义对比才公平。同时把裁剪前后的目标值差异记进日志如果某个算法有大量个体被裁剪说明它的步长控制有问题这个信号比最终均值有用得多。提示这五条里5.1 和 5.2 占了我遇到问题的七成以上。跑任何新实现之前先做 f1 的最优值检查和旋转矩阵的肉眼对照剩下的问题排查成本会低一个量级。6. 拿它做对比实验预算、重复次数和一份能交差的记录方式评估器对了之后真正的活是设计实验。cec2013测试集的常见预算口径是 1e4 × D 这个量级也就是 30 维跑 3e本文还有配套的精品资源点击获取