一.簡介:
有今天這篇博客是因為最近在做一個lua版的象棋游戲(忽略lua效率不高這件事),在做游戲的PVE功能的過程中需要實現一個簡單的象棋AI,於是對於象棋AI進行了一番研究,研究的主要資料來源於象棋巫師。下面的內容也主要是對於最大最小算法和AlphaBeta算法理解的一個記錄。對於棋類AI,目前沒有深入了解的打算,只要能實現我的游戲的基礎功能即可,而且我接觸棋類AI的實踐也才幾天,所以這篇博客也主要是記錄我自己的理解,難免會有一些理解不到位甚至理解錯誤的地方,如果您有幸看到了這些內容,您可以在評論區告知我,非常感謝。
我剛開始試圖理解算法時是直接看的最大最小算法的部分,說實話,看得是很懵的,直到我看到了評價函數,才將之前看到的算法實現給串聯起來。我想我之所以直接看算法很困難,是搞錯了順序,所以我也試圖按照我理解的順序去組織這篇文章。這篇文章主要按照評價函數--博弈樹--最小最大算法--AlphaBeta算法的順序展開。
二.評價函數
首先我們談到評價函數,因為這是棋類游戲AI的核心部分。我在我前兩天的博文中大概梳理了一下中國象棋中的評價函數(中國象棋評估函數建模),您可以作為參考,因為我對這個評價函數做了很多簡化。這里我們主要強調評估函數是什么,以及它的作用。
評估函數是評估什么的呢?現在我們可以將自己代入為一個象棋AI,現在該我下棋了,我想知道我該怎么下。作為一個AI,在程序設定的象棋規則內,我現在能夠找到很多種不同的着法,那么該選擇哪一種着法呢?很容易想到我可以給不同的着法進行評分,使用數學模型去量化這個着法的好壞,這樣作為一個AI,我就可以通過比較這個量化的結果來比較不同的着法,找到其中最好的着法。這個量化着法好壞的數學模型在程序中使用一個函數去實現它,這個函數就是我們的評價函數。評價函數應該就是AI程序的核心內容,因為它使得AI可以評判不同決策的優劣,評價着法的數學模型越精確,AI也就表現得越“聰明”。
那么評價函數該評價哪些方面呢?一個顯然的事情是,在棋類游戲中我們的一個着法的好壞不是取決於這個着法本身,同樣的一步棋因為在不同的局面中導致的結果一定是不一樣的,所以我們評判的應該是在下完棋后的整體棋局雙方的優勢程度,選擇各種着法中完成后對AI方最優勢的棋盤格局。在象棋中,一般從雙方棋子的數量、位置、靈活度、相互牽制等方面去評判當前雙方的優劣,利用這些衡量標准去建立我們的數學模型並用程序實現它就有了我們的評價函數。
三.博弈樹(搜索樹)
剛才在談到評價函數時,我們說可以讓計算機評價每一種着法的優劣,選擇其中最有利於AI方的着法,這就可以視作一個深度為1的博弈樹模型。下圖是一個博弈樹(圖片來自於象棋巫師的文章):
這是一個井字棋的博弈樹,可以看到博弈樹本質就是使用樹形結構去描述接下來選擇着法的不同棋盤可能性,每一種可能性的棋盤都是一個節點,未落子前的棋盤作為根節點,由其經過一步着法產生的棋盤各種棋盤布局作為其子節點,這一層深度為1,接下來又會衍生出深度為2的各種子節點,以此類推。理論上,隨着深度的增加,子節點的數量是指數級增加的。對於博弈樹,我們可以總結出以下規則:
1.奇數層的節點是由AI着棋后產生的;2.偶數層的節點是由玩家着棋后產生的;3.如果某個節點是葉子節點(沒有子節點),那么這個節點的棋盤布局應該是有一方獲勝或是判定為和棋。
四.最小最大算法
我們看到對於棋盤可能的發展,我們使用了博弈樹去描述它,而且在實際搜索下一步的着法時,我們不希望AI只是根據一步棋后的棋盤布局就決定(搜索深度為1),因為當下可能一些着法不能立即帶來收益,但是在多輪后會帶來更大的收益,這種情況在棋類游戲中經常出現,所以我們希望AI能多看幾步(加深搜索深度),這時就需要去模擬雙方博弈的過程:
如果我們的評價函數的返回值越大代表棋盤局面AI優勢越大,返回值越小代表玩家優勢越大,那么在博弈樹的奇數層(AI着棋),AI就會希望最后的結果越大越好,而在博弈樹的偶數層(玩家着棋),我們會假設玩家也是理性的,那么玩家則一定會傾向於評價函數的返回值小的棋局結果,這便是最大最小的名稱來源,在搜索時根據深度的不同應該選擇最大或者最小的結果作為AI或玩家的着棋預測。下面是一個簡單的搜索樹,我們使用它來詳細分析以下如何進行最大最小搜索:
可以看到A節點是根節點,有3個子節點,這3個子節點又分別有3個子節點,這些子節點的子節點的棋盤布局評價值如圖中所示。這個搜索樹最下面一層節點並非是葉子節點,還是有子節點的,只是我們沒有畫。這是一個搜索深度為2的搜索樹,這個樹中我們沒有計算每一個節點的評價值,只是計算了搜索深度節點的評價值。我們可以做出這樣的推導,現在AI想要在兩步后棋局達到最優,那么顯然應該是24,但是玩家會讓AI如願嗎?顯然不會,因為搜索深度為2的這一層節點是由玩家選擇着法形成的,所以這一層玩家應該會選擇最小值,也就是說玩家會選擇8、6、5這三個節點對應着法中的一種,那么我們就將這3個節點值作為B、C、D節點的評價值(B、C、D的深度值不用計算),顯然AI繼續評價A節點的着法時應該選擇B、C、D節點中的最大值8,也就是B節點,這樣AI獲得的預期收益最大。相信你也看出來了,對於一個規定了深度的搜索樹來說,我們只需要計算最后一層節點或葉子節點的評估值,上層節點的評估值無需計算由最后一層節點依次取得最大值或最小值向上類推確定,取最大還是最小由當前層是玩家決定着法還是AI決定着法來確定,這樣我們就合理地預估了在規定步驟(深度)AI想取得預期最大收益雙方的着法選擇。
五.負值最大函數--最大最小搜索的優化
我們每一次確定上一層的節點的評估值時都需要根據當前節點層數確定取最大值還是最小值,我們可以使用每次將計算出的評估值取相反數的方式來簡化邏輯,這樣我們就可以每一層節點都取最大值了。
六.Alpha-Beta搜索算法
最大最小算法有一個明顯的問題,就是我們的節點產生是指數增長的,相應的運行評估函數的次數也是指數增長。最后一層節點的節點數可以用m的n次方來確定,n是搜索深度,m是當前游戲平均每一步的可能着法數,像國際象棋每一步就平均有35種可能着法,35的n次方增大的速度太快了,搜索深度不可能太深,所以我們需要簡化不必要的節點評估值計算,也就是簡化搜索樹,AlphaBeta算法就可以理解為一種簡化后的最大最小搜索。下圖是一個部分搜索樹示意,我們可以梳理以下是如何簡化的:
可以看到,F、G、H、I節點是由玩家確定的着法產生的,而接下來由AI確定着法了,當我們計算完F的各種着法的評價值后我們知道如果走到了F這一步,那么我們應該會選擇12這一步的着法(選出最大的),所以F的評價值為12,然后繼續搜索G節點,發現G節點的第一種着法的評價值就是15,也就是說G節點的評價值至少是15,但是F、G、H、I這四個節點中玩家大概率會選擇最小值,那么玩家就不會選擇G節點,既然G節點最終會被拋棄(B節點的評價值不會由G節點產生),那么G節點下其他的着法的評價值也就沒有必要計算了。12這個值就可以暫時作為我們的Beta值,也就是F、G、H、I節點中目前找到的玩家可以選擇的最壞結果,一旦F、G、H、I中某個子節點的評價值高於12,就舍棄整個節點,而F、G、H、I中一旦某個評價值比Beta值12低,就將Beta值更新(這個Beta值就是最后B節點的評價值)。換言之,當玩家發現選擇G節點會讓AI獲得至少15的評價值時,是不會讓AI如願的。在評估F、G、H、I這四個最大值節點的過程中,Beta值作為AI會獲得的上限值存在,如果某個評價值高於Beta,就可以舍棄掉節點下的其他評價值計算,直接返回這個評價值,正所謂在最壞的中選最好的,F已經如此壞(12)了,G可能更壞(至少15)嗎?同樣地,在最小值節點地評價值計算過程中也應該會有一個評價值的下限(圖片來自於:最清晰易懂的MinMax算法和Alpha-Beta剪枝詳解):
如圖,圓圈是最小值節點,方塊是最大值節點,從左下角開始看,當我們首先遍歷得到最小值節點H的值為3時,接下來最大值節點D的值應該至少是3,此時再遍歷H節點的兄弟節點I的子節點,當遍歷到第一個子節點值為2時,說明I的節點值最多是2(I是最小值節點,其他比2大的子節點值不會取),這時實際上H節點就確定了一個下限3,當I節點的子節點值小於下限3時,就可以直接將這個值(2)作為I節點的值返回,因為I節點反正也會被舍去,所以究竟有多小也就無所謂了。這個下限就是Alpha值。換言之,當AI發現玩家可能取得2這個更小的評價值時,是不會讓AI如願的,所以AI也就舍棄了I節點。
總結:1.Alpha-Beta算法更多被叫做Alpha-Beta剪枝算法,它還是基於最大最小算法的,是對最大最小算法的運算優化,只是去除了其中大量的多余運算。
2.在評估最大值節點下的幾個最小值子節點的過程中,可以計算完一個子節點后合理地確定一個上限Beta,當其他兄弟節點的子節點值高於Beta時就直接將子節點值作為兄弟節點值返回,舍去其他兄弟節點的子節點運算,如果低於上限Beta就更新上限Beta;同樣地,在評估最小值節點下的幾個最大值子節點時,可以計算完一個子節點后確定一個下限Alpha,當其他兄弟節點的子節點值低於Alpha時舍去整個兄弟節點值得計算,當高於Alpha時更新Alpha。
3.因為所有得節點值都會匯總到根節點,所以這個上限值Beta和下限值Alpha是全局通用得,也就是說全局只需要確定一組Alpha值和Beta值即可,最后計算得到得根節點值一定是Alpha值或是Beta值