BERTimbau: apoio em comentários de saúde mental (PT-BR)
Classifica comentários de comunidades brasileiras de saúde mental no Reddit (r/AnsiedadeDepressao e r/desabafos) em três classes:
| Rótulo | Significado |
|---|---|
| B (apoio) | acolhe, encoraja, demonstra empatia |
| N (neutro) | informa, pergunta ou relata sem carga emocional |
| M (não-apoio) | minimiza o sofrimento, é hostil ou ofensivo |
Projeto acadêmico de PLN (ICMC/USP, 2026). Código, relatório e análise: github.com/renatospessotto/bertimbau-mental-health-ptbr.
Não use para moderação automática nem para qualquer decisão clínica. Com F1 de 0,36 na classe de não-apoio, o modelo erra muito justamente no caso mais sensível. O uso pretendido é pesquisa sobre a dinâmica de apoio em comunidades online.
Resultados (300 comentários de teste rotulados por humanos)
| Modelo | Acurácia | F1-macro | F1-ponderado |
|---|---|---|---|
Ensemble dos 5 folds + temperature scaling (ensemble/) |
0,657 | 0,590 | 0,661 |
| Modelo único do holdout (raiz deste repositório) | 0,647 | 0,594 | 0,658 |
| Pysentimiento (pronto, zero-shot) | 0,427 | 0,399 | 0,444 |
| XLM-RoBERTa cardiffnlp (pronto, zero-shot) | 0,377 | 0,358 | 0,382 |
- Ganho do ajuste no domínio: cerca de +0,19 de F1-macro sobre o melhor modelo pronto.
- F1 por classe (ensemble): apoio 0,76, neutro 0,65 e não-apoio 0,36.
- Calibração: o temperature scaling ajustado out-of-fold (T = 0,675) reduz o ECE do ensemble de 0,087 para 0,037.
- Validação cruzada (5 folds): F1-macro de 0,575 ± 0,018.
Uso
Modelo único (mais simples). Os pesos estão na raiz do repositório:
from transformers import pipeline
clf = pipeline("text-classification", model="RenatoSpessotto/bertimbau-apoio-saude-mental-ptbr", top_k=None)
clf("Força, você não está sozinho. Procura ajuda que melhora.")
A temperatura ajustada para esse modelo está em temperatura.json (T = 0,509). Para obter probabilidades calibradas, divida os logits por T antes do softmax.
Modelo final do projeto: ensemble calibrado. Faz a média dos logits dos 5 folds e divide por T:
import json, numpy as np, torch
from huggingface_hub import snapshot_download
from transformers import AutoTokenizer, AutoModelForSequenceClassification
path = snapshot_download("RenatoSpessotto/bertimbau-apoio-saude-mental-ptbr", allow_patterns="ensemble/*")
cfg = json.load(open(f"{path}/ensemble/modelo_final.json"))
tok = AutoTokenizer.from_pretrained(f"{path}/ensemble/fold_1")
def prever(textos):
enc = tok(textos, truncation=True, max_length=128, padding=True, return_tensors="pt")
logits = []
for fold in cfg["modelos"]:
m = AutoModelForSequenceClassification.from_pretrained(f"{path}/ensemble/{fold}").eval()
with torch.no_grad():
logits.append(m(**enc).logits.numpy())
z = np.mean(logits, axis=0) / cfg["temperatura"]
p = np.exp(z - z.max(1, keepdims=True)); p /= p.sum(1, keepdims=True)
return [dict(zip(cfg["ordem"], map(float, row))) for row in p]
Treino
- Dados: 2.995 comentários anotados por 6 pessoas da equipe, com guia de anotação e um anotador por comentário. A divisão é estratificada, com 10% para teste e semente 42.
- TAPT: pré-treino MLM nos ~10 mil comentários sem rótulo antes do ajuste.
- Fine-tuning: focal loss (γ=2) com pesos de classe, lr 2e-5, batch 16, max_length 128.
- Ensemble: k-fold estratificado com k=5 e média dos logits. A temperatura foi ajustada nas predições out-of-fold.
Limitações e ética
- Privacidade: os comentários vêm de pessoas em sofrimento. Nenhum texto do dataset é publicado, nem aqui nem no GitHub.
- Rótulos: cada comentário tem um único anotador, e a concordância entre anotadores não foi medida.
- Classe de não-apoio: é minoritária (cerca de 12% dos dados) e é onde o modelo mais erra.
- Generalização: o modelo foi treinado só com comentários do Reddit, com truncamento em 128 tokens, então pode não generalizar para outras plataformas.