Esta notebook fue creada originalmente como un blog post por Raúl E. López Briega en Matemáticas, Analisis de datos y Python. El contenido esta bajo la licencia BSD.
"El lenguaje sirve no sólo para expresar el pensamiento, sino para hacer posibles pensamientos que no podrían existir sin él."
El lenguaje es una de las herramientas centrales en nuestra vida social y profesional. Entre otras cosas, actúa como un medio para transmitir ideas, información, opiniones y sentimientos; así como para persuadir, pedir información, o dar ordenes. Asimismo, el lenguaje humano es algo que esta en constante cambio y evolución; y que puede llegar a ser muy ambiguo y variable. Tomemos por ejemplo la frase "comí una pizza con amigos" comparada con "comí una pizza con aceitunas"; su estructura es la misma, pero su significado es totalmente distinto. De la misma manera, un mismo mensaje puede ser expresado de formas diferentes; "comí una pizza con amigos" puede también ser expresado como "compartí una pizza con amigos".
Los seres humanos somos muy buenos a la hora de producir e interpretar el lenguaje, podemos expresar, percibir e interpretar significados muy elaborados en fracción de segundos casi sin dificultades; pero al mismo tiempo, somos también muy malos a la hora de entender y describir formalmente las reglas que lo gobiernan. Por este motivo, entender y producir el lenguaje por medio de una computadora es un problema muy difícil de resolver. Éste problema, es el campo de estudio de lo que en inteligencia artificial se conoce como Procesamiento del Lenguaje Natural o NLP por sus siglas en inglés.
El Procesamiento del Lenguaje Natural o NLP es una disciplina que se encuentra en la intersección de varias ciencias, tales como las Ciencias de la Computación, la Inteligencia Artificial y Psicología Cognitiva. Su idea central es la de darle a las máquinas la capacidad de leer y comprender los idiomas que hablamos los humanos. La investigación del Procesamiento del Lenguaje Natural tiene como objetivo responder a la pregunta de cómo las personas son capaces de comprender el significado de una oración oral / escrita y cómo las personas entienden lo que sucedió, cuándo y dónde sucedió; y las diferencias entre una suposición, una creencia o un hecho.
En general, en Procesamiento del Lenguaje Natural se utilizan seis niveles de comprensión con el objetivo de descubrir el significado del discurso. Estos niveles son:
Nivel fonético: Aquí se presta atención a la fonética, la forma en que las palabras son pronunciadas. Este nivel es importante cuando procesamos la palabra hablada, no así cuando trabajamos con texto escrito.
Nivel morfológico: Aquí nos interesa realizar un análisis morfológico del discurso; estudiar la estructura de las palabras para delimitarlas y clasificarlas.
Nivel sintáctico: Aquí se realiza un análisis de sintaxis, el cual incluye la acción de dividir una oración en cada uno de sus componentes.
Nivel semántico: Este nivel es un complemente del anterior, en el análisis semántico se busca entender el significado de la oración. Las palabras pueden tener múltiples significados, la idea es identificar el significado apropiado por medio del contexto de la oración.
Nivel discursivo: El nivel discursivo examina el significado de la oración en relación a otra oración en el texto o párrafo del mismo documento.
Nivel pragmático: Este nivel se ocupa del análisis de oraciones y cómo se usan en diferentes situaciones. Además, también cómo su significado cambia dependiendo de la situación.
Todos los niveles descritos aquí son inseparables y se complementan entre sí. El objetivo de los sistemas de NLP es incluir estas definiciones en una computadora y luego usarlas para crear una oración estructurada y sin ambigüedades con un significado bien definido.
Los algoritmos de Procesamiento del Lenguaje Natural suelen basarse en algoritmos de aprendizaje automático. En lugar de codificar manualmente grandes conjuntos de reglas, el NLP puede confiar en el aprendizaje automático para aprender estas reglas automáticamente analizando un conjunto de ejemplos y haciendo una inferencia estadística. En general, cuanto más datos analizados, más preciso será el modelo. Estos algoritmos pueden ser utilizados en algunas de las siguientes aplicaciones:
Resumir texto: Podemos utilizar los modelos de NLP para extraer las ideas más importantes y centrales mientras ignoramos la información irrelevante.
Crear chatbots: Podemos utilizar las técnicas de NLP para crear chatbots que puedan interactuar con las personas.
Generar automáticamente etiquetas de palabras clave: Con NLP también podemos realizar un análisis de contenido aprovechando el algoritmo de LDA para asignar palabras claves a párrafos del texto.
Reconocer entidades: Con NLP podemos identificar a las distintas entidades del texto como ser una persona, lugar u organización.
Análisis de sentimiento: También podemos utilizar NLP para identificar el sentimiento de una cadena de texto, desde muy negativo a neutral y a muy positivo.
Actualmente, Python es uno de los lenguajes más populares para trabajar en el campo la Inteligencia Artificial. Para abordar los problemas relacionados con el Procesamiento del Lenguaje Natural Python nos proporciona las siguientes librerías:
Como veremos, el NLP también se está sumando a la popularidad del Deep Learning, por lo que muchos de los frameworks que se utilizan en Deep Learning pueden ser aplicados para realizar modelos de NLP.
Hoy en día, es indispensable el uso de buenos recursos lingüísticos para el desarrollo de los sistemas de NLP. Estos recursos son esenciales para la creación de gramáticas, en el marco de aproximaciones simbólicas; o para llevar a cabo la formación de módulos basados en el aprendizaje automático.
Un corpus lingüístico es un conjunto amplio y estructurado de ejemplos reales de uso de la lengua. Estos ejemplos pueden ser textos (los más comunes), o muestras orales (generalmente transcritas). Un corpus lingüístico es un conjunto de textos relativamente grande, creado independientemente de sus posibles formas o usos. Es decir, en cuanto a su estructura, variedad y complejidad, un corpus debe reflejar una lengua, o su modalidad, de la forma más exacta posible; en cuanto a su uso, preocuparse de que su representación sea real. La idea es que representen al lenguaje de la mejor forma posible para que los modelos de NLP puedan aprender los patrones necesarios para entender el lenguaje. Encontrar un buen corpus sobre el cual trabajar no suele ser una tarea sencilla; un corpus que se suele utilizar para entrenar modelos es el que incluye la información extraída de wikipedia.
Hasta aquí la introducción, ahora llegó el momento de ensuciarse un poco las manos y comenzar a explorar algunos ejemplos de las herramientas que nos ofrece Python para trabajar con problemas de Procesamiento del Lenguaje Natural. Comencemos por ejemplo, descargando el corpus de wikipedia en español. Esto lo podemos hacer fácilmente utilizando Textacy.
In [1]:
# <!-- collapse=True -->
# Importando las librerías que vamos a utilizar
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import textacy
from textacy.datasets import Wikipedia
from collections import Counter, defaultdict
import warnings; warnings.simplefilter('ignore')
# graficos incrustados
%matplotlib inline
# función auxiliar
def leer_texto(texto):
"""Funcion auxiliar para leer un archivo de texto"""
with open(texto, 'r') as text:
return text.read()
In [2]:
# Descargando copus de wikipedia
wp = Wikipedia(data_dir='/home/raul/Documents/data', lang='es', version='latest')
wp.download()
In [3]:
# Chequeando la información descargada
wp.info
Out[3]:
In [4]:
for text in wp.texts(min_len=1000, limit=2):
print(text[:375], "\n")
Como podemos ver, con la ayuda de Textacy es muy fácil descargar la información de wikipedia para luego poder utilizarla de base para nuestro corpus. Veamos otros problemas que también podemos resolver con la ayuda de Textacy; como ser los casos de detectar el idioma o de procesar todo un texto y analizarlo.
In [5]:
# Detectando el idioma con taxtacy
saludos = ["Hola", "Hello", "Bonjour", "Guten Tag", "Buon giorno", "Bom dia"]
for saludo in saludos:
print(textacy.text_utils.detect_language(saludo))
In [6]:
# Cargando el modelo en español de spacy
nlp = textacy.data.spacy.load('es_core_web_md')
In [7]:
# detalle de stop words
# las stop words son las palabras más comunes de un corpus que en general
# queremos eliminar porque no son significativas para un análisis.
# Ocurren muchas veces, pero aportan muy poca información
stop = list(textacy.data.spacy.es.STOP_WORDS)
stop[:15]
Out[7]:
In [8]:
# Procesando un texto
# Procesando 1984 de George Orwell - mi novela favorita
texto = leer_texto('ORWELL_1984.txt')
texto_procesado = nlp(texto)
In [9]:
# Cuántas sentencias hay en el texto?
sentencias = [s for s in texto_procesado.sents]
print(len(sentencias))
In [10]:
# imprimir las primeras 10 sentencias para verificar el texto
print(sentencias[1:11])
In [11]:
# sentencias con las que aparece el Gran Hermano
[sent for sent in texto_procesado.sents if 'Gran Hermano' in sent.string][-10:]
Out[11]:
In [12]:
# <!-- collapse=True -->
def encontrar_personajes(doc):
"""
Devuelve una lista de los personajes de un `doc` con su cantidad de
ocurrencias
:param doc: NLP documento parseado por Spacy
:return: Lista de Tuplas con la forma
[('winston', 686), ("o'brien", 135), ('julia', 85),]
"""
personajes = Counter()
for ent in doc.ents:
if ent.label_ == 'PERSON':
personajes[ent.lemma_] += 1
return personajes.most_common()
In [13]:
# Extrayendo los personajes principales del texto y contando cuantas veces
# son nombrados.
print(encontrar_personajes(texto_procesado)[:20])
In [14]:
# <!-- collapse=True -->
def obtener_adj_pers(doc, personaje):
"""
Encontrar todos los adjetivos relacionados a un personaje en un `doc`
:param doc: NLP documento parseado por Spacy
:param personaje: un objeto String
:return: lista de adjetivos relacionados a un `personaje`
"""
adjetivos = []
for ent in doc.ents:
if ent.lemma_ == personaje:
for token in ent.subtree:
if token.pos_ == 'ADJ':
adjetivos.append(token.lemma_)
for ent in doc.ents:
if ent.lemma_ == personaje:
if ent.root.dep_ == 'nsubj':
for child in ent.root.head.children:
if child.dep_ == 'acomp':
adjetivos.append(child.lemma_)
return adjetivos
In [15]:
# Encontrar adjetivos que describen a algún personaje.
print(obtener_adj_pers(texto_procesado, "winston"))
In [16]:
# <!-- collapse=True -->
def personaje_verbo(doc, verbo):
"""
Encontrar los personajes que utilizan determinado `verbo` en `doc`
:param doc: NLP documento parseado por Spacy
:param verbo: un objeto String
:return: lista de personajes que utilizan `verbo`
"""
contar_verbo = Counter()
for ent in doc.ents:
if ent.label_ == 'PERSON' and ent.root.head.lemma_ == verbo:
contar_verbo[ent.text] += 1
return contar_verbo.most_common(10)
In [17]:
# Encontrar personajes que utilizan determinado verbo
personaje_verbo(texto_procesado, "dijo")
Out[17]:
In [18]:
# Trabajando con las entidades del texto
# Una entidad nombrada es cualquier objeto del mundo real como una persona,
# ubicación, organización o producto con un nombre propio.
# tipos de entidades del texto
set(ent.label_ for ent in texto_procesado.ents)
Out[18]:
In [19]:
# Entidades nombradas de tipo ORG
[ent for ent in texto_procesado.ents if ent.label_ == 'ORG'][:10]
Out[19]:
In [20]:
# Partes de la oración (POS)
# En las partes de la oración se etiquetan las palabras de acuerdo a lo
# que significan segun su contexto. Algunas de estas etiquetas pueden
# ser: Adjetivos, verbos, adverbios, conjunciones, pronombres, sustantivos.
# Etiquetas del texto
set(token.pos_ for token in texto_procesado)
Out[20]:
In [21]:
# Etiquetas de tipo ADJ
[token.orth_ for token in texto_procesado if token.pos_ == 'ADJ'][1:11]
Out[21]:
In [22]:
# Etiquetas de tipo PROPN
[token.orth_ for token in texto_procesado if token.pos_ == 'PROPN'][1:11]
Out[22]:
Como demuestran estos ejemplos Textacy / Spacy son herramientas muy poderosas que nos pueden ayudar a analizar y obtener información valiosa de un texto en forma rápida y sencilla.
Durante mucho tiempo, las técnicas principales de Procesamiento del Lenguaje Natural fueron dominadas por métodos de aprendizaje automático que utilizaron modelos lineales como las máquinas de vectores de soporte o la regresión logística, entrenados sobre vectores de características de muy alta dimensional pero muy escasos. Recientemente, el campo ha tenido cierto éxito en el cambio hacia modelos de deep learning sobre entradas más densas.
Las redes neuronales proporcionan una poderosa maquina de aprendizaje que es muy atractiva para su uso en problemas de lenguaje natural. Un componente importante en las redes neuronales para el lenguaje es el uso de una capa de word embedding, una asignación de símbolos discretos a vectores continuos en un espacio dimensional relativamente bajo. Cuando se utiliza word embedding, se transforman los distintos símbolos en objetos matemáticos sobre los que se pueden realizar operaciones. En particular, la distancia entre vectores puede equipararse a la distancia entre palabras, facilitando la generalización del comportamiento de una palabra sobre otra. Esta representación de palabras como vectores es aprendida por la red como parte del proceso de entrenamiento. Subiendo en la jerarquía, la red también aprende a combinar los vectores de palabras de una manera que es útil para la predicción. Esta capacidad alivia en cierta medida los problemas de dispersión de los datos. Hay dos tipos principales de arquitecturas de redes neuronales que resultan muy útiles en los problemas de Procesamiento del Lenguaje Natural: las Redes neuronales prealimentadas y las Redes neuronales recurrentes.
Para ejemplificar, veamos como podemos utilizar word embedding utilizando el modelo word2vec
de Gensim.
In [23]:
# <!-- collapse=True -->
# importando gensim y TSNE para graficar
import gensim
from sklearn.manifold import TSNE
In [24]:
# transformando el texto para pasarlo al modelo de gensim
texto = [[str(palabra).lower() for palabra in sent if str(palabra) not in stop ]
for sent in sentencias]
# generando el diccionario
diccionario = gensim.corpora.Dictionary(texto)
In [25]:
# creando el modelo
modelo = gensim.models.Word2Vec(texto, workers=4, size=100,
min_count=5, window=10, sample=1e-3)
In [26]:
# representación de la palabra hermano como vector.
modelo['hermano']
Out[26]:
In [27]:
# como convertimos las palabras en vectores y los vectores capturan muchas
# regularidades lingüísticas, podemos aplicar operaciones vectoriales para
# extraer muchas propiedades interesantes.
# palabras similares a persona
modelo.most_similar_cosmul('persona')
Out[27]:
In [28]:
# por último podemos graficar el modelo
vocab = list(modelo.wv.vocab)
X = modelo[vocab]
# aplicamos TSNE
tsne = TSNE(n_components=2)
X_tsne = tsne.fit_transform(X)
# transformamos en DataFrame
df = pd.concat([pd.DataFrame(X_tsne),
pd.Series(vocab)],
axis=1)
df.columns = ['x', 'y', 'palabra']
In [29]:
# creamos el gráfico
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(1, 1, 1)
# reducimos el dataset a 15 palabras para el ejemplo
df_e = df.head(15)
ax.scatter(df_e['x'], df_e['y'])
for i, txt in enumerate(df_e['palabra']):
ax.annotate(txt, (df_e['x'].iloc[i], df_e['y'].iloc[i]))
plt.show()
Aquí termina este artículo, obviamente el Procesamiento del Lenguaje Natural es un campo muy amplio y quedaron muchas cosas sin desarrollar. Espero que esta introducción les haya sido de utilidad.
Saludos!
Este post fue escrito por Raúl e. López Briega utilizando Jupyter notebook. Pueden descargar este notebook o ver su version estática en nbviewer.