Phase 01 - Lesson 01

Intuición de Álgebra Lineal

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

Todo modelo de IA es solo matemática de matrices con un sombrero elegante.

Tipo: Aprender Lenguajes: Python, Julia Requisitos previos: Fase 0 Tiempo: ~60 minutos

Objetivos de Aprendizaje

  • Implementar operaciones de vectores y matrices (suma, producto punto, multiplicación de matrices) desde cero en Python
  • Explicar geométricamente qué hacen el producto punto, la proyección y el proceso de Gram-Schmidt
  • Determinar la independencia lineal, el rango y la base de un conjunto de vectores usando reducción por filas
  • Conectar los conceptos de álgebra lineal con sus aplicaciones en IA: embeddings, scores de atención y LoRA

El Problema

Abre cualquier artículo de ML. En la primera página ya verás vectores, matrices, productos punto y transformaciones. Sin intuición de álgebra lineal, esto es solo un montón de símbolos. Con ella, puedes ver lo que una red neuronal está haciendo realmente -- mover puntos por el espacio.

No necesitas ser matemático. Necesitas ver qué significan estas operaciones geométricamente y luego programarlas tú mismo.

El Concepto

Los Vectores Son Puntos (y Direcciones)

Un vector es solo una lista de números. Pero esos números significan algo -- son coordenadas en el espacio.

Vector 2D [3, 2]:

x y Punto
3 2 El vector apunta desde el origen (0,0) hasta (3, 2) en el plano

El vector tiene magnitud sqrt(3^2 + 2^2) = sqrt(13) y apunta hacia arriba y a la derecha.

En IA, los vectores representan todo:

  • Una palabra → un vector de 768 números (su "significado" en el espacio de embedding)
  • Una imagen → un vector de millones de valores de píxel
  • Un usuario → un vector de preferencias

Las Matrices Son Transformaciones

Una matriz transforma un vector en otro. Puede rotar, escalar, estirar o proyectar.

graph LR
    subgraph Before
        A["Point A"]
        B["Point B"]
    end
    subgraph Matrix["Matrix Multiplication"]
        M["M (transformation)"]
    end
    subgraph After
        A2["Point A'"]
        B2["Point B'"]
    end
    A --> M
    B --> M
    M --> A2
    M --> B2

En IA, las matrices SON el modelo:

  • Pesos de redes neuronales → matrices que transforman la entrada en salida
  • Scores de atención → matrices que deciden en qué enfocarse
  • Embeddings → matrices que mapean palabras a vectores

El Producto Punto Mide la Similitud

El producto punto de dos vectores te dice qué tan similares son.

a · b = a₁×b₁ + a₂×b₂ + ... + aₙ×bₙ

Same direction:      a · b > 0  (similar)
Perpendicular:       a · b = 0  (unrelated)
Opposite direction:  a · b < 0  (dissimilar)

Así es literalmente como funcionan los motores de búsqueda, los sistemas de recomendación y RAG -- encontrar vectores con productos punto altos.

Independencia Lineal

Los vectores son linealmente independientes si ningún vector del conjunto puede escribirse como una combinación de los demás. Si v1, v2, v3 son independientes, generan un espacio 3D. Si uno es una combinación de los otros, solo generan un plano.

Por qué importa para la IA: tu matriz de características debe tener columnas linealmente independientes. Si dos características están perfectamente correlacionadas (linealmente dependientes), el modelo no puede distinguir sus efectos. Esto causa multicolinealidad en la regresión -- la matriz de pesos se vuelve inestable, y pequeños cambios en la entrada producen oscilaciones salvajes en la salida.

Ejemplo concreto:

v1 = [1, 0, 0]
v2 = [0, 1, 0]
v3 = [2, 1, 0]   # v3 = 2*v1 + v2

v1 y v2 son independientes -- ninguno es un múltiplo escalar o combinación del otro. Pero v3 = 2*v1 + v2, así que {v1, v2, v3} es un conjunto dependiente. Estos tres vectores están todos en el plano xy. No importa cómo los combines, no puedes alcanzar [0, 0, 1]. Tienes tres vectores pero solo dos dimensiones de libertad.

En un conjunto de datos: si feature_3 = 2*feature_1 + feature_2, agregar feature_3 le da al modelo cero información nueva. Peor aún, hace que las ecuaciones normales sean singulares -- no existe una solución única para los pesos.

Base y Rango

Una base es un conjunto mínimo de vectores linealmente independientes que generan todo el espacio. El número de vectores de la base es la dimensión del espacio.

La base estándar para el espacio 3D es {[1,0,0], [0,1,0], [0,0,1]}. Pero cualesquiera tres vectores independientes en 3D forman una base válida. La elección de la base es una elección de sistema de coordenadas.

El rango de una matriz = número de columnas linealmente independientes = número de filas linealmente independientes. Si rango < min(filas, columnas), la matriz tiene rango deficiente. Esto significa:

  • El sistema tiene infinitas soluciones (o ninguna)
  • Se pierde información en la transformación
  • La matriz no puede invertirse
Situación Rango Qué significa para ML
Rango completo (rango = min(m, n)) Máximo posible Existe una solución única de mínimos cuadrados. El modelo está bien condicionado.
Rango deficiente (rango < min(m, n)) Por debajo del máximo Las características son redundantes. Infinitas soluciones de pesos. Se necesita regularización.
Rango 1 1 Cada columna es una copia escalada de un vector. Todos los datos están en una línea.
Casi rango deficiente (valores singulares pequeños) Numéricamente bajo La matriz está mal condicionada. Un pequeño ruido en la entrada causa grandes cambios en la salida. Usa truncamiento de SVD o regresión ridge.

Proyección

Proyectar el vector a sobre el vector b da la componente de a en la dirección de b:

proj_b(a) = (a dot b / b dot b) * b

El residuo (a - proj_b(a)) es perpendicular a b. Esta descomposición ortogonal es la base del ajuste por mínimos cuadrados.

La proyección está en todas partes en ML:

  • La regresión lineal minimiza la distancia de las observaciones al espacio columna -- la solución ES una proyección
  • El PCA proyecta los datos sobre las direcciones de máxima varianza
  • La atención en transformers calcula proyecciones de queries sobre keys
graph LR
    subgraph Projection["Projection of a onto b"]
        direction TB
        O["Origin"] --> |"b (direction)"| B["b"]
        O --> |"a (original)"| A["a"]
        O --> |"proj_b(a)"| P["projection"]
        A -.-> |"residual (perpendicular)"| P
    end

Ejemplo: a = [3, 4], b = [1, 0]

proj_b(a) = (31 + 40) / (11 + 00) * [1, 0] = 3 * [1, 0] = [3, 0]

La proyección descarta la componente y. Esta es la reducción de dimensionalidad en su forma más simple -- desechar las direcciones que no te importan.

Proceso de Gram-Schmidt

Convertir cualquier conjunto de vectores independientes en una base ortonormal. Ortonormal significa que cada vector tiene longitud 1 y cada par es perpendicular.

El algoritmo:

  1. Toma el primer vector, normalízalo
  2. Toma el segundo vector, resta su proyección sobre el primero, normaliza
  3. Toma el tercer vector, resta sus proyecciones sobre todos los vectores anteriores, normaliza
  4. Repite para los vectores restantes
Input:  v1, v2, v3, ... (linearly independent)

u1 = v1 / |v1|

w2 = v2 - (v2 dot u1) * u1
u2 = w2 / |w2|

w3 = v3 - (v3 dot u1) * u1 - (v3 dot u2) * u2
u3 = w3 / |w3|

Output: u1, u2, u3, ... (orthonormal basis)

Así es como funciona internamente la descomposición QR. Q es la base ortonormal, R captura los coeficientes de proyección. La descomposición QR se usa en:

  • Resolución de sistemas lineales (más estable que la eliminación gaussiana)
  • Cálculo de autovalores (algoritmo QR)
  • Regresión por mínimos cuadrados (el método numérico estándar)

Construye

Paso 1: Vectores desde cero (Python)

class Vector:
    def __init__(self, components):
        self.components = list(components)
        self.dim = len(self.components)

    def __add__(self, other):
        return Vector([a + b for a, b in zip(self.components, other.components)])

    def __sub__(self, other):
        return Vector([a - b for a, b in zip(self.components, other.components)])

    def dot(self, other):
        return sum(a * b for a, b in zip(self.components, other.components))

    def magnitude(self):
        return sum(x**2 for x in self.components) ** 0.5

    def normalize(self):
        mag = self.magnitude()
        return Vector([x / mag for x in self.components])

    def cosine_similarity(self, other):
        return self.dot(other) / (self.magnitude() * other.magnitude())

    def __repr__(self):
        return f"Vector({self.components})"


a = Vector([1, 2, 3])
b = Vector([4, 5, 6])

print(f"a + b = {a + b}")
print(f"a · b = {a.dot(b)}")
print(f"|a| = {a.magnitude():.4f}")
print(f"cosine similarity = {a.cosine_similarity(b):.4f}")

Paso 2: Matrices desde cero (Python)

class Matrix:
    def __init__(self, rows):
        self.rows = [list(row) for row in rows]
        self.shape = (len(self.rows), len(self.rows[0]))

    def __matmul__(self, other):
        if isinstance(other, Vector):
            return Vector([
                sum(self.rows[i][j] * other.components[j] for j in range(self.shape[1]))
                for i in range(self.shape[0])
            ])
        rows = []
        for i in range(self.shape[0]):
            row = []
            for j in range(other.shape[1]):
                row.append(sum(
                    self.rows[i][k] * other.rows[k][j]
                    for k in range(self.shape[1])
                ))
            rows.append(row)
        return Matrix(rows)

    def transpose(self):
        return Matrix([
            [self.rows[j][i] for j in range(self.shape[0])]
            for i in range(self.shape[1])
        ])

    def __repr__(self):
        return f"Matrix({self.rows})"


rotation_90 = Matrix([[0, -1], [1, 0]])
point = Vector([3, 1])

rotated = rotation_90 @ point
print(f"Original: {point}")
print(f"Rotated 90°: {rotated}")

Paso 3: Por qué esto importa para la IA

import random

random.seed(42)
weights = Matrix([[random.gauss(0, 0.1) for _ in range(3)] for _ in range(2)])
input_vector = Vector([1.0, 0.5, -0.3])

output = weights @ input_vector
print(f"Input (3D): {input_vector}")
print(f"Output (2D): {output}")
print("This is what a neural network layer does -- matrix multiplication.")

Paso 4: Versión en Julia

a = [1.0, 2.0, 3.0]
b = [4.0, 5.0, 6.0]

println("a + b = ", a + b)
println("a · b = ", a ⋅ b)       # Julia supports unicode operators
println("|a| = ", √(a ⋅ a))
println("cosine = ", (a ⋅ b) / (√(a ⋅ a) * √(b ⋅ b)))

# Matrix-vector multiplication
W = [0.1 -0.2 0.3; 0.4 0.5 -0.1]
x = [1.0, 0.5, -0.3]
println("Wx = ", W * x)
println("This is a neural network layer.")

Paso 5: Independencia lineal y proyección desde cero (Python)

def is_linearly_independent(vectors):
    n = len(vectors)
    dim = len(vectors[0].components)
    mat = Matrix([v.components[:] for v in vectors])
    rows = [row[:] for row in mat.rows]
    rank = 0
    for col in range(dim):
        pivot = None
        for row in range(rank, len(rows)):
            if abs(rows[row][col]) > 1e-10:
                pivot = row
                break
        if pivot is None:
            continue
        rows[rank], rows[pivot] = rows[pivot], rows[rank]
        scale = rows[rank][col]
        rows[rank] = [x / scale for x in rows[rank]]
        for row in range(len(rows)):
            if row != rank and abs(rows[row][col]) > 1e-10:
                factor = rows[row][col]
                rows[row] = [rows[row][j] - factor * rows[rank][j] for j in range(dim)]
        rank += 1
    return rank == n


def project(a, b):
    scalar = a.dot(b) / b.dot(b)
    return Vector([scalar * x for x in b.components])


def gram_schmidt(vectors):
    orthonormal = []
    for v in vectors:
        w = v
        for u in orthonormal:
            proj = project(w, u)
            w = w - proj
        if w.magnitude() < 1e-10:
            continue
        orthonormal.append(w.normalize())
    return orthonormal


v1 = Vector([1, 0, 0])
v2 = Vector([1, 1, 0])
v3 = Vector([1, 1, 1])
basis = gram_schmidt([v1, v2, v3])
for i, u in enumerate(basis):
    print(f"u{i+1} = {u}")
    print(f"  |u{i+1}| = {u.magnitude():.6f}")

print(f"u1 · u2 = {basis[0].dot(basis[1]):.6f}")
print(f"u1 · u3 = {basis[0].dot(basis[2]):.6f}")
print(f"u2 · u3 = {basis[1].dot(basis[2]):.6f}")

Usalo

Ahora lo mismo con NumPy -- lo que realmente usaras en la práctica:

import numpy as np

a = np.array([1, 2, 3], dtype=float)
b = np.array([4, 5, 6], dtype=float)

print(f"a + b = {a + b}")
print(f"a · b = {np.dot(a, b)}")
print(f"|a| = {np.linalg.norm(a):.4f}")
print(f"cosine = {np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)):.4f}")

W = np.random.randn(2, 3) * 0.1
x = np.array([1.0, 0.5, -0.3])
print(f"Wx = {W @ x}")

Rango, Proyección y QR con NumPy

import numpy as np

A = np.array([[1, 2], [2, 4]])
print(f"Rank: {np.linalg.matrix_rank(A)}")

a = np.array([3, 4])
b = np.array([1, 0])
proj = (np.dot(a, b) / np.dot(b, b)) * b
print(f"Projection of {a} onto {b}: {proj}")

Q, R = np.linalg.qr(np.random.randn(3, 3))
print(f"Q is orthogonal: {np.allclose(Q @ Q.T, np.eye(3))}")
print(f"R is upper triangular: {np.allclose(R, np.triu(R))}")

PyTorch -- Los Tensores Son Vectores con Autodiff

import torch

x = torch.randn(3, requires_grad=True)
y = torch.tensor([1.0, 0.0, 0.0])

similarity = torch.dot(x, y)
similarity.backward()

print(f"x = {x.data}")
print(f"y = {y.data}")
print(f"dot product = {similarity.item():.4f}")
print(f"d(dot)/dx = {x.grad}")

El gradiente del producto punto respecto a x es simplemente y. PyTorch lo calculó automáticamente. Cada operación en una red neuronal se construye a partir de operaciones como esta -- multiplicaciones de matrices, productos punto, proyecciones -- y el autodiff rastrea los gradientes a través de todas ellas.

Acabas de construir desde cero lo que NumPy hace en una línea. Ahora sabes lo que ocurre por debajo.

Entrégalo

Esta lección produce:

  • outputs/prompt-linear-algebra-tutor.md -- un prompt para que asistentes de IA enseñen álgebra lineal mediante intuición geométrica

Conexiones

Todo en esta lección se conecta con partes específicas de la IA moderna:

Concepto Dónde aparece
Producto punto Scores de atención en transformers, similitud de coseno en RAG
Multiplicación de matrices Cada capa de red neuronal, cada transformación lineal
Independencia lineal Selección de características, evitar multicolinealidad
Rango Determinar si un sistema tiene solución, LoRA (low-rank adaptation)
Proyección Regresión lineal (proyectar sobre el espacio columna), PCA
Gram-Schmidt / QR Resolutores numéricos, cálculo de autovalores
Base ortonormal Computación numérica estable, transformaciones de whitening

LoRA merece una mención especial. Hace fine-tuning de grandes modelos de lenguaje descomponiendo las actualizaciones de pesos en matrices de bajo rango. En lugar de actualizar una matriz de pesos 4096x4096 (16M de parámetros), LoRA actualiza dos matrices de tamaño 4096x16 y 16x4096 (131K de parámetros). La restricción de rango 16 significa que LoRA asume que la actualización de pesos vive en un subespacio de 16 dimensiones del espacio completo de 4096 dimensiones. Eso es álgebra lineal haciendo trabajo real.

Ejercicios

  1. Implementa Vector.angle_between(other) que devuelva el ángulo en grados entre dos vectores
  2. Crea una matriz de escala 2D que duplique la coordenada x y triplique la coordenada y, luego aplícala al vector [1, 1]
  3. Dados 5 vectores aleatorios similares a palabras (dimensión 50), encuentra los dos más similares usando la similitud de coseno
  4. Verifica que la salida de Gram-Schmidt sea verdaderamente ortonormal: comprueba que cada par tiene producto punto 0 y cada vector tiene magnitud 1
  5. Crea una matriz 3x3 con rango 2. Verifica usando el método rank(). Luego explica que objeto geométrico generan las columnas.
  6. Proyecta el vector [1, 2, 3] sobre [1, 1, 1]. ¿Qué representa el resultado geométricamente?

Términos Clave

Término Lo que dice la gente Lo que realmente significa
Vector "Una flecha" Una lista de números que representa un punto o dirección en un espacio n-dimensional
Matriz "Una tabla de números" Una transformación que mapea vectores de un espacio a otro
Producto punto "Multiplicar y sumar" Una medida de qué tan alineados están dos vectores -- el núcleo de la búsqueda por similitud
Embedding "Algo de magia de IA" Un vector que representa el significado de algo (palabra, imagen, usuario)
Independencia lineal "No se superponen" Ningún vector del conjunto puede escribirse como combinación de los demás
Rango "Cuántas dimensiones" El número de columnas (o filas) linealmente independientes en una matriz
Proyección "La sombra" La componente de un vector en la dirección de otro
Base "Los ejes coordenados" Un conjunto mínimo de vectores independientes que generan el espacio
Ortonormal "Vectores unitarios perpendiculares" Vectores mutuamente perpendiculares y cada uno con longitud 1
0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).