Comment définir une métrique de performance personnalisée dans Keras?

11

J'ai essayé de définir une fonction métrique personnalisée (F1-Score) dans Keras (backend Tensorflow) en fonction de ce qui suit:

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

Jusqu'à présent, tout va bien, mais quand j'essaie de l'appliquer dans la compilation de modèles:

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

cela donne une erreur:

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.

Quel est le problème ici? Le fait que mes entrées de fonction f1_score ne sont pas des tableaux Tensorflow? Si oui, où / comment puis-je les convertir correctement?

Hendrik
la source
Hmm, le message d'erreur implique que vous obtenez des objets tenseurs. Vous avez peut-être besoin de l'évaluation après tout! Si c'est le cas, votre erreur sera probablement utilisée evallorsque vous voulez direeval()
Neil Slater

Réponses:

16

Vous devez utiliser les fonctions backend de Keras . Malheureusement, ils ne prennent pas en charge l' &opérateur-, de sorte que vous devez construire une solution de contournement: nous générons des matrices de la dimension batch_size x 3, où (par exemple pour un vrai positif) la première colonne est le vecteur de vérité au sol, la seconde la prédiction réelle et la troisième est sorte de colonne d'aide pour les étiquettes, qui ne contient que des vrais positifs. Ensuite, nous vérifions quelles instances sont des instances positives, prédites comme positives et l'assistant d'étiquette est également positif. Ce sont les vrais points positifs.

Nous pouvons faire cet analogue avec de faux positifs, de faux négatifs et de vrais négatifs avec quelques calculs inverses des étiquettes.

Votre métrique f1 peut ressembler à ceci:

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))

Puisque la calculatrice Keras-backend retourne nan pour la division par zéro, nous n'avons pas besoin de l'instruction if-else-pour l'instruction return.

Edit: J'ai trouvé une assez bonne idée pour une implémentation exacte. Le problème avec notre première approche est qu'elle est seulement "approximée", car elle est calculée par lots et ensuite moyennée. On pourrait également calculer cela après chaque époque avec le keras.callbacks. Veuillez trouver l'idée ici: https://github.com/fchollet/keras/issues/5794

Un exemple de mise en œuvre serait:

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

Pour que le réseau appelle cette fonction, il vous suffit de l'ajouter à vos rappels comme

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),
)

Ensuite, vous pouvez simplement accéder aux membres de la metricsvariable.

pexmar
la source
3
Merci, cela a déjà été vraiment utile. Savez-vous comment intégrer les mesures personnalisées dans un rappel de tensorboard afin qu'elles puissent être surveillées pendant la formation?
N.Kaiser