Procesamiento de lenguaje natural¶

Editado por: Luis Fernando Apáez Álvarez


  • Conceptos teóricos
  • Corpus
  • Tokenizadores
  • Lematizadores
  • Stemmers


Conceptos teóricos ¶

NLP1.png

Imagen generada en DALL E mini by craiyon.com

El procesamiento del lenguaje natural (NLP) estudia las interacciones entre las computadoras y el lenguaje natural, el lenguaje de los humanos.

Vemos múltiples ejemplos del NLP en acción en la vida diaria, como los asistentes virtuales , los traductores de distintos idiomas, la detección de sentimientos en los textos, entre muchas otras más.

Modelos¶

Para el tratado computacional de una lengua, se implica una modelización matemática de la misma para que ésta pueda ser entendida por la máquina, la cual sólo entiende y trabaja con ceros y unos. Además tenemos

  • Modelos lógicos basados en gramáticas: los lingüistas escriben reglas de reconocimiento de patrones estructurales, empleando formalismos gramaticales concretos. Estos modelos buscan reflejar la estrcutura lógica del lenguaje y son combinados con la información de diccionarios computacionales, lo cual permite definir así los patrones que se buscan reconocer para resolver una tarea en específico.

  • Modelos probabilísticos basados en datos: los lingüistas recogen colecciones de ejemplos y datos y a partir de ellos se calculan las frecuencias de diferentes unidades (letras, palabras, oraciones, etcétera) y su probabilidad de aparecer en un contexto determinado. Una vez calculada dicha probabilidad se puede predecir cuál será la siguiente unidad en un contexto dado, sin la necesidad de recurrir a reglas gramaticales explícitas.

Componentes¶

Los siguientes son algunos componentes del NLP, que su aplicación dependerá de cada fin específico:

  • Análisis morfológico: es el análisis interno de las palabras que forman oraciones para así extraer lemas, rasgos flexivos, unidades léxicas compuestas.

  • Análisis sintáctico. consiste en el análisis de la estructura de las oraciones de acuerdo con el modelo gramatical empleado, ya sea el lógico o el probabilístico.

  • Análisis semántico: proporciona la interpretación de las oraciones, una vez eliminadas las ambigüedades morfosintácticas.

  • Análisis pragmático: incorpora el análisis del contexto de uso a la interpretación final. Aquí se incluye el tratamiento del lenguaje figurado como el conocimiento del mundo específico necesario para entender un texto especializado.

Por ejemplo, para un conversor de texto a voz no se necesita el análisis prágmatico. Por otro lado, un sistema conversacional requiere de información muy detallada del contexto y del dominio temático.

Aplicaciones¶

Algunas de las aplicaciones del NLP son:

  • Sistemas conversacionales como siri, cortana o google assistant.

  • Análisis de sentimientos en los textos.

  • Detección automática de tópicos dentro de un documento.

  • Creación automática de resúmenes.

  • Clasificación automática de documentos por categorías.

Corpus ¶

corpus: conjunto lo más extenso y ordenado posible de datos o textos científicos, literarios, etc., que pueden servir de base a una investigación.

De manera más específica, dentro del NLP se trabaja con corpus lingüísticos: recopilación de un conjunto de textos de materiales escritos y/o hablados, agrupados bajo un conjunto de criterios mínimos, para realizar ciertos análisis lingüísticos. Su función principal es establecer la relación entre la teoría y los datos; es decir, un corpus debe cumplir los modelos teóricos con datos reales.

Todo corpus lingüístico es creado con base en determinados requerimientos, según nuestras necesidades y el tipo de análisis que se vaya a llevar a cabo con él. Existen criterior mínimos que el corpus debe cumplir para que la función principal sea llevada de manera existosa:

  • Variedad: los recursos que conforman el corpus deben ser diversos, como provenir de distintas fuentes, épocas, hablantes, etcétera.

  • Representabilidad: Esto se refiere a que el corpus abarque, de la manera más amplia posible, todas las formas y variedades que existen en la lengua en determinada área, tema o tiempo. Es como en probabilidad, cuando se busca analizar una población sumamente grande se toma una muestra, la cual debe representar de manera general a todo el conjunto poblacional. En el caso de corpus lingüísticos, la población es la lengua a analizar mientras que el conjunto de documentos es la muestra a emplear.

  • Equilibrio: El equilibro de un corpus está relacionado con la existencia de una neutralidad en todos los aspectos que se buscan analizar. Y aunque este parámetro es difícil de cumplir, se debe mantener lo más posible, de lo contrario los análisis que se lleven a cabo con el corpus podrían estar sesgados o limitar el uso del corpus en otras investigaciones.

  • Tamaño: Un corpus debe tener un tamaño que permita obtener resultados significativos. Según MacMullen (2003) los corpus muy grandes no son necesariamente representativos; de hecho pueden causar problemas para encontrar elementos menos abundantes pero más importantes.

  • Manejable por la computadora: En la actualidad muchos de los corpus lingüísticos se encuentran de manera digital; esto se recomienda ya que se pueden obtener beneficios de ello, por ejemplo se pueden realizar análisis o búsquedas de manera más rápida.

  • Derechos de autor: Este se debe de tener en cuenta cuando se realiza un corpus. La razón de ello se debe a que es necesario no violar las leyes o normatividades con respecto al uso o reproducción de documentos, ya que un corpus está conformado por documentos creados por distintas personas.

Tokenizadores ¶

Antes de realizar cualquier análisis lingüístico o de procesar un documento es necesario encontrar y separar cada uno de los elementos que lo conforman, para ello se emplean los tokenizadores. Los tokenizadores (también conocidos como analizadores léxicos o segmentadores de palabras) segmentan un conjunto de caracteres en unidades con significado llamados tokens (Jackson y Moulinier, 2002). Por ejemplo, podemos realizar la tokenización de la oración "El día está nublado, tal vez llueva" y obtener:

In [6]:
# Importamos la libreria nltk (muy importante para el NLP) 
import nltk
# Importamos la funcion word_tokenize() para tokenizar
from nltk import word_tokenize
# Descargamos el componente necesario
nltk.download('punkt')

# Cadena
msj = "El día está nublado, tal vez llueva"

# Realizamos la tokenizacion
word_tokenize(msj)
[nltk_data] Downloading package punkt to
[nltk_data]     C:\Users\usuario\AppData\Roaming\nltk_data...
[nltk_data]   Unzipping tokenizers\punkt.zip.
Out[6]:
['El', 'día', 'está', 'nublado', ',', 'tal', 'vez', 'llueva']

Vemos entonces que la tokenización de "El día está nublado, tal vez llueva" es:

El - día - está - nublado - , - tal -vez - llueva

Notamos en este caso que la coma no está aportando información relevante, siendo esto un aspecto a considerar en la limpieza necesaria de nuestros textos para extraer sólo los componentes más importantes, lo cual se verá en clases posteriores.

Así, tenemos entonces:

In [8]:
frase = "El día está nublado, tal vez llueva"
tokens = word_tokenize(msj)
print(f'Frase: {frase}\nTokens: {tokens}')
Frase: El día está nublado, tal vez llueva
Tokens: ['El', 'día', 'está', 'nublado', ',', 'tal', 'vez', 'llueva']

Además, podemos contar el número de apariciones de cada palabra dentro de un documento, para lo cual primero debemos tokenizar el documento.

Por ejemplo, de la frase "Y es en este día que nos encontramos, tal como lo habíamos prometido. El día no puede estar más reluciente, tal vez llueva al rato, tal vez no. El hecho es que estamos aquí."

al tokenizarla podemos después contar cuántas veces aparece cada token. Antes de ello quitaremos las comas, los puntos y los acentos, siendo lo anterior un pequeño proceso de limpieza:

In [13]:
frase = """Y es en este día que nos encontramos, tal como lo habíamos prometido.
El día no puede estar más reluciente, tal vez llueva al rato, tal vez no. 
El hecho es que estamos aquí."""

# Importamos la libreria para trabajar con expresiones regulares
import re

# Primero quitamos todos los caracteres que no son alfanumericos
# y los sustituimos por espacios en blanco
frase = re.sub(r'[\W]+', ' ', frase)
frase
Out[13]:
'Y es en este día que nos encontramos tal como lo habíamos prometido El día no puede estar más reluciente tal vez llueva al rato tal vez no El hecho es que estamos aquí '
In [14]:
# Creamos un patron para hallar todas las letras de la frase
# que tienen acentos
pattern = re.compile(r'[áéíóú]')
# Buscamos todas las coincidencias
pattern.findall(frase)
Out[14]:
['í', 'í', 'í', 'á', 'í']

Notamos que en la frase hay 5 letras acentuadas, de las cuales sólo son la í y la á, con base en lo anterior creamos un diccionario para posteriormente hacer las sustituciones:

In [15]:
# Creamos un diccionario con las vocales acentuadas y no acentuadas
dict_acent = {'á': 'a',
              'í': 'i'}

Luego, para cada match hallado con nuestro patrón, es decir, para cada coincidencia (['í', 'í', 'í', 'á', 'í']) del patrón en el texto, sustituiremos dicho match por el value asociado en el diccionario dict_acent. Por ejemplo cuando el match sea 'í', se sustituirá por 'i' (dict_acent['í']-->'i'):

In [16]:
frase = pattern.sub(lambda match: dict_acent[match[0]], frase)
frase
Out[16]:
'Y es en este dia que nos encontramos tal como lo habiamos prometido El dia no puede estar mas reluciente tal vez llueva al rato tal vez no El hecho es que estamos aqui '

Donde vemos que ya no hay letras acentuadas, finalmente convertimos todas las letras a minúsculas.

In [17]:
frase = frase.lower()
frase
Out[17]:
'y es en este dia que nos encontramos tal como lo habiamos prometido el dia no puede estar mas reluciente tal vez llueva al rato tal vez no el hecho es que estamos aqui '

Procedemos a tokenizar la frase:

In [18]:
tokens = word_tokenize(frase)
tokens
Out[18]:
['y',
 'es',
 'en',
 'este',
 'dia',
 'que',
 'nos',
 'encontramos',
 'tal',
 'como',
 'lo',
 'habiamos',
 'prometido',
 'el',
 'dia',
 'no',
 'puede',
 'estar',
 'mas',
 'reluciente',
 'tal',
 'vez',
 'llueva',
 'al',
 'rato',
 'tal',
 'vez',
 'no',
 'el',
 'hecho',
 'es',
 'que',
 'estamos',
 'aqui']

Luego, para contar el número de apariciones de cada palabra utilizaremos la función Counter() de la librería collections como sigue:

In [33]:
# Importacion necesaria
from collections import Counter

# Utilizamos la funcion Counter() sobre nuestra frase tokenizada
Counter(tokens)
Out[33]:
Counter({'y': 1,
         'es': 2,
         'en': 1,
         'este': 1,
         'dia': 2,
         'que': 2,
         'nos': 1,
         'encontramos': 1,
         'tal': 3,
         'como': 1,
         'lo': 1,
         'habiamos': 1,
         'prometido': 1,
         'el': 2,
         'no': 2,
         'puede': 1,
         'estar': 1,
         'mas': 1,
         'reluciente': 1,
         'vez': 2,
         'llueva': 1,
         'al': 1,
         'rato': 1,
         'hecho': 1,
         'estamos': 1,
         'aqui': 1})

Convertimos el resultado anterior a un diccionario para posteriormente convertirlo a un dataframe

In [35]:
dict_counter = dict(Counter(tokens))
dict_counter
Out[35]:
{'y': 1,
 'es': 2,
 'en': 1,
 'este': 1,
 'dia': 2,
 'que': 2,
 'nos': 1,
 'encontramos': 1,
 'tal': 3,
 'como': 1,
 'lo': 1,
 'habiamos': 1,
 'prometido': 1,
 'el': 2,
 'no': 2,
 'puede': 1,
 'estar': 1,
 'mas': 1,
 'reluciente': 1,
 'vez': 2,
 'llueva': 1,
 'al': 1,
 'rato': 1,
 'hecho': 1,
 'estamos': 1,
 'aqui': 1}
In [39]:
# Podemos visualizar mejor convirtiendo lo anterior a una dataframe
import pandas as pd
df_freq = pd.DataFrame([dict_counter]).transpose().reset_index()
df_freq.rename(columns={'index': 'Tokens', 0: 'Frecuencia'})
Out[39]:
Tokens Frecuencia
0 y 1
1 es 2
2 en 1
3 este 1
4 dia 2
5 que 2
6 nos 1
7 encontramos 1
8 tal 3
9 como 1
10 lo 1
11 habiamos 1
12 prometido 1
13 el 2
14 no 2
15 puede 1
16 estar 1
17 mas 1
18 reluciente 1
19 vez 2
20 llueva 1
21 al 1
22 rato 1
23 hecho 1
24 estamos 1
25 aqui 1

Lematizadores ¶

En todo documento escrito en una lengua flexiva, como el español y el italiano, existen variaciones léxicas de las palabras, es decir, se pueden tener casos como "escribimos", "democracias", "industrialización", etcétera. Pero dentro del procesamiento de lenguaje natural es necesario disminuir la cantidad de variaciones léxicas que existan en los documentos a analizar. Para ello se debe obtener el lema o forma canónica, es decir, la base o la forma de diccionario de una palabra (Manning et al., 2008).

El proceso de lematización se lleva a cabo de manera automática por parte de los humanos; cuando queremos buscar las palabras "encontramos" o "niñas" en un diccionario las pasamos a "encontrar" y "niño", para ello empleamos nuestro conocimiento del mundo.

Para reducir el número de variaciones léxicas, ya sea por flexiones (caminar → caminamos) o por derivaciones (activar → activación), existen dos herramientas que se emplean en NLP, que son:

  • Los lematizadores.
  • Los truncadores o stemmers.

Aunque frecuentemente se confunden ambos términos, cabe aclarar que son dos métodos distintos. Los lematizadores son herramientas que emplean diccionarios, al igual que reglas, que buscan obtener el lema de las palabras; además estas herramientas realizan un etiquetado para conocer la categoría gramatical de las palabras.

Por ejemplo, podemos considera la frase "The museum was taken care of by the police", al ser lematizado se obtiene

In [47]:
# Importaciones necesarias
from nltk.stem import WordNetLemmatizer

# Componentes necesarios
nltk.download('wordnet')
nltk.download('omw-1.4')

# Instanciamos
lemattizer = WordNetLemmatizer()

# Frase
frase = "The museum was taken care of by the police"

# Tokenizamos
tokens = word_tokenize(frase)

# Lematizamos cada palabra
for token in tokens:
    print(lemattizer.lemmatize(token))
The
museum
wa
taken
care
of
by
the
police
[nltk_data] Downloading package wordnet to
[nltk_data]     C:\Users\usuario\AppData\Roaming\nltk_data...
[nltk_data]   Package wordnet is already up-to-date!
[nltk_data] Downloading package omw-1.4 to
[nltk_data]     C:\Users\usuario\AppData\Roaming\nltk_data...
[nltk_data]   Package omw-1.4 is already up-to-date!

donde el único cambio fue de was a wa. El hecho de trabajar con una frase en ingles es que en Python no hay lematizadores para palabras al español (creo). Un ejemplo de lematización de palabras en español podría ser:

  • Frase: "El museo fue cuidado por los policías"
  • Lemas: el-museo-ser-cuidar-por-el-policia

Stemmers ¶

Los truncadores, en cambio, son herramientas que emplean la heurística, es decir, ciertas reglas, para cortar las partes finales de las palabras, con el fin de llegar a su lema; estas herramientas no usan ni analizadores morfológicos o etiquetadores, aunque hay truncadores que incluyen reglas para eliminar afijos derivacionales, como "-ción", "-ía".

Al no realizar un análisis morfológico o un etiquetado los stemmers no encuentran la diferencia entre "cuidado" que proviene del verbo y "cuidado" que es un sustantivo, como lo hacen los lematizadores. De igual forma, el problema de los stemmers es que el hecho de cortar las partes finales de las palabras no siempre implica que se obtendrá la forma canónica correcta. Por ejemplo, de la palabra "torres" el lema obtenido al truncar sería "torr", el cual es incorrecto. La mayor ventaja que tienen los stemmers sobre los lematizadores es la velocidad de procesamiento. Existen varios algorítmos para truncar, algunos de ellos son

In [48]:
# Algoritmo de Porter
PSt = nltk.stem.PorterStemmer()
# Algoritmo de Lancaster
LSt = nltk.stem.LancasterStemmer()

Por ejemplo, para la frase "The museum was taken care of by the police" podemos truncar con los dos algorítmos anteriores como sigue

In [50]:
# Frase
frase = "The museum was taken care of by the police"

# Tokenizamos
tokens = word_tokenize(frase)

# Truncamiento cada palabra
for token in tokens:
    #     Porter                  Lancaster
    print(PSt.stem(token), "-->", LSt.stem(token))
the --> the
museum --> muse
wa --> was
taken --> tak
care --> car
of --> of
by --> by
the --> the
polic --> pol

Stopswords ¶

Las palabras funcionales, palabras vacías o stop words, son las palabras que "carecen" de significado. Estas palabras son las de mayor frecuencia y las que aportan la menor cantidad de información, entre ellas se encuentran los artículos, las preposiciones, las conjunciones, entre otras. De las palabras funcionales se crean listas de paro o stoplists.

Según Pazienza et al. (2005), las palabras funcionales son automáticamente extraídas de un corpus genérico como aquellas con la más alta frecuencia, y posteriormente son validadas por expertos humanos. De igual manera, se pueden agregar algunas otras palabras que se desean eliminar en NLP. El objetivo de emplear stoplists en NLP es reducir la cantidad de datos a analizar. De igual manera disminuye el espacio en memoria o en disco empleado por las herramientas que analizan lenguaje natural. Por ejemplo, de la frase "El monitor de esa computadora se descompuso ", la limpieza después de las stopwords es: monitor-computadora-descompuso.

Por ejemplo, podemos ver todas las stopwords que son consideradas en el inglés:

In [54]:
# Importacion necesaria
from nltk.corpus import stopwords

# Componente necesario
nltk.download('stopwords')

# Stopwords del idioma ingles
stopwords_list = list(stopwords.words('english'))
for st in stopwords_list:
    print(st, end=", ")
i, me, my, myself, we, our, ours, ourselves, you, you're, you've, you'll, you'd, your, yours, yourself, yourselves, he, him, his, himself, she, she's, her, hers, herself, it, it's, its, itself, they, them, their, theirs, themselves, what, which, who, whom, this, that, that'll, these, those, am, is, are, was, were, be, been, being, have, has, had, having, do, does, did, doing, a, an, the, and, but, if, or, because, as, until, while, of, at, by, for, with, about, against, between, into, through, during, before, after, above, below, to, from, up, down, in, out, on, off, over, under, again, further, then, once, here, there, when, where, why, how, all, any, both, each, few, more, most, other, some, such, no, nor, not, only, own, same, so, than, too, very, s, t, can, will, just, don, don't, should, should've, now, d, ll, m, o, re, ve, y, ain, aren, aren't, couldn, couldn't, didn, didn't, doesn, doesn't, hadn, hadn't, hasn, hasn't, haven, haven't, isn, isn't, ma, mightn, mightn't, mustn, mustn't, needn, needn't, shan, shan't, shouldn, shouldn't, wasn, wasn't, weren, weren't, won, won't, wouldn, wouldn't, 
[nltk_data] Downloading package stopwords to
[nltk_data]     C:\Users\usuario\AppData\Roaming\nltk_data...
[nltk_data]   Package stopwords is already up-to-date!

Así, trabajando con la misma frase en inglés con la cual hemos estado trabajando

In [55]:
# Frase
frase = "The museum was taken care of by the police"

# Tokenizamos
tokens = word_tokenize(frase)

podemos quitarle todas las stopwords como sigue:

In [58]:
# Lista auxiliar para almacenar todos los tokens que
# no son stopswords
tokens_no_stopwords = []
# Recorremos cada token
for token in tokens:
    # Seleccionamos solo los tokens que no son stopwords
    if token not in stopwords_list:
        tokens_no_stopwords.append(token)
tokens_no_stopwords
Out[58]:
['The', 'museum', 'taken', 'care', 'police']
In [62]:
print(f'Frase original: {frase}')
print(f'Frase sin las stopwords: {" ".join(tokens_no_stopwords)}')
Frase original: The museum was taken care of by the police
Frase sin las stopwords: The museum taken care police

Recursos

  • Procesamiento del lenguaje natural