Parseo de dependencias¶

Un parseo de dependencias devuelve las dependencias que se dan entre los tokens de una oración. Estas dependencias suelen darse entre pares de tokens.

Aquí definimos un algoritmo basado en transiciones para parsear dependencias. Proponemos una serie de reglas simples para analizar de manera sencilla oraciones con estructura simple.

In [1]:
from nltk.stem import SnowballStemmer

#Genera un stemmer para simplificar los tokens
stemmer = SnowballStemmer('spanish')

Algoritmo¶

Definimos un parser que considera las relaciones entre sustantivos, verbos y determinantes. Se asumen las siguientes reglas:

  • Un determinante seguido de un sustantivo define una dependencia DET (determinante).
  • Un sustantivo seguido de un verbo define una dependencia NSUBJ (sujeto).
  • Un verbo seguid de un sustantantivo define una dependencia DOBJ (objeto directo)
In [2]:
#Categoría de tokens
Nouns = ['gat','perr', 'niñ', 'sop']
Verbs = ['corr', 'jueg', 'com', 'salt']
Dets = ['el', 'la', 'un', 'las', 'los', 'unos']

def parse(sentence):
    """
    Función para parseo de dependencias a partir de reglas simples basadas en 
    relaciones de sustantivos, verbos y determinantes.
    
    Arguments
    ---------
    sentence : list
        Lista de tokens para parsear.
    
    Returns
    -------
    dependencies : list
        Lista de dependencias entre pares de palabras.
    """
    #Inicializa el stack
    stack = ['root']
    #Guarda tokens en buffer
    buffer = sentence.split()
    #Guarda las dependencias
    dependencies = []
    
    #Agrega primer token del buffer a stack
    new_token = buffer.pop(0)
    stack.append(new_token) 
    
    #Criterio de finalización
    final = False
    while final == False:
        #Obtiene tokens en tope de stack
        w1 = stack[-1] 
        w2 = stack[-2]
        #Realiza stemming de los tokens
        s1 = stemmer.stem(w1)
        s2 = stemmer.stem(w2)
        
        #Regla LeftArrow para dependencia DET
        if s1 in Nouns and s2 in Dets:
            dependencies.append('{} <--DET {}'.format(w2,w1))
            stack.remove(w2)

        #Regla LeftArrow para dependencia NSUBJ
        elif s1 in Verbs and s2 in Nouns:
            dependencies.append('{} <--NSUBJ {}'.format(w2,w1))
            stack.remove(w2)
            
        #Regla RightArrow para dependencia OBJ
        elif s1 in Nouns and s2 in Verbs:
            dependencies.append('{} DOBJ--> {}'.format(w2,w1))
            stack.remove(w1)

        #Shift
        else:
            try:
                #Intenta obtener elemento del buffer
                new_token = buffer.pop(0)
                stack.append(new_token)
            except:
                #Si no lo logra detiene el algoritmo
                final = True

    return dependencies

Aplicamos el algoritmo a un ejemplo simple:

In [3]:
sentence = 'la niña come la sopa'

print(parse(sentence))
['la <--DET niña', 'niña <--NSUBJ come', 'la <--DET sopa', 'come DOBJ--> sopa']

Las dependencias que se dan son simples, y se pierden relaciones complejas; por ejemplo, oraciones como 'la sopa come la niña' asume que 'la sopa' es sujeto. Para lidiar con estos casos podría ampliarse las categorías de las palabras e incluir categorías como 'Sustantivo animado'.