<!--[if !vml]-->R<!--[endif]-->obocode 行为事件
坦克的主要都定义在一个主循环中,我们在程序中定义为上面四个策略定义四种战略如Move, Radar,Power,Target,当某一事件发生,基于这个事件而定的行为就会触发。而每个战略中都有不同的行为处理方式。这些行为通过遗传算法触 发,遗传算法将调用这些基本动作并搜索这些策略的最佳组合。基于这些基本动作将有4224 (=4*11*4*3*8)种可能发生。在Robocode AdvancedRobot 类下有如下的移动函数:
setAhead和ahead:让机器人向前移动一定距离.
setBack和back:让机器人向后移动一定距离
setMaxTurnRate:设置机器人最大的旋转速度
setMaxVelocity:设置机器人最大的运动速度
setStop和stop:停止移动或暂停机器人,并记住停止的位置
setResume和resume:重新开始移动停止的机器人
setTurnLeft和turnLeft:向左旋转机器人
setTurnRight和turnRight:向右旋转机器人
下面是 doMove 移动方法中使用部分程序代码:
Random:
switch(Math.random()*2) { |
Linear:
ahead(200); |
Circular:
setTurnRight(1000); |
anti gravity:
double forceX = 0; |
这里我们用遗传算法来控制机器人移动位置。这些策略是基于下面几点:机器人人自己的位置、速度和方位;对手的位置(x,y坐标)、速度、方位以及相对角;所有机器人和子弹位置,方位及速度;场地大小等参数。
当上面的信息在下一回移动中使用时,出输出一对坐标值,根据这对坐标在Robocode就能得到距离和角度。要想让移动实现遗传必须要让它实现在线学习:所以我们的代码必须做下面几件事:要有一个函数收集适应度值,在Robocode运行过程中要运用到遗传操作,遗传后代要在Robocode运行中产生,而不是事后由手写入代码。
本例中遗传算法为实现移动用到两个类GA和MovePattern。此处的GA比较简单主要完成数据和群体的定义,以及这些定义的读写文件操作。基中包括如下参数:群体大小、交叉概率、变异概率、精英概率(既告诉从当前群体到下一代中有多少移动不需要改变)、方程式中使用的加权系数大小,它通过一个主循环完成MovePattern的封装。MovePattern类中实现交叉、变异方法等方法,完成移动模式操作。而所有的输出保存在一个vector函数当中。Vector函数拥有一对实数数组,一个用于计算x坐标,另一个用于计算y坐标。通过对x,y坐标的计算,从而得到距离、角度等值,并产生相就在移动策略。如下,MovePattern包含三个参数,grad表示vector函数排列顺序,input即表示算法给出的输入编号,rang是加权的范围。
public class MovePatteren implements Comparable { |
交叉操作:每一个交叉操作执行如下步骤,先在交叉操作中产生一个特征码。这个特征码是个0到1之间的变量数组。有关交叉的基本原理可参考上面部分。最后通过遍历vector函数,把相应的加权值进行交叉操作。
protected MovePatteren crossOver(MovePatteren mate, boolean[] maskx, boolean[] masky) { |
这里的变异操作比较简单。把加权范围内的随机数值去代替0到数组长之间的随机数并保存到移动模式中。则完成整个数组的变异过程:
protected void mutate() { |
从上面的例子我们知道了遗传算法的大概实现,但并没有告诉我们这些组件是如何一起工作的。当 Robocode开始时,如果文件中没有数据,所以系统会依照输入的策略随机生成一个移动模式,如果文件中有数据,则加载这些数据。每一个移动模式在开始 都会给出了一个适应度值。当所有的移动模式都接收到适应度值,并完成各自的编号后,下面的操作将开始执行:
对所有的移动模式依据它们的适应度值进行分级处理
执行精英操作
执行交叉操作
应用变异操作
保存加权
算法重新开始
适应度值在进行运算过程中由机器人程序不断调整,以找到最优适应度。
限于篇副其他的一些策略本文不与详细说明,上面所有提到的策略和行为程序都可在网上或IBM的开发杂 志上找到成熟的讲解和例子机器人。有兴趣的朋友可以把这些策略都加入到自己的遗传算法中来。我们取群体大小为50,选择概率为0.7,交叉概率为0.6, 变异概率为0.3,与Robocode部分例子机器人测试,经过150代后你会发现系统产生了很多有趣的策略。比如撞击策略,这些策略都不在我们定义的策 略之中。
<!--[if !vml]-->中间解释程序进化机器人<!--[endif]-->
遗传算法可被看做任意基因组字符串。但是你必须决定这些字符所代表的意义,也就是说如何解释每一个基 因组。最简单的方法是把每一个基因组视为java代码,编译并运行它们。但是这些程序编译都很困难,所以也就有可能不能工作。Jacob Eisenstein设计了一种机器翻译语言TableRex来解决这个问题。在java中,TableRex被用于进化和解释动行时的Robocode 机器人。通过测试,只要我把TableRex解释程序作为文件放入Robocode控制器目录中,这些控制器就会读取文件并开始战斗。TableRex是 一些最适合遗传算法的二进制编程。只要符合TableRex程序文法,每个程序都能被解释。
<!--[if !vml]-->编码<!--[endif]-->
下表中显示了TableRex编码结构,它由一个行动作函数,二个输入和一个输出组成。如行6的值 ,这是个布尔型的表达式“值 line4 小于 90”,这个结果会在最后一行输出布尔为1的值。
Function | Input 1 | Input 2 | Output |
1. Random | ignore | ignore | 0,87 |
2. Divide | Const_1 | Const_2 | 0,5 |
3. Greater Than | Line 1 | Line 2 | 1 |
4. Normalize Angle | Enemy bearing | ignore | -50 |
5. Absolute Value | Line 4 | ignore | 50 |
6. Less Than | Line 4 | Const_90 | 1 |
7. And | Line 6 | Line 3 | 1 |
8. Multiply | Const_10 | Const_10 | 100 |
9. Less Than | Enemy distance | Line 8 | 0 |
10. And | Line 9 | Line 7 | 0 |
11. Multiply | Line 10 | Line 4 | 0 |
12 Output | Turn gun left | Line 11 | 0 |
输入的函数我们依照Robocode定义而定。如下表:
velocity |
TableRex有三个设计标准:
- 它是一种解释程序,能更快的进化程序,基于TableRex设计的机器人能有效的读写遗传数据;
- 拥有一个容易编码的固定基因组,使遗传中更容易交叉操作;
- 只要给TableRex一个简单的输入,它就很容易通过操作命令输出要的命令序列。如上表的最后输出左转炮管;
而整个TableRex解释程序由三部分组成:
- SmallBrain:TableRex的实现部分,此部分直接写在例子机器人处,也即自己写的测试机器人;
- BrainWorld:这是实现遗传算法的主方法,直接写入Robocode控制器当中,在Robocode运行当中运行;
- GeneticAlgorithm:这是遗传算法的定义部分,里面直接定义了所要用到的遗传操作函数;
下面我们来分析一个机器人如何通过TableRex达到遗传。
主要是声明选择、交叉、变异的方法。
GeneticAlgorithm是一个静态类,其中定义了遗传所要的基本参数,如下:
public abstract class GeneticAlgorithm { |
其中变异概率取0.03, 交叉概率取0.9,最优适应度为实型的最小。此部分是从保存的文件中读取各个基本参数遗传初始化群体。
依照适应度值选择群体中个体:
public String tourneySelect (){ |
交叉操作:通过从字符串中取子串的方法达到交叉操作:
public static String crossover (String g1, String g2){ |
变异操作:此部分先把基因转换为字符串流,通过setCharAt函数从指定的位置取反字符而达到变异:
public static String mutate (String genome, double p_mutation){ |
BrainWorld直接嵌入到Robocode控制器中,通过实现RobocodeListener接口来完成遗传的实例化。其最重要的有两个方法,计算最优适应度和产生遗传后代。
1. 实例化 GeneticAlgorithm:
genome_size = num_events * num_boxes * (input_bits * 2 + func_bits); |
2. 通过文件读取操作从遗传保存文件中读取参数到遗传类中,文件格式如下所示:
Robocode Location |
3. 计算最优适应度:
protected void evaluateAll (){ |
通过三个循环遍历整个群体,对各个适应度进行比较后找出最优适应度。
4. 产生遗传后代
public void newGeneration (){ |
通过复制(ga.copy)、交叉(ga.crossover)、变异(ga.mutate)操作,产生出遗传后代。
SmallBrain也即我们写的利用遗传算法的例子机器人,它开始读取遗传文件"genome.dat",产生新的编码,当扫描到敌人时把所有相关的信息写入数组robot_data,再通过循环操作进化写入输入运算,最后遍历输入运算决定输出机器人的动作。
1.编码:
public void parseGenome (String genome){ |
通过parseGenome方法,设置function,input1,input2等数组的参数,对要操作的机器人进行编码。这部分和最上面提来的TableRex编码表是一致的。
2.写入状态信息
public void onScannedRobot (ScannedRobotEvent e){ |
3.根据函数数组写入输入运算
for (int i = 0; i < num_boxes; i++){ |
此处注意最后是根据写入的操作运算进行输出
4.输出机器人动作命令
public void handleScanOutput (int outputType, double value){ |
最后我们可以看出TableRex程序中,smallBrain和BrainWorld之间以文件方式并行交互,smallBrain扫描信息,写入文件。BrainWorld根据文件数据进化机器人,并把进化结果写入文件,smallBrain根据进化后的数据产生机器人的动作。
GPBot 小型遗传机器人
Geep 的机器人GPBot系统正是采用了TableRex解释程序的简单例子。
GPBot仅由四行代码组成(每行都以分号结束),它做了如下一些定制达到代码最优化:忽略雷达旋转,让它直接随着炮管而转动TurnGunRight(INFINITY);把行为做为常量来实现,让它们能显示在进化代码序列的任意点。
OnScannedRobot() { |
GPBot所有事件都写在ScannedRobotEvent事件。每个方法都利用到了遗传算法进化机器人。第一行代码移动系统进化,适应度值依照个体躲避子弹,避墙和敌人的能力而设置;第二行代码指示坦克旋转指定的方向角。第三行代码瞄准系统进化指示炮管旋转指定的方向角, 适应度值依照个体打中敌人概率来设置。GPBot群体大小为256个个体,变异、交叉概率分别为0.9,选择长度为5。在最附录中提供了例子机器人人下载。
最后我们给出一些测试数据,看看我们的程序不同的测试结果。
变异概率变化测试:
群体大小: 25 |
我们选择变异概率分级从0.01到0.5这间。依照上面的遗传算法介绍,0.01的概率是比较合理的,所以我们以此为初始化值,下图中显示了所有的测试概率数据,从下图我们可以看出,开始一个很小的适应度值,从6代开始图形就增长很慢了,在13代的时候,有一个小的变化,到最后每个后代都相当逼近。
测试说明:
我们从上可以看出当我们给出初始的适应度在很小时,在第一代增长很多,经过一定数量的后代开始汇聚到一些最大的适应度值。由此我们得到证明,机器人在我们的学习环境中聪明的开始躲避子弹、墙、和其他机器人。而且它自己找到一个很好的瞄准位置给与敌人打击。
最后不能不说一下Jgap的使用, JGAP 是一款用Java编写的遗传算法包,由sourceforce上开发而来。它提供了基本的遗传算法,你可以使用它来解决一些适合用遗传算法解决的问题。而 且它给出了很多例子程序,可用于我们一些遗传算法的测试工作。由于它来自于开源组织sourceforce,所以开源的遗传功能将是研究简单遗传算法很好 工具。
近来在研究人工智能过程和坦克机器人时,发现国内也开发出了一个类似于 Robocode 仿真器的平台 AI-CODE,其思想延用 Robocode,但在 Robocode 基础上做了很多的改进,封装了一些函数模块,让开发者更侧重于算法和程序设计的学习。最有意思的这个平台能同时支持 Java,C,C++,C# 语言,从理论上看它支持任何语言。美中不足的是国内应用的例子还不是很多,远没有 Robocode 那么多可参考的例子。如果大家有兴趣可尝试在 AI-CODE 平台上用不同语言做一些遗传算法的测试。我想能帮助更多人工智能爱好者。其相关网站大家可到 http:///www.ai-code.org 去了解。