詳見本人的專案描述頁面:像黑白棋、五子棋、象棋、圍棋等棋類遊戲,是典型的零和博弈:
資訊完備,過程和結果都是確定性的,沒有隨機運氣成分(不同於大部分牌類遊戲);
雙方對弈,輪流行動;
此消彼長,對一方有利的即對另一方有害,量化後的雙方分數之和始終為0。
這類零和博弈,如果採用傳統的遞迴回溯演算法,其計算量是十分巨大的。如果對不必要計算的分支進行剪除,可以減少計算量。本文採用alpah beta剪枝演算法來求解。
關於alpha-beta剪枝演算法,網上的教程很多,不再贅述。可以參照:
其實這個演算法跟人類的下棋思路是接近一致的:
輪到我走,我肯定要走對我最有利的一步(但我不確定這一步對我是否有利);
如果我走這一步的話,對手肯定要走對我最不利的一步;
如果對手走了對我最不利的一步,我再走一步扳回優勢的一步;
…………
在演算過程中,那些我跟對手都不會走的「昏招」要排除掉;
假設我能算到第5步,我就知道第4步怎麼走最合適,進而知道第3步、第2步,最終決定接下來第1步怎麼走。
節點node:代表選擇出的最下層局面的評價(葉子節點值);
連線move:代表可選擇的招法;
深度depth:將底層設為0,向上遞增(當然也可以用頂層根節點為0,向下遞增),越深則越容易得出優質的解,但更耗費時間;
節點和節點下的連線(招法)處於同一層中;
招法是自上而下的,但節點評估是自下而上的,所以只能深度優先;
對葉子節點的評價值,有利於先手的為正數,有利於後手的為負數;
每個節點都有乙個alpha值和beta值,分別表示節點所在層可以最終選出的最大和最小評價值;
alpha初始設為-∞,beta初始設為+∞;
對於max層,不用計算beta值,因為先手不會選最小評價值;
同樣,對於min層,不用計算alpha值,因為後手不會選最大評價值;
對於max層的節點,取值是所有子節點中的最大值(即先手要選擇最有利於自己的局面);
同樣,對於min層的節點,取值是所有子節點中的最小值(即後手要選擇最不利於先手的局面);
max層的節點在遍歷子節點找alpha的時候,如果發現子節點中有大於beta值的節點,則放棄遍歷。因為它的父節點在min層,min層節點不會選擇乙個大於已有節點的節點(即對手不會選擇更有利於先手的局面),所有沒有必要繼續找下去,在此剪枝。這一步是剪枝演算法的核心!
同樣,min層的節點在遍歷子節點查詢beta的時候,如果發現子節點中有小於alpha值的節點,則放棄遍歷,在此剪枝。這一步同樣是剪枝演算法的核心!
簡單來講,alpha和beta值在初始化的時候,是父節點直接傳遞給子節點的;而子節點回傳時,min層節點將自己的beta值傳給父節點,而max層節點將自己的alpha值傳給父節點;min層父節點挑選回傳值中最小的乙個,跟當前層beta值比較,用較小值重新設定beta;max層父節點挑選回傳值中最大的乙個,跟當前層alpha值比較,用較大值重新設定alpha;如果任一節點發現當前alpha>beta,則放棄遍歷。
github上有個演算法過程演示,很容易理解:
上面提到的演算法,雙方評估函式相同(利於先手為正、利於後手為負),但區分max層和min層,在max層取alpha值,在min層取beta值,我們稱之為minimax搜尋。這種搜尋方法比較容易理解。
value = alphabetapruning(p, alpha, beta, !who); //取子節點的值另外有還有一種negamax搜尋方式,雙方評價函式不同(利於自己為正、利於對手為負),不區分maxx層和min層,全部取alpha值。這種搜尋方法寫起來更簡潔,網上很多alpha-beta剪枝演算法採用它,但它需要修改評價函式,使評價函式根據當前選手來定(先手不變,後手取反)。
value = -alphabetapruning(p, -beta, -alpha, !who); //取子節點的值對於直棋(九連棋、成三棋、莫里斯棋)而言,有不同於常規alpha-beta演算法的地方:
max層和min層不一定是嚴格交替分布的:alpha-beta剪枝演算法是從前向後深度優先搜尋,越早發現最優節點,後面就會越早發生剪枝,進而節省大量時間。一方行棋成三后可能要繼續執行去子,甚至還有九連棋這樣悶死讓行的,這會形成連續兩層以上的max或min,也會有在同一層既有max也有min的情況
不過這不影響搜尋演算法,我們只需要在取值前確定這是誰的回合就好。
如何將可能出現最優節點的招法放在前面,這裡就需要對子節點進行排序了。(我的演算法中,沒有具體實現優化排序)
另外,排序的隨機化,會使出招隨機化,表現為ai不會每一局都出相同的招。
在**測試階段,我發現當一方勝券在握的情況下,偶爾會進入迴圈僵局,類似於象棋中的「長將」。
分析原因:假設勝券在握的一方檢索6層深度,它在第3層和第5層都發現了決勝招,不考慮隨機出招的情況,演算法會走它發現第乙個決勝招法,而第乙個決勝招法可能會不斷加深,形成迴圈。
我們有兩種方案解決這個問題:
節點層次評估
常規演算法中,第3層和第5層的決勝局面評分是相同的。我們對評估值進行乙個優化,減去深度值,使第3層的評分高於第5層的評分。這樣,演算法就會選擇更短的決勝出招路徑了。
迭代加深
不直接將檢索深度設定為預置深度,而是從1層算起,然後2、3、4、5層……依次加深,如果找到決勝招數,就不再計算更深的層次了。
這個招法會有額外的計算開銷(算第2層時,1層的局面要重新生成),但在終局階段,由於其不再進行更深度的無謂檢索,實際開銷往往反而低於固定深度搜尋。
對當前局面的評估,僅僅與局面本身有關,與形成此局面的招法順序無關,如下面的兩種招法形成的局面是一致的:
對任一局面,它的映象局面跟它的評價值是相同的,如:
對任一局面,它的內外層翻轉局面跟它的評價值是相同的,如:
對任一局面,它的3個旋轉局面跟它的評價值是相同的,如:
對這類等價局面,重複搜尋會浪費大量的時間。
我們建立乙個特定大小的儲存了局面資訊的hash表,每次查詢新局面時,都在表中查詢有無等價局面,有的話直接取其值即可。這樣就節省了重複計算的時間。
hash表使用c++11裡的unordered_map。hash的計算有待完善。
計算量的大小嚴重依賴於著法的尋找順序,但並沒有足夠好的排序演算法能在很小的開銷下實現最優排序。
非最終局面時的評價函式,往往是程式設計者的主觀評價,並不嚴謹。評價函式直接決定了剪枝,有可能把最優的那一枝剪掉了。
因層深限制不能遍歷最終局面時,alpha-beta剪枝演算法並不能得到最優解。
對於圍棋,如果採用alpha-beta剪枝演算法,計算量是巨大的,尤其是開局和中局這種空位置很多的階段(不考慮開局庫),所以能算到的深度很小(恐怕連10層都沒有)。
人類在這種大廣度的遊戲中,往往可以靠經驗而非計算,來剪掉大量的「枝」。在這種情況下,經驗(開局庫)往往比演算法更具優勢。
在《天龍八部》中,無崖子所創珍瓏棋局,天下高手無人可破,卻被虛竹隨便破解。為什麼?因為虛竹下的是明顯的「昏招」,評價函式返回了乙個很大的負數,所以高手們都不會選的。只有計算的深度再增加幾層,演算法才會發現這步「昏招」其實是評分為正數的「妙招」。
這就是在神經網路演算法的alphago面世前,所有採用alpha-beta剪枝演算法的ai都難以在圍棋上戰勝人類大師的根本原因。
作為乙個非it界的外行人,我的alpha-beta剪枝演算法寫得一般般,勉強能用吧。
原始碼相見我的ninechess專案:
我的剪枝演算法暫未實現招法優化排序,置換表因效率問題取消掉了,所以算力有限,耗時較長。以後有有時間再繼續改善吧。
AlphaBeta剪枝演算法
關於alphabeta剪枝的文章太多,這個方法是所有其它搜尋方法的基礎,得多花些時間認真地理解。先把基本概念再回顧一遍 節點 在中國象棋中就是乙個棋盤的當前局面board,當然該輪到誰走棋也是確定的。這裡的圓形節點表示終止節點,在中國象棋裡就是一方被將死的情況 或者到達了搜尋的最大深度 後續不會再有...
Alpha beta剪枝演算法例項分析
看本章之前,請先參看前一篇文章 minimax演算法及例項分析 由於minimax演算法有乙個很大的問題就是計算複雜性。由於所需搜尋的節點數隨最大深度呈指數膨脹,而演算法的效果往往和深度相關,因此這極大限制了演算法的效果。alpha beta剪枝是對minimax的補充和改進。採用alpha bet...
Alpha beta剪枝演算法例項分析
原帖 看本章之前,請先參看前一篇文章 minimax演算法及例項分析 由於minimax演算法有乙個很大的問題就是計算複雜性。由於所需搜尋的節點數隨最大深度呈指數膨脹,而演算法的效果往往和深度相關,因此這極大限制了演算法的效果。alpha beta剪枝是對minimax的補充和改進。採用alpha ...