【原创】最详细的遗传算法(GA)解析(内附代码)

遗传算法

遗传算法(Genetic Algorithm,GA)最早是由美国的 John holland于20世纪70年代提出,该算法是模拟达尔文生物进化论的自然选择和遗传学机理的生物进化过程的计算模型,以此来模拟出较优的解。

遗传算法的大致步骤

遗传算法是从代表问题可能潜在的解集的一个种群 S k S_k Sk(population)开始的,而一个种群则由经过基因(gene)编码的一定数目的个体 x i x_i xi(individual)组成。染色体作为遗传物质的主要载体,其内部表现(即基因型)是某种基因组合,它决定了个体的形状的外部表现。因此,在一开始需要实现从表现型到基因型的映射即编码工作。我们使用一些编码来模拟其基因编码,如二进制编码。
根据优胜劣汰、适者生存的原理 ,我们计算每个个体的适应度,并依概率进行遗传操作,遗传操作包括以下三个基本遗传算子(genetic operator): 选择(select);交叉(crossover);变异(mutation),经过不断的迭代,直到达到了最大的进化代数,我们就可以得到一个较好的解(此时适应度最大的个体)。

遗传算法框架

带着问题学习是我认为比较好的一种方式,这里我们先来设定一个问题:

max f ( x ) = x 2 , x ∈ [ 0 , 1 ) f(x)=x^2,x\in[0,1) f(x)=x2,x[0,1)

这里我们要求一个非常简单的二次函数的最大值,下面我们看看如何通过遗传算法如何去求解。这里先用一个流程图解释一下遗传算法的过程:

Created with Raphaël 2.3.0 编码 初始化种群 不满足终止条件 计算适应度 遗传操作 输出 yes no

编码

这是非常自然的一件事,我们使用一些编码来代替表示各个个体的染色体,

二进制编码

二进制编码中的一个位能表示出2种状态的信息量,因此足够长的二进制染色体便能表示所有的特征。下面我们模拟一下编码过程(以四位二进制编码为例):
这里我们假设有六个个体( x 1 , x 2 , x 3 , x 4 , x 5 , x 6 x_1,x_2,x_3,x_4,x_5,x_6 x1x2x3x4x5x6

x: 1 16 \frac{1}{16} 161, 3 16 \frac{3}{16} 163, 5 16 \frac{5}{16} 165, 2 16 \frac{2}{16} 162, 13 16 \frac{13}{16} 1613, 9 16 \frac{9}{16} 169

对应的二进制编码为:
S:0001,0011,0101,0010,1101,1001

它有以下一些优点:

1.编码、解码操作简单易行;
2.交叉、变异等遗传操作便于实现。

浮点编码法

浮点法是指个体的每个基因值用某一范围内的一个浮点数来表示。在浮点数编码方法中,必须保证所有操作后基因值依然在给定的区间限制范围内。
优点:
1.精度高,适用于连续变量问题;
2.适用于表示范围比较大的数值,适合空间较大的一串算搜索;
3.降低了计算复杂性,提升效率。

格雷码

对于一些连续函数的优化问题,相邻整数的二进制编码可能具有较大的Hamming距离,例如15和16的二进制表示为01111和10000,因此需要改变所有的位才能从15改进到16,这种缺陷将降低遗传算子的搜索小效率。
格雷码中连续的两个整数编码值之间仅仅只有一个马尾是不相同的,其余码位都完全相同,例如15和16的格雷码位01000和11000。
设二进制码为 B = b m b m − 1 . . . b 2 b 1 B=b_mb_{m-1}...b_2b_1 B=bmbm1...b2b1, 其对应的格雷码为 G = g m g m − 1 . . . g 2 g 1 G=g_mg_{m-1}...g_2g_1 G=gmgm1...g2g1, 则二进制码转换为格雷码的转换公式为:

g m = b m g_m=b_m gm=bm
g i = b i + 1 ⊕ b i , i ∈ [ 1 , m − 1 ] g_i=b_{i+1}⊕b_i,i\in[1,m-1] gi=bi+1bi,i[1,m1]

优点:
1.便于提高遗传算法的局部搜索能力;
2.杂交、变异等遗传操作便于实现;
3.符合最小字符集原则;
4.便于用模式定理对算法进行理论分析。

初代群体的选取

从取值范围中取得n个可行解,构成初始种群。下面给出种群初始化的c++代码:

//这里我们选用eigen来构造矩阵
MatrixXi ga::inti_group(int group_number, int chromlength)
{
	 MatrixXi x(group_number, chromlength);
	 vector<vector<int>> random;
	 srand((unsigned)time(0));
	 for (int i = 0; i < group_number; i++)
	 {
		 vector<int>temp;

		 for (int j = 0; j < chromlength; j++)
		 {
			 double ran = rand() % 100 / (double)101;
			 if (ran < 0.5)
				 temp.push_back(1);
			 else
				 temp.push_back(0);
		 }
		 random.push_back(temp);
	 }
	 for (int i = 0; i < group_number; ++i) 
		 for (int c = 0; c < chromlength; ++c) 
			 x(i, c) = random[i][c];
	cout << x << endl;
	return x;
}

适应度函数

这里我们的适应度函数为 f(x)=x^2,上述六个个体的适应度为:
x 1 x_1 x1= 1 16 2 = 1 256 {\frac{1}{16}}^2=\frac{1}{256} 1612=2561
x 2 x_2 x2= 3 16 2 = 9 256 {\frac{3}{16}}^2=\frac{9}{256} 1632=2569
x 3 x_3 x3= 5 16 2 = 25 256 {\frac{5}{16}}^2=\frac{25}{256} 1652=25625
x 4 x_4 x4= 2 16 2 = 4 256 {\frac{2}{16}}^2=\frac{4}{256} 1622=2564
x 5 x_5 x5= 13 16 2 = 169 256 {\frac{13}{16}}^2=\frac{169}{256} 16132=256169
x 6 x_6 x6= 9 16 2 = 81 256 {\frac{9}{16}}^2=\frac{81}{256} 1692=25681

下面给出适应度函数的c++代码:

MatrixXd ga::cal_fitvalue(MatrixXi x,int group_number, int chromlength)
{
	MatrixXd res(group_number, 1);
	for (int i = 0; i < group_number; i++) {
		double temp = 0.0;
		for (int c = 0; c < chromlength; ++c)
		{
			temp = decodechro(x, chromlength, i);
			if (temp < 0)
				temp = 0;
		}
		res(i, 0) = pow(temp, 2);
	}
	//cout << res << endl;
		return res ;
}

遗传操作

遗传操作包括了:选择(select);交叉(crossover);变异(mutation),

选择(select)

在k-1轮中,我们从种群 S k − 1 S_{k-1} Sk1中抽取n个 x i x_i xi,放入 S k S_k Sk中。

轮盘赌策略(roulette wheel selection)

首先我们需要先抽取要遗传的染色体,设某一个个体被抽取的概率为 p i p_i pi,那么:

p i = f i ∑ i = 1 n f i p_i=\frac {f_i}{\sum_{i=1}^{n}f_i} pi=i=1nfifi

其中 f i f_i fi为适应度函数的值。可以看到,轮盘赌策略模型依据适应度的大小来决定个体能被抽取到的概率,从上述例子中我们可以知道个体的适应度:此时可以知道各个个体被选取的概率为:
x 1 x_1 x1= 1 1 + 9 + 25 + 4 + 169 + 81 = 1 289 \frac{1}{1+9+25+4+169+81}=\frac{1}{289} 1+9+25+4+169+811=2891
x 2 x_2 x2= 9 1 + 9 + 25 + 4 + 169 + 81 = 9 289 \frac{9}{1+9+25+4+169+81}=\frac{9}{289} 1+9+25+4+169+819=2899
x 3 x_3 x3= 25 1 + 9 + 25 + 4 + 169 + 81 = 25 289 \frac{25}{1+9+25+4+169+81}=\frac{25}{289} 1+9+25+4+169+8125=28925
x 4 x_4 x4= 4 1 + 9 + 25 + 4 + 169 + 81 = 4 289 \frac{4}{1+9+25+4+169+81}=\frac{4}{289} 1+9+25+4+169+814=2894
x 5 x_5 x5= 169 1 + 9 + 25 + 4 + 169 + 81 = 169 289 \frac{169}{1+9+25+4+169+81}=\frac{169}{289} 1+9+25+4+169+81169=289169
x 6 x_6 x6= 81 1 + 9 + 25 + 4 + 169 + 81 = 81 289 \frac{81}{1+9+25+4+169+81}=\frac{81}{289} 1+9+25+4+169+8181=28981

转盘如下所示:
在这里插入图片描述
所以适应度越高,被抽取到的概率越大(能够传下自己的染色体的概率)。

随机竞争策略(Stochastic Tournament)

先通过轮盘赌策略选出一对个体,再对其进行比较,适应度较大的个体就被选中,如此反复。

竞标赛策略(tournament selection)

每次从种群中随机选取特定数量的个体,然后挑选出其中适应度最大的个体进入到下一轮种群中,如此反复直到选满为止。一般来说,锦标赛选择策略会比轮盘赌选择策略有更好的通用性,而且性能更优。
还有一些其他策略,我们在这里不再一一介绍。

下面给出选择(select)的c++代码:

MatrixXi ga::selection(MatrixXd fitvalue, MatrixXi x, int group_number, int chromlength)
{
	MatrixXd mp(group_number, chromlength + 1);
	MatrixXi res(group_number, chromlength);
	double add_matrix = fitvalue.sum(), Probability = 0.0;
	for (int i = 0; i < group_number; i++)
	{
		double temp = fitvalue(i, 0);
		double num = decodechro(x, chromlength, i);
		Probability += temp / add_matrix;
		mp(i, 0) = Probability;
	}
	for (int i = 0; i < group_number; i++)
		for (int j = 1; j < chromlength + 1; j++)
			mp(i, j) = x(i, j - 1);
	srand((unsigned)time(NULL));
	for (int i = 0; i < group_number; i++)
	{
		double ran = rand() % 100 / (double)101;
		//cout << ran << endl;
		for (int k = 0; k < group_number; k++)
		{
			if (k == 0 && ran > 0 && ran <= mp(0, 0))
				for (int j = 0; j < chromlength; j++)
					res(i, j) = mp(0, j + 1);
			else if (k > 0 && ran <= mp(k, 0) && ran>mp(k - 1, 0))
				for (int j = 0; j < chromlength; j++)
					res(i, j) = mp(k, j + 1);
		}
	}
	return res;
}

这里我们使用了时间代价较高的算法,可以使用一些算法如二分法,来减少代码的时间代价。

交叉(crossover)

下面是交叉(crossover)的一个例子,在一次交叉中,我们随机把其中三个位于同一位置的编码进行交换,产生新的个体。
在这里插入图片描述

下面给出交叉(crossover)的c++代码:

void ga::Crossover(MatrixXi & x, int group_number, int chromlength)
{
	srand((unsigned)time(NULL));
	double ran1 = rand() % 100 / (double)101;
	if (ran1 > Crossover_probability)
		return ;
	for (int i = 0; i < group_number ; i=i+2)
	{
			double ran2 = rand() % 100 / (double)101;
			int place = ran2 * chromlength;
			for (int j = place; j < chromlength; j++)
			{
				MatrixXi temp(group_number, chromlength - place + 1);
				temp(i, j - place) = x(i, j);
				x(i, j) = x(i + 1, j);
				x(i + 1, j) = temp(i, j - place);
			}
	}
}
变异(mutation)

从种群 S k − 1 S_{k-1} Sk1中抽取一个 x i x_i xi,对其中的一位(或多位)取反(0变成1,1变成0),让我们来举个栗子:
个体初始二进制编码:
10100100101011
经过基因突变后:
10101101101010
可以看到,二进制编码中某些位置发生的反转,使得产生了一个新的个体。

我们使用三个变量 p r , p c , p m p_r,p_c,p_m pr,pc,pm 来表示这三个事件的概率,其中 0 < p r , p c , p m < 1 0<p_r,p_c,p_m<1 0<pr,pc,pm<1
p r + p c + p m = 1 p_r+p_c+p_m=1 pr+pc+pm=1,通过生成随机数,观察随机数取到哪个事件的概率区间,我们就可以来随机模拟遗传操作。

下面给出变异(mutation)的c++代码:

void ga::Mutation(MatrixXi & x, int group_number, int chromlength)
{
	srand((unsigned)time(NULL));
	double ran1 = rand() % 100 / (double)101;
	if (ran1 > Mutation_probability)
		return;
	for (int i = 0; i < group_number; i++)
	{
		double ran2 = rand() % 100 / (double)101;
		cout << ran2 << endl;
		int place = ran2 * chromlength;
		if (x(i, place) == 0)
			x(i, place) = 1;
		else 
			x(i, place) = 0;
	}
}

遗传算法的不足和启发

1.遗传算法不能很有效的解出问题的最优解,但总是会比之前改进一步。
2.遗传算法的作用在于调参,通过遗传算法可以进一步提高模型的准确率。

完整代码: 来源:
https://github.com/repos-algorithms/genetic/blob/master/genetic-helloworld.cpp

#pragma warning(disable:4786)		// disable debug warning

#include <iostream>					// for cout etc.
#include <vector>					// for vector class
#include <string>					// for string class
#include <algorithm>				// for sort algorithm
#include <time.h>					// for random seed
#include <math.h>					// for abs()

#define GA_POPSIZE		2048		// 种群数量
#define GA_MAXITER		16384		// 最大迭代次数
#define GA_ELITRATE		0.10f		// 存活比例
#define GA_MUTATIONRATE	0.25f		// 突变比例
#define GA_MUTATION		RAND_MAX * GA_MUTATIONRATE
#define GA_TARGET		std::string("Target!")

using namespace std;				// polluting global namespace, but hey...

struct ga_struct
{
	string str;						// the string
	unsigned int fitness;			// its fitness
};

typedef vector<ga_struct> ga_vector;// for brevity

void init_population(ga_vector &population,  
	ga_vector &buffer)  //初始化种群
{
	int tsize = GA_TARGET.size();

	for (int i = 0; i < GA_POPSIZE; i++) {
		ga_struct citizen;

		citizen.fitness = 0;
		citizen.str.erase();

		for (int j = 0; j < tsize; j++)
			citizen.str += (rand() % 90) + 32;

		population.push_back(citizen);
	}

	buffer.resize(GA_POPSIZE);
}

void calc_fitness(ga_vector &population)  //计算目标函数值
{
	string target = GA_TARGET;
	int tsize = target.size();
	unsigned int fitness;

	for (int i = 0; i < GA_POPSIZE; i++) {
		fitness = 0;
		for (int j = 0; j < tsize; j++) {
			fitness += abs(int(population[i].str[j] - target[j]));
		}

		population[i].fitness = fitness;
	}
}

bool fitness_sort(ga_struct x, ga_struct y)
{
	return (x.fitness < y.fitness);
}

inline void sort_by_fitness(ga_vector &population)  //排序
{
	sort(population.begin(), population.end(), fitness_sort);
}

void elitism(ga_vector &population,   
	ga_vector &buffer, int esize)  // 寻找最优个体
{
	for (int i = 0; i < esize; i++) {
		buffer[i].str = population[i].str;
		buffer[i].fitness = population[i].fitness;
	}
}

void mutate(ga_struct &member)  //突变算法
{
	int tsize = GA_TARGET.size();
	int ipos = rand() % tsize;
	int delta = (rand() % 90) + 32;

	member.str[ipos] = ((member.str[ipos] + delta) % 122);
}

void mate(ga_vector &population, ga_vector &buffer)  //配对
{
	int esize = GA_POPSIZE * GA_ELITRATE;
	int tsize = GA_TARGET.size(), spos, i1, i2;

	elitism(population, buffer, esize);

	// Mate the rest
	for (int i = esize; i < GA_POPSIZE; i++) {
		i1 = rand() % (GA_POPSIZE / 2);
		i2 = rand() % (GA_POPSIZE / 2);
		spos = rand() % tsize;

		buffer[i].str = population[i1].str.substr(0, spos) +
			population[i2].str.substr(spos, esize - spos);

		if (rand() < GA_MUTATION) mutate(buffer[i]);
	}
}

inline void print_best(ga_vector &gav)
{
	cout << "Best: " << gav[0].str << " (" << gav[0].fitness << ")" << endl;
}

inline void swap(ga_vector *&population,  //交叉算法
	ga_vector *&buffer)
{
	ga_vector *temp = population; population = buffer; buffer = temp;
}

int main()  //主函数
{
	srand(unsigned(time(NULL)));

	ga_vector pop_alpha, pop_beta;
	ga_vector *population, *buffer;

	init_population(pop_alpha, pop_beta);
	population = &pop_alpha;
	buffer = &pop_beta;

	for (int i = 0; i < GA_MAXITER; i++) {
		calc_fitness(*population);		
		sort_by_fitness(*population);	
		print_best(*population);		

		if ((*population)[0].fitness == 0) break;

		mate(*population, *buffer);		
		swap(population, buffer);		
	}

	return 0;
}

  • 9
    点赞
  • 98
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
遗传算法(GA)是一种模拟自然进化过程的优化算法,用于解决复杂的问题。车间调度是一种重要的管理问题,通过合理安排工作任务和资源分配,提高生产效率和资源利用率。在MATLAB中,可以使用遗传算法求解车间调度问题。遗传算法的基本步骤如下: 1. 初始化种群:随机生成一组车间调度方案作为初始种群。 2. 适应度函数:根据车间调度方案,计算每个个体的适应度值。 3. 选择操作:根据适应度值,选择一定数量的个体作为父代。 4. 交叉操作:对父代个体进行交叉操作,生成一定数量的子代。 5. 变异操作:对子代个体进行变异操作,引入一定的随机性。 6. 父代与子代合并:将父代和子代个体合并为新的种群。 7. 适应度评估:计算新种群中每个个体的适应度值。 8. 环境选择:根据适应度值,选择一定数量的个体作为下一代的父代。 9. 终止条件:达到最大迭代次数或找到满意的解。 10. 输出结果:输出找到的最优解,即最优车间调度方案。 具体的MATLAB代码实现可能包括以下几个部分: 1. 随机生成初始种群的函数。 2. 计算适应度值的函数。 3. 选择操作的函数。 4. 交叉操作的函数。 5. 变异操作的函数。 6. 计算适应度值的函数。 7. 输出最优解的函数。 8. 主程序:按照上述步骤依次执行,直到满足终止条件。 以上就是使用遗传算法求解车间调度问题的思路和大致的步骤。具体的MATLAB代码实现可以根据具体问题的需求和约束进行调整和编写。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员毛师傅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值