Dask:如何有效地分发遗传搜索算法?

时间:2017-11-22 14:20:30

标签: python multiprocessing dask

我已经实现了一种遗传搜索算法,并尝试将其并行化,但性能却很差(比单线程更差)。我怀疑这是由于通信开销造成的。

我在下面提供了伪代码,但实质上遗传算法会创建一个大型的“染色体”对象池,然后运行多次迭代:

  1. 根据每个染色体在“世界”中的表现进行评分。迭代过程中世界保持不变。
  2. 根据上一步计算的分数随机选择新的人口
  3. 转到步骤1进行n次迭代
  4. 评分算法(步骤1)是主要的瓶颈,因此分发处理此代码似乎很自然。

    我遇到了一些我希望可以得到帮助的问题:

    1. 如何将计算得分与map()传递给评分函数的对象相关联,即将每个持有得分的Future链接回Chromosome?我已经通过calculate_scores()方法返回对象以非常笨重的方式完成了这项工作,但实际上我需要的是,如果有更好的方法来维护链接,则返回float
    2. 评分函数的并行处理工作正常,但map()需要很长时间才能遍历所有对象。但是,与单线程版本相比,后续调用draw_chromosome_from_pool() 非常的速度慢到我还没有看到它完成的程度。我不知道是什么导致了这个,因为该方法总是在单线程版本中快速完成。是否有一些IPC继续将染色体拉回到本地过程,即使在所有的期货已经完成之后?本地流程是否以某种方式优先排序?
    3. 我担心每个周期构建/重建池的整体迭代性质将导致向工作人员传输大量数据。这个问题根源的问题是:Dask 实际上在工作池中来回发送数据的内容和时间。即什么时候环境()分配出来与染色体(),以及结果如何/何时回来?我已经阅读了docs,但要么找不到合适的细节,要么太愚蠢无法理解。
    4. 理想情况下,我认为(但需要更正)我想要的是一个分布式架构,其中每个工作人员在“永久”基础上本地保存Environment()数据,然后分发Chromosome()实例数据在迭代之间几乎没有重复的来回重复的染色体()数据得分。

      很长的帖子,所以如果你花时间阅读这篇文章,那就谢谢你了!

      class Chromosome(object):    # Small size: several hundred bytes per instance 
           def get_score():
                # Returns a float
           def set_score(i):
                # Stores a a float
      
      class Environment(object):   # Large size: 20-50Mb per instance, but only one instance
               def calculate_scores(chromosome):
                   # Slow calculation using attributes from chromosome and instance data
                   chromosome.set_score(x)
                   return chromosome
      
      class Evolver(object):
          def draw_chromosome_from_pool(self, max_score):
              while True:
                  individual = np.random.choice(self.chromosome_pool)
                  selection_chance = np.random.uniform()
                  if selection_chance < individual.get_score() / max_score:
                      return individual   
      
          def run_evolution()
               self.dask_client = Client()
               self.chromosome_pool = list()
               for i in range(10000):
                   self.chromosome_pool.append( Chromosome() )
      
               world_data = LoadWorldData() # Returns a pandas Dataframe
               self.world = Environment(world_data)
      
               iterations = 1000
               for i in range(iterations):
                   futures = self.dask_client.map(self.world.calculate_scores, self.chromosome_pool)
                   for future in as_completed(futures):
                        c = future.result()
                        highest_score = max(highest_score, c.get_score()) 
      
                   new_pool = set()
                   while len(new_pool)<self.pool_size:
                       mother = self.draw_chromosome_from_pool(highest_score)
                        # do stuff to build a new pool
      

1 个答案:

答案 0 :(得分:3)

是的,每次拨打电话

futures = self.dask_client.map(self.world.calculate_scores, self.chromosome_pool)

你正在序列化self.world,这很大。你可以在循环之前用

执行一次
future_world = client.scatter(self.world, broadcast=True)

然后在循环中

futures = self.dask_client.map(lambda ch: Environment.calculate_scores(future_world, ch), self.chromosome_pool)

将使用工作者已经存在的副本(或者使用相同的简单函数)。关键是future_world只是指向已经分发的东西的指针,但是dask会为你处理这个问题。

关于哪个染色体是哪个问题:使用as_completed会破坏您将其提交到map的顺序,但这不是您的代码所必需的。您可以在完成所有工作时使用wait进行处理,或者只是遍历future.result()(将等待每项任务完成),然后您将保留在chromosome_pool。