遗传算法解n-queens问题

一, 课程介绍:

1.1 实验内容

通过上一节的学习,对遗传算法的理论知识和简单实现有了初步了解。这一节,通过学习Pyevolve模块,来更好地解决问题。

这一节我们通过两个实例了解Pyevolve的使用方法,并且分析算法结果。通过实例n-queens问题了解Pyevolve遗传算法框架。

1.2 实验知识点

通过本节学习,我们将学习到以下知识点:

  • Pyevolve的算法框架,及使用操作
  • 进化算法的算子操作
  • 可视化结果分析

1.3 实验环境

  • 操作系统:Ubuntu 14.04.5
  • Xfce 终端
  • Python版本:2.7.6

1.4 适合人群

本课程难度为一般,属于初级级别课程。 比较适合以下用户:

  • 熟悉Python基础的用户
  • 对智能算法比较感兴趣的用户
  • 对多维问题优化有兴趣的用户

1.5 代码获取

(如果第一节有操作过,此步略过。)

cd Code
wget http://labfile.oss.aliyuncs.com/courses/776/Code_genetic.zip
unzip Code_genetic.zip

1.6 实验效果




二,开发准备

2.1 virtualenv环境配置

(如果在先前已经进行过,此步略过)

#安装virtualenv
sudo pip install virtualenv
#创建虚拟环境
virtualenv genetic_python
#cd genetic_python/bin/
source ./activate

配置成功之后的效果图:


2.2 在新配置的Python环境中安装相关模块

#由上节环境直接下一节实验进入无需安装
$ sudo apt-get install python-numpy  python-matplotlib

#本节实验需要安装模块
$ sudo pip install pyevolve

2.3 运行示例代码

  • rastrigin函数验证
cd Code/Code_genetic
Python rastrigin_problem.py
  • 皇后问题
cd Code/Code_genetic
Python 8queens.py
  • 提取数据库个体信息并画图
cd Code/Code_genetic
Python pyevole_graph.py -i queens -1

三,实验步骤

3.1 Pyevolve

Pyevolve是一个完全由Python编写的遗传算法模块,特点是: 纯Python语言编写;API接口简单好用;进化过程可视化;可扩展;优化速度快;操作算子全面;有默认参数;开源。

3.1.1 基本概念:
  • Raw score表示适应度函数返回的还未进行比例换算的适应度。
  • Fitness score对Rawscore进行比例换算后的适应度值,如果你使用线性的比例换算(Scaling LinerScaling(),fitness score将会使用线性方法进行换算,fitness score代表了个体与种群的相关程度)
  • Sample genome是所有genome进行复制的基础。
3.1.2 Pyevolve验证

在遗传算法中,经常使用一个函数来测试遗传算法,这个函数就是Rastrigin函数,对于有两个独立变量的 Rastrigin函数,其定义的形式如下:







如函数图所展现的,该函数有非常多的局部极小点,而仅仅只有一个全局最小点,这个点就是[0 0],在这个点处的函数的值为0,所以具有欺骗性,容易陷入局部解。该函数被用来测试遗传算法的主要原因是,对于传统的基于梯度的算法对付这个具有非常多局部极小点的函数来说,那是十分的困难。

所以我们可以通过示例代码验证pyevolve的搜索能力:

cd Code/Code_genetic
Python rastrigin_problem.py

如果不想操作此步,请继续往想看,有更好玩的例子学习呢!

3.2 n-queens 问题

八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。穷举法在问题规模不大的情况下还可以适用,回溯法是求解次问题的经典算法。但N皇后问题是个NP问题,随着皇后数目的增多,求解复杂程度激增,就需要利用人工智能的算法求解。遗传算法在求解一些NP完全问题上得到了广泛的应用。所以可以解决N皇后问题。

3.3 整体流程




3.4 定义目标函数

遗传算法的难处在于如何将一个现实问题转化为函数问题。比如现在我们要解决的N皇后问题。 这里一个比较好的思路是:

(1)用个体表示每一行中皇后的位置genome[i],genome[j]

(2)在函数中用一定的条件标准来表示现实中的规则

(3)函数返回一个正向相关的的数值,数值越大,满足规则的个体越多。

根据皇后问题的描述可知,皇后位置不能出现以下两种情况:

图一所示的,同行或同列。即i = j(同行), genome[i] = genome[j] (同列)。





(图一)

图二所示的,处于同一斜线。即 i !=j and abs(j - i) = abs(genome[j]-genome[i])。

因为我们的个体数目和格子数目相等,所以不会出现有多余的个体占据同一行,而且因为个体各不相同,所以不会出现同一列的情况。

所以最终,问题简化到只考虑棋子处于同一斜线的情况。





(图二)

分析过后,可以定义出皇后问题的描述函数:

首先导入依赖库

from pyevolve import *
from random import shuffle
#可以更改皇后数目,这里以64皇后为例
BOARD_SIZE = 64
def queens_eval(genome):
   #用一个变量表示,碰撞即不满足规则的个体数目
   collisions = 0 
   for i in xrange(0, BOARD_SIZE):
      #如果第i行不在genome中,则返回0
      #可以测试一下随机生成的个体是包含了全部位置信息
      if i not in genome: return 0
   for i in xrange(0, BOARD_SIZE):
      col = False
      #因为随机生成的个体两两不同,所以不会这里可以忽略
      #皇后处于同一行或同一列的情况,只需考虑对角线的情况。
      for j in xrange(0, BOARD_SIZE):
         if (i != j) and (abs(i-j) == abs(genome[j]-genome[i])):
            col = True
      if col == True: collisions +=1
      #返回没有产生碰撞的个体数目。
   return BOARD_SIZE-collisions

初始化个体,这里种群的多样性体现在一组位置列表的排序。 所以,初始化时对个体列表使用shuffle函数进行随机排序。

#对生成的个体进行随机排序
def queens_init(genome, **args):
   genome.genomeList = range(0, BOARD_SIZE)
   shuffle(genome.genomeList)

3.5 Pyevolve主体部分

def run_main(): 

   genome = G1DList.G1DList(BOARD_SIZE)
   #genome实例,这里使用的是1D列表项。
   #参数设置,适应度最大值不超过格子大小,保留两位小数。
   genome.setParams(bestrawscore=BOARD_SIZE, rounddecimal=2)
   #初始化genome,这里是返回随机排序的个体列表。
   genome.initializator.set(queens_init)
   #变异算子:使用“交换”变异
   genome.mutator.set(Mutators.G1DListMutatorSwap)
   #交叉算子
   genome.crossover.set(Crossovers.G1DListCrossoverCutCrossfill)
   #求解器是计算目标函数返回的满足规则的个体数
   genome.evaluator.set(queens_eval)
#简单遗传算法引擎
    ga = GSimpleGA.GSimpleGA(genome)
    #设置结束进行的标准:根据原函数值的收敛情况
    ga.terminationCriteria.set(GSimpleGA.RawScoreCriteria)
    #适应度最大值
    ga.setMinimax(Consts.minimaxType["maximize"])
    ############################################设置遗传参数
    ga.setPopulationSize(100)
    ga.setGenerations(1000)
    ga.setMutationRate(0.02)
    ga.setCrossoverRate(1.0)

3.6 数据可视化分析

将每代个体信息存入数据库,以便后期数据分析:

#queens是数据库中的ID
    sqlite_adapter = DBAdapters.DBSQLite(identify="queens")
    ga.setDBAdapter(sqlite_adapter)

当算法运行结束,所有个体信息都会保存到数据库当中,这时我们可以使用Pyevolve自带的画图模块进行数据分析:

这里需要说明的是,在程序运行完毕之后会在代码目录下生成一个Pyevolve数据库文件。使用pyevolve_graph函数可以对数据库进行操作, pyevolve_graph和数据库文件必须在同一个目录下。

这里还需要对传入参数进行说明:-i queens -1

-i是固定值,不需要改变。

queens是上面代码中设置的ID号,因为同一目录下的Pyevolve文件生成的数据都会保存在Pyevolve数据库文件中,所以用ID区分不同文件产生的数据。

最后一个数字表示不同的图形。 数字从-1 至 -9,每一个数字代表一种图形。就主要的几种图进行说明。

  • -1:画出每一代个体未经线性变换的最大值,最小值和平均值。
  • -2:画出经过线性变换之后个体的最大值,最小值和平均值。
  • -8:热量图,即反映了适应度的集中区域。




以传入参数为 -i queens -1为例:

cd Code/Code_genetic
Python pyevole_graph.py -i queens -1


据图可以看出,随着遗传代数的增加,每一代中个体的最大适应度,最小适应度以及最高适应度都总体趋势是不断增加。即每一代个体(皇后的位置),满足条件的比例越来越大,并最终全部满足而收敛。

3.7 打印信息

最后的一些打印操作:

#设置计划过程中,种群信息显示频率
    #即每隔10代,打印出个体适应度,原值信息
    ga.evolve(freq_stats=10)
    #获取最终的最优个体信息
    best = ga.bestIndividual()
    #打印相关信息
    print "\nBest individual score: %.2f\n" % (best.score,)
    print best

为了更加直观地显示最终结果,可以直接将得到的best值打印到棋子格中。

for i in xrange(BOARD_SIZE):
        print '+----+'+('----+')*(BOARD_SIZE-1)
        for j in xrange(BOARD_SIZE):
            if best[i] == 0:
               line ='| Q  |'+'    |'*(BOARD_SIZE-1)
            else:
               line ='|    |' +'    |'*(best[i]-1)+' Q  |'  +(BOARD_SIZE-1-best[i])*'    |'
        print line
   print '+----+'+('----+')*(BOARD_SIZE-1)

最终效果如实验效果图所示。

四,实验总结

在了解遗传算法流程的基础上,使用pyevolve框架更加容易理解。通过将n皇后问题转化为遗传算法可以操作的个体参数,在n比较大时,遗传算法可以更好地找到较优解。

五, 参考文献: