54

I am building a multi-class classifier with Keras 2.02 (with Tensorflow backend),and I do not know how to calculate precision and recall in Keras. Please help me.

Jimmy Du
  • 545
  • 1
  • 4
  • 6
  • I noticed from this page: https://ja.ojit.com/so/tensorflow/3439045 that you were able to use non standard losses such as precision_recall_auc_loss() with Keras. I've tried but failed to reuse the code snippet you provide. Would it be possible to publish a more complete example code? – Eric Chassande-Mottin Feb 10 '21 at 20:30

5 Answers5

66

Python package keras-metrics could be useful for this (I'm the package's author).

import keras
import keras_metrics

model = models.Sequential()
model.add(keras.layers.Dense(1, activation="sigmoid", input_dim=2))
model.add(keras.layers.Dense(1, activation="softmax"))

model.compile(optimizer="sgd",
              loss="binary_crossentropy",
              metrics=[keras_metrics.precision(), keras_metrics.recall()])

UPDATE: Starting with Keras version 2.3.0, such metrics as precision, recall, etc. are provided within library distribution package.

The usage is the following:

model.compile(optimizer="sgd",
              loss="binary_crossentropy",
              metrics=[keras.metrics.Precision(), keras.metrics.Recall()])
Yasha Bubnov
  • 886
  • 1
  • 9
  • 8
  • 4
    It might be a good idea to mention that you are the author of this package, both in the interest of full disclosure of your affiliation, and to lend your answer more credibility. – Mihai Chelaru May 26 '18 at 17:00
  • 1
    @Dref360 says "As of Keras 2.0, precision and recall were removed from the master branch." -- so is this the metrics that have been removed, or can you still use keras_metric with Keras 2.0? – J.D Jun 01 '18 at 07:26
  • 3
    it gives precision and recall = 0.000 in Keras 2.2.2 and keras-metrics 0.0.5. I used the same code as above. But when I use the solution posted by @Christian, it return values. – DrGeneral Nov 21 '18 at 07:43
  • 1
    @DrGeneral, could you, please, provide a model and the training data you use, so I could validate what's wrong with the implementation. I recall, there is a calculation issue with _Tensorflow_ backend with version ```<1.8.0```. – Yasha Bubnov Nov 22 '18 at 07:48
  • @YashaBubnov, ohh. I'm using tensorflow 1.5 and my model is a LSTM with sigmoid activation at the last layer. I'm glad to help you but I'm not allowed to share company's data. sorry :) – DrGeneral Nov 22 '18 at 10:41
  • calling the `tf.global_variables_initializer` breaks everything including keras builtin accuracy either `K.get_session().run(tf.global_variables_initializer())` or using `with` – Muayyad Alsadi Mar 03 '19 at 15:14
  • Hi. How do you access these metrics?? I have them printing out which is nice, but I need them in separate variables. I've tried accessing them via dot syntax or through the history but no luck. Thank you. – Edison Jul 14 '22 at 11:28
36

As of Keras 2.0, precision and recall were removed from the master branch. You will have to implement them yourself. Follow this guide to create custom metrics : Here.

Precision and recall equation can be found Here

Or reuse the code from keras before it was removed Here.

There metrics were remove because they were batch-wise so the value may or may not be correct.

Dref360
  • 628
  • 5
  • 9
  • 1
    Thanks for your answer. But when I reuse the code from keras before it was removed, I get 0 for both of precision and recall value. Why? – Jimmy Du Apr 06 '17 at 09:42
  • 20
    Why have they been removed? – Alex Oct 05 '17 at 11:22
  • 4
    Please make sure that your answer is useful even if the links break. – Martin Thoma Oct 16 '17 at 06:16
  • 1
    Explanation for removing can be found here, with a solution to compute it with a callback implementation: https://github.com/keras-team/keras/issues/5794 – RomaneG Mar 20 '18 at 13:47
  • Is it possible to adapt the code and compute the respective metrics across the whole dataset, i.e. train and test? I see there are some solutions provided in the github thread but I cannot tell if they are, again, batch-wise or across the entire dataset – TheDude May 06 '19 at 10:05
7

My answer is based on the comment of Keras GH issue. It calculates validation precision and recall at every epoch for a onehot-encoded classification task. Also please look at this SO answer to see how it can be done with keras.backend functionality.

import keras as keras
import numpy as np
from keras.optimizers import SGD
from sklearn.metrics import precision_score, recall_score

model = keras.models.Sequential()
# ...
sgd = SGD(lr=0.001, momentum=0.9)
model.compile(optimizer=sgd, loss='categorical_crossentropy', metrics=['accuracy'])


class Metrics(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self._data = []

    def on_epoch_end(self, batch, logs={}):
        X_val, y_val = self.validation_data[0], self.validation_data[1]
        y_predict = np.asarray(model.predict(X_val))

        y_val = np.argmax(y_val, axis=1)
        y_predict = np.argmax(y_predict, axis=1)

        self._data.append({
            'val_recall': recall_score(y_val, y_predict),
            'val_precision': precision_score(y_val, y_predict),
        })
        return

    def get_data(self):
        return self._data


metrics = Metrics()
history = model.fit(X_train, y_train, epochs=100, validation_data=(X_val, y_val), callbacks=[metrics])
metrics.get_data()
vogdb
  • 4,669
  • 3
  • 27
  • 29
  • Need to do `recall_score(y_val, y_predict, average=None)` and `precision_score(y_val, y_predict, average=None)` here. The default is `average=binary` and you'll get an error because this is multi-class classification. – Ethan Chen Mar 01 '19 at 01:03
  • Your code use an external reference of the object "model". – Gianmario Spacagna Jan 28 '20 at 10:09
  • @EthanChen do you mean after I have the history I can access the same precision, recall, auc, and accuracy and store them in separate variables by doing `recall=recall_score(y_val, y_predict, average=None)`? Same with all 4 metrics? Says metrics is not defined even though I already have the import. – Edison Jul 14 '22 at 11:32
2

Use Scikit Learn framework for this.

from sklearn.metrics import classification_report

history = model.fit(x_train, y_train, batch_size=32, epochs=10, verbose=1, validation_data=(x_test, y_test), shuffle=True)
pred = model.predict(x_test, batch_size=32, verbose=1)
predicted = np.argmax(pred, axis=1)
report = classification_report(np.argmax(y_test, axis=1), predicted)
print(report)

This blog is very useful.

fuenfundachtzig
  • 7,952
  • 13
  • 62
  • 87
  • 2
    This is something you do at the end. But to get these values for every epoch and see how they evolve? – yannis Apr 28 '18 at 08:21
2

This thread is a little stale, but just in case it'll help someone landing here. If you are willing to upgrade to Keras v2.1.6, there has been a lot of work on getting stateful metrics to work though there seems to be more work that is being done (https://github.com/keras-team/keras/pull/9446).

Anyway, I found the best way to integrate precision/recall was using the custom metric that subclasses Layer, shown by example in BinaryTruePositives.

For recall, this would look like:

class Recall(keras.layers.Layer):
    """Stateful Metric to count the total recall over all batches.

    Assumes predictions and targets of shape `(samples, 1)`.

    # Arguments
        name: String, name for the metric.
    """

    def __init__(self, name='recall', **kwargs):
        super(Recall, self).__init__(name=name, **kwargs)
        self.stateful = True

        self.recall = K.variable(value=0.0, dtype='float32')
        self.true_positives = K.variable(value=0, dtype='int32')
        self.false_negatives = K.variable(value=0, dtype='int32')
    def reset_states(self):
        K.set_value(self.recall, 0.0)
        K.set_value(self.true_positives, 0)
        K.set_value(self.false_negatives, 0)

    def __call__(self, y_true, y_pred):
        """Computes the number of true positives in a batch.

        # Arguments
            y_true: Tensor, batch_wise labels
            y_pred: Tensor, batch_wise predictions

        # Returns
            The total number of true positives seen this epoch at the
                completion of the batch.
        """
        y_true = K.cast(y_true, 'int32')
        y_pred = K.cast(K.round(y_pred), 'int32')

        # False negative calculations
        y_true = K.cast(y_true, 'int32')
        y_pred = K.cast(K.round(y_pred), 'int32')
        false_neg = K.cast(K.sum(K.cast(K.greater(y_pred, y_true), 'int32')), 'int32')
        current_false_neg = self.false_negatives * 1
        self.add_update(K.update_add(self.false_negatives,
                                     false_neg),
                        inputs=[y_true, y_pred])
        # True positive  calculations
        correct_preds = K.cast(K.equal(y_pred, y_true), 'int32')
        true_pos = K.cast(K.sum(correct_preds * y_true), 'int32')
        current_true_pos = self.true_positives * 1
        self.add_update(K.update_add(self.true_positives,
                                     true_pos),
                        inputs=[y_true, y_pred])
        # Combine
        recall = (K.cast(self.true_positives, 'float32') / (K.cast(self.true_positives, 'float32') + K.cast(self.false_negatives, 'float32') + K.cast(K.epsilon(), 'float32')))
        self.add_update(K.update(self.recall,
                                     recall),
                        inputs=[y_true, y_pred])

        return recall   
vsocrates
  • 172
  • 13