羣體智能優化算法之螢火蟲算法(Firefly Algorithm,FA)-看了還不會提刀來找我

在這裏插入圖片描述


最近有小夥伴看到了我之前寫的螢火蟲算法相關的文章(羣體智能優化算法之螢火蟲算法(Firefly Algorithm,FA)),說是比較感興趣,同時又有一些問題不太明白,所以今天我詳細地再結合代碼介紹一下這個算法,源碼可以通過“Github”獲取。

22.1 螢火蟲行爲

螢火蟲之間通過閃光來進行信息的交互,同時也能起到危險預警的作用。我們知道從光源到特定距離r處的光強服從平方反比定律,也就是說光強I隨着距離 r 的增加會逐漸降低,即 I1/r2I \propto 1 / r^{2},此外空氣也會吸收部分光線,導致光線隨着距離的增加而變得越來越弱。這兩個因素同時起作用,因而大多數螢火蟲只能在有限的距離內被其他螢火蟲發現。

  螢火蟲算法就是通過模擬螢火蟲的發光行爲而提出的,所以實際上其原理很簡單。爲了方便算法的描述,作者給出了三個理想化的假設:

  • 所有螢火蟲雌雄同體,以保證不管螢火蟲性別如何,都能被其他螢火蟲所吸引(每隻螢火蟲代表一個解,在實際問題中這和性別是沒關係的,因此不必建模);
  • 吸引度與它們的亮度成正比,因此,對於任何兩隻閃爍的螢火蟲,較暗的那隻會朝着較亮的那隻移動。吸引力與亮度程度會隨着距離的增加而減小。最亮的螢火蟲會隨機選擇方向進行移動(規定了解的更新方式,較暗移向較亮的可以認爲是全局搜索,而最亮的進行隨機移動屬於局部搜索);
  • 螢火蟲的亮度可受目標函數影響或決定,對於最大化問題,亮度可以簡單地與目標函數值成正比(建立了算法與領域問題的關係,規定了如何將目標值表示亮度)。

22.2 亮度和吸引度

在螢火蟲算法中,有兩個重要的問題:光強的變化和吸引度的形成。爲了簡單起見,我們總是可以假設螢火蟲的吸引度是由它的亮度決定的,而亮度又與目標函數相關。

  對於最大化優化問題,螢火蟲在某一位置x的亮度I可以設定與目標函數 f(x)f(\mathbf{x}) 成正比,即 I(x)f(x)I(\mathbf{x}) \propto f(\mathbf{x})。但是吸引度是相對的,它應該是由其他螢火蟲所感受或斷定的,因此該值會因螢火蟲i和螢火蟲j之間的距離 rijr_{i j} 不同而不同,同光強一樣,也會收到空氣吸收程度的影響。光強I(r)的變化遵循平方反比定律 I(r)=Is/r2I(r)=I_{s} / r^{2}IsI_{s} 爲光源處的強度。對於給定的具有固定光吸收係數 γ\gamma 的介質(如空氣),光強I與距離r有關,即 I=I0eγrI=I_{0} e^{-\gamma r}I0I_{0} 爲原始光強。爲了避免 Is/r2I_{s} / r^{2}r=0 時除以 0,採用一種考慮平方反比律和吸收的綜合近似表達方式:
I(r)=I0eγr2(1)I(r)=I_{0} e^{-\gamma r^{2}}\tag{1}
如果希望函數單調遞減的速度慢一點,則可以使用下式:
I(r)=I01+γr2(2)I(r)=\frac{I_{0}}{1+\gamma r^{2}}\tag{2}
有了光強接下來就可以定義吸引度 β\beta 了,由於螢火蟲的吸引度正比於光強,所以有:
β(r)=β0eγr2(3)\beta(r)=\beta_{0} e^{-\gamma r^{2}}\tag{3}
β0\beta_{0}r=0 處的吸引度。
在具體實現中,吸引度函數 β(r)\beta(r) 可以是任意形式的單調遞減函數:
β(r)=β0eγrm,(m1)(4)\beta(r)=\beta_{0} e^{-\gamma r^{m}}, \quad(m \geq 1)\tag{4}

22.3 距離和移動

任意兩隻螢火蟲ij在其各自位置 Xi\mathbf{X}_{i}Xj\mathbf{X}_{j} 上的距離爲笛卡爾距離:
rij=xixj=k=1d(xi,kxj,k)2(5)r_{i j}=\left\|\mathbf{x}_{i}-\mathbf{x}_{j}\right\|=\sqrt{\sum_{k=1}^{d}\left(x_{i, k}-x_{j, k}\right)^{2}}\tag{5}
其中 xi,kx_{i, k} 爲第i只螢火蟲空間座標 Xi\mathbf{X}_{i} 的第k維座標值。對於二維情況,rij=(xixj)2+(yiyj)2r_{i j}=\sqrt{\left(x_{i}-x_{j}\right)^{2}+\left(y_{i}-y_{j}\right)^{2}}

  螢火蟲i會向着比它更亮的其他螢火蟲j的方向移動:
xi=xi+β0eγrij2(xjxi)+α(rand12)(6)\mathbf{x}_{i}=\mathbf{x}_{i}+\beta_{0} e^{-\gamma r_{i j}^{2}}\left(\mathbf{x}_{j}-\mathbf{x}_{i}\right)+\alpha\left(\mathrm{rand}-\frac{1}{2}\right)\tag{6}
式中第二項刻畫了吸引度的作用,第三項爲隨機擾動項,α\alpha 爲步長,rand 爲[0,1]之間均勻分佈的隨機數。在絕大數應用中,可以設定 β0=1\beta_{0}=1,α[0,1]\alpha \in[0,1],當然也可以設定隨機項服從正太分佈N(0,1)或其他分佈。此外,如果數值範圍在不同維度上相差很大,如在一個維度上範圍爲 105-10^{5}10510^{5},另一個維度上範圍爲[-0.001,0.01],需要首先根據領域問題的實際取值範圍確定各個維度上的縮放係數 Sk(k=1,,d)S_{k}(k=1, \ldots, d)然後使用 αSk\alpha S_{k} 代替 α\alpha

22.4 算法和參數分析

總體來說螢火蟲算法原理很簡單,參數也不多,這也是其收斂速度快、性能穩定的原因。下面按照式(6)從左往右的順序一一分析一下這些參數對算法的影響。

  β0\beta_{0}:初始吸引度,也就是自身對自身的吸引度,該值通常設爲 1。爲什麼呢?我們先將隨機項忽略,我們將式(6)整理以下,可以得到:
xi=(1β0eγrij2)xi+β0eγrij2xj(7)\mathbf{x}_{i}=(1-\beta_{0} e^{-\gamma r_{i j}^{2}})\mathbf{x}_{i}+\beta_{0} e^{-\gamma r_{i j}^{2}}\mathbf{x}_{j}\tag{7}
沒有發現這個等式很熟悉嗎?它實際上表達的就是在 Xi\mathbf{X}_{i}Xj\mathbf{X}_{j} 連線上中間的一個點,設定 β0=1\beta_{0}=1一方面如果 Xi\mathbf{X}_{i}Xj\mathbf{X}_{j} 是同一個點,那麼得到的新點仍爲 Xi\mathbf{X}_{i},另一方面如果不是同一個點,那麼將確保新點爲兩點之間連線上的點

  γ\gamma:吸引度衰減參數,其取值範圍爲[0,+∞)。如果取 0,則吸引度爲常量 β=β0\beta=\beta_{0},這等於說光強不會隨着距離衰減,那麼螢火蟲就可以被其他任意位置的螢火蟲發現,所有的螢火蟲都會朝着最亮的螢火蟲靠近,從而很快收斂到一個最優值,這和只關注全局最優解的 PSO 有些類似;如果取值爲+∞,那麼 β\beta ->0,意味着在其他螢火蟲眼中吸引度幾乎爲 0,那麼螢火蟲就是短視的,相當於,火蟲是閉上眼睛隨機移動的,這時進行的就是純隨機搜索。由於 β\beta 取值一般介於這兩個極端之間(常取值爲 γ=1/L\gamma=1 / \sqrt{L}L爲領域問題各個維度的平均範圍),因此常能得到比 PSO 和隨機搜索更好的結果。

  α\alpha:控制隨機擾動的步長。該值不能太大,否則會導致算法出現震盪,無法收斂,也不能太小,否則無法進行有效的局部搜索。一般情況下,α=0.01L\alpha=0.01L。也可以設置自適應的 α\alpha,通過迭代次數來更新其值:
αt=α0δt,0<δ<1)(8)\left.\alpha_{t}=\alpha_{0} \delta^{t}, \quad 0<\delta<1\right)\tag{8}
其中 α0\alpha_{0} 爲初始隨機縮放因子,δ\delta 爲冷卻因子,通常 α0=0.01L\alpha_{0}=0.01Lδ\delta=0.95~0.97

完整的 FA 算法如下圖所示。

圖1 FA僞代碼

FA 算法有兩個遍歷種羣n的內循環,一個迭代次數t的外循環,因此算法在最壞情況下複雜度爲 O(n2t)O\left(n^{2} t\right),由於n很小(通常爲n = 40),而t很大(比如t = 5000),計算成本相對較低,因爲算法複雜度與t呈線性關係,主要的計算成本將在目標函數的評估上。如果 n 相對較大,則可以使用一個內部循環,通過排序算法對所有螢火蟲的吸引力或亮度進行排序。在這種情況下,FA 算法的複雜度爲 O(ntlog(n))O(n t \log (n))

  那麼爲什麼 FA 看起來簡單卻這麼有效呢? 首先,FA 是羣體智能算法,因此它具備羣體智能算法所有的優點;其次,FA 基於吸引度,吸引度隨着距離增加而降低,所以如果兩個螢火蟲離的很遠,較亮的螢火蟲不會將較暗的螢火蟲吸引過去,這導致了這樣一個事實,即整個種羣可以自動細分爲子羣體,每個子羣體可以圍繞每個模態或局部最優,在這些局部最優中可以找到全局最優解;最後,如果種羣規模比模態數多得多,這種細分使螢火蟲能夠同時找到所有極值。

22.5 代碼講解

瞭解了以上算法原理後,下面基於 Java1.8 採用面向對象方法進行代碼編碼。完整代碼可以點擊“這裏”查看, 包括 java 項目和算法原作者編寫的 matlab 代碼

  首先確定領域對象,包括螢火蟲 Firefly、目標函數 ObjectiveFun、位置 Position 和範圍 Range。

  其次實現算法類,由於 FA 存在多種改進版本,爲了表示不同的算法內容,這裏定義了算法接口,通過對其進行不同的實現建立不同的算法。

  最後針對具體優化問題進行了測試

  下面結合代碼具體說一下算法執行流程。

  1. 初始參數設置。主要設置種羣數量 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();
  1. 種羣初始化。根據設定的種羣大小,建立相應數量的螢火蟲,在目標函數自變量的取值範圍內隨機確定每個螢火蟲的位置;
@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())));
  }
}
  1. 對初始種羣進行亮度計算,即目標評估。對於最大化問題,評估過程中直接將目標作爲亮度,對於最小化問題,則對目標值取相反數或倒數。之後再對螢火蟲按照亮度進行排序,以確定當前種羣中最優的個體。
@Override
public void calcuLight() {
  // TODO Auto-generated method stub
  for (Firefly firefly : fireflies) {
    firefly.setLight(this.getObjectiveFun().getObjValue((firefly.getPosition().getPositionCode())));
  }
  Collections.sort(fireflies);
  // 展示螢火蟲分佈
}
  1. 螢火蟲移動。亮度較低的螢火蟲飛向亮度較高的螢火蟲,亮度最亮的螢火蟲隨機移動。注意第 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. 更新迭代器。迭代次數加 1,同時如果設置了步長自適應,則步長進行進一步的縮放。
public void incrementIter() {
  iterator++;
  if (isAdaptive) {
    alpha *= delta;
  }
}
  1. 循環。循環執行步驟 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)

圖2 目標函數三位曲面圖

x、y 取值範圍均爲[-5,5],在此區間上該函數存在兩個全局最優解(0,0)和(0,-4),多次運行程序可得到這兩個近似最優解。

圖3 全局最優解(0,0)

圖4 全局最優解(0,-4)

最後看一下螢火蟲的是怎麼移動的,通過圖 5 可以看出,螢火蟲形成了不同的子種羣,各自收斂到一個局部最優解,這恰恰印證了之前關於 FA 有效性分析中的自動化分子種羣的描述

圖5 螢火蟲動態移動過程

22.6 FAQ 環節

Q1:步長爲什麼要減小,會不會影響收斂速度?

  A1:自適應步長 α\alpha 其實在算法參數那一部分已經介紹到了,那裏我提到說 α\alpha 不能太大也不能太小,但實際上我們更希望步長在算法初期具有更大的探索性,而在算法後期需要收斂的時候應該使得算法更加穩定,我們才設計步長是不斷自適應減小的,但是有一點值得注意,這個參數本質上和收斂速度關係不是很大,這個參數更多的是促進種羣的多樣性,因爲步長是控制隨機項的,隨機越大,種羣越多樣,那麼算法也就不太可能收斂。

  Q2:亮度重新賦值的目的?

  A2:說白了亮度就是目標函數值(最大化問題),所以在算法中螢火蟲一旦進行了移動,就需要對其重新進行評估,要不怎麼知道下一步往哪個更亮的方向移動呢?

  Q3:開源代碼有嗎?

  A3:點擊“這裏”查看 github 鏈接。

  Q4:在處理圖像數據時是否也應該使用自適應步長?

  A4:自適應步長是針對算法而言的,和優化問題本身沒關係,要說有關係那就是步長初始值的設定,通常領域問題不同,初始值設定就不同。

  Q5:算法原作者給出的 matlab 代碼是否 OK?

  A5:這段代碼思路上沒啥問題,只不過是針對具體的優化問題而編寫的,在解決自己的實際問題時,可能需要進行部分修改。

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