Keras模型无法降低损失

5
我提供一个例子,其中一个tf.keras模型无法从非常简单的数据中学习。我使用的是tensorflow-gpu==2.0.0keras==2.3.0和Python 3.7。在我的帖子结尾处,我提供了复现我观察到的问题所需的Python代码。
  1. 数据

样本是形状为(6,16,16,16,3)的Numpy数组。为了使事情变得非常简单,我只考虑全是1和0的数组。1的数组被标记为1,0的数组被标记为0。我可以用这段代码生成一些样本(如下,n_samples=240):

def generate_fake_data():
    for j in range(1, 240 + 1):
        if j < 120:
            yield np.ones((6, 16, 16, 16, 3)), np.array([0., 1.])
        else:
            yield np.zeros((6, 16, 16, 16, 3)), np.array([1., 0.])

为了将这些数据输入到tf.keras模型中,我使用以下代码创建一个tf.data.Dataset实例。这将创建具有BATCH_SIZE = 12个样本的随机批次。
def make_tfdataset(for_training=True):
    dataset = tf.data.Dataset.from_generator(generator=lambda: generate_fake_data(),
                                             output_types=(tf.float32,
                                                           tf.float32),
                                             output_shapes=(tf.TensorShape([6, 16, 16, 16, 3]),
                                                            tf.TensorShape([2])))
    dataset = dataset.repeat()
    if for_training:
        dataset = dataset.shuffle(buffer_size=1000)
    dataset = dataset.batch(BATCH_SIZE)
    dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
    return dataset
  1. 模型

我提出以下模型来对我的样本进行分类:

def create_model(in_shape=(6, 16, 16, 16, 3)):

    input_layer = Input(shape=in_shape)

    reshaped_input = Lambda(lambda x: K.reshape(x, (-1, *in_shape[1:])))(input_layer)

    conv3d_layer = Conv3D(filters=64, kernel_size=8, strides=(2, 2, 2), padding='same')(reshaped_input)

    relu_layer_1 = ReLU()(conv3d_layer)

    pooling_layer = GlobalAveragePooling3D()(relu_layer_1)

    reshape_layer_1 = Lambda(lambda x: K.reshape(x, (-1, in_shape[0] * 64)))(pooling_layer)

    expand_dims_layer = Lambda(lambda x: K.expand_dims(x, 1))(reshape_layer_1)

    conv1d_layer = Conv1D(filters=1, kernel_size=1)(expand_dims_layer)

    relu_layer_2 = ReLU()(conv1d_layer)

    reshape_layer_2 = Lambda(lambda x: K.squeeze(x, 1))(relu_layer_2)

    out = Dense(units=2, activation='softmax')(reshape_layer_2)

    return Model(inputs=[input_layer], outputs=[out])

该模型使用Adam(默认参数)进行优化,并使用binary_crossentropy损失函数:

clf_model = create_model()
clf_model.compile(optimizer=Adam(),
                  loss='categorical_crossentropy',
                  metrics=['accuracy', 'categorical_crossentropy'])
clf_model.summary() 的输出如下:
Model: "model"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         [(None, 6, 16, 16, 16, 3) 0         
_________________________________________________________________
lambda (Lambda)              (None, 16, 16, 16, 3)     0         
_________________________________________________________________
conv3d (Conv3D)              (None, 8, 8, 8, 64)       98368     
_________________________________________________________________
re_lu (ReLU)                 (None, 8, 8, 8, 64)       0         
_________________________________________________________________
global_average_pooling3d (Gl (None, 64)                0         
_________________________________________________________________
lambda_1 (Lambda)            (None, 384)               0         
_________________________________________________________________
lambda_2 (Lambda)            (None, 1, 384)            0         
_________________________________________________________________
conv1d (Conv1D)              (None, 1, 1)              385       
_________________________________________________________________
re_lu_1 (ReLU)               (None, 1, 1)              0         
_________________________________________________________________
lambda_3 (Lambda)            (None, 1)                 0         
_________________________________________________________________
dense (Dense)                (None, 2)                 4         
=================================================================
Total params: 98,757
Trainable params: 98,757
Non-trainable params: 0
  1. 培训

模型的训练如下所示,训练了500个epochs:

train_ds = make_tfdataset(for_training=True)

history = clf_model.fit(train_ds,
                        epochs=500,
                        steps_per_epoch=ceil(240 / BATCH_SIZE),
                        verbose=1)
  1. 问题!

在500个epoch期间,模型损失始终保持在0.69左右,并且从未低于0.69。即使我将学习率设置为1e-2而不是1e-3,这也是如此。数据非常简单(只有0和1)。天真地说,我预计该模型的准确性应该比0.6高得多。事实上,我希望它能够很快达到100%的准确性。我错在哪里了?

  1. 完整的代码...
import numpy as np
import tensorflow as tf
import tensorflow.keras.backend as K
from math import ceil
from tensorflow.keras.layers import Input, Dense, Lambda, Conv1D, GlobalAveragePooling3D, Conv3D, ReLU
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam

BATCH_SIZE = 12


def generate_fake_data():
    for j in range(1, 240 + 1):
        if j < 120:
            yield np.ones((6, 16, 16, 16, 3)), np.array([0., 1.])
        else:
            yield np.zeros((6, 16, 16, 16, 3)), np.array([1., 0.])


def make_tfdataset(for_training=True):
    dataset = tf.data.Dataset.from_generator(generator=lambda: generate_fake_data(),
                                             output_types=(tf.float32,
                                                           tf.float32),
                                             output_shapes=(tf.TensorShape([6, 16, 16, 16, 3]),
                                                            tf.TensorShape([2])))
    dataset = dataset.repeat()
    if for_training:
        dataset = dataset.shuffle(buffer_size=1000)
    dataset = dataset.batch(BATCH_SIZE)
    dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
    return dataset


def create_model(in_shape=(6, 16, 16, 16, 3)):

    input_layer = Input(shape=in_shape)

    reshaped_input = Lambda(lambda x: K.reshape(x, (-1, *in_shape[1:])))(input_layer)

    conv3d_layer = Conv3D(filters=64, kernel_size=8, strides=(2, 2, 2), padding='same')(reshaped_input)

    relu_layer_1 = ReLU()(conv3d_layer)

    pooling_layer = GlobalAveragePooling3D()(relu_layer_1)

    reshape_layer_1 = Lambda(lambda x: K.reshape(x, (-1, in_shape[0] * 64)))(pooling_layer)

    expand_dims_layer = Lambda(lambda x: K.expand_dims(x, 1))(reshape_layer_1)

    conv1d_layer = Conv1D(filters=1, kernel_size=1)(expand_dims_layer)

    relu_layer_2 = ReLU()(conv1d_layer)

    reshape_layer_2 = Lambda(lambda x: K.squeeze(x, 1))(relu_layer_2)

    out = Dense(units=2, activation='softmax')(reshape_layer_2)

    return Model(inputs=[input_layer], outputs=[out])


train_ds = make_tfdataset(for_training=True)
clf_model = create_model(in_shape=(6, 16, 16, 16, 3))
clf_model.summary()
clf_model.compile(optimizer=Adam(lr=1e-3),
                  loss='categorical_crossentropy',
                  metrics=['accuracy', 'categorical_crossentropy'])

history = clf_model.fit(train_ds,
                        epochs=500,
                        steps_per_epoch=ceil(240 / BATCH_SIZE),
                        verbose=1)

从你的代码来看,我发现了几个值得质疑的设计决策;首先,你对重塑的目标是什么?无论是否有意为之,你的形状规范破坏了特征关系 - 这足以解释为什么你的模型没有任何进展。此外,你确定需要一个6D输入吗 - 预期的应用是什么? - OverLordGoldDragon
TimeDistributed不适用,因为它会暗示存在时间关系,而实际上不存在。(可能有例外情况,但对于您的应用程序来说很可能是多余的) - OverLordGoldDragon
2个回答

2

您的代码存在一个关键问题:维度混乱。您 永远不应该 改变的是批量维度 - 因为它按定义保存了您数据的独立样本。在您的第一个重塑操作中,您将特征维度与批量维度混合:

Tensor("input_1:0", shape=(12, 6, 16, 16, 16, 3), dtype=float32)
Tensor("lambda/Reshape:0", shape=(72, 16, 16, 16, 3), dtype=float32)

这就像是在输入一个形状为(16,16,16,3)的独立样本集。后续层也会遇到类似的问题。


解决方案:

  • 不要在每个步骤中重新调整形状(应该使用Reshape),而是通过调整现有的卷积和池化层来直接处理所有事情。
  • 除了输入和输出层之外,最好将每个层命名为简短且简单的名称 - 没有失去清晰度,因为每行都由层名称定义得很好
  • GlobalAveragePooling旨在成为最终层,因为它折叠特征维度 - 在您的情况下,如下所示:(12,16,16,16,3) --> (12,3); Conv之后几乎没有什么作用
  • 按上述方法,我用Conv3D替换了Conv1D
  • 除非您使用可变批量大小,否则始终使用batch_shape=而不是shape=,因为您可以完全检查层尺寸(非常有帮助)
  • 根据您的评论回复,您真正的batch_size是6
  • kernel_size=1和(尤其是)filters=1是非常弱的卷积,我相应地进行了替换 - 如果您希望可以恢复
  • 如果您的预期应用程序中只有2个类,则建议使用Dense(1, 'sigmoid')binary_crossentropy损失

最后一点:你可以把上面的所有东西都扔掉,除了维度重排建议,仍然可以获得完美的训练集性能;这就是问题的根源。

def create_model(batch_size, input_shape):

    ipt = Input(batch_shape=(batch_size, *input_shape))
    x   = Conv3D(filters=64, kernel_size=8, strides=(2, 2, 2),
                             activation='relu', padding='same')(ipt)
    x   = Conv3D(filters=8,  kernel_size=4, strides=(2, 2, 2),
                             activation='relu', padding='same')(x)
    x   = GlobalAveragePooling3D()(x)
    out = Dense(units=2, activation='softmax')(x)

    return Model(inputs=ipt, outputs=out)

BATCH_SIZE = 6
INPUT_SHAPE = (16, 16, 16, 3)
BATCH_SHAPE = (BATCH_SIZE, *INPUT_SHAPE)

def generate_fake_data():
    for j in range(1, 240 + 1):
        if j < 120:
            yield np.ones(INPUT_SHAPE), np.array([0., 1.])
        else:
            yield np.zeros(INPUT_SHAPE), np.array([1., 0.])


def make_tfdataset(for_training=True):
    dataset = tf.data.Dataset.from_generator(generator=lambda: generate_fake_data(),
                                 output_types=(tf.float32,
                                               tf.float32),
                                 output_shapes=(tf.TensorShape(INPUT_SHAPE),
                                                tf.TensorShape([2])))
    dataset = dataset.repeat()
    if for_training:
        dataset = dataset.shuffle(buffer_size=1000)
    dataset = dataset.batch(BATCH_SIZE)
    dataset = dataset.prefetch(tf.data.experimental.AUTOTUNE)
    return dataset

结果:


Epoch 28/500
40/40 [==============================] - 0s 3ms/step - loss: 0.0808 - acc: 1.0000

-1

因为您的标签可能是0或1,我建议将激活函数更改为softmax,并将输出神经元数量设置为2。现在,最后一层(输出)将如下所示:

out = Dense(units=2, activation='softmax')(reshaped_conv_features)

我之前也遇到过同样的问题,并且发现由于1或0的概率相关,而不是多标签分类问题,所以Softmax是更好的选择。Sigmoid分配的概率与其他可能的输出标签无关。


我根据你的答案更新了我的帖子。这并没有帮助到我...真正的问题(如果有的话)在别处。 - Pouteri
请尝试将批量大小至少增加到32,您可以在增加批量大小后尝试使用Sigmoid和Softmax损失函数,但我仍建议使用Softmax。 - Rachayita Giri
不要更改 BATCH_SIZE = 32 - Pouteri
SGD(lr=0.1) 仍然没有帮助。我不确定这是否是优化器的问题。 - Pouteri
即使您使用Sigmoid? - Rachayita Giri
显示剩余2条评论

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