scipy differential_evolution并行自定义目标函数卡死问题
问题原因
scipy.optimize.differential_evolution在设置workers参数大于1时,默认调用Python标准库multiprocessing实现多核并行,底层依赖pickle模块序列化目标函数后传递给子进程执行。scipy内置的rosen是预编译的C实现函数,可被正常序列化,因此官方示例可以正常运行。- Jupyter Notebook属于交互式运行环境,在单元格内直接定义的自定义函数属于交互作用域对象,Python 3.8在Windows、macOS系统下默认采用
spawn模式启动多进程,该模式无法正确序列化交互域中定义的函数,子进程始终无法获取目标函数的有效定义,就会一直阻塞,表现为主程序无响应、类似卡死的状态,和函数本身的逻辑写法无关,即使完全复制内置rosen的源码也会触发该问题。 - 即使是Linux系统,在Jupyter环境下直接运行未做适配的多进程代码,也有概率因为序列化失败、子进程递归启动导致死锁。
解决方案
根据使用场景选择对应方案即可:
- 方案1:将自定义目标函数写入独立的
.py格式脚本文件,再在Jupyter中通过import语句导入使用。例如新建cost_func.py文件写入自定义的Rosenbrock函数,在Notebook中通过from cost_func import rosen_custom导入后再调用差分进化函数,此时导入的函数属于可正常序列化的模块级函数,并行任务可正常运行。 - 方案2:如果将代码写在普通
.py脚本中运行,只需将调用差分进化的执行逻辑放入if __name__ == '__main__':代码块保护下,避免子进程启动时递归执行主脚本逻辑导致死锁,示例代码如下:
import numpy as np from scipy.optimize import differential_evolution def rosen_custom(x): x = np.asarray(x) r = np.sum(100.0 * (x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0, axis=0) return r if __name__ == '__main__': # 边界根据目标函数维度自行设置,示例为4维Rosenbrock函数的边界 bounds = [(0, 2), (0, 2), (0, 2), (0, 2)] result = differential_evolution(rosen_custom, bounds, strategy='rand1bin', workers=5) print(result)
- 方案3:如果希望直接在Jupyter Notebook中运行,不想额外创建独立脚本文件,可以替换标准库的多进程实现为
multiprocess库,该库用dill做序列化引擎,支持交互环境下定义的函数。首先执行pip install multiprocess安装依赖,再传入进程池的map方法作为workers参数即可,示例代码如下:
import numpy as np from scipy.optimize import differential_evolution import multiprocess def rosen_custom(x): x = np.asarray(x) r = np.sum(100.0 * (x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0, axis=0) return r if __name__ == '__main__': bounds = [(0, 2), (0, 2), (0, 2), (0, 2)] with multiprocess.Pool(5) as pool: result = differential_evolution(rosen_custom, bounds, strategy='rand1bin', workers=pool.map) print(result)
注意:Jupyter环境下使用并行时不要省略if __name__ == '__main__'保护块,否则可能触发子进程无限递归启动的问题。在Python 3.8 + scipy 1.8.1的环境下,方案3的兼容性最好。
内容的提问来源于stack exchange,提问作者RJ-mac
相关产品推荐
相关产品推荐

