Evaluating Models#

Available in: Open SourceEnterprise

FiftyOne provides a variety of builtin methods for evaluating your model predictions, including regressions, classifications, detections, polygons, instance and semantic segmentations, on both image and video datasets.

When you evaluate a model in FiftyOne, you get access to the standard aggregate metrics such as classification reports, confusion matrices, and PR curves for your model. In addition, FiftyOne can also record fine-grained statistics like accuracy and false positive counts at the sample-level, which you can interactively explore in the App to diagnose the strengths and weaknesses of your models on individual data samples.

Sample-level analysis often leads to critical insights that will help you improve your datasets and models. For example, viewing the samples with the most false positive predictions can reveal errors in your annotation schema. Or, viewing the cluster of samples with the lowest accuracy can reveal gaps in your training dataset that you need to address in order to improve your model’s performance. A key goal of FiftyOne is to help you uncover these insights on your data!

Note

Check out the tutorials page for in-depth walkthroughs of evaluating various types of models with FiftyOne, or the Model Evaluation Guide for a complete step-by-step walkthrough.

Overview#

FiftyOne’s evaluation methods are conveniently exposed as methods on all Dataset and DatasetView objects, which means that you can evaluate entire datasets or specific views into them via the same syntax.

Let’s illustrate the basic workflow by loading the quickstart dataset and analyzing the object detections in its predictions field using the evaluate_detections() method:

 1import fiftyone as fo
 2import fiftyone.zoo as foz
 3
 4dataset = foz.load_zoo_dataset("quickstart")
 5
 6# Evaluate the objects in the `predictions` field with respect to the
 7# objects in the `ground_truth` field
 8results = dataset.evaluate_detections(
 9    "predictions",
10    gt_field="ground_truth",
11    eval_key="eval",
12)
13
14session = fo.launch_app(dataset)

Per-class metrics#

You can also retrieve and interact with evaluation results via the SDK.

Running an evaluation returns an instance of a task-specific subclass of EvaluationResults that provides a handful of methods for generating aggregate statistics about your dataset.

1# Get the 10 most common classes in the dataset
2counts = dataset.count_values("ground_truth.detections.label")
3classes = sorted(counts, key=counts.get, reverse=True)[:10]
4
5# Print a classification report for the top-10 classes
6results.print_report(classes=classes)
               precision    recall  f1-score   support

       person       0.45      0.74      0.56       783
         kite       0.55      0.72      0.62       156
          car       0.12      0.54      0.20        61
         bird       0.63      0.67      0.65       126
       carrot       0.06      0.49      0.11        47
         boat       0.05      0.24      0.08        37
    surfboard       0.10      0.43      0.17        30
traffic light       0.22      0.54      0.31        24
     airplane       0.29      0.67      0.40        24
      giraffe       0.26      0.65      0.37        23

    micro avg       0.32      0.68      0.44      1311
    macro avg       0.27      0.57      0.35      1311
 weighted avg       0.42      0.68      0.51      1311

Note

For details on micro, macro, and weighted averaging, see the sklearn.metrics documentation.

Per-sample metrics#

In addition to standard aggregate metrics, when you pass an eval_key parameter to the evaluation routine, FiftyOne will populate helpful task-specific information about your model’s predictions on each sample, such as false negative/positive counts and per-sample accuracies.

Continuing with our example, let’s use dataset views and the FiftyOne App to leverage these sample metrics to investigate the samples with the most false positive predictions in the dataset:

 1import fiftyone as fo
 2from fiftyone import ViewField as F
 3
 4# Create a view that has samples with the most false positives first, and
 5# only includes false positive boxes in the `predictions` field
 6view = (
 7    dataset
 8    .sort_by("eval_fp", reverse=True)
 9    .filter_labels("predictions", F("eval") == "fp")
10)
11
12# Visualize results in the App
13session = fo.launch_app(view=view)
quickstart-evaluate-detections


Notice anything wrong? The sample with the most false positives is a plate of carrots where the entire plate has been boxed as a single example in the ground truth while the model is generating predictions for individual carrots!

If you’re familiar with COCO format (which is recognized by evaluate_detections() by default), you’ll notice that the issue here is that the iscrowd attribute of this ground truth annotation has been incorrectly set to 0. Resolving mistakes like these will provide a much more accurate picture of the real performance of a model.

Confusion matrices#

Note

The easiest way to work with confusion matrices in FiftyOne is via the Model Evaluation panel!

When you use evaluation methods such as evaluate_detections() that support confusion matrices, you can use the plot_confusion_matrix() method to render responsive plots that can be attached to App instances to interactively explore specific cases of your model’s performance:

1# Plot confusion matrix
2plot = results.plot_confusion_matrix(classes=classes)
3plot.show()
4
5# Connect to session
6session.plots.attach(plot)
detection-evaluation

In this setup, you can click on individual cells of the confusion matrix to select the corresponding ground truth and/or predicted objects in the App. For example, if you click on a diagonal cell of the confusion matrix, you will see the true positive examples of that class in the App.

Likewise, whenever you modify the Session’s view, either in the App or by programmatically setting session.view, the confusion matrix is automatically updated to show the cell counts for only those objects that are included in the current view.

Analyzing scenarios#

Available in: Open SourceEnterprise

Note

Did you know? You can create and analyze model evaluation scenarios in the App via the Scenario Analysis tab.

The use_subset() method allows you to evaluate the performance of your model under specific scenarios, i.e., subsets of the overall dataset on which evaluation was performed.

Consider the following example:

 1import fiftyone as fo
 2import fiftyone.zoo as foz
 3import fiftyone.utils.random as four
 4from fiftyone import ViewField as F
 5
 6dataset = foz.load_zoo_dataset("quickstart")
 7
 8four.random_split(dataset, {"sunny": 0.7, "cloudy": 0.2, "rainy": 0.1})
 9
10counts = dataset.count_values("ground_truth.detections.label")
11classes = sorted(counts, key=counts.get, reverse=True)[:5]
12
13dataset.save_view("take100", dataset.take(100))
14
15results = dataset.evaluate_detections(
16    "predictions",
17    gt_field="ground_truth",
18    eval_key="eval",
19)

By default, invoking methods on an EvaluationResults instance reports statistics across the entire evaluation:

1# Full results
2results.print_report(classes=classes)
              precision    recall  f1-score   support

      person       0.52      0.94      0.67       716
        kite       0.59      0.88      0.71       140
         car       0.18      0.80      0.29        61
        bird       0.65      0.78      0.71       110
      carrot       0.09      0.74      0.16        47

   micro avg       0.42      0.90      0.57      1074
   macro avg       0.41      0.83      0.51      1074
weighted avg       0.51      0.90      0.64      1074

However, you can use use_subset() to analyze the performance of the model on specific subsets of interest:

1# Sunny samples
2subset_def = dict(type="field", field="tags", value="sunny")
3with results.use_subset(subset_def):
4    results.print_report(classes=classes)
              precision    recall  f1-score   support

      person       1.00      0.93      0.96       495
        kite       1.00      0.90      0.95        62
         car       1.00      0.69      0.81        35
        bird       1.00      0.78      0.88       104
      carrot       1.00      0.69      0.82        36

   micro avg       1.00      0.88      0.94       732
   macro avg       1.00      0.80      0.88       732
weighted avg       1.00      0.88      0.94       732

Refer to use_subset() and get_subset_view() for a complete description of the supported syntax for defining subsets to analyze.

Managing evaluations#

When you run an evaluation with an eval_key argument, the evaluation is recorded on the dataset and you can retrieve information about it later, rename it, delete it (along with any modifications to your dataset that were performed by it), and retrieve the view that you evaluated on using the following methods on your dataset:

The example below demonstrates the basic interface:

 1# List evaluations you've run on a dataset
 2dataset.list_evaluations()
 3# ['eval']
 4
 5# Print information about an evaluation
 6print(dataset.get_evaluation_info("eval"))
 7
 8# Load existing evaluation results and use them
 9results = dataset.load_evaluation_results("eval")
10results.print_report()
11
12# Rename the evaluation
13# This will automatically rename any evaluation fields on your dataset
14dataset.rename_evaluation("eval", "still_eval")
15
16# Delete the evaluation
17# This will remove any evaluation data that was populated on your dataset
18dataset.delete_evaluation("still_eval")

Model Evaluation panel#

Available in: Open SourceEnterprise

When you load a dataset in the App that contains one or more evaluations, you can open the Model Evaluation panel to visualize and interactively explore the evaluation results in the App:

model-evaluation-compare

Note

Did you know? With FiftyOne Enterprise you can execute model evaluations natively from the App in the background while you work.