AI中的幾種搜索算法---基因算法

AI中的幾種搜索算法---基因算法

引言

進化計算(Evolutionary Computation)這個涵蓋的範圍比較廣,其中包括基因算法(Genetic Algorithm)、進化式策略(Evolutionary Strategy)、基因程序(Genetic Programming)等等。這篇是進化計算的開篇,我會從基因算法入手,進而介紹進化計算中的一些基本思想。

一、基因算法的基本介紹

1.核心思想

基因算法與A*、Tabu、BFS等一些啓發式算法,最大的不同便是:從針對個體,轉變到針對由個體組成的“羣體”(Population)。根據適應值(Fitness)來決定個體的優秀程度。

每一次操作,從羣體中挑選兩個優秀的個體,取出這兩個個體的基因,進行拆分重組,從而得到新方案,放入新一代的羣體中去。

其中利用到了生物學中的重組(Recombination)、選擇(Selection)和突變(Mutation)。所以在學習這一算法的時候,不妨和生物學中一些概念進行類比,這樣能夠更好地理解基因算法的工作原理。

基因算法,在挑選的過程中,隨機地挑選了兩個優秀個體;在對兩個個體的基因重組的時候,也引用了突變這一個不確定因素,整個過程貌似都籠罩在“隨機”陰影下。確實在某種意義上,基因算法是一種隨機搜索的算法。但必須指出的是基因算法在搜索能力上大大優於普通的隨機搜索。

2.基因拆分重組

接下來介紹一些常用的基因重組算法。

一般來說,包括這三個操作:交叉(Crossover)、突變Mutation和倒置(Inversion)。

接下來我解釋這三個操作:

1.     交叉

ParentA(1111111111111111)    ParentB(0000000000000000)

                    Crossover

ChildA(1111111100000000)     ChildB(0000000011111111)

就像上面,將ParentA切成兩段,同時也將ParentB的基因鏈也切成兩段。

先將ParentB的一半基因鏈接在ParentA的一半基因鏈後面,從而產生ChildA;同理可得ChildB,只是交換了ParentA和ParentB的順序。

2.     突變

ParentA(1111111111111111)    ParentB(0000000000000000)

                   Mutation

ChildA(1111111101111111)     ChildB(0000000000100000)

這裏ChildA從ParentA中得到了全部的基因,但是可以發現,其中ChildA中有一位是0,而0顯然不是ParentA的基因,所以這便是突變。

同理可得ChildB。

3.     倒置

ParentA(1111111111111111)    ParentB(0000000000000000)

                   Inversion

ChildA(1111111100011111)     ChildB(0000000111100000)

這個操作的結果有點類似突變。其實這個的操作過程是這樣的:去ParentA的一個片段,對這個片段中每一個基因進行取反,從而得到了ChildA。

3.基本流程

基因算法的流程也是簡單易懂的,接下來就大致描述一下這個流程:

1.     首先便是創建羣體,不斷隨機創建個體。

2.     從當前羣體中挑選兩個優秀個體,對其基因進行重組,生成兩個新個體,這兩個新個體便組成了新一代的羣體。

3.     利用步驟2,創建一個與當前羣體容量相當的新一代羣體,便將新一代羣體設定爲當前羣體。

4.     判斷是否得到了我們所需要的個體,如果得到就停止算法。

5.     判斷羣體是否不再符合要求(失去了多樣性),如果不符合就停止算法;如果符合就繼續步驟3。

僞代碼:

//
Population[2][NUM];
//隨機獲得NUM個個體,並放入羣體Population[0]中去
Rand(Population[0]);
curGeneration = 0;
While(TRUE)
{
newGeneration = curGeneration == 1 ? 0 : 1;
   For(i = 0 ; i < NUM ; ++i)
{
   //select : 從當前羣體Population中挑選優秀的個體作爲此次操作的父輩
   ParantA =  Select(Population[curGeneration]);
   ParentB =  Select(Population[curGeneration]);
       //Recombination: 重組ParentA和ParentB的基因鏈,獲得新個體Child
   Child  =  Recombination(ParentA,ParentB);    
//計算新一代個體的適應值
   CalculateFitness(Child);
   //如果獲得Child並不比父輩的優秀,重新重組
    If(Child.Fitness< ParentA.Fitness || Child.Fitness < ParentB.Fitness)
    {
      --i;
      Continue;
}
//將新個體加入新一代的羣體中
    Population[newGeneration][i]= Child;
}
//當羣體之間的個體差異很小的時候,考慮退出算法
If(avgfitness / maxfitness > 0.99999)  
 Break;
curGeneration = newGeneration;
}

二、TSP問題

1.TSP問題介紹

這裏舉TSP問題,TSP問題在另一篇文章《AI中的幾種搜索算法---SA搜索算法》有提到過,那個時候主要用的是SA算法對這個問題進行了求解。

這裏我們會用基因算法,再次求解這個問題。雖然已經介紹過TSP問題,這裏爲了閱讀的方便,我就直接拷貝了《AI中的幾種搜索算法---SA搜索算法》的部分內容。

TSP問題即旅行商問題:一個旅行商A被分配到一個任務,公司要求A去幾個城市進行公司業務拓展,所以A就會拿出地圖制定一個合理的路線。其中路線的要求便是消耗最小,並且能夠從某一個城市出發,並且最後返回該城市時,已經訪問過了所有城市。

2.TSP問題分析

這裏我們可以計算出整個路線的路程。而這個路程和我們之前提到過的Fitness成反比,路程越長,表示這個路線越差。


對於這個公式,我稍作解釋:Fitness就是我們一直提到的適應能力(適應值),Length(solution)計算路線solution的路程長度。

3. 雜交算子

這裏我們介紹一個新的基因重組算法。較之於之前介紹的“交叉”、“突變”和“倒置”,這個算法會複雜一點。

雜交算子,算法來於《構建“基因庫”求解TSP問題的混合遺傳算法》

接下來開始介紹:

1.首先從當前羣體中,隨機選取兩個優秀的個體作爲父輩:ParentA和ParentB。

2.隨機選取兩個基因位置(即處於基因鏈中第幾個位置):PosA1和PosA2。

3.找到ParentA基因鏈,PosA1和PosA2位置處的基因:G1和G2。

4.找到ParentB基因鏈中基因G1和G2所處的位置:PosB1和PosB2。

5.將ParentB基因鏈中,與處於ParentA基因鏈PosA1和PosA2之間相同的基因去掉。

6.然後如果PosB1 < PosB2,將處於ParentA基因鏈PosA1和PosA2之間的基因片段,在ParentB基因鏈的PosB1位置開始順序插入;

如果PosB1 >= PosB2,將將處於ParentA基因鏈PosA1和PosA2之間的基因片段,在ParentB基因鏈的PosB2位置開始逆序插入。

7.經過步驟6之後,得到新個體Child,如果Child並沒有比父輩優秀,則再回到步驟1,繼續相同操作;如果新個體優於父輩,則將新個體放入新一代羣體中,再繼續產生下一個個體。


下面舉一個具體的例子

隨機取PosA1 = 3 , PosA2 = 6 , 得到基因G1 = 3 , G2 = 6

在ParentB基因鏈的位置PosB1 = 8 , PosB2 = 5

ParentA :  1    2    3     4    5    6    7    8    9

ParentB:   2    4   7     8    6    5   1     3    9


將ParentB基因鏈,去除基因 3,4,5,6

得到:

ParentB:  2    x    7    8    x    x   1     x    9

 

因爲PosB1 > PosB2,所以在PosB2處開始逆序插入(3,4,5,6)

得到:

Child:    2    7    8     6    5    4    3     1   9

4.   代碼

如果想要詳細瞭解可以去基因算法解決TSP問題處下載

下面是整個基因算法的流程代碼:

//
int tsp_ga(City * cities,intnCities,int ** path)
{
    srand(time(NULL));//
    *path = new int[nCities];
    floatsumCurFitness ;
//初始化羣體,隨機得到一羣個體
    InitPopulation(cities,nCities,sumCurFitness);
    intcurGeneration = 0,generation = 0;
    int iBest =0;
    for(;1 ;++generation)//循環
    {
         int newGeneration = curGeneration == 0 ? 1 : 0;
         float sumFitness = 0.0 , maxFitness = 0.0;
          //開始獲得新個體
         for (int i = 0 ; i< NUMPOPULATION ; ++i)
         {
              //選取優秀個體作爲父輩
             int parenta =SelectParent(curGeneration,sumCurFitness);
             int parentb =SelectParent(curGeneration,sumCurFitness);
            //開始重組父輩的基因鏈,獲得新個體
             //這裏使用上面介紹的雜交算法
 Recombination(population[curGeneration][parenta],population[curGeneration][parentb],
                 population[newGeneration][i],nCities);
              //計算新個體的fitness
             CaculateFitness(cities,nCities,population[newGeneration][i]);
             float fitness= population[newGeneration][i].fitness;
              //如果新個體並沒有比父輩優秀,重新產生新個體
             if(fitness< population[curGeneration][parenta].fitness || fitness <population[curGeneration][parentb].fitness)
             {
                 --i;
                 continue;
             }
             if(fitness> maxFitness)
             {
                 maxFitness = fitness;
                 iBest = i;
             }
             sumFitness += fitness;
         }
         sumCurFitness = sumFitness;
         curGeneration = newGeneration;
         float result = sumFitness / (maxFitness*NUMPOPULATION );
         if( result >  0.99999)//如果羣體中個體差異很小,停止算法
             break;
    }
    for (int i = 0 ; i < nCities ; ++i)
    {
        (*path)[i] =population[curGeneration][iBest].path[i];
    }
   
    //清理資源
    for (int i  = 0;  i < NUMPOPULATION ; ++i)
    {
        delete[]population[0][i].path;
        delete[]population[1][i].path;
    }
    returngeneration;
}

//下面就是基因鏈重組算法的實現代碼

void Recombination(constIndividual & parenta,const Individual &parentb,Individual & child,int n)
{
    int posa1 =rand()%(n-1);
    int posa2 =rand()%n;
    while(posa2<= posa1)
        posa2 = rand()%n;
    //
    int lseg =posa2 - posa1 + 1;
    int * genseg= new int[lseg];
    for(int i = posa1 ; i <= posa2 ; ++i)
        genseg[i-posa1] = parenta.path[i];
    ////
    int posb1 =-1,posb2 = -1;
    int iChild =0 , iStartInsert = 0;
    for(int i = 0 ; i < n ; ++i)
    {
        int gb =parentb.path[i];
        int j =0;
        for(; j< lseg; ++j)
        {
            intg = genseg[j];
            if(gb== g)
            {
                if(0== j)
                {
                    posb1 = i;
                    iStartInsert = iChild;
                    iChild += lseg;
                }
                elseif(j == (lseg-1))
                    posb2 = i;
                break;
            }
        }
        //
        if(lseg== j)
        {
            child.path[iChild++] = gb;
        }
    }
    if(posb1< posb2)
        for(int i = 0 ; i < lseg ; ++i)
            child.path[i + iStartInsert] =genseg[i];
    else
        for(int i = 0 ; i < lseg ; ++i)
            child.path[i + iStartInsert] =genseg[lseg - i -1];
    delete[]genseg;
}

5.   效果圖

 

三、總結

基因算法總的來說體現了一個“優勝劣汰”的法則,優秀的基因存活下來。而且基因算法從針對於個體轉到了羣體,有別於A*這些普通的啓發式算法。

 

其中我在實現這個算法的時候,在嘗試基因鏈重組算法的時候,一直沒有找到一個能夠保證優秀基因遺傳下去的好方法,所在在網上搜了一下關於TSP和基因算法,找到了一篇論文《構建“基因庫”求解TSP問題的混合遺傳算法》,有興趣的讀者可以去看一下這篇文章。

 

如果有興趣的可以留言,一起交流一下算法學習的心得。

聲明:本文章是筆者整理資料所得原創文章,如轉載需註明出處,謝謝。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章