遗传算法是模拟生物在自然环境中的遗传和进化过程而形成的一种自适应全局优化概率搜索算法。它最早由美国密执安大学的 Holland 教授提出,起源于 60 年代对自然和人工自适应系统的研究。 70 年代De Jong基于遗传算法的思想在计算机上进行了大量的纯数假函数优化计算实验。在一系列研究工作的基础上, 80 年代由 Goldberg 进行归纳总结,形成厂遗传算法的基本框架。
本文主要以一个简单的案例写了一个实现,带大家简单的体验一下GA的基本过程。本文基于java编程,为了简化代码,使用了lombok插件。
虽然我以前上学时是学生物信息的,但是这里由于篇幅的问题,关于生物相关的内容只在需要时做简单介绍。
max z = x1方 + x2方
s.t.: x1>=-10 x1<=10;
x2>=-20 x2<=20
这是个简单的模型。可以知道,当x1=-10或10,并且x2=-20或20时,目标函数有最大值500。
GA算法有三个算子:选择(selection)、交叉(crossover)、变异(mutation)。
选择:简单理解就是物竞天择,适者生存。在子代中,适应环境的个体留下来的概率更大。编码时使用轮盘赌。
交叉:有性生殖生物在繁殖下一代时,两个源染色体之间通过交又重组。亦即在两个染色体的某一相同位置处 DNA 被切断,其前后两串分别交换又组合而形成两个新的染色体。(在算法中可以理解是片段交换,概率较大)
变异:在进行细胞复制时,有可能产生某些复制差错,从而使 DNA 某些位点发生变异。(在算法中可以理解是单个点发生变化,概率较小)
为了便于大家理解,我找了两个图说明交叉、变异:
交叉:
变异:
下面这几个概念是比较重要的。很多编程语言都是面向对象的(包括这里使用的JAVA),理解这几个概念有助于在解题时将数学公式抽象成对应的编程对象。
基因:一段DNA序列(如ATCGAATCACTGAT…)。这一段序列可能会对应生物的一个表现型,比如高/矮、胖/瘦等。基因是可以突变的,可以朝着好的方向变化(适应环境),也可以朝着坏的方向变化(不适应换行),但是随着一代一代的选择,最终保留下来的基因一定是适应环境的。 一个基因可以对应到一个决策变量,当决策变量在给定的范围内朝着目标函数值变大/小的方向变化时,这样的决策变量的值也有更大的概率保留下来。所以在编程时可以定义一个基因对象,对象中包括决策变量的相关信息以及相关的方法,比如编码(表现型->基因型)、解码(基因型->表现型)的方法。
染色体:一个染色体上有多个基因。
个体:单个生物体。包含有一到多个染色体。每个个体都有自己的适应度。
种群:多个个体组成一个群体。一个群体就可以一代一代繁衍,做个体做优胜劣汰的筛选等。
在编程时,根据模型的复杂程度可以定义上面几个对象,并在对象中定义该对象该有的方法。
由于上面的模型比较简单,我在编程时只是用了种群、个体、基因(决策变量)等几个对象。
一个决策变量其实就对应到一个基因。其实可以讲相关的编码、解码的方法也定义到这里。
package com.wuxiaolong.algorithm.geneticAlgorithm;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* Description:
* 每个决策变量的范围
* 每个决策变量有上限和下限
*
* @author 诸葛小猿
* @date 2021-09-21
*/
@Data
@AllArgsConstructor
public class DecisionVariable {
/**
* 决策变量名称
*/
private String name;
/**
* 决策变量描述
*/
private String desc;
/**
* 决策变量下限
*/
private Double lower;
/**
* 决策变量上限
*/
private Double upper;
}
package com.wuxiaolong.algorithm.geneticAlgorithm;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* Description:
* 种群中的每个个体定义
*
* @author 诸葛小猿
* @date 2021-09-21
*/
@Data
public class Individual {
/**
* 决策变量列表及其取值, key=x1,x2,x3...xn value=每个决策变量对应的值
*/
private Map<String,Double> decisionVariableMap = new HashMap<>();
/**
* 适应度
*/
private Double fitness = 0.0;
/**
* 适应度的概率
*/
private Double fitnessProbability = 0.0;
/**
* 累加的适应度概率
*/
private Double sumFitnessProbability = 0.0;
/**
* 计算个体的适应度(一般可以直接使用目标函数作为适应度计算公式)
*/
public void calculateFitness(){
for(Map.Entry<String, Double> decisionVariable : decisionVariableMap.entrySet()){
// max z = x1方 + x2方
this.fitness = this.fitness + decisionVariable.getValue() * decisionVariable.getValue();
}
}
/**
* 计算每个个体的适应度概率
*/
public void calculateFitnessProbability(Double sumFitness){
this.fitnessProbability = fitness / sumFitness;
}
}
package com.wuxiaolong.algorithm.geneticAlgorithm;
import lombok.Data;
import java.util.*;
import static com.wuxiaolong.algorithm.geneticAlgorithm.Constants.X1_BIN_LEN;
import static com.wuxiaolong.algorithm.geneticAlgorithm.Constants.X2_BIN_LEN;
/**
* Description: 种群
*
* @author 诸葛小猿
* @date 2021-09-21
*/
@Data
public class Population {
/**
* 个体列表
*/
private List<Individual> individualList = new ArrayList<>();
/**
* 适应度总和
*/
private Double sumFitness = 0.0;
/**
* 初始化种群
* @param populationSize
* @param decisionVariableList
*/
public void initPopulation(Integer populationSize, List<DecisionVariable> decisionVariableList){
for(int i=0; i<populationSize; i++){
Individual individual = new Individual();
for(int j=0; j<decisionVariableList.size(); j++){
individual.getDecisionVariableMap().put("x"+(j+1),Util.random(decisionVariableList.get(j)));
}
individualList.add(individual);
}
}
public void calculateFitnessAndProbability(){
calculateFitnessOneByOne();
calculateSumFitness();
calculateFitnessProbabilityOneByOne();
calculateSumFitnessProbability();
}
/**
* 计算每个个体的适应度
*/
public void calculateFitnessOneByOne(){
for (Individual individual: individualList) {
individual.calculateFitness();
}
}
/**
* 计算种群中每个个体的适应度总和
*/
public void calculateSumFitness(){
for (Individual individual: individualList) {
this.sumFitness = this.sumFitness + individual.getFitness();
}
}
/**
* 计算每个个体的适应度概率
*/
public void calculateFitnessProbabilityOneByOne(){
for (Individual individual: individualList) {
individual.calculateFitnessProbability(sumFitness);
}
}
/**
* 计算种群中每个个体的适应度累加概率总和
*/
public void calculateSumFitnessProbability(){
Double sumFitnessProbability = 0.0;
for (Individual individual: individualList) {
sumFitnessProbability = sumFitnessProbability + individual.getFitnessProbability();
individual.setSumFitnessProbability(sumFitnessProbability);
}
}
public Individual getTheBestFitnessIndividual(){
Individual best = null;
for (Individual individual: individualList) {
if(best==null){
best = individual;
}
if(individual.getFitness() > best.getFitness()){
best = individual;
}
}
return best;
}
/**
* 选择算子 (参考轮盘赌)
*/
public Population selection(){
Population midPopulation = new Population();
// 为每个个体生成0.0-1.0之间的随机数
List<Double> randList = new ArrayList<>();
for(int i=0; i<individualList.size(); i++){
randList.add(Util.random(0.0,1.0));
}
// 最好的个体
Individual best = getTheBestFitnessIndividual();
// 选择 轮盘赌
// 外层循环决定新种群的个体数量
for(int i=0; i<randList.size(); i++){
// 内存循环决定个体应该选哪个
Individual selected = null;
for(int j=0; j<individualList.size()-1; j++){
if(randList.get(i) < individualList.get(j).getSumFitnessProbability()){
selected = individualList.get(j);
break;
}
if(randList.get(i) >= individualList.get(j).getSumFitnessProbability()
&& randList.get(i) <= individualList.get(j+1).getSumFitnessProbability()){
selected = individualList.get(j+1);
break;
}
}
if(selected == null){
// 取适应度最大的
selected = getTheBestFitnessIndividual();
}
// 选择的个体(一定要重新new)
Individual newIndividual = new Individual();
for(Map.Entry<String, Double> entry : selected.getDecisionVariableMap().entrySet()){
String key = entry.getKey();
Double value = entry.getValue();
newIndividual.getDecisionVariableMap().put(key,value);
}
// 保证适应度高的个体一定是被更多次选择到
if(i % 5 == 0){
for(Map.Entry<String, Double> entry : best.getDecisionVariableMap().entrySet()){
String key = entry.getKey();
Double value = entry.getValue();
newIndividual.getDecisionVariableMap().put(key,value);
}
}
midPopulation.getIndividualList().add(newIndividual);
}
return midPopulation;
}
/**
* 交叉算子
* 第一个个体和第二个个体交互,第三个个体和第四个交互.........
*/
public void crossover(Population midPopulation,Double crossoverProbability,List<DecisionVariable> decisionVariableList){
for(int i=0; i<midPopulation.getIndividualList().size()-1; i=i+2){
// 生成随机概率值
Double currProbability = Util.random(0.0,1.0);
// 因为交叉的概率大
if(currProbability <= crossoverProbability){
// 找到父母
Individual father = midPopulation.getIndividualList().get(i);
Individual mather = midPopulation.getIndividualList().get(i+1);
// 记录当时的适应度
Double fatherFitness = father.getFitness();
Double matherFitness = mather.getFitness();
// 父个体的多个自变量
Double fatherX1 = father.getDecisionVariableMap().get("x1");
Double fatherX2 = father.getDecisionVariableMap().get("x2");
// 母个体的多个自变量
Double matherX1 = mather.getDecisionVariableMap().get("x1");
Double matherX2 = mather.getDecisionVariableMap().get("x2");
// 编码
String fatherX1BinStr = x1EnCoding(fatherX1 ,X1_BIN_LEN);
String fatherX2BinStr = x2EnCoding(fatherX2 ,X2_BIN_LEN);
String matherX1BinStr = x1EnCoding(matherX1 ,X1_BIN_LEN);
String matherX2BinStr = x2EnCoding(matherX2 ,X2_BIN_LEN);
// 随机生成x1的交换位置和x2的交换位置
Integer x1ExchangePoint = randomCrossPoint(X1_BIN_LEN);
Integer x2ExchangePoint = randomCrossPoint(X2_BIN_LEN);
// 在交换点切分x1
String fatherX1BinStrPrefix = fatherX1BinStr.substring(0, x1ExchangePoint + 1);
String fatherX1BinStrSufix = fatherX1BinStr.substring(x1ExchangePoint + 1);
String matherX1BinStrPrefix = matherX1BinStr.substring(0, x1ExchangePoint + 1);
String matherX1BinStrSufix = matherX1BinStr.substring(x1ExchangePoint + 1);
// 在交换点切分x2
String fatherX2BinStrPrefix = fatherX2BinStr.substring(0, x2ExchangePoint + 1);
String fatherX2BinStrSufix = fatherX2BinStr.substring(x2ExchangePoint + 1);
String matherX2BinStrPrefix = matherX2BinStr.substring(0, x2ExchangePoint + 1);
String matherX2BinStrSufix = matherX2BinStr.substring(x2ExchangePoint + 1);
// 交叉后的结果
String crossFatherX1BinStr = fatherX1BinStrPrefix + matherX1BinStrSufix;
String crossMatherX1BinStr = matherX1BinStrPrefix + fatherX1BinStrSufix;
String crossFatherX2BinStr = fatherX2BinStrPrefix + matherX2BinStrSufix;
String crossMatherX2BinStr = matherX2BinStrPrefix + fatherX2BinStrSufix;
// 解码
Double fatherX1Val = x1DeCoding(crossFatherX1BinStr);
Double matherX1Val = x1DeCoding(crossMatherX1BinStr);
Double fatherX2Val = x2DeCoding(crossFatherX2BinStr);
Double matherX2Val = x2DeCoding(crossMatherX2BinStr);
// 判断解码后的x1和x2是否在这两个决策变量的范围内(表明满足交换条件) // 比较交换后的值是否更好 todo
if( fatherX1Val >= decisionVariableList.get(0).getLower() && fatherX1Val <= decisionVariableList.get(0).getUpper()
&& matherX1Val >= decisionVariableList.get(0).getLower() && matherX1Val <= decisionVariableList.get(0).getUpper()
&& fatherX2Val >= decisionVariableList.get(1).getLower() && fatherX2Val <= decisionVariableList.get(1).getUpper()
&& matherX2Val >= decisionVariableList.get(1).getLower() && matherX2Val <= decisionVariableList.get(1).getUpper()
){
// 计算解码后的适应度
Double crossFatherFitness = fatherX1Val*fatherX1Val + fatherX2Val*fatherX2Val;
Double crossMatherFitness = matherX1Val*matherX1Val + matherX2Val*matherX2Val;
if(crossFatherFitness > fatherFitness){
father.getDecisionVariableMap().put("x1",fatherX1Val);
father.getDecisionVariableMap().put("x2",fatherX2Val);
}
if(crossMatherFitness > matherFitness){
mather.getDecisionVariableMap().put("x1",matherX1Val);
mather.getDecisionVariableMap().put("x2",matherX2Val);
}
}
}
// 所有不满足交换条件的父母个体都会直接进入下一代
if(midPopulation.getIndividualList().size()-i == 1 || midPopulation.getIndividualList().size()-i==0){
break;
}
}
}
/**
* 变异算子
*/
public void mutation(Population midPopulation,Double mutationProbability,List<DecisionVariable> decisionVariableList) {
for (int i = 0; i < midPopulation.getIndividualList().size(); i++) {
// 生成随机概率值
Double currProbability = Util.random(0.0, 1.0);
// 因为变异的概率小
if (currProbability <= mutationProbability) {
// 找到当前个体
Individual individual = midPopulation.getIndividualList().get(i);
// 记录当时的适应度
Double individualFitness = individual.getFitness();
// 当前个体的多个自变量
Double individualX1 = individual.getDecisionVariableMap().get("x1");
Double individualX2 = individual.getDecisionVariableMap().get("x2");
// 编码
String individualX1BinStr = x1EnCoding(individualX1 ,X1_BIN_LEN);
String individualX2BinStr = x2EnCoding(individualX2 ,X2_BIN_LEN);
// 随机生成x1的变异位置和x2的变异位置
Integer x1MutationPoint = randomMutationPoint(X1_BIN_LEN);
Integer x2MutationPoint = randomMutationPoint(X2_BIN_LEN);
// 将x1的变异位点取反(如果是0则变为1,如果是1则变为0)
char char0 = '0';
char char1 = '1';
String mutationX1BinStr = null;
char x1MutationPointChar = individualX1BinStr.charAt(x1MutationPoint);
if (x1MutationPointChar == char0) {
StringBuilder individualX1BinStrBuilder = new StringBuilder(individualX1BinStr);
individualX1BinStrBuilder.setCharAt(x1MutationPoint, char1);
mutationX1BinStr = individualX1BinStrBuilder.toString();
} else {
StringBuilder individualX1BinStrBuilder = new StringBuilder(individualX1BinStr);
individualX1BinStrBuilder.setCharAt(x1MutationPoint, char0);
mutationX1BinStr = individualX1BinStrBuilder.toString();
}
// 将x2的变异位点取反(如果是0则变为1,如果是1则变为0)
String mutationX2BinStr = null;
char x2MutationPointChar = individualX2BinStr.charAt(x2MutationPoint);
if (x2MutationPointChar == char0) {
StringBuilder individualX2BinStrBuilder = new StringBuilder(individualX2BinStr);
individualX2BinStrBuilder.setCharAt(x2MutationPoint, char1);
mutationX2BinStr = individualX2BinStrBuilder.toString();
} else {
StringBuilder individualX2BinStrBuilder = new StringBuilder(individualX2BinStr);
individualX2BinStrBuilder.setCharAt(x2MutationPoint, char0);
mutationX2BinStr = individualX2BinStrBuilder.toString();
}
// 解码
Double individualX1Val = x1DeCoding(mutationX1BinStr);
Double individualX2Val = x2DeCoding(mutationX2BinStr);
// 判断解码后的x1和x2是否在这两个决策变量的范围内(表明满足交换条件) // 比较交换后的值是否更好 todo
if (individualX1Val >= decisionVariableList.get(0).getLower() && individualX1Val <= decisionVariableList.get(0).getUpper()
&& individualX2Val >= decisionVariableList.get(1).getLower() && individualX2Val <= decisionVariableList.get(1).getUpper()
) {
// 计算解码后的适应度
Double mutationFatherFitness = individualX1Val*individualX1Val + individualX2Val*individualX2Val;
if(mutationFatherFitness > individualFitness){
// 修改对应father和mather并放入下一代
individual.getDecisionVariableMap().put("x1", individualX1Val);
individual.getDecisionVariableMap().put("x2", individualX2Val);
}
}
}
}
}
private String x1EnCoding(Double x1, Integer binLen){
// x1的最小值是-10
Double tmp = (x1 + 10.0) * Math.pow(10, 6);
// 一般提前预估号二进制字符串的长度范围 并确定最长情况
String x1BinStr = Integer.toBinaryString(tmp.intValue());
Integer x1BinStrLen = x1BinStr.length();
if(x1BinStrLen < binLen){
Integer prefix0Len = binLen - x1BinStrLen;
for(int t=0; t<prefix0Len; t++){
x1BinStr = "0" + x1BinStr;
}
}
return x1BinStr;
}
private String x2EnCoding(Double x2,Integer binLen){
// x1的最小值是-20
Double tmp = (x2 + 20.0) * Math.pow(10, 6);
// 一般提前预估号二进制字符串的长度范围 并确定最长情况
String x1BinStr = Integer.toBinaryString(tmp.intValue());
Integer x1BinStrLen = x1BinStr.length();
if(x1BinStrLen < binLen){
Integer prefix0Len = binLen - x1BinStrLen;
for(int t=0; t<prefix0Len; t++){
x1BinStr = "0" + x1BinStr;
}
}
return x1BinStr;
}
private Double x1DeCoding(String x1BinStr){
Integer val = Integer.valueOf(x1BinStr, 2);
Double x1 = val / Math.pow(10, 6) - 10;
return x1;
}
private Double x2DeCoding(String x2BinStr){
Integer val = Integer.valueOf(x2BinStr, 2);
Double x2 = val / Math.pow(10, 6) - 20;
return x2;
}
// 二进制长度为24,则交叉位点应该是 1-23之间
private Integer randomCrossPoint(Integer xBinLen){
Random rand = new Random();
return rand.nextInt(xBinLen -1 )+1;
}
// 二进制长度为24,则交叉位点应该是 0-23之间
private Integer randomMutationPoint(Integer xBinLen){
Random rand = new Random();
return rand.nextInt(xBinLen);
}
}
package com.wuxiaolong.algorithm.geneticAlgorithm;
import java.util.Arrays;
import java.util.List;
/**
* 定义相关的常量
*/
public class Constants {
// 决策变量定义
public static final DecisionVariable X1 = new DecisionVariable("x1","决策变量x1",-10.0,10.0);
public static final DecisionVariable X2 = new DecisionVariable("x2","决策变量x2",-20.0,20.0);
public static final List<DecisionVariable> DECISION_VARIABLE_LIST = Arrays.asList(X1, X2);
// 种群大小定义
public static final Integer POPULATION_SIZE = 100;
// 进化代数
public static final Integer GENERATION_NUM = 500;
// 交差的概率 用于判断两两个体是否需要交叉
public static final Double CROSSOVER_PROBABILITY = 0.85;
// 变异的概率 用于判断任一个体是否需要变异
public static final Double MUTATION_PROBABILITY = 0.05;
// x1和x2两个决策变量进行编码后的二进制字符串长度
public static final Integer X1_BIN_LEN = 24;
public static final Integer X2_BIN_LEN = 24;
public static Double fitnessFunction(Double x1, Double x2){
return x1 * x1 + x2 * x2;
}
}
package com.wuxiaolong.algorithm.geneticAlgorithm;
import java.util.Random;
/**
* Description:
*
* @author 诸葛小猿
* @date 2021-09-21
*/
public class Util {
/**
* 获取随机值 min <= 返回值x <= max
* @return
*/
public static Double random(Double min,Double max) {
Random rand = new Random();
double result=0;
for(int i=0; i<10; i++){
result = min + (rand.nextDouble() * (max - min));
result = (double) Math.round(result * 100) / 100;
// System.out.println(result);
}
return result;
}
/**
* 获取随机值 确保随机 均匀分布
* @return
*/
public static Double random(DecisionVariable xRange) {
return random(xRange.getLower(),xRange.getUpper());
}
public static void main(String[] args) {
int i =0;
while (true){
if(random(0.0,1.0) == 0.0){
System.out.println(i);
i=0;
}
i++;
}
}
}
package com.wuxiaolong.algorithm.geneticAlgorithm;
import com.alibaba.fastjson.JSON;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import static com.wuxiaolong.algorithm.geneticAlgorithm.Constants.*;
/**
* Description:
* 基于算法入口
*
* @author 诸葛小猿
* @date 2021-09-21
*/
public class GA {
/**
* max z = x1方 + x2方
* s.t.: x1>=-10 x1<=10;
* x2>=-20 x2<=20
*
* @param args
*/
public static void main(String[] args) {
// 初始化种群
Population population = new Population();
population.initPopulation(POPULATION_SIZE, DECISION_VARIABLE_LIST);
population.calculateFitnessAndProbability();
// 进化
Population midPopulation =population;
for(int i=0; i< GENERATION_NUM; i++){
// 选择(返回结果是新种群)
midPopulation = midPopulation.selection();
// 交叉
midPopulation.crossover(midPopulation,CROSSOVER_PROBABILITY,DECISION_VARIABLE_LIST);
// 变异
midPopulation.mutation(midPopulation,MUTATION_PROBABILITY,DECISION_VARIABLE_LIST);
// 计算每个个体适应度、种群适应度总和、每个个体适应度概率、每个个体的累加适应度概率
midPopulation.calculateFitnessAndProbability();
System.out.println(midPopulation.getTheBestFitnessIndividual().getFitness());
}
// 计算最后一代种群的适应度 (上面的代数循环结束获得的是最后一代种群)
midPopulation.calculateFitnessOneByOne();
// 在最后一代中获得适应度最好的个体
Individual theBestFitnessIndividual = midPopulation.getTheBestFitnessIndividual();
// 最后一代中适应度最好的个体对应的变量就是决策变量最终的解
Map<String, Double> decisionVariableMap = theBestFitnessIndividual.getDecisionVariableMap();
System.out.println("最后一次" +decisionVariableMap);
}
}
这里可以看到已经求出了一组最好的解。
但是多次运行时,还是可以看到下面的情况:
这是因为算法陷入了局部最优解的情况。所以代码还需要进一步优化。具体的优化这里就不说了。