五子棋AI 蒙特卡洛樹搜尋

2021-10-01 12:24:46 字數 2940 閱讀 6077

自高中時代做了乙個帶簡單ai的五子棋遊戲後,一直以來實現乙個更加厲害的五子棋ai算是我的小目標。之前也嘗試過使用 minmax 演算法,最終結果不甚理想。當然並不是演算法問題,而是搭配這個演算法需要許多領域知識,這些知識我並不了解,以至於結果與我的期望相去甚遠。

我心目中滿意的五子棋ai是這樣的:

不需要像 minmax 那樣去編寫乙個局面評估函式

演算法看上去很簡單很傻也沒關係,但可以通過訓練提高棋力

似乎真的可以理解局面

在 alphago 出現之前,我的思路都是採用遺傳演算法,因為我確實看到過遺傳演算法在桌面遊戲中的成功應用。但是遺傳演算法需要某些東西可以被遺傳跟突變,我那時並沒有想到哪些資訊該編碼進去,如果將棋形分數編碼,應該還是會得到乙個比較弱的ai。

後來 alphago 出現了,它幾乎就是我夢想中的 ai 了,除了需要人類棋譜。再後來 alphazero 出現了,不依賴人類棋譜,僅以自我對弈的方式就可以發現圍棋的奧妙。於是,我決定以 alphazero 的實現方法為藍本實現我的夢想五子棋 ai。alphazero 採用了深度學習和蒙特卡洛樹搜尋,深度學習我還沒有接觸過,為了不至於什麼東西也做不出來,我打算先從蒙特卡洛樹搜尋開始。

關於蒙特卡洛樹搜尋我並不想再做介紹,已經有很多講解的很好的文章了。我想實現乙個稍微通用點的並且盡量教科書式的蒙特卡洛樹搜尋,方便自己或者他人閱讀,今後也可以用於其他遊戲。鑑於我了解 c++,我打算採用 c++ 來實現。

樹有節點,需要定義節點資料型別。考慮到蒙特卡洛樹搜尋執行所需要的資料,我把節點定義成如下樣子:

templatestruct node_t ;
其中,w 表示分數;n 表示節點訪問次數;p = w/n;q = ln(n) * 2。p, q 是為了方便,將一些中間計算結果儲存在了節點中,並不是必要的。children 採用 std::vector 主要是想把記憶體管理的事情簡化掉,並且提高子節點遍歷的效率。節點的模版引數看著很奇怪,其實只是為了與其他地方統一。

蒙特卡洛演算法執行需要知道根節點以及狀態,我設計如下資料結構封裝這兩個資料。

templatestruct mcts_context ;
有了上面兩個資料結構,蒙特卡洛樹搜尋的大致框架就有了。

templatenode_t<_state>* mct_search(mcts_context<_state>& ctx, std::chrono::duration<_rep, _ra> dur) 

return &*std::max_element(

ctx.root.children.begin(), ctx.root.children.end(),

(const node_type& a, const node_type& b)

);}

其中 select, expand, playout, back_propagate 對應演算法的四個階段。要注意的是 playout 的行為是,隨機模擬結束後,若獲勝方為當前行棋方(呼叫 playout 時當前局面的行棋方)則返回 1,對手獲勝返回 -1, 平局返回 0。因為要做乙個通用的演算法,這裡肯定不能假設黑方白方,但兩人回合制遊戲一定存在當前方。最終結果乘以 -1 的理由是,若當前演算法要為白棋搜尋最好著法,則根節點對應的局面為白方行棋,那麼子節點對應白方每個合理著法之後的局面,而這些子節點對應局面的行棋方是黑方。如果子節點模擬結束後黑棋獲勝,就表示白棋行棋后導致黑棋獲勝,這顯然不是我們想要的。我們肯定更希望白棋行棋后導致白棋獲勝的著法,而行棋后的局面與局面當前行棋方總是相反關係,也就是說如果當前為白棋行棋則一定是黑棋走了一步棋造成的,所以最終結果乘以 -1。

由於節點只儲存動作並未儲存當前局面狀態,所以我將局面狀態繫結到 select, expand 階段。在 select/expand 階段選擇出子節點的同時將 context 中的狀態改變至節點對應的狀態,在一輪結束後再將狀態恢復至初始狀態。有些實現會採用 do_move/undo_move 達到同樣效果。

接下來就是實現框架中的每個階段了。

select 是選擇出葉子節點,其中葉子節點的定義是還沒有執行過隨機模擬的節點或者終結節點,也就是 n 為 0 或者沒有子節點的節點。

templatenode_t<_state>* select(mcts_context<_state>& ctx) 

);ctx.state = next(ctx.state, pnode->action);

} return pnode;

}

這裡我引入了乙個新函式 next(),它的作用是給定當前局面狀態和動作,返回動作執行後的下個局面狀態。由於是通用演算法,局面狀態的表示法還不確定,所以這裡實際是留給局面狀態實現方的介面。

expand 是擴充套件節點。

templatenode_t<_state>* expand(mcts_context<_state>& ctx, node_t<_state>* leaf) ;});

leaf = &leaf->children.first();

ctx.state = next(ctx.state, leaf->action);

return leaf;

}

這裡引入了乙個新的函式 actions() 用來獲取當前局面狀態的可行動作,如果沒有可行動作則表示這是最終局面了。同 next() ,這也是留給局面狀態實現方的介面。

playout 需要執行隨機模擬和判斷輸贏,有太多根局面狀態繫結的概念,所以也留給局面狀態實現方。

back_propagate 就是把結果反向傳播到根節點。

templatevoid back_propagate(node_t<_state>* leaf, double score) 

}

至此蒙特卡洛樹搜尋演算法就算是完成了,我留了 3 個介面(next(), actions(), playout())給外部,這三個函式的實現我將在接下來 局面狀態 的文章中具體講。整個結構肯定存在可以調整優化的地方,不過先不急。

蒙特卡洛樹搜尋 8 11 蒙特卡洛樹搜尋

我想蒙特卡洛樹搜尋 monte carlo tree search,mcts 突然為人所知還是得益於deepmind的alphago。一瞬間,關於解析alphago背後技術的文章大量湧現,其中的乙個核心技術就是本節mcts的乙個擴充套件。本節我們基於前面的知識,來看看mcts究竟是什麼?首先,我們給...

五子棋 AI篇

效果圖 贏法陣列 var wins 第count種贏法 var count 0 贏法的統計陣列 var mywin var computerwin 結束標識 var gameover false var chess document.getelementbyid chess var me true ...

MCTS 蒙特卡洛樹搜尋

最近想去做乙個小型的五子棋對弈,中間會用到蒙特卡洛樹,在此標記一下。mcts,即蒙特卡羅樹搜尋,是一類搜尋演算法樹的統稱,可以較為有效地解決一些搜尋空間巨大的問題。如乙個8 8的棋盤,第一步棋有64種著法,那麼第二步則有63種,依次類推,假如我們把第一步棋作為根節點,那麼其子節點就有63個,再往下的...