算法描述:
- 檢查每個基因解決問題的能力,並量化此能力值
- 選出當前記憶庫中的基因作為父代。選擇原則是:解決能力越強的別選到的概率越大。
- 將選出的兩者根據雜交率進行雜交,生成子代
- 根據變異率對子代進行變異
- 重復2、3、4,直到新的世代產生完畢
現在要使用這個算法來解決下列尋路問題:
有一個如圖所示的隨機生成的迷宮,在里面確定一個起點和一個重點,找到一條重起點到重點的通路。
類似的問題解決方法很多不再贅述,此處僅僅嘗試使用遺傳算法來解決。
基本思路就是把解決方案編碼為基因,然后通過遺傳算法的演進,找出一個解或者近似解。
解決問題:
編碼方式如下:
在迷宮中的行動只有上下左右四種,用兩位2進制編碼恰好合適
00 | → |
01 | ↑ |
10 | ← |
11 | ↓ |
接下來量化一種基因解決問題的能力,這里使用“到終點的格子距離”來度量,即:
diffx = end_point.x - x
diffy = end_point.y - y
這里的x/y這么確定:
如果按照一種基因的前進序列走到死路(即撞牆了),那么就取當前的x/y,而基因的有效長度也就止於此。
我定義的基因類如下:
local Genome = {} function Genome:new(len) local o = {} o.bits = {} o.fitness = 0 --math.randomseed(os.time()) for i = 1,len do if math.random()>0.5 then table.insert(o.bits,1) else table.insert(o.bits,0) end end return o end return Genome
初始化一個基因庫,這是第一代,基因的內容隨機,長度設定為合適的值,庫的尺寸推薦為基因長度的2倍。
設定好了第一代,需要確定每個基因的能力值,我們使用下面這個值來表示:
fitness = 1/(diffx+diffy+1)
其中加1是為了防止分母為零。
然后開始選擇父代進行繁衍,我們知道雜交和變異都不會決定整個種群的進化方向,決定方向的重大責任落在了選擇這一個步驟(考慮自然界是一個道理)
這里為了滿足所說的“解決能力越強的別選到的概率越大”原則,簡單的采用賭輪選擇法,就和賭輪一樣,輪盤上面積越大的區域小球停在其中的概率就越大。
--賭輪選擇 function Test1:select() local piece = math.random()*self.total_fitness local temp = 0 for i=1,#self.genomes do temp = temp + self.genomes[i].fitness if temp>=piece then return self.genomes[i] end end end
通過賭輪選擇在種群中選出父輩,接下來進行雜交:
雜交就是,如果滿足雜交條件(這里用雜交率來控制),隨機選擇一個雜交點,即在基因上隨機確定一個點,把之前的所有片段交換,后面的片段保持不變。
如果是自交或者不滿足雜交條件,就直接將父代送入子代。
--雜交 --return two arrays function Test1:crossover(dad,mum) local child1 = {} local child2 = {} local rn = math.random() if rn<self.crossover_rate then local pt = math.random(#dad) for i=1,pt-1 do child1[i] = dad[i] child2[i] = mum[i] end for i=pt,#dad do child1[i] = mum[i] child2[i] = dad[i] end else child1 = dad child2 = mum end return child1,child2 end
接着對產生的子代進行變異,變異率是預先設定的,只有滿足變異率的子代基因片段才會變異,變異方式采取直接翻轉片段
--變異 function Test1:mutate(bits) for i=1,#bits do if math.random()<self.mutation_rate then if bits[i]==1 then bits[i] = 0 else bits[i] = 1 end end end end
然后對整個種群都執行上述步驟,知道新的一代產生完畢。
這里是等待子代產生完畢才替換新一代的,我目前還不確定直接把新一代拿去替換當前世代中比較弱的一些個體會出現什么情況。
下面是演進代碼:
--演化 function Test1:step_in() self:detect_map() local all_chids_n = 0 while all_chids_n < self.gn do --測試地圖 self:detect_map() local dad = self:select() local mum = self:select() --創建新的子代 local child1 = Genome:new(self.gl) local child2 = Genome:new(self.gl) --雜交 child1.bits, child2.bits = self:crossover(dad.bits, mum.bits); --變異 self:mutate(child1.bits); self:mutate(child2.bits); --插入到新的基因庫,新的基因庫滿表明一個世代的變更 table.insert(self.new_genomes, child1) table.insert(self.new_genomes, child2) all_chids_n = all_chids_n + 2 end self.genomes = self.new_genomes self.new_genomes = { } --記錄世代數 self.ig = self.ig + 1 end
現在大家都在等待“君王”的出現。
“君王”的出現條件很簡單:只要他成功的抵達了終點。
if diffx+diffy<=2 then --中斷演進輸出當前基因 end
然后,看看程序的運行結果:
我是用了20X20的地圖尺寸,隨機添加了一些障礙,程序下面的控制台顯示整個嘗試花費了42代的演進。
值得注意的是,這個算法本來就比較耗時,再加上我使用lua進行模擬,速度比c++慢了許多,所以一個這種20X20的地圖找到一個解的時間往往是相當長的,甚至有時候可能找不到解。為了不讓整個程序無限制的等待下去,我限定整個種群只能演變200代,超過這個數量被視為此問題無解,如果使用的上傳的程序,可以點擊鼠標中鍵來重啟整個模擬。另外,如果運氣不太好的話,找到一組解的時間往往會相當的長,整個程序看上去好像死機了一樣,這個時候唯一做的事情就是等待,如果程序恢復正常的響應通常情況下說明已經找到一組解了,這時候一個小僵屍會沿着這條路徑從起點走到終點。
程序的所有參數可以在Test1.lua里面進行修改。
最后,為了防止隨機的基因進行一些無意義的反向行動(也就是上一步向左走下一步向右走這種),我禁止並修正了這些行為:
if bit1 == 0 and bit2 == 1 then if self.no_back then if bith1==1 and bith2 == 1 then y = y - 1 gs[i].bits[j] = 1 gs[i].bits[j+1] = 1 else y = y + 1 end else y = y + 1 end
end