1. 程式人生 > >R語言中的遺傳演算法詳細解析

R語言中的遺傳演算法詳細解析

前言

人類總是在生活中摸索規律,把規律總結為經驗,再把經驗傳給後人,讓後人發現更多的規規律,每一次知識的傳遞都是一次進化的過程,最終會形成了人類的智慧。自然界規律,讓人類適者生存地活了下來,聰明的科學家又把生物進化的規律,總結成遺傳演算法,擴充套件到了更廣的領域中。

本文將帶你走進遺傳演算法的世界。

目錄

  1. 遺傳演算法介紹
  2. 遺傳演算法原理
  3. 遺傳演算法R語言實現

1. 遺傳演算法介紹

遺傳演算法是一種解決最優化的搜尋演算法,是進化演算法的一種。進化演算法最初借鑑了達爾文的進化論和孟德爾的遺傳學說,從生物進化的一些現象發展起來,這些現象包括遺傳、基因突變、自然選擇和雜交等。遺傳演算法通過模仿自然界生物進化機制,發展出了隨機全域性搜尋和優化的方法。遺傳演算法其本質是一種高效、並行、全域性搜尋的方法,它能在搜尋過程中自動獲取和積累有關搜尋空間的知識,並自適應的控制搜尋過程,計算出全域性最優解。

遺傳演算法的操作使用適者生存的原則,在潛在的種群中逐次產生一個近似最優解的方案,在每一代中,根據個體在問題域中的適應度值和從自然遺傳學中借鑑來的再造方法進行個體選擇,產生一個新的近似解。這個過程會導致種群中個體的進化,得到的新個體比原來個體更能適應環境,就像自然界中的改造一樣。

如果從生物進化的角度,我們可以這樣理解。在一個種群中,個體數量已經有一定規模,為了進化發展,通過選擇和繁殖產生下一代的個體,其中繁殖過程包括交配和突變。根據適者生存的原則,選擇過程會根據新個體的適應度進行保留或淘汰,但也不是完全以適應度高低作為導向,如果單純選擇適應度高的個體可能會產生區域性最優的種群,而非全域性最優,這個種群將不會再進化,稱為早熟。之後,通過繁殖過程,讓個體兩兩交配產生下一代新個體,上一代個體中優秀的基因會保留給下一代,而劣制的基因將被個體另一半的基因所代替。最後,通過小概率事件發生基因突變,通過突變產生新的下一代個體,實現種群的變異進化。

經過這一系列的選擇、交配和突變的過程,產生的新一代個體將不同於初始的一代,並一代一代向增加整體適應度的方向發展,因為最好的個體總是更多的被選擇去產生下一代,而適應度低的個體逐漸被淘汰掉。這樣的過程不斷的重複:每個個體被評價,計算出適應度,兩個個體交配,然後突變,產生第三代。周而復始,直到終止條件滿足為止。

遺傳演算法需要注意的問題:

  • 遺傳演算法在適應度函式選擇不當的情況下有可能收斂於區域性最優,而不能達到全域性最優。
  • 初始種群的數量很重要,如果初始種群數量過多,演算法會佔用大量系統資源;如果初始種群數量過少,演算法很可能忽略掉最優解。
  • 對於每個解,一般根據實際情況進行編碼,這樣有利於編寫變異函式和適應度函式。
  • 在編碼過的遺傳演算法中,每次變異的編碼長度也影響到遺傳演算法的效率。如果變異程式碼長度過長,變異的多樣性會受到限制;如果變異程式碼過短,變異的效率會非常低下,選擇適當的變異長度是提高效率的關鍵。
  • 變異率是一個重要的引數。
  • 對於動態資料,用遺傳演算法求最優解比較困難,因為染色體種群很可能過早地收斂,而對以後變化了的資料不再產生變化。對於這個問題,研究者提出了一些方法增加基因的多樣性,從而防止過早的收斂。其中一種是所謂觸發式超級變異,就是當染色體群體的質量下降(彼此的區別減少)時增加變異概率;另一種叫隨機外來染色體,是偶爾加入一些全新的隨機生成的染色體個體,從而增加染色體多樣性。
  • 選擇過程很重要,但交叉和變異的重要性存在爭議。一種觀點認為交叉比變異更重要,因為變異僅僅是保證不丟失某些可能的解;而另一種觀點則認為交叉過程的作用只不過是在種群中推廣變異過程所造成的更新,對於初期的種群來說,交叉幾乎等效於一個非常大的變異率,而這麼大的變異很可能影響進化過程。
  • 遺傳演算法很快就能找到良好的解,即使是在很複雜的解空間中。
  • 遺傳演算法並不一定總是最好的優化策略,優化問題要具體情況具體分析。所以在使用遺傳演算法的同時,也可以嘗試其他演算法,互相補充,甚至根本不用遺傳演算法。
  • 遺傳演算法不能解決那些“大海撈針”的問題,所謂“大海撈針”問題就是沒有一個確切的適應度函式表徵個體好壞的問題,使得演算法的進化失去導向。
  • 對於任何一個具體的優化問題,調節遺傳演算法的引數可能會有利於更好的更快的收斂,這些引數包括個體數目、交叉率和變異率。例如太大的變異率會導致丟失最優解,而過小的變異率會導致演算法過早的收斂於區域性最優點。對於這些引數的選擇,現在還沒有實用的上下限。
  • 適應度函式對於演算法的速度和效果也很重要。

遺傳演算法的應用領域包括計算機自動設計、生產排程、電路設計、遊戲設計、機器人學習、模糊控制、時間表安排,神經網路訓練等。然而,我準備把遺傳演算法到金融領域,比如回測系統的引數尋優方案,我會在以後的文章中,介紹有關金融解決方案。

2. 遺傳演算法原理

在遺傳演算法裡,優化問題的解是被稱為個體,它表示為一個變數序列,叫做染色體或者基因串。染色體一般被表達為簡單的字串或數字串,也有其他表示法,這一過程稱為編碼。首先要建立種群,演算法隨機生成一定數量的個體,有時候也可以人工干預這個過程進行,以提高初始種群的質量。在每一代中,每一個個體都被評價,並通過計算適應度函式得到一個適應度數值。種群中的個體被按照適應度排序,適應度高的在前面。

接下來,是產生下一代個體的種群,通過選擇過程和繁殖過程完成。

選擇過程,是根據新個體的適應度進行的,但同時並不意味著完全的以適應度高低作為導向,因為單純選擇適應度高的個體將可能導致演算法快速收斂到區域性最優解而非全域性最優解,我們稱之為早熟。作為折中,遺傳演算法依據原則:適應度越高,被選擇的機會越高,而適應度低的,被選擇的機會就低。初始的資料可以通過這樣的選擇過程組成一個相對優化的群體。

繁殖過程,表示被選擇的個體進入交配過程,包括交配(crossover)和突變(mutation),交配對應演算法中的交叉操作。一般的遺傳演算法都有一個交配概率,範圍一般是0.6~1,這個交配概率反映兩個被選中的個體進行交配的概率。

例如,交配概率為0.8,則80%的“夫妻”個體會生育後代。每兩個個體通過交配產生兩個新個體,代替原來的“老”個體,而不交配的個體則保持不變。交配過程,父母的染色體相互交換,從而產生兩個新的染色體,第一個個體前半段是父親的染色體,後半段是母親的,第二個個體則正好相反。不過這裡指的半段並不是真正的一半,這個位置叫做交配點,也是隨機產生的,可以是染色體的任意位置。

突變過程,表示通過突變產生新的下一代個體。一般遺傳演算法都有一個固定的突變常數,又稱為變異概率,通常是0.1或者更小,這代表變異發生的概率。根據這個概率,新個體的染色體隨機的突變,通常就是改變染色體的一個位元組(0變到1,或者1變到0)。

遺傳演算法實現將不斷的重複這個過程:每個個體被評價,計算出適應度,兩個個體交配,然後突變,產生下一代,直到終止條件滿足為止。一般終止條件有以下幾種:

  • 進化次數限制
  • 計算耗費的資源限制,如計算時間、計算佔用的CPU,記憶體等
  • 個體已經滿足最優值的條件,即最優值已經找到
  • 當適應度已經達到飽和,繼續進化不會產生適應度更好的個體
  • 人為干預

演算法實現原理:

  • 1. 建立初始種群
  • 2. 迴圈:產生下一代
  • 3. 評價種群中的個體適應度
  • 4. 定義選擇的適應度函式
  • 5. 改變該種群(交配和變異)
  • 6. 返回第二步
  • 7. 滿足終止條件結束

3. 遺傳演算法R語言實現

本節的系統環境

  • Win7 64bit
  • R: 3.1.1 x86_64-w64-mingw32/x64 (64-bit)

一個典型的遺傳演算法要求:一個基因表示的求解域, 一個適應度函式來評價解決方案。

遺傳演算法的引數通常包括以下幾個:

  • 種群規模(Population),即種群中染色體個體的數目。
  • 染色體的基因個數(Size),即變數的數目。
  • 交配概率(Crossover),用於控制交叉計算的使用頻率。交叉操作可以加快收斂,使解達到最有希望的最優解區域,因此一般取較大的交叉概率,但交叉概率太高也可能導致過早收斂。
  • 變異概率(Mutation),用於控制變異計算的使用頻率,決定了遺傳演算法的區域性搜尋能力。
  • 中止條件(Termination),結束的標誌。

在R語言中,有一些現成的第三方包已經實現的遺傳演算法,我們可以直接進行使用。

  • mcga包,多變數的遺傳演算法,用於求解多維函式的最小值。
  • genalg包,多變數的遺傳演算法,用於求解多維函式的最小值。
  • rgenoud包,複雜的遺傳演算法,將遺傳演算法和衍生的擬牛頓演算法結合起來,可以求解複雜函式的最優化化問題。
  • gafit包,利用遺傳演算法求解一維函式的最小值。不支援R 3.1.1的版本。
  • GALGO包,利用遺傳演算法求解多維函式的最優化解。不支援R 3.1.1的版本。

本文將介紹mcga包和genalg包的遺傳演算法的使用。

3.1 mcga包

我們使用mcga包的mcga()函式,可以實現多變數的遺傳演算法。

mcga包是一個遺傳演算法快速的工具包,主要解決實值優化的問題。它使用的變數值表示基因序列,而不是位元組碼,因此不需要編解碼的處理。mcga實現了遺傳演算法的交配和突變的操作,並且可以進行大範圍和高精度的搜尋空間的計算,演算法的主要缺點是使用了256位的一元字母表。

首先,安裝mcga包。

> install.packages("mcga") > library(mcga)

檢視一下mcga()函式的定義。

> mcga
function (popsize, chsize, crossprob = 1, mutateprob = 0.01, elitism = 1, minval, maxval, maxiter = 10, evalFunc) 

引數說明:

  • popsize,個體數量,即染色體數目
  • chsize,基因數量,限引數的數量
  • crossprob,交配概率,預設為1.0
  • mutateprob,突變概率,預設為0.01
  • elitism,精英數量,直接複製到下一代的染色體數目,預設為1
  • minval,隨機生成初始種群的下邊界值
  • maxval,隨機生成初始種群的上邊界值
  • maxiter,繁殖次數,即迴圈次數,預設為10
  • evalFunc,適應度函式,用於給個體進行評價

接下來,我們給定一個優化的問題,通過mcga()函式,計算最優化的解。

題目1:設fx=(x1-5)^2 + (x2-55)^2 +(x3-555)^2 +(x4-5555)^2 +(x5-55555)^2,計算fx的最小值,其中x1,x2,x3,x4,x5為5個不同的變數。

從直觀上看,如果想得到fx的最小值,其實當x1=5,x2=55,x3=555,x4=5555,x5=55555時,fx=0為最小值。如果使用窮舉法,通過迴圈的方法找到這5個變數,估計會很費時的,我就不做測試了。下面我們看一下遺傳演算法的執行情況。

# 定義適應度函式
> f<-function(x){} # 程式碼省略 # 執行遺傳演算法 > m <- mcga( popsize=200, + chsize=5, + minval=0.0, + maxval=999999, + maxiter=2500, + crossprob=1.0, + mutateprob=0.01, + evalFunc=f) # 最優化的個體結果 > print(m$population[1,]) [1] 5.000317 54.997099 554.999873 5555.003120 55554.218695 # 執行時間 > m$costs[1] [1] 3.6104556

我們得到的最優化的結果為x1=5.000317, x2=54.997099, x3=554.999873, x4=5555.003120, x5=55554.218695,和我們預期的結果非常接近,而且耗時只有3.6秒。這個結果是非常令人滿意地,不是麼!如果使用窮舉法,時間複雜度為O(n^5),估計沒有5分鐘肯定算不出來。

當然,演算法執行時間和精度,都是通過引數進行配置的。如果增大個體數目或迴圈次數,一方面會增加演算法的計算時間,另一方面結果也可能變得更精準。所以,在實際的使用過程中,需要根據一定的經驗調整這幾個引數。

3.2 genalg包

我們使用genalg包的rbga()函式,也可以實現多變數的遺傳演算法。

genalg包不僅實現了遺傳演算法,還提供了遺傳演算法的資料視覺化,給使用者更直觀的角度理解演算法。預設圖顯示的最小和平均評價值,表示遺傳演算法的計算進度。直方圖顯出了基因選擇的頻率,即基因在當前個體中被選擇的次數。引數圖表示評價函式和變數值,非常方便地看到評價函式和變數值的相關關係。

首先,安裝genalg包。

> install.packages("genalg") > library(genalg)

檢視一下rbga()函式的定義。

> rbga(stringMin=c(), stringMax=c(), suggestions=NULL, popSize=200, iters=100, mutationChance=NA, elitism=NA, monitorFunc=NULL, evalFunc=NULL, showSettings=FALSE, verbose=FALSE)

引數說明:

  • stringMin,設定每個基因的最小值
  • stringMax,設定每個基因的最大值
  • suggestions,建議染色體的可選列表
  • popSize,個體數量,即染色體數目,預設為200
  • iters,迭代次數,預設為100
  • mutationChance,突變機會,預設為1/(size+1),它影響收斂速度和搜尋空間的探測,低機率導致更快收斂,高機率增加了搜尋空間的跨度。
  • elitism,精英數量,預設為20%,直接複製到下一代的染色體數目
  • monitorFunc,監控函式,每產生一代後執行
  • evalFunc,適應度函式,用於給個體進行評價
  • showSettings,列印設定,預設為false
  • verbose,列印演算法執行日誌,預設為false

接下來,我們給定一個優化的問題,通過rbga()函式,計算最優化的解。

題目2:設fx=abs(x1-sqrt(exp(1)))+abs(x2-log(pi)),計算fx的最小值,其中x1,x2為2個不同的變數。

從直觀上看,如果想得到fx的最小值,其實當x1=sqrt(exp(1))=1.648721, x2=log(pi)=1.14473時,fx=0為最小值。同樣地,如果使用窮舉法,通過迴圈的方法找到這2個變數,估計會很費時的,我也不做測試了。下面我們看一下rbga()函式的遺傳演算法的執行情況。

# 定義適應度函式
> f<-function(x){} #程式碼省略 # 定義監控函式 > monitor <- function(obj){} #程式碼省略 # 執行遺傳演算法 > m2 = rbga(c(1,1), + c(3,3), + popSize=100, + iters=1000, + evalFunc=f, + mutationChance=0.01, + verbose=TRUE, + monitorFunc=monitor + ) Testing the sanity of parameters... Not showing GA settings... Starting with random values in the given domains... Starting iteration 1 Calucating evaluation values... ....................................................... done. Sending current state to rgba.monitor()... Creating next generation... sorting results... applying elitism... applying crossover... applying mutations... 2 mutations applied Starting iteration 2 Calucating evaluation values... ...................... done. Sending current state to rgba.monitor()... Creating next generation... sorting results... applying elitism... applying crossover... applying mutations... 4 mutations applied Starting iteration 3 Calucating evaluation values... ....................... done. # 省略輸出...

程式執行截圖

需要注意的是,程式在要命令列介面執行,如果在RStudio中執行,會出現下面的錯誤提示。

Creating next generation...
  sorting results...
  applying elitism...
  applying crossover...
  applying mutations... 1 mutations applied Starting iteration 10 Calucating evaluation values... .......................... done. Sending current state to rgba.monitor()... Error in get(name, envir = asNamespace(pkg), inherits = FALSE) : object 'rversion' not found Graphics error: Error in get(name, envir = asNamespace(pkg), inherits = FALSE) : object 'rversion' not found

我們迭代1000次後,檢視計算結果。

# 計算結果
> m2$population[1,]
[1] 1.650571 1.145784

我們得到的最優化的結果為x1=1.650571, x2=1.145784,非常接近最終的結果。另外,我們可以通過genalg包的視覺化功能,看到迭代過程的每次的計算結果。下面截圖分為對應1次迭代,10次迭代,200次迭代和1000次迭代的計算結果。從圖中可以看出,隨著迭代次數的增加,優選出的結果集變得越來越少,而且越來越精準。

預設圖輸出,用於描述遺傳過程的進展,X軸為迭代次數,Y軸評價值,評價值越接近於0越好。在1000迭代1000次後,基本找到了精確的結果。

> plot(m2)

直方圖輸出,用於描述對染色體的基因選擇頻率,即一個基因在染色體中的當前人口被選擇的次數。當x1在1.65區域時,被選擇超過80次;當x2在1.146區域時,被選擇超過了80次。通過直方圖,我們可以理解為更優秀的基因被留給了後代。

> plot(m2,type='hist')

引數圖輸出,用於描述評價函式和變數的值的相關關係。對於x1,評價值越小,變數值越準確,但相關關係不明顯。對於x2,看不出相關關係。

> plot(m2,type='vars')

對比mcga包和genalg包,mcga包適合計算大範圍取值空間的最優解,而用genalg包對於大範圍取值空間的計算就表現就不太好了。從另一個方面講,genalg包提供了視覺化工具,可以讓我們直觀的看遺傳演算法的收斂過程,對於演算法的理解和調優是非常有幫助的。

在掌握了遺傳演算法後,我們就可以快度地處理一些優化的問題了,比如接下來我會介紹的金融回測系統的引數尋優方案。讓我們遠離窮舉法,珍惜CPU的每一秒時間。