📉 Previsão de Churn de Ponta a Ponta

Do Notebook à API em Produção

Olá, mundo! 👋 Hoje vou contar como foi construir um projeto completo de previsão de churn na minha pós em Machine Learning Engineering (FIAP). Muita gente para no “treinei o modelo e a métrica ficou boa”. Aqui a ideia foi ir além e chegar numa API rodando na nuvem, com testes, Docker e documentação. Vou explicar cada etapa passo a passo, com analogias, para você conseguir reproduzir o raciocínio nos seus próprios projetos.


🧠 O Que É Churn (e Por Que Empresas Se Importam Tanto)?

Imagine que você é dono de uma academia. Todo mês, alguns alunos cancelam a matrícula. Se você só descobre isso quando o aluno já foi embora, não dá mais para fazer nada. Agora imagine que você consegue identificar antes quem está com cara de quem vai sair (frequência caindo, plano mais caro, sem contrato longo) e oferecer uma condição especial a tempo.

Isso é churn prediction: usar dados históricos para estimar a probabilidade de cada cliente cancelar o serviço, dando à empresa a chance de agir antes.

Exemplo Prático:

No projeto, o cenário era uma operadora de telecomunicações perdendo clientes em ritmo acelerado. A diretoria queria um modelo que apontasse quem tinha risco de cancelamento. Usei o dataset público IBM Telco Customer Churn, com 7.043 clientes e 21 variáveis (perfil, serviços contratados, cobrança e tempo de casa).

Piadinha:
É igual namoro: se você só percebe que a pessoa quer terminar depois que ela já fez as malas, faltou dado (e atenção) lá atrás. 😅


🗺️ Antes de Codar: Definindo o Problema

A primeira etapa não teve nenhuma linha de código. Preenchi um ML Canvas, um documento que responde a perguntas como:

  • Quem vai usar as previsões? (equipe de retenção, marketing, gestão)
  • Como o negócio mede sucesso? (taxa de churn, retenção, custo das ações)
  • Qual a métrica técnica do modelo? (F1-score, com ROC-AUC, precision e recall de apoio)

Pense no ML Canvas como a planta da casa. Ninguém começa a construir sem planta, porque descobrir no meio da obra que faltou um banheiro custa caro.


🔎 Conhecendo os Dados: A Fase Detetive (EDA)

A análise exploratória (EDA) é o momento de conhecer os dados antes de treinar qualquer coisa. E ela já rendeu uma pequena investigação.

O mistério do TotalCharges: a coluna vinha como texto, e o culpado eram 11 registros com um espaço em branco no lugar do valor. Em vez de “forçar” a conversão, fui investigar. Resultado: todos eram clientes com tenure = 0, ou seja, recém-chegados que ainda não tinham fechado um ciclo de cobrança. O dado não estava errado, ele simplesmente ainda não existia. Como eram só ~0,15% da base, removi esses registros e segui com 7.032 clientes.

O que os dados mostraram:

  • Tempo de casa importa: a mediana de tenure é de ~10 meses em quem cancela e ~38 meses em quem fica.
  • Contrato é decisivo: contratos mensais têm ~43% de churn, contra ~11% nos anuais e ~3% nos de dois anos.
  • Suporte protege: sem OnlineSecurity e TechSupport, o churn fica entre 42% e 48%, contra ~15% em quem tem esses serviços.

Regra de Ouro:
Nunca converta ou apague dados “às cegas”. Investigue primeiro: o motivo do dado estranho muitas vezes é uma informação de negócio. 🔍


📏 Como Medir Sucesso: Accuracy Engana!

Aqui vem um detalhe importante: só 27% dos clientes cancelam. Isso significa que um modelo “preguiçoso”, que sempre responde “não vai cancelar”, acertaria ~73% das vezes e não serviria para absolutamente nada. Por isso, acurácia não foi a métrica principal.

Para entender as métricas que usei, imagine um pescador com uma rede tentando pegar peixes (clientes que vão cancelar) numa lagoa cheia de peixes e de botas velhas:

  • Recall: dos peixes da lagoa, quantos você pegou? (Se sobrou muito peixe, seu recall é baixo.)
  • Precision: do que veio na rede, quanto era peixe de verdade? (Se veio muita bota, sua precision é baixa.)
  • F1-score: a nota que equilibra os dois. Não adianta pegar todos os peixes com uma rede que também leva a lagoa inteira.
  • ROC-AUC: mede o quanto o modelo consegue separar peixes de botas, independentemente de onde você decide cortar.
MétricaPergunta que responde
RecallDos clientes que cancelaram, quantos eu identifiquei?
PrecisionDos que eu apontei como risco, quantos realmente cancelaram?
F1-scoreQual o equilíbrio entre as duas?
ROC-AUCO modelo separa bem quem cancela de quem fica?

🥊 A Batalha dos Modelos: Simples x Complexo

Comecei pelo mais simples possível, uma Regressão Logística (o baseline). Baseline é o “tempo de referência” de uma corrida: sem ele, você não sabe se o modelo sofisticado realmente melhorou alguma coisa.

Montei tudo dentro de um Pipeline do scikit-learn e fiz a divisão treino/teste (80/20, estratificada, random_state=42) antes de qualquer transformação. Isso evita o vazamento de dados (data leakage), que é como estudar para a prova com o gabarito colado ao lado. A nota fica ótima e não vale nada.

Depois testei o que normalmente se tenta para “melhorar”:

AbordagemF1PrecisionRecall
Regressão Logística (threshold 0,50)0,6100,6500,575
+ Features de engenharia0,5750,6330,527
+ class_weight="balanced"0,6080,4910,797
Random Forest (ajustada)0,6180,5040,797
Rede Neural MLP (ajustada)0,6150,4990,799
Regressão Logística + threshold 0,400,6290,5820,684

O que essa tabela ensina:

  1. Mais features nem sempre ajudam. As que criei (num_services, tenure_group) até pioraram o F1, porque o modelo linear já capturava essa informação pelas variáveis originais.
  2. O ROC-AUC quase não mexeu (ficou entre 0,833 e 0,836 em todos os modelos). Trocar de algoritmo não melhorou a capacidade de separação, o que sugere que o “teto” estava nos dados.
  3. A melhoria mais barata foi a que funcionou: sem retreinar nada, só mudando o ponto de corte, o F1 subiu para 0,629.

Dica do Professor:
Antes de comprar o carro esportivo, teste o popular. Às vezes ele já te leva aonde você precisa. 🚗


🎚️ O Threshold: O Botão de Sensibilidade

Todo modelo de classificação devolve uma probabilidade (ex.: “62% de chance de cancelar”). Quem transforma isso em “sim” ou “não” é o threshold (ponto de corte). O padrão é 0,50, mas eu usei 0,40:

python

probabilidade = modelo.predict_proba(dados)[0, 1]
previsao = int(probabilidade >= 0.40)

Pense num detector de fumaça. Se ele for pouco sensível, só apita quando a casa já está em chamas. Se for sensível demais, apita toda vez que você frita um ovo. Baixar o threshold de 0,50 para 0,40 é aumentar a sensibilidade: o modelo passa a pegar mais clientes em risco (o recall foi de 0,575 para 0,684), ao custo de mais alarmes falsos (a precision caiu de 0,650 para 0,582).

Qual é o valor certo? Depende do custo de cada erro:

  • Deixar passar quem ia cancelar = perder a receita do cliente.
  • Abordar quem não ia cancelar = gastar uma ação de retenção.

Se retenção é barata e o cliente vale muito, faz sentido ser sensível. Se a ação é cara, o corte sobe. É uma decisão de negócio, não só de estatística.

Um detalhe de engenharia: tratei o threshold como uma regra de decisão aplicada depois do treino, separada do modelo. Assim, se o negócio mudar de estratégia, não é preciso retreinar nada.

Piadinha:
Threshold é o botão de “sensibilidade” do seu modelo. Só cuidado para não deixar no máximo, ou até o espirro do cliente vira alerta de cancelamento. 🤧


🏗️ Do Notebook à API: Engenharia de Verdade

Um notebook com boas métricas é como cozinhar bem em casa. Uma API em produção é abrir o restaurante: precisa de padrão, receita escrita, higiene e gente que não é você conseguindo operar. Foram cinco passos:

  1. Modularizar: tirei o código do notebook e organizei em src/, com arquivos de responsabilidade única (preprocessing.py, predict.py, api.py). O modelo é carregado uma vez, na inicialização, e não a cada requisição.
  2. Criar a API (FastAPI): dois endpoints, /health (a API está no ar?) e /predict (recebe os dados do cliente e devolve a previsão). A entrada é validada com Pydantic, então uma categoria inexistente (ex.: um tipo de contrato inventado) é rejeitada antes de chegar ao modelo.
  3. Testar (pytest): quatro testes, cobrindo a lógica de predição (com um cliente de baixo risco e outro de alto risco) e os endpoints da API.
  4. Empacotar (Docker): imagem python:3.11-slim, com as dependências copiadas primeiro para aproveitar o cache, e só src/ e models/ dentro da imagem. É a “mala pronta”: roda igual em qualquer máquina.
  5. Publicar (Render): deploy em nuvem. Como é plano gratuito, a primeira chamada após um tempo parado pode demorar alguns segundos (cold start).

A resposta da API é simples, e propositalmente devolve a probabilidade junto com a classificação:

json

{
  "churn": 1,
  "probabilidade": 0.6235,
  "threshold": 0.4
}

Para quem prioriza ações de retenção, um cliente com 62% de risco e outro com 41% não merecem o mesmo tratamento, embora os dois estejam acima do corte.


🚧 Limitações: O Que o Modelo Não Sabe

Um bom projeto também diz onde ele não vale. Registrei isso no Model Card (uma “bula” do modelo):

  • O threshold foi escolhido olhando o conjunto de teste (1.407 clientes). O ideal, em produção, seria escolher o corte num conjunto de validação separado e guardar o teste só para a avaliação final. As métricas devem ser lidas com essa ressalva.
  • Só foi validado em um dataset. Em outra empresa ou outra população, o resultado pode ser diferente.
  • Clientes mudam com o tempo. Em uso real, é preciso monitorar e retreinar.
  • A previsão é um indicador de risco, não uma sentença. Ela apoia a decisão da equipe de negócio, não a substitui.

⚠️ Erros Comuns (e Como Evitá-los)
  1. Confiar na acurácia com dados desbalanceados
  • Errado: comemorar 73% de acerto num problema em que 73% já é “todo mundo fica”.
  • Certo: usar F1, recall, precision e ROC-AUC.
  1. Pular direto para o modelo complexo
  • Errado: começar com rede neural sem ter um ponto de comparação.
  • Certo: criar um baseline simples e exigir que o modelo complexo prove que vale o custo.
  1. Aceitar o threshold padrão sem pensar
  • Errado: usar 0,50 porque “é o padrão”.
  • Certo: escolher o corte considerando o custo de cada tipo de erro.
  1. Transformar os dados antes de separar treino e teste
  • Errado: normalizar a base inteira e só depois dividir (vazamento de dados).
  • Certo: dividir primeiro e ajustar as transformações apenas no treino, dentro de um Pipeline.
  1. Parar no notebook
  • Errado: entregar só o .ipynb com boas métricas.
  • Certo: modularizar, testar, empacotar e documentar.

📊 Tabela de Referência Rápida
Etapa do ProjetoO que resolve
ML CanvasAlinha problema, stakeholders e métricas antes de codar
EDARevela padrões e evita tratar dados “às cegas”
BaselineDá um ponto de comparação honesto
Pipeline + split antes do treinoEvita vazamento de dados
Ajuste de thresholdAlinha o modelo ao custo real dos erros
Testes (pytest)Garantem que a lógica continue funcionando
API + Docker + NuvemTornam o modelo utilizável por outras pessoas
Model CardDocumenta limitações e uso pretendido

🚀 Por Que Isso Importa?

Este projeto reforça o que discuti no post sobre Clean Code: um bom modelo não é só métrica bonita. É a diferença entre:

  • Um experimento que só roda na sua máquina vs. uma solução que outra pessoa consegue usar, testar e auditar.
  • Um modelo que promete mais do que entrega vs. um modelo com limites claros e decisões justificadas.

No fim, o “vencedor” foi o modelo mais simples da lista, mas com uma decisão de negócio bem pensada por trás. Esse é o tipo de simplicidade que vale buscar.

Curiosidade:
Em problemas desbalanceados, um modelo que sempre responde “não” pode ter mais de 70% de acurácia e ser completamente inútil. É por isso que, em churn e em fraude, quase ninguém olha só a acurácia. 🤯


📎 Explore o projeto:

E você, já ajustou o threshold de algum modelo pensando no custo do erro? Conta nos comentários!

📌 Hashtags: #MachineLearning #ChurnPrediction #MLEngineering #FastAPI #Docker #TechLegacy

Até a próxima! 👨💻

Eduardo C.
TechLegacy: Transformando código em legado!

Deixe um comentário