如何避免Theano计算梯度趋向NaN。

4

我正在使用5个卷积层,2个隐藏层和1个Softmax来尝试CNN。

架构如下:

cv0->relu->cv1->relu-cv2->relu->cv3->relu->cv4->relu->cv5->hid1->relu->hid2->relu->logistic softmax

通过对图像使用66个块的随机梯度来训练,目的是进行测试,只在单个图像上进行20个迭代的训练。
从网络中识别到错误在每次迭代中都会爆炸,因此梯度在第3或第4个迭代后就开始计算nan。
如下所示,在错误值爆炸成非常高的值之后,梯度产生了NaN,这被传播到整个网络中。
查看不同层次节点的权重值以了解发生了什么: layer8(softmax):
初始值[0.05436778 0.02379715]
epoch 1 [0.28402206 -0.20585714]
epoch 2 [-0.0527361184 0.0952038541]
epoch 3 [-7330.04199219 7330.12011719]
epoch 4 [nan nan] layer6(hid1):
初始值[-0.0254469 0.00760095 ..., -0.00587915 0.02619855 0.03809309]
epoch 1 [-0.0254469 0.00760095 ..., -0.00587915 0.02619855 0.03809309]
epoch 2 [-0.0254469 0.00760095 ..., -0.00587915 0.02619855 0.03809309
epoch 3 [-2.54468974e-02 1.79247314e+16 ..., -5.87915350e-03 2.61985492e-02 -2.06307964e+19]
epoch 4 [nan nan ..., nan nan nan] layer0(cv0):
初始化时为:
[[-0.01704694 -0.01683052 -0.0894756 ]
 [ 0.12275343 -0.05518051 -0.09202443]
 [-0.11599202 -0.04718829 -0.04359322]]

第三个时期是

[[-24165.15234375 -26490.89257812 -24820.1484375 ]
 [-27381.8203125  -26653.3359375  -24762.28710938]
 [-23120.56835938 -21189.44921875 -24513.65039062]]

很明显,权重值正在爆炸。
学习速率为0.01,为了解决这个问题,我将学习速率改为0.001,有时Nan会消失,网络会收敛,但有时会饱和并产生NaN。尝试更小的学习速率0.0001,并没有看到NaN出现。从结果来看,每次重新运行代码时,结果都非常不同,我认为这与权重初始化有关。
于是,我尝试了不同的权重初始化方法:
对于具有ReLU的卷积层:
W_bound_6 = numpy.sqrt(6. / (fan_in + fan_out))
W_bound_2 = numpy.sqrt(2. / (fan_in + fan_out))
W_values = numpy.asarray(
                numpy.random.randn(filter_shape[0], filter_shape[1], filter_shape[2], filter_shape[3]) * W_bound_2,
                dtype=theano.config.floatX)

对于隐藏层和softmax层

W_bound_2 = numpy.sqrt(2. / (filter_shape[0] + filter_shape[1]))
W_values = numpy.asarray(
                numpy.random.randn(filter_shape[0], filter_shape[1]) * W_bound_2,
                dtype=theano.config.floatX
            )

并将 b 初始化为零。

差别不是很大,我仍然看不出结果上有何不同。

我在这里发布我的问题,目的是:

  • 讨论我在权重初始化方面是否与编码正确;
  • 看看我是否可以避免使学习率非常小,而保持至少前几次迭代的高水平,因为在我的情况下,第四次迭代中会传递NaN。
  • 我想知道L1, L2正则化是否需要在成本函数中实现,因为我正在使用Theano,在哪里应该实现代码,还是我应该更改更新函数。

成本函数

-T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]), y])

更新功能

updates = [
        (param_i, param_i - learning_rate * grad_i)
        for param_i, grad_i in zip(classifier.params, grads)
    ]
  • 在我的结构中,每层后都使用ReLU实现,但在Softmax后不使用,这样做正确吗?

我对CNN不是很了解,但你尝试过剪辑梯度吗?在某个时候,你可能会有grads = T.grad(cost, classifier.params)。尝试添加一行新代码grads_clipped = [T.clip(g, -2, 2] for g in grads),并将更新中的zip中的grads替换为grads_clipped,即zip(classifier.params, grads_clipped) - Andrej Žukov-Gregorič
我曾在Google Group上看到过这种方法,以避免梯度爆炸。我不记得讨论的具体内容是什么,是否是一种好的技术。 - Feras
1
值得一试。关于L1,L2正则化,您只需要将其附加到您的损失函数末尾cost = -T.mean(T.log(self.p_y_given_x)[T.arange(y.shape[0]), y] + L1 + L2)即可。 - Andrej Žukov-Gregorič
2个回答

2
我正在研究不同的方法来避免这个问题,但是寻找其他人提出的正式解决方案并阅读一些理论解决方案后,我会在此处写下我的答案,以帮助其他人解决相同的问题。
这个问题的原因是使用softmax和交叉熵。因此,当计算梯度并除以零或inf时,会得到nan,这会向后传播所有网络参数。
避免这个问题的几个建议:
1. 如果错误开始增加,然后NaN随后出现:由于学习率过高而发散 2. 如果NaN突然出现:饱和单元产生非可区分梯度NaN计算,由于log(0) 3. NaN由于浮点问题(权重太高)或输出0/0,inf/inf,inf*weight上的激活 4. 解决方案: 1. 减少学习率 2. 更改权重初始化 3. 使用L2范数 4. 安全softmax(在log(x)中添加小值) 5. 梯度剪辑
在我的情况下,学习率解决了这个问题,但我仍在努力优化它。

0
我猜可能是“死relu”问题导致了数学错误。负对数似然成本函数涉及自然对数计算,不期望为零。Relu函数可以输出零,而零的自然对数未定义,因此会返回NaN。在最后一层中,尝试使用不输出负数和零的函数或尝试另一个代价函数。

负对数似然度将计算在softmax的概率分布上,因此我不认为它是死Relu。问题与高代价误差相关,这会导致NaN在整个网络中传播。 - Feras

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接