You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:35:20