Perché una sola ReLU non può imparare una ReLU?


15

Come follow-up della mia rete neurale non riesco nemmeno a imparare la distanza euclidea, ho semplificato ancora di più e ho cercato di addestrare una singola ReLU (con peso casuale) su una singola ReLU. Questa è la rete più semplice che ci sia, eppure la metà delle volte non riesce a convergere.

Se l'ipotesi iniziale ha lo stesso orientamento del bersaglio, impara rapidamente e converge al peso corretto di 1:

animazione di ReLU apprendimento di ReLU

curva di perdita che mostra i punti di convergenza

Se l'ipotesi iniziale è "all'indietro", si blocca con un peso pari a zero e non passa mai attraverso la regione di perdita inferiore:

l'animazione di ReLU non riesce a imparare ReLU

curva di perdita di ReLU non in grado di apprendere ReLU

primo piano della curva di perdita a 0

Non capisco perché. La discesa del gradiente non dovrebbe seguire facilmente la curva di perdita ai minimi globali?

Codice di esempio:

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

inserisci qui la descrizione dell'immagine

Cose simili accadono se aggiungo distorsione: la funzione di perdita 2D è fluida e semplice, ma se la relù si avvia sottosopra, gira e si blocca (punti di partenza rossi) e non segue il gradiente fino al minimo (piace fa per i punti di partenza blu):

inserisci qui la descrizione dell'immagine

Cose simili accadono se aggiungo anche peso e distorsione dell'output. (Capovolge da sinistra a destra o dall'alto verso il basso, ma non entrambi.)


3
@Sycorax No, questo non è un duplicato, chiede un problema specifico, non un consiglio generale. Ho trascorso molto tempo a ridurlo a un esempio minimo, completo e verificabile. Per favore, non cancellarlo solo perché è vagamente simile a qualche altra domanda troppo ampia. Uno dei passaggi della risposta accettata a questa domanda è "Innanzitutto, crea una piccola rete con un singolo livello nascosto e verifica che funzioni correttamente. Quindi aggiungi in modo incrementale ulteriore complessità del modello e verifica che anche ognuna di queste funzioni." È esattamente quello che sto facendo e non funziona.
endolith

2
Mi sto davvero godendo questa "serie" su NN applicata a funzioni semplici: eats_popcorn_gif:
Cam.Davidson.Pilon

ReLU funziona come un raddrizzatore ideale, ad esempio un diodo. È unidirezionale. Se si desidera correggere la direzione, prendere in considerazione l'utilizzo di softplus, quindi passare a ReLU quando l'allenamento è positivo o utilizzare qualche altra variante come ELU.
Carl,

x<0x<0

1
x

Risposte:


14

ww=0w=0w=1w è inizializzato per essere negativo, è possibile convergere in una soluzione non ottimale.

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

Ciò è legato al fenomeno dei morenti relu; per alcune discussioni, vedere Impossibile avviare la mia rete ReLU

Un approccio che potrebbe avere più successo sarebbe quello di utilizzare una diversa non linearità come la reli perde, che non ha il cosiddetto problema del "gradiente di sparizione". La funzione relu che perde è

g(x)={x,if x>0cx,otherwise
c|c|

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

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

wLeakyReLUReLUw=1

LeakyReLU risolve il 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

Il codice pertinente è di seguito; usare 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)

Ho visto lo stesso problema con LeakyReLU, ELU, SELU quando avevo un peso in uscita e una propensione, ma non sono sicuro di aver provato quelli senza uscita. Controllerò
endolith il

1
(Sì, hai ragione sul fatto che LeakyReLU ed ELU funzionano bene per questo esempio)
Endolith,

2
Ah, ho capito. Si sta facendo la discesa gradiente della funzione di perdita, è solo che la funzione di perdita diventa piatta (0 gradiente) a 0 quando si avvicina dal lato negativo, quindi la discesa del gradiente ci si blocca. Ora sembra ovvio. : D
endolith,

2
ww=0

2
w(i)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.