histlearn commited on
Commit
4e70841
·
verified ·
1 Parent(s): b662189

Initial release: 5 LoRA fold adapters + souped + model card

Browse files
README.md ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: Qwen/Qwen3-Reranker-0.6B
4
+ library_name: peft
5
+ language:
6
+ - pt
7
+ tags:
8
+ - text-classification
9
+ - community-notes
10
+ - portuguese
11
+ - reranker
12
+ - lora
13
+ - peft
14
+ - misinformation
15
+ pipeline_tag: text-classification
16
+ ---
17
+
18
+ # Community Notes Reranker (PT-BR) — Qwen3-Reranker fine-tunado
19
+
20
+ Cross-encoder fine-tunado com **LoRA** sobre `Qwen/Qwen3-Reranker-0.6B` para classificar a **utilidade** de notas da comunidade do X (antigo Twitter) em português brasileiro.
21
+
22
+ > Dado um par (tweet, nota), o modelo devolve a probabilidade de que a comunidade marcaria a nota como **útil** (`CURRENTLY_RATED_HELPFUL` / CRH) versus **não-útil** (`CURRENTLY_RATED_NOT_HELPFUL` / CRNH).
23
+
24
+ ## Como usar
25
+
26
+ ```python
27
+ import json, torch
28
+ from transformers import AutoTokenizer, AutoModelForCausalLM
29
+ from peft import PeftModel
30
+ from huggingface_hub import snapshot_download
31
+
32
+ REPO = "histlearn/community-notes-reranker-ptbr"
33
+ path = snapshot_download(REPO)
34
+ m = json.load(open(f"{path}/manifesto.json"))
35
+
36
+ tok = AutoTokenizer.from_pretrained(m["base_model"], padding_side="left")
37
+ base = AutoModelForCausalLM.from_pretrained(m["base_model"], torch_dtype=torch.float16)
38
+ model = PeftModel.from_pretrained(base, f"{path}/adapter_souped").cuda().eval()
39
+
40
+ def util_prob(tweet: str, nota: str) -> float:
41
+ text = (m["prompt_prefixo"] + "<Instruct>: " + m["instrucao"] +
42
+ "\n<Query>: " + tweet + "\n<Document>: " + nota + m["prompt_sufixo"])
43
+ enc = tok(text, return_tensors="pt", truncation=True, max_length=m["max_length"]).to(model.device)
44
+ with torch.no_grad():
45
+ l = model(**enc).logits[:, -1, :]
46
+ return float(torch.sigmoid(l[:, m["id_yes"]] - l[:, m["id_no"]]).item())
47
+
48
+ print(util_prob(
49
+ "Bolsonaro disse que a Terra e plana",
50
+ "Bolsonaro nunca afirmou isso; checagem em https://exemplo.org/checagem"
51
+ ))
52
+ ```
53
+
54
+ Exemplos completos em `examples/`:
55
+ - `inference_single_fold.py` — usa só o `adapter_fold_1` (rápido)
56
+ - `inference_ensemble.py` — média das probas dos 5 folds (**reproduz exatamente o número reportado** abaixo)
57
+ - `inference_souped.py` — usa o soup pré-computado (rápido, qualidade próxima)
58
+
59
+ ## Resultados
60
+
61
+ Avaliação **out-of-fold** sob `StratifiedGroupKFold(5)` agrupado por `tweetId` em 13.525 notas
62
+ (hidratação cobrindo 70% das notas estritas em PT-BR; 71,67% positivos).
63
+
64
+ | Modelo | macro-F1 | ROC-AUC | MCC | PR-AUC (minoritária) |
65
+ |---|---|---|---|---|
66
+ | **Ensemble de probas (5 folds)** | **0.7920** | **0.8932** | **0.5905** | **0.8293** |
67
+ | Soup-OOF (1 forward) | 0.9097 | 0.9714 | 0.8209 | 0.9505 |
68
+
69
+ Este modelo é o **D3** da escada experimental do projeto Community Notes BR. Para contexto:
70
+
71
+ | Baseline | macro-F1 | ROC-AUC | usa tweet? |
72
+ |---|---|---|---|
73
+ | Dummy (classe majoritária) | 0.4175 | 0.5000 | — |
74
+ | TF-IDF nota + LR | 0.7725 | 0.8622 | não |
75
+ | Embedding Qwen nota + LR | 0.7489 | 0.8488 | não |
76
+ | Embedding Qwen nota+tweet + LR | 0.7193 | 0.8057 | sim (frozen) |
77
+ | **D3 (este modelo)** | **0.7920** | **0.8932** | **sim (fine-tuned)** |
78
+ | Stacking de todos os baselines + D3 | 0.8282 | 0.9081 | sim |
79
+
80
+ Leitura central: **com cross-encoder fine-tunado, somar o tweet à nota traz ganho real** (D3 0.79 vs nota-só 0.77). Com embeddings frozen o tweet atrapalha — só com aprendizado conjunto da interação tweet↔nota o sinal aparece.
81
+
82
+
83
+ ### Soup de produção (única forward pass)
84
+
85
+ Carregando `adapter_souped/` em vez de um fold único, o usuário obtém uma versão "fundida" dos 5 adapters (média aritmética dos pesos LoRA). A qualidade dele foi validada em **Soup-OOF (leave-one-fold-out)**:
86
+
87
+ | Métrica | Ensemble de probas (5×forward) | Soup-OOF (1×forward) | Δ |
88
+ |---|---|---|---|
89
+ | macro-F1 | 0.7920 | 0.9097 | +11.78 pp |
90
+ | ROC-AUC | 0.8932 | 0.9714 | +7.82 pp |
91
+ | MCC | 0.5905 | 0.8209 | +23.04 pp |
92
+ | PR-AUC (minoritária) | 0.8293 | 0.9505 | +12.12 pp |
93
+
94
+ Esta é a versão recomendada para produção (custo de inferência = 1 fold único).
95
+
96
+
97
+ ## Dados de treino
98
+
99
+ - Dataset base: [`histlearn/notas-comunidade-ptbr`](https://huggingface.co/datasets/histlearn/notas-comunidade-ptbr) (notas em PT-BR, CC0, sem texto de tweet por restrição da X).
100
+ - Texto de tweet hidratado via *X syndication* (não redistribuído).
101
+ - Universo estrito: notas com `consenso ∈ {"CRH", "CRNH"}`.
102
+
103
+ ## Detalhes de treinamento
104
+
105
+ - Base: `Qwen/Qwen3-Reranker-0.6B`
106
+ - Método: **LoRA** (`r=16`, `α=32`, `dropout=0.1`, alvos `q_proj, k_proj, v_proj, o_proj`)
107
+ - Loss: `BCEWithLogitsLoss` sobre `logit(yes) − logit(no)`, com `pos_weight = n_neg / n_pos`
108
+ - Otimizador: AdamW, `lr = 0.0001`, `batch = 8`, `epochs = 2` por fold
109
+ - `max_length = 512`, mixed precision fp16
110
+ - Protocolo: `StratifiedGroupKFold(5)` agrupado por `tweetId` (evita vazamento intra-tweet)
111
+ - Seed: `42`
112
+
113
+ Template do prompt (literal, do manifesto):
114
+
115
+ ```
116
+ <|im_start|>system
117
+ Judge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be "yes" or "no".<|im_end|>
118
+ <|im_start|>user
119
+ <Instruct>: A nota e uma Community Note util e bem avaliada para o tweet?
120
+ <Query>: <texto do tweet>
121
+ <Document>: <texto da nota><|im_end|>
122
+ <|im_start|>assistant
123
+ <think>
124
+
125
+ </think>
126
+
127
+
128
+ ```
129
+
130
+ O score é `sigmoid(logits["yes"] − logits["no"])` na última posição.
131
+
132
+ ## Limitações e vieses
133
+
134
+ - **Viés de seleção:** o universo `CRH ∪ CRNH` exclui ~80% das notas (que ficaram em `NEEDS_MORE_RATINGS`). O modelo aprende a separar CRH×CRNH, não a fazer triagem de notas indecisas.
135
+ - **Viés de sobrevivência da hidratação:** tweets de notas CRH hidrataram 61%; tweets de CRNH hidrataram 87%. Notas CRH "boas" tinham tweet enganoso mais frequentemente removido. As métricas refletem o universo hidratado, não o universo total.
136
+ - **Domínio:** PT-BR, contexto político-social brasileiro 2024-2025. Generalização para outros idiomas, períodos ou plataformas exige re-treino.
137
+ - **Não é classificador de veracidade:** decide se a *comunidade marcaria como útil*, não se o conteúdo é factualmente correto. Útil≠verdadeiro, não-útil≠falso.
138
+ - **Sob deslocamento temporal:** o ensemble degrada ~1 pp macro-F1 ao treinar no passado e testar no futuro (testado no projeto); o D0e tabular sozinho degrada ~3 pp. Robusto, mas não imune.
139
+
140
+ ## Reprodutibilidade
141
+
142
+ O experimento completo está em [github.com/.../community-notes-br](https://github.com/) (substituir pelo repo do projeto): NB1 hidrata tweets, NB2 treina os 5 folds e gera os adapters publicados aqui, NB3 reorganiza a comparação por paradigmas. Os `oof_fold_{1..5}.npz` correspondentes a estes adapters estão no zip de artefatos do projeto.
143
+
144
+ ## Citação
145
+
146
+ ```bibtex
147
+ @misc{communitynotes_reranker_ptbr_2026,
148
+ author = {Rocha, Davi Machado},
149
+ title = {Community Notes Reranker (PT-BR) — Qwen3-Reranker fine-tunado},
150
+ year = {2026},
151
+ publisher = {Hugging Face},
152
+ url = {https://huggingface.co/histlearn/community-notes-reranker-ptbr},
153
+ note = {Disciplina Introdução à IA, prof. Ricardo M. Marcacini, ICMC/USP},
154
+ }
155
+ ```
156
+
157
+ ## Licença
158
+
159
+ Apache 2.0 (mesmo do base model). Use livre, atribuição apreciada.
adapter_fold_1/adapter_config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "Qwen/Qwen3-Reranker-0.6B",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 32,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.1,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": null,
26
+ "peft_type": "LORA",
27
+ "peft_version": "0.19.1",
28
+ "qalora_group_size": 16,
29
+ "r": 16,
30
+ "rank_pattern": {},
31
+ "revision": null,
32
+ "target_modules": [
33
+ "v_proj",
34
+ "o_proj",
35
+ "k_proj",
36
+ "q_proj"
37
+ ],
38
+ "target_parameters": null,
39
+ "task_type": "CAUSAL_LM",
40
+ "trainable_token_indices": null,
41
+ "use_bdlora": null,
42
+ "use_dora": false,
43
+ "use_qalora": false,
44
+ "use_rslora": false
45
+ }
adapter_fold_1/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:9497a46dcda94bf2c026b82d4ca3fc0fff205600bbc05c3580d25a51f9ac89a9
3
+ size 18380008
adapter_fold_2/adapter_config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "Qwen/Qwen3-Reranker-0.6B",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 32,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.1,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": null,
26
+ "peft_type": "LORA",
27
+ "peft_version": "0.19.1",
28
+ "qalora_group_size": 16,
29
+ "r": 16,
30
+ "rank_pattern": {},
31
+ "revision": null,
32
+ "target_modules": [
33
+ "v_proj",
34
+ "o_proj",
35
+ "k_proj",
36
+ "q_proj"
37
+ ],
38
+ "target_parameters": null,
39
+ "task_type": "CAUSAL_LM",
40
+ "trainable_token_indices": null,
41
+ "use_bdlora": null,
42
+ "use_dora": false,
43
+ "use_qalora": false,
44
+ "use_rslora": false
45
+ }
adapter_fold_2/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f8b8c55796f8c846678c5ac5d56b96c9faf9075ba83cf01b49aafa52ba15a358
3
+ size 18380008
adapter_fold_3/adapter_config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "Qwen/Qwen3-Reranker-0.6B",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 32,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.1,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": null,
26
+ "peft_type": "LORA",
27
+ "peft_version": "0.19.1",
28
+ "qalora_group_size": 16,
29
+ "r": 16,
30
+ "rank_pattern": {},
31
+ "revision": null,
32
+ "target_modules": [
33
+ "v_proj",
34
+ "o_proj",
35
+ "k_proj",
36
+ "q_proj"
37
+ ],
38
+ "target_parameters": null,
39
+ "task_type": "CAUSAL_LM",
40
+ "trainable_token_indices": null,
41
+ "use_bdlora": null,
42
+ "use_dora": false,
43
+ "use_qalora": false,
44
+ "use_rslora": false
45
+ }
adapter_fold_3/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:97313d9d2f7b3bf10c68567454bd6235cf9b2006253a29f4b1566bcb3600f16b
3
+ size 18380008
adapter_fold_4/adapter_config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "Qwen/Qwen3-Reranker-0.6B",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 32,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.1,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": null,
26
+ "peft_type": "LORA",
27
+ "peft_version": "0.19.1",
28
+ "qalora_group_size": 16,
29
+ "r": 16,
30
+ "rank_pattern": {},
31
+ "revision": null,
32
+ "target_modules": [
33
+ "v_proj",
34
+ "o_proj",
35
+ "k_proj",
36
+ "q_proj"
37
+ ],
38
+ "target_parameters": null,
39
+ "task_type": "CAUSAL_LM",
40
+ "trainable_token_indices": null,
41
+ "use_bdlora": null,
42
+ "use_dora": false,
43
+ "use_qalora": false,
44
+ "use_rslora": false
45
+ }
adapter_fold_4/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:809c53eb0613726acdb3dc7f567f9827d9d144993931b0ec25eb225a306b9ed9
3
+ size 18380008
adapter_fold_5/adapter_config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "Qwen/Qwen3-Reranker-0.6B",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 32,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.1,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": null,
26
+ "peft_type": "LORA",
27
+ "peft_version": "0.19.1",
28
+ "qalora_group_size": 16,
29
+ "r": 16,
30
+ "rank_pattern": {},
31
+ "revision": null,
32
+ "target_modules": [
33
+ "v_proj",
34
+ "o_proj",
35
+ "k_proj",
36
+ "q_proj"
37
+ ],
38
+ "target_parameters": null,
39
+ "task_type": "CAUSAL_LM",
40
+ "trainable_token_indices": null,
41
+ "use_bdlora": null,
42
+ "use_dora": false,
43
+ "use_qalora": false,
44
+ "use_rslora": false
45
+ }
adapter_fold_5/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:70e688fb737dabdfa971189d12fbd260f90aec91c5e2882975e544829d1271e5
3
+ size 18380008
adapter_souped/adapter_config.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "alora_invocation_tokens": null,
3
+ "alpha_pattern": {},
4
+ "arrow_config": null,
5
+ "auto_mapping": null,
6
+ "base_model_name_or_path": "Qwen/Qwen3-Reranker-0.6B",
7
+ "bias": "none",
8
+ "corda_config": null,
9
+ "ensure_weight_tying": false,
10
+ "eva_config": null,
11
+ "exclude_modules": null,
12
+ "fan_in_fan_out": false,
13
+ "inference_mode": true,
14
+ "init_lora_weights": true,
15
+ "layer_replication": null,
16
+ "layers_pattern": null,
17
+ "layers_to_transform": null,
18
+ "loftq_config": {},
19
+ "lora_alpha": 32,
20
+ "lora_bias": false,
21
+ "lora_dropout": 0.1,
22
+ "lora_ga_config": null,
23
+ "megatron_config": null,
24
+ "megatron_core": "megatron.core",
25
+ "modules_to_save": null,
26
+ "peft_type": "LORA",
27
+ "peft_version": "0.19.1",
28
+ "qalora_group_size": 16,
29
+ "r": 16,
30
+ "rank_pattern": {},
31
+ "revision": null,
32
+ "target_modules": [
33
+ "v_proj",
34
+ "o_proj",
35
+ "k_proj",
36
+ "q_proj"
37
+ ],
38
+ "target_parameters": null,
39
+ "task_type": "CAUSAL_LM",
40
+ "trainable_token_indices": null,
41
+ "use_bdlora": null,
42
+ "use_dora": false,
43
+ "use_qalora": false,
44
+ "use_rslora": false
45
+ }
adapter_souped/adapter_model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5ee5218a520aabf72e89f4175df201aaae0bfd9ac050eafd4283bcc9b455dc0b
3
+ size 18379976
examples/inference_ensemble.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Ensemble de probas: usa os 5 folds e devolve a media.
2
+ E a melhor pratica cientifica - replica o numero 0.7920 macro-F1 reportado.
3
+ Em GPU T4: ~250ms por par. Em CPU: ~30s por par.
4
+ """
5
+ import json, torch
6
+ from transformers import AutoTokenizer, AutoModelForCausalLM
7
+ from peft import PeftModel
8
+ from huggingface_hub import snapshot_download
9
+
10
+ REPO = "histlearn/community-notes-reranker-ptbr"
11
+ path = snapshot_download(REPO, allow_patterns=["manifesto.json", "adapter_fold_*/*"])
12
+
13
+ m = json.load(open(f"{path}/manifesto.json"))
14
+ tok = AutoTokenizer.from_pretrained(m["base_model"], padding_side="left")
15
+ base = AutoModelForCausalLM.from_pretrained(
16
+ m["base_model"], torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32)
17
+ if torch.cuda.is_available(): base.cuda()
18
+
19
+ def make_text(tw, nt):
20
+ return (m["prompt_prefixo"] + "<Instruct>: " + m["instrucao"] +
21
+ "\n<Query>: " + tw + "\n<Document>: " + nt + m["prompt_sufixo"])
22
+
23
+ def score_ensemble(tweet, nota):
24
+ probs = []
25
+ for k in range(1, 6):
26
+ model = PeftModel.from_pretrained(base, f"{path}/adapter_fold_{k}")
27
+ model.eval()
28
+ enc = tok(make_text(tweet, nota), return_tensors="pt",
29
+ truncation=True, max_length=m["max_length"]).to(model.device)
30
+ with torch.no_grad():
31
+ logits = model(**enc).logits[:, -1, :]
32
+ probs.append(float(torch.sigmoid(
33
+ logits[:, m["id_yes"]] - logits[:, m["id_no"]]).item()))
34
+ model.unload() # libera memoria do adapter
35
+ return sum(probs) / 5
36
+
37
+ print(score_ensemble("Bolsonaro disse que a Terra e plana",
38
+ "Bolsonaro nunca afirmou isso; checagem em https://exemplo.org"))
examples/inference_single_fold.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Inferencia rapida usando um unico fold (fold 1). Ideal para demos.
2
+ Em CPU: ~5-10s por par. Em GPU T4: ~50ms.
3
+ """
4
+ import json, torch
5
+ from transformers import AutoTokenizer, AutoModelForCausalLM
6
+ from peft import PeftModel
7
+ from huggingface_hub import snapshot_download
8
+
9
+ REPO = "histlearn/community-notes-reranker-ptbr"
10
+ path = snapshot_download(REPO, allow_patterns=["manifesto.json", "adapter_fold_1/*"])
11
+
12
+ m = json.load(open(f"{path}/manifesto.json"))
13
+ tok = AutoTokenizer.from_pretrained(m["base_model"], padding_side="left")
14
+ model = AutoModelForCausalLM.from_pretrained(
15
+ m["base_model"], torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32)
16
+ model = PeftModel.from_pretrained(model, f"{path}/adapter_fold_1")
17
+ if torch.cuda.is_available(): model.cuda()
18
+ model.eval()
19
+
20
+ def score(tweet, nota):
21
+ text = (m["prompt_prefixo"] + "<Instruct>: " + m["instrucao"] +
22
+ "\n<Query>: " + tweet + "\n<Document>: " + nota + m["prompt_sufixo"])
23
+ enc = tok(text, return_tensors="pt", truncation=True, max_length=m["max_length"]).to(model.device)
24
+ with torch.no_grad():
25
+ logits = model(**enc).logits[:, -1, :]
26
+ return float(torch.sigmoid(logits[:, m["id_yes"]] - logits[:, m["id_no"]]).item())
27
+
28
+ print(score("Bolsonaro disse que a Terra e plana",
29
+ "Bolsonaro nunca afirmou isso; checagem em https://exemplo.org"))
examples/inference_souped.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Inferencia usando o soup (media de pesos LoRA dos 5 folds).
2
+ Velocidade de fold unico; qualidade validada em soup-OOF (ver model card).
3
+ """
4
+ import json, torch
5
+ from transformers import AutoTokenizer, AutoModelForCausalLM
6
+ from peft import PeftModel
7
+ from huggingface_hub import snapshot_download
8
+
9
+ REPO = "histlearn/community-notes-reranker-ptbr"
10
+ path = snapshot_download(REPO, allow_patterns=["manifesto.json", "adapter_souped/*"])
11
+
12
+ m = json.load(open(f"{path}/manifesto.json"))
13
+ tok = AutoTokenizer.from_pretrained(m["base_model"], padding_side="left")
14
+ model = AutoModelForCausalLM.from_pretrained(
15
+ m["base_model"], torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32)
16
+ model = PeftModel.from_pretrained(model, f"{path}/adapter_souped")
17
+ if torch.cuda.is_available(): model.cuda()
18
+ model.eval()
19
+
20
+ def score(tweet, nota):
21
+ text = (m["prompt_prefixo"] + "<Instruct>: " + m["instrucao"] +
22
+ "\n<Query>: " + tweet + "\n<Document>: " + nota + m["prompt_sufixo"])
23
+ enc = tok(text, return_tensors="pt", truncation=True, max_length=m["max_length"]).to(model.device)
24
+ with torch.no_grad():
25
+ logits = model(**enc).logits[:, -1, :]
26
+ return float(torch.sigmoid(logits[:, m["id_yes"]] - logits[:, m["id_no"]]).item())
27
+
28
+ print(score("Bolsonaro disse que a Terra e plana",
29
+ "Bolsonaro nunca afirmou isso; checagem em https://exemplo.org"))
manifesto.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "base_model": "Qwen/Qwen3-Reranker-0.6B",
3
+ "tarefa": "utilidade de Community Note (CRH=1, CRNH=0)",
4
+ "entrada": "par (tweet, nota) no template Qwen3-Reranker",
5
+ "prompt_prefixo": "<|im_start|>system\nJudge whether the Document meets the requirements based on the Query and the Instruct provided. Note that the answer can only be \"yes\" or \"no\".<|im_end|>\n<|im_start|>user\n",
6
+ "prompt_sufixo": "<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n",
7
+ "instrucao": "A nota e uma Community Note util e bem avaliada para o tweet?",
8
+ "max_length": 512,
9
+ "score": "sigmoid(logits['yes'] - logits['no']) na ultima posicao",
10
+ "id_yes": 9693,
11
+ "id_no": 2152,
12
+ "lora": {
13
+ "r": 16,
14
+ "alpha": 32,
15
+ "dropout": 0.1,
16
+ "targets": [
17
+ "q_proj",
18
+ "k_proj",
19
+ "v_proj",
20
+ "o_proj"
21
+ ]
22
+ },
23
+ "n_folds": 5,
24
+ "epocas": 2,
25
+ "lr": 0.0001,
26
+ "batch": 8,
27
+ "random_state": 42
28
+ }