【问题标题】:Loss clipping in tensor flow (on DeepMind's DQN)张量流中的损失裁剪(在 DeepMind 的 DQN 上)
【发布时间】:2016-07-27 13:18:34
【问题描述】:

我正在尝试自己在张量流中实现 Deepmind 的 DQN 论文,但在裁剪损失函数时遇到了困难。

这是描述损失裁剪的自然论文的摘录:

我们还发现将更新中的误差项限制在 -1 和 1 之间很有帮助。因为绝对值损失函数 |x|对于 x 的所有负值,导数为 -1,对于 x 的所有正值,导数为 1,将平方误差限制在 -1 和 1 之间对应于对 (- 1,1) 间隔。这种形式的错误裁剪进一步提高了算法的稳定性。

(全文链接:http://www.nature.com/nature/journal/v518/n7540/full/nature14236.html

到目前为止我尝试过的是使用

clipped_loss_vec = tf.clip_by_value(loss, -1, 1)

将我计算的损失限制在 -1 和 +1 之间。在这种情况下,代理没有学习正确的策略。我打印了网络的梯度,发现如果损失低于-1,梯度会突然变成0!

我对这种情况发生的原因是裁剪损失是 (-inf,-1) U (1,inf) 中的一个常数函数,这意味着它在这些区域中的梯度为零。这反过来又确保了整个网络的梯度为零(可以将其想象为,无论我向网络提供什么输入图像,在本地邻域中的损失都保持在 -1,因为它已被裁剪)。

所以,我的问题是两个部分:

  1. Deepmind 在摘录中究竟是什么意思?他们是否意味着低于 -1 的损失被剪裁为 -1 而高于 +1 的损失被剪裁为 +1。如果是这样,他们是如何处理梯度的(即关于绝对值函数的所有部分是什么?)

  2. 我应该如何在张量流中实现损失裁剪,以使梯度在裁剪范围之外不会变为零(但可能保持在 +1 和 -1)? 谢谢!

【问题讨论】:

  • fleibfried 的回答是正确的。然而,一些 DQN 实现确实将损失从 -1 削减到 1。这是有效的,因为游戏奖励也从 -1 削减到 1,这缓解了这个问题。

标签: neural-network tensorflow deep-learning conv-neural-network


【解决方案1】:

首先,论文的代码是available online,这是一个非常宝贵的参考。

第 1 部分

如果你看一下代码,你会发现,在nql:getQUpdateNeuralQLearner.lua,第 180 行)中,它们剪掉了 Q-learning 函数的错误项:

-- delta = r + (1-terminal) * gamma * max_a Q(s2, a) - Q(s, a)
if self.clip_delta then
    delta[delta:ge(self.clip_delta)] = self.clip_delta
    delta[delta:le(-self.clip_delta)] = -self.clip_delta
end

第 2 部分

在 TensorFlow 中,假设您的神经网络的最后一层称为 self.outputself.actions 是所有动作的 one-hot 编码,self.q_targets_ 是目标的占位符,self.q 是您计算的问:

# The loss function
one = tf.Variable(1.0)
delta = self.q - self.q_targets_
absolute_delta = tf.abs(delta)
delta = tf.where(
    absolute_delta < one,
    tf.square(delta),
    tf.ones_like(delta) # squared error: (-1)^2 = 1
)

或者,使用tf.clip_by_value(并且实现更接近原始版本):

delta = tf.clip_by_value(
    self.q - self.q_targets_,               
    -1.0,                
    +1.0                 
)                                                 

【讨论】:

  • 看到一个最近的 TF 问题:github.com/tensorflow/tfjs/issues/338,tf.where 不能发挥作用,因为它不流动渐变。我个人也应用了 tf.clip_by_value,但它都不起作用
  • 从问题来看,现在似乎已修复。尽管如此,看起来 Huber 损失似乎是实现这一点的正确方法。
【解决方案2】:
  1. 在您引用的 Deep Mind 论文中,它们限制了损失的梯度。这可以防止巨大的梯度,从而提高鲁棒性。为此,他们对小范围内的错误使用二次损失函数,对较大的错误使用绝对值损失。
  2. 我建议实施Huber loss function。下面是一个python tensorflow实现。

    def huber_loss(y_true, y_pred, max_grad=1.):
        """Calculates the huber loss.
    
        Parameters
        ----------
        y_true: np.array, tf.Tensor
          Target value.
        y_pred: np.array, tf.Tensor
          Predicted value.
        max_grad: float, optional
          Positive floating point value. Represents the maximum possible
          gradient magnitude.
    
        Returns
        -------
        tf.Tensor
          The huber loss.
        """
        err = tf.abs(y_true - y_pred, name='abs')
        mg = tf.constant(max_grad, name='max_grad')
    
        lin = mg*(err-.5*mg)
        quad=.5*err*err
    
        return tf.where(err < mg, quad, lin)
    

【讨论】:

    【解决方案3】:
    1. 没有。他们实际上谈论的是错误裁剪,而不是关于损失裁剪,但据我所知,这是指同一件事,但会导致混淆。它们并不意味着低于 -1 的损失被剪裁为 -1,而高于 +1 的损失被剪裁为 +1,因为这会导致误差范围 [-1;1] 之外的零梯度,正如您所意识到的那样。相反,他们建议在误差值 1 时使用线性损失而不是二次损失。

    2. 计算误差值(r + \gamma \max_{a'} Q(s',a'; \theta_i^-) - Q(s,a; \theta_i))。如果此误差值在 [-1;1] 范围内,则对其进行平方,如果误差值 1,则保持原样。如果将其用作损失函数,则区间 [-1;1] 之外的梯度不会消失。

    为了获得“看起来平滑”的复合损失函数,您还可以将误差范围 [-1;1] 之外的平方损失替换为边界值 -1 和 1 处的一阶泰勒近似值。在在这种情况下,如果 e 是您的错误值,则在 e \in [-1;1] 的情况下将其平方,在 e 1 的情况下,将其替换为2e-1.

    【讨论】:

      【解决方案4】:

      我怀疑他们的意思是你应该将 gradient 剪辑到 [-1,1],而不是剪辑 损失函数。因此,您照常计算梯度,然后将梯度的每个分量剪裁到 [-1,1] 范围内(因此,如果它大于 +1,则将其替换为 +1;如果它小于-1,您将其替换为 -1);然后在梯度下降更新步骤中使用结果,而不是使用未修改的梯度。

      等价:定义一个函数f如下:

      f(x) = x^2          if x in [-0.5,0.5]
      f(x) = |x| - 0.25   if x < -0.5 or x > 0.5
      

      他们建议使用f(s) 作为损失函数,而不是使用s^2 形式的东西作​​为损失函数(其中s 是一些复杂的表达式)。这是平方损失和绝对值损失之间的某种混合:当s 很小时,它的行为类似于s^2,但是当s 变大时,它的行为就像绝对值(|s|) .

      请注意,f 的导数具有很好的特性,即它的导数始终在 [-1,1] 范围内:

      f'(x) = 2x    if x in [-0.5,0.5]
      f'(x) = +1    if x > +1
      f'(x) = -1    if x < -1
      

      因此,当你取这个基于f 的损失函数的梯度时,结果将与计算平方损失的梯度然后对其进行裁剪相同。

      因此,他们正在做的实际上是用Huber loss 替换平方损失。 f 函数只是 delta = 0.5 时 Huber 损失的两倍。

      现在的重点是以下两个替代方案是等价的:

      • 使用平方损失函数。计算这个损失函数的梯度,但是在做梯度下降的更新步骤之前,梯度到[-1,1]。

      • 使用 Huber 损失函数而不是平方损失函数。在梯度下降中直接(不变)计算这个损失函数的梯度。

      前者很容易实现。后者具有很好的特性(提高了稳定性;它比绝对值损失更好,因为它避免了在最小值附近振荡)。因为两者是等价的,这意味着我们得到了一个易于实现的方案,它具有平方损失的简单性和 Huber 损失的稳定性和鲁棒性。

      【讨论】:

      • 我认为这两种选择有一个微妙但重要的区别。当使用 DQN 时,权重 w 通过损失的梯度进行更新。因此对于 Huber 损失,梯度将是 d(Q_s,a)/dw 或 -d(Q_s,a)/dw [-0.5,0.5]。这是因为 |loss| 的梯度= +1 或 -1。使用平方损失函数并裁剪梯度意味着整个生成的梯度被限制为 [-1,+1] 而不仅仅是梯度的第一项。因此,要获得与 DQN 论文相似的结果,我认为应该使用 Huber 损失。
      猜你喜欢
      • 2018-12-16
      • 2018-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-02
      • 2018-02-03
      • 2019-02-11
      相关资源
      最近更新 更多