English · Español
00 — Motivación¶
Esta fase no se salta. El 80% de los bugs de IA que verás en los próximos seis meses viven aquí: vistas que parecen copias, broadcasting silencioso entre
(N,)y(N,1), semillas globales que ensucian la reproducibilidad. Si no se interiorizan ahora, vuelven una y otra vez disfrazados de "el modelo no converge".
Por qué existe la Fase 6¶
Hay una fuerte tentación de saltarse "ingeniería en Python" y zambullirse directamente en autograd. No lo hagas.
Cada fase posterior de este currículo se escribirá encima de NumPy. La forma en que NumPy trata la memoria — cuándo copia, cuándo crea una vista, cuándo hace broadcast silenciosamente — es el sustrato sobre el que se asentará el autograd de Borja. La forma en que Python trata las referencias — qué hace en realidad a = b cuando b es una lista, un array o un Tensor — es el sustrato sobre el que se asentará el grafo de autograd. La forma en que el GIL interactúa con la semántica de NumPy de liberar el lock en las llamadas C es lo que hace posible la carga de datos en paralelo en la Fase 18.
No puedes ver estos sustratos hasta que te pones a buscarlos. Son invisibles por diseño — Python y NumPy son lenguajes amables que ocultan sus modelos de coste. El precio de esa amabilidad es que los bugs también se ocultan.
Los tres tipos de bug que esta fase previene¶
1. Bugs silenciosos de shape¶
El bug de IA más común con diferencia es el desajuste de shape que no da error. Considera:
y_true = np.array([1.0, 2.0, 3.0]) # shape (3,)
y_pred = np.array([[1.1], [2.1], [3.1]]) # shape (3, 1)
loss = ((y_true - y_pred) ** 2).mean()
La intención del usuario: diferencia cuadrática por pares, media escalar. Lo que hace Python: broadcastea (3,) y (3,1) a (3,3) (sí, en serio — el broadcasting alinea desde la derecha, (3,) se convierte en (1,3) frente a (3,1), y el resultado es (3,3)). El .mean() devuelve un escalar, sí, pero es el escalar equivocado — promediado sobre 9 entradas en lugar de 3, incluyendo 6 términos cruzados sin sentido.
El código corre. El número es incorrecto. El modelo se entrena con basura.
La única defensa es conocer la regla de broadcasting al dedillo. El lab 02 de esta fase obliga a Borja a producir este bug intencionadamente para que el patrón quede grabado.
2. Bugs de aliasing¶
b es una vista, no una copia. b[0] = 999 escribe a través de a. Si a era un peso del modelo y b se suponía que era una versión temporal normalizada, el modelo queda corrupto in situ, silenciosamente.
La única defensa es saber cuándo NumPy copia y cuándo crea una vista. La regla completa: indexar con slices e índices int devuelve una vista; indexar con arrays de índices, arrays booleanos, o np.copy/np.ascontiguousarray devuelve una copia. arr.flags.OWNDATA te lo dice directamente.
3. Sorpresas del modelo de coste¶
Esto es la matemática correcta. Es el coste incorrecto. Un bucle Python sobre un array NumPy crea 10 millones de objetos float de Python, cada uno pasando por la asignación de PyObject, el dispatch de ** y el dispatch de +=. Espera más de 30 segundos.
Misma matemática. ~10 ms. Tres órdenes de magnitud.
La única defensa es interiorizar que iterar a nivel Python sobre un array NumPy es mala praxis y reconocerlo a primera vista en una revisión de código. El lab 03 obliga a Borja a medir la ratio a lo largo de varios tamaños de array para que la regla tenga un número detrás.
Qué significa aquí "Python para ingeniería de IA"¶
No significa enseñar la sintaxis de Python — Borja es un desarrollador con experiencia.
Significa tratar NumPy como una pieza de systems software con su propio modelo de memoria, su propio contrato de rendimiento y sus propios comportamientos sorprendentes, y aprender ese systems software como un ingeniero de seguridad aprende una CPU: de abajo hacia arriba, con mediciones, produciendo bugs intencionadamente.
Las cuatro páginas de teoría de esta fase recorren esa escalera:
01-references-mutation-gil.md— modelo de objetos de Python, semántica de referencias, mitos del GIL.02-strides-and-broadcasting.md— la quíntupla(data, shape, strides, dtype, flags)de NumPy; el algoritmo de broadcasting, formalizado.03-vectorization-and-profiling.md— el modelo de coste + los cuatro profilers, cuándo cada uno se gana el sueldo.
Los labs hacen que Borja produzca, a mano, los bugs que la teoría advirtió. El objetivo no es evitar bugs en el lab — el objetivo es producir los bugs en un entorno controlado para que no reaparezcan en la Fase 18 cuando el log del bucle de entrenamiento tiene 80 líneas y no puedes saber qué línea te miente.
Qué produce esta fase¶
Dos pequeños archivos de utilidades que importa cada fase posterior:
src/utils/seeding.py— unseed_everything(seed)que fija elrandomde Python, eldefault_rngde NumPy yPYTHONHASHSEED. Devuelve la semilla para registrarla en logs.src/utils/logging.py— unget_logger(name)que devuelve un logger respaldado porstructlogque escribe JSON con un campophase. Cada experimento posterior lo usa; no másprinta partir de la Fase 6.
Ambos suman menos de 40 LOC. El objetivo no es el código — el objetivo es que a partir de esta fase, la reproducibilidad y el logging estructurado sean infraestructura, no algo añadido a posteriori.
Recapitulación en un párrafo¶
La Fase 6 es el sustrato de ingeniería de Python+NumPy sobre el que se asienta todo desde la Fase 7 en adelante. El sustrato está lleno de trampas silenciosas — bugs de broadcasting, bugs de aliasing, sobrecoste del bucle Python — y la única manera de verlas es producirlas intencionadamente con medición. Al final de esta fase, Borja posee dos pequeños módulos de utilidad (seeding, logging) y un modelo interiorizado de coste y shape para cualquier expresión de NumPy que vaya a escribir en las 33 fases siguientes.
Siguiente: 01-references-mutation-gil.md