Update README.md
Browse files
README.md
CHANGED
|
@@ -6,222 +6,3 @@ sdk: {{sdk}}
|
|
| 6 |
sdk_version: "{{sdkVersion}}"
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
# LLMs.Revistas
|
| 13 |
-
# Recomendador de Revistas (Scopus)
|
| 14 |
-
|
| 15 |
-
Aplicación en **Gradio** que recomienda revistas a partir de una **consulta** (título/idea) y un **corpus Scopus** con embeddings precalculados. Incluye un **grafo interactivo** estilo *Connected Papers* para explorar artículos relacionados.
|
| 16 |
-
|
| 17 |
-
---
|
| 18 |
-
|
| 19 |
-
## ✨ Funcionalidades
|
| 20 |
-
|
| 21 |
-
* **Recomendación de revistas** por similitud semántica (E5) con agregación por media/máximo.
|
| 22 |
-
* **Fusión opcional con SPECTER** para mejorar afinidad temática (recall): `score = α·E5 + (1−α)·SPECTER`.
|
| 23 |
-
* **Grafo interactivo** (vis-network) de los artículos top:
|
| 24 |
-
|
| 25 |
-
* Nodos escalados por relevancia a la consulta.
|
| 26 |
-
* Aristas artículo–artículo por similitud (E5 o SPECTER).
|
| 27 |
-
* Filtros: **umbral de arista** y **rango de años**.
|
| 28 |
-
* Colores por **Año** o por **Comunidad** (clusters por modularidad).
|
| 29 |
-
* UI ligera y estable: *KNN edges*, física *Barnes–Hut* con apagado automático tras estabilizar.
|
| 30 |
-
|
| 31 |
-
---
|
| 32 |
-
|
| 33 |
-
## 🧰 Requisitos
|
| 34 |
-
|
| 35 |
-
* **Python** 3.9+
|
| 36 |
-
* Dependencias principales:
|
| 37 |
-
|
| 38 |
-
```bash
|
| 39 |
-
pip install numpy pandas pyarrow sentence-transformers gradio matplotlib networkx pillow
|
| 40 |
-
```
|
| 41 |
-
|
| 42 |
-
Opcionales:
|
| 43 |
-
|
| 44 |
-
```bash
|
| 45 |
-
pip install torch # acelera inferencia si dispones de GPU/CPU AVX
|
| 46 |
-
```
|
| 47 |
-
|
| 48 |
-
---
|
| 49 |
-
|
| 50 |
-
## 📦 Estructura del dataset (`scopus_corpus.parquet`)
|
| 51 |
-
|
| 52 |
-
Se espera un Parquet con, al menos, estas columnas:
|
| 53 |
-
|
| 54 |
-
| Columna | Tipo | Descripción |
|
| 55 |
-
| ------------------- | --------------------- | ----------------------------------------------- |
|
| 56 |
-
| `embedding` | list\[float32] | Embedding **E5** normalizado de cada documento. |
|
| 57 |
-
| `title` | str | Título del artículo |
|
| 58 |
-
| `abstract` | str (opcional) | Resumen del artículo |
|
| 59 |
-
| `source_title` | str | Revista |
|
| 60 |
-
| `year` | int | Año de publicación (0/NaN si no hay) |
|
| 61 |
-
| `issn`, `eissn` | str (opc.) | Identificadores |
|
| 62 |
-
| `doi`, `link` | str (opc.) | Enlace/DOI |
|
| 63 |
-
| `eid` | str (opc.) | ID estable para el grafo |
|
| 64 |
-
| `specter_embedding` | list\[float32] (opc.) | Embedding **SPECTER** normalizado |
|
| 65 |
-
|
| 66 |
-
> **Nota**: El campo `specter_embedding` es opcional. Si no existe, la casilla *Fusionar con SPECTER* no tendrá efecto.
|
| 67 |
-
|
| 68 |
-
---
|
| 69 |
-
|
| 70 |
-
## 🧪 Cómo generar SPECTER (offline)
|
| 71 |
-
|
| 72 |
-
Crea una versión enriquecida del Parquet con SPECTER:
|
| 73 |
-
|
| 74 |
-
```python
|
| 75 |
-
# build_science_embeddings.py
|
| 76 |
-
import numpy as np
|
| 77 |
-
import pandas as pd
|
| 78 |
-
import pyarrow.parquet as pq
|
| 79 |
-
import pyarrow as pa
|
| 80 |
-
from sentence_transformers import SentenceTransformer
|
| 81 |
-
from tqdm import trange
|
| 82 |
-
|
| 83 |
-
PARQUET_PATH = "scopus_corpus.parquet"
|
| 84 |
-
OUT_PATH = "scopus_corpus_with_specter.parquet"
|
| 85 |
-
BATCH = 64
|
| 86 |
-
DEVICE = "cpu" # cambia a "cuda" si tienes GPU
|
| 87 |
-
|
| 88 |
-
# 1) Carga
|
| 89 |
-
pt = pq.read_table(PARQUET_PATH)
|
| 90 |
-
df = pt.to_pandas()
|
| 91 |
-
|
| 92 |
-
# 2) Texto para SPECTER: "Title [SEP] Abstract"
|
| 93 |
-
def row_text(row):
|
| 94 |
-
t = str(row.get("title", "") or "")
|
| 95 |
-
a = str(row.get("abstract", "") or "")
|
| 96 |
-
return f"{t} [SEP] {a}" if a.strip() else t
|
| 97 |
-
texts = [row_text(r) for _, r in df.iterrows()]
|
| 98 |
-
|
| 99 |
-
# 3) Modelo SPECTER
|
| 100 |
-
specter = SentenceTransformer("allenai-specter", device=DEVICE)
|
| 101 |
-
|
| 102 |
-
# 4) Encode en batches (normalizado)
|
| 103 |
-
embs = []
|
| 104 |
-
for i in trange(0, len(texts), BATCH, desc="SPECTER"):
|
| 105 |
-
batch = texts[i:i+BATCH]
|
| 106 |
-
vecs = specter.encode(batch, normalize_embeddings=True, show_progress_bar=False)
|
| 107 |
-
embs.append(vecs.astype("float32"))
|
| 108 |
-
mat = np.vstack(embs).astype("float32")
|
| 109 |
-
|
| 110 |
-
df["specter_embedding"] = [v.tolist() for v in mat]
|
| 111 |
-
|
| 112 |
-
# 5) Guardar
|
| 113 |
-
pq.write_table(pa.Table.from_pandas(df), OUT_PATH)
|
| 114 |
-
print("OK ->", OUT_PATH)
|
| 115 |
-
```
|
| 116 |
-
|
| 117 |
-
Ejecuta y luego apunta la app al nuevo Parquet (`scopus_corpus_with_specter.parquet`).
|
| 118 |
-
|
| 119 |
-
---
|
| 120 |
-
|
| 121 |
-
## ▶️ Ejecución
|
| 122 |
-
|
| 123 |
-
1. (Opcional) entorno virtual:
|
| 124 |
-
|
| 125 |
-
```bash
|
| 126 |
-
python -m venv .venv
|
| 127 |
-
.venv\Scripts\activate # Windows
|
| 128 |
-
# o: source .venv/bin/activate
|
| 129 |
-
```
|
| 130 |
-
2. Instala dependencias (ver *Requisitos*).
|
| 131 |
-
3. Coloca el Parquet junto al script de la app.
|
| 132 |
-
4. Ejecuta:
|
| 133 |
-
|
| 134 |
-
```bash
|
| 135 |
-
python journal_recommender_app.py
|
| 136 |
-
```
|
| 137 |
-
5. Abre `http://127.0.0.1:7860` (usa `demo.launch(share=True)` si quieres un enlace público temporal).
|
| 138 |
-
|
| 139 |
-
---
|
| 140 |
-
|
| 141 |
-
## 🖥️ Uso de la app
|
| 142 |
-
|
| 143 |
-
1. **Título o idea**: escribe una consulta ≥ 5 caracteres.
|
| 144 |
-
2. (Opc.) **Año mínimo/máximo**: filtra el corpus antes del cálculo.
|
| 145 |
-
3. **Artículos considerados (top-k)**: tamaño del conjunto candidato.
|
| 146 |
-
4. **Nº de revistas a mostrar**: recorte de resultados en la tabla.
|
| 147 |
-
5. **Fusionar con SPECTER** (si disponible) + **Peso E5 (α)**: mezcla `α·E5 + (1−α)·SPECTER`.
|
| 148 |
-
6. **Recomendar**: calcula la tabla (agregación por revista, ranking híbrido).
|
| 149 |
-
7. **Ver red de similitud**: abre un grafo interactivo (iframe) con:
|
| 150 |
-
|
| 151 |
-
* **Color por**: Año / Comunidad.
|
| 152 |
-
* **Umbral**: oculta/mostrar aristas artículo–artículo según similitud.
|
| 153 |
-
* **Rango de años**: doble slider; mantiene visibles los sin año (0) por defecto.
|
| 154 |
-
* **Controles**: Fit (encajar), PNG (exportar), Ayuda; doble clic abre `link/DOI`.
|
| 155 |
-
|
| 156 |
-
---
|
| 157 |
-
|
| 158 |
-
## 🧠 Detalles de modelo y scoring
|
| 159 |
-
|
| 160 |
-
* **E5 (intfloat/multilingual-e5-small)**: recupera rápido; consulta con prefijo `"query: "`.
|
| 161 |
-
* **SPECTER (allenai-specter)**: embeddings científicos entrenados con señales de citación.
|
| 162 |
-
* **Fusión**: `score = α·cos(E5) + (1−α)·cos(SPECTER)`.
|
| 163 |
-
* **Ranking por revista**: media (`score`) + pico (`best`) + *boost* por volumen: `rank = 0.8·score + 0.2·best + 0.02·log1p(n)`.
|
| 164 |
-
* **Grafo**:
|
| 165 |
-
|
| 166 |
-
* Nodos = top-*N* artículos por `score` (híbrido si fusión activa).
|
| 167 |
-
* Aristas A–B = similitud doc–doc; si fusión activa, se usa SPECTER para mayor coherencia temática.
|
| 168 |
-
* Optimización: se construyen **K vecinos** por nodo (KNN) para evitar O(n²); física *Barnes–Hut* y apagado tras estabilizar.
|
| 169 |
-
|
| 170 |
-
---
|
| 171 |
-
|
| 172 |
-
## ⚙️ Configuración y personalización
|
| 173 |
-
|
| 174 |
-
* **Modelos**: cambia `MODEL_NAME_E5` / `MODEL_NAME_SPECTER`.
|
| 175 |
-
* **Parámetros grafo**: `top_nodes`, `doc_edge_threshold`, `KNN_EDGES`.
|
| 176 |
-
* **Colores/tema** (Gradio v4):
|
| 177 |
-
|
| 178 |
-
* Tema global:
|
| 179 |
-
|
| 180 |
-
```python
|
| 181 |
-
my_theme = gr.themes.Soft(primary_hue="teal")
|
| 182 |
-
with gr.Blocks(theme=my_theme): ...
|
| 183 |
-
```
|
| 184 |
-
* Un botón específico:
|
| 185 |
-
|
| 186 |
-
```css
|
| 187 |
-
#btn-recomendar > button{background:#2563eb;color:#fff}
|
| 188 |
-
#btn-recomendar > button:hover{background:#1d4ed8}
|
| 189 |
-
```
|
| 190 |
-
* **Física vis-network**: ajusta `gravitationalConstant`, `springLength`, `damping`.
|
| 191 |
-
|
| 192 |
-
---
|
| 193 |
-
|
| 194 |
-
## 🚀 Rendimiento
|
| 195 |
-
|
| 196 |
-
* Mantén `embedding`/`specter_embedding` en **float32** y **normalizados**.
|
| 197 |
-
* Usa `top_nodes ≤ 100` y `KNN_EDGES 6–10` para grafos fluidos.
|
| 198 |
-
* Si notas tirones:
|
| 199 |
-
|
| 200 |
-
* Sube `doc_edge_threshold` (menos aristas).
|
| 201 |
-
* Baja `top_nodes` y/o `KNN_EDGES`.
|
| 202 |
-
* Desactiva fusión SPECTER para comparar latencia.
|
| 203 |
-
|
| 204 |
-
---
|
| 205 |
-
|
| 206 |
-
## 🛠️ Solución de problemas
|
| 207 |
-
|
| 208 |
-
* **No cambia nada al activar SPECTER**: el Parquet no contiene `specter_embedding` o no se cargó. Verifica `SPECTER_AVAILABLE`.
|
| 209 |
-
* **La red “gira” sin parar**: física habilitada; el código la **apaga** tras estabilizar. Ajusta parámetros o baja nodos/aristas.
|
| 210 |
-
* **Gradio no muestra el grafo**: los `<script>` no se ejecutan en `gr.HTML`; se solventa con un **iframe** con `data:` URI y `sandbox`.
|
| 211 |
-
* **Error JSON en `set_options`**: vis-network espera **JSON**, no bloques JS. Usa `json.dumps` en las opciones.
|
| 212 |
-
|
| 213 |
-
---
|
| 214 |
-
|
| 215 |
-
## 📄 Licencia
|
| 216 |
-
|
| 217 |
-
Indica aquí la licencia de tu proyecto (p. ej., MIT). Revisa las licencias de **SentenceTransformers**, **allenai-specter**, **Gradio** y dependencias.
|
| 218 |
-
|
| 219 |
-
---
|
| 220 |
-
|
| 221 |
-
## 🙌 Créditos
|
| 222 |
-
|
| 223 |
-
* Embeddings: **intfloat/multilingual-e5-small**, **allenai-specter**.
|
| 224 |
-
* UI/servidor: **Gradio**.
|
| 225 |
-
* Grafo: **vis-network**, **networkx**.
|
| 226 |
-
|
| 227 |
-
> Hecho para explorar y recomendar de forma visual, precisa y rápida. ¡Disfruta! 🚀
|
|
|
|
| 6 |
sdk_version: "{{sdkVersion}}"
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|