需要确定蒙特卡洛树搜索的基本步骤。通常分为四个阶段:选择(Selection)、扩展(Expansion)、模拟(Simulation)和回溯(Backpropagation)。选择阶段是从根节点开始,按照某种策略选择子节点,直到到达一个可以扩展的节点。扩展阶段则是添加一个新的子节点。模拟阶段是从这个新节点开始,随机进行游戏直到结束,得到结果。最后,回溯阶段将模拟的结果反向传播到路径上的所有节点,更新它们的统计信息。
蒙特卡洛树搜索(Monte Carlo Tree Search, MCTS)是一种基于随机模拟的启发式搜索算法,主要用于复杂决策问题(如棋类游戏、路径规划等)。其核心思想是通过大量随机模拟探索可能的决策路径,逐步构建搜索树并优化决策选择。以下是详细讲解:
1. MCTS 的核心步骤
MCTS 通过迭代执行四个阶段来优化决策,每次迭代包含以下步骤:
(1) 选择(Selection)
- 目标:从根节点(当前状态)出发,根据策略选择子节点,直到到达一个可扩展的节点。
- 策略:通常使用 UCB1(Upper Confidence Bound) 公式平衡探索与利用:
UCT ( v i ) = w i n i + c ⋅ ln N n i \text{UCT}(v_i) = \frac{w_i}{n_i} + c \cdot \sqrt{\frac{\ln N}{n_i}} UCT(vi)=niwi+c⋅nilnN- (w_i):节点 (v_i) 的累计奖励(如胜利次数)。
- (n_i):节点 (v_i) 的访问次数。
- (N):父节点的总访问次数。
- (c):探索系数(通常设为 (\sqrt{2}))。
- 选择逻辑:优先选择 UCT 值高的子节点。
(2) 扩展(Expansion)
- 条件:当选择的节点未被完全展开(存在未探索的子节点)时,扩展树。
- 操作:添加一个或多个合法动作对应的新子节点到树中。
(3) 模拟(Simulation)
- 目标:从新扩展的节点出发,通过随机策略(如快速走子)模拟游戏直到终局。
- 结果:根据模拟结果得到奖励(如胜利=1,失败=0)。
(4) 回溯(Backpropagation)
- 目标:将模拟结果反向传播到路径上的所有节点,更新统计信息。
- 更新规则:
- 路径上每个节点的访问次数 (n_i) 增加 1。
- 累计奖励 (w_i) 根据模拟结果累加。
2. MCTS 的优势
- 无需启发函数:通过随机模拟代替人工设计评估函数。
- 适应高分支因子:适用于围棋等状态空间巨大的问题。
- 渐进优化:搜索时间越长,结果越可靠(Anytime Algorithm)。
- 平衡探索与利用:UCB1 公式避免陷入局部最优。
3. 应用场景
- 经典案例:AlphaGo 结合 MCTS 与神经网络击败人类围棋冠军。
- 其他场景:
- 棋类游戏(国际象棋、扑克)。
- 实时策略游戏(如《星际争霸》AI)。
- 机器人路径规划、资源调度等。
4. 变体与改进
- UCT(Upper Confidence Trees):使用 UCB1 的标准 MCTS。
- 结合神经网络(如 AlphaGo):
- 策略网络:指导模拟阶段的选择。
- 价值网络:替代随机模拟,直接评估节点胜率。
- 并行化 MCTS:通过多线程/分布式加速模拟。
5. 示例:围棋中的 MCTS
- 初始化:当前棋盘状态为根节点。
- 迭代:执行数千次 Selection→Expansion→Simulation→Backpropagation。
- 决策:选择根节点下访问次数最多的子节点作为落子位置。
6. 局限性
- 计算开销大:需大量模拟才能收敛。
- 依赖模拟质量:随机模拟可能无法反映真实策略。
- 动态环境适应慢:对实时变化的响应可能不足。
我们可以通过一些简单的python代码来理解代码
以下是一个简单的蒙特卡洛树搜索(MCTS)的 Python 代码示例,以解决一个假设的“二选一”游戏问题。游戏规则如下:
- 有两个动作(A 和 B),选择 A 有 70% 的概率获胜,选择 B 有 30% 的概率获胜。
- MCTS 的目标是通过模拟找到更优的动作。
代码实现
import math
import randomclass Node:def __init__(self, state, parent=None):self.state = state # 节点状态(如当前选择的动作)self.parent = parent # 父节点self.children = [] # 子节点列表self.visits = 0 # 节点访问次数self.value = 0 # 节点累计奖励(如胜利次数)def is_fully_expanded(self):# 假设每个节点最多有两个子节点(对应动作 A 和 B)return len(self.children) == 2def ucb(node, exploration_weight=1.414):if node.visits == 0:return float('inf') # 未访问过的节点优先选择# UCB公式:平均奖励 + 探索项return (node.value / node.visits) + exploration_weight * math.sqrt(math.log(node.parent.visits) / node.visits)def select(node):# 选择阶段:递归选择 UCB 值最高的子节点,直到叶子节点while node.is_fully_expanded():best_child = Nonebest_ucb = -float('inf')for child in node.children:current_ucb = ucb(child)if current_ucb > best_ucb:best_ucb = current_ucbbest_child = childnode = best_childreturn nodedef expand(node):# 扩展阶段:添加未探索的子节点possible_actions = ['A', 'B']for action in possible_actions:if action not in [child.state for child in node.children]:new_node = Node(state=action, parent=node)node.children.append(new_node)return new_node # 每次只扩展一个子节点return nodedef simulate(action):# 模拟阶段:根据动作的预设概率返回胜负if action == 'A':win_prob = 0.7 # 动作 A 的胜利概率else:win_prob = 0.3 # 动作 B 的胜利概率return 1 if random.random() < win_prob else 0def backpropagate(node, reward):# 回溯阶段:更新路径上的所有节点while node is not None:node.visits += 1node.value += rewardnode = node.parentdef mcts(root, iterations=1000):for _ in range(iterations):# 1. 选择阶段selected_node = select(root)# 2. 扩展阶段if selected_node.visits > 0: # 非叶子节点才扩展new_node = expand(selected_node)else:new_node = selected_node# 3. 模拟阶段reward = simulate(new_node.state)# 4. 回溯阶段backpropagate(new_node, reward)return rootif __name__ == "__main__":# 初始化根节点(假设根节点状态为空)root = Node(state=None)# 扩展根节点的初始子节点(动作 A 和 B)root.children = [Node(state='A', parent=root), Node(state='B', parent=root)]# 运行 MCTS 算法root = mcts(root, iterations=1000)# 输出结果total_visits = sum(child.visits for child in root.children)for child in root.children:print(f"动作 {child.state}: 访问次数={child.visits}, 胜率={child.value / child.visits:.2f}")
代码说明
-
Node 类:
- 保存节点的状态、父节点、子节点列表、访问次数和累计奖励。
-
UCB 公式:
- 平衡探索与利用,优先选择未访问过的节点(返回无限大)。
-
四个阶段:
- Selection:递归选择 UCB 值最高的子节点。
- Expansion:扩展未完全展开的节点。
- Simulation:根据动作预设概率模拟胜负。
- Backpropagation:更新路径上的所有节点的访问次数和奖励。
-
运行结果:
- 经过 1000 次迭代后,动作 A 的访问次数和胜率会显著高于动作 B。
示例输出
动作 A: 访问次数=732, 胜率=0.71
动作 B: 访问次数=268, 胜率=0.29
可以看到,MCTS 成功找到了胜率更高的动作 A。你可以通过调整 simulate
函数中的概率或迭代次数观察结果变化。
但是上面这个示范可能还不够,再看看下面这个例子
新示例:迷宫路径搜索
问题描述
- 迷宫结构:一个 3x3 网格迷宫,起点在左上角(0,0),终点在右下角(2,2)。
- 动作:每一步可以移动 ↑、↓、←、→,但不能超出边界。
- 奖励:到达终点奖励 +1,其他位置无奖励。
- 目标:用 MCTS 找到从起点到终点的最优路径。
MCTS 的核心挑战
- 状态空间复杂:每一步有多个可能的移动方向。
- 长期奖励未知:需通过模拟探索路径,而非依赖预先设定的概率。
改进后的 Python 代码
import math
import randomclass Node:def __init__(self, state, parent=None):self.state = state # 当前坐标 (x, y)self.parent = parentself.children = []self.visits = 0self.value = 0 # 累计奖励def is_fully_expanded(self):# 完全扩展:所有合法移动方向都已生成子节点return len(self.children) >= len(get_possible_actions(self.state))def ucb(node, exploration_weight=1.414):if node.visits == 0:return float('inf')return (node.value / node.visits) + exploration_weight * math.sqrt(math.log(node.parent.visits) / node.visits)def select(node):while node.is_fully_expanded() and not is_terminal(node.state):best_child = max(node.children, key=ucb)node = best_childreturn nodedef expand(node):possible_actions = get_possible_actions(node.state)for action in possible_actions:if action not in [child.state[-1] for child in node.children]:new_state = apply_action(node.state, action)new_node = Node(state=new_state, parent=node)node.children.append(new_node)return new_nodereturn nodedef simulate(state):# 随机移动直到终点或步数耗尽current_state = statefor _ in range(10): # 最大模拟步数if is_terminal(current_state):return 1 # 到达终点possible_actions = get_possible_actions(current_state)if not possible_actions:return 0 # 无路可走action = random.choice(possible_actions)current_state = apply_action(current_state, action)return 0 # 超时def backpropagate(node, reward):while node is not None:node.visits += 1node.value += rewardnode = node.parentdef mcts(root, iterations=1000):for _ in range(iterations):selected = select(root)if not is_terminal(selected.state):expanded = expand(selected)reward = simulate(expanded.state)else:reward = 1 # 直接到达终点backpropagate(expanded if 'expanded' in locals() else selected, reward)return root# 迷宫环境相关函数
def get_possible_actions(state):x, y = stateactions = []if x > 0:actions.append('↑')if x < 2:actions.append('↓')if y > 0:actions.append('←')if y < 2:actions.append('→')return actionsdef apply_action(state, action):x, y = stateif action == '↑': x -= 1elif action == '↓': x += 1elif action == '←': y -= 1elif action == '→': y += 1return (x, y)def is_terminal(state):return state == (2, 2)if __name__ == "__main__":root = Node(state=(0, 0))root = mcts(root, iterations=2000)# 输出最优路径best_path = []current = rootwhile current.children:best_child = max(current.children, key=lambda c: c.visits)best_path.append(best_child.state)current = best_childprint("最优路径:", best_path)
关键改进说明
-
动态环境建模:
- 每个节点的状态是当前坐标
(x, y)
,而非固定动作。 - 通过
get_possible_actions
和apply_action
动态生成子节点,模拟迷宫移动。
- 每个节点的状态是当前坐标
-
长期奖励探索:
- 模拟阶段:从扩展节点出发,随机移动直到终点或超时。
- 回溯阶段:奖励(是否到达终点)反向更新路径上的所有节点。
-
MCTS 的核心价值体现:
- 树结构动态扩展:根据移动方向生成子节点。
- 平衡探索与利用:UCB 公式指导选择高奖励路径,同时尝试未充分探索的方向。
- 无需预先知识:完全通过模拟学习迷宫的最优路径。
示例输出
最优路径: [(0, 1), (0, 2), (1, 2), (2, 2)]
(实际路径可能因随机模拟略有不同,但会趋近于最短路径 ↓→→ 或 →→↓)
总结
在改进后的示例中,MCTS 通过以下步骤真正体现了其核心能力:
- 动态构建搜索树:根据移动方向扩展节点。
- 模拟未知路径:无需预先知道迷宫结构。
- 优化长期奖励:通过回溯逐步聚焦到高胜率路径。