Forum

Por favor, o Regístrate para crear mensajes y debates.

Reducir el número de palabras de un texto: lematización y radicalización (stemming) con Python


Pizarra con una oración escrita
Imagen cortesía de 123RF.com

El siguiente artículo es una breve guía práctica de cómo y por qué hacer una lematización o un stemming a un texto. Estos procedimientos de Procesamiento de Lenguaje Natural (PLN) pueden ser muy útiles en diversas tareas, sobre todo en las de clasificar o recuperar de información. Son procedimientos que se pueden aprovechar también para extraer información de bases de datos de texto (por ejemplo de documentos de MongoDB).

¿Para qué necesito reducir el número de palabras en mis textos?

Podemos ver un texto como una unidad de comunicación humana bastante compleja. Un documento, hasta el más pequeño, está hecho no sólo de palabras, sino de un sinnúmero de relaciones semánticas que solo pueden ser descodificadas por quienes dominan ciertos códigos. En fin, que son un desastre y un dolor de cabeza para quienes están acostumbrados a la información estructurada (en tablas o esquemas, por ejemplo).

Extraer automáticamente información relevante de los textos es una tarea trivial para un ser humano, pero un verdadero reto para una máquina. Muchas veces no nos interesa conocer todos los significados de un texto, sino solamente algunos pertinentes para realizar una tarea. Aunque las computadoras (aún) no entienden el lenguaje natural, son muy competentes leyendo superficialmente grandes cantidades de texto en segundos.

Una buena técnica para obtener la información relevante de un texto consiste en eliminar los elementos que puedan ser irrelevantes, y resaltar más lo que los textos tienen en común que sus diferencias. Y esto es lo que te vamos a mostrar precisamente aquí.

Tokenización

Vamos a eliminar las palabras que tienen poco interés para nosotros. El primer paso es delimitar las palabras del texto, y convertir esas palabras en elementos de una lista. Este procedimiento es conocido como tokenización. Vamos a implementar esto en Python. Utilizaremos una librería estupenda para hacer Procesamiento de Lenguaje Natural llamada spacy. Puedes instalar spacy con pip $ pip install -U spacy o con conda $ conda install -c conda-forge spacy. Además de la librería, debes descargar el modelo de la lengua que vas a utilizar. Para descargar el modelo del español, por ejemplo, escribe en tu terminal lo siguiente: $ python -m spacy download es

import spacy
nlp = spacy.load(‘es_core_news_sm’)

text = “””Soy un texto. Normalmente soy más largo y más grande. Que no te engañe mi tamaño.”””

doc = nlp(text) # Crea un objeto de spacy tipo nlp

tokens = [t.orth_ for t in doc] # Crea una lista con las palabras del texto

¡Ya tenemos nuestro texto convertido en una lista de tokens! Lamentablemente, están todas las palabras. También podemos notar que se incluyen como tokens los signos de puntuación. Y nosotros queremos quedarnos solo con aquellas que sean más o menos representativas del texto. ¿Qué podemos hacer? Vamos a eliminar de esa lista las palabras muy comunes o poco informativas.

Limpieza del texto

Para ello, seguiremos usando spacy. Lo que vamos a hacer es pedirle a esta librería que construya la lista de tokens pero que no incluya palabras muy comunes y poco informativas desde el punto de vista léxico, tales como conjunciones (y, o, ni, que), preposiciones (a, en, para, por, entre otras) y verbos muy comunes (ser, ir, y otros más). ¿Estás listo? Es muy sencillo. Vamos a utilizar una condición que diga “todos los tokens del texto, siempre y cuando no se incluyan la puntuación ni las palabras poco representativas (stopwords)”.

import spacy
nlp = spacy.load(‘es_core_news_sm’)

text = “””Soy un texto. Normalmente soy más largo y más grande. Que no te engañe mi tamaño.”””

doc = nlp(text)

lexical_tokens = [t.orth_ for t in doc if not t.is_punct | t.is_stop]

¡Listo! Ya tenemos una lista de palabras que realmente podrían ser pistas sobre el tópico del texto, o que nos permitan clasificarlo más fácilmente.

Normalizar un texto con Python

El siguiente paso en nuestro flujo de trabajo consiste en normalizar el texto. Nuestro tokenizador reconoce formas como caminar, Caminar y CAMINAR como formas distintas. Además, el documento puede tener números y palabras compuestas por caracteres alfanuméricos y otros símbolos tales como #Ar1anaG. Si no nos interesan estas palabras, y queremos que en nuestra lista aparezcan solamente las formas convencionales (por ejemplo, caminar, sólo en minúsculas) debemos normalizar nuestro texto. Aprovecharemos el momentum para descartar palabras muy cortas (menores a 4 caracteres) para filtrar aún más nuestros tokens.

words = [t.lower() for t in lexical_tokens if len(t) > 3 and t.isalpha()]

Ahora podemos poner todo junto en una función:

import spacy
nlp = spacy.load(‘es_core_news_sm’)

def normalize(text):
doc = nlp(text)
words = [t.orth_ for t in doc if not t.is_punct | t.is_stop]
lexical_tokens = [t.lower() for t in words if len(t) > 3 and
t.isalpha()]

return lexical_tokens

word_list = normalize(“Soy un texto de prueba. ¿Cuántos tokens me quedarán después de la normalización?”)

¡Buen trabajo! Con eso hemos reducido notablemente nuestros textos. Ya sea que necesitemos buscar por palabra clave, o extraer features para implementar un algoritmo de aprendizaje automático (machine learning), estos textos tokenizados serán de mucha ayuda. Pero podemos hacerlo todavía mejor, ¿no?

¿En qué consiste la lematización?

A pesar de que ya tenemos una lista reducida de palabras, podemos achicarla aun más. Si haces la prueba con textos más extensos que el que usamos en el ejemplo, podrás observar que hay varias palabras diferentes en representación de una misma palabra. Ya va, ¿qué? Sí, así mismo. En español, por ejemplo, sabemos que canto, cantas, canta, cantamos, cantáis, cantan son distintas formas (conjugaciones) de un mismo verbo (cantar). Y que niña, niño, niñita, niños, niñotes, y otras más, son distintas formas del vocablo niño. Así que sería genial poder obviar las diferencias y juntar todas estas variantes en un mismo término. Y eso es precisamente lo que hace la lematización: relaciona una palabra flexionada o derivada con su forma canónica o lema. Y un lema no es otra cosa que la forma que tienen las palabras cuando las buscas en el diccionario.

import spacy
nlp = spacy.load(‘es_core_news_sm’)

text = “””Soy un texto que pide a gritos que lo procesen. Por eso yo canto, tú cantas, ella canta, nosotros cantamos, cantáis, cantan…”””

doc = nlp(text)

lemmas = [tok.lemma_.lower() for tok in doc]

Ya tienes tu lista de lemas completa.

Como el proceso de lematización toma en consideración la probable clase de palabra (adjetivo, verbo, sustantivo…) — también llamados POS — podemos usar dicha información para filtrar nuestra lista de lemas. La siguiente línea excluye los pronombres de nuestra lista de lemas:lemmas_no_pron = [tok.lemma_.lower() for tok in doc if tok.pos_ != ‘PRON’]

La lematización es un proceso clave en muchas tareas prácticas de PLN, pero tiene dos costos. Primero, es un proceso que consume recursos (sobre todo tiempo). Segundo, suele ser probabilística, así que en algunos casos obtendremos resultados inesperados.

Y ahora, con las raíces…

Se llama stemming al procedimiento de convertir palabras en raíces. Estas raíces son la parte invariable de palabras relacionadas sobre todo por su forma. De cierta manera se parece a la lematización, pero los resultados (las raíces) no tienen por qué ser palabras de un idioma. Por ejemplo, el algoritmo de stemming puede decidir que la raíz de amamos no es am- (la raíz que ) sino amam- (cosa que desconcertaría a mas de uno). Aquí va un ejemplo de stemming:

import nltk
from nltk import SnowballStemmer

spanishstemmer=SnowballStemmer(‘spanish’)

text = “””Soy un texto que pide a gritos que lo procesen. Por eso yo canto, tú cantas, ella canta, nosotros cantamos, cantáis, cantan…”””

tokens = normalize(text) # crear una lista de tokens

stems = [spanishstemmer.stem(token) for token in tokens]

La lista resultante de raíces es: [text’, ‘pid’, ‘grit’, ‘proces’, ‘cant’, ‘cant’, ‘cant’, ‘cant’, ‘cant’, ‘cant’.]

Como habrás podido notar, para encontrar las raíces en español nos hemos valido de otra librería de Python llamada nltk. Es otra librería fundamental para el procesamiento de lenguaje natural. En nltk hay muchas funciones para tareas de este tipo, en varios idiomas. En el ejemplo hemos utilizado el Snowball Stemmer porque funciona no sólo en inglés, sino en otras lenguas.

Además del snowball, nltk permite usar otros algoritmos, como el Porter o el Lancaster.

El stemming es mucho más rápido desde el punto de vista del procesamiento que la lematización. También tiene como ventaja que reconoce relaciones entre palabras de distinta clase. Podría reconocer, por ejemplo, que picante y picar tienen como raíz pic-. En otras palabras, el stemming puede reducir el número de elementos que forman nuestros textos. Y eso, en muchos casos, es lo que buscamos.

Una desventaja del stemming es que sus algoritmos son más simples que los de lematización. Pueden “recortar” demasiado la raíz y encontrar relaciones entre palabras que realmente no existen (overstemming). También puede suceder que deje raíces demasiado extensas o específicas, y que tengamos más bien un déficit de raíces (understemming), en cuyo caso palabras que deberían convertirse en una misma raíz no lo hacen. No hay mucho que hacer con eso, pero el stemming es una muy buena solución de compromiso en la mayoría de los casos.

Por cierto, podemos quedarnos solo con una ocurrencia de cualquiera de las listas anteriores utilizando set(nombre_de_la_lista) en Python. Eso nos genera un precioso vocabulario del texto. Genial, ¿no?


La tokenización, normalización, lematización y “radicalización” de un texto suelen ser procedimientos fundamentales para muchas tareas relacionadas con la extracción automática de features y de datos de los textos. Estos procedimientos están en la base de los grandes y pequeños buscadores de información. El stemming suele ser una buena solución cuando no importa demasiado la precisión y se requiere de un procesamiento eficiente. La lematización suele funcionar mejor cuando se necesita procesar palabras de manera similar a como lo hace un ser humano. Haz pruebas de precisión y rendimiento y quédate con el procedimiento que mejor se adapte a tus textos y requerimientos.