Modelo de algoritmo de classificação para determinar a saúde de um feto

Raffaela Loffredo
15 min readSep 19, 2023

--

Click here to read this article in English.

Nesse artigo trago o passo a passo que eu fiz na construção de um modelo de classificação para classificar a saúde de um feto com o auxílio das bibliotecas Pandas Profiling e PyCaret.

Sumário

  1. Saúde Fetal
  2. Objetivo Geral
    2.1. Objetivos Específicos
  3. Obtenção dos Dados
  4. Dicionário de Variáveis
  5. Importação dos Dados e das Bibliotecas
  6. Análise Exploratória dos Dados
    6.1. Por que utilizar Pandas Profiling?
    6.2. Gerar Relatório
  7. Tratamento dos Dados
  8. Criação dos modelos de Machine Learning
    8.1. Por que utilizar PyCaret?
    8.2. Configuração
    8.3. Criação e comparação de modelos
    8.4. Ajuste do modelo
    8.5. Visualizações
    8.6. Realizar previsões
    8.7. Finalizar o modelo
  9. Avaliação no conjunto de testes
  10. Conclusão

1. Saúde Fetal

Para cuidar da saúde do feto, bem como da grávida, existe uma especialidade médica conhecida como perinatalogia. Esse ramo da medicina tem por objetivo evitar complicações para garantir que tudo ocorra bem, da geração ao nascimento do bebê.

São diversos procedimentos que podem ser realizados: exames pré-natal, acompanhamento em casos de problemas de saúde pré-existentes na gestante (como pressão alta e diabetes), checagem do desenvolvimento do feto, verificação de anomalias congênitas e genéticas, entre outros.

Esse monitoramento é realizado com o objetivo de evitar patologias na mãe e/ou no bebê, bem como de evitar o óbito. O tópico é tão importante que a Organização das Nações Unidas (ONU), inseriu a redução da taxa de mortalidade materna e neonatal como um dos Objetivos de Desenvolvimento Sustentável.

Entre os diversos exames que podem ser realizados para avaliação da saúde do feto, a cardiotocografia (CTG) é considerada simples, de baixo custo e não-invasiva, que consiste na coleta da frequência cardíaca do feto, bem como das contrações uterinas. Por esses motivos, com os dados coletados a partir desse exame é que será construído esse projeto.

2. Objetivo Geral

Desenvolver um algoritmo para classificar a saúde do feto com base no resultado de exames cardiotocografia (CTG).

2.1. Objetivos Específicos

  • Realizar uma análise exploratória nos dados para conhecer o data set e verificar presença de anormalidades que necessitem de tratamentos adequados.
  • Criar modelos de machine learning com diversos tipos de aprendizado de máquina.
  • Avaliar modelos para encontrar o que tem o melhor desempenho.

3. Obtenção dos Dados

Os dados utilizados nesse projeto foram disponibilizados no Kaggle e foram baseados no estudo de Ayres de Campos et al. (2000). O arquivo foi salvo em nuvem, e encontra-se nesse link para o caso de ficar indisponível futuramente.

4. Dicionário de Variáveis

A compreensão do conjunto de dados passa pela checagem das variáveis disponíveis nele para que se possa realizar uma boa análise. Segue-se um resumo dos atributos encontrados e seus respectivos significados.

em ordem alfabética

abnormal_short_term_variability: porcentagem de tempo com variabilidade anormal no curto prazo
accelarations: quantidade de acelerações por segundo
baseline value: Frequência Cardíaca Fetal basal (FCF)
fetal_health: saúde do feto (1. normal; 2. suspeito; 3. patológico), classe alvo
fetal_movement: quantidade de movimentos fetais por segundo
histogram_max: valor máximo do histograma
histogram_mean: valor médio do histograma
histogram_median: mediana do histograma
histogram_min: valor mínimo do histograma
histogram_mode: moda do histograma
histogram_number_of_peaks: quantidade de picos no histograma
histogram_number_of_zeroes: quantidade de zeros no histograma
histogram_tendency: tendência do histograma
histogram_variance: variância do histograma
histogram_widthlargura do histograma
light_decelerationsquantidade de desacelerações leves por segundo
mean_value_of_long_term_variability: média da variabilidade no longo prazo
mean_value_of_short_term_variability: média da variabilidade no curto prazo
percentage_of_time_with_abnormal_long_term_variability: porcentagem de tempo com variabilidade anormal no longo prazo
prolongued_decelerations: quantidade de desacelerações prolongadas por segundo
severe_decelerations: quantidade de desacelerações severas por segundo
uterine_contractions: quantidade de contrações uterinas por segundo

5. Importação dos Dados e das Bibliotecas

Ao iniciar um projeto é necessário instalar pacotes, importar as bibliotecas que possuem funções específicas a serem utilizadas nas linhas de código seguintes e realizar as configurações necessárias para a saída do código. Também, se prossegue com a importação do dataset, salvando-o em uma variável específica para que seja usada posteriormente.

# instalar pacote adicional
!pip install pycaret -q # auto machine-learning
!pip install pandas_profiling -q # produção de relatório de análise exploratória
# importar as bibliotecas necessárias
import pandas as pd # manipulação de dados
from pandas_profiling import ProfileReport # produção de relatório de análise exploratória
from pycaret.classification import * # criação de modelos de classificação
# importar os dados e atribuir em uma variável
data_path = 'https://www.dropbox.com/scl/fi/ztok9mb5oni0j14xb48ek/fetal_health.csv?rlkey=8gu4bmlif43a3bf0hvr15dfmq&dl=1'
df_raw = pd.read_csv(data_path)

6. Análise Exploratória dos Dados

Essa é uma etapa essencial em projetos de ciência de dados onde se busca compreender melhor os dados seja por identificar padrões, outliers, possíveis relações entre as variáveis, etc. Nesse estudo, se exploraram informações que fossem relevantes para orientar as respostas dos objetivos indicados anteriormente (ver Objetivo Geral e Objetivos Específicos).

Para isso vou gerar um relatório que resume os dados, com o uso da biblioteca ProfileReport. A partir dele, se houver necessidade, será feito uma análise mais profunda. Contudo, ele já nos fornecerá informações suficientes para identificar anomalias como outliers e desbalanceamento nos dados.

6.1. Por que utilizar Pandas Profiling?

A biblioteca Pandas Profiling oferece a função ProfileReport que cria automaticamente um relatório completo de análise exploratória de dados. Ele inclui as seguintes informações:

  • Visão Geral
    Com a quantidade de registros e atributos do dataset, bem como valores ausentes e duplicados.
  • Estatística Descritiva
    Para cada variável irá calcular: média, mediana, valor máximo e mínimo, desvio padrão, quartis, entre outros dados estatísticos.
  • Distribuição
    Para as variáveis numéricas, ele irá criar gráficos como histogramas, boxplot, densidade, para que seja visualmente fácil compreender a distribuição dos dados.
  • Frequência
    Para os atributos numéricos será feita a identificação dos principais valores únicos, e suas respectivas contagens.
  • Correlação
    É gerada uma matriz de correlação para verificação do relacionamento entre as variáveis.

Por isso, o uso do ProfileReport agiliza a rotina da análise exploratória dos dados, contudo, não exime o cientista de dados de realizar o estudo aprofundado do relatório para compreender e interpretar os dados, além de identificar eventuais problemas que necessitam de tratamento.

6.2. Gerar Relatório

# criar relatório
report = ProfileReport(df_raw)

# visualizar relatório
report.to_notebook_iframe()

No relatório gerado acima percebe-se que:

  • o conjunto é formado por 2126 registros e 22 atributos
  • pelas primeiras e últimas entradas do dataset ele parece estar bem preenchido e sem anormalidades
  • não há dados ausentes
  • existem dados duplicados igual a 11 registros que representam 0.5% do conjunto
  • foram detectadas diversas correlações fortes, o que é comum quando os dados estão de certa forma repetidos mas expressos de outra forma, como por exemplo, a média, moda, mediana, valor máximo e mínimo do histograma que é o resultado final desse tipo de exame
  • o atributo severe_decelerations está desbalanceado, o que também era de se esperar uma vez que é mais incomum que isso aconteça
  • foram apontadas diversas variáveis com grandes quantidades de zero, o que também não apresenta grandes problemas uma vez que é comum nesse tipo de exame onde a maior preocupação são dados que fogem do comum
  • todos os dados são numéricos e estão expressos em tipo float, o que não há necessidade pois se tratam de valores inteiros. Em um dataset grande isso poderia influenciar consideravelmente no uso de memória e o recomendável seria alterar o tipo dessas variáveis para int, aqui, contudo, por se tratar de um conjunto pequeno, não é necessário esse tratamento.

Dessa forma, o único tratamento necessário será a identificação e exclusão dos registros duplicados.

7. Tratamento dos Dados

Nessa sessão, irei prosseguir com 2 etapas de tratamento de dados:

  • tratar dados duplicados
  • preparar dados para machine learning

Conforme visto na etapa anterior, existe a presença de dados duplicados nesse dataset. Portanto, nessa sessão vou prosseguir com a identificação e remoção desse dado.

# identificar e visualizar registros duplicados
duplicatas = df_raw.duplicated()
print(df_raw[duplicatas])

Na saída do código acima, pode-se reparar que as linhas mostradas, estão de acordo com o que foi mostrado no relatório gerado. Vamos prosseguir com a exclusão desses registros.

# remover linhas duplicadas
df_clean = df_raw.drop_duplicates()

Por fim, a etapa essencial em qualquer projeto de machine learning envolver a preparação dos dados com a separação dos mesmos em 2 conjuntos distintos: um conjunto de treino e outro de testes para que o de treino seja utilizado no treinamento do algoritmo e o de testes seja usado apenas ao final desse estudo, como forma de compreender os acertos e erros do modelo criado.

Será feito na proporção de 90% para treino e 10% para testes. Bem como, vou aproveitar para resetar o index dos conjuntos para que não haja nenhuma possibilidade de identificação e relação entre os dados e, visualizar o tamanho que ficaram os conjuntos.

# separar os dados em teste e treino
## criar variável 'test' com 10% do data frame original
## com seed para gerar resultados reproduzíveis
test = df_clean.sample(frac=0.10, random_state=28)

## criar variável 'train' com tudo o que não estiver em 'test'
train = df_clean.drop(test.index)

## resetar index
test.reset_index(inplace=True, drop=True)
train.reset_index(inplace=True, drop=True)

# checar tamanho dos conjuntos
print(train.shape)
print(test.shape)
'''
(1902, 22)
(211, 22)
'''

8. Criação dos modelos de Machine Learning

A construção de um projeto completo de regressão com a utilização da biblioteca PyCaret demanda 6 etapas:

  1. configuração
  2. criação e comparação de algoritmos
  3. ajuste do modelo
  4. visualização
  5. realizar previsões
  6. finalizar modelo

Na etapa posterior usaremos o conjunto de testes separado acima para avaliação do modelo criado.

8.1. Por que utilizar PyCaret?

PyCaret é uma biblioteca de uso simplificado que auxiliam o processo de construção de modelos de machine-learning por meio de funções automatizadas de etapas comuns ao desenvolvimento desses modelos. Isso faz dela uma ferramenta extremamente útil para rapidamente construir modelos de aprendizado de máquina.

Vale ressaltar que ferramentas de auto machine-learning como essa não substitui o trabalha do cientista de dados. A utilização dela deve ser feita com o intuito de dar suporte e agilidade ao processo da construção de modelos.

8.2. Configuração

A primeira etapa consiste em passar os parâmetros necessários para configurar os dados do nosso problema de regressão.

Para isso, informamos qual é o conjunto de dados que deverá ser utilizado, ou seja, df_clean; precisamos apontar qual é o atributo alvo, isto é, aquele que queremos que seja previsto pelo algoritmo; também passei uma divisão de 75% para o conjunto de treino, o que faz com que o conjunto de testes fique com os 25% restantes; coloquei True para o parâmetro normalize para que os dados sejam normalizados (pelo padrão da biblioteca que usa o ZScore); transformation, quando configurada em True, aplica potenciação para transformar os dados mais semelhantes à uma distribuição Gaussiana; remove_multicollinearity para ativar o valor limite inserido em multicollinearity_threshold, que determina o valor absoluto mínimo de correlação de Pearson na identificação de características que estejam correlacionadas; e, por fim, passei um valor inteiro para que os resultados possam ser reproduzidos novamente em qualquer ambiente.

A multicolinearidade refere-se à alta correlação entre as variáveis independentes. Como vimos na Análise Exploratória dos Dados, esse é um problema existente nesse conjunto de dados e isso pode causar problemas para o modelo identificar corretamente as classes. Ativando a remoção de multicolinearidade, excluímos as variáveis altamente correlacionadas entre si, uma vez que isso não adiciona informações vantajosas para o modelo e, com isso, melhoramos a qualidade e relevância dos atributos independentes, para que contribuam significativamente na previsão das classes.

# 1. configurar regressão
clf = setup(data=train, # conjunto de dados
target='fetal_health', # classe alvo
train_size=0.75, # definir tamanho da divisão dos conjuntos
normalize=True, # normalizar os dados em uma única escala (zscore)
transformation=True, # transformar dados em distribuição normal
remove_multicollinearity=True, # ativar o valor de 'multicollinearity_threshold'
multicollinearity_threshold=0.95, # valor mínimo para correlação de Person
session_id=73) # seed para gerar resultados reproduzíveis

8.3. Criação e comparação de modelos

Aqui, serão criados e treinados todos os modelos disponíveis na biblioteca do PyCaret, e então, avaliados com validação cruzada estratificada (por padrão 10 dobras). Ademais, os resultados, por padrão, são ranqueados de acordo com o valor obtido na Acurácia (mas isso poderia ser alterado se houvesse necessidade). Vou manter essa configuração, pois essa será a métrica utilizada também na próxima etapa.

Veremos abaixo as seguintes métricas de avaliação:

Accuracy: proporção de previsões certas, pelo total de previsões realizadas
AUC: (área abaixo da curva) indica o quanto o modelo consegue distinguir entre duas coisas
F1: média harmônica entre precisão e recall
Kappa: concordância entre as previsões do modelo e as classes reais
MCC: mede a qualidade das previsões de acordo com a matriz de confusão
Prec.: proporção de verdadeiros positivos, pelo total de identificações positivas
Recall: proporção dos positivos reais, pelo total de identificações de positivos reais

# 2. criar e comparar modelos
best = compare_models()

Acima, foram criados e testados 15 modelos diferentes. Sendo que, os que apresentaram os melhores resultados foram o Extreme Gradient Boosting e o Gradient Boosting Classifier.

Podemos confirmar o melhor algoritmo criado com o código abaixo:

# imprimir o melhor modelo
print(best)

Portanto, vamos dar continuidade utilizando o Extreme Gradient Boosting.

O XGBoost, como também é chamado, vem da família de classificadores supervisionados do tipo Árvores de Decisão. Ele vem sendo muito utilizado por profissionais da área devido ao seu alto grau de precisão e acurácia na criação dos modelos.

Isso se deve, parte, à grande quantidade de hiperparâmetros que podem ser ajustados, melhorando a performance do modelo e, com isso pode ser aplicado à problemas de diversos tipos, como classificação, regressão e detecção de anomalias, e dos mais diversos setores.

# instanciar modelo
xgb = create_model('xgboost')

Note que as médias das métricas mostradas acima são as mesmas para o modelo criado na lista de todos os algoritmos. Isso porque o modelo é criado e avaliado da mesma forma que foi feito anteriormente.

8.4. Ajuste do modelo

O modelo será otimizado pela métrica da acurácia e, como já é o padrão da função tune_model não há necessidade de especificá-la.

Relembrando que a acurácia é uma das métricas mais simples para avaliar um modelo. Ela é a proporção da quantidade de acertos do modelo, pela quantidade total prevista.

Seus resultados variam entre 0 e 1.0, sendo que quanto mais próximo de 1.0 melhor é o resultado.

Sua fórmula é dada por:

# ajuste dos parâmetros
tuned_xgb = tune_model(xgb)

Com o ajuste de parâmetros não tivemos nenhuma melhora no resultado da acurácia: saímos de 0.9488 para 0.9467. Portando, ele volta ao estado original do melhor resultado.

8.5. Visualizações

Nessa sessão vou gerar diferentes gráficos para visualizar e auxiliar a compreensão do modelo criado.

Primeiro, vamos à Matriz de confusão, para avaliar o desempenho do modelo em classificar a saúde do feto. Com ele podemos checar os erros e os acertos do modelo.

plot_model(tuned_xgb, plot='confusion_matrix')

Pode-se notar que a maior dificuldade do modelo está em distinguir corretamente os grupos 0 e 1, que somaram 18 previsões incorretas.

Vamos checar também quais atributos são mais relevantes para o modelo gerado com o gráfico abaixo.

plot_model(tuned_xgb, plot='feature')

A seguir, no gráfico ROC (Receiver Operating Characteristic) podemos verificar o quanto o modelo consegue distinguir as diferentes classes do problema. Nesse caso, entre um feto normal, suspeito e patológico, sendo que o valor dado por AUC varia entre 0 e 1, sendo que, quanto mais próximo de 1, indica que melhor é o modelo.

plot_model(tuned_xgb, plot='auc')

Já na imagem abaixo podemos checar os valores da precisão, recall e f1 para cada uma das classes do estudo.

plot_model(tuned_xgb, plot='class_report')

A seguir, podemos verificar visualmente as áreas de transição das classes (em fundo azul, vermelho e verde) e as previsões do modelo (em figuras geométricas e em cores de acordo com a classe prevista). Dessa forma, conseguimos ver os acertos e erros do algoritmo criado.

plot_model(tuned_xgb, plot='boundary')

Mais uma vez, podemos constatar acima, que a maior dificuldade do modelo está na distinção das classes 0 e 1.

Por fim, o gráfico de Curva de Aprendizado. Com ele podemos checar o desempenho do modelo à medida que o conjunto de treinamento aumenta. Ou seja, pode-se entender o comportamento do modelo com diferentes quantidades de dados, o que pode levar a reflexões quanto à necessidade de mais dados ou mesmo da complexidade do modelo.

plot_model(tuned_xgb, plot='learning')

Podemos observar acima, que o modelo estava no começo de uma possível convergência e, talvez, valesse a pena investir na aquisição de mais dados para resolver esse problema. Isso porque o valor do cross validation tem apresentado uma melhor significativa de acordo com a maior quantidade de dados que lhe foi passada.

Vale imprimir ainda, um modelo interativo geral de avaliação, disponível com a função evaluate_model o qual fornece diversas informações sobre o modelo construído. Por exemplo: a estrutura do pipeline do modelo e os hiperparâmetros finais utilizados.

evaluate_model(tuned_xgb)

8.6. Realizar previsões

Antes de finalizar o modelo, fazemos uma verificação no qual realizamos uma previsão, com o conjunto de testes separado pela função setup na primeira etapa de criação do modelo. Dessa forma, checamos se não há divergências nos resultados apresentados.

# fazer previsões
predict_model(tuned_xgb);

Observe que o modelo performou um pouco melhor do que o resultado obtido na etapa 2, de 0.9488 para 0.9496 e, dentro do desvio padrão de 0.0181.

8.7. Finalizar o modelo

Na última etapa do processo de criação do modelo, finalizamos ele com a função finalize_model que irá treinar o modelo com o conjunto completo que foi informado na função setup. Ou seja, ele irá incluir o conjunto que havia sido separado para os testes.

# finalizar modelo
final_xgb = finalize_model(tuned_xgb)

Podemos imprimir o modelo final, para checar os parâmetros utilizados no modelo.

# verificar parâmetros
print(final_xgb)

9. Avaliação no conjunto de testes

Para concluir esse estudo, utilizamos o conjunto de testes separado na sessão de Tratamento de Dados para que possamos avaliar o modelo com dados que ele nunca teve contato anteriormente.

# previsão em dados não vistos
prediction = predict_model(final_xgb, data=test)
prediction.head()

Observa-se que ele obteve uma acurácia dentro dos valores obtidos nos testes, de 0.9479 e, dentro do desvio padrão previsto de 0.0181.

Além disso, na saída completa do código acima, temos dois novos atributos: prediction_label no qual podemos ver a previsão do modelo para a saúde do feto, onde 1. normal; 2. suspeito; e, 3. patológico; e o prediction_score que mostra a probabilidade que o modelo calculou do resultado ser o previsto em prediction_label.

10. Conclusão

O estudo teve por objetivo central o desenvolvimento um algoritmo capaz de classificar a saúde de um feto, entre normal, suspeito e patológico, com base no resultado obtido a partir de um exame de cardiotocografia (CTG).

Com o uso da biblioteca Pandas Profiling foi possível obter em poucos segundos um panorama do conjunto de dados, com a geração de um relatório completo, que auxiliou na análise exploratória.

Após o devido tratamento dos dados, com a ajuda da biblioteca PyCaret, foram gerados 15 modelos de classificação, que foram avaliados por diversas métricas. Contudo, optou-se por ordenar os resultados pelo valor obtido na Acurácia. Com isso, se chegou ao melhor modelo criado pelo algoritmo Extreme Gradient Boosting com a acurácia igual a 0.9488.

Na sequência foi feito o ajuste de hiperparâmetros do modelo, ao qual não foi possível obter uma melhora do modelo. E, na etapa seguinte, de realizar testes com os dados do PyCaret o modelo ficou dentro do esperado, com um leve aprimoramento na métrica de avaliação, subindo para 0.9496. Já no teste final, onde se utilizaram novos dados, a acurácia resultou em 0.9479 e dentro dos limites de desvio padrão previstos e mostrando uma consistência no modelo criado.

Saiba Mais

Esse estudo encontra-se disponível nas plataformas Google Colab e GitHub. Basta clicar nos links das imagens abaixo para ser redirecionado.

[LoffredoDS] Modelo de algoritmo de classificação para determinar a saúde de um feto.ipynb
raffaloffredo/fetus_health_classification_portuguese

--

--

Raffaela Loffredo

💡 Transforming data into impactful solutions | Co-founder @BellumGalaxy | Data Scientist | Blockchain Data Analyst