PyTorch优化器未更新权重求助:训练俄罗斯方块AI遇问题
问题分析与修复建议
我帮你梳理下代码里导致权重不更新和y_batch不变的几个关键问题,都是DQN实现里容易踩的坑:
一、模型结构的核心问题(直接导致梯度无法有效传递)
你的CNN模型存在维度不匹配、激活层使用错误的问题,这会让梯度要么消失要么无法计算:
- Conv2d层输出维度异常:
self.hidden1 = Conv2d(1, 1, (22, 10)),假设你的BoardMatrix是(22,10)的棋盘,输入是(1,1,22,10)(batch, channel, H, W),经过这个卷积后输出是(1,1,1,1),再经过MaxPool2d(2,2)直接变成无效张量(核大小超过特征图尺寸),后续Linear层的输入维度完全不对,梯度根本传不回卷积层。 - 输出层与激活层不匹配:DQN需要输出每个动作对应的Q值,但你最后一层是
Linear(10,1)+Softmax(dim=1),输出维度是(batch,1),Softmax起不到任何作用。应该把输出层改成对应动作数量的维度,且DQN通常不需要Softmax(直接输出Q值即可)。 - Linear层输入维度错误:
self.hidden2 = Linear(1, 30)依赖前面卷积的1x1输出,但前面的Pool操作已经破坏了特征图,导致后续层的梯度传递中断。
修复后的模型示例:
class CNN(nn.Module): def __init__(self, board_height, board_width): super(CNN, self).__init__() self.number_of_actions = 5 # left, right, down, up, space self.gamma = 0.99 # DQN中gamma通常用0.99,0.01太小会削弱长期奖励影响 self.final_epsilon = 0.0001 self.initial_epsilon = 0.1 self.number_of_iterations = 2000000 self.replay_memory_size = 10000 self.minibatch_size = 32 # 输入格式:(batch, channel, H, W) → 比如(32,1,22,10) self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1) self.act1 = nn.ReLU() self.pool1 = nn.MaxPool2d(2, 2) # 输出(16,11,5) self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1) self.act2 = nn.ReLU() self.pool2 = nn.MaxPool2d(2, 2) # 输出(32,5,2) # 计算全连接层输入维度:32*5*2=320 self.fc1 = nn.Linear(32*5*2, 256) self.act3 = nn.ReLU() self.fc2 = nn.Linear(256, self.number_of_actions) # 输出每个动作的Q值 def forward(self, x): x = self.pool1(self.act1(self.conv1(x))) x = self.pool2(self.act2(self.conv2(x))) x = x.view(x.size(0), -1) # 展平成一维张量 x = self.act3(self.fc1(x)) x = self.fc2(x) return x
二、学习循环的逻辑错误(y_batch不变+权重不更新的直接原因)
1. Reward固定为0,y_batch自然无变化
你定义了reward = torch.tensor([[0.]], requires_grad=True),但从未根据游戏结果更新奖励值。比如:消一行给+100分,游戏结束给-1000分,方块成功下落给+1分,这样y_batch才会随游戏过程变化。
2. Replay Memory存储错误
DQN的经验回放需要存储**(state, action, reward, next_state, done)** 五元组,而你只存了self.BoardMatrix,导致采样minibatch时拿不到动作、奖励、下一个状态等关键信息,无法计算TD目标值。
修改回放存储逻辑:
# 每次动作执行后存储经验 state = self.BoardMatrix.copy() action_idx = 0 # 对应动作的索引:0=left,1=right,2=down,3=up,4=space reward = self.calculate_reward() # 自己实现奖励计算函数 next_state = self.BoardMatrix.copy() done = self.game_over # 是否游戏结束 replay_memory.append((state, action_idx, reward, next_state, done))
3. Action Batch处理错误
你用action_batch = 0.8这种标量计算q_value完全错误。DQN中应该根据选择的动作,取出对应的Q值,比如用one-hot编码或直接索引:
# 假设action是(minibatch_size,)的张量,每个元素是动作索引 q_value = outputs.gather(1, action.unsqueeze(1)).squeeze(1)
4. Y_batch构建逻辑错误
你现在的y_batch完全依赖固定的reward,没有用到下一个状态的Q值。正确的TD目标计算应该是:
# 拆分minibatch中的元素 states, actions, rewards, next_states, dones = zip(*minibatch) # 转换为模型可接受的张量格式 states = torch.tensor(states, dtype=torch.float32).unsqueeze(1) next_states = torch.tensor(next_states, dtype=torch.float32).unsqueeze(1) rewards = torch.tensor(rewards, dtype=torch.float32) dones = torch.tensor(dones, dtype=torch.bool) # 计算下一个状态的最大Q值 next_q_values = model(next_states).max(1)[0] # TD目标:游戏结束则仅取reward,否则reward + gamma*下一个状态的最大Q值 y_batch = rewards + self.gamma * next_q_values * (~dones) y_batch = y_batch.detach() # 避免梯度回传到目标网络
5. 优化器与损失函数问题
- 初始化时用了
CrossEntropyLoss(),但DQN是回归任务(预测Q值),应该用MSELoss(),且不要在循环里重复定义criterion,初始化一次即可。 - 学习率
1e-6太小,建议改成1e-4或1e-3,否则权重更新极慢甚至看不到变化。
三、其他细节问题
- 输入张量
inputs = torch.tensor([[self.BoardMatrix]])需要转换成float32并增加channel维度,比如inputs = torch.tensor([[self.BoardMatrix]], dtype=torch.float32),否则Conv2d会报错。 - 缺少Epsilon贪心策略:当前逻辑直接根据Q值选动作,会导致探索不足,应该加入随机选动作的逻辑,比如:
epsilon = max(self.final_epsilon, self.initial_epsilon - (self.initial_epsilon - self.final_epsilon)*iteration/self.number_of_iterations) if random.random() < epsilon: action_idx = random.randint(0, self.number_of_actions-1) else: q_values = model(inputs) action_idx = q_values.argmax().item()
把这些问题修复后,模型权重应该就能正常更新,y_batch也会随着游戏过程产生变化了。
内容的提问来源于stack exchange,提问作者Il Ven
相关产品推荐
相关产品推荐

