机器人算法

<!--[if !vml]-->R<!--[endif]-->obocode 行为事件

坦克的主要都定义在一个主循环中,我们在程序中定义为上面四个策略定义四种战略如Move, Radar,Power,Target,当某一事件发生,基于这个事件而定的行为就会触发。而每个战略中都有不同的行为处理方式。这些行为通过遗传算法触 发,遗传算法将调用这些基本动作并搜索这些策略的最佳组合。基于这些基本动作将有4224 (=4*11*4*3*8)种可能发生。在Robocode AdvancedRobot 类下有如下的移动函数:

setAheadahead:让机器人向前移动一定距离.

setBackback:让机器人向后移动一定距离

setMaxTurnRate:设置机器人最大的旋转速度

setMaxVelocity:设置机器人最大的运动速度

setStopstop:停止移动或暂停机器人,并记住停止的位置

setResumeresume:重新开始移动停止的机器人

setTurnLeftturnLeft:向左旋转机器人

setTurnRightturnRight:向右旋转机器人

下面是 doMove 移动方法中使用部分程序代码:

Random

switch(Math.random()*2) {
         case 0
setTurnRight(Math.random()*90);
break;
case 1
setTurnLeft(Math.random()*90);
         break;   }
         execute();

 

Linear

ahead(200);
setBack(200);

 

Circular

setTurnRight(1000);
setMaxVelocity(4);
ahead(1000);

 

anti gravity

         double forceX = 0;
                 double forceY = 0;
                 for (int i=0; i<targetInfo.size(); i++){
                                   TargetInformation ti = (TargetInformation)targetInfo.get(i);
                                   double targetToMeX = getX()-ti.x;
                                   double targetToMeY = getY()-ti.y;
                                   double targetDistance = Math.sqrt(ti.x * ti.x + ti.y * ti.y);
                                   forceX += (targetToMeX/(ti.distance * ti.distance));
                                   forceY += (targetToMeY/(ti.distance * ti.distance));
                 }
                 forceX += 1/(getX());
                 forceY += 1/(getY());
                 forceX += 1/(getX()-getBattleFieldWidth());
                 forceY += 1/(getY()-getBattleFieldHeight());
                 double forceMagnitude = Math.sqrt(forceX*forceX+forceY*forceY);
                 forceX*=8/forceMagnitude;
                 forceY*=8/forceMagnitude;
                 desiredX = getX() + forceX;
                 desiredY = getY() + forceY;

 

这里我们用遗传算法来控制机器人移动位置。这些策略是基于下面几点:机器人人自己的位置、速度和方位;对手的位置(x,y坐标)、速度、方位以及相对角;所有机器人和子弹位置,方位及速度;场地大小等参数。

当上面的信息在下一回移动中使用时,出输出一对坐标值,根据这对坐标在Robocode就能得到距离和角度。要想让移动实现遗传必须要让它实现在线学习:所以我们的代码必须做下面几件事:要有一个函数收集适应度值,在Robocode运行过程中要运用到遗传操作,遗传后代要在Robocode运行中产生,而不是事后由手写入代码。

遗传操作

本例中遗传算法为实现移动用到两个类GAMovePattern。此处的GA比较简单主要完成数据和群体的定义,以及这些定义的读写文件操作。基中包括如下参数:群体大小、交叉概率、变异概率、精英概率(既告诉从当前群体到下一代中有多少移动不需要改变)、方程式中使用的加权系数大小,它通过一个主循环完成MovePattern的封装。MovePattern类中实现交叉、变异方法等方法,完成移动模式操作。而所有的输出保存在一个vector函数当中。Vector函数拥有一对实数数组,一个用于计算x坐标,另一个用于计算y坐标。通过对x,y坐标的计算,从而得到距离、角度等值,并产生相就在移动策略。如下,MovePattern包含三个参数,grad表示vector函数排列顺序,input即表示算法给出的输入编号,rang是加权的范围。

public class MovePatteren implements Comparable {
         private int grad, input;
         private double range;
         protected double fitness=0;
         protected double[] weightsX, weightsY;   
… }

 

交叉操作:每一个交叉操作执行如下步骤,先在交叉操作中产生一个特征码。这个特征码是个01之间的变量数组。有关交叉的基本原理可参考上面部分。最后通过遍历vector函数,把相应的加权值进行交叉操作。

protected MovePatteren crossOver(MovePatteren mate, boolean[] maskx, boolean[] masky) {
                 double[] wx= new double[weightsX.length];
                 double[] wy= new double[weightsX.length];
                 for(int mask=0; mask<maskx.length; mask++) {
                          for(int g=0; g<=grad; g++) {
                                   int i=mask*(grad+1)+g;
                                   wx[i]=(maskx[mask]?this
mate).weightsX[i];
                                   wy[i]=(masky[g]?this
mate).weightsY[i];
                          }
                 }
                 return new MovePatteren(grad, input, range, wx, wy);
         }

 

这里的变异操作比较简单。把加权范围内的随机数值去代替0到数组长之间的随机数并保存到移动模式中。则完成整个数组的变异过程:

protected void mutate() {
weightsX[(int)(Math.random()*weightsX.length)]=Math.random()*range*2-range;
weightsY[(int)(Math.random()*weightsX.length)]=Math.random()*range*2-range;
}

 

从上面的例子我们知道了遗传算法的大概实现,但并没有告诉我们这些组件是如何一起工作的。当 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
energy
heading
gunHeading
gunHeat
distance to west wall
distance to north wall
distance to east wall
distance to south wall
constant: 1
constant: 2
constant: 10
constant: 90
enemyVelocity
enemyEnergy
enemyHeading
enemyBearing
enemyDistance

 

TableRex有三个设计标准:

  1. 它是一种解释程序,能更快的进化程序,基于TableRex设计的机器人能有效的读写遗传数据;
  2. 拥有一个容易编码的固定基因组,使遗传中更容易交叉操作;
  3. 只要给TableRex一个简单的输入,它就很容易通过操作命令输出要的命令序列。如上表的最后输出左转炮管;

而整个TableRex解释程序由三部分组成:

  • SmallBrainTableRex的实现部分,此部分直接写在例子机器人处,也即自己写的测试机器人;
  • BrainWorld:这是实现遗传算法的主方法,直接写入Robocode控制器当中,在Robocode运行当中运行;
  • GeneticAlgorithm:这是遗传算法的定义部分,里面直接定义了所要用到的遗传操作函数;

下面我们来分析一个机器人如何通过TableRex达到遗传。

GeneticAlgorithm

主要是声明选择、交叉、变异的方法。

GeneticAlgorithm是一个静态类,其中定义了遗传所要的基本参数,如下:

public abstract class GeneticAlgorithm {
    public int population_size;  //
群体长度
    public int genome_size;    //
基因个体长度
    public GFPair population[];  //
产生的群体
    public int best_index; 
    public double best_fitness = Double.MIN_VALUE;  //
最优适应度
    public double mutation = 0.03;  //
变异概率
public double crossover = 0.9;   //
交叉概率
public double copy = 0.1;
public int tourney_size = 3;

 

其中变异概率取0.03, 交叉概率取0.9,最优适应度为实型的最小。此部分是从保存的文件中读取各个基本参数遗传初始化群体。

依照适应度值选择群体中个体:

public String tourneySelect (){
         double best_fit = -1;
         int best_guy = -1;
         for (int i = 0; i < tourney_size; i++){
             int cur_guy = (int) (Math.random() * population.length); 
             if (population[cur_guy].fitness > best_fit){
                 best_fit = population[cur_guy].fitness;
                 best_guy = cur_guy;
             }
         }
        
         return population[best_guy].genome;
}

 

交叉操作:通过从字符串中取子串的方法达到交叉操作:

public static String crossover (String g1, String g2){
         int num_points = (int) Math.round (Math.random() * 4f);
         int point = (int) (g1.length() * Math.random());
         return g1.substring (0, point) + g2.substring (point);
    }

 

变异操作:此部分先把基因转换为字符串流,通过setCharAt函数从指定的位置取反字符而达到变异:

public static String mutate (String genome, double p_mutation){
StringBuffer genome_b = new StringBuffer (genome);
                          if (genome_b.charAt (point) == '1'){
                     genome_b.setCharAt(point, '0');
                 }
                 else {
                     genome_b.setCharAt (point, '1');
                
         return new String (genome_b);
    }

 

BrainWorld

BrainWorld直接嵌入到Robocode控制器中,通过实现RobocodeListener接口来完成遗传的实例化。其最重要的有两个方法,计算最优适应度和产生遗传后代。

1. 实例化 GeneticAlgorithm

genome_size = num_events * num_boxes * (input_bits * 2 + func_bits);
int population_size = Integer.parseInt (in.readLine());
ga = new GeneticAlgorithm (population_size, genome_size);   

 

2. 通过文件读取操作从遗传保存文件中读取参数到遗传类中,文件格式如下所示:

       Robocode Location
       Storage_directory
       population_size
       elitism
       crossover
       copy
       base_mutation
       ...

 

3. 计算最优适应度:

protected void evaluateAll (){
         ga.best_fitness = Double.MIN_VALUE;
         ga.worst_fitness = Double.MAX_VALUE;
  
         for (int i = 0; i < ga.population_size; i++){
             ga.population[i].fitness = 0;
             for (int j = 0; j < rspecs.length; j++){
                 ga.population[i].fitness +=
                     Math.pow (2.0, (all_results[i][j] - all_means[j]) / all_stdevs[j]); 
             }
             ga.mean_fitness += ga.population[i].fitness;
             if (ga.population[i].fitness > ga.best_fitness) {
                 ga.best_fitness = ga.population[i].fitness;
                 ga.best_index = i;
             }
           
         }
        
    }

 

 

通过三个循环遍历整个群体,对各个适应度进行比较后找出最优适应度。

4. 产生遗传后代

public void newGeneration (){

         String copy_pop[] = ga.copy (ga.population, ga.copy);
         String cross_pop[] = ga.crossover (ga.population, ga.crossover);
         copy_pop = ga.mutate (copy_pop, ga.mutation);
         cross_pop = ga.mutate (cross_pop, ga.mutation);
         for (int i = 0; i < copy_pop.length; i++){
             ga.population[i+elite_pop.length].genome = copy_pop[i];
         }
         for (int i = 0; i < cross_pop.length; i++){
             ga.population[i+elite_pop.length+copy_pop.length].genome = cross_pop[i];
         }
      
         current_generation++;
         evaluateAll();
    }

 

通过复制(ga.copy)、交叉(ga.crossover)、变异(ga.mutate)操作,产生出遗传后代。

SmallBrain

SmallBrain也即我们写的利用遗传算法的例子机器人,它开始读取遗传文件"genome.dat",产生新的编码,当扫描到敌人时把所有相关的信息写入数组robot_data,再通过循环操作进化写入输入运算,最后遍历输入运算决定输出机器人的动作。

1.编码:

public void parseGenome (String genome){
         functions = new int [num_boxes][num_events];
         inputs1 = new int [num_boxes][num_events];
         inputs2 = new int [num_boxes][num_events];
         robot_data = new double [num_boxes + num_system_inputs][num_events];

 

通过parseGenome方法,设置function,input1,input2等数组的参数,对要操作的机器人进行编码。这部分和最上面提来的TableRex编码表是一致的。

2.写入状态信息

public void onScannedRobot (ScannedRobotEvent e){
         robot_data[0][kSCAN_EVENT] = getVelocity();
         robot_data[1][kSCAN_EVENT] = getEnergy();
         robot_data[2][kSCAN_EVENT] = getHeading();         
….

 

3.根据函数数组写入输入运算

for (int i = 0; i < num_boxes; i++){
             switch (functions[i][event_num]){
             case 0
//greater than
                 robot_data [i + num_system_inputs][event_num] =
                     robot_data[inputs1[i][event_num]][event_num] >
                                   robot_data[inputs2[i][event_num]][event_num]?1f
0f;
                 break;
        
             case 2
//equal to
break;
             case 15
  //output
                 handleOutput (inputs1[i][event_num], robot_data [inputs2[i][event_num]][event_num]);
                 break;

 

此处注意最后是根据写入的操作运算进行输出

4.输出机器人动作命令

  public void handleScanOutput (int outputType, double value){
         switch (outputType % 16){
         case 0

             ahead (value); break;
         case 1

             back (value); break;
         case 2

             //maybe shouldn't use mod here
             fire (value % 3); break;
        

 

最后我们可以看出TableRex程序中,smallBrainBrainWorld之间以文件方式并行交互,smallBrain扫描信息,写入文件。BrainWorld根据文件数据进化机器人,并把进化结果写入文件,smallBrain根据进化后的数据产生机器人的动作。

GPBot 小型遗传机器人

Geep 的机器人GPBot系统正是采用了TableRex解释程序的简单例子。

GPBot仅由四行代码组成(每行都以分号结束),它做了如下一些定制达到代码最优化:忽略雷达旋转,让它直接随着炮管而转动TurnGunRight(INFINITY);把行为做为常量来实现,让它们能显示在进化代码序列的任意点。

OnScannedRobot() {
MoveTank(<GP#1>);
TurnTankRight(<GP#2>);
TurnGunRight(<GP#3>);
}

 

GPBot所有事件都写在ScannedRobotEvent事件。每个方法都利用到了遗传算法进化机器人。第一行代码移动系统进化,适应度值依照个体躲避子弹,避墙和敌人的能力而设置;第二行代码指示坦克旋转指定的方向角。第三行代码瞄准系统进化指示炮管旋转指定的方向角, 适应度值依照个体打中敌人概率来设置。GPBot群体大小为256个个体,变异、交叉概率分别为0.9,选择长度为5。在最附录中提供了例子机器人人下载。

测试结果

最后我们给出一些测试数据,看看我们的程序不同的测试结果。

变异概率变化测试:

群体大小: 25
变异概率(Mutation): tested
精英概率(Elitism): 0.1
比赛回合: 20 rounds
后代: 25

 

我们选择变异概率分级从0.010.5这间。依照上面的遗传算法介绍,0.01的概率是比较合理的,所以我们以此为初始化值,下图中显示了所有的测试概率数据,从下图我们可以看出,开始一个很小的适应度值,从6代开始图形就增长很慢了,在13代的时候,有一个小的变化,到最后每个后代都相当逼近。


4. 变异测试
图4. 变异测试

5. 6 9代放大特写
图5. 6 到 9代放大特写

测试说明:

我们从上可以看出当我们给出初始的适应度在很小时,在第一代增长很多,经过一定数量的后代开始汇聚到一些最大的适应度值。由此我们得到证明,机器人在我们的学习环境中聪明的开始躲避子弹、墙、和其他机器人。而且它自己找到一个很好的瞄准位置给与敌人打击。

最后不能不说一下Jgap的使用, JGAP 是一款用Java编写的遗传算法包,由sourceforce上开发而来。它提供了基本的遗传算法,你可以使用它来解决一些适合用遗传算法解决的问题。而 且它给出了很多例子程序,可用于我们一些遗传算法的测试工作。由于它来自于开源组织sourceforce,所以开源的遗传功能将是研究简单遗传算法很好 工具。

近来在研究人工智能过程和坦克机器人时,发现国内也开发出了一个类似于 Robocode 仿真器的平台 AI-CODE,其思想延用 Robocode,但在 Robocode 基础上做了很多的改进,封装了一些函数模块,让开发者更侧重于算法和程序设计的学习。最有意思的这个平台能同时支持 Java,C,C++,C# 语言,从理论上看它支持任何语言。美中不足的是国内应用的例子还不是很多,远没有 Robocode 那么多可参考的例子。如果大家有兴趣可尝试在 AI-CODE 平台上用不同语言做一些遗传算法的测试。我想能帮助更多人工智能爱好者。其相关网站大家可到 http:///www.ai-code.org 去了解。

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值