蒙特卡洛树搜索_蒙特卡洛树搜索完全入门指南

cfc5c59edc10d8deec0c49b55ff7ecb8.png

这是码路漫漫的

第 16 篇文章

9c4d5423045e93c92f48f9f21ad41b2b.png

上篇写的博弈树入门,收获了不错的阅读量和赞赏,甚至看到了我的算法启蒙恩师在朋友圈转发了我的文章。

8e5124e8c3431d51447e0066fac37d8e.png

超受鼓舞!兑现承诺,写一篇蒙特卡洛树搜索的入门文章。
关于蒙特卡洛树搜索,国内真的很难找到特别好的入门资料,很多还是错的,本文是前段时间为了实现自己的一个 AI,在阅读了几十篇国内外文章之后根据自己的理解整合写的,主要参照 INT8 的一篇英语博文 Monte Carlo Tree Search - beginners guide Machine learning 。不过虽然已经尽力写得通俗,但是蒙特卡洛搜索树比博弈树要难两个等级,没太多算法基础的可以不用搞懂每一个细节。

引言

在很长一段时间以来,学术界普遍认为,机器在围棋领域击败人类是完全不现实的,它被认为是人工智能的“圣杯”,尽管“深蓝”在二十年前已经击败了卡斯帕罗夫,但是在围棋上人工智能的表现还很弱。

关于深蓝的实现,可见上一篇文章你也能写棋类 AI —— 博弈树 step-by-step 实践

然而,2016 年 3 月,由 google Deepmind 研发的 ALphaGo 程序 4-1 击败了李世石,并且在一年后 AlphaGo Zero 又以 100-0 击败了它的前辈——毫无疑问,人类世界已经没有他的对手,柯洁也差得远。

82a565834957b71368bfd82f5690d7bf.png

Alpha Go / Zero系统将几种方法组合成一个伟大的工程:

  • 蒙特卡罗树搜索

  • 残余卷积神经网络 - 用于游戏评估和移动先验概率估计的策略和价值网络

  • 用于通过自我游戏训练网络的强化学习在这篇文章中,我们将重点关注卡特蒙洛树搜索算法。
    说到关注,我就想到下半年…中美合拍…文体两开花…多多关注…

    0f6f8fe2484f0384b5272824fa563df7.png

在此之前,棋类 AI 基本上使用的是博弈树算法——但是用在围棋上,效果很差,原因有两个:

  1. 棋局评判能力要求更高
    棋局的评判一般使用估值函数来评估,国际象棋的棋局局面特征比较明显,最容易想到的是可以给每个棋子和位置设置不同的分值,如果棋子之间的保护关系等特征,对局面的评价就已经很靠谱了。而对于围棋上述方法基本不起任何作用。

  2. 计算能力要求更高
    首先,国际象棋的棋盘大小为 64,围棋的大小为 361。由于棋盘大小的不同,每走一步国际象棋和围棋的计算量的要求是不一样的,围棋明显要求更高。这在博弈论中一般称之为分支因子,即平均每个落子后的合法走法,国际象棋的分支因子约为 35,而围棋大约是 250。另外一个可以说明计算能力要求不同的指标是搜索空间,在该指标上两者也存在指数级的差异,国际象棋是 10^50,而围棋是 10^171。 我们知道宇宙中的原子总数总共大约也才10^80,因此围棋的搜索空间绝对算是天文数字, 已经不能用千千万来形容了。
    不过说到千千万,我就想起孙悟空的头发…下半年…

    ea10389e228313238ade120b32158b2a.png

蒙特卡洛方法

首先我们聊聊「蒙特卡洛方法」,注意!这和蒙特卡洛树搜索不是同一种算法,很多科普文章都搞混了这两个概念,声称 AlphaGO 使用的是蒙特卡洛方法。
蒙特卡洛法方法是什么呢,它是评判棋盘局面的一种方法,我们上面说到,围棋很难写出好的估值函数,于是上世纪有人提出了一种神奇的方法:双方在某个局面下「随机」走子,注意是「随机」走,走到终局或者残局为止,随机很多次(比如一万盘),计算胜率,胜率越高的局面就越好。
这个方法是我高三的时候在「围棋世界」上看到的,那里有篇文章讲 alphago 用的就是这种方法,我当时如获至宝,因为这种方法也太太太美妙了,不用写极其复杂的估值函数,直接随机很多很多盘棋,多美妙!然后我们就套用前一篇文章我们将的博弈树的最大最小算法,完美!
但其实这是个伪算法,就举个极端的例子,比如说我下某步棋之后,对方有 100 种应对—— 99 种会导致劣势,但是有 1 种必胜下法,我就绝对不能下这步棋。
但是「蒙特卡洛树搜索」是个真算法,并且它其实在 alphago 之前早就有了,而且能胜业余的段级选手,在当时是很大的突破。

基本概念

蒙特卡洛树搜索(简称 MCTS)是 Rémi Coulom 在 2006 年在它的围棋人机对战引擎 「Crazy Stone」中首次发明并使用的的 ,并且取得了很好的效果。
我们先讲讲它用的原始 MCTS 算法(ALphago 有部分改进) 
蒙特卡洛树搜索,首先它肯定是棵搜索树

27f4073e1624fa7fdb8e364bf90177d6.png

我们回想一下我们下棋时的思维——并没有在脑海里面把所有可能列出来,而是根据「棋感」在脑海里大致筛选出了几种「最可能」的走法,然后再想走了这几种走法之后对手「最可能」的走法,然后再想自己结项来「最可能」的走法。这其实就是 MCTS 算法的设计思路。

上面这段很重要,可以反复品味

它经历下面 3 个过程(重复千千万万次)

  1. 选择(Selection)

  2. 扩展 (expansion)

  3. 模拟(Simluation)

  4. 回溯(Backpropagation)

这四个概念有点难,我们不按顺序解释这 4 个概念。

模拟(Simluation)

模拟借鉴了我们上面说的蒙特卡洛方法,快速走子只走一盘,分出个胜负。
我们每个节点(每个节点代表每个不同的局面)都有两个值,代表这个节点以及它的子节点模拟的次数和赢的次数,比如模拟了 10 次,赢了 4 盘,记为 4/10。
我们再看多一次这幅图,如图,每个节点都会标上这两个值。

27f4073e1624fa7fdb8e364bf90177d6.png
选择(Selection)

我们将节点分成三类:

  • 未访问:还没有评估过当前局面

  • 未完全展开:被评估过至少一次,但是子节点(下一步的局面)没有被全部访问过,可以进一步扩展

  • 完全展开:子节点被全部访问过
    我们找到目前认为「最有可能会走到的」一个未被评估的局面(双方都很聪明的情况下),并且选择它。

什么节点最有可能走到呢?最直观的想法是直接看节点的胜率(赢的次数/访问次数),哪个节点最大选择哪个,但是这样是不行的!因为如果一开始在某个节点进行模拟的时候,尽管这个节点不怎么好,但是一开始随机走子的时候赢了一盘,就会一直走这个节点了。
因此人们造了一个函数

fd2ce83c8f5918aca4c67f15ede13083.png

Q(v) 是该节点赢的次数,N(v) 是该节点模拟的次数,C 是一个常数。
因此我们每次选择的过程如下——从根节点出发,遵循最大最小原则,每次选择己方 UCT 值最优的一个节点,向下搜索,直到找到一个「未完全展开的节点」,根据我们上面的定义,未完全展开的节点一定有未访问的子节点,随便选一个进行扩展。
这个公式虽然我们造不出来,但是我们可以观赏它的巧妙之处,首先加号的前面部分就是我们刚刚说的胜率,然后加号的后面部分函数长这样:

8a0ff38bb811ef68854991462b8b07d1.png
1

随着访问次数的增加,加号后面的值越来越小,因此我们的选择会更加倾向于选择那些还没怎么被统计过的节点,避免了我们刚刚说的蒙特卡洛树搜索会碰到的陷阱——一开始走了歪路。

扩展(expansion)

将刚刚选择的节点加上一个统计信息为「0/0」的节点,然后进入下一步模拟(Simluation)

回溯(Backpropagation)

Backpropagation 很多资料翻译成反响传播,不过我觉得其实极其类似于递归里的回溯,就是从子节点开始,沿着刚刚向下的路径往回走,沿途更新各个父节点的统计信息。

c6b888715f6a9935788590eeae305056.png

再放一次这个图,可以观察一下在模拟过后,新的 0/0 节点,比如这里模拟输了,变成了 0/1,然后它的到根节点上的节点的统计信息的访问次数全部加 1,赢的次数不变。

实现伪代码(python)

def monte_carlo_tree_search(root):
    while resources_left(time, computational power):
        leaf = traverse(root) # leaf = unvisited node 
        simulation_result = rollout(leaf)
        backpropagate(leaf, simulation_result)
    return best_child(root)

def traverse(node):
    while fully_expanded(node):
        node = best_uct(node)
    return pick_univisted(node.children) or node # in case no children are present / node is terminal 

def rollout(node):
    while non_terminal(node):
        node = rollout_policy(node)
    return result(node) 

def rollout_policy(node):
    return pick_random(node.children)

def backpropagate(node, result):
   if is_root(node) return 
   node.stats = update_stats(node, result) 
   backpropagate(node.parent)

def best_child(node):
    pick child with highest number of visits

算法什么时候终止

取决于你什么时候想让他停止,比如说你可以设定一个计算限时,比如五秒后停止计算。
一般来说最佳走法就是具有最高访问次数的节点,这点可能稍微有点反直觉。这样评估的原因是因为蒙特卡洛树搜索算法的核心就是,越优秀的节点,越有可能走,反过来就是,走得越多的节点,越优秀

其实蒙特卡洛树搜索算法已经讲完了,关于算法正确性的证明相信多数人是没兴趣的,基于上面讲的纯粹的蒙特卡洛树搜索其实已经能打败一百个我了!那么讲到这里为止吧….等等,我们还有一点没讲——alpha狗狗到底改进了什么,使得它变成了世界最佳?

aea72fa2d847136ffc0b16a07f4a2b1f.png

它们对狗狗做了什么?

deepmind 将 MCTS 和近年来取得突破性进展的神经网络结合起来,主要是针对上面两个步骤作了改进:

1. 模拟
首先上面四步里,最玄学、感觉最不靠谱的一步是「模拟」,用随机快速走子的方法走完一盘棋,然后记录胜盘和下了多少盘,这一步虽然是蒙特卡洛树搜索的核心,但是并不那么准确。
在 alphago Lee 中,叶子节点的估值是两个部分的加权和:

  • 一种带有手工特征的浅层 softmax 神经网络:采用自定义快速走棋策略的标准走棋评估

  • 估值网络:基于 13 层卷积神经网络的位置评估,训练自 Alpha Go 自我对弈的三千万个不同位置(没有任何两个点来自同一场游戏)
    而 alphago zero 迈出了更远的一步,他们根本就不进行模拟,而是用一个 19 层 CNN 残差神经网络直接评估当前节点。(神经网络可以输出位置评估,得出每个位置的概率向量)
    也就是说,利用神经网络,无需模拟,直接能算出每个位置的概率,可以说是直接消除了玄学问题。

2. 选择
既然已经不是真的通过「模拟」的出赢的次数和已经评估的次数,那么我们之前通过 UCT 值的大小来向下搜索、选择一个未访问的叶子节点的方法也需要作出相应修改。
函数变为: 5be02dc7f9d5a5fde9e8db6f5df8f3f4.png
其中 UCT(v_i, v) 表示从状态(节点) v_i 转移到 v 的价值评估,P(v_i, v)表示从状态 v_i 转移到 v 的概率,或者用一个术语叫做「移动的先验概率」,这个概率是用策略网络训练出来的,基于人类游戏数据集的受监督学习。
有趣的是,在 Deepmind 的蒙特卡洛树搜索变种里,由于监督学习策略网络在实际中表现更好,因此它的结果被用来估测行动先验概率)。那么强化学习策略网络的目的是生成 3 千万位置数据集,用于训练估值网络(估值网络就是我们上一步说的用来替代「模拟」的网络)。
而在 Alphago Zero 中只有一个网络,它既是估值网络也是策略网络。它完全通过从随机初始状态开始进行自我对弈训练。并行训练多个网络,在每个检查点,基于当前最优的神经网络,评选出最优的网络用来生成训练数据。

最后,今年,公众号...文体两开花...多多关注

641371fd6affa7470bd097d39a1228e4.png

有趣的程序员

都在这里(* ̄∇ ̄*)

3f24ee4faf11f1305874e9e70665a6f2.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值