Supercharge Your Annotation Workflow with Active Learning
Tags: computervision, ai, machinelearning, datascience
Published: 2024-03-13 20:39:46

_Author: [Jacob Marks](https://www.linkedin.com/in/jacob-marks/) (Machine Learning Engineer at [Voxel51](https://voxel51.com/))_

## FiftyOne Active Learning Plugin Can Help You Label Data Faster! 

Welcome to week seven of Ten Weeks of Plugins. During these ten weeks, we will be building a FiftyOne Plugin (or multiple!) each week and sharing the lessons learned!


If you’re new to them, FiftyOne Plugins provide a flexible mechanism for anyone to extend the functionality of their FiftyOne App. You may find the following resources helpful:

- [FiftyOne Plugins Repo](https://github.com/voxel51/fiftyone-plugins)
- [FiftyOne Plugin Docs](https://docs.voxel51.com/plugins/index.html#downloading-plugins)
- [Plugins Channel](https://fiftyone-users.slack.com/archives/plugins) in the [FiftyOne Community Slack](https://slack.voxel51.com/)

What we’ve built so far:

Week 0: 🌩️ [Image Quality Issues](https://github.com/jacobmarks/image-quality-issues) & 📈 [Concept Interpolation](https://github.com/jacobmarks/concept-interpolation)
Week 1: 🎨 [AI Art Gallery](https://github.com/jacobmarks/ai-art-gallery) & [Twilio Automation](https://github.com/jacobmarks/twilio-automation-plugin)
Week 2: ❓[Visual Question Answering](https://github.com/jacobmarks/vqa-plugin)
Week 3: 🎥 [YouTube Player Panel](https://github.com/jacobmarks/fiftyone-youtube-panel-plugin)
Week 4: 🪞[Image Deduplication](https://github.com/jacobmarks/image-deduplication-plugin)
Week 5: 👓[Optical Character Recognition (OCR)](https://github.com/jacobmarks/pytesseract-ocr-plugin) & 🔑[Keyword Search](https://github.com/jacobmarks/keyword-search-plugin)
Week 6: 🎭[Zero-shot Prediction](https://github.com/jacobmarks/zero-shot-prediction-plugin)

Ok, let’s dive into this week’s FiftyOne Plugin — [Active Learning](https://github.com/jacobmarks/active-learning-plugin)!

## Active Learning 🏃🎓🔄

When it comes to machine learning, one of the most time-consuming and costly steps is data annotation. In the realm of computer vision, labeling images or videos can be an incredibly laborious task, often requiring a team of annotators and hours of meticulous work to generate high-quality labels. Even with a well-labeled dataset, the effort doesn't stop there: model training, evaluation, and then re-annotation in case of inaccuracies are all parts of an ongoing cycle.

What if you could make this iterative process smarter and more efficient? Enter [Active Learning](https://en.wikipedia.org/wiki/Active_learning_(machine_learning))—a paradigm that iteratively selects the most "informative" or "ambiguous" examples for labeling, thereby reducing the amount of manual annotation needed. In practical terms, this means your model gets better, faster, and with fewer labeled samples.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0f57pj6mu5kw2g669x48.jpeg)

This FiftyOne plugin brings Active Learning to your computer vision data, allowing you to integrate this accelerant directly into your annotation workflow. Now you can prioritize, query, and annotate the most crucial data points, all within the FiftyOne App—no coding necessary.

The best part? You can use this in tandem with your traditional annotation service providers (via FiftyOne’s integrations with [CVAT](https://docs.voxel51.com/integrations/cvat.html), [Labelbox](https://docs.voxel51.com/integrations/labelbox.html) and [Label Studio](https://docs.voxel51.com/integrations/labelstudio.html)), or even with last week’s [Zero-shot Prediction plugin](https://github.com/jacobmarks/zero-shot-prediction-plugin)!

Read on to learn how you can leverage the Active Learning Plugin to build high-quality models with less manual effort.

## Plugin Overview & Functionality

For the seventh week of [10 Weeks of Plugins](https://voxel51.com/blog/category/computer-vision-plugins/), I built an Active Learning Plugin. This plugin leverages the tried and tested modular active learning framework, [modAL](https://modal-python.readthedocs.io/en/latest/), to help you expedite your data labeling processes.

modAL is an active learning framework built on top of [Sci-kit learn](https://scikit-learn.org/stable/index.html). It allows you to apply a variety of active learning strategies to models from Sci-kit learn, including solitary “estimators” like the `KNeighborsClassier`, and ensembles like the `RandomForestClassifier`, and even to create [“committees” out of estimators](https://modal-python.readthedocs.io/en/latest/content/models/Committee.html), so you can estimate label uncertainty by comparing multiple hypotheses about the underlying data.

All of the necessary functionality from modAL is wrapped into FiftyOne Python [operators](https://docs.voxel51.com/plugins/index.html#operators), so that you can reap the rewards of Active Learning without writing a line of code.

💡At present, this plugin only supports Active Labeling for Classification tasks. However, it could be extended to [object detection](https://arxiv.org/pdf/2004.04699.pdf), [semantic segmentation](https://openaccess.thecvf.com/content_CVPR_2020/html/Siddiqui_ViewAL_Active_Learning_With_Viewpoint_Entropy_for_Semantic_Segmentation_CVPR_2020_paper.html), and other computer vision tasks.

The plugin has three operators:

- `create_learner`:  creates an active learning model and environment
- `query_learner`: queries the active learning model for samples to label
- `update_learner_predictions`: teaches the active learning model the previous queries, and updates the model’s predictions across the dataset

Because it uses caching, and it is _not_ a JavaScript plugin, it will only work with the open source version of FiftyOne.

### Creating the Active Learner

To create the Active Learner, your dataset must have:

1. Initial labels
2. Input features for the classifiers

First, for the purposes of illustration, I have taken a subset of the [Caltech101](https://docs.voxel51.com/user_guide/dataset_zoo/datasets.html#caltech-101) dataset with the labels `airplane`, `Motorbike`, `helicopter`, shuffled the data, and deleted the ground truth labels. In this blog post, we will use Active Learning to label this dataset. If you want to follow along at home, you can do so as follows: 

```
import fiftyone as fo
import fiftyone.zoo as foz
import fiftyone.brain as fob
from fiftyone import ViewField as F

caltech101  = foz.load_zoo_dataset("caltech101")

## create sub-dataset
dataset = caltech101.filter_labels(
    "ground_truth", 
    F("label").is_in(["airplanes", "Motorbikes", "helicopter"])
    ).shuffle().clone(name = "caltech3")
dataset.delete_sample_field("ground_truth")
dataset.persistent = True

session = fo.launch_app(dataset)
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ie9o19kpajczv1l39k3.png)

If you press " `` ` `` " to open up the operators list and select any of this plugin’s three operators, you will be met with warning messages like this:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m8buj7omyvfyfusazj6f.gif)

This is because you must have a set of labels to use to initialize the learner. We can either use tags, or we can generate some zero-shot classification predictions with our Week 6 [Zero-shot Prediction Plugin](https://github.com/jacobmarks/zero-shot-prediction-plugin), and use these as a starting point:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wzfu511y25rs5dfswwjt.gif)

Regardless of which approach we take to initial labels, the only requirement is that we have at least one example from each class we are trying to classify. You do _not_ need to run zero-shot labeling on the entire dataset, or tag all samples in the dataset.

💡In practice, typically a few examples from each class is good enough to start.

The second requirement is that the dataset has at least one field that can be used as an input feature to the “estimators” in the Active Learner. This could be any `fo.FloatField` (float-valued field) or any `fo.VectorField` (one-dimensional numpy array). If you want to use multiple fields as features, they will be concatenated into a one-dimensional feature vector.

If you don’t have any candidate feature fields, a good starting point is to use model [embeddings](https://voxel51.com/blog/fiftyone-computer-vision-embeddings-tips-and-tricks-mar-31-2023/).You can compute embeddings in Python by loading a model from the [FiftyOne Model Zoo](https://docs.voxel51.com/user_guide/model_zoo/index.html) and running  `compute_embeddings()`. For this walkthrough, I’ll compute embeddings using one more semantic model, [CLIP](https://docs.voxel51.com/user_guide/model_zoo/models.html#clip-vit-base32-torch), and one model trained more to represent data on the level of pixels and patches, [MobileNet](https://docs.voxel51.com/user_guide/model_zoo/models.html#mobilenet-v2-imagenet-torch):

```
mobilenet = foz.load_zoo_model("mobilenet-v2-imagenet-torch")
dataset.compute_embeddings(
    mobilenet, 
    embeddings_field = "mobilenet_embeddings"
    )

clip = foz.load_zoo_model("clip-vit-base32-torch")
dataset.compute_embeddings(
    clip, 
    embeddings_field = "clip_embeddings"
    )
```

This stores the embeddings in the fields `mobilenet_embeddings` and `clip_embeddings` on our samples.

I will also use a few additional scalar properties computed using our [Image Quality Issues Plugin](https://github.com/jacobmarks/image-quality-issues). Here is how the entropy is computed:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x291xncqgyzsqt2jx42u.gif)

The contrast and brightness are computed in analogous fashion.

Now that we have candidate input features on our samples and some initial labels, we can create an active learner!


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1j7czg434aggpr1uq65.gif)


We can choose:

- The field or fields to use as a feature vector
- The label field in which to store predictions
- The default batch size — the number of samples per query
- The `Active Learner`

For the latter of these, we can select from a variety of ensemble strategies, including [Random Forest](https://en.wikipedia.org/wiki/Random_forest), [Gradient Boosting](https://en.wikipedia.org/wiki/Gradient_boosting), [Bagging](https://en.wikipedia.org/wiki/Bootstrap_aggregating), and [AdaBoost](https://en.wikipedia.org/wiki/AdaBoost). When we make this top-level selection, the remainder of the form dynamically updates with appropriate hyperparameter configuration choices. 

Executing this operator creates a modAL `ActiveLearner` that uses an “uncertainty” batch sampling. The execution also invokes the generation of initial predictions, and triggers the reload of the dataset.

### Querying the Active Learner

Now that we have our learner, we can query the learner for the next batch of samples to label. If we’d like, we can override the default query batch size we set earlier:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6e60xr8ans5qy0i1zdb.gif)
In this case, we can see that the model is least confident about some helicopter images, which it has labeled as a plane. This is likely due to the fact that the dataset has far more planes and motorcycles than helicopters, and helicopters are typically more similar to planes than to motorcycles.

We can then tag the mistakes with the correct labels, either within the app, or by sending these samples for reannotation. For the sake of simplicity, we will do so in the app:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bbyfy3jkn2gu199etfj7.gif)

All samples that are not tagged are treated as having the correct labels when used to teach the learner.

### Teaching the Active Learner

After correcting the incorrect query labels, we can update our active learner by “teaching” it this new information:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jo4cxn9qq83hgl1lafmw.gif)

Running this operator updates our active learning model, updates the label field with new predictions, and reloads the app.

If we were to query the learner again, we would see that the samples in this new query are completely different from the samples in the first query:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e13k8trueydh1gmsnss9.gif)

We can go through this process as many times as we need to until we are confident in our labels. Because this uses uncertainty sampling, rather than random sampling, we should converge to a set of accurate labels very efficiently!

### Installing the Plugin

If you haven’t already done so, [install FiftyOne](https://docs.voxel51.com/getting_started/install.html):

```
pip install fiftyone
```
Then you can download this plugin from the command line with:

```
fiftyone plugins download https://github.com/jacobmarks/active-learning-plugin
```
Refresh the FiftyOne App, and you should see the three operators in your operators list when you press the " `` ` `` " key.

You can install the plugin’s requirements (modAL) by running

```
fiftyone plugins requirements @jacobmarks/active-learning-plugin --install
```

## Lessons Learned

The Active Learning plugin is a Python Plugin with the usual structure:

- `_init__.py`: defining operators
- `fiftyone.yml`: registering operators
- `README.md`: explaining the plugin, and giving install instructions
- `assets` folder: storeroom for icons
- `requirements.txt`: list of required Python packages

Additionally, the plugin has an `active_learning.py` file, which implements the active learning logic using modAL.

### Caching is King

This plugin uses the same caching mechanism from the [Keyword Search plugin](https://github.com/jacobmarks/keyword-search-plugin). For keyword search, we were storing a user’s preference, which made the user’s life easier but was not strictly essential. For Active Learning, however, statefulness is critical. The learner is learning, so we need to keep track of its current state, as well as which samples it has seen before.

In theory, this could be done by adding fields onto the dataset to keep track of this information. If you want to implement this version of the Active Learning plugin, I encourage you to do so!! 

### Don’t Reinvent the Wheel

When I set out to make an Active Learning plugin, I initially tried to implement everything from scratch. Sometimes this is necessary. But in other cases, including this one, robust solutions already exist. 

FiftyOne is flexible enough to bend and weave into workflows with tons of existing tools. Rather than spending your time reinventing the wheel, integrate these libraries into your FiftyOne Plugins so you can combine the strengths of other special-purpose libraries with FiftyOne’s data management and visualization infrastructure!

## Conclusion

In the fast-paced world of machine learning and computer vision, efficiency and effectiveness are of the essence. By intelligently selecting the most crucial samples for labeling, this plugin will help you save time, reduce costs, and enhance the overall quality of your models. Happy learning!

Stay tuned over the remaining weeks in the _Ten Weeks of FiftyOne Plugins_ while we continue to pump out a killer lineup of plugins! You can track our journey in our [ten-weeks-of-plugins repo](https://github.com/jacobmarks/ten-weeks-of-plugins) — and I encourage you to fork the repo and join me on this journey!