如何寫一個象棋 AI (二)

本文代碼:github.com/ParadeTo/ch…node

Demo地址:www.paradeto.com/chinese-che…python

上一篇文章結尾提到了最大最小值算法有進一步優化的空間,接下來繼續。git

alpha-beta 剪枝

上篇文章結尾提到了當最大最小值算法的搜索深度增大時,其計算複雜度會急劇上升。若是有辦法能夠去掉一些分支的話,算法的效率就會高一些了,這就輪到 alpha-beta 剪枝出場了。不過咱們先不討論該算法,咱們先玩一下上一篇的那個遊戲。github

上篇文章只是大概說了一下游戲的玩法,這裏咱們一步一步來認真玩一下。算法

有兩個袋子,袋子一中有一臺小米9和一臺華爲p30,袋子二中有一顆螺絲,一粒芝麻,一臺 Mac Pro,可是你和你朋友都不知道袋子裏的是什麼,遊戲規則是你選擇一個袋子,你朋友從中選擇一個物品給你,你想要獲得價值最大的物品,而你朋友則想方設法地要阻擾你(這樣的朋友仍是早點棄了的好)。兩我的都盲選的話就沒意思了,因此你和你朋友商量了一下,每一個人均可以儘量的嘗試屢次之後再作出最終的選擇。遊戲開始:多線程

你:選擇袋子一。post

你朋友:從袋子裏面拿出第一個物品,發現是華爲p30。暫時決定:若是你選擇袋子一,就給你華爲p30。放回。優化

你朋友:從袋子裏面拿出第二個物品,發現是小米9。跟華爲p30比較後暫時決定:若是你選擇袋子一,就給你小米9。放回。spa

你朋友:發現袋子中沒有其餘物品。作出最後決定:若是你選擇袋子一,就給你小米9。線程

你:記錄下能獲得的物品暫時爲小米9。

你:選擇袋子二。

你朋友:從袋子裏面拿出第一個物品,發現是一顆螺絲。跟你暫時能獲得的物品小米9比較後,你朋友決定直接告訴你:若是你選擇袋子二,就給你一顆螺絲。爲何這裏能夠省去比較袋子二中的其餘物品呢,明明一粒芝麻的價值更小啊?由於袋子二中的第一個物品的價值就已經小於你暫時能夠獲得的物品的價值了,這個信息已經足夠讓你放棄選擇袋子二了。

通過這些嘗試之後,最後你選擇了袋子一,而後你朋友把小米9給了你。

上面這個過程其實就是最大最小值算法加 alpha-beta 剪枝。其中選擇第二個袋子時你朋友決定放棄袋子中其餘物品的嘗試就體現了 alpha-beta 剪枝的思想。下面咱們來講說 alpha-beta 剪枝。

alpha-beta 剪枝算法中咱們定義:

  • alpha 記錄爲最大值層節點當前所能獲得的最大分數
  • beta 記錄爲最小值層節點當前所能獲得的最小分數

當最小值層的某個節點的 beta 小於 alpha 時,能夠中止該節點其他子節點的搜索。當最大值層的某個節點的 alpha 大於 beta 時,能夠中止該節點其他子節點的搜索。

這麼說確定聽不懂,下面就用下圖例子來實戰一下。

  1. 初始化根節點 alpha 爲 -∞,beta 爲 +∞,而後經過路徑一路傳遞到倒數第二排左邊的節點。

  1. 遍歷第一個子節點,更新 alpha 爲 4,更新當前節點值爲 4。判斷是否須要剪枝(比較當前節點的 alpha 和 beta,發現 4 小於 +∞,不能剪枝)。

  1. 遍歷第二個子節點,更新 alpha 爲 6,更新當前節點值爲 6。判斷是否須要剪枝(比較當前節點的 alpha 和 beta,發現 6 小於 +∞,不能剪枝)。

  1. 返回 6 到上一層,更新最小值層左邊第一個節點的 beta 爲 6,更新當前節點值爲 6。判斷是否須要剪枝(比較當前節點的 beta 和 alpha,發現 6 大於 -∞,不能剪枝)。

  1. 將 alpha=-∞,beta=6 傳給右邊的子節點,繼續遍歷 7 所在的節點,更新 alpha 爲 7,值爲7。判斷是否須要剪枝(比較當前節點的 beta 和 alpha,發現 6 小於於 7,知足剪枝條件。

  1. 返回 7 到上一層,無需更新。

接下來就不一一贅述了,讀者能夠本身試試把剩下的完成,最後的結果是這樣的:

結合代碼看能夠更好的理解:

function alphabeta(node, depth, α, β, maximizingPlayer) is
    if depth = 0 or node is a terminal node then
        return the heuristic value of node
    if maximizingPlayer then
        value := −∞
        for each child of node do
            value := max(value, alphabeta(child, depth − 1, α, β, FALSE))
            α := max(α, value)
            if α ≥ β then
                break (* β cut-off *)
        return value
    else
        value := +∞
        for each child of node do
            value := min(value, alphabeta(child, depth − 1, α, β, TRUE))
            β := min(β, value)
            if α ≥ β then
                break (* α cut-off *)
        return value
複製代碼

這就是 alpha-beta 剪枝的規則,別看只是減去了幾個分支不計算而已,若是每層每一個節點均可以排除掉幾個分支的話,對速度的優化仍是很是明顯的。

並行搜索

另一個優化的思路是並行計算,把每次產生的走法平均分紅多個任務並行處理,每一個並行的任務分別產生局部的最優解,最後彙總獲得全局的最優解便可。每個走法對應一個子任務是最快的,不過若是每一層都這樣的話,最後的子任務數量也會很是巨大,不論是多進程仍是多線程實現都是很不現實的,因此須要限制並行處理的深度。即使僅在第一層開啓並行計算,理論上也可使得計算速度快 30 倍(假設每一層平均產生 30 種走法)。

總結

利用最大最小值和 alpha-beta 算法就能夠實現簡單的象棋 AI 程序,不過該 AI 的棋力僅夠應付入門級的玩家。一個緣由是儘管採起了前面所說的優化方法後,實踐發現當搜索深度達到 5 之後,算法的計算時間就慢得不能接受了,沒法繼續提升搜索的深度;另一個緣由是局勢判斷的方法略顯簡單,能夠考慮加入一些象棋特有的「套路」來提升局勢判斷的準確性。後面針對這些問題再優化一下。

參考

  1. Alpha–beta pruning
相關文章
相關標籤/搜索