Modelo de Bayes Naïve Gaussiano¶
El modelo de Bayes Naïve Gaussiano es un modelo de red bayesiana simple, en donde la variable $Y$ (la clase) es el nodo padre de un conjunto de nodos $X_i$ que representan una evidencia. De tal forma, que dado un vector de evidencia $x = \begin{pmatrix} x_1 & x_2 \cdots & x_d \end{pmatrix}$ determina la probabilidad de que pertenezca a una de las clases $y$, valores que puede tomar la variable $Y$.
En el modelo Gaussiano, la estimación de las probabilidades se hace por medio de asumir que los datos tienen una distribución gaussiana; en este sentido, tenemos que estimar, a partir de datos de entrenamiento, los valores de las medias y las varianzas de cada una de las clases.
Algoritmo de Bayes Naïve Gaussiano¶
La implementación del algoritmo se hace a partir de una clase; esta clase guardará los priors: estos se pueden asignar en un principio o estimarse a partir de las clases. Estos priors responden a las tablas de probabilidad para la variable $Y$, $p(Y)$.
También estimaremos la tabla condicional para cada una de las variables $X_i$, que sólo tienen como padre a la variable $Y$. De tal forma, que estimaremos las probabilidades $p(X_i|Y)$; se asume que estas probabilidades son gaussianas, y por tanto estimaremos los parámetros de la distribución $\mu_y$ (las medias de las clases $y$), así como $\sigma^2_y$ (las varianzas de las clases $y$). Por tanto, las probabilidades que se estimarán serán:
$$p(X_i = x_i | Y=y) = \frac{1}{\sqrt{2\pi\sigma^2}}e^{-\frac{(x_i - \mu_y)^2}{2 \sigma_y^2}}$$
De tal forma, que la probabilidad conjunta estará determinada como:
$$p(Y=y, X_1=x_1, \cdots, X_d=x_d) = p(Y=y) \prod_{i=1}^d p(X_i = x_i | Y=y)$$
Y para poder decidir sobre una clase, simplemente se elegirá aquella clase que maximice la probabilidad estimada:
$$\hat{y} = \arg\max_y p(Y=y, X_1=x_1, \cdots, X_d=x_d)$$
import numpy as np
from collections import Counter
class GaussianNaiveBayes(object):
"""Clase para el modelo de Bayes ingenuo Gaussiano"""
def __init__(self, priors={}, labels=[]):
#Si hay priors predefinidos se consideran
#De otra forma, calcula los priors
self.priors = priors
#Guarda nombres de clases
self.labels = list(labels)
def fit(self, x,y):
"""Función para estimación de las probabilidades. Estima medias y varianzas"""
self.n, self.m = x.shape
self.samples = Counter(y)
self.num_cats = len(self.samples.keys())
#Si se ha dado nombres categóricos a las clases, los toma en cuenta
if self.labels != []:
self.labels = {k:name for k,name in enumerate(self.labels)}
else:
self.labels = {k:k for k in self.samples.keys()}
#Estima los priors
if self.priors == {}:
n = sum(self.samples.values())
self.priors = {i:f/n for i,f in self.samples.items()}
#Estima las medias
self.means = np.zeros((self.num_cats,self.m))
for x_i, y_i in zip(x,y):
self.means[y_i] += x_i/self.samples[y_i]
#Estima las varianzas
self.sigmas = np.zeros((self.num_cats,self.m))
for x_i, y_i in zip(x,y):
self.sigmas[y_i] += (x_i - self.means[y_i])**2/(self.samples[y_i]-1)
def predict_proba(self, x):
"""Predice la probabilidad en base a la función gaussiana"""
diff = (x-self.means)**2
value = diff/(2*self.sigmas)
probs = np.prod( np.exp(-value), axis=1 )
return {label:probs[k] for k,label in self.labels.items()}
def predict(self, x):
"""Devuelve la clase con mayor probabilidad"""
probs = self.predict_proba(x)
return max(probs, key=probs.get)
Conjunto de datos a utilizar¶
Para probar nuestro modelo de Bayes naïve gaussiano, utilizamos un conjunto de datos que se puede encontrar en la paquetería sklearn. Este conjunto de datos, iris, contiene 3 clases correspondientes a especies de iris: setosa, vesicolor y virginica.
Cada muestra está caracterizada por un vector de cuatro variables:
- La longitud de la sepa.
- Anchura de la sepa.
- Longitud del pétalo.
- Anchura del pétalo.
A partir de estas cuatro variables, estimaremos las medias y las varianzas de cada una de ellas, de tal forma que se pueda calcular la probabilidad de que un vector $x$ caracterizado por estas cuatro variables pertenezca a alguno de los tres tipos de iris.
Para probar qué también trabaja el modelo, separaremos los datos en los de entrenamiento y evaluación. El entrenamiento nos ayudará a estimar la media y la varianza. La evaluación se usará para estimar las clases y poder evaluar qué también lo hace el modelo.
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
#Carga los datos tabulares
dataset = load_iris()
x = dataset.data
y = dataset.target
#Separa los datos en entrenamiento y evaluación
x_train, x_test, y_train, y_test = train_test_split(
x, y, test_size=0.3, random_state=123
)
#Imprime las clases
print('Clases de los datos:\n{}'.format(dataset.target_names))
#Muestra los valores de los datos
pd.DataFrame(data=x_train, columns=dataset.feature_names)
Clases de los datos: ['setosa' 'versicolor' 'virginica']
sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | |
---|---|---|---|---|
0 | 5.8 | 2.8 | 5.1 | 2.4 |
1 | 6.3 | 3.4 | 5.6 | 2.4 |
2 | 5.5 | 2.3 | 4.0 | 1.3 |
3 | 5.1 | 3.8 | 1.5 | 0.3 |
4 | 4.4 | 3.0 | 1.3 | 0.2 |
... | ... | ... | ... | ... |
100 | 5.1 | 3.5 | 1.4 | 0.3 |
101 | 5.1 | 2.5 | 3.0 | 1.1 |
102 | 5.6 | 3.0 | 4.5 | 1.5 |
103 | 6.2 | 2.8 | 4.8 | 1.8 |
104 | 7.2 | 3.6 | 6.1 | 2.5 |
105 rows × 4 columns
Estimación de parámetros¶
Procedemos a cargar el modelo y aplicamos la función fit (que al igual que con el modelo frecuentista, estima los parámetros); en este caso, los parámetros son valores de la media y la varianza de cada una de las variables en cada una de las tres clases:
#Carga el modelo asignando etiquetas a las clases
model = GaussianNaiveBayes(labels=dataset.target_names)
#Estima el modelo
model.fit(x,y)
Podemos observar cuáles son los valores obtenidos para las varianzas:
print('\t\tValores de las medias')
pd.DataFrame(data=model.means, index=dataset.target_names, columns=dataset.feature_names)
Valores de las medias
sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | |
---|---|---|---|---|
setosa | 5.006 | 3.428 | 1.462 | 0.246 |
versicolor | 5.936 | 2.770 | 4.260 | 1.326 |
virginica | 6.588 | 2.974 | 5.552 | 2.026 |
Asimismo, podemos ver cuáles son los valores que el modelo ha estimado para las varianzas:
print('\t\tValores de las varianzas')
pd.DataFrame(data=model.sigmas, index=dataset.target_names, columns=dataset.feature_names)
Valores de las varianzas
sepal length (cm) | sepal width (cm) | petal length (cm) | petal width (cm) | |
---|---|---|---|---|
setosa | 0.124249 | 0.143690 | 0.030159 | 0.011106 |
versicolor | 0.266433 | 0.098469 | 0.220816 | 0.039106 |
virginica | 0.404343 | 0.104004 | 0.304588 | 0.075433 |
Evaluación del modelo¶
Con los datos de evaluación, podemos ahora usar el modelo con los parámetros obtenidos para poder predecir a que clase pertenece cada uno de los elementos en este conjunto de datos. Como podemos observar, el accuracy o exactitud es de 0.98. Lo que nos está diciendo que el modelo estima acertadamente el 98% de las clases.
from sklearn.metrics import classification_report
#Obtiene las predicciones
y_pred = [model.predict(x_i) for x_i in x_test]
#Imprime el reporte de clasificación
print(classification_report(y_pred, [model.labels[y_i] for y_i in y_test]))
precision recall f1-score support setosa 1.00 1.00 1.00 18 versicolor 1.00 0.83 0.91 12 virginica 0.88 1.00 0.94 15 accuracy 0.96 45 macro avg 0.96 0.94 0.95 45 weighted avg 0.96 0.96 0.95 45