本文档专门解释 kemm/ 与 benchmark 主线。目标是把 KEMM 主线补到和
docs/ship_simulation_reference.md 同样细的程度,让读者不必先翻完全部源码,也能
理解下面几件事:
- KEMM 主线到底解决什么问题
kemm/algorithms/、kemm/core/、kemm/benchmark/、kemm/reporting/各自负责什么- 一次 benchmark 实验从入口到图表是怎么跑完的
- KEMM 的每个子模块在代码里如何协同
- 哪些部分是标准公式,哪些部分是工程启发式或 benchmark-aware 增强
如果你只是想快速知道“改哪里”,先看 ai_developer_handoff.md。 如果你要核对公式与实现一致性,再配合 formula_audit.md 一起看。
KEMM 主线负责解决动态多目标优化问题。相比静态多目标问题,动态多目标问题的关键困难 在于:
- 目标函数会随时间变化
- 当前环境的最优解分布不一定能直接复用到下一环境
- 只靠随机重启通常代价太高
KEMM 的基本思想是:环境变化后,不是把种群全部丢掉重来,而是同时利用四类信息源:
- 历史记忆
memory - 漂移预测
drift prediction - 几何迁移
transfer - 随机重启
reinitialization
然后通过自适应策略分配模块,根据环境变化强度与历史表现,动态决定这几类信息源各应 该贡献多少候选样本。
从工程角度看,KEMM 主线承担三个任务:
- 给 benchmark 主线提供主要研究对象
- 给 ship 主线提供可复用的优化核心;当前 ship 默认图包已扩展到 dense harbor route planning、Pareto projection、risk breakdown 与 safety envelope 这类更物理解释型图
- 给后续消融实验和模块替换提供稳定骨架
kemm/
├── adapters/
│ ├── __init__.py
│ └── benchmark.py
├── algorithms/
│ ├── base.py
│ ├── baselines.py
│ └── kemm.py
├── benchmark/
│ ├── problems.py
│ └── metrics.py
├── core/
│ ├── adaptive.py
│ ├── drift.py
│ ├── memory.py
│ ├── transfer.py
│ └── types.py
└── reporting/
├── __init__.py
└── benchmark_report.py
此外,benchmark 主线的真实应用入口在:
apps/benchmark_runner.py
兼容入口在:
run_experiments.pybenchmark_algorithms.pyvisualization.py
这一层负责可直接运行的算法对象。
base.py定义所有动态多目标算法共享的基础能力,例如初始化、评价、非支配排序、拥挤距离、 环境选择和单代演化骨架。baselines.py保存用于 benchmark 对比的经典算法实现。kemm.py保存 KEMM 主体,是整个项目里最关键的算法编排文件。
这一层负责 KEMM 的子模块,每个文件理论上只做一个方面的工作。
adaptive.py策略选择与 reward 更新。drift.py前沿漂移建模与预测。memory.py历史环境压缩记忆与相似环境检索。transfer.py几何迁移与多源迁移。types.py用 dataclass 和类型别名承接配置、结果和结构化状态。
需要明确的一点是:当前 adaptive.py / drift.py / memory.py / transfer.py 仍然是
新架构下的真实实现区;根目录旧文件现在只保留兼容导出:
adaptive_operator.pypareto_drift.pycompressed_memory.pygeodesic_flow.py
因此,后续如果你要真正修改 KEMM 的核心子模块,应优先直接改:
kemm/core/adaptive.pykemm/core/drift.pykemm/core/memory.pykemm/core/transfer.py
这一层负责把“问题专用增强逻辑”从通用 KEMM 核心里拆出去。
benchmark.py保存 benchmark-only 的结构先验候选生成器。
这一层的意义很直接:
- 以后你改 KEMM 主体时,不需要碰 benchmark 测试函数专用逻辑
- 以后你改 benchmark prior 时,也不需要回到主流程类里找硬编码
- ship 主线可以天然不依赖这一层
这一层负责 benchmark 问题与指标,不直接承担算法流程。
problems.py定义动态测试问题、真实前沿生成和时间变量控制逻辑。metrics.py定义IGD / MIGD / SP / MS / HV等指标。
这一层负责把 benchmark 结果转换成报告产物,而不是直接在控制台里丢表格。
benchmark_report.py输出metrics.csv / ranks.csv / summary.json / summary.md
最常见的 benchmark 入口是:
python -m apps.benchmark_runner --quick兼容 wrapper python run_experiments.py --quick 仍然可用,但不再是首选入口。
调用链大致是:
apps.benchmark_runner.main()run_benchmark()ExperimentRunner.run_all()- benchmark task cache 先检查
(setting, algorithm, problem, run, ablation_variant) _run_single()或对应串/并行任务逻辑中创建算法实例- 算法对象在每次环境变化时调用
respond_to_change() - 实验结束后交给
ResultPresenter与export_benchmark_report()
benchmark 主线的标准输出结构是:
benchmark_outputs/
└── benchmark_YYYYMMDD_HHMMSS/
├── figures/
├── raw/
└── reports/
这和 ship 主线保持一致,便于统一归档与阅读。
这个文件不是算法实现本身,但它决定了 benchmark 主线是如何被组织成完整实验的。
负责集中描述:
- 使用哪些问题
- 使用哪些算法
- 每个问题重复多少次
- 每次环境变化跑多少代
- 是否启用
JY问题 - 是否强制跳过缓存
- 输出目录放在哪里
它的作用类似于实验计划表。
负责真正执行实验。核心循环是三层:
- 算法
- 问题
- 随机重复
在每个 (算法, 问题, 运行编号) 内部,又会继续遍历每次环境变化。
完整模式下,ExperimentRunner 还负责:
- 按
(setting, algorithm, problem, run, ablation_variant)读写 benchmark task cache - 预计算并复用真实 POF 快照和时间变量
- 只对 canonical setting 保留完整
IGD/HV曲线与KEMMChangeDiagnostics聚合
负责三类事情:
- 打印终端表格
- 画图
- 导出结构化报告
因此 apps/benchmark_runner.py 在工程角色上更像实验编排器。
从 CLI 角度看,这个入口现在还额外承接:
--skip-ablation--summary-only--force-rerun--algorithms--problems--workers
这里有一个现在很重要的边界:
ResultPresenter不再直接扒 KEMM 私有属性画图- 它会先整理出
BenchmarkFigurePayload - 图表层再消费
KEMMChangeDiagnostics这类结构化对象
这样后续你重构 respond_to_change() 时,只要结构化输出不变,可视化层通常不需要改。
BaseDMOEA 是所有动态多目标算法共享的基础层。它不关心某个算法是否有 memory 或
transfer,只关心一个动态多目标进化算法最基本要会什么。
- 初始化种群
- 统一调用目标函数
- 非支配排序
- 拥挤距离计算
- 环境选择
- 单代演化
- 得到当前 Pareto front
如果没有 BaseDMOEA:
- 每个算法都要重复实现一遍非支配排序和环境选择
- benchmark 对比不公平,因为不同算法可能隐式使用了不同基础操作
- ship 主线适配时会更难复用
所以它的价值是统一演化框架底盘。
这个文件保存 benchmark 对比算法。当前包含:
RI_DMOEAPPS_DMOEAKF_DMOEASVR_DMOEATr_DMOEAMMTL_DMOEA
这些算法的作用不是陪跑,而是定义 KEMM 的对照基线。也就是说,benchmark 主线能不能 支撑论文,关键取决于:
- KEMM 是否强于这些基线
- KEMM 的收益是否稳定
- 这些基线是否覆盖了合理的策略类型
从结构上看,这个文件的意义是把对手算法集中管理,而不是分散在实验脚本里。
这个文件定义动态多目标 benchmark 问题。它回答的是:
- 在时间
t下,某个问题的目标函数是什么 - 在时间
t下,理论真实前沿PF_t^*是什么
当前项目中主要包括:
FDA1FDA2FDA3dMOP1dMOP2dMOP3JY1JY4
补充说明:
dMOP3曾在早期实现中被误写成与FDA1数学等价- 当前版本已修复为
f1(x_I) = x_r的动态 active-coordinate 形式 - 如果你在历史 benchmark 输出里看到
FDA1与dMOP3的指标整行相同,应视为修复前的过期结果
因为 benchmark 指标例如 IGD 和 MIGD 需要知道真实前沿。
如果没有真实前沿,就只能比较算法之间谁更好,却很难说谁更接近真实最优分布。
通常一个问题对象要负责:
- 输入维度和变量边界
- 给定
X, t返回F(X, t) - 给定
t生成参考前沿样本
因此 problems.py 本质上是实验标准答案生成器。
这个文件定义 benchmark 评估指标,用来把“看起来不错”变成可量化比较。
IGD 衡量算法得到的前沿相对真实前沿的接近程度。
[ \mathrm{IGD}(P, PF^) = \frac{1}{|PF^|} \sum_{y \in PF^*} d(y, P) ]
MIGD 是时间上的平均:
[ \mathrm{MIGD} = \frac{1}{T} \sum_{t=1}^{T} \mathrm{IGD}(P_t, PF_t^*) ]
SP 衡量解集均匀性,越小越好。
[ \mathrm{SP} = \sqrt{\frac{1}{n-1}\sum_{i=1}^{n}(d_i - \bar d)^2} ]
MS 衡量覆盖范围,越大越好。
[ \mathrm{MS} = \sqrt{\frac{1}{m}\sum_{j=1}^{m} \left( \frac{\min(f_j^{max}, F_j^{max}) - \max(f_j^{min}, F_j^{min})}{F_j^{max} - F_j^{min}} \right)^2} ]
因为指标不是某个算法私有的,而是整个 benchmark 体系共享的评价标准。
这一层负责变化后应该把多少资源分给 memory、prediction、transfer、reinit。
UCB1BanditAdaptiveOperatorSelectorParetoFrontDriftDetector
UCB1 主体评分:
[ \mathrm{UCB}_i = Q_i + c \sqrt{\frac{\ln T}{N_i}} ]
其中:
Q_i:第i个策略的平均 rewardT:累计选择总次数N_i:该策略被选中的次数c:探索系数
标准 UCB1 是选一个 arm,而当前工程实现会进一步做 softmax:
[ p_i = \frac{\exp(\mathrm{UCB}_i / \tau)}{\sum_j \exp(\mathrm{UCB}_j / \tau)} ]
它更准确地说应被称为:
UCB-guided allocation
而不是标准单臂 UCB1 决策。
它的输入主要来自两方面:
- 历史 reward
- 当前变化强度的辅助信息
它的输出是:
- 四类策略的样本比例
这一层负责记住过去见过的环境,并在未来相似环境出现时复用历史精英。
LightweightVAEVAECompressedMemory
如果直接把所有历史环境和所有历史解都原样存起来:
- 空间成本很高
- 检索也会越来越慢
- 不容易建立当前环境与过去哪一类环境更接近的映射
所以当前方案是:
- 为每个环境构造一个固定维度的环境指纹
- 用轻量 VAE 压缩环境与精英解信息
- 在变化后按相似度检索若干历史环境
- 把历史精英拿出来作为候选池的一部分
VAE 的 ELBO:
\mathbb{E}_{q(z|x)}[\log p(x|z)]
- \beta D_{KL}(q(z|x)|p(z)) ]
在实现里通常按最小化损失写成:
[ \mathrm{Loss} = \mathrm{ReconLoss} + \beta \cdot \mathrm{KL} ]
respond_to_change() 中的 _build_memory_candidates() 会:
- 用当前环境探针向量做检索
- 取回最相似的若干历史环境
- 按相似度分配各环境可贡献的候选数量
- 用当前目标函数快速筛一遍,避免过时解直接进入下一代
这一层负责环境变化后,前沿大概会往哪里漂移。
LightweightGPRParetoFrontDriftPredictor
RBF 核:
[ k(x, x') = \sigma_f^2 \exp\left(- \frac{|x - x'|^2}{2l^2}\right) ]
预测均值:
[ \mu_* = k_*^T (K + \sigma_n^2 I)^{-1} y ]
预测方差:
[ \sigma_^2 = k_{**} - k_^T (K + \sigma_n^2 I)^{-1} k_* ]
这个模块要做三件事:
- 从每一时刻的 Pareto front 提取可比较的特征
- 用时间序列方式累积训练样本
- 在下一次变化时预测新前沿的形状或中心趋势
_build_prediction_candidates() 当前采用混合策略:
- 线性质心外推
- GP 生成候选
- 以 elite 中心为核心的扰动补样
这样做的原因是:
- 线性外推稳定、便宜
- GP 更有表达力
- elite 中心补样可以避免预测候选过于发散
这一层负责当当前环境和历史环境结构相似时,能否把旧环境里的结构知识迁到新环境。
GrassmannGeodesicFlowManifoldTransferLearningMultiSourceTransfer
Grassmann geodesic 的核心形式:
[ \Phi(t) = P_S U \cos(t\Theta) + R_S V \sin(t\Theta), \quad t \in [0,1] ]
这里的直觉是:不直接在原空间粗暴线性插值,而是在子空间流形上更平滑地连接源域和目标域。
_build_transfer_candidates() 的逻辑是:
- 从记忆里取回若干相似源域
- 从当前种群中抽目标域参考样本
- 用多源迁移生成候选
- 在迁移结果周围再加一层小扰动,补充多样性
这一步的作用主要是结构复用,与 memory 直接回放不同。
这一层不直接贡献优化性能,但它对工程稳定性很重要。
当前它的价值主要体现在:
- 用 dataclass 收口配置
- 用结构化对象表达结果
- 让 IDE 和测试更容易发现接口不一致
从长期维护角度看,它是让代码不继续散掉的基础设施。
当前已经显式提供:
KEMMConfig统一承接 reward window、探索系数、memory/drift/transfer 超参数、候选池启发式系数等KEMMChangeDiagnostics统一承接一次变化响应后的operator_ratios / requested_counts / actual_counts / candidate_pool_size / prediction_confidence / response_quality等信息
这是 KEMM 主线里最核心的编排器。它本身不是某个单独数学模块,而是把 adaptive、 memory、drift、transfer 和演化底盘组织成一个完整动态响应流程。
KEMM_DMOEA_Improved 的职责是:
- 在初始化时组装所有核心子模块
- 在环境变化时生成新候选池
- 在平稳阶段执行常规进化
- 维护 KEMM 的状态变量和历史信息
构造函数里主要组装:
_transfer_module_multi_src_transfer_vae_memory_operator_selector_drift_detector_drift_predictor_centroid_history_benchmark_adapter
这意味着 kemm.py 本质上是依赖注入和流程编排文件。
respond_to_change() 当前可以分解为下面九类逻辑:
- 归档上一环境信息
- 计算基础策略比例
- 结合当前变化强度修正比例
- 把比例映射为各类样本数量
- 分别生成各类候选
- 合并为候选池
- 统一评价候选池
- 通过环境选择保留
pop_size个样本 - 估计响应质量并更新 reward
当前候选池可能包含以下来源:
- memory candidates
- prediction candidates
- transfer candidates
- benchmark-aware prior candidates
- elite keep
- previous population reuse
- random reinitialization
这些来源的设计意图不同:
memory:复用过去的好结构prediction:根据时间趋势前推transfer:把相似域结构迁过来prior:benchmark 专用结构先验elite keep:保持连续性previous reuse:保留分布惯性reinit:防止陷入局部并补齐规模
现在 benchmark prior 已经通过 kemm/adapters/benchmark.py 注入,主流程里只保留
_build_prior_candidates() 和兼容旧接口的 _problem_aware_candidates() 包装。
它的特点是:
- 对标准测试问题很有效
- 对真实问题不一定成立
- 不应被写成通用理论模块
因此当前设计里:
- benchmark 主线可以显式开启
- ship 主线默认关闭
这是正确的架构边界。
为了更方便读代码,下面把关键私有函数逐个解释。
职责:
- 提取当前非支配精英
- 计算环境指纹
- 写入 VAE memory
- 更新漂移检测器
- 更新 GP 预测器
它的意义是给下一次变化响应准备历史上下文。
职责:
- 为 memory 检索构造固定维度探针向量
这不是论文标准公式,更像工程环境指纹。当前还包含过一次维度补齐 bug 修复。
职责:
- 在 UCB 基础比例之上,再结合当前变化强度和可迁移性做二次修正
这部分应明确标注为 engineering heuristic。
职责:
- 把连续比例转成整数样本数
- 保证总样本数与
pop_size同量级
职责:
- 从记忆里取回相似环境的精英
- 用当前目标函数再筛一遍
职责:
- 混合线性外推、GP 候选和 elite 中心扰动
职责:
- 基于相似源域做多源迁移
- 再加小扰动补多样性
职责:
- 只在 benchmark 模式下调用 adapter 生成结构先验候选
职责:
- 保留上一环境精英
- 在精英附近继续补样
职责:
- 保留上一代部分种群分布
职责:
- 在候选不足或变化很大时,用随机样本兜底
职责:
- 用轻量 proxy 度量当前响应质量
- 反馈给 adaptive selector
这里不是严格 IGD,而是工程代理量。
职责:
- 给 benchmark 可视化、日志和调试层提供结构化诊断接口
- 避免外部直接依赖私有属性
ship 主线并没有重新发明一套算法,而是通过 ship_simulation/optimizer/kemm_solver.py
复用同一个 KEMM_DMOEA_Improved。
适配时主要做了几件事:
- 用 ship 问题的
evaluate_population代替 benchmark 目标函数 - 用 ship 问题的变量边界替代 benchmark 的边界
- 注入直线初始解与场景感知绕行初始候选,帮助种群更快进入“安全且能推进”的区域
- 将 ship 场景里的 terminal progress 留在
fuel/time维度,不再混入risk目标 - 将 ship 侧约束违背度收敛为
bounds + safety + intrusion语义 - 关闭 benchmark-aware prior
因此可以把当前项目理解成:
kemm/负责通用算法核心ship_simulation/负责真实应用问题壳层
此外,最新 ship 报告层已经把“算法主线”和“实验可比性/统计性”拆开:
--strict-comparable在报告阶段自动追加random_matched / nsga_style_matched,用于预算对齐比较- 统计显著性结果固定导出
raw/statistical_tests.*与reports/statistical_significance.md - 扰动鲁棒性扫描通过
--robustness-sweep独立输出raw/robustness_*与reports/robustness_sweep.md
这三部分都属于“实验呈现与验证层增强”,不改变 KEMM 的核心四机制结构定义。
这里要特别强调一个边界:
heuristic detour seeds属于 ship 实例化层,不是 benchmark 主线的通用结构先验- 它们的作用是给物理场景提供安全绕行 warm start,而不是修改 KEMM 的四机制主架构
- 因此论文里可以写成“在 ship 场景中,KEMM 叠加了场景感知初始候选注入机制”,不要写成“通用 KEMM 必然包含该模块”
- KEMM 已被拆出为独立算法骨架
- benchmark 与 ship 共用一套核心
- 报告输出目录统一
- 关键步骤已可单独测试
apps/benchmark_runner.py还可以继续拆 presenter 与 plotting- KEMM 主线仍有 benchmark-aware 增强逻辑,需要在论文与文档里谨慎表述
如果要从 KEMM 主线切入,建议按下面顺序阅读:
README.mddocs/kemm_reference.mddocs/formula_audit.mdapps/benchmark_runner.pykemm/algorithms/base.pykemm/algorithms/kemm.pykemm/core/adaptive.pykemm/core/memory.pykemm/core/drift.pykemm/core/transfer.pykemm/benchmark/problems.pykemm/benchmark/metrics.py
可以用一句话概括:
KEMM 是一个面向动态多目标问题的组合式响应框架,它在环境变化后同时利用历史记忆、漂移预测、几何迁移和随机探索,通过自适应策略分配生成新的候选池,再用统一环境选择形成下一环境种群。
如果需要再压缩一点,可以强调三个关键词:
- 组合式
- 响应型
- 可迁移
如果要把 benchmark 主线组织成论文叙事,当前推荐顺序是:
- 用
benchmark_metrics_grid.png给出主结果 - 用
benchmark_rank_bar.png总结综合排名 - 用
benchmark_heatmap.png与benchmark_wilcoxon.png说明问题分布与统计显著性 - 用
benchmark_pairwise_wins.png补充逐对比较关系 - 用
benchmark_igd_time.png与benchmark_hv_time.png展示变化过程中的逼近与覆盖恢复 - 用
benchmark_operator_ratios.png、benchmark_response_quality.png、benchmark_prediction_confidence.png分解机制过程量 - 用
benchmark_change_dashboard.png做机制总览 - 用
benchmark_ablation.png说明模块必要性
benchmark_migd_bar.png 现在更适合作为附录/兼容图,而不是默认主图。
对应文档索引:
- 图表表达方式:
docs/figure_catalog.md - 图表参数与模板:
docs/visualization_guide.md