细胞的权重,读完第一个隐藏层,再向上读它的下一层,把所读到的数据依次保存到一个向量中,这样就实现了网络的编码。因此,如果我们有图14所示的网络,则它的权重编码向量将为:
0.3, -0.8, -0.2, 0.6, 0.1, -0.l, 0.4, 0.5
在这一网络中,为了简单,我没有把偏移值的权重包括进去。但在实际实现编码时,你必须包含偏移值这个权重,否则你肯定无法获得你所需要的结果。
图14 为权重编码。
在此之前讲的事情你都懂了吗?好极了,那下面就让我们转来考虑,怎样用遗传算法来操纵已编码的基因吧。
4.6 遗传算法(The Genetic Algorithm)
到此,所有的权重已经象二进制编码的基因组那样,形成了一个串,我们就可以象本书早先讨论过的那样来应用遗传算法了。遗传算法(GA)是在扫雷机已被允许按照用户指定的帧数(为了某种缘故, 我下面更喜欢将帧数称作滴答数,英文是ticks)运转后执行的。你可以在ini文件中找到这个滴答数(iNumTicks)的设置。下面是基因组结构体的代码。这些对于你应该是十分面熟的东西了。
Struct SGenome
{
vector
double dFitness;
SGenome():dFitness(0) {}
SGenome(vector
//重载'<'的排序方法
friend bool operator<(const SGenome& lhs, const SGenome& rhs) {
return (lhs.dFitness < rhs.dFitness); } };
从上面的代码你可看出,这一SGenome结构和我们在本书所有其他地方见到的SGenome结构几乎完全一致,唯一的差别就是这里的染色体是一个双精度向量std::vector。因此,可以和通常一样来应用杂交操作和选择操作。但突变操作则稍微有些不同,这里的权重值是用一个最大值为dMaxPerturbation的随机数来搔扰的。这一参数
dMaxPerturbation在ini文件中已作了声明。另外,作为浮点数遗传算法,突变率也被设定得更高些。在本工程中,它被设成为0.1。 下面就是扫雷机工程遗传算法类中所见到的突变函数的形式:
void CGenAlg::Mutate(vector
// 遍历权重向量,按突变率将每一个权重进行突变 for (int i=0; i // 我们要骚扰这个权重吗? if (RandFloat() < m_dMutationRate) { // 为权重增加或减小一个小的数量 chromo[i] += (RandomClamped() * CParams::dMaxPerturbatlon); } } } 如同以前的工程那样,我已为v1.0版本的Smart Minesweepers工程保留了一个非常简单的遗传算法。这样就能给你留下许多余地,可让你利用以前学到的技术来改进它。就象大多数别的工程一样,v1.O版只用轮盘赌方式选精英,并采用单点式杂交。 注意: 当程序运行时,权重可以被演化成为任意的大小,它们不受任何形式的限制。 4.7 扫雷机类(The CMinesweeper Class) 这一个类用来定义一个扫雷机。就象上一章描述的登月艇类一样,扫雷机类中有一个包含了扫雷机位置、速度、以及如何转换方向等数据的纪录。类中还包含扫雷机的视线向量(look-at vector);它的2个分量被用来作为神经网络的2个输入。这是一个规范化的向量,它是在每一帧中根据扫雷机本身的转动角度计算出来的,它指示了扫雷机当前是朝着哪一个方向,如图11所示。 下面就是CMinesweeper扫雷机类的声明: class CMinesweeper { private: // 扫雷机的神经网络 CNeuralNet m_ItsBrain; // 它在世界坐标里的位置 SVector2D m_vPosition; // 扫雷机面对的方向 SVector2D m_vLookAt; // 它的旋转(surprise surprise) double m_dRotation; double m_dSpeed; // 根据ANN保存输出 double m_lTrack, m_rTrack; m_lTrack和m_rTrack根据网络保存当前帧的输出。 这些就是用来决定扫雷机的移动速率和转动角度的数值。 // 用于度量扫雷机适应性的分数 double m_dFitness; 每当扫雷机找到一个地雷,它的适应性分数就要增加。 // 扫雷机画出来时的大小比例 double m_dScale; // 扫雷机最邻近地雷的下标位置 int m_iClosestMine; 在控制器类CControl1er中,有一个属于所有地雷的成员向量std::vector。而m_iClosestMine就是代表最靠近扫雷机的那个地雷在该向量中的位置的下标。 public: CMinesweeper(); // 利用从扫雷机环境得到的信息来更新人工神经网 bool Update(vector // 用来对扫雷机各个顶点进行变换,以便接着可以画它出来 void WorldTransform(vector // 返回一个向量到最邻近的地雷 5Vector2D GetClosestMine(vector int CheckForMine(vector void Reset(); // ----------------- 定义各种供访问用的函数 SVector2D Position()const { return m_vPosition; } void IncrementFitness(double val) { m_dFitness += val; } double Fitness()const { return m_dFitness; } void PutWeights(vector int GetNumberOfWeights()const { return m_ItsBrain.GetNumberOfWeights(); } }; 4.7.1 The CMinesweeper::Update Function(扫雷机更新函数) 需要更详细地向你说明的CMinesweeper类的方法只有一个,这就是Update更新函数。该函数在每一帧中都要被调用,以更新扫雷机神经网络。让我们考察这函数的肚子里有些什么货色: bool CMinesweeper::Update(vector //这一向量用来存放神经网络所有的输入 vector //计算从扫雷机到与其最接近的地雷(2个点)之间的向量 SVector2D vClosestMine = GetClosestMine(mines); //将该向量规范化 Vec2DNormalize(vClosestMine); 首先,该函数计算了扫雷机到与其最靠近的地雷之间的向量,然后使它规范化。(记住,向量规范化后它的长度等于1。)但扫雷机的视线向量(look-at vector)这时不需要再作规范化,因为它的长度已经等于1了。由于两个向量都有效地化成了同样的大小范围,我们就可以认为输入已经是标准化了,这我前面已讲过了。 //加入扫雷机->最近地雷之间的向量