Gramáticas Formales
Formalismos e implementaciones

Material para el seminario de la carrera de Letras de la UBA


Project maintained by fernandocar86 Hosted on GitHub Pages — Theme by mattgraham

Parsers de dependencias

Requerimientos

import re
import nltk
import spacy
from nltk import Tree
from spacy import displacy 
from nltk.parse import malt

No poseen distinción entre símbolos no terminales y terminales. Las estructuras representan relaciones de dependencia entre terminales. Ejemplos de parsers de dependencias:

Projective Dependency Parser NLTK

def dep_parser(sentence, grammar):         # define una función llamada dep_parser con dos argumentos
    sentence = sentence.lower()            # convierte a minúscula la oración
    if sentence.endswith('.'):             # si la oración termina con un punto
        sent = re.sub('\.','',sentence)    # se lo quita
    else:                                  # si no
        sent = sentence                    # la toma como está
    sent = sent.split()                    # divide la oración en palabras
    dep_gram = nltk.data.load(grammar, cache=False) # carga la gramática a nltk
    dep_gram = nltk.DependencyGrammar.fromstring(dep_gram) # parsea la gramática como gramática de dependencias
    pdp = nltk.ProjectiveDependencyParser(dep_gram) # aarga la gramática en el parser
    for tree in pdp.parse(sent):           # para cada árbol posible en mi gramática para esa oración
        print(tree)                        # lo imprime
        return(tree)
#Para correr el Proyective Dependency Parser

oracion1 = 'Pablo explotó el globo'    # Define la oración a analizar
grammar = 'gramaticas/DG1.txt'        # establece cuál va a ser mi gramática
dep_parser(oracion1, grammar)          # Para correr la función

Non-Projective Dependency Parser NLTK

def npdep_parser(sentence, grammar):                # define una función llamada dep_parser con dos argumentos
    sentence = sentence.lower()                     # convierte a minúscula la oración
    if sentence.endswith('.'):                      # si la oración termina con un punto
        sent = re.sub('\.',' ',sentence)            # se lo quita
    else:                                           # si no
        sent = sentence                             # la toma como está
    sent = sent.split()                             # divide la oración en palabras
    dep_gram = nltk.data.load(grammar, cache=False) # carga la gramática a nltk
    dep_gram = nltk.DependencyGrammar.fromstring(dep_gram) # parsea la gramática como gramática de dependencias
    pdp = nltk.NonprojectiveDependencyParser(dep_gram) # carga la gramática en el parser
    for tree in pdp.parse(sent):
        print(tree.tree().draw())
    return tree.tree()
#Para correr el Nonproyective Dependency Parser

#oracion2 = 'quién fuma el cigarrillo'  # Define la oración a analizar
#oracion2 = 'quién dijo fede que fuma'  # Define la oración a analizar
oracion2 = 'qué dijo fede que fuma'  # Define la oración a analizar
# Habría que arreglar la función npdep_parser para que pueda tomar estas dos últimas oraciones
grammar2 = 'gramaticas/DG2.txt'       # establece cuál va a ser mi gramática
npdep_parser(oracion2, grammar2)        # Para correr la función

PystanfordDependencies

import stanza
#stanza.download('es') # Baja el modelo para el españo
nlp = stanza.Pipeline('es') # Inicializa el modelo de español (con su pipeline de anotación)
nlp
doc = nlp("Pablo Neruda escribe poemas en Capri") # Anota una oración
doc
print(doc.entities)
doc.sentences[0].print_dependencies()
doc.sentences[0].dependencies
doc = nlp("el poeta chileno escribe poemas.")
doc
doc.entities
doc.sentences[0].print_dependencies()
def extraer_entidades(stanza_oracion):
    entidades = [] # Creo una lista vacía donde voy a guardar todas las entidades que encuentre en la oración
    for dependencia in stanza_oracion.dependencies: # Recorro las dependencias
        regidor, relacion, dependiente = dependencia # Las dependencias son tuplas de tres elementos que puedo separar
                                                     # en variables
        if regidor.deprel == "nsubj": # Asumimos que queremos encontrar los sujetos, pero podríamos comparar por PoS
                                      # ¿Cómo?
            entidad = [regidor]       # Creo una lista cuyo miembro inicial es el nucleo de la construccion
            for palabra in stanza_oracion.words: # Vuelvo a recorrer las palabras de la oración para encontrar
                                                 # todos los dependientes del sujeto, sean anteriores o posteriores
                if palabra.head == int(regidor.id): # Si el nucleo/regidor/head de una palabra coincide con el id
                    entidad.append(palabra)         # del núcleo de mi construcción, lo sumo a la lista de la entidad
            entidad = sorted(entidad, key=lambda x: x.id) # Ordeno la lista resultante por su número de id para 
                                                          # mantener la linealidad del texto.
            if entidad not in entidades:            # Chequeo que la entidad ya no exista en mi lista porque
                entidades.append(entidad)           # el regidor puede aparecer en más de una dependencia.
            
    return [" ".join([palabra.text for palabra in entidad]) for entidad in entidades] # Me quedo únicamente con el 
                                                       # texto de cada objeto Word y lo devuelvo unido por entidad
extraer_entidades(doc.sentences[0])
doc.sentences[0].words[0].head

FreeLing

FreeLing posee su propio server gratuito que disponibiliza el pipeline de la librería. También se puede descargar y correr como un programa local e incluso cuenta con una implementación en Python. Hoy vamos a probar FreeLing en su demo visual.

Si hay dudas con el uso de tags de PoS, se pueden revisar rápudamente en esta página: Descripción del Tagset

# Sugerencias para probar:
# A las 4 de la tarde tomo leche y como tostadas.
# No tengo más sueño.
# ¿A quién dijiste que viste el sábado?
# Dije que vi a mi tía.
# Pablo Neruda escribe poemas en Capri.
# pablo neruda escribe poemas en capri.

Malt Parser

A continuación:

# descarga maltparser desde la url indicada en una carpeta llamada maltparser-1.9.2 (la crea)
! wget -qO- http://maltparser.org/dist/maltparser-1.9.2.tar.gz | tar -xvz > /dev/null

# descarga el modelo engmalt.poly-1.7 en la carpeta maltparser-1.9.2
! wget -P maltparser-1.9.2 http://www.maltparser.org/mco/english_parser/engmalt.poly-1.7.mco
import os

here = os.path.abspath('.')
print('here es: ', here)
maltparser_folder = 'maltparser-1.9.2'
print(os.getenv('MALT_PARSER'))
os.environ['MALT_PARSER'] = os.path.join(here, maltparser_folder, '')
os.environ['MALT_MODEL'] = os.path.join(here, maltparser_folder, 'engmalt.poly-1.7.mco')
print(os.getenv('MALT_PARSER'))
! echo $MALT_PARSER
maltParser = nltk.parse.malt.MaltParser(os.getenv('MALT_PARSER'), os.getenv('MALT_MODEL'))
oracion_malt = 'John saw Mary with her new dress'
stemma = maltParser.parse_one(oracion_malt.split()).tree()
print(stemma)

Spacy - Dependency parser

Nota para quien no tenga la MV:

Antes de correr hay que instalar spacy. Con pip3, eso se puede hacer con el comando

pip3 install spacy

Hay que instalar también es_core_news_sm, un modelo entrenado mediante un corpus del español, con el comando

python3 -m spacy download es_core_news_sm

Alternativamente puede probarse de instalar es_core_news_md.

python3 -m spacy download es_core_news_md

En ese caso, para correrlo hay que cambiar en el código de abajo es_core_news_sm por es_core_news_md.

# Carga el modelo
nlp = spacy.load('es_core_news_sm')

# Procesa la oración
sent = "¿quiénes dijo Juan que estornudaron?"
doc = nlp(sent)

#options = {"compact": True, "bg": "#09a3d5","color": "white", "font": "Source Sans Pro"}
# Visualización
displacy.render(doc, style="dep", jupyter=True)
def gramaticadependencias(sentence):                # Define la función
    nlp = spacy.load('es_core_news_sm')             # Carga el modelo entrenado
    doc = nlp(sentence)                             # Procesa la oración con el modelo
    displacy.render(doc, style='dep', jupyter=True) # Visualiza
oracion_spacy = input('Escribí una oración\n')
gramaticadependencias(oracion_spacy)
nlp = spacy.load('es_core_news_sm')
doc = nlp(oracion_spacy)
root = [token for token in doc if token.head == token][0]
print('| {0:10}| {1:5}| {2:7}| {3:7}| {4:30} |'.format('TEXTO','DEP','N_IZQ','N_DER','ANCESTROS'))
print('|'+'='*69+'|')
for descendant in root.subtree:
    print('| {0:10}| {1:5}| {2:7}| {3:7}| {4:30} |'.format(
        descendant.text,
        descendant.dep_,
        descendant.n_lefts,
        descendant.n_rights,
        str([ancestor.text for ancestor in descendant.ancestors])
    )
)