Mirae Kang commited on
Commit ·
a938849
1
Parent(s): 52b0ede
docs: add technical reports, #16
Browse files- README.es.md +6 -0
- README.md +8 -0
- docs/RESULTS.es.md +2 -1
- docs/RESULTS.md +2 -1
- reports/final_report.es.md +217 -0
- reports/final_report.md +232 -0
README.es.md
CHANGED
|
@@ -140,6 +140,11 @@ Gráficos EDA: `reports/v2/`.
|
|
| 140 |
|
| 141 |
---
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
## Comparativa de modelos
|
| 144 |
|
| 145 |
Tabla canónica: [`reports/summary.csv`](reports/summary.csv)
|
|
@@ -169,3 +174,4 @@ pytest tests/ -v
|
|
| 169 |
| [docs/PIPELINE.es.md](docs/PIPELINE.es.md) | [docs/PIPELINE.md](docs/PIPELINE.md) |
|
| 170 |
| [docs/ARCHITECTURE.es.md](docs/ARCHITECTURE.es.md) | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
| 171 |
| [docs/RESULTS.es.md](docs/RESULTS.es.md) | [docs/RESULTS.md](docs/RESULTS.md) |
|
|
|
|
|
|
| 140 |
|
| 141 |
---
|
| 142 |
|
| 143 |
+
## Informe técnico de resultados
|
| 144 |
+
|
| 145 |
+
- **Español:** [reports/final_report.es.md](reports/final_report.es.md)
|
| 146 |
+
- **English:** [reports/final_report.md](reports/final_report.md)
|
| 147 |
+
|
| 148 |
## Comparativa de modelos
|
| 149 |
|
| 150 |
Tabla canónica: [`reports/summary.csv`](reports/summary.csv)
|
|
|
|
| 174 |
| [docs/PIPELINE.es.md](docs/PIPELINE.es.md) | [docs/PIPELINE.md](docs/PIPELINE.md) |
|
| 175 |
| [docs/ARCHITECTURE.es.md](docs/ARCHITECTURE.es.md) | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
|
| 176 |
| [docs/RESULTS.es.md](docs/RESULTS.es.md) | [docs/RESULTS.md](docs/RESULTS.md) |
|
| 177 |
+
| [reports/final_report.es.md](reports/final_report.es.md) | [reports/final_report.md](reports/final_report.md) |
|
README.md
CHANGED
|
@@ -203,6 +203,13 @@ Plots and EDA: `reports/v2/`. Per-run artifacts: `reports/pipeline/{lr,rf,xgboos
|
|
| 203 |
|
| 204 |
---
|
| 205 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
## Model comparison
|
| 207 |
|
| 208 |
Canonical table: [`reports/summary.csv`](reports/summary.csv)
|
|
@@ -237,3 +244,4 @@ Covers preprocessor, vectorizer, model binary output, and `/predict` response sh
|
|
| 237 |
| [docs/PIPELINE.md](docs/PIPELINE.md) | [docs/PIPELINE.es.md](docs/PIPELINE.es.md) |
|
| 238 |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | [docs/ARCHITECTURE.es.md](docs/ARCHITECTURE.es.md) |
|
| 239 |
| [docs/RESULTS.md](docs/RESULTS.md) | [docs/RESULTS.es.md](docs/RESULTS.es.md) |
|
|
|
|
|
|
| 203 |
|
| 204 |
---
|
| 205 |
|
| 206 |
+
## Technical results report
|
| 207 |
+
|
| 208 |
+
Full write-up (decisions, metrics, error analysis, limitations, roadmap):
|
| 209 |
+
|
| 210 |
+
- **English:** [reports/final_report.md](reports/final_report.md)
|
| 211 |
+
- **Español:** [reports/final_report.es.md](reports/final_report.es.md)
|
| 212 |
+
|
| 213 |
## Model comparison
|
| 214 |
|
| 215 |
Canonical table: [`reports/summary.csv`](reports/summary.csv)
|
|
|
|
| 244 |
| [docs/PIPELINE.md](docs/PIPELINE.md) | [docs/PIPELINE.es.md](docs/PIPELINE.es.md) |
|
| 245 |
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | [docs/ARCHITECTURE.es.md](docs/ARCHITECTURE.es.md) |
|
| 246 |
| [docs/RESULTS.md](docs/RESULTS.md) | [docs/RESULTS.es.md](docs/RESULTS.es.md) |
|
| 247 |
+
| [reports/final_report.md](reports/final_report.md) | [reports/final_report.es.md](reports/final_report.es.md) |
|
docs/RESULTS.es.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
# Resultados y comparativa de modelos
|
| 2 |
|
| 3 |
Datos: [`reports/summary.csv`](../reports/summary.csv)
|
| 4 |
-
Hiperparámetros: [`configs/best_params.yaml`](../configs/best_params.yaml)
|
|
|
|
| 5 |
|
| 6 |
## Mejor modelo sklearn (producción)
|
| 7 |
|
|
|
|
| 1 |
# Resultados y comparativa de modelos
|
| 2 |
|
| 3 |
Datos: [`reports/summary.csv`](../reports/summary.csv)
|
| 4 |
+
Hiperparámetros: [`configs/best_params.yaml`](../configs/best_params.yaml)
|
| 5 |
+
**Informe técnico completo:** [`reports/final_report.es.md`](../reports/final_report.es.md) · [EN](../reports/final_report.md)
|
| 6 |
|
| 7 |
## Mejor modelo sklearn (producción)
|
| 8 |
|
docs/RESULTS.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
# Model results and comparison
|
| 2 |
|
| 3 |
Canonical data: [`reports/summary.csv`](../reports/summary.csv)
|
| 4 |
-
Tuned hyperparameters: [`configs/best_params.yaml`](../configs/best_params.yaml)
|
|
|
|
| 5 |
|
| 6 |
## Best sklearn model (production)
|
| 7 |
|
|
|
|
| 1 |
# Model results and comparison
|
| 2 |
|
| 3 |
Canonical data: [`reports/summary.csv`](../reports/summary.csv)
|
| 4 |
+
Tuned hyperparameters: [`configs/best_params.yaml`](../configs/best_params.yaml)
|
| 5 |
+
**Full technical report:** [`reports/final_report.md`](../reports/final_report.md) · [ES](../reports/final_report.es.md)
|
| 6 |
|
| 7 |
## Best sklearn model (production)
|
| 8 |
|
reports/final_report.es.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Informe técnico de resultados — Detector de comentarios tóxicos (SignalMod)
|
| 2 |
+
|
| 3 |
+
**Proyecto:** Asistente de moderación binario (Seguro vs Tóxico) para comentarios estilo YouTube
|
| 4 |
+
**Dataset:** `youtoxic_english_1000.csv` (1.000 filas)
|
| 5 |
+
**Modelo en producción:** `models/final_model.joblib` — Regresión logística + TF-IDF (Optuna)
|
| 6 |
+
**Fecha del informe:** 2026-05-23
|
| 7 |
+
**Artefactos:** [`summary.csv`](summary.csv) · [`pipeline/lr/`](pipeline/lr/) · EDA [`v2/`](v2/)
|
| 8 |
+
|
| 9 |
+
**English version:** [final_report.md](final_report.md)
|
| 10 |
+
|
| 11 |
+
---
|
| 12 |
+
|
| 13 |
+
## 1. Resumen ejecutivo
|
| 14 |
+
|
| 15 |
+
Se implementó un pipeline NLP completo (preprocesado → TF-IDF → clasificador), una API FastAPI y una demo Streamlit. El **modelo seleccionado para producción** es **LR + TF-IDF** ajustado con Optuna, con **F1 (ponderado) = 0,7579** y **ROC-AUC = 0,81** en el test hold-out, y una **brecha CV–test de 4,76 pp** (dentro del objetivo de < 5 pp). Los modelos transformer están disponibles de forma opcional en el catálogo de la API, pero no son el predeterminado por latencia, dependencias y falta de evaluación en el mismo test del proyecto.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## 2. Decisiones tomadas
|
| 20 |
+
|
| 21 |
+
| Área | Decisión | Motivo |
|
| 22 |
+
|------|----------|--------|
|
| 23 |
+
| **Formulación** | Clasificación binaria sobre `IsToxic` | `configs/pipeline.yaml`; modo `binary` por defecto. |
|
| 24 |
+
| **Etiquetas (UI/API)** | **Seguro** / **Tóxico** | API (`is_toxic`) y Streamlit. |
|
| 25 |
+
| **Preprocesado** | Minúsculas → regex → lemas spaCy → stopwords NLTK + custom | `TextPreprocessor`. |
|
| 26 |
+
| **Tokens sensibles** | Conservar *black*, *white*, *police*, *cop*, etc. | Necesarios para contexto/bigramas (EDA). |
|
| 27 |
+
| **Vectorización** | TF-IDF (1–2 gramas) | `max_features=4045`, `min_df=2` (Optuna). |
|
| 28 |
+
| **Modelos base** | LR (ganador), RF y XGBoost en pipeline | `build_model()` + `--model`. |
|
| 29 |
+
| **Búsqueda de hiperparámetros** | Optuna → `best_params.yaml` | Exportado a `final_model.joblib`. |
|
| 30 |
+
| **Métrica principal** | F1 ponderado + ROC-AUC | `configs/models.yaml`. |
|
| 31 |
+
| **Sobreajuste** | \|F1 CV − F1 test\| < 5 pp | `cv_test_gap_pp` en `Evaluator`. |
|
| 32 |
+
| **Desbalance** | `class_weight: balanced` | LR y RF en configuración. |
|
| 33 |
+
| **Serving** | `ModelService` + FastAPI + Streamlit | joblib local; HF vía `PUT /model/{name}`. |
|
| 34 |
+
| **Despliegue** | Docker Compose (`youtube_hate_detector`) | API :8000, Streamlit :8501. |
|
| 35 |
+
| **Trazabilidad** | MLflow + `reports/summary.csv` | Fases 8–9 del pipeline. |
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## 3. Dataset y limitaciones
|
| 40 |
+
|
| 41 |
+
### 3.1 Estadísticas
|
| 42 |
+
|
| 43 |
+
| Estadística | Valor |
|
| 44 |
+
|-------------|-------|
|
| 45 |
+
| Comentarios totales | 1.000 |
|
| 46 |
+
| Seguros (`IsToxic = 0`) | 538 (53,8 %) |
|
| 47 |
+
| Tóxicos (`IsToxic = 1`) | 462 (46,2 %) |
|
| 48 |
+
| Longitud media del texto | ~186 caracteres |
|
| 49 |
+
|
| 50 |
+
Partición: **80 % train / 20 % test** estratificado → **200 muestras de test** por ejecución del pipeline.
|
| 51 |
+
|
| 52 |
+
### 3.2 Limitaciones del dataset
|
| 53 |
+
|
| 54 |
+
1. **Tamaño reducido (~1k)** — Alta varianza; subtipos de toxicidad poco representados.
|
| 55 |
+
2. **Solo inglés** — No generaliza a otros idiomas sin reentrenar.
|
| 56 |
+
3. **Sesgo temático** — Comentarios ligados a vídeos/noticias concretas (p. ej. contexto Ferguson).
|
| 57 |
+
4. **Multietiqueta dispersa** — `IsRacist`, `IsSexist`, etc. con muy pocos positivos; se mantuvo binario `IsToxic` (ver `reports/v2/05_multilabel_overlap.png`).
|
| 58 |
+
5. **Ruido en etiquetas** — Sarcasmo, ironía y casos límite subjetivos.
|
| 59 |
+
6. **Pérdida en preprocesado** — Lematización y stopwords pueden eliminar señales; textos vacíos se rellenan con el original.
|
| 60 |
+
7. **Vocabulario identitario** — *black*, *white*, *police* aparecen en FP y FN; riesgo en discurso político no tóxico.
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
|
| 64 |
+
## 4. Métricas de todos los modelos
|
| 65 |
+
|
| 66 |
+
Fuente canónica: [`summary.csv`](summary.csv).
|
| 67 |
+
|
| 68 |
+
### 4.1 Modelo sklearn en producción (LR Optuna)
|
| 69 |
+
|
| 70 |
+
Desde `configs/best_params.yaml` — referencia de `final_model.joblib`:
|
| 71 |
+
|
| 72 |
+
| Métrica | Valor |
|
| 73 |
+
|---------|-------|
|
| 74 |
+
| F1 (ponderado, test) | **0,7579** |
|
| 75 |
+
| F1 (train) | 0,8987 |
|
| 76 |
+
| ROC-AUC | **0,81** |
|
| 77 |
+
| Falsos positivos (FP) | 18 |
|
| 78 |
+
| Falsos negativos (FN) | 30 |
|
| 79 |
+
| Brecha train–test | 14,07 pp |
|
| 80 |
+
| **Brecha CV–test** | **4,76 pp** ✓ |
|
| 81 |
+
|
| 82 |
+
**Hiperparámetros:** `C ≈ 0,32`, `max_features = 4045`, bigramas, `min_df = 2`.
|
| 83 |
+
|
| 84 |
+
### 4.2 Re-ejecución del pipeline (LR, configuración por defecto)
|
| 85 |
+
|
| 86 |
+
Última corrida: `reports/pipeline/lr/exp_20260523_163600_lr.json`
|
| 87 |
+
|
| 88 |
+
| Métrica | Valor |
|
| 89 |
+
|---------|-------|
|
| 90 |
+
| F1 (ponderado, test) | 0,7387 |
|
| 91 |
+
| F1 (tóxico) | 0,7045 |
|
| 92 |
+
| ROC-AUC | 0,7838 |
|
| 93 |
+
| Precisión / recall (ponderados) | 0,7399 / 0,74 |
|
| 94 |
+
| FP / FN | 22 / 30 |
|
| 95 |
+
| F1 CV medio ± std | 0,7193 ± 0,0382 |
|
| 96 |
+
| Brecha CV–test | 1,94 pp |
|
| 97 |
+
|
| 98 |
+
El artefacto ajustado (**0,7579**) supera esta corrida; producción usa `final_model.joblib` tunado.
|
| 99 |
+
|
| 100 |
+
### 4.3 Pendientes en la tabla comparativa
|
| 101 |
+
|
| 102 |
+
| Modelo | Estado |
|
| 103 |
+
|--------|--------|
|
| 104 |
+
| Random Forest | `python -m src.pipeline.run_pipeline --model rf` |
|
| 105 |
+
| XGBoost | `python -m src.pipeline.run_pipeline --model xgboost` |
|
| 106 |
+
|
| 107 |
+
### 4.4 Modelos Hugging Face (catálogo API)
|
| 108 |
+
|
| 109 |
+
Disponibles en `ModelService`; **sin métricas en el test del proyecto** en `summary.csv`:
|
| 110 |
+
|
| 111 |
+
| Modelo | F1 (catálogo externo) | Producción |
|
| 112 |
+
|--------|------------------------|------------|
|
| 113 |
+
| LR + TF-IDF (local) | ~0,76 | **Sí** |
|
| 114 |
+
| DistilBERT Toxicity | ~0,85 | No |
|
| 115 |
+
| toxic-bert (multietiqueta) | ~0,88 | No |
|
| 116 |
+
| RoBERTa Toxicity | ~0,87 | No |
|
| 117 |
+
|
| 118 |
+
Experimentos notebook: `reports/v2/nb08_*`.
|
| 119 |
+
|
| 120 |
+
### 4.5 Tabla resumen
|
| 121 |
+
|
| 122 |
+
| Modelo | F1 (test) | ROC-AUC | FP | FN | Brecha CV–test | Por defecto |
|
| 123 |
+
|--------|-----------|---------|----|----|----------------|-------------|
|
| 124 |
+
| **LR + TF-IDF (ajustado)** | **0,7579** | **0,81** | 18 | 30 | **4,76 pp** | Sí |
|
| 125 |
+
| LR (re-ejecución pipeline) | 0,7387 | 0,784 | 22 | 30 | 1,94 pp | — |
|
| 126 |
+
| RF / XGBoost | — | — | — | — | — | Ejecutar pipeline |
|
| 127 |
+
| HF (catálogo) | — | — | — | — | — | Opcional |
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## 5. Modelo seleccionado y por qué
|
| 132 |
+
|
| 133 |
+
**Seleccionado:** **Regresión logística + TF-IDF** (`models/final_model.joblib`, Optuna).
|
| 134 |
+
|
| 135 |
+
**Motivos**
|
| 136 |
+
|
| 137 |
+
1. **Mejor rendimiento** en el test del proyecto (F1 0,7579, ROC-AUC 0,81).
|
| 138 |
+
2. **Cumple el criterio de generalización** — brecha CV–test < 5 pp.
|
| 139 |
+
3. **Operación** — inferencia rápida, sin GPU, artefacto pequeño, Docker ligero.
|
| 140 |
+
4. **Interpretabilidad** — coeficientes TF-IDF (`reports/v2/11_lr_coeficientes.png`).
|
| 141 |
+
5. **Mismo stack** en entrenamiento, API y Streamlit (`ModelService`).
|
| 142 |
+
|
| 143 |
+
**Por qué no transformer por defecto**
|
| 144 |
+
|
| 145 |
+
- Más peso (torch/transformers), arranque lento.
|
| 146 |
+
- Cifras del catálogo no evaluadas en `youtoxic_english_1000`.
|
| 147 |
+
- Útiles como **opción** en demos vía API.
|
| 148 |
+
|
| 149 |
+
---
|
| 150 |
+
|
| 151 |
+
## 6. Análisis de errores
|
| 152 |
+
|
| 153 |
+
Fuente: `reports/pipeline/lr/errors_lr.csv` (última corrida LR, n=200 test).
|
| 154 |
+
|
| 155 |
+
### 6.1 Resumen de confusión (pipeline LR)
|
| 156 |
+
|
| 157 |
+
| | Predicho seguro | Predicho tóxico |
|
| 158 |
+
|--|-----------------|-----------------|
|
| 159 |
+
| **Real seguro** | VN | **22 FP** |
|
| 160 |
+
| **Real tóxico** | **30 FN** | VP |
|
| 161 |
+
|
| 162 |
+
Modelo tunado en producción: **18 FP / 30 FN**.
|
| 163 |
+
|
| 164 |
+
### 6.2 Términos más frecuentes en errores
|
| 165 |
+
|
| 166 |
+
**Falsos positivos:** `black(14)`, `white(9)`, `shoot(8)`, `would(9)`, `police(5)`, `cop(6)` — el modelo reacciona a **vocabulario racial/policial en contexto informativo**.
|
| 167 |
+
|
| 168 |
+
**Falsos negativos:** `police(8)`, `criminal(6)`, `kill(5)`, `black(6)` — **toxicidad indirecta o comentarios largos** por debajo del umbral.
|
| 169 |
+
|
| 170 |
+
### 6.3 Patrones
|
| 171 |
+
|
| 172 |
+
| Tipo | Patrón |
|
| 173 |
+
|------|--------|
|
| 174 |
+
| FP | Debate político/racial sin insulto directo |
|
| 175 |
+
| FP | Palabrotas en contexto de frustración no dirigida |
|
| 176 |
+
| FN | Odio implícito, sarcasmo, texto muy largo |
|
| 177 |
+
|
| 178 |
+
Gráficos: [`pipeline/lr/cm_lr.png`](pipeline/lr/cm_lr.png), [`pipeline/lr/roc_lr.png`](pipeline/lr/roc_lr.png).
|
| 179 |
+
|
| 180 |
+
---
|
| 181 |
+
|
| 182 |
+
## 7. Posibles mejoras futuras
|
| 183 |
+
|
| 184 |
+
| Prioridad | Mejora | Beneficio esperado |
|
| 185 |
+
|-----------|--------|-------------------|
|
| 186 |
+
| Alta | **Más datos etiquetados** | Menos FP en política; mejor recall |
|
| 187 |
+
| Alta | **Ajuste de umbral** en validación | Alinear FP/FN con política de moderación |
|
| 188 |
+
| Alta | **Evaluar RF, XGBoost y DistilBERT** en el mismo test | Comparativa justa en `summary.csv` |
|
| 189 |
+
| Media | **Augmentación** (back-translation) | Menos sobreajuste (`reports/v2/15_*`) |
|
| 190 |
+
| Media | **Fine-tuning DistilBERT** en datos del proyecto | Mejor contexto que TF-IDF |
|
| 191 |
+
| Media | **Ensemble** LR + transformer | Ver `reports/v2/12_*` |
|
| 192 |
+
| Media | **Cola de revisión humana** | Casos con probabilidad 0,4–0,6 |
|
| 193 |
+
| Baja | **Cabezas multietiqueta** | Solo si hay positivos suficientes por etiqueta |
|
| 194 |
+
| Baja | **Frontend React** | UX tipo YouTube en producción |
|
| 195 |
+
| Baja | **PostgreSQL** | Auditoría de predicciones |
|
| 196 |
+
|
| 197 |
+
---
|
| 198 |
+
|
| 199 |
+
## 8. Reproducibilidad
|
| 200 |
+
|
| 201 |
+
```bash
|
| 202 |
+
python -m src.pipeline.run_pipeline --model lr
|
| 203 |
+
cat reports/summary.csv
|
| 204 |
+
docker compose up --build
|
| 205 |
+
```
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
## 9. Referencias
|
| 210 |
+
|
| 211 |
+
| Documento | Ruta |
|
| 212 |
+
|-----------|------|
|
| 213 |
+
| CSV comparativo | [`summary.csv`](summary.csv) |
|
| 214 |
+
| Resultados (ES) | [`../docs/RESULTS.es.md`](../docs/RESULTS.es.md) |
|
| 215 |
+
| Pipeline (ES) | [`../docs/PIPELINE.es.md`](../docs/PIPELINE.es.md) |
|
| 216 |
+
| API (ES) | [`../docs/API.es.md`](../docs/API.es.md) |
|
| 217 |
+
| Mejores hiperparámetros | [`../configs/best_params.yaml`](../configs/best_params.yaml) |
|
reports/final_report.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Technical Results Report — YouTube Toxic Comment Detector (SignalMod)
|
| 2 |
+
|
| 3 |
+
**Project:** Binary moderation assistant (Safe vs Toxic) for YouTube-style comments
|
| 4 |
+
**Dataset:** `youtoxic_english_1000.csv` (1,000 rows)
|
| 5 |
+
**Production model:** `models/final_model.joblib` — Logistic Regression + TF-IDF (Optuna-tuned)
|
| 6 |
+
**Report date:** 2026-05-23
|
| 7 |
+
**Related artifacts:** [`summary.csv`](summary.csv) · [`pipeline/lr/`](pipeline/lr/) · EDA [`v2/`](v2/)
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 1. Executive summary
|
| 12 |
+
|
| 13 |
+
We built an end-to-end NLP pipeline (preprocess → TF-IDF → classifier), a FastAPI inference service, and a Streamlit demo. The **selected production model** is **LR + TF-IDF** tuned with Optuna, reaching **F1 (weighted) = 0.7579** and **ROC-AUC = 0.81** on the held-out test split, with a **CV–test gap of 4.76 pp** (within the project’s < 5 pp overfitting target). Transformer models are available optionally via the API catalog but were not adopted as the default due to latency, dependency weight, and lack of evaluation on the same project test split.
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## 2. Decisions made
|
| 18 |
+
|
| 19 |
+
| Area | Decision | Rationale (from implementation) |
|
| 20 |
+
|------|----------|--------------------------------|
|
| 21 |
+
| **Task formulation** | Binary classification on `IsToxic` | Configured in `configs/pipeline.yaml`; multilabel columns exist but default mode is `binary`. |
|
| 22 |
+
| **Labels (UI/API)** | **Safe** / **Toxic** | User-facing copy in API (`is_toxic`) and Streamlit; raw CSV keeps `IsToxic`. |
|
| 23 |
+
| **Preprocessing** | Lowercase → regex cleanup → spaCy lemmas → NLTK + custom stopwords | `TextPreprocessor` in `src/features/text_preprocessor.py`. |
|
| 24 |
+
| **Sensitive tokens** | Keep *black*, *white*, *police*, *cop*, etc. | EDA decision documented in preprocessor: needed for context/bigrams, not removed as stopwords. |
|
| 25 |
+
| **Vectorization** | TF-IDF, unigrams + bigrams | Captures phrases like toxic bigrams; tuned `max_features=4045`, `min_df=2`. |
|
| 26 |
+
| **Baseline family** | LR (winner), RF, XGBoost available | `build_model()` in `src/models/baseline.py`; pipeline flag `--model`. |
|
| 27 |
+
| **Hyperparameter search** | Optuna → `configs/best_params.yaml` | Best LR stored; exported to `final_model.joblib`. |
|
| 28 |
+
| **Primary metric** | F1 weighted + ROC-AUC | `configs/models.yaml` → `evaluation.primary_metric`. |
|
| 29 |
+
| **Overfitting control** | \|CV F1 − test F1\| < 5 pp | Reported as `cv_test_gap_pp` in `Evaluator`. |
|
| 30 |
+
| **Class imbalance** | `class_weight: balanced` (LR/RF) | `configs/models.yaml`. |
|
| 31 |
+
| **Serving** | `ModelService` + FastAPI + Streamlit | Local joblib default; HF models switchable via `PUT /model/{name}`. |
|
| 32 |
+
| **Deployment** | Docker Compose (`youtube_hate_detector`) | API :8000, Streamlit :8501. |
|
| 33 |
+
| **Experiment tracking** | MLflow (`mlruns/`) + `reports/summary.csv` | Pipeline phase 8–9. |
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 3. Dataset overview and limitations
|
| 38 |
+
|
| 39 |
+
### 3.1 Statistics (raw split source)
|
| 40 |
+
|
| 41 |
+
| Statistic | Value |
|
| 42 |
+
|-----------|-------|
|
| 43 |
+
| Total comments | 1,000 |
|
| 44 |
+
| Safe (`IsToxic = 0`) | 538 (53.8%) |
|
| 45 |
+
| Toxic (`IsToxic = 1`) | 462 (46.2%) |
|
| 46 |
+
| Mean comment length | ~186 characters |
|
| 47 |
+
|
| 48 |
+
Train/test: **80/20 stratified** (`test_size: 0.2`, `random_state: 42`) → **200 test samples** per pipeline run.
|
| 49 |
+
|
| 50 |
+
### 3.2 Limitations
|
| 51 |
+
|
| 52 |
+
1. **Small sample size (~1k rows)** — High variance in metrics; rare toxicity subtypes are underrepresented.
|
| 53 |
+
2. **Single language (English)** — Model and tokenizer choices assume English YouTube comments.
|
| 54 |
+
3. **Topic / event bias** — Many comments relate to specific news videos (e.g. Ferguson-era discourse); generalization to other channels is uncertain.
|
| 55 |
+
4. **Multilabel sparsity** — Columns such as `IsRacist`, `IsSexist`, `IsHomophobic` have very few positives; project stayed on binary `IsToxic` (see EDA plots in `reports/v2/05_multilabel_overlap.png`).
|
| 56 |
+
5. **Label noise** — Crowd/annotator subjectivity on edge cases (sarcasm, reclaimed slurs, political rhetoric).
|
| 57 |
+
6. **Preprocessing information loss** — Lemmatization and stopword removal can remove cues; empty texts are back-filled with raw text in the pipeline.
|
| 58 |
+
7. **Identity-related vocabulary** — Frequent tokens (*black*, *white*, *police*) appear in both errors and valid discussion, increasing false positives/negatives on racial/political content.
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## 4. Metrics — all models
|
| 63 |
+
|
| 64 |
+
Canonical source: [`summary.csv`](summary.csv). Plots: `reports/pipeline/{model}/`, EDA: `reports/v2/`.
|
| 65 |
+
|
| 66 |
+
### 4.1 Production sklearn model (Optuna-tuned LR)
|
| 67 |
+
|
| 68 |
+
From `configs/best_params.yaml` — metrics on **held-out test** after tuning (reference for `final_model.joblib`):
|
| 69 |
+
|
| 70 |
+
| Metric | Value |
|
| 71 |
+
|--------|-------|
|
| 72 |
+
| F1 (weighted, test) | **0.7579** |
|
| 73 |
+
| F1 (train) | 0.8987 |
|
| 74 |
+
| ROC-AUC | **0.81** |
|
| 75 |
+
| False positives (FP) | 18 |
|
| 76 |
+
| False negatives (FN) | 30 |
|
| 77 |
+
| Train–test gap | 14.07 pp |
|
| 78 |
+
| **CV–test gap** | **4.76 pp** ✓ (< 5 pp) |
|
| 79 |
+
|
| 80 |
+
**Tuned hyperparameters:** `C ≈ 0.32`, `max_features = 4045`, `ngram_range = (1,2)`, `min_df = 2`, `sublinear_tf = false`.
|
| 81 |
+
|
| 82 |
+
### 4.2 Pipeline re-run (LR, default config path)
|
| 83 |
+
|
| 84 |
+
Latest automated run (`reports/pipeline/lr/exp_20260523_163600_lr.json`) — same split/preprocess, documents reproducibility:
|
| 85 |
+
|
| 86 |
+
| Metric | Value |
|
| 87 |
+
|--------|-------|
|
| 88 |
+
| F1 (weighted, test) | 0.7387 |
|
| 89 |
+
| F1 (toxic, pos=1) | 0.7045 |
|
| 90 |
+
| ROC-AUC | 0.7838 |
|
| 91 |
+
| Accuracy | 0.74 |
|
| 92 |
+
| FP / FN | 22 / 30 |
|
| 93 |
+
| CV F1 mean ± std | 0.7193 ± 0.0382 |
|
| 94 |
+
| CV–test gap | 1.94 pp |
|
| 95 |
+
| Train–test gap | 15.97 pp |
|
| 96 |
+
|
| 97 |
+
The tuned artifact (**0.7579**) outperforms this default-config rerun; production uses the tuned weights in `final_model.joblib`.
|
| 98 |
+
|
| 99 |
+
### 4.3 Sklearn baselines not yet in summary table
|
| 100 |
+
|
| 101 |
+
| Model | Status | Action |
|
| 102 |
+
|-------|--------|--------|
|
| 103 |
+
| Random Forest (`rf`) | Not evaluated in `summary.csv` | `python -m src.pipeline.run_pipeline --model rf` |
|
| 104 |
+
| XGBoost (`xgboost`) | Not evaluated in `summary.csv` | `python -m src.pipeline.run_pipeline --model xgboost` |
|
| 105 |
+
|
| 106 |
+
### 4.4 Hugging Face models (API catalog)
|
| 107 |
+
|
| 108 |
+
Available in `ModelService` for live switching; **not benchmarked on the project test split** in `summary.csv`:
|
| 109 |
+
|
| 110 |
+
| Model | Type | Catalog F1 note | Production default |
|
| 111 |
+
|-------|------|-----------------|--------------------|
|
| 112 |
+
| LR + TF-IDF (local) | joblib | 0.76 (aligned with tuning) | **Yes** |
|
| 113 |
+
| DistilBERT Toxicity | HF remote | ~0.85 (external) | No |
|
| 114 |
+
| toxic-bert (multilabel) | HF remote | ~0.88 (external, Jigsaw) | No |
|
| 115 |
+
| RoBERTa Toxicity | HF remote | ~0.87 (external) | No |
|
| 116 |
+
| Fine-tuned local HF | HF local | To be evaluated | No |
|
| 117 |
+
|
| 118 |
+
Notebook experiments (confusion matrices): `reports/v2/nb08_*`.
|
| 119 |
+
|
| 120 |
+
### 4.5 Comparison table (summary)
|
| 121 |
+
|
| 122 |
+
| Model | F1 (test) | ROC-AUC | FP | FN | CV–test gap (pp) | Default |
|
| 123 |
+
|-------|-----------|---------|----|----|------------------|---------|
|
| 124 |
+
| **LR + TF-IDF (tuned)** | **0.7579** | **0.81** | 18 | 30 | **4.76** | Yes |
|
| 125 |
+
| LR (pipeline rerun) | 0.7387 | 0.784 | 22 | 30 | 1.94 | — |
|
| 126 |
+
| RF | — | — | — | — | — | Run pipeline |
|
| 127 |
+
| XGBoost | — | — | — | — | — | Run pipeline |
|
| 128 |
+
| HF catalog models | — | — | — | — | — | Optional |
|
| 129 |
+
|
| 130 |
+
---
|
| 131 |
+
|
| 132 |
+
## 5. Selected model and justification
|
| 133 |
+
|
| 134 |
+
**Selected:** **Logistic Regression + TF-IDF** (`models/final_model.joblib`, Optuna-tuned).
|
| 135 |
+
|
| 136 |
+
**Why this model**
|
| 137 |
+
|
| 138 |
+
1. **Best project test performance** among trained sklearn artifacts (F1 0.7579, ROC-AUC 0.81).
|
| 139 |
+
2. **Meets generalization criterion** — CV–test gap 4.76 pp < 5 pp (train–test gap is higher but in-sample; CV gap is the rubric metric).
|
| 140 |
+
3. **Operational fit** — Inference < 50 ms, no GPU, small artifact (~100 KB joblib), runs in Docker without downloading large transformers.
|
| 141 |
+
4. **Interpretability** — TF-IDF coefficients inspectable (`reports/v2/11_lr_coeficientes.png`); helps moderation audits.
|
| 142 |
+
5. **Stable stack** — sklearn + spaCy + NLTK already used in training pipeline; same `ModelService` path for API and Streamlit.
|
| 143 |
+
|
| 144 |
+
**Why not transformers by default**
|
| 145 |
+
|
| 146 |
+
- Heavier dependencies (torch, transformers), slower cold start, higher memory.
|
| 147 |
+
- Catalog accuracy figures are **not** measured on `youtoxic_english_1000` test split in this repo.
|
| 148 |
+
- Acceptable as **optional** models via API for comparison demos.
|
| 149 |
+
|
| 150 |
+
---
|
| 151 |
+
|
| 152 |
+
## 6. Error analysis
|
| 153 |
+
|
| 154 |
+
Source: `reports/pipeline/lr/errors_lr.csv` and evaluator term counts (latest LR pipeline run, 52 errors on n=200 test).
|
| 155 |
+
|
| 156 |
+
### 6.1 Confusion summary (pipeline LR run)
|
| 157 |
+
|
| 158 |
+
| | Predicted Safe | Predicted Toxic |
|
| 159 |
+
|--|----------------|-----------------|
|
| 160 |
+
| **Actual Safe** | TN | **22 FP** |
|
| 161 |
+
| **Actual Toxic** | **30 FN** | TP |
|
| 162 |
+
|
| 163 |
+
Production tuned model: **18 FP / 30 FN** (fewer false positives).
|
| 164 |
+
|
| 165 |
+
### 6.2 Most common terms in errors
|
| 166 |
+
|
| 167 |
+
**False positives** (safe text marked toxic) — frequent lemmas:
|
| 168 |
+
|
| 169 |
+
`black(14)`, `white(9)`, `shoot(8)`, `would(9)`, `police(5)`, `cop(6)`, `people(7)`, `guy(7)`
|
| 170 |
+
|
| 171 |
+
→ Model often reacts to **identity and violence-related vocabulary in non-toxic political/news context**.
|
| 172 |
+
|
| 173 |
+
**False negatives** (toxic missed) — frequent lemmas:
|
| 174 |
+
|
| 175 |
+
`police(8)`, `black(6)`, `criminal(6)`, `kill(5)`, `make(6)`, `people(5)`
|
| 176 |
+
|
| 177 |
+
→ Missed toxicity when phrasing is **indirect, sarcastic, or embedded in long argumentative comments** (probabilities just below threshold).
|
| 178 |
+
|
| 179 |
+
### 6.3 Representative patterns
|
| 180 |
+
|
| 181 |
+
| Type | Pattern | Example risk |
|
| 182 |
+
|------|---------|----------------|
|
| 183 |
+
| FP | Political/racial discussion without insult | Comments about Ferguson, police, “black/white” discourse |
|
| 184 |
+
| FP | Profanity in non-directed context | e.g. mild expletives in traffic/frustration |
|
| 185 |
+
| FN | Long, indirect hate | Low model score despite toxic content in body |
|
| 186 |
+
| FN | Sarcasm / coded language | Hard for bag-of-words + linear model |
|
| 187 |
+
|
| 188 |
+
Artifacts: [`pipeline/lr/cm_lr.png`](pipeline/lr/cm_lr.png), [`pipeline/lr/roc_lr.png`](pipeline/lr/roc_lr.png).
|
| 189 |
+
|
| 190 |
+
---
|
| 191 |
+
|
| 192 |
+
## 7. Future improvements
|
| 193 |
+
|
| 194 |
+
| Priority | Improvement | Expected benefit |
|
| 195 |
+
|----------|-------------|----------------|
|
| 196 |
+
| High | **More labeled data** + domain-balanced sampling | Reduce FP on political text; improve recall |
|
| 197 |
+
| High | **Threshold tuning** on validation (per use case) | Trade FP vs FN for moderation policy |
|
| 198 |
+
| High | **Evaluate RF/XGBoost and fine-tuned DistilBERT** on same test split | Fair comparison in `summary.csv` |
|
| 199 |
+
| Medium | **Back-translation / synonym augmentation** | Mitigate small-data overfitting (EDA: `reports/v2/15_augmentation_*`) |
|
| 200 |
+
| Medium | **Fine-tune DistilBERT on project data** | Capture context better than TF-IDF |
|
| 201 |
+
| Medium | **Ensemble** LR + transformer vote | Notebook track: `reports/v2/12_ensemble_comparativa.png` |
|
| 202 |
+
| Medium | **Human-in-the-loop review queue** | Route borderline scores (e.g. 0.4–0.6) to moderators |
|
| 203 |
+
| Low | **Multilabel heads** for sparse sublabels | Only if minimum positive counts per label are met |
|
| 204 |
+
| Low | **React frontend** (if replacing Streamlit) | Production UX parity with YouTube |
|
| 205 |
+
| Low | **PostgreSQL logging** | Audit trail for predictions in production |
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
## 8. Reproducibility
|
| 210 |
+
|
| 211 |
+
```bash
|
| 212 |
+
# Retrain and refresh metrics
|
| 213 |
+
python -m src.pipeline.run_pipeline --model lr
|
| 214 |
+
|
| 215 |
+
# View comparison table
|
| 216 |
+
cat reports/summary.csv
|
| 217 |
+
|
| 218 |
+
# Run API + UI
|
| 219 |
+
docker compose up --build
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
---
|
| 223 |
+
|
| 224 |
+
## 9. References
|
| 225 |
+
|
| 226 |
+
| Document | Path |
|
| 227 |
+
|----------|------|
|
| 228 |
+
| Model comparison CSV | [`summary.csv`](summary.csv) |
|
| 229 |
+
| Results (EN) | [`../docs/RESULTS.md`](../docs/RESULTS.md) |
|
| 230 |
+
| Pipeline (EN) | [`../docs/PIPELINE.md`](../docs/PIPELINE.md) |
|
| 231 |
+
| API (EN) | [`../docs/API.md`](../docs/API.md) |
|
| 232 |
+
| Best hyperparameters | [`../configs/best_params.yaml`](../configs/best_params.yaml) |
|