遗传算法(Genetic Algorithm, GA)是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,是一种通过模拟自然进化过程搜索最优解的方法。
其主要特点是直接对结构对象进行操作,不存在求导和函数连续性的限定;具有内在的隐并行性和更好的全局寻优能力;采用概率化的寻优方法,不需要确定的规则就能自动获取和指导优化的搜索空间,自适应地调整搜索方向。
遗传算法以一种群体中的所有个体为对象,并利用随机化技术指导对一个被编码的参数空间进行高效搜索。其中,选择、交叉和变异构成了遗传算法的遗传操作;参数编码、初始群体的设定、适应度函数的设计、遗传操作设计、控制参数设定五个要素组成了遗传算法的核心内容。
遗传算法是从代表问题可能潜在的解集的一个种群(population)开始的,而一个种群则由经过基因(gene)编码的一定数目的个体(individual)组成。每个个体实际上是染色体(chromosome)带有特征的实体。
染色体作为遗传物质的主要载体,即多个基因的集合,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现,如黑头发的特征是由染色体中控制这一特征的某种基因组合决定的。因此,在一开始需要实现从表现型到基因型的映射即编码工作。由于仿照基因编码的工作很复杂,我们往往进行简化,如二进制编码。
初代种群产生之后,按照适者生存和优胜劣汰的原理,逐代(generation)演化产生出越来越好的近似解,在每一代,根据问题域中个体的适应度(fitness)大小选择(selection)个体,并借助于自然遗传学的遗传算子(genetic operators)进行组合交叉(crossover)和变异(mutation),产生出代表新的解集的种群。
这个过程将导致种群像自然进化一样的后生代种群比前代更加适应于环境,末代种群中的最优个体经过解码(decoding),可以作为问题近似最优解。
为了大家更好了解遗传算法,在此之前先简单介绍一下相关生物学术语,大家了解一下即可。
基因型(genotype):性状染色体的内部表现;
表现型(phenotype):染色体决定的性状的外部表现,或者说,根据基因型形成的个体的外部表现;
进化(evolution):种群逐渐适应生存环境,品质不断得到改良。生物的进化是以种群的形式进行的。
适应度(fitness):度量某个物种对于生存环境的适应程度。
选择(selection):以一定的概率从种群中选择若干个个体。一般,选择过程是一种基于适应度的优胜劣汰的过程。
复制(reproduction):细胞分裂时,遗传物质DNA通过复制而转移到新产生的细胞中,新细胞就继承了旧细胞的基因。
交叉(crossover):两个染色体的某一相同位置处DNA被切断,前后两串分别交叉组合形成两个新的染色体。也称基因重组或杂交;
变异(mutation):复制时可能(很小的概率)产生某些复制差错,变异产生新的染色体,表现出新的性状。
编码(coding):DNA中遗传信息在一个长链上按一定的模式排列。遗传编码可看作从表现型到基因型的映射。
解码(decoding):基因型到表现型的映射。
个体(individual):指染色体带有特征的实体;
种群(population):个体的集合,该集合内个体数称为种群
如下的函数图像:
现在我们要在既定的区间内找出函数的最大值。
学过高中数学的孩纸都知道,上面的函数存在着很多的极大值和极小值。而最大值则是指定区间的极大值中的最大的那一个。从图像上具体表现为,极大值像是一座座山峰,极小值则是像一座座山谷。因此,我们也可以把遗传算法的过程看作是一个在多元函数里面求最优解的过程。
这些山峰对应着局部最优解,其中有一个山峰是海拔最高的,这个山峰则对应的是全局最优解。那么,遗传算法要做的就是尽量爬到最高峰,而不是困在较低的小山峰上。(如果问题求解是最小值,那么要做的就是尽量走到最低谷,道理是一样的)。
既然我们把函数曲线理解成一个一个山峰和山谷组成的山脉。那么我们可以设想所得到的每一个解就是一只袋鼠,我们希望它们不断的向着更高处跳去,直到跳到最高的山峰。所以求最大值的过程就转化成一个“袋鼠跳”的过程。
下面介绍介绍“袋鼠跳”的几种方式。
爬山算法:一只袋鼠朝着比现在高的地方跳去。它找到了不远处的最高的山峰。但是这座山不一定是最高峰。这就是爬山算法,它不能保证局部最优值就是全局最优值。
模拟退火:袋鼠喝醉了。它随机地跳了很长时间。这期间,它可能走向高处,也可能踏入平地。但是,它渐渐清醒了并朝最高峰跳去。这就是模拟退火算法。
遗传算法:有很多袋鼠,它们降落到喜玛拉雅山脉的任意地方。这些袋鼠并不知道它们的任务是寻找珠穆朗玛峰。但每过几年,就在一些海拔高度较低的地方射杀一些袋鼠。于是,不断有袋鼠死于海拔较低的地方,而越是在海拔高的袋鼠越是能活得更久,也越有机会生儿育女。就这样经过许多年,这些袋鼠们竟然都不自觉地聚拢到了一个个的山峰上,可是在所有的袋鼠中,只有聚拢到珠穆朗玛峰的袋鼠被带回了美丽的澳洲。
遗传算法中每一条染色体,对应着遗传算法的一个解决方案,一般我们用适应性函数(fitness function)来衡量这个解决方案的优劣。所以从一个基因组到其解的适应度形成一个映射。遗传算法的实现过程实际上就像自然界的进化过程那样。
下面我们用袋鼠跳中的步骤一一对应解释,以方便大家理解:
首先寻找一种对问题潜在解进行“数字化”编码的方案。(建立表现型和基因型的映射关系)
随机初始化一个种群(那么第一批袋鼠就被随意地分散在山脉上),种群里面的个体就是这些数字化的编码。
接下来,通过适当的解码过程之后(得到袋鼠的位置坐标)。
用适应性函数对每一个基因个体作一次适应度评估(袋鼠爬得越高当然就越好,所以适应度相应越高)。
用选择函数按照某种规定择优选择(每隔一段时间,射杀一些所在海拔较低的袋鼠,以保证袋鼠总体数目持平。)。
让个体基因变异(让袋鼠随机地跳一跳)。
然后产生子代(希望存活下来的袋鼠是多产的,并在那里生儿育女)。
遗传算法并不保证你能获得问题的最优解,但是使用遗传算法的最大优点在于你不必去了解和操心如何去“找”最优解。(你不必去指导袋鼠向那边跳,跳多远。)而只要简单的“否定”一些表现不好的个体就行了。(把那些总是爱走下坡路的袋鼠射杀,这就是遗传算法的精粹!)
由此我们可以得出遗传算法的一般步骤:
由交叉和变异产生新一代种群,返回步骤2,直到最优解产生。
具体图解可以回到1.3查看。
编码是应用遗传算法时要解决的首要问题,也是设计遗传算法时的一个关键步骤。编码方法影响到交叉算子、变异算子等遗传算子的运算方法,大很大程度上决定了遗传进化的效率。
迄今为止人们已经提出了许多种不同的编码方法。总的来说,这些编码方法可以分为三大类:二进制编码法、浮点编码法、符号编码法。下面分别进行介绍:
就像人类的基因有AGCT 4种碱基序列一样。不过在这里我们只用了0和1两种碱基,然后将他们串成一条链形成染色体。一个位能表示出2种状态的信息量,因此足够长的二进制染色体便能表示所有的特征。这便是二进制编码。如下:
1110001010111
它由二进制符号0和1所组成的二值符号集。它有以下一些优点:
二进制编码的缺点是:对于一些连续函数的优化问题,由于其随机性使得其局部搜索能力较差,如对于一些高精度的问题(如上题),当解迫近于最优解后,由于其变异后表现型变化很大,不连续,所以会远离最优解,达不到稳定。
二进制编码虽然简单直观,但明显地。但是存在着连续函数离散化时的映射误差。个体长度较短时,可能达不到精度要求,而个体编码长度较长时,虽然能提高精度,但增加了解码的难度,使遗传算法的搜索空间急剧扩大。
所谓浮点法,是指个体的每个基因值用某一范围内的一个浮点数来表示。在浮点数编码方法中,必须保证基因值在给定的区间限制范围内,遗传算法中所使用的交叉、变异等遗传算子也必须保证其运算结果所产生的新个体的基因值也在这个区间限制范围内。如下所示:
1.2-3.2-5.3-7.2-1.4-9.7
浮点数编码方法有下面几个优点:
符号编码法是指个体染色体编码串中的基因值取自一个无数值含义、而只有代码含义的符号集如{A,B,C…}。
符号编码的主要优点是:
在上面介绍了一系列编码方式以后,那么,如何利用上面的编码来为我们的袋鼠染色体编码呢?首先我们要明确一点:编码无非就是建立从基因型到表现型的映射关系。这里的表现型可以理解为个体特征(比如身高、体重、毛色等等)。那么,在此问题下,我们关心的个体特征就是:袋鼠的位置坐标(因为我们要把海拔低的袋鼠给杀掉)。无论袋鼠长什么样,爱吃什么。我们关心的始终是袋鼠在哪里,并且只要知道了袋鼠的位置坐标(位置坐标就是相应的染色体编码,可以通过解码得出),我们就可以:
回到3.1中提的求一元函数最大值的问题。在上面我们把极大值比喻为山峰,那么,袋鼠的位置坐标可以比喻为区间[-1, 2]的某一个x坐标(有了x坐标,再通过函数表达式可以算出函数值 <==> 得到了袋鼠染色体编码,解码得到位置坐标,在喜马拉雅山脉地图查询位置坐标算出海拔高度)。这个x坐标是一个实数,现在,说白了就是怎么对这个x坐标进行编码。下面我们以二进制编码为例讲解,不过这种情况下以二进制编码比较复杂就是了。(如果以浮点数编码,其实就很简洁了,就一浮点数而已。)
我们说过,一定长度的二进制编码序列,只能表示一定精度的浮点数。在这里假如我们要求解精确到六位小数,由于区间长度为2 - (-1) = 3 ,为了保证精度要求,至少把区间[-1,2]分为3 × 10^6等份。又因为
2^21 = 2097152 < 3*10^6 < 2^22 = 4194304
所以编码的二进制串至少需要22位。
把一个二进制串(b0,b1,....bn)转化为区间里面对应的实数值可以通过下面两个步骤:
将一个二进制串代表的二进制数转化为10进制数:
对应区间内的实数:
package GeneticTSP;
import java.util.Random;
/**
* 遗传算法类
* 包含:
* 1.run 开始跑算法
* 2.createBeginningSpecies 创建种群
* 3.calRate 计算每一种物种被选中的概率
* 4.select 轮盘策略 选择适应度高的物种
* 5.crossover 染色体交叉
* 6.mutate 染色体变异
* 7.getBest 获得适应度最大的物种
*/
public class GeneticAlgorithm {
//开始遗传
SpeciesIndividual run(SpeciesPopulation list)
{
//创建初始种群
createBeginningSpecies(list);
for(int i=1;i<=TSPData.DEVELOP_NUM;i++)
{
//选择
select(list);
//交叉
crossover(list);
//变异
mutate(list);
}
return getBest(list);
}
//创建初始种群
void createBeginningSpecies(SpeciesPopulation list)
{
//100%随机
int randomNum=(int)(TSPData.SPECIES_NUM);
for(int i=1;i<=randomNum;i++)
{
SpeciesIndividual species=new SpeciesIndividual();//创建结点
species.createByRandomGenes();//初始种群基因
list.add(species);//添加物种
}
// //40%贪婪
// int greedyNum=TSPData.SPECIES_NUM-randomNum;
// for(int i=1;i<=greedyNum;i++)
// {
// SpeciesIndividual species=new SpeciesIndividual();//创建结点
// species.createByGreedyGenes();//初始种群基因
//
// this.add(species);//添加物种
// }
}
//计算每一物种被选中的概率
void calRate(SpeciesPopulation list)
{
//计算总适应度
float totalFitness=0.0f;
list.speciesNum=0;
SpeciesIndividual point=list.head.next;//游标
while(point != null)//寻找表尾结点
{
point.calFitness();//计算适应度
totalFitness += point.fitness;
list.speciesNum++;
point=point.next;
}
//计算选中概率
point=list.head.next;//游标
while(point != null)//寻找表尾结点
{
point.rate=point.fitness/totalFitness;
point=point.next;
}
}
//选择优秀物种(轮盘赌)
void select(SpeciesPopulation list)
{
//计算适应度
calRate(list);
//找出最大适应度物种
float talentDis=Float.MAX_VALUE;
SpeciesIndividual talentSpecies=null;
SpeciesIndividual point=list.head.next;//游标
while(point!=null)
{
if(talentDis > point.distance)
{
talentDis=point.distance;
talentSpecies=point;
}
point=point.next;
}
//将最大适应度物种复制talentNum个
SpeciesPopulation newSpeciesPopulation=new SpeciesPopulation();
int talentNum=(int)(list.speciesNum/4);
for(int i=1;i<=talentNum;i++)
{
//复制物种至新表
SpeciesIndividual newSpecies=talentSpecies.clone();
newSpeciesPopulation.add(newSpecies);
}
//轮盘赌list.speciesNum-talentNum次
int roundNum=list.speciesNum-talentNum;
for(int i=1;i<=roundNum;i++)
{
//产生0-1的概率
float rate=(float)Math.random();
SpeciesIndividual oldPoint=list.head.next;//游标
while(oldPoint != null && oldPoint != talentSpecies)//寻找表尾结点
{
if(rate <= oldPoint.rate)
{
SpeciesIndividual newSpecies=oldPoint.clone();
newSpeciesPopulation.add(newSpecies);
break;
}
else
{
rate=rate-oldPoint.rate;
}
oldPoint=oldPoint.next;
}
if(oldPoint == null || oldPoint == talentSpecies)
{
//复制最后一个
point=list.head;//游标
while(point.next != null)//寻找表尾结点
point=point.next;
SpeciesIndividual newSpecies=point.clone();
newSpeciesPopulation.add(newSpecies);
}
}
list.head=newSpeciesPopulation.head;
}
//交叉操作
void crossover(SpeciesPopulation list)
{
//以概率pcl~pch进行
float rate=(float)Math.random();
if(rate > TSPData.pcl && rate < TSPData.pch)
{
SpeciesIndividual point=list.head.next;//游标
Random rand=new Random();
int find=rand.nextInt(list.speciesNum);
while(point != null && find != 0)//寻找表尾结点
{
point=point.next;
find--;
}
if(point.next != null)
{
int begin=rand.nextInt(TSPData.CITY_NUM);
//取point和point.next进行交叉,形成新的两个染色体
for(int i=begin;i right)
{
int tmp;
tmp=left;
left=right;
right=tmp;
}
//逆转left-right下标元素
while(left < right)
{
String tmp;
tmp=point.genes[left];
point.genes[left]=point.genes[right];
point.genes[right]=tmp;
left++;
right--;
}
}
point=point.next;
}
}
//获得适应度最大的物种
SpeciesIndividual getBest(SpeciesPopulation list)
{
float distance=Float.MAX_VALUE;
SpeciesIndividual bestSpecies=null;
SpeciesIndividual point=list.head.next;//游标
while(point != null)//寻找表尾结点
{
if(distance > point.distance)
{
bestSpecies=point;
distance=point.distance;
}
point=point.next;
}
return bestSpecies;
}
}
MainRun.java
package GeneticTSP;
/**
* 主函数运行类
*/
public class MainRun {
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建遗传算法驱动对象
GeneticAlgorithm GA=new GeneticAlgorithm();
//创建初始种群
SpeciesPopulation speciesPopulation = new SpeciesPopulation();
//开始遗传算法(选择算子、交叉算子、变异算子)
SpeciesIndividual bestRate=GA.run(speciesPopulation);
//打印路径与最短距离
bestRate.printRate();
}
}
SpeciesIndividual.java
package GeneticTSP;
import java.util.Random;
/**
* 个体类
* 包含:
* 1.createByRandomGenes 初始物种基因(随机) 基因直接用城市序列编码
* 2.calFitness 计算物种适应度
* 3.printRate 打印路径
*/
public class SpeciesIndividual {
String[] genes;//基因序列
float distance;//路程
float fitness;//适应度
SpeciesIndividual next;
float rate;
SpeciesIndividual()
{
//初始化
this.genes=new String[TSPData.CITY_NUM];
this.fitness=0.0f;
this.distance=0.0f;
this.next=null;
rate=0.0f;
}
//初始物种基因(随机)
void createByRandomGenes()
{
//初始化基因为1-CITY_NUM序列
for(int i = 0;i < genes.length;i++)
{
genes[i]=Integer.toString(i+1);
}
//获取随机种子
Random rand=new Random();
for(int j=0;j");
System.out.print(genes[0]+"\n");
System.out.print("最短长度:" + distance);
}
}
SpeciesPopulation.java
package GeneticTSP;
/**
* 种群类
* 包含:
* 1.add 添加物种
* 2.traverse 遍历
*/
public class SpeciesPopulation {
SpeciesIndividual head;//头结点
int speciesNum;//物种数量
SpeciesPopulation()
{
head=new SpeciesIndividual();
speciesNum=TSPData.SPECIES_NUM;
}
//添加物种
void add(SpeciesIndividual species)
{
SpeciesIndividual point=head;//游标
while(point.next != null)//寻找表尾结点
point=point.next;
point.next=species;
}
//遍历
void traverse()
{
SpeciesIndividual point=head.next;//游标
while(point != null)//寻找表尾结点
{
for(int i=0;i
TSPData.java
package GeneticTSP;
/**
* TSP数据类
* 包含:
* disMap 各个城市间距离矩阵
*/
public class TSPData {
static int CITY_NUM; //城市数
static final int SPECIES_NUM=200; //种群数
static final int DEVELOP_NUM=1000; //进化代数
static final float pcl=0.6f,pch=0.95f;//交叉概率
static final float pm=0.4f;//变异概率
static final float[][] disMap; //地图数据
static
{
// int[][] cityPosition={
// {0,0},{12,32},{5,25},{8,45},{33,17},
// {25,7},{15,15},{15,25},{25,15},{41,12}};//10个城市(最优解:147)
// int[][] cityPosition={
// {60,200},{180,200},{80,180},{140,180},
// {20,160},{100,160},{200,160},{140,140},
// {40,120},{100,120},{180,100},{60,80},
// {120,80},{180,60},{20,40},{100,40},
// {200,40},{20,20},{60,20},{160,20}};//20个城市(最优解:870)
//
//城市坐标集合
int[][] cityPosition={
{1304, 2312},{3639, 1315},
{4177, 2244},{3712, 1399},
{3488, 1535},{3326, 1556},
{3238, 1229},{4196, 1004},
{4312, 790},{4386, 570},
{3007, 1970},{2562, 1756},
{2788, 1491},{2381, 1676},
{1332, 695},{3715, 1678},
{3918, 2179},{4061, 2370},
{3780, 2212},{3676, 2578},
{4029, 2838},{4263, 2931},
{3429, 1908},{3507, 2367},
{3394, 2643},{3439, 3201},
{2935, 3240},{3140, 3550},
{2545, 2357},{2778, 2826},
{2370, 2975}};//31个城市(最优解:14700)
//路径集合
CITY_NUM=cityPosition.length;
disMap=new float[CITY_NUM][CITY_NUM];
for(int i=0;i
注:这是一篇对遗传算法基础知识解释比较全面的文章,出于学习的目的进行转载。文章转载自此处,作者:短短的路走走停停被抢注啦,侵权必删。