Adding Lab2.
This commit is contained in:
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
# Laboratorio: clasificación tabular con MLP
|
||||||
|
|
||||||
|
El laboratorio está en español y distingue claramente los materiales públicos de los privados.
|
||||||
|
|
||||||
|
- datos_publicos contiene el único conjunto de entrenamiento que se entrega: 1,000 ejemplos, 750 de clase 0 y 250 de clase 1.
|
||||||
|
- notebooks/laboratorio_estudiante.ipynb es la versión para completar.
|
||||||
|
- notebooks/laboratorio_resuelto.ipynb es la referencia para docencia.
|
||||||
|
- notebooks/inferencia_configurable.ipynb reconstruye un modelo desde un diccionario que indica arquitectura y origen de pesos.
|
||||||
|
- instructor_privado contiene el test final balanceado, los pesos de referencia y la evaluación. No se distribuye.
|
||||||
|
|
||||||
|
Ejecute python3 generar_datos.py para generar los CSV con la ecuación analítica, el término de error y una semilla fija. El código sólo necesita Python 3, NumPy y Matplotlib.
|
||||||
|
|
||||||
|
La entrega del estudiante debe incluir código, una tabla de búsqueda de hiperparámetros, curvas de entrenamiento y desarrollo, métricas, la justificación del umbral y predicciones sobre el CSV que indique el instructor. El test privado nunca se usa para ajustar hiperparámetros ni umbral.
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
# Datos públicos
|
||||||
|
|
||||||
|
El archivo train_1000_desbalanceado.csv contiene 1,000 ejemplos: 750 de la clase 0 y 250 de la clase 1. Las entradas son x1, x2, x3 y x4; etiqueta es el objetivo.
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
id,x1,x2,x3,x4
|
||||||
|
ejemplo_0001,2.16279541,-0.15620382,1.52249089,1.65824076
|
||||||
|
ejemplo_0002,-0.33252016,-1.51252489,-0.74678853,2.48168432
|
||||||
|
ejemplo_0003,-0.67816516,-1.48254252,1.32306735,0.53641452
|
||||||
|
ejemplo_0004,-0.81252663,-0.39666178,0.80564212,-2.38488356
|
||||||
|
ejemplo_0005,1.11282146,0.39086547,1.85731970,-0.78875571
|
||||||
|
ejemplo_0006,-0.85655470,1.90412707,1.44050521,0.88793538
|
||||||
|
ejemplo_0007,-1.64834898,-0.63973431,-0.30206628,1.31444933
|
||||||
|
ejemplo_0008,-1.57711791,1.50301207,1.67408804,1.89669274
|
||||||
|
ejemplo_0009,-1.30573622,-1.21531243,1.38645484,1.79280184
|
||||||
|
ejemplo_0010,-1.47446448,1.32280710,-2.49155307,2.21472376
|
||||||
|
ejemplo_0011,-2.45185030,-1.18520910,2.03686553,-2.34874399
|
||||||
|
ejemplo_0012,-2.49821307,0.66322044,-2.46049594,-0.55338346
|
||||||
|
@@ -0,0 +1,13 @@
|
|||||||
|
id,probabilidad_clase_1,prediccion,umbral
|
||||||
|
ejemplo_0001,0.00557925,0,0.315
|
||||||
|
ejemplo_0002,0.00026550,0,0.315
|
||||||
|
ejemplo_0003,0.00176073,0,0.315
|
||||||
|
ejemplo_0004,0.00001668,0,0.315
|
||||||
|
ejemplo_0005,0.00000370,0,0.315
|
||||||
|
ejemplo_0006,0.02924983,0,0.315
|
||||||
|
ejemplo_0007,0.00000050,0,0.315
|
||||||
|
ejemplo_0008,0.00000003,0,0.315
|
||||||
|
ejemplo_0009,0.00000023,0,0.315
|
||||||
|
ejemplo_0010,0.00183952,0,0.315
|
||||||
|
ejemplo_0011,0.01807133,0,0.315
|
||||||
|
ejemplo_0012,0.92466868,1,0.315
|
||||||
|
+1001
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
"""Genera los datos públicos y privados del laboratorio de forma reproducible."""
|
||||||
|
from __future__ import annotations
|
||||||
|
import csv
|
||||||
|
from pathlib import Path
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
SEMILLA = 20260815
|
||||||
|
COLUMNAS = ("id", "x1", "x2", "x3", "x4", "etiqueta")
|
||||||
|
|
||||||
|
def ecuacion_analitica(x, error):
|
||||||
|
x1, x2, x3, x4 = x.T
|
||||||
|
puntuacion = np.sin(1.25*x1) + 0.72*x2**2 - 0.78*x3 + 0.42*x1*x4 - 0.28*x4**2 + error
|
||||||
|
return (puntuacion > 0.82).astype(np.int64)
|
||||||
|
|
||||||
|
def muestra_por_clase(rng, negativos, positivos):
|
||||||
|
pendientes = {0: negativos, 1: positivos}
|
||||||
|
xs, ys = [], []
|
||||||
|
while any(pendientes.values()):
|
||||||
|
x = rng.uniform(-2.5, 2.5, size=(600, 4))
|
||||||
|
y = ecuacion_analitica(x, rng.normal(0.0, 0.48, size=600))
|
||||||
|
for clase in (0, 1):
|
||||||
|
indice = np.flatnonzero(y == clase)[:pendientes[clase]]
|
||||||
|
if len(indice):
|
||||||
|
xs.append(x[indice]); ys.append(y[indice]); pendientes[clase] -= len(indice)
|
||||||
|
x, y = np.concatenate(xs), np.concatenate(ys)
|
||||||
|
orden = rng.permutation(len(y))
|
||||||
|
return x[orden], y[orden]
|
||||||
|
|
||||||
|
def guardar(ruta, x, y, prefijo):
|
||||||
|
ruta.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with ruta.open("w", encoding="utf-8", newline="") as archivo:
|
||||||
|
escritor = csv.writer(archivo); escritor.writerow(COLUMNAS)
|
||||||
|
for n, (fila, etiqueta) in enumerate(zip(x, y), 1):
|
||||||
|
escritor.writerow([f"{prefijo}_{n:04d}", *[f"{z:.8f}" for z in fila], int(etiqueta)])
|
||||||
|
|
||||||
|
def main():
|
||||||
|
raiz = Path(__file__).resolve().parent
|
||||||
|
rng = np.random.default_rng(SEMILLA)
|
||||||
|
x, y = muestra_por_clase(rng, 750, 250)
|
||||||
|
guardar(raiz/"datos_publicos"/"train_1000_desbalanceado.csv", x, y, "train")
|
||||||
|
ejemplo = raiz/"datos_publicos"/"ejemplo_entrada_inferencia.csv"
|
||||||
|
with ejemplo.open("w", encoding="utf-8", newline="") as archivo:
|
||||||
|
escritor = csv.writer(archivo)
|
||||||
|
escritor.writerow(("id", "x1", "x2", "x3", "x4"))
|
||||||
|
for n, fila in enumerate(x[:12], 1):
|
||||||
|
escritor.writerow([f"ejemplo_{n:04d}", *[f"{z:.8f}" for z in fila]])
|
||||||
|
x, y = muestra_por_clase(rng, 100, 100)
|
||||||
|
guardar(raiz/"instructor_privado"/"test_200_balanceado.csv", x, y, "test")
|
||||||
|
(raiz/"datos_publicos"/"README.md").write_text("# Datos públicos\n\nEl archivo train_1000_desbalanceado.csv contiene 1,000 ejemplos: 750 de la clase 0 y 250 de la clase 1. Las entradas son x1, x2, x3 y x4; etiqueta es el objetivo.\n", encoding="utf-8")
|
||||||
|
(raiz/"instructor_privado"/"README.md").write_text("# Material privado del instructor\n\nNo compartir test_200_balanceado.csv. Tiene 200 ejemplos exactamente balanceados, 100 por clase, para la evaluación final.\n", encoding="utf-8")
|
||||||
|
print("Datos generados: train=1000 (750/250); test privado=200 (100/100).")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
# Material privado del instructor
|
||||||
|
|
||||||
|
No compartir test_200_balanceado.csv. Tiene 200 ejemplos exactamente balanceados, 100 por clase, para la evaluación final.
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
BIN
Binary file not shown.
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"split": {
|
||||||
|
"train": 800,
|
||||||
|
"dev": 200,
|
||||||
|
"clase_1_train": 200,
|
||||||
|
"clase_1_dev": 50
|
||||||
|
},
|
||||||
|
"candidatos": [
|
||||||
|
{
|
||||||
|
"arquitectura": {
|
||||||
|
"tipo": "mlp",
|
||||||
|
"entrada": 4,
|
||||||
|
"ocultas": [
|
||||||
|
32,
|
||||||
|
16
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"costo_dev": 41,
|
||||||
|
"umbral_dev": 0.4049999999999999,
|
||||||
|
"accuracy_dev": 0.895,
|
||||||
|
"precision_dev": 0.7377049180327869,
|
||||||
|
"recall_dev": 0.9,
|
||||||
|
"f1_dev": 0.8108108108108109
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"arquitectura": {
|
||||||
|
"tipo": "mlp_residual_bn",
|
||||||
|
"entrada": 4,
|
||||||
|
"ancho": 32
|
||||||
|
},
|
||||||
|
"costo_dev": 48,
|
||||||
|
"umbral_dev": 0.05,
|
||||||
|
"accuracy_dev": 0.84,
|
||||||
|
"precision_dev": 0.6216216216216216,
|
||||||
|
"recall_dev": 0.92,
|
||||||
|
"f1_dev": 0.7419354838709677
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mejor": {
|
||||||
|
"arquitectura": {
|
||||||
|
"tipo": "mlp",
|
||||||
|
"entrada": 4,
|
||||||
|
"ocultas": [
|
||||||
|
32,
|
||||||
|
16
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"costo_dev": 41,
|
||||||
|
"umbral_dev": 0.4049999999999999,
|
||||||
|
"accuracy_dev": 0.895,
|
||||||
|
"precision_dev": 0.7377049180327869,
|
||||||
|
"recall_dev": 0.9,
|
||||||
|
"f1_dev": 0.8108108108108109
|
||||||
|
},
|
||||||
|
"evaluacion_privada": {
|
||||||
|
"tp": 85,
|
||||||
|
"fp": 13,
|
||||||
|
"fn": 15,
|
||||||
|
"tn": 87,
|
||||||
|
"accuracy": 0.86,
|
||||||
|
"precision": 0.8673469387755102,
|
||||||
|
"recall": 0.85,
|
||||||
|
"especificidad": 0.87,
|
||||||
|
"f1": 0.8585858585858585
|
||||||
|
}
|
||||||
|
}
|
||||||
+201
@@ -0,0 +1,201 @@
|
|||||||
|
id,x1,x2,x3,x4,etiqueta
|
||||||
|
test_0001,-1.45329337,-0.80200976,1.29580787,1.22693757,0
|
||||||
|
test_0002,-0.52045331,-0.68697770,-1.40199533,1.54980569,0
|
||||||
|
test_0003,-0.09167948,-1.15753259,-1.23926112,-2.05578252,0
|
||||||
|
test_0004,-0.95562309,2.23947848,-0.97812000,-0.37391722,1
|
||||||
|
test_0005,1.34148897,1.01976166,-1.83249406,1.26312340,1
|
||||||
|
test_0006,-0.60382765,-0.42498739,1.22335123,-0.31705208,0
|
||||||
|
test_0007,0.18621612,0.41779976,-2.14357433,-2.17898694,1
|
||||||
|
test_0008,2.11219013,2.16090114,1.16426707,-1.84287756,0
|
||||||
|
test_0009,0.82408644,-2.18348522,1.39197130,-2.01817722,1
|
||||||
|
test_0010,-0.68030530,-2.23125657,-2.08941012,1.74167698,1
|
||||||
|
test_0011,-2.35824483,-1.28573773,1.29431772,-0.20780914,0
|
||||||
|
test_0012,2.11852923,-2.36071958,0.70308976,0.18373273,1
|
||||||
|
test_0013,-1.67552723,1.95656447,2.18541988,-1.92263682,0
|
||||||
|
test_0014,-0.20424576,2.00422273,2.41835481,0.82287635,0
|
||||||
|
test_0015,1.62452182,-2.19005084,0.44618990,0.99007682,1
|
||||||
|
test_0016,1.73397093,0.66315434,0.71497331,0.97717974,0
|
||||||
|
test_0017,-2.36006033,0.42995369,-2.19505378,-0.31249831,1
|
||||||
|
test_0018,1.69117922,-1.88231376,-0.55647826,-2.48874896,1
|
||||||
|
test_0019,2.26095193,2.01167391,-1.98860354,-1.30947931,1
|
||||||
|
test_0020,-2.08186391,0.80842148,-0.71459713,-1.17981069,1
|
||||||
|
test_0021,0.60296969,1.05032325,-1.19790752,-2.24469607,0
|
||||||
|
test_0022,0.06976761,-1.26603746,0.97647176,1.16616602,0
|
||||||
|
test_0023,-2.16865011,-0.45891806,-1.28671892,0.66225600,0
|
||||||
|
test_0024,0.49667051,-1.67151035,-1.03456036,-0.44059422,1
|
||||||
|
test_0025,1.03159092,-2.40759248,-1.18394129,-1.25790615,1
|
||||||
|
test_0026,1.57952323,-1.07244028,-2.43149390,0.63345346,1
|
||||||
|
test_0027,0.13735245,-0.43567881,-0.54721837,-1.70363302,0
|
||||||
|
test_0028,-1.90781183,-0.88389181,-0.34347431,1.96026037,0
|
||||||
|
test_0029,0.96159706,1.91297323,1.90110582,-1.28053751,1
|
||||||
|
test_0030,-2.26873577,1.98767750,-2.23958875,0.64968906,1
|
||||||
|
test_0031,-2.00316907,0.71745776,0.86613662,1.76053487,0
|
||||||
|
test_0032,-2.45578718,-0.13125501,1.96488336,-1.84651659,0
|
||||||
|
test_0033,-0.67572968,-0.06126132,1.42320774,0.02233957,0
|
||||||
|
test_0034,1.32482990,2.44076184,0.32482658,1.29013522,1
|
||||||
|
test_0035,0.24476376,0.36764926,0.22911212,1.43762596,0
|
||||||
|
test_0036,0.54969604,-2.13096766,1.10118906,0.42006305,1
|
||||||
|
test_0037,-0.26726884,-1.54382713,-1.44717731,-0.92288292,1
|
||||||
|
test_0038,1.40974942,0.58111562,1.00359615,-0.47467149,0
|
||||||
|
test_0039,0.24965433,-0.98576518,0.10741817,-2.42164401,0
|
||||||
|
test_0040,-2.16194096,-2.07738078,0.36869224,1.89891176,0
|
||||||
|
test_0041,-0.06726050,-0.22152113,2.23315436,1.87187572,0
|
||||||
|
test_0042,0.45718322,-1.90091108,1.57001435,-1.65792686,0
|
||||||
|
test_0043,-2.18459398,1.20082129,-0.77987085,-1.37370548,1
|
||||||
|
test_0044,-1.59159843,1.30176166,2.34034269,-2.01184840,0
|
||||||
|
test_0045,2.25388348,-1.64404019,-1.91145566,2.42391413,1
|
||||||
|
test_0046,2.16611394,-1.48313696,-0.36738261,0.64232959,1
|
||||||
|
test_0047,-0.31113493,1.69809874,2.36299659,-0.48603705,0
|
||||||
|
test_0048,-1.24941210,-0.28803499,-0.86646972,-0.19631142,0
|
||||||
|
test_0049,-1.94501563,-2.41616468,2.07088800,-1.95012774,1
|
||||||
|
test_0050,-1.83257121,-0.88768063,-1.98256403,-0.29783165,1
|
||||||
|
test_0051,0.16047757,-1.25975942,1.61511934,-0.74494231,0
|
||||||
|
test_0052,2.31577261,1.18243490,-1.80013917,1.00554226,1
|
||||||
|
test_0053,-1.09179959,-0.36923188,-1.23969889,2.25487871,0
|
||||||
|
test_0054,-0.14822303,-0.95022517,-2.36470503,-1.84141731,1
|
||||||
|
test_0055,1.77175207,-0.33538042,-2.05550804,-0.01691591,1
|
||||||
|
test_0056,0.19813051,2.17931603,2.10657280,-2.14620972,0
|
||||||
|
test_0057,-0.39470845,1.16613610,-1.13228061,-0.54310162,1
|
||||||
|
test_0058,-0.14970876,0.89414771,0.40800137,1.58418574,0
|
||||||
|
test_0059,2.49857886,-0.04437076,0.61949553,-0.03382795,0
|
||||||
|
test_0060,0.83732642,-0.48623062,-1.97966632,1.30435953,1
|
||||||
|
test_0061,1.55460215,2.32672323,1.74610970,0.74218193,1
|
||||||
|
test_0062,1.78819140,0.59594612,0.85060473,-1.91082888,0
|
||||||
|
test_0063,-2.14346879,2.47045733,-2.44238526,-1.46627921,1
|
||||||
|
test_0064,2.38806503,-1.08699012,1.60109508,-2.40569650,0
|
||||||
|
test_0065,0.59316658,-0.16780638,-0.67948597,-1.07304932,0
|
||||||
|
test_0066,-2.24844187,0.73646848,-0.75090338,-2.16317527,1
|
||||||
|
test_0067,2.03343004,1.96296342,-1.12705154,-0.79836152,1
|
||||||
|
test_0068,1.63543612,0.90086255,2.10038823,0.27145194,0
|
||||||
|
test_0069,2.30425516,-1.42251118,1.03950495,2.12923819,0
|
||||||
|
test_0070,-1.90434601,-0.56559933,-0.68051150,-2.04794981,0
|
||||||
|
test_0071,0.16933848,-1.45869995,0.95382899,0.07099242,1
|
||||||
|
test_0072,-0.62419535,-1.84917292,-0.13990795,-2.33577645,0
|
||||||
|
test_0073,0.92333651,-2.40842454,1.50109107,-1.38579681,1
|
||||||
|
test_0074,-1.69447584,0.02847724,0.38192945,-2.11056830,0
|
||||||
|
test_0075,0.09334503,0.30772728,-0.03146323,-2.05359311,0
|
||||||
|
test_0076,0.01358586,-2.28170631,2.42432526,1.85723762,1
|
||||||
|
test_0077,-1.19217760,1.91295366,-0.98624024,-0.70372065,1
|
||||||
|
test_0078,0.67059249,0.81869234,-0.09079115,-0.09327915,0
|
||||||
|
test_0079,-1.35376536,-0.44382773,-0.94712589,1.78565187,0
|
||||||
|
test_0080,-0.60730785,-1.41680375,0.08145395,-1.36936940,0
|
||||||
|
test_0081,2.35699375,0.78325978,-1.80833117,0.72802736,1
|
||||||
|
test_0082,0.63086133,-1.49935097,-1.70914798,1.44214895,1
|
||||||
|
test_0083,-2.42748607,0.85924177,-2.09692344,0.22377841,1
|
||||||
|
test_0084,-1.31325624,-1.93071210,-0.09472675,0.68914311,0
|
||||||
|
test_0085,1.23908276,0.98596048,2.21314844,0.65154351,1
|
||||||
|
test_0086,0.14961595,-0.24398627,-0.45056574,-0.42777022,0
|
||||||
|
test_0087,1.25871123,0.60615861,-0.69371829,-0.59495777,0
|
||||||
|
test_0088,-0.91227054,0.69118054,2.02635968,1.77828215,0
|
||||||
|
test_0089,-0.83869894,0.76593256,-0.49577409,-0.43492475,0
|
||||||
|
test_0090,-1.66530589,2.44910222,-1.57113334,0.73054060,1
|
||||||
|
test_0091,-1.89585896,2.40074559,0.07165437,-2.24429887,1
|
||||||
|
test_0092,-1.74167063,-0.83882977,0.47400933,1.86572658,0
|
||||||
|
test_0093,-0.49298474,1.03984063,-1.14411905,0.59369068,0
|
||||||
|
test_0094,-1.67642716,-2.10752991,1.89351133,-0.91951711,0
|
||||||
|
test_0095,2.11585366,-0.18961527,0.74863511,0.77890455,1
|
||||||
|
test_0096,0.29991196,-1.96098617,1.48007879,2.48250993,0
|
||||||
|
test_0097,-0.11910194,-0.47242821,-2.24062159,-2.40340404,0
|
||||||
|
test_0098,-0.98286999,1.08827497,0.01598984,0.23765656,0
|
||||||
|
test_0099,-1.29372176,-0.34978669,-2.29364454,1.49697292,0
|
||||||
|
test_0100,-0.90665183,1.13854694,0.47737863,1.50534383,0
|
||||||
|
test_0101,1.75271206,-1.98582639,0.59173025,-2.25588044,0
|
||||||
|
test_0102,0.91463402,0.49026878,1.62948890,-2.45095511,0
|
||||||
|
test_0103,1.69123326,1.41298407,-2.01001064,-2.32060632,0
|
||||||
|
test_0104,-1.67766279,-1.43435384,-2.11647264,2.49811505,0
|
||||||
|
test_0105,-1.80144033,0.45373587,-2.16173209,-0.67985384,1
|
||||||
|
test_0106,0.59391963,-1.99721464,-0.64362664,0.39148548,1
|
||||||
|
test_0107,1.07519274,-1.02326075,1.07132256,-1.34998648,0
|
||||||
|
test_0108,-1.61872105,-2.43797584,-1.98749349,2.03329457,1
|
||||||
|
test_0109,-1.07774354,0.16030905,1.03025212,-0.86615910,0
|
||||||
|
test_0110,-2.38347676,0.49667620,0.08810167,-0.34257172,0
|
||||||
|
test_0111,-2.07833560,0.80057867,-0.96619428,-1.23069979,1
|
||||||
|
test_0112,-1.58705044,-1.06636337,0.35707168,0.97993168,0
|
||||||
|
test_0113,0.68680677,-0.43151628,-2.07827137,0.04313296,1
|
||||||
|
test_0114,0.86882697,-1.41471475,-0.34521398,-2.23708502,0
|
||||||
|
test_0115,-0.92371655,0.72933935,-1.84701534,-1.67426541,1
|
||||||
|
test_0116,1.12927661,-2.20661759,-0.80473702,-0.92109921,1
|
||||||
|
test_0117,0.75063499,-1.12182696,-1.87815189,-1.30570949,1
|
||||||
|
test_0118,-1.54189110,-2.12068811,-0.03445788,2.19694536,0
|
||||||
|
test_0119,1.41119451,2.28233921,1.36710184,0.03226869,1
|
||||||
|
test_0120,1.12335658,0.92576543,2.20734014,-1.00702253,0
|
||||||
|
test_0121,2.36918795,2.02651090,-2.13940262,2.21951023,1
|
||||||
|
test_0122,1.24559857,2.21571139,-1.47016421,0.67513295,1
|
||||||
|
test_0123,0.78132760,0.83636584,-1.49063294,-1.28601044,1
|
||||||
|
test_0124,-0.42653483,-0.20226462,1.33822818,2.21708881,0
|
||||||
|
test_0125,2.09229643,0.26185923,1.91241053,0.87587186,0
|
||||||
|
test_0126,1.11645014,0.21659179,-1.53846650,-1.02953752,1
|
||||||
|
test_0127,1.07644723,0.61617544,1.10957197,2.22305726,0
|
||||||
|
test_0128,-0.53472017,1.74743289,0.43954259,0.91183298,1
|
||||||
|
test_0129,2.06671301,-1.68453471,-2.49727568,-0.45354613,1
|
||||||
|
test_0130,1.30846906,-2.09069057,-0.94337302,-1.38366153,1
|
||||||
|
test_0131,-1.18821619,0.77820417,-0.44047102,0.71405693,0
|
||||||
|
test_0132,1.00825620,1.37613516,-0.60049548,1.77367282,1
|
||||||
|
test_0133,0.99924608,-0.46878608,1.25708870,-1.82238171,0
|
||||||
|
test_0134,1.20923187,0.93323130,-1.07566606,-1.22741123,1
|
||||||
|
test_0135,0.79048230,-0.58437966,-0.24593741,2.22256008,0
|
||||||
|
test_0136,-0.78646328,0.29195737,1.05568528,0.95617044,0
|
||||||
|
test_0137,-1.82975876,-1.79373133,0.19964861,0.26557118,1
|
||||||
|
test_0138,0.07511816,-0.30160901,-1.70564546,1.27121744,1
|
||||||
|
test_0139,1.82232529,0.84608299,1.48812937,0.63265312,0
|
||||||
|
test_0140,1.48304154,-0.23175462,0.29458534,1.65723926,0
|
||||||
|
test_0141,-1.89720659,1.03235315,-0.92870464,-2.41563946,1
|
||||||
|
test_0142,0.70191777,1.57894788,-0.38920993,-1.31113691,1
|
||||||
|
test_0143,2.12997911,0.25546600,-2.11885168,0.97053590,1
|
||||||
|
test_0144,2.08125423,-1.97791338,-1.95593453,-1.43279258,1
|
||||||
|
test_0145,-0.40414829,-0.93995837,2.21613817,-2.14350843,0
|
||||||
|
test_0146,1.49730457,0.83614150,1.95774998,-2.04684336,0
|
||||||
|
test_0147,0.31599577,-1.93663138,1.57165891,0.51237795,1
|
||||||
|
test_0148,-1.12266380,-1.98768988,1.05317509,-0.74157986,0
|
||||||
|
test_0149,2.26349408,2.36703096,-1.36109186,-0.83638554,1
|
||||||
|
test_0150,-0.30491254,1.32350843,0.29807829,0.61762198,1
|
||||||
|
test_0151,-1.46195082,-0.74578199,0.85586293,2.41662407,0
|
||||||
|
test_0152,-1.60419411,-0.45724031,0.85807840,1.59964163,0
|
||||||
|
test_0153,0.64099480,-0.73794623,-0.49725863,-0.72711901,1
|
||||||
|
test_0154,0.74989823,-2.49668144,0.62910560,-0.77756842,1
|
||||||
|
test_0155,0.39371566,-1.92057879,-2.19428423,-1.19302672,1
|
||||||
|
test_0156,-1.05778903,1.08420919,2.27326487,0.98267644,0
|
||||||
|
test_0157,2.40272972,1.57968001,1.23816567,-0.71726643,1
|
||||||
|
test_0158,-1.05083206,-1.80725526,-0.35338123,1.71865559,0
|
||||||
|
test_0159,-0.76141960,1.87287015,-1.64402836,0.58223314,1
|
||||||
|
test_0160,-2.13074779,-0.00527227,-0.61736199,1.17297520,0
|
||||||
|
test_0161,1.20382148,-2.18899859,0.17091663,1.91215298,1
|
||||||
|
test_0162,0.77711986,0.23372772,-0.05422450,-1.59924184,0
|
||||||
|
test_0163,1.50260590,-1.83434449,1.37518338,2.24123250,1
|
||||||
|
test_0164,0.25640917,1.13031102,-1.51621878,2.34800905,1
|
||||||
|
test_0165,0.27086379,-1.30615157,1.20917386,2.20912214,0
|
||||||
|
test_0166,0.05868963,-0.43013314,1.87917447,1.51617368,0
|
||||||
|
test_0167,-2.11811078,0.31449053,0.99168714,1.25147560,0
|
||||||
|
test_0168,-2.39563199,2.24448153,0.78142293,-1.16573941,1
|
||||||
|
test_0169,0.75088988,-0.69924804,-2.46954844,0.74754928,1
|
||||||
|
test_0170,2.40373350,-0.06562730,1.71760271,-1.11570584,0
|
||||||
|
test_0171,-0.22564064,2.45758864,1.28116831,-0.62193694,1
|
||||||
|
test_0172,-2.20774481,1.29075955,-1.18379939,-0.20688917,1
|
||||||
|
test_0173,-0.98214541,-1.32743540,-2.29362761,1.57875279,1
|
||||||
|
test_0174,0.95545335,-2.22974060,-0.17911193,2.19598009,1
|
||||||
|
test_0175,0.88527144,1.16625925,2.09386271,0.18397893,0
|
||||||
|
test_0176,-0.52440010,1.11472336,-2.45654324,2.36389928,0
|
||||||
|
test_0177,-0.47091838,0.03147500,-1.06883465,-2.23172693,0
|
||||||
|
test_0178,-1.44209585,-1.66102789,-1.04403717,0.04743861,1
|
||||||
|
test_0179,-0.75338703,-1.66413783,-1.88477911,2.18903776,1
|
||||||
|
test_0180,2.10103457,1.07289588,-1.27030198,-0.40487401,1
|
||||||
|
test_0181,0.85153441,0.20044776,1.87882761,1.74996884,0
|
||||||
|
test_0182,1.75927453,-1.75921244,1.74975537,-0.82601372,1
|
||||||
|
test_0183,2.49907267,-2.44099830,-1.21443559,2.05344654,1
|
||||||
|
test_0184,1.40000807,2.32155007,2.41148559,1.03830643,1
|
||||||
|
test_0185,-1.89305872,-1.04720188,-1.47374211,-0.70848786,1
|
||||||
|
test_0186,-2.37957805,-1.78963287,2.27290346,0.88734310,0
|
||||||
|
test_0187,-0.78091976,1.02549252,1.76628950,-2.37799837,0
|
||||||
|
test_0188,-0.86625762,-0.30128846,-0.89844486,-2.14972633,0
|
||||||
|
test_0189,-1.82915165,0.61862172,2.16940618,0.20235168,0
|
||||||
|
test_0190,-1.26064826,2.18851343,-0.29151919,-0.06185392,1
|
||||||
|
test_0191,-1.85280012,1.44904159,-2.10045311,-0.62109544,1
|
||||||
|
test_0192,-1.60477761,-1.74361636,-1.80323793,0.82332343,1
|
||||||
|
test_0193,0.42713831,1.06727749,0.37919761,1.44867722,1
|
||||||
|
test_0194,-1.32165112,-1.19786516,-1.98078520,-1.33092447,1
|
||||||
|
test_0195,0.66067093,-0.59922476,0.22879934,1.94027287,0
|
||||||
|
test_0196,0.04861905,0.88783688,0.61098210,-0.92653606,0
|
||||||
|
test_0197,1.77209903,0.06586325,0.17907839,1.02733614,1
|
||||||
|
test_0198,-0.92283097,-1.48616370,2.14012563,0.18272875,0
|
||||||
|
test_0199,0.43965858,2.04316190,-2.22104418,0.74529927,1
|
||||||
|
test_0200,1.31857489,-0.44833159,-1.31362535,0.75549619,1
|
||||||
|
Executable
+121
@@ -0,0 +1,121 @@
|
|||||||
|
"""Implementación didáctica con NumPy de MLP, residual MLP+BN y selección."""
|
||||||
|
from __future__ import annotations
|
||||||
|
import csv, json
|
||||||
|
from pathlib import Path
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
CARACTERISTICAS = ("x1", "x2", "x3", "x4")
|
||||||
|
|
||||||
|
def cargar_csv(ruta, con_etiqueta=True):
|
||||||
|
with Path(ruta).open(encoding="utf-8", newline="") as archivo: filas = list(csv.DictReader(archivo))
|
||||||
|
x = np.array([[float(f[c]) for c in CARACTERISTICAS] for f in filas])
|
||||||
|
ids = [f["id"] for f in filas]
|
||||||
|
return (x, np.array([int(f["etiqueta"]) for f in filas]), ids) if con_etiqueta else (x, ids)
|
||||||
|
|
||||||
|
def division_estratificada(y, proporcion_dev=0.20, semilla=17):
|
||||||
|
rng = np.random.default_rng(semilla); train=[]; dev=[]
|
||||||
|
for c in np.unique(y):
|
||||||
|
i = rng.permutation(np.flatnonzero(y == c)); n = round(len(i)*proporcion_dev)
|
||||||
|
dev.extend(i[:n]); train.extend(i[n:])
|
||||||
|
return rng.permutation(train), rng.permutation(dev)
|
||||||
|
|
||||||
|
def ajustar_estandarizador(x):
|
||||||
|
media=x.mean(0); desviacion=x.std(0)
|
||||||
|
return media, np.where(desviacion < 1e-12, 1.0, desviacion)
|
||||||
|
|
||||||
|
def transformar(x, media, desviacion): return (x-media)/desviacion
|
||||||
|
def sigmoide(z): return 1/(1+np.exp(-np.clip(z, -40, 40)))
|
||||||
|
|
||||||
|
def metricas(y, p, umbral=.5):
|
||||||
|
pred=(p>=umbral).astype(int); tp=int(((pred==1)&(y==1)).sum()); fp=int(((pred==1)&(y==0)).sum())
|
||||||
|
fn=int(((pred==0)&(y==1)).sum()); tn=int(((pred==0)&(y==0)).sum())
|
||||||
|
precision=tp/max(tp+fp,1); recall=tp/max(tp+fn,1); f1=2*precision*recall/max(precision+recall,1e-12)
|
||||||
|
return dict(tp=tp,fp=fp,fn=fn,tn=tn,accuracy=(tp+tn)/len(y),precision=precision,recall=recall,especificidad=tn/max(tn+fp,1),f1=f1)
|
||||||
|
|
||||||
|
def buscar_umbral(y,p,costo_fn=5,costo_fp=1):
|
||||||
|
filas=[]
|
||||||
|
for t in np.linspace(.05,.95,181):
|
||||||
|
m=metricas(y,p,t); filas.append(dict(umbral=float(t),costo=costo_fn*m["fn"]+costo_fp*m["fp"],**m))
|
||||||
|
return min(filas,key=lambda z:(z["costo"],-z["f1"])),filas
|
||||||
|
|
||||||
|
def _bn_adelante(z, entrenando, media, var):
|
||||||
|
if entrenando:
|
||||||
|
mu=z.mean(0,keepdims=True); va=z.var(0,keepdims=True); media[:]=.9*media+.1*mu; var[:]=.9*var+.1*va
|
||||||
|
else: mu,va=media,var
|
||||||
|
inv=1/np.sqrt(va+1e-5); hat=(z-mu)*inv
|
||||||
|
return hat,(hat,inv)
|
||||||
|
def _bn_atras(d,c):
|
||||||
|
hat,inv=c; n=len(d)
|
||||||
|
return inv/n*(n*d-d.sum(0,keepdims=True)-hat*(d*hat).sum(0,keepdims=True))
|
||||||
|
|
||||||
|
class MLP:
|
||||||
|
arquitectura="mlp"
|
||||||
|
def __init__(self, entrada=4, ocultas=(32,16), semilla=0):
|
||||||
|
r=np.random.default_rng(semilla); ds=(entrada,)+tuple(ocultas)+(1,)
|
||||||
|
self.params={f"{v}{i}": (r.normal(0,np.sqrt(2/ds[i]),(ds[i],ds[i+1])) if v=="W" else np.zeros((1,ds[i+1]))) for i in range(len(ds)-1) for v in ("W","b")}
|
||||||
|
def forward(self,x,entrenando=True):
|
||||||
|
a=[x]; z=[]; h=x; n=len(self.params)//2-1
|
||||||
|
for i in range(n): z.append(h@self.params[f"W{i}"]+self.params[f"b{i}"]); h=np.maximum(z[-1],0); a.append(h)
|
||||||
|
return h@self.params[f"W{n}"]+self.params[f"b{n}"],(a,z)
|
||||||
|
def probabilidad(self,x): return sigmoide(self.forward(x,False)[0]).ravel()
|
||||||
|
def perdida_y_gradiente(self,x,y,peso_positivo=1):
|
||||||
|
l,c=self.forward(x); yy=y[:,None]; w=np.where(yy==1,peso_positivo,1); p=sigmoide(l); d=(p-yy)*w/w.sum()
|
||||||
|
loss=np.sum(w*(np.maximum(l,0)-l*yy+np.log1p(np.exp(-np.abs(l)))))/w.sum(); a,z=c; n=len(self.params)//2-1; g={f"W{n}":a[-1].T@d,f"b{n}":d.sum(0,keepdims=True)}; dh=d@self.params[f"W{n}"].T
|
||||||
|
for i in range(n-1,-1,-1):
|
||||||
|
dz=dh*(z[i]>0); g[f"W{i}"]=a[i].T@dz; g[f"b{i}"]=dz.sum(0,keepdims=True); dh=dz@self.params[f"W{i}"].T
|
||||||
|
return float(loss),g
|
||||||
|
|
||||||
|
class MLPResidualBN:
|
||||||
|
arquitectura="mlp_residual_bn"
|
||||||
|
def __init__(self,entrada=4,ancho=32,semilla=0):
|
||||||
|
r=np.random.default_rng(semilla); ini=lambda a,b:r.normal(0,np.sqrt(2/a),(a,b))
|
||||||
|
self.params={"Win":ini(entrada,ancho),"bin":np.zeros((1,ancho)),"W1":ini(ancho,ancho),"b1":np.zeros((1,ancho)),"W2":ini(ancho,ancho),"b2":np.zeros((1,ancho)),"Wout":ini(ancho,1),"bout":np.zeros((1,1))}
|
||||||
|
self.media={k:np.zeros((1,ancho)) for k in ("in","1","2")}; self.var={k:np.ones((1,ancho)) for k in ("in","1","2")}
|
||||||
|
def forward(self,x,entrenando=True):
|
||||||
|
zi=x@self.params["Win"]+self.params["bin"]; ni,ci=_bn_adelante(zi,entrenando,self.media["in"],self.var["in"]); h0=np.maximum(ni,0)
|
||||||
|
z1=h0@self.params["W1"]+self.params["b1"]; n1,c1=_bn_adelante(z1,entrenando,self.media["1"],self.var["1"]); a1=np.maximum(n1,0)
|
||||||
|
z2=a1@self.params["W2"]+self.params["b2"]; n2,c2=_bn_adelante(z2,entrenando,self.media["2"],self.var["2"]); suma=h0+n2; h=np.maximum(suma,0)
|
||||||
|
return h@self.params["Wout"]+self.params["bout"],(x,ni,h0,n1,a1,n2,suma,h,ci,c1,c2)
|
||||||
|
def probabilidad(self,x): return sigmoide(self.forward(x,False)[0]).ravel()
|
||||||
|
def perdida_y_gradiente(self,x,y,peso_positivo=1):
|
||||||
|
l,c=self.forward(x,True); yy=y[:,None]; w=np.where(yy==1,peso_positivo,1); d=(sigmoide(l)-yy)*w/w.sum(); loss=np.sum(w*(np.maximum(l,0)-l*yy+np.log1p(np.exp(-np.abs(l)))))/w.sum()
|
||||||
|
x,ni,h0,n1,a1,n2,suma,h,ci,c1,c2=c; g={"Wout":h.T@d,"bout":d.sum(0,keepdims=True)}; ds=(d@self.params["Wout"].T)*(suma>0); dh0=ds.copy()
|
||||||
|
dz2=_bn_atras(ds,c2); g["W2"]=a1.T@dz2; g["b2"]=dz2.sum(0,keepdims=True); dn1=(dz2@self.params["W2"].T)*(n1>0)
|
||||||
|
dz1=_bn_atras(dn1,c1); g["W1"]=h0.T@dz1; g["b1"]=dz1.sum(0,keepdims=True); dh0+=dz1@self.params["W1"].T
|
||||||
|
dzi=_bn_atras(dh0*(ni>0),ci); g["Win"]=x.T@dzi; g["bin"]=dzi.sum(0,keepdims=True)
|
||||||
|
return float(loss),g
|
||||||
|
|
||||||
|
def crear_modelo(a,semilla=0):
|
||||||
|
if a["tipo"]=="mlp": return MLP(a.get("entrada",4),tuple(a.get("ocultas",[32,16])),semilla)
|
||||||
|
if a["tipo"]=="mlp_residual_bn": return MLPResidualBN(a.get("entrada",4),a.get("ancho",32),semilla)
|
||||||
|
raise ValueError("Arquitectura desconocida")
|
||||||
|
|
||||||
|
def entrenar(modelo,x,y,xd,yd,epocas=120,batch_size=64,learning_rate=.002,peso_positivo=None,semilla=0):
|
||||||
|
peso_positivo=peso_positivo or float((y==0).sum()/max((y==1).sum(),1)); r=np.random.default_rng(semilla); estado={k:[np.zeros_like(v),np.zeros_like(v)] for k,v in modelo.params.items()}; h={k:[] for k in ("loss_train","loss_dev","accuracy_dev","f1_dev")}; paso=0
|
||||||
|
for e in range(epocas):
|
||||||
|
for inicio in r.permutation(np.arange(0,len(y),batch_size)):
|
||||||
|
ind=np.arange(inicio,min(inicio+batch_size,len(y))); _,g=modelo.perdida_y_gradiente(x[ind],y[ind],peso_positivo); paso+=1
|
||||||
|
for k,v in modelo.params.items():
|
||||||
|
m,s=estado[k]; m[:]=.9*m+.1*g[k]; s[:]=.999*s+.001*g[k]**2; v[:]-=learning_rate*(m/(1-.9**paso))/(np.sqrt(s/(1-.999**paso))+1e-8)
|
||||||
|
lt,_=modelo.perdida_y_gradiente(x,y,peso_positivo); ld,_=modelo.perdida_y_gradiente(xd,yd,peso_positivo); met=metricas(yd,modelo.probabilidad(xd))
|
||||||
|
h["loss_train"].append(lt);h["loss_dev"].append(ld);h["accuracy_dev"].append(met["accuracy"]);h["f1_dev"].append(met["f1"])
|
||||||
|
return h
|
||||||
|
|
||||||
|
def buscar_hiperparametros(configs,x,y,xd,yd,semilla=0):
|
||||||
|
filas=[]
|
||||||
|
for i,c in enumerate(configs):
|
||||||
|
m=crear_modelo(c["arquitectura"],semilla+i); h=entrenar(m,x,y,xd,yd,**c["entrenamiento"],semilla=semilla+i); u,_=buscar_umbral(yd,m.probabilidad(xd),c.get("costo_fn",5),c.get("costo_fp",1)); filas.append(dict(configuracion=c,modelo=m,historia=h,**u))
|
||||||
|
return min(filas,key=lambda z:(z["costo"],-z["f1"])),filas
|
||||||
|
|
||||||
|
def guardar_modelo(ruta,modelo,arquitectura,media,desviacion):
|
||||||
|
ruta=Path(ruta);ruta.parent.mkdir(parents=True,exist_ok=True); d={f"param_{k}":v for k,v in modelo.params.items()};d.update(media=media,desviacion=desviacion,arquitectura_json=np.array(json.dumps(arquitectura)))
|
||||||
|
if isinstance(modelo,MLPResidualBN):
|
||||||
|
for k in modelo.media:d[f"media_bn_{k}"]=modelo.media[k];d[f"var_bn_{k}"]=modelo.var[k]
|
||||||
|
np.savez(ruta,**d)
|
||||||
|
|
||||||
|
def cargar_modelo(ruta,arquitectura):
|
||||||
|
d=np.load(ruta,allow_pickle=False); m=crear_modelo(arquitectura)
|
||||||
|
for k in m.params:m.params[k][:]=d[f"param_{k}"]
|
||||||
|
if isinstance(m,MLPResidualBN):
|
||||||
|
for k in m.media:m.media[k][:]=d[f"media_bn_{k}"];m.var[k][:]=d[f"var_bn_{k}"]
|
||||||
|
return m,d["media"],d["desviacion"]
|
||||||
Executable
+135
@@ -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
|
||||||
|
}
|
||||||
Executable
+463
File diff suppressed because one or more lines are too long
Executable
+200
@@ -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
|
||||||
|
}
|
||||||
Executable
BIN
Binary file not shown.
Executable
+131
@@ -0,0 +1,131 @@
|
|||||||
|
\documentclass[10pt]{article}
|
||||||
|
\usepackage[margin=1.8cm]{geometry}
|
||||||
|
\usepackage[utf8]{inputenc}
|
||||||
|
\usepackage[T1]{fontenc}
|
||||||
|
\usepackage[spanish]{babel}
|
||||||
|
\shorthandoff{<>}
|
||||||
|
\usepackage{amsmath,booktabs,array}
|
||||||
|
\usepackage{xcolor}
|
||||||
|
\usepackage{graphicx}
|
||||||
|
\setlength{\parindent}{0pt}
|
||||||
|
\newcommand{\campo}[1]{\underline{\hspace{#1}}}
|
||||||
|
\begin{document}
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
{\LARGE Reporte de laboratorio: clasificación tabular con MLP}\\[0.4em]
|
||||||
|
Nombre: Alejandro Lembke Barrientos\hfill Fecha: 25 de agosto de 2026\\
|
||||||
|
Repositorio o archivo entregado: \texttt{https://gitea.p-lao.com/aleleba/Procesamiento-de-Imagenes-y-Vision-Por-Computadora}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\textbf{Propósito.} Documentar una comparación reproducible entre un MLP convencional y un MLP residual con Batch Normalization, usando una división estratificada 800/200 y un conjunto de entrenamiento desbalanceado. Los valores de este reporte provienen de la ejecución real de \texttt{notebooks/laboratorio\_estudiante.ipynb}.
|
||||||
|
|
||||||
|
\section*{1. Datos, división y control de fuga}
|
||||||
|
\small
|
||||||
|
\begin{tabular}{@{}p{2.4cm}p{5.8cm}p{6.8cm}c@{}}
|
||||||
|
\toprule
|
||||||
|
Elemento & Valor & Evidencia o justificación & Estado\\
|
||||||
|
\midrule
|
||||||
|
Archivo público & train\_1000\_desbalanceado.csv & 1,000 ejemplos & Real\\
|
||||||
|
División & 800 train / 200 dev & Estratificada, semilla 31 & Real\\
|
||||||
|
Clases en train / dev & 600/200 clase 0 y 1 en train; 150/50 en dev & Proporción 75/25 conservada en ambas particiones & Real\\
|
||||||
|
Estandarización & Media y desviación de train & Sin usar dev ni test & Real\\
|
||||||
|
Test privado & No usado durante selección & Sólo evaluación final (a cargo del instructor) & Real\\
|
||||||
|
\bottomrule
|
||||||
|
\end{tabular}
|
||||||
|
\normalsize
|
||||||
|
|
||||||
|
\vspace{0.7em}
|
||||||
|
\textbf{Respuesta.} Describa una fuente posible de fuga de información y cómo la evitó:
|
||||||
|
|
||||||
|
\vspace{0.3em}
|
||||||
|
La fuente de fuga más directa habría sido ajustar la media y la desviación estándar del estandarizador (\texttt{ajustar\_estandarizador}) usando todo el conjunto de 1,000 ejemplos antes de dividir en train/dev, o recalcularlas después de ver las métricas de desarrollo. Se evitó ajustando el estandarizador exclusivamente con \texttt{x[indice\_train]} (800 ejemplos) y aplicando esa misma transformación a dev y, al final, al artefacto exportado; el umbral y la arquitectura también se eligieron mirando solo el costo en desarrollo, sin usar en ningún momento \texttt{test\_200\_balanceado.csv}.
|
||||||
|
|
||||||
|
\vspace{1.0cm}
|
||||||
|
|
||||||
|
\section*{2. Estrategia ante el desbalance}
|
||||||
|
\begin{tabular}{@{}p{3.1cm}p{5.4cm}p{5.3cm}@{}}
|
||||||
|
\toprule
|
||||||
|
Decisión & Valor elegido & Justificación\\
|
||||||
|
\midrule
|
||||||
|
Pérdida & BCE ponderada; peso positivo = 3.0 & Compensa la relación 600/200 de train.\\
|
||||||
|
Selección de modelo & Costo esperado en dev & Falso negativo cuesta 5; falso positivo cuesta 1.\\
|
||||||
|
Métricas reportadas & Precision, recall, F1, especificidad y accuracy & Accuracy sola oculta el desempeño de la clase minoritaria.\\
|
||||||
|
Umbral & Se elige sólo en dev & Se congela antes del test privado.\\
|
||||||
|
\bottomrule
|
||||||
|
\end{tabular}
|
||||||
|
|
||||||
|
\vspace{0.6em}
|
||||||
|
\textbf{Respuesta.} Declare los costos y explique por qué el umbral 0.5 puede no ser adecuado:
|
||||||
|
|
||||||
|
\vspace{0.3em}
|
||||||
|
Se asumió costo de falso negativo = 5 y costo de falso positivo = 1 (dejar pasar un caso positivo real es cinco veces más costoso que una falsa alarma). Con esos costos, el umbral que minimiza el costo esperado en desarrollo resultó ser 0.315, por debajo de 0.5: un umbral de 0.5 favorece la precisión pero deja escapar más falsos negativos, que en este problema son mucho más caros que los falsos positivos adicionales que introduce un umbral más bajo.
|
||||||
|
|
||||||
|
\vspace{0.8cm}
|
||||||
|
|
||||||
|
\section*{3. Búsqueda de hiperparámetros}
|
||||||
|
\small
|
||||||
|
\begin{tabular}{@{}lcccccccc@{}}
|
||||||
|
\toprule
|
||||||
|
ID & Arquitectura & Capas/ancho & LR & Épocas & Costo dev & F1 dev & Recall dev & Umbral\\
|
||||||
|
\midrule
|
||||||
|
A & MLP & [24,16] & 0.003 & 100 & 43 & 0.715 & 0.980 & 0.050\\
|
||||||
|
B & MLP & [48,24] & 0.002 & 100 & 43 & 0.796 & 0.900 & 0.315\\
|
||||||
|
C & Residual + BN & 24 & 0.002 & 100 & 55 & 0.686 & 0.940 & 0.060\\
|
||||||
|
D & Residual + BN & 40 & 0.0015 & 100 & 56 & 0.804 & 0.820 & 0.400\\
|
||||||
|
\bottomrule
|
||||||
|
\end{tabular}
|
||||||
|
\normalsize
|
||||||
|
|
||||||
|
\vspace{0.5em}
|
||||||
|
\textbf{Decisión.} Se elige la configuración de menor costo esperado en desarrollo; A y B empataron en costo (43), por lo que se desempató por mayor F1 en desarrollo, lo que favoreció a B (MLP, capas [48,24], learning rate 0.002, 100 épocas, umbral 0.315).
|
||||||
|
|
||||||
|
\vspace{0.8cm}
|
||||||
|
|
||||||
|
\section*{4. Curvas de entrenamiento y desarrollo}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=\linewidth]{entrega/curvas_entrenamiento.png}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\textbf{Interpretación.} Indique la época o checkpoint seleccionado y explique si observa sobreajuste, subajuste o estabilidad:
|
||||||
|
|
||||||
|
\vspace{0.3em}
|
||||||
|
Se entrenaron las 100 épocas completas y se usó el modelo final (época 100). La pérdida de train baja de forma monótona hasta $\approx 0.09$, mientras que la pérdida de dev baja rápido hasta $\approx 0.27$ (época $\approx 20$) y luego se mantiene estable con una leve subida hacia el final ($\approx 0.30$–$0.33$ después de la época 70), señal de un sobreajuste leve. Accuracy y F1 en dev crecen rápido y se estabilizan alrededor de la época 20–30 (accuracy $\approx 0.88$–$0.91$, F1 $\approx 0.78$–$0.82$), con ruido pero sin caída sostenida, por lo que la configuración es razonablemente estable y no sería indispensable un checkpoint anterior.
|
||||||
|
|
||||||
|
\vspace{0.5cm}
|
||||||
|
|
||||||
|
\section*{5. Selección de umbral y reporte de métricas}
|
||||||
|
\begin{center}
|
||||||
|
\includegraphics[width=0.7\linewidth]{entrega/curva_umbral.png}
|
||||||
|
\end{center}
|
||||||
|
|
||||||
|
\begin{center}
|
||||||
|
\begin{tabular}{@{}lccccc@{}}
|
||||||
|
\toprule
|
||||||
|
Conjunto & Accuracy & Precision & Recall & F1 & Especificidad\\
|
||||||
|
\midrule
|
||||||
|
Desarrollo, umbral 0.315 & 0.885 & 0.714 & 0.900 & 0.796 & 0.880\\
|
||||||
|
Test privado, umbral congelado & \campo{1cm} & \campo{1cm} & \campo{1cm} & \campo{1cm} & \campo{1cm}\\
|
||||||
|
\bottomrule
|
||||||
|
\end{tabular}
|
||||||
|
\end{center}
|
||||||
|
\footnotesize
|
||||||
|
La fila de test privado se deja en blanco a propósito: \texttt{instructor\_privado/test\_200\_balanceado.csv} nunca se usa ni se lee durante el desarrollo del laboratorio; esa evaluación final es exclusiva del instructor.
|
||||||
|
\normalsize
|
||||||
|
|
||||||
|
\vspace{0.5em}
|
||||||
|
\textbf{Matriz de confusión en desarrollo (umbral 0.315).}
|
||||||
|
\[
|
||||||
|
\begin{array}{c|cc}
|
||||||
|
& \text{Pred. 0} & \text{Pred. 1}\\ \hline
|
||||||
|
\text{Real 0} & 132 & 18\\
|
||||||
|
\text{Real 1} & 5 & 45
|
||||||
|
\end{array}
|
||||||
|
\]
|
||||||
|
|
||||||
|
\section*{6. Conclusión reproducible}
|
||||||
|
\begin{enumerate}
|
||||||
|
\item Arquitectura final, pesos y ruta del artefacto: MLP con capas ocultas [48, 24], entrada de dimensión 4; pesos guardados en \texttt{entrega/modelo\_elegido.npz}.
|
||||||
|
\item Umbral congelado y costos asumidos: umbral 0.315, elegido minimizando el costo esperado en desarrollo con costo de falso negativo = 5 y costo de falso positivo = 1.
|
||||||
|
\item Cambio que haría antes de desplegar: repetir la búsqueda de hiperparámetros con más de una semilla por configuración para verificar que la elección de arquitectura y umbral no dependa de una sola inicialización, y monitorear en producción la distribución de \texttt{x1..x4} para detectar drift respecto a los datos de entrenamiento.
|
||||||
|
\end{enumerate}
|
||||||
|
\end{document}
|
||||||
Executable
+61
@@ -0,0 +1,61 @@
|
|||||||
|
"""Ejecuta la solución de referencia y guarda el artefacto para inferencia."""
|
||||||
|
from __future__ import annotations
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from lib_modelos import (
|
||||||
|
ajustar_estandarizador, buscar_hiperparametros, buscar_umbral, cargar_csv,
|
||||||
|
division_estratificada, guardar_modelo, metricas, transformar,
|
||||||
|
)
|
||||||
|
|
||||||
|
RAIZ = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
def fila_publica(resultado):
|
||||||
|
return {
|
||||||
|
"arquitectura": resultado["configuracion"]["arquitectura"],
|
||||||
|
"costo_dev": resultado["costo"], "umbral_dev": resultado["umbral"],
|
||||||
|
"accuracy_dev": resultado["accuracy"], "precision_dev": resultado["precision"],
|
||||||
|
"recall_dev": resultado["recall"], "f1_dev": resultado["f1"],
|
||||||
|
}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
x, y, _ = cargar_csv(RAIZ/"datos_publicos"/"train_1000_desbalanceado.csv")
|
||||||
|
indice_train, indice_dev = division_estratificada(y, .20, semilla=31)
|
||||||
|
media, desviacion = ajustar_estandarizador(x[indice_train])
|
||||||
|
xt = transformar(x[indice_train], media, desviacion)
|
||||||
|
xd = transformar(x[indice_dev], media, desviacion)
|
||||||
|
|
||||||
|
configuraciones = [
|
||||||
|
{"arquitectura": {"tipo": "mlp", "entrada": 4, "ocultas": [32, 16]},
|
||||||
|
"entrenamiento": {"epocas": 100, "batch_size": 64, "learning_rate": .003},
|
||||||
|
"costo_fn": 5, "costo_fp": 1},
|
||||||
|
{"arquitectura": {"tipo": "mlp_residual_bn", "entrada": 4, "ancho": 32},
|
||||||
|
"entrenamiento": {"epocas": 100, "batch_size": 64, "learning_rate": .002},
|
||||||
|
"costo_fn": 5, "costo_fp": 1},
|
||||||
|
]
|
||||||
|
mejor, resultados = buscar_hiperparametros(configuraciones, xt, y[indice_train], xd, y[indice_dev], semilla=41)
|
||||||
|
carpeta = RAIZ/"instructor_privado"/"artefactos"
|
||||||
|
guardar_modelo(carpeta/"modelo_mejor.npz", mejor["modelo"], mejor["configuracion"]["arquitectura"], media, desviacion)
|
||||||
|
|
||||||
|
xpriv, ypriv, _ = cargar_csv(RAIZ/"instructor_privado"/"test_200_balanceado.csv")
|
||||||
|
ppriv = mejor["modelo"].probabilidad(transformar(xpriv, media, desviacion))
|
||||||
|
mpriv = metricas(ypriv, ppriv, mejor["umbral"])
|
||||||
|
resumen = {
|
||||||
|
"split": {"train": int(len(indice_train)), "dev": int(len(indice_dev)), "clase_1_train": int(y[indice_train].sum()), "clase_1_dev": int(y[indice_dev].sum())},
|
||||||
|
"candidatos": [fila_publica(r) for r in resultados],
|
||||||
|
"mejor": fila_publica(mejor),
|
||||||
|
"evaluacion_privada": mpriv,
|
||||||
|
}
|
||||||
|
(carpeta/"resumen_solucion.json").write_text(json.dumps(resumen, indent=2), encoding="utf-8")
|
||||||
|
|
||||||
|
historia = mejor["historia"]; ejes = range(1, len(historia["loss_train"])+1)
|
||||||
|
fig, ax = plt.subplots(1, 2, figsize=(10, 3.5))
|
||||||
|
ax[0].plot(ejes, historia["loss_train"], label="train"); ax[0].plot(ejes, historia["loss_dev"], label="dev")
|
||||||
|
ax[0].set(xlabel="Época", ylabel="Pérdida BCE ponderada", title="Curvas de pérdida"); ax[0].legend()
|
||||||
|
ax[1].plot(ejes, historia["accuracy_dev"], label="Accuracy dev"); ax[1].plot(ejes, historia["f1_dev"], label="F1 dev")
|
||||||
|
ax[1].set(xlabel="Época", ylabel="Métrica", title="Desarrollo"); ax[1].legend()
|
||||||
|
fig.tight_layout(); fig.savefig(carpeta/"curvas_solucion.png", dpi=160); plt.close(fig)
|
||||||
|
print(json.dumps(resumen, indent=2))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -7,6 +7,7 @@ Repositorio con los laboratorios de la asignatura.
|
|||||||
|
|
||||||
## Laboratorios
|
## Laboratorios
|
||||||
|
|
||||||
| # | Laboratorio | Notebook |
|
| # | Laboratorio | Notebook | Reporte |
|
||||||
|---|---|---|
|
|---|---|---|---|
|
||||||
| 1 | MLPs, verosimilitud, entrenamiento y evaluación | [Lab 1 - MLP](Lab1/lab1_mlp.ipynb) |
|
| 1 | MLPs, verosimilitud, entrenamiento y evaluación | [Lab 1 - MLP](Lab1/lab1_mlp.ipynb) | — |
|
||||||
|
| 2 | Clasificación tabular con MLP (desbalance de clases) | [Lab 2 - Clasificación](Lab2/notebooks/laboratorio_estudiante.ipynb) | [Reporte PDF](Lab2/plantilla_reporte_laboratorio.pdf) |
|
||||||
|
|||||||
Reference in New Issue
Block a user