Saltar a contenido

Capítulo 3: Matemáticas para IA

Módulo 01-FUNDAMENTOS · AI Master Academy

"Las matemáticas de la IA no se aprenden en una pizarra: se aprenden ejecutando código y viendo qué pasa."


Bienvenido/a al capítulo que más miedo da... y que menos miedo debería dar.

Si has llegado hasta aquí pensando "yo no valgo para las matemáticas", este capítulo está escrito exactamente para ti. No vamos a demostrar teoremas. No vamos a llenar pizarras de símbolos griegos sin explicar qué significan. Vamos a hacer algo mucho más útil: entender las 4 o 5 ideas matemáticas que mueven toda la IA moderna, y programarlas con nuestras propias manos.

Nuestra filosofía en este capítulo es simple:

Analogía Fórmula Código Python Dónde se usa en IA real

Cada concepto pasará por esas cuatro etapas. Si entiendes la analogía y ejecutas el código, la fórmula dejará de ser un jeroglífico y se convertirá en una simple "receta comprimida".

Nota

Todo el código de este capítulo usa Python y NumPy. Si aún no dominas Python, no te preocupes: el Capítulo 4 lo cubre a fondo. Aquí el código es deliberadamente simple y está comentado línea por línea. Puedes copiarlo, pegarlo y ejecutarlo tal cual.


Índice

  1. ¿Por qué necesitas matemáticas? (y cuánto exactamente)
  2. 1.1. El mito del matemático
  3. 1.2. Tabla honesta: qué mates necesitas según tu rol
  4. 1.3. El mapa del capítulo
  5. Álgebra lineal: el idioma de los datos
  6. 2.1. Escalares, vectores, matrices y tensores
  7. 2.2. Operaciones básicas: suma y multiplicación por escalar
  8. 2.3. El producto punto (explicado 3 veces)
  9. 2.4. Norma y distancias: euclidiana y coseno
  10. 2.5. Similitud coseno: LA operación de la era LLM
  11. 2.6. Multiplicación de matrices: el motor de las redes neuronales
  12. 2.7. Transformaciones lineales, identidad y transpuesta
  13. 2.8. Ejercicios rápidos de álgebra lineal
  14. Cálculo: el idioma del aprendizaje
  15. 3.1. La derivada: el velocímetro de las funciones
  16. 3.2. Mínimos, máximos y por qué entrenar = minimizar
  17. 3.3. Gradiente y descenso del gradiente
  18. 3.4. Descenso de gradiente para una regresión lineal
  19. 3.5. La regla de la cadena (semilla de backpropagation)
  20. 3.6. Ejercicios rápidos de cálculo
  21. Probabilidad y estadística: el idioma de la incertidumbre
  22. 4.1. Probabilidad básica y condicional
  23. 4.2. Teorema de Bayes: el ejemplo médico completo
  24. 4.3. Bayes en acción: el filtro de spam
  25. 4.4. Distribuciones: uniforme y normal
  26. 4.5. Media, mediana, varianza y desviación estándar
  27. 4.6. Un LLM es una máquina de probabilidad condicional
  28. 4.7. Softmax y la temperatura de los LLMs
  29. 4.8. Correlación vs. causalidad
  30. 4.9. Ejercicios rápidos de probabilidad
  31. Tabla maestra: cada concepto y dónde lo verás en la academia
  32. Buenas prácticas
  33. Errores comunes
  34. FAQ - Preguntas frecuentes
  35. Resumen del capítulo
  36. Bibliografía y recursos recomendados

1. ¿Por qué necesitas matemáticas? (y cuánto exactamente)

1.1. El mito del matemático

Empecemos desmontando el mito más dañino de todos:

"Para trabajar en IA necesito ser un genio de las matemáticas."

Falso. Rotundamente falso. Y te lo vamos a demostrar con datos, no con ánimos vacíos.

La IA moderna se construye en capas de abstracción. Igual que puedes conducir un coche sin saber diseñar un motor de combustión, puedes construir sistemas de IA extraordinarios sin saber demostrar el teorema de convergencia del descenso de gradiente estocástico. Lo que necesitas es entender qué hace el motor para no estrellarte:

  • Necesitas saber que un embedding es un vector, para entender por qué "buscar por significado" es posible.
  • Necesitas saber qué es un gradiente, para entender por qué tu modelo "no aprende" cuando la tasa de aprendizaje está mal.
  • Necesitas saber qué hace softmax y la temperatura, para configurar bien un LLM en producción.
  • Necesitas entender probabilidad condicional, para no prometerle a tu jefe que "el modelo nunca se equivocará".

Es decir: necesitas intuición operativa, no virtuosismo académico.

Caso empresarial

En 2023, una consultora española desplegó un chatbot RAG para un banco. El sistema devolvía respuestas irrelevantes. ¿El problema? El equipo comparaba embeddings con distancia euclidiana sin normalizar en lugar de similitud coseno. Un cambio de 3 líneas de código (que entenderás al final de este capítulo) arregló el sistema. Nadie del equipo necesitaba un doctorado: necesitaban entender la diferencia entre dos formas de medir "parecido" entre vectores. Eso es exactamente lo que aprenderás en la sección 2.5.

1.2. Tabla honesta: qué mates necesitas según tu rol

Aquí tienes la tabla que nadie te enseña. Compara honestamente lo que usa a diario un AI Engineer (quien construye productos con IA: RAG, agentes, fine-tuning, APIs) frente a un investigador de IA (quien inventa nuevas arquitecturas y publica papers):

Área matemática AI Engineer (tú, probablemente) Investigador/a de IA
Aritmética y porcentajes A diario (métricas, costes de tokens, precisión) A diario
Vectores y producto punto A diario (embeddings, búsqueda semántica, RAG) A diario
Similitud coseno A diario (es EL pan de cada día en la era LLM) Frecuente
Multiplicación de matrices Conceptual: saber qué es y por qué importa A diario, incluyendo optimización de kernels
Derivadas y gradientes Intuición: entender qué significa "el modelo aprende" A diario (diseño de funciones de pérdida)
Regla de la cadena / backprop Solo intuición (los frameworks lo hacen por ti) Dominio profundo
Probabilidad condicional y Bayes Frecuente (evaluación, umbrales, falsos positivos) A diario
Distribuciones (normal, etc.) Básico: leer histogramas, detectar anomalías Profundo (priors, inferencia bayesiana)
Softmax y temperatura A diario (configurar cualquier LLM) A diario
Estadística descriptiva Frecuente (analizar datasets y evaluaciones) A diario
Cálculo multivariable formal Casi nunca Frecuente
Descomposiciones matriciales (SVD, eigen...) Rara vez (quizá al leer sobre LoRA) Frecuente
Teoría de la medida, análisis real Nunca Según el área
Demostraciones formales Nunca Para publicar

Lectura de la tabla: = uso directo y habitual · = basta con la intuición · = no lo necesitas

La conclusión es liberadora: como AI Engineer, el 90% de tus matemáticas diarias son vectores, similitud coseno, la idea de gradiente y probabilidad básica. Exactamente lo que cubre este capítulo.

Consejo profesional

Cuando leas una oferta de empleo de "AI Engineer" que pida "sólidos conocimientos matemáticos", casi siempre se refiere al contenido de este capítulo. Las entrevistas técnicas reales preguntan cosas como "¿por qué usamos similitud coseno en vez de distancia euclidiana para embeddings?" o "¿qué hace la temperatura en un LLM?". Ambas las sabrás responder en unas horas.

1.3. El mapa del capítulo

Las tres grandes áreas que vamos a recorrer forman los tres "idiomas" de la IA:

flowchart TD
    A["Matemáticas para IA"] --> B["Álgebra Lineal<br/>el idioma de los DATOS"]
    A --> C["Cálculo<br/>el idioma del APRENDIZAJE"]
    A --> D["Probabilidad<br/>el idioma de la INCERTIDUMBRE"]

    B --> B1["Vectores → embeddings"]
    B --> B2["Producto punto → attention"]
    B --> B3["Similitud coseno → RAG y búsqueda semántica"]
    B --> B4["Multiplicación de matrices → redes neuronales"]

    C --> C1["Derivada → tasa de cambio del error"]
    C --> C2["Gradiente → dirección de mejora"]
    C --> C3["Descenso de gradiente → ENTRENAR un modelo"]
    C --> C4["Regla de la cadena → backpropagation"]

    D --> D1["Prob. condicional → P(token |contexto)"]
    D --> D2["Bayes → filtros, diagnósticos, evaluación"]
    D --> D3["Softmax → convertir números en probabilidades"]
    D --> D4["Temperatura → creatividad del LLM"]

Cada rama termina en algo que usarás en módulos posteriores de la academia. Nada de lo que aprendas aquí es "relleno académico".

Nota

Para ejecutar el código necesitas tener instalado NumPy. Si aún no lo tienes, abre tu terminal y ejecuta pip install numpy. En el Capítulo 4 explicaremos en detalle qué es pip y cómo gestionar entornos; por ahora, con ese comando basta.


2. Álgebra lineal: el idioma de los datos

Si la IA fuera un país, el álgebra lineal sería su idioma oficial. Todo lo que entra y sale de un modelo de IA —texto, imágenes, audio, tus clics, tus compras— se convierte primero en vectores y matrices. Sin excepción.

¿Por qué? Porque los ordenadores no entienden "gato", ni "me gusta esta película", ni una foto. Solo entienden números organizados en cajas. El álgebra lineal es la disciplina que estudia esas cajas de números y cómo operar con ellas.

2.1. Escalares, vectores, matrices y tensores

Vamos con la jerarquía fundamental. Piensa en ella como cajas de números de dimensión creciente:

Objeto ¿Qué es? Analogía Ejemplo en IA Notación habitual
Escalar Un solo número La temperatura de hoy: 23 °C La tasa de aprendizaje: 0.01 Letra minúscula: $x$, $a$
Vector Una lista ordenada de números La ficha de un jugador de fútbol: [goles, asistencias, edad] Un embedding de una palabra: 1536 números Minúscula en negrita: $\mathbf{v}$
Matriz Una tabla de números (filas × columnas) Una hoja de cálculo: cada fila un cliente, cada columna un dato Los pesos de una capa de red neuronal Mayúscula en negrita: $\mathbf{W}$
Tensor Una caja de números de 3 o más dimensiones Un cubo de Rubik donde cada casilla es un número Una imagen en color: alto × ancho × 3 canales RGB Mayúscula caligráfica: $\mathcal{T}$

La analogía clave: un vector es la "ficha de características" de algo

Imagina que quieres describir una película con números. Podrías puntuar de 0 a 10 cuánto tiene de cada género:

                acción  comedia  romance  ciencia-ficción
"Matrix"       [  9,      2,       3,         10       ]
"Notting Hill" [  1,      8,       9,          0       ]

¡Eso es un vector! Una lista ordenada de números donde cada posición significa algo. "Matrix" se ha convertido en el vector [9, 2, 3, 10]. Ya no es una película: es un punto en un espacio de 4 dimensiones.

Y aquí viene la idea más importante de toda la IA moderna:

Si conviertes las cosas en vectores, las cosas parecidas quedan CERCA en el espacio, y las diferentes quedan LEJOS.

Eso es exactamente lo que hace un embedding: convertir una palabra, una frase o un documento en un vector de cientos o miles de números, de forma que "perro" y "cachorro" queden cerca, y "perro" y "factura" queden lejos. Toda la búsqueda semántica, el RAG y los sistemas de recomendación viven de esta idea.

Visualicemos dos vectores en un plano de 2 dimensiones (solo 2 para poder dibujarlo; en IA real son cientos):

  eje "comedia"
   10 |
    9 |        ● Notting Hill (1, 9)... aproximado a 2D
    8 |
    7 |
    6 |
    5 |
    4 |
    3 |
    2 |                                  ● Matrix (9, 2)
    1 |
    0 +--+--+--+--+--+--+--+--+--+--→  eje "acción"
      0  1  2  3  4  5  6  7  8  9  10

Cada película es una flecha desde el origen (0,0) hasta su punto. Cuando digamos "el ángulo entre dos vectores", nos referimos al ángulo entre esas dos flechas.

Ahora en código

# Importamos NumPy, la librería estándar de cálculo numérico en Python.
# La abreviamos como "np" por convención universal.
import numpy as np

# --- ESCALAR: un solo número ---
temperatura = 0.7          # un escalar normal de Python (lo usaremos con LLMs)

# --- VECTOR: una lista ordenada de números ---
# np.array() convierte una lista de Python en un array de NumPy,
# que permite operaciones matemáticas rápidas.
matrix = np.array([9, 2, 3, 10])        # [acción, comedia, romance, sci-fi]
notting_hill = np.array([1, 8, 9, 0])   # la misma "ficha" para otra película

# --- MATRIZ: una tabla, aquí 2 filas (películas) x 4 columnas (géneros) ---
# Fíjate: una matriz es simplemente "una lista de vectores apilados".
catalogo = np.array([
    [9, 2, 3, 10],   # fila 0: Matrix
    [1, 8, 9, 0],    # fila 1: Notting Hill
])

# --- TENSOR: 3 o más dimensiones ---
# Simulamos una "imagen" diminuta de 2x2 píxeles con 3 canales de color (R,G,B).
imagen = np.array([
    [[255, 0, 0], [0, 255, 0]],    # fila de píxeles: uno rojo, uno verde
    [[0, 0, 255], [255, 255, 0]],  # fila de píxeles: uno azul, uno amarillo
])

# .shape nos dice la "forma" de la caja: cuántos números hay en cada dimensión.
print(matrix.shape)    # (4,)      -> vector de 4 números
print(catalogo.shape)  # (2, 4)    -> matriz de 2 filas y 4 columnas
print(imagen.shape)    # (2, 2, 3) -> tensor: 2 alto x 2 ancho x 3 canales

Nota

.shape será tu mejor amigo durante toda tu carrera. El 80% de los errores al programar IA son errores de shape: intentar operar cajas de números cuyas dimensiones no encajan. Acostúmbrate a imprimir el .shape de todo.

Advertencia

Un vector de NumPy con shape (4,) NO es lo mismo que una matriz de shape (1, 4) (una fila) ni que una de shape (4, 1) (una columna). Se comportan distinto al multiplicar. Cuando algo falle misteriosamente, imprime los shapes.

¿Y los embeddings reales?

Un embedding real de OpenAI (text-embedding-3-small) tiene 1536 dimensiones. Uno de un modelo open source como all-MiniLM-L6-v2 tiene 384. La idea es idéntica a nuestros vectores de películas de 4 dimensiones, solo que:

  1. Las dimensiones no las elige un humano ("acción", "comedia"...), las aprende el modelo durante el entrenamiento.
  2. Cada dimensión individual no suele tener un significado interpretable; el significado emerge del conjunto.

2.2. Operaciones básicas: suma y multiplicación por escalar

¿Qué son? Las dos operaciones más simples con vectores: sumarlos entre sí y "estirarlos" multiplicándolos por un número.

¿Para qué sirven en IA? La suma de vectores aparece en las conexiones residuales de los Transformers (módulo 03) y en la famosa aritmética de embeddings (rey - hombre + mujer ≈ reina). La multiplicación por escalar aparece cada vez que "escalas" algo: la tasa de aprendizaje escala al gradiente, la temperatura escala a los logits.

Analogía: Sumar vectores es como sumar listas de la compra casilla por casilla: si una receta pide [2 huevos, 1 harina] y otra [1 huevo, 3 harinas], juntas necesitan [3 huevos, 4 harinas]. Multiplicar por escalar es "hacer la receta para el doble de gente": 2 × [2, 1] = [4, 2].

Fórmulas:

$$\mathbf{a} + \mathbf{b} = [a_1 + b_1,\; a_2 + b_2,\; \dots,\; a_n + b_n]$$

$$k \cdot \mathbf{a} = [k \cdot a_1,\; k \cdot a_2,\; \dots,\; k \cdot a_n]$$

Código:

import numpy as np

# Dos vectores de ejemplo con 3 características cada uno.
a = np.array([2.0, 1.0, 4.0])
b = np.array([1.0, 3.0, 0.0])

# SUMA: NumPy suma posición a posición ("element-wise") automáticamente.
# No necesitas ningún bucle: esto es lo que hace a NumPy tan potente.
suma = a + b
print(suma)        # [3. 4. 4.]  <- (2+1, 1+3, 4+0)

# MULTIPLICACIÓN POR ESCALAR: multiplica CADA componente por el número.
doble = 2 * a
print(doble)       # [4. 2. 8.]  <- cada componente al doble

# También puedes combinar ambas: esto "estira" b y se lo suma a a.
combinacion = a + 0.5 * b
print(combinacion) # [2.5 2.5 4. ]

Interpretación geométrica: sumar vectores es "encadenar flechas" (pones una flecha a continuación de la otra); multiplicar por un escalar es estirar (k>1), encoger (0<k<1) o invertir (k<0) la flecha sin cambiar su dirección.

Error común

Intentar sumar vectores de distinta longitud. np.array([1,2]) + np.array([1,2,3]) lanza un error de broadcasting. En la vida real esto pasa cuando mezclas embeddings de modelos distintos (uno de 384 dims y otro de 1536): jamás son compatibles ni comparables, aunque ambos "sean embeddings".

2.3. El producto punto (explicado 3 veces)

Llegamos a la operación más importante de todo el capítulo. El producto punto (también llamado producto escalar o dot product) está dentro de:

  • Cada neurona de cada red neuronal del mundo.
  • El mecanismo de attention de los Transformers (el corazón de GPT, Claude y Gemini).
  • Cada búsqueda semántica y cada sistema RAG.
  • Cada recomendación de Netflix, Spotify o Amazon.

Merece que lo expliquemos tres veces, desde tres ángulos distintos. Cuando las tres explicaciones "hagan clic" a la vez, habrás entendido la mitad de la IA moderna.

Explicación 1: la algebraica (la receta)

¿Qué es? Multiplicas los vectores componente a componente y sumas todos los resultados. El resultado es un único número (un escalar).

Fórmula:

$$\mathbf{a} \cdot \mathbf{b} = \sum_{i=1}^{n} a_i \, b_i = a_1 b_1 + a_2 b_2 + \dots + a_n b_n$$

A mano, con nuestras películas:

Matrix        = [9, 2, 3, 10]
Notting Hill  = [1, 8, 9,  0]

producto punto = (9×1) + (2×8) + (3×9) + (10×0)
               =   9   +  16   +  27   +   0
               =  52

Explicación 2: la geométrica (las flechas)

¿Qué es? El producto punto mide cuánto apuntan dos flechas en la misma dirección, ponderado por sus longitudes.

Fórmula geométrica:

$$\mathbf{a} \cdot \mathbf{b} = |\mathbf{a}| \, |\mathbf{b}| \, \cos(\theta)$$

donde $|\mathbf{a}|$ es la longitud (norma) de cada vector y $\theta$ es el ángulo entre ellos.

Como el coseno vale 1 cuando el ángulo es 0° y vale 0 cuando es 90°:

   Mismo sentido (θ=0°)      Perpendiculares (θ=90°)     Sentidos opuestos (θ=180°)
        →  →                        ↑                            ←  →
        a  b                        a →b                         a  b

   producto punto GRANDE      producto punto CERO         producto punto NEGATIVO
   y positivo                 ("no tienen nada            ("dicen lo contrario")
   ("están de acuerdo")         que ver")

Explicación 3: la de la IA (la medida de parecido)

Esta es la que tienes que grabarte a fuego:

El producto punto es un "medidor de parecido" entre dos fichas de características.

Piénsalo con la fórmula algebraica: si dos vectores tienen valores grandes en las mismas posiciones, los productos $a_i b_i$ serán grandes y la suma será grande. Si donde uno tiene valores grandes el otro tiene ceros, los productos se anulan y la suma será pequeña.

  • Producto punto grande y positivo → los vectores "hablan de lo mismo".
  • Producto punto cercano a cero → no tienen relación.
  • Producto punto negativo → representan cosas opuestas.

Aquí está el secreto de los Transformers: el mecanismo de attention calcula el producto punto entre el vector de cada palabra (su query) y los vectores del resto de palabras (sus keys) para decidir a qué palabras prestar atención. Literalmente: "¿cuánto se parece lo que busco a lo que tú ofreces?". Lo verás en detalle en el módulo 03, pero la matemática es exactamente esta.

Código, de las dos maneras:

import numpy as np

matrix = np.array([9, 2, 3, 10])
notting_hill = np.array([1, 8, 9, 0])
love_actually = np.array([0, 9, 10, 0])   # otra comedia romántica

# FORMA 1: "a mano", para ver las tripas de la operación.
# a * b en NumPy multiplica componente a componente (NO es producto punto aún).
productos = matrix * notting_hill
print(productos)              # [ 9 16 27  0]  <- cada a_i * b_i

# .sum() suma todos los elementos: esto completa el producto punto.
print(productos.sum())        # 52

# FORMA 2: la profesional, con np.dot (o el operador @, que es equivalente).
print(np.dot(matrix, notting_hill))          # 52
print(matrix @ notting_hill)                 # 52  <- @ es el operador "producto"

# Ahora usemos el producto punto como MEDIDOR DE PARECIDO:
print(notting_hill @ love_actually)   # 162 -> ¡las dos rom-coms se parecen MUCHO!
print(matrix @ love_actually)         # 48  -> Matrix se parece poco a una rom-com

Fíjate en el resultado: el sistema, sin saber nada de cine, ha "descubierto" que Notting Hill se parece más a Love Actually que a Matrix. Solo con multiplicar y sumar números. Así de simple es la semilla de todo sistema de recomendación.

Advertencia

El producto punto crudo tiene un problema: favorece a los vectores largos. Un vector con números enormes dará productos punto enormes con todo el mundo, aunque no se parezca a nada. La solución es normalizar, y nos lleva directos a las dos siguientes secciones.

2.4. Norma y distancias: euclidiana y coseno

La norma: ¿cómo de larga es la flecha?

¿Qué es? La norma (o módulo) de un vector es su longitud como flecha. Se calcula con el teorema de Pitágoras generalizado: raíz cuadrada de la suma de los cuadrados.

Analogía: Si un vector es "andar 3 km al este y 4 km al norte", la norma es la distancia en línea recta desde donde saliste: 5 km (el clásico triángulo 3-4-5).

Fórmula:

$$|\mathbf{a}| = \sqrt{a_1^2 + a_2^2 + \dots + a_n^2}$$

import numpy as np

v = np.array([3.0, 4.0])

# Forma manual: elevar al cuadrado, sumar, raíz cuadrada.
norma_manual = np.sqrt((v ** 2).sum())   # sqrt(9 + 16) = sqrt(25)
print(norma_manual)                      # 5.0

# Forma profesional: np.linalg.norm hace exactamente lo mismo.
print(np.linalg.norm(v))                 # 5.0

Distancia euclidiana: ¿cómo de lejos están dos puntos?

¿Qué es? La distancia "en línea recta" entre las puntas de dos vectores. Es la norma del vector diferencia.

Fórmula:

$$d(\mathbf{a}, \mathbf{b}) = |\mathbf{a} - \mathbf{b}| = \sqrt{\sum_{i=1}^{n} (a_i - b_i)^2}$$

¿Para qué sirve en IA? Es la base del algoritmo k-NN (k vecinos más cercanos), del clustering K-Means (módulo 02) y de muchas bases de datos vectoriales cuando los vectores están normalizados.

import numpy as np

matrix = np.array([9, 2, 3, 10])
notting_hill = np.array([1, 8, 9, 0])

# Restamos los vectores (componente a componente) y medimos la longitud
# del vector resultante: esa es la distancia entre los dos puntos.
distancia = np.linalg.norm(matrix - notting_hill)
print(round(distancia, 2))    # 15.36 -> están bastante lejos (son pelis muy distintas)

¿Distancia o ángulo? El problema de la longitud

Imagina dos reseñas del mismo restaurante:

  • Reseña corta: "Buena comida, buen servicio" → vector pequeño.
  • Reseña larga: "Buena comida, muy buena comida, excelente comida, buen servicio, gran servicio..." → vector con los mismos "temas" pero valores mucho más grandes.

Hablan exactamente de lo mismo, pero la distancia euclidiana entre ellas es enorme (una flecha es mucho más larga que la otra). Lo que de verdad nos importa no es la longitud de las flechas, sino si apuntan en la misma dirección. Es decir: el ángulo.

Esa es la motivación de la estrella de la siguiente sección.

2.5. Similitud coseno: LA operación de la era LLM

Si solo pudieras llevarte una fórmula de este capítulo a una isla desierta (con WiFi), sería esta. La similitud coseno es la operación detrás de:

  • Búsqueda semántica: encontrar documentos por significado, no por palabras exactas.
  • RAG (Retrieval-Augmented Generation): el patrón arquitectónico más demandado del mercado, que verás a fondo en su propio módulo.
  • Sistemas de recomendación: "usuarios como tú también compraron...".
  • Detección de duplicados y clustering de textos.
  • Memoria a largo plazo de agentes de IA.

¿Qué es? Es el coseno del ángulo entre dos vectores. Ignora por completo la longitud de las flechas y mide solo si apuntan en la misma dirección.

Analogía: Dos personas señalan con el brazo. No importa si una tiene el brazo más largo que la otra: lo que importa es si señalan al mismo sitio. La similitud coseno mide exactamente eso.

Fórmula: (despejando el coseno de la fórmula geométrica del producto punto)

$$\text{sim}(\mathbf{a}, \mathbf{b}) = \cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{|\mathbf{a}| \, |\mathbf{b}|}$$

Es decir: producto punto dividido por el producto de las normas. La división "cancela" el efecto de las longitudes.

Interpretación del resultado:

Valor Ángulo Significado en IA
1.0 Significado idéntico (o casi): "coche" vs "automóvil"
0.7 – 0.99 pequeño Muy relacionados: "perro" vs "cachorro"
0.3 – 0.7 medio Relación temática débil: "perro" vs "veterinario"
≈ 0.0 90° Sin relación: "perro" vs "hipoteca"
< 0 > 90° Direcciones opuestas (raro con embeddings modernos, que suelen vivir en el "cuadrante positivo")

Implementación completa, línea por línea:

import numpy as np

def similitud_coseno(a, b):
    """Devuelve la similitud coseno entre dos vectores a y b."""
    # 1) Numerador: el producto punto, nuestro "medidor de parecido" crudo.
    producto_punto = np.dot(a, b)
    # 2) Denominador: las longitudes de ambos vectores, multiplicadas.
    norma_a = np.linalg.norm(a)   # longitud de la flecha a
    norma_b = np.linalg.norm(b)   # longitud de la flecha b
    # 3) Dividimos: así el parecido deja de depender de las longitudes.
    return producto_punto / (norma_a * norma_b)

# --- Mini catálogo de películas: [acción, comedia, romance, sci-fi] ---
matrix        = np.array([9, 2, 3, 10])
notting_hill  = np.array([1, 8, 9, 0])
love_actually = np.array([0, 9, 10, 0])
terminator    = np.array([10, 1, 2, 9])

# Comparamos todas contra Matrix:
print(round(similitud_coseno(matrix, terminator), 3))     # 0.982 -> ¡casi gemelas!
print(round(similitud_coseno(matrix, notting_hill), 3))   # 0.303 -> poco que ver
print(round(similitud_coseno(matrix, love_actually), 3))  # 0.253 -> aún menos

# Y las dos rom-coms entre sí:
print(round(similitud_coseno(notting_hill, love_actually), 3))  # 0.987 -> gemelas

El resultado es un mini-recomendador funcional: "Si te gustó Matrix, te recomendamos Terminator (98% de afinidad)". Con vectores de 4 dimensiones hechos a mano. Los sistemas reales hacen exactamente esto con embeddings de 1536 dimensiones generados por un modelo.

Así funciona una búsqueda semántica (el corazón de RAG)

import numpy as np

# Simulamos "embeddings" de frases (en la realidad los genera un modelo,
# aquí los inventamos pequeños para poder leerlos).
# Dimensiones imaginarias: [mascotas, finanzas, deporte]
frases = {
    "Mi perro come pienso":            np.array([0.9, 0.0, 0.1]),
    "El banco subió los intereses":    np.array([0.0, 0.95, 0.0]),
    "El partido acabó en empate":      np.array([0.05, 0.0, 0.9]),
    "Adopté un gato callejero":        np.array([0.85, 0.05, 0.0]),
}

# La consulta del usuario, ya convertida a embedding:
consulta = np.array([0.8, 0.1, 0.05])   # "¿qué come mi cachorro?"

def similitud_coseno(a, b):
    # La misma función de antes, compactada en una línea.
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Calculamos la similitud de la consulta con CADA frase del "índice":
for texto, embedding in frases.items():
    sim = similitud_coseno(consulta, embedding)
    print(f"{sim:.3f}  {texto}")

# Salida (ordenada mentalmente de mayor a menor):
# 0.997  Mi perro come pienso          <- ¡ganadora! se recupera esta
# 0.985  Adopté un gato callejero      <- también muy relacionada (mascotas)
# 0.107  El partido acabó en empate
# 0.116  El banco subió los intereses

Fíjate en lo mágico: la consulta dice "cachorro" y la frase ganadora dice "perro". No comparten ni una palabra, pero sus embeddings apuntan en la misma dirección. Eso es búsqueda por significado, y es imposible con la búsqueda clásica por palabras clave.

Caso empresarial

Un e-commerce mediano migró su buscador de "coincidencia de palabras" a búsqueda semántica con embeddings + similitud coseno. Antes, buscar "zapatillas para correr" no encontraba "deportivas running". Después de la migración, la tasa de conversión del buscador subió un 23%. La matemática que hizo posible ese negocio: la fórmula de esta sección.

Consejo profesional

Muchas bases de datos vectoriales (Pinecone, Qdrant, pgvector...) te dejan elegir la métrica: cosine, dot_product o euclidean. Regla práctica: si tus embeddings no están normalizados, usa cosine. Si están normalizados (norma = 1), el producto punto y el coseno dan el mismo resultado y el producto punto es más rápido. Los embeddings de OpenAI ya vienen normalizados, por eso su documentación permite ambas.

Error común

Comparar con similitud coseno embeddings de modelos diferentes (por ejemplo, indexaste documentos con un modelo y generas la consulta con otro). Los espacios vectoriales de modelos distintos no son compatibles: los números saldrán sin sentido y no habrá ningún error visible. Es uno de los bugs más silenciosos y comunes en sistemas RAG.

2.6. Multiplicación de matrices: el motor de las redes neuronales

¿Qué es? Una operación que combina dos matrices para producir una tercera, donde cada celda del resultado es un producto punto entre una fila de la primera matriz y una columna de la segunda.

¿Por qué es LA operación de las redes neuronales? Porque una capa de red neuronal hace, literalmente, esto:

$$\mathbf{y} = \mathbf{W}\mathbf{x} + \mathbf{b}$$

donde $\mathbf{x}$ es el vector de entrada, $\mathbf{W}$ es la matriz de pesos (lo que el modelo "aprende") y $\mathbf{b}$ es un vector de sesgo. Cuando lees que GPT-4 tiene "billones de parámetros", esos parámetros son, en su inmensa mayoría, celdas de matrices W que se multiplican con tus datos. Entrenar un modelo = ajustar los números de esas matrices. Ejecutar un modelo = multiplicar matrices millones de veces. Por eso se usan GPUs: son máquinas especializadas en multiplicar matrices en paralelo.

Analogía: Piensa en una matriz como una fábrica de opiniones. Entra un vector con los datos de una casa [metros, habitaciones, distancia al centro] y cada fila de la matriz es un "experto" que pondera esos datos a su manera y emite un número (su opinión). La salida es el vector con todas las opiniones. Una red neuronal profunda es una cadena de fábricas: la opinión de unos expertos alimenta a los siguientes.

La regla de compatibilidad (memorízala):

   (m × n) · (n × p)  =  (m × p)
       └──────┘
    ¡los de dentro deben coincidir!
    y el resultado tiene los de fuera

Una matriz de 2×3 solo puede multiplicarse por una de 3×algo. Si los números "de dentro" no coinciden, la operación no existe.

Ejemplo pequeño, paso a paso, a mano. Multipliquemos una matriz A (2×3) por una matriz B (3×2):

        A (2×3)              B (3×2)
      ┌         ┐          ┌       ┐
      │ 1  2  3 │          │ 7  8  │
      │ 4  5  6 │          │ 9  10 │
      └         ┘          │ 11 12 │
                           └       ┘

Resultado C = A·B tendrá forma (2×2).

C[0,0] = fila 0 de A · columna 0 de B
       = (1×7) + (2×9) + (3×11) = 7 + 18 + 33 = 58

C[0,1] = fila 0 de A · columna 1 de B
       = (1×8) + (2×10) + (3×12) = 8 + 20 + 36 = 64

C[1,0] = fila 1 de A · columna 0 de B
       = (4×7) + (5×9) + (6×11) = 28 + 45 + 66 = 139

C[1,1] = fila 1 de A · columna 1 de B
       = (4×8) + (5×10) + (6×12) = 32 + 50 + 72 = 154

        C (2×2)
      ┌          ┐
      │  58   64 │
      │ 139  154 │
      └          ┘

Observa el patrón: cada celda es un producto punto (fila × columna). La multiplicación de matrices es "muchos productos punto organizados". Por eso insistimos tanto en el producto punto: si lo entiendes, entiendes esto gratis.

En NumPy:

import numpy as np

# Definimos las mismas matrices del ejemplo a mano.
A = np.array([
    [1, 2, 3],
    [4, 5, 6],
])                        # shape (2, 3)

B = np.array([
    [7, 8],
    [9, 10],
    [11, 12],
])                        # shape (3, 2)

# El operador @ realiza la multiplicación de matrices.
C = A @ B
print(C)
# [[ 58  64]
#  [139 154]]   <- exactamente lo que calculamos a mano

print(C.shape)   # (2, 2) -> los "números de fuera": (2×3)·(3×2) = (2×2)

Advertencia

En NumPy, A * B NO es multiplicación de matrices: es multiplicación elemento a elemento (y falla si los shapes no coinciden). Para multiplicar matrices usa siempre A @ B o np.matmul(A, B). Confundir * con @ es probablemente el error de principiante más repetido de la historia de NumPy.

Advertencia

La multiplicación de matrices no es conmutativa: en general A @ B ≠ B @ A (a menudo una de las dos ni siquiera existe por incompatibilidad de shapes). El orden importa, siempre.

Una neurona y una capa, con matrices

Veamos el uso real: una mini "capa neuronal" que valora pisos.

import numpy as np

# ENTRADA: características de un piso -> [metros², habitaciones, km al centro]
x = np.array([80.0, 3.0, 5.0])

# PESOS: matriz W con 2 filas = 2 "neuronas" u opiniones que queremos calcular.
# Cada fila pondera las 3 características de entrada a su manera.
W = np.array([
    [0.5, 10.0, -2.0],   # neurona 0: "puntuación de amplitud" (penaliza lejanía)
    [0.1,  1.0,  3.0],   # neurona 1: "puntuación de afueras" (premia lejanía)
])                        # shape (2, 3)

# SESGO: un ajuste fijo que se suma a cada neurona (como el "término libre").
b = np.array([5.0, 0.0])

# LA CAPA COMPLETA: y = W·x + b  <- esta línea ES una capa de red neuronal.
y = W @ x + b
print(y)   # [65. 26.]  -> la opinión de cada neurona sobre este piso

# Desglose de la neurona 0 para que veas que solo es un producto punto:
# (0.5×80) + (10×3) + (-2×5) + 5 = 40 + 30 - 10 + 5 = 65 ✅
flowchart LR
    subgraph Entrada["Vector de entrada x"]
        x1["metros² = 80"]
        x2["habitaciones = 3"]
        x3["km centro = 5"]
    end
    subgraph Capa["Capa: y = W·x + b"]
        n1(("neurona 0<br/>fila 0 de W"))
        n2(("neurona 1<br/>fila 1 de W"))
    end
    subgraph Salida["Vector de salida y"]
        y1["y₀ = 65"]
        y2["y₁ = 26"]
    end
    x1 --> n1
    x2 --> n1
    x3 --> n1
    x1 --> n2
    x2 --> n2
    x3 --> n2
    n1 --> y1
    n2 --> y2

Cada flecha que entra en una neurona lleva asociado un peso (una celda de W). La neurona multiplica cada entrada por su peso y suma todo: un producto punto. Una red "profunda" simplemente encadena muchas capas como esta, con una función no lineal entre medias (lo verás en el módulo 03).

Consejo profesional

Cuando en las especificaciones de un modelo leas "7B de parámetros", ya sabes traducirlo: 7.000 millones de celdas repartidas en matrices W (y vectores b) que se multiplican con tus datos. El tamaño de esas matrices determina la memoria (VRAM) necesaria: 7B parámetros × 2 bytes (float16) ≈ 14 GB. Esta cuenta de servilleta te será utilísima al desplegar modelos.

2.7. Transformaciones lineales, identidad y transpuesta

Transformación lineal: la matriz como "máquina de deformar el espacio"

¿Qué es? Hasta ahora hemos visto la matriz como tabla de datos y como fábrica de opiniones. La tercera perspectiva es la más bella: una matriz es una función que transforma el espacio entero. Le das un vector, te devuelve otro vector: rota, estira, encoge, refleja o inclina todas las flechas del espacio de manera uniforme.

Intuición visual (descrita): Imagina el plano 2D como una hoja de papel cuadriculado elástico. Multiplicar todos los puntos por una matriz es agarrar la hoja y deformarla, con dos reglas: las líneas rectas siguen siendo rectas, y el origen (0,0) no se mueve. Una matriz de rotación gira toda la hoja; una de escala la estira; una de reflexión le da la vuelta como un espejo.

   ANTES (cuadrícula)          DESPUÉS de M = [[2,0],[0,1]]
                               (estira el eje x al doble)
   +--+--+--+                  +----+----+----+
   |  |  |  |                  |    |    |    |
   +--+--+--+       ──►        +----+----+----+
   |  |  |  |                  |    |    |    |
   +--+--+--+                  +----+----+----+

¿Para qué sirve en IA? Cada capa de una red neuronal es una transformación (lineal + un doblez no lineal) del espacio de datos. Entrenar una red es encontrar la secuencia de deformaciones que consigue que, al final, los datos de clases distintas queden separables: los emails de spam en una zona del espacio, los legítimos en otra. Esta visión geométrica del deep learning ("plegar el espacio hasta separar los datos") te acompañará toda la academia.

import numpy as np

# Matriz de rotación de 90 grados en sentido antihorario.
R = np.array([
    [0.0, -1.0],
    [1.0,  0.0],
])

# Un vector apuntando "al este".
este = np.array([1.0, 0.0])

# Al multiplicar, el vector rota 90°: ahora apunta "al norte".
print(R @ este)   # [0. 1.]  <- la matriz ha GIRADO la flecha

Matriz identidad: la transformación que no hace nada

¿Qué es? La matriz con unos en la diagonal y ceros fuera. Multiplicar por ella devuelve el vector intacto. Es el "1" de las matrices: $\mathbf{I}\mathbf{x} = \mathbf{x}$.

import numpy as np

I = np.eye(3)          # np.eye(n) crea la identidad de tamaño n×n
v = np.array([4.0, -2.0, 7.0])
print(I @ v)           # [ 4. -2.  7.]  <- sale exactamente lo que entró

¿Para qué sirve en IA? Aparece en las conexiones residuales de los Transformers (la idea de "deja pasar la entrada tal cual y añade solo una corrección", clave para entrenar redes muy profundas) y en técnicas de regularización. También en LoRA, la técnica estrella de fine-tuning barato, donde se parte de "no cambiar nada" (identidad conceptual) y se aprende solo una pequeña corrección.

Transpuesta: intercambiar filas por columnas

¿Qué es? La transpuesta $\mathbf{A}^T$ convierte las filas en columnas y viceversa. Si A es (2×3), su transpuesta es (3×2).

import numpy as np

A = np.array([
    [1, 2, 3],
    [4, 5, 6],
])                 # shape (2, 3)

print(A.T)         # .T calcula la transpuesta
# [[1 4]
#  [2 5]
#  [3 6]]          # shape (3, 2): filas y columnas intercambiadas

¿Para qué sirve en IA? Es el "adaptador universal de shapes". En la fórmula de attention de los Transformers, $QK^T$ transpone la matriz de keys precisamente para que los shapes encajen y cada celda del resultado sea el producto punto entre una query y una key. Cuando en el módulo 03 veas $\text{softmax}\left(\frac{QK^T}{\sqrt{d}}\right)V$, reconocerás cada pieza: una transpuesta, una multiplicación de matrices, una división por escalar y un softmax (sección 4.7). Toda la fórmula del attention está en este capítulo.

2.8. Ejercicios rápidos de álgebra lineal

Ejercicio 2.1. Crea los vectores a = [2, 0, 1] y b = [1, 3, 1] en NumPy y calcula: su suma, el producto punto y la norma de a.

Ver solución
import numpy as np

a = np.array([2, 0, 1])
b = np.array([1, 3, 1])

print(a + b)                 # [3 3 2]
print(a @ b)                 # (2×1)+(0×3)+(1×1) = 3
print(np.linalg.norm(a))     # sqrt(4+0+1) = sqrt(5) ≈ 2.236

Ejercicio 2.2. Sin usar el ordenador: ¿puede multiplicarse una matriz (3×2) por una (3×2)? ¿Y una (3×2) por una (2×5)? Si es posible, ¿qué shape tiene el resultado?

Ver solución - (3×**2**) · (**3**×2): **NO** es posible. Los números "de dentro" (2 y 3) no coinciden. - (3×**2**) · (**2**×5): **SÍ**. Los de dentro coinciden (2 y 2). El resultado tiene los de fuera: **(3×5)**.

Ejercicio 2.3. Añade la película interstellar = [6, 1, 4, 10] al ejemplo de similitud coseno de la sección 2.5 y comprueba si se parece más a Matrix o a Notting Hill.

Ver solución
import numpy as np

def similitud_coseno(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

matrix       = np.array([9, 2, 3, 10])
notting_hill = np.array([1, 8, 9, 0])
interstellar = np.array([6, 1, 4, 10])

print(round(similitud_coseno(interstellar, matrix), 3))        # ≈ 0.964
print(round(similitud_coseno(interstellar, notting_hill), 3))  # ≈ 0.335
# Interstellar se parece muchísimo más a Matrix (acción + sci-fi). Lógico.

Ejercicio 2.4 (de entrevista real). ¿Por qué para comparar embeddings se prefiere similitud coseno a distancia euclidiana?

Ver solución Porque la similitud coseno **ignora la magnitud** de los vectores y compara solo su **dirección** (su "tema" o significado). Dos textos que hablan de lo mismo con distinta longitud o intensidad producen vectores de magnitudes distintas pero direcciones parecidas: la euclidiana los consideraría lejanos; el coseno, casi idénticos. (Nota fina: con vectores normalizados a norma 1, ordenar por coseno y por euclidiana da el mismo ranking.)

3. Cálculo: el idioma del aprendizaje

Si el álgebra lineal responde a "¿cómo representamos los datos?", el cálculo responde a la pregunta del millón:

¿Cómo hace un modelo para APRENDER?

Spoiler de la respuesta completa: aprender = equivocarse, medir cuánto te has equivocado, y ajustar los parámetros en la dirección que reduce el error. El cálculo es la herramienta que nos dice cuál es esa dirección. Solo necesitas dos ideas: la derivada y el gradiente.

3.1. La derivada: el velocímetro de las funciones

¿Qué es? La derivada de una función en un punto mide cómo de rápido cambia la función en ese punto y en qué sentido. Es una "tasa de cambio instantánea".

Analogía del coche y el velocímetro : Imagina que conduces de Madrid a Valencia. Tu posición es una función del tiempo: posicion(t). El velocímetro de tu coche marca la derivada de esa función: cuántos km cambia tu posición por cada hora que pasa, en este instante exacto.

  • Velocímetro a 120 → tu posición cambia rápido (derivada grande).
  • Velocímetro a 0 → estás parado (derivada cero).
  • Marcha atrás → tu posición retrocede (derivada negativa).

Nadie necesita saber la definición formal de límite para leer un velocímetro. Con la IA pasa igual: necesitas leer el velocímetro, no fabricarlo.

Interpretación geométrica: la derivada en un punto es la pendiente de la curva en ese punto: cuánto sube (o baja) la función si das un pasito hacia la derecha.

  f(x) = x²                 pendiente negativa   pendiente cero   pendiente positiva
                                    ↓                  ↓                ↓
   f(x)
  9 |  ●                                                                ●
    |   \                                                              /
  4 |    ●                                                            ●
    |     \                                                          /
  1 |      ●                                                        ●
    |        \___                                               ___/
  0 +------------●----------------●-----------------●--------------→ x
       -3   -2   -1               0                 1     2    3
                          (aquí la derivada es 0:
                            ¡el fondo del valle!)

Fórmula (definición):

$$f'(x) = \lim_{h \to 0} \frac{f(x+h) - f(x)}{h}$$

Traducción al castellano: "da un pasito diminuto h hacia la derecha, mira cuánto cambió f, y divide el cambio entre el tamaño del paso". Es literalmente calcular una velocidad: distancia recorrida / tiempo empleado.

Código: derivada numérica (el velocímetro casero):

def derivada_numerica(f, x, h=0.0001):
    """Aproxima la derivada de f en el punto x dando un pasito diminuto h."""
    # 1) Evaluamos la función un pasito a la derecha de x.
    f_derecha = f(x + h)
    # 2) Evaluamos la función en x.
    f_aqui = f(x)
    # 3) Cambio en f dividido por el tamaño del paso = tasa de cambio.
    return (f_derecha - f_aqui) / h

# Nuestra función de prueba: f(x) = x². Su derivada exacta es f'(x) = 2x.
def f(x):
    return x ** 2

# Comprobamos el "velocímetro" en varios puntos:
print(derivada_numerica(f, 3))    # ≈ 6.0001  (exacta: 2·3 = 6)   -> sube fuerte
print(derivada_numerica(f, 0))    # ≈ 0.0001  (exacta: 0)         -> fondo del valle
print(derivada_numerica(f, -2))   # ≈ -3.9999 (exacta: -4)        -> baja fuerte

Nota

Los frameworks de deep learning (PyTorch, TensorFlow) no aproximan derivadas con pasitos: calculan derivadas exactas de forma automática con una técnica llamada diferenciación automática. Tú nunca derivarás a mano en tu trabajo. Pero entender qué calculan es imprescindible para diagnosticar entrenamientos.

Las únicas reglas de derivación que mencionaremos (por si lees papers, no hace falta memorizarlas):

Función $f(x)$ Derivada $f'(x)$ Lectura intuitiva
$c$ (constante) $0$ Algo que no cambia tiene velocidad cero
$x$ $1$ Cambia al mismo ritmo que avanzas
$x^2$ $2x$ Cuanto más lejos del 0, más rápido crece
$x^n$ $n\,x^{n-1}$ Generalización de la anterior
$e^x$ $e^x$ La función que crece a su propio ritmo
$\ln(x)$ $1/x$ Crece cada vez más despacio

3.2. Mínimos, máximos y por qué entrenar = minimizar

La idea que conecta el cálculo con la IA:

  1. Un modelo de IA tiene parámetros (las celdas de sus matrices W).
  2. Definimos una función de error (en la jerga: loss function o función de pérdida) que mide cuánto se equivoca el modelo con sus parámetros actuales. Error grande = modelo malo; error pequeño = modelo bueno.
  3. Entrenar el modelo = encontrar los valores de los parámetros que hacen el error lo más pequeño posible.

Es decir: entrenar es un problema de minimización. Todo el deep learning, desde una regresión hasta GPT, es "buscar el fondo del valle de la función de error".

¿Y cómo encontramos un mínimo? Aquí conecta la derivada: en el fondo de un valle, la pendiente es cero (mira el dibujo ASCII de la sección anterior: en x=0, la curva de x² está plana). Así que los mínimos (y los máximos) están donde la derivada se anula.

   error(w)
      |   \                                        /
      |    \                                      /
      |     \              ¡mínimo!              /
      |      \            derivada=0            /
      |       \_             ↓               _/
      |         \__        _____          __/
      |            \______/     \________/
      +--------------------------------------------→ w (parámetro)
                       ↑                ↑
                 mínimo GLOBAL     mínimo LOCAL
              (el mejor posible)  (un valle secundario)

Advertencia

Derivada cero no garantiza "el mejor mínimo". Puede ser un mínimo local (un valle secundario) o incluso un punto de silla. Las funciones de error de las redes neuronales reales tienen paisajes llenos de valles. En la práctica esto preocupa menos de lo que parece (en dimensiones altas casi todos los valles son "suficientemente buenos"), pero explica por qué entrenar dos veces el mismo modelo puede dar resultados ligeramente distintos.

¿Y por qué no resolvemos "derivada = 0" con álgebra y ya está? Porque un LLM tiene miles de millones de parámetros: la ecuación resultante es imposible de resolver de forma exacta. Necesitamos un método que funcione a tientas, paso a paso. Ese método es la estrella de la siguiente sección.

3.3. Gradiente y descenso del gradiente

El gradiente: la brújula del error

¿Qué es? Cuando la función depende de varias variables (¡como el error de un modelo, que depende de millones de parámetros!), tiene una derivada por cada variable. El gradiente es el vector que agrupa todas esas derivadas:

$$\nabla f = \left[ \frac{\partial f}{\partial w_1},\; \frac{\partial f}{\partial w_2},\; \dots,\; \frac{\partial f}{\partial w_n} \right]$$

(El símbolo $\partial$ se lee "derivada parcial": la derivada respecto a una variable tratando las demás como constantes. El símbolo $\nabla$ se lee "nabla".)

Propiedad mágica: el gradiente apunta en la dirección en la que la función crece más rápido. Por tanto, el gradiente negativo apunta hacia donde la función decrece más rápido. Si el error es una montaña, el gradiente negativo es la flecha que señala cuesta abajo.

La analogía de la montaña con niebla

Esta es LA analogía del deep learning. Grábatela:

Estás en una montaña de noche, con una niebla tan densa que solo ves tus pies. Quieres bajar al valle (el error mínimo). No puedes ver el camino completo, pero sí puedes sentir la pendiente bajo tus pies. Estrategia: (1) tantea con el pie hacia dónde baja más la pendiente, (2) da un paso en esa dirección, (3) repite. Paso a paso, sin ver nada más allá de tus pies, acabarás en el fondo de un valle.

Traducción a IA:

Elemento de la analogía Elemento matemático
La montaña La función de error (loss)
Tu posición en la montaña Los valores actuales de los parámetros
La altura a la que estás Cuánto se equivoca el modelo ahora
Sentir la pendiente con el pie Calcular el gradiente
Dar un paso cuesta abajo Actualizar los parámetros contra el gradiente
El tamaño de tu zancada La tasa de aprendizaje (learning rate)
Llegar al valle El modelo entrenado (error mínimo)

Fórmula del descenso del gradiente (la fórmula más importante del machine learning):

$$w_{\text{nuevo}} = w_{\text{viejo}} - \eta \cdot \nabla f(w_{\text{viejo}})$$

Palabra por palabra: - $w$: el parámetro (o vector de parámetros) que estamos ajustando. - $\eta$ (eta): la tasa de aprendizaje, el tamaño de la zancada. Un escalar pequeño, típicamente entre 0.0001 y 0.1. - $\nabla f$: el gradiente, la dirección cuesta arriba. - El signo menos: caminamos en dirección contraria al gradiente, es decir, cuesta abajo.

Implementación 1: minimizar f(x) = x² con Python puro

Vamos a implementar el algoritmo completo para la función más simple posible. Sabemos que el mínimo de $f(x)=x^2$ está en $x=0$; veamos si el algoritmo lo encuentra "a tientas".

# --- DESCENSO DE GRADIENTE desde cero, sin librerías ---

# La función que queremos minimizar: nuestra "montaña".
def f(x):
    return x ** 2

# Su derivada (calculada con la regla x² -> 2x de la tabla anterior).
# Nos dice la pendiente en cualquier punto: la dirección "cuesta arriba".
def derivada_f(x):
    return 2 * x

# HIPERPARÁMETROS (los ajustes del algoritmo, elegidos por el humano):
x = 5.0                   # posición inicial: empezamos lejos del mínimo, en x=5
tasa_aprendizaje = 0.1    # tamaño de la zancada (eta)
num_pasos = 25            # cuántos pasos daremos montaña abajo

# EL BUCLE DE ENTRENAMIENTO (idéntico en espíritu al de cualquier red neuronal):
for paso in range(num_pasos):
    # 1) Sentir la pendiente bajo los pies: calcular el gradiente en x.
    gradiente = derivada_f(x)

    # 2) Dar un paso CONTRA el gradiente (el signo menos = cuesta abajo).
    x = x - tasa_aprendizaje * gradiente

    # 3) (Opcional) Mirar el altímetro: ¿a qué altura del error estamos?
    if paso % 5 == 0:                      # imprimimos cada 5 pasos
        print(f"paso {paso:2d} | x = {x:8.5f} | f(x) = {f(x):8.5f}")

print(f"\nResultado final: x ≈ {x:.5f} (el mínimo verdadero es x = 0)")

Salida:

paso  0 | x =  4.00000 | f(x) = 16.00000
paso  5 | x =  1.31072 | f(x) =  1.71799
paso 10 | x =  0.42950 | f(x) =  0.18447
paso 15 | x =  0.14074 | f(x) =  0.01981
paso 20 | x =  0.04612 | f(x) =  0.00213

Resultado final: x ≈ 0.01889 (el mínimo verdadero es x = 0)

Observa la mecánica: en x=5 la pendiente es 2·5=10 (muy empinada), así que el primer paso es grande (baja de 5 a 4). A medida que se acerca al valle, la pendiente se suaviza y los pasos se hacen automáticamente más pequeños. El algoritmo frena solo al acercarse al mínimo. Elegante, ¿verdad?

Advertencia — la tasa de aprendizaje es delicada

  • Demasiado pequeña (ej. 0.0001): pasos diminutos, tardarás una eternidad en bajar.
  • Demasiado grande (ej. 1.1 para esta función): saltas al otro lado del valle, cada vez más lejos, y el error explota hasta infinito (verás nan en tus entrenamientos reales: casi siempre es esto).
  • Pruébalo: cambia tasa_aprendizaje a 1.1 en el código anterior y mira cómo x se dispara. Es el experimento más instructivo del capítulo.

3.4. Descenso de gradiente para una regresión lineal

Ahora el salto a un problema real: entrenar nuestro primer modelo de machine learning desde cero.

El problema: tenemos datos de pisos (metros cuadrados → precio) y queremos que la máquina aprenda la relación para predecir precios de pisos nuevos.

El modelo: una recta. precio = w · metros + b. Solo dos parámetros: la pendiente w (¿cuántos miles de € sube el precio por cada m² extra?) y el sesgo b (el "precio base").

La función de error: el error cuadrático medio (MSE, Mean Squared Error): para cada piso, mide la diferencia entre el precio real y el predicho, elévala al cuadrado (para que dé igual pasarse que quedarse corto, y para castigar más los errores grandes), y promedia:

$$\text{MSE}(w, b) = \frac{1}{n}\sum_{i=1}^{n} \big(y_i - (w\,x_i + b)\big)^2$$

Los gradientes (los damos calculados; salen de aplicar las reglas de derivación a la fórmula del MSE):

$$\frac{\partial \text{MSE}}{\partial w} = \frac{-2}{n}\sum_{i=1}^{n} x_i\big(y_i - (w\,x_i + b)\big) \qquad \frac{\partial \text{MSE}}{\partial b} = \frac{-2}{n}\sum_{i=1}^{n} \big(y_i - (w\,x_i + b)\big)$$

No memorices las fórmulas: entiende su espíritu. Ambas dicen "mira cuánto te equivocaste en cada piso (el paréntesis) y ajusta el parámetro en proporción".

El código completo, línea por línea:

import numpy as np

# --- 1. LOS DATOS: metros cuadrados y precio (en miles de €) de 6 pisos ---
metros  = np.array([50.0,  70.0,  80.0, 100.0, 120.0, 150.0])
precios = np.array([150.0, 200.0, 230.0, 285.0, 340.0, 425.0])
n = len(metros)             # número de ejemplos de entrenamiento: 6

# --- 2. EL MODELO: precio_predicho = w * metros + b ---
# Inicializamos los parámetros "a ciegas" (en cero): el modelo nace ignorante.
w = 0.0                     # pendiente: € (miles) por metro cuadrado
b = 0.0                     # sesgo: precio base

# --- 3. HIPERPARÁMETROS ---
tasa_aprendizaje = 0.0001   # zancada pequeña (los metros² son números grandes)
num_pasos = 20000           # daremos muchos pasos porque la zancada es pequeña

# --- 4. EL BUCLE DE ENTRENAMIENTO ---
for paso in range(num_pasos):
    # a) PREDICCIÓN: qué precios cree el modelo AHORA (con w y b actuales).
    #    NumPy aplica la fórmula a los 6 pisos a la vez (vectorización).
    predicciones = w * metros + b

    # b) ERROR de cada piso: real menos predicho. Vector de 6 errores.
    errores = precios - predicciones

    # c) MEDIR el error global (MSE): promedio de errores al cuadrado.
    mse = (errores ** 2).mean()

    # d) GRADIENTES: ¿hacia dónde es "cuesta abajo" para w y para b?
    #    Son las fórmulas de arriba traducidas a NumPy tal cual.
    gradiente_w = (-2 / n) * (metros * errores).sum()
    gradiente_b = (-2 / n) * errores.sum()

    # e) DAR EL PASO: actualizar cada parámetro CONTRA su gradiente.
    w = w - tasa_aprendizaje * gradiente_w
    b = b - tasa_aprendizaje * gradiente_b

    # f) Informe de progreso cada 4000 pasos.
    if paso % 4000 == 0:
        print(f"paso {paso:5d} | MSE = {mse:10.2f} | w = {w:.3f} | b = {b:.3f}")

# --- 5. EL MODELO ENTRENADO ---
print(f"\nModelo aprendido: precio ≈ {w:.2f} * metros + {b:.2f}")

# --- 6. USARLO: predecir el precio de un piso nuevo de 90 m² ---
piso_nuevo = 90.0
prediccion = w * piso_nuevo + b
print(f"Un piso de {piso_nuevo:.0f} m² costará ≈ {prediccion:.0f} mil €")

Salida (aproximada):

paso     0 | MSE =   83354.17 | w = 0.560 | b = 0.005
paso  4000 | MSE =      95.65 | w = 2.812 | b = 3.070
paso  8000 | MSE =      83.85 | w = 2.769 | b = 7.474
paso 12000 | MSE =      74.93 | w = 2.732 | b = 11.301
paso 16000 | MSE =      68.19 | w = 2.699 | b = 14.627

Modelo aprendido: precio ≈ 2.67 * metros + 17.52
Un piso de 90 m² costará ≈ 258 mil €

Lo que acabas de hacer es enorme. Este bucle de 5 pasos —predecir, medir error, calcular gradiente, actualizar, repetir— es exactamente el mismo bucle con el que se entrena GPT. Las diferencias son de escala, no de naturaleza:

Concepto Nuestra regresión Un LLM real
Parámetros 2 (w y b) Miles de millones
Datos 6 pisos Billones de tokens
Modelo Una recta Cientos de capas Transformer
Función de error MSE Cross-entropy sobre el siguiente token
Gradientes 2 fórmulas a mano Backpropagation automático
Hardware Tu portátil, 1 segundo Miles de GPUs, meses
El bucle de entrenamiento Idéntico Idéntico

Consejo profesional

Cuando en el trabajo veas una "curva de loss" bajando en TensorBoard o Weights & Biases, ya sabes qué es: el mse de nuestro bucle, impreso en cada paso. Si baja → el modelo aprende. Si se estanca alto → tasa de aprendizaje muy baja, modelo muy simple o datos con problemas. Si explota a nan → tasa de aprendizaje muy alta. Acabas de aprender a leer el electrocardiograma del deep learning.

flowchart TD
    A["Inicializar parámetros<br/>w, b aleatorios o en cero"] --> B["PREDECIR<br/>ŷ = w·x + b"]
    B --> C["MEDIR ERROR<br/>MSE entre ŷ y precios reales"]
    C --> D["CALCULAR GRADIENTES<br/>∂MSE/∂w y ∂MSE/∂b<br/>(¿hacia dónde es cuesta abajo?)"]
    D --> E["ACTUALIZAR<br/>w ← w − η·∇w<br/>b ← b − η·∇b"]
    E --> F{"¿Error suficientemente<br/>bajo o pasos agotados?"}
    F -- "No: seguir bajando"--> B
    F -- "Sí"--> G["Modelo entrenado<br/>listo para predecir"]

3.5. La regla de la cadena (semilla de backpropagation)

¿Qué es? Una regla para derivar funciones encadenadas (funciones dentro de funciones): la derivada de la cadena completa es el producto de las derivadas de cada eslabón.

Fórmula: si $y = f(g(x))$, entonces:

$$\frac{dy}{dx} = \frac{dy}{dg} \cdot \frac{dg}{dx}$$

Analogía de los engranajes : Tres engranajes conectados: el pedal de una bici (x), el plato (g) y la rueda (y). Si el plato gira 2 veces por cada pedalada, y la rueda gira 3 veces por cada vuelta del plato, entonces la rueda gira 2 × 3 = 6 veces por pedalada. Las tasas de cambio se multiplican a lo largo de la cadena. Eso es la regla de la cadena. Nada más.

¿Por qué es la base de backpropagation? Una red neuronal profunda es una cadena gigante de funciones: error(capa_N(...capa_2(capa_1(entrada))...)). Para entrenar necesitamos saber cómo afecta al error cada parámetro de cada capa, incluidas las primeras, que están muy "lejos" del error final. La regla de la cadena permite propagar la responsabilidad del error hacia atrás, capa por capa, multiplicando derivadas locales: por eso se llama backpropagation (propagación hacia atrás).

# Mini-demostración: derivada de y = (3x + 1)²  usando la regla de la cadena.
# Descomponemos en eslabones:  g(x) = 3x + 1   y   y = g²
# Derivadas de cada eslabón:   dg/dx = 3        y   dy/dg = 2g
# Regla de la cadena:          dy/dx = dy/dg · dg/dx = 2g · 3 = 6(3x+1)

def derivada_cadena(x):
    g = 3 * x + 1          # valor del eslabón intermedio ("forward pass")
    dy_dg = 2 * g          # derivada del eslabón exterior respecto a g
    dg_dx = 3              # derivada del eslabón interior respecto a x
    return dy_dg * dg_dx   # ¡multiplicar los eslabones! ("backward pass")

# Verificamos contra la derivada numérica de la sección 3.1:
def y(x):
    return (3 * x + 1) ** 2

def derivada_numerica(f, x, h=0.0001):
    return (f(x + h) - f(x)) / h

print(derivada_cadena(2))            # 42  (exacto: 6·(3·2+1) = 42)
print(derivada_numerica(y, 2))       # ≈ 42.0009 -> coincide ✅

Nota

Esto es solo la semilla. En el módulo 03 (Deep Learning) implementaremos backpropagation completo para una red neuronal, y verás que es exactamente esta multiplicación de eslabones, organizada con matrices. Si has entendido los engranajes, ya tienes el 70% de backprop.

3.6. Ejercicios rápidos de cálculo

Ejercicio 3.1. Usando la tabla de derivadas: ¿cuál es la derivada de $f(x) = x^3$? ¿Y su valor en $x = 2$? Verifica con la función derivada_numerica.

Ver solución Por la regla $x^n \to n\,x^{n-1}$: $f'(x) = 3x^2$. En $x=2$: $f'(2) = 3·4 = 12$.
def derivada_numerica(f, x, h=0.0001):
    return (f(x + h) - f(x)) / h

print(derivada_numerica(lambda x: x ** 3, 2))   # ≈ 12.001 ✅

Ejercicio 3.2. Modifica el descenso de gradiente de $f(x)=x^2$ para minimizar $f(x) = (x - 3)^2$ (cuyo mínimo está en x=3, y cuya derivada es $2(x-3)$). Empieza en x = 10.

Ver solución
def derivada_f(x):
    return 2 * (x - 3)      # única línea que cambia (además del comentario)

x = 10.0
tasa_aprendizaje = 0.1
for paso in range(50):
    x = x - tasa_aprendizaje * derivada_f(x)

print(round(x, 4))          # ≈ 3.0 -> el algoritmo encuentra el nuevo valle ✅

Ejercicio 3.3 (conceptual). Durante un entrenamiento, la curva de loss baja durante 100 pasos y de repente se dispara a valores enormes y luego a nan. ¿Cuál es la causa más probable y qué tocarías primero?

Ver solución La causa más probable es una **tasa de aprendizaje demasiado alta**: cerca del valle, los pasos son tan grandes que el algoritmo salta al otro lado cada vez más lejos ("rebota" montaña arriba) y el error crece sin control hasta desbordar la precisión numérica (`nan`). Primera acción: **reducir la tasa de aprendizaje** (por ejemplo, dividirla entre 10). Otras opciones que verás más adelante: gradient clipping o schedulers de learning rate.

4. Probabilidad y estadística: el idioma de la incertidumbre

La IA moderna no dice "este email ES spam". Dice "este email es spam con probabilidad 0.97". No dice "la siguiente palabra ES 'gato'". Dice "'gato' tiene probabilidad 0.41, 'perro' 0.22, 'coche' 0.01...". Toda la IA moderna es probabilística, y quien no entiende probabilidad malinterpreta sus sistemas: confía cuando no debe y desconfía cuando no toca.

4.1. Probabilidad básica y condicional

¿Qué es la probabilidad? Un número entre 0 y 1 que mide cuán plausible es un suceso. 0 = imposible, 1 = seguro, 0.5 = cara o cruz.

Reglas mínimas de supervivencia:

  • $P(A)$ se lee "probabilidad de A". Ejemplo: $P(\text{sacar un 6}) = 1/6 \approx 0.167$.
  • Las probabilidades de todos los resultados posibles suman 1 (esto será clave en softmax).
  • $P(\text{no } A) = 1 - P(A)$. Si la probabilidad de lluvia es 0.3, la de no-lluvia es 0.7.
  • Si A y B son independientes (no se afectan): $P(A \text{ y } B) = P(A) \cdot P(B)$.

¿Qué es la probabilidad condicional? La probabilidad de A sabiendo que B ya ocurrió. Se escribe $P(A \mid B)$ y se lee "probabilidad de A dado B". La barra vertical significa "dado que" o "condicionado a".

Analogía: ¿Probabilidad de que una persona cualquiera mida más de 1,90 m? Baja, quizá 2%. ¿Probabilidad de que mida más de 1,90 m dado que juega en un equipo profesional de baloncesto? Altísima, quizá 60%. La información nueva cambia las probabilidades. Eso es condicionar.

Fórmula:

$$P(A \mid B) = \frac{P(A \text{ y } B)}{P(B)}$$

Traducción: "de entre todos los casos donde ocurre B, ¿qué fracción también tiene A?".

import numpy as np

# Simulamos 100.000 lanzamientos de dos dados para VER la probabilidad condicional.
np.random.seed(42)                       # semilla: hace el experimento reproducible
dado1 = np.random.randint(1, 7, 100_000) # 100k tiradas del primer dado (1 a 6)
dado2 = np.random.randint(1, 7, 100_000) # 100k tiradas del segundo dado
suma = dado1 + dado2                     # la suma de cada pareja de tiradas

# P(suma = 12): sin información extra, solo puede ser 6+6 -> 1/36 ≈ 0.028
p_doce = (suma == 12).mean()             # .mean() de booleanos = proporción de True
print(f"P(suma=12)          = {p_doce:.3f}")   # ≈ 0.028

# P(suma = 12 | dado1 = 6): SABIENDO que el primer dado fue 6,
# solo necesitamos que el segundo sea 6 -> 1/6 ≈ 0.167
con_seis = (dado1 == 6)                        # filtro: casos donde ocurrió B
p_doce_dado_seis = (suma[con_seis] == 12).mean()  # frecuencia de A dentro de B
print(f"P(suma=12 | d1=6)   = {p_doce_dado_seis:.3f}")   # ≈ 0.167

# La información "el primer dado fue 6" multiplicó la probabilidad por 6.

¿Para qué sirve en IA? La probabilidad condicional ES el lenguaje de los LLMs (sección 4.6), de los clasificadores (P(spam | contenido del email)), de los diagnósticos (P(enfermedad | síntomas)) y de los sistemas de recomendación (P(compra | historial)).

4.2. Teorema de Bayes: el ejemplo médico completo

¿Qué es? La fórmula para invertir probabilidades condicionales: pasar de $P(B \mid A)$, que suele ser fácil de medir, a $P(A \mid B)$, que es la que de verdad quieres saber.

Fórmula:

$$P(A \mid B) = \frac{P(B \mid A) \cdot P(A)}{P(B)}$$

¿Por qué importa tantísimo? Porque la intuición humana es desastrosa invirtiendo condicionales, y ese fallo causa decisiones erróneas en medicina, justicia y... evaluación de modelos de IA. Vamos a verlo con el ejemplo clásico, con todos los números.

El ejemplo médico de los falsos positivos

Situación: - Una enfermedad afecta al 1% de la población: $P(\text{enferma}) = 0.01$. - Existe un test 90% sensible: si estás enferma, da positivo el 90% de las veces: $P(+ \mid \text{enferma}) = 0.90$. - El test tiene un 5% de falsos positivos: si estás sana, da positivo el 5% de las veces: $P(+ \mid \text{sana}) = 0.05$.

La pregunta: te haces el test y da positivo. ¿Cuál es la probabilidad de que realmente estés enferma, $P(\text{enferma} \mid +)$?

La intuición grita "90%"... y se equivoca estrepitosamente. Hagamos las cuentas con una población imaginaria de 10.000 personas:

Grupo Personas Test positivo Test negativo
Enfermas (1%) 100 90 (verdaderos positivos) 10 (falsos negativos)
Sanas (99%) 9.900 495 (falsos positivos ) 9.405 (verdaderos negativos)
Total 10.000 585 positivos 9.415

De las 585 personas con test positivo, solo 90 están realmente enfermas:

$$P(\text{enferma} \mid +) = \frac{90}{585} \approx 0.154 = \mathbf{15{,}4\%}$$

¡Solo un 15%! No un 90%. ¿Por qué? Porque la enfermedad es rara: hay tantísimas personas sanas (9.900) que incluso un pequeño 5% de falsos positivos entre ellas (495 personas) aplasta en número a los verdaderos positivos (90 personas). El factor $P(A)$ de la fórmula —la probabilidad "de base" o prior— es el que tu intuición ignora.

Ahora con la fórmula y con código:

# --- Teorema de Bayes: ejemplo médico, paso a paso ---

# Los tres datos del problema:
p_enferma = 0.01                 # P(enferma): prevalencia, el "prior"
p_pos_si_enferma = 0.90          # P(+ | enferma): sensibilidad del test
p_pos_si_sana = 0.05             # P(+ | sana): tasa de falsos positivos

# Paso 1: probabilidad total de dar positivo, P(+).
# Un positivo puede venir de dos caminos: enferma-y-positivo o sana-y-positivo.
p_sana = 1 - p_enferma                            # P(sana) = 0.99
p_positivo = (p_pos_si_enferma * p_enferma        # camino 1: 0.90 × 0.01 = 0.009
              + p_pos_si_sana * p_sana)           # camino 2: 0.05 × 0.99 = 0.0495
print(f"P(+) = {p_positivo:.4f}")                 # 0.0585 (el 5.85% da positivo)

# Paso 2: Bayes -> invertimos la condicional.
p_enferma_si_pos = (p_pos_si_enferma * p_enferma) / p_positivo
print(f"P(enferma | +) = {p_enferma_si_pos:.4f}") # 0.1538 -> ¡solo el 15.4%!

Caso empresarial

Este mismo error arruina proyectos de IA. Ejemplo: un modelo de detección de fraude con "95% de acierto" suena excelente... hasta que recuerdas que solo el 0.1% de las transacciones son fraudulentas. Con un 5% de falsos positivos, por cada fraude real detectado bloquearás decenas de transacciones legítimas, enfureciendo a clientes. Antes de celebrar la precisión de un modelo, pregunta siempre: ¿cuál es la tasa base del fenómeno? Este análisis bayesiano es exactamente lo que harás en el módulo de evaluación de modelos (precisión, recall y la matriz de confusión son primos directos de esta tabla de 10.000 personas).

4.3. Bayes en acción: el filtro de spam

El primer gran éxito comercial de Bayes en software fue el filtro de spam (años 90-2000, y sigue vivo). La idea:

Queremos: $P(\text{spam} \mid \text{el email contiene "GRATIS"})$.

Es fácil medir lo contrario contando en nuestro histórico de emails: ¿qué fracción de los spam contienen "GRATIS"? ¿Y de los legítimos? Bayes invierte la flecha:

# --- Mini filtro de spam bayesiano con datos de un histórico de 1000 emails ---

# Lo que sabemos por CONTAR en nuestros datos históricos:
p_spam = 0.30                    # 30% de nuestros emails históricos eran spam
p_ham = 1 - p_spam               # 70% eran legítimos ("ham" en la jerga)

p_gratis_si_spam = 0.60          # el 60% de los spam contenían "GRATIS"
p_gratis_si_ham = 0.02           # solo el 2% de los legítimos la contenían

# Paso 1: probabilidad total de que un email cualquiera contenga "GRATIS".
p_gratis = p_gratis_si_spam * p_spam + p_gratis_si_ham * p_ham
#        = 0.60×0.30 + 0.02×0.70 = 0.18 + 0.014 = 0.194

# Paso 2: Bayes -> probabilidad de spam DADO que aparece "GRATIS".
p_spam_si_gratis = (p_gratis_si_spam * p_spam) / p_gratis
print(f"P(spam | 'GRATIS') = {p_spam_si_gratis:.3f}")   # 0.928 -> ¡92.8%!

# Con una sola palabra, la probabilidad de spam saltó del 30% al 92.8%.
# Un filtro real combina la evidencia de TODAS las palabras del email
# (algoritmo "Naive Bayes", que verás en el módulo 02 de Machine Learning).

Consejo profesional

El patrón mental bayesiano —"empiezo con una creencia base (prior) y la actualizo con cada evidencia nueva"— es útil mucho más allá del código: es como deberías evaluar demos de productos de IA, resultados de A/B tests y hasta afirmaciones de proveedores. "Nuestro modelo acierta el 99%" no significa nada sin conocer la tasa base del problema.

4.4. Distribuciones: uniforme y normal

¿Qué es una distribución? La descripción de qué valores puede tomar algo aleatorio y con qué frecuencia. Es el "perfil de personalidad" de una variable aleatoria.

Distribución uniforme: todos los valores por igual

Un dado justo: cada cara tiene exactamente la misma probabilidad (1/6). Un random() entre 0 y 1: cualquier valor es igual de probable. Su histograma es plano:

 frecuencia
    |  ████ ████ ████ ████ ████ ████
    |  ████ ████ ████ ████ ████ ████
    |  ████ ████ ████ ████ ████ ████
    +----1----2----3----4----5----6---→ valor del dado
              (todo igual de probable)

Distribución normal (gaussiana): la campana que aparece en todas partes

La mayoría de los valores se agolpan alrededor de la media, y los valores extremos son raros. Su histograma es la famosa campana de Gauss:

 frecuencia
    ↑                    ▄▄███▄▄
    |                  ▄█████████▄
    |                ▄█████████████▄
    |              ▄█████████████████▄
    |           ▄▄█████████████████████▄▄
    |      ▄▄▄███████████████████████████▄▄▄
    +---──────────────────┼──────────────────---→ valor
                        media μ
         ← -2σ    -1σ     μ     +1σ    +2σ →
              (el 68% cae entre -1σ y +1σ,
               el 95% entre -2σ y +2σ)

¿Por qué aparece en todas partes? Por el teorema central del límite: cuando un resultado es la suma de muchas pequeñas influencias independientes (genética + nutrición + azar → altura; miles de decisiones de compra → ventas diarias; millones de pesos aleatorios → activaciones de una red), la distribución del total tiende a ser normal, sin importar cómo sea cada influencia individual. Es uno de los resultados más asombrosos de las matemáticas, y por eso la campana está en alturas, errores de medición, notas de exámenes y ruido de sensores.

¿Para qué sirve en IA? - Los pesos de las redes neuronales se inicializan con valores de una distribución normal. - Muchas técnicas asumen datos con forma normal (normalización, detección de anomalías: "está a más de 3σ de la media → sospechoso"). - El "ruido" que se añade y se quita en los modelos de difusión (Stable Diffusion, DALL·E) es ruido gaussiano: generar una imagen es, literalmente, esculpir una campana de Gauss.

import numpy as np

np.random.seed(42)                     # reproducibilidad del experimento

# UNIFORME: 10.000 valores entre 0 y 10, todos igual de probables.
uniforme = np.random.uniform(0, 10, 10_000)

# NORMAL: 10.000 valores con media 5 y desviación estándar 1.5.
normal = np.random.normal(loc=5, scale=1.5, size=10_000)
#                         loc = media (centro de la campana)
#                         scale = desviación estándar (anchura de la campana)

# Comparamos sus "personalidades" con un histograma de texto:
def histograma_ascii(datos, titulo):
    print(f"\n{titulo}")
    # np.histogram cuenta cuántos valores caen en cada uno de 10 "cubos".
    conteos, bordes = np.histogram(datos, bins=10, range=(0, 10))
    for i, c in enumerate(conteos):
        barra = "█" * (c // 60)                     # 1 bloque por cada 60 valores
        print(f"{bordes[i]:4.1f}-{bordes[i+1]:4.1f} | {barra}")

histograma_ascii(uniforme, "UNIFORME (plana):")
histograma_ascii(normal, "NORMAL (campana):")
# Verás la uniforme con barras casi iguales y la normal con joroba central.

4.5. Media, mediana, varianza y desviación estándar

Las cuatro medidas para "resumir" un montón de números. Con ejemplo de salarios, que es donde mejor se ve la trampa:

Medida ¿Qué responde? Fórmula / idea Talón de Aquiles
Media ($\mu$) ¿Cuál es el valor "típico"? Suma todo y divide entre n La destrozan los valores extremos
Mediana ¿Cuál es el valor del medio? Ordena y toma el central Ignora la magnitud de los extremos
Varianza ($\sigma^2$) ¿Cuánto se dispersan los datos? Media de las desviaciones al cuadrado Sus unidades están "al cuadrado" (€²), difícil de interpretar
Desv. estándar ($\sigma$) ¿Cuánto se dispersan, en unidades normales? Raíz cuadrada de la varianza Ninguno grave: es la reina

Fórmulas:

$$\mu = \frac{1}{n}\sum_{i=1}^{n} x_i \qquad \sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i - \mu)^2 \qquad \sigma = \sqrt{\sigma^2}$$

import numpy as np

# Salarios anuales (en miles de €) de una startup de 8 personas...
# 7 empleados normales y 1 CEO fundador.
salarios = np.array([28, 30, 32, 35, 35, 38, 40, 250])

# MEDIA: suma / cantidad. El CEO la infla escandalosamente.
print(f"Media:    {salarios.mean():.1f}")     # 61.0 -> "el salario típico es 61k" 

# MEDIANA: el valor central de los datos ordenados. Inmune al CEO.
print(f"Mediana:  {np.median(salarios):.1f}") # 35.0 -> el salario típico REAL ✅

# VARIANZA: promedio de las distancias (al cuadrado) respecto a la media.
print(f"Varianza: {salarios.var():.1f}")      # 5088.5 (en "miles de € al cuadrado"...)

# DESVIACIÓN ESTÁNDAR: raíz de la varianza. Dispersión en unidades normales.
print(f"Desv.est: {salarios.std():.1f}")      # 71.3 -> los datos se alejan ~71k
                                              #        de la media (¡enorme!, delata
                                              #        la presencia de un extremo)

Advertencia

"La media es 61k" es técnicamente cierto y prácticamente engañoso: nadie en esa empresa cobra cerca de 61k. Cuando los datos tienen valores extremos (salarios, precios de vivienda, tiempos de respuesta de una API, longitud de documentos), reporta la mediana o al menos ambas. En IA usarás esto constantemente: la latencia mediana (p50) y el percentil 95 (p95) de tu API de LLM dicen la verdad; la latencia media la esconde.

Consejo profesional

Antes de entrenar cualquier modelo o de indexar documentos para RAG, calcula siempre mean, median, std, min y max de tus datos (longitudes de texto, valores numéricos, fechas). Cinco líneas de NumPy que detectan el 50% de los problemas de calidad de datos antes de que cuesten dinero.

4.6. Un LLM es una máquina de probabilidad condicional

Ha llegado el momento de la revelación central de la era LLM. ¿Qué hace exactamente GPT/Claude/Gemini cuando escribe? Responde una única pregunta, una y otra vez:

$$P(\text{siguiente token} \mid \text{todo el contexto anterior})$$

Es decir: "dado todo lo escrito hasta ahora, ¿qué probabilidad tiene cada token posible de ser el siguiente?". Un token es aproximadamente una palabra o trozo de palabra; el "contexto" es tu prompt más lo que el modelo lleva generado.

Ejemplo intuitivo. Contexto: "El gato se subió al". El modelo produce una distribución sobre TODO su vocabulario (decenas de miles de tokens):

Token candidato P(token | "El gato se subió al")
tejado 0.34
árbol 0.28
sofá 0.19
coche 0.07
armario 0.05
microondas 0.001
fotosíntesis 0.0000001
... (todo el vocabulario) ... (todo suma 1)

El modelo elige un token de esa distribución (cómo lo elige depende de la temperatura, siguiente sección), lo añade al contexto, y vuelve a empezar: calcula la nueva distribución condicionada al contexto ampliado, elige, añade, repite. Un párrafo entero de un LLM es una cadena de miles de decisiones probabilísticas condicionales.

Consecuencias prácticas de entender esto:

  1. Las "alucinaciones" no son bugs, son la naturaleza del sistema: el modelo siempre produce el token más plausible estadísticamente, no el más verdadero. Si la continuación falsa es plausible según sus datos de entrenamiento, la generará con total confianza.
  2. Por eso el prompt importa tanto: el prompt es la condición de la probabilidad. Cambiar el contexto cambia toda la distribución. El prompt engineering es, matemáticamente, ingeniería de condiciones.
  3. Por eso el mismo prompt puede dar respuestas distintas: hay muestreo aleatorio sobre la distribución (salvo temperatura 0, y ni siquiera siempre entonces).
  4. Por eso RAG funciona: si metes los documentos correctos en el contexto, condicionas la distribución hacia continuaciones fieles a esos documentos.

Nota

Que el mecanismo sea "predecir el siguiente token" no hace al sistema trivial. Para predecir bien el siguiente token de "la capital de Francia es", el modelo necesita representar conocimiento geográfico; para continuar código, necesita representar la lógica del programa. La sencillez del objetivo (una probabilidad condicional) es compatible con una enorme sofisticación interna. Este debate lo retomaremos en el módulo de LLMs.

4.7. Softmax y la temperatura de los LLMs

El problema: la última capa de un LLM (una multiplicación de matrices, sección 2.6) produce un número crudo por cada token del vocabulario, llamados logits: [3.2, 1.1, -0.5, 5.0, ...]. Pueden ser negativos, enormes, y no suman nada en particular. Necesitamos convertirlos en probabilidades: números entre 0 y 1 que sumen exactamente 1.

¿Qué hace softmax? Exactamente esa conversión, en dos pasos: exponencia cada logit (lo que garantiza números positivos y amplifica las diferencias) y divide cada uno entre la suma total (lo que garantiza que todo sume 1).

Fórmula:

$$\text{softmax}(z_i) = \frac{e^{z_i}}{\sum_{j=1}^{n} e^{z_j}}$$

Analogía: softmax es un "reparto de tarta ponderado por mérito exponencial". Cada candidato aporta $e^{z_i}$ "puntos de mérito" y se lleva una porción de tarta proporcional a sus puntos. La exponencial hace que pequeñas ventajas en el logit se conviertan en grandes ventajas en probabilidad: es un "el que destaca, arrasa" suavizado (de ahí soft-max: un máximo blando).

Implementación línea por línea:

import numpy as np

def softmax(logits):
    """Convierte un vector de números crudos (logits) en probabilidades."""
    # TRUCO DE ESTABILIDAD: restamos el máximo antes de exponenciar.
    # No cambia el resultado matemático (se cancela en la división),
    # pero evita que e^1000 desborde a infinito. TODA implementación
    # profesional lleva esta línea.
    z = logits - np.max(logits)
    # Paso 1: exponenciar -> todos positivos, diferencias amplificadas.
    exponenciales = np.exp(z)
    # Paso 2: normalizar -> dividir entre la suma para que todo sume 1.
    return exponenciales / exponenciales.sum()

# Logits crudos del modelo para 4 tokens candidatos:
#                  tejado  árbol  sofá  microondas
logits = np.array([ 2.0,    1.7,   1.3,   -3.0 ])

probs = softmax(logits)
print(np.round(probs, 3))    # [0.451 0.334 0.224 0.003]  <- ¡probabilidades!
print(probs.sum())           # 1.0000...  <- suman exactamente 1 ✅

La temperatura: el dial de creatividad

Ahora la conexión estrella con los LLMs. La temperatura es un escalar que divide los logits antes del softmax:

$$\text{softmax}!\left(\frac{z_i}{T}\right)$$

  • T < 1 (ej. 0.2): los logits divididos se hacen más grandes en valor relativo → las diferencias se exageran → el token favorito acapara casi toda la probabilidad → salida predecible y conservadora.
  • T = 1: la distribución original del modelo, tal cual.
  • T > 1 (ej. 1.5): los logits se aplanan → los tokens improbables ganan opciones → salida variada, creativa... y más propensa a disparates.
import numpy as np

def softmax_con_temperatura(logits, temperatura):
    """Softmax con el dial de temperatura de los LLMs."""
    # Dividimos los logits por la temperatura ANTES del softmax.
    # T pequeña -> logits "más separados" -> distribución más picuda.
    # T grande  -> logits "más juntos"    -> distribución más plana.
    z = logits / temperatura
    z = z - np.max(z)                    # mismo truco de estabilidad
    e = np.exp(z)
    return e / e.sum()

logits = np.array([2.0, 1.7, 1.3, -3.0])   # tejado, árbol, sofá, microondas
tokens = ["tejado", "árbol", "sofá", "microondas"]

for T in [0.2, 1.0, 2.0]:
    probs = softmax_con_temperatura(logits, T)
    reparto = ", ".join(f"{t}={p:.3f}" for t, p in zip(tokens, probs))
    print(f"T={T}: {reparto}")

# Salida:
# T=0.2: tejado=0.807, árbol=0.180, sofá=0.024, microondas=0.000  <- casi determinista
# T=1.0: tejado=0.451, árbol=0.334, sofá=0.224, microondas=0.003  <- equilibrado
# T=2.0: tejado=0.362, árbol=0.311, sofá=0.255, microondas=0.030  <- ¡el microondas
#                                                                     ya tiene un 3%!

Consejo profesional

Reglas prácticas de temperatura en producción: 0.0–0.3 para extracción de datos, clasificación, generación de JSON o código (quieres consistencia); 0.7–1.0 para redacción y brainstorming; >1.2 casi nunca en producción. Y recuerda: temperatura 0 reduce la aleatoriedad pero no garantiza respuestas 100% idénticas ni verdaderas: sigue siendo la misma distribución, solo que tomando siempre el pico.

Error común

Creer que subir la temperatura "hace al modelo más inteligente" o bajar la temperatura "lo hace más preciso factualmente". Falso: la temperatura no cambia lo que el modelo sabe (los logits son los mismos), solo cambia cómo se muestrea de esa distribución. Un modelo que "no sabe" algo alucinará a cualquier temperatura.

4.8. Correlación vs. causalidad

¿Qué es la correlación? Una medida (entre -1 y 1) de cuánto se mueven juntas dos variables. +1 = suben y bajan perfectamente a la vez; -1 = cuando una sube la otra baja; 0 = no hay relación lineal.

El error clásico ( y carísimo): concluir que porque A y B se mueven juntas, A causa B. La correlación no distingue entre tres escenarios completamente diferentes:

  Escenario 1:  A ──causa──► B          (el helado SÍ da sed)
  Escenario 2:  B ──causa──► A          (¡la flecha va al revés!)
  Escenario 3:      C                   (una TERCERA variable causa ambas)
               ╱causa╲causa
              ▼       ▼
              A       B

Ejemplo clásico del escenario 3: las ventas de helado y los ahogamientos en piscinas están fuertemente correlacionados. ¿El helado causa ahogamientos? No: la variable oculta C es el verano (calor → más helados Y más gente en piscinas).

import numpy as np

np.random.seed(0)

# La variable OCULTA: temperatura de cada día del año (en °C).
temperatura = np.random.uniform(5, 35, 365)

# Helados vendidos: dependen de la temperatura (más calor, más helados) + ruido.
helados = 10 * temperatura + np.random.normal(0, 20, 365)

# Incidentes en piscinas: TAMBIÉN dependen de la temperatura + ruido.
# ¡Los helados no aparecen en esta fórmula para nada!
incidentes = 0.5 * temperatura + np.random.normal(0, 2, 365)

# np.corrcoef devuelve la matriz de correlaciones; [0,1] es la que nos interesa.
corr = np.corrcoef(helados, incidentes)[0, 1]
print(f"Correlación helados-incidentes: {corr:.2f}")   # ≈ 0.78 -> ¡alta!

# Correlación altísima entre dos variables SIN NINGUNA relación causal:
# ambas son hijas de la misma madre (la temperatura).

Caso empresarial

Una cadena de retail descubrió que los clientes que usaban su app móvil gastaban un 40% más, y concluyó: "hagamos que todos instalen la app y gastarán más". Invirtieron una fortuna en campañas de instalación... y el gasto no subió. ¿Por qué? La flecha causal iba al revés (y había una tercera variable): los clientes ya fieles y de alto gasto eran quienes se instalaban la app, no al revés. La app no causaba el gasto: la fidelidad causaba ambas cosas. Para demostrar causalidad se necesita un experimento controlado (A/B test: asignar la intervención al azar), no una correlación en datos históricos. Este es posiblemente el error analítico más caro y repetido del mundo empresarial.

Advertencia para tu carrera en IA

Los modelos de machine learning aprenden correlaciones, no causas. Un modelo de contratación entrenado con datos históricos sesgados aprenderá la correlación "perfil X → contratado" aunque sea injusta y no causal. Detectar cuándo tu modelo explota una correlación espuria (el famoso modelo que "detectaba tanques" pero en realidad detectaba días nublados en las fotos) es una de las habilidades más valiosas de un AI Engineer.

4.9. Ejercicios rápidos de probabilidad

Ejercicio 4.1. En el ejemplo médico de la sección 4.2, ¿cómo cambia $P(\text{enferma} \mid +)$ si la enfermedad afecta al 10% de la población en lugar de al 1%? Calcúlalo con el código.

Ver solución
p_enferma = 0.10                            # única línea que cambia
p_pos_si_enferma = 0.90
p_pos_si_sana = 0.05

p_positivo = p_pos_si_enferma * p_enferma + p_pos_si_sana * (1 - p_enferma)
print((p_pos_si_enferma * p_enferma) / p_positivo)   # ≈ 0.667
Sube del 15,4% al **66,7%**. Moraleja: el mismo test vale mucho más cuando el fenómeno es más frecuente (el *prior* manda). Por eso los cribados masivos de enfermedades raras generan tantos falsos positivos.

Ejercicio 4.2. Aplica tu función softmax a los logits [10.0, 10.0, 10.0] y a [100.0, 1.0, 1.0]. Antes de ejecutar, predice el resultado.

Ver solución
import numpy as np

def softmax(logits):
    z = logits - np.max(logits)
    e = np.exp(z)
    return e / e.sum()

print(softmax(np.array([10.0, 10.0, 10.0])))   # [0.333 0.333 0.333]
# Logits iguales -> reparto perfectamente equitativo.

print(softmax(np.array([100.0, 1.0, 1.0])))    # [1.000 0.000 0.000] (casi exacto)
# Un logit domina por mucho -> se lleva prácticamente toda la probabilidad.

Ejercicio 4.3. Calcula media y mediana de [10, 12, 11, 13, 12, 300]. ¿Cuál describirías como "el valor típico" y por qué?

Ver solución
import numpy as np
datos = np.array([10, 12, 11, 13, 12, 300])
print(datos.mean())        # 59.67
print(np.median(datos))    # 12.0
La **mediana (12)**: cinco de los seis valores están entre 10 y 13, así que 12 es el valor típico real. La media (59.67) está completamente distorsionada por el valor extremo 300 y no representa a **ningún** dato real.

Ejercicio 4.4 (conceptual). Un dashboard muestra que los días con más consultas al chatbot de soporte tienen más cancelaciones de clientes. Un directivo propone limitar el uso del chatbot para reducir cancelaciones. ¿Qué le dirías?

Ver solución Que confunde correlación con causalidad. La explicación más plausible es una **tercera variable**: los días con **problemas del producto** (caídas, bugs, errores de facturación) causan a la vez más consultas al chatbot Y más cancelaciones. El chatbot no causa las cancelaciones; probablemente las **reduce** (escenario 2: la relación causal real podría ir al revés). Limitar el chatbot podría empeorar las cancelaciones. Para saberlo de verdad: analizar los motivos de consulta, o diseñar un experimento controlado.

5. Tabla maestra: cada concepto y dónde lo verás en la academia

Esta tabla es tu mapa de conexiones. Márcala: volverás a ella durante toda la academia.

Concepto matemático Sección Dónde lo volverás a ver en la academia
Vector 2.1 Embeddings (módulo de LLMs y RAG), features de ML (módulo 02), representación de imágenes (visión)
Shape de arrays 2.1 Depuración diaria en NumPy/Pandas/PyTorch a partir del capítulo 4
Producto punto 2.3 Neuronas (módulo 03), attention de Transformers, scoring en recuperación RAG
Norma 2.4 Normalización de embeddings, regularización L2, gradient clipping
Distancia euclidiana 2.4 k-NN y K-Means (módulo 02), métricas en bases de datos vectoriales
Similitud coseno 2.5 Búsqueda semántica, RAG, sistemas de recomendación, memoria de agentes — módulos de LLM aplicado
Multiplicación de matrices 2.6 Toda capa de red neuronal (módulo 03), cálculo de VRAM para desplegar modelos, la fórmula QKᵀ del attention
Transpuesta 2.7 La fórmula del attention softmax(QKᵀ/√d)V (módulo 03)
Matriz identidad 2.7 Conexiones residuales de Transformers, intuición de LoRA (fine-tuning)
Transformación lineal 2.7 Interpretación geométrica del deep learning (módulo 03)
Derivada 3.1 Funciones de pérdida y su comportamiento (módulos 02 y 03)
Mínimos / loss 3.2 Curvas de entrenamiento, overfitting/underfitting (módulo 02), TensorBoard/W&B
Descenso de gradiente 3.3–3.4 Entrenamiento de TODO modelo: regresión (módulo 02), redes (módulo 03), fine-tuning de LLMs
Tasa de aprendizaje 3.3 El hiperparámetro nº 1 que ajustarás en fine-tuning y entrenamiento
Regla de la cadena 3.5 Backpropagation completo (módulo 03)
Probabilidad condicional 4.1 P(token | contexto): la definición misma de un LLM; clasificadores (módulo 02)
Teorema de Bayes 4.2–4.3 Naive Bayes (módulo 02), matriz de confusión, precisión/recall en evaluación de modelos
Distribución normal 4.4 Inicialización de pesos, normalización de datos, modelos de difusión (imágenes)
Media/mediana/σ 4.5 Análisis exploratorio de datos (módulo 02), monitorización de latencias en producción
Softmax 4.7 Capa final de todo clasificador y de todo LLM; la fórmula del attention
Temperatura 4.7 Parámetro de TODA llamada a una API de LLM (módulos de LLM aplicado y agentes)
Correlación vs. causalidad 4.8 Evaluación de modelos, detección de sesgos, análisis de negocio con IA

6. Buenas prácticas

  1. Ejecuta todo el código que leas. Las matemáticas con código solo funcionan si tecleas y ejecutas. Leer código de NumPy sin ejecutarlo es como aprender a nadar por correspondencia.
  2. Imprime .shape compulsivamente. Ante cualquier duda u error con arrays, lo primero es print(x.shape). Es el hábito nº 1 del profesional de IA.
  3. Usa @ para matrices y vectores, * para elemento-a-elemento. Y si dudas de cuál necesitas, haz un ejemplo diminuto de 2×2 a mano.
  4. Fija semillas (np.random.seed) en tus experimentos. La reproducibilidad no es opcional en ciencia ni en ingeniería: sin semilla, no puedes distinguir una mejora real de un golpe de suerte.
  5. Ante cualquier métrica de "acierto", pregunta por la tasa base. Un 95% de acierto puede ser excelente o basura según la frecuencia del fenómeno (recuerda el ejemplo médico bayesiano).
  6. Reporta mediana (y percentiles), no solo media, cuando los datos puedan tener extremos: latencias, costes, longitudes de documento.
  7. Cuando un entrenamiento falle, piensa en la montaña: ¿zancada muy grande (loss explota)? ¿muy pequeña (loss no baja)? ¿terreno mal escalado (features sin normalizar)? El 80% de los diagnósticos salen de esta imagen mental.
  8. No memorices fórmulas: memoriza intuiciones y ten las fórmulas a mano. En el trabajo real siempre tendrás la fórmula a un clic; lo que no puedes googlear en medio de una reunión es la intuición.
  9. Normaliza tus embeddings o usa similitud coseno. Nunca compares embeddings crudos con distancia euclidiana sin haber pensado en las magnitudes.
  10. Refuerza con vídeo lo que leas aquí: los vídeos de 3Blue1Brown (bibliografía) ponen animaciones a las mismas intuiciones de este capítulo. La combinación texto + código + animación es imbatible.

7. Errores comunes

# Error Por qué pasa Cómo evitarlo
1 Usar * creyendo que multiplica matrices En papel, la yuxtaposición AB significa producto matricial; en NumPy * es elemento a elemento Producto matricial = @. Siempre.
2 Sumar/comparar vectores de shapes incompatibles Mezclar embeddings de modelos distintos o transponer donde no toca Imprimir .shape de ambos operandos antes de operar
3 Comparar embeddings de modelos diferentes con coseno "Todos los embeddings son vectores, ¿no?" — pero cada modelo define su propio espacio Un solo modelo de embeddings por índice; re-indexar si cambias de modelo
4 Learning rate demasiado alto → loss en nan Se copia un valor de otro problema sin ajustar Ante nan: dividir la tasa entre 10 y reintentar
5 Interpretar P(enfermo | positivo) como la sensibilidad del test La intuición humana invierte condicionales sin darse cuenta Montar la tabla de 10.000 personas o aplicar Bayes explícitamente
6 Usar la media con datos que tienen valores extremos Es la medida "por defecto" que todos conocen Mirar también mediana y desviación estándar; graficar el histograma
7 Creer que temperatura baja = respuestas más verdaderas Confundir "menos aleatorio" con "más correcto" La temperatura muestrea la misma distribución; no añade conocimiento
8 Presentar una correlación como prueba de causa-efecto Es persuasiva y suele coincidir con lo que uno quiere creer Buscar terceras variables; exigir experimento controlado (A/B)
9 Implementar softmax sin restar el máximo La versión "de libro" no menciona el desbordamiento z = logits - np.max(logits) antes de np.exp
10 Estudiar matemáticas "en abstracto" durante meses antes de tocar IA El mito del matemático de la sección 1.1 Aprender exactamente lo que necesitas, con código, justo cuando lo necesitas (este capítulo)

8. FAQ - Preguntas frecuentes

1. No he tocado las matemáticas desde el instituto y se me daban mal. ¿De verdad puedo con esto? Sí, y no es una frase motivacional vacía. "Se me daban mal las matemáticas" casi siempre significa "se me daba mal el formato pizarra-y-examen". Aquí el formato es otro: analogía + código ejecutable + propósito claro. Si puedes entender "el producto punto mide parecido" y ejecutar 10 líneas de NumPy, puedes con todo el capítulo. Miles de desarrolladores sin base matemática trabajan hoy en IA.

2. ¿Necesito saber derivar e integrar a mano? No. Necesitas entender qué significa una derivada (tasa de cambio, pendiente) y qué hace el descenso de gradiente. Los frameworks calculan todas las derivadas automáticamente. Integrar no aparece prácticamente nunca en el trabajo de un AI Engineer. Si algún día lees un paper con una integral, casi siempre significa "promedio sobre una distribución" y con esa lectura te basta.

3. ¿Cuánto tiempo debería dedicar a este capítulo? Entre una y dos semanas a ritmo tranquilo: leerlo entero una vez, ejecutar todo el código, hacer los ejercicios y ver 3-4 vídeos de 3Blue1Brown de refuerzo. No intentes "dominarlo" antes de avanzar: los conceptos se consolidarán al usarlos en los módulos siguientes. Este capítulo está diseñado para que vuelvas a él.

4. ¿Por qué la similitud coseno y no la distancia euclidiana para embeddings? Porque el coseno mide la dirección (el "tema", el significado) e ignora la magnitud (que puede variar por la longitud del texto u otros factores irrelevantes). Dos textos sobre lo mismo con longitudes distintas: euclidiana los ve lejanos, coseno los ve gemelos. Matiz profesional: si los embeddings están normalizados a norma 1 (como los de OpenAI), ambas métricas producen el mismo ranking y entonces se usa producto punto por velocidad.

5. ¿Qué es exactamente un "parámetro" cuando dicen que un modelo tiene 7B? Cada una de las celdas numéricas de sus matrices de pesos W (y vectores de sesgo b) que se ajustan durante el entrenamiento. 7B = 7.000 millones de numeritos, organizados en matrices, que se multiplican con tus datos (sección 2.6) y que el descenso de gradiente (sección 3.3) fue ajustando durante el entrenamiento para minimizar el error de predicción del siguiente token.

6. ¿Temperatura 0 hace que el LLM sea determinista y no alucine? Ni lo uno ni lo otro del todo. Con T→0 se toma (casi) siempre el token más probable, lo que reduce muchísimo la variabilidad, pero factores de implementación (paralelismo en GPU, batching del proveedor) pueden producir pequeñas diferencias entre ejecuciones. Y sobre las alucinaciones: la temperatura no cambia lo que el modelo "sabe" (los logits); si el token más probable es falso, T=0 lo elegirá con total determinación. Contra alucinaciones: RAG, grounding y verificación, no el dial de temperatura.

7. ¿Debo aprender también eigenvalores, SVD, cadenas de Markov...? Para empezar a trabajar, no. Esos temas aparecen en momentos puntuales (PCA usa eigenvectores, LoRA se inspira en descomposiciones de bajo rango) y la academia te dará la píldora concreta cuando toque. Regla de oro: aprende matemáticas just-in-time (cuando las necesites para algo concreto), no just-in-case (por si acaso, en abstracto). La segunda estrategia es la que quema a la gente.

8. ¿Este capítulo me basta para las entrevistas de AI Engineer? Para la parte matemática de una entrevista típica de AI Engineer (no de investigador), cubre lo esencial: producto punto y attention, coseno vs. euclidiana, qué es el descenso de gradiente y el learning rate, softmax y temperatura, Bayes y falsos positivos, correlación vs. causalidad. Lo que faltará será la parte aplicada (frameworks, RAG, evaluación), que es justo el resto de la academia.

9. ¿Por qué se usa el error CUADRÁTICO y no el error a secas? Tres razones: (1) el cuadrado hace positivo cualquier error (pasarse en +10 y quedarse corto en −10 son igual de malos, y sin el cuadrado se cancelarían al promediar); (2) castiga proporcionalmente más los errores grandes (un error de 10 pesa 100, uno de 1 pesa 1), lo que empuja al modelo a evitar fallos catastróficos; y (3) es suave y derivable en todas partes, lo que facilita el descenso de gradiente. Hay alternativas (error absoluto, Huber) que verás en el módulo 02.

10. ¿Qué hago si me pierdo con la notación de un paper o un libro? Primero, tabla de la sección 2.1: minúscula = escalar, minúscula negrita = vector, mayúscula negrita = matriz. Segundo: ∑ significa "bucle for que suma", ∏ "bucle for que multiplica", ∂ "derivada respecto a una variable", ∇ "vector de todas las derivadas", | "dado que". Con esas seis traducciones se lee el 90% de la notación de los papers de deep learning. Y tercero: pega el fragmento a un LLM y pídele que te lo traduzca a código NumPy — es uno de sus mejores usos como herramienta de estudio.


9. Resumen del capítulo

Lo esencial, destilado:

  • No necesitas ser matemático para trabajar en IA: necesitas intuición operativa sobre un puñado de conceptos. Un AI Engineer usa a diario vectores, similitud coseno, la idea de gradiente y probabilidad básica; las matemáticas pesadas viven en la investigación.
  • Álgebra lineal = el idioma de los datos. Todo se convierte en vectores (fichas de características) y matrices (tablas de pesos o de datos). El producto punto es un medidor de parecido y es la operación interna de neuronas, attention y recomendadores. La similitud coseno (producto punto normalizado) es la operación reina de la era LLM: búsqueda semántica, RAG y recomendación funcionan gracias a ella. La multiplicación de matrices ("muchos productos punto organizados") es el motor de cada capa de red neuronal: y = W·x + b.
  • Cálculo = el idioma del aprendizaje. La derivada es un velocímetro: mide cómo cambia una función. Entrenar un modelo = minimizar una función de error, y el método universal es el descenso de gradiente: sentir la pendiente y dar un pasito cuesta abajo, repetido millones de veces. La tasa de aprendizaje (tamaño del paso) es el hiperparámetro más delicado. La regla de la cadena (las tasas de cambio se multiplican en cadena) es la semilla de backpropagation.
  • Probabilidad = el idioma de la incertidumbre. La probabilidad condicional P(A|B) es cómo la información nueva actualiza lo plausible; Bayes la invierte, y su lección estrella es que las tasas base mandan (el test "90% fiable" que solo implica 15% de enfermedad real). Un LLM es una máquina de P(siguiente token | contexto). Softmax convierte los números crudos del modelo en probabilidades, y la temperatura aplana o afila esa distribución (creatividad vs. consistencia). Y jamás confundas correlación con causalidad: los modelos aprenden correlaciones, y las decisiones de negocio exigen experimentos.
  • Con este capítulo has entrenado tu primer modelo (regresión lineal con descenso de gradiente en NumPy puro), has construido un buscador semántico de juguete y has implementado el softmax con temperatura de un LLM. Ya has hecho IA de verdad.

En el próximo capítulo pondremos a punto tu herramienta principal de trabajo: Python, con el nivel de profundidad que un ingeniero de IA necesita.


10. Bibliografía y recursos recomendados

Vídeo (empieza por aquí — refuerzan exactamente este capítulo):

  • 3Blue1Brown — "Essence of Linear Algebra" (YouTube, subtítulos en español): la mejor visualización jamás hecha de vectores, transformaciones lineales y producto punto. Los capítulos 1-4 y 9 complementan directamente nuestra sección 2. youtube.com/@3blue1brown
  • 3Blue1Brown — "Essence of Calculus" y su serie sobre redes neuronales y descenso de gradiente: animaciones de la montaña, la pendiente y backpropagation.
  • Khan Academy en español: cursos gratuitos y en castellano de álgebra lineal, cálculo diferencial y probabilidad, con ejercicios autocorregidos. Ideal para repasar cualquier hueco de base. es.khanacademy.org
  • StatQuest with Josh Starmer (YouTube): explicaciones de estadística y ML paso a paso, con un estilo desenfadado. Especialmente buenos sus vídeos de distribución normal y de descenso de gradiente.

Libros (gratuitos y legales online):

  • "Mathematics for Machine Learning" — Deisenroth, Faisal y Ong (Cambridge University Press). EL libro de referencia para las mates de ML, descargable gratis en mml-book.github.io. Nivel superior a este capítulo: úsalo como siguiente paso cuando quieras profundizar.
  • "Deep Learning" — Goodfellow, Bengio y Courville. Sus capítulos 2 (álgebra lineal), 3 (probabilidad) y 4 (computación numérica) son el estándar académico. Gratis en deeplearningbook.org.
  • "Think Stats" y "Think Bayes" — Allen B. Downey. Estadística y Bayes con Python en lugar de fórmulas, totalmente alineados con la filosofía de esta academia. Gratis en greenteapress.com.

Práctica interactiva:

Nota final

No intentes consumir toda la bibliografía ahora. Marca esta página, avanza al capítulo 4, y vuelve aquí cada vez que un módulo posterior te haga pensar "esto me suena del capítulo de mates". Así es como se aprende de verdad: en espiral, no en línea recta.


Anterior Módulo Siguiente
Capítulo 2: Historia de la IA 01-FUNDAMENTOS Capítulo 4: Python para IA