美文网首页手拉手,一起走
力的进化:智能火箭

力的进化:智能火箭

作者: 大龙10 | 来源:发表于2022-08-17 13:10 被阅读0次

    书名:代码本色:用编程模拟自然系统
    作者:Daniel Shiffman
    译者:周晗彬
    ISBN:978-7-115-36947-5
    第9章目录

    9.10 力的进化:智能火箭

    1、问题

    • 我们基于一个特殊原因选择火箭模型的思路。
      2009年,Jer Thorp在他的博客上发表了一个遗传算法示例“Smart Rockets”。
      Jer指出,NASA用进化计算技术解决了从卫星天线设计到火箭发射模式的一系列问题,这启发他创建这个Flash程序演示火箭的进化。
    • 以下是场景描述:
      一组火箭从屏幕底部开始发射,目标是击中屏幕中的靶子(绕过直线前进路线中的障碍物)。


    • 每个火箭都配有5个推进器,推进器产生大小和方向都可变的推力。推进器不是一次全部发射,也不会连续发射,它的发射次数由一个自定义序列决定。


    • 我们要演化自己的简化版智能火箭(其中受到了Jer Thorp的启发)。

    2、思路

    • 我们的火箭只有一个推进器,在每一帧动画中,这个推进器可以产生任何方向和强度的推力。
      这个模型并不符合真实场景,但它会简化框架构建过程。(之后我们可以优化火箭和推进器模型,让它更符合真实场景。)
    • 首先,我们要把第2章的Mover类重命名为Rocket类。
    class Rocket {
        PVector location; 火箭有3个向量:位置、速度和加速度
        PVector velocity;
        PVector acceleration;
        void applyForce(PVector f) { 将力转化为加速度(牛顿第二定律)
            acceleration.add(f);
        }
        void update() { 简单的物理模型(欧拉积分)
            velocity.add(acceleration); 速度根据加速度变化
            location.add(veloctiry); 位置根据速度变化
            acceleration.mult(0);
        }
    }
    
    • 有了以上代码框架,我们只需在每一帧中调用applyForce()函数并传入一个推进力,就能实现智能火箭的运动模拟。一旦draw()函数被调用,“推进器”就会对火箭施加一个推进力。

    3、定制遗传算法

      上一节提到了定制遗传算法的3个关键点,下面结合本例回顾这3个关键点。

    1)第1点:种群规模和突变率

    • 实际上,我们可以暂时推迟这一点的讨论。
      本例的策略是先选择一些合适的初始值(种群中有100个火箭,突变率是1%),然后构建整个系统,再根据Sketch运行结果更改这些参数。

    2)第2点:适应度函数

    • 我们知道发射火箭的目的是击中某个靶子。也就是说,火箭到靶子的距离越近,它的适应度就越高。适应度和距离成反比:距离越短,适应度越高;距离越长,适应度越低。
    • 假设靶子是一个PVector对象,我们可以用这种方式实现适应度函数:
    void fitness() {
        float d = PVector.dist(location, target); 计算距离
        fitness = 1/d; 适应度和距离成反比
    }
    
    • 这也许是最简单的适应度函数。
      我们用1除以距离,就能得到反比例关系:距离越大,得到的适应度值越小;距离越小,得到的适应度值越大。
      如果要让适应度和距离成指数关系,可以用1除以距离的平方。
      除此之外,我们还可以对适应度函数做一些额外的优化,但简单的实现是一个良好的开端。
    void fitness() {
        float d = PVector.dist(location, target);
        fitness = pow(1/d,2); 1除以距离的平方
    }
    

    3)第3点:基因型和表现型

    • 前面我们说到,每个火箭都有一个推进器。
      推进器会在每一帧产生方向和大小皆可变的推力。
      因此,我们需要在每一帧动画中获取一个PVector对象。这样一来,本例的基因型(控制火箭行为的数据)可以用PVector对象的数组表示。
    class DNA {
        PVector[] genes;
    
    • 好消息是:本例不需要对DNA类进行任何其他修改。
      在猴子敲键盘程序中,我们为DNA类开发了一系列功能,这些功能完全适用于本例。
      唯一的不同点在于基因数组的初始化方式。在前面的例子中,基因数组由字符组成,它的元素是随机字符;在本例中,我们应该用随机的PVector对象初始化DNA序列。如何创建一个随机的PVector对象?你的直觉可能是这样的:
    PVector v = new PVector(random(-1,1),random(-1,1));
    
    • 这种方法很好,有时候也能奏效,但并不严谨。
      如果将得到的向量画在一幅图中,就会得到图9-12。也就是说,所有向量在一起形成了一个正方形区域。对本例而言,可能没有太大问题,但它还是存在一些偏差:正方形对角线上的向量比水平或竖直的向量更长。


    • 更好的方案应该是:选择一个随机角度,在这个角度上创建长度为1的向量。
      这样得到的向量将会形成一个圆。这可以通过极坐标系到笛卡儿坐标系的转换来完成,还可以直接调用PVector的random2D()函数,后者会更方便一些。


    for (int i = 0; i < genes.length; i++) {
        genes[i] = PVector.random2D(); 按随机角度创建向量
    }
    
    • 实际上,长度为1的PVector向量代表一个强度很大的力。
      记住,力会改变加速度,而加速度会以每秒30次的频率改变速度。因此,我们需要在DNA类中再加入一个变量:maxforce变量,用于限制PVector对象的长度。我们用这种方式控制推进力的大小。
    class DNA {
        PVector[] genes; 基因序列是一组向量
        float maxforce = 0.1; 推进器的推力大小
        DNA() {
            genes = new PVector[lifetime]; 火箭生命期的每一帧分别对应一个向量对象
            for (int i = 0; i < genes.length; i++) {
                genes[i] = PVector.random2D();
                genes[i].mult(random(0, maxforce)); 用随机的方式改变向量长度,但不要超过最大推}
            }
        }
    
    • 还需要注意,PVector对象的数组长度等于lifetime。火箭生存期中的每一帧都需要有个向量。以上代码假设lifetime是个全局变量,存储了每一代的总帧数。
    • Rocket类可以参照第2章中向量和力的示例程序,它的作用就是表达PVector数组的遗传信息,也就是表现型。
      我们只需要在Rocket类中添加一个DNA对象和fitness变量。只有Rocket对象知道如何计算它和靶子之间的距离,因此我们需要把适应度函数放到Rocket类中实现。
    class Rocket {
        DNA dna; 火箭的DNA
        float fitness; 火箭的适应度
        PVector location;
        PVector velocity;
        PVector acceleration;
    
    • DNA对象在这里有什么用?
      我们会从DNA基因数组中逐个取出PVector对象,然后将这个对象施加到火箭上。为了实现这一点,我们还需要添加一个整型变量,作为遍历数组时的计数器。
    int geneCounter = 0;
    void run() {
        applyForce(dna.genes[geneCounter]); 将基因数组中的力向量作用在火箭上
        geneCounter++; 转到基因数组的下一个力向量
        update(); 更新火箭的物理属性
    }
    

    相关文章

      网友评论

        本文标题:力的进化:智能火箭

        本文链接:https://www.haomeiwen.com/subject/pgjrgrtx.html