Uno de los campos de mayor interés y aplicación dentro de la Inteligencia Artificial es el Natural Language Processing NPL (Procesamiento del Lenguaje Natural). Proporciona a los procesos computacionales la capacidad para interpretar, manipular y generar lenguaje humano. Hace que las máquinas comprendan el lenguaje que utilizamos a diario, con todas sus complejidades.
El NPL está presente en muchas aplicaciones que usamos día a día, desde chatboots, traductores entre distintos idiomas a sistemas inteligentes.
Un ejemplo sería tener un sistema en casa al que podamos dar indicaciones como «apaga todas las luces de la casa» o «enciende el reproductor de música y pon mi canción de rock favorita».
Modelo Bag of words
En este artículo desarrollaremos el modelo Bag of words. Se utiliza para representar el texto de una forma simple pero efectiva. Se emplea un array con tantas posiciones posibles como haya en el lenguaje de entrada. Cada palabra o token se vincula a un índice en el array, contando cuantas veces aparece dicho token en la frase. Es un modelo muy útil en tareas de clasificación, análisis de sentimientos, etc.
“El gato marrón saltó sobre la mesa marrón” -> [ 1 1 2 1 1 1 1]
Palabra | Índice | Frecuencia |
el | 0 | 1 |
gato | 1 | 1 |
marrón | 2 | 2 |
saltó | 3 | 1 |
sobre | 4 | 1 |
la | 5 | 1 |
mesa | 6 | 1 |
La longitud del array dependerá del conjunto de palabras que se quiera modelar. En Inglés se estima que hay más de 170.000 palabras diferentes, pero que la mayoría de las personas que hablan el idioma en la práctica usan 20.000 palabras. Este sería el tamaño necesario para representar cualquier frase.
El modelo tiene como limitaciones la pérdida de información sobre el orden de las palabras y la pérdida de semántica más profunda en los textos.
Cómo utilizar un modelo de Procesamiento de Lenguaje Natural NPL
Caso de estudio. Clasificar reseñas en restaurantes
Disponemos de un conjunto de entrenamiento formado por reseñas de clientes en restaurantes junto si la reseña es positiva o negativa.
Reseña | Valoración |
Wow… Loved this place. | 1 |
This was a largely disappointing dining experience | 0 |
We’ll never go again. | 0 |
… |
Utilizaremos el modelo de Bag-of-words para codificar cada reseña. Con esto obtendremos un vector numérico. Entrenaremos el modelo de clasificación supervisado Naive Bayes con los datos disponibles, para poder hacer posteriores predicciones sobre reseñas nuevas no disponibles en los datos de prueba.
Para realizar el ejemplo utilizaremos Python, librerías de IA y el entorno de desarrollo de codeblocks.
Importando las librerías y leer el fichero de reseñas
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
dataset = pd.read_csv('Restaurant_Reviews.tsv', delimiter = '\t', quoting = 3)
El archivo que disponemos para las reseñas y su valoración positiva o negativa está en formato TSV, es decir, la separación con tabuladores en lugar de las comas del formato CSV. Por ello indicamos a la función read_csv que el delimitador es \t.
Limpiar los textos
La limpieza de textos es una etapa fundamental en NPL. Consiste en eliminar información irrelevante o redundante que puede interferir el análisis o generar confusión en los resultados.
Una de las técnicas más usadas es la eliminación de las stop words. Son palabras muy frecuentes en un idioma y no aportan información semántica a la frase. Ejemplos en inglés son las “the”, “is”, “at”, “and”, entre otras.
Las etapas clásicas del proceso suelen ser:
- Tokenización: dividir el texto en fragmentos más pequeños, como palabras o frases. Cada unidad se llama token.
- Eliminación de caracteres especiales y puntuación.
- Conversión a minúsculas,
- Stemming o lematización: Opcionalmente se puede aplicar al texto un proceso de normalización morfológica para reducir las palabras a sus raíces (stemming) o formas canónicas (lematización). Esto ayuda a agrupar palabras relacionadas mediante una única forma.
import re
import nltk
nltk.download('stopwords')
from nltk.corpus import stopwords
from nltk.stem.porter import PorterStemmer
corpus = []
for i in range(0, 1000):
review = re.sub('[^a-zA-Z]', ' ', dataset['Review'][i])
review = review.lower()
review = review.split()
ps = PorterStemmer()
all_stopwords = stopwords.words('english')
all_stopwords.remove('not')
review = [ps.stem(word) for word in review if not word in set(all_stopwords)]
review = ' '.join(review)
corpus.append(review)
Se importan las librerías re y ntlk que contienen respectivamente utilidades para expresiones regulares y para procesamiento de lenguaje natural.
Se usa la función download para descargar el módulo ‘stopwords’.
A continuación se hace un bucle que recorre todas las entradas del dataset.
- Con la función sub se reemplaza todo lo que no sean letras por un espacio en blanco.
- Se pasan todas las letras a minúscula, con la función lower.
- Se crea un array ejecutando la función split, donde cada elemento es el token y el separador el espacio en blanco.
- Obtenemos las stopwords de inglés, y eliminamos la palabra ‘not’ porque en nuestro caso es determinante para saber si es una reseña positiva o negativa.
- Mediante la clase PorterStemmer y su función stem aplicamos stemming a cada token que no pertenezca a las stop words.
- Finalmente volvemos a concatenar separados por espacios los tokens procesados.
Para hacernos una idea comparemos el texto original con el texto obtenido tras el procesado:
print(corpus)
Texto original | Texto procesado |
Wow… Loved this place. | wow love place |
Not tasty and the texture was just nasty. | not tasti textur nasti |
The selection on the menu was great and so were the prices. | select menu great price |
The potatoes were like rubber and you could tell they had been made up ahead of time being kept under a warmer. | potato like rubber could tell made ahead time kept warmer |
Crear el modelo Bag of Words
Para disponer de un conjunto de entrenamiento numérico transformamos los tokens en vectores numéricos. Tendremos así el vector X con las variables dependientes que representan al texto y el vector Y con los indicadores de reseña positiva o negativa.
from sklearn.feature_extraction.text import CountVectorizer
cv = CountVectorizer(max_features = 1500)
X = cv.fit_transform(corpus).toarray()
y = dataset.iloc[:, -1].values
Se instancia la clase CountVectorizer indicando que el tamaño máximo del array resultante sea 1500. A continuación con la función fit_transform se transforman los tokens en los correspondientes arrays numéricos del modelo Bag of words.
Dividir los datos en conjunto de entrenamiento y prueba
Se realiza el proceso como en otros métodos aprendizaje supervisado:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.20, random_state = 0)
Entrenar el modelo Native Bayes
from sklearn.naive_bayes import GaussianNB
classifier = GaussianNB()
classifier.fit(X_train, y_train)
Creamos un objeto de la clase GaussianNB. Utilizamos la función fit con los datos de entrada del modelo Bag of Models y la variable independiente si es una reseña positiva o negativa.
Predicciones con los datos de prueba
y_pred = classifier.predict(X_test)
Matriz de confusión
Para evaluar cómo se ajusta el modelo hacemos la matriz de confusión.
from sklearn.metrics import confusion_matrix, accuracy_score
cm = confusion_matrix(y_test, y_pred)
print(cm)
accuracy_score(y_test, y_pred)
[[55 42]
[12 91]]
0.73
Observamos que hay 55 reseñas negativas que se clasifican correctamente junto con 91 reseñas positivas cuya predicción es correcta. Sin embargo hay 42 reseñas negativas que se clasifican como positivas y 12 reseñas positivas que se clasifican como negativas.
El ratio de acierto del modelo entrenado es del 73%.
Utilizar el modelo para hacer predicciones
En este punto podemos utilizar el modelo para evaluar nuevas reseñas que no pertenezcan al conjunto de datos de entrenamiento y comprobar si el modelo las clasifica como positivas o negativas.
Debemos aplicar el mismo proceso de tokenización que durante el entrenamiento del modelo. Para ello creamos la siguiente función:
def preprocesar_texto(new_review):
new_review = re.sub('[^a-zA-Z]', ' ', new_review)
new_review = new_review.lower()
new_review = new_review.split()
ps = PorterStemmer()
all_stopwords = stopwords.words('english')
all_stopwords.remove('not')
new_review = [ps.stem(word) for word in new_review if not word in set(all_stopwords)]
new_review = ' '.join(new_review)
new_corpus = [new_review]
return cv.transform(new_corpus).toarray()
Vemos como la reseña “I love this restaurant so much” es clasificada por el modelo como positiva:
new_review = 'I love this restaurant so much'
new_X_test = preprocesar_texto(new_review)
new_y_pred = classifier.predict(new_X_test)
print(new_y_pred)
[1]
Y la opinión “I hate this restaurant so much” es correctamente clasificada como negativa, ya que el modelo devuelve un 0 en la predicción.
new_review = 'I hate this restaurant so much'
new_X_test = preprocesar_texto(new_review)
new_y_pred = classifier.predict(new_X_test)
print(new_y_pred)
[0]
Conclusiones sobre el Procesamiento de Lenguaje Natural NPL
Hemos visto cómo utilizar un modelo de procesamiento de lenguaje natural NPL para clasificar automáticamente reseñas de restaurantes entre valoraciones positivas y negativas. Una vez realizada la codificación de Bag of words se utiliza el modelo Native Bayes para realizar la clasificación.
El ejemplo completo está disponible en Google Colab.