Spaces:
Runtime error
Runtime error
Upload 7 files
Browse files- README.md +13 -6
- app.py +255 -0
- cache_system.py +61 -0
- header.py +57 -0
- prompts.py +31 -0
- requirements.txt +6 -0
- utils.py +54 -0
README.md
CHANGED
@@ -1,13 +1,20 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
-
sdk_version: 4.24.0
|
8 |
-
app_file: app.py
|
9 |
pinned: false
|
10 |
license: apache-2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
---
|
12 |
|
13 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
+
title: NotiCIA
|
3 |
+
emoji: 📰
|
4 |
+
colorFrom: indigo
|
5 |
+
colorTo: pink
|
6 |
sdk: gradio
|
|
|
|
|
7 |
pinned: false
|
8 |
license: apache-2.0
|
9 |
+
suggested_hardware: t4-small
|
10 |
+
suggested_storage: small
|
11 |
+
app_file: app.py
|
12 |
+
fullWidth: true
|
13 |
+
models:
|
14 |
+
- somosnlp/NoticIA-7B
|
15 |
+
tags:
|
16 |
+
- summarization
|
17 |
+
- clickbait
|
18 |
---
|
19 |
|
20 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
@@ -0,0 +1,255 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import datetime
|
2 |
+
|
3 |
+
import gradio as gr
|
4 |
+
import torch
|
5 |
+
from cache_system import CacheHandler
|
6 |
+
from header import article, header
|
7 |
+
from newspaper import Article
|
8 |
+
from prompts import summarize_clickbait_short_prompt
|
9 |
+
from transformers import (
|
10 |
+
AutoModelForCausalLM,
|
11 |
+
AutoTokenizer,
|
12 |
+
BitsAndBytesConfig,
|
13 |
+
GenerationConfig,
|
14 |
+
LogitsProcessorList,
|
15 |
+
TextStreamer,
|
16 |
+
)
|
17 |
+
from utils import StopAfterTokenIsGenerated
|
18 |
+
|
19 |
+
total_runs = 0
|
20 |
+
|
21 |
+
# Cargar el tokenizador
|
22 |
+
tokenizer = AutoTokenizer.from_pretrained("somosnlp/NoticIA-7B")
|
23 |
+
|
24 |
+
# Cargamos el modelo en 4 bits para usar menos VRAM
|
25 |
+
# Usamos bitsandbytes por que es lo más sencillo de implementar para la demo aunque no es ni lo más rápido ni lo más eficiente
|
26 |
+
quantization_config = BitsAndBytesConfig(
|
27 |
+
load_in_4bit=True,
|
28 |
+
bnb_4bit_compute_dtype=torch.bfloat16,
|
29 |
+
bnb_4bit_use_double_quant=True,
|
30 |
+
)
|
31 |
+
|
32 |
+
model = AutoModelForCausalLM.from_pretrained(
|
33 |
+
"somosnlp/NoticIA-7B",
|
34 |
+
torch_dtype=torch.bfloat16,
|
35 |
+
device_map="auto",
|
36 |
+
quantization_config=quantization_config,
|
37 |
+
)
|
38 |
+
|
39 |
+
# Parámetros de generación.
|
40 |
+
generation_config = GenerationConfig(
|
41 |
+
max_new_tokens=128, # Los resúmenes son cortos, no necesitamos más tokens
|
42 |
+
min_new_tokens=1, # No queremos resúmenes vacíos
|
43 |
+
do_sample=True, # Un poquito mejor que greedy sampling
|
44 |
+
num_beams=1,
|
45 |
+
use_cache=True, # Eficiencia
|
46 |
+
top_k=40,
|
47 |
+
top_p=0.1,
|
48 |
+
repetition_penalty=1.1, # Ayuda a evitar que el modelo entre en bucles
|
49 |
+
encoder_repetition_penalty=1.1, # Favorecemos que el modelo cite el texto original
|
50 |
+
resumenerature=0.15, # resumeneratura baja para evitar que el modelo genere texto muy creativo.
|
51 |
+
)
|
52 |
+
|
53 |
+
# Stop words, para evitar que el modelo genere tokens que no queremos.
|
54 |
+
stop_words = [
|
55 |
+
"<s>",
|
56 |
+
"</s>",
|
57 |
+
"\\n",
|
58 |
+
"[/INST]",
|
59 |
+
"[INST]",
|
60 |
+
"### User:",
|
61 |
+
"### Assistant:",
|
62 |
+
"###",
|
63 |
+
"<start_of_turn>" "<end_of_turn>" "<end_of_turn>\n" "<end_of_turn>\\n",
|
64 |
+
"<eos>",
|
65 |
+
]
|
66 |
+
|
67 |
+
# Creamos un logits processor para detener la generación cuando el modelo genere un stop word
|
68 |
+
stop_criteria = LogitsProcessorList(
|
69 |
+
[
|
70 |
+
StopAfterTokenIsGenerated(
|
71 |
+
stops=[
|
72 |
+
torch.tensor(tokenizer.encode(stop_word, add_special_tokens=False))
|
73 |
+
for stop_word in stop_words.copy()
|
74 |
+
],
|
75 |
+
eos_token_id=tokenizer.eos_token_id,
|
76 |
+
)
|
77 |
+
]
|
78 |
+
)
|
79 |
+
|
80 |
+
|
81 |
+
def generate_text(url: str) -> (str, str):
|
82 |
+
"""
|
83 |
+
Dada una URL de una noticia, genera un resumen de una sola frase que revela la verdad detrás del titular.
|
84 |
+
|
85 |
+
Args:
|
86 |
+
url (str): URL de la noticia.
|
87 |
+
|
88 |
+
Returns:
|
89 |
+
str: Titular de la noticia.
|
90 |
+
str: Resumen de la noticia.
|
91 |
+
"""
|
92 |
+
global cache_handler
|
93 |
+
global total_runs
|
94 |
+
|
95 |
+
total_runs += 1
|
96 |
+
print(f"Total runs: {total_runs}. Last run: {datetime.datetime.now()}")
|
97 |
+
|
98 |
+
url = url.strip()
|
99 |
+
|
100 |
+
if url.startswith("https://twitter.com/") or url.startswith("https://x.com/"):
|
101 |
+
yield (
|
102 |
+
"🤖 Vaya, parece que has introducido la url de un tweet. No puedo acceder a tweets, tienes que introducir la URL de una noticia.",
|
103 |
+
"❌❌❌ Si el tweet contiene una noticia, dame la URL de la noticia ❌❌❌",
|
104 |
+
"Error",
|
105 |
+
)
|
106 |
+
return (
|
107 |
+
"🤖 Vaya, parece que has introducido la url de un tweet. No puedo acceder a tweets, tienes que introducir la URL de una noticia.",
|
108 |
+
"❌❌❌ Si el tweet contiene una noticia, dame la URL de la noticia ❌❌❌",
|
109 |
+
"Error",
|
110 |
+
)
|
111 |
+
|
112 |
+
# 1) Download the article
|
113 |
+
|
114 |
+
# progress(0, desc="🤖 Accediendo a la noticia")
|
115 |
+
|
116 |
+
# First, check if the URL is in the cache
|
117 |
+
headline, text, resumen = cache_handler.get_from_cache(url, 0)
|
118 |
+
if headline is not None and text is not None and resumen is not None:
|
119 |
+
yield headline, resumen
|
120 |
+
return headline, resumen
|
121 |
+
else:
|
122 |
+
try:
|
123 |
+
article = Article(url)
|
124 |
+
article.download()
|
125 |
+
article.parse()
|
126 |
+
headline = article.title
|
127 |
+
text = article.text
|
128 |
+
except Exception as e:
|
129 |
+
print(e)
|
130 |
+
headline = None
|
131 |
+
text = None
|
132 |
+
|
133 |
+
if headline is None or text is None:
|
134 |
+
yield (
|
135 |
+
"🤖 No he podido acceder a la notica, asegurate que la URL es correcta y que es posible acceder a la noticia desde un navegador.",
|
136 |
+
"❌❌❌ Inténtalo de nuevo ❌❌❌",
|
137 |
+
"Error",
|
138 |
+
)
|
139 |
+
return (
|
140 |
+
"🤖 No he podido acceder a la notica, asegurate que la URL es correcta y que es posible acceder a la noticia desde un navegador.",
|
141 |
+
"❌❌❌ Inténtalo de nuevo ❌❌❌",
|
142 |
+
"Error",
|
143 |
+
)
|
144 |
+
|
145 |
+
# progress(0.5, desc="🤖 Leyendo noticia")
|
146 |
+
|
147 |
+
try:
|
148 |
+
prompt = summarize_clickbait_short_prompt(headline=headline, body=text)
|
149 |
+
|
150 |
+
formatted_prompt = tokenizer.apply_chat_template(
|
151 |
+
[{"role": "user", "content": prompt}],
|
152 |
+
tokenize=False,
|
153 |
+
add_generation_prompt=True,
|
154 |
+
)
|
155 |
+
|
156 |
+
model_inputs = tokenizer(
|
157 |
+
[formatted_prompt], return_tensors="pt", add_special_tokens=False
|
158 |
+
)
|
159 |
+
|
160 |
+
streamer = TextStreamer(tokenizer=tokenizer, skip_prompt=True)
|
161 |
+
|
162 |
+
model_output = model.generate(
|
163 |
+
**model_inputs.to(model.device),
|
164 |
+
streamer=streamer,
|
165 |
+
generation_config=generation_config,
|
166 |
+
logits_processor=stop_criteria,
|
167 |
+
)
|
168 |
+
|
169 |
+
yield headline, streamer
|
170 |
+
|
171 |
+
resumen = tokenizer.batch_decode(
|
172 |
+
model_output,
|
173 |
+
skip_special_tokens=True,
|
174 |
+
clean_up_tokenization_spaces=True,
|
175 |
+
)[0].replace("<|end_of_turn|>", "")
|
176 |
+
|
177 |
+
resumen = resumen.split("GPT4 Correct Assistant:")[-1]
|
178 |
+
|
179 |
+
except Exception as e:
|
180 |
+
print(e)
|
181 |
+
yield (
|
182 |
+
"🤖 Error en la generación.",
|
183 |
+
"❌❌❌ Inténtalo de nuevo más tarde ❌❌❌",
|
184 |
+
"Error",
|
185 |
+
)
|
186 |
+
return (
|
187 |
+
"🤖 Error en la generación.",
|
188 |
+
"❌❌❌ Inténtalo de nuevo más tarde ❌❌❌",
|
189 |
+
"Error",
|
190 |
+
)
|
191 |
+
|
192 |
+
cache_handler.add_to_cache(
|
193 |
+
url=url, title=headline, text=text, summary_type=0, summary=resumen
|
194 |
+
)
|
195 |
+
yield headline, resumen
|
196 |
+
|
197 |
+
hits, misses, cache_len = cache_handler.get_cache_stats()
|
198 |
+
print(
|
199 |
+
f"Hits: {hits}, misses: {misses}, cache length: {cache_len}. Percent hits: {round(hits/(hits+misses)*100,2)}%."
|
200 |
+
)
|
201 |
+
return headline, resumen
|
202 |
+
|
203 |
+
|
204 |
+
# Usamos una cache para guardar las últimas URL procesadas
|
205 |
+
# Los usuarios seguramente introducirán en un mismo día la misma URL varias veces, por que
|
206 |
+
# diferentes personas querrán ver el resumen de la misma noticia.
|
207 |
+
# La cache se encarga de guardar los resúmenes de las noticias para que no tengamos que volver a generarlos.
|
208 |
+
# La cache tiene un tamaño máximo de 1000 elementos, cuando se llena, se elimina el elemento más antiguo.
|
209 |
+
cache_handler = CacheHandler(max_cache_size=1000)
|
210 |
+
|
211 |
+
|
212 |
+
demo = gr.Interface(
|
213 |
+
generate_text,
|
214 |
+
inputs=[
|
215 |
+
gr.Textbox(
|
216 |
+
label="🌐 URL de la noticia",
|
217 |
+
info="Introduce la URL de la noticia que deseas resumir.",
|
218 |
+
value="https://somosnlp.org/",
|
219 |
+
interactive=True,
|
220 |
+
)
|
221 |
+
],
|
222 |
+
outputs=[
|
223 |
+
gr.Textbox(
|
224 |
+
label="📰 Titular de la noticia",
|
225 |
+
interactive=False,
|
226 |
+
placeholder="Aquí aparecerá el título de la noticia",
|
227 |
+
),
|
228 |
+
gr.Textbox(
|
229 |
+
label="🗒️ Resumen",
|
230 |
+
interactive=False,
|
231 |
+
placeholder="Aquí aparecerá el resumen de la noticia.",
|
232 |
+
),
|
233 |
+
],
|
234 |
+
# headline="⚔️ Clickbait Fighter! ⚔️",
|
235 |
+
thumbnail="https://huggingface.co/datasets/Iker/NoticIA/resolve/main/assets/logo.png",
|
236 |
+
theme="JohnSmith9982/small_and_pretty",
|
237 |
+
description=header,
|
238 |
+
article=article,
|
239 |
+
cache_examples=False,
|
240 |
+
concurrency_limit=1,
|
241 |
+
examples=[
|
242 |
+
"https://www.huffingtonpost.es/virales/le-compra-abrigo-abuela-97nos-reaccion-fantasia.html",
|
243 |
+
"https://emisorasunidas.com/2023/12/29/que-pasara-el-15-de-enero-de-2024/",
|
244 |
+
"https://www.huffingtonpost.es/virales/llega-espana-le-llama-atencion-nombres-propios-persona.html",
|
245 |
+
"https://www.infobae.com/que-puedo-ver/2023/11/19/la-comedia-familiar-y-navidena-que-ya-esta-en-netflix-y-puedes-ver-en-estas-fiestas/",
|
246 |
+
"https://www.cope.es/n/1610984",
|
247 |
+
],
|
248 |
+
submit_btn="Generar resumen",
|
249 |
+
stop_btn="Detener generación",
|
250 |
+
clear_btn="Limpiar",
|
251 |
+
allow_flagging=False,
|
252 |
+
)
|
253 |
+
|
254 |
+
demo.queue(max_size=None)
|
255 |
+
demo.launch(share=False)
|
cache_system.py
ADDED
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from collections import OrderedDict
|
2 |
+
from datetime import datetime
|
3 |
+
from typing import Optional
|
4 |
+
|
5 |
+
|
6 |
+
class CacheHandler:
|
7 |
+
def __init__(self, max_cache_size: int = 1000):
|
8 |
+
# Using OrderedDict to maintain the order of insertion for efficient removal of oldest items
|
9 |
+
self.cache = OrderedDict()
|
10 |
+
self.max_cache_size = max_cache_size
|
11 |
+
self.misses = 0
|
12 |
+
self.hits = 0
|
13 |
+
|
14 |
+
def add_to_cache(
|
15 |
+
self, url: str, title: str, text: str, summary_type: int, summary: str
|
16 |
+
):
|
17 |
+
# If URL already exists, update it and move it to the end to mark it as the most recently used
|
18 |
+
if url in self.cache:
|
19 |
+
self.cache.move_to_end(url)
|
20 |
+
self.cache[url][f"summary_{summary_type}"] = summary
|
21 |
+
self.cache[url]["date"] = datetime.now()
|
22 |
+
else:
|
23 |
+
# Add new entry to the cache
|
24 |
+
self.cache[url] = {
|
25 |
+
"title": title,
|
26 |
+
"text": text,
|
27 |
+
"date": datetime.now(),
|
28 |
+
"summary_0": summary if summary_type == 0 else None,
|
29 |
+
"summary_50": summary if summary_type == 50 else None,
|
30 |
+
"summary_100": summary if summary_type == 100 else None,
|
31 |
+
}
|
32 |
+
# Remove the oldest item if cache exceeds max size
|
33 |
+
if len(self.cache) > self.max_cache_size:
|
34 |
+
self.cache.move_to_end(
|
35 |
+
"https://ikergarcia1996.github.io/Iker-Garcia-Ferrero/"
|
36 |
+
) # This is the default value in the demo, so we don't want to remove it
|
37 |
+
self.cache.popitem(last=False) # pop the oldest item
|
38 |
+
|
39 |
+
def get_from_cache(
|
40 |
+
self, url: str, summary_type: int, second_try: bool = False
|
41 |
+
) -> Optional[tuple]:
|
42 |
+
if url in self.cache and self.cache[url][f"summary_{summary_type}"] is not None:
|
43 |
+
# Move the accessed item to the end to mark it as recently used
|
44 |
+
self.cache.move_to_end(url)
|
45 |
+
self.hits += 1
|
46 |
+
if second_try:
|
47 |
+
# In the first try we didn't get the cache hit, probably because it was a shortened URL
|
48 |
+
# So me decrease the number of misses, because we got the cache hit in the end
|
49 |
+
self.misses -= 1
|
50 |
+
return (
|
51 |
+
self.cache[url]["title"],
|
52 |
+
self.cache[url]["text"],
|
53 |
+
self.cache[url][f"summary_{summary_type}"],
|
54 |
+
)
|
55 |
+
else:
|
56 |
+
if not second_try:
|
57 |
+
self.misses += 1
|
58 |
+
return None, None, None
|
59 |
+
|
60 |
+
def get_cache_stats(self):
|
61 |
+
return self.hits, self.misses, len(self.cache)
|
header.py
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
header = """
|
2 |
+
|
3 |
+
|
4 |
+
<p align="center">
|
5 |
+
<img src="https://huggingface.co/datasets/Iker/NoticIA/resolve/main/assets/logo.png" style="width: 50%;">
|
6 |
+
</p>
|
7 |
+
|
8 |
+
<p align="justify">
|
9 |
+
Los artículos 🖱️Clickbait buscan captar la atención de los lectores mediante la curiosidad, utilizando titulares que plantean preguntas o afirmaciones incompletas, sensacionalistas, exageradas o directamente engañosas. Estos titulares a menudo esconden la respuesta al clickbait hasta el final del artículo, obligando al lector a desplazarse a través de un sinfín de contenido irrelevante. El verdadero objetivo es atraer visitas a la página para exponer al usuario a una cantidad máxima de publicidad, sacrificando la calidad y el valor informativo en el proceso.
|
10 |
+
</p>
|
11 |
+
|
12 |
+
### ¿Por qué representa un problema?
|
13 |
+
<p align="justify">
|
14 |
+
La práctica del 🖱️Clickbait erosiona la confianza del público en las fuentes de noticias digitales y perjudica los ingresos publicitarios de los productores de contenido legítimo, que pueden experimentar una disminución en su tráfico web como resultado.
|
15 |
+
</p>
|
16 |
+
|
17 |
+
### ¿Qué acciones hemos tomado para abordar este desafío?
|
18 |
+
|
19 |
+
- 📰 Hemos desarrollado NoticIA, una colección que incluye 850 artículos de noticias en español caracterizados por titulares clickbait. Cada artículo está acompañado de un resumen generativo de alta calidad y concisión, redactado por expertos humanos. Explora [🤗NoticIA-it](https://huggingface.co/datasets/somosnlp/NoticIA-it).
|
20 |
+
- 📈 Evaluamos decenas de modelos de inteligencia artificial en este conjunto de datos. Los resultados se pueden consultar aquí: [NoticIA Benchmark](https://huggingface.co/somosnlp/Resumen_Noticias_Clickbait/resolve/main/Results_finetune.png).
|
21 |
+
- 🤖 Entrenamos un avanzado modelo de lenguaje con 7 billones de parámetros específicamente con nuestro dataset, [🤗NoticIA-7B](https://huggingface.co/somosnlp/NoticIA-7B).
|
22 |
+
|
23 |
+
<p align="justify">
|
24 |
+
NoticIA ofrece un escenario ideal para probar la habilidad de los modelos de lenguaje en la comprensión de textos en español. Esta tarea es compleja que discernir la pregunta oculta en un titular clickbait o identificar la información que realmente busca el usuario. Este reto implica filtrar grandes volúmenes de contenido superfluo para hallar y resumir de manera precisa y sucinta la información relevante.
|
25 |
+
</p>
|
26 |
+
|
27 |
+
## ¿Cómo funciona esta demo?
|
28 |
+
<p align="justify">
|
29 |
+
Solo introduce la URL de un artículo clickbait en el campo de texto y haz clic en el botón "Generar resumen" para probarla.
|
30 |
+
</p>
|
31 |
+
|
32 |
+
## Mirando hacia el futuro
|
33 |
+
- 📚 Planeamos expandir NoticIA con aún más artículos clickbait.
|
34 |
+
- 🔮 Introduciremos etiquetas adicionales al conjunto de datos, incluyendo métricas que cuantifiquen el grado de clickbait de los artículos.
|
35 |
+
- 📔 Estamos preparando un artículo para profundizar en los hallazgos y metodologías de nuestro proyecto.
|
36 |
+
|
37 |
+
""".strip()
|
38 |
+
|
39 |
+
|
40 |
+
article = """
|
41 |
+
|
42 |
+
Esta demo ha sido creada por [Iker García-Ferrero](https://ikergarcia1996.github.io/Iker-Garcia-Ferrero/) y [Begoña Altuna](https://www.linkedin.com/in/bego%C3%B1a-altuna-78014139). Somos investigadores en PLN en la Universidad del País Vasco, dentro del grupo de investigación [IXA](https://www.ixa.eus/) y formamos parte de [HiTZ, el Centro Vasco de Tecnología de la Lengua](https://www.hitz.eus/es).
|
43 |
+
|
44 |
+
|
45 |
+
<div style="display: flex; justify-content: space-around; width: 100%;">
|
46 |
+
<div style="width: 50%;" align="left">
|
47 |
+
<a href="http://ixa.si.ehu.es/">
|
48 |
+
<img src="https://raw.githubusercontent.com/ikergarcia1996/Iker-Garcia-Ferrero/master/icons/ixa.png" width="50" height="50" alt="Ixa NLP Group">
|
49 |
+
</a>
|
50 |
+
</div>
|
51 |
+
<div style="width: 50%;" align="right">
|
52 |
+
<a href="http://www.hitz.eus/">
|
53 |
+
<img src="https://raw.githubusercontent.com/ikergarcia1996/Iker-Garcia-Ferrero/master/icons/Hitz.png" width="300" height="50" alt="HiTZ Basque Center for Language Technologies">
|
54 |
+
</a>
|
55 |
+
</div>
|
56 |
+
</div>
|
57 |
+
""".strip()
|
prompts.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
def summarize_clickbait_short_prompt(
|
2 |
+
headline: str,
|
3 |
+
body: str,
|
4 |
+
) -> str:
|
5 |
+
"""
|
6 |
+
Generate the prompt for the model.
|
7 |
+
|
8 |
+
Args:
|
9 |
+
headline (`str`):
|
10 |
+
The headline of the article.
|
11 |
+
body (`str`):
|
12 |
+
The body of the article.
|
13 |
+
Returns:
|
14 |
+
`str`: The formatted prompt.
|
15 |
+
"""
|
16 |
+
|
17 |
+
return (
|
18 |
+
f"Ahora eres una Inteligencia Artificial experta en desmontar titulares sensacionalistas o clickbait. "
|
19 |
+
f"Tu tarea consiste en analizar noticias con titulares sensacionalistas y "
|
20 |
+
f"generar un resumen de una sola frase que revele la verdad detrás del titular.\n"
|
21 |
+
f"Este es el titular de la noticia: {headline}\n"
|
22 |
+
f"El titular plantea una pregunta o proporciona información incompleta. "
|
23 |
+
f"Debes buscar en el cuerpo de la noticia una frase que responda lo que se sugiere en el título. "
|
24 |
+
f"Siempre que puedas cita el texto original, especialmente si se trata de una frase que alguien ha dicho. "
|
25 |
+
f"Si citas una frase que alguien ha dicho, usa comillas para indicar que es una cita. "
|
26 |
+
f"Usa siempre las mínimas palabras posibles. No es necesario que la respuesta sea una oración completa. "
|
27 |
+
f"Puede ser sólo el foco de la pregunta. "
|
28 |
+
f"Recuerda responder siempre en Español.\n"
|
29 |
+
f"Este es el cuerpo de la noticia:\n"
|
30 |
+
f"{body}"
|
31 |
+
)
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
setuptools
|
2 |
+
gradio
|
3 |
+
transformers
|
4 |
+
numpy
|
5 |
+
bitsandbytes
|
6 |
+
newspaper3k
|
utils.py
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
from typing import List
|
3 |
+
|
4 |
+
import torch
|
5 |
+
from transformers import (
|
6 |
+
LogitsProcessor,
|
7 |
+
)
|
8 |
+
|
9 |
+
|
10 |
+
class StopAfterTokenIsGenerated(LogitsProcessor):
|
11 |
+
def __init__(self, stops: List[torch.tensor], eos_token_id: int):
|
12 |
+
super().__init__()
|
13 |
+
|
14 |
+
self.stops = stops
|
15 |
+
self.eos_token_id = eos_token_id
|
16 |
+
logging.info(f"Stopping criteria words ids: {self.stops}")
|
17 |
+
self.first_batch = True
|
18 |
+
|
19 |
+
def __call__(
|
20 |
+
self, input_ids: torch.LongTensor, scores: torch.FloatTensor
|
21 |
+
) -> torch.FloatTensor:
|
22 |
+
"""
|
23 |
+
Args:
|
24 |
+
input_ids (`torch.LongTensor` of shape `(batch_size, sequence_length)`):
|
25 |
+
Indices of input sequence tokens in the vocabulary. [What are input IDs?](../glossary#input-ids)
|
26 |
+
scores (`torch.FloatTensor` of shape `(batch_size, config.vocab_size)`):
|
27 |
+
Prediction scores of a language modeling head. These can be logits for each vocabulary when not using beam
|
28 |
+
search or log softmax for each vocabulary token when using beam search
|
29 |
+
|
30 |
+
Return:
|
31 |
+
`torch.FloatTensor` of shape `(batch_size, config.vocab_size)`: The processed prediction scores.
|
32 |
+
|
33 |
+
"""
|
34 |
+
if self.first_batch:
|
35 |
+
self.first_batch = False
|
36 |
+
return scores
|
37 |
+
|
38 |
+
for seq_no, seq in enumerate(input_ids):
|
39 |
+
# logging.info(seq_no)
|
40 |
+
for stop in self.stops:
|
41 |
+
stop = stop.to(device=seq.device, dtype=seq.dtype)
|
42 |
+
if (
|
43 |
+
len(seq) >= len(stop)
|
44 |
+
and torch.all((stop == seq[-len(stop) :])).item()
|
45 |
+
):
|
46 |
+
scores[seq_no, :] = -float("inf")
|
47 |
+
scores[seq_no, self.eos_token_id] = 0
|
48 |
+
# logging.info(f"Stopping criteria found: {stop}")
|
49 |
+
break
|
50 |
+
|
51 |
+
return scores
|
52 |
+
|
53 |
+
def reset(self):
|
54 |
+
self.first_batch = True
|