Phase 01 - Lesson 16

Métodos de Muestreo

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

El muestreo es cómo la IA explora el espacio de posibilidades.

Tipo: Construir Lenguaje: Python Requisitos previos: Fase 1, Lecciones 06-07 (Probabilidad, Teorema de Bayes) Tiempo: ~120 minutos

Objetivos de Aprendizaje

  • Implementar muestreo por CDF inversa, por rechazo y por importancia desde cero usando solo números aleatorios uniformes
  • Construir muestreo por temperatura, top-k y top-p (núcleo) para la generación de tokens de modelos de lenguaje
  • Explicar el truco de la reparametrización y por qué habilita la retropropagación a través del muestreo en VAEs
  • Ejecutar MCMC de Metropolis-Hastings para muestrear de una distribución objetivo no normalizada

El Problema

Un modelo de lenguaje termina de procesar tu prompt y produce un vector de 50.000 logits. Uno por cada token en su vocabulario. Ahora tiene que elegir uno. ¿Cómo?

Si siempre elige el token de mayor probabilidad, cada respuesta es idéntica. Determinista. Aburrida. Si elige uniformemente al azar, la salida es un sinsentido. La respuesta vive en algún lugar entre esos extremos, y ese algún lugar está controlado por el muestreo.

El muestreo no se limita a la generación de texto. El aprendizaje por refuerzo estima gradientes de política muestreando trayectorias. Los VAEs aprenden representaciones latentes muestreando de distribuciones aprendidas y retropropagando a través de la aleatoriedad. Los modelos de difusión generan imágenes muestreando ruido y eliminando el ruido iterativamente. Los métodos de Monte Carlo estiman integrales que no tienen solución cerrada. Los algoritmos de MCMC exploran distribuciones posteriores de alta dimensión imposibles de enumerar.

Todo sistema de IA generativa es un sistema de muestreo. La estrategia de muestreo determina la calidad, la diversidad y la controlabilidad de la salida. Esta lección construye todo método de muestreo importante desde cero, comenzando por números aleatorios uniformes y terminando con las técnicas que dan energía a los LLMs y modelos generativos modernos.

El Concepto

Por qué el Muestreo Importa

El muestreo aparece en cuatro roles fundamentales a lo largo de la IA y el aprendizaje automático:

Generación. Los modelos de lenguaje, los modelos de difusión y las GANs producen salida por muestreo. El algoritmo de muestreo controla directamente la creatividad, la coherencia y la diversidad. La temperatura, el top-k y el muestreo por núcleo son las perillas que los ingenieros giran a diario.

Entrenamiento. El descenso de gradiente estocástico muestrea mini-batches. El dropout muestrea neuronas para desactivar. El aumento de datos (data augmentation) muestrea transformaciones aleatorias. El muestreo por importancia repondera muestras para reducir la varianza del gradiente en el aprendizaje por refuerzo (PPO, TRPO).

Estimación. Muchas cantidades en ML no tienen solución cerrada. La pérdida esperada sobre una distribución de datos, la función de partición de un modelo basado en energía, la evidencia en la inferencia bayesiana. La estimación de Monte Carlo aproxima todas ellas promediando sobre muestras.

Exploración. Los algoritmos de MCMC exploran distribuciones posteriores en la inferencia bayesiana. Las estrategias evolutivas muestrean perturbaciones de parámetros. El muestreo de Thompson equilibra exploración y explotación en los bandits.

El desafío central: solo puedes muestrear directamente de distribuciones simples (uniforme, normal). Para todo lo demás, necesitas un método para convertir muestras simples en muestras de tu distribución objetivo.

Muestreo Aleatorio Uniforme

Todo método de muestreo comienza aquí. Un generador de números aleatorios uniformes produce valores en [0, 1) donde todo subintervalo de igual longitud tiene igual probabilidad.

U ~ Uniform(0, 1)

P(a <= U <= b) = b - a    for 0 <= a <= b <= 1

Properties:
  E[U] = 0.5
  Var(U) = 1/12

Para muestrear uniformemente de un conjunto discreto de n items, genera U y devuelve floor(n * U). Para muestrear de un rango continuo [a, b], calcula a + (b - a) * U.

La idea clave: un solo número aleatorio uniforme contiene exactamente la cantidad correcta de aleatoriedad para producir una muestra de cualquier distribución. El truco es encontrar la transformación correcta.

Método de la CDF Inversa (Muestreo por Transformación Inversa)

La función de distribución acumulada (CDF) mapea valores a probabilidades:

F(x) = P(X <= x)

Properties:
  F is non-decreasing
  F(-inf) = 0
  F(+inf) = 1
  F maps the real line to [0, 1]

La CDF inversa mapea probabilidades de vuelta a valores. Si U ~ Uniform(0, 1), entonces X = F_inverse(U) sigue la distribución objetivo.

Algorithm:
  1. Generate u ~ Uniform(0, 1)
  2. Return F_inverse(u)

Why it works:
  P(X <= x) = P(F_inverse(U) <= x) = P(U <= F(x)) = F(x)

Ejemplo de la distribución exponencial:

PDF: f(x) = lambda * exp(-lambda * x),   x >= 0
CDF: F(x) = 1 - exp(-lambda * x)

Solve F(x) = u for x:
  u = 1 - exp(-lambda * x)
  exp(-lambda * x) = 1 - u
  x = -ln(1 - u) / lambda

Since (1 - U) and U have the same distribution:
  x = -ln(u) / lambda

Esto funciona perfectamente cuando puedes escribir F_inverse en forma cerrada. Para la distribución normal, no hay CDF inversa en forma cerrada, así que usamos otros métodos (Box-Muller, o aproximación numérica).

Versión discreta: Para distribuciones discretas, construye la CDF como una suma acumulada, genera U y encuentra el primer índice donde la suma acumulada supera U. Así es como funciona sample_categorical en la Lección 06.

Muestreo por Rechazo

Cuando no puedes invertir la CDF pero puedes evaluar la PDF objetivo salvo una constante, el muestreo por rechazo funciona.

Target distribution: p(x)  (can evaluate, possibly unnormalized)
Proposal distribution: q(x)  (can sample from)
Bound: M such that p(x) <= M * q(x) for all x

Algorithm:
  1. Sample x ~ q(x)
  2. Sample u ~ Uniform(0, 1)
  3. If u < p(x) / (M * q(x)), accept x
  4. Otherwise, reject and go to step 1

Acceptance rate = 1/M

Cuanto más ajustado el límite M, mayor la tasa de aceptación. En bajas dimensiones (1-3), el muestreo por rechazo funciona bien. En altas dimensiones, la tasa de aceptación cae exponencialmente porque la mayor parte del volumen de la propuesta se rechaza. Esa es la maldición de la dimensionalidad para el muestreo por rechazo.

Ejemplo: muestrear de una normal truncada. Usa una propuesta uniforme sobre el rango truncado. La envolvente M es el máximo de la PDF normal en ese rango.

Ejemplo: muestrear de un semicírculo. Propón uniformemente en el rectángulo delimitador. Acepta si el punto cae dentro del semicírculo. Así es como Monte Carlo calcula pi: la tasa de aceptación es igual a la razón de áreas pi/4.

Muestreo por Importancia

A veces no necesitas muestras de la distribución objetivo p(x). Necesitas estimar una esperanza bajo p(x), y tienes muestras de una distribución diferente q(x).

Goal: estimate E_p[f(x)] = integral of f(x) * p(x) dx

Rewrite:
  E_p[f(x)] = integral of f(x) * (p(x)/q(x)) * q(x) dx
            = E_q[f(x) * w(x)]

where w(x) = p(x) / q(x)  are the importance weights.

Estimator:
  E_p[f(x)] ~ (1/N) * sum(f(x_i) * w(x_i))    where x_i ~ q(x)

Esto es crítico en el aprendizaje por refuerzo. En PPO (Proximal Policy Optimization), recolectas trayectorias bajo una política antigua pi_old pero quieres optimizar una política nueva pi_new. El peso de importancia es pi_new(a|s) / pi_old(a|s). PPO recorta estos pesos para evitar que la nueva política diverja demasiado de la antigua.

La varianza del estimador de muestreo por importancia depende de qué tan similar es q a p. Si q es muy diferente de p, unas pocas muestras reciben pesos enormes y dominan la estimación. El muestreo por importancia autonormalizado divide por la suma de los pesos para reducir este problema:

E_p[f(x)] ~ sum(w_i * f(x_i)) / sum(w_i)

Estimación de Monte Carlo

La estimación de Monte Carlo aproxima integrales promediando muestras aleatorias. La ley de los grandes números garantiza la convergencia.

Goal: estimate I = integral of g(x) dx over domain D

Method:
  1. Sample x_1, ..., x_N uniformly from D
  2. I ~ (Volume of D / N) * sum(g(x_i))

Error: O(1 / sqrt(N))   regardless of dimension

La tasa de error es independiente de la dimensión. Por eso los métodos de Monte Carlo dominan en altas dimensiones, donde la integración basada en cuadrícula es imposible.

Estimando pi:

Sample (x, y) uniformly from [-1, 1] x [-1, 1]
Count how many fall inside the unit circle: x^2 + y^2 <= 1
pi ~ 4 * (count inside) / (total count)

Estimando esperanzas:

E[f(X)] ~ (1/N) * sum(f(x_i))    where x_i ~ p(x)

The sample mean converges to the true expectation.
Variance of the estimator = Var(f(X)) / N

Markov Chain Monte Carlo (MCMC): Metropolis-Hastings

El MCMC construye una cadena de Markov cuya distribución estacionaria es la distribución objetivo p(x). Después de suficientes pasos, las muestras de la cadena son (aproximadamente) muestras de p(x).

Target: p(x)  (known up to a normalizing constant)
Proposal: q(x'|x)  (how to propose the next state given the current state)

Metropolis-Hastings algorithm:
  1. Start at some x_0
  2. For t = 1, 2, ..., T:
     a. Propose x' ~ q(x'|x_t)
     b. Compute acceptance ratio:
        alpha = [p(x') * q(x_t|x')] / [p(x_t) * q(x'|x_t)]
     c. Accept with probability min(1, alpha):
        - If u < alpha (u ~ Uniform(0,1)): x_{t+1} = x'
        - Otherwise: x_{t+1} = x_t
  3. Discard first B samples (burn-in)
  4. Return remaining samples

Para propuestas simétricas (q(x'|x) = q(x|x')), la razón se simplifica a p(x')/p(x). Ese es el algoritmo de Metropolis original.

Por qué funciona. La regla de aceptación garantiza el balance detallado: la probabilidad de estar en x y moverse a x' es igual a la probabilidad de estar en x' y moverse a x. El balance detallado implica que p(x) es la distribución estacionaria de la cadena.

Consideraciones prácticas:

  • Burn-in: descarta las muestras iniciales antes de que la cadena alcance el equilibrio
  • Adelgazamiento (thinning): conserva cada k-ésima muestra para reducir la autocorrelación
  • Escala de la propuesta: demasiado pequeña y la cadena se mueve lento (alta aceptación, exploración lenta); demasiado grande y la mayoría de las propuestas se rechazan (baja aceptación, atascada en el lugar)
  • La tasa de aceptación óptima para una propuesta gaussiana en altas dimensiones es aproximadamente 0.234

Muestreo de Gibbs

El muestreo de Gibbs es un caso especial de MCMC para distribuciones multivariadas. En lugar de proponer un movimiento en todas las dimensiones a la vez, actualiza una variable a la vez a partir de su distribución condicional.

Target: p(x_1, x_2, ..., x_d)

Algorithm:
  For each iteration t:
    Sample x_1^{t+1} ~ p(x_1 | x_2^t, x_3^t, ..., x_d^t)
    Sample x_2^{t+1} ~ p(x_2 | x_1^{t+1}, x_3^t, ..., x_d^t)
    ...
    Sample x_d^{t+1} ~ p(x_d | x_1^{t+1}, x_2^{t+1}, ..., x_{d-1}^{t+1})

El muestreo de Gibbs requiere que puedas muestrear de cada distribución condicional p(x_i | x_{-i}). Esto es directo para muchos modelos:

  • Redes bayesianas: las condicionales se desprenden de la estructura del grafo
  • Mezclas gaussianas: las condicionales son gaussianas
  • Modelos de Ising: la condicional de cada spin depende solo de sus vecinos

La tasa de aceptación siempre es 1 (toda propuesta se acepta) porque muestrear de la condicional exacta satisface automáticamente el balance detallado.

Limitación. Cuando las variables están altamente correlacionadas, el muestreo de Gibbs mezcla lento porque actualizar una variable a la vez no puede hacer grandes movimientos diagonales a través de la distribución.

Muestreo por Temperatura (Usado en LLMs)

Los modelos de lenguaje producen logits z_1, ..., z_V para cada token en el vocabulario. El softmax los convierte en probabilidades. La temperatura reescala los logits antes del softmax:

p_i = exp(z_i / T) / sum(exp(z_j / T))

T = 1.0: standard softmax (original distribution)
T -> 0:  argmax (deterministic, always picks highest logit)
T -> inf: uniform (all tokens equally likely)
T < 1.0: sharpens the distribution (more confident, less diverse)
T > 1.0: flattens the distribution (less confident, more diverse)

Por qué funciona. Dividir los logits por T < 1 amplifica las diferencias entre los logits. Si z_1 = 2 y z_2 = 1, dividir por T = 0.5 da z_1/T = 4 y z_2/T = 2, haciendo la diferencia mayor. Después del softmax, el token de mayor logit recibe una porción mucho mayor.

En la práctica:

  • T = 0.0: decodificación voraz, mejor para preguntas y respuestas factuales
  • T = 0.3-0.7: levemente creativa, buena para generación de código
  • T = 0.7-1.0: equilibrada, buena para conversación general
  • T = 1.0-1.5: escritura creativa, brainstorming
  • T > 1.5: cada vez más aleatoria, rara vez útil

La temperatura no cambia qué tokens son posibles. Cambia la masa de probabilidad asignada a cada token.

Muestreo Top-k

El muestreo top-k restringe el conjunto de candidatos a los k tokens con las mayores probabilidades, luego renormaliza y muestrea de ese conjunto restringido.

Algorithm:
  1. Compute softmax probabilities for all V tokens
  2. Sort tokens by probability (descending)
  3. Keep only the top k tokens
  4. Renormalize: p_i' = p_i / sum(p_j for j in top-k)
  5. Sample from the renormalized distribution

k = 1:  greedy decoding
k = V:  no filtering (standard sampling)
k = 40: typical setting, removes long tail of unlikely tokens

El top-k impide que el modelo seleccione tokens extremadamente improbables (errores de tipeo, sinsentidos) que existen en la cola larga de la distribución del vocabulario. El problema: k es fijo sin importar el contexto. Cuando el modelo está confiado (un token tiene 95% de probabilidad), k = 40 todavía permite 39 alternativas. Cuando el modelo está incierto (la probabilidad está repartida entre 1000 tokens), k = 40 corta opciones plausibles.

Muestreo Top-p (Núcleo)

El muestreo top-p ajusta dinámicamente el tamaño del conjunto de candidatos. En lugar de conservar un número fijo de tokens, conserva el menor conjunto de tokens cuya probabilidad acumulada supera p.

Algorithm:
  1. Compute softmax probabilities for all V tokens
  2. Sort tokens by probability (descending)
  3. Find smallest k such that sum of top-k probabilities >= p
  4. Keep only those k tokens
  5. Renormalize and sample

p = 0.9:  keeps tokens covering 90% of probability mass
p = 1.0:  no filtering
p = 0.1:  very restrictive, nearly greedy

Cuando el modelo está confiado, el muestreo por núcleo conserva pocos tokens (quizá 2-3). Cuando el modelo está incierto, conserva muchos (quizá 200). Este comportamiento adaptativo es por qué el muestreo por núcleo generalmente produce mejor texto que el top-k.

Combinaciones comunes:

  • Temperatura 0.7 + top-p 0.9: buena configuración de propósito general
  • Temperatura 0.0 (voraz): mejor para tareas deterministas
  • Temperatura 1.0 + top-k 50: la configuración del paper original de Fan et al. (2018)

El top-k y el top-p pueden combinarse. Aplica el top-k primero, luego el top-p sobre el conjunto restante.

Truco de la Reparametrización (Usado en VAEs)

Los autoencoders variacionales (VAEs) aprenden codificando entradas en una distribución en el espacio latente, muestreando de esa distribución y decodificando la muestra de vuelta. El problema: no puedes retropropagar a través de una operación de muestreo.

Standard sampling (not differentiable):
  z ~ N(mu, sigma^2)

  The randomness blocks gradient flow.
  d/d_mu [sample from N(mu, sigma^2)] = ???

El truco de la reparametrización separa la aleatoriedad de los parámetros:

Reparameterized sampling:
  epsilon ~ N(0, 1)          (fixed random noise, no parameters)
  z = mu + sigma * epsilon   (deterministic function of parameters)

  Now z is a deterministic, differentiable function of mu and sigma.
  d(z)/d(mu) = 1
  d(z)/d(sigma) = epsilon

  Gradients flow through mu and sigma.

Esto funciona porque N(mu, sigma^2) tiene la misma distribución que mu + sigma * N(0, 1). La idea clave: mueve la aleatoriedad a una fuente sin parámetros (epsilon), luego expresa la muestra como una transformación diferenciable de los parámetros.

En el bucle de entrenamiento del VAE:

  1. El encoder produce mu y log(sigma^2) para cada entrada
  2. Muestrea epsilon ~ N(0, 1)
  3. Calcula z = mu + sigma * epsilon
  4. Decodifica z para reconstruir la entrada
  5. Retropropaga por los pasos 4, 3, 2, 1 (posible porque el paso 3 es diferenciable)

Sin el truco de la reparametrización, los VAEs no pueden entrenarse con retropropagación estándar. Esta sola idea hizo prácticos a los VAEs.

Gumbel-Softmax (Muestreo Categórico Diferenciable)

El truco de la reparametrización funciona para distribuciones continuas (gaussiana). Para distribuciones categóricas discretas, necesitamos un enfoque diferente. El Gumbel-Softmax provee una aproximación diferenciable del muestreo categórico.

El truco Gumbel-Max (no diferenciable):

To sample from a categorical distribution with log-probabilities log(p_1), ..., log(p_k):
  1. Sample g_i ~ Gumbel(0, 1) for each category
     (g = -log(-log(u)), where u ~ Uniform(0, 1))
  2. Return argmax(log(p_i) + g_i)

This produces exact categorical samples.

Gumbel-Softmax (aproximación diferenciable):

Replace the hard argmax with a soft softmax:
  y_i = exp((log(p_i) + g_i) / tau) / sum(exp((log(p_j) + g_j) / tau))

tau (temperature) controls the approximation:
  tau -> 0:  approaches a one-hot vector (hard categorical)
  tau -> inf: approaches uniform (1/k, 1/k, ..., 1/k)
  tau = 1.0: soft approximation

El Gumbel-Softmax produce una relajación continua de una muestra discreta. La salida es un vector de probabilidad (one-hot suave) en lugar de un one-hot duro. Los gradientes fluyen a través del softmax. Durante el forward pass en el entrenamiento, puedes usar el estimador "straight-through": usa el argmax duro para el forward pass pero los gradientes suaves del Gumbel-Softmax para el backward pass.

Aplicaciones:

  • Variables latentes discretas en VAEs
  • Búsqueda de arquitectura neuronal (elección de operaciones discretas)
  • Mecanismos de atención dura (hard attention)
  • Aprendizaje por refuerzo con acciones discretas

Muestreo Estratificado

El muestreo estándar de Monte Carlo puede dejar huecos en el espacio muestral por azar. El muestreo estratificado fuerza una cobertura uniforme dividiendo el espacio en estratos y muestreando de cada uno.

Standard Monte Carlo:
  Sample N points uniformly from [0, 1]
  Some regions may have clusters, others gaps

Stratified sampling:
  Divide [0, 1] into N equal strata: [0, 1/N), [1/N, 2/N), ..., [(N-1)/N, 1)
  Sample one point uniformly within each stratum
  x_i = (i + u_i) / N   where u_i ~ Uniform(0, 1),  i = 0, ..., N-1

El muestreo estratificado siempre tiene varianza menor o igual comparado con Monte Carlo estándar:

Var(stratified) <= Var(standard Monte Carlo)

The improvement is largest when f(x) varies smoothly.
For piecewise-constant functions, stratified sampling is exact.

Aplicaciones:

  • Integración numérica (quasi-Monte Carlo)
  • Splits de datos de entrenamiento (asegurando el balance de clases en cada fold)
  • Muestreo por importancia con estratificación (combinando ambas técnicas)
  • NeRF (Neural Radiance Fields) usa muestreo estratificado a lo largo de los rayos de la cámara

Conexión con los Modelos de Difusión

Los modelos de difusión generan imágenes a través de un proceso de muestreo. El proceso directo agrega ruido gaussiano a una imagen a lo largo de T pasos hasta que se vuelve ruido puro. El proceso inverso aprende a eliminar el ruido, recuperando la imagen original paso a paso.

Forward process (known):
  x_t = sqrt(alpha_t) * x_{t-1} + sqrt(1 - alpha_t) * epsilon
  where epsilon ~ N(0, I)

  After T steps: x_T ~ N(0, I)  (pure noise)

Reverse process (learned):
  x_{t-1} = (1/sqrt(alpha_t)) * (x_t - (1 - alpha_t)/sqrt(1 - alpha_bar_t) * epsilon_theta(x_t, t)) + sigma_t * z
  where z ~ N(0, I)

  Each denoising step is a sampling step.

La conexión con los métodos de esta lección:

  • Cada paso de eliminación de ruido usa el truco de la reparametrización (muestrea ruido, aplica transformación determinista)
  • El cronograma de ruido {alpha_t} controla una forma de recocido de temperatura (temperature annealing)
  • El entrenamiento usa la estimación de Monte Carlo para aproximar el ELBO (límite inferior de la evidencia)
  • El muestreo ancestral en los modelos de difusión es una cadena de Markov (cada paso depende solo del estado actual)

Todo el proceso de generación de imágenes es muestreo iterativo: comienza desde el ruido y, en cada paso, muestrea una versión un poco menos ruidosa condicionada al modelo aprendido de eliminación de ruido.

Construye

Paso 1: Muestreo uniforme y por CDF inversa

import math
import random

def sample_uniform(a, b):
    return a + (b - a) * random.random()

def sample_exponential_inverse_cdf(lam):
    u = random.random()
    return -math.log(u) / lam

Genera 10.000 muestras exponenciales y verifica que la media sea 1/lambda.

Paso 2: Muestreo por rechazo

def rejection_sample(target_pdf, proposal_sample, proposal_pdf, M):
    while True:
        x = proposal_sample()
        u = random.random()
        if u < target_pdf(x) / (M * proposal_pdf(x)):
            return x

Usa el muestreo por rechazo para extraer de una distribución normal truncada. Verifica la forma haciendo el histograma de las muestras.

Paso 3: Muestreo por importancia

def importance_sampling_estimate(f, target_pdf, proposal_pdf, proposal_sample, n):
    total = 0
    for _ in range(n):
        x = proposal_sample()
        w = target_pdf(x) / proposal_pdf(x)
        total += f(x) * w
    return total / n

Estima E[X^2] bajo una distribución normal usando una propuesta uniforme. Compara con la respuesta conocida (mu^2 + sigma^2).

Paso 4: Estimación de pi por Monte Carlo

def monte_carlo_pi(n):
    inside = 0
    for _ in range(n):
        x = random.uniform(-1, 1)
        y = random.uniform(-1, 1)
        if x*x + y*y <= 1:
            inside += 1
    return 4 * inside / n

Paso 5: MCMC de Metropolis-Hastings

def metropolis_hastings(target_log_pdf, proposal_sample, proposal_log_pdf, x0, n_samples, burn_in):
    samples = []
    x = x0
    for i in range(n_samples + burn_in):
        x_new = proposal_sample(x)
        log_alpha = (target_log_pdf(x_new) + proposal_log_pdf(x, x_new)
                     - target_log_pdf(x) - proposal_log_pdf(x_new, x))
        if math.log(random.random()) < log_alpha:
            x = x_new
        if i >= burn_in:
            samples.append(x)
    return samples

Muestrea de una distribución bimodal (mezcla de dos gaussianas). Visualiza la trayectoria de la cadena.

Paso 6: Muestreo de Gibbs

def gibbs_sampling_2d(conditional_x_given_y, conditional_y_given_x, x0, y0, n_samples, burn_in):
    x, y = x0, y0
    samples = []
    for i in range(n_samples + burn_in):
        x = conditional_x_given_y(y)
        y = conditional_y_given_x(x)
        if i >= burn_in:
            samples.append((x, y))
    return samples

Paso 7: Muestreo por temperatura

def softmax(logits):
    max_l = max(logits)
    exps = [math.exp(z - max_l) for z in logits]
    total = sum(exps)
    return [e / total for e in exps]

def temperature_sample(logits, temperature):
    scaled = [z / temperature for z in logits]
    probs = softmax(scaled)
    return sample_from_probs(probs)

Muestra cómo la temperatura cambia la distribución de salida para un conjunto de logits de token.

Paso 8: Muestreo top-k y top-p

def top_k_sample(logits, k):
    indexed = sorted(enumerate(logits), key=lambda x: -x[1])
    top = indexed[:k]
    top_logits = [l for _, l in top]
    probs = softmax(top_logits)
    idx = sample_from_probs(probs)
    return top[idx][0]

def top_p_sample(logits, p):
    probs = softmax(logits)
    indexed = sorted(enumerate(probs), key=lambda x: -x[1])
    cumsum = 0
    selected = []
    for token_idx, prob in indexed:
        cumsum += prob
        selected.append((token_idx, prob))
        if cumsum >= p:
            break
    sel_probs = [pr for _, pr in selected]
    total = sum(sel_probs)
    sel_probs = [pr / total for pr in sel_probs]
    idx = sample_from_probs(sel_probs)
    return selected[idx][0]

Paso 9: Truco de la reparametrización

def reparam_sample(mu, sigma):
    epsilon = random.gauss(0, 1)
    return mu + sigma * epsilon

def reparam_gradient(mu, sigma, epsilon):
    dz_dmu = 1.0
    dz_dsigma = epsilon
    return dz_dmu, dz_dsigma

Demuestra que los gradientes fluyen a través de la muestra reparametrizada pero no a través del muestreo directo.

Paso 10: Gumbel-Softmax

def gumbel_sample():
    u = random.random()
    return -math.log(-math.log(u))

def gumbel_softmax(logits, temperature):
    gumbels = [math.log(p) + gumbel_sample() for p in logits]
    return softmax([g / temperature for g in gumbels])

Muestra cómo disminuir la temperatura hace que la salida se acerque a un vector one-hot.

Las implementaciones completas con todas las visualizaciones están en code/sampling.py.

Úsalo

Con NumPy y SciPy, las versiones de producción:

import numpy as np

rng = np.random.default_rng(42)

exponential_samples = rng.exponential(scale=2.0, size=10000)
print(f"Exponential mean: {exponential_samples.mean():.4f} (expected 2.0)")

from scipy import stats
normal = stats.norm(loc=0, scale=1)
print(f"CDF at 1.96: {normal.cdf(1.96):.4f}")
print(f"Inverse CDF at 0.975: {normal.ppf(0.975):.4f}")

logits = np.array([2.0, 1.0, 0.5, 0.1, -1.0])
temperature = 0.7
scaled = logits / temperature
probs = np.exp(scaled - scaled.max()) / np.exp(scaled - scaled.max()).sum()
token = rng.choice(len(logits), p=probs)
print(f"Sampled token index: {token}")

Para MCMC a escala, usa bibliotecas dedicadas:

  • PyMC: modelado bayesiano completo con NUTS (HMC adaptativo)
  • emcee: muestreador MCMC de ensemble
  • NumPyro/JAX: MCMC acelerado por GPU

Lo construiste desde cero. Ahora sabes lo que están haciendo las llamadas de biblioteca.

Ejercicios

  1. Implementa el muestreo por CDF inversa para la distribución de Cauchy. La CDF es F(x) = 0.5 + arctan(x)/pi. Genera 10.000 muestras y grafica el histograma contra la PDF verdadera. Nota las colas pesadas (valores extremos lejos del centro).

  2. Usa el muestreo por rechazo para generar muestras de una distribución Beta(2, 5) usando una propuesta Uniform(0, 1). Grafica las muestras aceptadas contra la PDF Beta verdadera. ¿Cuál es la tasa de aceptación teórica?

  3. Estima la integral de sin(x) de 0 a pi usando Monte Carlo con 1.000, 10.000 y 100.000 muestras. Compara el error en cada nivel. Verifica que el error escala como O(1/sqrt(N)).

  4. Implementa el Metropolis-Hastings para muestrear de una distribución 2D p(x, y) proporcional a exp(-(x^2 * y^2 + x^2 + y^2 - 8x - 8y) / 2). Grafica las muestras y la trayectoria de la cadena. Experimenta con diferentes desviaciones estándar de propuesta.

  5. Construye una demo completa de generación de texto: dado un vocabulario de 10 palabras con logits, genera secuencias de 20 tokens usando (a) voraz, (b) temperatura=0.7, (c) top-k=3, (d) top-p=0.9. Compara la diversidad de las salidas a lo largo de 5 corridas.

Términos Clave

Término Lo que la gente dice Lo que realmente significa
Muestreo "Extraer valores aleatorios" Generar valores de acuerdo con una distribución de probabilidad. El mecanismo detrás de toda IA generativa
Distribución uniforme "Todos igualmente probables" Todo valor en [a, b] tiene densidad de probabilidad igual 1/(b-a). El punto de partida para todos los métodos de muestreo
CDF inversa "Transformada de probabilidad" F_inverse(U) convierte una muestra uniforme en una muestra de cualquier distribución con CDF conocida. Exacta y eficiente
Muestreo por rechazo "Proponer y aceptar/rechazar" Generar de una propuesta simple, aceptar con probabilidad proporcional a la razón objetivo/propuesta. Exacto pero desperdicia muestras
Muestreo por importancia "Reponderar muestras" Estimar esperanzas bajo p(x) usando muestras de q(x) ponderando cada muestra por p(x)/q(x). Central a PPO en RL
Monte Carlo "Promediar muestras aleatorias" Aproximar integrales como promedios de muestras. Error O(1/sqrt(N)) sin importar la dimensión
MCMC "Caminata aleatoria que converge" Construir una cadena de Markov cuya distribución estacionaria es el objetivo. Metropolis-Hastings es el algoritmo fundamental
Metropolis-Hastings "Aceptar cuesta arriba, a veces cuesta abajo" Proponer movimientos, aceptar con base en la razón de densidades. El balance detallado garantiza la convergencia a la distribución objetivo
Muestreo de Gibbs "Una variable a la vez" Actualizar cada variable a partir de su distribución condicional manteniendo las demás fijas. Tasa de aceptación del 100%
Temperatura "Perilla de confianza" Divide los logits por T antes del softmax. T<1 afila (más confiado), T>1 aplana (más diverso)
Muestreo top-k "Conservar los k mejores" Poner en cero todos menos los k tokens de mayor probabilidad, renormalizar, muestrear. Tamaño fijo del conjunto de candidatos
Muestreo por núcleo (top-p) "Conservar los probables" Conservar el menor conjunto de tokens cuya probabilidad acumulada supera p. Tamaño adaptativo del conjunto de candidatos
Truco de la reparametrización "Mover la aleatoriedad afuera" Escribir z = mu + sigma * epsilon donde epsilon ~ N(0,1). Hace diferenciable el muestreo. Esencial para el entrenamiento de VAEs
Gumbel-Softmax "Muestreo categórico suave" Aproximación diferenciable del muestreo categórico usando ruido de Gumbel + softmax con temperatura
Muestreo estratificado "Cobertura forzada" Dividir el espacio muestral en estratos, muestrear de cada uno. Siempre tiene menor varianza que el Monte Carlo ingenuo
Burn-in "Periodo de calentamiento" Muestras iniciales del MCMC descartadas antes de que la cadena alcance su distribución estacionaria
Balance detallado "Condición de reversibilidad" p(x) * T(x->y) = p(y) * T(y->x). Condición suficiente para que p sea la distribución estacionaria de una cadena de Markov
Muestreo por difusión "Eliminación iterativa de ruido" Generar datos comenzando desde el ruido y aplicando pasos aprendidos de eliminación de ruido. Cada paso es una operación de muestreo condicional

Lectura Adicional

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