El algoritmo Naive Bayes en Python con Scikit-Learn

Al estudiar Probabilidad & Estadística, uno de los primeros y más importantes teoremas que aprenden los estudiantes es el Teorema de Bayes. Este teorema es la base de...

Al estudiar Probabilidad y Estadística, uno de los primeros y más importantes teoremas que aprenden los estudiantes es el Teorema de Bayes. Este teorema es la base del razonamiento deductivo, que se centra en determinar la probabilidad de que ocurra un evento en función del conocimiento previo de las condiciones que podrían estar relacionadas con el evento.

El Clasificador bayesiano ingenuo ​​trae el poder de este teorema al aprendizaje automático, construyendo un clasificador muy simple pero poderoso. En este artículo, veremos una descripción general de cómo funciona este clasificador, qué aplicaciones adecuadas tiene y cómo usarlo en solo unas pocas líneas de Python y la biblioteca Scikit-Learn.

Teoría detrás del teorema de Bayes {#teoría detrás del teorema de Bayes}

Si estudiaste Ciencias de la Computación, Matemáticas o cualquier otro campo relacionado con la estadística, es muy probable que en algún momento te hayas topado con la siguiente fórmula:

1
P(H|E) = (P(E|H) * P(H)) / P(E)

dónde

  • P(H|E) es la probabilidad de la hipótesis H dado el evento E, una probabilidad posterior.
  • P(E|H) es la probabilidad del evento E dado que la hipótesis H es verdadera.
  • P(H) es la probabilidad de que la hipótesis H sea cierta (independientemente de cualquier evento relacionado), o probabilidad previa de H.
  • P(E) es la probabilidad de que ocurra el evento (independientemente de la hipótesis).

Este es el teorema de Bayes. A primera vista puede ser difícil entenderlo, pero es muy intuitivo si lo exploramos a través de un ejemplo:

Digamos que nos interesa saber si un correo electrónico que contiene la palabra sexo (evento) es spam (hipótesis). Si volvemos a la descripción del teorema, este problema se puede formular como:

1
P(class=SPAM|contains="sex") = (P(contains="sex"|class=SPAM) * P(class=SPAM)) / P(contains="sex")

que en lenguaje sencillo es: La probabilidad de que un correo electrónico que contenga la palabra sexo sea spam es igual a la proporción de correos electrónicos SPAM que contengan la palabra sexo multiplicada por la proporción de correos electrónicos que sean spam y dividida por la proporción de correos electrónicos que contienen la palabra sexo.

Vamos a diseccionar esto pieza por pieza:

  • P(class=SPAM|contains="sex") es la probabilidad de que un correo electrónico sea SPAM dado que este correo contiene la palabra sexo. Esto es lo que nos interesa predecir.
  • P(contains="sex"|class=SPAM) es la probabilidad de que un correo electrónico contenga la palabra sexo dado que este correo electrónico ha sido reconocido como SPAM. Estos son nuestros datos de entrenamiento, que representan la correlación entre un correo electrónico que se considera SPAM y dicho correo electrónico que contiene la palabra sexo.
  • P(class=SPAM) es la probabilidad de que un correo electrónico sea SPAM (sin conocimiento previo de las palabras que contiene). Esta es simplemente la proporción de correos electrónicos que son SPAM en todo nuestro conjunto de capacitación. Multiplicamos por este valor porque nos interesa saber qué tan significativa es la información relacionada con los correos electrónicos SPAM. Si este valor es bajo, la importancia de cualquier evento relacionado con correos electrónicos SPAM también será baja.
  • P(contains="sex") es la probabilidad de que un correo electrónico contenga la palabra sexo. Esta es simplemente la proporción de correos electrónicos que contienen la palabra sexo en todo nuestro conjunto de entrenamiento. Dividimos por este valor porque cuanto más exclusiva es la palabra sexo, más importante es el contexto en el que aparece. Por lo tanto, si este número es bajo (la palabra aparece muy raramente), puede ser un gran indicador de que en los casos en que aparece, es una característica relevante para analizar.

En resumen, el Teorema de Bayes nos permite hacer una deducción razonada de eventos que suceden en el mundo real a partir del conocimiento previo de las observaciones que lo pueden implicar. Para aplicar este teorema a cualquier problema, necesitamos calcular los dos tipos de probabilidades que aparecen en la fórmula.

Probabilidades de clase {#probabilidades de clase}

En el teorema, P(A) representa las probabilidades de cada evento. En el Clasificador Naive Bayes, podemos interpretar estas Probabilidades de Clase simplemente como la frecuencia de cada instancia del evento dividida por el número total de instancias. Por ejemplo, en el ejemplo anterior de detección de spam, P(class=SPAM) representa el número de correos electrónicos clasificados como spam dividido por la suma de todas las instancias (esto es spam + no spam)

1
P(class=SPAM) = count(class=SPAM) / (count(class=notSPAM) + count(class=SPAM))

Probabilidades condicionales

En el teorema, ‘P(A|B)’ representa las probabilidades condicionales de un evento ‘A’ dado otro evento ‘B’. En el Clasificador Naive Bayes, estos codifican la probabilidad posterior de que ‘A’ ocurra cuando ‘B’ es verdadera.

Para el ejemplo de spam, P(class=SPAM|contains="sex") representa el número de instancias en las que un correo electrónico se considera spam y contiene la palabra sexo, dividido por el número total de mensajes de correo electrónico. correos que contienen la palabra sexo:

1
P(class=SPAM|contains="sex") = count(class=SPAM & contains=sex) / count(contains=sex)

Aplicaciones

La aplicación del Clasificador Naive Bayes se ha mostrado exitosa en diferentes escenarios. Un caso de uso clásico es la clasificación de documentos: determinar si un documento dado corresponde a ciertas categorías. No obstante, esta técnica tiene sus ventajas y limitaciones.

Ventajas

  • Naive Bayes es un algoritmo simple y fácil de implementar. Debido a esto, podría superar a modelos más complejos cuando la cantidad de datos es limitada.
  • Naive Bayes funciona bien con datos numéricos y categóricos. También se puede utilizar para realizar una regresión utilizando Gaussian Naive Bayes.

Limitaciones

  • Dada la construcción del teorema, no funciona bien cuando falta cierta combinación de valores en los datos de entrenamiento. En otras palabras, si no tiene ocurrencias de una etiqueta de clase y un determinado valor de atributo juntos (por ejemplo, class="spam", contains="$$$"), entonces la estimación de probabilidad basada en la frecuencia ser cero Dada la suposición de independencia condicional de Naive-Bayes, cuando se multiplican todas las probabilidades se obtiene cero.

  • Naive Bayes funciona bien siempre que las categorías se mantengan simples. Por ejemplo, funciona bien para problemas relacionados con palabras clave como características (p. ej., detección de spam), pero no funciona cuando la relación entre palabras es importante (p. ej., análisis de sentimientos).

Demostración en Scikit-Learn

¡Es tiempo de demostración! Usaremos Python 3 junto con Scikit-Learn para construir un detector de SPAM muy simple para mensajes SMS (para aquellos de ustedes que son jóvenes, esto es lo que usábamos para enviar mensajes en la Edad Media). Puede encontrar y descargar el conjunto de datos desde este enlace.

Necesitaremos tres bibliotecas que facilitarán mucho nuestra codificación: scikit-learn, pandas y nltk. Puede usar pip o conda para instalarlos.

Cargando los datos

SMS Spam Collection v.1 es un conjunto de mensajes SMS etiquetados que se han recopilado para la investigación de SMS Spam. Contiene un conjunto de mensajes SMS en inglés de 5.574 mensajes, etiquetados según sean ham (legítimos) o spam. La distribución es de un total de 4.827 mensajes SMS legítimos (86,6%) y un total de 747 (13,4%) mensajes de spam.

Si abrimos el conjunto de datos, veremos que tiene el formato [etiqueta] [pestaña] [mensaje], que se parece a esto:

1
2
3
4
5
6
7
ham Go until jurong point, crazy.. Available only in bugis n great world la e buffet... Cine there got amore wat...

ham Ok lar... Joking wif u oni...

spam    Free entry in 2 a wkly comp to win FA Cup final tkts 21st May 2005. Text FA to 87121 to receive entry question(std txt rate)T&C's apply 08452810075over18's

ham U dun say so early hor... U c already then say...

Para cargar los datos, podemos usar el método read_table de Pandas' Dataframe. Esto nos permite definir un separador (en este caso, una pestaña) y cambiar el nombre de las columnas en consecuencia:

1
2
3
4
5
6
import pandas as pd

df = pd.read_table('SMSSpamCollection',
                   sep='\t', 
                   header=None,
                   names=['label', 'message'])

Preprocesamiento

Una vez que tenemos nuestros datos listos, es hora de hacer un preprocesamiento. Nos centraremos en eliminar la variación inútil para nuestra tarea en cuestión. Primero, tenemos que convertir las etiquetas de cadenas a valores binarios para nuestro clasificador:

1
df['label'] = df.label.map({'ham': 0, 'spam': 1})

Segundo, convierta todos los caracteres del mensaje a minúsculas:

1
df['message'] = df.message.map(lambda x: x.lower())

Tercero, elimine cualquier puntuación:

1
df['message'] = df.message.str.replace('[^\w\s]', '')

Cuarto, tokenice los mensajes en palabras individuales usando nltk. Primero, tenemos que importar y descargar el tokenizador desde la consola:

1
2
import nltk
nltk.download()

Aparecerá una ventana de instalación. Vaya a la pestaña "Modelos" y seleccione "punkt" en la columna "Identificador". Luego haga clic en "Descargar" e instalará los archivos necesarios. ¡Entonces debería funcionar! Ahora podemos aplicar la tokenización:

1
df['message'] = df['message'].apply(nltk.word_tokenize)

Quinto, realizaremos algunas derivación de palabras. La idea de la lematización es normalizar nuestro texto para que todas las variaciones de palabras tengan el mismo significado, independientemente del tiempo. Uno de los algoritmos de derivación más populares es el Stemmer de portero:

1
2
3
4
5
from nltk.stem import PorterStemmer

stemmer = PorterStemmer()
 
df['message'] = df['message'].apply(lambda x: [stemmer.stem(y) for y in x])

Finalmente, transformaremos los datos en ocurrencias, que serán las características que alimentaremos a nuestro modelo:

1
2
3
4
5
6
7
from sklearn.feature_extraction.text import CountVectorizer

# This converts the list of words into space-separated strings
df['message'] = df['message'].apply(lambda x: ' '.join(x))

count_vect = CountVectorizer()
counts = count_vect.fit_transform(df['message'])

Podríamos dejarlo como el simple conteo de palabras por mensaje, pero es mejor usar Término Frecuencia Inversa Documento Frecuencia, más conocido como tf-idf:

1
2
3
4
5
from sklearn.feature_extraction.text import TfidfTransformer

transformer = TfidfTransformer().fit(counts)

counts = transformer.transform(counts)

Entrenando al modelo {#entrenando al modelo}

Ahora que hemos realizado la extracción de características de nuestros datos, es hora de construir nuestro modelo. Comenzaremos dividiendo nuestros datos en conjuntos de entrenamiento y prueba:

1
2
3
from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(counts, df['label'], test_size=0.1, random_state=69)

Luego, todo lo que tenemos que hacer es inicializar el Clasificador Naive Bayes y ajustar los datos. Para problemas de clasificación de texto, el Multinomial Naive Bayes Classifier es muy adecuado:

1
2
3
from sklearn.naive_bayes import MultinomialNB

model = MultinomialNB().fit(X_train, y_train)

Evaluación del modelo

Una vez que hayamos armado nuestro clasificador, podemos evaluar su desempeño en el conjunto de prueba:

1
2
3
4
5
import numpy as np

predicted = model.predict(X_test)

print(np.mean(predicted == y_test))

¡Felicidades! ¡Nuestro sencillo clasificador Naive Bayes tiene una precisión del 98,2 % con este conjunto de prueba específico! Pero no es suficiente solo proporcionar la precisión, ya que nuestro conjunto de datos está desequilibrado en lo que respecta a las etiquetas (86,6% legítimo en contraste con el 13,4% de spam). Podría suceder que nuestro clasificador se ajuste demasiado a la clase legítima mientras ignora la clase de spam. Para resolver esta incertidumbre, echemos un vistazo a la matriz de confusión:

1
2
3
from sklearn.metrics import confusion_matrix

print(confusion_matrix(y_test, predicted))

El método confusion_matrix imprimirá algo como esto:

1
2
[[478   4]
[   6  70]]

Como podemos ver, la cantidad de errores está bastante equilibrada entre mensajes legítimos y spam, con 4 mensajes legítimos clasificados como spam y 6 mensajes spam clasificados como legítimos. En general, estos son muy buenos resultados para nuestro clasificador simple.

Conclusión

En este artículo, hemos visto un curso intensivo sobre la teoría y la práctica del Clasificador Naive Bayes. Hemos creado un clasificador de bayesiano ingenuo multimodal simple que logra una precisión del 98,2 % en la detección de spam para mensajes SMS.