
SciPy 的 differential_evolution 支持自定义策略(callable),但默认不向策略函数暴露 DifferentialEvolutionSolver 实例。本文详解如何合理获取关键求解状态(如迭代计数、随机数生成器、种群能量等),并给出安全、可维护的实践方案。
scipy 的 `differential_evolution` 支持自定义策略(callable),但默认不向策略函数暴露 `differentialevolutionsolver` 实例。本文详解如何合理获取关键求解状态(如迭代计数、随机数生成器、种群能量等),并给出安全、可维护的实践方案。
在 SciPy 1.9+ 版本中,differential_evolution 允许通过 strategy= 参数传入自定义 callable(如类实例或函数),其签名必须为:
def __call__(self, candidate: int, population: np.ndarray, rng=None) -> np.ndarray:
...其中 candidate 是当前待变异个体的索引(0 到 popsize-1),population 是当前完整种群(shape (popsize, ndim)),rng 是 SciPy 内部使用的 numpy.random.Generator 实例。
⚠️ 关键事实:自定义策略无法直接访问 DifferentialEvolutionSolver 的私有属性(如 .nfev, .nit, .population_energies, .scale, .recombination)。这是因为策略被设计为“无状态纯函数式组件”——其行为应仅由输入参数和自身封装的状态决定,而非依赖求解器全局上下文。
✅ 推荐做法:策略内建状态 + 利用 rng 和 candidate 推断迭代阶段
由于 candidate 在每次迭代中从 0 递增至 popsize-1,当 candidate == 0 时即标志着新迭代开始。可据此在策略中维护内部状态:
import numpy as np
from scipy.optimize import differential_evolution
class AdaptiveStrategy:
def __init__(self, initial_scale=0.5, recombination=0.7):
self.scale = initial_scale
self.recombination = recombination
self.nit = 0 # 迭代计数器(自维护)
self.rng = None
def __call__(self, candidate, population, rng=None):
if rng is not None:
self.rng = rng # 缓存首次传入的 rng(同一迭代内 rng 相同)
# 检测新迭代:candidate 重置为 0
if candidate == 0:
self.nit += 1
# 示例:随迭代衰减 mutation scale
self.scale = max(0.1, self.scale * 0.995)
# 示例:随机扰动 recombination(利用 solver 的 rng)
if self.rng is not None:
self.recombination = self.rng.uniform(0.5, 0.9)
# 执行自定义变异逻辑(此处为简化示例)
idx = self.rng.choice(len(population), size=3, replace=False)
a, b, c = population[idx]
trial = a + self.scale * (b - c)
return np.clip(trial, -1, 1) # 确保在边界内
# 使用
result = differential_evolution(
lambda x: np.sum(x**2),
bounds=[(-2, 2)] * 3,
strategy=AdaptiveStrategy(),
seed=42
)? 获取种群适应度(energies)?用 callback 替代策略注入
策略本身不接收 population_energies,因为这会破坏接口简洁性且引入冗余耦合。若需基于适应度设计策略(如精英引导、自适应选择),应使用 callback 参数在每次迭代后读取:
def logging_callback(xk, convergence):
# 此时 solver._callback_state 包含最新状态(但属私有)
# 更稳妥方式:在 callback 中保存所需信息到外部对象
pass
# 或更推荐:继承 DifferentialEvolutionSolver(风险可控,见下文)⚠️ 高级选项:直接使用 DifferentialEvolutionSolver(需谨慎)
SciPy 提供了底层类 scipy.optimize._differentialevolution.DifferentialEvolutionSolver(路径可能随版本变化)。它公开了全部内部状态:
# 注意:此为私有 API,不保证向后兼容!
from scipy.optimize._differentialevolution import DifferentialEvolutionSolver
solver = DifferentialEvolutionSolver(
func=lambda x: np.sum(x**2),
bounds=[(-2, 2)] * 3,
popsize=15,
seed=42
)
solver.solve()
print(f"完成迭代数: {solver.nit}")
print(f"当前种群能量: {solver.population_energies}")✅ 适用场景:研究、调试、高度定制化算法开发。
❌ 不建议用于生产环境:因属私有模块,未来版本可能重构、重命名或移除。
✅ 总结与最佳实践
-
不要尝试 hack 求解器实例:
strategy的设计哲学是解耦,强行注入求解器引用会降低可测试性与可移植性。 -
策略应自持必要状态:如
scale、recombination、迭代计数、历史最优等,通过candidate==0触发更新。 -
复用
rng实现随机自适应:rng与求解器一致,确保结果可重现。 -
种群能量等全局信息 → 用
callback或DifferentialEvolutionSolver:前者适合监控与日志;后者适合深度定制(接受 API 不稳定风险)。 - 关注 SciPy 官方路线图:社区已讨论将求解器类公开化,未来可能提供更稳定的面向对象接口。
通过以上方式,你既能保持策略的简洁性与鲁棒性,又能灵活实现复杂自适应逻辑,真正发挥差分进化算法的潜力。


















