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