Spaces:
Running
Running
File size: 7,531 Bytes
5938c74 2ac97d5 a2ad1d2 5938c74 a2ad1d2 2ac97d5 5938c74 a2ad1d2 2ac97d5 5938c74 a2ad1d2 76f9a5f 233b2df a2ad1d2 233b2df a2ad1d2 233b2df a2ad1d2 233b2df a2ad1d2 233b2df a2ad1d2 233b2df a2ad1d2 233b2df a2ad1d2 76f9a5f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | ---
title: Notinhas Endpoint
emoji: 📝
colorFrom: green
colorTo: gray
sdk: gradio
sdk_version: 5.50.0
python_version: '3.10'
app_file: app.py
pinned: false
short_description: Classificador de utilidade para community notes em PT-BR.
models:
- BAAI/bge-m3
---
# Notinhas — endpoint de utilidade (FT-Solo)
Endpoint privado do modelo **FT-Solo** do projeto: dado o texto de uma *community
note* em português, devolve a probabilidade de ela ser classificada como "útil"
(`label_binary_strict = 1`), junto com uma leitura opcional da contribuição de
cada palavra.
Arquitetura: **bge-m3 (568M params) + LoRA + cabeça linear (Ensemble e Calibração)**, idêntica ao
`predict_from_text` do notebook FT-Solo em modo fiel
(fold 01).
## Estrutura do repositório
```
.
├── app.py # Gradio UI + API (abas Prever / Explicar / Sobre)
├── inference.py # Loader + predict + explain (occlusion word-level)
├── config.py # Constantes (modelo, prompt, paths, thresholds)
├── requirements.txt # Dependências Python
├── README.md # Este arquivo (com YAML header lido pelo HF)
├── .gitignore
└── artifacts/ # ← você popula isso (veja a seção Setup)
├── fold_01_adapter/ # Pasta do adapter LoRA
│ ├── adapter_config.json
│ └── adapter_model.safetensors
└── fold_01_head.pt # State dict do nn.Linear(1024, 1)
```
## Setup — do zero até o Space no ar
### 1. Criar o Space (privado)
Na UI do Hugging Face:
1. **New Space**.
2. SDK: **Gradio**.
3. Hardware: **T4 small** (recomendado — caber na memória em bf16 e inferência
em ~0,5 s). **A10G small** dá latência ainda menor. **ZeroGPU** funciona mas
com cold-start mais longo. **CPU** roda — inferência ~4–8 s com bge-m3 (vs 20–40 s do Qwen3).
4. Visibility: **Private**.
### 2. Popular `artifacts/`
Os pesos vêm do pipeline do projeto. O zip base do Drive (`artefatos_projeto.zip`)
traz as pastas com adapters e heads bge-m3. Rode localmente:
```bash
pip install gdown
gdown "https://drive.google.com/uc?id=1_wCCxZG25tcGIVHgrdfOj54vI5Iw6MUF" \
-O artefatos_projeto.zip
unzip -q artefatos_projeto.zip -d _raw/
# Estrutura esperada pelo Space:
mkdir -p artifacts
cp -r _raw/qwen4b_adapters/fold_01_adapter artifacts/
cp _raw/qwen4b_heads/fold_01_head.pt artifacts/
```
> **Qual fold usar?** O notebook escolhe dinamicamente o "melhor fold" via
> `qwen4b_ftsolo_manifest.json`. Para servir em produção é coerente reusar o
> mesmo. Se o manifesto apontar para outro fold (digamos, `fold_03`), renomeie
> os arquivos acima para `fold_01_adapter/` e `fold_01_head.pt` **ou** edite
> `config.py` para apontar para os nomes reais.
### 3. Commitar e subir
Spaces são repositórios git hospedados no HF. Dentro da pasta clonada do Space:
```bash
git lfs install # safetensors > 10 MB usam LFS
git lfs track "*.safetensors"
git lfs track "*.pt"
git add .gitattributes artifacts/
git add app.py inference.py config.py requirements.txt README.md .gitignore
git commit -m "feat: endpoint inicial FT-Solo"
git push
```
O adapter bge-m3 em LoRA costuma ficar entre **20 e 60 MB**
(dependendo do rank e dos módulos-alvo). A cabeça é ~20 KB. Tudo cabe
confortavelmente sem apertar quota.
### 4. (Opcional) Secrets
Em **Settings → Variables and secrets**:
- `HF_TOKEN` — só necessário se `BAAI/bge-m3` virar gated no futuro.
Hoje o modelo é público, então você pode ignorar.
### 5. Primeiro boot
Na primeira inicialização o Space:
1. Instala `requirements.txt` (~1 min).
2. Baixa `BAAI/bge-m3` da HF (~2 GB, ~30–60 s).
3. Carrega adapter + head (~5 s).
4. Fica pronto — e o warm-up do modelo já aconteceu, o primeiro request é rápido.
Acompanhe pela aba **Logs** do Space.
## Uso
### Via UI web
Basta acessar a URL privada do Space. Três abas:
- **Prever** — score + label + faixa de confiança.
- **Explicar** — o mesmo + texto com destaque por contribuição de palavra, mais
uma tabela dos top 5 tokens de cada lado.
- **Sobre** — detalhes técnicos e limitações.
### Via `gradio_client` (Python)
```python
from gradio_client import Client
client = Client("<seu-usuario>/<nome-do-space>", hf_token="hf_...")
# Só a probabilidade
card_html, payload = client.predict(
"Segundo o Ministério da Saúde, o número é falso. Fonte: https://...",
api_name="/predict",
)
print(payload)
# {'proba_util': 0.87, 'label': 'Útil', 'confidence_band': 'Alta'}
# Com explicação
card, highlight_html, tokens_html, full = client.predict(
"Essa nota é claramente desnecessária, opinião pessoal.",
api_name="/explain",
)
print(full["tokens"][:3], full["contributions"][:3])
```
### Via HTTP puro
Gradio 5 expõe as rotas em `/gradio_api/call/<api_name>`. Veja a doc oficial
em `https://<seu-space>.hf.space/?view=api` — o próprio Space gera a documentação
e exemplos de `curl` para os dois endpoints.
## Arquitetura e decisões
### Por que este stack
- **Gradio 5 em vez de FastAPI puro**: entrega UI + HTTP API de uma vez, com doc
automática. Para um endpoint privado de MVP, dobrar a utilidade sem dobrar o
código é o trade certo.
- **Occlusion em vez de SHAP Partition**: o notebook gasta 12–15 s/nota em SHAP
textual, justamente porque explora combinações de subconjuntos. Para servir
em tempo real, leave-one-out por palavra dá um `Δ` por token em ~N+1 forward
passes — 1 a 2 s para notas típicas, resultado visualmente comparável.
- **Fold único**: o notebook também usou fold único para SHAP textual. Ensemble
dos 5 folds é a extensão natural (listar `[(adapter_i, head_i) for i in 1..5]`,
mediar as sigmóides), mas não é obrigatório para o MVP.
### O que muda se você quiser escalar
- **Ensemble**: substituir `load_model()` por `load_models()` devolvendo uma lista
de pares `(encoder, head)`. `predict_batch` itera, mediana ou média das
probabilidades. Dobra VRAM e latência — só vale quando a performance marginal
justificar.
- **Vizinhos semânticos** (como na seção 5 do notebook): exige embutir
`embeddings_qwen3_4b_finetuned.npz` (≈200 MB) e o dataset mestre para
recuperar texto + label. É uma extensão natural — crie um `artifacts/knn_index/`
com FAISS e adicione uma aba "Vizinhos" ao Gradio.
- **Inference Endpoint** dedicado: se o Space virar gargalo, o mesmo repositório
de código pode ser deployado como **Inference Endpoint pago** da HF, que
aguenta paralelismo real e autoescala.
## Limitações
- O rótulo `helpful` mede **aceitabilidade bipartidária**, não qualidade editorial
— o notebook exemplifica casos em que vizinhos semânticos idênticos recebem
rótulos opostos por razões políticas, não textuais.
- Textos longos são truncados em 256 tokens.
- Predições são dependentes do fold servido; o notebook observou variação pequena
mas não nula entre folds.
## Créditos
Baseado no pipeline e no notebook de explicabilidade do projeto Notinhas.
O código aqui é o protótipo funcional da função `predict_from_text` virado serviço.
\n## Calibração e Ensemble\nEste Space carrega múltiplos folds como um **ensemble**, calculando a média das\nprobabilidades de 5 versões do modelo adaptado (LoRA + Cabeça Linear) para aumentar\na robustez da classificação. Além disso, as probabilidades passam por\n**Platt scaling** com base nos parâmetros do `config.py` para melhorar a calibração.\n |