thecocolab / data-imbalance

Evaluating the effect of data balance on different classification metrics
https://doi.org/10.1101/2022.07.18.500262
MIT License
6 stars 0 forks source link
classification classification-metrics machine-learning

Class imbalance should not throw you off balance: Choosing the right classifiers and performance metrics for brain decoding with imbalanced data

Machine learning (ML) is increasingly used in cognitive, computational and clinical neuroscience. The reliable and efficient application of ML requires a sound understanding of its subtleties and limitations. Training ML models on datasets with imbalanced classes is a particularly common problem, and it can have severe consequences if not adequately addressed. With the neuroscience ML user in mind, this paper provides a didactic assessment of the class imbalance problem and illustrates its impact through systematic manipulation of data imbalance ratios in (i) simulated data and (ii) brain data recorded with electroencephalography (EEG) and magnetoencephalography (MEG). Our results illustrate how the widely-used Accuracy (Acc) metric, which measures the overall proportion of successful predictions, yields misleadingly high performances, as class imbalance increases. Because Acc weights the per-class ratios of correct predictions proportionally to class size, it largely disregards the performance on the minority class. A binary classification model that learns to systematically vote for the majority class will yield an artificially high decoding accuracy that directly reflects the imbalance between the two classes, rather than any genuine generalizable ability to discriminate between them. We show that other evaluation metrics such as the Area Under the Curve (AUC) of the Receiver Operating Characteristic (ROC), and the less common Balanced Accuracy (BAcc) metric – defined as the arithmetic mean between sensitivity and specificity, provide more reliable performance evaluations for imbalanced data. Our findings also highlight the robustness of Random Forest (RF), and the benefits of using stratified cross-validation and hyperprameter optimization to tackle data imbalance. Critically, for neuroscience ML applications that seek to minimize overall classification error, we recommend the routine use of BAcc, which in the specific case of balanced data is equivalent to using standard Acc, and readily extends to multi-class settings. Importantly, we present a list of recommendations for dealing with imbalanced data, as well as open-source code to allow the neuroscience community to replicate and extend our observations and explore alternative approaches to coping with imbalanced data.


This repository contains the code to the analysis performed in the accompanying paper (https://doi.org/10.1101/2022.07.18.500262).

Installation

First, create and activate a new Python environment:

conda create -n imbalance python==3.8 -y
conda activate imbalance

Afterwards, clone the repository and install its dependencies as follows:

git clone git@github.com:thecocolab/data-imbalance.git
cd data-imbalance
pip install -e .

Reduced Installation

The -e flag allows you to make changes to the repository without the need to reinstall. If you are only planning to use the package and don't want to make changes you can skip cloning and install via

pip install git+https://github.com/thecocolab/data-imbalance.git

Usage

The Pipeline class is the central piece of code for running experiments. It works with on binary classification datasets using scikit-learn classifiers, automatically testing different dataset sizes, class distributions and evaluates classification performance using a range of metrics. By default, 5-fold cross-validation will be used to increase robustness of the results but it is possible to adjust the cross-validation strategy when configuring the pipeline.

The general workflow of using the Pipeline class looks as follows:

from imbalance.pipeline import Pipeline
from imbalance import viz

# load or generate dataset
x, y, groups = get_dataset(...)

# initialize pipeline
pl = Pipeline(x, y, groups,
              classifiers=["lda", "svm"],
              dataset_balance=[0.1, 0.3, 0.5, 0.7, 0.9])
# fit and evaluate classifiers on dataset configurations
pl.evaluate()

# visualize classification scores
viz.metric_balance(pl)

Note that x and y are the only required arguments to instantiate a Pipeline.

The Pipeline API

After calling pl.evaluate() on a Pipeline object, the nested dictionary pl.scores contains all results. The dictionary is structued in the following way:

pl.scores = {
    # dataset balance ratio
    0.3: {
        # dataset size ratio
        0.5: {
            # classifier name
            "clf1": {
                # metric name
                "metric1": (mean_score-metric1, std_score-metric1, p-value-metric1, permutation_score_metric1),
                "metric2": (mean_score-metric2, std_score-metric2, p-value-metric2, permutation_score_metric2),
                ...
            },
            "clf2": ...
        },
        0.7: ...
    },
    0.5: ...
}