Autor/a

Diego Villalba

Fecha de publicación

19 de mayo de 2026

Los métodos de aprendizaje supervisado pueden verse desde dos perspectivas complementarias. En una perspectiva determinista, el objetivo es aprender directamente una función de decisión \(f(\mathbf{x})\) que asigne cada observación a una clase, produzca una predicción continua o aproxime una relación funcional. En una perspectiva probabilística, en cambio, el objetivo es modelar explícitamente la incertidumbre: no sólo se decide una clase, sino que se estima qué tan plausible es cada clase dada la evidencia observada. Esto se traduce en que, si no observamos niguna caracteristica del objeto, la mejor decisión que podemos tomar depende de la probabilidad previa de cada clase.

Para ilustrar este hecho, supongamos que somos participantes dentro del famoso juego En familia con chabelo, donde se nos da la oportunidad de ganar un auto, para esto debemos de escoger dentro de 3 posibles puertas donde detrás se encuentra el auto o una cabra. Si no se nos da ninguna pista previa es razonable pensar que contamos con una probabilidad de \(\frac{1}{3}\) de ganar el auto, por lo que la mejor decisión es escoger una puerta al azar.

Notemos como la probabilidad de encontrar el auto detrás de las puertas cerradas es de \frac{1}{3}, mientras que la probabilidad de encontrar la cabra es de \frac{2}{3}

Notemos como la probabilidad de encontrar el auto detrás de las puertas cerradas es de \(\frac{1}{3}\), mientras que la probabilidad de encontrar la cabra es de \(\frac{2}{3}\)

Asumiendo que nuestra elección es la puerta 1, como sucede en muchas ocasiones el presentador nos hará notar que detras de la puerta tres hay una cabra, con lo cual es natural preguntarse: ¿Bajo que condiciones tendremos la mejor probabilidad de ganar?, es común pensar que dado que sabemos que detrás de las puerta de nuestra elección hay o una cabra o un auto la probabilidad de encntrar nuestro premio tras estas 2 puertas es de \(\frac{1}{2}\), sin embargo nuestra intución en ese caso sería incorrecta.

Una vez se ha abierto una puerta con una cabra, dado qeu el presentador solo podría haber abierto la puerta que tuviera detrás una cabra obliga a que la probabilidad de ganar el auto si cambiamos a la puerta 2 es de \frac{2}{3}

Una vez se ha abierto una puerta con una cabra, dado qeu el presentador solo podría haber abierto la puerta que tuviera detrás una cabra obliga a que la probabilidad de ganar el auto si cambiamos a la puerta 2 es de \(\frac{2}{3}\)

Para ver con más detalle por qué conviene cambiar de puerta, hagamos las cuentas paso a paso. Antes de que Chabelo abra una puerta, el auto puede estar en cualquiera de las tres puertas con la misma probabilidad:

\[ P(A_1)=P(A_2)=P(A_3)=\frac{1}{3} \]

donde \(A_i\) significa que el auto está detrás de la puerta \(i\).

Ahora bien, Chabelo sabe dónde está el auto y siempre abre una puerta que tiene una cabra. Además, si tiene dos puertas con cabra disponibles, asumimos que escoge una de ellas al azar. Entonces las posibilidades son las siguientes:

Las ramas donde cambiar gana valen por 2 porque tienen probabilidad \(\frac{1}{3}\), mientras que las ramas donde cambiar pierde valen \(\frac{1}{6}\). Esto ocurre porque si el auto está en nuestra puerta inicial, Chabelo tiene dos puertas posibles para abrir y divide ese caso en dos ramas de probabilidad \(\frac{1}{6}\). En cambio, si el auto está en una puerta distinta a la nuestra, Chabelo no tiene libertad: sólo puede abrir la única puerta restante con cabra. Por eso cada una de esas ramas conserva probabilidad \(\frac{1}{3}\), equivalente a dos partes de tamaño \(\frac{1}{6}\).

Elige tu puerta inicial:
El árbol muestra todos los caminos posibles del juego. Las ramas más probables (p = 1/3) contribuyen 2 unidades al conteo final; las menos probables (p = 1/6) contribuyen 1. Las ramas donde cambiar gana pesan 2 porque Chabelo estaba obligado a abrir una sola puerta. El total es 6 estados de igual peso.

Como en el ejemplo Chabelo abre la puerta 3 y muestra una cabra, debemos quedarnos únicamente con los casos compatibles con esa información. Es decir, descartamos todos los casos donde Chabelo abre la puerta 2 o donde el auto estaba detrás de la puerta 3.

Los casos posibles después de observar que Chabelo abrió la puerta 3 son:

Caso Ubicación del auto Probabilidad inicial Probabilidad de que Chabelo abra la puerta 3 Probabilidad conjunta
1 Puerta 1 \(\frac{1}{3}\) \(\frac{1}{2}\) \(\frac{1}{3}\cdot\frac{1}{2}=\frac{1}{6}\)
2 Puerta 2 \(\frac{1}{3}\) \(1\) \(\frac{1}{3}\cdot 1=\frac{1}{3}\)
3 Puerta 3 \(\frac{1}{3}\) \(0\) \(\frac{1}{3}\cdot 0=0\)

La probabilidad total de que Chabelo abra la puerta 3 es:

\[ P(\text{Chabelo abre puerta 3}) = \frac{1}{6}+\frac{1}{3}+0 \]

\[ P(\text{Chabelo abre puerta 3}) = \frac{1}{6}+\frac{2}{6} = \frac{3}{6} = \frac{1}{2} \]

Ahora actualizamos las probabilidades usando la información nueva. Primero, calculemos la probabilidad de que el auto esté en la puerta 1 dado que Chabelo abrió la puerta 3:

\[ P(A_1 \mid \text{Chabelo abre puerta 3}) = \frac{P(A_1 \cap \text{Chabelo abre puerta 3})} {P(\text{Chabelo abre puerta 3})} \]

\[ P(A_1 \mid \text{Chabelo abre puerta 3}) = \frac{\frac{1}{6}}{\frac{1}{2}} = \frac{1}{6}\cdot\frac{2}{1} = \frac{2}{6} = \frac{1}{3} \]

Por lo tanto, si nos quedamos con la puerta 1, la probabilidad de ganar sigue siendo:

\[ P(\text{ganar si nos quedamos}) = \frac{1}{3} \]

Ahora calculemos la probabilidad de que el auto esté en la puerta 2 dado que Chabelo abrió la puerta 3:

\[ P(A_2 \mid \text{Chabelo abre puerta 3}) = \frac{P(A_2 \cap \text{Chabelo abre puerta 3})} {P(\text{Chabelo abre puerta 3})} \]

\[ P(A_2 \mid \text{Chabelo abre puerta 3}) = \frac{\frac{1}{3}}{\frac{1}{2}} = \frac{1}{3}\cdot\frac{2}{1} = \frac{2}{3} \]

Por lo tanto, si cambiamos a la puerta 2, la probabilidad de ganar es:

\[ P(\text{ganar si cambiamos}) = \frac{2}{3} \]

La clave está en que la puerta que elegimos inicialmente conserva su probabilidad original de \(\frac{1}{3}\). La información nueva no reparte la probabilidad en partes iguales entre las dos puertas cerradas. Más bien, la probabilidad que antes estaba distribuida entre las dos puertas que no escogimos se concentra en la única puerta no abierta por Chabelo.

En resumen:

\[ P(\text{ganar si nos quedamos}) = \frac{1}{3} \]

\[ P(\text{ganar si cambiamos}) = \frac{2}{3} \]

Así que la mejor estrategia es cambiar de puerta. La pista de Chabelo no es una simple eliminación de una opción: es evidencia nueva que cambia nuestras probabilidades posteriores. Esta es precisamente la idea central del razonamiento bayesiano: actualizar nuestras creencias iniciales cuando observamos nueva información.

En esta simulación podemos observar como las probabilidades del problema a lo largo de las diferentes realizaciones convergen a los valores teóricos de \(\frac{1}{3}\) para la estrategia de quedarse y \(\frac{2}{3}\) para la estrategia de cambiar.

Mostrar código
import random
import plotly.graph_objects as go

def simular_monty_hall_plotly(n_simulaciones=10_000):
    puertas = [1, 2, 3]

    gana_quedandose = 0
    gana_cambiando = 0

    probabilidades_quedandose = []
    probabilidades_cambiando = []
    simulaciones = []

    for i in range(1, n_simulaciones + 1):
        # El auto se coloca al azar detrás de una puerta
        puerta_auto = random.choice(puertas)

        # El participante siempre escoge inicialmente la puerta 1
        eleccion_inicial = 1

        # Chabelo abre una puerta que no fue elegida y que no tiene el auto
        puertas_posibles_para_abrir = [
            puerta
            for puerta in puertas
            if puerta != eleccion_inicial and puerta != puerta_auto
        ]

        puerta_abierta = random.choice(puertas_posibles_para_abrir)

        # Si el participante cambia, escoge la única puerta cerrada restante
        puerta_cambio = [
            puerta
            for puerta in puertas
            if puerta != eleccion_inicial and puerta != puerta_abierta
        ][0]

        # Estrategia 1: quedarse con la puerta inicial
        if eleccion_inicial == puerta_auto:
            gana_quedandose += 1

        # Estrategia 2: cambiar de puerta
        if puerta_cambio == puerta_auto:
            gana_cambiando += 1

        # Probabilidades acumuladas hasta la simulación actual
        probabilidades_quedandose.append(gana_quedandose / i)
        probabilidades_cambiando.append(gana_cambiando / i)
        simulaciones.append(i)

    fig = go.Figure()

    fig.add_trace(
        go.Scatter(
            x=simulaciones,
            y=probabilidades_quedandose,
            mode="lines",
            name="Quedarse con la puerta inicial",
            line=dict(color="#1f77b4", width=2)
        )
    )

    fig.add_trace(
        go.Scatter(
            x=simulaciones,
            y=probabilidades_cambiando,
            mode="lines",
            name="Cambiar de puerta",
            line=dict(color="#d62728", width=2)
        )
    )

    fig.add_hline(
        y=1/3,
        line_dash="dash",
        line_color="#1f77b4",
        annotation_text="Valor teórico: 1/3",
        annotation_position="bottom"
    )

    fig.add_hline(
        y=2/3,
        line_dash="dash",
        line_color="#d62728",
        annotation_text="Valor teórico: 2/3",
        annotation_position="bottom"
    )

    fig.update_layout(
    title="Evolución de las probabilidades estimadas en el problema de Monty Hall",
    xaxis_title="Número de simulaciones",
    yaxis_title="Probabilidad acumulada de ganar",
    template="plotly_white",
    yaxis=dict(range=[0, 1]),

    legend=dict(
        title="Estrategia",
        orientation="h",
        yanchor="top",
        y=-0.2,
        xanchor="center",
        x=0.5
    )
)


    fig.show()

    print(f"Simulaciones realizadas: {n_simulaciones}")
    print(f"Probabilidad final de ganar quedándose: {probabilidades_quedandose[-1]:.4f}")
    print(f"Probabilidad final de ganar cambiando: {probabilidades_cambiando[-1]:.4f}")


simular_monty_hall_plotly(n_simulaciones=10_000)
Simulaciones realizadas: 10000
Probabilidad final de ganar quedándose: 0.3283
Probabilidad final de ganar cambiando: 0.6717

Esta es la diferencia central en los clasificadores bayesianos, la decisión se deriva de probabilidades a priori, densidades condicionales y probabilidades posteriores (Duda, Hart, y Stork 2001; Bishop 2006). Por ejemplo, si una caja contiene monedas de dos tipos y una de ellas es más frecuente, la decisión con menor probabilidad de error antes de observar la moneda es escoger la clase más probable. Sin embargo, una vez que se observa una característica, como el peso, la decisión debe incorporar esa evidencia. La pregunta deja de ser “¿qué clase es más frecuente?” y pasa a ser “¿qué clase es más probable dado el valor observado?”.

Si tuvieramos que clasificar entre monedas de un centavo y monedas de diez centavos, antes de observar el peso, la mejor decisión sería escoger la clase más frecuente. Sin embargo, una vez que se observa el peso, la decisión debe incorporar esa evidencia. La pregunta deja de ser “¿qué clase es más frecuente?” y pasa a ser “¿qué clase es más probable dado el valor observado?”.

Los clasificadores bayesianos usan el teorema de Bayes para decidir a qué clase pertenece una observación. Para cada clase \(\omega_i\), se considera qué tan probable es observar los datos \(\mathbf{x}\) si esa clase fuera la correcta, junto con qué tan probable era esa clase antes de ver los datos. Con esta información se calcula la probabilidad de cada clase después de observar \(\mathbf{x}\). La decisión más sencilla consiste en elegir la clase con mayor probabilidad posterior; si algunos errores son más costosos que otros, se puede modificar la decisión para tomar en cuenta esos costos (Duda, Hart, y Stork 2001; Jaynes 2003).

Este capítulo desarrolla la clasificación bayesiana desde sus fundamentos hasta algunas variantes prácticas: Naïve Bayes, clasificadores gaussianos, modelos de Markov para secuencias discretas, decisión bayesiana con costos y el criterio minimax cuando las probabilidades previas son inciertas. Los ejemplos computacionales están escritos en Python y buscan conectar la teoría con implementaciones reproducibles.

1 Vista probabilística de la clasificación

Sea \(\mathbf{x}\in\mathbb{R}^d\) una observación y sea \(\Omega=\{\omega_1,\dots,\omega_c\}\) el conjunto de clases posibles. En clasificación probabilística se supone que \(\mathbf{x}\) no determina de manera absoluta la clase, sino que modifica nuestras creencias sobre ella. Antes de observar \(\mathbf{x}\), la información disponible está representada por las probabilidades previas \(P(\omega_i)\). Después de observar \(\mathbf{x}\), la información relevante se resume en las probabilidades posteriores \(P(\omega_i\mid \mathbf{x})\).

Definición. La probabilidad posterior de la clase \(\omega_i\) dada una observación \(\mathbf{x}\) se obtiene mediante el teorema de Bayes: \[ P(\omega_i\mid \mathbf{x}) = \frac{p(\mathbf{x}\mid \omega_i)P(\omega_i)}{p(\mathbf{x})} \tag{1}\]

En la ecuación Ec. 34.1, \(p(\mathbf{x}\mid\omega_i)\) es la densidad condicional de la observación bajo la clase \(\omega_i\), \(P(\omega_i)\) es la probabilidad previa de esa clase y \(p(\mathbf{x})\) es la densidad marginal de la observación:

\[ p(\mathbf{x}) = \sum_{j=1}^c p(\mathbf{x}\mid\omega_j)P(\omega_j). \]

Como \(p(\mathbf{x})\) no depende de la clase que se compara, muchas reglas de decisión pueden escribirse en términos de la cantidad no normalizada \(p(\mathbf{x}\mid\omega_i)P(\omega_i)\).

La decisión de clasificación

Si no hay observaciones, una regla razonable para dos clases es escoger la clase más probable a priori:

\[ \text{decidir }\omega_1 \quad \text{si} \quad P(\omega_1)>P(\omega_2). \]

El error esperado de esta regla, cuando sólo existen dos clases, es

\[ P(e)=\min\{P(\omega_1),P(\omega_2)\}. \]

Una vez observada \(\mathbf{x}\), la decisión cambia: se debe comparar \(P(\omega_1\mid\mathbf{x})\) contra \(P(\omega_2\mid\mathbf{x})\). Esta distinción es la esencia de la clasificación bayesiana: la evidencia observada actualiza las probabilidades de las clases.

Definición. La regla de máxima probabilidad posterior, o regla MAP, asigna la observación \(\mathbf{x}\) a la clase con posterior máxima: \[ \hat{\omega}(\mathbf{x}) = \arg\max_{\omega_i\in\Omega} P(\omega_i\mid\mathbf{x}) \tag{2}\]

1.1 Ejemplo básico: El dilema del Cine

Esta persona dejó caer su boleto en el pasillo.

¿Le dices?

“Disculpe, señora”

o

“Disculpe, señor”

Tienes que hacer una conjetura.

Para responder de la manera mas acertada posible podemos hacer uso del teorema de Bayes, para esto definamos el evento \(H\) como “hombre” y el evento \(M\) como “mujer”, mientras que la evidencia observada es el tipo de cabello de la persona. Entonces, la probabilidad de que la persona sea hombre o mujer dado el tipo de cabello se calcula con Bayes:

\[ P(\text{sexo}\mid \text{cabello}) = \frac{ P(\text{cabello}\mid \text{sexo})P(\text{sexo}) }{ \sum_{\text{sexo}} P(\text{sexo})P(\text{cabello}\mid \text{sexo}) } \]

Donde bajo una asumpción justa podemos suponer que dentro del cine:

De cada 100 mujeres en el cine

  • 50 tienen cabello corto
  • 50 tienen cabello largo

De cada 100 hombres en el cine

  • 96 tienen cabello corto
  • 4 tienen cabello largo

Hagamos los calculos paso a paso, si asumimos que la proporción de hombres y mujeres en el cine es la misma, entonces las probabilidades previas son:

\[P(H)=P(M)=0.5\]

Mientras que las verosimilitudes son: \[P(\text{cabello largo}\mid H)=0.04\] \[P(\text{cabello largo}\mid M)=0.50\]

teniendo esto, Entonces los numeradores son:

\[ P(\text{largo}\mid H)P(H)=0.04(0.5)=0.02 \] \[ P(\text{largo}\mid M)P(M)=0.50(0.5)=0.25 \]

Entonces:

\[ P(H\mid \text{cabello largo}) = 0.02A \approx 0.07 \]

\[ P(M\mid \text{cabello largo}) = 0.25A \approx 0.93 \]

donde

\[ A=\frac{1}{0.02+0.25} \]

Así, bajo la regla de máxima probabilidad posterior, la decisión es:

\[ \Rightarrow \textbf{Decisión: mujer} \]

Nota

La idea central es que no decidimos solo por la característica observada, sino por cómo esa característica modifica nuestras creencias previas sobre cada clase.

1.2 Dilema en el cine: el contexto importa

Es importante notar que la decisión anterior depende de las probabilidades previas. Si el contexto cambia, las probabilidades previas también cambian, lo que puede llevar a una decisión diferente incluso con la misma evidencia observada. Por ejemplo, si esta situación ocurre mientras estás formado en la fila del baño de hombres, la proporción de hombres y mujeres cambia drásticamente, lo que afecta las probabilidades previas y, por ende, la decisión bayesiana.

¿Le dices?

“Disculpe, señora”

o

“Disculpe, señor”

Ahora las probabilidades previas cambian:

\[ P(H)=0.98, \qquad P(M)=0.02 \]

donde \(H\) representa “hombre” y \(M\) representa “mujer”. De nuevo asumiendo de manera justa que dentro de la fila:

De cada 2 mujeres en la fila

  • 1 tiene cabello corto
  • 1 tiene cabello largo

De cada 98 hombres en la fila

  • 94 tienen cabello corto
  • 4 tienen cabello largo

De los datos anteriores:

\[ P(\text{cabello largo}\mid H)=\frac{4}{98} \]

\[ P(\text{cabello largo}\mid M)=\frac{1}{2} \]

Calculamos los numeradores de Bayes:

\[ P(\text{cabello largo}\mid H)P(H) = \frac{4}{98}(0.98) = 0.04 \]

\[ P(\text{cabello largo}\mid M)P(M) = \frac{1}{2}(0.02) = 0.01 \]

La constante de normalización es:

\[ B= \frac{1}{0.04+0.01} = 20 \]

Entonces, bajo la misma regla de máxima probabilidad posterior, las probabilidades posteriores son:

\[ P(H\mid \text{cabello largo}) = 0.04B = 0.04(20) = 0.80 \]

\[ P(M\mid \text{cabello largo}) = 0.01B = 0.01(20) = 0.20 \]

Como:

\[ P(H\mid \text{cabello largo}) > P(M\mid \text{cabello largo}) \]

la decisión bayesiana es:

\[ \Rightarrow \textbf{Decisión: hombre} \]

Importante

La misma evidencia observada (cabello largo) puede llevar a una decisión distinta cuando cambian las probabilidades previas.

En clasificación bayesiana, el contexto modifica la inferencia.

1.3 Implementación básica: algoritmo general de un clasificador bayesiano

A continuación se muestra una implementación básica de un clasificador bayesiano para cualquier número de clases. El código calcula las probabilidades posteriores y toma la decisión de clasificación basada en la clase con mayor probabilidad posterior.

import numpy as np

def clasificador_bayesiano(verosimilitudes, priors, clases):
    """
    Clasificador bayesiano para cualquier número de clases.

    verosimilitudes: p(x | omega_i) para cada clase.
    priors: P(omega_i) para cada clase.
    clases: nombres de las clases.
    """
    verosimilitudes = np.array(verosimilitudes, dtype=float)
    priors = np.array(priors, dtype=float)
    clases = np.array(clases)

    evidencia = np.sum(verosimilitudes * priors)
    posteriores = (verosimilitudes * priors) / evidencia

    indice_decision = np.argmax(posteriores)
    decision = clases[indice_decision]

    return posteriores, decision


clases = ["normal", "enfermedad"]

# p(x | omega_i): qué tan probable es observar x en cada clase
verosimilitudes = [0.2, 0.4]

# P(omega_i): probabilidad previa de cada clase
priors = [0.9, 0.1]

posteriores, decision = clasificador_bayesiano(
    verosimilitudes,
    priors,
    clases
)

for clase, posterior in zip(clases, posteriores):
    print(f"P({clase} | x) = {posterior:.3f}")

print("Decisión:", decision)
P(normal | x) = 0.818
P(enfermedad | x) = 0.182
Decisión: normal

En este ejemplo, aunque la verosimilitud bajo la clase de enfermedad es mayor, el prior de la clase normal domina la decisión de error mínimo. Esto ilustra una idea importante: una evidencia local no se interpreta de manera aislada, sino en relación con la frecuencia previa de cada clase.

Actualización bayesiana — prior → posterior
Ajusta el prior P(ω₁) y las verosimilitudes p(x|ω) para ver cómo cambia la decisión



2 Clasificador bayesiano de error mínimo

La regla MAP tiene una interpretación óptima cuando todos los errores tienen el mismo costo: minimiza la probabilidad promedio de clasificación incorrecta. Para verlo, consideremos inicialmente dos clases. Si se asigna \(\mathbf{x}\) a \(\omega_1\), el error condicional es \(P(\omega_2\mid\mathbf{x})\); si se asigna a \(\omega_2\), el error condicional es \(P(\omega_1\mid\mathbf{x})\). Por tanto, para minimizar el error en cada punto \(\mathbf{x}\) se elige la clase con mayor posterior.

Definición. El error condicional de clasificación para dos clases es \[ P(e\mid\mathbf{x})= \begin{cases} P(\omega_2\mid\mathbf{x}), & \text{si se decide }\omega_1,\\ P(\omega_1\mid\mathbf{x}), & \text{si se decide }\omega_2. \end{cases} \tag{3}\]

El error promedio se obtiene integrando sobre todo el espacio de observaciones:

\[ P(e)=\int P(e\mid\mathbf{x})p(\mathbf{x})\,d\mathbf{x}. \]

Dado que \(p(\mathbf{x})\geq 0\), minimizar el error promedio se logra minimizando el error condicional punto a punto. Así, para dos clases:

\[ P(\omega_1\mid\mathbf{x})>P(\omega_2\mid\mathbf{x}) \quad \Longrightarrow \quad \mathbf{x}\in\omega_1. \]

2.1 Ejemplo en Python: decisión de error mínimo

El siguiente código muestra la regla de error mínimo para varias observaciones. Para cada observación se tienen las verosimilitudes \(p(\mathbf{x}\mid\omega_i)\) bajo dos clases. Después se calculan las posteriores, se elige la clase con mayor posterior y se reporta el error condicional mínimo.

import numpy as np

clases = np.array(["normal", "enfermedad"])

# Probabilidades previas P(omega_i)
priors = np.array([0.8, 0.2])

# Cada fila representa una observación distinta.
# Las columnas contienen p(x | normal) y p(x | enfermedad).
verosimilitudes = np.array([
    [0.50, 0.10],
    [0.35, 0.25],
    [0.20, 0.45],
    [0.10, 0.60],
    [0.40, 0.30]
])

# Numerador de Bayes: p(x | omega_i) P(omega_i)
puntajes_no_normalizados = verosimilitudes * priors

# Evidencia p(x), calculada para cada observación
evidencia = puntajes_no_normalizados.sum(axis=1, keepdims=True)

# Posteriores P(omega_i | x)
posteriores = puntajes_no_normalizados / evidencia

# Regla de error mínimo: elegir la clase con mayor posterior
indices_decision = np.argmax(posteriores, axis=1)
decisiones = clases[indices_decision]

# El error condicional mínimo es 1 menos la posterior más grande
errores_condicionales = 1 - np.max(posteriores, axis=1)

for i, (post, decision, error) in enumerate(
    zip(posteriores, decisiones, errores_condicionales),
    start=1
):
    print(f"Observación {i}")
    print(f"  P(normal | x) = {post[0]:.3f}")
    print(f"  P(enfermedad | x) = {post[1]:.3f}")
    print(f"  Decisión de error mínimo: {decision}")
    print(f"  Error condicional mínimo: {error:.3f}\n")
Observación 1
  P(normal | x) = 0.952
  P(enfermedad | x) = 0.048
  Decisión de error mínimo: normal
  Error condicional mínimo: 0.048

Observación 2
  P(normal | x) = 0.848
  P(enfermedad | x) = 0.152
  Decisión de error mínimo: normal
  Error condicional mínimo: 0.152

Observación 3
  P(normal | x) = 0.640
  P(enfermedad | x) = 0.360
  Decisión de error mínimo: normal
  Error condicional mínimo: 0.360

Observación 4
  P(normal | x) = 0.400
  P(enfermedad | x) = 0.600
  Decisión de error mínimo: enfermedad
  Error condicional mínimo: 0.400

Observación 5
  P(normal | x) = 0.842
  P(enfermedad | x) = 0.158
  Decisión de error mínimo: normal
  Error condicional mínimo: 0.158

En este ejemplo, una observación se clasifica como enfermedad sólo cuando su posterior supera a la posterior de normal. No basta con que \(p(\mathbf{x}\mid\text{enfermedad})\) sea grande de manera aislada: también importa el prior de cada clase. Por eso la decisión se basa en el producto \(p(\mathbf{x}\mid\omega_i)P(\omega_i)\) y no únicamente en la verosimilitud.

2.2 Formas equivalentes de la regla de decisión

Usando el teorema de Bayes, la comparación de posteriores puede escribirse como una comparación de productos entre verosimilitud y prior:

\[ p(\mathbf{x}\mid\omega_1)P(\omega_1) > p(\mathbf{x}\mid\omega_2)P(\omega_2). \]

Para dos clases, también puede expresarse mediante el cociente de verosimilitudes.

Definición. El cociente de verosimilitudes entre dos clases se define como \[ \ell(\mathbf{x})=\frac{p(\mathbf{x}\mid\omega_1)}{p(\mathbf{x}\mid\omega_2)} \tag{4}\]

La regla de decisión se vuelve

\[ \ell(\mathbf{x}) \gtrless_{\omega_2}^{\omega_1} \frac{P(\omega_2)}{P(\omega_1)}. \]

Si se trabaja en escala logarítmica, se evita el subdesbordamiento numérico y se transforman productos en sumas:

\[ \log \ell(\mathbf{x}) = \log p(\mathbf{x}\mid\omega_1)-\log p(\mathbf{x}\mid\omega_2). \]

Esta forma es especialmente útil en alta dimensión, en secuencias largas y en modelos donde las densidades son productos de muchas probabilidades pequeñas.

3 Clasificador Naïve Bayes

En alta dimensión, estimar la densidad conjunta \(p(\mathbf{x}\mid\omega_i)\) puede ser difícil. Si \(\mathbf{x}=(x_1,\dots,x_d)\), una densidad conjunta general requiere modelar dependencias entre todas las variables. Por ejemplo, en clasificación de textos no basta con preguntar si aparece una palabra: un documento puede contener cientos o miles de términos, y cada término puede aparecer varias veces.

Naïve Bayes simplifica el problema suponiendo independencia condicional de las características dada la clase (McCallum y Nigam 1998; Murphy 2012). La palabra naïve no significa que el método sea inútil, sino que usa una suposición fuerte: una vez que conocemos la clase, tratamos a las características como si aportaran evidencia de manera separada.

Definición. El supuesto de independencia condicional de Naïve Bayes establece que \[ p(\mathbf{x}\mid\omega_i)=p(x_1,x_2,\dots,x_d\mid\omega_i)=\prod_{k=1}^{d}p(x_k\mid\omega_i) \tag{5}\]

Con este supuesto, la regla MAP se escribe como:

\[ \hat{\omega}(\mathbf{x})=\arg\max_i \left[\log P(\omega_i)+\sum_{k=1}^{d}\log p(x_k\mid\omega_i)\right]. \]

Esta expresión tiene una lectura sencilla. El término \(\log P(\omega_i)\) representa qué tan probable era la clase antes de observar el documento. La suma \(\sum_{k=1}^{d}\log p(x_k\mid\omega_i)\) acumula la evidencia que aportan las características observadas. La clase elegida es aquella que obtiene la puntuación total más alta.

Mostrar código
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots

rng = np.random.default_rng(7)

# Datos con correlación entre variables
cov_full = np.array([[1.2, 0.85], [0.85, 0.9]])
cov_diag = np.diag(np.diag(cov_full))  # versión diagonal (NB)

mu1 = np.array([-1.5, -1.0])
mu2 = np.array([1.5,  1.0])

X1 = rng.multivariate_normal(mu1, cov_full, 80)
X2 = rng.multivariate_normal(mu2, cov_full, 80)

def ellipse_points(mu, cov, n_std=2, n_pts=120):
    """Elipse de confianza via descomposición espectral."""
    vals, vecs = np.linalg.eigh(cov)
    t = np.linspace(0, 2 * np.pi, n_pts)
    circle = np.stack([np.cos(t), np.sin(t)], axis=1)
    ellipse = circle @ (vecs * np.sqrt(vals) * n_std).T + mu
    return ellipse[:, 0], ellipse[:, 1]

def grid_posterior(X1, X2, cov, xlim, ylim, n=80):
    """Probabilidad posterior P(clase 1 | x) en un grid 2D."""
    def mvn(X, mu, cov):
        D = X.shape[1]
        diff = X - mu
        inv_cov = np.linalg.inv(cov)
        sign, logdet = np.linalg.slogdet(cov)
        maha = np.einsum('ij,jk,ik->i', diff, inv_cov, diff)
        return np.exp(-0.5 * maha - 0.5 * (D * np.log(2 * np.pi) + logdet))

    xg = np.linspace(xlim[0], xlim[1], n)
    yg = np.linspace(ylim[0], ylim[1], n)
    Xg, Yg = np.meshgrid(xg, yg)
    pts = np.c_[Xg.ravel(), Yg.ravel()]

    mu1_ = X1.mean(axis=0)
    mu2_ = X2.mean(axis=0)
    p1 = mvn(pts, mu1_, cov)
    p2 = mvn(pts, mu2_, cov)
    post = p1 / (p1 + p2 + 1e-300)
    return xg, yg, post.reshape(n, n)

xlim = (-5, 5); ylim = (-5, 5)
xg, yg, Z_full = grid_posterior(X1, X2, cov_full, xlim, ylim)
xg, yg, Z_diag = grid_posterior(X1, X2, cov_diag, xlim, ylim)

mu1_est = X1.mean(axis=0)
mu2_est = X2.mean(axis=0)

fig = make_subplots(
    rows=1, cols=2,
    subplot_titles=["QDA (covarianza completa)", "Naïve Bayes (covarianza diagonal)"]
)

for col, cov, Z, title in [(1, cov_full, Z_full, "QDA"), (2, cov_diag, Z_diag, "NB")]:
    fig.add_trace(go.Contour(
        x=xg, y=yg, z=Z,
        colorscale=[[0, "#4C72B0"], [0.5, "white"], [1, "#DD8452"]],
        opacity=0.40,
        showscale=False,
        contours=dict(showlines=True, start=0.1, end=0.9, size=0.2,
                      coloring="fill")
    ), row=1, col=col)

    for X, mu_e, c_col, name in [
        (X1, mu1_est, "#2563eb", "Clase 1"),
        (X2, mu2_est, "#dc2626", "Clase 2")
    ]:
        fig.add_trace(go.Scatter(
            x=X[:, 0], y=X[:, 1],
            mode="markers",
            marker=dict(color=c_col, size=5, opacity=0.55),
            name=name, showlegend=(col == 1)
        ), row=1, col=col)

        ex, ey = ellipse_points(mu_e, cov if col == 1 else np.diag(np.diag(cov_full)))
        fig.add_trace(go.Scatter(
            x=ex, y=ey, mode="lines",
            line=dict(color=c_col, width=2.2, dash="solid"),
            showlegend=False
        ), row=1, col=col)

fig.update_layout(
    title="Supuesto de independencia condicional: QDA vs Naïve Bayes",
    template="plotly_white",
    height=420,
    legend=dict(orientation="h", yanchor="bottom", y=1.08, xanchor="right", x=1)
)
fig.update_xaxes(range=xlim, title_text="x₁")
fig.update_yaxes(range=ylim, title_text="x₂")
fig.show()
Figura 1: Comparación geométrica entre el supuesto gaussiano general (QDA, covarianza completa) y Naïve Bayes gaussiano (covarianza diagonal). QDA permite elipses rotadas arbitrariamente; NB solo puede representar elipses alineadas con los ejes.

3.1 Naïve Bayes multinomial para texto

Una de las versiones más usadas en clasificación de documentos es Naïve Bayes multinomial. En este caso, cada documento se representa como un vector de conteos:

\[ \mathbf{x}=(x_1,x_2,\dots,x_d), \]

donde \(x_k\) indica cuántas veces aparece la palabra \(k\) del vocabulario en el documento. Si \(\theta_{ik}\) representa la probabilidad de observar la palabra \(k\) dentro de documentos de la clase \(\omega_i\), entonces el modelo asigna la siguiente puntuación:

\[ g_i(\mathbf{x}) = \log P(\omega_i) + \sum_{k=1}^{d}x_k\log \theta_{ik}. \]

La decisión se toma escogiendo la clase con mayor puntuación:

\[ \hat{\omega}(\mathbf{x}) = \arg\max_i g_i(\mathbf{x}). \]

En problemas de texto, las probabilidades \(\theta_{ik}\) se estiman contando palabras en los documentos de entrenamiento. Si \(N_{ik}\) es el número de veces que aparece la palabra \(k\) en documentos de la clase \(\omega_i\), una estimación directa sería:

\[ \theta_{ik} = \frac{N_{ik}}{\sum_{r=1}^{d}N_{ir}}. \]

El problema es que si una palabra nunca aparece en una clase durante el entrenamiento, entonces \(N_{ik}=0\) y su probabilidad estimada sería cero. Como el modelo multiplica probabilidades, una sola palabra con probabilidad cero puede anular toda la puntuación de una clase. Para evitarlo se usa suavizado de Laplace:

\[ \theta_{ik} = \frac{N_{ik}+\alpha}{\sum_{r=1}^{d}N_{ir}+\alpha d}. \]

Cuando \(\alpha=1\), se interpreta como si agregáramos una aparición ficticia de cada palabra en cada clase. Esto evita probabilidades cero y vuelve más estable al clasificador.

Tip

En la práctica se trabaja con logaritmos, no con productos directos. Esto evita problemas numéricos cuando se multiplican muchas probabilidades pequeñas y convierte el producto de evidencias en una suma de evidencias.

3.2 Ejemplo manual: clasificación de un documento corto

Supongamos que queremos clasificar documentos en dos clases: spam y ham. Después de entrenar el modelo, podríamos obtener las siguientes probabilidades:

Palabra \(P(\text{palabra}\mid\text{spam})\) \(P(\text{palabra}\mid\text{ham})\)
free \(0.30\) \(0.05\)
prize \(0.25\) \(0.05\)
project \(0.05\) \(0.30\)

Si los priors son iguales,

\[ P(\text{spam})=P(\text{ham})=0.5, \]

y observamos el documento:

\[ \mathbf{x}=\text{``free project prize''}, \]

entonces las puntuaciones logarítmicas son:

\[ g_{\text{spam}}(\mathbf{x}) = \log(0.5)+\log(0.30)+\log(0.05)+\log(0.25), \]

\[ g_{\text{ham}}(\mathbf{x}) = \log(0.5)+\log(0.05)+\log(0.30)+\log(0.05). \]

Aunque la palabra project favorece a ham, las palabras free y prize favorecen a spam. El clasificador suma toda la evidencia y decide por la clase con mayor puntuación final.

Mostrar código
import numpy as np
import plotly.graph_objects as go

palabras = ["free", "prize", "project", "meeting", "offer", "money", "update", "win"]
p_spam = np.array([0.30, 0.25, 0.05, 0.03, 0.18, 0.22, 0.02, 0.15])
p_ham  = np.array([0.05, 0.05, 0.30, 0.28, 0.06, 0.04, 0.20, 0.02])

# Log-likelihood ratio como barra de fondo (discriminatividad)
llr = np.log(p_spam / p_ham)

fig = go.Figure()

fig.add_trace(go.Bar(
    name="P(palabra | spam)",
    x=palabras, y=p_spam,
    marker_color="#dc2626",
    marker_line=dict(color="#7f1d1d", width=1),
    opacity=0.85
))

fig.add_trace(go.Bar(
    name="P(palabra | ham)",
    x=palabras, y=p_ham,
    marker_color="#2563eb",
    marker_line=dict(color="#1e3a6e", width=1),
    opacity=0.85
))

# Anotación de log-ratio sobre cada palabra
for i, (palabra, ratio) in enumerate(zip(palabras, llr)):
    color = "#dc2626" if ratio > 0 else "#2563eb"
    symbol = "▲" if ratio > 0 else "▼"
    fig.add_annotation(
        x=palabra,
        y=max(p_spam[i], p_ham[i]) + 0.015,
        text=f"{symbol} {abs(ratio):.2f}",
        showarrow=False,
        font=dict(size=10, color=color),
        yanchor="bottom"
    )

fig.update_layout(
    title="Verosimilitudes por clase — ejemplo spam/ham",
    xaxis_title="Palabra",
    yaxis_title="P(palabra | clase)",
    barmode="group",
    template="plotly_white",
    height=400,
    legend=dict(orientation="h", yanchor="bottom", y=1.01, xanchor="right", x=1),
    annotations=[dict(
        x=0.5, y=-0.18, xref="paper", yref="paper",
        text="▲/▼ = log P(spam)/P(ham). Positivo → favorece spam; negativo → favorece ham",
        showarrow=False, font=dict(size=11, color="#64748b")
    )]
)
fig.show()
Figura 2: Verosimilitudes por clase para el ejemplo spam/ham. Las barras muestran P(palabra|clase) para cada término del vocabulario. Las palabras con mayor diferencia entre barras son las más discriminativas.

3.3 Ejemplo en Python: Naïve Bayes para texto

Mostrar código
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline

texts = [
    "free money offer now",
    "win money free prize",
    "meeting schedule project update",
    "project discussion and meeting notes",
    "free prize offer",
    "schedule the project meeting"
]
labels = ["spam", "spam", "ham", "ham", "spam", "ham"]

model = make_pipeline(
    CountVectorizer(),
    MultinomialNB(alpha=1.0)
)

model.fit(texts, labels)

new_docs = ["free project prize", "meeting update schedule"]
print(model.predict(new_docs))
print(model.predict_proba(new_docs))
['spam' 'ham']
[[0.20924284 0.79075716]
 [0.9501296  0.0498704 ]]

El parámetro alpha=1.0 introduce suavizado de Laplace. Este suavizado evita que una palabra ausente en el entrenamiento de una clase anule completamente la probabilidad de un documento.

También podemos inspeccionar el vocabulario aprendido y las probabilidades estimadas por clase:

Mostrar código
import numpy as np

vectorizer = model.named_steps["countvectorizer"]
clf = model.named_steps["multinomialnb"]

vocabulario = vectorizer.get_feature_names_out()

for clase, log_probs in zip(clf.classes_, clf.feature_log_prob_):
    print(f"\nClase: {clase}")
    for palabra, log_prob in zip(vocabulario, log_probs):
        print(f"{palabra:12s} P(palabra | clase) = {np.exp(log_prob):.3f}")

Clase: ham
and          P(palabra | clase) = 0.074
discussion   P(palabra | clase) = 0.074
free         P(palabra | clase) = 0.037
meeting      P(palabra | clase) = 0.148
money        P(palabra | clase) = 0.037
notes        P(palabra | clase) = 0.074
now          P(palabra | clase) = 0.037
offer        P(palabra | clase) = 0.037
prize        P(palabra | clase) = 0.037
project      P(palabra | clase) = 0.148
schedule     P(palabra | clase) = 0.111
the          P(palabra | clase) = 0.074
update       P(palabra | clase) = 0.074
win          P(palabra | clase) = 0.037

Clase: spam
and          P(palabra | clase) = 0.040
discussion   P(palabra | clase) = 0.040
free         P(palabra | clase) = 0.160
meeting      P(palabra | clase) = 0.040
money        P(palabra | clase) = 0.120
notes        P(palabra | clase) = 0.040
now          P(palabra | clase) = 0.080
offer        P(palabra | clase) = 0.120
prize        P(palabra | clase) = 0.120
project      P(palabra | clase) = 0.040
schedule     P(palabra | clase) = 0.040
the          P(palabra | clase) = 0.040
update       P(palabra | clase) = 0.040
win          P(palabra | clase) = 0.080

Este segundo bloque muestra de manera explícita qué aprendió el modelo: para cada clase, estima qué palabras son más probables. Por eso Naïve Bayes suele ser fácil de interpretar en texto. Una palabra como free debería tener mayor probabilidad bajo spam, mientras que una palabra como meeting debería tener mayor probabilidad bajo ham.

Importante

Naïve Bayes no necesita que el supuesto de independencia sea perfectamente cierto para clasificar bien. Lo importante es que las puntuaciones relativas separen razonablemente las clases. Por eso puede funcionar bien incluso cuando las palabras de un documento claramente no son independientes entre sí.

3.4 Variantes comunes de Naïve Bayes

La misma idea puede adaptarse a distintos tipos de características:

Variante Tipo de característica Ejemplo de uso
Bernoulli Naïve Bayes Presencia o ausencia de una característica Si una palabra aparece o no aparece
Multinomial Naïve Bayes Conteos discretos Frecuencias de palabras en documentos
Gaussian Naïve Bayes Variables continuas Mediciones físicas, señales o atributos numéricos

La diferencia entre estas variantes está en cómo se modela \(p(x_k\mid\omega_i)\). El principio de decisión sigue siendo el mismo: estimar una probabilidad por clase, sumar la evidencia de las características y elegir la clase con mayor posterior.

Aunque el supuesto de independencia suele ser falso, el clasificador puede funcionar muy bien en tareas de texto, filtrado de spam y clasificación de documentos, porque para decidir no siempre se necesita estimar perfectamente las probabilidades; basta con obtener fronteras de decisión útiles (McCallum y Nigam 1998).

4 Clasificadores bayesianos gaussianos

Una familia muy importante de clasificadores bayesianos se obtiene cuando las densidades condicionales de clase se modelan mediante distribuciones normales. Este supuesto es razonable cuando las características observadas resultan de la agregación de muchos factores independientes o débilmente dependientes, de acuerdo con la intuición asociada al teorema central del límite (Wasserman 2004; Bishop 2006).

Definición. Una variable aleatoria continua unidimensional \(x\) tiene distribución normal con media \(\mu\) y varianza \(\sigma^2\), denotada \(\mathcal{N}(\mu,\sigma^2)\), si su densidad es \[ p(x)=\frac{1}{\sqrt{2\pi\sigma^2}}\exp\left[-\frac{1}{2}\left(\frac{x-\mu}{\sigma}\right)^2\right] \tag{6}\]

En dimensión \(d\), la distribución normal multivariada queda determinada por un vector de medias \(\boldsymbol{\mu}\) y una matriz de covarianza \(\boldsymbol{\Sigma}\).

Definición. Una observación \(\mathbf{x}\in\mathbb{R}^d\) sigue una distribución normal multivariada \(\mathcal{N}(\boldsymbol{\mu},\boldsymbol{\Sigma})\) si \[ p(\mathbf{x}) = \frac{1}{(2\pi)^{d/2}|\boldsymbol{\Sigma}|^{1/2}} \exp\left[-\frac{1}{2}(\mathbf{x}-\boldsymbol{\mu})^T\boldsymbol{\Sigma}^{-1}(\mathbf{x}-\boldsymbol{\mu})\right] \tag{7}\]

4.1 Funciones discriminantes gaussianas

Si cada clase \(\omega_i\) tiene una densidad condicional gaussiana

\[ p(\mathbf{x}\mid\omega_i)=\mathcal{N}(\mathbf{x};\boldsymbol{\mu}_i,\boldsymbol{\Sigma}_i), \]

entonces la decisión MAP puede obtenerse maximizando la función discriminante

\[ g_i(\mathbf{x}) = \log p(\mathbf{x}\mid\omega_i) + \log P(\omega_i). \]

Eliminando términos constantes comunes a todas las clases:

\[ g_i(\mathbf{x}) = -\frac{1}{2}\log|\boldsymbol{\Sigma}_i| -\frac{1}{2}(\mathbf{x}-\boldsymbol{\mu}_i)^T\boldsymbol{\Sigma}_i^{-1}(\mathbf{x}-\boldsymbol{\mu}_i) +\log P(\omega_i). \]

Cuando las matrices de covarianza son distintas entre clases, la frontera \(g_i(\mathbf{x})=g_j(\mathbf{x})\) es cuadrática. Este caso se conoce como análisis discriminante cuadrático, o QDA (Hastie, Tibshirani, y Friedman 2009).

4.2 Casos especiales

4.2.1 Covarianza esférica común y priors iguales

Si \(\boldsymbol{\Sigma}_i=\sigma^2\mathbf{I}\) para todas las clases y los priors son iguales, entonces maximizar \(g_i(\mathbf{x})\) equivale a minimizar la distancia euclidiana a la media de la clase:

\[ \hat{\omega}(\mathbf{x}) = \arg\min_i \|\mathbf{x}-\boldsymbol{\mu}_i\|^2. \]

Este caso corresponde a un clasificador de distancia mínima o template matching.

4.2.2 Covarianza común y priors arbitrarios

Si \(\boldsymbol{\Sigma}_i=\boldsymbol{\Sigma}\) para todas las clases, pero los priors no necesariamente son iguales, la función discriminante se vuelve lineal:

\[ g_i(\mathbf{x}) = \mathbf{w}_i^T\mathbf{x}+b_i, \]

con

\[ \mathbf{w}_i=\boldsymbol{\Sigma}^{-1}\boldsymbol{\mu}_i, \qquad b_i=-\frac{1}{2}\boldsymbol{\mu}_i^T\boldsymbol{\Sigma}^{-1}\boldsymbol{\mu}_i+\log P(\omega_i). \]

Este caso corresponde al análisis discriminante lineal, o LDA (Fisher 1936; Hastie, Tibshirani, y Friedman 2009). El término \(\log P(\omega_i)\) desplaza la frontera de decisión hacia la clase con menor prior, porque se requiere más evidencia para asignar una observación a una clase poco frecuente.

4.3 Ejemplo en Python: clasificador gaussiano con covarianza común

import numpy as np
from sklearn.datasets import make_blobs
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix

X, y = make_blobs(
    n_samples=600,
    centers=[[-2, 0], [2, 1]],
    cluster_std=[1.2, 1.2],
    random_state=42
)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

classes = np.unique(y_train)
means = np.array([X_train[y_train == k].mean(axis=0) for k in classes])
priors = np.array([np.mean(y_train == k) for k in classes])

# Estimación de covarianza común por máxima verosimilitud agrupada.
S = np.zeros((X_train.shape[1], X_train.shape[1]))
for k, mu in zip(classes, means):
    Xk = X_train[y_train == k]
    centered = Xk - mu
    S += centered.T @ centered
S /= len(X_train)
S_inv = np.linalg.inv(S)

W = means @ S_inv.T
b = np.array([
    -0.5 * mu.T @ S_inv @ mu + np.log(pi)
    for mu, pi in zip(means, priors)
])

scores = X_test @ W.T + b
y_pred = classes[np.argmax(scores, axis=1)]

print("Accuracy:", accuracy_score(y_test, y_pred))
print(confusion_matrix(y_test, y_pred))
Accuracy: 0.95
[[85  5]
 [ 4 86]]

Este código implementa la regla discriminante lineal sin llamar directamente a una clase de scikit-learn. La implementación deja visible la estructura bayesiana: estimación de medias, covarianza común, priors y maximización de \(g_i(\mathbf{x})\).

Frontera de decisión gaussiana (LDA)
Mueve las medias, la desviación estándar y el prior para ver cómo se desplaza la frontera de decisión




4.4 Ejemplo interactivo 2: dos gaussianas en 1D

El siguiente ejemplo muestra el caso más simple de clasificación gaussiana: una sola variable \(x\) y dos clases. Cada clase se modela con una distribución normal distinta:

\[ p(x\mid\omega_1)=\mathcal{N}(x;\mu_1,\sigma_1^2), \qquad p(x\mid\omega_2)=\mathcal{N}(x;\mu_2,\sigma_2^2). \]

Para clasificar un valor observado \(x\), no comparamos únicamente las densidades \(p(x\mid\omega_i)\). La regla MAP compara las densidades ponderadas por sus probabilidades previas:

\[ p(x\mid\omega_1)P(\omega_1) \quad\text{contra}\quad p(x\mid\omega_2)P(\omega_2). \]

La frontera de decisión aparece en los valores de \(x\) donde ambas cantidades son iguales. En esos puntos el clasificador está indiferente entre las dos clases:

\[ p(x\mid\omega_1)P(\omega_1) = p(x\mid\omega_2)P(\omega_2). \]

El control interactivo modifica \(P(\omega_1)\). Cuando aumenta el prior de una clase, esa clase necesita menos evidencia local para ser seleccionada. Por eso la frontera se desplaza hacia la otra clase: se requiere evidencia más fuerte para vencer a la clase que ahora es más probable a priori.

Mostrar código
import numpy as np
import plotly.graph_objects as go

x = np.linspace(-5, 7, 600)
mu1, s1 = 0.0, 1.0
mu2, s2 = 2.2, 1.4

def normal_pdf(x, mu, sigma):
    return np.exp(-0.5*((x-mu)/sigma)**2)/(sigma*np.sqrt(2*np.pi))

frames = []
prior_grid = np.linspace(0.1, 0.9, 9)
for p1 in prior_grid:
    p2 = 1 - p1
    f1 = normal_pdf(x, mu1, s1)
    f2 = normal_pdf(x, mu2, s2)
    un1 = f1 * p1
    un2 = f2 * p2
    post1 = un1 / (un1 + un2)
    post2 = 1 - post1

    score = un1 - un2
    crossing_idx = np.where(np.diff(np.sign(score)) != 0)[0]
    boundary_x = x[crossing_idx] if len(crossing_idx) > 0 else np.array([])
    boundary_y = np.interp(boundary_x, x, np.maximum(un1, un2))

    frames.append(go.Frame(
        data=[
            go.Scatter(x=x, y=un1, mode="lines", name="p(x|ω1)P(ω1)"),
            go.Scatter(x=x, y=un2, mode="lines", name="p(x|ω2)P(ω2)"),
            go.Scatter(x=x, y=post1, mode="lines", name="P(ω1|x)", yaxis="y2"),
            go.Scatter(x=x, y=post2, mode="lines", name="P(ω2|x)", yaxis="y2"),
            go.Scatter(
                x=boundary_x,
                y=boundary_y,
                mode="markers",
                name="frontera MAP",
                marker=dict(size=9, symbol="x", color="black")
            )
        ],
        name=f"P(ω1)={p1:.1f}"
    ))

fig = go.Figure(data=frames[4].data, frames=frames)
fig.update_layout(
    title="Dos clases gaussianas en 1D: el prior desplaza la decisión",
    xaxis_title="x",
    yaxis=dict(title="p(x | ωᵢ)P(ωᵢ)"),
    yaxis2=dict(title="posterior", overlaying="y", side="right", range=[0, 1]),
    height=500,
    legend=dict(orientation="h", y=-0.25),
    margin=dict(b=120),
    updatemenus=[dict(
        type="buttons",
        buttons=[dict(label="Animar", method="animate", args=[None])]
    )],
    sliders=[dict(
        steps=[dict(method="animate", args=[[fr.name], {"mode":"immediate"}], label=fr.name) for fr in frames]
    )]
)
fig.show()
Figura 3: Densidades, posterior y frontera de decisión para dos clases gaussianas.

La gráfica muestra tres ideas importantes. Primero, las curvas \(p(x\mid\omega_i)P(\omega_i)\) son las cantidades que realmente se comparan para decidir. Segundo, las posteriores \(P(\omega_i\mid x)\) cambian suavemente con \(x\): cerca de la media de una clase, su posterior suele aumentar. Tercero, si las varianzas son distintas, puede aparecer más de una frontera de decisión, porque las curvas gaussianas pueden cruzarse en más de un punto.

4.5 Clasificación gaussiana en 2D

En dos dimensiones, cada observación ya no es un número sino un vector:

\[ \mathbf{x}=(x_1,x_2)^T. \]

Cada clase se describe con tres elementos:

  • Un centro \(\boldsymbol{\mu}_i\).
  • Una matriz de covarianza \(\Sigma_i\).
  • Un prior \(P(\omega_i)\).

El centro \(\boldsymbol{\mu}_i\) indica dónde se concentra la clase. La matriz \(\Sigma_i\) indica la forma de la nube: qué tan dispersa está, si está alargada en alguna dirección y si las variables \(x_1\) y \(x_2\) están correlacionadas. El prior \(P(\omega_i)\) desplaza la decisión hacia la clase menos frecuente, porque se necesita más evidencia para asignar una observación a una clase con menor probabilidad previa.

La función discriminante para cada clase es:

\[ g_i(\mathbf{x}) = \log p(\mathbf{x}\mid\omega_i) + \log P(\omega_i). \]

La frontera se obtiene donde las dos clases tienen la misma puntuación:

\[ g_1(\mathbf{x})=g_2(\mathbf{x}). \]

Si las covarianzas son iguales, la frontera es lineal. Si las covarianzas son distintas, la frontera puede ser curva, porque cada clase mide la cercanía a su centro con una geometría diferente.

4.6 Ejemplo interactivo 3: frontera en 2D

El siguiente ejemplo simula dos nubes gaussianas en el plano. El fondo indica la región donde gana cada clase y la línea de contorno muestra los puntos donde \(g_1(\mathbf{x})-g_2(\mathbf{x})=0\). A un lado de esa frontera se decide \(\omega_1\) y al otro lado se decide \(\omega_2\).

Mostrar código
import numpy as np
import plotly.graph_objects as go

np.random.seed(7)
n = 180
mu1 = np.array([-1.2, 0.0])
mu2 = np.array([1.2, 0.6])
S1 = np.array([[1.0, 0.55], [0.55, 1.0]])
S2 = np.array([[1.2, -0.45], [-0.45, 0.75]])
X1 = np.random.multivariate_normal(mu1, S1, n)
X2 = np.random.multivariate_normal(mu2, S2, n)

xx, yy = np.meshgrid(np.linspace(-5, 5, 180), np.linspace(-4, 5, 180))
G = np.c_[xx.ravel(), yy.ravel()]

def log_gauss(X, mu, S):
    invS = np.linalg.inv(S)
    diff = X - mu
    q = np.sum(diff @ invS * diff, axis=1)
    return -0.5*q - 0.5*np.log(np.linalg.det(S))

p1 = 0.5
score = log_gauss(G, mu1, S1) + np.log(p1) - log_gauss(G, mu2, S2) - np.log(1-p1)
Z = score.reshape(xx.shape)
decision_region = (Z > 0).astype(int)

fig = go.Figure()
fig.add_trace(go.Contour(
    x=xx[0],
    y=yy[:,0],
    z=decision_region,
    contours=dict(start=0, end=1, size=1, coloring="heatmap"),
    colorscale=[[0, "rgba(214,39,40,0.18)"], [1, "rgba(31,119,180,0.18)"]],
    showscale=False,
    line=dict(width=0),
    name="región de decisión"
))
fig.add_trace(go.Contour(
    x=xx[0],
    y=yy[:,0],
    z=Z,
    contours=dict(start=0, end=0, size=1),
    showscale=False,
    line=dict(color="black", width=3),
    name="frontera MAP"
))
fig.add_trace(go.Scatter(x=X1[:,0], y=X1[:,1], mode="markers", name="ω1", opacity=0.75))
fig.add_trace(go.Scatter(x=X2[:,0], y=X2[:,1], mode="markers", name="ω2", opacity=0.75))
fig.update_layout(
    title="Frontera MAP en 2D con covarianzas distintas",
    xaxis_title="x1",
    yaxis_title="x2",
    height=520,
    legend=dict(orientation="h", y=-0.15),
    margin=dict(b=90)
)
fig.show()
Figura 4: Frontera MAP para dos clases gaussianas en dos dimensiones.

La frontera no depende sólo de qué media esté más cerca en distancia euclidiana. También depende de la orientación y dispersión de cada nube. Por ejemplo, una clase muy dispersa puede asignar probabilidad razonable a puntos alejados de su centro, mientras que una clase muy compacta exige que los puntos estén cerca de su media. Esa diferencia aparece en la forma curva de la frontera.

4.7 Interpretación geométrica

La cantidad

\[ (\mathbf{x}-\boldsymbol{\mu}_i)^T\Sigma_i^{-1}(\mathbf{x}-\boldsymbol{\mu}_i) \]

es una distancia de Mahalanobis.

Significa que el modelo no mide sólo cercanía euclidiana, sino cercanía relativa a la forma de la nube de datos.

Tip

La covarianza codifica orientación, escala y correlación.

5 ¿Cómo evaluar el supuesto gaussiano?

El supuesto de normalidad rara vez debe aceptarse sin diagnóstico. En aplicaciones reales conviene combinar razonamiento sustantivo, visualización y pruebas estadísticas. La visualización suele ser más informativa que una prueba aislada, porque permite detectar asimetrías, colas pesadas, multimodalidad o valores atípicos.

Algunas herramientas útiles son:

  • histogramas y estimaciones de densidad;
  • diagramas de caja;
  • gráficos Q-Q;
  • pruebas de bondad de ajuste, como Kolmogorov–Smirnov, Shapiro–Wilk o pruebas basadas en \(\chi^2\).
Mostrar código
import numpy as np
import plotly.graph_objects as go

rng = np.random.default_rng(123)
x = rng.normal(loc=0, scale=1, size=300)

# Cuantiles teóricos y empíricos (implementación manual, sin scipy)
n = len(x)
x_sorted = np.sort(x)
# Probabilidades usando la fórmula de Filliben (i-0.375)/(n+0.25)
probs = (np.arange(1, n + 1) - 0.375) / (n + 0.25)
# Cuantiles teóricos de N(0,1) usando la aproximación racional de Abramowitz & Stegun
def norm_ppf(p):
    """Aproximación del cuantil normal estándar para 0 < p < 1."""
    p = np.clip(p, 1e-10, 1 - 1e-10)
    sign = np.where(p < 0.5, -1.0, 1.0)
    pp = np.where(p < 0.5, p, 1 - p)
    t = np.sqrt(-2 * np.log(pp))
    c0, c1, c2 = 2.515517, 0.802853, 0.010328
    d1, d2, d3 = 1.432788, 0.189269, 0.001308
    num = c0 + c1 * t + c2 * t**2
    den = 1 + d1 * t + d2 * t**2 + d3 * t**3
    return sign * (t - num / den)

q_teoricos = norm_ppf(probs)

# Línea de referencia: regresión sobre cuartiles Q1 y Q3
q25_emp = np.percentile(x_sorted, 25)
q75_emp = np.percentile(x_sorted, 75)
q25_teo = norm_ppf(np.array([0.25]))[0]
q75_teo = norm_ppf(np.array([0.75]))[0]
slope = (q75_emp - q25_emp) / (q75_teo - q25_teo)
intercept = q25_emp - slope * q25_teo
ref_x = np.array([q_teoricos.min(), q_teoricos.max()])
ref_y = intercept + slope * ref_x

fig = go.Figure()

fig.add_trace(go.Scatter(
    x=q_teoricos, y=x_sorted,
    mode="markers",
    marker=dict(color="#2563eb", size=5, opacity=0.6,
                line=dict(width=0.5, color="#1e3a6e")),
    name="Cuantiles empíricos",
    hovertemplate="Q teórico: %{x:.3f}<br>Q empírico: %{y:.3f}<extra></extra>"
))

fig.add_trace(go.Scatter(
    x=ref_x, y=ref_y,
    mode="lines",
    line=dict(color="#dc2626", width=2, dash="dash"),
    name="Línea de referencia"
))

fig.update_layout(
    title="Gráfico Q-Q frente a la normal estándar",
    xaxis_title="Cuantiles teóricos N(0,1)",
    yaxis_title="Cuantiles empíricos",
    template="plotly_white",
    height=400,
    legend=dict(orientation="h", yanchor="bottom", y=1.01, xanchor="right", x=1)
)
fig.show()

# Prueba de normalidad manual (estadístico de correlación de cuantiles)
r_qq = float(np.corrcoef(q_teoricos, x_sorted)[0, 1])
print(f"Correlación Q-Q (≈1 indica normalidad): r = {r_qq:.4f}")
Figura 5: Gráfico Q-Q comparando cuantiles empíricos de una muestra aleatoria contra cuantiles teóricos de una normal estándar. Los puntos sobre la línea diagonal indican buen ajuste gaussiano.
Correlación Q-Q (≈1 indica normalidad): r = 0.9966

Una advertencia importante es que las pruebas de normalidad son sensibles al tamaño de muestra. Con muestras grandes pueden detectar desviaciones pequeñas sin relevancia práctica; con muestras pequeñas pueden carecer de potencia. Por ello, el diagnóstico debe vincularse con el objetivo de clasificación.

6 Clasificación bayesiana de secuencias discretas

Los clasificadores bayesianos no se limitan a variables continuas. También pueden aplicarse a datos discretos estructurados, como texto, secuencias genómicas o series de símbolos. En biología computacional, un ejemplo clásico es la identificación de islas CpG en el genoma humano (Durbin et al. 1998).

Las islas CpG son regiones cortas del ADN donde el dinucleótido CG aparece con mayor frecuencia que en otras regiones. Una definición determinista clásica considera regiones de longitud mayor a 200 pares de bases, contenido \(G+C\) superior a 50% y razón observada/esperada de CpG mayor a 0.60 (Gardiner-Garden y Frommer 1987). Sin embargo, una perspectiva probabilística permite construir modelos generativos para regiones CpG y no CpG, y decidir con base en la razón de verosimilitudes.

Definición. El contenido \(G+C\) de una secuencia de longitud \(N\) se define como \[ \%GC = \frac{N(C)+N(G)}{N} \tag{8}\]

Definición. La razón CpG observada/esperada puede escribirse como \[ \text{CpG ratio}=\frac{N(CpG)/N}{(N(C)/N)(N(G)/N)} \tag{9}\]

6.1 Cadenas de Markov de primer orden

Una cadena de Markov de primer orden modela una secuencia suponiendo que el símbolo actual depende sólo del símbolo inmediatamente anterior. Para ADN, el alfabeto es \(\mathcal{A}=\{A,C,G,T\}\).

Definición. Una secuencia de variables aleatorias \(X_1,X_2,\dots,X_n\) forma una cadena de Markov de primer orden si \[ P(X_n=x_n\mid X_1=x_1,\dots,X_{n-1}=x_{n-1})=P(X_n=x_n\mid X_{n-1}=x_{n-1}) \tag{10}\]

Las probabilidades de transición se denotan

\[ a_{st}=P(X_i=t\mid X_{i-1}=s), \]

donde \(s,t\in\mathcal{A}\). Para una secuencia \(\mathbf{x}=(x_1, \dots,x_L)\), su probabilidad bajo una cadena de Markov es

\[ P(\mathbf{x}) = P(x_1)\prod_{i=2}^{L} a_{x_{i-1}x_i}. \]

6.2 Log-odds para discriminar secuencias

Supongamos que se entrenan dos cadenas de Markov: una para islas CpG, denotada \(+\), y otra para regiones no CpG, denotada \(-\). La puntuación log-odds de una secuencia es

Definición. La puntuación log-odds entre dos modelos de Markov para una secuencia \(\mathbf{x}\) es \[ S(\mathbf{x})=\log\frac{P(\mathbf{x}\mid +)}{P(\mathbf{x}\mid -)} =\sum_{i=2}^{L}\log\frac{a^+_{x_{i-1}x_i}}{a^-_{x_{i-1}x_i}} \tag{11}\]

Si \(S(\mathbf{x})\) supera un umbral, la secuencia se clasifica como CpG; si no, como no CpG. El umbral puede fijarse por priors, por costos de error o por validación empírica.

6.3 Ejemplo en Python: puntuación de una secuencia de ADN

import numpy as np

alphabet = ["A", "C", "G", "T"]
idx = {s: i for i, s in enumerate(alphabet)}

A_plus = np.array([
    [0.180, 0.274, 0.426, 0.120],
    [0.171, 0.368, 0.274, 0.188],
    [0.161, 0.339, 0.375, 0.125],
    [0.079, 0.355, 0.384, 0.182],
])

A_minus = np.array([
    [0.300, 0.205, 0.285, 0.210],
    [0.322, 0.298, 0.078, 0.302],
    [0.248, 0.246, 0.298, 0.208],
    [0.177, 0.239, 0.292, 0.292],
])

def markov_log_odds(seq, A_pos, A_neg):
    score = 0.0
    for prev, curr in zip(seq[:-1], seq[1:]):
        i, j = idx[prev], idx[curr]
        score += np.log(A_pos[i, j] / A_neg[i, j])
    return score

seq = "ACGCGCGTACGCGT"
score = markov_log_odds(seq, A_plus, A_minus)
normalized_score = score / (len(seq) - 1)

print("Score:", score)
print("Length-normalized score:", normalized_score)
print("Decision:", "CpG" if score > 0 else "non-CpG")
Score: 5.9991878024311385
Length-normalized score: 0.46147598480239527
Decision: CpG

La normalización por longitud permite comparar secuencias de tamaños distintos. En problemas reales se recomienda estimar las matrices de transición con suavizado, por ejemplo mediante pseudo-conteos, para evitar probabilidades cero.

Log-odds de Markov — clasificador CpG
El mapa muestra log(a⁺ₛₜ / a⁻ₛₜ) por dinucleótido. Haz clic en dinucleótidos para construir una secuencia y acumular la puntuación log-odds.
Secuencia: (vacía)
Puntuación = 0.000   Decisión:

7 Clasificadores bayesianos de riesgo mínimo

La decisión de error mínimo supone que todos los errores cuestan lo mismo. Sin embargo, en muchas aplicaciones esto no es cierto. En diagnóstico médico, por ejemplo, un falso negativo puede ser mucho más costoso que un falso positivo. En detección de fraude, bloquear una transacción legítima y dejar pasar una transacción fraudulenta tampoco tienen el mismo costo. Por ello se introduce una función de pérdida.

Definición. La pérdida \(\lambda(\alpha_i,\omega_j)\) representa el costo de tomar la decisión \(\alpha_i\) cuando el verdadero estado de la naturaleza es \(\omega_j\): \[ \lambda_{ij}=\lambda(\alpha_i,\omega_j) \tag{12}\]

Dada una observación \(\mathbf{x}\), el riesgo condicional de tomar la decisión \(\alpha_i\) es la pérdida esperada bajo la distribución posterior de las clases.

Definición. El riesgo condicional de decidir \(\alpha_i\) dado \(\mathbf{x}\) es \[ R(\alpha_i\mid\mathbf{x})=\sum_{j=1}^{c}\lambda(\alpha_i,\omega_j)P(\omega_j\mid\mathbf{x}) \tag{13}\]

La regla de riesgo mínimo selecciona la decisión con menor riesgo condicional:

\[ \alpha^*(\mathbf{x})=\arg\min_i R(\alpha_i\mid\mathbf{x}). \]

Cuando \(\lambda_{ii}=0\) y \(\lambda_{ij}=1\) para \(i\neq j\), la regla de riesgo mínimo se reduce a la regla de error mínimo. Es decir, la clasificación MAP es un caso particular de la teoría de decisión bayesiana (Berger 1985; Duda, Hart, y Stork 2001).

7.1 Dos clases con matriz de pérdidas

Para dos clases y dos decisiones, la matriz de pérdidas puede escribirse como

\[ \begin{array}{c|cc} & \omega_1 & \omega_2\\ \hline \alpha_1 & \lambda_{11} & \lambda_{12}\\ \alpha_2 & \lambda_{21} & \lambda_{22} \end{array} \]

La decisión \(\alpha_1\) se toma si

\[ \lambda_{11}P(\omega_1\mid\mathbf{x})+ \lambda_{12}P(\omega_2\mid\mathbf{x}) < \lambda_{21}P(\omega_1\mid\mathbf{x})+ \lambda_{22}P(\omega_2\mid\mathbf{x}). \]

De manera equivalente, el umbral del cociente de verosimilitudes se modifica por los costos:

\[ \ell(\mathbf{x}) \gtrless_{\alpha_2}^{\alpha_1} \frac{P(\omega_2)}{P(\omega_1)} \cdot \frac{\lambda_{12}-\lambda_{22}}{\lambda_{21}-\lambda_{11}}. \]

7.2 Ejemplo en Python: error mínimo frente a riesgo mínimo

Mostrar código
px_w1 = 0.2
px_w2 = 0.4
p_w1 = 0.9
p_w2 = 0.1

posterior, _ = clasificador_bayesiano(
    verosimilitudes=[px_w1, px_w2],
    priors=[p_w1, p_w2],
    clases=["normal", "enfermedad"]
)

post_w1, post_w2 = posterior

# Matriz de pérdida:
# alpha_1 = decidir normal, alpha_2 = decidir enfermedad
# omega_1 = normal, omega_2 = enfermedad
loss = np.array([
    [0, 6],
    [1, 0]
])

risks = loss @ posterior

print("Posterior:", posterior)
print("Conditional risks:", risks)
print("Minimum-error decision:", "normal" if post_w1 > post_w2 else "enfermedad")
print("Minimum-risk decision:", "normal" if np.argmin(risks) == 0 else "enfermedad")
Posterior: [0.81818182 0.18181818]
Conditional risks: [1.09090909 0.81818182]
Minimum-error decision: normal
Minimum-risk decision: enfermedad

El resultado muestra que la decisión de error mínimo puede ser “normal”, mientras que la decisión de riesgo mínimo puede ser “enfermedad” si el costo de no detectar la enfermedad es suficientemente alto. Esta diferencia es fundamental en sistemas de decisión sensibles al costo.

Mostrar código
import numpy as np
import plotly.graph_objects as go

# Matriz de pérdida: λ₁₂=6 (falso negativo), λ₂₁=1 (falso positivo)
lam12 = 6.0  # costo de decidir "normal" cuando hay enfermedad
lam21 = 1.0  # costo de decidir "enfermedad" cuando es normal

post2 = np.linspace(0, 1, 300)  # P(ω₂|x)
post1 = 1 - post2

# R(α₁|x) = λ₁₂·P(ω₂|x)   → riesgo de decidir "normal"
# R(α₂|x) = λ₂₁·P(ω₁|x)   → riesgo de decidir "enfermedad"
R1 = lam12 * post2
R2 = lam21 * post1

# Umbral: R1 = R2  →  lam12·p2 = lam21·(1-p2)  →  p2* = lam21/(lam12+lam21)
threshold = lam21 / (lam12 + lam21)

fig = go.Figure()

fig.add_trace(go.Scatter(
    x=post2, y=R1,
    mode="lines",
    line=dict(color="#2563eb", width=2.5),
    name="R(α₁|x) = decidir normal",
    hovertemplate="P(ω₂|x)=%{x:.3f}<br>Riesgo=%{y:.3f}<extra></extra>"
))

fig.add_trace(go.Scatter(
    x=post2, y=R2,
    mode="lines",
    line=dict(color="#dc2626", width=2.5),
    name="R(α₂|x) = decidir enfermedad",
    hovertemplate="P(ω₂|x)=%{x:.3f}<br>Riesgo=%{y:.3f}<extra></extra>"
))

# Región donde conviene α₁ (decidir normal)
fig.add_vrect(x0=0, x1=threshold, fillcolor="#2563eb", opacity=0.07,
              line_width=0, annotation_text="decidir normal",
              annotation_position="top left",
              annotation_font=dict(color="#2563eb", size=11))

# Región donde conviene α₂ (decidir enfermedad)
fig.add_vrect(x0=threshold, x1=1, fillcolor="#dc2626", opacity=0.07,
              line_width=0, annotation_text="decidir enfermedad",
              annotation_position="top right",
              annotation_font=dict(color="#dc2626", size=11))

# Umbral
fig.add_vline(x=threshold, line_dash="dash", line_color="#475569", line_width=1.8,
              annotation_text=f"p* = λ₂₁/(λ₁₂+λ₂₁) = {threshold:.3f}",
              annotation_position="bottom right",
              annotation_font=dict(size=11))

fig.update_layout(
    title=f"Riesgo condicional por acción (λ₁₂={lam12:.0f}, λ₂₁={lam21:.0f})",
    xaxis_title="P(ω₂|x) — probabilidad posterior de enfermedad",
    yaxis_title="Riesgo condicional R(αᵢ|x)",
    template="plotly_white",
    height=400,
    legend=dict(orientation="h", yanchor="bottom", y=1.01, xanchor="right", x=1)
)
fig.show()
Figura 6: Riesgo condicional de cada acción como función de la probabilidad posterior P(ω₂|x). La intersección de las dos curvas define el umbral óptimo de decisión bajo la matriz de pérdida especificada.
Riesgo mínimo vs error mínimo
Ajusta los costos de la matriz de pérdida y la posterior P(ω₂|x) para ver cuándo ambas reglas difieren



8 Minimizar un tipo de error condicionado al otro

En algunos problemas no se busca minimizar una suma ponderada de errores, sino controlar explícitamente un tipo de error mientras se minimiza otro. Esta idea aparece en pruebas de hipótesis, detección de señales y clasificación con restricciones operativas. El caso clásico es minimizar la probabilidad de falso negativo sujeto a una cota sobre la probabilidad de falso positivo, o viceversa. La regla resultante se relaciona con el lema de Neyman–Pearson (Neyman y Pearson 1933).

Para dos clases, sea \(\mathcal{R}_1\) la región donde se decide \(\omega_1\) y \(\mathcal{R}_2\) la región donde se decide \(\omega_2\). Las probabilidades de error condicionales pueden escribirse como

\[ P_1(e)=\int_{\mathcal{R}_2}p(\mathbf{x}\mid\omega_1)d\mathbf{x}, \qquad P_2(e)=\int_{\mathcal{R}_1}p(\mathbf{x}\mid\omega_2)d\mathbf{x}. \]

Si se desea minimizar \(P_1(e)\) sujeto a \(P_2(e)=\epsilon\), se obtiene una regla basada en umbrales del cociente de verosimilitudes:

\[ \frac{p(\mathbf{x}\mid\omega_1)}{p(\mathbf{x}\mid\omega_2)} \gtrless \lambda. \]

El valor de \(\lambda\) se ajusta para cumplir la restricción sobre el error. En términos prácticos, esto equivale a desplazar el umbral de decisión hasta alcanzar una sensibilidad, especificidad, tasa de falsos positivos o tasa de falsos negativos deseada.

8.1 8.1 Ejemplo en Python: elegir umbral por restricción de falsos positivos

Mostrar código
from sklearn.metrics import roc_curve

rng = np.random.default_rng(7)
# Scores altos favorecen la clase positiva.
y_true = np.r_[np.zeros(500), np.ones(500)]
scores = np.r_[rng.normal(0, 1, 500), rng.normal(2, 1, 500)]

fpr, tpr, thresholds = roc_curve(y_true, scores)

max_fpr = 0.05
valid = np.where(fpr <= max_fpr)[0]
best_idx = valid[np.argmax(tpr[valid])]

print("Threshold:", thresholds[best_idx])
print("FPR:", fpr[best_idx])
print("TPR:", tpr[best_idx])
Threshold: 1.4606730052576595
FPR: 0.05
TPR: 0.726

Este enfoque es más adecuado que maximizar accuracy cuando una clase es rara o cuando los errores tienen consecuencias asimétricas.

9 Criterio minimax cuando los priors son desconocidos

La regla bayesiana clásica requiere conocer o estimar los priors \(P(\omega_i)\). Cuando estos priors son inciertos, una alternativa conservadora es el criterio minimax. La idea es escoger la regla de decisión que minimiza el peor riesgo posible bajo los priors admisibles (Berger 1985; Duda, Hart, y Stork 2001).

Definición. Una regla minimax selecciona la decisión o regla de decisión \(\alpha\) que minimiza el máximo riesgo posible: \[ \alpha_{\text{minimax}} = \arg\min_{\alpha}\max_{\pi\in\Pi} R(\alpha;\pi) \tag{14}\]

Aquí \(\pi\) representa el vector de priors y \(\Pi\) el conjunto de priors considerados plausibles. En problemas de dos clases, puede estudiarse el riesgo como función de \(P(\omega_1)\), ya que \(P(\omega_2)=1-P(\omega_1)\). La regla minimax evita depender de una estimación puntual del prior y protege contra escenarios adversos. Su desventaja es que puede ser demasiado conservadora si sí existe información confiable sobre las frecuencias reales de clase.

10 Recomendaciones prácticas

La clasificación bayesiana es conceptualmente elegante, pero su desempeño depende de la calidad del modelo probabilístico. Algunas recomendaciones prácticas son:

  1. Estimar priors con cuidado. En datos desbalanceados, los priors empíricos pueden reflejar sesgos de muestreo y no prevalencias reales.
  2. Trabajar en escala logarítmica para evitar problemas numéricos.
  3. Validar supuestos distribucionales cuando se usan modelos gaussianos.
  4. Usar suavizado en modelos discretos, especialmente en texto y secuencias.
  5. Elegir la métrica o función de pérdida de acuerdo con el problema real, no sólo con accuracy.
  6. Separar conceptualmente probabilidad, decisión y costo: una buena estimación probabilística no implica automáticamente una buena regla de decisión si el umbral no corresponde al costo operativo.

11 Conclusiones

Los clasificadores bayesianos proporcionan un marco unificado para tomar decisiones bajo incertidumbre. La regla MAP minimiza la probabilidad de error cuando los errores tienen el mismo costo, mientras que la regla de riesgo mínimo permite incorporar pérdidas asimétricas. Naïve Bayes muestra cómo construir clasificadores prácticos en alta dimensión mediante una suposición de independencia condicional. Los clasificadores gaussianos muestran cómo los supuestos sobre las densidades condicionales determinan la geometría de las fronteras de decisión: fronteras lineales bajo covarianza común y fronteras cuadráticas bajo covarianzas distintas. Por otro lado, los modelos de Markov muestran que el mismo principio puede aplicarse a secuencias discretas con dependencia entre símbolos.

La idea central del capítulo es que clasificar no consiste únicamente en trazar una frontera, sino en articular tres elementos: un modelo de cómo se generan los datos, una actualización probabilística de las creencias y una regla de decisión adaptada al objetivo. Esta separación hace que los métodos bayesianos sean especialmente útiles en problemas donde la incertidumbre, los costos de error y la interpretabilidad son componentes esenciales del sistema de aprendizaje.