种群类:Population
属性
种群类首先要储存的便是上一篇中提到的原始基因;其次为了应用精英保存策略、择伏分配等,需要记录最优个体的相关信息;同时在计算交叉变异概率的时候,需要用到求平均适应值的方法,所以为了简化计算,储存每个个体的适应值是很有必要的。
/// <summary>
/// 个体列表
/// </summary>
public List<Individual> ind;
/// <summary>
/// 原始基因
/// </summary>
private List<Store> gene_pool;
/// <summary>
/// 最优个体
/// </summary>
public Individual Fmax;
/// <summary>
/// 最优个体适应值
/// </summary>
public int fmax;
/// <summary>
/// 最优个体对应索引
/// </summary>
public int fmax_index;
/// <summary>
/// 每个个体的适应值
/// </summary>
public int[] fit_count;
方法
更新最优个体,并更新适应度
在精英保存策略和择伏分配策略中,都需要找出种群最优个体,所以这里要做的就是更新最优个体的相关信息,顺便更新个体适应值。
/// <summary>
/// 更新种群最优个体,并更新适应度
/// </summary>
public void update_max()
{
int fit;
for (int i = 0; i < ind.Count; i++)
{
fit = ind[i].fit_count();
fit_count[i] = fit;
if (fit > fmax)
{
fmax_index = i;
fmax = fit;
Fmax = ind[i];
}
}
}
交叉概率
在我主要参考(白嫖)的那篇论文里,交叉概率是这样定义的:
其中Pc1=0.9, Pc2=0.6, f'为参与交叉的两个个体中较大的适应值,favg为种群的平均适应值,fmax为种群的最大适应值。
为了求平均适应值,必须写一个小方法来计算,除此之外就是套公式了。
/// <summary>
/// 计算种群平均适应度
/// </summary>
/// <returns>平均适应值</returns>
public float avg()
{
float ans = 0;
foreach (var i in ind)
{
ans += i.fit_count();
}
return ans / ind.Count;
}
/// <summary>
/// 计算交叉概率
/// </summary>
/// <param name="ind1">个体一</param>
/// <param name="ind2">个体二</param>
/// <param name="Pc1">参数一</param>
/// <param name="Pc2">参数二</param>
/// <returns>交叉概率</returns>
public double cros_probability(Individual ind1, Individual ind2, double Pc1 = 0.9, double Pc2 = 0.6)
{
int fit1 = ind1.fit_count(), fit2 = ind2.fit_count(), f;
float fit_avg = avg();
update_max();
if (fit1 > fit2)
{
f = fit1;
}
else
{
f = fit2;
}
double ans = Pc1;
if (f >= fit_avg)
{
ans -= (Pc1 - Pc2) * (f - fit_avg) / (fmax - fit_avg);
}
return ans;
}
变异概率
变异概率的定义如下:
其中Pm1=0.1, Pm2=0.01, f为参与变异个体的适应值。
/// <summary>
/// 变异概率
/// </summary>
/// <param name="ind1">个体</param>
/// <param name="Pm1">参数一</param>
/// <param name="Pm2">参数二</param>
/// <returns>变异概率</returns>
public double mut_probability(Individual ind1, double Pm1 = 0.1, double Pm2 = 0.01)
{
int f = ind1.fit_count();
float fit_avg = avg();
update_max();
double ans = Pm1;
if (f >= fit_avg)
{
ans -= (Pm1 - Pm2) * (fmax - f) / (fmax - fit_avg);
}
return ans;
}
生成随机数
在交叉算子和变异算子中,要大量地用到生成一个随机整数;在轮盘赌算法以及遗传算法中,也要生成一个随机实数。
/// <summary>
/// 生成随机整数
/// </summary>
/// <param name="a">下限</param>
/// <param name="b">上限</param>
/// <returns>随机整数</returns>
public static int rand_int(int a, int b)
{
byte[] buffer = Guid.NewGuid().ToByteArray();
int iSeed = BitConverter.ToInt32(buffer, 0);
Random ran = new Random(iSeed);
return ran.Next(a, b);
}
/// <summary>
/// 生成(0,1)上的随机实数
/// </summary>
/// <returns>随机实数</returns>
public static double rand_double()
{
byte[] buffer = Guid.NewGuid().ToByteArray();
int iSeed = BitConverter.ToInt32(buffer, 0);
Random ran = new Random(iSeed);
return ran.NextDouble();
}
找到种群最劣个体
择伏分配策略中需要找出种群中的最劣个体。
/// <summary>
/// 找出最差个体对应索引
/// </summary>
/// <returns>最差个体对应索引</returns>
public int find_min_index()
{
int index = 0, fmin = int.MaxValue;
for (int i = 0; i < ind.Count; i++)
{
if (fit_count[i] < fmin)
{
index = i;
fmin = fit_count[i];
}
}
return index;
}
解码器
前面提到,解码器是将基因编码还原成基因的方法,由于是基于本种群的原始基因进行随机排序操作和随机旋转操作的,不需要改变原始基因本身,所以需要调用Store类的clone进行深复制,以保证原始基因的“纯洁”。
/// <summary>
/// 解码器,由两个基因片段编码生成个体的基因
/// </summary>
/// <param name="gene_order">基因片段编码一</param>
/// <param name="gene_rotate">基因片段编码二</param>
/// <returns>基因</returns>
public List<Store> decode(int[] gene_order, bool[] gene_rotate)
{
List<Store> ans = new List<Store>();
gene_pool.ForEach(i => ans.Add(i.Clone()));
for (int i = 0; i < gene_rotate.Length; i++)
{
if (gene_rotate[i])
{
ans[i].rotate();
}
}
return ans.OrderBy(e =>
{
var index = 0;
index = Array.IndexOf(gene_order, e.id);
if (index != -1)
{
return index;
}
return int.MaxValue;
}).ToList();
}
轮盘赌
轮盘赌算法是这样实现的:将每个个体的适应值进行归一化处理(也就是除以种群适应值总和),然后生成一个随机数,看看这个随机数落在哪个区间就选哪个个体。举例说明,比如有四个个体,适应值分别为1,2,3,4,归一化之后为0.1,0.2,0.3,0.4,我生成一个(0,1)上的一个随机实数r=0.61826,落在区间(0.1+0.2+0.3,0.1+0.2+0.3+0.4)上,所以选择了第四个个体。
可以很直观地看出,适应值越大的个体,所占的扇形面积越大,也就越有可能被选中,这正是轮盘赌的目的——优胜劣汰,这有助于提高遗传算法的收敛速度,但又由其随机性,使得算法不容易陷入局部最优解。这便是遗传算法独特魅力的第一处体现。
还有一个要注意的点,为了保证最优个体的“纯洁”,尽量不破坏最优个体的结构,最优个体不参与轮盘赌。
/// <summary>
/// 轮盘赌算法,随机选择个体
/// </summary>
/// <returns>随机选择的个体</returns>
public int roulette()
{
Dictionary<int, double> dict = new Dictionary<int, double>();
for (int i = 0; i < ind.Count; i++)
{
if (i != fmax_index)
{
dict[i] = (double) fit_count[i] / (fit_count.Sum()-fmax);
}
}
double r = rand_double(), sum = 0.0;
int index = 0;
foreach (var k in dict)
{
sum += k.Value;
if (sum > r)
{
index = k.Key;
break;
}
}
return index;
}