为什么单个ReLU无法学习ReLU?


15

作为我神经网络甚至无法学习欧几里德距离的后续操作,我进一步简化了方法,并尝试将单个ReLU(具有随机权重)训练为单个ReLU。这是目前最简单的网络,但有一半时间未能融合。

如果初始猜测与目标的方位相同,则它会快速学习并收敛到正确的权重1:

ReLU学习ReLU的动画

损耗曲线显示收敛点

如果最初的猜测是“向后”,则它的权重为零,并且永远不会经过它到达较低损失的区域:

ReLU动画无法学习ReLU

ReLU的损失曲线未能学习ReLU

损耗曲线在0的特写

我不明白为什么。梯度下降不应该轻易遵循损耗曲线达到全局最小值吗?

示例代码:

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, ReLU
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt

batch = 1000


def tests():
    while True:
        test = np.random.randn(batch)

        # Generate ReLU test case
        X = test
        Y = test.copy()
        Y[Y < 0] = 0

        yield X, Y


model = Sequential([Dense(1, input_dim=1, activation=None, use_bias=False)])
model.add(ReLU())
model.set_weights([[[-10]]])

model.compile(loss='mean_squared_error', optimizer='sgd')


class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.losses = []
        self.weights = []
        self.n = 0
        self.n += 1

    def on_epoch_end(self, batch, logs={}):
        self.losses.append(logs.get('loss'))
        w = model.get_weights()
        self.weights.append([x.flatten()[0] for x in w])
        self.n += 1


history = LossHistory()

model.fit_generator(tests(), steps_per_epoch=100, epochs=20,
                    callbacks=[history])

fig, (ax1, ax2) = plt.subplots(2, 1, True, num='Learning')

ax1.set_title('ReLU learning ReLU')
ax1.semilogy(history.losses)
ax1.set_ylabel('Loss')
ax1.grid(True, which="both")
ax1.margins(0, 0.05)

ax2.plot(history.weights)
ax2.set_ylabel('Weight')
ax2.set_xlabel('Epoch')
ax2.grid(True, which="both")
ax2.margins(0, 0.05)

plt.tight_layout()
plt.show()

在此处输入图片说明

如果我添加偏差,也会发生类似的事情:2D损失函数既平滑又简单,但是如果relu上下颠倒,它就会绕圈并卡住(红色起始点),并且不会遵循梯度下降到最小值(就像它一样)对于蓝色起点):

在此处输入图片说明

如果我也增加输出权重和偏差,也会发生类似的情况。(它会左右翻转,或上下翻转,但不能同时翻转)。


3
@Sycorax不,这不是重复的,它询问的是特定问题,而不是一般性建议。我花费了大量时间将其简化为最小,完整和可验证的示例。请不要仅仅因为它与其他一些过于宽泛的问题相似而删除它。对该问题的公认答案中的步骤之一是:“首先,建立一个具有单个隐藏层的小型网络,并验证其是否正常工作。然后逐步增加其他模型复杂性,并验证每个模型的正常性。” 那正是我在做的事,并且没有用。
endolith '18

2
我真的很喜欢这个应用于简单功能的NN系列:eats_popcorn_gif:
Cam.Davidson.Pilon

ReLU的功能类似于理想的整流器,例如二极管。它是单向的。如果您想纠正方向,请考虑使用softplus,然后在训练积极时切换到ReLU,或者使用其他一些变量,例如ELU。
卡尔,

这个说的另一种方式,一个RELU有望成为无用的,看看学习为 ; 它是平坦的,它不会学习。x < 0x<0x<0
卡尔,

1
当小于零时,梯度趋于零。它停了下来。x
卡尔,

Answers:


14

在您的图中有一个暗示,表明损耗是的函数。这些图在附近有一个“扭结” :这是因为在0的左侧,损耗的梯度逐渐消失为0(但是,是次优的解决方案,因为那里的损耗比)。此外,此图还显示了损失函数是非凸的(您可以在3个或更多位置绘制一条与损失曲线交叉的线),因此在使用局部优化器(例如SGD)时,我们应该谨慎行事。确实,以下分析表明,当初始化为负数时,有可能收敛到次优解。ww=0w = 0 w = 1 ww=0w=1w

优化问题是

minw,bf(x)y22f(x)=max(0,wx+b)

并且您正在使用一阶优化来做到这一点。这种方法的问题是具有梯度f

f(x)={w,if x>00,if x<0

当您从开始时,您将必须移至的另一侧才能接近正确的答案,即。这很困难,因为非常非常小,梯度也会逐渐变小。而且,您从左侧越接近0,则进度会越慢!w<00w=1|w|

这就是为什么在您的图中,对于为负的初始化,您的轨迹都停在。这也是第二个动画显示的内容。w(0)<0w(i)=0

这与垂死的鲁鲁现象有关;有关某些讨论,请参阅My ReLU网络无法启动

可能更成功的方法是使用不同的非线性,例如泄漏的磁阻效应,它没有所谓的“消失梯度”问题。泄漏的relu函数是

g(x)={x,if x>0cx,otherwise
其中是一个常数,因此小而积极。起作用的原因是导数不是“左侧”为0。c|c|

g(x)={1,if x>0c,if x<0

设置是普通的relu。大多数人选择为或。我还没有看到使用,但是我有兴趣研究一下它对这样的网络有什么影响。(请注意,对于这简化为恒等函数;对于,许多这样的层的组成可能会导致爆炸梯度,因为梯度在连续的层中会变大。)c=0c0.10.3c<0c=1,|c|>1

稍微修改OP的代码可以证明问题在于激活功能的选择。此代码将初始化为负数,并使用in代替normal 。损耗迅速减小到一个很小的值,权重正确地移动到,这是最佳的。wLeakyReLUReLUw=1

LeakyReLU解决了问题

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, ReLU
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt

batch = 1000


def tests():
    while True:
        test = np.random.randn(batch)

        # Generate ReLU test case
        X = test
        Y = test.copy()
        Y[Y < 0] = 0

        yield X, Y


model = Sequential(
    [Dense(1, 
           input_dim=1, 
           activation=None, 
           use_bias=False)
    ])
model.add(keras.layers.LeakyReLU(alpha=0.3))
model.set_weights([[[-10]]])

model.compile(loss='mean_squared_error', optimizer='sgd')


class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.losses = []
        self.weights = []
        self.n = 0
        self.n += 1

    def on_epoch_end(self, batch, logs={}):
        self.losses.append(logs.get('loss'))
        w = model.get_weights()
        self.weights.append([x.flatten()[0] for x in w])
        self.n += 1


history = LossHistory()

model.fit_generator(tests(), steps_per_epoch=100, epochs=20,
                    callbacks=[history])

fig, (ax1, ax2) = plt.subplots(2, 1, True, num='Learning')

ax1.set_title('LeakyReLU learning ReLU')
ax1.semilogy(history.losses)
ax1.set_ylabel('Loss')
ax1.grid(True, which="both")
ax1.margins(0, 0.05)

ax2.plot(history.weights)
ax2.set_ylabel('Weight')
ax2.set_xlabel('Epoch')
ax2.grid(True, which="both")
ax2.margins(0, 0.05)

plt.tight_layout()
plt.show()

另一层复杂性来自以下事实:我们不是无限地运动,而是有限地多次“跳跃”,这些跳跃将我们从一次迭代转移到了另一次迭代。这意味着在某些情况下负初始值不会卡住;这些情况是由于和梯度下降步长的特定组合而产生的,该步长足够大以“消失”在消失的梯度上。w w(0)

我已经使用了一些代码,发现将初始化保留在,并将优化器从SGD更改为Adam,Adam + AMSGrad或SGD +动量没有任何帮助。此外,从SGD更改为Adam不仅减缓了在此问题上消失的梯度,而且实际上减缓了进度。w(0)=10

另一方面,如果将初始化更改为并将优化器更改为Adam(步长为0.01),则实际上可以克服消失的梯度。如果您使用和SGD带动量(步长为0.01),它也可以使用。如果您使用香草SGD(步长为0.01)并且它甚至可以工作。w(0)=1 w(0)=1w(0)=1

相关代码如下;使用opt_sgdopt_adam

opt_sgd = keras.optimizers.SGD(lr=1e-2, momentum=0.9)
opt_adam = keras.optimizers.Adam(lr=1e-2, amsgrad=True)
model.compile(loss='mean_squared_error', optimizer=opt_sgd)

当我有输出权重和偏差时,我也遇到了LeakyReLU,ELU,SELU的相同问题,但是我不确定是否尝试了没有输出的问题。我会检查
endolith

1
(是的,对于这个示例,LeakyReLU和ELU正常工作是正确的)
endolith

2
知道了 它正在执行损失函数的梯度下降,只是当从负侧接近时,损失函数在0处变为平坦(0梯度),因此梯度下降被卡在那里。现在看来很明显。:D
endolith '18

2
究竟。请注意,损耗与的关系图如何在0附近出现“扭结”:这是因为在0的左侧,损耗的梯度逐渐消失为0(但是,这是次优的解决方案,因为损耗高于该值对于)。此外,此图还显示了损失函数是非凸的(您可以在3个或更多位置绘制一条与损失曲线交叉的线),因此在使用局部优化器(例如SGD)时,我们应该谨慎行事。ww=0
Sycorax说,请恢复莫妮卡

2
当使用relu激活时,如果步长足够大,无论的特定值是多少,即使没有动量的SGD 也可以越过边缘。w(i)
Sycorax说,恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.