Scipy差分进化:如何通过回调获取每代无效种群能量占比?
在Scipy差分进化中获取每步无效种群能量占比
默认情况下,Scipy differential_evolution 提供的回调函数仅能接收当前最优解(xk)和收敛度(convergence)两个参数,无法直接获取种群及能量信息。要实现需求,无需修改依赖包源码,可通过以下两种可行方案:
方案一:手动迭代私有求解器类(推荐)
Scipy的差分进化内部通过_DifferentialEvolutionSolver类实现迭代逻辑,我们可以直接实例化该类并手动循环迭代,从而在每一步获取种群和对应能量值,再传入自定义回调计算占比。
示例代码:
import numpy as np from scipy.optimize._differentialevolution import _DifferentialEvolutionSolver # 自定义目标函数(根据你的实际问题替换) def target_func(x): return x[0]**2 + x[1]**2 + x[2]**2 # 定义无效个体判断规则(根据你的约束条件替换) def is_invalid_individual(x): # 示例:x[0] > 0 视为无效个体 return x[0] > 0 # 自定义回调类,接收种群和能量参数 class IterationCallback: def __init__(self): self.iter_count = 0 def __call__(self, xk, convergence, population, pop_energies): # 计算无效种群的能量占比 invalid_mask = np.array([is_invalid_individual(ind) for ind in population]) invalid_energy_sum = pop_energies[invalid_mask].sum() total_energy_sum = pop_energies.sum() invalid_ratio = invalid_energy_sum / total_energy_sum if total_energy_sum != 0 else 0.0 print(f"迭代步数: {self.iter_count} | 无效种群能量占比: {invalid_ratio:.4f}") self.iter_count += 1 return False # 返回True会终止优化,按需调整 # 初始化求解器 bounds = [(-10, 10)] * 3 # 优化变量的边界 solver = _DifferentialEvolutionSolver( func=target_func, bounds=bounds, seed=42, # 设置种子保证结果可复现 maxiter=100 # 最大迭代步数 ) # 实例化回调 callback = IterationCallback() # 手动执行迭代 for _ in range(solver.maxiter): try: next(solver) # 执行一次迭代 except StopIteration: break # 提前收敛则终止 # 获取当前迭代的种群和能量值 current_pop = solver.population current_energies = solver.pop_energies # 调用回调并传入额外参数 stop_signal = callback(solver.xk, solver.convergence, current_pop, current_energies) if stop_signal: break # 获取最终优化结果 final_best_x = solver.x final_best_energy = solver.fun
注意事项
_DifferentialEvolutionSolver是Scipy的私有类,虽然在1.12.0版本中稳定可用,但未来版本可能存在API变更风险。- 需根据你的实际问题替换
target_func(目标函数)和is_invalid_individual(无效个体判断规则)。
方案二:猴子补丁扩展回调参数
若想保留differential_evolution的原生调用方式,可通过猴子补丁临时修改求解器的迭代逻辑,让回调函数能接收种群和能量参数。
示例代码:
import numpy as np from scipy.optimize import differential_evolution from scipy.optimize._differentialevolution import _DifferentialEvolutionSolver # 保存原迭代方法 original_next = _DifferentialEvolutionSolver.__next__ # 重定义迭代方法,扩展回调参数 def patched_next(self): result = original_next(self) # 若存在回调,传入种群和能量信息 if self.callback is not None: stop = self.callback(self.xk, self.convergence, self.population, self.pop_energies) if stop: raise StopIteration return result # 应用猴子补丁 _DifferentialEvolutionSolver.__next__ = patched_next # 自定义回调类 class PatchedCallback: def __init__(self): self.iter_count = 0 def __call__(self, xk, convergence, population, pop_energies): # 计算无效能量占比(逻辑同方案一) invalid_mask = np.array([ind[0] > 0 for ind in population]) invalid_ratio = pop_energies[invalid_mask].sum() / pop_energies.sum() if pop_energies.sum() !=0 else 0.0 print(f"迭代步数: {self.iter_count} | 无效种群能量占比: {invalid_ratio:.4f}") self.iter_count += 1 return False # 原生调用differential_evolution bounds = [(-10, 10)] * 3 result = differential_evolution( func=lambda x: x[0]**2 + x[1]**2 + x[2]**2, bounds=bounds, callback=PatchedCallback(), maxiter=100, seed=42 ) # 恢复原方法,避免影响其他代码 _DifferentialEvolutionSolver.__next__ = original_next
注意事项
- 猴子补丁会全局修改Scipy的求解器行为,使用后建议恢复原方法。
- 仅在单进程场景下可靠,若使用多进程(
workers>1),该方法无法正常获取种群信息。
内容的提问来源于stack exchange,提问作者Bb92
相关产品推荐
相关产品推荐

