Saúde dos Fetos e a I.A — Uma questão de política pública.

Utilizando modelos de inteligência artificial para tentar avaliar a saúde do feto a partir dos dados obtidos por exames de cardiotocografia.

Thales Ferraz
16 min readNov 21, 2021

Para acompanhar os códigos deste projeto, vá até o meu COLAB. Caso tenha interesse em mais projetos, consulte meu GITHUB.

Mulher grávida sob monitoramento fetal por médico | Foto Grátis (freepik.com)

O que você vai aprender nesta análise:

  • Cuidados na Gravidez, o que é Cardiotocografia e Saúde Fetal.
  • Algoritmos de classificação supervisionado e variáveis alvo.
  • O que é e como funciona o Light Gradient Boosting Machine e o Gradient Boosting Classifier.
  • Como usar o Pycaret, uma ferramenta de Auto Machine Learning.

A saúde e o bem-estar dos bebês antes do nascimento.

O terceiro Objetivo de Desenvolvimento Sustentável da Agenda de 2030 da ONU prevê “Assegurar uma vida saudável e promover o bem-estar para todas e todos, em todas as idades”. Naturalmente, este objetivo deve incluir os bebês que ainda estão em gestação. No entanto, o Brasil até hoje não possui um método ideal para definir quais óbitos fetais poderiam ter sido evitados com uma maior atuação do Sistema Único de Saúde(SUS) durante a gravidez de suas pacientes.

Ao longo da gestação de uma criança são necessários inúmeros cuidados alimentares, suplementação vitamínica e realização de exames com acompanhamento médico adequado. Desta forma, é possível garantir a saúde da gestante e do bebê, até mesmo evitando malformações ou problemas que resultariam em aborto.

Photo by Hush Naidoo Jade Photography on Unsplash

Uma maneira de avaliar a vitalidade e saúde do feto no final da gravidez é através da Cardiotocografia. Com esta avaliação em mãos é possível o médico intervir de forma imediata caso o feto esteja apresentando algum tipo de alteração em seu desenvolvimento ou saúde. Se por ventura, ele esteja apresentando estes problemas se pode dizer que está em um quadro de sofrimento fetal.

O sofrimento fetal é uma situação em que o bebê não recebe a quantidade suficiente de oxigênio no útero durante a gestação ou parto, o que pode interferir diretamente no seu crescimento e desenvolvimento.

Portanto, realizar a cardiotocografia é extremamente necessário, uma vez que este exame é simples e não invasivo. Ele é feito com a grávida sentada ou deitada, em repouso e dura cerca de 20 minutos. Sua aplicação consiste na colocação de dois cintos elásticos com sensores na barriga: um para captar os batimentos cardíacos do bebê e outro para captar a frequência e a intensidade das contrações uterinas. No caso de gravidez múltipla, haverá um sensor especial para cada coração.

O objetivo deste projeto é desenvolver um modelo de machine learning para classificar o estado de saúde de fetos em uma maternidade considerando os resultados obtidos pelos exames de cardiotocografia. Este algoritmo poderia ser utilizado como auxílio a médicos do SUS para uma melhor tomada de decisão quanto a saúde das gestantes e de seus bebês.

Obtenção dos dados

Photo by Markus Spiske on Unsplash

Obtenção dos dados

Neste projeto serão utilizados dados públicos disponibilizados no Kaggle: Fetal Health Classification. Estas informações são baseadas em um trabalho acadêmico que reuniu mais de 2 mil resultados de exames de Cardiotocografia em que foram classificadas a saúde de cada feto por três especialistas em obstetrícia. A classificação dada para a saúde de cada feto está dentro das seguintes classes:

  • Normal, assinalado como 1.
  • Suspeito, assinalado como 2.
  • Patológico, assinalado como 3.

As variáveis do dataset disponibilizado não possuem descrições detalhadas de seu significado. Portanto, realizaram-se pesquisas na internet para entender a possível interpretação de cada feature e sua aplicação dentro do contexto da análise.

Este projeto está baseado nos seguintes trabalhos acadêmicos:

  • Ayres de Campos et al. (2000) SisPorto 2.0 A Program for Automated Analysis of Cardiotocograms. J Matern Fetal Med 5:311–318 - link
  • Oliveira CA, Sá RA. Cardiotocografia anteparto. São Paulo: Federação Brasileira das Associações de Ginecologia e Obstetrícia (FEBRASGO); 2018. (Protocolo FEBRASGO — Obstetrícia, no. 81/Comissão Nacional Especializada em Medicina Fetal). link

Dicionário das variáveis:

baseado em: Use of Machine Learning Algorithms for Prediction of Fetal Risk using Cardiotocographic Data.

  • baseline value: Linha de Base da Frequência Cardíaca Fetal - FCF.
  • accelerations: número de acelerações da FCF por segundo.
  • fetal_movement: número de movimentos do feto por segundo.
  • uterine_contractions: número de contrações uterinas por segundo.
  • light_decelerations: número de desacelerações leves da FCF por segundo.
  • severe_decelerations: número de desacelerações agudas da FCF por segundo.
  • prolongued_decelerations: número de desacelerações prolongadas da FCF por segundo.
  • abnormal_short_term_variability: porcentagem do tempo em que ficou com uma variabilidade da FCF anormal por curtos períodos.
  • mean_value_of_short_term_variability: média da variabilidade da FCF em curtos períodos.
  • percentage_of_time_with_abnormal_long_term_variability: porcentagem do tempo em que ficou com uma variabilidade da FCF anormal por longos períodos.
  • mean_value_of_long_term_variability: média da variabilidade da FCF em longos períodos.
  • histogram_width: intervalo dos valores medidos da FCF.
  • histogram_min: mínimo valor da FCF medida.
  • histogram_max: máximo valor da FCF medida.
  • histogram_number_of_peaks: número de picos do histograma da FCF.
  • histogram_number_of_zeroes: número de zeros do histograma da FCF.
  • histogram_mode: moda dos valores medidos da FCF.
  • histogram_mean: média dos valores medidos da FCF.
  • histogram_median: mediana dos valores medidos da FCF.
  • histogram_variance: variânca dos valores medidos da FCF.
  • histogram_tendency: tendência dos valores medidos da FCF.
  • fetal_health: Saúde do feto, classificado como: Normal (1), Suspeita (2) e Patológico (3).

Análise Exploratória dos Dados

Tendo em mente uma possível interpretação dos significados das variáveis, podemos explorar um pouco mais nossa base de dados. Para isso, utilizaremos o máximo de visualizações gráficas e estatísticas descritivas que nos auxiliem a entender cada vez mais nosso problema.

Este é um passo muito importante em nossa análise, já que podemos tirar alguns insights das informações que ajudarão a construir um modelo de machine learning mais robusto e eficiente.

Em nossa base de dados possuímos cerca de 2.126 entradas e 22 variáveis, cada entrada representa o exame de cardiotocografia de um feto.

Sempre é importante verificar qual é o tipo de dado que estamos lidando. Algumas vezes variáveis numéricas vem com o formato de texto e precisamos tratar este tipo de inconsistência para realizar cálculos ou inputar dados em modelos de machine learning.

Para este banco de dados, todas as variáveis estão com seu type adequados. Além disso, todas elas são expressas como float64. Este formato representa números de ponto flutuante que ocupam 64 bits.

O objetivo desta análise é conseguir classificar a saúde dos fetos a partir das outras variáveis medidas pelos exames de Cardiotocografia. Portanto a coluna fetal_health é considerada nossa variável alvo, ou seja, a variável que queremos prever.

Neste projeto usaremos uma biblioteca low-code de auto machine learning, o PyCaret. Esta ferramenta permite preparar os dados e criar modelos de machine learning para o deploy em alguns minutos. Desta forma, reduzimos o tempo entre o ciclo de hipóteses e os insights que devemos ter nos projetos, tudo de uma forma dinâmica e eficiente.

Mas antes, é preciso dar uma olhada em como funciona o PyCaret internamente para entender algumas mudanças que serão realizadas.

O código por debaixo dos panos 🔍

Liberado apenas para leitores premium

array([1., 2., 3.]) 👀 <- valores da variável fetal_health

🤡: ok, vou prosseguir com minha análise multiclass com este array.

🐍: Calma lá, amigo. Não é assim que o PyCaret funciona!.

Para realizar as previsões o PyCaret usa o módulo predict_model. Este AutoML cria em seus códigos duas variáveis: a pred e a score. Posteriormente, ele faz uma list comprehension com estas variáveis.

pred = np.nan_to_num(estimator.predict(X_test_))

  • nan_to_num: transforma os valores vazios em zero e os infinitos em números grandes definidos pelo usuário.
  • estimator: o modelo de machine learning que foi criado.
  • X_test_: os valores de entrada da base de dados de teste.

score = estimator.predict_proba(X_test_)

  • estimator: o modelo de machine learning que foi criado.
  • predict_proba: retorna a probabilidade estimada para todas as classes.
  • X_test_: os valores de entrada da base de dados de teste.

Após estas duas variáveis criadas, utiliza-se uma list comprehension para guardar a previsão de cada score.

score = [s[pred[i]] for i, s in enumerate(score)]

“Aí que mora o perigo

Aí que eu caio lindo

Aí que eu sei das consequências, mesmo assim vou indo”,

(Henrique e Juliano, Arranhão)

Estamos prestes a cometer um erro.

Se fizermos um print isolado de cada variável dentro de um for, observamos que a pred[i] pode retornar o valor 3, ou seja, que o feto possui uma saúde considerada patológica. No entanto, o nosso score calculado acima tentará encontrar o score[3] e falhará miseravelmente. Uma vez que ele possui três elementos e sua contagem começa no zero.

Em outras palavras: só vai existir o score[0],score[1] e o score[2]. O score[3] estará fora do índice e isto está explicitado no erro.

Portanto, a nossa variável alvo precisa iniciar em zero.

Após os ajustes, precisamos verificar os dados nulos ou ausentes.

Dados nulos ou vazios são dados que estão com valores faltantes. O uso do isnull retorna um booleano dos valores que estão nesta situação. É importante ressaltar que um dado que está com o valor ZERO não é nulo, já que possui um valor.

Em nossa base de dados não foi encontrado nenhum valor nulo. Este é um cenário bem incomum utilizando dados de nosso cotidiano, provavelmente houve uma limpeza de dados antes da postagem no Kaggle.

A cardiotocografia é um exame preciso que retorna muitos valores numéricos. Uma maneira interessante de observar o comportamento destes dados é através de histogramas. Neles conseguimos analisar a distribuição dos dados, verificando quais são os intervalos que aparecem com uma maior frequência.

Embora tenham sido criados vários histogramas, é de suma importância o especialista avaliar o comportamento de cada variável, ou seja, de cada histograma. Uma vez que estes dados representam atributos relacionados a saúde da amostra de fetos e podem ajudar a entender melhor como está indo a saúde daqueles bebês.

Uma maneira interessante de avaliar como as variáveis se comportam em conjunto é através de uma matriz de correlação. Uma correlação de valor numérico 1 (100%) representa que existe uma grande relação estatística (associação) entre as variáveis. Isto significa que elas estão muito correlacionadas.

Já uma correlação próxima a 0 (0%), representa que as variáveis não possuem correlação significativa entre si. O aumento de uma não está relacionado ao aumento da outra.

Pela análise dos gráficos abaixo, podemos constatar que a saúde dos fetos estão divididas da seguinte maneira.

  • Normais: 77.8 %
  • Suspeitos: 13.9 %
  • Patológicos: 8.3 %

Além disso, é importante observar que nos dados existem muito mais fetos saudáveis do que os que apresentam alguma tipo de patologia. Este tipo de reflexão deve ser feito em todos os projetos de Data Science. Caso tenha mais interesse, fiz uma análise em que são envolvidos dados desbalanceados.

Os Gráficos de dispersão são utilizados para verificar se há uma possível relação entre as variáveis numéricas de ambos os eixos. Com os conhecimentos deste artigo podemos desdobrar novos gráficos sobre o monitoramento da saúde fetal e tirar outras interpretações que sejam necessárias.

Neste nosso gráfico analisado, percebemos que os fetos que possuem algum tipo de patologia apresentam valores de frequência cardíaca fetal média menores do que se comparado aos fetos normais ou suspeitos. Além disso, a quantidade de acelerações pode ser um bom indicativo para avaliar a vitalidade do feto.

Na análise abaixo utilizaremos box plots, em português se costuma utilizar para estes plots o feíssimo termo: diagramas de caixa. Estes gráficos nos dão uma noção visual de como estão se comportando os dados e suas estatísticas descritivas como um todo. Para um bom cientista de dados é importante saber sobre estas caixinhas já de cabeça.

No primeiro gráfico (o da esquerda), conseguimos perceber que os fetos que apresentam saúde normal em sua maioria possuem maior quantidade de contrações uterinas se comparado aos fetos com algum tipo de suspeita ou patologia. Talvez este possa ser um bom indicador para avaliar a saúde do feto.

Já pelo segundo gráfico (o da direita) podemos ter alguns argumentos para a nossa suspeita: fetos que possuem algum tipo de patologia apresentam frequência cardíaca anormais mais vezes. Isso parece um pouco óbvio e a representação visual reforça essa ideia.

Criação dos Modelos de Machine Learning

Lá em cima falamos um pouco sobre variáveis alvo. Portanto, considero que você ainda está junto comigo e já sabemos o que queremos fazer com estas variáveis. Mas qual técnica você recomendaria utilizar para fazer este tipo de previsão? Usar um algoritmo de classificação supervisionada parece uma boa, já que queremos predizer a class label com os dados que foram fornecidos e temos um histórico com valores que foram rotulados.

Neste nosso caso analisado, o alvo pode ser um feto: saudável, suspeito de patologia ou com patologia confirmada. Por termos mais de três classes possíveis, este é um caso em que utilizaremos um algoritmo de classificação do tipo multiclasse. Portanto, mãos à obra e fé código!

Antes de iniciar qualquer coisa, você deve seguir a cartilha do cientista de dados e fazer o bla bla bla de sempre: SEPARAR OS DADOS EM TREINO E TESTE.

Obs: Se ainda está em dúvida do porquê, veja este vídeo urgentemente.

Como queremos classificar a saúde do feto utilizaremos o módulo de classificação do Pycaret. O * (asterisco) não é um palavrão em python, apenas é uma maneira de pedir para o código importar tudo que está no pycaret.classification.

ATENÇÃO: O setup do Pycaret é o primeiro e único método mandatório para este framework de AutoML. Neste módulo são realizados procedimentos de limpeza e preparação dos dados (aquela coisa que é a mais importante de tudo), separação em treino e teste, etc. Além disso, o setup oferece alguns parâmetros padrão que podem ser modificados e desta forma conseguimos passar cada etapa de nossa pipeline desejada para o futuro modelo. Em resumo, leia a documentação e mude os parâmetros sem moderação.

Nossa base de dados contém muitos valores numéricos. Uma boa prática é normalizar estes números pelo método Z-Score Normalization, não precisamos passar o nome desta estratégia em algum parâmetro para o PyCaret porque ele já vem por padrão ao setarmos o True.

Além disso, nossos dados possuem muitos valores que abordam a quase mesma ocorrência (histograma da média, histograma da moda, etc). Por isso, utilizaremos um método para remover as variáveis que estão extremamente correlacionadas (threshold = 0.9). Para isso, setamos o remove_multicollinearity como True.

Por fim, deixamos o session_id como 42 para possibilitar que este notebook possa ser reproduzido.

Antes de saber qual modelo escolher, podemos utilizar o Compare Models. Deste modo, conseguimos avaliar as principais métricas de classificação de todos os modelos que estão na biblioteca do PyCaret. É importante ressaltar que esta comparação é feita utilizando os valores padrão dos hiperparâmetros de cada modelo, além disso o próprio PyCaret utiliza uma cross-validation com número de folds iguais a 10, número que pode ser alterado, para calcular a média das métricas.

As métricas disponíveis para o módulo de classificação são:

  • Accuracy — quanto o modelo classificou corretamente considerando todas as classes.
  • AUC — fornece uma estimativa da probabilidade de classificação correta de um feto escolhido ao acaso.
  • Recall — número de fetos que foram corretamente preditos como saudáveis sobre o número de fetos saudáveis. (obs: pode ser considerado os outro tipo de saúde dos fetos também).
  • Precision — número de fetos que foram corretamente preditos como saudáveis sobre todos os fetos que foram preditos como saudáveis — corretos + incorretos. (obs: pode ser considerado os outro tipo de saúde dos fetos também).
  • F1 — média harmônica entre a precision e o recall .
  • Kappa — concordância entre os valores previstos e os valores reais.
  • MCC — mede a qualidade do modelo considerando todos os campos da matriz de confusão.

Obs: TT (sec) é o tempo de execução do modelo em segundos. Além disso, utilizamos o parâmetro sort para ordenar de forma decrescente os modelos pela métrica Precision.

Se você já deu uma olhada no código pode ter se surpreendido com este parâmetro n_select. Este artigo do próprio criador do PyCaret nos ensina que o método compare_models retorna os n modelos selecionados, inclusive já criando estes modelos para gerar plots de análise ou fazer previsões.

Melhores modelos retornados pelo compare_models

Uma sucinta explicação do Light Gradient Boosting Machine

O Light Gradient Boosting Machine é uma biblioteca de código aberto que fornece uma implementação eficaz do algoritmo de aumento de gradiente estocástico. Esse algoritmo se popularizou em competições de machine learning devido ao seu grande desempenho preditivo com dados tabulares. Curiosidade: ainda não existe uma versão Diet, porém o nome Light vem do fato de seu processamento exigir muito menos dos computadores. Ou seja , ele é mais leve que os outros algoritmos de seu nível.

O LightGBM usa a aprendizagem baseada em árvores para construir suas soluções. As árvores são adicionadas uma de cada vez ao conjunto e treinadas para corrigir os erros das anteriores. Os erros são calculados por qualquer função de perda e um algoritmo de otimização de gradiente descendente é utilizado para minimizar esta perda.

Caso tenha interesse em se aprofundar neste algoritmo, aqui está a documentação oficial.

Uma sucinta explicação do Gradient Boosting Classifier

De forma simples, o Gradient Boosting Classifier é um algoritmo que constrói seus modelos em etapas, sempre aprendendo com os erros anteriores e melhorando a cada passagem. Desta forma, utilizando a técnica de ensemble o modelo faz a construção de pequenas árvores de decisão e agrega as predições destas árvores de forma incremental, ou seja, a construção da próxima árvore leva em conta os erros das árvores passadas. Caso tenha mais interesse no algoritmo, busque informações em: Gradient Boosting In Classification: Not a Black Box Anymore!.

Hyperparameter Tuning

Ok, temos os modelos prontos para resolver nossos problemas. Mas iremos fazer toda a análise com os parâmetros default? Não!. Para contornar esta situação utilizaremos a função Tune Model do PyCaret.

O tune_model utiliza o Randomized Grid Search para encontrar os melhores hiperparâmetros. Por default, ela utiliza um n_iter (número de iterações) iguais a 10. Ou seja, são selecionados 10 valores aleatórios para cada hiperparâmetro e posteriormente se avalia o resultado destas combinações. Nós podemos alterar a quantidade de iterações mudando o parâmetro n_iter. Naturalmente, quanto mais iterações colocarmos mais tempo levará ao processamento completo do modelo.

Além disso, também é possível otimizar o nosso modelo através da métrica que foi escolhida. Para isto, alteramos o parâmetro optmize. É importante ressaltar que por padrão, este parâmetro vem com a métrica Accuracy, portanto devemos obrigatoriamente alterar este parâmetro caso exista interesse na otimização em função de outra métrica.

Você já deve ter notado no compare_models que optamos por ordenar os valores pela métrica Precision. Escolhemos esta métrica por acreditar que ela seja a mais útil para ser analisada no problema. Uma vez que é mais importante o modelo acertar corretamente cada classe, ou seja, estamos buscando um algoritmo quase perfeito que não preveja fetos patológicos como saudáveis.

Checando os dois modelos de classificação escolhidos

Após selecionados os dois modelos, precisamos escolher qual deles utilizaremos para realizar as nossas previsões. Por conta disso, precisamos fazer uma checagem final utilizando o Predict Model do PyCaret.

Output do predict_model para o primeiro modelo
Output do predict_model para o segundo modelo

Avaliando as principais métricas dos modelos, percebemos que o primeiro modelo apresenta os melhores valores tanto para Recall quanto para Kappa. Desta forma, vamos optar em utilizá-lo nos próximos passos!

Bom, agora é necessário finalizar nosso modelo e para isso utilizamos o Finalize Model com o objetivo final de treinar o nosso modelo em todo o dataset que foi inicialmente disponibilizada para treino.

Prevendo classes com dados não vistos

Lembra que lá em cima nós separamos os dados em treino e validação? Até agora, utilizamos somente os dados de treino. Chegou a hora de nosso modelo conhecer os dados de validação e mostrar o seu poder de previsão. Para isso, retornaremos a utilizar o método predict_model, mas desta vez o parâmetro data será o dos dados de validação. Vale reforçar que ao chamarmos o predict_model novas colunas que representam a de previsões e as probabilidades associadas a estas previsões serão criadas. As colunas se chamarão Label e Score, respectivamente.

Após nossa previsão, temos que avaliar quantas vezes nosso modelo errou e qual foi a gravidade deste erro. Ao todo, o modelo previu incorretamente a saúde de 10 fetos, o que representaria cerca de 5% da base de dados de validação.

Para facilitar a compreensão, utilizaremos um gráfico que destacará em vermelho as 10 entradas que foram incorretamente rotuladas.

OK, errar é humano. E parece que os erros também pertencem a máquinas.

Agora é preciso saber exatamente quais fetos o modelo previu incorretamente. Para isso, criaremos uma tabela como todos os dados que foram previstos de forma errada.

Esta tabela contém todas as variáveis associadas a estes fetos. A coluna fetal_health representa o valor classificado por um médico. Já a coluna Label representa o classificado pelo algoritmo.

A tabela é muito grande e contém muitas informações, para uma melhor análise é melhor ir até o meu COLAB. Caso tenha interesse em mais projetos, consulte meu GITHUB.

Conclusões ✅

  • O número de acelerações e a frequência média cardíaca dos fetos dão uma noção da real saúde do feto. No entanto, é sempre muito importante ter o acompanhamento de um médico para uma melhor avaliação.
  • Problemas do tipo multiclasse precisam de uma maior atenção e tratamentos adequados.
  • Encontrar métricas para projetos de classificação nunca é uma tarefa fácil. É sempre importante analisar qual a principal resposta que se busca no problema e qual indicador se quer atingir.
  • Ler a documentação do PyCaret é fundamental para uma melhor performance do modelo.
  • O modelo se mostrou consistente, uma vez que apresentou uma pequena discrepância de apenas 5% e não fez nenhuma previsão absurda — a de um feto patológico ser considerado saudável.

--

--

Thales Ferraz

Explorando a paixão por dados e modelagem matemática. Compartilho insights valiosos e soluções eficazes para problemas cotidianos.