zoukankan      html  css  js  c++  java
  • Genetic Algorithms with python 学习笔记ch7

    Knights Problem

    Knights Problem 要求使用最小数量的 knight 来攻击整个棋盘上的方块,因此棋盘至少是 3 * 4 的才能满足情况。knight能够攻击的范围如下所示。

    image.png

    其 genetic.py 的代码为:

    import random
    import statistics
    import sys
    import time
    
    
    def _generate_parent(length, geneSet, get_fitness):
        genes = []
        while len(genes) < length:
            sampleSize = min(length - len(genes), len(geneSet))
            genes.extend(random.sample(geneSet, sampleSize))
        fitness = get_fitness(genes)
        return Chromosome(genes, fitness)
    
    
    def _mutate(parent, geneSet, get_fitness):
        childGenes = parent.Genes[:]
        index = random.randrange(0, len(parent.Genes))
        newGene, alternate = random.sample(geneSet, 2)
        childGenes[index] = alternate if newGene == childGenes[index] else newGene
        fitness = get_fitness(childGenes)
        return Chromosome(childGenes, fitness)
    
    
    def _mutate_custom(parent, custom_mutate, get_fitness):
        childGenes = parent.Genes[:]
        custom_mutate(childGenes)
        fitness = get_fitness(childGenes)
        return Chromosome(childGenes, fitness)
    
    
    def get_best(get_fitness, targetLen, optimalFitness, geneSet, display,
                 custom_mutate=None, custom_create=None):
        if custom_mutate is None:
            def fnMutate(parent):
                return _mutate(parent, geneSet, get_fitness)
        else:
            def fnMutate(parent):
                return _mutate_custom(parent, custom_mutate, get_fitness)
    
        if custom_create is None:
            def fnGenerateParent():
                return _generate_parent(targetLen, geneSet, get_fitness)
        else:
            def fnGenerateParent():
                genes = custom_create()
                return Chromosome(genes, get_fitness(genes))
    
    
        for improvement in _get_improvement(fnMutate, fnGenerateParent):
            display(improvement)
            if not optimalFitness > improvement.Fitness:
                return improvement
    
    
    def _get_improvement(new_child, generate_parent):
        bestParent = generate_parent()
        yield bestParent
        while True:
            child = new_child(bestParent)
            if bestParent.Fitness > child.Fitness:
                continue
            if not child.Fitness > bestParent.Fitness:
                bestParent = child
                continue
            yield child
            bestParent = child
    
    
    class Chromosome:
        def __init__(self, genes, fitness):
            self.Genes = genes
            self.Fitness = fitness
    
    
    class Benchmark:
        @staticmethod
        def run(function):
            timings = []
            stdout = sys.stdout
            for i in range(100):
                sys.stdout = None
                startTime = time.time()
                function()
                seconds = time.time() - startTime
                sys.stdout = stdout
                timings.append(seconds)
                mean = statistics.mean(timings)
                if i < 10 or i % 10 == 9:
                    print("{} {:3.2f} {:3.2f}".format(
                        1 + i, mean,
                        statistics.stdev(timings, mean) if i > 1 else 0))
    

    上面的代码中函数 get_best 增加了一个参数 custom_create ,目的是利用自定义的创造函数产生父染色体。

    KnightsTest.py 的完整代码为:

    import datetime
    import random
    import unittest
    
    import genetic
    
    
    def get_fitness(genes, boardWidth, boardHeight):
       attacked = set(pos
                      for kn in genes
                      for pos in get_attacks(kn, boardWidth, boardHeight))
       return len(attacked)
    
    
    def display(candidate, startTime, boardWidth, boardHeight):
       timeDiff = datetime.datetime.now() - startTime
       board = Board(candidate.Genes, boardWidth, boardHeight)
       board.print()
    
       print("{}
    	{}	{}".format(
           ' '.join(map(str, candidate.Genes)),
           candidate.Fitness,
           timeDiff))
    
    
    def mutate(genes, boardWidth, boardHeight, allPositions, nonEdgePositions):
       count = 2 if random.randint(0, 10) == 0 else 1
       while count > 0:
           count -= 1
           positionToKnightIndexes = dict((p, []) for p in allPositions)
           for i, knight in enumerate(genes):
               for position in get_attacks(knight, boardWidth, boardHeight):
                   positionToKnightIndexes[position].append(i)
           knightIndexes = set(i for i in range(len(genes)))
           unattacked = []
           for kvp in positionToKnightIndexes.items():
               if len(kvp[1]) > 1:
                   continue
               if len(kvp[1]) == 0:
                   unattacked.append(kvp[0])
                   continue
               for p in kvp[1]:  # len == 1
                   if p in knightIndexes:
                       knightIndexes.remove(p)
    
           potentialKnightPositions = 
               [p for positions in
                map(lambda x: get_attacks(x, boardWidth, boardHeight),
                    unattacked)
                for p in positions if p in nonEdgePositions] 
                   if len(unattacked) > 0 else nonEdgePositions
    
           geneIndex = random.randrange(0, len(genes)) 
               if len(knightIndexes) == 0 
               else random.choice([i for i in knightIndexes])
    
           position = random.choice(potentialKnightPositions)
           genes[geneIndex] = position
    
    
    def create(fnGetRandomPosition, expectedKnights):
       genes = [fnGetRandomPosition() for _ in range(expectedKnights)]
       return genes
    
    
    def get_attacks(location, boardWidth, boardHeight):
       return [i for i in set(
           Position(x + location.X, y + location.Y)
           for x in [-2, -1, 1, 2] if 0 <= x + location.X < boardWidth
           for y in [-2, -1, 1, 2] if 0 <= y + location.Y < boardHeight
           and abs(y) != abs(x))]
    
    
    class KnightsTests(unittest.TestCase):
       def test_3x4(self):
           width = 4
           height = 3
           # 1,0   2,0   3,0
           # 0,2   1,2   2,2
           # 2 	 N N N .
           # 1 	 . . . .
           # 0 	 . N N N
           #   	 0 1 2 3
           self.find_knight_positions(width, height, 6)
    
       def test_8x8(self):
           width = 8
           height = 8
           self.find_knight_positions(width, height, 14)
    
       def test_10x10(self):
           width = 10
           height = 10
           self.find_knight_positions(width, height, 22)
    
       def test_12x12(self):
           width = 12
           height = 12
           self.find_knight_positions(width, height, 28)
    
       def test_13x13(self):
           width = 13
           height = 13
           self.find_knight_positions(width, height, 32)
    
       def test_benchmark(self):
           genetic.Benchmark.run(lambda: self.test_10x10())
    
       def find_knight_positions(self, boardWidth, boardHeight, expectedKnights):
           startTime = datetime.datetime.now()
    
           def fnDisplay(candidate):
               display(candidate, startTime, boardWidth, boardHeight)
    
           def fnGetFitness(genes):
               return get_fitness(genes, boardWidth, boardHeight)
    
           allPositions = [Position(x, y)
                           for y in range(boardHeight)
                           for x in range(boardWidth)]
    
           if boardWidth < 6 or boardHeight < 6:
               nonEdgePositions = allPositions
           else:
               nonEdgePositions = [i for i in allPositions
                                   if 0 < i.X < boardWidth - 1 and
                                   0 < i.Y < boardHeight - 1]
    
           def fnGetRandomPosition():
               return random.choice(nonEdgePositions)
    
           def fnMutate(genes):
               mutate(genes, boardWidth, boardHeight, allPositions,
                      nonEdgePositions)
    
           def fnCreate():
               return create(fnGetRandomPosition, expectedKnights)
    
           optimalFitness = boardWidth * boardHeight
           best = genetic.get_best(fnGetFitness, None, optimalFitness, None,
                                   fnDisplay, fnMutate, fnCreate)
           self.assertTrue(not optimalFitness > best.Fitness)
    
    
    class Position:
       def __init__(self, x, y):
           self.X = x
           self.Y = y
    
       def __str__(self):
           return "{},{}".format(self.X, self.Y)
    
       def __eq__(self, other):
           return self.X == other.X and self.Y == other.Y
    
       def __hash__(self):
           return self.X * 1000 + self.Y
    
    
    class Board:
       def __init__(self, positions, width, height):
           board = [['.'] * width for _ in range(height)]
    
           for index in range(len(positions)):
               knightPosition = positions[index]
               board[knightPosition.Y][knightPosition.X] = 'N'
           self._board = board
           self._width = width
           self._height = height
    
       def print(self):
           # 0,0 prints in bottom left corner
           for i in reversed(range(self._height)):
               print(i, "	", ' '.join(self._board[i]))
           print(" 	", ' '.join(map(str, range(self._width))))
    
    
    if __name__ == '__main__':
       unittest.main()
    

    其中计算适应值的函数如下,适应值由棋盘上被攻击的方块数决定。

    def get_fitness(genes, boardWidth, boardHeight):
      attacked = set(pos
                     for kn in genes
                     for pos in get_attacks(kn, boardWidth, boardHeight))
      return len(attacked)
    

    为了缩小求解空间,我们自定义了函数 mutate。

    def mutate(genes, boardWidth, boardHeight, allPositions, nonEdgePositions):
      count = 2 if random.randint(0, 10) == 0 else 1
      while count > 0:
          count -= 1
          positionToKnightIndexes = dict((p, []) for p in allPositions)
          for i, knight in enumerate(genes):
              for position in get_attacks(knight, boardWidth, boardHeight):
                  positionToKnightIndexes[position].append(i)
          knightIndexes = set(i for i in range(len(genes)))
          unattacked = []
          for kvp in positionToKnightIndexes.items():
              if len(kvp[1]) > 1:
                  continue
              if len(kvp[1]) == 0:
                  unattacked.append(kvp[0])
                  continue
              for p in kvp[1]:  # len == 1
                  if p in knightIndexes:
                      knightIndexes.remove(p)
    
          potentialKnightPositions = 
              [p for positions in
               map(lambda x: get_attacks(x, boardWidth, boardHeight),
                   unattacked)
               for p in positions if p in nonEdgePositions] 
                  if len(unattacked) > 0 else nonEdgePositions
    
          geneIndex = random.randrange(0, len(genes)) 
              if len(knightIndexes) == 0 
              else random.choice([i for i in knightIndexes])
    
          position = random.choice(potentialKnightPositions)
          genes[geneIndex] = position
    

    首先,有十分之一的可能性突变两次,防止陷入局部最优解。
    在突变之前,先统计所有被攻击的方块并保存在键值对 positionToKnightIndexes 中,键为位置,值为攻击这个位置的所有骑士的列表。
    接着将所有的未被攻击的位置存储在 unattacked 中,所有已经攻击其他方块一次的knight的序号从 knightIndexs 中删除。
    如果未被攻击的方块数>0,则 potentialKnightPositions 为能够攻击untackked方块的并且还是非边界的位置。否则 potentialKnightPositions 为非边界的位置。
    如果 knightIndexes 长度不为0,则随机选择其中一个进行突变。如果为0,则在genes长度中选择一个索引进行突变。

  • 相关阅读:
    ubuntu 16.0.5 修改网卡为固定IP
    Ubuntu PostgreSQL安装和配置
    NPOI 1.2.1版本替换为2.4.0版本实体类变更
    C# 之 Math取整
    解决github 下载过慢的问题
    优伦自动语言话务员设置
    python3学习笔记 列表
    【postgresql】role "root" does not exist 解决办法
    Eclipse使用的小技巧
    Servlet
  • 原文地址:https://www.cnblogs.com/idella/p/13462258.html
Copyright © 2011-2022 走看看