Phase 01 - Lesson 02
Vectores, Matrices y Operaciones
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Toda red neuronal es solo multiplicación de matrices con pasos adicionales.
Tipo: Construir Lenguajes: Python, Julia Requisitos previos: Fase 1, Lección 01 (Intuición de Álgebra Lineal) Tiempo: ~60 minutos
Objetivos de Aprendizaje
- Construir una clase Matrix con operaciones elemento a elemento, multiplicación de matrices, transpuesta, determinante e inversa
- Distinguir la multiplicación elemento a elemento de la multiplicación de matrices y explicar cuando aplica cada una
- Implementar una única capa densa de red neuronal (
relu(W @ x + b)) usando solo la clase Matrix hecha desde cero - Explicar las reglas de broadcasting y cómo funciona la suma de bias en los frameworks de redes neuronales
El Problema
Quieres construir una red neuronal. Lees el código y ves esto:
output = activation(weights @ input + bias)
Ese @ es multiplicación de matrices. Los weights son una matriz. El input es un vector. Si no sabes qué hacen esas operaciones, esta línea es magia. Si lo sabes, es todo el forward pass de una capa en tres operaciones.
Cada imagen que tu modelo procesa es una matriz de valores de píxel. Cada embedding de palabra es un vector. Cada capa de cada red neuronal es una transformación matricial. No puedes construir sistemas de IA sin ser fluido en operaciones de matrices, de la misma forma que no puedes escribir código sin entender variables.
Esta lección construye esa fluidez desde cero.
El Concepto
Vectores: listas ordenadas de números
Un vector es una lista de números con dirección y magnitud. En IA, los vectores representan puntos de datos, características o parámetros.
v = [3, 4] -- a 2D vector
w = [1, 0, -2] -- a 3D vector
Un vector 2D [3, 4] apunta a las coordenadas (3, 4) en un plano. Su longitud (magnitud) es 5 (el triangulo 3-4-5).
Matrices: cuadrículas de números
Una matriz es una cuadrícula 2D. Filas y columnas. Una matriz m x n tiene m filas y n columnas.
A = | 1 2 3 | -- 2x3 matrix (2 rows, 3 columns)
| 4 5 6 |
En las redes neuronales, las matrices de pesos transforman vectores de entrada en vectores de salida. Una capa con 784 entradas y 128 salidas usa una matriz de pesos 128x784.
Por qué importan los formatos
La multiplicación de matrices tiene una regla estricta: (m x n) @ (n x p) = (m x p). Las dimensiones internas deben coincidir.
(128 x 784) @ (784 x 1) = (128 x 1)
weights input output
Inner dimensions: 784 = 784 -- valid
Si recibes un error de incompatibilidad de formato en PyTorch, esta es la razón.
El mapa de las operaciones
| Operación | Qué hace | Uso en redes neuronales |
|---|---|---|
| Suma | Combinar elemento a elemento | Sumar bias a la salida |
| Multiplicación por escalar | Escalar cada elemento | Tasa de aprendizaje * gradientes |
| Multiplicación de matrices | Transformar vectores | Forward pass de la capa |
| Transpuesta | Intercambiar filas y columnas | Backpropagation |
| Determinante | Resumen en un solo número | Verificar invertibilidad |
| Inversa | Deshacer una transformación | Resolver sistemas lineales |
| Identidad | Matriz que no hace nada | Inicialización, conexiones residuales |
Elemento a elemento vs multiplicación de matrices
Esta distinción confunde a los principiantes constantemente.
Elemento a elemento: multiplicar posiciones correspondientes. Ambas matrices deben tener el mismo formato.
| 1 2 | | 5 6 | | 5 12 |
| 3 4 | * | 7 8 | = | 21 32 |
Multiplicación de matrices: productos punto de filas y columnas. Las dimensiones internas deben coincidir.
| 1 2 | | 5 6 | | 1*5+2*7 1*6+2*8 | | 19 22 |
| 3 4 | @ | 7 8 | = | 3*5+4*7 3*6+4*8 | = | 43 50 |
Operaciones diferentes, resultados diferentes, reglas diferentes.
Broadcasting
Cuando sumas un vector de bias a una matriz de salidas, los formatos no coinciden. El broadcasting estira el array más pequeño para que encaje.
| 1 2 3 | + [10, 20, 30]
| 4 5 6 |
Broadcasting stretches the vector across rows:
| 1 2 3 | | 10 20 30 | | 11 22 33 |
| 4 5 6 | + | 10 20 30 | = | 14 25 36 |
Todo framework moderno hace esto automáticamente. Entenderlo evita confusión cuando los formatos parecen incorrectos pero el código funciona.
Construye
Paso 1: Clase Vector
class Vector:
def __init__(self, data):
self.data = list(data)
self.size = len(self.data)
def __repr__(self):
return f"Vector({self.data})"
def __add__(self, other):
return Vector([a + b for a, b in zip(self.data, other.data)])
def __sub__(self, other):
return Vector([a - b for a, b in zip(self.data, other.data)])
def __mul__(self, scalar):
return Vector([x * scalar for x in self.data])
def dot(self, other):
return sum(a * b for a, b in zip(self.data, other.data))
def magnitude(self):
return sum(x ** 2 for x in self.data) ** 0.5
Paso 2: Clase Matrix con operaciones principales
class Matrix:
def __init__(self, data):
self.data = [list(row) for row in data]
self.rows = len(self.data)
self.cols = len(self.data[0])
self.shape = (self.rows, self.cols)
def __repr__(self):
rows_str = "\n ".join(str(row) for row in self.data)
return f"Matrix({self.shape}):\n {rows_str}"
def __add__(self, other):
return Matrix([
[self.data[i][j] + other.data[i][j] for j in range(self.cols)]
for i in range(self.rows)
])
def __sub__(self, other):
return Matrix([
[self.data[i][j] - other.data[i][j] for j in range(self.cols)]
for i in range(self.rows)
])
def scalar_multiply(self, scalar):
return Matrix([
[self.data[i][j] * scalar for j in range(self.cols)]
for i in range(self.rows)
])
def element_wise_multiply(self, other):
return Matrix([
[self.data[i][j] * other.data[i][j] for j in range(self.cols)]
for i in range(self.rows)
])
def matmul(self, other):
return Matrix([
[
sum(self.data[i][k] * other.data[k][j] for k in range(self.cols))
for j in range(other.cols)
]
for i in range(self.rows)
])
def transpose(self):
return Matrix([
[self.data[j][i] for j in range(self.rows)]
for i in range(self.cols)
])
def determinant(self):
if self.shape == (1, 1):
return self.data[0][0]
if self.shape == (2, 2):
return self.data[0][0] * self.data[1][1] - self.data[0][1] * self.data[1][0]
det = 0
for j in range(self.cols):
minor = Matrix([
[self.data[i][k] for k in range(self.cols) if k != j]
for i in range(1, self.rows)
])
det += ((-1) ** j) * self.data[0][j] * minor.determinant()
return det
def inverse_2x2(self):
det = self.determinant()
if det == 0:
raise ValueError("Matrix is singular, no inverse exists")
return Matrix([
[self.data[1][1] / det, -self.data[0][1] / det],
[-self.data[1][0] / det, self.data[0][0] / det]
])
@staticmethod
def identity(n):
return Matrix([
[1 if i == j else 0 for j in range(n)]
for i in range(n)
])
Paso 3: Velo funcionar
A = Matrix([[1, 2], [3, 4]])
B = Matrix([[5, 6], [7, 8]])
print("A + B =", (A + B).data)
print("A @ B =", A.matmul(B).data)
print("A^T =", A.transpose().data)
print("det(A) =", A.determinant())
print("A^-1 =", A.inverse_2x2().data)
I = Matrix.identity(2)
print("A @ A^-1 =", A.matmul(A.inverse_2x2()).data)
Paso 4: Conecta con redes neuronales
import random
inputs = Matrix([[0.5], [0.8], [0.2]])
weights = Matrix([
[random.uniform(-1, 1) for _ in range(3)]
for _ in range(2)
])
bias = Matrix([[0.1], [0.1]])
def relu_matrix(m):
return Matrix([[max(0, val) for val in row] for row in m.data])
pre_activation = weights.matmul(inputs) + bias
output = relu_matrix(pre_activation)
print(f"Input shape: {inputs.shape}")
print(f"Weight shape: {weights.shape}")
print(f"Output shape: {output.shape}")
print(f"Output: {output.data}")
Esta es una única capa densa: output = relu(W @ x + b). Cada capa densa de cada red neuronal hace exactamente esto.
Usalo
NumPy hace todo lo anterior en menos líneas y ordenes de magnitud más rápido.
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print("A + B =\n", A + B)
print("A * B (element-wise) =\n", A * B)
print("A @ B (matrix multiply) =\n", A @ B)
print("A^T =\n", A.T)
print("det(A) =", np.linalg.det(A))
print("A^-1 =\n", np.linalg.inv(A))
print("I =\n", np.eye(2))
inputs = np.random.randn(3, 1)
weights = np.random.randn(2, 3)
bias = np.array([[0.1], [0.1]])
output = np.maximum(0, weights @ inputs + bias)
print(f"\nNeural network layer: {weights.shape} @ {inputs.shape} = {output.shape}")
print(f"Output:\n{output}")
El operador @ en Python llama a __matmul__. NumPy lo implementa con rutinas BLAS optimizadas escritas en C y Fortran. La misma matemática, 100x más rápido.
Broadcasting en NumPy:
matrix = np.array([[1, 2, 3], [4, 5, 6]])
bias = np.array([10, 20, 30])
print(matrix + bias)
NumPy hace broadcasting del bias 1D automáticamente en ambas filas. Así es como funciona la suma de bias en todo framework de red neuronal.
Entrégalo
Esta lección produce un prompt para enseñar operaciones de matrices mediante intuición geométrica. Ver outputs/prompt-matrix-operations.md.
La clase Matrix construida aquí es la base para el mini framework de red neuronal que construimos en la Fase 3, Lección 10.
Ejercicios
Verifica la inversa. Multiplica
A @ A.inverse_2x2()y confirma que obtienes la matriz identidad. Pruébalo con tres matrices 2x2 diferentes. ¿Qué ocurre cuando el determinante es cero?Implementa la inversa 3x3. Extiende la clase Matrix para calcular inversas de matrices 3x3 usando el método de la adjunta. Pruébala contra el
np.linalg.invde NumPy.Construye una red de dos capas. Usando solo tu clase Matrix (sin NumPy), crea una red neuronal de dos capas: entrada (3) -> oculta (4) -> salida (2). Inicializa pesos aleatorios, ejecuta un forward pass y verifica que todos los formatos sean correctos.
Términos Clave
| Término | Lo que dice la gente | Lo que realmente significa |
|---|---|---|
| Vector | "Una flecha" | Una lista ordenada de números. En IA: un punto en un espacio de alta dimensión. |
| Matriz | "Una tabla de números" | Una transformación lineal. Mapea vectores de un espacio a otro. |
| Multiplicación de matrices | "Solo multiplicar los números" | Productos punto entre cada fila de la primera matriz y cada columna de la segunda. El orden importa. |
| Transpuesta | "Voltearla" | Intercambiar filas y columnas. Convierte una matriz m x n en n x m. Crítico en backpropagation. |
| Determinante | "Algún número de la matriz" | Mide cuánto escala la matriz el área (2D) o el volumen (3D). Cero significa que la transformación colapsa una dimensión. |
| Inversa | "Deshacer la matriz" | La matriz que revierte la transformación. Solo existe cuando el determinante no es cero. |
| Matriz identidad | "La matriz aburrida" | El equivalente matricial de multiplicar por 1. Se usa en conexiones residuales (ResNets). |
| Broadcasting | "Arreglo mágico de formato" | Estirar un array más pequeño para que coincida con uno más grande, repitiendo a lo largo de las dimensiones ausentes. |
| Elemento a elemento | "Multiplicación normal" | Multiplicar posiciones correspondientes. Ambos arrays deben tener el mismo formato (o ser compatibles por broadcasting). |
Lectura Adicional
- 3Blue1Brown: Essence of Linear Algebra - intuición visual para cada operación abordada aquí
- NumPy documentation on broadcasting - las reglas exactas que sigue NumPy
- Stanford CS229 Linear Algebra Review - referencia concisa de álgebra lineal específica para ML