编写用户定义的岛屿#
虽然pagmo提供了许多UDI(参见岛屿列表)以访问多种并行化技术,但专家用户可以编写自己的扩展pygmo功能。在本教程中,我们将展示如何编写一个UDI。请记住,UDI是可以用来构建岛屿
的类,而群岛
则可以使用这些岛屿来分发/并行化优化任务,并通过广义岛屿模型迁移解决方案以提高整体优化质量。
我们鼓励用户阅读类island
的文档,以获取可以在UDI中实现或必须实现的详细方法列表。此外,教程使用类岛屿是理解整体使用的一个很好的起点。
UDI 是一个 Python 类,在其最简单的形式中,包含方法 run_evolve(self, algo, pop)
,该方法使用 algorithm
algo
来进化
population
pop
。
>>> import pygmo as pg
>>> class my_isl:
... def run_evolve(self, algo, pop):
... new_pop = algo.evolve(pop)
... return algo, new_pop
... def get_name(self):
... return "It's my island!"
我们还在上面包含了可选的方法 get_name(self)
,该方法将由各种 __repr__(self)
使用,以提供一些 pygmo 类的人类可读信息。然后可以使用上述 UDI 来构建 island
(类似于如何使用 UDP 来构建 问题
等)。
>>> isl = pg.island(algo = pg.de(100), prob = pg.ackley(5), udi = my_isl(), size = 20)
>>> print(isl)
Island name: It's my island!
C++ class name: ...
Status: idle
Algorithm: DE: Differential Evolution
Problem: Ackley Function
Replacement policy: Fair replace
Selection policy: Select best
Population size: 20
Champion decision vector: [...
Champion fitness: [...
这很简单!现在让我们理解我们实际上做了什么。对象 isl
现在包含了我们的 UDI,并且在构造时会打开一个线程,并在调用 evolve()
时将 run_evolve(self, algo, pop)
的执行委托给它。run_evolve()
方法必须返回用于进化的算法对象和进化后的种群。
但是有一个问题。我们在CPython中!一般来说,由于全局解释器锁(GIL)的存在,CPython会序列化Python代码的执行。因此,虽然上面的代码完全没问题并且可以在pygmo中运行,但一组my_isl
运行的进化不会并行执行,因为每个island
在执行其evolve()
方法时,会获取GIL并在evolve()
执行期间持有它。
因此,以下代码:
>>> archi = pg.archipelago(n = 5, algo = pg.de(100), prob = pg.rosenbrock(10), pop_size = 20, udi = my_isl())
>>> archi.evolve()
不会并行运行进化(仅使用不同的线程)。
为了正确编码UDI,需要编码def run_evolve(self, algo, pop)
,以便在将进化任务卸载到单独进程时释放GIL。
一个示例展示了如何使用Python的多进程模块来实现这一点。让我们看一下来自mp_island
的一些代码片段。
>>> def _evolve_func(algo, pop): # doctest : +SKIP
... new_pop = algo.evolve(pop)
... return algo, new_pop
>>> class mp_island(object): # doctest : +SKIP
... def __init__(self):
... # Init the process pool, if necessary.
... mp_island.init_pool()
...
... def run_evolve(self, algo, pop):
... with mp_island._pool_lock:
... res = mp_island._pool.apply_async(_evolve_func, (algo, pop))
... return res.get()
这里没有报告完整的细节,可以在mp_island
代码中阅读。简而言之,发生的情况是algo.evolve(pop)
被卸载到一个进程中(在构造时调用的共享池中初始化,调用init_pool()
静态方法)。指令res.get()
使得run_evolve
所在的线程保持等待进程执行,同时释放GIL,使得并行化有效。