Este es mi artículo número 11 en la serie de artículos sobre Python para PNL y el artículo 2 sobre la biblioteca Gensim en esta serie. En un artículo anterior , proporcioné una breve introducción a la biblioteca Gensim de Python. Expliqué cómo podemos crear diccionarios que asignen palabras a sus Id. Numéricas correspondientes. También discutimos cómo crear una bolsa de palabras de los diccionarios. En este artículo, estudiaremos cómo podemos realizar el modelado de temas utilizando la biblioteca Gensim.
He explicado cómo hacer el modelado de temas utilizando la biblioteca Scikit-Learn de Python, en mi artículo anterior . En ese artículo, expliqué cómo la asignación de direccionamiento latente (LDA) y la factorización de matriz no negativa (NMF) se pueden usar para el modelado de temas.
En este artículo, usaremos la biblioteca Gensim para el modelado de temas. Los enfoques empleados para el modelado de temas serán LDA y LSI (Latent Semantim Indexing).

Instalación de bibliotecas requeridas

Realizaremos el modelado de temas en el texto obtenido de los artículos de Wikipedia. Para raspar los artículos de Wikipedia, usaremos la API de Wikipedia. Para descargar la biblioteca API de Wikipedia, ejecute el siguiente comando:
$ pip install wikipedia
De lo contrario, si usa la distribución Anaconda de Python, puede usar uno de los siguientes comandos:
$ conda install -c conda-forge wikipedia
$ conda install -c conda-forge/label/cf201901 wikipedia
Para visualizar nuestro modelo temático, utilizaremos la pyLDAvisbiblioteca. Para descargar la biblioteca, ejecute el siguiente comando pip:
$ pip install pyLDAvis
Nuevamente, si usa la distribución Anaconda, puede ejecutar uno de los siguientes comandos:
$ conda install -c conda-forge pyldavis
$ conda install -c conda-forge/label/gcc7 pyldavis
$ conda install -c conda-forge/label/cf201901 pyldavis

Modelado de temas con LDA

En esta sección, realizaremos el modelado de temas de los artículos de Wikipedia utilizando LDA.
Descargaremos cuatro artículos de Wikipedia sobre los temas "Calentamiento global", "Inteligencia artificial", "Torre Eiffel" y "Mona Lisa". A continuación, vamos a preprocesar los artículos, seguido por el paso de modelado del tema. Finalmente, veremos cómo podemos visualizar el modelo LDA.

Artículos de Wikipedia que raspan

Ejecuta el siguiente script:
import wikipedia  
import nltk

nltk.download('stopwords')  
en_stop = set(nltk.corpus.stopwords.words('english'))

global_warming = wikipedia.page("Global Warming")  
artificial_intelligence = wikipedia.page("Artificial Intelligence")  
mona_lisa = wikipedia.page("Mona Lisa")  
eiffel_tower = wikipedia.page("Eiffel Tower")

corpus = [global_warming.content, artificial_intelligence.content, mona_lisa.content, eiffel_tower.content]  
En el script anterior, primero importamos las bibliotecas wikipedianltkTambién descargamos las nltkpalabras en inglés Usaremos estas palabras clave más adelante.
A continuación, descargamos el artículo de Wikipedia especificando el tema al pageobjeto de la wikipediabiblioteca. El objeto devuelto contiene información sobre la página descargada.
Para recuperar los contenidos de la página web, podemos utilizar el contentatributo. El contenido de los cuatro artículos se almacena en la lista nombrada corpus.

Preprocesamiento de datos

Para realizar el modelado de temas a través de LDA, necesitamos un diccionario de datos y la bolsa de palabras corpus. Desde el último artículo (vinculado anteriormente), sabemos que para crear un diccionario y una bolsa de palabras, necesitamos datos en forma de fichas.
Además, debemos eliminar elementos como las puntuaciones y palabras de parada de nuestro conjunto de datos. En aras de la uniformidad, convertiremos todas las fichas en minúsculas y también las minimizaremos. Además, eliminaremos todos los tokens que tengan menos de 5 caracteres.
Mira el siguiente script:
import re  
from nltk.stem import WordNetLemmatizer

stemmer = WordNetLemmatizer()

def preprocess_text(document):  
        # Remove all the special characters
        document = re.sub(r'\W', ' ', str(document))

        # 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
        tokens = document.split()
        tokens = [stemmer.lemmatize(word) for word in tokens]
        tokens = [word for word in tokens if word not in en_stop]
        tokens = [word for word in tokens if len(word)  > 5]

        return tokens
En el script anterior, creamos un método llamado preprocess_textque acepta un documento de texto como parámetro. El método utiliza operaciones de expresiones regulares para realizar una variedad de tareas. Repasemos brevemente lo que está sucediendo en la función anterior:
document = re.sub(r'\W', ' ', str(X[sen]))  
La línea anterior reemplaza todos los caracteres especiales y números por un espacio. Sin embargo, cuando elimina las puntuaciones, aparecen caracteres únicos sin significado en el texto. Por ejemplo, cuando reemplaza la puntuación en el texto Eiffel's, las palabras Eiffelsaparecen. Aquí el sno tiene significado, por lo tanto necesitamos reemplazarlo por espacio. El siguiente script hace eso:
document = re.sub(r'\s+[a-zA-Z]\s+', ' ', document)  
La secuencia de comandos anterior elimina solo caracteres dentro del texto. Para eliminar un solo carácter al principio del texto, se utiliza el siguiente código.
document = re.sub(r'\^[a-zA-Z]\s+', ' ', document)  
Cuando elimina espacios individuales dentro del texto, pueden aparecer múltiples espacios vacíos. El siguiente código reemplaza múltiples espacios vacíos por un solo espacio:
document = re.sub(r'\s+', ' ', document, flags=re.I)  
Cuando raspa un documento en línea, a bmenudo se anexa una cadena con el documento, lo que significa que el documento es binario. Para eliminar el prefijo b, se utiliza el siguiente script:
document = re.sub(r'^b\s+', '', document)  
El resto del método se explica por sí mismo. El documento se convierte a minúsculas y luego se divide en fichas. Las fichas están lematizadas y se eliminan las palabras de parada. Finalmente, todas las fichas que tengan menos de cinco caracteres se ignoran. El resto de los tokens se devuelven a la función de llamada.

Temas de modelado

Esta sección es la carne del artículo. Aquí veremos cómo se puede usar la función incorporada de la biblioteca Gensim para el modelado de temas. Pero antes de eso, necesitamos crear un corpus de todos los tokens (palabras) en los cuatro artículos de Wikipedia que raspamos. Mira el siguiente script:
processed_data = [];  
for doc in corpus:  
    tokens = preprocess_text(doc)
    processed_data.append(tokens)
El guión de arriba es sencillo. Recorremos la corpuslista que contiene los cuatro artículos de Wikipedia en forma de cadenas. En cada iteración, pasamos el documento al preprocess_textmétodo que creamos anteriormente. El método devuelve tokens para ese documento en particular. Los tokens se almacenan en la processed_datalista.
Al final del forciclo, todos los tokens de los cuatro artículos se almacenarán en la processed_datalista. Ahora podemos usar esta lista para crear un diccionario y la correspondiente bolsa de palabras del corpus. El siguiente script hace eso:
from gensim import corpora

gensim_dictionary = corpora.Dictionary(processed_data)  
gensim_corpus = [gensim_dictionary.doc2bow(token, allow_update=True) for token in processed_data]  
A continuación, guardaremos nuestro diccionario, así como la bolsa de palabras que utilizan pickle . Usaremos el diccionario guardado más adelante para hacer predicciones sobre los nuevos datos.
import pickle

pickle.dump(gensim_corpus, open('gensim_corpus_corpus.pkl', 'wb'))  
gensim_dictionary.save('gensim_dictionary.gensim')  
Ahora, tenemos todo lo necesario para crear el modelo LDA en Gensim. Usaremos la LdaModelclase del gensim.models.ldamodelmódulo para crear el modelo LDA. Necesitamos pasar la bolsa de palabras que creamos anteriormente como primer parámetro al LdaModelconstructor, seguido de la cantidad de temas, el diccionario que creamos anteriormente y la cantidad de pasadas (cantidad de iteraciones para el modelo).
Ejecuta el siguiente script:
import gensim

lda_model = gensim.models.ldamodel.LdaModel(gensim_corpus, num_topics=4, id2word=gensim_dictionary, passes=20)  
lda_model.save('gensim_model.gensim')  
Sí, es así de simple. En el script anterior, creamos el modelo LDA a partir de nuestro conjunto de datos y lo guardamos.
A continuación, vamos a imprimir 10 palabras para cada tema. Para ello, podemos utilizar el print_topicsmétodo. Ejecuta el siguiente script:
topics = lda_model.print_topics(num_words=10)  
for topic in topics:  
    print(topic)
La salida se ve así:
(0, '0.036*"painting" + 0.018*"leonardo" + 0.009*"louvre" + 0.009*"portrait" + 0.006*"museum" + 0.006*"century" + 0.006*"french" + 0.005*"giocondo" + 0.005*"original" + 0.004*"picture"')

(1, '0.016*"intelligence" + 0.014*"machine" + 0.012*"artificial" + 0.011*"problem" + 0.010*"learning" + 0.009*"system" + 0.008*"network" + 0.007*"research" + 0.007*"knowledge" + 0.007*"computer"')

(2, '0.026*"eiffel" + 0.008*"second" + 0.006*"french" + 0.006*"structure" + 0.006*"exposition" + 0.005*"tallest" + 0.005*"engineer" + 0.004*"design" + 0.004*"france" + 0.004*"restaurant"')

(3, '0.031*"climate" + 0.026*"change" + 0.024*"warming" + 0.022*"global" + 0.014*"emission" + 0.013*"effect" + 0.012*"greenhouse" + 0.011*"temperature" + 0.007*"carbon" + 0.006*"increase"')
El primer tema contiene palabras como paintinglouvreportraitfrench museum, etc. Podemos suponer que estas palabras pertenecen a un tema relacionado con una imagen con la conexión francesa.
Del mismo modo, la segunda contiene palabras como intelligencemachineresearch, etc. Podemos suponer que estas palabras pertenecen al tema relacionado con la Inteligencia Artificial.
De manera similar, las palabras del tercer y cuarto tema apuntan al hecho de que estas palabras son parte del tema Torre Eiffel y Calentamiento Global, respectivamente.
Podemos ver claramente que el modelo LDA ha identificado con éxito los cuatro temas en nuestro conjunto de datos.
Es importante mencionar aquí que LDA es un algoritmo de aprendizaje no supervisado y, en los problemas del mundo real, no conocerá los temas en el conjunto de datos de antemano. Simplemente se le entregará un corpus, los temas se crearán utilizando LDA y luego los nombres de los temas dependerán de usted.
Ahora vamos a crear 8 temas usando nuestro conjunto de datos. Imprimiremos 5 palabras por tema:
lda_model = gensim.models.ldamodel.LdaModel(gensim_corpus, num_topics=8, id2word=gensim_dictionary, passes=15)  
lda_model.save('gensim_model.gensim')  
topics = lda_model.print_topics(num_words=5)  
for topic in topics:  
    print(topic)
La salida se ve así:
(0, '0.000*"climate" + 0.000*"change" + 0.000*"eiffel" + 0.000*"warming" + 0.000*"global"')
(1, '0.018*"intelligence" + 0.016*"machine" + 0.013*"artificial" + 0.012*"problem" + 0.010*"learning"')
(2, '0.045*"painting" + 0.023*"leonardo" + 0.012*"louvre" + 0.011*"portrait" + 0.008*"museum"')
(3, '0.000*"intelligence" + 0.000*"machine" + 0.000*"problem" + 0.000*"artificial" + 0.000*"system"')
(4, '0.035*"climate" + 0.030*"change" + 0.027*"warming" + 0.026*"global" + 0.015*"emission"')
(5, '0.031*"eiffel" + 0.009*"second" + 0.007*"french" + 0.007*"structure" + 0.007*"exposition"')
(6, '0.000*"painting" + 0.000*"machine" + 0.000*"system" + 0.000*"intelligence" + 0.000*"problem"')
(7, '0.000*"climate" + 0.000*"change" + 0.000*"global" + 0.000*"machine" + 0.000*"intelligence"')
Una vez más, la cantidad de temas que desee crear depende de usted. Sigue intentando diferentes números hasta que encuentres temas adecuados. Para nuestro conjunto de datos, el número adecuado de temas es 4 ya que sabemos que nuestro cuerpo contiene palabras de cuatro artículos diferentes. Vuelva a los cuatro temas ejecutando el siguiente script:
lda_model = gensim.models.ldamodel.LdaModel(gensim_corpus, num_topics=4, id2word=gensim_dictionary, passes=20)  
lda_model.save('gensim_model.gensim')  
topics = lda_model.print_topics(num_words=10)  
for topic in topics:  
    print(topic)
Esta vez, verá resultados diferentes ya que los valores iniciales para los parámetros de LDA se seleccionan al azar. Los resultados esta vez son los siguientes:
(0, '0.031*"climate" + 0.027*"change" + 0.024*"warming" + 0.023*"global" + 0.014*"emission" + 0.013*"effect" + 0.012*"greenhouse" + 0.011*"temperature" + 0.007*"carbon" + 0.006*"increase"')

(1, '0.026*"eiffel" + 0.008*"second" + 0.006*"french" + 0.006*"structure" + 0.006*"exposition" + 0.005*"tallest" + 0.005*"engineer" + 0.004*"design" + 0.004*"france" + 0.004*"restaurant"')

(2, '0.037*"painting" + 0.019*"leonardo" + 0.009*"louvre" + 0.009*"portrait" + 0.006*"museum" + 0.006*"century" + 0.006*"french" + 0.005*"giocondo" + 0.005*"original" + 0.004*"subject"')

(3, '0.016*"intelligence" + 0.014*"machine" + 0.012*"artificial" + 0.011*"problem" + 0.010*"learning" + 0.009*"system" + 0.008*"network" + 0.007*"knowledge" + 0.007*"research" + 0.007*"computer"')
Puede ver que las palabras para el primer tema ahora están mayormente relacionadas con el calentamiento global, mientras que el segundo tema contiene palabras relacionadas con la torre Eiffel.

Evaluando el Modelo LDA

Como dije anteriormente, los modelos de aprendizaje no supervisados ​​son difíciles de evaluar ya que no hay una verdad concreta contra la cual podamos probar el resultado de nuestro modelo.
Supongamos que tenemos un nuevo documento de texto y queremos encontrar su tema utilizando el modelo LDA que acabamos de crear, podemos hacerlo utilizando el siguiente script:
test_doc = 'Great structures are build to remember an event happened in the history.'  
test_doc = preprocess_text(test_doc)  
bow_test_doc = gensim_dictionary.doc2bow(test_doc)

print(lda_model.get_document_topics(bow_test_doc))  
En el script anterior, creamos una cadena, creamos su representación del diccionario y luego convertimos la cadena en la bolsa de palabras corpus. La representación de la bolsa de palabras se pasa al get_document_topicsmétodo. La salida se ve así:
[(0, 0.08422605), (1, 0.7446843), (2, 0.087012805), (3, 0.08407689)]
La salida muestra que hay un 8,4% de probabilidad de que el nuevo documento pertenezca al tema 1 (vea las palabras para el tema 1 en la última salida). De manera similar, hay un 74.4% de probabilidades de que este documento pertenezca al segundo tema. Si nos fijamos en el segundo tema, contiene palabras relacionadas con la Torre Eiffel. Nuestro documento de prueba también contiene palabras relacionadas con estructuras y edificios. Por lo tanto, se le ha asignado el segundo tema.
Otra forma de evaluar el modelo LDA es a través de la perplejidad y la puntuación de coherencia .
Como regla general para un buen modelo LDA, la puntuación de perplejidad debe ser baja, mientras que la coherencia debe ser alta. La biblioteca Gensim tiene una CoherenceModelclase que se puede usar para encontrar la coherencia del modelo LDA. Para la perplejidad, el LdaModelobjeto contiene un log_perplexitymétodo que toma una bolsa de palabras corpus como parámetro y devuelve la perplejidad correspondiente.
print('\nPerplexity:', lda_model.log_perplexity(gensim_corpus))

from gensim.models import CoherenceModel

coherence_score_lda = CoherenceModel(model=lda_model, texts=processed_data, dictionary=gensim_dictionary, coherence='c_v')  
coherence_score = coherence_score_lda.get_coherence()

print('\nCoherence Score:', coherence_score)  
La CoherenceModelclase toma el modelo LDA, el texto en token, el diccionario y el diccionario como parámetros. Para obtener la puntuación de coherencia, get_coherencese utiliza el método. La salida se ve así:
Perplexity: -7.492867099178969

Coherence Score: 0.718387005948207  

Visualizando la LDA

Para visualizar nuestros datos, podemos utilizar la pyLDAvisbiblioteca que descargamos al principio del artículo. La biblioteca contiene un módulo para el modelo Gensim LDA. Primero debemos preparar la visualización pasando el diccionario, una bolsa de palabras corpus y el modelo LDA al preparemétodo. A continuación, tenemos que llamar a la displayen el gensimmódulo de la pyLDAvisbiblioteca, como se muestra a continuación:
gensim_dictionary = gensim.corpora.Dictionary.load('gensim_dictionary.gensim')  
gensim_corpus = pickle.load(open('gensim_corpus_corpus.pkl', 'rb'))  
lda_model = gensim.models.ldamodel.LdaModel.load('gensim_model.gensim')

import pyLDAvis.gensim

lda_visualization = pyLDAvis.gensim.prepare(lda_model, gensim_corpus, gensim_dictionary, sort_topics=False)  
pyLDAvis.display(lda_visualization)  
En la salida, verá la siguiente visualización:
Cada círculo en la imagen anterior corresponde a un tema. A partir de la salida del modelo LDA usando 4 temas, sabemos que el primer tema está relacionado con el Calentamiento Global, el segundo tema está relacionado con la Torre Eiffel, el tercer tema está relacionado con Mona Lisa, mientras que el cuarto tema está relacionado con Artificial Inteligencia.
La distancia entre círculos muestra cuán diferentes son los temas entre sí. Se puede ver que los círculos 2 y 3 se superponen. Esto se debe al hecho de que el tema 2 (Torre Eiffel) y el tema 3 (Mona Lisa) tienen muchas palabras en común, como "francés", "Francia", "Museo", "París", etc.
Si se desplaza sobre cualquier palabra a la derecha, solo verá el círculo del tema que contiene la palabra. Por ejemplo, si pasa el cursor sobre la palabra "clima", verá que los temas 2 y 4 desaparecen, ya que no contienen la palabra clima. El tamaño del tema 1 aumentará, ya que la mayoría de las apariciones de la palabra "clima" están dentro del primer tema. Un porcentaje muy pequeño está en el tema 3, como se muestra en la siguiente imagen:
De manera similar, si presiona el mouse sobre cualquiera de los círculos, a la derecha aparecerá una lista de los términos más frecuentes para ese tema, junto con la frecuencia de aparición en ese mismo tema. Por ejemplo, si se desplaza sobre el círculo 2, que corresponde al tema "Torre Eiffel", verá los siguientes resultados:
Desde la salida, puede ver que se ha seleccionado el círculo para el segundo tema, es decir, "Torre Eiffel". En la lista de la derecha, puede ver los términos más frecuentes para el tema. El término "eiffel" está en la parte superior. Además, es evidente que el término "eiffel" se produjo principalmente dentro de este tema.
Por otro lado, si observa el término "francés", puede ver claramente que alrededor de la mitad de las ocurrencias del término están dentro de este tema. Esto se debe a que el tema 3, es decir, "Mona Lisa" también contiene el término "francés" varias veces. Para verificar esto, haga clic en el círculo del tema 3 y coloque el cursor sobre el término "francés".

Modelado de temas vía LSI

En la sección anterior, vimos cómo realizar el modelado de temas a través de LDA. Veamos cómo podemos realizar el modelado de temas a través de la indexación semántica latente (LSI).
Para hacerlo, todo lo que tienes que hacer es usar la LsiModelclase. El resto del proceso sigue siendo absolutamente similar a lo que seguimos antes con LDA.
Mira el siguiente script:
from gensim.models import LsiModel

lsi_model = LsiModel(gensim_corpus, num_topics=4, id2word=gensim_dictionary)  
topics = lsi_model.print_topics(num_words=10)  
for topic in topics:  
    print(topic)
La salida se ve así:
(0, '-0.337*"intelligence" + -0.297*"machine" + -0.250*"artificial" + -0.240*"problem" + -0.208*"system" + -0.200*"learning" + -0.166*"network" + -0.161*"climate" + -0.159*"research" + -0.153*"change"')

(1, '-0.453*"climate" + -0.377*"change" + -0.344*"warming" + -0.326*"global" + -0.196*"emission" + -0.177*"greenhouse" + -0.168*"effect" + 0.162*"intelligence" + -0.158*"temperature" + 0.143*"machine"')

(2, '0.688*"painting" + 0.346*"leonardo" + 0.179*"louvre" + 0.175*"eiffel" + 0.170*"portrait" + 0.147*"french" + 0.127*"museum" + 0.117*"century" + 0.109*"original" + 0.092*"giocondo"')

(3, '-0.656*"eiffel" + 0.259*"painting" + -0.184*"second" + -0.145*"exposition" + -0.145*"structure" + 0.135*"leonardo" + -0.128*"tallest" + -0.116*"engineer" + -0.112*"french" + -0.107*"design"')

Conclusión

El modelado de temas es una tarea importante de la PNL. Existe una variedad de enfoques y bibliotecas que se pueden usar para el modelado de temas en Python. En este artículo, vimos cómo hacer el modelado de temas a través de la biblioteca Gensim en Python utilizando los enfoques LDA y LSI. También vimos cómo visualizar los resultados de nuestro modelo LDA.