Keras 模型训练良好,但预测值相同

A Keras model trains well, but predicts the same value

让我们尝试 MobileNet V. 2 在嘈杂的图像上定位亮带。是的,使用深度卷积网络来实现这样的策略有点矫枉过正,但最初它的目的就像冒烟测试一样,以确保模型有效。我们将在合成数据上对其进行训练:

import numpy as np
import tensorflow as tf
from matplotlib import pyplot as plt

SHAPE = (32, 320, 1)
def gen_sample():
    while True:
        data = np.random.normal(0, 1, SHAPE)
        i = np.random.randint(0, SHAPE[1]-8)
        data[:,i:i+8,:] += 4
        yield data.astype(np.float32), np.float32(i)

ds = tf.data.Dataset.from_generator(gen_sample, output_signature=(
    tf.TensorSpec(shape=SHAPE, dtype=tf.float32),
    tf.TensorSpec(shape=(), dtype=tf.float32))).batch(100)

d, i = next(gen_sample())
plt.figure()
plt.imshow(d)
plt.show()

现在我们构建并训练模型:

model = tf.keras.models.Sequential([
    tf.keras.applications.MobileNetV2(
        input_shape=SHAPE, include_top=False, weights=None, alpha=0.5),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(1)
])

model.compile(
    optimizer=tf.keras.optimizers.Adam(
        learning_rate=tf.keras.optimizers.schedules.ExponentialDecay(
            initial_learning_rate=0.01, decay_steps=1000, decay_rate=0.9)),
    loss='mean_squared_error')
history = model.fit(ds, steps_per_epoch=10, epochs=40)

我们使用生成的数据,所以不需要验证集,对吗?所以我们可以看看损失是如何减少的。而且它确实减少得很好:

Epoch 1/40
10/10 [==============================] - 27s 2s/step - loss: 15054.8417
Epoch 2/40
10/10 [==============================] - 23s 2s/step - loss: 193.9126
Epoch 3/40
10/10 [==============================] - 24s 2s/step - loss: 76.9586
Epoch 4/40
10/10 [==============================] - 25s 2s/step - loss: 68.8521
...
Epoch 37/40
10/10 [==============================] - 20s 2s/step - loss: 4.5258
Epoch 38/40
10/10 [==============================] - 20s 2s/step - loss: 22.1212
Epoch 39/40
10/10 [==============================] - 20s 2s/step - loss: 28.4854
Epoch 40/40
10/10 [==============================] - 20s 2s/step - loss: 18.0123

训练刚好没有停在最好的结果上,但应该还是合理的:答案应该在真值±8左右。让我们测试一下:

d, i = list(ds.take(1))[0]
model.evaluate(d, i)
np.stack((model.predict(d).ravel(), i.numpy()), 1)[:10,]
4/4 [==============================] - 0s 32ms/step - loss: 16955.7871
array([[ 66.84666 , 222.      ],
       [ 66.846664,  46.      ],
       [ 66.846664,  71.      ],
       [ 66.84668 , 268.      ],
       [ 66.846664,  86.      ],
       [ 66.84668 , 121.      ],
       [ 66.846664, 301.      ],
       [ 66.84667 , 106.      ],
       [ 66.84665 , 138.      ],
       [ 66.84667 ,  95.      ]], dtype=float32)

哇!这个巨大的评估损失从何而来?为什么模型一直预测相同的愚蠢值?训练的时候一切都那么好!

实际上,在一天左右的时间里我意识到发生了什么,但我向其他人提供了解决这个谜题并获得一些积分的可能性。

问题是在训练模式下正常运行的网络无法在推理模式下运行。可能是什么原因?基本上有两种层类型在两种模式下的工作方式不同:dropout 和 batch normalization。在MobileNet V. 2中,我们只有批量归一化,所以让我们考虑一下它是如何工作的。

在训练模式下,BN 层计算批量均值和方差,并使用这些批量值对数据进行归一化。同时,它会将均值和方差记为移动平均值,并使用名为 momentum.

的系数进行加权
moving_mean = moving_mean * momentum + mean(batch) * (1 - momentum)
moving_var = moving_var * momentum + var(batch) * (1 - momentum)

的确,这个 momentum 是一个重要的超参数,尤其是当真正的批次统计数据与初始值相去甚远时。假设初始方差值为1.0,动量为0.99(默认),真实数据方差为0.1。比 10% 的误差 (var < 0.11) 可以在 447 个批次后实现。

现在问题的根本原因是:在MobileNet中所有无数的BN层都有momentum=0.999,这意味着需要4497个批处理步骤才能达到同样的10%误差!当您在像 ImageNet 这样的非常大的异构数据集上进行小批量训练时,这是一个 100% 合理的超参数选择。但在这个玩具示例中,结果是 BN 层在 400 个批次中无法记住真实的数据统计信息,并且在推理过程中使用了完全错误的值!

而且修复非常简单:只需更改 model.compile:

之前的 momenta
for layer in model.layers[0].layers:
    if type(layer) is tf.keras.layers.BatchNormalization:
        layer.momentum = 0.9