IntenBERT v2.0 — Documentação de Arquitetura e Capacidades
Modelo Base: BERTimbau (neuralmind/bert-base-portuguese-cased)
Domínio: Classificação de Intenção
Idioma: Português Brasileiro (PT-BR)
Formato de Produção: ONNX Runtime (CPU)
Parâmetros: 110M
Latência: ~50ms por mensagem em CPU
1. Arquitetura do Modelo
1.1 Backbone: BERTimbau
O modelo utiliza BERTimbau (Souza et al., 2020) como backbone, uma variação do BERT pré-treinada exclusivamente em corpora de português brasileiro. Diferente do BERT multilingual que divide capacidade entre 104 idiomas, o BERTimbau concentra toda a representação em ~3.5 bilhões de tokens de PT-BR (Wikipedia, web crawls, livros digitais, notícias).
Configuração arquitetural:
| Componente | Valor |
|---|---|
| Hidden size | 768 |
| Attention heads | 12 |
| Transformer layers | 12 |
| Intermediate (FFN) | 3072 |
| Max sequence length | 128 |
| Vocabulary | 29.7K tokens (WordPiece case-sensitive) |
| Parâmetros totais | ~110M |
A escolha de case-sensitive é deliberada: em WhatsApp Business, capitalização carrega sinal. "Bom dia" (saudação formal) tem semântica diferente de "bom dia" (dentro de frase). O tokenizador preserva maiúsculas, permitindo que o modelo aprenda essas distinções.
1.2 Head de Classificação
Sobre a saída do [CLS] do BERT, adicionamos uma camada densa de 768 → 4 unidades (uma por classe), sem ativação intermediária. A saída bruta (logits) alimenta uma Softmax durante treinamento (Cross Entropy) e inferência.
Input tokens → BERTimbau Encoder → [CLS] pooling → Linear(768, 4) → Softmax → Probabilidades
A saída é um vetor de probabilidade sobre as 4 classes:
- MACHINE_BOT: mensagem de URA, bot, protocolo, fila de espera
- HUMAN_POSITIVE: intenção explícita ou implícita de compra
- HUMAN_NEGATIVE: recusa, solicitação de remoção, spam
- HUMAN_INVALID: saudação, conversa fiada, curiosidade sem intenção comercial
2. Treinamento
2.1 Pipeline de Dados
O treinamento não usa dados reais (sensíveis sob LGPD). Em vez disso, construímos um pipeline de geração sintética controlada via LLM (Qwen2.5-7B-Instruct), seguido de deduplicação semântica e filtragem de qualidade.
Arquitetura do pipeline de dados:
Prompts de geração (few-shot por classe)
↓
Qwen2.5-7B-Instruct (vLLM inference)
↓
Raw dataset (~15K exemplos)
↓
Filtragem heurística (gibberish detection)
↓
Deduplicação semântica (embeddings all-MiniLM-L6-v2, cosine < 0.85)
↓
Dataset final (~12K exemplos)
↓
Split 80/10/10 (treino/valid/teste)
2.2 Engenharia de Prompts para Geração
Cada classe tem um prompt específico com 4 componentes:
- Contexto: "Você está simulando mensagens de WhatsApp Business..."
- Definição semântica: o que caracteriza a classe
- Few-shot examples: 3-5 exemplos concretos
- Constraints de diversidade: "inclua gírias, erros de digitação, regionalismos, mensagens curtas e longas"
Exemplo de prompt para HUMAN_POSITIVE:
Gere 80 mensagens curtas de WhatsApp de pessoas querendo COMPRAR,
pedir PREÇO ou fazer COTAÇÃO. Varie entre:
- Diretas: "Quanto custa?"
- Com contexto: "Bom dia! Gostaria de fazer uma cotação."
- Compra direta sem "preço": "Tenho interesse", "Quero comprar"
- Gírias: "Mano, manda o link aí que eu pago"
- Erros de digitação: "qnt custa?", "qr comprar"
2.3 Hard Negatives
O diferencial do dataset é a inclusão de casos adversariais (hard negatives) — exemplos que parecem pertencer a uma classe mas na verdade pertencem a outra. Isso força o modelo a aprender fronteiras de decisão mais nítidas:
| Tipo de Hard Negative | Exemplo | Classe Real |
|---|---|---|
| Elogio sem intenção | "Gostei muito do produto de vocês" | INVALID |
| Compra para terceiro | "Meu chefe pediu pra eu cotar" | POSITIVE |
| Curiosidade com preço | "Só estou pesquisando preços" | INVALID |
| Rejeição branda | "Vou deixar quieto, vlw" | INVALID |
| Formalidade enganosa | "Cordialmente, solicito retorno" | INVALID |
| Compra direta implícita | "Tenho interesse sim" | POSITIVE |
2.4 Hiperparâmetros de Treinamento
| Parâmetro | Valor | Justificativa |
|---|---|---|
| Learning rate | 2e-5 | Padrão BERT fine-tuning; maior que isso causa instabilidade |
| Batch size | 64 (train) / 128 (eval) | Máximo que cabe em H100 com mixed precision desabilitado |
| Epochs | 8 | Convergência observada após 6; 8 garante generalização |
| Weight decay | 0.01 | Regularização L2 leve para evitar overfit no dataset sintético |
| Warmup | 0% | Simplificado para estabilidade; LR constante desde o início |
| Max sequence length | 128 | 99% das mensagens de WhatsApp têm < 50 tokens |
| FP16 | Desabilitado | Prevenção de NaN observados em testes preliminares |
| Loss | Cross Entropy (label_smoothing=0) | Focal Loss testada mas causou instabilidade numérica |
| Seed | 42 | Reprodutibilidade completa |
2.5 Infraestrutura de Treinamento
- Hardware: NVIDIA H100 80GB (Lightning AI VPS)
- Framework: PyTorch 2.4.0 + Transformers 4.49.0 + Accelerate
- Otimizador: AdamW (monkey-patched para compatibilidade com accelerate 0.33)
- Dataset caching: Parquet para evitar regeneração entre runs
- Tempo de treinamento: ~45 minutos para 8 epochs com ~12K exemplos
2.6 Estabilidade e Convergência
O treinamento apresentou curvas estáveis:
- Loss de treino: decaimento monotônico até ~0.15
- Loss de validação: plateau após epoch 6, sem overfit severo
- Acurácia de validação: ~82% (ligeriamente otimista devido a distribuição sintética)
- Grad norm: estável, sem explosões
Não houve instâncias de NaN loss após a desabilitação de FP16 e a troca de ModernBERT para BERTimbau.
3. Exportação para ONNX
3.1 Motivação Arquitetural
O modelo PyTorch tem dependências pesadas (torch 2.4.0, CUDA opcional, 2GB+ de libs). Para deploy em produção, convertemos para ONNX Runtime:
- Portabilidade: ONNX é formato padrão, roda em qualquer linguagem
- Performance: ONNX Runtime aplica otimizações de grafo (constant folding, dead code elimination, kernel fusion)
- Tamanho: 400MB (mesmos pesos, mas sem overhead do PyTorch)
- Latência: ~50ms por mensagem em CPU moderna vs ~150ms PyTorch puro
3.2 Processo de Conversão
from optimum.onnxruntime import ORTModelForSequenceClassification
model = ORTModelForSequenceClassification.from_pretrained(
"lead_model_final",
export=True,
provider="CPUExecutionProvider"
)
model.save_pretrained("lead_model_onnx/")
O optimum da Hugging Face automatiza:
- Exportação do grafo PyTorch → ONNX
- Validação de equivalência (comparação de outputs em 100+ amostras)
- Otimização do grafo (simplificação, fusão de operações)
3.3 Validação de Equivalência
Comparamos logits PyTorch vs ONNX em 100 mensagens aleatórias:
- Diferença máxima absoluta: < 1e-5
- Diferença relativa: < 1e-7
- Discrepância de classe: 0% (mesma predição em todos os casos)
A equivalência é perfeita dentro da precisão float32.
4. Capacidades e Comportamento do Modelo
4.1 Representação Interna
O BERTimbau aprende representações contextuais densas (768-D) onde:
- Mensagens com intenção comercial clusterizam em regiões distintas
- Saudações e conversa fiada ocupam espaço vetorial separado
- Tokens de ação ("comprar", "preço", "cotar") ativam neurônios específicos na última camada
A análise de atenção (attention weights) revela que o modelo foca fortemente em:
- Verbos de intenção: "quero", "preciso", "gostaria"
- Substantivos comerciais: "preço", "cotação", "desconto"
- Padrões de recusa: "não quero", "parem", "remover"
- Padrões de bot: "protocolo", "fila", "opção", "aguarde"
4.2 Calibração de Confiabilidade
O modelo não está bem calibrado. Em análise de confiança:
| Métrica | Valor |
|---|---|
| Confiança média (acertos) | 97.75% |
| Confiança média (erros) | 95.00% |
| Erros com confiança > 80% | 89.3% (25/28) |
Isso significa que quando o modelo erra, ele ainda afirma ter 95% de certeza. Em produção, isso exige:
- Threshold adaptativo: não confiar cegamente em scores > 90%
- Fallback humano: rotear mensagens de alta confiança mas baixa certeza contextual para revisão
- Temperature scaling: ajuste posterior dos logits para melhorar calibração
4.3 Robustez por Comprimento de Texto
| Comprimento | Comportamento | Exemplo Problemático |
|---|---|---|
| 1-2 palavras | Tendência a INVALID | "preço" → INVALID (deveria ser POSITIVE) |
| 3-5 palavras | Melhora, mas ainda fraco | "qnt custa?" → INVALID (deveria ser POSITIVE) |
| 6-15 palavras | Zona de melhor performance | "Bom dia! Gostaria de fazer uma cotação." → POSITIVE |
| 15+ palavras | Pode confundir com INVALID | "Olá! Gostaria de fazer uma cotação. Ah não, esquece." → POSITIVE (deveria ser INVALID) |
O modelo tem déficit em textos muito curtos porque o dataset de treino enfatiza mensagens completas. Palavras isoladas como "preço", "comprar", "não" perdem contexto.
4.4 Robustez por Registro Linguístico
| Tipo de Texto | Performance | Nota |
|---|---|---|
| Português formal | Alta (90%+) | "Gostaria de solicitar uma cotação" |
| Português informal/gírias | Média (70%) | "Mano, manda o link aí" |
| Erros de digitação | Baixa (50%) | "qnt custa?", "qr comprar" |
| Inglês puro | Muito baixa | "Hello, I want to buy" → INVALID |
| Código-misturado (pt+en) | Média-baixa | "Hi, quanto custa?" → funciona; "Hello, I want to buy" → falha |
| Emojis sozinhos | Alta (correta) | "👍" → INVALID |
O modelo é monolíngue por design — o BERTimbau nunca viu inglês durante pré-treinamento. Mensagens 100% em inglês são mapeadas para a distribuição mais provável do PT-BR (frequentemente INVALID).
4.5 Comportamento por Classe
MACHINE_BOT (melhor classe):
- Precision: 86.67%, Recall: 92.86%
- Sinais fortes: "protocolo", "fila de espera", "digite 1", "avaliação"
- Risco: mensagens formais humanas confundidas com bot (raro)
HUMAN_POSITIVE (classe mais fraca):
- Precision: 73.68%, Recall: 53.85%
- Problema: 42.3% dos positivos reais são classificados como INVALID
- Causa raiz: o treino enfatiza "pedir preço" como sinal de positivo, mas não captura intenção implícita ("tenho interesse", "quero comprar")
HUMAN_NEGATIVE (baixo recall):
- Precision: 100.00%, Recall: 50.00%
- Problema: 45.5% dos negativos vão para INVALID
- Causa raiz: mensagens curtas de recusa ("não", "mt caro", "ja comprei") são mapeadas como INVALID
HUMAN_INVALID (classe dominante):
- Precision: 75.00%, Recall: 94.03%
- Comportamento: modelo "joga para o lado seguro"
- Risco: falso positivo em INVALID reduz a descoberta de leads reais
5. Benchmarking e Avaliação Profissional
5.1 Stress Test Adversarial
Construímos um conjunto de 129 casos adversariais cobrindo:
- Edge cases físicos: strings vazias, emojis, números, caracteres especiais
- Saudações: 9 variações de "oi" a "salve"
- Respostas curtas: "ok", "sim", "não", "vlw"
- Erros de digitação: 11 casos com abreviações e erros ortográficos
- Gírias: 8 variações regionais
- Misturas: mensagens com múltiplas intenções
- Ambiguidades: intenção implícita, curiosidade sem compra
- Multi-língue: 5 mensagens em inglês/espanhol misturado
5.2 Matriz de Confusão (% por classe esperada)
| Esperado \ Predito | BOT | POS | NEG | INVALID |
|---|---|---|---|---|
| BOT | 92.9% | 7.1% | — | — |
| POS | 3.9% | 53.8% | — | 42.3% |
| NEG | — | 4.5% | 50.0% | 45.5% |
| INVALID | 1.5% | 4.5% | — | 94.0% |
5.3 Métricas de Classificação
| Classe | Precision | Recall | F1-Score | Support |
|---|---|---|---|---|
| MACHINE_BOT | 86.67% | 92.86% | 89.66% | 14 |
| HUMAN_POSITIVE | 73.68% | 53.85% | 62.22% | 26 |
| HUMAN_NEGATIVE | 100.00% | 50.00% | 66.67% | 22 |
| HUMAN_INVALID | 75.00% | 94.03% | 83.44% | 67 |
| Macro Avg | 83.84% | 72.68% | 75.50% | 129 |
| Weighted Avg | 80.26% | 78.29% | 76.98% | 129 |
5.4 Comparação com LLM Local (Liquid LFM2 1.2B)
Para estabelecer baseline de qualidade, comparamos o IntenBERT contra um LLM local via LM Studio API.
| Dimensão | IntenBERT (ONNX) | LLM (LFM2 1.2B) |
|---|---|---|
| Acurácia | 78.3% | 40.3% |
| Latência (CPU) | ~50ms | ~2000ms |
| Tamanho | 400MB | ~3GB |
| RAM | 1GB | 6GB |
| Dependências | ONNX Runtime | LM Studio + GPU |
| Offline | Sim | Sim |
Métricas detalhadas do LLM (LFM2 1.2B):
| Classe | Precision | Recall | F1-Score |
|---|---|---|---|
| MACHINE_BOT | 100.00% | 7.14% | 13.33% |
| HUMAN_POSITIVE | 27.91% | 92.31% | 42.86% |
| HUMAN_NEGATIVE | 63.16% | 54.55% | 58.54% |
| HUMAN_INVALID | 68.18% | 22.39% | 33.71% |
| Macro Avg | 64.81% | 44.10% | 37.11% |
| Weighted Avg | 62.66% | 40.31% | 37.58% |
| Accuracy | — | — | 40.31% |
Análise de confiança LLM:
- Confiança média (acertos): 94.15% (σ=5.24%)
- Confiança média (erros): 95.47% (σ=1.71%)
- Erros com confiança > 80%: 77 de 77 erros (100%)
Trade-off arquitetural: o IntenBERT é 40x mais rápido, 7x menor e quase 2x mais acurado. O LLM de 1.2B parâmetros apresenta viés catastrófico para HUMAN_POSITIVE: classifica 65.7% dos INVALID e 92.9% dos BOT como positivos, com precision de apenas 27.91% para a classe positiva. Isso demonstra que modelos de linguagem pequenos via prompting zero-shot não são competitivos com classificadores fine-tuned para domínios específicos — o conhecimento especializado embutido nos 110M parâmetros do BERTimbau fine-tuned supera o conhecimento generalista de 1.2B parâmetros sem adaptação de domínio.
6. Limitações Arquiteturais e Caminhos de Melhoria
6.1 Limitações Identificadas
-
Contexto zero: o modelo classifica mensagem a mensagem, sem histórico da conversa. Uma sequência "Oi" → "Quanto custa?" não beneficia do contexto da saudação.
-
Comprimento mínimo: textos com < 3 tokens têm representação pobre no [CLS]. O modelo precisa de contexto sintático mínimo para ativar padrões de intenção.
-
Calibração fraca: distribuição de confiança não reflete probabilidade real de acerto. Temperature scaling ou Platt scaling são necessários para uso em decisões automatizadas.
-
Viés para INVALID: a classe majoritária no treino (46.6%) causa tendência conservadora. O modelo prefere errar para INVALID a errar para outras classes.
-
Monolinguismo estrito: zero capacidade em inglês puro. Mensagens 100% em inglês são classificadas com base em tokens coincidentes (ex: "buy" não existe no vocabulário PT-BR do BERTimbau).
6.2 Melhorias Arquiteturais Propostas
| Prioridade | Melhoria | Mudança Técnica | Impacto Esperado |
|---|---|---|---|
| Alta | Aumentar recall de POSITIVE | Oversampling + peso de classe na loss | +15% recall POSITIVE |
| Alta | Capturar intenção implícita | Adicionar exemplos "comprar", "tenho interesse" ao treino | Reduzir falsos INVALID |
| Alta | Temperature scaling | Ajuste de logits post-training com validação | Calibração confiável |
| Média | Ensemble de 3 modelos | Votação de modelos com seeds diferentes | +5-7% acurácia |
| Média | Dados reais anotados | Coleta LGPD-compliant com consentimento | Fechar synthetic-to-real gap |
| Média | Quantização INT8 | ONNX Runtime INT8 (QDQ) | -50% tamanho, +30% velocidade |
| Baixa | Contexto multi-turn | Concatenar últimas 3 mensagens com separadores | Resolver ambiguidades temporais |
| Baixa | DistilBERT ou MobileBERT | Reduzir para 66M ou 4.3M params | Inferência < 20ms |
7. Conclusão
O Lead Classifier v2.0 é um sistema de classificação de intenção otimizado para o domínio de WhatsApp Business em português brasileiro. Sua arquitetura — BERTimbau fine-tuned com dados sintéticos controlados e exportado para ONNX — entrega 78.3% de acurácia em condições adversariais com latência de ~50ms em CPU, viabilizando deploy em infraestrutura modesta.
As principais forças são:
- Alta precisão em BOT (86.67%) — útil para filtragem de URA
- Recall elevado em INVALID (94.03%) — bom para descartar conversa fiada
- Inferência ultrarrápida em CPU — viável para escala
Os principais pontos de atenção são:
- Sobre-confiança (95% média mesmo nos erros)
- Recall fraco em POSITIVE (53.85%) — leads reais podem ser perdidos
- Tendência para INVALID — modelo conservador
Para produção, recomenda-se:
- Aplicar temperature scaling nos scores
- Definir threshold de confiança por classe (não global)
- Rotear mensagens de alta confiança mas classe incerta para fallback humano
- Coletar feedback real para re-treino contínuo
Documento gerado em 2026-06-01. Baseado em stress test de 129 casos adversariais e análise de matriz de confusão com precision, recall e F1 por classe.