這幾天把圍棋A.I.最後的部分寫好了,9路小棋盤上看上去運行得還不錯,更名為FoolGo。先講一下UCT博弈樹的實現。
鑒於FoolGo的MC類比速度和棋盤對象的大小,如果直接用樹結構實現,用不了幾分鐘,我的MBP的4G記憶體就會被棋盤擠爆。所以要通過置換表實現博弈樹。
雜湊演算法當然是zobrist雜湊。如果雜湊值的類型是uint32_t的話,不同棋局雜湊衝突的機率就是1 / ~(uint32_t)0——可以認為不同的雜湊值意味著不同的棋局。不用自己寫雜湊表,C++11新增有標準容器unordered_map,很含蓄的名字,就是雜湊表。
zobrist雜湊值可以增量計算,不過實現起來有些麻煩,暫不實現,因為FoolGo目前的效能瓶頸還是在落子動作上。
UCT搜尋過程中多處要取到子節點的雜湊值,如果為此落子很昂貴,因此置換表項須要攜帶子節點的雜湊值,然後通過ChildKey函數取得雜湊值:
template <BoardLen BOARD_LEN>HashKey Engine<BOARD_LEN>::ChildKey(const BoardInGm<BOARD_LEN> &parent, PointIndex indx) const{ HashKey prnt_key = parent.HashKey(); HashKey chldrn_key = table_[prnt_key].children_key_[indx]; if (chldrn_key == NONE) { BoardInGm<BOARD_LEN> b; b.Copy(parent); PlayerColor color = OppstColor(b.LastPlayer()); b.PlayMove(Move(color, indx)); HashKey key = b.HashKey(); table_[prnt_key].children_key_[indx] = key; return key; } else { return chldrn_key; }}
有了這個置換表後,易實現UCT博弈樹。只是剛寫完時bug較多,這種海量計算的函數,debug起來比較頭疼……
測試下來,在9路的初始棋盤上,類比3w局MC對局用時大概9秒多,隨著棋局的進行會加快。9秒一步還是比較愉快,就拿來和一個線上圍棋A.I.單挑了一下 http://peepo.com/ 。FoolGo無恥地執黑先行:
棋盤上面的數值是FoolGo對該落子點的評估(即下在那裡的話最後能占几子)
之前走成裂形了,可以看到FoolGo對局面的評估也持悲觀,不過這串的滾打包收還不錯。
雙方地區基本確定,黑棋敗勢已定……
額操作失誤了,我的測試程式沒法悔棋,PK到此為止。
把類比局數的參數設大一些應該會更強,不過人家的線上A.I.幾秒種一步棋,我也不好意思再佔便宜。
代碼:https://github.com/chncwang/FoolGo
額對了,目前找工作中,之前做過iPhone開發,想轉型做服務端的C++開發,比較嚮往的方向是推薦演算法和搜尋引擎開發。我還是比較感興趣用C++高效實現一些東西。聯絡郵箱:chncwang@gmail.com,工作地點杭州。