最近有小夥伴看到了我之前寫的螢火蟲算法相關的文章(羣體智能優化算法之螢火蟲算法(Firefly Algorithm,FA)),說是比較感興趣,同時又有一些問題不太明白,所以今天我詳細地再結合代碼介紹一下這個算法,源碼可以通過“Github”獲取。
22.1 螢火蟲行爲
螢火蟲之間通過閃光來進行信息的交互,同時也能起到危險預警的作用。我們知道從光源到特定距離r處的光強服從平方反比定律,也就是說光強I隨着距離 r 的增加會逐漸降低,即 ,此外空氣也會吸收部分光線,導致光線隨着距離的增加而變得越來越弱。這兩個因素同時起作用,因而大多數螢火蟲只能在有限的距離內被其他螢火蟲發現。
螢火蟲算法就是通過模擬螢火蟲的發光行爲而提出的,所以實際上其原理很簡單。爲了方便算法的描述,作者給出了三個理想化的假設:
- 所有螢火蟲雌雄同體,以保證不管螢火蟲性別如何,都能被其他螢火蟲所吸引(每隻螢火蟲代表一個解,在實際問題中這和性別是沒關係的,因此不必建模);
- 吸引度與它們的亮度成正比,因此,對於任何兩隻閃爍的螢火蟲,較暗的那隻會朝着較亮的那隻移動。吸引力與亮度程度會隨着距離的增加而減小。最亮的螢火蟲會隨機選擇方向進行移動(規定了解的更新方式,較暗移向較亮的可以認爲是全局搜索,而最亮的進行隨機移動屬於局部搜索);
- 螢火蟲的亮度可受目標函數影響或決定,對於最大化問題,亮度可以簡單地與目標函數值成正比(建立了算法與領域問題的關係,規定了如何將目標值表示亮度)。
22.2 亮度和吸引度
在螢火蟲算法中,有兩個重要的問題:光強的變化和吸引度的形成。爲了簡單起見,我們總是可以假設螢火蟲的吸引度是由它的亮度決定的,而亮度又與目標函數相關。
對於最大化優化問題,螢火蟲在某一位置x的亮度I可以設定與目標函數 成正比,即 。但是吸引度是相對的,它應該是由其他螢火蟲所感受或斷定的,因此該值會因螢火蟲i和螢火蟲j之間的距離 不同而不同,同光強一樣,也會收到空氣吸收程度的影響。光強I(r)的變化遵循平方反比定律 , 爲光源處的強度。對於給定的具有固定光吸收係數 的介質(如空氣),光強I與距離r有關,即 , 爲原始光強。爲了避免 在r=0 時除以 0,採用一種考慮平方反比律和吸收的綜合近似表達方式:
如果希望函數單調遞減的速度慢一點,則可以使用下式:
有了光強接下來就可以定義吸引度 了,由於螢火蟲的吸引度正比於光強,所以有:
爲r=0 處的吸引度。
在具體實現中,吸引度函數 可以是任意形式的單調遞減函數:
22.3 距離和移動
任意兩隻螢火蟲i和j在其各自位置 和 上的距離爲笛卡爾距離:
其中 爲第i只螢火蟲空間座標 的第k維座標值。對於二維情況,。
螢火蟲i會向着比它更亮的其他螢火蟲j的方向移動:
式中第二項刻畫了吸引度的作用,第三項爲隨機擾動項, 爲步長,rand 爲[0,1]之間均勻分佈的隨機數。在絕大數應用中,可以設定 ,,當然也可以設定隨機項服從正太分佈N(0,1)或其他分佈。此外,如果數值範圍在不同維度上相差很大,如在一個維度上範圍爲 到 ,另一個維度上範圍爲[-0.001,0.01],需要首先根據領域問題的實際取值範圍確定各個維度上的縮放係數 ,然後使用 代替 。
22.4 算法和參數分析
總體來說螢火蟲算法原理很簡單,參數也不多,這也是其收斂速度快、性能穩定的原因。下面按照式(6)從左往右的順序一一分析一下這些參數對算法的影響。
:初始吸引度,也就是自身對自身的吸引度,該值通常設爲 1。爲什麼呢?我們先將隨機項忽略,我們將式(6)整理以下,可以得到:
沒有發現這個等式很熟悉嗎?它實際上表達的就是在 和 連線上中間的一個點,設定 ,一方面如果 和 是同一個點,那麼得到的新點仍爲 ,另一方面如果不是同一個點,那麼將確保新點爲兩點之間連線上的點。
:吸引度衰減參數,其取值範圍爲[0,+∞)。如果取 0,則吸引度爲常量 ,這等於說光強不會隨着距離衰減,那麼螢火蟲就可以被其他任意位置的螢火蟲發現,所有的螢火蟲都會朝着最亮的螢火蟲靠近,從而很快收斂到一個最優值,這和只關注全局最優解的 PSO 有些類似;如果取值爲+∞,那麼 ->0,意味着在其他螢火蟲眼中吸引度幾乎爲 0,那麼螢火蟲就是短視的,相當於,火蟲是閉上眼睛隨機移動的,這時進行的就是純隨機搜索。由於 取值一般介於這兩個極端之間(常取值爲 ,L爲領域問題各個維度的平均範圍),因此常能得到比 PSO 和隨機搜索更好的結果。
:控制隨機擾動的步長。該值不能太大,否則會導致算法出現震盪,無法收斂,也不能太小,否則無法進行有效的局部搜索。一般情況下,。也可以設置自適應的 ,通過迭代次數來更新其值:
其中 爲初始隨機縮放因子, 爲冷卻因子,通常 ,=0.95~0.97。
完整的 FA 算法如下圖所示。
FA 算法有兩個遍歷種羣n的內循環,一個迭代次數t的外循環,因此算法在最壞情況下複雜度爲 ,由於n很小(通常爲n = 40),而t很大(比如t = 5000),計算成本相對較低,因爲算法複雜度與t呈線性關係,主要的計算成本將在目標函數的評估上。如果 n 相對較大,則可以使用一個內部循環,通過排序算法對所有螢火蟲的吸引力或亮度進行排序。在這種情況下,FA 算法的複雜度爲 。
那麼爲什麼 FA 看起來簡單卻這麼有效呢? 首先,FA 是羣體智能算法,因此它具備羣體智能算法所有的優點;其次,FA 基於吸引度,吸引度隨着距離增加而降低,所以如果兩個螢火蟲離的很遠,較亮的螢火蟲不會將較暗的螢火蟲吸引過去,這導致了這樣一個事實,即整個種羣可以自動細分爲子羣體,每個子羣體可以圍繞每個模態或局部最優,在這些局部最優中可以找到全局最優解;最後,如果種羣規模比模態數多得多,這種細分使螢火蟲能夠同時找到所有極值。
22.5 代碼講解
瞭解了以上算法原理後,下面基於 Java1.8 採用面向對象方法進行代碼編碼。完整代碼可以點擊“這裏”查看, 包括 java 項目和算法原作者編寫的 matlab 代碼
首先確定領域對象,包括螢火蟲 Firefly、目標函數 ObjectiveFun、位置 Position 和範圍 Range。
其次實現算法類,由於 FA 存在多種改進版本,爲了表示不同的算法內容,這裏定義了算法接口,通過對其進行不同的實現建立不同的算法。
最後針對具體優化問題進行了測試。
下面結合代碼具體說一下算法執行流程。
- 初始參數設置。主要設置種羣數量 popNum,最大迭代次數 maxGen,求解問題維度 dim,步長 alpha,初始吸引度 initAttraction,吸收因子 gamma,步長自適應 isAdaptive 和目標函數 objectiveFun 等。
// 新建目標函數
ObjectiveFun objectiveFun = new ObjectiveFun();
FANormal faNormal = FANormal.builder().popNum(40).maxGen(200).dim(2).alpha(0.2).initAttraction(1.0).gamma(1.0)
.isAdaptive(true).objectiveFun(objectiveFun).build();
faNormal.start();
- 種羣初始化。根據設定的種羣大小,建立相應數量的螢火蟲,在目標函數自變量的取值範圍內隨機確定每個螢火蟲的位置;
@Override
public void initPop() {
// TODO Auto-generated method stub
System.out.println("**********種羣初始化**********");
fireflies = new ArrayList<>();
for (int i = 0; i < popNum; i++) {
fireflies.add(new Firefly(new Position(this.dim, objectiveFun.getRange())));
}
}
- 對初始種羣進行亮度計算,即目標評估。對於最大化問題,評估過程中直接將目標作爲亮度,對於最小化問題,則對目標值取相反數或倒數。之後再對螢火蟲按照亮度進行排序,以確定當前種羣中最優的個體。
@Override
public void calcuLight() {
// TODO Auto-generated method stub
for (Firefly firefly : fireflies) {
firefly.setLight(this.getObjectiveFun().getObjValue((firefly.getPosition().getPositionCode())));
}
Collections.sort(fireflies);
// 展示螢火蟲分佈
}
- 螢火蟲移動。亮度較低的螢火蟲飛向亮度較高的螢火蟲,亮度最亮的螢火蟲隨機移動。注意第 15 行計算了取值範圍向量,這是針對多維空間優化問題時不同維度取值範圍不同而設定的,對於取值範圍較大的維度,在隨機搜索時可以以較大的步長移動,而對於取值範圍較小的維度,最好移動的不要太劇烈,所以通過該取值範圍向量就可以很好的控制各個維度的變化量。
@Override
public void fireflyMove() {
// TODO Auto-generated method stub
for (int i = 0; i < popNum; i++) {
for (int j = 0; j < popNum; j++) {
Firefly fireflyi = fireflies.get(i);
Firefly fireflyj = fireflies.get(j);
if (i != j && fireflyj.getLight() > fireflyi.getLight()) { // 當螢火蟲j的亮度大於螢火蟲i的亮度時
double[] codei = fireflyi.getPosition().getPositionCode();
double[] codej = fireflyj.getPosition().getPositionCode();
// 計算螢火蟲之間的距離
double disij = calcDistance(codei, codej);
// 計算吸引度beta
double attraction = initAttraction * Math.pow(Math.E, -gamma * disij * disij); // 計算螢火蟲j對螢火蟲i的吸引度
double[] scale = fireflyi.getPosition().getRange().getScale();
double[] newPositionCode = IntStream.range(0, this.dim).mapToDouble(ind -> codei[ind]
+ attraction * (codej[ind] - codei[ind]) + alpha * (random.nextDouble() - 0.5) * scale[ind])
.toArray();
fireflyi.getPosition().setPositionCode(newPositionCode);
}
}
}
// 對最亮的螢火蟲進行隨機移動
Firefly bestFirefly = fireflies.get(popNum - 1);
double[] scale = bestFirefly.getPosition().getRange().getScale();
double[] newPositionCode = IntStream.range(0, dim).mapToDouble(
i -> bestFirefly.getPosition().getPositionCode()[i] + alpha * (random.nextDouble() - 0.5) * scale[i])
.toArray();
bestFirefly.getPosition().setPositionCode(newPositionCode);
}
- 更新迭代器。迭代次數加 1,同時如果設置了步長自適應,則步長進行進一步的縮放。
public void incrementIter() {
iterator++;
if (isAdaptive) {
alpha *= delta;
}
}
- 循環。循環執行步驟 2~5,直到迭代次數達到設定的最大迭代次數。
while (this.iterator < maxGen) {
calcuLight();
fireflyMove();
incrementIter();
System.out.println("**********第" + iterator + "代最優解:" + fireflies.get(popNum - 1) + "**********");
}
測試中使用的目標函數(最大化)方程和圖像分別如下:
exp(-(x-4)^2-(y-4)^2)+exp(-(x+4)^2-(y-4)^2)+2*exp(-x^2-(y+4)^2)+2*exp(-x^2-y^2)
x、y 取值範圍均爲[-5,5],在此區間上該函數存在兩個全局最優解(0,0)和(0,-4),多次運行程序可得到這兩個近似最優解。
最後看一下螢火蟲的是怎麼移動的,通過圖 5 可以看出,螢火蟲形成了不同的子種羣,各自收斂到一個局部最優解,這恰恰印證了之前關於 FA 有效性分析中的自動化分子種羣的描述。
22.6 FAQ 環節
Q1:步長爲什麼要減小,會不會影響收斂速度?
A1:自適應步長 其實在算法參數那一部分已經介紹到了,那裏我提到說 不能太大也不能太小,但實際上我們更希望步長在算法初期具有更大的探索性,而在算法後期需要收斂的時候應該使得算法更加穩定,我們才設計步長是不斷自適應減小的,但是有一點值得注意,這個參數本質上和收斂速度關係不是很大,這個參數更多的是促進種羣的多樣性,因爲步長是控制隨機項的,隨機越大,種羣越多樣,那麼算法也就不太可能收斂。
Q2:亮度重新賦值的目的?
A2:說白了亮度就是目標函數值(最大化問題),所以在算法中螢火蟲一旦進行了移動,就需要對其重新進行評估,要不怎麼知道下一步往哪個更亮的方向移動呢?
Q3:開源代碼有嗎?
A3:點擊“這裏”查看 github 鏈接。
Q4:在處理圖像數據時是否也應該使用自適應步長?
A4:自適應步長是針對算法而言的,和優化問題本身沒關係,要說有關係那就是步長初始值的設定,通常領域問題不同,初始值設定就不同。
Q5:算法原作者給出的 matlab 代碼是否 OK?
A5:這段代碼思路上沒啥問題,只不過是針對具體的優化問題而編寫的,在解決自己的實際問題時,可能需要進行部分修改。