Phase 01 - Lesson 07
Teorema de Bayes
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
La probabilidad trata de lo que esperas. El teorema de Bayes trata de lo que aprendes.
Tipo: Build Lenguaje: Python Requisitos previos: Fase 1, Lección 06 (Fundamentos de Probabilidad) Tiempo: ~75 minutos
Objetivos de Aprendizaje
- Aplicar el teorema de Bayes para calcular probabilidades a posteriori a partir de priors, verosimilitudes y evidencia
- Construir un clasificador de texto Naive Bayes desde cero con suavizado de Laplace y cálculo en espacio logarítmico
- Comparar la estimación MLE y MAP y explicar como MAP corresponde a la regularización L2
- Implementar la actualización bayesiana secuencial usando priors conjugados Beta-Binomial para pruebas A/B
El Problema
Una prueba médica es 99% precisa. Das positivo. ¿Cuáles son las probabilidades de que realmente tengas la enfermedad?
La mayoría de la gente dice 99%. La respuesta real depende de cuan rara es la enfermedad. Si 1 de cada 10.000 personas la tiene, un resultado positivo solo te da alrededor de un 1% de probabilidad de estar enfermo. El otro 99% de los resultados positivos son falsas alarmas de personas sanas.
Esto no es una pregunta con trampa. Es el teorema de Bayes. Todo filtro de spam, todo diagnóstico médico, todo modelo de machine learning que cuantifica la incertidumbre usa exactamente este razonamiento. Empiezas con una creencia. Ves evidencia. Actualizas.
Si construyes sistemas de ML sin entender esto, malinterpretarás las salidas de los modelos, fijarás umbrales malos y entregarás predicciones demasiado confiadas.
El Concepto
De la probabilidad conjunta a Bayes
Ya sabes de la Lección 06 que la probabilidad condicional es:
P(A|B) = P(A y B) / P(B)
Y simétricamente:
P(B|A) = P(A y B) / P(A)
Ambas expresiones comparten el mismo numerador: P(A y B). Iguálalas y reorganiza:
P(A y B) = P(A|B) * P(B) = P(B|A) * P(A)
Por lo tanto:
P(A|B) = P(B|A) * P(A) / P(B)
Ese es el teorema de Bayes. Cuatro cantidades, una ecuación.
Las cuatro partes
| Parte | Nombre | Lo que significa |
|---|---|---|
| P(A|B) | Posterior | Tu creencia actualizada sobre A después de ver la evidencia B |
| P(B|A) | Verosimilitud | Cuan probable es la evidencia B si A es verdadero |
| P(A) | Prior | Tu creencia sobre A antes de ver cualquier evidencia |
| P(B) | Evidencia | Probabilidad total de observar B bajo todas las posibilidades |
El término de la evidencia P(B) actúa como un normalizador. Puedes expandirlo usando la ley de la probabilidad total:
P(B) = P(B|A) * P(A) + P(B|no A) * P(no A)
Ejemplo de la prueba médica
Una enfermedad afecta a 1 de cada 10.000 personas. La prueba es 99% precisa (detecta al 99% de las personas enfermas, da falsos positivos el 1% de las veces).
P(enfermo) = 0.0001 (prior: la enfermedad es rara)
P(positivo|enfermo) = 0.99 (verosimilitud: la prueba lo detecta)
P(positivo|sano) = 0.01 (tasa de falso positivo)
P(positivo) = P(positivo|enfermo) * P(enfermo) + P(positivo|sano) * P(sano)
= 0.99 * 0.0001 + 0.01 * 0.9999
= 0.000099 + 0.009999
= 0.010098
P(enfermo|positivo) = P(positivo|enfermo) * P(enfermo) / P(positivo)
= 0.99 * 0.0001 / 0.010098
= 0.0098
= 0.98%
Menos del 1%. El prior domina. Cuando una condición es rara, incluso pruebas precisas producen en su mayoría falsos positivos. Por eso los médicos solicitan pruebas de confirmación.
Ejemplo del filtro de spam
Recibes un correo que contiene la palabra "lotería". Es spam?
P(spam) = 0.3 (30% de los correos son spam)
P("loteria"|spam) = 0.05 (5% de los correos de spam contienen "loteria")
P("loteria"|no spam) = 0.001 (0.1% de los correos legitimos contienen "loteria")
P("loteria") = 0.05 * 0.3 + 0.001 * 0.7
= 0.015 + 0.0007
= 0.0157
P(spam|"loteria") = 0.05 * 0.3 / 0.0157
= 0.955
= 95.5%
Una palabra cambia la probabilidad de 30% a 95.5%. Un filtro de spam real aplica Bayes a cientos de palabras simultáneamente.
Naive Bayes: la suposición de independencia
Naive Bayes extiende esto a múltiples features asumiendo que todas las features son condicionalmente independientes dada la clase:
P(clase | feature_1, feature_2, ..., feature_n)
= P(clase) * P(feature_1|clase) * P(feature_2|clase) * ... * P(feature_n|clase)
/ P(feature_1, feature_2, ..., feature_n)
La parte "naive" (ingenua) es la suposición de independencia. En texto, las apariciones de palabras no son independientes ("New" y "York" están correlacionadas). Pero la suposición funciona sorprendentemente bien en la practica porque el clasificador solo necesita ordenar las clases, no producir probabilidades calibradas.
Como el denominador es el mismo para todas las clases, puedes ignorarlo y solo comparar los numeradores:
score(clase) = P(clase) * producto de P(feature_i | clase)
Elige la clase con el mayor score.
Estimación por máxima verosimilitud (MLE)
Como obtienes P(feature|clase) a partir de los datos de entrenamiento? Cuenta.
P("free"|spam) = (numero de correos de spam que contienen "free") / (total de correos de spam)
Esto es MLE: elegir los valores de parámetro que hacen que los datos observados sean más probables. Estas maximizando la función de verosimilitud, que para conteos discretos se reduce a la frecuencia relativa.
Problema: si una palabra nunca aparece en spam durante el entrenamiento, MLE le da probabilidad cero. Una sola palabra no vista mata todo el producto. Corrige esto con el suavizado de Laplace:
P(palabra|clase) = (count(palabra, clase) + 1) / (total_de_palabras_en_la_clase + tamano_del_vocabulario)
Agregar 1 a cada conteo garantiza que ninguna probabilidad sea nunca cero.
Máximo a posteriori (MAP)
MLE pregunta: que parámetros maximizan P(datos|parámetros)?
MAP pregunta: que parámetros maximizan P(parámetros|datos)?
Por el teorema de Bayes:
P(parametros|datos) proporcional a P(datos|parametros) * P(parametros)
MAP agrega un prior sobre los parámetros mismos. Si crees que los parámetros deben ser pequeños, lo codificas como un prior que penaliza los valores grandes. Esto es idéntico a la regularización L2 en ML. La penalización "ridge" en la regresión ridge es literalmente un prior Gaussiano sobre los pesos.
| Estimación | Optimiza | Equivalente en ML |
|---|---|---|
| MLE | P(datos|params) | Entrenamiento sin regularización |
| MAP | P(datos|params) * P(params) | Regularización L2 / L1 |
Bayesiano vs frecuentista: la diferencia practica
Los frecuentistas tratan los parámetros como incógnitas fijas. Preguntan: "Si repitiera este experimento muchas veces, ¿qué pasaría?"
Los bayesianos tratan los parámetros como distribuciones. Preguntan: "Dado lo que he observado, ¿qué creo sobre los parámetros?"
Para construir sistemas de ML, la diferencia practica:
| Aspecto | Frecuentista | Bayesiano |
|---|---|---|
| Salida | Estimación puntual | Distribución sobre valores |
| Incertidumbre | Intervalos de confianza (sobre el procedimiento) | Intervalos de credibilidad (sobre el parámetro) |
| Pocos datos | Puede sobreajustarse | El prior actúa como regularización |
| Cómputo | Generalmente más rápido | A menudo requiere muestreo (MCMC) |
La mayor parte del ML en producción es frecuentista (SGD, estimaciones puntuales). Los métodos bayesianos brillan cuando necesitas incertidumbre calibrada (decisiones medicas, sistemas críticos de seguridad) o cuando los datos son escasos (few-shot learning, arranque en frío).
Por que el pensamiento bayesiano importa para ML
La conexión es más profunda que una analogía:
Los priors son regularización. Un prior Gaussiano sobre los pesos es regularización L2. Un prior de Laplace es L1. Cada vez que agregas un término de regularización, estas haciendo una afirmación bayesiana sobre que valores de parámetro esperas.
Los posteriores son incertidumbre. Una sola probabilidad predicha no te dice nada sobre cuan confiado esta el modelo en esa estimación. Los métodos bayesianos te dan una distribución: "Creo que P(spam) está entre 0.8 y 0.95."
Las actualizaciones de Bayes son aprendizaje en línea. El posterior de hoy se convierte en el prior de mañana. Cuando tu modelo ve nuevos datos, actualiza sus creencias incrementalmente en lugar de reentrenar desde cero.
La comparación de modelos es bayesiana. El criterio de información bayesiano (BIC), la verosimilitud marginal y los factores de Bayes usan razonamiento bayesiano para elegir entre modelos sin sobreajuste.
Construye
Paso 1: Función del teorema de Bayes
def bayes(prior, likelihood, false_positive_rate):
evidence = likelihood * prior + false_positive_rate * (1 - prior)
posterior = likelihood * prior / evidence
return posterior
result = bayes(prior=0.0001, likelihood=0.99, false_positive_rate=0.01)
print(f"P(sick|positive) = {result:.4f}")
Paso 2: Clasificador Naive Bayes
import math
from collections import defaultdict
class NaiveBayes:
def __init__(self, smoothing=1.0):
self.smoothing = smoothing
self.class_counts = defaultdict(int)
self.word_counts = defaultdict(lambda: defaultdict(int))
self.class_word_totals = defaultdict(int)
self.vocab = set()
def train(self, documents, labels):
for doc, label in zip(documents, labels):
self.class_counts[label] += 1
words = doc.lower().split()
for word in words:
self.word_counts[label][word] += 1
self.class_word_totals[label] += 1
self.vocab.add(word)
def predict(self, document):
words = document.lower().split()
total_docs = sum(self.class_counts.values())
vocab_size = len(self.vocab)
best_class = None
best_score = float("-inf")
for cls in self.class_counts:
score = math.log(self.class_counts[cls] / total_docs)
for word in words:
count = self.word_counts[cls].get(word, 0)
total = self.class_word_totals[cls]
score += math.log((count + self.smoothing) / (total + self.smoothing * vocab_size))
if score > best_score:
best_score = score
best_class = cls
return best_class
Las log-probabilidades evitan el subdesbordamiento. Multiplicar muchas probabilidades pequeñas produce números demasiado pequeños para el punto flotante. Sumar log-probabilidades es numéricamente estable y matemáticamente equivalente.
Paso 3: Entrenar con datos de spam
train_docs = [
"win free money now",
"free lottery ticket winner",
"claim your prize today free",
"urgent offer free cash",
"congratulations you won free",
"meeting tomorrow at noon",
"project update attached",
"can we schedule a call",
"quarterly report review",
"lunch on thursday sounds good",
"team standup notes attached",
"please review the pull request",
]
train_labels = [
"spam", "spam", "spam", "spam", "spam",
"ham", "ham", "ham", "ham", "ham", "ham", "ham",
]
classifier = NaiveBayes()
classifier.train(train_docs, train_labels)
test_messages = [
"free money waiting for you",
"meeting rescheduled to friday",
"you won a free prize",
"please review the attached report",
]
for msg in test_messages:
print(f" '{msg}' -> {classifier.predict(msg)}")
Paso 4: Inspeccionar las probabilidades aprendidas
def show_top_words(classifier, cls, n=5):
vocab_size = len(classifier.vocab)
total = classifier.class_word_totals[cls]
probs = {}
for word in classifier.vocab:
count = classifier.word_counts[cls].get(word, 0)
probs[word] = (count + classifier.smoothing) / (total + classifier.smoothing * vocab_size)
sorted_words = sorted(probs.items(), key=lambda x: x[1], reverse=True)
for word, prob in sorted_words[:n]:
print(f" {word}: {prob:.4f}")
print("\nTop spam words:")
show_top_words(classifier, "spam")
print("\nTop ham words:")
show_top_words(classifier, "ham")
Usa
Scikit-learn provee implementaciones de naive Bayes listas para producción:
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import classification_report
vectorizer = CountVectorizer()
X_train = vectorizer.fit_transform(train_docs)
clf = MultinomialNB()
clf.fit(X_train, train_labels)
X_test = vectorizer.transform(test_messages)
predictions = clf.predict(X_test)
for msg, pred in zip(test_messages, predictions):
print(f" '{msg}' -> {pred}")
El mismo algoritmo. CountVectorizer se encarga de la tokenización y la construcción del vocabulario. MultinomialNB se encarga del suavizado y de las log-probabilidades internamente. Tu versión hecha desde cero hace lo mismo en 40 líneas.
Entrega
La clase NaiveBayes construida aquí demuestra el pipeline completo: tokenización, estimación de probabilidad con suavizado de Laplace, predicción en espacio logarítmico. El código en code/bayes.py se ejecuta de extremo a extremo sin dependencias más allá de la biblioteca estándar de Python.
Priors Conjugados
Cuando el prior y el posterior pertenecen a la misma familia de distribuciones, el prior se llama "conjugado". Esto hace que la actualización bayesiana sea álgebraicamente limpia -- obtienes un posterior en forma cerrada sin integración numérica.
| Verosimilitud | Prior Conjugado | Posterior | Ejemplo |
|---|---|---|---|
| Bernoulli | Beta(a, b) | Beta(a + éxitos, b + fracasos) | Estimación del sesgo de una moneda |
| Normal (varianza conocida) | Normal(mu_0, sigma_0) | Normal(media ponderada, varianza menor) | Calibración de sensor |
| Poisson | Gamma(a, b) | Gamma(a + suma de conteos, b + n) | Modelado de tasas de llegada |
| Multinomial | Dirichlet(alpha) | Dirichlet(alpha + conteos) | Modelado de tópicos, modelos de lenguaje |
Por que esto importa: sin priors conjugados, necesitas muestreo de Monte Carlo o inferencia variacional para aproximar el posterior. Con priors conjugados, solo actualizas dos números.
La distribución Beta es el prior conjugado más común en la practica. Beta(a, b) representa tu creencia sobre un parámetro de probabilidad. La media es a/(a+b). Cuanto mayor sea a+b, más concentrada (confiada) es la distribución.
Casos especiales del prior Beta:
- Beta(1, 1) = uniforme. No tienes opinion sobre el parámetro.
- Beta(10, 10) = con pico en 0.5. Crees firmemente que el parámetro está cerca de 0.5.
- Beta(1, 10) = sesgado hacia 0. Crees que el parámetro es pequeño.
La regla de actualización es absurdamente simple:
Prior: Beta(a, b)
Datos: s exitos, f fracasos
Posterior: Beta(a + s, b + f)
Sin integrales. Sin muestreo. Solo suma.
Actualización Bayesiana Secuencial
La inferencia bayesiana es naturalmente secuencial. El posterior de hoy se convierte en el prior de mañana. Así es como los sistemas reales aprenden incrementalmente sin reprocesar todos los datos históricos.
Ejemplo concreto: estimar si una moneda es justa.
Día 1: Aún sin datos. Empieza con Beta(1, 1) -- un prior uniforme. No tienes opinion.
- Media del prior: 0.5
- El prior es plano en [0, 1]
Día 2: Observa 7 caras, 3 cruces. Posterior = Beta(1 + 7, 1 + 3) = Beta(8, 4)
- Media del posterior: 8/12 = 0.667
- La evidencia sugiere que la moneda está sesgada hacia las caras
Día 3: Observa 5 caras más, 5 cruces más. Usa el posterior de ayer como el prior de hoy. Posterior = Beta(8 + 5, 4 + 5) = Beta(13, 9)
- Media del posterior: 13/22 = 0.591
- Los nuevos datos equilibrados jalaron la estimación de vuelta hacia 0.5
graph LR
A["Prior<br/>Beta(1,1)<br/>mean = 0.50"] -->|"7H, 3T"| B["Posterior 1<br/>Beta(8,4)<br/>mean = 0.67"]
B -->|"becomes prior"| C["Prior 2<br/>Beta(8,4)"]
C -->|"5H, 5T"| D["Posterior 2<br/>Beta(13,9)<br/>mean = 0.59"]
El orden de las observaciones no importa. Beta(1,1) actualizado con todas las 12 caras y 8 cruces a la vez da Beta(13, 9) -- el mismo resultado. La actualización secuencial y la actualización por lotes son matemáticamente equivalentes. Pero la actualización secuencial te permite tomar decisiones en cada paso sin almacenar datos en bruto.
Esta es la base del aprendizaje en línea en sistemas de ML en producción. El muestreo de Thompson para bandidos, los sistemas de recomendación incrementales y los detectores de anomalías en streaming usan este patron.
Conexión con las Pruebas A/B
La prueba A/B es inferencia bayesiana disfrazada.
Escenario: estas probando dos colores de botón. Variante A (azul) y variante B (verde). Quieres saber cual recibe más clics.
La prueba A/B bayesiana:
- Prior. Empieza con Beta(1, 1) para ambas variantes. Sin preferencia previa.
- Datos. Variante A: 50 clics de 1000 vistas. Variante B: 65 clics de 1000 vistas.
- Posteriores.
- A: Beta(1 + 50, 1 + 950) = Beta(51, 951). Media = 0.051
- B: Beta(1 + 65, 1 + 935) = Beta(66, 936). Media = 0.066
- Decisión. Calcula P(B > A) -- la probabilidad de que la tasa de conversión verdadera de B sea mayor que la de A.
Calcular P(B > A) analíticamente es difícil. Pero Monte Carlo lo vuelve trivial:
1. Extrae 100.000 muestras de Beta(51, 951) -> samples_A
2. Extrae 100.000 muestras de Beta(66, 936) -> samples_B
3. P(B > A) = fraccion de muestras donde B > A
Si P(B > A) > 0.95, entregas la variante B. Si está entre 0.05 y 0.95, sigues recolectando datos. Si P(B > A) < 0.05, entregas la variante A.
Ventajas sobre la prueba A/B frecuentista:
- Obtienes una afirmación directa de probabilidad: "hay un 97% de probabilidad de que B sea mejor"
- Sin confusión de p-valor. Sin la jerga de "no rechazar la hipótesis nula".
- Puedes revisar los resultados en cualquier momento sin inflar las tasas de falso positivo (sin el "problema de espiar")
- Puedes incorporar conocimiento previo (por ejemplo, pruebas anteriores sugieren que las tasas de conversión suelen ser de 3-8%)
| Aspecto | A/B Frecuentista | A/B Bayesiano |
|---|---|---|
| Salida | p-valor | P(B > A) |
| Interpretación | "¿Cuán sorprendentes son estos datos si A=B?" | "¿Cuán probable es que B sea mejor que A?" |
| Parada temprana | Infla los falsos positivos | Segura en cualquier punto (dado un prior bien elegido y un modelo correctamente especificado) |
| Conocimiento previo | No se usa | Codificado como prior Beta |
| Regla de decisión | p < 0.05 | P(B > A) > umbral |
Ejercicios
Pruebas múltiples. Un paciente da positivo dos veces en pruebas independientes (ambas 99% precisas, prevalencia de la enfermedad 1 de cada 10.000). ¿Cuál es P(enfermo) tras ambas pruebas? Usa el posterior de la primera prueba como el prior de la segunda.
Impacto del suavizado. Ejecuta el clasificador de spam con valores de suavizado de 0.01, 0.1, 1.0 y 10.0. ¿Cómo cambian las probabilidades de las palabras más frecuentes? Que pasa con suavizado=0 y una palabra que aparece solo en ham?
Agrega features. Extiende la clase NaiveBayes para que también use la longitud del mensaje (corto/largo) como una feature junto a los conteos de palabras. Estima P(corto|spam) y P(corto|ham) a partir de los datos de entrenamiento e incorpóralas en el score de predicción.
MAP a mano. Dados los datos observados (7 caras en 10 lanzamientos de moneda), calcula la estimación MAP del sesgo usando un prior Beta(2,2). Compárala con la estimación MLE (7/10).
Términos Clave
| Término | Lo que dice la gente | Lo que realmente significa |
|---|---|---|
| Prior | "Mi conjetura inicial" | P(hipótesis) antes de observar la evidencia. En ML: el término de regularización. |
| Verosimilitud | "Cuan bien encajan los datos" | P(evidencia|hipótesis). Cuan probables son los datos observados bajo una hipótesis especifica. |
| Posterior | "Mi creencia actualizada" | P(hipótesis|evidencia). El prior multiplicado por la verosimilitud, luego normalizado. |
| Evidencia | "La constante de normalización" | P(datos) sobre todas las hipótesis. Garantiza que el posterior sume 1. |
| Naive Bayes | "Ese clasificador de texto simple" | Un clasificador que asume que las features son independientes dada la clase. Funciona bien a pesar de la suposición falsa. |
| Suavizado de Laplace | "Suavizado add-one" | Agregar un pequeño conteo a cada feature para evitar probabilidades cero de datos no vistos. |
| MLE | "Solo usa las frecuencias" | Elegir parámetros que maximizan P(datos|parámetros). Sin prior. Puede sobreajustarse con pocos datos. |
| MAP | "MLE con un prior" | Elegir parámetros que maximizan P(datos|parámetros) * P(parámetros). Equivalente a MLE regularizado. |
| Log-probabilidad | "Trabajar en espacio logarítmico" | Usar log(P) en lugar de P para evitar el subdesbordamiento de punto flotante al multiplicar muchos números pequeños. |
| Falso positivo | "Una alarma equivocada" | La prueba dice positivo, pero el estado verdadero es negativo. Provoca la falacia de la tasa base. |
Lectura Adicional
- 3Blue1Brown: Bayes' theorem - explicación visual con el ejemplo de la prueba médica
- Stanford CS229: Generative Learning Algorithms - naive Bayes y su conexión con los modelos discriminativos
- Think Bayes - libro gratuito, estadística bayesiana con código Python
- scikit-learn Naive Bayes - implementaciones de producción y cuando usar cada variante