У меня есть набор данных для задачи двоичной классификации, в которой оба класса представлены одинаково. Поскольку набор данных не помещается в память (4 миллиона точек данных), я сохраняю его в виде файла HDF5, который считывается и постепенно добавляется в простую модель Keras с помощью fit_generator
. Проблема в том, что я получаю низкую точность проверки с fit_generator
, тогда как все в порядке, если я просто использую fit
. Я упоминал, что набор данных не умещается в памяти, но для целей отладки и для остальной части этого поста я использую только 100 КБ точек данных 4M.
Поскольку цель состоит в том, чтобы сделать многослойное 10-кратное резюме для полного набора данных, я вручную делю индексы набора данных на индексы для обучающих, проверочных и оценочных наборов. Я вызываю fit_generator
с функцией генератора, дающей партии обучающих (или проверочных) образцов и меток, охватывающих указанные индексы из первой четверти файла HDF5, затем из второго квартала и т. Д.
Я знаю, что часть проверки fit_generator
использует test_on_batch
под капотом, как и evaluate_generator
. Я также попробовал решение, используя подходы train_on_batch
и test_on_batch
, но с тем же результатом: точность проверки низкая с fit_generator
и т.п., но высокая с fit
если набор данных загружается в память одновременно. Модель одинакова в обоих случаях (fit
против fit_generator
).
Набор данных и модель
Мой набор данных отладки содержит ~ 100 тыс. Выборок и меток (~ 50 тыс. В классе 0 и ~ 50 тыс. В классе 1). Обучение и проверка выполняется на 75% данных (у меня примерно 60 тысяч образцов для обучения и 15 тысяч для проверки). Эти два класса в равной степени распределены между обучающими и проверочными образцами.
Вот очень простая модель, которую я использую:
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_generator
вызывается с shuffle=False
, потому что именно генератор обрабатывает тасование (в любом случае, указание shuffle=True
не меняет val_acc
).
Генератор метода
Последний кусочек головоломки: генератор. Здесь n_parts
- это количество частей, на которые файл HDF5 разделен для загрузки. Затем я сохраняю только те строки - в загруженном в настоящее время part
файла HDF5 - которые фактически попадают в выбранную indexes
. Сохраненные элементы (partial_features
) и метки (partial_labels
) на самом деле являются строками с индексами partial_indexes
в файле HDF5.
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=False
в fit_generator
. Помимо того, что val_acc
может немного измениться, по-прежнему, по сути, он равен ~ 0,5, если я использую fit_generator
, и ~ 0,9, если я использую fit
.
Вы видите что-то не так с моим подходом? С моим генератором? Любая помощь приветствуется!
Я застрял на этой проблеме уже 10 дней. В качестве альтернативы, какой другой вариант (Keras или другая библиотека) мне нужно обучить модели на наборе данных, который не помещается в память?