← Voltar ao estudo de caso

Aprofundamento técnico · Face Clock Evoluir

A estrutura técnica nos detalhes.

Esta página documenta o que sustenta os números do estudo de caso: como cada abordagem de classificação foi comparada sob o mesmo protocolo, como um bug silencioso foi diagnosticado, como a causa do subajuste do modelo foi isolada por experimento e como a aplicação é protegida em camadas.

  • Experimentos controlados
  • Comparação de baselines
  • scikit-learn
  • PyTorch
  • Segurança da aplicação

Experimento de classificação

Uma escada de baselines, do trivial ao híbrido.

O classificador de atestados não foi apenas ajustado até parecer bom: foi comparado com alternativas explícitas, do classificador trivial ao híbrido, todas sob o mesmo protocolo de seleção e a mesma medição final.

O classificador vive em um serviço próprio e foi estruturado com fine-tuning seletivo e uma comparação reproduzível entre abordagens. A partição é estratificada, gerada por script com semente fixa e versionada: 297 documentos para treino, 64 para validação e 64 para teste final; o conjunto de teste não participou nem do early stopping nem da escolha de hiperparâmetros.

A comparação foi montada como uma escada de baselines, do trivial ao híbrido. Cada candidato teve seus hiperparâmetros escolhidos por busca em grade com validação cruzada estratificada de cinco dobras dentro do treino, otimizando F1 da classe minoritária — atestado representa 31% da base. Nenhuma imagem de teste participou da busca; ela só foi usada na medição final. O piso é dado por um classificador que sempre responde a classe majoritária: 68,8% de accuracy com F1 zero, o que já mostra que accuracy isolada não descreve este problema.

Escada de baselines no teste reservado
Abordagem Accuracy Precisão Recall F1
Classe majoritária (piso) 0,6880,0000,0000,000
HOG + SVM linear 0,7500,5830,7000,636
HOG + SVM RBF 0,7970,7330,5500,629
EfficientNet, cabeça original 0,7660,6090,7000,651
Embeddings + Regressão Logística 0,8750,8750,7000,778
64 documentos de teste, avaliados uma única vez. Precisão, recall e F1 referem-se à classe atestado; o limiar é o padrão de 0,5.
0,778 F1 para atestado da solução vencedora: EfficientNet como extratora de embeddings + Regressão Logística
0,875 accuracy da solução híbrida no teste reservado, acima da cabeça original da rede (0,766)
−7 falsos positivos em relação à cabeça neural original, preservando o mesmo recall de 0,700

O experimento evitou uma conclusão superficial em duas direções. HOG + SVM, uma abordagem inteiramente clássica, chegou a F1 0,636 — melhor que o piso, mas abaixo da cabeça original da rede (0,651): bordas e layout não bastam para separar os documentos. E o ganho da alternativa vencedora não veio de trocar a rede por um método clássico, e sim de recombiná-los: a EfficientNet fornece os embeddings de 1.280 dimensões e a Regressão Logística faz a decisão final. O OCR continua como camada de revisão por regras.

As duas cabeças recuperam a mesma quantidade de atestados, 14 dos 20 do teste; o que muda é o custo dessa recuperação, de 9 falsos positivos para 2. E a melhora não é apenas escolha de limiar: a ordenação dos documentos por probabilidade também ficou melhor, com ROC AUC de 0,814 para 0,892 e PR AUC de 0,697 para 0,823.

Com 64 documentos de teste, diferenças pequenas de F1 não sustentam ranking. Entre os três classificadores sobre embeddings, a validação cruzada ficou dentro de 0,004 (0,904 a 0,908) e a ordem observada no teste não é significativa; nos baselines HOG, o melhor em validação cruzada nem foi o melhor no teste. A validação cruzada dos embeddings também não é comparável de igual para igual com a do HOG: o backbone foi ajustado nessas mesmas imagens de treino, então seu número interno é otimista por construção. A comparação justa entre as duas famílias é a do teste, onde nenhuma delas tinha visto os documentos.

Método de investigação

Medir antes de concluir.

O OCR do pipeline vinha entregando resultados ruins em parte dos documentos. Em vez de trocar a ferramenta, a origem do problema foi investigada — e não estava no OCR.

468 documentos triados automaticamente por sinais objetivos de qualidade
38 imagens chegavam rotacionadas ao modelo por metadado EXIF ignorado
2.071 linhas removidas ao concluir que a premissa de um experimento não se sustentava
  • Diagnóstico do bug silencioso: fotos de celular carregam na tag EXIF Orientation a rotação necessária para exibição. Visualizadores aplicam essa tag; a biblioteca de imagem usada no pipeline, não. O documento chegava deitado à rede neural e ao OCR sem gerar erro algum — apenas texto ilegível. Nas amostras verificadas, normalizar a orientação levou a extração de nenhum termo reconhecido para a identificação correta do documento.
  • Descarte do próprio falso positivo: o detector de orientação do OCR apontava rotação em 45% da base. O cruzamento com o tipo de arquivo mostrou que 91 dos 109 casos de “180°” eram capturas de tela — que nunca estão de cabeça para baixo. O sinal era ruído do detector, não característica do dado; a hipótese só foi confirmada pelo metadado EXIF.
  • Correção sem desalinhar treino e produção: normalizar a orientação apenas na inferência criaria train/serve skew, já que o modelo havia sido treinado com as mesmas imagens rotacionadas. A correção foi centralizada em um único ponto de leitura, aplicada também ao carregador de dados do treino, seguida de re-treinamento — cujo efeito sobre o classificador foi reportado como neutro, não como ganho.
  • Escolha de métrica por adequação, não por sofisticação: a divergência de Jensen-Shannon foi cogitada para comparar dois pipelines e descartada após demonstração numérica: em um caso construído com discordância em 100% dos documentos, a divergência entre as distribuições marginais resulta exatamente zero. A comparação passou a usar instrumentos pareados, que enxergam a discordância documento a documento.

Diagnóstico experimental

Descartar hipóteses, inclusive as próprias.

O classificador não estava aprendendo o quanto deveria. Em vez de partir para o palpite usual — modelo maior — cada causa candidata virou um experimento controlado, com grupo de controle e uma variável por vez.

3 hipóteses descartadas por experimento: capacidade, sobreajuste e regularização da perda
4 épocas para os mesmos parâmetros ajustarem por completo um subconjunto, refutando falta de capacidade
+16 pontos percentuais de acurácia em validação, somando duas causas isoladas por experimento
  • A resposta óbvia, testada e rejeitada: a reação padrão a um modelo que não aprende é aumentar a capacidade. O teste de memorização mostrou que os mesmos parâmetros treináveis ajustam integralmente um subconjunto pequeno em quatro épocas quando a regularização é retirada. Capacidade existia de sobra — parâmetros adicionais custariam treino mais lento sem mover o resultado.
  • Uma hipótese própria, derrubada pelo experimento: a suspeita inicial era que a suavização de rótulos impunha um teto de confiança, já que o teto teórico ficava logo acima do máximo observado. O experimento controlado removeu a suavização e o teto não subiu. A proximidade entre os dois números era coincidência, não causa — e a conclusão anterior foi revista.
  • A correção intuitiva pioraria o sistema: o limiar de decisão parecia alto demais e baixá-lo seria o ajuste natural. Simular o pipeline completo em nove limiares mostrou o oposto: reduzir o valor melhora o classificador isolado e degrada o resultado final, porque passa a dar voz ao componente menos preciso. A medição evitou uma regressão em produção.
  • A causa real, e uma segunda hipótese corrigida: restava o conjunto de transformações aplicadas às imagens de treino. A suspeita era o espelhamento horizontal, inválido para documentos porque texto espelhado não existe entre as entradas reais. Removê-lo isoladamente não mudou nada mensurável; reduzir a carga completa de transformações elevou a acurácia de validação em treze pontos percentuais e manteve o modelo aprendendo por vinte e três épocas, contra quatro antes. O problema era o volume agregado, não a transformação que a intuição apontava.
  • Um segundo gargalo, exposto pela primeira correção: com o modelo já melhor, notei que o treino parava enquanto ainda melhorava — o critério de parada media acurácia, que num conjunto pequeno de validação avança em saltos grandes e satura. Comparei três critérios em treinos isolados; o que eu havia apostado ficou em último, e o vencedor levou o modelo a treinar três vezes mais tempo antes de parar.
  • A conclusão anterior, invalidada pelas próprias correções: enquanto o classificador estava travado, a medição indicava que ele quase não contribuía — o OCR sustentava o resultado sozinho. Refazer a mesma avaliação inverteu o quadro: a rede passou a cruzar o limiar de decisão em 23,7% dos documentos contra 0,6% antes, a concordância entre os dois componentes saiu do nível do acaso para moderada, e o erro quadrático das probabilidades caiu a menos de um quarto do valor original. A conclusão valia para o modelo defeituoso, não para o problema. Como os instrumentos de avaliação já estavam prontos, reverificar custou uma reexecução, sem escrever código novo.

Validação de arquitetura

Um alarme de overfitting que o 5-fold desarmou.

A configuração de descongelamento usada em produção parecia sobreajustar: 97% de acerto no treino contra 70% num teste de apenas 40 imagens. Em vez de aceitar o diagnóstico, três profundidades de descongelamento foram comparadas sob o mesmo protocolo de validação cruzada.

A rede parte de pesos pré-treinados na ImageNet; a pergunta é quantos blocos finais descongelar para o domínio de atestados. Três opções entraram na comparação: só a cabeça classificadora, a cabeça mais o último bloco de features, e a cabeça mais os dois últimos blocos — esta última, a configuração já em uso. Cada uma rodou em 5-fold estratificado sobre as 406 imagens de treino, até 30 épocas com parada antecipada de paciência 7, sempre com a mesma semente e o mesmo balanceamento de classes.

Acurácia de validação por profundidade de descongelamento, 5-fold Gráfico de colunas com barras de erro (desvio-padrão) comparando três configurações: A, 67,0% mais ou menos 3,9; B, 74,9% mais ou menos 6,7; C, a configuração em produção, 84,2% mais ou menos 1,7. 0% 25% 50% 75% 100% Configuração A: 67,0% de acurácia média de validação, descartada. 67,0% A — só a cabeça descartada Configuração B: 74,9% de acurácia média de validação, descartada. 74,9% B — +1 bloco descartada Configuração C: 84,2% de acurácia média de validação, mantida no pipeline. 84,2% C — +2 blocos (atual) mantida no pipeline
Acurácia média de validação de cada configuração, com desvio-padrão entre as cinco dobras: a configuração em produção não só vence — é também a mais estável das três.
84,2% ± 1,7 acurácia média de validação da configuração em produção — a mais alta e a mais estável das três
67,0% ± 3,9 acurácia da configuração que só treina a cabeça classificadora — capacidade insuficiente para o domínio
40 imagens no teste que gerou o alarme inicial — variância grande demais para sustentar uma conclusão sozinha

A configuração intermediária, com apenas um bloco descongelado, generalizou pior e de forma mais instável (74,9% ± 6,7%) do que a atual com dois blocos — descongelar mais não piorou o quadro, contrariando a suspeita natural de que mais parâmetros treináveis aumentariam o sobreajuste. O gap de 27 pontos percentuais que motivou o alarme inicial não se repetiu no 5-fold: ali, entre treino e validação, ficou em torno de 10 pontos para a configuração atual — a diferença vinha da variância de um conjunto de teste pequeno, não de memorização real. A arquitetura já em produção foi confirmada, e o próximo ganho de qualidade depende de mais dados — sobretudo da classe minoritária, não-atestado — e não de mexer na profundidade de descongelamento.

Engenharia e segurança

Confiabilidade construída em camadas.

A identidade é validada em diferentes níveis: usuário, sessão, autorização, dispositivo e tentativa biométrica. Cada camada reduz um tipo distinto de risco.

Em termos simples

O login gera uma credencial temporária assinada; as rotas protegidas validam essa credencial antes de executar qualquer ação.

Login → JWT assinado → Bearer token → claims validadas → rota autorizada

O OAuth2PasswordBearer do FastAPI padroniza como a API recebe o token. Neste projeto, ele não representa login social por Google ou Microsoft.

  • JWT com claims tipadas: o token assinado carrega identidade, perfil de acesso, modalidade de trabalho e expiração, sem confiar em um user_id enviado pelo cliente.
  • Logout persistente: tokens revogados ficam no PostgreSQL, permanecendo inválidos após reinícios e entre múltiplos processos da aplicação.
  • Senhas com bcrypt: hashes são produzidos pelo Passlib com custo computacional deliberado; a senha original nunca é armazenada ou recuperada.
  • Recuperação controlada: redefinições usam tokens de uso único, com expiração e marcação de utilização.
  • Autorização e anti-IDOR: além de autenticar, a API verifica se o usuário pode acessar cada recurso, inclusive documentos médicos sensíveis.
  • Totem provisionado: cada dispositivo usa API key obtida por ativação com PIN e proteção contra força bruta; a chave não é incorporada ao HTML público.
  • Biometria protegida: múltiplos frames, vivacidade, impressão SHA-256 anti-replay e controles para dados sensíveis segundo a LGPD.
  • Implantação restrita: backend exposto apenas localmente, HTTPS por túnel, migrações versionadas e volumes persistentes.

Contexto do projeto

Voltar à visão geral do sistema.