Blog

ES
Clasificar reseñas de productos con Naive Bayes

Clasificar reseñas de productos con Naive Bayes

15 min de lectura

Ejercicio paso a paso de como clasificar reseñas de clientes a productos usando NPL con el clasificador Naive Bayes para decir si son positivos o negativos.

He estado adentrándome en el Machine Learning y en este ejercicio haré uso del clasificador de Naive Bayes para clasificar reviews de usuarios en positivas o negativas. Naive Bayes es una muy buena opción para comenzar con este ejercicio, pero es importante decir que tecnologías como los Transformers son mejor opción hoy en día.

Naive Bayes

Naive Bayes es un algoritmo de clasificación supervisada para predecir a qué categoría pertenece un dato, basándose en probabilidades. El nombre proviene de la palabra Bayes, que hace referencia al Teorema de Bayes, y el Naive por la ingenuidad de su lógica.

Tiene como ventajas la velocidad y la eficiencia, pero por contraparte tiene la característica de la ingenuidad que asume que las palabras no tienen relación entre sí y no entiende el orden. Pero aun así el algoritmo tiene muchos casos útiles como:

  • Filtros de spam.
  • Análisis de sentimientos.
  • Sistemas de recomendaciones.

Bibliotecas necesarias

Necesitamos tener instaladas las siguientes bibliotecas. Te recomiendo hacer este ejercicio en Google Colab para facilitar la configuración del entorno. Configura el Runtime como T4 para poder soportar todo el tamaño del csv.

  • sklearn: fundamental para la vectorización de texto, el entrenamiento del modelo y el cálculo de métricas.
  • nltk: la utilizaremos principalmente para la gestión y filtrado de stopwords.
  • spaCy: nuestra herramienta principal para la tokenización.
  • joblib: para guardar y cargar modelos de Machine Learning.

Preparación de los datos

Cargar el dataset

Puedes descargar el dataset aquí: reviews.csv. El dataset cuenta con 210000 reseñas y con las columnas: stars, review_body, language y product_category. Todas las reseñas están en español.

import pandas as pd

df = pd.read_csv('reviews.csv')

Antes de hacer limpieza de datos, es importante entender cómo se distribuyen nuestras etiquetas. Visualizaremos la frecuencia de las puntuaciones para verificar si contamos con un dataset balanceado.

import matplotlib.pyplot as plt
import seaborn as sns

plt.figure(figsize=(8, 4))
sns.countplot(x='stars', data=df)
plt.title('Distribution of Scores')
plt.show()

Distribución de las stars en el dataset reviews

Un desbalance significativo (por ejemplo, tener muchas reseñas de 5 estrellas y casi ninguna de 1) podría sesgar las predicciones de nuestro modelo. El dataset que tenemos esta balanceado.

Eliminar las filas nulas

Es necesario limpiar el dataset eliminando las filas que no contienen información completa porque pueden generar errores.

# Conocer la cantidad de valores nulos por columna
df.isnull().sum()

# Eliminar filas que contengan valores nulos
df.dropna(inplace=True)

Limpieza y Normalización de Texto

El siguiente paso es estandarizar el contenido. Crearemos una función de limpieza que utilice expresiones regulares para eliminar el ruido de los datos.

import re
import string

def preprocess_text(text):
    """
    Realiza una limpieza del texto: conversión a minúsculas, 
    eliminación de ruido (URLs, HTML, etiquetas) y normalización de caracteres.
    """

    # Estandarización a minúsculas
    text = str(text).lower()

    # Eliminar contenido entre corchetes (ej. etiquetas o referencias)
    text = re.sub(r'\[.*?\]', '', text)

    # Eliminar URLs y direcciones web
    text = re.sub(r'https?://\S+|www\.\S+', '', text)

    # Eliminar etiquetas HTML
    text = re.sub(r'<.*?>+', '', text)

    # Reemplazar saltos de línea por espacios
    text = re.sub(r'\n', ' ', text)

    # Eliminar palabras que contienen dígitos
    text = re.sub(r'\w*\d\w*', '', text)

    # Eliminar espacios en blanco sobrantes
    text = text.strip()

    return text

df["clean_review"] = df["review_body"].apply(preprocess_text)

Remover las Stopwords

Esta técnica elimina las palabras comunes como "el", "la", "de". Las cuales no aportan significado semántico para clasificar los textos.

Al eliminarlas, incrementamos la velocidad de procesamiento y enfocamos el modelo en los términos importantes. Pero se debe aplicar con cuidado, ya que en ciertos contextos (como el análisis de sentimiento) eliminar una negación puede alterar el sentido de la frase.

import nltk
from nltk.corpus import stopwords

# Descargamos el corpus de stopwords en español
nltk.download('stopwords')
stopword_es = set(stopwords.words('spanish'))
  • El corpus es un conjunto amplio y estructurado de textos. En este caso, descargamos una base de datos lingüística predefinida de nltk.

Tokenización y Lematización

Para que un algoritmo entienda el texto, debemos descomponerlo y normalizarlo:

  • La tokenización es el proceso de dividir el texto en unidades más pequeñas, llamadas tokens. Esto es esencial para análisis posteriores como el conteo de palabras o la vectorización.
  • La lematización utiliza un diccionario para reducir cada palabra a su raíz lingüística o lema. Es mucho más preciso y mantiene el sentido, pero es más lento y requiere más recursos computacionales. Ejemplo: Las formas verbales "correrá", "corre" y "corriendo" se normalizan a "correr".

¿Por qué reducir el vocabulario? Buscamos eliminar el ruido y mejorar la eficiencia. Cada palabra se convertirá en una columna en nuestra matriz de características. Con el Stopwords y la lematización buscamos agrupar términos para obtener mayor eficiencia.

Debemos descargar el modelo de idioma específico para español (es_core_news_sm):

!python -m spacy download es_core_news_sm
import spacy

nlp_es = spacy.load('es_core_news_sm')

def clean(text):
    """
    Aplica tokenización, eliminación de stopwords y lematización
    utilizando el modelo de spaCy.
    """

    doc = nlp_es(text)
    
    # Filtramos por stopwords y extraemos el lema de cada token
    lemmatized = [token.lemma_ for token in doc if token.text.lower() not in stopword_es]
    
    return " ".join(lemmatized).strip()

# Aplicamos la transformación al DataFrame
df["clean_review"] = df["clean_review"].apply(clean)

Vectorizacion con TF-IDF

En esta etapa transformamos el texto en representaciones numéricas denominadas vectores. Existen varios métodos para hacer esto, pero en este ejemplo utilizaremos el TF-IDF (Term Frequency - Inverse Document Frequency).

El TF-IDF no solo mide qué tan frecuente es un término en un documento, sino qué tan relevante es en comparación con todo el conjunto de datos (corpus). Este método se basa en el modelo de Bag of Words, el cual analiza la importancia de los términos sin considerar su orden o el contexto.

from sklearn.feature_extraction.text import TfidfVectorizer

corpus = df["clean_review"].tolist()

tfidf = TfidfVectorizer()
tfidf_matrix = tfidf.fit_transform(corpus)

print(f"Dimensiones de la matriz: {tfidf_matrix.shape}")

Clasificador Naive Bayes

Ahora vamos a entrenar y evaluar un clasificador Naive Bayes para determinar el sentimiento (positivo o negativo) de las reseñas.

Primero debemos construir nuestra variable objetivo: si tiene más de 3 estrellas, será un comentario 1 (positivo) y si tiene menos de 3 estrellas, será 0 (negativo).

df["sentiment"] = df["stars"].apply(lambda x: 1 if x > 3 else 0)

División del dataset

Para garantizar que nuestro modelo sea capaz de generalizar, dividiremos nuestro dataset en dos grupos:

  • Training set: los datos que el modelo utilizará para aprender las relaciones entre las palabras y el sentimiento.
  • Test set: datos que el modelo nunca ha visto y que usaremos para validar su precisión final.
from sklearn.model_selection import train_test_split

X = tfidf_matrix
y = df["sentiment"]

X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42
)
  • Definimos un 20% del dataset para la prueba final.
  • El random_state=42 nos asegura que si se corre el código de nuevo, la división sea la misma y los resultados sean reproducibles.

Entrenamiento

Utilizaremos MultinomialNB el cual es una variante del algoritmo Naive Bayes especifica para trabajar con datos discretos donde se tengan conteo o frecuencia de eventos. Este trata cada palabra como un evento que ocurre una cierta cantidad de veces.

from sklearn.naive_bayes import MultinomialNB

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

Existen otras opciones como las siguientes:

Algoritmos de Naive Bayes

Evaluación del modelo

Una vez entrenado, debemos medir que tan bien funciona nuestro modelo y que desempeño tiene. Usaremos algunas métricas que nos ayudaran con esto:

  • Accuracy: el porcentaje total de predicciones correctas.
  • Precision: indica la calidad de las predicciones positivas. ¿Qué tan confiable es cuando dice "positivo"?
  • Recall: mide la capacidad del modelo para identificar todos los casos positivos reales.
from sklearn.metrics import accuracy_score, classification_report

y_pred = nb_classifier.predict(X_test)

print("Accuracy:", accuracy_score(y_test, y_pred))
print("Reporte de clasificación:\n", classification_report(y_test, y_pred))

Si quieres investigar más sobre las métricas, lee esto: https://byandrev.dev/es/blog/performance-metrics-in-machine-learning.

Guardar el modelo

Ahora guardaremos el modelo para usarlo en futuras ocasiones, para esto utilizamos la librería joblib.

import joblib

model_path = "/content/nb_classifier_model.pkl"
joblib.dump(nb_classifier, model_path)

print(f"Modelo exportado exitosamente en: {model_path}")

Importar el modelo

loaded_model = joblib.load(model_path)

Pruebas con datos reales

Para que el modelo entienda una nueva frase, esta debe pasar por el mismo proceso de vectorización (TF-IDF) que usamos durante el entrenamiento.

new_review = "Es un producto excelente, superó mis expectativas"

clean_review = clean(preprocess_text(new_review))
new_vector = tfidf.transform([clean_review])
prediction = loaded_model.predict(new_vector)

print(f"Reseña: '{new_review}'")
print(f"Predicción de sentimiento: {prediction[0]}")

Comparte este artículo en

Avatar byandrev

Andres Parra

Software Engineer

Soy Andrés Parra, Ingeniero de Software apasionado por crear soluciones tecnológicas escalables e innovadoras. Me especializo en la construcción de aplicaciones web modernas, dominando un stack versátil que incluye JavaScript, TypeScript, Python y Java, junto con frameworks como React, Next.js y Spring Boot. Interesado en las últimas tecnologías y herramientas de desarrollo.