Почему потеря go вверх? - PullRequest
0 голосов
/ 23 февраля 2020

Выполнение приведенного ниже кода иногда приводит к тому, что потери растут во время тренировок, а затем остаются там. Почему это так?

import tensorflow as tf
from tensorflow.keras import layers, losses, models

FEATURE_COUNT = 2
TRAINING_SET_SIZE = 128


def patch_nans(t: tf.Tensor) -> tf.Tensor:
    """:return t with nans replaced by zeros"""
    nan_mask = tf.math.is_nan(t)
    return tf.where(nan_mask, tf.zeros_like(t), t)


def check_numerics(t: tf.Tensor) -> tf.Tensor:
    """Throw an exception if t contains nans."""
    return tf.debugging.check_numerics(t, "t")


def get_model() -> models.Model:
    inp = layers.Input(shape=[FEATURE_COUNT])
    mid = layers.Dense(units=64)(inp)
    mid = layers.ReLU()(mid)
    mid = layers.Dense(units=1)(mid)
    mid = layers.Lambda(patch_nans)(mid)
    out = layers.Lambda(check_numerics)(mid)
    return models.Model(inp, out)


model = get_model()
model.compile(
    optimizer=tf.optimizers.SGD(),
    loss=losses.mean_squared_error
)
model.summary()

features = tf.random.normal(shape=[TRAINING_SET_SIZE, FEATURE_COUNT])
features_with_nans = tf.maximum(tf.math.log(features + 1), tf.zeros_like(features))
labels = tf.random.normal(shape=[TRAINING_SET_SIZE, 1])

# Evaluate the model before training
model.evaluate(features_with_nans, labels, batch_size=8)

# Evaluate the model while training
model.fit(features_with_nans, labels, batch_size=8, epochs=4)

Модель представляет собой простую последовательную модель с двумя слоями, потери - MSE, а тренировочный набор не имеет экстремальных значений (кроме NaN).

Выдержка из прогона, где потеря увеличивается:

  8/128 [>.............................] - ETA: 0s - loss: 0.4720
128/128 [==============================] - 0s 593us/sample - loss: 1.1050
Train on 128 samples
Epoch 1/4

  8/128 [>.............................] - ETA: 3s - loss: 2.3937
128/128 [==============================] - 0s 2ms/sample - loss: 1.1096
Epoch 2/4

  8/128 [>.............................] - ETA: 0s - loss: 1.1668
128/128 [==============================] - 0s 141us/sample - loss: 1.1202
Epoch 3/4

  8/128 [>.............................] - ETA: 0s - loss: 1.0059
128/128 [==============================] - 0s 141us/sample - loss: 1.1202
Epoch 4/4

  8/128 [>.............................] - ETA: 0s - loss: 1.6480
128/128 [==============================] - 0s 156us/sample - loss: 1.1202

1 Ответ

0 голосов
/ 27 февраля 2020

Если у вас есть nan в вашей модели, у вас будет nan в градиентах, это неизбежно.

И если у вас есть nan в градиентах, которые суммируются, у вас будет nan во всех весах модели.

Если у вас есть nan в весах модели, вы ничего не можете сделать с этой моделью.

Проверьте это самостоятельно с помощью print(model.get_weights()) после тренировки.


Потери увеличиваются, потому что модель внезапно начинает выводить только нули (потому что все веса равны nan), и во втором проходе она больше не меняется.


Почему?

Да, я знаю, это звучит странно, поскольку вы заменили nans до вычисления потерь, но некоторое внутреннее поведение в тензорном потоке все равно увидит эти nans - очень вероятно, что это все еще применяя правило цепочки, он не понимает, что когда есть ноль, он должен просто пропустить все предыдущие слои - в конце концов, это компьютер и zero * nan = nan.

Решение?

Если вы действительно хотите использовать nans (хотя это звучит не очень хорошая идея), вы должны удалить их в самом начале.

Вот предложение, в котором вы удаляете nans в начале, затем используете ту же маску nan для обнуления конечных результатов для nans, а также преобразуете метки в ноль там, где есть nans. Таким образом, ваша потеря будет хорошо себя вести:

import tensorflow.keras.backend as K

#uses a given nan mask to zero the outputs at specified places
def removeNan(x):
    t, nan_mask = x
    return tf.where(nan_mask, tf.zeros_like(t), t)


#a changed model that removes the nans at the very beginning
#later this model uses the same nan mask to zero the outputs
def get_model2() -> models.Model:
    inp = layers.Input(shape=[FEATURE_COUNT])

    #remove the nans before anything!!!! Keep the mask for applying to the outputs
    nanMask = layers.Lambda(lambda x: tf.math.is_nan(x))(inp)
    mid = layers.Lambda(removeNan)([inp, nanMask])

    mid = layers.Dense(units=64)(mid)
    mid = layers.ReLU()(mid)
    mid = layers.Dense(units=1)(mid)

    #apply the mask again, just to have consistent results
    out = layers.Lambda(removeNan)([mid, nanMask])
    return models.Model(inp, out)


#your features and labels
features = tf.random.normal(shape=[TRAINING_SET_SIZE, FEATURE_COUNT])
features_with_nans = tf.maximum(tf.math.log(features + 1), tf.zeros_like(features))
labels = tf.random.normal(shape=[TRAINING_SET_SIZE, 1])


#remember to make the labels have zero too, so you get a more trustable loss value:
feature_nans = 0*K.sum(features_with_nans, axis=-1, keepdims=True)
labels_with_nans = labels + feature_nans
labels_with_nans = K.switch(tf.math.is_nan(labels_with_nans), 
                            K.zeros_like(labels_with_nans), 
                            labels_with_nans)

#build new model
model = get_model2()
model.compile(
    optimizer=tf.optimizers.SGD(),
    loss=losses.mean_squared_error
)
model.summary()

#fit and check weights
model.fit(features_with_nans, labels_with_nans, batch_size=10, epochs=5)
print(model.get_weights())

Внимание (необходимо проверить): я где-то читал, что в графическом процессоре или TPU nans будут внутренне заменены нулями, чтобы можно было использовать оборудование ,

Если это правда, вам определенно следует использовать что-то другое вместо nan, например, значение -10000, которое вы используете в качестве маски в предложенном мною методе.

...