【发布时间】: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 是两个节点属性 vi、vj 之间的元素均值,它们形成具有边属性 e 的边。 Net 是一个单层前馈网络。这样一来,训练损失在大约 30 个 epoch 后突然跳到 NaN,batch size 为 32。如果 batch size 为 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.5
【问题讨论】:
-
你提到的图是有向无环图吗?
-
它是无向的,图中可能存在循环。
-
也许你应该对节点值引入一些正则化?似乎您要解决的问题类似于因子图,当因子图有循环时,消息传递算法可能难以收敛。我不确定这是否与您的问题有关。请您写一个更详细的描述,以便我们分析,好吗?
-
我认为我的图表不一定是因子图。我所有的图表都代表晶体结构(所谓的crystal graphs)。如果 2 个原子之间的距离小于阈值,我认为它们是“粘合的”。有了这个定义,我可能有循环图。节点(原子)属性已正确规范化。但我相信问题出在这个边缘属性
e。因为只有当我连接e时,渐变才会开始爆炸。请让我知道您需要更多详细信息。我可以更新问题。
标签: python tensorflow machine-learning keras gradient