Stable Baselines3 A2C国际象棋模型训练停滞,动作空间存疑?
国际象棋A2C模型训练停滞问题排查
问题背景
使用python-chess与stable_baselines3库构建A2C国际象棋模型,经过10万局训练后模型几乎无进展。采用继承自gymnasium.Env的自定义ChessEnvironment环境,怀疑问题出在动作空间的构建上。
相关代码片段
导入代码
import chess import re import gymnasium as gym import numpy as np from stable_baselines3 import A2C from stable_baselines3.common.policies import ActorCriticPolicy from torch.nn import ReLU
自定义环境__init__函数
def __init__(self): super().__init__() # Representing board as a 1-hot encoded 8x8 grid self.observation_space = gym.spaces.Box(low=0, high=1, shape=(8, 8, 12), dtype=np.uint8) # Initialize the chessboards self.board = chess.Board() self.last_board = self.board # Action space self.move_dict = make_move_list(self.board) self.action_space = gym.spaces.Discrete(len(self.move_dict)) # ... Some more parameters ... # Policy kwargs for A2C model self.policy_kwargs = dict( net_arch=dict(pi=[128, 128, 64], vf=[128, 128, 64]), activation_fn=ReLU, )
step函数
def step(self, action): move = self.move_dict[action] self.move_list.append(move) # Save the board as is before making the move for reward calculation self.last_board = self.board # Execute the specified action on the chessboard self.board.push_san(move) self.sync_action_space() # Convert the board to the observation format observation = np.zeros((8, 8, 12), dtype=np.uint8) for square, piece in self.board.piece_map().items(): piece_type = piece.piece_type piece_color = int(piece.color) observation[square // 8][square % 8][piece_type - 1 + 6 * piece_color] = 1 # Calculate the reward reward = self.calculate_reward() # Check if the episode is done terminated = (self.board.result() != '*') # Return the observation, reward, done flag, and additional info return observation, reward, terminated, False, {}
sync_action_space函数
def sync_action_space(self): self.move_dict = make_move_list(self.board) self.action_space = gym.spaces.Discrete(len(self.move_dict))
核心疑问
模型无法学习是否(或主要)受动作空间设计不当影响?还是应该调整超参数(或奖励函数中不同启发式的权重)?
问题分析与解决建议
1. 动作空间设计是核心问题
你当前的动作空间实现完全不符合stable_baselines3的预期:
- SB3的策略网络输出维度是固定的,与初始化时
action_space的维度绑定。每次sync_action_space修改action_space的大小,会导致模型输出的logits维度与实际可选动作数不匹配。 - 更严重的是,
move_dict每次重新生成,动作索引对应的移动会完全变化——模型之前学习到的"索引X对应好棋"的经验,在下一个状态里可能变成非法移动或坏棋,直接导致训练混乱,根本无法收敛。
修复方案:
改用固定大小的全局动作空间,结合动作掩码过滤非法移动:
- 预生成国际象棋所有可能的合法移动(总共约1968种,包括所有UCI格式的移动),构建固定的
move_dict,动作空间设为gym.spaces.Discrete(1968)。 - 使用SB3的
MaskablePolicy,在每个step生成合法动作的掩码(标记哪些动作是当前状态下的合法移动),让模型仅从合法动作中选择。 - 若暂时不想用掩码,也可以在step里检查动作对应的移动是否合法:非法动作直接给负奖励(比如-0.5),且不执行该动作,避免游戏崩溃。
2. 奖励函数可能过于稀疏或不合理
如果你的奖励函数只有终局奖励(赢+1,输-1,和0),模型会因奖励稀疏难以学习——大部分步骤没有反馈,无法指导模型优化中间决策。
优化方向:
- 加入中间奖励:吃子奖励(不同棋子权重不同,比如皇后+0.5,兵+0.1)、控制中心奖励、棋子位置奖励(比如马在中心给+0.05)、避免送子的负奖励等。
- 调整奖励权重:比如终局奖励权重高于中间奖励,避免模型过度追求短期收益。
3. 观测空间信息不足
当前的8x8x12观测只包含棋子的位置和类型,但国际象棋的决策还依赖很多额外信息:
- 王车易位的权限
- 吃过路兵的可能性
- 当前回合(黑方/白方)
- 棋子数量差
优化方向:
在观测空间中增加额外通道,比如:
- 第13通道:标记双方是否还能王车易位
- 第14通道:标记是否存在吃过路兵的可能
- 第15通道:标记当前回合颜色(白方为1,黑方为0)
4. 超参数与算法选择
A2C默认参数不一定适合象棋这种复杂的延迟回报环境:
- 增大
n_steps:从默认的5改为32或64,让模型能积累更长序列的回报估计。 - 降低学习率:从默认的3e-4改为1e-4或5e-5,避免训练震荡。
- 调整
gamma:从0.99改为0.999,更重视长期回报。 - 考虑换用PPO:SB3的PPO比A2C稳定性更好,更适合复杂环境的训练。
5. 训练策略优化
- 采用自我对弈:让模型和不断迭代的自己对弈,而不是固定弱对手,逐步提升训练难度。
- 定期评估:每隔一定训练步数,让模型和固定强度的对手(比如python-chess的Level 1)对弈,监控实际水平变化,避免过拟合。
内容的提问来源于stack exchange,提问作者moistnar
相关产品推荐
相关产品推荐

