遗传算法入门(连载1-10)

注:整合by Leytton     原文:http://blog.csdn.net/zzwu/article/category/243066






(连载之一)
.
扎自<游戏编程中的人工智能技术>第三章
清华大学出版社出版


生物只有经过许多世代的不断演化(evolution),才能更好地完成生存与繁衍的任务。遗传算法也遵循同样的方式,需要随着时间的推移不断成长、演化,最后才能收敛,得到针对某类特定问题的一个或多个解。因此,了解一些有关有生命的机体如何演化的知识,对理解遗传算法的演化机制是是有帮助的。本章的开始几页将扼要阐述自然演化的机制(通常称为“湿”演化算法),以及与之相关的术语。即使你当年在中学里对生物并不擅长,也无须担心。本章不会涉及到过深的细节,但对于理解自然演化的基本机制已经足够。抛开以上不论,当你读完本章或下一章后,我想,你也会和我一样,深深叹服自然母亲的令人着迷!

。。从本质上说,任何生物机体不过就是一大堆细胞的集合。每个细胞都包含若干组相同的DNA链,人们一般称之为染色体(chromosome)。染色体中包含的DNA分为两股,这两股DNA链以螺旋状绞合在一起,如下面图3.1所示那样,这就是我们所熟悉的DNA双螺旋结构模型。



图 3.1 DNA双螺旋结构 
 

。。单个的染色体是由称作基因(gene)的更小的结构模块组成,而基因则又由称作核苷酸(nucleotide)的物质组成。核苷酸一共只有四种类型,即:腺嘌呤(thymine)、鸟嘌呤(adenine)、胞嘧啶(cytocine)、胸腺嘧啶(guanine)。它们常简写为T、A、C、G(我不知道为什么?...<一笑>)。这些核苷酸相互连接起来,形成若干很长的基因链,而每个基因编码了生物机体的某种特征,如头发的颜色,耳朵的样子,等。一个基因可能具有的不同设置(如头发的黑色、棕色或金黄色),称为等位基因(allele),它们沿染色体纵向所处的物理部位称为基因的座位(locus)。

。。一个细胞中的染色体组(collection)包含了复制该机体所需的全部信息。这就是克隆怎样实行的秘密。你可以从被克隆施主(donor)身上,哪怕是一个血细胞中包含的信息,复制出整个生物机体,例如一头羊。新的羊将会在每一个方面和施主羊完全相同。染色体的这一集合就称为生物机体的基因组(genome)。在一特殊基因组中等位基因的一种状态称为该机体的遗传类型(genotype)。这些就是用来生成实际的生物机体  - 所谓表现型(phenotype) - 本身的硬编码指令。你和我都是表现型。我们的DNA携带了我们的遗传类型。如将这些术语用到其他领域中,则,设计汽车用的成套蓝图就是一个遗传类型;在生产线上隆隆作响的成品汽车就是一个表现型;只有设计被定型之前的,那些完全阵旧的设计,才勉强称得上是一个基因组。

。。行了,行话说到此已经足够了。现在让我们讨论,怎样把所有这些应用到进化中去。如果你属于偶尔有机会离开计算机屏幕的那种人(因为我的朋友告诉我,我才知道外边还有一个世界呢!),你可能已经注意到,对于千千万万的动物和植物 - 小到只有在显微镜下才能看到的单细胞生物,大到从空间卫星上也能见到的巨大珊瑚礁 - 地球是它们共同的家,不管它们的大小怎样、形状或颜色又怎样。一个生物机体被认为取得了成功,如果它得到了配偶并生下了一个子机体,而后者完全有希望来继续进一步复制自己。

。。为了做到这一点,生物机体必须善长许多工作。例如,能寻找食物和水、能面对掠食者来保卫自己、能使自己吸引潜在的配偶,等。所有这些特长在某种程度上都和生物机体的遗传类型 - 生命的蓝图有关。生物机体的某些基因将会产生有助于它走向成功的属性,而另一些基因则可能要妨碍它取得成功。一个生物的成功的量度就是它的适应性。生物机体愈能适应,它的子孙后代也就愈多。下面转来讨论我们的关键部分...

。。当两个生物机体配对和复制时,它们的染色体相互混合,产生一个由双方基因组成的全新的染色体组。这一过程就叫重组(recombination)或交叠(crossover,又译杂交,交叉,交换)。这样就意味,后代继承的可能大部分是上一代的优良基因,也可能继承了它们不少的不良基因。如果是前一种情况,后代就可能变得比它的父母更能成功(例如,它对掠食者有更强的自卫机制);如为后一种情况,后代甚至就有可能不能再复制自己。这里要着重注意的是,愈能适应的子孙后代就愈有可能继续复制并将其基因传给下一个子孙后代。由此就会显示一种趋向,每一代总是比其父母一代生存和匹配得更完美。

。。作为它的一个很方便得到的例子,我们设想,雌性动物仅仅吸引大眼睛的雄性。这样,在追求雌性配偶的雄性中,眼睛的尺寸愈大,其获得成功的可能性也愈大。你可以说,动物的适应性正比于它的眼睛的直径。因此,你就可以看到,从一个具有不同大小眼睛的雄性群体出发,当动物进化时,在同位基因中,能产生大眼睛雄性动物的基因,相对于产生小眼睛雄性动物的基因,就更有可能被复制到下一代。由此可以推出,当进化几代之后,大眼睛将会在雄性群体占据统治地位。过些时候,你就可以说,生物正在向一种特殊的遗传基因收敛。



-连载1完-


(连载之二)

.
扎自<游戏编程中的人工智能技术>第三章


 清华大学出版社


(本章由zzwu译)

 

   不过,有些读者可能已经会想到,如果这是繁殖期生物机体内唯一发生的事情,那幺即使经历成千上万代后,适应能力最强的成员的眼睛也只能象初始群体中最大的眼睛一样大。而根据我们对自然界的观察中发现,人类或动物的眼睛尺寸实际存在一代大于一代的趋势。之所以会发生这种情况,是因为当基因传递给子孙后代的过程中,会有很小的概率发生差错,从而使基因得到微小的改变。这多少有点象中国古老的耳语传话游戏:在一队人中,把一条消息一个接一个地传递下去;第一个人对着第二个人的耳朵轻声地讲述一个故事,第二个人再轻声地把此故事传向第三个人,等,直到最后那个人再把听到的故事讲出来。通常这都会诺出很多笑话:最后一个人讲出来故事与第一个所讲的已是面目全非。其实,这种类型的差错在把信息从一个系统传递给另一系统时实际都会发生的。图3.2显示的一列图画就是一个令人惊讶的例子。这是一次测试的结果:第一个人画出了一只鸟类的图(见左上角) 交给第二人,第二人看了以后自己重画一个给他的下一个人,这样重复下去,直到最后那个人画出来的图形就会被显著‘异化'。如果你有机会和十几个朋友聚集在一起,我推荐你做一下这个小试验,因为原始图画能有如此多变化似乎难以置信。

有趣的事实
....古代的硬币容易产生这种类型的信息丢失差错。早期厄尔利凯尔特人和条顿人所使用的硬币大量地被假冒着;在早先的原始硬币上能找到一位皇帝的头像(那时已经在许多城市和乡镇用于支付)到后来则变成一匹马或一碗果子的形状了。你一眼就能看出当时使用的假冒货币,无需使用任何高科技的紫外线设备来探测!

 

图 3.2 信息移转的一个试验。 ( Thames 和 Hudson 提供的幻想图形 )


   你可以说,图形或故事的情节在从一个人到另一个人的传递过程中,已经发生了变异 (或突变,mutation) ,同样的变异在生物繁衍过程中会在它们的基因中出现。发生变异的概率通常都很小,但经历大量世代之后变异就会显得很可观。一些变异对生物将是不利的 (这有最大的可能),另一些则对生物的适应性可能没有任何影响,但也有一些则可能会给生物带来一些明显的利益,使它能超过与其同类的生物。在前面我们所讲的例子中,你看到的能使动物引起眼睛直径变大的基因突变就是一种有利的突变,它将使该动物与群体其余动物相比,就好像一个超级的时髦模特儿那样显得突出。这种使眼睛变得越来越大的趋势需要基因参与才能实现。当进化过程经历成千上万代之后,就会使动物长出一对如同盛菜的盘子那样大的眼睛!见图3.3。



图 3.3 一个 Adonis 的进化


   进化机制除了能改进已具备的特征之外,也能产生各种各样全新特征。让我们再以眼睛的进化作为一个例子来说明吧。

   可以设想,曾有一个时期动物就根本没有眼睛。那时,动物在它们的环境中航行完全是靠嗅觉和触觉来躲避掠食它们的动物。他们也相当擅长于这样做,因为他们靠这样已经历了成千上万个世代。在那个时候,鼻子大和手脚长的男性是受女孩子们欢迎的。然而,突然有一天,当两个动物配对时,一个基因突变发生在为皮肤细胞提供的蓝图上。这一突变使其后代在他们的头上发育出了一个具有相当光敏效应的细胞,使其后代能足够识别周围环境是亮的还是暗的。这样就给他带来了一个微小的优点,因为,如果一种肉动物,比如一只鹰,来到了某个范围以内,则它将阻挡了光线,这时,该动物就会感觉得到,就可迅速跑到隐蔽的地方去躲藏起来。另外,这种皮肤细胞还能指示现在是晚上或白天,或告诉他现在是在地面之上或地面之下,这些信息在捕食和吸取营养时都能为它提供方便。你能看到这一新型皮肤细胞将使这一动物与群体中其余的动物相比,具备了稍多的优点,并因此也就有更多的生存和繁殖的机会。过了一段时间,由于进化机制的作用,许多动物的染色体中都会出现具有光敏皮肤细胞的基因。

   现在,如果你再作一些外推,想象这一光敏细胞基因得到了进一步的有利突变,则你能看到,经过许多许多世代后,光敏细胞经过分化形成为一个区域;这个区域不断变大,产生出一些更为确定的特征,例如形成一个晶体,或产生能区别颜色的视觉细胞;还可以想象,一个突变使某个动物由一个光敏区域改变为两个光敏区域,由此就使那个动物有了立体视觉。立体视觉对一个生物体来说是一个巨大的进步,因为这能精确告诉他目标离开他有多远。当然,你也可以把会对眼睛产生不利影响的突变装入同样那些基因。但这里重要的一点是,这样生长出来的后代将不会和已具备改进型眼睛的堂表亲戚们那样取得成功,它们最终将会灭绝。只有成功的基因才会得到继承。你观察自然界中存在的任何特征就能发现,它们的进化都是利用无数微小的突变发展而来的,且它们都是对拥有者有利。难以置信吧?

   这些重组和变异机制说明了进化怎么完成。我希望现在你已经理解,有机体是怎么逐步形成各种不同类型的特征,以帮助它们在其生存环境中取得更大的成功。

-连载2完- 


.

(连载之三)
.
扎自<游戏编程中的人工智能技术>第三章

清华大学出版社

(本章由zzwu译)


3.2.二进制数速成(A Quick Lesson in Binary Numbers)

    当进入更深层的学习之前,我必需确保你对二进制记数系统的理解。如果你已经知道二进制记数的工作原理,可以跳过这一小节。如果你还不了解,就让我来启发你...  

   我认为了解二进制数(基为2的数)的最容易的方法,就是首先查看一下十进制数:你为什么使用十进制数字(基为十的数)和怎样使用十进制计数?  

   人们通常相信,人类之所以采用基数为十的记数法来计数,是因为我们的双手共有十个手指的缘故。设想我们的一个祖先,不妨称他为Ug,几十万年前在计算一个猛犸群中猛犸的数目。Ug利用2个拳头来开始计算,当他每看到一个猛犸,就伸出一个手指;这样1个、2个地继续下去,直到他所有的手指都被用上为止;这样他就知道他已经算到10个猛犸。但因猛犸群中包含的猛犸远远超过10个,Ug不得不再想一种方法来计算更大的数目。他狠抓了一下他的脑袋,就产生了一个想法:叫他的一个朋友Frak来帮忙。Ug想到用Frak的一个手指来代表他计算到的那10个猛犸,然后他自己的手指就得到解脱,可重新开始用来计算第11、12、13个猛犸,等等,直到20,这时就需要使用Frak的另一个手指。你能看出,采用这样的过程,Ug和Frak最多可以计算到110个猛犸(那真是一桩了不起的奇观,不是吗?),但为了统计出更多的猛犸数目,他们就不得不去招募另一位朋友了。  

   当人们最终学会了怎么写出数字时,就是使用类似方法来完成的。为了表示基数为10的数字,你创建一系列的列(columns),每一列代表人的一双手,例如:  

1000位 100位 10位 个位
       

   因此,要计数到15,你先在个位(列)由0开始不断递增,直到9,然后,因个位已不能再增,你就在10位记1,并从新在个位由0开始不断增加,直到如下结束:  

1000位 100位 10位 个位
    1 5

   数字15由一个十位和5个个位组成。(我知道,你听到这些会感到非常显然,但是这种详细的分析是必要的。)你能看到,二进制数系(或不管哪一种进制数系)都用同样的方式工作。但二进制计数时不用10个数字,而只用2个[译注:原文误为1个],其中一个是0,另一个是1。这样,当你在写2进制数时,表示数的列(在2进制数人们称作‘bit')的形式应为:

16 8 4 2 个位
         

   现在你就可以来计算15。首先,你在个位(列)加1,得:

16 8 4 2 个位
        1

 

   这时,因为你已经没有更大的数字可以用了(请记住,2进制数中最大的数是1),你必须增加个位左边的那个列,并将个位数从新变为0,因此数字2的形式如下:

16 8 4 2 个位
      1 0

数字3的形式为:

16 8 4 2 个位
      1 1

数字4的形式为:

16 8 4 2 个位
    1 0 0

 

等等,直到数字15:

16 8 4 2 个位
  1 1 1 1

 

   这就是计算15所要做的全部过程了。至此,你应该能够转换十进制数为二进制,或反过来,把二进制转换为十进制了。我同时必须指出,二进制数字也常常写成一组有固定长度的位,特别当它与计算机联系起来讨论时如此。这就是为什么处理器常被说成是8位、I6位、32位、或64位的原因。这意味,如果你要把15写成8位的二进制,则你就要写成下面这样的形式,其中高位都是0,但也要在前面写出来,以使整个长度达到8:

   00001111

   为了确保你理解这一概念,作为一个练习,在你继续进入下一节以前,试回答下列问题(答案附在本章最后):  

   1.把十进制27转换为二进制。  

   2.把二进制数10101转换为十进制。  

   3.把十进制数135表示成为一个8位的二进制数。  

   不难吧?既然你对二进制数有了一个初步概念,下面就让我们来讨论令人更加激动的内容吧!

 -连载3完-


(连载之四).
.
扎自<游戏编程中的人工智能技术>第三章

 清华大学出版社

(本章由zzwu译)

3.3计算机内的进化( Evolution Inside Your Computer )

    遗传算法的工作过程本质上就是模拟生物的进化过程。首先,要规定一种编码方法,使得你的问题的任何一个潜在可行解都能表示成为一个“数字”染色体。然后,创建一个由随机的染色体组成的初始群体(每个染色体代表了一个不同的候选解),并在一段时期中,以培育适应性最强的个体的办法,让它们进化,在此期间,染色体的某些位置上要加入少量的变异。经过许多世代后,运气好一点,遗传算法将会收敛到一个解。遗传算法不保证一定能得到解,如果有解也不保证找到的是最优解,但只要采用的方法正确,你通常都能为遗传算法编出一个能够很好运行的程序。遗传算法的最大优点就是,你不需要知道怎么去解决一个问题;你需要知道的仅仅是,用怎么的方式对可行解进行编码,使得它能能被遗传算法机制所利用。

   通常,代表可行解的染色体采用一系列的二进制位作为编码。在运行开始时,你创建一个染色体的群体,每个染色体都是一组随机的2进制位。2进制位(即染色体)的长度在整个群体中都是一样的。作为一个例子,长度为 20的染色体的形状如下:

01010010100101001111

   重要的事情就在于,每个染色体都用这样的方式编码成为由 0和1组成的字符串,而它们通过 译码 就能用来表示你手头问题的一个解。这可能是一个很差的解,也可能是一个十分完美的解,但每一个单个的染色体都代表了一个可行解(下面就将讨论有关编码的更多的细节)。初始群体通常都是 很糟的 ,有点象英国板球队或美国足球队(抱歉了!)。但不管怎样,正如我前面说过的那样,一个初始的群体已经创建完成(对这一例子,不妨设共有100个成员),这样,你就可以开始做下面列出的一系列工作(你不用担心用蓝字显示的那些词句,我后面马上就会来解释一切):

 

不断进行下列循环,直到寻找出一个解 :

1.检查每个染色体,看它解决问题的性能怎样,并相应地为它分配一个适应性分数。

2.从当前群体中选出2个成员。被选出的概率正比于染色体的适应性,适应性分数愈高,被选中的可能性也就愈大。常用的方法就是采用所谓的轮盘赌选择法或轮选择法(Roulette wheel selection)。

3.按照预先设定的杂交率(Crossover Rate),从每个选中染色体的一个随机的点上进行杂交(crossover)。

4.按照预定的变异率(mutation rate),通过对被选染色体的位的循环,把相应的位实行翻转(flip)。

5.重复步骤2,3,4,直到100个成员的新群体被创建出来。

结束循环


    以上算法中步骤1 到步骤5 的一次循环称为一个代(或世代,generation)。而我把这整个的循环称作一个时代(epoch) ,在我的正文和代码中将始终都用这样方式来称呼。


3.3.1 什么是轮盘赌选择? ( What's the Roulette Wheel Selection )

    轮盘赌选择是从染色体群体中选择一些成员的方法,被选中的机率和它们的适应性分数成比例,染色体的适应性分数愈高,被选中的概率也愈多。这不保证适应性分数最高的成员一定能选入下一代,仅仅说明它有最大的概率被选中。其工作过程是这样的:

    设想群体全体成员的适当性分数由一张饼图来代表 (见图3.4),这一饼图就和用于赌博的转轮形状一样。我们要为群体中每一染色体指定饼图中一个小块。块的大小与染色体的适应性分数成比例,适应性分数愈高,它在饼图中对应的小块所占面积也愈大。为了选取一个染色体,你要做的,就是旋转这个轮子,并把一个小球抛入其中,让它翻来翻去地跳动,直到轮盘停止时,看小球停止在哪一块上,就选中与它对应的那个染色体。本章后面我就会告诉你怎样来编写这种程序的准确算法。

图 3.4 染色体的轮盘赌式选择


3.3.2 什么是杂交率?( What's the Crossover Rate )

    杂交率就是用来确定 2个染色体进行局部的位(bit)的互换以产生2个新的子代的概率。 实验表明这一数值通常取为0.7左右是理想的,尽管某些问题领域可能需要更高一些或较低一些的值。

    每一次,我们从群体中选择 2个染色体,同时生成其值在0到1之间一个随机数,然后根据此数据的值来确定两个染色体是否要进行杂交。如果数值低于杂交率(0.7)就进行杂交,然后你就沿着染色体的长度随机选择一个位置,并把此位置后面所有的位进行互换。

    例如,设给定的 2个染色体为:

10001001110010010

01010001001000011


    沿着它们的长度你随机选择一个位置,比如说 10,然后互换第10位之后所有位。这样两个染色体就变成了(我已在开始互换的位置加了一个空格):

 

10001001101000011

01010001010010010


3.3.3 什么是变异率?( What's the Mutation Rate? )

    变异率(突变率) 就是在一个染色体中将位实行翻转(flip,即0 变1,1变 0)的几率。这对于二进制编码的基因来说通常都是很低的值,比如 0.001 。

因此,无论你从群体中怎样选择染色体,你首先是检查是否要杂交,然后再从头到尾检查子代染色体的各个位,并按所规定的几率对其中的某些位实行突变(翻转)。

 

3.3.4 怎么搞的啦!( Phew! )

    如果你对上面讲东西感到有些茫然,那也不必担心!从现在开始直到本章结束,所有阅读材料大多数都被设计用来重读两遍。这里有很多需要你理解的新概念,且它们都是相互混杂在一起。我相信对于你这是最好的学习方法。当你通读第一遍时,你有望对一些基本概念得到一些孤立的感性认识,而在阅读第二遍时(如果我做的工作是正确的话),你就能看到各种不同的概念怎样相互联系起来。而当你最后结合源程序来开始编程玩弄时,每一件事物都只是考虑怎样周密地进行安排的问题,到那时你的工作仅仅是一种如何来提炼你的知识和技能的事了(那是比较容易的一部分)。

注意:

     在本书所附的光盘的 Chaper3/Pathfinder 文件夹中,你能找到 Pathfinder 工程的全部源码。

     如果你想在进一步阅读课文之前窥视一下工程的全貌,在 文件 Chaper3 Executable 中有一个预先制作好的执行程序, Pathfinder.exe。

 

-连载4完-



(连载之五)
.
扎自<游戏编程中的人工智能技术>第三章

 清华大学出版社

(本章由zzwu译)

3.4 帮助 Bob 回家( Helping Bob Home )

        由于寻找路径问题被看成是游戏人工智能的一块神圣基石,我们下面就来创建一个遗传算法,用在一个非常简单的场景中解决寻找路径问题。为此,我们将创建一个迷宫,它的左边有一入口,右边有一出口,并有一些障碍物散布在其中。然后在出发点放置一个虚拟的人,我们叫他鲍勃(Bob),然后要为他解决如何寻找路径的问题,使他能找到出口,并避免与所有障碍物相碰撞。下面我将说明怎样来产生Bob的染色体的编码,但首先需要解释怎样来表示迷宫...

   迷宫是一个2D整数型数组;用0来表示开放的空间,1代表墙壁或障碍物,5是起始点,8是出口。因此,整数数组:

.{ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,.
1,0,1,0,0,0,0,0,1,1,1,0,0,0,1,
..5,0,0,0,0,0,0,0,1,1,1,0,0,0,1,
..1,0,0,0,1,1,1,0,0,1,0,0,0,0,1,
..1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,
. 1,1,0,0,1,1,1,0,0,0,0,0,1,0,1, 
..1,0,0,0,0,1,0,0,0,0,1,1,1,0,1,
..1,0,1,1,0,0,0,1,0,0,0,0,0,0,8,
..1,0,1,1,0,0,0,1,0,0,0,0,0,0,1,
.. 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 }

   在屏幕上看起来将会有下面图3.5的样子:


图 3.5 Bob的迷宫,用红色标出了入口和出口

  作者已把这种地图设计方法封装在一个被称作CBobsMap的类中,它定义为:

 class CBobsMap
{           
      
private:
                
   //保存地图用的存储器 (一个2维整型数组)
    static const int map[MAP_HEIGHT][MAP_WIDTH];  
    static const int m_iMapWidth;   //地图的宽度   
    static const int m_iMapHeight;  //地图的高度 
    
   //起始点在数组中的下标        
    static const int m_iStartX;    
    static const int m_iStartY;              
       
   //终点的数组下标       
    static const int m_iEndX;              
    static const int m_iEndY;           

public:              
   //你可以利用这一数组作为 Bob 存储器,如果需要的话 
    int memory[MAP_HEIGHT][MAP_WIDTH];   
   
    CBobsMap() 
    { 
      ResetMemory(); 
    }
             
   //利用一个字符串来记录Bob行进的方向,其中每一个字符代表
   //Bob所走的一步;检查Bob离开出口还有多远; 
   //返回一个与到达出口距离成正比的适应性分数 
    double TestRoute(const vector &vecPath,
                     CBobsMap     &memory);             
                             
   //Render函数利用Windows GDI在一个给定的surface上显示地图
    void Render(const int    cxClient,
                const int    cyClient,
                HDC          surface); 

   //画出能够存放于存储器中的不管什么样的路径 
    void MemoryRender(const int cxClient,
                      const int cyClient.
                      HDC       surface); 
    void ResetMemory(); 
};

.     由上可以看出,我们只需要以常量的形式来保存地图数组以及起点和终点的坐标就行了。这些数据是在文件CBobsMap.cpp 中定义的,在光盘上你能找到它的相关的文件夹。除了存储迷宫的数据外,这个Map类也用来记录Bob在迷宫中所走过的路程:memory[][] 。这对遗传算法本身而言不是本质的,但为了显示目的,使你能看到 Bob 怎样在迷宫中漫游,设置一个记录是必需的。这里重要的是成员函数TestRoute(),它需要利用一系列的行进方向来检测Bob 走了多远。这里我不准备花费时间来列出TestRoute 函数的清单,因为这是十分简单的那种函数,但要列出来却可能需要长长的2页。我们只需要说明一下就行了。给出一个方向向量,它的每个分量能代表向北(North)、向南(South)、向东(East)、向西(West)四个方向之一,让Bob 按照它在地图中行走, TestRoute 计算 Bob 能到达的最远点的位置,然后返回一个适应性分数,它正比于Bob 最终位置离出口的距离。他所到达的位置离开出口愈近,奖励给他的适应性分数也愈高。如果他实际已到达了出口,则我们就要向他表示祝贺了,他将得到满分1。这时循环就会自动结束,因为你已经找到一个解了,可以喊乌拉了!
...再有,不要因为理解不了这个类的任何一点而操心。我们下面马上就会开始讨论有关的每一件事情的。

-连载5完-

.

(连载之六)
.
扎自<游戏编程中的人工智能技术>第三章

 清华大学出版社

(本章由zzwu译)

 

3.4.1为染色体编码
(Ecoding the Chromosome)

          
   每个染色体必须把小人Bob 的每一个行动编入代码中。Bob的行动仅限为4个方向向东(East),向南(South),向西(West),向北(North)
    
故编码后的染色体应该就是代表这4个方向信息的一个字符串。传统的编码方法就是把方
向变换成二进制的代码。四个方向只要2位就够了,例如下表所示的那样:
      
二进制代码 十进制译码 代表的方向
00 0 向北
01 1 向南
10 2 向东
11 3 向西

 这样,如果你得到了一个随机的二进制字符串,你就能将它译码出Bob行动时所遵循的

一系列方向。例如染色体:

                    111110011011101110010101

代表的基因就是
            11,11,10,01,10,11,10,11,10,01,01,01
			  
当把二进制代码译成十进制时,就成为
            3,3,2,1,2,3,2,3,2,1,1,1

再把这些放进一个表格中,就可以使你相信这是一样的一些概念: 
        
二进制代码 十进制译码 代表的方向
11 3 West
11 3 West
10 2 East
01 1 South
10 2 East
11 3 West
10 2 East
11 3 West
10 2 East
01 1 South
01 1 South
01 1 South

      到此,你要做的全部就是将Bob置于迷宫的起点,然后告诉他根据这张表所列的方向一步步地走。如果按某一个方向前进将使Bob碰到墙壁或障碍物,则只需忽略该方向并继续按下一个方向去走就行了。这样不断下去,直到所有方向用光或Bob到达出口时为止。
    
    如果你想象有几百个这样的随机的染色体,你就能看到它们中的某些可能为Bob译码出到达出口的一套方向(问题的一个解),但它们中的大多数将是失败的。
    
    遗传算法以随机的2进制串(染色体)作为初始群体,测试它们每一个能让Bob走到离开出口有多么接近,然后让其中最好的那些来孵化后代,期望它们的子孙中能有比Bob走得离出口更近一点。这样继续下去,直到找出一个解,或直到Bob绝望地在一个角落里被粘住不动为止(你将看到,这种情况是可能发生的)。
      
    因此,我们应定义一种结构,其中包含一个2进制位串(染色体),以及一个与该染色体相联系的适应性分数。我把这个结构称为SGenome结构,它的定义如下:  
  

struct SGenome    {      vector <int>  vecBits;      double   dFitness;      SGenome():dFitness(0){}      SGenome(const int num_bits):dFitness(0)      {        //创造随机二进制位串        for (int i=0; i<num_bits; ++i)          {             vecBits.push_back(RandInt(0,1));          }      }   };     
正如你能见到的那样,如果你在创建SGenome对象时把一个整型数作为参数传递给构造函数,则它就会自动创建一个以此整数为长度的随机2进制位串,
并将其适应性分数初始化为零,这样就把基因组什么都准备好了。
  
程序注释
    std::vectorSTL(Standard Templete Library)标准模板库的一部分, 这是一种为处理动态数组而预先建立好的类。如果要把数据加入STL中,可使用
push_back()方法。

下面是一个简单的例子:
  
  #include <vector>   for (int i=0; i<10; i++)     {         MyFirstVector.push_back(i);             cout << endl << MyFirstVector[i];     }
要清空一个向量,使用clear()方法
        MyFirstVector.clear();

你可利用size()方法来得到向量中元素的数目

        NyFirstVector.size()

就是这样。
  
    不需要你去考虑内存管理问题-std::vector能够为你来做所有这些!当需要时,我会在整个程序中使用它。

    SGenome结构中不具备怎样为染色体(vecBits)进行译码的知识; 这是需要由遗传算法类自己来完成的一项任务。现在让我们来快速窥视一下这个类的定义。
我已把它称作CgaBob类(有时我对我的原始创见自己也很吃惊,但我确实是这样做的)。
class CgaBob
{
private:
//基因组群体
vector<SGenome> m_vecGenomes
//群体的大小
int m_iPopSize
double m_dCrossoverRate;
double m_dMutationRate;
//每个染色体含有多少bits
int m_iChromoLength;
//每个基因有多少bits
int m_iGeneLength;
int m_iFittestGenome;
double m_dBestFitnessScore;
double m_dTotalFitnessScore;
int m_iGeneration;
//为 map 类创建一个实例
CBobsMap m_BobsMap;
//另一个CbobsMap对象用来保存每一代的最佳路径的一个记
//录,这是被访问小格的一个数组,它仅仅是为了显示目的而使用的。
CBobsMap m_BobsBrain;
//让你知道运行是否仍在进行中
bool m_bBusy;
void Mutate(vector<int>&vecBits);
void Crossover(const vector<int>&mum,  
               const vector<int>&dad,  
               vector<int>&baby1,
               vector<int>&baby2);
SGenome& RouletteWheel Selection();
//用新的适应性分数来更新基因组原有的适
//应性分数,并计算群体的最高适应性分数和适应性分数最高的那个成员。
void UpdateFitnessScores();
//把一个位向量译成为一个方向的(整数)向量
vector<int> Decode(const vector<int> &bits);
//把一个位向量变换为十进制数。用于译码
int BinToInt(const vector<int> &v);
//创建一个随机的二进制位串的初始群体
void CreateStartPopulation();
public:
CgaBob(double cross_rat,
        double mut_rat,
       int pop_size,
       int num_bits,
      int gene_len):m_dCrossoverRate(cross_rat),
                      m_dMutationRate(mut_rat),
                      m_iPopSize(pop_size),
                      m_iChromoLength(num_bits),
                      m_dTotalFitnessScore(0.0),
                      m_iGeneration(0),
                      m_iGeneLength(gene_len),
                      m_bBusy(false)
{
  CreateStartPopulation();
}
void Run(HNND hwnd);
void Epoch();
void Render(int cxClient, int cyClient, HDC surface);
//访问用的方法
int Generation(){return m_iGeneration;}
int GetFittest(){return m_iFittestGenome;}
bool Started(){return m_bBusy;}
void Stop(){m_bBusy = false;}
};

由上你可看出,当这个类的一个实例被创建时,构造函数初始化所有的变量,并调用CreateStartPopulation()。这一短小函数创建了所需数量的基因组群体。

每个基因组一开始包含的是一个由随机2进制位串组成的染色体,其适应性分数则被设置为零。


-连载6完-


(连载之七)
.
扎自<游戏编程中的人工智能技术>第三章
.
 清华大学出版社
.

3.4.2 Epoch (时代)
        
    遗传算法类中最烩灵人口的内容就是 Epoch()方法。这就是我们前面3.3节讲过的遗传算法的那个循环。它是这个类的工作部门(workhorse)。这一方法与所有工作或多或少都有连系。下面就让我们来更近距离地考察它 ... 
 
 void CgaBob::epoch()  
 {  
   UpdateFitnessScores();  

    在每一个 epoch 循环内所要做的第一件事情,就是测试染色体群中每一个成员的适应性分数。 UpdateFitnessScores() 是用来对每个基因组的二进制染色体编码进行译码的函数,而由它再把译码所得到的一系列结果,也就是由代表东、南、西、北四个方向的整数,发送给 CBobsMap::TestRoute 。后者检查Bob在地图中游走了多远,并根据Bob离开出口的最终距离,返回一个相应的适应性分数。让我通过很少几行源码来告诉你怎样计算Bob的适应性分数: 
  
   int DiffX = abs(posX - m_iEndX);  
   int DiffY = abs(posY - m_iEndY); 
      
    这里,DiffX和DiffY 就是Bob所在格子相对于迷宫出口的水平和垂直偏离值。试考察图 3.6 的例子。灰色小格代表Bob通过迷宫的路程,上面写着B的小格是他最终所到达的地方。在这一位置上,Diffx = 3,而 DiffY = 0。
    return 1/(double)(DiffX+DiffY+1); 
    
    上面一行程序就是计算Bob的适应性分数,它把DiffX,DiffY这两个数字加起来,然后求倒数。DiffX,DiffY的和中还加了一个1,这是为了避免分母出现0的错误。如果Bob到达出口,DiffX+DiffY=0.
    UpdateFitnessScores 也保持对每一代适应性分数最高的基因组以及所有与基因组相关的适应性分数的跟踪。这些数值在执行赌轮选择要使用。

图 3.6 Bob尝试寻找迷宫出口 

      这最后一行式子就是计算 Bob 的适应性分数。它把 DiffX与DiffY 两个数字加起来然后求倒数。DiffX与DiffY的和数中还加了一个1,这是为了确保除法不会出现一个分母为零的错误,如果 Bob到达出口,Diffx + DiffY = 0。

     UpdateFitnessScores 也保持对每一代里适应分最高的基因组、以及与所有基因组相关的适应性分数的跟踪。这些数值在执行轮盘赌选择时需要使用。到此,你已经知道了函数 UpdateFitnessScores() 所做的全部工作,让我们回到 Epoch 函数的讨论 ...

     由于在每一个Epoch中都需要创建一个新的基因组群,因此,当它们在创建出来时(每次2个基因组),我们需要寻找一些地方来保存它们。

   //现在创建一个新的群体

   int NewBabies = 0;   

  //为婴儿基因组创建存储器  

   vector<SGenome> vecBabyGenomes;

现在继续讨论遗传算法循环中所处理的各种事务。

   while (NewBabies < m_iPopSize)    

   {

    //用轮盘赌法选择 2 个上辈(parents)

     SGenome mum = RouletteWheelSelection();   

     SGenome dad = RouletteWheelSelection();

    在每次迭代过程中,我们需要选择 2 个基因组来作为 2 个新生婴儿的染色体的上辈。我今后常喜欢把这2个上辈分别称为 dad (父亲)和 mum (母亲)因为他们将来就是要生孩子的)。你应该回忆得起来,一个基因组的适应性愈强,则由轮盘赌方法选择作为父母的几率也愈大。

    //杂交操作    

     SGenome baby1, baby2; 

     Crossover(mum.vecBits ,dad.vecBits, baby1.vecBits, baby2.vecBits);

    以上2行的工作是:创建 2 个空白基因组,这就是2个婴儿;它们与所选的上辈一起传递给杂交函数Crossover() 。这一函数执行了杂交(需要依赖于所设杂交率m_dCrossoverRate来进行),并把新的染色体的2进制位串存放到2个新生婴儿 baby1和baby2之中。

   // 变异操作  

    Mutate(baby1.vecBits);  

    Mutate(baby2.vecBits);

    以上这 2 步是对婴儿实行突变!这听起来可怕,但这对他们是有利的。一个婴儿的位的突变概率依赖于所选的参数 m_dMutationRate(突变率)。

   // 把2个新生因个婴儿加入新群体 

    vecBabyGenomes.push_back(baby1); 

    vecBabyGenomes.push_back(baby2); 

    NewBabies += 2; 

  }

    这 2 个新生后代最终要加入到新的群体中,这样就完成了一次 Loop 的迭代过程。这一过程需要不断重复,直到创建出来的后代总量和初始群体的大小相同。

   // 把所有婴儿复制到初始群体 

    m_vecGenomes = vecBabyGenomes; 

   // 代的计数加1 

   ++m_iGeneration; 

 } 

    这里,原有的那个群体由新生一代所组成的群体来代替,并把代的计数器加1,以跟踪当前的代。就是这么一些了!呵呵,不难吧?

    这一 Epoch函数将无止境地重复,直到染色体收敛到了一个解,或到用户要求停止时为止。下面我将会向你显示上述各种操作(算子)的代码,但在此首先让我们来聊聊,应该如何确定使用的参数值。

-连载7完- 
 

(连载之八)
.
扎自<游戏编程中的人工智能技术>第三章

 清华大学出版社

 3.4.3 参数值的选取
(Choosing the Parameter Value )

。。你可能想了解我是如何知道需要采用这些变量初值?这可是价值百万美元的问题,因至今尚未有快速有效的规则能确定这些值,有的只是一些原则性的指导。而且,我已把程序用到的所有参数存放在文件defines.h 中了。这些参数中大多数将是一目了然的,但有其中几个我想说明一下,即  
       
       #define CROSSOVER_RATE 0.7   
       #define MUTATION_RATE  0.001   
       #define POP_SIZE       140   
       #define CHROMO_LENGTH  70   
       
选择这些值最终还得归结为每个人对遗传算法所得到的“感觉”,你只能通过自己的编程实践、用各种不同的参数值进行调试、看结果会发生什么,并从中选取适合的值。不同的问题需要不同的值,但是通常来说,如果你在使用二进制编码的染色体,则把杂交率设定为O.7,变异率设为0.001,将是很好的初始缺省值。而确定群体大小的一条有用规则是将基因组的数目取为染色体长度的2倍。   
	
。。因 70表示 Bob 的 35步的最大可能移动数目,所以这里选择70作为染色体的长度,它比 Bob 为穿越地图到达出口所需的步数还要多一些。当你学习了以后几章的方法后可以使遗传算法变得更为有效,到时你就能将这个长度减少下来。 
        
历史的注释 
。。遗传算法是 John Holland大脑的产物,早在上个世纪60年代,他已提出这种想法。但不可思议的是,他没有感到需要在计算机上实际试验出结果,而宁愿利用笔和纸来作修修补补的工作! 直到后来他的一名学生编写出程序并在一台个人计算机上运行后,才使人们终于看到在软件中利用他的思想能够得到什么。 
3.4.4 算子函数(The Operator Functions) 。。我们现在从头到尾来考察一遍遗传算法的各种操作(或称算子)函数-选择、杂交、变异-的代码。尽管很简单,但与你一起通读一遍源码能给你重温一次这些函数的机会。这可使你在了解遗传算法的知识时对它们具有更确切的认识。 3.4.4.1重温轮盘赌选择 (Roulette Whell Selection Revisited ) 让我们从轮盘赌选择算法开始。请记住,这一个函数的功能是从群体中选择一个基因组,选中的几率正比于基因组的适应性分数。 SGenome& CgaBob::RouletteWheelSelection() { double fSlice = RandFloat()*m_dTotalFitnessScore; 。。我们从零到整个适应分范围内随机选取了一实数fSlice 。我喜欢把此数看作整个适应性分数饼图中的一块,如早先在图3.4中所示。 [但并不是其中一块,译注] double cfTotal = O; int SelectedGenome = 0; for (int i=O; i<m_iPopSize; ++i) { cfTotal += m_vecGenomes[i].dFitness; if (cfTotal > fSlice) { SelectedGenome = i; break; } } return m_vecGenomes[SelectedGenome]; } 。。现在,程序通过循环来考察各基因组,把它们相应的适应性分数一个一个累加起来,直到这一 部分累加和 大于 fSlice 值时,就返回该基因组。就是这样简单。 3.4.4.2 重温杂交操作(Crossover Revisited) 。。这一函数要求2个染色体在同一随机位置上断裂开,然后将它们在断开点以后的部分进行互换,以形成 2 个新的染色体 ( 子代 ) 。 void CgaBob::Crossover ( const vector<int> &mum, const vector<int> &dad, vector<int> &baby1, vector<int> &baby2) { 这一函数共传入 4 个参数,参数传递均采用引用( reference )方式,其中前2 个传入父辈parent 的染色体(别忘记 , 染色体只是一个整数型的矢量std::vector ),后 2 个则是用来copy 子代染色体的空矢量。 if ( (RandFloat() > m_dCrossoverRate) || (mum == dad) ) { baby1 = mum; baby2 = dad; return; } 这里,首先是进行检测,看 mumdad 两个上辈是否需要进行杂交。杂交发生的概率是由参数 m_dCrossoverRate 确定。如果不发生杂交,则2个上辈染色体不作任何改变地就直接复制为子代,函数立即返回。 int cp = RandInt(0, m_iChromoLength - 1) ; 。。沿染色体的长度随机选择一个点来裂开染色体。 for (int i=0; i<cp; i++) { baby1.push_back(mum[i]); baby2.push_back(dad[i]); } for (i=cp; i<mum.size(); i++) { baby1.push_back(dad[i]); baby2.push_back(mum[i]); 这两个小循环把 2 个 parent 染色体在杂交点( CP,crossover point )以后的所有位进行了互换,并把新的染色体赋给了 2 个子代 : baby1 和 baby2 。 3.4.4.3 重温变异操作(Mutation Revisited) 这一函数所做的工作,不过就是沿着一个染色体的长度,一bit一bit地进行考察,并按m_dMutationRate给定的几率,将其中某些bit实行翻转。 void CgaBob::Mutate(vector<int> &vecBits) { for (int curBit=0; curBit<vecBits.size(); curBit++) { //是否要翻转此bit? If (RandFloat() < m_dMutationRate) ( //是,就翻转此bit vecBits[curBit] = !vecBits[curBit]; } }//移到下一个bit } 。。就是这些了。你的第一遗传算法程序也就这样完成了!下面让我花一些时间来说明一下,当你在运行 Pathfinder 程序时,你能看到些什么?
-连载8完-

(连载之九)


.
扎自<游戏编程中的人工智能技术>第三章


 清华大学出版社


3.4.5 运行找路径者程序 (Running the Pathfinder Program)

.......当你运行 Pathfinder 程序时,你将看到,程序不是每次都能找到一条通往出口的路径。 Bob 有时会被粘住在一个局部地区不确定地逗来逗去,如同一个喝醉了酒的人在试着寻找他的回家的路。这主要由于群体太快地收敛到一个特殊类型的染色体。这样,由于群体中的成员变得如此相似, crossover 算子的有益效应这时实际上已经不能发挥作用,所有发生的事情都是靠总量很少的变异 操作在起作用 。但因变异率设置很低,当染色体类型的差异消失后,仅仅依靠变异本身已不足以去发现一个解。另外,由于轮盘赌选择的工作方式,使得任何一代的最合适的染色体无法保证传到下一代。 这意味着,只要在适当时候,立即杀死这个成员,遗传算法就能在群体中找到一个几乎完全的解,但在这样做时,它将失去它所拥有的所有好的基因!在后面的章节中,我 将会谈到这些问题,并介绍一些技术来帮助维护基因组的差异性且同时能保留那些较好的基因组。但在这里,我首先需要花费一些时间来考察不同的编码方法,并考察它们怎样和你可能遇到的问题类型关联在一起。这就是我在下一个章中将要做的事情。 

3.4.6 二进制数转换问题的答案[见第98页]

 

l. .11011

2. .21

3.5 练习题(Stuff to Try)

 

........从现在开始,在每一章的后面,我都要给你出一些点子,让你按照它来编写出相应的游戏程序。我不强调这些程序本身有多么重要。但这是唯一能使你对那些算法产生“感性”认识的方法。而且,当你开始去做复杂的题目时,这种“感性”认识将变得非常重要。

1.  为杂交率,突变率,群体尺寸,染色体长度等参数设置各种不同的值来进行试验,观察它们对算法的效率有什么影响?

2.  试去掉杂交操作,而增加突变率,看会发生什么结果?如果单用杂交操作,而不利用突变,又会发生什么?

3. 修改适应性分数的计算函数,使多次进入同一小格的染色体得到惩罚。这应该导致更有效的到出口的路径。

4. 你能想到另外的什么办法使路径的寻找更为有效吗?

-连载9完-

 

预告:下一次的最后连载包括下列内容:

。。* 提供本连载所介绍内容的完整的VC++源程序和执行程序的下载。

。。* 介绍《游戏编程中的人工智能技术》全书内容和国外网站对它的评价。

。。* 提供原书后面所附遗传算法的参考文献,包括书、文章、以及相关网站。

。。* 设立了一个简单的调查表,以了解各位对这样的连载有什么看法,并确定

         今后是否要搞类似的连载以及如何改进,等。



(连载之10)

..

这是《遗传算法入门》连载的最后一篇,将对连载来源进行一些说明。

0.本连载来自《游戏编程中的人工智能技术》一书,是该书第三章一章的基本上完整的内容。

1.全书介绍遗传算法(GA)和神经网络(NN)等人工智能技术的原理,以及它们在游戏编程中的应用。

2.<游戏编程中的人工智能技术> 目录(这是我交给出版社的原始手稿目录)如下

译者序 ………………………………………………………………………………… i
前言 ……………………………………………………………………………………vi
致谢 ……………………………………………………………………………………ix
关于作者 ………………………………………………………………………………ix
目录 ……………………………………………………………………………………xi
丛书编辑者的来信 ……………………………………………………………………xx
引言 …………………………………………………………………………………xxii


第一部分 Windows编程


第1章 Windows编程初步 ………………………………………………………2

1.1 历史一瞥 ………………………………………………………………………… 2
  1.1.1 Windows 1.0 …………………………………………………………………3
  1.1.2 Windows 2.0 …………………………………………………………………3
  1.1.3 Windows 3.0和3.1 ………………………………………………………… 3
  1.1.4 Windows 9.5 …………………………………………………………………4
  1.1.5 Windows 9.8.及其后续版本 ……………………………………………… 5
1.2 Hello World! …………………………………………………………………… 5
1.3 编写第一个Windows程序 …………………………………………………………6
  1.3.1 匈牙利表示法 ……………………………………………………………… 9
  1.3.2 第一个窗口 …………………………………………………………………11
    1.3.2.1 注册你的窗口 …………………………………………………………12
    1.3.2.2 创建窗口 ………………………………………………………………15
  1.3.3 Windows消息循环(Message Pump)…………………………………………18
  1.3.4 Windows过程(Windows Procedure)……………………………………… 21
    1.3.4.1 WM_CREATE消息 ……………………………………………………… 23
    1.3.4.2 WM_PAINT消息 …………………………………………………………24
    1.3.4.3 WM_DESTROY消息 ………………………………………………………26
    1.3.4.4 下面怎样办呢?……………………………………………………… 27
  1.3.5 键盘输入 ……………………………………………………………………27
    1.3.5.1 虚拟键代码(Virtual Key Codes)……………………………………27
  1.3.6 嗒的嗒!…………………………………………………………………… 29

第2章 Windows编程进阶………………………………………………………30

2.1 Windows图形设备接口(GDI)…………………………………………………… 30
  2.1.1 设备描述表(Device Context, DC)……………………………………… 31
    2.1.1.1 怎样得到句柄(Handle)呢?………………………………………… 31
  2.1.2 各种绘图工具:画笔、画刷、颜色、线和形状 …………………………33
    2.1.2.1 自定义画笔(Pen)………………………………………………………37
    2.1.2.2 画刷(Brushes) ……………………………………………………… 40
    2.1.2.3 形状(Shapes)… ………………………………………………………42
2.2 文本(Text)………………… ……………………………………………………46
  2.2.1 TextOut …………………………………………………………………… 47
  2.2.2 DrawText ……………………………………………………………………47
  2.2.3 加入颜色(color)和透明度(Transparency)………………………………48
  2.2.4 实时消息抽取循环 …………………………………………………………49
2.3 如何创建后备缓冲区? … ………………………………………………………51
  2.3.1 这听上去很棒,但怎样来实现呢?……………………………………… 53
  2.3.2 我怎样来使用后备缓冲器呢?…………………………………………… 55
  2.3.3 保持干净(Tidy)…………………………………………………………… 57
2.4 使用资源(Resources)……………………………………………………………59
  2.4.1 图标(Icons)…………………………………………………………………60
  2.4.2 光标(Cursors)………………………………………………………………61
  2.4.3 菜单(Menu)………………………………………………………………… 62
  2.4.4 为菜单添加具体功能 ………………………………………………………63
2.5 对话框(Dialog Boxes)………………………………………………………… 65
  2.5.1 一个简单的对话框 …………………………………………………………66
  2.5.2 一些更有用的知识 …………………………………………………………68
2.6 正确定时(Timing)……………………………………………………………… 73
2.7 结束了!………………………………………………………………………… 74


第二部分 遗传算法

第3章 遗传算法入门 …………………………………………………………76

3.1 鸟和蜜蜂………………………………………………………………………… 76
3.2 二进制数速成…………………………………………………………………… 81
3.3 计算机内的进化………………………………………………………………… 83
  3.3.1 什么是赌轮选择(Roulette Wheel Selection)? ………………………84
  3.3.2 什么是杂交率(Crossover Rate)? ………………………………………85
  3.3.3 什么是变异率(Mutation Rate)? ……………………………………… 85
  3.3.4 咂搞的呀!………………………………………………………………… 85
3.4 帮助Bob找回家 ………………………………………………………………… 86
  3.4.1 为染色体编码 ………………………………………………………………88
  3.4.2 Epoch方法 ………………………………………………………………… 93
  3.4.3 选取参数值 …………………………………………………………………95
  3.4.4 算子函数(Operator Functions)………………………………………… 96
    3.4.4.1 重温赌轮选择 …………………………………………………………96
    3.4.4.2 重温杂交(Crossover)算子 ………………………………………… 97
    3.4.4.3 重温变异(Mutation)算子 ……………………………………………98
  3.4.5 运行寻路人(Pathfinder)程序 ……………………………………………99
3.5 练习题 ……………………………………………………………………………99

第4章 置换码与巡回销售员问题 ……………………………………………100

4.1 巡回销售员问题(TSP)………………………………………………………… 100
  4.1.1 小心陷阱 ………………………………………………………………… 102
  4.1.2  CmapTSP,SGenome,CgaTSP………………………………………… 104
    4.1.2.1 CmapTSP类 ……………………………………………………………104
    4.1.2.2 SGenome结构 …………………………………………………………107
    4.1.2.3 CgaTSP类 …………………………………………………………… 109
4.2 置换变异算子(Permutation Mutation Operator)………………………… 111
4.3 置换杂交算子(Permutation Crossover Operator)…………………………115
4.4 挑选一个适应性函数 ………………………………………………………… 116
4.5 选择 (Selection) …………………………………………………………… 118
4.6 把一切组装在一起 …………………………………………………………… 119
  4.6.1 #defines文件 …………………………………………………………… 120
4.7 总结 …………………………………………………………………………… 121
4.8 练习 …………………………………………………………………………… 122

第5章 遗传算法优化 ………………………………………………………… 123

5.1 TSP用的各种算子……………………………………………………………… 124
  5.1.1 各种置换变异算子…………………………………………………………124
    5.1.1.2 移位变异(Displacement Mutation,DM)………………………… 127
    5.1.1.3 插入变异 (Insertion Mutation,IM) ……………………………128
    5.1.1.4 倒置变异 (Inversion Mutation,IVM) ………………………… 129
    5.1.1.5 倒置移位变异(Displaced Inversion Mutation,DIM)………… 129
  5.1.2 各种置换杂交算子 ……………………………………………………… 130
    5.1.2.1 基于顺序的杂交(Order-Based Crossover,OBX)…………………130
    5.1.2.2 基于位置的杂交(Position-Based Crossover,PBX)…………… 133
5.2 各种处理工具 ………………………………………………………………… 136
  5.2.1 选择(Selection)技术 ……………………………………………………137
    5.2.1.1 精英选择(Elitism)………………………………………………… 138
    5.2.1.2 稳态选择(Steady State Selection)………………………………138
    5.2.1.3 适应性比例选择(Fitness Proportionate Selection)………… 138
    5.2.1.4 赌轮选择(Roulette Wheel Selection)……………………………138
    5.2.1.5 随机遍及取样(Stochastic Universal Sampling)……………… 139
    5.2.1.6 锦标赛选择(Tournament Selection)………………………………140
  5.2.2 变比技术(Scaling Techniques)…………………………………………142
    5.2.2.1 排名变比(Rank Scaling)……………………………………………142
    5.2.2.2 西格玛变比(Sigma Scaling)……………………………………… 143
    5.2.2.3 波兹曼变比(Boltzmann Scaling)………………………………… 146
  5.2.3 其它杂交算子 …………………………………………………………… 147
    5.2.3.1 单点杂交(Single-Point Crossover)………………………………147
    5.2.3.2 两点杂交(Two-Point Crossover)………………………………… 147
    5.2.3.3 多点杂交(Multi-Point Crossover)……………………………… 148
  5.2.4 子群技术(Niching Techniques)…………………………………………150
5.3 总结 …………………………………………………………………………… 151
5.4 练习 …………………………………………………………………………… 151

第6章 登月也不难………………………………………………………………152

6.1 创建和处理矢量图形 ………………………………………………………… 153
  6.1.1 顶点和顶点缓冲 ………………………………………………………… 153
  6.1.2 顶点变换 ………………………………………………………………… 155
    6.1.2.1 平移(Translation)………………………………………………… 156
    6.1.2.2 变比(Scaling)……………………………………………………… 157
    6.1.2.3 旋转(Rotation)………………………………………………………157
    6.1.2.4 综合运用 …………………………………………………………… 159
  6.1.3 矩阵魔法(Matrix Magic)…………………………………………………161
    6.1.3.1 矩阵究竟是什么?……………………………………………………161
    6.1.3.2 矩阵的乘法 ………………………………………………………… 162
    6.1.3.3 单位矩阵 …………………………………………………………… 163
    6.1.3.4 用矩阵变换顶点 …………………………………………………… 163
    6.1.3.5 奇妙部分来了 ……………………………………………………… 165
6.2 矢量是什么?……………………………………………………………………166
  6.2.1 矢量加、减法 …………………………………………………………… 168
  6.2.2 计算矢量大小 …………………………………………………………… 169
  6.2.3 矢量的数量乘 …………………………………………………………… 170
  6.2.5 矢量的分解(投影)…………………………………………………………171
  6.2.6 奇妙的点积(Dot Product)……………………………………………… 172
  6.2.7 SVector2.D实用工具类 ………………………………………………… 173
6.3 绝顶聪明的牛顿 ……………………………………………………………… 173
  6.3.1 时间(Time)…………………………………………………………………174
  6.3.2 长度(Length)………………………………………………………………175
  6.3.3 质量(Mass)…………………………………………………………………175
  6.3.4 力(Force)………………………………………………………………… 176
  6.3.5 运动-速度(Velocity)……………………………………………………177
  6.3.6 运动-加速度(Acceleration)……………………………………………178
  6.3.7 感觉到了力,真快活 …………………………………………………… 180
  6.3.8 引力(Gravity)…………………………………………………………… 180
6.4 登月工程-人控制的 ………………………………………………………… 181
  6.4.1 Ccontroller类的定义 ……………………………………………………182
  6.4.2 CLander类的定义 …………………………………………………………183
  6.4.3 UpdateShip函数 ………………………………………………………… 185
6.5 遗传算法控制的登月艇 ……………………………………………………… 191
  6.5.1 为基因组编码 …………………………………………………………… 191
  6.5.2 杂交和变异操作 ………………………………………………………… 193
  6.5.3 适应性函数(Fitness Function)…………………………………………194
  6.5.4 更新函数(Update Function)…………………………………………… 196
  6.5.5 运行程序 ………………………………………………………………… 199
6.6 小结 …………………………………………………………………………… 199
6.7 习题 …………………………………………………………………………… 199


第三部分 神经网络

第7章 用平常语言讲神经网络…………………………………………………202

7.1 神经网络介绍……………………………………………………………………202
7.2 一个生物学的神经网络-大脑 …………………………………………………203
7.3 数字版的神经网络 …………………………………………………………… 206
  7.3.1 现在需要一些数学了 …………………………………………………… 208
  7.3.2 我知道什么是神经细胞了,但用它来干什么呢?………………………210
7.4 聪明的扫雷机工程 …………………………………………………………… 212
  7.4.1 选择输出 ………………………………………………………………… 213
  7.4.2 选择输入 ………………………………………………………………… 215
  7.4.3 隐藏的神经细胞要多少?…………………………………………………216
  7.4.4 CNeuralNeth文件 …………………………………………………………217
    7.4.4.1 SNeuron结构  ……………………………………………………… 217
    7.4.4.2 SNeuronLayer结构 ………………………………………………… 219
    7.4.4.3 CNeuralNet类 ……………………………………………………… 219
  7.4.5 神经网络的编码 ………………………………………………………… 224
  7.4.6 遗传算法 ………………………………………………………………… 225
  7.4.7 扫雷机类(CMinesweeper Class)…………………………………………226
    7.4.7.1 CMinesweeper::Update函数 ……………………………………… 228
  7.4.8 CController类 ……………………………………………………………230
    7.4.8.1 CController::Update方法 ……………………………………………233
  7.4.9 运行此程序 ……………………………………………………………… 235
    7.4.1.0 功能的两个改进 …………………………………………………… 236
    7.4.1.1 改进一 ……………………………………………………………… 236
    7.4.1.2 改进二 ……………………………………………………………… 239
7.5 最后说几句 …………………………………………………………………… 241
7.6 练习题 ………………………………………………………………………… 241

第8章 为你的Bot提供知觉器 …………………………………………………242

8.1 回避障碍物 …………………………………………………………………… 243
  8.1.1 认识环境 ………………………………………………………………… 243
  8.1.2 适应性函数 ……………………………………………………………… 246
8.2 为您的Bot提供一个记忆器(memory)………………………………………… 248
  8.2.1 适应性函数 ……………………………………………………………… 255
  8.3 本章小结 …………………………………………………………………… 256
  8.4 练习题 ……………………………………………………………………… 257

第9章 有监督的训练方法 …………………………………………………… 258

9.1 异或函数(XOR Function)………………………………………………………258
  9.1.1 反向传播(BP)怎么工作?…………………………………………………259
    9.1.1.1 调整输出层的权重 ………………………………………………… 261
    9.1.1.2 为隐藏层调整权重 ………………………………………………… 262
    9.1.1.3 一个例子 …………………………………………………………… 262
    9.1.1.4 改变成CNeuralNet代码 …………………………………………… 265
9.2  RecognizeIt - 鼠标手势的识别 ……………………………………………270
  9.2.1 用向量来表示一个手势 ………………………………………………… 271
  9.2.2 训练网络(Training the Network)………………………………………273
  9.2.3 记录并变换鼠标数据 …………………………………………………… 275
  9.2.4 增加新的手势 …………………………………………………………… 277
  9.2.5 CController类 ……………………………………………………………278
9.3 一些有用的技术和窍门 ……………………………………………………… 281
  9.3.1 增加动量(Momentum)………………………………………………………281
  9.3.2 过拟合(Over Fitting)……………………………………………………282
  9.3.3 柔性最大激励函数 ……………………………………………………… 284
9.4 监督学习的应用 ……………………………………………………………… 285
9.5 一个现代寓言 ………………………………………………………………… 286
9.6 练习题 ………………………………………………………………………… 287

第10 章 实时演化 …………………………………………………………… 288

10.1 聪明的外星人(Brainy Aliens)………………………………………………288
  10.1.1 程序实现 …………………………………………………………………290
    10.1.1.1 Roswell再现了:外星人大脑的尸体解剖 ……………………… 290
    10.1.1.2 外星人的演化 ………………………………………………………295
    10.1.1.3 CController::Update方法 ………………………………………… 299
  10.1.2 运行Brainy Aliens程序 ………………………………………………… 301
10.2 练习题………………………………………………………………………… 302

第11章 神经网络拓扑结构的演化 ……………………………………………303

11.1 竞争约定(competing convention)问题 ……………………………………304
11.2 直接编码 ………………………………………………………………………305
  11.2.1 GENITOR(基因子)…………………………………………………………305
  11.2.2 二进制矩阵编码 …………………………………………………………306
    11.2.2.1 几个相关问题 ………………………………………………………307
  11.2.3 基于节点的编码 …………………………………………………………308
  11.2.4 基于路径的编码 …………………………………………………………311
11.3 间接编码 ………………………………………………………………………311
  11.3.1 基于文法的编码 …………………………………………………………312
  11.3.2 二维生长编码 ……………………………………………………………313
11.4 NEAT(拓扑扩张的神经演化)………………………………………………… 314
  11.4.1 NEAT基因组 ………………………………………………………………315
    11.4.1.1 SLinkGene结构 …………………………………………………… 315
    11.4.1.2 SNeuronGene结构 ………………………………………………… 317
    11.4.1.3 CGenome类……………………………………………………………319
  11.4.2 算子和创新(Operators and Innovations)………………………………322
    11.4.2.1 CGenome::AddLink方法 ……………………………………………324
    11.4.2.2 CGenome::AddNeuron方法 …………………………………………329
    11.4.2.3 创新怎样帮助我们设计一个有效的杂交操作 ……………………336
  11.4.3 物种形成(Speciation)………………………………………………… 341
    11.4.3.1 兼容性测试 …………………………………………………………342
    11.4.3.2 CSpecies 类…………………………………………………………345
  11.4.4 Cga换时代方法(Cga Epoch Method) ………………………………… 348
  11.4.5 将基因组转变为表现型 …………………………………………………354
    11.4.5.1 SLink结构……………………………………………………………355
    11.4.5.2 SNeuron结构…………………………………………………………355
    11.4.5.3 把所有东西组合在一起…………………………………………… 357
    11.4.5.4 CNeuralNet类……………………………………………………… 358
  11.4.6 运行Demo程序 ……………………………………………………………363
11.5 本章小结 ……………………………………………… …………………… 364
11.6 练习题………………………………………………………………………… 365

第四部分 附录

附录A WEB资源 …………………………………………………………………367

  A1 相关的URL地址 ……………………………………………………………… 367
  A2 新闻组 …………………………………………………………………………368

附录B 参考书目及推荐读物 ………………………………………………… 369

  B1 技术书 …………………………………………………………………………369
  B2 论文 ……………………………………………………………………………370
  B3 能激发思想的书 ………………………………………………………………371
  B4 好得见血的科幻小说!……………………………………………………… 372

附录C 光盘上有些什么?……………………………………………………………373

技术支持 …………………………………………………………………………… 374

后记 ………………………………………………………………………………… 375

索引 ………………………………………………………………………………… 376

3.该书是英文《AI Techniques for Game Programming》的翻译,原书在Internet上可以找到很多好评,是目前国外、国内许多游戏学校或培训班的指定教材。其特点就是浅近易懂,且提供大量实际例子,也写得生动活泼,被认为是人工智能难得的好书,是一本指南性读物。

4.本书中文由沙鹰和我翻译,连载的那一章由本人翻译,所以没有任何侵权行为。同时也欢迎大家引用,大家可以放心。

5.本书现在已交清华大学出版社出版。这里的第三章是较早翻译并较早(2005年)贴出来的,系未经出版社审校的原始译文。出版时有过一些错误更正,但也不可避免会引入一些新的差错(最终出版时有不少删改,未经本人校对,十分遗憾!)。如果发现问题,请对照检查。

6.本连载所介绍内容的完整的VC++源程序和执行程序请到下面下载:

www.zzwu.cn/ai/AIch03.rar

谢谢各位读者提出的问题,我已作了更正,插图也已经都有了。

欢迎各位和其他网友继续批评指正!


预 告


游戏编程中的人工智能技术


<神经网络入门>连载

 即将开始

(共6篇)


1:生物学的神经网络-大脑介绍

2:数字版的神经网络

3:聪明的扫雷机工程

4:CNeuralNet.h等代码

5:神经网络的编码及遗传算法

6:功能的两个改进


posted @ 2014-08-14 15:01  Leytton  阅读(789)  评论(0编辑  收藏  举报