图神经网络中的梯度爆炸问题
Gradient exploding problem in a graph neural network
我有一个梯度爆炸的问题,试了好几天都没能解决。我在 TensorFlow 中实现了一个自定义消息传递图形神经网络,用于从图形数据中预测连续值。每个图表都与一个目标值相关联。图的每个节点由节点属性向量表示,节点之间的边由边属性向量表示。
在消息传递层中,节点属性以某种方式更新(例如,通过聚合其他 node/edge 属性),并返回这些更新的节点属性。
现在,我设法找出我的代码中出现梯度问题的位置。我有以下代码段。
to_concat = [neighbors_mean, e]
z = K.concatenate(to_concat, axis=-1)
output = self.Net(z)
这里,neighbors_mean
是两个节点属性 vi
、vj
之间的逐元素平均值,它们形成具有边属性 e
的边。 Net
是单层前馈网络。这样,训练损失在批大小为 32 的大约 30 个 epoch 后突然跳到 NaN。如果批大小为 128,梯度仍然在大约 200 个 epoch 后爆炸。
我发现,在这种情况下,梯度爆炸是因为边缘属性 e
。如果我没有将 neighbors_mean
与 e
连接起来而只是使用下面的代码,就不会出现梯度爆炸。
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)
我基本上尝试了网上建议的所有方法来避免梯度爆炸。
- 应用渐变裁剪 -
Adam(lr, clipnorm=1, clipvalue=5)
和 tf.clip_by_global_norm(gradients, 1.0)
- 我的目标变量总是按比例缩放
- 权重使用
glorot_uniform
分布进行初始化
- 对权重应用正则化
- 尝试了更大的批量大小(直到 256,尽管在某些时候会发生延迟梯度爆炸)
- 尝试降低学习率
我在这里错过了什么?我绝对知道它与连接 e
有关。但鉴于 0.5e 对我很重要。我还能做些什么来避免模型中的数值溢出?
看起来不错,因为您已经按照大多数解决方案解决了梯度爆炸问题。以下是您可以尝试的所有解决方案的列表
避免梯度爆炸问题的解决方案
适当的权重初始化:根据使用的激活函数使用适当的权重初始化。
Initialization
Activation Function
He
ReLU & variants
LeCun
SELU
Glorot
Softmax, Logistic, None, Tanh
重新设计你的神经网络:在神经网络中使用更少的层数and/or使用更小的批量大小
Choosing Non Saturation activation function: 选择正确的具有降低学习率的激活函数
- ReLU
- 有漏洞的 ReLU
- 随机泄漏 ReLU (RReLU)
- 参数泄漏 ReLU (PReLU)
- 指数线性单位 (ELU)
批量归一化:理想情况下使用批量归一化before/after每一层,基于最适合你的数据集的方法。
-
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")
])
梯度裁剪:好的默认值是 clipnorm=1.0 和 clipvalue=0.5
确保使用了正确的优化器:由于您使用了 Adam 优化器,请检查其他优化器是否最适合您的情况。有关可用优化器的信息,请参阅 this documentation [SGD、RMSprop、Adam、Adadelta、Adagrad、Adamax、Nadam、Ftrl]
通过时间截断反向传播:通常适用于 RNNS,请参考此 documentation
使用LSTM(RNN的解决方案)
在层上使用权重正则化器:将kernel_regularizer
设置为L1或L2。 Weight regularizer document reference
有关详细信息,请参阅 Hands on Machine learning with scikit-learn, keras and tensorflow 书中的第 11 章 Aurélien
感谢这个很棒的调试工具,我解决了这个问题tf.debugging.check_numerics
。
我最初确定连接 e
是问题所在,然后意识到传递给 e
的值比 neighbors_mean
中的值大得多,后者与 e
。一旦将它们连接起来并通过神经网络发送(Net()
在我的代码中),我观察到一些输出为数百个,并且随着训练的进行慢慢达到数千个。
这是有问题的,因为我在消息传递层中有一个 softmax 操作。请注意,softmax 计算指数 (exi/Σexj)。高于 e709 的任何值都会导致 Python 中的数值溢出。这产生了 inf
值,最终所有东西都变成了 nan
是我代码中的问题。所以,这在技术上不是梯度爆炸问题,这就是为什么它不能用梯度裁剪来解决的原因。
我是如何跟踪问题的?
我将 tf.debugging.check_numerics()
片段放在几个 layers/tensors 下,我认为会产生 nan 值。像这样:
tf.debugging.check_numerics(layerN, "LayerN is producing nans!")
一旦层输出在训练期间变为 inf
或 nan
,就会产生 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,并且该层不会产生大量输出。这在不降低性能的情况下解决了问题。
或者,可以使用 。
我有一个梯度爆炸的问题,试了好几天都没能解决。我在 TensorFlow 中实现了一个自定义消息传递图形神经网络,用于从图形数据中预测连续值。每个图表都与一个目标值相关联。图的每个节点由节点属性向量表示,节点之间的边由边属性向量表示。
在消息传递层中,节点属性以某种方式更新(例如,通过聚合其他 node/edge 属性),并返回这些更新的节点属性。
现在,我设法找出我的代码中出现梯度问题的位置。我有以下代码段。
to_concat = [neighbors_mean, e]
z = K.concatenate(to_concat, axis=-1)
output = self.Net(z)
这里,neighbors_mean
是两个节点属性 vi
、vj
之间的逐元素平均值,它们形成具有边属性 e
的边。 Net
是单层前馈网络。这样,训练损失在批大小为 32 的大约 30 个 epoch 后突然跳到 NaN。如果批大小为 128,梯度仍然在大约 200 个 epoch 后爆炸。
我发现,在这种情况下,梯度爆炸是因为边缘属性 e
。如果我没有将 neighbors_mean
与 e
连接起来而只是使用下面的代码,就不会出现梯度爆炸。
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
.
我有一个自定义的损失函数来训练这个模型,但是我发现这不是损失的问题(其他损失也导致了同样的问题)。下面是我的自定义损失函数。请注意,虽然这是一个单输出回归网络,但我的 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)
我基本上尝试了网上建议的所有方法来避免梯度爆炸。
- 应用渐变裁剪 -
Adam(lr, clipnorm=1, clipvalue=5)
和tf.clip_by_global_norm(gradients, 1.0)
- 我的目标变量总是按比例缩放
- 权重使用
glorot_uniform
分布进行初始化 - 对权重应用正则化
- 尝试了更大的批量大小(直到 256,尽管在某些时候会发生延迟梯度爆炸)
- 尝试降低学习率
我在这里错过了什么?我绝对知道它与连接 e
有关。但鉴于 0.5
看起来不错,因为您已经按照大多数解决方案解决了梯度爆炸问题。以下是您可以尝试的所有解决方案的列表
避免梯度爆炸问题的解决方案
适当的权重初始化:根据使用的激活函数使用适当的权重初始化。
Initialization Activation Function He ReLU & variants LeCun SELU Glorot Softmax, Logistic, None, Tanh 重新设计你的神经网络:在神经网络中使用更少的层数and/or使用更小的批量大小
Choosing Non Saturation activation function: 选择正确的具有降低学习率的激活函数
- ReLU
- 有漏洞的 ReLU
- 随机泄漏 ReLU (RReLU)
- 参数泄漏 ReLU (PReLU)
- 指数线性单位 (ELU)
批量归一化:理想情况下使用批量归一化before/after每一层,基于最适合你的数据集的方法。
-
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") ])
-
梯度裁剪:好的默认值是 clipnorm=1.0 和 clipvalue=0.5
确保使用了正确的优化器:由于您使用了 Adam 优化器,请检查其他优化器是否最适合您的情况。有关可用优化器的信息,请参阅 this documentation [SGD、RMSprop、Adam、Adadelta、Adagrad、Adamax、Nadam、Ftrl]
通过时间截断反向传播:通常适用于 RNNS,请参考此 documentation
使用LSTM(RNN的解决方案)
在层上使用权重正则化器:将
kernel_regularizer
设置为L1或L2。 Weight regularizer document reference
有关详细信息,请参阅 Hands on Machine learning with scikit-learn, keras and tensorflow 书中的第 11 章 Aurélien
感谢这个很棒的调试工具,我解决了这个问题tf.debugging.check_numerics
。
我最初确定连接 e
是问题所在,然后意识到传递给 e
的值比 neighbors_mean
中的值大得多,后者与 e
。一旦将它们连接起来并通过神经网络发送(Net()
在我的代码中),我观察到一些输出为数百个,并且随着训练的进行慢慢达到数千个。
这是有问题的,因为我在消息传递层中有一个 softmax 操作。请注意,softmax 计算指数 (exi/Σexj)。高于 e709 的任何值都会导致 Python 中的数值溢出。这产生了 inf
值,最终所有东西都变成了 nan
是我代码中的问题。所以,这在技术上不是梯度爆炸问题,这就是为什么它不能用梯度裁剪来解决的原因。
我是如何跟踪问题的?
我将 tf.debugging.check_numerics()
片段放在几个 layers/tensors 下,我认为会产生 nan 值。像这样:
tf.debugging.check_numerics(layerN, "LayerN is producing nans!")
一旦层输出在训练期间变为 inf
或 nan
,就会产生 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,并且该层不会产生大量输出。这在不降低性能的情况下解决了问题。
或者,可以使用