【问题标题】:Gradient exploding problem in a graph neural network图神经网络中的梯度爆炸问题
【发布时间】:2021-11-24 08:30:33
【问题描述】:

我有一个梯度爆炸问题,我尝试了几天后无法解决。我在 TensorFlow 中实现了一个自定义消息传递图神经网络,用于从图数据中预测连续值。每个图表与一个目标值相关联。图的每个节点由一个节点属性向量表示,节点之间的边由一个边属性向量表示。

在消息传递层中,节点属性以某种方式更新(例如,通过聚合其他节点/边缘属性),并返回这些更新的节点属性。

现在,我设法找出了我的代码中出现梯度问题的位置。我有下面的sn-p。

to_concat = [neighbors_mean, e]
z = K.concatenate(to_concat, axis=-1)
output = self.Net(z)

这里,neighbors_mean 是两个节点属性 vivj 之间的元素均值,它们形成具有边属性 e 的边。 Net 是一个单层前馈网络。这样一来,训练损失在大约 30 个 epoch 后突然跳到 NaN,batch size 为 32。如果 batch size 为 128,梯度在大约 200 个 epoch 后仍然会爆炸。

我发现,在这种情况下,渐变由于边缘属性e 而爆炸。如果我没有将neighbors_meane 连接起来,只使用下面的代码,就不会出现渐变爆炸。

output = self.Net(neighbors_mean)

我还可以通过如下的 sigmoid 函数发送e 来避免梯度爆炸。但这会降低性能(最终 MAE),因为 e 中的值非线性映射到 0-1 范围。请注意,Rectified Linear Unit (ReLU) 代替 sigmoid 不起作用。

to_concat = [neighbors_mean, tf.math.sigmoid(e)]
z = K.concatenate(to_concat, axis=-1)
output = self.Net(z)

顺便提一下,e 带有一个与两个对应节点之间的距离相关的值,并且该距离始终在 0.5-4 范围内。 e 中没有大值或 NaN。

我有一个自定义的损失函数来训练这个模型,但是我发现这不是损失的问题(其他损失也导致了同样的问题)。下面是我的自定义损失函数。请注意,虽然这是一个单输出回归网络,但我的 NN 的最后一层有两个神经元,与预测的均值和 log(sigma) 相关。

def robust_loss(y_true, y_pred):
  """
  Computes the robust loss between labels and predictions.
  """
  mean, sigma = tf.split(y_pred, 2, axis=-1)
  # tried limiting 'sigma' with  sigma = tf.clip_by_value(sigma,-4,1.0) but the gradients still explode
  loss =  np.sqrt(2.0) * K.abs(mean - y_true) * K.exp(-sigma)  + sigma
  return K.mean(loss)

我基本上尝试了网上建议的所有方法以避免渐变爆炸。

  1. 应用渐变剪裁 - Adam(lr, clipnorm=1, clipvalue=5)tf.clip_by_global_norm(gradients, 1.0)
  2. 我的目标变量总是按比例缩放的
  3. 权重使用glorot_uniform 分布初始化
  4. 对权重应用正则化
  5. 尝试了更大的批量大小(直到 256,尽管在某些时候会发生延迟梯度爆炸)
  6. 尝试降低学习率

我在这里缺少什么?我绝对知道它与连接e 有关。但是鉴于 0.5e 对我很重要。我还能做些什么来避免模型中的数值溢出?

【问题讨论】:

  • 你提到的图是有向无环图吗?
  • 它是无向的,图中可能存在循环。
  • 也许你应该对节点值引入一些正则化?似乎您要解决的问题类似于因子图,当因子图有循环时,消息传递算法可能难以收敛。我不确定这是否与您的问题有关。请您写一个更详细的描述,以便我们分析,好吗?
  • 我认为我的图表不一定是因子图。我所有的图表都代表晶体结构(所谓的crystal graphs)。如果 2 个原子之间的距离小于阈值,我认为它们是“粘合的”。有了这个定义,我可能有循环图。节点(原子)属性已正确规范化。但我相信问题出在这个边缘属性e。因为只有当我连接e 时,渐变才会开始爆炸。请让我知道您需要更多详细信息。我可以更新问题。

标签: python tensorflow machine-learning keras gradient


【解决方案1】:

看起来很棒,因为您已经按照大多数解决方案来解决梯度爆炸问题。以下是您可以尝试的所有解决方案的列表

避免梯度爆炸问题的解决方案

  1. 适当的权重初始化:根据使用的激活函数使用适当的权重初始化。

    Initialization Activation Function
    He ReLU & variants
    LeCun SELU
    Glorot Softmax, Logistic, None, Tanh
  2. 重新设计您的神经网络:在神经网络中使用更少的层和/或使用更小的批量大小

  3. 选择非饱和激活函数:选择正确的激活函数并降低学习率

    • ReLU
    • ReLU 泄漏
    • 随机泄漏 ReLU (RReLU)
    • 参数泄漏 ReLU (PReLU)
    • 指数线性单元 (ELU)
  4. 批量标准化:理想情况下,根据最适合您的数据集的方法,在每一层之前/之后使用批量标准化。

    • 每一层之后Paper reference

      model = keras.models.Sequential([
                           keras.layers.Flatten(input_shape=[28, 28]),
                           keras.layers.BatchNormalization(),
                           keras.layers.Dense(300, activation="elu", 
                           kernel_initializer="he_normal"),
                           keras.layers.BatchNormalization(),
                           keras.layers.Dense(100, activation="elu", 
                           kernel_initializer="he_normal"),
                           keras.layers.BatchNormalization(),
                           keras.layers.Dense(10, activation="softmax")
               ])
      
    • 每一层之前

       model = keras.models.Sequential([
                           keras.layers.Flatten(input_shape=[28, 28]),
                           keras.layers.BatchNormalization(),
                           keras.layers.Dense(300, kernel_initializer="he_normal", use_bias=False),
                           keras.layers.BatchNormalization(),
                           keras.layers.Activation("elu"),
                           keras.layers.Dense(100, kernel_initializer="he_normal", use_bias=False),
                           keras.layers.Activation("elu"),
                           keras.layers.BatchNormalization(),
                           keras.layers.Dense(10, activation="softmax")
               ])
      
  5. 渐变剪裁:好的默认值是 clipnorm=1.0 和 clipvalue=0.5

  6. 确保使用了正确的优化器:由于您使用了 Adam 优化器,请检查其他优化器是否最适合您的情况。有关可用优化器的信息,请参阅 this documentation [SGD, RMSprop, Adam, Adadelta, Adagrad, Adamax, Nadam, Ftrl]

  7. 随时间截断的反向传播:通常适用于 RNNS,请参阅 documentation

  8. 使用 LSTM(RNN 的解决方案)

  9. 在层上使用权重正则化器:将 kernel_regularizer 设置为 L1 或 L2。 Weight regularizer document reference

有关更多信息,请参阅Aurélien 撰写的使用 scikit-learn、keras 和 tensorflow 进行机器学习一书的第 11 章

【讨论】:

  • 感谢您花时间回答。几天来,我几乎尝试了所有这些东西,但都没有奏效。我怀疑这个问题可能是因为我连接neighbors_meane 的两个张量的尺度不同。我不明白的是为什么模型像往常一样训练几个时期,然后突然损失跳到 NaN。
  • 您似乎只使用 ReLU 并在 sigmoid 中表现良好。请尝试我在 3 中提供的建议,选择正确的非饱和激活函数,尝试不同的 ReLU 变体,例如 Leaky ReLU、随机泄漏 ReLU (RReLU)、参数泄漏 ReLU (PReLU) 或指数线性单元 (ELU)
【解决方案2】:

感谢这个很酷的调试工具tf.debugging.check_numerics,我解决了这个问题。

我最初发现连接e 是问题所在,然后意识到传递给e 的值远大于neighbors_mean 中与e 连接的值。一旦它们被连接并通过神经网络发送(在我的代码中为Net()),我观察到一些输出以数百为顺序,随着训练的进行逐渐达到数千。

这是有问题的,因为我在消息传递层中有一个 softmax 操作。请注意,softmax 计算的是指数 (exi/Σexj)。任何高于 e709 的内容都会导致 Python 中的数值溢出。这产生了inf 值,最终变成nan 是我代码中的问题。所以,这在技术上不是梯度爆炸问题,这就是为什么它不能通过梯度裁剪来解决。

我是如何跟踪问题的?

我将tf.debugging.check_numerics()sn-ps 放在我认为会产生 nan 值的几个层/张量下。像这样的:

tf.debugging.check_numerics(layerN, "LayerN is producing nans!")

一旦层输出在训练期间变为infnan,就会产生InvalidArgumentError

Traceback (most recent call last):
  File "trainer.py", line 506, in <module>
    worker.train_model()
  File "trainer.py", line 211, in train_model
    l, tmae = train_step(*batch)
  File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/eager/def_function.py", line 828, in __call__
    result = self._call(*args, **kwds)
  File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/eager/def_function.py", line 855, in _call
    return self._stateless_fn(*args, **kwds)  # pylint: disable=not-callable
  File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/eager/function.py", line 2943, in __call__
    filtered_flat_args, captured_inputs=graph_function.captured_inputs)  # pylint: disable=protected-access
  File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/eager/function.py", line 1919, in _call_flat
    ctx, args, cancellation_manager=cancellation_manager))
  File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/eager/function.py", line 560, in call
    ctx=ctx)
  File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/eager/execute.py", line 60, in quick_execute
    inputs, attrs, num_outputs)
tensorflow.python.framework.errors_impl.InvalidArgumentError:  LayerN is producing nans! : Tensor had NaN values

现在我们知道问题出在哪里了。

如何解决问题

我将内核约束应用于神经网络权重,其输出被传递到 softmax 函数。

layers.Dense(x, name="layer1", kernel_regularizer=regularizers.l2(1e-6), kernel_constraint=min_max_norm(min_value=1e-30, max_value=1.0))

这应该确保所有权重都小于 1,并且该层不会产生大的输出。这在不降低性能的情况下解决了问题。

或者,可以使用numerically stable implementation of the softmax function

【讨论】:

    猜你喜欢
    • 2022-09-28
    • 2013-11-24
    • 1970-01-01
    • 2020-08-09
    • 2018-11-24
    • 2011-02-21
    • 2021-09-01
    • 2019-10-07
    • 1970-01-01
    相关资源
    最近更新 更多