← Voltar pro projeto

AUC Boa, F1 Zero: a Armadilha do Threshold Padrão

O capítulo 3 definiu as features de cada um dos quatro cenários. Agora é a hora de treinar os modelos de verdade, comparar contra baseline, e registrar cada tentativa com MLflow (parâmetro e métrica de cada run, só local, mlruns/, sem subir pra lugar nenhum, é boa prática de rastreabilidade, não vira gráfico aqui no blog). Notebook completo, executado, público no Colab.

Cenário 1: atraso na entrega, e a armadilha do threshold padrão

Baseline (sempre chuta "no prazo") → Regressão Logística → Random Forest → XGBoost, nas features honestas do capítulo 3 (95.968 pedidos, taxa real de atraso de 6,76%):

ModeloAUCF1PrecisãoRevocação
Baseline0,50000,00000,00000,0000
Regressão Logística0,60640,00311,00000,0015
Random Forest0,73880,00000,00000,0000
XGBoost0,72610,02680,40000,0139

Carregando dados reais...

Repara numa coisa estranha nessa tabela: o Random Forest tem a melhor AUC de todo mundo (0,7388), mas F1, precisão e revocação zerados. Não é bug, é a mecânica da coisa. AUC mede se o modelo consegue ranquear melhor os casos arriscados que os seguros, comparando pares de exemplo sem nunca decidir nada, e nisso o Random Forest manda bem. F1, precisão e revocação, em compensação, dependem de uma decisão binária: acima de 0,5 de probabilidade o modelo grita "vai atrasar!", abaixo ele fica calado. Com só 6,76% dos pedidos atrasando de verdade, o Random Forest aprendeu tão bem a reconhecer a classe majoritária que nunca cruza 0,5 de probabilidade pra nenhum pedido, nem pros que realmente atrasam. Ele sabe ranquear risco, só nunca "aposta" no positivo.

Precisão aqui é: dos pedidos que o modelo marcou como "vai atrasar", quantos atrasaram de verdade. Revocação é o oposto: dos pedidos que atrasaram de verdade, quantos o modelo pegou. F1 é a média harmônica dos dois, um jeito de resumir "o modelo é bom nas duas pontas" num só número. É a mesma tríade que eu já tinha usado lá na playlist Pattern Recognition, na detecção de fraude em cartão de crédito, outro problema de classe super desbalanceada, e o sintoma é parecido: métrica de ranking (AUC) parece ótima, métrica de decisão (F1) desmascara que o modelo, do jeito que está, não serve pra decidir nada sozinho.

O XGBoost, apesar da AUC um pouco menor (0,7261), é o único que realmente "aposta": F1 0,0268, precisão 0,40 (quando ele marca "vai atrasar", acerta em 4 de cada 10 vezes), revocação 0,0139 (mas só pega 1,4% dos atrasos reais). Nenhum dos quatro está pronto pra produção assim, do jeito que está publicado aqui. O próximo passo de verdade seria mexer no threshold de decisão em vez de usar 0,5 cego, ou balancear a classe no treino, mas isso é assunto pra outro dia, esse capítulo é sobre comparar modelo, não sobre ajustar limiar de decisão.

Cenário 2: nota da avaliação (regressão)

Baseline (sempre chuta a média) → Regressão Linear → Random Forest → XGBoost, nas features do capítulo 3 (95.829 pedidos):

ModeloMAE
Baseline-0,00010,9950
Regressão Linear0,12320,9228
Random Forest0,20280,8765
XGBoost0,18610,8794

Carregando dados reais...

R² mede quanto da variação da nota o modelo explica (1,0 seria previsão perfeita, 0 seria "chutar sempre a média"). MAE é o erro médio em estrelas, quanto o modelo erra pra cima ou pra baixo, na média, comparado com a nota real. O Random Forest ganha aqui, de raspão, do XGBoost (0,2028 contra 0,1861), o oposto do que normalmente se espera desses dois. Mas o dado mais honesto é o teto baixo dos dois: nem o melhor modelo passa de 20% de variância explicada. Isso bate com o que o capítulo 3 já tinha achado usando informação mútua, atraso_dias e delivery_days dominam disparado a nota, e sobra pouca informação nas outras features (preço, frete, parcelas) pra um modelo qualquer explorar. A nota do cliente depende de um monte de coisa que não está nesse dataframe (qualidade real do produto, embalagem, atendimento), não é falta de modelo melhor, é falta de feature.

Cenário 3: valor do frete (regressão)

Baseline → Regressão Linear → Random Forest → XGBoost, nas features físicas do produto mais a distance_km calculada no capítulo 3 (98.650 pedidos):

ModeloMAE
Baseline-0,00018,6057
Regressão Linear0,53225,3800
Random Forest0,61784,5947
XGBoost0,63854,3112

Carregando dados reais...

Aqui sim o XGBoost ganha claro dos outros três, R² 0,6385 e erro médio de R$ 4,31. Faz sentido físico: transportadora calcula frete numa fórmula que mistura peso, dimensão e distância de um jeito não-linear (peso volumétrico, faixas de preço por distância), e árvore de decisão (Random Forest, XGBoost) capta esse tipo de não-linearidade muito melhor que uma reta de Regressão Linear, que já fica em segundo lugar mesmo assim (R² 0,53). Comparado ao teto baixo do cenário 2, esse aqui é o cenário onde "mais dado numérico relevante" realmente compra performance de modelo.

Cenário 4: segmentação de clientes, primeira rodada do RFM

Diferente dos três anteriores, aqui não tem "modelo certo" pra comparar, é K-means rodando pra vários valores de K (de 2 a 8) nas três variáveis RFM escalonadas (recência, frequência, valor monetário) do capítulo 3. Pra escolher o K, usei uma métrica diferente do método do cotovelo que o professor usou lá na playlist Pattern Recognition: o coeficiente de silhueta. Ele mede, pra cada cliente, o quão parecido ele é do próprio grupo comparado ao grupo vizinho mais próximo, e a média de todo mundo vira o score do K inteiro. Fica entre -1 (cliente no grupo errado) e 1 (grupos compactos e bem separados).

KSilhuetaInércia
20,7430207.275,6
30,4569141.952,7
40,490094.963,3
50,419579.940,5
60,438765.668,3
70,441856.032,0
80,449550.353,9

Carregando dados reais...

K=2 disparado na frente (0,743, quase o dobro de qualquer outro K), e a inércia cai suave e continuamente sem cotovelo óbvio nenhum, dois sinais concordando: o corte mais "limpo" estatisticamente é dividir a base em dois grupos só. Só que eu já sei, do capítulo 3, o que esse corte provavelmente É: os 3,06% de clientes que compraram mais de uma vez contra os 96,94% de compra única. Estatisticamente é o K mais "puro", mas de negócio é quase a mesma informação que eu já tinha sem rodar clustering nenhum. Do K=3 pra frente a silhueta oscila meio sem padrão claro (entre 0,42 e 0,49), sem um segundo K que se destaque com folga. Vou levar essa tensão (K estatisticamente ótimo vs. segmentação rica o suficiente pra virar ação de negócio) pro capítulo 5, que é onde eu de fato escolho um K final, visualizo os grupos e interpreto o que cada um significa.

Fechando o capítulo

Os quatro modelos treinados e comparados contra baseline, com MLflow registrando cada tentativa. O achado mais importante não foi "qual modelo ganhou", foi a demonstração de que AUC boa não garante decisão boa: o Random Forest do cenário 1 tem a melhor métrica de ranking e zero utilidade prática no threshold padrão, uma armadilha real de quem só olha uma métrica e já sai comemorando. Frete é o cenário onde o modelo mais "compra" performance real (XGBoost, R² 0,64), nota de avaliação esbarra num teto baixo de informação disponível, e RFM levanta uma pergunta em aberto sobre o que significa "melhor" clusterização, que fica pro capítulo 5, junto com SHAP pra abrir a caixa-preta desses modelos e as conclusões finais do projeto.