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

时间:2021-05-12 10:43:22

标签: python tensorflow keras mobilenet

让我们尝试让 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()

A sample image

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

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)

哇!这巨大的评价损失从何而来?为什么该模型不断预测相同的愚蠢值?培训期间一切都很好!

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

1 个答案:

答案 0 :(得分:1)

问题是在训练模式下合理运行的网络无法在推理模式下运行。可能是什么原因?在这两种模式下,基本上有两种层类型的工作方式不同:dropout 和批量归一化。在 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。比 447 个批次后可以达到 10% 的误差 (var < 0.11)。

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

修复方法非常简单:只需更改 model.compile 之前的动量:

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