LaTex ecuations
Some checks failed
build_docker / essential (push) Successful in 1s
build_docker / build_paddle_ocr (push) Has started running
build_docker / build_doctr_gpu (push) Has been cancelled
build_docker / build_easyocr (push) Has been cancelled
build_docker / build_easyocr_gpu (push) Has been cancelled
build_docker / build_doctr (push) Has been cancelled
build_docker / build_raytune (push) Has been cancelled
build_docker / build_paddle_ocr_gpu (push) Has been cancelled

This commit is contained in:
2026-02-04 21:07:27 +01:00
parent e9c937a042
commit b91e31e173
9 changed files with 157 additions and 108 deletions

View File

@@ -404,30 +404,36 @@ def main():
print(" ✓ Removed template table example") print(" ✓ Removed template table example")
break break
# Define chapters # Define chapters with their number for bookmark creation
chapters = [ chapters = [
('Introducción', 'intro', 'Contexto'), ('Introducción', 'intro', 'Contexto', 1),
('Contexto', 'contexto', 'Objetivos'), ('Contexto', 'contexto', 'Objetivos', 2),
('Objetivos', 'objetivos', 'Desarrollo'), ('Objetivos', 'objetivos', 'Desarrollo', 3),
('Desarrollo', 'desarrollo', 'Conclusiones'), ('Desarrollo', 'desarrollo', 'Conclusiones', 4),
('Conclusiones', 'conclusiones', 'Referencias'), ('Conclusiones', 'conclusiones', 'Referencias', 5),
] ]
print("Replacing chapter contents...") print("Replacing chapter contents...")
for chapter_keyword, doc_key, next_keyword in chapters: for chapter_keyword, doc_key, next_keyword, chapter_num in chapters:
print(f" Processing: {chapter_keyword}") print(f" Processing: {chapter_keyword}")
start_elem = find_section_element(soup, chapter_keyword) start_elem = find_section_element(soup, chapter_keyword)
end_elem = find_section_element(soup, next_keyword) end_elem = find_section_element(soup, next_keyword)
if start_elem and end_elem: if start_elem and end_elem:
# Add bookmark anchor for chapter cross-references (e.g., _Ref_Sec2 for Chapter 2)
bookmark_id = f"_Ref_Sec{chapter_num}"
bookmark_anchor = soup.new_tag('a')
bookmark_anchor['name'] = bookmark_id
start_elem.insert(0, bookmark_anchor)
remove_elements_between(start_elem, end_elem) remove_elements_between(start_elem, end_elem)
new_content_html, counters = extract_section_content(docs[doc_key], counters=counters) new_content_html, counters = extract_section_content(docs[doc_key], counters=counters)
new_soup = BeautifulSoup(new_content_html, 'html.parser') new_soup = BeautifulSoup(new_content_html, 'html.parser')
insert_point = start_elem insert_point = start_elem
for new_elem in reversed(list(new_soup.children)): for new_elem in reversed(list(new_soup.children)):
insert_point.insert_after(new_elem) insert_point.insert_after(new_elem)
print(f" ✓ Replaced content") print(f" ✓ Replaced content (bookmark: {bookmark_id})")
else: else:
if not start_elem: if not start_elem:
print(f" Warning: Could not find start element for {chapter_keyword}") print(f" Warning: Could not find start element for {chapter_keyword}")

View File

@@ -403,7 +403,7 @@ def handle_bullet_list(lines, i):
def handle_numbered_list(lines, i): def handle_numbered_list(lines, i):
"""Handle numbered list (1., 2., etc). """Handle numbered list (1., 2., etc) with nested bullet sub-lists.
Args: Args:
lines: List of markdown lines lines: List of markdown lines
@@ -413,6 +413,8 @@ def handle_numbered_list(lines, i):
Tuple of (html_blocks, new_index) Tuple of (html_blocks, new_index)
""" """
html_blocks = [] html_blocks = []
# Each item is a tuple: (main_text, nested_bullets)
# where nested_bullets is a list of bullet point strings
numbered_items = [] numbered_items = []
while i < len(lines): while i < len(lines):
@@ -423,8 +425,8 @@ def handle_numbered_list(lines, i):
if i < len(lines) and re.match(r'^\d+\.\s', lines[i]): if i < len(lines) and re.match(r'^\d+\.\s', lines[i]):
item_text = re.sub(r'^\d+\.\s*', '', lines[i]).strip() item_text = re.sub(r'^\d+\.\s*', '', lines[i]).strip()
i += 1 i += 1
# Collect any nested/indented content # Collect any nested/indented content (bullet points)
nested_lines = [] nested_bullets = []
while i < len(lines): while i < len(lines):
current = lines[i] current = lines[i]
# Stop conditions # Stop conditions
@@ -436,34 +438,53 @@ def handle_numbered_list(lines, i):
break break
if current.startswith('**Tabla') or current.startswith('**Figura'): if current.startswith('**Tabla') or current.startswith('**Figura'):
break break
if current.strip() and not current.startswith(' ') and not current.startswith('\t') and not current.startswith('-'): # Check for non-indented, non-bullet content (end of nested)
if nested_lines or not current.strip(): stripped = current.strip()
if stripped and not current.startswith(' ') and not current.startswith('\t') and not stripped.startswith('-'):
break break
if current.strip(): # Collect indented bullet points
cleaned = current.strip() if stripped.startswith('- '):
if cleaned.startswith('- '): bullet_text = stripped[2:].strip()
cleaned = cleaned[2:] nested_bullets.append(bullet_text)
nested_lines.append(cleaned)
i += 1 i += 1
# Combine item with nested content
if nested_lines:
item_text = item_text + '<br/>' + '<br/>'.join(nested_lines)
item_text = convert_latex_formulas(item_text) item_text = convert_latex_formulas(item_text)
numbered_items.append(md_to_html_para(item_text)) numbered_items.append((md_to_html_para(item_text), nested_bullets))
else: else:
break break
# Output with proper First/Middle/Last classes # Output numbered items with nested bullet lists
for idx, item in enumerate(numbered_items): for idx, (item_text, nested_bullets) in enumerate(numbered_items):
num = idx + 1 num = idx + 1
if len(numbered_items) == 1: if len(numbered_items) == 1:
cls = 'MsoListParagraph' cls = 'MsoListParagraph'
elif idx == 0: elif idx == 0:
cls = 'MsoListParagraphCxSpFirst' cls = 'MsoListParagraphCxSpFirst'
elif idx == len(numbered_items) - 1: elif idx == len(numbered_items) - 1 and not nested_bullets:
cls = 'MsoListParagraphCxSpLast' cls = 'MsoListParagraphCxSpLast'
else: else:
cls = 'MsoListParagraphCxSpMiddle' cls = 'MsoListParagraphCxSpMiddle'
html_blocks.append(f'<p class={cls} style="margin-left:36pt;text-indent:-18pt"><span lang=ES>{num}.<span style="font-size:7pt">&nbsp;&nbsp;&nbsp;</span>{item}</span></p>')
# Main numbered item
html_blocks.append(f'<p class={cls} style="margin-left:36pt;text-indent:-18pt"><span lang=ES>{num}.<span style="font-size:7pt">&nbsp;&nbsp;&nbsp;</span>{item_text}</span></p>')
# Nested bullet sub-list (indented further)
if nested_bullets:
for bullet_idx, bullet_text in enumerate(nested_bullets):
bullet_text = convert_latex_formulas(bullet_text)
bullet_html = md_to_html_para(bullet_text)
# Determine class for sub-list items
if bullet_idx == 0:
sub_cls = 'MsoListParagraphCxSpFirst'
elif bullet_idx == len(nested_bullets) - 1:
# If this is the last bullet of the last numbered item, use Last
if idx == len(numbered_items) - 1:
sub_cls = 'MsoListParagraphCxSpLast'
else:
sub_cls = 'MsoListParagraphCxSpLast'
else:
sub_cls = 'MsoListParagraphCxSpMiddle'
# Nested bullets at 54pt margin (36pt + 18pt)
html_blocks.append(f'<p class={sub_cls} style="margin-left:54pt;text-indent:-18pt"><span lang=ES style="font-family:Symbol">·</span><span lang=ES style="font-size:7pt">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span lang=ES>{bullet_html}</span></p>')
return html_blocks, i return html_blocks, i

View File

@@ -16,7 +16,7 @@ El OCR actúa como puente entre el mundo físico del documento impreso y el mund
El procesamiento de documentos en español presenta particularidades que complican el reconocimiento automático de texto. Los caracteres especiales propios del idioma (la letra ñ, las vocales acentuadas á, é, í, ó, ú, la diéresis ü, y los signos de puntuación invertidos ¿, ¡) no están presentes en muchos conjuntos de entrenamiento internacionales, lo que puede degradar el rendimiento de modelos preentrenados predominantemente en inglés. El procesamiento de documentos en español presenta particularidades que complican el reconocimiento automático de texto. Los caracteres especiales propios del idioma (la letra ñ, las vocales acentuadas á, é, í, ó, ú, la diéresis ü, y los signos de puntuación invertidos ¿, ¡) no están presentes en muchos conjuntos de entrenamiento internacionales, lo que puede degradar el rendimiento de modelos preentrenados predominantemente en inglés.
La Tabla 1 resume los principales desafíos lingüísticos del OCR en español: La [Tabla 1](#tabla-1) resume los principales desafíos lingüísticos del OCR en español:
**Tabla 1.** *Desafíos lingüísticos específicos del OCR en español.* **Tabla 1.** *Desafíos lingüísticos específicos del OCR en español.*
@@ -37,7 +37,7 @@ Los modelos OCR basados en redes neuronales profundas, como los empleados en Pad
La adaptación de modelos preentrenados a dominios específicos típicamente requiere fine-tuning con datos etiquetados del dominio objetivo y recursos computacionales significativos. Esta barrera técnica y económica excluye a muchos investigadores y organizaciones de beneficiarse plenamente de estas tecnologías. La adaptación de modelos preentrenados a dominios específicos típicamente requiere fine-tuning con datos etiquetados del dominio objetivo y recursos computacionales significativos. Esta barrera técnica y económica excluye a muchos investigadores y organizaciones de beneficiarse plenamente de estas tecnologías.
La Tabla 2 ilustra los requisitos típicos para diferentes estrategias de mejora de OCR: La [Tabla 2](#tabla-2) ilustra los requisitos típicos para diferentes estrategias de mejora de OCR:
**Tabla 2.** *Comparación de estrategias de mejora de modelos OCR.* **Tabla 2.** *Comparación de estrategias de mejora de modelos OCR.*
@@ -118,10 +118,10 @@ La relevancia de este problema radica en su aplicabilidad inmediata. Una metodol
## Estructura del trabajo ## Estructura del trabajo
El documento sigue una estructura que refleja el proceso investigador. Tras esta introducción, el **Capítulo 2** sitúa el trabajo en su contexto técnico y revisa las tecnologías OCR basadas en aprendizaje profundo. Se describen las arquitecturas de detección, los modelos de reconocimiento y los trabajos previos en optimización de estos sistemas. El documento sigue una estructura que refleja el proceso investigador. Tras esta introducción, el [Capítulo 2](#capitulo-2) sitúa el trabajo en su contexto técnico y revisa las tecnologías OCR basadas en aprendizaje profundo. Se describen las arquitecturas de detección, los modelos de reconocimiento y los trabajos previos en optimización de estos sistemas.
El **Capítulo 3** traduce las preguntas de investigación en objetivos concretos, siguiendo la metodología SMART. Además, describe con detalle el enfoque experimental: preparación del dataset, métricas de evaluación y configuración del proceso de optimización con Ray Tune y Optuna. El [Capítulo 3](#capitulo-3) traduce las preguntas de investigación en objetivos concretos, siguiendo la metodología SMART. Además, describe con detalle el enfoque experimental: preparación del dataset, métricas de evaluación y configuración del proceso de optimización con Ray Tune y Optuna.
El núcleo del trabajo se desarrolla en el **Capítulo 4**, que presenta el estudio comparativo y la optimización de hiperparámetros en tres fases: planteamiento de la comparativa con evaluación de EasyOCR, PaddleOCR y DocTR; desarrollo de la optimización mediante 64 trials con Ray Tune; y análisis crítico de los resultados obtenidos. El núcleo del trabajo se desarrolla en el [Capítulo 4](#capitulo-4), que presenta el estudio comparativo y la optimización de hiperparámetros en tres fases: planteamiento de la comparativa con evaluación de EasyOCR, PaddleOCR y DocTR; desarrollo de la optimización mediante 64 trials con Ray Tune; y análisis crítico de los resultados obtenidos.
Finalmente, el **Capítulo 5** sintetiza las contribuciones, evalúa el grado de cumplimiento de los objetivos y propone líneas de trabajo futuro. Los **Anexos** proporcionan acceso al repositorio de código fuente y datos, así como tablas detalladas de resultados experimentales. Finalmente, el [Capítulo 5](#capitulo-5) sintetiza las contribuciones, evalúa el grado de cumplimiento de los objetivos y propone líneas de trabajo futuro. Los Anexos proporcionan acceso al repositorio de código fuente y datos, así como tablas detalladas de resultados experimentales.

View File

@@ -171,7 +171,7 @@ El espacio de búsqueda se definió utilizando `tune.choice()` para parámetros
#### Arquitectura de Ejecución #### Arquitectura de Ejecución
Se implementó una arquitectura basada en contenedores Docker para aislar los servicios OCR y facilitar la reproducibilidad (ver sección 4.2.3 para detalles de la arquitectura). Se implementó una arquitectura basada en contenedores Docker para aislar los servicios OCR y facilitar la reproducibilidad (ver Desarrollo de la comparativa en el [Capítulo 4](#capitulo-4) para detalles de la arquitectura).
#### Ejecución con Docker Compose #### Ejecución con Docker Compose

View File

@@ -1130,7 +1130,7 @@ Para documentos académicos en español similares a los evaluados:
**Fuente:** [`src/results/raytune_paddle_results_20260119_122609.csv`](https://seryus.ddns.net/unir/MastersThesis/src/branch/main/src/results/raytune_paddle_results_20260119_122609.csv). **Fuente:** [`src/results/raytune_paddle_results_20260119_122609.csv`](https://seryus.ddns.net/unir/MastersThesis/src/branch/main/src/results/raytune_paddle_results_20260119_122609.csv).
#### Cuándo Aplicar Esta Metodología #### ¿Cuándo aplicar esta metodología?
La optimización de hiperparámetros es recomendable cuando: La optimización de hiperparámetros es recomendable cuando:
@@ -1144,7 +1144,7 @@ La optimización de hiperparámetros es recomendable cuando:
5. **Sin datos de entrenamiento**: No se dispone de datasets etiquetados para fine-tuning. 5. **Sin datos de entrenamiento**: No se dispone de datasets etiquetados para fine-tuning.
#### Cuándo NO Aplicar Esta Metodología #### ¿Cuándo no aplicar esta metodología?
La optimización de hiperparámetros puede ser insuficiente cuando: La optimización de hiperparámetros puede ser insuficiente cuando:
@@ -1262,4 +1262,4 @@ PaddleOCR ofrece dos variantes de modelos: Mobile (optimizados para dispositivos
Los modelos Server, a pesar de ofrecer potencialmente mayor precisión, resultan inviables en hardware con VRAM limitada (≤6 GB) debido a errores de memoria (Out of Memory). Los modelos Mobile, con un consumo de memoria 88 veces menor, funcionan de manera estable y ofrecen rendimiento suficiente para el caso de uso evaluado. Los modelos Server, a pesar de ofrecer potencialmente mayor precisión, resultan inviables en hardware con VRAM limitada (≤6 GB) debido a errores de memoria (Out of Memory). Los modelos Mobile, con un consumo de memoria 88 veces menor, funcionan de manera estable y ofrecen rendimiento suficiente para el caso de uso evaluado.
La validación con aceleración GPU demuestra que la configuración optimizada mediante Ray Tune mejora la precisión (CER: 8.85% → 7.72% en dataset completo, 0.79% en mejor trial individual) y, combinada con la aceleración de 82x proporcionada por GPU, resulta prácticamente aplicable en escenarios de producción real. Las conclusiones derivadas de esta validación se presentan en el Capítulo 5. La validación con aceleración GPU demuestra que la configuración optimizada mediante Ray Tune mejora la precisión (CER: 8.85% → 7.72% en dataset completo, 0.79% en mejor trial individual) y, combinada con la aceleración de 82x proporcionada por GPU, resulta prácticamente aplicable en escenarios de producción real. Las conclusiones derivadas de esta validación se presentan en el [Capítulo 5](#capitulo-5).

View File

@@ -14,7 +14,7 @@ El objetivo principal del trabajo era alcanzar un CER inferior al 2% en document
**Fuente:** [`docs/metrics/metrics_paddle.md`](https://seryus.ddns.net/unir/MastersThesis/src/branch/main/docs/metrics/metrics_paddle.md). **Fuente:** [`docs/metrics/metrics_paddle.md`](https://seryus.ddns.net/unir/MastersThesis/src/branch/main/docs/metrics/metrics_paddle.md).
> **Nota:** El objetivo de CER < 2% se cumple en el mejor trial individual (0.79%, 5 páginas). La validación sobre el conjunto de datos completo (45 páginas) muestra un CER de 7.72%, evidenciando sobreajuste al subconjunto de optimización. Esta diferencia se analiza en detalle en el Capítulo 4. > **Nota:** El objetivo de CER < 2% se cumple en el mejor trial individual (0.79%, 5 páginas). La validación sobre el conjunto de datos completo (45 páginas) muestra un CER de 7.72%, evidenciando sobreajuste al subconjunto de optimización. Esta diferencia se analiza en detalle en el [Capítulo 4](#capitulo-4).
### Cumplimiento de los Objetivos Específicos ### Cumplimiento de los Objetivos Específicos

View File

@@ -247,7 +247,7 @@ Se ejecutaron 64 trials por servicio utilizando Ray Tune con Optuna sobre las p
**Fuente:** [`src/results/raytune_paddle_results_20260119_122609.csv`](https://seryus.ddns.net/unir/MastersThesis/src/branch/main/src/results/raytune_paddle_results_20260119_122609.csv). **Fuente:** [`src/results/raytune_paddle_results_20260119_122609.csv`](https://seryus.ddns.net/unir/MastersThesis/src/branch/main/src/results/raytune_paddle_results_20260119_122609.csv).
> **Nota:** Ver [Figura 15](#figura-15) en el Capítulo 4 para la representación gráfica de esta distribución. > **Nota:** Ver [Figura 15](#figura-15) en el [Capítulo 4](#capitulo-4) para la representación gráfica de esta distribución.
### Configuración Óptima PaddleOCR ### Configuración Óptima PaddleOCR
@@ -282,7 +282,7 @@ La siguiente configuración logró el mejor rendimiento en el ajuste de hiperpar
**Fuente:** Datos de tiempo CPU de [`src/raytune_paddle_subproc_results_20251207_192320.csv`](https://seryus.ddns.net/unir/MastersThesis/src/branch/main/src/raytune_paddle_subproc_results_20251207_192320.csv) y tiempos de GPU en trials de ajuste. Elaboración propia. **Fuente:** Datos de tiempo CPU de [`src/raytune_paddle_subproc_results_20251207_192320.csv`](https://seryus.ddns.net/unir/MastersThesis/src/branch/main/src/raytune_paddle_subproc_results_20251207_192320.csv) y tiempos de GPU en trials de ajuste. Elaboración propia.
> **Nota:** Ver [Figura 20](#figura-20) en el Capítulo 4 para la representación gráfica de esta comparación. > **Nota:** Ver [Figura 20](#figura-20) en el [Capítulo 4](#capitulo-4) para la representación gráfica de esta comparación.
### Análisis de Errores por Servicio ### Análisis de Errores por Servicio

View File

@@ -92,25 +92,23 @@ def md_to_html_para(text):
def convert_latex_formulas(text): def convert_latex_formulas(text):
"""Convert LaTeX formulas to MathML for Word compatibility.""" """Convert LaTeX formulas to styled text for easy copy-paste into Word equation editor.
# Block formulas $$...$$
Word's equation editor accepts LaTeX directly, so we preserve the LaTeX code
in a visually distinct format that users can copy and paste.
"""
# Block formulas $$...$$ - center and style as equation placeholder
def convert_block(match): def convert_block(match):
latex = match.group(1) latex = match.group(1).strip()
try: # Style as centered, monospace text that's easy to identify and copy
mathml = latex_to_mathml(latex, display="block") return f'<p class=MsoNormal style="text-align:center;background:#f5f5f5;padding:8pt;margin:6pt 40pt;font-family:Consolas;font-size:10pt"><span lang=ES>{latex}</span></p>'
return f'<p class=MsoNormal style="text-align:center">{mathml}</p>'
except:
return match.group(0) # Keep original if conversion fails
text = re.sub(r'\$\$([^$]+)\$\$', convert_block, text) text = re.sub(r'\$\$([^$]+)\$\$', convert_block, text)
# Inline formulas $...$ # Inline formulas $...$ - style as inline code
def convert_inline(match): def convert_inline(match):
latex = match.group(1) latex = match.group(1).strip()
try: return f'<span style="font-family:Consolas;font-size:10pt;background:#f5f5f5;padding:1pt 3pt">{latex}</span>'
return latex_to_mathml(latex, display="inline")
except:
return match.group(0)
text = re.sub(r'\$([^$]+)\$', convert_inline, text) text = re.sub(r'\$([^$]+)\$', convert_inline, text)
return text return text

File diff suppressed because one or more lines are too long