簡體   English   中英

使用基本的低級TensorFlow訓練循環訓練tf.keras模型不起作用

[英]Training a tf.keras model with a basic low-level TensorFlow training loop doesn't work

注意:可以在下面找到重現我的問題的自包含示例的所有代碼。

我有一個tf.keras.models.Model實例,需要使用低級TensorFlow API編寫的訓練循環來訓練它。

問題:使用基本的標准低級TensorFlow訓練循環訓練完全相同的tf.keras模型,並使用model.fit()自己的model.fit()方法訓練一次產生非常不同的結果。 我想在我的低級TF訓練循環中找出我做錯了什么。

該模型是一個簡單的圖像分類模型,我在Caltech256上訓練(鏈接到下面的tfrecords)。

使用低級別的TensorFlow訓練循環,訓練損失首先會減少,但是經過1000次訓練后,失去平穩狀態然后再次開始增加:

在此輸入圖像描述

另一方面,使用普通的Keras訓練循環在同一數據集上訓練相同的模型,按預期工作:

在此輸入圖像描述

在我的低級TensorFlow訓練循環中我缺少什么?

以下是重現問題的代碼(下載帶有底部鏈接的TFRecords):

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)

這是簡單的TensorFlow訓練循環:

# 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]})

下面是標准的Keras訓練循環,它按預期工作。 請注意,上面模型中密集層的激活需要從None更改為'softmax',以便Keras循環工作。

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)

您也可以下載TFRecords為Caltech256數據集在這里 (約850 MB)。

更新:

我設法解決了這個問題:更換低級TF丟失功能

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

通過它的Keras等價物

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

訣竅。 現在,低級TensorFlow訓練循環的行為與model.fit()類似。

這提出了一個新問題:

tf.keras.backend.categorical_crossentropy()做什么tf.nn.softmax_cross_entropy_with_logits_v2()不會導致后者執行得更糟? (我知道后者需要logits,而不是softmax輸出,所以這不是問題)

替換低級TF丟失功能

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

通過它的Keras等價物

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

訣竅。 現在,低級TensorFlow訓練循環的行為與model.fit()類似。

但是,我不知道為什么會這樣。 如果有人知道為什么tf.keras.backend.categorical_crossentropy()表現良好而tf.nn.softmax_cross_entropy_with_logits_v2()根本不起作用,請發一個答案。

另一個重要說明:

為了訓練具有低級TF訓練循環和tf.data.Dataset對象的tf.keras模型,通常不應該在迭代器輸出上調用模型。 也就是說,不應該這樣做:

model_output = model(features)

相反,應該創建一個模型,其中輸入層設置為在迭代器輸出上構建而不是創建占位符,如下所示:

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

這在此示例中無關緊要,但如果模型中的任何層具有需要在訓練期間運行的內部更新(例如,BatchNormalization),則它變得相關。

您在最后一層上應用softmax激活

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

並且在使用tf.nn.softmax_cross_entropy_with_logits_v2再次應用softmax,因為它需要未縮放的logits。 從文檔:

警告:此操作需要未縮放的日志,因為它在內部執行logmax以提高效率。 不要使用softmax的輸出調用此op,因為它會產生不正確的結果。

因此,刪除最后一層的softmax激活,它應該工作。

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))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM