28 天自制你的 AlphaGo(五):蒙特卡洛树搜索(MCTS)基础

蒙特卡洛树搜索(MCTS)是所有现代围棋程序的核心组件。在此之上可以加入各种小技巧(如 UCT,RAVE/AMAF,Progressive
Bias,Virtual win & lose,Progressive Widening,LGR,Criticality
等等)和大改进(如 AlphaGo 的策略网络和价值网络)。

网上很少见到关于 MCTS 的详细介绍,而且许多看似详细的介绍实际有错误,甚至许多人会混淆蒙特卡洛树搜索和蒙特卡洛方法。这两者有本质区别。用做过渲染器的朋友会理解的话来说:蒙特卡洛方法有偏差(Bias),而MCTS没有偏差(Bias)。我们在后文会解释。

一、极小极大(Minimax)搜索

先看传统的博弈游戏树搜索,著名的极小极大(Minimax)搜索,学过算法的朋友会清楚。看下图,假设现在轮到黑棋,黑棋有b1和b2两手可选,白棋对于b1有w1和w2两手可选,白棋对于b2有w3 w4 w5三手可选:

然后假设走完w1/w2/w3/w4/w5后,经过局面评估,黑棋的未来胜率分别是 50%/48%/62%/45%/58%(等一下,这些胜率是怎么评估出来的?我们后文会说这个问题)。

请问,黑棋此时最佳的着法是b1还是b2?如果是用蒙特卡洛方法,趋近的会是其下所有胜率的平均值。例如经过蒙特卡洛模拟,会发现b1后续的胜率是49% = (50+48)/2,而b2后续的胜率是55% = (62+45+58)/3。

于是蒙特卡洛方法说应该走b2,因为55%比49%的胜率高。但这是错误的。因为如果白棋够聪明,会在黑棋走b1的时候回应以w2(尽量降低黑棋的胜率),在黑棋走b2的时候回应以w4(尽量降低黑棋的胜率)。

所以走b1后黑棋的真实胜率是48%,走b2后黑棋的真实胜率是45%。黑棋的正解是b1。这就是 Minimax 搜索的核心思想:在搜索树中,每次轮到黑棋走时,走对黑棋最有利的;轮到白棋走时,走对黑棋最不利的。由于围棋是零和游戏,这就可以达到最优解。这是一个由底往上的过程:先把搜索树画到我们可以承受的深度,然后逐层往上取最大值或最小值回溯,就可以看到双方的正解(如果胜率评估是准确的)。而实际编程的时候,是往下不断生长节点,然后动态更新每个父节点的胜率值。

下图是一个更多层的例子:

值得注意的是,在实际对局中,胜率评估会有不准确的地方,这就会导致“地平线效应”,即由于电脑思考的深度不够,且胜率评估不够准确,因此没有看见正解。

Minimax 搜索还有许多后续发展,如课本会说的 Alpha-beta 剪枝,以及更进一步的 Null Window / NegaScout / MTD(f) 等等。可惜这些方法更适合象棋等棋类,对于围棋的意义不大(除非已经接近终局),请读者思考原因。

蒙特卡洛树搜索和蒙特卡洛方法的区别在于:

  • 如果用蒙特卡洛方法做上一百万次模拟,b1和b2的胜率仍然会固定在49%和55%,不会进步,永远错误。所以它的结果存在偏差(Bias),当然,也有方差(Variance)。
  • 而蒙特卡洛树搜索在一段时间模拟后,b1和b2的胜率就会向48%和45%收敛,从而给出正确的答案。所以它的结果不存在偏差(Bias),只存在方差(Variance)。但是,对于复杂的局面,它仍然有可能长期陷入陷阱,直到很久之后才开始收敛到正确答案。

二、蒙特卡洛树搜索

如果想把 Minimax 搜索运用到围棋上,立刻会遇到两个大问题:

  • 搜索树太广。棋盘太大了,每一方在每一步都有很多着法可选。
  • 很难评估胜率。除非把搜索树走到终局,这意味着要走够三百多步(因为对于电脑来说,甚至很难判断何时才是双方都同意的终局,所以只能傻傻地填子,一直到双方都真的没地方可以走为止)。简单地说,搜索树也需要特别深。

蒙特卡洛树搜索的意义在于部分解决了上述两个问题:

  • 它可以给出一个局面评估,虽然不准,但比没有强。这就部分解决了第二个问题。
  • 根据它的设计,搜索树会较好地自动集中到“更值得搜索的变化”(注意,也不一定准)。如果发现一个不错的着法,蒙特卡洛树搜索会较快地把它看到很深,可以说它结合了广度优先搜索和深度优先搜索,类似于启发式搜索。这就部分解决了第一个问题。

最后,随着搜索树的自动生长,蒙特卡洛树搜索可以保证在足够长的时间后收敛到完美解(但可能需要极长的时间)。下面看具体过程(需要指出,下图是网络上常见的一个图,但其实有错误):

上图中每个节点代表一个局面。而 A/B 代表这个节点被访问 B 次,黑棋胜利了 A 次。例如一开始的根节点是 12/21,代表总共模拟了 21 次,黑棋胜利了 12 次。

我们将不断重复一个过程(很多万次):

这个过程的第一步叫选择(Selection)。从根节点往下走,每次都选一个“最值得看的子节点”(具体规则稍后说),直到来到一个“存在未扩展的子节点”的节点,如图中的 3/3 节点。什么叫做“存在未扩展的子节点”,其实就是指这个局面存在未走过的后续着法。

第二步叫扩展(Expansion),我们给这个节点加上一个 0/0 子节点,对应之前所说的“未扩展的子节点”,就是还没有试过的一个着法。

第三步是模拟(Simluation)。从上面这个没有试过的着法开始,用快速走子策略(Rollout
policy)走到底,得到一个胜负结果。按照普遍的观点,快速走子策略适合选择一个棋力很弱但走子很快的策略。因为如果这个策略走得慢(比如用
AlphaGo
的策略网络走棋),虽然棋力会更强,结果会更准确,但由于耗时多了,在单位时间内的模拟次数就少了,所以不一定会棋力更强,有可能会更弱。这也是为什么我们一般只模拟一次,因为如果模拟多次,虽然更准确,但更慢。

第四步是回溯(Backpropagation)。把模拟的结果加到它的所有父节点上。例如第三步模拟的结果是 0/1(代表黑棋失败),那么就把这个节点的所有父节点加上 0/1。

三、重要细节

怎么选择节点?和从前一样:如果轮到黑棋走,就选对于黑棋有利的;如果轮到白棋走,就选对于黑棋最不利的。但不能太贪心,不能每次都只选择“最有利的/最不利的”,因为这会意味着搜索树的广度不够,容易忽略实际更好的选择。

因此,最简单有效的选择公式是这样的:

其中
x 是节点的当前胜率估计(注意,如前所述,要考虑当前是黑棋走还是白棋走!),N 是节点的访问次数。C 是一个常数。C
越大就越偏向于广度搜索,C 越小就越偏向于深度搜索。注意对于原始的 UCT 有一个理论最优的 C
值,但由于我们的目标并不是最小化“遗憾”,因此需要根据实际情况调参。

我们看例子说明这是什么意思,就看之前的图吧。假设根节点是轮到黑棋走。那么我们首先需要在 7/10、5/8、0/3 之间选择:

如果 C 比较小,我们将会选择 7/10,接着就要在 2/4 和 5/6 间选择。注意,由于现在是白棋走,需要把胜率估计倒过来:

那么我们下一步肯定应该选 2/4。所以说之前的图是错误的,因为制图的人并没有注意到要把胜率倒过来(有朋友会说是不是可以认为它的白圈代表白棋的胜率,但这样它的回溯过程就是错的)。

最后,AlphaGo
的策略网络,可以用于改进上述的分数公式,让我们更准确地选择需扩展的节点。而 AlphaGo
的价值网络,可以与快速走子策略的模拟结果相结合,得到更准确的局面评估结果。注意,如果想写出高效的程序,这里还有很多细节,因为策略网络和价值网络的运行毕竟需要时间,我们不希望等到网络给出结果再进行下一步,在等的时候可以先做其它事情,例如
AlphaGo 还有一个所谓 Tree policy,是在策略网络给出结果之前先用着。

相信大家现在就可以写出正确的 MCTS
程序了(注意:最终应该选择访问量最大的节点,而不是胜率最高的节点,简单地说是因为访问量最大的节点的结果更可靠)。如果要写一个高效的程序,你会发现必须自己写一个内存池。关于
MCTS 还有很多话题可以说,这篇就到这里吧。

本系列已更新多篇,其它几篇的传送门:

28 天自制你的 AlphaGo(一)

28 天自制你的 AlphaGo(二):训练策略网络,真正与之对弈

28天自制你的AlphaGo(三):对策略网络的深入分析以及它的弱点所在

28天自制你的AlphaGo(四):结合强化学习与深度学习的Policy Gradient(左右互搏自我进化的基础)

本文作者:彭博

本文转自雷锋网禁止二次转载,原文链接

时间: 2024-09-14 12:51:51

28 天自制你的 AlphaGo(五):蒙特卡洛树搜索(MCTS)基础的相关文章

28天自制你的AlphaGo(三):对策略网络的深入分析以及它的弱点所在

一.神经网络在围棋中的历史 再次回顾 AlphaGo v13 的三大组件: MCTS(蒙特卡洛树搜索) CNN (卷积神经网络,包括:策略网络 policy network.快速走子网络 playout network.价值网络 value network) RL (强化学习) 在上世纪90年代初期,大家就已经开始实验将神经网络(当时是浅层的)与强化学习应用于棋类游戏.最著名的例子是西洋双陆棋 Backgammon 的 TD-Gammon,它在自我对弈了150万局后,就达到了相当强的棋力,摘选

28 天自制你的 AlphaGo(二):训练策略网络,真正与之对弈

上次我们介绍了围棋基础和如何搭建深度学习环境,这篇我们安装 TensorFlow,真正训练一下 AlphaGo v13 的 policy network,并且你还可以与它真正对弈,因为前几天已经有网友在做可以运行的 AlphaGo v13 的简化版:brilee/MuGo.所以这个过程真的已经很傻瓜化,毫不夸张地说,人人都可以拥有一只小狗了,只要你把棋谱喂给它,它就能学到棋谱的棋风.(本文是给大家快速找到感觉,后续我们会从头写代码,因为这位网友的代码真的很多 bug) 如果还没有装 CUDA 等

28天自制你的AlphaGo(四):结合强化学习与深度学习的Policy Gradient(左右互搏自我进化的基础)

本篇提前回答一个大家经常问的问题:强化学习在 AlphaGo 中究竟是怎么用的?比如说,SL策略网络,是怎么变成 RL 策略网络的? | Policy Gradient:简单而有效 很有意思的是,很少见到有人回答上述问题(可能是因为 AlphaGo 论文在此写得很简略).其实,这个问题的答案特别简单: 如果我赢了棋,就说明这次我选择的策略是正确的.所以可以对于这次所经历的每一个局面,都加强选择这局的走法的概率. 如果我输了棋,就说明这次我选择的策略是错误的.所以可以对于这次所经历的每一个局面,都

【一文读懂AlphaGo Zero算法】白话蒙特卡洛树搜索和ResNet

AlphaGo Zero 令人惊艳.不过,有些评论似乎渲染过度,把它的算法说得神乎其神.大数医达创始人,CMU计算机学院暨机器人研究所博士邓侃在本文中,尝试用大白话,通俗地解释 AlphaGo Zero,弄清楚蒙特卡洛树搜索(Monte Carlo Tree Search,MCTS).深度学习启发函数和置信上限这三大核心概念. AlphaGo Zero 引起巨大社会轰动 只告诉机器围棋的基本规则,但是不告诉它人类摸索了上千年才总结出来的定式等围棋战术,让机器完全依靠自学,打败人类.这个题目不仅

[译]什么是蒙特卡洛树搜索

本文讲的是[译]什么是蒙特卡洛树搜索, 原文地址:What is MCTS? 原文作者:cameronius 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m- 译者:CACppuccino 校对者:ppp-man joyking7 什么是蒙特卡洛树搜索 蒙特卡洛树搜索(MCTS)是一种在人工智能问题中进行决策优化的方法,通常是对于那些在组合游戏中需要移动规划的部分.蒙特卡洛树搜索将随机模拟的通用性与树搜索的准确性进行了结合. 冯·诺依曼于 1928 年提出的极

Ruby on rails开发从头来(五十六)- ActiveRecord基础(一对多关联关系)

一对多关联可以使我们表示一组对象,例如,一个order可以包含有任意多个line item,在数据库中,所有的line item记录都通过外键关联到特定的order. 在Active Record中,通过在父对象中的has_many来定义到子对象的关联,在子对象中使用belongs_to来指定父对象.我们已经在上一篇中了解了belongs_to声明,实际上,在一对多的情况下,和一对一是相同的,所以我们来了解has_many声明. 开发从头来(五十六)- ActiveRecord基础(一对多关联关

仿百度壁纸客户端(五)——实现搜索动画GestureDetector手势识别,动态更新搜索关键字

仿百度壁纸客户端(五)--实现搜索动画GestureDetector手势识别,动态更新搜索关键字 百度壁纸系列 仿百度壁纸客户端(一)--主框架搭建,自定义Tab + ViewPager + Fragment 仿百度壁纸客户端(二)--主页自定义ViewPager广告定时轮播图 仿百度壁纸客户端(三)--首页单向,双向事件冲突处理,壁纸列表的实现 仿百度壁纸客户端(四)--自定义上拉加载实现精选壁纸墙 仿百度壁纸客户端(五)--实现搜索动画GestureDetector手势识别,动态更新搜索关键

Ruby on rails开发从头来(五十九)- ActiveRecord基础(预加载子记录)

预加载子记录讨论的问题和"延迟加载"是相同的.通常Active Record会推迟从数据库中加载子记录,直到你需要他们,例如,通过Rdoc中的例子,我们假定博客程序有一个Model,像下面这样: class Post < ActiveRecord::Base belongs_to :author has_many :comments, :order => 'created_on DESC' end 如果我们遍历所有的post,访问作者和评论属性,我们使用一个Sql查询来返回

Ruby on rails开发从头来(五十八)- ActiveRecord基础(自关联)

或许存在这样的情况,在一个表中,一条记录关联到表中的另一条记录,例如,公司中的每个雇员都有上级和下级,而他们同时又是雇员,在Rails中你可以这样使用Employee类: class Employee < ActiveRecord::Base belongs_to :manager, :class_name => "Employee", :foreign_key => "manager_id" belongs_to :mentor, :class_