Come definire una metrica delle prestazioni personalizzata in Keras?


11

Ho provato a definire una funzione metrica personalizzata (punteggio F1) in Keras (backend Tensorflow) in base a quanto segue:

def f1_score(tags, predicted):

    tags = set(tags)
    predicted = set(predicted)

    tp = len(tags & predicted)
    fp = len(predicted) - tp 
    fn = len(tags) - tp

    if tp>0:
        precision=float(tp)/(tp+fp)
        recall=float(tp)/(tp+fn)
        return 2*((precision*recall)/(precision+recall))
    else:
        return 0

Fin qui tutto bene, ma quando provo ad applicarlo nella compilazione del modello:

model1.compile(loss="binary_crossentropy", optimizer=Adam(), metrics=[f1_score])

dà errore:

TypeError                                 Traceback (most recent call last)
<ipython-input-85-4eca4def003f> in <module>()
      5 model1.add(Dense(output_dim=10, activation="sigmoid"))
      6 
----> 7 model1.compile(loss="binary_crossentropy", optimizer=Adam(), metrics=[f1_score])
      8 
      9 h=model1.fit(X_train, Y_train, batch_size=500, nb_epoch=5, verbose=True, validation_split=0.1)

/home/buda/anaconda2/lib/python2.7/site-packages/keras/models.pyc in compile(self, optimizer, loss, metrics, sample_weight_mode, **kwargs)
    522                            metrics=metrics,
    523                            sample_weight_mode=sample_weight_mode,
--> 524                            **kwargs)
    525         self.optimizer = self.model.optimizer
    526         self.loss = self.model.loss

/home/buda/anaconda2/lib/python2.7/site-packages/keras/engine/training.pyc in compile(self, optimizer, loss, metrics, loss_weights, sample_weight_mode, **kwargs)
    664                 else:
    665                     metric_fn = metrics_module.get(metric)
--> 666                     self.metrics_tensors.append(metric_fn(y_true, y_pred))
    667                     if len(self.output_names) == 1:
    668                         self.metrics_names.append(metric_fn.__name__)

<ipython-input-84-b8a5752b6d55> in f1_score(tags, predicted)
      4     #tf.convert_to_tensor(img.eval())
      5 
----> 6     tags = set(tags)
      7     predicted = set(predicted)
      8 

/home/buda/anaconda2/lib/python2.7/site-packages/tensorflow/python/framework/ops.pyc in __iter__(self)
    493       TypeError: when invoked.
    494     """
--> 495     raise TypeError("'Tensor' object is not iterable.")
    496 
    497   def __bool__(self):

TypeError: 'Tensor' object is not iterable.

Qual è il problema qui? Il fatto che gli input della mia funzione f1_score non siano array Tensorflow? In tal caso, dove / come posso convertirli correttamente?


Hmm, il messaggio di errore implica che stai ottenendo oggetti tensore. Forse hai bisogno di eval dopo tutto! Se è così, è probabile che il tuo errore stia usando evalquando vuoi direeval()
Neil Slater

Risposte:


16

Devi usare le funzioni di backend di Keras . Sfortunatamente non supportano l' &operatore, quindi è necessario creare una soluzione alternativa: generiamo matrici della dimensione batch_size x 3, dove (ad es. Per il vero positivo) la prima colonna è il vettore di verità di base, la seconda la previsione effettiva e la terza è tipo di una colonna helper etichetta, che contiene nel caso di quelli veri solo positivi. Quindi controlliamo quali istanze sono istanze positive, sono previste come positive e anche l'helper etichetta è positivo. Questi sono i veri aspetti positivi.

Possiamo realizzare questo analogo con falsi positivi, falsi negativi e veri negativi con alcuni calcoli inversi delle etichette.

La metrica f1 può apparire come segue:

def f1_score(y_true, y_pred):
    """
    f1 score

    :param y_true:
    :param y_pred:
    :return:
    """
    tp_3d = K.concatenate(
        [
            K.cast(y_true, 'bool'),
            K.cast(K.round(y_pred), 'bool'),
            K.cast(K.ones_like(y_pred), 'bool')
        ], axis=1
    )

    fp_3d = K.concatenate(
        [
            K.cast(K.abs(y_true - K.ones_like(y_true)), 'bool'),
            K.cast(K.round(y_pred), 'bool'),
            K.cast(K.ones_like(y_pred), 'bool')
        ], axis=1
    )

    fn_3d = K.concatenate(
        [
            K.cast(y_true, 'bool'),
            K.cast(K.abs(K.round(y_pred) - K.ones_like(y_pred)), 'bool'),
            K.cast(K.ones_like(y_pred), 'bool')
        ], axis=1
    )

    tp = K.sum(K.cast(K.all(tp_3d, axis=1), 'int32'))
    fp = K.sum(K.cast(K.all(fp_3d, axis=1), 'int32'))
    fn = K.sum(K.cast(K.all(fn_3d, axis=1), 'int32'))

    precision = tp / (tp + fp)
    recall = tp / (tp + fn)
    return 2 * ((precision * recall) / (precision + recall))

Poiché il calcolatore back-end di Keras restituisce nan per divisione per zero, non abbiamo bisogno dell'istruzione if-else per l'istruzione return.

Modifica: ho trovato una buona idea per un'implementazione esatta. Il problema con il nostro primo approccio è che è solo "approssimato", poiché viene calcolato in modo batch e successivamente mediato. Si potrebbe anche calcolare questo dopo ogni epoca con la keras.callbacks. Trova l'idea qui: https://github.com/fchollet/keras/issues/5794

Un'implementazione di esempio sarebbe:

import keras
import numpy as np
import sklearn.metrics as sklm


class Metrics(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.confusion = []
        self.precision = []
        self.recall = []
        self.f1s = []
        self.kappa = []
        self.auc = []

    def on_epoch_end(self, epoch, logs={}):
        score = np.asarray(self.model.predict(self.validation_data[0]))
        predict = np.round(np.asarray(self.model.predict(self.validation_data[0])))
        targ = self.validation_data[1]

        self.auc.append(sklm.roc_auc_score(targ, score))
        self.confusion.append(sklm.confusion_matrix(targ, predict))
        self.precision.append(sklm.precision_score(targ, predict))
        self.recall.append(sklm.recall_score(targ, predict))
        self.f1s.append(sklm.f1_score(targ, predict))
        self.kappa.append(sklm.cohen_kappa_score(targ, predict))

        return

Per fare in modo che la rete chiami questa funzione è sufficiente aggiungerla a callback come

metrics = Metrics()
model.fit(
    train_instances.x,
    train_instances.y,
    batch_size,
    epochs,
    verbose=2,
    callbacks=[metrics],
    validation_data=(valid_instances.x, valid_instances.y),
)

Quindi puoi semplicemente accedere ai membri della metricsvariabile.


3
Grazie, questo è già stato davvero utile. Sai come incorporare le metriche personalizzate in una callback tensorboard in modo che possano essere monitorate durante l'allenamento?
N.Kaiser,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.