LECCIÓN 3 · FUNDAMENTOS

Operaciones con tensores

Aquí está la maquinaria de cálculo. La aritmética elemento a elemento, el "broadcasting" (que ahorra mucho código) y la multiplicación de matrices — la operación que define a las redes neuronales.

1. Aritmética elemento a elemento

Las operaciones + - * / se aplican posición por posición:

a = torch.tensor([1., 2., 3.])
b = torch.tensor([10., 20., 30.])
a + b      # suma posición a posición
a * b      # producto posición a posición (NO es matricial)
b / a
salida reala + b → tensor([11., 22., 33.]) a * b → tensor([10., 40., 90.]) b / a → tensor([10., 10., 10.])
⚠️ Ojo: a * b es producto elemento a elemento, NO multiplicación de matrices. Para la matricial se usa @ (lo vemos abajo). Confundirlos es un error clásico.

2. Broadcasting: operar formas distintas

El broadcasting permite operar tensores de formas diferentes "estirando" automáticamente el más pequeño. Sumar un vector a cada fila de una matriz:

M = torch.tensor([[1., 2., 3.],
                  [4., 5., 6.]])   # forma (2, 3)
v = torch.tensor([10., 20., 30.])      # forma (3,)
M + v      # v se suma a CADA fila de M
M * 2      # un escalar se aplica a todo
salida realM + v: tensor([[11., 22., 33.], [14., 25., 36.]]) M * 2: tensor([[ 2., 4., 6.], [ 8., 10., 12.]])
El broadcasting es lo que hace que sumar el bias a toda una capa sea una sola línea (z = x @ W + b): el vector de bias b se suma automáticamente a cada fila del resultado. Lo usarás todo el tiempo sin darte cuenta.

3. Multiplicación de matrices: el operador @

Esta es la operación de las redes neuronales (la suma ponderada de tu curso, en forma matricial). Se usa @ o torch.matmul:

A = torch.tensor([[1., 2.],
                  [3., 4.]])
B = torch.tensor([[5., 6.],
                  [7., 8.]])
A @ B      # multiplicación de matrices
salida realA @ B: tensor([[19., 22.], [43., 50.]])
✍️ Cada celda, a mano (fila de A · columna de B)
[0,0] = fila0·col0 = (1·5)+(2·7) = 5 + 14  = 19
[0,1] = fila0·col1 = (1·6)+(2·8) = 6 + 16  = 22
[1,0] = fila1·col0 = (3·5)+(4·7) = 15 + 28 = 43
[1,1] = fila1·col1 = (3·6)+(4·8) = 18 + 32 = 50

Matriz por vector (una capa procesando una entrada):

x = torch.tensor([1., 2.])
A @ x
salida realtensor([ 5., 11.])
✍️ A mano
resultado[0] = fila0·x = (1·1)+(2·2) = 1 + 4 = 5
resultado[1] = fila1·x = (3·1)+(4·2) = 3 + 8 = 11
📐 Regla de oro del matmul: para multiplicar (m × n) @ (n × p), el n del medio debe coincidir; el resultado es (m × p). Si no coinciden, PyTorch lanza un error de forma — el más común al construir redes.

4. Reducciones: resumir un tensor en menos números

Operaciones que "colapsan" valores: suma, media, máximo…

t = torch.tensor([[1., 2., 3.],
                  [4., 5., 6.]])
t.sum()      # suma de TODO
t.mean()     # promedio de todo
t.max()      # máximo de todo
salida realsum: 21.0 mean: 3.5 max: 6.0

Lo poderoso es el argumento dim: reducir solo a lo largo de un eje.

t.sum(dim=0)   # suma por COLUMNAS (colapsa las filas)
t.sum(dim=1)   # suma por FILAS (colapsa las columnas)
salida realsum(dim=0) → tensor([5., 7., 9.]) (1+4, 2+5, 3+6) sum(dim=1) → tensor([6., 15.]) (1+2+3, 4+5+6)
✍️ A mano, sobre t = [[1,2,3],[4,5,6]]
dim=0 (sumar bajando las FILAS, una suma por columna):
   columna 0: 1 + 4 = 5
   columna 1: 2 + 5 = 7
   columna 2: 3 + 6 = 9   →  [5, 7, 9]

dim=1 (sumar a lo ancho de las COLUMNAS, una suma por fila):
   fila 0: 1 + 2 + 3 = 6
   fila 1: 4 + 5 + 6 = 15  →  [6, 15]
dim indica qué dimensión desaparece. dim=0 elimina las filas (queda un resultado por columna); dim=1 elimina las columnas (un resultado por fila). Esto confunde al principio — la regla "dim = la que se colapsa" te salva.

argmax devuelve la posición del máximo, no el valor (clave para clasificación: "¿qué clase ganó?"):

t.argmax()   # índice del valor más grande (aplanado)
salida real5 (el 6.0 está en la posición 5 si aplanamos)

5. Resumen

+ - * / operan elemento a elemento; broadcasting estira formas compatibles (así se suma el bias); @ es la multiplicación de matrices (el corazón de las capas); las reducciones (sum, mean, argmax) con dim resumen a lo largo de un eje. Con esto ya puedes calcular el forward de una red a mano en PyTorch. En la próxima lección llega la magia: autograd calculará el backward por ti.