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.
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.
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.
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: 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.
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:
# 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.
['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:
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:
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
'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í '
# 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)
['í', 'í', 'í', 'á', 'í']
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:
# 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'
):
frase = pattern.sub(lambda match: dict_acent[match[0]], frase)
frase
'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.
frase = frase.lower()
frase
'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:
tokens = word_tokenize(frase)
tokens
['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:
# Importacion necesaria
from collections import Counter
# Utilizamos la funcion Counter() sobre nuestra frase tokenizada
Counter(tokens)
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
dict_counter = dict(Counter(tokens))
dict_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}
# 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'})
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 |
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:
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
# 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:
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
# 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
# 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
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:
# 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
# Frase
frase = "The museum was taken care of by the police"
# Tokenizamos
tokens = word_tokenize(frase)
podemos quitarle todas las stopwords como sigue:
# 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
['The', 'museum', 'taken', 'care', 'police']
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