UP | HOME

基因演算法

基因演算法是一個通用的框架,指涉流程有 selection、crossover、mutation、reinsertion 組成的進化迴圈的一系列演算法。

1. 流程

其中 selection、crossover、mutation、reinsertion 等等都是可以替換的超參數。一個進化迴圈可以用以下虛擬碼替代

def evolve(population, problem, generation) do
    population = evaluate(population, &problem.fitness_function/1, opts)
  best = hd(population)
    if problem.terminate?(population, generation) do
    best
  else
    {parents, leftover} = select(population)
    children = crossover(parents)
    mutants = mutation(population)
    offspring = children ++ mutants
    new_population = reinsertion(parents, offspring, leftover)
    evolve(new_population, problem, generation + 1)
  end
end

evaluate 比較無聊,就是評估當前 population 中每個 chromosome 並排序而已,chromosome 跟 population 的概念如下所示

  1. chromosome 個體,表示問題的解法,用一個叫做 genes 的序列編碼表示
  2. population 全體,表示當前代的所有解法,也可以簡單的說是 List of chromosome

有了上面的資訊,我們就可以進一步來了解 selection、crossover、mutation、reinsertion 是怎麼操作 population 的了。

1.1. selection

首先,selection 是用來選擇要拿去 crossover 的 parents。其中最常用的策略是選最好的 n 個,可以定義成

def elite(population, n) do
  population
  |> Enum.take(n)
end

而這個策略會搭配一個超參數叫做 selection rate,表示會選擇整個 population 中的多少部分出來做 parents,比如 0.8 表示 80% 的 population

1.2. crossover

crossover 會把兩個被當成 parents 的 chromosome 中的 genes 進行交換,就像 selection,它也有很多可行的策略可以替換,比如隨意選擇一點裁斷進行互換:

def single_point(p1, p2) do
  cx_point = :rand.uniform(p1.size)
  {p1_head, p1_tail} = Enum.split(p1.genes, cx_point)
  {p2_head, p2_tail} = Enum.split(p2.genes, cx_point)
  {c1, c2} = {p1_head ++ p2_tail, p2_head ++ p1_tail}

  {%Chromosome{genes: c1, size: length(c1)}, %Chromosome{genes: c2, size: length(c2)}}
end

或是根據一定機率決定要不要互換一個基因

def uniform(p1, p2, rate) do
  {c1, c2} =
    p1.genes
    |> Enum.zip(p2.genes)
    |> Enum.map(fn {x, y} ->
      if :rand.uniform() < rate do
        {x, y}
      else
        {y, x}
      end
    end)
    |> Enum.unzip()

  {%Chromosome{genes: c1, size: length(c1)}, %Chromosome{genes: c2, size: length(c2)}}
end

要是剛好解法編碼是浮點數的話,還可以選擇具備權重特性的策略

def whole_arithmetic(p1, p2, alpha) do
  {c1, c2} =
    p1.genes
    |> Enum.zip(p2.genes)
    |> Enum.map(fn {x, y} ->
      {
        x * alpha + y * (1 - alpha),
        x * (1 - alpha) + y * alpha
      }
    end)
    |> Enum.unzip()

  {%Chromosome{genes: c1, size: length(c1)}, %Chromosome{genes: c2, size: length(c2)}}
end

這個很容易就能看出來說交換的方式就是按照權重去分配,假設 alpha 大,就是自己的資訊為主,反之則是對方為主。

1.3. mutation

mutation 這個過程是選出一小部分(通常是 5%)的 chromosome 進行一些修改,就好像自然界的隨機變異,這個方法可以避免徹底陷入搜尋空間的局部中沒辦法離開。 常用的策略有 flip bit

def flip(chromosome) do
  genes =
    chromosome.genes
    |> Enum.map(&Bitwise.bxor(&1, 1))

  %Chromosome{genes: genes, size: chromosome.size}
end

重排等等

def scramble(chromosome) do
  genes =
    chromosome.genes
    |> Enum.shuffle()

  %Chromosome{genes: genes, size: chromosome.size}
end

這個部分的最終結果,是生出所謂的 chlidren

1.4. reinsertion

現在,我們有 parents, chlidren, leftover, mutants 等變數,chlidren 跟 mutants 構成 offspring,reinsertion 的任務就是適當的組合這些東西變成新的 population。最常見的策略是選最好的個體出來

def elitist(parents, offspring, leftover, survival_rate) do
  old = parents ++ leftover
  n = floor(length(old) * survival_rate)

  survivors =
    old
    |> Enum.sort_by(& &1.fitness, &>=/2)
    |> Enum.take(n)

  offspring ++ survivors
end

這一步結束之後就會進入新一輪的進化

1.5. termination criteria

最後,每個問題都應該定義自己的終止條件,常用的幾種是

  1. 達到最佳結果
  2. 代數 generation 已經達到一定值
  3. 不再產生更好的解(可以比較當前的最佳解與之前最佳解的連續變化率得知)
  4. 超過公司預算
  5. 你覺得等夠久了

或是以上條件的組合等等。

2. 結論

有了上面的描述,我想你已經對基因演算法有基本的了解,要更進一步的學習編寫可以參考《Genetic Algorithms in Elixir: Solve problems using Evolution》。而要是想要在實際場域中採用,我想就需要更多的練習跟參考別人的實驗了。

3. 輔助程式

問題的定義

defmodule Problem do
  alias Types.Chromosome

  @type t :: module()

  @callback genotype :: Chromosome.t()
  @callback fitness_function(Chromosome.t()) :: number()
  @callback terminate?(Enum.t(), integer(), integer()) :: boolean()
end

個體的定義

defmodule Types.Chromosome do
  @type t :: %__MODULE__{
          genes: Enum.t(),
          size: integer(),
          fitness: number(),
          age: integer()
        }

  @enforce_keys :genes
  defstruct [:genes, size: 0, fitness: 0, age: 0]
end
Date: 2023-01-31 Tue 00:00
Author: Lîm Tsú-thuàn