前言
介绍 Monte Carlo Tree Search 算法。
Operating System: Ubuntu 22.04.4 LTS
介绍
Monte Carlo Tree Search(MCTS)算法是一种用于决策过程的启发式搜索算法,它结合了蒙特卡洛方法与树形结构搜索的优势,常用于解决游戏、棋类等具有较复杂状态空间的决策问题。以下是Monte Carlo Tree Search算法的详细介绍:
算法核心思想
MCTS算法的核心思想是通过模拟多次游戏(或问题)的随机过程来评估和选择最优的行动策略。它不需要明确的状态转移模型,而是通过探索(Exploration)与利用(Exploitation)的平衡来构建一个搜索树,并在树上进行选择、扩展、模拟和回溯四个主要步骤。
主要步骤
- 选择(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是探索参数。
- 扩展(Expansion):
- 对于选定的未完全扩展的节点,随机选择一个未探索的行动,并创建一个新的子节点。
- 模拟(Simulation):
- 从新创建的节点开始,进行随机游戏直到达到游戏结束的状态。
- 模拟的目的是为了评估该节点的潜在价值,通常是通过胜/负的结果。
- 回溯(Backpropagation):
- 将模拟的结果回溯到根节点,更新所有经过节点的统计数据(如访问次数和胜利次数)。
算法流程
- 初始化:创建一个根节点代表当前游戏状态。
- 循环执行以下步骤直到满足停止条件(如时间限制或迭代次数):
- 选择:从当前树中选择一个节点。
- 扩展:如果选择的节点不是终端节点,则创建一个新的子节点。
- 模拟:从新节点开始进行随机模拟,直到得到一个结果。
- 回溯:使用模拟的结果更新回溯路径上的所有节点的统计数据。
- 选择具有最高统计胜率的行动。
优点
- 不需要显式的模型,适用于模型未知或难以获取的情况。
- 能有效处理具有高分支因子的搜索空间。
- 通过模拟,可以处理包含随机性的问题。
缺点
- 计算资源消耗较大,尤其是对于复杂问题。
- 对于需要即时决策的问题,可能存在时间压力。
应用
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$ 是自然对数。
这个公式由两部分组成:
平均奖励 $\bar{X}_a$:这部分代表了到目前为止,选择臂 $a$ 的平均奖励值。它反映了到目前为止对臂 $a$ 的了解程度,即“利用”部分。
探索因子 $\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()
代码解释
GameState类:
- 表示井字棋的游戏状态,包括棋盘、当前玩家和游戏结束状态。
- 提供判断游戏是否结束、获取可能走法和执行走法的方法。
MCTSNode类:
- 表示MCTS树中的节点,包含游戏状态、父节点、走法、访问次数和累积价值。
- 提供判断节点是否完全扩展的方法。
**选择子节点 (select_child函数)**:
- 使用UCB公式选择具有最高UCB值的子节点。
**扩展节点 (expand函数)**:
- 从选择的叶子节点扩展一个新的子节点,选择一个未探索的走法。
**模拟 (simulate函数)**:
- 从新扩展的节点开始进行随机模拟,直到游戏结束,返回结果。
**回溯 (backpropagate函数)**:
- 将模拟结果反向传播回树的各个节点,更新访问次数和累积价值。
**MCTS主函数 (mcts函数)**:
- 执行选择、扩展、模拟和回溯四个步骤,迭代指定次数。
- 最终选择访问次数最多的子节点作为最佳走法。
**主程序 (main函数)**:
- 初始化游戏状态和根节点,运行MCTS算法,输出最佳走法。
通过运行此代码,可以观察MCTS算法在井字棋游戏中的决策过程,并找到一个不错的走法。
结语
第二百五十三篇博文写完,开心!!!!
今天,也是充满希望的一天。