Python蒙特卡洛树搜索算法实现的黑白棋AI系统
資源下載地址:https://download.csdn.net/download/sheziqiong/85836329
資源下載地址:https://download.csdn.net/download/sheziqiong/85836329
利用給出的 board.py,使用蒙特卡洛樹搜索算法來完成黑白棋 AI。
AI 需要完成的功能:
在當前棋盤狀態下,選擇一個合法且在算法上最優的落子位置,作為返回值
搜索及決策時間不超過一分鐘,若無合法位置則返回 None
在游戲結束(即雙方均無合法落子位置)時,盡量最大化己方與對方的棋子數量差
設計思想
While (time_is_enough): For (node = root; all son_node.visited in node.sons; node = choson_son) Choson_son = the son with max UCB of node# Selection,從根往下,選擇一個兒子沒有被完全訪問過的節點Expand_Candidate = x if ((x in node.sons) and (not x.visited))Node_to_expand = random.choice(Expand_Candidate)# Expansion,隨機選擇一個沒有被訪問過的兒子節點Leaf = nodeFor (node = Node_to_sxpand; node has son; node = random.choice(node.sons)) Leaf = node # Simulation,隨機選擇兒子節點直到葉子節點 For (node = Leaf; node != root; node = node.father) Update(node) # Back Propagation, 更新訪問過的信息以及勝負/獎勵分數信息在搜索過程中,每一次“采樣”都有四個步驟:選擇,擴展,模擬和反向傳播:
但是根據 UCB 的 score 函數組成,其實能發現 k 如果只是作為乘上去的系數,本質上就是 C,不過添加一個 k 可以方便調整也更直觀而已。
最后就是搜索次數可以對搜索效果產生影響了,由于給出了一分鐘的落子時限,雖然在測試時使用的 25s 采樣時間效果已經不錯了,但在提交的時候應該還是會頂著時間上限吧(笑)
代碼內容
================================================================ //UCB1:def UCB1(self, color, board_in):""":param color: 當前節點對應的顏色:param board_in: 當前棋盤狀態:return : 根據采樣結果,對 AI 方最有利的落子位置"""board = deepcopy(board_in)score_act = Noneact = Noneaction_list = list(board.get_legal_actions(color))rec_sum = 0 # 記錄這個節點的總分(用來算 select 的子節點)for action in action_list:play_board = deepcopy(board)play_board._move(action, color)tmp_key = tuple(np.ravel(play_board._board))# 計算該 action 后對應的棋盤的 key 值if self.rec.get((color, tmp_key)):# 訪問過則繼續計算總分rec_sum += self.rec.get((color, tmp_key))for action in action_list:play_board = deepcopy(board)play_board._move(action, color)tmp_key = tuple(np.ravel(play_board._board))score_tmp = (self.scr.get((color, tmp_key)) / self.rec.get((color, tmp_key)) + self.C * math.sqrt(math.log(rec_sum) / self.rec.get((color, tmp_key))))# 計算鍵值 以及積分if score_act == None:score_act, act = (score_tmp, action)else:if score_act < score_tmp:score_act, act = (score_tmp, action)# 更新積分最高的子節點return act ==================================================================== //選擇:def Select(self, board):""" :param board: 輸入需要被搜索的棋盤:return: color 是 select 到最后的那個節點已經落子的棋子顏色, act 是上一個 落子的位置, tmpkey 是這個棋盤的狀態"""color = self.colorwhile(True):# 一直 select 直到有一個節點沒有完全被擴展action_list = list(board.get_legal_actions(color))if len(action_list) == 0:return None, None, Noneall_explored = True # 這個節點的子節點是否全部訪問過non_vis_son = [] # 記錄沒有訪問過的兒子節點rec_sum = 0 # 記錄這個節點的總分(用來算 select 的子節點)for action in action_list:play_board = deepcopy(board)play_board._move(action, color)tmp_key = tuple(np.ravel(play_board._board))# 計算該 action 后對應的棋盤的 key 值if not self.rec.get((color, tmp_key)):# 沒有訪問過則記錄 該子節點 以及更新節點未訪問信息all_explored = Falsenon_vis_son.append((action, tmp_key))else:# 訪問過則繼續計算總分rec_sum += self.rec.get((color, tmp_key))if all_explored:# 如果全部訪問過,則在該節點中選擇分數最高的兒子act = self.UCB1(color, board)else:# 有未訪問節點,則隨機返回一個未訪問節點,作為 extend 的對象act, tmp_key = (random.choice(non_vis_son))board._move(act, color)return (color, act, tmp_key)# 到這里的時候應該是要 select 下一個節點了board._move(act, color)tmp_key = tuple(np.ravel(board._board))# 落子,更新新棋盤的 key 值self.vis.add((color, tmp_key))# 記錄路徑上的節點信息color = "X" if color == "O" else "O"# 切換顏色 ==================================================================== //擴展:def Expand(self, board, color, act, tmpkey):""":param board: 當前要擴展的棋盤:param color: 當前已經落子的棋子顏色:param act: 當前已經落子的位置:param tmpkey: 當前棋盤狀態:return: 返回乘上系數后得到的分差"""game_state, scr_diff = self.Simulate(board, color)self.rec[(color, tmpkey)] = 1# 記錄該節點下的訪問次數+1if (game_state == 0 and self.color == "O") or (game_state == 1 and self.color == "X"):scr_diff = - scr_diff# 把 scr_diff 改成(AI-對方)的分差,可以為負scr_diff *= 0.4# 加一個系數if color == self.color:# 如果當前決策節點的顏色是 AI 的顏色,則加上分差,否則減去分差self.scr[(color, tmpkey)] = scr_diffelse:self.scr[(color, tmpkey)] = - scr_diffreturn scr_diff ==================================================================== //模擬:def Simulate(self, board, player):"""用隨機來模擬下棋過程:param board: 當前棋盤狀態:param player: 當前剛完成落子的玩家:return: (winner, 分數差), 其中 winner 是 0 黑棋, 1 白棋, 2 平局"""while(True):player = "X" if player == "O" else "O"# 切換執棋方legal_actions = list(board.get_legal_actions(player))if len(legal_actions) == 0:if self.game_over(board):return board.get_winner()# 0 黑棋, 1 白棋, 2 平局# 后面還有個分數差的參數break else:continueif len(legal_actions) == 0:action = Noneelse:action = random.choice(legal_actions)# 用隨機落子來模擬if action is None:continueelse:board._move(action, player)if self.game_over(board):return board.get_winner() ==================================================================== //Back Propagation:def BackPropagate(self, scr_diff):""":param scr_diff: 乘上系數的 AI 與對手的分數差"""for (color, key) in self.vis:self.rec[(color, key)] += 1if color == self.color: # 如果當前決策節點的顏色是 AI 的顏色,則加上分差,否則減去分差self.scr[(color, key)] += scr_diffelse:self.scr[(color, key)] -= scr_diff ==================================================================== //UCTS 的主要部分:def MCTS_choice(self, board_input):""":param board_input: 輸入當前棋盤:return: 返回落子坐標樹的狀態節點用 rec 和 scr 兩個 dict 來存儲,存下了(當前落子方,棋盤狀態): (訪問次數,合計分數)的狀態"""starttime = datetime.datetime.now()count = 0while True: count += 1currenttime = datetime.datetime.now()if (currenttime - starttime).seconds > 3 or count > 1000:break board = deepcopy(board_input)color = "X" if self.color == "O" else "O"# color 是對方的顏色self.vis = set() # 記錄樹上搜索過的路徑,方便更新color, act, tmpkey = self.Select(board) # color 是 select 到最后的那個節點已經落子的棋子顏色# act 是上一個落子的位置# tmpkey 是這個棋盤的狀態if color == None:# 如果沒有可以落子的地方,進入下一輪嘗試continue scr_diff = self.Expand(board, color, act, tmpkey)# Expand 得到當前擴展節點的分數,并用于 bpself.BackPropagate(scr_diff)print(count)return self.UCB1(self.color, board_input)實驗結果
資源下載地址:https://download.csdn.net/download/sheziqiong/85836329
資源下載地址:https://download.csdn.net/download/sheziqiong/85836329
總結
以上是生活随笔為你收集整理的Python蒙特卡洛树搜索算法实现的黑白棋AI系统的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS牛奶按钮样式
- 下一篇: [Java] 用java来突破一下人类极