Volver al catálogo
Season 55 17 Episodios 1h 8m 2026

PyTorch Fundamentals

v2.11 — Edición 2026. Un curso en audio exhaustivo sobre la creación de modelos de deep learning utilizando PyTorch versión 2.11. Cubre Tensors, Autograd, Neural Networks, Optimizers, DataLoaders y el compilador de PyTorch.

Frameworks de AI/ML Python Core Ciencia de datos
PyTorch Fundamentals
Reproduciendo ahora
Click play to start
0:00
0:00
1
La identidad central de PyTorch
Descubre el propósito fundamental de PyTorch y qué lo diferencia de las bibliotecas matemáticas tradicionales. Este episodio explica el papel de los Tensors, Autograd y la aceleración por GPU en el deep learning moderno.
4m 26s
2
Entendiendo los Tensors de PyTorch
Sumérgete en los Tensors, la estructura de datos fundamental de PyTorch. Aprende cómo conectan los datos sin procesar con las Neural Networks y comparten memoria sin problemas con los arrays de Numpy.
4m 35s
3
Operaciones con Tensors y memoria
Aprende a manipular Tensors de forma eficiente. Este episodio cubre operaciones aritméticas, concatenación, transferencias entre dispositivos y las implicaciones de memoria de las operaciones in-place.
4m 11s
4
La magia de Autograd
Analizamos el motor que hace posible el deep learning en PyTorch. Aprende cómo Autograd rastrea operaciones dinámicamente y calcula derivadas complejas de forma automática.
4m 00s
5
Controlando el seguimiento de gradientes
Descubre cómo desactivar el seguimiento de gradientes de PyTorch para ahorrar memoria y acelerar los cálculos. Esencial para ejecutar inferencias y congelar los parámetros del modelo.
3m 42s
6
Datasets y manejo de datos
Aprende a desacoplar el procesamiento de datos de la arquitectura de tu modelo utilizando la clase Dataset de PyTorch. Exploramos la carga diferida y las estructuras de datasets personalizados.
3m 27s
7
DataLoaders y procesamiento por lotes
Libera toda la velocidad de tu hardware envolviendo los Datasets en DataLoaders. Aprende a agrupar en lotes, mezclar y multiprocesar tus flujos de datos.
4m 27s
8
Transformaciones de datos
Descubre cómo preprocesar datos sin procesar sobre la marcha antes de que lleguen a tu Neural Network. Cubrimos los transforms de torchvision como ToTensor y funciones Lambda personalizadas.
4m 12s
9
Diseñando redes con nn.Module
Explora el plano estructural de cada Neural Network de PyTorch. Aprende a crear subclases de nn.Module, definir capas en la inicialización y enrutar datos en el forward pass.
4m 02s
10
Capas Linear y activaciones
Echa un vistazo al interior de la Neural Network. Desglosamos el módulo nn.Linear y explicamos por qué las funciones de activación no lineales como ReLU son matemáticamente esenciales.
4m 43s
11
El contenedor nn.Sequential
Optimiza tu código de PyTorch usando el contenedor nn.Sequential. Aprende a encajar capas de forma limpia y a inspeccionar los parámetros de tu modelo.
4m 03s
12
Entendiendo las Loss Functions
Antes de que una IA pueda aprender, debe medir sus errores. Nos sumergimos en las Loss Functions de PyTorch, comparando CrossEntropyLoss para clasificación y MSELoss para regresión.
3m 35s
13
Optimizers y descenso de gradiente
Explora cómo el Optimizer actualiza los pesos del modelo para reducir el error. Aprende el crucial baile de tres pasos de zero_grad(), backward() y step().
3m 50s
15
Validación e inferencia
Evalúa tu modelo objetivamente. Aprende a cambiar tu red al modo de evaluación, congelar gradientes y extraer predicciones precisas sobre datos no vistos.
4m 05s
16
Guardando y cargando modelos
¡No pierdas el progreso que tanto te ha costado! Hablamos de las formas más seguras de serializar los pesos de tu modelo usando state_dict y volver a cargarlos de forma segura.
3m 43s
17
Acelerando la velocidad con torch.compile
Desbloquea la característica definitoria de PyTorch 2.0. Aprende cómo el decorador torch.compile realiza una compilación JIT de tu código Python en kernels optimizados para lograr aceleraciones masivas.
3m 48s
18
Compiladores y graph breaks
Sumérgete bajo el capó del compilador de PyTorch. Exploramos los graph breaks, el flujo de control dinámico y por qué torch.compile triunfa donde los sistemas heredados fracasaron.
3m 56s

Episodios

1

La identidad central de PyTorch

4m 26s

Descubre el propósito fundamental de PyTorch y qué lo diferencia de las bibliotecas matemáticas tradicionales. Este episodio explica el papel de los Tensors, Autograd y la aceleración por GPU en el deep learning moderno.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 1 de 18. Escribes un modelo matemático complejo en Python, pero al escalarlo, satura completamente tu procesador. Necesitas ejecutar esos cálculos en hardware paralelo y calcular continuamente todas sus derivadas, pero reescribir todo en un lenguaje de bajo nivel te llevaría semanas. Esa tensión se resuelve gracias a la identidad central de PyTorch. Cuando le echas un primer vistazo a PyTorch, muchas veces parece exactamente igual que NumPy. Creas arrays, multiplicas matrices y manipulas números. Esta similitud visual causa mucha confusión al principio. La gente asume que PyTorch es simplemente otra librería matemática estándar. No lo es. Mientras que las librerías matemáticas estándar están diseñadas para computación numérica CPU-bound, PyTorch está diseñado desde cero para aprovechar el hardware paralelo y construir grafos computacionales dinámicos. El bloque fundamental de este framework es el tensor. Un tensor es, esencialmente, un array multidimensional. Si tienes una cuadrícula de números que representa una imagen, una onda sonora o un bloque de texto, la almacenas en un tensor. La diferencia crucial entre un array estándar y un tensor de PyTorch es dónde pueden vivir y ejecutarse esos datos. Los tensores pueden moverse sin problemas de la memoria del sistema de tu ordenador a una GPU. Imagina una multiplicación de matrices masiva. Tienes dos cuadrículas con millones de números. Si le pides a una CPU estándar que las multiplique, procesa los cálculos de forma secuencial o en batches muy pequeños. El proceso sufre y se atasca. Como los tensores están diseñados explícitamente para la aceleración por hardware, puedes enviar exactamente esos mismos datos a una GPU. La GPU contiene miles de pequeños cores diseñados para ejecutar operaciones matemáticas simultáneamente. Un cálculo masivo que tarda minutos en una CPU termina al instante en una GPU. PyTorch actúa como puente, traduciendo tu código Python estándar en instrucciones para ese hardware paralelo. Un hardware rápido es solo la mitad de lo que necesitas para machine learning. Entrenar una red neuronal requiere cálculo continuo. Necesitas saber exactamente cómo ajustar una variable cambia tu output final, lo que significa calcular gradientes constantemente. Hacer esto manualmente para un modelo con miles de millones de parámetros es imposible. Esto nos lleva al segundo pilar de PyTorch, que es Autograd. Autograd es un motor de diferenciación automática. Cuando realizas operaciones matemáticas con tensores, PyTorch no solo calcula el número final. Construye silenciosamente un mapa en segundo plano. Registra cada suma, multiplicación y transformación de datos en un grafo computacional dinámico. Cuando llegas al final de tu cálculo, simplemente le pides al framework que calcule los gradientes. PyTorch recorre hacia atrás ese grafo invisible, aplicando la regla de la cadena del cálculo automáticamente. Recibes las derivadas exactas para cada parámetro de tu modelo sin tener que escribir tú mismo ningún código de cálculo. Como este grafo se construye dinámicamente sobre la marcha, se adapta a tu código. Si un loop estándar de Python o un if-statement cambia el flujo de tus datos, el grafo se ajusta inmediatamente. El verdadero poder de PyTorch no es solo que se ejecute rápido o haga cálculos. Te da la velocidad de ejecución de un superordenador y el rigor matemático de un motor de cálculo automatizado, totalmente oculto tras un código Python ordinario y legible. Si quieres ayudar a que sigan saliendo estos episodios, puedes buscar DevStoriesEU en Patreon y apoyar el programa. Eso es todo por este episodio. Gracias por escuchar, ¡y a seguir construyendo!
2

Entendiendo los Tensors de PyTorch

4m 35s

Sumérgete en los Tensors, la estructura de datos fundamental de PyTorch. Aprende cómo conectan los datos sin procesar con las Neural Networks y comparten memoria sin problemas con los arrays de Numpy.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 2 de 18. Cada imagen, onda de sonido y documento de texto que le pasas a una red neuronal acaba convirtiéndose exactamente en la misma estructura de datos. Si todo es solo una cuadrícula de números, puede que te preguntes por qué necesitamos un objeto especializado en lugar de usar los arrays estándar de programación. La respuesta está en entender los tensores de PyTorch. Un tensor es una estructura de datos especializada que se ve y se comporta de forma muy parecida a un array o una matriz. En PyTorch, los tensores son la moneda de cambio universal. Guardan tus inputs en crudo, los outputs que genera tu modelo y los parámetros internos de la propia red neuronal. La gente suele dar por hecho que los tensores son exactamente idénticos a los arrays de NumPy. Es verdad que se parecen, y comparten muchos de sus comportamientos. La diferencia clave es lo que los tensores te permiten hacer. Mientras que un array estándar se queda en la memoria principal de tu sistema y se ejecuta en tu procesador central, un tensor está diseñado para pasarse fácilmente a una unidad de procesamiento gráfico, o GPU, para conseguir una aceleración por hardware masiva. Los tensores también traen de serie la infraestructura interna necesaria para el gradient tracking, lo que permite que las redes neuronales aprendan. Puedes inicializar un tensor de varias formas. La vía más directa es pasar los datos en crudo, como una lista de números estándar de Python, directamente al constructor del tensor. También puedes crear un tensor nuevo a partir de uno que ya exista. Cuando haces esto, el nuevo tensor hereda automáticamente las propiedades del original, lo que significa que tendrá las mismas dimensiones y el mismo data type, a menos que los sobrescribas explícitamente. Por otro lado, si solo necesitas un placeholder, puedes definir un shape, que es una simple colección de números que representan las dimensiones que quieres, y pedirle a PyTorch que genere un tensor lleno de números aleatorios, todo unos o todo ceros, basándose en ese shape. Una vez que tienes un tensor, vas a comprobar con frecuencia tres atributos principales. El primero es el shape, que te dice el tamaño exacto del tensor en cada dimensión. El segundo es el data type, que indica el tipo de números que hay guardados dentro, como floats de 32 bits o integers. El tercero es el atributo device. Este te dice dónde reside físicamente el tensor ahora mismo, ya sea en la CPU o en una GPU específica. Tienes que llevar un control de esto porque PyTorch requiere que los tensores estén en el mismo device antes de que puedan interactuar. Los tensores y los arrays estándar a menudo necesitan trabajar juntos, lo que nos lleva al bridge de NumPy. Los tensores que residen en la CPU pueden, de hecho, compartir su memoria subyacente con un array de NumPy. Imagina que cargas una fotografía de alta resolución usando una librería estándar de procesamiento de imágenes de Python. Esa imagen se carga en la memoria de tu sistema como un array de NumPy estándar. Puedes pasarle ese array a PyTorch usando una función dedicada que crea un tensor a partir de NumPy. PyTorch no duplica los datos de los píxeles subyacentes en un nuevo bloque de memoria. Simplemente envuelve la dirección de memoria existente con su propia interfaz de tensor. Cambiar un valor en el tensor cambia inmediatamente el valor en el array de NumPy, y viceversa. Esta conversión zero-copy ahorra tanto memoria como tiempo de procesamiento. Cuando terminas de pasar los datos por tu modelo y necesitas devolver los resultados a una herramienta de visualización estándar, llamas a un único método en el tensor para volver a exponerlo como un array de NumPy, usando exactamente esa misma memoria compartida. El verdadero poder de un tensor no es solo almacenar una cuadrícula de números, sino llevar consigo el contexto de hardware específico y la estructura de memoria necesarios para pasar datos en crudo a través de una red neuronal sin fricción. ¡Gracias por escuchar, y feliz programación a todos!
3

Operaciones con Tensors y memoria

4m 11s

Aprende a manipular Tensors de forma eficiente. Este episodio cubre operaciones aritméticas, concatenación, transferencias entre dispositivos y las implicaciones de memoria de las operaciones in-place.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 3 de 18. Un simple guion bajo en tu código puede ahorrar gigabytes de memoria, pero también podría romper silenciosamente toda tu red neuronal. Saber cuándo usar ese guion bajo se reduce a entender las operaciones con tensores y la memoria. Imagina un escenario práctico. Tienes tres vectores de características separados que representan datos de texto, audio e imagen. Quieres combinarlos y multiplicarlos por una matriz de pesos. Por defecto, PyTorch crea tensores en la CPU. Pero para cálculos matriciales pesados, quieres usar un acelerador de hardware. Puedes comprobar si hay una GPU disponible usando las comprobaciones integradas del framework. Si la hay, mueves tus tensores llamando al método to. Le pasas el nombre del dispositivo de destino, como el string cuda, a este método. PyTorch entonces copia el tensor de la RAM del sistema a la memoria dedicada de tu tarjeta gráfica. Con tus tensores en el hardware adecuado, necesitas combinar los tres vectores de características separados en uno solo. Haces esto usando la función concatenate, comúnmente escrita como cat. Le pasas una lista de tus tensores y especificas una dimensión. Si los combinas a lo largo de la dimensión de la columna, tus tres tensores estrechos se unen lado a lado para formar un tensor más ancho. Ahora tienes un input unificado residiendo en la memoria de la GPU. PyTorch maneja más de cien operaciones diferentes, pero la aritmética es la base. Para procesar tu vector de características combinado, necesitas multiplicarlo por una matriz de pesos. Puedes usar el método matmul, o simplemente usar el símbolo de arroba como un atajo conveniente. Esto realiza una verdadera multiplicación matemática de matrices, calculando los productos escalares de filas y columnas, y devuelve un tensor completamente nuevo que contiene los resultados. A veces necesitas matemáticas elemento a elemento en su lugar. Supón que quieres aplicar una máscara binaria a tu tensor, forzando ciertos valores a cero. Para esto, usas el método mul, o el operador de asterisco estándar. Esto no hace una multiplicación de matrices. Simplemente multiplica el primer elemento del tensor A por el primer elemento del tensor B, el segundo por el segundo, y así sucesivamente. Cada vez que ejecutas operaciones como la multiplicación de matrices o la suma elemento a elemento, PyTorch asigna memoria nueva para el resultado. Cuando operas con millones de parámetros, esto consume rápidamente tu memoria de hardware disponible. Aquí es donde tienes que prestar atención. PyTorch proporciona operaciones in-place para gestionar el overhead de memoria. Cualquier operación que termine con un guion bajo opera in-place. Si usas el método add estándar, obtienes un nuevo tensor. Si usas el método add con un guion bajo, PyTorch sobrescribe directamente los valores dentro del tensor existente. El consumo de memoria se mantiene exactamente igual. Aunque las operaciones in-place son muy eficientes para la memoria, también son peligrosas. Cuando sobrescribes un tensor, borras sus valores anteriores. Las redes neuronales dependen de un registro completo de los estados pasados para calcular derivadas durante la fase de aprendizaje. Si sobrescribes un tensor usando una operación in-place, destruyes el historial de computación que el sistema necesita para actualizar el modelo. Reserva las operaciones in-place para formatear datos antes de que entren en tu modelo, y cíñete a las operaciones estándar durante el entrenamiento para mantener intacto tu historial de computación. Eso es todo por este episodio. Gracias por escuchar, y sigue construyendo.
4

La magia de Autograd

4m 00s

Analizamos el motor que hace posible el deep learning en PyTorch. Aprende cómo Autograd rastrea operaciones dinámicamente y calcula derivadas complejas de forma automática.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 4 de 18. Entrenar una red neuronal implica calcular la derivada de tu error con respecto a millones de parámetros. Hacerlo a mano requeriría páginas de cálculos y reescribir todo constantemente cada vez que cambies la arquitectura de tu modelo. PyTorch soluciona esto haciendo un seguimiento de tus cálculos en segundo plano, un concepto conocido como la Magia de Autograd. Autograd es el motor de diferenciación integrado de PyTorch. Calcula automáticamente los gradientes para cualquier grafo computacional. Para ver cómo funciona, imagina una transformación lineal estándar. Tienes un tensor de entrada con tus datos, una matriz de pesos y un vector de bias. El objetivo es calcular una salida, compararla con el valor objetivo real y calcular el error, o loss. Tus datos de entrada son fijos, por lo que no necesitas sus derivadas. Pero los tensores de pesos y bias deben actualizarse más tarde, lo que significa que necesitas absolutamente sus gradientes. Le indicas esto a PyTorch ajustando un flag llamado requires grad a true cuando creas esos tensores de parámetros. Esto le dice al motor de autograd que empiece a vigilarlos. Cuando realizas operaciones sobre estos tensores observados, como multiplicar la entrada por los pesos, sumar el bias y calcular el loss final, PyTorch hace dos cosas a la vez. Calcula el resultado numérico real y, simultáneamente, construye un grafo acíclico dirigido, o DAG. En este grafo, tus tensores iniciales son las hojas y las operaciones matemáticas que aplicaste son las raíces. Cada nuevo tensor creado por una operación tiene un atributo que almacena una referencia a la función que lo creó. Esto le dice a autograd exactamente cómo calcular la derivada para ese paso matemático específico. Este grafo no es una estructura estática definida al inicio de tu script. PyTorch construye el DAG dinámicamente desde cero en cada iteración. Cuando ejecutas un forward pass, se construye un grafo totalmente nuevo sobre la marcha. Esta ejecución dinámica significa que tu red puede cambiar su comportamiento en cada paso. Puedes usar el flujo de control estándar de Python, como sentencias if o bucles, y el motor rastreará limpiamente cualquier ruta que los datos hayan tomado realmente durante esa ejecución específica. Una vez que tu forward pass produce el tensor de loss final, activas el cálculo del gradiente llamando al método backward sobre ese loss. Autograd recorre inmediatamente el grafo en sentido inverso. Utiliza la regla de la cadena para calcular las derivadas del loss con respecto a cada tensor que tiene requires grad ajustado a true. Luego toma esos valores calculados y los almacena en el atributo grad de tus tensores de pesos y bias. El cálculo complejo se abstrae por completo. Hay veces en las que solo quieres pasar datos por el modelo sin calcular gradientes, como al evaluar un modelo entrenado. Rastrear el historial requiere memoria y computación extra. Puedes evitar por completo que autograd construya el grafo envolviendo tu bloque de código en el context manager torch no grad. Esto detiene temporalmente el seguimiento y ejecuta los cálculos mucho más rápido. El verdadero poder de autograd es que transforma cualquier código Python en una estructura matemática totalmente diferenciable sin que tengas que escribir manualmente las fórmulas de las derivadas. Gracias por escuchar. Cuidaos todos.
5

Controlando el seguimiento de gradientes

3m 42s

Descubre cómo desactivar el seguimiento de gradientes de PyTorch para ahorrar memoria y acelerar los cálculos. Esencial para ejecutar inferencias y congelar los parámetros del modelo.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 5 de 18. Si ejecutas un modelo recién entrenado con un gran batch de imágenes de test sin cambiar una configuración específica, tu aplicación acabará fallando por un error de out-of-memory. Aunque solo estés haciendo predicciones, el modelo está acaparando memoria en secreto para recordar cada operación matemática que realiza hasta que el sistema muere. Controlar el Gradient Tracking es la forma de evitar esto. Por defecto, los tensores de PyTorch están diseñados para aprender. Si un tensor tiene su gradient requirement en true, PyTorch hace tracking de cada operación realizada sobre él. Crea un computation graph en segundo plano, vinculando inputs, weights y outputs para poder calcular los gradientes más tarde durante la backpropagation. Este motor de tracking es genial para el training, pero requiere mucho overhead. Cuando finaliza el training, tus prioridades cambian. Imagina que acabas de terminar de entrenar un clasificador de imágenes y necesitas ejecutar predicciones en un millón de imágenes nuevas. Ya no necesitas actualizar los weights del modelo. Solo quieres el forward pass. Si dejas la maquinaria de tracking en funcionamiento, PyTorch crea un computation graph masivo e inútil para ese millón de imágenes, quemando tu RAM y ralentizando tus compute cycles. Para evitar esto, tienes dos herramientas principales. La primera es un context manager llamado torch punto no grad. Lo usas para envolver bloques enteros de código. Cuando metes tu forward pass dentro de un bloque no grad, le estás diciendo a PyTorch que apague temporalmente el motor de tracking. Cualquier operación realizada dentro de ese bloque no se registrará. Incluso si los tensores de input se trackean normalmente, los outputs creados dentro del bloque tendrán sus gradient requirements en false. Esta es tu herramienta para ejecutar evaluación, testing o bulk predictions. Apaga el graph para todo lo que esté dentro de su scope. La segunda herramienta es el método detach. Mientras que no grad gestiona bloques de código, detach gestiona tensores individuales. Llamar a detach en un tensor devuelve un nuevo tensor que comparte exactamente los mismos datos subyacentes que el original, pero está completamente desconectado del computation graph. No tiene historial. La gente a menudo confunde cuándo usar cuál. Usa el context manager torch punto no grad cuando quieras silenciar el tracking para una secuencia de operaciones, como al pasar de training a inference. Usa el método detach cuando estés construyendo activamente un computation graph durante el training, pero necesites sacar un tensor específico de ese graph. Un caso de uso común para detach es cuando necesitas pasar un tensor a una librería de Python diferente, como NumPy, que no entiende los computation graphs de PyTorch. Primero le haces detach al tensor, quitándole la carga del tracking, y luego entregas los números en crudo. Deshabilitar el Gradient Tracking también es una técnica fundamental para congelar parámetros. Si estás haciendo fine-tuning de un modelo preentrenado masivo, probablemente no quieras entrenarlo entero desde cero. Puedes iterar por las capas base del modelo y poner sus gradient requirements en false. PyTorch deja de trackearlas por completo. Durante el backward pass, esas capas congeladas no calcularán gradientes y no se actualizarán, ahorrando cantidades masivas de memoria y acelerando drásticamente tu proceso de fine-tuning. El Gradient Tracking es maquinaria industrial pesada diseñada estrictamente para el aprendizaje. Siempre que un tensor no necesite aprender, desactiva esta maquinaria para recuperar tu memoria y velocidad. Eso es todo por este episodio. ¡Gracias por escuchar, y sigue construyendo!
6

Datasets y manejo de datos

3m 27s

Aprende a desacoplar el procesamiento de datos de la arquitectura de tu modelo utilizando la clase Dataset de PyTorch. Exploramos la carga diferida y las estructuras de datasets personalizados.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 6 de 18. Tu modelo es tan bueno como los datos que le pasas, pero ¿qué haces cuando tu dataset ocupa un terabyte y tu máquina solo tiene dieciséis gigabytes de RAM? La respuesta está en cómo haces el fetch de esos datos. Este episodio trata sobre Datasets y Data Handling. La lógica de procesamiento de datos puede volverse un lío rápidamente. Si mezclas tu código de lectura de archivos, decodificación y formateo directamente en el training loop de tu modelo, tu proyecto se vuelve frágil y difícil de mantener. PyTorch te anima a desacoplar estos conceptos. Quieres que tu preparación de datos esté completamente separada de tu algoritmo de entrenamiento. Para lograrlo, PyTorch proporciona una primitiva llamada Dataset, ubicada en el módulo torch punto utils punto data. La clase Dataset actúa como un wrapper estandarizado para tu raw data. Para gestionar tus propios archivos específicos, creas una custom class que hereda de esta primitiva. Al crear un custom dataset, debes implementar tres métodos específicos. Estos son init, len y getitem. El método init se ejecuta exactamente una vez al crear el objeto dataset. Aquí es donde configuras tus directorios y paths. Un error frecuente que cometen los principiantes es intentar cargar todos los datos reales en memoria justo aquí. No hagas esto. Si tienes cincuenta mil imágenes de alta resolución, leerlas todas en memoria durante la inicialización hará crashear tu máquina inmediatamente. En su lugar, usa init para cargar un index ligero. Por ejemplo, podrías leer un archivo CSV que contenga los nombres de archivo de las imágenes en una columna y sus labels de texto correspondientes en otra. Solo estás construyendo el mapa, no guardando el territorio. El siguiente es el método len. Este simplemente devuelve el número total de samples en tu dataset. Si tu archivo CSV tiene cincuenta mil filas, este método devuelve el número cincuenta mil. El sistema confía en esto para conocer los límites absolutos de tus datos disponibles, para no solicitar un index que no existe. El trabajo pesado ocurre en el método getitem. Esta función está diseñada para cargar y devolver un solo sample en un index específico solicitado. Cuando el sistema necesita el sample número cuarenta y dos, llama a getitem y le pasa ese número. Tu código busca la fila cuarenta y dos en el CSV que cargaste antes. Lee el string del file path de esa fila. Entonces, y solo entonces, accede al disco, encuentra el archivo y decodifica los píxeles reales de la imagen en memoria. Coge el label de esa misma fila del CSV, y devuelve la imagen y el label juntos como un tuple. Esta técnica se llama lazy loading. Solo consumes memoria para el dato específico que necesitas, justo en el momento en que estás listo para procesarlo. Al aislar esta lógica dentro del método getitem, tu training code nunca necesita saber si los datos vienen de un disco duro local, un network stream o una base de datos compleja. Simplemente solicita un index y recibe un output estandarizado. Separar el mecanismo de cómo hacer el fetch de los datos de cómo consumirlos, es la base del código de machine learning escalable. Si estos episodios te resultan útiles y quieres apoyar el programa, puedes buscar DevStoriesEU en Patreon. Eso es todo por este episodio. Gracias por escuchar, ¡y sigue construyendo!
7

DataLoaders y procesamiento por lotes

4m 27s

Libera toda la velocidad de tu hardware envolviendo los Datasets en DataLoaders. Aprende a agrupar en lotes, mezclar y multiprocesar tus flujos de datos.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 7 de 18. Las GPU son increíblemente rápidas, pero se quedarán completamente inactivas si tu CPU no puede pasarles datos lo suficientemente rápido. Los training loops suelen hacer cuello de botella no en los cálculos, sino al cargar el siguiente conjunto de archivos desde el disco. La solución a esto es separar la recuperación de datos de la ejecución del modelo usando DataLoaders y batching. Es fácil confundir los roles de un Dataset y un DataLoader. Un Dataset tiene exactamente un trabajo: obtener un solo elemento y su etiqueta. No sabe nada del proceso de entrenamiento en general. El DataLoader es un wrapper alrededor de ese Dataset. Actúa como el manager responsable de organizar esos elementos individuales en grupos, aleatorizar su orden y usar múltiples procesos para cargarlos de forma eficiente. Durante el entrenamiento, los modelos rara vez miran un solo dato a la vez. Actualizan sus pesos internos basándose en un grupo de elementos evaluados simultáneamente, conocido como minibatch. Este enfoque hace que el proceso de entrenamiento sea más estable y aprovecha al máximo la capacidad de procesamiento paralelo del hardware. Para construir un minibatch manualmente, tendrías que escribir un loop para extraer muestras individuales, apilarlas en una estructura de tensor más grande y gestionar edge cases, como que el batch final sea más pequeño que el resto. El DataLoader se encarga de todo esto automáticamente. Inicializas un DataLoader pasándole tu objeto Dataset y un parámetro llamado batch size. Si estableces el batch size en 64, el DataLoader extraerá 64 elementos distintos del Dataset, los consolidará en un único tensor y los servirá de golpe. En tu código, el DataLoader se comporta como un iterable estándar de Python. Haces un loop sobre él. Cada vez que el loop avanza, el DataLoader devuelve el siguiente batch completo de datos y el batch de etiquetas correspondiente. También le pasas un parámetro shuffle. Si una red neuronal procesa los datos de entrenamiento exactamente en la misma secuencia cada vez, podría memorizar esa secuencia específica en lugar de aprender las features reales. Poner shuffle a true le dice al DataLoader que aleatorice el orden de los elementos del dataset al inicio de cada epoch. Una vez que se ha devuelto cada batch y el dataset se ha agotado, el loop termina. La próxima vez que iteres sobre el DataLoader, generará una secuencia aleatoria totalmente nueva. Esta es la parte que importa. El DataLoader también acepta un parámetro para el número de workers. Cuando usas múltiples workers, el DataLoader levanta procesos de CPU en background para obtener los datos. Imagínate alimentar una red neuronal con esas 64 imágenes. Mientras tu GPU está ocupada calculando gradientes para el batch actual, los workers de CPU en background están leyendo, decodificando y apilando simultáneamente las siguientes 64 imágenes. Para cuando la GPU termina su paso matemático actual, el siguiente batch de datos ya está esperando en memoria. La GPU nunca se queda esperando. Un training loop de alto rendimiento aísla la realidad lenta e impredecible de las operaciones de disco de la realidad rápida y estructurada del entrenamiento del modelo. El DataLoader proporciona ese aislamiento, convirtiendo una colección de archivos independientes en un pipeline continuo y paralelizado de minibatches. Eso es todo por este episodio. ¡Gracias por escuchar, y sigue construyendo!
8

Transformaciones de datos

4m 12s

Descubre cómo preprocesar datos sin procesar sobre la marcha antes de que lleguen a tu Neural Network. Cubrimos los transforms de torchvision como ToTensor y funciones Lambda personalizadas.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 8 de 18. Las redes neuronales solo calculan números, pero tus datos del mundo real suelen ser una colección desordenada de archivos de imagen raw y categorías de texto. Si escribes loops manuales para convertir cada imagen y label antes de empezar el training, tu código se volverá rápidamente un desastre frágil e ilegible. Las Data Transformations son el mecanismo que resuelve esto, convirtiendo automáticamente tus datos raw a un formato listo para el modelo justo cuando se necesita. Los datos rara vez llegan listos para machine learning. Tienes que manipularlos a un formato tensor específico antes de pasarlos a tu red. PyTorch gestiona esto limpiamente aplicando transforms al vuelo durante el proceso de data loading. Cuando inicializas un dataset, especialmente en librerías como torchvision, defines estas modificaciones usando dos argumentos específicos. Usas el argumento transform exclusivamente para tus input features, como tus imágenes raw. Usas el argumento target transform exclusivamente para tus labels. Es fundamental mantener estos dos separados, ya que operan de forma independiente en diferentes mitades de tus datos. Veamos primero las input features. Digamos que tienes un dataset de imágenes PIL raw. Una red neuronal no puede leer un objeto de imagen PIL directamente. Para solucionar esto, pasas un transform built-in de torchvision llamado ToTensor al argumento transform. Cuando el dataset carga una imagen, ToTensor ejecuta automáticamente dos pasos. Primero, convierte la imagen PIL en un float tensor de PyTorch. Segundo, escala los valores de intensidad de los píxeles. Los píxeles de las imágenes raw generalmente van de cero a doscientos cincuenta y cinco. La operación ToTensor normaliza estos valores a un rango de float entre cero y uno. El dataset aplica esta operación estrictamente a medida que se obtiene cada imagen. Eso cubre los inputs, pero ¿qué pasa con los outputs? Los labels de tu dataset podrían ser simples integers que representan diferentes categorías. Por ejemplo, el número tres podría significar un perro. Pero para calcular el loss durante el training, tu modelo a menudo requiere que esos labels sean vectores one-hot encoded, en lugar de integers individuales. Esto significa que necesitas un array donde todos los valores sean cero, excepto el index que representa la class correcta, que se establece en uno. Para manejar custom logic como esta, PyTorch proporciona Lambda transforms. Un Lambda transform hace un wrap de cualquier función definida por el usuario para que pueda aplicarse durante el data loading. Escribes una función corta que recibe tu integer label como input. Dentro de esa función, creas un tensor de ceros que coincide con el número total de categorías en tu dataset. Luego, usas una operación interna de PyTorch para hacer un scatter de un valor de uno en el index específico que corresponde a tu integer label. Pasas esta función personalizada a un Lambda transform, y luego lo asignas al argumento target transform de tu dataset. Esto crea un pipeline altamente eficiente. Un worker thread extrae un único registro raw de tu disco. La imagen llega al argumento transform, pasa por ToTensor y emerge como un float tensor normalizado. Simultáneamente, la categoría integer llega al argumento target transform, ejecuta tu función Lambda personalizada y se convierte en un vector one-hot encoded. Ambas partes ahora están formateadas matemáticamente y se entregan directamente a tu modelo. El verdadero poder de esta arquitectura es la separation of concerns. Al adjuntar estas Data Transformations directamente a la definición del dataset, tu training loop en sí se mantiene completamente ciego a la realidad desordenada de tus archivos raw. Eso es todo por este episodio. ¡Gracias por escuchar, y sigue construyendo!
9

Diseñando redes con nn.Module

4m 02s

Explora el plano estructural de cada Neural Network de PyTorch. Aprende a crear subclases de nn.Module, definir capas en la inicialización y enrutar datos en el forward pass.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 9 de 18. Cada red neuronal en PyTorch, desde un clasificador de imágenes básico hasta un modelo de lenguaje masivo, comparte exactamente el mismo esquema subyacente. Si no comprendes cómo este esquema organiza los datos y la lógica, acabarás peleándote con el framework a cada paso. Diseñar redes con nn.Module es la forma de dominar esta estructura. nn.Module es la clase base para todos los componentes de redes neuronales en PyTorch. Actúa como un contenedor universal. Cuando construyes un modelo personalizado, creas una clase que hereda de nn.Module. Esta herencia le otorga automáticamente a tu clase la capacidad de rastrear sus propios parámetros, calcular gradientes e integrarse sin problemas con el resto del ecosistema PyTorch. También permite arquitecturas anidadas. Puedes colocar módulos dentro de otros módulos, creando un árbol de capas que el módulo padre rastrea y gestiona como una sola unidad. Imagina que estás montando el esqueleto en blanco de un clasificador de imágenes totalmente nuevo. Construir este esqueleto requiere definir dos métodos específicos: el método initialize y el método forward. PyTorch impone una estricta separación de responsabilidades entre estas dos etapas. Primero está el método initialize. Imagínalo como tu inventario. Cuando se instancia la clase, este método se ejecuta exactamente una vez. Lo usas para declarar todas las capas individuales y las operaciones matemáticas que tu modelo necesitará finalmente. Aquí no estás procesando ningún dato real. Simplemente estás cogiendo componentes estructurales de la estantería, configurando sus formas de entrada y salida, y guardándolos como variables internas dentro de tu clase. Luego está el método forward. Esta es tu línea de montaje activa. El método forward toma un tensor de entrada y dicta exactamente cómo viaja a través del inventario que acabas de declarar. Escribes la secuencia de operaciones paso a paso. Coges el tensor de imagen de entrada, lo pasas por una operación de flattening, introduces ese resultado en una serie de capas densas y, finalmente, devuelves las predicciones de salida. Cada modelo personalizado debe definir este método forward para establecer el flujo de datos. Esto nos lleva a una trampa común. Como escribiste explícitamente la lógica del flujo de datos dentro de un método llamado forward, el instinto natural es pasar tus datos llamando a model punto forward. No lo hagas. Debes llamar al modelo directamente como si fuera una función normal, pasando tu entrada directamente al objeto model instanciado. Internamente, ejecutar el objeto model directamente activa varios hooks críticos en segundo plano que PyTorch necesita para gestionar el estado de la red. Llamar directamente al método forward omite estos hooks y provocará un comportamiento inesperado durante tu training loop. Una vez que tu clase está definida y has creado un objeto model, tienes una red funcional. Sin embargo, por defecto, PyTorch crea este objeto y todos sus pesos internos en la memoria de la CPU de tu sistema. Para entrenar a velocidades realistas, necesitas enviar esta arquitectura a un acelerador. Esto lo logras comprobando si hay disponible una GPU CUDA o un chip especializado como el MPS de Apple, y asignando ese hardware de destino a una variable device. Luego, llamas al método to en tu modelo, pasándole esa variable device. Este único comando mueve inmediatamente todos los parámetros inicializados del modelo fuera de la memoria estándar y hacia la memoria de alta velocidad de tu acelerador de hardware. El rasgo definitorio de nn.Module es cómo impone una clara frontera arquitectónica entre los componentes estáticos que tu modelo posee en memoria, y la ruta dinámica que toman tus datos para ser procesados. Gracias por escuchar. Cuidaos todos.
10

Capas Linear y activaciones

4m 43s

Echa un vistazo al interior de la Neural Network. Desglosamos el módulo nn.Linear y explicamos por qué las funciones de activación no lineales como ReLU son matemáticamente esenciales.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. PyTorch Fundamentals, episodio 10 de 18. Si apilas cien capas de redes neuronales sin un truco matemático específico, toda la red colapsa matemáticamente en una única línea recta. El culpable es el álgebra lineal, y la solución requiere entender las Linear Layers y las Activations. Una red neuronal es, fundamentalmente, una secuencia de operaciones matemáticas sobre tensores. La operación más común es la Linear Layer, definida en PyTorch como nn.Linear. Este módulo aplica una transformación afín a los datos de entrada. Contiene dos tensores internos que aprende con el tiempo: los weights y los biases. Cuando los datos pasan por ella, la capa multiplica el input por la matriz de weights y le suma el bias. Coge una imagen estándar en escala de grises de 28 por 28 píxeles. Antes de que una Linear Layer pueda procesarla, haces un flatten de la cuadrícula bidimensional para convertirla en un array unidimensional de 784 números. Pasas ese array de 784 valores a una capa nn.Linear configurada para devolver un output de 512 features. Por debajo, PyTorch crea una matriz de weights que mapea los 784 inputs a 512 outputs. Multiplica los valores de tus píxeles por estos weights, los suma, añade un término de bias para desplazar el resultado, y devuelve 512 números nuevos. Durante el training, PyTorch actualiza continuamente estos weights y biases. Estos forman la verdadera memoria de tu modelo. Podrías pensar que una red neuronal profunda es simplemente una larga secuencia de estas Linear Layers apiladas una tras otra. Esta es la parte que importa. Si encadenas varias operaciones nn.Linear juntas sin nada entre medias, las matemáticas se simplifican. La matriz A multiplicada por la matriz B es simplemente otra matriz, C. Apilar diez Linear Layers tiene exactamente la misma capacidad matemática que calcular una sola Linear Layer. Tu red profunda se reduce a una ecuación lineal plana, completamente incapaz de aprender patrones complejos del mundo real. Para detener este colapso matemático, introduces una no linealidad inmediatamente después de la Linear Layer. A estas se las llama activation functions. La activation más utilizada en PyTorch es nn.ReLU, que significa Rectified Linear Unit. Después de que la Linear Layer calcule sus 512 outputs, pasas ese tensor directamente a una función ReLU. La lógica de ReLU es brutalmente simple. Mira cada número en el tensor. Si un número es menor que cero, ReLU lo cambia exactamente a cero. Si un número es cero o positivo, ReLU lo deja tal cual. Ese único codo en el cero destruye la linealidad. Impide que la siguiente Linear Layer se fusione matemáticamente con la anterior. Al forzar los valores negativos a cero, ReLU también crea representaciones sparse. Esto significa que solo un subconjunto específico de neuronas se activa para un input dado, haciendo que la red sea altamente eficiente. El flujo de datos es consistente. Tu imagen tras el flatten entra en la Linear Layer, es transformada por los weights y biases, y luego llega a la activation ReLU, donde los outputs negativos se eliminan. Luego puedes pasar tranquilamente este tensor activado a una segunda Linear Layer para extraer patrones más profundos y abstractos. Una Linear Layer determina cuánta importancia matemática darle a cada input, pero la activation function le da a la red la geometría real necesaria para aprender las formas impredecibles de los datos reales. Gracias por pasar unos minutos conmigo. Hasta la próxima, cuídate.
11

El contenedor nn.Sequential

4m 03s

Optimiza tu código de PyTorch usando el contenedor nn.Sequential. Aprende a encajar capas de forma limpia y a inspeccionar los parámetros de tu modelo.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 11 de 18. Escribir métodos forward personalizados para cada red neuronal se vuelve tedioso rápidamente cuando solo estás apilando capas estándar. No siempre necesitas enrutar manualmente los datos de una función a la siguiente. A veces, solo necesitas encajar las capas como si fueran piezas de LEGO. Eso es exactamente lo que hace el contenedor nn.Sequential. El contenedor nn.Sequential es un pipeline ordenado de módulos de redes neuronales. Cuando pasas datos a este contenedor, los datos fluyen a través de los módulos internos en la secuencia exacta en la que se añadieron. Piensa en montar un Multilayer Perceptron estándar de tres capas. Normalmente, definirías tus capas lineales y funciones de activación en un método de inicialización, y luego escribirías un método forward personalizado. En ese método forward, cogerías explícitamente el input, lo pasarías a la capa uno, lo envolverías en una activación ReLU, pasarías ese resultado a la capa dos, aplicarías otro ReLU y se lo pasarías a la capa final. Con Sequential, te saltas por completo el método forward. Instancias el contenedor y le pasas tus módulos directamente como argumentos. Le pasas un módulo Linear, seguido de un módulo ReLU, un segundo módulo Linear, otro ReLU y un módulo Linear final. PyTorch gestiona automáticamente el routing de los datos. El output del primer módulo se convierte al instante en el input del segundo, avanzando automáticamente por la chain. Este contenedor es muy eficiente, pero tiene una limitación estricta. Sirve estrictamente para un flujo de datos lineal y directo. No puede manejar arquitecturas complejas que requieran branching, múltiples inputs o skip connections. Si estás construyendo algo como una Residual Network, donde los datos se saltan ciertas capas y se vuelven a sumar más adelante, Sequential no te servirá. Para cualquier topología no lineal, sigues teniendo que escribir un módulo personalizado con un método forward dedicado. Una vez que has encadenado tus capas, a menudo necesitas inspeccionar lo que acabas de construir. Cada capa de tu contenedor Sequential es una subclase de nn.Module, lo que significa que PyTorch registra y hace el tracking automáticamente de todo el estado subyacente. Para ver este estado, usas el método named_parameters. Llamar a named_parameters en tu modelo te da un iterador sobre todos los pesos y biases de su interior. Cada elemento que devuelve es un par simple: el nombre del parámetro y el propio tensor del parámetro. Como usaste un contenedor Sequential sin nombrar explícitamente tus capas, PyTorch genera nombres numéricos basados en su índice. Verás nombres como zero dot weight para los pesos de la primera capa lineal, o zero dot bias para sus términos de bias. El tensor que lo acompaña contiene los valores numéricos reales, el shape de la matriz, y si requiere el cálculo del gradiente. Hacer un loop por named_parameters es la forma estándar de verificar tu arquitectura. Puedes hacer un print rápidamente del tamaño de cada matriz de pesos para confirmar que tus dimensiones de input y output se alinean perfectamente en toda la chain antes de empezar a pasar datos reales por el sistema. El verdadero poder del contenedor Sequential combinado con el tracking de parámetros es que PyTorch absorbe el trabajo de la gestión del estado y el routing de datos, permitiéndote centrarte por completo en el shape de tu red. Eso es todo por este episodio. ¡Nos vemos en el próximo!
12

Entendiendo las Loss Functions

3m 35s

Antes de que una IA pueda aprender, debe medir sus errores. Nos sumergimos en las Loss Functions de PyTorch, comparando CrossEntropyLoss para clasificación y MSELoss para regresión.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. PyTorch Fundamentals, episodio 12 de 18. Para enseñar a una red neuronal a acertar, primero tienes que medir de forma rigurosa exactamente cuánto se equivoca. Si no puedes cuantificar el fallo, tu modelo no puede aprender de él. Esto nos lleva a entender las loss functions. Cuando una red sin entrenar procesa datos, su output es esencialmente una suposición. Una loss function evalúa esa suposición. Mide el grado de diferencia entre el resultado que produjo el modelo y la verdad absoluta del target value. El output de una loss function siempre es un único número escalar. Todo tu proceso de entrenamiento existe para acercar ese único número lo máximo posible a cero. Como las diferentes tareas de machine learning tienen distintas definiciones de equivocarse, PyTorch ofrece varias loss functions. Si estás creando un modelo de regresión para predecir un valor continuo, como la temperatura de mañana, mides la distancia entre tu suposición y la temperatura real. Para esto, usas el Mean Square Error, que en PyTorch se llama nn.MSELoss. Pero la clasificación es diferente. Supón que tienes un modelo que categoriza imágenes de ropa en diez categorías de moda. El modelo analiza la imagen de un abrigo y devuelve diez raw scores, uno para cada categoría posible. Estos raw scores sin normalizar se llaman logits. La respuesta correcta es solo un único número entero, que representa la clase correcta. No puedes simplemente restarle un index de clase a un raw score. En su lugar, necesitas una función que penalice al modelo por darle scores bajos a la clase correcta y scores altos a las clases incorrectas. Para clasificación, la herramienta estándar es nn.CrossEntropyLoss. Inicializas tu loss function, le pasas los diez logits raw de tu modelo junto con la label entera correcta, y te devuelve tu penalización escalar. Esta es la parte que importa. Aquí hay una trampa enorme para los desarrolladores. En muchos libros de texto de machine learning, una red de clasificación termina con una capa softmax. Softmax fuerza los logits raw a una distribución de probabilidad ordenada donde todos los scores suman exactamente uno. Por esto, los desarrolladores suelen añadir manualmente una operación softmax justo al final de su modelo de PyTorch. Si estás usando nn.CrossEntropyLoss, hacer eso es un error. En PyTorch, nn.CrossEntropyLoss aplica automáticamente una función LogSoftmax internamente antes de calcular el negative log likelihood. Está construida para aceptar logits raw sin normalizar directamente. Si tu modelo devuelve probabilidades porque ya aplicaste softmax, pasarlas a nn.CrossEntropyLoss significa que estás aplicando las matemáticas dos veces. Esto comprime tus gradients, ralentiza drásticamente el entrenamiento y arruina la capacidad de tu modelo para aprender de forma efectiva. La regla que debes recordar es que tu red neuronal simplemente debería devolver números raw. Mantén los outputs de tu modelo raw, pásalos directamente a nn.CrossEntropyLoss, y deja que PyTorch haga el trabajo pesado de convertir esos logits en una penalización significativa. Gracias por escuchar, ¡happy coding a todos!
13

Optimizers y descenso de gradiente

3m 50s

Explora cómo el Optimizer actualiza los pesos del modelo para reducir el error. Aprende el crucial baile de tres pasos de zero_grad(), backward() y step().

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 13 de 18. El bug más común en el training de PyTorch no son los malos datos o una arquitectura incorrecta. Es olvidarte de limpiar tus cálculos matemáticos anteriores, haciendo que tu red se descontrole. Hoy cubrimos los optimizers y el Gradient Descent, que manejan exactamente cómo tu modelo aprende de sus errores. Tu modelo hace una predicción, y tú calculas la loss para ver cuánto se ha desviado. Ahora necesitas ajustar los weights internos de la red neuronal para que la siguiente predicción sea un poco más precisa. Este proceso de ajustar los parámetros para minimizar la loss se llama optimization. El optimizer es el algoritmo específico que controla cómo cambian esos weights. Para configurar un optimizer, tienes que darle dos cosas. Primero, le pasas un iterable que contiene los parámetros del modelo que quieres que ajuste. Segundo, le proporcionas un learning rate. El learning rate es un hyperparameter fundamental que controla la magnitud de los cambios aplicados a los weights. Si el learning rate es demasiado pequeño, el optimizer da pasos microscópicos, haciendo que el training sea dolorosamente lento. Si el learning rate es demasiado grande, el optimizer se pasa de los valores óptimos, lo que lleva a un comportamiento salvaje e impredecible. Un algoritmo estándar para esta tarea es el Stochastic Gradient Descent, o SGD. Evalúa la pendiente de tu loss function y da un step en la dirección opuesta para descender hacia el menor error posible. Una vez que inicializas tu optimizer SGD con tus parámetros y tu learning rate, las actualizaciones reales ocurren en una secuencia estricta de tres pasos. El paso uno es limpiar la pizarra. Llamas al comando zero grad en el optimizer. Aquí es donde vive ese bug tan común. PyTorch acumula los gradients por defecto. Cuando calcula nuevos gradients, no sobrescribe los antiguos; simplemente suma los nuevos números a los totales existentes. Si te saltas este paso de zero grad, las matemáticas de tu batch actual se corrompen con los números sobrantes del batch anterior. Pon siempre a cero los gradients antes de hacer cualquier otra cosa. El paso dos es calcular los nuevos gradients. Tomas tu valor de loss calculado y llamas al comando backward sobre él. Esto activa la backpropagation. PyTorch viaja hacia atrás a través de la arquitectura de tu red. Calcula la derivada de la loss con respecto a todos y cada uno de los parámetros. Básicamente, averigua exactamente cuánto contribuyó cada weight individual al error general. Estos gradients calculados se guardan directamente dentro de los objetos de los parámetros. El paso tres es aplicar la corrección. Llamas al comando step en el optimizer. El optimizer mira los gradients almacenados en cada parámetro durante el backward pass. Multiplica esos gradients por el learning rate para averiguar el tamaño exacto del ajuste, y luego actualiza los weights reales en memoria. Este ciclo se repite para cada batch. Pon a cero los gradients, calcula la backward loss, haz step en el optimizer. El detalle crítico a recordar es que el optimizer solo actualiza los parámetros que se le dieron explícitamente durante la configuración. Si necesitas hacer freeze a una capa de tu red, simplemente excluyes sus parámetros cuando inicializas el optimizer, y esos weights permanecerán fijos permanentemente. Por cierto, si queréis apoyar el programa, podéis buscar DevStoriesEU en Patreon. Gracias por escuchar. Cuidaos todos.
15

Validación e inferencia

4m 05s

Evalúa tu modelo objetivamente. Aprende a cambiar tu red al modo de evaluación, congelar gradientes y extraer predicciones precisas sobre datos no vistos.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. PyTorch Fundamentals, episodio 15 de 18. Tu modelo puede funcionar a la perfección con su training data, pero la única prueba real de una IA es cómo maneja lo desconocido. Si te basas únicamente en el feedback que ve tu optimizer, podrías estar creando un banco de memoria muy caro. Para comprobar si tu modelo realmente generaliza al mundo real, necesitas validación e inferencia. Durante la fase de training, te fijas en la training loss. Ese número existe para guiar al optimizer interno. Obliga al modelo a ajustar sus pesos hasta que el error matemático se reduce. Pero una training loss baja no significa que tengas un buen modelo. Simplemente significa que el modelo es muy bueno respondiendo a preguntas que ya ha visto. La validation accuracy es una métrica completamente distinta que nos dice a los humanos si el modelo puede hacer predicciones correctas con datos completamente nuevos. Para obtener esta métrica, tienes que ejecutar un validation loop contra un test dataset dedicado. Antes de pasarle un solo dato de test a la red, tienes que cambiar el estado del modelo. Esto lo haces llamando al método eval en tu objeto model. Llamar a eval cambia la red al modo de evaluación. Ciertas capas internas se comportan de forma diferente durante el training que durante la inferencia. Llamar a eval las obliga a bloquear su comportamiento para que tus predicciones sigan siendo consistentes. Si te saltas este paso, tus resultados de test serán fundamentalmente poco fiables. Eso cubre el estado del modelo. A continuación, tienes que controlar el propio motor desactivando el gradient tracking. Esto lo haces envolviendo tu código de validación dentro de un context manager no grad. Durante el training, PyTorch construye constantemente un computational graph en memoria, guardando el historial de cada operación para poder calcular los gradients más tarde. En un validation loop, ya has terminado por completo con el training. No quieres actualizar los pesos. El bloque no grad le dice a PyTorch que deje de rastrear el historial. Esta es la parte que importa. Desactivar el tracking evita actualizaciones accidentales en tu modelo, pero también libera una cantidad enorme de memoria y acelera drásticamente la computación. Dentro de ese bloque no grad, la lógica en sí es muy sencilla. Iteras sobre tu test dataset en batches. Para cada batch, pasas los datos de entrada por el modelo. El modelo calcula el forward pass y devuelve sus predicciones raw. Si estás haciendo clasificación, el modelo no devuelve una etiqueta de texto limpia. En su lugar, devuelve una lista de puntuaciones numéricas para cada una de las categorías que conoce. Para averiguar qué categoría eligió realmente el modelo, necesitas la función argmax. Argmax examina la lista de puntuaciones raw y encuentra el número más alto. Luego devuelve la posición del index de esa puntuación más alta. Ese index es tu predicción de clase elegida. Una vez que tienes las predicciones del modelo, las comparas directamente con las true labels proporcionadas por el test dataset. Cuentas exactamente cuántas predicciones coinciden con las true labels. Llevas un total acumulado de estas coincidencias correctas a lo largo de todos los batches. Cuando el loop termina, divides el número total de predicciones correctas entre el número total de elementos en el test dataset. El resultado es tu porcentaje de accuracy final. El training loop obliga a tu modelo a ajustarse a los datos históricos, pero las estrictas restricciones del validation loop demuestran si ese modelo es realmente útil. Gracias por escuchar. Cuidaos todos.
16

Guardando y cargando modelos

3m 43s

¡No pierdas el progreso que tanto te ha costado! Hablamos de las formas más seguras de serializar los pesos de tu modelo usando state_dict y volver a cargarlos de forma segura.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 16 de 18. Entrenar un clasificador de imágenes durante cincuenta épocas puede llevar semanas y consumir miles de dólares en computación. Sin embargo, en el momento en que tu script de Python termina de ejecutarse, todos esos patrones que tanto ha costado conseguir desaparecen por completo de la memoria. Para proteger esa inversión, necesitas una forma de guardar tu progreso en disco. Eso es precisamente lo que resuelve guardar y cargar modelos. Dentro de cada modelo de PyTorch hay un diccionario interno llamado state dict. Este diccionario mapea cada capa de tu red a sus correspondientes tensores de parámetros. Contiene los pesos y sesgos reales que tu modelo aprendió durante el entrenamiento. La estructura del modelo es solo código, pero el state dict es la inteligencia. Para persistir tu modelo, extraes este diccionario y lo guardas en un archivo. Esto lo haces usando una función llamada torch punto save. Le pasas dos cosas. Primero, el state dict de tu modelo. Segundo, la ruta del archivo donde quieres guardarlo, que tradicionalmente usa la extensión punto pth. Con una sola línea de código, tu entrenamiento de cincuenta épocas se guarda de forma segura en tu disco duro como un único archivo que contiene solo datos de tensores en crudo. Es posible que veas ejemplos online que omiten por completo el state dict y pasan directamente el objeto del modelo a torch punto save. No hagas esto. Guardar el modelo completo depende en gran medida de la serialización pickle de Python. Esto vincula el archivo guardado a la estructura de directorios y definiciones de clase exactas que estaban presentes cuando se creó el archivo. Si más adelante refactorizas tu código o mueves un archivo, el modelo fallará al cargar. Ceñirse al state dict es mucho más seguro y significativamente más robusto, ya que solo estás guardando los datos, no el código. Cuando llega el momento de hacer deploy de tu clasificador para inferencia en producción, tienes que invertir el proceso. Como solo guardaste los pesos, PyTorch necesita saber cómo es la estructura de la red. Empiezas instanciando una versión completamente en blanco de tu clase de modelo. Esto te proporciona el esqueleto de la arquitectura. A continuación, llamas a torch punto load y le pasas la ruta de tu archivo para volver a leer el diccionario en memoria. Cuando llamas a torch punto load, hay una buena práctica moderna y crucial que debes seguir. Pasa siempre el argumento weights only puesto a true. Los archivos pickle de Python pueden contener código ejecutable arbitrario. Si descargas un modelo preentrenado de internet y lo cargas a ciegas, podría ejecutar scripts maliciosos en tu máquina. Poner weights only a true restringe el loader para que solo deserialice tensores estándar de PyTorch, manteniendo tu sistema seguro. Finalmente, con tu modelo en blanco listo y tu diccionario seguro cargado, llamas a load state dict en el modelo y le pasas el diccionario. PyTorch mapea los pesos cargados a las capas correspondientes en el esqueleto en blanco. Tu modelo ya está completamente restaurado y listo para hacer predicciones. Nunca confíes tu inversión en entrenamiento a un objeto serializado frágil; separa siempre la arquitectura en tu código de los parámetros aprendidos en tu disco. Gracias por estar ahí. Espero que hayas aprendido algo nuevo.
17

Acelerando la velocidad con torch.compile

3m 48s

Desbloquea la característica definitoria de PyTorch 2.0. Aprende cómo el decorador torch.compile realiza una compilación JIT de tu código Python en kernels optimizados para lograr aceleraciones masivas.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 17 de 18. Pasas semanas ajustando la arquitectura de un modelo para lograr una mejora de velocidad del cinco por ciento. Pero a menudo, el verdadero bottleneck no son tus cálculos matemáticos. Es el propio Python, y las constantes e ineficientes transferencias de datos entre tu memoria y la GPU. Solucionar esto no requiere reescribir tu codebase. Hoy vamos a ver cómo potenciar la velocidad con torch dot compile. Introducida en PyTorch 2.0, esta feature pasa tu código de una ejecución estándar a un workflow altamente optimizado. Se suele pensar que acelerar PyTorch para producción implica escribir custom kernels en C plus plus o cambiar fundamentalmente tu arquitectura. No es el caso. No cambias nada dentro de tu modelo. Simplemente envuelves tu modelo existente en una única function call. Para entender por qué esto genera un salto tan grande en performance, tienes que ver cómo se ejecuta PyTorch normalmente. El PyTorch estándar opera en eager mode. Ejecuta exactamente lo que le dices, justo cuando se lo pides, una operación cada vez. Si tu código le dice a la GPU que sume dos tensores, multiplique el resultado por otro tensor y aplique una función de activación, el eager mode trata eso como tres eventos aislados. En cada paso, la GPU lee datos de su memoria principal, hace los cálculos y vuelve a escribir el resultado intermedio. El ancho de banda de la memoria de la GPU es limitado. Ese constante trasiego de datos tarda mucho más que los propios cálculos. Cuando pasas tu modelo por la función compile, PyTorch cambia de táctica. Utiliza una herramienta interna llamada TorchDynamo para capturar tus operaciones en un computation graph antes de ejecutarlas. Al ver la secuencia completa, encuentra ineficiencias. Luego, usa un backend compiler para generar una nueva versión, fuertemente optimizada, de tus operaciones. La técnica principal que usa es la kernel fusion. En lugar de leer y escribir en memoria tres veces por separado, el código compilado fusiona esos pasos. La GPU lee los datos una vez, los mantiene en sus registros internos más rápidos, hace la suma, la multiplicación y la activación de forma consecutiva, y luego escribe el resultado final una sola vez. El overhead de Python desaparece, y se evita el bottleneck de memoria. La implementación es muy sencilla. Instancias tu modelo como siempre. Luego, llamas a torch dot compile, le pasas tu modelo, y asignas el output a una nueva variable. Pasas tus datos a través de esta versión compilada. Si TorchDynamo encuentra un construct de Python raro que no puede optimizar de forma segura, no rompe tu programa. Simplemente deja esa pequeña sección en eager mode estándar y compila el resto. Cuando hagas un benchmark de esto, presta atención a la primera ejecución. La pasada inicial tarda bastante más porque la compilación real ocurre exactamente cuando llegan los primeros datos. Pero en la segunda pasada, el tiempo de inferencia cae drásticamente. Ya no tienes que elegir entre la flexibilidad del eager mode durante el desarrollo y la velocidad bruta de un backend compilado en producción. Eso es todo por este episodio. Gracias por escuchar, ¡y sigue construyendo!
18

Compiladores y graph breaks

3m 56s

Sumérgete bajo el capó del compilador de PyTorch. Exploramos los graph breaks, el flujo de control dinámico y por qué torch.compile triunfa donde los sistemas heredados fracasaron.

Descargar
Hola, soy Alex de DEV STORIES DOT EU. Fundamentos de PyTorch, episodio 18 de 18. Los compiladores de IA antiguos exigían execution paths perfectamente predecibles, y fallaban estrepitosamente si incluías estructuras dinámicas y complejas de Python en tu modelo. Modificabas un conditional statement, y todo el proceso de compilación crasheaba. PyTorch dos punto cero maneja ese mismo código arbitrario sin quejarse. El motor detrás de esta flexibilidad se basa en cómo el compilador gestiona los graph breaks. Si trabajaste con la herramienta de compilación legacy, TorchScript, sabes que requería estructuras de código rígidas. TorchScript se basaba en un static typing estricto y una ejecución predecible. Si tu modelo tenía un control flow muy dinámico, dependía de diccionarios estándar de Python, o llamaba a librerías externas que no eran de tensores, TorchScript solía rechazarlo. Los ingenieros a menudo tenían que reescribir partes importantes de la arquitectura de su modelo solo para satisfacer al compilador. PyTorch dos punto cero aborda esto de una forma completamente distinta. En lugar de exigir código estático por adelantado, el compilador nativo analiza tu ejecución de Python de forma dinámica. Captura todas las operaciones matemáticas que puede optimizar de forma segura y las empaqueta en un computational graph muy eficiente. Inevitablemente, el compilador se encontrará con código que no puede mapear fácilmente a una estructura de graph optimizada. Cuando se topa con esta lógica impredecible, lanza un graph break. Un graph break no es un error, y no es un crash. Es simplemente un mecanismo de fallback. Significa que el compilador devuelve el control de forma elegante a la eager execution estándar de PyTorch para ese segmento específico de código. Imagina una función en la que ejecutas una serie de multiplicaciones de matrices pesadas, seguidas de un if-statement de Python que comprueba el valor medio de un tensor para decidir la siguiente operación. Esa condición depende de los datos. El execution path es completamente desconocido hasta el momento exacto en que los valores del tensor se calculan en runtime. Cuando haces un trace de esta función con el compilador, este analiza el flujo. Coge las multiplicaciones de matrices que ocurren antes de la condición y las compila en un sub-graph rápido y optimizado. Luego, llega al complicado if-statement. Como no puede predecir el resultado, crea un graph break. El compilador deja que el Python estándar ejecute la condición en eager mode. Una vez que se evalúa la condición y se elige la ruta, el compilador retoma el control, cogiendo las operaciones restantes y compilándolas en un segundo sub-graph optimizado. El sistema segmenta automáticamente tu código. Obtienes islas compiladas de matemáticas rápidas separadas por puentes de Python estándar. Tu modelo sigue ejecutándose sin problemas. Esta es la parte que importa. Es probable que veas logs de rendimiento señalando graph breaks en tu arquitectura. Aunque por lo general quieres minimizarlos para exprimir al máximo la velocidad de ejecución, existen puramente como una red de seguridad. Garantizan que tu código siempre produzca el resultado matemático correcto, incluso si no se puede fusionar cada línea en un único kernel. El principal cambio de diseño en la compilación moderna de PyTorch es priorizar una ejecución fluida sobre la optimización total, asegurando que el motor se adapte a tu lógica arbitraria en lugar de obligar a tu lógica a adaptarse al motor. Con esto concluye nuestra serie de PyTorch. Te animo encarecidamente a que explores la documentación oficial, pruebes estas herramientas de compilación de forma práctica, y visites DEV STORIES DOT EU para sugerir temas para futuras series. Me gustaría tomarme un momento para darte las gracias por escuchar; nos ayuda muchísimo. ¡Que tengas un gran día!