本文是对10分钟搞懂遗传算法的学习笔记
一、作用
遗传算法模拟了自然选择,物竞天择、适者生存,通过 N 代的遗传、交叉、变异、复制,进化出问题的最优解。
二、相关概念
在遗传算法中,我们首先需要将要解决的问题映射成一个数学问题,也就是所谓的“数学建模”。
1.基因和染色体
这个问题的一个可行解被称为“染色体”。一个可行解一般由多个元素构成,那么每一个元素就被称为染色体上的一个“基因”。
例如:[1,2,3],[1,3,2],[3,2,1]均是函数 3x+4y+5z<100
的可行解(代进去成立即为可行解),那么这些可行解在遗传算法中均称为“染色体”。可行解由 3 个元素构成,每个元素都称为染色体的一个基因。
2.适应度函数
用于衡量染色体优劣
遗传算法在运行过程中会进行 N 次迭代,每次迭代都会生成若干条染色体。适应度函数会给本次迭代中生成的所有染色体打个分,来评判这些染色体的适应度,然后将适应度低的染色体淘汰,只保留适应度高的染色体,从而讲过若干次迭代后染色体的质量将越来越好。
3.交叉
交叉需从上一代染色体中寻找两条染色体,一条爸爸,一条妈妈。然后将这两个染色体的某一个位置断开,并拼接在一起形成新的染色体。这条染色体就既包含了爸爸的基因也包含了妈妈的基因。
遗传算法每次迭代会生成 N 条染色体,在遗传算法中一次迭代被称为一次进化。每次进化新的染色体生成的方法——交叉。
Q:如何从上一代染色体中选出爸爸和妈妈?
不是随机选择的,一般是通过轮盘赌算法完成。
每一次进化完成后,都要计算每一条染色体的适应度+适应度概率。在交叉过程中就需要根据这个概率来选择父母染色体。适应度高的染色体被选中的概率越高。(这就是遗传算法能够保留优良基因的原因)
染色体 i 被选中的概率 = 染色体 i 的适应度 / 所有染色体的适应度之和
4.变异
交叉生成新的染色体后,在新染色体上随机选择若干个基因,然后随机修改基因的值,从而给现有的染色体引入新基因,突破了当前搜索的限制,更有利于算法寻找全局最优解。
交叉能保证每次进化留下优良的基因,但它仅仅是对原有的结果集进行选择,基因还是那么几个,只不过交换了它们的顺序。这只能保证 N 次进化后,计算结果更接近于局部最优解,而永远没办法达到全局最优解(?????),为了解决这个问题,需引入变异。
5.复制
在每次进化中,为了保留上一代优良的染色体,需要将上一代中适应度最高的几条染色体直接原封不动地复制给下一代。
假设每次进化都需要生成 N 条染色体,那么每次进化中,通过交叉方式需要生成 N-M 条,剩余的 M 条染色体通过复制上一代适应度最高的 M 条染色体而来。
三、使用
1.流程图
2.题目
采用遗传算法解决负载均衡问题:假设有 N 个任务,需要负载均衡器分配给 M 个服务器节点去处理。每个任务的任务长度、每台服务器节点(下面简称“节点”)的处理速度已知,请给出一种任务分配方式,使得所有任务的总处理时间最短。
Q:究竟进化多少次?
每一次进化都会更优,因此理论上进化的次数越多越好,但在实际应用中往往会在结果精确度和执行效率之间寻找一个平衡点,一般有两种方式。
限定进化次数
实际中,不同的输入会导致得到最优解时的迭代次数相差甚远。限定误差范围
一旦发现了当前的结果已经在误差范围内了,那么就终止算法。缺点是:有些情况下需要很多很多才能进入误差范围,这种不确定性导致算法的执行效率不可控。
Q:如何初始化第一代染色体
一般是随机指定一个可行解。
Q:染色体适应度如何计算
适应度是评判个体适应程度的指标。一般根据目标函数而定。
本文的目标是使所有任务的总处理时间最少,时间越短适应度越大。适应度 = 1 / 所有任务的总处理时间
Q:染色体适应度概率如何计算
单个染色体的适应度概率 = 该染色体的适应度 / 所有染色体的适应度之和
Q:交叉时,如何选择出来“爸爸和妈妈”
根据轮盘赌算法找出爸爸和妈妈然后交叉。
个体被选中概率,与其的适应度概率成正比
轮盘赌算法及其实现
/**
* 根据轮盘赌算法,返回被选中的某个染色体的数组下标
* @param selectionProbability
* @return
*/
public int getSelectedOne_byRoulette(double[] selectionProbability) {
Random random = new Random();
double probTotal = random.nextDouble();
System.out.println("probTotal=" + probTotal);
double sum = 0.0;
for (int i = 0; i < selectionProbability.length; i++) {
sum += selectionProbability[i];
if (sum >= probTotal) {
return i;
}
}
return 0;
}
Q:如何变异
随机选择染色体中一个或几个位置将基因变异。要注意变异后该染色体仍需是可行解。
Q:复制时,将哪些染色体复制到新的种群?
将上次迭代的种群中的适应度最高的几个(这个复制比例需指定)染色体直接复制到本代种群中。
- 数学建模
拿到这个问题后首先要将这个实际问题映射成遗传算法的数学模型。
1)任务长度矩阵(长度为 N )
将任务从 0 开始编号,用一个一维数组存储每个任务的时长
tasks = {2,4,6,8}
tasks[i]
:表第 i 个任务的长度。
第 0 个任务的长度为 2;
第 1 个任务的长度为 4;
第 2 个任务的长度为 6;
第 3 个任务的长度为 8;
2)节点处理速度矩阵(长度为 M )
将处理器节点从 0 开始编号,用一个一维数组存储每个处理器的处理速度(单位时间内可处理的长度)
nodes = {2,1}
nodes[i]
表第 i 个节点的处理速度。
第 0 个节点的处理速度为 2;
第 1 个节点的处理速度为 1。
3)任务-节点矩阵(N × M )
timeMatrix[][]
1 2
2 4
3 6
4 8
timeMatrix[i][j]
表第 i 个任务在第 j 个节点上处理的话,所需处理时间。
tasksMatrix[i][j] = tasks[i] / nodes[j]
4)染色体(长度为 N )
一个可行解就是一个染色体,就是一个一维数组
chromosome={3,2,1,0}
chromosome[i]=j
表将第 i 个任务分配到节点 j 上处理(任务编号从 0 开始;节点编号从 0 开始)
将任务 0 分配给 3 号节点处理;
将任务 1 分配给 2 号节点处理;
将任务 2 分配给 1 号节点处理;
将任务 3 分配给 0 号节点处理。
5)适应度(长度为 chromosomeNum )
记录本次进化生成的 N 条染色体的适应度,将染色体从 0 开始编号。
adaptablility={0.6,2,3.2,1.8}
adaptablility[i]
表第 i 个染色体的适应度
6)适应度概率(长度为 chromosomeNum )
selectionProbability={0.1,0.4,0.2,0.3}
selectionProbability[i]
表第 i 个染色体的适应度概率,所有染色体的适应度概率和为 1 。
selectionProbability[i] = adaptablility[i] / 所有染色体的适应度和
4.代码
/**
* 遗传算法
* N 个任务,M 个处理器
* @param iteratorNum 迭代次数
* @param chromosomeNum 染色体数量
*/
public void gaSearch(int iteratorNum, int chromosomeNum) {
// 初始化第一代染色体
int[] chermoMatrix = createGeneration();
// 迭代繁衍
int i = 1;
while (i < iteratorNum) {
// 计算上一代染色体的适应度
double[] adaptability = calAdaptability(chromosomeMatrix);
// 计算上一代染色体的适应度概率
double[] selectionProbability = calSelectionProbability(adaptability);
// 生成新一代染色体
chromosomeMatrix = createGeneration(chromosomeMatrix);
}
}
参考文献
附录(完整代码)
import java.util.*;
/**
* <pre>
* author : 杨丽金
* time : 2018/12/15
* desc : 遗传算法实现:解决任务分配问题
* 假设有 N 个任务,需要负载均衡器分配给 M 个服务器节点去处理。
* 每个任务的任务长度、每台服务器节点(下面简称“节点”)的处理速度已知,
* 请给出一种任务分配方式,使得所有任务的总处理时间最短。
* version: 1.0
* </pre>
*/
public class GenerationAlgorithm {
// 将任务从 0 开始编号,存储每个任务的长度
int[] tasks;
int TASK_NUM;
// 将处理器从 0 开始编号,存储每个处理器处理任务的速度 可处理长度/单位时间
int[] nodes;
// 处理器个数
int NODE_NUM;
// timeMatrix[i][j]:第 i 个任务放在处理器 j 上处理需要的时间
double[][] timeMatrix;
// adaptabilitu[i] 染色体 i 的适应度概率
double[] adaptability;
// selectionProbability[i]:第 i 个染色体的适应度概率
double[] selectionProbability;
int iteratorNum;
int chromosomeNum;
// 染色体复制比例
double cp;
public void ga() {
inputParam();
inputData();
initMatrix();
gaSearch(iteratorNum, chromosomeNum);
}
public void initMatrix() {
// 任务-处理时间
timeMatrix = new double[TASK_NUM][NODE_NUM];
for (int i = 0; i < TASK_NUM; i++) {
for (int j = 0; j < NODE_NUM; j++) {
// 第i个任务放在处理器j上处理,所需时间
timeMatrix[i][j] = (tasks[i] + 1.0) / nodes[j];
}
}
// 染色体适应度
adaptability = new double[chromosomeNum];
// 染色体适应度概率
selectionProbability = new double[chromosomeNum];
}
/**
* 输入任务序列、节点序列
*/
public void inputData() {
// 任务矩阵
tasks = new int[TASK_NUM];
// 节点矩阵
nodes = new int[NODE_NUM];
Random r = new Random();
// 随机生成任务长度[10,100]
for (int i = 0; i < TASK_NUM; i++) {
tasks[i] = r.nextInt(91) + 10;
}
// 随机生成处理器处理任务速度[10,100]
for (int i = 0; i < NODE_NUM; i++) {
nodes[i] = r.nextInt(91) + 10;
}
}
/**
* 初始化相关参数
*/
public void inputParam() {
// 任务个数
TASK_NUM = 100;
// 处理器个数
NODE_NUM = 10;
// 迭代次数
iteratorNum = 100;
// 群体大小
chromosomeNum = 10;
// 染色体复制的比例(每代中保留适应度较高的染色体直接成为下一代)
cp = 0.2;
}
/**
* 遗传算法迭代
*
* @param iteratorNum
* @param chromosomeNum
*/
public void gaSearch(int iteratorNum, int chromosomeNum) {
// 初始化第一代群体
List < int[] > chromosomeMatrix = createGeneration(null, chromosomeNum);
// 迭代繁衍
for (int i = 2; i <= iteratorNum; i++) {
// 计算上一代群体染色体的适应度
adaptability = calAdaptability(chromosomeMatrix);
// System.out.println("迭代次数:"+i+",计算上一代群体染色体的适应度:"+Arrays.toString(adaptability));
// 计算上一代染色体的适应度概率
selectionProbability = calSelectionProbability(adaptability);
// System.out.println("迭代次数:"+i+",计算上一代染色体的适应度概率:"+Arrays.toString(selectionProbability));
// 生成新一代染色体
chromosomeMatrix = createGeneration(chromosomeMatrix, chromosomeNum);
System.out.println("迭代次数:" + i + ",的每个染色体总处理时间:");
for (int j = 0; j < chromosomeMatrix.size(); j++) {
System.out.println("处理时间:" + getTotalProcessTime(chromosomeMatrix.get(j)));
}
}
}
/**
* 得到一个染色体总的处理时间
* @param chromosome
* @return
*/
private double getTotalProcessTime(int[] chromosome) {
double sum = 0;
for (int j = 0; j < chromosome.length; j++) {
// 将第j号任务分配到第nodeId号处理器行
int nodeId = chromosome[j];
sum += timeMatrix[j][nodeId];
}
return sum;
}
/**
* 计算群体中染色体的适应度概率
* @param adaptability
* @return
*/
public double[] calSelectionProbability(double[] adaptability) {
if (adaptability == null || adaptability.length == 0) {
return null;
}
double[] selectionProb = new double[adaptability.length];
// 总适应度
double sum = 0;
for (int i = 0; i < adaptability.length; i++) {
sum += adaptability[i];
}
for (int i = 0; i < adaptability.length; i++) {
selectionProb[i] = adaptability[i] / sum;
}
return selectionProb;
}
/**
* 计算群体中染色体的适应度
* @param chromosomeMatrix
* @return
*/
public double[] calAdaptability(List < int[] > chromosomeMatrix) {
double[] adaptAbility = new double[chromosomeMatrix.size()];
for (int i = 0; i < chromosomeMatrix.size(); i++) {
// 对每一个染色体,计算总的处理时间
int[] chromosome = chromosomeMatrix.get(i);
double sum = getTotalProcessTime(chromosome);
adaptAbility[i] = 1 / sum;
}
return adaptAbility;
}
/**
* 生成每次迭代后的群体
*
* @param chromosomeMatrix:上一代群体
* @param chromosomeNum:群体中染色体数目
* @return
*/
public List < int[] > createGeneration(List < int[] > chromosomeMatrix, int chromosomeNum) {
Random r = new Random();
if (chromosomeMatrix == null || chromosomeMatrix.size() == 0) {
chromosomeMatrix = new ArrayList < > ();
// 生成第一代染色体:随机生成染色体
for (int i = 0; i < chromosomeNum; i++) {
// 生成每个个体
int[] chromosome = new int[TASK_NUM];
for (int j = 0; j < TASK_NUM; j++) {
// 第j个任务交给第r.nextInt(NODE_NUM)个节点处理
chromosome[j] = r.nextInt(NODE_NUM); //[0,NODE_NUM)
}
chromosomeMatrix.add(chromosome);
}
return chromosomeMatrix;
} else {
// 根据上一代群体生成本代群体
// 一部分复制,另一份交叉-变异
int copyNum = (int)(chromosomeNum * cp);
List < int[] > crossChromosome = cross(chromosomeMatrix, chromosomeNum - copyNum);
System.out.println("交叉产生的染色体个数:" + crossChromosome.size());
// 从上一群体中复制的染色体
List < int[] > copyChromosome = copy(chromosomeMatrix, copyNum);
System.out.println("复制产生的染色体个数:" + copyChromosome.size());
// 生成新一代染色体
List < int[] > newChromosomeMatrix = new ArrayList < > ();
newChromosomeMatrix.addAll(crossChromosome);
newChromosomeMatrix.addAll(copyChromosome);
System.out.println("新一代染色体个数:" + newChromosomeMatrix.size());
return newChromosomeMatrix;
}
}
/**
* 从上一代群体chromosomeMatrix中复制产生 num 个染色体
* @param chromosomeMatrix
* @param num
* @return
*/
public List < int[] > copy(List < int[] > chromosomeMatrix, int num) {
// 从上一代群体中找出适应度最高的num个染色体,把这些染色体复制到新的群体中
return getMaxN(chromosomeMatrix, selectionProbability, num);
}
/**
* 得到适应度最高的前num个染色体
* @param chromosomeMatrix
* @param selectionProbability
* @param num
* @return
*/
public List < int[] > getMaxN(List < int[] > chromosomeMatrix, double[] selectionProbability, int num) {
PriorityQueue < Chromosome > queue = new PriorityQueue < > (num);
for (int i = 0; i < selectionProbability.length; i++) {
if (queue.size() < num) {
queue.add(new Chromosome(i, selectionProbability[i]));
} else {
Chromosome mincs = queue.peek();
if (mincs.selectionProb < selectionProbability[i]) {
queue.poll();
queue.add(new Chromosome(i, selectionProbability[i]));
}
}
}
List < int[] > result = new ArrayList < > ();
// 变量PriorityQueue
if (queue != null && queue.size() != 0) {
Iterator < Chromosome > iterator = queue.iterator();
while (iterator.hasNext()) {
Chromosome chromosome = iterator.next();
result.add(chromosomeMatrix.get(chromosome.id));
}
}
return result;
}
public static class Chromosome implements Comparable < Chromosome > {
int id;
double selectionProb;
public Chromosome() {}
public Chromosome(int id, double selectionProb) {
this.id = id;
this.selectionProb = selectionProb;
}
@Override
public int compareTo(Chromosome o) {
if (o == null) return -1;
double r = this.selectionProb - o.selectionProb;
if (r > 0) {
return 1;
} else if (r < 0) {
return -1;
} else {
return 0;
}
}
}
/**
* 从上一代群体chromosomeMatrix中交叉-变异产生 num 个染色体
*
* @param chromosomeMatrix
* @param num
* @return
*/
public List < int[] > cross(List < int[] > chromosomeMatrix, int num) {
if (chromosomeMatrix == null || chromosomeMatrix.size() == 0) {
return null;
}
Random r = new Random();
List < int[] > crossChromosome = new ArrayList < > ();
for (int i = 0; i < num; i++) {
int[] chromosome = new int[TASK_NUM];
// 采用轮盘赌算法获得爸爸和妈妈染色体
int fatherIndex = getSelectedOne_byRoulette(selectionProbability);
int motherIndex = getSelectedOne_byRoulette(selectionProbability);
// 交叉位置
int crossIndex = r.nextInt(TASK_NUM);
System.arraycopy(chromosomeMatrix.get(fatherIndex), 0,
chromosome, 0, crossIndex + 1);
System.arraycopy(chromosomeMatrix.get(motherIndex),
crossIndex + 1, chromosome, crossIndex + 1, TASK_NUM - crossIndex - 1);
// 交叉后变异
// 变异位置
int mutationIndex = r.nextInt(TASK_NUM);
int newNodesNum = r.nextInt(NODE_NUM);
chromosome[mutationIndex] = newNodesNum;
crossChromosome.add(chromosome);
}
return crossChromosome;
}
/**
* 根据轮盘赌算法,返回被选中的某个染色体的数组下标
*
* @param selectionProbability
* @return
*/
public int getSelectedOne_byRoulette(double[] selectionProbability) {
Random random = new Random();
double probTotal = random.nextDouble();
double sum = 0.0;
for (int i = 0; i < selectionProbability.length; i++) {
sum += selectionProbability[i];
if (sum >= probTotal) {
return i;
}
}
return 0;
}
public static void main(String[] args) {
new GenerationAlgorithm().ga();
}
}
第 2 次迭代结果
迭代次数: 2, 的每个染色体总处理时间:
处理时间: 193.722942136212
处理时间: 231.6206347935423
处理时间: 185.09082856428054
处理时间: 198.01607879801534
处理时间: 205.59216705325903
处理时间: 211.81626082539924
处理时间: 247.09925385487205
处理时间: 182.59624665573205
处理时间: 181.69308004783602
处理时间: 179.55895258229776
第 100 次迭代结果
迭代次数: 100, 的每个染色体总处理时间:
处理时间: 112.43065214976716
处理时间: 114.15450455769397
处理时间: 116.34684859699807
处理时间: 121.91051599048023
处理时间: 112.62397000322431
处理时间: 113.31138094406218
处理时间: 113.55016350652896
处理时间: 123.63909005718553
处理时间: 112.49751909508238
处理时间: 112.43065214976716