Clasificación de texto con Python y Scikit-Learn

La clasificación de textos es una de las tareas más importantes en el procesamiento del lenguaje natural. Es el proceso de clasificar cadenas de texto o documentos en diferentes...

Introducción

La clasificación de textos es una de las tareas más importantes del Procesamiento natural del lenguaje. Es el proceso de clasificar cadenas de texto o documentos en diferentes categorías, según el contenido de las cadenas. La clasificación de texto tiene una variedad de aplicaciones, como detectar el sentimiento del usuario de un tweet, clasificar un correo electrónico como spam o ham, clasificar publicaciones de blog en diferentes categorías, etiquetar automáticamente las consultas de los clientes, etc.

En este artículo, veremos un ejemplo real de clasificación de texto. Entrenaremos un modelo de aprendizaje automático capaz de predecir si la crítica de una película determinada es positiva o negativa. Este es un ejemplo clásico de análisis sentimental donde los sentimientos de las personas hacia una entidad en particular se clasifican en diferentes categorías.

Conjunto de datos

El conjunto de datos que vamos a utilizar para este artículo se puede descargar del [Grupo de procesamiento del lenguaje natural de Cornell](http://www.cs.cornell.edu/people/pabo/movie-review-data/review_polarity .tar.gz). El conjunto de datos consta de un total de 2000 documentos. La mitad de los documentos contienen críticas positivas sobre una película, mientras que la mitad restante contiene críticas negativas. Se pueden encontrar más detalles sobre el conjunto de datos en este enlace.

Descomprima o extraiga el conjunto de datos una vez que lo descargue. Abra la carpeta "txt_sentoken". La carpeta contiene dos subcarpetas: "neg" y "pos". Si abre estas carpetas, puede ver los documentos de texto que contienen reseñas de películas.

Análisis de opinión con Scikit-Learn

Ahora que hemos descargado los datos, es hora de ver algo de acción. En esta sección, realizaremos una serie de pasos necesarios para predecir los sentimientos a partir de reseñas de diferentes películas. Estos pasos se pueden utilizar para cualquier tarea de clasificación de texto. Usaremos la biblioteca Scikit-Learn de Python para el aprendizaje automático para entrenar un modelo de clasificación de texto.

Los siguientes son los pasos necesarios para crear un modelo de clasificación de texto en Python:

  1. Importación de bibliotecas
  2. Importación del conjunto de datos
  3. Preprocesamiento de texto
  4. Conversión de texto a números
  5. Conjuntos de entrenamiento y prueba
  6. Modelo de clasificación de texto de entrenamiento y sentimiento predictivo
  7. Evaluación del modelo
  8. Guardando y Cargando el Modelo

Importación de bibliotecas

Ejecute el siguiente script para importar las bibliotecas requeridas:

1
2
3
4
5
6
7
import numpy as np
import re
import nltk
from sklearn.datasets import load_files
nltk.download('stopwords')
import pickle
from nltk.corpus import stopwords

Importación del conjunto de datos

Usaremos la función load_files de la biblioteca sklearn_datasets para importar el conjunto de datos a nuestra aplicación. La función load_files divide automáticamente el conjunto de datos en conjuntos de datos y objetivos. Por ejemplo, en nuestro caso, le pasaremos la ruta al directorio "txt_sentoken". load_files tratará cada carpeta dentro de la carpeta "txt_sentoken" como una categoría y todos los documentos dentro de esa carpeta serán asignados a su categoría correspondiente.

Ejecute el siguiente script para ver la función load_files en acción:

1
2
movie_data = load_files(r"D:\txt_sentoken")
X, y = movie_data.data, movie_data.target

En el script anterior, la función load_files carga los datos de las carpetas "neg" y "pos" en la variable X, mientras que las categorías objetivo se almacenan en y. Aquí X es una lista de 2000 elementos de tipo cadena donde cada elemento corresponde a una revisión de un solo usuario. De manera similar, y es una matriz numérica de tamaño 2000. Si imprime y en la pantalla, verá una matriz de 1 y 0. Esto se debe a que, para cada categoría, la función load_files agrega un número a la matriz numpy de destino. Tenemos dos categorías: "neg" y "pos", por lo tanto, se agregaron 1 y 0 a la matriz de destino.

Preprocesamiento de texto {#preprocesamiento de texto}

Una vez que se ha importado el conjunto de datos, el siguiente paso es preprocesar el texto. El texto puede contener números, caracteres especiales y espacios no deseados. Dependiendo del problema al que nos enfrentemos, es posible que necesitemos o no eliminar estos caracteres especiales y números del texto. Sin embargo, en aras de la explicación, eliminaremos todos los caracteres especiales, números y espacios no deseados de nuestro texto. Ejecute el siguiente script para preprocesar los datos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
documents = []

from nltk.stem import WordNetLemmatizer

stemmer = WordNetLemmatizer()

for sen in range(0, len(X)):
    # Remove all the special characters
    document = re.sub(r'\W', ' ', str(X[sen]))
    
    # remove all single characters
    document = re.sub(r'\s+[a-zA-Z]\s+', ' ', document)
    
    # Remove single characters from the start
    document = re.sub(r'\^[a-zA-Z]\s+', ' ', document) 
    
    # Substituting multiple spaces with single space
    document = re.sub(r'\s+', ' ', document, flags=re.I)
    
    # Removing prefixed 'b'
    document = re.sub(r'^b\s+', '', document)
    
    # Converting to Lowercase
    document = document.lower()
    
    # Lemmatization
    document = document.split()

    document = [stemmer.lemmatize(word) for word in document]
    document = ' '.join(document)
    
    documents.append(document)

En el script anterior usamos Expresiones regulares de la biblioteca Python re para realizar diferentes tareas de preprocesamiento. Comenzamos eliminando todos los caracteres que no son palabras, como caracteres especiales, números, etc.

A continuación, eliminamos todos los caracteres individuales. Por ejemplo, cuando eliminamos el signo de puntuación de "David's" y lo reemplazamos con un espacio, obtenemos "David" y un solo carácter "s", que no tiene significado. Para eliminar dichos caracteres individuales, usamos la expresión regular \s+[a-zA-Z]\s+ que sustituye todos los caracteres individuales que tienen espacios a cada lado, con un solo espacio.

A continuación, usamos la expresión regular \^[a-zA-Z]\s+ para reemplazar un solo carácter desde el principio del documento, con un solo espacio. Reemplazar caracteres individuales con un solo espacio puede generar varios espacios, lo que no es lo ideal.

Nuevamente usamos la expresión regular \s+ para reemplazar uno o más espacios con un solo espacio. Cuando tiene un conjunto de datos en formato de bytes, la letra del alfabeto "b" se agrega antes de cada cadena. La expresión regular ^b\s+ elimina "b" del comienzo de una cadena. El siguiente paso es convertir los datos a minúsculas para que las palabras que en realidad son las mismas pero tienen casos diferentes puedan tratarse por igual.

El último paso de preprocesamiento es la lematización. En la lematización, reducimos la palabra a la forma raíz del diccionario. Por ejemplo, "gatos" se convierte en "gato". La lematización se realiza para evitar crear características que sean semánticamente similares pero sintácticamente diferentes. Por ejemplo, no queremos dos características diferentes llamadas "gatos" y "gato", que son semánticamente similares, por lo que realizamos la lematización.

Conversión de texto a números

Las máquinas, a diferencia de los humanos, no pueden entender el texto en bruto. Las máquinas solo pueden ver números. En particular, las técnicas estadísticas como el aprendizaje automático solo pueden tratar con números. Por lo tanto, necesitamos convertir nuestro texto en números.

Existen diferentes enfoques para convertir texto en la forma numérica correspondiente. El modelo de bolsa de palabras y el [Modelo de inserción de palabras](https://en.wikipedia.org/wiki/ Word_embedding) son dos de los enfoques más utilizados. En este artículo, usaremos el modelo de bolsa de palabras para convertir nuestro texto en números.

Bolsa de palabras

El siguiente script utiliza el modelo de bolsa de palabras para convertir documentos de texto en características numéricas correspondientes:

1
2
3
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(max_features=1500, min_df=5, max_df=0.7, stop_words=stopwords.words('english'))
X = vectorizer.fit_transform(documents).toarray()

El script anterior usa la clase CountVectorizer de la biblioteca sklearn.feature_extraction.text. Hay algunos parámetros importantes que deben pasarse al constructor de la clase. El primer parámetro es el parámetro max_features, que se establece en 1500. Esto se debe a que cuando convierte palabras en números usando el enfoque de bolsa de palabras, todas las palabras únicas en todos los documentos se convierten en características. Todos los documentos pueden contener decenas de miles de palabras únicas. Pero las palabras que tienen una frecuencia de aparición muy baja no suelen ser un buen parámetro para clasificar documentos. Por lo tanto, establecemos el parámetro max_features en 1500, lo que significa que queremos usar las 1500 palabras más frecuentes como características para entrenar nuestro clasificador.

El siguiente parámetro es min_df y se ha establecido en 5. Esto corresponde a la cantidad mínima de documentos que deben contener esta característica. Así que solo incluimos aquellas palabras que aparecen en al menos 5 documentos. De manera similar, para la función max_df, el valor se establece en 0.7; en el que la fracción corresponde a un porcentaje. Aquí 0.7 significa que debemos incluir solo aquellas palabras que ocurren en un máximo del 70% de todos los documentos. Las palabras que aparecen en casi todos los documentos no suelen ser adecuadas para la clasificación porque no proporcionan ninguna información única sobre el documento.

Finalmente, eliminamos Para las palabras de nuestro texto ya que, en el caso del análisis de sentimiento, las palabras vacías pueden no contener información útil. Para eliminar las palabras vacías, pasamos el objeto stopwords de la biblioteca nltk.corpus al parámetro stop_words.

La función fit_transform de la clase CountVectorizer convierte documentos de texto en características numéricas correspondientes.

Búsqueda de TFIDF

El enfoque de la bolsa de palabras funciona bien para convertir texto en números. Sin embargo, tiene un inconveniente. Asigna una puntuación a una palabra en función de su aparición en un documento en particular. No tiene en cuenta el hecho de que la palabra también podría tener una alta frecuencia de aparición en otros documentos. TFIDF resuelve este problema multiplicando la frecuencia del término de una palabra por la frecuencia inversa del documento. TF significa "Frecuencia de término", mientras que IDF significa "Frecuencia de documento inversa".

El término frecuencia se calcula como:

1
Term frequency = (Number of Occurrences of a word)/(Total words in the document)

Y la frecuencia inversa del documento se calcula como:

1
IDF(word) = Log((Total number of documents)/(Number of documents containing the word))

El valor TFIDF para una palabra en un documento en particular es mayor si la frecuencia de aparición de esa palabra es mayor en ese documento específico pero menor en todos los demás documentos.

Para convertir los valores obtenidos utilizando el modelo de bolsa de palabras en valores TFIDF, ejecute el siguiente script:

1
2
3
from sklearn.feature_extraction.text import TfidfTransformer
tfidfconverter = TfidfTransformer()
X = tfidfconverter.fit_transform(X).toarray()
Nota:

También puede convertir directamente documentos de texto en valores de características TFIDF (sin convertir primero los documentos en características de bolsa de palabras) utilizando el siguiente script:

1
2
3
from sklearn.feature_extraction.text import TfidfVectorizer
tfidfconverter = TfidfVectorizer(max_features=1500, min_df=5, max_df=0.7, stop_words=stopwords.words('english'))
X = tfidfconverter.fit_transform(documents).toarray()

Conjuntos de entrenamiento y prueba {#conjuntos de entrenamiento y prueba}

Como cualquier otro problema de aprendizaje automático supervisado, necesitamos dividir nuestros datos en conjuntos de entrenamiento y prueba. Para ello, utilizaremos la utilidad train_test_split de la biblioteca sklearn.model_selection. Ejecute el siguiente script:

1
2
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

La secuencia de comandos anterior divide los datos en un conjunto de prueba del 20 % y un conjunto de entrenamiento del 80 %.

Modelo de clasificación de texto de entrenamiento y sentimiento de predicción {#modelo de clasificación de texto de entrenamiento y sentimiento de predicción}

Hemos dividido nuestros datos en conjuntos de entrenamiento y prueba. Ahora es el momento de ver la acción real. Usaremos el Algoritmo de bosque aleatorio para entrenar nuestro modelo. Puede utilizar cualquier otro modelo de su elección.

Para entrenar nuestro modelo de aprendizaje automático usando el algoritmo de bosque aleatorio, usaremos la clase RandomForestClassifier de la biblioteca sklearn.ensemble. El método fit de esta clase se usa para entrenar el algoritmo. Necesitamos pasar los datos de entrenamiento y los conjuntos de objetivos de entrenamiento a este método. Echa un vistazo al siguiente script:

1
2
classifier = RandomForestClassifier(n_estimators=1000, random_state=0)
classifier.fit(X_train, y_train) 

Finalmente, para predecir el sentimiento de los documentos en nuestro conjunto de prueba, podemos usar el método predecir de la clase RandomForestClassifier como se muestra a continuación:

1
y_pred = classifier.predict(X_test)

Enhorabuena, ha entrenado con éxito su primer modelo de clasificación de texto y ha realizado algunas predicciones. Ahora es el momento de ver el rendimiento del modelo que acaba de crear.

Evaluación del modelo

Para evaluar el rendimiento de un modelo de clasificación como el que acabamos de entrenar, podemos utilizar métricas como la matriz de confusión, Medida F1 y la precisión.

Para encontrar estos valores, podemos usar las utilidades classification_report, confusion_matrix y accuracy_score de la biblioteca sklearn.metrics. Ejecute el siguiente script para hacerlo:

1
2
3
4
5
from sklearn.metrics import classification_report, confusion_matrix, accuracy_score

print(confusion_matrix(y_test,y_pred))
print(classification_report(y_test,y_pred))
print(accuracy_score(y_test, y_pred))

La salida se ve así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[[180  28]
 [ 30 162]]
             precision    recall  f1-score   support

          0       0.86      0.87      0.86       208
          1       0.85      0.84      0.85       192

avg / total       0.85      0.85      0.85       400

0.855

A partir de la salida, se puede ver que nuestro modelo logró una precisión del 85,5 %, lo cual es muy bueno dado que elegimos aleatoriamente todos los parámetros para CountVectorizer, así como para nuestro algoritmo de bosque aleatorio.

Guardar y cargar el modelo

En el script anterior, nuestro modelo de aprendizaje automático no tardó mucho en ejecutarse. Una de las razones del tiempo de entrenamiento rápido es el hecho de que teníamos un conjunto de entrenamiento relativamente más pequeño. Teníamos 2000 documentos, de los cuales usamos el 80% (1600) para capacitación. Sin embargo, en escenarios del mundo real, puede haber millones de documentos. En tales casos, puede llevar horas o incluso días (si tiene máquinas más lentas) entrenar los algoritmos. Por lo tanto, se recomienda guardar el modelo una vez entrenado.

Podemos guardar nuestro modelo como un objeto pickle en Python. Para hacerlo, ejecute el siguiente script:

1
2
with open('text_classifier', 'wb') as picklefile:
    pickle.dump(classifier,picklefile)

Una vez que ejecute el script anterior, puede ver el archivo text_classifier en su directorio de trabajo. Hemos guardado nuestro modelo entrenado y podemos usarlo más tarde para hacer predicciones directamente, sin entrenamiento.

Para cargar el modelo, podemos usar el siguiente código:

1
2
with open('text_classifier', 'rb') as training_model:
    model = pickle.load(training_model)

Cargamos nuestro modelo entrenado y lo almacenamos en la variable model. Predigamos el sentimiento para el conjunto de prueba utilizando nuestro modelo cargado y veamos si podemos obtener los mismos resultados. Ejecute el siguiente script:

1
2
3
4
5
y_pred2 = model.predict(X_test)

print(confusion_matrix(y_test, y_pred2))
print(classification_report(y_test, y_pred2))
print(accuracy_score(y_test, y_pred2)) 

La salida se ve así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[[180  28]
 [ 30 162]]
             precision    recall  f1-score   support

          0       0.86      0.87      0.86       208
          1       0.85      0.84      0.85       192

avg / total       0.85      0.85      0.85       400

0.855

El resultado es similar al que obtuvimos anteriormente, que mostró que guardamos y cargamos correctamente el modelo.

Yendo más lejos: proyecto de extremo a extremo portátil

¿Tu naturaleza inquisitiva te hace querer ir más allá? Recomendamos consultar nuestro Proyecto guiado: ["Subtítulos de imágenes con CNN y Transformers con Keras"](https://wikihtp.com/courses/image-captioning-with-cnns-and -transformadores-con-keras/#cta){target="_blank"}.

En este proyecto guiado, aprenderá a crear un modelo de subtítulos de imágenes, que acepta una imagen como entrada y produce un subtítulo de texto como salida.

Aprenderás a:

  • Preprocesar texto
  • Vectorizar la entrada de texto fácilmente
  • Trabaje con la API tf.data y cree conjuntos de datos de alto rendimiento
  • Cree Transformers desde cero con TensorFlow/Keras y KerasNLP: la adición horizontal oficial a Keras para crear modelos NLP de última generación
  • Cree arquitecturas híbridas donde la salida de una red se codifica para otra

¿Cómo enmarcamos los subtítulos de las imágenes? La mayoría lo considera un ejemplo de aprendizaje profundo generativo, porque estamos enseñando a una red a generar descripciones. Sin embargo, me gusta verlo como una instancia de traducción automática neuronal: estamos traduciendo las características visuales de una imagen en palabras. A través de la traducción, estamos generando una nueva representación de esa imagen, en lugar de simplemente generar un nuevo significado. Verlo como traducción, y solo por generación de extensión, enfoca la tarea bajo una luz diferente y la hace un poco más intuitiva.

Enmarcar el problema como uno de traducción hace que sea más fácil averiguar qué arquitectura querremos usar. Los transformadores solo de codificador son excelentes para comprender el texto (análisis de opinión, clasificación, etc.) porque los codificadores codifican representaciones significativas. Los modelos de solo decodificador son excelentes para la generación (como GPT-3), ya que los decodificadores pueden inferir representaciones significativas en otra secuencia con el mismo significado. La traducción generalmente se realiza mediante una arquitectura de codificador-decodificador, donde los codificadores codifican una representación significativa de una oración (o imagen, en nuestro caso) y los decodificadores aprenden a convertir esta secuencia en otra representación significativa que es más interpretable para nosotros (como una oración).

Conclusión

La clasificación de textos es una de las tareas de PNL más utilizadas. En este artículo, vimos un ejemplo simple de cómo se puede realizar la clasificación de texto en Python. Realizamos el análisis sentimental de críticas de cine.

Le aconsejaría que cambie algún otro algoritmo de aprendizaje automático para ver si puede mejorar el rendimiento. Además, intente cambiar los parámetros de la clase CountVectorizer para ver si puede obtener alguna mejora.