蒙特卡洛搜索树python_python实现的基于蒙特卡洛树搜索(MCTS)与UCT RAVE的五子棋游戏...

本文介绍了基于蒙特卡洛树搜索(MCTS)和UCT算法的五子棋游戏AI实现。通过详细阐述MCTS的工作原理,包括选择、扩展、模拟和反馈四个阶段,以及UCT公式,展示了如何在Python中实现MCTS。文章还探讨了UCT的改进版——UCT RAVE,以及剪枝策略,以提高算法效率。
摘要由CSDN通过智能技术生成

更新

2017.2.23有更新,见文末。

MCTS与UCT

下面的内容引用自徐心和与徐长明的论文《计算机博弈原理与方法学概述》:

蒙特卡洛模拟对局就是从某一棋局出发,随机走棋。有人形象地比喻,让两个傻子下棋,他们只懂得棋规,不懂得策略,最终总是可以决出胜负。这个胜负是有偶然性的。但是如果让成千上万对傻子下这盘棋,那么结果的统计还是可以给出该棋局的固有胜率和胜率最高的着法。

蒙特卡洛树搜索通过迭代来一步步地扩展博弈树的规模,UCT 树是不对称生长的,其生长顺序也是不能预知的。它是根据子节点的性能指标导引扩展的方向,这一性能指标便是 UCB 值。它表示在搜索过程中既要充分利用已有的知识,给胜率高的节点更多的机会,又要考虑探索那些暂时胜率不高的兄弟节点,这种对于“利用”(Exploitation)和“探索”(Exploration)进行权衡的关系便体现在 UCT 着法选择函数的定义上,即子节点\(N_{i}\) 的 UCB 值按如下公式计算:

\[\frac{W_{i}}{N_{i}} + \sqrt{\frac{C \times lnN}{N_{i}}}

\]

其中:

\(W_{i}\):子节点获胜的次数;

\(N_{i}\):子节点参与模拟的次数;

\(N\):当前节点参与模拟的次数

\(C\):加权系数。

可见 UCB 公式由两部分组成,其中前一部分就是对已有知识的利用,而后一部分则是对未充分模拟节点的探索。C小偏重利用;而 C大则重视探索。需要通过实验设定参数来控制访问节点的次数和扩展节点的阈值。

后面可以看到,在实际编写代码时,当前节点指的并不是具体的着法,而是当前整个棋局,其子节点才是具体的着法,它势必参与了每个子节点所参与的模拟,所以N就等于其所有子节点参与模拟的次数之和。当C取1.96时,置信区间的置信度达到95%,也是实际选择的值。

蒙特卡洛树搜索(MCTS)仅展开根据 UCB 公式所计算过的节点,并且会采用一种自动的方式对性能指标好的节点进行更多的搜索。具体步骤概括如下:

1.由当前局面建立根节点,生成根节点的全部子节点,分别进行模拟对局;

2.从根节点开始,进行最佳优先搜索;

3.利用 UCB 公式计算每个子节点的 UCB 值,选择最大值的子节点;

4.若此节点不是叶节点,则以此节点作为根节点,重复 2;

5.直到遇到叶节点,如果叶节点未曾经被模拟对局过,对这个叶节点模拟对局;否则为这个叶节点随机生成子节点,并进行模拟对局;

6.将模拟对局的收益(一般胜为 1 负为 0)按对应颜色更新该节点及各级祖先节点,同时增加该节点以上所有节点的访问次数;

7.回到 2,除非此轮搜索时间结束或者达到预设循环次数;

8.从当前局面的子节点中挑选平均收益最高的给出最佳着法。

由此可见 UCT 算法就是在设定的时间内不断完成从根节点按照 UCB 的指引最终走到某一个叶节点的过程。而算法的基本流程包括了选择好的分支(Selection)、在叶子节点上扩展一层(Expansion)、模拟对局(Simulation)和结果回馈(Backpropagation)这样四个部分。

UCT 树搜索还有一个显著优点就是可以随时结束搜索并返回结果,在每一时刻,对 UCT 树来说都有一个相对最优的结果。

代码实现

Board 类

Board类用于存储当前棋盘的状态,它实际上也是MCTS算法的根节点。

class Board(object):

"""

board for game

"""

def __init__(self, width=8, height=8, n_in_row=5):

self.width = width

self.height = height

self.states = {} # 记录当前棋盘的状态,键是位置,值是棋子,这里用玩家来表示棋子类型

self.n_in_row = n_in_row # 表示几个相同的棋子连成一线算作胜利

def init_board(self):

if self.width < self.n_in_row or self.height < self.n_in_row:

raise Exception('board width and height can not less than %d' % self.n_in_row) # 棋盘不能过小

self.availables = list(range(self.width * self.height)) # 表示棋盘上所有合法的位置,这里简单的认为空的位置即合法

for m in self.availables:

self.states[m] = -1 # -1表示当前位置为空

def move_to_location(self, move):

h = move // self.width

w = move % self.width

return [h, w]

def location_to_move(self, location):

if(len(location) != 2):

return -1

h = location[0]

w = location[1]

move = h * self.width + w

if(move not in range(self.width * self.height)):

return -1

return move

def update(self, player, move): # player在move处落子,更新棋盘

self.states[move] = player

self.availables.remove(move)

MCTS 类

核心类,用于实现基于UCB的MCTS算法。

class MCTS(object):

"""

AI player, use Monte Carlo Tree Search with UCB

"""

def __init__(self, board, play_turn, n_in_row=5, time=5, max_actions=1000):

self.board = board

self.play_turn = play_turn # 出手顺序

self.calculation_time = float(time) # 最大运算时间

self.max_actions = max_actions # 每次模拟对局最多进行的步数

self.n_in_row = n_in_row

self.player = play_turn[0] # 轮到电脑出手,所以出手顺序中第一个总是电脑

self.confident = 1.96 # UCB中的常数

self.plays = {} # 记录着法参与模拟的次数,键形如(player, move),即(玩家,落子)

self.wins = {} # 记录着法获胜的次数

self.max_depth = 1

def get_action(self): # return move

if len(self.board.availables) == 1:

return self.board.availables[0] # 棋盘只剩最后一个落子位置,直接返回

# 每次计算下一步时都要清空plays和wins表,因为经过AI和玩家的2步棋之后,整个棋盘的局面发生了变化,原来的记录已经不适用了——原先普通的一步现在可能是致胜的一步,如果不清空,会影响现在的结果,导致这一步可能没那么“致胜”了

self.plays = {}

self.wins = {}

simulations = 0

begin = time.time()

while time.time() - begin < self.calculation_time:

board_copy = copy.deepcopy(self.board) # 模拟会修改board的参数,所以必须进行深拷贝,与原board进行隔离

play_turn_copy = copy.deepcopy(self.play_turn) # 每次模拟都必须按照固定的顺序进行,所以进行深拷贝防止顺序被修改

self.run_simulation(board_copy, play_turn_copy) # 进行MCTS

simulations += 1

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值