Keras 模型如何成功训练,但之后又抱怨层不兼容?

How can a Keras model train successfully, but then complain about incompatible layers afterwards?

我正在使用一个简单的三层模型:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(6,), name="flatten"),
    tf.keras.layers.Dense(128, activation="relu", name="dense1"),
    tf.keras.layers.Dense(1, name="dense2")
])

model.compile(
    optimizer=tf.keras.optimizers.Adam(0.001),
    loss=tf.keras.losses.MeanAbsoluteError()
)

好的,编译成功。我已经为它准备了一些数据,让我们检查一下:

print(features)
print(labels)

这将打印两个列表:

[[1.0, 0.6747252747252748, 0.5652173913043478, 0.6817120622568094, 0.48387096774193544, 0.8536585365853658], [1.0, 0.7692307692307693, 0.717391304347826, 0.7184824902723735, 0.4637096774193548, 0.8536585365853658], (many more features...)]
[18.0, 15.0, (many more labels, same amount as features...)]

太棒了。现在我将训练模型并打印损失历史:

print(
    model.fit(
        features,
        labels,
        verbose=0,
        epochs=100,
        validation_data=(features, labels)
    ).history["val_loss"]
)

这会打印:

[22.92747688293457, 22.328025817871094, (many more epochs...), 3.36980938911438, 3.3660128116607666]

太好了,训练成功了,随着时间的推移,损失已经下降了。现在我想手动调用模型:

print(
    model(
        features[0]
    )
)

但这抱怨:

ValueError: Layer "sequential" expects 1 input(s), but it received 6 input tensors. Inputs received: [<tf.Tensor: shape=(), dtype=float32, numpy=1.0>, <tf.Tensor: shape=(), dtype=float32, numpy=0.6747253>, <tf.Tensor: shape=(), dtype=float32, numpy=0.5652174>, <tf.Tensor: shape=(), dtype=float32, numpy=0.6817121>, <tf.Tensor: shape=(), dtype=float32, numpy=0.48387095>, <tf.Tensor: shape=(), dtype=float32, numpy=0.85365856>]

我不明白为什么我不能将它作为列表传递,因为在 .fit 调用中没问题,但通过一些阅读和反复试验,我找到了解决方案为此使用 tf.constant:

print(
    model(
        tf.constant(features[0])
    )
)

但是现在又报错了!

ValueError: Exception encountered when calling layer "sequential" (type Sequential).

Input 0 of layer "dense1" is incompatible with the layer: expected axis -1 of input shape to have value 6, but received input with shape (6, 1)

Call arguments received:
  • inputs=tf.Tensor(shape=(6,), dtype=float32)
  • training=None
  • mask=None

似乎第二层与第一层不兼容!那是什么意思?我绝对不明白的是,如果图层不兼容,那么这个编译是如何开始的?更糟糕的是,为什么训练成功了?显然,如果模型没有报错地编译,并且我将输入很好地传递到第一层,那么第二层就不可能有问题吗?

这里出了什么问题?在我看来,这似乎不合逻辑。一定有什么我错过了。

首先为什么要使用 Flatten 层?您可以将展平层取出并仅使用

model = tf.keras.Sequential([
    tf.keras.layers.Dense(128, activation="relu", name="dense1", input_shape=(6,)),
    tf.keras.layers.Dense(1, name="dense2")
])

并且看起来我们已经作为 input_shape 传递了一个与 (6, None) 相同的元组 (6,)。如果我们 运行 model.summary() 我们会得到

Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense1 (Dense)              (None, 128)               896       
                                                                 
 dense2 (Dense)              (None, 1)                 129       
                                                                 
=================================================================
Total params: 1,025
Trainable params: 1,025
Non-trainable params: 0
_________________________________________________________________

现在我们可以正常配合了

features = [[1.0, 0.6747252747252748, 0.5652173913043478, 0.6817120622568094, 0.48387096774193544, 0.8536585365853658], [1.0, 0.7692307692307693, 0.717391304347826, 0.7184824902723735, 0.4637096774193548, 0.8536585365853658]]
labels = [18.0, 15.0]

history = model.fit(
  features,
  labels,
  verbose=0,
  epochs=100,
  validation_data=(features, labels)
)

现在如果你想做预测你可以做

# Using .predict 
model.predict(features)
>>> array([[15.692635], [16.437447]], dtype=float32)
model.predict([features[0]])
>>> array([[15.692635]], dtype=float32)
# Using functional way
model(np.array(features))
>>><tf.Tensor: shape=(2, 1), dtype=float32, numpy=array([[15.692635],[16.437447]],dtype=float32)>
model(np.array(features[0]).reshape(1,-1))
>>> <tf.Tensor: shape=(1, 1), dtype=float32, numpy=array([[15.692635]], dtype=float32)>

.predict 和将模型本身用作函数之间的这种差异一定是由于 predict 方法和来自模型 class 的 __call__ 的不同实现。

似乎预测方法更灵活,可能会对输入进行一些修改以使用它进行预测,而功能方法可能会尝试按原样使用输入,因此我们需要将其传递为二维数组