web-dev-qa-db-fra.com

La formation d'un modèle tf.keras avec une boucle de formation basique TensorFlow ne fonctionne pas

Remarque: Tout le code d'un exemple autonome pour reproduire mon problème se trouve ci-dessous.

J'ai une instance tf.keras.models.Model Et je dois la former avec une boucle de formation écrite dans l'API TensorFlow de bas niveau.

Le problème: entraîner le même modèle tf.keras exactement une fois avec une boucle de formation TensorFlow basique et standard et une fois avec la méthode model.fit() de Keras produit des résultats très différents. Je voudrais savoir ce que je fais mal dans ma boucle d'entraînement TF de bas niveau.

Le modèle est un modèle de classification d'image simple que je forme sur Caltech256 (lien vers les tfrecords ci-dessous).

Avec la boucle d'entraînement TensorFlow de bas niveau, la perte d'entraînement diminue d'abord comme il se doit, mais après seulement 1000 étapes d'entraînement, les plateaux de perte puis recommencent à augmenter:

enter image description here

La formation du même modèle sur le même ensemble de données à l'aide de la boucle de formation Keras normale, d'autre part, fonctionne comme prévu:

enter image description here

Que manque-t-il dans ma boucle d'entraînement TensorFlow de bas niveau?

Voici le code pour reproduire le problème (téléchargez les TFRecords avec le lien en bas):

import tensorflow as tf
from tqdm import trange
import sys
import glob
import os

sess = tf.Session()
tf.keras.backend.set_session(sess)

num_classes = 257
image_size = (224, 224, 3)

# Build a tf.data.Dataset from TFRecords.

tfrecord_directory = 'path/to/tfrecords/directory'

tfrecord_filennames = glob.glob(os.path.join(tfrecord_directory, '*.tfrecord'))

feature_schema = {'image': tf.FixedLenFeature([], tf.string),
                  'filename': tf.FixedLenFeature([], tf.string),
                  'label': tf.FixedLenFeature([], tf.int64)}

dataset = tf.data.Dataset.from_tensor_slices(tfrecord_filennames)
dataset = dataset.shuffle(len(tfrecord_filennames)) # Shuffle the TFRecord file names.
dataset = dataset.flat_map(lambda filename: tf.data.TFRecordDataset(filename))
dataset = dataset.map(lambda single_example_proto: tf.parse_single_example(single_example_proto, feature_schema)) # Deserialize tf.Example objects.
dataset = dataset.map(lambda sample: (sample['image'], sample['label']))
dataset = dataset.map(lambda image, label: (tf.image.decode_jpeg(image, channels=3), label)) # Decode JPEG images.
dataset = dataset.map(lambda image, label: (tf.image.resize_image_with_pad(image, target_height=image_size[0], target_width=image_size[1]), label))
dataset = dataset.map(lambda image, label: (tf.image.per_image_standardization(image), label))
dataset = dataset.map(lambda image, label: (image, tf.one_hot(indices=label, depth=num_classes))) # Convert labels to one-hot format.
dataset = dataset.shuffle(buffer_size=10000)
dataset = dataset.repeat()
dataset = dataset.batch(32)

iterator = dataset.make_one_shot_iterator()
features, labels = iterator.get_next()

# Build a simple model.

input_tensor = tf.keras.layers.Input(shape=image_size)
x = tf.keras.layers.Conv2D(64, (3,3), strides=(2,2), activation='relu', kernel_initializer='he_normal')(input_tensor)
x = tf.keras.layers.Conv2D(64, (3,3), strides=(2,2), activation='relu', kernel_initializer='he_normal')(x)
x = tf.keras.layers.Conv2D(128, (3,3), strides=(2,2), activation='relu', kernel_initializer='he_normal')(x)
x = tf.keras.layers.Conv2D(256, (3,3), strides=(2,2), activation='relu', kernel_initializer='he_normal')(x)
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dense(num_classes, activation=None, kernel_initializer='he_normal')(x)
model = tf.keras.models.Model(input_tensor, x)

Voici la boucle de formation TensorFlow simple:

# Build the training-relevant part of the graph.

model_output = model(features)

loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=tf.stop_gradient(labels), logits=model_output))

train_op = tf.train.AdamOptimizer().minimize(loss)

# The next block is for the metrics.
with tf.variable_scope('metrics') as scope:
    predictions_argmax = tf.argmax(model_output, axis=-1, output_type=tf.int64)
    labels_argmax = tf.argmax(labels, axis=-1, output_type=tf.int64)
    mean_loss_value, mean_loss_update_op = tf.metrics.mean(loss)
    acc_value, acc_update_op = tf.metrics.accuracy(labels=labels_argmax, predictions=predictions_argmax)
    local_metric_vars = tf.contrib.framework.get_variables(scope=scope, collection=tf.GraphKeys.LOCAL_VARIABLES)
    metrics_reset_op = tf.variables_initializer(var_list=local_metric_vars)

# Run the training

epochs = 3
steps_per_Epoch = 1000

fetch_list = [mean_loss_value,
              acc_value,
              train_op,
              mean_loss_update_op,
              acc_update_op]

sess.run(tf.global_variables_initializer())
sess.run(tf.local_variables_initializer())

with sess.as_default():

    for Epoch in range(1, epochs+1):

        tr = trange(steps_per_Epoch, file=sys.stdout)
        tr.set_description('Epoch {}/{}'.format(Epoch, epochs))

        sess.run(metrics_reset_op)

        for train_step in tr:

            ret = sess.run(fetch_list, feed_dict={tf.keras.backend.learning_phase(): 1})

            tr.set_postfix(ordered_dict={'loss': ret[0],
                                         'accuracy': ret[1]})

Voici la boucle de formation Keras standard, qui fonctionne comme prévu. Notez que l'activation de la couche dense dans le modèle ci-dessus doit être modifiée de None à 'softmax' pour que la boucle Keras fonctionne.

epochs = 3
steps_per_Epoch = 1000

model.compile(optimizer='adam',
              loss='categorical_crossentropy',
              metrics=['accuracy'])

history = model.fit(dataset,
                    epochs=epochs,
                    steps_per_Epoch=steps_per_Epoch)

Vous pouvez télécharger les TFRecords pour le jeu de données Caltech256 ici (environ 850 Mo).

MISE À JOUR:

J'ai réussi à résoudre le problème: remplacement de la fonction de perte TF de bas niveau

loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=tf.stop_gradient(labels), logits=model_output))

par son équivalent Keras

loss = tf.reduce_mean(tf.keras.backend.categorical_crossentropy(target=labels, output=model_output, from_logits=True))

fait l'affaire. Maintenant, la boucle d'entraînement de bas niveau TensorFlow se comporte exactement comme model.fit().

Cela soulève une nouvelle question:

Qu'est-ce que tf.keras.backend.categorical_crossentropy() fait que tf.nn.softmax_cross_entropy_with_logits_v2() cela n'amène-t-il pas ce dernier à faire bien pire? (Je sais que ce dernier a besoin de logits, pas de sortie softmax, donc ce n'est pas le problème)

18
Alex

Remplacement de la fonction de perte TF de bas niveau

loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=tf.stop_gradient(labels), logits=model_output))

par son équivalent Keras

loss = tf.reduce_mean(tf.keras.backend.categorical_crossentropy(target=labels, output=model_output, from_logits=True))

fait l'affaire. Maintenant, la boucle de formation de bas niveau TensorFlow se comporte exactement comme model.fit().

Cependant, je ne sais pas pourquoi. Si quelqu'un sait pourquoi tf.keras.backend.categorical_crossentropy() se comporte bien alors que tf.nn.softmax_cross_entropy_with_logits_v2() ne fonctionne pas du tout, veuillez poster une réponse.

Une autre note importante:

Afin de former un modèle tf.keras Avec une boucle d'apprentissage TF de bas niveau et un objet tf.data.Dataset, Il ne faut généralement pas appeler le modèle sur la sortie de l'itérateur. Autrement dit, il ne faut pas faire cela:

model_output = model(features)

Au lieu de cela, il faut créer un modèle dans lequel la couche d'entrée est définie pour s'appuyer sur la sortie de l'itérateur au lieu de créer un espace réservé, comme ceci:

input_tensor = tf.keras.layers.Input(tensor=features)

Cela n'a pas d'importance dans cet exemple, mais cela devient pertinent si des couches du modèle ont des mises à jour internes qui doivent être exécutées pendant la formation (par exemple BatchNormalization).

6
Alex

Vous appliquez une activation softmax sur votre dernière couche

x = tf.keras.layers.Dense(num_classes, activation='softmax', kernel_initializer='he_normal')(x)

et vous appliquez encore un softmax lorsque vous utilisez tf.nn.softmax_cross_entropy_with_logits_v2 car il attend des logits non mis à l'échelle. De la documentation:

AVERTISSEMENT: cette opération attend des logits non mis à l'échelle, car elle effectue un softmax sur les logits en interne pour plus d'efficacité. N'appelez pas cette opération avec la sortie de softmax, car cela produira des résultats incorrects.

Ainsi, supprimez l'activation softmax de votre dernière couche et cela devrait fonctionner.

x = tf.keras.layers.Dense(num_classes, activation=None, kernel_initializer='he_normal')(x)
[...]
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(labels=tf.stop_gradient(labels), logits=model_output))
0
BiBi