TensorFlow2-tf.keras: потери и веса моделей внезапно становятся «нан» при обучении MTCNN PNet - PullRequest
0 голосов
/ 09 апреля 2020

Я пытался использовать tfrecords для обучения PNet MTCNN. Сначала потери плавно снижались в течение первых нескольких эпох, а затем стали «нан», и вес модели также уменьшился.

Ниже приведены моя модель структуры и результаты обучения:

def pnet_train1(train_with_landmark = False):

    X = Input(shape = (12, 12, 3), name = 'Pnet_input')

    M = Conv2D(10, 3, strides = 1, padding = 'valid', kernel_initializer = glorot_normal, kernel_regularizer = l2(0.00001), name = 'Pnet_conv1')(X)
    M = PReLU(shared_axes = [1, 2], name = 'Pnet_prelu1')(M)
    M = MaxPooling2D(pool_size = 2, name = 'Pnet_maxpool1')(M) # default 'pool_size' is 2!!! 

    M = Conv2D(16, 3, strides = 1, padding = 'valid', kernel_initializer = glorot_normal, kernel_regularizer = l2(0.00001), name = 'Pnet_conv2')(M)
    M = PReLU(shared_axes= [1, 2], name = 'Pnet_prelu2')(M)

    M = Conv2D(32, 3, strides = 1, padding = 'valid', kernel_initializer = glorot_normal, kernel_regularizer = l2(0.00001), name = 'Pnet_conv3')(M)
    M = PReLU(shared_axes= [1, 2], name = 'Pnet_prelu3')(M)

    Classifier_conv = Conv2D(1, 1, activation = 'sigmoid', name = 'Pnet_classifier_conv', kernel_initializer = glorot_normal)(M)
    Bbox_regressor_conv = Conv2D(4, 1, name = 'Pnet_bbox_regressor_conv', kernel_initializer = glorot_normal)(M)
    Landmark_regressor_conv = Conv2D(12, 1, name = 'Pnet_landmark_regressor_conv', kernel_initializer = glorot_normal)(M)

    Classifier = Reshape((1, ), name = 'Pnet_classifier')(Classifier_conv)
    Bbox_regressor = Reshape((4, ), name = 'Pnet_bbox_regressor')(Bbox_regressor_conv) 
    if train_with_landmark: 
        Landmark_regressor = Reshape((12, ), name = 'Pnet_landmark_regressor')(Landmark_regressor_conv)
        Pnet_output = Concatenate()([Classifier, Bbox_regressor, Landmark_regressor]) 
        model = Model(X, Pnet_output) 
    else:
        Pnet_output = Concatenate()([Classifier, Bbox_regressor])
        model = Model(X, Pnet_output)

    return model

model = pnet_train1(True)
model.compile(optimizer = Adam(lr = 0.001), loss = custom_loss)
model.fit(ds, steps_per_epoch = 1636, epochs = 100, validation_data = ds, validation_steps = 1636)

pnet записи тренировок
Я знаю, что могут быть некоторые причины, поэтому я попробовал следующее уже проверяет:

  1. Проверьте набор данных, чтобы увидеть, есть ли неверные данные:
    Мой набор данных:
    X: изображения формы (12, 12, 3);
    Y: метка, координаты регрессии бокса и координаты регрессии с 6 ориентирами, объединенные вместе формы (17,).
    Для метки это может быть 1, -1, 0, -2, где будут участвовать только метки 1 и 0 при расчете пользовательской потери я написал сам.
    Для координат roi и landmark все они принадлежат [-1, 1].
    Для данных изображения это будет обрабатываться как: (x - 127,5) / 128. перед отправкой в ​​тренировочный поток.
    Чтобы убедиться, что именно данные приводят к потере 'nan', я извлекаю одну серию (скажем, как 1792 выборки) из набора данных в виде numpy массивов ((1792, 12, 12, 3), (1792, 17)). Простое обучение этой партии данных все еще поднимает проблему. В эпоху, прямо перед тем, как потери становятся 'nan', потеря кажется нормальной, и все веса модели принадлежат (-1, 1), которые являются очень маленькими значениями:
model.fit(x, y, batch_size = 896, epochs = 10)
Train on 1792 samples
Epoch 1/10
1792/1792 [==============================] - 0s 74us/sample - loss: 0.1579
Epoch 2/10
1792/1792 [==============================] - 0s 66us/sample - loss: 0.1574
Epoch 3/10
1792/1792 [==============================] - 0s 66us/sample - loss: 0.1567
Epoch 4/10
1792/1792 [==============================] - 0s 65us/sample - loss: 0.1550
Epoch 5/10
1792/1792 [==============================] - 0s 61us/sample - loss: 0.1556
Epoch 6/10
1792/1792 [==============================] - 0s 70us/sample - loss: 0.1527
Epoch 7/10
1792/1792 [==============================] - 0s 71us/sample - loss: 0.1532
Epoch 8/10
1792/1792 [==============================] - 0s 67us/sample - loss: 0.1509
Epoch 9/10
1792/1792 [==============================] - 0s 66us/sample - loss: 0.1501
Epoch 10/10
1792/1792 [==============================] - 0s 67us/sample - loss: 0.1495
Out[111]: <tensorflow.python.keras.callbacks.History at 0x1f767efa088>

temp_weights_list = []
for layer in model.layers:

    temp_layer = model.get_layer(layer.name)
    temp_weights = temp_layer.get_weights()
    temp_weights_list.append(temp_weights)

model.fit(x, y, batch_size = 896, epochs = 10)
Train on 1792 samples
Epoch 1/10
1792/1792 [==============================] - 0s 70us/sample - loss: nan
Epoch 2/10
1792/1792 [==============================] - 0s 61us/sample - loss: nan
Epoch 3/10
1792/1792 [==============================] - 0s 61us/sample - loss: nan
Epoch 4/10
1792/1792 [==============================] - 0s 61us/sample - loss: nan

Тогда Я перезагружаю структуру модели и загружаю обычные веса, тренированные до того, как потеря стала странной, «нань» произошла снова.
Затем я извлекла другую партию образцов из моего набора данных, тот же случай произошел.
Таким образом, сейчас Я думаю, что мой набор данных правильный (я использую img_string = open(img_path, 'rb').read() для генерации TFRecords, будет возникать ошибка, если изображение повреждено или путь к нему недопустим).

Поскольку обычно это проблема взрыва градиента, я попытался:
добавить слой BatchNormalization,
добавить L2-Norm к весам,
использовать инициализацию Xavier
и выбрать меньшую скорость обучения. Вот моя новая структура модели:
def pnet_train2(train_with_landmark = False):

    X = Input(shape = (12, 12, 3), name = 'Pnet_input')

    M = Conv2D(10, 3, strides = 1, padding = 'valid', use_bias = False, kernel_initializer = glorot_normal, kernel_regularizer = l2(0.00001), name = 'Pnet_conv1')(X)
    M = BatchNormalization(axis = -1, name = 'Pnet_bn1')(M)
    M = PReLU(shared_axes = [1, 2], name = 'Pnet_prelu1')(M)
    M = MaxPooling2D(pool_size = 2, name = 'Pnet_maxpool1')(M) # default 'pool_size' is 2!!! 

    M = Conv2D(16, 3, strides = 1, padding = 'valid', use_bias = False, kernel_initializer = glorot_normal, kernel_regularizer = l2(0.00001), name = 'Pnet_conv2')(M)
    M = BatchNormalization(axis = -1, name = 'Pnet_bn2')(M)
    M = PReLU(shared_axes= [1, 2], name = 'Pnet_prelu2')(M)

    M = Conv2D(32, 3, strides = 1, padding = 'valid', use_bias = False, kernel_initializer = glorot_normal, kernel_regularizer = l2(0.00001), name = 'Pnet_conv3')(M)
    M = BatchNormalization(axis = -1, name = 'Pnet_bn3')(M)
    M = PReLU(shared_axes= [1, 2], name = 'Pnet_prelu3')(M)

    Classifier_conv = Conv2D(1, 1, activation = 'sigmoid', name = 'Pnet_classifier_conv', kernel_initializer = glorot_normal)(M)
    Bbox_regressor_conv = Conv2D(4, 1, name = 'Pnet_bbox_regressor_conv', kernel_initializer = glorot_normal)(M)
    Landmark_regressor_conv = Conv2D(12, 1, name = 'Pnet_landmark_regressor_conv', kernel_initializer = glorot_normal)(M)

    Classifier = Reshape((1, ), name = 'Pnet_classifier')(Classifier_conv)
    Bbox_regressor = Reshape((4, ), name = 'Pnet_bbox_regressor')(Bbox_regressor_conv) 
    if train_with_landmark: 
        Landmark_regressor = Reshape((12, ), name = 'Pnet_landmark_regressor')(Landmark_regressor_conv)
        Pnet_output = Concatenate()([Classifier, Bbox_regressor, Landmark_regressor]) 
        model = Model(X, Pnet_output) 
    else:
        Pnet_output = Concatenate()([Classifier, Bbox_regressor])
        model = Model(X, Pnet_output)

    return model

И я изменяю начальную скорость обучения с 0,001 (это значение по умолчанию для Адама в Керасе) до 0,0001. То же самое происходило все время. Просто процесс медленнее, так как скорость обучения меньше ...

Последнее, что пришло мне в голову, это обычная потеря, которую я написал сам. Это специализировано для многозадачной задачи MTCNN. Поскольку выходные данные моей модели имеют форму (17,) / (размер партии, 17), я заменил потерю на «mse» для проверки. Это может не дать очень хороший результат регрессии, но это должно работать по крайней мере. Произошло то же самое - после нескольких эпох для уменьшения потерь, потери и вес модели стали 'nan' ...

Для дальнейшего размышления, float32 делает значение между -2 147 483 648 и 2 147 483 647 всеми. доступный. Учитывая, что все прежние веса равны 10 ^ -2 или около того, я думаю, что некоторый знаменатель может стать ~ 0 в потере, которая вызывает эту проблему. Но для «mse» нет вычисления деления, поэтому я все еще не понимаю истинную причину.

Я действительно не уверен, что это ошибка или какие-то ошибки, которые я сделал. Так что любой совет приветствуется. Заранее спасибо.

Обновлено 2020.04.10 01: 57:
Я попытался найти эпоху, когда потеря идет не так. Последняя эпоха, которая имеет нормальные потери, обновляет вес модели до 'nan'. Таким образом, я думаю, что проблема возникает при обратном распространении (нормальные потери (0,0814) - обратная подпорка - веса модели до «нан» - следующая потеря до «нан»). Я использовал следующие коды для получения весов модели:

model.fit(x, y, batch_size = 1792, epochs = 1)
Train on 1792 samples
1792/1792 [==============================] - 0s 223us/sample - loss: 0.0814
Out[36]: <tensorflow.python.keras.callbacks.History at 0x205ff7b0188>

temp_weights_list1 = []
for layer in model.layers:

    temp_layer = model.get_layer(layer.name)
    temp_weights = temp_layer.get_weights()
    temp_weights_list1.append(temp_weights)
...