我想实现以下算法,来自这本书,第13.6节:
我不明白如何在pytorch中实现更新规则(w的规则与theta的规则非常相似)。
据我所知,torch需要一个损失来使用loss.backwward()
。
这种形式似乎不适用于引用的算法。
我仍然确信在pytorch中实现这种更新规则是有正确方法的。
如果给定V(s,w)是神经网络的输出,由w参数化,我非常希望能看到一个关于如何更新w权重的代码片段。
编辑: Chris Holland提出了一个实现方法,我已经实现了它。它在Cartpole上没有收敛,我想知道我是否做错了什么。
评论者确实收敛到了函数gamma*f(n)=f(n)-1
的解,这恰好是序列gamma+gamma^2+...+gamma^inf
的和,这意味着gamma=1时发散。gamma=0.99时收敛到100,gamma=0.5时收敛到2,以此类推。无论是演员还是策略都如此。
代码如下:
def _update_grads_with_eligibility(self, is_critic, delta, discount, ep_t): gamma = self.args.gamma if is_critic: params = list(self.critic_nn.parameters()) lamb = self.critic_lambda eligibilities = self.critic_eligibilities else: params = list(self.actor_nn.parameters()) lamb = self.actor_lambda eligibilities = self.actor_eligibilities is_episode_just_started = (ep_t == 0) if is_episode_just_started: eligibilities.clear() for i, p in enumerate(params): if not p.requires_grad: continue eligibilities.append(torch.zeros_like(p.grad, requires_grad=False)) # eligibility traces for i, p in enumerate(params): if not p.requires_grad: continue eligibilities[i][:] = (gamma * lamb * eligibilities[i]) + (discount * p.grad) p.grad[:] = delta.squeeze() * eligibilities[i]
和
expected_reward_from_t = self.critic_nn(s_t)probs_t = self.actor_nn(s_t)expected_reward_from_t1 = torch.tensor([[0]], dtype=torch.float)if s_t1 is not None: # s_t不是终止状态,s_t1存在。 expected_reward_from_t1 = self.critic_nn(s_t1)delta = r_t + gamma * expected_reward_from_t1.data - expected_reward_from_t.datanegative_expected_reward_from_t = -expected_reward_from_tself.critic_optimizer.zero_grad()negative_expected_reward_from_t.backward()self._update_grads_with_eligibility(is_critic=True, delta=delta, discount=discount, ep_t=ep_t)self.critic_optimizer.step()
编辑2:Chris Holland的解决方案有效。问题源于我代码中的一个错误,导致以下代码行
if s_t1 is not None: expected_reward_from_t1 = self.critic_nn(s_t1)
总是被调用,因此expected_reward_from_t1
永远不会为零,因此没有为贝尔曼方程递归指定停止条件。
在没有奖励设计的情况下,gamma=1
,lambda=0.6
,以及演员和评论者的单隐藏层大小为128,这在500个回合内收敛到了一个相当稳定的最优策略。
如图所示,gamma=0.99
时收敛更快(最佳折扣回合奖励约为86.6)。
非常感谢@XXX,他“尝试了这个方法”
回答:
我打算试一试。
.backward()
不需要损失函数,它只需要一个可微分的标量输出。它近似计算关于模型参数的梯度。让我们先看看价值函数的更新情况。
我们有一个关于v的梯度,我们可以通过以下方式近似这个梯度:
v = model(s)v.backward()
这给了我们v
的梯度,它具有模型参数的维度。假设我们已经计算了其他参数更新,我们可以计算实际的优化器更新:
for i, p in enumerate(model.parameters()): z_theta[i][:] = gamma * lamda * z_theta[i] + l * p.grad p.grad[:] = alpha * delta * z_theta[i]
然后我们可以使用opt.step()
来使用调整后的梯度更新模型参数。