白话大数据——大数据算法:白话遗传算法

什么是大数据,大数据的价值是什么?

简单地说我们需要一种算法,这种算法的特点是:当给定的已知"基础数据"越多,通过运算得到的"解"就越优,可用于预测技术,如天气预测、股票预测、信用预测等等。为了实现这种运算,我们需要有能力存储大量数据所以有了云存储;还要足够快的运算速度所以我们需要网格计算、云计算。


自然界的高明之处就在于它总是能找到最简单的方法解决最复杂的问题。

生物的进化是一个奇妙的优化过程,它通过选择淘汰,突然变异,基因遗传等规律产生适应环境变化的优良物种。遗传算法是根据生物进化思想而启发得出的一种全局优化算法。遗传算法的概念最早是由Bagley J.D在1967年提出的;而开始遗传算法的理论和方法的系统性研究的是1975年,这一开创性工作是由Michigan大学的J.H.Holland所实行。当时,其主要目的是说明自然和人工系统的自适应过程。遗传算法简称GA(Genetic Algorithm),在本质上是一种不依赖具体问题的直接搜索方法。遗传算法在模式识别、神经网络、图像处理、机器学习、工业优化控制、自适应控制、生物科学、社会科学等方面都得到应用。在人工智能研究中,现在人们认为“遗传算法、自适应系统、细胞自动机、混沌理论与人工智能一样,都是对今后十年的计算技术有重大影响的关键技术”。遗传算法的基本思想是基于Darwin进化论和Mendel的遗传学说的。Darwin进化论最重要的是适者生存原理。它认为每一物种在发展中越来越适应环境。物种每个个体的基本特征由后代所继承,但后代又会产生一些异于父代的新变化。在环境变化时,只有那些能适应环境的个体特征方能保留下来。Mendel遗传学说最重要的是基因遗传原理。它认为遗传以密码方式存在细胞中,并以基因形式包含在染色体内。每个基因有特殊的位置并控制某种特殊性质;所以,每个基因产生的个体对环境具有某种适应性。基因突变和基因杂交可产生更适应于环境的后代。经过存优去劣的自然淘汰,适应性高的基因结构得以保存下来。

1. 基因遗传概念
1.1染色体、基因、种群(基因组)

生物在经过几代进化后在生存和繁殖能力上有了很大提高。遗传算法也在随之发展。所有生物体都是细胞的集合,每一个细胞都包含DNA串称作染色体。染色体是一个双绳结构,它们以螺旋方式相互连接。而染色体是由基因组成的。组成基因的物质被称作脱氧核糖核酸。包含四种类型,简写为T、A、C、G。 这些脱氧核糖核酸联结为基因链,每一个基因编码对应有机体特定的部分,例如黑头发和尖鼻子,基因设定不同的值产生不同效果,例如有金色、黑色、红色的头发。
    一个细胞含有的染色体包含繁殖下一代所有必要的信息. 这就是克隆的依据, 只要有一只羊的血细胞,就能复制出完全一样的另一只羊。 染色体的集合称作种群,种群的值称作基因型。 所以可以说我们都是基因型(染色体的集合)。
1.2 适应度、交叉、变异
度量生物是否成功(满足环境要求)的标准称作适应度。当两个有机体进行繁殖, 他们的染色体混合在一起,生成一个全新的染色体,这个染色体由其父母的染色体组成。 这一过程称作重组或交叉。这意味着后代继承了更好的基因。如果继承了不好的基因,它将不能继续繁殖,正所谓‘适者生存’,后代的适应度越高,繁殖的可能性就越高,它的子孙会继承他的优良基因。一代一代适应度不断提高。
每一代的转变都是非常缓慢的,在一代代的继承过程中就会发生变化,类似于‘传话游戏’最终的信息与原始信息可能有很大差异,这就是突变,这种变化是微弱的但意义巨大,一些变异是危险的(大多数可能是这样的),一些对适应度没有什么影响,还有一些会带给生物体与众不同的优势,这一优势将会不断得到继承和发展,逐渐变为主流。
2. 理解遗传算法
活的有机体是解决问题的专家。它们所表现出来的各种才能足以使编写得最完美的计算机程序黯然失色。为了解决某些问题,程序员们往往要花费数月乃至数年的脑力劳动,而有机体则能通过进化和自然选择等机制自动获取这种能力。
大多数有机体通过两种基本过程进化而来:自然选择和有性繁殖。第一个过程决定了群体中的哪些成员能够生存下来并传宗接代,第二个过程则保证了其后代能够获取来自父体和母体的不同的生存能力和特征。而突变则是个体之间产生差异的根本原因。事实上,有性繁殖所继承的父母的基因正是发生在祖辈身上的千万次突变经优胜劣汰凝练出来的结晶。
生物的基因型决定了外在的表现型,不同的个体对应着个体间不同的基因编码。假使我们要找到适合某种特殊要求的个体,这也意味着是要找出个体对应的特定基因。同时,生物体的繁殖、选择与突变都将通过个体的基因得到表达,从而,我们可以通过对个体基因的操作来模拟这些过程。
首先你要找到一种方法可以将你问题的任何可能解用编码(如二进制编码)描绘出来,每一个二进制编码构成的可能解就是一个染色体。然后你要创建一个由一些染色体组成的初始种群 (每一个染色体代表一个可能解) ,通过有选择的相互繁殖(交叉过程)得到适应度(适应度是一个已定义函数用来给每一个染色体(可能解)打分)更高的新个体,这期间要加一点变异。幸运的话几代之后,遗传算法可以得到一个解(满足某个已定义的特殊条件要求的染色体)。
GA并不需要你知道如何解决问题,你只要找到一种方法将问题用GA机制可以利用的编码表达出来 。通常染色体被编码为一条连续的二进制串,开始运行,GA首先创建一组染色体(初始种群), 每一个染色体的基因都是随机的(重要的是你必须保证这些染色体都可以代表一个可能解),通常随机的初始种群适应度是很低的。
在计算机中,我们能够用0和1构成的二进制串来对基因进行编码。这样,对这些二进制串的不同操作就可以用来模拟生物进化的各种过程。
**********************************说明********************************
例如:00代表东;01代表南;10代表西;11代表北,现在我需要实现一个龙卷风
那么00011011 就可以代表一个龙卷风(东南西北)这就是一个成功的解(他本身也是一个染色体,一个成功的染色体。)11100100(北西南东)也是一个解。而00011100只是一个可能解。

*******************************************************************************


GA算法的目标是找出最适合环境的最佳个体,而所使用的手段就是进化过程,也就是:自然选择、有性繁殖(交叉)和变异。


2.1 自然选择
正如我们在遗传算法基本结构中已经知道的那样,用来交叉的染色体是从从父代种群中选择出来。那么怎样选择这些染色体呢?根据达尔文的进化论,适应环境的(好的)个体(染色体)将生存下来并且交叉产生新的一代。选择好的(染色体)的方法有很多,比如轮盘赌选择方法(Roulette Wheel Selection)、(Boltman Selection)、锦标赛选择方法(Tournament Selection)、分级选择方法(Rank Selection)、稳定状态选择方法(Steady State Selection)等。
这里只讨论轮盘赌选择方法(Roulette Wheel Selection)

父代的选择是根据他们的适应度做出的。个体(染色体)越是适应环境,那么它被选择到的机会就越大。想像一个轮盘赌的机器上放置了种群中所有的染色体。每一个染色体所占的地方的大小和它的适应度成正比。如下图所示:


然后开始扔弹子,扔到那个地方就把对应的染色体拿出来。显然,适应度越大的染色体被选到的机会就越大。
*********************************说明*******************************
这个过程可以被下面的这个算法来模拟:
1.[创建]  随机产生一组染色体,
2.[求和] 计算所有染色体(二进制串)的适应度的和S;
3.[选择] 在区间(0,S)上随机的产生一个数r;
4.[循环] 从某个染色体开始,逐一取出染色体来,把它的适应度加到b上去(b开始为0),如果b大于r,则停止循环并返回当前染色体,当然适应度越大被选择的可能性越高(适应度高加起来使b大与r的可能性越高,当然有一种可能前面加了很多适应度高的染色体最后碰巧加了一个适应度低的,而它正好被选了出来,没错是有这种可能性,它就是一个漏网之鱼,但这种可能性毕竟较低,依然是适应度越高被选择的可能性越大)。
************************************************************************
2.2 交叉
在自然选择之后重要的遗传操作是交叉。生物体在进行有性繁殖时,子代的基因由父个体和母个体的基因配对来产生,从而使子代同时具备了父、母的部分特征。
操作时过程如下:任意挑选经过选择操作后种群中的两个个体(染色体‘二进制串’)作为交叉对象,随机产生一个交叉点位置;将选中的两个个体作为父、母个体并将在交叉点位置之右的代码串互换,形成两个新的子个体。此过程重复多次进行,直到子个体数目与父代相同为止。
********************************说明********************************
假设我们选中两个染色体(基因的集合)
1100000 11000
0011000 00011
我们随机选中一个交叉位置7,交换其余的5个基因(二进制数)
产生两个新的染色体
1100000 00011
0011000 11000
这个过程可以被下面的这个算法来模拟(接上面<自然选择>一节NOTICE中的算法模拟):
5.[分裂]随机选中二进制串的一个点为交叉位置
6.[交叉]根据交叉率(并不是选中的父染色体一定会被交叉,有一定的可能性父染色体(二进制串)没有交叉直接赋给了两个子染色体)交换在交叉点位置后的其余基因
************************************************************************
2.3 变异
如果只考虑交叉操作实现进化机制,在多数情况下是不够的,这与生物界近亲繁殖影响进化历程是类似的。因为种群的个体数是有限的,经过若干代选择及交叉作用,源于一个较好祖先的子个体将逐渐充斥整个种群。这样,在经过一段时间后,种群将不再进化,所求得的最优个体依赖于初始个体而不能代表问题的最优解。因此,在进化中,有必要加入具有新遗传基因的个体,解决的办法之一是模仿自然界的生物变异。自然界中生物的变异在某一代、某个个体上很少发生,即使发生,也是局部细微的改变。然而,从长远来看,变异却是必然而持续的过程。正是变异,提供了不竭的进化源泉。
操作方法是在选中的个体中,对个体中的某些基因执行异向转化。如果某位基因为1,产生变异时就是把它变成0;反亦反之。
********************************说明********************************
例如:上一例的一个子染色体110000000011 变异为 110000000010
这个过程可以被下面的这个算法来模拟(接上面<交叉>一节NOTICE中的算法模拟):
7.[变异]根据变异率(不是染色体中的基因都会发生变异,只有很少的基因发生了变化)对染色体中的某些基因执行异向转化
************************************************************************
2.4遗传算法过程
一般而言,一个世代的简单进化过程包括了基于适应度的选择、交叉和变异操作。在随机生成初始种群后,对种群反复施加上述操作,直至有满足条件的个体产生,这种模拟生物进化过程的算法称为遗传算法。
********************************说明********************************
遗传算法的基本结构
1[开始] 按照问题的特点随机产生一个拥有N个染色体(二进制串)的种群;
2[适应度计算] 用适应度函数f(x)计算种群每一个染色体(二进制串)的适应度,这是算法的难点(确定一个函数这个函数可以表达出每染色体(可能解)与目标的差距,差距越小适应度越高);
3[产生新的种群] 重复以下步骤,一直到新的母体群建立起来为止:
a.[选择 (Selection)]按照种群中的各个染色体的适应度从中选择父代染色体(适应度越大,机会越大);
b.[交叉 (Crossover)]任意挑选经过选择操作后种群中的两个染色体作为交叉对象,以一定的交叉概率交叉两个父代(染色体)的基因以产生子代。
c.[变异(Mutation)]以一定的概率对子代基因的进行变异;
d.[接受]把这个子代放到新的种群中去;
e.[替换]对新产生的种群进行更深入的算法运算;
f.[测验]如果终止条件满足,则停止;并且把当前母体群中最好的解输出;
g.[循环]如果f不满足,则转到b;继续进行
************************************************************************
3.遗传算法应用案例
遗传算法在本质上是一种不依赖具体问题的直接搜索方法。案例将尽可能对照上文算法模拟的每一步给出对应的程序实现方法。(案例源于《Ai Techniques For Game Programming》)
3.1 程序简介
一个迷宫,含有一个出口和入口,以及一些分散的障碍。然后定义一个虚拟人称作Bob,程序就是描绘出Bob避开所有的障碍从入口到出口的路径。
3.2 建立迷宫
   迷宫由一个2D数组表示,0代表空地(无障碍),1代表有障碍,5代表起点,8代表终点,这个迷宫可以表示为:
  {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,
8, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1,
1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1,
1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1,
1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1,
1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 5,
1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
效果如图:
这个地图被包含在类:CBobsMap 中
class CBobsMap
{
private:
// 迷宫地图数组定义
static const int map[MAP_HEIGHT][MAP_WIDTH];
static const int m_iMapWidth;
static const int m_iMapHeight;
//起点定义
static const int m_iStartX;
static const int m_iStartY;
// 终点定义
static const int m_iEndX;
static const int m_iEndY;
public:
// 保存Bob的路径(虚拟人走过的部分),对遗传算法没有作用,只是为了GDI显示
int memory[MAP_HEIGHT][MAP_WIDTH];
CBobsMap()
{
ResetMemory();
}
// 根据所走的距离給一个适应度的分数
double TestRoute(const vector<int> &vecPath, CBobsMap &memory);
// 使用 windows GDI 显示地图
void Render(const int cxClient, const int cyClient, HDC surface);
// 画出Bob的路径
void MemoryRender(const int cxClient, const int cyClient, HDC surface);
void ResetMemory();
};
记录Bob的路径的数组 memory[ ][ ] 对于遗传算法不是必需的,目的是为了通过GDI观察Bob的行为。重要的函数是TestRoute(),获得一系列的方向,并测试它们以观察Bob所走的距离。这个函数比较简单,可以直观的用四个方向代表路径:东南西北。例如:00代表东;01代表南;10代表西;11代表北,那么00011011 就可以代表:向东、向南、向西、再向北走,走了一个圈。
TestRoute()计算出Bob所能达到的最远的位置,并给出一个适应度分数。走的越接近出口(越远),分数越高。如果Bob走到终点便获得一个最高分,这就是一个解。
3.3 染色体编码
每一个染色体编码代表Bob的一个移动。移动可以用四个方向表示:东南西北。因此染色体编码就有这四个方向组成。一般的方法是把这四个方向转化为二进制
代码    解码    方向
00    0    北
01    1    南
10    2    东
11    3    西
因此,只要用一个二进制字符串就可表示Bob的行为,
例如:111110011011101110010101
代表基因:11, 11, 10, 01, 10, 11, 10, 11, 10, 01, 01, 01
十进制表示:3, 3, 2, 1, 2, 3, 2, 3, 2, 1, 1, 1
表示为方向:
代码    解码    方向
11    3    W
10    2    E
01    1    S
10    2    E
11    3    W
10    2    E
01    1    S
01    1    S
01    1    S
然后,Bob就要沿着这些方向走。如果一个方向使Bob遇到障碍,便忽略这个方向,程序移到下一个方向,直到用尽每一个方向或到达出口,但大多数是失败的。遗传算法生成初始染色体,测试他们能使Bob走多远(接近出口)。然后由他们繁殖下一代以更接近出口,直到得到一个解或最终失败。
所以必须定义一个结构体储存染色体(二进制串),及其相应分数。
struct SGenome
{
vector<int> vecBits;//二进制串
double dFitness; //分数
SGenome():dFitness(0){ }
SGenome(const int num_bits):dFitness(0)
{
// 创建随机染色体
for (int i=0; i<num_bits; ++i)
{
vecBits.push_back(RandInt(0, 1));
}
}
};
std::vector:是STL(standard template library)标准模版库的一部分,它是一个预定义的类以特别方式操作动态数组,通过push_back()方法增加元素。
#include<vector>
std::vector<int> MyFirstVector;
for (int i=0; i< 10; i++)
{
MyFirstVector.push_back(i);
cout << endl << MyFirstVector[i];
}
MyFirstVector.clear();
MyFirstVector.size()
SGenome 结构并不知道染色体如何编码进化(应用遗传算法),它是由CgaBob(c代表class ;ga代表genetic algorithm)类实现:
class CgaBob
{
private:
// 创建种群成员
vector<SGenome> m_vecGenomes;
// 定义种群成员数
int m_iPopSize;
// 交叉率
double m_dCrossoverRate;             
// 变异率
double m_dMutationRate;
// 染色体长度
int m_iChromoLength;
// 基因长度
int m_iGeneLength;
int m_iFittestGenome;
double m_dBestFitnessScore;
double m_dTotalFitnessScore;
int m_iGeneration;
// 创建地图类对象
CBobsMap m_BobsMap;
//另一个地图类对象用来保存每一代最好的路经,只是为了利用GDI显示
CBobsMap m_BobsBrain;
// 运行标志
bool m_bBusy;
// 变异函数
void Mutate(vector<int> &vecBits);
// 交叉函数
void Crossover(const vector<int> &mum,
const vector<int> &dad,
vector<int> &baby1,
vector<int> &baby2);
SGenome& RouletteWheelSelection();
// 根据新的计算分数和种群成员更新种群
void UpdateFitnessScores();
// 方向编码
vector<int> Decode(const vector<int> &bits);
// 将二进制转化为十进制
int BinToInt(const vector<int> &v);
// 创建初始染色体
void CreateStartPopulation();
public:
CgaBob(double cross_rat,
double mut_rat,
int pop_size,
int num_bits,
int gene_len):m_dCrossoverRate(cross_rat),
m_dMutationRate(mut_rat),
m_iPopSize(pop_size),
m_iChromoLength(num_bits),
m_dTotalFitnessScore(0.0),
m_iGeneration(0),
m_iGeneLength(gene_len),
m_bBusy(false)
{
CreateStartPopulation();
}
void Run(HWND hwnd);
void Epoch();
void Render(int cxClient, int cyClient, HDC surface);
// 存取器方法
int Generation(){return m_iGeneration;}
int GetFittest(){return m_iFittestGenome;}
bool Started(){return m_bBusy;}
void Stop(){m_bBusy = false;}
};
CreateStartPopulation()方法创建初始种群成员。
3.4 组成遗传算法架构Epoch()方法
Epoch()是组成遗传算法架构的方法,将遗传算法每一步联系在一起。
void CgaBob::Epoch()
{
UpdateFitnessScores();
UpdateFitnessScores()函数分析
vector<int> vecDirections = Decode(m_vecGenomes[i].vecBits);// 对每一个种群的染色体进行编码(利用二进制表示东南西北方向,转化为十进制)
m_vecGenomes[i].dFitness = m_BobsMap.TestRoute(vecDirections, TempMemory);// 计算出每一个染色体的分数
TestRoute 函数分析
double CBobsMap::TestRoute(const vector<int> &vecPath, CBobsMap &Bobs)
{
……
Int DiffX = abs(posX - m_iEndX);
int DiffY = abs(posY - m_iEndY);
return 1/(double)(DiffX+DiffY+1);
}
DiffX 和 DiffY 是由Bob所走到的最后位置分别减去终点水平和垂直的位置,如图灰色的部分代表Bob通过迷宫的路经,DiffX=5-0=5,DiffY=2-2=0。
返回
return 1/(double)(DiffX+DiffY+1);
这一行计算分数为:DiffX 和 DiffY相加之和的倒数,加1是确保被除数不    为零,如图到达终点被除数DiffX+DiffY=0,显然当到达终点时被除数最小    为1,返回值(分数)最高(分数最高为1)。
m_dTotalFitnessScore += m_vecGenomes[i].dFitness;//计算种群的适应
度总分数
m_dBestFitnessScore = m_vecGenomes[i].dFitness;//保存种群染色体中的最高分数
UpdateFitnessScores 将为后面的“自然选择”(轮盘赌)作准备
即前面在<自然选择>一节NOTICE中提到的:
1.[创建]随机产生一组染色体;① (这个数字标志表示上面提到的遗传算         法模拟的每一步)
2.[求和]计算所有染色体(二进制串)的适应度的和S;②
另外还保存了种群中染色体的最高分
// 创建新成员
int NewBabies = 0;
// 为新种群创建存储空间
vector<SGenome> vecBabyGenomes;
// 下面是遗传算法循环产生新种群,这种操作不断重复直到后代的成员数与上一代// 相同:
while (NewBabies < m_iPopSize)
{
// 应用‘轮盘赌’选择两个父母
SGenome mum = RouletteWheelSelection();
SGenome dad = RouletteWheelSelection();
// 交叉操作
// 创建两个空的后代:baby1, baby2,与其父母一道装入Crossover函数。这个函
// 数根据m_dCrossoverRate 变量(交叉率)进行交叉操作,生成新的染色体串装// 入baby1, baby2。
SGenome baby1, baby2;
Crossover(mum.vecBits, dad.vecBits, baby1.vecBits, baby2.vecBits);
// 变异操作
// 根据m_dMutationRate变量(变异率)对baby1, baby2进行变异操作。
Mutate(baby1.vecBits);
Mutate(baby2.vecBits);
// 添加到新种群
vecBabyGenomes.push_back(baby1);
vecBabyGenomes.push_back(baby2);
NewBabies += 2;
}
// 这两个新的后代最终添加为种群新的成员,完成了一次循环
// 将新种群的值赋给初始种群,生成下一代将以此为初始种群以产生更好的染色体
m_vecGenomes = vecBabyGenomes;
// 增加种群(代)总数,计算共进行了多少代进化(从第一个种群‘第一代’开始// 继而形成了多少代才找到了一个解或最终失败(可能进化了很多次也没有找到
// 解)或被用户终止循环)
++m_iGeneration;
}
// Epoch 函数不断重复,直到染色体中有一个是解(到达终点)或者用户决定停下// 来
3.5 选择参数值
所有参数都在defines.h文件中定义
#define CROSSOVER_RATE 0.7
#define MUTATION_RATE 0.001
#define POP_SIZE 140
#define CHROMO_LENGTH 70
交叉率0.7和变异0.001是默认值,成员数一般为染色体长度的2倍(这里就是140),染色体长度为70可以保证Bob找到出口。
3.6 操作函数
3.6.1 ‘轮盘赌’——自然选择

利用“轮盘赌”的方式从种群中选择一个基因。
SGenome& CgaBob::RouletteWheelSelection()
{
// 首先在零和种群的适应度的和之间选择一个随机数fSlice
// 就是<自然选择>一节中 NOTICE:
//[选择] 在区间(0,S)上随机的产生一个数r ③
double fSlice = RandFloat() * m_dTotalFitnessScore;
double cfTotal = 0;
int SelectedGenome = 0;
// 4.[循环] 从某个染色体开始,逐一取出染色体来,把它的适应度加到b上去
//(b开始为0),如果b大于r,则停止循环并返回当前染色体 ④,当然适应度越// 大被选择的可能性越高(适应度高加起来使b大与r的可能性越高,当然有一种// 可能前面加了很多适应度高的染色体最后碰巧加了一个适应度低的,而它正好被// 选了出来,没错是有这种可能性,它就是一个漏网之鱼,但这种可能性毕竟较低,// 依然是适应度越高被选择的可能性越大)。
for (int i=0; i<m_iPopSize; ++i)
{
cfTotal += m_vecGenomes[i].dFitness;
if (cfTotal > fSlice)
{
SelectedGenome = i;
break;
}
}
return m_vecGenomes[SelectedGenome];
}
// 代码不断跌代,逐一取出染色体来,把它的适应度加到cfTotal
// 上去(cfTotal开始为0),cfTotal += m_vecGenomes[i].dFitness;
// 当总分数大于fSlice,就返回在那个点的染色体:
// return m_vecGenomes[SelectedGenome];
3.6.2 交叉操作
这个函数将在一个随机点处分裂染色体,然后交叉随机点之后的部分形成两个新染色体(后代)
// 这个函数将两个父染色体进行交叉(染色体是表示一组方向的集合),两个空的// 变量(子染色体)用来拷贝
void CgaBob::Crossover
( const vector<int> &mum,
const vector<int> &dad,
vector<int> &baby1,
vector<int> &baby2)
{
// 首先判断交叉是否会发生,交叉的发生基于参数m_dCrossoverRate(交叉率)
// 如果交叉不会发生则父母的染色体直接拷贝给子
// 染色体,没有发生改变
if ( (RandFloat() > m_dCrossoverRate) || (mum == dad))
{
baby1 = mum;
baby2 = dad;
return;
}
// 根据染色体长度选择一个随机点分裂染色体。
// 这就是上面<交叉>一节NOTICE中提到的一步算法:
//[分裂]随机选中二进制串的一个点为交叉位置 ⑤
int cp = RandInt(0, m_iChromoLength - 1);
// 这两个循环根据交叉点交叉两个父染色体,产生两个新染色体附给 baby1 和 // baby2。这就是上面<交叉>一节“注意”中提到的另一步算法:
//[交叉]根据交叉率交换在交叉点位置后的其余基因 ⑥
for (int i=0; i<cp; i++)
{
baby1.push_back(mum[i]);
baby2.push_back(dad[i]);
}
for (i=cp; i<mum.size(); i++)
{
baby1.push_back(dad[i]);
baby2.push_back(mum[i]);
}
}
3.6.3 变异操作
这个函数在染色体内根据变异率改变其中的字节。
void CgaBob::Mutate(vector<int> &vecBits)
{
for (int curBit=0; curBit<vecBits.size(); curBit++)
{
// 随机数与变异率比较
if (RandFloat() < m_dMutationRate)
{
// 改变一个字节 0变1,或1变0
// 这就是上面<变异>一节NOTICE中提到的一步算法:
// [变异]根据变异率(不是染色体中的基因都会发生变异,只有很少的基因发
// 生了变化)对染色体中的某些基因执行异向转化 ⑦
vecBits[curBit] = !vecBits[curBit];
}
}//next bit
}
3.7 程序运行失败的原因
    运行这个程序时,会发现程序并不能每一次都找到出口(最终无法得到一个解)。这也许是种群中的成员(染色体)过于相似,交叉操作实际上几乎被消除了,毕竟变异的发生是有限的,变异率很低,一旦染色体不具备多样性变异本身不足以找到一个解(近亲繁殖好可怕呀);另外如同前面在<    自然选择>一节中提到的‘轮盘赌’的方式可能会扼杀掉一些比较好的解,失去所有好的染色体的可能性是存在的。
3.8 遗传算法应用的另一个案例——用遗传算法软件设计更好的汽车
舒马赫霸占一级方程式赛车的冠军宝座已经很久了,他的技术固然是关键因素,车子本身也很重要。英国伦敦大学的科学家正在尝试用遗传算法软件,通过‘适者生存’的“进化”过程得到更好的赛车设计方案。
  据英国《新科学家》杂志报道,科学家以电脑游戏“一级方程式挑战赛”为基础,得到了不断打破新纪录的赛车参数配置方案。他们说,如果利用真实的比赛数据,用同样方法有可能设计出更好的真正赛车。
  设计人员提出多种初步方案,在计算机上对不同方案的效果进行模拟。效果差的方案被淘汰掉,好的方案生存下来,互相“交叉”并发生“变异”,最终得到令人满意的方案。遗传算法已经被用于设计一级方程式比赛的中途维修方案和某些零件。
  在“一级方程式挑战赛”游戏里,玩家可对汽车的68个参数进行调整,包括极限转速、传动比和轮胎气压等,这些因素会影响汽车的性能。科学家先随机生成多组参数,在虚拟赛道上进行试验,然后保留名次位居参赛车总数前40%的那些参数组合,让它们发生“进化”,几代之后所得参数组合就会越来越出色。
4.遗传算法总结
尽管遗传算法对解决清楚的问题,如整数排序效率不高,但对现实生活中复杂的问题它能很好的处理。遗传算法的好处之一就是可以解决一些所谓的非结构问题,常见的算法如果要解决问题,就要事先描述问题的所有特点,针对问题的每个分枝设计不同的策略。而遗传算法有一定程度的所谓学习能力。自然界的高明之处就在于它总是能找到最简单的方法解决最复杂的问题。
4.1 遗传算法模型
典型的遗传算法CGA(Canonical Genetic Algorithm)通常用于解决下面这一类的静态最优化问题:
考虑对于一组长度为L的二进制编码 bi,i=1,2,…,n;有
bi∈{0,1}L    
给定目标函数f,有f(bi),并且
0<f(bi)<∞
同时
f(bi)≠f(bi+1)
求满足下式
max{f(bi)|bi∈{0,1}L}的bi。
很明显,遗传算法是一种最优化方法,它通过进化和遗传机理,从给出的原始种群中,不断进化产生新的解,最后收敛到一个特定的串bi处,即求出最优解。
4.2 遗传算法的特点
1.    传统优化算法是从单个初始值迭代求最优解的,容易误入局部最优解。遗传算法从串集开始搜索,复盖面大,利于全局择优。这是遗传算法与传统优化算法的极大区别。
2.    遗传算法求解时使用特定问题的信息极少,容易形成通用算法程序。由于遗传
算法使用适应度这一信息进行搜索,并不需要问题导数等与问题直接相关的信息。遗传算法只需适应度和串编码等通用信息,故几乎可处理任何问题。
3.    遗传算法有极强的容错能力,遗传算法的初始串集本身就带有大量与最优解甚
远的信息;通过选择、交叉、变异操作能迅速排除与最优解相差极大的串;这是一个强烈的滤波过程;并且是一个并行滤波机制。故而,遗传算法有很高的容错能力。
4.    遗传算法中的选择、交叉和变异都是随机操作,而不是确定的精确规则。这说
明遗传算法是采用随机方法进行最优解搜索,选择体现了向最优解迫近,交叉体现了最优解的产生,变异体现了全局最优解的复盖。
5.遗传算法具有隐含的并行性
遗传算法的基础理论是图式定理。它的有关内容如下:
(1)    图式(Schema)概念
一个基因串用符号集{0,1,*}表示,则称为一个因式;其中*可以是0或1。例如:H=1 x x 0 x x是一个图式。
(2)    图式的阶和长度
图式中0和1的个数称为图式的阶,并用0(H)表示。图式中第1位数字和最后位数字间的距离称为图式的长度,并用δ(H)表示。对于图式H=1x x0x x,有0(H)=2,δ(H)=4(存在有两个‘0’和4个‘1’的情况,即
H=111001等)。
(3)Holland图式定理
低阶,短长度的图式在群体遗传过程中将会按指数规律增加。当群体的大小为n时,每代处理的图式数目为0(n3)。
遗传算法这种处理能力称为隐含并行性(Implicit Parallelism)。它说明遗传算法其内在具有并行处理的特质。
4.3 遗传算法的应用关键
遗传算法在应用中最关键的问题有如下3个
1.    串(染色体)的编码方式
这本质是问题编码。一般把问题的各种参数用二进制编码,构成子串;然后把子串拼接构成“染色体”串。串长度及编码形式对算法收敛影响极大。
2.    适应函数的确定
适应函数(fitness function)也称对象函数(object function),这是问题求解品质的测量函数;往往也称为问题的“环境”。一般可以把问题的模型函数作为对象函数;但有时需要另行构造。
3.    遗传算法自身参数设定
遗传算法自身参数有3个,即群体大小n、交叉概率Pc和变异概率Pm。
群体大小n太小时难以求出最优解,太大则增长收敛时间。一般n=30-160。
交叉概率Pc太小时难以向前搜索,太大则容易破坏高适应值的结构。一般取Pc=0.25-0.75。变异概率Pm太小时难以产生新的基因结构,太大使遗传算法成了单纯的随机搜索。一般取Pm=0.01—0.2。
4.4 遗传算法目前的不足
遗传算法虽然可以在多种领域都有实际应用,并且也展示了它潜力和宽广前景,但是遗传算法还有大量的问题需要研究,目前也还有各种不足。首先,在变量多,取值范围大或无给定范围时,收敛速度下降;其次,可找到最优解附近,但无法精确确定最优解位置;最后,遗传算法的参数选择尚未有定量方法。对遗传算法,还需要进一步研究其数学基础理论;还需要在理论上证明它与其它优化技术的优劣及原因;还需研究硬件化的遗传算法;以及遗传算法的通用编程和形式等。
4.5 总之
遗传算法已经在很多复杂问题(比如说NP-难题)、机器学习和简单的进化规划中得到了使用。遗传算法在一些艺术领域也取得了很大成就,比如说进化图片和进化音乐。
遗传算法的优势在于他的并行性。遗传算法在搜索空间中非常独立地移动(按照基因型而不是表现型),所以它几乎不可能像其它算法那样“粘”在局部极值点。
遗传算法更容易实现。一旦你有了一个遗传算法的程序,如果你想解决一个新的问题,你只需要针对新的问题重新进行基因编码就行。如果编码方法也相同,那你只需要改变一下适应度函数就可以了。当然,选择编码方法和适应度函数是一件非常难的问题。
遗传算法作为一种非确定性的拟自然算法 ,为复杂系统的优化提供了一种新的方法,并且经过实践证明效果显著。尽管遗传算法在很多领域具有广泛的应用价值 ,但它仍存在一些问题,各国学者一直在探索着对遗传算法的改进,以使遗传算法有更广泛的应用领域。总之,遗传算法的未来是非常的美好的,只要我们对它们进行细致的分析,对它的缺点加以改造,优点进行继承,把它应用到我们的生产当中去,这样在生产当中还可以对它的缺点进行完善。
5.附录
5.1 VC++ 设计规范——前缀

sz    pointer to first character of a zero terminated string
str   string
i     int
n     number or int
ui    unsigned int
c     char
w     WORD (unsigned short)
dw    DWORD (unsigned long)
fn    function pointer
d     double
by    byte
l     long
p     pointer
lp    long pointer
lpstr long pointer to a string
h     handle
m_    class member
g_    global type
hwnd  Window handle
hdc   handle to a Windows device context
5.2 遗传算法部分应用列表:
1.非线性动态系统——预测,数据分析;
2.神经网络的结构和权重设计;
3.自动控制导弹的轨道设计;
4.进化LISP规划(遗传规划);
5.战略计划;
6.蛋白质分子的形状的寻找;
7.旅行商问题和时间序列排序问题;
8.构图的函数问题;
5.3 遗传算法的基本概念
由于遗传算法是由进化论和遗传学机理而产生的直接搜索优化方法;故而在这个算法中要用到各种进化和遗传学的概念。这些概念如下:
1.串(String)[染色体]
它是个体(Individual)的形式,在算法中为二进制串,并且对应于遗传学中的染色体(Chromosome)。
2.群体(Population)[种群或基因组]
个体(染色体)的集合称为群体,串是群体的元素
3.群体大小(Population Size)
在群体中个体的数量称为群体的大小。
4.基因(Gene)
基因是串中的元素,基因用于表示个体的特征。例如有一个串S=1011,则其中的1,0,1,1这4个元素分别称为基因。它们的值称为等位基因(Alletes)。
5.基因位置(Gene Position)
一个基因在串中的位置称为基因位置,有时也简称基因位。基因位置由串的左向右计算,
例如在串S=1101中,0的基因位置是3。基因位置对应于遗传学中的位点(Locus)。
6.基因特征值(Gene Feature)
在用串表示整数时,基因的特征值与二进制数的权一致;例如在串S=1011中,基因位置3中的1,它的基因特征值为2;基因位置1中的1,它的基因特征值为8。
7.串结构空间SS
在串中,基因任意组合所构成的串的集合。基因操作是在结构空间中进行的。串结构空间对应于遗传学中的基因型(Genotype)的集合。
8.参数空间SP
这是串空间在物理系统中的映射,它对应于遗传学中的表现型(Phenotype)的集合。
9.非线性
它对应遗传学中的异位显性(Epistasis)
10.适应度(Fitness)
表示某一个个体对于环境的适应程度。


©️2020 CSDN 皮肤主题: 酷酷鲨 设计师:CSDN官方博客 返回首页