¿Por qué necesitamos probabilidad en deep learning?

El machine learning es, en esencia, la matemática de la incertidumbre. Un modelo de lenguaje no produce una sola palabra siguiente — produce una distribución de probabilidad sobre cada palabra de su vocabulario, y muestreamos de esa distribución para generar texto. Un clasificador no declara "esto es un gato" — asigna probabilidades como $[0.92, 0.05, 0.03]$ entre las clases, y elegimos la más probable. Incluso durante el entrenamiento, la función de pérdida que guía el aprendizaje ( cross-entropy ) se define completamente en términos de probabilidad: mide qué tan bien la distribución predicha por el modelo se ajusta a la verdadera.

Presiona Run en el código a continuación para ver cómo luce la distribución de salida de un clasificador:

import json, js

categories = ["cat", "dog", "bird"]
probabilities = [0.92, 0.05, 0.03]

plot_data = [
    {
        "title": "Classifier output: a probability distribution over classes",
        "x_label": "Class",
        "y_label": "Probability",
        "x_data": categories,
        "lines": [
            {"label": "P(class)", "data": probabilities, "color": "#6366f1", "type": "bar"}
        ]
    }
]
js.window.py_plot_data = json.dumps(plot_data)
👋 ¡Hola! Este track cubre los fundamentos, pero no es necesario saberlo de memoria para usar un modelo, ni siquiera para entrenar uno. Puedes leerlo, absorber lo que te resulte natural, y luego pasar a los tracks divertidos (por ejemplo, construir tu propio GPT). Si en algún momento necesitas repasar algún concepto, ¡este track siempre estará aquí para ti!

Las distribuciones de probabilidad no son solo salidas — están entretejidas en la estructura de cómo se construyen y optimizan los modelos. La La función softmax convierte logits en bruto en una distribución de probabilidad válida. Los esquemas de inicialización de pesos (Glorot, He) muestrean valores iniciales de parámetros de distribuciones cuidadosamente elegidas. Los modelos de difusión generan imágenes añadiendo y eliminando sistemáticamente ruido gaussiano. Y la cuantización NF4 de QLoRA usa la CDF inversa de la distribución normal para colocar niveles de cuantización donde los pesos del modelo son más densos. No te preocupes si estos términos no te resultan familiares — cada uno se cubre en su propio track. Lo importante es que la probabilidad está en todas partes, y este artículo te da los fundamentos.

Entonces, ¿qué es exactamente una distribución de probabilidad? ¿Cómo la describimos matemáticamente? ¿Y cómo calculamos cantidades útiles a partir de ella — como la probabilidad de que un valor caiga en algún rango, o el valor por debajo del cual se encuentra cierta fracción de los resultados? Este artículo construye esos fundamentos. Comenzaremos con las dos distribuciones más simples e importantes — la uniforme y la normal — luego desarrollaremos las herramientas matemáticas fundamentales (la función de densidad, la CDF y la función cuantil) que nos permiten trabajar con cualquier distribución, y cerraremos con la mecánica del muestreo.

La distribución uniforme

La distribución de probabilidad más simple es la distribución uniforme : todos los resultados son igualmente probables. Es la distribución de "no tengo conocimiento previo, así que todas las posibilidades son igualmente plausibles".

Una distribución uniforme puede ser discreta o continua . Lanzar un dado justo es uniforme discreta: cada una de las seis caras tiene probabilidad $P(X = k) = \frac{1}{6}$. Elegir una carta al azar de un mazo barajado es otro ejemplo — cada una de las 52 cartas tiene probabilidad $\frac{1}{52}$. La versión continua distribuye densidad de probabilidad igual sobre un intervalo real $[a, b]$: $f(x) = \frac{1}{b - a}$ dentro del intervalo, $0$ fuera. Si eliges un número real aleatorio entre 0 y 1 sin sesgo (es decir, sin preferencia por ninguna región sobre otra), estás muestreando de la uniforme estándar $U(0, 1)$, donde $f(x) = 1$ en todo el intervalo.

$$f(x) = \begin{cases} \frac{1}{b - a} & \text{if } a \leq x \leq b \\ 0 & \text{otherwise} \end{cases}$$

La media es $\frac{a + b}{2}$ (el punto medio), y la varianza es $\frac{(b - a)^2}{12}$ — intervalos más amplios significan más dispersión.

💡 ¿De dónde sale el 12? Tomemos la uniforme estándar $U(0, 1)$: la media es $\frac{1}{2}$, así que $E[X^2] = \int_0^1 x^2 \cdot 1 \, dx = \frac{1}{3}$, y $\text{Var}(X) = E[X^2] - (E[X])^2 = \frac{1}{3} - \frac{1}{4} = \frac{1}{12}$. Para una $U(a, b)$ general, el ancho del intervalo escala la varianza por $(b - a)^2$, dando $\frac{(b-a)^2}{12}$.

¿Por qué esto importa para el deep learning? Varias razones:

  • Base de inicialización de pesos: antes de Glorot y He, los pesos a menudo se extraían de distribuciones uniformes simples. Incluso los esquemas modernos (como kaiming_uniform_ de PyTorch) usan distribuciones uniformes con límites cuidadosamente elegidos.
  • Generación de números aleatorios: cada llamada a torch.rand() o np.random.rand() extrae de $U(0, 1)$. Esta es la base — como veremos en la sección de Muestreo, puedes transformar números aleatorios uniformes en muestras de cualquier distribución.
  • Dropout: durante el entrenamiento, las máscaras de dropout se generan comparando muestras de $U(0, 1)$ con la tasa de dropout $p$. Si la muestra excede $p$, la neurona permanece; de lo contrario se pone a cero.
  • Mezcla de datos y augmentation: posiciones aleatorias de recorte, rangos de variación de color y mezcla de lotes dependen de variables aleatorias uniformes.

Ejecuta el código a continuación para ver cómo luce una distribución uniforme. La curva se llama curva de densidad — muestra qué tan "densamente empaquetada" está la probabilidad en cada punto. Más alta significa que los valores tienden a concentrarse ahí, más plana significa que la probabilidad está dispersa. Para una distribución uniforme la curva de densidad es plana, porque ningún valor es favorecido. Graficamos dos: la estándar $U(0, 1)$ y la más amplia $U(-2, 3)$. Observa cómo la más amplia tiene una curva más baja — la probabilidad se distribuye sobre un intervalo más grande, pero el área total bajo cada curva es exactamente 1.

import math, json, js

def uniform_pdf(x, a, b):
    if a <= x <= b:
        return 1.0 / (b - a)
    return 0.0

x_vals = [i * 0.05 for i in range(-60, 81)]  # -3.0 to 4.0

# U(0, 1)
pdf_01 = [uniform_pdf(x, 0, 1) for x in x_vals]

# U(-2, 3)
pdf_neg23 = [uniform_pdf(x, -2, 3) for x in x_vals]

plot_data = [
    {
        "title": "Uniform Density",
        "x_label": "x",
        "y_label": "f(x)",
        "x_data": x_vals,
        "lines": [
            {"label": "U(0, 1) density", "data": pdf_01, "color": "#3b82f6"},
            {"label": "U(-2, 3) density", "data": pdf_neg23, "color": "#f59e0b"}
        ]
    }
]
js.window.py_plot_data = json.dumps(plot_data)
📌 La distribución uniforme tiene entropía máxima (una medida de incertidumbre — cuanto mayor la entropía, menos predecible el resultado; ver el artículo de entropía) entre todas las distribuciones en un intervalo acotado. En términos de teoría de la información, es la distribución que hace menos suposiciones sobre dónde caerán los valores — exactamente por eso es la opción predeterminada cuando no tienes conocimiento previo.

La distribución normal (gaussiana)

La distribución normal es la distribución más importante en estadística y deep learning. Su curva en forma de campana aparece en todas partes: en la distribución de errores de medición, en los valores iniciales de los pesos de redes neuronales, en el ruido añadido por los modelos de difusión, y en la distribución empírica de los pesos de modelos preentrenados (razón por la cual la cuantización NF4 funciona tan bien). Comprender su fórmula es esencial porque es el bloque fundamental de tantas técnicas que encontrarás.

La distribución normal está parametrizada por dos valores: la media $\mu$ y la desviación estándar $\sigma$. Su PDF es:

$$f(x) = \frac{1}{\sigma\sqrt{2\pi}} \, e^{\displaystyle -\frac{(x - \mu)^2}{2\sigma^2}} = \frac{1}{\sigma\sqrt{2\pi}} \exp\!\left(-\frac{(x - \mu)^2}{2\sigma^2}\right)$$

Ambas notaciones significan lo mismo: $e^x = \exp(x)$, donde $e \approx 2.718$ es el número de Euler. Verás ambas en libros y código — $e^{(\cdot)}$ es más visual, $\exp(\cdot)$ evita superíndices diminutos cuando el exponente es largo. La fórmula parece intimidante, pero cada parte tiene su propósito. ¡Desglosémosla!

$\mu$ (mu) — la media. Este es el centro de la campana: el valor donde se ubica el pico. Desplazar $\mu$ a la izquierda o derecha desliza toda la distribución a lo largo del eje $x$ sin cambiar su forma. En deep learning, cuando decimos que los pesos se inicializan desde $\mathcal{N}(0, \sigma)$, el $\mu = 0$ significa que los pesos iniciales están centrados alrededor de cero.

$\sigma$ (sigma) — la desviación estándar. Esto controla el ancho de la campana. Un $\sigma$ pequeño significa que los valores se agrupan estrechamente alrededor de la media (una campana alta y estrecha), mientras que un $\sigma$ grande significa que se dispersan ampliamente (una campana baja y ancha). La varianza $\sigma^2$ aparece en la fórmula como la unidad natural de dispersión.

$\frac{1}{\sigma\sqrt{2\pi}}$ — la constante de normalización. Este factor asegura que el área total bajo la curva sea exactamente 1 (lo que la convierte en una PDF válida). Sin él, cambiar $\sigma$ cambiaría el área total. Cuando $\sigma$ es pequeño, la campana es estrecha pero aún debe tener área 1, así que este factor la hace alta. Cuando $\sigma$ es grande, la campana es ancha, así que este factor la hace baja. Es puramente un término contable que normaliza la probabilidad total a 1.

$-\frac{(x - \mu)^2}{2\sigma^2}$ — el exponente. Este es el corazón de la fórmula. Es una distancia al cuadrado desde la media, escalada por la varianza, con un signo negativo. $(x - \mu)^2$ mide qué tan lejos está $x$ del centro — siempre es no negativo y simétrico (estar 5 unidades por encima de la media se trata igual que 5 unidades por debajo). Dividir por $2\sigma^2$ normaliza esta distancia por la dispersión: una desviación de 1 en una distribución con $\sigma = 0.5$ es significativa (exponente = $-2$), pero en una distribución con $\sigma = 10$ es despreciable (exponente = $-0.005$). El signo negativo significa que los puntos lejanos de $\mu$ obtienen un exponente muy negativo, lo que lleva a $\exp(\cdot)$ hacia cero.

Ahora tracemos el comportamiento en los límites:

  • En $x = \mu$: el exponente es $-\frac{(\mu - \mu)^2}{2\sigma^2} = 0$, así que $\exp(0) = 1$, y $f(\mu) = \frac{1}{\sigma\sqrt{2\pi}}$. Este es el pico de la campana. Observa que la altura del pico es inversamente proporcional a $\sigma$ — una distribución más concentrada tiene un pico más alto.
  • Cuando $|x - \mu| \to \infty$: el exponente tiende a $-\infty$, así que $f(x) \to 0$. Las colas de la campana decaen hacia cero, pero nunca llegan a alcanzarlo — la distribución normal tiene soporte infinito (está definida sobre todos los números reales), aunque los valores lejanos de la media son astronómicamente improbables.
  • Cuando $\sigma \to 0$: la distribución se vuelve infinitamente alta e infinitamente estrecha, concentrando toda la probabilidad en un solo punto $\mu$. En el límite, esto se aproxima a una función delta de Dirac — un "pico" de altura infinita pero ancho cero que aún tiene área 1.
  • Cuando $\sigma \to \infty$: la distribución se aplana hacia cero en todas partes. La campana se vuelve tan ancha que la densidad en cualquier punto dado es despreciable, aproximándose a una distribución uniforme sobre toda la recta real.

Una regla práctica útil es la regla 68-95-99.7 : aproximadamente el 68% de los valores extraídos de una distribución normal caen dentro de $1\sigma$ de la media, el 95% dentro de $2\sigma$, y el 99.7% dentro de $3\sigma$. Esto te dice que valores a más de $3\sigma$ de la media son extremadamente raros (aproximadamente 3 de cada 1,000).

La distribución normal estándar es el caso especial con $\mu = 0$ y $\sigma = 1$, escrita $\mathcal{N}(0, 1)$. Sirve como la distribución de referencia — cualquier distribución normal $\mathcal{N}(\mu, \sigma^2)$ puede convertirse a ella mediante la sustitución $z = \frac{x - \mu}{\sigma}$, un proceso llamado estandarización .

¿Por qué esta distribución es tan importante para el deep learning?

  • Inicialización de pesos: esquemas como Glorot (Xavier) y He/Kaiming muestrean pesos iniciales de distribuciones normales con $\sigma$ cuidadosamente elegidos para mantener un flujo de gradientes estable a través de redes profundas.
  • Distribuciones de pesos preentrenados: empíricamente, los pesos de redes neuronales entrenadas siguen distribuciones aproximadamente normales. Esta observación es lo que hace funcionar la cuantización NF4 — colocar niveles de cuantización en los cuantiles de una distribución normal coincide con donde realmente están los pesos.
  • Modelos de difusión: el proceso directo en la generación de imágenes basada en difusión añade progresivamente ruido gaussiano a una imagen hasta que se convierte en ruido puro de $\mathcal{N}(0, I)$. El proceso inverso aprende a eliminar el ruido paso a paso.
  • El teorema del límite central: la suma de muchas variables aleatorias independientes tiende hacia una distribución normal, independientemente de las distribuciones originales. El deep learning está lleno de sumas — sumas ponderadas de entradas, promedios sobre lotes, gradientes acumulados — por lo que las distribuciones normales surgen naturalmente.

El gráfico a continuación superpone tres PDFs normales para ilustrar cómo $\mu$ y $\sigma$ afectan la forma. $\mathcal{N}(0, 1)$ es la campana estándar. $\mathcal{N}(0, 0.5)$ es más alta y estrecha ($\sigma$ más pequeño concentra la probabilidad). $\mathcal{N}(2, 1)$ tiene la misma forma que la normal estándar pero está desplazada a la derecha ($\mu$ diferente).

import math, json, js

def norm_pdf(x, mu=0, sigma=1):
    coeff = 1.0 / (sigma * math.sqrt(2 * math.pi))
    exponent = -((x - mu) ** 2) / (2 * sigma ** 2)
    return coeff * math.exp(exponent)

x_vals = [i * 0.05 for i in range(-140, 141)]  # -7.0 to 7.0

mu_values = [-3, -2, -1, 0, 1, 2, 3]
sigma_values = [0.3, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0]
default_mu = 3    # index of mu=0
default_sig = 3   # index of sigma=1.0
n_mu = len(mu_values)
n_sig = len(sigma_values)

# Trace 0: reference N(0,1), always visible
ref_y = [norm_pdf(x, 0, 1) for x in x_vals]
traces = [{
    "x": x_vals, "y": ref_y, "mode": "lines",
    "name": "N(0, 1) reference",
    "line": {"color": "#f59e0b", "dash": "dash", "width": 2},
    "visible": True
}]

# Traces 1..n_mu*n_sig: one per (mu, sigma) pair
for mi, mu in enumerate(mu_values):
    for si, sig in enumerate(sigma_values):
        y = [norm_pdf(x, mu, sig) for x in x_vals]
        traces.append({
            "x": x_vals, "y": y, "mode": "lines",
            "name": f"N({mu}, {sig})",
            "line": {"color": "#6366f1", "width": 2.5},
            "visible": (mi == default_mu and si == default_sig)
        })

def make_vis(active_mi, active_si):
    vis = [True]  # reference always visible
    for mi in range(n_mu):
        for si in range(n_sig):
            vis.append(mi == active_mi and si == active_si)
    return vis

# Sigma slider steps
sig_steps = []
for si, sig in enumerate(sigma_values):
    sig_steps.append({
        "label": f"{sig:.2g}",
        "method": "update",
        "args": [
            {"visible": make_vis(default_mu, si)},
            {"title": f"N({mu_values[default_mu]}, {sig})"}
        ]
    })

# Mu dropdown buttons
mu_buttons = []
for mi, mu in enumerate(mu_values):
    mu_buttons.append({
        "label": f"\u03bc = {mu}",
        "method": "update",
        "args": [
            {"visible": make_vis(mi, default_sig)},
            {"title": f"N({mu}, {sigma_values[default_sig]})"}
        ]
    })

layout = {
    "title": f"N({mu_values[default_mu]}, {sigma_values[default_sig]})",
    "xaxis": {"title": "x", "range": [-7, 7]},
    "yaxis": {"title": "f(x)", "range": [0, 1.4]},
    "sliders": [{
        "active": default_sig,
        "currentvalue": {"prefix": "\u03c3 = "},
        "pad": {"t": 80},
        "steps": sig_steps
    }],
    "updatemenus": [{
        "type": "dropdown",
        "active": default_mu,
        "buttons": mu_buttons,
        "x": 0.0, "xanchor": "left",
        "y": 1.15, "yanchor": "top",
        "showactive": True
    }],
    "legend": {"x": 0.8, "y": 1.0},
    "margin": {"t": 100}
}

js.window.py_plotly_data = json.dumps({"data": traces, "layout": layout})
💡 Observa que $\mathcal{N}(0, 0.5)$ alcanza un pico por encima de $f(x) = 0.7$ — muy por encima de 1 si $\sigma$ fuera aún más pequeño. Este es un ejemplo concreto de una PDF que toma valores mayores que 1 y es perfectamente válida. La densidad es alta porque la probabilidad está concentrada en una banda muy estrecha, pero el área total sigue siendo exactamente 1.

La función de densidad de probabilidad (PDF)

Antes de formalizar la densidad, necesitamos una distinción clave. Una distribución discreta asigna probabilidades específicas a resultados individuales — $P(X = 3) = \frac{1}{6}$ para un dado justo, o $P(\text{next token} = \text{"the"}) = 0.12$ para un modelo de lenguaje. Esta asignación se llama función de masa de probabilidad (PMF) . Pero las distribuciones que acabamos de presentar (uniforme y normal) son continuas — sus resultados abarcan un rango incontable de valores reales.

Aquí está el salto conceptual: para una distribución continua, la probabilidad de que $X$ sea igual a cualquier valor exacto es cero. No aproximadamente cero — exactamente cero. Hay una cantidad incontable de números reales posibles, y si cada uno tuviera incluso una pequeña probabilidad positiva, el total excedería 1. Así que no podemos usar una PMF. En su lugar, necesitamos una función que describa dónde los valores son densos sin asignar probabilidad a puntos individuales. Esa función es la función de densidad de probabilidad.

Una función de densidad de probabilidad (PDF) , escrita $f(x)$, describe la probabilidad relativa de que una variable aleatoria continua tome un valor dado. La palabra clave es densidad — así como la densidad de masa indica cuánta masa está empaquetada en una unidad de volumen, la densidad de probabilidad indica cuánta probabilidad está empaquetada en una unidad de $x$. Una densidad alta en un punto significa que es probable que los valores se agrupen cerca de él. Una densidad baja significa que es poco probable encontrar valores allí.

Pero aquí está la idea clave que confunde a muchos estudiantes: $f(x)$ no es una probabilidad. No puedes decir "la probabilidad de $X = 3$ es $f(3)$." Las probabilidades provienen de las áreas bajo la curva . Para obtener una probabilidad real, debes integrar la PDF sobre un intervalo:

$$P(a \leq X \leq b) = \int_a^b f(x) \, dx$$

Desglosemos esto.

$P(a \leq X \leq b)$ es la probabilidad de que la variable aleatoria $X$ tome un valor entre $a$ y $b$. Esto sí es una probabilidad propiamente dicha — un número entre 0 y 1.

$\int_a^b f(x) \, dx$ es el área bajo la curva $f(x)$ entre $a$ y $b$. Geométricamente, estás sombreando la región entre la curva de la PDF y el eje $x$ desde $a$ hasta $b$, y calculando su área. Cuanto más ancho sea el intervalo o más alta la PDF en esa región, mayor será la probabilidad.

Ahora tracemos el comportamiento en los límites. A medida que el intervalo se reduce a un solo punto ($a = b$), el ancho de la región tiende a cero, y por lo tanto el área tiende a cero: $\int_a^a f(x) \, dx = 0$. Esto confirma lo que dijimos antes — $P(X = \text{exactamente } c) = 0$ para cualquier distribución continua. No importa cuán alta sea la PDF en un punto, una región de ancho cero tiene área cero. En el otro extremo, integrar sobre toda la recta real debe dar 1, porque la probabilidad total de que la variable aleatoria tome algún valor es segura:

$$\int_{-\infty}^{\infty} f(x) \, dx = 1$$

Esto nos lleva a las dos propiedades que toda PDF válida debe satisfacer:

  • $f(x) \geq 0$ para todo $x$ — la densidad no puede ser negativa. Una probabilidad negativa no tiene sentido.
  • $\int_{-\infty}^{\infty} f(x) \, dx = 1$ — la probabilidad total en todos los valores posibles debe ser igual a 1. Algo debe ocurrir.
📌 $f(x)$ PUEDE ser mayor que 1. Esto sorprende a muchas personas, pero recuerda: $f(x)$ es una densidad, no una probabilidad. Una distribución muy estrecha y puntiaguda puede tener $f(x) > 1$ cerca de su pico, siempre y cuando el área total bajo la curva siga siendo exactamente 1. Por ejemplo, una distribución uniforme en $[0, 0.5]$ tiene $f(x) = 2$ en todo su soporte — una densidad de 2, pero el área total es $2 \times 0.5 = 1$. Perfectamente válido.

El gráfico a continuación muestra la PDF de la distribución normal estándar (la campana de Gauss). La región sombreada entre $x = -1$ y $x = 1$ representa $P(-1 \leq X \leq 1)$ — aproximadamente el 68% del área total. Observa cómo la densidad alcanza su máximo en $x = 0$ y decae simétricamente en ambas direcciones.

import math, json, js

def norm_pdf(x, mu=0, sigma=1):
    """PDF of the normal distribution."""
    coeff = 1.0 / (sigma * math.sqrt(2 * math.pi))
    exponent = -((x - mu) ** 2) / (2 * sigma ** 2)
    return coeff * math.exp(exponent)

# Generate x values from -4 to 4
x_vals = [i * 0.05 for i in range(-80, 81)]  # -4.0 to 4.0 in steps of 0.05

# Full PDF curve
y_full = [norm_pdf(x) for x in x_vals]

# Shaded region: PDF between -1 and 1, zero elsewhere
y_shaded = [norm_pdf(x) if -1 <= x <= 1 else 0 for x in x_vals]

plot_data = [
    {
        "title": "Standard Normal PDF with P(-1 ≤ X ≤ 1) ≈ 0.683 shaded",
        "x_label": "x",
        "y_label": "f(x)",
        "x_data": x_vals,
        "lines": [
            {"label": "N(0,1) PDF", "data": y_full, "color": "#3b82f6"},
            {"label": "P(-1 ≤ X ≤ 1)", "data": y_shaded, "color": "#93c5fd", "fill": True}
        ]
    }
]
js.window.py_plot_data = json.dumps(plot_data)

La función de distribución acumulativa (CDF)

La PDF nos dice dónde la probabilidad es densa, pero a menudo necesitamos responder una pregunta diferente: ¿cuál es la probabilidad de que $X$ sea menor o igual a algún valor $x$? Por ejemplo, ¿qué fracción de los pesos de una red neuronal caen por debajo de 0.5? ¿Cuál es la probabilidad de que una muestra aleatoria de $\mathcal{N}(0, 1)$ sea negativa? La función de distribución acumulativa (CDF) responde exactamente a esto.

$$F(x) = P(X \leq x) = \int_{-\infty}^{x} f(t) \, dt$$

Desglosemos esto.

$F(x)$ es la CDF evaluada en el punto $x$. Proporciona la probabilidad total acumulada desde $-\infty$ hasta $x$ — el área bajo la curva de la PDF a la izquierda de $x$.

$\int_{-\infty}^{x} f(t) \, dt$ es la integral de la PDF desde menos infinito hasta $x$. Usamos $t$ como la variable muda de integración (en lugar de $x$) para distinguir la variable sobre la que se integra del límite superior fijo $x$. A medida que $x$ aumenta, acumulamos más área, por lo que $F(x)$ crece — por eso se llama función de distribución "acumulativa".

La CDF tiene varias propiedades importantes:

  • Monótonamente no decreciente: $F(x)$ solo puede subir o mantenerse plana, nunca disminuir. Una vez que la probabilidad se ha acumulado, no puede des-acumularse.
  • $\lim_{x \to -\infty} F(x) = 0$: a medida que nos movemos infinitamente hacia la izquierda, no se ha acumulado ninguna probabilidad aún.
  • $\lim_{x \to \infty} F(x) = 1$: a medida que nos movemos infinitamente hacia la derecha, toda la probabilidad se ha acumulado.
  • $F(x) \in [0, 1]$: la CDF siempre es una probabilidad válida.

Para la distribución normal estándar $\mathcal{N}(0, 1)$, la CDF se denota tradicionalmente $\Phi(x)$:

$$\Phi(x) = \frac{1}{\sqrt{2\pi}} \int_{-\infty}^{x} e^{-t^2/2} \, dt$$

Esta integral no tiene solución en forma cerrada — no existe ninguna combinación de funciones elementales que sea igual a $\Phi(x)$. Debemos calcularla numéricamente. Afortunadamente, $\Phi(x)$ se puede expresar usando la función de error $\text{erf}(x)$, que está disponible en la mayoría de los lenguajes de programación:

$$\Phi(x) = \frac{1}{2}\left[1 + \text{erf}\!\left(\frac{x}{\sqrt{2}}\right)\right]$$

Algunos valores clave que vale la pena memorizar:

  • $\Phi(0) = 0.5$ — exactamente la mitad de la distribución se encuentra por debajo de la media. Por simetría, exactamente la mitad se encuentra por encima.
  • $\Phi(1.96) \approx 0.975$ — el 97.5% de la distribución se encuentra por debajo de $x = 1.96$. Esta es la base del famoso intervalo de confianza del 95%: el 95% central de la distribución se encuentra entre $-1.96$ y $+1.96$.
  • $\Phi(-1.96) \approx 0.025$ — solo el 2.5% de la distribución se encuentra por debajo de $x = -1.96$, la imagen especular del valor anterior.

El gráfico a continuación muestra $\Phi(x)$ — la característica curva en forma de S (similar a una sigmoide). Comienza cerca de 0 en el extremo izquierdo, pasa por 0.5 en $x = 0$, y se aproxima a 1 en el extremo derecho. La parte más empinada de la curva está en $x = 0$, donde la PDF (la derivada de la CDF) es más alta.

import math, json, js

def norm_cdf(x):
    """CDF of the standard normal distribution using math.erf."""
    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))

x_vals = [i * 0.05 for i in range(-80, 81)]  # -4.0 to 4.0
y_cdf = [norm_cdf(x) for x in x_vals]

# Horizontal reference lines at key probabilities
y_half = [0.5 for _ in x_vals]
y_025 = [0.025 for _ in x_vals]
y_975 = [0.975 for _ in x_vals]

plot_data = [
    {
        "title": "Standard Normal CDF Φ(x)",
        "x_label": "x",
        "y_label": "Φ(x)",
        "x_data": x_vals,
        "lines": [
            {"label": "Φ(x)", "data": y_cdf, "color": "#8b5cf6"},
            {"label": "Φ = 0.5 (at x = 0)", "data": y_half, "color": "#9ca3af"},
            {"label": "Φ = 0.025 (at x ≈ -1.96)", "data": y_025, "color": "#f87171"},
            {"label": "Φ = 0.975 (at x ≈ 1.96)", "data": y_975, "color": "#f87171"}
        ]
    }
]
js.window.py_plot_data = json.dumps(plot_data)

El siguiente código calcula la CDF en varios valores importantes de $x$ y los muestra como una tabla. Estos valores aparecen repetidamente en estadística y machine learning — vale la pena tener intuición sobre ellos.

import math, json, js

def norm_cdf(x):
    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))

x_values = [-3.0, -2.0, -1.96, -1.0, 0.0, 1.0, 1.96, 2.0, 3.0]

rows = []
for x in x_values:
    cdf_val = norm_cdf(x)
    interpretation = ""
    if x == 0.0:
        interpretation = "Mean: exactly half below"
    elif abs(x) == 1.0:
        interpretation = f"{'Below' if x < 0 else 'Above'} 1σ"
    elif abs(x - 1.96) < 0.01 or abs(x + 1.96) < 0.01:
        interpretation = "95% confidence boundary"
    elif abs(x) == 2.0:
        interpretation = f"{'Below' if x < 0 else 'Above'} 2σ"
    elif abs(x) == 3.0:
        interpretation = f"{'Below' if x < 0 else 'Above'} 3σ"
    rows.append([f"{x:+.2f}", f"{cdf_val:.6f}", interpretation])

js.window.py_table_data = json.dumps({
    "headers": ["x", "Φ(x)", "Interpretation"],
    "rows": rows
})

print("Key insight: Φ(x) + Φ(-x) = 1 for all x (by symmetry)")
print(f"Verify: Φ(1.96) + Φ(-1.96) = {norm_cdf(1.96):.6f} + {norm_cdf(-1.96):.6f} = {norm_cdf(1.96) + norm_cdf(-1.96):.6f}")
💡 La CDF también nos da probabilidades para rangos. Como $P(a \leq X \leq b) = \Phi(b) - \Phi(a)$, podemos verificar la regla 68-95-99.7: $\Phi(1) - \Phi(-1) \approx 0.8413 - 0.1587 = 0.6827$, o aproximadamente el 68.3% dentro de $\pm 1\sigma$. Para $\pm 2\sigma$: $\Phi(2) - \Phi(-2) \approx 0.9772 - 0.0228 = 0.9545$, o 95.4%.

La función cuantil (CDF inversa)

La CDF responde "¿qué fracción de la distribución se encuentra por debajo de $x$?" Pero a veces necesitamos la pregunta inversa: "¿en qué valor de $x$ una fracción dada $p$ de la distribución se encuentra por debajo?" Por ejemplo, ¿qué valor separa el 97.5% inferior del 2.5% superior? Esto es lo que proporciona la función cuantil (también llamada CDF inversa o función de punto porcentual ).

Para la normal estándar, la función cuantil se escribe $\Phi^{-1}(p)$. Dada una probabilidad $p \in (0, 1)$, devuelve el $z$-score donde $\Phi(z) = p$ — el punto donde exactamente una fracción $p$ de la distribución se encuentra a su izquierda.

Algunos ejemplos clave:

  • $\Phi^{-1}(0.5) = 0$ — la mediana. La mitad de la distribución se encuentra por debajo de 0, lo cual tiene sentido porque la normal estándar es simétrica respecto a 0.
  • $\Phi^{-1}(0.975) \approx 1.96$ — el percentil 97.5. Este es el valor por encima del cual solo se encuentra el 2.5% de la distribución. Es el límite derecho del intervalo de confianza del 95%.
  • $\Phi^{-1}(0.025) \approx -1.96$ — el percentil 2.5. Solo el 2.5% de la distribución se encuentra por debajo de este valor. Es el límite izquierdo del intervalo de confianza del 95%.

Ahora el comportamiento en los límites:

  • $\Phi^{-1}(0) = -\infty$: ningún valor finito captura el 0% de la distribución. Tendrías que ir infinitamente hacia la izquierda para tener cero probabilidad acumulada.
  • $\Phi^{-1}(1) = +\infty$: necesitarías incluir toda la recta real infinita para capturar el 100% de la distribución.
  • $\Phi^{-1}(0.5) = 0$: la normal estándar es simétrica respecto a 0, por lo que la mediana es igual a la media.

Esta función tiene una aplicación directa e importante en deep learning. QLoRA usa la cuantización NF4 con la función cuantil para diseñar un tipo de datos de 4 bits optimizado para pesos con distribución normal. La idea: dividir el rango de probabilidad $[0, 1]$ en $2^b = 16$ intervalos de igual probabilidad y colocar cada nivel de cuantización en el cuantil del punto medio de su intervalo:

$$q_i = \Phi^{-1}\!\left(\frac{2i + 1}{2 \cdot 2^b}\right)$$

Esto coloca niveles densamente cerca de $\mu = 0$ (donde la mayoría de los pesos se agrupan) y escasamente en las colas (donde existen pocos pesos), minimizando el error de cuantización bajo la suposición de que los pesos están aproximadamente distribuidos normalmente.

El gráfico a continuación muestra la función cuantil — una curva S inversa. Es una imagen especular de la CDF: donde la CDF mapea valores de $x$ a probabilidades, la función cuantil mapea probabilidades de vuelta a valores de $x$. Observa cómo es empinada cerca de $p = 0$ y $p = 1$ (un pequeño cambio en la probabilidad corresponde a un gran cambio en $x$ en las colas) y relativamente plana cerca de $p = 0.5$ (las probabilidades se acumulan rápidamente cerca del centro).

import math, json, js

def norm_ppf(p):
    """Approximate inverse CDF of standard normal (Abramowitz & Stegun)."""
    if p <= 0:
        return -4.0
    if p >= 1:
        return 4.0
    if p > 0.5:
        return -norm_ppf(1.0 - p)
    t = math.sqrt(-2.0 * math.log(p))
    c0, c1, c2 = 2.515517, 0.802853, 0.010328
    d1, d2, d3 = 1.432788, 0.189269, 0.001308
    return -(t - (c0 + c1*t + c2*t*t) / (1 + d1*t + d2*t*t + d3*t*t*t))

# Probabilities from 0.01 to 0.99
p_vals = [i * 0.005 for i in range(2, 199)]  # 0.01 to 0.99
z_vals = [norm_ppf(p) for p in p_vals]

plot_data = [
    {
        "title": "Standard Normal Quantile Function Φ⁻¹(p)",
        "x_label": "Probability p",
        "y_label": "z-score (Φ⁻¹(p))",
        "x_data": p_vals,
        "lines": [
            {"label": "Φ⁻¹(p)", "data": z_vals, "color": "#ef4444"}
        ]
    }
]
js.window.py_plot_data = json.dumps(plot_data)

El siguiente código calcula valores cuantiles en probabilidades clave y muestra cómo NF4 los usa para colocar sus 16 niveles de cuantización:

import math, json, js

def norm_ppf(p):
    """Approximate inverse CDF of standard normal (Abramowitz & Stegun)."""
    if p <= 0: return -4.0
    if p >= 1: return 4.0
    if p > 0.5:
        return -norm_ppf(1.0 - p)
    t = math.sqrt(-2.0 * math.log(p))
    c0, c1, c2 = 2.515517, 0.802853, 0.010328
    d1, d2, d3 = 1.432788, 0.189269, 0.001308
    return -(t - (c0 + c1*t + c2*t*t) / (1 + d1*t + d2*t*t + d3*t*t*t))

# Key quantile values
rows_key = []
for p in [0.025, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.975]:
    z = norm_ppf(p)
    rows_key.append([f"{p:.3f}", f"{z:+.4f}", f"{p*100:.1f}th percentile"])

# NF4 levels
rows_nf4 = []
for i in range(16):
    p = (2 * i + 1) / 32
    z = norm_ppf(p)
    rows_nf4.append([str(i), f"{p:.5f}", f"{z:+.4f}"])

# Combine into two tables displayed sequentially
js.window.py_table_data = json.dumps({
    "headers": ["Probability p", "Φ⁻¹(p)", "Meaning"],
    "rows": rows_key
})

print("\nNF4 quantization levels (16 levels for 4-bit):")
print(f"{'Level':>5} {'Prob p':>10} {'Φ⁻¹(p)':>10}")
print("-" * 28)
for row in rows_nf4:
    print(f"{row[0]:>5} {row[1]:>10} {row[2]:>10}")
💡 La función cuantil es el puente matemático entre "intervalos de igual probabilidad" y "dónde colocar los niveles de cuantización". Debido a que la distribución normal concentra su masa cerca de la media, los intervalos de igual probabilidad se traducen en niveles estrechamente espaciados cerca de cero y niveles ampliamente espaciados en las colas — exactamente el diseño que minimiza el error de cuantización para pesos con distribución normal.

Muestreo de distribuciones

Cada entrenamiento de red neuronal comienza muestreando: pesos iniciales de una distribución normal, máscaras de dropout de ensayos de Bernoulli uniformes, mini-lotes mezclados aleatoriamente. Los modelos de difusión muestrean ruido en cada paso. Los modelos de lenguaje muestrean tokens en cada paso. Pero, ¿qué significa realmente "muestrear", y cómo puede un computador — que solo puede generar salidas deterministas — producir números aleatorios de una distribución específica?

La base es el generador de números aleatorios uniformes . Fuentes de hardware o algorítmicas producen valores de $U(0, 1)$ — números que tienen igual probabilidad de caer en cualquier lugar entre 0 y 1. En PyTorch, esto es torch.rand() . Todo lo demás se construye sobre esta base.

Para muestrear de cualquier otra distribución, usamos el método de la transformada inversa : generamos $u \sim U(0, 1)$, luego calculamos $x = F^{-1}(u)$, donde $F^{-1}$ es la función cuantil de la distribución objetivo. El resultado $x$ sigue esa distribución objetivo exactamente.

$$u \sim U(0, 1), \quad x = F^{-1}(u) \implies x \sim F$$

¿Por qué funciona esto? La CDF $F$ mapea valores a probabilidades uniformemente distribuidas en $[0, 1]$. Invertirla mapea probabilidades uniformes de vuelta a valores distribuidos según $F$. Las regiones donde la CDF es empinada (alta densidad) consumen un amplio rango de valores $u$, por lo que producen más muestras. Las regiones donde es plana (baja densidad) consumen un rango estrecho, produciendo menos muestras.

Para la distribución normal específicamente, torch.randn() produce muestras de $\mathcal{N}(0, 1)$ usando algoritmos optimizados (típicamente la transformada de Box-Muller o el método Ziggurat, que son más eficientes que el cálculo directo de la CDF inversa). Para obtener muestras de $\mathcal{N}(\mu, \sigma^2)$:

$$x = \mu + \sigma \cdot \epsilon, \quad \epsilon \sim \mathcal{N}(0, 1)$$

Este es el truco de reparametrización — expresar una muestra de una normal arbitraria como una transformación determinista de una muestra normal estándar. Esta identidad aparentemente simple es crucial para el entrenamiento. En VAEs y modelos de difusión , necesitamos que los gradientes fluyan a través de la operación de muestreo. No puedes retropropagar a través de "extraer una muestra aleatoria", pero sí puedes retropropagar a través de la función determinista $\mu + \sigma \cdot \epsilon$ porque la aleatoriedad ($\epsilon$) es externa a los parámetros ($\mu$, $\sigma$).

El gráfico a continuación demuestra visualmente el muestreo por transformada inversa. Generamos 1000 muestras de $U(0, 1)$, las transformamos a través de la CDF inversa normal $\Phi^{-1}$, y mostramos que el histograma resultante coincide con la campana de Gauss.

import math, json, js

def norm_ppf(p):
    """Approximate inverse CDF of standard normal (Abramowitz & Stegun)."""
    if p <= 0.0001: return -3.5
    if p >= 0.9999: return 3.5
    if p > 0.5:
        return -norm_ppf(1.0 - p)
    t = math.sqrt(-2.0 * math.log(p))
    c0, c1, c2 = 2.515517, 0.802853, 0.010328
    d1, d2, d3 = 1.432788, 0.189269, 0.001308
    return -(t - (c0 + c1*t + c2*t*t) / (1 + d1*t + d2*t*t + d3*t*t*t))

def norm_pdf(x, mu=0, sigma=1):
    coeff = 1.0 / (sigma * math.sqrt(2 * math.pi))
    exponent = -((x - mu) ** 2) / (2 * sigma ** 2)
    return coeff * math.exp(exponent)

# Simple LCG random number generator for reproducibility
seed = 42
def lcg_rand():
    global seed
    seed = (1103515245 * seed + 12345) & 0x7fffffff
    return seed / 0x7fffffff

# Generate 1000 U(0,1) samples
n = 1000
uniform_samples = [lcg_rand() for _ in range(n)]

# Transform through inverse CDF
normal_samples = [norm_ppf(u) for u in uniform_samples]

# Histogram for uniform samples (20 bins from 0 to 1)
u_bins = 20
u_counts = [0] * u_bins
for u in uniform_samples:
    idx = min(int(u * u_bins), u_bins - 1)
    u_counts[idx] += 1
u_bin_centers = [(i + 0.5) / u_bins for i in range(u_bins)]
u_density = [c / (n * (1.0 / u_bins)) for c in u_counts]

# Histogram for normal samples (30 bins from -4 to 4)
n_bins = 30
n_min, n_max = -4.0, 4.0
bin_width = (n_max - n_min) / n_bins
n_counts = [0] * n_bins
for x in normal_samples:
    idx = int((x - n_min) / bin_width)
    if 0 <= idx < n_bins:
        n_counts[idx] += 1
n_bin_centers = [n_min + (i + 0.5) * bin_width for i in range(n_bins)]
n_density = [c / (n * bin_width) for c in n_counts]

# True normal PDF for overlay
x_smooth = [i * 0.05 for i in range(-80, 81)]
y_true = [norm_pdf(x) for x in x_smooth]

plot_data = [
    {
        "title": "Step 1: Uniform samples U(0, 1)",
        "x_label": "u",
        "y_label": "Density",
        "x_data": u_bin_centers,
        "lines": [
            {"label": "Uniform samples", "data": u_density, "color": "#6366f1", "type": "bar"}
        ]
    },
    {
        "title": "Step 2: After inverse CDF transform — matches N(0, 1)",
        "x_label": "x",
        "y_label": "Density",
        "x_data": n_bin_centers,
        "lines": [
            {"label": "Transformed samples", "data": n_density, "color": "#6366f1", "type": "bar"},
            {"label": "True N(0,1) PDF", "data": [norm_pdf(x) for x in n_bin_centers], "color": "#ef4444"}
        ]
    }
]
js.window.py_plot_data = json.dumps(plot_data)
💡 La inicialización de pesos conecta todo esto. $\texttt{torch.nn.init.kaiming\_normal\_}$ muestrea de $\mathcal{N}(0, \sigma^2)$ donde $\sigma = \sqrt{2/n_{\text{in}}}$. Internamente: genera $\epsilon \sim \mathcal{N}(0,1)$, multiplica por $\sqrt{2/n_{\text{in}}}$. Los pesos iniciales se escalan para que la varianza se preserve a través de las capas — demasiado pequeños y las señales se desvanecen, demasiado grandes y explotan. La elección entre normal y uniforme importa menos que acertar la escala.

Conectando todo

La PDF, la CDF y la función cuantil forman una cadena, cada una derivada de la anterior:

  • PDF $f(x)$: "¿Qué tan densa es la probabilidad en este punto?" Este es el punto de partida. Te indica dónde es probable que los valores se agrupen, pero sus valores son densidades, no probabilidades.
  • CDF $F(x) = \int_{-\infty}^{x} f(t) \, dt$: "¿Cuánta probabilidad total se encuentra a la izquierda de este punto?" Se obtiene integrando la PDF. Convierte información de densidad en probabilidades acumulativas.
  • Cuantil $F^{-1}(p)$: "¿En qué punto se ha acumulado esta cantidad de probabilidad?" Se obtiene invirtiendo la CDF. Convierte probabilidades de vuelta en valores en la escala original.

Estas tres funciones son diferentes vistas de la misma distribución subyacente. La CDF es la integral de la PDF. La PDF es la derivada de la CDF. La función cuantil es la inversa funcional de la CDF. Conocer cualquiera de ellas determina completamente las otras dos.

El gráfico a continuación muestra las tres funciones para la distribución normal estándar en un solo gráfico. La PDF azul alcanza su pico en $x = 0$ con densidad $\approx 0.399$. La CDF morada pasa por 0.5 en $x = 0$ (la mitad de la probabilidad acumulada). Y la función cuantil roja, graficada en un eje conceptual diferente, muestra el mapeo inverso.

import math, json, js

def norm_pdf(x, mu=0, sigma=1):
    coeff = 1.0 / (sigma * math.sqrt(2 * math.pi))
    exponent = -((x - mu) ** 2) / (2 * sigma ** 2)
    return coeff * math.exp(exponent)

def norm_cdf(x):
    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))

def norm_ppf(p):
    if p <= 0: return -4.0
    if p >= 1: return 4.0
    if p > 0.5:
        return -norm_ppf(1.0 - p)
    t = math.sqrt(-2.0 * math.log(p))
    c0, c1, c2 = 2.515517, 0.802853, 0.010328
    d1, d2, d3 = 1.432788, 0.189269, 0.001308
    return -(t - (c0 + c1*t + c2*t*t) / (1 + d1*t + d2*t*t + d3*t*t*t))

x_vals = [i * 0.05 for i in range(-80, 81)]  # -4.0 to 4.0

y_pdf = [norm_pdf(x) for x in x_vals]
y_cdf = [norm_cdf(x) for x in x_vals]

# For the quantile function, we plot it on the same x-axis
# by treating x as a probability input (only valid for 0 < x < 1 region)
# Instead, we plot the inverse: for each x, show ppf(cdf(x)) = x (identity check)
# Better: show all three as separate sub-plots

# We use two charts: one for PDF + CDF (same x-axis), one for quantile function

plot_data = [
    {
        "title": "PDF and CDF of the Standard Normal Distribution",
        "x_label": "x",
        "y_label": "Value",
        "x_data": x_vals,
        "lines": [
            {"label": "PDF f(x)", "data": y_pdf, "color": "#3b82f6"},
            {"label": "CDF Φ(x)", "data": y_cdf, "color": "#8b5cf6"}
        ]
    },
    {
        "title": "Quantile Function Φ⁻¹(p)",
        "x_label": "Probability p",
        "y_label": "z-score",
        "x_data": [i * 0.005 for i in range(2, 199)],
        "lines": [
            {"label": "Φ⁻¹(p)", "data": [norm_ppf(i * 0.005) for i in range(2, 199)], "color": "#ef4444"}
        ]
    }
]
js.window.py_plot_data = json.dumps(plot_data)

Ejecuta el código a continuación para ver la correspondencia entre las tres funciones en puntos clave, haciendo concreta la cadena de relaciones:

import math, json, js

def norm_pdf(x, mu=0, sigma=1):
    coeff = 1.0 / (sigma * math.sqrt(2 * math.pi))
    exponent = -((x - mu) ** 2) / (2 * sigma ** 2)
    return coeff * math.exp(exponent)

def norm_cdf(x):
    return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))

def norm_ppf(p):
    if p <= 0: return -4.0
    if p >= 1: return 4.0
    if p > 0.5:
        return -norm_ppf(1.0 - p)
    t = math.sqrt(-2.0 * math.log(p))
    c0, c1, c2 = 2.515517, 0.802853, 0.010328
    d1, d2, d3 = 1.432788, 0.189269, 0.001308
    return -(t - (c0 + c1*t + c2*t*t) / (1 + d1*t + d2*t*t + d3*t*t*t))

x_values = [-3.0, -1.96, -1.0, 0.0, 1.0, 1.96, 3.0]
rows = []
for x in x_values:
    f_x = norm_pdf(x)
    phi_x = norm_cdf(x)
    # Verify round-trip: ppf(cdf(x)) should equal x
    roundtrip = norm_ppf(phi_x)
    rows.append([
        f"{x:+.2f}",
        f"{f_x:.4f}",
        f"{phi_x:.4f}",
        f"{roundtrip:+.4f}"
    ])

js.window.py_table_data = json.dumps({
    "headers": ["x", "PDF f(x)", "CDF Φ(x)", "Φ⁻¹(Φ(x)) ≈ x"],
    "rows": rows
})

print("The last column verifies the round-trip: applying Φ⁻¹ to Φ(x) recovers x.")
print("Small discrepancies are due to the rational approximation used for Φ⁻¹.")
💡 La propiedad de ida y vuelta $\Phi^{-1}(\Phi(x)) = x$ es lo que hace que estas funciones sean inversas. En la práctica, la aproximación racional para $\Phi^{-1}$ introduce errores diminutos (del orden de $10^{-4}$), pero para aplicaciones como la cuantización NF4 estos errores son despreciables — los niveles de cuantización no necesitan ser exactos con precisión de milésimas.

Quiz

Pon a prueba tu comprensión de las distribuciones de probabilidad, PDFs, CDFs y la distribución normal.

Si una función de densidad de probabilidad tiene f(x) = 2.5 en algún punto x, ¿es una PDF válida?

¿Cuál es Φ(0) para la distribución normal estándar N(0, 1)?

¿A qué es aproximadamente igual Φ⁻¹(0.975) para la normal estándar?

¿Por qué QLoRA usa la función cuantil Φ⁻¹ para calcular los niveles de cuantización NF4?

Una distribución uniforme U(2, 8) tiene f(x) = 1/6 para x en [2, 8]. ¿Cuál es P(3 ≤ X ≤ 5)?

El método de la transformada inversa genera u ~ U(0, 1) y luego calcula x = F⁻¹(u). ¿Por qué las regiones de alta densidad en la distribución objetivo reciben más muestras?