У ваших сюжетах є натяк на втрату як функцію . Ці ділянки мають "перегин" біля : це тому, що зліва від 0 градієнт втрати зникає до 0 (однак, - це неоптимальне рішення, оскільки втрати там вищі, ніж для ). Більше того, цей сюжет показує, що функція втрат не випукла (ви можете провести лінію, яка перетинає криву втрат у 3 і більше місцях), так що сигналізує про те, що нам слід бути обережними при використанні локальних оптимізаторів, таких як SGD. Дійсно, наступний аналіз показує, що коли ініціалізується як негативний, можна перейти до субоптимального рішення.ww=0w = 0 w = 1 ww=0w=1w
Проблема оптимізації -
minw,bf(x)∥f(x)−y∥22=max(0,wx+b)
і ви використовуєте оптимізацію першого порядку для цього. Проблема такого підходу полягає в тому, що має градієнтf
f′(x)={w,0,if x>0if x<0
Коли ви почнете з , вам доведеться перейти на іншу сторону щоб наблизитись до правильної відповіді, яка є . Це важко зробити, бо коли у вас єдуже, дуже малий, градієнт також стане малим. Більше того, чим ближче ви станете до 0 зліва, тим повільніше буде ваш прогрес!w<00w=1|w|
Ось чому у ваших графіках для ініціалізації, які є від'ємними , всі ваші траєкторії затримуються біля . Це також те, що показує ваша друга анімація.w(0)<0w(i)=0
Це пов’язано з явищем вмираючої релу; для деякого обговорення див. статтю My ReLU не вдалося запустити
Підхід, який міг би бути більш успішним, полягав би у використанні іншої нелінійності, такої як протікання релу, яка не має так званого "зникаючого градієнта". Функція протікання relu є
g(x)={x,cx,if x>0otherwise
де є постійною, так щоневеликий і позитивний. Причиною цього є похідна не 0 "зліва".c|c|
g′(x)={1,c,if x>0if x<0
Встановлення є звичайною релу. Більшість людей обирають таким, як або . Я не бачив використовується, хоча мені було б цікаво ознайомитись з тим, який ефект, якщо такий є, на такі мережі. (Зверніть увагу, що при це зводиться до функції тотожності; для , склади багатьох таких шарів можуть спричинити вибух градієнтів, оскільки градієнти стають більшими в послідовних шарах.)c=0c0.10.3c<0c=1,|c|>1
Злегка зміна коду ОП демонструє, що проблема полягає у виборі функції активації. Цей код ініціалізує на негатив і використовує замість звичайного . Втрати швидко зменшуються до невеликого значення, а вага правильно переміщується до , що є оптимальним.ww = 1LeakyReLU
ReLU
w=1
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)