00253 Monte Carlo Tree Search算法


前言

介绍 Monte Carlo Tree Search 算法。

Operating System: Ubuntu 22.04.4 LTS

介绍

Monte Carlo Tree Search(MCTS)算法是一种用于决策过程的启发式搜索算法,它结合了蒙特卡洛方法与树形结构搜索的优势,常用于解决游戏、棋类等具有较复杂状态空间的决策问题。以下是Monte Carlo Tree Search算法的详细介绍:

算法核心思想

MCTS算法的核心思想是通过模拟多次游戏(或问题)的随机过程来评估和选择最优的行动策略。它不需要明确的状态转移模型,而是通过探索(Exploration)与利用(Exploitation)的平衡来构建一个搜索树,并在树上进行选择、扩展、模拟和回溯四个主要步骤。

主要步骤

  1. 选择(Selection)
    • 从根节点开始,选择最优的子节点,直到达到一个未完全扩展的节点(即还有未探索的行动)。
    • 选择的标准通常是基于一个平衡探索与利用的公式,如UCB1(Upper Confidence Bound 1):
      $$
      UCB1 = \frac{W_i}{N_i} + c \sqrt{\frac{\ln N_p}{N_i}}
      $$
      其中,$W_i$ 是节点i的胜利次数,$N_i$ 是节点i的访问次数,$N_p$ 是父节点的访问次数,c是探索参数。
  2. 扩展(Expansion)
    • 对于选定的未完全扩展的节点,随机选择一个未探索的行动,并创建一个新的子节点。
  3. 模拟(Simulation)
    • 从新创建的节点开始,进行随机游戏直到达到游戏结束的状态。
    • 模拟的目的是为了评估该节点的潜在价值,通常是通过胜/负的结果。
  4. 回溯(Backpropagation)
    • 将模拟的结果回溯到根节点,更新所有经过节点的统计数据(如访问次数和胜利次数)。

算法流程

  1. 初始化:创建一个根节点代表当前游戏状态。
  2. 循环执行以下步骤直到满足停止条件(如时间限制或迭代次数):
    • 选择:从当前树中选择一个节点。
    • 扩展:如果选择的节点不是终端节点,则创建一个新的子节点。
    • 模拟:从新节点开始进行随机模拟,直到得到一个结果。
    • 回溯:使用模拟的结果更新回溯路径上的所有节点的统计数据。
  3. 选择具有最高统计胜率的行动。

优点

  • 不需要显式的模型,适用于模型未知或难以获取的情况。
  • 能有效处理具有高分支因子的搜索空间。
  • 通过模拟,可以处理包含随机性的问题。

缺点

  • 计算资源消耗较大,尤其是对于复杂问题。
  • 对于需要即时决策的问题,可能存在时间压力。

应用

MCTS算法在许多领域都有应用,尤其是在棋类游戏(如围棋、国际象棋)中取得了显著的成功。AlphaGo就是结合了深度学习与MCTS算法的著名例子。

通过上述步骤,MCTS算法能够在复杂的决策问题中找到较优的解决方案,它是现代人工智能领域中非常重要的一种算法。

UCB1

UCB1(Upper Confidence Bound 1)公式是一种用于多臂老虎机(Multi-Armed Bandit)问题的策略,它旨在平衡探索(Exploration)和利用(Exploitation)之间的权衡。在多臂老虎机问题中,一个决策者需要在一系列潜在的行动(称为“臂”)中选择一个,每个臂都有不同的平均奖励,但具体的奖励分布是未知的。UCB1公式能够帮助决策者在没有完整信息的情况下做出选择。

UCB1公式如下:

$$
UCB1(a) = \bar{X}_a + \sqrt{\frac{2 \ln n}{n_a}}
$$

其中:

  • $\bar{X}_a$ 是到目前为止,从选择臂 $a$ 得到的平均奖励。
  • $n$ 是到目前为止总的尝试次数。
  • $n_a$ 是到目前为止选择臂 $a$ 的次数。
  • $\ln$ 是自然对数。

这个公式由两部分组成:

  1. 平均奖励 $\bar{X}_a$:这部分代表了到目前为止,选择臂 $a$ 的平均奖励值。它反映了到目前为止对臂 $a$ 的了解程度,即“利用”部分。

  2. 探索因子 $\sqrt{\frac{2 \ln n}{n_a}}$:这部分代表了探索新臂的不确定性。它鼓励选择那些还没有被充分尝试的臂。随着 $n_a$ 的增加,这个值会减小,意味着随着我们更频繁地尝试某个臂,我们对其不确定性的减少,因此探索的必要性也降低。

UCB1公式的工作原理如下:

  • 当 $n_a$ 很小时(即臂 $a$ 被尝试的次数很少),探索因子会很大,这会使得 $UCB1(a)$ 的值变大,从而鼓励选择这个臂,即使它的平均奖励 $\bar{X}_a$ 不是最高的。
  • 当 $n_a$ 增加时,探索因子会减小,$UCB1(a)$ 的值会更多地由平均奖励 $\bar{X}_a$ 决定,这反映了利用已知的最佳臂的趋势。

UCB1算法的一个关键特性是它具有低后悔(Regret)保证,即它能保证在长期运行中,算法的总后悔(即与总是选择最优臂相比所失去的奖励)是有限的。

在实际应用中,UCB1算法通过在每一步选择具有最高UCB1值的臂来操作,从而平衡探索和利用,以期望在长期内获得最大的累积奖励。

蒙特卡洛树搜索(MCTS)算法实现

以下是使用Python实现的MCTS算法,应用于井字棋(Tic-Tac-Toe)游戏。

代码实现

import math
import random
from copy import deepcopy

class GameState:
    def __init__(self):
        self.board = [[0 for _ in range(3)] for _ in range(3)]
        self.player = 1  # 1 for player one, -1 for player two
        self.game_over = False
        self.winner = None

    def is_game_over(self):
        # Check rows
        for row in self.board:
            if sum(row) == 3:
                self.winner = 1
                self.game_over = True
                return True
            if sum(row) == -3:
                self.winner = -1
                self.game_over = True
                return True
        # Check columns
        for col in range(3):
            if self.board[0][col] + self.board[1][col] + self.board[2][col] == 3:
                self.winner = 1
                self.game_over = True
                return True
            if self.board[0][col] + self.board[1][col] + self.board[2][col] == -3:
                self.winner = -1
                self.game_over = True
                return True
        # Check diagonals
        if self.board[0][0] + self.board[1][1] + self.board[2][2] == 3:
            self.winner = 1
            self.game_over = True
            return True
        if self.board[0][0] + self.board[1][1] + self.board[2][2] == -3:
            self.winner = -1
            self.game_over = True
            return True
        if self.board[0][2] + self.board[1][1] + self.board[2][0] == 3:
            self.winner = 1
            self.game_over = True
            return True
        if self.board[0][2] + self.board[1][1] + self.board[2][0] == -3:
            self.winner = -1
            self.game_over = True
            return True
        # Check for tie
        if all(all(cell != 0 for cell in row) for row in self.board):
            self.game_over = True
            return True
        return False

    def get_possible_moves(self):
        moves = []
        for i in range(3):
            for j in range(3):
                if self.board[i][j] == 0:
                    moves.append((i,j))
        return moves

    def make_move(self, move):
        if self.board[move[0]][move[1]] == 0:
            self.board[move[0]][move[1]] = self.player
            self.player *= -1
        else:
            print("Invalid move")
        self.is_game_over()

class MCTSNode:
    def __init__(self, game_state, parent=None, move=None):
        self.game_state = game_state
        self.parent = parent
        self.move = move
        self.children = []
        self.visits = 0
        self.value = 0.0

    def is_fully_expanded(self):
        possible_moves = self.game_state.get_possible_moves()
        return len(self.children) == len(possible_moves)

def select_child(node):
    c = math.sqrt(2)
    best_ucb = -float('inf')
    best_child = None
    for child in node.children:
        if child.visits == 0:
            ucb = float('inf')
        else:
            ucb = (child.value / child.visits) + c * math.sqrt(math.log(node.visits) / child.visits)
        if ucb > best_ucb:
            best_ucb = ucb
            best_child = child
    return best_child

def expand(node):
    possible_moves = node.game_state.get_possible_moves()
    for move in possible_moves:
        if not any(child.move == move for child in node.children):
            new_game_state = deepcopy(node.game_state)
            new_game_state.make_move(move)
            new_node = MCTSNode(new_game_state, parent=node, move=move)
            node.children.append(new_node)
            return new_node
    return node

def simulate(node):
    game = deepcopy(node.game_state)
    while not game.is_game_over():
        possible_moves = game.get_possible_moves()
        move = random.choice(possible_moves)
        game.make_move(move)
    if game.winner == 1:
        return 1
    elif game.winner == -1:
        return -1
    else:
        return 0

def backpropagate(node, result):
    while node is not None:
        node.visits += 1
        node.value += result
        node = node.parent

def mcts(root_node, iterations):
    for _ in range(iterations):
        node = root_node
        while not node.game_state.is_game_over() and node.is_fully_expanded():
            node = select_child(node)
        if not node.game_state.is_game_over():
            expanded_node = expand(node)
            if expanded_node.game_state.is_game_over():
                result = get_game_result(expanded_node.game_state)
            else:
                result = simulate(expanded_node)
        else:
            result = get_game_result(node.game_state)
        backpropagate(node, result)
    best_child = max(root_node.children, key=lambda child: child.visits)
    return best_child.move

def get_game_result(game_state):
    if game_state.winner == 1:
        return 1
    elif game_state.winner == -1:
        return -1
    else:
        return 0

def main():
    game = GameState()
    root = MCTSNode(game)
    best_move = mcts(root, iterations=1000)
    print("Best move:", best_move)

if __name__ == "__main__":
    main()

代码解释

  1. GameState类

    • 表示井字棋的游戏状态,包括棋盘、当前玩家和游戏结束状态。
    • 提供判断游戏是否结束、获取可能走法和执行走法的方法。
  2. MCTSNode类

    • 表示MCTS树中的节点,包含游戏状态、父节点、走法、访问次数和累积价值。
    • 提供判断节点是否完全扩展的方法。
  3. **选择子节点 (select_child函数)**:

    • 使用UCB公式选择具有最高UCB值的子节点。
  4. **扩展节点 (expand函数)**:

    • 从选择的叶子节点扩展一个新的子节点,选择一个未探索的走法。
  5. **模拟 (simulate函数)**:

    • 从新扩展的节点开始进行随机模拟,直到游戏结束,返回结果。
  6. **回溯 (backpropagate函数)**:

    • 将模拟结果反向传播回树的各个节点,更新访问次数和累积价值。
  7. **MCTS主函数 (mcts函数)**:

    • 执行选择、扩展、模拟和回溯四个步骤,迭代指定次数。
    • 最终选择访问次数最多的子节点作为最佳走法。
  8. **主程序 (main函数)**:

    • 初始化游戏状态和根节点,运行MCTS算法,输出最佳走法。

通过运行此代码,可以观察MCTS算法在井字棋游戏中的决策过程,并找到一个不错的走法。

结语

第二百五十三篇博文写完,开心!!!!

今天,也是充满希望的一天。


文章作者: LuYF-Lemon-love
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LuYF-Lemon-love !
  目录