作者: abruzzi
链接:http://abruzzi.javaeye.com/blog/266677
发表时间: 2008年11月13日
声明:本文系JavaEye网站发布的原创博客文章,未经作者书面许可,严禁任何网站转载本文,否则必将追究法律责任!
在维基(wiki)中关于自然选择的词条的搜索结果如下:
基因是遗传特征的基础,也是自然选择的单位,自然选择则是演化的主要机制。经过自然选择而能够称成功生存, 称为“适应”;当一个物种中的不同族群因为自然选择而产生生物分类学上的差异时,则称为“物种形成”; 若是族群因为不受自然选择青睐而导致族群规模缩小进而消失,则称为“灭绝”。
这个理论最早是由查尔斯·达尔文在1859年出版的《物种起源》中提出,并因此广为人知。这个观念也衍生出一些特例。 例如性选择(简称性择)解释有性生殖生物的某些遗传特征得以保存的原因;人工选择(简称人择)则将自然选择概念 应用在受人类圈养的生物上,例如家畜、宠物与农作物的育种。此外自然选择中,与性选择、人工选择无关的部分, 则称为生态选择。
* 过度繁殖。
* 遗传和变异。
* 生存斗争,由个体之间的斗争和于无机环境之间的斗争造成。
* 适应。
遗传算法(Genetic Algorithm)由密歇根大学的约翰·霍兰德(John Holland)和他的同事于二十世纪六十年代在对细胞自动机(cellular automata)进行研究时率先提出。是一种全局优化算法,应用领域比较广泛。
遗传算法借鉴了达尔文的进化论,也使用了一些达尔文定义的术语,如选择,遗传,变异,适应度等。当然,也引入了一些后来自然科学家提出的概念,如基因,DNA,染色体等。
好和坏的区别:
如果一个个体适应自身所在的环境,则此个体为“好”,反之为“坏”。在遗传算法中,这个概念由“适应度”来表示。当然,适应度跟具体要解决的问题有关。通常,适应度计算函数会返回一个0-1之间的浮点数。
进化论的本质:
遗传算法的自然语言描述:
- 从一个种群中随机的取出一定数量的个体,用以进化计算
- 对这些个体进行选择,遗传,变异等操作
- 计算个体的适应度,并排序,选择出下一次迭代的个体,淘汰适应度较低的一些个体
- 如果选择出的个体已经足够“好”,则停止计算,否则继续1-3的操作
最后计算的结果即为最优解集合。
- 染色体
一个染色体上可以有很多个DNA,每个DNA上有很多个基因,基因可以通过交叉,变异等操作发生改变,染色体是遗传算法的基本单元,一个染色体可以是一个一维的数组,数组中的每一个单元都是一个基因,因此,染色体被实现成串类型。 - 基因
如一个染色体中STRING,包含有五个基因,STRING[] = {"X","Y","Z","W","Q"},基因STRING[0]的等位基因为X,STRING[1]上的等位基因为Y等。 - 选择
复制操作,将上一级(父)的染色体复制给下一级(子),此为遗传,通过遗传操作可以将父代的优秀的,适应环境的因素传递给子代,但是不发生改变 - 交叉
发生部分改变,将两个染色体的一部分互换,即为交叉。当然,从种群的角度来看,交叉同样不影响种群中的基因情况。但是对于个体来说,交叉操作可能会产生比较优秀的染色体。比如,一个染色体SA含有两个不利于适应的基因,SA[3]和SA[4],但是染色体SB含有两个利于适应环境的基因SB[3]和SB[4],此时如果SA和SB发生交叉操作,且交叉位置为3,则SA可以提高自己的适应度,从而加快迭代的结束。 - 变异
变异,即基因上的等位基因发生改变,这种改变是随机的,不确定的。至于这种改变是否是“好”的,需要靠适应度函数来确定。 - 适应度(fitness)
个体对环境的适应程度叫做适应度(fitness)。为了体现染色体的适应能力,引入了对问题中的每一个染色体都能进行度量的函数,叫适应度函数.
由于操作之前,个体对自己将来会进化成什么样的结果是一无所知的,所以需要有一个结束点,一个度量方式。当然,自然界是没有结束点的,生物只能说适应了目前的环境,但是它会一直进化,直到适应了新的环境或者灭绝。
一个基于JAVA的遗传算法的实现是JGAP(Java Genetic Algorithm Package)。参见:http://jgap.sourceforge.net/
遗传算法就是遍历搜索空间或连接池,从中找出最优的解。搜索空间中全部都是个体,而群体为搜索空间的一个子集。
并不是所有被选择了的染色体都要进行交叉操作和变异操作,而是以一定的概率进行,一般在程序设计中交叉发生的概率要比变异发生的概率选取得大若干个数量级。大部分遗传算法的步骤都很类似,常使用如下参数:
- Fitness函数
这是与具体实现有密切关系的一个参数,通常需要自己实现,定义“好”与“坏”的界限 - 适应度阀值
适合度中的设定的阀值,当最优个体的适应度达到给定的阀值,或者最优个体的适应度和群体适应度不再上升时(变化率为零),则算法的迭代过程收敛、算法结束。否则,用经过选择、交叉、变异所得到的新一代群体取代上一代群体,并返回到选择操作处继续循环执行。 - 交叉概率
在循环中进行交叉操作所用到的概率。交叉概率一般取0.6至0.95之间的值,Pc太小时难以向前搜索,太大则容易破坏高适应值的结构。 - 变异概率
从个体群中产生变异的概率,变异概率一般取0.01至0.03之间的值变异概率Pm太小时难以产生新的基因结构,太大使遗传算法成了单纯的随机搜索。 - 个体的数目
有定长和变长两种。它对算法的性能也有影响。由于GA是一个概率过程,所以每次迭代的情况是不一样的,系统参数不同,迭代情况也不同。
JGAP对很多参数设置进行了隐藏,封装。使用者只需要提供自己的Fitness函数即可,当然如果你对GA比较熟悉的话,可以对这些隐式参数进行修改。
JGAP中自带的例子中有这样一个例子:MinimizingMakeChangeFitnessFunction.java,我们下来就以这个例子来说一说这个GA包的用法。
- 设计自己的染色体(Chromosome)
- 实现适应度函数
- 设置Configuration 对象
- 建立一个种群(可能的解集)
- 开始进化(运行遗传算法)
下面是一个例子,我删除了一些注释和版权信息等,并做了一些简单的格式化
package examples;
import org.jgap.*;
public class MinimizingMakeChangeFitnessFunction
extends FitnessFunction {
private final int m_targetAmount;
public static final int MAX_BOUND = 4000;
public MinimizingMakeChangeFitnessFunction(int a_targetAmount) {
if (a_targetAmount < 1 || a_targetAmount >= MAX_BOUND) {
throw new IllegalArgumentException(
"Change amount must be between 1 and " + MAX_BOUND + " cents.");
}
m_targetAmount = a_targetAmount;
}
public double evaluate(IChromosome a_subject) {
boolean defaultComparation = a_subject.getConfiguration().
getFitnessEvaluator().isFitter(2, 1);
int changeAmount = amountOfChange(a_subject);
int totalCoins = getTotalNumberOfCoins(a_subject);
int changeDifference = Math.abs(m_targetAmount - changeAmount);
double fitness;
if (defaultComparation) {
fitness = 0.0d;
} else {
fitness = MAX_BOUND/2;
}
if (defaultComparation) {
fitness += changeDifferenceBonus(MAX_BOUND/2, changeDifference);
} else {
fitness -= changeDifferenceBonus(MAX_BOUND/2, changeDifference);
}
if (defaultComparation) {
fitness -= computeCoinNumberPenalty(MAX_BOUND/2, totalCoins);
} else {
fitness += computeCoinNumberPenalty(MAX_BOUND/2, totalCoins);
}
return Math.max(1.0d, fitness);
}
protected double changeDifferenceBonus(double a_maxFitness,
int a_changeDifference) {
if (a_changeDifference == 0) {
return a_maxFitness;
} else {
if (a_changeDifference * a_changeDifference >= a_maxFitness / 2) {
return 0.0d;
} else {
return a_maxFitness / 2 - a_changeDifference * a_changeDifference;
}
}
}
protected double computeCoinNumberPenalty(double a_maxFitness, int a_coins) {
if (a_coins == 1) {
return 0;
} else {
return (Math.min(a_maxFitness, a_coins * a_coins));
}
}
public static int amountOfChange(IChromosome a_potentialSolution) {
int numQuarters = getNumberOfCoinsAtGene(a_potentialSolution, 0);
int numDimes = getNumberOfCoinsAtGene(a_potentialSolution, 1);
int numNickels = getNumberOfCoinsAtGene(a_potentialSolution, 2);
int numPennies = getNumberOfCoinsAtGene(a_potentialSolution, 3);
return (numQuarters * 25) + (numDimes * 10) + (numNickels * 5) +
numPennies;
}
public static int getNumberOfCoinsAtGene(IChromosome a_potentialSolution,
int a_position) {
Integer numCoins =
(Integer) a_potentialSolution.getGene(a_position).getAllele();
return numCoins.intValue();
}
public static int getTotalNumberOfCoins(IChromosome a_potentialsolution) {
int totalCoins = 0;
int numberOfGenes = a_potentialsolution.size();
for (int i = 0; i < numberOfGenes; i++) {
totalCoins += getNumberOfCoinsAtGene(a_potentialsolution, i);
}
return totalCoins;
}
}
FitnessFunction 是一个抽象类,MinimizingMakeChangeFitnessFunction集成该类,并对evaluate方法给出具体实现,这个方法就是所谓的适应度函数。
确定染色体的结构及基因
第一步,决定“染色体”的结构和性质,如染色体包含多少个基因,以及这些基因有什么表现形式(如人类基因记载了人的肤色,面貌特征等信息)。这个例子中,程序会生成一堆零钱,这些零钱包含了一些种类的美国硬币,而钱数正好与用户的输入向匹配。这个例子中,染色体的表现形式就是一堆硬币。每个染色体中有四个基因(15分,10分,5分和1分)
这个染色体可以看作是一个串:STRING[4] = {"quarter","dime","nickel","pennie"},第一个位置上的数目为3(等位基因),则表示15×3 = 45,第二个位置上为5(等位基因),则表示10×5 = 50分,等等。
适应度函数的实现---什么算“好”
在这个例子中,我们认为,硬币的总数目恰好等于用户指定的数目(如85,34等),而且硬币的数目尽可能的少,想必学习过算法设计与分析课程的有点熟悉吧(贪婪算法的一个例子就是这个大名鼎鼎的“找钱问题”)。
在就是一些处理字符串,计算总数之类的方法,总的没有什么难以理解的地方,就不消多说了,如果还不明白可以留言,我会耐心回答的,呵呵。
总之:在使用JGAP时,关键问题有两点:
- 确定染色体,以及基因的分配
- 确定适应度函数(描述如何区分好坏)
小结:
遗传算法的应用领域还是比较广泛的,不过解决的问题可以统称为全局优化相关的问题 。大家可以在参考文献给出的链接中获取更多的信息。
参考文献:
中文维基:http://zh.wikipedia.org/w/index.php?title=%E8%87%AA%E7%84%B6%E9%80%89%E6%8B%A9&variant=zh-cn
IBM developeWorks:http://www-128.ibm.com/developerworks/cn/java/j-lo-robocode1/index.html