神经网络深入(连载4)拓扑扩张

来源:互联网 发布:java面向对象关键字 编辑:程序博客网 时间:2024/04/28 15:48

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



连载4:拓扑扩张


11.4 NEAT(拓扑扩张的神经演化)

    NEAT是Neuro Evolution of AugmentingTopologies(扩张拓扑的神经演化)的简称。这是得克萨斯州大学Kenneth Stanley Owen 和 Risto Miikkulainen 合作开发的一种网络。 它采用基于节点的编码来描述网络的结构和连接权重。当创建新的节点和链接时,它借助于所产生的历史数据,避免了竞争约定问题,所以这是一种非常漂亮的方法。NEAT也企图把它所生成的网络的尺寸减到最小。为此,在演化开始时,它让群体中每个神经网络都有最小的拓扑结构,然后在演化进行中,始终都是一个一个地在网络中添加神经细胞和连接。由于这种过程和自然界所有的生物机体的生长过程一样- 随着时间的推移,不断发育长大、不断增加复杂性 - 因而,这是一种非常吸引人的解决方法,而这也是我在本章中要特别着重介绍这种技术的部分原因。

为了使这一思想得到实现,需要利拥相当多的代码。当我描述NEAT演化规则的每一部分时我都会把有关代码列出来。我将按照程序固有的顺序来进行讲解,这样做,源程序代码本身就能帮助你加强对注释文本的理解、帮助你快速掌握概念。本章的所有源程序均可在光盘的Chapter11/NEAT Sweepers文件夹下找到。

首先让我描述网络怎么编码。

11.4.1 NEAT基因组(NEATGenome)

NEAT基因组的结构包括一张神经细胞基因(neuron gene [译注2])表和一张链接基因(link gene)表。一个链接基因的信息包括:它所连接的2个神经细胞、与此连接相关联的权重、用来说明链接是否已启用(enabled)的一个标帜、用来说明链接是否为返回(recurrent)的一个标帜,此外还有一个创新数(innovation number,有关它我们马上就会作更多说明)。神经细胞基因描述了该神经细胞在网络内的功能,它可以是一个输入神经细胞[译注1],或者是一个输出神经细胞,或者一个隐藏神经细胞,再或者是一个偏移神经细胞[译注1]。每个神经细胞基因也拥有一个唯一的ID(标识号)。

图11.12显示了一个简单网络的基因组的2种基因表。

11.4.1.1SLinkGene(链接基因的结构)

    链接基因的结构称作SlinkGene,它放在文件genes.h中。它的定义如下:

StructSlinkGene
{
 //该链接(link)所连结的2个神经细胞的标识(ID)
 int     FromNeuron,

            ToNeuron;

double   dWeight;

 

 

[译注1] "输入神经细胞"和"偏移神经细胞"都是NEAT的演化对象,在演化结果中都不代表神经细胞。

[译注2] 由注1可知,如果把‘神经细胞基因’改称‘节点基因’就可以更确切些。


图11.12  为 NEAT网络编码

//指明本link当前是否已Enabled的标志(flag)

bool  bEnabled;

 

//指明本link是否Recurrent的标志

bool    bRecurrent;

//我下面就会告诉有关这一个值的所有一切

int    InnovationID;

SLinkGene(){}

SlinkGene(int     in,

int        out,

bool       enable,

int         tag,

double     w,

bool        rec= false):bEnabled(enable),                           InnovationID(tag),

FromNeuron(in),

ToNeuron(out),

dWeight(w),

bRecurrent(rec)

{}

// 重载’<’运算符用于排序(我们以创新标识ID作为应用范畴)
    friend bool operator<(constSLinkGene& lhs, const SLinkGene& rhs)
     {
       return (lhs.InnovationID < rhs.InnovationID);
     }
};


11.4.1.2 SNeuronGene(神经细胞基因结构)

    神经细胞基因的结构体记作SNeuronGene,其代码可在genes.h中找到。下面是它的定义;

structSNeuronGene

{

     //它的标识号码

int       iID;

 

     //它的类型

neuron_type NeuronType;

 

这是一个枚举类型。其值为input、hidden、bias、output 和none。有关none类型如何使用,下一节讨论innovations(创新)时你就会知道。

    //它是Recurrent的吗?

bool bRecurrent;

NEAT中,一个自返神经细胞(recurrent neuron)定义为存在一个从自己连向自己的链接的神经细胞,如图 11.13所示。

//设置S形函数的曲率(弯曲性)

double dActivationResponse;

在本实现中,每个神经细胞的S形函数的激励响应也要分别地进行演化。

//在网格中的位置

double dSplitY, dSplitX;


图11.13  带有2个进入链接的神经细胞:

它有一个向外的链接和一个环形自返链接

如果你想在一个2D格点上布置(laid out)一个神经网络,就必须了解每个神经细胞在格子上的坐标。利用这一信息,我们还可以在显示器上画出这个网络,这能为用户提供视觉上的帮助。

当一基因组最初建立时,所有神经细胞都指定了一个平面坐标(SplitX,SplitY)。我现在暂时只讨论它们的Y坐标值 SplitY,但X坐标值SplitX也可以用类似方法进行计算。每个输入神经细胞的SplitY值指定为0,每个输出神经细胞的SplitY值指定1。当一个神经细胞加入到输入输出之间时,就要有效地断裂开一个链接,新的神经细胞的SplitY值取为它上下2个邻近接点的中点值。图11.14 应能帮助阐明这一点。


图 11.14  计算SplitY深度值的几步例子

这一信息除了可用在网络绘图程序中计算显示坐标外,在计算整个网络的深度(depth)

以及确定一个新加入的连接是否返回连接也是极有用的。

   SNeuronGene(neuron_type type,

                 int           id,

                 double       y,

                 double       x,

                 bool          r= false ):iID(id),

                                            NeuronType(type),

                                            bRecurrent(r),

                                            pReuronMarker(NULL),

                                            dSplitV(y),

                                            dSplitX(x)

   {}

};

 

11.4.1.3 CGenome(基因组类)

下面是基因组类CGenome的定义。其中包含的某些方法和成员你可能还无法理解,现在只需要快速浏览一下这个类,然后就转到下一节去。

(请注意,为了简单起见,我已删去了那些访问方法)。

classCGenome

{

private:

    

    //它的标识(ID)

int                  m_GenomeID;

 

    //组成此基因组的所有神经细胞

    vector<SNeuronGene>  m_vecNeurons;

    

    //所有的链接

    vector<SLinkGene>    m_vecLinks;

   

    //指向它的表现型的指针

    CNeuralNet*         m_pPhenotype;

    

//它的原始适应性分数

    double               m_dFitness;

 

    //当它已被放置进一物种并已作了相应调整后的适当性分数

    double               m_dAdjustedFitness;

 

    //要求孵化的下一代的子孙数目

    double               m_dAmountToSpawn;

 

    //分别用来保存输入和输出数目的2个纪录

    int                  m_iNumlnputs,

                          m_iNumOutputs;

 

    //保存该基因组进入的物种的轨迹(仅用于显示目的)

    int                   m_iSpecies;

 

    //返回true,如果指定的链接已是基因组的一个部分

    bool DuplicateLink(int NeuronIn, intNeuronOut);

 

    //给定一个神经细胞ID时,本函数就能找到它在m_vecNeurons中的位置

    int GetElementPos(int neuron_id);

 

//测试传入的ID是否与已经存在的某个神经细胞ID相同。

    //这一测试在AddNeuron中需要使用

    bool AlreadyHaveThisNeuronID(const int ID);

 

publlc:

 

     CGenome();

//本构造函数创建了一个最小的基因,它有输出与输入神经细胞,但每个输入神

//经细胞都连结到每个输出神经细胞

CGenome(int id, int inputs, int outputs);

 

//本构造函数利用一个SLinkGenes向量、一个SneuronGenes向量、

//和一个标识号ID来创建一个基因组

CGenome(int                                         id,

                   vector<SNeuronGene> neurons,

                   vector<SLinkGene>              genes,

                   int                                         inputs,

                  int                                         outputs);

//析构函数

~CGenome();

 

//复制构造函数

CGenome(const CGenome& g);

 

//为operator赋值

CGenome& operator = (const CGenome& g);

 

//由基因组创建神经网络

CNeuralNet*               CreatePhenotype(int depth);

 

//删除神经网络

int               DeletePhenotype();

 

//按突变率在基因组中增加一个链接

void                            AddLink(double         MutationRate,

                                                    double         ChanceOfRecurrent,

                                                    CInnovation       &innovation,

                                                    int                      NumTrysToFindLoop,

                                                    int                      NumTrysToAddLink);

 

//和一个神经细胞

void                       AddNeuron(double            MutationRate,

                                                   CInnovation      &innovation,

int                    NumTrysToFindOldLink);

//这一函数对连接权量实行突变

void                            MutateWeights(doublemut_rate,

                                                                double prob_new_mut,

                                                                double dMaxPertubation);

 

//骚扰神经细胞的激励响应

     void      MutateActivationResponse(double mut_rate,

doubleMaxPertubation);

 

     //计算本基因组和其他基因组之间的兼容性分

double GetCompatibilityScore(const CGenome &genome);

 

void            SortGenes();

 

     //重载 ’<’ 运算符用于排序。从最合适到最不合适的顺序来排

     friend booloperator<(const CGenome& lhs, const CGenome& rhs)

     {

        return(lhs.m_dFitness > rhs.m_dFitness);

     }

};


0 0
原创粉丝点击