English · Español
Lab 02 — La trampa del broadcasting¶
Objetivo: producir, intencionadamente, el bug
(N,) * (N,1) → (N,N). Después arreglarlo. Después catalogar dos sorpresas más de broadcasting para que nunca te vuelvan a sorprender.Tiempo estimado: 45–60 minutos.
Prerrequisitos: lab 00.
Lo que produces¶
Un directorio experiments/06-broadcasting-trap/ que contiene:
bug.py— script que reproduce el bug e imprime pruebas (shape incorrecto, número incorrecto).fix.py— el mismo cómputo hecho correctamente; imprime pruebas.catalog.py— tres situaciones más de broadcasting; para cada una, imprime los shapes de entrada, el shape de salida esperado y el shape de salida real (oValueError).manifest.json— esquema estándar.README.md— un párrafo por situación encatalog.pyexplicando por qué el broadcast se resuelve como lo hace.
TODOs¶
Bloque A — reproducir el bug clásico¶
En bug.py:
- Genera "predicciones"
y_pred = np.arange(10, dtype=np.float32)— shape(10,). - Genera "objetivos"
y_true = (np.arange(10, dtype=np.float32) + 0.1).reshape(10, 1)— shape(10, 1). - Calcula
err = y_pred - y_true. Imprimeerr.shape. (Debería imprimir(10, 10), no(10,).) - Calcula
mse_wrong = (err ** 2).mean(). Imprime el valor. - Calcula también el MSE intencionado
mse_right(usa un bucle manual o((y_pred - y_true.squeeze()) ** 2).mean()). Imprime el valor. - Asegura
mse_wrong != mse_rightpara hacer el bug innegable.
Bloque B — arreglarlo de tres maneras¶
En fix.py, muestra tres arreglos idiomáticos para el bug de arriba:
- Coincidir shapes explícitamente:
y_true_flat = y_true.squeeze(). Luego((y_pred - y_true_flat) ** 2).mean(). - Coincidir al revés:
y_pred_col = y_pred[:, None]. Luego((y_pred_col - y_true) ** 2).mean(). - Ser paranoico:
assert y_pred.shape == y_true.squeeze().shape. Luego procede.
Imprime los tres resultados. Deberían coincidir dentro de la precisión fp32 (~1e-7).
Bloque C — catalogar tres situaciones más de broadcasting¶
En catalog.py, para cada una de las siguientes, escribe una sección: shapes de entrada → shape de salida predicho → shape de salida real → explicación de un párrafo citando la regla de broadcast.
Situación 1: a.shape = (3, 4), b.shape = (4,). Calcula a + b.
Situación 2: a.shape = (3, 4), b.shape = (3,). Calcula a + b. (Pista: esta es la trampa de "quería broadcastear sobre filas pero no reshapé".)
Situación 3: a.shape = (B, 1, N, D), b.shape = (1, H, N, D) (forma de attention). Calcula a + b.
Para cada una, tu código debe:
- Imprimir los shapes de entrada.
- Imprimir tu predicción del shape de salida antes de calcular (usa un comentario).
- Imprimir el shape de salida real.
- Capturar cualquier
ValueErrore imprimirlo en su lugar.
Bloque D — fijarlo en memoria¶
- En
README.md, escribe tres reglas mnemotécnicas con tus propias palabras. Deberían mapearse directamente a: (i) alinear a la derecha, (ii) los dims coinciden o son 1, (iii) el resultado es el máximo por pares. Si no puedes escribir la regla, aún no la posees.
Restricciones¶
- fp32. Coincide con fases posteriores.
- Sin
try / except Exception. Captura elValueErrorespecífico que esperas; deja que las excepciones inesperadas estallen para que te enteres. - Imprime, no logues. Este lab es interactivo; la estructura de logging no es el punto aquí. (Excepción a la regla del lab 00.)
Resultados esperados¶
bug.pyimprimeerr.shape = (10, 10)y dos valores de MSE distintos. El "incorrecto" es aproximadamente una décima parte del "correcto" (porque estás promediando 100 entradas donde 90 son términos cruzados de magnitud ~0–9).fix.pyimprime tres valores de MSE coincidentes.catalog.pysituación 1:(3, 4). Situación 2:ValueError. Situación 3:(B, H, N, D).
Condiciones de parada¶
Hecho cuando:
- Los tres scripts corren satisfactoriamente (donde el éxito para
bug.pyincluye que el assertion se dispare — eso es el bug). - La situación 2 de
catalog.pylanzaValueErrory tu código la captura limpiamente. README.mdtiene las tres reglas mnemotécnicas con tus propias palabras.
Escollos¶
reshape(-1, 1)frente a[:, None]. Funcionalmente idénticos para arrays 1-D. Elige uno y úsalo consistentemente.[:, None]es más idiomático para insertar ejes.squeezesin argumento de eje. Elimina todos los ejes de tamaño 1. Peligroso si pretendías eliminar un eje específico. Sé explícito:squeeze(axis=-1).(N, 1) - (N, 1)NO broadcastea a(N, N). Ambos shapes son(N, 1), totalmente compatibles por eje, el resultado es(N, 1). El bug solo ocurre cuando uno es(N,)y el otro es(N, 1).- Los shapes de mayor dimensión confunden el ojo. Escribe los shapes alineados a la derecha antes de calcular:
Luego por eje:
(B, H, N, D). Cinco segundos de papel ahorran una hora de depuración. - El mensaje de error de NumPy. Cuando el broadcasting falla, NumPy imprime los dos shapes — léelos. El shape que te sorprende es el que necesita
[:, None]o.squeeze().
Cuándo consultar solutions/¶
Después de que los tres scripts funcionen y README.md contenga las reglas con tus propias palabras. solutions/02-broadcasting-trap-ref.md (escrito en la apertura de fase) proporciona las explicaciones de referencia.
Siguiente lab: lab/03-vectorization-budget.md.