贡献者: int256
本文使用了 python 3.7 中提供的特性(future
中的 annotations
)与 numpy
库。使用版本 pytohn 3.12.2 与 numpy 1.26.4 可以确保无问题运行。全部代码可以在笔者的 Github 仓库查看:
https://github.com/tripleInt/MCTS-TTT/,或 Pastebin https://paste.ubuntu.com/p/NQtdDSx3sY/。
前面已经讨论过蒙特卡洛树搜索算法的理论,下面通过讲解例题进行实战练习,这利于我们更深入地理解这算法。首先回顾例题:
在实现蒙特卡洛树搜索这个算法前,我们需要先做一些准备,定义好游戏的各种内容类。
因为要处理连续问题,我们可以使用 “求和” 的方式检查,故可以取特殊值
其中 __init__
方法相当于是构造函数,__repr__
方法提供了一个将类转化为 str
的方式。具体原理是利用了 python 的魔法方法。
然后对于每一步操作,我们可以考虑为是落点与棋子类型的组合。故定义一个操作类 Move
:
STATUS
常量存储了后面棋盘中每个位置的数字代表这个位置的状态的情况,X
、O
分别对应
显然对于这个问题来说,搜索的状态应该是当前棋盘的情况。我们定义一个表示状态的类 State
并实现一些方法帮助我们在后面进行搜索。
首先考虑其构造函数,需要记录的信息,显然有当前棋盘的情况(使用 numpy
提供的 np.array
来表示)、下一步应当哪方下棋。我们额外开辟一个属性用来记录需要多少连续棋子可以赢得这场游戏。故可以写出 __init__
方法:
我们经常会需要获取棋盘的形状(即大小),故再定义一个属性用来表示棋盘大小。这里使用装饰器 @property
,这类似于定义了一个属性的 get
。
接下来考虑到我们需要判断当前游戏局势(判断游戏是否结束),故类似的定义一个 @property
装饰的 result
表示当前局势与 isOver
表示是否结束(已经不能继续下棋):
np.min
和 np.max
来帮助我们通过求和解决判断是否有棋子连续到足够个数的一方。
接下来需要考虑下棋操作。首先考虑搜索需要用到当前的所有可能的走法,故这里编写一个方法来实现这个功能:
然后考虑需要判断某种走法对当前的棋盘来说是否合法,由位置(是否在棋盘内、该位置是否有棋子)和这种走法下棋的一方是否是State 记录下来的将要下棋的一方共同决定:
以及需要根据走法获取一个在当前棋盘进行该走法后的下一状态,这也可以编写一个方法来实现,这里需要注意是返回一个新的 State
对象,用到了 python 3.7 的 future 库中的特性。同时要分清什么时候使用 `self.checkerboard`,什么时候是更新返回的 newCheckerboard
:
最后实现一个方法来输出当前棋盘状态(这是题目要求的):
对于不同的电脑环境、不同的终端、不同的字体可能每个字符的长度不同,需要适当调整这里的第
接下来考虑蒙特卡洛树的每个结点。将其定义为一个类 MCTSNode
:
shuffle
可以直接使用 random
库提供的 random.shuffle
。
接下来是实现蒙特卡洛树搜索算法中的通过 UCB 选择子节点扩展:
其中q
就是 UCB 公式式 1 中的
需要注意这里的 q
应当使用 dict.get
的方式,因为有可能还没有更新过,否则就要使用 defaultdict
来定义 self._results
。
下面实现蒙特卡洛搜索需要的各种操作(Expand、Simulation 对应 Rollout,Back Propagate)。
这里反向传播使用了尾递归的方式,也可以使用 while 循环的方法。
同样需要注意这里 backpropagate
也使用的是 dict.get
方法。
最后是实现搜索树。对于一棵树我们往往只需要记录根节点就获得了所有信息。
接下来是实现蒙特卡洛树搜索中的操作。
可以直接根据前面的 MCTSNode.bestSon()
方法获得到要选择的结点,一直 while
循环到叶子结点就可以了:
根据 UCB 算法进行搜索扩展找最好的操作。
这里使用时间限制搜索扩展。每次选择最佳走法进行更新即可。这里限制搜索扩展时间为
全部代码整合后的可以在笔者的 Github 仓库查看: https://github.com/tripleInt/MCTS-TTT/,或 Pastebin:https://paste.ubuntu.com/p/NQtdDSx3sY/。
友情链接: 超理论坛 | ©小时科技 保留一切权利