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

强化学习Agent在终极井字棋环境中始终选相同动作问题排查

问题描述

我正在训练一个强化学习Agent玩终极井字棋(9x9棋盘的井字棋扩展版,带有额外规则),已基于OpenAI Gym搭建环境,尝试用Stable Baselines3的PPO和DQN网络训练,但该Agent始终为每个状态选择相同的动作,且该动作多数时候是无效的。调整训练超参数、更换网络类型、修改奖励值后均无改善,怀疑问题源于环境本身。

环境构造函数

def __init__(self):
    super(UltimateTicTacToeEnv, self).__init__()

    self.reset()

    self.action_space = Discrete(81)  # 9 boards * 9 squares = 81 actions spaces.

    self.observation_space = Box(low=0, high=2, shape=(83,), dtype=np.int) # 81 squares from the board + pointer + current_player

Step方法

def step(self, action):
    reward = 0
    # Since the action is from 0 to 80, it gets the board and the square that the action corresponds to
    board = action // 9
    square = action % 9

    self.board.update()

    if self.board.isValid(board, square): # checks if the move is valid

        reward += 1 # increases the reward if the move is valid

        self.board.addValue(self.current_player, board, square) # adds move to board

        self.board.update() # updates the board with the action

        if (Board.hasWon(self.board.values[board]) == self.current_player):# checks if player won mini-3x3 in which action was played

            reward += 1 # increases reward if agent has won the mini 3x3 square in which the action was taken
        
        done, winner = self.check_game_over(board, square) # checks if game is over, and who won if it is over
        if done:
            if (winner == self.current_player): reward += 5 # reward for agent winning game

        self.current_player = 3 - self.current_player # switching between players
    else:
        reward -= 1 # reward is decreased if the agent takes an invalid action
        done = False

    return self.get_state(), reward, done, {} # get_state() returns a numpy array of length 83. The first 81 elements are the board. other 2 are pointer in which next move should be played, and current_player

PPO训练代码

policy_kwargs = dict(
    net_arch=dict(pi=[83, 256, 256, 256, 81], vf=[83, 256, 256, 256, 81]),
)

model = PPO("MlpPolicy", env, verbose=1, learning_rate=2.5e-3, n_steps=2048, batch_size=64, 
            n_epochs=10, gamma=0.99, gae_lambda=0.95, clip_range=0.2, ent_coef=0.005, policy_kwargs=policy_kwargs, device="cuda")

DQN训练代码

policy_kwargs = dict(
    net_arch=[83, 256, 256, 256, 81],
)
model = DQN("MlpPolicy", env, verbose=1, learning_rate=2.5e-3, policy_kwargs=policy_kwargs, device='cuda')

可能的原因分析
  • 观测空间类型不匹配:Stable Baselines3的MlpPolicy默认期望浮点型输入,但当前observation_space用了np.int类型,整数输入会导致网络无法有效提取状态特征,学习停滞。
  • 无效动作惩罚不足:当前无效动作仅扣1点奖励,惩罚力度太弱,Agent可能因探索成本低而持续选择同一个无效动作。
  • 状态表示存在问题:get_state()返回的"下一个落子棋盘指针"可能未正确传递,Agent无法感知终极井字棋的核心规则约束(当前落子位置决定下一个落子的棋盘),导致状态判断错误。
  • 网络架构冗余且错误:手动指定输入层(83)和输出层(81)是多余的,MlpPolicy会自动适配观测/动作空间,强行指定反而可能导致维度不匹配;过多的隐藏层也可能导致梯度消失或过拟合。
  • 未实现动作掩码:Agent会浪费大量探索步数在无效动作上,无法聚焦于有效动作的学习,导致策略收敛到随机或固定动作。
  • 初始化逻辑异常:构造函数中调用的reset()若未正确初始化current_player、board等核心状态,会导致Agent始终处于错误初始状态,无法学习到有效策略。

修复建议
  • 修正观测空间类型:将观测空间的dtype改为np.float32,适配RL框架的输入要求:
    self.observation_space = Box(low=0, high=2, shape=(83,), dtype=np.float32)
    
  • 强化奖励机制:
    • 增大无效动作惩罚,例如改为reward -= 5,让Agent主动规避无效动作。
    • 提升正向奖励的区分度:赢小棋盘奖励改为+3,赢全局奖励改为+20,强化核心目标的信号。
    • 添加负向结束奖励:平局奖励-2,输局奖励-10,避免Agent消极博弈。
  • 实现动作掩码:
    在环境中添加动作掩码方法,让Agent只在有效动作中选择:
    # 环境新增方法
    def action_masks(self):
        mask = np.zeros(81, dtype=bool)
        for action in range(81):
            board = action // 9
            square = action % 9
            if self.board.isValid(board, square):
                mask[action] = True
        return mask
    
    PPO训练时启用掩码支持:
    policy_kwargs = dict(
        net_arch=dict(pi=[256, 256, 256], vf=[256, 256, 256]),
        action_masking=True
    )
    model = PPO("MlpPolicy", env, verbose=1, learning_rate=2.5e-3, n_steps=2048, batch_size=64, 
                n_epochs=10, gamma=0.99, gae_lambda=0.95, clip_range=0.2, ent_coef=0.01,
                policy_kwargs=policy_kwargs, device="cuda")
    
    DQN可使用Stable Baselines3 Contrib中的MaskableDQN来支持动作掩码。
  • 简化网络架构:去掉手动指定的输入输出层,让框架自动适配:
    # PPO的policy_kwargs
    policy_kwargs = dict(net_arch=dict(pi=[256,256,256], vf=[256,256,256]))
    # DQN的policy_kwargs
    policy_kwargs = dict(net_arch=[256,256,256])
    
  • 验证状态初始化与表示:
    • 检查reset()是否正确初始化current_player、board和落子指针,确保每次开局状态一致且符合规则。
    • 打印get_state()的返回值,确认指针和当前玩家的位置、数值正确,且与观测空间的定义匹配。
  • 调整探索参数:
    • PPO增大ent_coef到0.01~0.05,鼓励Agent探索更多动作。
    • DQN增大exploration_fraction到0.3,延长探索周期,让Agent有足够机会尝试有效动作。

内容的提问来源于stack exchange,提问作者Samatva K

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 17:59:59