最详细的遗传算法解析!!

  • 遗传算法
  • 遗传算法的大致步骤
  • 遗传算法框架
  • 编码
  • 二进制编码
  • 浮点编码法
  • 格雷码
  • 初代群体的选取
  • 适应度函数
  • 遗传操作
  • 选择(select)
  • 轮盘赌策略(roulette wheel selection)
  • 随机竞争策略(Stochastic Tournament)
  • 竞标赛策略(tournament selection)
  • 交叉(crossover)
  • 变异(mutation)
  • 遗传算法的不足和启发


遗传算法

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

遗传算法的大致步骤

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

遗传算法框架

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

max java实现用遗传算法自动排产工单_算法_03

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


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


编码

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

二进制编码

二进制编码中的一个位能表示出2种状态的信息量,因此足够长的二进制染色体便能表示所有的特征。下面我们模拟一下编码过程(以四位二进制编码为例):
这里我们假设有六个个体(java实现用遗传算法自动排产工单_算法_04

x: java实现用遗传算法自动排产工单_机器学习_05,java实现用遗传算法自动排产工单_java实现用遗传算法自动排产工单_06,java实现用遗传算法自动排产工单_深度学习_07,java实现用遗传算法自动排产工单_机器学习_08,java实现用遗传算法自动排产工单_机器学习_09,java实现用遗传算法自动排产工单_深度学习_10

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

它有以下一些优点:

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

浮点编码法

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

格雷码

对于一些连续函数的优化问题,相邻整数的二进制编码可能具有较大的Hamming距离,例如15和16的二进制表示为01111和10000,因此需要改变所有的位才能从15改进到16,这种缺陷将降低遗传算子的搜索小效率。
格雷码中连续的两个整数编码值之间仅仅只有一个马尾是不相同的,其余码位都完全相同,例如15和16的格雷码位01000和11000。
设二进制码为 java实现用遗传算法自动排产工单_c++_11, 其对应的格雷码为 java实现用遗传算法自动排产工单_算法_12, 则二进制码转换为格雷码的转换公式为:

java实现用遗传算法自动排产工单_c++_13
java实现用遗传算法自动排产工单_c++_14

优点:
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,上述六个个体的适应度为:
java实现用遗传算法自动排产工单_c++_15=java实现用遗传算法自动排产工单_c++_16
java实现用遗传算法自动排产工单_c++_17=java实现用遗传算法自动排产工单_算法_18
java实现用遗传算法自动排产工单_机器学习_19=java实现用遗传算法自动排产工单_java实现用遗传算法自动排产工单_20
java实现用遗传算法自动排产工单_java实现用遗传算法自动排产工单_21=java实现用遗传算法自动排产工单_算法_22
java实现用遗传算法自动排产工单_c++_23=java实现用遗传算法自动排产工单_机器学习_24
java实现用遗传算法自动排产工单_c++_25=java实现用遗传算法自动排产工单_java实现用遗传算法自动排产工单_26

下面给出适应度函数的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轮中,我们从种群java实现用遗传算法自动排产工单_算法_27中抽取n个java实现用遗传算法自动排产工单_c++_02,放入java实现用遗传算法自动排产工单_c++中。

轮盘赌策略(roulette wheel selection)

首先我们需要先抽取要遗传的染色体,设某一个个体被抽取的概率为java实现用遗传算法自动排产工单_深度学习_30,那么:

java实现用遗传算法自动排产工单_机器学习_31

其中java实现用遗传算法自动排产工单_深度学习_32为适应度函数的值。可以看到,轮盘赌策略模型依据适应度的大小来决定个体能被抽取到的概率,从上述例子中我们可以知道个体的适应度:此时可以知道各个个体被选取的概率为:
java实现用遗传算法自动排产工单_c++_15=java实现用遗传算法自动排产工单_深度学习_34
java实现用遗传算法自动排产工单_c++_17=java实现用遗传算法自动排产工单_算法_36
java实现用遗传算法自动排产工单_机器学习_19=java实现用遗传算法自动排产工单_java实现用遗传算法自动排产工单_38
java实现用遗传算法自动排产工单_java实现用遗传算法自动排产工单_21=java实现用遗传算法自动排产工单_算法_40
java实现用遗传算法自动排产工单_c++_23=java实现用遗传算法自动排产工单_机器学习_42
java实现用遗传算法自动排产工单_c++_25=java实现用遗传算法自动排产工单_算法_44

转盘如下所示:

java实现用遗传算法自动排产工单_算法_45


所以适应度越高,被抽取到的概率越大(能够传下自己的染色体的概率)。

随机竞争策略(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)的一个例子,在一次交叉中,我们随机把其中三个位于同一位置的编码进行交换,产生新的个体。

java实现用遗传算法自动排产工单_算法_46

下面给出交叉(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)

从种群java实现用遗传算法自动排产工单_算法_27中抽取一个java实现用遗传算法自动排产工单_c++_02,对其中的一位(或多位)取反(0变成1,1变成0),让我们来举个栗子:
个体初始二进制编码:
10100100101011
经过基因突变后:
10101101101010
可以看到,二进制编码中某些位置发生的反转,使得产生了一个新的个体。

我们使用三个变量java实现用遗传算法自动排产工单_c++_49 来表示这三个事件的概率,其中java实现用遗传算法自动排产工单_机器学习_50
java实现用遗传算法自动排产工单_深度学习_51,通过生成随机数,观察随机数取到哪个事件的概率区间,我们就可以来随机模拟遗传操作。

下面给出变异(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.遗传算法的作用在于调参,通过遗传算法可以进一步提高模型的准确率。

#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;
}