目录

0. 序言

1. 蒙特卡洛算法的前身今世

2. 蒙特卡洛搜索算法的原理

2.1 Exploration and Exploitation(探索与利用)

2.2 Upper Confidence Bounds(UCB)

2.3 蒙特卡罗搜索的基本操作

2.3.1 选择

2.3.2 扩展

2.3.3 模拟

2.3.4 反向传播

2.4 蒙特卡洛搜索算法的流程图

3. 蒙特卡洛搜索算法示例

4. 蒙特卡洛搜索树算法的实现

4.1 TreeNode树节点类

4.2 选择

4.3 扩展

4.4 模拟

4.5 反向传播

5. 基于蒙特卡洛搜索算法的五子棋示例

6. 参考


0. 序言

相信许多人和我一样,对人工智能的认识始于2016年,Deepmind推出的AlphaGo与围棋大师李世石的惊世一战,让“人工智能”这个名词,被千家万户所熟知。尽管深度神经网络早在很多年前凭借其在图像识别等方面一骑绝尘的表现成为科研机构研究的热点,自此之后,人工智能才变成了一股时代的浪潮,浩浩荡荡裹挟着我们向着陌生未知的领域奔涌而去。

最近一直在做棋类博弈相关方面的探究,各种算法千变万化,都离不开对蒙特卡洛搜索算法(Monte Carlo Tree Search)这一经典搜索算法的使用,本文将以讲清基本的蒙特卡洛搜索算法(后文简称为MCTS)为目的,介绍这一算法涉及的方方面面的知识点,力求让读者以及自己能彻底琢磨透这一算法。

1. 蒙特卡洛算法的前身今世

人们对游戏AI的探索始于完美信息博弈游戏(perfect information games),这类游戏的特点就是游戏参与者没有隐藏的信息(例如英雄联盟等moba类游戏的战争迷雾),也没有任何不确定因素(比如玩大富翁的时候要掷色子)。这类游戏显然是最好研究的(像星际争霸这样的游戏AI至今还没有战胜人类)。因为在游戏中的每一步都是确定的,所以理论上,我们是可以穷举所有的情况从而构建一颗“游戏树”(如下图井字棋的游戏树),我们可以按照依次取最大值最小值的原则不断向下搜索(因为敌我双方的目的是相反的),这种方法就叫做Minmax方法。

然而,这种方法要求我们要穷举所有可能的情况,这在绝大多数的游戏中显然是不现实的,因此人们又想到寻求一个函数对当前局面进行判断,在树扩展的过程中及时对这棵树进行剪枝,这种方法在国际象棋这个领域取得了一定成果,但是对大部分游戏,依然不行。

而在此类领域,MCTS体现其强大的能力。我们想象,有两个对围棋一无所知的人,小明和小白,他们虽然不知道怎么下才能赢,但是他们依然遵守规则坚持不懈的下棋。经过一番激烈的菜鸡互啄,小明最终赢了小白,这个时候小明对围棋的认识加深了一步——“原来第一步要下天元才能赢啊!”(这不正确!),小白对围棋的认识也加深了一步——“第一步下在棋盘边缘上会输啊!”,小明和小白继续下棋,下到天荒地老,他们二人对围棋的认识都在不断加深,好多好多年过去了,小明赢了6000盘,小白赢了4000盘,那么我们就可以认为,在这种情况下小明胜率60%,小白胜率40%。这个例子告诉我们什么呢?大力出奇迹啊!通过大量的模拟,我们可以提高对某一种事物的认识,并对当前局面有一个更好的估计,这就是MCTS的基本思想,下面我们对其原理和步骤进行进一步介绍。

2. 蒙特卡洛搜索算法的原理

2.1 Exploration and Exploitation(探索与利用)

在具体讲MCTS之前,我们首先介绍Exploration and Exploitation,这个在强化学习经常遇到的一个困境和难题。回到我们上面提到的小明和小白下围棋的例子,什么是“Exploration”?探索就是向未知的领域勇敢的进发。如果小明不尝试新的招法,他永远不会发现,开局最好的下法不是下天元,是四个角星位附近。什么是“Exploitation”?利用就是经验万岁。经验有时候虽然不靠谱,但大多数时候还是管用的,而且你下的越多,你的经验就越靠谱。探索也好,利用也罢,怎么去确定他们的分配比例呢?这就是难题所在了,我们可以设置一个概率参数p,以p的概率探索,以1-p的概率利用。还有很多种方法,我们下面要介绍的UCB也是其中的一种。

2.2 Upper Confidence Bounds(UCB)

在本文中,我们采用UCB方法来确定何时进行探索和利用。公式如下:

其中 v_i 是节点估计的值(比如胜率),n_i 是节点被访问的次数,而 N 则是其父节点已经被访问的总次数。C 是可调整参数。(引用自蒙特卡洛搜索MCTS)

前者代表我们的经验,而后者代表我们的勇气。我们重点看一下我们的“勇气”。后面的值越大,代表着相对父节点的访问,我们访问当前这个子节点的次数偏少,因此我们要多多关注它,反之,则正好相反。

2.3 蒙特卡罗搜索的基本操作

算法的每次迭代分为四步——选择扩展模拟反向传播。我们先简单介绍一下这四步,后文我们会给一个详细迭代的例子。

2.3.1 选择

基于2.2中的选择算法,从根节点开始,我们选择采用UCB计算得到的最大的值的孩子节点,如此向下搜索,直到我们来到树的底部的叶子节点(没有孩子节点的节点),等待下一步操作。

2.3.2 扩展

到达叶子节点后,如果还没有到达终止状态(比如五子棋的五子连星),那么我们就要对这个节点进行扩展,扩展出一个或多个节点(也就是进行一个可能的action然后进入下一个状态)。

2.3.3 模拟

之后,我们基于目前的这个状态,根据某一种策略(例如random policy)进行模拟,直到游戏结束为止,产生结果,比如胜利或者失败。

2.3.4 反向传播

根据模拟的结果,我们要自底向上,反向更新所有节点的信息,具体更新哪些信息在后面示例和实现中会讲。

2.4 蒙特卡洛搜索算法的流程图

具体的解析在第三节会讲。

3. 蒙特卡洛搜索算法示例

如果只看上面的解析,可能会因为语言不准确等原因,产生许多误解,因此首先先放上一个标准算法,然后再实际操作一个例子。

下面我将用一个例子,去讲解蒙特卡洛算法的实现。

第一次迭代:

1. 扩展:没有可以选择的节点,因此我们要进行扩展。我们假设从根节点一共有三个可选的动作,一共可以扩展出三个可选的节点。

2. 选择:此时对于这三个节点,其值均为0/0(即访问次数为0,胜利次数也为0),带入公式,其UCB值均为正无穷。我们就选择第一个节点。

3. 模拟:从当前选择的节点开始,根据一定的policy function进行模拟,直到到达terminal state。policy function在后面实现过程中会细讲。我们假设这次模拟的结果是胜利。

4. 反向传播:将选择的节点的值更新为1/1,如下图所示。因为没有相应的父节点,因此这里还不是很能体现反向传播的用法。我们会通过接下来的迭代来体现。

第二次迭代与第三次迭代:

这两次迭代是一样的,因为我们根据UCB公式,未被访问过的节点其值是正无穷,也就是说,是一定会被选择到的。我们假设这两次模拟的结果都是失败(最右侧节点的值忘记更新了)。

第四次迭代:

这一次迭代,我们假设是最左侧节点的UCB值最大,再此节点的基础上进行扩展。我们假设可以扩展出两个节点。注意要不断更新其父节点的值。

多次迭代:

后面的迭代过程大同小异,我做了一个粗糙的动图来展示整个过程,最需要注意的就是反向传播的问题。

4. 蒙特卡洛搜索树算法的实现

前几天尝试过自己实现一下这个算法,很可惜,出了一些bug,目前还没有调试出问题所在。所以只能用别人写好的现成的代码讲了。我参考的是github上的实现mcts,这个代码已经被封装成了package,可以简单的调用,我觉得它很好的一点是可以指定模拟的时间,而且其实现非常的简单,完全按照上述给的算法伪代码实现的。在讲解完这份代码后,我会用其实现一个基于蒙特卡洛搜算算法的智障五子棋AI(智障点明了这个AI根本无法使用)。

4.1 TreeNode树节点类

class treeNode():def __init__(self, state, parent):self.state = stateself.isTerminal = state.isTerminal()self.isFullyExpanded = self.isTerminalself.parent = parentself.numVisits = 0self.totalReward = 0self.children = {}

这个类一共包括了7个属性。

state需要我们自己去定义这个类,后面例子中会讲到。

isTerminal是代表当前的状态是否是终结态,从这里我们也可以看到,isTerminal()是我们在state类中一定要实现的方法。

isFullyExpanded是指节点是否完全扩展,不理解这个意思的可以回头看第三节的算法描述和举例。

parent就是指当前节点的父节点。

numVisits代表节点被访问的次数。

totalReward是获取的奖励,如果我们定义1为胜利,0为失败,那么我们在上文中定义的3/4之类就可以表达为totalReward/numVisits。

children是当前节点的子节点的集合。

4.2 选择

def getBestChild(self, node, explorationValue):bestValue = float("-inf")bestNodes = []for child in node.children.values():nodeValue = child.totalReward / child.numVisits + explorationValue * math.sqrt(2 * math.log(node.numVisits) / child.numVisits) # UCB公式if nodeValue > bestValue:bestValue = nodeValuebestNodes = [child]elif nodeValue == bestValue:bestNodes.append(child)return random.choice(bestNodes)  # 如果有多个节点值相等,从中随机选择一个。

我们前文已经讲的非常清楚了,我们要计算所有节点的UCB值,从中选取最大的那个,以解决“探索和利用难题”。

4.3 扩展

def expand(self, node):actions = node.state.getPossibleActions()for action in actions:if action not in node.children:newNode = treeNode(node.state.takeAction(action), node)node.children[action] = newNodeif len(actions) == len(node.children):node.isFullyExpanded = Truereturn newNoderaise Exception("Should never reach here")

这个地方又出现了我们自己写的state要实现的几个方法,getPossibleActions,takeAction。

这里的逻辑也很简单,我们获取当前状态下所有可能的动作,把没有加入的动作导致的state全部加进来,就能够得到fullyexpanded的节点了。

4.4 模拟

def search(self, initialState):self.root = treeNode(initialState, None)if self.limitType == 'time':timeLimit = time.time() + self.timeLimit / 1000while time.time() < timeLimit:self.executeRound()else:for i in range(self.searchLimit):self.executeRound()bestChild = self.getBestChild(self.root, 0)return self.getAction(self.root, bestChild)def executeRound(self):node = self.selectNode(self.root)reward = self.rollout(node.state)self.backpropogate(node, reward)def selectNode(self, node):while not node.isTerminal:if node.isFullyExpanded:node = self.getBestChild(node, self.explorationConstant)else:return self.expand(node)return node

这一段代码糅合了很多东西,简单来说,就是选择了节点,不找到terminal绝不停止,找到了就返回reward,然后反向传播。

4.5 反向传播

这个最简单了,从当前节点开始,一直往上走就可以了。

def backpropogate(self, node, reward):while node is not None:node.numVisits += 1node.totalReward += rewardnode = node.parent

完整的代码见https://github.com/pbsinclair42/MCTS/blob/master/mcts.py

5. 基于蒙特卡洛搜索算法的五子棋示例

我们主体是要实现两个类,state类和action类。

class GoBangState():def __init__(self,board, currentPlayer=1, last_move = [0,0]):self.board = board   #五子棋棋盘self.currentPlayer = currentPlayer  # 执黑还是执白,1是黑,-1是白self.last_move = last_move # 上一手棋的位置def getPossibleActions(self):"""最开始考虑用以下的代码,但是搜索空间实在是太大了possibleActions = []for i in range(len(self.board)):for j in range(len(self.board[i])):if self.board[i][j] == 0:possibleActions.append(Action(player=self.currentPlayer, x=i, y=j))return possibleActions"""# 此处改成在上一手棋周围进行搜索possibleActions = []search_size = 1while len(possibleActions)==0:for i in range(self.last_move[0]-search_size,self.last_move[0]+search_size+1):for j in range(self.last_move[1]-search_size,self.last_move[1]+search_size+1):if i<0 or j<0 or i>=len(self.board) or j>=len(self.board[i]):continueif self.board[i][j] == 0:possibleActions.append(Action(player=self.currentPlayer, x=i, y=j))search_size+=1return possibleActionsdef takeAction(self, action):newState = deepcopy(self)newState.board[action.x][action.y] = action.playernewState.currentPlayer = self.currentPlayer * -1return newStatedef isTerminal(self):# judge函数引自https://www.jianshu.com/p/cd3805a56585flag = judge(self.board)if flag!=0:return True# 要注意无处落子的情况for i in range(len(self.board)):for j in range(len(self.board[i])):if self.board[i][j]==0:return Falsereturn Truedef getReward(self):flag = judge(self.board)return flag
class Action():def __init__(self, player, x, y):self.player = playerself.x = xself.y = ydef __str__(self):return str((self.x, self.y))def __repr__(self):return str(self)def __eq__(self, other):return self.__class__ == other.__class__ and self.x == other.x and self.y == other.y and self.player == other.playerdef __hash__(self):return hash((self.x, self.y, self.player))

下面附上judge函数,来自https://www.jianshu.com/p/cd3805a56585:

def judge(board):for n in range(15):# 判断垂直方向胜利flag = 0# flag是一个标签,表示是否有连续以上五个相同颜色的棋子for b in board:if b[n] == 1:flag += 1if flag == 5:return 1else:# else表示此时没有连续相同的棋子,标签flag重置为0flag = 0flag = 0for b in board:if b[n] == 2:flag += 1if flag == 5:return -1else:flag = 0# 判断水平方向胜利flag = 0for b in board[n]:if b == 1:flag += 1if flag == 5:return 1else:flag = 0flag = 0for b in board[n]:if b == 2:flag += 1if flag == 5:return -1else:flag = 0# 判断正斜方向胜利for x in range(4, 25):flag = 0for i, b in enumerate(board):if 14 >= x - i >= 0 and b[x - i] == 1:flag += 1if flag == 5:return 1else:flag = 0for x in range(4, 25):flag = 0for i, b in enumerate(board):if 14 >= x - i >= 0 and b[x - i] == 2:flag += 1if flag == 5:return -1else:flag = 0# 判断反斜方向胜利for x in range(11, -11, -1):flag = 0for i, b in enumerate(board):if 0 <= x + i <= 14 and b[x + i] == 1:flag += 1if flag == 5:return 1else:flag = 0for x in range(11, -11, -1):flag = 0for i, b in enumerate(board):if 0 <= x + i <= 14 and b[x + i] == 2:flag += 1if flag == 5:return -1else:flag = 0return 0

获取输出的调用代码如下:

state = GoBangState(board._board,-1,[row,col])
m = mcts(timeLimit=30000,rolloutPolicy=weightPolicy)
action = m.search(initialState=state)

完整的代码就不放出来了,只是做一个示例,里面抄袭别人代码的内容太多了,哪天全部替换成自己写的代码了再放上去,哈哈哈!

6. 参考

Introduction to Monte Carlo Tree Seach

蒙特卡洛搜索算法的python实现

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

蒙特卡洛搜索

python五子棋

文章中可能会出现不少错误,欢迎大家批评指正!

面向初学者的蒙特卡洛树搜索MCTS详解及其实现相关推荐

  1. 强化学习(八):Dyna架构与蒙特卡洛树搜索MCTS

    强化学习(八):Dyna架构与蒙特卡洛树搜索MCTS   在基于表格型强化学习方法中,比较常见的方法有动态规划法.蒙特卡洛法,时序差分法,多步引导法等.其中动态规划法是一种基于模型的方法(Model- ...

  2. 蒙特卡洛树搜索 MCTS

    原文地址 http://mcts.ai/about/index.html 什么是 MCTS? 全称 Monte Carlo Tree Search,是一种人工智能问题中做出最优决策的方法,一般是在组合 ...

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

    更新 2017.2.23有更新,见文末. MCTS与UCT 下面的内容引用自徐心和与徐长明的论文<计算机博弈原理与方法学概述>: 蒙特卡洛模拟对局就是从某一棋局出发,随机走棋.有人形象地比 ...

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

     转自: http://www.cnblogs.com/xmwd/p/python_game_based_on_MCTS_and_UCT_RAVE.html 更新 2017.2.23有更新,见文末 ...

  5. 蒙特卡洛树搜索 MCTS 入门

    引言   你如果是第一次听到蒙特卡洛,可能会认为这是一个人名.那么你就大错特错,蒙特卡洛不是一个人名,而是一个地方,还一个赌场名!!!但是这不是我们的重点.   我们今天的主题就是入门蒙特卡洛树搜索, ...

  6. 蒙特卡洛树搜索(MCTS)的实例代码

    另一篇博客对代码的讲解 原理: 在当前树节点(设为A)状态下,如果所有子节点都展开了,则按UCT算法选择最优节点作为当前节点,循环下去,直到该节点有未展开的子节点,则从未展开的子节点里瞎选一个并展开它 ...

  7. 蒙特卡洛树搜索(MCTS)实现简易五子棋AI

    蒙特卡洛树搜索算法可以通过自我对弈模拟得到不同状态分支中获胜的概率,从而获得最优的策略.代码部分可以分为Node类和State类.Node类通过关联父节点和子节点实现树结构,同时保存每个节点的属性:S ...

  8. 围棋AI,蒙特卡洛树搜索

    目录 1 蒙特卡罗方法(Monte Carlo method) 2. 蒙特卡洛树搜索(Monte Carlo Tree Search,MCTS) 3 Upper Confidence Bounds(U ...

  9. 蒙特卡洛树搜索(MCTS)详解

    蒙特卡洛树搜索(MCTS)详解 蒙特卡洛树搜索是一种经典的树搜索算法,名镇一时的 AlphaGo 的技术背景就是结合蒙特卡洛树搜索和深度策略价值网络,因此击败了当时的围棋世界冠军.它对于求解这种大规模 ...

最新文章

  1. mysql 集群操作系统_高性能MySQL集群详解(二)
  2. Hive应用:外部分区表
  3. DFT实训教程笔记3(bibili版本)-SOC Scan Implementtation Scan Practice Session II
  4. 使用 GitHub, Jekyll 打造自己的免费独立博客
  5. 有意思的前端函数面试题
  6. success 已正常处理 hide_最新微信小程序授权的详细处理思路(一)
  7. 基于pip的安装lxml库报错解决方案
  8. checkA.php,php window平台模拟checkdnsrr函数检测_php
  9. 2012,新的一年,新的开始
  10. hdu 2873 Bomb Game 博弈论
  11. Packt发布了2018年技能提升报告
  12. 【代码源 Div1 - 108】#464. 数数(主席树,区间比k小的数的个数)HDU4417
  13. mac apache php.ini,Mac自带的Apache使用详解
  14. 上完选修计算机绘图课心得,【高师院校化工制图课教学中存在的问题与对策】 一周化工制图实训课的心得体会...
  15. NanoPi NEO3上手日记第一天——把玩&刷固件
  16. 【笔记】架构整洁之道
  17. office安装下载
  18. php工作心得简50字,50字简短个人工作总结
  19. zabbix代理服务器配置
  20. 鼠眼看Linux调度器 by raise_sail @ chinaunix

热门文章

  1. 【FineBI】使用FineBI制作自定义地图过程
  2. 使用计算机注意什么时候,【干货】使用笔记本电脑一定注意这几点,不然会严重影响电脑寿命...
  3. 英语作文万能句子计算机专业,英语作文万能句子(精选12篇)
  4. 2008情人节祝福短信大全
  5. java: 无法访问com.google.protobuf.GeneratedMessageV3
  6. 代码操纵Outlook 2010日历显示第二时区
  7. 电分糊涂日记之《电路基本概念定律》
  8. 一条线直销,一条线循环简析
  9. RapidShare的下载方法
  10. 2020年写给自己的一封信