Wie definiere ich eine benutzerdefinierte Leistungsmetrik in Keras?

11

Ich habe versucht, eine benutzerdefinierte metrische Funktion (F1-Score) in Keras (Tensorflow-Backend) wie folgt zu definieren:

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

So weit, so gut, aber wenn ich versuche, es bei der Modellzusammenstellung anzuwenden:

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

es gibt Fehler:

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.

Was ist das Problem hier? Die Tatsache, dass meine f1_score-Funktionseingänge keine Tensorflow-Arrays sind? Wenn ja, wo / wie kann ich sie richtig konvertieren?

Hendrik
quelle
Hmm, die Fehlermeldung impliziert, dass Sie Tensorobjekte erhalten. Vielleicht brauchen Sie doch die Bewertung! Wenn ja, wird Ihr Fehler wahrscheinlich verwendet, evalwenn Sie meineneval()
Neil Slater

Antworten:

16

Sie müssen Keras-Backend-Funktionen verwenden . Leider unterstützen sie den &Operator nicht, so dass Sie eine Problemumgehung erstellen müssen: Wir generieren Matrizen der Dimension batch_size x 3, wobei (z. B. für wahres Positiv) die erste Spalte der Grundwahrheitsvektor ist, die zweite die tatsächliche Vorhersage und die dritte Art einer Label-Helfer-Spalte, die im Falle von wirklich positiven nur solche enthält. Dann prüfen wir, welche Instanzen positive Instanzen sind, werden als positiv vorhergesagt und der Label-Helfer ist ebenfalls positiv. Das sind die wahren positiven Aspekte.

Wir können dieses Analogon mit falsch positiven, falsch negativen und wahr negativen Ergebnissen mit einigen umgekehrten Berechnungen der Bezeichnungen machen.

Ihre f1-Metrik sieht möglicherweise wie folgt aus:

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

Da der Keras-Backend-Rechner nan zur Division durch Null zurückgibt, benötigen wir die if-else-Anweisung nicht für die return-Anweisung.

Edit: Ich habe eine ziemlich gute Idee für eine genaue Implementierung gefunden. Das Problem bei unserem ersten Ansatz ist, dass er nur "angenähert" wird, da er chargenweise berechnet und anschließend gemittelt wird. Man könnte dies auch nach jeder Epoche mit dem keras.callbacks berechnen . Die Idee finden Sie hier: https://github.com/fchollet/keras/issues/5794

Eine beispielhafte Implementierung wäre:

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

Damit das Netzwerk diese Funktion aufruft, fügen Sie sie einfach Ihren Rückrufen wie hinzu

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

Dann können Sie einfach auf die Mitglieder der zugreifen metrics Variablen .

pexmar
quelle
3
Danke, das war schon sehr nützlich. Wissen Sie, wie Sie die benutzerdefinierten Metriken in einen Tensorboard-Rückruf integrieren können, damit sie während des Trainings überwacht werden können?
N.Kaiser