Ads
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.
Ads
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 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:
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.
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()

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.
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)
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)
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'))
nltk.Para que un algoritmo entienda el texto, debemos descomponerlo y normalizarlo:
¿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)
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}")
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)
Para garantizar que nuestro modelo sea capaz de generalizar, dividiremos nuestro dataset en dos grupos:
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
)
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.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:

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:
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.
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}")
loaded_model = joblib.load(model_path)
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