¿Por qué un solo ReLU no puede aprender un ReLU?

15

Como seguimiento a Mi red neuronal ni siquiera puede aprender la distancia euclidiana , simplifiqué aún más y traté de entrenar una única ReLU (con peso aleatorio) en una sola ReLU. Esta es la red más simple que existe y, sin embargo, la mitad del tiempo no logra converger.

Si la suposición inicial está en la misma orientación que el objetivo, aprende rápidamente y converge al peso correcto de 1:

animación de aprendizaje ReLU ReLU

curva de pérdida que muestra puntos de convergencia

Si la suposición inicial es "al revés", se queda atascado en un peso de cero y nunca pasa a la región de menor pérdida:

animación de ReLU que no aprende ReLU

curva de pérdida de ReLU que no aprende ReLU

primer plano de la curva de pérdida en 0

No entiendo por qué. ¿No debería el descenso de gradiente seguir fácilmente la curva de pérdida a los mínimos globales?

Código de ejemplo:

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

ingrese la descripción de la imagen aquí

Suceden cosas similares si agrego sesgo: la función de pérdida 2D es suave y simple, pero si el relu comienza al revés, da vueltas y se atasca (puntos de inicio rojos), y no sigue el gradiente hacia abajo al mínimo (como hace para puntos de partida azules):

ingrese la descripción de la imagen aquí

Suceden cosas similares si agrego también el peso de salida y el sesgo. (Se moverá de izquierda a derecha o de abajo hacia arriba, pero no ambas).

endolito
fuente
3
@Sycorax No, esto no es un duplicado, pregunta por un problema específico, no por consejos generales. He pasado una cantidad de tiempo considerable reduciendo esto a un ejemplo mínimo, completo y verificable. No lo elimine solo porque es vagamente similar a alguna otra pregunta general. Uno de los pasos en la respuesta aceptada a esa pregunta es "Primero, construya una pequeña red con una sola capa oculta y verifique que funcione correctamente. Luego agregue incrementalmente la complejidad del modelo adicional y verifique que cada uno de ellos también funcione". Eso es exactamente lo que estoy haciendo y no está funcionando.
endolito
2
Realmente estoy disfrutando de esta "serie" en NN aplicada a funciones simples: eats_popcorn_gif:
Cam.Davidson.Pilon
ReLU funciona como un rectificador ideal, por ejemplo, un diodo. Es unidireccional. Si desea corregir la dirección, considere usar softplus, luego cambie a ReLU cuando el entrenamiento sea positivo, o use alguna otra variante como ELU.
Carl
x<0x<0
1
X

Respuestas:

14

ww=0w=0w=1w se inicializa como negativo, es posible converger a una solución subóptima.

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

F

F(X)={w,Si X>0 00 0,Si X<0 0

w<0 00 0w=1El |wEl |

w(0 0)<0 0w(yo)=0 0

Esto está relacionado con el fenómeno de la muerte relu; para alguna discusión, vea Mi red ReLU no se inicia

Un enfoque que podría ser más exitoso sería utilizar una no linealidad diferente, como la fuga relu, que no tiene el llamado problema del "gradiente de fuga". La función relu permeable es

sol(X)={X,Si X>0 0CX,de otra manera
CEl |CEl |

sol(X)={1,Si X>0 0C,Si X<0 0

C=0 0C0.10,3C<0 0C=1,El |CEl |>1

wLeakyReLUReLUw=1

LeakyReLU soluciona el 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 0)

w(0 0)=-10

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

El código relevante está debajo; usar opt_sgdo 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 dice reinstalar a Mónica
fuente
Vi el mismo problema con LeakyReLU, ELU, SELU cuando tuve un peso de salida y un sesgo, pero no estoy seguro de si probé aquellos sin la salida. Lo comprobaré
endolith el
1
(Sí, tienes razón en que LeakyReLU y ELU funcionan bien para este ejemplo)
endolito el
2
Oh ya entiendo. Se está haciendo descenso de gradiente de la función de pérdida, es sólo que la función de pérdida se vuelve plano (0 gradiente) a 0 cuando se aproxima desde el lado negativo, por lo que el descenso de gradiente se atasca allí. Ahora parece obvio. : D
endolito el
2
ww=0 0
2
w(i)