编写用户定义的岛屿#

虽然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,使得并行化有效。