LSTM 'recurrent_dropout' с 'relu' дает NaN

Любой ненулевой recurrent_dropout дает NaN потерь и веса; последние равны либо 0, либо NaN. Бывает для сложенных, неглубоких, stateful, return_sequences = любое, с & без Bidirectional(), activation='relu', loss='binary_crossentropy'. NaN встречаются в нескольких пакетах.

Какие-нибудь исправления? Помощь приветствуется.


УСТРАНЕНИЕ НЕИСПРАВНОСТЕЙ:

  • recurrent_dropout=0.2,0.1,0.01,1e-6
  • kernel_constraint=maxnorm(0.5,axis=0)
  • recurrent_constraint=maxnorm(0.5,axis=0)
  • clipnorm=50 (эмпирически определено), оптимизатор Nadam
  • activation='tanh' - нет NaN, вес стабилен, протестировано до 10 партий
  • lr=2e-6,2e-5 - нет NaN, вес стабилен, протестировано до 10 партий
  • lr=5e-5 - нет NaN, вес стабилен, для 3 партий - NaN в партии 4
  • batch_shape=(32,48,16) - большие потери для 2 партий, NaN на 3 партии

ПРИМЕЧАНИЕ: batch_shape=(32,672,16), 17 вызовов train_on_batch на пакет


СРЕДА:

  • Keras 2.2.4 (бэкэнд TensorFlow), Python 3.7, Spyder 3.3.7 через Anaconda
  • GTX 1070 6 ГБ, i7-7700HQ, 12 ГБ ОЗУ, Win-10.0.17134 x64
  • CuDNN 10+, новейшие диски Nvidia

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ:

Расхождение моделей является спонтанным и происходит при разных обновлениях поезда даже с фиксированными начальными числами - случайными начальными числами Numpy, Random и TensorFlow. Кроме того, при первом расхождении веса слоя LSTM все нормальны - только позже переходят к NaN.

Ниже по порядку: (1) входы в LSTM; (2) LSTM выходов; (3) Dense(1,'sigmoid') выходов - три последовательных, по Dropout(0.5) между каждым. Предыдущий (1) - это Conv1D слой. Справа: веса LSTM. "BEFORE" = 1 поезд до обновления; "AFTER = 1 поезд обновлен после

ДО расхождения: image

AT расхождения: image

## LSTM outputs, flattened, stats
(mean,std)        = (inf,nan)
(min,max)         = (0.00e+00,inf)
(abs_min,abs_max) = (0.00e+00,inf)

ПОСЛЕ расхождения: image

## Recurrent Gates Weights:
array([[nan, nan, nan, ..., nan, nan, nan],
       [ 0.,  0., -0., ..., -0.,  0.,  0.],
       [ 0., -0., -0., ..., -0.,  0.,  0.],
       ...,
       [nan, nan, nan, ..., nan, nan, nan],
       [ 0.,  0., -0., ..., -0.,  0., -0.],
       [ 0.,  0., -0., ..., -0.,  0.,  0.]], dtype=float32)
## Dense Sigmoid Outputs:
array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
        1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]], dtype=float32)


МИНИМАЛЬНЫЙ ВОСПРОИЗВОДИМЫЙ ПРИМЕР:

from keras.layers import Input,Dense,LSTM,Dropout
from keras.models import Model
from keras.optimizers  import Nadam 
from keras.constraints import MaxNorm as maxnorm
import numpy as np
ipt = Input(batch_shape=(32,672,16))
x = LSTM(512, activation='relu', return_sequences=False,
              recurrent_dropout=0.3,
              kernel_constraint   =maxnorm(0.5, axis=0),
              recurrent_constraint=maxnorm(0.5, axis=0))(ipt)
out = Dense(1, activation='sigmoid')(x)

model = Model(ipt,out)
optimizer = Nadam(lr=4e-4, clipnorm=1)
model.compile(optimizer=optimizer,loss='binary_crossentropy')
for train_update,_ in enumerate(range(100)):
    x = np.random.randn(32,672,16)
    y = np.array([1]*5 + [0]*27)
    np.random.shuffle(y)
    loss = model.train_on_batch(x,y)
    print(train_update+1,loss,np.sum(y))

Наблюдения: следующее ускоряет расхождение:

  • Высшее units (LSTM)
  • Более # слоев (LSTM)
  • Выше lr ‹< нет расхождения при <=1e-4, протестировано до 400 поездов
  • Меньше '1' меток ‹< нет расхождений с y ниже, даже с lr=1e-3; протестировано до 400 поездов

y = np.random.randint(0,2,32) # makes more '1' labels


ОБНОВЛЕНИЕ: не исправлено в TF2; воспроизводимый также с использованием from tensorflow.keras импорта.


person OverLordGoldDragon    schedule 15.08.2019    source источник
comment
У вас действительно 1350 временных шагов? Это сделает обучение очень нестабильным.   -  person Dr. Snoopy    schedule 16.08.2019
comment
@MatiasValdenegro Нет проблем со стабильностью с 10000+ временных шагов, пока recurrent_dropout=0 (хотя модель изо всех сил пытается учиться, веса не плохо себя ведут)   -  person OverLordGoldDragon    schedule 16.08.2019
comment
Именно это и означает нестабильное обучение: вы перемещаете одну вещь, и модель ломается, и потери доходят до бесконечности или наночастиц.   -  person Dr. Snoopy    schedule 16.08.2019
comment
@MatiasValdenegro Пробовал с 48 временными шагами, без изменений. Модель сходится для большого количества конфигураций, только recurrent_dropout проблематична   -  person OverLordGoldDragon    schedule 16.08.2019
comment
@OverLordGoldDragon Здесь та же проблема. Вы нашли решение?   -  person MLguy    schedule 18.11.2019
comment
@MLguy Нет, но это в моем списке для расследования - я постараюсь обойтись без него   -  person OverLordGoldDragon    schedule 19.11.2019
comment
Чтобы сделать оговорку в ответ на @MatiasValdenegro, 1350 временных шагов с LSTM - довольно плохая идея - не столько из-за нестабильности, сколько из-за невозможности изучить долгосрочные зависимости. Это можно увидеть явно, визуализируя градиенты   -  person OverLordGoldDragon    schedule 13.12.2019
comment
@MatiasValdenegro Я ем свои слова о нестабильности, но требовалось объяснение. Очевидно, мы оба упустили то, что должно было быть очевидным.   -  person OverLordGoldDragon    schedule 09.01.2020
comment
@MLguy разобрался   -  person OverLordGoldDragon    schedule 09.01.2020


Ответы (1)


Изучив формулы LSTM глубже и покопавшись в исходном коде, все стало кристально ясно.

Вердикт: recurrent_dropout не имеет к этому никакого отношения; вещь зацикливается там, где этого никто не ожидает.


Фактический виновник: аргумент activation, теперь 'relu', применяется к повторяющимся преобразованиям - в отличие от практически каждого учебника, показывающего его как безобидный 'tanh'.

Т.е. activation не только для преобразования скрытого вывода в вывод - исходный код; он работает непосредственно с вычислением обоих повторяющихся состояний, ячейки и скрытого:

c = f * c_tm1 + i * self.activation(x_c + K.dot(h_tm1_c, self.recurrent_kernel_c))
h = o * self.activation(c)

Решение (я):

  • Apply BatchNormalization to LSTM's inputs, especially if previous layer's outputs are unbounded (ReLU, ELU, etc)
    • If previous layer's activations are tightly bounded (e.g. tanh, sigmoid), apply BN before activations (use activation=None, then BN, then Activation layer)
  • Используйте activation='selu'; стабильнее, но еще может расходиться
  • Используйте нижний lr
  • Применить градиентную обрезку
  • Используйте меньше временных шагов

Дополнительные ответы на некоторые оставшиеся вопросы:

  • Почему было recurrent_dropout заподозрено? Плохая настройка тестирования; только сейчас я сосредоточился на форсировании дивергенции без этого. Тем не менее, иногда это ускоряло расхождение - что можно объяснить обнулением вкладов без повторения, которые в противном случае компенсировали бы мультипликативное подкрепление.
  • Почему ненулевое среднее значение ускоряет расхождение? Аддитивная симметрия; Распределения с ненулевым средним асимметричны, с преобладанием одного знака, что способствует большим предварительным активациям и, следовательно, большим ReLU.
  • Почему обучение может быть стабильным для сотен итераций с низким значением lr? Экстремальные активации вызывают большие градиенты из-за большой ошибки; с низким lr это означает, что веса регулируются для предотвращения таких активаций - тогда как высокий lr прыгает слишком быстро и слишком быстро.
  • Почему составные LSTM расходятся быстрее? Помимо подачи ReLU самому себе, LSTM подает следующий LSTM, который затем подпитывает себя фейерверком ReLU'd ReLU.

ОБНОВЛЕНИЕ 22.01.2020: recurrent_dropout на самом деле может быть фактором, способствующим этому, поскольку он использует перевернутый отсев, увеличивая скрытые преобразования во время обучения, смягчая расходящееся поведение во многих временных шагах. Проблема Git по этому вопросу здесь

person OverLordGoldDragon    schedule 09.01.2020