蒙特卡洛树搜索在实践中是如何实现的

Posted

技术标签:

【中文标题】蒙特卡洛树搜索在实践中是如何实现的【英文标题】:How is Monte Carlo Tree Search implemented in practice 【发布时间】:2018-08-30 00:47:06 【问题描述】:

我在一定程度上了解算法的工作原理。我不完全理解的是算法是如何实际在实践中实现的。

我有兴趣了解对于相当复杂的游戏(可能是国际象棋)来说,最佳方法是什么。即递归方法?异步?同时?平行线?分散式?数据结构和/或数据库?

-- 我们期望在单台机器上看到什么类型的限制? (我们可以在多个内核上同时运行...gpu 吗?)

-- 如果每个分支都导致一个全新的游戏被玩,(这可能达到数百万)我们如何保持整个系统的稳定? & 我们如何重用已经玩过的分支?

【问题讨论】:

我了解这可能过于宽泛,但在标记此内容之前提供任何链接/参考资料将不胜感激。 【参考方案1】:

递归方法?异步?同时?平行线?分散式?数据结构和/或数据库

在 MCTS 中,递归实现没有什么意义(这在其他树搜索算法中很常见,例如基于 minimax 的算法),因为您总是从当前游戏状态(根节点)直到您选择评估的游戏状态(终端游戏状态,除非您选择使用播放阶段的深度限制和启发式评估函数进行非标准实现)。使用while 循环的更明显的实现就可以了。 如果这是您第一次实现该算法,我建议您先使用单线程实现。不过,这是一种相对容易并行化的算法,对此有多篇论文。您可以简单地并行运行多个模拟(其中模拟 = 选择 + 扩展 + 播放 + 反向传播)。您可以尝试确保在反向传播期间所有内容都得到干净的更新,但您也可以简单地决定根本不使用任何锁/阻塞等,无论如何,所有模拟中已经有足够的随机性,所以如果您从几个模拟中丢失信息由于简单地实现了并行化,这里和那里确实不会造成太大的伤害。 对于数据结构,与minimax 之类的算法不同,您实际上确实需要显式构建一棵树并将其存储在内存中(随着算法的运行逐渐构建)。因此,您需要一个带有Nodes 的通用树数据结构,其中包含一个后继/子Nodes 列表,以及一个指向父Node 的指针(模拟结果的反向传播所必需的)。

我们希望在单台机器上看到什么类型的限制? (我们可以在多个内核上同时运行...gpu 吗?)

可以跨多个内核运行(请参阅上面关于并行化的要点)。我没有看到算法的任何部分特别适合 GPU 实现(没有大型矩阵乘法或类似的东西),因此 GPU 不太可能有趣。

如果每个分支都会导致一个全新的游戏被玩,(这可能会达到数百万)我们如何保持整个系统的稳定? & 我们如何重用已经玩过的分支?

在最常用的实现中,算法在扩展阶段(在选择阶段之后遇到的第一个节点)每次迭代/模拟只创建一个新节点存储在内存中。在同一模拟的播放阶段生成的所有其他游戏状态根本不会让任何节点存储在内存中。这可以控制内存使用情况,这意味着您的树只会相对缓慢地增长(以每个模拟 1 个节点的速度)。这确实意味着您对先前模拟的分支的重用会稍微减少,因为您不会将看到的所有内容都存储在内存中。您可以选择为扩展阶段实施不同的策略(例如,为播放阶段生成的所有游戏状态创建新节点)。但是,如果您这样做,则必须仔细监控内存使用情况。

【讨论】:

感谢您的回答!非常冗长。最后一件事,我是否正确假设整个系统在每个节点上执行一个新游戏,然后可以生成其他新的和完整的游戏(尽管内存中没有存储任何内容)——总而言之,只是为了满足第一场比赛 @rambossa 每个节点只对应一个游戏状态,而不是一个完整的游戏(假设你有一个确定性游戏,比如国际象棋)。向树中添加节点时,可以选择在节点中也存储相应的游戏状态,也可以选择不存储。如果您存储它们,通过相同节点的后续选择阶段会更快,因为它们不再需要(重新)计算这些游戏状态。但是,早期的模拟可能会更慢,因为您必须在应用移动之前复制游戏状态(以避免同时修改存储在父节点中的游戏状态对象)

以上是关于蒙特卡洛树搜索在实践中是如何实现的的主要内容,如果未能解决你的问题,请参考以下文章

蒙特卡洛树搜索:井字游戏的实现

为啥蒙特卡洛树搜索会重置树

蒙特卡洛树搜索介绍

蒙特卡洛树搜索介绍

蒙特卡洛树搜索介绍

蒙特卡洛树搜索介绍