2 Commits
Author SHA1 Message Date
aleleba 1feef1d487 fixing name report 2026-08-25 23:01:28 -06:00
aleleba 548746321e Adding Lab2. 2026-08-25 12:19:45 -06:00
23 changed files with 2482 additions and 3 deletions
Executable
+13
View File
@@ -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.
+3
View File
@@ -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
View File
@@ -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
1 id x1 x2 x3 x4
2 ejemplo_0001 2.16279541 -0.15620382 1.52249089 1.65824076
3 ejemplo_0002 -0.33252016 -1.51252489 -0.74678853 2.48168432
4 ejemplo_0003 -0.67816516 -1.48254252 1.32306735 0.53641452
5 ejemplo_0004 -0.81252663 -0.39666178 0.80564212 -2.38488356
6 ejemplo_0005 1.11282146 0.39086547 1.85731970 -0.78875571
7 ejemplo_0006 -0.85655470 1.90412707 1.44050521 0.88793538
8 ejemplo_0007 -1.64834898 -0.63973431 -0.30206628 1.31444933
9 ejemplo_0008 -1.57711791 1.50301207 1.67408804 1.89669274
10 ejemplo_0009 -1.30573622 -1.21531243 1.38645484 1.79280184
11 ejemplo_0010 -1.47446448 1.32280710 -2.49155307 2.21472376
12 ejemplo_0011 -2.45185030 -1.18520910 2.03686553 -2.34874399
13 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
1 id probabilidad_clase_1 prediccion umbral
2 ejemplo_0001 0.00557925 0 0.315
3 ejemplo_0002 0.00026550 0 0.315
4 ejemplo_0003 0.00176073 0 0.315
5 ejemplo_0004 0.00001668 0 0.315
6 ejemplo_0005 0.00000370 0 0.315
7 ejemplo_0006 0.02924983 0 0.315
8 ejemplo_0007 0.00000050 0 0.315
9 ejemplo_0008 0.00000003 0 0.315
10 ejemplo_0009 0.00000023 0 0.315
11 ejemplo_0010 0.00183952 0 0.315
12 ejemplo_0011 0.01807133 0 0.315
13 ejemplo_0012 0.92466868 1 0.315
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.
Binary file not shown.
+54
View File
@@ -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()
+3
View File
@@ -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.
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.
+66
View File
@@ -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
View File
@@ -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
1 id x1 x2 x3 x4 etiqueta
2 test_0001 -1.45329337 -0.80200976 1.29580787 1.22693757 0
3 test_0002 -0.52045331 -0.68697770 -1.40199533 1.54980569 0
4 test_0003 -0.09167948 -1.15753259 -1.23926112 -2.05578252 0
5 test_0004 -0.95562309 2.23947848 -0.97812000 -0.37391722 1
6 test_0005 1.34148897 1.01976166 -1.83249406 1.26312340 1
7 test_0006 -0.60382765 -0.42498739 1.22335123 -0.31705208 0
8 test_0007 0.18621612 0.41779976 -2.14357433 -2.17898694 1
9 test_0008 2.11219013 2.16090114 1.16426707 -1.84287756 0
10 test_0009 0.82408644 -2.18348522 1.39197130 -2.01817722 1
11 test_0010 -0.68030530 -2.23125657 -2.08941012 1.74167698 1
12 test_0011 -2.35824483 -1.28573773 1.29431772 -0.20780914 0
13 test_0012 2.11852923 -2.36071958 0.70308976 0.18373273 1
14 test_0013 -1.67552723 1.95656447 2.18541988 -1.92263682 0
15 test_0014 -0.20424576 2.00422273 2.41835481 0.82287635 0
16 test_0015 1.62452182 -2.19005084 0.44618990 0.99007682 1
17 test_0016 1.73397093 0.66315434 0.71497331 0.97717974 0
18 test_0017 -2.36006033 0.42995369 -2.19505378 -0.31249831 1
19 test_0018 1.69117922 -1.88231376 -0.55647826 -2.48874896 1
20 test_0019 2.26095193 2.01167391 -1.98860354 -1.30947931 1
21 test_0020 -2.08186391 0.80842148 -0.71459713 -1.17981069 1
22 test_0021 0.60296969 1.05032325 -1.19790752 -2.24469607 0
23 test_0022 0.06976761 -1.26603746 0.97647176 1.16616602 0
24 test_0023 -2.16865011 -0.45891806 -1.28671892 0.66225600 0
25 test_0024 0.49667051 -1.67151035 -1.03456036 -0.44059422 1
26 test_0025 1.03159092 -2.40759248 -1.18394129 -1.25790615 1
27 test_0026 1.57952323 -1.07244028 -2.43149390 0.63345346 1
28 test_0027 0.13735245 -0.43567881 -0.54721837 -1.70363302 0
29 test_0028 -1.90781183 -0.88389181 -0.34347431 1.96026037 0
30 test_0029 0.96159706 1.91297323 1.90110582 -1.28053751 1
31 test_0030 -2.26873577 1.98767750 -2.23958875 0.64968906 1
32 test_0031 -2.00316907 0.71745776 0.86613662 1.76053487 0
33 test_0032 -2.45578718 -0.13125501 1.96488336 -1.84651659 0
34 test_0033 -0.67572968 -0.06126132 1.42320774 0.02233957 0
35 test_0034 1.32482990 2.44076184 0.32482658 1.29013522 1
36 test_0035 0.24476376 0.36764926 0.22911212 1.43762596 0
37 test_0036 0.54969604 -2.13096766 1.10118906 0.42006305 1
38 test_0037 -0.26726884 -1.54382713 -1.44717731 -0.92288292 1
39 test_0038 1.40974942 0.58111562 1.00359615 -0.47467149 0
40 test_0039 0.24965433 -0.98576518 0.10741817 -2.42164401 0
41 test_0040 -2.16194096 -2.07738078 0.36869224 1.89891176 0
42 test_0041 -0.06726050 -0.22152113 2.23315436 1.87187572 0
43 test_0042 0.45718322 -1.90091108 1.57001435 -1.65792686 0
44 test_0043 -2.18459398 1.20082129 -0.77987085 -1.37370548 1
45 test_0044 -1.59159843 1.30176166 2.34034269 -2.01184840 0
46 test_0045 2.25388348 -1.64404019 -1.91145566 2.42391413 1
47 test_0046 2.16611394 -1.48313696 -0.36738261 0.64232959 1
48 test_0047 -0.31113493 1.69809874 2.36299659 -0.48603705 0
49 test_0048 -1.24941210 -0.28803499 -0.86646972 -0.19631142 0
50 test_0049 -1.94501563 -2.41616468 2.07088800 -1.95012774 1
51 test_0050 -1.83257121 -0.88768063 -1.98256403 -0.29783165 1
52 test_0051 0.16047757 -1.25975942 1.61511934 -0.74494231 0
53 test_0052 2.31577261 1.18243490 -1.80013917 1.00554226 1
54 test_0053 -1.09179959 -0.36923188 -1.23969889 2.25487871 0
55 test_0054 -0.14822303 -0.95022517 -2.36470503 -1.84141731 1
56 test_0055 1.77175207 -0.33538042 -2.05550804 -0.01691591 1
57 test_0056 0.19813051 2.17931603 2.10657280 -2.14620972 0
58 test_0057 -0.39470845 1.16613610 -1.13228061 -0.54310162 1
59 test_0058 -0.14970876 0.89414771 0.40800137 1.58418574 0
60 test_0059 2.49857886 -0.04437076 0.61949553 -0.03382795 0
61 test_0060 0.83732642 -0.48623062 -1.97966632 1.30435953 1
62 test_0061 1.55460215 2.32672323 1.74610970 0.74218193 1
63 test_0062 1.78819140 0.59594612 0.85060473 -1.91082888 0
64 test_0063 -2.14346879 2.47045733 -2.44238526 -1.46627921 1
65 test_0064 2.38806503 -1.08699012 1.60109508 -2.40569650 0
66 test_0065 0.59316658 -0.16780638 -0.67948597 -1.07304932 0
67 test_0066 -2.24844187 0.73646848 -0.75090338 -2.16317527 1
68 test_0067 2.03343004 1.96296342 -1.12705154 -0.79836152 1
69 test_0068 1.63543612 0.90086255 2.10038823 0.27145194 0
70 test_0069 2.30425516 -1.42251118 1.03950495 2.12923819 0
71 test_0070 -1.90434601 -0.56559933 -0.68051150 -2.04794981 0
72 test_0071 0.16933848 -1.45869995 0.95382899 0.07099242 1
73 test_0072 -0.62419535 -1.84917292 -0.13990795 -2.33577645 0
74 test_0073 0.92333651 -2.40842454 1.50109107 -1.38579681 1
75 test_0074 -1.69447584 0.02847724 0.38192945 -2.11056830 0
76 test_0075 0.09334503 0.30772728 -0.03146323 -2.05359311 0
77 test_0076 0.01358586 -2.28170631 2.42432526 1.85723762 1
78 test_0077 -1.19217760 1.91295366 -0.98624024 -0.70372065 1
79 test_0078 0.67059249 0.81869234 -0.09079115 -0.09327915 0
80 test_0079 -1.35376536 -0.44382773 -0.94712589 1.78565187 0
81 test_0080 -0.60730785 -1.41680375 0.08145395 -1.36936940 0
82 test_0081 2.35699375 0.78325978 -1.80833117 0.72802736 1
83 test_0082 0.63086133 -1.49935097 -1.70914798 1.44214895 1
84 test_0083 -2.42748607 0.85924177 -2.09692344 0.22377841 1
85 test_0084 -1.31325624 -1.93071210 -0.09472675 0.68914311 0
86 test_0085 1.23908276 0.98596048 2.21314844 0.65154351 1
87 test_0086 0.14961595 -0.24398627 -0.45056574 -0.42777022 0
88 test_0087 1.25871123 0.60615861 -0.69371829 -0.59495777 0
89 test_0088 -0.91227054 0.69118054 2.02635968 1.77828215 0
90 test_0089 -0.83869894 0.76593256 -0.49577409 -0.43492475 0
91 test_0090 -1.66530589 2.44910222 -1.57113334 0.73054060 1
92 test_0091 -1.89585896 2.40074559 0.07165437 -2.24429887 1
93 test_0092 -1.74167063 -0.83882977 0.47400933 1.86572658 0
94 test_0093 -0.49298474 1.03984063 -1.14411905 0.59369068 0
95 test_0094 -1.67642716 -2.10752991 1.89351133 -0.91951711 0
96 test_0095 2.11585366 -0.18961527 0.74863511 0.77890455 1
97 test_0096 0.29991196 -1.96098617 1.48007879 2.48250993 0
98 test_0097 -0.11910194 -0.47242821 -2.24062159 -2.40340404 0
99 test_0098 -0.98286999 1.08827497 0.01598984 0.23765656 0
100 test_0099 -1.29372176 -0.34978669 -2.29364454 1.49697292 0
101 test_0100 -0.90665183 1.13854694 0.47737863 1.50534383 0
102 test_0101 1.75271206 -1.98582639 0.59173025 -2.25588044 0
103 test_0102 0.91463402 0.49026878 1.62948890 -2.45095511 0
104 test_0103 1.69123326 1.41298407 -2.01001064 -2.32060632 0
105 test_0104 -1.67766279 -1.43435384 -2.11647264 2.49811505 0
106 test_0105 -1.80144033 0.45373587 -2.16173209 -0.67985384 1
107 test_0106 0.59391963 -1.99721464 -0.64362664 0.39148548 1
108 test_0107 1.07519274 -1.02326075 1.07132256 -1.34998648 0
109 test_0108 -1.61872105 -2.43797584 -1.98749349 2.03329457 1
110 test_0109 -1.07774354 0.16030905 1.03025212 -0.86615910 0
111 test_0110 -2.38347676 0.49667620 0.08810167 -0.34257172 0
112 test_0111 -2.07833560 0.80057867 -0.96619428 -1.23069979 1
113 test_0112 -1.58705044 -1.06636337 0.35707168 0.97993168 0
114 test_0113 0.68680677 -0.43151628 -2.07827137 0.04313296 1
115 test_0114 0.86882697 -1.41471475 -0.34521398 -2.23708502 0
116 test_0115 -0.92371655 0.72933935 -1.84701534 -1.67426541 1
117 test_0116 1.12927661 -2.20661759 -0.80473702 -0.92109921 1
118 test_0117 0.75063499 -1.12182696 -1.87815189 -1.30570949 1
119 test_0118 -1.54189110 -2.12068811 -0.03445788 2.19694536 0
120 test_0119 1.41119451 2.28233921 1.36710184 0.03226869 1
121 test_0120 1.12335658 0.92576543 2.20734014 -1.00702253 0
122 test_0121 2.36918795 2.02651090 -2.13940262 2.21951023 1
123 test_0122 1.24559857 2.21571139 -1.47016421 0.67513295 1
124 test_0123 0.78132760 0.83636584 -1.49063294 -1.28601044 1
125 test_0124 -0.42653483 -0.20226462 1.33822818 2.21708881 0
126 test_0125 2.09229643 0.26185923 1.91241053 0.87587186 0
127 test_0126 1.11645014 0.21659179 -1.53846650 -1.02953752 1
128 test_0127 1.07644723 0.61617544 1.10957197 2.22305726 0
129 test_0128 -0.53472017 1.74743289 0.43954259 0.91183298 1
130 test_0129 2.06671301 -1.68453471 -2.49727568 -0.45354613 1
131 test_0130 1.30846906 -2.09069057 -0.94337302 -1.38366153 1
132 test_0131 -1.18821619 0.77820417 -0.44047102 0.71405693 0
133 test_0132 1.00825620 1.37613516 -0.60049548 1.77367282 1
134 test_0133 0.99924608 -0.46878608 1.25708870 -1.82238171 0
135 test_0134 1.20923187 0.93323130 -1.07566606 -1.22741123 1
136 test_0135 0.79048230 -0.58437966 -0.24593741 2.22256008 0
137 test_0136 -0.78646328 0.29195737 1.05568528 0.95617044 0
138 test_0137 -1.82975876 -1.79373133 0.19964861 0.26557118 1
139 test_0138 0.07511816 -0.30160901 -1.70564546 1.27121744 1
140 test_0139 1.82232529 0.84608299 1.48812937 0.63265312 0
141 test_0140 1.48304154 -0.23175462 0.29458534 1.65723926 0
142 test_0141 -1.89720659 1.03235315 -0.92870464 -2.41563946 1
143 test_0142 0.70191777 1.57894788 -0.38920993 -1.31113691 1
144 test_0143 2.12997911 0.25546600 -2.11885168 0.97053590 1
145 test_0144 2.08125423 -1.97791338 -1.95593453 -1.43279258 1
146 test_0145 -0.40414829 -0.93995837 2.21613817 -2.14350843 0
147 test_0146 1.49730457 0.83614150 1.95774998 -2.04684336 0
148 test_0147 0.31599577 -1.93663138 1.57165891 0.51237795 1
149 test_0148 -1.12266380 -1.98768988 1.05317509 -0.74157986 0
150 test_0149 2.26349408 2.36703096 -1.36109186 -0.83638554 1
151 test_0150 -0.30491254 1.32350843 0.29807829 0.61762198 1
152 test_0151 -1.46195082 -0.74578199 0.85586293 2.41662407 0
153 test_0152 -1.60419411 -0.45724031 0.85807840 1.59964163 0
154 test_0153 0.64099480 -0.73794623 -0.49725863 -0.72711901 1
155 test_0154 0.74989823 -2.49668144 0.62910560 -0.77756842 1
156 test_0155 0.39371566 -1.92057879 -2.19428423 -1.19302672 1
157 test_0156 -1.05778903 1.08420919 2.27326487 0.98267644 0
158 test_0157 2.40272972 1.57968001 1.23816567 -0.71726643 1
159 test_0158 -1.05083206 -1.80725526 -0.35338123 1.71865559 0
160 test_0159 -0.76141960 1.87287015 -1.64402836 0.58223314 1
161 test_0160 -2.13074779 -0.00527227 -0.61736199 1.17297520 0
162 test_0161 1.20382148 -2.18899859 0.17091663 1.91215298 1
163 test_0162 0.77711986 0.23372772 -0.05422450 -1.59924184 0
164 test_0163 1.50260590 -1.83434449 1.37518338 2.24123250 1
165 test_0164 0.25640917 1.13031102 -1.51621878 2.34800905 1
166 test_0165 0.27086379 -1.30615157 1.20917386 2.20912214 0
167 test_0166 0.05868963 -0.43013314 1.87917447 1.51617368 0
168 test_0167 -2.11811078 0.31449053 0.99168714 1.25147560 0
169 test_0168 -2.39563199 2.24448153 0.78142293 -1.16573941 1
170 test_0169 0.75088988 -0.69924804 -2.46954844 0.74754928 1
171 test_0170 2.40373350 -0.06562730 1.71760271 -1.11570584 0
172 test_0171 -0.22564064 2.45758864 1.28116831 -0.62193694 1
173 test_0172 -2.20774481 1.29075955 -1.18379939 -0.20688917 1
174 test_0173 -0.98214541 -1.32743540 -2.29362761 1.57875279 1
175 test_0174 0.95545335 -2.22974060 -0.17911193 2.19598009 1
176 test_0175 0.88527144 1.16625925 2.09386271 0.18397893 0
177 test_0176 -0.52440010 1.11472336 -2.45654324 2.36389928 0
178 test_0177 -0.47091838 0.03147500 -1.06883465 -2.23172693 0
179 test_0178 -1.44209585 -1.66102789 -1.04403717 0.04743861 1
180 test_0179 -0.75338703 -1.66413783 -1.88477911 2.18903776 1
181 test_0180 2.10103457 1.07289588 -1.27030198 -0.40487401 1
182 test_0181 0.85153441 0.20044776 1.87882761 1.74996884 0
183 test_0182 1.75927453 -1.75921244 1.74975537 -0.82601372 1
184 test_0183 2.49907267 -2.44099830 -1.21443559 2.05344654 1
185 test_0184 1.40000807 2.32155007 2.41148559 1.03830643 1
186 test_0185 -1.89305872 -1.04720188 -1.47374211 -0.70848786 1
187 test_0186 -2.37957805 -1.78963287 2.27290346 0.88734310 0
188 test_0187 -0.78091976 1.02549252 1.76628950 -2.37799837 0
189 test_0188 -0.86625762 -0.30128846 -0.89844486 -2.14972633 0
190 test_0189 -1.82915165 0.61862172 2.16940618 0.20235168 0
191 test_0190 -1.26064826 2.18851343 -0.29151919 -0.06185392 1
192 test_0191 -1.85280012 1.44904159 -2.10045311 -0.62109544 1
193 test_0192 -1.60477761 -1.74361636 -1.80323793 0.82332343 1
194 test_0193 0.42713831 1.06727749 0.37919761 1.44867722 1
195 test_0194 -1.32165112 -1.19786516 -1.98078520 -1.33092447 1
196 test_0195 0.66067093 -0.59922476 0.22879934 1.94027287 0
197 test_0196 0.04861905 0.88783688 0.61098210 -0.92653606 0
198 test_0197 1.77209903 0.06586325 0.17907839 1.02733614 1
199 test_0198 -0.92283097 -1.48616370 2.14012563 0.18272875 0
200 test_0199 0.43965858 2.04316190 -2.22104418 0.74529927 1
201 test_0200 1.31857489 -0.44833159 -1.31362535 0.75549619 1
+121
View File
@@ -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"]
+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
}
+131
View File
@@ -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 2030 (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}
BIN
View File
Binary file not shown.
+61
View File
@@ -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()
+4 -3
View File
@@ -7,6 +7,7 @@ Repositorio con los laboratorios de la asignatura.
## Laboratorios
| # | Laboratorio | Notebook |
|---|---|---|
| 1 | MLPs, verosimilitud, entrenamiento y evaluación | [Lab 1 - MLP](Lab1/lab1_mlp.ipynb) |
| # | Laboratorio | Notebook | Reporte |
|---|---|---|---|
| 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/reporte_laboratorio.pdf) |