AIdeaText commited on
Commit
51b9c15
verified
1 Parent(s): 22dbdb7

Delete modules/text_analysis/semantic_analysis.py

Browse files
modules/text_analysis/semantic_analysis.py DELETED
@@ -1,486 +0,0 @@
1
- # modules/text_analysis/semantic_analysis.py
2
-
3
- # 1. Importaciones est谩ndar del sistema
4
- import logging
5
- import io
6
- import base64
7
- from collections import Counter, defaultdict
8
-
9
- # 2. Importaciones de terceros
10
- import streamlit as st
11
- import spacy
12
- import networkx as nx
13
- import matplotlib.pyplot as plt
14
- from sklearn.feature_extraction.text import TfidfVectorizer
15
- from sklearn.metrics.pairwise import cosine_similarity
16
-
17
- # Solo configurar si no hay handlers ya configurados
18
- logger = logging.getLogger(__name__)
19
-
20
- # 4. Importaciones locales
21
- from .stopwords import (
22
- process_text,
23
- clean_text,
24
- get_custom_stopwords,
25
- get_stopwords_for_spacy
26
- )
27
-
28
-
29
- # Define colors for grammatical categories
30
- POS_COLORS = {
31
- 'ADJ': '#FFA07A', 'ADP': '#98FB98', 'ADV': '#87CEFA', 'AUX': '#DDA0DD',
32
- 'CCONJ': '#F0E68C', 'DET': '#FFB6C1', 'INTJ': '#FF6347', 'NOUN': '#90EE90',
33
- 'NUM': '#FAFAD2', 'PART': '#D3D3D3', 'PRON': '#FFA500', 'PROPN': '#20B2AA',
34
- 'SCONJ': '#DEB887', 'SYM': '#7B68EE', 'VERB': '#FF69B4', 'X': '#A9A9A9',
35
- }
36
-
37
- POS_TRANSLATIONS = {
38
- 'es': {
39
- 'ADJ': 'Adjetivo', 'ADP': 'Preposici贸n', 'ADV': 'Adverbio', 'AUX': 'Auxiliar',
40
- 'CCONJ': 'Conjunci贸n Coordinante', 'DET': 'Determinante', 'INTJ': 'Interjecci贸n',
41
- 'NOUN': 'Sustantivo', 'NUM': 'N煤mero', 'PART': 'Part铆cula', 'PRON': 'Pronombre',
42
- 'PROPN': 'Nombre Propio', 'SCONJ': 'Conjunci贸n Subordinante', 'SYM': 'S铆mbolo',
43
- 'VERB': 'Verbo', 'X': 'Otro',
44
- },
45
- 'en': {
46
- 'ADJ': 'Adjective', 'ADP': 'Preposition', 'ADV': 'Adverb', 'AUX': 'Auxiliary',
47
- 'CCONJ': 'Coordinating Conjunction', 'DET': 'Determiner', 'INTJ': 'Interjection',
48
- 'NOUN': 'Noun', 'NUM': 'Number', 'PART': 'Particle', 'PRON': 'Pronoun',
49
- 'PROPN': 'Proper Noun', 'SCONJ': 'Subordinating Conjunction', 'SYM': 'Symbol',
50
- 'VERB': 'Verb', 'X': 'Other',
51
- },
52
- 'fr': {
53
- 'ADJ': 'Adjectif', 'ADP': 'Pr茅position', 'ADV': 'Adverbe', 'AUX': 'Auxiliaire',
54
- 'CCONJ': 'Conjonction de Coordination', 'DET': 'D茅terminant', 'INTJ': 'Interjection',
55
- 'NOUN': 'Nom', 'NUM': 'Nombre', 'PART': 'Particule', 'PRON': 'Pronom',
56
- 'PROPN': 'Nom Propre', 'SCONJ': 'Conjonction de Subordination', 'SYM': 'Symbole',
57
- 'VERB': 'Verbe', 'X': 'Autre',
58
- }
59
- }
60
-
61
- ENTITY_LABELS = {
62
- 'es': {
63
- "Personas": "lightblue",
64
- "Lugares": "lightcoral",
65
- "Inventos": "lightgreen",
66
- "Fechas": "lightyellow",
67
- "Conceptos": "lightpink"
68
- },
69
- 'en': {
70
- "People": "lightblue",
71
- "Places": "lightcoral",
72
- "Inventions": "lightgreen",
73
- "Dates": "lightyellow",
74
- "Concepts": "lightpink"
75
- },
76
- 'fr': {
77
- "Personnes": "lightblue",
78
- "Lieux": "lightcoral",
79
- "Inventions": "lightgreen",
80
- "Dates": "lightyellow",
81
- "Concepts": "lightpink"
82
- }
83
- }
84
-
85
- #################################################################################
86
- def fig_to_bytes(fig):
87
- """Convierte una figura de matplotlib a bytes."""
88
- try:
89
- buf = io.BytesIO()
90
- fig.savefig(buf, format='png', dpi=300, bbox_inches='tight')
91
- buf.seek(0)
92
- return buf.getvalue()
93
- except Exception as e:
94
- logger.error(f"Error en fig_to_bytes: {str(e)}")
95
- return None
96
-
97
- ###########################################################
98
- def perform_semantic_analysis(text, nlp, lang_code, semantic_t):
99
- """
100
- Realiza el an谩lisis sem谩ntico completo del texto.
101
- """
102
- if not text or not nlp or not lang_code:
103
- logger.error("Par谩metros inv谩lidos para el an谩lisis sem谩ntico")
104
- return {
105
- 'success': False,
106
- 'error': 'Par谩metros inv谩lidos'
107
- }
108
-
109
- try:
110
- logger.info(f"Starting semantic analysis for language: {lang_code}")
111
-
112
- # Procesar texto y remover stopwords
113
- doc = nlp(text)
114
- if not doc:
115
- logger.error("Error al procesar el texto con spaCy")
116
- return {
117
- 'success': False,
118
- 'error': 'Error al procesar el texto'
119
- }
120
-
121
- # Identificar conceptos clave
122
- logger.info("Identificando conceptos clave...")
123
- stopwords = get_custom_stopwords(lang_code)
124
- key_concepts = identify_key_concepts(doc, stopwords=stopwords)
125
-
126
- if not key_concepts:
127
- logger.warning("No se identificaron conceptos clave")
128
- return {
129
- 'success': False,
130
- 'error': 'No se pudieron identificar conceptos clave'
131
- }
132
-
133
- # Crear grafo de conceptos
134
- logger.info(f"Creando grafo de conceptos con {len(key_concepts)} conceptos...")
135
- concept_graph = create_concept_graph(doc, key_concepts)
136
-
137
- if not concept_graph.nodes():
138
- logger.warning("Se cre贸 un grafo vac铆o")
139
- return {
140
- 'success': False,
141
- 'error': 'No se pudo crear el grafo de conceptos'
142
- }
143
-
144
- # Visualizar grafo
145
- logger.info("Visualizando grafo...")
146
- plt.clf() # Limpiar figura actual
147
-
148
- concept_graph_fig = visualize_concept_graph(concept_graph, lang_code, semantic_t)
149
-
150
- # Convertir a bytes
151
- logger.info("Convirtiendo grafo a bytes...")
152
- graph_bytes = fig_to_bytes(concept_graph_fig)
153
-
154
- if not graph_bytes:
155
- logger.error("Error al convertir grafo a bytes")
156
- return {
157
- 'success': False,
158
- 'error': 'Error al generar visualizaci贸n'
159
- }
160
-
161
- # Limpiar recursos
162
- plt.close(concept_graph_fig)
163
- plt.close('all')
164
-
165
- result = {
166
- 'success': True,
167
- 'key_concepts': key_concepts,
168
- 'concept_graph': graph_bytes
169
- }
170
-
171
- logger.info("An谩lisis sem谩ntico completado exitosamente")
172
- return result
173
-
174
- except Exception as e:
175
- logger.error(f"Error in perform_semantic_analysis: {str(e)}")
176
- plt.close('all') # Asegurarse de limpiar recursos
177
- return {
178
- 'success': False,
179
- 'error': str(e)
180
- }
181
- finally:
182
- plt.close('all') # Asegurar limpieza incluso si hay error
183
-
184
- ############################################################
185
-
186
- def identify_key_concepts(doc, stopwords, min_freq=2, min_length=3):
187
- """
188
- Identifica conceptos clave en el texto, excluyendo entidades nombradas.
189
- Args:
190
- doc: Documento procesado por spaCy
191
- stopwords: Lista de stopwords
192
- min_freq: Frecuencia m铆nima para considerar un concepto
193
- min_length: Longitud m铆nima del concepto
194
- Returns:
195
- List[Tuple[str, int]]: Lista de tuplas (concepto, frecuencia)
196
- """
197
- try:
198
- word_freq = Counter()
199
-
200
- # Crear conjunto de tokens que son parte de entidades
201
- entity_tokens = set()
202
- for ent in doc.ents:
203
- entity_tokens.update(token.i for token in ent)
204
-
205
- # Procesar tokens
206
- for token in doc:
207
- # Verificar si el token no es parte de una entidad nombrada
208
- if (token.i not in entity_tokens and # No es parte de una entidad
209
- token.lemma_.lower() not in stopwords and # No es stopword
210
- len(token.lemma_) >= min_length and # Longitud m铆nima
211
- token.is_alpha and # Es alfab茅tico
212
- not token.is_punct and # No es puntuaci贸n
213
- not token.like_num and # No es n煤mero
214
- not token.is_space and # No es espacio
215
- not token.is_stop and # No es stopword de spaCy
216
- not token.pos_ == 'PROPN' and # No es nombre propio
217
- not token.pos_ == 'SYM' and # No es s铆mbolo
218
- not token.pos_ == 'NUM' and # No es n煤mero
219
- not token.pos_ == 'X'): # No es otro
220
-
221
- # Convertir a min煤sculas y a帽adir al contador
222
- word_freq[token.lemma_.lower()] += 1
223
-
224
- # Filtrar conceptos por frecuencia m铆nima y ordenar por frecuencia
225
- concepts = [(word, freq) for word, freq in word_freq.items()
226
- if freq >= min_freq]
227
- concepts.sort(key=lambda x: x[1], reverse=True)
228
-
229
- logger.info(f"Identified {len(concepts)} key concepts after excluding entities")
230
- return concepts[:10]
231
-
232
- except Exception as e:
233
- logger.error(f"Error en identify_key_concepts: {str(e)}")
234
- return []
235
-
236
- ########################################################################
237
-
238
- def create_concept_graph(doc, key_concepts):
239
- """
240
- Crea un grafo de relaciones entre conceptos, ignorando entidades.
241
- Args:
242
- doc: Documento procesado por spaCy
243
- key_concepts: Lista de tuplas (concepto, frecuencia)
244
- Returns:
245
- nx.Graph: Grafo de conceptos
246
- """
247
- try:
248
- G = nx.Graph()
249
-
250
- # Crear un conjunto de conceptos clave para b煤squeda r谩pida
251
- concept_words = {concept[0].lower() for concept in key_concepts}
252
-
253
- # Crear conjunto de tokens que son parte de entidades
254
- entity_tokens = set()
255
- for ent in doc.ents:
256
- entity_tokens.update(token.i for token in ent)
257
-
258
- # A帽adir nodos al grafo
259
- for concept, freq in key_concepts:
260
- G.add_node(concept.lower(), weight=freq)
261
-
262
- # Analizar cada oraci贸n
263
- for sent in doc.sents:
264
- # Obtener conceptos en la oraci贸n actual, excluyendo entidades
265
- current_concepts = []
266
- for token in sent:
267
- if (token.i not in entity_tokens and
268
- token.lemma_.lower() in concept_words):
269
- current_concepts.append(token.lemma_.lower())
270
-
271
- # Crear conexiones entre conceptos en la misma oraci贸n
272
- for i, concept1 in enumerate(current_concepts):
273
- for concept2 in current_concepts[i+1:]:
274
- if concept1 != concept2:
275
- if G.has_edge(concept1, concept2):
276
- G[concept1][concept2]['weight'] += 1
277
- else:
278
- G.add_edge(concept1, concept2, weight=1)
279
-
280
- return G
281
-
282
- except Exception as e:
283
- logger.error(f"Error en create_concept_graph: {str(e)}")
284
- return nx.Graph()
285
-
286
- ###############################################################################
287
-
288
- def visualize_concept_graph(G, lang_code, semantic_t):
289
- """
290
- Visualiza el grafo de conceptos con layout consistente.
291
- Args:
292
- G: networkx.Graph - Grafo de conceptos
293
- lang_code: str - C贸digo del idioma
294
- Returns:
295
- matplotlib.figure.Figure - Figura del grafo
296
- """
297
- try:
298
- # Crear nueva figura con mayor tama帽o y definir los ejes expl铆citamente
299
- fig, ax = plt.subplots(figsize=(15, 10))
300
-
301
- if not G.nodes():
302
- logger.warning("Grafo vac铆o, retornando figura vac铆a")
303
- return fig
304
-
305
- # Convertir grafo no dirigido a dirigido para mostrar flechas
306
- DG = nx.DiGraph(G)
307
-
308
- # Calcular centralidad de los nodos para el color
309
- centrality = nx.degree_centrality(G)
310
-
311
- # Establecer semilla para reproducibilidad
312
- seed = 42
313
-
314
- # Calcular layout con par谩metros fijos
315
- pos = nx.spring_layout(
316
- DG,
317
- k=2, # Distancia ideal entre nodos
318
- iterations=50, # N煤mero de iteraciones
319
- seed=seed # Semilla fija para reproducibilidad
320
- )
321
-
322
- # Calcular factor de escala basado en n煤mero de nodos
323
- num_nodes = len(DG.nodes())
324
- scale_factor = 1000 if num_nodes < 10 else 500 if num_nodes < 20 else 200
325
-
326
- # Obtener pesos ajustados
327
- node_weights = [DG.nodes[node].get('weight', 1) * scale_factor for node in DG.nodes()]
328
- edge_weights = [DG[u][v].get('weight', 1) for u, v in DG.edges()]
329
-
330
- # Crear mapa de colores basado en centralidad
331
- node_colors = [plt.cm.viridis(centrality[node]) for node in DG.nodes()]
332
-
333
- # Dibujar nodos
334
- nodes = nx.draw_networkx_nodes(
335
- DG,
336
- pos,
337
- node_size=node_weights,
338
- node_color=node_colors,
339
- alpha=0.7,
340
- ax=ax
341
- )
342
-
343
- # Dibujar aristas con flechas
344
- edges = nx.draw_networkx_edges(
345
- DG,
346
- pos,
347
- width=edge_weights,
348
- alpha=0.6,
349
- edge_color='gray',
350
- arrows=True,
351
- arrowsize=20,
352
- arrowstyle='->',
353
- connectionstyle='arc3,rad=0.2',
354
- ax=ax
355
- )
356
-
357
- # Ajustar tama帽o de fuente seg煤n n煤mero de nodos
358
- font_size = 12 if num_nodes < 10 else 10 if num_nodes < 20 else 8
359
-
360
- # Dibujar etiquetas con fondo blanco para mejor legibilidad
361
- labels = nx.draw_networkx_labels(
362
- DG,
363
- pos,
364
- font_size=font_size,
365
- font_weight='bold',
366
- bbox=dict(
367
- facecolor='white',
368
- edgecolor='none',
369
- alpha=0.7
370
- ),
371
- ax=ax
372
- )
373
-
374
- # A帽adir leyenda de centralidad
375
- sm = plt.cm.ScalarMappable(
376
- cmap=plt.cm.viridis,
377
- norm=plt.Normalize(vmin=0, vmax=1)
378
- )
379
- sm.set_array([])
380
- plt.colorbar(sm, ax=ax, label=semantic_t.get('concept_centrality', 'Centralidad de los conceptos clave'))
381
-
382
- plt.title(semantic_t.get('concept_network', 'Relaciones entre los conceptos clave'), pad=20, fontsize=14)
383
- ax.set_axis_off()
384
-
385
- # Ajustar el layout para que la barra de color no se superponga
386
- plt.tight_layout()
387
-
388
- return fig
389
-
390
- except Exception as e:
391
- logger.error(f"Error en visualize_concept_graph: {str(e)}")
392
- return plt.figure() # Retornar figura vac铆a en caso de error
393
-
394
- ########################################################################
395
- def create_entity_graph(entities):
396
- G = nx.Graph()
397
- for entity_type, entity_list in entities.items():
398
- for entity in entity_list:
399
- G.add_node(entity, type=entity_type)
400
- for i, entity1 in enumerate(entity_list):
401
- for entity2 in entity_list[i+1:]:
402
- G.add_edge(entity1, entity2)
403
- return G
404
-
405
-
406
- #############################################################
407
- def visualize_entity_graph(G, lang_code):
408
- fig, ax = plt.subplots(figsize=(12, 8))
409
- pos = nx.spring_layout(G)
410
- for entity_type, color in ENTITY_LABELS[lang_code].items():
411
- node_list = [node for node, data in G.nodes(data=True) if data['type'] == entity_type]
412
- nx.draw_networkx_nodes(G, pos, nodelist=node_list, node_color=color, node_size=500, alpha=0.8, ax=ax)
413
- nx.draw_networkx_edges(G, pos, width=1, alpha=0.5, ax=ax)
414
- nx.draw_networkx_labels(G, pos, font_size=8, font_weight="bold", ax=ax)
415
- ax.set_title(f"Relaciones entre Entidades ({lang_code})", fontsize=16)
416
- ax.axis('off')
417
- plt.tight_layout()
418
- return fig
419
-
420
-
421
- #################################################################################
422
- def create_topic_graph(topics, doc):
423
- G = nx.Graph()
424
- for topic in topics:
425
- G.add_node(topic, weight=doc.text.count(topic))
426
- for i, topic1 in enumerate(topics):
427
- for topic2 in topics[i+1:]:
428
- weight = sum(1 for sent in doc.sents if topic1 in sent.text and topic2 in sent.text)
429
- if weight > 0:
430
- G.add_edge(topic1, topic2, weight=weight)
431
- return G
432
-
433
- def visualize_topic_graph(G, lang_code):
434
- fig, ax = plt.subplots(figsize=(12, 8))
435
- pos = nx.spring_layout(G)
436
- node_sizes = [G.nodes[node]['weight'] * 100 for node in G.nodes()]
437
- nx.draw_networkx_nodes(G, pos, node_size=node_sizes, node_color='lightgreen', alpha=0.8, ax=ax)
438
- nx.draw_networkx_labels(G, pos, font_size=10, font_weight="bold", ax=ax)
439
- edge_weights = [G[u][v]['weight'] for u, v in G.edges()]
440
- nx.draw_networkx_edges(G, pos, width=edge_weights, alpha=0.5, ax=ax)
441
- ax.set_title(f"Relaciones entre Temas ({lang_code})", fontsize=16)
442
- ax.axis('off')
443
- plt.tight_layout()
444
- return fig
445
-
446
- ###########################################################################################
447
- def generate_summary(doc, lang_code):
448
- sentences = list(doc.sents)
449
- summary = sentences[:3] # Toma las primeras 3 oraciones como resumen
450
- return " ".join([sent.text for sent in summary])
451
-
452
- def extract_entities(doc, lang_code):
453
- entities = defaultdict(list)
454
- for ent in doc.ents:
455
- if ent.label_ in ENTITY_LABELS[lang_code]:
456
- entities[ent.label_].append(ent.text)
457
- return dict(entities)
458
-
459
- def analyze_sentiment(doc, lang_code):
460
- positive_words = sum(1 for token in doc if token.sentiment > 0)
461
- negative_words = sum(1 for token in doc if token.sentiment < 0)
462
- total_words = len(doc)
463
- if positive_words > negative_words:
464
- return "Positivo"
465
- elif negative_words > positive_words:
466
- return "Negativo"
467
- else:
468
- return "Neutral"
469
-
470
- def extract_topics(doc, lang_code):
471
- vectorizer = TfidfVectorizer(stop_words='english', max_features=5)
472
- tfidf_matrix = vectorizer.fit_transform([doc.text])
473
- feature_names = vectorizer.get_feature_names_out()
474
- return list(feature_names)
475
-
476
- # Aseg煤rate de que todas las funciones necesarias est茅n exportadas
477
- __all__ = [
478
- 'perform_semantic_analysis',
479
- 'identify_key_concepts',
480
- 'create_concept_graph',
481
- 'visualize_concept_graph',
482
- 'fig_to_bytes', # Faltaba esta coma
483
- 'ENTITY_LABELS',
484
- 'POS_COLORS',
485
- 'POS_TRANSLATIONS'
486
- ]