From 57df34ac5a4b53d5e6840a03bed83816ec8f4943 Mon Sep 17 00:00:00 2001 From: sergio Date: Tue, 16 Dec 2025 00:53:27 +0100 Subject: [PATCH 1/2] deliberable_16_12_2025 --- .claudeignore | 6 + .gitignore | 7 +- README.md | 330 +- TFM_Sergio_Jimenez_OCR.docx | Bin 0 -> 105461 bytes TFM_Sergio_Jimenez_OCR.pdf | Bin 0 -> 1271464 bytes apply_content.py | 609 ++ claude.md | 543 ++ docs/00_resumen.md | 25 + docs/01_introduccion.md | 51 + docs/02_contexto_estado_arte.md | 218 + docs/03_objetivos_metodologia.md | 277 + docs/04_desarrollo_especifico.md | 566 ++ docs/05_conclusiones_trabajo_futuro.md | 113 + docs/06_referencias_bibliograficas.md | 50 + docs/07_anexo_a.md | 68 + generate_mermaid_figures.py | 113 + instructions/plantilla_individual.docx | Bin 104695 -> 106302 bytes instructions/plantilla_individual.htm | 6075 +++++++++++++++++ instructions/plantilla_individual.pdf | Bin 0 -> 215733 bytes .../colorschememapping.xml | 2 + .../plantilla_individual_files/filelist.xml | 21 + .../plantilla_individual_files/header.htm | 113 + .../plantilla_individual_files/image001.png | Bin 0 -> 10596 bytes .../plantilla_individual_files/image002.gif | Bin 0 -> 3991 bytes .../plantilla_individual_files/image003.png | Bin 0 -> 23934 bytes .../plantilla_individual_files/image004.jpg | Bin 0 -> 16869 bytes .../plantilla_individual_files/image005.png | Bin 0 -> 13490 bytes .../plantilla_individual_files/image006.gif | Bin 0 -> 25952 bytes .../plantilla_individual_files/item0001.xml | 258 + .../plantilla_individual_files/item0003.xml | 1 + .../plantilla_individual_files/item0005.xml | 1 + .../plantilla_individual_files/item0007.xml | 1 + .../plantilla_individual_files/props002.xml | 2 + .../plantilla_individual_files/props004.xml | 2 + .../plantilla_individual_files/props006.xml | 2 + .../plantilla_individual_files/props008.xml | 2 + .../plantilla_individual_files/themedata.thmx | Bin 0 -> 3149 bytes package-lock.json | 4127 +++++++++++ package.json | 5 + paddle_ocr_fine_tune_unir_raytune.ipynb | 1319 ---- src/dataset_manager.py | 45 + src/paddle_ocr_fine_tune_unir_raytune.ipynb | 2772 ++++++++ .../paddle_ocr_tuning.py | 148 +- src/prepare_dataset.ipynb | 506 ++ ...paddle_subproc_results_20251207_192320.csv | 65 + thesis_output/figures/figura_1.png | Bin 0 -> 19801 bytes thesis_output/figures/figura_2.png | Bin 0 -> 19649 bytes thesis_output/figures/figura_3.png | Bin 0 -> 15985 bytes thesis_output/figures/figura_4.png | Bin 0 -> 38109 bytes thesis_output/figures/figura_5.png | Bin 0 -> 29993 bytes thesis_output/figures/figura_6.png | Bin 0 -> 16914 bytes thesis_output/figures/figura_7.png | Bin 0 -> 17910 bytes thesis_output/figures/figura_8.png | Bin 0 -> 44517 bytes thesis_output/figures/figures_manifest.json | 42 + thesis_output/plantilla_individual.htm | Bin 0 -> 1450106 bytes .../colorschememapping.xml | 2 + .../plantilla_individual_files/filelist.xml | 15 + .../plantilla_individual_files/header.htm | Bin 0 -> 6124 bytes .../plantilla_individual_files/image001.png | Bin 0 -> 10596 bytes .../plantilla_individual_files/image002.gif | Bin 0 -> 3991 bytes .../plantilla_individual_files/image003.gif | Bin 0 -> 3991 bytes .../plantilla_individual_files/image003.png | Bin 0 -> 23934 bytes .../plantilla_individual_files/image004.jpg | Bin 0 -> 16869 bytes .../plantilla_individual_files/image005.png | Bin 0 -> 13490 bytes .../plantilla_individual_files/image006.gif | Bin 0 -> 25952 bytes .../plantilla_individual_files/item0001.xml | 258 + .../plantilla_individual_files/item0003.xml | 1 + .../plantilla_individual_files/item0005.xml | 1 + .../plantilla_individual_files/item0007.xml | 1 + .../plantilla_individual_files/item0013.xml | 258 + .../plantilla_individual_files/item0015.xml | 1 + .../plantilla_individual_files/item0017.xml | 1 + .../plantilla_individual_files/item0019.xml | 258 + .../plantilla_individual_files/item0021.xml | 1 + .../plantilla_individual_files/item0023.xml | 1 + .../plantilla_individual_files/props002.xml | 2 + .../plantilla_individual_files/props004.xml | 2 + .../plantilla_individual_files/props006.xml | 2 + .../plantilla_individual_files/props008.xml | 2 + .../plantilla_individual_files/props014.xml | 2 + .../plantilla_individual_files/props016.xml | 2 + .../plantilla_individual_files/props018.xml | 2 + .../plantilla_individual_files/props020.xml | 2 + .../plantilla_individual_files/props022.xml | 2 + .../plantilla_individual_files/props024.xml | 2 + .../plantilla_individual_files/themedata.thmx | Bin 0 -> 3149 bytes thesis_report.docx | Bin 73990 -> 0 bytes thesis_report.pdf | Bin 619622 -> 0 bytes 88 files changed, 17836 insertions(+), 1467 deletions(-) create mode 100644 .claudeignore create mode 100644 TFM_Sergio_Jimenez_OCR.docx create mode 100644 TFM_Sergio_Jimenez_OCR.pdf create mode 100644 apply_content.py create mode 100644 claude.md create mode 100644 docs/00_resumen.md create mode 100644 docs/01_introduccion.md create mode 100644 docs/02_contexto_estado_arte.md create mode 100644 docs/03_objetivos_metodologia.md create mode 100644 docs/04_desarrollo_especifico.md create mode 100644 docs/05_conclusiones_trabajo_futuro.md create mode 100644 docs/06_referencias_bibliograficas.md create mode 100644 docs/07_anexo_a.md create mode 100644 generate_mermaid_figures.py create mode 100644 instructions/plantilla_individual.htm create mode 100644 instructions/plantilla_individual.pdf create mode 100644 instructions/plantilla_individual_files/colorschememapping.xml create mode 100644 instructions/plantilla_individual_files/filelist.xml create mode 100644 instructions/plantilla_individual_files/header.htm create mode 100644 instructions/plantilla_individual_files/image001.png create mode 100644 instructions/plantilla_individual_files/image002.gif create mode 100644 instructions/plantilla_individual_files/image003.png create mode 100644 instructions/plantilla_individual_files/image004.jpg create mode 100644 instructions/plantilla_individual_files/image005.png create mode 100644 instructions/plantilla_individual_files/image006.gif create mode 100644 instructions/plantilla_individual_files/item0001.xml create mode 100644 instructions/plantilla_individual_files/item0003.xml create mode 100644 instructions/plantilla_individual_files/item0005.xml create mode 100644 instructions/plantilla_individual_files/item0007.xml create mode 100644 instructions/plantilla_individual_files/props002.xml create mode 100644 instructions/plantilla_individual_files/props004.xml create mode 100644 instructions/plantilla_individual_files/props006.xml create mode 100644 instructions/plantilla_individual_files/props008.xml create mode 100644 instructions/plantilla_individual_files/themedata.thmx create mode 100644 package-lock.json create mode 100644 package.json delete mode 100644 paddle_ocr_fine_tune_unir_raytune.ipynb create mode 100644 src/dataset_manager.py create mode 100644 src/paddle_ocr_fine_tune_unir_raytune.ipynb rename paddle_ocr_tuning.py => src/paddle_ocr_tuning.py (51%) create mode 100644 src/prepare_dataset.ipynb create mode 100644 src/raytune_paddle_subproc_results_20251207_192320.csv create mode 100644 thesis_output/figures/figura_1.png create mode 100644 thesis_output/figures/figura_2.png create mode 100644 thesis_output/figures/figura_3.png create mode 100644 thesis_output/figures/figura_4.png create mode 100644 thesis_output/figures/figura_5.png create mode 100644 thesis_output/figures/figura_6.png create mode 100644 thesis_output/figures/figura_7.png create mode 100644 thesis_output/figures/figura_8.png create mode 100644 thesis_output/figures/figures_manifest.json create mode 100644 thesis_output/plantilla_individual.htm create mode 100644 thesis_output/plantilla_individual_files/colorschememapping.xml create mode 100644 thesis_output/plantilla_individual_files/filelist.xml create mode 100644 thesis_output/plantilla_individual_files/header.htm create mode 100644 thesis_output/plantilla_individual_files/image001.png create mode 100644 thesis_output/plantilla_individual_files/image002.gif create mode 100644 thesis_output/plantilla_individual_files/image003.gif create mode 100644 thesis_output/plantilla_individual_files/image003.png create mode 100644 thesis_output/plantilla_individual_files/image004.jpg create mode 100644 thesis_output/plantilla_individual_files/image005.png create mode 100644 thesis_output/plantilla_individual_files/image006.gif create mode 100644 thesis_output/plantilla_individual_files/item0001.xml create mode 100644 thesis_output/plantilla_individual_files/item0003.xml create mode 100644 thesis_output/plantilla_individual_files/item0005.xml create mode 100644 thesis_output/plantilla_individual_files/item0007.xml create mode 100644 thesis_output/plantilla_individual_files/item0013.xml create mode 100644 thesis_output/plantilla_individual_files/item0015.xml create mode 100644 thesis_output/plantilla_individual_files/item0017.xml create mode 100644 thesis_output/plantilla_individual_files/item0019.xml create mode 100644 thesis_output/plantilla_individual_files/item0021.xml create mode 100644 thesis_output/plantilla_individual_files/item0023.xml create mode 100644 thesis_output/plantilla_individual_files/props002.xml create mode 100644 thesis_output/plantilla_individual_files/props004.xml create mode 100644 thesis_output/plantilla_individual_files/props006.xml create mode 100644 thesis_output/plantilla_individual_files/props008.xml create mode 100644 thesis_output/plantilla_individual_files/props014.xml create mode 100644 thesis_output/plantilla_individual_files/props016.xml create mode 100644 thesis_output/plantilla_individual_files/props018.xml create mode 100644 thesis_output/plantilla_individual_files/props020.xml create mode 100644 thesis_output/plantilla_individual_files/props022.xml create mode 100644 thesis_output/plantilla_individual_files/props024.xml create mode 100644 thesis_output/plantilla_individual_files/themedata.thmx delete mode 100644 thesis_report.docx delete mode 100644 thesis_report.pdf diff --git a/.claudeignore b/.claudeignore new file mode 100644 index 0000000..b3d2cc0 --- /dev/null +++ b/.claudeignore @@ -0,0 +1,6 @@ +~$*.docx +results/ +__pycache__/ +dataset +results +.DS_Store diff --git a/.gitignore b/.gitignore index 100b6f6..686d80f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ ~$*.docx results/ -__pycache__/* +__pycache__/ +dataset +results +.DS_Store +.claude +node_modules diff --git a/README.md b/README.md index 805e5a6..ac8da34 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,311 @@ -# Sistema OCR multimotor con IA para PDFs escaneados en español +# Optimización de Hiperparámetros OCR con Ray Tune para Documentos Académicos en Español -**Trabajo Fin de Máster (TFM) – Tipo 2: Desarrollo de Software** -**Líneas:** Percepción computacional · Aprendizaje automático -**Autor:** Sergio Jiménez Jiménez · **UNIR** · **Año:** 2025 +**Trabajo Fin de Máster (TFM) – Máster Universitario en Inteligencia Artificial** +**Líneas:** Percepción computacional · Aprendizaje automático +**Autor:** Sergio Jiménez Jiménez · **UNIR** · **Año:** 2025 -> Extracción de texto desde **PDFs escaneados** en **español** mediante **motores OCR basados en IA** (EasyOCR · PaddleOCR · DocTR). -> Se excluyen soluciones clásicas como **Tesseract** o propietarias como **ABBYY**, centrando el proyecto en modelos neuronales modernos. +> Optimización sistemática de hiperparámetros de **PaddleOCR (PP-OCRv5)** mediante **Ray Tune** con **Optuna** para mejorar el reconocimiento óptico de caracteres en documentos académicos en español. --- -## 🧭 Objetivo +## Objetivo -Desarrollar y evaluar un **sistema OCR multimotor** capaz de: -- Procesar PDFs escaneados extremo a extremo (**PDF → Imagen → Preprocesado → OCR → Evaluación**). -- **Reducir el CER al menos un 15 %** respecto a una línea base neuronal (EasyOCR). -- Mantener **tiempos por página** adecuados y un pipeline **modular y reproducible**. +Optimizar el rendimiento de PaddleOCR para documentos académicos en español mediante ajuste de hiperparámetros, alcanzando un **CER inferior al 2%** sin requerir fine-tuning del modelo ni recursos GPU dedicados. -**Métricas principales:** -- **CER** (*Character Error Rate*) -- **WER** (*Word Error Rate*) -- **Latencia por página* +**Resultado alcanzado:** CER = **1.49%** (objetivo cumplido) --- -## 🧩 Alcance y diseño +## Resultados Principales -- **Idioma:** español (texto impreso, no manuscrito). -- **Entrada:** PDFs escaneados con calidad variable, ruido o inclinación. -- **Motores evaluados:** - - **EasyOCR** – baseline neuronal ligera. - - **PaddleOCR (PP-OCR)** – referencia industrial multilingüe. - - **DocTR (Mindee)** – arquitectura PyTorch modular con salida estructurada. -- **Evaluación:** CER, WER y latencia promedio por página. +| Modelo | CER | Precisión Caracteres | WER | Precisión Palabras | +|--------|-----|---------------------|-----|-------------------| +| PaddleOCR (Baseline) | 7.78% | 92.22% | 14.94% | 85.06% | +| **PaddleOCR-HyperAdjust** | **1.49%** | **98.51%** | **7.62%** | **92.38%** | ---- +**Mejora obtenida:** Reducción del CER en un **80.9%** -## 🏗️ Arquitectura del sistema +### Configuración Óptima Encontrada -```text -PDF (escaneado) - └─► Conversión a imagen (PyMuPDF / pdf2image) - └─► Preprocesado (OpenCV) - └─► OCR (EasyOCR | PaddleOCR | DocTR) - └─► Evaluación (CER · WER · latencia) +```python +config_optimizada = { + "textline_orientation": True, # CRÍTICO - reduce CER ~70% + "use_doc_orientation_classify": False, + "use_doc_unwarping": False, + "text_det_thresh": 0.4690, # Correlación -0.52 con CER + "text_det_box_thresh": 0.5412, + "text_det_unclip_ratio": 0.0, + "text_rec_score_thresh": 0.6350, +} ``` -## 🔜 Próximos pasos +--- -1. Ajustar parámetros y arquitecturas en DocTR (detector y reconocedor). -2. Añadir métricas de latencia. -3. Incorporar postprocesamiento lingüístico (corrección ortográfica). -4. Explorar TrOCR o MMOCR como comparación avanzada en la segunda fase. +## Metodología + +### Pipeline de Trabajo + +``` +PDF (académico UNIR) + └─► Conversión a imagen (PyMuPDF, 300 DPI) + └─► Extracción de ground truth + └─► OCR con PaddleOCR (PP-OCRv5) + └─► Evaluación (CER, WER con jiwer) + └─► Optimización (Ray Tune + Optuna) +``` + +### Experimento de Optimización + +| Parámetro | Valor | +|-----------|-------| +| Número de trials | 64 | +| Algoritmo de búsqueda | OptunaSearch (TPE) | +| Métrica objetivo | CER (minimizar) | +| Trials concurrentes | 2 | +| Tiempo total | ~6 horas (CPU) | + +--- + +## Estructura del Repositorio + +``` +MastersThesis/ +├── docs/ # Capítulos del TFM en Markdown (estructura UNIR) +│ ├── 00_resumen.md # Resumen + Abstract + Keywords +│ ├── 01_introduccion.md # Cap. 1: Introducción (1.1-1.3) +│ ├── 02_contexto_estado_arte.md # Cap. 2: Contexto y estado del arte (2.1-2.3) +│ ├── 03_objetivos_metodologia.md # Cap. 3: Objetivos y metodología (3.1-3.4) +│ ├── 04_desarrollo_especifico.md # Cap. 4: Desarrollo específico (4.1-4.3) +│ ├── 05_conclusiones_trabajo_futuro.md # Cap. 5: Conclusiones (5.1-5.2) +│ ├── 06_referencias_bibliograficas.md # Referencias bibliográficas (APA) +│ └── 07_anexo_a.md # Anexo A: Código fuente y datos +├── thesis_output/ # Documento final generado +│ ├── plantilla_individual.htm # TFM completo (abrir en Word) +│ └── figures/ # Figuras generadas desde Mermaid +│ ├── figura_1.png ... figura_7.png +│ └── figures_manifest.json +├── src/ +│ ├── paddle_ocr_fine_tune_unir_raytune.ipynb # Experimento principal +│ ├── paddle_ocr_tuning.py # Script de evaluación CLI +│ ├── dataset_manager.py # Clase ImageTextDataset +│ ├── prepare_dataset.ipynb # Preparación del dataset +│ └── raytune_paddle_subproc_results_*.csv # Resultados de 64 trials +├── results/ # Resultados de benchmarks +├── instructions/ # Plantilla e instrucciones UNIR +│ ├── instrucciones.pdf +│ ├── plantilla_individual.pdf +│ └── plantilla_individual.htm +├── apply_content.py # Genera documento TFM desde docs/ + plantilla +├── generate_mermaid_figures.py # Convierte diagramas Mermaid a PNG +├── ocr_benchmark_notebook.ipynb # Benchmark comparativo inicial +└── README.md +``` + +--- + +## Hallazgos Clave + +1. **`textline_orientation=True` es crítico**: Reduce el CER en un 69.7%. Para documentos con layouts mixtos (tablas, encabezados), la clasificación de orientación de línea es esencial. + +2. **Umbral `text_det_thresh` importante**: Correlación -0.52 con CER. Valores óptimos entre 0.4-0.5. Valores < 0.1 causan fallos catastróficos (CER >40%). + +3. **Componentes innecesarios para PDFs digitales**: `use_doc_orientation_classify` y `use_doc_unwarping` no mejoran el rendimiento en documentos académicos digitales. + +--- + +## Requisitos + +| Componente | Versión | +|------------|---------| +| Python | 3.11.9 | +| PaddlePaddle | 3.2.2 | +| PaddleOCR | 3.3.2 | +| Ray | 2.52.1 | +| Optuna | 4.6.0 | +| jiwer | (para métricas CER/WER) | +| PyMuPDF | (para conversión PDF) | + +--- + +## Uso + +### Preparar dataset +```bash +# Ejecutar prepare_dataset.ipynb para convertir PDF a imágenes y extraer ground truth +jupyter notebook src/prepare_dataset.ipynb +``` + +### Ejecutar optimización +```bash +# Ejecutar el notebook principal de Ray Tune +jupyter notebook src/paddle_ocr_fine_tune_unir_raytune.ipynb +``` + +### Evaluación individual +```bash +python src/paddle_ocr_tuning.py \ + --pdf-folder ./dataset \ + --textline-orientation True \ + --text-det-thresh 0.469 \ + --text-det-box-thresh 0.541 \ + --text-rec-score-thresh 0.635 +``` + +--- + +## Fuentes de Datos + +- **Dataset**: Instrucciones para la elaboración del TFE (UNIR), 24 páginas +- **Resultados Ray Tune (PRINCIPAL)**: `src/raytune_paddle_subproc_results_20251207_192320.csv` - 64 trials de optimización con todas las métricas y configuraciones + +--- + +## Generación del Documento TFM + +### Prerrequisitos + +```bash +# Instalar dependencias de Python +pip install beautifulsoup4 + +# Instalar mermaid-cli para generación de figuras +npm install @mermaid-js/mermaid-cli +``` + +### Flujo de Generación del Documento + +El documento TFM se genera en **3 pasos** que deben ejecutarse en orden: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ PASO 1: generate_mermaid_figures.py │ +│ ────────────────────────────────────────────────────────────────── │ +│ • Lee diagramas Mermaid de docs/*.md │ +│ • Genera thesis_output/figures/figura_*.png │ +│ • Crea figures_manifest.json con títulos │ +└─────────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────────┐ +│ PASO 2: apply_content.py │ +│ ────────────────────────────────────────────────────────────────── │ +│ • Lee plantilla desde instructions/plantilla_individual.htm │ +│ • Inserta contenido de docs/*.md en cada capítulo │ +│ • Genera tablas con formato APA y figuras con referencias │ +│ • Guarda en thesis_output/plantilla_individual.htm │ +└─────────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────────┐ +│ PASO 3: Abrir en Microsoft Word │ +│ ────────────────────────────────────────────────────────────────── │ +│ • Abrir thesis_output/plantilla_individual.htm │ +│ • Ctrl+A → F9 para actualizar índices (contenidos/figuras/tablas) │ +│ • Guardar como TFM_Sergio_Jimenez.docx │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### Comandos de Generación + +```bash +# Desde el directorio raíz del proyecto: + +# PASO 1: Generar figuras PNG desde diagramas Mermaid +python3 generate_mermaid_figures.py +# Output: thesis_output/figures/figura_1.png ... figura_8.png + +# PASO 2: Aplicar contenido de docs/ a la plantilla UNIR +python3 apply_content.py +# Output: thesis_output/plantilla_individual.htm + +# PASO 3: Abrir en Word y finalizar documento +# - Abrir thesis_output/plantilla_individual.htm en Microsoft Word +# - Ctrl+A → F9 para actualizar todos los índices +# - IMPORTANTE: Ajustar manualmente el tamaño de las imágenes para legibilidad +# (seleccionar imagen → clic derecho → Tamaño y posición → ajustar al ancho de página) +# - Guardar como .docx +``` + +### Notas Importantes para Edición en Word + +1. **Ajuste de imágenes**: Las figuras Mermaid pueden requerir ajuste manual de tamaño para ser legibles. Seleccionar cada imagen y ajustar al ancho de texto (~16cm). + +2. **Actualización de índices**: Después de cualquier cambio, usar Ctrl+A → F9 para regenerar índices. + +3. **Formato de código**: Los bloques de código usan Consolas 9pt. Verificar que no se corten líneas largas. + +### Archivos de Entrada y Salida + +| Script | Entrada | Salida | +|--------|---------|--------| +| `generate_mermaid_figures.py` | `docs/*.md` (bloques ```mermaid```) | `thesis_output/figures/figura_*.png`, `figures_manifest.json` | +| `apply_content.py` | `instructions/plantilla_individual.htm`, `docs/*.md`, `thesis_output/figures/*.png` | `thesis_output/plantilla_individual.htm` | + +### Contenido Generado Automáticamente + +- **30 tablas** con formato APA (Tabla X. *Título* + Fuente: ...) +- **8 figuras** desde Mermaid (Figura X. *Título* + Fuente: Elaboración propia) +- **25 referencias** en formato APA con sangría francesa +- **Resumen/Abstract** con palabras clave +- **Índices** actualizables (contenidos, figuras, tablas) +- Eliminación automática de textos de instrucción de la plantilla + +--- + +## Trabajo Pendiente para Completar el TFM + +### Contexto: Limitaciones de Hardware + +Este trabajo adoptó la estrategia de **optimización de hiperparámetros** en lugar de **fine-tuning** debido a: +- **Sin GPU dedicada**: Ejecución exclusivamente en CPU +- **Tiempo de inferencia elevado**: ~69 segundos/página en CPU +- **Fine-tuning inviable**: Entrenar modelos de deep learning sin GPU requeriría tiempos prohibitivos + +La optimización de hiperparámetros demostró ser una **alternativa efectiva** al fine-tuning, logrando una reducción del 80.9% en el CER sin reentrenar el modelo. + +### Tareas Completadas + +- [x] **Estructura docs/ según plantilla UNIR**: Todos los capítulos siguen numeración exacta (1.1, 1.2, etc.) +- [x] **Añadir diagramas Mermaid**: 7 diagramas añadidos (pipeline OCR, arquitectura Ray Tune, gráficos de comparación) +- [x] **Generar documento TFM unificado**: Script `apply_content.py` genera documento completo desde docs/ +- [x] **Convertir Mermaid a PNG**: Script `generate_mermaid_figures.py` genera figuras automáticamente + +### Tareas Pendientes + +#### 1. Validación del Enfoque (Prioridad Alta) +- [ ] **Validación cruzada en otros documentos**: Evaluar la configuración óptima en otros tipos de documentos en español (facturas, formularios, contratos) para verificar generalización +- [ ] **Ampliar el dataset**: El dataset actual tiene solo 24 páginas. Construir un corpus más amplio y diverso (mínimo 100 páginas) +- [ ] **Validación del ground truth**: Revisar manualmente el texto de referencia extraído automáticamente para asegurar su exactitud + +#### 2. Experimentación Adicional (Prioridad Media) +- [ ] **Explorar `text_det_unclip_ratio`**: Este parámetro quedó fijado en 0.0. Incluirlo en el espacio de búsqueda podría mejorar resultados +- [ ] **Comparativa con fine-tuning** (si se obtiene acceso a GPU): Cuantificar la brecha de rendimiento entre optimización de hiperparámetros y fine-tuning real +- [ ] **Evaluación con GPU**: Medir tiempos de inferencia con aceleración GPU para escenarios de producción + +#### 3. Documentación y Presentación (Prioridad Alta) +- [ ] **Crear presentación**: Preparar slides para la defensa del TFM +- [ ] **Revisión final del documento**: Verificar formato, índices y contenido en Word + +#### 4. Extensiones Futuras (Opcional) +- [ ] **Herramienta de configuración automática**: Desarrollar una herramienta que determine automáticamente la configuración óptima para un nuevo tipo de documento +- [ ] **Benchmark público para español**: Publicar un benchmark de OCR para documentos en español que facilite comparación de soluciones +- [ ] **Optimización multi-objetivo**: Considerar CER, WER y tiempo de inferencia simultáneamente + +### Recomendación de Próximos Pasos + +1. **Inmediato**: Abrir documento generado en Word, actualizar índices (Ctrl+A, F9), guardar como .docx +2. **Corto plazo**: Validar en 2-3 tipos de documentos adicionales para demostrar generalización +3. **Para la defensa**: Crear presentación con visualizaciones de resultados + +--- + +## Licencia + +Este proyecto es parte de un Trabajo Fin de Máster académico. + +--- + +## Referencias + +- [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) +- [Ray Tune](https://docs.ray.io/en/latest/tune/index.html) +- [Optuna](https://optuna.org/) +- [jiwer](https://github.com/jitsi/jiwer) diff --git a/TFM_Sergio_Jimenez_OCR.docx b/TFM_Sergio_Jimenez_OCR.docx new file mode 100644 index 0000000000000000000000000000000000000000..fdb3bdac25c9134b67ce33c32e3ee82a6c687380 GIT binary patch literal 105461 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