feat: add preprocessing, new version. #3
Browse files- .gitignore +0 -1
- notebooks/02_preprocessing_v2.ipynb +835 -0
.gitignore
CHANGED
|
@@ -54,4 +54,3 @@ mlruns/
|
|
| 54 |
mlartifacts/
|
| 55 |
|
| 56 |
#jony
|
| 57 |
-
02_preprocessing_v2.ipynb
|
|
|
|
| 54 |
mlartifacts/
|
| 55 |
|
| 56 |
#jony
|
|
|
notebooks/02_preprocessing_v2.ipynb
ADDED
|
@@ -0,0 +1,835 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"metadata": {},
|
| 6 |
+
"source": [
|
| 7 |
+
"# 🔧 Notebook 02 — Preprocesamiento de Texto\n",
|
| 8 |
+
"\n",
|
| 9 |
+
"### ¿Qué hace este notebook?\n",
|
| 10 |
+
"Construimos y validamos el pipeline de limpieza de texto **paso a paso**.\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"### ¿Por qué se hace así?\n",
|
| 13 |
+
"El texto crudo de YouTube tiene ruido que engaña al modelo: URLs, menciones, caracteres raros (`\\xa0`), contracciones rotas (`don t`).\n",
|
| 14 |
+
"Antes de vectorizar necesitamos texto limpio y normalizado.\n",
|
| 15 |
+
"\n",
|
| 16 |
+
"### Herramientas\n",
|
| 17 |
+
"- **`re`** → expresiones regulares para limpiar ruido estructural\n",
|
| 18 |
+
"- **`NLTK`** → lista curada de 179 stopwords en inglés\n",
|
| 19 |
+
"- **`spaCy`** → lematización con modelo de lenguaje real `en_core_web_sm`\n",
|
| 20 |
+
"- **`MLflow`** → registrar qué configuración de preprocesamiento usamos\n",
|
| 21 |
+
"\n",
|
| 22 |
+
"### Output de este notebook\n",
|
| 23 |
+
"- Columna `clean_text` lista para vectorizar\n",
|
| 24 |
+
"- `data/processed/v2/comments_preprocessed.csv`\n",
|
| 25 |
+
"- Experimento registrado en MLflow: `Youtube_project_experiment`"
|
| 26 |
+
]
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"cell_type": "markdown",
|
| 30 |
+
"metadata": {},
|
| 31 |
+
"source": [
|
| 32 |
+
"## 0. Imports y configuración\n",
|
| 33 |
+
"\n",
|
| 34 |
+
"Cargamos todo desde el YAML. Ningún valor hardcodeado en el notebook."
|
| 35 |
+
]
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"cell_type": "code",
|
| 39 |
+
"execution_count": 1,
|
| 40 |
+
"metadata": {},
|
| 41 |
+
"outputs": [
|
| 42 |
+
{
|
| 43 |
+
"name": "stderr",
|
| 44 |
+
"output_type": "stream",
|
| 45 |
+
"text": [
|
| 46 |
+
"/home/under/miniconda3/envs/py310/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
|
| 47 |
+
" from .autonotebook import tqdm as notebook_tqdm\n"
|
| 48 |
+
]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"name": "stdout",
|
| 52 |
+
"output_type": "stream",
|
| 53 |
+
"text": [
|
| 54 |
+
"PROJECT_ROOT : /mnt/c/Users/under/Documents/F5/3_Projects/Project_9_Equipo3/Project_YT\n",
|
| 55 |
+
"Python : 3.10.20\n"
|
| 56 |
+
]
|
| 57 |
+
}
|
| 58 |
+
],
|
| 59 |
+
"source": [
|
| 60 |
+
"import re\n",
|
| 61 |
+
"import sys\n",
|
| 62 |
+
"import yaml\n",
|
| 63 |
+
"import pandas as pd\n",
|
| 64 |
+
"import numpy as np\n",
|
| 65 |
+
"import matplotlib.pyplot as plt\n",
|
| 66 |
+
"import seaborn as sns\n",
|
| 67 |
+
"import mlflow\n",
|
| 68 |
+
"import nltk\n",
|
| 69 |
+
"import spacy\n",
|
| 70 |
+
"from nltk.corpus import stopwords\n",
|
| 71 |
+
"from pathlib import Path\n",
|
| 72 |
+
"import warnings\n",
|
| 73 |
+
"warnings.filterwarnings('ignore')\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"# Ruta raiz — sube desde notebooks/ a la raiz del proyecto\n",
|
| 76 |
+
"PROJECT_ROOT = Path.cwd().parent\n",
|
| 77 |
+
"sys.path.insert(0, str(PROJECT_ROOT))\n",
|
| 78 |
+
"\n",
|
| 79 |
+
"plt.rcParams['figure.figsize'] = (12, 5)\n",
|
| 80 |
+
"plt.rcParams['axes.spines.top'] = False\n",
|
| 81 |
+
"plt.rcParams['axes.spines.right'] = False\n",
|
| 82 |
+
"\n",
|
| 83 |
+
"print(f'PROJECT_ROOT : {PROJECT_ROOT}')\n",
|
| 84 |
+
"print(f'Python : {sys.version.split()[0]}')"
|
| 85 |
+
]
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"cell_type": "code",
|
| 89 |
+
"execution_count": 2,
|
| 90 |
+
"metadata": {},
|
| 91 |
+
"outputs": [
|
| 92 |
+
{
|
| 93 |
+
"name": "stdout",
|
| 94 |
+
"output_type": "stream",
|
| 95 |
+
"text": [
|
| 96 |
+
"Configuracion de preprocesamiento:\n",
|
| 97 |
+
" lowercase: True\n",
|
| 98 |
+
" remove_urls: True\n",
|
| 99 |
+
" remove_mentions: True\n",
|
| 100 |
+
" remove_emojis: True\n",
|
| 101 |
+
" remove_special_chars: True\n",
|
| 102 |
+
" remove_stopwords: True\n",
|
| 103 |
+
" lemmatize: True\n",
|
| 104 |
+
" min_token_length: 2\n",
|
| 105 |
+
" language: en\n"
|
| 106 |
+
]
|
| 107 |
+
}
|
| 108 |
+
],
|
| 109 |
+
"source": [
|
| 110 |
+
"# Carga de configuracion desde YAML\n",
|
| 111 |
+
"CONFIG_PATH = PROJECT_ROOT / 'configs' / 'features.yaml'\n",
|
| 112 |
+
"\n",
|
| 113 |
+
"with open(CONFIG_PATH) as f:\n",
|
| 114 |
+
" config = yaml.safe_load(f)\n",
|
| 115 |
+
"\n",
|
| 116 |
+
"prep_cfg = config['preprocessing']\n",
|
| 117 |
+
"print('Configuracion de preprocesamiento:')\n",
|
| 118 |
+
"for k, v in prep_cfg.items():\n",
|
| 119 |
+
" print(f' {k}: {v}')"
|
| 120 |
+
]
|
| 121 |
+
},
|
| 122 |
+
{
|
| 123 |
+
"cell_type": "code",
|
| 124 |
+
"execution_count": null,
|
| 125 |
+
"metadata": {},
|
| 126 |
+
"outputs": [
|
| 127 |
+
{
|
| 128 |
+
"name": "stdout",
|
| 129 |
+
"output_type": "stream",
|
| 130 |
+
"text": [
|
| 131 |
+
"spaCy : core_web_sm v3.8.0\n",
|
| 132 |
+
"NLTK : 198 stopwords en ingles\n"
|
| 133 |
+
]
|
| 134 |
+
}
|
| 135 |
+
],
|
| 136 |
+
"source": [
|
| 137 |
+
"# Descargar recursos NLTK\n",
|
| 138 |
+
"nltk.download('stopwords', quiet=True)\n",
|
| 139 |
+
"nltk.download('punkt', quiet=True)\n",
|
| 140 |
+
"\n",
|
| 141 |
+
"# Cargar modelo spaCy\n",
|
| 142 |
+
"# Si no lo tienes instalado: python -m spacy download en_core_web_sm\n",
|
| 143 |
+
"nlp = spacy.load('en_core_web_sm', disable=['parser', 'ner'])\n",
|
| 144 |
+
"\n",
|
| 145 |
+
"print(f\"spaCy : {nlp.meta['name']} v{nlp.meta['version']}\")\n",
|
| 146 |
+
"print(f'NLTK : {len(stopwords.words(\"english\"))} stopwords en ingles')"
|
| 147 |
+
]
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"cell_type": "markdown",
|
| 151 |
+
"metadata": {},
|
| 152 |
+
"source": [
|
| 153 |
+
"## 1. Carga de datos\n",
|
| 154 |
+
"\n",
|
| 155 |
+
"Las rutas vienen del YAML de pipeline, no se escriben a mano."
|
| 156 |
+
]
|
| 157 |
+
},
|
| 158 |
+
{
|
| 159 |
+
"cell_type": "code",
|
| 160 |
+
"execution_count": 4,
|
| 161 |
+
"metadata": {},
|
| 162 |
+
"outputs": [
|
| 163 |
+
{
|
| 164 |
+
"name": "stdout",
|
| 165 |
+
"output_type": "stream",
|
| 166 |
+
"text": [
|
| 167 |
+
"Dataset : (1000, 15)\n",
|
| 168 |
+
"Texto : \"Text\"\n",
|
| 169 |
+
"Target : \"IsToxic\"\n",
|
| 170 |
+
"Sublabels: ['IsAbusive', 'IsProvocative', 'IsHatespeech', 'IsRacist', 'IsObscene']\n",
|
| 171 |
+
"\n",
|
| 172 |
+
"Muestra texto crudo:\n",
|
| 173 |
+
" -> \"You call yourself an anarchist but defend a cop shooting an unarmed civilian. I'm highly disappointe\"\n",
|
| 174 |
+
" -> 'My mother told me the same thing.\\xa0 God Bless this woman.'\n",
|
| 175 |
+
" -> 'Love it I same the saem thing Go Peggy! #stupid \\xa0\\nYa Killing ya selves more quicker than\\xa0 STLPD cou'\n"
|
| 176 |
+
]
|
| 177 |
+
}
|
| 178 |
+
],
|
| 179 |
+
"source": [
|
| 180 |
+
"PIPELINE_PATH = PROJECT_ROOT / 'configs' / 'pipeline.yaml'\n",
|
| 181 |
+
"\n",
|
| 182 |
+
"with open(PIPELINE_PATH) as f:\n",
|
| 183 |
+
" pipeline_cfg = yaml.safe_load(f)\n",
|
| 184 |
+
"\n",
|
| 185 |
+
"DATA_PATH = PROJECT_ROOT / pipeline_cfg['data']['raw_path']\n",
|
| 186 |
+
"TEXT_COL = pipeline_cfg['data']['text_column']\n",
|
| 187 |
+
"TARGET_BIN = pipeline_cfg['data']['target_binary']\n",
|
| 188 |
+
"SUBLABELS = pipeline_cfg['data']['target_multilabel']\n",
|
| 189 |
+
"\n",
|
| 190 |
+
"df = pd.read_csv(DATA_PATH)\n",
|
| 191 |
+
"print(f'Dataset : {df.shape}')\n",
|
| 192 |
+
"print(f'Texto : \"{TEXT_COL}\"')\n",
|
| 193 |
+
"print(f'Target : \"{TARGET_BIN}\"')\n",
|
| 194 |
+
"print(f'Sublabels: {SUBLABELS}')\n",
|
| 195 |
+
"print()\n",
|
| 196 |
+
"print('Muestra texto crudo:')\n",
|
| 197 |
+
"for t in df[TEXT_COL].sample(3, random_state=42):\n",
|
| 198 |
+
" print(f' -> {repr(t[:100])}')"
|
| 199 |
+
]
|
| 200 |
+
},
|
| 201 |
+
{
|
| 202 |
+
"cell_type": "markdown",
|
| 203 |
+
"metadata": {},
|
| 204 |
+
"source": [
|
| 205 |
+
"## 2. Pipeline paso a paso\n",
|
| 206 |
+
"\n",
|
| 207 |
+
"Construimos cada función por separado para entender su efecto antes de encadenarlas.\n",
|
| 208 |
+
"\n",
|
| 209 |
+
"```\n",
|
| 210 |
+
"texto raw\n",
|
| 211 |
+
" → [1] lowercase\n",
|
| 212 |
+
" → [2] regex: URLs, @menciones, \\xa0, contracciones rotas, números\n",
|
| 213 |
+
" → [3] spaCy: tokenización + lematización\n",
|
| 214 |
+
" → [4] NLTK: filtrado stopwords + tokens cortos + puntuación\n",
|
| 215 |
+
" → texto limpio\n",
|
| 216 |
+
"```"
|
| 217 |
+
]
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"cell_type": "code",
|
| 221 |
+
"execution_count": null,
|
| 222 |
+
"metadata": {},
|
| 223 |
+
"outputs": [
|
| 224 |
+
{
|
| 225 |
+
"name": "stdout",
|
| 226 |
+
"output_type": "stream",
|
| 227 |
+
"text": [
|
| 228 |
+
"PASO 1 — Lowercase\n",
|
| 229 |
+
"-----------------------------------------------------------------\n",
|
| 230 |
+
" ANTES : Stephan, Thank you for the video. It takes all the available information into ac\n",
|
| 231 |
+
" DESPUES: stephan, thank you for the video. it takes all the available information into ac\n",
|
| 232 |
+
"\n",
|
| 233 |
+
" ANTES : Body cams should also air what the police run into day to day.\n",
|
| 234 |
+
"Not just when the\n",
|
| 235 |
+
" DESPUES: body cams should also air what the police run into day to day.\n",
|
| 236 |
+
"not just when the\n",
|
| 237 |
+
"\n",
|
| 238 |
+
" ANTES : This is a really sad story. Someone is killed for no reason, racial war increase\n",
|
| 239 |
+
" DESPUES: this is a really sad story. someone is killed for no reason, racial war increase\n",
|
| 240 |
+
"\n"
|
| 241 |
+
]
|
| 242 |
+
}
|
| 243 |
+
],
|
| 244 |
+
"source": [
|
| 245 |
+
"# ── PASO 1: Lowercase ──\n",
|
| 246 |
+
"# Por que: 'BLACK' y 'black' son la misma palabra.\n",
|
| 247 |
+
"# Sin esto el modelo aprende 'BLACK' y 'black' como features distintas.\n",
|
| 248 |
+
"\n",
|
| 249 |
+
"def to_lowercase(text: str) -> str:\n",
|
| 250 |
+
" \"\"\"Convierte el texto a minusculas.\"\"\"\n",
|
| 251 |
+
" return str(text).lower()\n",
|
| 252 |
+
"\n",
|
| 253 |
+
"# Validacion visual\n",
|
| 254 |
+
"print('PASO 1 — Lowercase')\n",
|
| 255 |
+
"print('-' * 65)\n",
|
| 256 |
+
"for ex in df[TEXT_COL].sample(3, random_state=1).tolist():\n",
|
| 257 |
+
" print(f' ANTES : {ex[:60]}')\n",
|
| 258 |
+
" print(f' DESPUES: {to_lowercase(ex)[:60]}')\n",
|
| 259 |
+
" print()"
|
| 260 |
+
]
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"cell_type": "code",
|
| 264 |
+
"execution_count": null,
|
| 265 |
+
"metadata": {},
|
| 266 |
+
"outputs": [
|
| 267 |
+
{
|
| 268 |
+
"name": "stdout",
|
| 269 |
+
"output_type": "stream",
|
| 270 |
+
"text": [
|
| 271 |
+
"PASO 2 — Limpieza Regex\n",
|
| 272 |
+
"-----------------------------------------------------------------\n",
|
| 273 |
+
" ANTES : 'Check this out http://youtube.com/watch?v=abc123 ok?'\n",
|
| 274 |
+
" DESPUES: 'Check this out ok?'\n",
|
| 275 |
+
"\n",
|
| 276 |
+
" ANTES : \"Hey @username you're so stupid\\xa0\\xa0 really\"\n",
|
| 277 |
+
" DESPUES: 'Hey youre so stupid really'\n",
|
| 278 |
+
"\n",
|
| 279 |
+
" ANTES : 'dont\\nyou see?\\n\\nThis is wrong!!!'\n",
|
| 280 |
+
" DESPUES: 'dont you see? This is wrong!!!'\n",
|
| 281 |
+
"\n",
|
| 282 |
+
" ANTES : 'He has 100 guns and 50 knives'\n",
|
| 283 |
+
" DESPUES: 'He has guns and knives'\n",
|
| 284 |
+
"\n"
|
| 285 |
+
]
|
| 286 |
+
}
|
| 287 |
+
],
|
| 288 |
+
"source": [
|
| 289 |
+
"# ── PASO 2: Limpieza con Regex ────────────────────────────────────────────\n",
|
| 290 |
+
"# Por que regex: hay ruido sistematico en comentarios de YouTube.\n",
|
| 291 |
+
"# El EDA mostro: \\xa0 embebidos, saltos de linea, URLs, @menciones.\n",
|
| 292 |
+
"# Orden importante: primero lo mas especifico, luego lo general.\n",
|
| 293 |
+
"\n",
|
| 294 |
+
"def clean_regex(text: str) -> str:\n",
|
| 295 |
+
" \"\"\"Limpieza con expresiones regulares.\"\"\"\n",
|
| 296 |
+
" # URLs completas\n",
|
| 297 |
+
" text = re.sub(r'http\\S+|www\\.\\S+', '', text)\n",
|
| 298 |
+
" # Menciones @usuario\n",
|
| 299 |
+
" text = re.sub(r'@\\w+', '', text)\n",
|
| 300 |
+
" # Saltos de linea y tabulaciones -> espacio\n",
|
| 301 |
+
" text = re.sub(r'[\\n\\t\\r]', ' ', text)\n",
|
| 302 |
+
" # Caracteres no-ASCII: \\xa0, emojis, caracteres especiales\n",
|
| 303 |
+
" text = re.sub(r'[^\\x00-\\x7F]+', ' ', text)\n",
|
| 304 |
+
" # Apostrofes: \"don't\" -> \"dont\", \"it's\" -> \"its\"\n",
|
| 305 |
+
" text = re.sub(r\"'\", '', text)\n",
|
| 306 |
+
" # Numeros solos sin contexto lexico util\n",
|
| 307 |
+
" text = re.sub(r'\\b\\d+\\b', '', text)\n",
|
| 308 |
+
" # Espacios multiples -> uno solo\n",
|
| 309 |
+
" text = re.sub(r'\\s+', ' ', text)\n",
|
| 310 |
+
" return text.strip()\n",
|
| 311 |
+
"\n",
|
| 312 |
+
"# Validacion con casos problematicos reales del dataset (ejemplo)\n",
|
| 313 |
+
"print('PASO 2 — Limpieza Regex')\n",
|
| 314 |
+
"print('-' * 65)\n",
|
| 315 |
+
"test_cases = [\n",
|
| 316 |
+
" 'Check this out http://youtube.com/watch?v=abc123 ok?',\n",
|
| 317 |
+
" 'Hey @username you\\'re so stupid\\xa0\\xa0 really',\n",
|
| 318 |
+
" 'dont\\nyou see?\\n\\nThis is wrong!!!',\n",
|
| 319 |
+
" 'He has 100 guns and 50 knives',\n",
|
| 320 |
+
"]\n",
|
| 321 |
+
"for tc in test_cases:\n",
|
| 322 |
+
" print(f' ANTES : {repr(tc)}')\n",
|
| 323 |
+
" print(f' DESPUES: {repr(clean_regex(tc))}')\n",
|
| 324 |
+
" print()"
|
| 325 |
+
]
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"cell_type": "code",
|
| 329 |
+
"execution_count": null,
|
| 330 |
+
"metadata": {},
|
| 331 |
+
"outputs": [
|
| 332 |
+
{
|
| 333 |
+
"name": "stdout",
|
| 334 |
+
"output_type": "stream",
|
| 335 |
+
"text": [
|
| 336 |
+
"PASO 3+4 — Lematizacion (spaCy) + Filtrado (NLTK)\n",
|
| 337 |
+
"-----------------------------------------------------------------\n",
|
| 338 |
+
" ANTES : black people are being killed by cops\n",
|
| 339 |
+
" DESPUES: black people kill cop\n",
|
| 340 |
+
"\n",
|
| 341 |
+
" ANTES : you are so stupid and racist thug\n",
|
| 342 |
+
" DESPUES: stupid racist thug\n",
|
| 343 |
+
"\n",
|
| 344 |
+
" ANTES : running faster than the police officers\n",
|
| 345 |
+
" DESPUES: run fast police officer\n",
|
| 346 |
+
"\n",
|
| 347 |
+
" ANTES : these cops are criminals and killers\n",
|
| 348 |
+
" DESPUES: cop criminal killer\n",
|
| 349 |
+
"\n"
|
| 350 |
+
]
|
| 351 |
+
}
|
| 352 |
+
],
|
| 353 |
+
"source": [
|
| 354 |
+
"# ── PASO 3 + 4: Lematizacion con spaCy + filtrado con NLTK ───────────────\n",
|
| 355 |
+
"#\n",
|
| 356 |
+
"# Por que spaCy para LEMATIZAR:\n",
|
| 357 |
+
"# Conoce gramatica inglesa real. // 'running' -> 'run' | 'cops' -> 'cop' | 'better' -> 'good'\n",
|
| 358 |
+
"\n",
|
| 359 |
+
"# Por que NLTK para STOPWORDS:\n",
|
| 360 |
+
"# Lista curada de 179 palabras funcionales (the, is, at, which...)\n",
|
| 361 |
+
"# Mas explicita y facil de personalizar que la lista interna de spaCy\n",
|
| 362 |
+
"#\n",
|
| 363 |
+
"# DECISION CRITICA del EDA:\n",
|
| 364 |
+
"# NO anadir 'black', 'white', 'police', 'cop' a stopwords.\n",
|
| 365 |
+
"# Aparecen en ambas clases pero con contexto DISTINTO.\n",
|
| 366 |
+
"# El modelo necesita verlas para discriminar por bigrams.\n",
|
| 367 |
+
"\n",
|
| 368 |
+
"STOP_WORDS = set(stopwords.words('english'))\n",
|
| 369 |
+
"\n",
|
| 370 |
+
"# Stopwords custom: palabras tematicas sin valor discriminante\n",
|
| 371 |
+
"CUSTOM_STOPWORDS = {'youtube', 'video', 'watch', 'like', 'comment', 'channel'}\n",
|
| 372 |
+
"STOP_WORDS = STOP_WORDS | CUSTOM_STOPWORDS\n",
|
| 373 |
+
"\n",
|
| 374 |
+
"MIN_TOKEN_LEN = prep_cfg.get('min_token_length', 2)\n",
|
| 375 |
+
"\n",
|
| 376 |
+
"def lemmatize_and_filter(text: str) -> str:\n",
|
| 377 |
+
" \"\"\"Lematiza con spaCy y filtra stopwords con NLTK.\"\"\"\n",
|
| 378 |
+
" doc = nlp(text)\n",
|
| 379 |
+
" tokens = [\n",
|
| 380 |
+
" token.lemma_\n",
|
| 381 |
+
" for token in doc\n",
|
| 382 |
+
" if not token.is_punct # sin puntuacion\n",
|
| 383 |
+
" and not token.is_space # sin espacios\n",
|
| 384 |
+
" and len(token.text) >= MIN_TOKEN_LEN # tokens de al menos 2 chars\n",
|
| 385 |
+
" and token.lemma_ not in STOP_WORDS # sin stopwords\n",
|
| 386 |
+
" ]\n",
|
| 387 |
+
" return ' '.join(tokens)\n",
|
| 388 |
+
"\n",
|
| 389 |
+
"# Validacion: ver exactamente que hace la lematizacion\n",
|
| 390 |
+
"print('PASO 3+4 — Lematizacion (spaCy) + Filtrado (NLTK)')\n",
|
| 391 |
+
"print('-' * 65)\n",
|
| 392 |
+
"test_texts = [\n",
|
| 393 |
+
" 'black people are being killed by cops',\n",
|
| 394 |
+
" 'you are so stupid and racist thug',\n",
|
| 395 |
+
" 'running faster than the police officers',\n",
|
| 396 |
+
" 'these cops are criminals and killers',\n",
|
| 397 |
+
"]\n",
|
| 398 |
+
"for tt in test_texts:\n",
|
| 399 |
+
" result = lemmatize_and_filter(tt)\n",
|
| 400 |
+
" print(f' ANTES : {tt}')\n",
|
| 401 |
+
" print(f' DESPUES: {result}')\n",
|
| 402 |
+
" print()"
|
| 403 |
+
]
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"cell_type": "code",
|
| 407 |
+
"execution_count": null,
|
| 408 |
+
"metadata": {},
|
| 409 |
+
"outputs": [
|
| 410 |
+
{
|
| 411 |
+
"name": "stdout",
|
| 412 |
+
"output_type": "stream",
|
| 413 |
+
"text": [
|
| 414 |
+
"PIPELINE COMPLETO — Ejemplos reales del dataset\n",
|
| 415 |
+
"======================================================================\n",
|
| 416 |
+
"[NO TOXICO ]\n",
|
| 417 |
+
" CRUDO : You call yourself an anarchist but defend a cop shooting an unarmed civilian. I'm highly disappointe\n",
|
| 418 |
+
" LIMPIO : call anarchist defend cop shoot unarmed civilian highly disappointed beginning refer square know blu\n",
|
| 419 |
+
"\n",
|
| 420 |
+
"[NO TOXICO ]\n",
|
| 421 |
+
" CRUDO : My mother told me the same thing. God Bless this woman.\n",
|
| 422 |
+
" LIMPIO : mother tell I thing god bless woman\n",
|
| 423 |
+
"\n",
|
| 424 |
+
"[NO TOXICO ]\n",
|
| 425 |
+
" CRUDO : Love it I same the saem thing Go Peggy! #stupid \n",
|
| 426 |
+
"Ya Killing ya selves more quicker than STLPD cou\n",
|
| 427 |
+
" LIMPIO : love saem thing go peggy stupid ya kill ya self quick stlpd could ever wakeup\n",
|
| 428 |
+
"\n",
|
| 429 |
+
"[TOXICO ]\n",
|
| 430 |
+
" CRUDO : Next time they do that, line up some cars and start making burnout smoke to \"riot gas\" them. Non let\n",
|
| 431 |
+
" LIMPIO : next time line car start make burnout smoke riot gas non lethal eficient double check actualy non le\n",
|
| 432 |
+
"\n",
|
| 433 |
+
"[NO TOXICO ]\n",
|
| 434 |
+
" CRUDO : He was Robbing the Store and Being a Big Man .\n",
|
| 435 |
+
"If you Play with Fire you Will Get Burnt .?\n",
|
| 436 |
+
"The Polic\n",
|
| 437 |
+
" LIMPIO : rob store big man play fire get burn police job\n",
|
| 438 |
+
"\n",
|
| 439 |
+
"[NO TOXICO ]\n",
|
| 440 |
+
" CRUDO : The crazy thing is I thought offices never do anything I want of random people over\n",
|
| 441 |
+
" LIMPIO : crazy thing think office never anything want random people\n",
|
| 442 |
+
"\n"
|
| 443 |
+
]
|
| 444 |
+
}
|
| 445 |
+
],
|
| 446 |
+
"source": [
|
| 447 |
+
"# ── PIPELINE COMPLETO ──\n",
|
| 448 |
+
"\n",
|
| 449 |
+
"def preprocess_text(text: str) -> str:\n",
|
| 450 |
+
" \"\"\"\n",
|
| 451 |
+
" Pipeline completo de preprocesamiento NLP.\n",
|
| 452 |
+
"\n",
|
| 453 |
+
" Pasos:\n",
|
| 454 |
+
" 1. lowercase\n",
|
| 455 |
+
" 2. limpieza regex (URLs, menciones, chars especiales)\n",
|
| 456 |
+
" 3. lematizacion con spaCy\n",
|
| 457 |
+
" 4. filtrado stopwords con NLTK\n",
|
| 458 |
+
"\n",
|
| 459 |
+
" \"\"\"\n",
|
| 460 |
+
" text = to_lowercase(text)\n",
|
| 461 |
+
" text = clean_regex(text)\n",
|
| 462 |
+
" text = lemmatize_and_filter(text)\n",
|
| 463 |
+
" return text\n",
|
| 464 |
+
"\n",
|
| 465 |
+
"# Verificacion con ejemplos reales del dataset\n",
|
| 466 |
+
"print('PIPELINE COMPLETO — Ejemplos reales del dataset')\n",
|
| 467 |
+
"print('=' * 70)\n",
|
| 468 |
+
"for _, row in df.sample(6, random_state=42).iterrows():\n",
|
| 469 |
+
" label = 'TOXICO ' if row[TARGET_BIN] else 'NO TOXICO '\n",
|
| 470 |
+
" print(f'[{label}]')\n",
|
| 471 |
+
" print(f' CRUDO : {row[TEXT_COL][:100]}')\n",
|
| 472 |
+
" print(f' LIMPIO : {preprocess_text(row[TEXT_COL])[:100]}')\n",
|
| 473 |
+
" print()"
|
| 474 |
+
]
|
| 475 |
+
},
|
| 476 |
+
{
|
| 477 |
+
"cell_type": "markdown",
|
| 478 |
+
"metadata": {},
|
| 479 |
+
"source": [
|
| 480 |
+
"## 3. Aplicar el pipeline al dataset completo\n",
|
| 481 |
+
"\n",
|
| 482 |
+
"Procesamos las 1000 filas y validamos que no haya pérdida de información crítica."
|
| 483 |
+
]
|
| 484 |
+
},
|
| 485 |
+
{
|
| 486 |
+
"cell_type": "code",
|
| 487 |
+
"execution_count": 9,
|
| 488 |
+
"metadata": {},
|
| 489 |
+
"outputs": [
|
| 490 |
+
{
|
| 491 |
+
"name": "stdout",
|
| 492 |
+
"output_type": "stream",
|
| 493 |
+
"text": [
|
| 494 |
+
"Procesando dataset completo...\n",
|
| 495 |
+
"Completado: 1000 comentarios procesados\n"
|
| 496 |
+
]
|
| 497 |
+
}
|
| 498 |
+
],
|
| 499 |
+
"source": [
|
| 500 |
+
"print('Procesando dataset completo...')\n",
|
| 501 |
+
"df['clean_text'] = df[TEXT_COL].apply(preprocess_text)\n",
|
| 502 |
+
"print(f'Completado: {len(df)} comentarios procesados')"
|
| 503 |
+
]
|
| 504 |
+
},
|
| 505 |
+
{
|
| 506 |
+
"cell_type": "code",
|
| 507 |
+
"execution_count": 10,
|
| 508 |
+
"metadata": {},
|
| 509 |
+
"outputs": [
|
| 510 |
+
{
|
| 511 |
+
"name": "stdout",
|
| 512 |
+
"output_type": "stream",
|
| 513 |
+
"text": [
|
| 514 |
+
" CRUDO LIMPIO REDUCCION\n",
|
| 515 |
+
"----------------------------------------------------\n",
|
| 516 |
+
" mean 33.8 16.6 50.7%\n",
|
| 517 |
+
" median 19.0 9.0 52.6%\n",
|
| 518 |
+
" min 1.0 1.0 0.0%\n",
|
| 519 |
+
" max 815.0 373.0 54.2%\n",
|
| 520 |
+
"\n",
|
| 521 |
+
"Comentarios vacios tras limpieza : 0\n"
|
| 522 |
+
]
|
| 523 |
+
}
|
| 524 |
+
],
|
| 525 |
+
"source": [
|
| 526 |
+
"# Estadisticas antes vs despues\n",
|
| 527 |
+
"df['tokens_raw'] = df[TEXT_COL].str.split().str.len()\n",
|
| 528 |
+
"df['tokens_clean'] = df['clean_text'].str.split().str.len()\n",
|
| 529 |
+
"\n",
|
| 530 |
+
"print(f\"{'':20} {'CRUDO':>8} {'LIMPIO':>8} {'REDUCCION':>11}\")\n",
|
| 531 |
+
"print('-' * 52)\n",
|
| 532 |
+
"for stat in ['mean', 'median', 'min', 'max']:\n",
|
| 533 |
+
" raw = getattr(df['tokens_raw'], stat)()\n",
|
| 534 |
+
" clean = getattr(df['tokens_clean'], stat)()\n",
|
| 535 |
+
" pct = (1 - clean / raw) * 100 if raw > 0 else 0\n",
|
| 536 |
+
" print(f' {stat:18} {raw:8.1f} {clean:8.1f} {pct:10.1f}%')\n",
|
| 537 |
+
"\n",
|
| 538 |
+
"print()\n",
|
| 539 |
+
"empty_after = (df['tokens_clean'] == 0).sum()\n",
|
| 540 |
+
"print(f'Comentarios vacios tras limpieza : {empty_after}')"
|
| 541 |
+
]
|
| 542 |
+
},
|
| 543 |
+
{
|
| 544 |
+
"cell_type": "code",
|
| 545 |
+
"execution_count": null,
|
| 546 |
+
"metadata": {},
|
| 547 |
+
"outputs": [
|
| 548 |
+
{
|
| 549 |
+
"data": {
|
| 550 |
+
"image/png": "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VapUkaQLiq24LuZ5Oz4+3nz/xRdf6O2335bVatWYMWMKbd+mTRs1bdpULVu2VOXKlXX8+HFt377drM/OzlZkZKS6dOmiRYsWadeuXbrppps0aNAghYaG6rffftN3332nDz/8UGvXrlW1atUu6t6By0KZroAIwGcKW+ja4zm1WG/dunULLPp7+tWnTx+P2+02258uP73ZREZGhqdJkyYFNoM4evSop2HDhkX2mz+OC93oo0OHDkX2GR0d7bWQ9Jnxnsu4cePOGW9RCynnN3r06ALnV6pUydwYJP/9ZGdnexo3blygfe3atQuNPysry3P99dcXGWfbtm092dnZ53W/69ev9zrXbrcXWBR70KBBRV4rMDDQs2vXrrNeo7gbl4wfP94sz7/w9uzZs8+778jISE9cXFyBuG+88Ubz77qov78L/W8DAIDS5q/Pc4Up7sYl5/Mcca6+4+PjPWFhYQXi/vvf/272UdTz4q5du8wNNQp7jRo1ymw7efLkIttZLBbPV199ddbPKP9959/47vTLZrN53feFxHa27/tcLuZ5O/9mM6dfbdq0KfS5smbNmkVep379+p68vDyPx+Px/Pbbb4U+L+Z/nf47AK50TDcG/FxkZKTWrVun0aNHq06dOnI6nQoODtY111yjWbNmad68eWedQhAcHKyFCxcqNjZWkvT6669r3LhxioiI0Nq1azV58mQ1btxYgYGBCgoKUu3atXXLLbfo7bffVuvWrYsV8/3336/bb79dNWvWVEhIiGw2mypXrqzk5GR99dVXXtN9L9TEiRP16aefqnPnzipfvrzsdrtiY2N18803e03BPZdx48bpnnvuUUREhPkL6Zo1awqNzeFwaOnSpUpOTlZYWJjCwsJ02223ea2vkp/T6dTSpUs1depUNWrUSIGBgQoICFDDhg01ZcoULVmyRA6H47zibNmypde0jy5dungtVC2d+sV3wIABqlOnjsLDw2W1WhUVFaVevXrpyy+/POu0Gl8LDQ3Vl19+qR49eig4OFiRkZG677779OGHH55zaszF/rcBAEBZ4XmubFWrVk2rVq1SYmKiAgMDFRMTozFjxmjWrFnnPLdGjRr69ttvNXToUFWvXl12u11hYWFq27at5s+fr6lTp5ptu3btqnvvvVcNGjRQuXLlZLVaFRkZqU6dOunzzz9XmzZtzjvmESNG6KWXXlLNmjXlcDjUtGlTLVy40GsU6IXEdjEu5nn7rbfe0oABA1ShQgVFRESoX79++uSTTwptO3r0aPXs2VPx8fEKCgqS3W5XtWrVdN999+mLL74w1+SsWrWqvvvuO40cOVJ169ZVQECAQkNDVbduXfXv31//+9//zNGWwJXO8HjOYyVWAAAuIxcyzRwAAOBc9uzZYyaw2rVrV+SPuZeSCRMmaOLEiZKk2bNna+DAgb4NCMAlj5GEAAAAAAAAgJ8jSQgAAAAAAAD4OZKEAAAAAAAAgJ9jTUIAAAAAAADAzzGSEAAAAAAAAPBzJAkBAAAAAAAAP0eSUJLH49GxY8fEzGsAAIArA893AAAAF4YkoaTjx48rPDxcx48fL9PrZmZnKeLB2xXx4O3KzM7yqsvKytMt3Rbrlm6LlZWVV6ZxAQAAXO589XwHAABwuSJJCAAAAAAAAPg5koQAAAAAAACAnyNJCAAAAAAAAPg5koQAAAAAAACAnyNJCAAAAAAAAPg5m68DAADAH3k8HuXl5cnlcvk6FPghu90uq9Xq6zAAAMAlxOVyKTc319dhoBhK6tmOJKEP2a02jep8i/k+P6vVolvvqGm+BwBcOXJycpSSkqITJ074OhT4KcMwFBcXp5CQEF+HAgAAfMzj8Sg1NVVpaWm+DgUXISIiQjExMTIMo9h9GB6Px1OCMV2Wjh07pvDwcKWnpyssLMzX4QAArmBut1s7d+6U1WpVxYoV5XA4LuofcuBCeTweHTp0SCdOnFDt2rWv2BGFPN8BAHB+UlJSlJaWpqioKAUFBfFsepnxeDw6ceKEDh48qIiICFWqVKnYfTGSEACAMpSTkyO3260qVaooKCjI1+HAT1WsWFF79uxRbm7uFZskBAAA5+ZyucwEYfny5X0dDoopMDBQknTw4EFFRUUV+/mOJKEPud1u7TjwhySpTnRlWSyWfHUe/fF7hiSpcpUQWSxk8gHgSpL/f/OBssYIAQAAIMlcg5Afry9/p7/Di/kRmCShD53MzVHC1H9Ikv6Y9oaCnQFmXU6OSyPuXyNJ+u8HHRUQwFcFAAAAAABKHj8gXv5K4jtkGAMAAPCZ4cOHa+DAgb4OAwAAAPB7DE8DAOAScPKkS9lZ7lK9hjPAosDAC5t68NVXX+nJJ5/UunXr5PF4FB8fr+TkZA0fPlwOh6OUIgUAAADK3sqVK3XDDTfo6NGjioiI8HU4ZY4kIQAAl4DsLLe2bM4otUShM8Cihk1CLihJuHDhQvXt21eTJ0/WW2+9pQoVKuinn37S1KlTlZKSovj4eLNtbm6u7HZ7aYQOAACAMubOPC7Picwyu54RFCxLcOj5tT3HtNrx48drwoQJxYrj2muvVUpKisLDw4t1/uWOJCEAAJeI7Cy3Tp4s3dGE58vj8eiBBx7QqFGjNHz4cLO8bt26mjNnjvbs2SPDMPT666/rySef1PHjx7V+/XpVr17d65fX4cOHKy0tTXPmzJEkrV69WkOGDNHu3bvVqVMnlStXzuu6Gzdu1IMPPqht27YpNjZWjz/+uPr27VtGdw0AAABJ8pzIVNbXy+Q+kVHq17IEhSjg2o7SeSYJU1JSzPfz58/XuHHjtGPHDrMsJCSk2LE4HA7FxMQU+/zLHWsSAgCAAnbu3Kndu3efM0H3v//9Txs3btTu3bvP2efRo0d10003aejQoUpLS9Ndd92l//73v2Z9WlqaOnfurD59+ujQoUOaNWuW7r77bq1Zs+ai7wcAAAAXxn0iQ57M46X+utBEZExMjPkKDw+XYRjmcVRUlJ599lnFxcXJ6XSqSZMmWrx4saRTP4J37NhRSUlJ8ng8kqQjR44oLi5O48aNk3RqurFhGEpLSzOvt2bNGiUmJiooKEjlypVTUlKSjh49KknKzs7WAw88oKioKAUEBOi6667Thg0bSuDT9w2ShAAAoIBDhw5JkipXrnzWduPHj1dERISCgoLO2efChQsVGxure++9VzabTT169FD79u3N+k8//VQVK1bUsGHDZLfb1a5dO91xxx164403Lu5mAAAA4BdeeOEF/etf/9IzzzyjH374QUlJSbrpppu0c+dOGYahN954Qxs2bND06dMlSffdd58qV65sJgnPtHnzZnXo0EH169fX2rVr9dVXX6lHjx5yuVySpEceeUQffPCB3njjDX377beqVauWkpKSdOTIkTK755LEdGMfslttGta+h/k+P6vVopturma+BwCgLFWoUEGS9Mcff6hmzZpFtqtatep597l//36vdQwlKT4+XllZWZKkffv2qVq1al71NWrU0OrVq8/7GgAAAPBfzzzzjEaNGqU+ffpIkp5++mmtWLFCzz//vGbMmKHKlSvr3//+t/r376/U1FQtWrRI3333nWy2wtNj06ZNU4sWLTRz5kyz7Oqrr5YkZWZmatasWZozZ466dOkiSXr11Ve1dOlSvfbaaxo5cmQp323JI0noQw6bTZN73llond1uUf9Bdcs4IgAATrnqqqtUrVo1vfPOOxo7dmyR7SyWv37IOr3+y4kTJ8w1CVNSUhQYGChJio2N1W+//eZ1/t69exUVFSVJiouL0549e7zq9+zZo7i4uIu9HQAAAFzhjh07pv3796tNmzZe5W3atNH3339vHt9666366KOPNHXqVM2aNUu1a9cuss/Nmzfr1ltvLbRu165dys3N9bqe3W5Xy5Yt9eOPP17k3fgGQ9TKWGZejv7MzijwyszL8XVoAACYDMPQiy++qKlTp+rFF1/U4cOHJUk///yzBg0aVCDZJ50afVi1alW98cYbcrvdWrFihRYtWmTWd+vWTX/88YdeffVV5eXl6dNPP9UXX3xh1nft2lUHDx7UzJkzlZeXpy+//FJz585V//79S/+GgYvkzjwu16HUi3q5M4/7+jYAALjinThxQps2bZLVatXOnTvP2vb0j93+gpGEZeykK0drD/+mk65cuT0eHTmWrgCrXd2rNVSwzWG2c7s9+vPQSUlShYqBsljOvsU3AODy5wwovd/uitN39+7d9dlnn+mJJ57Q448/LunU9OJ+/fqpUqVKhZ7z+uuva/DgwXrqqafUrVs39enTR7m5uZKkyMhIffzxxxo6dKhGjBihG2+8UcnJyeaaLuXKldNnn32m4cOHa/To0YqNjdWsWbN03XXXFfOugbJzsbtAXujOjgAAwFtYWJhiY2O1Zs0atWvXzixfs2aNWrZsaR4//PDDslgs+uyzz9S1a1d169bNa53s/Bo1aqTly5dr4sSJBepq1qwph8OhNWvWmEvq5ObmasOGDRo+fHjJ3lwZIUnoAydducp05Sg7N1djXp0lSer4xEyvNjk5Lt3/t1NrMP33g44KCOCrAoArmTPAooZNQkr9GhfquuuuM3eEO9PpXeHy69Chg37++eci+0tMTNTWrVuLrG/ZsqW+/vrrC44TuBSc3gWyWOeWcCwAAPijkSNHavz48apZs6aaNGmi2bNna/PmzZo7d66kUxvlvf7661q7dq2aNWumkSNHasCAAfrhhx9Urly5Av2NHj1aDRs21P3336/77rtPDodDK1as0K233qoKFSpo8ODBGjlypCIjI1W1alVNmzZNJ06c0KBBg8r61ksEmScAAC4BgYFWBQZafR0GAAAAIOnUKPey+BHLElRyP5Q/8MADSk9P18MPP6yDBw+qfv36+t///qfatWvr0KFDGjRokCZMmKBmzZpJkiZOnKglS5bovvvu0/z58wv0d9VVV2nJkiUaM2aMWrZsqcDAQLVq1Up9+/aVJE2dOlVut1v9+vXT8ePH1aJFC33++eeFJhwvByQJAQAAAAAAYDKCgk8tg1GG1yuOgQMHauDAgeaxxWLR+PHjNX78+AJtK1asqNTUVK8yu92ujRs3mseJiYkFZsu0a9dOa9asKfT6AQEBmj59uqZPn16s+C81JAnLmMvlUU6OW9l5buXk/pWTz831KD0tV6f/FrOzXGbdyZNuBQSUdaQAAAAAAMAfWYJDWSfXD5EkLGNut5R2NE9HT+QoNy/XLD+enqf9ezKUnXUqcZib+1eSMCeLVWoAAAAAAABQekgS+oDL5TFfZplbys5y6+TJ00lCEoMAAAAAAAAoGxe+zSEAAAAAAACAKwojCX3IMCxqVL2BHFarrIZ3vtZiMVSnXkXZbIYsVsNHEQIAAAAAAMAfkCT0IZvVqvaN2yrc6ZTDZpfybS5utVrU+tqqCgy0yG5nwCcAAAAAAABKD9knAABw3q6++motXLiwVPqeO3eurr322lLpGwAAAMDZMZLQhzwej07mZMnmccnj8RSoy87Kk2QpUAcAuPJk5uXopCunVK8RaHUo2OY4r7aJiYnq1auXhg8f7lW+bdu2UojslOTkZCUnJ5da/wAAAACKRpLQh/JceXrls9mSpC4PvCDJ/lddnlvz5/0gSWqZkOiD6AAAZemkK0drD/+mk67cUuk/0GpXQvn4804SAgAAAPAvTDcGAOAScdKVq0xXTqm8Sir5WK1aNS1YsECSNGfOHDVp0kTjxo1ThQoVFBMTo/nz52vNmjVq0KCBwsPDNWjQILndp9bcXblypSIiIvTiiy+qUqVKiomJ0fjx480R86f7O+3AgQO67bbbVLFiRVWtWlVjx45VXl5eidwHAAAAUJYGDhyoXr16+TqMs2IkIQAAKLatW7fqb3/7m1JTU/XGG2/onnvuUVJSklatWqXs7Gw1bdpUCxYs0M033yxJOn78uL799lvt2rVLe/fu1Y033qgaNWpowIABBfq+4447FBMTo927d+vw4cPq2rWrgoODNWbMmLK+TQAAAL9SFkvh5Hchy+JIpxJub7zxhqZMmaJHH33ULF+wYIH+7//+76KWbZswYYIWLFigzZs3F7uPwrzwwguX/HJyJAkBAECxVaxYUQ888IAkqW/fvvr73/+uQYMGqXz58pKkdu3a6dtvvzWThG63W08//bSCgoJUt25dDR06VG+99VaBJOEff/yhL774QqmpqQoJCVFISIjGjh2rCRMmkCQEAAAoZaW9FE5+xV0WJyAgQE8//bTuvfdelStXrpSiKznh4eG+DuGcmG4MAACKLTo62nwfFBRUaFlGRoZ5HBAQoKioKPM4Pj5ef/zxR4F+9+3bp4CAAK++atSooX379pVo/AAAAChcaS6FUxLL4nTs2FExMTGaMmXKWdt98MEHuvrqq+V0OlWtWjX961//KrLtnDlzNHHiRH3//fcyDEOGYWjOnDmSpL1796pnz54KCQlRWFiYbrvtNh04cECS9NNPPykoKEjz5s0z+3r33XcVGBio7du3Syo43djtdmvatGmqVauWnE6nqlatqieffNKs37Jli9q3b6/AwECVL19e99xzj9dzdWkgSQgAAMpMVlaWDh48aB7v3btXlStXLtAuLi5OWVlZ5oOXJO3Zs0dxcXFlEicAAAAubVarVU899ZRefPHFIn9I3rRpk2677Tb16dNHW7Zs0YQJE/T444+bib8z3X777Xr44Yd19dVXKyUlRSkpKbr99tvldrvVs2dPHTlyRKtWrdLSpUv166+/6vbbb5ck1a1bV88884zuv/9+7d27V/v27dN9992np59+WvXr1y/0WqNHj9bUqVP1+OOPa/v27Zo3b575A3lmZqaSkpJUrlw5bdiwQe+9956WLVumoUOHXvwHdxZMNwYAAIXKy8tTVlaWeWwYxkX3abFYNHr0aL300kvau3evZsyYoQkTJhRoV7lyZd1www36xz/+oZdfflmHDx/Wk08+WejahQAAAPBP//d//6cmTZpo/Pjxeu211wrUP/vss+rQoYMef/xxSdJVV12l7du365///KcGDhxYoH1gYKBCQkJks9kUExNjli9dulRbtmzR7t27VaVKFUnSm2++qauvvlobNmzQNddco/vvv1+LFi3SnXfeKYfDoWuuuUbDhg0rNO7jx4/rhRde0EsvvWQ+39asWVPXXXedJGnevHnKysrSm2++qeDgYEnSSy+9pB49eujpp5/2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XC6XVq5cqSeeeEKTJk06r74BAAAAn083zi8iIkJXXXWVfvnlF8XExCgnJ0dpaWlebQ4cOGBOM4qJidGBAwcK1J+uK8ro0aOVnp5uvn7//feSvREAAK4gkZGR6tixY6Fr1ISEhGjZsmVavny5qlWrpvLly+uOO+4wR/R37NhRkydPVu/evVWpUiW53W41aNCg0OvUr19fY8eOVfv27VW+fHnNnz9fN91003nH+dBDD8lqtSo6OloNGjQw10EsSq9evfTss89qyJAhCg8P1zXXXKMtW7YU2b59+/a69957de2116pWrVpq2LChQkNDzfq//e1vuvPOO3X99derRo0aatq0qVd9//79NWzYMHXv3l0xMTH6/vvv9cknn5jrHT711FPq0qVLkddfvXq1AgMDlZSUpPT0dAUGBiowMNCsX7FiherUqaPQ0FA9+OCDmjlzpjp3vvB1KAEAAOCfDM/5zuEpAxkZGapataomTJigAQMGqGLFinr77bfNRcx37NihunXrau3atWrdurU+++wzde/eXSkpKeZC46+88opGjhypgwcPeq0DdDbHjh1TeHi40tPTS31h75SM43p323YdPZHltXHJ8gde0NGf7Tp50i3pjI1L/pOo6EoBpRoXAKBsZGVlaffu3apevboCAvjfdvhGaf4dTpkyRR9++KF++uknBQYG6tprr9XTTz+tOnXqmG0SExO1atUqr/Puvfdevfzyy+bx3r17NXjwYK1YsUIhISEaMGCApkyZcl6byEhl+3wnSa5DqTqxbIE8mcfP3bgQRnCogjr2krVi0T90AwAAlCafrkn4j3/8Qz169FB8fLz279+v8ePHy2q1qm/fvgoPD9egQYP00EMPKTIyUmFhYRo2bJgSEhLUunVrSVKnTp1Uv3599evXT9OmTVNqaqoee+wxDRky5LwThAAAACg5q1at0pAhQ3TNNdcoLy9PY8aMUadOnbR9+3av9RHvvvtur+nQQUFB5nuXy6Vu3bopJiZGX3/9tVJSUtS/f3/Z7XY99dRTZXo/AAAA/sKnScJ9+/apb9++Onz4sCpWrKjrrrtO69atU8WKFSVJzz33nCwWi3r37q3s7GwlJSVp5syZ5vlWq1ULFy7U4MGDlZCQoODgYA0YMOCyWX/HMAxVi64qu8Uqi+E989tiGKocFyar1ZDlkpoUDgAAULTFixd7Hc+ZM0dRUVHatGmT2rZta5YHBQUVuTzMkiVLtH37di1btkzR0dFq0qSJJk+erFGjRmnChAlyOByleg8AAAD+yKdJwnfeeees9QEBAZoxY4ZmzJhRZJv4+HgtWrSopEMrEzarTb0Suivc6ZTTZpfkNuusNos6JtVWYKBF9jMWrAcAALhcpKenSzq1tmV+c+fO1X//+1/FxMSoR48eevzxx83RhGvXrlXDhg0VHR1ttk9KStLgwYO1bds2NW3atMB1srOzvXaTzr/zNAAAAM7Np0lCAAAAXLncbreGDx+uNm3aeG1Yc8cddyg+Pl6xsbH64YcfNGrUKO3YsUMffvihJCk1NdUrQSjJPD69Kc6ZpkyZookTJ5bSnQAAAFz5SBICAACgVAwZMkRbt27VV1995VV+zz33mO8bNmyoSpUqqUOHDtq1a5dq1qxZrGuNHj1aDz30kHl87NgxValSpXiBAwAA+CGShD6Um5erf382W4aktkP+Jcn+V12uS+/O+0GS1LBJW686AACAS93QoUO1cOFCrV69WnFxcWdt26pVK0nSL7/8opo1ayomJkbffPONV5sDBw5IUpHrGDqdTjauAwAAuAhsieFjea485bryCq/Lcysvz11oHQAAwKXI4/Fo6NCh+uijj/TFF1+oevXq5zxn8+bNkqRKlSpJkhISErRlyxYdPHjQbLN06VKFhYWpfv36pRI3AACAv2MkIQAAAErMkCFDNG/ePH388ccKDQ011xAMDw9XYGCgdu3apXnz5qlr164qX768fvjhB40YMUJt27ZVo0aNJEmdOnVS/fr11a9fP02bNk2pqal67LHHNGTIEEYLAgAAlBJGEgIAgDLVpEkTzZkzR9KpHW6vvfZan8QxYcIE9erVyzwOCQnRli1bSuVaTz31lPr27VsqfV9qZs2apfT0dCUmJqpSpUrma/78+ZIkh8OhZcuWqVOnTqpbt64efvhh9e7dW5988onZh9Vq1cKFC2W1WpWQkKA777xT/fv316RJk3x1WwAAAFc8koQAAKCAxMREGYahZcuWeZX/85//lGEYGj58eIlcJzk5WV9//XWJ9HWxMjIy1LBhw1Lpe8yYMXr77bdLrL+lS5eqWbNmCg0NVf369bV48eIS6/tieTyeQl8DBw6UJFWpUkWrVq3S4cOHlZWVpZ07d2ratGkKCwvz6ic+Pl6LFi3SiRMndOjQIT3zzDOy2ZgEAwAAUFpIEgIAgELVqVNHs2fP9iqbPXu26tat66OIIEm//vqr/u///k+TJk1Senq6pk2bpt69e+vXX3/1dWgAAAC4jJEkBAAAherTp48+++wzpaenS5LWr18v6a+daE/btWuXevTooYoVKyo+Pl5PPPGE3O6/Nt566aWXVKVKFZUvX15jx471OnfOnDlq0qSJefzss8+qdu3aCg0NVc2aNfXSSy+ZdXv27JFhGHrrrbdUq1YtRUREaODAgcrNzZV0aiRgz549FRUVpfDwcLVt21bff//9ed+vYRjmBhoTJkxQ9+7dde+99yo8PFzVq1fXypUrtWDBAtWqVUvlypXzupfT9zFmzBiVL19eVatW1cyZM836M6c2//LLL0pKSlJkZKRq1qyp559//rzjXLx4sZo1a6bu3bvLYrGoe/fuatmypd58883z7gMAAAA4E0lCHzIMQ5XLxyq+YpwsMgrURceEqFJsqAy+JQC44mVmZxX5ysrNOe+2J3OKbnuhIiIi1LlzZ3Oa7Ouvv6677rrLq82JEyfUoUMHdejQQX/88Ye+/PJLvfPOO+YIxC+++EJjx47Vu+++q5SUFEnS1q1bi7xmfHy8vvjiCx07dkz/+c9/NHLkSK1Zs8arzWeffabvvvtO27dv1/LlyzV37lxJktvt1h133KHdu3frwIEDatq0qW677TZ5PJ4LvndJWrJkiZKSknTkyBH169dPd955pz7++GN9//33WrNmjf71r3/p22+/Ndtv3bpVhmEoJSVF8+fP16OPPqrVq1cX6DcvL0/du3dX48aNtX//fn300UeaNm2a5s2bZ7aJiIjQV199VWhcbre7wD253W798MMPxbpPAAAAQGJ3Y5+yWW269fpeCnc65bQ7JP016sJms6hztzoKDLTI4bD6LkgAQJmo/MiAIus61W+qd+991Dyu/dg9OpGTXWjbNrXq69Nh483jRhOH6nDmcUlS2gvzLziuu+66S4899pgGDBigDz74QFu3btWjj/4Vy6effqpy5cqZaxRWrVpVDz74oObNm6dBgwZp7ty5Sk5OVkJCgqRTI+ryjw48U+/evc33N9xwg5KSkrRy5Uq1adPGLB83bpxCQ0MVGhqqzp07a9OmTRo4cKDCwsJ0++23m+0mTpyo6dOna//+/apcufIF33vz5s118803Szo1qnLy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4NXH3r171a1bNwUFBSkqKkojR45UXl7eRccGAAAAAAAA+ItiJQlvvfVWLVmy5KIvHhcXp6lTp2rTpk3auHGj2rdvr549e2rbtm2SpBEjRuiTTz7Re++9p1WrVmn//v26+eabzfNdLpe6deumnJwcff3113rjjTc0Z84cpjsDAAAAAAAAF6BY041r1aqlxx9/XOvWrVPDhg1lt9u96h944IHz6qdHjx5ex08++aRmzZqldevWKS4uTq+99prmzZun9u3bS5Jmz56tevXqad26dWrdurWWLFmi7du3a9myZYqOjlaTJk00efJkjRo1ShMmTJDD4SjO7QEAAAAAAAB+pVhJwldeeUUhISFatWqVVq1a5VVnGMZ5Jwnzc7lceu+995SZmamEhARt2rRJubm56tixo9mmbt26qlq1qtauXavWrVtr7dq1atiwoaKjo802SUlJGjx4sLZt26amTZsW5/bKjNVqVZ92vRVid8hhtZ9RZ1G3m+rK6TRkdxRrwCcAAAAAAABwXoqVfdq9e3eRr19//fWC+tqyZYtCQkLkdDp133336aOPPlL9+vWVmpoqh8OhiIgIr/bR0dFKTU2VJKWmpnolCE/Xn64rSnZ2to4dO+b18gWLYVFMuWhVLh8jq8X7q7BYDFWoGKyo6BBZLEYRPQAAAFx6Vq9erR49eig2NlaGYWjBggVe9QMHDpRhGF6vzp07e7U5cuSIkpOTFRYWpoiICA0aNEgZGRlleBcAAAD+5aKGqOXk5GjHjh0XtVFInTp1tHnzZq1fv16DBw/WgAEDtH379osJ65ymTJmi8PBw81WlSpVSvR4AAIA/yczMVOPGjTVjxowi23Tu3FkpKSnm6+233/aqT05O1rZt27R06VItXLhQq1ev1j333FPaoQMAAPitYk03PnHihIYNG6Y33nhDkvTzzz+rRo0aGjZsmCpXrqxHH330vPtyOByqVauWJKl58+basGGDXnjhBd1+++3KyclRWlqa12jCAwcOKCYmRpIUExOjb775xqu/07sfn25TmNGjR+uhhx4yj48dO+aTRKHL7dJ3u35QgM2m9pVqKn/O1uVy68dtB2W3G2rSIqTMYwMAACiuLl26qEuXLmdt43Q6i3xe+/HHH7V48WJt2LBBLVq0kCS9+OKL6tq1q5555hnFxsaWeMwAAAD+rlgjCUePHq3vv/9eK1euVEBAgFnesWNHzZ8//6ICcrvdys7OVvPmzWW327V8+XKzbseOHdq7d68SEhIkSQkJCdqyZYsOHjxotlm6dKnCwsJUv379Iq/hdDoVFhbm9fIFt9utr7at1bLvv1Se23VGnUebNvyhdV/vkyvP45P4AAAASsvKlSsVFRWlOnXqaPDgwTp8+LBZt3btWkVERJgJQunUc6bFYtH69et9ES4AAMAVr1gjCRcsWKD58+erdevWMoy/1su7+uqrtWvXrvPuZ/To0erSpYuqVq2q48ePa968eVq5cqU+//xzhYeHa9CgQXrooYcUGRmpsLAwDRs2TAkJCWrdurUkqVOnTqpfv7769eunadOmKTU1VY899piGDBkip9NZnFsDAABAKevcubNuvvlmVa9eXbt27dKYMWPUpUsXrV27VlarVampqYqKivI6x2azKTIyssh1p7Ozs5WdnW0e+2rNaQAAgMtVsZKEhw4dKvDgJp1afyZ/0vBcDh48qP79+yslJUXh4eFq1KiRPv/8c914442SpOeee04Wi0W9e/dWdna2kpKSNHPmTPN8q9WqhQsXavDgwUpISFBwcLAGDBigSZMmFee2AAAAUAb69Oljvm/YsKEaNWqkmjVrauXKlerQoUOx+pwyZYomTpxYUiECAAD4nWIlCVu0aKFPP/1Uw4YNkyQzMfif//zHnAp8Pl577bWz1gcEBGjGjBlnXfQ6Pj5eixYtOu9rAgAA4NJSo0YNVahQQb/88os6dOigmJgYr+VkJCkvL09Hjhwpch3DS2XNaQAAgMtVsZKETz31lLp06aLt27crLy9PL7zwgrZv366vv/5aq1atKukYAQAAcAXbt2+fDh8+rEqVKkk6te50WlqaNm3apObNm0uSvvjiC7ndbrVq1arQPpxOJ8vNAAAAXIRibVxy3XXXafPmzcrLy1PDhg21ZMkSRUVFae3ateaDHAAAAPxTRkaGNm/erM2bN0uSdu/erc2bN2vv3r3KyMjQyJEjtW7dOu3Zs0fLly9Xz549VatWLSUlJUmS6tWrp86dO+vuu+/WN998ozVr1mjo0KHq06cPOxsDAACUkmKNJJSkmjVr6tVXXy3JWAAAAHAF2Lhxo2644Qbz+PQ04AEDBmjWrFn64Ycf9MYbbygtLU2xsbHq1KmTJk+e7DUScO7cuRo6dKg6dOhgrlE9ffr0Mr8XAAAAf1GsJOHevXvPWl+1atViBeNvrFarerfpqRCHXQ6r/Yw6i5K6XiWHw5DdUawBnwAAAD6RmJgoj8dTZP3nn39+zj4iIyM1b968kgwLAAAAZ1GsJGG1atXOuouxy+UqdkD+xGJYVKViZYU7nbJaLJLcf9VZDMVUClVgoEUWy/nvGA0AAAAAAABcqGIlCb/77juv49zcXH333Xd69tln9eSTT5ZIYAAAAAAAAADKRrGShI0bNy5Q1qJFC8XGxuqf//ynbr755osOzB+43C5t3bNdATabOlSqJemvEYNut0c//3RIdruhJi1CfBckAAAAAAAArngluthdnTp1tGHDhpLs8ormdru14ocv9dm3K5TrzvOqc7ncWr/2d321eq9ceUWv6QMAAAAAAABcrGKNJDx27JjXscfjUUpKiiZMmKDatWuXSGAAAAAAAAAAykaxkoQREREFNi7xeDyqUqWK3nnnnRIJDAAAAAAAAEDZKFaS8IsvvvBKElosFlWsWFG1atWSzVasLgEAAAAAAAD4SLEyeomJiSUcBgAAAAAAAABfKdbGJVOmTNHrr79eoPz111/X008/fdFBAQAAAAAAACg7xUoS/vvf/1bdunULlF999dV6+eWXLzooAAAAAAAAAGWnWNONU1NTValSpQLlFStWVEpKykUH5S+sFqt6tu6qILtddqv3V2G1WtThxlpyOA3Z7EYRPQAAAAAAAAAXr1hJwipVqmjNmjWqXr26V/maNWsUGxtbIoH5A4vFouox1RTudMpmsUpy56szFFc1XIGBFlmtxRrwCQAAAAAAAJyXYiUJ7777bg0fPly5ublq3769JGn58uV65JFH9PDDD5dogAAAAAAAAABKV7GShCNHjtThw4d1//33KycnR5IUEBCgUaNGafTo0SUa4JXM5Xbpp993KshuU4dKtST9Na3Y7fbo118Oy+6wqEmLEN8FCQAAAAAAgCtesZKEhmHo6aef1uOPP64ff/xRgYGBql27tpxOZ0nHd0Vzu91a+t0XkqQHWneTZDfrXC631nz5myTp1r7xvggPAAAAAAAAfuKiFrtLTU3VkSNHVLNmTTmdTnk8npKKCwAAAAAAAEAZKVaS8PDhw+rQoYOuuuoqde3a1dzReNCgQaxJCAAAAAAAAFxmipUkHDFihOx2u/bu3augoCCz/Pbbb9fixYtLLDgAAAAAAAAA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3/nOd6S8vDxgP6dPn5bx48eL2WyW+Ph4mTdvnrjd7g5uzY2ZOnWqpKamitFolB49esi4ceP8/0GJhH/7Q2mYJAz3OEyePFl69uwpRqNRevXqJZMnT5YTJ07414d7++v93//9n9xxxx1iMplk0KBB8sc//jFgfbifE0VENm7cKAAatUtEG/2gurpa5syZIykpKRIRESF9+/aVn//85+J0Ov1ltNAPtCCcx3ct1Rbn/nCh9TFxU5qKjd1ul29/+9vSo0cPMRgMkpqaKk8++WSjhHu4xobfJUJrLjZnz56V+++/X7p37y4mk0n69+8v8+fPlytXrgTsJxxjw+9foTUVGy33mZZSRETa9VJFIiIiIiIiIiIi6tI4JyEREREREREREZHGMUlIRERERERERESkcUwSEhERERERERERaRyThERERERERERERBrHJCEREREREREREZHGMUlIRERERERERESkcUwSEhERERERERERaRyThERERERERERERBrHJCERURdTUFAARVFw+fLlDj/2r371K9x5550dflwiIiKizsBx1zWnT5+GoigoLi4G0LGxURQFa9asaffjEFHTmCQkoi5nypQpUBQFS5YsCVi+Zs0aKIrSSbW6tXS1QScRERF1TRx33bxwHXfde++9KC8vR7du3dr9WOXl5Rg/fny7H4eImsYkIRF1SREREfjNb36DS5cudXZVWsTlcnV2FYiIiIhuCMddFIzRaERSUlKHJIuTkpJgMpna/ThE1DQmCYmoS8rKykJSUhIWL14cskywX21fffVVpKWl+V9PmTIFkyZNwksvvYTExETExMTghRdegMfjwfz589G9e3fcdtttWLZsWcB+zp07h+9///uIiYlB9+7dMXHiRJw+fbrRfl988UUkJydj4MCBAIBDhw7hn/7pn2A2mxEXF4dp06bBZrM12db169djwIABMJvN+OY3vxlwnHo7duzA2LFjYTab0bt3b8yePRu1tbVB97d8+XI8//zzOHDgABRFgaIoWL58OQDg7NmzmDhxIqxWK6Kjo/H9738f58+fD1m3kydPom/fvpg1axZEBE6nE8888wx69eqFyMhIjB49GgUFBQHHjomJwcaNGzF48GBYrVbk5OSgvLzcX6agoACjRo1CZGQkYmJiMGbMGJw5c6bJGBEREVH74bgrEMdd17a9/nbj+uOtXbsWAwcOhMViwXe/+13Y7Xb8+c9/RlpaGmJjYzF79mx4vV7/ftLS0vDrX/8ajz76KCIjI9GrVy8sXbo04FgNbzdu7r3leJKofTBJSERdkk6nw0svvYQ33ngDX3zxxU3t67PPPsNXX32Fbdu24ZVXXsGiRYvwz//8z4iNjcXu3bsxY8YMTJ8+3X8ct9uN7OxsREVFYfv27di5c6d/0HX9L9ebN29GSUkJNm3ahLVr16K2thbZ2dmIjY3Fnj17sGrVKnz66aeYNWtWyLqdO3cODz/8MB566CEUFxfjJz/5CRYuXBhQ5uTJk8jJycEjjzyCgwcPYuXKldixY0fI/U6ePBnz5s3D0KFDUV5ejvLyckyePBk+nw8TJ05EVVUVtm7dik2bNuHUqVOYPHly0P0cPHgQ9913Hx577DH8/ve/h6IomDVrFgoLC7FixQocPHgQ3/ve95CTk4PS0lL/dna7Hb/73e/w9ttvY9u2bTh79iyeeeYZAIDH48GkSZPwwAMP4ODBgygsLMS0adN4OxMREVEn4rjrGo67mma32/H6669jxYoV2LBhAwoKCvCd73wH69evx/r16/H222/jD3/4A/76178GbPfyyy8jIyMD+/fvx8KFCzFnzhxs2rQp6DGae285niRqR0JE1MXk5eXJxIkTRUTknnvukalTp4qIyOrVq+X609aiRYskIyMjYNv/+I//kNTU1IB9paamitfr9S8bOHCgjB071v/a4/FIZGSkvP/++yIi8vbbb8vAgQPF5/P5yzidTjGbzbJx40b/fhMTE8XpdPrL/PGPf5TY2Fix2Wz+ZevWrRNVVaWioiJoW/Pz82XIkCEByxYsWCAA5NKlSyIi8sQTT8i0adMCymzfvl1UVZW6urqg+w0Wm08++UR0Op2cPXvWv+zIkSMCQIqKigK227lzp8TGxsrvfvc7f9kzZ86ITqeTL7/8MmC/48aNk/z8fBERWbZsmQCQEydO+NcvXbpUEhMTRUTk4sWLAkAKCgqC1puIiIg6FsddHHfVKysrEwCyf/9+ERHZsmVLQGyCHW/69OlisVikpqbGvyw7O1umT5/uf52amio5OTkBx5o8ebKMHz/e/xqArF69WkSaf285niRqP7ySkIi6tN/85jf485//jGPHjt3wPoYOHQpVvXa6S0xMRHp6uv+1TqdDXFwcKisrAQAHDhzAiRMnEBUVBavVCqvViu7du8PhcODkyZP+7dLT02E0Gv2vjx07hoyMDERGRvqXjRkzBj6fDyUlJUHrduzYMYwePTpgWWZmZsDrAwcOYPny5f66WK1WZGdnw+fzoaysrMVxOHbsGHr37o3evXv7lw0ZMgQxMTEB8T179iy+9a1v4bnnnsO8efP8yw8dOgSv14sBAwYE1GXr1q0BcbFYLOjXr5//dc+ePf2x7d69O6ZMmYLs7Gw89NBDeO211wJuiSEiIqLOw3EXx13NaXi8xMREpKWlwWq1Biyrr0O9hnHOzMwM2c+ae285niRqP/rOrgARUVPuv/9+ZGdnIz8/H1OmTAlYp6oqRCRgmdvtbrQPg8EQ8FpRlKDLfD4fAMBms2HEiBF49913G+2rR48e/r+vH7i0J5vNhunTp2P27NmN1qWkpLT58Xr06IHk5GS8//77mDp1KqKjo/310Ol02LdvH3Q6XcA21w8Mg8X2+vdp2bJlmD17NjZs2ICVK1fiF7/4BTZt2oR77rmnzdtCRERELcdxF8ddzWnt+9teOJ4kah9MEhJRl7dkyRLceeed/kmq6/Xo0QMVFRUQEf8cJMXFxTd9vLvuugsrV65EQkKCf6DWEoMHD8by5ctRW1vrH8ju3LkTqqo2qvv123z00UcBy3bt2tWoPkePHkX//v1bXBej0RgwYXT9sc6dO4dz5875f9U+evQoLl++jCFDhvjLmc1mrF27Fg8++CCys7PxySefICoqCsOHD4fX60VlZSXGjh3b4roEM3z4cAwfPhz5+fnIzMzEe++9x0EdERFRF8BxF8dd7aFhnHft2oXBgwcHLdvS97YrtIso3PB2YyLq8tLT05Gbm4vXX389YPk3vvENXLhwAb/97W9x8uRJLF26FB9//PFNHy83Nxfx8fGYOHEitm/fjrKyMhQUFGD27NlNTuadm5uLiIgI5OXl4fDhw9iyZQv+9V//FT/60Y+QmJgYdJsZM2agtLQU8+fPR0lJCd577z3/E/HqLViwAH/7298wa9YsFBcXo7S0FB9++GGTE3OnpaWhrKwMxcXF+Prrr+F0OpGVleWP5d///ncUFRXh8ccfxwMPPICRI0cGbB8ZGYl169ZBr9dj/PjxsNlsGDBgAHJzc/H444/jgw8+QFlZGYqKirB48WKsW7euRbEtKytDfn4+CgsLcebMGXzyyScoLS0NOUgkIiKijsVxF8dd7WHnzp347W9/i+PHj2Pp0qVYtWoV5syZE7Rsc+9tV2oXUbhhkpCIbgkvvPBCo9sWBg8ejP/8z//E0qVLkZGRgaKiIv/T3G6GxWLBtm3bkJKSgocffhiDBw/GE088AYfD0eQv3BaLBRs3bkRVVRXuvvtufPe738W4cePw+9//PuQ2KSkp+N///V+sWbMGGRkZeOutt/DSSy8FlBk2bBi2bt2K48ePY+zYsRg+fDiee+45JCcnh9zvI488gpycHHzzm99Ejx498P7770NRFHz44YeIjY3F/fffj6ysLPTt2xcrV64Mug+r1YqPP/4YIoIJEyagtrYWy5Ytw+OPP4558+Zh4MCBmDRpEvbs2dPi228sFgs+//xzPPLIIxgwYACmTZuGmTNnYvr06S3anoiIiNofx10cd7W1efPmYe/evRg+fDj+/d//Ha+88gqys7ODlm3uve1K7SIKN4o0nFiCiIiIiIiIiKgNpKWlYe7cuZg7d25nV4WImsErCYmIiIiIiIiIiDSOSUIiIiIiIiIiIiKN4+3GREREREREREREGscrCYmIiIiIiIiIiDSOSUIiIiIiIiIiIiKNY5KQiIiIiIiIiIhI45gkJCIiIiIiIiIi0jgmCYmIiIiIiIiIiDSOSUIiIiIiIiIiIiKNY5KQiIiIiIiIiIhI45gkJCIiIiIiIiIi0jgmCYmIiIiIiIiIiDTu/wHz7mIOnsUPTAAAAABJRU5ErkJggg==",
|
| 551 |
+
"text/plain": [
|
| 552 |
+
"<Figure size 1300x500 with 2 Axes>"
|
| 553 |
+
]
|
| 554 |
+
},
|
| 555 |
+
"metadata": {},
|
| 556 |
+
"output_type": "display_data"
|
| 557 |
+
},
|
| 558 |
+
{
|
| 559 |
+
"name": "stdout",
|
| 560 |
+
"output_type": "stream",
|
| 561 |
+
"text": [
|
| 562 |
+
"Guardado en reports/07_tokens_comparativa.png\n"
|
| 563 |
+
]
|
| 564 |
+
}
|
| 565 |
+
],
|
| 566 |
+
"source": [
|
| 567 |
+
"# Visualizacion: distribucion tokens antes vs despues\n",
|
| 568 |
+
"fig, axes = plt.subplots(1, 2, figsize=(13, 5))\n",
|
| 569 |
+
"\n",
|
| 570 |
+
"# Comparativa global\n",
|
| 571 |
+
"axes[0].hist(df['tokens_raw'], bins=40, alpha=0.6, label='Crudo',\n",
|
| 572 |
+
" color='#7F77DD', edgecolor='white')\n",
|
| 573 |
+
"axes[0].hist(df['tokens_clean'], bins=40, alpha=0.6, label='Limpio',\n",
|
| 574 |
+
" color='#5DCAA5', edgecolor='white')\n",
|
| 575 |
+
"axes[0].axvline(df['tokens_raw'].median(), color='#534AB7',\n",
|
| 576 |
+
" linestyle='--', lw=1.5, label=f'Mediana crudo: {df[\"tokens_raw\"].median():.0f}')\n",
|
| 577 |
+
"axes[0].axvline(df['tokens_clean'].median(), color='#0F6E56',\n",
|
| 578 |
+
" linestyle='--', lw=1.5, label=f'Mediana limpio: {df[\"tokens_clean\"].median():.0f}')\n",
|
| 579 |
+
"axes[0].set_title('Tokens: crudo vs limpio', fontweight='bold')\n",
|
| 580 |
+
"axes[0].set_xlabel('Numero de tokens')\n",
|
| 581 |
+
"axes[0].set_ylabel('Frecuencia')\n",
|
| 582 |
+
"axes[0].legend(fontsize=9)\n",
|
| 583 |
+
"\n",
|
| 584 |
+
"# Por clase\n",
|
| 585 |
+
"for clase, color in [('Toxico', '#E8593C'), ('No toxico', '#5DCAA5')]:\n",
|
| 586 |
+
" mask = df[TARGET_BIN] == (clase == 'Toxico')\n",
|
| 587 |
+
" axes[1].hist(df.loc[mask, 'tokens_clean'], bins=30, alpha=0.6,\n",
|
| 588 |
+
" label=clase, color=color, edgecolor='white')\n",
|
| 589 |
+
"axes[1].set_title('Tokens limpios por clase', fontweight='bold')\n",
|
| 590 |
+
"axes[1].set_xlabel('Numero de tokens limpios')\n",
|
| 591 |
+
"axes[1].legend()\n",
|
| 592 |
+
"\n",
|
| 593 |
+
"plt.tight_layout()\n",
|
| 594 |
+
"plt.savefig(PROJECT_ROOT / 'reports' / 'v2' / '07_tokens_comparativa.png',\n",
|
| 595 |
+
" dpi=150, bbox_inches='tight')\n",
|
| 596 |
+
"plt.show()\n",
|
| 597 |
+
"print('💾 Guardado en reports/07_tokens_comparativa.png')"
|
| 598 |
+
]
|
| 599 |
+
},
|
| 600 |
+
{
|
| 601 |
+
"cell_type": "code",
|
| 602 |
+
"execution_count": 12,
|
| 603 |
+
"metadata": {},
|
| 604 |
+
"outputs": [
|
| 605 |
+
{
|
| 606 |
+
"name": "stdout",
|
| 607 |
+
"output_type": "stream",
|
| 608 |
+
"text": [
|
| 609 |
+
"Comentarios con < 3 tokens tras limpieza: 100\n",
|
| 610 |
+
"\n",
|
| 611 |
+
" [NO TOXICO]\n",
|
| 612 |
+
" CRUDO : I agree with the protestor.\n",
|
| 613 |
+
" LIMPIO : \"agree protestor\"\n",
|
| 614 |
+
"\n",
|
| 615 |
+
" [TOXICO]\n",
|
| 616 |
+
" CRUDO : I think ,he kill him\n",
|
| 617 |
+
" LIMPIO : \"think kill\"\n",
|
| 618 |
+
"\n",
|
| 619 |
+
" [NO TOXICO]\n",
|
| 620 |
+
" CRUDO : 1 word: Provocateur........\n",
|
| 621 |
+
" LIMPIO : \"word provocateur\"\n",
|
| 622 |
+
"\n",
|
| 623 |
+
" [NO TOXICO]\n",
|
| 624 |
+
" CRUDO : I LIKE TURTLES!\n",
|
| 625 |
+
" LIMPIO : \"turtle\"\n",
|
| 626 |
+
"\n",
|
| 627 |
+
" [TOXICO]\n",
|
| 628 |
+
" CRUDO : CNN is such Bullcrap!\n",
|
| 629 |
+
" LIMPIO : \"cnn bullcrap\"\n",
|
| 630 |
+
"\n",
|
| 631 |
+
" [TOXICO]\n",
|
| 632 |
+
" CRUDO : You all are some dumb as people\n",
|
| 633 |
+
" LIMPIO : \"dumb people\"\n",
|
| 634 |
+
"\n"
|
| 635 |
+
]
|
| 636 |
+
}
|
| 637 |
+
],
|
| 638 |
+
"source": [
|
| 639 |
+
"# Casos problematicos: comentarios con menos de 3 tokens tras limpieza\n",
|
| 640 |
+
"# Son comentarios que casi desaparecen -> hay que revisarlos\n",
|
| 641 |
+
"short_clean = df[df['tokens_clean'] < 3]\n",
|
| 642 |
+
"print(f'Comentarios con < 3 tokens tras limpieza: {len(short_clean)}')\n",
|
| 643 |
+
"print()\n",
|
| 644 |
+
"for _, row in short_clean.head(6).iterrows():\n",
|
| 645 |
+
" label = 'TOXICO' if row[TARGET_BIN] else 'NO TOXICO'\n",
|
| 646 |
+
" print(f' [{label}]')\n",
|
| 647 |
+
" print(f' CRUDO : {row[TEXT_COL][:80]}')\n",
|
| 648 |
+
" print(f' LIMPIO : \"{row[\"clean_text\"]}\"')\n",
|
| 649 |
+
" print()"
|
| 650 |
+
]
|
| 651 |
+
},
|
| 652 |
+
{
|
| 653 |
+
"cell_type": "markdown",
|
| 654 |
+
"metadata": {},
|
| 655 |
+
"source": [
|
| 656 |
+
"## 4. Registro en MLflow\n",
|
| 657 |
+
"\n",
|
| 658 |
+
"Guardamos exactamente qué configuración usamos.\n",
|
| 659 |
+
"Si mañana cambiamos `min_token_length` o las custom stopwords,\n",
|
| 660 |
+
"podremos comparar qué configuración produjo mejores métricas al modelar.\n",
|
| 661 |
+
"\n",
|
| 662 |
+
"> Para ver el dashboard: `mlflow ui` desde la raíz del proyecto.\n",
|
| 663 |
+
"\n",
|
| 664 |
+
"> mlflow ui --backend-store-uri file:./mlruns --port 5001 "
|
| 665 |
+
]
|
| 666 |
+
},
|
| 667 |
+
{
|
| 668 |
+
"cell_type": "code",
|
| 669 |
+
"execution_count": 13,
|
| 670 |
+
"metadata": {},
|
| 671 |
+
"outputs": [
|
| 672 |
+
{
|
| 673 |
+
"name": "stderr",
|
| 674 |
+
"output_type": "stream",
|
| 675 |
+
"text": [
|
| 676 |
+
"2026/05/11 14:32:35 INFO mlflow.tracking.fluent: Experiment with name 'Youtube_project_experiment' does not exist. Creating a new experiment.\n"
|
| 677 |
+
]
|
| 678 |
+
},
|
| 679 |
+
{
|
| 680 |
+
"name": "stdout",
|
| 681 |
+
"output_type": "stream",
|
| 682 |
+
"text": [
|
| 683 |
+
"MLflow run registrado\n",
|
| 684 |
+
" Run ID : d01f86f75bd749b8b6a14240af14ec0b\n",
|
| 685 |
+
" Experimento: Youtube_project_experiments\n",
|
| 686 |
+
" Ver UI : mlflow ui --backend-store-uri file:///mnt/c/Users/under/Documents/F5/3_Projects/Project_9_Equipo3/Project_YT/mlruns\n"
|
| 687 |
+
]
|
| 688 |
+
}
|
| 689 |
+
],
|
| 690 |
+
"source": [
|
| 691 |
+
"# ── Configuración MLflow ──\n",
|
| 692 |
+
"MLFLOW_DIR = PROJECT_ROOT / 'mlruns'\n",
|
| 693 |
+
"EXPERIMENT_NAME = 'Youtube_project_experiment'\n",
|
| 694 |
+
"\n",
|
| 695 |
+
"mlflow.set_tracking_uri(f\"file:{MLFLOW_DIR}\")\n",
|
| 696 |
+
"mlflow.set_experiment(EXPERIMENT_NAME)\n",
|
| 697 |
+
"\n",
|
| 698 |
+
"\n",
|
| 699 |
+
"with mlflow.start_run(run_name='preprocessing_v2'):\n",
|
| 700 |
+
"\n",
|
| 701 |
+
" # Params: que configuracion usamos\n",
|
| 702 |
+
" mlflow.log_param('spacy_model', 'en_core_web_sm')\n",
|
| 703 |
+
" mlflow.log_param('nltk_stopwords', 'english')\n",
|
| 704 |
+
" mlflow.log_param('custom_stopwords', list(CUSTOM_STOPWORDS))\n",
|
| 705 |
+
" mlflow.log_param('min_token_length', MIN_TOKEN_LEN)\n",
|
| 706 |
+
" mlflow.log_param('remove_urls', prep_cfg['remove_urls'])\n",
|
| 707 |
+
" mlflow.log_param('remove_mentions', prep_cfg['remove_mentions'])\n",
|
| 708 |
+
" mlflow.log_param('lemmatize', prep_cfg['lemmatize'])\n",
|
| 709 |
+
"\n",
|
| 710 |
+
" # Metrics: que produce este preprocesamiento\n",
|
| 711 |
+
" reduction = (1 - df['tokens_clean'].mean() / df['tokens_raw'].mean()) * 100\n",
|
| 712 |
+
" mlflow.log_metric('avg_tokens_raw', round(df['tokens_raw'].mean(), 2))\n",
|
| 713 |
+
" mlflow.log_metric('avg_tokens_clean', round(df['tokens_clean'].mean(), 2))\n",
|
| 714 |
+
" mlflow.log_metric('median_tokens_clean', float(df['tokens_clean'].median()))\n",
|
| 715 |
+
" mlflow.log_metric('token_reduction_pct', round(reduction, 2))\n",
|
| 716 |
+
" mlflow.log_metric('empty_after_clean', int(empty_after))\n",
|
| 717 |
+
" mlflow.log_metric('short_texts_lt3', len(short_clean))\n",
|
| 718 |
+
"\n",
|
| 719 |
+
" # Artefactos: archivos que produce\n",
|
| 720 |
+
" out_csv = PROJECT_ROOT / 'data' / 'processed' / 'v2' /'comments_preprocessed.csv'\n",
|
| 721 |
+
" df[['CommentId', TEXT_COL, 'clean_text', TARGET_BIN] + SUBLABELS].to_csv(\n",
|
| 722 |
+
" out_csv, index=False\n",
|
| 723 |
+
" )\n",
|
| 724 |
+
" mlflow.log_artifact(str(out_csv))\n",
|
| 725 |
+
" mlflow.log_artifact(str(PROJECT_ROOT / 'reports' / 'v2' / '07_tokens_comparativa.png'))\n",
|
| 726 |
+
" mlflow.log_artifact(str(CONFIG_PATH))\n",
|
| 727 |
+
"\n",
|
| 728 |
+
" run_id = mlflow.active_run().info.run_id\n",
|
| 729 |
+
" print(f'MLflow run registrado')\n",
|
| 730 |
+
" print(f' Run ID : {run_id}')\n",
|
| 731 |
+
" print(f' Experimento: Youtube_project_experiments')\n",
|
| 732 |
+
" print(f' Ver UI : mlflow ui --backend-store-uri file://{MLFLOW_DIR}')"
|
| 733 |
+
]
|
| 734 |
+
},
|
| 735 |
+
{
|
| 736 |
+
"cell_type": "markdown",
|
| 737 |
+
"metadata": {},
|
| 738 |
+
"source": [
|
| 739 |
+
"## 5. Conclusiones y decisiones"
|
| 740 |
+
]
|
| 741 |
+
},
|
| 742 |
+
{
|
| 743 |
+
"cell_type": "code",
|
| 744 |
+
"execution_count": 16,
|
| 745 |
+
"metadata": {},
|
| 746 |
+
"outputs": [
|
| 747 |
+
{
|
| 748 |
+
"name": "stdout",
|
| 749 |
+
"output_type": "stream",
|
| 750 |
+
"text": [
|
| 751 |
+
"\n",
|
| 752 |
+
"CONCLUSIONES — PREPROCESAMIENTO\n",
|
| 753 |
+
"=======================================================\n",
|
| 754 |
+
"Pipeline aplicado:\n",
|
| 755 |
+
" 1. lowercase\n",
|
| 756 |
+
" 2. regex: URLs, @menciones, \\xa0, apostrofes, numeros\n",
|
| 757 |
+
" 3. spaCy: lematizacion (en_core_web_sm)\n",
|
| 758 |
+
" 4. NLTK: stopwords english + custom\n",
|
| 759 |
+
"\n",
|
| 760 |
+
"Resultado:\n",
|
| 761 |
+
" Reduccion tokens : 50.7%\n",
|
| 762 |
+
" Tokens mediana : 9 (antes: 19)\n",
|
| 763 |
+
" Comentarios vacios: 0\n",
|
| 764 |
+
"\n",
|
| 765 |
+
"Decisiones clave:\n",
|
| 766 |
+
" NO quitados: black, white, police, cop\n",
|
| 767 |
+
" -> EDA mostro que aparecen en ambas clases con contexto distinto\n",
|
| 768 |
+
" -> el modelo necesita verlas para aprender por bigrams\n",
|
| 769 |
+
" SI quitados: youtube, video, watch, like, comment, channel\n",
|
| 770 |
+
" -> ruido tematico sin valor discriminante\n",
|
| 771 |
+
" spaCy para lemma, no NLTK Stemmer\n",
|
| 772 |
+
" -> stemmer corta letras, lemma entiende gramatica\n",
|
| 773 |
+
"\n",
|
| 774 |
+
"Archivo guardado:\n",
|
| 775 |
+
" data/processed/comments_preprocessed.csv\n",
|
| 776 |
+
"\n",
|
| 777 |
+
"\n"
|
| 778 |
+
]
|
| 779 |
+
}
|
| 780 |
+
],
|
| 781 |
+
"source": [
|
| 782 |
+
"reduction = (1 - df['tokens_clean'].mean() / df['tokens_raw'].mean()) * 100\n",
|
| 783 |
+
"\n",
|
| 784 |
+
"print(f\"\"\"\n",
|
| 785 |
+
"CONCLUSIONES — PREPROCESAMIENTO\n",
|
| 786 |
+
"{'='*55}\n",
|
| 787 |
+
"Pipeline aplicado:\n",
|
| 788 |
+
" 1. lowercase\n",
|
| 789 |
+
" 2. regex: URLs, @menciones, \\\\xa0, apostrofes, numeros\n",
|
| 790 |
+
" 3. spaCy: lematizacion (en_core_web_sm)\n",
|
| 791 |
+
" 4. NLTK: stopwords english + custom\n",
|
| 792 |
+
"\n",
|
| 793 |
+
"Resultado:\n",
|
| 794 |
+
" Reduccion tokens : {reduction:.1f}%\n",
|
| 795 |
+
" Tokens mediana : {df['tokens_clean'].median():.0f} (antes: {df['tokens_raw'].median():.0f})\n",
|
| 796 |
+
" Comentarios vacios: {empty_after}\n",
|
| 797 |
+
"\n",
|
| 798 |
+
"Decisiones clave:\n",
|
| 799 |
+
" NO quitados: black, white, police, cop\n",
|
| 800 |
+
" -> EDA mostro que aparecen en ambas clases con contexto distinto\n",
|
| 801 |
+
" -> el modelo necesita verlas para aprender por bigrams\n",
|
| 802 |
+
" SI quitados: youtube, video, watch, like, comment, channel\n",
|
| 803 |
+
" -> ruido tematico sin valor discriminante\n",
|
| 804 |
+
" spaCy para lemma, no NLTK Stemmer\n",
|
| 805 |
+
" -> stemmer corta letras, lemma entiende gramatica\n",
|
| 806 |
+
"\n",
|
| 807 |
+
"Archivo guardado:\n",
|
| 808 |
+
" data/processed/comments_preprocessed.csv\n",
|
| 809 |
+
"\n",
|
| 810 |
+
"\"\"\")"
|
| 811 |
+
]
|
| 812 |
+
}
|
| 813 |
+
],
|
| 814 |
+
"metadata": {
|
| 815 |
+
"kernelspec": {
|
| 816 |
+
"display_name": "py310",
|
| 817 |
+
"language": "python",
|
| 818 |
+
"name": "python3"
|
| 819 |
+
},
|
| 820 |
+
"language_info": {
|
| 821 |
+
"codemirror_mode": {
|
| 822 |
+
"name": "ipython",
|
| 823 |
+
"version": 3
|
| 824 |
+
},
|
| 825 |
+
"file_extension": ".py",
|
| 826 |
+
"mimetype": "text/x-python",
|
| 827 |
+
"name": "python",
|
| 828 |
+
"nbconvert_exporter": "python",
|
| 829 |
+
"pygments_lexer": "ipython3",
|
| 830 |
+
"version": "3.10.20"
|
| 831 |
+
}
|
| 832 |
+
},
|
| 833 |
+
"nbformat": 4,
|
| 834 |
+
"nbformat_minor": 4
|
| 835 |
+
}
|