Rhulli commited on
Commit
e445da5
·
verified ·
1 Parent(s): 658fb5e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +240 -235
app.py CHANGED
@@ -1,235 +1,240 @@
1
- import re
2
- import unicodedata
3
- import io
4
- import torch
5
- import gradio as gr
6
- import pdfplumber
7
- import pandas as pd
8
- from transformers import (
9
- AutoTokenizer,
10
- AutoModelForTokenClassification,
11
- AutoModelForCausalLM,
12
- BitsAndBytesConfig,
13
- )
14
- from peft import PeftModel
15
-
16
- # --- Funciones de normalización y limpieza ---
17
- _SPACE_VARIANTS = r"[\u202f\u00a0\u2009\u200a\u2060]"
18
-
19
- def _normalise_apostrophes(text: str) -> str:
20
- return text.replace("´", "'").replace("’", "'")
21
-
22
- def _normalise_spaces(text: str, collapse: bool = True) -> str:
23
- text = re.sub(_SPACE_VARIANTS, " ", text)
24
- text = unicodedata.normalize("NFKC", text)
25
- if collapse:
26
- text = re.sub(r"[ ]{2,}", " ", text)
27
- return text.strip()
28
-
29
- def _clean_timex(ent: str) -> str:
30
- ent = ent.replace("</s>", "").strip()
31
- return re.sub(r"[\.]+$", "", ent)
32
-
33
- # --- Identificadores de los modelos ---
34
- NER_ID = "Rhulli/Roberta-ner-temporal-expresions-secondtrain"
35
- ID2LABEL = {0: "O", 1: "B-TIMEX", 2: "I-TIMEX"}
36
- BASE_ID = "google/gemma-2b-it"
37
- ADAPTER_ID = "Rhulli/gemma-2b-it-TIMEX3"
38
-
39
- # --- Configuración de cuantización para el modelo de normalización ---
40
- quant_config = BitsAndBytesConfig(
41
- load_in_4bit=True,
42
- bnb_4bit_quant_type="nf4",
43
- bnb_4bit_compute_dtype=torch.float16,
44
- )
45
-
46
- def load_models():
47
- # Carga del modelo NER
48
- ner_tok = AutoTokenizer.from_pretrained(NER_ID)
49
- ner_mod = AutoModelForTokenClassification.from_pretrained(NER_ID)
50
- ner_mod.eval()
51
- if torch.cuda.is_available():
52
- ner_mod.to("cuda")
53
-
54
- # Carga del modelo de normalización (LoRA + 4bit)
55
- base_mod = AutoModelForCausalLM.from_pretrained(
56
- BASE_ID,
57
- quantization_config=quant_config,
58
- device_map="auto"
59
- )
60
- norm_tok = AutoTokenizer.from_pretrained(ADAPTER_ID, use_fast=True)
61
- norm_mod = PeftModel.from_pretrained(
62
- base_mod,
63
- ADAPTER_ID,
64
- device_map="auto"
65
- )
66
- norm_mod.eval()
67
-
68
- return ner_tok, ner_mod, norm_tok, norm_mod
69
-
70
- # Carga inicial de los modelos
71
- ner_tok, ner_mod, norm_tok, norm_mod = load_models()
72
- eos_id = norm_tok.convert_tokens_to_ids("<end_of_turn>")
73
-
74
- # --- Lectura de archivos ---
75
- def read_file(file_obj) -> str:
76
- """
77
- Lee contenido de un archivo (.txt o .pdf) usando su ruta file_obj.name.
78
- """
79
- path = file_obj.name
80
- if path.lower().endswith('.pdf'):
81
- full = ''
82
- with pdfplumber.open(path) as pdf:
83
- for page in pdf.pages:
84
- txt = page.extract_text()
85
- if txt:
86
- full += txt + '\n'
87
- return full
88
- else:
89
- with open(path, 'rb') as f:
90
- data = f.read()
91
- try:
92
- return data.decode('utf-8')
93
- except:
94
- return data.decode('latin-1', errors='ignore')
95
-
96
- # --- Procesamiento de texto ---
97
- def extract_timex(text: str):
98
- text_norm = _normalise_spaces(_normalise_apostrophes(text))
99
- inputs = ner_tok(text_norm, return_tensors="pt", truncation=True)
100
- if torch.cuda.is_available():
101
- inputs = {k: v.to("cuda") for k, v in inputs.items()}
102
- with torch.no_grad():
103
- logits = ner_mod(**inputs).logits
104
-
105
- preds = torch.argmax(logits, dim=2)[0].cpu().numpy()
106
- tokens = ner_tok.convert_ids_to_tokens(inputs["input_ids"][0])
107
-
108
- entities = []
109
- current = []
110
- for tok, lab in zip(tokens, preds):
111
- tag = ID2LABEL.get(lab, "O")
112
- if tag == "B-TIMEX":
113
- if current:
114
- entities.append(ner_tok.convert_tokens_to_string(current).strip())
115
- current = [tok]
116
- elif tag == "I-TIMEX" and current:
117
- current.append(tok)
118
- else:
119
- if current:
120
- entities.append(ner_tok.convert_tokens_to_string(current).strip())
121
- current = []
122
- if current:
123
- entities.append(ner_tok.convert_tokens_to_string(current).strip())
124
-
125
- return [_clean_timex(e) for e in entities]
126
-
127
- def normalize_timex(expr: str, dct: str) -> str:
128
- prompt = (
129
- f"<start_of_turn>user\n"
130
- f"Tu tarea es normalizar la expresión temporal al formato TIMEX3, utilizando la fecha de anclaje (DCT) cuando sea necesaria.\n"
131
- f"Fecha de Anclaje (DCT): {dct}\n"
132
- f"Expresión Original: {expr}<end_of_turn>\n"
133
- f"<start_of_turn>model\n"
134
- )
135
- inputs = norm_tok(prompt, return_tensors="pt").to(norm_mod.device)
136
- outputs = norm_mod.generate(**inputs, max_new_tokens=64, eos_token_id=eos_id)
137
-
138
- full_decoded = norm_tok.decode(
139
- outputs[0, inputs.input_ids.shape[1]:],
140
- skip_special_tokens=False
141
- )
142
- raw_tag = full_decoded.split("<end_of_turn>")[0].strip()
143
- # Reemplazar corchetes por angulares
144
- return raw_tag.replace("[", "<").replace("]", ">")
145
-
146
- # --- Pipeline principal ---
147
- def run_pipeline(files, raw_text, dct):
148
- rows = []
149
- file_list = files if isinstance(files, list) else ([files] if files else [])
150
-
151
- # Procesar texto libre
152
- if raw_text:
153
- for line in raw_text.splitlines():
154
- if line.strip():
155
- for expr in extract_timex(line):
156
- rows.append({
157
- 'Expresión': expr,
158
- 'Normalización': normalize_timex(expr, dct)
159
- })
160
-
161
- # Procesar archivos
162
- for f in file_list:
163
- content = read_file(f)
164
- for line in content.splitlines():
165
- if line.strip():
166
- for expr in extract_timex(line):
167
- rows.append({
168
- 'Expresión': expr,
169
- 'Normalización': normalize_timex(expr, dct)
170
- })
171
-
172
- df = pd.DataFrame(rows)
173
- if df.empty:
174
- df = pd.DataFrame([], columns=['Expresión', 'Normalización'])
175
-
176
- # Devolver dos valores: la tabla y un string vacío para logs
177
- return df, ""
178
-
179
- # --- Interfaz Gradio ---
180
- with gr.Blocks() as demo:
181
- gr.Markdown(
182
- """
183
- ## TIMEX Extractor & Normalizer
184
-
185
- Esta aplicación permite extraer expresiones temporales de textos o archivos (.txt, .pdf)
186
- y normalizarlas a formato TIMEX3.
187
-
188
- **Cómo usar:**
189
- - Sube uno o varios archivos en la columna izquierda.
190
- - Ajusta la *Fecha de Anclaje (DCT)* justo debajo de los archivos.
191
- - Escribe o pega tu texto en la columna derecha.
192
- - Pulsa **Procesar** para ver los resultados en la tabla debajo.
193
-
194
- **Columnas de salida:**
195
- - *Expresión*: la frase temporal extraída.
196
- - *Normalización*: la etiqueta TIMEX3 generada (con `< >`).
197
- """
198
- )
199
-
200
- with gr.Row():
201
- with gr.Column(scale=1):
202
- files = gr.File(
203
- file_types=['.txt', '.pdf'],
204
- file_count='multiple',
205
- label='Archivos (.txt, .pdf)'
206
- )
207
- dct_input = gr.Textbox(
208
- value="2025-06-11",
209
- label="Fecha de Anclaje (YYYY-MM-DD)"
210
- )
211
- run_btn = gr.Button("Procesar")
212
- with gr.Column(scale=2):
213
- raw_text = gr.Textbox(
214
- lines=15,
215
- placeholder='Pega o escribe aquí tu texto... (opcional si subes archivos)',
216
- label='Texto libre'
217
- )
218
-
219
- output_table = gr.Dataframe(
220
- headers=['Expresión', 'Normalización'],
221
- label="Resultados"
222
- )
223
- output_logs = gr.Textbox(
224
- label="Logs",
225
- lines=5,
226
- interactive=False
227
- )
228
-
229
- run_btn.click(
230
- fn=run_pipeline,
231
- inputs=[files, raw_text, dct_input],
232
- outputs=[output_table, output_logs]
233
- )
234
-
235
- demo.launch(debug=True, server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
1
+ from huggingface_hub import login
2
+
3
+ login()
4
+
5
+
6
+ import re
7
+ import unicodedata
8
+ import io
9
+ import torch
10
+ import gradio as gr
11
+ import pdfplumber
12
+ import pandas as pd
13
+ from transformers import (
14
+ AutoTokenizer,
15
+ AutoModelForTokenClassification,
16
+ AutoModelForCausalLM,
17
+ BitsAndBytesConfig,
18
+ )
19
+ from peft import PeftModel
20
+
21
+ # --- Funciones de normalización y limpieza ---
22
+ _SPACE_VARIANTS = r"[\u202f\u00a0\u2009\u200a\u2060]"
23
+
24
+ def _normalise_apostrophes(text: str) -> str:
25
+ return text.replace("´", "'").replace("’", "'")
26
+
27
+ def _normalise_spaces(text: str, collapse: bool = True) -> str:
28
+ text = re.sub(_SPACE_VARIANTS, " ", text)
29
+ text = unicodedata.normalize("NFKC", text)
30
+ if collapse:
31
+ text = re.sub(r"[ ]{2,}", " ", text)
32
+ return text.strip()
33
+
34
+ def _clean_timex(ent: str) -> str:
35
+ ent = ent.replace("</s>", "").strip()
36
+ return re.sub(r"[\.]+$", "", ent)
37
+
38
+ # --- Identificadores de los modelos ---
39
+ NER_ID = "Rhulli/Roberta-ner-temporal-expresions-secondtrain"
40
+ ID2LABEL = {0: "O", 1: "B-TIMEX", 2: "I-TIMEX"}
41
+ BASE_ID = "google/gemma-2b-it"
42
+ ADAPTER_ID = "Rhulli/gemma-2b-it-TIMEX3"
43
+
44
+ # --- Configuración de cuantización para el modelo de normalización ---
45
+ quant_config = BitsAndBytesConfig(
46
+ load_in_4bit=True,
47
+ bnb_4bit_quant_type="nf4",
48
+ bnb_4bit_compute_dtype=torch.float16,
49
+ )
50
+
51
+ def load_models():
52
+ # Carga del modelo NER
53
+ ner_tok = AutoTokenizer.from_pretrained(NER_ID)
54
+ ner_mod = AutoModelForTokenClassification.from_pretrained(NER_ID)
55
+ ner_mod.eval()
56
+ if torch.cuda.is_available():
57
+ ner_mod.to("cuda")
58
+
59
+ # Carga del modelo de normalización (LoRA + 4bit)
60
+ base_mod = AutoModelForCausalLM.from_pretrained(
61
+ BASE_ID,
62
+ quantization_config=quant_config,
63
+ device_map="auto"
64
+ )
65
+ norm_tok = AutoTokenizer.from_pretrained(ADAPTER_ID, use_fast=True)
66
+ norm_mod = PeftModel.from_pretrained(
67
+ base_mod,
68
+ ADAPTER_ID,
69
+ device_map="auto"
70
+ )
71
+ norm_mod.eval()
72
+
73
+ return ner_tok, ner_mod, norm_tok, norm_mod
74
+
75
+ # Carga inicial de los modelos
76
+ ner_tok, ner_mod, norm_tok, norm_mod = load_models()
77
+ eos_id = norm_tok.convert_tokens_to_ids("<end_of_turn>")
78
+
79
+ # --- Lectura de archivos ---
80
+ def read_file(file_obj) -> str:
81
+ """
82
+ Lee contenido de un archivo (.txt o .pdf) usando su ruta file_obj.name.
83
+ """
84
+ path = file_obj.name
85
+ if path.lower().endswith('.pdf'):
86
+ full = ''
87
+ with pdfplumber.open(path) as pdf:
88
+ for page in pdf.pages:
89
+ txt = page.extract_text()
90
+ if txt:
91
+ full += txt + '\n'
92
+ return full
93
+ else:
94
+ with open(path, 'rb') as f:
95
+ data = f.read()
96
+ try:
97
+ return data.decode('utf-8')
98
+ except:
99
+ return data.decode('latin-1', errors='ignore')
100
+
101
+ # --- Procesamiento de texto ---
102
+ def extract_timex(text: str):
103
+ text_norm = _normalise_spaces(_normalise_apostrophes(text))
104
+ inputs = ner_tok(text_norm, return_tensors="pt", truncation=True)
105
+ if torch.cuda.is_available():
106
+ inputs = {k: v.to("cuda") for k, v in inputs.items()}
107
+ with torch.no_grad():
108
+ logits = ner_mod(**inputs).logits
109
+
110
+ preds = torch.argmax(logits, dim=2)[0].cpu().numpy()
111
+ tokens = ner_tok.convert_ids_to_tokens(inputs["input_ids"][0])
112
+
113
+ entities = []
114
+ current = []
115
+ for tok, lab in zip(tokens, preds):
116
+ tag = ID2LABEL.get(lab, "O")
117
+ if tag == "B-TIMEX":
118
+ if current:
119
+ entities.append(ner_tok.convert_tokens_to_string(current).strip())
120
+ current = [tok]
121
+ elif tag == "I-TIMEX" and current:
122
+ current.append(tok)
123
+ else:
124
+ if current:
125
+ entities.append(ner_tok.convert_tokens_to_string(current).strip())
126
+ current = []
127
+ if current:
128
+ entities.append(ner_tok.convert_tokens_to_string(current).strip())
129
+
130
+ return [_clean_timex(e) for e in entities]
131
+
132
+ def normalize_timex(expr: str, dct: str) -> str:
133
+ prompt = (
134
+ f"<start_of_turn>user\n"
135
+ f"Tu tarea es normalizar la expresión temporal al formato TIMEX3, utilizando la fecha de anclaje (DCT) cuando sea necesaria.\n"
136
+ f"Fecha de Anclaje (DCT): {dct}\n"
137
+ f"Expresión Original: {expr}<end_of_turn>\n"
138
+ f"<start_of_turn>model\n"
139
+ )
140
+ inputs = norm_tok(prompt, return_tensors="pt").to(norm_mod.device)
141
+ outputs = norm_mod.generate(**inputs, max_new_tokens=64, eos_token_id=eos_id)
142
+
143
+ full_decoded = norm_tok.decode(
144
+ outputs[0, inputs.input_ids.shape[1]:],
145
+ skip_special_tokens=False
146
+ )
147
+ raw_tag = full_decoded.split("<end_of_turn>")[0].strip()
148
+ # Reemplazar corchetes por angulares
149
+ return raw_tag.replace("[", "<").replace("]", ">")
150
+
151
+ # --- Pipeline principal ---
152
+ def run_pipeline(files, raw_text, dct):
153
+ rows = []
154
+ file_list = files if isinstance(files, list) else ([files] if files else [])
155
+
156
+ # Procesar texto libre
157
+ if raw_text:
158
+ for line in raw_text.splitlines():
159
+ if line.strip():
160
+ for expr in extract_timex(line):
161
+ rows.append({
162
+ 'Expresión': expr,
163
+ 'Normalización': normalize_timex(expr, dct)
164
+ })
165
+
166
+ # Procesar archivos
167
+ for f in file_list:
168
+ content = read_file(f)
169
+ for line in content.splitlines():
170
+ if line.strip():
171
+ for expr in extract_timex(line):
172
+ rows.append({
173
+ 'Expresión': expr,
174
+ 'Normalización': normalize_timex(expr, dct)
175
+ })
176
+
177
+ df = pd.DataFrame(rows)
178
+ if df.empty:
179
+ df = pd.DataFrame([], columns=['Expresión', 'Normalización'])
180
+
181
+ # Devolver dos valores: la tabla y un string vacío para logs
182
+ return df, ""
183
+
184
+ # --- Interfaz Gradio ---
185
+ with gr.Blocks() as demo:
186
+ gr.Markdown(
187
+ """
188
+ ## TIMEX Extractor & Normalizer
189
+
190
+ Esta aplicación permite extraer expresiones temporales de textos o archivos (.txt, .pdf)
191
+ y normalizarlas a formato TIMEX3.
192
+
193
+ **Cómo usar:**
194
+ - Sube uno o varios archivos en la columna izquierda.
195
+ - Ajusta la *Fecha de Anclaje (DCT)* justo debajo de los archivos.
196
+ - Escribe o pega tu texto en la columna derecha.
197
+ - Pulsa **Procesar** para ver los resultados en la tabla debajo.
198
+
199
+ **Columnas de salida:**
200
+ - *Expresión*: la frase temporal extraída.
201
+ - *Normalización*: la etiqueta TIMEX3 generada (con `< >`).
202
+ """
203
+ )
204
+
205
+ with gr.Row():
206
+ with gr.Column(scale=1):
207
+ files = gr.File(
208
+ file_types=['.txt', '.pdf'],
209
+ file_count='multiple',
210
+ label='Archivos (.txt, .pdf)'
211
+ )
212
+ dct_input = gr.Textbox(
213
+ value="2025-06-11",
214
+ label="Fecha de Anclaje (YYYY-MM-DD)"
215
+ )
216
+ run_btn = gr.Button("Procesar")
217
+ with gr.Column(scale=2):
218
+ raw_text = gr.Textbox(
219
+ lines=15,
220
+ placeholder='Pega o escribe aquí tu texto... (opcional si subes archivos)',
221
+ label='Texto libre'
222
+ )
223
+
224
+ output_table = gr.Dataframe(
225
+ headers=['Expresión', 'Normalización'],
226
+ label="Resultados"
227
+ )
228
+ output_logs = gr.Textbox(
229
+ label="Logs",
230
+ lines=5,
231
+ interactive=False
232
+ )
233
+
234
+ run_btn.click(
235
+ fn=run_pipeline,
236
+ inputs=[files, raw_text, dct_input],
237
+ outputs=[output_table, output_logs]
238
+ )
239
+
240
+ demo.launch(debug=True, server_name="0.0.0.0", server_port=7860)