Keras的.fit比手动Tensorflow表现更好

5
我刚接触Tensorflow和Keras。为了入门,我按照教程https://www.tensorflow.org/tutorials/quickstart/advanced进行学习。现在我正在尝试将其适配到CIFAR10数据集上,而不是MNIST数据集上。我重新创建了这个模型https://keras.io/examples/cifar10_cnn/,并试图在自己的代码库中运行它。
逻辑上讲,如果模型、批次大小和优化器都相同,那么两者应该执行相同,但实际上并非如此。我认为可能是我在准备数据方面犯了错误。因此,我将keras代码中的model.fit函数复制到我的脚本中,并且仍然表现更好。使用.fit给出了大约75%的准确率,在25个周期内,而手动方法需要大约60个周期。使用.fit我也实现了稍微更好的最大准确率。
我想知道的是:.fit是否在幕后做了一些优化训练的操作?我需要添加什么来使我的代码达到相同的性能呢?我是否明显地做错了什么?
谢谢你花时间看这个问题。
主要代码:

import tensorflow as tf
from tensorflow import keras
import msvcrt
from Plotter import Plotter


#########################Configuration Settings#############################

BatchSize = 32
ModelName = "CifarModel"

############################################################################


(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()

print("x_train",x_train.shape)
print("y_train",y_train.shape)
print("x_test",x_test.shape)
print("y_test",y_test.shape)

x_train, x_test = x_train / 255.0, x_test / 255.0

# Convert class vectors to binary class matrices.
y_train = keras.utils.to_categorical(y_train, 10)
y_test = keras.utils.to_categorical(y_test, 10)



train_ds = tf.data.Dataset.from_tensor_slices(
    (x_train, y_train)).batch(BatchSize)

test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(BatchSize)


loss_object = tf.keras.losses.CategoricalCrossentropy(from_logits=True)
optimizer = tf.keras.optimizers.RMSprop(learning_rate=0.0001,decay=1e-6)

# Create an instance of the model
model = ModelManager.loadModel(ModelName,10)


train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.CategoricalAccuracy(name='train_accuracy')

test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.CategoricalAccuracy(name='test_accuracy')



########### Using this function I achieve better results ##################

model.compile(loss='categorical_crossentropy',
              optimizer=optimizer,
              metrics=['accuracy'])
model.fit(x_train, y_train,
              batch_size=BatchSize,
              epochs=100,
              validation_data=(x_test, y_test),
              shuffle=True,
              verbose=2)

############################################################################

########### Using the below code I achieve worse results ##################

@tf.function
def train_step(images, labels):
  with tf.GradientTape() as tape:
    predictions = model(images, training=True)
    loss = loss_object(labels, predictions)
  gradients = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))

  train_loss(loss)
  train_accuracy(labels, predictions)

@tf.function
def test_step(images, labels):
  predictions = model(images, training=False)
  t_loss = loss_object(labels, predictions)

  test_loss(t_loss)
  test_accuracy(labels, predictions)

epoch = 0
InterruptLoop = False
while InterruptLoop == False:
  #Shuffle training data
  train_ds.shuffle(1000)
  epoch = epoch + 1
  # Reset the metrics at the start of the next epoch
  train_loss.reset_states()
  train_accuracy.reset_states()
  test_loss.reset_states()
  test_accuracy.reset_states()

  for images, labels in train_ds:
    train_step(images, labels)

  for test_images, test_labels in test_ds:
    test_step(test_images, test_labels)

  test_accuracy = test_accuracy.result() * 100
  train_accuracy = train_accuracy.result() * 100

  #Print update to console
  template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
  print(template.format(epoch,
                        train_loss.result(),
                        train_accuracy ,
                        test_loss.result(),
                        test_accuracy))

  # Check if keyboard pressed
  while msvcrt.kbhit():
    char = str(msvcrt.getch())
    if char == "b'q'":
      InterruptLoop = True
      print("Stopping loop")

这个模型:

from tensorflow.keras.layers import Dense, Flatten, Conv2D, Dropout, MaxPool2D
from tensorflow.keras import Model

class ModelData(Model):
  def __init__(self,NumberOfOutputs):
    super(ModelData, self).__init__()
    self.conv1 = Conv2D(32, 3, activation='relu', padding='same', input_shape=(32,32,3))
    self.conv2 = Conv2D(32, 3, activation='relu')
    self.maxpooling1 = MaxPool2D(pool_size=(2,2))
    self.dropout1 = Dropout(0.25)
    ############################
    self.conv3 = Conv2D(64,3,activation='relu',padding='same')
    self.conv4 = Conv2D(64,3,activation='relu')
    self.maxpooling2 = MaxPool2D(pool_size=(2,2))
    self.dropout2 = Dropout(0.25)
    ############################
    self.flatten = Flatten()
    self.d1 = Dense(512, activation='relu')
    self.dropout3 = Dropout(0.5)
    self.d2 = Dense(NumberOfOutputs,activation='softmax')

  def call(self, x):
    x = self.conv1(x)
    x = self.conv2(x)
    x = self.maxpooling1(x)
    x = self.dropout1(x)
    x = self.conv3(x)
    x = self.conv4(x)
    x = self.maxpooling2(x)
    x = self.dropout2(x)
    x = self.flatten(x)
    x = self.d1(x)
    x = self.dropout3(x)
    x = self.d2(x)
    return x

我没有使用过CategoricalAccuracy,但我非常确定它与Accuracy不同。如果是这样的话,那么你正在尝试使用两个不同的指标来比较结果。 - Aramakus
我不确定keras的fit方法中是否有可以隐藏的内容,但我认为差异是由于不同的洗牌方式引起的。您可以尝试使用keras方法train_on_batch,确保批次对于keras和tf都相同。 最后一件事:这两个模型的渐近行为怎么样?100或200个时期之后会发生什么?我认为基准应该在大量时期之后进行评估,以便消除任何内部波动。 - Giuseppe Angora
感谢您的评论!根据https://www.tensorflow.org/api_docs/python/tf/keras/Model#compile(在指标下),字符串“accuracy”将转换为最合适的度量标准。我将字符串更改为“CategoricalAccuracy”以确保并获得完全相同的结果。.fit仍然表现更好。 - TheOneTheOnly2
我关闭了两个的洗牌,但没有任何区别。使用train_on_batch方法与'fit'方法给出相同的结果,尽管其他所有内容都与手动方法相同。渐近地:它们在大约相同的值(78-80%)上停滞,这对于此模型来说是可以预期的,尽管fit方法在+-60个时期内达到该值,而手动方法仅在大约130个时期后才能达到该值。它们最终将停滞在几乎相同的值,但使用fit方法始终可以在更少的时期内使其更接近。使用手动方法和太多参数的Adam可能会导致不稳定,但使用相同设置的fit从未变得不稳定。 - TheOneTheOnly2
1
你是否碰巧弄清楚了model.fit()和手动方法之间存在差异的原因?我在处理我的数据集时遇到了类似的问题,但我不知道原因。我还没有尝试关闭shuffling,但你说它没有起作用,所以我怀疑其他原因在起作用。 - Malek
很抱歉让你失望,但我从未解决过这个谜团。你可以尝试关闭洗牌功能,看看是否能解决你的问题,但对我来说并没有什么帮助。我现在使用PyTorch,因为它提供了更多的控制。 - TheOneTheOnly2
2个回答

1
我知道这个问题已经有答案了,但我遇到了同样的问题,而解决方法似乎与文档中所述的不同。
我在此复制并粘贴我在GitHub上找到的答案(以及相关链接),它解决了我的问题:
问题是由于您在自定义循环中的损失函数中进行广播引起的。确保预测和标签的尺寸相等。目前(对于MAE),它们是[128,1]和[128]。只需使用tf.squeeze或tf.expand_dims即可。
链接:https://github.com/tensorflow/tensorflow/issues/28394 基本翻译:在计算损失时,请始终确保张量的形状。

1
感谢您的回答和提供的链接!我接受这个答案,因为在评论中我提到洗牌不会改变结果。 - TheOneTheOnly2

0

即使在评论中已经提供了解决方案,为了造福社区,在此将其放在答案部分。

在相同的数据集上,当使用Keras Model.fit和使用Tensorflow构建的Model时,如果数据被洗牌,则准确性可能会有所不同,因为当我们洗牌数据时,训练和测试(或验证)数据之间的拆分将是不同的,在这两种情况下(Keras和Tensorflow)都会导致不同的训练和测试数据。

如果我们想在KerasTensorflow中使用相同的数据集和类似的架构观察到类似的结果,我们可以关闭数据洗牌

希望这能帮助到您。祝学习愉快!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接