Я реализовал раннюю остановку с помощью «ModelCheckpoint». Цель состоит в том, чтобы выбрать наиболее обученную версию модели и вернуть соответствующий номер эпохи.
early_stopping = EarlyStopping(monitor='val_loss', patience=earlyStoppingEpochs, restore_best_weights=False)
checkpoint_filepath = '/tmp/checkpoint.weights.h5'
checkpoint = ModelCheckpoint(filepath=checkpoint_filepath, save_weights_only=True, monitor = "val_loss", mode = "min", save_best_only=True, verbose=verbose)
history = model.fit(Train_Data, Train_Label, epochs=epochs, batch_size=batch_size, validation_data=(Val_Data, Val_Label), verbose=verbose, callbacks=[early_stopping, checkpoint])
hist = model.history.history['val_loss']
finalEpochs = np.argmin(hist) + 1
model.load_weights(checkpoint_filepath)
Все работает. Единственный момент, который меня еще смущает, это то, что не учитываются потери при валидации необученной модели. Вместо этого первоначальная потеря устанавливается на Inf. «Эпоха 1: значение val_loss улучшено с **inf ** до 2,35898, модель сохранена в /tmp/checkpoint.weights.h5»
Однако в моем приложении возможно, что даже первая эпоха обучения приведет к ухудшению потерь при проверке. Затем он должен вернуть необученную модель как лучшую модель и идеальный номер эпохи, равный 0.
Знаете ли вы, можно ли настроить поведение так, чтобы в начале EarlyStopping использовалась не inf, а потеря валидации необученной модели?
В Керасе, согласно этому документу, она:
https://keras.io/api/callbacks/early_stopping/
Вы действительно можете указать параметр start_from_epoch=0
(по умолчанию он равен 0). Чтобы избежать -inf, возможно, вы могли бы начать через несколько эпох (например, 2); в противном случае вам придется реализовать свой собственный обратный вызов для достижения желаемого.
Вот полный пример, протестированный с последней версией keras==3.4.1
from keras.callbacks import Callback
from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Flatten
from keras.utils import to_categorical
# Custom EarlyStoppingCallback class definition
class CustomEarlyStoppingCallback(Callback):
def __init__(self, patience=0, min_delta=0, initial_best=float('inf')):
super(CustomEarlyStoppingCallback, self).__init__()
self.patience = patience
self.min_delta = min_delta
self.best_weights = None
self.wait = 0
self.stopped_epoch = 0
self.best = initial_best
def on_train_begin(self, logs=None):
self.wait = 0
self.stopped_epoch = 0
# self.best = self.best (already set during initialization)
self.best_weights = None
def on_epoch_end(self, epoch, logs=None):
current_loss = logs.get("val_loss")
if current_loss is None:
return
if current_loss < self.best - self.min_delta:
self.best = current_loss
self.wait = 0
self.best_weights = self.model.get_weights()
else:
self.wait += 1
if self.wait >= self.patience:
self.stopped_epoch = epoch
self.model.stop_training = True
self.model.set_weights(self.best_weights)
def on_train_end(self, logs=None):
if self.stopped_epoch > 0:
print(f'Restoring model weights from the end of the best epoch: {self.stopped_epoch - self.patience + 1}')
# Load and prepare the MNIST dataset
(x_train, y_train), (x_val, y_val) = mnist.load_data()
# Normalize the data
x_train = x_train.astype('float32') / 255.0
x_val = x_val.astype('float32') / 255.0
# One-hot encode the labels
y_train = to_categorical(y_train, 10)
y_val = to_categorical(y_val, 10)
# Create a simple model
model = Sequential([
Flatten(input_shape=(28, 28)),
Dense(64, activation='relu'),
Dense(64, activation='relu'),
Dense(10, activation='softmax')
])
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
custom_early_stopping = CustomEarlyStoppingCallback(patience=3, min_delta=0.001, initial_best=2.64)
history = model.fit(x_train, y_train, epochs=100, validation_data=(x_val, y_val), callbacks=[custom_early_stopping])
Боюсь, что в противном случае вам придется реализовать свой собственный обратный вызов для достижения желаемого.
Я обновил ответ примером, с которого вы можете начать: D
Большое спасибо за ваш комментарий. Это самое «чистое» решение.
Если вам это подходит, вы можете принять ответ как тот, который решил вашу проблему. Спасибо
Могу ли я добавить callbacks=[early_stopping, checkpoint], чтобы получить лучшую модель? То есть, если Early_stopping останавливается после первой эпохи, потому что необученная модель является лучшей, возвращает ли контрольная точка необученную модель?
Вы можете вручную оценить модель перед началом обучения и установить ее в качестве базовой линии для ранней остановки. Вот как вы можете настроить свой код:
Пример:
initial_val_loss = model.evaluate(Val_Data, Val_Label, verbose=0)
class CustomEarlyStopping(EarlyStopping):
def __init__(self, initial_val_loss, *args, **kwargs):
super().__init__(*args, **kwargs)
self.initial_val_loss = initial_val_loss
self.best = initial_val_loss
Надеюсь, это поможет.
Большое спасибо. Звучит многообещающе. Тогда мне просто нужно написать что-то вроде FinalEpochs = np.argmin([initial_val_loss, hist]). Если в течение первой эпохи потери усилятся, будет ли model.load_weights(checkpoint_filepath) автоматически возвращать модель до первой эпохи?
Я уже попробовал ваш код, но, похоже, он не работает. Я пытался:
startLoss = 0.00001 Early_stopping = CustomEarlyStopping(monitor='val_loss', терпение=earlyStoppingEpochs, restre_best_weights=False, Initial_val_loss=startLoss) И я получаю: Эпоха 1: val_loss улучшено с inf до 0,30686 Эпоха 2: val_loss улучшено с 0,30686 до 0,2 3336
Вам пришлось выполнить дополнительное редактирование в классе: # if self.wait >= self.patience and epoch > 0: ИЗМЕНИТЬ НА if self.wait >= self.patience and epoch >= 0: Теперь это работает, большое спасибо много!
Спасибо за ваш ответ. Если я правильно понимаю, я могу использовать этот параметр, чтобы указать, что он не должен выполнять EarlySopping в первые n эпох. К сожалению, в этом случае мою проблему это не решит. Я бы хотел уже проверить EarlyStopping от эпохи 0 до эпохи 1.