算法(二)之遗传算法(SGA)
遗传算法(Genetic Algorithm)又叫基因进化算法或进化算法,是模拟达尔文的遗传选择和自然淘汰的生物进化过程的计算模型,属于启发式搜索算法一种。
下面通过下面例子的求解,来逐步认识遗传算法的操作过程。我参考了博客(http://blog.csdn.net/b2b160/article/details/4680853/),这个博客没提供代码,为了新手更好的学习,我用java实现了程序
例:求下述二元函数的最大值:
(1) 个体编码
遗传算法的运算对象是表示个体的符号串,所以必须把变量 x1, x2 编码为一种
符号串。本题中,用无符号二进制整数来表示。
因 x1, x2 为 0 ~ 7之间的整数,所以分别用3位无符号二进制整数来表示,将它们连接在一起所组成的6位无符号二进制数就形成了个体的基因型,表示一个可行解。
例如,基因型 X=101110 所对应的表现型是:x=[ 5,6 ]。
个体的表现型x和基因型X之间可通过编码和解码程序相互转换。
1 /** 2 * 3 */ 4 package com.math.algorithm; 5 6 /** 7 * @author summer 8 * 9 */ 10 public class Codec { 11 12 static final int CODEC_LEN = 3; 13 14 public static String encode(int x,int y){ 15 16 return MathUtils.toBinaryString(x,CODEC_LEN)+ 17 MathUtils.toBinaryString(y,CODEC_LEN); 18 } 19 20 public static double[] decode(String s){ 21 22 double[] r = new double[2]; 23 String s1 = s.substring(0, s.length()/2) ; 24 String s2 = s.substring(s1.length()); 25 r[0] = MathUtils.toInt(s1); 26 r[1] = MathUtils.toInt(s2); 27 return r; 28 } 29 30 public static void main(String[] args){ 31 32 System.out.println(encode(5,6)); 33 System.out.println(encode(1,2)); 34 double[] r =decode("101110"); 35 System.out.println("x="+r[0]+" y="+r[1]); 36 r =decode("001010"); 37 System.out.println("x="+r[0]+" y="+r[1]); 38 } 39 40 }
(2) 初始群体的产生
遗传算法是对群体进行的进化操作,需要给其淮备一些表示起始搜索点的初始
群体数据。
本例中,群体规模的大小取为4,即群体由4个个体组成,每个个体可通过随机
方法产生。
如:011101,101011,011100,111001
/** * */ package com.math.algorithm; import java.util.List; import java.util.Random; /** * @author summer * */ public class GeneGroupInit { static final int SIZE = 4; static final int MAX_VAL = 8; public static void init(List<String> gene){ gene.clear(); Random r = new Random(); for(int i=0;i<SIZE;i++){ int x = r.nextInt(MAX_VAL); if(x ==0) x = r.nextInt(MAX_VAL); int y = r.nextInt(MAX_VAL); if( y ==0) y = r.nextInt(MAX_VAL); System.out.println("init x="+x+" y="+y + " norm="+(MathUtils.norm(x,y))); gene.add(Codec.encode(x,y)); } } }
(3) 适应度汁算
遗传算法中以个体适应度的大小来评定各个个体的优劣程度,从而决定其遗传
机会的大小。
本例中,目标函数总取非负值,并且是以求函数最大值为优化目标,故可直接
利用目标函数值作为个体的适应度。
public static double evaluate(double[] g){ return MathUtils.norm(g); }
(4) 选择运算
选择运算(或称为复制运算)把当前群体中适应度较高的个体按某种规则或模型遗传到下一代群体中。一般要求适应度较高的个体将有更多的机会遗传到下一代
群体中。
本例中,我们采用与适应度成正比的概率来确定各个个体复制到下一代群体中
的数量。其具体操作过程是:
• 先计算出群体中所有个体的适应度的总和 Sfi ( i=1.2,…,M );
• 其次计算出每个个体的相对适应度的大小 fi / Sfi ,它即为每个个体被遗传
到下一代群体中的概率,
• 每个概率值组成一个区域,全部概率值之和为1;
• 最后再产生一个0到1之间的随机数,依据该随机数出现在上述哪一个概率区
域内来确定各个个体被选中的次数。
package com.math.algorithm; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * @author summer * */ public class Choose { public static double evaluate(double[] g){ return MathUtils.norm(g); } public static double decodeAndEvaluate(String s){ double[] e = Codec.decode(s); return evaluate(e); } public static List<String> choose(List<String> gene){ List<String> cgene = new ArrayList<String>(); int[] index = new int[gene.size()]; double[] evals = new double[gene.size()]; double sum = 0; double x =0; int idx = 0; Random r= new Random(); for(int i=0;i<gene.size();i++){ double eval = decodeAndEvaluate(gene.get(i)); evals[i] = eval; if(x<eval){ x = eval; idx = i; } sum += eval; } index[0] = idx; double prop = 0; for(int i=0;i<evals.length;i++){ prop += evals[i]; evals[i] = prop/sum; } for(int i=1;i<gene.size();i++){ double t = r.nextDouble(); int j; for(j=0;j<evals.length;j++){ if(t < evals[j]){ break; } } index[i] = j; } cgene.addAll(gene); gene.clear(); for(int i:index){ gene.add(cgene.get(i)); } return cgene; } }
(5) 交叉运算
交叉运算是遗传算法中产生新个体的主要操作过程,它以某一概率相互交换某
两个个体之间的部分染色体。
本例采用单点交叉的方法,其具体操作过程是:
• 先对群体进行随机配对;
• 其次随机设置交叉点位置;
• 最后再相互交换配对染色体之间的部分基因。
1 /** 2 * 3 */ 4 package com.math.algorithm; 5 6 import java.util.ArrayList; 7 import java.util.Collections; 8 import java.util.List; 9 import java.util.Random; 10 11 import org.apache.commons.lang.StringUtils; 12 13 /** 14 * 交叉算子 15 * @author summer 16 * 17 */ 18 public class OnePointCrossover { 19 20 public static List<String> cross(List<String> gene,double pcross){ 21 22 Random r = new Random(); 23 List<String> genes = new ArrayList<String>(); 24 genes.addAll(gene); 25 //Collections.shuffle(genes); 26 int pos ; 27 int size = genes.get(0).length(); 28 String[] parent = new String[2]; 29 String[] son ; 30 31 gene.clear(); 32 for(int i=0;i<genes.size();i=i+2){ 33 34 parent[0] = genes.get(i); 35 parent[1] = genes.get(i+1); 36 37 if(MathUtils.check(pcross,0.000001)){ 38 pos = r.nextInt(size-1)+1; 39 }else{ 40 pos = size; 41 } 42 43 if(pos == size){ 44 son = parent; 45 }else{ 46 son = operate(parent,pos); 47 } 48 49 Collections.addAll(gene, son); 50 } 51 return genes; 52 } 53 //110100 000100 54 //001000 111000 55 56 private static String[] operate(String[] parent,int pos){ 57 58 String[] son = new String[2]; 59 son[0] = StringUtils.overlay(parent[0],StringUtils.left(parent[1],pos),0,pos); 60 son[1] = StringUtils.overlay(parent[1],StringUtils.left(parent[0],pos),0,pos); 61 62 return son; 63 } 64 65 66 67 public static void main(String[] args){ 68 List<String> parent= new ArrayList<String>(); 69 Collections.addAll(parent,new String[]{"110111","001000","101110","010001"}); 70 GeneticAlgorithm.print(parent); 71 List<String> son = cross(parent,0.85); 72 System.out.println(son); 73 System.out.println(parent); 74 } 75 }
(6) 变异运算
变异运算是对个体的某一个或某一些基因座上的基因值按某一较小的概率进
行改变,它也是产生新个体的一种操作方法。
本例中,我们采用基本位变异的方法来进行变异运算,其具体操作过程是:
• 首先确定出各个个体的基因变异位置,下表所示为随机产生的变异点位置,
其中的数字表示变异点设置在该基因座处;
• 然后依照某一概率将变异点的原有基因值取反。
对群体P(t)进行一轮选择、交叉、变异运算之后可得到新一代的群体p(t+1)。
1 /** 2 * 3 */ 4 package com.math.algorithm; 5 6 import java.util.ArrayList; 7 import java.util.List; 8 import java.util.Random; 9 10 /** 11 * @author summer 12 * 13 */ 14 public class Mutation { 15 16 static Random rnd = new Random(); 17 18 public static List<String> mutation(List<String> gene,double MUTATION_RATE){ 19 20 List<String> genes = new ArrayList<String>(); 21 genes.addAll(gene); 22 int pos; 23 for(int i=0;i<gene.size();i++){ 24 25 if(check(MUTATION_RATE)){ 26 27 String g = gene.get(i); 28 pos = rnd.nextInt(g.length()); 29 gene.set(i, operate(g,pos)); 30 } 31 } 32 return genes; 33 } 34 35 private static String operate(String g,int pos){ 36 37 if(pos > g.length() || pos<0) 38 return g; 39 else{ 40 char[] gc = g.toCharArray(); 41 if(gc[pos] == '0') 42 gc[pos] = '1'; 43 else{ 44 gc[pos] = '0'; 45 } 46 47 return new String(gc); 48 } 49 } 50 51 public static boolean check(double pcross){ 52 53 return MathUtils.check(pcross, 0.00000001); 54 } 55 56 public static void main(String[] args){ 57 58 String s ="010101"; 59 s = operate(s,2); 60 System.out.println(s); 61 s = operate(s,3); 62 System.out.println(s); 63 } 64 }
从上表中可以看出,群体经过一代进化之后,其适应度的最大值、平均值都得
到了明显的改进。事实上,这里已经找到了最佳个体“111111”。
1 /** 2 * 3 */ 4 package com.math.algorithm; 5 6 import java.util.ArrayList; 7 import java.util.HashMap; 8 import java.util.List; 9 import java.util.Map; 10 11 12 /** 13 * @author summer 14 * 15 */ 16 public class GeneticAlgorithm { 17 18 19 static final double CROSS_RATE = 0.85; 20 static final double MUTATION_RATE = 0.003; 21 static List<String> gene = new ArrayList<String>(GeneGroupInit.SIZE); 22 23 public static void init(){ 24 25 GeneGroupInit.init(gene); 26 } 27 28 public static List<String> choose(){ 29 30 return Choose.choose(gene); 31 } 32 33 public static List<String> cross(){ 34 35 return OnePointCrossover.cross(gene, CROSS_RATE); 36 } 37 38 public static List<String> mutation(){ 39 40 return Mutation.mutation(gene,MUTATION_RATE); 41 } 42 43 static void print(){ 44 for(String s:gene) 45 System.out.println(s); 46 } 47 48 static void print(List<String> cgene){ 49 for(String s:cgene) 50 System.out.println(s); 51 } 52 53 public static void computer(){ 54 55 System.out.println("+++++++++++++init+++++++++++++"); 56 init(); 57 print(); 58 System.out.println("+++++++++++++choose+++++++++++++"); 59 List<String> gene = choose(); 60 print(gene); 61 System.out.println("-----------"); 62 print(); 63 System.out.println("+++++++++++++cross+++++++++++++"); 64 gene = cross(); 65 print(gene); 66 System.out.println("-----------"); 67 print(); 68 System.out.println("+++++++++++++mutation+++++++++++++"); 69 gene = mutation(); 70 print(gene); 71 System.out.println("-----------"); 72 print(); 73 } 74 75 public static void main(String[] args){ 76 77 final int N = 10; 78 Map<Integer,List<String>> result = new HashMap<Integer,List<String>>(); 79 for(int i=0;i<N;i++){ 80 System.out.println("************************************"); 81 computer(); 82 result.put(i,new ArrayList<String>(gene)); 83 } 84 Map<String,Double> evaluateMap = new HashMap<String,Double>(); 85 String idx = ""; 86 double max = 0; 87 double[] evaluate = new double[N]; 88 for(Map.Entry<Integer,List<String>> e : result.entrySet()){ 89 90 max = 0; 91 List<String> val = e.getValue(); 92 for(int i=0;i<val.size();i++){ 93 String v = val.get(i); 94 double eval = Choose.decodeAndEvaluate(v); 95 evaluate[i] = eval; 96 if(eval > max){ 97 max = eval; 98 idx = v; 99 } 100 101 } 102 evaluateMap.put(idx,max); 103 System.out.println(val + " max=" + max); 104 } 105 106 } 107 108 109 }
程序运算结果:
[110111, 110111, 110001, 110111] max=85.0 [110110, 110110, 110110, 110110] max=72.0 [011010, 101100, 101100, 100100] max=41.0 [001011, 111110, 011101, 111110] max=85.0 [111111, 111111, 101011, 111111] max=98.0 [100011, 101110, 100010, 101111] max=74.0 [110111, 110111, 101111, 110111] max=85.0 [111001, 010111, 010110, 111001] max=53.0 [010110, 100100, 100111, 011100] max=65.0 [111100, 110001, 111101, 011000] max=74.0
[注意]
需要说明的是,表中有些栏的数据是随机产生的。这里为了更好地说明问题,
我们特意选择了一些较好的数值以便能够得到较好的结果,而在实际运算过程中
有可能需要一定的循环次数才能达到这个最优结果。