算法学习之遗传算法
学人工智能这门课时老师要求我们完成一个遗传算法的实验。去机房前天晚上我忙于写作业,然后代码写到2点才写好。结果第二天睡过头。。。。白写了一晚上。
实验内容:
用遗传算法求函数f(x)=x2的最大值,其中x为[0,31]间的整数。
在网上查了下资料后,发现遗传算法的步骤主要如下:
(1) 个体编码
遗传算法的运算对象是表示个体的符号串,所以必须把变量 x 编码为一种
符号串。本题中,用无符号二进制整数来表示。
因 x 为 0 ~ 31之间的整数,所以分别用5位无符号二进制整数来表示,表示一个可
行解。
例如,基因型 X=11111 所对应的表现型是:x=[ 31]。
个体的表现型x和基因型X之间可通过编码和解码程序相互转换
(2) 初始群体的产生遗传算法是对群体进行的进化操作,需要给其淮备一些表示起始搜索点的初始群体数据。
本例中,群体规模的大小随便取为4,即群体由4个个体组成,每个个体可通过随机方法产生。
如:10000,10011,10010,10100
(3) 适应度汁算
遗传算法中以个体适应度的大小来评定各个个体的优劣程度,从而决定其遗传
机会的大小。
本例中,目标函数总取非负值,并且是以求函数最大值为优化目标,故可直接
利用目标函数值作为个体的适应度
//初始群体的产生 int N=4;//群体大小 body bs[]=init(N);//初始化种群 int sum=0;//总适应度 for(int i=0;i<N;i++){ sum+=bs[i].v; } for(int i=0;i<N;i++){ bs[i].rat=bs[i].v/(sum*1.0); }
初始化后的数据
个体编码:10000 (基因型)
个体数:16 (表现型)
适度:256 (x^2)
概率:0.19090231170768082(适度/总适度)
个体编码:10011
个体数:19
适度:361
概率:0.2692020879940343
个体编码:10010
个体数:18
适度:324
概率:0.24161073825503357
个体编码:10100
个体数:20
适度:400
概率:0.29828486204325133
(4) 选择运算
选择运算(或称为复制运算)把当前群体中适应度较高的个体按某种规则或模型遗传到下一代群体中。一般要求适应度较高的个体将有更多的机会遗传到下一代
群体中。
本例中,我们采用与适应度成正比的概率来确定各个个体复制到下一代群体中
的数量。其具体操作过程是:
• 先计算出群体中所有个体的适应度的总和 fi ( i=1.2,…,M );
• 其次计算出每个个体的相对适应度的大小 fi / fi ,它即为每个个体被遗传
到下一代群体中的概率,
• 每个概率值组成一个区域,全部概率值之和为1;(比如上面0-0.19为一个区域,0.19到0.19+0.26为一个区域,一次后推)
• 最后再产生4个0到1之间的随机数,依据该随机数出现在上述哪一个概率区
域内来确定下一轮被选中的个体。
//选择运算 body oldbs[]=bs;//先复制一份 double a=oldbs[0].rat; double b=oldbs[1].rat+a; double c=oldbs[2].rat+b; for(int i=0;i<N;i++){ double rand=Math.random(); if(rand<=a)bs[i]=oldbs[0]; if(rand>a&&rand<=b)bs[i]=oldbs[1]; if(rand>b&&rand<=c)bs[i]=oldbs[2]; if(rand>c&&rand<=1)bs[i]=oldbs[3]; } printbody(bs);
选择计算后的数据
个体编码:10000
个体数:16
适度:256
概率:0.19090231170768082
个体编码:10011
个体数:19
适度:361
概率:0.2692020879940343
个体编码:10100
个体数:20
适度:400
概率:0.29828486204325133
个体编码:10000
个体数:16
适度:256
概率:0.19090231170768082
(5) 交叉运算
交叉运算是遗传算法中产生新个体的主要操作过程,它以某一概率相互交换某
两个个体之间的部分染色体。
本例采用单点交叉的方法,其具体操作过程是:
• 先对群体进行随机配对;
• 其次随机设置交叉点位置;
• 最后再相互交换配对染色体之间的部分基因。
//交叉运算 (单点交叉) body oldbs1[] =bs; for (int i = 0; i < 4; i+=2) { int r1 = rand(1, 5); //System.out.println("交叉点:" + r1); String s1 = oldbs1[i].binary.substring(0, r1); String s2 = oldbs1[i].binary.substring(r1); String s3 = oldbs1[i + 1].binary.substring(0, r1); String s4 = oldbs1[i + 1].binary.substring(r1); //System.out.println(s1 + " " + s2 + " " + s3 + " " + s4); bs[i] = new body(s1 + s4); //System.out.println("b[i]" + bs[i].binary); bs[i + 1] = new body(s3 + s2); //System.out.println("b[i+1]" + bs[i + 1].binary); } printbody(bs);
交叉运算后的数据
个体编码:10011
个体数:19
适度:361
概率:0.0
个体编码:10000
个体数:16
适度:256
概率:0.0
个体编码:10000
个体数:16
适度:256
概率:0.0
个体编码:10100
个体数:20
适度:400
概率:0.0
(6) 变异运算
变异运算是对个体的某一个或某一些基因座上的基因值按某一较小的概率进
行改变,它也是产生新个体的一种操作方法。
本例中,我们采用基本位变异的方法来进行变异运算,其具体操作过程是:
• 首先确定出各个个体的基因变异位置,下表所示为随机产生的变异点位置,
其中的数字表示变异点设置在该基因座处;
• 然后依照某一概率将变异点的原有基因值取反。
//变异运算 double db=0.2;//变异概率 for (int i = 0; i < N; i++) { int is = rand(0, 6);// 变异位置 //System.out.println("变异位置"+is); if (Math.random() <= db) {//如果小于就变异 char[] cs = bs[i].binary.toCharArray(); if (cs[is] == '1') cs[is] = '0'; else cs[is] = '1'; bs[i] = new body(new String(cs)); } } printbody(bs);
变异运算后的数据
个体编码:10011
个体数:19
适度:361
概率:0.0
个体编码:10000
个体数:16
适度:256
概率:0.0
个体编码:10000
个体数:16
适度:256
概率:0.0
个体编码:10100
个体数:20
适度:400
概率:0.0
到这里一轮进化就完成了,这里发现一轮后的数据并没有比刚初始化的数据更接近解,于是可以弄个循环把这一轮的4个数据再来一次选择,交叉,变异,试试。直到出现最优解。
完整代码
package gh; import java.math.BigInteger; /** * 遗传算法求x^2最大值,x(0...31) * @author ganhang * */ public class Genetic { public static void main(String[] args) { Genetic g=new Genetic(); g.test(); } //测试 public void test(){ //初始群体的产生 int N=4;//群体大小 body bs[]=init(N);//初始化种群 int sum=0;//总适应度 for(int i=0;i<N;i++){ sum+=bs[i].v; } for(int i=0;i<N;i++){ bs[i].rat=bs[i].v/(sum*1.0); } printbody(bs); //选择运算 body oldbs[]=bs; double a=oldbs[0].rat; double b=oldbs[1].rat+a; double c=oldbs[2].rat+b; for(int i=0;i<N;i++){ double rand=Math.random(); if(rand<=a)bs[i]=oldbs[0]; if(rand>a&&rand<=b)bs[i]=oldbs[1]; if(rand>b&&rand<=c)bs[i]=oldbs[2]; if(rand>c&&rand<=1)bs[i]=oldbs[3]; } printbody(bs); //交叉运算 (单点交叉) body oldbs1[] =bs; for (int i = 0; i < 4; i+=2) { int r1 = rand(1, 5); System.out.println("交叉点:" + r1); String s1 = oldbs1[i].binary.substring(0, r1); String s2 = oldbs1[i].binary.substring(r1); String s3 = oldbs1[i + 1].binary.substring(0, r1); String s4 = oldbs1[i + 1].binary.substring(r1); System.out.println(s1 + " " + s2 + " " + s3 + " " + s4); bs[i] = new body(s1 + s4); //System.out.println("b[i]" + bs[i].binary); bs[i + 1] = new body(s3 + s2); //System.out.println("b[i+1]" + bs[i + 1].binary); } printbody(bs); //变异运算 double db=0.2;//变异概率 for (int i = 0; i < N; i++) { int is = rand(0, 6);// 变异位置 System.out.println("变异位置"+is); if (Math.random() <= db) {//如果小于就变异 char[] cs = bs[i].binary.toCharArray(); if (cs[is] == '1') cs[is] = '0'; else cs[is] = '1'; bs[i] = new body(new String(cs)); } } printbody(bs); } //输出 public void printbody(body[] bs){ for(body b:bs){ System.out.println("个体编码:"+b.binary); System.out.println("个体数:"+twoToten(b.binary)); System.out.println("适度:"+b.v); System.out.println("概率:"+b.rat); System.out.println(); } System.out.println("-------------------------"); } //初始群体的产生 n个数 public body[] init(int n){ body []bs = new body[n]; for(int i=0;i<n;i++){ bs[i]=new body(randBinary()); } return bs; } //适应度计算 y=x^2 public int count(int n){ return n*n; } //二进制转十进制 public int twoToten(String s){ BigInteger bi=new BigInteger(s, 2); return Integer.parseInt(bi.toString()); } //随机产生一个五位二进制 public String randBinary(){ StringBuilder sb=new StringBuilder(); for(int i=0;i<5;i++){ if(Math.random()>=0.5)sb.append(1); else sb.append(0); } return sb.toString(); } //返回a到b之间的数 public int rand(int a,int b){ return a+(int)(Math.random()*(b-a)); } //个体类 private class body{ String binary ; //二进制 int v;//适度 double rat;//个体的相对适应度的大小,即为被遗传到下一代群体中的概率 public body(String s){ this.binary=s; this.v=count(twoToten(s)); } } }