Jako kontynuacja Mojej sieci neuronowej nie mogę nawet nauczyć się odległości euklidesowej , uprościłem jeszcze bardziej i próbowałem wyszkolić jedną jednostkę ReLU (o losowej wadze) do jednej jednostki ReLU. Jest to najprostsza z dostępnych sieci, a mimo to w połowie przypadków nie jest ona zbieżna.
Jeśli początkowe przypuszczenie jest w tej samej orientacji co cel, uczy się szybko i zbiega do prawidłowej masy 1:
Jeśli początkowe przypuszczenie jest „wstecz”, utknie na wadze zerowej i nigdy nie przejdzie przez to do regionu o niższej stracie:
Nie rozumiem dlaczego. Czy spadek gradientu nie powinien łatwo podążać za krzywą strat do minimów globalnych?
Przykładowy kod:
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()
Podobne rzeczy się zdarzają, jeśli dodam błąd: funkcja utraty 2D jest płynna i prosta, ale jeśli relu zaczyna się do góry nogami, krąży wokół i blokuje się (czerwone punkty początkowe) i nie podąża za gradientem do minimum (jak to dotyczy niebieskich punktów początkowych):
Podobne rzeczy się zdarzają, jeśli dodam również wagę wyjściową i odchylenie. (Będzie się obracać od lewej do prawej lub od dołu do góry, ale nie jedno i drugie.)
Odpowiedzi:
i używasz do tego optymalizacji pierwszego rzędu. Problem z tym podejściem polega na tym, że ma gradientf
Kiedy zaczniesz od , będziesz musiał przejść na drugą stronę aby zbliżyć się do poprawnej odpowiedzi, czyli . Jest to trudne, ponieważ gdy maszbardzo, bardzo mały, gradient również zniknie znikomo. Co więcej, im bardziej zbliżasz się do 0 od lewej, tym wolniejszy będzie twój postęp!w<0 0 w=1 |w|
Dlatego na twoich wykresach dla inicjalizacji, które są ujemne , wszystkie trajektorie utknęły w pobliżu . To także pokazuje twoja druga animacja.w(0)<0 w(i)=0
Jest to związane ze zjawiskiem reling umierania; w celu omówienia, patrz: Moja sieć ReLU nie uruchamia się
Podejście, które może być bardziej skuteczne, polegałoby na zastosowaniu innej nieliniowości, takiej jak nieszczelny relu, który nie ma tak zwanego problemu „znikającego gradientu”. Nieszczelna funkcja relu to
Ustawienie jest zwykłym relu. Większość ludzi wybiera jako lub . Nie widziałem zastosowanego , chociaż chciałbym zobaczyć badanie tego, jaki wpływ, jeśli w ogóle, ma on na takie sieci. (Zauważ, że dla ogranicza się to do funkcji tożsamości; dla kompozycje wielu takich warstw mogą powodować wybuchanie gradientów, ponieważ gradienty stają się większe w kolejnych warstwach.)c=0 c 0.1 0.3 c<0 c=1, |c|>1
Nieznaczna modyfikacja kodu OP zapewnia, że problem leży w wyborze funkcji aktywacji. Ten kod inicjuje aby był ujemny i używa zwykłego . Strata szybko spada do niewielkiej wartości, a waga prawidłowo przesuwa się do , co jest optymalne.w w=1
LeakyReLU
ReLU
Kolejna warstwa złożoności wynika z faktu, że nie poruszamy się w nieskończoność, lecz w skończonej liczbie „skoków”, które przenoszą nas od jednej iteracji do następnej. Oznacza to, że istnieją pewne okoliczności, w których ujemne początkowe wartości nie utkną; przypadki te powstają dla poszczególnych kombinacji i stopni kroku spadku gradientu wystarczająco dużego, aby „przeskoczyć” ponad zanikający gradient.w w(0)
Bawiłem się trochę z tym kodem i odkryłem, że pozostawienie inicjalizacji na i zmiana optymalizatora z SGD na Adama, Adama + AMSGrad lub pęd SGD + nic nie pomaga. Co więcej, zmiana z SGD na Adama faktycznie spowalnia postęp, a także nie pomaga przezwyciężyć zanikającego gradientu tego problemu.w(0)=−10
Z drugiej strony, jeśli zmienisz inicjalizację na i zmienisz optymalizator na Adama (rozmiar kroku 0,01), wtedy możesz faktycznie pokonać znikający gradient. Działa również, jeśli używasz i SGD z pędem (wielkość kroku 0,01). Działa nawet, jeśli używasz waniliowego SGD (rozmiar kroku 0,01) i .w(0)=−1 w(0)=−1 w(0)=−1
Odpowiedni kod znajduje się poniżej; użyj
opt_sgd
lubopt_adam
.źródło