Introducción al topic modeling con Gensim (I): fundamentos y preprocesamiento de textos
En esta serie de artículos que comenzamos hoy aprenderemos como identificar las temáticas de documentos de textos mediante una técnica de procesamiento del lenguaje natural (NLP del inglés natural language processing) denominada topic modeling. Además, aplicaremos este modelo en un ejemplo práctico en el que obtendremos los tópicos de un conjunto de noticias extraídas de diferentes periódicos digitales, usando para ello la librería de Python Gensim.
Dedicaremos esta primera publicación de la serie a explicar esta técnica, centrándonos en el modelo LDA, y a realizar el preprocesamento necesario a los textos para un buen funcionamiento del topic modeling que incluye la tokenización de los textos, la eliminación de las stopwords y el proceso de stemming.
Pero antes de meternos manos a la obra con el código, debemos explicar los conceptos fundamentales del topic modeling. ¡Empezemos!
¿Qué es topic modeling y para que se utiliza?
El topic modeling es una técnica no supervisada de NLP, capaz de detectar y extraer de manera automática relaciones semánticas latentes de grandes volúmenes de información.
Estas relaciones son los llamados tópicos, que son un conjunto de palabras que suelen aparecer juntas en los mismos contextos y nos permiten observar relaciones que seríamos incapaces de observar a simple vista.
Existen diversas técnicas que pueden ser usadas para obtener estos tópicos. El principal algoritmo y que además será el que utilizaremos en esta publicación, es el modelo latent dirichlet allocation (LDA), propuesto por David Blei en 2011, que nos devuelve por un lado los diferentes tópicos que componen la colección de documentos y por otro lado cuánto de cada tópico está presente en cada documento.
Los tópicos consisten en una distribución de probabilidades de aparición de las distintas palabras del vocabulario.
Si quieres saber más sobre el modelo LDA y su funcionamiento, recomendamos leer este artículo de Blei.
A modo de ejemplo ilustrativo y para entender mejor este modelo, imaginemos a un periodista que tiene disponible una gran colección de noticias, pero está interesado únicamente en las noticias relacionadas con la violencia de género.
Utilizando el modelo LDA con el conjunto de noticias disponibles nos podría producir los siguientes resultados:
Vemos que, en el caso de los tópicos, se ha generado el Tópico 0, que contiene la palabra violencia en un 5.7%, la palabra género en un 4.5% y la palabra mujer en un 3.7%. Estas son las palabras que más contribuyen al tópico, por lo que el periodista podría interpretar que este tópico tiene relación con la violencia de género.
Por otro lado, en el caso de los documentos, el algoritmo nos dice que la noticia con el titular Sevilla registra 300 denuncias por violencia de género está compuesta en un 43.34% del Tópico 0. Por lo tanto, al contener este tópico en tal alto porcentaje, el periodista vinculará el artículo a la violencia de género.
Para el ejemplo práctico en Python que viene a continuación, utilizaremos como corpus un conjunto de noticas en castellano al igual que en el ejemplo anterior del periodista.
Obteniendo las noticias con un rastreador web
En este tutorial utilizaremos como corpus 5665 noticias extraídas de distintos periódicos digitales españoles como El Diario o El Mundo. Para este ejemplo y para que podáis ver el poder de esta técnica, esas 5665 noticias nos serán más que suficientes, pero si queréis aplicarlo en un problema real, cuantos más documentos le metamos al modelo, mejor.
Para obtener estas noticas hemos implementado un rastreador web usando el framework Scrapy de Python que se encarga de extraer y almacenar las noticias de las páginas webs que se le indican y que en este caso son algunos de los periódicos más conocidos en España.
Posteriormente hemos exportado esa información como un fichero .txt con el titular de la noticia y su texto. Podéis descargar el conjunto de datos aquí.
En este articulo no se explicará como implementar el rastreador web, pero tranquilos, en este blog ya mostramos en una publicación anterior como extraer paso a paso con Scrapy los datos de un sitio web. Además, en un segundo tutorial explicamos como extraer los datos de manera recursiva de todas las URLs de un dominio.
Preparando los textos para el topic modeling
Es hora de adentrarnos en el topic modeling. Una vez ya tenemos a nuestra disposición los 5665 textos con los que vamos a entrenar nuestro modelo LDA, hay que limpiarlos y preprocesarlos, y para ello vamos a utilizamos la librería NLTK de Python. Este paso es muy importante para asegurarnos que el modelo identifique correctamente las palabras adecuadas y pueda evitar el ruido.
Pero antes de nada, importamos las librerías que vamos a necesitar como las de Gensim y NLTK.
import json, re
import pandas as pd
from nltk.corpus import stopwords
from nltk.stem import SnowballStemmer
from nltk.tokenize import ToktokTokenizer
El siguiente paso será cargar el fichero de texto creado en el anterior paso y convertirlo en un objeto DataFrame
, que nos proporciona la librería pandas, con dos columnas para los titulares y los contenidos de cada noticia.
Una vez hemos construido nuestro DataFrame
, mostramos las primeras cinco noticias mediante la función head()
para asegurarnos de que se han cargado los datos de manera correcta.
with open('noticias.txt') as json_file:
datos = json.load(json_file)
tuplas = list(zip([noticia.get("titular") for noticia in datos],
[noticia.get("texto") for noticia in datos]))
df = pd.DataFrame(tuplas, columns =['Titular', 'Noticia'])
print(df.shape)
df.head()
Una vez tenemos el DataFrame
, es hora de realizar una limpieza inicial de los textos de cada noticia, paso imprescindible antes de cualquier tarea de minería de textos, donde eliminamos los caracteres especiales como ¿ o ¡, las palabras con un solo carácter que normalmente no contienen información útil y convertimos todo a minúsculas entre otras tareas.
En el siguiente código implementamos un método que se encarga de realizar esta limpieza inicial de los textos:
def limpiar_texto(texto):
"""
Función para realizar la limpieza de un texto dado.
"""
# Eliminamos los caracteres especiales
texto = re.sub(r'\W', ' ', str(texto))
# Eliminado las palabras que tengo un solo caracter
texto = re.sub(r'\s+[a-zA-Z]\s+', ' ', texto)
# Sustituir los espacios en blanco en uno solo
texto = re.sub(r'\s+', ' ', texto, flags=re.I)
# Convertimos textos a minusculas
texto = texto.lower()
return texto
Seguidamente aplicamos la función a cada noticia contenida en nuestro objeto y mostramos de nuevo las 5 primeras:
df["Tokens"] = df.Noticia.apply(limpiar_texto)
df.head()
ToktokTokenizer
con la función tokenize()
para tokenizar nuestros textos.tokenizer = ToktokTokenizer()
df["Tokens"] = df.Tokens.apply(tokenizer.tokenize)
df.head()
Bien, a continuación, eliminamos las stopwords que componen los textos, es decir, las palabras comunes que no aportan significado, como yo, el o y. Previamente debemos descargar la lista de stopwords en castellano mediante el comando stopwords.words("spanish")
.
En el siguiente código vemos como construir y aplicar a nuestros datos una función para filtrar fuera de los textos las stopwords y los dígitos:
STOPWORDS = set(stopwords.words("spanish"))
def filtrar_stopword_digitos(tokens):
"""
Filtra stopwords y digitos de una lista de tokens.
"""
return [token for token in tokens if token not in STOPWORDS
and not token.isdigit()]
df["Tokens"] = df.Tokens.apply(filtrar_stopword_digitos)
df.head()
En la esta fase final reducimos cada palabra a su raíz mediante el proceso de stemming. Por ejemplo, este algoritmo reduciría las palabras escribir, escribiendo y escribió a escrib. Para este ejemplo usaré el algoritmo de Porter incluido en la librería NLTK y compatible con el idioma castellano. Para utilizarlo, simplemente creamos un objeto SnowballStemmer
y usamos la función stem()
como podemos ver en el siguiente código:
stemmer = SnowballStemmer("spanish")
def stem_palabras(tokens):
"""
Reduce cada palabra de una lista dada a su raíz.
"""
return [stemmer.stem(token) for token in tokens]
df["Tokens"] = df.Tokens.apply(stem_palabras)
df.head()
A continuación mostramos las primeras diez palabras procesadas de una noticia de nuestra colección a modo de ejemplo:
print(df.Tokens[0][0:10])
¡Estupendo! Ya tenemos los datos listos para construir nuestro diccionario y entrenar el modelo LDA.
En la siguiente publicación de esta serie se explicará como entrenar y aplicar un modelo LDA a nuestros textos y se interpretarán los resultados obtenidos.
Esperamos que este tutorial os haya resultado útil y si tenéis cualquier duda no dudéis en escribirlas en los comentarios. Podéis descargar el cuaderno Jupyter Notebook con el código Python desde mi cuenta de Github.