Расчет Fscore для каждой эпохи с использованием keras (не пакетно) - PullRequest
0 голосов
/ 08 мая 2020

Суть этого вопроса:

Я бы хотел найти правильный способ вычисления Fscore для данных проверки и обучения после каждой эпохи (не пакетно)

Для задачи двоичной классификации я хотел бы вычислить Fscore после каждой эпохи, используя простую модель keras. Но вопрос о том, как вычислить Fscore, кажется довольно обсуждаемым.

Я знаю, что keras работает пакетно, и один из способов вычисления fscore для каждой партии будет { ссылка } (Расчет Fscore: f1).

Пакетный расчет может быть довольно запутанным, и я предпочитаю рассчитывать Fscore после каждой эпохи . Поэтому простой вызов history.history['f1'] или history.history['val_f1'] делает трюк , а не , потому что он показывает пакетные fscores.

Я решил, что один из способов - сохранить каждую модель с помощью from keras.callbacks import ModelCheckpoint функция:

  1. Сохранение веса каждой модели после каждой эпохи
  2. Повторная загрузка модели и использование model.evaluate или model.predict

Редактировать:

Используя бэкэнд тензорного потока, я решил отслеживать TruePositives, FalsePositives и FalseNegatives (как предложил зонтик29). Но теперь самое интересное: Результаты при перезагрузке модели отличаются для обучающих данных (TP, FP, FN разные) но не для набора проверки!

Итак, простая модель, хранящая веса для восстановления каждой модели и пересчета TP, FN, TP (и, наконец, Fscore), выглядит так:

from keras.metrics import TruePositives, TrueNegatives, FalseNegatives, FalsePositives

## simple keras model
sequence_input = Input(shape=(input_dim,), dtype='float32')
preds = Dense(1, activation='sigmoid',name='output')(sequence_input)
model = Model(sequence_input, preds)

model.compile(loss='binary_crossentropy',
              optimizer='adam',
              metrics=[TruePositives(name='true_positives'),
                       TrueNegatives(name='true_negatives'),
                       FalseNegatives(name='false_negatives'),
                       FalsePositives(name='false_positives'),
                       f1])

# model checkpoints
filepath="weights-improvement-{epoch:02d}-{val_f1:.2f}.hdf5"
checkpoint = ModelCheckpoint(os.path.join(savemodel,filepath), monitor='val_f1', verbose=1, save_best_only=False, save_weights_only=True, mode='auto')
callbacks_list = [checkpoint]

history = model.fit(x_train, y_train, validation_data=(x_val, y_val), epochs=epoch, batch_size=batch,
                    callbacks=[callbacks_list])

## Saving TP, FN, FP to calculate Fscore
tp.append(history.history['true_positives'])
fp.append(history.history['false_positives'])
fn.append(history.history['false_negatives'])

arr_train = np.stack((tp, fp, fn), axis=1)

## doing the same for tp_val, fp_val, fn_val 
[...]
arr_val = np.stack((tp_val, fp_val, fn_val), axis=1)

## following method just showes batch-wise fscores and shouldnt be used:
## f1_sc.append(history.history['f1'])  

Перезагрузка модели после каждой эпохи для вычисления Fscore (Метод predict с sklearn fscore metri c from sklearn.metrics import f1_score эквивалентен вычислению fscore metri c из TP, FP, FN):

Fscore_val = []
fscorepredict_val_sklearn = []
Fscore_train = []
fscorepredict_train = []

## model_loads contains list of model-paths
for i in model_loads:
    ## rebuilding the model each time since only weights are stored
    sequence_input = Input(shape=(input_dim,), dtype='float32')
    preds = Dense(1, activation='sigmoid',name='output')(sequence_input)
    model = Model(sequence_input, preds)
    model.load_weights(i)

    # Compile model (required to make predictions)
    model.compile(loss='binary_crossentropy',
                  optimizer='adam',
                  metrics=[TruePositives(name='true_positives'),
                           TrueNegatives(name='true_negatives'),
                           FalseNegatives(name='false_negatives'),
                           FalsePositives(name='false_positives'),
                           f1
                           ])    

    ### For Validation data
    ## using evaluate
    y_pred =  model.evaluate(x_val, y_val, verbose=0)
    Fscore_val.append(y_pred)  ## contains (loss,tp,fp,fn, f1-batchwise)

    ## using predict
    y_pred = model.predict(x_val)
    val_preds = [1 if x > 0.5 else 0 for x in y_pred]
    cm = f1_score(y_val, val_preds)
    fscorepredict_val_sklearn.append(cm)  ## equivalent to Fscore calculated from Fscore_vals tp,fp, fn


    ### For the training data
    y_pred =  model.evaluate(x_train, y_train, verbose=0) 
    Fscore_train.append(y_pred) ## also contains (loss,tp,fp,fn, f1-batchwise)

    y_pred =  model.predict(x_train, verbose=0)  # gives probabilities
    train_preds = [1 if x > 0.5 else 0 for x in y_pred]
    cm = f1_score(y_train, train_preds)
    fscorepredict_train.append(cm)

Расчет Fscore из tp, fn и fp с использованием Fscore_val tp, fn, fp и сравнение его с fscorepredict_val_sklearn эквивалентно и идентично вычислению его из arr_val.

Однако, количество tp, fn и fp отличается при сравнении Fscore_train и arr_train. Таким образом, я получаю разные оценки. Количество tp, fn, fp должно быть одинаковым, но их нет .. Это ошибка?

Кому из них мне доверять? На самом деле fscorepredict_train кажутся более заслуживающими доверия, поскольку они начинаются выше «всегда угадывает класс 1» -Fscore (при отзыве = 1). (fscorepredict_train[0]=0.6784 vs f_hist[0]=0.5736 vs always-guessing-class-1-fscore = 0,6751)

[ Примечание: Fscore_train[0] = [0.6853608025386962, 2220.0, 250.0, 111.0, 1993.0, 0.6730511784553528] (loss, tp, tn, fp, fn) приводит к fscore = 0,6784, поэтому Fscore от Fscore_train = fscorepredict_train]

1 Ответ

1 голос
/ 08 мая 2020

Я предоставляю настраиваемый обратный вызов, который вычисляет оценку (в вашем случае F1 из sklearn) по ВСЕМ данным в конце эпохи (для поезда и, возможно, проверки)

class F1History(tf.keras.callbacks.Callback):

    def __init__(self, train, validation=None):
        super(F1History, self).__init__()
        self.validation = validation
        self.train = train

    def on_epoch_end(self, epoch, logs={}):

        logs['F1_score_train'] = float('-inf')
        X_train, y_train = self.train[0], self.train[1]
        y_pred = (self.model.predict(X_train).ravel()>0.5)+0
        score = f1_score(y_train, y_pred)       

        if (self.validation):
            logs['F1_score_val'] = float('-inf')
            X_valid, y_valid = self.validation[0], self.validation[1]
            y_val_pred = (self.model.predict(X_valid).ravel()>0.5)+0
            val_score = f1_score(y_valid, y_val_pred)
            logs['F1_score_train'] = np.round(score, 5)
            logs['F1_score_val'] = np.round(val_score, 5)
        else:
            logs['F1_score_train'] = np.round(score, 5)

здесь фиктивный пример :

x_train = np.random.uniform(0,1, (30,10))
y_train = np.random.randint(0,2, (30))

x_val = np.random.uniform(0,1, (20,10))
y_val = np.random.randint(0,2, (20))

sequence_input = Input(shape=(10,), dtype='float32')
preds = Dense(1, activation='sigmoid',name='output')(sequence_input)
model = Model(sequence_input, preds)

es = EarlyStopping(patience=3, verbose=1, min_delta=0.001, monitor='F1_score_val', mode='max', restore_best_weights=True)
model.compile(loss='binary_crossentropy', optimizer='adam')
model.fit(x_train,y_train, epochs=10, 
          callbacks=[F1History(train=(x_train,y_train),validation=(x_val,y_val)),es])

вывод print:

Epoch 1/10
1/1 [==============================] - 0s 78ms/step - loss: 0.7453 - F1_score_train: 0.3478 - F1_score_val: 0.4762
Epoch 2/10
1/1 [==============================] - 0s 57ms/step - loss: 0.7448 - F1_score_train: 0.3478 - F1_score_val: 0.4762
Epoch 3/10
1/1 [==============================] - 0s 58ms/step - loss: 0.7444 - F1_score_train: 0.3478 - F1_score_val: 0.4762
Epoch 4/10
1/1 [==============================] - ETA: 0s - loss: 0.7439Restoring model weights from the end of the best epoch.
1/1 [==============================] - 0s 70ms/step - loss: 0.7439 - F1_score_train: 0.3478 - F1_score_val: 0.4762

У меня TF 2.2 и работает без проблем, надеюсь это поможет

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...