Keras的fit_generator验证准确性低,但fit的验证准确性不高

时间:2019-05-14 11:47:20

标签: python machine-learning keras deep-learning

我有一个用于二进制分类问题的数据集,其中两个类均被平等地表示。由于数据集无法容纳到内存中(400万个数据点),我将其存储为HDF5文件,该文件将通过fit_generator读取并递增地输入到简单的Keras模型中。问题是fit_generator的验证准确性降低,而如果我仅使用fit,一切都很好。我确实提到数据集不适合内存,但是出于调试目的,在本文的其余部分中,我仅使用100k的4M数据点。

由于目标是对整个数据集进行分层10倍CV,因此我将数据集索引手动划分为用于训练,验证和评估集的索引。我用生成器函数调用fit_generator,从HDF5文件的第一季度开始,然后从第二季度开始,生成涵盖指定索引的成批训练(或验证)样本和标签,

我知道fit_generator的验证部分在内部使用test_on_batchevaluate_generator也是如此。我还尝试了使用train_on_batchtest_on_batch方法的解决方案,但结果相同:fit_generator 等类似的方法,验证准确性低,但是如果数据集一次全部加载到内存中,则> fit 高。两种情况下的模型都是相同的fitfit_generator)。

数据集和模型

我的调试数据集具有约100k样本和标签(第0类为约50k,第1类为约50k)。训练和验证是对75%的数据执行的(我大约有60k样本用于训练,而15k用于验证)。这两个类别在训练和验证样本之间平均分配。

这是我使用的非常简单的模型:

input_layer = Input(shape=(2581,), dtype='float32')
hidden_layer = Dense(512, activation='relu', input_shape=(2581, 1))(input_layer)
output_layer = Dense(1, activation='sigmoid')(hidden_layer)

model = Model(inputs=[input_layer], outputs=[output_layer])
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])

fit很棒...

由于这个小的数据集很容易放入内存,因此这就是我直接使用上面创建的模型使用fit的方式; train_idx是训练集的索引,valid_idx是验证集的索引:

model.fit(features[train_idx], labels[train_idx],
          batch_size=128, epochs=5,
          shuffle=True,
          validation_data=(features[valid_idx], labels[valid_idx]))

这是val_acc带来的fit

58847/58847 [==============================] - 4s 70us/step - loss: 0.4075 - acc: 0.8334 - val_loss: 0.3259 - val_acc: 0.8828
Epoch 2/5
58847/58847 [==============================] - 4s 61us/step - loss: 0.2757 - acc: 0.8960 - val_loss: 0.2686 - val_acc: 0.9039
Epoch 3/5
58847/58847 [==============================] - 4s 61us/step - loss: 0.2219 - acc: 0.9212 - val_loss: 0.2162 - val_acc: 0.9227
Epoch 4/5
58847/58847 [==============================] - 4s 61us/step - loss: 0.1855 - acc: 0.9353 - val_loss: 0.1992 - val_acc: 0.9314
Epoch 5/5
58847/58847 [==============================] - 4s 60us/step - loss: 0.1583 - acc: 0.9456 - val_loss: 0.1763 - val_acc: 0.9390

...但是fit_generator不是

我希望fit_generator会得到相同的结果:

model.fit_generator(generate_data(hdf5_file, train_idx, batch_size),
                    steps_per_epoch=len(train_idx) // batch_size,
                    epochs=5,
                    shuffle=False,
                    validation_data=generate_data(hdf5_file, valid_idx, batch_size),
                    validation_steps=len(valid_idx) // batch_size)

对于每个时期,我得到的都是相同的val_acc,就好像不断预测只有一个班级一样:

460/460 [==============================] - 8s 17ms/step - loss: 0.3230 - acc: 0.9447 - val_loss: 6.9277 - val_acc: 0.4941
Epoch 2/5
460/460 [==============================] - 6s 14ms/step - loss: 0.9536 - acc: 0.8627 - val_loss: 7.1385 - val_acc: 0.4941
Epoch 3/5
460/460 [==============================] - 6s 14ms/step - loss: 0.8764 - acc: 0.8839 - val_loss: 7.0521 - val_acc: 0.4941
Epoch 4/5
460/460 [==============================] - 6s 13ms/step - loss: 0.9005 - acc: 0.8885 - val_loss: 7.0459 - val_acc: 0.4941
Epoch 5/5
460/460 [==============================] - 6s 14ms/step - loss: 0.9259 - acc: 0.8907 - val_loss: 7.0880 - val_acc: 0.4941

请注意:

  • generate_data生成器用于培训和验证。
  • fit_generatorshuffle=False一起调用是因为它是处理改组的生成器(在任何情况下,指定shuffle=True都不会更改val_acc)。

生成器方法

最后一个难题:发电机。在这里,n_parts是HDF5文件被分割成要加载的部分的数量。然后,我仅保留实际落入所选part中的行-在当前加载的HDF5文件的indexes中。保留的要素(partial_features和标签(partial_labels)实际上是HDF5文件中索引partial_indexes上的行。

def generate_data(hdf5_file, indexes, batch_size, n_parts=4):
    part = 0
    with h5py.File(hdf5_file, 'r') as h5:
        dset = h5.get('features')
        part_size = dset.shape[0] // n_parts

    while True:
        with h5py.File(hdf5_file, 'r') as h5:
            dset = h5.get('features')
            dset_start = part * part_size
            dset_end = (part + 1) * part_size if part < n_parts - 1 else dset.shape[0]
            partial_features = dset[dset_start:dset_end, :-1]
            partial_labels = dset[dset_start:dset_end, -1]

        partial_indexes = list()
        for index in indexes:
            if dset_start <= index < dset_end:
                partial_indexes.append(index)
        partial_indexes = np.asarray(partial_indexes)

        offset = part * part_size
        part = part + 1 if part < n_parts - 1 else 0
        if not len(partial_indexes):
            continue

        partial_features = partial_features[partial_indexes - offset]
        partial_labels = partial_labels[partial_indexes - offset]

        batch_indexes = [idx for idx in range(0, len(partial_features), batch_size)]

        random.shuffle(batch_indexes)
        for idx in batch_indexes:
            yield np.asarray(partial_features[idx:idx + batch_size, :]), \
                  np.asarray(partial_labels[idx:idx + batch_size])

我确实尝试过仅对训练集,仅对验证集以及两者进行改组。我确实在shuffle=True中用shuffle=Falsefit_generator尝试了这些组合。除了val_acc可能会有所变化的事实之外,如果我使用fit_generator,它实际上仍为〜0.5,而如果我使用fit,则实际上为〜0.9。

您认为我的方法有什么问题吗?用我的发电机?任何帮助表示赞赏!

我已经在这个问题上停留了10天了。另外,我还必须通过其他哪些选项(Keras或其他库)在不适合内存的数据集上训练模型?

1 个答案:

答案 0 :(得分:0)

我终于弄清楚了,如果其他人偶然发现了类似的问题,我将把发现的结果发表出来,以备将来参考:发生器不是问题,而是样本中的顺序 HDF5文件是。

该模型用于二进制分类问题,其中数据集中的标签为零或一。问题在于HDF5文件最初包含所有标有1的样本,然后是所有标有0的样本(其中正样本和负样本的数量大致相同)。这意味着当生成器功能将HDF5文件分为4部分时,前两部分仅包含正样本,后两部分仅包含负样本。

如果以随机顺序将样本写入HDF5文件,以使文件的任何连续部分大致包含相同数量的正样本和负样本,则可以修复此问题。这样,在训练过程中的任何给定时间,模型都会以大致相等的比例呈现正负数据