【发布时间】:2019-10-01 10:33:05
【问题描述】:
我有一个二元分类问题的数据集,其中两个类的表示相同。由于数据集不适合内存(400 万个数据点),我将其存储为 HDF5 文件,该文件通过fit_generator 逐步读取并输入到简单的 Keras 模型中。问题是我使用fit_generator 的验证准确度很低,而如果我只是使用fit,一切都很好。我确实提到过数据集不适合内存,但出于调试目的以及本文的其余部分,我只使用了 100k 的 4M 数据点。
由于目标是对整个数据集进行分层 10 倍 CV,我手动将数据集索引划分为训练、验证和评估集的索引。我使用生成器函数调用fit_generator,生成一批训练(或验证)样本和标签,这些样本和标签覆盖了 HDF5 文件第一季度的指定索引,然后是第二季度等。
我知道fit_generator 的验证部分在后台使用test_on_batch,evaluate_generator 也是如此。我还尝试了使用train_on_batch 和test_on_batch 方法的解决方案,但结果相同:验证准确度fit_generator 等低,但fit 高 如果数据集一次全部加载到内存中。两种情况下的模型相同(fit vs fit_generator)。
数据集和模型
我的调试数据集有约 100k 样本和标签(约 50k 类 0 和约 50k 类 1)。对 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]))
这是我通过fit 得到的val_acc:
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)
对于每个 epoch,我得到的是相同的 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_generator与shuffle=False一起调用,因为它是处理洗牌的生成器(在任何情况下,指定shuffle=True不会更改val_acc)。
生成器方法
最后一块拼图:发电机。这里,n_parts 是 HDF5 文件被拆分为加载的部分数。然后,我只保留当前加载的 HDF5 文件的part 中的行,这些行实际上属于选定的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])
我确实尝试过仅对训练集、仅验证集以及两者进行改组。我确实在fit_generator 中尝试了shuffle=True 和shuffle=False 的这些组合。除了val_acc 可能会发生一些变化之外,如果我使用fit_generator,它基本上仍然是~0.5,如果我使用fit,它基本上仍然是~0.9。
你觉得我的方法有什么问题吗?用我的发电机?任何帮助表示赞赏!
我已经被这个问题困扰了 10 天了。或者,我必须在不适合内存的数据集上训练模型的其他选项(Keras 或其他库)吗?
【问题讨论】:
-
只是为了澄清:如果你使用 100k 样本数据集和
fit和fit_generator,为什么 minibatch 的数量与 128 相同的batch_size如此不同?(58847 vs 460) -
@a-d:我有大约 60k(大多数情况下为 58800)个样本用于训练。当使用
fit时,它们都出现在 Keras 进度计数中。使用fit_generator时,仍然是 58800 = 460 * 128(其中 128 是批量大小)。
标签: python machine-learning keras deep-learning