遗传算法是模仿自然界生物进化机制发展起来的随机全局搜索和优化方法,它借鉴了达尔文的进化论和孟德尔的遗传学说。其本质是一种高效、并行、全局搜索的方法,它能在搜索过程中自动获取和积累有关搜索空间的知识,并自适应的控制搜索过程以求得最优解。遗传算法操作使用适者生存的原则,在潜在的解决方案种群中逐次产生一个近似最优解的方案,在遗传算法的每一代中,根据个体在问题域中的适应度值和从自然遗传学中借鉴来的再造方法进行个体选择,产生一个新的近似解。这个过程导致种群中个体的进化,得到的新个体比原来个体更能适应环境,就像自然界中的改造一样。
遗传算法是一个迭代过程,在每次迭代中都保留一组候选解,并按某种优劣指标进行排序,然后按某种指标从中选出一些解,利用遗传算子,对其进行运算以产生新一代的一组解。重复上述过程,直到满足指定的收敛要求为止。
遗传算法涉及5大要素:参数编码、初始群体设定、适应度函数设计、遗传操作设计和控制参数设定。标准遗传算法流程框图如下:
遗传编码:用遗传算法求解问题时,必须在目标问题实际表示与遗传算法染色体结构之间建立联系,即确定编码和解码运算。常见的有二进制编码、Gray编码、实数编码、有序编码等,此次遗传算法解决TSP问题我们就采用有序编码,即用城市的编码序列表示一个遗传序列。
适应值函数:遗传算法将问题空间表示为染色体位串空间,为了执行适者生存的原则,必须对个体位串的适应性进行评价。由于适应值是群体中个体生存机会选择的唯一确定性指标,所以适应值函数的形成直接决定着群体的进化行为根据实际问题的经济含义,适应值可以是销售收入、利润、生存占有率或机器的可靠性等。
遗传操作:有三个,分别是选择、交叉、变异。
(1)选择:即从当前群体中选出个体以生成交配池的过程。所选择的这些个体具有良好的特征,以便产生优良的后代。选择的策略有很多种,解决TSP问题我们用轮盘赌选择,它与个体的适应值挂钩。
(2)交叉:将两个个体的遗传物质交换产生新的个体,它可以把两个个体的优良“格式”传递到下一代的某个个体中,使其具有优于前驱的性能。
具体的交叉步骤:从交配池随机取出要交配的一对个体,随机选择一个或多个交叉点,根据交叉概率参数实施交叉操作。交叉算法有单点交叉、多点交叉、均匀交叉。
(3)变异:在个体中遗传物质被改变,它可以使运算过程中丢弃的个体的某些重要特性得以恢复。
初始化群体:初始化群体中的个体一般是随机产生的,在问题解空间中均匀采样,随机生成一定数目的个体(为群体规模的2倍),然后从中挑出较好的个体构成初始群体。
控制参数的选取:主要的参数包括群体规模,交叉概率,变异概率,位串长度。
(1)选择编码策略,把参数集合X和域转换为相应编码空间S。
(2)定义适应值函数f(x)
(3)定义遗传策略,包括选择群体大小、选择、交叉、变异方法以及群体交叉概率、变异概率等遗传参数。
(4)随机初始化生成群体。
(5)计算群体中个体的适应值f(x)。
(6)按照遗传策略,运用选择、交叉和变异操作作用于群体,形成下一代群体。
(7)判断群体性能是否满足某一指标,或者已完成预定迭代次数,不满足则返回步骤(6),或者修改遗传策略再返回步骤(6)。
TSP问题,假设一个旅行商人要去n个城市,他必须经过且只经过每个城市一次,要求最后回到出发的城市,并且要求他选择的路径是所有路径中的最小值
拟使用的方法
基因编码->初始化种群->评估适配值-> 产生新种群->迭代…
(1)采用模拟退火算法解决Tsp问题中的策略,随机生成得一组个体组成的种群,然后根据路径长度得到每个个体的适配值(长度越短,个体存活率越高,可用总距离的倒数,即1/distance),确定迭代次数n。
(2)随机选择种群中两个个体,使用轮盘赌策略,每次按一定概率返回种群中个体下标。在这种策略下,个体的适配值越高,被选中的概率也越高。随机选择两个交叉点,执行交叉操作,将两点之间的基因段交换,判断进行交叉操作的个体之间的冲突的城市,进行冲突处理,直到路径满足每个城市只经过一次条件。
(3)对产生的新个体采用模拟退火算法中产生邻域的操作执行变异操作。判断新种群是否可以替代旧种群。得到新种群后,重新计算新种群各个个体的适配值,进行下一次迭代,直到进行n次迭代。
种群初始化:
void GeneticAlgorithm::init(){//随机初始化种群
group.resize(group_size,way()); //way()构造函数随机初始化
}
遗传操作:
//选择
void GeneticAlgorithm::choose(vector & group){
double sum_fitness = 0;
double fitness[group_size];//适应性数组
double chance[group_size];//概率数组
double pick;//用于轮盘赌的随机数
vector next;
//计算适应值总和
for (int i = 0; i < group_size; i++) {
fitness[i] = 1 / group[i].length;
sum_fitness += fitness[i];
}
//计算种群每个个体被抽中的概率
for (int i = 0; i < group_size; i++) {
chance[i] = fitness[i] / sum_fitness;
}
//轮盘赌
for (int i = 0; i < group_size; i++) {
pick = ((double)rand()) / RAND_MAX;//0到1的随机数
for (int j = 0; j < group_size; j++) {
pick -= chance[j];//不断减去当前个体的概率,直到pick为<=0
if (pick <= 0) {
next.push_back(group[j]);//选中
break;
}
if (j == group_size - 1) {//仍未选中,但是已经到最后一个了
next.push_back(group[j]);
}
}
}
group = next;
}
//交叉
void GeneticAlgorithm::cross(vector & group) {
int point = 0;
int choice1, choice2;//随机选择两个交叉点
while (point < group_size/*防止越界*/) {
//交叉概率
double pick = ((double)rand()) / RAND_MAX;//0到1的随机数
if (pick > p_cross)
continue;//大于p_cross不交叉
else {//否则交叉
choice1 = point;
choice2 = point + 1;
group[choice1].getNewSolution_cross(group[choice2]);//交叉
}
point += 2;
}
}
//变异
void GeneticAlgorithm::varia(vector & group) {//变异
int point = 0;
while (point < group_size) {
//变异概率
double pick = ((double)rand()) / RAND_MAX;//0到1的随机数
if (pick < p_varia) {//小于p_varia才变异
group[point].getNewSolution_varia();
}
point++;
}
}
遗传算法:
way GeneticAlgorithm::GA() {
srand((unsigned)time(NULL)); // 初始化随机数种子
init(); // 初始化种群
way best;
for (int i = 0; i < time_to_breed; i++) {
vector old_group = group;
choose(group);//选择
cross(group);//交叉
for(int other = 0;other < 5;other++){
varia(group);//变异
judge(old_group,group);//判断该子代能否取代亲本,若能则取代
}
for (int j = 0; j < group_size; j++) {
if (group[j].length < best.length){//贪心策略
best = group[j];
cout << i << "->>" << best.length << endl;
}
}
}
return best;
}
可以看到整个过程是不断下降的,这一点与模拟退火的演算过程不同,遗传算法比较快,但是最终结果的没有模拟退火的好。
核心源码和测试数据下载(无GUI):https://github.com/ZeusYang/AILearning
参考资料:《人工智能基础教程(第二版》作者:朱福喜