演化算法(一) 基本概念

演化算法

1、简介

演化算法,又称为进化算法(Evolutionary Algorithm)、进化计算(EvolutionaryComputing)或遗传算法(Genetic Algorithm),是一种元启发式(metaheuristic)方法(定义见:http://en.wikipedia.org/wiki/Metaheuristic)。前面谈到的模拟退火算法也是一种元启发式算法。二者的主要区别也是演化算法的主要特点:虽然它们都在解空间内进行搜索,但模拟退火算法是一种轨迹式(trajectory)方法,而演化算法是一种基于种群(population-based)的方法。

具体地说,前者在搜索解空间时是沿着某一条轨迹前进的,最终收敛到某个解的局部。每次迭代时只对一个解操作。而后者的搜索过程是从一个初始解的集合(称为初始种群)开始的,种群中的每一个解都沿着一定的轨迹搜索,每前进一步称为种群的进化,得到的解集称为种群的一代(generation)。这样便增加了在庞大解空间中找到最优解的概率。在种群进化时,种群中的解会发生变异、杂交和选择等操作从而生成新的解,构成新一代种群。如下面两图所示。

这个过程与自然界生物的进化很类似,生物总群的每一代都会有或多或少的变异发生,而不用个体之间的杂交也是很普遍的现象。变异和杂交所产生的新个体不一定会适应生存环境,这就是自然选择的过程:适应的个体存活下来,不适应的个体遭到淘汰,最后存活下来的个体组成新一代的种群。

                                                                                                                                                          


2. 伪代码

一个演化算法的基本结果如下:

P <- GenerateInitialPopulation()

Evaluate(P)

while

termination conditions not metdo

  P’ <- Recombine(P)

  P’’ <- Mutate(P’)

 Evaluate(P’’)

P <- Select(P’’ U P)

endwhile

注意,在循环中的Recombine和Mutate操作即对应杂交算子和变异算子,有时重组(Recombine)也称作交叉(crossover)。这两种操作是必需的,但是顺序不是固定的,即也可以先进行变异然后再交叉。选择(select)操作是演化的关键一步,这里采用的方式是综合交叉和变异产生的后代和当代个体,选择优势个体(评估函数较好)组成下一代种群。选择的依据就是每一个解的评估函数值(通过Evaluate计算)。另一种方案是仅从新个体中选择适当数量的个体作为下一代种群。

3.设计要素

设计一个演化算法有以下几点需要考虑:

a.  表示方式

一个演化算法或是算法中的算子是否有效很大程度上依赖于解的表示方式。有些算子对一种表示方式的解有很好地效果,而对另一种表示方式的解却是无效的甚至是无法实现的。例如,求解SAT问题时经常使用的一种算子是反位操作,即对每一个变量的布尔值进行取反。这时表达所有变量真值情况(一个解)的很自然的方式就是使用一个长度为n的二进制位向量(n是变量的个数),以1表示变量取true值,以0表示变量取false值。

然而,同样的算子却不能用于TSP问题。TSP问题要求找到旅行商的一个城市环游路线,使得耗费最小。如果使用整数标记了每座城市,那么城市标号的一个排列就是一个解,这也是一种很自然的表示方式。对这样的解进行变换的常用算子就是2-交换算子:随机交换两个城市的位置形成新的排列。显然,这种算子是二进制位向量表示无法实现的。

总之,解的表示方式会对算子的设计甚至整个算法的效果产生决定性影响,在设计时要具体问题具体分析。

b.  评估函数

评估函数最常见的选择就是优化目标。显然,如果每次选择新一代个体都使用导致较好优化目标的个体,那么最终的结果是令人满意的。但有时候也会使用其他标准作为评估函数(考虑种群多样性等其他因素,详见第4节)。设计评估函数的常识性指导原则是:最优解应该被赋予最优的评估值

c.  变化算子

变化算子是演化算法的基础。种群能够不断地演化最后接近最优个体得益于变化算子的作用。把算子分成交叉和变异两类也是来自自然界的灵感。自然界中的生物的染色体(解)上的基因(部分解)发生变异时,生物的性状通常会发生显著变化。从长远来看,这种变异是有利于生物体生存的。不同生物体之间的杂交会使双亲个体的优良性状遗传到后代个体中,也同样有利于生物的生存。这就是交叉和变异算子会有助于产生近似最优解得原因。

变化算子的设计通常是问题相关的。某一种算子对特定问题有好的结果,却不一定是通用的。例如,一种常用的变异算子是反序(inverse),它使一个解的随机一段子序列中的元素反序。如下图,解s1中两个间隔点之间的部分反序后得到的新解为s2。

S1: 2 5 6 |3 4 8 9| 1 7

S2: 2 5 6 |9 8 4 3| 1 7

这个算子适用TSP问题的原因是,TSP的一个解中可能含有违反几何原理的子路径存在,如下图,1,2,3,4四个城市之间的距离(耗费)可能有如图所示的关系。路径1->3->4->2->1显然要比路径1->4->3->2->1要短,而反序算子就可以打破类似后者的子路径。



然而,这个算子在其他问题如生产调度问题中不一定有效。

d. 选择

选择算子的基本方法就是选择评估函数较优的个体进入下一代种群。选择的过程可以是确定的,也可以是随机的。但是要保证一点:评估值越好,被选中的概率应该越大。

选择的具体方式有很多。例如,可以定义如下选择概率:


Pi定义了第i个个体被选择的概率,Fi是其评估函数值,分母是所有个体的评估函数值得平均值。如果某个个体的评估函数值大于或等于平均值,则确定被选中;如果某个个体的评估函数值小于均值,则以这个概率被选中。显然,评估函数值越大接近均值,越容易被选中(这里定义评估函数值较大为优)。

另一种方式是,对当前种群进行多次随机取样,每次从样本中选取一个或多个最优解,直到选择了足够多的解为止。

e. 初始解

初始种群对算法的结果有一定的影响。在解空间中,如果初始解距离最优解很近,那么进过有限几次迭代,种群就可以进化到较优的状态;如果初始解选择的不好,那么算法效果就会受影响。

4. 解的强化(intensification)和多样化(diversification)

在种群进化的过程中,解的变化主要有两个方向:强化和多样化。强化是指解的质量有越变越好的趋势。然而,如果一直沿着某个解或某些解的领域范围搜索,容易陷入局部最优(local optimal)的情况,导致过早收敛(prematureconvergence)。如下图所示,



解S1就是一个局部最优解,而显然解S2(可能也是局部最优)比它要好,而接下来的搜索如果不跳出S1的邻域就无法获得较为精确的近似解。多样化方向策略就是为了解决这个问题。简单地说,多样化就是尽量使得搜索范围均匀分布在解空间中,而不只是局限于某个小的范围内。看起来,解的强化和多样化是相互矛盾的两个进化方向,但是二者对于得到较优的解都是不可或缺的。因此,考虑两个方向的平衡显得尤为重要,这也是设计演化算法的关键所在。

在演化算法的变化算子中,交叉算子就是为了实现解的多样化。通过使不同解的一部分按照一定的规则进行重组,得到一些与原来的解有一定距离的新解,消除了解的局部最优性。而变异算子就是为了实现解的强化。通过对解本身进行一定程度的扰动,进行局部邻域的搜索,找到局部领域中质量较好的解。这两种算子的设计与特定问题紧密相关,没有百试不爽的算子可用。

5.实现举例


这里借助TSP问题来举一例,看一看演化算法的具体实现。

首先定义一些算法必备的变量,具体功能详见注释。其中反转率是指在实现变异算子反转操作时,被反转的城市范围所占整个解维度(城市数量)的比例。问题解的表示方式:一个解就是城市编号的一个排列。


    //种群的大小
    private int pSize;
    //初始种群
    private ArrayList<ArrayList<Integer>> population;
    //交叉后的种群
    private ArrayList<ArrayList<Integer>> crossPop;
    //变异后的种群
    private ArrayList<ArrayList<Integer>> mutatePop;
    //最大迭代次数
    private int maxIter;
    //反转率
    private double mrate;

接下来是算法的主体部分。整个演化过程执行一定的迭代次数,可以多次执行取平均值。在演化之前要生成一个初始种群。这里采用随机生成一个排列的方法生成一个解。

public void run() {
		//首先生成初始种群
		long start,end;
		double sumTime = 0;
		double sumDist = 0;
		int times = 50;
		while(times-- > 0)
		{
			start = System.currentTimeMillis();
			initialPopulation();
			int count = 0;
			while (++count <= maxIter) {
				// 进行重组操作
				recombinePop();
				// 进行变异操作
				mutatePop();
				// 进行选择操作
				selectNextGen();
			}
			shortestDist = getOptimal();
			end = System.currentTimeMillis();
			sumDist += shortestDist;
			sumTime += end - start;
		}
		System.out.println("50 次平均结果:" + sumDist / 50 + "\t" + sumTime / 50);
	}


private void initialPopulation() {
		int c = 0;
		ArrayList<Integer> solution;
		while(++c <= pSize)
		{
			solution = randomSolution();
			population.add(solution);
		}
		
	}
        private ArrayList<Integer> randomSolution() {
		// 随机产生一个解
		ArrayList<Integer> res = new ArrayList<Integer>();
		Random rand = new Random(System.nanoTime());
		ArrayList<Integer> seed = new ArrayList<Integer>();
		int j,index;
		for(j = 0; j < number; j++)seed.add(j);
		while(!seed.isEmpty())
		{
			index = rand.nextInt(seed.size());
			res.add(seed.get(index));
			seed.remove(index);
		}
		return res;
	}
第一步是重组操作,算子的思想是:选取一个切入点,保留第一个个体的从第一个城市到切入点城市的部分,剩下部分由第二个个体的城市无重复填满。两个个体随机从种群中选取。

private void recombinePop() {
		// 交叉思想:选取一个切入点 保留第一个个体的从第一个城市到切入点城市的部分 剩下部分由第二个个体的城市无重复填满
		crossPop = new ArrayList<ArrayList<Integer>>();
		int i,j,cut,m,n,c = 0;
		Random rand = new Random(System.nanoTime());
		
		int[] isUsed;
		while(++c <= pSize)
		{
			isUsed = new int[number];
			for(int index : isUsed)index = 0;
			i = rand.nextInt(pSize);
			do j = rand.nextInt(pSize);
			while(i == j);
			
			ArrayList<Integer> newSol = new ArrayList<Integer>();
			cut = rand.nextInt(number);
			for(n= 0; n <= cut; n++)
			{
				newSol.add(population.get(i).get(n));
				isUsed[population.get(i).get(n)] = 1;
			}
			for(m = 0; m < number; m++)
			{
				if(isUsed[population.get(j).get(m)] == 0)
				{
					newSol.add(population.get(j).get(m));
				}
			}
			crossPop.add(newSol);
		}
		//population = (ArrayList<ArrayList<Integer>>)crossPop.clone();
	}

第二步是变异操作,算子的思想是:对每一个解简单执行inverse算子。

private void mutatePop() {
		// 变异思想:对每一个解简单地进行inverse算子 反转的部分解大小为mrate*number
		mutatePop = new ArrayList<ArrayList<Integer>>();
		int size = (int)mrate * number;
		Random rand = new Random(System.nanoTime());
		int start,end;
		
		for(ArrayList<Integer> sol : crossPop)
		{
			start = rand.nextInt(number - size + 1);
			end = start + size - 1;
			sol = inverseMove(sol, start, end);
			mutatePop.add(sol);
		}
		
	}

其中inverse算子的实现如下:

private ArrayList<Integer> inverseMove(ArrayList<Integer> sol, int cut1, int cut2) {
		// TODO Auto-generated method stub
		int i = cut1;
		int j = cut2;
		while(i < j)
		{
			int job1 = sol.get(i);
			int job2 = sol.get(j);
			
			sol.remove(j);
			sol.add(j, job1);
			sol.remove(i);
			sol.add(i, job2);
			
			i++;
			j--;
		}
		return sol;
	}


最后一步是选择操作,算子的思想是:在原来种群和经过变异和交叉操作后的种群中选择较好的个体进入下一轮。这里的评估函数就是旅行商的旅行距离,也就是优化目标本身。

private void selectNextGen() {
		// 在原来种群和经过变异和交叉操作后的种群中选择较好的个体进入下一轮
		Candidate can = null;
		mutatePop.addAll(population);
		ArrayList<Candidate> canList = new ArrayList<Candidate>();
		
		for(ArrayList<Integer> sol : mutatePop)
		{
			can =  new Candidate();
			can.setList(sol);
			can.setSortIndex(getTotalDist(sol));
			canList.add(can);
		}
		
		Collections.sort(canList, new MyComparator1());
		
		population = new ArrayList<ArrayList<Integer>>();
		int i,size = pSize;
		for(i = 0;  i < size; i++)
		{
			//System.out.println(canList.get(i).getSortIndex());
			population.add(canList.get(i).getList());
		}
		
	}

另外,计算旅行距离的函数实现如下。

public static double getTotalDist(ArrayList<Integer> list, double[][] distances) {
		
	    int number = list.size();
		double res = 0;
		int i = 0;
		for(; i < number - 1; i++)
			res += distances[list.get(i)][list.get(i + 1)];
		if(number > 1)
			res += distances[list.get(number - 1)][0];
		return res;
	}

}

6.参考资料

a. 维基百科

b. 如何求解问题——现代启发式方法

c.Metaheuristic in Combinatorial Optimization:Overview and Conceptual Comparison

(2012年3月25日)

注:今后我仍将继续学习演化算法,尤其算子的设计,初始解的构造等方面,并更新有关内容。
  • 10
    点赞
  • 76
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
遗传算法是一种基于遗传学和进化论的优化算法,用于寻找复杂问题的最优解。它模拟生物进化的过程,通过选择和交叉等操作,不断优化种群中的个体,以适应特定的环境和目标。遗传算法广泛应用于函数优化、组合优化、机器学习、神经网络、人工生命等领域。 遗传算法基本概念: 1. 适应度函数:适应度函数用于评估每个个体的优劣程度,是遗传算法中优化目标的核心。适应度函数越小,表示个体越优秀。 2. 选择操作:选择操作通过随机选择适应度较高的个体,保留其基因信息,淘汰适应度较低的个体,以此来增加种群中优秀个体的比例。 3. 交叉操作:交叉操作将两个个体的基因信息进行交叉,产生新的个体,以此来创造新的优秀个体。 4. 变异操作:变异操作通过改变个体的某些基因,来增加种群的多样性,避免种群陷入局部最优解。 遗传算法的应用: 1. 函数优化:遗传算法可以用于优化函数问题,例如寻找最大值或最小值。 2. 组合优化:遗传算法可以用于解决组合优化问题,例如旅行商问题、背包问题等。 3. 机器学习:遗传算法可以用于机器学习领域,例如优化神经网络结构、选择特征、参数调整等。 4. 人工生命:遗传算法可以用于人工生命领域,例如模拟生命进化的过程,研究生命的起源和演化等。 总的来说,遗传算法是一种强大的优化算法,能够在复杂的问题中找到全局最优解。但是,由于遗传算法的计算复杂度较高,需要充分考虑算法参数的选择和问题特征的适应性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值