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

  1. 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?

  2. 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.inv de NumPy.

  3. 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

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).