Чому жоден ReLU не може вивчити RELU?


15

У процесі моєї нейронної мережі навіть не можна вивчити евклідову відстань, я ще більше спростив і спробував навчити один РеЛУ (з випадковою вагою) до одного РеЛУ. Це найпростіша мережа, яка є, і все ж половину часу вона не зможе конвергуватися.

Якщо початкова здогадка має таку саму орієнтацію, що і ціль, вона швидко вчиться і переходить до правильної ваги 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 є гладкою і простою, але якщо релу починається догори дном, він кружляє навколо і застрягає (червоні вихідні точки), а не дотримується градієнта вниз до мінімуму (як це робить для синіх вихідних точок):

введіть тут опис зображення

Подібні речі трапляються, якщо я додаю також вихідну вагу та зміщення. (Він перевернеться вліво-вправо або вниз-вгору, але не обидва.)


3
@Sycorax Ні, це не дублікат, він запитує про конкретну проблему, а не загальну пораду. Я витратив чималу кількість часу, зводячи це до мінімального, повного та перевіреного прикладу. Будь ласка, не видаляйте її лише тому, що вона розпливчасто нагадує якесь інше надшироке питання. Один із кроків у прийнятій відповіді на це питання - "Спочатку побудуйте невелику мережу з одним прихованим шаром і переконайтеся, що вона працює правильно. Потім поступово додайте додаткову складність моделі та переконайтеся, що кожен із них також працює." Це саме те, що я роблю, і це не працює.
ендоліт

2
Мені дуже подобається ця "серія" в NN, застосована до простих функцій: eats_popcorn_gif:
Cam.Davidson.Pilon

ReLU функціонує як ідеальний випрямляч, наприклад, діод. Він односпрямований. Якщо ви хочете, щоб напрямок було правильним, подумайте про використання softplus, а потім перехід на ReLU, коли навчання позитивне, або використання іншого варіанту, наприклад ELU.
Карл

Якщо сказати це іншим способом, очікується, що ReLU буде марним для , дивіться на навчання для ; вона плоска, вона не вчиться. x < 0x<0x<0
Карл

1
Градієнт має тенденцію до нуля для нижче нуля; це прилавки. x
Карл

Відповіді:


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

Встановлення є звичайною релу. Більшість людей обирають таким, як або . Я не бачив використовується, хоча мені було б цікаво ознайомитись з тим, який ефект, якщо такий є, на такі мережі. (Зверніть увагу, що при це зводиться до функції тотожності; для , склади багатьох таких шарів можуть спричинити вибух градієнтів, оскільки градієнти стають більшими в послідовних шарах.)c=0c0.10.3c<0c=1,|c|>1

Злегка зміна коду ОП демонструє, що проблема полягає у виборі функції активації. Цей код ініціалізує на негатив і використовує замість звичайного . Втрати швидко зменшуються до невеликого значення, а вага правильно переміщується до , що є оптимальним.ww = 1LeakyReLUReLUw=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 )w(0)

Деякі я грав з цим кодом, і я виявив, що залишення ініціалізації у та зміна оптимізатора з SGD на Adam, Adam + AMSGrad або SGD + імпульс нічого не допомагає. Більше того, перехід від SGD до Адама фактично уповільнює прогрес, крім того, що не допомагає подолати зникаючий градієнт цієї проблеми.w(0)=10

З іншого боку, якщо змінити ініціалізацію на і оптимізатор змінити на Адам (розмір кроку 0,01), то ви зможете реально подолати зникаючий градієнт. Він також працює, якщо ви використовуєте і SGD з імпульсом (розмір кроку 0,01). Це навіть працює, якщо ви використовуєте ванільний SGD (розмір кроку 0,01) і .w(0)=1 w ( 0 ) = - 1 w ( 0 ) = - 1w(0)=1w(0)=1

Відповідний код нижче; використання opt_sgdабо 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)

Я бачив таку ж проблему з LeakyReLU, ELU, SELU, коли мав вихідну вагу та упередженість, але я не впевнений, чи спробував я їх без виходу. Я перевірю
endolith

1
(Так, ви праві, що LeakyReLU та ELU прекрасно працюють для цього прикладу)
endolith

2
О, я розумію. Це буде робити градієнтний спуск функції втрат, це просто , що функція втрат стає плоским (0 градієнта) в 0 при наближенні з негативного боку, так що градієнтні застряє там. Тепер це здається очевидним. : D
ендоліт

2
Саме так. Зверніть увагу на те, як ваші сюжетні збитки проти мають "перегин" біля 0: це тому, що зліва від 0 градієнт втрати зникає до 0 (однак, це неоптимальне рішення, оскільки втрати там вищі, ніж є для ). Більше того, цей сюжет показує, що функція втрат не випукла (ви можете провести лінію, яка перетинає криву втрат у 3 і більше місцях), так що сигналізує про те, що нам слід бути обережними при використанні локальних оптимізаторів, таких як SGD. w = 0ww=0
Sycorax каже, що повернеться до Моніки

2
Під час використання активації relu навіть SGD без імпульсу може перейти через губу, якщо розмір кроку досить великий для будь-якого конкретного значення . w(i)
Sycorax каже, що поверніть Моніку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.