Если у вас есть 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
, которое вы используете в качестве маски в предложенном мною методе.