蒙特卡洛树搜索

蒙特卡洛树搜索的基本概念

蒙特卡洛树搜索会多次模拟博弈,并尝试根据模拟结果预测最优的移动方案。蒙特卡洛树搜索的主要概念是搜索,即沿着博弈树向下的一组遍历过程。单次遍历的路径会从根节点(当前博弈状态)延伸到没有完全展开的节点,未完全展开的节点表示其子节点至少有一个未访问到。遇到未完全展开的节点时,它的一个未访问子节点将会作为单次模拟的根节点,随后模拟的结果将会反向传播回当前树的根节点并更新博弈树的节点统计数据。一旦搜索受限于时间或计算力而终止,下一步行动将基于收集到的统计数据进行决策。

井字棋进行性说明

初始状态

初始状态,棋盘状态为空,根节点处有9种选择,根节点是未展开的节点。从初始状态开始,进行simulations_number次的模拟对局,模拟对局的目的是在模拟对局的过程中,尽可能多地保存着每个节点的(奖励值Q,访问次数N)信息,模拟对局结束后,在决定根节点的最佳的下一步落子。

一次模拟对局(simulations_number=1):
(1)展开节点

code1——寻找一个节点去更新信息

    #寻找一个节点去更新信息
    def tree_policy(self):
        current_node = self.root
        while not current_node.is_terminal_node():
            #如果找到一个没有完全展开节点,通过current_node.expand()找node,找到后立刻       返回,终止while
            if not current_node.is_fully_expanded():
                return current_node.expand()
            #如果当前节点是完全展开节点,通过best_child()计算最佳的子节点,然后把最佳的子节点作为current_node,继续while循环。这样,如果一直出现current_node最佳的子节点是完全展开节点,则最后找到终节点返回(实际上这不存在,因为全部展开需要搜索9!=362880次,对于其他棋谱搜索空间更大)
            else:
                current_node = current_node.best_child()
        return current_node

从根节点开始,此时根节点就是当前节点current_node,当current_node不是终节点(即current_node所对应的棋盘状态还没分出胜负并且还可以落子,如图.1所示,这里的current_node对应的棋盘状态为空,一定满足条件)时,判断当前节点是否是完全展开的节点,如果不是完全展开的节点,那么需要扩展开节点current_node.expand(),即随机选择一个没有展开的子节点进行访问,然后当前节点标记所展开的子节点(随着模拟对局次数的增加,当前节点标记所展开的子节点数目变多,当标记所有子节点时候,此时当前节点则是完全展开节点了)
code2——展开一个节点

#current_node.expand()扩展过程
 def expand(self):
        #弹出一个可以移动的位置
        action = self.untried_actions.pop()
        #当前节点执行随意一个可行的action 步骤得到下一个棋盘状态,进入到节点node
        next_state = self.state.move(action)
        child_node = TwoPlayersGameMonteCarloTreeSearchNode(next_state, parent = self)
        #标记节点node
        self.children.append(child_node)
        #返回子节点node
        return child_node

如果当前节点是完全展开的节点则通过best_child()计算最佳的子节点,然后把最佳的子节点作为current_node,继续while循环。这样,如果一直出现current_node最佳的子节点是完全展开节点,则最后找到终节点返回(实际上这很少存在,因为全部展开需要搜索9!=362880次(至少模拟对局也是9!次,因为模拟对局一次后,下一次的模拟对局,对于根节点来说,以前的节点并不在棋盘状态中,下一次开始时候的棋盘状态依然是空白,根节点只是保存着是否访问过的信息),对于其他棋谱搜索空间更大,则蒙特卡洛树的好处就是不用展开所有节点,而是根据模拟对局数返回的信息进行决策),此时终节点已经跑完了一次胜负,则直接走反向传播的步骤,而不需将返回的节点进行rollout()得出结果。

树的置信上限UCT

UCT(vi,v)=Q(vi)N(vi)+clog(N(v))N(vi) U C T ( v i , v ) = Q ( v i ) N ( v i ) + c log ⁡ ( N ( v ) ) N ( v i )

UCT 最大的节点就是蒙特卡洛树搜索遍历过程中选择的节点。UCT 函数如何运行?
首先,该函数为节点 v 的子节点 vi v i 而定义,它包括两个组件:第一个组件是 Q(vi)N(vi) Q ( v i ) N ( v i ) ,又叫做 exploitation 组件,可以理解为赢/输率,总模拟奖励(simulation reward)除以总访问次数,即节点 vi v i 的胜率评估结果。我们当然更想遍历具备高赢率的节点。
第二个 UCT 组件 是 clog(N(v))N(vi) c log ⁡ ( N ( v ) ) N ( v i ) ,exploration。exploration 组件支持未被探索的节点,这些节点相对来说更少被访问( N(vi) N ( v i ) 较低)。 N(v) N ( v ) 确定时,该函数随着节点访问量的增加而递减,给访问量少的节点提供更高的被选中几率,以指引 exploration 探索。

最终,UCT 公式中的参数 c 控制蒙特卡洛树搜索中 expolitation 和 exploration 组件之间的平衡。UCT 函数中的一个重要标志是:在竞争性游戏中,其 exploitaion 组件 Qi Q i 的计算通常与在节点 i 处行动的玩家有关,这意味着在遍历博弈树时,玩家视角根据被遍历的节点而变化:对于任意连续节点,玩家视角都是相反的。
code3——通过树的置信上限UCT寻找最佳的子节点

    def best_child(self, c_param = 1.4):
        #计算每个子节点的树的置信上限(UCT)
        choices_weights = [
            (c.q / (c.n)) + c_param * np.sqrt((2 * np.log(self.n) / (c.n)))
            for c in self.children
        ]
        #返回最大UCT对应的子节点
        return self.children[np.argmax(choices_weights)] 

这里写图片描述

图.1 初始状态

假设这里不是完全展开的节点,我们选择的行动为action=(2,1)即在(2,1)处落子,那么棋盘状态将由 s0 s 0 变为 s1 s 1 ,假设落子后的节点为node,则将在node处开始进行rollout()策略不断对弈(如图.2所示)(这里的rollout()是每一次都轮流随机落子),直到分出胜负,返回奖励reward给node节点(因为当前节点的下一步落子是玩家1,而落子到node处,如果最终结果是玩家1赢,那么node节点得到reward为1,输了则是-1,平局得到0)。
这里写图片描述

图.2 选择一个节点展开

(2)反向传播:将模拟结果传播回去

反向传播过程就是在对局结束后,把信息反馈给节点node(reward取值1,-1,或者0),用来更新节点的(Q,N)信息。更新过程是从node节点到根节点的一条路径。这里假设node节点得到的reward值为1,那么node节点的信息更新为(Q,N)=(1,1),然后反向传播给根节点,使得根节点信息也更新为(Q,N)=(1,1)。自此,一次完整的模拟对局结束。如图.3 所示
这里写图片描述

图.3 反向传播

模拟对局结束

模拟对局结束后,c_param = 0 使得只选择计算每个子节点的树的置信上限(UCT)公式的前半部分,即平均每次访问该节点得到的Q值,该值越大的位置,就是根节点真正落子的位置
code4——模拟对局

  def best_action(self, simulations_number):
        for _ in range(0, simulations_number):
            #寻找一个节点用来更新信息 (1.探索阶段2.选择阶段)           
            v = self.tree_policy()
            #从节点v开始,随机走,返回对局的结果,赢1,输-1
            reward = v.rollout()
            #反向传播(3.反向传播阶段)
            v.backpropagate(reward)
        # exploitation only
        return self.root.best_child(c_param = 0.)

参考
1. AlphaGo背后的力量:蒙特卡洛树搜索入门指南
2. Monte Carlo Tree Search – beginners guide
3. 代码monte-carlo-tree-search

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值