引言
上一章講解了遺傳算法的基本思想與工作流程,構(gòu)建了物種類Species及評(píng)價(jià)物種優(yōu)劣的適應(yīng)度函數(shù)。本章敘述如何利用求得的物種適應(yīng)度去進(jìn)行優(yōu)勝劣汰的“選擇算子”、物種間繁衍配對(duì)的“交叉算子”以及單個(gè)物種基因突變的“變異算子”。
選擇算子
1.選擇概率
物種si(i=1,2,…,n)的適應(yīng)度f(wàn)(si)已然得到,接下來(lái)就要利用f(si),求得它在整個(gè)種群中被選擇(即遺傳到下一代)的概率。這個(gè)概率表示為:
產(chǎn)生了概率以后,我們便需要進(jìn)行選擇。
2.輪盤賭選擇法
采用“輪盤賭選擇法”,形象點(diǎn)說,就像我們經(jīng)常見到的抽獎(jiǎng)轉(zhuǎn)轉(zhuǎn)盤一樣:
即每次篩選就相當(dāng)于轉(zhuǎn)動(dòng)輪盤,概率大的面積就越大,自然就更容易被選上。那么“轉(zhuǎn)”多少次呢?這里就涉及種群容量的約定了,我們?nèi)绻x得過小,那么物種的多樣性不夠,很難有機(jī)會(huì)產(chǎn)生更優(yōu)秀的物種(就像如果地球上其他生物都滅絕了,只剩人類,那么物種改變的機(jī)會(huì),即路線更短的概率相對(duì)就越小了),而如果過大,那么算法的效率會(huì)降低,隨機(jī)性更大,最后很不容易收斂。而根據(jù)一些文章和經(jīng)驗(yàn),每一輪我們就維持上一輪的種群容量大小即可,保持種群總量不變,即由初始種群的大小決定。那初始種群大小又選多大呢?這個(gè)參數(shù)就需要根據(jù)具體問題的規(guī)模來(lái)進(jìn)行調(diào)整了,如果城市數(shù)很少,可以適當(dāng)小一些,如果很多,就調(diào)大點(diǎn)。
3.選擇過程
下面舉一個(gè)實(shí)際例子來(lái)說明具體怎么選擇。假設(shè)我們初始化了這樣一個(gè)種群,也計(jì)算了他們的適應(yīng)度、選擇概率,得到如下的表:
從上表很容易看出:物種2因?yàn)槁肪€較短所以適應(yīng)度高,進(jìn)而經(jīng)過4次輪賭直接被選中了2次,而較次的物種1被選中了1次,物種3雖然適應(yīng)度比物種4高,但由于算法的隨機(jī)性并沒有被選上,而是物種4“僥幸”被選上了。所以新的種群應(yīng)該是這樣的:一個(gè)物種1、兩個(gè)物種2和一個(gè)物種4,即種群基因?yàn)椋?25367498、325974681、325974681和974613528。
4.精英保留策略
這里我們不難發(fā)現(xiàn)一個(gè)問題:倘若物種2的選中概率沒有51.54%那么高,而是稍低一些(保證仍是種群中適應(yīng)度最高的物種),那么這時(shí)它參與輪賭被選中的次數(shù)就難說有2次了,而很可能多多地讓那些資質(zhì)現(xiàn)在并非很好的物種1、3、4遺傳到了下一代。但物種2仍是適應(yīng)度最高的精英物種啊!讓它懷才不遇,最后落得個(gè)滄海遺珠之憾,的確有失公平。
所以這時(shí),我們加入一個(gè)“精英保留策略”,即并非所有物種均參與賭輪,而是在輪賭之前,先選出適應(yīng)度最高的那個(gè)物種,復(fù)制若干個(gè)后提前進(jìn)入下一代,接著再讓剩余的物種參與賭輪進(jìn)入下一代,最終兩部分合成一個(gè)新種群。這樣避免了因?yàn)楦怕试颍沟脙?yōu)秀物種滄海遺珠的情況發(fā)生,但這樣做也容易陷入局部最優(yōu),所以多少個(gè)精英這個(gè)參數(shù)就需要不斷地調(diào)整了,據(jù)一些研究經(jīng)驗(yàn)來(lái)看,一般復(fù)制1/4效果是比較好的。
下面是整個(gè)選擇算子的實(shí)現(xiàn)代碼:
//選擇優(yōu)秀物種(輪盤賭)
void select(List<SpeciesNode> speciesList)
{
//計(jì)算選擇概率
calRate(speciesList);
//找出最大適應(yīng)度物種
float talentFitness=Float.MAX_VALUE;
SpeciesNode talentSpecies=null;
for(SpeciesNode species : speciesList)
{
if(species.fitness < talentFitness)
{
talentFitness = species.fitness;
talentSpecies = species;
}
}
//將最大適應(yīng)度物種復(fù)制talentNum個(gè)
List<SpeciesNode> newSpeciesList=new ArrayList<SpeciesNode>();
int talentNum = (int)(speciesList.size() * tp); //tp:復(fù)制比重
for(int i=1;i<=talentNum;i++)
{
//復(fù)制物種至新表
SpeciesNode newSpecies=talentSpecies.clone();
newSpeciesList.add(newSpecies);
}
//輪盤賭speciesList.speciesNum-talentNum次
int roundNum=speciesList.size()-talentNum;
for(int i=1;i<=roundNum;i++)
{
//產(chǎn)生0-1的概率
float rate=(float)Math.random();
for(SpeciesNode species : speciesList)
{
if(species == talentSpecies || rate > species.rate) //精英物種或未選中,跳過
rate=rate-species.rate;
else //該物種在本次輪賭中選中
{
SpeciesNode newSpecies=species.clone();
newSpeciesList.add(newSpecies);
break;
}
}
}
speciesList = newSpeciesList;
}
交叉算子
交叉是對(duì)種群內(nèi)兩物種的基因序列進(jìn)行裁剪組合的操作,一般以一定概率執(zhí)行,而不是每次都執(zhí)行。物種的配對(duì)選取最好隨機(jī),而不要1和2配對(duì),3和4配對(duì)(因?yàn)樵谑褂镁⒈A舨呗詴r(shí)很可能是連續(xù)追加進(jìn)種群的,這樣相當(dāng)于近親繁殖,很難擦出火花即產(chǎn)生路線長(zhǎng)度比雙親都短的后代基因)。那么雙親的基因序列之間具體怎么交叉呢?
由于物種基因的編碼形式是以“城市編號(hào)”為元素的,在實(shí)現(xiàn)交叉操作時(shí)首先任選一個(gè)位置作為起點(diǎn),交換兩個(gè)物種的后半段基因。但需注意的是,交叉后的后半段基因可能與物種的前半段基因重復(fù),故而還需進(jìn)行基因沖突處理,即把物種1所有重復(fù)的基因與物種2所有重復(fù)的基因?qū)?yīng)交換。交叉操作具體如下圖所示:
具體實(shí)現(xiàn)代碼如下:
//交叉操作
void crossover(List<SpeciesNode> speciesList)
{
for(int n=0;n<speciesList.size();n+=2) //兩兩配對(duì)
{
if(n+1 >= speciesList.size()) //已無(wú)可配對(duì)的母物種(種群容量為奇數(shù))
break; //結(jié)束
SpeciesNode fatherSpecies = speciesList.get(n); //父物種
SpeciesNode motherSpecies = speciesList.get(n+1); //母物種
//交叉概率 pcl < rate < pch
float rate=(float)Math.random();
if(rate > Constant.pcl && rate < Constant.pch)
{
int crossPosition=rand.nextInt(Constant.CITY_NUM); //隨機(jī)生成交叉點(diǎn)
List<Integer> fatherDuplicateGenesList = new ArrayList<Integer>(); // 存儲(chǔ)父物種前半段所有重復(fù)基因的位置
List<Integer> motherDuplicateGenesList = new ArrayList<Integer>(); // 存儲(chǔ)母物種前半段所有重復(fù)基因的位置
//后半段基因挨個(gè)位置進(jìn)行互換
for(int i=crossPosition;i<Constant.CITY_NUM;i++)
{
//基因互換
int gene;
gene=fatherSpecies.genes[i];
fatherSpecies.genes[i]=motherSpecies.genes[i];
motherSpecies.genes[i]=gene;
//檢測(cè)fatherSpecies.genes[i]是否與父物種前半段某位置基因重復(fù),若是則記錄
for(int j=0;j<crossPosition;j++)
if(fatherSpecies.genes[i] == fatherSpecies.genes[j])
fatherDuplicateGenesList.add(j);
//母物種同理
for(int j=0;j<crossPosition;j++)
if(motherSpecies.genes[i] == motherSpecies.genes[j])
motherDuplicateGenesList.add(j);
}
//去重
for(int k=0;k<fatherDuplicateGenesList.size();k++)
{
//父、母物種前半段重復(fù)的基因?qū)?yīng)交換
int fatherGenePosition = fatherDuplicateGenesList.get(k);
int motherGenePosition = motherDuplicateGenesList.get(k);
int gene;
gene=fatherSpecies.genes[fatherGenePosition];
fatherSpecies.genes[fatherGenePosition]=motherSpecies.genes[motherGenePosition];
motherSpecies.genes[i]=gene;
}
}
}
}
變異算子
“變異”是跳出局部最優(yōu)解的一個(gè)重要法寶,是對(duì)基因序列進(jìn)行若干變換的一種操作,一般按非常小的概率執(zhí)行。本文采用的是一種學(xué)界普遍認(rèn)為效果較好的一種變異方式,即隨機(jī)選取基因序列的兩個(gè)位置k和m,逆轉(zhuǎn)其k~m間的城市編號(hào),即若現(xiàn)有物種:
隨機(jī)產(chǎn)生1和n之間的兩相異整數(shù)k和m,若k<m,執(zhí)行逆轉(zhuǎn)變換,得到新的基因序列:
下面是代碼:
//變異操作
void mutate(List<SpeciesNode> speciesList)
{
//每一物種均有變異的機(jī)會(huì),以概率pm進(jìn)行
for(SpeciesNode species : speciesList)
{
float rate=(float)Math.random();
if(rate < Constant.pm)
{
//尋找逆轉(zhuǎn)的左右端點(diǎn)
Random rand=new Random();
int left=rand.nextInt(Constant.CITY_NUM);
int right=rand.nextInt(Constant.CITY_NUM);
if(left > right)
{
int tmp;
tmp=left;
left=right;
right=tmp;
}
//逆轉(zhuǎn)left-right下標(biāo)元素
while(left < right)
{
int tmp;
tmp=species.genes[left];
species.genes[left]=species.genes[right];
species.genes[right]=tmp;
left++;
right--;
}
}
}
}
結(jié)語(yǔ)
啊~累死了,遺傳算法求解TSP的基本思想差不多就是這些啦。下一章將給出GeneticAlgorithm類的一個(gè)總控調(diào)用流程、遺傳算法的一些常量參數(shù)定義及算法的實(shí)際運(yùn)行效果。