在相同的数据上训练相同的模型会产生截然不同的测试精度

Training the same model on the same data yielding extremely different test accuracy

我的模型得到的测试精度非常不一致,但无法弄清楚原因。

我试图对一些 TensorFlow/Keras 的东西进行基准测试,发现我的结果不可靠。不是时间,而是模型的测试精度。在某些运行中,模型会达到测试精度 = 0.65,有时它只会达到 0.35。相同的架构,相同的优化器在相同的数据集上训练,一切都相同。

我尝试删除大多数随机源,例如使用相同的 numpy rng 种子、TensorFlow rng 种子、在运行之间重置 TensorFlow 后端,以及使用相同的种子对数据集进行混洗等。问题仍然存在。

谁能帮我弄清楚我做错了什么?

Here is a reproducible example 在 Google Colab 上, 但是这个问题也会在本地发生在 Python、TensorFlow 和 Cuda/Cudnn.

的不同版本上

here is the dataset我正在使用。它只是 CIFAR-10,训练分区分为训练和验证。

编辑: 问题似乎是我在 .shufflebatch 之间的一些交互引起的,更具体地说是它们的参数 buffer_sizereshuffle_each_iterationdrop_remainder。 以下组合似乎可以避免该问题(但会导致大量内存使用):

dataset.shuffle(buffer_size=dataset.cardinality().numpy(),
                reshuffle_each_iteration=False,
                seed=rng_seed)
        .batch(batch_size=batch_size,
               drop_remainder=True)

看来您遇到了 2 个问题,每个问题都导致了不同的结果 运行:

  1. 使用 GPU
  2. 使用tf.data.AUTOTUNE

1 的解决方案: 将您在 Colab 中的 运行时间切换为“None”

2 的解决方案: 将您的 return 从 tf.data.AUTOTUNE 更改为固定的 BatchSize(例如 128):

train.prefetch(128),
val.prefetch(128),
test.prefetch(128),

你也可以看看我改编的your code here.

为了速度,在接下来的所有实验中,我都将epoch固定为1

最暴力的解决方案之一是在构建模型之前添加以下两行,不需要任何其他更改并支持random:

tf.keras.utils.set_random_seed(some_seed)
tf.config.experimental.enable_op_determinism()

即:

def why_u_inconsistent():
    # tf.keras.backend.clear_session()
    # tf.random.set_seed(42)
    # np.random.seed(42)
    tf.keras.utils.set_random_seed(0)
    tf.config.experimental.enable_op_determinism()
    train, val, test = get_train_val_test()
    model = get_model()
    model.fit(train, validation_data=val, epochs=1, verbose=0)
    scores = model.evaluate(test, verbose=0)
    for name, value in zip(model.metrics_names, scores):
        print(f"test {name}: {value}")

输出是(你会满意的,因为它们完全一样):

run: 0
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 3.872633934020996
test accuracy: 0.1818999946117401
=====
run: 1
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 3.872633934020996
test accuracy: 0.1818999946117401
=====
run: 2
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 3.872633934020996
test accuracy: 0.1818999946117401
=====
run: 3
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 3.872633934020996
test accuracy: 0.1818999946117401
=====
run: 4
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 3.872633934020996
test accuracy: 0.1818999946117401

P.S。在设置随机种子时,我们应该使用tf.keras.utils.set_random_seed,包括randomnumpy.randomtensorflow.random。他们实际上使用了三个不同的随机随机实例,即他们的随机种子不会相互影响,但确实影响了我们的代码,因为我们不可避免地使用它们。当然也可以单独设置,tf.keras.utils.set_random_seed只是一种方便的方式。不幸的是,您的原始代码没有设置基本模块的种子random

然后,我检查了每个 run_nr 的数据管道输出是否必须相同。我将每个批次的reduce_mean写入csv文件来比较每个run_nr的输出,发现它们根本没有区别。所以在这里,我们可以将问题绘制为 determinism problem and I have explained

关于你的问题,我找到了更好的解释,即2个关键点momentumlearning_rate使你的算法遇到determinism问题。如果我们不使用tf.config.experimental.enable_op_determinism,任何算法在最后几位精度都是不确定的(可能是cudaTensorFloat-32 dtype造成的),但是momentum累积不确定性误差和 learning_rate 放大误差。如果我们使用 SGD(不是说你必须使用它)没有 momentum 的优化器,像这样:

...
    optimizer = tf.keras.optimizers.SGD(0.001)
    model.compile(
        optimizer=optimizer,
        loss="categorical_crossentropy",
        metrics=["accuracy"],
    )
...
def why_u_inconsistent(index):
    tf.keras.utils.set_random_seed(0)
    # tf.config.experimental.enable_op_determinism()
    train, val, test = get_train_val_test()
    
    model = get_model()
    model.fit(train, validation_data=val, epochs=1, verbose=0)
    scores = model.evaluate(test, verbose=0)
    for name, value in zip(model.metrics_names, scores):
        print(f"test {name}: {value}")
...

1 纪元之后的输出将如下所示,其中输出非常接近:

run: 0
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 2.20281720161438
test accuracy: 0.1808999925851822
=====
run: 1
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 2.202397346496582
test accuracy: 0.1809999942779541
=====
run: 2
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 2.2010912895202637
test accuracy: 0.18019999563694
=====
run: 3
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 2.200165271759033
test accuracy: 0.18230000138282776
=====
run: 4
Found 42500 files belonging to 10 classes.
Found 7500 files belonging to 10 classes.
Found 10000 files belonging to 10 classes.
test loss: 2.2025880813598633
test accuracy: 0.18019999563694
=====

所以让我们得出一个更好的结论:

  1. 使用tf.keras.utils.set_random_seed(some_seed)控制所有需要的随机行为

  2. 尽可能优化算法,减轻计算精度的影响。即让算法不产生或积累精度上的小误差。可调节的键有很多,不仅是optimizeroptimizer的参数,还有其他东西,如bacth_sizegradient_clipgradient_norm等。

  3. 如果你想获得完全一致的可重现结果并接受其副作用(减速),请使用tf.config.experimental.enable_op_determinism