先来干货,百度云链接:https://pan.baidu.com/s/1hS-mfFSDwwQOGNogWcR_SQ 密码:gis6
云盘中的源码是纯JS写的算法,直接在网页中打开即可。本文直接结合问题、代码对遗传算法进行讲解。
***************路由器排布问题:
路由器个数即圆的个数,路由器半径即圆的半径,种群大小是遗传算法中重要概念。此题是要求在这个500×500区域里,给定路由器个数和覆盖半径,要求尽可能多的覆盖这片区域,且中心区域需要二次覆盖。
此例是初学遗传算法时所写,写得不好,有兴趣的朋友可以查看源码研究。这里不讲述此问题
*********************机器人拣货路径问题
问题如下:在500×500区域内有若干个货物,机器人要在尽可能短的路径下拾取所有货物,即TSP问题。简单理解:遍历所有点,且总路径最小。下图为(标准)遗传算法解决机器人拣货路径.html,输入货物个数与种群个体数,点击“确定”出现如下界面(之后可点击"迭代100次"进行迭代,第一次点会很卡,因为计算量很大,一直点“迭代100次”可观察)
研究一个组合问题,首先需要说明问题的三要素:
(1)约束条件:区域面积等
(2)决策变量:货物个数
(3)目标函数:总距离
问题确立,接下来简单说明遗传算法,再详细讲解如何实施:
遗传算法即利用达尔文进化论观点生成的群体算法。
此段较抽象,是理论方法,之后会介绍详细方法:第一步,生成初代种群,一个种群中有若干个个体,每一个个体都是一种解法,对于每一个个体进行适应度函数计算,取适应度最大的个体作为当前最优解。第二步,进行繁衍,迭代法不断生成子代种群,如何生成子代种群,首先对父级种群进行编码,一般是生成01001000100111...类似的二进制编码作为染色体,每个个体生成一条染色体,随机抓取两条染色体交叉生成子代染色体(随机抓取的策略有轮盘赌、胜者为王、平均主义等,轮盘赌可自行百度,是一种符合遗传学的选择策略),若子代染色体不符合标准则舍弃(舍弃规则需要根据设定的编码规则来),直到生成足够数量且符合标准的子代。对子代染色体进行变异,最后将染色体解码生成子代种群,此时,同样适应度最大为最优解。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>(标准)遗传算法解决机器人拣货路径问题(即TSP问题具体应用)</title>
</head>
<body>
货物数量:<input id="goodsNum" style="width: 300"></input>
种群个体数:<input id="GeneNum" style="width: 300"></input>
<input type="button" value="确 定" name="formSubmit" onclick="checkFinish();">
<input type="button" value="迭代100次" name="NextSubmit" onclick="Next();">
<canvas id="drawing" width="500" height="500" style="border:#0F0 solid 1px">A Drawing of something</canvas>
<script>
var drawing=document.getElementById("drawing");
if(drawing.getContext){
var context=drawing.getContext("2d");
}
var goodsNumInput=document.getElementById("goodsNum");//货物数量input
var goodsNum=0;//货物数量
var GeneNumInput=document.getElementById("GeneNum");//种群个体数input
var GeneNum=0;//种群个体数
var goodsLocation;//位置矩阵
var C;//距离矩阵
var Pop;//种群
function checkFinish(){
drawing.height=drawing.height;//清空
//获取当前各参数
goodsNum=goodsNumInput.value;//货物数量
GeneNum=GeneNumInput.value;//种群个体数
if(goodsNum==""||GeneNum==""){
alert("不允许存在空的参数");
return ;
}
//随机生成货物位置,货物位置为2倍货物数量的数组,存放x,y坐标
goodsLocation=initGoodsLocation(goodsNum);
//绘制货物
drawGoods(goodsLocation,goodsNum);
//生成距离矩阵C
C = distanceArr(goodsLocation,goodsNum);
//生成初代种群,表现为二维数组[种群大小][货物数]
Pop = initPopulation(goodsNum,GeneNum);
//计算适应度函数,注输入应为数字表现的种群
var PopScore = adaptCount(Pop,C);
//计算初代种群适应度最大并绘制图样
var maxIndexOf=PopScore.indexOf(Math.max.apply(Math, PopScore));
drawPic(goodsLocation,Pop[maxIndexOf]);
}
function Next(){
drawing.height=drawing.height;//清空
goodsNum=goodsNumInput.value;//货物数量
GeneNum=GeneNumInput.value;//种群个体数
drawGoods(goodsLocation,goodsNum);//绘制货物
for(var D=0;D<100;D++){
//将种群个体编码,便于之后进行选择交叉
var PopCode = coder(Pop,goodsNum);//一维,长度为种群大小
//进行选择交叉,选择策略为轮盘赌
var PopScore = adaptCount(Pop,C);//获取适应度分数,一维,长度为种群大小
var scoreArr = new Array();//百分比适应度记录数组
var totalScore=0;
for(var i=0;i<PopScore.length;i++){
totalScore+=PopScore[i];
}
for(var i=0;i<PopScore.length;i++){
scoreArr.push(PopScore[i]/totalScore);
}
for(var i=1;i<PopScore.length;i++){
scoreArr[i]=scoreArr[i]+scoreArr[i-1];
}
var childPop=new Array();
while(childPop.length<GeneNum){
//轮盘赌
var father,mother;
var randomNum=Math.random();
for(var i=0;i<GeneNum;i++){
if(randomNum<=scoreArr[i]){
father=i;
break;
}
}
randomNum=Math.random();
for(var i=0;i<GeneNum;i++){
if(randomNum<=scoreArr[i]){
mother=i;
break;
}
}
//交叉遗传
var child = getChild(PopCode[father],PopCode[mother]);//单个子代编码
//解码
var childArr = DeCoder(child,goodsNum);//单个子代数组模式
//判断是否满足要求,即遍历所有位置,且仅有一次
if(childKiller(childArr,goodsNum)){
childPop.push(childArr);
}else{continue;}
}
Pop=childPop;
//变异
Pop=getChange(Pop);
}
//计算适应度最大并绘制图样
PopScore = adaptCount(Pop,C);
var maxIndexOf=PopScore.indexOf(Math.max.apply(Math, PopScore));
drawPic(goodsLocation,Pop[maxIndexOf]);
}
//初始化货物位置
function initGoodsLocation(goodsNum){
var goodsLocation=new Array();
for(var i=0;i<goodsNum;i++){
goodsLocation.push(Math.random()*500);//x坐标
goodsLocation.push(Math.random()*500);//y坐标
}
return goodsLocation;
}
//绘制货物方法
function drawGoods(goodsLocation,goodsNum){
context.fillStyle="black";
context.font='25px Arial';
for(var i=0;i<goodsNum;i++){
context.fillText(i+1,goodsLocation[2*i],goodsLocation[2*i+1]);
}
}
//生成距离矩阵C
function distanceArr(goodsLocation,goodsNum){
var Arr = new Array(goodsNum);
for(var i=0;i<goodsNum;i++){
var Arr2 = new Array(goodsNum);
Arr[i]=Arr2;
}
for(var i=0;i<goodsNum;i++){
for(var j=i;j<goodsNum;j++){
if(j==i){
Arr[i][j]=0;
}else{
var x_x=parseFloat(goodsLocation[2*i]-goodsLocation[2*j]);
var y_y=parseFloat(goodsLocation[2*i+1]-goodsLocation[2*j+1]);
var distance = Math.pow(x_x,2)+Math.pow(y_y,2);
Arr[i][j]=Math.sqrt(distance);
Arr[j][i]=Arr[i][j];
}
}
}
return Arr;
}
//初代种群生成
function initPopulation(goodsNum,GeneNum){
var initArrReturn = new Array();
for(var j=0;j<GeneNum;j++){
var initArr = new Array();
for(var i=0;i<goodsNum;i++){
initArr.push(i);
}
for(var i=0;i<initArr.length;i++){
var rand=parseInt(Math.random () *initArr.length);
var k=initArr[i];
initArr[i]=initArr[rand];
initArr[rand]=k;
}
initArrReturn.push(initArr);
}
return initArrReturn;
}
//适应度得分
function adaptCount(initPop,C){
var adaptArr = new Array();
for(var i=0;i<initPop.length;i++){
var sum=0;
for(var j=0;j<initPop[i].length-1;j++){
sum+=parseFloat(C[initPop[i][j]][initPop[i][j+1]]);
}
if(i==initPop.length[i]-1){
sum+=parseFloat(C[initPop[i][initPop.length[i]-1]][initPop[i][0]]);
}
adaptArr.push(1/sum);
}
return adaptArr;
}
//绘制直线
function drawPic(goodsLocation,Pop){
for(var i=0;i<Pop.length-1;i++){
context.beginPath();
context.moveTo(goodsLocation[2*Pop[i]],goodsLocation[2*Pop[i]+1]);
context.lineTo(goodsLocation[2*Pop[i+1]],goodsLocation[2*Pop[i+1]+1]);
context.lineWidth=1;
context.strokeStyle="red";
context.stroke();
context.closePath();
}
context.beginPath();
context.moveTo(goodsLocation[2*Pop[Pop.length-1]],goodsLocation[2*Pop[Pop.length-1]+1]);
context.lineTo(goodsLocation[2*Pop[0]],goodsLocation[(2*Pop[0])+1]);
context.lineWidth=1;
context.strokeStyle="red";
context.stroke();
context.closePath();
}
//编码
function coder(Pop,goodsNum){
var coderReturn = new Array();
//定义需要几位二进制数
var codeNum = 1;
var goodsNum_code=parseInt(goodsNum);
while(goodsNum_code>=2){
goodsNum_code=parseInt(goodsNum_code/2);
codeNum++;
}
for(var i=0;i<Pop.length;i++){
var codeString="";
for(var j=0;j<Pop[i].length;j++){
var codeStringPart = Pop[i][j].toString(2);
if(codeStringPart.length<codeNum){
for(var k=0;k<(parseInt(codeNum)-codeStringPart.length+1);k++){
codeStringPart="0"+codeStringPart;
}
}
codeString+=codeStringPart;
}
coderReturn.push(codeString);
}
return coderReturn;
}
//通过两个编码即父母,获得子代编码
function getChild(father,mother){
var fatherArr = father.split("");
var motherArr = mother.split("");
var child="";
for(var i=0;i<fatherArr.length;i++){
if(Math.random()>0.5){
child+=fatherArr[i];
}else{
child+=motherArr[i];
}
}
return child;
}
//解码
function DeCoder(child,goodsNum){
var coderReturn = new Array();
//定义需要几位二进制数
var codeNum = 1;
var goodsNum_code=parseInt(goodsNum);
while(goodsNum_code>=2){
goodsNum_code=parseInt(goodsNum_code/2);
codeNum++;
}
var childArr = child.split("");
for(var i=0;i<childArr.length/codeNum;i++){
var numBy2="";//二进制数拆分
for(var j=0;j<codeNum;j++){
numBy2+=childArr[i*codeNum+j];
}
coderReturn.push(parseInt(numBy2,2));
}
return coderReturn;
}
//判断子代是否符合要求
//sort()慎用!!!直接改变了原数组的顺序,所以该方法中赋值不能直接childArr_help=childArr,储存地址指向同一位置
function childKiller(childArr,goodsNum){
var childArr_help=new Array();
for(var i=0;i<childArr.length;i++){
childArr_help[i]=childArr[i];
}
childArr_help=childArr_help.sort();
for(var i=0;i<childArr_help.length;i++){
if(childArr_help[i]>=goodsNum){
return false;
}
}
for(var i=1;i<childArr_help.length;i++){
if(childArr_help[i]==childArr_help[i-1]){
return false;
}
}
return true;
}
//变异,种群变异率这里定义为0.1,基因变异为一对
function getChange(Pop){
var changeRate=0.1;
for(var i=0;i<Pop.length*changeRate;i++){
var changePop = parseInt(Math.random()*Pop.length)
var gene = Pop[changePop];
var x = parseInt(Math.random()*Pop[i].length);
var y = parseInt(Math.random()*Pop[i].length);
var genePart = gene[x];
gene[x]=gene[y];
gene[y]=genePart;
Pop[changePop]=gene;
}
return Pop;
}
</script>
</body>
</html>
对于此题,首先需要架构环境,利用html自带的canvas进行绘制,并写好网页的各框等。
初始化问题:需要生成货物,假设有N个,货物是由X,Y坐标定义的,定义一个一位数组goodsLocation储存所有点的距离[X1,Y1,X2,Y2...],再定义一个N阶方阵(即N×N的二维数组)记录点与点之间的距离,距离矩阵为C,比如点1和点2之间的距离调用C[0][0]即可(注意下标)。
生成初代种群,用二维数组Pop记录,Pop第一维大小为种群大小即个体数,第二维大小为货物数,比如该种群我们定义2个个体,有5个货物,Pop的形式如[[1,2,3,4,5],[2,3,5,1,4]],对应两种解法--第一种先捡1号货物,再捡2号、3号、4号、5号,第二种解法是先捡2号,再3、5、1、4号。之后个体的储存形式都是如此。
定义适应度函数,计算每个个体的适应度大小。最后将适应度最大的个体在图中绘制,作为初代最优解。
适应度函数:一般来讲,遗传算法的适应度函数定义为越大适应度越好,此题中目标是距离最小,而距离和与目标相反,所以这里的适应度取距离和的倒数。
接下来就是遗传算法的核心,进行编码,轮盘赌策略进行选择,基因交叉,解码生成子代,淘汰不合要求子代,最后变异。
编码:生成二进制的过程。比如现有2个个体[1,2,3,4,5]和[2,3,5,1,4],1生成001,2生成010...所以编码生成001010011100101,010011101001100,此处注意二进制化过程中,1默认生成1,2生成10,4生成100,它们的位数不一样,所以为了统一,我们补零,生成位数一样的两个个体。
轮盘赌进行选择:轮盘赌不想多解释,不懂自行百度
选择2个个体进行交叉:比如001010011100101,010011101001100这两个个体,第一位均为0,无论怎么交叉仍为0,第二位的数字则不一样了,随机选择一位生成子代
解码:假设生成了001001001001001001的子代,解码就是编码的逆过程,生成[1,1,1,1,1]
淘汰过程:题目要求遍历所有点且只能一次,所以[1,1,1,1,1]的子代是不合要求的,进行淘汰,只有当所有数字有且仅出现一次才留存。同时,还有可能生成形同[1,1,1,1,7]的子代,为什么?因为三位编码最大值为111(二进制),对应7(十进制),对于超过N范畴的子代同样淘汰。
一直执行编码、交叉,解码,淘汰的过程直到生成N个子代个体,生成了满足种群数量的子代种群
变异:首先需要确定变异率和变异策略,这段代码中变异率为0.1,即10个个体中会有一个变异,变异策略为其中一对基因变异,比如变异个体开始是[1,2,3,4,5],随机取两个位置进行调换生成[2,1,3,4,5],作为变异个体。
至此,子代种群生成了,执行适应度函数,评价适应度,适应度最大值最为当前最优解。
遗传算法就实现了!!!
接下来是遗传算法更深层次的理解:
不断迭代100次,你会发现一个问题:结果不稳定
思考:不稳定应该是由变异造成的,所以写了一份(变异率)遗传算法解决机器人拣货路径.html,可改变变异率,当变异率设定为0时,你会发现在有限次迭代后,结果稳定下来,不再改变,因为种群统一了,即所有的个体都是一样的,如果采用胜者为王策略这种收敛速度会更快,但是这个解是局部最优解。
局部最优解是遗传算法的一大弊端,所以这是变异存在的原因:变异提供了一种跳出局部最优解的策略。但在我这个程序中,由于变异有可能将当前最优解反而变异掉了,得不偿失!
思考如何化解,首先想到是改变适应度函数,参见(适应度函数)遗传算法解决机器人拣货路径.html,适应度函数计算为总距离和的倒数,第一想法会不会因为由于倒数过小反而影响了决策,所以更换为“常数-总距离”作为新适应度函数。写完之后一想,完全不如倒数的方法,懂点数学应该都能想到。
再次思考,改良遗传算法,参见:结合禁忌算法中“禁忌表”思维,那就记录下每代中最佳的个体,保证最佳个体完全出现在后一代中,即保证其绝对不交叉与变异。当出现更加优秀的个体,再记录这个更优秀的个体。这样就能保证子代的最优解永远不低于父代的最优解。同时变异策略改变,之前的变异策略为更改一对,比如[1,2,3,4,5]变成[2,1,3,4,5](改变其中2个数的位置),现在随机改变对数,比如随机改变3次,[1,2,3,4,5]->[2,1,3,4,5]->[2,3,1,4,5]->[2,3,1,5,4],这样就能加快找到全局最优解的速度。可打开(改良)遗传算法解决机器人拣货路径.html,测试一下是不是收敛状况与速度都较之前有了很大的提升(改良遗传算法的方法有很多,比如神经网络+遗传算法等,智者见智吧)
结论:
1.适应度函数将影响遗传算法的收敛速度和性能(教授给我说是:适应度函数由问题和建立的模型决定)
2.遗传算法中选择策略、交叉策略等直接决定了算法的优劣。所以同样的问题,同样的遗传算法,不同的人设计出来性能完全不一样。
3.变异是为了跳出局部最优解,变异策略和变异率影响跳出速度。
4.若没有变异,遗传算法迭代到最后会生成完全一样的个体组成的种群。该解可能只是局部最优解,而当种群大小最够大或者说无穷大时,迭代无数代后总会生成全局最优解。(所以遗传算法需要考虑种群大小和迭代次数)
如有疑惑或者想要交流,可以给我留言,我会尽快回复您,如有错误,请指出,谢谢!