BERTimbau Legal Judgment PT-BR ⚖️
Fine-tuning do BERTimbau base para prever o desfecho
de recursos julgados pelo Tribunal de Justiça de Alagoas (TJAL) a partir da descrição da decisão:
no (não provido), partial (parcialmente provido) ou yes (provido).
| Métrica (teste, n=405) | Valor |
|---|---|
| F1 macro | 0.682 |
| Acurácia | 0.738 |
| F1 ponderado | 0.740 |
| F1 macro (validação) | 0.735 |
🧪 Demo com human-in-the-loop e monitoramento de drift: Legal NLP Feedback Engine · código no GitHub
Uso
import torch
from transformers import AutoModelForSequenceClassification, AutoTokenizer
repo = "juliodourado1/bertimbau-legal-judgment-ptbr"
tok = AutoTokenizer.from_pretrained(repo)
model = AutoModelForSequenceClassification.from_pretrained(repo).eval()
def predict(text: str, max_length: int = 512, head: int = 128) -> dict[str, float]:
ids = tok(text, add_special_tokens=False)["input_ids"]
if len(ids) > max_length - 2: # head+tail, igual ao treino
ids = ids[:head] + ids[-(max_length - 2 - head):]
ids = torch.tensor([[tok.cls_token_id, *ids, tok.sep_token_id]])
with torch.inference_mode():
probs = model(input_ids=ids).logits.softmax(-1)[0]
return {model.config.id2label[i]: round(p.item(), 3) for i, p in enumerate(probs)}
predict("APELAÇÃO CÍVEL. MANDADO DE SEGURANÇA. SERVIDOR PÚBLICO. SUPRESSÃO DE GRATIFICAÇÃO. "
"NÃO OBSERVÂNCIA ÀS GARANTIAS DO CONTRADITÓRIO E DA AMPLA DEFESA.")
Para textos curtos (≤ 510 tokens) o pipeline("text-classification", model=repo) dá o mesmo resultado.
Dados
joelniklaus/brazilian_court_decisions:
4.043 ementas do TJAL (Lage-Freitas et al., 2022, PeerJ Computer Science), com os splits oficiais.
| split | no | partial | yes | total |
|---|---|---|---|---|
| train | 1960 | 677 | 597 | 3234 |
| validation | 221 | 96 | 87 | 404 |
| test | 234 | 93 | 78 | 405 |
Decisão de projeto: entrada sem vazamento de rótulo. O campo ementa_text termina com o dispositivo
("RECURSO CONHECIDO E NÃO PROVIDO"), o que tornaria a tarefa trivial. O modelo usa decision_description,
em que o desfecho foi removido, que é a formulação de Legal Judgment Prediction do artigo original.
Treino
| Modelo base | neuralmind/bert-base-portuguese-cased (110M parâmetros) |
| Entrada | 512 tokens, truncamento head+tail (primeiros 128 + últimos 382; Sun et al., 2019) |
| Épocas / LR / batch efetivo | 5 / 4e-05 / 16 (8 × 2 acumulação de gradiente) |
| Otimização | AdamW, weight decay 0,01, warmup linear 10% |
| Desbalanceamento | cross-entropy com pesos inversos à frequência [0.55, 1.59, 1.81] |
| Seleção de checkpoint | melhor F1 macro na validação |
| Hardware | Apple M4 16 GB (MPS), bf16, gradient checkpointing, ~110 min (retomado do checkpoint da época 1 após OOM) |
Script de treino reprodutível: train.py
python train.py --batch-size 8 --grad-accum 2 --epochs 5 --bf16 --grad-checkpointing --seed 42
Métricas brutas: results.json (e o baseline em results_v1_256tok.json).
Ablação: por que 512 tokens com head+tail
67% das descrições passam de 256 tokens (mediana 323, p90 680), e as frases decisivas ficam no fim da ementa. O corte padrão à direita descarta justamente essa parte.
| configuração | épocas | F1 macro (teste) | acurácia (teste) |
|---|---|---|---|
| 256 tokens, corte à direita | 4 | 0.530 | 0.541 |
| 512 tokens, head+tail (128 + 382) | 5 | 0.682 | 0.738 |
Resultados por classe (teste)
| classe | precisão | recall | F1 | suporte |
|---|---|---|---|---|
no (não provido) |
0.851 | 0.829 | 0.840 | 234 |
partial (parcialmente provido) |
0.625 | 0.591 | 0.608 | 93 |
yes (provido) |
0.562 | 0.641 | 0.599 | 78 |
Matriz de confusão (linhas = real, colunas = previsto):
| no | partial | yes | |
|---|---|---|---|
| no | 194 | 18 | 22 |
| partial | 21 | 55 | 17 |
| yes | 13 | 15 | 50 |
Limitações e uso responsável
- Treinado só com decisões de um tribunal estadual (TJAL); o desempenho em outros tribunais, como o TST, não foi medido.
- A classe
partialé a mais difícil: a fronteira entre provimento parcial e total é sutil no texto da ementa. - Ferramenta de pesquisa e triagem. Não substitui análise jurídica nem deve embasar decisões sobre pessoas.
- Textos acima de 512 tokens perdem o trecho do meio (truncamento head+tail). Use a mesma truncagem na inferência; o
pipelinepadrão corta à direita. VejaHFClassifier.encodeno repositório do app.
Citação do dataset
@article{lage2022predicting,
title = {Predicting Brazilian Court Decisions},
author = {Lage-Freitas, Andr{\'e} and Allende-Cid, H{\'e}ctor and Santana, Orivaldo and Oliveira-Lage, L{\'i}via},
journal = {PeerJ Computer Science},
volume = {8},
pages = {e904},
year = {2022}
}