跳到主要內容

002互動

奇幻的神經網路進化之旅:讓 50 隻小鳥自己學會 Flappy Bird

沒有人教這些小鳥怎麼飛。給牠們一顆只有六個權重的大腦,加上優勝劣汰,幾十代之後牠們就能優雅地穿過每一根管子。

發布
閱讀時間
5 分鐘

當你看到螢幕上那些小鳥神奇地穿越障礙物時,也許會想:「這些賽博小鳥是怎麼變得這麼聰明的?」今天就讓我們一起揭開 Flappy Bird 背後的神秘面紗,探索那個讓小鳥從笨拙到優雅飛翔的進化之旅。

下面的五十隻小鳥現在就在你的瀏覽器裡進化。先看牠們撞牆撞個幾代,然後把速度調快。

fig 01/neuroevolution / flappy
世代
1
存活
50
通過管數
0
最高紀錄
0

領頭鳥的大腦

鳥的高度開口高度拍翅膀?0.50 不拍

正權重 負權重

每一代通過的管數(100 根就畢業,換下一代)

第一代還沒結束

速度
實心的那隻是目前還活著的領頭鳥,淡淡的一團是其他同伴。右邊是領頭鳥的整顆大腦:線的顏色是權重的正負,粗細是大小,節點越亮代表當下的輸出越強。下方的折線是每一代通過了幾根管子;飛過 100 根就算畢業,換下一代上場。

什麼是神經進化演算法?

如果你不曾聽過「神經進化演算法」(Neuroevolution),別擔心。它聽起來像是科幻電影裡的名詞,但實際上概念出奇簡單。

想像一下:

  • 神經網路:模仿人腦的電腦程式
  • 遺傳演算法:模仿達爾文進化論的優勝劣汰過程
  • 神經進化:將上面兩者結合在一起的絕妙點子

簡單說,就是讓一群「電子鳥腦」彼此競爭,然後讓表現最好的「鳥腦」傳遞基因到下一代。就像現實生物的進化一樣,但速度快了幾百萬倍!

鳥腦解剖學:簡易版

我們的 AI 鳥有一個極其簡單的「大腦」結構,就是上面儀器右邊畫的那張圖:

  • 輸入:只有兩個神經元,分別接收鳥的高度和下一個開口的高度
  • 隱藏層:有兩個神經元進行數據處理
  • 輸出:一個神經元,超過 0.5 就拍動翅膀

就這樣!總共六個權重,沒有什麼深度學習,沒有複雜的卷積。但別小看這個「鳥腦」,它能創造奇蹟!

從菜鳥到鳥霸王:進化的秘訣

第一步:隨機大亂鬥

content/posts/ai-flappy-bird/components/world.ts
// 初始化 50 隻完全隨機的鳥
const population = new Population({
  shape: [2, 2, 1], // 2 個輸入、2 個隱藏神經元、1 個輸出
  size: 50,
});

最初,所有的鳥都是「菜鳥」,完全隨機行動。就像把 50 個初學者丟進鋼管舞教室,然後看誰能不摔斷腿堅持最久。大部分會悲慘撞牆,但總會有幾隻運氣好一點的。

第二步:優生學(不是邪惡的那種)

lib/ml/neuroevolution.ts
elitism: 0.2,    // 保留成績最好的 20%
randomRate: 0.2, // 讓 20% 完全隨機(防止陷入局部最優)
                 // 其餘 60% 是優秀鳥的後代

我們會做幾件事:

  • 保留表現最好的鳥(菁英主義萬歲!)
  • 引入一些全新的隨機鳥(新鮮血液!)
  • 讓菁英鳥「交配」產生後代(不要想太多…只是混合牠們的神經權重)

第三步:突變基因,創造未來

lib/ml/neuroevolution.ts
mutationRate: 0.1,  // 每個權重有 10% 的機率發生突變
mutationRange: 0.5, // 突變幅度最大為 ±0.5

為了避免「近親繁殖」問題,我們會隨機改變一些神經連接的權重。這就像是自然界的基因突變,有時候會創造出超能力,有時候…嗯,就是災難。

AI 鳥類行為學:奇怪但有效的策略

經過幾十代進化後,你會發現這些 AI 鳥發展出一些有趣的行為模式:

  • 驚慌失措鳥:總是在最後一刻才跳,看起來像是有嚴重的拖延症。
  • 超級保守鳥:保持在畫面中央,就像一個不願冒險的會計師。
  • 節奏大師鳥:發展出幾乎有音樂感的律動,讓人不禁想放些背景音樂。
  • 作弊鳥?:有時候看起來牠們找到了遊戲的漏洞,但實際上只是演算法最佳化的結果。

為什麼這麼簡單的東西能有效?

你可能會問:「真的這麼簡單就能讓 AI 學會玩遊戲?」

答案是:是的!因為:

  • 問題很單純:Flappy Bird 是一個狀態簡單、動作有限的遊戲。
  • 大量嘗試錯誤:電腦可以在短時間內嘗試數千次,比你自己練習幾百小時還有效。
  • 適者生存:這是自然界最古老的學習法則之一,就算應用在虛擬鳥身上也一樣有效。

不過進化不保證成功。 如果你看到分數一直上不去,按「重新開始」換一批祖先就好。

技術實現的獨特之處

lib/ml/neuroevolution.ts
private breed(a: Float32Array, b: Float32Array): Float32Array {
  const child = Float32Array.from(a); // 複製第一個親代的基因
  for (let i = 0; i < child.length; i++) {
    if (rng() <= 0.5) child[i] = b[i]; // 基因交叉:50% 機率採用第二個親代的基因
    if (rng() <= mutationRate) child[i] += rng() * mutationRange * 2 - mutationRange; // 隨機突變
  }
  return child;
}

這段程式碼是整個系統的「DNA 實驗室」,負責將兩個成功的神經網路「基因」混合並產生下一代。這就像是把兩隻聰明鳥的「腦細胞連接方式」攪拌在一起,再加點突變調味料。

結論:AI 與達爾文的共舞

下次當你看到那些小鳥優雅地穿越管道時,請記住:牠們不是被編程要這樣做的,而是「學會」了這樣做。通過無數代的失敗、學習和進化,從隨機行動到精確控制,這些虛擬生物展示了一個簡單而深刻的真理:

有時候,成功的秘訣不是聰明的設計,而是給予足夠的嘗試機會和適應環境的能力。

如果一群像素鳥能通過簡單的進化演算法學會玩遊戲,那麼我們人類是否也是通過類似的過程學會我們所有技能的呢?也許我們每個人都是自己基因和經驗的神經進化實驗品…

好了,深度思考時間結束。回到上面把速度調到「最快」,讓我們看看幾代之後那些小傢伙能有多厲害!

技術補充

如果你對神經進化演算法背後的數學和更多技術細節感興趣,可以參考以下資源:


這篇文章最初於 2025 年 3 月發表在我的舊站。搬到這本筆記時,互動元件用 TypeScript 整個重寫了。