遗传算法概念:
基于达尔文的进化论,物竞天择,适者生存;认为生物总是向着更加贴合于环境的方向进化;通过各种基因的遗传、杂交、变异、复制等手段,慢慢使整个种群更加贴合于自然环境;遗传算法也是模拟生物的遗传、杂交、变异、复制手段逐渐进化为最优解!
名词概念解析:
基因和染色体:
染色体在数学建模上可以看作是可行解,例如 3x+4y+5z<100,它的可行解为[1,2,3]、[1,3,2]、[3,2,1],那么这个可行解就是它的染色体。每一个染色体有无数个基因控制,所以在这些可行解中,里面的每一元素称之为基因。
适应度函数:
在自然界中,生物的进化总是遵循优胜略汰,适者生存;一些适应不了环境的个体和染色体逐渐被淘汰,而那些基因强大,能够很好的适应环境的个体和染色体则被保留和遗传;同样遗传算法 中的适应度函数就扮演者这个
优胜略汰,适者生存
的角色,在运行过程中,会进行多次迭代,每一迭代都会产生多个染色体即可行解,适应度函数会为每一个染色体打分,适应度高的保留,适应度低的淘汰,这样会使整个解种群的染色体更加优良。更加贴近环境。
交叉:
遗传算法每一次迭代都会产生n条染色体,我们称之为进化,每一次进化所需要的染色体从哪来,这里就要引入交叉的概念,可以理解为交配;交叉过程需要选择两条染色体,一个充当母亲,一个充当父亲;将这两条染色体从某一结点切开,再组合在一起,组成一个新的染色体;这样新染色体既包含了父亲的其因也包含了母亲的基因。
但是如何从上一代选出父母基因呢!可以通过轮盘赌算法来实现适应度越高的染色体被选中的概率越高,公式如下:
染色体i被选择的概率 = 染色体i的适应度 / 所有染色体的适应度之和
变异:
交叉能保证每次进化留下优良的基因,但它仅仅是对原有的结果集进行选择,基因还是那么几个,只不过交换了他们的组合顺序。这只能保证经过N次进化后,计算结果更接近于局部最优解,而永远没办法达到全局最优解,为了解决这一个问题,我们需要引入变异。变异很好理解。当我们通过交叉生成了一条新的染色体后,需要在新染色体上随机选择若干个基因,然后随机修改基因的值,从而给现有的染色体引入了新的基因,突破了当前搜索的限制,更有利于算法寻找到全局最优解。
复制
为了保证上一代的优良染色体,会将上一代适应度最高的几条染色体原封不动的复制给下一代
算法思路
- 算法初期阶段会随机生成一组可行解,就是祖先代染色体
- 采用适应度函数计算每一条染色体的适应度,并根据适应度计算再下一次进化中被选中当作父体母体的概率。
- 通过交叉产生N-M条染色体
- 对这N-M条染色体进行变异操作。
- 通过复制生成M条染色体。
至此N条染色体生成完毕,紧接着再次计算适应度和选中概率重复2-5步骤,直至限定进化次数或者误差允许范围为止。
实例
问题适应度:要求函数的最小值,那么就要使得到结果越小的个体,适应度越高。
精度:想要精确到小数点后6位,那么定义域的区间至少需要划分成6*10^6等份,再求出表示这个量级所需要的二进制位数,作为基因的位数。
代码详解
初始化,第一代染色体编码
package com.geneticalgorithm;
/**
* 初始化群体,进行染色体编码
* @author 皇甫
*/
public class Initialization {
/**
* 初始化一条染色体,随机产生二进制染色体基因
* @param GENE 基因位数
* @return 染色体
*/
public String initSingle(int GENE){
StringBuilder res = new StringBuilder();
for(int i = 0;i<GENE;i++){
if(Math.random()<0.5){
res.append(0);
}else{
res.append(1);
}
}
return res.toString();
}
/**
* 初始化一组染色体,即为祖先染色体,第一代
* @param GENE 基因个数
* @param groupSize 染色体个数
* @return 一组染色体
*/
public String[] initGroup(int GENE,int groupSize){
String[] isAll = new String[groupSize];
for (int i =0;i<groupSize;i++){
isAll[i] = initSingle(GENE);
}
return isAll;
}
}
解码类,将二进制转换为十进制
package com.geneticalgorithm;
/**
* 基因解码,将原始基因的二进制转换为十进制
* @author 皇甫
*/
public class Decoding {
public double[] decoding(String single,int GENE){
//将字符串分割成两个基因 x y
int a = Integer.parseInt(single.substring(0, GENE/2),2 );
int b = Integer.parseInt(single.substring(GENE/2,GENE),2 );
double[] x = new double[2];
x[0] = a*(6.0-0)/(Math.pow(2, GENE/2)-1);
x[1] = b*(6.0-0)/(Math.pow(2, GENE/2)-1);
return x;
}
}
计算个体适应度
package com.geneticalgorithm;
/**
* 计算适应度函数
* @author 皇甫
*/
public class Adaptability {
/**
* 计算个体的适应度,返回值越大,则3-返回值越小
* @param single
* @param GENE
* @return
*/
public double calculationFitness(String single,int GENE){
//解码
Decoding decoding = new Decoding();
double[] xyValue = decoding.decoding(single, GENE);
double yValue = Math.sin(2*xyValue[0])*Math.sin(2*xyValue[0])+Math.sin(2*xyValue[1])*Math.sin(2*xyValue[1]);
return yValue;
}
/**
* 批量计算适应度
* @param singles 染色体
* @param GENE 基因数目
* @return 群体适应度
*/
public double[] calculationFitnessAll(String[] singles,int GENE){
double [] fit = new double[singles.length];
for(int i = 0;i<singles.length;i++){
fit[i] = calculationFitness(singles[i],GENE );
}
return fit;
}
/**
* 计算出所有染色体中适应度最大的个体,输出下标,即结果最大值下标
* @param fit 群体适应度
* @return 极值下标
*/
public int extremumSubscript(double[] fit){
double max = fit[0];
int n = 0;
for(int i = 1 ;i<fit.length;i++){
if(fit[i]>max){
max = fit[0];
n=i;
}
}
return n;
}
/**
* 返回适应度最大值
* @param fit
* @return
*/
public double extremumValue(double[] fit){
return fit[extremumSubscript(fit)];
}
}
轮盘赌算法选择适应度高的群体
package com.geneticalgorithm;
/**
* 选择父体母体 即适应度越大,被选择的几率越大,使用轮盘赌算法,为了保证群体总数,被淘汰的
* 个体由随机新个体替代
* @author 皇甫
*/
public class Select {
private Initialization initialization = new Initialization();
private Adaptability adaptability = new Adaptability();
/**
* 轮盘赌算法选择适应度高的群体
* @param group 群体染色体
* @param GENE 基因数
* @return
*/
public String[] newGroup(String[] group,int GENE){
//初始化染色体概率数组
double[] probability = new double[group.length];
//初始化新群体数组
String[] newGroup = new String[group.length];
//适应度和详见计算适应度的公式 文档中有
double f = 0;
//得到适应度数组并求和
double[] doubles = adaptability.calculationFitnessAll(group, GENE);
for (double aDouble : doubles) {
f+=aDouble;
}
//将选中概率填充到概率数组
for (int i=0;i<doubles.length;i++) {
probability[i] = doubles[i]/f;
}
//求出极值下标
int maxNum = adaptability.extremumSubscript(doubles);
//求出适应度最大个体
String valueMax = group[maxNum];
//开始转动轮盘
for(int i =0;i<doubles.length;i++){
double r = Math.random();
//累计概率
int q = 0;
//适应度最大的个体直接继承
if(i == maxNum){
newGroup[i] = valueMax;
//将本个体选中概率置为0
probability[i] = 0;
}
//开始寻找轮盘指针再那个区域
for(int j=0;j<doubles.length;j++){
q+=probability[j];
if(r<q){
//如果被选中,保留进下一代,将选中概率置为0
newGroup[i] = group[j];
probability[j] = 0;
break;
}
//没有被选中就重新生成新染色体
newGroup[i] = initialization.initSingle(GENE);
}
}
return newGroup;
}
}
父体母体进行交叉
package com.geneticalgorithm;
/**
* 父体母体进行交叉
* 染色体依次两两配对,随机在一对染色体上选取一点分成两段,然后互换重组为新的两条染色体。
* @author 皇甫
*/
public class Cross {
private Adaptability adaptability = new Adaptability();
/**
* 交叉
* @param group 种群
* @param GENE 基因
* @param crossRate
* @return
*/
public String[] cross(String[] group,int GENE,double crossRate){
String temp1,temp2;
int pos = 0;
//获取适应度数组
double[] fit = adaptability.calculationFitnessAll(group, GENE);
//计算极值序号
int index = adaptability.extremumSubscript(fit);
//获取适应度最大的染色体
String maxStr = group[index];
for(int i = 0; i < group.length; i++){
if(Math.random() < crossRate){
//交叉点
pos = (int)(Math.random()*GENE) + 1;
//用来防止数组越界
temp1 = group[i].substring(0, pos) + group[(i+1) % group.length].substring(pos);
temp2 = group[(i+1) % group.length].substring(0, pos) + group[i].substring(pos);
group[i] = temp1;
group[(i+1) % group.length] = temp2;
}
}
group[0] = maxStr;
return group;
}
}
变异,在染色体上随机选取一位,翻转其二进制位
package com.geneticalgorithm;
/**
* 变异
* 在染色体上随机选取一位,翻转其二进制位
* @author 皇甫
*/
public class Variation {
/**
*
* @param str 即将要改动的字符串
* @param num 改动哪一位
* @param pos 改成什么
* @return
*/
public String replacePos(String str,int num,String pos){
String temp;
if(num == 0){
temp = pos + str.substring(1);
}else if(num == str.length()-1){
temp = str.substring(0, str.length() - 1) + pos;
}else{
String temp1 = str.substring(0, num);
String temp2 = str.substring(num + 1);
temp = temp1 + pos + temp2;
}
return temp;
}
public String[] mutation(String[] group,int GENE,double MP){
Adaptability fitness = new Adaptability();
double[] fit = fitness.calculationFitnessAll(group,GENE);
//计算适应度最大的染色体序号
int mFitNum = fitness.extremumSubscript(fit);
String max = group[mFitNum];
for(int i = 0; i < group.length * MP; i++){
//从[0,GENE * group.length)区间取随机数
int n = (int) (Math.random() * GENE * group.length );
//取得的染色体数组下标
int chrNum = (int) (n / GENE);
//取得的基因下标
int gNum = (int)(n % GENE);
String temp = "";
if(group[chrNum].charAt(gNum) == '0' ){
temp = replacePos(group[chrNum], gNum, "1");
}else{
temp = replacePos(group[chrNum], gNum, "0");
}
group[chrNum] = temp;
}
group[0] = max;
return group;
}
}
测试
package com.geneticalgorithm;
public class GAmain {
/**染色体数(群体中个体数)*/
public static final int groupsize = 10;
/**变异概率*/
public static final double MP = 0.15;
/**交叉概率*/
public static final double CP = 0.6;
/**迭代次数*/
public static final int ITERA = 1000;
/**精确度,选择精确到小数点后几位*/
public static final int accuracy = 8;
/**求出精度对应的所需基因数*/
int temp = (int) ((int)Math.log(6)+ accuracy*Math.log(10) );
//基因数
int GENE = temp * 2;
/**
* 输出原群体和适应度,测试用
* */
public void outAll(String[] group){
Adaptability fitness = new Adaptability();
System.out.println("原群体");
for(String str:group){
System.out.println(str);
}
double fit[] = fitness.calculationFitnessAll(group,GENE);
System.out.println("原群体适应度");
for(double str:fit){
System.out.println(str);
}
}
/**输出适应度最大值,以及返回最优的个体,测试用*/
public int outMax(String str,String[] group){
Adaptability fitness = new Adaptability();
double[] fit = fitness.calculationFitnessAll(group,GENE);
double max = fitness.extremumValue(fit);
System.out.println(str+"后适应度最大值为"+max);
return fitness.extremumSubscript(fit);
}
public static void main(String[] args) {
Initialization init = new Initialization();
Adaptability fitness = new Adaptability();
Select rws = new Select();
Cross cross = new Cross();
Variation mutation = new Variation();
Decoding decode = new Decoding();
GAmain ga = new GAmain();
//初始化
String group[] = init.initGroup(ga.GENE,groupsize);
//输出原群体的适应度
//ga.outAll(group);
for(int i = 0; i < ITERA; i++){
//选择
group = rws.newGroup(group, ga.GENE);
/**输出适应度最大值,以及返回最优的个体,测试用*/
//ga.outMax("选择",group);
//交叉
group = cross.cross(group,ga.GENE,CP);
/**输出适应度最大值,以及返回最优的个体,测试用*/
// ga.outMax("交叉",group);
//变异
group = mutation.mutation(group, ga.GENE, MP);
/**输出适应度最大值,以及返回最优的个体,测试用*/
//ga.outMax("变异",group);
// System.out.println("");
}
int max = ga.outMax("完成", group);
double best[] = decode.decoding(group[max], ga.GENE); //解码
double result = 3-fitness.calculationFitness(group[max], ga.GENE);
System.out.println("x1 = "+best[0]+"\n"+"x2 = "+best[1]);
System.out.println("最小值为"+result);
}
}
结果集