Adding Lab2.

This commit is contained in:
2026-08-25 12:19:45 -06:00
parent a3f00b0d60
commit 548746321e
23 changed files with 2482 additions and 3 deletions
+135
View File
@@ -0,0 +1,135 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Inferencia configurable\n",
"\n",
"Este notebook recibe un CSV con las columnas id, x1, x2, x3 y x4. Carga los pesos desde un diccionario que declara explícitamente la ruta y arquitectura, estandariza con los valores guardados y produce un CSV de predicciones.\n",
"\n",
"La ruta configurada por defecto es un artefacto del instructor. Para inferencia de estudiante, cambie ruta_pesos a su propio modelo exportado y mantenga una arquitectura coincidente.\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Raíz del laboratorio: /home/aleleba/projects/cursos/Universidad/Procesamiento de Imagenes y Vision por Computadora/Labs/Lab2\n"
]
}
],
"source": [
"from pathlib import Path\n",
"import sys\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"candidatos = [Path.cwd().resolve(), Path.cwd().resolve() / 'dist' / 'laboratorio_clasificacion', Path.cwd().resolve().parent]\n",
"RAIZ = next((ruta for ruta in candidatos if (ruta / 'lib_modelos.py').exists()), None)\n",
"if RAIZ is None:\n",
" raise FileNotFoundError('No se encontró la carpeta laboratorio_clasificacion.')\n",
"sys.path.insert(0, str(RAIZ))\n",
"print('Raíz del laboratorio:', RAIZ)\n",
"from lib_modelos import cargar_csv, cargar_modelo, transformar\n"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"CONFIG_MODELOS = {\n",
" 'referencia_instructor': {\n",
" 'ruta_pesos': RAIZ / 'instructor_privado' / 'artefactos' / 'modelo_mejor.npz',\n",
" 'arquitectura': {'tipo': 'mlp', 'entrada': 4, 'ocultas': [32, 16]},\n",
" 'umbral': 0.405,\n",
" },\n",
" # Modelo propio, exportado en la sección 6 de laboratorio_estudiante.ipynb:\n",
" 'mi_modelo': {\n",
" 'ruta_pesos': RAIZ / 'entrega' / 'modelo_elegido.npz',\n",
" 'arquitectura': {'tipo': 'mlp', 'entrada': 4, 'ocultas': [48, 24]},\n",
" 'umbral': 0.315,\n",
" },\n",
"}\n",
"NOMBRE_MODELO = 'mi_modelo'\n",
"RUTA_ARCHIVO = RAIZ / 'datos_publicos' / 'ejemplo_entrada_inferencia.csv'\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Ejecución\n",
"\n",
"Cambie RUTA_ARCHIVO por el CSV recibido. No incluya la columna etiqueta: inferencia no requiere ni debe leer etiquetas.\n"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Predicciones guardadas en: /home/aleleba/projects/cursos/Universidad/Procesamiento de Imagenes y Vision por Computadora/Labs/Lab2/datos_publicos/ejemplo_entrada_inferencia_predicciones.csv\n",
"Filas procesadas: 12\n"
]
}
],
"source": [
"import csv\n",
"\n",
"config = CONFIG_MODELOS[NOMBRE_MODELO]\n",
"if not config['ruta_pesos'].exists():\n",
" raise FileNotFoundError(f\"No se encontraron los pesos: {config['ruta_pesos']}\")\n",
"if not RUTA_ARCHIVO.exists():\n",
" raise FileNotFoundError('Actualice RUTA_ARCHIVO con el archivo de entrada.')\n",
"\n",
"modelo, media, desviacion = cargar_modelo(config['ruta_pesos'], config['arquitectura'])\n",
"x, ids = cargar_csv(RUTA_ARCHIVO, con_etiqueta=False)\n",
"probabilidades = modelo.probabilidad(transformar(x, media, desviacion))\n",
"predicciones = (probabilidades >= config['umbral']).astype(int)\n",
"\n",
"salida = RUTA_ARCHIVO.with_name(RUTA_ARCHIVO.stem + '_predicciones.csv')\n",
"with salida.open('w', encoding='utf-8', newline='') as archivo:\n",
" escritor = csv.writer(archivo)\n",
" escritor.writerow(['id', 'probabilidad_clase_1', 'prediccion', 'umbral'])\n",
" for ident, p, pred in zip(ids, probabilidades, predicciones):\n",
" escritor.writerow([ident, f'{p:.8f}', int(pred), config['umbral']])\n",
"print('Predicciones guardadas en:', salida)\n",
"print('Filas procesadas:', len(ids))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Verificación de arquitectura\n",
"\n",
"Si se cambia el archivo de pesos, la entrada arquitectura debe coincidir exactamente con el modelo usado al guardarlo: tipo mlp y lista ocultas, o tipo mlp_residual_bn y ancho. El notebook falla de forma visible si las dimensiones son incompatibles.\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.x"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
File diff suppressed because one or more lines are too long
+200
View File
@@ -0,0 +1,200 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Laboratorio resuelto: clasificación tabular con MLP\n",
"\n",
"Esta referencia separa 800/200 de forma estratificada, ajusta el estandarizador sólo en entrenamiento, pondera la clase positiva y elige configuración y umbral con desarrollo. El bloque final de evaluación privada es exclusivamente para el instructor.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"import sys\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"candidatos = [Path.cwd().resolve(), Path.cwd().resolve() / 'dist' / 'laboratorio_clasificacion', Path.cwd().resolve().parent]\n",
"RAIZ = next((ruta for ruta in candidatos if (ruta / 'lib_modelos.py').exists()), None)\n",
"if RAIZ is None:\n",
" raise FileNotFoundError('No se encontró la carpeta laboratorio_clasificacion.')\n",
"sys.path.insert(0, str(RAIZ))\n",
"print('Raíz del laboratorio:', RAIZ)\n",
"from lib_modelos import (cargar_csv, division_estratificada, ajustar_estandarizador, transformar, buscar_hiperparametros, buscar_umbral, metricas, guardar_modelo)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Datos y división\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"x, y, ids = cargar_csv(RAIZ / 'datos_publicos' / 'train_1000_desbalanceado.csv')\n",
"indice_train, indice_dev = division_estratificada(y, proporcion_dev=.20, semilla=31)\n",
"media, desviacion = ajustar_estandarizador(x[indice_train])\n",
"x_train = transformar(x[indice_train], media, desviacion)\n",
"x_dev = transformar(x[indice_dev], media, desviacion)\n",
"y_train, y_dev = y[indice_train], y[indice_dev]\n",
"print('Train/dev:', len(y_train), len(y_dev))\n",
"print('Proporción positiva train/dev:', y_train.mean(), y_dev.mean())\n",
"peso_positivo = (y_train == 0).sum() / (y_train == 1).sum()\n",
"print('Peso positivo:', peso_positivo)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Decisiones para el desbalance\n",
"\n",
"Se usa una BCE ponderada con peso positivo igual a negativos/positivos. Accuracy no basta: un clasificador que siempre predice cero tendría 75% de accuracy y recall nulo. La selección minimiza costo esperado en desarrollo, con costo de falso negativo 5 y costo de falso positivo 1; se reportan también precision, recall y F1.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"configuraciones = [\n",
" {'arquitectura': {'tipo': 'mlp', 'entrada': 4, 'ocultas': [24, 16]},\n",
" 'entrenamiento': {'epocas': 100, 'batch_size': 64, 'learning_rate': .003}, 'costo_fn': 5, 'costo_fp': 1},\n",
" {'arquitectura': {'tipo': 'mlp', 'entrada': 4, 'ocultas': [48, 24]},\n",
" 'entrenamiento': {'epocas': 100, 'batch_size': 64, 'learning_rate': .002}, 'costo_fn': 5, 'costo_fp': 1},\n",
" {'arquitectura': {'tipo': 'mlp_residual_bn', 'entrada': 4, 'ancho': 24},\n",
" 'entrenamiento': {'epocas': 100, 'batch_size': 64, 'learning_rate': .002}, 'costo_fn': 5, 'costo_fp': 1},\n",
" {'arquitectura': {'tipo': 'mlp_residual_bn', 'entrada': 4, 'ancho': 40},\n",
" 'entrenamiento': {'epocas': 100, 'batch_size': 64, 'learning_rate': .0015}, 'costo_fn': 5, 'costo_fp': 1},\n",
"]\n",
"mejor, resultados = buscar_hiperparametros(configuraciones, x_train, y_train, x_dev, y_dev, semilla=41)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"for n, r in enumerate(resultados, 1):\n",
" a = r['configuracion']['arquitectura']\n",
" print(n, a, 'costo=', r['costo'], 'umbral=', round(r['umbral'], 3),\n",
" 'F1=', round(r['f1'], 3), 'recall=', round(r['recall'], 3),\n",
" 'precision=', round(r['precision'], 3))\n",
"print('Selección:', mejor['configuracion']['arquitectura'])\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Curvas y umbral\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"historia = mejor['historia']\n",
"epocas = np.arange(1, len(historia['loss_train']) + 1)\n",
"fig, ax = plt.subplots(1, 2, figsize=(11, 3.5))\n",
"ax[0].plot(epocas, historia['loss_train'], label='train')\n",
"ax[0].plot(epocas, historia['loss_dev'], label='dev')\n",
"ax[0].set(xlabel='Época', ylabel='BCE ponderada', title='Pérdida'); ax[0].legend()\n",
"ax[1].plot(epocas, historia['accuracy_dev'], label='accuracy dev')\n",
"ax[1].plot(epocas, historia['f1_dev'], label='F1 dev')\n",
"ax[1].set(xlabel='Época', ylabel='Métrica', title='Desarrollo'); ax[1].legend()\n",
"plt.show()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"prob_dev = mejor['modelo'].probabilidad(x_dev)\n",
"umbral, recorrido = buscar_umbral(y_dev, prob_dev, costo_fn=5, costo_fp=1)\n",
"plt.figure(figsize=(6, 3.5))\n",
"plt.plot([f['umbral'] for f in recorrido], [f['costo'] for f in recorrido])\n",
"plt.axvline(umbral['umbral'], color='crimson', linestyle='--', label=f\"umbral={umbral['umbral']:.3f}\")\n",
"plt.xlabel('Umbral'); plt.ylabel('Costo esperado en desarrollo'); plt.legend(); plt.show()\n",
"print(umbral)\n",
"print(metricas(y_dev, prob_dev, umbral['umbral']))\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Exportación\n",
"\n",
"La selección se hizo sólo con desarrollo. El siguiente artefacto contiene los pesos, media, desviación y configuración necesaria para reproducir inferencia.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"guardar_modelo(RAIZ / 'entrega_solucion' / 'modelo_elegido.npz', mejor['modelo'],\n",
" mejor['configuracion']['arquitectura'], media, desviacion)\n",
"CONFIGURACION = {\n",
" 'ruta_pesos': str(RAIZ / 'entrega_solucion' / 'modelo_elegido.npz'),\n",
" 'arquitectura': mejor['configuracion']['arquitectura'],\n",
" 'umbral': umbral['umbral'],\n",
"}\n",
"CONFIGURACION\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Evaluación privada — instructor\n",
"\n",
"Ejecute este bloque sólo cuando las decisiones estén cerradas. Nunca se devuelve este CSV al estudiantado.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# BLOQUE DEL INSTRUCTOR\n",
"# x_test, y_test, _ = cargar_csv(RAIZ / 'instructor_privado' / 'test_200_balanceado.csv')\n",
"# prob_test = mejor['modelo'].probabilidad(transformar(x_test, media, desviacion))\n",
"# print(metricas(y_test, prob_test, CONFIGURACION['umbral']))\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"name": "python",
"version": "3.x"
}
},
"nbformat": 4,
"nbformat_minor": 5
}