从零构建AlphaZero风格五子棋AI:Python强化学习实战解析
1. 从零理解AlphaZero的核心思想
第一次听说AlphaZero是在2017年,当时它仅用8小时训练就击败了国际象棋世界冠军程序Stockfish。作为一个棋类爱好者兼程序员,我立刻被这个"从零开始"的学习方式吸引了。后来我花了三个月时间,用Python复现了一个简化版的AlphaZero五子棋AI,今天就把这个实战经验完整分享给大家。
AlphaZero的核心思想可以用三个关键词概括:自对弈、蒙特卡洛树搜索(MCTS)和神经网络。想象一下教小朋友下棋的过程:首先他会胡乱下几盘(自对弈),然后慢慢记住哪些走法容易赢(神经网络),同时在下棋时会"脑补"几步之后的局面(MCTS)。AlphaZero的学习过程惊人地相似,只是用数学方法实现了这个过程。
与传统棋类AI最大的不同是,AlphaZero不需要任何人类棋谱。我刚开始也很怀疑:没有人类经验真能学会下棋吗?实测发现,通过设计合理的奖励机制(比如五子连珠得1分,被对手连珠得-1分),AI完全能自己发现经典开局策略。这就像给AI一本空棋谱,让它自己记录胜负经验。
2. 搭建五子棋基础环境
2.1 棋盘表示与规则实现
我们先从最基础的15×15棋盘开始。在Python中,我用numpy数组表示棋盘状态:
import numpy as np
class Board:
def __init__(self):
self.size = 15
self.board = np.zeros((self.size, self.size)) # 0空 1黑棋 2白棋
self.current_player = 1 # 黑棋先行
def is_valid_move(self, x, y):
return 0 <= x < self.size and 0 <= y < self.size and self.board[x,y] == 0
def place_stone(self, x, y):
if self.is_valid_move(x, y):
self.board[x,y] = self.current_player
self.current_player = 3 - self.current_player # 切换玩家
return True
return False
判断胜负的算法需要检查四个方向(水平、垂直、两个对角线)。这里有个优化技巧:只需要检查最后落子位置的周边区域,不需要全盘扫描:
def check_win(self, x, y):
directions = [(1,0), (0,1), (1,1), (1,-1)] # 四个检查方向
player = self.board[x,y]
for dx, dy in directions:
count = 1
# 正向检查
nx, ny = x + dx, y + dy
while 0 <= nx < self.size and 0 <= ny < self.size and self.board[nx,ny] == player:
count += 1
nx += dx
ny += dy
# 反向检查
nx, ny = x - dx, y - dy
while 0 <= nx < self.size and 0 <= ny < self.size and self.board[nx,ny] == player:
count += 1
nx -= dx
ny -= dy
if count >= 5:
return True
return False
2.2 强化学习环境设计
强化学习需要定义三个核心要素:状态(State)、动作(Action)和奖励(Reward)。在五子棋中:
- 状态:当前棋盘布局+当前执子方
- 动作:在合法位置落子(15×15=225种可能)
- 奖励:胜利+1,失败-1,平局0,未结束游戏0
我设计了一个Env类来管理游戏进程:
class Env:
def __init__(self):
self.board = Board()
self.done = False
self.winner = None
def step(self, action):
x, y = action // 15, action % 15 # 将动作编号转换为坐标
if not self.board.place_stone(x, y):
return self.board.board, -10, True, {} # 非法走子惩罚
if self.board.check_win(x, y):
self.done = True
self.winner = self.board.board[x,y]
return self.board.board, 1, True, {}
return self.board.board, 0, False, {}
3. 蒙特卡洛树搜索(MCTS)实现
3.1 节点与搜索树结构
MCTS的核心是不断扩展搜索树。每个节点需要存储以下信息:
class Node:
def __init__(self, parent=None, action=None):
self.parent = parent # 父节点
self.action = action # 导致本节点的动作
self.children = [] # 子节点列表
self.visit_count = 0 # 访问次数
self.total_value = 0 # 累计价值
self.prior = 0 # 先验概率(来自神经网络)
搜索过程分为四个阶段,我把它比喻成城市规划:
- 选择(Selection):从根节点出发,选择最有"潜力"的子节点,就像规划师选择最有发展前景的区域
- 扩展(Expansion):当遇到未探索的节点时,扩展新节点,就像开发新城区
- 模拟(Simulation):用神经网络评估新节点价值,就像请专家评估地块价值
- 回溯(Backpropagation):将评估结果反向传播到路径上的所有节点,就像根据销售数据调整整个区域的估值
3.2 UCB算法实现
选择节点时使用UCB公式平衡探索与利用:
def ucb_score(node, parent_visit_count, c_puct=1.0):
# Q值:节点平均价值
q_value = node.total_value / (node.visit_count + 1e-6)
# U项:探索项
u_value = c_puct * node.prior * np.sqrt(parent_visit_count) / (node.visit_count + 1)
return q_value + u_value
def select_child(node):
# 选择UCB分数最高的子节点
scores = [ucb_score(child, node.visit_count) for child in node.children]
return node.children[np.argmax(scores)]
在实际项目中,我发现c_puct参数对性能影响很大。经过测试,设置在1.0-1.5之间效果最好。太小时AI过于保守,太大则容易冒险。
4. 神经网络设计与训练
4.1 网络架构
使用PyTorch实现的双头神经网络:
import torch
import torch.nn as nn
import torch.nn.functional as F
class AlphaZeroNet(nn.Module):
def __init__(self, board_size=15):
super().__init__()
self.conv1 = nn.Conv2d(3, 32, 3, padding=1) # 输入通道3:当前玩家棋子,对手棋子,当前执子方
self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
self.conv3 = nn.Conv2d(64, 128, 3, padding=1)
# 策略头
self.policy_conv = nn.Conv2d(128, 2, 1)
self.policy_fc = nn.Linear(2*board_size*board_size, board_size*board_size)
# 价值头
self.value_conv = nn.Conv2d(128, 1, 1)
self.value_fc1 = nn.Linear(board_size*board_size, 64)
self.value_fc2 = nn.Linear(64, 1)
def forward(self, x):
# 共享特征提取
x = F.relu(self.conv1(x))
x = F.relu(self.conv2(x))
x = F.relu(self.conv3(x))
# 策略头
p = F.relu(self.policy_conv(x))
p = p.view(p.size(0), -1)
p = F.log_softmax(self.policy_fc(p), dim=1)
# 价值头
v = F.relu(self.value_conv(x))
v = v.view(v.size(0), -1)
v = F.relu(self.value_fc1(v))
v = torch.tanh(self.value_fc2(v)) # 输出[-1,1]之间的估值
return p, v
4.2 训练技巧
训练数据来自AI的自对弈记录。我总结了几个关键点:
- 数据增强:通过旋转和镜像增加数据多样性
def augment_data(state, policy):
# 随机选择0-3次90度旋转
k = np.random.randint(0, 4)
state = np.rot90(state, k, axes=(1,2))
policy = np.rot90(policy.reshape(15,15), k).flatten()
# 50%概率水平翻转
if np.random.random() > 0.5:
state = np.flip(state, 2)
policy = np.flip(policy.reshape(15,15), 1).flatten()
return state, policy
- 学习率调整:使用余弦退火策略
optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=100)
- 正则化:添加L2正则和策略熵奖励
def loss_fn(policy_logits, value, target_policy, target_value):
# 策略损失
policy_loss = -torch.mean(torch.sum(target_policy * policy_logits, dim=1))
# 价值损失
value_loss = F.mse_loss(value.squeeze(), target_value)
# 熵奖励(鼓励探索)
entropy = -torch.mean(torch.sum(torch.exp(policy_logits) * policy_logits, dim=1))
return policy_loss + value_loss - 0.01*entropy
5. 完整训练流程与对弈测试
5.1 自对弈数据生成
完整的训练循环如下:
def self_play(model, num_games=100):
data = []
for _ in range(num_games):
env = Env()
game_data = []
state = env.board.board
while not env.done:
# 运行MCTS获取走子概率
root = run_mcts(model, state, env.board.current_player)
policy = get_policy(root) # 根据访问次数计算概率
# 保存训练数据
game_data.append((state, env.board.current_player, policy))
# 选择动作(添加探索噪声)
action = select_action(root, temperature=1.0)
state, reward, done, _ = env.step(action)
# 为每一步数据添加最终奖励
for i, (s, player, p) in enumerate(game_data):
z = 0
if env.winner is not None:
z = 1 if env.winner == player else -1
data.append((s, player, p, z))
return data
5.2 人机对弈测试
实现一个简单的命令行对战界面:
def human_vs_ai(model):
env = Env()
while not env.done:
print_board(env.board.board)
if env.board.current_player == 1: # 人类玩家
x, y = map(int, input("输入你的走子坐标(x y): ").split())
action = x * 15 + y
else: # AI玩家
print("AI思考中...")
root = run_mcts(model, env.board.board, 2)
action = select_action(root, temperature=0.1)
_, _, done, _ = env.step(action)
print_board(env.board.board)
if env.winner == 1:
print("你赢了!")
elif env.winner == 2:
print("AI赢了!")
else:
print("平局!")
在训练过程中,我发现几个常见问题及解决方案:
- AI过于保守:增加UCB公式中的c_puct参数,鼓励更多探索
- 训练初期走子随机:适当降低初始温度参数(temperature)
- 过拟合:增加数据增强和L2正则化强度
- 训练速度慢:使用GPU加速,或减少MCTS的模拟次数
经过约10万局自对弈训练后,我的五子棋AI已经能达到业余高段水平。最有趣的是,AI自己发现了一些人类棋谱中常见的开局策略,比如"花月"、"浦月"等经典开局,完全是从零自学的。
更多推荐


所有评论(0)