Trabalhando com classes desbalanceadas em problemas Machine Learning

Tatiana Escovedo
10 min readJun 29, 2020

Estratégias para lidar com o desbalanceamento de classes em problemas de classificação binária usando Python e Scikit-Learn

Motivação

Quando trabalhamos em problemas do mundo real, encontramos muitas vezes datasets desbalanceados por natureza, especialmente em problemas relacionados à saúde. Nestes casos, em geral, estamos interessados em detectar a presença de uma doença (como por exemplo, câncer de mama ou diabetes). Entretanto, pelo fato desta característica de interesse ser minoria na população, teremos inevitavelmente um dataset desbalanceado.

Neste contexto, na maior parte das vezes estamos mais interessados na classe minoritária, ou seja, determinar se uma pessoa efetivamente tem uma doença. Porém, a maioria dos algoritmos de machine learning para classificação foi projetada para datasets com um número equivalente de exemplos para cada classe, resultando muitas vezes em modelos com baixo desempenho preditivo, especialmente para os exemplos pertencentes à classe minoritária.

Apenas para fins de alinhamento de conceitos, considera-se neste tutorial que classe majoritária é aquela em que mais da metade dos exemplos pertençam a ela. O dataset que você irá trabalhar pode ter um desequilíbrio leve, que poderia ser desprezado, ou grande, onde é necessária alguma ação para construir um modelo com melhor desempenho (como por exemplo, em um dataset onde menos de 10% dos exemplos pertença à classe de interesse).

O dataset usado neste tutorial será o Pima Indians Diabetes, proveniente originalmente do Instituto Nacional de Diabetes e Doenças Digestivas e Renais. Seu objetivo é prever se um paciente desenvolverá ou não diabetes, com base em certas medidas de diagnóstico médico. Este dataset é um subconjunto do dataset original e está disponível tanto no Kaggle como em diversas URLs. Nele, todos os pacientes são mulheres com pelo menos 21 anos de idade e de herança indígena Pima. O dataset apresenta em diversos atributos relacionados a dados médicos e uma variável de classe binária (0 ou 1). As variáveis ​​preditoras incluem o número de gestações que a paciente teve, seu IMC, nível de insulina, idade e assim por diante.

É importante ressaltar que este dataset não apresenta um desequilíbrio grande: veremos que para cada exemplo pertencente à classe de interesse (1 — com diabetes), há 2 outros exemplos pertencentes à classe normal (0 — sem diabetes). Ainda assim, ele será utilizado por ser um dataset muito conhecido e de simples entendimento. As técnicas aqui ilustradas poderão ser facilmente utilizadas em qualquer outro dataset de seu interesse que seja desbalanceado.

O principal desafio de trabalharmos em problemas de classificação com datsets desequilibrados é exatamente a falta de exemplos para a classe minoritária e a diferença na importância dos erros de classificação entre as classes, sendo muitas vezes necessário realizar etapas adicionais, tais como utilizar técnicas de re-amostragem (sub-amostragem e super-amostragem) e métricas de avaliação alternativas (precisão, recall e F-score).

A técnica de sub-amostragem consiste em excluir exemplos da classe majoritária no conjunto de dados de treinamento, enquanto a técnica de super-amostragem sintetizar novos exemplos na classe minoritária no conjunto de dados de treinamento. A Figura a seguir ilustra estes conceitos:

Técnicas de subamostragem (undersampling) e superamostragem (oversampling). Fonte: https://www.kaggle.com/rafjaa/resampling-strategies-for-imbalanced-datasets

A acurácia é a métrica mais comum para problemas de classificação, embora nem sempre seja adequada, sendo potencialmente perigosa quando usada em datasets desbalanceados. Se 95% dos dados pertencerem à classe negativa, é fácil ver que um classificador dummy terá acurácia de 95%, simplesmente prevendo a classe negativa sempre, o que não é aceitável na prática, pois não estaremos identificando os exemplos pertencentes à classe positiva. Para tal, podemos usar métricas alternativas como precisão e recall, que permitem que o desempenho do modelo seja considerado com foco na classe minoritária (neste caso, se a pessoa terá diabetes). Para entender estas métricas, é importante definirmos os conceitos de Verdadeiro Positivo, Falso Positivo, Verdadeiro Negativo e Falso Negativo:

  • Verdadeiro Positivo (VP): a classe verdadeira é positivo, e a saída do modelo é positiva (acertou);
  • Falso Positivo (FP): a classe verdadeira é negativo, mas a saída do modelo é positiva (errou);
  • Verdadeiro Negativo (VN): a classe verdadeira é negativo, e a saída do modelo é negativa (acertou);
  • Falso Negativo (FN): a classe verdadeira é positivo, mas a saída do modelo é negativa (errou).

A precisão calcula a proporção do número total de exemplos classificados como positivos corretamente (verdadeiros positivos) dividido pelo número total de exemplos classificados como positivo pelo modelo (corretamente ou incorretamente). Quando maximizamos a precisão (mais próximo de 1), minimizamos os falsos positivos:

Precisão = VP/(VP + FP)

O recall calcula a proporção do número total de exemplos classificados como positivos corretamente (verdadeiros positivos) dividido pelo número total de exemplos com classe positiva. Quando maximizamos o recall, (mais próximo de 1), minimizamos os falsos negativos.

Recall = VP / (VP + FN)

Na prática, quase sempre temos que escolher entre uma alta precisão ou um alto recall pois, tipicamente, é impossível ter ambos. No nosso exemplo, possivelmente priorizaríamos ter uma alta precisão (minimizar a quantidade do número de mulheres com potencial de desenvolver diabetes não detectadas) e podemos tolerar um recall mais baixo (algumas mulheres sem potencial de desenvolver diabetes, mas detectadas como com potencial), mas é importante alinhar esta decisão com especialistas do negócio.

O desempenho de um modelo pode ser resumido por uma única pontuação que calcula a média da precisão e do recall, denominada F-Score. A maximização da F-Score maximizará a precisão e a recuperação ao mesmo tempo:

F-Score = (2 * Precisão * Recall) / (Precisão + Recall)

Tutorial

Para este tutorial, iremos utilizar Python e as bibliotecas Scikit-Learn e Imbalanced-Learn. É importante ressaltar que, como o foco deste artigo é o desbalanceamento de classes, não iremos abordar com detalhes outras etapas muito importantes em problemas preditivos de machine learning, tais como o pré-processamento de dados e a busca por modelos de classificação. Sugerimos que consulte outros tutoriais para se aprofundar nestas etapas.

O código-fonte deste tutorial está disponível em um notebook do Google Colab, que pode ser consultado aqui.

Vamos iniciar importando todos os pacotes que usaremos neste tutorial:

Importação de pacotes necessários

Carga de dados e mini-análise exploratória

Usando Pandas, iremos importar o dataset diretamente de uma URL onde ele se encontra disponível, e o carregaremos para a variável dataset, conforme mostrado no código abaixo. Iremos especificar nomes para as colunas e também indicaremos que não há informações de cabeçalho (header), para que o primeiro exemplo não seja considerado o nome da coluna. Com o dataset carregado, vamos utilizar a função head() para exibir as primeiras linhas do dataset:

Carga de dados
Resultado da função head()

Vamos agora exibir o número de exemplos de cada classe. Conforme já mencionado, verificaremos que este dataset não apresenta um desequilíbrio grande: veremos que existem 500 exemplos pertencentes à classe 0 e 268 pertencentes à classe 1.

Distribuição das classes do dataset

Pré-processamento

Em problemas de aprendizado supervisionado, é uma boa prática usar um conjunto de teste (em alguns lugares da literatura também chamado de conjunto de validação), uma amostra dos dados que não será usada para a construção do modelo, mas somente no fim do projeto para confirmar a precisão do modelo final. É um teste que podemos usar para verificar o quão boa foi a construção do modelo, e para nos dar uma ideia de como o modelo irá performar nas estimativas em dados não vistos. Neste tutorial, usaremos 80% do conjunto de dados para modelagem e guardaremos 20% para teste.

Primeiramente, iremos sinalizar quais são as colunas de atributos (X, as 8 primeiras) e qual é a coluna das classes (Y — a última).

Separação em atributos e classe

Em seguida, iremos definir uma semente aleatória (para garantirmos a reprodutibilidade destes resultados), e faremos a separação dos conjuntos de treino e teste por meio da função train_test_split(), que retornará 4 estruturas de dados: os atributos e classes para o conjunto de teste e os atributos e classes para o conjunto de treino. Repare que estratificaremos os conjuntos usando as classes, o que sinalizamos passando o valor y para o parâmetro stratify.

Divisão em conjuntos de treino e teste

Finalmente, criamos o modelo de classificação (uma regressão logística simples com solver liblinear), treinamos o modelo com o conjunto de treino e fazemos as predições no conjunto de testes, exibindo as métricas de avaliação em seguida. Repare que alcançamos um F-score de 0.58:

Sub-amostragem da classe majoritária

Uma das formas de lidar com o desbalanceamento de classes é alterar o conjunto de dados de treinamento para ter uma distribuição de classe mais equilibrada. Uma das formas de alcançar este resultado é excluindo exemplos da classe majoritária, fazendo uma “sub-amostragem”. Uma desvantagem desta abordagem é que possivelmente excluiremos exemplos úteis da classe majoritária.

Vamos agora utilizar a função RandomUnderSampler(), que faz uma sub-amostragem da classe majoritária, para reamostrar o conjunto de treino. Configuramos o parâmetro sampling_strategy com o valor 1, definindo que queremos uma proporção de 1:1 entre as classes. Em seguida, verificamos a distribuição de classes no conjunto de treino (agora na variável Y_under). Repare que foram retirados exemplos da classe 0.

Sub-amostragem

Finalmente, vamos aplicar um modelo idêntico ao da etapa anterior, mas agora usando dados com reamostragem. Repare que o F-score subiu de 0.58 para 0.67, indicando um modelo mais adequado para o problema.

Modelo de classificação

Super-amostragem da classe minoritária

Para balancearmos nosso dataset de treino, podemos em vez de excluir exemplos da classe majoritária, adicionar novos exemplos da classe minoritária. Se duplicássemos exemplos existentes da classe minoritária não estaríamos adicionando nenhuma nova informação para o modelo, então podemos criar novos exemplos sintetizando exemplos existentes no conjunto de treino, com pequenas diferenças aleatórias.

Para tal, vamos utilizar a função SMOTE(), que faz uma super-amostragem da classe minoritária. Da mesma forma que fizemos anteriormente, configuramos o parâmetro sampling_strategy com o valor 1, definindo que queremos uma proporção de 1:1 entre as classes. Em seguida, verificamos a distribuição de classes no conjunto de treino (agora na variável Y_under). Repare que foram gerados novos exemplos para a classe 1.

Super-amostragem

Usando a super-amostragem, conseguimos um F-score de 0.65 (melhor do que com os dados sem reamostragem, mas não tão bom quanto usando a reamostragem anterior).

Modelo de Classificação

Combinando Sub-amostragem e Super-amostragem

Vamos agora usar a função SMOTEENN(), que combina a sub-amostragem com a super-amostragem SMOTE. Da mesma forma que fizemos anteriormente, configuramos o parâmetro sampling_strategy com o valor 1, definindo que queremos uma proporção de 1:1 entre as classes. Em seguida, verificamos a distribuição de classes no conjunto de treino (agora na variável Y_under). Repare que foram retirados exemplos da classe 0 e foram gerados novos exemplos para a classe 1.

Reamostragem Smoteen

Usando esta técnica de amostragem, conseguimos um F-score de 0.61 (ainda melhor do que com os dados sem reamostragem, mas não tão bom quanto usando as técnicas de reamostragem anteriores).

Modelo de Classificação

Algoritmos sensíveis a custos

Em vez de técnicas de reamostragem, podemos levar em consideração os custos dos erros de classificação ao treinar um modelo de machine learning. Por exemplo, podemos penalizar o modelo de formas diferentes, dependendo se o erro de classificação incorreta for na classe minoritária ou na classe majoritária.

Diversos modelos da biblioteca Scikit-learn oferecem o atributo class_weight na sua criação, possibilitando que especifiquemos uma ponderação inversamente proporcional à distribuição da classe. Ou seja, erros de classificação em exemplos da classe menos frequente (minoritária) serão mais penalizados do que erros de classificação em exemplos da classe mais frequente (majoritária). Para tal, basta definir o parâmetro class_weight como a string balanced.

Vamos então criar um modelo de regressão logística com esta configuração, treiná-lo no conjunto de treino original e aplicá-lo no conjunto de teste. Repare que o resultado obtido considerando a métrica F-score é quase tão bom quanto o resultado que obtivemos com a utilização da técnica de sub-amostragem.

Conclusão

Este artigo apresentou alguns exemplos de técnicas para tratar datasets com classes desbalanceadas. É importante ressaltar que a decisão de qual técnica utilizar deve ser mais investigada em problemas reais, entendendo os prós e contras de sua utilização, sempre acompanhado por especialista do negócio.

O objetivo deste artigo não foi ser exaustivo, uma vez que existem na literatura diversas outras técnicas não abordadas aqui. Para saber mais sobre como trabalhar com datasets desbalanceados, recomenda-se a leitura dos seguintes materiais:

--

--

Tatiana Escovedo

Coordinator at Petrobras / Lato Sensu Teacher and Coordinator at PUC-Rio / Data Scientist / Speaker / Writer