Por que um único ReLU não pode aprender um ReLU?

15

Como um acompanhamento de Minha rede neural não consegue nem aprender a distância euclidiana , simplifiquei ainda mais e tentei treinar uma única ReLU (com peso aleatório) em uma única ReLU. Essa é a rede mais simples que existe e, ainda assim, metade do tempo em que falha na convergência.

Se o palpite inicial estiver na mesma orientação que o destino, ele aprenderá rapidamente e convergirá para o peso correto de 1:

animação de ReLU aprendendo ReLU

curva de perda mostrando pontos de convergência

Se o palpite inicial for "para trás", ele fica preso a um peso zero e nunca passa por ele para a região de menor perda:

animação de ReLU falhando em aprender ReLU

curva de perda de ReLU que não aprende ReLU

close da curva de perda em 0

Eu não entendo o porquê. A descida do gradiente não deve seguir facilmente a curva de perda até os mínimos globais?

Código de exemplo:

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()

insira a descrição da imagem aqui

Coisas semelhantes acontecem se eu adicionar viés: a função de perda 2D é suave e simples, mas se o relu começar de cabeça para baixo, ele circula e fica preso (pontos de partida vermelhos) e não segue o gradiente até o mínimo (como ele faz para pontos de partida azuis):

insira a descrição da imagem aqui

Coisas semelhantes acontecem se eu adicionar peso e viés de saída também. (Virará da esquerda para a direita ou de cima para baixo, mas não ambos.)

endólito
fonte
3
@ Sycorax Não, isso não é duplicado, ele pergunta sobre um problema específico, não conselhos gerais. Passei uma quantidade significativa de tempo reduzindo isso para um exemplo mínimo, completo e verificável. Por favor, não exclua-o apenas porque é vagamente semelhante a alguma outra questão abrangente. Uma das etapas da resposta aceita para essa pergunta é "Primeiro, construa uma rede pequena com uma única camada oculta e verifique se ela funciona corretamente. Em seguida, adicione incrementalmente a complexidade do modelo e verifique se cada uma delas também funciona". É exatamente o que estou fazendo e não está funcionando.
endolith
2
Estou realmente gostando disso "série" na NN aplicado a funções simples: eats_popcorn_gif:
Cam.Davidson.Pilon
ReLU funciona como um retificador ideal, por exemplo, um diodo. É unidirecional. Se você deseja que a direção seja corrigida, considere usar o softplus e, em seguida, mudar para ReLU quando o treinamento for positivo ou usar alguma outra variante, como ELU.
Carl
x<0x<0
1
x

Respostas:

14

ww=0 0w=0w=1w inicializado como negativo, é possível convergir para uma solução abaixo do ideal.

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

f

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

w<00w=1|w|

w(0)<0w(i)=0

Isso está relacionado ao fenômeno da morte relu; para alguma discussão, consulte Minha rede ReLU falha ao iniciar

Uma abordagem que poderia ser mais bem-sucedida seria usar uma não-linearidade diferente, como o relu com vazamento, que não possui a chamada questão do "gradiente de fuga". A função relu com vazamento é

g(x)={x,if x>0cx,de outra forma
c|c|

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

c=0c0.10.3c<0c=1,|c|>1

wLeakyReLUReLUw=1

LeakyReLU corrige o problema

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)

w(0)=10

w(0)=1 w(0)=1w(0)=1

O código relevante está abaixo; use opt_sgdou opt_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)
Sycorax diz restabelecer Monica
fonte
Eu vi o mesmo problema com LeakyReLU, ELU, SELU quando tive um peso e um viés de saída, mas não tenho certeza se tentei aqueles sem a saída. Vou verificar
endolith
1
(Sim, você está certo de que LeakyReLU e ELU funcionam bem para este exemplo)
endolith
2
Oh, entendi. Ele está fazendo a descida gradiente da função de perda, mas a função de perda fica plana (gradiente 0) a 0 quando se aproxima do lado negativo, para que a descida do gradiente fique presa ali. Agora parece óbvio. : D
endólito 2/12
2
ww=0
2
w(i)