Keras 一维分割模型总是对偶数个项目进行分类

Keras 1D segmentation model always classifies even number of items

我正在尝试训练一维 CNN 来识别文本字符串的特定部分。

输入是 (128,1) 形状的数组,包含 128 个字符,目的是让网络 class 将每个字符化为特定的 class。为了便于说明,输入数组可能如下所示:

array(['3', '!', 'd', 'o', 'g', '.', '?', '8', '7', 'a', 'p', 'p', 'l',
       'e', 'f', 'd', '$', '5'], dtype='<U1')

相应的标签如下所示:

array([0, 0, 1, 1, 1, 0, 0, 0, 0, 2, 2, 2, 2, 2, 0, 0, 0, 0])

想法是网络将 class 将字符 "d", "o", "g" 化为 class 1(比如,动物),将 "a", "p", "p", "l", "e" 化为 class2(水果)剩下的变成class0.

计划构建一个架构类似于 U-Net 的网络,但现在我正在试验一个非常简单的 downsample/upsample 网络,如下所示:

def get_model(seq_size,n_classes):
    
    inputs = tf.keras.Input(shape=seq_size)
    
#     Downsample phase

    x = tf.keras.layers.Conv1D(32,11,padding="same")(inputs)
    x = tf.keras.layers.BatchNormalization()(x)
    x = tf.keras.layers.Activation("relu")(x)
    
    x = tf.keras.layers.MaxPooling1D(2,padding="same")(x)    
    
    x = tf.keras.layers.Conv1D(64,5,padding="same")(x)
    x = tf.keras.layers.BatchNormalization()(x)
    x = tf.keras.layers.Activation("relu")(x)
    
    x = tf.keras.layers.MaxPooling1D(2,padding="same")(x)  
    
#     Upsample phase    
    
    x = tf.keras.layers.Conv1DTranspose(128,5,padding="same")(x)
    x = tf.keras.layers.BatchNormalization()(x)
    x = tf.keras.layers.Activation("relu")(x)
    
    x = tf.keras.layers.UpSampling1D(2)(x)  
    
    x = tf.keras.layers.Conv1DTranspose(256,7,padding="same")(x)
    x = tf.keras.layers.BatchNormalization()(x)
    x = tf.keras.layers.Activation("relu")(x)
    
    x = tf.keras.layers.UpSampling1D(2)(x)     
    
    outputs = tf.keras.layers.Conv1D(n_classes,1,activation="softmax",padding="same")(x)
    
    model = tf.keras.Model(inputs,outputs)
    return model 

输入形状为 (128,1)n_classes = 5

该模型对于基线工作得很好,但它有一个有趣的怪癖,我正在努力解决这个问题:当它对字符进行预测时,它总是 class确定偶数个字符(或“像素”,如果将其视为类似于图像分割任务)。所以在上面的例子中,它会识别 !dogdog. 属于 class 1,而 7appleapplef 属于 class 2 .

只有当单词包含奇数个字符时才会出现问题,这让我认为这可能与最大池化和上采样操作有关。我试图通过了解这些操作在 Keras 中的工作方式来找到答案,但这并没有取得成果。因此,如果有人能阐明为什么预测总是偶数个字符,以及我该如何纠正,我将不胜感激!

编辑 来自评论中的建议:

澄清一下,数组仅使用 ord 函数进行编码,然后 min/max 归一化到范围 0:1.

我使用稀疏分类交叉熵作为损失函数,训练设置如下:

loss = tf.keras.losses.SparseCategoricalCrossentropy()
opt = tf.keras.optimizers.Adam()

model.compile(optimizer=opt,loss=loss,metrics=["accuracy"])

callbacks = [tf.keras.callbacks.ModelCheckpoint("trial.h5",save_best_only=True)]

epochs = 10
model.fit(train_gen, epochs=epochs, validation_data=test_gen, callbacks=callbacks)

其中 train_gentest_gen 是构建为 tf.keras.utils.Sequence subclass 的数据生成器。

我认为当您使用 UpSampling1D 时,每个值都会重复两次。这意味着最后一步的输入包含 pair-wise 个重复值。然后它将为相邻字符给出相同的预测 class 。如果我的猜测是正确的,您将始终看到 2k 和 2k+1 个字符的相同预测。

您可以通过检查

中的输入 x 来确认
outputs = tf.keras.layers.Conv1D(n_classes,1,activation="softmax",padding="same")(x)

它应该看起来像 [a, a, b, b, c, c, ...]

要解决此问题,您可能可以在 outputs = ...x = tf.keras.layers.UpSampling1D(2)(x)

之间添加一个额外的步骤