多类神经网络在优化后总是预测 1 类

数据挖掘 Python 神经网络 喀拉斯 多类分类 多标签分类
2021-10-14 22:58:59

在训练期间,神经网络稳定在一个位置,它总是预测 5 个类别中的 1 个。

我的训练集和测试集分布如下:

Train Set
Samples: 269,501. Features: 157
Data distribution
16.24% 'a'
39.93% 'b'
9.31%  'c'
20.86% 'd'
13.67% 'e'

Test Set
Samples: 33,967. Features: 157
Data distribution
10.83% 'a'
35.39% 'b'
19.86% 'c'
16.25% 'd'
17.66% 'e'

注意班级的百分比b

我正在训练一个带有 dropout 的 mlp,并且训练测试(又名验证)的准确性都处于稳定状态,完全匹配我的 5 个班级中的 1 个的训练和测试分布,即它正在学习总是预测 5 个班级中的 1 个班级我已经验证了分类器总是在预测b

我已经尝试batch_size了 0.25 和 1.0 并确保数据被重新洗牌。我尝试了有SGDAdam没有衰减和不同学习率的优化器,但结果仍然相同。尝试了 0.2 和 0.5 的 dropout。EarlyStopping300 个时代。

每隔一段时间,我就会遇到这样一种情况,在训练过程中,它会跳出训练准确度和验证准确度的固定位置,但随后验证总是下降而训练上升——或者换句话说,过度拟合。

输出,在 6 个 epoch 后切断。仅仅使用这个特殊的 SGD 优化器,它并不总是收敛得这么快:

Epoch 1/2000
Epoch 00000: val_acc improved from -inf to 0.35387, saving model to /home/user/src/thing/models/weights.hdf
269501/269501 [==============================] - 0s - loss: 1.6094 - acc: 0.1792 - val_loss: 1.6073 - val_acc: 0.3539
Epoch 2/2000
Epoch 00001: val_acc did not improve
269501/269501 [==============================] - 0s - loss: 1.6060 - acc: 0.3993 - val_loss: 1.6042 - val_acc: 0.3539
Epoch 3/2000
Epoch 00002: val_acc did not improve
269501/269501 [==============================] - 0s - loss: 1.6002 - acc: 0.3993 - val_loss: 1.6005 - val_acc: 0.3539
Epoch 4/2000
Epoch 00003: val_acc did not improve
269501/269501 [==============================] - 0s - loss: 1.5930 - acc: 0.3993 - val_loss: 1.5967 - val_acc: 0.3539
Epoch 5/2000
Epoch 00004: val_acc did not improve
269501/269501 [==============================] - 0s - loss: 1.5851 - acc: 0.3993 - val_loss: 1.5930 - val_acc: 0.3539
Epoch 6/2000

代码:模型创建:

def create_mlp(input_dim, output_dim, dropout=0.5, arch=None):
    """Setup neural network model (keras.models.Sequential)"""
    # default mlp architecture
    arch = arch if arch else [64,32,32,16]

    # setup densely connected NN architecture (MLP)
    model = Sequential()
    model.add(Dropout(dropout, input_shape=(input_dim,)))
    for output in arch:
        model.add(Dense(output, activation='relu', W_constraint=maxnorm(3)))
        model.add(Dropout(dropout))
    model.add(Dense(output_dim, activation='sigmoid'))

    # compile model and save architecture to disk
    sgd = SGD(lr=0.01, momentum=0.9, decay=0.0001, nesterov=True)
    # adam = Adam(lr=0.001, decay=0.0001)
    model.compile(loss='categorical_crossentropy',
                  optimizer=sgd,
                  metrics=['accuracy'])
    return model

经过一些预处理后在 main 内部:

    # labels must be one-hot encoded for loss='categorical_crossentropy'
    # meaning, of possible labels 0,1,2: 0->[1,0,0]; 1->[0,1,0]; 2->[0,0,1]
    y_train_onehot = to_categorical(y_train, n_classes)
    y_test_onehot = to_categorical(y_test, n_classes)

    # get neural network architecture and save to disk
    model = create_mlp(input_dim=train_dim, output_dim=n_classes)
    with open(clf_file(typ='arch'), 'w') as f:
        f.write(model.to_yaml())

    # output logs to tensorflow TensorBoard
    # NOTE: don't use param histogram_freqs until keras issue fixed
    #       https://github.com/fchollet/keras/pull/5175
    tensorboard = TensorBoard(log_dir=opts.tf_dir)

    # only save model weights for best performing model
    checkpoint = ModelCheckpoint(clf_file(typ='weights'),
                                 monitor='val_acc',
                                 verbose=1,
                                 save_best_only=True)

    # stop training early if validation accuracy doesn't improve for long enough
    early_stopping = EarlyStopping(monitor='val_acc', patience=300)

    # shuffle data for good measure before fitting
    x_train, y_train_onehot = shuffle(x_train, y_train_onehot)

    np.random.seed(seed)
    model.fit(x_train, y_train_onehot,
              nb_epoch=opts.epochs,
              batch_size=train_batch_size,
              shuffle=True,
              callbacks=[tensorboard, checkpoint, early_stopping],
              validation_data=(x_test,y_test_onehot))
3个回答

通过与幼稚模型进行比较,您可以学到很多东西。幼稚模型是没有任何特征的模型。默认情况下,它将始终预测最有可能的目标。请注意,这正是您的模型正在做的事情。这表明这些特征无助于做出预测。您是否进行了基本的分布分析,以了解哪些特征会影响目标的分布?这就是我要开始的地方。

这可能是您的代码中的错误、训练集的问题(可能您的文件格式不太正确)或其他一些实现问题。

您确定要在最后一层使用 sigmoid 激活函数吗?我本来希望正常的方法是使用 softmax 作为最后一层(这样您就可以将输出视为每个类的概率,即,它们被归一化为总和为 1)。你可以试试。

或者,这可能是一个“类不平衡”问题。对该术语进行一些搜索,您会发现一堆处理它的标准方法。您可以平衡训练集,或在实例上使用“权重”,或根据先验调整阈值。然而,正如其他人所指出的那样,这种不平衡还不够严重,以至于我预计它会导致这种强烈的偏见。

您的特征也可能无用并且无助于预测输出(例如,它们与输出无关或不相关)。这也将与您所看到的一致。


旁注:我的理解是,Adam 优化器通常比普通的 SGD 更有效,尽管我认为没有任何理由认为这是这里的问题。

我的猜测是您提供的数据没有足够的信息来预测a,b,c,d或者e. 因此,因为b在数据集中被过度表示,它总是会预测b,因为那是最安全的赌注。如果您对输入一无所知,或者您无法从中提取任何有用的信息,您可能也总是会预测b,只是因为它在选择随机样本时最有可能。

要解决此问题,您要么需要获得更好的数据,其中包含更多信息,要么平衡您的数据集(如果您的任务允许),以便所有标签出现的频率相同。