問(wèn)題描述
如圖所示,在X軸上有5個(gè)點(diǎn),分別為x1,x2,x3,x4,x5。這5個(gè)點(diǎn)的實(shí)際間距已知為L(zhǎng),但實(shí)際中由于測(cè)量誤差的存在,每個(gè)點(diǎn)x1,x2,x3,x4,x5會(huì)有一系列如圖中黑色圈內(nèi)所示的測(cè)量點(diǎn)。那么如何在實(shí)際測(cè)量點(diǎn)中取值可以使得相鄰位置的間距最接近L,問(wèn)題可以描述為如下數(shù)學(xué)公式:
F=min∑|xi+1?xi?L|,1≤i≤4
F=min∑|xi+1?xi?L|,1≤i≤4
解決思路
通過(guò)查詢資料發(fā)現(xiàn),我要解決的問(wèn)題和TSP問(wèn)題(旅行商問(wèn)題)很像。TSP問(wèn)題中是預(yù)先給定數(shù)量已知位置固定的點(diǎn),然后求得是旅行商人從任意一個(gè)點(diǎn)出發(fā)遍歷所有的點(diǎn)(中間每個(gè)點(diǎn)只能經(jīng)過(guò)一次)最后回到這個(gè)點(diǎn)時(shí)路徑最短,具體可以參考維基百科旅行商問(wèn)題。
我要解決問(wèn)題的特點(diǎn)是點(diǎn)之間的間距固定,但每個(gè)點(diǎn)的位置上有n個(gè)測(cè)量點(diǎn),我的最終目的是選擇最優(yōu)的測(cè)量點(diǎn)使得路徑距離和4L之間的偏差最小,換言之也是使得路徑的最短(只不過(guò)是與4L做差值之后最短),這就與TSP問(wèn)題不謀而合了。雖然每個(gè)位置上有個(gè)n個(gè)測(cè)量點(diǎn),但每次只從每個(gè)位置上取出一個(gè)測(cè)量點(diǎn),這樣就形成一條線路,然后計(jì)算路徑間距,最后通過(guò)比較即可選擇出最優(yōu)的路徑,這就和TSP問(wèn)題求解的思路是一樣的了。
但是如果遍歷每個(gè)位置上的點(diǎn)來(lái)求所有的路線,這樣當(dāng)測(cè)量點(diǎn)數(shù)n比較大時(shí)計(jì)算量就相對(duì)很大了,所以就想到了用啟發(fā)式搜索算法的方式來(lái)搜索最優(yōu)解,最后使用遺傳算法來(lái)解決這個(gè)問(wèn)題。
遺傳算法
遺傳算法,顧名思義,可以聯(lián)想到自然界種群繁衍、基因遺傳的過(guò)程,實(shí)際上它也是借鑒進(jìn)化生物學(xué)中的一些現(xiàn)象發(fā)展起來(lái)的(交叉,變異,選擇以及遺傳等等)維基百科遺傳算法,是一種通過(guò)模擬生物進(jìn)化過(guò)程搜索最優(yōu)解的啟發(fā)式搜索算法。
遺傳算法的本質(zhì)就是模仿自然界優(yōu)勝劣汰、適者生存的過(guò)程。它往往從實(shí)際問(wèn)題的解集出發(fā)通過(guò)一定的編碼方式形成問(wèn)題域中“基因”、“染色體”和“個(gè)體”的概念,進(jìn)而確定初始種群(由一定數(shù)量的個(gè)體組成),然后根據(jù)問(wèn)題域中的適應(yīng)度函數(shù)(FitnessFunction),通過(guò)一代代的選擇(Selection)、交叉(Crossover)和變異(Mutation)等方式模擬這個(gè)種群的進(jìn)化過(guò)程,最后逐漸進(jìn)化出較好的個(gè)體(也就是解集中近似的最優(yōu)解)。將遺傳算法應(yīng)用到實(shí)際問(wèn)題的流程大致如下:
1.對(duì)實(shí)際問(wèn)題的解集進(jìn)行編碼,使其可以對(duì)應(yīng)生物遺傳過(guò)程中“基因”、“染色體”和“個(gè)體”的概念。比如本題中,解集就是每個(gè)位置上的隨機(jī)選一個(gè)測(cè)量點(diǎn)連起來(lái)的路徑,這樣我可以對(duì)測(cè)量點(diǎn)進(jìn)行編號(hào),使得每個(gè)測(cè)量點(diǎn)就代表了一個(gè)“基因”,然后一條路勁就代表了一條“染色體”,進(jìn)而形成一個(gè)個(gè)體(每個(gè)個(gè)體只有一條“染色體”)。具體如下圖所示:
2.確定問(wèn)題域中的適應(yīng)度函數(shù)(FitnessFunction)。這個(gè)一般實(shí)際問(wèn)題都會(huì)已經(jīng)給出,比如本題中的適應(yīng)度函數(shù)就是前面所述的數(shù)學(xué)公式:
F=min∑|xi+1?xi?L|,1≤i≤4
F=min∑|xi+1?xi?L|,1≤i≤4
3.確定初始種群(population)。這個(gè)可以用random的方式隨機(jī)生成,如果為了比較快的收斂到較優(yōu)解,也可以一開(kāi)始就生成一些表現(xiàn)優(yōu)良的“個(gè)體”。
4.然后根據(jù)適應(yīng)度函數(shù)進(jìn)行選擇(Selection)、交叉(Crossover)和變異(Mutation),通過(guò)一定代數(shù)的遺傳即可選出近似的最優(yōu)解。
以上就是我查閱資料后對(duì)遺傳算法的一個(gè)基本的理解,下面我將具體介紹每個(gè)步驟中使用的方法(包括編碼的方式,個(gè)體的選擇,交叉的方式等)并展示相應(yīng)的代碼。(如果還想對(duì)遺傳算法有更深入的了解,可以看這里知乎如何通俗易懂的理解遺傳算法)
解題過(guò)程
1.編碼方式
對(duì)問(wèn)題域的解集進(jìn)行編碼,獲得相應(yīng)的“基因”、“染色體”等,常用的編碼方式有兩種:
1)實(shí)數(shù)編碼:使用實(shí)數(shù)進(jìn)行編碼(比如0,1,2等等)。
2)二進(jìn)制編碼:這個(gè)編碼方式就是使用二進(jìn)制0、1來(lái)表示問(wèn)題域中解集。
對(duì)于本題中,每個(gè)位置上有n個(gè)測(cè)量點(diǎn),顯然用二進(jìn)制編碼方式不太合適,而如果用實(shí)數(shù)編碼的方式則可以很好的表示每個(gè)位置上的測(cè)量點(diǎn)。因此,我使用實(shí)數(shù)(0~n-1)對(duì)每個(gè)位置上的測(cè)量點(diǎn)進(jìn)行編號(hào),這樣我只要新建一個(gè)二維數(shù)組即可表示每個(gè)位置上的所有測(cè)量點(diǎn)。同時(shí),使用random的方式隨機(jī)生成測(cè)量點(diǎn),即可將測(cè)量點(diǎn)的坐標(biāo)值保存在數(shù)組中對(duì)應(yīng)的位置。其代碼如下:
//pointNum是位置數(shù)(本題中是5),realPointNum是每個(gè)位置上實(shí)際的測(cè)量點(diǎn)數(shù)
classGARandom{
privateintpointNum,realPointNum;
publicGARandom(intpointNum,intrealPointNum){
this.pointNum=pointNum;
this.realPointNum=realPointNum;
}
publicdouble[][]randomInitPopulation(){
double[][]x=newdouble[pointNum][realPointNum];
for(inti=0;i<x.length;i++){
for(intj=0;j<x[0].length;j++){
if(i==j){
x[i][j]=i*10;//將一組收斂值放入其中,用以后面測(cè)試算法的性能
}else{
x[i][j]=i*10+(i+1)*Math.random();
}
}
}
returnx;
}
}
2.確定適應(yīng)度函數(shù)
本題的適應(yīng)度函數(shù)就是要使得相鄰位置的間距最接近L,即前面所描述的數(shù)學(xué)公式:
F=min∑|xi+1?xi?L|,1≤i≤4
F=min∑|xi+1?xi?L|,1≤i≤4
題目中有5個(gè)位置,也就是要選出使每?jī)蓚€(gè)相鄰的位置最接近固定間距L的測(cè)量點(diǎn)。所以可以像TSP問(wèn)題一樣事先計(jì)算出相鄰位置測(cè)量點(diǎn)之間的距離并將其保存在數(shù)組中,然后在種群進(jìn)化的過(guò)程中,根據(jù)種群的“染色體”(也就是路徑)計(jì)算出總的偏差,以此來(lái)判斷其適應(yīng)度的好壞(這里是距離越小適應(yīng)度越好)。計(jì)算相鄰位置測(cè)量點(diǎn)距離的代碼如下:
//求得每?jī)蓚€(gè)位置,所有點(diǎn)之間的距離
distance=newdouble[pointNum-1][realPointNum][realPointNum];
for(inti=0;i<pointNum-1;i++){
for(intj=0;j<realPointNum;j++){
for(intk=0;k<realPointNum;k++){
distance[i][j][k]=Math.abs(x[i+1][k]-x[i][j]-L);
}
}
}
評(píng)價(jià)個(gè)體適應(yīng)度的代碼如下:
//根據(jù)先驗(yàn)條件求得個(gè)體適應(yīng)度,并根據(jù)適應(yīng)度求得單個(gè)個(gè)體的概率以及個(gè)體的累積概率
//以便選擇階段使用
privatevoidevaluate(int[][]chromosome){
intk=0;
doublelen=0;
doublesumFitness=0;
double[]tempFitness=newdouble[scale];
//根據(jù)距離數(shù)組求得每條路徑的適應(yīng)度,也就是和固定距離L的偏差的和
while(k<chromosome.length){
for(inti=0;i<chromosome[k].length-1;i++){
len+=distance[i][chromosome[k][i]][chromosome[k][i+1]];
}
fitness[k]=len;
len=0;
k++;
}
//求總的適應(yīng)度
for(inti=0;i<scale;i++){
tempFitness[i]=10.0/(fitness[i]+1);//計(jì)算適應(yīng)度,這里距離越小適應(yīng)度越大,因此采用倒數(shù)的方式表示
sumFitness+=tempFitness[i];
}
//根據(jù)適應(yīng)度,轉(zhuǎn)化成相應(yīng)的單個(gè)個(gè)體概率和個(gè)體的累積概率,用于后面的輪盤(pán)賭選擇策略
doubletempP=0;
for(inti=0;i<scale;i++){
ps[i]=tempFitness[i]/sumFitness;//單個(gè)個(gè)體概率
tempP+=ps[i];
pc[i]=tempP;//個(gè)體累積概率
}
}
3.確定初始種群
這里我采用了隨機(jī)生成的方式,但是為了使初始種群中能覆蓋所有經(jīng)過(guò)實(shí)數(shù)編號(hào)的測(cè)量點(diǎn)(0~n-1),所以我讓前n個(gè)體的“染色體”如下所示:
這種方式使得初始種群的前n個(gè)個(gè)體的“染色體”排列是全0,全1直到全n-1,這樣盡可能將所有的測(cè)量點(diǎn)都覆蓋進(jìn)去,避免隨機(jī)生成的時(shí)候漏掉一些測(cè)量點(diǎn)。其代碼如下:
//生成父代種群的“染色體”,也就是隨機(jī)選取每個(gè)位置上的點(diǎn)組成一個(gè)網(wǎng)絡(luò)
//scale是種群規(guī)模,pointNum是位置數(shù)(x1-x5)
oldPopulation=newint[scale][pointNum];
for(inti=0;i<scale;i++){
for(intj=0;j<pointNum;j++){
if(i<realPointNum){
oldPopulation[i][j]=i;
}else{
oldPopulation[i][j]=rand.nextInt(realPointNum);
}
}
}
4.選擇(Selection)
確定初始種群后,就根據(jù)適應(yīng)度函數(shù)計(jì)算出初代最優(yōu)的個(gè)體,并將其直接遺傳給子代。這里這么做的原因是,保存表現(xiàn)最優(yōu)良的個(gè)體,讓其余個(gè)體進(jìn)行交叉或變異(當(dāng)然還有其他的方式,這個(gè)由你自己決定),這種方式也叫做精英選擇。然后通過(guò)輪盤(pán)賭選擇方式,隨機(jī)選擇個(gè)體放到子代中去。這個(gè)輪盤(pán)賭選擇方式是根據(jù)每個(gè)個(gè)體適應(yīng)度占總適應(yīng)度的概率進(jìn)行選擇的,想詳細(xì)了解的可以看這篇博文輪盤(pán)賭策略。選擇階段的代碼如下:
//精英選擇(選擇上一代中fitness最好的個(gè)體,然后直接保存到下一代中)
privatevoidselectMaxFitness(){
intmaxId=0;
doubletempFitness=fitness[0];
//
for(inti=1;i<scale;i++){
if(tempFitness>fitness[i]){
tempFitness=fitness[i];
maxId=i;
}
}
if(bestLength>tempFitness){
bestLength=tempFitness;
bestGen=t;
System.arraycopy(oldPopulation[maxId],0,bestChoice,0,pointNum);
}
copyPopulation(0,maxId);
}
//輪盤(pán)賭選擇策略
privatevoidselect(){
intj,selectId;
doubler;
selectMaxFitness();//精英選擇,保留上一代fitness最好的個(gè)體
for(inti=1;i<scale;i++){
r=rand.nextDouble();
for(j=0;j<scale;j++){
if(r<=pc[j]){
break;
}
}
if(j<scale){
selectId=j;
copyPopulation(i,selectId);
}
}
}
5.交叉(Crossover)
選擇完之后,就要對(duì)這些個(gè)體進(jìn)行“染色體”交叉,用以產(chǎn)生子代。交叉的方式有很多,我這里選擇了最簡(jiǎn)單的單點(diǎn)交叉,即通過(guò)random.nextDouble()隨機(jī)生成一個(gè)數(shù),當(dāng)它小于交叉概率時(shí),即表明可以進(jìn)行“染色體”的交叉,然后隨機(jī)生成一個(gè)索引值,然后將相鄰的“染色體”位于索引值后的部分進(jìn)行交叉。其代碼如下:
//單點(diǎn)交叉
privatevoidcrossover(){
for(intk=1;k<scale/2;k+=2){
doublepCrossoverTemp=rand.nextDouble();
//小于交叉概率時(shí)進(jìn)行“染色體”交叉,將交叉索引(包括交叉索引處的元素)后的元素進(jìn)行互換
if(pCrossoverTemp<=pCrossover){
inttempCrossover;
intindexCrossover=1+rand.nextInt(pointNum-1);//排除索引值為0的情況,整體交換沒(méi)有意義
for(inti=indexCrossover;i<pointNum;i++){
tempCrossover=newPopulation[k][i];
newPopulation[k][i]=newPopulation[k+1][i];
newPopulation[k+1][i]=tempCrossover;
}
}
}
}
當(dāng)然這是最最簡(jiǎn)單的交叉算子,如果想使用表現(xiàn)更好的算子,可以看這篇博文遺傳算法中幾種交叉算子。
6.變異(Mutation)
變異這個(gè)部分,我沒(méi)有查閱很多資料,直接用了最簡(jiǎn)單的單點(diǎn)變異。其代碼如下:
privatevoidmutation(){
for(inti=1;i<scale;i++){
doublepMutationTemp=rand.nextDouble();
if(pMutationTemp<pMutation){
//隨機(jī)選擇變異位置
intmutationIndex=rand.nextInt(pointNum);
//將變異位置的值保存下來(lái)
inttemp=newPopulation[i][mutationIndex];
//獲得變異值
intmutationTemp=rand.nextInt(realPointNum);
//確保變異值和之前的值不一樣
while(mutationTemp==temp){
mutationTemp=rand.nextInt(realPointNum);
}
newPopulation[i][mutationIndex]=mutationTemp;
}
}
}
7.重復(fù)操作
從確定初代種群開(kāi)始到經(jīng)過(guò)第一次的選擇、交叉和變異這就產(chǎn)生了第一個(gè)子代。然后重復(fù)4、5、6這三個(gè)步驟,整個(gè)遺傳算法就有如自然界進(jìn)化一般,在適應(yīng)度函數(shù)的制約下,自動(dòng)朝著最優(yōu)解的方向“進(jìn)化”而去,當(dāng)然遺傳算法一般得到只是最優(yōu)解的近似解。
代碼測(cè)試
測(cè)試輸入
初始種群的大小設(shè)定為30,即初始種群包含30個(gè)個(gè)體;每個(gè)位置實(shí)際測(cè)量點(diǎn)的數(shù)量設(shè)定為10個(gè);“進(jìn)化”的代數(shù)設(shè)定為2000,即算法要?dú)v經(jīng)2000代的“遺傳進(jìn)化”;相鄰位置的固定間距設(shè)定為10;交叉概率定為0.8;變異概率定為0.1。
測(cè)試結(jié)果
由上圖可以看出經(jīng)過(guò)2000次的選擇、交叉和變異,在固定間距是10的情況下,算法在第355代找到了最佳的路徑01234,也就是我事先輸入到數(shù)組的0,10,20,30,40這組數(shù)據(jù)。